diff --git a/.drone.yml b/.drone.yml index 749ab62c5..6b9e0425b 100644 --- a/.drone.yml +++ b/.drone.yml @@ -13,4 +13,4 @@ steps: image: akkadius/eqemu-server:latest commands: - sudo chown eqemu:eqemu /drone/src/ * -R - - git submodule init && git submodule update && mkdir -p build && cd build && cmake -DEQEMU_BUILD_LOGIN=ON -DEQEMU_BUILD_BOTS=ON -DEQEMU_BUILD_LUA=ON -G 'Unix Makefiles' .. && make -j$((`nproc`-4)) \ No newline at end of file + - git submodule init && git submodule update && mkdir -p build && cd build && cmake -DEQEMU_BUILD_LOGIN=ON -DEQEMU_ENABLE_BOTS=ON -DEQEMU_BUILD_LUA=ON -G 'Unix Makefiles' .. && make -j$((`nproc`-4)) diff --git a/.gitignore b/.gitignore index 4f64e32a6..be8fccbc3 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,4 @@ bin/ /x64 /client_files/**/CMakeFiles/ submodules/libuv +.idea \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 526d68099..407b1a221 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 3.2) +CMAKE_MINIMUM_REQUIRED(VERSION 3.7) SET(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/" ${CMAKE_MODULE_PATH}) @@ -131,6 +131,47 @@ OPTION(EQEMU_BUILD_TESTS "Build utility tests." OFF) OPTION(EQEMU_BUILD_CLIENT_FILES "Build Client Import/Export Data Programs." ON) OPTION(EQEMU_PREFER_LUA "Build with normal Lua even if LuaJIT is found." OFF) +#PRNG options +OPTION(EQEMU_ADDITIVE_LFIB_PRNG "Use Additive LFib for PRNG." OFF) +MARK_AS_ADVANCED(EQEMU_ADDITIVE_LFIB_PRNG) +OPTION(EQEMU_BIASED_INT_DIST "Use biased int dist instead of uniform." OFF) +MARK_AS_ADVANCED(EQEMU_BIASED_INT_DIST) +SET(EQEMU_CUSTOM_PRNG_ENGINE "" CACHE STRING "Custom random engine. (ex. std::default_random_engine)") +MARK_AS_ADVANCED(EQEMU_CUSTOM_PRNG_ENGINE) + +IF(CMAKE_COMPILER_IS_GNUCXX) + OPTION(EQEMU_SFMT19937 "Use GCC's extention for SIMD Fast MT19937." OFF) + MARK_AS_ADVANCED(EQEMU_SFMT19937) +ENDIF() + +IF(EQEMU_ADDITIVE_LFIB_PRNG) + ADD_DEFINITIONS(-DUSE_ADDITIVE_LFIB_PRNG) + IF(EQEMU_SFMT19937) + MESSAGE(STATUS "SFMT19937 and ADDITITVE_LFIB_PRNG both set, SFMT19937 ignored.") + SET(EQEMU_SFMT19937 OFF) + ENDIF() + IF(NOT EQEMU_CUSTOM_PRNG_ENGINE STREQUAL "") + MESSAGE(STATUS "CUSTOM_PRNG_ENGINE and ADDITITVE_LFIB_PRNG both set, CUSTOM_PRNG_ENGINE ignored.") + SET(EQEMU_CUSTOM_PRNG_ENGINE "") + ENDIF() +ENDIF() + +IF(EQEMU_SFMT19937) + ADD_DEFINITIONS(-DUSE_SFMT19937) + IF(NOT EQEMU_CUSTOM_PRNG_ENGINE STREQUAL "") + MESSAGE(STATUS "CUSTOM_PRNG_ENGINE and SFMT19937 both set, CUSTOM_PRNG_ENGINE ignored.") + SET(EQEMU_CUSTOM_PRNG_ENGINE "") + ENDIF() +ENDIF() + +IF(NOT EQEMU_CUSTOM_PRNG_ENGINE STREQUAL "") + ADD_DEFINITIONS(-DUSE_CUSTOM_PRNG_ENGINE=${EQEMU_CUSTOM_PRNG_ENGINE}) +ENDIF() + +IF(EQEMU_BIASED_INT_DIST) + ADD_DEFINITIONS(-DBIASED_INT_DIST) +ENDIF() + IF(EQEMU_COMMANDS_LOGGING) ADD_DEFINITIONS(-DCOMMANDS_LOGGING) ENDIF(EQEMU_COMMANDS_LOGGING) @@ -183,6 +224,9 @@ IF(OpenSSL_FOUND AND MBEDTLS_FOUND) SET(TLS_LIBRARY_LIBS ${OPENSSL_LIBRARIES}) SET(TLS_LIBRARY_INCLUDE ${OPENSSL_INCLUDE_DIR}) ADD_DEFINITIONS(-DEQEMU_USE_OPENSSL) + IF(${OPENSSL_VERSION} VERSION_GREATER_EQUAL "1.1.1") + ADD_DEFINITIONS(-DCPPHTTPLIB_OPENSSL_SUPPORT) + ENDIF() ELSEIF(TLS_LIBRARY_SELECTION STREQUAL "mbedTLS") SET(TLS_LIBRARY_TYPE " mbedTLS") SET(TLS_LIBRARY_ENABLED ON) @@ -198,6 +242,9 @@ ELSEIF(OpenSSL_FOUND) SET(TLS_LIBRARY_LIBS ${OPENSSL_LIBRARIES}) SET(TLS_LIBRARY_INCLUDE ${OPENSSL_INCLUDE_DIR}) ADD_DEFINITIONS(-DEQEMU_USE_OPENSSL) + IF(${OPENSSL_VERSION} VERSION_GREATER_EQUAL "1.1.1") + ADD_DEFINITIONS(-DCPPHTTPLIB_OPENSSL_SUPPORT) + ENDIF() ELSEIF(MBEDTLS_FOUND) SET(TLS_LIBRARY_TYPE " mbedTLS") SET(TLS_LIBRARY_ENABLED ON) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..22dc230bf --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,22 @@ +We expect contributors and community members to act professionally and respectfully, and we expect our forums and Discord channels to be dignified environments that expand the community and enhance the learning experience for new members. + +Specifically: + +* Respect people, their ideas, and their work. +* Be kind. Be courteous. Be welcoming. +* Listen. Consider and acknowledge people's points before responding. +* Be respectful of differing viewpoints and experience levels. +* Accept constructive criticism and work together toward decisions. +* Focus on what is best for the community and users. + +Examples of unacceptable behavior by participants include: + +* The use of violent threats, trolling, insulting/derogatory comments, abusive or discriminatory language, or personal attacks. +* Public or private harassment. +* Publishing others' private information, such as a physical or electronic address, without explicit permission. +* Conduct which could reasonably be considered inappropriate in a professional setting. +* Advocating for or encouraging any of the above behaviors. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, forum posts, Discord messages, and other contributions that are not aligned with this code of conduct. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project. diff --git a/README.md b/README.md index fea2c23a4..225c246ab 100644 --- a/README.md +++ b/README.md @@ -18,16 +18,16 @@ |**Install Count**|![Windows Install Count](http://analytics.akkadius.com/?install_count&windows_count)|![Linux Install Count](http://analytics.akkadius.com/?install_count&linux_count)| ### > Windows -* [Install Guide](https://eqemu.gitbook.io/server/categories/installation/server-installation-windows) +* [Install Guide](https://docs.eqemu.io/server/installation/server-installation-windows/) ### > Debian/Ubuntu/CentOS/Fedora -* [Install Guide](https://eqemu.gitbook.io/server/categories/installation/server-installation-linux) +* [Install Guide](https://docs.eqemu.io/server/installation/server-installation-linux/) * You can use curl or wget to kick off the installer (whichever your OS has) > curl -O https://raw.githubusercontent.com/EQEmu/Server/master/utils/scripts/linux_installer/install.sh install.sh && chmod 755 install.sh && ./install.sh -> wget --no-check-certificate https://raw.githubusercontent.com/EQEmu/Server/master/utils/scripts/linux_installer/install.sh -O install.sh && chmod 755 install.sh && ./install.sh +> wget --no-check-certificate https://raw.githubusercontent.com/EQEmu/Server/master/utils/scripts/linux_installer/install.sh -O install.sh && chmod 755 install.sh && ./install.sh ## Supported Clients @@ -56,7 +56,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://eqemu.gitbook.io/) +- [EQEmulator Wiki](https://docs.eqemu.io/) ## Related Repositories * [ProjectEQ Quests](https://github.com/ProjectEQ/projecteqquests) diff --git a/changelog.txt b/changelog.txt index 955a0a2e8..2e26a697d 100644 --- a/changelog.txt +++ b/changelog.txt @@ -3,7 +3,7 @@ ############################################ # # New changelog can be found here -# https://eqemu.gitbook.io/changelog/ +# https://docs.eqemu.io/server/changelog/server # ############################################ # Deprecated diff --git a/client_files/export/main.cpp b/client_files/export/main.cpp index fd8b19026..ff7f1f3dd 100644 --- a/client_files/export/main.cpp +++ b/client_files/export/main.cpp @@ -83,8 +83,9 @@ int main(int argc, char **argv) content_db.SetMysql(database.getMySQL()); } - database.LoadLogSettings(LogSys.log_settings); - LogSys.StartFileLogs(); + LogSys.SetDatabase(&database) + ->LoadLogDatabaseSettings() + ->StartFileLogs(); std::string arg_1; diff --git a/client_files/import/main.cpp b/client_files/import/main.cpp index 64b2aee41..2fbdf421a 100644 --- a/client_files/import/main.cpp +++ b/client_files/import/main.cpp @@ -80,8 +80,9 @@ int main(int argc, char **argv) { content_db.SetMysql(database.getMySQL()); } - database.LoadLogSettings(LogSys.log_settings); - LogSys.StartFileLogs(); + LogSys.SetDatabase(&database) + ->LoadLogDatabaseSettings() + ->StartFileLogs(); ImportSpells(&content_db); ImportSkillCaps(&content_db); diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 67cf4433c..e9de82316 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -16,6 +16,7 @@ SET(common_sources database_instances.cpp dbcore.cpp deity.cpp + dynamic_zone_base.cpp emu_constants.cpp emu_limits.cpp emu_opcodes.cpp @@ -68,7 +69,9 @@ SET(common_sources rulesys.cpp say_link.cpp serialize_buffer.cpp + server_event_scheduler.cpp serverinfo.cpp + shared_tasks.cpp shareddb.cpp skills.cpp spdat.cpp @@ -151,6 +154,7 @@ SET(repositories repositories/base/base_character_disciplines_repository.h repositories/base/base_character_expedition_lockouts_repository.h repositories/base/base_character_inspect_messages_repository.h + repositories/base/base_character_instance_safereturns_repository.h repositories/base/base_character_item_recast_repository.h repositories/base/base_character_languages_repository.h repositories/base/base_character_leadership_abilities_repository.h @@ -162,11 +166,15 @@ SET(repositories repositories/base/base_character_potionbelt_repository.h repositories/base/base_character_skills_repository.h repositories/base/base_character_spells_repository.h + repositories/base/base_character_task_timers_repository.h repositories/base/base_character_tasks_repository.h repositories/base/base_char_create_combinations_repository.h repositories/base/base_char_create_point_allocations_repository.h repositories/base/base_char_recipe_list_repository.h repositories/base/base_completed_tasks_repository.h + repositories/base/base_completed_shared_tasks_repository.h + repositories/base/base_completed_shared_task_members_repository.h + repositories/base/base_completed_shared_task_activity_state_repository.h repositories/base/base_content_flags_repository.h repositories/base/base_damageshieldtypes_repository.h repositories/base/base_data_buckets_repository.h @@ -174,10 +182,10 @@ SET(repositories repositories/base/base_discovered_items_repository.h repositories/base/base_doors_repository.h repositories/base/base_dynamic_zones_repository.h + repositories/base/base_dynamic_zone_members_repository.h repositories/base/base_eventlog_repository.h repositories/base/base_expeditions_repository.h repositories/base/base_expedition_lockouts_repository.h - repositories/base/base_expedition_members_repository.h repositories/base/base_faction_base_data_repository.h repositories/base/base_faction_list_repository.h repositories/base/base_faction_list_mod_repository.h @@ -251,6 +259,11 @@ SET(repositories repositories/base/base_rule_sets_repository.h repositories/base/base_rule_values_repository.h repositories/base/base_saylink_repository.h + repositories/base/base_server_scheduled_events_repository.h + repositories/base/base_shared_tasks_repository.h + repositories/base/base_shared_task_activity_state_repository.h + repositories/base/base_shared_task_dynamic_zones_repository.h + repositories/base/base_shared_task_members_repository.h repositories/base/base_skill_caps_repository.h repositories/base/base_spawn2_repository.h repositories/base/base_spawnentry_repository.h @@ -314,6 +327,7 @@ SET(repositories repositories/character_disciplines_repository.h repositories/character_expedition_lockouts_repository.h repositories/character_inspect_messages_repository.h + repositories/character_instance_safereturns_repository.h repositories/character_item_recast_repository.h repositories/character_languages_repository.h repositories/character_leadership_abilities_repository.h @@ -325,11 +339,15 @@ SET(repositories repositories/character_potionbelt_repository.h repositories/character_skills_repository.h repositories/character_spells_repository.h + repositories/character_task_timers_repository.h repositories/character_tasks_repository.h repositories/char_create_combinations_repository.h repositories/char_create_point_allocations_repository.h repositories/char_recipe_list_repository.h repositories/completed_tasks_repository.h + repositories/completed_shared_tasks_repository.h + repositories/completed_shared_task_members_repository.h + repositories/completed_shared_task_activity_state_repository.h repositories/content_flags_repository.h repositories/damageshieldtypes_repository.h repositories/data_buckets_repository.h @@ -337,10 +355,10 @@ SET(repositories repositories/discovered_items_repository.h repositories/doors_repository.h repositories/dynamic_zones_repository.h + repositories/dynamic_zone_members_repository.h repositories/eventlog_repository.h repositories/expeditions_repository.h repositories/expedition_lockouts_repository.h - repositories/expedition_members_repository.h repositories/faction_base_data_repository.h repositories/faction_list_repository.h repositories/faction_list_mod_repository.h @@ -414,6 +432,11 @@ SET(repositories repositories/rule_sets_repository.h repositories/rule_values_repository.h repositories/saylink_repository.h + repositories/server_scheduled_events_repository.h + repositories/shared_tasks_repository.h + repositories/shared_task_activity_state_repository.h + repositories/shared_task_dynamic_zones_repository.h + repositories/shared_task_members_repository.h repositories/skill_caps_repository.h repositories/spawn2_repository.h repositories/spawnentry_repository.h @@ -446,6 +469,7 @@ SET(repositories ) SET(common_headers + additive_lagged_fibonacci_engine.h any.h base_packet.h base_data.h @@ -460,12 +484,14 @@ SET(common_headers cli/argh.h cli/eqemu_command_handler.h cli/terminal_color.hpp + cron/croncpp.h database/database_dump_service.h data_verification.h database.h database_schema.h dbcore.h deity.h + dynamic_zone_base.h emu_constants.h emu_limits.h emu_opcodes.h @@ -500,6 +526,7 @@ SET(common_headers guild_base.h guilds.h http/httplib.h + http/uri.h inventory_profile.h inventory_slot.h ipc_mutex.h @@ -541,13 +568,16 @@ SET(common_headers say_link.h seperator.h serialize_buffer.h + server_event_scheduler.h serverinfo.h servertalk.h + shared_tasks.h shareddb.h skills.h spdat.h string_util.h struct_strategy.h + tasks.h textures.h timer.h types.h @@ -609,7 +639,8 @@ SET(common_headers StackWalker/StackWalker.h util/memory_stream.h util/directory.h - util/uuid.h) + util/uuid.h +) SOURCE_GROUP(Event FILES event/event_loop.h diff --git a/common/additive_lagged_fibonacci_engine.h b/common/additive_lagged_fibonacci_engine.h new file mode 100644 index 000000000..b396b4436 --- /dev/null +++ b/common/additive_lagged_fibonacci_engine.h @@ -0,0 +1,147 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2021 EQEMu Development Team (http://eqemulator.net) + + 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 +*/ + +#pragma once + +#include +#include +#include +#include +#include + +/* + * This is an additive lagged fibonacci generator as seen in The Art of Computer Programming, Vol. 2 + * This should roughly match the implementation that EQ's client uses and be compatible with our Random class + * + * EQ's rand looks like it was from an example implementation that as posted on pscode.com + * + * You might also want to consider defining BIASED_INT_DIST as well to more closely match EQ + */ + +namespace EQ { + template + class additive_lagged_fibonacci_engine { + static_assert(std::is_unsigned::value, "result_type must be an unsigned integral type"); + static_assert(0u < j && j < k, "0 < j < k"); + static_assert(0u < w && w <= std::numeric_limits::digits, + "template argument substituting w out of bounds"); + + public: + using result_type = UIntType; + static constexpr size_t word_size = w; + static constexpr size_t short_lag = j; + static constexpr size_t long_lag = k; + static constexpr result_type default_seed = 19780503u; // default for subtract_with_carry_engine + + additive_lagged_fibonacci_engine() : additive_lagged_fibonacci_engine(default_seed) {} + + explicit additive_lagged_fibonacci_engine(result_type sd) { seed(sd); } + + void seed(result_type seed = default_seed) + { + state1 = long_lag - long_lag; + state2 = long_lag - short_lag; + state[0] = static_cast(seed) & ((1u << word_size) - 1); + state[1] = 1; + for (int i = 2; i < long_lag; ++i) + state[i] = (state[i - 1] + state[i - 2]) & ((1u << word_size) - 1); + return; + } + // TODO: seed via seed_seq + + static constexpr result_type min() { return 0; } + static constexpr result_type max() { return ((1u << word_size) - 1) >> 6; } + + void discard(unsigned long long z) { + for (; z != 0ULL; --z) + (*this)(); + } + + result_type operator()() { + result_type rand = (state[state1] + state[state2]) & ((1u << word_size) - 1); + state[state1] = rand; + if (++state1 == long_lag) + state1 = 0; + if (++state2 == long_lag) + state2 = 0; + + return rand >> 6; + } + + private: + result_type state1; + result_type state2; + result_type state[long_lag]; + + public: + template + friend bool operator==(const additive_lagged_fibonacci_engine &x, + const additive_lagged_fibonacci_engine &y) + { + return std::equal(x.state, x.state + long_lag, y.state) && x.state1 == y.state1 && + x.state2 == y.state2; + } + + template + friend bool operator!=(const additive_lagged_fibonacci_engine &x, + const additive_lagged_fibonacci_engine &y) + { return !(x == y); } + + template + friend std::basic_ostream & + operator<<(std::basic_istream &os, additive_lagged_fibonacci_engine &x) + { + using ios_base = typename std::basic_istream::ios_base; + + const typename ios_base::fmtflags flags = os.flags(); + const CharT fill = os.fill(); + const CharT space = os.widen(' '); + os.flags(ios_base::dec | ios_base::fixed | ios_base::left); + os.fill(space); + + for (size_t i = 0; i < long_lag; ++i) + os << x.state[i] << space; + os << x.state1 << space << x.state2; + + os.flags(flags); + os.fill(fill); + return os; + } + + template + friend std::basic_istream & + operator>>(std::basic_istream &is, additive_lagged_fibonacci_engine &x) + { + using ios_base = typename std::basic_istream::ios_base; + + const typename ios_base::fmtflags flags = is.flags(); + is.flags(ios_base::dec | ios_base::skipws); + + for (size_t i = 0; i < long_lag; ++i) + is >> x.state[i]; + is >> x.state1; + is >> x.state2; + + is.flags(flags); + return is; + } + }; + + using EQRand = additive_lagged_fibonacci_engine; +}; + diff --git a/common/bodytypes.h b/common/bodytypes.h index fd64b1825..0528e14f6 100644 --- a/common/bodytypes.h +++ b/common/bodytypes.h @@ -27,16 +27,17 @@ typedef enum { BT_Extraplanar = 6, BT_Magical = 7, //this name might be a bit off, BT_SummonedUndead = 8, - BT_RaidGiant = 9, - // ... + BT_RaidGiant = 9, //Velious era Raid Giant + BT_RaidColdain = 10, //Velious era Raid Coldain BT_NoTarget = 11, //no name, can't target this bodytype BT_Vampire = 12, BT_Atenha_Ra = 13, BT_Greater_Akheva = 14, BT_Khati_Sha = 15, - BT_Seru = 16, //not confirmed.... + BT_Seru = 16, + BT_Grieg_Veneficus = 17, BT_Draz_Nurakk = 18, - BT_Zek = 19, + BT_Zek = 19, //"creatures from the Plane of War." BT_Luggald = 20, BT_Animal = 21, BT_Insect = 22, @@ -46,17 +47,18 @@ typedef enum { BT_Dragon = 26, BT_Summoned2 = 27, BT_Summoned3 = 28, - //29 + BT_Dragon2 = 29, //database data indicates this is a dragon type (kunark and DoN?) BT_VeliousDragon = 30, //might not be a tight set - // ... + BT_Familiar = 31, BT_Dragon3 = 32, BT_Boxes = 33, BT_Muramite = 34, //tribal dudes // ... BT_NoTarget2 = 60, // ... - BT_SwarmPet = 63, //is this valid, or made up? - // ... + BT_SwarmPet = 63, //Looks like weapon proc related temp pets and few misc pets, should not be used for checking swarm pets in general. + BT_MonsterSummon = 64, + // 65, trap or effect related? BT_InvisMan = 66, //no name, seen on 'InvisMan', can be /targeted BT_Special = 67 } bodyType; diff --git a/common/content/world_content_service.cpp b/common/content/world_content_service.cpp index 949ebcc0d..c869d486a 100644 --- a/common/content/world_content_service.cpp +++ b/common/content/world_content_service.cpp @@ -22,6 +22,7 @@ #include "../database.h" #include "../rulesys.h" #include "../eqemu_logsys.h" +#include "../repositories/content_flags_repository.h" WorldContentService::WorldContentService() @@ -34,8 +35,12 @@ int WorldContentService::GetCurrentExpansion() const return current_expansion; } -void WorldContentService::SetExpansionContext() +WorldContentService *WorldContentService::SetExpansionContext() { + // do a rule manager reload until where we store expansion is changed to somewhere else + RuleManager::Instance()->LoadRules(GetDatabase(), "default", true); + + // pull expansion from rules int expansion = RuleI(Expansion, CurrentExpansion); if (expansion >= Expansion::Classic && expansion <= Expansion::MaxId) { content_service.SetCurrentExpansion(expansion); @@ -46,6 +51,8 @@ void WorldContentService::SetExpansionContext() GetCurrentExpansion(), GetCurrentExpansionName() ); + + return this; } std::string WorldContentService::GetCurrentExpansionName() @@ -72,15 +79,47 @@ void WorldContentService::SetCurrentExpansion(int current_expansion) /** * @return */ -const std::vector &WorldContentService::GetContentFlags() const +const std::vector &WorldContentService::GetContentFlags() const { return content_flags; } +/** + * @return + */ +std::vector WorldContentService::GetContentFlagsEnabled() +{ + std::vector enabled_flags; + + for (auto &f: GetContentFlags()) { + if (f.enabled) { + enabled_flags.emplace_back(f.flag_name); + } + } + + return enabled_flags; +} + +/** + * @return + */ +std::vector WorldContentService::GetContentFlagsDisabled() +{ + std::vector disabled_flags; + + for (auto &f: GetContentFlags()) { + if (!f.enabled) { + disabled_flags.emplace_back(f.flag_name); + } + } + + return disabled_flags; +} + /** * @param content_flags */ -void WorldContentService::SetContentFlags(std::vector content_flags) +void WorldContentService::SetContentFlags(std::vector content_flags) { WorldContentService::content_flags = content_flags; } @@ -89,13 +128,69 @@ void WorldContentService::SetContentFlags(std::vector content_flags * @param content_flag * @return */ -bool WorldContentService::IsContentFlagEnabled(const std::string& content_flag) +bool WorldContentService::IsContentFlagEnabled(const std::string &content_flag) { - for (auto &flag : GetContentFlags()) { - if (flag == content_flag) { + for (auto &f: GetContentFlags()) { + if (f.flag_name == content_flag && f.enabled == true) { return true; } } return false; } + +void WorldContentService::ReloadContentFlags() +{ + std::vector set_content_flags; + auto flags = ContentFlagsRepository::All(*GetDatabase()); + + set_content_flags.reserve(flags.size()); + for (auto &f: flags) { + set_content_flags.push_back(f); + + LogInfo( + "Loaded content flag [{}] [{}]", + f.flag_name, + (f.enabled ? "Enabled" : "Disabled") + ); + } + + SetContentFlags(set_content_flags); +} + +Database *WorldContentService::GetDatabase() const +{ + return m_database; +} + +WorldContentService *WorldContentService::SetDatabase(Database *database) +{ + WorldContentService::m_database = database; + + return this; +} + +void WorldContentService::SetContentFlag(const std::string &content_flag_name, bool enabled) +{ + auto flags = ContentFlagsRepository::GetWhere( + *GetDatabase(), + fmt::format("flag_name = '{}'", content_flag_name) + ); + + auto f = ContentFlagsRepository::NewEntity(); + if (!flags.empty()) { + f = flags.front(); + } + + f.enabled = enabled ? 1 : 0; + f.flag_name = content_flag_name; + + if (!flags.empty()) { + ContentFlagsRepository::UpdateOne(*GetDatabase(), f); + } + else { + ContentFlagsRepository::InsertOne(*GetDatabase(), f); + } + + ReloadContentFlags(); +} diff --git a/common/content/world_content_service.h b/common/content/world_content_service.h index 1edc91160..512f2c92c 100644 --- a/common/content/world_content_service.h +++ b/common/content/world_content_service.h @@ -23,6 +23,9 @@ #include #include +#include "../repositories/content_flags_repository.h" + +class Database; namespace Expansion { static const int EXPANSION_ALL = -1; @@ -158,14 +161,25 @@ public: bool IsCurrentExpansionTheBurningLands() { return current_expansion == Expansion::ExpansionNumber::TheBurningLands; } bool IsCurrentExpansionTormentOfVelious() { return current_expansion == Expansion::ExpansionNumber::TormentOfVelious; } + const std::vector &GetContentFlags() const; + std::vector GetContentFlagsEnabled(); + std::vector GetContentFlagsDisabled(); + bool IsContentFlagEnabled(const std::string& content_flag); + void SetContentFlags(std::vector content_flags); + void ReloadContentFlags(); + WorldContentService * SetExpansionContext(); + + WorldContentService * SetDatabase(Database *database); + Database *GetDatabase() const; + + void SetContentFlag(const std::string &content_flag_name, bool enabled); + private: int current_expansion{}; - std::vector content_flags; -public: - const std::vector &GetContentFlags() const; - bool IsContentFlagEnabled(const std::string& content_flag); - void SetContentFlags(std::vector content_flags); - void SetExpansionContext(); + std::vector content_flags; + + // reference to database + Database *m_database; }; extern WorldContentService content_service; diff --git a/common/cron/croncpp.h b/common/cron/croncpp.h new file mode 100644 index 000000000..5a22a7f72 --- /dev/null +++ b/common/cron/croncpp.h @@ -0,0 +1,876 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#if __cplusplus > 201402L +#include +#define CRONCPP_IS_CPP17 +#endif + +namespace cron +{ +#ifdef CRONCPP_IS_CPP17 + #define HAS_STRING_VIEW + #define STRING_VIEW std::string_view + #define STRING_VIEW_NPOS std::string_view::npos + #define CONSTEXPTR constexpr +#else +#define STRING_VIEW std::string const & +#define STRING_VIEW_NPOS std::string::npos +#define CONSTEXPTR +#endif + + using cron_int = uint8_t; + + constexpr std::time_t INVALID_TIME = static_cast(-1); + + constexpr size_t INVALID_CRON_INDEX = static_cast(-1); + + class cronexpr; + + namespace detail + { + enum class cron_field + { + second, + minute, + hour_of_day, + day_of_week, + day_of_month, + month, + year + }; + + template + static bool find_next(cronexpr const & cex, + std::tm& date, + size_t const dot); + } + + struct bad_cronexpr : public std::runtime_error + { + public: + explicit bad_cronexpr(STRING_VIEW message) : + std::runtime_error(message.data()) + {} + }; + + + struct cron_standard_traits + { + static const cron_int CRON_MIN_SECONDS = 0; + static const cron_int CRON_MAX_SECONDS = 59; + + static const cron_int CRON_MIN_MINUTES = 0; + static const cron_int CRON_MAX_MINUTES = 59; + + static const cron_int CRON_MIN_HOURS = 0; + static const cron_int CRON_MAX_HOURS = 23; + + static const cron_int CRON_MIN_DAYS_OF_WEEK = 0; + static const cron_int CRON_MAX_DAYS_OF_WEEK = 6; + + static const cron_int CRON_MIN_DAYS_OF_MONTH = 1; + static const cron_int CRON_MAX_DAYS_OF_MONTH = 31; + + static const cron_int CRON_MIN_MONTHS = 1; + static const cron_int CRON_MAX_MONTHS = 12; + + static const cron_int CRON_MAX_YEARS_DIFF = 4; + +#ifdef CRONCPP_IS_CPP17 + static const inline std::vector DAYS = { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; + static const inline std::vector MONTHS = { "NIL", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; +#else + static std::vector& DAYS() + { + static std::vector days = { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; + return days; + } + + static std::vector& MONTHS() + { + static std::vector months = { "NIL", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; + return months; + } +#endif + }; + + struct cron_oracle_traits + { + static const cron_int CRON_MIN_SECONDS = 0; + static const cron_int CRON_MAX_SECONDS = 59; + + static const cron_int CRON_MIN_MINUTES = 0; + static const cron_int CRON_MAX_MINUTES = 59; + + static const cron_int CRON_MIN_HOURS = 0; + static const cron_int CRON_MAX_HOURS = 23; + + static const cron_int CRON_MIN_DAYS_OF_WEEK = 1; + static const cron_int CRON_MAX_DAYS_OF_WEEK = 7; + + static const cron_int CRON_MIN_DAYS_OF_MONTH = 1; + static const cron_int CRON_MAX_DAYS_OF_MONTH = 31; + + static const cron_int CRON_MIN_MONTHS = 0; + static const cron_int CRON_MAX_MONTHS = 11; + + static const cron_int CRON_MAX_YEARS_DIFF = 4; + +#ifdef CRONCPP_IS_CPP17 + static const inline std::vector DAYS = { "NIL", "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; + static const inline std::vector MONTHS = { "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; +#else + + static std::vector& DAYS() + { + static std::vector days = { "NIL", "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; + return days; + } + + static std::vector& MONTHS() + { + static std::vector months = { "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; + return months; + } +#endif + }; + + struct cron_quartz_traits + { + static const cron_int CRON_MIN_SECONDS = 0; + static const cron_int CRON_MAX_SECONDS = 59; + + static const cron_int CRON_MIN_MINUTES = 0; + static const cron_int CRON_MAX_MINUTES = 59; + + static const cron_int CRON_MIN_HOURS = 0; + static const cron_int CRON_MAX_HOURS = 23; + + static const cron_int CRON_MIN_DAYS_OF_WEEK = 1; + static const cron_int CRON_MAX_DAYS_OF_WEEK = 7; + + static const cron_int CRON_MIN_DAYS_OF_MONTH = 1; + static const cron_int CRON_MAX_DAYS_OF_MONTH = 31; + + static const cron_int CRON_MIN_MONTHS = 1; + static const cron_int CRON_MAX_MONTHS = 12; + + static const cron_int CRON_MAX_YEARS_DIFF = 4; + +#ifdef CRONCPP_IS_CPP17 + static const inline std::vector DAYS = { "NIL", "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; + static const inline std::vector MONTHS = { "NIL", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; +#else + static std::vector& DAYS() + { + static std::vector days = { "NIL", "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; + return days; + } + + static std::vector& MONTHS() + { + static std::vector months = { "NIL", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; + return months; + } +#endif + }; + + class cronexpr; + + template + static cronexpr make_cron(STRING_VIEW expr); + + class cronexpr + { + std::bitset<60> seconds; + std::bitset<60> minutes; + std::bitset<24> hours; + std::bitset<7> days_of_week; + std::bitset<31> days_of_month; + std::bitset<12> months; + + friend bool operator==(cronexpr const & e1, cronexpr const & e2); + friend bool operator!=(cronexpr const & e1, cronexpr const & e2); + + template + friend bool detail::find_next(cronexpr const & cex, + std::tm& date, + size_t const dot); + + friend std::string to_string(cronexpr const & cex); + + template + friend cronexpr make_cron(STRING_VIEW expr); + }; + + inline bool operator==(cronexpr const & e1, cronexpr const & e2) + { + return + e1.seconds == e2.seconds && + e1.minutes == e2.minutes && + e1.hours == e2.hours && + e1.days_of_week == e2.days_of_week && + e1.days_of_month == e2.days_of_month && + e1.months == e2.months; + } + + inline bool operator!=(cronexpr const & e1, cronexpr const & e2) + { + return !(e1 == e2); + } + + inline std::string to_string(cronexpr const & cex) + { + return + cex.seconds.to_string() + " " + + cex.minutes.to_string() + " " + + cex.hours.to_string() + " " + + cex.days_of_month.to_string() + " " + + cex.months.to_string() + " " + + cex.days_of_week.to_string(); + } + + namespace utils + { + inline std::time_t tm_to_time(std::tm& date) + { + return std::mktime(&date); + } + + inline std::tm* time_to_tm(std::time_t const * date, std::tm* const out) + { +#ifdef _WIN32 + errno_t err = localtime_s(out, date); + return 0 == err ? out : nullptr; +#else + return localtime_r(date, out); +#endif + } + + inline std::tm to_tm(STRING_VIEW time) + { + std::istringstream str(time.data()); + str.imbue(std::locale(setlocale(LC_ALL, nullptr))); + + std::tm result; + str >> std::get_time(&result, "%Y-%m-%d %H:%M:%S"); + if (str.fail()) throw std::runtime_error("Parsing date failed!"); + + result.tm_isdst = -1; // DST info not available + + return result; + } + + inline std::string to_string(std::tm const & tm) + { + std::ostringstream str; + str.imbue(std::locale(setlocale(LC_ALL, nullptr))); + str << std::put_time(&tm, "%Y-%m-%d %H:%M:%S"); + if (str.fail()) throw std::runtime_error("Writing date failed!"); + + return str.str(); + } + + inline std::string to_upper(std::string text) + { + std::transform(std::begin(text), std::end(text), + std::begin(text), static_cast(std::toupper)); + + return text; + } + + static std::vector split(STRING_VIEW text, char const delimiter) + { + std::vector tokens; + std::string token; + std::istringstream tokenStream(text.data()); + while (std::getline(tokenStream, token, delimiter)) + { + tokens.push_back(token); + } + return tokens; + } + + CONSTEXPTR inline bool contains(STRING_VIEW text, char const ch) noexcept + { + return STRING_VIEW_NPOS != text.find_first_of(ch); + } + } + + namespace detail + { + + inline cron_int to_cron_int(STRING_VIEW text) + { + try + { + return static_cast(std::stoul(text.data())); + } + catch (std::exception const & ex) + { + throw bad_cronexpr(ex.what()); + } + } + + static std::string replace_ordinals( + std::string text, + std::vector const & replacement) + { + for (size_t i = 0; i < replacement.size(); ++i) + { + auto pos = text.find(replacement[i]); + if (std::string::npos != pos) + text.replace(pos, 3 ,std::to_string(i)); + } + + return text; + } + + static std::pair make_range( + STRING_VIEW field, + cron_int const minval, + cron_int const maxval) + { + cron_int first = 0; + cron_int last = 0; + if (field.size() == 1 && field[0] == '*') + { + first = minval; + last = maxval; + } + else if (!utils::contains(field, '-')) + { + first = to_cron_int(field); + last = first; + } + else + { + auto parts = utils::split(field, '-'); + if (parts.size() != 2) + throw bad_cronexpr("Specified range requires two fields"); + + first = to_cron_int(parts[0]); + last = to_cron_int(parts[1]); + } + + if (first > maxval || last > maxval) + { + throw bad_cronexpr("Specified range exceeds maximum"); + } + if (first < minval || last < minval) + { + throw bad_cronexpr("Specified range is less than minimum"); + } + if (first > last) + { + throw bad_cronexpr("Specified range start exceeds range end"); + } + + return { first, last }; + } + + template + static void set_cron_field( + STRING_VIEW value, + std::bitset& target, + cron_int const minval, + cron_int const maxval) + { + if(value.length() > 0 && value[value.length()-1] == ',') + throw bad_cronexpr("Value cannot end with comma"); + + auto fields = utils::split(value, ','); + if (fields.empty()) + throw bad_cronexpr("Expression parsing error"); + + for (auto const & field : fields) + { + if (!utils::contains(field, '/')) + { +#ifdef CRONCPP_IS_CPP17 + auto[first, last] = detail::make_range(field, minval, maxval); +#else + auto range = detail::make_range(field, minval, maxval); + auto first = range.first; + auto last = range.second; +#endif + for (cron_int i = first - minval; i <= last - minval; ++i) + { + target.set(i); + } + } + else + { + auto parts = utils::split(field, '/'); + if (parts.size() != 2) + throw bad_cronexpr("Incrementer must have two fields"); + +#ifdef CRONCPP_IS_CPP17 + auto[first, last] = detail::make_range(parts[0], minval, maxval); +#else + auto range = detail::make_range(parts[0], minval, maxval); + auto first = range.first; + auto last = range.second; +#endif + + if (!utils::contains(parts[0], '-')) + { + last = maxval; + } + + auto delta = detail::to_cron_int(parts[1]); + if(delta <= 0) + throw bad_cronexpr("Incrementer must be a positive value"); + + for (cron_int i = first - minval; i <= last - minval; i += delta) + { + target.set(i); + } + } + } + } + + template + static void set_cron_days_of_week( + std::string value, + std::bitset<7>& target) + { + auto days = utils::to_upper(value); + auto days_replaced = detail::replace_ordinals( + days, +#ifdef CRONCPP_IS_CPP17 + Traits::DAYS +#else + Traits::DAYS() +#endif + ); + + if (days_replaced.size() == 1 && days_replaced[0] == '?') + days_replaced[0] = '*'; + + set_cron_field( + days_replaced, + target, + Traits::CRON_MIN_DAYS_OF_WEEK, + Traits::CRON_MAX_DAYS_OF_WEEK); + } + + template + static void set_cron_days_of_month( + std::string value, + std::bitset<31>& target) + { + if (value.size() == 1 && value[0] == '?') + value[0] = '*'; + + set_cron_field( + value, + target, + Traits::CRON_MIN_DAYS_OF_MONTH, + Traits::CRON_MAX_DAYS_OF_MONTH); + } + + template + static void set_cron_month( + std::string value, + std::bitset<12>& target) + { + auto month = utils::to_upper(value); + auto month_replaced = replace_ordinals( + month, +#ifdef CRONCPP_IS_CPP17 + Traits::MONTHS +#else + Traits::MONTHS() +#endif + ); + + set_cron_field( + month_replaced, + target, + Traits::CRON_MIN_MONTHS, + Traits::CRON_MAX_MONTHS); + } + + template + inline size_t next_set_bit( + std::bitset const & target, + size_t /*minimum*/, + size_t /*maximum*/, + size_t offset) + { + for (auto i = offset; i < N; ++i) + { + if (target.test(i)) return i; + } + + return INVALID_CRON_INDEX; + } + + inline void add_to_field( + std::tm& date, + cron_field const field, + int const val) + { + switch (field) + { + case cron_field::second: + date.tm_sec += val; + break; + case cron_field::minute: + date.tm_min += val; + break; + case cron_field::hour_of_day: + date.tm_hour += val; + break; + case cron_field::day_of_week: + case cron_field::day_of_month: + date.tm_mday += val; + break; + case cron_field::month: + date.tm_mon += val; + break; + case cron_field::year: + date.tm_year += val; + break; + } + + if (INVALID_TIME == utils::tm_to_time(date)) + throw bad_cronexpr("Invalid time expression"); + } + + inline void set_field( + std::tm& date, + cron_field const field, + int const val) + { + switch (field) + { + case cron_field::second: + date.tm_sec = val; + break; + case cron_field::minute: + date.tm_min = val; + break; + case cron_field::hour_of_day: + date.tm_hour = val; + break; + case cron_field::day_of_week: + date.tm_wday = val; + break; + case cron_field::day_of_month: + date.tm_mday = val; + break; + case cron_field::month: + date.tm_mon = val; + break; + case cron_field::year: + date.tm_year = val; + break; + } + + if (INVALID_TIME == utils::tm_to_time(date)) + throw bad_cronexpr("Invalid time expression"); + } + + inline void reset_field( + std::tm& date, + cron_field const field) + { + switch (field) + { + case cron_field::second: + date.tm_sec = 0; + break; + case cron_field::minute: + date.tm_min = 0; + break; + case cron_field::hour_of_day: + date.tm_hour = 0; + break; + case cron_field::day_of_week: + date.tm_wday = 0; + break; + case cron_field::day_of_month: + date.tm_mday = 1; + break; + case cron_field::month: + date.tm_mon = 0; + break; + case cron_field::year: + date.tm_year = 0; + break; + } + + if (INVALID_TIME == utils::tm_to_time(date)) + throw bad_cronexpr("Invalid time expression"); + } + + inline void reset_all_fields( + std::tm& date, + std::bitset<7> const & marked_fields) + { + for (size_t i = 0; i < marked_fields.size(); ++i) + { + if (marked_fields.test(i)) + reset_field(date, static_cast(i)); + } + } + + inline void mark_field( + std::bitset<7> & orders, + cron_field const field) + { + if (!orders.test(static_cast(field))) + orders.set(static_cast(field)); + } + + template + static size_t find_next( + std::bitset const & target, + std::tm& date, + unsigned int const minimum, + unsigned int const maximum, + unsigned int const value, + cron_field const field, + cron_field const next_field, + std::bitset<7> const & marked_fields) + { + auto next_value = next_set_bit(target, minimum, maximum, value); + if (INVALID_CRON_INDEX == next_value) + { + add_to_field(date, next_field, 1); + reset_field(date, field); + next_value = next_set_bit(target, minimum, maximum, 0); + } + + if (INVALID_CRON_INDEX == next_value || next_value != value) + { + set_field(date, field, static_cast(next_value)); + reset_all_fields(date, marked_fields); + } + + return next_value; + } + + template + static size_t find_next_day( + std::tm& date, + std::bitset<31> const & days_of_month, + size_t day_of_month, + std::bitset<7> const & days_of_week, + size_t day_of_week, + std::bitset<7> const & marked_fields) + { + unsigned int count = 0; + unsigned int maximum = 366; + while ( + (!days_of_month.test(day_of_month - Traits::CRON_MIN_DAYS_OF_MONTH) || + !days_of_week.test(day_of_week - Traits::CRON_MIN_DAYS_OF_WEEK)) + && count++ < maximum) + { + add_to_field(date, cron_field::day_of_month, 1); + + day_of_month = date.tm_mday; + day_of_week = date.tm_wday; + + reset_all_fields(date, marked_fields); + } + + return day_of_month; + } + + template + static bool find_next(cronexpr const & cex, + std::tm& date, + size_t const dot) + { + bool res = true; + + std::bitset<7> marked_fields{ 0 }; + std::bitset<7> empty_list{ 0 }; + + unsigned int second = date.tm_sec; + auto updated_second = find_next( + cex.seconds, + date, + Traits::CRON_MIN_SECONDS, + Traits::CRON_MAX_SECONDS, + second, + cron_field::second, + cron_field::minute, + empty_list); + + if (second == updated_second) + { + mark_field(marked_fields, cron_field::second); + } + + unsigned int minute = date.tm_min; + auto update_minute = find_next( + cex.minutes, + date, + Traits::CRON_MIN_MINUTES, + Traits::CRON_MAX_MINUTES, + minute, + cron_field::minute, + cron_field::hour_of_day, + marked_fields); + if (minute == update_minute) + { + mark_field(marked_fields, cron_field::minute); + } + else + { + res = find_next(cex, date, dot); + if (!res) return res; + } + + unsigned int hour = date.tm_hour; + auto updated_hour = find_next( + cex.hours, + date, + Traits::CRON_MIN_HOURS, + Traits::CRON_MAX_HOURS, + hour, + cron_field::hour_of_day, + cron_field::day_of_week, + marked_fields); + if (hour == updated_hour) + { + mark_field(marked_fields, cron_field::hour_of_day); + } + else + { + res = find_next(cex, date, dot); + if (!res) return res; + } + + unsigned int day_of_week = date.tm_wday; + unsigned int day_of_month = date.tm_mday; + auto updated_day_of_month = find_next_day( + date, + cex.days_of_month, + day_of_month, + cex.days_of_week, + day_of_week, + marked_fields); + if (day_of_month == updated_day_of_month) + { + mark_field(marked_fields, cron_field::day_of_month); + } + else + { + res = find_next(cex, date, dot); + if (!res) return res; + } + + unsigned int month = date.tm_mon; + auto updated_month = find_next( + cex.months, + date, + Traits::CRON_MIN_MONTHS, + Traits::CRON_MAX_MONTHS, + month, + cron_field::month, + cron_field::year, + marked_fields); + if (month != updated_month) + { + if (date.tm_year - dot > Traits::CRON_MAX_YEARS_DIFF) + return false; + + res = find_next(cex, date, dot); + if (!res) return res; + } + + return res; + } + } + + template + static cronexpr make_cron(STRING_VIEW expr) + { + cronexpr cex; + + if (expr.empty()) + throw bad_cronexpr("Invalid empty cron expression"); + + auto fields = utils::split(expr, ' '); + fields.erase( + std::remove_if(std::begin(fields), std::end(fields), + [](STRING_VIEW s) {return s.empty(); }), + std::end(fields)); + if (fields.size() != 6) + throw bad_cronexpr("cron expression must have six fields"); + + detail::set_cron_field(fields[0], cex.seconds, Traits::CRON_MIN_SECONDS, Traits::CRON_MAX_SECONDS); + detail::set_cron_field(fields[1], cex.minutes, Traits::CRON_MIN_MINUTES, Traits::CRON_MAX_MINUTES); + detail::set_cron_field(fields[2], cex.hours, Traits::CRON_MIN_HOURS, Traits::CRON_MAX_HOURS); + + detail::set_cron_days_of_week(fields[5], cex.days_of_week); + + detail::set_cron_days_of_month(fields[3], cex.days_of_month); + + detail::set_cron_month(fields[4], cex.months); + + return cex; + } + + template + static std::tm cron_next(cronexpr const & cex, std::tm date) + { + time_t original = utils::tm_to_time(date); + if (INVALID_TIME == original) return {}; + + if (!detail::find_next(cex, date, date.tm_year)) + return {}; + + time_t calculated = utils::tm_to_time(date); + if (INVALID_TIME == calculated) return {}; + + if (calculated == original) + { + add_to_field(date, detail::cron_field::second, 1); + if (!detail::find_next(cex, date, date.tm_year)) + return {}; + } + + return date; + } + + template + static std::time_t cron_next(cronexpr const & cex, std::time_t const & date) + { + std::tm val; + std::tm* dt = utils::time_to_tm(&date, &val); + if (dt == nullptr) return INVALID_TIME; + + time_t original = utils::tm_to_time(*dt); + if (INVALID_TIME == original) return INVALID_TIME; + + if(!detail::find_next(cex, *dt, dt->tm_year)) + return INVALID_TIME; + + time_t calculated = utils::tm_to_time(*dt); + if (INVALID_TIME == calculated) return calculated; + + if (calculated == original) + { + add_to_field(*dt, detail::cron_field::second, 1); + if(!detail::find_next(cex, *dt, dt->tm_year)) + return INVALID_TIME; + } + + return utils::tm_to_time(*dt); + } +} diff --git a/common/database.cpp b/common/database.cpp index 5a2d53940..76673a31b 100644 --- a/common/database.cpp +++ b/common/database.cpp @@ -39,6 +39,7 @@ #include "unix.h" #include #include + #endif #include "database.h" @@ -46,6 +47,8 @@ #include "extprofile.h" #include "string_util.h" #include "database_schema.h" +#include "http/httplib.h" +#include "http/uri.h" extern Client client; @@ -703,17 +706,17 @@ bool Database::SaveCharacterCreate(uint32 character_id, uint32 account_id, Playe "(%u, %u, %u, %f, %f, %f, %f, %i), " "(%u, %u, %u, %f, %f, %f, %f, %i), " "(%u, %u, %u, %f, %f, %f, %f, %i)", - character_id, pp->binds[0].zoneId, 0, pp->binds[0].x, pp->binds[0].y, pp->binds[0].z, pp->binds[0].heading, 0, - character_id, pp->binds[1].zoneId, 0, pp->binds[1].x, pp->binds[1].y, pp->binds[1].z, pp->binds[1].heading, 1, - character_id, pp->binds[2].zoneId, 0, pp->binds[2].x, pp->binds[2].y, pp->binds[2].z, pp->binds[2].heading, 2, - character_id, pp->binds[3].zoneId, 0, pp->binds[3].x, pp->binds[3].y, pp->binds[3].z, pp->binds[3].heading, 3, - character_id, pp->binds[4].zoneId, 0, pp->binds[4].x, pp->binds[4].y, pp->binds[4].z, pp->binds[4].heading, 4 + character_id, pp->binds[0].zone_id, 0, pp->binds[0].x, pp->binds[0].y, pp->binds[0].z, pp->binds[0].heading, 0, + character_id, pp->binds[1].zone_id, 0, pp->binds[1].x, pp->binds[1].y, pp->binds[1].z, pp->binds[1].heading, 1, + character_id, pp->binds[2].zone_id, 0, pp->binds[2].x, pp->binds[2].y, pp->binds[2].z, pp->binds[2].heading, 2, + character_id, pp->binds[3].zone_id, 0, pp->binds[3].x, pp->binds[3].y, pp->binds[3].z, pp->binds[3].heading, 3, + character_id, pp->binds[4].zone_id, 0, pp->binds[4].x, pp->binds[4].y, pp->binds[4].z, pp->binds[4].heading, 4 ); results = QueryDatabase(query); /* HoTT Ability */ if(RuleB(Character, GrantHoTTOnCreate)) { - query = StringFormat("INSERT INTO `character_leadership_abilities` (id, slot, rank) VALUES (%u, %i, %i)", character_id, 14, 1); + query = StringFormat("INSERT INTO `character_leadership_abilities` (id, slot, `rank`) VALUES (%u, %i, %i)", character_id, 14, 1); results = QueryDatabase(query); } @@ -870,36 +873,60 @@ void Database::GetCharName(uint32 char_id, char* name) { } } -const char* Database::GetCharNameByID(uint32 char_id) { +std::string Database::GetCharNameByID(uint32 char_id) { std::string query = fmt::format("SELECT `name` FROM `character_data` WHERE id = {}", char_id); auto results = QueryDatabase(query); + std::string res; if (!results.Success()) { - return ""; + return res; } if (results.RowCount() == 0) { - return ""; + return res; } auto row = results.begin(); - return row[0]; + res = row[0]; + return res; } -const char* Database::GetNPCNameByID(uint32 npc_id) { +std::string Database::GetNPCNameByID(uint32 npc_id) { std::string query = fmt::format("SELECT `name` FROM `npc_types` WHERE id = {}", npc_id); auto results = QueryDatabase(query); + std::string res; if (!results.Success()) { - return ""; + return res; } if (results.RowCount() == 0) { - return ""; + return res; } auto row = results.begin(); - return row[0]; + res = row[0]; + return res; +} + +std::string Database::GetCleanNPCNameByID(uint32 npc_id) { + std::string query = fmt::format("SELECT `name` FROM `npc_types` WHERE id = {}", npc_id); + auto results = QueryDatabase(query); + std::string res; + std::string mob_name; + + if (!results.Success()) { + return res; + } + + if (results.RowCount() == 0) { + return res; + } + + auto row = results.begin(); + mob_name = row[0]; + CleanMobName(mob_name.begin(), mob_name.end(), std::back_inserter(res)); + return res; } bool Database::LoadVariables() { @@ -971,10 +998,24 @@ bool Database::SetVariable(const std::string varname, const std::string &varvalu } // Get zone starting points from DB -bool Database::GetSafePoints(const char* short_name, uint32 version, float* safe_x, float* safe_y, float* safe_z, int16* minstatus, uint8* minlevel, char *flag_needed) { +bool Database::GetSafePoints(const char* zone_short_name, uint32 instance_version, float* safe_x, float* safe_y, float* safe_z, float* safe_heading, int16* min_status, uint8* min_level, char *flag_needed) { - std::string query = StringFormat("SELECT safe_x, safe_y, safe_z, min_status, min_level, flag_needed FROM zone " - " WHERE short_name='%s' AND (version=%i OR version=0) ORDER BY version DESC", short_name, version); + if (zone_short_name == nullptr) + return false; + + std::string query = fmt::format( + SQL( + SELECT + `safe_x`, `safe_y`, `safe_z`, `safe_heading`, `min_status`, `min_level`, `flag_needed` + FROM + zone + WHERE + `short_name` = '{}' + AND + (`version` = {} OR `version` = 0) + ORDER BY `version` DESC + ), zone_short_name, instance_version + ); auto results = QueryDatabase(query); if (!results.Success()) @@ -987,16 +1028,24 @@ bool Database::GetSafePoints(const char* short_name, uint32 version, float* safe if (safe_x != nullptr) *safe_x = atof(row[0]); + if (safe_y != nullptr) *safe_y = atof(row[1]); + if (safe_z != nullptr) *safe_z = atof(row[2]); - if (minstatus != nullptr) - *minstatus = atoi(row[3]); - if (minlevel != nullptr) - *minlevel = atoi(row[4]); + + if (safe_heading != nullptr) + *safe_heading = atof(row[3]); + + if (min_status != nullptr) + *min_status = atoi(row[4]); + + if (min_level != nullptr) + *min_level = atoi(row[5]); + if (flag_needed != nullptr) - strcpy(flag_needed, row[5]); + strcpy(flag_needed, row[6]); return true; } @@ -1993,62 +2042,64 @@ void Database::ClearRaidLeader(uint32 gid, uint32 rid) QueryDatabase(query); } -void Database::UpdateAdventureStatsEntry(uint32 char_id, uint8 theme, bool win) +void Database::UpdateAdventureStatsEntry(uint32 char_id, uint8 theme, bool win, bool remove) { - std::string field; - - switch(theme) - { - case 1: - { + switch(theme) { + case LDoNThemes::GUK: { field = "guk_"; break; } - case 2: - { + case LDoNThemes::MIR: { field = "mir_"; break; } - case 3: - { + case LDoNThemes::MMC: { field = "mmc_"; break; } - case 4: - { + case LDoNThemes::RUJ: { field = "ruj_"; break; } - case 5: - { + case LDoNThemes::TAK: { field = "tak_"; break; } - default: - { + default: { return; } } - if (win) - field += "wins"; - else - field += "losses"; + field += win ? "wins" : "losses"; + std::string field_operation = remove ? "-" : "+"; - std::string query = StringFormat("UPDATE `adventure_stats` SET %s=%s+1 WHERE player_id=%u",field.c_str(), field.c_str(), char_id); + std::string query = fmt::format( + "UPDATE `adventure_stats` SET {} = {} {} 1 WHERE player_id = {}", + field, + field, + field_operation, + char_id + ); auto results = QueryDatabase(query); - if (results.RowsAffected() != 0) + if (results.RowsAffected() != 0) { return; + } - query = StringFormat("INSERT INTO `adventure_stats` SET %s=1, player_id=%u", field.c_str(), char_id); - QueryDatabase(query); + if (!remove) { + query = fmt::format( + "INSERT INTO `adventure_stats` SET {} = 1, player_id = {}", + field, + char_id + ); + QueryDatabase(query); + } } bool Database::GetAdventureStats(uint32 char_id, AdventureStats_Struct *as) { - std::string query = StringFormat( + std::string query = fmt::format( "SELECT " "`guk_wins`, " "`mir_wins`, " @@ -2063,7 +2114,7 @@ bool Database::GetAdventureStats(uint32 char_id, AdventureStats_Struct *as) "FROM " "`adventure_stats` " "WHERE " - "player_id = %u ", + "player_id = {}", char_id ); auto results = QueryDatabase(query); @@ -2145,95 +2196,6 @@ uint32 Database::GetRaidIDByCharID(uint32 character_id) { return 0; } -/** - * @param log_settings - */ -void Database::LoadLogSettings(EQEmuLogSys::LogSettings *log_settings) -{ - std::string query = - "SELECT " - "log_category_id, " - "log_category_description, " - "log_to_console, " - "log_to_file, " - "log_to_gmsay " - "FROM " - "logsys_categories " - "ORDER BY log_category_id"; - - auto results = QueryDatabase(query); - int log_category_id = 0; - - int *categories_in_database = new int[1000]; - - for (auto row = results.begin(); row != results.end(); ++row) { - log_category_id = atoi(row[0]); - if (log_category_id <= Logs::None || log_category_id >= Logs::MaxCategoryID) { - continue; - } - - log_settings[log_category_id].log_to_console = static_cast(atoi(row[2])); - log_settings[log_category_id].log_to_file = static_cast(atoi(row[3])); - log_settings[log_category_id].log_to_gmsay = static_cast(atoi(row[4])); - - /** - * Determine if any output method is enabled for the category - * and set it to 1 so it can used to check if category is enabled - */ - const bool log_to_console = log_settings[log_category_id].log_to_console > 0; - const bool log_to_file = log_settings[log_category_id].log_to_file > 0; - const bool log_to_gmsay = log_settings[log_category_id].log_to_gmsay > 0; - const bool is_category_enabled = log_to_console || log_to_file || log_to_gmsay; - - if (is_category_enabled) { - log_settings[log_category_id].is_category_enabled = 1; - } - - /** - * This determines whether or not the process needs to actually file log anything. - * If we go through this whole loop and nothing is set to any debug level, there is no point to create a file or keep anything open - */ - if (log_settings[log_category_id].log_to_file > 0) { - LogSys.file_logs_enabled = true; - } - - categories_in_database[log_category_id] = 1; - } - - /** - * Auto inject categories that don't exist in the database... - */ - for (int log_index = Logs::AA; log_index != Logs::MaxCategoryID; log_index++) { - if (categories_in_database[log_index] != 1) { - - LogInfo( - "New Log Category [{0}] doesn't exist... Automatically adding to [logsys_categories] table...", - Logs::LogCategoryName[log_index] - ); - - auto inject_query = fmt::format( - "INSERT INTO logsys_categories " - "(log_category_id, " - "log_category_description, " - "log_to_console, " - "log_to_file, " - "log_to_gmsay) " - "VALUES " - "({0}, '{1}', {2}, {3}, {4})", - log_index, - EscapeString(Logs::LogCategoryName[log_index]), - std::to_string(log_settings[log_index].log_to_console), - std::to_string(log_settings[log_index].log_to_file), - std::to_string(log_settings[log_index].log_to_gmsay) - ); - - QueryDatabase(inject_query); - } - } - - delete[] categories_in_database; -} - int Database::CountInvSnapshots() { std::string query = StringFormat("SELECT COUNT(*) FROM (SELECT * FROM `inventory_snapshots` a GROUP BY `charid`, `time_index`) b"); auto results = QueryDatabase(query); @@ -2311,6 +2273,35 @@ int Database::GetIPExemption(std::string account_ip) { return RuleI(World, MaxClientsPerIP); } +void Database::SetIPExemption(std::string account_ip, int exemption_amount) { + std::string query = fmt::format( + "SELECT `exemption_id` FROM `ip_exemptions` WHERE `exemption_ip` = '{}'", + account_ip + ); + + auto results = QueryDatabase(query); + uint32 exemption_id = 0; + if (results.Success() && results.RowCount() > 0) { + auto row = results.begin(); + exemption_id = atoi(row[0]); + } + + query = fmt::format( + "INSERT INTO `ip_exemptions` (`exemption_ip`, `exemption_amount`) VALUES ('{}', {})", + account_ip, + exemption_amount + ); + + if (exemption_id != 0) { + query = fmt::format( + "UPDATE `ip_exemptions` SET `exemption_amount` = {} WHERE `exemption_ip` = '{}'", + exemption_amount, + account_ip + ); + } + QueryDatabase(query); +} + int Database::GetInstanceID(uint32 char_id, uint32 zone_id) { std::string query = StringFormat("SELECT instance_list.id FROM instance_list INNER JOIN instance_list_player ON instance_list.id = instance_list_player.id WHERE instance_list.zone = '%i' AND instance_list_player.charid = '%i'", zone_id, char_id); auto results = QueryDatabase(query); @@ -2461,3 +2452,67 @@ bool Database::CopyCharacter( return true; } +void Database::SourceDatabaseTableFromUrl(std::string table_name, std::string url) +{ + try { + uri request_uri(url); + + LogHTTP( + "[SourceDatabaseTableFromUrl] parsing url [{}] path [{}] host [{}] query_string [{}] protocol [{}] port [{}]", + url, + request_uri.get_path(), + request_uri.get_host(), + request_uri.get_query(), + request_uri.get_scheme(), + request_uri.get_port() + ); + + if (!DoesTableExist(table_name)) { + LogMySQLQuery("Table [{}] does not exist. Downloading from Github and installing...", table_name); + + // http get request + httplib::Client cli( + fmt::format( + "{}://{}", + request_uri.get_scheme(), + request_uri.get_host() + ).c_str() + ); + + cli.set_connection_timeout(0, 60000000); // 60 sec + cli.set_read_timeout(60, 0); // 60 seconds + cli.set_write_timeout(60, 0); // 60 seconds + + int sourced_queries = 0; + + if (auto res = cli.Get(request_uri.get_path().c_str())) { + if (res->status == 200) { + for (auto &s: SplitString(res->body, ';')) { + if (!trim(s).empty()) { + auto results = QueryDatabase(s); + if (!results.ErrorMessage().empty()) { + LogError("Error sourcing SQL [{}]", results.ErrorMessage()); + return; + } + sourced_queries++; + } + } + } + } + else { + LogError("Error retrieving URL [{}]", url); + } + + LogMySQLQuery( + "Table [{}] installed. Sourced [{}] queries", + table_name, + sourced_queries + ); + } + + } + catch (std::invalid_argument iae) { + LogError("[SourceDatabaseTableFromUrl] URI parser error [{}]", iae.what()); + } +} + diff --git a/common/database.h b/common/database.h index db428b981..64856ef80 100644 --- a/common/database.h +++ b/common/database.h @@ -78,6 +78,7 @@ class PTimerList; #define SQL(...) #__VA_ARGS__ +class LogSettings; class Database : public DBcore { public: Database(); @@ -138,8 +139,9 @@ public: 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); + std::string GetCharNameByID(uint32 char_id); + std::string GetNPCNameByID(uint32 npc_id); + std::string GetCleanNPCNameByID(uint32 npc_id); void LoginIP(uint32 AccountID, const char* LoginIP); /* Instancing */ @@ -174,7 +176,7 @@ public: /* Adventure related. */ - void UpdateAdventureStatsEntry(uint32 char_id, uint8 theme, bool win); + void UpdateAdventureStatsEntry(uint32 char_id, uint8 theme, bool win = false, bool remove = false); bool GetAdventureStats(uint32 char_id, AdventureStats_Struct *as); /* Account Related */ @@ -196,7 +198,8 @@ public: void GetAccountFromID(uint32 id, char* oAccountName, int16* oStatus); void SetAgreementFlag(uint32 acctid); - int GetIPExemption(std::string account_ip); + int GetIPExemption(std::string account_ip); + void SetIPExemption(std::string account_ip, int exemption_amount); int GetInstanceID(uint32 char_id, uint32 zone_id); @@ -242,7 +245,7 @@ public: /* General Queries */ - bool GetSafePoints(const char* short_name, uint32 version, float* safe_x = 0, float* safe_y = 0, float* safe_z = 0, int16* minstatus = 0, uint8* minlevel = 0, char *flag_needed = nullptr); + bool GetSafePoints(const char* zone_short_name, uint32 instance_version, float* safe_x = 0, float* safe_y = 0, float* safe_z = 0, float* safe_heading = 0, int16* minstatus = 0, uint8* minlevel = 0, char *flag_needed = nullptr); bool GetZoneGraveyard(const uint32 graveyard_id, uint32* graveyard_zoneid = 0, float* graveyard_x = 0, float* graveyard_y = 0, float* graveyard_z = 0, float* graveyard_heading = 0); bool GetZoneLongName(const char* short_name, char** long_name, char* file_name = 0, float* safe_x = 0, float* safe_y = 0, float* safe_z = 0, uint32* graveyard_id = 0, uint32* maxclients = 0); bool LoadPTimers(uint32 charid, PTimerList &into); @@ -267,8 +270,8 @@ public: int CountInvSnapshots(); void ClearInvSnapshots(bool from_now = false); - /* EQEmuLogSys */ - void LoadLogSettings(EQEmuLogSys::LogSettings* log_settings); + void SourceDatabaseTableFromUrl(std::string table_name, std::string url); + private: diff --git a/common/database_conversions.cpp b/common/database_conversions.cpp index d49651f4d..4ffe058e8 100644 --- a/common/database_conversions.cpp +++ b/common/database_conversions.cpp @@ -48,7 +48,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA namespace Convert { struct BindStruct { - /*000*/ uint32 zoneId; + /*000*/ uint32 zone_id; /*004*/ float x; /*008*/ float y; /*012*/ float z; @@ -1320,18 +1320,18 @@ bool Database::CheckDatabaseConvertPPDeblob(){ if (rquery != ""){ results = QueryDatabase(rquery); } /* Run Bind Home Convert */ - if (pp->binds[4].zoneId < 999 && !_ISNAN_(pp->binds[4].x) && !_ISNAN_(pp->binds[4].y) && !_ISNAN_(pp->binds[4].z) && !_ISNAN_(pp->binds[4].heading)) { + if (pp->binds[4].zone_id < 999 && !_ISNAN_(pp->binds[4].x) && !_ISNAN_(pp->binds[4].y) && !_ISNAN_(pp->binds[4].z) && !_ISNAN_(pp->binds[4].heading)) { rquery = StringFormat("REPLACE INTO `character_bind` (id, zone_id, instance_id, x, y, z, heading, is_home)" " VALUES (%u, %u, %u, %f, %f, %f, %f, 1)", - character_id, pp->binds[4].zoneId, 0, pp->binds[4].x, pp->binds[4].y, pp->binds[4].z, pp->binds[4].heading); + character_id, pp->binds[4].zone_id, 0, pp->binds[4].x, pp->binds[4].y, pp->binds[4].z, pp->binds[4].heading); if (rquery != ""){ results = QueryDatabase(rquery); } } /* Run Bind Convert */ - if (pp->binds[0].zoneId < 999 && !_ISNAN_(pp->binds[0].x) && !_ISNAN_(pp->binds[0].y) && !_ISNAN_(pp->binds[0].z) && !_ISNAN_(pp->binds[0].heading)) { + if (pp->binds[0].zone_id < 999 && !_ISNAN_(pp->binds[0].x) && !_ISNAN_(pp->binds[0].y) && !_ISNAN_(pp->binds[0].z) && !_ISNAN_(pp->binds[0].heading)) { rquery = StringFormat("REPLACE INTO `character_bind` (id, zone_id, instance_id, x, y, z, heading, is_home)" " VALUES (%u, %u, %u, %f, %f, %f, %f, 0)", - character_id, pp->binds[0].zoneId, 0, pp->binds[0].x, pp->binds[0].y, pp->binds[0].z, pp->binds[0].heading); + character_id, pp->binds[0].zone_id, 0, pp->binds[0].x, pp->binds[0].y, pp->binds[0].z, pp->binds[0].heading); if (rquery != ""){ results = QueryDatabase(rquery); } } /* Run Language Convert */ @@ -1452,7 +1452,7 @@ bool Database::CheckDatabaseConvertPPDeblob(){ for (i = 0; i < MAX_LEADERSHIP_AA_ARRAY; i++){ if (pp->leader_abilities.ranks[i] > 0 && pp->leader_abilities.ranks[i] < 6){ if (first_entry != 1){ - rquery = StringFormat("REPLACE INTO `character_leadership_abilities` (id, slot, rank) VALUES (%i, %u, %u)", character_id, i, pp->leader_abilities.ranks[i]); + rquery = StringFormat("REPLACE INTO `character_leadership_abilities` (id, slot, `rank`) VALUES (%i, %u, %u)", character_id, i, pp->leader_abilities.ranks[i]); first_entry = 1; } rquery = rquery + StringFormat(", (%i, %u, %u)", character_id, i, pp->leader_abilities.ranks[i]); diff --git a/common/database_instances.cpp b/common/database_instances.cpp index 0c4be4bd6..75edd9fd3 100644 --- a/common/database_instances.cpp +++ b/common/database_instances.cpp @@ -20,6 +20,8 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "../common/rulesys.h" #include "../common/string_util.h" #include "../common/timer.h" +#include "../common/repositories/dynamic_zone_members_repository.h" +#include "../common/repositories/dynamic_zones_repository.h" #include "database.h" @@ -493,8 +495,8 @@ void Database::DeleteInstance(uint16 instance_id) query = StringFormat("DELETE FROM spawn_condition_values WHERE instance_id=%u", instance_id); QueryDatabase(query); - query = fmt::format("DELETE FROM dynamic_zones WHERE instance_id={}", instance_id); - QueryDatabase(query); + DynamicZoneMembersRepository::DeleteByInstance(*this, instance_id); + DynamicZonesRepository::DeleteWhere(*this, fmt::format("instance_id = {}", instance_id)); BuryCorpsesInInstance(instance_id); } @@ -585,7 +587,8 @@ void Database::PurgeExpiredInstances() QueryDatabase(fmt::format("DELETE FROM respawn_times WHERE instance_id IN ({})", imploded_instance_ids)); QueryDatabase(fmt::format("DELETE FROM spawn_condition_values WHERE instance_id IN ({})", imploded_instance_ids)); QueryDatabase(fmt::format("UPDATE character_corpses SET is_buried = 1, instance_id = 0 WHERE instance_id IN ({})", imploded_instance_ids)); - QueryDatabase(fmt::format("DELETE FROM dynamic_zones WHERE instance_id IN ({})", imploded_instance_ids)); + DynamicZoneMembersRepository::DeleteByManyInstances(*this, imploded_instance_ids); + DynamicZonesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", imploded_instance_ids)); } void Database::SetInstanceDuration(uint16 instance_id, uint32 new_duration) diff --git a/common/database_schema.h b/common/database_schema.h index 95a550d69..fc110bf2d 100644 --- a/common/database_schema.h +++ b/common/database_schema.h @@ -51,7 +51,9 @@ namespace DatabaseSchema { {"character_disciplines", "id"}, {"character_enabledtasks", "charid"}, {"character_expedition_lockouts", "character_id"}, + {"character_exp_modifiers", "character_id"}, {"character_inspect_messages", "id"}, + {"character_instance_safereturns", "character_id"}, {"character_item_recast", "id"}, {"character_languages", "id"}, {"character_leadership_abilities", "id"}, @@ -63,6 +65,7 @@ namespace DatabaseSchema { {"character_potionbelt", "id"}, {"character_skills", "id"}, {"character_spells", "id"}, + {"character_task_timers", "character_id"}, {"character_tasks", "charid"}, {"character_tribute", "id"}, {"completed_tasks", "charid"}, @@ -79,7 +82,6 @@ namespace DatabaseSchema { {"player_titlesets", "char_id"}, {"quest_globals", "charid"}, {"timers", "char_id"}, - {"titles", "char_id"}, {"trader", "char_id"}, {"zone_flags", "charID"} }; @@ -116,7 +118,9 @@ namespace DatabaseSchema { "character_disciplines", "character_enabledtasks", "character_expedition_lockouts", + "character_exp_modifiers", "character_inspect_messages", + "character_instance_safereturns", "character_item_recast", "character_languages", "character_leadership_abilities", @@ -128,6 +132,7 @@ namespace DatabaseSchema { "character_potionbelt", "character_skills", "character_spells", + "character_task_timers", "character_tasks", "character_tribute", "completed_tasks", @@ -152,7 +157,6 @@ namespace DatabaseSchema { "spell_buckets", "spell_globals", "timers", - "titles", "trader", "trader_audit", "zone_flags" @@ -215,6 +219,7 @@ namespace DatabaseSchema { "npc_types_tint", "object", "pets", + "pets_beastlord_data", "pets_equipmentset", "pets_equipmentset_entries", "proximities", @@ -263,6 +268,7 @@ namespace DatabaseSchema { "perl_event_export_settings", "profanity_list", "rule_sets", + "titles", "rule_values", "variables", }; @@ -307,17 +313,20 @@ namespace DatabaseSchema { "banned_ips", "bug_reports", "bugs", + "completed_shared_task_activity_state", + "completed_shared_task_members", + "completed_shared_tasks", + "dynamic_zone_members", "dynamic_zones", "eventlog", "expedition_lockouts", - "expedition_members", "expeditions", "gm_ips", "group_id", "group_leaders", "hackers", - "ip_exemptions", "instance_list", + "ip_exemptions", "item_tick", "lfguild", "merchantlist_temp", @@ -328,7 +337,11 @@ namespace DatabaseSchema { "reports", "respawn_times", "saylink", - + "server_scheduled_events", + "shared_task_activity_state", + "shared_task_dynamic_zones", + "shared_task_members", + "shared_tasks", }; } diff --git a/common/dynamic_zone_base.cpp b/common/dynamic_zone_base.cpp new file mode 100644 index 000000000..f791ed0b5 --- /dev/null +++ b/common/dynamic_zone_base.cpp @@ -0,0 +1,599 @@ +#include "dynamic_zone_base.h" +#include "database.h" +#include "eqemu_logsys.h" +#include "repositories/instance_list_repository.h" +#include "repositories/instance_list_player_repository.h" +#include "rulesys.h" +#include "servertalk.h" +#include "util/uuid.h" + +DynamicZoneBase::DynamicZoneBase(DynamicZonesRepository::DynamicZoneInstance&& entry) +{ + LoadRepositoryResult(std::move(entry)); +} + +uint32_t DynamicZoneBase::Create() +{ + if (GetInstanceID() == 0) + { + CreateInstance(); + } + + m_uuid = EQ::Util::UUID::Generate().ToString(); + m_id = SaveToDatabase(); + + return m_id; +} + +uint32_t DynamicZoneBase::CreateInstance() +{ + if (m_instance_id) + { + LogDynamicZones("CreateInstance failed, instance id [{}] already created", m_instance_id); + return 0; + } + + if (!m_zone_id) + { + LogDynamicZones("CreateInstance failed, invalid zone id [{}]", m_zone_id); + return 0; + } + + uint16_t unused_instance_id = 0; + if (!GetDatabase().GetUnusedInstanceID(unused_instance_id)) // todo: doesn't this race with insert? + { + LogDynamicZones("Failed to find unused instance id"); + return 0; + } + + m_start_time = std::chrono::system_clock::now(); + m_expire_time = m_start_time + m_duration; + + auto insert_instance = InstanceListRepository::NewEntity(); + insert_instance.id = unused_instance_id; + insert_instance.zone = m_zone_id; + insert_instance.version = m_zone_version; + insert_instance.start_time = static_cast(std::chrono::system_clock::to_time_t(m_start_time)); + insert_instance.duration = static_cast(m_duration.count()); + insert_instance.never_expires = m_never_expires; + + auto instance = InstanceListRepository::InsertOne(GetDatabase(), insert_instance); + if (instance.id == 0) + { + LogDynamicZones("Failed to create instance [{}] for zone [{}]", unused_instance_id, m_zone_id); + return 0; + } + + m_instance_id = instance.id; + + return m_instance_id; +} + +void DynamicZoneBase::LoadRepositoryResult(DynamicZonesRepository::DynamicZoneInstance&& dz_entry) +{ + m_id = dz_entry.id; + m_uuid = std::move(dz_entry.uuid); + m_name = std::move(dz_entry.name); + m_leader.id = dz_entry.leader_id; + m_min_players = dz_entry.min_players; + m_max_players = dz_entry.max_players; + m_instance_id = dz_entry.instance_id; + m_type = static_cast(dz_entry.type); + m_compass.zone_id = dz_entry.compass_zone_id; + m_compass.x = dz_entry.compass_x; + m_compass.y = dz_entry.compass_y; + m_compass.z = dz_entry.compass_z; + m_safereturn.zone_id = dz_entry.safe_return_zone_id; + m_safereturn.x = dz_entry.safe_return_x; + m_safereturn.y = dz_entry.safe_return_y; + m_safereturn.z = dz_entry.safe_return_z; + m_safereturn.heading = dz_entry.safe_return_heading; + m_zonein.x = dz_entry.zone_in_x; + m_zonein.y = dz_entry.zone_in_y; + m_zonein.z = dz_entry.zone_in_z; + m_zonein.heading = dz_entry.zone_in_heading; + m_has_zonein = (dz_entry.has_zone_in != 0); + // instance_list portion + m_zone_id = dz_entry.zone; + m_zone_version = dz_entry.version; + m_start_time = std::chrono::system_clock::from_time_t(dz_entry.start_time); + m_duration = std::chrono::seconds(dz_entry.duration); + m_never_expires = (dz_entry.never_expires != 0); + m_expire_time = m_start_time + m_duration; +} + +void DynamicZoneBase::AddMemberFromRepositoryResult( + DynamicZoneMembersRepository::MemberWithName&& entry) +{ + auto status = DynamicZoneMemberStatus::Unknown; + + if (m_leader.id == entry.character_id) + { + m_leader.name = entry.character_name; + } + + AddInternalMember({ entry.character_id, std::move(entry.character_name), status }); +} + +uint32_t DynamicZoneBase::SaveToDatabase() +{ + LogDynamicZonesDetail("Saving dz instance [{}] to database", m_instance_id); + + if (m_instance_id != 0) + { + auto insert_dz = DynamicZonesRepository::NewEntity(); + insert_dz.uuid = m_uuid; + insert_dz.name = m_name; + insert_dz.leader_id = m_leader.id; + insert_dz.min_players = m_min_players; + insert_dz.max_players = m_max_players; + insert_dz.instance_id = m_instance_id, + insert_dz.type = static_cast(m_type); + insert_dz.compass_zone_id = m_compass.zone_id; + insert_dz.compass_x = m_compass.x; + insert_dz.compass_y = m_compass.y; + insert_dz.compass_z = m_compass.z; + insert_dz.safe_return_zone_id = m_safereturn.zone_id; + insert_dz.safe_return_x = m_safereturn.x; + insert_dz.safe_return_y = m_safereturn.y; + insert_dz.safe_return_z = m_safereturn.z; + insert_dz.safe_return_heading = m_safereturn.heading; + insert_dz.zone_in_x = m_zonein.x; + insert_dz.zone_in_y = m_zonein.y; + insert_dz.zone_in_z = m_zonein.z; + insert_dz.zone_in_heading = m_zonein.heading; + insert_dz.has_zone_in = m_has_zonein; + + auto inserted_dz = DynamicZonesRepository::InsertOne(GetDatabase(), insert_dz); + return inserted_dz.id; + } + return 0; +} + +bool DynamicZoneBase::AddMember(const DynamicZoneMember& add_member) +{ + if (HasMember(add_member.id)) + { + return false; + } + + DynamicZoneMembersRepository::AddMember(GetDatabase(), m_id, add_member.id); + GetDatabase().AddClientToInstance(m_instance_id, add_member.id); + + ProcessMemberAddRemove(add_member, false); + SendServerPacket(CreateServerMemberAddRemovePacket(add_member, false).get()); + + return true; +} + +bool DynamicZoneBase::RemoveMember(uint32_t character_id) +{ + auto remove_member = GetMemberData(character_id); + return RemoveMember(remove_member); +} + +bool DynamicZoneBase::RemoveMember(const std::string& character_name) +{ + auto remove_member = GetMemberData(character_name); + return RemoveMember(remove_member); +} + +bool DynamicZoneBase::RemoveMember(const DynamicZoneMember& remove_member) +{ + if (remove_member.id == 0) + { + return false; + } + + DynamicZoneMembersRepository::RemoveMember(GetDatabase(), m_id, remove_member.id); + GetDatabase().RemoveClientFromInstance(m_instance_id, remove_member.id); + + ProcessMemberAddRemove(remove_member, true); + SendServerPacket(CreateServerMemberAddRemovePacket(remove_member, true).get()); + + return true; +} + +bool DynamicZoneBase::SwapMember( + const DynamicZoneMember& add_member, const std::string& remove_char_name) +{ + auto remove_member = GetMemberData(remove_char_name); + if (!add_member.IsValid() || !remove_member.IsValid()) + { + return false; + } + + // make remove and add atomic to avoid racing with separate world messages + DynamicZoneMembersRepository::RemoveMember(GetDatabase(), m_id, remove_member.id); + GetDatabase().RemoveClientFromInstance(m_instance_id, remove_member.id); + + DynamicZoneMembersRepository::AddMember(GetDatabase(), m_id, add_member.id); + GetDatabase().AddClientToInstance(m_instance_id, add_member.id); + + ProcessMemberAddRemove(remove_member, true); + ProcessMemberAddRemove(add_member, false); + SendServerPacket(CreateServerMemberSwapPacket(remove_member, add_member).get()); + + return true; +} + +void DynamicZoneBase::RemoveAllMembers() +{ + DynamicZoneMembersRepository::RemoveAllMembers(GetDatabase(), m_id); + GetDatabase().RemoveClientsFromInstance(GetInstanceID()); + + ProcessRemoveAllMembers(); + SendServerPacket(CreateServerRemoveAllMembersPacket().get()); +} + +void DynamicZoneBase::SaveMembers(const std::vector& members) +{ + LogDynamicZonesDetail("Saving [{}] member(s) for dz [{}]", members.size(), m_id); + + m_members = members; + + // the lower level instance_list_players needs to be kept updated as well + std::vector insert_members; + std::vector insert_players; + for (const auto& member : m_members) + { + DynamicZoneMembersRepository::DynamicZoneMembers member_entry{}; + member_entry.dynamic_zone_id = m_id; + member_entry.character_id = member.id; + insert_members.emplace_back(member_entry); + + InstanceListPlayerRepository::InstanceListPlayer player_entry; + player_entry.id = static_cast(m_instance_id); + player_entry.charid = static_cast(member.id); + insert_players.emplace_back(player_entry); + } + + DynamicZoneMembersRepository::InsertOrUpdateMany(GetDatabase(), insert_members); + InstanceListPlayerRepository::InsertOrUpdateMany(GetDatabase(), insert_players); +} + +void DynamicZoneBase::SetCompass(const DynamicZoneLocation& location, bool update_db) +{ + ProcessCompassChange(location); + + if (update_db) + { + LogDynamicZonesDetail("Saving [{}] compass zone: [{}] xyz: ([{}], [{}], [{}])", + m_id, m_compass.zone_id, m_compass.x, m_compass.y, m_compass.z); + + DynamicZonesRepository::UpdateCompass(GetDatabase(), + m_id, m_compass.zone_id, m_compass.x, m_compass.y, m_compass.z); + + SendServerPacket(CreateServerDzLocationPacket(ServerOP_DzSetCompass, location).get()); + } +} + +void DynamicZoneBase::SetCompass(uint32_t zone_id, float x, float y, float z, bool update_db) +{ + SetCompass({ zone_id, x, y, z, 0.0f }, update_db); +} + +void DynamicZoneBase::SetSafeReturn(const DynamicZoneLocation& location, bool update_db) +{ + m_safereturn = location; + + if (update_db) + { + LogDynamicZonesDetail("Saving [{}] safereturn zone: [{}] xyzh: ([{}], [{}], [{}], [{}])", + m_id, m_safereturn.zone_id, m_safereturn.x, m_safereturn.y, m_safereturn.z, m_safereturn.heading); + + DynamicZonesRepository::UpdateSafeReturn(GetDatabase(), m_id, m_safereturn.zone_id, + m_safereturn.x, m_safereturn.y, m_safereturn.z, m_safereturn.heading); + + SendServerPacket(CreateServerDzLocationPacket(ServerOP_DzSetSafeReturn, location).get()); + } +} + +void DynamicZoneBase::SetSafeReturn(uint32_t zone_id, float x, float y, float z, float heading, bool update_db) +{ + SetSafeReturn({ zone_id, x, y, z, heading }, update_db); +} + +void DynamicZoneBase::SetZoneInLocation(const DynamicZoneLocation& location, bool update_db) +{ + m_zonein = location; + m_has_zonein = true; + + if (update_db) + { + LogDynamicZonesDetail("Saving [{}] zonein zone: [{}] xyzh: ([{}], [{}], [{}], [{}])", + m_id, m_zone_id, m_zonein.x, m_zonein.y, m_zonein.z, m_zonein.heading); + + DynamicZonesRepository::UpdateZoneIn(GetDatabase(), m_id, m_zone_id, + m_zonein.x, m_zonein.y, m_zonein.z, m_zonein.heading, m_has_zonein); + + SendServerPacket(CreateServerDzLocationPacket(ServerOP_DzSetZoneIn, location).get()); + } +} + +void DynamicZoneBase::SetZoneInLocation(float x, float y, float z, float heading, bool update_db) +{ + SetZoneInLocation({ 0, x, y, z, heading }, update_db); +} + +void DynamicZoneBase::SetLeader(const DynamicZoneMember& new_leader, bool update_db) +{ + m_leader = new_leader; + + if (update_db) + { + DynamicZonesRepository::UpdateLeaderID(GetDatabase(), m_id, new_leader.id); + } +} + +uint32_t DynamicZoneBase::GetSecondsRemaining() const +{ + auto remaining = std::chrono::duration_cast(GetDurationRemaining()).count(); + return std::max(0, static_cast(remaining)); +} + +std::unique_ptr DynamicZoneBase::CreateServerMemberAddRemovePacket( + const DynamicZoneMember& member, bool removed) +{ + constexpr uint32_t pack_size = sizeof(ServerDzMember_Struct); + auto pack = std::make_unique(ServerOP_DzAddRemoveMember, pack_size); + auto buf = reinterpret_cast(pack->pBuffer); + buf->dz_id = GetID(); + buf->dz_zone_id = GetZoneID(); + buf->dz_instance_id = GetInstanceID(); + buf->sender_zone_id = GetCurrentZoneID(); + buf->sender_instance_id = GetCurrentInstanceID(); + buf->removed = removed; + buf->character_id = member.id; + buf->character_status = static_cast(member.status); + strn0cpy(buf->character_name, member.name.c_str(), sizeof(buf->character_name)); + + return pack; +} + +std::unique_ptr DynamicZoneBase::CreateServerMemberSwapPacket( + const DynamicZoneMember& remove_member, const DynamicZoneMember& add_member) +{ + constexpr uint32_t pack_size = sizeof(ServerDzMemberSwap_Struct); + auto pack = std::make_unique(ServerOP_DzSwapMembers, pack_size); + auto buf = reinterpret_cast(pack->pBuffer); + buf->dz_id = GetID(); + buf->dz_zone_id = GetZoneID(); + buf->dz_instance_id = GetInstanceID(); + buf->sender_zone_id = GetCurrentZoneID(); + buf->sender_instance_id = GetCurrentInstanceID(); + buf->add_character_status = static_cast(add_member.status); + buf->add_character_id = add_member.id; + buf->remove_character_id = remove_member.id; + strn0cpy(buf->add_character_name, add_member.name.c_str(), sizeof(buf->add_character_name)); + strn0cpy(buf->remove_character_name, remove_member.name.c_str(), sizeof(buf->remove_character_name)); + + return pack; +} + +std::unique_ptr DynamicZoneBase::CreateServerRemoveAllMembersPacket() +{ + constexpr uint32_t pack_size = sizeof(ServerDzID_Struct); + auto pack = std::make_unique(ServerOP_DzRemoveAllMembers, pack_size); + auto buf = reinterpret_cast(pack->pBuffer); + buf->dz_id = GetID(); + buf->dz_zone_id = GetZoneID(); + buf->dz_instance_id = GetInstanceID(); + buf->sender_zone_id = GetCurrentZoneID(); + buf->sender_instance_id = GetCurrentInstanceID(); + + return pack; +} + +std::unique_ptr DynamicZoneBase::CreateServerDzLocationPacket( + uint16_t server_opcode, const DynamicZoneLocation& location) +{ + constexpr uint32_t pack_size = sizeof(ServerDzLocation_Struct); + auto pack = std::make_unique(server_opcode, pack_size); + auto buf = reinterpret_cast(pack->pBuffer); + buf->dz_id = GetID(); + buf->sender_zone_id = GetCurrentZoneID(); + buf->sender_instance_id = GetCurrentInstanceID(); + buf->zone_id = location.zone_id; + buf->x = location.x; + buf->y = location.y; + buf->z = location.z; + buf->heading = location.heading; + + return pack; +} + +std::unique_ptr DynamicZoneBase::CreateServerMemberStatusPacket( + uint32_t character_id, DynamicZoneMemberStatus status) +{ + constexpr uint32_t pack_size = sizeof(ServerDzMemberStatus_Struct); + auto pack = std::make_unique(ServerOP_DzUpdateMemberStatus, pack_size); + auto buf = reinterpret_cast(pack->pBuffer); + buf->dz_id = GetID(); + buf->sender_zone_id = GetCurrentZoneID(); + buf->sender_instance_id = GetCurrentInstanceID(); + buf->status = static_cast(status); + buf->character_id = character_id; + + return pack; +} + +uint32_t DynamicZoneBase::GetDatabaseMemberCount() +{ + return DynamicZoneMembersRepository::GetCountWhere(GetDatabase(), + fmt::format("dynamic_zone_id = {}", m_id)); +} + +bool DynamicZoneBase::HasDatabaseMember(uint32_t character_id) +{ + if (character_id == 0) + { + return false; + } + + auto entries = DynamicZoneMembersRepository::GetWhere(GetDatabase(), fmt::format( + "dynamic_zone_id = {} AND character_id = {}", + m_id, character_id + )); + + return entries.size() != 0; +} + +void DynamicZoneBase::AddInternalMember(const DynamicZoneMember& member) +{ + if (!HasMember(member.id)) + { + m_members.emplace_back(member); + } +} + +void DynamicZoneBase::RemoveInternalMember(uint32_t character_id) +{ + m_members.erase(std::remove_if(m_members.begin(), m_members.end(), + [&](const DynamicZoneMember& member) { return member.id == character_id; } + ), m_members.end()); +} + +bool DynamicZoneBase::HasMember(uint32_t character_id) +{ + return std::any_of(m_members.begin(), m_members.end(), + [&](const DynamicZoneMember& member) { return member.id == character_id; }); +} + +bool DynamicZoneBase::HasMember(const std::string& character_name) +{ + return std::any_of(m_members.begin(), m_members.end(), + [&](const DynamicZoneMember& member) { + return strcasecmp(member.name.c_str(), character_name.c_str()) == 0; + }); +} + +DynamicZoneMember DynamicZoneBase::GetMemberData(uint32_t character_id) +{ + auto it = std::find_if(m_members.begin(), m_members.end(), + [&](const DynamicZoneMember& member) { return member.id == character_id; }); + + DynamicZoneMember member_data; + if (it != m_members.end()) + { + member_data = *it; + } + return member_data; +} + +DynamicZoneMember DynamicZoneBase::GetMemberData(const std::string& character_name) +{ + auto it = std::find_if(m_members.begin(), m_members.end(), + [&](const DynamicZoneMember& member) { + return strcasecmp(member.name.c_str(), character_name.c_str()) == 0; + }); + + DynamicZoneMember member_data; + if (it != m_members.end()) + { + member_data = *it; + } + return member_data; +} + +bool DynamicZoneBase::SetInternalMemberStatus(uint32_t character_id, DynamicZoneMemberStatus status) +{ + if (status == DynamicZoneMemberStatus::InDynamicZone && !RuleB(DynamicZone, EnableInDynamicZoneStatus)) + { + status = DynamicZoneMemberStatus::Online; + } + + if (character_id == m_leader.id) + { + m_leader.status = status; + } + + auto it = std::find_if(m_members.begin(), m_members.end(), + [&](const DynamicZoneMember& member) { return member.id == character_id; }); + + if (it != m_members.end() && it->status != status) + { + it->status = status; + return true; + } + + return false; +} + +void DynamicZoneBase::SetMemberStatus(uint32_t character_id, DynamicZoneMemberStatus status) +{ + auto update_member = GetMemberData(character_id); + if (update_member.IsValid()) + { + ProcessMemberStatusChange(character_id, status); + SendServerPacket(CreateServerMemberStatusPacket(character_id, status).get()); + } +} + +void DynamicZoneBase::ProcessMemberAddRemove(const DynamicZoneMember& member, bool removed) +{ + if (!removed) + { + AddInternalMember(member); + } + else + { + RemoveInternalMember(member.id); + } +} + +bool DynamicZoneBase::ProcessMemberStatusChange(uint32_t character_id, DynamicZoneMemberStatus status) +{ + return SetInternalMemberStatus(character_id, status); +} + +std::string DynamicZoneBase::GetDynamicZoneTypeName(DynamicZoneType dz_type) +{ + switch (dz_type) + { + case DynamicZoneType::Expedition: + return "Expedition"; + case DynamicZoneType::Tutorial: + return "Tutorial"; + case DynamicZoneType::Task: + return "Task"; + case DynamicZoneType::Mission: + return "Mission"; + case DynamicZoneType::Quest: + return "Quest"; + } + return "Unknown"; +} + +EQ::Net::DynamicPacket DynamicZoneBase::GetSerializedDzPacket() +{ + EQ::Net::DynamicPacket dyn_pack; + dyn_pack.PutSerialize(0, *this); + + LogDynamicZonesDetail("Serialized server dz size [{}]", dyn_pack.Length()); + return dyn_pack; +} + +std::unique_ptr DynamicZoneBase::CreateServerDzCreatePacket( + uint16_t origin_zone_id, uint16_t origin_instance_id) +{ + EQ::Net::DynamicPacket dyn_pack = GetSerializedDzPacket(); + + auto pack_size = sizeof(ServerDzCreateSerialized_Struct) + dyn_pack.Length(); + auto pack = std::make_unique(ServerOP_DzCreated, static_cast(pack_size)); + auto buf = reinterpret_cast(pack->pBuffer); + buf->origin_zone_id = origin_zone_id; + buf->origin_instance_id = origin_instance_id; + buf->cereal_size = static_cast(dyn_pack.Length()); + memcpy(buf->cereal_data, dyn_pack.Data(), dyn_pack.Length()); + + return pack; +} + +void DynamicZoneBase::LoadSerializedDzPacket(char* cereal_data, uint32_t cereal_size) +{ + LogDynamicZonesDetail("Deserializing server dz size [{}]", cereal_size); + EQ::Util::MemoryStreamReader ss(cereal_data, cereal_size); + cereal::BinaryInputArchive archive(ss); + archive(*this); +} diff --git a/common/dynamic_zone_base.h b/common/dynamic_zone_base.h new file mode 100644 index 000000000..eafea5ca0 --- /dev/null +++ b/common/dynamic_zone_base.h @@ -0,0 +1,211 @@ +#ifndef COMMON_DYNAMIC_ZONE_BASE_H +#define COMMON_DYNAMIC_ZONE_BASE_H + +#include "eq_constants.h" +#include "net/packet.h" +#include "repositories/dynamic_zones_repository.h" +#include "repositories/dynamic_zone_members_repository.h" +#include +#include +#include +#include +#include +#include + +class Database; +class ServerPacket; + +struct DynamicZoneMember +{ + uint32_t id = 0; + std::string name; + DynamicZoneMemberStatus status = DynamicZoneMemberStatus::Unknown; + + DynamicZoneMember() = default; + DynamicZoneMember(uint32_t id, std::string name_) + : id(id), name{std::move(name_)} {} + DynamicZoneMember(uint32_t id, std::string name_, DynamicZoneMemberStatus status_) + : id(id), name{std::move(name_)}, status(status_) {} + + bool IsOnline() const { return status == DynamicZoneMemberStatus::Online || + status == DynamicZoneMemberStatus::InDynamicZone; } + bool IsValid() const { return id != 0 && !name.empty(); } + + template + void serialize(Archive& archive) + { + archive(id, name, status); + } +}; + +struct DynamicZoneLocation +{ + uint32_t zone_id = 0; + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; + float heading = 0.0f; + + DynamicZoneLocation() = default; + DynamicZoneLocation(uint32_t zone_id_, float x_, float y_, float z_, float heading_) + : zone_id(zone_id_), x(x_), y(y_), z(z_), heading(heading_) {} + + template + void serialize(Archive& archive) + { + archive(zone_id, x, y, z, heading); + } +}; + +class DynamicZoneBase +{ +public: + virtual ~DynamicZoneBase() = default; + DynamicZoneBase(const DynamicZoneBase&) = default; + DynamicZoneBase(DynamicZoneBase&&) = default; + DynamicZoneBase& operator=(const DynamicZoneBase&) = default; + DynamicZoneBase& operator=(DynamicZoneBase&&) = default; + DynamicZoneBase() = default; + DynamicZoneBase(uint32_t dz_id) : m_id(dz_id) {} + DynamicZoneBase(DynamicZoneType type) : m_type(type) {} + DynamicZoneBase(DynamicZonesRepository::DynamicZoneInstance&& entry); + + static std::string GetDynamicZoneTypeName(DynamicZoneType dz_type); + + virtual void SetSecondsRemaining(uint32_t seconds_remaining) = 0; + + uint64_t GetExpireTime() const { return std::chrono::system_clock::to_time_t(m_expire_time); } + uint32_t GetID() const { return m_id; } + uint16_t GetInstanceID() const { return static_cast(m_instance_id); } + uint32_t GetLeaderID() const { return m_leader.id; } + uint32_t GetMaxPlayers() const { return m_max_players; } + uint32_t GetMemberCount() const { return static_cast(m_members.size()); } + uint32_t GetMinPlayers() const { return m_min_players; } + uint32_t GetSecondsRemaining() const; + uint16_t GetZoneID() const { return static_cast(m_zone_id); } + uint32_t GetZoneIndex() const { return (m_instance_id << 16) | (m_zone_id & 0xffff); } + uint32_t GetZoneVersion() const { return m_zone_version; } + DynamicZoneType GetType() const { return m_type; } + const std::string& GetLeaderName() const { return m_leader.name; } + const std::string& GetName() const { return m_name; } + const std::string& GetUUID() const { return m_uuid; } + const DynamicZoneMember& GetLeader() const { return m_leader; } + const std::vector& GetMembers() const { return m_members; } + const DynamicZoneLocation& GetCompassLocation() const { return m_compass; } + const DynamicZoneLocation& GetSafeReturnLocation() const { return m_safereturn; } + const DynamicZoneLocation& GetZoneInLocation() const { return m_zonein; } + std::chrono::system_clock::duration GetDurationRemaining() const { return m_expire_time - std::chrono::system_clock::now(); } + + bool AddMember(const DynamicZoneMember& add_member); + void AddMemberFromRepositoryResult(DynamicZoneMembersRepository::MemberWithName&& entry); + uint32_t GetDatabaseMemberCount(); + DynamicZoneMember GetMemberData(uint32_t character_id); + DynamicZoneMember GetMemberData(const std::string& character_name); + EQ::Net::DynamicPacket GetSerializedDzPacket(); + bool HasDatabaseMember(uint32_t character_id); + bool HasMember(uint32_t character_id); + bool HasMember(const std::string& character_name); + bool HasMembers() const { return !m_members.empty(); } + bool HasZoneInLocation() const { return m_has_zonein; } + bool IsExpired() const { return m_expire_time < std::chrono::system_clock::now(); } + bool IsInstanceID(uint32_t instance_id) const { return (m_instance_id != 0 && m_instance_id == instance_id); } + bool IsValid() const { return m_instance_id != 0; } + bool IsSameDz(uint32_t zone_id, uint32_t instance_id) const { return zone_id == m_zone_id && instance_id == m_instance_id; } + void LoadSerializedDzPacket(char* cereal_data, uint32_t cereal_size); + void RemoveAllMembers(); + bool RemoveMember(uint32_t character_id); + bool RemoveMember(const std::string& character_name); + bool RemoveMember(const DynamicZoneMember& remove_member); + void SaveMembers(const std::vector& members); + void SetCompass(const DynamicZoneLocation& location, bool update_db = false); + void SetCompass(uint32_t zone_id, float x, float y, float z, bool update_db = false); + void SetDuration(uint32_t seconds) { m_duration = std::chrono::seconds(seconds); } + void SetLeader(const DynamicZoneMember& leader, bool update_db = false); + void SetMaxPlayers(uint32_t max_players) { m_max_players = max_players; } + void SetMemberStatus(uint32_t character_id, DynamicZoneMemberStatus status); + void SetMinPlayers(uint32_t min_players) { m_min_players = min_players; } + void SetName(const std::string& name) { m_name = name; } + void SetSafeReturn(const DynamicZoneLocation& location, bool update_db = false); + void SetSafeReturn(uint32_t zone_id, float x, float y, float z, float heading, bool update_db = false); + void SetType(DynamicZoneType type) { m_type = type; } + void SetUUID(std::string uuid) { m_uuid = std::move(uuid); } + void SetZoneInLocation(const DynamicZoneLocation& location, bool update_db = false); + void SetZoneInLocation(float x, float y, float z, float heading, bool update_db = false); + bool SwapMember(const DynamicZoneMember& add_member, const std::string& remove_char_name); + +protected: + virtual uint16_t GetCurrentInstanceID() { return 0; } + virtual uint16_t GetCurrentZoneID() { return 0; } + virtual Database& GetDatabase() = 0; + virtual void ProcessCompassChange(const DynamicZoneLocation& location) { m_compass = location; } + virtual void ProcessMemberAddRemove(const DynamicZoneMember& member, bool removed); + virtual bool ProcessMemberStatusChange(uint32_t member_id, DynamicZoneMemberStatus status); + virtual void ProcessRemoveAllMembers(bool silent = false) { m_members.clear(); } + virtual bool SendServerPacket(ServerPacket* packet) = 0; + + void AddInternalMember(const DynamicZoneMember& member); + uint32_t Create(); + uint32_t CreateInstance(); + void LoadRepositoryResult(DynamicZonesRepository::DynamicZoneInstance&& dz_entry); + void RemoveInternalMember(uint32_t character_id); + uint32_t SaveToDatabase(); + bool SetInternalMemberStatus(uint32_t character_id, DynamicZoneMemberStatus status); + + std::unique_ptr CreateServerDzCreatePacket(uint16_t origin_zone_id, uint16_t origin_instance_id); + std::unique_ptr CreateServerDzLocationPacket(uint16_t server_opcode, const DynamicZoneLocation& location); + std::unique_ptr CreateServerMemberAddRemovePacket(const DynamicZoneMember& member, bool removed); + std::unique_ptr CreateServerMemberStatusPacket(uint32_t character_id, DynamicZoneMemberStatus status); + std::unique_ptr CreateServerMemberSwapPacket(const DynamicZoneMember& remove_member, const DynamicZoneMember& add_member); + std::unique_ptr CreateServerRemoveAllMembersPacket(); + + uint32_t m_id = 0; + uint32_t m_zone_id = 0; + uint32_t m_instance_id = 0; + uint32_t m_zone_version = 0; + uint32_t m_min_players = 0; + uint32_t m_max_players = 0; + bool m_never_expires = false; + bool m_has_zonein = false; + bool m_has_member_statuses = false; + std::string m_name; + std::string m_uuid; + DynamicZoneMember m_leader; + DynamicZoneType m_type{ DynamicZoneType::None }; + DynamicZoneLocation m_compass; + DynamicZoneLocation m_safereturn; + DynamicZoneLocation m_zonein; + std::chrono::seconds m_duration; + std::chrono::time_point m_start_time; + std::chrono::time_point m_expire_time; + std::vector m_members; + +public: + template + void serialize(Archive& archive) + { + archive( + m_id, + m_zone_id, + m_instance_id, + m_zone_version, + m_min_players, + m_max_players, + m_never_expires, + m_has_zonein, + m_has_member_statuses, + m_name, + m_uuid, + m_leader, + m_type, + m_compass, + m_safereturn, + m_zonein, + m_duration, + m_start_time, + m_expire_time, + m_members + ); + } +}; + +#endif diff --git a/common/emu_constants.cpp b/common/emu_constants.cpp index 8a6d2a8bd..4d5c40b64 100644 --- a/common/emu_constants.cpp +++ b/common/emu_constants.cpp @@ -18,6 +18,9 @@ */ #include "emu_constants.h" +#include "languages.h" +#include "data_verification.h" +#include "bodytypes.h" int16 EQ::invtype::GetInvTypeSize(int16 inv_type) { @@ -147,8 +150,235 @@ const char *EQ::constants::GetStanceName(StanceType stance_type) { } int EQ::constants::ConvertStanceTypeToIndex(StanceType stance_type) { - if (stance_type >= EQ::constants::stancePassive && stance_type <= EQ::constants::stanceBurnAE) + if (EQ::ValueWithin(stance_type, EQ::constants::stancePassive, EQ::constants::stanceBurnAE)) { return (stance_type - EQ::constants::stancePassive); + } return 0; } + +const std::map& EQ::constants::GetLanguageMap() +{ + static const std::map language_map = { + { LANG_COMMON_TONGUE, "Common Tongue" }, + { LANG_BARBARIAN, "Barbarian" }, + { LANG_ERUDIAN, "Erudian" }, + { LANG_ELVISH, "Elvish" }, + { LANG_DARK_ELVISH, "Dark Elvish" }, + { LANG_DWARVISH, "Dwarvish" }, + { LANG_TROLL, "Troll" }, + { LANG_OGRE, "Ogre" }, + { LANG_GNOMISH, "Gnomish" }, + { LANG_HALFLING, "Halfling" }, + { LANG_THIEVES_CANT, "Thieves Cant" }, + { LANG_OLD_ERUDIAN, "Old Erudian" }, + { LANG_ELDER_ELVISH, "Elder Elvish" }, + { LANG_FROGLOK, "Froglok" }, + { LANG_GOBLIN, "Goblin" }, + { LANG_GNOLL, "Gnoll" }, + { LANG_COMBINE_TONGUE, "Combine Tongue" }, + { LANG_ELDER_TEIRDAL, "Elder Teirdal" }, + { LANG_LIZARDMAN, "Lizardman" }, + { LANG_ORCISH, "Orcish" }, + { LANG_FAERIE, "Faerie" }, + { LANG_DRAGON, "Dragon" }, + { LANG_ELDER_DRAGON, "Elder Dragon" }, + { LANG_DARK_SPEECH, "Dark Speech" }, + { LANG_VAH_SHIR, "Vah Shir" }, + { LANG_ALARAN, "Alaran" }, + { LANG_HADAL, "Hadal" }, + { LANG_UNKNOWN, "Unknown" } + }; + return language_map; +} + +std::string EQ::constants::GetLanguageName(int language_id) +{ + if (EQ::ValueWithin(language_id, LANG_COMMON_TONGUE, LANG_UNKNOWN)) { + auto languages = EQ::constants::GetLanguageMap(); + return languages[language_id]; + } + return std::string(); +} + +const std::map& EQ::constants::GetLDoNThemeMap() +{ + static const std::map ldon_theme_map = { + { LDoNThemes::Unused, "Unused" }, + { LDoNThemes::GUK, "Deepest Guk" }, + { LDoNThemes::MIR, "Miragul's Menagerie" }, + { LDoNThemes::MMC, "Mistmoore Catacombs" }, + { LDoNThemes::RUJ, "Rujarkian Hills" }, + { LDoNThemes::TAK, "Takish-Hiz" }, + }; + return ldon_theme_map; +} + +std::string EQ::constants::GetLDoNThemeName(uint32 theme_id) +{ + if (EQ::ValueWithin(theme_id, LDoNThemes::Unused, LDoNThemes::TAK)) { + auto ldon_themes = EQ::constants::GetLDoNThemeMap(); + return ldon_themes[theme_id]; + } + return std::string(); +} + +const std::map& EQ::constants::GetFlyModeMap() +{ + static const std::map flymode_map = { + { GravityBehavior::Ground, "Ground" }, + { GravityBehavior::Flying, "Flying" }, + { GravityBehavior::Levitating, "Levitating" }, + { GravityBehavior::Water, "Water" }, + { GravityBehavior::Floating, "Floating" }, + { GravityBehavior::LevitateWhileRunning, "Levitating While Running" }, + }; + return flymode_map; +} + +std::string EQ::constants::GetFlyModeName(uint8 flymode_id) +{ + if (EQ::ValueWithin(flymode_id, GravityBehavior::Ground, GravityBehavior::LevitateWhileRunning)) { + auto flymodes = EQ::constants::GetFlyModeMap(); + return flymodes[flymode_id]; + } + return std::string(); +} + +const std::map& EQ::constants::GetBodyTypeMap() +{ + static const std::map bodytype_map = { + { BT_Humanoid, "Humanoid" }, + { BT_Lycanthrope, "Lycanthrope" }, + { BT_Undead, "Undead" }, + { BT_Giant, "Giant" }, + { BT_Construct, "Construct" }, + { BT_Extraplanar, "Extraplanar" }, + { BT_Magical, "Magical" }, + { BT_SummonedUndead, "Summoned Undead" }, + { BT_RaidGiant, "Raid Giant" }, + { BT_RaidColdain, "Raid Coldain" }, + { BT_NoTarget, "Untargetable" }, + { BT_Vampire, "Vampire" }, + { BT_Atenha_Ra, "Aten Ha Ra" }, + { BT_Greater_Akheva, "Greater Akheva" }, + { BT_Khati_Sha, "Khati Sha" }, + { BT_Seru, "Seru" }, + { BT_Grieg_Veneficus, "Grieg Veneficus" }, + { BT_Draz_Nurakk, "Draz Nurakk" }, + { BT_Zek, "Zek" }, + { BT_Luggald, "Luggald" }, + { BT_Animal, "Animal" }, + { BT_Insect, "Insect" }, + { BT_Monster, "Monster" }, + { BT_Summoned, "Summoned" }, + { BT_Plant, "Plant" }, + { BT_Dragon, "Dragon" }, + { BT_Summoned2, "Summoned 2" }, + { BT_Summoned3, "Summoned 3" }, + { BT_Dragon2, "Dragon 2" }, + { BT_VeliousDragon, "Velious Dragon" }, + { BT_Familiar, "Familiar" }, + { BT_Dragon3, "Dragon 3" }, + { BT_Boxes, "Boxes" }, + { BT_Muramite, "Muramite" }, + { BT_NoTarget2, "Untargetable 2" }, + { BT_SwarmPet, "Swarm Pet" }, + { BT_MonsterSummon, "Monster Summon" }, + { BT_InvisMan, "Invisible Man" }, + { BT_Special, "Special" }, + }; + return bodytype_map; +} + +std::string EQ::constants::GetBodyTypeName(bodyType bodytype_id) +{ + auto bodytypes = EQ::constants::GetBodyTypeMap(); + if (!bodytypes[bodytype_id].empty()) { + return bodytypes[bodytype_id]; + } + return std::string(); +} + +const std::map& EQ::constants::GetAccountStatusMap() +{ + static const std::map account_status_map = { + { AccountStatus::Player, "Player" }, + { AccountStatus::Steward, "Steward" }, + { AccountStatus::ApprenticeGuide, "Apprentice Guide" }, + { AccountStatus::Guide, "Guide" }, + { AccountStatus::QuestTroupe, "Quest Troupe" }, + { AccountStatus::SeniorGuide, "Senior Guide" }, + { AccountStatus::GMTester, "GM Tester" }, + { AccountStatus::EQSupport, "EQ Support" }, + { AccountStatus::GMStaff, "GM Staff" }, + { AccountStatus::GMAdmin, "GM Admin" }, + { AccountStatus::GMLeadAdmin, "GM Lead Admin" }, + { AccountStatus::QuestMaster, "Quest Master" }, + { AccountStatus::GMAreas, "GM Areas" }, + { AccountStatus::GMCoder, "GM Coder" }, + { AccountStatus::GMMgmt, "GM Mgmt" }, + { AccountStatus::GMImpossible, "GM Impossible" }, + { AccountStatus::Max, "GM Max" } + }; + return account_status_map; +} + +std::string EQ::constants::GetAccountStatusName(uint8 account_status) +{ + auto account_statuses = EQ::constants::GetAccountStatusMap(); + std::string status_name; + for (auto status_level = account_statuses.rbegin(); status_level != account_statuses.rend(); ++status_level) { + if (account_status >= status_level->first) { + status_name = status_level->second; + break; + } + } + + return status_name; +} + +const std::map& EQ::constants::GetConsiderLevelMap() +{ + static const std::map consider_level_map = { + { ConsiderLevel::Ally, "Ally" }, + { ConsiderLevel::Warmly, "Warmly" }, + { ConsiderLevel::Kindly, "Kindly" }, + { ConsiderLevel::Amiably, "Amiably" }, + { ConsiderLevel::Indifferently, "Indifferently" }, + { ConsiderLevel::Apprehensively, "Apprehensively" }, + { ConsiderLevel::Dubiously, "Dubiously" }, + { ConsiderLevel::Threateningly, "Threateningly" }, + { ConsiderLevel::Scowls, "Scowls" } + }; + return consider_level_map; +} + +std::string EQ::constants::GetConsiderLevelName(uint8 faction_consider_level) +{ + auto consider_levels = EQ::constants::GetConsiderLevelMap(); + if (!consider_levels[faction_consider_level].empty()) { + return consider_levels[faction_consider_level]; + } + return std::string(); +} + +const std::map& EQ::constants::GetEnvironmentalDamageMap() +{ + static const std::map damage_type_map = { + { EnvironmentalDamage::Lava, "Lava" }, + { EnvironmentalDamage::Drowning, "Drowning" }, + { EnvironmentalDamage::Falling, "Falling" }, + { EnvironmentalDamage::Trap, "Trap" } + }; + return damage_type_map; +} + +std::string EQ::constants::GetEnvironmentalDamageName(uint8 damage_type) +{ + if (EQ::ValueWithin(damage_type, EnvironmentalDamage::Lava, EnvironmentalDamage::Trap)) { + auto damage_types = EQ::constants::GetEnvironmentalDamageMap(); + return damage_types[damage_type]; + } + return std::string(); +} diff --git a/common/emu_constants.h b/common/emu_constants.h index a29418394..f6272274e 100644 --- a/common/emu_constants.h +++ b/common/emu_constants.h @@ -22,6 +22,7 @@ #include "eq_limits.h" #include "emu_versions.h" +#include "bodytypes.h" #include @@ -220,9 +221,46 @@ namespace EQ stanceBurnAE }; + enum GravityBehavior : uint8 { + Ground, + Flying, + Levitating, + Water, + Floating, + LevitateWhileRunning + }; + + enum EnvironmentalDamage : uint8 { + Lava = 250, + Drowning, + Falling, + Trap + }; + const char *GetStanceName(StanceType stance_type); int ConvertStanceTypeToIndex(StanceType stance_type); + extern const std::map& GetLanguageMap(); + std::string GetLanguageName(int language_id); + + extern const std::map& GetLDoNThemeMap(); + std::string GetLDoNThemeName(uint32 theme_id); + + extern const std::map& GetFlyModeMap(); + std::string GetFlyModeName(uint8 flymode_id); + + extern const std::map& GetBodyTypeMap(); + std::string GetBodyTypeName(bodyType bodytype_id); + + extern const std::map& GetAccountStatusMap(); + std::string GetAccountStatusName(uint8 account_status); + + extern const std::map& GetConsiderLevelMap(); + std::string GetConsiderLevelName(uint8 consider_level); + + extern const std::map& GetEnvironmentalDamageMap(); + std::string GetEnvironmentalDamageName(uint8 damage_type); + const int STANCE_TYPE_FIRST = stancePassive; const int STANCE_TYPE_LAST = stanceBurnAE; const int STANCE_TYPE_COUNT = stanceBurnAE; @@ -325,13 +363,57 @@ namespace EQ Guild }; }; // namespace consent - } /*EQEmu*/ +enum ServerLockType : int { + List, + Lock, + Unlock +}; + +enum AccountStatus : uint8 { + Player = 0, + Steward = 10, + ApprenticeGuide = 20, + Guide = 50, + QuestTroupe = 80, + SeniorGuide = 81, + GMTester = 85, + EQSupport = 90, + GMStaff = 95, + GMAdmin = 100, + GMLeadAdmin = 150, + QuestMaster = 160, + GMAreas = 170, + GMCoder = 180, + GMMgmt = 200, + GMImpossible = 250, + Max = 255 +}; + +enum Invisibility : uint8 { + Visible, + Invisible, + Special = 255 +}; + +enum AugmentActions : int { + Insert, + Remove, + Swap, + Destroy +}; + +enum ConsiderLevel : uint8 { + Ally = 1, + Warmly, + Kindly, + Amiably, + Indifferently, + Apprehensively, + Dubiously, + Threateningly, + Scowls +}; + #endif /*COMMON_EMU_CONSTANTS_H*/ - -/* hack list to prevent circular references - - eq_limits.h:EQ::inventory::LookupEntry::InventoryTypeSize[n]; - -*/ diff --git a/common/emu_oplist.h b/common/emu_oplist.h index 46fd564e6..f5386e9b5 100644 --- a/common/emu_oplist.h +++ b/common/emu_oplist.h @@ -129,8 +129,8 @@ N(OP_DisciplineTimer), N(OP_DisciplineUpdate), N(OP_DiscordMerchantInventory), N(OP_DoGroupLeadershipAbility), -N(OP_DuelResponse), -N(OP_DuelResponse2), +N(OP_DuelDecline), +N(OP_DuelAccept), N(OP_DumpName), N(OP_Dye), N(OP_DynamicWall), @@ -355,7 +355,6 @@ N(OP_OpenContainer), N(OP_OpenDiscordMerchant), N(OP_OpenGuildTributeMaster), N(OP_OpenInventory), -N(OP_OpenNewTasksWindow), N(OP_OpenTributeMaster), N(OP_PDeletePetition), N(OP_PetBuffWindow), @@ -464,6 +463,19 @@ N(OP_SetServerFilter), N(OP_SetStartCity), N(OP_SetTitle), N(OP_SetTitleReply), +N(OP_SharedTaskMemberList), +N(OP_SharedTaskAddPlayer), +N(OP_SharedTaskRemovePlayer), +N(OP_SharedTaskMakeLeader), +N(OP_SharedTaskMemberInvite), +N(OP_SharedTaskInvite), +N(OP_SharedTaskInviteResponse), +N(OP_SharedTaskAcceptNew), +N(OP_SharedTaskMemberChange), +N(OP_SharedTaskPlayerList), +N(OP_SharedTaskSelectWindow), +N(OP_SharedTaskQuit), +N(OP_TaskTimers), N(OP_Shielding), N(OP_ShopDelItem), N(OP_ShopEnd), @@ -499,7 +511,8 @@ N(OP_TaskActivityComplete), N(OP_TaskDescription), N(OP_TaskHistoryReply), N(OP_TaskHistoryRequest), -N(OP_TaskMemberList), +N(OP_TaskRequestTimer), +N(OP_TaskSelectWindow), N(OP_Taunt), N(OP_TestBuff), N(OP_TGB), @@ -567,4 +580,5 @@ N(OP_ZoneServerReady), N(OP_ZoneSpawns), N(OP_ZoneUnavail), N(OP_ResetAA), +N(OP_UnderWorld), // mail and chat opcodes located in ../mail_oplist.h diff --git a/common/eq_constants.h b/common/eq_constants.h index 1a0c517bd..8949ea109 100644 --- a/common/eq_constants.h +++ b/common/eq_constants.h @@ -65,6 +65,7 @@ #define AT_FindBits 46 // set FindBits, whatever those are! #define AT_TextureType 48 // TextureType #define AT_FacePick 49 // Turns off face pick window? maybe ... +#define AT_AntiCheat 51 // sent by the client randomly telling the server how long since last action has occured #define AT_GuildShow 52 // this is what MQ2 call sit, not sure #define AT_Offline 53 // Offline mode @@ -197,6 +198,492 @@ namespace Chat { const uint16 Stun = 340; }; +// generation SQL: +// SELECT CONCAT(' constexpr uint16 ', UPPER(short_name), ' = ' , zoneidnumber, '; // ', long_name) from zone group by zoneidnumber ORDER BY zoneidnumber; +namespace Zones { + constexpr uint16 QEYNOS = 1; // South Qeynos + constexpr uint16 QEYNOS2 = 2; // North Qeynos + constexpr uint16 QRG = 3; // The Surefall Glade + constexpr uint16 QEYTOQRG = 4; // The Qeynos Hills + constexpr uint16 HIGHPASS = 5; // Highpass Hold + constexpr uint16 HIGHKEEP = 6; // High Keep + constexpr uint16 FREPORTN = 8; // North Freeport + constexpr uint16 FREPORTW = 9; // West Freeport + constexpr uint16 FREPORTE = 10; // East Freeport + constexpr uint16 RUNNYEYE = 11; // The Liberated Citadel of Runnyeye + constexpr uint16 QEY2HH1 = 12; // The Western Plains of Karana + constexpr uint16 NORTHKARANA = 13; // The Northern Plains of Karana + constexpr uint16 SOUTHKARANA = 14; // The Southern Plains of Karana + constexpr uint16 EASTKARANA = 15; // Eastern Plains of Karana + constexpr uint16 BEHOLDER = 16; // Gorge of King Xorbb + constexpr uint16 BLACKBURROW = 17; // Blackburrow + constexpr uint16 PAW = 18; // The Lair of the Splitpaw + constexpr uint16 RIVERVALE = 19; // Rivervale + constexpr uint16 KITHICOR = 20; // Kithicor Forest + constexpr uint16 COMMONS = 21; // West Commonlands + constexpr uint16 ECOMMONS = 22; // East Commonlands + constexpr uint16 ERUDNINT = 23; // The Erudin Palace + constexpr uint16 ERUDNEXT = 24; // Erudin + constexpr uint16 NEKTULOS = 25; // The Nektulos Forest + constexpr uint16 CSHOME = 26; // Sunset Home + constexpr uint16 LAVASTORM = 27; // The Lavastorm Mountains + constexpr uint16 NEKTROPOS = 28; // Nektropos + constexpr uint16 HALAS = 29; // Halas + constexpr uint16 EVERFROST = 30; // Everfrost Peaks + constexpr uint16 SOLDUNGA = 31; // Solusek's Eye + constexpr uint16 SOLDUNGB = 32; // Nagafen's Lair + constexpr uint16 MISTY = 33; // Misty Thicket + constexpr uint16 NRO = 34; // Northern Desert of Ro + constexpr uint16 SRO = 35; // Southern Desert of Ro + constexpr uint16 BEFALLEN = 36; // Befallen + constexpr uint16 OASIS = 37; // Oasis of Marr + constexpr uint16 TOX = 38; // Toxxulia Forest + constexpr uint16 HOLE = 39; // The Hole + constexpr uint16 NERIAKA = 40; // Neriak - Foreign Quarter + constexpr uint16 NERIAKB = 41; // Neriak - Commons + constexpr uint16 NERIAKC = 42; // Neriak - 3rd Gate + constexpr uint16 NERIAKD = 43; // Neriak Palace + constexpr uint16 NAJENA = 44; // Najena + constexpr uint16 QCAT = 45; // The Qeynos Aqueduct System + constexpr uint16 INNOTHULE = 46; // Innothule Swamp + constexpr uint16 FEERROTT = 47; // The Feerrott + constexpr uint16 CAZICTHULE = 48; // Accursed Temple of CazicThule + constexpr uint16 OGGOK = 49; // Oggok + constexpr uint16 RATHEMTN = 50; // The Rathe Mountains + constexpr uint16 LAKERATHE = 51; // Lake Rathetear + constexpr uint16 GROBB = 52; // Grobb + constexpr uint16 AVIAK = 53; // Aviak Village + constexpr uint16 GFAYDARK = 54; // The Greater Faydark + constexpr uint16 AKANON = 55; // Ak'Anon + constexpr uint16 STEAMFONT = 56; // Steamfont Mountains + constexpr uint16 LFAYDARK = 57; // The Lesser Faydark + constexpr uint16 CRUSHBONE = 58; // Crushbone + constexpr uint16 MISTMOORE = 59; // The Castle of Mistmoore + constexpr uint16 KALADIMA = 60; // South Kaladim + constexpr uint16 FELWITHEA = 61; // Northern Felwithe + constexpr uint16 FELWITHEB = 62; // Southern Felwithe + constexpr uint16 UNREST = 63; // The Estate of Unrest + constexpr uint16 KEDGE = 64; // Kedge Keep + constexpr uint16 GUKTOP = 65; // The City of Guk + constexpr uint16 GUKBOTTOM = 66; // The Ruins of Old Guk + constexpr uint16 KALADIMB = 67; // North Kaladim + constexpr uint16 BUTCHER = 68; // Butcherblock Mountains + constexpr uint16 OOT = 69; // Ocean of Tears + constexpr uint16 CAULDRON = 70; // Dagnor's Cauldron + constexpr uint16 AIRPLANE = 71; // The Plane of Sky + constexpr uint16 FEARPLANE = 72; // The Plane of Fear + constexpr uint16 PERMAFROST = 73; // The Permafrost Caverns + constexpr uint16 KERRARIDGE = 74; // Kerra Isle + constexpr uint16 PAINEEL = 75; // Paineel + constexpr uint16 HATEPLANE = 76; // Plane of Hate + constexpr uint16 ARENA = 77; // The Arena + constexpr uint16 FIELDOFBONE = 78; // The Field of Bone + constexpr uint16 WARSLIKSWOOD = 79; // The Warsliks Woods + constexpr uint16 SOLTEMPLE = 80; // The Temple of Solusek Ro + constexpr uint16 DROGA = 81; // The Temple of Droga + constexpr uint16 CABWEST = 82; // Cabilis West + constexpr uint16 SWAMPOFNOHOPE = 83; // The Swamp of No Hope + constexpr uint16 FIRIONA = 84; // Firiona Vie + constexpr uint16 LAKEOFILLOMEN = 85; // Lake of Ill Omen + constexpr uint16 DREADLANDS = 86; // The Dreadlands + constexpr uint16 BURNINGWOOD = 87; // The Burning Wood + constexpr uint16 KAESORA = 88; // Kaesora + constexpr uint16 SEBILIS = 89; // The Ruins of Sebilis + constexpr uint16 CITYMIST = 90; // The City of Mist + constexpr uint16 SKYFIRE = 91; // The Skyfire Mountains + constexpr uint16 FRONTIERMTNS = 92; // Frontier Mountains + constexpr uint16 OVERTHERE = 93; // The Overthere + constexpr uint16 EMERALDJUNGLE = 94; // The Emerald Jungle + constexpr uint16 TRAKANON = 95; // Trakanon's Teeth + constexpr uint16 TIMOROUS = 96; // Timorous Deep + constexpr uint16 KURN = 97; // Kurn's Tower + constexpr uint16 ERUDSXING = 98; // Erud's Crossing + constexpr uint16 STONEBRUNT = 100; // The Stonebrunt Mountains + constexpr uint16 WARRENS = 101; // The Warrens + constexpr uint16 KARNOR = 102; // Karnor's Castle + constexpr uint16 CHARDOK = 103; // Chardok + constexpr uint16 DALNIR = 104; // The Crypt of Dalnir + constexpr uint16 CHARASIS = 105; // The Howling Stones + constexpr uint16 CABEAST = 106; // Cabilis East + constexpr uint16 NURGA = 107; // The Mines of Nurga + constexpr uint16 VEESHAN = 108; // Veeshan's Peak + constexpr uint16 VEKSAR = 109; // Veksar + constexpr uint16 ICECLAD = 110; // The Iceclad Ocean + constexpr uint16 FROZENSHADOW = 111; // The Tower of Frozen Shadow + constexpr uint16 VELKETOR = 112; // Velketor's Labyrinth + constexpr uint16 KAEL = 113; // Kael Drakkel + constexpr uint16 SKYSHRINE = 114; // Skyshrine + constexpr uint16 THURGADINA = 115; // The City of Thurgadin + constexpr uint16 EASTWASTES = 116; // Eastern Wastes + constexpr uint16 COBALTSCAR = 117; // Cobaltscar + constexpr uint16 GREATDIVIDE = 118; // The Great Divide + constexpr uint16 WAKENING = 119; // The Wakening Land + constexpr uint16 WESTWASTES = 120; // The Western Wastes + constexpr uint16 CRYSTAL = 121; // The Crystal Caverns + constexpr uint16 NECROPOLIS = 123; // Dragon Necropolis + constexpr uint16 TEMPLEVEESHAN = 124; // The Temple of Veeshan + constexpr uint16 SIRENS = 125; // Siren's Grotto + constexpr uint16 MISCHIEFPLANE = 126; // The Plane of Mischief + constexpr uint16 GROWTHPLANE = 127; // The Plane of Growth + constexpr uint16 SLEEPER = 128; // The Sleeper's Tomb + constexpr uint16 THURGADINB = 129; // Icewell Keep + constexpr uint16 ERUDSXING2 = 130; // Marauders Mire + constexpr uint16 SHADOWHAVEN = 150; // Shadow Haven + constexpr uint16 BAZAAR = 151; // The Bazaar + constexpr uint16 NEXUS = 152; // Nexus + constexpr uint16 ECHO_ = 153; // The Echo Caverns + constexpr uint16 ACRYLIA = 154; // The Acrylia Caverns + constexpr uint16 SHARVAHL = 155; // The City of Shar Vahl + constexpr uint16 PALUDAL = 156; // The Paludal Caverns + constexpr uint16 FUNGUSGROVE = 157; // The Fungus Grove + constexpr uint16 VEXTHAL = 158; // Vex Thal + constexpr uint16 SSERU = 159; // Sanctus Seru + constexpr uint16 KATTA = 160; // Katta Castellum + constexpr uint16 NETHERBIAN = 161; // Netherbian Lair + constexpr uint16 SSRATEMPLE = 162; // Ssraeshza Temple + constexpr uint16 GRIEGSEND = 163; // Grieg's End + constexpr uint16 THEDEEP = 164; // The Deep + constexpr uint16 SHADEWEAVER = 165; // Shadeweaver's Thicket + constexpr uint16 HOLLOWSHADE = 166; // Hollowshade Moor + constexpr uint16 GRIMLING = 167; // Grimling Forest + constexpr uint16 MSERU = 168; // Marus Seru + constexpr uint16 LETALIS = 169; // Mons Letalis + constexpr uint16 TWILIGHT = 170; // The Twilight Sea + constexpr uint16 THEGREY = 171; // The Grey + constexpr uint16 TENEBROUS = 172; // The Tenebrous Mountains + constexpr uint16 MAIDEN = 173; // The Maiden's Eye + constexpr uint16 DAWNSHROUD = 174; // The Dawnshroud Peaks + constexpr uint16 SCARLET = 175; // The Scarlet Desert + constexpr uint16 UMBRAL = 176; // The Umbral Plains + constexpr uint16 AKHEVA = 179; // The Akheva Ruins + constexpr uint16 ARENA2 = 180; // The Arena Two + constexpr uint16 JAGGEDPINE = 181; // The Jaggedpine Forest + constexpr uint16 NEDARIA = 182; // Nedaria's Landing + constexpr uint16 TUTORIAL = 183; // EverQuest Tutorial + constexpr uint16 LOAD = 184; // Loading Zone + constexpr uint16 LOAD2 = 185; // New Loading Zone + constexpr uint16 HATEPLANEB = 186; // The Plane of Hate + constexpr uint16 SHADOWREST = 187; // Shadowrest + constexpr uint16 TUTORIALA = 188; // The Mines of Gloomingdeep + constexpr uint16 TUTORIALB = 189; // The Mines of Gloomingdeep + constexpr uint16 CLZ = 190; // Loading + constexpr uint16 CODECAY = 200; // The Crypt of Decay + constexpr uint16 POJUSTICE = 201; // The Plane of Justice + constexpr uint16 POKNOWLEDGE = 202; // The Plane of Knowledge + constexpr uint16 POTRANQUILITY = 203; // The Plane of Tranquility + constexpr uint16 PONIGHTMARE = 204; // The Plane of Nightmares + constexpr uint16 PODISEASE = 205; // The Plane of Disease + constexpr uint16 POINNOVATION = 206; // The Plane of Innovation + constexpr uint16 POTORMENT = 207; // Torment, the Plane of Pain + constexpr uint16 POVALOR = 208; // The Plane of Valor + constexpr uint16 BOTHUNDER = 209; // Bastion of Thunder + constexpr uint16 POSTORMS = 210; // The Plane of Storms + constexpr uint16 HOHONORA = 211; // The Halls of Honor + constexpr uint16 SOLROTOWER = 212; // The Tower of Solusek Ro + constexpr uint16 POWAR = 213; // Plane of War + constexpr uint16 POTACTICS = 214; // Drunder, the Fortress of Zek + constexpr uint16 POAIR = 215; // The Plane of Air + constexpr uint16 POWATER = 216; // The Plane of Water + constexpr uint16 POFIRE = 217; // The Plane of Fire + constexpr uint16 POEARTHA = 218; // The Plane of Earth + constexpr uint16 POTIMEA = 219; // The Plane of Time + constexpr uint16 HOHONORB = 220; // The Temple of Marr + constexpr uint16 NIGHTMAREB = 221; // The Lair of Terris Thule + constexpr uint16 POEARTHB = 222; // The Plane of Earth + constexpr uint16 POTIMEB = 223; // The Plane of Time + constexpr uint16 GUNTHAK = 224; // The Gulf of Gunthak + constexpr uint16 DULAK = 225; // Dulak's Harbor + constexpr uint16 TORGIRAN = 226; // The Torgiran Mines + constexpr uint16 NADOX = 227; // The Crypt of Nadox + constexpr uint16 HATESFURY = 228; // Hate's Fury + constexpr uint16 GUKA = 229; // Deepest Guk: Cauldron of Lost Souls + constexpr uint16 RUJA = 230; // The Rujarkian Hills: Bloodied Quarries + constexpr uint16 TAKA = 231; // Takish-Hiz: Sunken Library + constexpr uint16 MIRA = 232; // Miragul's Menagerie: Silent Gallery + constexpr uint16 MMCA = 233; // Mistmoore's Catacombs: Forlorn Caverns + constexpr uint16 GUKB = 234; // The Drowning Crypt + constexpr uint16 RUJB = 235; // The Rujarkian Hills: Halls of War + constexpr uint16 TAKB = 236; // Takish-Hiz: Shifting Tower + constexpr uint16 MIRB = 237; // Miragul's Menagerie: Frozen Nightmare + constexpr uint16 MMCB = 238; // Mistmoore's Catacombs: Dreary Grotto + constexpr uint16 GUKC = 239; // Deepest Guk: Ancient Aqueducts + constexpr uint16 RUJC = 240; // The Rujarkian Hills: Wind Bridges + constexpr uint16 TAKC = 241; // Takish-Hiz: Within the Compact + constexpr uint16 MIRC = 242; // The Spider Den + constexpr uint16 MMCC = 243; // Mistmoore's Catacombs: Struggles within the Progeny + constexpr uint16 GUKD = 244; // The Mushroom Grove + constexpr uint16 RUJD = 245; // The Rujarkian Hills: Prison Break + constexpr uint16 TAKD = 246; // Takish-Hiz: Royal Observatory + constexpr uint16 MIRD = 247; // Miragul's Menagerie: Hushed Banquet + constexpr uint16 MMCD = 248; // Mistmoore's Catacombs: Chambers of Eternal Affliction + constexpr uint16 GUKE = 249; // Deepest Guk: The Curse Reborn + constexpr uint16 RUJE = 250; // The Rujarkian Hills: Drudge Hollows + constexpr uint16 TAKE = 251; // Takish-Hiz: River of Recollection + constexpr uint16 MIRE = 252; // The Frosted Halls + constexpr uint16 MMCE = 253; // Mistmoore's Catacombs: Sepulcher of the Damned + constexpr uint16 GUKF = 254; // Deepest Guk: Chapel of the Witnesses + constexpr uint16 RUJF = 255; // The Rujarkian Hills: Fortified Lair of the Taskmasters + constexpr uint16 TAKF = 256; // Takish-Hiz: Sandfall Corridors + constexpr uint16 MIRF = 257; // The Forgotten Wastes + constexpr uint16 MMCF = 258; // Mistmoore's Catacombs: Scion Lair of Fury + constexpr uint16 GUKG = 259; // The Root Garden + constexpr uint16 RUJG = 260; // The Rujarkian Hills: Hidden Vale of Deceit + constexpr uint16 TAKG = 261; // Takish-Hiz: Balancing Chamber + constexpr uint16 MIRG = 262; // Miragul's Menagerie: Heart of the Menagerie + constexpr uint16 MMCG = 263; // Mistmoore's Catacombs: Cesspits of Putrescence + constexpr uint16 GUKH = 264; // Deepest Guk: Accursed Sanctuary + constexpr uint16 RUJH = 265; // The Rujarkian Hills: Blazing Forge + constexpr uint16 TAKH = 266; // Takish-Hiz: Sweeping Tides + constexpr uint16 MIRH = 267; // The Morbid Laboratory + constexpr uint16 MMCH = 268; // Mistmoore's Catacombs: Aisles of Blood + constexpr uint16 RUJI = 269; // The Rujarkian Hills: Arena of Chance + constexpr uint16 TAKI = 270; // Takish-Hiz: Antiquated Palace + constexpr uint16 MIRI = 271; // The Theater of Imprisoned Horror + constexpr uint16 MMCI = 272; // Mistmoore's Catacombs: Halls of Sanguinary Rites + constexpr uint16 RUJJ = 273; // The Rujarkian Hills: Barracks of War + constexpr uint16 TAKJ = 274; // Takish-Hiz: Prismatic Corridors + constexpr uint16 MIRJ = 275; // Miragul's Menagerie: Grand Library + constexpr uint16 MMCJ = 276; // Mistmoore's Catacombs: Infernal Sanctuary + constexpr uint16 CHARDOKB = 277; // Chardok: The Halls of Betrayal + constexpr uint16 SOLDUNGC = 278; // The Caverns of Exile + constexpr uint16 ABYSMAL = 279; // The Abysmal Sea + constexpr uint16 NATIMBI = 280; // Natimbi, the Broken Shores + constexpr uint16 QINIMI = 281; // Qinimi, Court of Nihilia + constexpr uint16 RIWWI = 282; // Riwwi, Coliseum of Games + constexpr uint16 BARINDU = 283; // Barindu, Hanging Gardens + constexpr uint16 FERUBI = 284; // Ferubi, Forgotten Temple of Taelosia + constexpr uint16 SNPOOL = 285; // Sewers of Nihilia, Pool of Sludg + constexpr uint16 SNLAIR = 286; // Sewers of Nihilia, Lair of Trapp + constexpr uint16 SNPLANT = 287; // Sewers of Nihilia, Purifying Pla + constexpr uint16 SNCREMATORY = 288; // Sewers of Nihilia, Emanating Cre + constexpr uint16 TIPT = 289; // Tipt, Treacherous Crags + constexpr uint16 VXED = 290; // Vxed, the Crumbling Caverns + constexpr uint16 YXTTA = 291; // Yxtta, Pulpit of Exiles + constexpr uint16 UQUA = 292; // Uqua, the Ocean God Chantry + constexpr uint16 KODTAZ = 293; // Kod'Taz, Broken Trial Grounds + constexpr uint16 IKKINZ = 294; // Ikkinz, Chambers of Transcendence + constexpr uint16 QVIC = 295; // Qvic, Prayer Grounds of Calling + constexpr uint16 INKTUTA = 296; // Inktu'Ta, the Unmasked Chapel + constexpr uint16 TXEVU = 297; // Txevu, Lair of the Elite + constexpr uint16 TACVI = 298; // Tacvi, The Broken Temple + constexpr uint16 QVICB = 299; // Qvic, the Hidden Vault + constexpr uint16 WALLOFSLAUGHTER = 300; // Wall of Slaughter + constexpr uint16 BLOODFIELDS = 301; // The Bloodfields + constexpr uint16 DRANIKSSCAR = 302; // Dranik's Scar + constexpr uint16 CAUSEWAY = 303; // Nobles' Causeway + constexpr uint16 CHAMBERSA = 304; // Muramite Proving Grounds + constexpr uint16 CHAMBERSB = 305; // Muramite Proving Grounds + constexpr uint16 CHAMBERSC = 306; // Muramite Proving Grounds + constexpr uint16 CHAMBERSD = 307; // Muramite Proving Grounds + constexpr uint16 CHAMBERSE = 308; // Muramite Proving Grounds + constexpr uint16 CHAMBERSF = 309; // Muramite Proving Grounds + constexpr uint16 PROVINGGROUNDS = 316; // Muramite Proving Grounds + constexpr uint16 ANGUISH = 317; // Anguish, the Fallen Palace + constexpr uint16 DRANIKHOLLOWSA = 318; // Dranik's Hollows + constexpr uint16 DRANIKHOLLOWSB = 319; // Dranik's Hollows + constexpr uint16 DRANIKHOLLOWSC = 320; // Dranik's Hollows + constexpr uint16 DRANIKCATACOMBSA = 328; // Catacombs of Dranik + constexpr uint16 DRANIKCATACOMBSB = 329; // Catacombs of Dranik + constexpr uint16 DRANIKCATACOMBSC = 330; // Catacombs of Dranik + constexpr uint16 DRANIKSEWERSA = 331; // Sewers of Dranik + constexpr uint16 DRANIKSEWERSB = 332; // Sewers of Dranik + constexpr uint16 DRANIKSEWERSC = 333; // Sewers of Dranik + constexpr uint16 RIFTSEEKERS = 334; // Riftseekers' Sanctum + constexpr uint16 HARBINGERS = 335; // Harbinger's Spire + constexpr uint16 DRANIK = 336; // The Ruined City of Dranik + constexpr uint16 BROODLANDS = 337; // The Broodlands + constexpr uint16 STILLMOONA = 338; // Stillmoon Temple + constexpr uint16 STILLMOONB = 339; // The Ascent + constexpr uint16 THUNDERCREST = 340; // Thundercrest Isles + constexpr uint16 DELVEA = 341; // Lavaspinner's Lair + constexpr uint16 DELVEB = 342; // Tirranun's Delve + constexpr uint16 THENEST = 343; // The Nest + constexpr uint16 GUILDLOBBY = 344; // Guild Lobby + constexpr uint16 GUILDHALL = 345; // Guild Hall + constexpr uint16 BARTER = 346; // The Barter Hall + constexpr uint16 ILLSALIN = 347; // Ruins of Illsalin + constexpr uint16 ILLSALINA = 348; // Illsalin Marketplace + constexpr uint16 ILLSALINB = 349; // Temple of Korlach + constexpr uint16 ILLSALINC = 350; // The Nargil Pits + constexpr uint16 DREADSPIRE = 351; // Dreadspire Keep + constexpr uint16 DRACHNIDHIVE = 354; // The Hive + constexpr uint16 DRACHNIDHIVEA = 355; // The Hatchery + constexpr uint16 DRACHNIDHIVEB = 356; // The Cocoons + constexpr uint16 DRACHNIDHIVEC = 357; // Queen Sendaii`s Lair + constexpr uint16 WESTKORLACH = 358; // Stoneroot Falls + constexpr uint16 WESTKORLACHA = 359; // Prince's Manor + constexpr uint16 WESTKORLACHB = 360; // Caverns of the Lost + constexpr uint16 WESTKORLACHC = 361; // Lair of the Korlach + constexpr uint16 EASTKORLACH = 362; // The Undershore + constexpr uint16 EASTKORLACHA = 363; // Snarlstone Dens + constexpr uint16 SHADOWSPINE = 364; // Shadow Spine + constexpr uint16 CORATHUS = 365; // Corathus Creep + constexpr uint16 CORATHUSA = 366; // Sporali Caverns + constexpr uint16 CORATHUSB = 367; // The Corathus Mines + constexpr uint16 NEKTULOSA = 368; // Shadowed Grove + constexpr uint16 ARCSTONE = 369; // Arcstone, Isle of Spirits + constexpr uint16 RELIC = 370; // Relic, the Artifact City + constexpr uint16 SKYLANCE = 371; // Skylance + constexpr uint16 DEVASTATION = 372; // The Devastation + constexpr uint16 DEVASTATIONA = 373; // The Seething Wall + constexpr uint16 RAGE = 374; // Sverag, Stronghold of Rage + constexpr uint16 RAGEA = 375; // Razorthorn, Tower of Sullon Zek + constexpr uint16 TAKISHRUINS = 376; // Ruins of Takish-Hiz + constexpr uint16 TAKISHRUINSA = 377; // The Root of Ro + constexpr uint16 ELDDAR = 378; // The Elddar Forest + constexpr uint16 ELDDARA = 379; // Tunare's Shrine + constexpr uint16 THEATER = 380; // Theater of Blood + constexpr uint16 THEATERA = 381; // Deathknell, Tower of Dissonance + constexpr uint16 FREEPORTEAST = 382; // East Freeport + constexpr uint16 FREEPORTWEST = 383; // West Freeport + constexpr uint16 FREEPORTSEWERS = 384; // Freeport Sewers + constexpr uint16 FREEPORTACADEMY = 385; // Academy of Arcane Sciences + constexpr uint16 FREEPORTTEMPLE = 386; // Temple of Marr + constexpr uint16 FREEPORTMILITIA = 387; // Freeport Militia House: My Precious + constexpr uint16 FREEPORTARENA = 388; // Arena + constexpr uint16 FREEPORTCITYHALL = 389; // City Hall + constexpr uint16 FREEPORTTHEATER = 390; // Theater of the Tranquil + constexpr uint16 FREEPORTHALL = 391; // Hall of Truth: Bounty + constexpr uint16 NORTHRO = 392; // North Desert of Ro + constexpr uint16 SOUTHRO = 393; // South Desert of Ro + constexpr uint16 CRESCENT = 394; // Crescent Reach + constexpr uint16 MOORS = 395; // Blightfire Moors + constexpr uint16 STONEHIVE = 396; // Stone Hive + constexpr uint16 MESA = 397; // Goru`kar Mesa + constexpr uint16 ROOST = 398; // Blackfeather Roost + constexpr uint16 STEPPES = 399; // The Steppes + constexpr uint16 ICEFALL = 400; // Icefall Glacier + constexpr uint16 VALDEHOLM = 401; // Valdeholm + constexpr uint16 FROSTCRYPT = 402; // Frostcrypt, Throne of the Shade King + constexpr uint16 SUNDEROCK = 403; // Sunderock Springs + constexpr uint16 VERGALID = 404; // Vergalid Mines + constexpr uint16 DIREWIND = 405; // Direwind Cliffs + constexpr uint16 ASHENGATE = 406; // Ashengate, Reliquary of the Scale + constexpr uint16 HIGHPASSHOLD = 407; // Highpass Hold + constexpr uint16 COMMONLANDS = 408; // The Commonlands + constexpr uint16 OCEANOFTEARS = 409; // The Ocean of Tears + constexpr uint16 KITHFOREST = 410; // Kithicor Forest + constexpr uint16 BEFALLENB = 411; // Befallen + constexpr uint16 HIGHPASSKEEP = 412; // HighKeep + constexpr uint16 INNOTHULEB = 413; // The Innothule Swamp + constexpr uint16 TOXXULIA = 414; // Toxxulia Forest + constexpr uint16 MISTYTHICKET = 415; // The Misty Thicket + constexpr uint16 KATTACASTRUM = 416; // Katta Castrum + constexpr uint16 THALASSIUS = 417; // Thalassius, the Coral Keep + constexpr uint16 ATIIKI = 418; // Jewel of Atiiki + constexpr uint16 ZHISZA = 419; // Zhisza, the Shissar Sanctuary + constexpr uint16 SILYSSAR = 420; // Silyssar, New Chelsith + constexpr uint16 SOLTERIS = 421; // Solteris, the Throne of Ro + constexpr uint16 BARREN = 422; // Barren Coast + constexpr uint16 BURIEDSEA = 423; // The Buried Sea + constexpr uint16 JARDELSHOOK = 424; // Jardel's Hook + constexpr uint16 MONKEYROCK = 425; // Monkey Rock + constexpr uint16 SUNCREST = 426; // Suncrest Isle + constexpr uint16 DEADBONE = 427; // Deadbone Reef + constexpr uint16 BLACKSAIL = 428; // Blacksail Folly + constexpr uint16 MAIDENSGRAVE = 429; // Maiden's Grave + constexpr uint16 REDFEATHER = 430; // Redfeather Isle + constexpr uint16 SHIPMVP = 431; // The Open Sea + constexpr uint16 SHIPMVU = 432; // The Open Sea + constexpr uint16 SHIPPVU = 433; // The Open Sea + constexpr uint16 SHIPUVU = 434; // The Open Sea + constexpr uint16 SHIPMVM = 435; // The Open Sea + constexpr uint16 MECHANOTUS = 436; // Fortress Mechanotus + constexpr uint16 MANSION = 437; // Meldrath's Majestic Mansion + constexpr uint16 STEAMFACTORY = 438; // The Steam Factory + constexpr uint16 SHIPWORKSHOP = 439; // S.H.I.P. Workshop + constexpr uint16 GYROSPIREB = 440; // Gyrospire Beza + constexpr uint16 GYROSPIREZ = 441; // Gyrospire Zeka + constexpr uint16 DRAGONSCALE = 442; // Dragonscale Hills + constexpr uint16 LOPINGPLAINS = 443; // Loping Plains + constexpr uint16 HILLSOFSHADE = 444; // Hills of Shade + constexpr uint16 BLOODMOON = 445; // Bloodmoon Keep + constexpr uint16 CRYSTALLOS = 446; // Crystallos, Lair of the Awakened + constexpr uint16 GUARDIAN = 447; // The Mechamatic Guardian + constexpr uint16 STEAMFONTMTS = 448; // The Steamfont Mountains + constexpr uint16 CRYPTOFSHADE = 449; // Crypt of Shade + constexpr uint16 DRAGONSCALEB = 451; // Deepscar's Den + constexpr uint16 OLDFIELDOFBONE = 452; // Field of Scale + constexpr uint16 OLDKAESORAA = 453; // Kaesora Library + constexpr uint16 OLDKAESORAB = 454; // Kaesora Hatchery + constexpr uint16 OLDKURN = 455; // Kurn's Tower + constexpr uint16 OLDKITHICOR = 456; // Bloody Kithicor + constexpr uint16 OLDCOMMONS = 457; // Old Commonlands + constexpr uint16 OLDHIGHPASS = 458; // Highpass Hold + constexpr uint16 THEVOIDA = 459; // The Void + constexpr uint16 THEVOIDB = 460; // The Void + constexpr uint16 THEVOIDC = 461; // The Void + constexpr uint16 THEVOIDD = 462; // The Void + constexpr uint16 THEVOIDE = 463; // The Void + constexpr uint16 THEVOIDF = 464; // The Void + constexpr uint16 THEVOIDG = 465; // The Void + constexpr uint16 OCEANGREENHILLS = 466; // Oceangreen Hills + constexpr uint16 OCEANGREENVILLAGE = 467; // Oceangreen Village + constexpr uint16 OLDBLACKBURROW = 468; // BlackBurrow + constexpr uint16 BERTOXTEMPLE = 469; // Temple of Bertoxxulous + constexpr uint16 DISCORD = 470; // Korafax, Home of the Riders + constexpr uint16 DISCORDTOWER = 471; // Citadel of the Worldslayer + constexpr uint16 OLDBLOODFIELD = 472; // Old Bloodfields + constexpr uint16 PRECIPICEOFWAR = 473; // The Precipice of War + constexpr uint16 OLDDRANIK = 474; // City of Dranik + constexpr uint16 TOSKIRAKK = 475; // Toskirakk + constexpr uint16 KORASCIAN = 476; // Korascian Warrens + constexpr uint16 RATHECHAMBER = 477; // Rathe Council Chamber + constexpr uint16 BRELLSREST = 480; // Brell's Rest + constexpr uint16 FUNGALFOREST = 481; // Fungal Forest + constexpr uint16 UNDERQUARRY = 482; // The Underquarry + constexpr uint16 COOLINGCHAMBER = 483; // The Cooling Chamber + constexpr uint16 SHININGCITY = 484; // Kernagir, the Shining City + constexpr uint16 ARTHICREX = 485; // Arthicrex + constexpr uint16 FOUNDATION = 486; // The Foundation + constexpr uint16 LICHENCREEP = 487; // Lichen Creep + constexpr uint16 PELLUCID = 488; // Pellucid Grotto + constexpr uint16 STONESNAKE = 489; // Volska's Husk + constexpr uint16 BRELLSTEMPLE = 490; // Brell's Temple + constexpr uint16 CONVORTEUM = 491; // The Convorteum + constexpr uint16 BRELLSARENA = 492; // Brell's Arena + constexpr uint16 WEDDINGCHAPEL = 493; // Wedding Chapel + constexpr uint16 WEDDINGCHAPELDARK = 494; // Wedding Chapel + constexpr uint16 DRAGONCRYPT = 495; // Lair of the Risen + constexpr uint16 FEERROTT2 = 700; // The Feerrott + constexpr uint16 THULEHOUSE1 = 701; // House of Thule + constexpr uint16 THULEHOUSE2 = 702; // House of Thule, Upper Floors + constexpr uint16 HOUSEGARDEN = 703; // The Grounds + constexpr uint16 THULELIBRARY = 704; // The Library + constexpr uint16 WELL = 705; // The Well + constexpr uint16 FALLEN = 706; // Erudin Burning + constexpr uint16 MORELLCASTLE = 707; // Morell's Castle + constexpr uint16 SOMNIUM = 708; // Sanctum Somnium + constexpr uint16 ALKABORMARE = 709; // Al'Kabor's Nightmare + constexpr uint16 MIRAGULMARE = 710; // Miragul's Nightmare + constexpr uint16 THULEDREAM = 711; // Fear Itself + constexpr uint16 NEIGHBORHOOD = 712; // Sunrise Hills + constexpr uint16 ARGATH = 724; // Argath, Bastion of Illdaera + constexpr uint16 ARELIS = 725; // Valley of Lunanyn + constexpr uint16 SARITHCITY = 726; // Sarith, City of Tides + constexpr uint16 RUBAK = 727; // Rubak Oseka, Temple of the Sea + constexpr uint16 BEASTDOMAIN = 728; // Beasts' Domain + constexpr uint16 RESPLENDENT = 729; // The Resplendent Temple + constexpr uint16 PILLARSALRA = 730; // Pillars of Alra + constexpr uint16 WINDSONG = 731; // Windsong Sanctuary + constexpr uint16 CITYOFBRONZE = 732; // Erillion, City of Bronze + constexpr uint16 SEPULCHER = 733; // Sepulcher of Order + constexpr uint16 EASTSEPULCHER = 734; // Sepulcher East + constexpr uint16 WESTSEPULCHER = 735; // Sepulcher West + constexpr uint16 SHARDSLANDING = 752; // Shard's Landing + constexpr uint16 XORBB = 753; // Valley of King Xorbb + constexpr uint16 KAELSHARD = 754; // Kael Drakkel: The King's Madness + constexpr uint16 EASTWASTESSHARD = 755; // East Wastes: Zeixshi-Kar's Awakening + constexpr uint16 CRYSTALSHARD = 756; // The Crystal Caverns: Fragment of Fear + constexpr uint16 BREEDINGGROUNDS = 757; // The Breeding Grounds + constexpr uint16 EVILTREE = 758; // Evantil, the Vile Oak + constexpr uint16 GRELLETH = 759; // Grelleth's Palace, the Chateau of Filth + constexpr uint16 CHAPTERHOUSE = 760; // Chapterhouse of the Fallen + constexpr uint16 ARTTEST = 996; // Art Testing Domain + constexpr uint16 FHALLS = 998; // The Forgotten Halls + constexpr uint16 APPRENTICE = 999; // Designer Apprentice +} + //ZoneChange_Struct->success values #define ZONE_ERROR_NOMSG 0 #define ZONE_ERROR_NOTREADY -1 @@ -438,6 +925,10 @@ static const uint8 SkillDamageTypes[EQ::skills::HIGHEST_SKILL + 1] = // change t static const uint32 MAX_SPELL_DB_ID_VAL = 65535; +static const uint32 DB_FACTION_GEM_CHOPPERS = 255; +static const uint32 DB_FACTION_HERETICS = 265; +static const uint32 DB_FACTION_KING_AKANON = 333; + enum ChatChannelNames : uint16 { ChatChannel_Guild = 0, @@ -464,4 +955,58 @@ namespace ZoneBlockedSpellTypes { const uint8 Region = 2; }; +enum class DynamicZoneType +{ + None = 0, + Expedition, + Tutorial, + Task, + Mission, // Shared Task + Quest +}; + +enum class DynamicZoneMemberStatus : uint8_t +{ + Unknown = 0, + Online, + Offline, + InDynamicZone, + LinkDead +}; + +enum LDoNThemes { + Unused = 0, + GUK, + MIR, + MMC, + RUJ, + TAK +}; + +enum LDoNThemeBits { + UnusedBit = 0, + GUKBit = 1, + MIRBit = 2, + MMCBit = 4, + RUJBit = 8, + TAKBit = 16 +}; + +enum StartZoneIndex { + Odus = 0, + Qeynos, + Halas, + Rivervale, + Freeport, + Neriak, + Grobb, + Oggok, + Kaladim, + GreaterFaydark, + Felwithe, + Akanon, + Cabilis, + SharVahl +}; + #endif /*COMMON_EQ_CONSTANTS_H*/ diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index 57d4c0795..49d6a7454 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -384,7 +384,9 @@ struct NewZone_Struct { /*0716*/ uint32 FastRegenEndurance; /*0720*/ uint32 NPCAggroMaxDist; /*0724*/ uint32 underworld_teleport_index; // > 0 teleports w/ zone point index, invalid succors, if this value is 0, it prevents you from running off edges that would end up underworld -/*0728*/ +/*0728*/ uint32 LavaDamage; // Seen 50 +/*0732*/ uint32 MinLavaDamage; // Seen 10 +/*0736*/ }; /* @@ -446,6 +448,7 @@ struct ManaChange_Struct /*08*/ uint32 spell_id; /*12*/ uint8 keepcasting; // won't stop the cast. Change mana while casting? /*13*/ uint8 padding[3]; // client doesn't read it, garbage data seems like +/*16*/ int32 slot; // -1 normal, otherwise clear ETA and GCD }; struct SwapSpell_Struct @@ -829,7 +832,7 @@ struct LeadershipAA_Struct { * Size: 20 Octets */ struct BindStruct { - /*000*/ uint32 zoneId; + /*000*/ uint32 zone_id; /*004*/ float x; /*008*/ float y; /*012*/ float z; @@ -1772,7 +1775,7 @@ struct GMZoneRequest_Struct { /*0068*/ float x; /*0072*/ float y; /*0076*/ float z; -/*0080*/ char unknown0080[4]; +/*0080*/ float heading; /*0084*/ uint32 success; // 0 if command failed, 1 if succeeded? /*0088*/ // /*072*/ int8 success; // =0 client->server, =1 server->client, -X=specific error @@ -2131,31 +2134,31 @@ struct AdventureLeaderboard_Struct };*/ struct Illusion_Struct { //size: 256 - SoF -/*000*/ uint32 spawnid; -/*004*/ char charname[64]; // -/*068*/ uint16 race; // -/*070*/ char unknown006[2]; -/*072*/ uint8 gender; -/*073*/ uint8 texture; -/*074*/ uint8 unknown008; // -/*075*/ uint8 unknown009; // -/*076*/ uint8 helmtexture; // -/*077*/ uint8 unknown010; // -/*078*/ uint8 unknown011; // -/*079*/ uint8 unknown012; // -/*080*/ uint32 face; // -/*084*/ uint8 hairstyle; // -/*085*/ uint8 haircolor; // -/*086*/ uint8 beard; // -/*087*/ uint8 beardcolor; // -/*088*/ float size; // -/*092*/ uint32 drakkin_heritage; // -/*096*/ uint32 drakkin_tattoo; // -/*100*/ uint32 drakkin_details; // -/*104*/ EQ::TintProfile armor_tint; // -/*140*/ uint8 eyecolor1; // Field Not Identified in any Illusion Struct -/*141*/ uint8 eyecolor2; // Field Not Identified in any Illusion Struct -/*142*/ uint8 unknown138[114]; // +/*000*/ uint32 spawnid; +/*004*/ char charname[64]; // +/*068*/ uint16 race; // +/*070*/ char unknown006[2]; +/*072*/ uint8 gender; +/*073*/ uint8 texture; +/*074*/ uint8 unknown008; // +/*075*/ uint8 unknown009; // +/*076*/ uint8 helmtexture; // +/*077*/ uint8 unknown010; // +/*078*/ uint8 unknown011; // +/*079*/ uint8 unknown012; // +/*080*/ uint32 face; // +/*084*/ uint8 hairstyle; // +/*085*/ uint8 haircolor; // +/*086*/ uint8 beard; // +/*087*/ uint8 beardcolor; // +/*088*/ float size; // +/*092*/ uint32 drakkin_heritage; // +/*096*/ uint32 drakkin_tattoo; // +/*100*/ uint32 drakkin_details; // +/*104*/ EQ::TintProfile armor_tint; // +/*140*/ uint8 eyecolor1; // Field Not Identified in any Illusion Struct +/*141*/ uint8 eyecolor2; // Field Not Identified in any Illusion Struct +/*142*/ uint8 unknown138[114]; // /*256*/ }; @@ -2759,7 +2762,7 @@ struct EnvDamage2_Struct { /*0004*/ uint16 unknown4; /*0006*/ uint32 damage; /*0010*/ uint8 unknown10[12]; -/*0022*/ uint8 dmgtype; //FA = Lava; FC = Falling +/*0022*/ uint8 dmgtype; // FA = Lava, FB = Drowning, FC = Falling, FD = Trap /*0023*/ uint8 unknown2[4]; /*0027*/ uint16 constant; //Always FFFF /*0029*/ uint16 unknown29; @@ -3247,7 +3250,7 @@ struct TraderClick_Struct{ }; struct FormattedMessage_Struct{ - uint32 unknown0; + uint32 unknown0; // 1 means from world server uint32 string_id; uint32 type; char message[0]; @@ -3255,7 +3258,7 @@ struct FormattedMessage_Struct{ struct SimpleMessage_Struct{ uint32 string_id; uint32 color; - uint32 unknown8; + uint32 unknown8; // 1 means from world server }; struct GuildMemberUpdate_Struct { @@ -3714,17 +3717,66 @@ struct SetTitleReply_Struct { uint32 entity_id; }; -struct TaskMemberList_Struct { -/*00*/ uint32 gopher_id; -/*04*/ uint32 unknown04; -/*08*/ uint32 member_count; //1 less than the number of members -/*12*/ char list_pointer[0]; +struct SharedTaskMemberList_Struct { +/*00*/ uint32 gopher_id; +/*04*/ uint32 unknown04; +/*08*/ uint32 member_count; //1 less than the number of members +///*12*/ char list_pointer[0]; + char member_name[1]; //null terminated string + uint32 monster_mission; // class chosen + uint8 task_leader; //boolean flag + /* list is of the form: - char member_name[1] //null terminated string uint8 task_leader //boolean flag */ }; +struct SharedTaskQuit_Struct { + int32 field1; + int32 field2; + int32 field3; +}; + +struct SharedTaskAddPlayer_Struct { + int32 field1; + int32 field2; + char player_name[64]; +}; + +struct SharedTaskMakeLeader_Struct { + int32 field1; + int32 field2; + char player_name[64]; +}; + +struct SharedTaskRemovePlayer_Struct { + int32 field1; + int32 field2; + char player_name[64]; +}; + +struct SharedTaskInvite_Struct { + int32_t unknown00; // probably the unique character id sent in some packets + int32_t invite_id; // invite id sent back in response + char task_name[64]; + char inviter_name[64]; +}; + +struct SharedTaskInviteResponse_Struct { + int32_t unknown00; // 0 + int32_t invite_id; // same id sent in the invite, probably for server verification + int8_t accepted; // 0: declined 1: accepted + int8_t padding[3]; // padding garbage probably +}; + +struct SharedTaskAccept_Struct { + int32_t unknown00; + int32_t unknown04; + uint32_t npc_entity_id; // npc task giver entity id (sent in selection window) + uint32_t task_id; + float reward_multiplier; // added after titanium (sent in selection window) +}; + #if 0 // Old struct not used by Task System implementation but left for reference @@ -3817,7 +3869,7 @@ struct TaskHistory_Struct { #endif struct AcceptNewTask_Struct { - uint32 unknown00; + uint32 task_type; // type sent in selection window uint32 task_id; //set to 0 for 'decline' uint32 task_master_id; //entity ID }; @@ -4291,8 +4343,8 @@ struct AARankPrereq_Struct struct AARankEffect_Struct { int32 effect_id; - int32 base1; - int32 base2; + int32 base_value; + int32 limit_value; int32 slot; }; @@ -4300,8 +4352,8 @@ struct AARankEffect_Struct struct AA_Ability { /*00*/ uint32 skill_id; -/*04*/ uint32 base1; -/*08*/ uint32 base2; +/*04*/ uint32 base_value; +/*08*/ uint32 limit_value; /*12*/ uint32 slot; }; @@ -4861,30 +4913,31 @@ struct ExpeditionInviteResponse_Struct /*079*/ uint8 unknown079; // padding garbage? }; -struct ExpeditionInfo_Struct +struct DynamicZoneInfo_Struct { /*000*/ uint32 client_id; /*004*/ uint32 unknown004; // added after titanium -/*008*/ uint32 assigned; // padded bool, 0: not in expedition (clear data), 1: in expedition +/*008*/ uint32 assigned; // padded bool, 0: clear info, 1: fill window info /*012*/ uint32 max_players; -/*016*/ char expedition_name[128]; +/*016*/ char dz_name[128]; /*144*/ char leader_name[64]; +//*208*/ uint32 dz_type; // only in newer clients, if not 1 (expedition type) window does not auto show when dz info assigned }; -struct ExpeditionMemberEntry_Struct +struct DynamicZoneMemberEntry_Struct { -/*000*/ char name[64]; // variable length, null terminated, max 0x40 (64) -/*064*/ uint8 expedition_status; // 0: unknown, 1: Online, 2: Offline, 3: In Dynamic Zone, 4: Link Dead +/*000*/ char name[64]; // variable length, null terminated, max 0x40 (64) +/*064*/ uint8 online_status; // 0: unknown, 1: Online, 2: Offline, 3: In Dynamic Zone, 4: Link Dead }; -struct ExpeditionMemberList_Struct +struct DynamicZoneMemberList_Struct { /*000*/ uint32 client_id; /*004*/ uint32 member_count; -/*008*/ ExpeditionMemberEntry_Struct members[0]; // variable length +/*008*/ DynamicZoneMemberEntry_Struct members[0]; // variable length }; -struct ExpeditionMemberListName_Struct +struct DynamicZoneMemberListName_Struct { /*000*/ uint32 client_id; /*004*/ uint32 unknown004; @@ -4907,7 +4960,7 @@ struct ExpeditionLockoutTimers_Struct /*008*/ ExpeditionLockoutTimerEntry_Struct timers[0]; }; -struct ExpeditionSetLeaderName_Struct +struct DynamicZoneLeaderName_Struct { /*000*/ uint32 client_id; /*004*/ uint32 unknown004; @@ -5529,6 +5582,23 @@ struct SayLinkBodyFrame_Struct { /*056*/ }; +struct UpdateMovementEntry { + /* 00 */ float Y; + /* 04 */ float X; + /* 08 */ float Z; + /* 12 */ uint8 type; + /* 13 */ unsigned int timestamp; + /* 17 */ +}; + +struct UnderWorld { + /* 00 */ int spawn_id; + /* 04 */ float y; + /* 08 */ float x; + /* 12 */ float z; + /* 16 */ +}; + // Restore structure packing to default #pragma pack() diff --git a/common/eqemu_config.cpp b/common/eqemu_config.cpp index 4f29bb901..1790e7788 100644 --- a/common/eqemu_config.cpp +++ b/common/eqemu_config.cpp @@ -44,6 +44,12 @@ void EQEmuConfig::parse_config() if (_root["server"]["world"]["loginserver"].get("legacy", "0").asString() == "1") { LoginLegacy = true; } LoginAccount = _root["server"]["world"]["loginserver"].get("account", "").asString(); LoginPassword = _root["server"]["world"]["loginserver"].get("password", "").asString(); + + // at least today, this is wrong a majority of the time + // remove this if eqemulator ever upgrades its loginserver + if (LoginHost.find("login.eqemulator.net") != std::string::npos) { + LoginLegacy = true; + } } else { char str[32]; @@ -62,12 +68,19 @@ void EQEmuConfig::parse_config() loginconfig->LoginLegacy = false; if (_root["server"]["world"][str].get("legacy", "0").asString() == "1") { loginconfig->LoginLegacy = true; } + + // at least today, this is wrong a majority of the time + // remove this if eqemulator ever upgrades its loginserver + if (loginconfig->LoginHost.find("login.eqemulator.net") != std::string::npos) { + loginconfig->LoginLegacy = true; + } + loginlist.Insert(loginconfig); } while (LoginCount < 100); } - // from xml converts to json as locked: "", so i default to "false". + // from xml converts to json as locked: "", so i default to "false". //The only way to enable locked is by switching to true, meaning this value is always false until manually set true Locked = false; if (_root["server"]["world"].get("locked", "false").asString() == "true") { Locked = true; } diff --git a/common/eqemu_logsys.cpp b/common/eqemu_logsys.cpp index 368d8fbec..e5f0c63c1 100644 --- a/common/eqemu_logsys.cpp +++ b/common/eqemu_logsys.cpp @@ -22,8 +22,8 @@ #include "rulesys.h" #include "platform.h" #include "string_util.h" -#include "database.h" #include "misc.h" +#include "repositories/logsys_categories_repository.h" #include #include @@ -31,6 +31,7 @@ #include #include #include +#include std::ofstream process_log; @@ -96,7 +97,7 @@ EQEmuLogSys::EQEmuLogSys() */ EQEmuLogSys::~EQEmuLogSys() = default; -void EQEmuLogSys::LoadLogSettingsDefaults() +EQEmuLogSys *EQEmuLogSys::LoadLogSettingsDefaults() { /** * Get Executable platform currently running this code (Zone/World/etc) @@ -127,6 +128,10 @@ void EQEmuLogSys::LoadLogSettingsDefaults() log_settings[Logs::HotReload].log_to_gmsay = static_cast(Logs::General); log_settings[Logs::HotReload].log_to_console = static_cast(Logs::General); log_settings[Logs::Loot].log_to_gmsay = static_cast(Logs::General); + log_settings[Logs::Scheduler].log_to_console = static_cast(Logs::General); + log_settings[Logs::Cheat].log_to_console = static_cast(Logs::General); + log_settings[Logs::HTTP].log_to_console = static_cast(Logs::General); + log_settings[Logs::HTTP].log_to_gmsay = static_cast(Logs::General); /** * RFC 5424 @@ -176,6 +181,8 @@ void EQEmuLogSys::LoadLogSettingsDefaults() else if (EQEmuLogSys::log_platform == EQEmuExePlatform::ExePlatformHC) { platform_file_name = "hc"; } + + return this; } /** @@ -206,11 +213,7 @@ std::string EQEmuLogSys::FormatOutMessageString( const std::string &in_message ) { - std::string return_string; - - if (IsRfc5424LogCategory(log_category)) { - return_string = "[" + GetPlatformName() + "] "; - } + std::string return_string = "[" + GetPlatformName() + "] "; return return_string + "[" + Logs::LogCategoryName[log_category] + "] " + in_message; } @@ -234,9 +237,11 @@ void EQEmuLogSys::ProcessGMSay( } /** - * Check to see if the process that actually ran this is zone + * Processes that actually support hooks */ - if (EQEmuLogSys::log_platform == EQEmuExePlatform::ExePlatformZone) { + if (EQEmuLogSys::log_platform == EQEmuExePlatform::ExePlatformZone || + EQEmuLogSys::log_platform == EQEmuExePlatform::ExePlatformWorld + ) { on_log_gmsay_hook(log_category, message); } } @@ -604,3 +609,76 @@ void EQEmuLogSys::EnableConsoleLogging() log_settings[log_index].is_category_enabled = 1; } } + +EQEmuLogSys *EQEmuLogSys::LoadLogDatabaseSettings() +{ + auto categories = LogsysCategoriesRepository::GetWhere( + *m_database, + "TRUE ORDER BY log_category_id" + ); + + // keep track of categories + std::vector db_categories{}; + db_categories.reserve(categories.size()); + + // loop through database categories + for (auto &c: categories) { + if (c.log_category_id <= Logs::None || c.log_category_id >= Logs::MaxCategoryID) { + continue; + } + + log_settings[c.log_category_id].log_to_console = static_cast(c.log_to_console); + log_settings[c.log_category_id].log_to_file = static_cast(c.log_to_file); + log_settings[c.log_category_id].log_to_gmsay = static_cast(c.log_to_gmsay); + + // Determine if any output method is enabled for the category + // and set it to 1 so it can used to check if category is enabled + const bool log_to_console = log_settings[c.log_category_id].log_to_console > 0; + const bool log_to_file = log_settings[c.log_category_id].log_to_file > 0; + const bool log_to_gmsay = log_settings[c.log_category_id].log_to_gmsay > 0; + const bool is_category_enabled = log_to_console || log_to_file || log_to_gmsay; + + if (is_category_enabled) { + log_settings[c.log_category_id].is_category_enabled = 1; + } + + // This determines whether or not the process needs to actually file log anything. + // If we go through this whole loop and nothing is set to any debug level, there + // is no point to create a file or keep anything open + if (log_settings[c.log_category_id].log_to_file > 0) { + LogSys.file_logs_enabled = true; + } + + db_categories.emplace_back(c.log_category_id); + } + + // Auto inject categories that don't exist in the database... + for (int i = Logs::AA; i != Logs::MaxCategoryID; i++) { + if (std::find(db_categories.begin(), db_categories.end(), i) == db_categories.end()) { + LogInfo( + "Automatically adding new log category [{0}]", + Logs::LogCategoryName[i] + ); + + auto new_category = LogsysCategoriesRepository::NewEntity(); + new_category.log_category_id = i; + new_category.log_category_description = EscapeString(Logs::LogCategoryName[i]); + new_category.log_to_console = log_settings[i].log_to_console; + new_category.log_to_gmsay = log_settings[i].log_to_gmsay; + new_category.log_to_file = log_settings[i].log_to_file; + + LogsysCategoriesRepository::InsertOne(*m_database, new_category); + } + } + + LogInfo("Loaded [{}] log categories", categories.size()); + + return this; +} + +EQEmuLogSys *EQEmuLogSys::SetDatabase(Database *db) +{ + m_database = db; + + return this; +} diff --git a/common/eqemu_logsys.h b/common/eqemu_logsys.h index d28478a97..11808d891 100644 --- a/common/eqemu_logsys.h +++ b/common/eqemu_logsys.h @@ -23,8 +23,9 @@ #include #include -#include +#include #include +#include #ifdef _WIN32 #ifdef utf16_to_utf8 @@ -37,9 +38,9 @@ namespace Logs { enum DebugLevel { - General = 1, /* 1 - Low-Level general debugging, useful info on single line */ - Moderate, /* 2 - Informational based, used in functions, when particular things load */ - Detail /* 3 - Use this for extreme detail in logging, usually in extreme debugging in the stack or interprocess communication */ + General = 1, // 1 - Low-Level general debugging, useful info on single line + Moderate, // 2 - Informational based, used in functions, when particular things load + Detail // 3 - Use this for extreme detail in logging, usually in extreme debugging in the stack or interprocess communication }; /** @@ -120,13 +121,19 @@ namespace Logs { Loot, Expeditions, DynamicZones, + Scheduler, + Cheat, + ClientList, + DiaWind, + HTTP, + Saylink, MaxCategoryID /* Don't Remove this */ }; /** * If you add to this, make sure you update LogCategory */ - static const char* LogCategoryName[LogCategory::MaxCategoryID] = { + static const char *LogCategoryName[LogCategory::MaxCategoryID] = { "", "AA", "AI", @@ -199,11 +206,19 @@ namespace Logs { "Loot", "Expeditions", "DynamicZones", + "Scheduler", + "Cheat", + "ClientList", + "DialogueWindow", + "HTTP", + "Saylink", }; } #include "eqemu_logsys_log_aliases.h" +class Database; + class EQEmuLogSys { public: EQEmuLogSys(); @@ -214,7 +229,8 @@ public: * This should be handled on deconstructor but to be safe we use it anyways. */ void CloseFileLogs(); - void LoadLogSettingsDefaults(); + EQEmuLogSys *LoadLogSettingsDefaults(); + EQEmuLogSys *LoadLogDatabaseSettings(); /** * @param directory_name @@ -244,7 +260,7 @@ public: * Used in file logs to prepend a timestamp entry for logs * @param time_stamp */ - void SetCurrentTimeStamp(char* time_stamp); + void SetCurrentTimeStamp(char *time_stamp); /** * @param log_name @@ -271,101 +287,53 @@ public: /** * Internally used memory reference for all log settings per category * These are loaded via DB and have defaults loaded in LoadLogSettingsDefaults - * Database loaded via Database::LoadLogSettings(log_settings) + * Database loaded via LogSys.SetDatabase(&database)->LoadLogDatabaseSettings(); */ LogSettings log_settings[Logs::LogCategory::MaxCategoryID]{}; bool file_logs_enabled = false; - /** - * Sets Executable platform (Zone/World/UCS) etc. - */ - int log_platform = 0; + int log_platform = 0; + std::string platform_file_name; - /** - * File name used in writing logs - */ - std::string platform_file_name; - /** - * GMSay Client Message colors mapped by category - * - * @param log_category - * @return - */ + // gmsay uint16 GetGMSayColorFromCategory(uint16 log_category); - /** - * @param f - */ - void SetGMSayHandler(std::function f) { on_log_gmsay_hook = f; } + EQEmuLogSys * SetGMSayHandler(std::function f) { + on_log_gmsay_hook = f; + return this; + } - /** - * @param f - */ - void SetConsoleHandler(std::function f) { on_log_console_hook = f; } - - /** - * Silence console logging - */ + // console + void SetConsoleHandler( + std::function f + ) { on_log_console_hook = f; } void SilenceConsoleLogging(); - - /** - * Turn on all console logging - */ void EnableConsoleLogging(); + // database + EQEmuLogSys *SetDatabase(Database *db); + private: - /** - * Callback pointer to zone process for hooking logs to zone using GMSay - */ - std::function on_log_gmsay_hook; - std::function on_log_console_hook; + // reference to database + Database *m_database; + + std::function on_log_gmsay_hook; + std::function on_log_console_hook; - /** - * Formats log messages like '[Category] This is a log message' - */ std::string FormatOutMessageString(uint16 log_category, const std::string &in_message); - - /** - * Linux console color messages mapped by category - * - * @param log_category - * @return - */ std::string GetLinuxConsoleColorFromCategory(uint16 log_category); - - /** - * Windows console color messages mapped by category - */ uint16 GetWindowsConsoleColorFromCategory(uint16 log_category); - /** - * @param debug_level - * @param log_category - * @param message - */ void ProcessConsoleMessage(uint16 debug_level, uint16 log_category, const std::string &message); - - /** - * @param debug_level - * @param log_category - * @param message - */ void ProcessGMSay(uint16 debug_level, uint16 log_category, const std::string &message); - - /** - * @param debug_level - * @param log_category - * @param message - */ void ProcessLogWrite(uint16 debug_level, uint16 log_category, const std::string &message); - - /** - * @param log_category - * @return - */ bool IsRfc5424LogCategory(uint16 log_category); }; diff --git a/common/eqemu_logsys_log_aliases.h b/common/eqemu_logsys_log_aliases.h index e32b43b4f..753417d81 100644 --- a/common/eqemu_logsys_log_aliases.h +++ b/common/eqemu_logsys_log_aliases.h @@ -636,6 +636,66 @@ OutF(LogSys, Logs::Detail, Logs::DynamicZones, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) +#define LogScheduler(message, ...) do {\ + if (LogSys.log_settings[Logs::Scheduler].is_category_enabled == 1)\ + OutF(LogSys, Logs::General, Logs::Scheduler, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogSchedulerDetail(message, ...) do {\ + if (LogSys.log_settings[Logs::Scheduler].is_category_enabled == 1)\ + OutF(LogSys, Logs::Detail, Logs::Scheduler, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogCheat(message, ...) do {\ + if (LogSys.log_settings[Logs::Cheat].is_category_enabled == 1)\ + OutF(LogSys, Logs::General, Logs::Cheat, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogCheatDetail(message, ...) do {\ + if (LogSys.log_settings[Logs::Cheat].is_category_enabled == 1)\ + OutF(LogSys, Logs::Detail, Logs::Cheat, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogClientList(message, ...) do {\ + if (LogSys.log_settings[Logs::ClientList].is_category_enabled == 1)\ + OutF(LogSys, Logs::General, Logs::ClientList, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogClientListDetail(message, ...) do {\ + if (LogSys.log_settings[Logs::ClientList].is_category_enabled == 1)\ + OutF(LogSys, Logs::Detail, Logs::ClientList, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogDiaWind(message, ...) do {\ + if (LogSys.log_settings[Logs::DiaWind].is_category_enabled == 1)\ + OutF(LogSys, Logs::General, Logs::DiaWind, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogDiaWindDetail(message, ...) do {\ + if (LogSys.log_settings[Logs::DiaWind].is_category_enabled == 1)\ + OutF(LogSys, Logs::Detail, Logs::DiaWind, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogHTTP(message, ...) do {\ + if (LogSys.log_settings[Logs::HTTP].is_category_enabled == 1)\ + OutF(LogSys, Logs::General, Logs::HTTP, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogHTTPDetail(message, ...) do {\ + if (LogSys.log_settings[Logs::HTTP].is_category_enabled == 1)\ + OutF(LogSys, Logs::Detail, Logs::HTTP, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogSaylink(message, ...) do {\ + if (LogSys.log_settings[Logs::Saylink].is_category_enabled == 1)\ + OutF(LogSys, Logs::General, Logs::Saylink, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogSaylinkDetail(message, ...) do {\ + if (LogSys.log_settings[Logs::Saylink].is_category_enabled == 1)\ + OutF(LogSys, Logs::Detail, Logs::Saylink, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + #define Log(debug_level, log_category, message, ...) do {\ if (LogSys.log_settings[log_category].is_category_enabled == 1)\ LogSys.Out(debug_level, log_category, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ @@ -1008,6 +1068,30 @@ #define LogDynamicZonesDetail(message, ...) do {\ } while (0) +#define LogCheatList(message, ...) do {\ +} while (0) + +#define LogCheatDetail(message, ...) do {\ +} while (0) + +#define LogClientList(message, ...) do {\ +} while (0) + +#define LogClientListDetail(message, ...) do {\ +} while (0) + +#define LogDiaWind(message, ...) do {\ +} while (0) + +#define LogDiaWindDetail(message, ...) do {\ +} while (0) + +#define LogHTTP(message, ...) do {\ +} while (0) + +#define LogHTTPDetail(message, ...) do {\ +} while (0) + #define Log(debug_level, log_category, message, ...) do {\ } while (0) diff --git a/common/faction.cpp b/common/faction.cpp index 3dd8584e1..5f3fddacc 100644 --- a/common/faction.cpp +++ b/common/faction.cpp @@ -20,31 +20,31 @@ #include "races.h" #include "rulesys.h" -const char *FactionValueToString(FACTION_VALUE fv) +const char *FactionValueToString(FACTION_VALUE faction_value) { - switch (fv) { + switch (faction_value) { case FACTION_ALLY: - return ("Ally"); + return "Ally"; case FACTION_WARMLY: - return ("Warmly"); + return "Warmly"; case FACTION_KINDLY: - return ("Kindly"); - case FACTION_AMIABLE: - return ("Amiable"); - case FACTION_INDIFFERENT: - return ("Indifferent"); - case FACTION_APPREHENSIVE: - return ("Apprehensive"); - case FACTION_DUBIOUS: - return ("Dubious"); - case FACTION_THREATENLY: - return ("Threatenly"); + return "Kindly"; + case FACTION_AMIABLY: + return "Amiably"; + case FACTION_INDIFFERENTLY: + return "Indifferently"; + case FACTION_APPREHENSIVELY: + return "Apprehensively"; + case FACTION_DUBIOUSLY: + return "Dubiously"; + case FACTION_THREATENINGLY: + return "Threateningly"; case FACTION_SCOWLS: - return ("Scowls, ready to attack."); + return "Scowls"; default: break; } - return ("Unknown Faction Con"); + return "Unknown"; } @@ -70,19 +70,19 @@ FACTION_VALUE CalculateFaction(FactionMods* fm, int32 tmpCharacter_value) return FACTION_KINDLY; } if (character_value >= RuleI(Faction, AmiablyFactionMinimum)) { - return FACTION_AMIABLE; + return FACTION_AMIABLY; } if (character_value >= RuleI(Faction, IndifferentlyFactionMinimum)) { - return FACTION_INDIFFERENT; + return FACTION_INDIFFERENTLY; } if (character_value >= RuleI(Faction, ApprehensivelyFactionMinimum)) { - return FACTION_APPREHENSIVE; + return FACTION_APPREHENSIVELY; } if (character_value >= RuleI(Faction, DubiouslyFactionMinimum)) { - return FACTION_DUBIOUS; + return FACTION_DUBIOUSLY; } if (character_value >= RuleI(Faction, ThreateninglyFactionMinimum)) { - return FACTION_THREATENLY; + return FACTION_THREATENINGLY; } return FACTION_SCOWLS; } diff --git a/common/faction.h b/common/faction.h index bc086669d..907e393fc 100755 --- a/common/faction.h +++ b/common/faction.h @@ -27,13 +27,13 @@ enum FACTION_VALUE { FACTION_ALLY = 1, FACTION_WARMLY = 2, FACTION_KINDLY = 3, - FACTION_AMIABLE = 4, + FACTION_AMIABLY = 4, - FACTION_INDIFFERENT = 5, + FACTION_INDIFFERENTLY = 5, - FACTION_APPREHENSIVE = 6, - FACTION_DUBIOUS = 7, - FACTION_THREATENLY = 8, + FACTION_APPREHENSIVELY = 6, + FACTION_DUBIOUSLY = 7, + FACTION_THREATENINGLY = 8, FACTION_SCOWLS = 9 }; @@ -50,8 +50,8 @@ struct NPCFactionList { struct FactionMods { int32 base; - int16 min; // The lowest your personal earned faction can go - before race/class/diety adjustments. - int16 max; // The highest your personal earned faction can go - before race/class/diety adjustments. + int16 min; // The lowest your personal earned faction can go - before race/class/deity adjustments. + int16 max; // The highest your personal earned faction can go - before race/class/deity adjustments. int32 class_mod; int32 race_mod; int32 deity_mod; @@ -61,8 +61,8 @@ struct Faction { int32 id; std::map mods; int16 base; - int16 min; // The lowest your personal earned faction can go - before race/class/diety adjustments. - int16 max; // The highest your personal earned faction can go - before race/class/diety adjustments. + int16 min; // The lowest your personal earned faction can go - before race/class/deity adjustments. + int16 max; // The highest your personal earned faction can go - before race/class/deity adjustments. char name[50]; }; @@ -75,6 +75,6 @@ struct NPCFaction uint8 temp; }; -const char *FactionValueToString(FACTION_VALUE fv); +const char *FactionValueToString(FACTION_VALUE faction_value); FACTION_VALUE CalculateFaction(FactionMods* fm, int32 tmpCharacter_value); #endif diff --git a/common/features.h b/common/features.h index 6881e2e1b..e1ca61f64 100644 --- a/common/features.h +++ b/common/features.h @@ -204,7 +204,7 @@ enum { //some random constants #define MIN_LEVEL_ALCHEMY 25 //chance ratio that a -#define THREATENLY_ARRGO_CHANCE 32 // 32/128 (25%) chance that a mob will arrgo on con Threatenly +#define THREATENINGLY_AGGRO_CHANCE 32 // 32/128 (25%) chance that a mob will arrgo on con Threatenly //max factions per npc faction list #define MAX_NPC_FACTIONS 20 diff --git a/common/file_util.cpp b/common/file_util.cpp index 8c9aca9a4..7e2535512 100644 --- a/common/file_util.cpp +++ b/common/file_util.cpp @@ -64,4 +64,9 @@ void FileUtil::mkdir(const std::string& directory_name) } ::mkdir(directory_name.c_str(), 0755); #endif -} \ No newline at end of file +} + +bool file_exists(const std::string& name) { + std::ifstream f(name.c_str()); + return f.good(); +} diff --git a/common/file_util.h b/common/file_util.h index 05869d303..02e47d198 100644 --- a/common/file_util.h +++ b/common/file_util.h @@ -28,5 +28,6 @@ public: static void mkdir(const std::string& directory_name); }; +bool file_exists(const std::string& name); #endif //EQEMU_FILE_UTIL_H diff --git a/common/guild_base.cpp b/common/guild_base.cpp index b7ac8515c..07ba3b040 100644 --- a/common/guild_base.cpp +++ b/common/guild_base.cpp @@ -63,7 +63,7 @@ bool BaseGuildManager::LoadGuilds() { for (auto row=results.begin();row!=results.end();++row) _CreateGuild(atoi(row[0]), row[1], atoi(row[2]), atoi(row[3]), row[4], row[5], row[6], row[7]); - query = "SELECT guild_id,rank,title,can_hear,can_speak,can_invite,can_remove,can_promote,can_demote,can_motd,can_warpeace FROM guild_ranks"; + query = "SELECT guild_id,`rank`,title,can_hear,can_speak,can_invite,can_remove,can_promote,can_demote,can_motd,can_warpeace FROM guild_ranks"; results = m_db->QueryDatabase(query); if (!results.Success()) @@ -131,7 +131,7 @@ bool BaseGuildManager::RefreshGuild(uint32 guild_id) { info = _CreateGuild(guild_id, row[0], atoi(row[1]), atoi(row[2]), row[3], row[4], row[5], row[6]); - query = StringFormat("SELECT guild_id, rank, title, can_hear, can_speak, can_invite, can_remove, can_promote, can_demote, can_motd, can_warpeace " + query = StringFormat("SELECT guild_id, `rank`, title, can_hear, can_speak, can_invite, can_remove, can_promote, can_demote, can_motd, can_warpeace " "FROM guild_ranks WHERE guild_id=%lu", (unsigned long)guild_id); results = m_db->QueryDatabase(query); @@ -268,7 +268,7 @@ bool BaseGuildManager::_StoreGuildDB(uint32 guild_id) { m_db->DoEscapeString(title_esc, rankInfo.name.c_str(), rankInfo.name.length()); query = StringFormat("INSERT INTO guild_ranks " - "(guild_id,rank,title,can_hear,can_speak,can_invite,can_remove,can_promote,can_demote,can_motd,can_warpeace)" + "(guild_id,`rank`,title,can_hear,can_speak,can_invite,can_remove,can_promote,can_demote,can_motd,can_warpeace)" " VALUES(%d,%d,'%s',%d,%d,%d,%d,%d,%d,%d,%d)", guild_id, rank, title_esc, rankInfo.permissions[GUILD_HEAR], @@ -738,7 +738,7 @@ bool BaseGuildManager::DBSetGuild(uint32 charid, uint32 guild_id, uint8 rank) { std::string query; if(guild_id != GUILD_NONE) { - query = StringFormat("REPLACE INTO guild_members (char_id,guild_id,rank,public_note) VALUES(%d,%d,%d,'')", charid, guild_id, rank); + query = StringFormat("REPLACE INTO guild_members (char_id,guild_id,`rank`,public_note) VALUES(%d,%d,%d,'')", charid, guild_id, rank); auto results = m_db->QueryDatabase(query); if (!results.Success()) { @@ -758,7 +758,7 @@ bool BaseGuildManager::DBSetGuild(uint32 charid, uint32 guild_id, uint8 rank) { } bool BaseGuildManager::DBSetGuildRank(uint32 charid, uint8 rank) { - std::string query = StringFormat("UPDATE guild_members SET rank=%d WHERE char_id=%d", rank, charid); + std::string query = StringFormat("UPDATE guild_members SET `rank`=%d WHERE char_id=%d", rank, charid); return(QueryWithLogging(query, "setting a guild member's rank")); } @@ -1208,7 +1208,7 @@ BaseGuildManager::RankInfo::RankInfo() { BaseGuildManager::GuildInfo::GuildInfo() { leader_char_id = 0; - minstatus = 0; + minstatus = AccountStatus::Player; } uint32 BaseGuildManager::DoesAccountContainAGuildLeader(uint32 AccountID) @@ -1225,6 +1225,66 @@ uint32 BaseGuildManager::DoesAccountContainAGuildLeader(uint32 AccountID) return results.RowCount(); } +std::string BaseGuildManager::GetGuildNameByID(uint32 guild_id) const { + if(guild_id == GUILD_NONE) { + return std::string(); + } + std::map::const_iterator res; + res = m_guilds.find(guild_id); + if(res == m_guilds.end()) { + return "Invalid Guild"; + } + return res->second->name; +} +std::string BaseGuildManager::GetGuildRankName(uint32 guild_id, uint8 rank) const +{ + if(rank > GUILD_MAX_RANK) { + return "Invalid Rank"; + } + + std::map::const_iterator res; + res = m_guilds.find(guild_id); + if(res == m_guilds.end()) { + return "Invalid Guild Rank"; + } + + return res->second->ranks[rank].name; +} + +uint32 BaseGuildManager::GetGuildIDByCharacterID(uint32 character_id) +{ + if(!m_db) { + return GUILD_NONE; + } + + std::string query = fmt::format( + "SELECT `guild_id` FROM `guild_members` WHERE char_id = {} LIMIT 1", + character_id + ); + auto results = m_db->QueryDatabase(query); + if(!results.Success() || !results.RowCount()) { + return GUILD_NONE; + } + + auto row = results.begin(); + auto guild_id = std::stoul(row[0]); + return guild_id; +} + +bool BaseGuildManager::IsCharacterInGuild(uint32 character_id, uint32 guild_id) +{ + auto current_guild_id = GetGuildIDByCharacterID(character_id); + + if (current_guild_id == GUILD_NONE) { + return false; + } + + if (guild_id && current_guild_id != guild_id) { + return false; + } + + return true; +} \ No newline at end of file diff --git a/common/guild_base.h b/common/guild_base.h index ac8b1a895..122a9ff2f 100644 --- a/common/guild_base.h +++ b/common/guild_base.h @@ -76,8 +76,12 @@ class BaseGuildManager bool GetGuildChannel(uint32 GuildID, char *ChannelBuffer) const; const char *GetRankName(uint32 guild_id, uint8 rank) const; const char *GetGuildName(uint32 guild_id) const; + std::string GetGuildNameByID(uint32 guild_id) const; + std::string GetGuildRankName(uint32 guild_id, uint8 rank) const; + bool IsCharacterInGuild(uint32 character_id, uint32 guild_id = 0); bool GetGuildNameByID(uint32 guild_id, std::string &into) const; uint32 GetGuildIDByName(const char *GuildName); + uint32 GetGuildIDByCharacterID(uint32 character_id); bool IsGuildLeader(uint32 guild_id, uint32 char_id) const; uint8 GetDisplayedRank(uint32 guild_id, uint8 rank, uint32 char_id) const; bool CheckGMStatus(uint32 guild_id, uint8 status) const; diff --git a/common/http/httplib.h b/common/http/httplib.h index 827d5f1e0..e697e106a 100644 --- a/common/http/httplib.h +++ b/common/http/httplib.h @@ -1,13 +1,104 @@ // // httplib.h // -// Copyright (c) 2019 Yuji Hirose. All rights reserved. +// Copyright (c) 2021 Yuji Hirose. All rights reserved. // MIT License // #ifndef CPPHTTPLIB_HTTPLIB_H #define CPPHTTPLIB_HTTPLIB_H +/* + * Configuration + */ + +#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND +#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT +#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 5 +#endif + +#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND +#define CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND 300 +#endif + +#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND +#define CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_READ_TIMEOUT_SECOND +#define CPPHTTPLIB_READ_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_READ_TIMEOUT_USECOND +#define CPPHTTPLIB_READ_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_WRITE_TIMEOUT_SECOND +#define CPPHTTPLIB_WRITE_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_WRITE_TIMEOUT_USECOND +#define CPPHTTPLIB_WRITE_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_IDLE_INTERVAL_SECOND +#define CPPHTTPLIB_IDLE_INTERVAL_SECOND 0 +#endif + +#ifndef CPPHTTPLIB_IDLE_INTERVAL_USECOND +#ifdef _WIN32 +#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 10000 +#else +#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 0 +#endif +#endif + +#ifndef CPPHTTPLIB_REQUEST_URI_MAX_LENGTH +#define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_REDIRECT_MAX_COUNT +#define CPPHTTPLIB_REDIRECT_MAX_COUNT 20 +#endif + +#ifndef CPPHTTPLIB_PAYLOAD_MAX_LENGTH +#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH ((std::numeric_limits::max)()) +#endif + +#ifndef CPPHTTPLIB_TCP_NODELAY +#define CPPHTTPLIB_TCP_NODELAY false +#endif + +#ifndef CPPHTTPLIB_RECV_BUFSIZ +#define CPPHTTPLIB_RECV_BUFSIZ size_t(4096u) +#endif + +#ifndef CPPHTTPLIB_COMPRESSION_BUFSIZ +#define CPPHTTPLIB_COMPRESSION_BUFSIZ size_t(16384u) +#endif + +#ifndef CPPHTTPLIB_THREAD_POOL_COUNT +#define CPPHTTPLIB_THREAD_POOL_COUNT \ + ((std::max)(8u, std::thread::hardware_concurrency() > 0 \ + ? std::thread::hardware_concurrency() - 1 \ + : 0)) +#endif + +#ifndef CPPHTTPLIB_RECV_FLAGS +#define CPPHTTPLIB_RECV_FLAGS 0 +#endif + +#ifndef CPPHTTPLIB_SEND_FLAGS +#define CPPHTTPLIB_SEND_FLAGS 0 +#endif + +/* + * Headers + */ + #ifdef _WIN32 #ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS @@ -17,8 +108,16 @@ #define _CRT_NONSTDC_NO_DEPRECATE #endif //_CRT_NONSTDC_NO_DEPRECATE -#if defined(_MSC_VER) && _MSC_VER < 1900 +#if defined(_MSC_VER) +#ifdef _WIN64 +using ssize_t = __int64; +#else +using ssize_t = int; +#endif + +#if _MSC_VER < 1900 #define snprintf _snprintf_s +#endif #endif // _MSC_VER #ifndef S_ISREG @@ -35,49 +134,97 @@ #include #include + +#include #include +#ifndef WSA_FLAG_NO_HANDLE_INHERIT +#define WSA_FLAG_NO_HANDLE_INHERIT 0x80 +#endif + +#ifdef _MSC_VER #pragma comment(lib, "ws2_32.lib") +#pragma comment(lib, "crypt32.lib") +#pragma comment(lib, "cryptui.lib") +#endif #ifndef strcasecmp #define strcasecmp _stricmp #endif // strcasecmp -typedef SOCKET socket_t; -#else +using socket_t = SOCKET; +#ifdef CPPHTTPLIB_USE_POLL +#define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout) +#endif + +#else // not _WIN32 + #include #include +#include #include #include +#ifdef __linux__ +#include +#endif +#include +#ifdef CPPHTTPLIB_USE_POLL +#include +#endif +#include #include -#include #include #include #include -typedef int socket_t; +using socket_t = int; #define INVALID_SOCKET (-1) #endif //_WIN32 -#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include +#include +#include +#include #include #include #include +#include #include +#include +#include #include #include #include -#include #ifdef CPPHTTPLIB_OPENSSL_SUPPORT #include +#include #include #include +#if defined(_WIN32) && defined(OPENSSL_USE_APPLINK) +#include +#endif + +#include +#include + +#if OPENSSL_VERSION_NUMBER < 0x1010100fL +#error Sorry, OpenSSL versions prior to 1.1.1 are not supported +#endif + #if OPENSSL_VERSION_NUMBER < 0x10100000L +#include inline const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *asn1) { return M_ASN1_STRING_data(asn1); } @@ -88,147 +235,388 @@ inline const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *asn1) { #include #endif -/* - * Configuration - */ -#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5 -#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND 0 -#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 5 -#define CPPHTTPLIB_READ_TIMEOUT_SECOND 5 -#define CPPHTTPLIB_READ_TIMEOUT_USECOND 0 -#define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192 -#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH (std::numeric_limits::max)() -#define CPPHTTPLIB_RECV_BUFSIZ 4096 +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +#include +#include +#endif +/* + * Declaration + */ namespace httplib { namespace detail { +/* + * Backport std::make_unique from C++14. + * + * NOTE: This code came up with the following stackoverflow post: + * https://stackoverflow.com/questions/10149840/c-arrays-and-make-unique + * + */ + + template + typename std::enable_if::value, std::unique_ptr>::type + make_unique(Args &&...args) { + return std::unique_ptr(new T(std::forward(args)...)); + } + + template + typename std::enable_if::value, std::unique_ptr>::type + make_unique(std::size_t n) { + typedef typename std::remove_extent::type RT; + return std::unique_ptr(new RT[n]); + } + struct ci { bool operator()(const std::string &s1, const std::string &s2) const { - return std::lexicographical_compare( - s1.begin(), s1.end(), s2.begin(), s2.end(), - [](char c1, char c2) { return ::tolower(c1) < ::tolower(c2); }); + return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), + s2.end(), + [](unsigned char c1, unsigned char c2) { + return ::tolower(c1) < ::tolower(c2); + }); } }; } // namespace detail - enum class HttpVersion { v1_0 = 0, v1_1 }; + using Headers = std::multimap; - typedef std::multimap Headers; + using Params = std::multimap; + using Match = std::smatch; - template - std::pair make_range_header(uint64_t value, - Args... args); + using Progress = std::function; - typedef std::multimap Params; - typedef std::smatch Match; - typedef std::function Progress; + struct Response; + using ResponseHandler = std::function; - struct MultipartFile { + struct MultipartFormData { + std::string name; + std::string content; std::string filename; std::string content_type; - size_t offset = 0; - size_t length = 0; }; - typedef std::multimap MultipartFiles; + using MultipartFormDataItems = std::vector; + using MultipartFormDataMap = std::multimap; + + class DataSink { + public: + DataSink() : os(&sb_), sb_(*this) {} + + DataSink(const DataSink &) = delete; + DataSink &operator=(const DataSink &) = delete; + DataSink(DataSink &&) = delete; + DataSink &operator=(DataSink &&) = delete; + + std::function write; + std::function done; + std::function is_writable; + std::ostream os; + + private: + class data_sink_streambuf : public std::streambuf { + public: + explicit data_sink_streambuf(DataSink &sink) : sink_(sink) {} + + protected: + std::streamsize xsputn(const char *s, std::streamsize n) { + sink_.write(s, static_cast(n)); + return n; + } + + private: + DataSink &sink_; + }; + + data_sink_streambuf sb_; + }; + + using ContentProvider = + std::function; + + using ContentProviderWithoutLength = + std::function; + + using ContentProviderResourceReleaser = std::function; + + using ContentReceiverWithProgress = + std::function; + + using ContentReceiver = + std::function; + + using MultipartContentHeader = + std::function; + + class ContentReader { + public: + using Reader = std::function; + using MultipartReader = std::function; + + ContentReader(Reader reader, MultipartReader multipart_reader) + : reader_(std::move(reader)), + multipart_reader_(std::move(multipart_reader)) {} + + bool operator()(MultipartContentHeader header, + ContentReceiver receiver) const { + return multipart_reader_(std::move(header), std::move(receiver)); + } + + bool operator()(ContentReceiver receiver) const { + return reader_(std::move(receiver)); + } + + Reader reader_; + MultipartReader multipart_reader_; + }; + + using Range = std::pair; + using Ranges = std::vector; struct Request { - std::string version; std::string method; - std::string target; std::string path; Headers headers; std::string body; + + std::string remote_addr; + int remote_port = -1; + + // for server + std::string version; + std::string target; Params params; - MultipartFiles files; + MultipartFormDataMap files; + Ranges ranges; Match matches; + // for client + ResponseHandler response_handler; + ContentReceiverWithProgress content_receiver; Progress progress; - #ifdef CPPHTTPLIB_OPENSSL_SUPPORT - const SSL *ssl; + const SSL *ssl = nullptr; #endif bool has_header(const char *key) const; std::string get_header_value(const char *key, size_t id = 0) const; + template + T get_header_value(const char *key, size_t id = 0) const; size_t get_header_value_count(const char *key) const; void set_header(const char *key, const char *val); + void set_header(const char *key, const std::string &val); bool has_param(const char *key) const; std::string get_param_value(const char *key, size_t id = 0) const; size_t get_param_value_count(const char *key) const; + bool is_multipart_form_data() const; + bool has_file(const char *key) const; - MultipartFile get_file_value(const char *key) const; + MultipartFormData get_file_value(const char *key) const; + + // private members... + size_t redirect_count_ = CPPHTTPLIB_REDIRECT_MAX_COUNT; + size_t content_length_ = 0; + ContentProvider content_provider_; + bool is_chunked_content_provider_ = false; + size_t authorization_count_ = 0; }; struct Response { std::string version; - int status; + int status = -1; + std::string reason; Headers headers; std::string body; - std::function streamcb; + std::string location; // Redirect location bool has_header(const char *key) const; std::string get_header_value(const char *key, size_t id = 0) const; + template + T get_header_value(const char *key, size_t id = 0) const; size_t get_header_value_count(const char *key) const; void set_header(const char *key, const char *val); + void set_header(const char *key, const std::string &val); - void set_redirect(const char *uri); + void set_redirect(const char *url, int status = 302); + void set_redirect(const std::string &url, int status = 302); void set_content(const char *s, size_t n, const char *content_type); void set_content(const std::string &s, const char *content_type); - Response() : status(-1) {} + void set_content_provider( + size_t length, const char *content_type, ContentProvider provider, + ContentProviderResourceReleaser resource_releaser = nullptr); + + void set_content_provider( + const char *content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser = nullptr); + + void set_chunked_content_provider( + const char *content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser = nullptr); + + Response() = default; + Response(const Response &) = default; + Response &operator=(const Response &) = default; + Response(Response &&) = default; + Response &operator=(Response &&) = default; + ~Response() { + if (content_provider_resource_releaser_) { + content_provider_resource_releaser_(content_provider_success_); + } + } + + // private members... + size_t content_length_ = 0; + ContentProvider content_provider_; + ContentProviderResourceReleaser content_provider_resource_releaser_; + bool is_chunked_content_provider_ = false; + bool content_provider_success_ = false; }; class Stream { public: - virtual ~Stream() {} - virtual int read(char *ptr, size_t size) = 0; - virtual int write(const char *ptr, size_t size1) = 0; - virtual int write(const char *ptr) = 0; - virtual std::string get_remote_addr() const = 0; + virtual ~Stream() = default; + + virtual bool is_readable() const = 0; + virtual bool is_writable() const = 0; + + virtual ssize_t read(char *ptr, size_t size) = 0; + virtual ssize_t write(const char *ptr, size_t size) = 0; + virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0; + virtual socket_t socket() const = 0; template - void write_format(const char *fmt, const Args &... args); + ssize_t write_format(const char *fmt, const Args &...args); + ssize_t write(const char *ptr); + ssize_t write(const std::string &s); }; - class SocketStream : public Stream { + class TaskQueue { public: - SocketStream(socket_t sock); - virtual ~SocketStream(); + TaskQueue() = default; + virtual ~TaskQueue() = default; - virtual int read(char *ptr, size_t size); - virtual int write(const char *ptr, size_t size); - virtual int write(const char *ptr); - virtual std::string get_remote_addr() const; + virtual void enqueue(std::function fn) = 0; + virtual void shutdown() = 0; + + virtual void on_idle(){}; + }; + + class ThreadPool : public TaskQueue { + public: + explicit ThreadPool(size_t n) : shutdown_(false) { + while (n) { + threads_.emplace_back(worker(*this)); + n--; + } + } + + ThreadPool(const ThreadPool &) = delete; + ~ThreadPool() override = default; + + void enqueue(std::function fn) override { + std::unique_lock lock(mutex_); + jobs_.push_back(std::move(fn)); + cond_.notify_one(); + } + + void shutdown() override { + // Stop all worker threads... + { + std::unique_lock lock(mutex_); + shutdown_ = true; + } + + cond_.notify_all(); + + // Join... + for (auto &t : threads_) { + t.join(); + } + } private: - socket_t sock_; + struct worker { + explicit worker(ThreadPool &pool) : pool_(pool) {} + + void operator()() { + for (;;) { + std::function fn; + { + std::unique_lock lock(pool_.mutex_); + + pool_.cond_.wait( + lock, [&] { return !pool_.jobs_.empty() || pool_.shutdown_; }); + + if (pool_.shutdown_ && pool_.jobs_.empty()) { break; } + + fn = pool_.jobs_.front(); + pool_.jobs_.pop_front(); + } + + assert(true == static_cast(fn)); + fn(); + } + } + + ThreadPool &pool_; + }; + friend struct worker; + + std::vector threads_; + std::list> jobs_; + + bool shutdown_; + + std::condition_variable cond_; + std::mutex mutex_; }; - class BufferStream : public Stream { - public: - BufferStream() {} - virtual ~BufferStream() {} + using Logger = std::function; - virtual int read(char *ptr, size_t size); - virtual int write(const char *ptr, size_t size); - virtual int write(const char *ptr); - virtual std::string get_remote_addr() const; + using SocketOptions = std::function; - const std::string &get_buffer() const; - - private: - std::string buffer; - }; + inline void default_socket_options(socket_t sock) { + int yes = 1; +#ifdef _WIN32 + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&yes), + sizeof(yes)); + setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, + reinterpret_cast(&yes), sizeof(yes)); +#else +#ifdef SO_REUSEPORT + setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast(&yes), + sizeof(yes)); +#else + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&yes), + sizeof(yes)); +#endif +#endif + } class Server { public: - typedef std::function Handler; - typedef std::function Logger; + using Handler = std::function; + + using ExceptionHandler = + std::function; + + enum class HandlerResponse { + Handled, + Unhandled, + }; + using HandlerWithResponse = + std::function; + + using HandlerWithContentReader = std::function; + + using Expect100ContinueHandler = + std::function; Server(); @@ -237,32 +625,72 @@ namespace httplib { virtual bool is_valid() const; Server &Get(const char *pattern, Handler handler); + Server &Get(const char *pattern, size_t pattern_len, Handler handler); Server &Post(const char *pattern, Handler handler); - + Server &Post(const char *pattern, size_t pattern_len, Handler handler); + Server &Post(const char *pattern, HandlerWithContentReader handler); + Server &Post(const char *pattern, size_t pattern_len, + HandlerWithContentReader handler); Server &Put(const char *pattern, Handler handler); + Server &Put(const char *pattern, size_t pattern_len, Handler handler); + Server &Put(const char *pattern, HandlerWithContentReader handler); + Server &Put(const char *pattern, size_t pattern_len, + HandlerWithContentReader handler); Server &Patch(const char *pattern, Handler handler); + Server &Patch(const char *pattern, size_t pattern_len, Handler handler); + Server &Patch(const char *pattern, HandlerWithContentReader handler); + Server &Patch(const char *pattern, size_t pattern_len, + HandlerWithContentReader handler); Server &Delete(const char *pattern, Handler handler); + Server &Delete(const char *pattern, size_t pattern_len, Handler handler); + Server &Delete(const char *pattern, HandlerWithContentReader handler); + Server &Delete(const char *pattern, size_t pattern_len, + HandlerWithContentReader handler); Server &Options(const char *pattern, Handler handler); + Server &Options(const char *pattern, size_t pattern_len, Handler handler); - bool set_base_dir(const char *path); + bool set_base_dir(const char *dir, const char *mount_point = nullptr); + bool set_mount_point(const char *mount_point, const char *dir, + Headers headers = Headers()); + bool remove_mount_point(const char *mount_point); + Server &set_file_extension_and_mimetype_mapping(const char *ext, + const char *mime); + Server &set_file_request_handler(Handler handler); - void set_error_handler(Handler handler); - void set_logger(Logger logger); + Server &set_error_handler(HandlerWithResponse handler); + Server &set_error_handler(Handler handler); + Server &set_exception_handler(ExceptionHandler handler); + Server &set_pre_routing_handler(HandlerWithResponse handler); + Server &set_post_routing_handler(Handler handler); - void set_keep_alive_max_count(size_t count); - void set_payload_max_length(uint64_t length); + Server &set_expect_100_continue_handler(Expect100ContinueHandler handler); + Server &set_logger(Logger logger); + Server &set_address_family(int family); + Server &set_tcp_nodelay(bool on); + Server &set_socket_options(SocketOptions socket_options); + + Server &set_default_headers(Headers headers); + + Server &set_keep_alive_max_count(size_t count); + Server &set_keep_alive_timeout(time_t sec); + + Server &set_read_timeout(time_t sec, time_t usec = 0); + template + Server &set_read_timeout(const std::chrono::duration &duration); + + Server &set_write_timeout(time_t sec, time_t usec = 0); + template + Server &set_write_timeout(const std::chrono::duration &duration); + + Server &set_idle_interval(time_t sec, time_t usec = 0); + template + Server &set_idle_interval(const std::chrono::duration &duration); + + Server &set_payload_max_length(size_t length); + + bool bind_to_port(const char *host, int port, int socket_flags = 0); int bind_to_any_port(const char *host, int socket_flags = 0); - - bool bind(const char *host, int port, int socket_flags = 0) - { - if (bind_internal(host, port, socket_flags) < 0) return false; - - return true; - } - - bool poll(); - bool listen_after_bind(); bool listen(const char *host, int port, int socket_flags = 0); @@ -270,170 +698,734 @@ namespace httplib { bool is_running() const; void stop(); - protected: - bool process_request(Stream &strm, bool last_connection, - bool &connection_close, - std::function setup_request = nullptr); + std::function new_task_queue; - size_t keep_alive_max_count_; - size_t payload_max_length_; + protected: + bool process_request(Stream &strm, bool close_connection, + bool &connection_closed, + const std::function &setup_request); + + std::atomic svr_sock_; + size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT; + time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND; + time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND; + time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND; + time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND; + time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND; + time_t idle_interval_sec_ = CPPHTTPLIB_IDLE_INTERVAL_SECOND; + time_t idle_interval_usec_ = CPPHTTPLIB_IDLE_INTERVAL_USECOND; + size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH; private: - typedef std::vector> Handlers; + using Handlers = std::vector>; + using HandlersForContentReader = + std::vector>; - socket_t create_server_socket(const char *host, int port, - int socket_flags) const; + socket_t create_server_socket(const char *host, int port, int socket_flags, + SocketOptions socket_options) const; int bind_internal(const char *host, int port, int socket_flags); bool listen_internal(); - bool routing(Request &req, Response &res); - bool handle_file_request(Request &req, Response &res); - bool dispatch_request(Request &req, Response &res, Handlers &handlers); + bool routing(Request &req, Response &res, Stream &strm); + bool handle_file_request(const Request &req, Response &res, + bool head = false); + bool dispatch_request(Request &req, Response &res, const Handlers &handlers); + bool + dispatch_request_for_content_reader(Request &req, Response &res, + ContentReader content_reader, + const HandlersForContentReader &handlers); bool parse_request_line(const char *s, Request &req); - void write_response(Stream &strm, bool last_connection, const Request &req, + void apply_ranges(const Request &req, Response &res, + std::string &content_type, std::string &boundary); + bool write_response(Stream &strm, bool close_connection, const Request &req, Response &res); + bool write_response_with_content(Stream &strm, bool close_connection, + const Request &req, Response &res); + bool write_response_core(Stream &strm, bool close_connection, + const Request &req, Response &res, + bool need_apply_ranges); + bool write_content_with_provider(Stream &strm, const Request &req, + Response &res, const std::string &boundary, + const std::string &content_type); + bool read_content(Stream &strm, Request &req, Response &res); + bool + read_content_with_content_receiver(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver); + bool read_content_core(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + MultipartContentHeader mulitpart_header, + ContentReceiver multipart_receiver); - virtual bool read_and_close_socket(socket_t sock); + virtual bool process_and_close_socket(socket_t sock); + + struct MountPointEntry { + std::string mount_point; + std::string base_dir; + Headers headers; + }; + std::vector base_dirs_; std::atomic is_running_; - std::atomic svr_sock_; - std::string base_dir_; + std::map file_extension_and_mimetype_map_; + Handler file_request_handler_; Handlers get_handlers_; Handlers post_handlers_; + HandlersForContentReader post_handlers_for_content_reader_; Handlers put_handlers_; + HandlersForContentReader put_handlers_for_content_reader_; Handlers patch_handlers_; + HandlersForContentReader patch_handlers_for_content_reader_; Handlers delete_handlers_; + HandlersForContentReader delete_handlers_for_content_reader_; Handlers options_handlers_; - Handler error_handler_; + HandlerWithResponse error_handler_; + ExceptionHandler exception_handler_; + HandlerWithResponse pre_routing_handler_; + Handler post_routing_handler_; + Logger logger_; + Expect100ContinueHandler expect_100_continue_handler_; + + int address_family_ = AF_UNSPEC; + bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; + SocketOptions socket_options_ = default_socket_options; + + Headers default_headers_; + }; + + enum class Error { + Success = 0, + Unknown, + Connection, + BindIPAddress, + Read, + Write, + ExceedRedirectCount, + Canceled, + SSLConnection, + SSLLoadingCerts, + SSLServerVerification, + UnsupportedMultipartBoundaryChars, + Compression, + }; + + inline std::ostream &operator<<(std::ostream &os, const Error &obj) { + os << static_cast::type>(obj); + return os; + } + + class Result { + public: + Result(std::unique_ptr &&res, Error err, + Headers &&request_headers = Headers{}) + : res_(std::move(res)), err_(err), + request_headers_(std::move(request_headers)) {} + // Response + operator bool() const { return res_ != nullptr; } + bool operator==(std::nullptr_t) const { return res_ == nullptr; } + bool operator!=(std::nullptr_t) const { return res_ != nullptr; } + const Response &value() const { return *res_; } + Response &value() { return *res_; } + const Response &operator*() const { return *res_; } + Response &operator*() { return *res_; } + const Response *operator->() const { return res_.get(); } + Response *operator->() { return res_.get(); } + + // Error + Error error() const { return err_; } + + // Request Headers + bool has_request_header(const char *key) const; + std::string get_request_header_value(const char *key, size_t id = 0) const; + template + T get_request_header_value(const char *key, size_t id = 0) const; + size_t get_request_header_value_count(const char *key) const; + + private: + std::unique_ptr res_; + Error err_; + Headers request_headers_; + }; + + class ClientImpl { + public: + explicit ClientImpl(const std::string &host); + + explicit ClientImpl(const std::string &host, int port); + + explicit ClientImpl(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path); + + virtual ~ClientImpl(); + + virtual bool is_valid() const; + + Result Get(const char *path); + Result Get(const char *path, const Headers &headers); + Result Get(const char *path, Progress progress); + Result Get(const char *path, const Headers &headers, Progress progress); + Result Get(const char *path, ContentReceiver content_receiver); + Result Get(const char *path, const Headers &headers, + ContentReceiver content_receiver); + Result Get(const char *path, ContentReceiver content_receiver, + Progress progress); + Result Get(const char *path, const Headers &headers, + ContentReceiver content_receiver, Progress progress); + Result Get(const char *path, ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const char *path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const char *path, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress); + Result Get(const char *path, const Headers &headers, + ResponseHandler response_handler, ContentReceiver content_receiver, + Progress progress); + + Result Get(const char *path, const Params ¶ms, const Headers &headers, + Progress progress = nullptr); + Result Get(const char *path, const Params ¶ms, const Headers &headers, + ContentReceiver content_receiver, Progress progress = nullptr); + Result Get(const char *path, const Params ¶ms, const Headers &headers, + ResponseHandler response_handler, ContentReceiver content_receiver, + Progress progress = nullptr); + + Result Head(const char *path); + Result Head(const char *path, const Headers &headers); + + Result Post(const char *path); + Result Post(const char *path, const char *body, size_t content_length, + const char *content_type); + Result Post(const char *path, const Headers &headers, const char *body, + size_t content_length, const char *content_type); + Result Post(const char *path, const std::string &body, + const char *content_type); + Result Post(const char *path, const Headers &headers, const std::string &body, + const char *content_type); + Result Post(const char *path, size_t content_length, + ContentProvider content_provider, const char *content_type); + Result Post(const char *path, ContentProviderWithoutLength content_provider, + const char *content_type); + Result Post(const char *path, const Headers &headers, size_t content_length, + ContentProvider content_provider, const char *content_type); + Result Post(const char *path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const char *content_type); + Result Post(const char *path, const Params ¶ms); + Result Post(const char *path, const Headers &headers, const Params ¶ms); + Result Post(const char *path, const MultipartFormDataItems &items); + Result Post(const char *path, const Headers &headers, + const MultipartFormDataItems &items); + Result Post(const char *path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + + Result Put(const char *path); + Result Put(const char *path, const char *body, size_t content_length, + const char *content_type); + Result Put(const char *path, const Headers &headers, const char *body, + size_t content_length, const char *content_type); + Result Put(const char *path, const std::string &body, + const char *content_type); + Result Put(const char *path, const Headers &headers, const std::string &body, + const char *content_type); + Result Put(const char *path, size_t content_length, + ContentProvider content_provider, const char *content_type); + Result Put(const char *path, ContentProviderWithoutLength content_provider, + const char *content_type); + Result Put(const char *path, const Headers &headers, size_t content_length, + ContentProvider content_provider, const char *content_type); + Result Put(const char *path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const char *content_type); + Result Put(const char *path, const Params ¶ms); + Result Put(const char *path, const Headers &headers, const Params ¶ms); + + Result Patch(const char *path); + Result Patch(const char *path, const char *body, size_t content_length, + const char *content_type); + Result Patch(const char *path, const Headers &headers, const char *body, + size_t content_length, const char *content_type); + Result Patch(const char *path, const std::string &body, + const char *content_type); + Result Patch(const char *path, const Headers &headers, + const std::string &body, const char *content_type); + Result Patch(const char *path, size_t content_length, + ContentProvider content_provider, const char *content_type); + Result Patch(const char *path, ContentProviderWithoutLength content_provider, + const char *content_type); + Result Patch(const char *path, const Headers &headers, size_t content_length, + ContentProvider content_provider, const char *content_type); + Result Patch(const char *path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const char *content_type); + + Result Delete(const char *path); + Result Delete(const char *path, const Headers &headers); + Result Delete(const char *path, const char *body, size_t content_length, + const char *content_type); + Result Delete(const char *path, const Headers &headers, const char *body, + size_t content_length, const char *content_type); + Result Delete(const char *path, const std::string &body, + const char *content_type); + Result Delete(const char *path, const Headers &headers, + const std::string &body, const char *content_type); + + Result Options(const char *path); + Result Options(const char *path, const Headers &headers); + + bool send(Request &req, Response &res, Error &error); + Result send(const Request &req); + + size_t is_socket_open() const; + + void stop(); + + void set_default_headers(Headers headers); + + void set_address_family(int family); + void set_tcp_nodelay(bool on); + void set_socket_options(SocketOptions socket_options); + + void set_connection_timeout(time_t sec, time_t usec = 0); + template + void + set_connection_timeout(const std::chrono::duration &duration); + + void set_read_timeout(time_t sec, time_t usec = 0); + template + void set_read_timeout(const std::chrono::duration &duration); + + void set_write_timeout(time_t sec, time_t usec = 0); + template + void set_write_timeout(const std::chrono::duration &duration); + + void set_basic_auth(const char *username, const char *password); + void set_bearer_token_auth(const char *token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_digest_auth(const char *username, const char *password); +#endif + + void set_keep_alive(bool on); + void set_follow_location(bool on); + + void set_url_encode(bool on); + + void set_compress(bool on); + + void set_decompress(bool on); + + void set_interface(const char *intf); + + void set_proxy(const char *host, int port); + void set_proxy_basic_auth(const char *username, const char *password); + void set_proxy_bearer_token_auth(const char *token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_proxy_digest_auth(const char *username, const char *password); +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void enable_server_certificate_verification(bool enabled); +#endif + + void set_logger(Logger logger); + + protected: + struct Socket { + socket_t sock = INVALID_SOCKET; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + SSL *ssl = nullptr; +#endif + + bool is_open() const { return sock != INVALID_SOCKET; } + }; + + Result send_(Request &&req); + + virtual bool create_and_connect_socket(Socket &socket, Error &error); + + // All of: + // shutdown_ssl + // shutdown_socket + // close_socket + // should ONLY be called when socket_mutex_ is locked. + // Also, shutdown_ssl and close_socket should also NOT be called concurrently + // with a DIFFERENT thread sending requests using that socket. + virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully); + void shutdown_socket(Socket &socket); + void close_socket(Socket &socket); + + bool process_request(Stream &strm, Request &req, Response &res, + bool close_connection, Error &error); + + bool write_content_with_provider(Stream &strm, const Request &req, + Error &error); + + void copy_settings(const ClientImpl &rhs); + + // Socket endoint information + const std::string host_; + const int port_; + const std::string host_and_port_; + + // Current open socket + Socket socket_; + mutable std::mutex socket_mutex_; + std::recursive_mutex request_mutex_; + + // These are all protected under socket_mutex + size_t socket_requests_in_flight_ = 0; + std::thread::id socket_requests_are_from_thread_ = std::thread::id(); + bool socket_should_be_closed_when_request_is_done_ = false; + + // Default headers + Headers default_headers_; + + // Settings + std::string client_cert_path_; + std::string client_key_path_; + + time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND; + time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND; + time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND; + time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND; + time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND; + time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND; + + std::string basic_auth_username_; + std::string basic_auth_password_; + std::string bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string digest_auth_username_; + std::string digest_auth_password_; +#endif + + bool keep_alive_ = false; + bool follow_location_ = false; + + bool url_encode_ = true; + + int address_family_ = AF_UNSPEC; + bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; + SocketOptions socket_options_ = nullptr; + + bool compress_ = false; + bool decompress_ = true; + + std::string interface_; + + std::string proxy_host_; + int proxy_port_ = -1; + + std::string proxy_basic_auth_username_; + std::string proxy_basic_auth_password_; + std::string proxy_bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string proxy_digest_auth_username_; + std::string proxy_digest_auth_password_; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + bool server_certificate_verification_ = true; +#endif + Logger logger_; - // TODO: Use thread pool... - std::mutex running_threads_mutex_; - int running_threads_; + private: + socket_t create_client_socket(Error &error) const; + bool read_response_line(Stream &strm, const Request &req, Response &res); + bool write_request(Stream &strm, Request &req, bool close_connection, + Error &error); + bool redirect(Request &req, Response &res, Error &error); + bool handle_request(Stream &strm, Request &req, Response &res, + bool close_connection, Error &error); + std::unique_ptr send_with_content_provider( + Request &req, + // const char *method, const char *path, const Headers &headers, + const char *body, size_t content_length, ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const char *content_type, Error &error); + Result send_with_content_provider( + const char *method, const char *path, const Headers &headers, + const char *body, size_t content_length, ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const char *content_type); + + virtual bool process_socket(const Socket &socket, + std::function callback); + virtual bool is_ssl() const; }; class Client { public: - Client(const char *host, int port = 80, time_t timeout_sec = 300); + // Universal interface + explicit Client(const char *scheme_host_port); - virtual ~Client(); + explicit Client(const char *scheme_host_port, + const std::string &client_cert_path, + const std::string &client_key_path); - virtual bool is_valid() const; + // HTTP only interface + explicit Client(const std::string &host, int port); - std::shared_ptr Get(const char *path, Progress progress = nullptr); - std::shared_ptr Get(const char *path, const Headers &headers, - Progress progress = nullptr); + explicit Client(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path); - std::shared_ptr Head(const char *path); - std::shared_ptr Head(const char *path, const Headers &headers); + ~Client(); - std::shared_ptr Post(const char *path, const std::string &body, - const char *content_type); - std::shared_ptr Post(const char *path, const Headers &headers, - const std::string &body, - const char *content_type); + bool is_valid() const; - std::shared_ptr Post(const char *path, const Params ¶ms); - std::shared_ptr Post(const char *path, const Headers &headers, - const Params ¶ms); + Result Get(const char *path); + Result Get(const char *path, const Headers &headers); + Result Get(const char *path, Progress progress); + Result Get(const char *path, const Headers &headers, Progress progress); + Result Get(const char *path, ContentReceiver content_receiver); + Result Get(const char *path, const Headers &headers, + ContentReceiver content_receiver); + Result Get(const char *path, ContentReceiver content_receiver, + Progress progress); + Result Get(const char *path, const Headers &headers, + ContentReceiver content_receiver, Progress progress); + Result Get(const char *path, ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const char *path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const char *path, const Headers &headers, + ResponseHandler response_handler, ContentReceiver content_receiver, + Progress progress); + Result Get(const char *path, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress); - std::shared_ptr Put(const char *path, const std::string &body, - const char *content_type); - std::shared_ptr Put(const char *path, const Headers &headers, - const std::string &body, - const char *content_type); + Result Get(const char *path, const Params ¶ms, const Headers &headers, + Progress progress = nullptr); + Result Get(const char *path, const Params ¶ms, const Headers &headers, + ContentReceiver content_receiver, Progress progress = nullptr); + Result Get(const char *path, const Params ¶ms, const Headers &headers, + ResponseHandler response_handler, ContentReceiver content_receiver, + Progress progress = nullptr); - std::shared_ptr Patch(const char *path, const std::string &body, - const char *content_type); - std::shared_ptr Patch(const char *path, const Headers &headers, - const std::string &body, - const char *content_type); + Result Head(const char *path); + Result Head(const char *path, const Headers &headers); - std::shared_ptr Delete(const char *path, - const std::string &body = std::string(), - const char *content_type = nullptr); - std::shared_ptr Delete(const char *path, const Headers &headers, - const std::string &body = std::string(), - const char *content_type = nullptr); + Result Post(const char *path); + Result Post(const char *path, const char *body, size_t content_length, + const char *content_type); + Result Post(const char *path, const Headers &headers, const char *body, + size_t content_length, const char *content_type); + Result Post(const char *path, const std::string &body, + const char *content_type); + Result Post(const char *path, const Headers &headers, const std::string &body, + const char *content_type); + Result Post(const char *path, size_t content_length, + ContentProvider content_provider, const char *content_type); + Result Post(const char *path, ContentProviderWithoutLength content_provider, + const char *content_type); + Result Post(const char *path, const Headers &headers, size_t content_length, + ContentProvider content_provider, const char *content_type); + Result Post(const char *path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const char *content_type); + Result Post(const char *path, const Params ¶ms); + Result Post(const char *path, const Headers &headers, const Params ¶ms); + Result Post(const char *path, const MultipartFormDataItems &items); + Result Post(const char *path, const Headers &headers, + const MultipartFormDataItems &items); + Result Post(const char *path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Put(const char *path); + Result Put(const char *path, const char *body, size_t content_length, + const char *content_type); + Result Put(const char *path, const Headers &headers, const char *body, + size_t content_length, const char *content_type); + Result Put(const char *path, const std::string &body, + const char *content_type); + Result Put(const char *path, const Headers &headers, const std::string &body, + const char *content_type); + Result Put(const char *path, size_t content_length, + ContentProvider content_provider, const char *content_type); + Result Put(const char *path, ContentProviderWithoutLength content_provider, + const char *content_type); + Result Put(const char *path, const Headers &headers, size_t content_length, + ContentProvider content_provider, const char *content_type); + Result Put(const char *path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const char *content_type); + Result Put(const char *path, const Params ¶ms); + Result Put(const char *path, const Headers &headers, const Params ¶ms); + Result Patch(const char *path); + Result Patch(const char *path, const char *body, size_t content_length, + const char *content_type); + Result Patch(const char *path, const Headers &headers, const char *body, + size_t content_length, const char *content_type); + Result Patch(const char *path, const std::string &body, + const char *content_type); + Result Patch(const char *path, const Headers &headers, + const std::string &body, const char *content_type); + Result Patch(const char *path, size_t content_length, + ContentProvider content_provider, const char *content_type); + Result Patch(const char *path, ContentProviderWithoutLength content_provider, + const char *content_type); + Result Patch(const char *path, const Headers &headers, size_t content_length, + ContentProvider content_provider, const char *content_type); + Result Patch(const char *path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const char *content_type); - std::shared_ptr Options(const char *path); - std::shared_ptr Options(const char *path, const Headers &headers); + Result Delete(const char *path); + Result Delete(const char *path, const Headers &headers); + Result Delete(const char *path, const char *body, size_t content_length, + const char *content_type); + Result Delete(const char *path, const Headers &headers, const char *body, + size_t content_length, const char *content_type); + Result Delete(const char *path, const std::string &body, + const char *content_type); + Result Delete(const char *path, const Headers &headers, + const std::string &body, const char *content_type); - bool send(Request &req, Response &res); + Result Options(const char *path); + Result Options(const char *path, const Headers &headers); - protected: - bool process_request(Stream &strm, Request &req, Response &res, - bool &connection_close); + bool send(Request &req, Response &res, Error &error); + Result send(const Request &req); - const std::string host_; - const int port_; - time_t timeout_sec_; - const std::string host_and_port_; + size_t is_socket_open() const; + + void stop(); + + void set_default_headers(Headers headers); + + void set_address_family(int family); + void set_tcp_nodelay(bool on); + void set_socket_options(SocketOptions socket_options); + + void set_connection_timeout(time_t sec, time_t usec = 0); + template + void + set_connection_timeout(const std::chrono::duration &duration); + + void set_read_timeout(time_t sec, time_t usec = 0); + template + void set_read_timeout(const std::chrono::duration &duration); + + void set_write_timeout(time_t sec, time_t usec = 0); + template + void set_write_timeout(const std::chrono::duration &duration); + + void set_basic_auth(const char *username, const char *password); + void set_bearer_token_auth(const char *token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_digest_auth(const char *username, const char *password); +#endif + + void set_keep_alive(bool on); + void set_follow_location(bool on); + + void set_url_encode(bool on); + + void set_compress(bool on); + + void set_decompress(bool on); + + void set_interface(const char *intf); + + void set_proxy(const char *host, int port); + void set_proxy_basic_auth(const char *username, const char *password); + void set_proxy_bearer_token_auth(const char *token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_proxy_digest_auth(const char *username, const char *password); +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void enable_server_certificate_verification(bool enabled); +#endif + + void set_logger(Logger logger); + + // SSL +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_ca_cert_path(const char *ca_cert_file_path, + const char *ca_cert_dir_path = nullptr); + + void set_ca_cert_store(X509_STORE *ca_cert_store); + + long get_openssl_verify_result() const; + + SSL_CTX *ssl_context() const; +#endif private: - socket_t create_client_socket() const; - bool read_response_line(Stream &strm, Response &res); - void write_request(Stream &strm, Request &req); + std::unique_ptr cli_; - virtual bool read_and_close_socket(socket_t sock, Request &req, - Response &res); - virtual bool is_ssl() const; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + bool is_ssl_ = false; +#endif }; #ifdef CPPHTTPLIB_OPENSSL_SUPPORT - class SSLSocketStream : public Stream { -public: - SSLSocketStream(socket_t sock, SSL *ssl); - virtual ~SSLSocketStream(); - - virtual int read(char *ptr, size_t size); - virtual int write(const char *ptr, size_t size); - virtual int write(const char *ptr); - virtual std::string get_remote_addr() const; - -private: - socket_t sock_; - SSL *ssl_; -}; - -class SSLServer : public Server { + class SSLServer : public Server { public: SSLServer(const char *cert_path, const char *private_key_path, const char *client_ca_cert_file_path = nullptr, const char *client_ca_cert_dir_path = nullptr); - virtual ~SSLServer(); + SSLServer(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store = nullptr); - virtual bool is_valid() const; + ~SSLServer() override; + + bool is_valid() const override; private: - virtual bool read_and_close_socket(socket_t sock); + bool process_and_close_socket(socket_t sock) override; SSL_CTX *ctx_; std::mutex ctx_mutex_; }; -class SSLClient : public Client { +class SSLClient : public ClientImpl { public: - SSLClient(const char *host, int port = 443, time_t timeout_sec = 300, - const char *client_cert_path = nullptr, - const char *client_key_path = nullptr); + explicit SSLClient(const std::string &host); - virtual ~SSLClient(); + explicit SSLClient(const std::string &host, int port); - virtual bool is_valid() const; + explicit SSLClient(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path); - void set_ca_cert_path(const char *ca_ceert_file_path, + explicit SSLClient(const std::string &host, int port, X509 *client_cert, + EVP_PKEY *client_key); + + ~SSLClient() override; + + bool is_valid() const override; + + void set_ca_cert_path(const char *ca_cert_file_path, const char *ca_cert_dir_path = nullptr); - void enable_server_certificate_verification(bool enabled); + + void set_ca_cert_store(X509_STORE *ca_cert_store); long get_openssl_verify_result() const; + SSL_CTX *ssl_context() const; + private: - virtual bool read_and_close_socket(socket_t sock, Request &req, - Response &res); - virtual bool is_ssl() const; + bool create_and_connect_socket(Socket &socket, Error &error) override; + void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override; + void shutdown_ssl_impl(Socket &socket, bool shutdown_socket); + + bool process_socket(const Socket &socket, + std::function callback) override; + bool is_ssl() const override; + + bool connect_with_proxy(Socket &sock, Response &res, bool &success, + Error &error); + bool initialize_ssl(Socket &socket, Error &error); + + bool load_certs(); bool verify_host(X509 *server_cert) const; bool verify_host_with_subject_alt_name(X509 *server_cert) const; @@ -442,285 +1434,127 @@ private: SSL_CTX *ctx_; std::mutex ctx_mutex_; + std::once_flag initialize_cert_; + std::vector host_components_; + std::string ca_cert_file_path_; std::string ca_cert_dir_path_; - bool server_certificate_verification_ = false; long verify_result_ = 0; + + friend class ClientImpl; }; #endif +// ---------------------------------------------------------------------------- + /* * Implementation */ + namespace detail { - template void split(const char *b, const char *e, char d, Fn fn) { - int i = 0; - int beg = 0; - - while (e ? (b + i != e) : (b[i] != '\0')) { - if (b[i] == d) { - fn(&b[beg], &b[i]); - beg = i + 1; - } - i++; - } - - if (i) { fn(&b[beg], &b[i]); } - } - -// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer` -// to store data. The call can set memory on stack for performance. - class stream_line_reader { - public: - stream_line_reader(Stream &strm, char *fixed_buffer, size_t fixed_buffer_size) - : strm_(strm), fixed_buffer_(fixed_buffer), - fixed_buffer_size_(fixed_buffer_size) {} - - const char *ptr() const { - if (glowable_buffer_.empty()) { - return fixed_buffer_; - } else { - return glowable_buffer_.data(); - } - } - - size_t size() const { - if (glowable_buffer_.empty()) { - return fixed_buffer_used_size_; - } else { - return glowable_buffer_.size(); - } - } - - bool getline() { - fixed_buffer_used_size_ = 0; - glowable_buffer_.clear(); - - for (size_t i = 0;; i++) { - char byte; - auto n = strm_.read(&byte, 1); - - if (n < 0) { - return false; - } else if (n == 0) { - if (i == 0) { - return false; - } else { - break; - } - } - - append(byte); - - if (byte == '\n') { break; } - } - + inline bool is_hex(char c, int &v) { + if (0x20 <= c && isdigit(c)) { + v = c - '0'; + return true; + } else if ('A' <= c && c <= 'F') { + v = c - 'A' + 10; + return true; + } else if ('a' <= c && c <= 'f') { + v = c - 'a' + 10; return true; } + return false; + } - private: - void append(char c) { - if (fixed_buffer_used_size_ < fixed_buffer_size_ - 1) { - fixed_buffer_[fixed_buffer_used_size_++] = c; - fixed_buffer_[fixed_buffer_used_size_] = '\0'; + inline bool from_hex_to_i(const std::string &s, size_t i, size_t cnt, + int &val) { + if (i >= s.size()) { return false; } + + val = 0; + for (; cnt; i++, cnt--) { + if (!s[i]) { return false; } + int v = 0; + if (is_hex(s[i], v)) { + val = val * 16 + v; } else { - if (glowable_buffer_.empty()) { - assert(fixed_buffer_[fixed_buffer_used_size_] == '\0'); - glowable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_); - } - glowable_buffer_ += c; - } - } - - Stream &strm_; - char *fixed_buffer_; - const size_t fixed_buffer_size_; - size_t fixed_buffer_used_size_; - std::string glowable_buffer_; - }; - - inline int close_socket(socket_t sock) { -#ifdef _WIN32 - return closesocket(sock); -#else - return close(sock); -#endif - } - - inline int select_read(socket_t sock, time_t sec, time_t usec) { - fd_set fds; - FD_ZERO(&fds); - FD_SET(sock, &fds); - - timeval tv; - tv.tv_sec = static_cast(sec); - tv.tv_usec = static_cast(usec); - - return select(static_cast(sock + 1), &fds, nullptr, nullptr, &tv); - } - - inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) { - fd_set fdsr; - FD_ZERO(&fdsr); - FD_SET(sock, &fdsr); - - auto fdsw = fdsr; - auto fdse = fdsr; - - timeval tv; - tv.tv_sec = static_cast(sec); - tv.tv_usec = static_cast(usec); - - if (select(static_cast(sock + 1), &fdsr, &fdsw, &fdse, &tv) < 0) { - return false; - } else if (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw)) { - int error = 0; - socklen_t len = sizeof(error); - if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (char *)&error, &len) < 0 || - error) { return false; } - } else { - return false; } - return true; } - template - inline bool read_and_close_socket(socket_t sock, size_t keep_alive_max_count, - T callback) { - bool ret = false; - - if (keep_alive_max_count > 0) { - auto count = keep_alive_max_count; - while (count > 0 && - detail::select_read(sock, CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND, - CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND) > 0) { - SocketStream strm(sock); - auto last_connection = count == 1; - auto connection_close = false; - - ret = callback(strm, last_connection, connection_close); - if (!ret || connection_close) { break; } - - count--; - } - } else { - SocketStream strm(sock); - auto dummy_connection_close = false; - ret = callback(strm, true, dummy_connection_close); - } - - close_socket(sock); + inline std::string from_i_to_hex(size_t n) { + const char *charset = "0123456789abcdef"; + std::string ret; + do { + ret = charset[n & 15] + ret; + n >>= 4; + } while (n > 0); return ret; } - inline int shutdown_socket(socket_t sock) { -#ifdef _WIN32 - return shutdown(sock, SD_BOTH); -#else - return shutdown(sock, SHUT_RDWR); -#endif - } - - template - socket_t create_socket(const char *host, int port, Fn fn, - int socket_flags = 0) { -#ifdef _WIN32 - #define SO_SYNCHRONOUS_NONALERT 0x20 -#define SO_OPENTYPE 0x7008 - - int opt = SO_SYNCHRONOUS_NONALERT; - setsockopt(INVALID_SOCKET, SOL_SOCKET, SO_OPENTYPE, (char *)&opt, - sizeof(opt)); -#endif - - // Get address info - struct addrinfo hints; - struct addrinfo *result; - - memset(&hints, 0, sizeof(struct addrinfo)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = socket_flags; - hints.ai_protocol = 0; - - auto service = std::to_string(port); - - if (getaddrinfo(host, service.c_str(), &hints, &result)) { - return INVALID_SOCKET; + inline size_t to_utf8(int code, char *buff) { + if (code < 0x0080) { + buff[0] = (code & 0x7F); + return 1; + } else if (code < 0x0800) { + buff[0] = static_cast(0xC0 | ((code >> 6) & 0x1F)); + buff[1] = static_cast(0x80 | (code & 0x3F)); + return 2; + } else if (code < 0xD800) { + buff[0] = static_cast(0xE0 | ((code >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (code & 0x3F)); + return 3; + } else if (code < 0xE000) { // D800 - DFFF is invalid... + return 0; + } else if (code < 0x10000) { + buff[0] = static_cast(0xE0 | ((code >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (code & 0x3F)); + return 3; + } else if (code < 0x110000) { + buff[0] = static_cast(0xF0 | ((code >> 18) & 0x7)); + buff[1] = static_cast(0x80 | ((code >> 12) & 0x3F)); + buff[2] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[3] = static_cast(0x80 | (code & 0x3F)); + return 4; } - for (auto rp = result; rp; rp = rp->ai_next) { - // Create a socket -#ifdef _WIN32 - auto sock = WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, - nullptr, 0, WSA_FLAG_NO_HANDLE_INHERIT); -#else - auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); -#endif - if (sock == INVALID_SOCKET) { continue; } - -#ifndef _WIN32 - if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) { continue; } -#endif - - // Make 'reuse address' option available - int yes = 1; - setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&yes, sizeof(yes)); -#ifdef SO_REUSEPORT - setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, (char *)&yes, sizeof(yes)); -#endif - - // bind or connect - if (fn(sock, *rp)) { - freeaddrinfo(result); - return sock; - } - - close_socket(sock); - } - - freeaddrinfo(result); - return INVALID_SOCKET; + // NOTREACHED + return 0; } - inline void set_nonblocking(socket_t sock, bool nonblocking) { -#ifdef _WIN32 - auto flags = nonblocking ? 1UL : 0UL; - ioctlsocket(sock, FIONBIO, &flags); -#else - auto flags = fcntl(sock, F_GETFL, 0); - fcntl(sock, F_SETFL, - nonblocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK))); -#endif - } +// NOTE: This code came up with the following stackoverflow post: +// https://stackoverflow.com/questions/180947/base64-decode-snippet-in-c + inline std::string base64_encode(const std::string &in) { + static const auto lookup = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - inline bool is_connection_error() { -#ifdef _WIN32 - return WSAGetLastError() != WSAEWOULDBLOCK; -#else - return errno != EINPROGRESS; -#endif - } + std::string out; + out.reserve(in.size()); - inline std::string get_remote_addr(socket_t sock) { - struct sockaddr_storage addr; - socklen_t len = sizeof(addr); + int val = 0; + int valb = -6; - if (!getpeername(sock, (struct sockaddr *)&addr, &len)) { - char ipstr[NI_MAXHOST]; - - if (!getnameinfo((struct sockaddr *)&addr, len, ipstr, sizeof(ipstr), - nullptr, 0, NI_NUMERICHOST)) { - return ipstr; + for (auto c : in) { + val = (val << 8) + static_cast(c); + valb += 8; + while (valb >= 0) { + out.push_back(lookup[(val >> valb) & 0x3F]); + valb -= 6; } } - return std::string(); + if (valb > -6) { out.push_back(lookup[((val << 8) >> (valb + 8)) & 0x3F]); } + + while (out.size() % 4) { + out.push_back('='); + } + + return out; } inline bool is_file(const std::string &path) { @@ -770,239 +1604,32 @@ private: return true; } - inline void read_file(const std::string &path, std::string &out) { - std::ifstream fs(path, std::ios_base::binary); - fs.seekg(0, std::ios_base::end); - auto size = fs.tellg(); - fs.seekg(0); - out.resize(static_cast(size)); - fs.read(&out[0], size); - } + inline std::string encode_query_param(const std::string &value) { + std::ostringstream escaped; + escaped.fill('0'); + escaped << std::hex; - inline std::string file_extension(const std::string &path) { - std::smatch m; - auto pat = std::regex("\\.([a-zA-Z0-9]+)$"); - if (std::regex_search(path, m, pat)) { return m[1].str(); } - return std::string(); - } - - inline const char *find_content_type(const std::string &path) { - auto ext = file_extension(path); - if (ext == "txt") { - return "text/plain"; - } else if (ext == "html") { - return "text/html"; - } else if (ext == "css") { - return "text/css"; - } else if (ext == "jpeg" || ext == "jpg") { - return "image/jpg"; - } else if (ext == "png") { - return "image/png"; - } else if (ext == "gif") { - return "image/gif"; - } else if (ext == "svg") { - return "image/svg+xml"; - } else if (ext == "ico") { - return "image/x-icon"; - } else if (ext == "json") { - return "application/json"; - } else if (ext == "pdf") { - return "application/pdf"; - } else if (ext == "js") { - return "application/javascript"; - } else if (ext == "xml") { - return "application/xml"; - } else if (ext == "xhtml") { - return "application/xhtml+xml"; - } - return nullptr; - } - - inline const char *status_message(int status) { - switch (status) { - case 200: return "OK"; - case 301: return "Moved Permanently"; - case 302: return "Found"; - case 303: return "See Other"; - case 304: return "Not Modified"; - case 400: return "Bad Request"; - case 403: return "Forbidden"; - case 404: return "Not Found"; - case 413: return "Payload Too Large"; - case 414: return "Request-URI Too Long"; - case 415: return "Unsupported Media Type"; - default: - case 500: return "Internal Server Error"; - } - } - - inline bool has_header(const Headers &headers, const char *key) { - return headers.find(key) != headers.end(); - } - - inline const char *get_header_value(const Headers &headers, const char *key, - size_t id = 0, const char *def = nullptr) { - auto it = headers.find(key); - std::advance(it, id); - if (it != headers.end()) { return it->second.c_str(); } - return def; - } - - inline uint64_t get_header_value_uint64(const Headers &headers, const char *key, - int def = 0) { - auto it = headers.find(key); - if (it != headers.end()) { - return std::strtoull(it->second.data(), nullptr, 10); - } - return def; - } - - inline bool read_headers(Stream &strm, Headers &headers) { - static std::regex re(R"((.+?):\s*(.+?)\s*\r\n)"); - - const auto bufsiz = 2048; - char buf[bufsiz]; - - stream_line_reader reader(strm, buf, bufsiz); - - for (;;) { - if (!reader.getline()) { return false; } - if (!strcmp(reader.ptr(), "\r\n")) { break; } - std::cmatch m; - if (std::regex_match(reader.ptr(), m, re)) { - auto key = std::string(m[1]); - auto val = std::string(m[2]); - headers.emplace(key, val); + for (auto c : value) { + if (std::isalnum(static_cast(c)) || c == '-' || c == '_' || + c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || + c == ')') { + escaped << c; + } else { + escaped << std::uppercase; + escaped << '%' << std::setw(2) + << static_cast(static_cast(c)); + escaped << std::nouppercase; } } - return true; - } - - inline bool read_content_with_length(Stream &strm, std::string &out, size_t len, - Progress progress) { - out.assign(len, 0); - size_t r = 0; - while (r < len) { - auto n = strm.read(&out[r], len - r); - if (n <= 0) { return false; } - - r += n; - - if (progress) { - if (!progress(r, len)) { return false; } - } - } - - return true; - } - - inline void skip_content_with_length(Stream &strm, size_t len) { - char buf[CPPHTTPLIB_RECV_BUFSIZ]; - size_t r = 0; - while (r < len) { - auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ); - if (n <= 0) { return; } - r += n; - } - } - - inline bool read_content_without_length(Stream &strm, std::string &out) { - char buf[CPPHTTPLIB_RECV_BUFSIZ]; - for (;;) { - auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ); - if (n < 0) { - return false; - } else if (n == 0) { - return true; - } - out.append(buf, n); - } - - return true; - } - - inline bool read_content_chunked(Stream &strm, std::string &out) { - const auto bufsiz = 16; - char buf[bufsiz]; - - stream_line_reader reader(strm, buf, bufsiz); - - if (!reader.getline()) { return false; } - - auto chunk_len = std::stoi(reader.ptr(), 0, 16); - - while (chunk_len > 0) { - std::string chunk; - if (!read_content_with_length(strm, chunk, chunk_len, nullptr)) { - return false; - } - - if (!reader.getline()) { return false; } - - if (strcmp(reader.ptr(), "\r\n")) { break; } - - out += chunk; - - if (!reader.getline()) { return false; } - - chunk_len = std::stoi(reader.ptr(), 0, 16); - } - - if (chunk_len == 0) { - // Reader terminator after chunks - if (!reader.getline() || strcmp(reader.ptr(), "\r\n")) return false; - } - - return true; - } - - template - bool read_content(Stream &strm, T &x, uint64_t payload_max_length, - bool &exceed_payload_max_length, - Progress progress = Progress()) { - if (has_header(x.headers, "Content-Length")) { - auto len = get_header_value_uint64(x.headers, "Content-Length", 0); - if (len == 0) { - const auto &encoding = - get_header_value(x.headers, "Transfer-Encoding", 0, ""); - if (!strcasecmp(encoding, "chunked")) { - return read_content_chunked(strm, x.body); - } - } - - if ((len > payload_max_length) || - // For 32-bit platform - (sizeof(size_t) < sizeof(uint64_t) && - len > std::numeric_limits::max())) { - exceed_payload_max_length = true; - skip_content_with_length(strm, len); - return false; - } - - return read_content_with_length(strm, x.body, len, progress); - } else { - const auto &encoding = - get_header_value(x.headers, "Transfer-Encoding", 0, ""); - if (!strcasecmp(encoding, "chunked")) { - return read_content_chunked(strm, x.body); - } - return read_content_without_length(strm, x.body); - } - return true; - } - - template inline void write_headers(Stream &strm, const T &info) { - for (const auto &x : info.headers) { - strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str()); - } - strm.write("\r\n"); + return escaped.str(); } inline std::string encode_url(const std::string &s) { std::string result; + result.reserve(s.size()); - for (auto i = 0; s[i]; i++) { + for (size_t i = 0; s[i]; i++) { switch (s[i]) { case ' ': result += "%20"; break; case '+': result += "%2B"; break; @@ -1010,16 +1637,16 @@ private: case '\n': result += "%0A"; break; case '\'': result += "%27"; break; case ',': result += "%2C"; break; - case ':': result += "%3A"; break; + // case ':': result += "%3A"; break; // ok? probably... case ';': result += "%3B"; break; default: auto c = static_cast(s[i]); if (c >= 0x80) { result += '%'; char hex[4]; - size_t len = snprintf(hex, sizeof(hex) - 1, "%02X", c); + auto len = snprintf(hex, sizeof(hex) - 1, "%02X", c); assert(len == 2); - result.append(hex, len); + result.append(hex, static_cast(len)); } else { result += s[i]; } @@ -1030,80 +1657,8 @@ private: return result; } - inline bool is_hex(char c, int &v) { - if (0x20 <= c && isdigit(c)) { - v = c - '0'; - return true; - } else if ('A' <= c && c <= 'F') { - v = c - 'A' + 10; - return true; - } else if ('a' <= c && c <= 'f') { - v = c - 'a' + 10; - return true; - } - return false; - } - - inline bool from_hex_to_i(const std::string &s, size_t i, size_t cnt, - int &val) { - if (i >= s.size()) { return false; } - - val = 0; - for (; cnt; i++, cnt--) { - if (!s[i]) { return false; } - int v = 0; - if (is_hex(s[i], v)) { - val = val * 16 + v; - } else { - return false; - } - } - return true; - } - - inline std::string from_i_to_hex(uint64_t n) { - const char *charset = "0123456789abcdef"; - std::string ret; - do { - ret = charset[n & 15] + ret; - n >>= 4; - } while (n > 0); - return ret; - } - - inline size_t to_utf8(int code, char *buff) { - if (code < 0x0080) { - buff[0] = (code & 0x7F); - return 1; - } else if (code < 0x0800) { - buff[0] = (0xC0 | ((code >> 6) & 0x1F)); - buff[1] = (0x80 | (code & 0x3F)); - return 2; - } else if (code < 0xD800) { - buff[0] = (0xE0 | ((code >> 12) & 0xF)); - buff[1] = (0x80 | ((code >> 6) & 0x3F)); - buff[2] = (0x80 | (code & 0x3F)); - return 3; - } else if (code < 0xE000) { // D800 - DFFF is invalid... - return 0; - } else if (code < 0x10000) { - buff[0] = (0xE0 | ((code >> 12) & 0xF)); - buff[1] = (0x80 | ((code >> 6) & 0x3F)); - buff[2] = (0x80 | (code & 0x3F)); - return 3; - } else if (code < 0x110000) { - buff[0] = (0xF0 | ((code >> 18) & 0x7)); - buff[1] = (0x80 | ((code >> 12) & 0x3F)); - buff[2] = (0x80 | ((code >> 6) & 0x3F)); - buff[3] = (0x80 | (code & 0x3F)); - return 4; - } - - // NOTREACHED - return 0; - } - - inline std::string decode_url(const std::string &s) { + inline std::string decode_url(const std::string &s, + bool convert_plus_to_space) { std::string result; for (size_t i = 0; i < s.size(); i++) { @@ -1123,13 +1678,13 @@ private: int val = 0; if (from_hex_to_i(s, i + 1, 2, val)) { // 2 digits hex codes - result += val; + result += static_cast(val); i += 2; // '00' } else { result += s[i]; } } - } else if (s[i] == '+') { + } else if (convert_plus_to_space && s[i] == '+') { result += ' '; } else { result += s[i]; @@ -1139,18 +1694,1568 @@ private: return result; } + inline void read_file(const std::string &path, std::string &out) { + std::ifstream fs(path, std::ios_base::binary); + fs.seekg(0, std::ios_base::end); + auto size = fs.tellg(); + fs.seekg(0); + out.resize(static_cast(size)); + fs.read(&out[0], static_cast(size)); + } + + inline std::string file_extension(const std::string &path) { + std::smatch m; + static auto re = std::regex("\\.([a-zA-Z0-9]+)$"); + if (std::regex_search(path, m, re)) { return m[1].str(); } + return std::string(); + } + + inline bool is_space_or_tab(char c) { return c == ' ' || c == '\t'; } + + inline std::pair trim(const char *b, const char *e, size_t left, + size_t right) { + while (b + left < e && is_space_or_tab(b[left])) { + left++; + } + while (right > 0 && is_space_or_tab(b[right - 1])) { + right--; + } + return std::make_pair(left, right); + } + + inline std::string trim_copy(const std::string &s) { + auto r = trim(s.data(), s.data() + s.size(), 0, s.size()); + return s.substr(r.first, r.second - r.first); + } + + template void split(const char *b, const char *e, char d, Fn fn) { + size_t i = 0; + size_t beg = 0; + + while (e ? (b + i < e) : (b[i] != '\0')) { + if (b[i] == d) { + auto r = trim(b, e, beg, i); + if (r.first < r.second) { fn(&b[r.first], &b[r.second]); } + beg = i + 1; + } + i++; + } + + if (i) { + auto r = trim(b, e, beg, i); + if (r.first < r.second) { fn(&b[r.first], &b[r.second]); } + } + } + +// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer` +// to store data. The call can set memory on stack for performance. + class stream_line_reader { + public: + stream_line_reader(Stream &strm, char *fixed_buffer, size_t fixed_buffer_size) + : strm_(strm), fixed_buffer_(fixed_buffer), + fixed_buffer_size_(fixed_buffer_size) {} + + const char *ptr() const { + if (glowable_buffer_.empty()) { + return fixed_buffer_; + } else { + return glowable_buffer_.data(); + } + } + + size_t size() const { + if (glowable_buffer_.empty()) { + return fixed_buffer_used_size_; + } else { + return glowable_buffer_.size(); + } + } + + bool end_with_crlf() const { + auto end = ptr() + size(); + return size() >= 2 && end[-2] == '\r' && end[-1] == '\n'; + } + + bool getline() { + fixed_buffer_used_size_ = 0; + glowable_buffer_.clear(); + + for (size_t i = 0;; i++) { + char byte; + auto n = strm_.read(&byte, 1); + + if (n < 0) { + return false; + } else if (n == 0) { + if (i == 0) { + return false; + } else { + break; + } + } + + append(byte); + + if (byte == '\n') { break; } + } + + return true; + } + + private: + void append(char c) { + if (fixed_buffer_used_size_ < fixed_buffer_size_ - 1) { + fixed_buffer_[fixed_buffer_used_size_++] = c; + fixed_buffer_[fixed_buffer_used_size_] = '\0'; + } else { + if (glowable_buffer_.empty()) { + assert(fixed_buffer_[fixed_buffer_used_size_] == '\0'); + glowable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_); + } + glowable_buffer_ += c; + } + } + + Stream &strm_; + char *fixed_buffer_; + const size_t fixed_buffer_size_; + size_t fixed_buffer_used_size_ = 0; + std::string glowable_buffer_; + }; + + inline int close_socket(socket_t sock) { +#ifdef _WIN32 + return closesocket(sock); +#else + return close(sock); +#endif + } + + template inline ssize_t handle_EINTR(T fn) { + ssize_t res = false; + while (true) { + res = fn(); + if (res < 0 && errno == EINTR) { continue; } + break; + } + return res; + } + + inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) { +#ifdef CPPHTTPLIB_USE_POLL + struct pollfd pfd_read; + pfd_read.fd = sock; + pfd_read.events = POLLIN; + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); +#else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return 1; } +#endif + + fd_set fds; + FD_ZERO(&fds); + FD_SET(sock, &fds); + + timeval tv; + tv.tv_sec = static_cast(sec); + tv.tv_usec = static_cast(usec); + + return handle_EINTR([&]() { + return select(static_cast(sock + 1), &fds, nullptr, nullptr, &tv); + }); +#endif + } + + inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) { +#ifdef CPPHTTPLIB_USE_POLL + struct pollfd pfd_read; + pfd_read.fd = sock; + pfd_read.events = POLLOUT; + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); +#else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return 1; } +#endif + + fd_set fds; + FD_ZERO(&fds); + FD_SET(sock, &fds); + + timeval tv; + tv.tv_sec = static_cast(sec); + tv.tv_usec = static_cast(usec); + + return handle_EINTR([&]() { + return select(static_cast(sock + 1), nullptr, &fds, nullptr, &tv); + }); +#endif + } + + inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) { +#ifdef CPPHTTPLIB_USE_POLL + struct pollfd pfd_read; + pfd_read.fd = sock; + pfd_read.events = POLLIN | POLLOUT; + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + auto poll_res = handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); + + if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) { + int error = 0; + socklen_t len = sizeof(error); + auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, + reinterpret_cast(&error), &len); + return res >= 0 && !error; + } + return false; +#else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return false; } +#endif + + fd_set fdsr; + FD_ZERO(&fdsr); + FD_SET(sock, &fdsr); + + auto fdsw = fdsr; + auto fdse = fdsr; + + timeval tv; + tv.tv_sec = static_cast(sec); + tv.tv_usec = static_cast(usec); + + auto ret = handle_EINTR([&]() { + return select(static_cast(sock + 1), &fdsr, &fdsw, &fdse, &tv); + }); + + if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) { + int error = 0; + socklen_t len = sizeof(error); + return getsockopt(sock, SOL_SOCKET, SO_ERROR, + reinterpret_cast(&error), &len) >= 0 && + !error; + } + return false; +#endif + } + + class SocketStream : public Stream { + public: + SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec); + ~SocketStream() override; + + bool is_readable() const override; + bool is_writable() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + + private: + socket_t sock_; + time_t read_timeout_sec_; + time_t read_timeout_usec_; + time_t write_timeout_sec_; + time_t write_timeout_usec_; + }; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + class SSLSocketStream : public Stream { +public: + SSLSocketStream(socket_t sock, SSL *ssl, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec); + ~SSLSocketStream() override; + + bool is_readable() const override; + bool is_writable() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + +private: + socket_t sock_; + SSL *ssl_; + time_t read_timeout_sec_; + time_t read_timeout_usec_; + time_t write_timeout_sec_; + time_t write_timeout_usec_; +}; +#endif + + class BufferStream : public Stream { + public: + BufferStream() = default; + ~BufferStream() override = default; + + bool is_readable() const override; + bool is_writable() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + + const std::string &get_buffer() const; + + private: + std::string buffer; + size_t position = 0; + }; + + inline bool keep_alive(socket_t sock, time_t keep_alive_timeout_sec) { + using namespace std::chrono; + auto start = steady_clock::now(); + while (true) { + auto val = select_read(sock, 0, 10000); + if (val < 0) { + return false; + } else if (val == 0) { + auto current = steady_clock::now(); + auto duration = duration_cast(current - start); + auto timeout = keep_alive_timeout_sec * 1000; + if (duration.count() > timeout) { return false; } + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } else { + return true; + } + } + } + + template + inline bool + process_server_socket_core(socket_t sock, size_t keep_alive_max_count, + time_t keep_alive_timeout_sec, T callback) { + assert(keep_alive_max_count > 0); + auto ret = false; + auto count = keep_alive_max_count; + while (count > 0 && keep_alive(sock, keep_alive_timeout_sec)) { + auto close_connection = count == 1; + auto connection_closed = false; + ret = callback(close_connection, connection_closed); + if (!ret || connection_closed) { break; } + count--; + } + return ret; + } + + template + inline bool + process_server_socket(socket_t sock, size_t keep_alive_max_count, + time_t keep_alive_timeout_sec, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + return process_server_socket_core( + sock, keep_alive_max_count, keep_alive_timeout_sec, + [&](bool close_connection, bool &connection_closed) { + SocketStream strm(sock, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm, close_connection, connection_closed); + }); + } + + template + inline bool process_client_socket(socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + SocketStream strm(sock, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm); + } + + inline int shutdown_socket(socket_t sock) { +#ifdef _WIN32 + return shutdown(sock, SD_BOTH); +#else + return shutdown(sock, SHUT_RDWR); +#endif + } + + template + socket_t create_socket(const char *host, int port, int address_family, + int socket_flags, bool tcp_nodelay, + SocketOptions socket_options, + BindOrConnect bind_or_connect) { + // Get address info + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = address_family; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = socket_flags; + hints.ai_protocol = 0; + + auto service = std::to_string(port); + + if (getaddrinfo(host, service.c_str(), &hints, &result)) { +#ifdef __linux__ + res_init(); +#endif + return INVALID_SOCKET; + } + + for (auto rp = result; rp; rp = rp->ai_next) { + // Create a socket +#ifdef _WIN32 + auto sock = + WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, nullptr, 0, + WSA_FLAG_NO_HANDLE_INHERIT | WSA_FLAG_OVERLAPPED); + /** + * Since the WSA_FLAG_NO_HANDLE_INHERIT is only supported on Windows 7 SP1 + * and above the socket creation fails on older Windows Systems. + * + * Let's try to create a socket the old way in this case. + * + * Reference: + * https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketa + * + * WSA_FLAG_NO_HANDLE_INHERIT: + * This flag is supported on Windows 7 with SP1, Windows Server 2008 R2 with + * SP1, and later + * + */ + if (sock == INVALID_SOCKET) { + sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + } +#else + auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); +#endif + if (sock == INVALID_SOCKET) { continue; } + +#ifndef _WIN32 + if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) { continue; } +#endif + + if (tcp_nodelay) { + int yes = 1; + setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&yes), + sizeof(yes)); + } + + if (socket_options) { socket_options(sock); } + + if (rp->ai_family == AF_INET6) { + int no = 0; + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast(&no), + sizeof(no)); + } + + // bind or connect + if (bind_or_connect(sock, *rp)) { + freeaddrinfo(result); + return sock; + } + + close_socket(sock); + } + + freeaddrinfo(result); + return INVALID_SOCKET; + } + + inline void set_nonblocking(socket_t sock, bool nonblocking) { +#ifdef _WIN32 + auto flags = nonblocking ? 1UL : 0UL; + ioctlsocket(sock, FIONBIO, &flags); +#else + auto flags = fcntl(sock, F_GETFL, 0); + fcntl(sock, F_SETFL, + nonblocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK))); +#endif + } + + inline bool is_connection_error() { +#ifdef _WIN32 + return WSAGetLastError() != WSAEWOULDBLOCK; +#else + return errno != EINPROGRESS; +#endif + } + + inline bool bind_ip_address(socket_t sock, const char *host) { + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + if (getaddrinfo(host, "0", &hints, &result)) { return false; } + + auto ret = false; + for (auto rp = result; rp; rp = rp->ai_next) { + const auto &ai = *rp; + if (!::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { + ret = true; + break; + } + } + + freeaddrinfo(result); + return ret; + } + +#if !defined _WIN32 && !defined ANDROID +#define USE_IF2IP +#endif + +#ifdef USE_IF2IP + inline std::string if2ip(const std::string &ifn) { + struct ifaddrs *ifap; + getifaddrs(&ifap); + for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) { + if (ifa->ifa_addr && ifn == ifa->ifa_name) { + if (ifa->ifa_addr->sa_family == AF_INET) { + auto sa = reinterpret_cast(ifa->ifa_addr); + char buf[INET_ADDRSTRLEN]; + if (inet_ntop(AF_INET, &sa->sin_addr, buf, INET_ADDRSTRLEN)) { + freeifaddrs(ifap); + return std::string(buf, INET_ADDRSTRLEN); + } + } + } + } + freeifaddrs(ifap); + return std::string(); + } +#endif + + inline socket_t create_client_socket( + const char *host, int port, int address_family, bool tcp_nodelay, + SocketOptions socket_options, time_t connection_timeout_sec, + time_t connection_timeout_usec, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, const std::string &intf, Error &error) { + auto sock = create_socket( + host, port, address_family, 0, tcp_nodelay, std::move(socket_options), + [&](socket_t sock2, struct addrinfo &ai) -> bool { + if (!intf.empty()) { +#ifdef USE_IF2IP + auto ip = if2ip(intf); + if (ip.empty()) { ip = intf; } + if (!bind_ip_address(sock2, ip.c_str())) { + error = Error::BindIPAddress; + return false; + } +#endif + } + + set_nonblocking(sock2, true); + + auto ret = + ::connect(sock2, ai.ai_addr, static_cast(ai.ai_addrlen)); + + if (ret < 0) { + if (is_connection_error() || + !wait_until_socket_is_ready(sock2, connection_timeout_sec, + connection_timeout_usec)) { + error = Error::Connection; + return false; + } + } + + set_nonblocking(sock2, false); + + { + timeval tv; + tv.tv_sec = static_cast(read_timeout_sec); + tv.tv_usec = static_cast(read_timeout_usec); + setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv)); + } + { + timeval tv; + tv.tv_sec = static_cast(write_timeout_sec); + tv.tv_usec = static_cast(write_timeout_usec); + setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv)); + } + + error = Error::Success; + return true; + }); + + if (sock != INVALID_SOCKET) { + error = Error::Success; + } else { + if (error == Error::Success) { error = Error::Connection; } + } + + return sock; + } + + inline void get_remote_ip_and_port(const struct sockaddr_storage &addr, + socklen_t addr_len, std::string &ip, + int &port) { + if (addr.ss_family == AF_INET) { + port = ntohs(reinterpret_cast(&addr)->sin_port); + } else if (addr.ss_family == AF_INET6) { + port = + ntohs(reinterpret_cast(&addr)->sin6_port); + } + + std::array ipstr{}; + if (!getnameinfo(reinterpret_cast(&addr), addr_len, + ipstr.data(), static_cast(ipstr.size()), nullptr, + 0, NI_NUMERICHOST)) { + ip = ipstr.data(); + } + } + + inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + + if (!getpeername(sock, reinterpret_cast(&addr), + &addr_len)) { + get_remote_ip_and_port(addr, addr_len, ip, port); + } + } + + inline constexpr unsigned int str2tag_core(const char *s, size_t l, + unsigned int h) { + return (l == 0) ? h + : str2tag_core(s + 1, l - 1, + (h * 33) ^ static_cast(*s)); + } + + inline unsigned int str2tag(const std::string &s) { + return str2tag_core(s.data(), s.size(), 0); + } + + namespace udl { + + inline constexpr unsigned int operator"" _(const char *s, size_t l) { + return str2tag_core(s, l, 0); + } + + } // namespace udl + + inline const char * + find_content_type(const std::string &path, + const std::map &user_data) { + auto ext = file_extension(path); + + auto it = user_data.find(ext); + if (it != user_data.end()) { return it->second.c_str(); } + + using udl::operator""_; + + switch (str2tag(ext)) { + default: return nullptr; + case "css"_: return "text/css"; + case "csv"_: return "text/csv"; + case "txt"_: return "text/plain"; + case "vtt"_: return "text/vtt"; + case "htm"_: + case "html"_: return "text/html"; + + case "apng"_: return "image/apng"; + case "avif"_: return "image/avif"; + case "bmp"_: return "image/bmp"; + case "gif"_: return "image/gif"; + case "png"_: return "image/png"; + case "svg"_: return "image/svg+xml"; + case "webp"_: return "image/webp"; + case "ico"_: return "image/x-icon"; + case "tif"_: return "image/tiff"; + case "tiff"_: return "image/tiff"; + case "jpg"_: + case "jpeg"_: return "image/jpeg"; + + case "mp4"_: return "video/mp4"; + case "mpeg"_: return "video/mpeg"; + case "webm"_: return "video/webm"; + + case "mp3"_: return "audio/mp3"; + case "mpga"_: return "audio/mpeg"; + case "weba"_: return "audio/webm"; + case "wav"_: return "audio/wave"; + + case "otf"_: return "font/otf"; + case "ttf"_: return "font/ttf"; + case "woff"_: return "font/woff"; + case "woff2"_: return "font/woff2"; + + case "7z"_: return "application/x-7z-compressed"; + case "atom"_: return "application/atom+xml"; + case "pdf"_: return "application/pdf"; + case "js"_: + case "mjs"_: return "application/javascript"; + case "json"_: return "application/json"; + case "rss"_: return "application/rss+xml"; + case "tar"_: return "application/x-tar"; + case "xht"_: + case "xhtml"_: return "application/xhtml+xml"; + case "xslt"_: return "application/xslt+xml"; + case "xml"_: return "application/xml"; + case "gz"_: return "application/gzip"; + case "zip"_: return "application/zip"; + case "wasm"_: return "application/wasm"; + } + } + + inline const char *status_message(int status) { + switch (status) { + case 100: return "Continue"; + case 101: return "Switching Protocol"; + case 102: return "Processing"; + case 103: return "Early Hints"; + case 200: return "OK"; + case 201: return "Created"; + case 202: return "Accepted"; + case 203: return "Non-Authoritative Information"; + case 204: return "No Content"; + case 205: return "Reset Content"; + case 206: return "Partial Content"; + case 207: return "Multi-Status"; + case 208: return "Already Reported"; + case 226: return "IM Used"; + case 300: return "Multiple Choice"; + case 301: return "Moved Permanently"; + case 302: return "Found"; + case 303: return "See Other"; + case 304: return "Not Modified"; + case 305: return "Use Proxy"; + case 306: return "unused"; + case 307: return "Temporary Redirect"; + case 308: return "Permanent Redirect"; + case 400: return "Bad Request"; + case 401: return "Unauthorized"; + case 402: return "Payment Required"; + case 403: return "Forbidden"; + case 404: return "Not Found"; + case 405: return "Method Not Allowed"; + case 406: return "Not Acceptable"; + case 407: return "Proxy Authentication Required"; + case 408: return "Request Timeout"; + case 409: return "Conflict"; + case 410: return "Gone"; + case 411: return "Length Required"; + case 412: return "Precondition Failed"; + case 413: return "Payload Too Large"; + case 414: return "URI Too Long"; + case 415: return "Unsupported Media Type"; + case 416: return "Range Not Satisfiable"; + case 417: return "Expectation Failed"; + case 418: return "I'm a teapot"; + case 421: return "Misdirected Request"; + case 422: return "Unprocessable Entity"; + case 423: return "Locked"; + case 424: return "Failed Dependency"; + case 425: return "Too Early"; + case 426: return "Upgrade Required"; + case 428: return "Precondition Required"; + case 429: return "Too Many Requests"; + case 431: return "Request Header Fields Too Large"; + case 451: return "Unavailable For Legal Reasons"; + case 501: return "Not Implemented"; + case 502: return "Bad Gateway"; + case 503: return "Service Unavailable"; + case 504: return "Gateway Timeout"; + case 505: return "HTTP Version Not Supported"; + case 506: return "Variant Also Negotiates"; + case 507: return "Insufficient Storage"; + case 508: return "Loop Detected"; + case 510: return "Not Extended"; + case 511: return "Network Authentication Required"; + + default: + case 500: return "Internal Server Error"; + } + } + + inline bool can_compress_content_type(const std::string &content_type) { + return (!content_type.find("text/") && content_type != "text/event-stream") || + content_type == "image/svg+xml" || + content_type == "application/javascript" || + content_type == "application/json" || + content_type == "application/xml" || + content_type == "application/xhtml+xml"; + } + + enum class EncodingType { None = 0, Gzip, Brotli }; + + inline EncodingType encoding_type(const Request &req, const Response &res) { + auto ret = + detail::can_compress_content_type(res.get_header_value("Content-Type")); + if (!ret) { return EncodingType::None; } + + const auto &s = req.get_header_value("Accept-Encoding"); + (void)(s); + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + // TODO: 'Accept-Encoding' has br, not br;q=0 + ret = s.find("br") != std::string::npos; + if (ret) { return EncodingType::Brotli; } +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + // TODO: 'Accept-Encoding' has gzip, not gzip;q=0 + ret = s.find("gzip") != std::string::npos; + if (ret) { return EncodingType::Gzip; } +#endif + + return EncodingType::None; + } + + class compressor { + public: + virtual ~compressor(){}; + + typedef std::function Callback; + virtual bool compress(const char *data, size_t data_length, bool last, + Callback callback) = 0; + }; + + class decompressor { + public: + virtual ~decompressor() {} + + virtual bool is_valid() const = 0; + + typedef std::function Callback; + virtual bool decompress(const char *data, size_t data_length, + Callback callback) = 0; + }; + + class nocompressor : public compressor { + public: + ~nocompressor(){}; + + bool compress(const char *data, size_t data_length, bool /*last*/, + Callback callback) override { + if (!data_length) { return true; } + return callback(data, data_length); + } + }; + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + class gzip_compressor : public compressor { +public: + gzip_compressor() { + std::memset(&strm_, 0, sizeof(strm_)); + strm_.zalloc = Z_NULL; + strm_.zfree = Z_NULL; + strm_.opaque = Z_NULL; + + is_valid_ = deflateInit2(&strm_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8, + Z_DEFAULT_STRATEGY) == Z_OK; + } + + ~gzip_compressor() { deflateEnd(&strm_); } + + bool compress(const char *data, size_t data_length, bool last, + Callback callback) override { + assert(is_valid_); + + auto flush = last ? Z_FINISH : Z_NO_FLUSH; + + strm_.avail_in = static_cast(data_length); + strm_.next_in = const_cast(reinterpret_cast(data)); + + int ret = Z_OK; + + std::array buff{}; + do { + strm_.avail_out = static_cast(buff.size()); + strm_.next_out = reinterpret_cast(buff.data()); + + ret = deflate(&strm_, flush); + if (ret == Z_STREAM_ERROR) { return false; } + + if (!callback(buff.data(), buff.size() - strm_.avail_out)) { + return false; + } + } while (strm_.avail_out == 0); + + assert((last && ret == Z_STREAM_END) || (!last && ret == Z_OK)); + assert(strm_.avail_in == 0); + return true; + } + +private: + bool is_valid_ = false; + z_stream strm_; +}; + +class gzip_decompressor : public decompressor { +public: + gzip_decompressor() { + std::memset(&strm_, 0, sizeof(strm_)); + strm_.zalloc = Z_NULL; + strm_.zfree = Z_NULL; + strm_.opaque = Z_NULL; + + // 15 is the value of wbits, which should be at the maximum possible value + // to ensure that any gzip stream can be decoded. The offset of 32 specifies + // that the stream type should be automatically detected either gzip or + // deflate. + is_valid_ = inflateInit2(&strm_, 32 + 15) == Z_OK; + } + + ~gzip_decompressor() { inflateEnd(&strm_); } + + bool is_valid() const override { return is_valid_; } + + bool decompress(const char *data, size_t data_length, + Callback callback) override { + assert(is_valid_); + + int ret = Z_OK; + + strm_.avail_in = static_cast(data_length); + strm_.next_in = const_cast(reinterpret_cast(data)); + + std::array buff{}; + while (strm_.avail_in > 0) { + strm_.avail_out = static_cast(buff.size()); + strm_.next_out = reinterpret_cast(buff.data()); + + ret = inflate(&strm_, Z_NO_FLUSH); + assert(ret != Z_STREAM_ERROR); + switch (ret) { + case Z_NEED_DICT: + case Z_DATA_ERROR: + case Z_MEM_ERROR: inflateEnd(&strm_); return false; + } + + if (!callback(buff.data(), buff.size() - strm_.avail_out)) { + return false; + } + } + + return ret == Z_OK || ret == Z_STREAM_END; + } + +private: + bool is_valid_ = false; + z_stream strm_; +}; +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + class brotli_compressor : public compressor { +public: + brotli_compressor() { + state_ = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr); + } + + ~brotli_compressor() { BrotliEncoderDestroyInstance(state_); } + + bool compress(const char *data, size_t data_length, bool last, + Callback callback) override { + std::array buff{}; + + auto operation = last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS; + auto available_in = data_length; + auto next_in = reinterpret_cast(data); + + for (;;) { + if (last) { + if (BrotliEncoderIsFinished(state_)) { break; } + } else { + if (!available_in) { break; } + } + + auto available_out = buff.size(); + auto next_out = buff.data(); + + if (!BrotliEncoderCompressStream(state_, operation, &available_in, + &next_in, &available_out, &next_out, + nullptr)) { + return false; + } + + auto output_bytes = buff.size() - available_out; + if (output_bytes) { + callback(reinterpret_cast(buff.data()), output_bytes); + } + } + + return true; + } + +private: + BrotliEncoderState *state_ = nullptr; +}; + +class brotli_decompressor : public decompressor { +public: + brotli_decompressor() { + decoder_s = BrotliDecoderCreateInstance(0, 0, 0); + decoder_r = decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT + : BROTLI_DECODER_RESULT_ERROR; + } + + ~brotli_decompressor() { + if (decoder_s) { BrotliDecoderDestroyInstance(decoder_s); } + } + + bool is_valid() const override { return decoder_s; } + + bool decompress(const char *data, size_t data_length, + Callback callback) override { + if (decoder_r == BROTLI_DECODER_RESULT_SUCCESS || + decoder_r == BROTLI_DECODER_RESULT_ERROR) { + return 0; + } + + const uint8_t *next_in = (const uint8_t *)data; + size_t avail_in = data_length; + size_t total_out; + + decoder_r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT; + + std::array buff{}; + while (decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) { + char *next_out = buff.data(); + size_t avail_out = buff.size(); + + decoder_r = BrotliDecoderDecompressStream( + decoder_s, &avail_in, &next_in, &avail_out, + reinterpret_cast(&next_out), &total_out); + + if (decoder_r == BROTLI_DECODER_RESULT_ERROR) { return false; } + + if (!callback(buff.data(), buff.size() - avail_out)) { return false; } + } + + return decoder_r == BROTLI_DECODER_RESULT_SUCCESS || + decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT; + } + +private: + BrotliDecoderResult decoder_r; + BrotliDecoderState *decoder_s = nullptr; +}; +#endif + + inline bool has_header(const Headers &headers, const char *key) { + return headers.find(key) != headers.end(); + } + + inline const char *get_header_value(const Headers &headers, const char *key, + size_t id = 0, const char *def = nullptr) { + auto rng = headers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { return it->second.c_str(); } + return def; + } + + template + inline T get_header_value(const Headers & /*headers*/, const char * /*key*/, + size_t /*id*/ = 0, uint64_t /*def*/ = 0) {} + + template <> + inline uint64_t get_header_value(const Headers &headers, + const char *key, size_t id, + uint64_t def) { + auto rng = headers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { + return std::strtoull(it->second.data(), nullptr, 10); + } + return def; + } + + template + inline bool parse_header(const char *beg, const char *end, T fn) { + // Skip trailing spaces and tabs. + while (beg < end && is_space_or_tab(end[-1])) { + end--; + } + + auto p = beg; + while (p < end && *p != ':') { + p++; + } + + if (p == end) { return false; } + + auto key_end = p; + + if (*p++ != ':') { return false; } + + while (p < end && is_space_or_tab(*p)) { + p++; + } + + if (p < end) { + fn(std::string(beg, key_end), decode_url(std::string(p, end), false)); + return true; + } + + return false; + } + + inline bool read_headers(Stream &strm, Headers &headers) { + const auto bufsiz = 2048; + char buf[bufsiz]; + stream_line_reader line_reader(strm, buf, bufsiz); + + for (;;) { + if (!line_reader.getline()) { return false; } + + // Check if the line ends with CRLF. + if (line_reader.end_with_crlf()) { + // Blank line indicates end of headers. + if (line_reader.size() == 2) { break; } + } else { + continue; // Skip invalid line. + } + + // Exclude CRLF + auto end = line_reader.ptr() + line_reader.size() - 2; + + parse_header(line_reader.ptr(), end, + [&](std::string &&key, std::string &&val) { + headers.emplace(std::move(key), std::move(val)); + }); + } + + return true; + } + + inline bool read_content_with_length(Stream &strm, uint64_t len, + Progress progress, + ContentReceiverWithProgress out) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + + uint64_t r = 0; + while (r < len) { + auto read_len = static_cast(len - r); + auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); + if (n <= 0) { return false; } + + if (!out(buf, static_cast(n), r, len)) { return false; } + r += static_cast(n); + + if (progress) { + if (!progress(r, len)) { return false; } + } + } + + return true; + } + + inline void skip_content_with_length(Stream &strm, uint64_t len) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + uint64_t r = 0; + while (r < len) { + auto read_len = static_cast(len - r); + auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); + if (n <= 0) { return; } + r += static_cast(n); + } + } + + inline bool read_content_without_length(Stream &strm, + ContentReceiverWithProgress out) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + uint64_t r = 0; + for (;;) { + auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ); + if (n < 0) { + return false; + } else if (n == 0) { + return true; + } + + if (!out(buf, static_cast(n), r, 0)) { return false; } + r += static_cast(n); + } + + return true; + } + + inline bool read_content_chunked(Stream &strm, + ContentReceiverWithProgress out) { + const auto bufsiz = 16; + char buf[bufsiz]; + + stream_line_reader line_reader(strm, buf, bufsiz); + + if (!line_reader.getline()) { return false; } + + unsigned long chunk_len; + while (true) { + char *end_ptr; + + chunk_len = std::strtoul(line_reader.ptr(), &end_ptr, 16); + + if (end_ptr == line_reader.ptr()) { return false; } + if (chunk_len == ULONG_MAX) { return false; } + + if (chunk_len == 0) { break; } + + if (!read_content_with_length(strm, chunk_len, nullptr, out)) { + return false; + } + + if (!line_reader.getline()) { return false; } + + if (strcmp(line_reader.ptr(), "\r\n")) { break; } + + if (!line_reader.getline()) { return false; } + } + + if (chunk_len == 0) { + // Reader terminator after chunks + if (!line_reader.getline() || strcmp(line_reader.ptr(), "\r\n")) + return false; + } + + return true; + } + + inline bool is_chunked_transfer_encoding(const Headers &headers) { + return !strcasecmp(get_header_value(headers, "Transfer-Encoding", 0, ""), + "chunked"); + } + + template + bool prepare_content_receiver(T &x, int &status, + ContentReceiverWithProgress receiver, + bool decompress, U callback) { + if (decompress) { + std::string encoding = x.get_header_value("Content-Encoding"); + std::unique_ptr decompressor; + + if (encoding.find("gzip") != std::string::npos || + encoding.find("deflate") != std::string::npos) { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + decompressor = detail::make_unique(); +#else + status = 415; + return false; +#endif + } else if (encoding.find("br") != std::string::npos) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + decompressor = detail::make_unique(); +#else + status = 415; + return false; +#endif + } + + if (decompressor) { + if (decompressor->is_valid()) { + ContentReceiverWithProgress out = [&](const char *buf, size_t n, + uint64_t off, uint64_t len) { + return decompressor->decompress(buf, n, + [&](const char *buf2, size_t n2) { + return receiver(buf2, n2, off, len); + }); + }; + return callback(std::move(out)); + } else { + status = 500; + return false; + } + } + } + + ContentReceiverWithProgress out = [&](const char *buf, size_t n, uint64_t off, + uint64_t len) { + return receiver(buf, n, off, len); + }; + return callback(std::move(out)); + } + + template + bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status, + Progress progress, ContentReceiverWithProgress receiver, + bool decompress) { + return prepare_content_receiver( + x, status, std::move(receiver), decompress, + [&](const ContentReceiverWithProgress &out) { + auto ret = true; + auto exceed_payload_max_length = false; + + if (is_chunked_transfer_encoding(x.headers)) { + ret = read_content_chunked(strm, out); + } else if (!has_header(x.headers, "Content-Length")) { + ret = read_content_without_length(strm, out); + } else { + auto len = get_header_value(x.headers, "Content-Length"); + if (len > payload_max_length) { + exceed_payload_max_length = true; + skip_content_with_length(strm, len); + ret = false; + } else if (len > 0) { + ret = read_content_with_length(strm, len, std::move(progress), out); + } + } + + if (!ret) { status = exceed_payload_max_length ? 413 : 400; } + return ret; + }); + } + + inline ssize_t write_headers(Stream &strm, const Headers &headers) { + ssize_t write_len = 0; + for (const auto &x : headers) { + auto len = + strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str()); + if (len < 0) { return len; } + write_len += len; + } + auto len = strm.write("\r\n"); + if (len < 0) { return len; } + write_len += len; + return write_len; + } + + inline bool write_data(Stream &strm, const char *d, size_t l) { + size_t offset = 0; + while (offset < l) { + auto length = strm.write(d + offset, l - offset); + if (length < 0) { return false; } + offset += static_cast(length); + } + return true; + } + + template + inline bool write_content(Stream &strm, const ContentProvider &content_provider, + size_t offset, size_t length, T is_shutting_down, + Error &error) { + size_t end_offset = offset + length; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + if (write_data(strm, d, l)) { + offset += l; + } else { + ok = false; + } + } + return ok; + }; + + data_sink.is_writable = [&](void) { return ok && strm.is_writable(); }; + + while (offset < end_offset && !is_shutting_down()) { + if (!content_provider(offset, end_offset - offset, data_sink)) { + error = Error::Canceled; + return false; + } + if (!ok) { + error = Error::Write; + return false; + } + } + + error = Error::Success; + return true; + } + + template + inline bool write_content(Stream &strm, const ContentProvider &content_provider, + size_t offset, size_t length, + const T &is_shutting_down) { + auto error = Error::Success; + return write_content(strm, content_provider, offset, length, is_shutting_down, + error); + } + + template + inline bool + write_content_without_length(Stream &strm, + const ContentProvider &content_provider, + const T &is_shutting_down) { + size_t offset = 0; + auto data_available = true; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + offset += l; + if (!write_data(strm, d, l)) { ok = false; } + } + return ok; + }; + + data_sink.done = [&](void) { data_available = false; }; + + data_sink.is_writable = [&](void) { return ok && strm.is_writable(); }; + + while (data_available && !is_shutting_down()) { + if (!content_provider(offset, 0, data_sink)) { return false; } + if (!ok) { return false; } + } + return true; + } + + template + inline bool + write_content_chunked(Stream &strm, const ContentProvider &content_provider, + const T &is_shutting_down, U &compressor, Error &error) { + size_t offset = 0; + auto data_available = true; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + data_available = l > 0; + offset += l; + + std::string payload; + if (compressor.compress(d, l, false, + [&](const char *data, size_t data_len) { + payload.append(data, data_len); + return true; + })) { + if (!payload.empty()) { + // Emit chunked response header and footer for each chunk + auto chunk = + from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; + if (!write_data(strm, chunk.data(), chunk.size())) { ok = false; } + } + } else { + ok = false; + } + } + return ok; + }; + + data_sink.done = [&](void) { + if (!ok) { return; } + + data_available = false; + + std::string payload; + if (!compressor.compress(nullptr, 0, true, + [&](const char *data, size_t data_len) { + payload.append(data, data_len); + return true; + })) { + ok = false; + return; + } + + if (!payload.empty()) { + // Emit chunked response header and footer for each chunk + auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; + if (!write_data(strm, chunk.data(), chunk.size())) { + ok = false; + return; + } + } + + static const std::string done_marker("0\r\n\r\n"); + if (!write_data(strm, done_marker.data(), done_marker.size())) { + ok = false; + } + }; + + data_sink.is_writable = [&](void) { return ok && strm.is_writable(); }; + + while (data_available && !is_shutting_down()) { + if (!content_provider(offset, 0, data_sink)) { + error = Error::Canceled; + return false; + } + if (!ok) { + error = Error::Write; + return false; + } + } + + error = Error::Success; + return true; + } + + template + inline bool write_content_chunked(Stream &strm, + const ContentProvider &content_provider, + const T &is_shutting_down, U &compressor) { + auto error = Error::Success; + return write_content_chunked(strm, content_provider, is_shutting_down, + compressor, error); + } + + template + inline bool redirect(T &cli, Request &req, Response &res, + const std::string &path, const std::string &location, + Error &error) { + Request new_req = req; + new_req.path = path; + new_req.redirect_count_ -= 1; + + if (res.status == 303 && (req.method != "GET" && req.method != "HEAD")) { + new_req.method = "GET"; + new_req.body.clear(); + new_req.headers.clear(); + } + + Response new_res; + + auto ret = cli.send(new_req, new_res, error); + if (ret) { + req = new_req; + res = new_res; + res.location = location; + } + return ret; + } + + inline std::string params_to_query_str(const Params ¶ms) { + std::string query; + + for (auto it = params.begin(); it != params.end(); ++it) { + if (it != params.begin()) { query += "&"; } + query += it->first; + query += "="; + query += encode_query_param(it->second); + } + return query; + } + + inline std::string append_query_params(const char *path, const Params ¶ms) { + std::string path_with_query = path; + const static std::regex re("[^?]+\\?.*"); + auto delm = std::regex_match(path, re) ? '&' : '?'; + path_with_query += delm + params_to_query_str(params); + return path_with_query; + } + inline void parse_query_text(const std::string &s, Params ¶ms) { - split(&s[0], &s[s.size()], '&', [&](const char *b, const char *e) { + std::set cache; + split(s.data(), s.data() + s.size(), '&', [&](const char *b, const char *e) { + std::string kv(b, e); + if (cache.find(kv) != cache.end()) { return; } + cache.insert(kv); + std::string key; std::string val; - split(b, e, '=', [&](const char *b, const char *e) { + split(b, e, '=', [&](const char *b2, const char *e2) { if (key.empty()) { - key.assign(b, e); + key.assign(b2, e2); } else { - val.assign(b, e); + val.assign(b2, e2); } }); - params.emplace(key, decode_url(val)); + + if (!key.empty()) { + params.emplace(decode_url(key, true), decode_url(val, true)); + } }); } @@ -1158,182 +3263,487 @@ private: std::string &boundary) { auto pos = content_type.find("boundary="); if (pos == std::string::npos) { return false; } - boundary = content_type.substr(pos + 9); - return true; + if (boundary.length() >= 2 && boundary.front() == '"' && + boundary.back() == '"') { + boundary = boundary.substr(1, boundary.size() - 2); + } + return !boundary.empty(); } - inline bool parse_multipart_formdata(const std::string &boundary, - const std::string &body, - MultipartFiles &files) { - static std::string dash = "--"; - static std::string crlf = "\r\n"; + inline bool parse_range_header(const std::string &s, Ranges &ranges) try { + static auto re_first_range = std::regex(R"(bytes=(\d*-\d*(?:,\s*\d*-\d*)*))"); + std::smatch m; + if (std::regex_match(s, m, re_first_range)) { + auto pos = static_cast(m.position(1)); + auto len = static_cast(m.length(1)); + bool all_valid_ranges = true; + split(&s[pos], &s[pos + len], ',', [&](const char *b, const char *e) { + if (!all_valid_ranges) return; + static auto re_another_range = std::regex(R"(\s*(\d*)-(\d*))"); + std::cmatch cm; + if (std::regex_match(b, e, cm, re_another_range)) { + ssize_t first = -1; + if (!cm.str(1).empty()) { + first = static_cast(std::stoll(cm.str(1))); + } - static std::regex re_content_type("Content-Type: (.*?)", - std::regex_constants::icase); + ssize_t last = -1; + if (!cm.str(2).empty()) { + last = static_cast(std::stoll(cm.str(2))); + } - static std::regex re_content_disposition( - "Content-Disposition: form-data; name=\"(.*?)\"(?:; filename=\"(.*?)\")?", - std::regex_constants::icase); - - auto dash_boundary = dash + boundary; - - auto pos = body.find(dash_boundary); - if (pos != 0) { return false; } - - pos += dash_boundary.size(); - - auto next_pos = body.find(crlf, pos); - if (next_pos == std::string::npos) { return false; } - - pos = next_pos + crlf.size(); - - while (pos < body.size()) { - next_pos = body.find(crlf, pos); - if (next_pos == std::string::npos) { return false; } - - std::string name; - MultipartFile file; - - auto header = body.substr(pos, (next_pos - pos)); - - while (pos != next_pos) { - std::smatch m; - if (std::regex_match(header, m, re_content_type)) { - file.content_type = m[1]; - } else if (std::regex_match(header, m, re_content_disposition)) { - name = m[1]; - file.filename = m[2]; + if (first != -1 && last != -1 && first > last) { + all_valid_ranges = false; + return; + } + ranges.emplace_back(std::make_pair(first, last)); } + }); + return all_valid_ranges; + } + return false; + } catch (...) { return false; } - pos = next_pos + crlf.size(); + class MultipartFormDataParser { + public: + MultipartFormDataParser() = default; - next_pos = body.find(crlf, pos); - if (next_pos == std::string::npos) { return false; } + void set_boundary(std::string &&boundary) { boundary_ = boundary; } - header = body.substr(pos, (next_pos - pos)); + bool is_valid() const { return is_valid_; } + + bool parse(const char *buf, size_t n, const ContentReceiver &content_callback, + const MultipartContentHeader &header_callback) { + + static const std::regex re_content_disposition( + "^Content-Disposition:\\s*form-data;\\s*name=\"(.*?)\"(?:;\\s*filename=" + "\"(.*?)\")?\\s*$", + std::regex_constants::icase); + static const std::string dash_ = "--"; + static const std::string crlf_ = "\r\n"; + + buf_.append(buf, n); // TODO: performance improvement + + while (!buf_.empty()) { + switch (state_) { + case 0: { // Initial boundary + auto pattern = dash_ + boundary_ + crlf_; + if (pattern.size() > buf_.size()) { return true; } + auto pos = buf_.find(pattern); + if (pos != 0) { return false; } + buf_.erase(0, pattern.size()); + off_ += pattern.size(); + state_ = 1; + break; + } + case 1: { // New entry + clear_file_info(); + state_ = 2; + break; + } + case 2: { // Headers + auto pos = buf_.find(crlf_); + while (pos != std::string::npos) { + // Empty line + if (pos == 0) { + if (!header_callback(file_)) { + is_valid_ = false; + return false; + } + buf_.erase(0, crlf_.size()); + off_ += crlf_.size(); + state_ = 3; + break; + } + + static const std::string header_name = "content-type:"; + const auto header = buf_.substr(0, pos); + if (start_with_case_ignore(header, header_name)) { + file_.content_type = trim_copy(header.substr(header_name.size())); + } else { + std::smatch m; + if (std::regex_match(header, m, re_content_disposition)) { + file_.name = m[1]; + file_.filename = m[2]; + } + } + + buf_.erase(0, pos + crlf_.size()); + off_ += pos + crlf_.size(); + pos = buf_.find(crlf_); + } + if (state_ != 3) { return true; } + break; + } + case 3: { // Body + { + auto pattern = crlf_ + dash_; + if (pattern.size() > buf_.size()) { return true; } + + auto pos = find_string(buf_, pattern); + + if (!content_callback(buf_.data(), pos)) { + is_valid_ = false; + return false; + } + + off_ += pos; + buf_.erase(0, pos); + } + { + auto pattern = crlf_ + dash_ + boundary_; + if (pattern.size() > buf_.size()) { return true; } + + auto pos = buf_.find(pattern); + if (pos != std::string::npos) { + if (!content_callback(buf_.data(), pos)) { + is_valid_ = false; + return false; + } + + off_ += pos + pattern.size(); + buf_.erase(0, pos + pattern.size()); + state_ = 4; + } else { + if (!content_callback(buf_.data(), pattern.size())) { + is_valid_ = false; + return false; + } + + off_ += pattern.size(); + buf_.erase(0, pattern.size()); + } + } + break; + } + case 4: { // Boundary + if (crlf_.size() > buf_.size()) { return true; } + if (buf_.compare(0, crlf_.size(), crlf_) == 0) { + buf_.erase(0, crlf_.size()); + off_ += crlf_.size(); + state_ = 1; + } else { + auto pattern = dash_ + crlf_; + if (pattern.size() > buf_.size()) { return true; } + if (buf_.compare(0, pattern.size(), pattern) == 0) { + buf_.erase(0, pattern.size()); + off_ += pattern.size(); + is_valid_ = true; + state_ = 5; + } else { + return true; + } + } + break; + } + case 5: { // Done + is_valid_ = false; + return false; + } + } } - pos = next_pos + crlf.size(); - - next_pos = body.find(crlf + dash_boundary, pos); - - if (next_pos == std::string::npos) { return false; } - - file.offset = pos; - file.length = next_pos - pos; - - pos = next_pos + crlf.size() + dash_boundary.size(); - - next_pos = body.find(crlf, pos); - if (next_pos == std::string::npos) { return false; } - - files.emplace(name, file); - - pos = next_pos + crlf.size(); + return true; } - return true; - } + private: + void clear_file_info() { + file_.name.clear(); + file_.filename.clear(); + file_.content_type.clear(); + } + + bool start_with_case_ignore(const std::string &a, + const std::string &b) const { + if (a.size() < b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) { + if (::tolower(a[i]) != ::tolower(b[i])) { return false; } + } + return true; + } + + bool start_with(const std::string &a, size_t off, + const std::string &b) const { + if (a.size() - off < b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) { + if (a[i + off] != b[i]) { return false; } + } + return true; + } + + size_t find_string(const std::string &s, const std::string &pattern) const { + auto c = pattern.front(); + + size_t off = 0; + while (off < s.size()) { + auto pos = s.find(c, off); + if (pos == std::string::npos) { return s.size(); } + + auto rem = s.size() - pos; + if (pattern.size() > rem) { return pos; } + + if (start_with(s, pos, pattern)) { return pos; } + + off = pos + 1; + } + + return s.size(); + } + + std::string boundary_; + + std::string buf_; + size_t state_ = 0; + bool is_valid_ = false; + size_t off_ = 0; + MultipartFormData file_; + }; inline std::string to_lower(const char *beg, const char *end) { std::string out; auto it = beg; while (it != end) { - out += ::tolower(*it); + out += static_cast(::tolower(*it)); it++; } return out; } - inline void make_range_header_core(std::string &) {} + inline std::string make_multipart_data_boundary() { + static const char data[] = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - template - inline void make_range_header_core(std::string &field, uint64_t value) { - if (!field.empty()) { field += ", "; } - field += std::to_string(value) + "-"; + // std::random_device might actually be deterministic on some + // platforms, but due to lack of support in the c++ standard library, + // doing better requires either some ugly hacks or breaking portability. + std::random_device seed_gen; + // Request 128 bits of entropy for initialization + std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), seed_gen()}; + std::mt19937 engine(seed_sequence); + + std::string result = "--cpp-httplib-multipart-data-"; + + for (auto i = 0; i < 16; i++) { + result += data[engine() % (sizeof(data) - 1)]; + } + + return result; } - template - inline void make_range_header_core(std::string &field, uint64_t value1, - uint64_t value2, Args... args) { - if (!field.empty()) { field += ", "; } - field += std::to_string(value1) + "-" + std::to_string(value2); - make_range_header_core(field, args...); + inline std::pair + get_range_offset_and_length(const Request &req, size_t content_length, + size_t index) { + auto r = req.ranges[index]; + + if (r.first == -1 && r.second == -1) { + return std::make_pair(0, content_length); + } + + auto slen = static_cast(content_length); + + if (r.first == -1) { + r.first = (std::max)(static_cast(0), slen - r.second); + r.second = slen - 1; + } + + if (r.second == -1) { r.second = slen - 1; } + return std::make_pair(r.first, static_cast(r.second - r.first) + 1); } -#ifdef CPPHTTPLIB_ZLIB_SUPPORT - inline bool can_compress(const std::string &content_type) { - return !content_type.find("text/") || content_type == "image/svg+xml" || - content_type == "application/javascript" || - content_type == "application/json" || - content_type == "application/xml" || - content_type == "application/xhtml+xml"; + inline std::string make_content_range_header_field(size_t offset, size_t length, + size_t content_length) { + std::string field = "bytes "; + field += std::to_string(offset); + field += "-"; + field += std::to_string(offset + length - 1); + field += "/"; + field += std::to_string(content_length); + return field; + } + + template + bool process_multipart_ranges_data(const Request &req, Response &res, + const std::string &boundary, + const std::string &content_type, + SToken stoken, CToken ctoken, + Content content) { + for (size_t i = 0; i < req.ranges.size(); i++) { + ctoken("--"); + stoken(boundary); + ctoken("\r\n"); + if (!content_type.empty()) { + ctoken("Content-Type: "); + stoken(content_type); + ctoken("\r\n"); + } + + auto offsets = get_range_offset_and_length(req, res.body.size(), i); + auto offset = offsets.first; + auto length = offsets.second; + + ctoken("Content-Range: "); + stoken(make_content_range_header_field(offset, length, res.body.size())); + ctoken("\r\n"); + ctoken("\r\n"); + if (!content(offset, length)) { return false; } + ctoken("\r\n"); + } + + ctoken("--"); + stoken(boundary); + ctoken("--\r\n"); + + return true; + } + + inline bool make_multipart_ranges_data(const Request &req, Response &res, + const std::string &boundary, + const std::string &content_type, + std::string &data) { + return process_multipart_ranges_data( + req, res, boundary, content_type, + [&](const std::string &token) { data += token; }, + [&](const char *token) { data += token; }, + [&](size_t offset, size_t length) { + if (offset < res.body.size()) { + data += res.body.substr(offset, length); + return true; + } + return false; + }); + } + + inline size_t + get_multipart_ranges_data_length(const Request &req, Response &res, + const std::string &boundary, + const std::string &content_type) { + size_t data_length = 0; + + process_multipart_ranges_data( + req, res, boundary, content_type, + [&](const std::string &token) { data_length += token.size(); }, + [&](const char *token) { data_length += strlen(token); }, + [&](size_t /*offset*/, size_t length) { + data_length += length; + return true; + }); + + return data_length; + } + + template + inline bool write_multipart_ranges_data(Stream &strm, const Request &req, + Response &res, + const std::string &boundary, + const std::string &content_type, + const T &is_shutting_down) { + return process_multipart_ranges_data( + req, res, boundary, content_type, + [&](const std::string &token) { strm.write(token); }, + [&](const char *token) { strm.write(token); }, + [&](size_t offset, size_t length) { + return write_content(strm, res.content_provider_, offset, length, + is_shutting_down); + }); + } + + inline std::pair + get_range_offset_and_length(const Request &req, const Response &res, + size_t index) { + auto r = req.ranges[index]; + + if (r.second == -1) { + r.second = static_cast(res.content_length_) - 1; + } + + return std::make_pair(r.first, r.second - r.first + 1); + } + + inline bool expect_content(const Request &req) { + if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" || + req.method == "PRI" || req.method == "DELETE") { + return true; + } + // TODO: check if Content-Length is set + return false; + } + + inline bool has_crlf(const char *s) { + auto p = s; + while (*p) { + if (*p == '\r' || *p == '\n') { return true; } + p++; + } + return false; + } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + template +inline std::string message_digest(const std::string &s, Init init, + Update update, Final final, + size_t digest_length) { + using namespace std; + + std::vector md(digest_length, 0); + CTX ctx; + init(&ctx); + update(&ctx, s.data(), s.size()); + final(md.data(), &ctx); + + stringstream ss; + for (auto c : md) { + ss << setfill('0') << setw(2) << hex << (unsigned int)c; + } + return ss.str(); } -inline void compress(std::string &content) { - z_stream strm; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - - auto ret = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8, - Z_DEFAULT_STRATEGY); - if (ret != Z_OK) { return; } - - strm.avail_in = content.size(); - strm.next_in = (Bytef *)content.data(); - - std::string compressed; - - const auto bufsiz = 16384; - char buff[bufsiz]; - do { - strm.avail_out = bufsiz; - strm.next_out = (Bytef *)buff; - deflate(&strm, Z_FINISH); - compressed.append(buff, bufsiz - strm.avail_out); - } while (strm.avail_out == 0); - - content.swap(compressed); - - deflateEnd(&strm); +inline std::string MD5(const std::string &s) { + return message_digest(s, MD5_Init, MD5_Update, MD5_Final, + MD5_DIGEST_LENGTH); } -inline void decompress(std::string &content) { - z_stream strm; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; +inline std::string SHA_256(const std::string &s) { + return message_digest(s, SHA256_Init, SHA256_Update, SHA256_Final, + SHA256_DIGEST_LENGTH); +} - // 15 is the value of wbits, which should be at the maximum possible value to - // ensure that any gzip stream can be decoded. The offset of 16 specifies that - // the stream to decompress will be formatted with a gzip wrapper. - auto ret = inflateInit2(&strm, 16 + 15); - if (ret != Z_OK) { return; } - - strm.avail_in = content.size(); - strm.next_in = (Bytef *)content.data(); - - std::string decompressed; - - const auto bufsiz = 16384; - char buff[bufsiz]; - do { - strm.avail_out = bufsiz; - strm.next_out = (Bytef *)buff; - inflate(&strm, Z_NO_FLUSH); - decompressed.append(buff, bufsiz - strm.avail_out); - } while (strm.avail_out == 0); - - content.swap(decompressed); - - inflateEnd(&strm); +inline std::string SHA_512(const std::string &s) { + return message_digest(s, SHA512_Init, SHA512_Update, SHA512_Final, + SHA512_DIGEST_LENGTH); } #endif #ifdef _WIN32 - class WSInit { + #ifdef CPPHTTPLIB_OPENSSL_SUPPORT +// NOTE: This code came up with the following stackoverflow post: +// https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store +inline bool load_system_certs_on_windows(X509_STORE *store) { + auto hStore = CertOpenSystemStoreW((HCRYPTPROV_LEGACY)NULL, L"ROOT"); + + if (!hStore) { return false; } + + PCCERT_CONTEXT pContext = NULL; + while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) != + nullptr) { + auto encoded_cert = + static_cast(pContext->pbCertEncoded); + + auto x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded); + if (x509) { + X509_STORE_add_cert(store, x509); + X509_free(x509); + } + } + + CertFreeCertificateContext(pContext); + CertCloseStore(hStore, 0); + + return true; +} +#endif + +class WSInit { public: WSInit() { WSADATA wsaData; @@ -1346,16 +3756,157 @@ public: static WSInit wsinit_; #endif +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + inline std::pair make_digest_authentication_header( + const Request &req, const std::map &auth, + size_t cnonce_count, const std::string &cnonce, const std::string &username, + const std::string &password, bool is_proxy = false) { + using namespace std; + + string nc; + { + stringstream ss; + ss << setfill('0') << setw(8) << hex << cnonce_count; + nc = ss.str(); + } + + auto qop = auth.at("qop"); + if (qop.find("auth-int") != std::string::npos) { + qop = "auth-int"; + } else { + qop = "auth"; + } + + std::string algo = "MD5"; + if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); } + + string response; + { + auto H = algo == "SHA-256" ? detail::SHA_256 + : algo == "SHA-512" ? detail::SHA_512 + : detail::MD5; + + auto A1 = username + ":" + auth.at("realm") + ":" + password; + + auto A2 = req.method + ":" + req.path; + if (qop == "auth-int") { A2 += ":" + H(req.body); } + + response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce + + ":" + qop + ":" + H(A2)); + } + + auto field = "Digest username=\"" + username + "\", realm=\"" + + auth.at("realm") + "\", nonce=\"" + auth.at("nonce") + + "\", uri=\"" + req.path + "\", algorithm=" + algo + + ", qop=" + qop + ", nc=\"" + nc + "\", cnonce=\"" + cnonce + + "\", response=\"" + response + "\""; + + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, field); +} +#endif + + inline bool parse_www_authenticate(const Response &res, + std::map &auth, + bool is_proxy) { + auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate"; + if (res.has_header(auth_key)) { + static auto re = std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~"); + auto s = res.get_header_value(auth_key); + auto pos = s.find(' '); + if (pos != std::string::npos) { + auto type = s.substr(0, pos); + if (type == "Basic") { + return false; + } else if (type == "Digest") { + s = s.substr(pos + 1); + auto beg = std::sregex_iterator(s.begin(), s.end(), re); + for (auto i = beg; i != std::sregex_iterator(); ++i) { + auto m = *i; + auto key = s.substr(static_cast(m.position(1)), + static_cast(m.length(1))); + auto val = m.length(2) > 0 + ? s.substr(static_cast(m.position(2)), + static_cast(m.length(2))) + : s.substr(static_cast(m.position(3)), + static_cast(m.length(3))); + auth[key] = val; + } + return true; + } + } + } + return false; + } + +// https://stackoverflow.com/questions/440133/how-do-i-create-a-random-alpha-numeric-string-in-c/440240#answer-440240 + inline std::string random_string(size_t length) { + auto randchar = []() -> char { + const char charset[] = "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + const size_t max_index = (sizeof(charset) - 1); + return charset[static_cast(std::rand()) % max_index]; + }; + std::string str(length, 0); + std::generate_n(str.begin(), length, randchar); + return str; + } + + class ContentProviderAdapter { + public: + explicit ContentProviderAdapter( + ContentProviderWithoutLength &&content_provider) + : content_provider_(content_provider) {} + + bool operator()(size_t offset, size_t, DataSink &sink) { + return content_provider_(offset, sink); + } + + private: + ContentProviderWithoutLength content_provider_; + }; + + template + inline void duration_to_sec_and_usec(const T &duration, U callback) { + auto sec = std::chrono::duration_cast(duration).count(); + auto usec = std::chrono::duration_cast( + duration - std::chrono::seconds(sec)) + .count(); + callback(sec, usec); + } + } // namespace detail // Header utilities - template - inline std::pair make_range_header(uint64_t value, - Args... args) { - std::string field; - detail::make_range_header_core(field, value, args...); - field.insert(0, "bytes="); - return std::make_pair("Range", field); + inline std::pair make_range_header(Ranges ranges) { + std::string field = "bytes="; + auto i = 0; + for (auto r : ranges) { + if (i != 0) { field += ", "; } + if (r.first != -1) { field += std::to_string(r.first); } + field += '-'; + if (r.second != -1) { field += std::to_string(r.second); } + i++; + } + return std::make_pair("Range", std::move(field)); + } + + inline std::pair + make_basic_authentication_header(const std::string &username, + const std::string &password, + bool is_proxy = false) { + auto field = "Basic " + detail::base64_encode(username + ":" + password); + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, std::move(field)); + } + + inline std::pair + make_bearer_token_authentication_header(const std::string &token, + bool is_proxy = false) { + auto field = "Bearer " + token; + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, std::move(field)); } // Request implementation @@ -1367,13 +3918,26 @@ static WSInit wsinit_; return detail::get_header_value(headers, key, id, ""); } + template + inline T Request::get_header_value(const char *key, size_t id) const { + return detail::get_header_value(headers, key, id, 0); + } + inline size_t Request::get_header_value_count(const char *key) const { auto r = headers.equal_range(key); - return std::distance(r.first, r.second); + return static_cast(std::distance(r.first, r.second)); } inline void Request::set_header(const char *key, const char *val) { - headers.emplace(key, val); + if (!detail::has_crlf(key) && !detail::has_crlf(val)) { + headers.emplace(key, val); + } + } + + inline void Request::set_header(const char *key, const std::string &val) { + if (!detail::has_crlf(key) && !detail::has_crlf(val.c_str())) { + headers.emplace(key, val); + } } inline bool Request::has_param(const char *key) const { @@ -1381,25 +3945,31 @@ static WSInit wsinit_; } inline std::string Request::get_param_value(const char *key, size_t id) const { - auto it = params.find(key); - std::advance(it, id); - if (it != params.end()) { return it->second; } + auto rng = params.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { return it->second; } return std::string(); } inline size_t Request::get_param_value_count(const char *key) const { auto r = params.equal_range(key); - return std::distance(r.first, r.second); + return static_cast(std::distance(r.first, r.second)); + } + + inline bool Request::is_multipart_form_data() const { + const auto &content_type = get_header_value("Content-Type"); + return !content_type.find("multipart/form-data"); } inline bool Request::has_file(const char *key) const { return files.find(key) != files.end(); } - inline MultipartFile Request::get_file_value(const char *key) const { + inline MultipartFormData Request::get_file_value(const char *key) const { auto it = files.find(key); if (it != files.end()) { return it->second; } - return MultipartFile(); + return MultipartFormData(); } // Response implementation @@ -1412,117 +3982,242 @@ static WSInit wsinit_; return detail::get_header_value(headers, key, id, ""); } + template + inline T Response::get_header_value(const char *key, size_t id) const { + return detail::get_header_value(headers, key, id, 0); + } + inline size_t Response::get_header_value_count(const char *key) const { auto r = headers.equal_range(key); - return std::distance(r.first, r.second); + return static_cast(std::distance(r.first, r.second)); } inline void Response::set_header(const char *key, const char *val) { - headers.emplace(key, val); + if (!detail::has_crlf(key) && !detail::has_crlf(val)) { + headers.emplace(key, val); + } } - inline void Response::set_redirect(const char *url) { - set_header("Location", url); - status = 302; + inline void Response::set_header(const char *key, const std::string &val) { + if (!detail::has_crlf(key) && !detail::has_crlf(val.c_str())) { + headers.emplace(key, val); + } + } + + inline void Response::set_redirect(const char *url, int stat) { + if (!detail::has_crlf(url)) { + set_header("Location", url); + if (300 <= stat && stat < 400) { + this->status = stat; + } else { + this->status = 302; + } + } + } + + inline void Response::set_redirect(const std::string &url, int stat) { + set_redirect(url.c_str(), stat); } inline void Response::set_content(const char *s, size_t n, const char *content_type) { body.assign(s, n); + + auto rng = headers.equal_range("Content-Type"); + headers.erase(rng.first, rng.second); set_header("Content-Type", content_type); } inline void Response::set_content(const std::string &s, const char *content_type) { - body = s; + set_content(s.data(), s.size(), content_type); + } + + inline void Response::set_content_provider( + size_t in_length, const char *content_type, ContentProvider provider, + ContentProviderResourceReleaser resource_releaser) { + assert(in_length > 0); set_header("Content-Type", content_type); + content_length_ = in_length; + content_provider_ = std::move(provider); + content_provider_resource_releaser_ = resource_releaser; + is_chunked_content_provider_ = false; } -// Rstream implementation - template - inline void Stream::write_format(const char *fmt, const Args &... args) { - const auto bufsiz = 2048; - char buf[bufsiz]; - -#if defined(_MSC_VER) && _MSC_VER < 1900 - auto n = _snprintf_s(buf, bufsiz, bufsiz - 1, fmt, args...); -#else - auto n = snprintf(buf, bufsiz - 1, fmt, args...); -#endif - if (n > 0) { - if (n >= bufsiz - 1) { - std::vector glowable_buf(bufsiz); - - while (n >= static_cast(glowable_buf.size() - 1)) { - glowable_buf.resize(glowable_buf.size() * 2); -#if defined(_MSC_VER) && _MSC_VER < 1900 - n = _snprintf_s(&glowable_buf[0], glowable_buf.size(), - glowable_buf.size() - 1, fmt, args...); -#else - n = snprintf(&glowable_buf[0], glowable_buf.size() - 1, fmt, args...); -#endif - } - write(&glowable_buf[0], n); - } else { - write(buf, n); - } - } + inline void Response::set_content_provider( + const char *content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser) { + set_header("Content-Type", content_type); + content_length_ = 0; + content_provider_ = detail::ContentProviderAdapter(std::move(provider)); + content_provider_resource_releaser_ = resource_releaser; + is_chunked_content_provider_ = false; } -// Socket stream implementation - inline SocketStream::SocketStream(socket_t sock) : sock_(sock) {} - - inline SocketStream::~SocketStream() {} - - inline int SocketStream::read(char *ptr, size_t size) { - if (detail::select_read(sock_, CPPHTTPLIB_READ_TIMEOUT_SECOND, - CPPHTTPLIB_READ_TIMEOUT_USECOND) > 0) { - return recv(sock_, ptr, static_cast(size), 0); - } - return -1; + inline void Response::set_chunked_content_provider( + const char *content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser) { + set_header("Content-Type", content_type); + content_length_ = 0; + content_provider_ = detail::ContentProviderAdapter(std::move(provider)); + content_provider_resource_releaser_ = resource_releaser; + is_chunked_content_provider_ = true; } - inline int SocketStream::write(const char *ptr, size_t size) { - return send(sock_, ptr, static_cast(size), 0); +// Result implementation + inline bool Result::has_request_header(const char *key) const { + return request_headers_.find(key) != request_headers_.end(); } - inline int SocketStream::write(const char *ptr) { + inline std::string Result::get_request_header_value(const char *key, + size_t id) const { + return detail::get_header_value(request_headers_, key, id, ""); + } + + template + inline T Result::get_request_header_value(const char *key, size_t id) const { + return detail::get_header_value(request_headers_, key, id, 0); + } + + inline size_t Result::get_request_header_value_count(const char *key) const { + auto r = request_headers_.equal_range(key); + return static_cast(std::distance(r.first, r.second)); + } + +// Stream implementation + inline ssize_t Stream::write(const char *ptr) { return write(ptr, strlen(ptr)); } - inline std::string SocketStream::get_remote_addr() const { - return detail::get_remote_addr(sock_); + inline ssize_t Stream::write(const std::string &s) { + return write(s.data(), s.size()); } + template + inline ssize_t Stream::write_format(const char *fmt, const Args &...args) { + const auto bufsiz = 2048; + std::array buf; + +#if defined(_MSC_VER) && _MSC_VER < 1900 + auto sn = _snprintf_s(buf.data(), bufsiz - 1, buf.size() - 1, fmt, args...); +#else + auto sn = snprintf(buf.data(), buf.size() - 1, fmt, args...); +#endif + if (sn <= 0) { return sn; } + + auto n = static_cast(sn); + + if (n >= buf.size() - 1) { + std::vector glowable_buf(buf.size()); + + while (n >= glowable_buf.size() - 1) { + glowable_buf.resize(glowable_buf.size() * 2); +#if defined(_MSC_VER) && _MSC_VER < 1900 + n = static_cast(_snprintf_s(&glowable_buf[0], glowable_buf.size(), + glowable_buf.size() - 1, fmt, + args...)); +#else + n = static_cast( + snprintf(&glowable_buf[0], glowable_buf.size() - 1, fmt, args...)); +#endif + } + return write(&glowable_buf[0], n); + } else { + return write(buf.data(), n); + } + } + + namespace detail { + +// Socket stream implementation + inline SocketStream::SocketStream(socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec) + : sock_(sock), read_timeout_sec_(read_timeout_sec), + read_timeout_usec_(read_timeout_usec), + write_timeout_sec_(write_timeout_sec), + write_timeout_usec_(write_timeout_usec) {} + + inline SocketStream::~SocketStream() {} + + inline bool SocketStream::is_readable() const { + return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; + } + + inline bool SocketStream::is_writable() const { + return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0; + } + + inline ssize_t SocketStream::read(char *ptr, size_t size) { + if (!is_readable()) { return -1; } + +#ifdef _WIN32 + if (size > static_cast((std::numeric_limits::max)())) { + return -1; + } + return recv(sock_, ptr, static_cast(size), CPPHTTPLIB_RECV_FLAGS); +#else + return handle_EINTR( + [&]() { return recv(sock_, ptr, size, CPPHTTPLIB_RECV_FLAGS); }); +#endif + } + + inline ssize_t SocketStream::write(const char *ptr, size_t size) { + if (!is_writable()) { return -1; } + +#ifdef _WIN32 + if (size > static_cast((std::numeric_limits::max)())) { + return -1; + } + return send(sock_, ptr, static_cast(size), CPPHTTPLIB_SEND_FLAGS); +#else + return handle_EINTR( + [&]() { return send(sock_, ptr, size, CPPHTTPLIB_SEND_FLAGS); }); +#endif + } + + inline void SocketStream::get_remote_ip_and_port(std::string &ip, + int &port) const { + return detail::get_remote_ip_and_port(sock_, ip, port); + } + + inline socket_t SocketStream::socket() const { return sock_; } + // Buffer stream implementation - inline int BufferStream::read(char *ptr, size_t size) { -#if defined(_MSC_VER) && _MSC_VER < 1900 - return static_cast(buffer._Copy_s(ptr, size, size)); + inline bool BufferStream::is_readable() const { return true; } + + inline bool BufferStream::is_writable() const { return true; } + + inline ssize_t BufferStream::read(char *ptr, size_t size) { +#if defined(_MSC_VER) && _MSC_VER <= 1900 + auto len_read = buffer._Copy_s(ptr, size, size, position); #else - return static_cast(buffer.copy(ptr, size)); + auto len_read = buffer.copy(ptr, size, position); #endif - } + position += static_cast(len_read); + return static_cast(len_read); + } - inline int BufferStream::write(const char *ptr, size_t size) { - buffer.append(ptr, size); - return static_cast(size); - } + inline ssize_t BufferStream::write(const char *ptr, size_t size) { + buffer.append(ptr, size); + return static_cast(size); + } - inline int BufferStream::write(const char *ptr) { - size_t size = strlen(ptr); - buffer.append(ptr, size); - return static_cast(size); - } + inline void BufferStream::get_remote_ip_and_port(std::string & /*ip*/, + int & /*port*/) const {} - inline std::string BufferStream::get_remote_addr() const { return ""; } + inline socket_t BufferStream::socket() const { return 0; } - inline const std::string &BufferStream::get_buffer() const { return buffer; } + inline const std::string &BufferStream::get_buffer() const { return buffer; } + + } // namespace detail // HTTP server implementation inline Server::Server() - : keep_alive_max_count_(CPPHTTPLIB_KEEPALIVE_MAX_COUNT), - payload_max_length_(CPPHTTPLIB_PAYLOAD_MAX_LENGTH), is_running_(false), - svr_sock_(INVALID_SOCKET), running_threads_(0) { + : new_task_queue( + [] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }), + svr_sock_(INVALID_SOCKET), is_running_(false) { #ifndef _WIN32 signal(SIGPIPE, SIG_IGN); #endif @@ -1531,57 +4226,278 @@ static WSInit wsinit_; inline Server::~Server() {} inline Server &Server::Get(const char *pattern, Handler handler) { - get_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); + return Get(pattern, strlen(pattern), handler); + } + + inline Server &Server::Get(const char *pattern, size_t pattern_len, + Handler handler) { + get_handlers_.push_back( + std::make_pair(std::regex(pattern, pattern_len), std::move(handler))); return *this; } inline Server &Server::Post(const char *pattern, Handler handler) { - post_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); + return Post(pattern, strlen(pattern), handler); + } + + inline Server &Server::Post(const char *pattern, size_t pattern_len, + Handler handler) { + post_handlers_.push_back( + std::make_pair(std::regex(pattern, pattern_len), std::move(handler))); + return *this; + } + + inline Server &Server::Post(const char *pattern, + HandlerWithContentReader handler) { + return Post(pattern, strlen(pattern), handler); + } + + inline Server &Server::Post(const char *pattern, size_t pattern_len, + HandlerWithContentReader handler) { + post_handlers_for_content_reader_.push_back( + std::make_pair(std::regex(pattern, pattern_len), std::move(handler))); return *this; } inline Server &Server::Put(const char *pattern, Handler handler) { - put_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); + return Put(pattern, strlen(pattern), handler); + } + + inline Server &Server::Put(const char *pattern, size_t pattern_len, + Handler handler) { + put_handlers_.push_back( + std::make_pair(std::regex(pattern, pattern_len), std::move(handler))); + return *this; + } + + inline Server &Server::Put(const char *pattern, + HandlerWithContentReader handler) { + return Put(pattern, strlen(pattern), handler); + } + + inline Server &Server::Put(const char *pattern, size_t pattern_len, + HandlerWithContentReader handler) { + put_handlers_for_content_reader_.push_back( + std::make_pair(std::regex(pattern, pattern_len), std::move(handler))); return *this; } inline Server &Server::Patch(const char *pattern, Handler handler) { - patch_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); + return Patch(pattern, strlen(pattern), handler); + } + + inline Server &Server::Patch(const char *pattern, size_t pattern_len, + Handler handler) { + patch_handlers_.push_back( + std::make_pair(std::regex(pattern, pattern_len), std::move(handler))); + return *this; + } + + inline Server &Server::Patch(const char *pattern, + HandlerWithContentReader handler) { + return Patch(pattern, strlen(pattern), handler); + } + + inline Server &Server::Patch(const char *pattern, size_t pattern_len, + HandlerWithContentReader handler) { + patch_handlers_for_content_reader_.push_back( + std::make_pair(std::regex(pattern, pattern_len), std::move(handler))); return *this; } inline Server &Server::Delete(const char *pattern, Handler handler) { - delete_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); + return Delete(pattern, strlen(pattern), handler); + } + + inline Server &Server::Delete(const char *pattern, size_t pattern_len, + Handler handler) { + delete_handlers_.push_back( + std::make_pair(std::regex(pattern, pattern_len), std::move(handler))); + return *this; + } + + inline Server &Server::Delete(const char *pattern, + HandlerWithContentReader handler) { + return Delete(pattern, strlen(pattern), handler); + } + + inline Server &Server::Delete(const char *pattern, size_t pattern_len, + HandlerWithContentReader handler) { + delete_handlers_for_content_reader_.push_back( + std::make_pair(std::regex(pattern, pattern_len), std::move(handler))); return *this; } inline Server &Server::Options(const char *pattern, Handler handler) { - options_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); + return Options(pattern, strlen(pattern), handler); + } + + inline Server &Server::Options(const char *pattern, size_t pattern_len, + Handler handler) { + options_handlers_.push_back( + std::make_pair(std::regex(pattern, pattern_len), std::move(handler))); return *this; } - inline bool Server::set_base_dir(const char *path) { - if (detail::is_dir(path)) { - base_dir_ = path; - return true; + inline bool Server::set_base_dir(const char *dir, const char *mount_point) { + return set_mount_point(mount_point, dir); + } + + inline bool Server::set_mount_point(const char *mount_point, const char *dir, + Headers headers) { + if (detail::is_dir(dir)) { + std::string mnt = mount_point ? mount_point : "/"; + if (!mnt.empty() && mnt[0] == '/') { + base_dirs_.push_back({mnt, dir, std::move(headers)}); + return true; + } } return false; } - inline void Server::set_error_handler(Handler handler) { - error_handler_ = handler; + inline bool Server::remove_mount_point(const char *mount_point) { + for (auto it = base_dirs_.begin(); it != base_dirs_.end(); ++it) { + if (it->mount_point == mount_point) { + base_dirs_.erase(it); + return true; + } + } + return false; } - inline void Server::set_logger(Logger logger) { logger_ = logger; } + inline Server & + Server::set_file_extension_and_mimetype_mapping(const char *ext, + const char *mime) { + file_extension_and_mimetype_map_[ext] = mime; + return *this; + } - inline void Server::set_keep_alive_max_count(size_t count) { + inline Server &Server::set_file_request_handler(Handler handler) { + file_request_handler_ = std::move(handler); + return *this; + } + + inline Server &Server::set_error_handler(HandlerWithResponse handler) { + error_handler_ = std::move(handler); + return *this; + } + + inline Server &Server::set_error_handler(Handler handler) { + error_handler_ = [handler](const Request &req, Response &res) { + handler(req, res); + return HandlerResponse::Handled; + }; + return *this; + } + + inline Server &Server::set_exception_handler(ExceptionHandler handler) { + exception_handler_ = std::move(handler); + return *this; + } + + inline Server &Server::set_pre_routing_handler(HandlerWithResponse handler) { + pre_routing_handler_ = std::move(handler); + return *this; + } + + inline Server &Server::set_post_routing_handler(Handler handler) { + post_routing_handler_ = std::move(handler); + return *this; + } + + inline Server &Server::set_logger(Logger logger) { + logger_ = std::move(logger); + return *this; + } + + inline Server & + Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) { + expect_100_continue_handler_ = std::move(handler); + + return *this; + } + + inline Server &Server::set_address_family(int family) { + address_family_ = family; + return *this; + } + + inline Server &Server::set_tcp_nodelay(bool on) { + tcp_nodelay_ = on; + return *this; + } + + inline Server &Server::set_socket_options(SocketOptions socket_options) { + socket_options_ = std::move(socket_options); + return *this; + } + + inline Server &Server::set_default_headers(Headers headers) { + default_headers_ = std::move(headers); + return *this; + } + + inline Server &Server::set_keep_alive_max_count(size_t count) { keep_alive_max_count_ = count; + return *this; } - inline void Server::set_payload_max_length(uint64_t length) { + inline Server &Server::set_keep_alive_timeout(time_t sec) { + keep_alive_timeout_sec_ = sec; + return *this; + } + + inline Server &Server::set_read_timeout(time_t sec, time_t usec) { + read_timeout_sec_ = sec; + read_timeout_usec_ = usec; + return *this; + } + + template + inline Server & + Server::set_read_timeout(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); + return *this; + } + + inline Server &Server::set_write_timeout(time_t sec, time_t usec) { + write_timeout_sec_ = sec; + write_timeout_usec_ = usec; + return *this; + } + + template + inline Server & + Server::set_write_timeout(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); + return *this; + } + + inline Server &Server::set_idle_interval(time_t sec, time_t usec) { + idle_interval_sec_ = sec; + idle_interval_usec_ = usec; + return *this; + } + + template + inline Server & + Server::set_idle_interval(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_idle_interval(sec, usec); }); + return *this; + } + + inline Server &Server::set_payload_max_length(size_t length) { payload_max_length_ = length; + return *this; } + inline bool Server::bind_to_port(const char *host, int port, int socket_flags) { + if (bind_internal(host, port, socket_flags) < 0) return false; + return true; + } inline int Server::bind_to_any_port(const char *host, int socket_flags) { return bind_internal(host, 0, socket_flags); } @@ -1589,8 +4505,7 @@ static WSInit wsinit_; inline bool Server::listen_after_bind() { return listen_internal(); } inline bool Server::listen(const char *host, int port, int socket_flags) { - if (bind_internal(host, port, socket_flags) < 0) return false; - return listen_internal(); + return bind_to_port(host, port, socket_flags) && listen_internal(); } inline bool Server::is_running() const { return is_running_; } @@ -1598,22 +4513,23 @@ static WSInit wsinit_; inline void Server::stop() { if (is_running_) { assert(svr_sock_ != INVALID_SOCKET); - std::atomic sock (svr_sock_.exchange(INVALID_SOCKET)); + std::atomic sock(svr_sock_.exchange(INVALID_SOCKET)); detail::shutdown_socket(sock); detail::close_socket(sock); } } inline bool Server::parse_request_line(const char *s, Request &req) { - static std::regex re("(GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS) " - "(([^?]+)(?:\\?(.+?))?) (HTTP/1\\.[01])\r\n"); + const static std::regex re( + "(GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH|PRI) " + "(([^? ]+)(?:\\?([^ ]*?))?) (HTTP/1\\.[01])\r\n"); std::cmatch m; if (std::regex_match(s, m, re)) { req.version = std::string(m[5]); req.method = std::string(m[1]); req.target = std::string(m[2]); - req.path = detail::decode_url(m[3]); + req.path = detail::decode_url(m[3], false); // Parse query text auto len = std::distance(m[4].first, m[4].second); @@ -1625,131 +4541,304 @@ static WSInit wsinit_; return false; } - inline void Server::write_response(Stream &strm, bool last_connection, + inline bool Server::write_response(Stream &strm, bool close_connection, const Request &req, Response &res) { + return write_response_core(strm, close_connection, req, res, false); + } + + inline bool Server::write_response_with_content(Stream &strm, + bool close_connection, + const Request &req, + Response &res) { + return write_response_core(strm, close_connection, req, res, true); + } + + inline bool Server::write_response_core(Stream &strm, bool close_connection, + const Request &req, Response &res, + bool need_apply_ranges) { assert(res.status != -1); - if (400 <= res.status && error_handler_) { error_handler_(req, res); } + if (400 <= res.status && error_handler_ && + error_handler_(req, res) == HandlerResponse::Handled) { + need_apply_ranges = true; + } - // Response line - strm.write_format("HTTP/1.1 %d %s\r\n", res.status, - detail::status_message(res.status)); + std::string content_type; + std::string boundary; + if (need_apply_ranges) { apply_ranges(req, res, content_type, boundary); } - // Headers - if (last_connection || req.get_header_value("Connection") == "close") { + // Prepare additional headers + if (close_connection || req.get_header_value("Connection") == "close") { res.set_header("Connection", "close"); - } - - if (!last_connection && req.get_header_value("Connection") == "Keep-Alive") { - res.set_header("Connection", "Keep-Alive"); - } - - if (res.body.empty()) { - if (!res.has_header("Content-Length")) { - if (res.streamcb) { - // Streamed response - res.set_header("Transfer-Encoding", "chunked"); - } else { - res.set_header("Content-Length", "0"); - } - } } else { -#ifdef CPPHTTPLIB_ZLIB_SUPPORT - // TODO: 'Accpet-Encoding' has gzip, not gzip;q=0 - const auto &encodings = req.get_header_value("Accept-Encoding"); - if (encodings.find("gzip") != std::string::npos && - detail::can_compress(res.get_header_value("Content-Type"))) { - detail::compress(res.body); - res.set_header("Content-Encoding", "gzip"); - } -#endif - - if (!res.has_header("Content-Type")) { - res.set_header("Content-Type", "text/plain"); - } - - auto length = std::to_string(res.body.size()); - res.set_header("Content-Length", length.c_str()); + std::stringstream ss; + ss << "timeout=" << keep_alive_timeout_sec_ + << ", max=" << keep_alive_max_count_; + res.set_header("Keep-Alive", ss.str()); } - detail::write_headers(strm, res); + if (!res.has_header("Content-Type") && + (!res.body.empty() || res.content_length_ > 0 || res.content_provider_)) { + res.set_header("Content-Type", "text/plain"); + } + + if (!res.has_header("Content-Length") && res.body.empty() && + !res.content_length_ && !res.content_provider_) { + res.set_header("Content-Length", "0"); + } + + if (!res.has_header("Accept-Ranges") && req.method == "HEAD") { + res.set_header("Accept-Ranges", "bytes"); + } + + if (post_routing_handler_) { post_routing_handler_(req, res); } + + // Response line and headers + { + detail::BufferStream bstrm; + + if (!bstrm.write_format("HTTP/1.1 %d %s\r\n", res.status, + detail::status_message(res.status))) { + return false; + } + + if (!detail::write_headers(bstrm, res.headers)) { return false; } + + // Flush buffer + auto &data = bstrm.get_buffer(); + strm.write(data.data(), data.size()); + } // Body + auto ret = true; if (req.method != "HEAD") { if (!res.body.empty()) { - strm.write(res.body.c_str(), res.body.size()); - } else if (res.streamcb) { - bool chunked_response = !res.has_header("Content-Length"); - uint64_t offset = 0; - bool data_available = true; - while (data_available) { - std::string chunk = res.streamcb(offset); - offset += chunk.size(); - data_available = !chunk.empty(); - // Emit chunked response header and footer for each chunk - if (chunked_response) - chunk = detail::from_i_to_hex(chunk.size()) + "\r\n" + chunk + "\r\n"; - if (strm.write(chunk.c_str(), chunk.size()) < 0) break; // Stop on error + if (!strm.write(res.body)) { ret = false; } + } else if (res.content_provider_) { + if (write_content_with_provider(strm, req, res, boundary, + content_type)) { + res.content_provider_success_ = true; + } else { + res.content_provider_success_ = false; + ret = false; } } } // Log if (logger_) { logger_(req, res); } + + return ret; } - inline bool Server::handle_file_request(Request &req, Response &res) { - if (!base_dir_.empty() && detail::is_valid_path(req.path)) { - std::string path = base_dir_ + req.path; + inline bool + Server::write_content_with_provider(Stream &strm, const Request &req, + Response &res, const std::string &boundary, + const std::string &content_type) { + auto is_shutting_down = [this]() { + return this->svr_sock_ == INVALID_SOCKET; + }; - if (!path.empty() && path.back() == '/') { path += "index.html"; } + if (res.content_length_ > 0) { + if (req.ranges.empty()) { + return detail::write_content(strm, res.content_provider_, 0, + res.content_length_, is_shutting_down); + } else if (req.ranges.size() == 1) { + auto offsets = + detail::get_range_offset_and_length(req, res.content_length_, 0); + auto offset = offsets.first; + auto length = offsets.second; + return detail::write_content(strm, res.content_provider_, offset, length, + is_shutting_down); + } else { + return detail::write_multipart_ranges_data( + strm, req, res, boundary, content_type, is_shutting_down); + } + } else { + if (res.is_chunked_content_provider_) { + auto type = detail::encoding_type(req, res); - if (detail::is_file(path)) { - detail::read_file(path, res.body); - auto type = detail::find_content_type(path); - if (type) { res.set_header("Content-Type", type); } - res.status = 200; - return true; + std::unique_ptr compressor; + if (type == detail::EncodingType::Gzip) { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + compressor = detail::make_unique(); +#endif + } else if (type == detail::EncodingType::Brotli) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + compressor = detail::make_unique(); +#endif + } else { + compressor = detail::make_unique(); + } + assert(compressor != nullptr); + + return detail::write_content_chunked(strm, res.content_provider_, + is_shutting_down, *compressor); + } else { + return detail::write_content_without_length(strm, res.content_provider_, + is_shutting_down); } } + } + inline bool Server::read_content(Stream &strm, Request &req, Response &res) { + MultipartFormDataMap::iterator cur; + if (read_content_core( + strm, req, res, + // Regular + [&](const char *buf, size_t n) { + if (req.body.size() + n > req.body.max_size()) { return false; } + req.body.append(buf, n); + return true; + }, + // Multipart + [&](const MultipartFormData &file) { + cur = req.files.emplace(file.name, file); + return true; + }, + [&](const char *buf, size_t n) { + auto &content = cur->second.content; + if (content.size() + n > content.max_size()) { return false; } + content.append(buf, n); + return true; + })) { + const auto &content_type = req.get_header_value("Content-Type"); + if (!content_type.find("application/x-www-form-urlencoded")) { + detail::parse_query_text(req.body, req.params); + } + return true; + } return false; } - inline socket_t Server::create_server_socket(const char *host, int port, - int socket_flags) const { + inline bool Server::read_content_with_content_receiver( + Stream &strm, Request &req, Response &res, ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver) { + return read_content_core(strm, req, res, std::move(receiver), + std::move(multipart_header), + std::move(multipart_receiver)); + } + + inline bool Server::read_content_core(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + MultipartContentHeader mulitpart_header, + ContentReceiver multipart_receiver) { + detail::MultipartFormDataParser multipart_form_data_parser; + ContentReceiverWithProgress out; + + if (req.is_multipart_form_data()) { + const auto &content_type = req.get_header_value("Content-Type"); + std::string boundary; + if (!detail::parse_multipart_boundary(content_type, boundary)) { + res.status = 400; + return false; + } + + multipart_form_data_parser.set_boundary(std::move(boundary)); + out = [&](const char *buf, size_t n, uint64_t /*off*/, uint64_t /*len*/) { + /* For debug + size_t pos = 0; + while (pos < n) { + auto read_size = std::min(1, n - pos); + auto ret = multipart_form_data_parser.parse( + buf + pos, read_size, multipart_receiver, mulitpart_header); + if (!ret) { return false; } + pos += read_size; + } + return true; + */ + return multipart_form_data_parser.parse(buf, n, multipart_receiver, + mulitpart_header); + }; + } else { + out = [receiver](const char *buf, size_t n, uint64_t /*off*/, + uint64_t /*len*/) { return receiver(buf, n); }; + } + + if (req.method == "DELETE" && !req.has_header("Content-Length")) { + return true; + } + + if (!detail::read_content(strm, req, payload_max_length_, res.status, nullptr, + out, true)) { + return false; + } + + if (req.is_multipart_form_data()) { + if (!multipart_form_data_parser.is_valid()) { + res.status = 400; + return false; + } + } + + return true; + } + + inline bool Server::handle_file_request(const Request &req, Response &res, + bool head) { + for (const auto &entry : base_dirs_) { + // Prefix match + if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point)) { + std::string sub_path = "/" + req.path.substr(entry.mount_point.size()); + if (detail::is_valid_path(sub_path)) { + auto path = entry.base_dir + sub_path; + if (path.back() == '/') { path += "index.html"; } + + if (detail::is_file(path)) { + detail::read_file(path, res.body); + auto type = + detail::find_content_type(path, file_extension_and_mimetype_map_); + if (type) { res.set_header("Content-Type", type); } + for (const auto &kv : entry.headers) { + res.set_header(kv.first.c_str(), kv.second); + } + res.status = req.has_header("Range") ? 206 : 200; + if (!head && file_request_handler_) { + file_request_handler_(req, res); + } + return true; + } + } + } + } + return false; + } + + inline socket_t + Server::create_server_socket(const char *host, int port, int socket_flags, + SocketOptions socket_options) const { return detail::create_socket( - host, port, + host, port, address_family_, socket_flags, tcp_nodelay_, + std::move(socket_options), [](socket_t sock, struct addrinfo &ai) -> bool { - if (::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { + if (::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { return false; } if (::listen(sock, 5)) { // Listen through 5 channels return false; } return true; - }, - socket_flags); + }); } inline int Server::bind_internal(const char *host, int port, int socket_flags) { if (!is_valid()) { return -1; } - svr_sock_ = create_server_socket(host, port, socket_flags); + svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_); if (svr_sock_ == INVALID_SOCKET) { return -1; } if (port == 0) { - struct sockaddr_storage address; - socklen_t len = sizeof(address); - if (getsockname(svr_sock_, reinterpret_cast(&address), - &len) == -1) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + if (getsockname(svr_sock_, reinterpret_cast(&addr), + &addr_len) == -1) { return -1; } - if (address.ss_family == AF_INET) { - return ntohs(reinterpret_cast(&address)->sin_port); - } else if (address.ss_family == AF_INET6) { - return ntohs( - reinterpret_cast(&address)->sin6_port); + if (addr.ss_family == AF_INET) { + return ntohs(reinterpret_cast(&addr)->sin_port); + } else if (addr.ss_family == AF_INET6) { + return ntohs(reinterpret_cast(&addr)->sin6_port); } else { return -1; } @@ -1758,119 +4847,150 @@ static WSInit wsinit_; } } - inline bool Server::poll() { - auto ret = true; - - is_running_ = true; - - if (svr_sock_ == INVALID_SOCKET) { - // The server socket was closed by 'stop' method. - return false; - } - - auto val = detail::select_read(svr_sock_, 0, 1000); - - if (val == 0) { // Timeout - return false; - } - - socket_t sock = accept(svr_sock_, nullptr, nullptr); - - if (sock == INVALID_SOCKET) { - if (svr_sock_ != INVALID_SOCKET) { - detail::close_socket(svr_sock_); - ret = false; - } else { - ; // The server socket was closed by user. - } - return false; - } - - read_and_close_socket(sock); - - is_running_ = false; - - return ret; - } - inline bool Server::listen_internal() { auto ret = true; - is_running_ = true; - for (;;) { - if (svr_sock_ == INVALID_SOCKET) { - // The server socket was closed by 'stop' method. - break; - } + { + std::unique_ptr task_queue(new_task_queue()); - auto val = detail::select_read(svr_sock_, 0, 100000); - - if (val == 0) { // Timeout - continue; - } - - socket_t sock = accept(svr_sock_, nullptr, nullptr); - - if (sock == INVALID_SOCKET) { - if (svr_sock_ != INVALID_SOCKET) { - detail::close_socket(svr_sock_); - ret = false; - } else { - ; // The server socket was closed by user. + while (svr_sock_ != INVALID_SOCKET) { +#ifndef _WIN32 + if (idle_interval_sec_ > 0 || idle_interval_usec_ > 0) { +#endif + auto val = detail::select_read(svr_sock_, idle_interval_sec_, + idle_interval_usec_); + if (val == 0) { // Timeout + task_queue->on_idle(); + continue; + } +#ifndef _WIN32 } - break; - } +#endif + socket_t sock = accept(svr_sock_, nullptr, nullptr); - // TODO: Use thread pool... - std::thread([=]() { - { - std::lock_guard guard(running_threads_mutex_); - running_threads_++; + if (sock == INVALID_SOCKET) { + if (errno == EMFILE) { + // The per-process limit of open file descriptors has been reached. + // Try to accept new connections after a short sleep. + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + continue; + } + if (svr_sock_ != INVALID_SOCKET) { + detail::close_socket(svr_sock_); + ret = false; + } else { + ; // The server socket was closed by user. + } + break; } - read_and_close_socket(sock); - { - std::lock_guard guard(running_threads_mutex_); - running_threads_--; + timeval tv; + tv.tv_sec = static_cast(read_timeout_sec_); + tv.tv_usec = static_cast(read_timeout_usec_); + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv)); + } + { + timeval tv; + tv.tv_sec = static_cast(write_timeout_sec_); + tv.tv_usec = static_cast(write_timeout_usec_); + setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv)); } - }).detach(); - } - // TODO: Use thread pool... - for (;;) { - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - std::lock_guard guard(running_threads_mutex_); - if (!running_threads_) { break; } +#if __cplusplus > 201703L + task_queue->enqueue([=, this]() { process_and_close_socket(sock); }); +#else + task_queue->enqueue([=]() { process_and_close_socket(sock); }); +#endif + } + + task_queue->shutdown(); } is_running_ = false; - return ret; } - inline bool Server::routing(Request &req, Response &res) { - if (req.method == "GET" && handle_file_request(req, res)) { return true; } + inline bool Server::routing(Request &req, Response &res, Stream &strm) { + if (pre_routing_handler_ && + pre_routing_handler_(req, res) == HandlerResponse::Handled) { + return true; + } + // File handler + bool is_head_request = req.method == "HEAD"; + if ((req.method == "GET" || is_head_request) && + handle_file_request(req, res, is_head_request)) { + return true; + } + + if (detail::expect_content(req)) { + // Content reader handler + { + ContentReader reader( + [&](ContentReceiver receiver) { + return read_content_with_content_receiver( + strm, req, res, std::move(receiver), nullptr, nullptr); + }, + [&](MultipartContentHeader header, ContentReceiver receiver) { + return read_content_with_content_receiver(strm, req, res, nullptr, + std::move(header), + std::move(receiver)); + }); + + if (req.method == "POST") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + post_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "PUT") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + put_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "PATCH") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + patch_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "DELETE") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + delete_handlers_for_content_reader_)) { + return true; + } + } + } + + // Read content into `req.body` + if (!read_content(strm, req, res)) { return false; } + } + + // Regular handler if (req.method == "GET" || req.method == "HEAD") { return dispatch_request(req, res, get_handlers_); } else if (req.method == "POST") { return dispatch_request(req, res, post_handlers_); } else if (req.method == "PUT") { return dispatch_request(req, res, put_handlers_); - } else if (req.method == "PATCH") { - return dispatch_request(req, res, patch_handlers_); } else if (req.method == "DELETE") { return dispatch_request(req, res, delete_handlers_); } else if (req.method == "OPTIONS") { return dispatch_request(req, res, options_handlers_); + } else if (req.method == "PATCH") { + return dispatch_request(req, res, patch_handlers_); } + + res.status = 400; return false; } inline bool Server::dispatch_request(Request &req, Response &res, - Handlers &handlers) { + const Handlers &handlers) { for (const auto &x : handlers) { const auto &pattern = x.first; const auto &handler = x.second; @@ -1883,424 +5003,1509 @@ static WSInit wsinit_; return false; } - inline bool - Server::process_request(Stream &strm, bool last_connection, - bool &connection_close, - std::function setup_request) { - const auto bufsiz = 2048; - char buf[bufsiz]; + inline void Server::apply_ranges(const Request &req, Response &res, + std::string &content_type, + std::string &boundary) { + if (req.ranges.size() > 1) { + boundary = detail::make_multipart_data_boundary(); - detail::stream_line_reader reader(strm, buf, bufsiz); + auto it = res.headers.find("Content-Type"); + if (it != res.headers.end()) { + content_type = it->second; + res.headers.erase(it); + } + + res.headers.emplace("Content-Type", + "multipart/byteranges; boundary=" + boundary); + } + + auto type = detail::encoding_type(req, res); + + if (res.body.empty()) { + if (res.content_length_ > 0) { + size_t length = 0; + if (req.ranges.empty()) { + length = res.content_length_; + } else if (req.ranges.size() == 1) { + auto offsets = + detail::get_range_offset_and_length(req, res.content_length_, 0); + auto offset = offsets.first; + length = offsets.second; + auto content_range = detail::make_content_range_header_field( + offset, length, res.content_length_); + res.set_header("Content-Range", content_range); + } else { + length = detail::get_multipart_ranges_data_length(req, res, boundary, + content_type); + } + res.set_header("Content-Length", std::to_string(length)); + } else { + if (res.content_provider_) { + if (res.is_chunked_content_provider_) { + res.set_header("Transfer-Encoding", "chunked"); + if (type == detail::EncodingType::Gzip) { + res.set_header("Content-Encoding", "gzip"); + } else if (type == detail::EncodingType::Brotli) { + res.set_header("Content-Encoding", "br"); + } + } + } + } + } else { + if (req.ranges.empty()) { + ; + } else if (req.ranges.size() == 1) { + auto offsets = + detail::get_range_offset_and_length(req, res.body.size(), 0); + auto offset = offsets.first; + auto length = offsets.second; + auto content_range = detail::make_content_range_header_field( + offset, length, res.body.size()); + res.set_header("Content-Range", content_range); + if (offset < res.body.size()) { + res.body = res.body.substr(offset, length); + } else { + res.body.clear(); + res.status = 416; + } + } else { + std::string data; + if (detail::make_multipart_ranges_data(req, res, boundary, content_type, + data)) { + res.body.swap(data); + } else { + res.body.clear(); + res.status = 416; + } + } + + if (type != detail::EncodingType::None) { + std::unique_ptr compressor; + std::string content_encoding; + + if (type == detail::EncodingType::Gzip) { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + compressor = detail::make_unique(); + content_encoding = "gzip"; +#endif + } else if (type == detail::EncodingType::Brotli) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + compressor = detail::make_unique(); + content_encoding = "br"; +#endif + } + + if (compressor) { + std::string compressed; + if (compressor->compress(res.body.data(), res.body.size(), true, + [&](const char *data, size_t data_len) { + compressed.append(data, data_len); + return true; + })) { + res.body.swap(compressed); + res.set_header("Content-Encoding", content_encoding); + } + } + } + + auto length = std::to_string(res.body.size()); + res.set_header("Content-Length", length); + } + } + + inline bool Server::dispatch_request_for_content_reader( + Request &req, Response &res, ContentReader content_reader, + const HandlersForContentReader &handlers) { + for (const auto &x : handlers) { + const auto &pattern = x.first; + const auto &handler = x.second; + + if (std::regex_match(req.path, req.matches, pattern)) { + handler(req, res, content_reader); + return true; + } + } + return false; + } + + inline bool + Server::process_request(Stream &strm, bool close_connection, + bool &connection_closed, + const std::function &setup_request) { + std::array buf{}; + + detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); // Connection has been closed on client - if (!reader.getline()) { return false; } + if (!line_reader.getline()) { return false; } Request req; Response res; res.version = "HTTP/1.1"; + for (const auto &header : default_headers_) { + if (res.headers.find(header.first) == res.headers.end()) { + res.headers.insert(header); + } + } + +#ifdef _WIN32 + // TODO: Increase FD_SETSIZE statically (libzmq), dynamically (MySQL). +#else +#ifndef CPPHTTPLIB_USE_POLL + // Socket file descriptor exceeded FD_SETSIZE... + if (strm.socket() >= FD_SETSIZE) { + Headers dummy; + detail::read_headers(strm, dummy); + res.status = 500; + return write_response(strm, close_connection, req, res); + } +#endif +#endif + // Check if the request URI doesn't exceed the limit - if (reader.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) { + if (line_reader.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) { + Headers dummy; + detail::read_headers(strm, dummy); res.status = 414; - write_response(strm, last_connection, req, res); - return true; + return write_response(strm, close_connection, req, res); } // Request line and headers - if (!parse_request_line(reader.ptr(), req) || + if (!parse_request_line(line_reader.ptr(), req) || !detail::read_headers(strm, req.headers)) { res.status = 400; - write_response(strm, last_connection, req, res); - return true; + return write_response(strm, close_connection, req, res); } if (req.get_header_value("Connection") == "close") { - connection_close = true; + connection_closed = true; } - req.set_header("REMOTE_ADDR", strm.get_remote_addr().c_str()); + if (req.version == "HTTP/1.0" && + req.get_header_value("Connection") != "Keep-Alive") { + connection_closed = true; + } - // Body - if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH") { - bool exceed_payload_max_length = false; - if (!detail::read_content(strm, req, payload_max_length_, - exceed_payload_max_length)) { - res.status = exceed_payload_max_length ? 413 : 400; - write_response(strm, last_connection, req, res); - return !exceed_payload_max_length; - } + strm.get_remote_ip_and_port(req.remote_addr, req.remote_port); + req.set_header("REMOTE_ADDR", req.remote_addr); + req.set_header("REMOTE_PORT", std::to_string(req.remote_port)); - const auto &content_type = req.get_header_value("Content-Type"); - - if (req.get_header_value("Content-Encoding") == "gzip") { -#ifdef CPPHTTPLIB_ZLIB_SUPPORT - detail::decompress(req.body); -#else - res.status = 415; - write_response(strm, last_connection, req, res); - return true; -#endif - } - - if (!content_type.find("application/x-www-form-urlencoded")) { - detail::parse_query_text(req.body, req.params); - } else if (!content_type.find("multipart/form-data")) { - std::string boundary; - if (!detail::parse_multipart_boundary(content_type, boundary) || - !detail::parse_multipart_formdata(boundary, req.body, req.files)) { - res.status = 400; - write_response(strm, last_connection, req, res); - return true; - } + if (req.has_header("Range")) { + const auto &range_header_value = req.get_header_value("Range"); + if (!detail::parse_range_header(range_header_value, req.ranges)) { + res.status = 416; + return write_response(strm, close_connection, req, res); } } - // TODO: Add additional request info if (setup_request) { setup_request(req); } - if (routing(req, res)) { - if (res.status == -1) { res.status = 200; } - } else { - res.status = 404; + if (req.get_header_value("Expect") == "100-continue") { + auto status = 100; + if (expect_100_continue_handler_) { + status = expect_100_continue_handler_(req, res); + } + switch (status) { + case 100: + case 417: + strm.write_format("HTTP/1.1 %d %s\r\n\r\n", status, + detail::status_message(status)); + break; + default: return write_response(strm, close_connection, req, res); + } } - write_response(strm, last_connection, req, res); - return true; + // Rounting + bool routed = false; + try { + routed = routing(req, res, strm); + } catch (std::exception &e) { + if (exception_handler_) { + exception_handler_(req, res, e); + routed = true; + } else { + res.status = 500; + res.set_header("EXCEPTION_WHAT", e.what()); + } + } catch (...) { + res.status = 500; + res.set_header("EXCEPTION_WHAT", "UNKNOWN"); + } + + if (routed) { + if (res.status == -1) { res.status = req.ranges.empty() ? 200 : 206; } + return write_response_with_content(strm, close_connection, req, res); + } else { + if (res.status == -1) { res.status = 404; } + return write_response(strm, close_connection, req, res); + } } inline bool Server::is_valid() const { return true; } - inline bool Server::read_and_close_socket(socket_t sock) { - return detail::read_and_close_socket( - sock, keep_alive_max_count_, - [this](Stream &strm, bool last_connection, bool &connection_close) { - return process_request(strm, last_connection, connection_close); + inline bool Server::process_and_close_socket(socket_t sock) { + auto ret = detail::process_server_socket( + sock, keep_alive_max_count_, keep_alive_timeout_sec_, read_timeout_sec_, + read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, + [this](Stream &strm, bool close_connection, bool &connection_closed) { + return process_request(strm, close_connection, connection_closed, + nullptr); }); + + detail::shutdown_socket(sock); + detail::close_socket(sock); + return ret; } // HTTP client implementation - inline Client::Client(const char *host, int port, time_t timeout_sec) - : host_(host), port_(port), timeout_sec_(timeout_sec), - host_and_port_(host_ + ":" + std::to_string(port_)) {} + inline ClientImpl::ClientImpl(const std::string &host) + : ClientImpl(host, 80, std::string(), std::string()) {} - inline Client::~Client() {} + inline ClientImpl::ClientImpl(const std::string &host, int port) + : ClientImpl(host, port, std::string(), std::string()) {} - inline bool Client::is_valid() const { return true; } + inline ClientImpl::ClientImpl(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path) + // : (Error::Success), host_(host), port_(port), + : host_(host), port_(port), + host_and_port_(host_ + ":" + std::to_string(port_)), + client_cert_path_(client_cert_path), client_key_path_(client_key_path) {} - inline socket_t Client::create_client_socket() const { - return detail::create_socket( - host_.c_str(), port_, [=](socket_t sock, struct addrinfo &ai) -> bool { - detail::set_nonblocking(sock, true); - - auto ret = connect(sock, ai.ai_addr, static_cast(ai.ai_addrlen)); - if (ret < 0) { - if (detail::is_connection_error() || - !detail::wait_until_socket_is_ready(sock, timeout_sec_, 0)) { - detail::close_socket(sock); - return false; - } - } - - detail::set_nonblocking(sock, false); - return true; - }); + inline ClientImpl::~ClientImpl() { + std::lock_guard guard(socket_mutex_); + shutdown_socket(socket_); + close_socket(socket_); } - inline bool Client::read_response_line(Stream &strm, Response &res) { - const auto bufsiz = 2048; - char buf[bufsiz]; + inline bool ClientImpl::is_valid() const { return true; } - detail::stream_line_reader reader(strm, buf, bufsiz); + inline void ClientImpl::copy_settings(const ClientImpl &rhs) { + client_cert_path_ = rhs.client_cert_path_; + client_key_path_ = rhs.client_key_path_; + connection_timeout_sec_ = rhs.connection_timeout_sec_; + read_timeout_sec_ = rhs.read_timeout_sec_; + read_timeout_usec_ = rhs.read_timeout_usec_; + write_timeout_sec_ = rhs.write_timeout_sec_; + write_timeout_usec_ = rhs.write_timeout_usec_; + basic_auth_username_ = rhs.basic_auth_username_; + basic_auth_password_ = rhs.basic_auth_password_; + bearer_token_auth_token_ = rhs.bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + digest_auth_username_ = rhs.digest_auth_username_; + digest_auth_password_ = rhs.digest_auth_password_; +#endif + keep_alive_ = rhs.keep_alive_; + follow_location_ = rhs.follow_location_; + url_encode_ = rhs.url_encode_; + address_family_ = rhs.address_family_; + tcp_nodelay_ = rhs.tcp_nodelay_; + socket_options_ = rhs.socket_options_; + compress_ = rhs.compress_; + decompress_ = rhs.decompress_; + interface_ = rhs.interface_; + proxy_host_ = rhs.proxy_host_; + proxy_port_ = rhs.proxy_port_; + proxy_basic_auth_username_ = rhs.proxy_basic_auth_username_; + proxy_basic_auth_password_ = rhs.proxy_basic_auth_password_; + proxy_bearer_token_auth_token_ = rhs.proxy_bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_; + proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_; +#endif +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + server_certificate_verification_ = rhs.server_certificate_verification_; +#endif + logger_ = rhs.logger_; + } - if (!reader.getline()) { return false; } + inline socket_t ClientImpl::create_client_socket(Error &error) const { + if (!proxy_host_.empty() && proxy_port_ != -1) { + return detail::create_client_socket( + proxy_host_.c_str(), proxy_port_, address_family_, tcp_nodelay_, + socket_options_, connection_timeout_sec_, connection_timeout_usec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, interface_, error); + } + return detail::create_client_socket( + host_.c_str(), port_, address_family_, tcp_nodelay_, socket_options_, + connection_timeout_sec_, connection_timeout_usec_, read_timeout_sec_, + read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, interface_, + error); + } - const static std::regex re("(HTTP/1\\.[01]) (\\d+?) .*\r\n"); + inline bool ClientImpl::create_and_connect_socket(Socket &socket, + Error &error) { + auto sock = create_client_socket(error); + if (sock == INVALID_SOCKET) { return false; } + socket.sock = sock; + return true; + } + + inline void ClientImpl::shutdown_ssl(Socket & /*socket*/, + bool /*shutdown_gracefully*/) { + // If there are any requests in flight from threads other than us, then it's + // a thread-unsafe race because individual ssl* objects are not thread-safe. + assert(socket_requests_in_flight_ == 0 || + socket_requests_are_from_thread_ == std::this_thread::get_id()); + } + + inline void ClientImpl::shutdown_socket(Socket &socket) { + if (socket.sock == INVALID_SOCKET) { return; } + detail::shutdown_socket(socket.sock); + } + + inline void ClientImpl::close_socket(Socket &socket) { + // If there are requests in flight in another thread, usually closing + // the socket will be fine and they will simply receive an error when + // using the closed socket, but it is still a bug since rarely the OS + // may reassign the socket id to be used for a new socket, and then + // suddenly they will be operating on a live socket that is different + // than the one they intended! + assert(socket_requests_in_flight_ == 0 || + socket_requests_are_from_thread_ == std::this_thread::get_id()); + + // It is also a bug if this happens while SSL is still active +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + assert(socket.ssl == nullptr); +#endif + if (socket.sock == INVALID_SOCKET) { return; } + detail::close_socket(socket.sock); + socket.sock = INVALID_SOCKET; + } + + inline bool ClientImpl::read_response_line(Stream &strm, const Request &req, + Response &res) { + std::array buf; + + detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); + + if (!line_reader.getline()) { return false; } + + const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r\n"); std::cmatch m; - if (std::regex_match(reader.ptr(), m, re)) { + if (!std::regex_match(line_reader.ptr(), m, re)) { + return req.method == "CONNECT"; + } + res.version = std::string(m[1]); + res.status = std::stoi(std::string(m[2])); + res.reason = std::string(m[3]); + + // Ignore '100 Continue' + while (res.status == 100) { + if (!line_reader.getline()) { return false; } // CRLF + if (!line_reader.getline()) { return false; } // next response line + + if (!std::regex_match(line_reader.ptr(), m, re)) { return false; } res.version = std::string(m[1]); res.status = std::stoi(std::string(m[2])); + res.reason = std::string(m[3]); } return true; } - inline bool Client::send(Request &req, Response &res) { - if (req.path.empty()) { return false; } + inline bool ClientImpl::send(Request &req, Response &res, Error &error) { + std::lock_guard request_mutex_guard(request_mutex_); - auto sock = create_client_socket(); - if (sock == INVALID_SOCKET) { return false; } + { + std::lock_guard guard(socket_mutex_); + // Set this to false immediately - if it ever gets set to true by the end of + // the request, we know another thread instructed us to close the socket. + socket_should_be_closed_when_request_is_done_ = false; - return read_and_close_socket(sock, req, res); + auto is_alive = false; + if (socket_.is_open()) { + is_alive = detail::select_write(socket_.sock, 0, 0) > 0; + if (!is_alive) { + // Attempt to avoid sigpipe by shutting down nongracefully if it seems + // like the other side has already closed the connection Also, there + // cannot be any requests in flight from other threads since we locked + // request_mutex_, so safe to close everything immediately + const bool shutdown_gracefully = false; + shutdown_ssl(socket_, shutdown_gracefully); + shutdown_socket(socket_); + close_socket(socket_); + } + } + + if (!is_alive) { + if (!create_and_connect_socket(socket_, error)) { return false; } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + // TODO: refactoring + if (is_ssl()) { + auto &scli = static_cast(*this); + if (!proxy_host_.empty() && proxy_port_ != -1) { + bool success = false; + if (!scli.connect_with_proxy(socket_, res, success, error)) { + return success; + } + } + + if (!scli.initialize_ssl(socket_, error)) { return false; } + } +#endif + } + + // Mark the current socket as being in use so that it cannot be closed by + // anyone else while this request is ongoing, even though we will be + // releasing the mutex. + if (socket_requests_in_flight_ > 1) { + assert(socket_requests_are_from_thread_ == std::this_thread::get_id()); + } + socket_requests_in_flight_ += 1; + socket_requests_are_from_thread_ = std::this_thread::get_id(); + } + + for (const auto &header : default_headers_) { + if (req.headers.find(header.first) == req.headers.end()) { + req.headers.insert(header); + } + } + + auto close_connection = !keep_alive_; + auto ret = process_socket(socket_, [&](Stream &strm) { + return handle_request(strm, req, res, close_connection, error); + }); + + // Briefly lock mutex in order to mark that a request is no longer ongoing + { + std::lock_guard guard(socket_mutex_); + socket_requests_in_flight_ -= 1; + if (socket_requests_in_flight_ <= 0) { + assert(socket_requests_in_flight_ == 0); + socket_requests_are_from_thread_ = std::thread::id(); + } + + if (socket_should_be_closed_when_request_is_done_ || close_connection || + !ret) { + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); + } + } + + if (!ret) { + if (error == Error::Success) { error = Error::Unknown; } + } + + return ret; } - inline void Client::write_request(Stream &strm, Request &req) { - BufferStream bstrm; + inline Result ClientImpl::send(const Request &req) { + auto req2 = req; + return send_(std::move(req2)); + } - // Request line - auto path = detail::encode_url(req.path); + inline Result ClientImpl::send_(Request &&req) { + auto res = detail::make_unique(); + auto error = Error::Success; + auto ret = send(req, *res, error); + return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers)}; + } - bstrm.write_format("%s %s HTTP/1.1\r\n", req.method.c_str(), path.c_str()); + inline bool ClientImpl::handle_request(Stream &strm, Request &req, + Response &res, bool close_connection, + Error &error) { + if (req.path.empty()) { + error = Error::Connection; + return false; + } + + auto req_save = req; + + bool ret; + + if (!is_ssl() && !proxy_host_.empty() && proxy_port_ != -1) { + auto req2 = req; + req2.path = "http://" + host_and_port_ + req.path; + ret = process_request(strm, req2, res, close_connection, error); + req = req2; + req.path = req_save.path; + } else { + ret = process_request(strm, req, res, close_connection, error); + } + + if (!ret) { return false; } + + if (300 < res.status && res.status < 400 && follow_location_) { + req = req_save; + ret = redirect(req, res, error); + } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if ((res.status == 401 || res.status == 407) && + req.authorization_count_ < 5) { + auto is_proxy = res.status == 407; + const auto &username = + is_proxy ? proxy_digest_auth_username_ : digest_auth_username_; + const auto &password = + is_proxy ? proxy_digest_auth_password_ : digest_auth_password_; + + if (!username.empty() && !password.empty()) { + std::map auth; + if (detail::parse_www_authenticate(res, auth, is_proxy)) { + Request new_req = req; + new_req.authorization_count_ += 1; + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + new_req.headers.erase(key); + new_req.headers.insert(detail::make_digest_authentication_header( + req, auth, new_req.authorization_count_, detail::random_string(10), + username, password, is_proxy)); + + Response new_res; + + ret = send(new_req, new_res, error); + if (ret) { res = new_res; } + } + } + } +#endif + + return ret; + } + + inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) { + if (req.redirect_count_ == 0) { + error = Error::ExceedRedirectCount; + return false; + } + + auto location = detail::decode_url(res.get_header_value("location"), true); + if (location.empty()) { return false; } + + const static std::regex re( + R"(^(?:(https?):)?(?://([^:/?#]*)(?::(\d+))?)?([^?#]*(?:\?[^#]*)?)(?:#.*)?)"); + + std::smatch m; + if (!std::regex_match(location, m, re)) { return false; } + + auto scheme = is_ssl() ? "https" : "http"; + + auto next_scheme = m[1].str(); + auto next_host = m[2].str(); + auto port_str = m[3].str(); + auto next_path = m[4].str(); + + auto next_port = port_; + if (!port_str.empty()) { + next_port = std::stoi(port_str); + } else if (!next_scheme.empty()) { + next_port = next_scheme == "https" ? 443 : 80; + } + + if (next_scheme.empty()) { next_scheme = scheme; } + if (next_host.empty()) { next_host = host_; } + if (next_path.empty()) { next_path = "/"; } + + if (next_scheme == scheme && next_host == host_ && next_port == port_) { + return detail::redirect(*this, req, res, next_path, location, error); + } else { + if (next_scheme == "https") { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + SSLClient cli(next_host.c_str(), next_port); + cli.copy_settings(*this); + return detail::redirect(cli, req, res, next_path, location, error); +#else + return false; +#endif + } else { + ClientImpl cli(next_host.c_str(), next_port); + cli.copy_settings(*this); + return detail::redirect(cli, req, res, next_path, location, error); + } + } + } + + inline bool ClientImpl::write_content_with_provider(Stream &strm, + const Request &req, + Error &error) { + auto is_shutting_down = []() { return false; }; + + if (req.is_chunked_content_provider_) { + // TODO: Brotli suport + std::unique_ptr compressor; +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_) { + compressor = detail::make_unique(); + } else +#endif + { + compressor = detail::make_unique(); + } + + return detail::write_content_chunked(strm, req.content_provider_, + is_shutting_down, *compressor, error); + } else { + return detail::write_content(strm, req.content_provider_, 0, + req.content_length_, is_shutting_down, error); + } + } // namespace httplib + + inline bool ClientImpl::write_request(Stream &strm, Request &req, + bool close_connection, Error &error) { + // Prepare additional headers + if (close_connection) { req.headers.emplace("Connection", "close"); } - // Headers if (!req.has_header("Host")) { if (is_ssl()) { if (port_ == 443) { - req.set_header("Host", host_.c_str()); + req.headers.emplace("Host", host_); } else { - req.set_header("Host", host_and_port_.c_str()); + req.headers.emplace("Host", host_and_port_); } } else { if (port_ == 80) { - req.set_header("Host", host_.c_str()); + req.headers.emplace("Host", host_); } else { - req.set_header("Host", host_and_port_.c_str()); + req.headers.emplace("Host", host_and_port_); } } } - if (!req.has_header("Accept")) { req.set_header("Accept", "*/*"); } + if (!req.has_header("Accept")) { req.headers.emplace("Accept", "*/*"); } if (!req.has_header("User-Agent")) { - req.set_header("User-Agent", "cpp-httplib/0.2"); + req.headers.emplace("User-Agent", "cpp-httplib/0.7"); } - // TODO: Support KeepAlive connection - // if (!req.has_header("Connection")) { - req.set_header("Connection", "close"); - // } - if (req.body.empty()) { - if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH") { - req.set_header("Content-Length", "0"); + if (req.content_provider_) { + if (!req.is_chunked_content_provider_) { + auto length = std::to_string(req.content_length_); + req.headers.emplace("Content-Length", length); + } + } else { + if (req.method == "POST" || req.method == "PUT" || + req.method == "PATCH") { + req.headers.emplace("Content-Length", "0"); + } } } else { if (!req.has_header("Content-Type")) { - req.set_header("Content-Type", "text/plain"); + req.headers.emplace("Content-Type", "text/plain"); } if (!req.has_header("Content-Length")) { auto length = std::to_string(req.body.size()); - req.set_header("Content-Length", length.c_str()); + req.headers.emplace("Content-Length", length); } } - detail::write_headers(bstrm, req); - - // Body - if (!req.body.empty()) { bstrm.write(req.body.c_str(), req.body.size()); } - - // Flush buffer - auto &data = bstrm.get_buffer(); - strm.write(data.data(), data.size()); - } - - inline bool Client::process_request(Stream &strm, Request &req, Response &res, - bool &connection_close) { - // Send request - write_request(strm, req); - - // Receive response and headers - if (!read_response_line(strm, res) || - !detail::read_headers(strm, res.headers)) { - return false; + if (!basic_auth_password_.empty() || !basic_auth_username_.empty()) { + req.headers.insert(make_basic_authentication_header( + basic_auth_username_, basic_auth_password_, false)); } - if (res.get_header_value("Connection") == "close" || - res.version == "HTTP/1.0") { - connection_close = true; + if (!proxy_basic_auth_username_.empty() && + !proxy_basic_auth_password_.empty()) { + req.headers.insert(make_basic_authentication_header( + proxy_basic_auth_username_, proxy_basic_auth_password_, true)); + } + + if (!bearer_token_auth_token_.empty()) { + req.headers.insert(make_bearer_token_authentication_header( + bearer_token_auth_token_, false)); + } + + if (!proxy_bearer_token_auth_token_.empty()) { + req.headers.insert(make_bearer_token_authentication_header( + proxy_bearer_token_auth_token_, true)); + } + + // Request line and headers + { + detail::BufferStream bstrm; + + const auto &path = url_encode_ ? detail::encode_url(req.path) : req.path; + bstrm.write_format("%s %s HTTP/1.1\r\n", req.method.c_str(), path.c_str()); + + detail::write_headers(bstrm, req.headers); + + // Flush buffer + auto &data = bstrm.get_buffer(); + if (!detail::write_data(strm, data.data(), data.size())) { + error = Error::Write; + return false; + } } // Body - if (req.method != "HEAD") { - bool exceed_payload_max_length = false; - if (!detail::read_content(strm, res, std::numeric_limits::max(), - exceed_payload_max_length, req.progress)) { - return false; - } - - if (res.get_header_value("Content-Encoding") == "gzip") { -#ifdef CPPHTTPLIB_ZLIB_SUPPORT - detail::decompress(res.body); -#else - return false; -#endif - } + if (req.body.empty()) { + return write_content_with_provider(strm, req, error); + } else { + return detail::write_data(strm, req.body.data(), req.body.size()); } return true; } - inline bool Client::read_and_close_socket(socket_t sock, Request &req, - Response &res) { - return detail::read_and_close_socket( - sock, 0, - [&](Stream &strm, bool /*last_connection*/, bool &connection_close) { - return process_request(strm, req, res, connection_close); - }); + inline std::unique_ptr ClientImpl::send_with_content_provider( + Request &req, + // const char *method, const char *path, const Headers &headers, + const char *body, size_t content_length, ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const char *content_type, Error &error) { + + // Request req; + // req.method = method; + // req.headers = headers; + // req.path = path; + + if (content_type) { req.headers.emplace("Content-Type", content_type); } + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_) { req.headers.emplace("Content-Encoding", "gzip"); } +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_ && !content_provider_without_length) { + // TODO: Brotli support + detail::gzip_compressor compressor; + + if (content_provider) { + auto ok = true; + size_t offset = 0; + DataSink data_sink; + + data_sink.write = [&](const char *data, size_t data_len) -> bool { + if (ok) { + auto last = offset + data_len == content_length; + + auto ret = compressor.compress( + data, data_len, last, [&](const char *data, size_t data_len) { + req.body.append(data, data_len); + return true; + }); + + if (ret) { + offset += data_len; + } else { + ok = false; + } + } + return ok; + }; + + data_sink.is_writable = [&](void) { return ok && true; }; + + while (ok && offset < content_length) { + if (!content_provider(offset, content_length - offset, data_sink)) { + error = Error::Canceled; + return nullptr; + } + } + } else { + if (!compressor.compress(body, content_length, true, + [&](const char *data, size_t data_len) { + req.body.append(data, data_len); + return true; + })) { + error = Error::Compression; + return nullptr; + } + } + } else +#endif + { + if (content_provider) { + req.content_length_ = content_length; + req.content_provider_ = std::move(content_provider); + req.is_chunked_content_provider_ = false; + } else if (content_provider_without_length) { + req.content_length_ = 0; + req.content_provider_ = detail::ContentProviderAdapter( + std::move(content_provider_without_length)); + req.is_chunked_content_provider_ = true; + req.headers.emplace("Transfer-Encoding", "chunked"); + } else { + req.body.assign(body, content_length); + ; + } + } + + auto res = detail::make_unique(); + return send(req, *res, error) ? std::move(res) : nullptr; } - inline bool Client::is_ssl() const { return false; } + inline Result ClientImpl::send_with_content_provider( + const char *method, const char *path, const Headers &headers, + const char *body, size_t content_length, ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const char *content_type) { + Request req; + req.method = method; + req.headers = headers; + req.path = path; - inline std::shared_ptr Client::Get(const char *path, - Progress progress) { - return Get(path, Headers(), progress); + auto error = Error::Success; + + auto res = send_with_content_provider( + req, + // method, path, headers, + body, content_length, std::move(content_provider), + std::move(content_provider_without_length), content_type, error); + + return Result{std::move(res), error, std::move(req.headers)}; } - inline std::shared_ptr - Client::Get(const char *path, const Headers &headers, Progress progress) { + inline bool ClientImpl::process_request(Stream &strm, Request &req, + Response &res, bool close_connection, + Error &error) { + // Send request + if (!write_request(strm, req, close_connection, error)) { return false; } + + // Receive response and headers + if (!read_response_line(strm, req, res) || + !detail::read_headers(strm, res.headers)) { + error = Error::Read; + return false; + } + + // Body + if ((res.status != 204) && req.method != "HEAD" && req.method != "CONNECT") { + auto redirect = 300 < res.status && res.status < 400 && follow_location_; + + if (req.response_handler && !redirect) { + if (!req.response_handler(res)) { + error = Error::Canceled; + return false; + } + } + + auto out = + req.content_receiver + ? static_cast( + [&](const char *buf, size_t n, uint64_t off, uint64_t len) { + if (redirect) { return true; } + auto ret = req.content_receiver(buf, n, off, len); + if (!ret) { error = Error::Canceled; } + return ret; + }) + : static_cast( + [&](const char *buf, size_t n, uint64_t /*off*/, + uint64_t /*len*/) { + if (res.body.size() + n > res.body.max_size()) { + return false; + } + res.body.append(buf, n); + return true; + }); + + auto progress = [&](uint64_t current, uint64_t total) { + if (!req.progress || redirect) { return true; } + auto ret = req.progress(current, total); + if (!ret) { error = Error::Canceled; } + return ret; + }; + + int dummy_status; + if (!detail::read_content(strm, res, (std::numeric_limits::max)(), + dummy_status, std::move(progress), std::move(out), + decompress_)) { + if (error != Error::Canceled) { error = Error::Read; } + return false; + } + } + + if (res.get_header_value("Connection") == "close" || + (res.version == "HTTP/1.0" && res.reason != "Connection established")) { + // TODO this requires a not-entirely-obvious chain of calls to be correct + // for this to be safe. Maybe a code refactor (such as moving this out to + // the send function and getting rid of the recursiveness of the mutex) + // could make this more obvious. + + // This is safe to call because process_request is only called by + // handle_request which is only called by send, which locks the request + // mutex during the process. It would be a bug to call it from a different + // thread since it's a thread-safety issue to do these things to the socket + // if another thread is using the socket. + std::lock_guard guard(socket_mutex_); + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); + } + + // Log + if (logger_) { logger_(req, res); } + + return true; + } + + inline bool + ClientImpl::process_socket(const Socket &socket, + std::function callback) { + return detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, std::move(callback)); + } + + inline bool ClientImpl::is_ssl() const { return false; } + + inline Result ClientImpl::Get(const char *path) { + return Get(path, Headers(), Progress()); + } + + inline Result ClientImpl::Get(const char *path, Progress progress) { + return Get(path, Headers(), std::move(progress)); + } + + inline Result ClientImpl::Get(const char *path, const Headers &headers) { + return Get(path, headers, Progress()); + } + + inline Result ClientImpl::Get(const char *path, const Headers &headers, + Progress progress) { Request req; req.method = "GET"; req.path = path; req.headers = headers; - req.progress = progress; + req.progress = std::move(progress); - auto res = std::make_shared(); - - return send(req, *res) ? res : nullptr; + return send_(std::move(req)); } - inline std::shared_ptr Client::Head(const char *path) { + inline Result ClientImpl::Get(const char *path, + ContentReceiver content_receiver) { + return Get(path, Headers(), nullptr, std::move(content_receiver), nullptr); + } + + inline Result ClientImpl::Get(const char *path, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, Headers(), nullptr, std::move(content_receiver), + std::move(progress)); + } + + inline Result ClientImpl::Get(const char *path, const Headers &headers, + ContentReceiver content_receiver) { + return Get(path, headers, nullptr, std::move(content_receiver), nullptr); + } + + inline Result ClientImpl::Get(const char *path, const Headers &headers, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, headers, nullptr, std::move(content_receiver), + std::move(progress)); + } + + inline Result ClientImpl::Get(const char *path, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return Get(path, Headers(), std::move(response_handler), + std::move(content_receiver), nullptr); + } + + inline Result ClientImpl::Get(const char *path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return Get(path, headers, std::move(response_handler), + std::move(content_receiver), nullptr); + } + + inline Result ClientImpl::Get(const char *path, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, Headers(), std::move(response_handler), + std::move(content_receiver), std::move(progress)); + } + + inline Result ClientImpl::Get(const char *path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + Request req; + req.method = "GET"; + req.path = path; + req.headers = headers; + req.response_handler = std::move(response_handler); + req.content_receiver = + [content_receiver](const char *data, size_t data_length, + uint64_t /*offset*/, uint64_t /*total_length*/) { + return content_receiver(data, data_length); + }; + req.progress = std::move(progress); + + return send_(std::move(req)); + } + + inline Result ClientImpl::Get(const char *path, const Params ¶ms, + const Headers &headers, Progress progress) { + if (params.empty()) { return Get(path, headers); } + + std::string path_with_query = detail::append_query_params(path, params); + return Get(path_with_query.c_str(), headers, progress); + } + + inline Result ClientImpl::Get(const char *path, const Params ¶ms, + const Headers &headers, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, params, headers, nullptr, content_receiver, progress); + } + + inline Result ClientImpl::Get(const char *path, const Params ¶ms, + const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + if (params.empty()) { + return Get(path, headers, response_handler, content_receiver, progress); + } + + std::string path_with_query = detail::append_query_params(path, params); + return Get(path_with_query.c_str(), headers, response_handler, + content_receiver, progress); + } + + inline Result ClientImpl::Head(const char *path) { return Head(path, Headers()); } - inline std::shared_ptr Client::Head(const char *path, - const Headers &headers) { + inline Result ClientImpl::Head(const char *path, const Headers &headers) { Request req; req.method = "HEAD"; req.headers = headers; req.path = path; - auto res = std::make_shared(); - - return send(req, *res) ? res : nullptr; + return send_(std::move(req)); } - inline std::shared_ptr Client::Post(const char *path, - const std::string &body, - const char *content_type) { + inline Result ClientImpl::Post(const char *path) { + return Post(path, std::string(), nullptr); + } + + inline Result ClientImpl::Post(const char *path, const char *body, + size_t content_length, + const char *content_type) { + return Post(path, Headers(), body, content_length, content_type); + } + + inline Result ClientImpl::Post(const char *path, const Headers &headers, + const char *body, size_t content_length, + const char *content_type) { + return send_with_content_provider("POST", path, headers, body, content_length, + nullptr, nullptr, content_type); + } + + inline Result ClientImpl::Post(const char *path, const std::string &body, + const char *content_type) { return Post(path, Headers(), body, content_type); } - inline std::shared_ptr Client::Post(const char *path, - const Headers &headers, - const std::string &body, - const char *content_type) { - Request req; - req.method = "POST"; - req.headers = headers; - req.path = path; - - req.headers.emplace("Content-Type", content_type); - req.body = body; - - auto res = std::make_shared(); - - return send(req, *res) ? res : nullptr; + inline Result ClientImpl::Post(const char *path, const Headers &headers, + const std::string &body, + const char *content_type) { + return send_with_content_provider("POST", path, headers, body.data(), + body.size(), nullptr, nullptr, + content_type); } - inline std::shared_ptr Client::Post(const char *path, - const Params ¶ms) { + inline Result ClientImpl::Post(const char *path, const Params ¶ms) { return Post(path, Headers(), params); } - inline std::shared_ptr - Client::Post(const char *path, const Headers &headers, const Params ¶ms) { - std::string query; - for (auto it = params.begin(); it != params.end(); ++it) { - if (it != params.begin()) { query += "&"; } - query += it->first; - query += "="; - query += detail::encode_url(it->second); - } + inline Result ClientImpl::Post(const char *path, size_t content_length, + ContentProvider content_provider, + const char *content_type) { + return Post(path, Headers(), content_length, std::move(content_provider), + content_type); + } + inline Result ClientImpl::Post(const char *path, + ContentProviderWithoutLength content_provider, + const char *content_type) { + return Post(path, Headers(), std::move(content_provider), content_type); + } + + inline Result ClientImpl::Post(const char *path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const char *content_type) { + return send_with_content_provider("POST", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type); + } + + inline Result ClientImpl::Post(const char *path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const char *content_type) { + return send_with_content_provider("POST", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type); + } + + inline Result ClientImpl::Post(const char *path, const Headers &headers, + const Params ¶ms) { + auto query = detail::params_to_query_str(params); return Post(path, headers, query, "application/x-www-form-urlencoded"); } - inline std::shared_ptr Client::Put(const char *path, - const std::string &body, - const char *content_type) { + inline Result ClientImpl::Post(const char *path, + const MultipartFormDataItems &items) { + return Post(path, Headers(), items); + } + + inline Result ClientImpl::Post(const char *path, const Headers &headers, + const MultipartFormDataItems &items) { + return Post(path, headers, items, detail::make_multipart_data_boundary()); + } + inline Result ClientImpl::Post(const char *path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + for (size_t i = 0; i < boundary.size(); i++) { + char c = boundary[i]; + if (!std::isalnum(c) && c != '-' && c != '_') { + return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; + } + } + + std::string body; + + for (const auto &item : items) { + body += "--" + boundary + "\r\n"; + body += "Content-Disposition: form-data; name=\"" + item.name + "\""; + if (!item.filename.empty()) { + body += "; filename=\"" + item.filename + "\""; + } + body += "\r\n"; + if (!item.content_type.empty()) { + body += "Content-Type: " + item.content_type + "\r\n"; + } + body += "\r\n"; + body += item.content + "\r\n"; + } + + body += "--" + boundary + "--\r\n"; + + std::string content_type = "multipart/form-data; boundary=" + boundary; + return Post(path, headers, body, content_type.c_str()); + } + + inline Result ClientImpl::Put(const char *path) { + return Put(path, std::string(), nullptr); + } + + inline Result ClientImpl::Put(const char *path, const char *body, + size_t content_length, const char *content_type) { + return Put(path, Headers(), body, content_length, content_type); + } + + inline Result ClientImpl::Put(const char *path, const Headers &headers, + const char *body, size_t content_length, + const char *content_type) { + return send_with_content_provider("PUT", path, headers, body, content_length, + nullptr, nullptr, content_type); + } + + inline Result ClientImpl::Put(const char *path, const std::string &body, + const char *content_type) { return Put(path, Headers(), body, content_type); } - inline std::shared_ptr Client::Put(const char *path, - const Headers &headers, - const std::string &body, - const char *content_type) { - Request req; - req.method = "PUT"; - req.headers = headers; - req.path = path; - - req.headers.emplace("Content-Type", content_type); - req.body = body; - - auto res = std::make_shared(); - - return send(req, *res) ? res : nullptr; + inline Result ClientImpl::Put(const char *path, const Headers &headers, + const std::string &body, + const char *content_type) { + return send_with_content_provider("PUT", path, headers, body.data(), + body.size(), nullptr, nullptr, + content_type); } - inline std::shared_ptr Client::Patch(const char *path, - const std::string &body, - const char *content_type) { + inline Result ClientImpl::Put(const char *path, size_t content_length, + ContentProvider content_provider, + const char *content_type) { + return Put(path, Headers(), content_length, std::move(content_provider), + content_type); + } + + inline Result ClientImpl::Put(const char *path, + ContentProviderWithoutLength content_provider, + const char *content_type) { + return Put(path, Headers(), std::move(content_provider), content_type); + } + + inline Result ClientImpl::Put(const char *path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const char *content_type) { + return send_with_content_provider("PUT", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type); + } + + inline Result ClientImpl::Put(const char *path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const char *content_type) { + return send_with_content_provider("PUT", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type); + } + + inline Result ClientImpl::Put(const char *path, const Params ¶ms) { + return Put(path, Headers(), params); + } + + inline Result ClientImpl::Put(const char *path, const Headers &headers, + const Params ¶ms) { + auto query = detail::params_to_query_str(params); + return Put(path, headers, query, "application/x-www-form-urlencoded"); + } + + inline Result ClientImpl::Patch(const char *path) { + return Patch(path, std::string(), nullptr); + } + + inline Result ClientImpl::Patch(const char *path, const char *body, + size_t content_length, + const char *content_type) { + return Patch(path, Headers(), body, content_length, content_type); + } + + inline Result ClientImpl::Patch(const char *path, const Headers &headers, + const char *body, size_t content_length, + const char *content_type) { + return send_with_content_provider("PATCH", path, headers, body, + content_length, nullptr, nullptr, + content_type); + } + + inline Result ClientImpl::Patch(const char *path, const std::string &body, + const char *content_type) { return Patch(path, Headers(), body, content_type); } - inline std::shared_ptr Client::Patch(const char *path, - const Headers &headers, - const std::string &body, - const char *content_type) { - Request req; - req.method = "PATCH"; - req.headers = headers; - req.path = path; - - req.headers.emplace("Content-Type", content_type); - req.body = body; - - auto res = std::make_shared(); - - return send(req, *res) ? res : nullptr; + inline Result ClientImpl::Patch(const char *path, const Headers &headers, + const std::string &body, + const char *content_type) { + return send_with_content_provider("PATCH", path, headers, body.data(), + body.size(), nullptr, nullptr, + content_type); } - inline std::shared_ptr Client::Delete(const char *path, - const std::string &body, - const char *content_type) { - return Delete(path, Headers(), body, content_type); + inline Result ClientImpl::Patch(const char *path, size_t content_length, + ContentProvider content_provider, + const char *content_type) { + return Patch(path, Headers(), content_length, std::move(content_provider), + content_type); } - inline std::shared_ptr Client::Delete(const char *path, - const Headers &headers, - const std::string &body, - const char *content_type) { + inline Result ClientImpl::Patch(const char *path, + ContentProviderWithoutLength content_provider, + const char *content_type) { + return Patch(path, Headers(), std::move(content_provider), content_type); + } + + inline Result ClientImpl::Patch(const char *path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const char *content_type) { + return send_with_content_provider("PATCH", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type); + } + + inline Result ClientImpl::Patch(const char *path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const char *content_type) { + return send_with_content_provider("PATCH", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type); + } + + inline Result ClientImpl::Delete(const char *path) { + return Delete(path, Headers(), std::string(), nullptr); + } + + inline Result ClientImpl::Delete(const char *path, const Headers &headers) { + return Delete(path, headers, std::string(), nullptr); + } + + inline Result ClientImpl::Delete(const char *path, const char *body, + size_t content_length, + const char *content_type) { + return Delete(path, Headers(), body, content_length, content_type); + } + + inline Result ClientImpl::Delete(const char *path, const Headers &headers, + const char *body, size_t content_length, + const char *content_type) { Request req; req.method = "DELETE"; req.headers = headers; req.path = path; if (content_type) { req.headers.emplace("Content-Type", content_type); } - req.body = body; + req.body.assign(body, content_length); - auto res = std::make_shared(); - - return send(req, *res) ? res : nullptr; + return send_(std::move(req)); } - inline std::shared_ptr Client::Options(const char *path) { + inline Result ClientImpl::Delete(const char *path, const std::string &body, + const char *content_type) { + return Delete(path, Headers(), body.data(), body.size(), content_type); + } + + inline Result ClientImpl::Delete(const char *path, const Headers &headers, + const std::string &body, + const char *content_type) { + return Delete(path, headers, body.data(), body.size(), content_type); + } + + inline Result ClientImpl::Options(const char *path) { return Options(path, Headers()); } - inline std::shared_ptr Client::Options(const char *path, - const Headers &headers) { + inline Result ClientImpl::Options(const char *path, const Headers &headers) { Request req; req.method = "OPTIONS"; - req.path = path; req.headers = headers; + req.path = path; - auto res = std::make_shared(); + return send_(std::move(req)); + } - return send(req, *res) ? res : nullptr; + inline size_t ClientImpl::is_socket_open() const { + std::lock_guard guard(socket_mutex_); + return socket_.is_open(); + } + + inline void ClientImpl::stop() { + std::lock_guard guard(socket_mutex_); + + // If there is anything ongoing right now, the ONLY thread-safe thing we can + // do is to shutdown_socket, so that threads using this socket suddenly + // discover they can't read/write any more and error out. Everything else + // (closing the socket, shutting ssl down) is unsafe because these actions are + // not thread-safe. + if (socket_requests_in_flight_ > 0) { + shutdown_socket(socket_); + + // Aside from that, we set a flag for the socket to be closed when we're + // done. + socket_should_be_closed_when_request_is_done_ = true; + return; + } + + // Otherwise, sitll holding the mutex, we can shut everything down ourselves + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); + } + + inline void ClientImpl::set_connection_timeout(time_t sec, time_t usec) { + connection_timeout_sec_ = sec; + connection_timeout_usec_ = usec; + } + + template + inline void ClientImpl::set_connection_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) { + set_connection_timeout(sec, usec); + }); + } + + inline void ClientImpl::set_read_timeout(time_t sec, time_t usec) { + read_timeout_sec_ = sec; + read_timeout_usec_ = usec; + } + + template + inline void ClientImpl::set_read_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); + } + + inline void ClientImpl::set_write_timeout(time_t sec, time_t usec) { + write_timeout_sec_ = sec; + write_timeout_usec_ = usec; + } + + template + inline void ClientImpl::set_write_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); + } + + inline void ClientImpl::set_basic_auth(const char *username, + const char *password) { + basic_auth_username_ = username; + basic_auth_password_ = password; + } + + inline void ClientImpl::set_bearer_token_auth(const char *token) { + bearer_token_auth_token_ = token; + } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + inline void ClientImpl::set_digest_auth(const char *username, + const char *password) { + digest_auth_username_ = username; + digest_auth_password_ = password; +} +#endif + + inline void ClientImpl::set_keep_alive(bool on) { keep_alive_ = on; } + + inline void ClientImpl::set_follow_location(bool on) { follow_location_ = on; } + + inline void ClientImpl::set_url_encode(bool on) { url_encode_ = on; } + + inline void ClientImpl::set_default_headers(Headers headers) { + default_headers_ = std::move(headers); + } + + inline void ClientImpl::set_address_family(int family) { + address_family_ = family; + } + + inline void ClientImpl::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; } + + inline void ClientImpl::set_socket_options(SocketOptions socket_options) { + socket_options_ = std::move(socket_options); + } + + inline void ClientImpl::set_compress(bool on) { compress_ = on; } + + inline void ClientImpl::set_decompress(bool on) { decompress_ = on; } + + inline void ClientImpl::set_interface(const char *intf) { interface_ = intf; } + + inline void ClientImpl::set_proxy(const char *host, int port) { + proxy_host_ = host; + proxy_port_ = port; + } + + inline void ClientImpl::set_proxy_basic_auth(const char *username, + const char *password) { + proxy_basic_auth_username_ = username; + proxy_basic_auth_password_ = password; + } + + inline void ClientImpl::set_proxy_bearer_token_auth(const char *token) { + proxy_bearer_token_auth_token_ = token; + } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + inline void ClientImpl::set_proxy_digest_auth(const char *username, + const char *password) { + proxy_digest_auth_username_ = username; + proxy_digest_auth_password_ = password; +} +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + inline void ClientImpl::enable_server_certificate_verification(bool enabled) { + server_certificate_verification_ = enabled; +} +#endif + + inline void ClientImpl::set_logger(Logger logger) { + logger_ = std::move(logger); } /* @@ -2309,119 +6514,222 @@ static WSInit wsinit_; #ifdef CPPHTTPLIB_OPENSSL_SUPPORT namespace detail { -template -inline bool -read_and_close_socket_ssl(socket_t sock, size_t keep_alive_max_count, - // TODO: OpenSSL 1.0.2 occasionally crashes... - // The upcoming 1.1.0 is going to be thread safe. - SSL_CTX *ctx, std::mutex &ctx_mutex, - U SSL_connect_or_accept, V setup, T callback) { +template +inline SSL *ssl_new(socket_t sock, SSL_CTX *ctx, std::mutex &ctx_mutex, + U SSL_connect_or_accept, V setup) { SSL *ssl = nullptr; { std::lock_guard guard(ctx_mutex); ssl = SSL_new(ctx); } - if (!ssl) { - close_socket(sock); - return false; - } + if (ssl) { + set_nonblocking(sock, true); + auto bio = BIO_new_socket(static_cast(sock), BIO_NOCLOSE); + BIO_set_nbio(bio, 1); + SSL_set_bio(ssl, bio, bio); - auto bio = BIO_new_socket(sock, BIO_NOCLOSE); - SSL_set_bio(ssl, bio, bio); - - if (!setup(ssl)) { - SSL_shutdown(ssl); - { - std::lock_guard guard(ctx_mutex); - SSL_free(ssl); - } - - close_socket(sock); - return false; - } - - bool ret = false; - - if (SSL_connect_or_accept(ssl) == 1) { - if (keep_alive_max_count > 0) { - auto count = keep_alive_max_count; - while (count > 0 && - detail::select_read(sock, CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND, - CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND) > 0) { - SSLSocketStream strm(sock, ssl); - auto last_connection = count == 1; - auto connection_close = false; - - ret = callback(ssl, strm, last_connection, connection_close); - if (!ret || connection_close) { break; } - - count--; + if (!setup(ssl) || SSL_connect_or_accept(ssl) != 1) { + SSL_shutdown(ssl); + { + std::lock_guard guard(ctx_mutex); + SSL_free(ssl); } + set_nonblocking(sock, false); + return nullptr; + } + BIO_set_nbio(bio, 0); + set_nonblocking(sock, false); + } + + return ssl; +} + +inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl, + bool shutdown_gracefully) { + // sometimes we may want to skip this to try to avoid SIGPIPE if we know + // the remote has closed the network connection + // Note that it is not always possible to avoid SIGPIPE, this is merely a + // best-efforts. + if (shutdown_gracefully) { SSL_shutdown(ssl); } + + std::lock_guard guard(ctx_mutex); + SSL_free(ssl); +} + +template +bool ssl_connect_or_accept_nonblocking(socket_t sock, SSL *ssl, + U ssl_connect_or_accept, + time_t timeout_sec, + time_t timeout_usec) { + int res = 0; + while ((res = ssl_connect_or_accept(ssl)) != 1) { + auto err = SSL_get_error(ssl, res); + switch (err) { + case SSL_ERROR_WANT_READ: + if (select_read(sock, timeout_sec, timeout_usec) > 0) { continue; } + break; + case SSL_ERROR_WANT_WRITE: + if (select_write(sock, timeout_sec, timeout_usec) > 0) { continue; } + break; + default: break; + } + return false; + } + return true; +} + +template +inline bool +process_server_socket_ssl(SSL *ssl, socket_t sock, size_t keep_alive_max_count, + time_t keep_alive_timeout_sec, + time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec, + T callback) { + return process_server_socket_core( + sock, keep_alive_max_count, keep_alive_timeout_sec, + [&](bool close_connection, bool &connection_closed) { + SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm, close_connection, connection_closed); + }); +} + +template +inline bool +process_client_socket_ssl(SSL *ssl, socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm); +} + +#if OPENSSL_VERSION_NUMBER < 0x10100000L +static std::shared_ptr> openSSL_locks_; + +class SSLThreadLocks { +public: + SSLThreadLocks() { + openSSL_locks_ = + std::make_shared>(CRYPTO_num_locks()); + CRYPTO_set_locking_callback(locking_callback); + } + + ~SSLThreadLocks() { CRYPTO_set_locking_callback(nullptr); } + +private: + static void locking_callback(int mode, int type, const char * /*file*/, + int /*line*/) { + auto &lk = (*openSSL_locks_)[static_cast(type)]; + if (mode & CRYPTO_LOCK) { + lk.lock(); } else { - SSLSocketStream strm(sock, ssl); - auto dummy_connection_close = false; - ret = callback(ssl, strm, true, dummy_connection_close); + lk.unlock(); } } +}; - SSL_shutdown(ssl); - { - std::lock_guard guard(ctx_mutex); - SSL_free(ssl); - } - - close_socket(sock); - - return ret; -} +#endif class SSLInit { public: SSLInit() { +#if OPENSSL_VERSION_NUMBER < 0x1010001fL SSL_load_error_strings(); SSL_library_init(); +#else + OPENSSL_init_ssl( + OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL); +#endif } - ~SSLInit() { ERR_free_strings(); } + ~SSLInit() { +#if OPENSSL_VERSION_NUMBER < 0x1010001fL + ERR_free_strings(); +#endif + } + +private: +#if OPENSSL_VERSION_NUMBER < 0x10100000L + SSLThreadLocks thread_init_; +#endif }; +// SSL socket stream implementation +inline SSLSocketStream::SSLSocketStream(socket_t sock, SSL *ssl, + time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec) + : sock_(sock), ssl_(ssl), read_timeout_sec_(read_timeout_sec), + read_timeout_usec_(read_timeout_usec), + write_timeout_sec_(write_timeout_sec), + write_timeout_usec_(write_timeout_usec) { + SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY); +} + +inline SSLSocketStream::~SSLSocketStream() {} + +inline bool SSLSocketStream::is_readable() const { + return detail::select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; +} + +inline bool SSLSocketStream::is_writable() const { + return detail::select_write(sock_, write_timeout_sec_, write_timeout_usec_) > + 0; +} + +inline ssize_t SSLSocketStream::read(char *ptr, size_t size) { + if (SSL_pending(ssl_) > 0) { + return SSL_read(ssl_, ptr, static_cast(size)); + } else if (is_readable()) { + auto ret = SSL_read(ssl_, ptr, static_cast(size)); + if (ret < 0) { + auto err = SSL_get_error(ssl_, ret); +#ifdef _WIN32 + while (err == SSL_ERROR_WANT_READ || + err == SSL_ERROR_SYSCALL && WSAGetLastError() == WSAETIMEDOUT) { +#else + while (err == SSL_ERROR_WANT_READ) { +#endif + if (SSL_pending(ssl_) > 0) { + return SSL_read(ssl_, ptr, static_cast(size)); + } else if (is_readable()) { + ret = SSL_read(ssl_, ptr, static_cast(size)); + if (ret >= 0) { return ret; } + err = SSL_get_error(ssl_, ret); + } else { + return -1; + } + } + } + return ret; + } + return -1; +} + +inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) { + if (is_writable()) { return SSL_write(ssl_, ptr, static_cast(size)); } + return -1; +} + +inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip, + int &port) const { + detail::get_remote_ip_and_port(sock_, ip, port); +} + +inline socket_t SSLSocketStream::socket() const { return sock_; } + static SSLInit sslinit_; } // namespace detail -// SSL socket stream implementation -inline SSLSocketStream::SSLSocketStream(socket_t sock, SSL *ssl) - : sock_(sock), ssl_(ssl) {} - -inline SSLSocketStream::~SSLSocketStream() {} - -inline int SSLSocketStream::read(char *ptr, size_t size) { - if (SSL_pending(ssl_) > 0 || - detail::select_read(sock_, CPPHTTPLIB_READ_TIMEOUT_SECOND, - CPPHTTPLIB_READ_TIMEOUT_USECOND) > 0) { - return SSL_read(ssl_, ptr, size); - } - return -1; -} - -inline int SSLSocketStream::write(const char *ptr, size_t size) { - return SSL_write(ssl_, ptr, size); -} - -inline int SSLSocketStream::write(const char *ptr) { - return write(ptr, strlen(ptr)); -} - -inline std::string SSLSocketStream::get_remote_addr() const { - return detail::get_remote_addr(sock_); -} - // SSL HTTP server implementation inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path, const char *client_ca_cert_file_path, const char *client_ca_cert_dir_path) { - ctx_ = SSL_CTX_new(SSLv23_server_method()); + ctx_ = SSL_CTX_new(TLS_method()); if (ctx_) { SSL_CTX_set_options(ctx_, @@ -2456,39 +6764,111 @@ inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path, } } +inline SSLServer::SSLServer(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store) { + ctx_ = SSL_CTX_new(SSLv23_server_method()); + + if (ctx_) { + SSL_CTX_set_options(ctx_, + SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + + if (SSL_CTX_use_certificate(ctx_, cert) != 1 || + SSL_CTX_use_PrivateKey(ctx_, private_key) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } else if (client_ca_cert_store) { + + SSL_CTX_set_cert_store(ctx_, client_ca_cert_store); + + SSL_CTX_set_verify( + ctx_, + SSL_VERIFY_PEER | + SSL_VERIFY_FAIL_IF_NO_PEER_CERT, // SSL_VERIFY_CLIENT_ONCE, + nullptr); + } + } +} + inline SSLServer::~SSLServer() { if (ctx_) { SSL_CTX_free(ctx_); } } inline bool SSLServer::is_valid() const { return ctx_; } -inline bool SSLServer::read_and_close_socket(socket_t sock) { - return detail::read_and_close_socket_ssl( - sock, keep_alive_max_count_, ctx_, ctx_mutex_, SSL_accept, - [](SSL * /*ssl*/) { return true; }, - [this](SSL *ssl, Stream &strm, bool last_connection, - bool &connection_close) { - return process_request(strm, last_connection, connection_close, - [&](Request &req) { req.ssl = ssl; }); - }); +inline bool SSLServer::process_and_close_socket(socket_t sock) { + auto ssl = detail::ssl_new( + sock, ctx_, ctx_mutex_, + [&](SSL *ssl) { + return detail::ssl_connect_or_accept_nonblocking( + sock, ssl, SSL_accept, read_timeout_sec_, read_timeout_usec_); + }, + [](SSL * /*ssl*/) { return true; }); + + bool ret = false; + if (ssl) { + ret = detail::process_server_socket_ssl( + ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, + [this, ssl](Stream &strm, bool close_connection, + bool &connection_closed) { + return process_request(strm, close_connection, connection_closed, + [&](Request &req) { req.ssl = ssl; }); + }); + + // Shutdown gracefully if the result seemed successful, non-gracefully if + // the connection appeared to be closed. + const bool shutdown_gracefully = ret; + detail::ssl_delete(ctx_mutex_, ssl, shutdown_gracefully); + } + + detail::shutdown_socket(sock); + detail::close_socket(sock); + return ret; } // SSL HTTP client implementation -inline SSLClient::SSLClient(const char *host, int port, time_t timeout_sec, - const char *client_cert_path, - const char *client_key_path) - : Client(host, port, timeout_sec) { +inline SSLClient::SSLClient(const std::string &host) + : SSLClient(host, 443, std::string(), std::string()) {} + +inline SSLClient::SSLClient(const std::string &host, int port) + : SSLClient(host, port, std::string(), std::string()) {} + +inline SSLClient::SSLClient(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path) + : ClientImpl(host, port, client_cert_path, client_key_path) { ctx_ = SSL_CTX_new(SSLv23_client_method()); detail::split(&host_[0], &host_[host_.size()], '.', [&](const char *b, const char *e) { host_components_.emplace_back(std::string(b, e)); }); - if (client_cert_path && client_key_path) { - if (SSL_CTX_use_certificate_file(ctx_, client_cert_path, + if (!client_cert_path.empty() && !client_key_path.empty()) { + if (SSL_CTX_use_certificate_file(ctx_, client_cert_path.c_str(), SSL_FILETYPE_PEM) != 1 || - SSL_CTX_use_PrivateKey_file(ctx_, client_key_path, SSL_FILETYPE_PEM) != - 1) { + SSL_CTX_use_PrivateKey_file(ctx_, client_key_path.c_str(), + SSL_FILETYPE_PEM) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } + } +} + +inline SSLClient::SSLClient(const std::string &host, int port, + X509 *client_cert, EVP_PKEY *client_key) + : ClientImpl(host, port) { + ctx_ = SSL_CTX_new(SSLv23_client_method()); + + detail::split(&host_[0], &host_[host_.size()], '.', + [&](const char *b, const char *e) { + host_components_.emplace_back(std::string(b, e)); + }); + if (client_cert != nullptr && client_key != nullptr) { + if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 || + SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) { SSL_CTX_free(ctx_); ctx_ = nullptr; } @@ -2497,6 +6877,10 @@ inline SSLClient::SSLClient(const char *host, int port, time_t timeout_sec, inline SSLClient::~SSLClient() { if (ctx_) { SSL_CTX_free(ctx_); } + // Make sure to shut down SSL since shutdown_ssl will resolve to the + // base function rather than the derived function once we get to the + // base class destructor, and won't free the SSL (causing a leak). + shutdown_ssl_impl(socket_, true); } inline bool SSLClient::is_valid() const { return ctx_; } @@ -2507,59 +6891,197 @@ inline void SSLClient::set_ca_cert_path(const char *ca_cert_file_path, if (ca_cert_dir_path) { ca_cert_dir_path_ = ca_cert_dir_path; } } -inline void SSLClient::enable_server_certificate_verification(bool enabled) { - server_certificate_verification_ = enabled; +inline void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (ca_cert_store) { + if (ctx_) { + if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store) { + // Free memory allocated for old cert and use new store `ca_cert_store` + SSL_CTX_set_cert_store(ctx_, ca_cert_store); + } + } else { + X509_STORE_free(ca_cert_store); + } + } } inline long SSLClient::get_openssl_verify_result() const { return verify_result_; } -inline bool SSLClient::read_and_close_socket(socket_t sock, Request &req, - Response &res) { +inline SSL_CTX *SSLClient::ssl_context() const { return ctx_; } - return is_valid() && - detail::read_and_close_socket_ssl( - sock, 0, ctx_, ctx_mutex_, - [&](SSL *ssl) { - if (ca_cert_file_path_.empty()) { - SSL_CTX_set_verify(ctx_, SSL_VERIFY_NONE, nullptr); - } else { - if (!SSL_CTX_load_verify_locations( - ctx_, ca_cert_file_path_.c_str(), nullptr)) { - return false; - } - SSL_CTX_set_verify(ctx_, SSL_VERIFY_PEER, nullptr); - } +inline bool SSLClient::create_and_connect_socket(Socket &socket, Error &error) { + return is_valid() && ClientImpl::create_and_connect_socket(socket, error); +} - if (SSL_connect(ssl) != 1) { return false; } +// Assumes that socket_mutex_ is locked and that there are no requests in flight +inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res, + bool &success, Error &error) { + success = true; + Response res2; + if (!detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) { + Request req2; + req2.method = "CONNECT"; + req2.path = host_and_port_; + return process_request(strm, req2, res2, false, error); + })) { + // Thread-safe to close everything because we are assuming there are no + // requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + success = false; + return false; + } - if (server_certificate_verification_) { - verify_result_ = SSL_get_verify_result(ssl); + if (res2.status == 407) { + if (!proxy_digest_auth_username_.empty() && + !proxy_digest_auth_password_.empty()) { + std::map auth; + if (detail::parse_www_authenticate(res2, auth, true)) { + Response res3; + if (!detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) { + Request req3; + req3.method = "CONNECT"; + req3.path = host_and_port_; + req3.headers.insert(detail::make_digest_authentication_header( + req3, auth, 1, detail::random_string(10), + proxy_digest_auth_username_, proxy_digest_auth_password_, + true)); + return process_request(strm, req3, res3, false, error); + })) { + // Thread-safe to close everything because we are assuming there are + // no requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + success = false; + return false; + } + } + } else { + res = res2; + return false; + } + } - if (verify_result_ != X509_V_OK) { return false; } + return true; +} - auto server_cert = SSL_get_peer_certificate(ssl); +inline bool SSLClient::load_certs() { + bool ret = true; - if (server_cert == nullptr) { return false; } + std::call_once(initialize_cert_, [&]() { + std::lock_guard guard(ctx_mutex_); + if (!ca_cert_file_path_.empty()) { + if (!SSL_CTX_load_verify_locations(ctx_, ca_cert_file_path_.c_str(), + nullptr)) { + ret = false; + } + } else if (!ca_cert_dir_path_.empty()) { + if (!SSL_CTX_load_verify_locations(ctx_, nullptr, + ca_cert_dir_path_.c_str())) { + ret = false; + } + } else { +#ifdef _WIN32 + detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_)); +#else + SSL_CTX_set_default_verify_paths(ctx_); +#endif + } + }); - if (!verify_host(server_cert)) { - X509_free(server_cert); - return false; - } - X509_free(server_cert); - } + return ret; +} - return true; - }, - [&](SSL *ssl) { - SSL_set_tlsext_host_name(ssl, host_.c_str()); - return true; - }, - [&](SSL * /*ssl*/, Stream &strm, bool /*last_connection*/, - bool &connection_close) { - return process_request(strm, req, res, connection_close); - }); +inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) { + auto ssl = detail::ssl_new( + socket.sock, ctx_, ctx_mutex_, + [&](SSL *ssl) { + if (server_certificate_verification_) { + if (!load_certs()) { + error = Error::SSLLoadingCerts; + return false; + } + SSL_set_verify(ssl, SSL_VERIFY_NONE, nullptr); + } + + if (!detail::ssl_connect_or_accept_nonblocking( + socket.sock, ssl, SSL_connect, connection_timeout_sec_, + connection_timeout_usec_)) { + error = Error::SSLConnection; + return false; + } + + if (server_certificate_verification_) { + verify_result_ = SSL_get_verify_result(ssl); + + if (verify_result_ != X509_V_OK) { + error = Error::SSLServerVerification; + return false; + } + + auto server_cert = SSL_get_peer_certificate(ssl); + + if (server_cert == nullptr) { + error = Error::SSLServerVerification; + return false; + } + + if (!verify_host(server_cert)) { + X509_free(server_cert); + error = Error::SSLServerVerification; + return false; + } + X509_free(server_cert); + } + + return true; + }, + [&](SSL *ssl) { + SSL_set_tlsext_host_name(ssl, host_.c_str()); + return true; + }); + + if (ssl) { + socket.ssl = ssl; + return true; + } + + shutdown_socket(socket); + close_socket(socket); + return false; +} + +inline void SSLClient::shutdown_ssl(Socket &socket, bool shutdown_gracefully) { + shutdown_ssl_impl(socket, shutdown_gracefully); +} + +inline void SSLClient::shutdown_ssl_impl(Socket &socket, + bool shutdown_gracefully) { + if (socket.sock == INVALID_SOCKET) { + assert(socket.ssl == nullptr); + return; + } + if (socket.ssl) { + detail::ssl_delete(ctx_mutex_, socket.ssl, shutdown_gracefully); + socket.ssl = nullptr; + } + assert(socket.ssl == nullptr); +} + +inline bool +SSLClient::process_socket(const Socket &socket, + std::function callback) { + assert(socket.ssl); + return detail::process_client_socket_ssl( + socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, std::move(callback)); } inline bool SSLClient::is_ssl() const { return true; } @@ -2600,6 +7122,7 @@ SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const { struct in_addr addr; size_t addr_len = 0; +#ifndef __MINGW32__ if (inet_pton(AF_INET6, host_.c_str(), &addr6)) { type = GEN_IPADD; addr_len = sizeof(struct in6_addr); @@ -2607,6 +7130,7 @@ SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const { type = GEN_IPADD; addr_len = sizeof(struct in_addr); } +#endif auto alt_names = static_cast( X509_get_ext_d2i(server_cert, NID_subject_alt_name, nullptr, nullptr)); @@ -2617,23 +7141,21 @@ SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const { auto count = sk_GENERAL_NAME_num(alt_names); - for (auto i = 0; i < count && !dsn_matched; i++) { + for (decltype(count) i = 0; i < count && !dsn_matched; i++) { auto val = sk_GENERAL_NAME_value(alt_names, i); if (val->type == type) { auto name = (const char *)ASN1_STRING_get0_data(val->d.ia5); auto name_len = (size_t)ASN1_STRING_length(val->d.ia5); - if (strlen(name) == name_len) { - switch (type) { - case GEN_DNS: dsn_matched = check_host_name(name, name_len); break; + switch (type) { + case GEN_DNS: dsn_matched = check_host_name(name, name_len); break; - case GEN_IPADD: - if (!memcmp(&addr6, name, addr_len) || - !memcmp(&addr, name, addr_len)) { - ip_mached = true; - } - break; + case GEN_IPADD: + if (!memcmp(&addr6, name, addr_len) || + !memcmp(&addr, name, addr_len)) { + ip_mached = true; } + break; } } } @@ -2642,7 +7164,6 @@ SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const { } GENERAL_NAMES_free((STACK_OF(GENERAL_NAME) *)alt_names); - return ret; } @@ -2654,7 +7175,9 @@ inline bool SSLClient::verify_host_with_common_name(X509 *server_cert) const { auto name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName, name, sizeof(name)); - if (name_len != -1) { return check_host_name(name, name_len); } + if (name_len != -1) { + return check_host_name(name, static_cast(name_len)); + } } return false; @@ -2689,6 +7212,452 @@ inline bool SSLClient::check_host_name(const char *pattern, } #endif +// Universal client implementation + inline Client::Client(const char *scheme_host_port) + : Client(scheme_host_port, std::string(), std::string()) {} + + inline Client::Client(const char *scheme_host_port, + const std::string &client_cert_path, + const std::string &client_key_path) { + const static std::regex re(R"(^(?:([a-z]+)://)?([^:/?#]+)(?::(\d+))?)"); + + std::cmatch m; + if (std::regex_match(scheme_host_port, m, re)) { + auto scheme = m[1].str(); + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if (!scheme.empty() && (scheme != "http" && scheme != "https")) { +#else + if (!scheme.empty() && scheme != "http") { +#endif + std::string msg = "'" + scheme + "' scheme is not supported."; + throw std::invalid_argument(msg); + return; + } + + auto is_ssl = scheme == "https"; + + auto host = m[2].str(); + + auto port_str = m[3].str(); + auto port = !port_str.empty() ? std::stoi(port_str) : (is_ssl ? 443 : 80); + + if (is_ssl) { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + cli_ = detail::make_unique(host.c_str(), port, + client_cert_path, client_key_path); + is_ssl_ = is_ssl; +#endif + } else { + cli_ = detail::make_unique(host.c_str(), port, + client_cert_path, client_key_path); + } + } else { + cli_ = detail::make_unique(scheme_host_port, 80, + client_cert_path, client_key_path); + } + } + + inline Client::Client(const std::string &host, int port) + : cli_(detail::make_unique(host, port)) {} + + inline Client::Client(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path) + : cli_(detail::make_unique(host, port, client_cert_path, + client_key_path)) {} + + inline Client::~Client() {} + + inline bool Client::is_valid() const { + return cli_ != nullptr && cli_->is_valid(); + } + + inline Result Client::Get(const char *path) { return cli_->Get(path); } + inline Result Client::Get(const char *path, const Headers &headers) { + return cli_->Get(path, headers); + } + inline Result Client::Get(const char *path, Progress progress) { + return cli_->Get(path, std::move(progress)); + } + inline Result Client::Get(const char *path, const Headers &headers, + Progress progress) { + return cli_->Get(path, headers, std::move(progress)); + } + inline Result Client::Get(const char *path, ContentReceiver content_receiver) { + return cli_->Get(path, std::move(content_receiver)); + } + inline Result Client::Get(const char *path, const Headers &headers, + ContentReceiver content_receiver) { + return cli_->Get(path, headers, std::move(content_receiver)); + } + inline Result Client::Get(const char *path, ContentReceiver content_receiver, + Progress progress) { + return cli_->Get(path, std::move(content_receiver), std::move(progress)); + } + inline Result Client::Get(const char *path, const Headers &headers, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, headers, std::move(content_receiver), + std::move(progress)); + } + inline Result Client::Get(const char *path, ResponseHandler response_handler, + ContentReceiver content_receiver) { + return cli_->Get(path, std::move(response_handler), + std::move(content_receiver)); + } + inline Result Client::Get(const char *path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return cli_->Get(path, headers, std::move(response_handler), + std::move(content_receiver)); + } + inline Result Client::Get(const char *path, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, std::move(response_handler), + std::move(content_receiver), std::move(progress)); + } + inline Result Client::Get(const char *path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, headers, std::move(response_handler), + std::move(content_receiver), std::move(progress)); + } + inline Result Client::Get(const char *path, const Params ¶ms, + const Headers &headers, Progress progress) { + return cli_->Get(path, params, headers, progress); + } + inline Result Client::Get(const char *path, const Params ¶ms, + const Headers &headers, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, params, headers, content_receiver, progress); + } + inline Result Client::Get(const char *path, const Params ¶ms, + const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, params, headers, response_handler, content_receiver, + progress); + } + + inline Result Client::Head(const char *path) { return cli_->Head(path); } + inline Result Client::Head(const char *path, const Headers &headers) { + return cli_->Head(path, headers); + } + + inline Result Client::Post(const char *path) { return cli_->Post(path); } + inline Result Client::Post(const char *path, const char *body, + size_t content_length, const char *content_type) { + return cli_->Post(path, body, content_length, content_type); + } + inline Result Client::Post(const char *path, const Headers &headers, + const char *body, size_t content_length, + const char *content_type) { + return cli_->Post(path, headers, body, content_length, content_type); + } + inline Result Client::Post(const char *path, const std::string &body, + const char *content_type) { + return cli_->Post(path, body, content_type); + } + inline Result Client::Post(const char *path, const Headers &headers, + const std::string &body, const char *content_type) { + return cli_->Post(path, headers, body, content_type); + } + inline Result Client::Post(const char *path, size_t content_length, + ContentProvider content_provider, + const char *content_type) { + return cli_->Post(path, content_length, std::move(content_provider), + content_type); + } + inline Result Client::Post(const char *path, + ContentProviderWithoutLength content_provider, + const char *content_type) { + return cli_->Post(path, std::move(content_provider), content_type); + } + inline Result Client::Post(const char *path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const char *content_type) { + return cli_->Post(path, headers, content_length, std::move(content_provider), + content_type); + } + inline Result Client::Post(const char *path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const char *content_type) { + return cli_->Post(path, headers, std::move(content_provider), content_type); + } + inline Result Client::Post(const char *path, const Params ¶ms) { + return cli_->Post(path, params); + } + inline Result Client::Post(const char *path, const Headers &headers, + const Params ¶ms) { + return cli_->Post(path, headers, params); + } + inline Result Client::Post(const char *path, + const MultipartFormDataItems &items) { + return cli_->Post(path, items); + } + inline Result Client::Post(const char *path, const Headers &headers, + const MultipartFormDataItems &items) { + return cli_->Post(path, headers, items); + } + inline Result Client::Post(const char *path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + return cli_->Post(path, headers, items, boundary); + } + inline Result Client::Put(const char *path) { return cli_->Put(path); } + inline Result Client::Put(const char *path, const char *body, + size_t content_length, const char *content_type) { + return cli_->Put(path, body, content_length, content_type); + } + inline Result Client::Put(const char *path, const Headers &headers, + const char *body, size_t content_length, + const char *content_type) { + return cli_->Put(path, headers, body, content_length, content_type); + } + inline Result Client::Put(const char *path, const std::string &body, + const char *content_type) { + return cli_->Put(path, body, content_type); + } + inline Result Client::Put(const char *path, const Headers &headers, + const std::string &body, const char *content_type) { + return cli_->Put(path, headers, body, content_type); + } + inline Result Client::Put(const char *path, size_t content_length, + ContentProvider content_provider, + const char *content_type) { + return cli_->Put(path, content_length, std::move(content_provider), + content_type); + } + inline Result Client::Put(const char *path, + ContentProviderWithoutLength content_provider, + const char *content_type) { + return cli_->Put(path, std::move(content_provider), content_type); + } + inline Result Client::Put(const char *path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const char *content_type) { + return cli_->Put(path, headers, content_length, std::move(content_provider), + content_type); + } + inline Result Client::Put(const char *path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const char *content_type) { + return cli_->Put(path, headers, std::move(content_provider), content_type); + } + inline Result Client::Put(const char *path, const Params ¶ms) { + return cli_->Put(path, params); + } + inline Result Client::Put(const char *path, const Headers &headers, + const Params ¶ms) { + return cli_->Put(path, headers, params); + } + inline Result Client::Patch(const char *path) { return cli_->Patch(path); } + inline Result Client::Patch(const char *path, const char *body, + size_t content_length, const char *content_type) { + return cli_->Patch(path, body, content_length, content_type); + } + inline Result Client::Patch(const char *path, const Headers &headers, + const char *body, size_t content_length, + const char *content_type) { + return cli_->Patch(path, headers, body, content_length, content_type); + } + inline Result Client::Patch(const char *path, const std::string &body, + const char *content_type) { + return cli_->Patch(path, body, content_type); + } + inline Result Client::Patch(const char *path, const Headers &headers, + const std::string &body, const char *content_type) { + return cli_->Patch(path, headers, body, content_type); + } + inline Result Client::Patch(const char *path, size_t content_length, + ContentProvider content_provider, + const char *content_type) { + return cli_->Patch(path, content_length, std::move(content_provider), + content_type); + } + inline Result Client::Patch(const char *path, + ContentProviderWithoutLength content_provider, + const char *content_type) { + return cli_->Patch(path, std::move(content_provider), content_type); + } + inline Result Client::Patch(const char *path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const char *content_type) { + return cli_->Patch(path, headers, content_length, std::move(content_provider), + content_type); + } + inline Result Client::Patch(const char *path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const char *content_type) { + return cli_->Patch(path, headers, std::move(content_provider), content_type); + } + inline Result Client::Delete(const char *path) { return cli_->Delete(path); } + inline Result Client::Delete(const char *path, const Headers &headers) { + return cli_->Delete(path, headers); + } + inline Result Client::Delete(const char *path, const char *body, + size_t content_length, const char *content_type) { + return cli_->Delete(path, body, content_length, content_type); + } + inline Result Client::Delete(const char *path, const Headers &headers, + const char *body, size_t content_length, + const char *content_type) { + return cli_->Delete(path, headers, body, content_length, content_type); + } + inline Result Client::Delete(const char *path, const std::string &body, + const char *content_type) { + return cli_->Delete(path, body, content_type); + } + inline Result Client::Delete(const char *path, const Headers &headers, + const std::string &body, + const char *content_type) { + return cli_->Delete(path, headers, body, content_type); + } + inline Result Client::Options(const char *path) { return cli_->Options(path); } + inline Result Client::Options(const char *path, const Headers &headers) { + return cli_->Options(path, headers); + } + + inline bool Client::send(Request &req, Response &res, Error &error) { + return cli_->send(req, res, error); + } + + inline Result Client::send(const Request &req) { return cli_->send(req); } + + inline size_t Client::is_socket_open() const { return cli_->is_socket_open(); } + + inline void Client::stop() { cli_->stop(); } + + inline void Client::set_default_headers(Headers headers) { + cli_->set_default_headers(std::move(headers)); + } + + inline void Client::set_address_family(int family) { + cli_->set_address_family(family); + } + + inline void Client::set_tcp_nodelay(bool on) { cli_->set_tcp_nodelay(on); } + + inline void Client::set_socket_options(SocketOptions socket_options) { + cli_->set_socket_options(std::move(socket_options)); + } + + inline void Client::set_connection_timeout(time_t sec, time_t usec) { + cli_->set_connection_timeout(sec, usec); + } + + template + inline void Client::set_connection_timeout( + const std::chrono::duration &duration) { + cli_->set_connection_timeout(duration); + } + + inline void Client::set_read_timeout(time_t sec, time_t usec) { + cli_->set_read_timeout(sec, usec); + } + + template + inline void + Client::set_read_timeout(const std::chrono::duration &duration) { + cli_->set_read_timeout(duration); + } + + inline void Client::set_write_timeout(time_t sec, time_t usec) { + cli_->set_write_timeout(sec, usec); + } + + template + inline void + Client::set_write_timeout(const std::chrono::duration &duration) { + cli_->set_write_timeout(duration); + } + + inline void Client::set_basic_auth(const char *username, const char *password) { + cli_->set_basic_auth(username, password); + } + inline void Client::set_bearer_token_auth(const char *token) { + cli_->set_bearer_token_auth(token); + } +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + inline void Client::set_digest_auth(const char *username, + const char *password) { + cli_->set_digest_auth(username, password); +} +#endif + + inline void Client::set_keep_alive(bool on) { cli_->set_keep_alive(on); } + inline void Client::set_follow_location(bool on) { + cli_->set_follow_location(on); + } + + inline void Client::set_url_encode(bool on) { cli_->set_url_encode(on); } + + inline void Client::set_compress(bool on) { cli_->set_compress(on); } + + inline void Client::set_decompress(bool on) { cli_->set_decompress(on); } + + inline void Client::set_interface(const char *intf) { + cli_->set_interface(intf); + } + + inline void Client::set_proxy(const char *host, int port) { + cli_->set_proxy(host, port); + } + inline void Client::set_proxy_basic_auth(const char *username, + const char *password) { + cli_->set_proxy_basic_auth(username, password); + } + inline void Client::set_proxy_bearer_token_auth(const char *token) { + cli_->set_proxy_bearer_token_auth(token); + } +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + inline void Client::set_proxy_digest_auth(const char *username, + const char *password) { + cli_->set_proxy_digest_auth(username, password); +} +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + inline void Client::enable_server_certificate_verification(bool enabled) { + cli_->enable_server_certificate_verification(enabled); +} +#endif + + inline void Client::set_logger(Logger logger) { cli_->set_logger(logger); } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + inline void Client::set_ca_cert_path(const char *ca_cert_file_path, + const char *ca_cert_dir_path) { + if (is_ssl_) { + static_cast(*cli_).set_ca_cert_path(ca_cert_file_path, + ca_cert_dir_path); + } +} + +inline void Client::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (is_ssl_) { + static_cast(*cli_).set_ca_cert_store(ca_cert_store); + } +} + +inline long Client::get_openssl_verify_result() const { + if (is_ssl_) { + return static_cast(*cli_).get_openssl_verify_result(); + } + return -1; // NOTE: -1 doesn't match any of X509_V_ERR_??? +} + +inline SSL_CTX *Client::ssl_context() const { + if (is_ssl_) { return static_cast(*cli_).ssl_context(); } + return nullptr; +} +#endif + +// ---------------------------------------------------------------------------- + } // namespace httplib -#endif // CPPHTTPLIB_HTTPLIB_H \ No newline at end of file +#endif // CPPHTTPLIB_HTTPLIB_H diff --git a/common/http/uri.h b/common/http/uri.h new file mode 100644 index 000000000..04b891e45 --- /dev/null +++ b/common/http/uri.h @@ -0,0 +1,633 @@ +// Copyright (C) 2015 Ben Lewis +// Licensed under the MIT license. +// https://github.com/ben-zen/uri-library + +#pragma once + +#include +#include +#include +#include +#include + +class uri { + /* URIs are broadly divided into two categories: hierarchical and + * non-hierarchical. Both hierarchical URIs and non-hierarchical URIs have a + * few elements in common; all URIs have a scheme of one or more alphanumeric + * characters followed by a colon, and they all may optionally have a query + * component preceded by a question mark, and a fragment component preceded by + * an octothorpe (hash mark: '#'). The query consists of stanzas separated by + * either ampersands ('&') or semicolons (';') (but only one or the other), + * and each stanza consists of a key and an optional value; if the value + * exists, the key and value must be divided by an equals sign. + * + * The following is an example from Wikipedia of a hierarchical URI: + * scheme:[//[user:password@]domain[:port]][/]path[?query][#fragment] + */ + +public: + + enum class scheme_category { + Hierarchical, + NonHierarchical + }; + + enum class component { + Scheme, + Content, + Username, + Password, + Host, + Port, + Path, + Query, + Fragment + }; + + enum class query_argument_separator { + ampersand, + semicolon + }; + + uri( + char const *uri_text, scheme_category category = scheme_category::Hierarchical, + query_argument_separator separator = query_argument_separator::ampersand + ) : + m_category(category), + m_path_is_rooted(false), + m_port(0), + m_separator(separator) + { + setup(std::string(uri_text), category); + }; + + uri( + std::string const &uri_text, scheme_category category = scheme_category::Hierarchical, + query_argument_separator separator = query_argument_separator::ampersand + ) : + m_category(category), + m_path_is_rooted(false), + m_port(0), + m_separator(separator) + { + setup(uri_text, category); + }; + + uri( + std::map const &components, + scheme_category category, + bool rooted_path, + query_argument_separator separator = query_argument_separator::ampersand + ) : + m_category(category), + m_path_is_rooted(rooted_path), + m_separator(separator) + { + if (components.count(component::Scheme)) { + if (components.at(component::Scheme).length() == 0) { + throw std::invalid_argument("Scheme cannot be empty."); + } + m_scheme = components.at(component::Scheme); + } + else { + throw std::invalid_argument("A URI must have a scheme."); + } + + if (category == scheme_category::Hierarchical) { + if (components.count(component::Content)) { + throw std::invalid_argument("The content component is only for use in non-hierarchical URIs."); + } + + bool has_username = components.count(component::Username); + bool has_password = components.count(component::Password); + if (has_username && has_password) { + m_username = components.at(component::Username); + m_password = components.at(component::Password); + } + else if ((has_username && !has_password) || (!has_username && has_password)) { + throw std::invalid_argument("If a username or password is supplied, both must be provided."); + } + + if (components.count(component::Host)) { + m_host = components.at(component::Host); + } + + if (components.count(component::Port)) { + m_port = std::stoul(components.at(component::Port)); + } + + if (components.count(component::Path)) { + m_path = components.at(component::Path); + } + else { + throw std::invalid_argument("A path is required on a hierarchical URI, even an empty path."); + } + } + else { + if (components.count(component::Username) + || components.count(component::Password) + || components.count(component::Host) + || components.count(component::Port) + || components.count(component::Path)) { + throw std::invalid_argument("None of the hierarchical components are allowed in a non-hierarchical URI."); + } + + if (components.count(component::Content)) { + m_content = components.at(component::Content); + } + else { + throw std::invalid_argument( + "Content is a required component for a non-hierarchical URI, even an empty string." + ); + } + } + + if (components.count(component::Query)) { + m_query = components.at(component::Query); + } + + if (components.count(component::Fragment)) { + m_fragment = components.at(component::Fragment); + } + } + + uri(uri const &other, std::map const &replacements) : + m_category(other.m_category), + m_path_is_rooted(other.m_path_is_rooted), + m_separator(other.m_separator) + { + m_scheme = (replacements.count(component::Scheme)) + ? replacements.at(component::Scheme) : other.m_scheme; + + if (m_category == scheme_category::Hierarchical) { + m_username = (replacements.count(component::Username)) + ? replacements.at(component::Username) : other.m_username; + + m_password = (replacements.count(component::Password)) + ? replacements.at(component::Password) : other.m_password; + + m_host = (replacements.count(component::Host)) + ? replacements.at(component::Host) : other.m_host; + + m_port = (replacements.count(component::Port)) + ? std::stoul(replacements.at(component::Port)) : other.m_port; + + m_path = (replacements.count(component::Path)) + ? replacements.at(component::Path) : other.m_path; + } + else { + m_content = (replacements.count(component::Content)) + ? replacements.at(component::Content) : other.m_content; + } + + m_query = (replacements.count(component::Query)) + ? replacements.at(component::Query) : other.m_query; + + m_fragment = (replacements.count(component::Fragment)) + ? replacements.at(component::Fragment) : other.m_fragment; + } + + // Copy constructor; just use the copy assignment operator internally. + uri(uri const &other) + { + *this = other; + }; + + // Copy assignment operator + uri &operator=(uri const &other) + { + if (this != &other) { + m_scheme = other.m_scheme; + m_content = other.m_content; + m_username = other.m_username; + m_password = other.m_password; + m_host = other.m_host; + m_path = other.m_path; + m_query = other.m_query; + m_fragment = other.m_fragment; + m_query_dict = other.m_query_dict; + m_category = other.m_category; + m_port = other.m_port; + m_path_is_rooted = other.m_path_is_rooted; + m_separator = other.m_separator; + } + return *this; + } + + ~uri() {}; + + std::string const &get_scheme() const + { + return m_scheme; + }; + + scheme_category get_scheme_category() const + { + return m_category; + }; + + std::string const &get_content() const + { + if (m_category != scheme_category::NonHierarchical) { + throw std::domain_error("The content component is only valid for non-hierarchical URIs."); + } + return m_content; + }; + + std::string const &get_username() const + { + if (m_category != scheme_category::Hierarchical) { + throw std::domain_error("The username component is only valid for hierarchical URIs."); + } + return m_username; + }; + + std::string const &get_password() const + { + if (m_category != scheme_category::Hierarchical) { + throw std::domain_error("The password component is only valid for hierarchical URIs."); + } + return m_password; + }; + + std::string const &get_host() const + { + if (m_category != scheme_category::Hierarchical) { + throw std::domain_error("The host component is only valid for hierarchical URIs."); + } + return m_host; + }; + + unsigned long get_port() const + { + if (m_category != scheme_category::Hierarchical) { + throw std::domain_error("The port component is only valid for hierarchical URIs."); + } + return m_port; + }; + + std::string const &get_path() const + { + if (m_category != scheme_category::Hierarchical) { + throw std::domain_error("The path component is only valid for hierarchical URIs."); + } + return m_path; + }; + + std::string const &get_query() const + { + return m_query; + }; + + std::map const &get_query_dictionary() const + { + return m_query_dict; + }; + + std::string const &get_fragment() const + { + return m_fragment; + }; + + std::string to_string() const + { + std::string full_uri; + full_uri.append(m_scheme); + full_uri.append(":"); + + if (m_content.length() > m_path.length()) { + full_uri.append("//"); + if (!(m_username.empty() || m_password.empty())) { + full_uri.append(m_username); + full_uri.append(":"); + full_uri.append(m_password); + full_uri.append("@"); + } + + full_uri.append(m_host); + + if (m_port != 0) { + full_uri.append(":"); + full_uri.append(std::to_string(m_port)); + } + } + + if (m_path_is_rooted) { + full_uri.append("/"); + } + full_uri.append(m_path); + + if (!m_query.empty()) { + full_uri.append("?"); + full_uri.append(m_query); + } + + if (!m_fragment.empty()) { + full_uri.append("#"); + full_uri.append(m_fragment); + } + + return full_uri; + }; + +private: + + void setup(std::string const &uri_text, scheme_category category) + { + size_t const uri_length = uri_text.length(); + + if (uri_length == 0) { + throw std::invalid_argument("URIs cannot be of zero length."); + } + + std::string::const_iterator cursor = parse_scheme( + uri_text, + uri_text.begin()); + // After calling parse_scheme, *cursor == ':'; none of the following parsers + // expect a separator character, so we advance the cursor upon calling them. + cursor = parse_content(uri_text, (cursor + 1)); + + if ((cursor != uri_text.end()) && (*cursor == '?')) { + cursor = parse_query(uri_text, (cursor + 1)); + } + + if ((cursor != uri_text.end()) && (*cursor == '#')) { + cursor = parse_fragment(uri_text, (cursor + 1)); + } + + init_query_dictionary(); // If the query string is empty, this will be empty too. + + }; + + std::string::const_iterator parse_scheme( + std::string const &uri_text, + std::string::const_iterator scheme_start + ) + { + std::string::const_iterator scheme_end = scheme_start; + while ((scheme_end != uri_text.end()) && (*scheme_end != ':')) { + if (!(std::isalnum(*scheme_end) || (*scheme_end == '-') + || (*scheme_end == '+') || (*scheme_end == '.'))) { + throw std::invalid_argument( + "Invalid character found in the scheme component. Supplied URI was: \"" + + uri_text + "\"." + ); + } + ++scheme_end; + } + + if (scheme_end == uri_text.end()) { + throw std::invalid_argument( + "End of URI found while parsing the scheme. Supplied URI was: \"" + + uri_text + "\"." + ); + } + + if (scheme_start == scheme_end) { + throw std::invalid_argument( + "Scheme component cannot be zero-length. Supplied URI was: \"" + + uri_text + "\"." + ); + } + + m_scheme = std::move(std::string(scheme_start, scheme_end)); + return scheme_end; + }; + + std::string::const_iterator parse_content( + std::string const &uri_text, + std::string::const_iterator content_start + ) + { + std::string::const_iterator content_end = content_start; + while ((content_end != uri_text.end()) && (*content_end != '?') && (*content_end != '#')) { + ++content_end; + } + + m_content = std::move(std::string(content_start, content_end)); + + if ((m_category == scheme_category::Hierarchical) && (m_content.length() > 0)) { + // If it's a hierarchical URI, the content should be parsed for the hierarchical components. + std::string::const_iterator path_start = m_content.begin(); + std::string::const_iterator path_end = m_content.end(); + if (!m_content.compare(0, 2, "//")) { + // In this case an authority component is present. + std::string::const_iterator authority_cursor = (m_content.begin() + 2); + if (m_content.find_first_of('@') != std::string::npos) { + std::string::const_iterator userpass_divider = parse_username( + uri_text, + m_content, + authority_cursor + ); + authority_cursor = parse_password(uri_text, m_content, (userpass_divider + 1)); + // After this call, *authority_cursor == '@', so we skip over it. + ++authority_cursor; + } + + authority_cursor = parse_host(uri_text, m_content, authority_cursor); + + if ((authority_cursor != m_content.end()) && (*authority_cursor == ':')) { + authority_cursor = parse_port(uri_text, m_content, (authority_cursor + 1)); + } + + if ((authority_cursor != m_content.end()) && (*authority_cursor == '/')) { + // Then the path is rooted, and we should note this. + m_path_is_rooted = true; + path_start = authority_cursor + 1; + } + + // If we've reached the end and no path is present then set path_start + // to the end. + if (authority_cursor == m_content.end()) { + path_start = m_content.end(); + } + } + else if (!m_content.compare(0, 1, "/")) { + m_path_is_rooted = true; + ++path_start; + } + + // We can now build the path based on what remains in the content string, + // since that's all that exists after the host and optional port component. + m_path = std::move(std::string(path_start, path_end)); + } + return content_end; + }; + + std::string::const_iterator parse_username( + std::string const &uri_text, + std::string const &content, + std::string::const_iterator username_start + ) + { + std::string::const_iterator username_end = username_start; + // Since this is only reachable when '@' was in the content string, we can + // ignore the end-of-string case. + while (*username_end != ':') { + if (*username_end == '@') { + throw std::invalid_argument( + "Username must be followed by a password. Supplied URI was: \"" + + uri_text + "\"." + ); + } + ++username_end; + } + m_username = std::move(std::string(username_start, username_end)); + return username_end; + }; + + std::string::const_iterator parse_password( + std::string const &uri_text, + std::string const &content, + std::string::const_iterator password_start + ) + { + std::string::const_iterator password_end = password_start; + while (*password_end != '@') { + ++password_end; + } + + m_password = std::move(std::string(password_start, password_end)); + return password_end; + }; + + std::string::const_iterator parse_host( + std::string const &uri_text, + std::string const &content, + std::string::const_iterator host_start + ) + { + std::string::const_iterator host_end = host_start; + // So, the host can contain a few things. It can be a domain, it can be an + // IPv4 address, it can be an IPv6 address, or an IPvFuture literal. In the + // case of those last two, it's of the form [...] where what's between the + // brackets is a matter of which IPv?? version it is. + while (host_end != content.end()) { + if (*host_end == '[') { + // We're parsing an IPv6 or IPvFuture address, so we should handle that + // instead of the normal procedure. + while ((host_end != content.end()) && (*host_end != ']')) { + ++host_end; + } + + if (host_end == content.end()) { + throw std::invalid_argument( + "End of content component encountered " + "while parsing the host component. " + "Supplied URI was: \"" + + uri_text + "\"." + ); + } + + ++host_end; + break; + // We can stop looping, we found the end of the IP literal, which is the + // whole of the host component when one's in use. + } + else if ((*host_end == ':') || (*host_end == '/')) { + break; + } + else { + ++host_end; + } + } + + m_host = std::move(std::string(host_start, host_end)); + return host_end; + }; + + std::string::const_iterator parse_port( + std::string const &uri_text, + std::string const &content, + std::string::const_iterator port_start + ) + { + std::string::const_iterator port_end = port_start; + while ((port_end != content.end()) && (*port_end != '/')) { + if (!std::isdigit(*port_end)) { + throw std::invalid_argument( + "Invalid character while parsing the port. " + "Supplied URI was: \"" + uri_text + "\"." + ); + } + + ++port_end; + } + + m_port = std::stoul(std::string(port_start, port_end)); + return port_end; + }; + + std::string::const_iterator parse_query( + std::string const &uri_text, + std::string::const_iterator query_start + ) + { + std::string::const_iterator query_end = query_start; + while ((query_end != uri_text.end()) && (*query_end != '#')) { + // Queries can contain almost any character except hash, which is reserved + // for the start of the fragment. + ++query_end; + } + m_query = std::move(std::string(query_start, query_end)); + return query_end; + }; + + std::string::const_iterator parse_fragment( + std::string const &uri_text, + std::string::const_iterator fragment_start + ) + { + m_fragment = std::move(std::string(fragment_start, uri_text.end())); + return uri_text.end(); + }; + + void init_query_dictionary() + { + if (!m_query.empty()) { + // Loop over the query string looking for '&'s, then check each one for + // an '=' to find keys and values; if there's not an '=' then the key + // will have an empty value in the map. + char separator = (m_separator == query_argument_separator::ampersand) ? '&' : ';'; + size_t carat = 0; + size_t stanza_end = m_query.find_first_of(separator); + do { + std::string stanza = m_query.substr( + carat, + ((stanza_end != std::string::npos) ? (stanza_end - carat) : std::string::npos)); + size_t key_value_divider = stanza.find_first_of('='); + std::string key = stanza.substr(0, key_value_divider); + std::string value; + if (key_value_divider != std::string::npos) { + value = stanza.substr((key_value_divider + 1)); + } + + if (m_query_dict.count(key) != 0) { + throw std::invalid_argument("Bad key in the query string!"); + } + + m_query_dict.emplace(key, value); + carat = ((stanza_end != std::string::npos) ? (stanza_end + 1) + : std::string::npos); + stanza_end = m_query.find_first_of(separator, carat); + } while ((stanza_end != std::string::npos) + || (carat != std::string::npos)); + } + } + + std::string m_scheme; + std::string m_content; + std::string m_username; + std::string m_password; + std::string m_host; + std::string m_path; + std::string m_query; + std::string m_fragment; + + std::map m_query_dict; + + scheme_category m_category; + unsigned long m_port; + bool m_path_is_rooted; + query_argument_separator m_separator; +}; diff --git a/common/inventory_profile.cpp b/common/inventory_profile.cpp index e93146598..fbc794351 100644 --- a/common/inventory_profile.cpp +++ b/common/inventory_profile.cpp @@ -224,7 +224,7 @@ EQ::ItemInstance* EQ::InventoryProfile::GetItem(int16 slot_id, uint8 bagidx) con return GetItem(InventoryProfile::CalcSlotId(slot_id, bagidx)); } -// Put an item snto specified slot +// Put an item into specified slot int16 EQ::InventoryProfile::PutItem(int16 slot_id, const ItemInstance& inst) { if (slot_id <= EQ::invslot::POSSESSIONS_END && slot_id >= EQ::invslot::POSSESSIONS_BEGIN) { @@ -399,7 +399,7 @@ bool EQ::InventoryProfile::SwapItem( } // Remove item from inventory (with memory delete) -bool EQ::InventoryProfile::DeleteItem(int16 slot_id, uint8 quantity) { +bool EQ::InventoryProfile::DeleteItem(int16 slot_id, int16 quantity) { // Pop item out of inventory map (or queue) ItemInstance *item_to_delete = PopItem(slot_id); @@ -590,6 +590,68 @@ bool EQ::InventoryProfile::HasSpaceForItem(const ItemData *ItemToTry, int16 Quan // Checks that user has at least 'quantity' number of items in a given inventory slot // Returns first slot it was found in, or SLOT_INVALID if not found +bool EQ::InventoryProfile::HasAugmentEquippedByID(uint32 item_id) +{ + bool has_equipped = false; + ItemInstance* item = nullptr; + + for (int slot_id = EQ::invslot::EQUIPMENT_BEGIN; slot_id <= EQ::invslot::EQUIPMENT_END; ++slot_id) { + item = GetItem(slot_id); + if (item && item->ContainsAugmentByID(item_id)) { + has_equipped = true; + break; + } + } + + return has_equipped; +} + +int EQ::InventoryProfile::CountAugmentEquippedByID(uint32 item_id) +{ + int quantity = 0; + ItemInstance* item = nullptr; + + for (int slot_id = EQ::invslot::EQUIPMENT_BEGIN; slot_id <= EQ::invslot::EQUIPMENT_END; ++slot_id) { + item = GetItem(slot_id); + if (item && item->ContainsAugmentByID(item_id)) { + quantity += item->CountAugmentByID(item_id); + } + } + + return quantity; +} + +bool EQ::InventoryProfile::HasItemEquippedByID(uint32 item_id) +{ + bool has_equipped = false; + ItemInstance* item = nullptr; + + for (int slot_id = EQ::invslot::EQUIPMENT_BEGIN; slot_id <= EQ::invslot::EQUIPMENT_END; ++slot_id) { + item = GetItem(slot_id); + if (item && item->GetID() == item_id) { + has_equipped = true; + break; + } + } + + return has_equipped; +} + +int EQ::InventoryProfile::CountItemEquippedByID(uint32 item_id) +{ + int quantity = 0; + ItemInstance* item = nullptr; + + for (int slot_id = EQ::invslot::EQUIPMENT_BEGIN; slot_id <= EQ::invslot::EQUIPMENT_END; ++slot_id) { + item = GetItem(slot_id); + if (item && item->GetID() == item_id) { + quantity += item->IsStackable() ? item->GetCharges() : 1; + } + } + + return quantity; +} + //This function has a flaw in that it only returns the last stack that it looked at //when quantity is greater than 1 and not all of quantity can be found in 1 stack. int16 EQ::InventoryProfile::HasItem(uint32 item_id, uint8 quantity, uint8 where) diff --git a/common/inventory_profile.h b/common/inventory_profile.h index 2ff3d0565..c35f5679d 100644 --- a/common/inventory_profile.h +++ b/common/inventory_profile.h @@ -132,7 +132,7 @@ namespace EQ bool SwapItem(int16 source_slot, int16 destination_slot, SwapItemFailState& fail_state, uint16 race_id = 0, uint8 class_id = 0, uint16 deity_id = 0, uint8 level = 0); // Remove item from inventory - bool DeleteItem(int16 slot_id, uint8 quantity = 0); + bool DeleteItem(int16 slot_id, int16 quantity = 0); // Checks All items in a bag for No Drop bool CheckNoDrop(int16 slot_id, bool recurse = true); @@ -140,6 +140,18 @@ namespace EQ // Remove item from inventory (and take control of memory) ItemInstance* PopItem(int16 slot_id); + // Check if player has a specific item equipped by Item ID + bool HasItemEquippedByID(uint32 item_id); + + // Check how many of a specific item the player has equipped by Item ID + int CountItemEquippedByID(uint32 item_id); + + // Check if player has a specific augment equipped by Item ID + bool HasAugmentEquippedByID(uint32 item_id); + + // Check how many of a specific augment the player has equipped by Item ID + int CountAugmentEquippedByID(uint32 item_id); + // Check whether there is space for the specified number of the specified item. bool HasSpaceForItem(const ItemData *ItemToTry, int16 Quantity); @@ -190,6 +202,7 @@ namespace EQ void SetCustomItemData(uint32 character_id, int16 slot_id, std::string identifier, float value); void SetCustomItemData(uint32 character_id, int16 slot_id, std::string identifier, bool value); std::string GetCustomItemData(int16 slot_id, std::string identifier); + static int GetItemStatValue(uint32 item_id, const char* identifier); protected: /////////////////////////////// // Protected Methods diff --git a/common/item_data.h b/common/item_data.h index d3ae5f928..e3123e22f 100644 --- a/common/item_data.h +++ b/common/item_data.h @@ -482,7 +482,7 @@ namespace EQ uint32 Haste; uint32 DamageShield; uint32 RecastDelay; - uint32 RecastType; + int RecastType; uint32 AugDistiller; bool Attuneable; bool NoPet; diff --git a/common/item_instance.cpp b/common/item_instance.cpp index d0bbbca50..cd6d17712 100644 --- a/common/item_instance.cpp +++ b/common/item_instance.cpp @@ -689,6 +689,45 @@ bool EQ::ItemInstance::IsAugmented() return false; } +bool EQ::ItemInstance::ContainsAugmentByID(uint32 item_id) +{ + if (!m_item || !m_item->IsClassCommon()) { + return false; + } + + if (!item_id) { + return false; + } + + for (uint8 augment_slot = invaug::SOCKET_BEGIN; augment_slot <= invaug::SOCKET_END; ++augment_slot) { + if (GetAugmentItemID(augment_slot) == item_id) { + return true; + } + } + + return false; +} + +int EQ::ItemInstance::CountAugmentByID(uint32 item_id) +{ + int quantity = 0; + if (!m_item || !m_item->IsClassCommon()) { + return quantity; + } + + if (!item_id) { + return quantity; + } + + for (uint8 augment_slot = invaug::SOCKET_BEGIN; augment_slot <= invaug::SOCKET_END; ++augment_slot) { + if (GetAugmentItemID(augment_slot) == item_id) { + quantity++; + } + } + + return quantity; +} + // Has attack/delay? bool EQ::ItemInstance::IsWeapon() const { @@ -1706,4 +1745,4 @@ EvolveInfo::EvolveInfo(uint32 first, uint8 max, bool allkills, uint32 L2, uint32 EvolveInfo::~EvolveInfo() { -} +} \ No newline at end of file diff --git a/common/item_instance.h b/common/item_instance.h index 297908a50..c6b97dd78 100644 --- a/common/item_instance.h +++ b/common/item_instance.h @@ -132,6 +132,8 @@ namespace EQ void DeleteAugment(uint8 slot); ItemInstance* RemoveAugment(uint8 index); bool IsAugmented(); + bool ContainsAugmentByID(uint32 item_id); + int CountAugmentByID(uint32 item_id); ItemInstance* GetOrnamentationAug(int32 ornamentationAugtype) const; bool UpdateOrnamentationInfo(); static bool CanTransform(const ItemData *ItemToTry, const ItemData *Container, bool AllowAll = false); diff --git a/common/net/console_server_connection.cpp b/common/net/console_server_connection.cpp index aab26d188..9e245a8f8 100644 --- a/common/net/console_server_connection.cpp +++ b/common/net/console_server_connection.cpp @@ -15,7 +15,7 @@ EQ::Net::ConsoleServerConnection::ConsoleServerConnection(ConsoleServer *parent, memset(m_line, 0, MaxConsoleLineLength); m_accept_messages = false; m_user_id = 0; - m_admin = 0; + m_admin = AccountStatus::Player; m_connection->OnRead(std::bind(&ConsoleServerConnection::OnRead, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); m_connection->OnDisconnect(std::bind(&ConsoleServerConnection::OnDisconnect, this, std::placeholders::_1)); @@ -29,7 +29,7 @@ EQ::Net::ConsoleServerConnection::ConsoleServerConnection(ConsoleServer *parent, if (addr.find("127.0.0.1") != std::string::npos || addr.find("::0") != std::string::npos) { SendLine("Connection established from localhost, assuming admin"); m_status = ConsoleStatusLoggedIn; - m_admin = 255; + m_admin = AccountStatus::Max; SendPrompt(); } else { diff --git a/common/net/servertalk_client_connection.cpp b/common/net/servertalk_client_connection.cpp index 88cfc021a..0c789330d 100644 --- a/common/net/servertalk_client_connection.cpp +++ b/common/net/servertalk_client_connection.cpp @@ -21,32 +21,16 @@ EQ::Net::ServertalkClient::~ServertalkClient() void EQ::Net::ServertalkClient::Send(uint16_t opcode, EQ::Net::Packet &p) { + // pad zero size packets + if (p.Length() == 0) { + p.PutUInt8(0, 0); + } + EQ::Net::DynamicPacket out; -#ifdef ENABLE_SECURITY - if (m_encrypted) { - if (p.Length() == 0) { - p.PutUInt8(0, 0); - } - - out.PutUInt32(0, p.Length() + crypto_secretbox_MACBYTES); - out.PutUInt16(4, opcode); - - std::unique_ptr cipher(new unsigned char[p.Length() + crypto_secretbox_MACBYTES]); - - crypto_box_easy_afternm(&cipher[0], (unsigned char*)p.Data(), p.Length(), m_nonce_ours, m_shared_key); - (*(uint64_t*)&m_nonce_ours[0])++; - out.PutData(6, &cipher[0], p.Length() + crypto_secretbox_MACBYTES); - } - else { - out.PutUInt32(0, p.Length()); - out.PutUInt16(4, opcode); - out.PutPacket(6, p); - } -#else out.PutUInt32(0, p.Length()); out.PutUInt16(4, opcode); out.PutPacket(6, p); -#endif + InternalSend(ServertalkMessage, out); } @@ -87,14 +71,18 @@ void EQ::Net::ServertalkClient::Connect() m_connection = connection; m_connection->OnDisconnect([this](EQ::Net::TCPConnection *c) { LogF(Logs::General, Logs::TCPConnection, "Connection lost to {0}:{1}, attempting to reconnect...", m_addr, m_port); - m_encrypted = false; m_connection.reset(); }); m_connection->OnRead(std::bind(&EQ::Net::ServertalkClient::ProcessData, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); m_connection->Start(); - SendHello(); + SendHandshake(); + + if (m_on_connect_cb) { + m_on_connect_cb(this); + } + m_connecting = false; }); } @@ -188,67 +176,11 @@ void EQ::Net::ServertalkClient::ProcessReadBuffer() void EQ::Net::ServertalkClient::ProcessHello(EQ::Net::Packet &p) { -#ifdef ENABLE_SECURITY - memset(m_public_key_ours, 0, crypto_box_PUBLICKEYBYTES); - memset(m_public_key_theirs, 0, crypto_box_PUBLICKEYBYTES); - memset(m_private_key_ours, 0, crypto_box_SECRETKEYBYTES); - memset(m_nonce_ours, 0, crypto_box_NONCEBYTES); - memset(m_nonce_theirs, 0, crypto_box_NONCEBYTES); - memset(m_shared_key, 0, crypto_box_BEFORENMBYTES); - m_encrypted = false; - try { - bool enc = p.GetInt8(0) == 1 ? true : false; - - if (enc) { - if (p.Length() == (1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES)) { - memcpy(m_public_key_theirs, (char*)p.Data() + 1, crypto_box_PUBLICKEYBYTES); - memcpy(m_nonce_theirs, (char*)p.Data() + 1 + crypto_box_PUBLICKEYBYTES, crypto_box_NONCEBYTES); - m_encrypted = true; - - SendHandshake(); - - if (m_on_connect_cb) { - m_on_connect_cb(this); - } - } - else { - LogError("Could not process hello, size != {0}", 1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES); - } - } - else { - SendHandshake(); - - if (m_on_connect_cb) { - m_on_connect_cb(this); - } - } - } - catch (std::exception &ex) { - LogError("Error parsing hello from server: {0}", ex.what()); - m_connection->Disconnect(); + SendHandshake(); if (m_on_connect_cb) { - m_on_connect_cb(nullptr); - } - } -#else - try { - bool enc = p.GetInt8(0) == 1 ? true : false; - - if (enc) { - SendHandshake(true); - - if (m_on_connect_cb) { - m_on_connect_cb(this); - } - } - else { - SendHandshake(); - - if (m_on_connect_cb) { - m_on_connect_cb(this); - } + m_on_connect_cb(this); } } catch (std::exception &ex) { @@ -259,7 +191,6 @@ void EQ::Net::ServertalkClient::ProcessHello(EQ::Net::Packet &p) m_on_connect_cb(nullptr); } } -#endif } void EQ::Net::ServertalkClient::ProcessMessage(EQ::Net::Packet &p) @@ -269,45 +200,7 @@ void EQ::Net::ServertalkClient::ProcessMessage(EQ::Net::Packet &p) auto opcode = p.GetUInt16(4); if (length > 0) { auto data = p.GetString(6, length); -#ifdef ENABLE_SECURITY - if (m_encrypted) { - size_t message_len = length - crypto_secretbox_MACBYTES; - std::unique_ptr decrypted_text(new unsigned char[message_len]); - if (crypto_box_open_easy_afternm(&decrypted_text[0], (unsigned char*)&data[0], length, m_nonce_theirs, m_shared_key)) - { - LogError("Error decrypting message from server"); - (*(uint64_t*)&m_nonce_theirs[0])++; - return; - } - EQ::Net::StaticPacket decrypted_packet(&decrypted_text[0], message_len); - - (*(uint64_t*)&m_nonce_theirs[0])++; - - auto cb = m_message_callbacks.find(opcode); - if (cb != m_message_callbacks.end()) { - cb->second(opcode, decrypted_packet); - } - - if (m_message_callback) { - m_message_callback(opcode, decrypted_packet); - } - } - else { - size_t message_len = length; - EQ::Net::StaticPacket packet(&data[0], message_len); - - auto cb = m_message_callbacks.find(opcode); - if (cb != m_message_callbacks.end()) { - cb->second(opcode, packet); - } - - if (m_message_callback) { - m_message_callback(opcode, packet); - } - } - -#else size_t message_len = length; EQ::Net::StaticPacket packet(&data[0], message_len); @@ -319,7 +212,6 @@ void EQ::Net::ServertalkClient::ProcessMessage(EQ::Net::Packet &p) if (m_message_callback) { m_message_callback(opcode, packet); } -#endif } } catch (std::exception &ex) { @@ -327,54 +219,11 @@ void EQ::Net::ServertalkClient::ProcessMessage(EQ::Net::Packet &p) } } -void EQ::Net::ServertalkClient::SendHandshake(bool downgrade) +void EQ::Net::ServertalkClient::SendHandshake() { EQ::Net::DynamicPacket handshake; -#ifdef ENABLE_SECURITY - if (m_encrypted) { - crypto_box_keypair(m_public_key_ours, m_private_key_ours); - randombytes_buf(m_nonce_ours, crypto_box_NONCEBYTES); - - crypto_box_beforenm(m_shared_key, m_public_key_theirs, m_private_key_ours); - - handshake.PutData(0, m_public_key_ours, crypto_box_PUBLICKEYBYTES); - handshake.PutData(crypto_box_PUBLICKEYBYTES, m_nonce_ours, crypto_box_NONCEBYTES); - - memset(m_public_key_ours, 0, crypto_box_PUBLICKEYBYTES); - memset(m_public_key_theirs, 0, crypto_box_PUBLICKEYBYTES); - memset(m_private_key_ours, 0, crypto_box_SECRETKEYBYTES); - - size_t cipher_length = m_identifier.length() + 1 + m_credentials.length() + 1 + crypto_secretbox_MACBYTES; - size_t data_length = m_identifier.length() + 1 + m_credentials.length() + 1; - - std::unique_ptr signed_buffer(new unsigned char[cipher_length]); - std::unique_ptr data_buffer(new unsigned char[data_length]); - - memset(&data_buffer[0], 0, data_length); - memcpy(&data_buffer[0], m_identifier.c_str(), m_identifier.length()); - memcpy(&data_buffer[1 + m_identifier.length()], m_credentials.c_str(), m_credentials.length()); - - crypto_box_easy_afternm(&signed_buffer[0], &data_buffer[0], data_length, m_nonce_ours, m_shared_key); - - (*(uint64_t*)&m_nonce_ours[0])++; - - handshake.PutData(crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES, &signed_buffer[0], cipher_length); - } - else { - handshake.PutString(0, m_identifier); - handshake.PutString(m_identifier.length() + 1, m_credentials); - handshake.PutUInt8(m_identifier.length() + 1 + m_credentials.length(), 0); - } -#else handshake.PutString(0, m_identifier); handshake.PutString(m_identifier.length() + 1, m_credentials); handshake.PutUInt8(m_identifier.length() + 1 + m_credentials.length(), 0); -#endif - - if (downgrade) { - InternalSend(ServertalkClientDowngradeSecurityHandshake, handshake); - } - else { - InternalSend(ServertalkClientHandshake, handshake); - } + InternalSend(ServertalkClientDowngradeSecurityHandshake, handshake); } diff --git a/common/net/servertalk_client_connection.h b/common/net/servertalk_client_connection.h index ad5c24d65..704b48ded 100644 --- a/common/net/servertalk_client_connection.h +++ b/common/net/servertalk_client_connection.h @@ -4,9 +4,6 @@ #include "../event/timer.h" #include "servertalk_common.h" #include "packet.h" -#ifdef ENABLE_SECURITY -#include -#endif namespace EQ { @@ -34,8 +31,7 @@ namespace EQ void ProcessReadBuffer(); void ProcessHello(EQ::Net::Packet &p); void ProcessMessage(EQ::Net::Packet &p); - void SendHandshake() { SendHandshake(false); } - void SendHandshake(bool downgrade); + void SendHandshake(); std::unique_ptr m_timer; @@ -45,23 +41,11 @@ namespace EQ bool m_connecting; int m_port; bool m_ipv6; - bool m_encrypted; std::shared_ptr m_connection; std::vector m_buffer; std::unordered_map> m_message_callbacks; std::function m_message_callback; std::function m_on_connect_cb; - -#ifdef ENABLE_SECURITY - unsigned char m_public_key_ours[crypto_box_PUBLICKEYBYTES]; - unsigned char m_private_key_ours[crypto_box_SECRETKEYBYTES]; - unsigned char m_nonce_ours[crypto_box_NONCEBYTES]; - - unsigned char m_public_key_theirs[crypto_box_PUBLICKEYBYTES]; - unsigned char m_nonce_theirs[crypto_box_NONCEBYTES]; - - unsigned char m_shared_key[crypto_box_BEFORENMBYTES]; -#endif }; } } diff --git a/common/net/servertalk_common.h b/common/net/servertalk_common.h index f30d21b00..aa779b5ed 100644 --- a/common/net/servertalk_common.h +++ b/common/net/servertalk_common.h @@ -15,4 +15,4 @@ namespace EQ ServertalkMessage, }; } -} \ No newline at end of file +} diff --git a/common/net/servertalk_server.cpp b/common/net/servertalk_server.cpp index 297d4cd31..d362362d3 100644 --- a/common/net/servertalk_server.cpp +++ b/common/net/servertalk_server.cpp @@ -10,12 +10,10 @@ EQ::Net::ServertalkServer::~ServertalkServer() void EQ::Net::ServertalkServer::Listen(const ServertalkServerOptions& opts) { - m_encrypted = opts.encrypted; m_credentials = opts.credentials; - m_allow_downgrade = opts.allow_downgrade; m_server = std::make_unique(); m_server->Listen(opts.port, opts.ipv6, [this](std::shared_ptr connection) { - m_unident_connections.push_back(std::make_shared(connection, this, m_encrypted, m_allow_downgrade)); + m_unident_connections.push_back(std::make_shared(connection, this)); }); } diff --git a/common/net/servertalk_server.h b/common/net/servertalk_server.h index e6062d847..f58243e98 100644 --- a/common/net/servertalk_server.h +++ b/common/net/servertalk_server.h @@ -5,10 +5,6 @@ #include #include -#ifdef ENABLE_SECURITY -#include -#endif - namespace EQ { namespace Net @@ -17,18 +13,9 @@ namespace EQ { int port; bool ipv6; - bool encrypted; - bool allow_downgrade; std::string credentials; ServertalkServerOptions() { -#ifdef ENABLE_SECURITY - encrypted = true; - allow_downgrade = true; -#else - encrypted = false; - allow_downgrade = true; -#endif ipv6 = false; } }; diff --git a/common/net/servertalk_server_connection.cpp b/common/net/servertalk_server_connection.cpp index 6bfae6ea1..f3823cda6 100644 --- a/common/net/servertalk_server_connection.cpp +++ b/common/net/servertalk_server_connection.cpp @@ -3,16 +3,15 @@ #include "../eqemu_logsys.h" #include "../util/uuid.h" -EQ::Net::ServertalkServerConnection::ServertalkServerConnection(std::shared_ptr c, EQ::Net::ServertalkServer *parent, bool encrypted, bool allow_downgrade) +EQ::Net::ServertalkServerConnection::ServertalkServerConnection(std::shared_ptr c, EQ::Net::ServertalkServer *parent) { m_connection = c; m_parent = parent; - m_encrypted = encrypted; - m_allow_downgrade = allow_downgrade; m_uuid = EQ::Util::UUID::Generate().ToString(); m_connection->OnRead(std::bind(&ServertalkServerConnection::OnRead, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); m_connection->OnDisconnect(std::bind(&ServertalkServerConnection::OnDisconnect, this, std::placeholders::_1)); m_connection->Start(); + m_legacy_mode = false; } EQ::Net::ServertalkServerConnection::~ServertalkServerConnection() @@ -21,32 +20,73 @@ EQ::Net::ServertalkServerConnection::~ServertalkServerConnection() void EQ::Net::ServertalkServerConnection::Send(uint16_t opcode, EQ::Net::Packet & p) { - EQ::Net::DynamicPacket out; -#ifdef ENABLE_SECURITY - if (m_encrypted) { - if (p.Length() == 0) { + if (m_legacy_mode) { + if (!m_connection) + return; + + if (opcode == ServerOP_UsertoWorldReq) { + auto req_in = (UsertoWorldRequest_Struct*)p.Data(); + + EQ::Net::DynamicPacket req; + size_t i = 0; + req.PutUInt32(i, req_in->lsaccountid); i += 4; + req.PutUInt32(i, req_in->worldid); i += 4; + req.PutUInt32(i, req_in->FromID); i += 4; + req.PutUInt32(i, req_in->ToID); i += 4; + req.PutData(i, req_in->IPAddr, 64); i += 64; + + EQ::Net::DynamicPacket out; + out.PutUInt16(0, ServerOP_UsertoWorldReqLeg); + out.PutUInt16(2, req.Length() + 4); + out.PutPacket(4, req); + + m_connection->Write((const char*)out.Data(), out.Length()); + return; + } + + if (opcode == ServerOP_LSClientAuth) { + auto req_in = (ClientAuth_Struct*)p.Data(); + + EQ::Net::DynamicPacket req; + size_t i = 0; + req.PutUInt32(i, req_in->loginserver_account_id); i += 4; + req.PutData(i, req_in->account_name, 30); i += 30; + req.PutData(i, req_in->key, 30); i += 30; + req.PutUInt8(i, req_in->lsadmin); i += 1; + req.PutUInt16(i, req_in->is_world_admin); i += 2; + req.PutUInt32(i, req_in->ip); i += 4; + req.PutUInt8(i, req_in->is_client_from_local_network); i += 1; + + EQ::Net::DynamicPacket out; + out.PutUInt16(0, ServerOP_LSClientAuthLeg); + out.PutUInt16(2, req.Length() + 4); + out.PutPacket(4, req); + + m_connection->Write((const char*)out.Data(), out.Length()); + return; + } + + EQ::Net::DynamicPacket out; + out.PutUInt16(0, opcode); + out.PutUInt16(2, p.Length() + 4); + out.PutPacket(4, p); + + m_connection->Write((const char*)out.Data(), out.Length()); + } else { + // pad zero size packets + // pad packets that would cause a collision with legacy identification code + // It's unlikely we'd send a 4MB msg for any reason but just incase. + if (p.Length() == 0 || p.Length() == 43061256) { p.PutUInt8(0, 0); } - out.PutUInt32(0, p.Length() + crypto_secretbox_MACBYTES); - out.PutUInt16(4, opcode); - - std::unique_ptr cipher(new unsigned char[p.Length() + crypto_secretbox_MACBYTES]); - crypto_box_easy_afternm(&cipher[0], (unsigned char*)p.Data(), p.Length(), m_nonce_ours, m_shared_key); - (*(uint64_t*)&m_nonce_ours[0])++; - out.PutData(6, &cipher[0], p.Length() + crypto_secretbox_MACBYTES); - } - else { + EQ::Net::DynamicPacket out; out.PutUInt32(0, p.Length()); out.PutUInt16(4, opcode); out.PutPacket(6, p); + + InternalSend(ServertalkMessage, out); } -#else - out.PutUInt32(0, p.Length()); - out.PutUInt16(4, opcode); - out.PutPacket(6, p); -#endif - InternalSend(ServertalkMessage, out); } void EQ::Net::ServertalkServerConnection::SendPacket(ServerPacket *p) @@ -71,17 +111,41 @@ void EQ::Net::ServertalkServerConnection::OnMessage(std::functionConnectionIdentified(this); + ProcessOldReadBuffer(); + return; + } + /* //header: //uint32 length; @@ -109,6 +173,7 @@ void EQ::Net::ServertalkServerConnection::ProcessReadBuffer() } break; case ServertalkClientHandshake: + case ServertalkClientDowngradeSecurityHandshake: ProcessHandshake(p); break; case ServertalkMessage: @@ -125,10 +190,8 @@ void EQ::Net::ServertalkServerConnection::ProcessReadBuffer() } break; case ServertalkClientHandshake: - ProcessHandshake(p); - break; case ServertalkClientDowngradeSecurityHandshake: - ProcessHandshake(p, true); + ProcessHandshake(p); break; case ServertalkMessage: ProcessMessage(p); @@ -147,6 +210,57 @@ void EQ::Net::ServertalkServerConnection::ProcessReadBuffer() } } +void EQ::Net::ServertalkServerConnection::ProcessOldReadBuffer() +{ + size_t current = 0; + size_t total = m_buffer.size(); + + while (current < total) { + auto left = total - current; + + /* + //header: + //uint32 length; + //uint8 type; + */ + uint16_t length = 0; + uint16_t opcode = 0; + if (left < 4) { + break; + } + + opcode = *(uint16_t*)&m_buffer[current]; + length = *(uint16_t*)&m_buffer[current + 2]; + if (length < 4) { + break; + } + + length -= 4; + + if (current + 4 + length > total) { + break; + } + + if (length == 0) { + EQ::Net::DynamicPacket p; + ProcessMessageOld(opcode, p); + } + else { + EQ::Net::StaticPacket p(&m_buffer[current + 4], length); + ProcessMessageOld(opcode, p); + } + + current += length + 4; + } + + if (current == total) { + m_buffer.clear(); + } + else { + m_buffer.erase(m_buffer.begin(), m_buffer.begin() + current); + } +} + void EQ::Net::ServertalkServerConnection::OnDisconnect(TCPConnection *c) { m_parent->ConnectionDisconnected(this); @@ -155,36 +269,14 @@ void EQ::Net::ServertalkServerConnection::OnDisconnect(TCPConnection *c) void EQ::Net::ServertalkServerConnection::SendHello() { EQ::Net::DynamicPacket hello; - -#ifdef ENABLE_SECURITY - memset(m_public_key_ours, 0, crypto_box_PUBLICKEYBYTES); - memset(m_public_key_theirs, 0, crypto_box_PUBLICKEYBYTES); - memset(m_private_key_ours, 0, crypto_box_SECRETKEYBYTES); - memset(m_nonce_ours, 0, crypto_box_NONCEBYTES); - memset(m_nonce_theirs, 0, crypto_box_NONCEBYTES); - - if (m_encrypted) { - hello.PutInt8(0, 1); - - crypto_box_keypair(m_public_key_ours, m_private_key_ours); - randombytes_buf(m_nonce_ours, crypto_box_NONCEBYTES); - - hello.PutData(1, m_public_key_ours, crypto_box_PUBLICKEYBYTES); - hello.PutData(1 + crypto_box_PUBLICKEYBYTES, m_nonce_ours, crypto_box_NONCEBYTES); - } - else { - hello.PutInt8(0, 0); - } -#else hello.PutInt8(0, 0); -#endif InternalSend(ServertalkServerHello, hello); } void EQ::Net::ServertalkServerConnection::InternalSend(ServertalkPacketType type, EQ::Net::Packet &p) { - if (!m_connection) + if (!m_connection || m_legacy_mode) return; EQ::Net::DynamicPacket out; @@ -197,71 +289,8 @@ void EQ::Net::ServertalkServerConnection::InternalSend(ServertalkPacketType type m_connection->Write((const char*)out.Data(), out.Length()); } -void EQ::Net::ServertalkServerConnection::ProcessHandshake(EQ::Net::Packet &p, bool downgrade_security) +void EQ::Net::ServertalkServerConnection::ProcessHandshake(EQ::Net::Packet &p) { -#ifdef ENABLE_SECURITY - if (downgrade_security && m_allow_downgrade && m_encrypted) { - LogF(Logs::General, Logs::TCPConnection, "Downgraded encrypted connection to plaintext because otherside didn't support encryption {0}:{1}", - m_connection->RemoteIP(), m_connection->RemotePort()); - m_encrypted = false; - } - - if (m_encrypted) { - try { - if (p.Length() > (crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES)) { - memcpy(m_public_key_theirs, (char*)p.Data(), crypto_box_PUBLICKEYBYTES); - memcpy(m_nonce_theirs, (char*)p.Data() + crypto_box_PUBLICKEYBYTES, crypto_box_NONCEBYTES); - - crypto_box_beforenm(m_shared_key, m_public_key_theirs, m_private_key_ours); - - size_t cipher_len = p.Length() - crypto_box_PUBLICKEYBYTES - crypto_box_NONCEBYTES; - size_t message_len = cipher_len - crypto_secretbox_MACBYTES; - std::unique_ptr decrypted_text(new unsigned char[message_len]); - - if (crypto_box_open_easy_afternm(&decrypted_text[0], (unsigned char*)p.Data() + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES, cipher_len, m_nonce_theirs, m_shared_key)) - { - LogError("Error decrypting handshake from client, dropping connection."); - m_connection->Disconnect(); - return; - } - - m_identifier = (const char*)&decrypted_text[0]; - std::string credentials = (const char*)&decrypted_text[0] + (m_identifier.length() + 1); - - if (!m_parent->CheckCredentials(credentials)) { - LogError("Got incoming connection with invalid credentials during handshake, dropping connection."); - m_connection->Disconnect(); - return; - } - - m_parent->ConnectionIdentified(this); - (*(uint64_t*)&m_nonce_theirs[0])++; - } - } - catch (std::exception &ex) { - LogError("Error parsing handshake from client: {0}", ex.what()); - m_connection->Disconnect(); - } - } - else { - try { - m_identifier = p.GetCString(0); - auto credentials = p.GetCString(m_identifier.length() + 1); - - if (!m_parent->CheckCredentials(credentials)) { - LogError("Got incoming connection with invalid credentials during handshake, dropping connection."); - m_connection->Disconnect(); - return; - } - - m_parent->ConnectionIdentified(this); - } - catch (std::exception &ex) { - LogError("Error parsing handshake from client: {0}", ex.what()); - m_connection->Disconnect(); - } - } -#else try { m_identifier = p.GetCString(0); auto credentials = p.GetCString(m_identifier.length() + 1); @@ -278,7 +307,6 @@ void EQ::Net::ServertalkServerConnection::ProcessHandshake(EQ::Net::Packet &p, b LogError("Error parsing handshake from client: {0}", ex.what()); m_connection->Disconnect(); } -#endif } void EQ::Net::ServertalkServerConnection::ProcessMessage(EQ::Net::Packet &p) @@ -288,46 +316,6 @@ void EQ::Net::ServertalkServerConnection::ProcessMessage(EQ::Net::Packet &p) auto opcode = p.GetUInt16(4); if (length > 0) { auto data = p.GetString(6, length); -#ifdef ENABLE_SECURITY - if (m_encrypted) { - size_t message_len = length - crypto_secretbox_MACBYTES; - std::unique_ptr decrypted_text(new unsigned char[message_len]); - - if (crypto_box_open_easy_afternm(&decrypted_text[0], (unsigned char*)&data[0], length, m_nonce_theirs, m_shared_key)) - { - LogError("Error decrypting message from client"); - (*(uint64_t*)&m_nonce_theirs[0])++; - return; - } - - EQ::Net::StaticPacket decrypted_packet(&decrypted_text[0], message_len); - - (*(uint64_t*)&m_nonce_theirs[0])++; - - auto cb = m_message_callbacks.find(opcode); - if (cb != m_message_callbacks.end()) { - cb->second(opcode, decrypted_packet); - } - - if (m_message_callback) { - m_message_callback(opcode, decrypted_packet); - } - } - else { - size_t message_len = length; - EQ::Net::StaticPacket packet(&data[0], message_len); - - auto cb = m_message_callbacks.find(opcode); - if (cb != m_message_callbacks.end()) { - cb->second(opcode, packet); - } - - if (m_message_callback) { - m_message_callback(opcode, packet); - } - } - -#else size_t message_len = length; EQ::Net::StaticPacket packet(&data[0], message_len); @@ -339,10 +327,26 @@ void EQ::Net::ServertalkServerConnection::ProcessMessage(EQ::Net::Packet &p) if (m_message_callback) { m_message_callback(opcode, packet); } -#endif } } catch (std::exception &ex) { LogError("Error parsing message from client: {0}", ex.what()); } } + +void EQ::Net::ServertalkServerConnection::ProcessMessageOld(uint16_t opcode, EQ::Net::Packet &p) +{ + try { + auto cb = m_message_callbacks.find(opcode); + if (cb != m_message_callbacks.end()) { + cb->second(opcode, p); + } + + if (m_message_callback) { + m_message_callback(opcode, p); + } + } + catch (std::exception &ex) { + LogError("Error parsing legacy message from client: {0}", ex.what()); + } +} diff --git a/common/net/servertalk_server_connection.h b/common/net/servertalk_server_connection.h index 112a8f2a6..8e262baa1 100644 --- a/common/net/servertalk_server_connection.h +++ b/common/net/servertalk_server_connection.h @@ -4,9 +4,6 @@ #include "servertalk_common.h" #include "packet.h" #include -#ifdef ENABLE_SECURITY -#include -#endif namespace EQ { @@ -16,7 +13,7 @@ namespace EQ class ServertalkServerConnection { public: - ServertalkServerConnection(std::shared_ptr c, ServertalkServer *parent, bool encrypted, bool allow_downgrade); + ServertalkServerConnection(std::shared_ptr c, ServertalkServer *parent); ~ServertalkServerConnection(); void Send(uint16_t opcode, EQ::Net::Packet &p); @@ -30,12 +27,13 @@ namespace EQ private: void OnRead(TCPConnection* c, const unsigned char* data, size_t sz); void ProcessReadBuffer(); + void ProcessOldReadBuffer(); void OnDisconnect(TCPConnection* c); void SendHello(); void InternalSend(ServertalkPacketType type, EQ::Net::Packet &p); - void ProcessHandshake(EQ::Net::Packet &p) { ProcessHandshake(p, false); } - void ProcessHandshake(EQ::Net::Packet &p, bool security_downgrade); + void ProcessHandshake(EQ::Net::Packet &p); void ProcessMessage(EQ::Net::Packet &p); + void ProcessMessageOld(uint16_t opcode, EQ::Net::Packet &p); std::shared_ptr m_connection; ServertalkServer *m_parent; @@ -45,19 +43,7 @@ namespace EQ std::function m_message_callback; std::string m_identifier; std::string m_uuid; - - bool m_encrypted; - bool m_allow_downgrade; -#ifdef ENABLE_SECURITY - unsigned char m_public_key_ours[crypto_box_PUBLICKEYBYTES]; - unsigned char m_private_key_ours[crypto_box_SECRETKEYBYTES]; - unsigned char m_nonce_ours[crypto_box_NONCEBYTES]; - - unsigned char m_public_key_theirs[crypto_box_PUBLICKEYBYTES]; - unsigned char m_nonce_theirs[crypto_box_NONCEBYTES]; - - unsigned char m_shared_key[crypto_box_BEFORENMBYTES]; -#endif + bool m_legacy_mode; }; } } diff --git a/common/net/websocket_server.cpp b/common/net/websocket_server.cpp index dda1a4a96..f74f437ba 100644 --- a/common/net/websocket_server.cpp +++ b/common/net/websocket_server.cpp @@ -5,6 +5,7 @@ #include #include #include +#include "../emu_constants.h" struct MethodHandlerEntry { @@ -174,13 +175,13 @@ Json::Value EQ::Net::WebsocketServer::Login(WebsocketServerConnection *connectio auto r = _impl->login_handler(connection, user, pass); if (r.logged_in) { - connection->SetAuthorized(true, r.account_name, r.account_id, 255); + connection->SetAuthorized(true, r.account_name, r.account_id, AccountStatus::Max); ret["status"] = "Ok"; } else if (user == "admin" && (connection->RemoteIP() == "127.0.0.1" || connection->RemoteIP() == "::")) { r.logged_in = true; r.account_id = 0; - connection->SetAuthorized(true, r.account_name, r.account_id, 255); + connection->SetAuthorized(true, r.account_name, r.account_id, AccountStatus::Max); ret["status"] = "Ok"; } else { diff --git a/common/opcode_dispatch.h b/common/opcode_dispatch.h index 496dcdea3..a19cb79a3 100644 --- a/common/opcode_dispatch.h +++ b/common/opcode_dispatch.h @@ -115,8 +115,8 @@ IN(OP_GMTraining, GMTrainee_Struct); IN(OP_GMEndTraining, GMTrainEnd_Struct); IN(OP_GMTrainSkill, GMSkillChange_Struct); IN(OP_RequestDuel, Duel_Struct); -IN(OP_DuelResponse, DuelResponse_Struct); -IN(OP_DuelResponse2, Duel_Struct); +IN(OP_DuelDecline, DuelResponse_Struct); +IN(OP_DuelAccept, Duel_Struct); IN(OP_SpawnAppearance, SpawnAppearance_Struct); IN(OP_BazaarInspect, BazaarInspect_Struct); IN(OP_Death, Death_Struct); diff --git a/common/opcode_map.cpp b/common/opcode_map.cpp index 7c8fed281..c0556cf2a 100644 --- a/common/opcode_map.cpp +++ b/common/opcode_map.cpp @@ -240,8 +240,8 @@ void load_opcode_names() opcode_map[0x00a1] = "LiveOP_SaveOnZoneReq"; opcode_map[0x0185] = "LiveOP_Logout"; opcode_map[0x0298] = "LiveOP_RequestDuel"; - opcode_map[0x0a5d] = "LiveOP_DuelResponse"; - opcode_map[0x016e] = "LiveOP_DuelResponse2"; + opcode_map[0x0a5d] = "LiveOP_DuelDecline"; + opcode_map[0x016e] = "LiveOP_DuelAccept"; opcode_map[0x007c] = "LiveOP_InstillDoubt"; opcode_map[0x00ac] = "LiveOP_SafeFallSuccess"; opcode_map[0x02fb] = "LiveOP_DisciplineUpdate"; diff --git a/common/patches/rof.cpp b/common/patches/rof.cpp index cb5400f1f..908ecc26b 100644 --- a/common/patches/rof.cpp +++ b/common/patches/rof.cpp @@ -772,13 +772,13 @@ namespace RoF ENCODE(OP_DzExpeditionInfo) { - ENCODE_LENGTH_EXACT(ExpeditionInfo_Struct); - SETUP_DIRECT_ENCODE(ExpeditionInfo_Struct, structs::ExpeditionInfo_Struct); + ENCODE_LENGTH_EXACT(DynamicZoneInfo_Struct); + SETUP_DIRECT_ENCODE(DynamicZoneInfo_Struct, structs::DynamicZoneInfo_Struct); OUT(client_id); OUT(assigned); OUT(max_players); - strn0cpy(eq->expedition_name, emu->expedition_name, sizeof(eq->expedition_name)); + strn0cpy(eq->dz_name, emu->dz_name, sizeof(eq->dz_name)); strn0cpy(eq->leader_name, emu->leader_name, sizeof(eq->leader_name)); FINISH_ENCODE(); @@ -824,8 +824,8 @@ namespace RoF ENCODE(OP_DzSetLeaderName) { - ENCODE_LENGTH_EXACT(ExpeditionSetLeaderName_Struct); - SETUP_DIRECT_ENCODE(ExpeditionSetLeaderName_Struct, structs::ExpeditionSetLeaderName_Struct); + ENCODE_LENGTH_EXACT(DynamicZoneLeaderName_Struct); + SETUP_DIRECT_ENCODE(DynamicZoneLeaderName_Struct, structs::DynamicZoneLeaderName_Struct); OUT(client_id); strn0cpy(eq->leader_name, emu->leader_name, sizeof(eq->leader_name)); @@ -835,7 +835,7 @@ namespace RoF ENCODE(OP_DzMemberList) { - SETUP_VAR_ENCODE(ExpeditionMemberList_Struct); + SETUP_VAR_ENCODE(DynamicZoneMemberList_Struct); SerializeBuffer buf; buf.WriteUInt32(emu->client_id); @@ -843,7 +843,7 @@ namespace RoF for (uint32 i = 0; i < emu->member_count; ++i) { buf.WriteString(emu->members[i].name); - buf.WriteUInt8(emu->members[i].expedition_status); + buf.WriteUInt8(emu->members[i].online_status); } __packet->size = buf.size(); @@ -855,8 +855,8 @@ namespace RoF ENCODE(OP_DzMemberListName) { - ENCODE_LENGTH_EXACT(ExpeditionMemberListName_Struct); - SETUP_DIRECT_ENCODE(ExpeditionMemberListName_Struct, structs::ExpeditionMemberListName_Struct); + ENCODE_LENGTH_EXACT(DynamicZoneMemberListName_Struct); + SETUP_DIRECT_ENCODE(DynamicZoneMemberListName_Struct, structs::DynamicZoneMemberListName_Struct); OUT(client_id); OUT(add_name); @@ -867,7 +867,7 @@ namespace RoF ENCODE(OP_DzMemberListStatus) { - auto emu = reinterpret_cast((*p)->pBuffer); + auto emu = reinterpret_cast((*p)->pBuffer); if (emu->member_count == 1) { ENCODE_FORWARD(OP_DzMemberList); @@ -1634,20 +1634,6 @@ namespace RoF FINISH_ENCODE(); } - ENCODE(OP_ManaChange) - { - ENCODE_LENGTH_EXACT(ManaChange_Struct); - SETUP_DIRECT_ENCODE(ManaChange_Struct, structs::ManaChange_Struct); - - OUT(new_mana); - OUT(stamina); - OUT(spell_id); - OUT(keepcasting); - eq->slot = -1; // this is spell gem slot. It's -1 in normal operation - - FINISH_ENCODE(); - } - ENCODE(OP_MercenaryDataResponse) { //consume the packet @@ -1863,8 +1849,8 @@ namespace RoF /*fill in some unknowns with observed values, hopefully it will help */ eq->unknown800 = -1; eq->unknown844 = 600; - eq->unknown880 = 50; - eq->unknown884 = 10; + OUT(LavaDamage); + OUT(MinLavaDamage); eq->unknown888 = 1; eq->unknown889 = 0; eq->unknown890 = 1; @@ -1968,7 +1954,7 @@ namespace RoF for (int r = 0; r < 5; r++) { - outapp->WriteUInt32(emu->binds[r].zoneId); + outapp->WriteUInt32(emu->binds[r].zone_id); outapp->WriteFloat(emu->binds[r].x); outapp->WriteFloat(emu->binds[r].y); outapp->WriteFloat(emu->binds[r].z); diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index 4a902061d..e6404a62a 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -821,13 +821,13 @@ namespace RoF2 ENCODE(OP_DzExpeditionInfo) { - ENCODE_LENGTH_EXACT(ExpeditionInfo_Struct); - SETUP_DIRECT_ENCODE(ExpeditionInfo_Struct, structs::ExpeditionInfo_Struct); + ENCODE_LENGTH_EXACT(DynamicZoneInfo_Struct); + SETUP_DIRECT_ENCODE(DynamicZoneInfo_Struct, structs::DynamicZoneInfo_Struct); OUT(client_id); OUT(assigned); OUT(max_players); - strn0cpy(eq->expedition_name, emu->expedition_name, sizeof(eq->expedition_name)); + strn0cpy(eq->dz_name, emu->dz_name, sizeof(eq->dz_name)); strn0cpy(eq->leader_name, emu->leader_name, sizeof(eq->leader_name)); FINISH_ENCODE(); @@ -873,8 +873,8 @@ namespace RoF2 ENCODE(OP_DzSetLeaderName) { - ENCODE_LENGTH_EXACT(ExpeditionSetLeaderName_Struct); - SETUP_DIRECT_ENCODE(ExpeditionSetLeaderName_Struct, structs::ExpeditionSetLeaderName_Struct); + ENCODE_LENGTH_EXACT(DynamicZoneLeaderName_Struct); + SETUP_DIRECT_ENCODE(DynamicZoneLeaderName_Struct, structs::DynamicZoneLeaderName_Struct); OUT(client_id); strn0cpy(eq->leader_name, emu->leader_name, sizeof(eq->leader_name)); @@ -884,7 +884,7 @@ namespace RoF2 ENCODE(OP_DzMemberList) { - SETUP_VAR_ENCODE(ExpeditionMemberList_Struct); + SETUP_VAR_ENCODE(DynamicZoneMemberList_Struct); SerializeBuffer buf; buf.WriteUInt32(emu->client_id); @@ -892,7 +892,7 @@ namespace RoF2 for (uint32 i = 0; i < emu->member_count; ++i) { buf.WriteString(emu->members[i].name); - buf.WriteUInt8(emu->members[i].expedition_status); + buf.WriteUInt8(emu->members[i].online_status); } __packet->size = buf.size(); @@ -904,8 +904,8 @@ namespace RoF2 ENCODE(OP_DzMemberListName) { - ENCODE_LENGTH_EXACT(ExpeditionMemberListName_Struct); - SETUP_DIRECT_ENCODE(ExpeditionMemberListName_Struct, structs::ExpeditionMemberListName_Struct); + ENCODE_LENGTH_EXACT(DynamicZoneMemberListName_Struct); + SETUP_DIRECT_ENCODE(DynamicZoneMemberListName_Struct, structs::DynamicZoneMemberListName_Struct); OUT(client_id); OUT(add_name); @@ -916,7 +916,7 @@ namespace RoF2 ENCODE(OP_DzMemberListStatus) { - auto emu = reinterpret_cast((*p)->pBuffer); + auto emu = reinterpret_cast((*p)->pBuffer); if (emu->member_count == 1) { ENCODE_FORWARD(OP_DzMemberList); @@ -1683,20 +1683,6 @@ namespace RoF2 FINISH_ENCODE(); } - ENCODE(OP_ManaChange) - { - ENCODE_LENGTH_EXACT(ManaChange_Struct); - SETUP_DIRECT_ENCODE(ManaChange_Struct, structs::ManaChange_Struct); - - OUT(new_mana); - OUT(stamina); - OUT(spell_id); - OUT(keepcasting); - eq->slot = -1; // this is spell gem slot. It's -1 in normal operation - - FINISH_ENCODE(); - } - ENCODE(OP_MercenaryDataResponse) { //consume the packet @@ -1919,8 +1905,8 @@ namespace RoF2 eq->SkyRelated2 = -1; eq->NPCAggroMaxDist = 600; eq->FilterID = 2008; // Guild Lobby observed value - eq->LavaDamage = 50; - eq->MinLavaDamage = 10; + OUT(LavaDamage); + OUT(MinLavaDamage); eq->bDisallowManaStone = 1; eq->bNoBind = 0; eq->bNoAttack = 0; @@ -2025,7 +2011,7 @@ namespace RoF2 for (int r = 0; r < 5; r++) { - outapp->WriteUInt32(emu->binds[r].zoneId); + outapp->WriteUInt32(emu->binds[r].zone_id); outapp->WriteFloat(emu->binds[r].x); outapp->WriteFloat(emu->binds[r].y); outapp->WriteFloat(emu->binds[r].z); diff --git a/common/patches/rof2_ops.h b/common/patches/rof2_ops.h index 22b6e6662..130686cdf 100644 --- a/common/patches/rof2_ops.h +++ b/common/patches/rof2_ops.h @@ -93,7 +93,6 @@ E(OP_ItemVerifyReply) E(OP_LeadershipExpUpdate) E(OP_LogServer) E(OP_LootItem) -E(OP_ManaChange) E(OP_MercenaryDataResponse) E(OP_MercenaryDataUpdate) E(OP_MoveItem) diff --git a/common/patches/rof2_structs.h b/common/patches/rof2_structs.h index c5111da6d..5fa9a8c5f 100644 --- a/common/patches/rof2_structs.h +++ b/common/patches/rof2_structs.h @@ -1053,7 +1053,7 @@ struct LeadershipAA_Struct { * Size: 20 Octets */ struct BindStruct { - /*000*/ uint32 zoneId; + /*000*/ uint32 zone_id; /*004*/ float x; /*008*/ float y; /*012*/ float z; @@ -2084,7 +2084,7 @@ struct GMZoneRequest_Struct { /*0068*/ float x; /*0072*/ float y; /*0076*/ float z; -/*0080*/ char unknown0080[4]; +/*0080*/ float heading; /*0084*/ uint32 success; // 0 if command failed, 1 if succeeded? /*0088*/ // /*072*/ int8 success; // =0 client->server, =1 server->client, -X=specific error @@ -3061,7 +3061,7 @@ struct EnvDamage2_Struct { /*0006*/ uint32 damage; /*0010*/ float unknown10; // New to Underfoot - Seen 1 /*0014*/ uint8 unknown14[12]; -/*0026*/ uint8 dmgtype; // FA = Lava; FC = Falling +/*0026*/ uint8 dmgtype; // FA = Lava, FB = Drowning, FC = Falling, FD = Trap /*0027*/ uint8 unknown27[4]; /*0031*/ uint16 unknown31; // New to Underfoot - Seen 66 /*0033*/ uint16 constant; // Always FFFF @@ -4361,8 +4361,8 @@ struct UseAA_Struct { struct AA_Ability { /*00*/ uint32 skill_id; -/*04*/ uint32 base1; -/*08*/ uint32 base2; +/*04*/ uint32 base_value; +/*08*/ uint32 limit_value; /*12*/ uint32 slot; /*16*/ }; @@ -4908,31 +4908,31 @@ struct ExpeditionInviteResponse_Struct /*079*/ uint8 unknown079; // padding garbage? }; -struct ExpeditionInfo_Struct +struct DynamicZoneInfo_Struct { /*000*/ uint32 client_id; /*004*/ uint32 unknown004; -/*008*/ uint32 assigned; // padded bool, 0: not in expedition (clear data), 1: in expedition +/*008*/ uint32 assigned; // padded bool, 0: clear info, 1: fill window info /*012*/ uint32 max_players; -/*016*/ char expedition_name[128]; +/*016*/ char dz_name[128]; /*144*/ char leader_name[64]; -//*208*/ uint32 unknown208; // live sends 01 00 00 00 here but client doesn't read it +//*208*/ uint32 dz_type; // only in newer clients, if not 1 (expedition type) window does not auto show when dz info assigned }; -struct ExpeditionMemberEntry_Struct +struct DynamicZoneMemberEntry_Struct { -/*000*/ char name[1]; // variable length, null terminated, max 0x40 (64) -/*000*/ uint8 expedition_status; // 0: unknown 1: Online, 2: Offline, 3: In Dynamic Zone, 4: Link Dead +/*000*/ char name[1]; // variable length, null terminated, max 0x40 (64) +/*000*/ uint8 online_status; // 0: unknown 1: Online, 2: Offline, 3: In Dynamic Zone, 4: Link Dead }; -struct ExpeditionMemberList_Struct +struct DynamicZoneMemberList_Struct { /*000*/ uint32 client_id; /*004*/ uint32 member_count; // number of players in window -/*008*/ ExpeditionMemberEntry_Struct members[0]; // variable length +/*008*/ DynamicZoneMemberEntry_Struct members[0]; // variable length }; -struct ExpeditionMemberListName_Struct +struct DynamicZoneMemberListName_Struct { /*000*/ uint32 client_id; /*004*/ uint32 unknown004; @@ -4955,7 +4955,7 @@ struct ExpeditionLockoutTimers_Struct /*008*/ ExpeditionLockoutTimerEntry_Struct timers[0]; }; -struct ExpeditionSetLeaderName_Struct +struct DynamicZoneLeaderName_Struct { /*000*/ uint32 client_id; /*004*/ uint32 unknown004; diff --git a/common/patches/rof_ops.h b/common/patches/rof_ops.h index f06834d21..7cca08e61 100644 --- a/common/patches/rof_ops.h +++ b/common/patches/rof_ops.h @@ -79,7 +79,6 @@ E(OP_ItemVerifyReply) E(OP_LeadershipExpUpdate) E(OP_LogServer) E(OP_LootItem) -E(OP_ManaChange) E(OP_MercenaryDataResponse) E(OP_MercenaryDataUpdate) E(OP_MoveItem) diff --git a/common/patches/rof_structs.h b/common/patches/rof_structs.h index a67e7b972..e068e94a9 100644 --- a/common/patches/rof_structs.h +++ b/common/patches/rof_structs.h @@ -581,8 +581,8 @@ struct NewZone_Struct { /*0868*/ uint32 underworld_teleport_index; // > 0 teleports w/ zone point index, invalid succors, -1 affects some collisions /*0872*/ uint32 scriptIDSomething3; /*0876*/ uint32 SuspendBuffs; -/*0880*/ uint32 unknown880; // Seen 50 -/*0884*/ uint32 unknown884; // Seen 10 +/*0880*/ uint32 LavaDamage; // Seen 50 +/*0884*/ uint32 MinLavaDamage; // Seen 10 /*0888*/ uint8 unknown888; // Seen 1 /*0889*/ uint8 unknown889; // Seen 0 (POK) or 1 (rujj) /*0890*/ uint8 unknown890; // Seen 1 @@ -998,7 +998,7 @@ struct LeadershipAA_Struct { * Size: 20 Octets */ struct BindStruct { - /*000*/ uint32 zoneId; + /*000*/ uint32 zone_id; /*004*/ float x; /*008*/ float y; /*012*/ float z; @@ -2062,7 +2062,7 @@ struct GMZoneRequest_Struct { /*0068*/ float x; /*0072*/ float y; /*0076*/ float z; -/*0080*/ char unknown0080[4]; +/*0080*/ float heading; /*0084*/ uint32 success; // 0 if command failed, 1 if succeeded? /*0088*/ // /*072*/ int8 success; // =0 client->server, =1 server->client, -X=specific error @@ -3032,7 +3032,7 @@ struct EnvDamage2_Struct { /*0006*/ uint32 damage; /*0010*/ float unknown10; // New to Underfoot - Seen 1 /*0014*/ uint8 unknown14[12]; -/*0026*/ uint8 dmgtype; // FA = Lava; FC = Falling +/*0026*/ uint8 dmgtype; // FA = Lava, FB = Drowning, FC = Falling, FD = Trap /*0027*/ uint8 unknown27[4]; /*0031*/ uint16 unknown31; // New to Underfoot - Seen 66 /*0033*/ uint16 constant; // Always FFFF @@ -4305,8 +4305,8 @@ struct UseAA_Struct { struct AA_Ability { /*00*/ uint32 skill_id; -/*04*/ uint32 base1; -/*08*/ uint32 base2; +/*04*/ uint32 base_value; +/*08*/ uint32 limit_value; /*12*/ uint32 slot; /*16*/ }; @@ -4841,30 +4841,30 @@ struct ExpeditionInviteResponse_Struct /*079*/ uint8 unknown079; // padding garbage? }; -struct ExpeditionInfo_Struct +struct DynamicZoneInfo_Struct { /*000*/ uint32 client_id; /*004*/ uint32 unknown004; /*008*/ uint32 assigned; // padded bool /*012*/ uint32 max_players; -/*016*/ char expedition_name[128]; +/*016*/ char dz_name[128]; /*144*/ char leader_name[64]; }; -struct ExpeditionMemberEntry_Struct +struct DynamicZoneMemberEntry_Struct { -/*000*/ char name[1]; // variable length, null terminated, max 0x40 (64) -/*000*/ uint8 expedition_status; // 0: unknown 1: Online, 2: Offline, 3: In Dynamic Zone, 4: Link Dead +/*000*/ char name[1]; // variable length, null terminated, max 0x40 (64) +/*000*/ uint8 online_status; // 0: unknown 1: Online, 2: Offline, 3: In Dynamic Zone, 4: Link Dead }; -struct ExpeditionMemberList_Struct +struct DynamicZoneMemberList_Struct { /*000*/ uint32 client_id; /*004*/ uint32 member_count; // number of players in window -/*008*/ ExpeditionMemberEntry_Struct members[0]; // variable length +/*008*/ DynamicZoneMemberEntry_Struct members[0]; // variable length }; -struct ExpeditionMemberListName_Struct +struct DynamicZoneMemberListName_Struct { /*000*/ uint32 client_id; /*004*/ uint32 unknown004; @@ -4887,7 +4887,7 @@ struct ExpeditionLockoutTimers_Struct /*008*/ ExpeditionLockoutTimerEntry_Struct timers[0]; }; -struct ExpeditionSetLeaderName_Struct +struct DynamicZoneLeaderName_Struct { /*000*/ uint32 client_id; /*004*/ uint32 unknown004; diff --git a/common/patches/sod.cpp b/common/patches/sod.cpp index d6eae5838..32e58e432 100644 --- a/common/patches/sod.cpp +++ b/common/patches/sod.cpp @@ -545,13 +545,13 @@ namespace SoD ENCODE(OP_DzExpeditionInfo) { - ENCODE_LENGTH_EXACT(ExpeditionInfo_Struct); - SETUP_DIRECT_ENCODE(ExpeditionInfo_Struct, structs::ExpeditionInfo_Struct); + ENCODE_LENGTH_EXACT(DynamicZoneInfo_Struct); + SETUP_DIRECT_ENCODE(DynamicZoneInfo_Struct, structs::DynamicZoneInfo_Struct); OUT(client_id); OUT(assigned); OUT(max_players); - strn0cpy(eq->expedition_name, emu->expedition_name, sizeof(eq->expedition_name)); + strn0cpy(eq->dz_name, emu->dz_name, sizeof(eq->dz_name)); strn0cpy(eq->leader_name, emu->leader_name, sizeof(eq->leader_name)); FINISH_ENCODE(); @@ -597,8 +597,8 @@ namespace SoD ENCODE(OP_DzSetLeaderName) { - ENCODE_LENGTH_EXACT(ExpeditionSetLeaderName_Struct); - SETUP_DIRECT_ENCODE(ExpeditionSetLeaderName_Struct, structs::ExpeditionSetLeaderName_Struct); + ENCODE_LENGTH_EXACT(DynamicZoneLeaderName_Struct); + SETUP_DIRECT_ENCODE(DynamicZoneLeaderName_Struct, structs::DynamicZoneLeaderName_Struct); OUT(client_id); strn0cpy(eq->leader_name, emu->leader_name, sizeof(eq->leader_name)); @@ -608,7 +608,7 @@ namespace SoD ENCODE(OP_DzMemberList) { - SETUP_VAR_ENCODE(ExpeditionMemberList_Struct); + SETUP_VAR_ENCODE(DynamicZoneMemberList_Struct); SerializeBuffer buf; buf.WriteUInt32(emu->client_id); @@ -616,7 +616,7 @@ namespace SoD for (uint32 i = 0; i < emu->member_count; ++i) { buf.WriteString(emu->members[i].name); - buf.WriteUInt8(emu->members[i].expedition_status); + buf.WriteUInt8(emu->members[i].online_status); } __packet->size = buf.size(); @@ -628,8 +628,8 @@ namespace SoD ENCODE(OP_DzMemberListName) { - ENCODE_LENGTH_EXACT(ExpeditionMemberListName_Struct); - SETUP_DIRECT_ENCODE(ExpeditionMemberListName_Struct, structs::ExpeditionMemberListName_Struct); + ENCODE_LENGTH_EXACT(DynamicZoneMemberListName_Struct); + SETUP_DIRECT_ENCODE(DynamicZoneMemberListName_Struct, structs::DynamicZoneMemberListName_Struct); OUT(client_id); OUT(add_name); @@ -640,7 +640,7 @@ namespace SoD ENCODE(OP_DzMemberListStatus) { - auto emu = reinterpret_cast((*p)->pBuffer); + auto emu = reinterpret_cast((*p)->pBuffer); if (emu->member_count == 1) { ENCODE_FORWARD(OP_DzMemberList); @@ -1170,20 +1170,6 @@ namespace SoD FINISH_ENCODE(); } - ENCODE(OP_ManaChange) - { - ENCODE_LENGTH_EXACT(ManaChange_Struct); - SETUP_DIRECT_ENCODE(ManaChange_Struct, structs::ManaChange_Struct); - - OUT(new_mana); - OUT(stamina); - OUT(spell_id); - OUT(keepcasting); - eq->slot = -1; // this is spell gem slot. It's -1 in normal operation - - FINISH_ENCODE(); - } - ENCODE(OP_MercenaryDataResponse) { //consume the packet @@ -1388,8 +1374,8 @@ namespace SoD /*fill in some unknowns with observed values, hopefully it will help */ eq->unknown800 = -1; eq->unknown844 = 600; - eq->unknown880 = 50; - eq->unknown884 = 10; + OUT(LavaDamage); + OUT(MinLavaDamage); eq->unknown888 = 1; eq->unknown889 = 0; eq->unknown890 = 1; @@ -1475,7 +1461,7 @@ namespace SoD eq->level1 = emu->level; // OUT(unknown00022[2]); for (r = 0; r < 5; r++) { - OUT(binds[r].zoneId); + OUT(binds[r].zone_id); OUT(binds[r].x); OUT(binds[r].y); OUT(binds[r].z); @@ -1874,8 +1860,8 @@ namespace SoD for(auto i = 0; i < eq->total_abilities; ++i) { eq->abilities[i].skill_id = inapp->ReadUInt32(); - eq->abilities[i].base1 = inapp->ReadUInt32(); - eq->abilities[i].base2 = inapp->ReadUInt32(); + eq->abilities[i].base_value = inapp->ReadUInt32(); + eq->abilities[i].limit_value = inapp->ReadUInt32(); eq->abilities[i].slot = inapp->ReadUInt32(); } diff --git a/common/patches/sod_ops.h b/common/patches/sod_ops.h index 5216a77ce..c98ab5ff0 100644 --- a/common/patches/sod_ops.h +++ b/common/patches/sod_ops.h @@ -63,7 +63,6 @@ E(OP_ItemVerifyReply) E(OP_LeadershipExpUpdate) E(OP_LogServer) E(OP_LootItem) -E(OP_ManaChange) E(OP_MercenaryDataResponse) E(OP_MercenaryDataUpdate) E(OP_MoveItem) diff --git a/common/patches/sod_structs.h b/common/patches/sod_structs.h index 4e7735bf1..c39b5b65f 100644 --- a/common/patches/sod_structs.h +++ b/common/patches/sod_structs.h @@ -450,8 +450,8 @@ struct NewZone_Struct { /*0868*/ uint32 underworld_teleport_index; // > 0 teleports w/ zone point index, invalid succors, -1 affects some collisions /*0872*/ uint32 scriptIDSomething3; /*0876*/ uint32 SuspendBuffs; -/*0880*/ uint32 unknown880; //seen 50 -/*0884*/ uint32 unknown884; //seen 10 +/*0880*/ uint32 LavaDamage; //seen 50 +/*0884*/ uint32 MinLavaDamage; //seen 10 /*0888*/ uint8 unknown888; //seen 1 /*0889*/ uint8 unknown889; //seen 0 (POK) or 1 (rujj) /*0890*/ uint8 unknown890; //seen 1 @@ -803,7 +803,7 @@ struct LeadershipAA_Struct { * Size: 20 Octets */ struct BindStruct { - /*000*/ uint32 zoneId; + /*000*/ uint32 zone_id; /*004*/ float x; /*008*/ float y; /*012*/ float z; @@ -1714,7 +1714,7 @@ struct GMZoneRequest_Struct { /*0068*/ float x; /*0072*/ float y; /*0076*/ float z; -/*0080*/ char unknown0080[4]; +/*0080*/ float heading; /*0084*/ uint32 success; // 0 if command failed, 1 if succeeded? /*0088*/ // /*072*/ int8 success; // =0 client->server, =1 server->client, -X=specific error @@ -2539,7 +2539,7 @@ struct EnvDamage2_Struct { /*0004*/ uint16 unknown4; /*0006*/ uint32 damage; /*0010*/ uint8 unknown10[12]; -/*0022*/ uint8 dmgtype; //FA = Lava; FC = Falling +/*0022*/ uint8 dmgtype; // FA = Lava, FB = Drowning, FC = Falling, FD = Trap /*0023*/ uint8 unknown2[4]; /*0027*/ uint16 constant; //Always FFFF /*0029*/ uint16 unknown29; @@ -3748,8 +3748,8 @@ struct UseAA_Struct { struct AA_Ability { /*00*/ uint32 skill_id; -/*04*/ uint32 base1; -/*08*/ uint32 base2; +/*04*/ uint32 base_value; +/*08*/ uint32 limit_value; /*12*/ uint32 slot; /*16*/ }; @@ -4196,30 +4196,30 @@ struct ExpeditionInviteResponse_Struct /*079*/ uint8 unknown079; // padding garbage? }; -struct ExpeditionInfo_Struct +struct DynamicZoneInfo_Struct { /*000*/ uint32 client_id; /*004*/ uint32 unknown004; /*008*/ uint32 assigned; // padded bool /*012*/ uint32 max_players; -/*016*/ char expedition_name[128]; +/*016*/ char dz_name[128]; /*144*/ char leader_name[64]; }; -struct ExpeditionMemberEntry_Struct +struct DynamicZoneMemberEntry_Struct { -/*000*/ char name[1]; // variable length, null terminated, max 0x40 (64) -/*000*/ uint8 expedition_status; // 0: unknown 1: Online, 2: Offline, 3: In Dynamic Zone, 4: Link Dead +/*000*/ char name[1]; // variable length, null terminated, max 0x40 (64) +/*000*/ uint8 online_status; // 0: unknown 1: Online, 2: Offline, 3: In Dynamic Zone, 4: Link Dead }; -struct ExpeditionMemberList_Struct +struct DynamicZoneMemberList_Struct { /*000*/ uint32 client_id; /*004*/ uint32 member_count; // number of players in window -/*008*/ ExpeditionMemberEntry_Struct members[0]; // variable length +/*008*/ DynamicZoneMemberEntry_Struct members[0]; // variable length }; -struct ExpeditionMemberListName_Struct +struct DynamicZoneMemberListName_Struct { /*000*/ uint32 client_id; /*004*/ uint32 unknown004; @@ -4242,7 +4242,7 @@ struct ExpeditionLockoutTimers_Struct /*008*/ ExpeditionLockoutTimerEntry_Struct timers[0]; }; -struct ExpeditionSetLeaderName_Struct +struct DynamicZoneLeaderName_Struct { /*000*/ uint32 client_id; /*004*/ uint32 unknown004; diff --git a/common/patches/sof.cpp b/common/patches/sof.cpp index f24f265ff..ff77e788b 100644 --- a/common/patches/sof.cpp +++ b/common/patches/sof.cpp @@ -533,13 +533,13 @@ namespace SoF ENCODE(OP_DzExpeditionInfo) { - ENCODE_LENGTH_EXACT(ExpeditionInfo_Struct); - SETUP_DIRECT_ENCODE(ExpeditionInfo_Struct, structs::ExpeditionInfo_Struct); + ENCODE_LENGTH_EXACT(DynamicZoneInfo_Struct); + SETUP_DIRECT_ENCODE(DynamicZoneInfo_Struct, structs::DynamicZoneInfo_Struct); OUT(client_id); OUT(assigned); OUT(max_players); - strn0cpy(eq->expedition_name, emu->expedition_name, sizeof(eq->expedition_name)); + strn0cpy(eq->dz_name, emu->dz_name, sizeof(eq->dz_name)); strn0cpy(eq->leader_name, emu->leader_name, sizeof(eq->leader_name)); FINISH_ENCODE(); @@ -585,8 +585,8 @@ namespace SoF ENCODE(OP_DzSetLeaderName) { - ENCODE_LENGTH_EXACT(ExpeditionSetLeaderName_Struct); - SETUP_DIRECT_ENCODE(ExpeditionSetLeaderName_Struct, structs::ExpeditionSetLeaderName_Struct); + ENCODE_LENGTH_EXACT(DynamicZoneLeaderName_Struct); + SETUP_DIRECT_ENCODE(DynamicZoneLeaderName_Struct, structs::DynamicZoneLeaderName_Struct); OUT(client_id); strn0cpy(eq->leader_name, emu->leader_name, sizeof(eq->leader_name)); @@ -596,7 +596,7 @@ namespace SoF ENCODE(OP_DzMemberList) { - SETUP_VAR_ENCODE(ExpeditionMemberList_Struct); + SETUP_VAR_ENCODE(DynamicZoneMemberList_Struct); SerializeBuffer buf; buf.WriteUInt32(emu->client_id); @@ -604,7 +604,7 @@ namespace SoF for (uint32 i = 0; i < emu->member_count; ++i) { buf.WriteString(emu->members[i].name); - buf.WriteUInt8(emu->members[i].expedition_status); + buf.WriteUInt8(emu->members[i].online_status); } __packet->size = buf.size(); @@ -616,8 +616,8 @@ namespace SoF ENCODE(OP_DzMemberListName) { - ENCODE_LENGTH_EXACT(ExpeditionMemberListName_Struct); - SETUP_DIRECT_ENCODE(ExpeditionMemberListName_Struct, structs::ExpeditionMemberListName_Struct); + ENCODE_LENGTH_EXACT(DynamicZoneMemberListName_Struct); + SETUP_DIRECT_ENCODE(DynamicZoneMemberListName_Struct, structs::DynamicZoneMemberListName_Struct); OUT(client_id); OUT(add_name); @@ -628,7 +628,7 @@ namespace SoF ENCODE(OP_DzMemberListStatus) { - auto emu = reinterpret_cast((*p)->pBuffer); + auto emu = reinterpret_cast((*p)->pBuffer); if (emu->member_count == 1) { ENCODE_FORWARD(OP_DzMemberList); @@ -966,20 +966,6 @@ namespace SoF FINISH_ENCODE(); } - ENCODE(OP_ManaChange) - { - ENCODE_LENGTH_EXACT(ManaChange_Struct); - SETUP_DIRECT_ENCODE(ManaChange_Struct, structs::ManaChange_Struct); - - OUT(new_mana); - OUT(stamina); - OUT(spell_id); - OUT(keepcasting); - eq->slot = -1; // this is spell gem slot. It's -1 in normal operation - - FINISH_ENCODE(); - } - ENCODE(OP_MemorizeSpell) { ENCODE_LENGTH_EXACT(MemorizeSpell_Struct); @@ -1066,8 +1052,8 @@ namespace SoF /*fill in some unknowns with observed values, hopefully it will help */ eq->unknown796 = -1; eq->unknown840 = 600; - eq->unknown876 = 50; - eq->unknown880 = 10; + OUT(LavaDamage); + OUT(MinLavaDamage); eq->unknown884 = 1; eq->unknown885 = 0; eq->unknown886 = 1; @@ -1140,7 +1126,7 @@ namespace SoF eq->level1 = emu->level; // OUT(unknown00022[2]); for (r = 0; r < 5; r++) { - OUT(binds[r].zoneId); + OUT(binds[r].zone_id); OUT(binds[r].x); OUT(binds[r].y); OUT(binds[r].z); @@ -1545,8 +1531,8 @@ namespace SoF for(auto i = 0; i < eq->total_abilities; ++i) { eq->abilities[i].skill_id = inapp->ReadUInt32(); - eq->abilities[i].base1 = inapp->ReadUInt32(); - eq->abilities[i].base2 = inapp->ReadUInt32(); + eq->abilities[i].base_value = inapp->ReadUInt32(); + eq->abilities[i].limit_value = inapp->ReadUInt32(); eq->abilities[i].slot = inapp->ReadUInt32(); } diff --git a/common/patches/sof_ops.h b/common/patches/sof_ops.h index 653bb5979..0dd34e060 100644 --- a/common/patches/sof_ops.h +++ b/common/patches/sof_ops.h @@ -59,7 +59,6 @@ E(OP_ItemVerifyReply) E(OP_LeadershipExpUpdate) E(OP_LogServer) E(OP_LootItem) -E(OP_ManaChange) E(OP_MemorizeSpell) E(OP_MoveItem) E(OP_NewSpawn) diff --git a/common/patches/sof_structs.h b/common/patches/sof_structs.h index bf88bf5c2..d750d24ad 100644 --- a/common/patches/sof_structs.h +++ b/common/patches/sof_structs.h @@ -454,8 +454,8 @@ struct NewZone_Struct { /*0864*/ uint32 underworld_teleport_index; // > 0 teleports w/ zone point index, invalid succors, -1 affects some collisions /*0868*/ uint32 scriptIDSomething3; /*0872*/ uint32 SuspendBuffs; -/*0876*/ uint32 unknown876; //seen 50 -/*0880*/ uint32 unknown880; //seen 10 +/*0876*/ uint32 LavaDamage; //seen 50 +/*0880*/ uint32 MinLavaDamage; //seen 10 /*0884*/ uint8 unknown884; //seen 1 /*0885*/ uint8 unknown885; //seen 0 (POK) or 1 (rujj) /*0886*/ uint8 unknown886; //seen 1 @@ -804,7 +804,7 @@ struct LeadershipAA_Struct { * Size: 20 Octets */ struct BindStruct { - /*000*/ uint32 zoneId; + /*000*/ uint32 zone_id; /*004*/ float x; /*008*/ float y; /*012*/ float z; @@ -1742,7 +1742,7 @@ struct GMZoneRequest_Struct { /*0068*/ float x; /*0072*/ float y; /*0076*/ float z; -/*0080*/ char unknown0080[4]; +/*0080*/ float heading; /*0084*/ uint32 success; // 0 if command failed, 1 if succeeded? /*0088*/ // /*072*/ int8 success; // =0 client->server, =1 server->client, -X=specific error @@ -2509,7 +2509,7 @@ struct EnvDamage2_Struct { /*0004*/ uint16 unknown4; /*0006*/ uint32 damage; /*0010*/ uint8 unknown10[12]; -/*0022*/ uint8 dmgtype; //FA = Lava; FC = Falling +/*0022*/ uint8 dmgtype; // FA = Lava, FB = Drowning, FC = Falling, FD = Trap /*0023*/ uint8 unknown2[4]; /*0027*/ uint16 constant; //Always FFFF /*0029*/ uint16 unknown29; @@ -3673,8 +3673,8 @@ struct UseAA_Struct { struct AA_Ability { /*00*/ uint32 skill_id; -/*04*/ uint32 base1; -/*08*/ uint32 base2; +/*04*/ uint32 base_value; +/*08*/ uint32 limit_value; /*12*/ uint32 slot; /*16*/ }; @@ -4112,29 +4112,29 @@ struct ExpeditionInviteResponse_Struct /*075*/ uint8 unknown079; // padding/garbage? }; -struct ExpeditionInfo_Struct +struct DynamicZoneInfo_Struct { /*000*/ uint32 client_id; /*004*/ uint32 assigned; // padded bool /*008*/ uint32 max_players; -/*012*/ char expedition_name[128]; +/*012*/ char dz_name[128]; /*140*/ char leader_name[64]; }; -struct ExpeditionMemberEntry_Struct +struct DynamicZoneMemberEntry_Struct { -/*000*/ char name[1]; // variable length, null terminated, max 0x40 (64) -/*000*/ uint8 expedition_status; // 0: unknown 1: Online, 2: Offline, 3: In Dynamic Zone, 4: Link Dead +/*000*/ char name[1]; // variable length, null terminated, max 0x40 (64) +/*000*/ uint8 online_status; // 0: unknown 1: Online, 2: Offline, 3: In Dynamic Zone, 4: Link Dead }; -struct ExpeditionMemberList_Struct +struct DynamicZoneMemberList_Struct { /*000*/ uint32 client_id; /*004*/ uint32 member_count; -/*008*/ ExpeditionMemberEntry_Struct members[0]; // variable length +/*008*/ DynamicZoneMemberEntry_Struct members[0]; // variable length }; -struct ExpeditionMemberListName_Struct +struct DynamicZoneMemberListName_Struct { /*000*/ uint32 client_id; /*004*/ uint32 add_name; // padded bool, 0: remove name, 1: add name with unknown status @@ -4156,7 +4156,7 @@ struct ExpeditionLockoutTimers_Struct /*008*/ ExpeditionLockoutTimerEntry_Struct timers[0]; }; -struct ExpeditionSetLeaderName_Struct +struct DynamicZoneLeaderName_Struct { /*000*/ uint32 client_id; /*004*/ char leader_name[64]; diff --git a/common/patches/titanium.cpp b/common/patches/titanium.cpp index f7d97b8d9..25ef3be5c 100644 --- a/common/patches/titanium.cpp +++ b/common/patches/titanium.cpp @@ -476,13 +476,13 @@ namespace Titanium ENCODE(OP_DzExpeditionInfo) { - ENCODE_LENGTH_EXACT(ExpeditionInfo_Struct); - SETUP_DIRECT_ENCODE(ExpeditionInfo_Struct, structs::ExpeditionInfo_Struct); + ENCODE_LENGTH_EXACT(DynamicZoneInfo_Struct); + SETUP_DIRECT_ENCODE(DynamicZoneInfo_Struct, structs::DynamicZoneInfo_Struct); OUT(client_id); OUT(assigned); OUT(max_players); - strn0cpy(eq->expedition_name, emu->expedition_name, sizeof(eq->expedition_name)); + strn0cpy(eq->dz_name, emu->dz_name, sizeof(eq->dz_name)); strn0cpy(eq->leader_name, emu->leader_name, sizeof(eq->leader_name)); FINISH_ENCODE(); @@ -528,8 +528,8 @@ namespace Titanium ENCODE(OP_DzSetLeaderName) { - ENCODE_LENGTH_EXACT(ExpeditionSetLeaderName_Struct); - SETUP_DIRECT_ENCODE(ExpeditionSetLeaderName_Struct, structs::ExpeditionSetLeaderName_Struct); + ENCODE_LENGTH_EXACT(DynamicZoneLeaderName_Struct); + SETUP_DIRECT_ENCODE(DynamicZoneLeaderName_Struct, structs::DynamicZoneLeaderName_Struct); OUT(client_id); strn0cpy(eq->leader_name, emu->leader_name, sizeof(eq->leader_name)); @@ -539,7 +539,7 @@ namespace Titanium ENCODE(OP_DzMemberList) { - SETUP_VAR_ENCODE(ExpeditionMemberList_Struct); + SETUP_VAR_ENCODE(DynamicZoneMemberList_Struct); SerializeBuffer buf; buf.WriteUInt32(emu->client_id); @@ -547,7 +547,7 @@ namespace Titanium for (uint32 i = 0; i < emu->member_count; ++i) { buf.WriteString(emu->members[i].name); - buf.WriteUInt8(emu->members[i].expedition_status); + buf.WriteUInt8(emu->members[i].online_status); } __packet->size = buf.size(); @@ -559,8 +559,8 @@ namespace Titanium ENCODE(OP_DzMemberListName) { - ENCODE_LENGTH_EXACT(ExpeditionMemberListName_Struct); - SETUP_DIRECT_ENCODE(ExpeditionMemberListName_Struct, structs::ExpeditionMemberListName_Struct); + ENCODE_LENGTH_EXACT(DynamicZoneMemberListName_Struct); + SETUP_DIRECT_ENCODE(DynamicZoneMemberListName_Struct, structs::DynamicZoneMemberListName_Struct); OUT(client_id); OUT(add_name); @@ -571,7 +571,7 @@ namespace Titanium ENCODE(OP_DzMemberListStatus) { - auto emu = reinterpret_cast((*p)->pBuffer); + auto emu = reinterpret_cast((*p)->pBuffer); if (emu->member_count == 1) { ENCODE_FORWARD(OP_DzMemberList); @@ -932,6 +932,19 @@ namespace Titanium FINISH_ENCODE(); } + ENCODE(OP_ManaChange) + { + ENCODE_LENGTH_EXACT(ManaChange_Struct); + SETUP_DIRECT_ENCODE(ManaChange_Struct, structs::ManaChange_Struct); + + OUT(new_mana); + OUT(stamina); + OUT(spell_id); + OUT(keepcasting); + + FINISH_ENCODE(); + } + ENCODE(OP_MemorizeSpell) { ENCODE_LENGTH_EXACT(MemorizeSpell_Struct); @@ -1022,7 +1035,7 @@ namespace Titanium eq->level1 = emu->level; // OUT(unknown00022[2]); for (r = 0; r < 5; r++) { - OUT(binds[r].zoneId); + OUT(binds[r].zone_id); OUT(binds[r].x); OUT(binds[r].y); OUT(binds[r].z); @@ -1338,8 +1351,8 @@ namespace Titanium for(auto i = 0; i < eq->total_abilities; ++i) { eq->abilities[i].skill_id = inapp->ReadUInt32(); - eq->abilities[i].base1 = inapp->ReadUInt32(); - eq->abilities[i].base2 = inapp->ReadUInt32(); + eq->abilities[i].base_value = inapp->ReadUInt32(); + eq->abilities[i].limit_value = inapp->ReadUInt32(); eq->abilities[i].slot = inapp->ReadUInt32(); } diff --git a/common/patches/titanium_ops.h b/common/patches/titanium_ops.h index 961c850fe..c7557a32f 100644 --- a/common/patches/titanium_ops.h +++ b/common/patches/titanium_ops.h @@ -55,6 +55,7 @@ E(OP_ItemPacket) E(OP_LeadershipExpUpdate) E(OP_LFGuild) E(OP_LootItem) +E(OP_ManaChange) E(OP_MemorizeSpell) E(OP_MoveItem) E(OP_OnLevelMessage) diff --git a/common/patches/titanium_structs.h b/common/patches/titanium_structs.h index b2beae0cd..cf918717a 100644 --- a/common/patches/titanium_structs.h +++ b/common/patches/titanium_structs.h @@ -738,7 +738,7 @@ struct LeadershipAA_Struct { * Size: 20 Octets */ struct BindStruct { - /*000*/ uint32 zoneId; + /*000*/ uint32 zone_id; /*004*/ float x; /*008*/ float y; /*012*/ float z; @@ -1509,7 +1509,7 @@ struct GMZoneRequest_Struct { /*0068*/ float x; /*0072*/ float y; /*0076*/ float z; -/*0080*/ char unknown0080[4]; +/*0080*/ float heading; /*0084*/ uint32 success; // 0 if command failed, 1 if succeeded? /*0088*/ // /*072*/ int8 success; // =0 client->server, =1 server->client, -X=specific error @@ -3180,8 +3180,8 @@ struct UseAA_Struct { struct AA_Ability { /*00*/ uint32 skill_id; -/*04*/ uint32 base1; -/*08*/ uint32 base2; +/*04*/ uint32 base_value; +/*08*/ uint32 limit_value; /*12*/ uint32 slot; }; @@ -3323,29 +3323,29 @@ struct ExpeditionInviteResponse_Struct /*075*/ uint8 unknown079; // padding/garbage? }; -struct ExpeditionInfo_Struct +struct DynamicZoneInfo_Struct { /*000*/ uint32 client_id; /*004*/ uint32 assigned; // padded bool /*008*/ uint32 max_players; -/*012*/ char expedition_name[128]; +/*012*/ char dz_name[128]; /*140*/ char leader_name[64]; }; -struct ExpeditionMemberEntry_Struct +struct DynamicZoneMemberEntry_Struct { -/*000*/ char name[1]; // variable length, null terminated, max 0x40 (64) -/*000*/ uint8 expedition_status; // 0: unknown 1: Online, 2: Offline, 3: In Dynamic Zone, 4: Link Dead +/*000*/ char name[1]; // variable length, null terminated, max 0x40 (64) +/*000*/ uint8 online_status; // 0: unknown 1: Online, 2: Offline, 3: In Dynamic Zone, 4: Link Dead }; -struct ExpeditionMemberList_Struct +struct DynamicZoneMemberList_Struct { /*000*/ uint32 client_id; /*004*/ uint32 member_count; -/*008*/ ExpeditionMemberEntry_Struct members[0]; // variable length +/*008*/ DynamicZoneMemberEntry_Struct members[0]; // variable length }; -struct ExpeditionMemberListName_Struct +struct DynamicZoneMemberListName_Struct { /*000*/ uint32 client_id; /*004*/ uint32 add_name; // padded bool, 0: remove name, 1: add name with unknown status @@ -3367,7 +3367,7 @@ struct ExpeditionLockoutTimers_Struct /*008*/ ExpeditionLockoutTimerEntry_Struct timers[0]; }; -struct ExpeditionSetLeaderName_Struct +struct DynamicZoneLeaderName_Struct { /*000*/ uint32 client_id; /*004*/ char leader_name[64]; diff --git a/common/patches/uf.cpp b/common/patches/uf.cpp index 47dafbbf3..1e33ed307 100644 --- a/common/patches/uf.cpp +++ b/common/patches/uf.cpp @@ -675,13 +675,13 @@ namespace UF ENCODE(OP_DzExpeditionInfo) { - ENCODE_LENGTH_EXACT(ExpeditionInfo_Struct); - SETUP_DIRECT_ENCODE(ExpeditionInfo_Struct, structs::ExpeditionInfo_Struct); + ENCODE_LENGTH_EXACT(DynamicZoneInfo_Struct); + SETUP_DIRECT_ENCODE(DynamicZoneInfo_Struct, structs::DynamicZoneInfo_Struct); OUT(client_id); OUT(assigned); OUT(max_players); - strn0cpy(eq->expedition_name, emu->expedition_name, sizeof(eq->expedition_name)); + strn0cpy(eq->dz_name, emu->dz_name, sizeof(eq->dz_name)); strn0cpy(eq->leader_name, emu->leader_name, sizeof(eq->leader_name)); FINISH_ENCODE(); @@ -727,8 +727,8 @@ namespace UF ENCODE(OP_DzSetLeaderName) { - ENCODE_LENGTH_EXACT(ExpeditionSetLeaderName_Struct); - SETUP_DIRECT_ENCODE(ExpeditionSetLeaderName_Struct, structs::ExpeditionSetLeaderName_Struct); + ENCODE_LENGTH_EXACT(DynamicZoneLeaderName_Struct); + SETUP_DIRECT_ENCODE(DynamicZoneLeaderName_Struct, structs::DynamicZoneLeaderName_Struct); OUT(client_id); strn0cpy(eq->leader_name, emu->leader_name, sizeof(eq->leader_name)); @@ -738,7 +738,7 @@ namespace UF ENCODE(OP_DzMemberList) { - SETUP_VAR_ENCODE(ExpeditionMemberList_Struct); + SETUP_VAR_ENCODE(DynamicZoneMemberList_Struct); SerializeBuffer buf; buf.WriteUInt32(emu->client_id); @@ -746,7 +746,7 @@ namespace UF for (uint32 i = 0; i < emu->member_count; ++i) { buf.WriteString(emu->members[i].name); - buf.WriteUInt8(emu->members[i].expedition_status); + buf.WriteUInt8(emu->members[i].online_status); } __packet->size = buf.size(); @@ -758,8 +758,8 @@ namespace UF ENCODE(OP_DzMemberListName) { - ENCODE_LENGTH_EXACT(ExpeditionMemberListName_Struct); - SETUP_DIRECT_ENCODE(ExpeditionMemberListName_Struct, structs::ExpeditionMemberListName_Struct); + ENCODE_LENGTH_EXACT(DynamicZoneMemberListName_Struct); + SETUP_DIRECT_ENCODE(DynamicZoneMemberListName_Struct, structs::DynamicZoneMemberListName_Struct); OUT(client_id); OUT(add_name); @@ -770,7 +770,7 @@ namespace UF ENCODE(OP_DzMemberListStatus) { - auto emu = reinterpret_cast((*p)->pBuffer); + auto emu = reinterpret_cast((*p)->pBuffer); if (emu->member_count == 1) { ENCODE_FORWARD(OP_DzMemberList); @@ -1390,20 +1390,6 @@ namespace UF FINISH_ENCODE(); } - ENCODE(OP_ManaChange) - { - ENCODE_LENGTH_EXACT(ManaChange_Struct); - SETUP_DIRECT_ENCODE(ManaChange_Struct, structs::ManaChange_Struct); - - OUT(new_mana); - OUT(stamina); - OUT(spell_id); - OUT(keepcasting); - eq->slot = -1; // this is spell gem slot. It's -1 in normal operation - - FINISH_ENCODE(); - } - ENCODE(OP_MercenaryDataResponse) { //consume the packet @@ -1614,8 +1600,8 @@ namespace UF /*fill in some unknowns with observed values, hopefully it will help */ eq->unknown800 = -1; eq->unknown844 = 600; - eq->unknown880 = 50; - eq->unknown884 = 10; + OUT(LavaDamage); + OUT(MinLavaDamage); eq->unknown888 = 1; eq->unknown889 = 0; eq->unknown890 = 1; @@ -1705,7 +1691,7 @@ namespace UF eq->level1 = emu->level; // OUT(unknown00022[2]); for (r = 0; r < 5; r++) { - OUT(binds[r].zoneId); + OUT(binds[r].zone_id); OUT(binds[r].x); OUT(binds[r].y); OUT(binds[r].z); @@ -2139,8 +2125,8 @@ namespace UF for(auto i = 0; i < eq->total_abilities; ++i) { eq->abilities[i].skill_id = inapp->ReadUInt32(); - eq->abilities[i].base1 = inapp->ReadUInt32(); - eq->abilities[i].base2 = inapp->ReadUInt32(); + eq->abilities[i].base_value = inapp->ReadUInt32(); + eq->abilities[i].limit_value = inapp->ReadUInt32(); eq->abilities[i].slot = inapp->ReadUInt32(); } diff --git a/common/patches/uf_ops.h b/common/patches/uf_ops.h index 76b7e014b..5d728f067 100644 --- a/common/patches/uf_ops.h +++ b/common/patches/uf_ops.h @@ -68,7 +68,6 @@ E(OP_ItemVerifyReply) E(OP_LeadershipExpUpdate) E(OP_LogServer) E(OP_LootItem) -E(OP_ManaChange) E(OP_MercenaryDataResponse) E(OP_MercenaryDataUpdate) E(OP_MoveItem) diff --git a/common/patches/uf_structs.h b/common/patches/uf_structs.h index 4a4ac8a36..a0ee2ba8d 100644 --- a/common/patches/uf_structs.h +++ b/common/patches/uf_structs.h @@ -450,8 +450,8 @@ struct NewZone_Struct { /*0868*/ uint32 underworld_teleport_index; // > 0 teleports w/ zone point index, invalid succors, -1 affects some collisions /*0872*/ uint32 scriptIDSomething3; /*0876*/ uint32 SuspendBuffs; -/*0880*/ uint32 unknown880; //seen 50 -/*0884*/ uint32 unknown884; //seen 10 +/*0880*/ uint32 LavaDamage; //seen 50 +/*0884*/ uint32 MinLavaDamage; //seen 10 /*0888*/ uint8 unknown888; //seen 1 /*0889*/ uint8 unknown889; //seen 0 (POK) or 1 (rujj) /*0890*/ uint8 unknown890; //seen 1 @@ -833,7 +833,7 @@ struct LeadershipAA_Struct { * Size: 20 Octets */ struct BindStruct { - /*000*/ uint32 zoneId; + /*000*/ uint32 zone_id; /*004*/ float x; /*008*/ float y; /*012*/ float z; @@ -1755,7 +1755,7 @@ struct GMZoneRequest_Struct { /*0068*/ float x; /*0072*/ float y; /*0076*/ float z; -/*0080*/ char unknown0080[4]; +/*0080*/ float heading; /*0084*/ uint32 success; // 0 if command failed, 1 if succeeded? /*0088*/ // /*072*/ int8 success; // =0 client->server, =1 server->client, -X=specific error @@ -3803,8 +3803,8 @@ struct UseAA_Struct { struct AA_Ability { /*00*/ uint32 skill_id; -/*04*/ uint32 base1; -/*08*/ uint32 base2; +/*04*/ uint32 base_value; +/*08*/ uint32 limit_value; /*12*/ uint32 slot; /*16*/ }; @@ -4277,30 +4277,30 @@ struct ExpeditionInviteResponse_Struct /*079*/ uint8 unknown079; // padding garbage? }; -struct ExpeditionInfo_Struct +struct DynamicZoneInfo_Struct { /*000*/ uint32 client_id; /*004*/ uint32 unknown004; /*008*/ uint32 assigned; // padded bool /*012*/ uint32 max_players; -/*016*/ char expedition_name[128]; +/*016*/ char dz_name[128]; /*144*/ char leader_name[64]; }; -struct ExpeditionMemberEntry_Struct +struct DynamicZoneMemberEntry_Struct { -/*000*/ char name[1]; // variable length, null terminated, max 0x40 (64) -/*000*/ uint8 expedition_status; // 0: unknown 1: Online, 2: Offline, 3: In Dynamic Zone, 4: Link Dead +/*000*/ char name[1]; // variable length, null terminated, max 0x40 (64) +/*000*/ uint8 online_status; // 0: unknown 1: Online, 2: Offline, 3: In Dynamic Zone, 4: Link Dead }; -struct ExpeditionMemberList_Struct +struct DynamicZoneMemberList_Struct { /*000*/ uint32 client_id; /*004*/ uint32 member_count; // number of players in window -/*008*/ ExpeditionMemberEntry_Struct members[0]; // variable length +/*008*/ DynamicZoneMemberEntry_Struct members[0]; // variable length }; -struct ExpeditionMemberListName_Struct +struct DynamicZoneMemberListName_Struct { /*000*/ uint32 client_id; /*004*/ uint32 unknown004; @@ -4323,7 +4323,7 @@ struct ExpeditionLockoutTimers_Struct /*008*/ ExpeditionLockoutTimerEntry_Struct timers[0]; }; -struct ExpeditionSetLeaderName_Struct +struct DynamicZoneLeaderName_Struct { /*000*/ uint32 client_id; /*004*/ uint32 unknown004; diff --git a/common/platform.cpp b/common/platform.cpp index b67267155..e5481c339 100644 --- a/common/platform.cpp +++ b/common/platform.cpp @@ -46,15 +46,15 @@ std::string GetPlatformName() { switch (GetExecutablePlatformInt()) { case EQEmuExePlatform::ExePlatformWorld: - return "WorldServer"; + return "World"; case EQEmuExePlatform::ExePlatformQueryServ: - return "QueryServer"; + return "QS"; case EQEmuExePlatform::ExePlatformZone: - return "ZoneServer"; + return "Zone"; case EQEmuExePlatform::ExePlatformUCS: return "UCS"; case EQEmuExePlatform::ExePlatformLogin: - return "LoginServer"; + return "Login"; case EQEmuExePlatform::ExePlatformSocket_Server: return "SocketServer"; case EQEmuExePlatform::ExePlatformSharedMemory: @@ -70,4 +70,4 @@ std::string GetPlatformName() default: return ""; } -} \ No newline at end of file +} diff --git a/common/ptimer.h b/common/ptimer.h index 2232b55b5..349f20534 100644 --- a/common/ptimer.h +++ b/common/ptimer.h @@ -45,6 +45,8 @@ enum : int { //values for pTimerType pTimerLinkedSpellReuseStart = 28, pTimerLinkedSpellReuseEnd = 48, + pTimerShieldAbility = 86, + pTimerLayHands = 87, //these IDs are used by client too pTimerHarmTouch = 89, //so dont change them diff --git a/common/races.cpp b/common/races.cpp index 68f4717b6..bb7cce9c0 100644 --- a/common/races.cpp +++ b/common/races.cpp @@ -2232,3 +2232,15 @@ bool PlayerAppearance::IsValidWoad(uint16 race_id, uint8 gender_id, uint8 woad_v } return false; } + +const char* GetGenderName(uint32 gender_id) { + const char* gender_name = "Unknown"; + if (gender_id == MALE) { + gender_name = "Male"; + } else if (gender_id == FEMALE) { + gender_name = "Female"; + } else if (gender_id == NEUTER) { + gender_name = "Neuter"; + } + return gender_name; +} \ No newline at end of file diff --git a/common/races.h b/common/races.h index 5e7411a25..68e6fa061 100644 --- a/common/races.h +++ b/common/races.h @@ -851,6 +851,7 @@ const char* GetRaceIDName(uint16 race_id); const char* GetPlayerRaceName(uint32 player_race_value); +const char* GetGenderName(uint32 gender_id); uint32 GetPlayerRaceValue(uint16 race_id); uint32 GetPlayerRaceBit(uint16 race_id); @@ -860,7 +861,6 @@ uint16 GetRaceIDFromPlayerRaceBit(uint32 player_race_bit); float GetRaceGenderDefaultHeight(int race, int gender); - // player race-/gender-based model feature validators namespace PlayerAppearance { @@ -1603,6 +1603,14 @@ namespace PlayerAppearance #define RACE_FALLEN_KNIGHT_722 722 #define RACE_SERVANT_OF_SHADOW_723 723 #define RACE_LUCLIN_724 724 +#define RACE_XARIC_725 725 +#define RACE_DERVISH_726 726 +#define RACE_DERVISH_727 727 +#define RACE_LUCLIN_728 728 +#define RACE_LUCLIN_729 729 +#define RACE_ORB_730 730 +#define RACE_LUCLIN_731 731 +#define RACE_PEGASUS_732 732 #define RACE_INTERACTIVE_OBJECT_2250 2250 #endif diff --git a/common/random.h b/common/random.h index 203755efd..6d351121f 100644 --- a/common/random.h +++ b/common/random.h @@ -1,5 +1,5 @@ /* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2014 EQEMu Development Team (http://eqemulator.net) + Copyright (C) 2001-2021 EQEMu Development Team (http://eqemulator.net) 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 @@ -25,10 +25,24 @@ #include #include -/* This uses mt19937 seeded with the std::random_device - * The idea is to have this be included as a member of another class - * so mocking out for testing is easier - * If you need to reseed random.Reseed() +#ifdef USE_SFMT19937 +// only GCC supports, so lets turn it off +#ifndef __GNUC__ +#undef USE_SFMT19937 +#endif +#ifdef __clang__ +#undef USE_SFMT19937 +#endif +#endif + +#ifdef USE_ADDITIVE_LFIB_PRNG +#include "additive_lagged_fibonacci_engine.h" +#elif defined(USE_SFMT19937) +#include +#endif + +/* This uses mt19937 (or other compatible engine) seeded with the std::random_device. The idea is to have this be + * included as a member of another class so mocking out for testing is easier If you need to reseed random.Reseed() * Eventually this should be derived from an abstract base class */ @@ -40,7 +54,12 @@ namespace EQ { { if (low > high) std::swap(low, high); +// EQ uses biased int distribution, so I guess we can support it :P +#ifdef BIASED_INT_DIST + return low + m_gen() % (high - low + 1); +#else return int_dist(m_gen, int_param_t(low, high)); // [low, high] +#endif } // AKA old MakeRandomFloat @@ -98,11 +117,24 @@ namespace EQ { } private: +#ifndef BIASED_INT_DIST typedef std::uniform_int_distribution::param_type int_param_t; - typedef std::uniform_real_distribution::param_type real_param_t; - std::mt19937 m_gen; std::uniform_int_distribution int_dist; +#endif + typedef std::uniform_real_distribution::param_type real_param_t; std::uniform_real_distribution real_dist; +// define USE_CUSTOM_PRNG_ENGINE to any supported random engine like: +// #define USE_CUSTOM_PRNG_ENGINE std::default_random_engine +#ifdef USE_ADDITIVE_LFIB_PRNG + EQRand +#elif defined(USE_SFMT19937) + __gnu_cxx::sfmt19937 +#elif defined(USE_CUSTOM_PRNG_ENGINE) + USE_CUSTOM_PRNG_ENGINE +#else + std::mt19937 +#endif + m_gen; }; } diff --git a/common/repositories/base/base_aa_ability_repository.h b/common/repositories/base/base_aa_ability_repository.h index fbf16f0ba..a6079b617 100644 --- a/common/repositories/base/base_aa_ability_repository.h +++ b/common/repositories/base/base_aa_ability_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_aa_rank_effects_repository.h b/common/repositories/base/base_aa_rank_effects_repository.h index fa0eebcd1..3fca2393c 100644 --- a/common/repositories/base/base_aa_rank_effects_repository.h +++ b/common/repositories/base/base_aa_rank_effects_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_aa_rank_prereqs_repository.h b/common/repositories/base/base_aa_rank_prereqs_repository.h index 04b1a0a38..a2d0f584a 100644 --- a/common/repositories/base/base_aa_rank_prereqs_repository.h +++ b/common/repositories/base/base_aa_rank_prereqs_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_aa_ranks_repository.h b/common/repositories/base/base_aa_ranks_repository.h index 8145e3e96..57bf035d2 100644 --- a/common/repositories/base/base_aa_ranks_repository.h +++ b/common/repositories/base/base_aa_ranks_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_account_flags_repository.h b/common/repositories/base/base_account_flags_repository.h index af9c9b223..68929ef37 100644 --- a/common/repositories/base/base_account_flags_repository.h +++ b/common/repositories/base/base_account_flags_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_account_ip_repository.h b/common/repositories/base/base_account_ip_repository.h index 1384c4ef2..2a3a93e62 100644 --- a/common/repositories/base/base_account_ip_repository.h +++ b/common/repositories/base/base_account_ip_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ @@ -74,7 +74,7 @@ public: entry.accid = 0; entry.ip = ""; entry.count = 1; - entry.lastused = current_timestamp(); + entry.lastused = ""; return entry; } diff --git a/common/repositories/base/base_account_repository.h b/common/repositories/base/base_account_repository.h index 6283caf13..681b2c1e5 100644 --- a/common/repositories/base/base_account_repository.h +++ b/common/repositories/base/base_account_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_account_rewards_repository.h b/common/repositories/base/base_account_rewards_repository.h index ee6213e16..4fd0ecb63 100644 --- a/common/repositories/base/base_account_rewards_repository.h +++ b/common/repositories/base/base_account_rewards_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_adventure_details_repository.h b/common/repositories/base/base_adventure_details_repository.h index 656ba8908..cf6b27197 100644 --- a/common/repositories/base/base_adventure_details_repository.h +++ b/common/repositories/base/base_adventure_details_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_adventure_members_repository.h b/common/repositories/base/base_adventure_members_repository.h index 4cdc357fd..80f824175 100644 --- a/common/repositories/base/base_adventure_members_repository.h +++ b/common/repositories/base/base_adventure_members_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_adventure_stats_repository.h b/common/repositories/base/base_adventure_stats_repository.h index b1634c080..bd21e2fef 100644 --- a/common/repositories/base/base_adventure_stats_repository.h +++ b/common/repositories/base/base_adventure_stats_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_adventure_template_entry_flavor_repository.h b/common/repositories/base/base_adventure_template_entry_flavor_repository.h index 8b4bfa7b6..fc586a252 100644 --- a/common/repositories/base/base_adventure_template_entry_flavor_repository.h +++ b/common/repositories/base/base_adventure_template_entry_flavor_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_adventure_template_entry_repository.h b/common/repositories/base/base_adventure_template_entry_repository.h index 5e4b9ca7c..6a348b350 100644 --- a/common/repositories/base/base_adventure_template_entry_repository.h +++ b/common/repositories/base/base_adventure_template_entry_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_adventure_template_repository.h b/common/repositories/base/base_adventure_template_repository.h index 076b8abc5..b54e4fa89 100644 --- a/common/repositories/base/base_adventure_template_repository.h +++ b/common/repositories/base/base_adventure_template_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ @@ -148,7 +148,7 @@ public: entry.zone_in_time = 1800; entry.win_points = 0; entry.lose_points = 0; - entry.theme = 1; + entry.theme = LDoNThemes::GUK; entry.zone_in_zone_id = 0; entry.zone_in_x = 0; entry.zone_in_y = 0; diff --git a/common/repositories/base/base_alternate_currency_repository.h b/common/repositories/base/base_alternate_currency_repository.h index 832da0f84..1687d66c3 100644 --- a/common/repositories/base/base_alternate_currency_repository.h +++ b/common/repositories/base/base_alternate_currency_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_auras_repository.h b/common/repositories/base/base_auras_repository.h index e884fc50b..478413ffa 100644 --- a/common/repositories/base/base_auras_repository.h +++ b/common/repositories/base/base_auras_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_base_data_repository.h b/common/repositories/base/base_base_data_repository.h index 3574f254a..ea152964b 100644 --- a/common/repositories/base/base_base_data_repository.h +++ b/common/repositories/base/base_base_data_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ @@ -19,7 +19,7 @@ class BaseBaseDataRepository { public: struct BaseData { int level; - int class; + int class_; float hp; float mana; float end; @@ -39,7 +39,7 @@ public: { return { "level", - "class", + "`class`", "hp", "mana", "end", @@ -84,7 +84,7 @@ public: BaseData entry{}; entry.level = 0; - entry.class = 0; + entry.class_ = 0; entry.hp = 0; entry.mana = 0; entry.end = 0; @@ -129,7 +129,7 @@ public: BaseData entry{}; entry.level = atoi(row[0]); - entry.class = atoi(row[1]); + entry.class_ = atoi(row[1]); entry.hp = static_cast(atof(row[2])); entry.mana = static_cast(atof(row[3])); entry.end = static_cast(atof(row[4])); @@ -172,7 +172,7 @@ public: auto columns = Columns(); update_values.push_back(columns[0] + " = " + std::to_string(base_data_entry.level)); - update_values.push_back(columns[1] + " = " + std::to_string(base_data_entry.class)); + update_values.push_back(columns[1] + " = " + std::to_string(base_data_entry.class_)); update_values.push_back(columns[2] + " = " + std::to_string(base_data_entry.hp)); update_values.push_back(columns[3] + " = " + std::to_string(base_data_entry.mana)); update_values.push_back(columns[4] + " = " + std::to_string(base_data_entry.end)); @@ -203,7 +203,7 @@ public: std::vector insert_values; insert_values.push_back(std::to_string(base_data_entry.level)); - insert_values.push_back(std::to_string(base_data_entry.class)); + insert_values.push_back(std::to_string(base_data_entry.class_)); insert_values.push_back(std::to_string(base_data_entry.hp)); insert_values.push_back(std::to_string(base_data_entry.mana)); insert_values.push_back(std::to_string(base_data_entry.end)); @@ -242,7 +242,7 @@ public: std::vector insert_values; insert_values.push_back(std::to_string(base_data_entry.level)); - insert_values.push_back(std::to_string(base_data_entry.class)); + insert_values.push_back(std::to_string(base_data_entry.class_)); insert_values.push_back(std::to_string(base_data_entry.hp)); insert_values.push_back(std::to_string(base_data_entry.mana)); insert_values.push_back(std::to_string(base_data_entry.end)); @@ -285,7 +285,7 @@ public: BaseData entry{}; entry.level = atoi(row[0]); - entry.class = atoi(row[1]); + entry.class_ = atoi(row[1]); entry.hp = static_cast(atof(row[2])); entry.mana = static_cast(atof(row[3])); entry.end = static_cast(atof(row[4])); @@ -319,7 +319,7 @@ public: BaseData entry{}; entry.level = atoi(row[0]); - entry.class = atoi(row[1]); + entry.class_ = atoi(row[1]); entry.hp = static_cast(atof(row[2])); entry.mana = static_cast(atof(row[3])); entry.end = static_cast(atof(row[4])); diff --git a/common/repositories/base/base_blocked_spells_repository.h b/common/repositories/base/base_blocked_spells_repository.h index d17524a2c..3905a30d6 100644 --- a/common/repositories/base/base_blocked_spells_repository.h +++ b/common/repositories/base/base_blocked_spells_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_bug_reports_repository.h b/common/repositories/base/base_bug_reports_repository.h index ecd1335ef..1f5385994 100644 --- a/common/repositories/base/base_bug_reports_repository.h +++ b/common/repositories/base/base_bug_reports_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ @@ -154,9 +154,9 @@ public: entry._unknown_value = 0; entry.bug_report = ""; entry.system_info = ""; - entry.report_datetime = current_timestamp(); + entry.report_datetime = ""; entry.bug_status = 0; - entry.last_review = current_timestamp(); + entry.last_review = ""; entry.last_reviewer = "None"; entry.reviewer_notes = ""; diff --git a/common/repositories/base/base_bugs_repository.h b/common/repositories/base/base_bugs_repository.h index 9ac9b9330..da35828c1 100644 --- a/common/repositories/base/base_bugs_repository.h +++ b/common/repositories/base/base_bugs_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_buyer_repository.h b/common/repositories/base/base_buyer_repository.h index 1b1cb9800..292792ddb 100644 --- a/common/repositories/base/base_buyer_repository.h +++ b/common/repositories/base/base_buyer_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_char_create_combinations_repository.h b/common/repositories/base/base_char_create_combinations_repository.h index ed806d6bd..8423f9751 100644 --- a/common/repositories/base/base_char_create_combinations_repository.h +++ b/common/repositories/base/base_char_create_combinations_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ @@ -20,7 +20,7 @@ public: struct CharCreateCombinations { int allocation_id; int race; - int class; + int class_; int deity; int start_zone; int expansions_req; @@ -36,7 +36,7 @@ public: return { "allocation_id", "race", - "class", + "`class`", "deity", "start_zone", "expansions_req", @@ -77,7 +77,7 @@ public: entry.allocation_id = 0; entry.race = 0; - entry.class = 0; + entry.class_ = 0; entry.deity = 0; entry.start_zone = 0; entry.expansions_req = 0; @@ -118,7 +118,7 @@ public: entry.allocation_id = atoi(row[0]); entry.race = atoi(row[1]); - entry.class = atoi(row[2]); + entry.class_ = atoi(row[2]); entry.deity = atoi(row[3]); entry.start_zone = atoi(row[4]); entry.expansions_req = atoi(row[5]); @@ -157,7 +157,7 @@ public: update_values.push_back(columns[0] + " = " + std::to_string(char_create_combinations_entry.allocation_id)); update_values.push_back(columns[1] + " = " + std::to_string(char_create_combinations_entry.race)); - update_values.push_back(columns[2] + " = " + std::to_string(char_create_combinations_entry.class)); + update_values.push_back(columns[2] + " = " + std::to_string(char_create_combinations_entry.class_)); update_values.push_back(columns[3] + " = " + std::to_string(char_create_combinations_entry.deity)); update_values.push_back(columns[4] + " = " + std::to_string(char_create_combinations_entry.start_zone)); update_values.push_back(columns[5] + " = " + std::to_string(char_create_combinations_entry.expansions_req)); @@ -184,7 +184,7 @@ public: insert_values.push_back(std::to_string(char_create_combinations_entry.allocation_id)); insert_values.push_back(std::to_string(char_create_combinations_entry.race)); - insert_values.push_back(std::to_string(char_create_combinations_entry.class)); + insert_values.push_back(std::to_string(char_create_combinations_entry.class_)); insert_values.push_back(std::to_string(char_create_combinations_entry.deity)); insert_values.push_back(std::to_string(char_create_combinations_entry.start_zone)); insert_values.push_back(std::to_string(char_create_combinations_entry.expansions_req)); @@ -219,7 +219,7 @@ public: insert_values.push_back(std::to_string(char_create_combinations_entry.allocation_id)); insert_values.push_back(std::to_string(char_create_combinations_entry.race)); - insert_values.push_back(std::to_string(char_create_combinations_entry.class)); + insert_values.push_back(std::to_string(char_create_combinations_entry.class_)); insert_values.push_back(std::to_string(char_create_combinations_entry.deity)); insert_values.push_back(std::to_string(char_create_combinations_entry.start_zone)); insert_values.push_back(std::to_string(char_create_combinations_entry.expansions_req)); @@ -258,7 +258,7 @@ public: entry.allocation_id = atoi(row[0]); entry.race = atoi(row[1]); - entry.class = atoi(row[2]); + entry.class_ = atoi(row[2]); entry.deity = atoi(row[3]); entry.start_zone = atoi(row[4]); entry.expansions_req = atoi(row[5]); @@ -288,7 +288,7 @@ public: entry.allocation_id = atoi(row[0]); entry.race = atoi(row[1]); - entry.class = atoi(row[2]); + entry.class_ = atoi(row[2]); entry.deity = atoi(row[3]); entry.start_zone = atoi(row[4]); entry.expansions_req = atoi(row[5]); diff --git a/common/repositories/base/base_char_create_point_allocations_repository.h b/common/repositories/base/base_char_create_point_allocations_repository.h index 7b3f4a2d6..668fc5062 100644 --- a/common/repositories/base/base_char_create_point_allocations_repository.h +++ b/common/repositories/base/base_char_create_point_allocations_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_char_recipe_list_repository.h b/common/repositories/base/base_char_recipe_list_repository.h index cb3e33707..541859f49 100644 --- a/common/repositories/base/base_char_recipe_list_repository.h +++ b/common/repositories/base/base_char_recipe_list_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_character_activities_repository.h b/common/repositories/base/base_character_activities_repository.h index 9c4a7b144..e4014c0a4 100644 --- a/common/repositories/base/base_character_activities_repository.h +++ b/common/repositories/base/base_character_activities_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_character_alt_currency_repository.h b/common/repositories/base/base_character_alt_currency_repository.h index 9e2ebc695..31228ba3b 100644 --- a/common/repositories/base/base_character_alt_currency_repository.h +++ b/common/repositories/base/base_character_alt_currency_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_character_alternate_abilities_repository.h b/common/repositories/base/base_character_alternate_abilities_repository.h index de4ef1cb4..a3616643d 100644 --- a/common/repositories/base/base_character_alternate_abilities_repository.h +++ b/common/repositories/base/base_character_alternate_abilities_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_character_auras_repository.h b/common/repositories/base/base_character_auras_repository.h index 8ac89e458..10f9d3ac4 100644 --- a/common/repositories/base/base_character_auras_repository.h +++ b/common/repositories/base/base_character_auras_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_character_bandolier_repository.h b/common/repositories/base/base_character_bandolier_repository.h index a208ac9da..f4c725fb7 100644 --- a/common/repositories/base/base_character_bandolier_repository.h +++ b/common/repositories/base/base_character_bandolier_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_character_bind_repository.h b/common/repositories/base/base_character_bind_repository.h index 6ae2aee1d..c31979a8d 100644 --- a/common/repositories/base/base_character_bind_repository.h +++ b/common/repositories/base/base_character_bind_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_character_buffs_repository.h b/common/repositories/base/base_character_buffs_repository.h index 6e577e154..51d179c86 100644 --- a/common/repositories/base/base_character_buffs_repository.h +++ b/common/repositories/base/base_character_buffs_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_character_corpse_items_repository.h b/common/repositories/base/base_character_corpse_items_repository.h index a1d50d125..37343835f 100644 --- a/common/repositories/base/base_character_corpse_items_repository.h +++ b/common/repositories/base/base_character_corpse_items_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_character_corpses_repository.h b/common/repositories/base/base_character_corpses_repository.h index bb7df4e0d..f215a351f 100644 --- a/common/repositories/base/base_character_corpses_repository.h +++ b/common/repositories/base/base_character_corpses_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ @@ -38,7 +38,7 @@ public: int level; int race; int gender; - int class; + int class_; int deity; int texture; int helm_texture; @@ -95,7 +95,7 @@ public: "level", "race", "gender", - "class", + "`class`", "deity", "texture", "helm_texture", @@ -177,7 +177,7 @@ public: entry.level = 0; entry.race = 0; entry.gender = 0; - entry.class = 0; + entry.class_ = 0; entry.deity = 0; entry.texture = 0; entry.helm_texture = 0; @@ -259,7 +259,7 @@ public: entry.level = atoi(row[17]); entry.race = atoi(row[18]); entry.gender = atoi(row[19]); - entry.class = atoi(row[20]); + entry.class_ = atoi(row[20]); entry.deity = atoi(row[21]); entry.texture = atoi(row[22]); entry.helm_texture = atoi(row[23]); @@ -338,7 +338,7 @@ public: update_values.push_back(columns[17] + " = " + std::to_string(character_corpses_entry.level)); update_values.push_back(columns[18] + " = " + std::to_string(character_corpses_entry.race)); update_values.push_back(columns[19] + " = " + std::to_string(character_corpses_entry.gender)); - update_values.push_back(columns[20] + " = " + std::to_string(character_corpses_entry.class)); + update_values.push_back(columns[20] + " = " + std::to_string(character_corpses_entry.class_)); update_values.push_back(columns[21] + " = " + std::to_string(character_corpses_entry.deity)); update_values.push_back(columns[22] + " = " + std::to_string(character_corpses_entry.texture)); update_values.push_back(columns[23] + " = " + std::to_string(character_corpses_entry.helm_texture)); @@ -406,7 +406,7 @@ public: insert_values.push_back(std::to_string(character_corpses_entry.level)); insert_values.push_back(std::to_string(character_corpses_entry.race)); insert_values.push_back(std::to_string(character_corpses_entry.gender)); - insert_values.push_back(std::to_string(character_corpses_entry.class)); + insert_values.push_back(std::to_string(character_corpses_entry.class_)); insert_values.push_back(std::to_string(character_corpses_entry.deity)); insert_values.push_back(std::to_string(character_corpses_entry.texture)); insert_values.push_back(std::to_string(character_corpses_entry.helm_texture)); @@ -482,7 +482,7 @@ public: insert_values.push_back(std::to_string(character_corpses_entry.level)); insert_values.push_back(std::to_string(character_corpses_entry.race)); insert_values.push_back(std::to_string(character_corpses_entry.gender)); - insert_values.push_back(std::to_string(character_corpses_entry.class)); + insert_values.push_back(std::to_string(character_corpses_entry.class_)); insert_values.push_back(std::to_string(character_corpses_entry.deity)); insert_values.push_back(std::to_string(character_corpses_entry.texture)); insert_values.push_back(std::to_string(character_corpses_entry.helm_texture)); @@ -562,7 +562,7 @@ public: entry.level = atoi(row[17]); entry.race = atoi(row[18]); entry.gender = atoi(row[19]); - entry.class = atoi(row[20]); + entry.class_ = atoi(row[20]); entry.deity = atoi(row[21]); entry.texture = atoi(row[22]); entry.helm_texture = atoi(row[23]); @@ -633,7 +633,7 @@ public: entry.level = atoi(row[17]); entry.race = atoi(row[18]); entry.gender = atoi(row[19]); - entry.class = atoi(row[20]); + entry.class_ = atoi(row[20]); entry.deity = atoi(row[21]); entry.texture = atoi(row[22]); entry.helm_texture = atoi(row[23]); diff --git a/common/repositories/base/base_character_currency_repository.h b/common/repositories/base/base_character_currency_repository.h index 17134a15d..a50a4cd7b 100644 --- a/common/repositories/base/base_character_currency_repository.h +++ b/common/repositories/base/base_character_currency_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_character_data_repository.h b/common/repositories/base/base_character_data_repository.h index 623a5a875..b95d42447 100644 --- a/common/repositories/base/base_character_data_repository.h +++ b/common/repositories/base/base_character_data_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ @@ -32,7 +32,7 @@ public: float heading; int gender; int race; - int class; + int class_; int level; int deity; int birthday; @@ -72,7 +72,7 @@ public: int sta; int cha; int dex; - int int; + int int_; int agi; int wis; int zone_change_count; @@ -144,7 +144,7 @@ public: "heading", "gender", "race", - "class", + "`class`", "level", "deity", "birthday", @@ -184,7 +184,7 @@ public: "sta", "cha", "dex", - "int", + "`int`", "agi", "wis", "zone_change_count", @@ -281,7 +281,7 @@ public: entry.heading = 0; entry.gender = 0; entry.race = 0; - entry.class = 0; + entry.class_ = 0; entry.level = 0; entry.deity = 0; entry.birthday = 0; @@ -321,7 +321,7 @@ public: entry.sta = 0; entry.cha = 0; entry.dex = 0; - entry.int = 0; + entry.int_ = 0; entry.agi = 0; entry.wis = 0; entry.zone_change_count = 0; @@ -368,7 +368,7 @@ public: entry.aa_points_spent_old = 0; entry.aa_points_old = 0; entry.e_last_invsnapshot = 0; - entry.deleted_at = 0; + entry.deleted_at = ""; return entry; } @@ -418,7 +418,7 @@ public: entry.heading = static_cast(atof(row[11])); entry.gender = atoi(row[12]); entry.race = atoi(row[13]); - entry.class = atoi(row[14]); + entry.class_ = atoi(row[14]); entry.level = atoi(row[15]); entry.deity = atoi(row[16]); entry.birthday = atoi(row[17]); @@ -458,7 +458,7 @@ public: entry.sta = atoi(row[51]); entry.cha = atoi(row[52]); entry.dex = atoi(row[53]); - entry.int = atoi(row[54]); + entry.int_ = atoi(row[54]); entry.agi = atoi(row[55]); entry.wis = atoi(row[56]); entry.zone_change_count = atoi(row[57]); @@ -552,7 +552,7 @@ public: update_values.push_back(columns[11] + " = " + std::to_string(character_data_entry.heading)); update_values.push_back(columns[12] + " = " + std::to_string(character_data_entry.gender)); update_values.push_back(columns[13] + " = " + std::to_string(character_data_entry.race)); - update_values.push_back(columns[14] + " = " + std::to_string(character_data_entry.class)); + update_values.push_back(columns[14] + " = " + std::to_string(character_data_entry.class_)); update_values.push_back(columns[15] + " = " + std::to_string(character_data_entry.level)); update_values.push_back(columns[16] + " = " + std::to_string(character_data_entry.deity)); update_values.push_back(columns[17] + " = " + std::to_string(character_data_entry.birthday)); @@ -592,7 +592,7 @@ public: update_values.push_back(columns[51] + " = " + std::to_string(character_data_entry.sta)); update_values.push_back(columns[52] + " = " + std::to_string(character_data_entry.cha)); update_values.push_back(columns[53] + " = " + std::to_string(character_data_entry.dex)); - update_values.push_back(columns[54] + " = " + std::to_string(character_data_entry.int)); + update_values.push_back(columns[54] + " = " + std::to_string(character_data_entry.int_)); update_values.push_back(columns[55] + " = " + std::to_string(character_data_entry.agi)); update_values.push_back(columns[56] + " = " + std::to_string(character_data_entry.wis)); update_values.push_back(columns[57] + " = " + std::to_string(character_data_entry.zone_change_count)); @@ -675,7 +675,7 @@ public: insert_values.push_back(std::to_string(character_data_entry.heading)); insert_values.push_back(std::to_string(character_data_entry.gender)); insert_values.push_back(std::to_string(character_data_entry.race)); - insert_values.push_back(std::to_string(character_data_entry.class)); + insert_values.push_back(std::to_string(character_data_entry.class_)); insert_values.push_back(std::to_string(character_data_entry.level)); insert_values.push_back(std::to_string(character_data_entry.deity)); insert_values.push_back(std::to_string(character_data_entry.birthday)); @@ -715,7 +715,7 @@ public: insert_values.push_back(std::to_string(character_data_entry.sta)); insert_values.push_back(std::to_string(character_data_entry.cha)); insert_values.push_back(std::to_string(character_data_entry.dex)); - insert_values.push_back(std::to_string(character_data_entry.int)); + insert_values.push_back(std::to_string(character_data_entry.int_)); insert_values.push_back(std::to_string(character_data_entry.agi)); insert_values.push_back(std::to_string(character_data_entry.wis)); insert_values.push_back(std::to_string(character_data_entry.zone_change_count)); @@ -806,7 +806,7 @@ public: insert_values.push_back(std::to_string(character_data_entry.heading)); insert_values.push_back(std::to_string(character_data_entry.gender)); insert_values.push_back(std::to_string(character_data_entry.race)); - insert_values.push_back(std::to_string(character_data_entry.class)); + insert_values.push_back(std::to_string(character_data_entry.class_)); insert_values.push_back(std::to_string(character_data_entry.level)); insert_values.push_back(std::to_string(character_data_entry.deity)); insert_values.push_back(std::to_string(character_data_entry.birthday)); @@ -846,7 +846,7 @@ public: insert_values.push_back(std::to_string(character_data_entry.sta)); insert_values.push_back(std::to_string(character_data_entry.cha)); insert_values.push_back(std::to_string(character_data_entry.dex)); - insert_values.push_back(std::to_string(character_data_entry.int)); + insert_values.push_back(std::to_string(character_data_entry.int_)); insert_values.push_back(std::to_string(character_data_entry.agi)); insert_values.push_back(std::to_string(character_data_entry.wis)); insert_values.push_back(std::to_string(character_data_entry.zone_change_count)); @@ -941,7 +941,7 @@ public: entry.heading = static_cast(atof(row[11])); entry.gender = atoi(row[12]); entry.race = atoi(row[13]); - entry.class = atoi(row[14]); + entry.class_ = atoi(row[14]); entry.level = atoi(row[15]); entry.deity = atoi(row[16]); entry.birthday = atoi(row[17]); @@ -981,7 +981,7 @@ public: entry.sta = atoi(row[51]); entry.cha = atoi(row[52]); entry.dex = atoi(row[53]); - entry.int = atoi(row[54]); + entry.int_ = atoi(row[54]); entry.agi = atoi(row[55]); entry.wis = atoi(row[56]); entry.zone_change_count = atoi(row[57]); @@ -1067,7 +1067,7 @@ public: entry.heading = static_cast(atof(row[11])); entry.gender = atoi(row[12]); entry.race = atoi(row[13]); - entry.class = atoi(row[14]); + entry.class_ = atoi(row[14]); entry.level = atoi(row[15]); entry.deity = atoi(row[16]); entry.birthday = atoi(row[17]); @@ -1107,7 +1107,7 @@ public: entry.sta = atoi(row[51]); entry.cha = atoi(row[52]); entry.dex = atoi(row[53]); - entry.int = atoi(row[54]); + entry.int_ = atoi(row[54]); entry.agi = atoi(row[55]); entry.wis = atoi(row[56]); entry.zone_change_count = atoi(row[57]); diff --git a/common/repositories/base/base_character_disciplines_repository.h b/common/repositories/base/base_character_disciplines_repository.h index 941e36056..a1870e5a0 100644 --- a/common/repositories/base/base_character_disciplines_repository.h +++ b/common/repositories/base/base_character_disciplines_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_character_expedition_lockouts_repository.h b/common/repositories/base/base_character_expedition_lockouts_repository.h index d50dd1f9e..cfdc17a3b 100644 --- a/common/repositories/base/base_character_expedition_lockouts_repository.h +++ b/common/repositories/base/base_character_expedition_lockouts_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_character_inspect_messages_repository.h b/common/repositories/base/base_character_inspect_messages_repository.h index 0a8cbc3e6..d7158e9a4 100644 --- a/common/repositories/base/base_character_inspect_messages_repository.h +++ b/common/repositories/base/base_character_inspect_messages_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_character_instance_safereturns_repository.h b/common/repositories/base/base_character_instance_safereturns_repository.h new file mode 100644 index 000000000..735c2e4c0 --- /dev/null +++ b/common/repositories/base/base_character_instance_safereturns_repository.h @@ -0,0 +1,355 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories + */ + +#ifndef EQEMU_BASE_CHARACTER_INSTANCE_SAFERETURNS_REPOSITORY_H +#define EQEMU_BASE_CHARACTER_INSTANCE_SAFERETURNS_REPOSITORY_H + +#include "../../database.h" +#include "../../string_util.h" + +class BaseCharacterInstanceSafereturnsRepository { +public: + struct CharacterInstanceSafereturns { + int id; + int character_id; + int instance_zone_id; + int instance_id; + int safe_zone_id; + float safe_x; + float safe_y; + float safe_z; + float safe_heading; + }; + + static std::string PrimaryKey() + { + return std::string("id"); + } + + static std::vector Columns() + { + return { + "id", + "character_id", + "instance_zone_id", + "instance_id", + "safe_zone_id", + "safe_x", + "safe_y", + "safe_z", + "safe_heading", + }; + } + + static std::string ColumnsRaw() + { + return std::string(implode(", ", Columns())); + } + + static std::string TableName() + { + return std::string("character_instance_safereturns"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + ColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static CharacterInstanceSafereturns NewEntity() + { + CharacterInstanceSafereturns entry{}; + + entry.id = 0; + entry.character_id = 0; + entry.instance_zone_id = 0; + entry.instance_id = 0; + entry.safe_zone_id = 0; + entry.safe_x = 0; + entry.safe_y = 0; + entry.safe_z = 0; + entry.safe_heading = 0; + + return entry; + } + + static CharacterInstanceSafereturns GetCharacterInstanceSafereturnsEntry( + const std::vector &character_instance_safereturnss, + int character_instance_safereturns_id + ) + { + for (auto &character_instance_safereturns : character_instance_safereturnss) { + if (character_instance_safereturns.id == character_instance_safereturns_id) { + return character_instance_safereturns; + } + } + + return NewEntity(); + } + + static CharacterInstanceSafereturns FindOne( + Database& db, + int character_instance_safereturns_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE id = {} LIMIT 1", + BaseSelect(), + character_instance_safereturns_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + CharacterInstanceSafereturns entry{}; + + entry.id = atoi(row[0]); + entry.character_id = atoi(row[1]); + entry.instance_zone_id = atoi(row[2]); + entry.instance_id = atoi(row[3]); + entry.safe_zone_id = atoi(row[4]); + entry.safe_x = static_cast(atof(row[5])); + entry.safe_y = static_cast(atof(row[6])); + entry.safe_z = static_cast(atof(row[7])); + entry.safe_heading = static_cast(atof(row[8])); + + return entry; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int character_instance_safereturns_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + character_instance_safereturns_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + CharacterInstanceSafereturns character_instance_safereturns_entry + ) + { + std::vector update_values; + + auto columns = Columns(); + + update_values.push_back(columns[1] + " = " + std::to_string(character_instance_safereturns_entry.character_id)); + update_values.push_back(columns[2] + " = " + std::to_string(character_instance_safereturns_entry.instance_zone_id)); + update_values.push_back(columns[3] + " = " + std::to_string(character_instance_safereturns_entry.instance_id)); + update_values.push_back(columns[4] + " = " + std::to_string(character_instance_safereturns_entry.safe_zone_id)); + update_values.push_back(columns[5] + " = " + std::to_string(character_instance_safereturns_entry.safe_x)); + update_values.push_back(columns[6] + " = " + std::to_string(character_instance_safereturns_entry.safe_y)); + update_values.push_back(columns[7] + " = " + std::to_string(character_instance_safereturns_entry.safe_z)); + update_values.push_back(columns[8] + " = " + std::to_string(character_instance_safereturns_entry.safe_heading)); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + implode(", ", update_values), + PrimaryKey(), + character_instance_safereturns_entry.id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static CharacterInstanceSafereturns InsertOne( + Database& db, + CharacterInstanceSafereturns character_instance_safereturns_entry + ) + { + std::vector insert_values; + + insert_values.push_back(std::to_string(character_instance_safereturns_entry.id)); + insert_values.push_back(std::to_string(character_instance_safereturns_entry.character_id)); + insert_values.push_back(std::to_string(character_instance_safereturns_entry.instance_zone_id)); + insert_values.push_back(std::to_string(character_instance_safereturns_entry.instance_id)); + insert_values.push_back(std::to_string(character_instance_safereturns_entry.safe_zone_id)); + insert_values.push_back(std::to_string(character_instance_safereturns_entry.safe_x)); + insert_values.push_back(std::to_string(character_instance_safereturns_entry.safe_y)); + insert_values.push_back(std::to_string(character_instance_safereturns_entry.safe_z)); + insert_values.push_back(std::to_string(character_instance_safereturns_entry.safe_heading)); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + implode(",", insert_values) + ) + ); + + if (results.Success()) { + character_instance_safereturns_entry.id = results.LastInsertedID(); + return character_instance_safereturns_entry; + } + + character_instance_safereturns_entry = NewEntity(); + + return character_instance_safereturns_entry; + } + + static int InsertMany( + Database& db, + std::vector character_instance_safereturns_entries + ) + { + std::vector insert_chunks; + + for (auto &character_instance_safereturns_entry: character_instance_safereturns_entries) { + std::vector insert_values; + + insert_values.push_back(std::to_string(character_instance_safereturns_entry.id)); + insert_values.push_back(std::to_string(character_instance_safereturns_entry.character_id)); + insert_values.push_back(std::to_string(character_instance_safereturns_entry.instance_zone_id)); + insert_values.push_back(std::to_string(character_instance_safereturns_entry.instance_id)); + insert_values.push_back(std::to_string(character_instance_safereturns_entry.safe_zone_id)); + insert_values.push_back(std::to_string(character_instance_safereturns_entry.safe_x)); + insert_values.push_back(std::to_string(character_instance_safereturns_entry.safe_y)); + insert_values.push_back(std::to_string(character_instance_safereturns_entry.safe_z)); + insert_values.push_back(std::to_string(character_instance_safereturns_entry.safe_heading)); + + insert_chunks.push_back("(" + implode(",", insert_values) + ")"); + } + + std::vector insert_values; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + CharacterInstanceSafereturns entry{}; + + entry.id = atoi(row[0]); + entry.character_id = atoi(row[1]); + entry.instance_zone_id = atoi(row[2]); + entry.instance_id = atoi(row[3]); + entry.safe_zone_id = atoi(row[4]); + entry.safe_x = static_cast(atof(row[5])); + entry.safe_y = static_cast(atof(row[6])); + entry.safe_z = static_cast(atof(row[7])); + entry.safe_heading = static_cast(atof(row[8])); + + all_entries.push_back(entry); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, std::string where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + CharacterInstanceSafereturns entry{}; + + entry.id = atoi(row[0]); + entry.character_id = atoi(row[1]); + entry.instance_zone_id = atoi(row[2]); + entry.instance_id = atoi(row[3]); + entry.safe_zone_id = atoi(row[4]); + entry.safe_x = static_cast(atof(row[5])); + entry.safe_y = static_cast(atof(row[6])); + entry.safe_z = static_cast(atof(row[7])); + entry.safe_heading = static_cast(atof(row[8])); + + all_entries.push_back(entry); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, std::string where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + +}; + +#endif //EQEMU_BASE_CHARACTER_INSTANCE_SAFERETURNS_REPOSITORY_H diff --git a/common/repositories/base/base_character_item_recast_repository.h b/common/repositories/base/base_character_item_recast_repository.h index 433d0f0f0..fbf105e3d 100644 --- a/common/repositories/base/base_character_item_recast_repository.h +++ b/common/repositories/base/base_character_item_recast_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_character_languages_repository.h b/common/repositories/base/base_character_languages_repository.h index 64217e8d7..96bb48e8f 100644 --- a/common/repositories/base/base_character_languages_repository.h +++ b/common/repositories/base/base_character_languages_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_character_leadership_abilities_repository.h b/common/repositories/base/base_character_leadership_abilities_repository.h index ac872d88b..63ee5a0dd 100644 --- a/common/repositories/base/base_character_leadership_abilities_repository.h +++ b/common/repositories/base/base_character_leadership_abilities_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_character_material_repository.h b/common/repositories/base/base_character_material_repository.h index 1b434cf2c..723828fcc 100644 --- a/common/repositories/base/base_character_material_repository.h +++ b/common/repositories/base/base_character_material_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_character_memmed_spells_repository.h b/common/repositories/base/base_character_memmed_spells_repository.h index b47750533..846d852d5 100644 --- a/common/repositories/base/base_character_memmed_spells_repository.h +++ b/common/repositories/base/base_character_memmed_spells_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_character_pet_buffs_repository.h b/common/repositories/base/base_character_pet_buffs_repository.h index 3dc6eb689..b4ef96b0a 100644 --- a/common/repositories/base/base_character_pet_buffs_repository.h +++ b/common/repositories/base/base_character_pet_buffs_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_character_pet_info_repository.h b/common/repositories/base/base_character_pet_info_repository.h index 91f65d216..b93b2a1e5 100644 --- a/common/repositories/base/base_character_pet_info_repository.h +++ b/common/repositories/base/base_character_pet_info_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_character_pet_inventory_repository.h b/common/repositories/base/base_character_pet_inventory_repository.h index 4e7e26456..b12549dd1 100644 --- a/common/repositories/base/base_character_pet_inventory_repository.h +++ b/common/repositories/base/base_character_pet_inventory_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_character_potionbelt_repository.h b/common/repositories/base/base_character_potionbelt_repository.h index 16d1cbfcd..7606449ce 100644 --- a/common/repositories/base/base_character_potionbelt_repository.h +++ b/common/repositories/base/base_character_potionbelt_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_character_skills_repository.h b/common/repositories/base/base_character_skills_repository.h index d654fd448..16454fa21 100644 --- a/common/repositories/base/base_character_skills_repository.h +++ b/common/repositories/base/base_character_skills_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_character_spells_repository.h b/common/repositories/base/base_character_spells_repository.h index eb34b0d43..29291cf65 100644 --- a/common/repositories/base/base_character_spells_repository.h +++ b/common/repositories/base/base_character_spells_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_character_task_timers_repository.h b/common/repositories/base/base_character_task_timers_repository.h new file mode 100644 index 000000000..f7b5fda49 --- /dev/null +++ b/common/repositories/base/base_character_task_timers_repository.h @@ -0,0 +1,336 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories + */ + +#ifndef EQEMU_BASE_CHARACTER_TASK_TIMERS_REPOSITORY_H +#define EQEMU_BASE_CHARACTER_TASK_TIMERS_REPOSITORY_H + +#include "../../database.h" +#include "../../string_util.h" +#include + +class BaseCharacterTaskTimersRepository { +public: + struct CharacterTaskTimers { + int id; + int character_id; + int task_id; + int timer_type; + time_t expire_time; + }; + + static std::string PrimaryKey() + { + return std::string("id"); + } + + static std::vector Columns() + { + return { + "id", + "character_id", + "task_id", + "timer_type", + "expire_time", + }; + } + + static std::vector SelectColumns() + { + return { + "id", + "character_id", + "task_id", + "timer_type", + "UNIX_TIMESTAMP(expire_time)", + }; + } + + static std::string ColumnsRaw() + { + return std::string(implode(", ", Columns())); + } + + static std::string SelectColumnsRaw() + { + return std::string(implode(", ", SelectColumns())); + } + + static std::string TableName() + { + return std::string("character_task_timers"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + SelectColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static CharacterTaskTimers NewEntity() + { + CharacterTaskTimers entry{}; + + entry.id = 0; + entry.character_id = 0; + entry.task_id = 0; + entry.timer_type = 0; + entry.expire_time = std::time(nullptr); + + return entry; + } + + static CharacterTaskTimers GetCharacterTaskTimersEntry( + const std::vector &character_task_timerss, + int character_task_timers_id + ) + { + for (auto &character_task_timers : character_task_timerss) { + if (character_task_timers.id == character_task_timers_id) { + return character_task_timers; + } + } + + return NewEntity(); + } + + static CharacterTaskTimers FindOne( + Database& db, + int character_task_timers_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE id = {} LIMIT 1", + BaseSelect(), + character_task_timers_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + CharacterTaskTimers entry{}; + + entry.id = atoi(row[0]); + entry.character_id = atoi(row[1]); + entry.task_id = atoi(row[2]); + entry.timer_type = atoi(row[3]); + entry.expire_time = strtoll(row[4], nullptr, 10); + + return entry; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int character_task_timers_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + character_task_timers_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + CharacterTaskTimers character_task_timers_entry + ) + { + std::vector update_values; + + auto columns = Columns(); + + update_values.push_back(columns[1] + " = " + std::to_string(character_task_timers_entry.character_id)); + update_values.push_back(columns[2] + " = " + std::to_string(character_task_timers_entry.task_id)); + update_values.push_back(columns[3] + " = " + std::to_string(character_task_timers_entry.timer_type)); + update_values.push_back(columns[4] + " = FROM_UNIXTIME(" + std::to_string(character_task_timers_entry.expire_time) + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + implode(", ", update_values), + PrimaryKey(), + character_task_timers_entry.id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static CharacterTaskTimers InsertOne( + Database& db, + CharacterTaskTimers character_task_timers_entry + ) + { + std::vector insert_values; + + insert_values.push_back(std::to_string(character_task_timers_entry.id)); + insert_values.push_back(std::to_string(character_task_timers_entry.character_id)); + insert_values.push_back(std::to_string(character_task_timers_entry.task_id)); + insert_values.push_back(std::to_string(character_task_timers_entry.timer_type)); + insert_values.push_back("FROM_UNIXTIME(" + std::to_string(character_task_timers_entry.expire_time) + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + implode(",", insert_values) + ) + ); + + if (results.Success()) { + character_task_timers_entry.id = results.LastInsertedID(); + return character_task_timers_entry; + } + + character_task_timers_entry = NewEntity(); + + return character_task_timers_entry; + } + + static int InsertMany( + Database& db, + std::vector character_task_timers_entries + ) + { + std::vector insert_chunks; + + for (auto &character_task_timers_entry: character_task_timers_entries) { + std::vector insert_values; + + insert_values.push_back(std::to_string(character_task_timers_entry.id)); + insert_values.push_back(std::to_string(character_task_timers_entry.character_id)); + insert_values.push_back(std::to_string(character_task_timers_entry.task_id)); + insert_values.push_back(std::to_string(character_task_timers_entry.timer_type)); + insert_values.push_back("FROM_UNIXTIME(" + std::to_string(character_task_timers_entry.expire_time) + ")"); + + insert_chunks.push_back("(" + implode(",", insert_values) + ")"); + } + + std::vector insert_values; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + CharacterTaskTimers entry{}; + + entry.id = atoi(row[0]); + entry.character_id = atoi(row[1]); + entry.task_id = atoi(row[2]); + entry.timer_type = atoi(row[3]); + entry.expire_time = strtoll(row[4], nullptr, 10); + + all_entries.push_back(entry); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, std::string where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + CharacterTaskTimers entry{}; + + entry.id = atoi(row[0]); + entry.character_id = atoi(row[1]); + entry.task_id = atoi(row[2]); + entry.timer_type = atoi(row[3]); + entry.expire_time = strtoll(row[4], nullptr, 10); + + all_entries.push_back(entry); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, std::string where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + +}; + +#endif //EQEMU_BASE_CHARACTER_TASK_TIMERS_REPOSITORY_H diff --git a/common/repositories/base/base_character_tasks_repository.h b/common/repositories/base/base_character_tasks_repository.h index 9d8371f34..58ba04194 100644 --- a/common/repositories/base/base_character_tasks_repository.h +++ b/common/repositories/base/base_character_tasks_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_completed_shared_task_activity_state_repository.h b/common/repositories/base/base_completed_shared_task_activity_state_repository.h new file mode 100644 index 000000000..83244b31d --- /dev/null +++ b/common/repositories/base/base_completed_shared_task_activity_state_repository.h @@ -0,0 +1,337 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories + */ + +#ifndef EQEMU_BASE_COMPLETED_SHARED_TASK_ACTIVITY_STATE_REPOSITORY_H +#define EQEMU_BASE_COMPLETED_SHARED_TASK_ACTIVITY_STATE_REPOSITORY_H + +#include "../../database.h" +#include "../../string_util.h" +#include + +class BaseCompletedSharedTaskActivityStateRepository { +public: + struct CompletedSharedTaskActivityState { + int64 shared_task_id; + int activity_id; + int done_count; + time_t updated_time; + time_t completed_time; + }; + + static std::string PrimaryKey() + { + return std::string("shared_task_id"); + } + + static std::vector Columns() + { + return { + "shared_task_id", + "activity_id", + "done_count", + "updated_time", + "completed_time", + }; + } + + static std::vector SelectColumns() + { + return { + "shared_task_id", + "activity_id", + "done_count", + "UNIX_TIMESTAMP(updated_time)", + "UNIX_TIMESTAMP(completed_time)", + }; + } + + static std::string ColumnsRaw() + { + return std::string(implode(", ", Columns())); + } + + static std::string SelectColumnsRaw() + { + return std::string(implode(", ", SelectColumns())); + } + + static std::string TableName() + { + return std::string("completed_shared_task_activity_state"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + SelectColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static CompletedSharedTaskActivityState NewEntity() + { + CompletedSharedTaskActivityState entry{}; + + entry.shared_task_id = 0; + entry.activity_id = 0; + entry.done_count = 0; + entry.updated_time = 0; + entry.completed_time = 0; + + return entry; + } + + static CompletedSharedTaskActivityState GetCompletedSharedTaskActivityStateEntry( + const std::vector &completed_shared_task_activity_states, + int completed_shared_task_activity_state_id + ) + { + for (auto &completed_shared_task_activity_state : completed_shared_task_activity_states) { + if (completed_shared_task_activity_state.shared_task_id == completed_shared_task_activity_state_id) { + return completed_shared_task_activity_state; + } + } + + return NewEntity(); + } + + static CompletedSharedTaskActivityState FindOne( + Database& db, + int completed_shared_task_activity_state_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE id = {} LIMIT 1", + BaseSelect(), + completed_shared_task_activity_state_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + CompletedSharedTaskActivityState entry{}; + + entry.shared_task_id = strtoll(row[0], nullptr, 10); + entry.activity_id = atoi(row[1]); + entry.done_count = atoi(row[2]); + entry.updated_time = strtoll(row[3] ? row[3] : "-1", nullptr, 10); + entry.completed_time = strtoll(row[4] ? row[4] : "-1", nullptr, 10); + + return entry; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int completed_shared_task_activity_state_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + completed_shared_task_activity_state_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + CompletedSharedTaskActivityState completed_shared_task_activity_state_entry + ) + { + std::vector update_values; + + auto columns = Columns(); + + update_values.push_back(columns[0] + " = " + std::to_string(completed_shared_task_activity_state_entry.shared_task_id)); + update_values.push_back(columns[1] + " = " + std::to_string(completed_shared_task_activity_state_entry.activity_id)); + update_values.push_back(columns[2] + " = " + std::to_string(completed_shared_task_activity_state_entry.done_count)); + update_values.push_back(columns[3] + " = FROM_UNIXTIME(" + (completed_shared_task_activity_state_entry.updated_time > 0 ? std::to_string(completed_shared_task_activity_state_entry.updated_time) : "null") + ")"); + update_values.push_back(columns[4] + " = FROM_UNIXTIME(" + (completed_shared_task_activity_state_entry.completed_time > 0 ? std::to_string(completed_shared_task_activity_state_entry.completed_time) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + implode(", ", update_values), + PrimaryKey(), + completed_shared_task_activity_state_entry.shared_task_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static CompletedSharedTaskActivityState InsertOne( + Database& db, + CompletedSharedTaskActivityState completed_shared_task_activity_state_entry + ) + { + std::vector insert_values; + + insert_values.push_back(std::to_string(completed_shared_task_activity_state_entry.shared_task_id)); + insert_values.push_back(std::to_string(completed_shared_task_activity_state_entry.activity_id)); + insert_values.push_back(std::to_string(completed_shared_task_activity_state_entry.done_count)); + insert_values.push_back("FROM_UNIXTIME(" + (completed_shared_task_activity_state_entry.updated_time > 0 ? std::to_string(completed_shared_task_activity_state_entry.updated_time) : "null") + ")"); + insert_values.push_back("FROM_UNIXTIME(" + (completed_shared_task_activity_state_entry.completed_time > 0 ? std::to_string(completed_shared_task_activity_state_entry.completed_time) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + implode(",", insert_values) + ) + ); + + if (results.Success()) { + completed_shared_task_activity_state_entry.shared_task_id = results.LastInsertedID(); + return completed_shared_task_activity_state_entry; + } + + completed_shared_task_activity_state_entry = NewEntity(); + + return completed_shared_task_activity_state_entry; + } + + static int InsertMany( + Database& db, + std::vector completed_shared_task_activity_state_entries + ) + { + std::vector insert_chunks; + + for (auto &completed_shared_task_activity_state_entry: completed_shared_task_activity_state_entries) { + std::vector insert_values; + + insert_values.push_back(std::to_string(completed_shared_task_activity_state_entry.shared_task_id)); + insert_values.push_back(std::to_string(completed_shared_task_activity_state_entry.activity_id)); + insert_values.push_back(std::to_string(completed_shared_task_activity_state_entry.done_count)); + insert_values.push_back("FROM_UNIXTIME(" + (completed_shared_task_activity_state_entry.updated_time > 0 ? std::to_string(completed_shared_task_activity_state_entry.updated_time) : "null") + ")"); + insert_values.push_back("FROM_UNIXTIME(" + (completed_shared_task_activity_state_entry.completed_time > 0 ? std::to_string(completed_shared_task_activity_state_entry.completed_time) : "null") + ")"); + + insert_chunks.push_back("(" + implode(",", insert_values) + ")"); + } + + std::vector insert_values; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + CompletedSharedTaskActivityState entry{}; + + entry.shared_task_id = strtoll(row[0], nullptr, 10); + entry.activity_id = atoi(row[1]); + entry.done_count = atoi(row[2]); + entry.updated_time = strtoll(row[3] ? row[3] : "-1", nullptr, 10); + entry.completed_time = strtoll(row[4] ? row[4] : "-1", nullptr, 10); + + all_entries.push_back(entry); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, std::string where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + CompletedSharedTaskActivityState entry{}; + + entry.shared_task_id = strtoll(row[0], nullptr, 10); + entry.activity_id = atoi(row[1]); + entry.done_count = atoi(row[2]); + entry.updated_time = strtoll(row[3] ? row[3] : "-1", nullptr, 10); + entry.completed_time = strtoll(row[4] ? row[4] : "-1", nullptr, 10); + + all_entries.push_back(entry); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, std::string where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + +}; + +#endif //EQEMU_BASE_COMPLETED_SHARED_TASK_ACTIVITY_STATE_REPOSITORY_H diff --git a/common/repositories/base/base_completed_shared_task_members_repository.h b/common/repositories/base/base_completed_shared_task_members_repository.h new file mode 100644 index 000000000..05f72ce3a --- /dev/null +++ b/common/repositories/base/base_completed_shared_task_members_repository.h @@ -0,0 +1,317 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories + */ + +#ifndef EQEMU_BASE_COMPLETED_SHARED_TASK_MEMBERS_REPOSITORY_H +#define EQEMU_BASE_COMPLETED_SHARED_TASK_MEMBERS_REPOSITORY_H + +#include "../../database.h" +#include "../../string_util.h" +#include + +class BaseCompletedSharedTaskMembersRepository { +public: + struct CompletedSharedTaskMembers { + int64 shared_task_id; + int64 character_id; + int is_leader; + }; + + static std::string PrimaryKey() + { + return std::string("shared_task_id"); + } + + static std::vector Columns() + { + return { + "shared_task_id", + "character_id", + "is_leader", + }; + } + + static std::vector SelectColumns() + { + return { + "shared_task_id", + "character_id", + "is_leader", + }; + } + + static std::string ColumnsRaw() + { + return std::string(implode(", ", Columns())); + } + + static std::string SelectColumnsRaw() + { + return std::string(implode(", ", SelectColumns())); + } + + static std::string TableName() + { + return std::string("completed_shared_task_members"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + SelectColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static CompletedSharedTaskMembers NewEntity() + { + CompletedSharedTaskMembers entry{}; + + entry.shared_task_id = 0; + entry.character_id = 0; + entry.is_leader = 0; + + return entry; + } + + static CompletedSharedTaskMembers GetCompletedSharedTaskMembersEntry( + const std::vector &completed_shared_task_memberss, + int completed_shared_task_members_id + ) + { + for (auto &completed_shared_task_members : completed_shared_task_memberss) { + if (completed_shared_task_members.shared_task_id == completed_shared_task_members_id) { + return completed_shared_task_members; + } + } + + return NewEntity(); + } + + static CompletedSharedTaskMembers FindOne( + Database& db, + int completed_shared_task_members_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE id = {} LIMIT 1", + BaseSelect(), + completed_shared_task_members_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + CompletedSharedTaskMembers entry{}; + + entry.shared_task_id = strtoll(row[0], nullptr, 10); + entry.character_id = strtoll(row[1], nullptr, 10); + entry.is_leader = atoi(row[2]); + + return entry; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int completed_shared_task_members_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + completed_shared_task_members_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + CompletedSharedTaskMembers completed_shared_task_members_entry + ) + { + std::vector update_values; + + auto columns = Columns(); + + update_values.push_back(columns[0] + " = " + std::to_string(completed_shared_task_members_entry.shared_task_id)); + update_values.push_back(columns[1] + " = " + std::to_string(completed_shared_task_members_entry.character_id)); + update_values.push_back(columns[2] + " = " + std::to_string(completed_shared_task_members_entry.is_leader)); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + implode(", ", update_values), + PrimaryKey(), + completed_shared_task_members_entry.shared_task_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static CompletedSharedTaskMembers InsertOne( + Database& db, + CompletedSharedTaskMembers completed_shared_task_members_entry + ) + { + std::vector insert_values; + + insert_values.push_back(std::to_string(completed_shared_task_members_entry.shared_task_id)); + insert_values.push_back(std::to_string(completed_shared_task_members_entry.character_id)); + insert_values.push_back(std::to_string(completed_shared_task_members_entry.is_leader)); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + implode(",", insert_values) + ) + ); + + if (results.Success()) { + completed_shared_task_members_entry.shared_task_id = results.LastInsertedID(); + return completed_shared_task_members_entry; + } + + completed_shared_task_members_entry = NewEntity(); + + return completed_shared_task_members_entry; + } + + static int InsertMany( + Database& db, + std::vector completed_shared_task_members_entries + ) + { + std::vector insert_chunks; + + for (auto &completed_shared_task_members_entry: completed_shared_task_members_entries) { + std::vector insert_values; + + insert_values.push_back(std::to_string(completed_shared_task_members_entry.shared_task_id)); + insert_values.push_back(std::to_string(completed_shared_task_members_entry.character_id)); + insert_values.push_back(std::to_string(completed_shared_task_members_entry.is_leader)); + + insert_chunks.push_back("(" + implode(",", insert_values) + ")"); + } + + std::vector insert_values; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + CompletedSharedTaskMembers entry{}; + + entry.shared_task_id = strtoll(row[0], nullptr, 10); + entry.character_id = strtoll(row[1], nullptr, 10); + entry.is_leader = atoi(row[2]); + + all_entries.push_back(entry); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, std::string where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + CompletedSharedTaskMembers entry{}; + + entry.shared_task_id = strtoll(row[0], nullptr, 10); + entry.character_id = strtoll(row[1], nullptr, 10); + entry.is_leader = atoi(row[2]); + + all_entries.push_back(entry); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, std::string where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + +}; + +#endif //EQEMU_BASE_COMPLETED_SHARED_TASK_MEMBERS_REPOSITORY_H diff --git a/common/repositories/base/base_completed_shared_tasks_repository.h b/common/repositories/base/base_completed_shared_tasks_repository.h new file mode 100644 index 000000000..9c1e3a87d --- /dev/null +++ b/common/repositories/base/base_completed_shared_tasks_repository.h @@ -0,0 +1,347 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories + */ + +#ifndef EQEMU_BASE_COMPLETED_SHARED_TASKS_REPOSITORY_H +#define EQEMU_BASE_COMPLETED_SHARED_TASKS_REPOSITORY_H + +#include "../../database.h" +#include "../../string_util.h" +#include + +class BaseCompletedSharedTasksRepository { +public: + struct CompletedSharedTasks { + int64 id; + int task_id; + time_t accepted_time; + time_t expire_time; + time_t completion_time; + int is_locked; + }; + + static std::string PrimaryKey() + { + return std::string("id"); + } + + static std::vector Columns() + { + return { + "id", + "task_id", + "accepted_time", + "expire_time", + "completion_time", + "is_locked", + }; + } + + static std::vector SelectColumns() + { + return { + "id", + "task_id", + "UNIX_TIMESTAMP(accepted_time)", + "UNIX_TIMESTAMP(expire_time)", + "UNIX_TIMESTAMP(completion_time)", + "is_locked", + }; + } + + static std::string ColumnsRaw() + { + return std::string(implode(", ", Columns())); + } + + static std::string SelectColumnsRaw() + { + return std::string(implode(", ", SelectColumns())); + } + + static std::string TableName() + { + return std::string("completed_shared_tasks"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + SelectColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static CompletedSharedTasks NewEntity() + { + CompletedSharedTasks entry{}; + + entry.id = 0; + entry.task_id = 0; + entry.accepted_time = 0; + entry.expire_time = 0; + entry.completion_time = 0; + entry.is_locked = 0; + + return entry; + } + + static CompletedSharedTasks GetCompletedSharedTasksEntry( + const std::vector &completed_shared_taskss, + int completed_shared_tasks_id + ) + { + for (auto &completed_shared_tasks : completed_shared_taskss) { + if (completed_shared_tasks.id == completed_shared_tasks_id) { + return completed_shared_tasks; + } + } + + return NewEntity(); + } + + static CompletedSharedTasks FindOne( + Database& db, + int completed_shared_tasks_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE id = {} LIMIT 1", + BaseSelect(), + completed_shared_tasks_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + CompletedSharedTasks entry{}; + + entry.id = strtoll(row[0], nullptr, 10); + entry.task_id = atoi(row[1]); + entry.accepted_time = strtoll(row[2] ? row[2] : "-1", nullptr, 10); + entry.expire_time = strtoll(row[3] ? row[3] : "-1", nullptr, 10); + entry.completion_time = strtoll(row[4] ? row[4] : "-1", nullptr, 10); + entry.is_locked = atoi(row[5]); + + return entry; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int completed_shared_tasks_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + completed_shared_tasks_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + CompletedSharedTasks completed_shared_tasks_entry + ) + { + std::vector update_values; + + auto columns = Columns(); + + update_values.push_back(columns[0] + " = " + std::to_string(completed_shared_tasks_entry.id)); + update_values.push_back(columns[1] + " = " + std::to_string(completed_shared_tasks_entry.task_id)); + update_values.push_back(columns[2] + " = FROM_UNIXTIME(" + (completed_shared_tasks_entry.accepted_time > 0 ? std::to_string(completed_shared_tasks_entry.accepted_time) : "null") + ")"); + update_values.push_back(columns[3] + " = FROM_UNIXTIME(" + (completed_shared_tasks_entry.expire_time > 0 ? std::to_string(completed_shared_tasks_entry.expire_time) : "null") + ")"); + update_values.push_back(columns[4] + " = FROM_UNIXTIME(" + (completed_shared_tasks_entry.completion_time > 0 ? std::to_string(completed_shared_tasks_entry.completion_time) : "null") + ")"); + update_values.push_back(columns[5] + " = " + std::to_string(completed_shared_tasks_entry.is_locked)); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + implode(", ", update_values), + PrimaryKey(), + completed_shared_tasks_entry.id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static CompletedSharedTasks InsertOne( + Database& db, + CompletedSharedTasks completed_shared_tasks_entry + ) + { + std::vector insert_values; + + insert_values.push_back(std::to_string(completed_shared_tasks_entry.id)); + insert_values.push_back(std::to_string(completed_shared_tasks_entry.task_id)); + insert_values.push_back("FROM_UNIXTIME(" + (completed_shared_tasks_entry.accepted_time > 0 ? std::to_string(completed_shared_tasks_entry.accepted_time) : "null") + ")"); + insert_values.push_back("FROM_UNIXTIME(" + (completed_shared_tasks_entry.expire_time > 0 ? std::to_string(completed_shared_tasks_entry.expire_time) : "null") + ")"); + insert_values.push_back("FROM_UNIXTIME(" + (completed_shared_tasks_entry.completion_time > 0 ? std::to_string(completed_shared_tasks_entry.completion_time) : "null") + ")"); + insert_values.push_back(std::to_string(completed_shared_tasks_entry.is_locked)); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + implode(",", insert_values) + ) + ); + + if (results.Success()) { + completed_shared_tasks_entry.id = results.LastInsertedID(); + return completed_shared_tasks_entry; + } + + completed_shared_tasks_entry = NewEntity(); + + return completed_shared_tasks_entry; + } + + static int InsertMany( + Database& db, + std::vector completed_shared_tasks_entries + ) + { + std::vector insert_chunks; + + for (auto &completed_shared_tasks_entry: completed_shared_tasks_entries) { + std::vector insert_values; + + insert_values.push_back(std::to_string(completed_shared_tasks_entry.id)); + insert_values.push_back(std::to_string(completed_shared_tasks_entry.task_id)); + insert_values.push_back("FROM_UNIXTIME(" + (completed_shared_tasks_entry.accepted_time > 0 ? std::to_string(completed_shared_tasks_entry.accepted_time) : "null") + ")"); + insert_values.push_back("FROM_UNIXTIME(" + (completed_shared_tasks_entry.expire_time > 0 ? std::to_string(completed_shared_tasks_entry.expire_time) : "null") + ")"); + insert_values.push_back("FROM_UNIXTIME(" + (completed_shared_tasks_entry.completion_time > 0 ? std::to_string(completed_shared_tasks_entry.completion_time) : "null") + ")"); + insert_values.push_back(std::to_string(completed_shared_tasks_entry.is_locked)); + + insert_chunks.push_back("(" + implode(",", insert_values) + ")"); + } + + std::vector insert_values; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + CompletedSharedTasks entry{}; + + entry.id = strtoll(row[0], nullptr, 10); + entry.task_id = atoi(row[1]); + entry.accepted_time = strtoll(row[2] ? row[2] : "-1", nullptr, 10); + entry.expire_time = strtoll(row[3] ? row[3] : "-1", nullptr, 10); + entry.completion_time = strtoll(row[4] ? row[4] : "-1", nullptr, 10); + entry.is_locked = atoi(row[5]); + + all_entries.push_back(entry); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, std::string where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + CompletedSharedTasks entry{}; + + entry.id = strtoll(row[0], nullptr, 10); + entry.task_id = atoi(row[1]); + entry.accepted_time = strtoll(row[2] ? row[2] : "-1", nullptr, 10); + entry.expire_time = strtoll(row[3] ? row[3] : "-1", nullptr, 10); + entry.completion_time = strtoll(row[4] ? row[4] : "-1", nullptr, 10); + entry.is_locked = atoi(row[5]); + + all_entries.push_back(entry); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, std::string where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + +}; + +#endif //EQEMU_BASE_COMPLETED_SHARED_TASKS_REPOSITORY_H diff --git a/common/repositories/base/base_completed_tasks_repository.h b/common/repositories/base/base_completed_tasks_repository.h index bee95bf79..61ec15cf2 100644 --- a/common/repositories/base/base_completed_tasks_repository.h +++ b/common/repositories/base/base_completed_tasks_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_content_flags_repository.h b/common/repositories/base/base_content_flags_repository.h index f59ad8663..3b57905ee 100644 --- a/common/repositories/base/base_content_flags_repository.h +++ b/common/repositories/base/base_content_flags_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_damageshieldtypes_repository.h b/common/repositories/base/base_damageshieldtypes_repository.h index b2c6be57e..9e08f4ed9 100644 --- a/common/repositories/base/base_damageshieldtypes_repository.h +++ b/common/repositories/base/base_damageshieldtypes_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_data_buckets_repository.h b/common/repositories/base/base_data_buckets_repository.h index cef173a69..f50e21592 100644 --- a/common/repositories/base/base_data_buckets_repository.h +++ b/common/repositories/base/base_data_buckets_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ @@ -18,7 +18,7 @@ class BaseDataBucketsRepository { public: struct DataBuckets { - int id; + int64 id; std::string key; std::string value; int expires; @@ -110,7 +110,7 @@ public: if (results.RowCount() == 1) { DataBuckets entry{}; - entry.id = atoi(row[0]); + entry.id = strtoll(row[0], NULL, 10); entry.key = row[1] ? row[1] : ""; entry.value = row[2] ? row[2] : ""; entry.expires = atoi(row[3]); @@ -241,7 +241,7 @@ public: for (auto row = results.begin(); row != results.end(); ++row) { DataBuckets entry{}; - entry.id = atoi(row[0]); + entry.id = strtoll(row[0], NULL, 10); entry.key = row[1] ? row[1] : ""; entry.value = row[2] ? row[2] : ""; entry.expires = atoi(row[3]); @@ -269,7 +269,7 @@ public: for (auto row = results.begin(); row != results.end(); ++row) { DataBuckets entry{}; - entry.id = atoi(row[0]); + entry.id = strtoll(row[0], NULL, 10); entry.key = row[1] ? row[1] : ""; entry.value = row[2] ? row[2] : ""; entry.expires = atoi(row[3]); diff --git a/common/repositories/base/base_db_str_repository.h b/common/repositories/base/base_db_str_repository.h index f9a827d95..e94b29772 100644 --- a/common/repositories/base/base_db_str_repository.h +++ b/common/repositories/base/base_db_str_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_discovered_items_repository.h b/common/repositories/base/base_discovered_items_repository.h index 555566f9d..03affdd09 100644 --- a/common/repositories/base/base_discovered_items_repository.h +++ b/common/repositories/base/base_discovered_items_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_doors_repository.h b/common/repositories/base/base_doors_repository.h index 75d740742..bcb0d5f49 100644 --- a/common/repositories/base/base_doors_repository.h +++ b/common/repositories/base/base_doors_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ @@ -53,7 +53,6 @@ public: int max_expansion; std::string content_flags; std::string content_flags_disabled; - int is_instance_door; }; static std::string PrimaryKey() @@ -99,7 +98,6 @@ public: "max_expansion", "content_flags", "content_flags_disabled", - "is_instance_door", }; } @@ -170,7 +168,6 @@ public: entry.max_expansion = 0; entry.content_flags = ""; entry.content_flags_disabled = ""; - entry.is_instance_door = 0; return entry; } @@ -241,7 +238,6 @@ public: entry.max_expansion = atoi(row[32]); entry.content_flags = row[33] ? row[33] : ""; entry.content_flags_disabled = row[34] ? row[34] : ""; - entry.is_instance_door = atoi(row[35]); return entry; } @@ -309,7 +305,6 @@ public: update_values.push_back(columns[32] + " = " + std::to_string(doors_entry.max_expansion)); update_values.push_back(columns[33] + " = '" + EscapeString(doors_entry.content_flags) + "'"); update_values.push_back(columns[34] + " = '" + EscapeString(doors_entry.content_flags_disabled) + "'"); - update_values.push_back(columns[35] + " = " + std::to_string(doors_entry.is_instance_door)); auto results = db.QueryDatabase( fmt::format( @@ -366,7 +361,6 @@ public: insert_values.push_back(std::to_string(doors_entry.max_expansion)); insert_values.push_back("'" + EscapeString(doors_entry.content_flags) + "'"); insert_values.push_back("'" + EscapeString(doors_entry.content_flags_disabled) + "'"); - insert_values.push_back(std::to_string(doors_entry.is_instance_door)); auto results = db.QueryDatabase( fmt::format( @@ -431,7 +425,6 @@ public: insert_values.push_back(std::to_string(doors_entry.max_expansion)); insert_values.push_back("'" + EscapeString(doors_entry.content_flags) + "'"); insert_values.push_back("'" + EscapeString(doors_entry.content_flags_disabled) + "'"); - insert_values.push_back(std::to_string(doors_entry.is_instance_door)); insert_chunks.push_back("(" + implode(",", insert_values) + ")"); } @@ -500,7 +493,6 @@ public: entry.max_expansion = atoi(row[32]); entry.content_flags = row[33] ? row[33] : ""; entry.content_flags_disabled = row[34] ? row[34] : ""; - entry.is_instance_door = atoi(row[35]); all_entries.push_back(entry); } @@ -560,7 +552,6 @@ public: entry.max_expansion = atoi(row[32]); entry.content_flags = row[33] ? row[33] : ""; entry.content_flags_disabled = row[34] ? row[34] : ""; - entry.is_instance_door = atoi(row[35]); all_entries.push_back(entry); } diff --git a/common/repositories/base/base_expedition_members_repository.h b/common/repositories/base/base_dynamic_zone_members_repository.h similarity index 53% rename from common/repositories/base/base_expedition_members_repository.h rename to common/repositories/base/base_dynamic_zone_members_repository.h index dde0be588..b0f41ca08 100644 --- a/common/repositories/base/base_expedition_members_repository.h +++ b/common/repositories/base/base_dynamic_zone_members_repository.h @@ -4,24 +4,23 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ -#ifndef EQEMU_BASE_EXPEDITION_MEMBERS_REPOSITORY_H -#define EQEMU_BASE_EXPEDITION_MEMBERS_REPOSITORY_H +#ifndef EQEMU_BASE_DYNAMIC_ZONE_MEMBERS_REPOSITORY_H +#define EQEMU_BASE_DYNAMIC_ZONE_MEMBERS_REPOSITORY_H #include "../../database.h" #include "../../string_util.h" -class BaseExpeditionMembersRepository { +class BaseDynamicZoneMembersRepository { public: - struct ExpeditionMembers { + struct DynamicZoneMembers { int id; - int expedition_id; + int dynamic_zone_id; int character_id; - int is_current_member; }; static std::string PrimaryKey() @@ -33,9 +32,8 @@ public: { return { "id", - "expedition_id", + "dynamic_zone_id", "character_id", - "is_current_member", }; } @@ -46,7 +44,7 @@ public: static std::string TableName() { - return std::string("expedition_members"); + return std::string("dynamic_zone_members"); } static std::string BaseSelect() @@ -67,53 +65,51 @@ public: ); } - static ExpeditionMembers NewEntity() + static DynamicZoneMembers NewEntity() { - ExpeditionMembers entry{}; + DynamicZoneMembers entry{}; - entry.id = 0; - entry.expedition_id = 0; - entry.character_id = 0; - entry.is_current_member = 1; + entry.id = 0; + entry.dynamic_zone_id = 0; + entry.character_id = 0; return entry; } - static ExpeditionMembers GetExpeditionMembersEntry( - const std::vector &expedition_memberss, - int expedition_members_id + static DynamicZoneMembers GetDynamicZoneMembersEntry( + const std::vector &dynamic_zone_memberss, + int dynamic_zone_members_id ) { - for (auto &expedition_members : expedition_memberss) { - if (expedition_members.id == expedition_members_id) { - return expedition_members; + for (auto &dynamic_zone_members : dynamic_zone_memberss) { + if (dynamic_zone_members.id == dynamic_zone_members_id) { + return dynamic_zone_members; } } return NewEntity(); } - static ExpeditionMembers FindOne( + static DynamicZoneMembers FindOne( Database& db, - int expedition_members_id + int dynamic_zone_members_id ) { auto results = db.QueryDatabase( fmt::format( "{} WHERE id = {} LIMIT 1", BaseSelect(), - expedition_members_id + dynamic_zone_members_id ) ); auto row = results.begin(); if (results.RowCount() == 1) { - ExpeditionMembers entry{}; + DynamicZoneMembers entry{}; - entry.id = atoi(row[0]); - entry.expedition_id = atoi(row[1]); - entry.character_id = atoi(row[2]); - entry.is_current_member = atoi(row[3]); + entry.id = atoi(row[0]); + entry.dynamic_zone_id = atoi(row[1]); + entry.character_id = atoi(row[2]); return entry; } @@ -123,7 +119,7 @@ public: static int DeleteOne( Database& db, - int expedition_members_id + int dynamic_zone_members_id ) { auto results = db.QueryDatabase( @@ -131,7 +127,7 @@ public: "DELETE FROM {} WHERE {} = {}", TableName(), PrimaryKey(), - expedition_members_id + dynamic_zone_members_id ) ); @@ -140,16 +136,15 @@ public: static int UpdateOne( Database& db, - ExpeditionMembers expedition_members_entry + DynamicZoneMembers dynamic_zone_members_entry ) { std::vector update_values; auto columns = Columns(); - update_values.push_back(columns[1] + " = " + std::to_string(expedition_members_entry.expedition_id)); - update_values.push_back(columns[2] + " = " + std::to_string(expedition_members_entry.character_id)); - update_values.push_back(columns[3] + " = " + std::to_string(expedition_members_entry.is_current_member)); + update_values.push_back(columns[1] + " = " + std::to_string(dynamic_zone_members_entry.dynamic_zone_id)); + update_values.push_back(columns[2] + " = " + std::to_string(dynamic_zone_members_entry.character_id)); auto results = db.QueryDatabase( fmt::format( @@ -157,24 +152,23 @@ public: TableName(), implode(", ", update_values), PrimaryKey(), - expedition_members_entry.id + dynamic_zone_members_entry.id ) ); return (results.Success() ? results.RowsAffected() : 0); } - static ExpeditionMembers InsertOne( + static DynamicZoneMembers InsertOne( Database& db, - ExpeditionMembers expedition_members_entry + DynamicZoneMembers dynamic_zone_members_entry ) { std::vector insert_values; - insert_values.push_back(std::to_string(expedition_members_entry.id)); - insert_values.push_back(std::to_string(expedition_members_entry.expedition_id)); - insert_values.push_back(std::to_string(expedition_members_entry.character_id)); - insert_values.push_back(std::to_string(expedition_members_entry.is_current_member)); + insert_values.push_back(std::to_string(dynamic_zone_members_entry.id)); + insert_values.push_back(std::to_string(dynamic_zone_members_entry.dynamic_zone_id)); + insert_values.push_back(std::to_string(dynamic_zone_members_entry.character_id)); auto results = db.QueryDatabase( fmt::format( @@ -185,29 +179,28 @@ public: ); if (results.Success()) { - expedition_members_entry.id = results.LastInsertedID(); - return expedition_members_entry; + dynamic_zone_members_entry.id = results.LastInsertedID(); + return dynamic_zone_members_entry; } - expedition_members_entry = NewEntity(); + dynamic_zone_members_entry = NewEntity(); - return expedition_members_entry; + return dynamic_zone_members_entry; } static int InsertMany( Database& db, - std::vector expedition_members_entries + std::vector dynamic_zone_members_entries ) { std::vector insert_chunks; - for (auto &expedition_members_entry: expedition_members_entries) { + for (auto &dynamic_zone_members_entry: dynamic_zone_members_entries) { std::vector insert_values; - insert_values.push_back(std::to_string(expedition_members_entry.id)); - insert_values.push_back(std::to_string(expedition_members_entry.expedition_id)); - insert_values.push_back(std::to_string(expedition_members_entry.character_id)); - insert_values.push_back(std::to_string(expedition_members_entry.is_current_member)); + insert_values.push_back(std::to_string(dynamic_zone_members_entry.id)); + insert_values.push_back(std::to_string(dynamic_zone_members_entry.dynamic_zone_id)); + insert_values.push_back(std::to_string(dynamic_zone_members_entry.character_id)); insert_chunks.push_back("(" + implode(",", insert_values) + ")"); } @@ -225,9 +218,9 @@ public: return (results.Success() ? results.RowsAffected() : 0); } - static std::vector All(Database& db) + static std::vector All(Database& db) { - std::vector all_entries; + std::vector all_entries; auto results = db.QueryDatabase( fmt::format( @@ -239,12 +232,11 @@ public: all_entries.reserve(results.RowCount()); for (auto row = results.begin(); row != results.end(); ++row) { - ExpeditionMembers entry{}; + DynamicZoneMembers entry{}; - entry.id = atoi(row[0]); - entry.expedition_id = atoi(row[1]); - entry.character_id = atoi(row[2]); - entry.is_current_member = atoi(row[3]); + entry.id = atoi(row[0]); + entry.dynamic_zone_id = atoi(row[1]); + entry.character_id = atoi(row[2]); all_entries.push_back(entry); } @@ -252,9 +244,9 @@ public: return all_entries; } - static std::vector GetWhere(Database& db, std::string where_filter) + static std::vector GetWhere(Database& db, std::string where_filter) { - std::vector all_entries; + std::vector all_entries; auto results = db.QueryDatabase( fmt::format( @@ -267,12 +259,11 @@ public: all_entries.reserve(results.RowCount()); for (auto row = results.begin(); row != results.end(); ++row) { - ExpeditionMembers entry{}; + DynamicZoneMembers entry{}; - entry.id = atoi(row[0]); - entry.expedition_id = atoi(row[1]); - entry.character_id = atoi(row[2]); - entry.is_current_member = atoi(row[3]); + entry.id = atoi(row[0]); + entry.dynamic_zone_id = atoi(row[1]); + entry.character_id = atoi(row[2]); all_entries.push_back(entry); } @@ -307,4 +298,4 @@ public: }; -#endif //EQEMU_BASE_EXPEDITION_MEMBERS_REPOSITORY_H +#endif //EQEMU_BASE_DYNAMIC_ZONE_MEMBERS_REPOSITORY_H diff --git a/common/repositories/base/base_dynamic_zones_repository.h b/common/repositories/base/base_dynamic_zones_repository.h index 13aabb07b..a6f43bb73 100644 --- a/common/repositories/base/base_dynamic_zones_repository.h +++ b/common/repositories/base/base_dynamic_zones_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ @@ -18,23 +18,28 @@ class BaseDynamicZonesRepository { public: struct DynamicZones { - int id; - int instance_id; - int type; - int compass_zone_id; - float compass_x; - float compass_y; - float compass_z; - int safe_return_zone_id; - float safe_return_x; - float safe_return_y; - float safe_return_z; - float safe_return_heading; - float zone_in_x; - float zone_in_y; - float zone_in_z; - float zone_in_heading; - int has_zone_in; + int id; + int instance_id; + int type; + std::string uuid; + std::string name; + int leader_id; + int min_players; + int max_players; + int compass_zone_id; + float compass_x; + float compass_y; + float compass_z; + int safe_return_zone_id; + float safe_return_x; + float safe_return_y; + float safe_return_z; + float safe_return_heading; + float zone_in_x; + float zone_in_y; + float zone_in_z; + float zone_in_heading; + int has_zone_in; }; static std::string PrimaryKey() @@ -48,6 +53,11 @@ public: "id", "instance_id", "type", + "uuid", + "name", + "leader_id", + "min_players", + "max_players", "compass_zone_id", "compass_x", "compass_y", @@ -100,6 +110,11 @@ public: entry.id = 0; entry.instance_id = 0; entry.type = 0; + entry.uuid = ""; + entry.name = ""; + entry.leader_id = 0; + entry.min_players = 0; + entry.max_players = 0; entry.compass_zone_id = 0; entry.compass_x = 0; entry.compass_y = 0; @@ -152,20 +167,25 @@ public: entry.id = atoi(row[0]); entry.instance_id = atoi(row[1]); entry.type = atoi(row[2]); - entry.compass_zone_id = atoi(row[3]); - entry.compass_x = static_cast(atof(row[4])); - entry.compass_y = static_cast(atof(row[5])); - entry.compass_z = static_cast(atof(row[6])); - entry.safe_return_zone_id = atoi(row[7]); - entry.safe_return_x = static_cast(atof(row[8])); - entry.safe_return_y = static_cast(atof(row[9])); - entry.safe_return_z = static_cast(atof(row[10])); - entry.safe_return_heading = static_cast(atof(row[11])); - entry.zone_in_x = static_cast(atof(row[12])); - entry.zone_in_y = static_cast(atof(row[13])); - entry.zone_in_z = static_cast(atof(row[14])); - entry.zone_in_heading = static_cast(atof(row[15])); - entry.has_zone_in = atoi(row[16]); + entry.uuid = row[3] ? row[3] : ""; + entry.name = row[4] ? row[4] : ""; + entry.leader_id = atoi(row[5]); + entry.min_players = atoi(row[6]); + entry.max_players = atoi(row[7]); + entry.compass_zone_id = atoi(row[8]); + entry.compass_x = static_cast(atof(row[9])); + entry.compass_y = static_cast(atof(row[10])); + entry.compass_z = static_cast(atof(row[11])); + entry.safe_return_zone_id = atoi(row[12]); + entry.safe_return_x = static_cast(atof(row[13])); + entry.safe_return_y = static_cast(atof(row[14])); + entry.safe_return_z = static_cast(atof(row[15])); + entry.safe_return_heading = static_cast(atof(row[16])); + entry.zone_in_x = static_cast(atof(row[17])); + entry.zone_in_y = static_cast(atof(row[18])); + entry.zone_in_z = static_cast(atof(row[19])); + entry.zone_in_heading = static_cast(atof(row[20])); + entry.has_zone_in = atoi(row[21]); return entry; } @@ -201,20 +221,25 @@ public: update_values.push_back(columns[1] + " = " + std::to_string(dynamic_zones_entry.instance_id)); update_values.push_back(columns[2] + " = " + std::to_string(dynamic_zones_entry.type)); - update_values.push_back(columns[3] + " = " + std::to_string(dynamic_zones_entry.compass_zone_id)); - update_values.push_back(columns[4] + " = " + std::to_string(dynamic_zones_entry.compass_x)); - update_values.push_back(columns[5] + " = " + std::to_string(dynamic_zones_entry.compass_y)); - update_values.push_back(columns[6] + " = " + std::to_string(dynamic_zones_entry.compass_z)); - update_values.push_back(columns[7] + " = " + std::to_string(dynamic_zones_entry.safe_return_zone_id)); - update_values.push_back(columns[8] + " = " + std::to_string(dynamic_zones_entry.safe_return_x)); - update_values.push_back(columns[9] + " = " + std::to_string(dynamic_zones_entry.safe_return_y)); - update_values.push_back(columns[10] + " = " + std::to_string(dynamic_zones_entry.safe_return_z)); - update_values.push_back(columns[11] + " = " + std::to_string(dynamic_zones_entry.safe_return_heading)); - update_values.push_back(columns[12] + " = " + std::to_string(dynamic_zones_entry.zone_in_x)); - update_values.push_back(columns[13] + " = " + std::to_string(dynamic_zones_entry.zone_in_y)); - update_values.push_back(columns[14] + " = " + std::to_string(dynamic_zones_entry.zone_in_z)); - update_values.push_back(columns[15] + " = " + std::to_string(dynamic_zones_entry.zone_in_heading)); - update_values.push_back(columns[16] + " = " + std::to_string(dynamic_zones_entry.has_zone_in)); + update_values.push_back(columns[3] + " = '" + EscapeString(dynamic_zones_entry.uuid) + "'"); + update_values.push_back(columns[4] + " = '" + EscapeString(dynamic_zones_entry.name) + "'"); + update_values.push_back(columns[5] + " = " + std::to_string(dynamic_zones_entry.leader_id)); + update_values.push_back(columns[6] + " = " + std::to_string(dynamic_zones_entry.min_players)); + update_values.push_back(columns[7] + " = " + std::to_string(dynamic_zones_entry.max_players)); + update_values.push_back(columns[8] + " = " + std::to_string(dynamic_zones_entry.compass_zone_id)); + update_values.push_back(columns[9] + " = " + std::to_string(dynamic_zones_entry.compass_x)); + update_values.push_back(columns[10] + " = " + std::to_string(dynamic_zones_entry.compass_y)); + update_values.push_back(columns[11] + " = " + std::to_string(dynamic_zones_entry.compass_z)); + update_values.push_back(columns[12] + " = " + std::to_string(dynamic_zones_entry.safe_return_zone_id)); + update_values.push_back(columns[13] + " = " + std::to_string(dynamic_zones_entry.safe_return_x)); + update_values.push_back(columns[14] + " = " + std::to_string(dynamic_zones_entry.safe_return_y)); + update_values.push_back(columns[15] + " = " + std::to_string(dynamic_zones_entry.safe_return_z)); + update_values.push_back(columns[16] + " = " + std::to_string(dynamic_zones_entry.safe_return_heading)); + update_values.push_back(columns[17] + " = " + std::to_string(dynamic_zones_entry.zone_in_x)); + update_values.push_back(columns[18] + " = " + std::to_string(dynamic_zones_entry.zone_in_y)); + update_values.push_back(columns[19] + " = " + std::to_string(dynamic_zones_entry.zone_in_z)); + update_values.push_back(columns[20] + " = " + std::to_string(dynamic_zones_entry.zone_in_heading)); + update_values.push_back(columns[21] + " = " + std::to_string(dynamic_zones_entry.has_zone_in)); auto results = db.QueryDatabase( fmt::format( @@ -239,6 +264,11 @@ public: insert_values.push_back(std::to_string(dynamic_zones_entry.id)); insert_values.push_back(std::to_string(dynamic_zones_entry.instance_id)); insert_values.push_back(std::to_string(dynamic_zones_entry.type)); + insert_values.push_back("'" + EscapeString(dynamic_zones_entry.uuid) + "'"); + insert_values.push_back("'" + EscapeString(dynamic_zones_entry.name) + "'"); + insert_values.push_back(std::to_string(dynamic_zones_entry.leader_id)); + insert_values.push_back(std::to_string(dynamic_zones_entry.min_players)); + insert_values.push_back(std::to_string(dynamic_zones_entry.max_players)); insert_values.push_back(std::to_string(dynamic_zones_entry.compass_zone_id)); insert_values.push_back(std::to_string(dynamic_zones_entry.compass_x)); insert_values.push_back(std::to_string(dynamic_zones_entry.compass_y)); @@ -285,6 +315,11 @@ public: insert_values.push_back(std::to_string(dynamic_zones_entry.id)); insert_values.push_back(std::to_string(dynamic_zones_entry.instance_id)); insert_values.push_back(std::to_string(dynamic_zones_entry.type)); + insert_values.push_back("'" + EscapeString(dynamic_zones_entry.uuid) + "'"); + insert_values.push_back("'" + EscapeString(dynamic_zones_entry.name) + "'"); + insert_values.push_back(std::to_string(dynamic_zones_entry.leader_id)); + insert_values.push_back(std::to_string(dynamic_zones_entry.min_players)); + insert_values.push_back(std::to_string(dynamic_zones_entry.max_players)); insert_values.push_back(std::to_string(dynamic_zones_entry.compass_zone_id)); insert_values.push_back(std::to_string(dynamic_zones_entry.compass_x)); insert_values.push_back(std::to_string(dynamic_zones_entry.compass_y)); @@ -335,20 +370,25 @@ public: entry.id = atoi(row[0]); entry.instance_id = atoi(row[1]); entry.type = atoi(row[2]); - entry.compass_zone_id = atoi(row[3]); - entry.compass_x = static_cast(atof(row[4])); - entry.compass_y = static_cast(atof(row[5])); - entry.compass_z = static_cast(atof(row[6])); - entry.safe_return_zone_id = atoi(row[7]); - entry.safe_return_x = static_cast(atof(row[8])); - entry.safe_return_y = static_cast(atof(row[9])); - entry.safe_return_z = static_cast(atof(row[10])); - entry.safe_return_heading = static_cast(atof(row[11])); - entry.zone_in_x = static_cast(atof(row[12])); - entry.zone_in_y = static_cast(atof(row[13])); - entry.zone_in_z = static_cast(atof(row[14])); - entry.zone_in_heading = static_cast(atof(row[15])); - entry.has_zone_in = atoi(row[16]); + entry.uuid = row[3] ? row[3] : ""; + entry.name = row[4] ? row[4] : ""; + entry.leader_id = atoi(row[5]); + entry.min_players = atoi(row[6]); + entry.max_players = atoi(row[7]); + entry.compass_zone_id = atoi(row[8]); + entry.compass_x = static_cast(atof(row[9])); + entry.compass_y = static_cast(atof(row[10])); + entry.compass_z = static_cast(atof(row[11])); + entry.safe_return_zone_id = atoi(row[12]); + entry.safe_return_x = static_cast(atof(row[13])); + entry.safe_return_y = static_cast(atof(row[14])); + entry.safe_return_z = static_cast(atof(row[15])); + entry.safe_return_heading = static_cast(atof(row[16])); + entry.zone_in_x = static_cast(atof(row[17])); + entry.zone_in_y = static_cast(atof(row[18])); + entry.zone_in_z = static_cast(atof(row[19])); + entry.zone_in_heading = static_cast(atof(row[20])); + entry.has_zone_in = atoi(row[21]); all_entries.push_back(entry); } @@ -376,20 +416,25 @@ public: entry.id = atoi(row[0]); entry.instance_id = atoi(row[1]); entry.type = atoi(row[2]); - entry.compass_zone_id = atoi(row[3]); - entry.compass_x = static_cast(atof(row[4])); - entry.compass_y = static_cast(atof(row[5])); - entry.compass_z = static_cast(atof(row[6])); - entry.safe_return_zone_id = atoi(row[7]); - entry.safe_return_x = static_cast(atof(row[8])); - entry.safe_return_y = static_cast(atof(row[9])); - entry.safe_return_z = static_cast(atof(row[10])); - entry.safe_return_heading = static_cast(atof(row[11])); - entry.zone_in_x = static_cast(atof(row[12])); - entry.zone_in_y = static_cast(atof(row[13])); - entry.zone_in_z = static_cast(atof(row[14])); - entry.zone_in_heading = static_cast(atof(row[15])); - entry.has_zone_in = atoi(row[16]); + entry.uuid = row[3] ? row[3] : ""; + entry.name = row[4] ? row[4] : ""; + entry.leader_id = atoi(row[5]); + entry.min_players = atoi(row[6]); + entry.max_players = atoi(row[7]); + entry.compass_zone_id = atoi(row[8]); + entry.compass_x = static_cast(atof(row[9])); + entry.compass_y = static_cast(atof(row[10])); + entry.compass_z = static_cast(atof(row[11])); + entry.safe_return_zone_id = atoi(row[12]); + entry.safe_return_x = static_cast(atof(row[13])); + entry.safe_return_y = static_cast(atof(row[14])); + entry.safe_return_z = static_cast(atof(row[15])); + entry.safe_return_heading = static_cast(atof(row[16])); + entry.zone_in_x = static_cast(atof(row[17])); + entry.zone_in_y = static_cast(atof(row[18])); + entry.zone_in_z = static_cast(atof(row[19])); + entry.zone_in_heading = static_cast(atof(row[20])); + entry.has_zone_in = atoi(row[21]); all_entries.push_back(entry); } diff --git a/common/repositories/base/base_eventlog_repository.h b/common/repositories/base/base_eventlog_repository.h index 999fb455b..25b6ea1cc 100644 --- a/common/repositories/base/base_eventlog_repository.h +++ b/common/repositories/base/base_eventlog_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ @@ -89,7 +89,7 @@ public: entry.status = 0; entry.charname = ""; entry.target = "None"; - entry.time = current_timestamp(); + entry.time = ""; entry.descriptiontype = ""; entry.description = ""; entry.event_nid = 0; diff --git a/common/repositories/base/base_expedition_lockouts_repository.h b/common/repositories/base/base_expedition_lockouts_repository.h index f2f2bd5be..68a606923 100644 --- a/common/repositories/base/base_expedition_lockouts_repository.h +++ b/common/repositories/base/base_expedition_lockouts_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ @@ -78,7 +78,7 @@ public: entry.id = 0; entry.expedition_id = 0; entry.event_name = ""; - entry.expire_time = current_timestamp(); + entry.expire_time = ""; entry.duration = 0; entry.from_expedition_uuid = ""; diff --git a/common/repositories/base/base_expeditions_repository.h b/common/repositories/base/base_expeditions_repository.h index 8fea9a698..f77be8bfa 100644 --- a/common/repositories/base/base_expeditions_repository.h +++ b/common/repositories/base/base_expeditions_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ @@ -18,15 +18,10 @@ class BaseExpeditionsRepository { public: struct Expeditions { - int id; - std::string uuid; - int dynamic_zone_id; - std::string expedition_name; - int leader_id; - int min_players; - int max_players; - int add_replay_on_join; - int is_locked; + int id; + int dynamic_zone_id; + int add_replay_on_join; + int is_locked; }; static std::string PrimaryKey() @@ -38,12 +33,7 @@ public: { return { "id", - "uuid", "dynamic_zone_id", - "expedition_name", - "leader_id", - "min_players", - "max_players", "add_replay_on_join", "is_locked", }; @@ -82,12 +72,7 @@ public: Expeditions entry{}; entry.id = 0; - entry.uuid = ""; entry.dynamic_zone_id = 0; - entry.expedition_name = ""; - entry.leader_id = 0; - entry.min_players = 0; - entry.max_players = 0; entry.add_replay_on_join = 1; entry.is_locked = 0; @@ -126,14 +111,9 @@ public: Expeditions entry{}; entry.id = atoi(row[0]); - entry.uuid = row[1] ? row[1] : ""; - entry.dynamic_zone_id = atoi(row[2]); - entry.expedition_name = row[3] ? row[3] : ""; - entry.leader_id = atoi(row[4]); - entry.min_players = atoi(row[5]); - entry.max_players = atoi(row[6]); - entry.add_replay_on_join = atoi(row[7]); - entry.is_locked = atoi(row[8]); + entry.dynamic_zone_id = atoi(row[1]); + entry.add_replay_on_join = atoi(row[2]); + entry.is_locked = atoi(row[3]); return entry; } @@ -167,14 +147,9 @@ public: auto columns = Columns(); - update_values.push_back(columns[1] + " = '" + EscapeString(expeditions_entry.uuid) + "'"); - update_values.push_back(columns[2] + " = " + std::to_string(expeditions_entry.dynamic_zone_id)); - update_values.push_back(columns[3] + " = '" + EscapeString(expeditions_entry.expedition_name) + "'"); - update_values.push_back(columns[4] + " = " + std::to_string(expeditions_entry.leader_id)); - update_values.push_back(columns[5] + " = " + std::to_string(expeditions_entry.min_players)); - update_values.push_back(columns[6] + " = " + std::to_string(expeditions_entry.max_players)); - update_values.push_back(columns[7] + " = " + std::to_string(expeditions_entry.add_replay_on_join)); - update_values.push_back(columns[8] + " = " + std::to_string(expeditions_entry.is_locked)); + update_values.push_back(columns[1] + " = " + std::to_string(expeditions_entry.dynamic_zone_id)); + update_values.push_back(columns[2] + " = " + std::to_string(expeditions_entry.add_replay_on_join)); + update_values.push_back(columns[3] + " = " + std::to_string(expeditions_entry.is_locked)); auto results = db.QueryDatabase( fmt::format( @@ -197,12 +172,7 @@ public: std::vector insert_values; insert_values.push_back(std::to_string(expeditions_entry.id)); - insert_values.push_back("'" + EscapeString(expeditions_entry.uuid) + "'"); insert_values.push_back(std::to_string(expeditions_entry.dynamic_zone_id)); - insert_values.push_back("'" + EscapeString(expeditions_entry.expedition_name) + "'"); - insert_values.push_back(std::to_string(expeditions_entry.leader_id)); - insert_values.push_back(std::to_string(expeditions_entry.min_players)); - insert_values.push_back(std::to_string(expeditions_entry.max_players)); insert_values.push_back(std::to_string(expeditions_entry.add_replay_on_join)); insert_values.push_back(std::to_string(expeditions_entry.is_locked)); @@ -235,12 +205,7 @@ public: std::vector insert_values; insert_values.push_back(std::to_string(expeditions_entry.id)); - insert_values.push_back("'" + EscapeString(expeditions_entry.uuid) + "'"); insert_values.push_back(std::to_string(expeditions_entry.dynamic_zone_id)); - insert_values.push_back("'" + EscapeString(expeditions_entry.expedition_name) + "'"); - insert_values.push_back(std::to_string(expeditions_entry.leader_id)); - insert_values.push_back(std::to_string(expeditions_entry.min_players)); - insert_values.push_back(std::to_string(expeditions_entry.max_players)); insert_values.push_back(std::to_string(expeditions_entry.add_replay_on_join)); insert_values.push_back(std::to_string(expeditions_entry.is_locked)); @@ -277,14 +242,9 @@ public: Expeditions entry{}; entry.id = atoi(row[0]); - entry.uuid = row[1] ? row[1] : ""; - entry.dynamic_zone_id = atoi(row[2]); - entry.expedition_name = row[3] ? row[3] : ""; - entry.leader_id = atoi(row[4]); - entry.min_players = atoi(row[5]); - entry.max_players = atoi(row[6]); - entry.add_replay_on_join = atoi(row[7]); - entry.is_locked = atoi(row[8]); + entry.dynamic_zone_id = atoi(row[1]); + entry.add_replay_on_join = atoi(row[2]); + entry.is_locked = atoi(row[3]); all_entries.push_back(entry); } @@ -310,14 +270,9 @@ public: Expeditions entry{}; entry.id = atoi(row[0]); - entry.uuid = row[1] ? row[1] : ""; - entry.dynamic_zone_id = atoi(row[2]); - entry.expedition_name = row[3] ? row[3] : ""; - entry.leader_id = atoi(row[4]); - entry.min_players = atoi(row[5]); - entry.max_players = atoi(row[6]); - entry.add_replay_on_join = atoi(row[7]); - entry.is_locked = atoi(row[8]); + entry.dynamic_zone_id = atoi(row[1]); + entry.add_replay_on_join = atoi(row[2]); + entry.is_locked = atoi(row[3]); all_entries.push_back(entry); } diff --git a/common/repositories/base/base_faction_base_data_repository.h b/common/repositories/base/base_faction_base_data_repository.h index c5bfc8c43..9357d4ce8 100644 --- a/common/repositories/base/base_faction_base_data_repository.h +++ b/common/repositories/base/base_faction_base_data_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_faction_list_mod_repository.h b/common/repositories/base/base_faction_list_mod_repository.h index 673c6ac96..0181f5ba3 100644 --- a/common/repositories/base/base_faction_list_mod_repository.h +++ b/common/repositories/base/base_faction_list_mod_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_faction_list_repository.h b/common/repositories/base/base_faction_list_repository.h index 32e20bb6c..f985d7aad 100644 --- a/common/repositories/base/base_faction_list_repository.h +++ b/common/repositories/base/base_faction_list_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_faction_values_repository.h b/common/repositories/base/base_faction_values_repository.h index 181779da3..2176d287b 100644 --- a/common/repositories/base/base_faction_values_repository.h +++ b/common/repositories/base/base_faction_values_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_fishing_repository.h b/common/repositories/base/base_fishing_repository.h index 6ee2bf098..2c6ad7b43 100644 --- a/common/repositories/base/base_fishing_repository.h +++ b/common/repositories/base/base_fishing_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_forage_repository.h b/common/repositories/base/base_forage_repository.h index 0aa684b1b..4f80658cc 100644 --- a/common/repositories/base/base_forage_repository.h +++ b/common/repositories/base/base_forage_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_friends_repository.h b/common/repositories/base/base_friends_repository.h index 187994a85..a01746174 100644 --- a/common/repositories/base/base_friends_repository.h +++ b/common/repositories/base/base_friends_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_global_loot_repository.h b/common/repositories/base/base_global_loot_repository.h index 98170acf5..81ee74549 100644 --- a/common/repositories/base/base_global_loot_repository.h +++ b/common/repositories/base/base_global_loot_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ @@ -27,7 +27,7 @@ public: int rare; int raid; std::string race; - std::string class; + std::string class_; std::string bodytype; std::string zone; int hot_zone; @@ -54,7 +54,7 @@ public: "rare", "raid", "race", - "class", + "`class`", "bodytype", "zone", "hot_zone", @@ -106,7 +106,7 @@ public: entry.rare = 0; entry.raid = 0; entry.race = ""; - entry.class = ""; + entry.class_ = ""; entry.bodytype = ""; entry.zone = ""; entry.hot_zone = 0; @@ -158,7 +158,7 @@ public: entry.rare = atoi(row[6]); entry.raid = atoi(row[7]); entry.race = row[8] ? row[8] : ""; - entry.class = row[9] ? row[9] : ""; + entry.class_ = row[9] ? row[9] : ""; entry.bodytype = row[10] ? row[10] : ""; entry.zone = row[11] ? row[11] : ""; entry.hot_zone = atoi(row[12]); @@ -207,7 +207,7 @@ public: update_values.push_back(columns[6] + " = " + std::to_string(global_loot_entry.rare)); update_values.push_back(columns[7] + " = " + std::to_string(global_loot_entry.raid)); update_values.push_back(columns[8] + " = '" + EscapeString(global_loot_entry.race) + "'"); - update_values.push_back(columns[9] + " = '" + EscapeString(global_loot_entry.class) + "'"); + update_values.push_back(columns[9] + " = '" + EscapeString(global_loot_entry.class_) + "'"); update_values.push_back(columns[10] + " = '" + EscapeString(global_loot_entry.bodytype) + "'"); update_values.push_back(columns[11] + " = '" + EscapeString(global_loot_entry.zone) + "'"); update_values.push_back(columns[12] + " = " + std::to_string(global_loot_entry.hot_zone)); @@ -245,7 +245,7 @@ public: insert_values.push_back(std::to_string(global_loot_entry.rare)); insert_values.push_back(std::to_string(global_loot_entry.raid)); insert_values.push_back("'" + EscapeString(global_loot_entry.race) + "'"); - insert_values.push_back("'" + EscapeString(global_loot_entry.class) + "'"); + insert_values.push_back("'" + EscapeString(global_loot_entry.class_) + "'"); insert_values.push_back("'" + EscapeString(global_loot_entry.bodytype) + "'"); insert_values.push_back("'" + EscapeString(global_loot_entry.zone) + "'"); insert_values.push_back(std::to_string(global_loot_entry.hot_zone)); @@ -291,7 +291,7 @@ public: insert_values.push_back(std::to_string(global_loot_entry.rare)); insert_values.push_back(std::to_string(global_loot_entry.raid)); insert_values.push_back("'" + EscapeString(global_loot_entry.race) + "'"); - insert_values.push_back("'" + EscapeString(global_loot_entry.class) + "'"); + insert_values.push_back("'" + EscapeString(global_loot_entry.class_) + "'"); insert_values.push_back("'" + EscapeString(global_loot_entry.bodytype) + "'"); insert_values.push_back("'" + EscapeString(global_loot_entry.zone) + "'"); insert_values.push_back(std::to_string(global_loot_entry.hot_zone)); @@ -341,7 +341,7 @@ public: entry.rare = atoi(row[6]); entry.raid = atoi(row[7]); entry.race = row[8] ? row[8] : ""; - entry.class = row[9] ? row[9] : ""; + entry.class_ = row[9] ? row[9] : ""; entry.bodytype = row[10] ? row[10] : ""; entry.zone = row[11] ? row[11] : ""; entry.hot_zone = atoi(row[12]); @@ -382,7 +382,7 @@ public: entry.rare = atoi(row[6]); entry.raid = atoi(row[7]); entry.race = row[8] ? row[8] : ""; - entry.class = row[9] ? row[9] : ""; + entry.class_ = row[9] ? row[9] : ""; entry.bodytype = row[10] ? row[10] : ""; entry.zone = row[11] ? row[11] : ""; entry.hot_zone = atoi(row[12]); diff --git a/common/repositories/base/base_gm_ips_repository.h b/common/repositories/base/base_gm_ips_repository.h index 56027c7a2..5fa6b1f54 100644 --- a/common/repositories/base/base_gm_ips_repository.h +++ b/common/repositories/base/base_gm_ips_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_goallists_repository.h b/common/repositories/base/base_goallists_repository.h index c736d9e01..ff937c169 100644 --- a/common/repositories/base/base_goallists_repository.h +++ b/common/repositories/base/base_goallists_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_graveyard_repository.h b/common/repositories/base/base_graveyard_repository.h index 500775a94..f104e2236 100644 --- a/common/repositories/base/base_graveyard_repository.h +++ b/common/repositories/base/base_graveyard_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_ground_spawns_repository.h b/common/repositories/base/base_ground_spawns_repository.h index 6b2a0d5cc..4d8b9168d 100644 --- a/common/repositories/base/base_ground_spawns_repository.h +++ b/common/repositories/base/base_ground_spawns_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_group_id_repository.h b/common/repositories/base/base_group_id_repository.h index 8005f003e..313bfffbe 100644 --- a/common/repositories/base/base_group_id_repository.h +++ b/common/repositories/base/base_group_id_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_group_leaders_repository.h b/common/repositories/base/base_group_leaders_repository.h index 97e7028cd..d549adaaf 100644 --- a/common/repositories/base/base_group_leaders_repository.h +++ b/common/repositories/base/base_group_leaders_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_guild_members_repository.h b/common/repositories/base/base_guild_members_repository.h index 931d0bdf6..577817241 100644 --- a/common/repositories/base/base_guild_members_repository.h +++ b/common/repositories/base/base_guild_members_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_guild_ranks_repository.h b/common/repositories/base/base_guild_ranks_repository.h index b608cfaa5..cbf7763f7 100644 --- a/common/repositories/base/base_guild_ranks_repository.h +++ b/common/repositories/base/base_guild_ranks_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_guild_relations_repository.h b/common/repositories/base/base_guild_relations_repository.h index 2ddcd854f..5afe63413 100644 --- a/common/repositories/base/base_guild_relations_repository.h +++ b/common/repositories/base/base_guild_relations_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_guilds_repository.h b/common/repositories/base/base_guilds_repository.h index e1cd14f14..b38f223fc 100644 --- a/common/repositories/base/base_guilds_repository.h +++ b/common/repositories/base/base_guilds_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_hackers_repository.h b/common/repositories/base/base_hackers_repository.h index 4a492deac..3d25dc90a 100644 --- a/common/repositories/base/base_hackers_repository.h +++ b/common/repositories/base/base_hackers_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ @@ -80,7 +80,7 @@ public: entry.name = ""; entry.hacked = ""; entry.zone = ""; - entry.date = current_timestamp(); + entry.date = ""; return entry; } diff --git a/common/repositories/base/base_instance_list_player_repository.h b/common/repositories/base/base_instance_list_player_repository.h index 4b8aed4ee..92e4687b1 100644 --- a/common/repositories/base/base_instance_list_player_repository.h +++ b/common/repositories/base/base_instance_list_player_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_instance_list_repository.h b/common/repositories/base/base_instance_list_repository.h index 55b31703f..bdf7abfa6 100644 --- a/common/repositories/base/base_instance_list_repository.h +++ b/common/repositories/base/base_instance_list_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_inventory_repository.h b/common/repositories/base/base_inventory_repository.h index 62308ebdf..d17c7ff62 100644 --- a/common/repositories/base/base_inventory_repository.h +++ b/common/repositories/base/base_inventory_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_inventory_snapshots_repository.h b/common/repositories/base/base_inventory_snapshots_repository.h index 4d34062f3..ec91303b1 100644 --- a/common/repositories/base/base_inventory_snapshots_repository.h +++ b/common/repositories/base/base_inventory_snapshots_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_ip_exemptions_repository.h b/common/repositories/base/base_ip_exemptions_repository.h index dadd49b8e..6952c4dab 100644 --- a/common/repositories/base/base_ip_exemptions_repository.h +++ b/common/repositories/base/base_ip_exemptions_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_item_tick_repository.h b/common/repositories/base/base_item_tick_repository.h index 77b0ec71e..2579c3c40 100644 --- a/common/repositories/base/base_item_tick_repository.h +++ b/common/repositories/base/base_item_tick_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_items_repository.h b/common/repositories/base/base_items_repository.h index da124eca0..8bbbd979e 100644 --- a/common/repositories/base/base_items_repository.h +++ b/common/repositories/base/base_items_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ @@ -717,7 +717,7 @@ public: entry.itemclass = 0; entry.itemtype = 0; entry.ldonprice = 0; - entry.ldontheme = 0; + entry.ldontheme = LDoNThemes::Unused; entry.ldonsold = 0; entry.light = 0; entry.lore = ""; @@ -797,8 +797,8 @@ public: entry.scrolllevel2 = 0; entry.scrolllevel = 0; entry.UNK157 = 0; - entry.serialized = 0; - entry.verified = 0; + entry.serialized = ""; + entry.verified = ""; entry.serialization = ""; entry.source = ""; entry.UNK033 = 0; diff --git a/common/repositories/base/base_ldon_trap_entries_repository.h b/common/repositories/base/base_ldon_trap_entries_repository.h index 05fc73bf5..a9e8b9052 100644 --- a/common/repositories/base/base_ldon_trap_entries_repository.h +++ b/common/repositories/base/base_ldon_trap_entries_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_ldon_trap_templates_repository.h b/common/repositories/base/base_ldon_trap_templates_repository.h index 27def1890..022482232 100644 --- a/common/repositories/base/base_ldon_trap_templates_repository.h +++ b/common/repositories/base/base_ldon_trap_templates_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_level_exp_mods_repository.h b/common/repositories/base/base_level_exp_mods_repository.h index eaa4a5f4a..410d4d25c 100644 --- a/common/repositories/base/base_level_exp_mods_repository.h +++ b/common/repositories/base/base_level_exp_mods_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_lfguild_repository.h b/common/repositories/base/base_lfguild_repository.h index 10e2f0682..be7337579 100644 --- a/common/repositories/base/base_lfguild_repository.h +++ b/common/repositories/base/base_lfguild_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_login_accounts_repository.h b/common/repositories/base/base_login_accounts_repository.h index 25dc167d1..7b2e68f11 100644 --- a/common/repositories/base/base_login_accounts_repository.h +++ b/common/repositories/base/base_login_accounts_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ @@ -87,9 +87,9 @@ public: entry.account_email = ""; entry.source_loginserver = ""; entry.last_ip_address = ""; - entry.last_login_date = 0; - entry.created_at = 0; - entry.updated_at = current_timestamp(); + entry.last_login_date = ""; + entry.created_at = ""; + entry.updated_at = ""; return entry; } diff --git a/common/repositories/base/base_login_api_tokens_repository.h b/common/repositories/base/base_login_api_tokens_repository.h index 88ed5c74a..7d5f7f98f 100644 --- a/common/repositories/base/base_login_api_tokens_repository.h +++ b/common/repositories/base/base_login_api_tokens_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ @@ -79,8 +79,8 @@ public: entry.token = ""; entry.can_write = 0; entry.can_read = 0; - entry.created_at = 0; - entry.updated_at = current_timestamp(); + entry.created_at = ""; + entry.updated_at = ""; return entry; } diff --git a/common/repositories/base/base_login_server_admins_repository.h b/common/repositories/base/base_login_server_admins_repository.h index 771eb014f..be6b3bcf7 100644 --- a/common/repositories/base/base_login_server_admins_repository.h +++ b/common/repositories/base/base_login_server_admins_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ @@ -85,7 +85,7 @@ public: entry.first_name = ""; entry.last_name = ""; entry.email = ""; - entry.registration_date = 0; + entry.registration_date = ""; entry.registration_ip_address = ""; return entry; diff --git a/common/repositories/base/base_login_server_list_types_repository.h b/common/repositories/base/base_login_server_list_types_repository.h index 16b47381c..443182c05 100644 --- a/common/repositories/base/base_login_server_list_types_repository.h +++ b/common/repositories/base/base_login_server_list_types_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_login_world_servers_repository.h b/common/repositories/base/base_login_world_servers_repository.h index 96dc1f8ae..cce6a0bff 100644 --- a/common/repositories/base/base_login_world_servers_repository.h +++ b/common/repositories/base/base_login_world_servers_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ @@ -88,7 +88,7 @@ public: entry.short_name = ""; entry.tag_description = ""; entry.login_server_list_type_id = 0; - entry.last_login_date = 0; + entry.last_login_date = ""; entry.last_ip_address = ""; entry.login_server_admin_id = 0; entry.is_server_trusted = 0; diff --git a/common/repositories/base/base_logsys_categories_repository.h b/common/repositories/base/base_logsys_categories_repository.h index 4077e32f7..fd15aec24 100644 --- a/common/repositories/base/base_logsys_categories_repository.h +++ b/common/repositories/base/base_logsys_categories_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_lootdrop_entries_repository.h b/common/repositories/base/base_lootdrop_entries_repository.h index 524b00588..4be4630a3 100644 --- a/common/repositories/base/base_lootdrop_entries_repository.h +++ b/common/repositories/base/base_lootdrop_entries_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_lootdrop_repository.h b/common/repositories/base/base_lootdrop_repository.h index abacd5f8b..d70c3201c 100644 --- a/common/repositories/base/base_lootdrop_repository.h +++ b/common/repositories/base/base_lootdrop_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_loottable_entries_repository.h b/common/repositories/base/base_loottable_entries_repository.h index e8143e7e3..67e073be7 100644 --- a/common/repositories/base/base_loottable_entries_repository.h +++ b/common/repositories/base/base_loottable_entries_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_loottable_repository.h b/common/repositories/base/base_loottable_repository.h index 88ff30ec9..a41229069 100644 --- a/common/repositories/base/base_loottable_repository.h +++ b/common/repositories/base/base_loottable_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_mail_repository.h b/common/repositories/base/base_mail_repository.h index 674881b64..2123e6396 100644 --- a/common/repositories/base/base_mail_repository.h +++ b/common/repositories/base/base_mail_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_merchantlist_repository.h b/common/repositories/base/base_merchantlist_repository.h index 437b419cd..f7dc02df6 100644 --- a/common/repositories/base/base_merchantlist_repository.h +++ b/common/repositories/base/base_merchantlist_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_merchantlist_temp_repository.h b/common/repositories/base/base_merchantlist_temp_repository.h index 1db32b796..21626aa3d 100644 --- a/common/repositories/base/base_merchantlist_temp_repository.h +++ b/common/repositories/base/base_merchantlist_temp_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_name_filter_repository.h b/common/repositories/base/base_name_filter_repository.h index 5f9c19a8b..984f2b142 100644 --- a/common/repositories/base/base_name_filter_repository.h +++ b/common/repositories/base/base_name_filter_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_npc_emotes_repository.h b/common/repositories/base/base_npc_emotes_repository.h index cc0503d16..6660ed59e 100644 --- a/common/repositories/base/base_npc_emotes_repository.h +++ b/common/repositories/base/base_npc_emotes_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_npc_faction_entries_repository.h b/common/repositories/base/base_npc_faction_entries_repository.h index a25a9a258..1084b9a5e 100644 --- a/common/repositories/base/base_npc_faction_entries_repository.h +++ b/common/repositories/base/base_npc_faction_entries_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_npc_faction_repository.h b/common/repositories/base/base_npc_faction_repository.h index 5cb58fafa..87bff25d1 100644 --- a/common/repositories/base/base_npc_faction_repository.h +++ b/common/repositories/base/base_npc_faction_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_npc_scale_global_base_repository.h b/common/repositories/base/base_npc_scale_global_base_repository.h index e701c7686..b7eafe57a 100644 --- a/common/repositories/base/base_npc_scale_global_base_repository.h +++ b/common/repositories/base/base_npc_scale_global_base_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_npc_spells_effects_entries_repository.h b/common/repositories/base/base_npc_spells_effects_entries_repository.h index 563176079..f6ca532ee 100644 --- a/common/repositories/base/base_npc_spells_effects_entries_repository.h +++ b/common/repositories/base/base_npc_spells_effects_entries_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_npc_spells_effects_repository.h b/common/repositories/base/base_npc_spells_effects_repository.h index a6b5e52bd..6983a002e 100644 --- a/common/repositories/base/base_npc_spells_effects_repository.h +++ b/common/repositories/base/base_npc_spells_effects_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_npc_spells_entries_repository.h b/common/repositories/base/base_npc_spells_entries_repository.h index 97139ab2b..a165d6965 100644 --- a/common/repositories/base/base_npc_spells_entries_repository.h +++ b/common/repositories/base/base_npc_spells_entries_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_npc_spells_repository.h b/common/repositories/base/base_npc_spells_repository.h index 448683064..e7f9d784e 100644 --- a/common/repositories/base/base_npc_spells_repository.h +++ b/common/repositories/base/base_npc_spells_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_npc_types_repository.h b/common/repositories/base/base_npc_types_repository.h index b127988fa..25020d8a8 100644 --- a/common/repositories/base/base_npc_types_repository.h +++ b/common/repositories/base/base_npc_types_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ @@ -23,7 +23,7 @@ public: std::string lastname; int level; int race; - int class; + int class_; int bodytype; int hp; int mana; @@ -140,6 +140,7 @@ public: int model; int flymode; int always_aggro; + int exp_mod; }; static std::string PrimaryKey() @@ -155,7 +156,7 @@ public: "lastname", "level", "race", - "class", + "`class`", "bodytype", "hp", "mana", @@ -272,6 +273,7 @@ public: "model", "flymode", "always_aggro", + "exp_mod", }; } @@ -312,7 +314,7 @@ public: entry.lastname = ""; entry.level = 0; entry.race = 0; - entry.class = 0; + entry.class_ = 0; entry.bodytype = 1; entry.hp = 0; entry.mana = 0; @@ -429,6 +431,7 @@ public: entry.model = 0; entry.flymode = -1; entry.always_aggro = 0; + entry.exp_mod = 100; return entry; } @@ -469,7 +472,7 @@ public: entry.lastname = row[2] ? row[2] : ""; entry.level = atoi(row[3]); entry.race = atoi(row[4]); - entry.class = atoi(row[5]); + entry.class_ = atoi(row[5]); entry.bodytype = atoi(row[6]); entry.hp = atoi(row[7]); entry.mana = atoi(row[8]); @@ -586,6 +589,7 @@ public: entry.model = atoi(row[119]); entry.flymode = atoi(row[120]); entry.always_aggro = atoi(row[121]); + entry.exp_mod = atoi(row[122]); return entry; } @@ -623,7 +627,7 @@ public: update_values.push_back(columns[2] + " = '" + EscapeString(npc_types_entry.lastname) + "'"); update_values.push_back(columns[3] + " = " + std::to_string(npc_types_entry.level)); update_values.push_back(columns[4] + " = " + std::to_string(npc_types_entry.race)); - update_values.push_back(columns[5] + " = " + std::to_string(npc_types_entry.class)); + update_values.push_back(columns[5] + " = " + std::to_string(npc_types_entry.class_)); update_values.push_back(columns[6] + " = " + std::to_string(npc_types_entry.bodytype)); update_values.push_back(columns[7] + " = " + std::to_string(npc_types_entry.hp)); update_values.push_back(columns[8] + " = " + std::to_string(npc_types_entry.mana)); @@ -740,6 +744,7 @@ public: update_values.push_back(columns[119] + " = " + std::to_string(npc_types_entry.model)); update_values.push_back(columns[120] + " = " + std::to_string(npc_types_entry.flymode)); update_values.push_back(columns[121] + " = " + std::to_string(npc_types_entry.always_aggro)); + update_values.push_back(columns[122] + " = " + std::to_string(npc_types_entry.exp_mod)); auto results = db.QueryDatabase( fmt::format( @@ -766,7 +771,7 @@ public: insert_values.push_back("'" + EscapeString(npc_types_entry.lastname) + "'"); insert_values.push_back(std::to_string(npc_types_entry.level)); insert_values.push_back(std::to_string(npc_types_entry.race)); - insert_values.push_back(std::to_string(npc_types_entry.class)); + insert_values.push_back(std::to_string(npc_types_entry.class_)); insert_values.push_back(std::to_string(npc_types_entry.bodytype)); insert_values.push_back(std::to_string(npc_types_entry.hp)); insert_values.push_back(std::to_string(npc_types_entry.mana)); @@ -883,6 +888,7 @@ public: insert_values.push_back(std::to_string(npc_types_entry.model)); insert_values.push_back(std::to_string(npc_types_entry.flymode)); insert_values.push_back(std::to_string(npc_types_entry.always_aggro)); + insert_values.push_back(std::to_string(npc_types_entry.exp_mod)); auto results = db.QueryDatabase( fmt::format( @@ -917,7 +923,7 @@ public: insert_values.push_back("'" + EscapeString(npc_types_entry.lastname) + "'"); insert_values.push_back(std::to_string(npc_types_entry.level)); insert_values.push_back(std::to_string(npc_types_entry.race)); - insert_values.push_back(std::to_string(npc_types_entry.class)); + insert_values.push_back(std::to_string(npc_types_entry.class_)); insert_values.push_back(std::to_string(npc_types_entry.bodytype)); insert_values.push_back(std::to_string(npc_types_entry.hp)); insert_values.push_back(std::to_string(npc_types_entry.mana)); @@ -1034,6 +1040,7 @@ public: insert_values.push_back(std::to_string(npc_types_entry.model)); insert_values.push_back(std::to_string(npc_types_entry.flymode)); insert_values.push_back(std::to_string(npc_types_entry.always_aggro)); + insert_values.push_back(std::to_string(npc_types_entry.exp_mod)); insert_chunks.push_back("(" + implode(",", insert_values) + ")"); } @@ -1072,7 +1079,7 @@ public: entry.lastname = row[2] ? row[2] : ""; entry.level = atoi(row[3]); entry.race = atoi(row[4]); - entry.class = atoi(row[5]); + entry.class_ = atoi(row[5]); entry.bodytype = atoi(row[6]); entry.hp = atoi(row[7]); entry.mana = atoi(row[8]); @@ -1189,6 +1196,7 @@ public: entry.model = atoi(row[119]); entry.flymode = atoi(row[120]); entry.always_aggro = atoi(row[121]); + entry.exp_mod = atoi(row[122]); all_entries.push_back(entry); } @@ -1218,7 +1226,7 @@ public: entry.lastname = row[2] ? row[2] : ""; entry.level = atoi(row[3]); entry.race = atoi(row[4]); - entry.class = atoi(row[5]); + entry.class_ = atoi(row[5]); entry.bodytype = atoi(row[6]); entry.hp = atoi(row[7]); entry.mana = atoi(row[8]); @@ -1335,6 +1343,7 @@ public: entry.model = atoi(row[119]); entry.flymode = atoi(row[120]); entry.always_aggro = atoi(row[121]); + entry.exp_mod = atoi(row[122]); all_entries.push_back(entry); } diff --git a/common/repositories/base/base_npc_types_tint_repository.h b/common/repositories/base/base_npc_types_tint_repository.h index 3ba577f36..ea01c8d01 100644 --- a/common/repositories/base/base_npc_types_tint_repository.h +++ b/common/repositories/base/base_npc_types_tint_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_object_contents_repository.h b/common/repositories/base/base_object_contents_repository.h index 576429ba0..3ab7c70b8 100644 --- a/common/repositories/base/base_object_contents_repository.h +++ b/common/repositories/base/base_object_contents_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_object_repository.h b/common/repositories/base/base_object_repository.h index a572ed712..dc098db06 100644 --- a/common/repositories/base/base_object_repository.h +++ b/common/repositories/base/base_object_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_perl_event_export_settings_repository.h b/common/repositories/base/base_perl_event_export_settings_repository.h index d58084463..50c0a63f6 100644 --- a/common/repositories/base/base_perl_event_export_settings_repository.h +++ b/common/repositories/base/base_perl_event_export_settings_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_petitions_repository.h b/common/repositories/base/base_petitions_repository.h index 30dd9feaa..36a781ab0 100644 --- a/common/repositories/base/base_petitions_repository.h +++ b/common/repositories/base/base_petitions_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ @@ -33,7 +33,7 @@ public: int checkouts; int unavailables; int ischeckedout; - int senttime; + int64 senttime; }; static std::string PrimaryKey() @@ -161,7 +161,7 @@ public: entry.checkouts = atoi(row[12]); entry.unavailables = atoi(row[13]); entry.ischeckedout = atoi(row[14]); - entry.senttime = atoi(row[15]); + entry.senttime = strtoll(row[15], NULL, 10); return entry; } @@ -340,7 +340,7 @@ public: entry.checkouts = atoi(row[12]); entry.unavailables = atoi(row[13]); entry.ischeckedout = atoi(row[14]); - entry.senttime = atoi(row[15]); + entry.senttime = strtoll(row[15], NULL, 10); all_entries.push_back(entry); } @@ -380,7 +380,7 @@ public: entry.checkouts = atoi(row[12]); entry.unavailables = atoi(row[13]); entry.ischeckedout = atoi(row[14]); - entry.senttime = atoi(row[15]); + entry.senttime = strtoll(row[15], NULL, 10); all_entries.push_back(entry); } diff --git a/common/repositories/base/base_pets_beastlord_data_repository.h b/common/repositories/base/base_pets_beastlord_data_repository.h new file mode 100644 index 000000000..61c7d75ba --- /dev/null +++ b/common/repositories/base/base_pets_beastlord_data_repository.h @@ -0,0 +1,338 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories + */ + +#ifndef EQEMU_BASE_PETS_BEASTLORD_DATA_REPOSITORY_H +#define EQEMU_BASE_PETS_BEASTLORD_DATA_REPOSITORY_H + +#include "../../database.h" +#include "../../string_util.h" + +class BasePetsBeastlordDataRepository { +public: + struct PetsBeastlordData { + int player_race; + int pet_race; + int texture; + int helm_texture; + int gender; + float size_modifier; + int face; + }; + + static std::string PrimaryKey() + { + return std::string("player_race"); + } + + static std::vector Columns() + { + return { + "player_race", + "pet_race", + "texture", + "helm_texture", + "gender", + "size_modifier", + "face", + }; + } + + static std::string ColumnsRaw() + { + return std::string(implode(", ", Columns())); + } + + static std::string TableName() + { + return std::string("pets_beastlord_data"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + ColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static PetsBeastlordData NewEntity() + { + PetsBeastlordData entry{}; + + entry.player_race = 1; + entry.pet_race = 42; + entry.texture = 0; + entry.helm_texture = 0; + entry.gender = 2; + entry.size_modifier = 1; + entry.face = 0; + + return entry; + } + + static PetsBeastlordData GetPetsBeastlordDataEntry( + const std::vector &pets_beastlord_datas, + int pets_beastlord_data_id + ) + { + for (auto &pets_beastlord_data : pets_beastlord_datas) { + if (pets_beastlord_data.player_race == pets_beastlord_data_id) { + return pets_beastlord_data; + } + } + + return NewEntity(); + } + + static PetsBeastlordData FindOne( + Database& db, + int pets_beastlord_data_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE id = {} LIMIT 1", + BaseSelect(), + pets_beastlord_data_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + PetsBeastlordData entry{}; + + entry.player_race = atoi(row[0]); + entry.pet_race = atoi(row[1]); + entry.texture = atoi(row[2]); + entry.helm_texture = atoi(row[3]); + entry.gender = atoi(row[4]); + entry.size_modifier = static_cast(atof(row[5])); + entry.face = atoi(row[6]); + + return entry; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int pets_beastlord_data_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + pets_beastlord_data_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + PetsBeastlordData pets_beastlord_data_entry + ) + { + std::vector update_values; + + auto columns = Columns(); + + update_values.push_back(columns[0] + " = " + std::to_string(pets_beastlord_data_entry.player_race)); + update_values.push_back(columns[1] + " = " + std::to_string(pets_beastlord_data_entry.pet_race)); + update_values.push_back(columns[2] + " = " + std::to_string(pets_beastlord_data_entry.texture)); + update_values.push_back(columns[3] + " = " + std::to_string(pets_beastlord_data_entry.helm_texture)); + update_values.push_back(columns[4] + " = " + std::to_string(pets_beastlord_data_entry.gender)); + update_values.push_back(columns[5] + " = " + std::to_string(pets_beastlord_data_entry.size_modifier)); + update_values.push_back(columns[6] + " = " + std::to_string(pets_beastlord_data_entry.face)); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + implode(", ", update_values), + PrimaryKey(), + pets_beastlord_data_entry.player_race + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static PetsBeastlordData InsertOne( + Database& db, + PetsBeastlordData pets_beastlord_data_entry + ) + { + std::vector insert_values; + + insert_values.push_back(std::to_string(pets_beastlord_data_entry.player_race)); + insert_values.push_back(std::to_string(pets_beastlord_data_entry.pet_race)); + insert_values.push_back(std::to_string(pets_beastlord_data_entry.texture)); + insert_values.push_back(std::to_string(pets_beastlord_data_entry.helm_texture)); + insert_values.push_back(std::to_string(pets_beastlord_data_entry.gender)); + insert_values.push_back(std::to_string(pets_beastlord_data_entry.size_modifier)); + insert_values.push_back(std::to_string(pets_beastlord_data_entry.face)); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + implode(",", insert_values) + ) + ); + + if (results.Success()) { + pets_beastlord_data_entry.player_race = results.LastInsertedID(); + return pets_beastlord_data_entry; + } + + pets_beastlord_data_entry = NewEntity(); + + return pets_beastlord_data_entry; + } + + static int InsertMany( + Database& db, + std::vector pets_beastlord_data_entries + ) + { + std::vector insert_chunks; + + for (auto &pets_beastlord_data_entry: pets_beastlord_data_entries) { + std::vector insert_values; + + insert_values.push_back(std::to_string(pets_beastlord_data_entry.player_race)); + insert_values.push_back(std::to_string(pets_beastlord_data_entry.pet_race)); + insert_values.push_back(std::to_string(pets_beastlord_data_entry.texture)); + insert_values.push_back(std::to_string(pets_beastlord_data_entry.helm_texture)); + insert_values.push_back(std::to_string(pets_beastlord_data_entry.gender)); + insert_values.push_back(std::to_string(pets_beastlord_data_entry.size_modifier)); + insert_values.push_back(std::to_string(pets_beastlord_data_entry.face)); + + insert_chunks.push_back("(" + implode(",", insert_values) + ")"); + } + + std::vector insert_values; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + PetsBeastlordData entry{}; + + entry.player_race = atoi(row[0]); + entry.pet_race = atoi(row[1]); + entry.texture = atoi(row[2]); + entry.helm_texture = atoi(row[3]); + entry.gender = atoi(row[4]); + entry.size_modifier = static_cast(atof(row[5])); + entry.face = atoi(row[6]); + + all_entries.push_back(entry); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, std::string where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + PetsBeastlordData entry{}; + + entry.player_race = atoi(row[0]); + entry.pet_race = atoi(row[1]); + entry.texture = atoi(row[2]); + entry.helm_texture = atoi(row[3]); + entry.gender = atoi(row[4]); + entry.size_modifier = static_cast(atof(row[5])); + entry.face = atoi(row[6]); + + all_entries.push_back(entry); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, std::string where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + +}; + +#endif //EQEMU_BASE_PETS_BEASTLORD_DATA_REPOSITORY_H diff --git a/common/repositories/base/base_pets_equipmentset_entries_repository.h b/common/repositories/base/base_pets_equipmentset_entries_repository.h index ecd9b2643..1999b35b7 100644 --- a/common/repositories/base/base_pets_equipmentset_entries_repository.h +++ b/common/repositories/base/base_pets_equipmentset_entries_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_pets_equipmentset_repository.h b/common/repositories/base/base_pets_equipmentset_repository.h index 0f27a879f..ebb04c02c 100644 --- a/common/repositories/base/base_pets_equipmentset_repository.h +++ b/common/repositories/base/base_pets_equipmentset_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_pets_repository.h b/common/repositories/base/base_pets_repository.h index 75681e6c4..1dc25f333 100644 --- a/common/repositories/base/base_pets_repository.h +++ b/common/repositories/base/base_pets_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_player_titlesets_repository.h b/common/repositories/base/base_player_titlesets_repository.h index 12c800953..dad10ca0d 100644 --- a/common/repositories/base/base_player_titlesets_repository.h +++ b/common/repositories/base/base_player_titlesets_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_proximities_repository.h b/common/repositories/base/base_proximities_repository.h index 6b1f697b0..8dfff3279 100644 --- a/common/repositories/base/base_proximities_repository.h +++ b/common/repositories/base/base_proximities_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_quest_globals_repository.h b/common/repositories/base/base_quest_globals_repository.h index 0870938fd..7324e7c29 100644 --- a/common/repositories/base/base_quest_globals_repository.h +++ b/common/repositories/base/base_quest_globals_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_raid_details_repository.h b/common/repositories/base/base_raid_details_repository.h index a0f717bc6..a53949908 100644 --- a/common/repositories/base/base_raid_details_repository.h +++ b/common/repositories/base/base_raid_details_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_raid_members_repository.h b/common/repositories/base/base_raid_members_repository.h index cb7fc2c65..88169a5ab 100644 --- a/common/repositories/base/base_raid_members_repository.h +++ b/common/repositories/base/base_raid_members_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_reports_repository.h b/common/repositories/base/base_reports_repository.h index caebd4107..89aa474b7 100644 --- a/common/repositories/base/base_reports_repository.h +++ b/common/repositories/base/base_reports_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_respawn_times_repository.h b/common/repositories/base/base_respawn_times_repository.h index df69f2be9..0ca8d525f 100644 --- a/common/repositories/base/base_respawn_times_repository.h +++ b/common/repositories/base/base_respawn_times_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_rule_sets_repository.h b/common/repositories/base/base_rule_sets_repository.h index 31dab96d9..181e064a8 100644 --- a/common/repositories/base/base_rule_sets_repository.h +++ b/common/repositories/base/base_rule_sets_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_rule_values_repository.h b/common/repositories/base/base_rule_values_repository.h index 6bd6fe2d6..821ccfdc2 100644 --- a/common/repositories/base/base_rule_values_repository.h +++ b/common/repositories/base/base_rule_values_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_saylink_repository.h b/common/repositories/base/base_saylink_repository.h index e02b1954c..d9206a137 100644 --- a/common/repositories/base/base_saylink_repository.h +++ b/common/repositories/base/base_saylink_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_server_scheduled_events_repository.h b/common/repositories/base/base_server_scheduled_events_repository.h new file mode 100644 index 000000000..1cc319d6a --- /dev/null +++ b/common/repositories/base/base_server_scheduled_events_repository.h @@ -0,0 +1,427 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories + */ + +#ifndef EQEMU_BASE_SERVER_SCHEDULED_EVENTS_REPOSITORY_H +#define EQEMU_BASE_SERVER_SCHEDULED_EVENTS_REPOSITORY_H + +#include "../../database.h" +#include "../../string_util.h" + +class BaseServerScheduledEventsRepository { +public: + struct ServerScheduledEvents { + int id; + std::string description; + std::string event_type; + std::string event_data; + int minute_start; + int hour_start; + int day_start; + int month_start; + int year_start; + int minute_end; + int hour_end; + int day_end; + int month_end; + int year_end; + std::string cron_expression; + std::string created_at; + std::string deleted_at; + }; + + static std::string PrimaryKey() + { + return std::string("id"); + } + + static std::vector Columns() + { + return { + "id", + "description", + "event_type", + "event_data", + "minute_start", + "hour_start", + "day_start", + "month_start", + "year_start", + "minute_end", + "hour_end", + "day_end", + "month_end", + "year_end", + "cron_expression", + "created_at", + "deleted_at", + }; + } + + static std::string ColumnsRaw() + { + return std::string(implode(", ", Columns())); + } + + static std::string TableName() + { + return std::string("server_scheduled_events"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + ColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static ServerScheduledEvents NewEntity() + { + ServerScheduledEvents entry{}; + + entry.id = 0; + entry.description = ""; + entry.event_type = ""; + entry.event_data = ""; + entry.minute_start = 0; + entry.hour_start = 0; + entry.day_start = 0; + entry.month_start = 0; + entry.year_start = 0; + entry.minute_end = 0; + entry.hour_end = 0; + entry.day_end = 0; + entry.month_end = 0; + entry.year_end = 0; + entry.cron_expression = ""; + entry.created_at = ""; + entry.deleted_at = ""; + + return entry; + } + + static ServerScheduledEvents GetServerScheduledEventsEntry( + const std::vector &server_scheduled_eventss, + int server_scheduled_events_id + ) + { + for (auto &server_scheduled_events : server_scheduled_eventss) { + if (server_scheduled_events.id == server_scheduled_events_id) { + return server_scheduled_events; + } + } + + return NewEntity(); + } + + static ServerScheduledEvents FindOne( + Database& db, + int server_scheduled_events_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE id = {} LIMIT 1", + BaseSelect(), + server_scheduled_events_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + ServerScheduledEvents entry{}; + + entry.id = atoi(row[0]); + entry.description = row[1] ? row[1] : ""; + entry.event_type = row[2] ? row[2] : ""; + entry.event_data = row[3] ? row[3] : ""; + entry.minute_start = atoi(row[4]); + entry.hour_start = atoi(row[5]); + entry.day_start = atoi(row[6]); + entry.month_start = atoi(row[7]); + entry.year_start = atoi(row[8]); + entry.minute_end = atoi(row[9]); + entry.hour_end = atoi(row[10]); + entry.day_end = atoi(row[11]); + entry.month_end = atoi(row[12]); + entry.year_end = atoi(row[13]); + entry.cron_expression = row[14] ? row[14] : ""; + entry.created_at = row[15] ? row[15] : ""; + entry.deleted_at = row[16] ? row[16] : ""; + + return entry; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int server_scheduled_events_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + server_scheduled_events_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + ServerScheduledEvents server_scheduled_events_entry + ) + { + std::vector update_values; + + auto columns = Columns(); + + update_values.push_back(columns[1] + " = '" + EscapeString(server_scheduled_events_entry.description) + "'"); + update_values.push_back(columns[2] + " = '" + EscapeString(server_scheduled_events_entry.event_type) + "'"); + update_values.push_back(columns[3] + " = '" + EscapeString(server_scheduled_events_entry.event_data) + "'"); + update_values.push_back(columns[4] + " = " + std::to_string(server_scheduled_events_entry.minute_start)); + update_values.push_back(columns[5] + " = " + std::to_string(server_scheduled_events_entry.hour_start)); + update_values.push_back(columns[6] + " = " + std::to_string(server_scheduled_events_entry.day_start)); + update_values.push_back(columns[7] + " = " + std::to_string(server_scheduled_events_entry.month_start)); + update_values.push_back(columns[8] + " = " + std::to_string(server_scheduled_events_entry.year_start)); + update_values.push_back(columns[9] + " = " + std::to_string(server_scheduled_events_entry.minute_end)); + update_values.push_back(columns[10] + " = " + std::to_string(server_scheduled_events_entry.hour_end)); + update_values.push_back(columns[11] + " = " + std::to_string(server_scheduled_events_entry.day_end)); + update_values.push_back(columns[12] + " = " + std::to_string(server_scheduled_events_entry.month_end)); + update_values.push_back(columns[13] + " = " + std::to_string(server_scheduled_events_entry.year_end)); + update_values.push_back(columns[14] + " = '" + EscapeString(server_scheduled_events_entry.cron_expression) + "'"); + update_values.push_back(columns[15] + " = '" + EscapeString(server_scheduled_events_entry.created_at) + "'"); + update_values.push_back(columns[16] + " = '" + EscapeString(server_scheduled_events_entry.deleted_at) + "'"); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + implode(", ", update_values), + PrimaryKey(), + server_scheduled_events_entry.id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static ServerScheduledEvents InsertOne( + Database& db, + ServerScheduledEvents server_scheduled_events_entry + ) + { + std::vector insert_values; + + insert_values.push_back(std::to_string(server_scheduled_events_entry.id)); + insert_values.push_back("'" + EscapeString(server_scheduled_events_entry.description) + "'"); + insert_values.push_back("'" + EscapeString(server_scheduled_events_entry.event_type) + "'"); + insert_values.push_back("'" + EscapeString(server_scheduled_events_entry.event_data) + "'"); + insert_values.push_back(std::to_string(server_scheduled_events_entry.minute_start)); + insert_values.push_back(std::to_string(server_scheduled_events_entry.hour_start)); + insert_values.push_back(std::to_string(server_scheduled_events_entry.day_start)); + insert_values.push_back(std::to_string(server_scheduled_events_entry.month_start)); + insert_values.push_back(std::to_string(server_scheduled_events_entry.year_start)); + insert_values.push_back(std::to_string(server_scheduled_events_entry.minute_end)); + insert_values.push_back(std::to_string(server_scheduled_events_entry.hour_end)); + insert_values.push_back(std::to_string(server_scheduled_events_entry.day_end)); + insert_values.push_back(std::to_string(server_scheduled_events_entry.month_end)); + insert_values.push_back(std::to_string(server_scheduled_events_entry.year_end)); + insert_values.push_back("'" + EscapeString(server_scheduled_events_entry.cron_expression) + "'"); + insert_values.push_back("'" + EscapeString(server_scheduled_events_entry.created_at) + "'"); + insert_values.push_back("'" + EscapeString(server_scheduled_events_entry.deleted_at) + "'"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + implode(",", insert_values) + ) + ); + + if (results.Success()) { + server_scheduled_events_entry.id = results.LastInsertedID(); + return server_scheduled_events_entry; + } + + server_scheduled_events_entry = NewEntity(); + + return server_scheduled_events_entry; + } + + static int InsertMany( + Database& db, + std::vector server_scheduled_events_entries + ) + { + std::vector insert_chunks; + + for (auto &server_scheduled_events_entry: server_scheduled_events_entries) { + std::vector insert_values; + + insert_values.push_back(std::to_string(server_scheduled_events_entry.id)); + insert_values.push_back("'" + EscapeString(server_scheduled_events_entry.description) + "'"); + insert_values.push_back("'" + EscapeString(server_scheduled_events_entry.event_type) + "'"); + insert_values.push_back("'" + EscapeString(server_scheduled_events_entry.event_data) + "'"); + insert_values.push_back(std::to_string(server_scheduled_events_entry.minute_start)); + insert_values.push_back(std::to_string(server_scheduled_events_entry.hour_start)); + insert_values.push_back(std::to_string(server_scheduled_events_entry.day_start)); + insert_values.push_back(std::to_string(server_scheduled_events_entry.month_start)); + insert_values.push_back(std::to_string(server_scheduled_events_entry.year_start)); + insert_values.push_back(std::to_string(server_scheduled_events_entry.minute_end)); + insert_values.push_back(std::to_string(server_scheduled_events_entry.hour_end)); + insert_values.push_back(std::to_string(server_scheduled_events_entry.day_end)); + insert_values.push_back(std::to_string(server_scheduled_events_entry.month_end)); + insert_values.push_back(std::to_string(server_scheduled_events_entry.year_end)); + insert_values.push_back("'" + EscapeString(server_scheduled_events_entry.cron_expression) + "'"); + insert_values.push_back("'" + EscapeString(server_scheduled_events_entry.created_at) + "'"); + insert_values.push_back("'" + EscapeString(server_scheduled_events_entry.deleted_at) + "'"); + + insert_chunks.push_back("(" + implode(",", insert_values) + ")"); + } + + std::vector insert_values; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + ServerScheduledEvents entry{}; + + entry.id = atoi(row[0]); + entry.description = row[1] ? row[1] : ""; + entry.event_type = row[2] ? row[2] : ""; + entry.event_data = row[3] ? row[3] : ""; + entry.minute_start = atoi(row[4]); + entry.hour_start = atoi(row[5]); + entry.day_start = atoi(row[6]); + entry.month_start = atoi(row[7]); + entry.year_start = atoi(row[8]); + entry.minute_end = atoi(row[9]); + entry.hour_end = atoi(row[10]); + entry.day_end = atoi(row[11]); + entry.month_end = atoi(row[12]); + entry.year_end = atoi(row[13]); + entry.cron_expression = row[14] ? row[14] : ""; + entry.created_at = row[15] ? row[15] : ""; + entry.deleted_at = row[16] ? row[16] : ""; + + all_entries.push_back(entry); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, std::string where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + ServerScheduledEvents entry{}; + + entry.id = atoi(row[0]); + entry.description = row[1] ? row[1] : ""; + entry.event_type = row[2] ? row[2] : ""; + entry.event_data = row[3] ? row[3] : ""; + entry.minute_start = atoi(row[4]); + entry.hour_start = atoi(row[5]); + entry.day_start = atoi(row[6]); + entry.month_start = atoi(row[7]); + entry.year_start = atoi(row[8]); + entry.minute_end = atoi(row[9]); + entry.hour_end = atoi(row[10]); + entry.day_end = atoi(row[11]); + entry.month_end = atoi(row[12]); + entry.year_end = atoi(row[13]); + entry.cron_expression = row[14] ? row[14] : ""; + entry.created_at = row[15] ? row[15] : ""; + entry.deleted_at = row[16] ? row[16] : ""; + + all_entries.push_back(entry); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, std::string where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + +}; + +#endif //EQEMU_BASE_SERVER_SCHEDULED_EVENTS_REPOSITORY_H diff --git a/common/repositories/base/base_shared_task_activity_state_repository.h b/common/repositories/base/base_shared_task_activity_state_repository.h new file mode 100644 index 000000000..e599f3cb7 --- /dev/null +++ b/common/repositories/base/base_shared_task_activity_state_repository.h @@ -0,0 +1,337 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories + */ + +#ifndef EQEMU_BASE_SHARED_TASK_ACTIVITY_STATE_REPOSITORY_H +#define EQEMU_BASE_SHARED_TASK_ACTIVITY_STATE_REPOSITORY_H + +#include "../../database.h" +#include "../../string_util.h" +#include + +class BaseSharedTaskActivityStateRepository { +public: + struct SharedTaskActivityState { + int64 shared_task_id; + int activity_id; + int done_count; + time_t updated_time; + time_t completed_time; + }; + + static std::string PrimaryKey() + { + return std::string("shared_task_id"); + } + + static std::vector Columns() + { + return { + "shared_task_id", + "activity_id", + "done_count", + "updated_time", + "completed_time", + }; + } + + static std::vector SelectColumns() + { + return { + "shared_task_id", + "activity_id", + "done_count", + "UNIX_TIMESTAMP(updated_time)", + "UNIX_TIMESTAMP(completed_time)", + }; + } + + static std::string ColumnsRaw() + { + return std::string(implode(", ", Columns())); + } + + static std::string SelectColumnsRaw() + { + return std::string(implode(", ", SelectColumns())); + } + + static std::string TableName() + { + return std::string("shared_task_activity_state"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + SelectColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static SharedTaskActivityState NewEntity() + { + SharedTaskActivityState entry{}; + + entry.shared_task_id = 0; + entry.activity_id = 0; + entry.done_count = 0; + entry.updated_time = 0; + entry.completed_time = 0; + + return entry; + } + + static SharedTaskActivityState GetSharedTaskActivityStateEntry( + const std::vector &shared_task_activity_states, + int shared_task_activity_state_id + ) + { + for (auto &shared_task_activity_state : shared_task_activity_states) { + if (shared_task_activity_state.shared_task_id == shared_task_activity_state_id) { + return shared_task_activity_state; + } + } + + return NewEntity(); + } + + static SharedTaskActivityState FindOne( + Database& db, + int shared_task_activity_state_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE id = {} LIMIT 1", + BaseSelect(), + shared_task_activity_state_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + SharedTaskActivityState entry{}; + + entry.shared_task_id = strtoll(row[0], nullptr, 10); + entry.activity_id = atoi(row[1]); + entry.done_count = atoi(row[2]); + entry.updated_time = strtoll(row[3] ? row[3] : "-1", nullptr, 10); + entry.completed_time = strtoll(row[4] ? row[4] : "-1", nullptr, 10); + + return entry; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int shared_task_activity_state_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + shared_task_activity_state_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + SharedTaskActivityState shared_task_activity_state_entry + ) + { + std::vector update_values; + + auto columns = Columns(); + + update_values.push_back(columns[0] + " = " + std::to_string(shared_task_activity_state_entry.shared_task_id)); + update_values.push_back(columns[1] + " = " + std::to_string(shared_task_activity_state_entry.activity_id)); + update_values.push_back(columns[2] + " = " + std::to_string(shared_task_activity_state_entry.done_count)); + update_values.push_back(columns[3] + " = FROM_UNIXTIME(" + (shared_task_activity_state_entry.updated_time > 0 ? std::to_string(shared_task_activity_state_entry.updated_time) : "null") + ")"); + update_values.push_back(columns[4] + " = FROM_UNIXTIME(" + (shared_task_activity_state_entry.completed_time > 0 ? std::to_string(shared_task_activity_state_entry.completed_time) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + implode(", ", update_values), + PrimaryKey(), + shared_task_activity_state_entry.shared_task_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static SharedTaskActivityState InsertOne( + Database& db, + SharedTaskActivityState shared_task_activity_state_entry + ) + { + std::vector insert_values; + + insert_values.push_back(std::to_string(shared_task_activity_state_entry.shared_task_id)); + insert_values.push_back(std::to_string(shared_task_activity_state_entry.activity_id)); + insert_values.push_back(std::to_string(shared_task_activity_state_entry.done_count)); + insert_values.push_back("FROM_UNIXTIME(" + (shared_task_activity_state_entry.updated_time > 0 ? std::to_string(shared_task_activity_state_entry.updated_time) : "null") + ")"); + insert_values.push_back("FROM_UNIXTIME(" + (shared_task_activity_state_entry.completed_time > 0 ? std::to_string(shared_task_activity_state_entry.completed_time) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + implode(",", insert_values) + ) + ); + + if (results.Success()) { + shared_task_activity_state_entry.shared_task_id = results.LastInsertedID(); + return shared_task_activity_state_entry; + } + + shared_task_activity_state_entry = NewEntity(); + + return shared_task_activity_state_entry; + } + + static int InsertMany( + Database& db, + std::vector shared_task_activity_state_entries + ) + { + std::vector insert_chunks; + + for (auto &shared_task_activity_state_entry: shared_task_activity_state_entries) { + std::vector insert_values; + + insert_values.push_back(std::to_string(shared_task_activity_state_entry.shared_task_id)); + insert_values.push_back(std::to_string(shared_task_activity_state_entry.activity_id)); + insert_values.push_back(std::to_string(shared_task_activity_state_entry.done_count)); + insert_values.push_back("FROM_UNIXTIME(" + (shared_task_activity_state_entry.updated_time > 0 ? std::to_string(shared_task_activity_state_entry.updated_time) : "null") + ")"); + insert_values.push_back("FROM_UNIXTIME(" + (shared_task_activity_state_entry.completed_time > 0 ? std::to_string(shared_task_activity_state_entry.completed_time) : "null") + ")"); + + insert_chunks.push_back("(" + implode(",", insert_values) + ")"); + } + + std::vector insert_values; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + SharedTaskActivityState entry{}; + + entry.shared_task_id = strtoll(row[0], nullptr, 10); + entry.activity_id = atoi(row[1]); + entry.done_count = atoi(row[2]); + entry.updated_time = strtoll(row[3] ? row[3] : "-1", nullptr, 10); + entry.completed_time = strtoll(row[4] ? row[4] : "-1", nullptr, 10); + + all_entries.push_back(entry); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, std::string where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + SharedTaskActivityState entry{}; + + entry.shared_task_id = strtoll(row[0], nullptr, 10); + entry.activity_id = atoi(row[1]); + entry.done_count = atoi(row[2]); + entry.updated_time = strtoll(row[3] ? row[3] : "-1", nullptr, 10); + entry.completed_time = strtoll(row[4] ? row[4] : "-1", nullptr, 10); + + all_entries.push_back(entry); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, std::string where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + +}; + +#endif //EQEMU_BASE_SHARED_TASK_ACTIVITY_STATE_REPOSITORY_H diff --git a/common/repositories/base/base_shared_task_dynamic_zones_repository.h b/common/repositories/base/base_shared_task_dynamic_zones_repository.h new file mode 100644 index 000000000..ab60aa54d --- /dev/null +++ b/common/repositories/base/base_shared_task_dynamic_zones_repository.h @@ -0,0 +1,293 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories + */ + +#ifndef EQEMU_BASE_SHARED_TASK_DYNAMIC_ZONES_REPOSITORY_H +#define EQEMU_BASE_SHARED_TASK_DYNAMIC_ZONES_REPOSITORY_H + +#include "../../database.h" +#include "../../string_util.h" + +class BaseSharedTaskDynamicZonesRepository { +public: + struct SharedTaskDynamicZones { + int64 shared_task_id; + int dynamic_zone_id; + }; + + static std::string PrimaryKey() + { + return std::string("shared_task_id"); + } + + static std::vector Columns() + { + return { + "shared_task_id", + "dynamic_zone_id", + }; + } + + static std::string ColumnsRaw() + { + return std::string(implode(", ", Columns())); + } + + static std::string TableName() + { + return std::string("shared_task_dynamic_zones"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + ColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static SharedTaskDynamicZones NewEntity() + { + SharedTaskDynamicZones entry{}; + + entry.shared_task_id = 0; + entry.dynamic_zone_id = 0; + + return entry; + } + + static SharedTaskDynamicZones GetSharedTaskDynamicZonesEntry( + const std::vector &shared_task_dynamic_zoness, + int shared_task_dynamic_zones_id + ) + { + for (auto &shared_task_dynamic_zones : shared_task_dynamic_zoness) { + if (shared_task_dynamic_zones.shared_task_id == shared_task_dynamic_zones_id) { + return shared_task_dynamic_zones; + } + } + + return NewEntity(); + } + + static SharedTaskDynamicZones FindOne( + Database& db, + int shared_task_dynamic_zones_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE id = {} LIMIT 1", + BaseSelect(), + shared_task_dynamic_zones_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + SharedTaskDynamicZones entry{}; + + entry.shared_task_id = strtoll(row[0], NULL, 10); + entry.dynamic_zone_id = atoi(row[1]); + + return entry; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int shared_task_dynamic_zones_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + shared_task_dynamic_zones_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + SharedTaskDynamicZones shared_task_dynamic_zones_entry + ) + { + std::vector update_values; + + auto columns = Columns(); + + update_values.push_back(columns[0] + " = " + std::to_string(shared_task_dynamic_zones_entry.shared_task_id)); + update_values.push_back(columns[1] + " = " + std::to_string(shared_task_dynamic_zones_entry.dynamic_zone_id)); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + implode(", ", update_values), + PrimaryKey(), + shared_task_dynamic_zones_entry.shared_task_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static SharedTaskDynamicZones InsertOne( + Database& db, + SharedTaskDynamicZones shared_task_dynamic_zones_entry + ) + { + std::vector insert_values; + + insert_values.push_back(std::to_string(shared_task_dynamic_zones_entry.shared_task_id)); + insert_values.push_back(std::to_string(shared_task_dynamic_zones_entry.dynamic_zone_id)); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + implode(",", insert_values) + ) + ); + + if (results.Success()) { + shared_task_dynamic_zones_entry.shared_task_id = results.LastInsertedID(); + return shared_task_dynamic_zones_entry; + } + + shared_task_dynamic_zones_entry = NewEntity(); + + return shared_task_dynamic_zones_entry; + } + + static int InsertMany( + Database& db, + std::vector shared_task_dynamic_zones_entries + ) + { + std::vector insert_chunks; + + for (auto &shared_task_dynamic_zones_entry: shared_task_dynamic_zones_entries) { + std::vector insert_values; + + insert_values.push_back(std::to_string(shared_task_dynamic_zones_entry.shared_task_id)); + insert_values.push_back(std::to_string(shared_task_dynamic_zones_entry.dynamic_zone_id)); + + insert_chunks.push_back("(" + implode(",", insert_values) + ")"); + } + + std::vector insert_values; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + SharedTaskDynamicZones entry{}; + + entry.shared_task_id = strtoll(row[0], NULL, 10); + entry.dynamic_zone_id = atoi(row[1]); + + all_entries.push_back(entry); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, std::string where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + SharedTaskDynamicZones entry{}; + + entry.shared_task_id = strtoll(row[0], NULL, 10); + entry.dynamic_zone_id = atoi(row[1]); + + all_entries.push_back(entry); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, std::string where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + +}; + +#endif //EQEMU_BASE_SHARED_TASK_DYNAMIC_ZONES_REPOSITORY_H diff --git a/common/repositories/base/base_shared_task_members_repository.h b/common/repositories/base/base_shared_task_members_repository.h new file mode 100644 index 000000000..6e5d36227 --- /dev/null +++ b/common/repositories/base/base_shared_task_members_repository.h @@ -0,0 +1,302 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories + */ + +#ifndef EQEMU_BASE_SHARED_TASK_MEMBERS_REPOSITORY_H +#define EQEMU_BASE_SHARED_TASK_MEMBERS_REPOSITORY_H + +#include "../../database.h" +#include "../../string_util.h" + +class BaseSharedTaskMembersRepository { +public: + struct SharedTaskMembers { + int64 shared_task_id; + int64 character_id; + int is_leader; + }; + + static std::string PrimaryKey() + { + return std::string("shared_task_id"); + } + + static std::vector Columns() + { + return { + "shared_task_id", + "character_id", + "is_leader", + }; + } + + static std::string ColumnsRaw() + { + return std::string(implode(", ", Columns())); + } + + static std::string TableName() + { + return std::string("shared_task_members"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + ColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static SharedTaskMembers NewEntity() + { + SharedTaskMembers entry{}; + + entry.shared_task_id = 0; + entry.character_id = 0; + entry.is_leader = 0; + + return entry; + } + + static SharedTaskMembers GetSharedTaskMembersEntry( + const std::vector &shared_task_memberss, + int shared_task_members_id + ) + { + for (auto &shared_task_members : shared_task_memberss) { + if (shared_task_members.shared_task_id == shared_task_members_id) { + return shared_task_members; + } + } + + return NewEntity(); + } + + static SharedTaskMembers FindOne( + Database& db, + int shared_task_members_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE id = {} LIMIT 1", + BaseSelect(), + shared_task_members_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + SharedTaskMembers entry{}; + + entry.shared_task_id = strtoll(row[0], NULL, 10); + entry.character_id = strtoll(row[1], NULL, 10); + entry.is_leader = atoi(row[2]); + + return entry; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int shared_task_members_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + shared_task_members_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + SharedTaskMembers shared_task_members_entry + ) + { + std::vector update_values; + + auto columns = Columns(); + + update_values.push_back(columns[0] + " = " + std::to_string(shared_task_members_entry.shared_task_id)); + update_values.push_back(columns[1] + " = " + std::to_string(shared_task_members_entry.character_id)); + update_values.push_back(columns[2] + " = " + std::to_string(shared_task_members_entry.is_leader)); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + implode(", ", update_values), + PrimaryKey(), + shared_task_members_entry.shared_task_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static SharedTaskMembers InsertOne( + Database& db, + SharedTaskMembers shared_task_members_entry + ) + { + std::vector insert_values; + + insert_values.push_back(std::to_string(shared_task_members_entry.shared_task_id)); + insert_values.push_back(std::to_string(shared_task_members_entry.character_id)); + insert_values.push_back(std::to_string(shared_task_members_entry.is_leader)); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + implode(",", insert_values) + ) + ); + + if (results.Success()) { + shared_task_members_entry.shared_task_id = results.LastInsertedID(); + return shared_task_members_entry; + } + + shared_task_members_entry = NewEntity(); + + return shared_task_members_entry; + } + + static int InsertMany( + Database& db, + std::vector shared_task_members_entries + ) + { + std::vector insert_chunks; + + for (auto &shared_task_members_entry: shared_task_members_entries) { + std::vector insert_values; + + insert_values.push_back(std::to_string(shared_task_members_entry.shared_task_id)); + insert_values.push_back(std::to_string(shared_task_members_entry.character_id)); + insert_values.push_back(std::to_string(shared_task_members_entry.is_leader)); + + insert_chunks.push_back("(" + implode(",", insert_values) + ")"); + } + + std::vector insert_values; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + SharedTaskMembers entry{}; + + entry.shared_task_id = strtoll(row[0], NULL, 10); + entry.character_id = strtoll(row[1], NULL, 10); + entry.is_leader = atoi(row[2]); + + all_entries.push_back(entry); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, std::string where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + SharedTaskMembers entry{}; + + entry.shared_task_id = strtoll(row[0], NULL, 10); + entry.character_id = strtoll(row[1], NULL, 10); + entry.is_leader = atoi(row[2]); + + all_entries.push_back(entry); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, std::string where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + +}; + +#endif //EQEMU_BASE_SHARED_TASK_MEMBERS_REPOSITORY_H diff --git a/common/repositories/base/base_shared_tasks_repository.h b/common/repositories/base/base_shared_tasks_repository.h new file mode 100644 index 000000000..405bf09ed --- /dev/null +++ b/common/repositories/base/base_shared_tasks_repository.h @@ -0,0 +1,346 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories + */ + +#ifndef EQEMU_BASE_SHARED_TASKS_REPOSITORY_H +#define EQEMU_BASE_SHARED_TASKS_REPOSITORY_H + +#include "../../database.h" +#include "../../string_util.h" +#include + +class BaseSharedTasksRepository { +public: + struct SharedTasks { + int64 id; + int task_id; + time_t accepted_time; + time_t expire_time; + time_t completion_time; + int is_locked; + }; + + static std::string PrimaryKey() + { + return std::string("id"); + } + + static std::vector Columns() + { + return { + "id", + "task_id", + "accepted_time", + "expire_time", + "completion_time", + "is_locked", + }; + } + + static std::vector SelectColumns() + { + return { + "id", + "task_id", + "UNIX_TIMESTAMP(accepted_time)", + "UNIX_TIMESTAMP(expire_time)", + "UNIX_TIMESTAMP(completion_time)", + "is_locked", + }; + } + + static std::string ColumnsRaw() + { + return std::string(implode(", ", Columns())); + } + + static std::string SelectColumnsRaw() + { + return std::string(implode(", ", SelectColumns())); + } + + static std::string TableName() + { + return std::string("shared_tasks"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + SelectColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static SharedTasks NewEntity() + { + SharedTasks entry{}; + + entry.id = 0; + entry.task_id = 0; + entry.accepted_time = 0; + entry.expire_time = 0; + entry.completion_time = 0; + entry.is_locked = 0; + + return entry; + } + + static SharedTasks GetSharedTasksEntry( + const std::vector &shared_taskss, + int shared_tasks_id + ) + { + for (auto &shared_tasks : shared_taskss) { + if (shared_tasks.id == shared_tasks_id) { + return shared_tasks; + } + } + + return NewEntity(); + } + + static SharedTasks FindOne( + Database& db, + int shared_tasks_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE id = {} LIMIT 1", + BaseSelect(), + shared_tasks_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + SharedTasks entry{}; + + entry.id = strtoll(row[0], nullptr, 10); + entry.task_id = atoi(row[1]); + entry.accepted_time = strtoll(row[2] ? row[2] : "-1", nullptr, 10); + entry.expire_time = strtoll(row[3] ? row[3] : "-1", nullptr, 10); + entry.completion_time = strtoll(row[4] ? row[4] : "-1", nullptr, 10); + entry.is_locked = atoi(row[5]); + + return entry; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int shared_tasks_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + shared_tasks_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + SharedTasks shared_tasks_entry + ) + { + std::vector update_values; + + auto columns = Columns(); + + update_values.push_back(columns[1] + " = " + std::to_string(shared_tasks_entry.task_id)); + update_values.push_back(columns[2] + " = FROM_UNIXTIME(" + (shared_tasks_entry.accepted_time > 0 ? std::to_string(shared_tasks_entry.accepted_time) : "null") + ")"); + update_values.push_back(columns[3] + " = FROM_UNIXTIME(" + (shared_tasks_entry.expire_time > 0 ? std::to_string(shared_tasks_entry.expire_time) : "null") + ")"); + update_values.push_back(columns[4] + " = FROM_UNIXTIME(" + (shared_tasks_entry.completion_time > 0 ? std::to_string(shared_tasks_entry.completion_time) : "null") + ")"); + update_values.push_back(columns[5] + " = " + std::to_string(shared_tasks_entry.is_locked)); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + implode(", ", update_values), + PrimaryKey(), + shared_tasks_entry.id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static SharedTasks InsertOne( + Database& db, + SharedTasks shared_tasks_entry + ) + { + std::vector insert_values; + + insert_values.push_back(std::to_string(shared_tasks_entry.id)); + insert_values.push_back(std::to_string(shared_tasks_entry.task_id)); + insert_values.push_back("FROM_UNIXTIME(" + (shared_tasks_entry.accepted_time > 0 ? std::to_string(shared_tasks_entry.accepted_time) : "null") + ")"); + insert_values.push_back("FROM_UNIXTIME(" + (shared_tasks_entry.expire_time > 0 ? std::to_string(shared_tasks_entry.expire_time) : "null") + ")"); + insert_values.push_back("FROM_UNIXTIME(" + (shared_tasks_entry.completion_time > 0 ? std::to_string(shared_tasks_entry.completion_time) : "null") + ")"); + insert_values.push_back(std::to_string(shared_tasks_entry.is_locked)); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + implode(",", insert_values) + ) + ); + + if (results.Success()) { + shared_tasks_entry.id = results.LastInsertedID(); + return shared_tasks_entry; + } + + shared_tasks_entry = NewEntity(); + + return shared_tasks_entry; + } + + static int InsertMany( + Database& db, + std::vector shared_tasks_entries + ) + { + std::vector insert_chunks; + + for (auto &shared_tasks_entry: shared_tasks_entries) { + std::vector insert_values; + + insert_values.push_back(std::to_string(shared_tasks_entry.id)); + insert_values.push_back(std::to_string(shared_tasks_entry.task_id)); + insert_values.push_back("FROM_UNIXTIME(" + (shared_tasks_entry.accepted_time > 0 ? std::to_string(shared_tasks_entry.accepted_time) : "null") + ")"); + insert_values.push_back("FROM_UNIXTIME(" + (shared_tasks_entry.expire_time > 0 ? std::to_string(shared_tasks_entry.expire_time) : "null") + ")"); + insert_values.push_back("FROM_UNIXTIME(" + (shared_tasks_entry.completion_time > 0 ? std::to_string(shared_tasks_entry.completion_time) : "null") + ")"); + insert_values.push_back(std::to_string(shared_tasks_entry.is_locked)); + + insert_chunks.push_back("(" + implode(",", insert_values) + ")"); + } + + std::vector insert_values; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + SharedTasks entry{}; + + entry.id = strtoll(row[0], nullptr, 10); + entry.task_id = atoi(row[1]); + entry.accepted_time = strtoll(row[2] ? row[2] : "-1", nullptr, 10); + entry.expire_time = strtoll(row[3] ? row[3] : "-1", nullptr, 10); + entry.completion_time = strtoll(row[4] ? row[4] : "-1", nullptr, 10); + entry.is_locked = atoi(row[5]); + + all_entries.push_back(entry); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, std::string where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + SharedTasks entry{}; + + entry.id = strtoll(row[0], nullptr, 10); + entry.task_id = atoi(row[1]); + entry.accepted_time = strtoll(row[2] ? row[2] : "-1", nullptr, 10); + entry.expire_time = strtoll(row[3] ? row[3] : "-1", nullptr, 10); + entry.completion_time = strtoll(row[4] ? row[4] : "-1", nullptr, 10); + entry.is_locked = atoi(row[5]); + + all_entries.push_back(entry); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, std::string where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + +}; + +#endif //EQEMU_BASE_SHARED_TASKS_REPOSITORY_H diff --git a/common/repositories/base/base_skill_caps_repository.h b/common/repositories/base/base_skill_caps_repository.h index b17753782..dee1ede92 100644 --- a/common/repositories/base/base_skill_caps_repository.h +++ b/common/repositories/base/base_skill_caps_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ @@ -19,7 +19,7 @@ class BaseSkillCapsRepository { public: struct SkillCaps { int skillID; - int class; + int class_; int level; int cap; int class_; @@ -34,7 +34,7 @@ public: { return { "skillID", - "class", + "`class`", "level", "cap", "class_", @@ -74,7 +74,7 @@ public: SkillCaps entry{}; entry.skillID = 0; - entry.class = 0; + entry.class_ = 0; entry.level = 0; entry.cap = 0; entry.class_ = 0; @@ -114,7 +114,7 @@ public: SkillCaps entry{}; entry.skillID = atoi(row[0]); - entry.class = atoi(row[1]); + entry.class_ = atoi(row[1]); entry.level = atoi(row[2]); entry.cap = atoi(row[3]); entry.class_ = atoi(row[4]); @@ -152,7 +152,7 @@ public: auto columns = Columns(); update_values.push_back(columns[0] + " = " + std::to_string(skill_caps_entry.skillID)); - update_values.push_back(columns[1] + " = " + std::to_string(skill_caps_entry.class)); + update_values.push_back(columns[1] + " = " + std::to_string(skill_caps_entry.class_)); update_values.push_back(columns[2] + " = " + std::to_string(skill_caps_entry.level)); update_values.push_back(columns[3] + " = " + std::to_string(skill_caps_entry.cap)); update_values.push_back(columns[4] + " = " + std::to_string(skill_caps_entry.class_)); @@ -178,7 +178,7 @@ public: std::vector insert_values; insert_values.push_back(std::to_string(skill_caps_entry.skillID)); - insert_values.push_back(std::to_string(skill_caps_entry.class)); + insert_values.push_back(std::to_string(skill_caps_entry.class_)); insert_values.push_back(std::to_string(skill_caps_entry.level)); insert_values.push_back(std::to_string(skill_caps_entry.cap)); insert_values.push_back(std::to_string(skill_caps_entry.class_)); @@ -212,7 +212,7 @@ public: std::vector insert_values; insert_values.push_back(std::to_string(skill_caps_entry.skillID)); - insert_values.push_back(std::to_string(skill_caps_entry.class)); + insert_values.push_back(std::to_string(skill_caps_entry.class_)); insert_values.push_back(std::to_string(skill_caps_entry.level)); insert_values.push_back(std::to_string(skill_caps_entry.cap)); insert_values.push_back(std::to_string(skill_caps_entry.class_)); @@ -250,7 +250,7 @@ public: SkillCaps entry{}; entry.skillID = atoi(row[0]); - entry.class = atoi(row[1]); + entry.class_ = atoi(row[1]); entry.level = atoi(row[2]); entry.cap = atoi(row[3]); entry.class_ = atoi(row[4]); @@ -279,7 +279,7 @@ public: SkillCaps entry{}; entry.skillID = atoi(row[0]); - entry.class = atoi(row[1]); + entry.class_ = atoi(row[1]); entry.level = atoi(row[2]); entry.cap = atoi(row[3]); entry.class_ = atoi(row[4]); diff --git a/common/repositories/base/base_spawn2_repository.h b/common/repositories/base/base_spawn2_repository.h index 921c9262d..6ff7146ee 100644 --- a/common/repositories/base/base_spawn2_repository.h +++ b/common/repositories/base/base_spawn2_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ @@ -29,6 +29,7 @@ public: int respawntime; int variance; int pathgrid; + int path_when_zone_idle; int _condition; int cond_value; int enabled; @@ -58,6 +59,7 @@ public: "respawntime", "variance", "pathgrid", + "path_when_zone_idle", "_condition", "cond_value", "enabled", @@ -112,6 +114,7 @@ public: entry.respawntime = 0; entry.variance = 0; entry.pathgrid = 0; + entry.path_when_zone_idle = 0; entry._condition = 0; entry.cond_value = 1; entry.enabled = 1; @@ -166,14 +169,15 @@ public: entry.respawntime = atoi(row[8]); entry.variance = atoi(row[9]); entry.pathgrid = atoi(row[10]); - entry._condition = atoi(row[11]); - entry.cond_value = atoi(row[12]); - entry.enabled = atoi(row[13]); - entry.animation = atoi(row[14]); - entry.min_expansion = atoi(row[15]); - entry.max_expansion = atoi(row[16]); - entry.content_flags = row[17] ? row[17] : ""; - entry.content_flags_disabled = row[18] ? row[18] : ""; + entry.path_when_zone_idle = atoi(row[11]); + entry._condition = atoi(row[12]); + entry.cond_value = atoi(row[13]); + entry.enabled = atoi(row[14]); + entry.animation = atoi(row[15]); + entry.min_expansion = atoi(row[16]); + entry.max_expansion = atoi(row[17]); + entry.content_flags = row[18] ? row[18] : ""; + entry.content_flags_disabled = row[19] ? row[19] : ""; return entry; } @@ -217,14 +221,15 @@ public: update_values.push_back(columns[8] + " = " + std::to_string(spawn2_entry.respawntime)); update_values.push_back(columns[9] + " = " + std::to_string(spawn2_entry.variance)); update_values.push_back(columns[10] + " = " + std::to_string(spawn2_entry.pathgrid)); - update_values.push_back(columns[11] + " = " + std::to_string(spawn2_entry._condition)); - update_values.push_back(columns[12] + " = " + std::to_string(spawn2_entry.cond_value)); - update_values.push_back(columns[13] + " = " + std::to_string(spawn2_entry.enabled)); - update_values.push_back(columns[14] + " = " + std::to_string(spawn2_entry.animation)); - update_values.push_back(columns[15] + " = " + std::to_string(spawn2_entry.min_expansion)); - update_values.push_back(columns[16] + " = " + std::to_string(spawn2_entry.max_expansion)); - update_values.push_back(columns[17] + " = '" + EscapeString(spawn2_entry.content_flags) + "'"); - update_values.push_back(columns[18] + " = '" + EscapeString(spawn2_entry.content_flags_disabled) + "'"); + update_values.push_back(columns[11] + " = " + std::to_string(spawn2_entry.path_when_zone_idle)); + update_values.push_back(columns[12] + " = " + std::to_string(spawn2_entry._condition)); + update_values.push_back(columns[13] + " = " + std::to_string(spawn2_entry.cond_value)); + update_values.push_back(columns[14] + " = " + std::to_string(spawn2_entry.enabled)); + update_values.push_back(columns[15] + " = " + std::to_string(spawn2_entry.animation)); + update_values.push_back(columns[16] + " = " + std::to_string(spawn2_entry.min_expansion)); + update_values.push_back(columns[17] + " = " + std::to_string(spawn2_entry.max_expansion)); + update_values.push_back(columns[18] + " = '" + EscapeString(spawn2_entry.content_flags) + "'"); + update_values.push_back(columns[19] + " = '" + EscapeString(spawn2_entry.content_flags_disabled) + "'"); auto results = db.QueryDatabase( fmt::format( @@ -257,6 +262,7 @@ public: insert_values.push_back(std::to_string(spawn2_entry.respawntime)); insert_values.push_back(std::to_string(spawn2_entry.variance)); insert_values.push_back(std::to_string(spawn2_entry.pathgrid)); + insert_values.push_back(std::to_string(spawn2_entry.path_when_zone_idle)); insert_values.push_back(std::to_string(spawn2_entry._condition)); insert_values.push_back(std::to_string(spawn2_entry.cond_value)); insert_values.push_back(std::to_string(spawn2_entry.enabled)); @@ -305,6 +311,7 @@ public: insert_values.push_back(std::to_string(spawn2_entry.respawntime)); insert_values.push_back(std::to_string(spawn2_entry.variance)); insert_values.push_back(std::to_string(spawn2_entry.pathgrid)); + insert_values.push_back(std::to_string(spawn2_entry.path_when_zone_idle)); insert_values.push_back(std::to_string(spawn2_entry._condition)); insert_values.push_back(std::to_string(spawn2_entry.cond_value)); insert_values.push_back(std::to_string(spawn2_entry.enabled)); @@ -357,14 +364,15 @@ public: entry.respawntime = atoi(row[8]); entry.variance = atoi(row[9]); entry.pathgrid = atoi(row[10]); - entry._condition = atoi(row[11]); - entry.cond_value = atoi(row[12]); - entry.enabled = atoi(row[13]); - entry.animation = atoi(row[14]); - entry.min_expansion = atoi(row[15]); - entry.max_expansion = atoi(row[16]); - entry.content_flags = row[17] ? row[17] : ""; - entry.content_flags_disabled = row[18] ? row[18] : ""; + entry.path_when_zone_idle = atoi(row[11]); + entry._condition = atoi(row[12]); + entry.cond_value = atoi(row[13]); + entry.enabled = atoi(row[14]); + entry.animation = atoi(row[15]); + entry.min_expansion = atoi(row[16]); + entry.max_expansion = atoi(row[17]); + entry.content_flags = row[18] ? row[18] : ""; + entry.content_flags_disabled = row[19] ? row[19] : ""; all_entries.push_back(entry); } @@ -400,14 +408,15 @@ public: entry.respawntime = atoi(row[8]); entry.variance = atoi(row[9]); entry.pathgrid = atoi(row[10]); - entry._condition = atoi(row[11]); - entry.cond_value = atoi(row[12]); - entry.enabled = atoi(row[13]); - entry.animation = atoi(row[14]); - entry.min_expansion = atoi(row[15]); - entry.max_expansion = atoi(row[16]); - entry.content_flags = row[17] ? row[17] : ""; - entry.content_flags_disabled = row[18] ? row[18] : ""; + entry.path_when_zone_idle = atoi(row[11]); + entry._condition = atoi(row[12]); + entry.cond_value = atoi(row[13]); + entry.enabled = atoi(row[14]); + entry.animation = atoi(row[15]); + entry.min_expansion = atoi(row[16]); + entry.max_expansion = atoi(row[17]); + entry.content_flags = row[18] ? row[18] : ""; + entry.content_flags_disabled = row[19] ? row[19] : ""; all_entries.push_back(entry); } diff --git a/common/repositories/base/base_spawn_condition_values_repository.h b/common/repositories/base/base_spawn_condition_values_repository.h index c54eee597..ca796270f 100644 --- a/common/repositories/base/base_spawn_condition_values_repository.h +++ b/common/repositories/base/base_spawn_condition_values_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_spawn_conditions_repository.h b/common/repositories/base/base_spawn_conditions_repository.h index e0e7fa0ab..4a3435fd7 100644 --- a/common/repositories/base/base_spawn_conditions_repository.h +++ b/common/repositories/base/base_spawn_conditions_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_spawn_events_repository.h b/common/repositories/base/base_spawn_events_repository.h index da52bbb8a..66490d2ca 100644 --- a/common/repositories/base/base_spawn_events_repository.h +++ b/common/repositories/base/base_spawn_events_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_spawnentry_repository.h b/common/repositories/base/base_spawnentry_repository.h index 4a04bf7d6..3bfb7b5e0 100644 --- a/common/repositories/base/base_spawnentry_repository.h +++ b/common/repositories/base/base_spawnentry_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_spawngroup_repository.h b/common/repositories/base/base_spawngroup_repository.h index 0baba76aa..a9ab5e3e6 100644 --- a/common/repositories/base/base_spawngroup_repository.h +++ b/common/repositories/base/base_spawngroup_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_spell_buckets_repository.h b/common/repositories/base/base_spell_buckets_repository.h index 39ce3ff42..7e6d5e46d 100644 --- a/common/repositories/base/base_spell_buckets_repository.h +++ b/common/repositories/base/base_spell_buckets_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ @@ -18,7 +18,7 @@ class BaseSpellBucketsRepository { public: struct SpellBuckets { - int spellid; + int64 spellid; std::string key; std::string value; }; @@ -107,7 +107,7 @@ public: if (results.RowCount() == 1) { SpellBuckets entry{}; - entry.spellid = atoi(row[0]); + entry.spellid = strtoll(row[0], NULL, 10); entry.key = row[1] ? row[1] : ""; entry.value = row[2] ? row[2] : ""; @@ -235,7 +235,7 @@ public: for (auto row = results.begin(); row != results.end(); ++row) { SpellBuckets entry{}; - entry.spellid = atoi(row[0]); + entry.spellid = strtoll(row[0], NULL, 10); entry.key = row[1] ? row[1] : ""; entry.value = row[2] ? row[2] : ""; @@ -262,7 +262,7 @@ public: for (auto row = results.begin(); row != results.end(); ++row) { SpellBuckets entry{}; - entry.spellid = atoi(row[0]); + entry.spellid = strtoll(row[0], NULL, 10); entry.key = row[1] ? row[1] : ""; entry.value = row[2] ? row[2] : ""; diff --git a/common/repositories/base/base_spell_globals_repository.h b/common/repositories/base/base_spell_globals_repository.h index 40ce563ce..26e836597 100644 --- a/common/repositories/base/base_spell_globals_repository.h +++ b/common/repositories/base/base_spell_globals_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_spells_new_repository.h b/common/repositories/base/base_spells_new_repository.h index e59ce3b28..111140f27 100644 --- a/common/repositories/base/base_spells_new_repository.h +++ b/common/repositories/base/base_spells_new_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ @@ -199,8 +199,8 @@ public: int pvpresistcalc; int pvpresistcap; int spell_category; - int field181; - int field182; + int pvp_duration; + int pvp_duration_cap; int pcnpc_only_flag; int cast_not_standing; int can_mgb; @@ -446,8 +446,8 @@ public: "pvpresistcalc", "pvpresistcap", "spell_category", - "field181", - "field182", + "pvp_duration", + "pvp_duration_cap", "pcnpc_only_flag", "cast_not_standing", "can_mgb", @@ -718,8 +718,8 @@ public: entry.pvpresistcalc = 100; entry.pvpresistcap = -150; entry.spell_category = -99; - entry.field181 = 7; - entry.field182 = 65; + entry.pvp_duration = 0; + entry.pvp_duration_cap = 0; entry.pcnpc_only_flag = 0; entry.cast_not_standing = 0; entry.can_mgb = 0; @@ -990,8 +990,8 @@ public: entry.pvpresistcalc = atoi(row[178]); entry.pvpresistcap = atoi(row[179]); entry.spell_category = atoi(row[180]); - entry.field181 = atoi(row[181]); - entry.field182 = atoi(row[182]); + entry.pvp_duration = atoi(row[181]); + entry.pvp_duration_cap = atoi(row[182]); entry.pcnpc_only_flag = atoi(row[183]); entry.cast_not_standing = atoi(row[184]); entry.can_mgb = atoi(row[185]); @@ -1260,8 +1260,8 @@ public: update_values.push_back(columns[178] + " = " + std::to_string(spells_new_entry.pvpresistcalc)); update_values.push_back(columns[179] + " = " + std::to_string(spells_new_entry.pvpresistcap)); update_values.push_back(columns[180] + " = " + std::to_string(spells_new_entry.spell_category)); - update_values.push_back(columns[181] + " = " + std::to_string(spells_new_entry.field181)); - update_values.push_back(columns[182] + " = " + std::to_string(spells_new_entry.field182)); + update_values.push_back(columns[181] + " = " + std::to_string(spells_new_entry.pvp_duration)); + update_values.push_back(columns[182] + " = " + std::to_string(spells_new_entry.pvp_duration_cap)); update_values.push_back(columns[183] + " = " + std::to_string(spells_new_entry.pcnpc_only_flag)); update_values.push_back(columns[184] + " = " + std::to_string(spells_new_entry.cast_not_standing)); update_values.push_back(columns[185] + " = " + std::to_string(spells_new_entry.can_mgb)); @@ -1518,8 +1518,8 @@ public: insert_values.push_back(std::to_string(spells_new_entry.pvpresistcalc)); insert_values.push_back(std::to_string(spells_new_entry.pvpresistcap)); insert_values.push_back(std::to_string(spells_new_entry.spell_category)); - insert_values.push_back(std::to_string(spells_new_entry.field181)); - insert_values.push_back(std::to_string(spells_new_entry.field182)); + insert_values.push_back(std::to_string(spells_new_entry.pvp_duration)); + insert_values.push_back(std::to_string(spells_new_entry.pvp_duration_cap)); insert_values.push_back(std::to_string(spells_new_entry.pcnpc_only_flag)); insert_values.push_back(std::to_string(spells_new_entry.cast_not_standing)); insert_values.push_back(std::to_string(spells_new_entry.can_mgb)); @@ -1784,8 +1784,8 @@ public: insert_values.push_back(std::to_string(spells_new_entry.pvpresistcalc)); insert_values.push_back(std::to_string(spells_new_entry.pvpresistcap)); insert_values.push_back(std::to_string(spells_new_entry.spell_category)); - insert_values.push_back(std::to_string(spells_new_entry.field181)); - insert_values.push_back(std::to_string(spells_new_entry.field182)); + insert_values.push_back(std::to_string(spells_new_entry.pvp_duration)); + insert_values.push_back(std::to_string(spells_new_entry.pvp_duration_cap)); insert_values.push_back(std::to_string(spells_new_entry.pcnpc_only_flag)); insert_values.push_back(std::to_string(spells_new_entry.cast_not_standing)); insert_values.push_back(std::to_string(spells_new_entry.can_mgb)); @@ -2054,8 +2054,8 @@ public: entry.pvpresistcalc = atoi(row[178]); entry.pvpresistcap = atoi(row[179]); entry.spell_category = atoi(row[180]); - entry.field181 = atoi(row[181]); - entry.field182 = atoi(row[182]); + entry.pvp_duration = atoi(row[181]); + entry.pvp_duration_cap = atoi(row[182]); entry.pcnpc_only_flag = atoi(row[183]); entry.cast_not_standing = atoi(row[184]); entry.can_mgb = atoi(row[185]); @@ -2315,8 +2315,8 @@ public: entry.pvpresistcalc = atoi(row[178]); entry.pvpresistcap = atoi(row[179]); entry.spell_category = atoi(row[180]); - entry.field181 = atoi(row[181]); - entry.field182 = atoi(row[182]); + entry.pvp_duration = atoi(row[181]); + entry.pvp_duration_cap = atoi(row[182]); entry.pcnpc_only_flag = atoi(row[183]); entry.cast_not_standing = atoi(row[184]); entry.can_mgb = atoi(row[185]); diff --git a/common/repositories/base/base_start_zones_repository.h b/common/repositories/base/base_start_zones_repository.h index 75a8595ca..9a627de40 100644 --- a/common/repositories/base/base_start_zones_repository.h +++ b/common/repositories/base/base_start_zones_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_starting_items_repository.h b/common/repositories/base/base_starting_items_repository.h index edb2cafa9..0f40bb16d 100644 --- a/common/repositories/base/base_starting_items_repository.h +++ b/common/repositories/base/base_starting_items_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ @@ -20,7 +20,7 @@ public: struct StartingItems { int id; int race; - int class; + int class_; int deityid; int zoneid; int itemid; @@ -43,7 +43,7 @@ public: return { "id", "race", - "class", + "`class`", "deityid", "zoneid", "itemid", @@ -91,7 +91,7 @@ public: entry.id = 0; entry.race = 0; - entry.class = 0; + entry.class_ = 0; entry.deityid = 0; entry.zoneid = 0; entry.itemid = 0; @@ -139,7 +139,7 @@ public: entry.id = atoi(row[0]); entry.race = atoi(row[1]); - entry.class = atoi(row[2]); + entry.class_ = atoi(row[2]); entry.deityid = atoi(row[3]); entry.zoneid = atoi(row[4]); entry.itemid = atoi(row[5]); @@ -184,7 +184,7 @@ public: auto columns = Columns(); update_values.push_back(columns[1] + " = " + std::to_string(starting_items_entry.race)); - update_values.push_back(columns[2] + " = " + std::to_string(starting_items_entry.class)); + update_values.push_back(columns[2] + " = " + std::to_string(starting_items_entry.class_)); update_values.push_back(columns[3] + " = " + std::to_string(starting_items_entry.deityid)); update_values.push_back(columns[4] + " = " + std::to_string(starting_items_entry.zoneid)); update_values.push_back(columns[5] + " = " + std::to_string(starting_items_entry.itemid)); @@ -218,7 +218,7 @@ public: insert_values.push_back(std::to_string(starting_items_entry.id)); insert_values.push_back(std::to_string(starting_items_entry.race)); - insert_values.push_back(std::to_string(starting_items_entry.class)); + insert_values.push_back(std::to_string(starting_items_entry.class_)); insert_values.push_back(std::to_string(starting_items_entry.deityid)); insert_values.push_back(std::to_string(starting_items_entry.zoneid)); insert_values.push_back(std::to_string(starting_items_entry.itemid)); @@ -260,7 +260,7 @@ public: insert_values.push_back(std::to_string(starting_items_entry.id)); insert_values.push_back(std::to_string(starting_items_entry.race)); - insert_values.push_back(std::to_string(starting_items_entry.class)); + insert_values.push_back(std::to_string(starting_items_entry.class_)); insert_values.push_back(std::to_string(starting_items_entry.deityid)); insert_values.push_back(std::to_string(starting_items_entry.zoneid)); insert_values.push_back(std::to_string(starting_items_entry.itemid)); @@ -306,7 +306,7 @@ public: entry.id = atoi(row[0]); entry.race = atoi(row[1]); - entry.class = atoi(row[2]); + entry.class_ = atoi(row[2]); entry.deityid = atoi(row[3]); entry.zoneid = atoi(row[4]); entry.itemid = atoi(row[5]); @@ -343,7 +343,7 @@ public: entry.id = atoi(row[0]); entry.race = atoi(row[1]); - entry.class = atoi(row[2]); + entry.class_ = atoi(row[2]); entry.deityid = atoi(row[3]); entry.zoneid = atoi(row[4]); entry.itemid = atoi(row[5]); diff --git a/common/repositories/base/base_task_activities_repository.h b/common/repositories/base/base_task_activities_repository.h index ad7f1fee4..e88334e1b 100644 --- a/common/repositories/base/base_task_activities_repository.h +++ b/common/repositories/base/base_task_activities_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_tasks_repository.h b/common/repositories/base/base_tasks_repository.h index 3d1982bf2..23c76d788 100644 --- a/common/repositories/base/base_tasks_repository.h +++ b/common/repositories/base/base_tasks_repository.h @@ -14,6 +14,7 @@ #include "../../database.h" #include "../../string_util.h" +#include class BaseTasksRepository { public: @@ -29,11 +30,18 @@ public: int cashreward; int xpreward; int rewardmethod; + int reward_radiant_crystals; + int reward_ebon_crystals; int minlevel; int maxlevel; + int level_spread; + int min_players; + int max_players; int repeatable; int faction_reward; std::string completion_emote; + int replay_timer_seconds; + int request_timer_seconds; }; static std::string PrimaryKey() @@ -55,11 +63,47 @@ public: "cashreward", "xpreward", "rewardmethod", + "reward_radiant_crystals", + "reward_ebon_crystals", "minlevel", "maxlevel", + "level_spread", + "min_players", + "max_players", "repeatable", "faction_reward", "completion_emote", + "replay_timer_seconds", + "request_timer_seconds", + }; + } + + static std::vector SelectColumns() + { + return { + "id", + "type", + "duration", + "duration_code", + "title", + "description", + "reward", + "rewardid", + "cashreward", + "xpreward", + "rewardmethod", + "reward_radiant_crystals", + "reward_ebon_crystals", + "minlevel", + "maxlevel", + "level_spread", + "min_players", + "max_players", + "repeatable", + "faction_reward", + "completion_emote", + "replay_timer_seconds", + "request_timer_seconds", }; } @@ -68,6 +112,11 @@ public: return std::string(implode(", ", Columns())); } + static std::string SelectColumnsRaw() + { + return std::string(implode(", ", SelectColumns())); + } + static std::string TableName() { return std::string("tasks"); @@ -77,7 +126,7 @@ public: { return fmt::format( "SELECT {} FROM {}", - ColumnsRaw(), + SelectColumnsRaw(), TableName() ); } @@ -95,22 +144,29 @@ public: { Tasks entry{}; - entry.id = 0; - entry.type = 0; - entry.duration = 0; - entry.duration_code = 0; - entry.title = ""; - entry.description = ""; - entry.reward = ""; - entry.rewardid = 0; - entry.cashreward = 0; - entry.xpreward = 0; - entry.rewardmethod = 2; - entry.minlevel = 0; - entry.maxlevel = 0; - entry.repeatable = 1; - entry.faction_reward = 0; - entry.completion_emote = ""; + entry.id = 0; + entry.type = 0; + entry.duration = 0; + entry.duration_code = 0; + entry.title = ""; + entry.description = ""; + entry.reward = ""; + entry.rewardid = 0; + entry.cashreward = 0; + entry.xpreward = 0; + entry.rewardmethod = 2; + entry.reward_radiant_crystals = 0; + entry.reward_ebon_crystals = 0; + entry.minlevel = 0; + entry.maxlevel = 0; + entry.level_spread = 0; + entry.min_players = 0; + entry.max_players = 0; + entry.repeatable = 1; + entry.faction_reward = 0; + entry.completion_emote = ""; + entry.replay_timer_seconds = 0; + entry.request_timer_seconds = 0; return entry; } @@ -146,22 +202,29 @@ public: if (results.RowCount() == 1) { Tasks entry{}; - entry.id = atoi(row[0]); - entry.type = atoi(row[1]); - entry.duration = atoi(row[2]); - entry.duration_code = atoi(row[3]); - entry.title = row[4] ? row[4] : ""; - entry.description = row[5] ? row[5] : ""; - entry.reward = row[6] ? row[6] : ""; - entry.rewardid = atoi(row[7]); - entry.cashreward = atoi(row[8]); - entry.xpreward = atoi(row[9]); - entry.rewardmethod = atoi(row[10]); - entry.minlevel = atoi(row[11]); - entry.maxlevel = atoi(row[12]); - entry.repeatable = atoi(row[13]); - entry.faction_reward = atoi(row[14]); - entry.completion_emote = row[15] ? row[15] : ""; + entry.id = atoi(row[0]); + entry.type = atoi(row[1]); + entry.duration = atoi(row[2]); + entry.duration_code = atoi(row[3]); + entry.title = row[4] ? row[4] : ""; + entry.description = row[5] ? row[5] : ""; + entry.reward = row[6] ? row[6] : ""; + entry.rewardid = atoi(row[7]); + entry.cashreward = atoi(row[8]); + entry.xpreward = atoi(row[9]); + entry.rewardmethod = atoi(row[10]); + entry.reward_radiant_crystals = atoi(row[11]); + entry.reward_ebon_crystals = atoi(row[12]); + entry.minlevel = atoi(row[13]); + entry.maxlevel = atoi(row[14]); + entry.level_spread = atoi(row[15]); + entry.min_players = atoi(row[16]); + entry.max_players = atoi(row[17]); + entry.repeatable = atoi(row[18]); + entry.faction_reward = atoi(row[19]); + entry.completion_emote = row[20] ? row[20] : ""; + entry.replay_timer_seconds = atoi(row[21]); + entry.request_timer_seconds = atoi(row[22]); return entry; } @@ -206,11 +269,18 @@ public: update_values.push_back(columns[8] + " = " + std::to_string(tasks_entry.cashreward)); update_values.push_back(columns[9] + " = " + std::to_string(tasks_entry.xpreward)); update_values.push_back(columns[10] + " = " + std::to_string(tasks_entry.rewardmethod)); - update_values.push_back(columns[11] + " = " + std::to_string(tasks_entry.minlevel)); - update_values.push_back(columns[12] + " = " + std::to_string(tasks_entry.maxlevel)); - update_values.push_back(columns[13] + " = " + std::to_string(tasks_entry.repeatable)); - update_values.push_back(columns[14] + " = " + std::to_string(tasks_entry.faction_reward)); - update_values.push_back(columns[15] + " = '" + EscapeString(tasks_entry.completion_emote) + "'"); + update_values.push_back(columns[11] + " = " + std::to_string(tasks_entry.reward_radiant_crystals)); + update_values.push_back(columns[12] + " = " + std::to_string(tasks_entry.reward_ebon_crystals)); + update_values.push_back(columns[13] + " = " + std::to_string(tasks_entry.minlevel)); + update_values.push_back(columns[14] + " = " + std::to_string(tasks_entry.maxlevel)); + update_values.push_back(columns[15] + " = " + std::to_string(tasks_entry.level_spread)); + update_values.push_back(columns[16] + " = " + std::to_string(tasks_entry.min_players)); + update_values.push_back(columns[17] + " = " + std::to_string(tasks_entry.max_players)); + update_values.push_back(columns[18] + " = " + std::to_string(tasks_entry.repeatable)); + update_values.push_back(columns[19] + " = " + std::to_string(tasks_entry.faction_reward)); + update_values.push_back(columns[20] + " = '" + EscapeString(tasks_entry.completion_emote) + "'"); + update_values.push_back(columns[21] + " = " + std::to_string(tasks_entry.replay_timer_seconds)); + update_values.push_back(columns[22] + " = " + std::to_string(tasks_entry.request_timer_seconds)); auto results = db.QueryDatabase( fmt::format( @@ -243,11 +313,18 @@ public: insert_values.push_back(std::to_string(tasks_entry.cashreward)); insert_values.push_back(std::to_string(tasks_entry.xpreward)); insert_values.push_back(std::to_string(tasks_entry.rewardmethod)); + insert_values.push_back(std::to_string(tasks_entry.reward_radiant_crystals)); + insert_values.push_back(std::to_string(tasks_entry.reward_ebon_crystals)); insert_values.push_back(std::to_string(tasks_entry.minlevel)); insert_values.push_back(std::to_string(tasks_entry.maxlevel)); + insert_values.push_back(std::to_string(tasks_entry.level_spread)); + insert_values.push_back(std::to_string(tasks_entry.min_players)); + insert_values.push_back(std::to_string(tasks_entry.max_players)); insert_values.push_back(std::to_string(tasks_entry.repeatable)); insert_values.push_back(std::to_string(tasks_entry.faction_reward)); insert_values.push_back("'" + EscapeString(tasks_entry.completion_emote) + "'"); + insert_values.push_back(std::to_string(tasks_entry.replay_timer_seconds)); + insert_values.push_back(std::to_string(tasks_entry.request_timer_seconds)); auto results = db.QueryDatabase( fmt::format( @@ -288,11 +365,18 @@ public: insert_values.push_back(std::to_string(tasks_entry.cashreward)); insert_values.push_back(std::to_string(tasks_entry.xpreward)); insert_values.push_back(std::to_string(tasks_entry.rewardmethod)); + insert_values.push_back(std::to_string(tasks_entry.reward_radiant_crystals)); + insert_values.push_back(std::to_string(tasks_entry.reward_ebon_crystals)); insert_values.push_back(std::to_string(tasks_entry.minlevel)); insert_values.push_back(std::to_string(tasks_entry.maxlevel)); + insert_values.push_back(std::to_string(tasks_entry.level_spread)); + insert_values.push_back(std::to_string(tasks_entry.min_players)); + insert_values.push_back(std::to_string(tasks_entry.max_players)); insert_values.push_back(std::to_string(tasks_entry.repeatable)); insert_values.push_back(std::to_string(tasks_entry.faction_reward)); insert_values.push_back("'" + EscapeString(tasks_entry.completion_emote) + "'"); + insert_values.push_back(std::to_string(tasks_entry.replay_timer_seconds)); + insert_values.push_back(std::to_string(tasks_entry.request_timer_seconds)); insert_chunks.push_back("(" + implode(",", insert_values) + ")"); } @@ -326,22 +410,29 @@ public: for (auto row = results.begin(); row != results.end(); ++row) { Tasks entry{}; - entry.id = atoi(row[0]); - entry.type = atoi(row[1]); - entry.duration = atoi(row[2]); - entry.duration_code = atoi(row[3]); - entry.title = row[4] ? row[4] : ""; - entry.description = row[5] ? row[5] : ""; - entry.reward = row[6] ? row[6] : ""; - entry.rewardid = atoi(row[7]); - entry.cashreward = atoi(row[8]); - entry.xpreward = atoi(row[9]); - entry.rewardmethod = atoi(row[10]); - entry.minlevel = atoi(row[11]); - entry.maxlevel = atoi(row[12]); - entry.repeatable = atoi(row[13]); - entry.faction_reward = atoi(row[14]); - entry.completion_emote = row[15] ? row[15] : ""; + entry.id = atoi(row[0]); + entry.type = atoi(row[1]); + entry.duration = atoi(row[2]); + entry.duration_code = atoi(row[3]); + entry.title = row[4] ? row[4] : ""; + entry.description = row[5] ? row[5] : ""; + entry.reward = row[6] ? row[6] : ""; + entry.rewardid = atoi(row[7]); + entry.cashreward = atoi(row[8]); + entry.xpreward = atoi(row[9]); + entry.rewardmethod = atoi(row[10]); + entry.reward_radiant_crystals = atoi(row[11]); + entry.reward_ebon_crystals = atoi(row[12]); + entry.minlevel = atoi(row[13]); + entry.maxlevel = atoi(row[14]); + entry.level_spread = atoi(row[15]); + entry.min_players = atoi(row[16]); + entry.max_players = atoi(row[17]); + entry.repeatable = atoi(row[18]); + entry.faction_reward = atoi(row[19]); + entry.completion_emote = row[20] ? row[20] : ""; + entry.replay_timer_seconds = atoi(row[21]); + entry.request_timer_seconds = atoi(row[22]); all_entries.push_back(entry); } @@ -366,22 +457,29 @@ public: for (auto row = results.begin(); row != results.end(); ++row) { Tasks entry{}; - entry.id = atoi(row[0]); - entry.type = atoi(row[1]); - entry.duration = atoi(row[2]); - entry.duration_code = atoi(row[3]); - entry.title = row[4] ? row[4] : ""; - entry.description = row[5] ? row[5] : ""; - entry.reward = row[6] ? row[6] : ""; - entry.rewardid = atoi(row[7]); - entry.cashreward = atoi(row[8]); - entry.xpreward = atoi(row[9]); - entry.rewardmethod = atoi(row[10]); - entry.minlevel = atoi(row[11]); - entry.maxlevel = atoi(row[12]); - entry.repeatable = atoi(row[13]); - entry.faction_reward = atoi(row[14]); - entry.completion_emote = row[15] ? row[15] : ""; + entry.id = atoi(row[0]); + entry.type = atoi(row[1]); + entry.duration = atoi(row[2]); + entry.duration_code = atoi(row[3]); + entry.title = row[4] ? row[4] : ""; + entry.description = row[5] ? row[5] : ""; + entry.reward = row[6] ? row[6] : ""; + entry.rewardid = atoi(row[7]); + entry.cashreward = atoi(row[8]); + entry.xpreward = atoi(row[9]); + entry.rewardmethod = atoi(row[10]); + entry.reward_radiant_crystals = atoi(row[11]); + entry.reward_ebon_crystals = atoi(row[12]); + entry.minlevel = atoi(row[13]); + entry.maxlevel = atoi(row[14]); + entry.level_spread = atoi(row[15]); + entry.min_players = atoi(row[16]); + entry.max_players = atoi(row[17]); + entry.repeatable = atoi(row[18]); + entry.faction_reward = atoi(row[19]); + entry.completion_emote = row[20] ? row[20] : ""; + entry.replay_timer_seconds = atoi(row[21]); + entry.request_timer_seconds = atoi(row[22]); all_entries.push_back(entry); } diff --git a/common/repositories/base/base_tasksets_repository.h b/common/repositories/base/base_tasksets_repository.h index c3feb4ccd..0944f57b3 100644 --- a/common/repositories/base/base_tasksets_repository.h +++ b/common/repositories/base/base_tasksets_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_timers_repository.h b/common/repositories/base/base_timers_repository.h index f1d6d97b1..ea3774bc2 100644 --- a/common/repositories/base/base_timers_repository.h +++ b/common/repositories/base/base_timers_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_titles_repository.h b/common/repositories/base/base_titles_repository.h index c108c887b..e270b51d1 100644 --- a/common/repositories/base/base_titles_repository.h +++ b/common/repositories/base/base_titles_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ @@ -24,7 +24,7 @@ public: int max_skill_value; int min_aa_points; int max_aa_points; - int class; + int class_; int gender; int char_id; int status; @@ -48,7 +48,7 @@ public: "max_skill_value", "min_aa_points", "max_aa_points", - "class", + "`class`", "gender", "char_id", "status", @@ -97,7 +97,7 @@ public: entry.max_skill_value = -1; entry.min_aa_points = -1; entry.max_aa_points = -1; - entry.class = -1; + entry.class_ = -1; entry.gender = -1; entry.char_id = -1; entry.status = -1; @@ -146,7 +146,7 @@ public: entry.max_skill_value = atoi(row[3]); entry.min_aa_points = atoi(row[4]); entry.max_aa_points = atoi(row[5]); - entry.class = atoi(row[6]); + entry.class_ = atoi(row[6]); entry.gender = atoi(row[7]); entry.char_id = atoi(row[8]); entry.status = atoi(row[9]); @@ -192,7 +192,7 @@ public: update_values.push_back(columns[3] + " = " + std::to_string(titles_entry.max_skill_value)); update_values.push_back(columns[4] + " = " + std::to_string(titles_entry.min_aa_points)); update_values.push_back(columns[5] + " = " + std::to_string(titles_entry.max_aa_points)); - update_values.push_back(columns[6] + " = " + std::to_string(titles_entry.class)); + update_values.push_back(columns[6] + " = " + std::to_string(titles_entry.class_)); update_values.push_back(columns[7] + " = " + std::to_string(titles_entry.gender)); update_values.push_back(columns[8] + " = " + std::to_string(titles_entry.char_id)); update_values.push_back(columns[9] + " = " + std::to_string(titles_entry.status)); @@ -227,7 +227,7 @@ public: insert_values.push_back(std::to_string(titles_entry.max_skill_value)); insert_values.push_back(std::to_string(titles_entry.min_aa_points)); insert_values.push_back(std::to_string(titles_entry.max_aa_points)); - insert_values.push_back(std::to_string(titles_entry.class)); + insert_values.push_back(std::to_string(titles_entry.class_)); insert_values.push_back(std::to_string(titles_entry.gender)); insert_values.push_back(std::to_string(titles_entry.char_id)); insert_values.push_back(std::to_string(titles_entry.status)); @@ -270,7 +270,7 @@ public: insert_values.push_back(std::to_string(titles_entry.max_skill_value)); insert_values.push_back(std::to_string(titles_entry.min_aa_points)); insert_values.push_back(std::to_string(titles_entry.max_aa_points)); - insert_values.push_back(std::to_string(titles_entry.class)); + insert_values.push_back(std::to_string(titles_entry.class_)); insert_values.push_back(std::to_string(titles_entry.gender)); insert_values.push_back(std::to_string(titles_entry.char_id)); insert_values.push_back(std::to_string(titles_entry.status)); @@ -317,7 +317,7 @@ public: entry.max_skill_value = atoi(row[3]); entry.min_aa_points = atoi(row[4]); entry.max_aa_points = atoi(row[5]); - entry.class = atoi(row[6]); + entry.class_ = atoi(row[6]); entry.gender = atoi(row[7]); entry.char_id = atoi(row[8]); entry.status = atoi(row[9]); @@ -355,7 +355,7 @@ public: entry.max_skill_value = atoi(row[3]); entry.min_aa_points = atoi(row[4]); entry.max_aa_points = atoi(row[5]); - entry.class = atoi(row[6]); + entry.class_ = atoi(row[6]); entry.gender = atoi(row[7]); entry.char_id = atoi(row[8]); entry.status = atoi(row[9]); diff --git a/common/repositories/base/base_tool_game_objects_repository.h b/common/repositories/base/base_tool_game_objects_repository.h new file mode 100644 index 000000000..3e1f8600f --- /dev/null +++ b/common/repositories/base/base_tool_game_objects_repository.h @@ -0,0 +1,346 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories + */ + +#ifndef EQEMU_BASE_TOOL_GAME_OBJECTS_REPOSITORY_H +#define EQEMU_BASE_TOOL_GAME_OBJECTS_REPOSITORY_H + +#include "../../database.h" +#include "../../string_util.h" +#include + +class BaseToolGameObjectsRepository { +public: + struct ToolGameObjects { + int id; + int zoneid; + std::string zonesn; + std::string object_name; + std::string file_from; + int is_global; + }; + + static std::string PrimaryKey() + { + return std::string("id"); + } + + static std::vector Columns() + { + return { + "id", + "zoneid", + "zonesn", + "object_name", + "file_from", + "is_global", + }; + } + + static std::vector SelectColumns() + { + return { + "id", + "zoneid", + "zonesn", + "object_name", + "file_from", + "is_global", + }; + } + + static std::string ColumnsRaw() + { + return std::string(implode(", ", Columns())); + } + + static std::string SelectColumnsRaw() + { + return std::string(implode(", ", SelectColumns())); + } + + static std::string TableName() + { + return std::string("tool_game_objects"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + SelectColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static ToolGameObjects NewEntity() + { + ToolGameObjects entry{}; + + entry.id = 0; + entry.zoneid = 0; + entry.zonesn = ""; + entry.object_name = ""; + entry.file_from = ""; + entry.is_global = 0; + + return entry; + } + + static ToolGameObjects GetToolGameObjectsEntry( + const std::vector &tool_game_objectss, + int tool_game_objects_id + ) + { + for (auto &tool_game_objects : tool_game_objectss) { + if (tool_game_objects.id == tool_game_objects_id) { + return tool_game_objects; + } + } + + return NewEntity(); + } + + static ToolGameObjects FindOne( + Database& db, + int tool_game_objects_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE id = {} LIMIT 1", + BaseSelect(), + tool_game_objects_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + ToolGameObjects entry{}; + + entry.id = atoi(row[0]); + entry.zoneid = atoi(row[1]); + entry.zonesn = row[2] ? row[2] : ""; + entry.object_name = row[3] ? row[3] : ""; + entry.file_from = row[4] ? row[4] : ""; + entry.is_global = atoi(row[5]); + + return entry; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int tool_game_objects_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + tool_game_objects_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + ToolGameObjects tool_game_objects_entry + ) + { + std::vector update_values; + + auto columns = Columns(); + + update_values.push_back(columns[1] + " = " + std::to_string(tool_game_objects_entry.zoneid)); + update_values.push_back(columns[2] + " = '" + EscapeString(tool_game_objects_entry.zonesn) + "'"); + update_values.push_back(columns[3] + " = '" + EscapeString(tool_game_objects_entry.object_name) + "'"); + update_values.push_back(columns[4] + " = '" + EscapeString(tool_game_objects_entry.file_from) + "'"); + update_values.push_back(columns[5] + " = " + std::to_string(tool_game_objects_entry.is_global)); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + implode(", ", update_values), + PrimaryKey(), + tool_game_objects_entry.id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static ToolGameObjects InsertOne( + Database& db, + ToolGameObjects tool_game_objects_entry + ) + { + std::vector insert_values; + + insert_values.push_back(std::to_string(tool_game_objects_entry.id)); + insert_values.push_back(std::to_string(tool_game_objects_entry.zoneid)); + insert_values.push_back("'" + EscapeString(tool_game_objects_entry.zonesn) + "'"); + insert_values.push_back("'" + EscapeString(tool_game_objects_entry.object_name) + "'"); + insert_values.push_back("'" + EscapeString(tool_game_objects_entry.file_from) + "'"); + insert_values.push_back(std::to_string(tool_game_objects_entry.is_global)); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + implode(",", insert_values) + ) + ); + + if (results.Success()) { + tool_game_objects_entry.id = results.LastInsertedID(); + return tool_game_objects_entry; + } + + tool_game_objects_entry = NewEntity(); + + return tool_game_objects_entry; + } + + static int InsertMany( + Database& db, + std::vector tool_game_objects_entries + ) + { + std::vector insert_chunks; + + for (auto &tool_game_objects_entry: tool_game_objects_entries) { + std::vector insert_values; + + insert_values.push_back(std::to_string(tool_game_objects_entry.id)); + insert_values.push_back(std::to_string(tool_game_objects_entry.zoneid)); + insert_values.push_back("'" + EscapeString(tool_game_objects_entry.zonesn) + "'"); + insert_values.push_back("'" + EscapeString(tool_game_objects_entry.object_name) + "'"); + insert_values.push_back("'" + EscapeString(tool_game_objects_entry.file_from) + "'"); + insert_values.push_back(std::to_string(tool_game_objects_entry.is_global)); + + insert_chunks.push_back("(" + implode(",", insert_values) + ")"); + } + + std::vector insert_values; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + ToolGameObjects entry{}; + + entry.id = atoi(row[0]); + entry.zoneid = atoi(row[1]); + entry.zonesn = row[2] ? row[2] : ""; + entry.object_name = row[3] ? row[3] : ""; + entry.file_from = row[4] ? row[4] : ""; + entry.is_global = atoi(row[5]); + + all_entries.push_back(entry); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, std::string where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + ToolGameObjects entry{}; + + entry.id = atoi(row[0]); + entry.zoneid = atoi(row[1]); + entry.zonesn = row[2] ? row[2] : ""; + entry.object_name = row[3] ? row[3] : ""; + entry.file_from = row[4] ? row[4] : ""; + entry.is_global = atoi(row[5]); + + all_entries.push_back(entry); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, std::string where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + +}; + +#endif //EQEMU_BASE_TOOL_GAME_OBJECTS_REPOSITORY_H diff --git a/common/repositories/base/base_trader_repository.h b/common/repositories/base/base_trader_repository.h index 1eded1551..1481e6154 100644 --- a/common/repositories/base/base_trader_repository.h +++ b/common/repositories/base/base_trader_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_tradeskill_recipe_entries_repository.h b/common/repositories/base/base_tradeskill_recipe_entries_repository.h index 13c293c77..274c58ca1 100644 --- a/common/repositories/base/base_tradeskill_recipe_entries_repository.h +++ b/common/repositories/base/base_tradeskill_recipe_entries_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_tradeskill_recipe_repository.h b/common/repositories/base/base_tradeskill_recipe_repository.h index 453d8b7dc..019cea88f 100644 --- a/common/repositories/base/base_tradeskill_recipe_repository.h +++ b/common/repositories/base/base_tradeskill_recipe_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_traps_repository.h b/common/repositories/base/base_traps_repository.h index b76072525..07ab550b4 100644 --- a/common/repositories/base/base_traps_repository.h +++ b/common/repositories/base/base_traps_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_tribute_levels_repository.h b/common/repositories/base/base_tribute_levels_repository.h index 2e49dab99..fef67ff27 100644 --- a/common/repositories/base/base_tribute_levels_repository.h +++ b/common/repositories/base/base_tribute_levels_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_tributes_repository.h b/common/repositories/base/base_tributes_repository.h index 1acd2f63b..c3a7f6324 100644 --- a/common/repositories/base/base_tributes_repository.h +++ b/common/repositories/base/base_tributes_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_veteran_reward_templates_repository.h b/common/repositories/base/base_veteran_reward_templates_repository.h index f41c1da3c..283742309 100644 --- a/common/repositories/base/base_veteran_reward_templates_repository.h +++ b/common/repositories/base/base_veteran_reward_templates_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_zone_points_repository.h b/common/repositories/base/base_zone_points_repository.h index e34d9bafb..b29cae43a 100644 --- a/common/repositories/base/base_zone_points_repository.h +++ b/common/repositories/base/base_zone_points_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ diff --git a/common/repositories/base/base_zone_repository.h b/common/repositories/base/base_zone_repository.h index f31b98491..3478c9fa1 100644 --- a/common/repositories/base/base_zone_repository.h +++ b/common/repositories/base/base_zone_repository.h @@ -4,7 +4,7 @@ * This repository was automatically generated and is NOT to be modified directly. * Any repository modifications are meant to be made to the repository extending the base. * Any modifications to base repositories are to be made by the generator only - * + * * @generator ./utils/scripts/generators/repository-generator.pl * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories */ @@ -26,6 +26,7 @@ public: float safe_x; float safe_y; float safe_z; + float safe_heading; float graveyard_id; int min_level; int min_status; @@ -76,7 +77,7 @@ public: int castoutdoor; int hotzone; int insttype; - int shutdowndelay; + int64 shutdowndelay; int peqzone; int expansion; int suspendbuffs; @@ -109,6 +110,8 @@ public: std::string content_flags; std::string content_flags_disabled; int underworld_teleport_index; + int lava_damage; + int min_lava_damage; }; static std::string PrimaryKey() @@ -127,6 +130,7 @@ public: "safe_x", "safe_y", "safe_z", + "safe_heading", "graveyard_id", "min_level", "min_status", @@ -210,6 +214,8 @@ public: "content_flags", "content_flags_disabled", "underworld_teleport_index", + "lava_damage", + "min_lava_damage", }; } @@ -253,6 +259,7 @@ public: entry.safe_x = 0; entry.safe_y = 0; entry.safe_z = 0; + entry.safe_heading = 0; entry.graveyard_id = 0; entry.min_level = 0; entry.min_status = 0; @@ -336,6 +343,8 @@ public: entry.content_flags = ""; entry.content_flags_disabled = ""; entry.underworld_teleport_index = 0; + entry.lava_damage = 50; + entry.min_lava_damage = 10; return entry; } @@ -379,89 +388,92 @@ public: entry.safe_x = static_cast(atof(row[5])); entry.safe_y = static_cast(atof(row[6])); entry.safe_z = static_cast(atof(row[7])); - entry.graveyard_id = static_cast(atof(row[8])); - entry.min_level = atoi(row[9]); - entry.min_status = atoi(row[10]); - entry.zoneidnumber = atoi(row[11]); - entry.version = atoi(row[12]); - entry.timezone = atoi(row[13]); - entry.maxclients = atoi(row[14]); - entry.ruleset = atoi(row[15]); - entry.note = row[16] ? row[16] : ""; - entry.underworld = static_cast(atof(row[17])); - entry.minclip = static_cast(atof(row[18])); - entry.maxclip = static_cast(atof(row[19])); - entry.fog_minclip = static_cast(atof(row[20])); - entry.fog_maxclip = static_cast(atof(row[21])); - entry.fog_blue = atoi(row[22]); - entry.fog_red = atoi(row[23]); - entry.fog_green = atoi(row[24]); - entry.sky = atoi(row[25]); - entry.ztype = atoi(row[26]); - entry.zone_exp_multiplier = static_cast(atof(row[27])); - entry.walkspeed = static_cast(atof(row[28])); - entry.time_type = atoi(row[29]); - entry.fog_red1 = atoi(row[30]); - entry.fog_green1 = atoi(row[31]); - entry.fog_blue1 = atoi(row[32]); - entry.fog_minclip1 = static_cast(atof(row[33])); - entry.fog_maxclip1 = static_cast(atof(row[34])); - entry.fog_red2 = atoi(row[35]); - entry.fog_green2 = atoi(row[36]); - entry.fog_blue2 = atoi(row[37]); - entry.fog_minclip2 = static_cast(atof(row[38])); - entry.fog_maxclip2 = static_cast(atof(row[39])); - entry.fog_red3 = atoi(row[40]); - entry.fog_green3 = atoi(row[41]); - entry.fog_blue3 = atoi(row[42]); - entry.fog_minclip3 = static_cast(atof(row[43])); - entry.fog_maxclip3 = static_cast(atof(row[44])); - entry.fog_red4 = atoi(row[45]); - entry.fog_green4 = atoi(row[46]); - entry.fog_blue4 = atoi(row[47]); - entry.fog_minclip4 = static_cast(atof(row[48])); - entry.fog_maxclip4 = static_cast(atof(row[49])); - entry.fog_density = static_cast(atof(row[50])); - entry.flag_needed = row[51] ? row[51] : ""; - entry.canbind = atoi(row[52]); - entry.cancombat = atoi(row[53]); - entry.canlevitate = atoi(row[54]); - entry.castoutdoor = atoi(row[55]); - entry.hotzone = atoi(row[56]); - entry.insttype = atoi(row[57]); - entry.shutdowndelay = atoi(row[58]); - entry.peqzone = atoi(row[59]); - entry.expansion = atoi(row[60]); - entry.suspendbuffs = atoi(row[61]); - entry.rain_chance1 = atoi(row[62]); - entry.rain_chance2 = atoi(row[63]); - entry.rain_chance3 = atoi(row[64]); - entry.rain_chance4 = atoi(row[65]); - entry.rain_duration1 = atoi(row[66]); - entry.rain_duration2 = atoi(row[67]); - entry.rain_duration3 = atoi(row[68]); - entry.rain_duration4 = atoi(row[69]); - entry.snow_chance1 = atoi(row[70]); - entry.snow_chance2 = atoi(row[71]); - entry.snow_chance3 = atoi(row[72]); - entry.snow_chance4 = atoi(row[73]); - entry.snow_duration1 = atoi(row[74]); - entry.snow_duration2 = atoi(row[75]); - entry.snow_duration3 = atoi(row[76]); - entry.snow_duration4 = atoi(row[77]); - entry.gravity = static_cast(atof(row[78])); - entry.type = atoi(row[79]); - entry.skylock = atoi(row[80]); - entry.fast_regen_hp = atoi(row[81]); - entry.fast_regen_mana = atoi(row[82]); - entry.fast_regen_endurance = atoi(row[83]); - entry.npc_max_aggro_dist = atoi(row[84]); - entry.max_movement_update_range = atoi(row[85]); - entry.min_expansion = atoi(row[86]); - entry.max_expansion = atoi(row[87]); - entry.content_flags = row[88] ? row[88] : ""; - entry.content_flags_disabled = row[89] ? row[89] : ""; - entry.underworld_teleport_index = atoi(row[90]); + entry.safe_heading = static_cast(atof(row[8])); + entry.graveyard_id = static_cast(atof(row[9])); + entry.min_level = atoi(row[10]); + entry.min_status = atoi(row[11]); + entry.zoneidnumber = atoi(row[12]); + entry.version = atoi(row[13]); + entry.timezone = atoi(row[14]); + entry.maxclients = atoi(row[15]); + entry.ruleset = atoi(row[16]); + entry.note = row[17] ? row[17] : ""; + entry.underworld = static_cast(atof(row[18])); + entry.minclip = static_cast(atof(row[19])); + entry.maxclip = static_cast(atof(row[20])); + entry.fog_minclip = static_cast(atof(row[21])); + entry.fog_maxclip = static_cast(atof(row[22])); + entry.fog_blue = atoi(row[23]); + entry.fog_red = atoi(row[24]); + entry.fog_green = atoi(row[25]); + entry.sky = atoi(row[26]); + entry.ztype = atoi(row[27]); + entry.zone_exp_multiplier = static_cast(atof(row[28])); + entry.walkspeed = static_cast(atof(row[29])); + entry.time_type = atoi(row[30]); + entry.fog_red1 = atoi(row[31]); + entry.fog_green1 = atoi(row[32]); + entry.fog_blue1 = atoi(row[33]); + entry.fog_minclip1 = static_cast(atof(row[34])); + entry.fog_maxclip1 = static_cast(atof(row[35])); + entry.fog_red2 = atoi(row[36]); + entry.fog_green2 = atoi(row[37]); + entry.fog_blue2 = atoi(row[38]); + entry.fog_minclip2 = static_cast(atof(row[39])); + entry.fog_maxclip2 = static_cast(atof(row[40])); + entry.fog_red3 = atoi(row[41]); + entry.fog_green3 = atoi(row[42]); + entry.fog_blue3 = atoi(row[43]); + entry.fog_minclip3 = static_cast(atof(row[44])); + entry.fog_maxclip3 = static_cast(atof(row[45])); + entry.fog_red4 = atoi(row[46]); + entry.fog_green4 = atoi(row[47]); + entry.fog_blue4 = atoi(row[48]); + entry.fog_minclip4 = static_cast(atof(row[49])); + entry.fog_maxclip4 = static_cast(atof(row[50])); + entry.fog_density = static_cast(atof(row[51])); + entry.flag_needed = row[52] ? row[52] : ""; + entry.canbind = atoi(row[53]); + entry.cancombat = atoi(row[54]); + entry.canlevitate = atoi(row[55]); + entry.castoutdoor = atoi(row[56]); + entry.hotzone = atoi(row[57]); + entry.insttype = atoi(row[58]); + entry.shutdowndelay = strtoll(row[59], NULL, 10); + entry.peqzone = atoi(row[60]); + entry.expansion = atoi(row[61]); + entry.suspendbuffs = atoi(row[62]); + entry.rain_chance1 = atoi(row[63]); + entry.rain_chance2 = atoi(row[64]); + entry.rain_chance3 = atoi(row[65]); + entry.rain_chance4 = atoi(row[66]); + entry.rain_duration1 = atoi(row[67]); + entry.rain_duration2 = atoi(row[68]); + entry.rain_duration3 = atoi(row[69]); + entry.rain_duration4 = atoi(row[70]); + entry.snow_chance1 = atoi(row[71]); + entry.snow_chance2 = atoi(row[72]); + entry.snow_chance3 = atoi(row[73]); + entry.snow_chance4 = atoi(row[74]); + entry.snow_duration1 = atoi(row[75]); + entry.snow_duration2 = atoi(row[76]); + entry.snow_duration3 = atoi(row[77]); + entry.snow_duration4 = atoi(row[78]); + entry.gravity = static_cast(atof(row[79])); + entry.type = atoi(row[80]); + entry.skylock = atoi(row[81]); + entry.fast_regen_hp = atoi(row[82]); + entry.fast_regen_mana = atoi(row[83]); + entry.fast_regen_endurance = atoi(row[84]); + entry.npc_max_aggro_dist = atoi(row[85]); + entry.max_movement_update_range = atoi(row[86]); + entry.min_expansion = atoi(row[87]); + entry.max_expansion = atoi(row[88]); + entry.content_flags = row[89] ? row[89] : ""; + entry.content_flags_disabled = row[90] ? row[90] : ""; + entry.underworld_teleport_index = atoi(row[91]); + entry.lava_damage = atoi(row[92]); + entry.min_lava_damage = atoi(row[93]); return entry; } @@ -502,89 +514,92 @@ public: update_values.push_back(columns[5] + " = " + std::to_string(zone_entry.safe_x)); update_values.push_back(columns[6] + " = " + std::to_string(zone_entry.safe_y)); update_values.push_back(columns[7] + " = " + std::to_string(zone_entry.safe_z)); - update_values.push_back(columns[8] + " = " + std::to_string(zone_entry.graveyard_id)); - update_values.push_back(columns[9] + " = " + std::to_string(zone_entry.min_level)); - update_values.push_back(columns[10] + " = " + std::to_string(zone_entry.min_status)); - update_values.push_back(columns[11] + " = " + std::to_string(zone_entry.zoneidnumber)); - update_values.push_back(columns[12] + " = " + std::to_string(zone_entry.version)); - update_values.push_back(columns[13] + " = " + std::to_string(zone_entry.timezone)); - update_values.push_back(columns[14] + " = " + std::to_string(zone_entry.maxclients)); - update_values.push_back(columns[15] + " = " + std::to_string(zone_entry.ruleset)); - update_values.push_back(columns[16] + " = '" + EscapeString(zone_entry.note) + "'"); - update_values.push_back(columns[17] + " = " + std::to_string(zone_entry.underworld)); - update_values.push_back(columns[18] + " = " + std::to_string(zone_entry.minclip)); - update_values.push_back(columns[19] + " = " + std::to_string(zone_entry.maxclip)); - update_values.push_back(columns[20] + " = " + std::to_string(zone_entry.fog_minclip)); - update_values.push_back(columns[21] + " = " + std::to_string(zone_entry.fog_maxclip)); - update_values.push_back(columns[22] + " = " + std::to_string(zone_entry.fog_blue)); - update_values.push_back(columns[23] + " = " + std::to_string(zone_entry.fog_red)); - update_values.push_back(columns[24] + " = " + std::to_string(zone_entry.fog_green)); - update_values.push_back(columns[25] + " = " + std::to_string(zone_entry.sky)); - update_values.push_back(columns[26] + " = " + std::to_string(zone_entry.ztype)); - update_values.push_back(columns[27] + " = " + std::to_string(zone_entry.zone_exp_multiplier)); - update_values.push_back(columns[28] + " = " + std::to_string(zone_entry.walkspeed)); - update_values.push_back(columns[29] + " = " + std::to_string(zone_entry.time_type)); - update_values.push_back(columns[30] + " = " + std::to_string(zone_entry.fog_red1)); - update_values.push_back(columns[31] + " = " + std::to_string(zone_entry.fog_green1)); - update_values.push_back(columns[32] + " = " + std::to_string(zone_entry.fog_blue1)); - update_values.push_back(columns[33] + " = " + std::to_string(zone_entry.fog_minclip1)); - update_values.push_back(columns[34] + " = " + std::to_string(zone_entry.fog_maxclip1)); - update_values.push_back(columns[35] + " = " + std::to_string(zone_entry.fog_red2)); - update_values.push_back(columns[36] + " = " + std::to_string(zone_entry.fog_green2)); - update_values.push_back(columns[37] + " = " + std::to_string(zone_entry.fog_blue2)); - update_values.push_back(columns[38] + " = " + std::to_string(zone_entry.fog_minclip2)); - update_values.push_back(columns[39] + " = " + std::to_string(zone_entry.fog_maxclip2)); - update_values.push_back(columns[40] + " = " + std::to_string(zone_entry.fog_red3)); - update_values.push_back(columns[41] + " = " + std::to_string(zone_entry.fog_green3)); - update_values.push_back(columns[42] + " = " + std::to_string(zone_entry.fog_blue3)); - update_values.push_back(columns[43] + " = " + std::to_string(zone_entry.fog_minclip3)); - update_values.push_back(columns[44] + " = " + std::to_string(zone_entry.fog_maxclip3)); - update_values.push_back(columns[45] + " = " + std::to_string(zone_entry.fog_red4)); - update_values.push_back(columns[46] + " = " + std::to_string(zone_entry.fog_green4)); - update_values.push_back(columns[47] + " = " + std::to_string(zone_entry.fog_blue4)); - update_values.push_back(columns[48] + " = " + std::to_string(zone_entry.fog_minclip4)); - update_values.push_back(columns[49] + " = " + std::to_string(zone_entry.fog_maxclip4)); - update_values.push_back(columns[50] + " = " + std::to_string(zone_entry.fog_density)); - update_values.push_back(columns[51] + " = '" + EscapeString(zone_entry.flag_needed) + "'"); - update_values.push_back(columns[52] + " = " + std::to_string(zone_entry.canbind)); - update_values.push_back(columns[53] + " = " + std::to_string(zone_entry.cancombat)); - update_values.push_back(columns[54] + " = " + std::to_string(zone_entry.canlevitate)); - update_values.push_back(columns[55] + " = " + std::to_string(zone_entry.castoutdoor)); - update_values.push_back(columns[56] + " = " + std::to_string(zone_entry.hotzone)); - update_values.push_back(columns[57] + " = " + std::to_string(zone_entry.insttype)); - update_values.push_back(columns[58] + " = " + std::to_string(zone_entry.shutdowndelay)); - update_values.push_back(columns[59] + " = " + std::to_string(zone_entry.peqzone)); - update_values.push_back(columns[60] + " = " + std::to_string(zone_entry.expansion)); - update_values.push_back(columns[61] + " = " + std::to_string(zone_entry.suspendbuffs)); - update_values.push_back(columns[62] + " = " + std::to_string(zone_entry.rain_chance1)); - update_values.push_back(columns[63] + " = " + std::to_string(zone_entry.rain_chance2)); - update_values.push_back(columns[64] + " = " + std::to_string(zone_entry.rain_chance3)); - update_values.push_back(columns[65] + " = " + std::to_string(zone_entry.rain_chance4)); - update_values.push_back(columns[66] + " = " + std::to_string(zone_entry.rain_duration1)); - update_values.push_back(columns[67] + " = " + std::to_string(zone_entry.rain_duration2)); - update_values.push_back(columns[68] + " = " + std::to_string(zone_entry.rain_duration3)); - update_values.push_back(columns[69] + " = " + std::to_string(zone_entry.rain_duration4)); - update_values.push_back(columns[70] + " = " + std::to_string(zone_entry.snow_chance1)); - update_values.push_back(columns[71] + " = " + std::to_string(zone_entry.snow_chance2)); - update_values.push_back(columns[72] + " = " + std::to_string(zone_entry.snow_chance3)); - update_values.push_back(columns[73] + " = " + std::to_string(zone_entry.snow_chance4)); - update_values.push_back(columns[74] + " = " + std::to_string(zone_entry.snow_duration1)); - update_values.push_back(columns[75] + " = " + std::to_string(zone_entry.snow_duration2)); - update_values.push_back(columns[76] + " = " + std::to_string(zone_entry.snow_duration3)); - update_values.push_back(columns[77] + " = " + std::to_string(zone_entry.snow_duration4)); - update_values.push_back(columns[78] + " = " + std::to_string(zone_entry.gravity)); - update_values.push_back(columns[79] + " = " + std::to_string(zone_entry.type)); - update_values.push_back(columns[80] + " = " + std::to_string(zone_entry.skylock)); - update_values.push_back(columns[81] + " = " + std::to_string(zone_entry.fast_regen_hp)); - update_values.push_back(columns[82] + " = " + std::to_string(zone_entry.fast_regen_mana)); - update_values.push_back(columns[83] + " = " + std::to_string(zone_entry.fast_regen_endurance)); - update_values.push_back(columns[84] + " = " + std::to_string(zone_entry.npc_max_aggro_dist)); - update_values.push_back(columns[85] + " = " + std::to_string(zone_entry.max_movement_update_range)); - update_values.push_back(columns[86] + " = " + std::to_string(zone_entry.min_expansion)); - update_values.push_back(columns[87] + " = " + std::to_string(zone_entry.max_expansion)); - update_values.push_back(columns[88] + " = '" + EscapeString(zone_entry.content_flags) + "'"); - update_values.push_back(columns[89] + " = '" + EscapeString(zone_entry.content_flags_disabled) + "'"); - update_values.push_back(columns[90] + " = " + std::to_string(zone_entry.underworld_teleport_index)); + update_values.push_back(columns[8] + " = " + std::to_string(zone_entry.safe_heading)); + update_values.push_back(columns[9] + " = " + std::to_string(zone_entry.graveyard_id)); + update_values.push_back(columns[10] + " = " + std::to_string(zone_entry.min_level)); + update_values.push_back(columns[11] + " = " + std::to_string(zone_entry.min_status)); + update_values.push_back(columns[12] + " = " + std::to_string(zone_entry.zoneidnumber)); + update_values.push_back(columns[13] + " = " + std::to_string(zone_entry.version)); + update_values.push_back(columns[14] + " = " + std::to_string(zone_entry.timezone)); + update_values.push_back(columns[15] + " = " + std::to_string(zone_entry.maxclients)); + update_values.push_back(columns[16] + " = " + std::to_string(zone_entry.ruleset)); + update_values.push_back(columns[17] + " = '" + EscapeString(zone_entry.note) + "'"); + update_values.push_back(columns[18] + " = " + std::to_string(zone_entry.underworld)); + update_values.push_back(columns[19] + " = " + std::to_string(zone_entry.minclip)); + update_values.push_back(columns[20] + " = " + std::to_string(zone_entry.maxclip)); + update_values.push_back(columns[21] + " = " + std::to_string(zone_entry.fog_minclip)); + update_values.push_back(columns[22] + " = " + std::to_string(zone_entry.fog_maxclip)); + update_values.push_back(columns[23] + " = " + std::to_string(zone_entry.fog_blue)); + update_values.push_back(columns[24] + " = " + std::to_string(zone_entry.fog_red)); + update_values.push_back(columns[25] + " = " + std::to_string(zone_entry.fog_green)); + update_values.push_back(columns[26] + " = " + std::to_string(zone_entry.sky)); + update_values.push_back(columns[27] + " = " + std::to_string(zone_entry.ztype)); + update_values.push_back(columns[28] + " = " + std::to_string(zone_entry.zone_exp_multiplier)); + update_values.push_back(columns[29] + " = " + std::to_string(zone_entry.walkspeed)); + update_values.push_back(columns[30] + " = " + std::to_string(zone_entry.time_type)); + update_values.push_back(columns[31] + " = " + std::to_string(zone_entry.fog_red1)); + update_values.push_back(columns[32] + " = " + std::to_string(zone_entry.fog_green1)); + update_values.push_back(columns[33] + " = " + std::to_string(zone_entry.fog_blue1)); + update_values.push_back(columns[34] + " = " + std::to_string(zone_entry.fog_minclip1)); + update_values.push_back(columns[35] + " = " + std::to_string(zone_entry.fog_maxclip1)); + update_values.push_back(columns[36] + " = " + std::to_string(zone_entry.fog_red2)); + update_values.push_back(columns[37] + " = " + std::to_string(zone_entry.fog_green2)); + update_values.push_back(columns[38] + " = " + std::to_string(zone_entry.fog_blue2)); + update_values.push_back(columns[39] + " = " + std::to_string(zone_entry.fog_minclip2)); + update_values.push_back(columns[40] + " = " + std::to_string(zone_entry.fog_maxclip2)); + update_values.push_back(columns[41] + " = " + std::to_string(zone_entry.fog_red3)); + update_values.push_back(columns[42] + " = " + std::to_string(zone_entry.fog_green3)); + update_values.push_back(columns[43] + " = " + std::to_string(zone_entry.fog_blue3)); + update_values.push_back(columns[44] + " = " + std::to_string(zone_entry.fog_minclip3)); + update_values.push_back(columns[45] + " = " + std::to_string(zone_entry.fog_maxclip3)); + update_values.push_back(columns[46] + " = " + std::to_string(zone_entry.fog_red4)); + update_values.push_back(columns[47] + " = " + std::to_string(zone_entry.fog_green4)); + update_values.push_back(columns[48] + " = " + std::to_string(zone_entry.fog_blue4)); + update_values.push_back(columns[49] + " = " + std::to_string(zone_entry.fog_minclip4)); + update_values.push_back(columns[50] + " = " + std::to_string(zone_entry.fog_maxclip4)); + update_values.push_back(columns[51] + " = " + std::to_string(zone_entry.fog_density)); + update_values.push_back(columns[52] + " = '" + EscapeString(zone_entry.flag_needed) + "'"); + update_values.push_back(columns[53] + " = " + std::to_string(zone_entry.canbind)); + update_values.push_back(columns[54] + " = " + std::to_string(zone_entry.cancombat)); + update_values.push_back(columns[55] + " = " + std::to_string(zone_entry.canlevitate)); + update_values.push_back(columns[56] + " = " + std::to_string(zone_entry.castoutdoor)); + update_values.push_back(columns[57] + " = " + std::to_string(zone_entry.hotzone)); + update_values.push_back(columns[58] + " = " + std::to_string(zone_entry.insttype)); + update_values.push_back(columns[59] + " = " + std::to_string(zone_entry.shutdowndelay)); + update_values.push_back(columns[60] + " = " + std::to_string(zone_entry.peqzone)); + update_values.push_back(columns[61] + " = " + std::to_string(zone_entry.expansion)); + update_values.push_back(columns[62] + " = " + std::to_string(zone_entry.suspendbuffs)); + update_values.push_back(columns[63] + " = " + std::to_string(zone_entry.rain_chance1)); + update_values.push_back(columns[64] + " = " + std::to_string(zone_entry.rain_chance2)); + update_values.push_back(columns[65] + " = " + std::to_string(zone_entry.rain_chance3)); + update_values.push_back(columns[66] + " = " + std::to_string(zone_entry.rain_chance4)); + update_values.push_back(columns[67] + " = " + std::to_string(zone_entry.rain_duration1)); + update_values.push_back(columns[68] + " = " + std::to_string(zone_entry.rain_duration2)); + update_values.push_back(columns[69] + " = " + std::to_string(zone_entry.rain_duration3)); + update_values.push_back(columns[70] + " = " + std::to_string(zone_entry.rain_duration4)); + update_values.push_back(columns[71] + " = " + std::to_string(zone_entry.snow_chance1)); + update_values.push_back(columns[72] + " = " + std::to_string(zone_entry.snow_chance2)); + update_values.push_back(columns[73] + " = " + std::to_string(zone_entry.snow_chance3)); + update_values.push_back(columns[74] + " = " + std::to_string(zone_entry.snow_chance4)); + update_values.push_back(columns[75] + " = " + std::to_string(zone_entry.snow_duration1)); + update_values.push_back(columns[76] + " = " + std::to_string(zone_entry.snow_duration2)); + update_values.push_back(columns[77] + " = " + std::to_string(zone_entry.snow_duration3)); + update_values.push_back(columns[78] + " = " + std::to_string(zone_entry.snow_duration4)); + update_values.push_back(columns[79] + " = " + std::to_string(zone_entry.gravity)); + update_values.push_back(columns[80] + " = " + std::to_string(zone_entry.type)); + update_values.push_back(columns[81] + " = " + std::to_string(zone_entry.skylock)); + update_values.push_back(columns[82] + " = " + std::to_string(zone_entry.fast_regen_hp)); + update_values.push_back(columns[83] + " = " + std::to_string(zone_entry.fast_regen_mana)); + update_values.push_back(columns[84] + " = " + std::to_string(zone_entry.fast_regen_endurance)); + update_values.push_back(columns[85] + " = " + std::to_string(zone_entry.npc_max_aggro_dist)); + update_values.push_back(columns[86] + " = " + std::to_string(zone_entry.max_movement_update_range)); + update_values.push_back(columns[87] + " = " + std::to_string(zone_entry.min_expansion)); + update_values.push_back(columns[88] + " = " + std::to_string(zone_entry.max_expansion)); + update_values.push_back(columns[89] + " = '" + EscapeString(zone_entry.content_flags) + "'"); + update_values.push_back(columns[90] + " = '" + EscapeString(zone_entry.content_flags_disabled) + "'"); + update_values.push_back(columns[91] + " = " + std::to_string(zone_entry.underworld_teleport_index)); + update_values.push_back(columns[92] + " = " + std::to_string(zone_entry.lava_damage)); + update_values.push_back(columns[93] + " = " + std::to_string(zone_entry.min_lava_damage)); auto results = db.QueryDatabase( fmt::format( @@ -614,6 +629,7 @@ public: insert_values.push_back(std::to_string(zone_entry.safe_x)); insert_values.push_back(std::to_string(zone_entry.safe_y)); insert_values.push_back(std::to_string(zone_entry.safe_z)); + insert_values.push_back(std::to_string(zone_entry.safe_heading)); insert_values.push_back(std::to_string(zone_entry.graveyard_id)); insert_values.push_back(std::to_string(zone_entry.min_level)); insert_values.push_back(std::to_string(zone_entry.min_status)); @@ -697,6 +713,8 @@ public: insert_values.push_back("'" + EscapeString(zone_entry.content_flags) + "'"); insert_values.push_back("'" + EscapeString(zone_entry.content_flags_disabled) + "'"); insert_values.push_back(std::to_string(zone_entry.underworld_teleport_index)); + insert_values.push_back(std::to_string(zone_entry.lava_damage)); + insert_values.push_back(std::to_string(zone_entry.min_lava_damage)); auto results = db.QueryDatabase( fmt::format( @@ -734,6 +752,7 @@ public: insert_values.push_back(std::to_string(zone_entry.safe_x)); insert_values.push_back(std::to_string(zone_entry.safe_y)); insert_values.push_back(std::to_string(zone_entry.safe_z)); + insert_values.push_back(std::to_string(zone_entry.safe_heading)); insert_values.push_back(std::to_string(zone_entry.graveyard_id)); insert_values.push_back(std::to_string(zone_entry.min_level)); insert_values.push_back(std::to_string(zone_entry.min_status)); @@ -817,6 +836,8 @@ public: insert_values.push_back("'" + EscapeString(zone_entry.content_flags) + "'"); insert_values.push_back("'" + EscapeString(zone_entry.content_flags_disabled) + "'"); insert_values.push_back(std::to_string(zone_entry.underworld_teleport_index)); + insert_values.push_back(std::to_string(zone_entry.lava_damage)); + insert_values.push_back(std::to_string(zone_entry.min_lava_damage)); insert_chunks.push_back("(" + implode(",", insert_values) + ")"); } @@ -858,89 +879,92 @@ public: entry.safe_x = static_cast(atof(row[5])); entry.safe_y = static_cast(atof(row[6])); entry.safe_z = static_cast(atof(row[7])); - entry.graveyard_id = static_cast(atof(row[8])); - entry.min_level = atoi(row[9]); - entry.min_status = atoi(row[10]); - entry.zoneidnumber = atoi(row[11]); - entry.version = atoi(row[12]); - entry.timezone = atoi(row[13]); - entry.maxclients = atoi(row[14]); - entry.ruleset = atoi(row[15]); - entry.note = row[16] ? row[16] : ""; - entry.underworld = static_cast(atof(row[17])); - entry.minclip = static_cast(atof(row[18])); - entry.maxclip = static_cast(atof(row[19])); - entry.fog_minclip = static_cast(atof(row[20])); - entry.fog_maxclip = static_cast(atof(row[21])); - entry.fog_blue = atoi(row[22]); - entry.fog_red = atoi(row[23]); - entry.fog_green = atoi(row[24]); - entry.sky = atoi(row[25]); - entry.ztype = atoi(row[26]); - entry.zone_exp_multiplier = static_cast(atof(row[27])); - entry.walkspeed = static_cast(atof(row[28])); - entry.time_type = atoi(row[29]); - entry.fog_red1 = atoi(row[30]); - entry.fog_green1 = atoi(row[31]); - entry.fog_blue1 = atoi(row[32]); - entry.fog_minclip1 = static_cast(atof(row[33])); - entry.fog_maxclip1 = static_cast(atof(row[34])); - entry.fog_red2 = atoi(row[35]); - entry.fog_green2 = atoi(row[36]); - entry.fog_blue2 = atoi(row[37]); - entry.fog_minclip2 = static_cast(atof(row[38])); - entry.fog_maxclip2 = static_cast(atof(row[39])); - entry.fog_red3 = atoi(row[40]); - entry.fog_green3 = atoi(row[41]); - entry.fog_blue3 = atoi(row[42]); - entry.fog_minclip3 = static_cast(atof(row[43])); - entry.fog_maxclip3 = static_cast(atof(row[44])); - entry.fog_red4 = atoi(row[45]); - entry.fog_green4 = atoi(row[46]); - entry.fog_blue4 = atoi(row[47]); - entry.fog_minclip4 = static_cast(atof(row[48])); - entry.fog_maxclip4 = static_cast(atof(row[49])); - entry.fog_density = static_cast(atof(row[50])); - entry.flag_needed = row[51] ? row[51] : ""; - entry.canbind = atoi(row[52]); - entry.cancombat = atoi(row[53]); - entry.canlevitate = atoi(row[54]); - entry.castoutdoor = atoi(row[55]); - entry.hotzone = atoi(row[56]); - entry.insttype = atoi(row[57]); - entry.shutdowndelay = atoi(row[58]); - entry.peqzone = atoi(row[59]); - entry.expansion = atoi(row[60]); - entry.suspendbuffs = atoi(row[61]); - entry.rain_chance1 = atoi(row[62]); - entry.rain_chance2 = atoi(row[63]); - entry.rain_chance3 = atoi(row[64]); - entry.rain_chance4 = atoi(row[65]); - entry.rain_duration1 = atoi(row[66]); - entry.rain_duration2 = atoi(row[67]); - entry.rain_duration3 = atoi(row[68]); - entry.rain_duration4 = atoi(row[69]); - entry.snow_chance1 = atoi(row[70]); - entry.snow_chance2 = atoi(row[71]); - entry.snow_chance3 = atoi(row[72]); - entry.snow_chance4 = atoi(row[73]); - entry.snow_duration1 = atoi(row[74]); - entry.snow_duration2 = atoi(row[75]); - entry.snow_duration3 = atoi(row[76]); - entry.snow_duration4 = atoi(row[77]); - entry.gravity = static_cast(atof(row[78])); - entry.type = atoi(row[79]); - entry.skylock = atoi(row[80]); - entry.fast_regen_hp = atoi(row[81]); - entry.fast_regen_mana = atoi(row[82]); - entry.fast_regen_endurance = atoi(row[83]); - entry.npc_max_aggro_dist = atoi(row[84]); - entry.max_movement_update_range = atoi(row[85]); - entry.min_expansion = atoi(row[86]); - entry.max_expansion = atoi(row[87]); - entry.content_flags = row[88] ? row[88] : ""; - entry.content_flags_disabled = row[89] ? row[89] : ""; - entry.underworld_teleport_index = atoi(row[90]); + entry.safe_heading = static_cast(atof(row[8])); + entry.graveyard_id = static_cast(atof(row[9])); + entry.min_level = atoi(row[10]); + entry.min_status = atoi(row[11]); + entry.zoneidnumber = atoi(row[12]); + entry.version = atoi(row[13]); + entry.timezone = atoi(row[14]); + entry.maxclients = atoi(row[15]); + entry.ruleset = atoi(row[16]); + entry.note = row[17] ? row[17] : ""; + entry.underworld = static_cast(atof(row[18])); + entry.minclip = static_cast(atof(row[19])); + entry.maxclip = static_cast(atof(row[20])); + entry.fog_minclip = static_cast(atof(row[21])); + entry.fog_maxclip = static_cast(atof(row[22])); + entry.fog_blue = atoi(row[23]); + entry.fog_red = atoi(row[24]); + entry.fog_green = atoi(row[25]); + entry.sky = atoi(row[26]); + entry.ztype = atoi(row[27]); + entry.zone_exp_multiplier = static_cast(atof(row[28])); + entry.walkspeed = static_cast(atof(row[29])); + entry.time_type = atoi(row[30]); + entry.fog_red1 = atoi(row[31]); + entry.fog_green1 = atoi(row[32]); + entry.fog_blue1 = atoi(row[33]); + entry.fog_minclip1 = static_cast(atof(row[34])); + entry.fog_maxclip1 = static_cast(atof(row[35])); + entry.fog_red2 = atoi(row[36]); + entry.fog_green2 = atoi(row[37]); + entry.fog_blue2 = atoi(row[38]); + entry.fog_minclip2 = static_cast(atof(row[39])); + entry.fog_maxclip2 = static_cast(atof(row[40])); + entry.fog_red3 = atoi(row[41]); + entry.fog_green3 = atoi(row[42]); + entry.fog_blue3 = atoi(row[43]); + entry.fog_minclip3 = static_cast(atof(row[44])); + entry.fog_maxclip3 = static_cast(atof(row[45])); + entry.fog_red4 = atoi(row[46]); + entry.fog_green4 = atoi(row[47]); + entry.fog_blue4 = atoi(row[48]); + entry.fog_minclip4 = static_cast(atof(row[49])); + entry.fog_maxclip4 = static_cast(atof(row[50])); + entry.fog_density = static_cast(atof(row[51])); + entry.flag_needed = row[52] ? row[52] : ""; + entry.canbind = atoi(row[53]); + entry.cancombat = atoi(row[54]); + entry.canlevitate = atoi(row[55]); + entry.castoutdoor = atoi(row[56]); + entry.hotzone = atoi(row[57]); + entry.insttype = atoi(row[58]); + entry.shutdowndelay = strtoll(row[59], NULL, 10); + entry.peqzone = atoi(row[60]); + entry.expansion = atoi(row[61]); + entry.suspendbuffs = atoi(row[62]); + entry.rain_chance1 = atoi(row[63]); + entry.rain_chance2 = atoi(row[64]); + entry.rain_chance3 = atoi(row[65]); + entry.rain_chance4 = atoi(row[66]); + entry.rain_duration1 = atoi(row[67]); + entry.rain_duration2 = atoi(row[68]); + entry.rain_duration3 = atoi(row[69]); + entry.rain_duration4 = atoi(row[70]); + entry.snow_chance1 = atoi(row[71]); + entry.snow_chance2 = atoi(row[72]); + entry.snow_chance3 = atoi(row[73]); + entry.snow_chance4 = atoi(row[74]); + entry.snow_duration1 = atoi(row[75]); + entry.snow_duration2 = atoi(row[76]); + entry.snow_duration3 = atoi(row[77]); + entry.snow_duration4 = atoi(row[78]); + entry.gravity = static_cast(atof(row[79])); + entry.type = atoi(row[80]); + entry.skylock = atoi(row[81]); + entry.fast_regen_hp = atoi(row[82]); + entry.fast_regen_mana = atoi(row[83]); + entry.fast_regen_endurance = atoi(row[84]); + entry.npc_max_aggro_dist = atoi(row[85]); + entry.max_movement_update_range = atoi(row[86]); + entry.min_expansion = atoi(row[87]); + entry.max_expansion = atoi(row[88]); + entry.content_flags = row[89] ? row[89] : ""; + entry.content_flags_disabled = row[90] ? row[90] : ""; + entry.underworld_teleport_index = atoi(row[91]); + entry.lava_damage = atoi(row[92]); + entry.min_lava_damage = atoi(row[93]); all_entries.push_back(entry); } @@ -973,89 +997,92 @@ public: entry.safe_x = static_cast(atof(row[5])); entry.safe_y = static_cast(atof(row[6])); entry.safe_z = static_cast(atof(row[7])); - entry.graveyard_id = static_cast(atof(row[8])); - entry.min_level = atoi(row[9]); - entry.min_status = atoi(row[10]); - entry.zoneidnumber = atoi(row[11]); - entry.version = atoi(row[12]); - entry.timezone = atoi(row[13]); - entry.maxclients = atoi(row[14]); - entry.ruleset = atoi(row[15]); - entry.note = row[16] ? row[16] : ""; - entry.underworld = static_cast(atof(row[17])); - entry.minclip = static_cast(atof(row[18])); - entry.maxclip = static_cast(atof(row[19])); - entry.fog_minclip = static_cast(atof(row[20])); - entry.fog_maxclip = static_cast(atof(row[21])); - entry.fog_blue = atoi(row[22]); - entry.fog_red = atoi(row[23]); - entry.fog_green = atoi(row[24]); - entry.sky = atoi(row[25]); - entry.ztype = atoi(row[26]); - entry.zone_exp_multiplier = static_cast(atof(row[27])); - entry.walkspeed = static_cast(atof(row[28])); - entry.time_type = atoi(row[29]); - entry.fog_red1 = atoi(row[30]); - entry.fog_green1 = atoi(row[31]); - entry.fog_blue1 = atoi(row[32]); - entry.fog_minclip1 = static_cast(atof(row[33])); - entry.fog_maxclip1 = static_cast(atof(row[34])); - entry.fog_red2 = atoi(row[35]); - entry.fog_green2 = atoi(row[36]); - entry.fog_blue2 = atoi(row[37]); - entry.fog_minclip2 = static_cast(atof(row[38])); - entry.fog_maxclip2 = static_cast(atof(row[39])); - entry.fog_red3 = atoi(row[40]); - entry.fog_green3 = atoi(row[41]); - entry.fog_blue3 = atoi(row[42]); - entry.fog_minclip3 = static_cast(atof(row[43])); - entry.fog_maxclip3 = static_cast(atof(row[44])); - entry.fog_red4 = atoi(row[45]); - entry.fog_green4 = atoi(row[46]); - entry.fog_blue4 = atoi(row[47]); - entry.fog_minclip4 = static_cast(atof(row[48])); - entry.fog_maxclip4 = static_cast(atof(row[49])); - entry.fog_density = static_cast(atof(row[50])); - entry.flag_needed = row[51] ? row[51] : ""; - entry.canbind = atoi(row[52]); - entry.cancombat = atoi(row[53]); - entry.canlevitate = atoi(row[54]); - entry.castoutdoor = atoi(row[55]); - entry.hotzone = atoi(row[56]); - entry.insttype = atoi(row[57]); - entry.shutdowndelay = atoi(row[58]); - entry.peqzone = atoi(row[59]); - entry.expansion = atoi(row[60]); - entry.suspendbuffs = atoi(row[61]); - entry.rain_chance1 = atoi(row[62]); - entry.rain_chance2 = atoi(row[63]); - entry.rain_chance3 = atoi(row[64]); - entry.rain_chance4 = atoi(row[65]); - entry.rain_duration1 = atoi(row[66]); - entry.rain_duration2 = atoi(row[67]); - entry.rain_duration3 = atoi(row[68]); - entry.rain_duration4 = atoi(row[69]); - entry.snow_chance1 = atoi(row[70]); - entry.snow_chance2 = atoi(row[71]); - entry.snow_chance3 = atoi(row[72]); - entry.snow_chance4 = atoi(row[73]); - entry.snow_duration1 = atoi(row[74]); - entry.snow_duration2 = atoi(row[75]); - entry.snow_duration3 = atoi(row[76]); - entry.snow_duration4 = atoi(row[77]); - entry.gravity = static_cast(atof(row[78])); - entry.type = atoi(row[79]); - entry.skylock = atoi(row[80]); - entry.fast_regen_hp = atoi(row[81]); - entry.fast_regen_mana = atoi(row[82]); - entry.fast_regen_endurance = atoi(row[83]); - entry.npc_max_aggro_dist = atoi(row[84]); - entry.max_movement_update_range = atoi(row[85]); - entry.min_expansion = atoi(row[86]); - entry.max_expansion = atoi(row[87]); - entry.content_flags = row[88] ? row[88] : ""; - entry.content_flags_disabled = row[89] ? row[89] : ""; - entry.underworld_teleport_index = atoi(row[90]); + entry.safe_heading = static_cast(atof(row[8])); + entry.graveyard_id = static_cast(atof(row[9])); + entry.min_level = atoi(row[10]); + entry.min_status = atoi(row[11]); + entry.zoneidnumber = atoi(row[12]); + entry.version = atoi(row[13]); + entry.timezone = atoi(row[14]); + entry.maxclients = atoi(row[15]); + entry.ruleset = atoi(row[16]); + entry.note = row[17] ? row[17] : ""; + entry.underworld = static_cast(atof(row[18])); + entry.minclip = static_cast(atof(row[19])); + entry.maxclip = static_cast(atof(row[20])); + entry.fog_minclip = static_cast(atof(row[21])); + entry.fog_maxclip = static_cast(atof(row[22])); + entry.fog_blue = atoi(row[23]); + entry.fog_red = atoi(row[24]); + entry.fog_green = atoi(row[25]); + entry.sky = atoi(row[26]); + entry.ztype = atoi(row[27]); + entry.zone_exp_multiplier = static_cast(atof(row[28])); + entry.walkspeed = static_cast(atof(row[29])); + entry.time_type = atoi(row[30]); + entry.fog_red1 = atoi(row[31]); + entry.fog_green1 = atoi(row[32]); + entry.fog_blue1 = atoi(row[33]); + entry.fog_minclip1 = static_cast(atof(row[34])); + entry.fog_maxclip1 = static_cast(atof(row[35])); + entry.fog_red2 = atoi(row[36]); + entry.fog_green2 = atoi(row[37]); + entry.fog_blue2 = atoi(row[38]); + entry.fog_minclip2 = static_cast(atof(row[39])); + entry.fog_maxclip2 = static_cast(atof(row[40])); + entry.fog_red3 = atoi(row[41]); + entry.fog_green3 = atoi(row[42]); + entry.fog_blue3 = atoi(row[43]); + entry.fog_minclip3 = static_cast(atof(row[44])); + entry.fog_maxclip3 = static_cast(atof(row[45])); + entry.fog_red4 = atoi(row[46]); + entry.fog_green4 = atoi(row[47]); + entry.fog_blue4 = atoi(row[48]); + entry.fog_minclip4 = static_cast(atof(row[49])); + entry.fog_maxclip4 = static_cast(atof(row[50])); + entry.fog_density = static_cast(atof(row[51])); + entry.flag_needed = row[52] ? row[52] : ""; + entry.canbind = atoi(row[53]); + entry.cancombat = atoi(row[54]); + entry.canlevitate = atoi(row[55]); + entry.castoutdoor = atoi(row[56]); + entry.hotzone = atoi(row[57]); + entry.insttype = atoi(row[58]); + entry.shutdowndelay = strtoll(row[59], NULL, 10); + entry.peqzone = atoi(row[60]); + entry.expansion = atoi(row[61]); + entry.suspendbuffs = atoi(row[62]); + entry.rain_chance1 = atoi(row[63]); + entry.rain_chance2 = atoi(row[64]); + entry.rain_chance3 = atoi(row[65]); + entry.rain_chance4 = atoi(row[66]); + entry.rain_duration1 = atoi(row[67]); + entry.rain_duration2 = atoi(row[68]); + entry.rain_duration3 = atoi(row[69]); + entry.rain_duration4 = atoi(row[70]); + entry.snow_chance1 = atoi(row[71]); + entry.snow_chance2 = atoi(row[72]); + entry.snow_chance3 = atoi(row[73]); + entry.snow_chance4 = atoi(row[74]); + entry.snow_duration1 = atoi(row[75]); + entry.snow_duration2 = atoi(row[76]); + entry.snow_duration3 = atoi(row[77]); + entry.snow_duration4 = atoi(row[78]); + entry.gravity = static_cast(atof(row[79])); + entry.type = atoi(row[80]); + entry.skylock = atoi(row[81]); + entry.fast_regen_hp = atoi(row[82]); + entry.fast_regen_mana = atoi(row[83]); + entry.fast_regen_endurance = atoi(row[84]); + entry.npc_max_aggro_dist = atoi(row[85]); + entry.max_movement_update_range = atoi(row[86]); + entry.min_expansion = atoi(row[87]); + entry.max_expansion = atoi(row[88]); + entry.content_flags = row[89] ? row[89] : ""; + entry.content_flags_disabled = row[90] ? row[90] : ""; + entry.underworld_teleport_index = atoi(row[91]); + entry.lava_damage = atoi(row[92]); + entry.min_lava_damage = atoi(row[93]); all_entries.push_back(entry); } diff --git a/common/repositories/character_instance_safereturns_repository.h b/common/repositories/character_instance_safereturns_repository.h new file mode 100644 index 000000000..89bfddead --- /dev/null +++ b/common/repositories/character_instance_safereturns_repository.h @@ -0,0 +1,108 @@ +/** + * 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_CHARACTER_INSTANCE_SAFERETURNS_REPOSITORY_H +#define EQEMU_CHARACTER_INSTANCE_SAFERETURNS_REPOSITORY_H + +#include "../database.h" +#include "../string_util.h" +#include "base/base_character_instance_safereturns_repository.h" + +class CharacterInstanceSafereturnsRepository: public BaseCharacterInstanceSafereturnsRepository { +public: + + /** + * This file was auto generated and can be modified and extended upon + * + * Base repository methods are automatically + * generated in the "base" version of this repository. The base repository + * is immutable and to be left untouched, while methods in this class + * are used as extension methods for more specific persistence-layer + * accessors or mutators. + * + * Base Methods (Subject to be expanded upon in time) + * + * Note: Not all tables are designed appropriately to fit functionality with all base methods + * + * InsertOne + * UpdateOne + * DeleteOne + * FindOne + * GetWhere(std::string where_filter) + * DeleteWhere(std::string where_filter) + * InsertMany + * All + * + * Example custom methods in a repository + * + * CharacterInstanceSafereturnsRepository::GetByZoneAndVersion(int zone_id, int zone_version) + * CharacterInstanceSafereturnsRepository::GetWhereNeverExpires() + * CharacterInstanceSafereturnsRepository::GetWhereXAndY() + * CharacterInstanceSafereturnsRepository::DeleteWhereXAndY() + * + * Most of the above could be covered by base methods, but if you as a developer + * find yourself re-using logic for other parts of the code, its best to just make a + * method that can be re-used easily elsewhere especially if it can use a base repository + * method and encapsulate filters there + */ + + // Custom extended repository methods here + + static CharacterInstanceSafereturns InsertOneOrUpdate( + Database& db, CharacterInstanceSafereturns& character_instance_safereturns_entry) + { + std::vector insert_values; + + insert_values.push_back(std::to_string(character_instance_safereturns_entry.id)); + insert_values.push_back(std::to_string(character_instance_safereturns_entry.character_id)); + insert_values.push_back(std::to_string(character_instance_safereturns_entry.instance_zone_id)); + insert_values.push_back(std::to_string(character_instance_safereturns_entry.instance_id)); + insert_values.push_back(std::to_string(character_instance_safereturns_entry.safe_zone_id)); + insert_values.push_back(std::to_string(character_instance_safereturns_entry.safe_x)); + insert_values.push_back(std::to_string(character_instance_safereturns_entry.safe_y)); + insert_values.push_back(std::to_string(character_instance_safereturns_entry.safe_z)); + insert_values.push_back(std::to_string(character_instance_safereturns_entry.safe_heading)); + + auto results = db.QueryDatabase(fmt::format(SQL( + {} VALUES ({}) + ON DUPLICATE KEY UPDATE + instance_zone_id = VALUES(instance_zone_id), + instance_id = VALUES(instance_id), + safe_zone_id = VALUES(safe_zone_id), + safe_x = VALUES(safe_x), + safe_y = VALUES(safe_y), + safe_z = VALUES(safe_z), + safe_heading = VALUES(safe_heading) + ), + BaseInsert(), + implode(",", insert_values) + )); + + if (results.Success()) + { + character_instance_safereturns_entry.id = results.LastInsertedID(); + return character_instance_safereturns_entry; + } + + return NewEntity(); + } +}; + +#endif //EQEMU_CHARACTER_INSTANCE_SAFERETURNS_REPOSITORY_H diff --git a/common/repositories/character_task_timers_repository.h b/common/repositories/character_task_timers_repository.h new file mode 100644 index 000000000..7f0b07962 --- /dev/null +++ b/common/repositories/character_task_timers_repository.h @@ -0,0 +1,70 @@ +/** + * 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_CHARACTER_TASK_TIMERS_REPOSITORY_H +#define EQEMU_CHARACTER_TASK_TIMERS_REPOSITORY_H + +#include "../database.h" +#include "../string_util.h" +#include "base/base_character_task_timers_repository.h" + +class CharacterTaskTimersRepository: public BaseCharacterTaskTimersRepository { +public: + + /** + * This file was auto generated and can be modified and extended upon + * + * Base repository methods are automatically + * generated in the "base" version of this repository. The base repository + * is immutable and to be left untouched, while methods in this class + * are used as extension methods for more specific persistence-layer + * accessors or mutators. + * + * Base Methods (Subject to be expanded upon in time) + * + * Note: Not all tables are designed appropriately to fit functionality with all base methods + * + * InsertOne + * UpdateOne + * DeleteOne + * FindOne + * GetWhere(std::string where_filter) + * DeleteWhere(std::string where_filter) + * InsertMany + * All + * + * Example custom methods in a repository + * + * CharacterTaskTimersRepository::GetByZoneAndVersion(int zone_id, int zone_version) + * CharacterTaskTimersRepository::GetWhereNeverExpires() + * CharacterTaskTimersRepository::GetWhereXAndY() + * CharacterTaskTimersRepository::DeleteWhereXAndY() + * + * Most of the above could be covered by base methods, but if you as a developer + * find yourself re-using logic for other parts of the code, its best to just make a + * method that can be re-used easily elsewhere especially if it can use a base repository + * method and encapsulate filters there + */ + + // Custom extended repository methods here + +}; + +#endif //EQEMU_CHARACTER_TASK_TIMERS_REPOSITORY_H diff --git a/common/repositories/completed_shared_task_activity_state_repository.h b/common/repositories/completed_shared_task_activity_state_repository.h new file mode 100644 index 000000000..afb0ef3fd --- /dev/null +++ b/common/repositories/completed_shared_task_activity_state_repository.h @@ -0,0 +1,70 @@ +/** + * 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_COMPLETED_SHARED_TASK_ACTIVITY_STATE_REPOSITORY_H +#define EQEMU_COMPLETED_SHARED_TASK_ACTIVITY_STATE_REPOSITORY_H + +#include "../database.h" +#include "../string_util.h" +#include "base/base_completed_shared_task_activity_state_repository.h" + +class CompletedSharedTaskActivityStateRepository: public BaseCompletedSharedTaskActivityStateRepository { +public: + + /** + * This file was auto generated and can be modified and extended upon + * + * Base repository methods are automatically + * generated in the "base" version of this repository. The base repository + * is immutable and to be left untouched, while methods in this class + * are used as extension methods for more specific persistence-layer + * accessors or mutators. + * + * Base Methods (Subject to be expanded upon in time) + * + * Note: Not all tables are designed appropriately to fit functionality with all base methods + * + * InsertOne + * UpdateOne + * DeleteOne + * FindOne + * GetWhere(std::string where_filter) + * DeleteWhere(std::string where_filter) + * InsertMany + * All + * + * Example custom methods in a repository + * + * CompletedSharedTaskActivityStateRepository::GetByZoneAndVersion(int zone_id, int zone_version) + * CompletedSharedTaskActivityStateRepository::GetWhereNeverExpires() + * CompletedSharedTaskActivityStateRepository::GetWhereXAndY() + * CompletedSharedTaskActivityStateRepository::DeleteWhereXAndY() + * + * Most of the above could be covered by base methods, but if you as a developer + * find yourself re-using logic for other parts of the code, its best to just make a + * method that can be re-used easily elsewhere especially if it can use a base repository + * method and encapsulate filters there + */ + + // Custom extended repository methods here + +}; + +#endif //EQEMU_COMPLETED_SHARED_TASK_ACTIVITY_STATE_REPOSITORY_H diff --git a/common/repositories/completed_shared_task_members_repository.h b/common/repositories/completed_shared_task_members_repository.h new file mode 100644 index 000000000..6c0cf609a --- /dev/null +++ b/common/repositories/completed_shared_task_members_repository.h @@ -0,0 +1,70 @@ +/** + * 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_COMPLETED_SHARED_TASK_MEMBERS_REPOSITORY_H +#define EQEMU_COMPLETED_SHARED_TASK_MEMBERS_REPOSITORY_H + +#include "../database.h" +#include "../string_util.h" +#include "base/base_completed_shared_task_members_repository.h" + +class CompletedSharedTaskMembersRepository: public BaseCompletedSharedTaskMembersRepository { +public: + + /** + * This file was auto generated and can be modified and extended upon + * + * Base repository methods are automatically + * generated in the "base" version of this repository. The base repository + * is immutable and to be left untouched, while methods in this class + * are used as extension methods for more specific persistence-layer + * accessors or mutators. + * + * Base Methods (Subject to be expanded upon in time) + * + * Note: Not all tables are designed appropriately to fit functionality with all base methods + * + * InsertOne + * UpdateOne + * DeleteOne + * FindOne + * GetWhere(std::string where_filter) + * DeleteWhere(std::string where_filter) + * InsertMany + * All + * + * Example custom methods in a repository + * + * CompletedSharedTaskMembersRepository::GetByZoneAndVersion(int zone_id, int zone_version) + * CompletedSharedTaskMembersRepository::GetWhereNeverExpires() + * CompletedSharedTaskMembersRepository::GetWhereXAndY() + * CompletedSharedTaskMembersRepository::DeleteWhereXAndY() + * + * Most of the above could be covered by base methods, but if you as a developer + * find yourself re-using logic for other parts of the code, its best to just make a + * method that can be re-used easily elsewhere especially if it can use a base repository + * method and encapsulate filters there + */ + + // Custom extended repository methods here + +}; + +#endif //EQEMU_COMPLETED_SHARED_TASK_MEMBERS_REPOSITORY_H diff --git a/common/repositories/completed_shared_tasks_repository.h b/common/repositories/completed_shared_tasks_repository.h new file mode 100644 index 000000000..bbf46f16c --- /dev/null +++ b/common/repositories/completed_shared_tasks_repository.h @@ -0,0 +1,70 @@ +/** + * 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_COMPLETED_SHARED_TASKS_REPOSITORY_H +#define EQEMU_COMPLETED_SHARED_TASKS_REPOSITORY_H + +#include "../database.h" +#include "../string_util.h" +#include "base/base_completed_shared_tasks_repository.h" + +class CompletedSharedTasksRepository: public BaseCompletedSharedTasksRepository { +public: + + /** + * This file was auto generated and can be modified and extended upon + * + * Base repository methods are automatically + * generated in the "base" version of this repository. The base repository + * is immutable and to be left untouched, while methods in this class + * are used as extension methods for more specific persistence-layer + * accessors or mutators. + * + * Base Methods (Subject to be expanded upon in time) + * + * Note: Not all tables are designed appropriately to fit functionality with all base methods + * + * InsertOne + * UpdateOne + * DeleteOne + * FindOne + * GetWhere(std::string where_filter) + * DeleteWhere(std::string where_filter) + * InsertMany + * All + * + * Example custom methods in a repository + * + * CompletedSharedTasksRepository::GetByZoneAndVersion(int zone_id, int zone_version) + * CompletedSharedTasksRepository::GetWhereNeverExpires() + * CompletedSharedTasksRepository::GetWhereXAndY() + * CompletedSharedTasksRepository::DeleteWhereXAndY() + * + * Most of the above could be covered by base methods, but if you as a developer + * find yourself re-using logic for other parts of the code, its best to just make a + * method that can be re-used easily elsewhere especially if it can use a base repository + * method and encapsulate filters there + */ + + // Custom extended repository methods here + +}; + +#endif //EQEMU_COMPLETED_SHARED_TASKS_REPOSITORY_H diff --git a/common/repositories/criteria/content_filter_criteria.h b/common/repositories/criteria/content_filter_criteria.h index 6fa1adc71..8d73eefe4 100644 --- a/common/repositories/criteria/content_filter_criteria.h +++ b/common/repositories/criteria/content_filter_criteria.h @@ -40,43 +40,48 @@ namespace ContentFilterCriteria { } criteria += fmt::format( - " AND ({}min_expansion <= {} OR {}min_expansion = 0)", + " AND ({}min_expansion <= {} OR {}min_expansion = -1)", table_prefix, current_expansion_filter_criteria, table_prefix ); criteria += fmt::format( - " AND ({}max_expansion >= {} OR {}max_expansion = 0)", + " AND ({}max_expansion >= {} OR {}max_expansion = -1)", table_prefix, current_expansion_filter_criteria, table_prefix ); - std::vector flags = content_service.GetContentFlags(); + std::vector flags_disabled = content_service.GetContentFlagsDisabled(); + std::vector flags_enabled = content_service.GetContentFlagsEnabled(); std::string flags_in_filter_enabled; std::string flags_in_filter_disabled; - if (!flags.empty()) { + if (!flags_enabled.empty()) { flags_in_filter_enabled = fmt::format( " OR CONCAT(',', {}content_flags, ',') REGEXP ',({}),' ", table_prefix, - implode("|", flags) + implode("|", flags_enabled) ); + } + if (!flags_disabled.empty()) { flags_in_filter_disabled = fmt::format( - " OR CONCAT(',', {}content_flags_disabled, ',') NOT REGEXP ',({}),' ", + " OR CONCAT(',', {}content_flags_disabled, ',') REGEXP ',({}),' ", table_prefix, - implode("|", flags) + implode("|", flags_disabled) ); } criteria += fmt::format( - " AND ({}content_flags IS NULL{}) ", + " AND (({}content_flags IS NULL OR {}content_flags = ''){}) ", + table_prefix, table_prefix, flags_in_filter_enabled ); criteria += fmt::format( - " AND ({}content_flags_disabled IS NULL{}) ", + " AND (({}content_flags_disabled IS NULL OR {}content_flags_disabled = ''){}) ", + table_prefix, table_prefix, flags_in_filter_disabled ); diff --git a/common/repositories/dynamic_zone_members_repository.h b/common/repositories/dynamic_zone_members_repository.h new file mode 100644 index 000000000..3ccdd17b8 --- /dev/null +++ b/common/repositories/dynamic_zone_members_repository.h @@ -0,0 +1,231 @@ +/** + * 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_DYNAMIC_ZONE_MEMBERS_REPOSITORY_H +#define EQEMU_DYNAMIC_ZONE_MEMBERS_REPOSITORY_H + +#include "../database.h" +#include "../string_util.h" +#include "base/base_dynamic_zone_members_repository.h" + +class DynamicZoneMembersRepository: public BaseDynamicZoneMembersRepository { +public: + + /** + * This file was auto generated and can be modified and extended upon + * + * Base repository methods are automatically + * generated in the "base" version of this repository. The base repository + * is immutable and to be left untouched, while methods in this class + * are used as extension methods for more specific persistence-layer + * accessors or mutators. + * + * Base Methods (Subject to be expanded upon in time) + * + * Note: Not all tables are designed appropriately to fit functionality with all base methods + * + * InsertOne + * UpdateOne + * DeleteOne + * FindOne + * GetWhere(std::string where_filter) + * DeleteWhere(std::string where_filter) + * InsertMany + * All + * + * Example custom methods in a repository + * + * DynamicZoneMembersRepository::GetByZoneAndVersion(int zone_id, int zone_version) + * DynamicZoneMembersRepository::GetWhereNeverExpires() + * DynamicZoneMembersRepository::GetWhereXAndY() + * DynamicZoneMembersRepository::DeleteWhereXAndY() + * + * Most of the above could be covered by base methods, but if you as a developer + * find yourself re-using logic for other parts of the code, its best to just make a + * method that can be re-used easily elsewhere especially if it can use a base repository + * method and encapsulate filters there + */ + + // Custom extended repository methods here + + struct MemberWithName { + uint32_t id; + uint32_t dynamic_zone_id; + uint32_t character_id; + std::string character_name; + }; + + static std::string SelectMembersWithNames() + { + return std::string(SQL( + SELECT + dynamic_zone_members.id, + dynamic_zone_members.dynamic_zone_id, + dynamic_zone_members.character_id, + character_data.name + FROM dynamic_zone_members + INNER JOIN character_data ON dynamic_zone_members.character_id = character_data.id + )); + } + + static std::vector GetAllWithNames(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase(SelectMembersWithNames()); + if (results.Success()) + { + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) + { + MemberWithName entry{}; + + int col = 0; + entry.id = strtoul(row[col++], nullptr, 10); + entry.dynamic_zone_id = strtoul(row[col++], nullptr, 10); + entry.character_id = strtoul(row[col++], nullptr, 10); + entry.character_name = row[col++]; + + all_entries.emplace_back(std::move(entry)); + } + } + + return all_entries; + } + + static int DeleteByInstance(Database& db, int instance_id) + { + auto results = db.QueryDatabase(fmt::format(SQL( + DELETE dynamic_zone_members + FROM dynamic_zone_members + INNER JOIN dynamic_zones ON dynamic_zone_members.dynamic_zone_id = dynamic_zones.id + WHERE dynamic_zones.instance_id = {} + ), instance_id)); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int DeleteByManyInstances(Database& db, const std::string& joined_instance_ids) + { + auto results = db.QueryDatabase(fmt::format(SQL( + DELETE dynamic_zone_members + FROM dynamic_zone_members + INNER JOIN dynamic_zones ON dynamic_zone_members.dynamic_zone_id = dynamic_zones.id + WHERE dynamic_zones.instance_id IN ({}) + ), joined_instance_ids)); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int GetCountWhere(Database& db, const std::string& where_filter) + { + auto results = db.QueryDatabase(fmt::format( + "SELECT COUNT(*) FROM {} WHERE {};", TableName(), where_filter)); + + uint32_t count = 0; + if (results.Success() && results.RowCount() > 0) + { + auto row = results.begin(); + count = strtoul(row[0], nullptr, 10); + } + return count; + } + + static void AddMember(Database& db, uint32_t dynamic_zone_id, uint32_t character_id) + { + db.QueryDatabase(fmt::format(SQL( + INSERT INTO {} + (dynamic_zone_id, character_id) + VALUES + ({}, {}) + ON DUPLICATE KEY UPDATE id = id; + ), + TableName(), + dynamic_zone_id, + character_id + )); + } + + static void RemoveMember(Database& db, uint32_t dynamic_zone_id, uint32_t character_id) + { + db.QueryDatabase(fmt::format(SQL( + DELETE FROM {} + WHERE dynamic_zone_id = {} AND character_id = {}; + ), + TableName(), dynamic_zone_id, character_id + )); + } + + static void RemoveAllMembers(Database& db, uint32_t dynamic_zone_id) + { + db.QueryDatabase(fmt::format(SQL( + DELETE FROM {} + WHERE dynamic_zone_id = {}; + ), + TableName(), dynamic_zone_id + )); + } + + static void RemoveAllMembers(Database& db, std::vector dynamic_zone_ids) + { + if (!dynamic_zone_ids.empty()) + { + db.QueryDatabase(fmt::format(SQL( + DELETE FROM {} + WHERE dynamic_zone_id IN ({}); + ), + TableName(), fmt::join(dynamic_zone_ids, ",") + )); + } + } + + static int InsertOrUpdateMany(Database& db, + const std::vector& dynamic_zone_members_entries) + { + std::vector insert_chunks; + + for (auto &dynamic_zone_members_entry: dynamic_zone_members_entries) + { + std::vector insert_values; + + insert_values.push_back(std::to_string(dynamic_zone_members_entry.id)); + insert_values.push_back(std::to_string(dynamic_zone_members_entry.dynamic_zone_id)); + insert_values.push_back(std::to_string(dynamic_zone_members_entry.character_id)); + + insert_chunks.push_back("(" + implode(",", insert_values) + ")"); + } + + std::vector insert_values; + + auto results = db.QueryDatabase( + fmt::format( + "INSERT INTO {} ({}) VALUES {} ON DUPLICATE KEY UPDATE id = id;", + TableName(), + ColumnsRaw(), + implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } +}; + +#endif //EQEMU_DYNAMIC_ZONE_MEMBERS_REPOSITORY_H diff --git a/common/repositories/dynamic_zones_repository.h b/common/repositories/dynamic_zones_repository.h index 03435457c..677fcc2f8 100644 --- a/common/repositories/dynamic_zones_repository.h +++ b/common/repositories/dynamic_zones_repository.h @@ -65,6 +65,297 @@ public: // Custom extended repository methods here + struct DynamicZoneInstance + { + uint32_t id; + std::string uuid; + std::string name; + int leader_id; + int min_players; + int max_players; + int instance_id; + int type; + int compass_zone_id; + float compass_x; + float compass_y; + float compass_z; + int safe_return_zone_id; + float safe_return_x; + float safe_return_y; + float safe_return_z; + float safe_return_heading; + float zone_in_x; + float zone_in_y; + float zone_in_z; + float zone_in_heading; + int has_zone_in; + int zone; + int version; + int is_global; + uint32_t start_time; + int duration; + int never_expires; + }; + + static std::string SelectDynamicZoneJoinInstance() + { + return std::string(SQL( + SELECT + dynamic_zones.id, + dynamic_zones.uuid, + dynamic_zones.name, + dynamic_zones.leader_id, + dynamic_zones.min_players, + dynamic_zones.max_players, + dynamic_zones.instance_id, + dynamic_zones.type, + dynamic_zones.compass_zone_id, + dynamic_zones.compass_x, + dynamic_zones.compass_y, + dynamic_zones.compass_z, + dynamic_zones.safe_return_zone_id, + dynamic_zones.safe_return_x, + dynamic_zones.safe_return_y, + dynamic_zones.safe_return_z, + dynamic_zones.safe_return_heading, + dynamic_zones.zone_in_x, + dynamic_zones.zone_in_y, + dynamic_zones.zone_in_z, + dynamic_zones.zone_in_heading, + dynamic_zones.has_zone_in, + instance_list.zone, + instance_list.version, + instance_list.is_global, + instance_list.start_time, + instance_list.duration, + instance_list.never_expires + FROM dynamic_zones + INNER JOIN instance_list ON dynamic_zones.instance_id = instance_list.id + )); + } + + static DynamicZoneInstance FillWithInstanceFromRow(MySQLRequestRow& row) + { + DynamicZoneInstance entry{}; + + int col = 0; + entry.id = strtoul(row[col++], nullptr, 10); + entry.uuid = row[col++]; + entry.name = row[col++]; + entry.leader_id = strtol(row[col++], nullptr, 10); + entry.min_players = strtol(row[col++], nullptr, 10); + entry.max_players = strtol(row[col++], nullptr, 10); + entry.instance_id = strtol(row[col++], nullptr, 10); + entry.type = strtol(row[col++], nullptr, 10); + entry.compass_zone_id = strtol(row[col++], nullptr, 10); + entry.compass_x = strtof(row[col++], nullptr); + entry.compass_y = strtof(row[col++], nullptr); + entry.compass_z = strtof(row[col++], nullptr); + entry.safe_return_zone_id = strtol(row[col++], nullptr, 10); + entry.safe_return_x = strtof(row[col++], nullptr); + entry.safe_return_y = strtof(row[col++], nullptr); + entry.safe_return_z = strtof(row[col++], nullptr); + entry.safe_return_heading = strtof(row[col++], nullptr); + entry.zone_in_x = strtof(row[col++], nullptr); + entry.zone_in_y = strtof(row[col++], nullptr); + entry.zone_in_z = strtof(row[col++], nullptr); + entry.zone_in_heading = strtof(row[col++], nullptr); + entry.has_zone_in = strtol(row[col++], nullptr, 10) != 0; + // from instance_list + entry.zone = strtol(row[col++], nullptr, 10); + entry.version = strtol(row[col++], nullptr, 10); + entry.is_global = strtol(row[col++], nullptr, 10); + entry.start_time = strtoul(row[col++], nullptr, 10); + entry.duration = strtol(row[col++], nullptr, 10); + entry.never_expires = strtol(row[col++], nullptr, 10); + + return entry; + } + + static std::vector AllWithInstanceNotExpired(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase(fmt::format(SQL( + {} WHERE + (instance_list.start_time + instance_list.duration) > UNIX_TIMESTAMP() + AND instance_list.never_expires = 0 + ), SelectDynamicZoneJoinInstance())); + + if (results.Success()) + { + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) + { + DynamicZoneInstance entry = FillWithInstanceFromRow(row); + all_entries.emplace_back(std::move(entry)); + } + } + + return all_entries; + } + + static void UpdateCompass(Database& db, uint32_t dz_id, int zone_id, float x, float y, float z) + { + if (dz_id != 0) + { + std::string query = fmt::format(SQL( + UPDATE {} SET + compass_zone_id = {}, + compass_x = {}, + compass_y = {}, + compass_z = {} + WHERE {} = {}; + ), TableName(), zone_id, x, y, z, PrimaryKey(), dz_id); + + db.QueryDatabase(query); + } + } + + static void UpdateSafeReturn(Database& db, uint32_t dz_id, int zone_id, float x, float y, float z, float heading) + { + if (dz_id != 0) + { + std::string query = fmt::format(SQL( + UPDATE {} SET + safe_return_zone_id = {}, + safe_return_x = {}, + safe_return_y = {}, + safe_return_z = {}, + safe_return_heading = {} + WHERE {} = {}; + ), TableName(), zone_id, x, y, z, heading, PrimaryKey(), dz_id); + + db.QueryDatabase(query); + } + } + + static void UpdateZoneIn(Database& db, uint32_t dz_id, uint32_t zone_id, float x, float y, float z, float heading, bool has_zone_in) + { + if (dz_id != 0) + { + std::string query = fmt::format(SQL( + UPDATE {} SET + zone_in_x = {}, + zone_in_y = {}, + zone_in_z = {}, + zone_in_heading = {}, + has_zone_in = {} + WHERE {} = {}; + ), TableName(), x, y, z, heading, has_zone_in, PrimaryKey(), dz_id); + + db.QueryDatabase(query); + } + } + + static void UpdateLeaderID(Database& db, uint32_t dz_id, uint32_t leader_id) + { + if (dz_id != 0) + { + std::string query = fmt::format(SQL( + UPDATE {} SET leader_id = {} WHERE {} = {}; + ), TableName(), leader_id, PrimaryKey(), dz_id); + + db.QueryDatabase(query); + } + } + + struct DynamicZoneInstancePlayerCount + { + uint32_t id; + int type; + int instance; + int zone; + int version; + uint32_t start_time; + int duration; + int member_count; + }; + + static std::string SelectDynamicZoneInstancePlayerCount() + { + return std::string(SQL( + SELECT + dynamic_zones.id, + dynamic_zones.type, + instance_list.id, + instance_list.zone, + instance_list.version, + instance_list.start_time, + instance_list.duration, + COUNT(dynamic_zone_members.character_id) member_count + FROM dynamic_zones + INNER JOIN instance_list ON dynamic_zones.instance_id = instance_list.id + LEFT JOIN dynamic_zone_members ON dynamic_zones.id = dynamic_zone_members.dynamic_zone_id + GROUP BY instance_list.id + ORDER BY dynamic_zones.id; + )); + }; + + static std::vector AllDzInstancePlayerCounts(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase(SelectDynamicZoneInstancePlayerCount()); + if (results.Success()) + { + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) + { + DynamicZoneInstancePlayerCount entry{}; + + int col = 0; + entry.id = strtoul(row[col++], nullptr, 10); + entry.type = strtol(row[col++], nullptr, 10); + entry.instance = strtol(row[col++], nullptr, 10); + entry.zone = strtol(row[col++], nullptr, 10); + entry.version = strtol(row[col++], nullptr, 10); + entry.start_time = strtoul(row[col++], nullptr, 10); + entry.duration = strtol(row[col++], nullptr, 10); + entry.member_count = strtol(row[col++], nullptr, 10); + + all_entries.emplace_back(std::move(entry)); + } + } + return all_entries; + } + + static std::vector GetStaleIDs(Database& db) + { + std::vector all_entries; + + // dzs with no members, missing instance, or expired instance + auto results = db.QueryDatabase(SQL( + SELECT + dynamic_zones.id + FROM dynamic_zones + LEFT JOIN instance_list ON dynamic_zones.instance_id = instance_list.id + LEFT JOIN + ( + SELECT dynamic_zone_id, COUNT(*) member_count + FROM dynamic_zone_members + GROUP BY dynamic_zone_id + ) dynamic_zone_members + ON dynamic_zone_members.dynamic_zone_id = dynamic_zones.id + WHERE + instance_list.id IS NULL + OR dynamic_zone_members.member_count IS NULL + OR dynamic_zone_members.member_count = 0 + OR ((instance_list.start_time + instance_list.duration) <= UNIX_TIMESTAMP() + AND instance_list.never_expires = 0); + )); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) + { + all_entries.push_back(atoi(row[0])); + } + + return all_entries; + } }; #endif //EQEMU_DYNAMIC_ZONES_REPOSITORY_H diff --git a/common/repositories/expedition_lockouts_repository.h b/common/repositories/expedition_lockouts_repository.h index 0f33c4209..5eab18915 100644 --- a/common/repositories/expedition_lockouts_repository.h +++ b/common/repositories/expedition_lockouts_repository.h @@ -65,6 +65,58 @@ public: // Custom extended repository methods here + struct ExpeditionLockoutsWithTimestamp { + uint32_t id; + uint32_t expedition_id; + std::string event_name; + time_t expire_time; + int duration; + std::string from_expedition_uuid; + }; + + static std::vector GetWithTimestamp( + Database& db, const std::vector& expedition_ids) + { + if (expedition_ids.empty()) + { + return {}; + } + + std::vector all_entries; + + auto results = db.QueryDatabase(fmt::format(SQL( + SELECT + id, + expedition_id, + event_name, + UNIX_TIMESTAMP(expire_time), + duration, + from_expedition_uuid + FROM expedition_lockouts + WHERE expedition_id IN ({}) + ), + fmt::join(expedition_ids, ",") + )); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) + { + ExpeditionLockoutsWithTimestamp entry{}; + + int col = 0; + entry.id = strtoul(row[col++], nullptr, 10); + entry.expedition_id = strtoul(row[col++], nullptr, 10); + entry.event_name = row[col++]; + entry.expire_time = strtoull(row[col++], nullptr, 10); + entry.duration = strtol(row[col++], nullptr, 10); + entry.from_expedition_uuid = row[col++]; + + all_entries.emplace_back(std::move(entry)); + } + + return all_entries; + } }; #endif //EQEMU_EXPEDITION_LOCKOUTS_REPOSITORY_H diff --git a/common/repositories/expeditions_repository.h b/common/repositories/expeditions_repository.h index 3cd2f2352..1470cbad0 100644 --- a/common/repositories/expeditions_repository.h +++ b/common/repositories/expeditions_repository.h @@ -90,11 +90,10 @@ public: character_data.name, MAX(expeditions.id) FROM character_data - LEFT JOIN expedition_members - ON character_data.id = expedition_members.character_id - AND expedition_members.is_current_member = TRUE + LEFT JOIN dynamic_zone_members + ON character_data.id = dynamic_zone_members.character_id LEFT JOIN expeditions - ON expedition_members.expedition_id = expeditions.id + ON dynamic_zone_members.dynamic_zone_id = expeditions.dynamic_zone_id WHERE character_data.name IN ({}) GROUP BY character_data.id ORDER BY FIELD(character_data.name, {}) @@ -120,6 +119,36 @@ public: return entries; } + + static uint32_t GetIDByMemberID(Database& db, uint32_t character_id) + { + if (character_id == 0) + { + return 0; + } + + uint32_t expedition_id = 0; + + auto results = db.QueryDatabase(fmt::format(SQL( + SELECT + expeditions.id + FROM expeditions + INNER JOIN dynamic_zone_members + ON expeditions.dynamic_zone_id = dynamic_zone_members.dynamic_zone_id + WHERE + dynamic_zone_members.character_id = {} + ), + character_id + )); + + if (results.Success() && results.RowCount() > 0) + { + auto row = results.begin(); + expedition_id = std::strtoul(row[0], nullptr, 10); + } + + return expedition_id; + } }; #endif //EQEMU_EXPEDITIONS_REPOSITORY_H diff --git a/common/repositories/instance_list_player_repository.h b/common/repositories/instance_list_player_repository.h index f865e5fd8..3a526a73e 100644 --- a/common/repositories/instance_list_player_repository.h +++ b/common/repositories/instance_list_player_repository.h @@ -65,6 +65,34 @@ public: // Custom extended repository methods here + static int InsertOrUpdateMany(Database& db, + const std::vector& instance_list_player_entries) + { + std::vector insert_chunks; + + for (auto &instance_list_player_entry: instance_list_player_entries) + { + std::vector insert_values; + + insert_values.push_back(std::to_string(instance_list_player_entry.id)); + insert_values.push_back(std::to_string(instance_list_player_entry.charid)); + + insert_chunks.push_back("(" + implode(",", insert_values) + ")"); + } + + std::vector insert_values; + + auto results = db.QueryDatabase( + fmt::format( + "INSERT INTO {} ({}) VALUES {} ON DUPLICATE KEY UPDATE id = VALUES(id)", + TableName(), + ColumnsRaw(), + implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } }; #endif //EQEMU_INSTANCE_LIST_PLAYER_REPOSITORY_H diff --git a/common/repositories/instance_list_repository.h b/common/repositories/instance_list_repository.h index 312211295..6af82e302 100644 --- a/common/repositories/instance_list_repository.h +++ b/common/repositories/instance_list_repository.h @@ -65,6 +65,15 @@ public: // Custom extended repository methods here + static int UpdateDuration(Database& db, int instance_id, uint32_t new_duration) + { + auto results = db.QueryDatabase(fmt::format( + "UPDATE {} SET duration = {} WHERE {} = {};", + TableName(), new_duration, PrimaryKey(), instance_id + )); + + return (results.Success() ? results.RowsAffected() : 0); + } }; #endif //EQEMU_INSTANCE_LIST_REPOSITORY_H diff --git a/common/repositories/server_scheduled_events_repository.h b/common/repositories/server_scheduled_events_repository.h new file mode 100644 index 000000000..edcc21dbc --- /dev/null +++ b/common/repositories/server_scheduled_events_repository.h @@ -0,0 +1,70 @@ +/** + * 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_SERVER_SCHEDULED_EVENTS_REPOSITORY_H +#define EQEMU_SERVER_SCHEDULED_EVENTS_REPOSITORY_H + +#include "../database.h" +#include "../string_util.h" +#include "base/base_server_scheduled_events_repository.h" + +class ServerScheduledEventsRepository: public BaseServerScheduledEventsRepository { +public: + + /** + * This file was auto generated and can be modified and extended upon + * + * Base repository methods are automatically + * generated in the "base" version of this repository. The base repository + * is immutable and to be left untouched, while methods in this class + * are used as extension methods for more specific persistence-layer + * accessors or mutators. + * + * Base Methods (Subject to be expanded upon in time) + * + * Note: Not all tables are designed appropriately to fit functionality with all base methods + * + * InsertOne + * UpdateOne + * DeleteOne + * FindOne + * GetWhere(std::string where_filter) + * DeleteWhere(std::string where_filter) + * InsertMany + * All + * + * Example custom methods in a repository + * + * ServerScheduledEventsRepository::GetByZoneAndVersion(int zone_id, int zone_version) + * ServerScheduledEventsRepository::GetWhereNeverExpires() + * ServerScheduledEventsRepository::GetWhereXAndY() + * ServerScheduledEventsRepository::DeleteWhereXAndY() + * + * Most of the above could be covered by base methods, but if you as a developer + * find yourself re-using logic for other parts of the code, its best to just make a + * method that can be re-used easily elsewhere especially if it can use a base repository + * method and encapsulate filters there + */ + + // Custom extended repository methods here + +}; + +#endif //EQEMU_SERVER_SCHEDULED_EVENTS_REPOSITORY_H diff --git a/common/repositories/shared_task_activity_state_repository.h b/common/repositories/shared_task_activity_state_repository.h new file mode 100644 index 000000000..1e48c7e76 --- /dev/null +++ b/common/repositories/shared_task_activity_state_repository.h @@ -0,0 +1,70 @@ +/** + * 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_SHARED_TASK_ACTIVITY_STATE_REPOSITORY_H +#define EQEMU_SHARED_TASK_ACTIVITY_STATE_REPOSITORY_H + +#include "../database.h" +#include "../string_util.h" +#include "base/base_shared_task_activity_state_repository.h" + +class SharedTaskActivityStateRepository: public BaseSharedTaskActivityStateRepository { +public: + + /** + * This file was auto generated and can be modified and extended upon + * + * Base repository methods are automatically + * generated in the "base" version of this repository. The base repository + * is immutable and to be left untouched, while methods in this class + * are used as extension methods for more specific persistence-layer + * accessors or mutators. + * + * Base Methods (Subject to be expanded upon in time) + * + * Note: Not all tables are designed appropriately to fit functionality with all base methods + * + * InsertOne + * UpdateOne + * DeleteOne + * FindOne + * GetWhere(std::string where_filter) + * DeleteWhere(std::string where_filter) + * InsertMany + * All + * + * Example custom methods in a repository + * + * SharedTaskActivityStateRepository::GetByZoneAndVersion(int zone_id, int zone_version) + * SharedTaskActivityStateRepository::GetWhereNeverExpires() + * SharedTaskActivityStateRepository::GetWhereXAndY() + * SharedTaskActivityStateRepository::DeleteWhereXAndY() + * + * Most of the above could be covered by base methods, but if you as a developer + * find yourself re-using logic for other parts of the code, its best to just make a + * method that can be re-used easily elsewhere especially if it can use a base repository + * method and encapsulate filters there + */ + + // Custom extended repository methods here + +}; + +#endif //EQEMU_SHARED_TASK_ACTIVITY_STATE_REPOSITORY_H diff --git a/common/repositories/shared_task_dynamic_zones_repository.h b/common/repositories/shared_task_dynamic_zones_repository.h new file mode 100644 index 000000000..61ee9c9f5 --- /dev/null +++ b/common/repositories/shared_task_dynamic_zones_repository.h @@ -0,0 +1,70 @@ +/** + * 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_SHARED_TASK_DYNAMIC_ZONES_REPOSITORY_H +#define EQEMU_SHARED_TASK_DYNAMIC_ZONES_REPOSITORY_H + +#include "../database.h" +#include "../string_util.h" +#include "base/base_shared_task_dynamic_zones_repository.h" + +class SharedTaskDynamicZonesRepository: public BaseSharedTaskDynamicZonesRepository { +public: + + /** + * This file was auto generated and can be modified and extended upon + * + * Base repository methods are automatically + * generated in the "base" version of this repository. The base repository + * is immutable and to be left untouched, while methods in this class + * are used as extension methods for more specific persistence-layer + * accessors or mutators. + * + * Base Methods (Subject to be expanded upon in time) + * + * Note: Not all tables are designed appropriately to fit functionality with all base methods + * + * InsertOne + * UpdateOne + * DeleteOne + * FindOne + * GetWhere(std::string where_filter) + * DeleteWhere(std::string where_filter) + * InsertMany + * All + * + * Example custom methods in a repository + * + * SharedTaskDynamicZonesRepository::GetByZoneAndVersion(int zone_id, int zone_version) + * SharedTaskDynamicZonesRepository::GetWhereNeverExpires() + * SharedTaskDynamicZonesRepository::GetWhereXAndY() + * SharedTaskDynamicZonesRepository::DeleteWhereXAndY() + * + * Most of the above could be covered by base methods, but if you as a developer + * find yourself re-using logic for other parts of the code, its best to just make a + * method that can be re-used easily elsewhere especially if it can use a base repository + * method and encapsulate filters there + */ + + // Custom extended repository methods here + +}; + +#endif //EQEMU_SHARED_TASK_DYNAMIC_ZONES_REPOSITORY_H diff --git a/common/repositories/expedition_members_repository.h b/common/repositories/shared_task_members_repository.h similarity index 81% rename from common/repositories/expedition_members_repository.h rename to common/repositories/shared_task_members_repository.h index 87304a99b..51b8606ad 100644 --- a/common/repositories/expedition_members_repository.h +++ b/common/repositories/shared_task_members_repository.h @@ -18,14 +18,14 @@ * */ -#ifndef EQEMU_EXPEDITION_MEMBERS_REPOSITORY_H -#define EQEMU_EXPEDITION_MEMBERS_REPOSITORY_H +#ifndef EQEMU_SHARED_TASK_MEMBERS_REPOSITORY_H +#define EQEMU_SHARED_TASK_MEMBERS_REPOSITORY_H #include "../database.h" #include "../string_util.h" -#include "base/base_expedition_members_repository.h" +#include "base/base_shared_task_members_repository.h" -class ExpeditionMembersRepository: public BaseExpeditionMembersRepository { +class SharedTaskMembersRepository: public BaseSharedTaskMembersRepository { public: /** @@ -52,10 +52,10 @@ public: * * Example custom methods in a repository * - * ExpeditionMembersRepository::GetByZoneAndVersion(int zone_id, int zone_version) - * ExpeditionMembersRepository::GetWhereNeverExpires() - * ExpeditionMembersRepository::GetWhereXAndY() - * ExpeditionMembersRepository::DeleteWhereXAndY() + * SharedTaskMembersRepository::GetByZoneAndVersion(int zone_id, int zone_version) + * SharedTaskMembersRepository::GetWhereNeverExpires() + * SharedTaskMembersRepository::GetWhereXAndY() + * SharedTaskMembersRepository::DeleteWhereXAndY() * * Most of the above could be covered by base methods, but if you as a developer * find yourself re-using logic for other parts of the code, its best to just make a @@ -67,4 +67,4 @@ public: }; -#endif //EQEMU_EXPEDITION_MEMBERS_REPOSITORY_H +#endif //EQEMU_SHARED_TASK_MEMBERS_REPOSITORY_H diff --git a/common/repositories/shared_tasks_repository.h b/common/repositories/shared_tasks_repository.h new file mode 100644 index 000000000..3ec764beb --- /dev/null +++ b/common/repositories/shared_tasks_repository.h @@ -0,0 +1,70 @@ +/** + * 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_SHARED_TASKS_REPOSITORY_H +#define EQEMU_SHARED_TASKS_REPOSITORY_H + +#include "../database.h" +#include "../string_util.h" +#include "base/base_shared_tasks_repository.h" + +class SharedTasksRepository: public BaseSharedTasksRepository { +public: + + /** + * This file was auto generated and can be modified and extended upon + * + * Base repository methods are automatically + * generated in the "base" version of this repository. The base repository + * is immutable and to be left untouched, while methods in this class + * are used as extension methods for more specific persistence-layer + * accessors or mutators. + * + * Base Methods (Subject to be expanded upon in time) + * + * Note: Not all tables are designed appropriately to fit functionality with all base methods + * + * InsertOne + * UpdateOne + * DeleteOne + * FindOne + * GetWhere(std::string where_filter) + * DeleteWhere(std::string where_filter) + * InsertMany + * All + * + * Example custom methods in a repository + * + * SharedTasksRepository::GetByZoneAndVersion(int zone_id, int zone_version) + * SharedTasksRepository::GetWhereNeverExpires() + * SharedTasksRepository::GetWhereXAndY() + * SharedTasksRepository::DeleteWhereXAndY() + * + * Most of the above could be covered by base methods, but if you as a developer + * find yourself re-using logic for other parts of the code, its best to just make a + * method that can be re-used easily elsewhere especially if it can use a base repository + * method and encapsulate filters there + */ + + // Custom extended repository methods here + +}; + +#endif //EQEMU_SHARED_TASKS_REPOSITORY_H diff --git a/common/repositories/template/base_repository.template b/common/repositories/template/base_repository.template index 8b1695049..d4ab7b6fc 100644 --- a/common/repositories/template/base_repository.template +++ b/common/repositories/template/base_repository.template @@ -14,6 +14,7 @@ #include "../../database.h" #include "../../string_util.h" +#include class Base{{TABLE_NAME_CLASS}}Repository { public: @@ -33,11 +34,23 @@ public: }; } + static std::vector SelectColumns() + { + return { +{{SELECT_COLUMNS_LIST_QUOTED}} + }; + } + static std::string ColumnsRaw() { return std::string(implode(", ", Columns())); } + static std::string SelectColumnsRaw() + { + return std::string(implode(", ", SelectColumns())); + } + static std::string TableName() { return std::string("{{TABLE_NAME_VAR}}"); @@ -47,7 +60,7 @@ public: { return fmt::format( "SELECT {} FROM {}", - ColumnsRaw(), + SelectColumnsRaw(), TableName() ); } diff --git a/common/repositories/tool_game_objects_repository.h b/common/repositories/tool_game_objects_repository.h new file mode 100644 index 000000000..d4d6009b5 --- /dev/null +++ b/common/repositories/tool_game_objects_repository.h @@ -0,0 +1,70 @@ +/** + * 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_TOOL_GAME_OBJECTS_REPOSITORY_H +#define EQEMU_TOOL_GAME_OBJECTS_REPOSITORY_H + +#include "../database.h" +#include "../string_util.h" +#include "base/base_tool_game_objects_repository.h" + +class ToolGameObjectsRepository: public BaseToolGameObjectsRepository { +public: + + /** + * This file was auto generated and can be modified and extended upon + * + * Base repository methods are automatically + * generated in the "base" version of this repository. The base repository + * is immutable and to be left untouched, while methods in this class + * are used as extension methods for more specific persistence-layer + * accessors or mutators. + * + * Base Methods (Subject to be expanded upon in time) + * + * Note: Not all tables are designed appropriately to fit functionality with all base methods + * + * InsertOne + * UpdateOne + * DeleteOne + * FindOne + * GetWhere(std::string where_filter) + * DeleteWhere(std::string where_filter) + * InsertMany + * All + * + * Example custom methods in a repository + * + * ToolGameObjectsRepository::GetByZoneAndVersion(int zone_id, int zone_version) + * ToolGameObjectsRepository::GetWhereNeverExpires() + * ToolGameObjectsRepository::GetWhereXAndY() + * ToolGameObjectsRepository::DeleteWhereXAndY() + * + * Most of the above could be covered by base methods, but if you as a developer + * find yourself re-using logic for other parts of the code, its best to just make a + * method that can be re-used easily elsewhere especially if it can use a base repository + * method and encapsulate filters there + */ + + // Custom extended repository methods here + +}; + +#endif //EQEMU_TOOL_GAME_OBJECTS_REPOSITORY_H diff --git a/common/ruletypes.h b/common/ruletypes.h index 5d311279f..33fa0d018 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -70,7 +70,6 @@ RULE_INT(Character, HPRegenMultiplier, 100, "The hitpoint regeneration is multip RULE_INT(Character, ManaRegenMultiplier, 100, "The mana regeneration is multiplied by value/100 (up to the caps)") RULE_INT(Character, EnduranceRegenMultiplier, 100, "The endurance regeneration is multiplied by value/100 (up to the caps)") RULE_BOOL(Character, OldMinMana, false, "This is used for servers that want to follow older skill cap formulas so they can still have some regen w/o mediate") -RULE_INT(Character, ConsumptionMultiplier, 100, "Item's hunger restored = value x item's food level. 100=normal, 50=player eat 2x as fast, 200=player eat 2x as slow") RULE_BOOL(Character, HealOnLevel, false, "Setting whether a player should heal completely when leveling") RULE_BOOL(Character, FeignKillsPet, false, "Setting whether Feign Death kills the player pet") RULE_INT(Character, ItemManaRegenCap, 15, "Limit on mana regeneration granted by items") @@ -92,6 +91,7 @@ RULE_INT(Character, ItemDSMitigationCap, 50, "Limit on damageshield mitigation g RULE_INT(Character, ItemEnduranceRegenCap, 15, "Limit on endurance regeneration granted by items") RULE_INT(Character, ItemExtraDmgCap, 150, "Cap for bonuses to melee skills like Bash, Frenzy, etc.") RULE_INT(Character, HasteCap, 100, "Haste cap for non-v3(over haste) haste") +RULE_INT(Character, Hastev3Cap, 25, "Haste cap for v3(over haste) haste") RULE_INT(Character, SkillUpModifier, 100, "The probability for a skill-up is multiplied by value/100") RULE_BOOL(Character, SharedBankPlat, false, "Shared bank platinum. Off by default to prevent duplication") RULE_BOOL(Character, BindAnywhere, false, "Allows players to bind their soul anywhere in the world") @@ -139,6 +139,7 @@ RULE_INT(Character, TradeskillUpMakePoison, 2, "Make Poison skillup rate adjustm RULE_INT(Character, TradeskillUpPottery, 4, "Pottery skillup rate adjustment. Lower is faster") RULE_INT(Character, TradeskillUpResearch, 1, "Research skillup rate adjustment. Lower is faster") RULE_INT(Character, TradeskillUpTinkering, 2, "Tinkering skillup rate adjustment. Lower is faster") +RULE_INT(Character, TradeskillUpTailoring, 2, "Tailoring skillup rate adjustment. Lower is faster") RULE_BOOL(Character, MarqueeHPUpdates, false, "Will show health percentage in center of screen if health lesser than 100%") RULE_INT(Character, IksarCommonTongue, 95, "Starting value for Common Tongue for Iksars") RULE_INT(Character, OgreCommonTongue, 95, "Starting value for Common Tongue for Ogres") @@ -163,6 +164,35 @@ RULE_BOOL(Character, UseNoJunkFishing, false, "Disregards junk items when fishin 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_BOOL(Character, EnableCharacterEXPMods, false, "Enables character zone-based experience modifiers.") +RULE_BOOL(Character, PVPEnableGuardFactionAssist, true, "Enables faction based assisting against the aggresor in pvp.") +RULE_BOOL(Character, SkillUpFromItems, true, "Allow Skill ups from clickable items") +RULE_BOOL(Character, EnableTestBuff, false, "Allow the use of /testbuff") +RULE_BOOL(Character, UseResurrectionSickness, true, "Use Resurrection Sickness based on Resurrection spell cast, set to false to disable Resurrection Sickness.") +RULE_INT(Character, OldResurrectionSicknessSpellID, 757, "757 is Default Old Resurrection Sickness Spell ID") +RULE_INT(Character, ResurrectionSicknessSpellID, 756, "756 is Default Resurrection Sickness Spell ID") +RULE_BOOL(Character, EnableBardMelody, true, "Enable Bard /melody by default, to disable change to false for a classic experience.") +RULE_BOOL(Character, EnableRangerAutoFire, true, "Enable Ranger /autofire by default, to disable change to false for a classic experience.") +RULE_BOOL(Character, EnableTGB, true, "Enable /tgb (Target Group Buff) by default, to disable change to false for a classic experience.") +RULE_INT(Character, SkillUpMaximumChancePercentage, 25, "Maximum chance to improve a combat skill, before skill-specific modifiers. This should be greater than SkillUpMinimumChancePercentage.") +RULE_INT(Character, SkillUpMinimumChancePercentage, 2, "Minimum chance to improve a combat skill, after skill-specific modifiers. This should be lesser than SkillUpMaximumChancePercentage.") +RULE_INT(Character, WarriorTrackingDistanceMultiplier, 0, "If you want warriors to be able to track, increase this above 0. 0 disables tracking packets.") +RULE_INT(Character, ClericTrackingDistanceMultiplier, 0, "If you want clerics to be able to track, increase this above 0. 0 disables tracking packets.") +RULE_INT(Character, PaladinTrackingDistanceMultiplier, 0, "If you want paladins to be able to track, increase this above 0. 0 disables tracking packets.") +RULE_INT(Character, RangerTrackingDistanceMultiplier, 12, "If you want rangers to be able to track, increase this above 0. 0 disables tracking packets.") +RULE_INT(Character, ShadowKnightTrackingDistanceMultiplier, 0, "If you want shadow knights to be able to track, increase this above 0. 0 disables tracking packets.") +RULE_INT(Character, DruidTrackingDistanceMultiplier, 10, "If you want druids to be able to track, increase this above 0. 0 disables tracking packets.") +RULE_INT(Character, MonkTrackingDistanceMultiplier, 0, "If you want monks to be able to track, increase this above 0. 0 disables tracking packets.") +RULE_INT(Character, BardTrackingDistanceMultiplier, 7, "If you want bards to be able to track, increase this above 0. 0 disables tracking packets.") +RULE_INT(Character, RogueTrackingDistanceMultiplier, 0, "If you want rogues to be able to track, increase this above 0. 0 disables tracking packets.") +RULE_INT(Character, ShamanTrackingDistanceMultiplier, 0, "If you want shaman to be able to track, increase this above 0. 0 disables tracking packets.") +RULE_INT(Character, NecromancerTrackingDistanceMultiplier, 0, "If you want necromancers to be able to track, increase this above 0. 0 disables tracking packets.") +RULE_INT(Character, WizardTrackingDistanceMultiplier, 0, "If you want wizards to be able to track, increase this above 0. 0 disables tracking packets.") +RULE_INT(Character, MagicianTrackingDistanceMultiplier, 0, "If you want magicians to be able to track, increase this above 0. 0 disables tracking packets.") +RULE_INT(Character, EnchanterTrackingDistanceMultiplier, 0, "If you want enchanters to be able to track, increase this above 0. 0 disables tracking packets.") +RULE_INT(Character, BeastlordTrackingDistanceMultiplier, 0, "If you want beastlords to be able to track, increase this above 0. 0 disables tracking packets.") +RULE_INT(Character, BerserkerTrackingDistanceMultiplier, 0, "If you want berserkers to be able to track, increase this above 0. 0 disables tracking packets.") +RULE_BOOL(Character, OnInviteReceiveAlreadyinGroupMessage, true, "If you want clients to receive a message when trying to invite a player into a group that is currently in another group.") RULE_CATEGORY_END() RULE_CATEGORY(Mercs) @@ -176,12 +206,10 @@ RULE_INT(Mercs, AggroRadius, 100, "Determines the distance from which a merc wil RULE_INT(Mercs, AggroRadiusPuller, 25, "Determines the distance from which a merc will aggro group member's target, if they have the group role of puller (also used to determine the distance at which a healer merc will begin healing a group member, if they have the group role of puller)") RULE_INT(Mercs, ResurrectRadius, 50, "Determines the distance from which a healer merc will attempt to resurrect a group member's corpse") RULE_INT(Mercs, ScaleRate, 100, "Merc scale factor") -RULE_BOOL(Mercs, MercsUsePathing, true, "Mercs will use node pathing when moving") RULE_BOOL(Mercs, AllowMercSuspendInCombat, true, "Allow merc suspend in combat") RULE_CATEGORY_END() RULE_CATEGORY(Guild) -RULE_INT(Guild, MaxMembers, 2048, "Maximum number of members a guild can have") RULE_BOOL(Guild, PlayerCreationAllowed, false, "Allow players to create a guild using the window in Underfoot+") RULE_INT(Guild, PlayerCreationLimit, 1, "Only allow use of the UF+ window if the account has < than this number of guild leaders on it") RULE_INT(Guild, PlayerCreationRequiredStatus, 0, "Required status to create a guild") @@ -206,12 +234,14 @@ RULE_REAL(Pets, AttackCommandRange, 150, "Range at which a pet will respond to a RULE_BOOL(Pets, UnTargetableSwarmPet, false, "Setting whether swarm pets should be targetable") RULE_REAL(Pets, PetPowerLevelCap, 10, "Maximum number of levels a player pet can go up with pet power") RULE_BOOL(Pets, CanTakeNoDrop, false, "Setting whether anyone can give no-drop items to pets") +RULE_BOOL(Pets, LivelikeBreakCharmOnInvis, true, "Default: true will break charm on any type of invis (hide/ivu/iva/etc) false will only break if the pet can not see you (ex. you have an undead pet and cast IVU") RULE_CATEGORY_END() RULE_CATEGORY(GM) RULE_INT(GM, MinStatusToSummonItem, 250, "Minimum required status to summon items") RULE_INT(GM, MinStatusToZoneAnywhere, 250, "Minimum required status to zone anywhere") RULE_INT(GM, MinStatusToLevelTarget, 100, "Minimum required status to set the level of a player") +RULE_INT(GM, MinStatusToBypassLockedServer, 100, "Players >= this status can log in to the server even when it is locked") RULE_CATEGORY_END() RULE_CATEGORY(World) @@ -230,7 +260,6 @@ RULE_INT(World, AddMaxClientsStatus, -1, "Accounts with status >= this rule will RULE_BOOL(World, MaxClientsSetByStatus, false, "If true, IP Limiting will be set to the status on the account as long as the status is > MaxClientsPerIP") RULE_BOOL(World, EnableIPExemptions, false, "If true, ip_exemptions table is used, if there is no entry for the IP it will default to RuleI(World, MaxClientsPerIP)") RULE_BOOL(World, ClearTempMerchantlist, true, "Clears temp merchant items when world boots") -RULE_BOOL(World, DeleteStaleCorpeBackups, true, "Deletes stale corpse backups older than 2 weeks") RULE_BOOL(World, GMAccountIPList, false, "Check IP list against GM accounts. This increases the security of GM accounts, e.g. if you only allow localhost '127.0.0.1' for GM accounts. Think carefully about what you enter!") RULE_INT(World, MinGMAntiHackStatus, 1, "Minimum status to check against AntiHack list") RULE_INT(World, SoFStartZoneID, -1, "Sets the Starting Zone for SoF Clients separate from Titanium Clients (-1 is disabled)") @@ -242,7 +271,6 @@ RULE_INT(World, PVPMinLevel, 0, "Minimum level to pvp") RULE_BOOL (World, IsGMPetitionWindowEnabled, false, "Setting whether the GM petition window is available") RULE_INT (World, FVNoDropFlag, 0, "Sets the Firiona Vie settings on the client, allowing trading of no-drop items. 1=for all players, 2=for GM only") RULE_BOOL (World, IPLimitDisconnectAll, false, "Disconnect all current clients by IP if they go over the IP limit. This should allow people to quickly reconnect in the case of dead sessions waiting to timeout") -RULE_BOOL(World, MaxClientsSimplifiedLogic, false, "New logic that only uses ExemptMaxClientsStatus and MaxClientsPerIP. Done on the loginserver. This mimics the P99-style special IP rules") RULE_INT (World, TellQueueSize, 20, "Maximum tell queue size") RULE_BOOL(World, StartZoneSameAsBindOnCreation, true, "Should the start zone always be the same location as your bind?") RULE_BOOL(World, EnforceCharacterLimitAtLogin, false, "Enforce the limit for characters that are online at login") @@ -253,21 +281,12 @@ RULE_CATEGORY(Zone) RULE_INT(Zone, ClientLinkdeadMS, 90000, "The time a client remains link dead on the server after a sudden disconnection (milliseconds)") RULE_INT(Zone, GraveyardTimeMS, 1200000, "Time until a player corpse is moved to a zone's graveyard, if one is specified for the zone (milliseconds)") RULE_BOOL(Zone, EnableShadowrest, 1, "Enables or disables the Shadowrest zone feature for player corpses. Default is turned on") -RULE_BOOL(Zone, UsePlayerCorpseBackups, true, "Keeps backups of player corpses") -RULE_INT(Zone, MQWarpExemptStatus, -1, "Required status level to exempt the MQWarpDetector. Set to -1 to disable this feature") -RULE_INT(Zone, MQZoneExemptStatus, -1, "Required status level to exempt the MQZoneDetector. Set to -1 to disable this feature") -RULE_INT(Zone, MQGateExemptStatus, -1, "Required status level to exempt the MQGateDetector. Set to -1 to disable this feature") -RULE_INT(Zone, MQGhostExemptStatus, -1, "Required status level to exempt the MGhostDetector. Set to -1 to disable this feature") -RULE_BOOL(Zone, EnableMQWarpDetector, true, "Enable the MQWarp Detector. Set to False to disable this feature") -RULE_BOOL(Zone, EnableMQZoneDetector, true, "Enable the MQZone Detector. Set to False to disable this feature") -RULE_BOOL(Zone, EnableMQGateDetector, true, "Enable the MQGate Detector. Set to False to disable this feature") -RULE_BOOL(Zone, EnableMQGhostDetector, true, "Enable the MQGhost Detector. Set to False to disable this feature") -RULE_REAL(Zone, MQWarpDetectionDistanceFactor, 9.0, "Distance factor for MQ-Warp detection. Clients move at 4.4 about if in a straight line but with movement and to acct for lag we raise it a bit") RULE_INT(Zone, AutoShutdownDelay, 5000, "How long a dynamic zone stays loaded while empty (milliseconds)") -RULE_INT(Zone, PEQZoneReuseTime, 900, "Time between two uses of the #peqzone command (seconds)") +RULE_INT(Zone, PEQZoneReuseTime, 900, "Seconds between two uses of the #peqzone command (Set to 0 to disable)") RULE_INT(Zone, PEQZoneDebuff1, 4454, "First debuff casted by #peqzone Default is Cursed Keeper's Blight") RULE_INT(Zone, PEQZoneDebuff2, 2209, "Second debuff casted by #peqzone Default is Tendrils of Apathy") RULE_BOOL(Zone, UsePEQZoneDebuffs, true, "Setting if the command #peqzone applies the defined debuffs") +RULE_INT(Zone, PEQZoneHPRatio, 75, "Required HP Ratio to use #peqzone") RULE_REAL(Zone, HotZoneBonus, 0.75, "Value which is added to the experience multiplier. This also applies to AA experience.") RULE_INT(Zone, EbonCrystalItemID, 40902, "Item ID for Ebon Crystal") RULE_INT(Zone, RadiantCrystalItemID, 40903, "Item ID for Radiant Crystal") @@ -280,6 +299,7 @@ RULE_BOOL(Zone, EnableZoneControllerGlobals, false, "Enables the ability to use RULE_INT(Zone, GlobalLootMultiplier, 1, "Sets Global Loot drop multiplier for database based drops, useful for double, triple loot etc") RULE_BOOL(Zone, KillProcessOnDynamicShutdown, true, "When process has booted a zone and has hit its zone shut down timer, it will hard kill the process to free memory back to the OS") RULE_INT(Zone, SecondsBeforeIdle, 60, "Seconds before IDLE_WHEN_EMPTY define kicks in") +RULE_INT(Zone, SpawnEventMin, 3, "When strict is set in spawn_events, specifies the max EQ minutes into the trigger hour a spawn_event will fire. Going below 3 may cause the spawn_event to not fire.") RULE_CATEGORY_END() RULE_CATEGORY(Map) @@ -292,7 +312,6 @@ RULE_INT(Map, FindBestZHeightAdjust, 1, "Adds this to the current Z before seeki RULE_CATEGORY_END() RULE_CATEGORY(Pathing) -RULE_BOOL(Pathing, Guard, true, "Enable pathing for mobs moving to their guard point") RULE_BOOL(Pathing, Find, true, "Enable pathing for FindPerson requests from the client") RULE_BOOL(Pathing, Fear, true, "Enable pathing for fear") RULE_REAL(Pathing, NavmeshStepSize, 100.0f, "Step size for the movement manager") @@ -302,9 +321,6 @@ RULE_CATEGORY_END() RULE_CATEGORY(Watermap) // enable these to use the water detection code. Requires Water Maps generated by awater utility -RULE_BOOL(Watermap, CheckWaypointsInWaterWhenLoading, false, "Does not apply BestZ as waypoints are loaded if they are in water") -RULE_BOOL(Watermap, CheckForWaterAtWaypoints, false, "Check if a mob has moved into/out of water when at waypoints and sets flymode") -RULE_BOOL(Watermap, CheckForWaterWhenMoving, false, "Checks if a mob has moved into/out of water each time it's loc is recalculated") RULE_BOOL(Watermap, CheckForWaterOnSendTo, false, "Checks if a mob has moved into/out of water on SendTo") RULE_BOOL(Watermap, CheckForWaterWhenFishing, false, "Only lets a player fish near water (if a water map exists for the zone)") RULE_REAL(Watermap, FishingRodLength, 30, "How far in front of player water must be for fishing to work") @@ -313,16 +329,11 @@ RULE_REAL(Watermap, FishingLineStepSize, 1, "Basic step size for fishing calc, t RULE_CATEGORY_END() RULE_CATEGORY(Spells) -RULE_REAL(Spells, ResistChance, 2.0, "chance to resist given no resists and same level") -RULE_REAL(Spells, ResistMod, 0.40, "Multiplier, chance to resist = this * ResistAmount") -RULE_REAL(Spells, PartialHitChance, 0.7, "The chance when a spell is resisted that it will partial hit") -RULE_REAL(Spells, PartialHitChanceFear, 0.25, "The chance when a fear spell is resisted that it will partial hit") RULE_INT(Spells, BaseCritChance, 0, "Base percentage chance that everyone has to crit a spell") RULE_INT(Spells, BaseCritRatio, 100, "Base percentage bonus to damage on a successful spell crit. 100=2xdamage") RULE_INT(Spells, WizCritLevel, 12, "Level wizards first get spell crits") RULE_INT(Spells, WizCritChance, 7, "Wizards crit chance, on top of BaseCritChance") RULE_INT(Spells, WizCritRatio, 0, "Wizards crit bonus, on top of BaseCritRatio (should be 0 for Live-like)") -RULE_INT(Spells, ResistPerLevelDiff, 85, "Resist per level difference. 85=8.5") RULE_INT(Spells, TranslocateTimeLimit, 0, "If not zero, time in seconds to accept a Translocate") RULE_INT(Spells, SacrificeMinLevel, 46, "First level the spell Sacrifice will work on") RULE_INT(Spells, SacrificeMaxLevel, 69, "Last level the spell Sacrifice will work on") @@ -335,7 +346,7 @@ RULE_INT(Spells, MaxDiscSlotsNPC, 0, "Maximum number of NPC disc slots. NPC don' RULE_INT(Spells, MaxTotalSlotsNPC, 60, "Maximum total of NPC slots. The default value is the limit of the Titanium client") RULE_INT(Spells, MaxTotalSlotsPET, 30, "Maximum total of pet slots. The default value is the limit of the Titanium client") RULE_BOOL (Spells, EnableBlockedBuffs, true, "Allow blocked spells") -RULE_INT(Spells, ReflectType, 3, "Reflect type. 0=disabled, 1=single target player spells only, 2=all player spells, 3=all single target spells, 4=all spells") +RULE_INT(Spells, ReflectType, 4, "Reflect type. 0=disabled, 1=single target player spells only, 2=all player spells, 3=all single target spells, 4=all spells") RULE_BOOL(Spells, ReflectMessagesClose, true, "True (Live functionality) is for Reflect messages to show to players within close proximity. False shows just player reflecting") RULE_INT(Spells, VirusSpreadDistance, 30, "The distance a viral spell will jump to its next victim") RULE_BOOL(Spells, LiveLikeFocusEffects, true, "Determines whether specific healing, dmg and mana reduction focuses are randomized") @@ -348,7 +359,6 @@ RULE_INT(Spells, CharismaEffectivenessCap, 255, "Determines how much resist modi RULE_BOOL(Spells, CharismaCharmDuration, false, "Allow CHA resist mod to extend charm duration") RULE_INT(Spells, CharmBreakCheckChance, 25, "Determines chance for a charm break check to occur each buff tick") RULE_BOOL(Spells, CharmDisablesSpecialAbilities, false, "When charm is cast on an NPC, strip their special abilities") -RULE_INT(Spells, MaxCastTimeReduction, 50, "Maximum percent your spell cast time can be reduced by spell haste") RULE_INT(Spells, RootBreakFromSpells, 55, "Chance for root to break when cast on") RULE_INT(Spells, DeathSaveCharismaMod, 3, "Determines how much charisma effects chance of death save firing") RULE_INT(Spells, DivineInterventionHeal, 8000, "Divine intervention heal amount") @@ -390,21 +400,32 @@ RULE_BOOL(Spells, AllowItemTGB, false, "Target group buff (/tgb) doesn't work wi RULE_BOOL(Spells, NPCInnateProcOverride, true, "NPC innate procs override the target type to single target") RULE_BOOL(Spells, OldRainTargets, false, "Use old incorrectly implemented maximum targets for rains") RULE_BOOL(Spells, NPCSpellPush, false, "Enable spell push on NPCs") +RULE_BOOL(Spells, July242002PetResists, true, "Enable Pets using PCs resist change from July 24 2002") +RULE_INT(Spells, AOEMaxTargets, 0, "Max number of targets a Targeted AOE spell can cast on. Set to 0 for no limit.") +RULE_BOOL(Spells, CazicTouchTargetsPetOwner, true, "If True, causes Cazic Touch to swap targets from pet to pet owner if a pet is tanking.") +RULE_BOOL(Spells, PreventFactionWarOnCharmBreak, false, "Enable spell interupts and dot removal on charm break to prevent faction wars.") +RULE_BOOL(Spells, AllowDoubleInvis, false, "Allows you to cast invisibility spells on a player that is already invisible") +RULE_BOOL(Spells, AllowSpellMemorizeFromItem, false, "Allows players to memorize spells by right-clicking spell scrolls") +RULE_BOOL(Spells, InvisRequiresGroup, false, "Invis requires the the target to be in group.") +RULE_INT(Spells, ClericInnateHealFocus, 5, "Clerics on live get a 5 pct innate heal focus") +RULE_BOOL(Spells, DOTsScaleWithSpellDmg, false, "Allow SpellDmg stat to affect DoT spells") +RULE_BOOL(Spells, HOTsScaleWithHealAmt, false, "Allow HealAmt stat to affect HoT spells") +RULE_BOOL(Spells, CompoundLifetapHeals, true, "True: Lifetap heals calculate damage bonuses and then heal bonuses. False: Lifetaps heal using the amount damaged to mob.") +RULE_BOOL(Spells, UseFadingMemoriesMaxLevel, false, "Enables to limit field in spell data to set the max level that over which an NPC will ignore fading memories effect and not lose aggro.") RULE_CATEGORY_END() RULE_CATEGORY(Combat) RULE_REAL(Combat, AERampageSafeZone, 0.018, "max hit ae ramp reduction range") RULE_INT(Combat, PetBaseCritChance, 0, "Pet base crit chance") RULE_INT(Combat, NPCBashKickLevel, 6, "The level that NPCcan KICK/BASH") -RULE_INT(Combat, NPCBashKickStunChance, 15, "Percent chance that a bash/kick will stun") RULE_INT(Combat, MeleeCritDifficulty, 8900, "Value against which is rolled to check if a melee crit is triggered. Lower is easier") RULE_INT(Combat, ArcheryCritDifficulty, 3400, "Value against which is rolled to check if an archery crit is triggered. Lower is easier") RULE_INT(Combat, ThrowingCritDifficulty, 1100, "Value against which is rolled to check if a throwing crit is triggered. Lower is easier") RULE_BOOL(Combat, NPCCanCrit, false, "Setting whether an NPC can land critical hits") RULE_BOOL(Combat, UseIntervalAC, true, "Switch whether bonuses, armour class, multipliers, classes and caps should be considered in the calculation of damage values") -RULE_INT(Combat, PetAttackMagicLevel, 30, "Level at which pets can cause magic damage") +RULE_INT(Combat, PetAttackMagicLevel, 10, "Level at which pets can cause magic damage, no longer used") +RULE_INT(Combat, NPCAttackMagicLevel, 10, "Level at which NPC and pets can cause magic damage") RULE_BOOL(Combat, EnableFearPathing, true, "Setting whether to use pathing during fear") -RULE_REAL(Combat, FleeMultiplier, 2.0, "Determines how quickly a NPC will slow down while fleeing. Decrease multiplier to slow NPC down quicker") RULE_BOOL(Combat, FleeGray, true, "If true FleeGrayHPRatio will be used") RULE_INT(Combat, FleeGrayHPRatio, 50, "HP percentage when a Gray NPC begins to flee") RULE_INT(Combat, FleeGrayMaxLevel, 18, "NPC above this level won't do gray/green con flee") @@ -415,8 +436,6 @@ RULE_REAL(Combat, AvgProcsPerMinute, 2.0, "Average proc rate per minute") RULE_REAL(Combat, ProcPerMinDexContrib, 0.075, "Increases the probability of a proc increased by DEX by the value indicated") RULE_REAL(Combat, BaseProcChance, 0.035, "Base chance for procs") RULE_REAL(Combat, ProcDexDivideBy, 11000, "Divisor for the probability of a proc increased by dexterity") -RULE_BOOL(Combat, AdjustSpecialProcPerMinute, true, "Set PPM for special abilities like HeadShot (Live does this as of 4-14)") -RULE_REAL(Combat, AvgSpecialProcsPerMinute, 2.0, "Unclear what best value is atm") RULE_REAL(Combat, BaseHitChance, 69.0, "Base chance to hit") RULE_REAL(Combat, NPCBonusHitChance, 26.0, "Bonus chance to hit for NPC") RULE_REAL(Combat, HitFalloffMinor, 5.0, "Hit will fall off up to value over the initial level range (percent)") @@ -430,22 +449,12 @@ RULE_REAL(Combat, MinChancetoHit, 5.0, "Minimum percentage chance to hit with re RULE_REAL(Combat, MaxChancetoHit, 95.0, "Maximum percentage chance to hit with regular melee/ranged") RULE_INT(Combat, MinRangedAttackDist, 25, "Minimum Distance to use Ranged Attacks") RULE_BOOL(Combat, ArcheryBonusRequiresStationary, true, "does the 2x archery bonus chance require a stationary npc") -RULE_REAL(Combat, ArcheryBaseDamageBonus, 1, "Percentage modifier to base archery Damage 0.5=50% base damage, 1=100%,2=200%") RULE_REAL(Combat, ArcheryNPCMultiplier, 1.0, "Value is multiplied by the regular dmg to get the archery dmg") RULE_BOOL(Combat, AssistNoTargetSelf, true, "When assisting a target that does not have a target: true = target self, false = leave target as was before assist (false = live like)") RULE_INT(Combat, MaxRampageTargets, 3, "Maximum number of people hit with rampage") RULE_INT(Combat, DefaultRampageTargets, 1, "Default number of people to hit with rampage") RULE_BOOL(Combat, RampageHitsTarget, false, "Rampage will hit the target if it still has targets left") RULE_INT(Combat, MaxFlurryHits, 2, "Maximum number of extra hits from flurry") -RULE_INT(Combat, MonkDamageTableBonus, 5, "Percentage bonus monks get to their damage table calcs") -RULE_INT(Combat, FlyingKickBonus, 25, "Percentage Modifier that this skill gets to str and skill bonuses") -RULE_INT(Combat, DragonPunchBonus, 20, "Percentage Modifier that this skill gets to str and skill bonuses") -RULE_INT(Combat, EagleStrikeBonus, 15, "Percentage Modifier that this skill gets to str and skill bonuses") -RULE_INT(Combat, TigerClawBonus, 10, "Percentage Modifier that this skill gets to str and skill bonuses") -RULE_INT(Combat, RoundKickBonus, 5, "Percentage Modifier that this skill gets to str and skill bonuses") -RULE_INT(Combat, FrenzyBonus, 0, "Percentage Modifier to damage") -RULE_INT(Combat, BackstabBonus, 0, "Percentage Modifier to damage") -RULE_BOOL(Combat, ProcTargetOnly, true, "true = procs will only affect our target, false = procs will affect all of our targets") RULE_REAL(Combat, NPCACFactor, 2.25, "If UseIntervalAC is enabled, the armor class for NPC is divided by this value") RULE_INT(Combat, ClothACSoftcap, 75, "If OldACSoftcapRules is true: armorclass softcap for cloth armor") RULE_INT(Combat, LeatherACSoftcap, 100, "If OldACSoftcapRules is true: armorclass softcap for leather armor") @@ -476,22 +485,19 @@ RULE_REAL(Combat, AvgDefProcsPerMinute, 2.0, "Average defense procs per minute") RULE_REAL(Combat, DefProcPerMinAgiContrib, 0.075, "How much agility contributes to defensive proc rate") RULE_INT(Combat, SpecialAttackACBonus, 15, "Percent amount of damage per AC gained for certain special attacks (damage = AC*SpecialAttackACBonus/100)") RULE_INT(Combat, NPCFlurryChance, 20, "Chance for NPC to flurry") -RULE_BOOL (Combat,TauntOverLevel, 1, "Allows you to taunt NPC's over warriors level") -RULE_REAL (Combat,TauntSkillFalloff, 0.33, "For every taunt skill point that's not maxed you lose this percentage chance to taunt") -RULE_BOOL (Combat,EXPFromDmgShield, false, "Determine if damage from a damage shield counts for experience gain") +RULE_BOOL(Combat, TauntOverLevel, 1, "Allows you to taunt NPC's over warriors level") +RULE_REAL(Combat, TauntSkillFalloff, 0.33, "For every taunt skill point that's not maxed you lose this percentage chance to taunt") +RULE_BOOL(Combat, EXPFromDmgShield, false, "Determine if damage from a damage shield counts for experience gain") RULE_INT(Combat, MonkACBonusWeight, 15, "Usually, a monk under this weight threshold gets an AC bonus") -RULE_INT(Combat, ClientStunLevel, 55, "This is the level where client kicks and bashes can stun the target") RULE_INT(Combat, QuiverHasteCap, 1000, "Quiver haste cap 1000 on live for a while, currently 700 on live") -RULE_BOOL(Combat, UseArcheryBonusRoll, false, "Make the 51+ archery bonus require an actual roll") -RULE_INT(Combat, ArcheryBonusChance, 50, "Chance for 50+ archery bonus damage if Combat:UseArcheryBonusRoll is true") RULE_INT(Combat, BerserkerFrenzyStart, 35, "Percentage Health Points below which Warrior and Berserker start frenzy") RULE_INT(Combat, BerserkerFrenzyEnd, 45, "Percentage Health Points above which Warrior and Berserker end frenzy") RULE_BOOL(Combat, OneProcPerWeapon, true, "If enabled, One proc per weapon per round") -RULE_BOOL(Combat, ProjectileDmgOnImpact, true, "If enabled, projectiles (ie arrows) will hit on impact, instead of instantly") -RULE_BOOL(Combat, MeleePush, true, "Eenable melee push") +RULE_BOOL(Combat, ProjectileDmgOnImpact, true, "If enabled, projectiles (i.e. arrows) will hit on impact, instead of instantly") +RULE_BOOL(Combat, MeleePush, true, "Enable melee push") RULE_INT(Combat, MeleePushChance, 50, "NPC chance the target will be pushed. Made up, 100 actually isn't that bad") RULE_BOOL(Combat, UseLiveCombatRounds, true, "Turn this false if you don't want to worry about fixing up combat rounds for NPCs") -RULE_INT(Combat, NPCAssistCap, 5, "Maxiumium number of NPC that will assist another NPC at once") +RULE_INT(Combat, NPCAssistCap, 5, "Maximum number of NPC that will assist another NPC at once") RULE_INT(Combat, NPCAssistCapTimer, 6000, "Time a NPC will take to clear assist aggro cap space (milliseconds)") RULE_BOOL(Combat, UseRevampHandToHand, false, "Use h2h revamped dmg/delays I believe this was implemented during SoF") RULE_BOOL(Combat, ClassicMasterWu, false, "Classic Master Wu uses a random special, modern doesn't") @@ -503,6 +509,10 @@ RULE_BOOL(Combat, UseNPCDamageClassLevelMods, true, "Uses GetClassLevelDamageMod RULE_BOOL(Combat, UseExtendedPoisonProcs, false, "Allow old school poisons to last until characrer zones, at a lower proc rate") RULE_BOOL(Combat, EnableSneakPull, false, "Enable implementation of Sneak Pull") RULE_INT(Combat, SneakPullAssistRange, 400, "Modified range of assist for sneak pull") +RULE_BOOL(Combat, Classic2HBAnimation, false, "2HB will use the 2 hand piercing animation instead of the overhead slashing animation") +RULE_BOOL(Combat, ArcheryConsumesAmmo, true, "Set to false to disable Archery Ammo Consumption") +RULE_BOOL(Combat, ThrowingConsumesAmmo, true, "Set to false to disable Throwing Ammo Consumption") +RULE_BOOL(Combat, UseLiveRiposteMechanics, false, "Set to true to disable SPA 173 SE_RiposteChance from making those with the effect on them immune to enrage, can longer riposte from a riposte.") RULE_CATEGORY_END() RULE_CATEGORY(NPC) @@ -522,7 +532,7 @@ RULE_BOOL(NPC, SmartLastFightingDelayMoving, true, "When true, mobs that started RULE_BOOL(NPC, ReturnNonQuestNoDropItems, false, "Returns NO DROP items on NPC that don't have an EVENT_TRADE sub in their script") RULE_INT(NPC, StartEnrageValue, 9, " Percentage HP that an NPC will begin to enrage") RULE_BOOL(NPC, LiveLikeEnrage, false, "If set to true then only player controlled pets will enrage") -RULE_BOOL(NPC, EnableMeritBasedFaction, false, "If set to true, faction will given in the same way as experience (solo/group/raid)") +RULE_BOOL(NPC, EnableMeritBasedFaction, false, "If set to true, faction will be given in the same way as experience (solo/group/raid)") RULE_INT(NPC, NPCToNPCAggroTimerMin, 500, "Minimum time span after which one NPC aggro another NPC (milliseconds)") RULE_INT(NPC, NPCToNPCAggroTimerMax, 6000, "Maximum time span after which one NPC aggro another NPC (milliseconds)") RULE_BOOL(NPC, UseClassAsLastName, true, "Uses class archetype as LastName for NPC with none") @@ -534,6 +544,7 @@ RULE_BOOL(NPC, NPCHealOnGate, true, "Will the NPC Heal on Gate") RULE_BOOL(NPC, UseMeditateBasedManaRegen, false, "Based NPC ooc regen on Meditate skill") RULE_REAL(NPC, NPCHealOnGateAmount, 25, "How much the NPC will heal on gate if enabled") RULE_BOOL(NPC, AnimalsOpenDoors, true, "Determines or not whether animals open doors or not when they approach them") +RULE_INT(NPC, MaxRaceID, 732, "Maximum Race ID, RoF2 by default supports up to 732") RULE_CATEGORY_END() RULE_CATEGORY(Aggro) @@ -543,7 +554,6 @@ RULE_INT(Aggro, MeleeRangeAggroMod, 10, "Aggro increase against targets in melee RULE_INT(Aggro, CurrentTargetAggroMod, 0, "Aggro increase against current target. 0% = prefer the current target to any other. Makes it harder for our NPC to switch targets") RULE_INT(Aggro, CriticallyWoundedAggroMod, 100, "Aggro increase against critical wounded targets") RULE_INT(Aggro, SpellAggroMod, 100, "Aggro increase for spells") -RULE_INT(Aggro, SongAggroMod, 33, "Aggro increase for songs") RULE_INT(Aggro, PetSpellAggroMod, 10, "Aggro increase for pet spells") RULE_REAL(Aggro, TunnelVisionAggroMod, 0.75, "People not currently the top hate generate this much hate on a Tunnel Vision mob") RULE_INT(Aggro, MaxScalingProcAggro, 400, "Set to -1 for no limit. Maximum amount of aggro that HP scaling SPA effect in a proc will add") @@ -555,6 +565,7 @@ RULE_INT(Aggro, ClientAggroCheckMovingInterval, 1000, "Interval in which clients RULE_INT(Aggro, ClientAggroCheckIdleInterval, 6000, "Interval in which clients actually check for aggro while idle - in milliseconds - this should be higher than ClientAggroCheckMovingInterval") RULE_REAL(Aggro, PetAttackRange, 40000.0, "Maximum squared range /pet attack works at default is 200") RULE_BOOL(Aggro, NPCAggroMaxDistanceEnabled, true, "If enabled, NPC's will drop aggro beyond 600 units or what is defined at the zone level") +RULE_BOOL(Aggro, AggroPlayerPets, false, "If enabled, NPCs will aggro player pets") RULE_CATEGORY_END() RULE_CATEGORY(TaskSystem) @@ -564,6 +575,7 @@ RULE_BOOL(TaskSystem, RecordCompletedTasks, true, "Record completed tasks") RULE_BOOL(TaskSystem, RecordCompletedOptionalActivities, false, "Record completed optional activities") RULE_BOOL(TaskSystem, KeepOneRecordPerCompletedTask, true, "Keep only one record per completed task") RULE_BOOL(TaskSystem, EnableTaskProximity, true, "Enable task proximity system") +RULE_INT(TaskSystem, RequestCooldownTimerSeconds, 15, "Seconds between allowing characters to request tasks (live-like default: 15 seconds)") RULE_CATEGORY_END() RULE_CATEGORY(Range) @@ -575,7 +587,6 @@ RULE_INT(Range, SpellParticles, 135, "The packet range in which spell particles RULE_INT(Range, DamageMessages, 50, "The packet range in which damage messages are sent (non-crit)") RULE_INT(Range, SpellMessages, 75, "The packet range in which spell damage messages are sent") RULE_INT(Range, SongMessages, 75, "The packet range in which song messages are sent") -RULE_INT(Range, MobPositionUpdates, 600, "The packet range in which mob position updates are sent") RULE_INT(Range, ClientPositionUpdates, 300, "Distance in which the own changed position is communicated to other clients") RULE_INT(Range, CriticalDamage, 80, "The packet range in which critical hit messages are sent") RULE_INT(Range, MobCloseScanDistance, 600, "Close scan distance") @@ -595,19 +606,11 @@ RULE_INT(Bots, HealRotationMaxTargets, 12, "Maximum number of heal rotation targ RULE_REAL(Bots, ManaRegen, 2.0, "Adjust mana regen for bots, 1 is fast and higher numbers slow it down 3 is about the same as players") RULE_BOOL(Bots, PreferNoManaCommandSpells, true, "Give sorting priority to newer no-mana spells (i.e., 'Bind Affinity')") RULE_BOOL(Bots, QuestableSpawnLimit, false, "Optional quest method to manage bot spawn limits using the quest_globals name bot_spawn_limit, see: /bazaar/Aediles_Thrall.pl") -RULE_BOOL(Bots, QuestableSpells, false, "Anita Thrall's (Anita_Thrall.pl) Bot Spell Scriber quests") RULE_INT(Bots, SpawnLimit, 71, "Number of bots a character can have spawned at one time, You + 71 bots is a 12 group pseudo-raid") -RULE_BOOL(Bots, UpdatePositionWithTimer, false, "Sends a position update with every positive movement timer check") -RULE_BOOL(Bots, UsePathing, true, "Bots will use node pathing when moving") RULE_BOOL(Bots, BotGroupXP, false, "Determines whether client gets experience for bots outside their group") -RULE_BOOL(Bots, BotBardUseOutOfCombatSongs, true, "Determines whether bard bots use additional out of combat songs (optional script)") RULE_BOOL(Bots, BotLevelsWithOwner, false, "Auto-updates spawned bots as owner levels/de-levels (false is original behavior)") -RULE_BOOL(Bots, BotCharacterLevelEnabled, false, "Enables required level to spawn bots") RULE_INT(Bots, BotCharacterLevel, 0, "If level is greater that value player can spawn bots if BotCharacterLevelEnabled is true") RULE_INT(Bots, CasterStopMeleeLevel, 13, "Level at which caster bots stop melee attacks") -RULE_INT(Bots, AllowedClasses, 0xFFFFFFFF, "Bitmask of allowed bot classes") -RULE_INT(Bots, AllowedRaces, 0xFFFFFFFF, "Bitmask of allowed bot races") -RULE_INT(Bots, AllowedGenders, 0x3, "Bitmask of allowed bot genders") RULE_BOOL(Bots, AllowOwnerOptionAltCombat, true, "When option is enabled, bots will use an auto-/shared-aggro combat model") RULE_BOOL(Bots, AllowOwnerOptionAutoDefend, true, "When option is enabled, bots will defend their owner on enemy aggro") RULE_REAL(Bots, LeashDistance, 562500.0f, "Distance a bot is allowed to travel from leash owner before being pulled back (squared value)") @@ -616,6 +619,11 @@ RULE_BOOL(Bots, AllowApplyPotionCommand, true, "Allows the use of the bot comman RULE_BOOL(Bots, RestrictApplyPotionToRogue, true, "Restricts the bot command 'applypotion' to rogue-usable potions (i.e., poisons)") RULE_BOOL(Bots, DisplayHealDamage, false, "Enables the display of bot heal damage to the bot owner client") RULE_BOOL(Bots, DisplaySpellDamage, false, "Enables the display of bot spell damage to the bot owner client") +RULE_BOOL(Bots, OldRaceRezEffects, false, "Older clients had ID 757 for races with high starting STR, but it doesn't seem used anymore") +RULE_BOOL(Bots, ResurrectionSickness, true, "Use Resurrection Sickness based on Resurrection spell cast, set to false to disable Resurrection Sickness.") +RULE_INT(Bots, OldResurrectionSicknessSpell, 757, "757 is Default Old Resurrection Sickness Spell") +RULE_INT(Bots, ResurrectionSicknessSpell, 756, "756 is Default Resurrection Sickness Spell") + RULE_CATEGORY_END() #endif @@ -634,6 +642,10 @@ RULE_INT(Chat, IntervalDurationMS, 60000, "Interval length in milliseconds") RULE_INT(Chat, KarmaUpdateIntervalMS, 1200000, "Karma update interval in milliseconds") RULE_INT(Chat, KarmaGlobalChatLimit, 72, "Amount of karma you need to be able to talk in ooc/auction/chat below the level limit") RULE_INT(Chat, GlobalChatLevelLimit, 8, "Level limit you need to of reached to talk in ooc/auction/chat if your karma is too low") +RULE_BOOL(Chat, AutoInjectSaylinksToSay, true, "Automatically injects saylinks into dialogue that has [brackets in them]") +RULE_BOOL(Chat, AutoInjectSaylinksToClientMessage, true, "Automatically injects saylinks into dialogue that has [brackets in them]") +RULE_BOOL(Chat, QuestDialogueUsesDialogueWindow, false, "Pipes all quest dialogue to dialogue window") +RULE_BOOL(Chat, DialogueWindowAnimatesNPCsIfNoneSet, true, "If there is no animation specified in the dialogue window markdown then it will choose a random greet animation such as wave or salute") RULE_CATEGORY_END() RULE_CATEGORY(Merchant) @@ -684,12 +696,10 @@ RULE_INT(Adventure, ItemIDToEnablePorts, 41000, "ItemID to enable adventure port RULE_INT(Adventure, LDoNTrapDistanceUse, 625, "LDoN trap distance use") RULE_REAL(Adventure, LDoNBaseTrapDifficulty, 15.0, "LDoN base trap difficulty") RULE_REAL(Adventure, LDoNCriticalFailTrapThreshold, 10.0, "LDoN critical fail trap threshold") -RULE_INT(Adventure, LDoNAdventureExpireTime, 1800, "LDoN adventure expire time (seconds)") RULE_CATEGORY_END() RULE_CATEGORY(AA) RULE_INT(AA, ExpPerPoint, 23976503, "Amount of experience per AA. Is the same as the amount of experience to go from level 51 to level 52") -RULE_BOOL(AA, Stacking, true, "Allow AA that belong to the same group to stack on SOF+ clients") RULE_BOOL(AA, NormalizedAAEnabled, false, "TSS+ change to AA that normalizes AA experience to a fixed # of white con kills independent of level") RULE_INT(AA, NormalizedAANumberOfWhiteConPerAA, 25, "The number of white con kills per AA point") RULE_BOOL(AA, ModernAAScalingEnabled, false, "Are we linearly scaling AA experience based on total # of earned AA?") @@ -716,13 +726,11 @@ RULE_CATEGORY(QueryServ) RULE_BOOL(QueryServ, PlayerLogChat, false, "Log player chat") RULE_BOOL(QueryServ, PlayerLogTrades, false, "Log player trades") RULE_BOOL(QueryServ, PlayerDropItems, false, "Log player dropping items") -RULE_BOOL(QueryServ, PlayerLogHandins, false, "Log player handins") +RULE_BOOL(QueryServ, PlayerLogHandins, false, "Log player hand ins") RULE_BOOL(QueryServ, PlayerLogNPCKills, false, "Log player NPC kills") RULE_BOOL(QueryServ, PlayerLogDeletes, false, "Log player deletes") RULE_BOOL(QueryServ, PlayerLogMoves, false, "Log player moves") RULE_BOOL(QueryServ, PlayerLogMerchantTransactions, false, "Log merchant transactions") -RULE_BOOL(QueryServ, PlayerLogPCCoordinates, false, "Log player coordinates with certain events") -RULE_BOOL(QueryServ, PlayerLogDropItem, false, "Log player drop item") RULE_BOOL(QueryServ, PlayerLogZone, false, "Log player zone events") RULE_BOOL(QueryServ, PlayerLogDeaths, false, "Log player deaths") RULE_BOOL(QueryServ, PlayerLogConnectDisconnect, false, "Logs player connect/disconnect state") @@ -730,11 +738,9 @@ RULE_BOOL(QueryServ, PlayerLogLevels, false, "Log player leveling/deleveling") RULE_BOOL(QueryServ, PlayerLogAARate, false, "Log player AA experience rates") RULE_BOOL(QueryServ, PlayerLogQGlobalUpdate, false, "Log player QGlobal updates") RULE_BOOL(QueryServ, PlayerLogTaskUpdates, false, "Log player Task updates") -RULE_BOOL(QueryServ, PlayerLogKeyringAddition, false, "Log player keyring additions") RULE_BOOL(QueryServ, PlayerLogAAPurchases, false, "Log player AA purchases") RULE_BOOL(QueryServ, PlayerLogTradeSkillEvents, false, "Log player tradeskill transactions") RULE_BOOL(QueryServ, PlayerLogIssuedCommandes, false, "Log player issued commands") -RULE_BOOL(QueryServ, PlayerLogMoneyTransactions, false, "Log player money transaction/splits") RULE_BOOL(QueryServ, PlayerLogAlternateCurrencyTransactions, false, "Log player alternate currency transactions") RULE_CATEGORY_END() @@ -745,6 +751,7 @@ RULE_BOOL(Inventory, EnforceAugmentWear, true, "Forces augment wear slot validat RULE_BOOL(Inventory, DeleteTransformationMold, true, "False if you want mold to last forever") RULE_BOOL(Inventory, AllowAnyWeaponTransformation, false, "Weapons can use any weapon transformation") RULE_BOOL(Inventory, TransformSummonedBags, false, "Transforms summoned bags into disenchanted ones instead of deleting") +RULE_BOOL(Inventory, AllowMultipleOfSameAugment, false, "Allows multiple of the same augment to be placed in an item via #augmentitem or MQ2, set to true to allow") RULE_CATEGORY_END() RULE_CATEGORY(Client) @@ -771,6 +778,7 @@ RULE_CATEGORY_END() RULE_CATEGORY(Logging) RULE_BOOL(Logging, PrintFileFunctionAndLine, false, "Ex: [World Server] [net.cpp::main:309] Loading variables...") +RULE_BOOL(Logging, WorldGMSayLogging, true, "Relay worldserver logging to zone processes via GM say output") RULE_CATEGORY_END() RULE_CATEGORY(HotReload) @@ -782,6 +790,7 @@ RULE_CATEGORY_END() RULE_CATEGORY(Expansion) RULE_INT(Expansion, CurrentExpansion, -1, "The current expansion enabled for the server [-1 = ALL, 0 = Classic, 1 = Kunark etc.]") +RULE_BOOL(Expansion, UseCurrentExpansionAAOnly, false, "When true will only load AA ranks that match CurrentExpansion rule") RULE_CATEGORY_END() RULE_CATEGORY(Instances) @@ -792,17 +801,40 @@ RULE_CATEGORY_END() RULE_CATEGORY(Expedition) RULE_INT(Expedition, MinStatusToBypassPlayerCountRequirements, 80, "Minimum GM status to bypass minimum player requirements for Expedition creation") -RULE_BOOL(Expedition, EmptyDzShutdownEnabled, true, "Enable early instance shutdown after last member of expedition removed") -RULE_INT(Expedition, EmptyDzShutdownDelaySeconds, 1500, "Seconds to set dynamic zone instance expiration if early shutdown enabled") -RULE_INT(Expedition, WorldExpeditionProcessRateMS, 6000, "Timer interval (ms) that world checks expedition states") RULE_BOOL(Expedition, AlwaysNotifyNewLeaderOnChange, false, "Always notify clients when made expedition leader. If false (live-like) new leaders are only notified when made leader via /dzmakeleader") RULE_REAL(Expedition, LockoutDurationMultiplier, 1.0, "Multiplies lockout duration by this value when new lockouts are added") -RULE_BOOL(Expedition, EnableInDynamicZoneStatus, false, "Enables the 'In Dynamic Zone' member status in expedition window. If false (live-like) players inside the dz will show as 'Online'") -RULE_INT(Expedition, ChooseLeaderCooldownTime, 2000, "Cooldown time (ms) between choosing a new leader for automatic leader changes") +RULE_INT(Expedition, ChooseLeaderCooldownTime, 2000, "Cooldown time (milliseconds) between choosing a new leader for automatic leader changes") RULE_CATEGORY_END() RULE_CATEGORY(DynamicZone) -RULE_INT(DynamicZone, ClientRemovalDelayMS, 60000, "Delay (ms) until a client is teleported out of dynamic zone after being removed as member") +RULE_INT(DynamicZone, ClientRemovalDelayMS, 60000, "Delay (milliseconds) until a client is teleported out of dynamic zone after being removed as member") +RULE_BOOL(DynamicZone, EmptyShutdownEnabled, true, "Enable early instance shutdown for dynamic zones that have no members") +RULE_INT(DynamicZone, EmptyShutdownDelaySeconds, 1500, "Seconds to set dynamic zone instance expiration if early shutdown enabled") +RULE_BOOL(DynamicZone, EnableInDynamicZoneStatus, false, "Enables the 'In Dynamic Zone' member status in dynamic zone window. If false (live-like) players inside the dynamic zone will show as 'Online'") +RULE_INT(DynamicZone, WorldProcessRate, 6000, "Timer interval (milliseconds) that systems check their dynamic zone states") +RULE_CATEGORY_END() + +RULE_CATEGORY(Cheat) +RULE_REAL(Cheat, MQWarpDetectionDistanceFactor, 9.0, "clients move at 4.4 about if in a straight line but with movement and to acct for lag we raise it a bit") +RULE_INT(Cheat, MQWarpExemptStatus, -1, "Required status level to exempt the MQWarpDetector. Set to -1 to disable this feature.") +RULE_INT(Cheat, MQZoneExemptStatus, -1, "Required status level to exempt the MQZoneDetector. Set to -1 to disable this feature.") +RULE_INT(Cheat, MQGateExemptStatus, -1, "Required status level to exempt the MQGateDetector. Set to -1 to disable this feature.") +RULE_INT(Cheat, MQGhostExemptStatus, -1, "Required status level to exempt the MQGhostDetector. Set to -1 to disable this feature.") +RULE_INT(Cheat, MQFastMemExemptStatus, -1, "Required status level to exempt the MQFastMemDetector. Set to -1 to disable this feature.") +RULE_BOOL(Cheat, EnableMQWarpDetector, true, "Enable the MQWarp Detector. Set to False to disable this feature.") +RULE_BOOL(Cheat, EnableMQZoneDetector, true, "Enable the MQZone Detector. Set to False to disable this feature.") +RULE_BOOL(Cheat, EnableMQGateDetector, true, "Enable the MQGate Detector. Set to False to disable this feature.") +RULE_BOOL(Cheat, EnableMQGhostDetector, true, "Enable the MQGhost Detector. Set to False to disable this feature.") +RULE_BOOL(Cheat, EnableMQFastMemDetector, true, "Enable the MQFastMem Detector. Set to False to disable this feature.") +RULE_BOOL(Cheat, MarkMQWarpLT, false, "Mark clients makeing smaller warps") +RULE_CATEGORY_END() + +RULE_CATEGORY(Command) +RULE_BOOL(Command, DyeCommandRequiresDyes, false, "Enable this to require a Prismatic Dye (32557) each time someone uses #dye.") +RULE_CATEGORY_END() + +RULE_CATEGORY(Doors) +RULE_BOOL(Doors, RequireKeyOnCursor, false, "Enable this to require pre-keyring keys to be on player cursor to open doors.") RULE_CATEGORY_END() #undef RULE_CATEGORY diff --git a/common/say_link.cpp b/common/say_link.cpp index b47897112..a10dc4b2d 100644 --- a/common/say_link.cpp +++ b/common/say_link.cpp @@ -1,5 +1,5 @@ /* EQEMu: Everquest Server Emulator - + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.net) This program is free software; you can redistribute it and/or modify @@ -11,7 +11,7 @@ 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 @@ -24,7 +24,10 @@ #include "item_instance.h" #include "item_data.h" #include "../zone/zonedb.h" +#include +// static bucket global +std::vector g_cached_saylinks = {}; bool EQ::saylink::DegenerateLinkBody(SayLinkBody_Struct &say_link_body_struct, const std::string &say_link_body) { @@ -102,13 +105,13 @@ const std::string &EQ::SayLinkEngine::GenerateLink() m_Link = ""; LogError("SayLinkEngine::GenerateLink() failed to generate a useable say link"); LogError(">> LinkType: {}, Lengths: [link: {}({}), body: {}({}), text: {}({})]", - m_LinkType, - m_Link.length(), - EQ::constants::SAY_LINK_MAXIMUM_SIZE, - m_LinkBody.length(), - EQ::constants::SAY_LINK_BODY_SIZE, - m_LinkText.length(), - EQ::constants::SAY_LINK_TEXT_SIZE + m_LinkType, + m_Link.length(), + EQ::constants::SAY_LINK_MAXIMUM_SIZE, + m_LinkBody.length(), + EQ::constants::SAY_LINK_BODY_SIZE, + m_LinkText.length(), + EQ::constants::SAY_LINK_TEXT_SIZE ); LogError(">> LinkBody: {}", m_LinkBody.c_str()); LogError(">> LinkText: {}", m_LinkText.c_str()); @@ -295,33 +298,9 @@ std::string EQ::SayLinkEngine::GenerateQuestSaylink(std::string saylink_text, bo { uint32 saylink_id = 0; - /** - * Query for an existing phrase and id in the saylink table - */ - std::string query = StringFormat( - "SELECT `id` FROM `saylink` WHERE `phrase` = '%s' LIMIT 1", - EscapeString(saylink_text).c_str()); - - auto results = database.QueryDatabase(query); - - if (results.Success()) { - if (results.RowCount() >= 1) { - for (auto row = results.begin(); row != results.end(); ++row) - saylink_id = static_cast(atoi(row[0])); - } - else { - std::string insert_query = StringFormat( - "INSERT INTO `saylink` (`phrase`) VALUES ('%s')", - EscapeString(saylink_text).c_str()); - - results = database.QueryDatabase(insert_query); - if (!results.Success()) { - LogError("Error in saylink phrase queries {}", results.ErrorMessage().c_str()); - } - else { - saylink_id = results.LastInsertedID(); - } - } + SaylinkRepository::Saylink saylink = GetOrSaveSaylink(saylink_text); + if (saylink.id > 0) { + saylink_id = saylink.id; } /** @@ -339,4 +318,193 @@ std::string EQ::SayLinkEngine::GenerateQuestSaylink(std::string saylink_text, bo linker.SetProxyText(link_name.c_str()); return linker.GenerateLink(); -} \ No newline at end of file +} + +std::string EQ::SayLinkEngine::InjectSaylinksIfNotExist(const char *message) +{ + std::string new_message = message; + int link_index = 0; + int saylink_index = 0; + std::vector links = {}; + std::vector saylinks = {}; + int saylink_length = 50; + std::string saylink_separator = "\u0012"; + std::string saylink_partial = "00000"; + + LogSaylinkDetail("new_message pre pass 1 [{}]", new_message); + + // first pass - strip existing saylinks by putting placeholder anchors on them + for (auto &saylink: split_string(new_message, saylink_separator)) { + if (!saylink.empty() && saylink.length() > saylink_length && + saylink.find(saylink_partial) != std::string::npos) { + saylinks.emplace_back(saylink); + + LogSaylinkDetail("Found saylink [{}]", saylink); + + // replace with anchor + find_replace( + new_message, + fmt::format("{}", saylink), + fmt::format("", saylink_index) + ); + + saylink_index++; + } + } + + LogSaylinkDetail("new_message post pass 1 [{}]", new_message); + + LogSaylinkDetail("saylink separator count [{}]", std::count(new_message.begin(), new_message.end(), '\u0012')); + + // loop through brackets until none exist + if (new_message.find('[') != std::string::npos) { + for (auto &b: split_string(new_message, "[")) { + if (!b.empty() && b.find(']') != std::string::npos) { + std::vector right_split = split_string(b, "]"); + if (!right_split.empty()) { + std::string bracket_message = trim(right_split[0]); + + // we shouldn't see a saylink fragment here, ignore this bracket + if (bracket_message.find(saylink_partial) != std::string::npos) { + continue; + } + + // skip where multiple saylinks are within brackets + if (bracket_message.find(saylink_separator) != std::string::npos && + std::count(bracket_message.begin(), bracket_message.end(), '\u0012') > 1) { + continue; + } + + // if non empty bracket contents + if (!bracket_message.empty()) { + LogSaylinkDetail("Found bracket_message [{}]", bracket_message); + + // already a saylink + // todo: improve this later + if (!bracket_message.empty() && + (bracket_message.length() > saylink_length || + bracket_message.find(saylink_separator) != std::string::npos)) { + links.emplace_back(bracket_message); + } + else { + links.emplace_back( + EQ::SayLinkEngine::GenerateQuestSaylink( + bracket_message, + false, + bracket_message + ) + ); + } + + // replace with anchor + find_replace( + new_message, + fmt::format("[{}]", bracket_message), + fmt::format("", link_index) + ); + + link_index++; + } + } + } + } + } + + LogSaylinkDetail("new_message post pass 2 (post brackets) [{}]", new_message); + + // strip any current delimiters of saylinks + find_replace(new_message, saylink_separator, ""); + + // pop links onto anchors + link_index = 0; + for (auto &link: links) { + + // strip any current delimiters of saylinks + find_replace(link, saylink_separator, ""); + + find_replace( + new_message, + fmt::format("", link_index), + fmt::format("[\u0012{}\u0012]", link) + ); + link_index++; + } + + LogSaylinkDetail("new_message post pass 3 (post prelink anchor pop) [{}]", new_message); + + // pop links onto anchors + saylink_index = 0; + for (auto &link: saylinks) { + // strip any current delimiters of saylinks + find_replace(link, saylink_separator, ""); + + // check to see if we did a double anchor pass (existing saylink that was also inside brackets) + // this means we found a saylink and we're checking to see if we're already encoded before double encoding + if (new_message.find(fmt::format("\u0012\u0012", saylink_index)) != std::string::npos) { + LogSaylinkDetail("Found encoded saylink at index [{}]", saylink_index); + + find_replace( + new_message, + fmt::format("\u0012\u0012", saylink_index), + fmt::format("\u0012{}\u0012", link) + ); + saylink_index++; + continue; + } + + find_replace( + new_message, + fmt::format("", saylink_index), + fmt::format("\u0012{}\u0012", link) + ); + saylink_index++; + } + + LogSaylinkDetail("new_message post pass 4 (post saylink anchor pop) [{}]", new_message); + + return new_message; +} + +void EQ::SayLinkEngine::LoadCachedSaylinks() +{ + auto saylinks = SaylinkRepository::GetWhere(database, "phrase not like '%#%'"); + LogSaylink("Loaded [{}] saylinks into cache", saylinks.size()); + g_cached_saylinks = saylinks; +} + +SaylinkRepository::Saylink EQ::SayLinkEngine::GetOrSaveSaylink(std::string saylink_text) +{ + // return cached saylink if exist + if (!g_cached_saylinks.empty()) { + for (auto &s: g_cached_saylinks) { + if (s.phrase == saylink_text) { + return s; + } + } + } + + auto saylinks = SaylinkRepository::GetWhere( + database, + fmt::format("phrase = '{}'", EscapeString(saylink_text)) + ); + + // return if found from the database + if (!saylinks.empty()) { + return saylinks[0]; + } + + // if not found in database - save + if (saylinks.empty()) { + auto new_saylink = SaylinkRepository::NewEntity(); + new_saylink.phrase = saylink_text; + + // persist to database + auto link = SaylinkRepository::InsertOne(database, new_saylink); + if (link.id > 0) { + g_cached_saylinks.emplace_back(link); + return link; + } + } + + return {}; +} diff --git a/common/say_link.h b/common/say_link.h index c0ea5a235..aec8d8879 100644 --- a/common/say_link.h +++ b/common/say_link.h @@ -1,17 +1,17 @@ /* EQEMu: Everquest Server Emulator - + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.net) 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 @@ -23,7 +23,7 @@ #include "types.h" #include - +#include "repositories/saylink_repository.h" struct ServerLootItem_Struct; @@ -105,6 +105,8 @@ namespace EQ void Reset(); + static std::string InjectSaylinksIfNotExist(const char *message); + static void LoadCachedSaylinks(); private: void generate_body(); void generate_text(); @@ -120,6 +122,7 @@ namespace EQ std::string m_LinkBody; std::string m_LinkText; bool m_Error; + static SaylinkRepository::Saylink GetOrSaveSaylink(std::string saylink_text); }; } /*EQEmu*/ diff --git a/common/server_event_scheduler.cpp b/common/server_event_scheduler.cpp new file mode 100644 index 000000000..1034eebfe --- /dev/null +++ b/common/server_event_scheduler.cpp @@ -0,0 +1,247 @@ +#include "../common/database.h" +#include "../common/string_util.h" +#include "server_event_scheduler.h" +#include "../common/cron/croncpp.h" +#include +#include +#include + +ServerEventScheduler::ServerEventScheduler() +{ + m_last_polled_minute = -1; + m_events = {}; + m_active_events = {}; +} + +ServerEventScheduler::~ServerEventScheduler() = default; + +void ServerEventScheduler::LoadScheduledEvents() +{ + if (!ValidateDatabaseConnection()) { + return; + } + + std::time_t time = std::time(nullptr); + std::tm *now = std::localtime(&time); + + m_events = ServerScheduledEventsRepository::GetWhere(*m_database, "deleted_at is null"); + for (auto &e: m_events) { + + auto start = BuildStartTimeFromEvent(e, now); + auto end = BuildEndTimeFromEvent(e, now); + + // data excluded from output because it can be very large + + LogScheduler( + "Loaded Event ({}) [{}] type [{}] start [{}/{}/{} {:02}:{:02}:00] end [{}/{}/{} {:02}:{:02}:00] cron [{}] created [{}]", + e.id, + e.description, + e.event_type, + start.tm_mon + 1, + start.tm_mday, + start.tm_year + 1900, + start.tm_hour, + start.tm_min, + end.tm_mon + 1, + end.tm_mday, + end.tm_year + 1900, + end.tm_hour, + end.tm_min, + e.cron_expression, + e.created_at + ); + } + + LogScheduler("Loaded scheduled events [{}]", m_events.size()); +} + +// checks to see if event is ready to be activated +bool ServerEventScheduler::ValidateEventReadyToActivate( + ServerScheduledEventsRepository::ServerScheduledEvents &e +) +{ + + // if there is a cron expression, it will try to parse it first before falling back to + // alternative time logic + if (!e.cron_expression.empty()) { + try { + auto cron = cron::make_cron(e.cron_expression); + std::time_t cron_now = std::time(nullptr); + std::time_t cron_next = cron::cron_next(cron, cron_now); + + // we have to pad our now window just a tad so we don't miss the cron window + if ((cron_now + 10) >= cron_next) { + LogScheduler("Cron time has been met! Event scheduling ({}) [{}]", e.id, e.description); + return true; + } + + LogSchedulerDetail("Cron now [{}] cron next [{}]\n", cron_now, cron_next); + } + catch (cron::bad_cronexpr const &ex) { + LogScheduler( + "Error: Cron expression error [{}] see [https://github.com/mariusbancila/croncpp#cron-expressions]", + ex.what() + ); + } + + return false; + } + + std::time_t time = std::time(nullptr); + std::tm *now = std::localtime(&time); + time_t now_time_unix = mktime(now); + auto start = BuildStartTimeFromEvent(e, now); + auto end = BuildEndTimeFromEvent(e, now); + time_t start_time_unix = mktime(&start); + + bool doesnt_end = ( + e.year_end == 0 && + e.month_end == 0 && + e.day_end == 0 && + e.hour_end == 0 && + e.minute_end == 0 + ); + + time_t end_time_unix; + if (!doesnt_end) { + end_time_unix = mktime(&end); + } + + if (now_time_unix >= start_time_unix && (doesnt_end || now_time_unix < end_time_unix)) { + LogSchedulerDetail( + "[ValidateEventReadyToActivate] now_time [{}] start_time [{}] doesnt_end [{}] end_time [{}]", + now_time_unix, + start_time_unix, + doesnt_end ? "true" : "false", + end_time_unix + ); + return true; + } + + return false; +} + +ServerEventScheduler *ServerEventScheduler::SetDatabase(Database *db) +{ + m_database = db; + + return this; +} + +bool ServerEventScheduler::ValidateDatabaseConnection() +{ + if (!m_database) { + LogError("[ServerEventScheduler::LoadScheduledEvents] No database connection"); + return false; + } + + return true; +} + +// in this function we simply look at events we have internally and events +// in the database and determine if any edits have been made +// this helps inform decisions to tell all zones to reload their events +bool ServerEventScheduler::CheckIfEventsChanged() +{ + auto events = ServerScheduledEventsRepository::GetWhere(*m_database, "deleted_at is null"); + + // first check if the size changed, if it did this is the easiest step + if (m_events.size() != events.size()) { + LogSchedulerDetail("[CheckIfEventsChanged] Event size has changed"); + m_events = events; + return true; + } + + // compare fields of database fields to internal events to see if any fields changed + for (auto &e: m_events) { + for (auto &dbe: events) { + if (dbe.id == e.id) { + if ( + dbe.description != e.description || + dbe.event_type != e.event_type || + dbe.event_data != e.event_data || + dbe.minute_start != e.minute_start || + dbe.hour_start != e.hour_start || + dbe.day_start != e.day_start || + dbe.month_start != e.month_start || + dbe.year_start != e.year_start || + dbe.minute_end != e.minute_end || + dbe.hour_end != e.hour_end || + dbe.day_end != e.day_end || + dbe.month_end != e.month_end || + dbe.year_end != e.year_end || + dbe.cron_expression != e.cron_expression || + dbe.created_at != e.created_at || + dbe.deleted_at != e.deleted_at + ) { + LogSchedulerDetail("[CheckIfEventsChanged] Field change detected"); + m_events = events; + return true; + } + } + } + } + + return false; +} + +// checks if event is active +bool ServerEventScheduler::IsEventActive(ServerScheduledEventsRepository::ServerScheduledEvents &e) +{ + for (auto &a: m_active_events) { + if (a.id == e.id) { + return true; + } + } + + return false; +} + +bool ServerEventScheduler::RemoveActiveEvent(ServerScheduledEventsRepository::ServerScheduledEvents &e) +{ + m_active_events.erase( + std::remove_if( + m_active_events.begin(), + m_active_events.end(), + [&](ServerScheduledEventsRepository::ServerScheduledEvents const &active_event) { + return active_event.id == e.id; + } + ), + m_active_events.end()); + + return false; +} + +std::tm ServerEventScheduler::BuildStartTimeFromEvent( + ServerScheduledEventsRepository::ServerScheduledEvents &e, + std::tm *now +) +{ + struct tm time{}; + time.tm_year = ((e.year_start > 0) ? e.year_start - 1900 : now->tm_year); + time.tm_mon = ((e.month_start > 0) ? e.month_start - 1 : now->tm_mon); + time.tm_mday = ((e.day_start > 0) ? e.day_start : now->tm_mday); + time.tm_hour = ((e.hour_start > 0) ? e.hour_start : now->tm_hour); + time.tm_min = ((e.minute_start > 0) ? e.minute_start : now->tm_min); + time.tm_sec = 0; + time.tm_isdst = now->tm_isdst; + + return time; +} + +std::tm ServerEventScheduler::BuildEndTimeFromEvent( + ServerScheduledEventsRepository::ServerScheduledEvents &e, + std::tm *now +) +{ + struct tm time{}; + time.tm_year = ((e.year_end > 0) ? e.year_end - 1900 : now->tm_year); + time.tm_mon = ((e.month_end > 0) ? e.month_end - 1 : now->tm_mon); + time.tm_mday = ((e.day_end > 0) ? e.day_end : now->tm_mday); + time.tm_hour = ((e.hour_end > 0) ? e.hour_end : now->tm_hour); + time.tm_min = ((e.minute_end > 0) ? e.minute_end : now->tm_min); + time.tm_sec = 0; + time.tm_isdst = now->tm_isdst; + + return time; +} diff --git a/common/server_event_scheduler.h b/common/server_event_scheduler.h new file mode 100644 index 000000000..5671eaf7c --- /dev/null +++ b/common/server_event_scheduler.h @@ -0,0 +1,57 @@ +#ifndef EQEMU_SERVER_EVENT_SCHEDULER_H +#define EQEMU_SERVER_EVENT_SCHEDULER_H + +#include "../common/repositories/server_scheduled_events_repository.h" +#include +#include + +namespace ServerEvents { + static const std::string EVENT_TYPE_HOT_ZONE_ACTIVE = "hot_zone_activate"; + static const std::string EVENT_TYPE_BROADCAST = "broadcast"; + static const std::string EVENT_TYPE_RELOAD_WORLD = "reload_world"; + static const std::string EVENT_TYPE_RULE_CHANGE = "rule_change"; + static const std::string EVENT_TYPE_CONTENT_FLAG_CHANGE = "content_flag_change"; +} + +class ServerEventScheduler { +public: + virtual ~ServerEventScheduler(); + ServerEventScheduler(); + ServerEventScheduler *SetDatabase(Database *db); + void LoadScheduledEvents(); + bool CheckIfEventsChanged(); + +protected: + + // events directly from the database + std::vector m_events; + + // used to track only when it is convenient to undo an action from an active event + // typically there should be two separate events to turn something on / off + // hotzones use this right now simply to keep us from toggling off the hotzone + // every minute we trigger and then immediately turning it right back on + std::vector m_active_events; + + // simple ticker used to determine when the last polled minute was so that when the minute + // changes we fire checking the scheduler + int m_last_polled_minute; + + // validates an event is currently active or not + bool ValidateEventReadyToActivate(ServerScheduledEventsRepository::ServerScheduledEvents &e); + + // is event active + bool IsEventActive(ServerScheduledEventsRepository::ServerScheduledEvents &e); + + // remove active event + bool RemoveActiveEvent(ServerScheduledEventsRepository::ServerScheduledEvents &e); + + // build time object from event + std::tm BuildStartTimeFromEvent(ServerScheduledEventsRepository::ServerScheduledEvents &e, tm *now); + std::tm BuildEndTimeFromEvent(ServerScheduledEventsRepository::ServerScheduledEvents &e, tm *now); + + // reference to database + Database *m_database; + bool ValidateDatabaseConnection(); +}; + +#endif //EQEMU_SERVER_EVENT_SCHEDULER_H diff --git a/common/servertalk.h b/common/servertalk.h index 40be635ad..37c96d844 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -6,7 +6,10 @@ #include "../common/eq_packet_structs.h" #include "../common/net/packet.h" #include +#include +#include #include +#include #define SERVER_TIMEOUT 45000 // how often keepalive gets sent #define INTERSERVER_TIMER 10000 @@ -141,33 +144,33 @@ #define ServerOP_LFPMatches 0x0214 #define ServerOP_ClientVersionSummary 0x0215 +// expedition #define ServerOP_ExpeditionCreate 0x0400 -#define ServerOP_ExpeditionDeleted 0x0401 -#define ServerOP_ExpeditionLeaderChanged 0x0402 #define ServerOP_ExpeditionLockout 0x0403 -#define ServerOP_ExpeditionMemberChange 0x0404 -#define ServerOP_ExpeditionMemberSwap 0x0405 -#define ServerOP_ExpeditionMemberStatus 0x0406 -#define ServerOP_ExpeditionGetOnlineMembers 0x0407 #define ServerOP_ExpeditionDzAddPlayer 0x0408 #define ServerOP_ExpeditionDzMakeLeader 0x0409 -#define ServerOP_ExpeditionDzCompass 0x040a -#define ServerOP_ExpeditionDzSafeReturn 0x040b -#define ServerOP_ExpeditionDzZoneIn 0x040c #define ServerOP_ExpeditionCharacterLockout 0x040d #define ServerOP_ExpeditionSaveInvite 0x040e #define ServerOP_ExpeditionRequestInvite 0x040f #define ServerOP_ExpeditionReplayOnJoin 0x0410 #define ServerOP_ExpeditionLockState 0x0411 -#define ServerOP_ExpeditionMembersRemoved 0x0412 -#define ServerOP_ExpeditionDzDuration 0x0413 #define ServerOP_ExpeditionLockoutDuration 0x0414 -#define ServerOP_ExpeditionSecondsRemaining 0x0415 -#define ServerOP_ExpeditionExpireWarning 0x0416 -#define ServerOP_ExpeditionChooseNewLeader 0x0417 -#define ServerOP_DzCharacterChange 0x0450 -#define ServerOP_DzRemoveAllCharacters 0x0451 +// dz +#define ServerOP_DzAddRemoveMember 0x0450 +#define ServerOP_DzRemoveAllMembers 0x0451 +#define ServerOP_DzSetSecondsRemaining 0x0452 +#define ServerOP_DzDurationUpdate 0x0453 +#define ServerOP_DzSetCompass 0x0454 +#define ServerOP_DzSetSafeReturn 0x0455 +#define ServerOP_DzSetZoneIn 0x0456 +#define ServerOP_DzSwapMembers 0x0457 +#define ServerOP_DzGetMemberStatuses 0x0458 +#define ServerOP_DzUpdateMemberStatus 0x0459 +#define ServerOP_DzLeaderChanged 0x045a +#define ServerOP_DzExpireWarning 0x045b +#define ServerOP_DzCreated 0x045c +#define ServerOP_DzDeleted 0x045d #define ServerOP_LSInfo 0x1000 #define ServerOP_LSStatus 0x1001 @@ -222,90 +225,29 @@ #define ServerOP_UCSServerStatusRequest 0x4009 #define ServerOP_UCSServerStatusReply 0x4010 #define ServerOP_HotReloadQuests 0x4011 +#define ServerOP_UpdateSchedulerEvents 0x4012 +#define ServerOP_ReloadContentFlags 0x4013 -#define ServerOP_CZCastSpellPlayer 0x4500 -#define ServerOP_CZCastSpellGroup 0x4501 -#define ServerOP_CZCastSpellRaid 0x4502 -#define ServerOP_CZCastSpellGuild 0x4503 -#define ServerOP_CZMarqueePlayer 0x4504 -#define ServerOP_CZMarqueeGroup 0x4505 -#define ServerOP_CZMarqueeRaid 0x4506 -#define ServerOP_CZMarqueeGuild 0x4507 -#define ServerOP_CZMessagePlayer 0x4508 -#define ServerOP_CZMessageGroup 0x4509 -#define ServerOP_CZMessageRaid 0x4510 -#define ServerOP_CZMessageGuild 0x4511 -#define ServerOP_CZMovePlayer 0x4512 -#define ServerOP_CZMoveGroup 0x4513 -#define ServerOP_CZMoveRaid 0x4514 -#define ServerOP_CZMoveGuild 0x4515 -#define ServerOP_CZMoveInstancePlayer 0x4516 -#define ServerOP_CZMoveInstanceGroup 0x4517 -#define ServerOP_CZMoveInstanceRaid 0x4518 -#define ServerOP_CZMoveInstanceGuild 0x4519 -#define ServerOP_CZRemoveSpellPlayer 0x4520 -#define ServerOP_CZRemoveSpellGroup 0x4521 -#define ServerOP_CZRemoveSpellRaid 0x4522 -#define ServerOP_CZRemoveSpellGuild 0x4523 -#define ServerOP_CZSetEntityVariableByClientName 0x4524 -#define ServerOP_CZSetEntityVariableByNPCTypeID 0x4525 -#define ServerOP_CZSetEntityVariableByGroupID 0x4526 -#define ServerOP_CZSetEntityVariableByRaidID 0x4527 -#define ServerOP_CZSetEntityVariableByGuildID 0x4528 -#define ServerOP_CZSignalClient 0x4529 -#define ServerOP_CZSignalClientByName 0x4530 -#define ServerOP_CZSignalNPC 0x4531 -#define ServerOP_CZSignalGroup 0x4532 -#define ServerOP_CZSignalRaid 0x4533 -#define ServerOP_CZSignalGuild 0x4534 -#define ServerOP_CZTaskActivityResetPlayer 0x4535 -#define ServerOP_CZTaskActivityResetGroup 0x4536 -#define ServerOP_CZTaskActivityResetRaid 0x4537 -#define ServerOP_CZTaskActivityResetGuild 0x4538 -#define ServerOP_CZTaskActivityUpdatePlayer 0x4539 -#define ServerOP_CZTaskActivityUpdateGroup 0x4540 -#define ServerOP_CZTaskActivityUpdateRaid 0x4541 -#define ServerOP_CZTaskActivityUpdateGuild 0x4542 -#define ServerOP_CZTaskAssignPlayer 0x4543 -#define ServerOP_CZTaskAssignGroup 0x4544 -#define ServerOP_CZTaskAssignRaid 0x4545 -#define ServerOP_CZTaskAssignGuild 0x4546 -#define ServerOP_CZTaskDisablePlayer 0x4547 -#define ServerOP_CZTaskDisableGroup 0x4548 -#define ServerOP_CZTaskDisableRaid 0x4549 -#define ServerOP_CZTaskDisableGuild 0x4550 -#define ServerOP_CZTaskEnablePlayer 0x4551 -#define ServerOP_CZTaskEnableGroup 0x4552 -#define ServerOP_CZTaskEnableRaid 0x4553 -#define ServerOP_CZTaskEnableGuild 0x4554 -#define ServerOP_CZTaskFailPlayer 0x4555 -#define ServerOP_CZTaskFailGroup 0x4556 -#define ServerOP_CZTaskFailRaid 0x4557 -#define ServerOP_CZTaskFailGuild 0x4558 -#define ServerOP_CZTaskRemovePlayer 0x4559 -#define ServerOP_CZTaskRemoveGroup 0x4560 -#define ServerOP_CZTaskRemoveRaid 0x4561 -#define ServerOP_CZTaskRemoveGuild 0x4562 -#define ServerOP_CZClientMessageString 0x4563 +#define ServerOP_CZDialogueWindow 0x4500 +#define ServerOP_CZLDoNUpdate 0x4501 +#define ServerOP_CZMarquee 0x4502 +#define ServerOP_CZMessage 0x4503 +#define ServerOP_CZMove 0x4504 +#define ServerOP_CZSetEntityVariable 0x4505 +#define ServerOP_CZSignal 0x4506 +#define ServerOP_CZSpell 0x4507 +#define ServerOP_CZTaskUpdate 0x4508 +#define ServerOP_CZClientMessageString 0x4509 -#define ServerOP_WWAssignTask 0x4750 -#define ServerOP_WWCastSpell 0x4751 -#define ServerOP_WWCompleteActivity 0x4752 -#define ServerOP_WWDisableTask 0x4753 -#define ServerOP_WWEnableTask 0x4754 -#define ServerOP_WWFailTask 0x4755 -#define ServerOP_WWMarquee 0x4756 -#define ServerOP_WWMessage 0x4757 -#define ServerOP_WWMove 0x4758 -#define ServerOP_WWMoveInstance 0x4759 -#define ServerOP_WWRemoveSpell 0x4760 -#define ServerOP_WWRemoveTask 0x4761 -#define ServerOP_WWResetActivity 0x4762 -#define ServerOP_WWSetEntityVariableClient 0x4763 -#define ServerOP_WWSetEntityVariableNPC 0x4764 -#define ServerOP_WWSignalClient 0x4765 -#define ServerOP_WWSignalNPC 0x4766 -#define ServerOP_WWUpdateActivity 0x4767 +#define ServerOP_WWDialogueWindow 0x4750 +#define ServerOP_WWLDoNUpdate 0x4751 +#define ServerOP_WWMarquee 0x4752 +#define ServerOP_WWMessage 0x4753 +#define ServerOP_WWMove 0x4754 +#define ServerOP_WWSetEntityVariable 0x4755 +#define ServerOP_WWSignal 0x4756 +#define ServerOP_WWSpell 0x4757 +#define ServerOP_WWTaskUpdate 0x4758 /** * QueryServer @@ -319,8 +261,84 @@ #define ServerOP_QSSendQuery 0x5006 #define ServerOP_QSPlayerDropItem 0x5007 +enum { + CZUpdateType_Character, + CZUpdateType_Group, + CZUpdateType_Raid, + CZUpdateType_Guild, + CZUpdateType_Expedition, + CZUpdateType_ClientName, + CZUpdateType_NPC +}; + +enum { + CZLDoNUpdateSubtype_AddLoss, + CZLDoNUpdateSubtype_AddPoints, + CZLDoNUpdateSubtype_AddWin, + CZLDoNUpdateSubtype_RemoveLoss, + CZLDoNUpdateSubtype_RemoveWin, +}; + +enum { + CZMoveUpdateSubtype_MoveZone, + CZMoveUpdateSubtype_MoveZoneInstance +}; + +enum { + CZSpellUpdateSubtype_Cast, + CZSpellUpdateSubtype_Remove +}; + +enum { + CZTaskUpdateSubtype_ActivityReset, + CZTaskUpdateSubtype_ActivityUpdate, + CZTaskUpdateSubtype_AssignTask, + CZTaskUpdateSubtype_DisableTask, + CZTaskUpdateSubtype_EnableTask, + CZTaskUpdateSubtype_FailTask, + CZTaskUpdateSubtype_RemoveTask +}; + +enum { + WWLDoNUpdateType_AddLoss, + WWLDoNUpdateType_AddPoints, + WWLDoNUpdateType_AddWin, + WWLDoNUpdateType_RemoveLoss, + WWLDoNUpdateType_RemoveWin +}; + +enum { + WWMoveUpdateType_MoveZone, + WWMoveUpdateType_MoveZoneInstance +}; + +enum { + WWSetEntityVariableUpdateType_Character, + WWSetEntityVariableUpdateType_NPC +}; + +enum { + WWSignalUpdateType_Character, + WWSignalUpdateType_NPC +}; + +enum { + WWSpellUpdateType_Cast, + WWSpellUpdateType_Remove +}; + +enum { + WWTaskUpdateType_ActivityReset, + WWTaskUpdateType_ActivityUpdate, + WWTaskUpdateType_AssignTask, + WWTaskUpdateType_DisableTask, + WWTaskUpdateType_EnableTask, + WWTaskUpdateType_FailTask, + WWTaskUpdateType_RemoveTask +}; + /* Query Serv Generic Packet Flag/Type Enumeration */ -enum { QSG_LFGuild = 0 }; +enum { QSG_LFGuild = 0 }; enum { QSG_LFGuild_PlayerMatches = 0, QSG_LFGuild_UpdatePlayerInfo, QSG_LFGuild_RequestPlayerInfo, QSG_LFGuild_UpdateGuildInfo, QSG_LFGuild_GuildMatches, QSG_LFGuild_RequestGuildInfo }; @@ -380,7 +398,10 @@ public: } void WriteUInt8(uint8 value) { *(uint8 *)(pBuffer + _wpos) = value; _wpos += sizeof(uint8); } + void WriteInt8(uint8_t value) { *(uint8_t *)(pBuffer + _wpos) = value; _wpos += sizeof(uint8_t); } void WriteUInt32(uint32 value) { *(uint32 *)(pBuffer + _wpos) = value; _wpos += sizeof(uint32); } + void WriteInt32(int32_t value) { *(int32_t *)(pBuffer + _wpos) = value; _wpos += sizeof(int32_t); } + void WriteString(const char * str) { uint32 len = static_cast(strlen(str)) + 1; memcpy(pBuffer + _wpos, str, len); _wpos += len; } uint8 ReadUInt8() { uint8 value = *(uint8 *)(pBuffer + _rpos); _rpos += sizeof(uint8); return value; } @@ -1418,481 +1439,107 @@ struct QSGeneralQuery_Struct { char QueryString[0]; }; -struct CZCastSpellPlayer_Struct { - int character_id; - uint32 spell_id; -}; - -struct CZCastSpellGroup_Struct { - int group_id; - uint32 spell_id; -}; - -struct CZCastSpellRaid_Struct { - int raid_id; - uint32 spell_id; -}; - -struct CZCastSpellGuild_Struct { - int guild_id; - uint32 spell_id; -}; - -struct CZClientSignal_Struct { - int character_id; - uint32 signal; -}; - -struct CZGroupSignal_Struct { - int group_id; - uint32 signal; -}; - -struct CZRaidSignal_Struct { - int raid_id; - uint32 signal; -}; - -struct CZGuildSignal_Struct { - int guild_id; - uint32 signal; -}; - -struct CZNPCSignal_Struct { - uint32 npctype_id; - uint32 signal; -}; - struct CZClientMessageString_Struct { uint32 string_id; uint16 chat_type; - char character_name[64]; + char client_name[64]; uint32 args_size; char args[1]; // null delimited }; -struct CZClientSignalByName_Struct { - char character_name[64]; +struct CZDialogueWindow_Struct { + uint8 update_type; // 0 - Character, 1 - Group, 2 - Raid, 3 - Guild, 4 - Expedition, 5 - Character Name + int update_identifier; // Character ID, Group ID, Raid ID, Guild ID, or Expedition ID based on update type, 0 for Character Name + char message[4096]; + char client_name[64]; // Only used by Character Name Type, else empty +}; + +struct CZLDoNUpdate_Struct { + uint8 update_type; // 0 - Character, 1 - Group, 2 - Raid, 3 - Guild, 4 - Expedition, 5 - Character Name + uint8 update_subtype; // 0 - Loss, 1 - Points, 2 - Win + int update_identifier; // Character ID, Group ID, Raid ID, Guild ID, or Expedition ID based on update type, 0 for Character Name + uint32 theme_id; + int points; // Only used in Points Subtype, else 1 + char client_name[64]; // Only used by Character Name Type, else empty +}; + +struct CZMarquee_Struct { + uint8 update_type; // 0 - Character, 1 - Group, 2 - Raid, 3 - Guild, 4 - Expedition, 5 - Character Name + int update_identifier; // Character ID, Group ID, Raid ID, Guild ID, or Expedition ID based on update type, 0 for Character Name + uint32 type; + uint32 priority; + uint32 fade_in; + uint32 fade_out; + uint32 duration; + char message[512]; + char client_name[64]; // Only used by Character Name Type, else empty +}; + +struct CZMessage_Struct { + uint8 update_type; // 0 - Character, 1 - Group, 2 - Raid, 3 - Guild, 4 - Expedition, 5 - Character Name + int update_identifier; // Group ID, Raid ID, Guild ID, or Expedition ID based on update type, 0 for Character Name + uint32 type; + char message[512]; + char client_name[64]; // Only used by Character Name Type, else empty +}; + +struct CZMove_Struct { + uint8 update_type; // 0 - Character, 1 - Group, 2 - Raid, 3 - Guild, 4 - Expedition, 5 - Character Name + uint8 update_subtype; // 0 - Move Zone, 1 - Move Zone Instance + int update_identifier; // Character ID, Group ID, Raid ID, Guild ID, or Expedition ID based on update type, 0 for Character Name + uint16 instance_id; // Only used by Move Zone Instance, else 0 + char zone_short_name[32]; // Only by with Move Zone, else empty + char client_name[64]; // Only used by Character Name Type, else empty +}; + +struct CZSetEntityVariable_Struct { + uint8 update_type; // 0 - Character, 1 - Group, 2 - Raid, 3 - Guild, 4 - Expedition, 5 - Character Name, 6 - NPC + int update_identifier; // Group ID, Raid ID, Guild ID, Expedition ID, or NPC ID based on update type, 0 for Character Name + char variable_name[256]; + char variable_value[256]; + char client_name[64]; // Only used by Character Type, else empty +}; + +struct CZSignal_Struct { + uint8 update_type; // 0 - Character, 1 - Group, 2 - Raid, 3 - Guild, 4 - Expedition, 5 - Character Name, 6 - NPC + int update_identifier; // Character ID, Group ID, Raid ID, Guild ID, Expedition ID, or NPC ID based on update type, 0 for Character Name uint32 signal; + char client_name[64]; // Only used by Character Name Type, else empty }; -struct CZCompleteActivityPlayer_Struct { - int character_id; - uint32 task_id; - int activity_id; -}; - -struct CZCompleteActivityGroup_Struct { - int group_id; - uint32 task_id; - int activity_id; -}; - -struct CZCompleteActivityRaid_Struct { - int raid_id; - uint32 task_id; - int activity_id; -}; - -struct CZCompleteActivityGuild_Struct { - int guild_id; - uint32 task_id; - int activity_id; -}; - -struct CZMovePlayer_Struct { - int character_id; - char zone_short_name[32]; -}; - -struct CZMarqueePlayer_Struct { - int character_id; - uint32 type; - uint32 priority; - uint32 fade_in; - uint32 fade_out; - uint32 duration; - char message[512]; -}; - -struct CZMarqueeGroup_Struct { - int group_id; - uint32 type; - uint32 priority; - uint32 fade_in; - uint32 fade_out; - uint32 duration; - char message[512]; -}; - -struct CZMarqueeRaid_Struct { - int raid_id; - uint32 type; - uint32 priority; - uint32 fade_in; - uint32 fade_out; - uint32 duration; - char message[512]; -}; - -struct CZMarqueeGuild_Struct { - int guild_id; - uint32 type; - uint32 priority; - uint32 fade_in; - uint32 fade_out; - uint32 duration; - char message[512]; -}; - -struct CZMessagePlayer_Struct { - uint32 type; - char character_name[64]; - char message[512]; -}; - -struct CZMessageGroup_Struct { - uint32 type; - int group_id; - char message[512]; -}; - -struct CZMessageRaid_Struct { - uint32 type; - int raid_id; - char message[512]; -}; - -struct CZMessageGuild_Struct { - uint32 type; - int guild_id; - char message[512]; -}; - -struct CZMoveGroup_Struct { - int group_id; - char zone_short_name[32]; -}; - -struct CZMoveRaid_Struct { - int raid_id; - char zone_short_name[32]; -}; - -struct CZMoveGuild_Struct { - int guild_id; - char zone_short_name[32]; -}; - -struct CZMoveInstancePlayer_Struct { - int character_id; - uint16 instance_id; -}; - -struct CZMoveInstanceGroup_Struct { - int group_id; - uint16 instance_id; -}; - -struct CZMoveInstanceRaid_Struct { - int raid_id; - uint16 instance_id; -}; - -struct CZMoveInstanceGuild_Struct { - int guild_id; - uint16 instance_id; -}; - -struct CZRemoveSpellPlayer_Struct { - int character_id; +struct CZSpell_Struct { + uint8 update_type; // 0 - Character, 1 - Group, 2 - Raid, 3 - Guild, 4 - Expedition, 5 - Character Name + uint8 update_subtype; // 0 - Cast Spell, 1 - Remove Spell + int update_identifier; // Character ID, Group ID, Raid ID, Guild ID, or Expedition ID based on update type, 0 for Character Name uint32 spell_id; + char client_name[64]; // Only used by Character Name Type, else empty }; -struct CZRemoveSpellGroup_Struct { - int group_id; - uint32 spell_id; +struct CZTaskUpdate_Struct { + uint8 update_type; // 0 - Character, 1 - Group, 2 - Raid, 3 - Guild, 4 - Expedition, 5 - Character Name + uint8 update_subtype; // 0 - Activity Reset, 1 - Activity Update, 2 - Assign Task, 3 - Disable Task, 4 - Enable Task, 5 - Fail Task, 6 - Remove Task + int update_identifier; // Character ID, Group ID, Raid ID, Guild ID, or Expedition ID based on update type, 0 for Character Name + uint32 task_identifier; + int task_subidentifier; // Activity ID for Activity Reset and Activity Update, NPC Entity ID for Assign Task, else -1 + int update_count; // Only used by Activity Update, else 1 + bool enforce_level_requirement; // Only used by Assign Task + char client_name[64]; // Only used by Character Name Type, else empty }; -struct CZRemoveSpellRaid_Struct { - int raid_id; - uint32 spell_id; -}; - -struct CZRemoveSpellGuild_Struct { - int guild_id; - uint32 spell_id; -}; - -struct CZRemoveTaskPlayer_Struct { - int character_id; - uint32 task_id; -}; - -struct CZRemoveTaskGroup_Struct { - int group_id; - uint32 task_id; -}; - -struct CZRemoveTaskRaid_Struct { - int raid_id; - uint32 task_id; -}; - -struct CZRemoveTaskGuild_Struct { - int guild_id; - uint32 task_id; -}; - -struct CZResetActivityPlayer_Struct { - int character_id; - uint32 task_id; - int activity_id; -}; - -struct CZResetActivityGroup_Struct { - int group_id; - uint32 task_id; - int activity_id; -}; - -struct CZResetActivityRaid_Struct { - int raid_id; - uint32 task_id; - int activity_id; -}; - -struct CZResetActivityGuild_Struct { - int guild_id; - uint32 task_id; - int activity_id; -}; - -struct CZSetEntVarByNPCTypeID_Struct { - uint32 npctype_id; - char variable_name[256]; - char variable_value[256]; -}; - -struct CZSetEntVarByClientName_Struct { - char character_name[64]; - char variable_name[256]; - char variable_value[256]; -}; - -struct CZSetEntVarByGroupID_Struct { - int group_id; - char variable_name[256]; - char variable_value[256]; -}; - -struct CZSetEntVarByRaidID_Struct { - int raid_id; - char variable_name[256]; - char variable_value[256]; -}; - -struct CZSetEntVarByGuildID_Struct { - int guild_id; - char variable_name[256]; - char variable_value[256]; -}; - -struct CZTaskActivityResetPlayer_Struct { - int character_id; - uint32 task_id; - int activity_id; -}; - -struct CZTaskActivityResetGroup_Struct { - int group_id; - uint32 task_id; - int activity_id; -}; - -struct CZTaskActivityResetRaid_Struct { - int raid_id; - uint32 task_id; - int activity_id; -}; - -struct CZTaskActivityResetGuild_Struct { - int guild_id; - uint32 task_id; - int activity_id; -}; - -struct CZTaskActivityUpdatePlayer_Struct { - int character_id; - uint32 task_id; - int activity_id; - int activity_count; -}; - -struct CZTaskActivityUpdateGroup_Struct { - int group_id; - uint32 task_id; - int activity_id; - int activity_count; -}; - -struct CZTaskActivityUpdateRaid_Struct { - int raid_id; - uint32 task_id; - int activity_id; - int activity_count; -}; - -struct CZTaskActivityUpdateGuild_Struct { - int guild_id; - uint32 task_id; - int activity_id; - int activity_count; -}; - -struct CZTaskAssignPlayer_Struct { - uint16 npc_entity_id; - int character_id; - uint32 task_id; - bool enforce_level_requirement; -}; - -struct CZTaskAssignGroup_Struct { - uint16 npc_entity_id; - int group_id; - uint32 task_id; - bool enforce_level_requirement; -}; - -struct CZTaskAssignRaid_Struct { - uint16 npc_entity_id; - int raid_id; - uint32 task_id; - bool enforce_level_requirement; -}; - -struct CZTaskAssignGuild_Struct { - uint16 npc_entity_id; - int guild_id; - uint32 task_id; - bool enforce_level_requirement; -}; - -struct CZTaskDisablePlayer_Struct { - int character_id; - uint32 task_id; -}; - -struct CZTaskDisableGroup_Struct { - int group_id; - uint32 task_id; -}; - -struct CZTaskDisableRaid_Struct { - int raid_id; - uint32 task_id; -}; - -struct CZTaskDisableGuild_Struct { - int guild_id; - uint32 task_id; -}; - -struct CZTaskEnablePlayer_Struct { - int character_id; - uint32 task_id; -}; - -struct CZTaskEnableGroup_Struct { - int group_id; - uint32 task_id; -}; - -struct CZTaskEnableRaid_Struct { - int raid_id; - uint32 task_id; -}; - -struct CZTaskEnableGuild_Struct { - int guild_id; - uint32 task_id; -}; - -struct CZTaskFailPlayer_Struct { - int character_id; - uint32 task_id; -}; - -struct CZTaskFailGroup_Struct { - int group_id; - uint32 task_id; -}; - -struct CZTaskFailRaid_Struct { - int raid_id; - uint32 task_id; -}; - -struct CZTaskFailGuild_Struct { - int guild_id; - uint32 task_id; -}; - -struct CZTaskRemovePlayer_Struct { - uint16 npc_entity_id; - int character_id; - uint32 task_id; -}; - -struct CZTaskRemoveGroup_Struct { - uint16 npc_entity_id; - int group_id; - uint32 task_id; -}; - -struct CZTaskRemoveRaid_Struct { - uint16 npc_entity_id; - int raid_id; - uint32 task_id; -}; - -struct CZTaskRemoveGuild_Struct { - uint16 npc_entity_id; - int guild_id; - uint32 task_id; -}; - -struct WWAssignTask_Struct { - uint16 npc_entity_id; - uint32 task_id; - bool enforce_level_requirement; +struct WWDialogueWindow_Struct { + char message[4096]; uint8 min_status; uint8 max_status; }; -struct WWCastSpell_Struct { - uint32 spell_id; +struct WWLDoNUpdate_Struct { + uint8 update_type; // 0 - Loss, 1 - Points, 2 - Win + uint32 theme_id; + int points; // Only used in Points Subtype, else 1 uint8 min_status; uint8 max_status; }; -struct WWDisableTask_Struct { - uint32 task_id; - uint8 min_status; - uint8 max_status; -}; - -struct WWEnableTask_Struct { - uint32 task_id; - uint8 min_status; - uint8 max_status; -}; - -struct WWFailTask_Struct { - uint32 task_id; - uint8 min_status; - uint8 max_status; -}; struct WWMarquee_Struct { uint32 type; uint32 priority; @@ -1912,63 +1559,41 @@ struct WWMessage_Struct { }; struct WWMove_Struct { - char zone_short_name[32]; + uint8 update_type; // 0 - Move Zone, 1 - Move Zone Instance + char zone_short_name[32]; // Used with Move Zone + uint16 instance_id; // Used with Move Zone Instance uint8 min_status; uint8 max_status; }; -struct WWMoveInstance_Struct { - uint16 instance_id; +struct WWSetEntityVariable_Struct { + uint8 update_type; // 0 - Character, 1 - NPC + char variable_name[256]; + char variable_value[256]; uint8 min_status; uint8 max_status; }; -struct WWRemoveSpell_Struct { +struct WWSignal_Struct { + uint8 update_type; // 0 - Character, 1 - NPC + uint32 signal; + uint8 min_status; + uint8 max_status; +}; + +struct WWSpell_Struct { + uint8 update_type; // 0 - Cast Spell, 1 - Remove Spell uint32 spell_id; uint8 min_status; uint8 max_status; }; -struct WWRemoveTask_Struct { - uint32 task_id; - uint8 min_status; - uint8 max_status; - -}; - -struct WWResetActivity_Struct { - uint32 task_id; - int activity_id; - uint8 min_status; - uint8 max_status; -}; - -struct WWSetEntVarClient_Struct { - char variable_name[256]; - char variable_value[256]; - uint8 min_status; - uint8 max_status; -}; - -struct WWSetEntVarNPC_Struct { - char variable_name[256]; - char variable_value[256]; -}; - -struct WWSignalClient_Struct { - uint32 signal; - uint8 min_status; - uint8 max_status; -}; - -struct WWSignalNPC_Struct { - uint32 signal; -}; - -struct WWUpdateActivity_Struct { - uint32 task_id; - int activity_id; - int activity_count; +struct WWTaskUpdate_Struct { + uint8 update_type; // 0 - Activity Reset, 1 - Activity Update, 2 - Assign Task, 3 - Disable Task, 4 - Enable Task, 5 - Fail Task, 6 - Remove Task + uint32 task_identifier; + int task_subidentifier; // Activity ID for Activity Reset and Activity Update, NPC Entity ID for Assign Task, else -1 + int update_count; // Update Count for Activity Update, else 1 + bool enforce_level_requirement; // Only used by Assign Task, else false uint8 min_status; uint8 max_status; }; @@ -2002,51 +1627,28 @@ struct ServerExpeditionID_Struct { uint32 sender_instance_id; }; -struct ServerExpeditionLeaderID_Struct { - uint32 expedition_id; +struct ServerDzLeaderID_Struct { + uint32 dz_id; uint32 leader_id; }; -struct ServerExpeditionMemberChange_Struct { - uint32 expedition_id; - uint32 sender_zone_id; - uint16 sender_instance_id; - uint8 removed; // 0: added, 1: removed - uint32 char_id; - char char_name[64]; -}; - -struct ServerExpeditionMemberSwap_Struct { - uint32 expedition_id; - uint32 sender_zone_id; - uint16 sender_instance_id; - uint32 add_char_id; - uint32 remove_char_id; - char add_char_name[64]; - char remove_char_name[64]; -}; - -struct ServerExpeditionMemberStatus_Struct { - uint32 expedition_id; +struct ServerDzMemberStatus_Struct { + uint32 dz_id; uint32 sender_zone_id; uint16 sender_instance_id; uint8 status; // 0: unknown 1: Online 2: Offline 3: In Dynamic Zone 4: Link Dead uint32 character_id; }; -struct ServerExpeditionCharacterEntry_Struct { - uint32 expedition_id; +struct ServerDzMemberStatusEntry_Struct { uint32 character_id; - uint32 character_zone_id; - uint16 character_instance_id; - uint8 character_online; // 0: offline 1: online + uint8 online_status; // 0: unknown 1: Online 2: Offline 3: In Dynamic Zone 4: Link Dead }; -struct ServerExpeditionCharacters_Struct { - uint32 sender_zone_id; - uint16 sender_instance_id; +struct ServerDzMemberStatuses_Struct { + uint32 dz_id; uint32 count; - ServerExpeditionCharacterEntry_Struct entries[0]; + ServerDzMemberStatusEntry_Struct entries[0]; }; struct ServerExpeditionLockout_Struct { @@ -2090,13 +1692,8 @@ struct ServerExpeditionCharacterID_Struct { uint32_t character_id; }; -struct ServerExpeditionUpdateDuration_Struct { - uint32_t expedition_id; - uint32_t new_duration_seconds; -}; - -struct ServerExpeditionExpireWarning_Struct { - uint32_t expedition_id; +struct ServerDzExpireWarning_Struct { + uint32_t dz_id; uint32_t minutes_remaining; }; @@ -2109,31 +1706,67 @@ struct ServerDzCommand_Struct { }; struct ServerDzCommandMakeLeader_Struct { - uint32 expedition_id; + uint32 dz_id; uint32 requester_id; uint8 is_online; // set by world, 0: new leader name offline, 1: online uint8 is_success; // set by world, 0: makeleader failed, 1: success (is online member) char new_leader_name[64]; }; -struct ServerDzLocation_Struct { - uint32 owner_id; // system associated with the dz (expedition, shared task, etc) +struct ServerDzID_Struct { + uint32 dz_id; uint16 dz_zone_id; - uint16 dz_instance_id; + uint16 dz_instance_id; // for cache-independent redundancy (messages to dz's instance) uint32 sender_zone_id; uint16 sender_instance_id; - uint32 zone_id; // compass or safereturn zone id +}; + +struct ServerDzLocation_Struct { + uint32 dz_id; + uint32 sender_zone_id; + uint16 sender_instance_id; + uint32 zone_id; float y; float x; float z; float heading; }; -struct ServerDzCharacter_Struct { - uint16 zone_id; - uint16 instance_id; - uint8 remove; // 0: added 1: removed +struct ServerDzMember_Struct { + uint32 dz_id; + uint16 dz_zone_id; + uint16 dz_instance_id; // for cache redundancy + uint16 sender_zone_id; + uint16 sender_instance_id; + uint8 removed; // 0: added, 1: removed uint32 character_id; + uint8 character_status; // 0: unknown 1: Online 2: Offline 3: In Dynamic Zone 4: Link Dead + char character_name[64]; +}; + +struct ServerDzMemberSwap_Struct { + uint32 dz_id; + uint16 dz_zone_id; + uint16 dz_instance_id; // for cache redundancy + uint16 sender_zone_id; + uint16 sender_instance_id; + uint32 add_character_id; + uint32 remove_character_id; + uint8 add_character_status; + char add_character_name[64]; + char remove_character_name[64]; +}; + +struct ServerDzSetDuration_Struct { + uint32 dz_id; + uint32 seconds; +}; + +struct ServerDzCreateSerialized_Struct { + uint16_t origin_zone_id; + uint16_t origin_instance_id; + uint32_t cereal_size; + char cereal_data[0]; }; #pragma pack() diff --git a/common/shared_tasks.cpp b/common/shared_tasks.cpp new file mode 100644 index 000000000..18ffbfe78 --- /dev/null +++ b/common/shared_tasks.cpp @@ -0,0 +1,139 @@ +#include "shared_tasks.h" +#include "repositories/character_data_repository.h" +#include + +std::vector SharedTask::GetActivityState() const +{ + return m_shared_task_activity_state; +} + +std::vector SharedTask::GetMembers() const +{ + return m_members; +} + +void SharedTask::SetSharedTaskActivityState(const std::vector &activity_state) +{ + SharedTask::m_shared_task_activity_state = activity_state; +} + +void SharedTask::SetTaskData(const TasksRepository::Tasks &task_data) +{ + SharedTask::m_task_data = task_data; +} + +void SharedTask::SetTaskActivityData(const std::vector &task_activity_data) +{ + SharedTask::m_task_activity_data = task_activity_data; +} + +const TasksRepository::Tasks &SharedTask::GetTaskData() const +{ + return m_task_data; +} + +const std::vector &SharedTask::GetTaskActivityData() const +{ + return m_task_activity_data; +} + +void SharedTask::SetMembers(const std::vector &members) +{ + SharedTask::m_members = members; +} + +const SharedTasksRepository::SharedTasks &SharedTask::GetDbSharedTask() const +{ + return m_db_shared_task; +} + +void SharedTask::SetDbSharedTask(const SharedTasksRepository::SharedTasks &m_db_shared_task) +{ + SharedTask::m_db_shared_task = m_db_shared_task; +} + +SharedTaskRequestCharacters SharedTask::GetRequestCharacters(Database &db, uint32_t requested_character_id) +{ + SharedTaskRequestCharacters request{}; + + request.group_type = SharedTaskRequestGroupType::Group; + request.characters = CharacterDataRepository::GetWhere( + db, fmt::format( + "id IN (select charid from group_id where groupid = (select groupid from group_id where charid = {}))", + requested_character_id + ) + ); + + if (request.characters.empty()) { + request.group_type = SharedTaskRequestGroupType::Raid; + request.characters = CharacterDataRepository::GetWhere( + db, fmt::format( + "id IN (select charid from raid_members where raidid = (select raidid from raid_members where charid = {}))", + requested_character_id + ) + ); + } + + if (request.characters.empty()) // solo request + { + request.group_type = SharedTaskRequestGroupType::Solo; + request.characters = CharacterDataRepository::GetWhere( + db, fmt::format( + "id = {} LIMIT 1", + requested_character_id + ) + ); + } + + request.lowest_level = std::numeric_limits::max(); + request.highest_level = 0; + for (const auto &character: request.characters) { + request.lowest_level = std::min(request.lowest_level, character.level); + request.highest_level = std::max(request.highest_level, character.level); + request.character_ids.emplace_back(character.id); // convenience + } + + return request; +} + +void SharedTask::AddCharacterToMemberHistory(uint32_t character_id) +{ + auto it = std::find(member_id_history.begin(), member_id_history.end(), character_id); + if (it == member_id_history.end()) { + member_id_history.emplace_back(character_id); + } +} + +SharedTaskMember SharedTask::FindMemberFromCharacterID(uint32_t character_id) const +{ + auto it = std::find_if( + m_members.begin(), m_members.end(), + [&](const SharedTaskMember &member) { + return member.character_id == character_id; + } + ); + + return it != m_members.end() ? *it : SharedTaskMember{}; +} + +SharedTaskMember SharedTask::FindMemberFromCharacterName(const std::string &character_name) const +{ + auto it = std::find_if( + m_members.begin(), m_members.end(), + [&](const SharedTaskMember &member) { + return strcasecmp(member.character_name.c_str(), character_name.c_str()) == 0; + } + ); + + return it != m_members.end() ? *it : SharedTaskMember{}; +} + +SharedTaskMember SharedTask::GetLeader() const +{ + for (const auto &member : m_members) { + if (member.is_leader) { + return member; + } + } + return {}; +} diff --git a/common/shared_tasks.h b/common/shared_tasks.h new file mode 100644 index 000000000..2a2363ffd --- /dev/null +++ b/common/shared_tasks.h @@ -0,0 +1,204 @@ +#ifndef EQEMU_SHARED_TASKS_H +#define EQEMU_SHARED_TASKS_H + +#include "database.h" +#include "types.h" +#include "repositories/character_data_repository.h" +#include "repositories/tasks_repository.h" +#include "repositories/task_activities_repository.h" +#include "repositories/shared_tasks_repository.h" +#include +#include +#include + +// ops +#define ServerOP_SharedTaskRequest 0x0300 // zone -> world. Player trying to get task. Relayed world -> zone on confirmation +#define ServerOP_SharedTaskAddPlayer 0x0301 // bidirectional. /taskaddplayer request zone -> world. success world -> zone +#define ServerOP_SharedTaskMakeLeader 0x0302 // zone -> world -> zone +#define ServerOP_SharedTaskRemovePlayer 0x0303 // zone -> world -> zone +#define ServerOP_SharedTaskAttemptRemove 0x0304 // zone -> world. Player trying to delete task +#define ServerOP_SharedTaskUpdate 0x0305 // zone -> world. Client sending task update to world. Relayed world -> zone on confirmation +#define ServerOP_SharedTaskMemberlist 0x0306 // world -> zone. Send shared task memberlist +#define ServerOP_SharedTaskRequestMemberlist 0x0307 // zone -> world. Send shared task memberlist (zone in initial for now, could change) +#define ServerOP_SharedTaskAcceptNewTask 0x0308 // world -> zone. World verified, continue AcceptNewTask +#define ServerOP_SharedTaskInvitePlayer 0x0309 // world -> zone. Sends task invite to player +#define ServerOP_SharedTaskInviteAcceptedPlayer 0x0310 // zone -> world. Confirming task invite +#define ServerOP_SharedTaskCreateDynamicZone 0x0311 // zone -> world +#define ServerOP_SharedTaskPurgeAllCommand 0x0312 // zone -> world +#define ServerOP_SharedTaskPlayerList 0x0313 // zone -> world /taskplayerlist command +#define ServerOP_SharedTaskMemberChange 0x0314 // world -> zone. Send shared task single member added/removed (client also handles message) +#define ServerOP_SharedTaskKickPlayers 0x0315 // zone -> world /kickplayers task + +enum class SharedTaskRequestGroupType { + Solo = 0, + Group, + Raid +}; + +// used in +// ServerOP_SharedTaskRequest + +// ServerOP_SharedTaskAcceptNewTask +struct ServerSharedTaskRequest_Struct { + uint32 requested_character_id; + uint32 requested_task_id; + uint32 requested_npc_type_id; // original task logic passthrough + uint32 accept_time; +}; + +// ServerOP_SharedTaskInvitePlayer +struct ServerSharedTaskInvitePlayer_Struct { + uint32 requested_character_id; + uint32 invite_shared_task_id; + char task_name[64]; + char inviter_name[64]; +}; + +// ServerOP_SharedTaskAttemptRemove +// gets re-used when sent back to clients +struct ServerSharedTaskAttemptRemove_Struct { + uint32 requested_character_id; + uint32 requested_task_id; + bool remove_from_db; +}; + +// used in the shared task request process (currently) +struct SharedTaskMember { + uint32 character_id = 0; + std::string character_name; + bool is_leader = false; + + template + void serialize(Archive& archive) + { + archive(character_id, character_name, is_leader); + } +}; + +// used in shared task requests to validate group/raid members +struct SharedTaskRequestCharacters { + int lowest_level; + int highest_level; + SharedTaskRequestGroupType group_type; + std::vector character_ids; + std::vector characters; +}; + +// ServerOP_SharedTaskMemberlist +// builds the buffer and sends to clients directly +struct ServerSharedTaskMemberListPacket_Struct { + uint32 destination_character_id; + uint32 cereal_size; + char cereal_serialized_members[0]; // serialized member list using cereal +}; + +struct ServerSharedTaskMemberChangePacket_Struct { + uint32 destination_character_id; + uint32 shared_task_id; + bool removed; + char player_name[64]; +}; + +struct SharedTaskActivityStateEntry { + uint32 activity_id; + uint32 done_count; + uint32 max_done_count; // goalcount + uint32 updated_time; + uint32 completed_time; +}; + +struct ServerSharedTaskActivityUpdate_Struct { + uint32 source_character_id; + uint32 task_id; + uint32 activity_id; + uint32 done_count; + bool ignore_quest_update; +}; + +struct ServerSharedTaskRequestMemberlist_Struct { + uint32 source_character_id; + uint32 task_id; +}; + +struct ServerSharedTaskRemovePlayer_Struct { + uint32 source_character_id; + uint32 task_id; + char player_name[64]; +}; + +struct ServerSharedTaskAddPlayer_Struct { + uint32 source_character_id; + uint32 task_id; + char player_name[64]; +}; + +struct ServerSharedTaskMakeLeader_Struct { + uint32 source_character_id; + uint32 task_id; + char player_name[64]; +}; + +struct ServerSharedTaskInviteAccepted_Struct { + uint32 source_character_id; + uint32 shared_task_id; + bool accepted; + char player_name[64]; +}; + +struct ServerSharedTaskCreateDynamicZone_Struct { + uint32 source_character_id; + uint32 task_id; + uint32 cereal_size; + char cereal_data[0]; // serialized dz with creation parameters +}; + +struct ServerSharedTaskPlayerList_Struct { + uint32 source_character_id; + uint32 task_id; +}; + +struct ServerSharedTaskKickPlayers_Struct { + uint32 source_character_id; + uint32 task_id; +}; + +class SharedTask { +public: + // used in both zone and world validation + static SharedTaskRequestCharacters GetRequestCharacters(Database& db, uint32_t requested_character_id); + + void AddCharacterToMemberHistory(uint32_t character_id); + SharedTaskMember FindMemberFromCharacterID(uint32_t character_id) const; + SharedTaskMember FindMemberFromCharacterName(const std::string& character_name) const; + SharedTaskMember GetLeader() const; + std::vector GetActivityState() const; + std::vector GetMembers() const; + + // getters + const std::vector &GetTaskActivityData() const; + const TasksRepository::Tasks &GetTaskData() const; + + // setters + void SetMembers(const std::vector &members); + void SetSharedTaskActivityState(const std::vector &activity_state); + void SetTaskActivityData(const std::vector &task_activity_data); + void SetTaskData(const TasksRepository::Tasks &task_data); + + // active record of database shared task + const SharedTasksRepository::SharedTasks &GetDbSharedTask() const; + void SetDbSharedTask(const SharedTasksRepository::SharedTasks &m_db_shared_task); + + std::vector m_shared_task_activity_state; + std::vector m_members; + std::vector member_id_history; // past and present members for replay timers + std::vector dynamic_zone_ids; + +protected: + SharedTasksRepository::SharedTasks m_db_shared_task; + + // reference to task data (only for this shared task) + TasksRepository::Tasks m_task_data; + std::vector m_task_activity_data; +}; + +#endif //EQEMU_SHARED_TASKS_H diff --git a/common/shareddb.cpp b/common/shareddb.cpp index 6f2af55a3..7f913edf3 100644 --- a/common/shareddb.cpp +++ b/common/shareddb.cpp @@ -501,7 +501,7 @@ bool SharedDatabase::GetSharedBank(uint32 id, EQ::InventoryProfile *inv, bool is for (auto row = results.begin(); row != results.end(); ++row) { int16 slot_id = (int16)atoi(row[0]); uint32 item_id = (uint32)atoi(row[1]); - int8 charges = (int8)atoi(row[2]); + int16 charges = (int16)atoi(row[2]); uint32 aug[EQ::invaug::SOCKET_COUNT]; aug[0] = (uint32)atoi(row[3]); @@ -1118,7 +1118,7 @@ void SharedDatabase::LoadItems(void *data, uint32 size, int32 items, uint32 max_ item.Haste = (uint32)atoul(row[ItemField::haste]); item.DamageShield = (uint32)atoul(row[ItemField::damageshield]); item.RecastDelay = (uint32)atoul(row[ItemField::recastdelay]); - item.RecastType = (uint32)atoul(row[ItemField::recasttype]); + item.RecastType = (int)atoi(row[ItemField::recasttype]); item.GuildFavor = (uint32)atoul(row[ItemField::guildfavor]); item.AugDistiller = (uint32)atoul(row[ItemField::augdistiller]); item.Attuneable = (atoi(row[ItemField::attuneable]) == 0) ? false : true; @@ -1682,7 +1682,7 @@ void SharedDatabase::LoadDamageShieldTypes(SPDat_Spell_Struct* sp, int32 iMaxSpe for(auto row = results.begin(); row != results.end(); ++row) { int spellID = atoi(row[0]); if((spellID > 0) && (spellID <= iMaxSpellID)) - sp[spellID].DamageShieldType = atoi(row[1]); + sp[spellID].damage_shield_type = atoi(row[1]); } } @@ -1762,48 +1762,48 @@ void SharedDatabase::LoadSpells(void *data, int max_spells) { strn0cpy(sp[tempid].spell_fades, row[8], sizeof(sp[tempid].spell_fades)); sp[tempid].range=static_cast(atof(row[9])); - sp[tempid].aoerange=static_cast(atof(row[10])); - sp[tempid].pushback=static_cast(atof(row[11])); - sp[tempid].pushup=static_cast(atof(row[12])); + sp[tempid].aoe_range=static_cast(atof(row[10])); + sp[tempid].push_back=static_cast(atof(row[11])); + sp[tempid].push_up=static_cast(atof(row[12])); sp[tempid].cast_time=atoi(row[13]); sp[tempid].recovery_time=atoi(row[14]); sp[tempid].recast_time=atoi(row[15]); - sp[tempid].buffdurationformula=atoi(row[16]); - sp[tempid].buffduration=atoi(row[17]); - sp[tempid].AEDuration=atoi(row[18]); + sp[tempid].buff_duration_formula=atoi(row[16]); + sp[tempid].buff_duration=atoi(row[17]); + sp[tempid].aoe_duration=atoi(row[18]); sp[tempid].mana=atoi(row[19]); int y=0; for(y=0; y< EFFECT_COUNT;y++) - sp[tempid].base[y]=atoi(row[20+y]); // effect_base_value + sp[tempid].base_value[y]=atoi(row[20+y]); // effect_base_value for(y=0; y < EFFECT_COUNT; y++) - sp[tempid].base2[y]=atoi(row[32+y]); // effect_limit_value + sp[tempid].limit_value[y]=atoi(row[32+y]); // effect_limit_value for(y=0; y< EFFECT_COUNT;y++) - sp[tempid].max[y]=atoi(row[44+y]); + sp[tempid].max_value[y]=atoi(row[44+y]); for(y=0; y< 4;y++) - sp[tempid].components[y]=atoi(row[58+y]); + sp[tempid].component[y]=atoi(row[58+y]); for(y=0; y< 4;y++) - sp[tempid].component_counts[y]=atoi(row[62+y]); + sp[tempid].component_count[y]=atoi(row[62+y]); for(y=0; y< 4;y++) - sp[tempid].NoexpendReagent[y]=atoi(row[66+y]); + sp[tempid].no_expend_reagent[y]=atoi(row[66+y]); for(y=0; y< EFFECT_COUNT;y++) sp[tempid].formula[y]=atoi(row[70+y]); - sp[tempid].goodEffect=atoi(row[83]); - sp[tempid].Activated=atoi(row[84]); - sp[tempid].resisttype=atoi(row[85]); + sp[tempid].good_effect=atoi(row[83]); + sp[tempid].activated=atoi(row[84]); + sp[tempid].resist_type=atoi(row[85]); for(y=0; y< EFFECT_COUNT;y++) - sp[tempid].effectid[y]=atoi(row[86+y]); + sp[tempid].effect_id[y]=atoi(row[86+y]); - sp[tempid].targettype = (SpellTargetType) atoi(row[98]); - sp[tempid].basediff=atoi(row[99]); + sp[tempid].target_type = (SpellTargetType) atoi(row[98]); + sp[tempid].base_difficulty=atoi(row[99]); int tmp_skill = atoi(row[100]);; @@ -1812,58 +1812,61 @@ void SharedDatabase::LoadSpells(void *data, int max_spells) { else sp[tempid].skill = (EQ::skills::SkillType) tmp_skill; - sp[tempid].zonetype=atoi(row[101]); - sp[tempid].EnvironmentType=atoi(row[102]); - sp[tempid].TimeOfDay=atoi(row[103]); + sp[tempid].zone_type=atoi(row[101]); + sp[tempid].environment_type=atoi(row[102]); + sp[tempid].time_of_day=atoi(row[103]); for(y=0; y < PLAYER_CLASS_COUNT;y++) sp[tempid].classes[y]=atoi(row[104+y]); - sp[tempid].CastingAnim=atoi(row[120]); - sp[tempid].SpellAffectIndex=atoi(row[123]); + sp[tempid].casting_animation=atoi(row[120]); + sp[tempid].spell_affect_index=atoi(row[123]); sp[tempid].disallow_sit=atoi(row[124]); - sp[tempid].diety_agnostic=atoi(row[125]); + sp[tempid].deity_agnostic=atoi(row[125]); for (y = 0; y < 16; y++) sp[tempid].deities[y]=atoi(row[126+y]); sp[tempid].new_icon=atoi(row[144]); sp[tempid].uninterruptable=atoi(row[146]) != 0; - sp[tempid].ResistDiff=atoi(row[147]); - sp[tempid].dot_stacking_exempt = atoi(row[148]) != 0; - sp[tempid].RecourseLink = atoi(row[150]); + sp[tempid].resist_difficulty=atoi(row[147]); + sp[tempid].unstackable_dot = atoi(row[148]) != 0; + sp[tempid].recourse_link = atoi(row[150]); sp[tempid].no_partial_resist = atoi(row[151]) != 0; sp[tempid].short_buff_box = atoi(row[154]); - sp[tempid].descnum = atoi(row[155]); - sp[tempid].typedescnum = atoi(row[156]); - sp[tempid].effectdescnum = atoi(row[157]); + sp[tempid].description_id = atoi(row[155]); + sp[tempid].type_description_id = atoi(row[156]); + sp[tempid].effect_description_id = atoi(row[157]); sp[tempid].npc_no_los = atoi(row[159]) != 0; + sp[tempid].feedbackable = atoi(row[160]) != 0; sp[tempid].reflectable = atoi(row[161]) != 0; - sp[tempid].bonushate=atoi(row[162]); + sp[tempid].bonus_hate=atoi(row[162]); sp[tempid].ldon_trap = atoi(row[165]) != 0; - sp[tempid].EndurCost=atoi(row[166]); - sp[tempid].EndurTimerIndex=atoi(row[167]); - sp[tempid].IsDisciplineBuff = atoi(row[168]) != 0; - sp[tempid].HateAdded=atoi(row[173]); - sp[tempid].EndurUpkeep=atoi(row[174]); - sp[tempid].numhitstype = atoi(row[175]); - sp[tempid].numhits = atoi(row[176]); - sp[tempid].pvpresistbase=atoi(row[177]); - sp[tempid].pvpresistcalc=atoi(row[178]); - sp[tempid].pvpresistcap=atoi(row[179]); + sp[tempid].endurance_cost=atoi(row[166]); + sp[tempid].timer_id=atoi(row[167]); + sp[tempid].is_discipline = atoi(row[168]) != 0; + sp[tempid].hate_added=atoi(row[173]); + sp[tempid].endurance_upkeep=atoi(row[174]); + sp[tempid].hit_number_type = atoi(row[175]); + sp[tempid].hit_number = atoi(row[176]); + sp[tempid].pvp_resist_base=atoi(row[177]); + sp[tempid].pvp_resist_per_level=atoi(row[178]); + sp[tempid].pvp_resist_cap=atoi(row[179]); sp[tempid].spell_category=atoi(row[180]); + sp[tempid].pvp_duration = atoi(row[181]); + sp[tempid].pvp_duration_cap = atoi(row[182]); sp[tempid].pcnpc_only_flag=atoi(row[183]); sp[tempid].cast_not_standing = atoi(row[184]) != 0; sp[tempid].can_mgb=atoi(row[185]); sp[tempid].dispel_flag = atoi(row[186]); - sp[tempid].MinResist = atoi(row[189]); - sp[tempid].MaxResist = atoi(row[190]); + sp[tempid].min_resist = atoi(row[189]); + sp[tempid].max_resist = atoi(row[190]); sp[tempid].viral_targets = atoi(row[191]); sp[tempid].viral_timer = atoi(row[192]); - sp[tempid].NimbusEffect = atoi(row[193]); + sp[tempid].nimbus_effect = atoi(row[193]); sp[tempid].directional_start = static_cast(atoi(row[194])); sp[tempid].directional_end = static_cast(atoi(row[195])); sp[tempid].sneak = atoi(row[196]) != 0; @@ -1871,26 +1874,29 @@ void SharedDatabase::LoadSpells(void *data, int max_spells) { sp[tempid].no_detrimental_spell_aggro = atoi(row[198]) != 0; sp[tempid].suspendable = atoi(row[200]) != 0; sp[tempid].viral_range = atoi(row[201]); - sp[tempid].songcap = atoi(row[202]); + sp[tempid].song_cap = atoi(row[202]); sp[tempid].no_block = atoi(row[205]); - sp[tempid].spellgroup=atoi(row[207]); + sp[tempid].spell_group=atoi(row[207]); sp[tempid].rank = atoi(row[208]); sp[tempid].no_resist=atoi(row[209]); - sp[tempid].CastRestriction = atoi(row[211]); - sp[tempid].AllowRest = atoi(row[212]) != 0; - sp[tempid].InCombat = atoi(row[213]) != 0; - sp[tempid].OutofCombat = atoi(row[214]) != 0; + sp[tempid].cast_restriction = atoi(row[211]); + sp[tempid].allow_rest = atoi(row[212]) != 0; + sp[tempid].can_cast_in_combat = atoi(row[213]) != 0; + sp[tempid].can_cast_out_of_combat = atoi(row[214]) != 0; sp[tempid].override_crit_chance = atoi(row[217]); - sp[tempid].aemaxtargets = atoi(row[218]); + sp[tempid].aoe_max_targets = atoi(row[218]); sp[tempid].no_heal_damage_item_mod = atoi(row[219]); - sp[tempid].persistdeath = atoi(row[224]) != 0; - sp[tempid].min_dist = atof(row[227]); - sp[tempid].min_dist_mod = atof(row[228]); - sp[tempid].max_dist = atof(row[229]); - sp[tempid].max_dist_mod = atof(row[230]); + sp[tempid].caster_requirement_id = atoi(row[220]); + sp[tempid].spell_class = atoi(row[221]); + sp[tempid].spell_subclass = atoi(row[222]); + sp[tempid].persist_death = atoi(row[224]) != 0; + sp[tempid].min_distance = atof(row[227]); + sp[tempid].min_distance_mod = atof(row[228]); + sp[tempid].max_distance = atof(row[229]); + sp[tempid].max_distance_mod = atof(row[230]); sp[tempid].min_range = static_cast(atoi(row[231])); sp[tempid].no_remove = atoi(row[232]) != 0; - sp[tempid].DamageShieldType = 0; + sp[tempid].damage_shield_type = 0; } LoadDamageShieldTypes(sp, max_spells); diff --git a/common/skills.cpp b/common/skills.cpp index 81b1a926c..2276a0e39 100644 --- a/common/skills.cpp +++ b/common/skills.cpp @@ -177,8 +177,7 @@ bool EQ::skills::IsMeleeDmg(SkillType skill) const std::map& EQ::skills::GetSkillTypeMap() { - /* VS2013 code - static const std::map skill_use_types_map = { + static const std::map skill_type_map = { { Skill1HBlunt, "1H Blunt" }, { Skill1HSlashing, "1H Slashing" }, { Skill2HBlunt, "2H Blunt" }, @@ -258,103 +257,18 @@ const std::map& EQ::skills::GetSkillTypeMap( { SkillTripleAttack, "Triple Attack" }, { Skill2HPiercing, "2H Piercing" } }; - */ - - /* VS2012 code - begin */ - - static const char* skill_use_names[SkillCount] = { - "1H Blunt", - "1H Slashing", - "2H Blunt", - "2H Slashing", - "Abjuration", - "Alteration", - "Apply Poison", - "Archery", - "Backstab", - "Bind Wound", - "Bash", - "Block", - "Brass Instruments", - "Channeling", - "Conjuration", - "Defense", - "Disarm", - "Disarm Traps", - "Divination", - "Dodge", - "Double Attack", - "Dragon Punch", - "Dual Wield", - "Eagle Strike", - "Evocation", - "Feign Death", - "Flying Kick", - "Forage", - "Hand to Hand", - "Hide", - "Kick", - "Meditate", - "Mend", - "Offense", - "Parry", - "Pick Lock", - "1H Piercing", - "Riposte", - "Round Kick", - "Safe Fall", - "Sense Heading", - "Singing", - "Sneak", - "Specialize Abjuration", - "Specialize Alteration", - "Specialize Conjuration", - "Specialize Divination", - "Specialize Evocation", - "Pick Pockets", - "Stringed Instruments", - "Swimming", - "Throwing", - "Tiger Claw", - "Tracking", - "Wind Instruments", - "Fishing", - "Make Poison", - "Tinkering", - "Research", - "Alchemy", - "Baking", - "Tailoring", - "Sense Traps", - "Blacksmithing", - "Fletching", - "Brewing", - "Alcohol Tolerance", - "Begging", - "Jewelry Making", - "Pottery", - "Percussion Instruments", - "Intimidation", - "Berserking", - "Taunt", - "Frenzy", - "Remove Traps", - "Triple Attack", - "2H Piercing" - }; - - static std::map skill_type_map; - - skill_type_map.clear(); - - for (int i = Skill1HBlunt; i < SkillCount; ++i) - skill_type_map[(SkillType)i] = skill_use_names[i]; - - /* VS2012 code - end */ - return skill_type_map; } +std::string EQ::skills::GetSkillName(SkillType skill) +{ + if (skill >= Skill1HBlunt && skill <= Skill2HPiercing) { + auto skills = GetSkillTypeMap(); + return skills[skill]; + } + return std::string(); +} + EQ::SkillProfile::SkillProfile() { memset(&Skill, 0, (sizeof(uint32) * PACKET_SKILL_ARRAY_SIZE)); diff --git a/common/skills.h b/common/skills.h index 81790ede1..38f549015 100644 --- a/common/skills.h +++ b/common/skills.h @@ -171,6 +171,7 @@ namespace EQ extern const std::map& GetSkillTypeMap(); + std::string GetSkillName(SkillType skill); } /*skills*/ struct SkillProfile { // prototype - not implemented diff --git a/common/spdat.cpp b/common/spdat.cpp index fe6e4db9c..08530df80 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -87,7 +87,7 @@ bool IsTargetableAESpell(uint16 spell_id) { - if (IsValidSpell(spell_id) && spells[spell_id].targettype == ST_AETarget) { + if (IsValidSpell(spell_id) && spells[spell_id].target_type == ST_AETarget) { return true; } @@ -103,8 +103,8 @@ bool IsLifetapSpell(uint16 spell_id) { // Ancient Lifebane: 2115 if (IsValidSpell(spell_id) && - (spells[spell_id].targettype == ST_Tap || - spells[spell_id].targettype == ST_TargetAETap || + (spells[spell_id].target_type == ST_Tap || + spells[spell_id].target_type == ST_TargetAETap || spell_id == 2115)) return true; @@ -124,7 +124,7 @@ bool IsStunSpell(uint16 spell_id) bool IsSummonSpell(uint16 spellid) { for (int o = 0; o < EFFECT_COUNT; o++) { - uint32 tid = spells[spellid].effectid[o]; + uint32 tid = spells[spellid].effect_id[o]; if (tid == SE_SummonPet || tid == SE_SummonItem || tid == SE_SummonPC) return true; } @@ -140,10 +140,10 @@ bool IsEvacSpell(uint16 spellid) bool IsDamageSpell(uint16 spellid) { for (int o = 0; o < EFFECT_COUNT; o++) { - uint32 tid = spells[spellid].effectid[o]; + uint32 tid = spells[spellid].effect_id[o]; if ((tid == SE_CurrentHPOnce || tid == SE_CurrentHP) && - spells[spellid].targettype != ST_Tap && spells[spellid].buffduration < 1 && - spells[spellid].base[o] < 0) + spells[spellid].target_type != ST_Tap && spells[spellid].buff_duration < 1 && + spells[spellid].base_value[o] < 0) return true; } @@ -163,8 +163,8 @@ bool IsCureSpell(uint16 spell_id) bool CureEffect = false; for(int i = 0; i < EFFECT_COUNT; i++){ - if (sp.effectid[i] == SE_DiseaseCounter || sp.effectid[i] == SE_PoisonCounter - || sp.effectid[i] == SE_CurseCounter || sp.effectid[i] == SE_CorruptionCounter) + if (sp.effect_id[i] == SE_DiseaseCounter || sp.effect_id[i] == SE_PoisonCounter + || sp.effect_id[i] == SE_CurseCounter || sp.effect_id[i] == SE_CorruptionCounter) CureEffect = true; } @@ -179,8 +179,8 @@ bool IsSlowSpell(uint16 spell_id) const SPDat_Spell_Struct &sp = spells[spell_id]; for(int i = 0; i < EFFECT_COUNT; i++) - if ((sp.effectid[i] == SE_AttackSpeed && sp.base[i] < 100) || - (sp.effectid[i] == SE_AttackSpeed4)) + if ((sp.effect_id[i] == SE_AttackSpeed && sp.base_value[i] < 100) || + (sp.effect_id[i] == SE_AttackSpeed4)) return true; return false; @@ -191,8 +191,8 @@ bool IsHasteSpell(uint16 spell_id) const SPDat_Spell_Struct &sp = spells[spell_id]; for(int i = 0; i < EFFECT_COUNT; i++) - if(sp.effectid[i] == SE_AttackSpeed) - return (sp.base[i] < 100); + if(sp.effect_id[i] == SE_AttackSpeed) + return (sp.base_value[i] < 100); return false; } @@ -210,7 +210,7 @@ bool IsPercentalHealSpell(uint16 spell_id) bool IsGroupOnlySpell(uint16 spell_id) { - return IsValidSpell(spell_id) && spells[spell_id].goodEffect == 2; + return IsValidSpell(spell_id) && spells[spell_id].good_effect == 2; } bool IsBeneficialSpell(uint16 spell_id) @@ -219,10 +219,10 @@ bool IsBeneficialSpell(uint16 spell_id) return false; // You'd think just checking goodEffect flag would be enough? - if (spells[spell_id].goodEffect == 1) { + if (spells[spell_id].good_effect == 1) { // If the target type is ST_Self or ST_Pet and is a SE_CancleMagic spell // it is not Beneficial - SpellTargetType tt = spells[spell_id].targettype; + SpellTargetType tt = spells[spell_id].target_type; if (tt != ST_Self && tt != ST_Pet && IsEffectInSpell(spell_id, SE_CancelMagic)) return false; @@ -231,14 +231,14 @@ bool IsBeneficialSpell(uint16 spell_id) // We need to check more things! if (tt == ST_Target || tt == ST_AETarget || tt == ST_Animal || tt == ST_Undead || tt == ST_Pet) { - uint16 sai = spells[spell_id].SpellAffectIndex; + uint16 sai = spells[spell_id].spell_affect_index; // If the resisttype is magic and SpellAffectIndex is Calm/memblur/dispell sight // it's not beneficial - if (spells[spell_id].resisttype == RESIST_MAGIC) { + if (spells[spell_id].resist_type == RESIST_MAGIC) { // checking these SAI cause issues with the rng defensive proc line // So I guess instead of fixing it for real, just a quick hack :P - if (spells[spell_id].effectid[0] != SE_DefensiveProc && + if (spells[spell_id].effect_id[0] != SE_DefensiveProc && (sai == SAI_Calm || sai == SAI_Dispell_Sight || sai == SAI_Memory_Blur || sai == SAI_Calm_Song)) return false; @@ -252,7 +252,7 @@ bool IsBeneficialSpell(uint16 spell_id) } // And finally, if goodEffect is not 0 or if it's a group spell it's beneficial - return spells[spell_id].goodEffect != 0 || IsGroupSpell(spell_id); + return spells[spell_id].good_effect != 0 || IsGroupSpell(spell_id); } bool IsDetrimentalSpell(uint16 spell_id) @@ -260,6 +260,18 @@ bool IsDetrimentalSpell(uint16 spell_id) return !IsBeneficialSpell(spell_id); } +bool IsInvisSpell(uint16 spell_id) +{ + if (IsEffectInSpell(spell_id, SE_Invisibility) || + IsEffectInSpell(spell_id, SE_Invisibility2) || + IsEffectInSpell(spell_id, SE_InvisVsUndead) || + IsEffectInSpell(spell_id, SE_InvisVsUndead2) || + IsEffectInSpell(spell_id, SE_InvisVsAnimals)) { + return true; + } + return false; +} + bool IsInvulnerabilitySpell(uint16 spell_id) { return IsEffectInSpell(spell_id, SE_DivineAura); @@ -352,8 +364,8 @@ bool IsImprovedDamageSpell(uint16 spell_id) bool IsAEDurationSpell(uint16 spell_id) { if (IsValidSpell(spell_id) && - (spells[spell_id].targettype == ST_AETarget || spells[spell_id].targettype == ST_UndeadAE) && - spells[spell_id].AEDuration != 0) + (spells[spell_id].target_type == ST_AETarget || spells[spell_id].target_type == ST_UndeadAE) && + spells[spell_id].aoe_duration != 0) return true; return false; @@ -371,7 +383,7 @@ bool IsPureNukeSpell(uint16 spell_id) effect_count++; if (effect_count == 1 && IsEffectInSpell(spell_id, SE_CurrentHP) && - spells[spell_id].buffduration == 0 && IsDamageSpell(spell_id)) + spells[spell_id].buff_duration == 0 && IsDamageSpell(spell_id)) return true; return false; @@ -380,7 +392,7 @@ bool IsPureNukeSpell(uint16 spell_id) bool IsAENukeSpell(uint16 spell_id) { if (IsValidSpell(spell_id) && IsPureNukeSpell(spell_id) && - spells[spell_id].aoerange > 0) + spells[spell_id].aoe_range > 0) return true; return false; @@ -389,7 +401,7 @@ bool IsAENukeSpell(uint16 spell_id) bool IsPBAENukeSpell(uint16 spell_id) { if (IsValidSpell(spell_id) && IsPureNukeSpell(spell_id) && - spells[spell_id].aoerange > 0 && spells[spell_id].targettype == ST_AECaster) + spells[spell_id].aoe_range > 0 && spells[spell_id].target_type == ST_AECaster) return true; return false; @@ -398,7 +410,7 @@ bool IsPBAENukeSpell(uint16 spell_id) bool IsAERainNukeSpell(uint16 spell_id) { if (IsValidSpell(spell_id) && IsPureNukeSpell(spell_id) && - spells[spell_id].aoerange > 0 && spells[spell_id].AEDuration > 1000) + spells[spell_id].aoe_range > 0 && spells[spell_id].aoe_duration > 1000) return true; return false; @@ -412,12 +424,12 @@ bool IsPartialCapableSpell(uint16 spell_id) // spell uses 600 (partial) scale if first effect is damage, else it uses 200 scale. // this includes DoTs. no_partial_resist excludes spells like necro snares for (int o = 0; o < EFFECT_COUNT; o++) { - auto tid = spells[spell_id].effectid[o]; + auto tid = spells[spell_id].effect_id[o]; if (IsBlankSpellEffect(spell_id, o)) continue; - if ((tid == SE_CurrentHPOnce || tid == SE_CurrentHP) && spells[spell_id].base[o] < 0) + if ((tid == SE_CurrentHPOnce || tid == SE_CurrentHP) && spells[spell_id].base_value[o] < 0) return true; return false; @@ -440,9 +452,9 @@ bool IsResistableSpell(uint16 spell_id) bool IsGroupSpell(uint16 spell_id) { if (IsValidSpell(spell_id) && - (spells[spell_id].targettype == ST_AEBard || - spells[spell_id].targettype == ST_Group || - spells[spell_id].targettype == ST_GroupTeleport)) + (spells[spell_id].target_type == ST_AEBard || + spells[spell_id].target_type == ST_Group || + spells[spell_id].target_type == ST_GroupTeleport)) return true; return false; @@ -452,7 +464,7 @@ bool IsGroupSpell(uint16 spell_id) bool IsTGBCompatibleSpell(uint16 spell_id) { if (IsValidSpell(spell_id) && - (!IsDetrimentalSpell(spell_id) && spells[spell_id].buffduration != 0 && + (!IsDetrimentalSpell(spell_id) && spells[spell_id].buff_duration != 0 && !IsBardSong(spell_id) && !IsEffectInSpell(spell_id, SE_Illusion))) return true; @@ -461,7 +473,7 @@ bool IsTGBCompatibleSpell(uint16 spell_id) bool IsBardSong(uint16 spell_id) { - if (IsValidSpell(spell_id) && spells[spell_id].classes[BARD - 1] < 255 && !spells[spell_id].IsDisciplineBuff) + if (IsValidSpell(spell_id) && spells[spell_id].classes[BARD - 1] < 255 && !spells[spell_id].is_discipline) return true; return false; @@ -475,7 +487,7 @@ bool IsEffectInSpell(uint16 spellid, int effect) return false; for (j = 0; j < EFFECT_COUNT; j++) - if (spells[spellid].effectid[j] == effect) + if (spells[spellid].effect_id[j] == effect) return true; return false; @@ -486,15 +498,15 @@ bool IsEffectInSpell(uint16 spellid, int effect) // the blanks bool IsBlankSpellEffect(uint16 spellid, int effect_index) { - int effect, base, formula; + int effect, base_value, formula; - effect = spells[spellid].effectid[effect_index]; - base = spells[spellid].base[effect_index]; + effect = spells[spellid].effect_id[effect_index]; + base_value = spells[spellid].base_value[effect_index]; formula = spells[spellid].formula[effect_index]; // SE_CHA is "spacer" // SE_Stacking* are also considered blank where this is used - if (effect == SE_Blank || (effect == SE_CHA && base == 0 && formula == 100) || + if (effect == SE_Blank || (effect == SE_CHA && base_value == 0 && formula == 100) || effect == SE_StackingCommand_Block || effect == SE_StackingCommand_Overwrite) return true; @@ -549,7 +561,7 @@ int GetSpellEffectIndex(uint16 spell_id, int effect) return -1; for (i = 0; i < EFFECT_COUNT; i++) - if (spells[spell_id].effectid[i] == effect) + if (spells[spell_id].effect_id[i] == effect) return i; return -1; @@ -579,7 +591,7 @@ bool BeneficialSpell(uint16 spell_id) /*|| spells[spell_id].stacking == 27*/ ) return true; - switch (spells[spell_id].goodEffect) { + switch (spells[spell_id].good_effect) { case 1: case 3: return true; @@ -590,7 +602,7 @@ bool BeneficialSpell(uint16 spell_id) bool GroupOnlySpell(uint16 spell_id) { - switch (spells[spell_id].goodEffect) { + switch (spells[spell_id].good_effect) { case 2: case 3: return true; @@ -611,9 +623,9 @@ int32 CalculatePoisonCounters(uint16 spell_id) int32 Counters = 0; for (int i = 0; i < EFFECT_COUNT; i++) - if (spells[spell_id].effectid[i] == SE_PoisonCounter && - spells[spell_id].base[i] > 0) - Counters += spells[spell_id].base[i]; + if (spells[spell_id].effect_id[i] == SE_PoisonCounter && + spells[spell_id].base_value[i] > 0) + Counters += spells[spell_id].base_value[i]; return Counters; } @@ -625,9 +637,9 @@ int32 CalculateDiseaseCounters(uint16 spell_id) int32 Counters = 0; for (int i = 0; i < EFFECT_COUNT; i++) - if(spells[spell_id].effectid[i] == SE_DiseaseCounter && - spells[spell_id].base[i] > 0) - Counters += spells[spell_id].base[i]; + if(spells[spell_id].effect_id[i] == SE_DiseaseCounter && + spells[spell_id].base_value[i] > 0) + Counters += spells[spell_id].base_value[i]; return Counters; } @@ -639,9 +651,9 @@ int32 CalculateCurseCounters(uint16 spell_id) int32 Counters = 0; for (int i = 0; i < EFFECT_COUNT; i++) - if(spells[spell_id].effectid[i] == SE_CurseCounter && - spells[spell_id].base[i] > 0) - Counters += spells[spell_id].base[i]; + if(spells[spell_id].effect_id[i] == SE_CurseCounter && + spells[spell_id].base_value[i] > 0) + Counters += spells[spell_id].base_value[i]; return Counters; } @@ -653,9 +665,9 @@ int32 CalculateCorruptionCounters(uint16 spell_id) int32 Counters = 0; for (int i = 0; i < EFFECT_COUNT; i++) - if (spells[spell_id].effectid[i] == SE_CorruptionCounter && - spells[spell_id].base[i] > 0) - Counters += spells[spell_id].base[i]; + if (spells[spell_id].effect_id[i] == SE_CorruptionCounter && + spells[spell_id].base_value[i] > 0) + Counters += spells[spell_id].base_value[i]; return Counters; } @@ -683,7 +695,7 @@ bool IsDisciplineBuff(uint16 spell_id) if (!IsValidSpell(spell_id)) return false; - if (spells[spell_id].IsDisciplineBuff && spells[spell_id].targettype == ST_Self) + if (spells[spell_id].is_discipline && spells[spell_id].target_type == ST_Self) return true; return false; @@ -695,7 +707,7 @@ bool IsDiscipline(uint16 spell_id) return false; if (spells[spell_id].mana == 0 && - (spells[spell_id].EndurCost || spells[spell_id].EndurUpkeep)) + (spells[spell_id].endurance_cost || spells[spell_id].endurance_upkeep)) return true; return false; @@ -707,7 +719,7 @@ bool IsCombatSkill(uint16 spell_id) return false; //Check if Discipline - if ((spells[spell_id].mana == 0 && (spells[spell_id].EndurCost || spells[spell_id].EndurUpkeep))) + if ((spells[spell_id].mana == 0 && (spells[spell_id].endurance_cost || spells[spell_id].endurance_upkeep))) return true; return false; @@ -726,7 +738,7 @@ bool IsRuneSpell(uint16 spell_id) { if (IsValidSpell(spell_id)) for (int i = 0; i < EFFECT_COUNT; i++) - if (spells[spell_id].effectid[i] == SE_Rune) + if (spells[spell_id].effect_id[i] == SE_Rune) return true; return false; @@ -736,7 +748,7 @@ bool IsMagicRuneSpell(uint16 spell_id) { if(IsValidSpell(spell_id)) for(int i = 0; i < EFFECT_COUNT; i++) - if(spells[spell_id].effectid[i] == SE_AbsorbMagicAtt) + if(spells[spell_id].effect_id[i] == SE_AbsorbMagicAtt) return true; return false; @@ -746,8 +758,8 @@ bool IsManaTapSpell(uint16 spell_id) { if (IsValidSpell(spell_id)) for (int i = 0; i < EFFECT_COUNT; i++) - if (spells[spell_id].effectid[i] == SE_CurrentMana && - spells[spell_id].targettype == ST_Tap) + if (spells[spell_id].effect_id[i] == SE_CurrentMana && + spells[spell_id].target_type == ST_Tap) return true; return false; @@ -776,8 +788,8 @@ bool IsPartialDeathSaveSpell(uint16 spell_id) return false; for (int i = 0; i < EFFECT_COUNT; i++) - if (spells[spell_id].effectid[i] == SE_DeathSave && - spells[spell_id].base[i] == 1) + if (spells[spell_id].effect_id[i] == SE_DeathSave && + spells[spell_id].base_value[i] == 1) return true; return false; @@ -790,8 +802,8 @@ bool IsFullDeathSaveSpell(uint16 spell_id) return false; for (int i = 0; i < EFFECT_COUNT; i++) - if (spells[spell_id].effectid[i] == SE_DeathSave && - spells[spell_id].base[i] == 2) + if (spells[spell_id].effect_id[i] == SE_DeathSave && + spells[spell_id].base_value[i] == 2) return true; return false; @@ -821,6 +833,14 @@ bool IsTeleportSpell(uint16 spell_id) return false; } +bool IsTranslocateSpell(uint16 spell_id) +{ + if (IsEffectInSpell(spell_id, SE_Translocate)) + return true; + + return false; +} + bool IsGateSpell(uint16 spell_id) { if (IsEffectInSpell(spell_id, SE_Gate)) @@ -832,7 +852,7 @@ bool IsGateSpell(uint16 spell_id) bool IsPlayerIllusionSpell(uint16 spell_id) { if (IsEffectInSpell(spell_id, SE_Illusion) && - spells[spell_id].targettype == ST_Self) + spells[spell_id].target_type == ST_Self) return true; return false; @@ -841,7 +861,7 @@ bool IsPlayerIllusionSpell(uint16 spell_id) int GetSpellEffectDescNum(uint16 spell_id) { if (IsValidSpell(spell_id)) - return spells[spell_id].effectdescnum; + return spells[spell_id].effect_description_id; return -1; } @@ -852,12 +872,12 @@ DmgShieldType GetDamageShieldType(uint16 spell_id, int32 DSType) // else, make a guess, based on the resist type. Default return value is DS_THORNS if (IsValidSpell(spell_id)) { LogSpells("DamageShieldType for spell [{}] ([{}]) is [{}]", spell_id, - spells[spell_id].name, spells[spell_id].DamageShieldType); + spells[spell_id].name, spells[spell_id].damage_shield_type); - if (spells[spell_id].DamageShieldType) - return (DmgShieldType) spells[spell_id].DamageShieldType; + if (spells[spell_id].damage_shield_type) + return (DmgShieldType) spells[spell_id].damage_shield_type; - switch (spells[spell_id].resisttype) { + switch (spells[spell_id].resist_type) { case RESIST_COLD: return DS_TORMENT; case RESIST_FIRE: @@ -887,12 +907,12 @@ bool IsLDoNObjectSpell(uint16 spell_id) int32 GetSpellResistType(uint16 spell_id) { - return spells[spell_id].resisttype; + return spells[spell_id].resist_type; } int32 GetSpellTargetType(uint16 spell_id) { - return (int32)spells[spell_id].targettype; + return (int32)spells[spell_id].target_type; } bool IsHealOverTimeSpell(uint16 spell_id) @@ -917,7 +937,7 @@ bool IsFastHealSpell(uint16 spell_id) const int MaxFastHealCastingTime = 2000; if (spells[spell_id].cast_time <= MaxFastHealCastingTime && - spells[spell_id].effectid[0] == 0 && spells[spell_id].base[0] > 0 && + spells[spell_id].effect_id[0] == 0 && spells[spell_id].base_value[0] > 0 && !IsGroupSpell(spell_id)) return true; @@ -929,7 +949,7 @@ bool IsVeryFastHealSpell(uint16 spell_id) const int MaxFastHealCastingTime = 1000; if (spells[spell_id].cast_time <= MaxFastHealCastingTime && - spells[spell_id].effectid[0] == 0 && spells[spell_id].base[0] > 0 && + spells[spell_id].effect_id[0] == 0 && spells[spell_id].base_value[0] > 0 && !IsGroupSpell(spell_id)) return true; @@ -938,8 +958,8 @@ bool IsVeryFastHealSpell(uint16 spell_id) bool IsRegularSingleTargetHealSpell(uint16 spell_id) { - if(spells[spell_id].effectid[0] == 0 && spells[spell_id].base[0] > 0 && - spells[spell_id].targettype == ST_Target && spells[spell_id].buffduration == 0 && + if(spells[spell_id].effect_id[0] == 0 && spells[spell_id].base_value[0] > 0 && + spells[spell_id].target_type == ST_Target && spells[spell_id].buff_duration == 0 && !IsCompleteHealSpell(spell_id) && !IsHealOverTimeSpell(spell_id) && !IsGroupSpell(spell_id)) return true; @@ -965,7 +985,7 @@ bool IsGroupCompleteHealSpell(uint16 spell_id) bool IsGroupHealOverTimeSpell(uint16 spell_id) { - if( IsGroupSpell(spell_id) && IsHealOverTimeSpell(spell_id) && spells[spell_id].buffduration < 10) + if( IsGroupSpell(spell_id) && IsHealOverTimeSpell(spell_id) && spells[spell_id].buff_duration < 10) return true; return false; @@ -996,8 +1016,8 @@ bool IsResistDebuffSpell(uint16 spell_id) bool IsSelfConversionSpell(uint16 spell_id) { if (GetSpellTargetType(spell_id) == ST_Self && IsEffectInSpell(spell_id, SE_CurrentMana) && - IsEffectInSpell(spell_id, SE_CurrentHP) && spells[spell_id].base[GetSpellEffectIndex(spell_id, SE_CurrentMana)] > 0 && - spells[spell_id].base[GetSpellEffectIndex(spell_id, SE_CurrentHP)] < 0) + IsEffectInSpell(spell_id, SE_CurrentHP) && spells[spell_id].base_value[GetSpellEffectIndex(spell_id, SE_CurrentMana)] > 0 && + spells[spell_id].base_value[GetSpellEffectIndex(spell_id, SE_CurrentHP)] < 0) return true; else return false; @@ -1006,7 +1026,7 @@ bool IsSelfConversionSpell(uint16 spell_id) // returns true for both detrimental and beneficial buffs bool IsBuffSpell(uint16 spell_id) { - if (IsValidSpell(spell_id) && (spells[spell_id].buffduration || spells[spell_id].buffdurationformula)) + if (IsValidSpell(spell_id) && (spells[spell_id].buff_duration || spells[spell_id].buff_duration_formula)) return true; return false; @@ -1014,7 +1034,7 @@ bool IsBuffSpell(uint16 spell_id) bool IsPersistDeathSpell(uint16 spell_id) { - if (IsValidSpell(spell_id) && spells[spell_id].persistdeath) + if (IsValidSpell(spell_id) && spells[spell_id].persist_death) return true; return false; @@ -1031,8 +1051,8 @@ bool IsSuspendableSpell(uint16 spell_id) uint32 GetMorphTrigger(uint32 spell_id) { for (int i = 0; i < EFFECT_COUNT; ++i) - if (spells[spell_id].effectid[i] == SE_CastOnFadeEffect) - return spells[spell_id].base[i]; + if (spells[spell_id].effect_id[i] == SE_CastOnFadeEffect) + return spells[spell_id].base_value[i]; return 0; } @@ -1040,9 +1060,9 @@ uint32 GetMorphTrigger(uint32 spell_id) bool IsCastonFadeDurationSpell(uint16 spell_id) { for (int i = 0; i < EFFECT_COUNT; ++i) { - if (spells[spell_id].effectid[i] == SE_CastOnFadeEffect - || spells[spell_id].effectid[i] == SE_CastOnFadeEffectNPC - || spells[spell_id].effectid[i] == SE_CastOnFadeEffectAlways){ + if (spells[spell_id].effect_id[i] == SE_CastOnFadeEffect + || spells[spell_id].effect_id[i] == SE_CastOnFadeEffectNPC + || spells[spell_id].effect_id[i] == SE_CastOnFadeEffectAlways){ return true; } @@ -1053,7 +1073,7 @@ bool IsCastonFadeDurationSpell(uint16 spell_id) bool IsPowerDistModSpell(uint16 spell_id) { if (IsValidSpell(spell_id) && - (spells[spell_id].max_dist_mod || spells[spell_id].min_dist_mod) && spells[spell_id].max_dist > spells[spell_id].min_dist) + (spells[spell_id].max_distance_mod || spells[spell_id].min_distance_mod) && spells[spell_id].max_distance > spells[spell_id].min_distance) return true; return false; @@ -1062,8 +1082,8 @@ bool IsPowerDistModSpell(uint16 spell_id) uint32 GetPartialMeleeRuneReduction(uint32 spell_id) { for (int i = 0; i < EFFECT_COUNT; ++i) - if (spells[spell_id].effectid[i] == SE_MitigateMeleeDamage) - return spells[spell_id].base[i]; + if (spells[spell_id].effect_id[i] == SE_MitigateMeleeDamage) + return spells[spell_id].base_value[i]; return 0; } @@ -1071,8 +1091,8 @@ uint32 GetPartialMeleeRuneReduction(uint32 spell_id) uint32 GetPartialMagicRuneReduction(uint32 spell_id) { for (int i = 0; i < EFFECT_COUNT; ++i) - if (spells[spell_id].effectid[i] == SE_MitigateSpellDamage) - return spells[spell_id].base[i]; + if (spells[spell_id].effect_id[i] == SE_MitigateSpellDamage) + return spells[spell_id].base_value[i]; return 0; } @@ -1080,8 +1100,8 @@ uint32 GetPartialMagicRuneReduction(uint32 spell_id) uint32 GetPartialMeleeRuneAmount(uint32 spell_id) { for (int i = 0; i < EFFECT_COUNT; ++i) - if (spells[spell_id].effectid[i] == SE_MitigateMeleeDamage) - return spells[spell_id].max[i]; + if (spells[spell_id].effect_id[i] == SE_MitigateMeleeDamage) + return spells[spell_id].max_value[i]; return 0; } @@ -1089,8 +1109,8 @@ uint32 GetPartialMeleeRuneAmount(uint32 spell_id) uint32 GetPartialMagicRuneAmount(uint32 spell_id) { for (int i = 0; i < EFFECT_COUNT; ++i) - if (spells[spell_id].effectid[i] == SE_MitigateSpellDamage) - return spells[spell_id].max[i]; + if (spells[spell_id].effect_id[i] == SE_MitigateSpellDamage) + return spells[spell_id].max_value[i]; return 0; } @@ -1099,7 +1119,7 @@ uint32 GetPartialMagicRuneAmount(uint32 spell_id) bool DetrimentalSpellAllowsRest(uint16 spell_id) { if (IsValidSpell(spell_id)) - return spells[spell_id].AllowRest; + return spells[spell_id].allow_rest; return false; } @@ -1118,7 +1138,7 @@ bool IsStackableDot(uint16 spell_id) if (!IsValidSpell(spell_id)) return false; const auto &spell = spells[spell_id]; - if (spell.dot_stacking_exempt || spell.goodEffect || !spell.buffdurationformula) + if (spell.unstackable_dot || spell.good_effect || !spell.buff_duration_formula) return false; return IsEffectInSpell(spell_id, SE_CurrentHP) || IsEffectInSpell(spell_id, SE_GravityEffect); } @@ -1210,7 +1230,7 @@ bool IsEffectIgnoredInStacking(int spa) case SE_LimitClass: case SE_LimitRace: case SE_FcBaseEffects: - case 415: + case SE_FFItemClass: case SE_SkillDamageAmount2: case SE_FcLimitUse: case SE_FcIncreaseNumHits: @@ -1218,6 +1238,67 @@ bool IsEffectIgnoredInStacking(int spa) case SE_LimitUseType: case SE_GravityEffect: case 425: + //Spell effects implemented after ROF2, following same pattern, lets assume these should go here. + case SE_Fc_Spell_Damage_Pct_IncomingPC: + case SE_Fc_Spell_Damage_Amt_IncomingPC: + case SE_Ff_CasterClass: + case SE_Ff_Same_Caster: + case SE_Proc_Timer_Modifier: + case SE_Weapon_Stance: + case SE_TwinCastBlocker: + case SE_Fc_CastTimeAmt: + case SE_Fc_CastTimeMod2: + case SE_Ff_DurationMax: + case SE_Ff_Endurance_Max: + case SE_Ff_Endurance_Min: + case SE_Ff_ReuseTimeMin: + case SE_Ff_ReuseTimeMax: + case SE_Ff_Value_Min: + case SE_Ff_Value_Max: + case SE_Ff_FocusTimerMin: + return true; + default: + return false; + } +} + +bool IsFocusLimit(int spa) +{ + switch (spa) { + case SE_LimitMaxLevel: + case SE_LimitResist: + case SE_LimitTarget: + case SE_LimitEffect: + case SE_LimitSpellType: + case SE_LimitSpell: + case SE_LimitMinDur: + case SE_LimitInstant: + case SE_LimitMinLevel: + case SE_LimitCastTimeMin: + case SE_LimitCastTimeMax: + case SE_LimitCombatSkills: + case SE_LimitManaMin: + case SE_LimitSpellGroup: + case SE_LimitManaMax: + case SE_LimitSpellClass: + case SE_LimitSpellSubclass: + case SE_LimitClass: + case SE_LimitRace: + case SE_LimitCastingSkill: + case SE_LimitUseMin: + case SE_LimitUseType: + case SE_Ff_Override_NotFocusable: + case SE_Ff_CasterClass: + case SE_Ff_Same_Caster: + case SE_Ff_DurationMax: + case SE_Ff_Endurance_Max: + case SE_Ff_Endurance_Min: + case SE_Ff_ReuseTimeMin: + case SE_Ff_ReuseTimeMax: + case SE_Ff_Value_Min: + case SE_Ff_Value_Max: + case SE_Ff_FocusTimerMin: + case SE_FFItemClass: return true; default: return false; @@ -1227,7 +1308,7 @@ bool IsEffectIgnoredInStacking(int spa) uint32 GetNimbusEffect(uint16 spell_id) { if (IsValidSpell(spell_id)) - return (int32)spells[spell_id].NimbusEffect; + return (int32)spells[spell_id].nimbus_effect; return 0; } @@ -1241,9 +1322,9 @@ int32 GetFuriousBash(uint16 spell_id) int32 mod = 0; for (int i = 0; i < EFFECT_COUNT; ++i) - if (spells[spell_id].effectid[i] == SE_SpellHateMod) - mod = spells[spell_id].base[i]; - else if (spells[spell_id].effectid[i] == SE_LimitEffect && spells[spell_id].base[i] == 999) + if (spells[spell_id].effect_id[i] == SE_SpellHateMod) + mod = spells[spell_id].base_value[i]; + else if (spells[spell_id].effect_id[i] == SE_LimitEffect && spells[spell_id].base_value[i] == 999) found_effect_limit = true; if (found_effect_limit) @@ -1264,8 +1345,8 @@ bool IsSpellUsableThisZoneType(uint16 spell_id, uint8 zone_type) { //check if spell can be cast in any zone (-1 or 255), then if spell zonetype matches zone's zonetype // || spells[spell_id].zonetype == 255 comparing signed 8 bit int to 255 is always false - if (IsValidSpell(spell_id) && (spells[spell_id].zonetype == -1 || - spells[spell_id].zonetype == zone_type)) + if (IsValidSpell(spell_id) && (spells[spell_id].zone_type == -1 || + spells[spell_id].zone_type == zone_type)) return true; return false; @@ -1276,3 +1357,346 @@ const char* GetSpellName(uint16 spell_id) return spells[spell_id].name; } +bool SpellRequiresTarget(int spell_id) +{ + if (spells[spell_id].target_type == ST_AEClientV1 || + spells[spell_id].target_type == ST_Self || + spells[spell_id].target_type == ST_AECaster || + spells[spell_id].target_type == ST_Ring || + spells[spell_id].target_type == ST_Beam) { + return false; + } + return true; +} + +bool IsInstrumentModAppliedToSpellEffect(int32 spell_id, int effect) +{ + + //Effects that are verified modifiable by bard instrument/singing mods, or highly likely due to similiar type of effect. + switch (effect) { + + //Only modify instant endurance or mana effects (Ie. Mana drain, Crescendo line) + case SE_CurrentEndurance: + case SE_CurrentMana: { + if (spells[spell_id].buff_duration == 0) { + return true; + } + //Mana regen is not modified. + return false; + } + + case SE_CurrentHP: + case SE_ArmorClass: + case SE_ACv2: + case SE_MovementSpeed: + case SE_ATK: + case SE_STR: + case SE_DEX: + case SE_AGI: + case SE_STA: + case SE_INT: + case SE_WIS: + case SE_CHA: + case SE_AllStats: + case SE_ResistFire: + case SE_ResistCold: + case SE_ResistPoison: + case SE_ResistDisease: + case SE_ResistMagic: + case SE_ResistAll: + case SE_ResistCorruption: + case SE_Rune: + case SE_AbsorbMagicAtt: + case SE_DamageShield: + case SE_MitigateDamageShield: + case SE_Amplification: //On live Amplification is modified by singing mods, including itself. + case SE_TripleAttackChance: + case SE_Flurry: + case SE_DamageModifier: + case SE_DamageModifier2: + case SE_MinDamageModifier: + case SE_ProcChance: + case SE_PetFlurry: // ? Need verified + case SE_DiseaseCounter: + case SE_PoisonCounter: + case SE_CurseCounter: + case SE_CorruptionCounter: + return true; + + /* + Following are confirmed NOT modifiable by instrument/singing mods. + Focus Effects, Proc Effects, Spell Triggers are not modified but handled elsewhere, not neccessary to checked here. + */ + + case SE_AttackSpeed: //(Haste AND Slow not modifiable) + case SE_AttackSpeed2: + case SE_AttackSpeed3: + case SE_Lull: + case SE_ChangeFrenzyRad: + case SE_Harmony: + case SE_AddFaction: + //case SE_CurrentMana: // duration only + case SE_ManaRegen_v2: + //case SE_CurrentEndurance: // duration only + case SE_PersistentEffect: + case SE_ReduceReuseTimer: + case SE_Stun: + case SE_Mez: + case SE_WipeHateList: //? + case SE_CancelMagic: + case SE_ManaAbsorbPercentDamage: + case SE_ResistSpellChance: + case SE_Reflect: + case SE_MitigateSpellDamage: + case SE_MitigateMeleeDamage: + case SE_AllInstrumentMod: + case SE_AddSingingMod: + case SE_SongModCap: + case SE_BardSongRange: + case SE_TemporaryPets: + case SE_SpellOnDeath: + return false; + default: + return true; + } + //Allowing anything not confirmed to be restricted / allowed to receive modifiers, as to not inhbit anyone making custom bard songs. +} + +bool IsPulsingBardSong(int32 spell_id) +{ + if (!IsValidSpell(spell_id)) { + return false; + } + + if (spells[spell_id].buff_duration == 0xFFFF || + spells[spell_id].recast_time> 0 || + spells[spell_id].mana > 0 || + IsEffectInSpell(spell_id, SE_TemporaryPets) || + IsEffectInSpell(spell_id, SE_Familiar)) { + return false; + } + + return true; +} + +int GetSpellStatValue(uint32 spell_id, const char* stat_identifier, uint8 slot) +{ + if (!IsValidSpell(spell_id)) + return 0; + + if (!stat_identifier) + return 0; + + if (slot > 0) + slot -= 1; + + std::string id = stat_identifier; + for(uint32 i = 0; i < id.length(); ++i) { + id[i] = tolower(id[i]); + } + + if (slot < 16) { + if (id == "classes") { return spells[spell_id].classes[slot]; } + else if (id == "deities") { return spells[spell_id].deities[slot]; } + } + + if (slot < 12) { + if (id == "base") { return spells[spell_id].base_value[slot]; } + else if (id == "base2") { return spells[spell_id].limit_value[slot]; } + else if (id == "max") { return spells[spell_id].max_value[slot]; } + else if (id == "formula") { return spells[spell_id].formula[slot]; } + else if (id == "effectid") { return spells[spell_id].effect_id[slot]; } + } + + if (slot < 4) { + if (id == "components") { return spells[spell_id].component[slot]; } + else if (id == "component_counts") { return spells[spell_id].component_count[slot]; } + else if (id == "noexpendreagent") { return spells[spell_id].no_expend_reagent[slot]; } + } + + if (id == "range") { return static_cast(spells[spell_id].range); } + else if (id == "aoe_range") { return static_cast(spells[spell_id].aoe_range); } + else if (id == "push_back") { return static_cast(spells[spell_id].push_back); } + else if (id == "push_up") { return static_cast(spells[spell_id].push_up); } + else if (id == "cast_time") { return spells[spell_id].cast_time; } + else if (id == "recovery_time") { return spells[spell_id].recovery_time; } + else if (id == "recast_time") { return spells[spell_id].recast_time; } + else if (id == "buff_duration_formula") { return spells[spell_id].buff_duration_formula; } + else if (id == "buff_duration") { return spells[spell_id].buff_duration; } + else if (id == "aeduration") { return spells[spell_id].aoe_duration; } + else if (id == "mana") { return spells[spell_id].mana; } + //else if (id == "LightType") {stat = spells[spell_id].LightType; } - Not implemented + else if (id == "goodeffect") { return spells[spell_id].good_effect; } + else if (id == "activated") { return spells[spell_id].activated; } + else if (id == "resisttype") { return spells[spell_id].resist_type; } + else if (id == "targettype") { return spells[spell_id].target_type; } + else if (id == "basediff") { return spells[spell_id].base_difficulty; } + else if (id == "skill") { return spells[spell_id].skill; } + else if (id == "zonetype") { return spells[spell_id].zone_type; } + else if (id == "environmenttype") { return spells[spell_id].environment_type; } + else if (id == "timeofday") { return spells[spell_id].time_of_day; } + else if (id == "castinganim") { return spells[spell_id].casting_animation; } + else if (id == "spellaffectindex") { return spells[spell_id].spell_affect_index; } + else if (id == "disallow_sit") { return spells[spell_id].disallow_sit; } + //else if (id == "spellanim") {stat = spells[spell_id].spellanim; } - Not implemented + else if (id == "uninterruptable") { return spells[spell_id].uninterruptable; } + else if (id == "resistdiff") { return spells[spell_id].resist_difficulty; } + else if (id == "dot_stacking_exempt") { return spells[spell_id].unstackable_dot; } + else if (id == "recourselink") { return spells[spell_id].recourse_link; } + else if (id == "no_partial_resist") { return spells[spell_id].no_partial_resist; } + else if (id == "short_buff_box") { return spells[spell_id].short_buff_box; } + else if (id == "descnum") { return spells[spell_id].description_id; } + else if (id == "effectdescnum") { return spells[spell_id].effect_description_id; } + else if (id == "npc_no_los") { return spells[spell_id].npc_no_los; } + else if (id == "feedbackable") { return spells[spell_id].feedbackable; } + else if (id == "reflectable") { return spells[spell_id].reflectable; } + else if (id == "bonushate") { return spells[spell_id].bonus_hate; } + else if (id == "endurcost") { return spells[spell_id].endurance_cost; } + else if (id == "endurtimerindex") { return spells[spell_id].timer_id; } + else if (id == "isdisciplinebuff") { return spells[spell_id].is_discipline; } + else if (id == "hateadded") { return spells[spell_id].hate_added; } + else if (id == "endurupkeep") { return spells[spell_id].endurance_upkeep; } + else if (id == "numhitstype") { return spells[spell_id].hit_number_type; } + else if (id == "numhits") { return spells[spell_id].hit_number; } + else if (id == "pvpresistbase") { return spells[spell_id].pvp_resist_base; } + else if (id == "pvpresistcalc") { return spells[spell_id].pvp_resist_per_level; } + else if (id == "pvpresistcap") { return spells[spell_id].pvp_resist_cap; } + else if (id == "spell_category") { return spells[spell_id].spell_category; } + else if (id == "can_mgb") { return spells[spell_id].can_mgb; } + else if (id == "dispel_flag") { return spells[spell_id].dispel_flag; } + else if (id == "minresist") { return spells[spell_id].min_resist; } + else if (id == "maxresist") { return spells[spell_id].max_resist; } + else if (id == "viral_targets") { return spells[spell_id].viral_targets; } + else if (id == "viral_timer") { return spells[spell_id].viral_timer; } + else if (id == "nimbuseffect") { return spells[spell_id].nimbus_effect; } + else if (id == "directional_start") { return static_cast(spells[spell_id].directional_start); } + else if (id == "directional_end") { return static_cast(spells[spell_id].directional_end); } + else if (id == "not_focusable") { return spells[spell_id].not_focusable; } + else if (id == "suspendable") { return spells[spell_id].suspendable; } + else if (id == "viral_range") { return spells[spell_id].viral_range; } + else if (id == "spellgroup") { return spells[spell_id].spell_group; } + else if (id == "rank") { return spells[spell_id].rank; } + else if (id == "no_resist") { return spells[spell_id].no_resist; } + else if (id == "castrestriction") { return spells[spell_id].cast_restriction; } + else if (id == "allowrest") { return spells[spell_id].allow_rest; } + else if (id == "incombat") { return spells[spell_id].can_cast_in_combat; } + else if (id == "outofcombat") { return spells[spell_id].can_cast_out_of_combat; } + else if (id == "aemaxtargets") { return spells[spell_id].aoe_max_targets; } + else if (id == "no_heal_damage_item_mod") { return spells[spell_id].no_heal_damage_item_mod; } + else if (id == "persistdeath") { return spells[spell_id].persist_death; } + else if (id == "min_dist") { return static_cast(spells[spell_id].min_distance); } + else if (id == "min_dist_mod") { return static_cast(spells[spell_id].min_distance_mod); } + else if (id == "max_dist") { return static_cast(spells[spell_id].max_distance); } + else if (id == "min_range") { return static_cast(spells[spell_id].min_range); } + else if (id == "damageshieldtype") { return spells[spell_id].damage_shield_type; } + + return 0; +} + +bool IsVirusSpell(int32 spell_id) +{ + if (GetViralMinSpreadTime(spell_id) && GetViralMaxSpreadTime(spell_id) && GetViralSpreadRange(spell_id)){ + return true; + } + return false; +} + +int32 GetViralMinSpreadTime(int32 spell_id) +{ + return spells[spell_id].viral_targets; +} + +int32 GetViralMaxSpreadTime(int32 spell_id) +{ + return spells[spell_id].viral_timer; +} + +int32 GetViralSpreadRange(int32 spell_id) +{ + return spells[spell_id].viral_range; +} + +uint32 GetProcLimitTimer(int32 spell_id, int proc_type) { + + //This allows for support for effects that may have multiple different proc types and timers. + if (!IsValidSpell(spell_id)) { + return 0; + } + + bool use_next_timer = false; + for (int i = 0; i < EFFECT_COUNT; ++i) { + + if (proc_type == ProcType::MELEE_PROC) { + if (spells[spell_id].effect_id[i] == SE_WeaponProc || spells[spell_id].effect_id[i] == SE_AddMeleeProc) { + use_next_timer = true; + } + } + + if (proc_type == ProcType::RANGED_PROC) { + if (spells[spell_id].effect_id[i] == SE_RangedProc) { + use_next_timer = true; + } + } + + if (proc_type == ProcType::DEFENSIVE_PROC) { + if (spells[spell_id].effect_id[i] == SE_DefensiveProc) { + use_next_timer = true; + } + } + + if (use_next_timer && spells[spell_id].effect_id[i] == SE_Proc_Timer_Modifier) { + return spells[spell_id].limit_value[i]; + } + } + return 0; +} + +bool CastRestrictedSpell(int spellid) +{ + switch (spellid) { + case SPELL_TOUCH_OF_VINITRAS: + case SPELL_DESPERATE_HOPE: + case SPELL_CHARM: + case SPELL_METAMORPHOSIS65: + case SPELL_JT_BUFF: + case SPELL_CAN_O_WHOOP_ASS: + case SPELL_PHOENIX_CHARM: + case SPELL_CAZIC_TOUCH: + case SPELL_AVATAR_KNOCKBACK: + case SPELL_SHAPECHANGE65: + case SPELL_SUNSET_HOME1218: + case SPELL_SUNSET_HOME819: + case SPELL_SHAPECHANGE75: + case SPELL_SHAPECHANGE80: + case SPELL_SHAPECHANGE85: + case SPELL_SHAPECHANGE90: + case SPELL_SHAPECHANGE95: + case SPELL_SHAPECHANGE100: + case SPELL_SHAPECHANGE25: + case SPELL_SHAPECHANGE30: + case SPELL_SHAPECHANGE35: + case SPELL_SHAPECHANGE40: + case SPELL_SHAPECHANGE45: + case SPELL_SHAPECHANGE50: + case SPELL_NPC_AEGOLISM: + case SPELL_SHAPECHANGE55: + case SPELL_SHAPECHANGE60: + case SPELL_COMMAND_OF_DRUZZIL: + case SPELL_SHAPECHANGE70: + return true; + default: + return false; + } +} + +bool IgnoreCastingRestriction(int32 spell_id) { + /* + field 'cast_not_standing' allows casting when sitting, stunned, mezed, Divine Aura, through SPA 343 Interrupt casting + Likely also allows for casting while feared, but need to confirm. Possibly also while charmed. + This field also allows for damage to ignore DA immunity. + */ + if (spells[spell_id].cast_not_standing) { + return true; + } + return false; +} diff --git a/common/spdat.h b/common/spdat.h index d6d6ef144..caa80ce54 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -26,26 +26,530 @@ #define SPELLBOOK_UNKNOWN 0xFFFFFFFF //player profile spells are 32 bit //some spell IDs which will prolly change, but are needed +#define SPELL_LIFEBURN 2755 #define SPELL_LEECH_TOUCH 2766 #define SPELL_LAY_ON_HANDS 87 #define SPELL_HARM_TOUCH 88 #define SPELL_HARM_TOUCH2 2821 #define SPELL_IMP_HARM_TOUCH 2774 #define SPELL_NPC_HARM_TOUCH 929 +#define SPELL_AVATAR_ST_PROC 2434 +#define SPELL_CAZIC_TOUCH 982 +#define SPELL_TOUCH_OF_VINITRAS 2859 +#define SPELL_DESPERATE_HOPE 841 +#define SPELL_CHARM 300 +#define SPELL_METAMORPHOSIS65 2314 +#define SPELL_JT_BUFF 3716 +#define SPELL_CAN_O_WHOOP_ASS 911 +#define SPELL_PHOENIX_CHARM 3014 +#define SPELL_AVATAR_KNOCKBACK 905 +#define SPELL_SHAPECHANGE65 2079 +#define SPELL_SUNSET_HOME1218 1218 +#define SPELL_SUNSET_HOME819 819 +#define SPELL_SHAPECHANGE75 780 +#define SPELL_SHAPECHANGE80 781 +#define SPELL_SHAPECHANGE85 782 +#define SPELL_SHAPECHANGE90 783 +#define SPELL_SHAPECHANGE95 784 +#define SPELL_SHAPECHANGE100 785 +#define SPELL_SHAPECHANGE25 1200 +#define SPELL_SHAPECHANGE30 1201 +#define SPELL_SHAPECHANGE35 1202 +#define SPELL_SHAPECHANGE40 1203 +#define SPELL_SHAPECHANGE45 1204 +#define SPELL_SHAPECHANGE50 1205 +#define SPELL_NPC_AEGOLISM 1343 +#define SPELL_SHAPECHANGE55 1923 +#define SPELL_SHAPECHANGE60 1924 +#define SPELL_COMMAND_OF_DRUZZIL 3355 +#define SPELL_SHAPECHANGE70 6503 +#define SPELL_MANA_BURN 2751 +#define SPELL_LIFE_BURN 2755 +#define SPELL_TOUCH_OF_THE_DIVINE 4789 +// these have known hardcoded behavior but we don't do anything yet, move them above this comment when fixed +#define SPELL_THE_DAINS_JUSTICE 1476 +#define SPELL_MODULATION 1502 +#define SPELL_TORPOR 1576 +#define SPELL_SPLURT 1620 +#define SPELL_SEBILITE_POX 1814 +#define SPELL_SOUL_WELL 1816 +#define SPELL_MYSTICAL_TRANSVERGENCE 2716 +#define SPELL_ACT_OF_VALOR 2775 +#define SPELL_STOICISM 3694 +#define SPELL_ALTER_PLANE_HATE 666 +#define SPELL_ALTER_PLANE_SKY 674 +#define SPELL_DENONS_DESPERATE_DIRGE 742 +#define SPELL_BOND_OF_SATHIR 833 +#define SPELL_DISEASED_CLOUD 836 +#define SPELL_ACTING_RESIST 775 +#define SPELL_ACTING_SHIELD 776 +#define SPELL_ACTING_GUARD 777 +#define SPELL_GUIDE_ACTING 778 +#define SPELL_BYE_BYE 779 +#define SPELL_ACTING_RESIST_II 1206 +#define SPELL_ACTING_SHIELD_II 1207 +#define SPELL_ACTING_GUARD_II 1208 +#define SPELL_GUIDE_ACTING2 1209 +#define SPELL_BYE_BYTE2 1210 +#define SPELL_GUIDE_CANCEL_MAGIC 1211 +#define SPELL_GUIDE_JOURNEY 1212 +#define SPELL_GUIDE_VISION 1213 +#define SPELL_GUIDE_HEALTH 1214 +#define SPELL_GUIDE_INVULNERABILITY 1215 +#define SPELL_GUIDE_BOLT 1216 +#define SPELL_GUIDE_MEMORY_BLUE 1217 +#define SPELL_GUIDE_ALLIANCE 1219 +#define SPELL_SPECIAL_SIGHT 1220 +#define SPELL_TERROR_OF_DARKNESS 1221 +#define SPELL_TERROR_OF_SHADOWS 1222 +#define SPELL_TERROR_OF_DEATH 1223 +#define SPELL_TERROR_OF_TERRIS 1224 +#define SPELL_VOICE_OF_DARKNESS 1225 +#define SPELL_VOICE_OF_SHADOWS 1226 +#define SPELL_VOICE_OF_DEATH 1227 +#define SPELL_VOICE_OF_TERRIS 1228 +#define SPELL_VENGEANCE_V 1229 +#define SPELL_VENGEANCE_VII 1230 +#define SPELL_VENGEANCE_VIII 1231 +#define SPELL_VENGEANCE_IX 1232 +#define SPELL_CORRUPTED_LACERATION 1233 +#define SPELL_VISIONS_OF_CHAOS 1234 +#define SPELL_VISIONS_OF_PAIN 1235 +#define SPELL_COMMANDING_PRESENCE 1236 +#define SPELL_MALICIOUS_INTENT 1237 +#define SPELL_CURSE_OF_FLAMES 1238 +#define SPELL_DEVOURING_CONFLAGRATION 1239 +#define SPELL_AVATAR_SHIELD 1240 +#define SPELL_AVATAR_SIGHT 1241 +#define SPELL_AVATAR_GUARD 1242 +#define SPELL_AVATAR_RESIST 1243 +#define SPELL_MAGI_BOLT 1244 +#define SPELL_MAGI_STRIKE 1245 +#define SPELL_MAGI_CURSE 1246 +#define SPELL_MAGI_CIRCLE 1247 +#define SPELL_SPIRITUAL_ECHO 1248 +#define SPELL_BRISTLING_ARMAMENT 1249 +#define SPELL_WATON_DESTRUCTION 1250 +#define SPELL_TRANSLOCATE_GROUP 1334 +#define SPELL_TRANSLOCATE 1422 +#define SPELL_ACTING_MAGIC_RESIST_I 1900 +#define SPELL_ACTING_FIRE_RESIST_I 1901 +#define SPELL_ACTING_COLD_RESIST_I 1902 +#define SPELL_ACTING_POISON_RESIST_I 1903 +#define SPELL_ACTING_DISEASE_RESIST_I 1904 +#define SPELL_ACTING_MAGIC_RESIST_II 1905 +#define SPELL_ACTING_FIRE_RESIST_II 1906 +#define SPELL_ACTING_COLD_RESIST_II 1907 +#define SPELL_ACTING_POISON_RESIST_II 1908 +#define SPELL_ACTING_DISEASE_RESIST_II 1909 +#define SPELL_ACTING_FIRE_SHIELD 1910 +#define SPELL_ACTING_POISON_SHIELD 1911 +#define SPELL_ACTING_COLD_SHIELD 1912 +#define SPELL_ACTING_DISEASE_SHIELD 1913 +#define SPELL_ACTING_ARMOR_I 1914 +#define SPELL_ACTING_ARMOR_II 1915 +#define SPELL_ACTING_ARMOR_III 1916 +#define SPELL_ACTING_HEALTH_I 1917 +#define SPELL_ACTING_HEALTH_II 1918 +#define SPELL_ACTING_HEALTH_III 1919 +#define SPELL_ACTING_HEALTH_IV 1920 +#define SPELL_ACTING_SPIRIT_I 1921 +#define SPELL_ACTING_SPIRIT_II 1922 +#define SPELL_RESURRECTION_SICKNESS 756 +#define SPELL_RESURRECTION_SICKNESS4 757 +#define SPELL_TELEPORT 3243 +#define SPELL_RESURRECTION_SICKNESS2 5249 +#define SPELL_REVIVAL_SICKNESS 13087 +#define SPELL_RESURRECTION_SICKNESS3 37624 +#define SPELL_PACT_OF_HATE_RECOURSE 40375 +#define SPELL_INCENDIARY_OOZE_BUFF 32513 +#define SPELL_EYE_OF_ZOMM 323 +#define SPELL_MINOR_ILLUSION 287 +#define SPELL_ILLUSION_TREE 601 +#define SPELL_ILLUSION_FEMALE 1731 +#define SPELL_ILLUSION_MALE 1732 +#define SPELL_UNSUMMON_SELF 892 +//spellgroup ids +#define SPELLGROUP_FRENZIED_BURNOUT 2754 +#define SPELLGROUP_ILLUSION_OF_GRANDEUR 38603 +#define SPELLGROUP_ROGUES_FURY 16861 +#define SPELLGROUP_HARMONIOUS_PRECISION 15634 +#define SPELLGROUP_HARMONIOUS_EXPANSE 15633 +#define SPELLGROUP_FURIOUS_RAMPAGE 38106 +#define SPELLGROUP_SHROUD_OF_PRAYER 41050 #define EFFECT_COUNT 12 #define MAX_SPELL_TRIGGER 12 // One for each slot(only 6 for AA since AA use 2) #define MAX_RESISTABLE_EFFECTS 12 // Number of effects that are typcially checked agianst resists. -#define MaxLimitInclude 16 //Number(x 0.5) of focus Limiters that have inclusive checks used when calcing focus effects +#define MaxLimitInclude 18 //Number(x 0.5) of focus Limiters that have inclusive checks used when calcing focus effects #define MAX_SKILL_PROCS 4 //Number of spells to check skill procs from. (This is arbitrary) [Single spell can have multiple proc checks] +#define MAX_AA_PROCS 16 //(Actual Proc Amount is MAX_AA_PROCS/4) Number of spells to check AA procs from. (This is arbitrary) #define MAX_SYMPATHETIC_PROCS 10 // Number of sympathetic procs a client can have (This is arbitrary) +#define MAX_FOCUS_PROC_LIMIT_TIMERS 20 //Number of focus recast timers that can be going at same time (This is arbitrary) +#define MAX_PROC_LIMIT_TIMERS 8 //Number of proc delay timers that can be going at same time, different proc types get their own timer array. (This is arbitrary) +#define MAX_APPEARANCE_EFFECTS 20 //Up to 20 Appearance Effects can be saved to a mobs appearance effect array, these will be sent to other clients when they enter a zone (This is arbitrary) +#define MAX_CAST_ON_SKILL_USE 36 //Actual amount is MAX/3 + +//instrument item id's used as song components +#define INSTRUMENT_HAND_DRUM 13000 +#define INSTRUMENT_WOODEN_FLUTE 13001 +#define INSTRUMENT_LUTE 13011 +#define INSTRUMENT_HORN 13012 const int Z_AGGRO=10; const uint32 MobAISpellRange=100; // max range of buffs +enum FocusLimitIncludes { + IncludeExistsSELimitResist = 0, + IncludeFoundSELimitResist = 1, + IncludeExistsSELimitSpell = 2, + IncludeFoundSELimitSpell = 3, + IncludeExistsSELimitEffect = 4, + IncludeFoundSELimitEffect = 5, + IncludeExistsSELimitTarget = 6, + IncludeFoundSELimitTarget = 7, + IncludeExistsSELimitSpellGroup = 8, + IncludeFoundSELimitSpellGroup = 9, + IncludeExistsSELimitCastingSkill = 10, + IncludeFoundSELimitCastingSkill = 11, + IncludeExistsSELimitSpellClass = 12, + IncludeFoundSELimitSpellClass = 13, + IncludeExistsSELimitSpellSubclass = 14, + IncludeFoundSELimitSpellSubclass = 15, + IncludeExistsSEFFItemClass = 16, + IncludeFoundSEFFItemClass = 17 +}; +/* + The id's correspond to 'type' 39 in live(2021) dbstr_us gives the message for target and caster restricted effects. These are not present in the ROF2 dbstr_us. + If from CasterRestriction spell field. "Your target does not meet the spell requirements. ." Msg in combat window, color red. + If set as limit in a direct damage or heal spell (SPA 0) do not give message. +*/ +enum SpellRestriction +{ + UNKNOWN_3 = 3, // | caster restriction | seen in spell 30183 Mind Spiral + IS_NOT_ON_HORSE = 5, // | caster restriction | + IS_ANIMAL_OR_HUMANOID = 100, // This spell will only work on animals or humanoid creatures. + IS_DRAGON = 101, // This spell will only work on dragons. + IS_ANIMAL_OR_INSECT = 102, // This spell will only work on animals or insects. + IS_BODY_TYPE_MISC = 103, // This spell will only work on humanoids, lycanthropes, giants, Kael Drakkel giants, Coldain, animals, insects, constructs, dragons, Skyshrine dragons, Muramites, or creatures constructed from magic. + IS_BODY_TYPE_MISC2 = 104, // This spell will only work on humanoids, lycanthropes, giants, Kael Drakkel giants, Coldain, animals, or insects. + IS_PLANT = 105, // This spell will only work on plants. + IS_GIANT = 106, // This spell will only work on animals. | Live used to have this on spells restricted to Giants, but those spells were removed... We still have them + IS_NOT_ANIMAL_OR_HUMANOID = 108, // This spell will only work on targets that are neither animals or humanoid. + IS_BIXIE = 109, // This spell will only work on bixies. + IS_HARPY = 110, // This spell will only work on harpies. + IS_GNOLL = 111, // This spell will only work on gnolls. + IS_SPORALI = 112, // This spell will only work on fungusoids. + IS_KOBOLD = 113, // This spell will only work on kobolds. + IS_FROSTCRYPT_SHADE = 114, // This spell will only work on undead creatures or the Shades of Frostcrypt. + IS_DRAKKIN = 115, // This spell will only work on Drakkin. + IS_UNDEAD_OR_VALDEHOLM_GIANT = 116, // This spell will only work on undead creatures or the inhabitants of Valdeholm. + IS_ANIMAL_OR_PLANT = 117, // This spell will only work on plants or animals. + IS_SUMMONED = 118, // This spell will only work on constructs, elementals, or summoned elemental minions. + IS_WIZARD_USED_ON_MAGE_FIRE_PET = 119, // This spell will only work on wizards. | Live uses this on high level mage fire pets, which are wizard class + IS_UNDEAD = 120, // + IS_NOT_UNDEAD_OR_SUMMONED_OR_VAMPIRE = 121, // This spell will only work on creatures that are not undead, constructs, elementals, or vampires. + IS_FAE_OR_PIXIE = 122, // This spell will only work on Fae or pixies. + IS_HUMANOID = 123, // + IS_UNDEAD_AND_HP_LESS_THAN_10_PCT = 124, // The Essence Extractor whirrs but does not light up. + IS_CLOCKWORK_AND_HP_LESS_THAN_45_PCT = 125, // This spell will only work on clockwork gnomes. + IS_WISP_AND_HP_LESS_THAN_10_PCT = 126, // This spell will only work on wisps at or below 10% of their maximum HP. + IS_CLASS_MELEE_THAT_CAN_BASH_OR_KICK_EXCEPT_BARD = 127, // This spell will only work on non-bard targets that can bash or kick. + IS_CLASS_PURE_MELEE = 128, // This spell will only affect melee classes (warriors, monks, rogues, and berserkers). + IS_CLASS_PURE_CASTER = 129, // This spell will only affect pure caster classes (necromancers, wizards, magicians, and enchanters). + IS_CLASS_HYBRID_CLASS = 130, // This spell will only affect hybrid classes (paladins, rangers, shadow knights, bards, and beastlords). + IS_CLASS_WARRIOR = 131, // This spell will only affect Warriors. + IS_CLASS_CLERIC = 132, // This spell will only affect Clerics. + IS_CLASS_PALADIN = 133, // This spell will only affect Paladins. + IS_CLASS_RANGER = 134, // This spell will only affect Rangers. + IS_CLASS_SHADOWKNIGHT = 135, // This spell will only affect Shadow Knights. + IS_CLASS_DRUID = 136, // This spell will only affect Druids. + IS_CLASS_MONK = 137, // This spell will only affect Monks. + IS_CLASS_BARD = 138, // This spell will only affect Bards. + IS_CLASS_ROGUE = 139, // This spell will only affect Rogues. + IS_CLASS_SHAMAN = 140, // This spell will only affect Shamans. + IS_CLASS_NECRO = 141, // This spell will only affect Necromancers. + IS_CLASS_WIZARD = 142, // This spell will only affect Wizards. + IS_CLASS_MAGE = 143, // This spell will only affect Magicians. + IS_CLASS_ENCHANTER = 144, // This spell will only affect Enchanters. + IS_CLASS_BEASTLORD = 145, // This spell will only affect Beastlords. + IS_CLASS_BERSERKER = 146, // This spell will only affect Berserkers. + IS_CLASS_CLR_SHM_DRU = 147, // This spell will only affect priest classes (clerics, druids, and shaman). + IS_CLASS_NOT_WAR_PAL_SK = 148, // This spell will not affect Warriors, Paladins, or Shadow Knights. + IS_LEVEL_UNDER_100 = 150, // This spell will not affect any target over level 100. + IS_NOT_RAID_BOSS = 190, // This spell will not affect raid bosses. + IS_RAID_BOSS = 191, // This spell will only affect raid bosses. + FRENZIED_BURNOUT_ACTIVE = 192, // This spell will only cast if you have Frenzied Burnout active. + FRENZIED_BURNOUT_NOT_ACTIVE = 193, // This spell will only cast if you do not have Frenzied Burnout active. + UNKNOWN_199 = 199, // + IS_HP_ABOVE_75_PCT = 201, // + IS_HP_LESS_THAN_20_PCT = 203, // Your target's HP must be at 20% of its maximum or below. | caster restriction | + IS_HP_LESS_THAN_50_PCT = 204, // Your target's HP must be at 50% of its maximum or below. | caster restriction | + IS_HP_LESS_THAN_75_PCT = 205, // Your target's HP must be at 75% of its maximum or below. + IS_NOT_IN_COMBAT = 216, // This spell will only affect creatures that are not in combat. + HAS_AT_LEAST_1_PET_ON_HATELIST = 221, // + HAS_AT_LEAST_2_PETS_ON_HATELIST = 222, // + HAS_AT_LEAST_3_PETS_ON_HATELIST = 223, // + HAS_AT_LEAST_4_PETS_ON_HATELIST = 224, // + HAS_AT_LEAST_5_PETS_ON_HATELIST = 225, // + HAS_AT_LEAST_6_PETS_ON_HATELIST = 226, // + HAS_AT_LEAST_7_PETS_ON_HATELIST = 227, // + HAS_AT_LEAST_8_PETS_ON_HATELIST = 228, // + HAS_AT_LEAST_9_PETS_ON_HATELIST = 229, // + HAS_AT_LEAST_10_PETS_ON_HATELIST = 230, // + HAS_AT_LEAST_11_PETS_ON_HATELIST = 231, // + HAS_AT_LEAST_12_PETS_ON_HATELIST = 232, // + HAS_AT_LEAST_13_PETS_ON_HATELIST = 233, // + HAS_AT_LEAST_14_PETS_ON_HATELIST = 234, // + HAS_AT_LEAST_15_PETS_ON_HATELIST = 235, // + HAS_AT_LEAST_16_PETS_ON_HATELIST = 236, // + HAS_AT_LEAST_17_PETS_ON_HATELIST = 237, // + HAS_AT_LEAST_18_PETS_ON_HATELIST = 238, // + HAS_AT_LEAST_19_PETS_ON_HATELIST = 239, // + HAS_AT_LEAST_20_PETS_ON_HATELIST = 240, // + IS_HP_LESS_THAN_35_PCT = 250, // Your target's HP must be at 35% of its maximum or below. + HAS_BETWEEN_1_TO_2_PETS_ON_HATELIST = 260, // between 1 and 2 pets + HAS_BETWEEN_3_TO_5_PETS_ON_HATELIST = 261, // between 3 and 5 pets + HAS_BETWEEN_6_TO_9_PETS_ON_HATELIST = 262, // between 6 and 9 pets + HAS_BETWEEN_10_TO_14_PETS_ON_HATELIST = 263, // between 10 and 14 pets + HAS_MORE_THAN_14_PETS_ON_HATELIST = 264, // 15 or more pets + IS_CLASS_CHAIN_OR_PLATE = 304, // This spell will only affect plate or chain wearing classes. + IS_HP_BETWEEN_5_AND_9_PCT = 350, // Your target's HP must be between 5% and 9% of its maximum. + IS_HP_BETWEEN_10_AND_14_PCT = 351, // Your target's HP must be between 10% and 14% of its maximum. + IS_HP_BETWEEN_15_AND_19_PCT = 352, // Your target's HP must be between 15% and 19% of its maximum. + IS_HP_BETWEEN_20_AND_24_PCT = 353, // Your target's HP must be between 20% and 24% of its maximum. + IS_HP_BETWEEN_25_AND_29_PCT = 354, // Your target's HP must be between 25% and 29% of its maximum. + IS_HP_BETWEEN_30_AND_34_PCT = 355, // Your target's HP must be between 30% and 34% of its maximum. + IS_HP_BETWEEN_35_AND_39_PCT = 356, // Your target's HP must be between 35% and 39% of its maximum. + IS_HP_BETWEEN_40_AND_44_PCT = 357, // Your target's HP must be between 40% and 44% of its maximum. + IS_HP_BETWEEN_45_AND_49_PCT = 358, // Your target's HP must be between 45% and 49% of its maximum. + IS_HP_BETWEEN_50_AND_54_PCT = 359, // Your target's HP must be between 50% and 54% of its maximum. + IS_HP_BETWEEN_55_AND_59_PCT = 360, // Your target's HP must be between 55% and 59% of its maximum. + IS_HP_BETWEEN_5_AND_15_PCT = 398, // Your target's HP must be between 5% and 15% of its maximum. + IS_HP_BETWEEN_15_AND_25_PCT = 399, // Your target's HP must be between 15% and 25% of its maximum. + IS_HP_BETWEEN_1_AND_25_PCT = 400, // Your target's HP must be at 25% of its maximum or below. + IS_HP_BETWEEN_25_AND_35_PCT = 401, // Your target's HP must be between 25% and 35% of its maximum. + IS_HP_BETWEEN_35_AND_45_PCT = 402, // Your target's HP must be between 35% and 45% of its maximum. + IS_HP_BETWEEN_45_AND_55_PCT = 403, // Your target's HP must be between 45% and 55% of its maximum. + IS_HP_BETWEEN_55_AND_65_PCT = 404, // Your target's HP must be between 55% and 65% of its maximum. + IS_HP_BETWEEN_65_AND_75_PCT = 405, // Your target's HP must be between 65% and 75% of its maximum. + IS_HP_BETWEEN_75_AND_85_PCT = 406, // Your target's HP must be between 75% and 85% of its maximum. + IS_HP_BETWEEN_85_AND_95_PCT = 407, // Your target's HP must be between 85% and 95% of its maximum. + IS_HP_ABOVE_45_PCT = 408, // Your target's HP must be at least 45% of its maximum. + IS_HP_ABOVE_55_PCT = 409, // Your target's HP must be at least 55% of its maximum. + UNKNOWN_TOO_MUCH_HP_410 = 410, // Your target has too much HP to be affected by this spell. + UNKNOWN_TOO_MUCH_HP_411 = 411, // Your target has too much HP to be affected by this spell. + IS_HP_ABOVE_99_PCT = 412, // + IS_MANA_ABOVE_10_PCT = 429, // You must have at least 10% of your maximum mana available to cast this spell. | caster restriction | + IS_HP_BELOW_5_PCT = 501, // + IS_HP_BELOW_10_PCT = 502, // + IS_HP_BELOW_15_PCT = 503, // + IS_HP_BELOW_20_PCT = 504, // Your target's HP must be at 20% of its maximum or below. + IS_HP_BELOW_25_PCT = 505, // + IS_HP_BELOW_30_PCT = 506, // + IS_HP_BELOW_35_PCT = 507, // + IS_HP_BELOW_40_PCT = 508, // + IS_HP_BELOW_45_PCT = 509, // Your target's HP must be at 45% of its maximum or below. + IS_HP_BELOW_50_PCT = 510, // + IS_HP_BELOW_55_PCT = 511, // + IS_HP_BELOW_60_PCT = 512, // + IS_HP_BELOW_65_PCT = 513, // + IS_HP_BELOW_70_PCT = 514, // + IS_HP_BELOW_75_PCT = 515, // + IS_HP_BELOW_80_PCT = 516, // + IS_HP_BELOW_85_PCT = 517, // + IS_HP_BELOW_90_PCT = 518, // This ability requires you to be at or below 90% of your maximum HP. | caster restriction | + IS_HP_BELOW_95_PCT = 519, // + IS_MANA_BELOW_UNKNOWN_PCT = 521, // + IS_ENDURANCE_BELOW_40_PCT = 522, // + IS_MANA_BELOW_40_PCT = 523, // + IS_HP_ABOVE_20_PCT = 524, // Your target's HP must be at least 21% of its maximum. + IS_BODY_TYPE_UNDEFINED = 600, // This spell will only work on creatures with an undefined body type. + IS_BODY_TYPE_HUMANOID = 601, // This spell will only work on humanoid creatures. + IS_BODY_TYPE_WEREWOLF = 602, // This spell will only work on lycanthrope creatures. + IS_BODY_TYPE_UNDEAD = 603, // This spell will only work on undead creatures. + IS_BODY_TYPE_GIANTS = 604, // This spell will only work on giants. + IS_BODY_TYPE_CONSTRUCTS = 605, // This spell will only work on constructs. + IS_BODY_TYPE_EXTRAPLANAR = 606, // This spell will only work on extraplanar creatures. + IS_BODY_TYPE_MAGICAL_CREATURE = 607, // This spell will only work on creatures constructed from magic. + IS_BODY_TYPE_UNDEADPET = 608, // This spell will only work on animated undead servants. + IS_BODY_TYPE_KAELGIANT = 609, // This spell will only work on the Giants of Kael Drakkal. + IS_BODY_TYPE_COLDAIN = 610, // This spell will only work on Coldain Dwarves. + IS_BODY_TYPE_VAMPIRE = 612, // This spell will only work on vampires. + IS_BODY_TYPE_ATEN_HA_RA = 613, // This spell will only work on Aten Ha Ra. + IS_BODY_TYPE_GREATER_AHKEVANS = 614, // This spell will only work on Greater Ahkevans. + IS_BODY_TYPE_KHATI_SHA = 615, // This spell will only work on Khati Sha. + IS_BODY_TYPE_LORD_INQUISITOR_SERU = 616, // This spell will only work on Lord Inquisitor Seru. + IS_BODY_TYPE_GRIEG_VENEFICUS = 617, // This spell will only work on Grieg Veneficus. + IS_BODY_TYPE_FROM_PLANE_OF_WAR = 619, // This spell will only work on creatures from the Plane of War. + IS_BODY_TYPE_LUGGALD = 620, // This spell will only work on Luggalds. + IS_BODY_TYPE_ANIMAL = 621, // This spell will only work on animals. + IS_BODY_TYPE_INSECT = 622, // This spell will only work on insects. + IS_BODY_TYPE_MONSTER = 623, // This spell will only work on monsters. + IS_BODY_TYPE_ELEMENTAL = 624, // This spell will only work on elemental creatures. + IS_BODY_TYPE_PLANT = 625, // This spell will only work on plants. + IS_BODY_TYPE_DRAGON2 = 626, // This spell will only work on dragons. + IS_BODY_TYPE_SUMMONED_ELEMENTAL = 627, // This spell will only work on summoned elementals. + IS_BODY_TYPE_WARDER = 628, // + IS_BODY_TYPE_DRAGON_OF_TOV = 630, // This spell will only work on Dragons of Veeshan's Temple. + IS_BODY_TYPE_FAMILIAR = 631, // This spell will only work on familiars. + IS_BODY_TYPE_MURAMITE = 634, // This spell will only work on Muramites. + IS_NOT_UNDEAD_OR_SUMMONED = 635, // + IS_NOT_PLANT = 636, // This spell will not affect plants. + IS_NOT_CLIENT = 700, // This spell will not work on adventurers. + IS_CLIENT = 701, // This spell will only work on adventurers. + IS_LEVEL_ABOVE_42_AND_IS_CLIENT = 800, // This spell will only work on level 43 or higher adventurers. + UNKNOWN_812 = 812, // | seen in spell 22616 Thaumatize Pet Mana Regen Base | + UNKNOWN_814 = 814, // | seen in spell 22704 Vegetentacles I | + IS_TREANT = 815, // This spell will only work on treants. + IS_BIXIE2 = 816, // This spell will only work on bixies. + IS_SCARECROW = 817, // This spell will only work on scarecrows. + IS_VAMPIRE_OR_UNDEAD_OR_UNDEADPET = 818, // This spell will only work on vampires, undead, or animated undead creatures. + IS_NOT_VAMPIRE_OR_UNDEAD = 819, // This spell will not work on vampires or undead creatures. + IS_CLASS_KNIGHT_HYBRID_MELEE = 820, // This spell will only work on knights, hybrids, or melee classes. + IS_CLASS_WARRIOR_CASTER_PRIEST = 821, // This spell will only work on warriors, casters, or priests. + UNKNOWN_822 = 822, // | seen in spell 22870 Morell's Distraction 822 | + IS_END_BELOW_21_PCT = 825, // This ability requires you to be at or below 21% of your maximum endurance. + IS_END_BELOW_25_PCT = 826, // This ability requires you to be at or below 25% of your maximum endurance. + IS_END_BELOW_29_PCT = 827, // This ability requires you to be at or below 29% of your maximum endurance. + IS_REGULAR_SERVER = 836, // + IS_PROGRESSION_SERVER = 837, // + IS_GOD_EXPANSION_UNLOCKED = 839, // + UNKNOWN_840 = 840, // | caster restriction | seen in spell 6883 Expedient Recovery + UNKNOWN_841 = 841, // | caster restriction | seen in spell 32192 Merciless Blow + IS_HUMANOID_LEVEL_84_MAX = 842, // + IS_HUMANOID_LEVEL_86_MAX = 843, // + IS_HUMANOID_LEVEL_88_MAX = 844, // + HAS_CRYSTALLIZED_FLAME_BUFF = 845, // This spell will only work on targets afflicted by Crystallized Flame. | On live spell does not appear to be a buff + HAS_INCENDIARY_OOZE_BUFF = 847, // This spell will only work on targets afflicted by Incendiary Ooze. + IS_LEVEL_90_MAX = 860, // + IS_LEVEL_92_MAX = 861, // + IS_LEVEL_94_MAX = 862, // + IS_LEVEL_95_MAX = 863, // + IS_LEVEL_97_MAX = 864, // + IS_LEVEL_99_MAX = 865, // + HAS_WEAPONSTANCE_DEFENSIVE_PROFICIENCY = 866, // | caster restriction | + HAS_WEAPONSTANCE_TWO_HAND_PROFICIENCY = 867, // | caster restriction | + HAS_WEAPONSTANCE_DUAL_WEILD_PROFICIENCY = 868, // | caster restriction | + IS_LEVEL_100_MAX = 869, // + IS_LEVEL_102_MAX = 870, // + IS_LEVEL_104_MAX = 871, // + IS_LEVEL_105_MAX = 872, // + IS_LEVEL_107_MAX = 873, // + IS_LEVEL_109_MAX = 874, // + IS_LEVEL_110_MAX = 875, // + IS_LEVEL_112_MAX = 876, // + IS_LEVEL_114_MAX = 877, // + HAS_TBL_ESIANTI_ACCESS = 997, // This spell will only transport adventurers who have gained access to Esianti: Palace of the Winds. | not implemented + HAS_ITEM_CLOCKWORK_SCRAPS = 999, // + IS_BETWEEN_LEVEL_1_AND_75 = 1000, // + IS_BETWEEN_LEVEL_76_AND_85 = 1001, // + IS_BETWEEN_LEVEL_86_AND_95 = 1002, // + IS_BETWEEN_LEVEL_96_AND_105 = 1003, // + IS_HP_LESS_THAN_80_PCT = 1004, // + IS_LEVEL_ABOVE_34 = 1474, // Your target must be level 35 or higher. + IN_TWO_HANDED_STANCE = 2000, // You must be in your two-handed stance to use this ability. + IN_DUAL_WIELD_HANDED_STANCE = 2001, // You must be in your dual-wielding stance to use this ability. + IN_SHIELD_STANCE = 2002, // You must be in your shield stance to use this ability. + NOT_IN_TWO_HANDED_STANCE = 2010, // You may not use this ability if you are in your two-handed stance. + NOT_IN_DUAL_WIELD_HANDED_STANCE = 2011, // You may not use this ability if you are in your dual-wielding stance. + NOT_IN_SHIELD_STANCE = 2012, // You may not use this ability if you are in your shield stance. + LEVEL_46_MAX = 2761, // + DISABLED_UNTIL_EXPANSION_ROK = 7000, // This ability is disabled until Ruins of Kunark. + DISABLED_UNTIL_EXPANSION_SOV = 7001, // This ability is disabled until Scars of Velious. + DISABLED_UNTIL_EXPANSION_SOL = 7002, // This ability is disabled until Shadows of Luclin. + DISABLED_UNTIL_EXPANSION_POP = 7003, // This ability is disabled until Planes of Power. + DISABLED_UNTIL_EXPANSION_LOY = 7004, // This ability is disabled until Legacy of Ykesha. + DISABLED_UNTIL_EXPANSION_LDON = 7005, // This ability is disabled until Lost Dungeons of Norrath. + DISABLED_UNTIL_EXPANSION_GOD = 7006, // This ability is disabled until Gates of Discord. + DISABLED_UNTIL_EXPANSION_OOW = 7007, // This ability is disabled until Omens of War. + DISABLED_UNTIL_EXPANSION_DON = 7008, // This ability is disabled until Dragons of Norrath. + DISABLED_UNTIL_EXPANSION_DOD = 7009, // This ability is disabled until Depths of Darkhollow. + DISABLED_UNTIL_EXPANSION_POR = 7010, // This ability is disabled until Prophecy of Ro. + DISABLED_UNTIL_EXPANSION_TSS = 7011, // This ability is disabled until Serpent's Spine. + DISABLED_UNTIL_EXPANSION_TBS = 7012, // This ability is disabled until Buried Sea. + DISABLED_UNTIL_EXPANSION_SOF = 7013, // This ability is disabled until Secrets of Faydwer. + DISABLED_UNTIL_EXPANSION_SOD = 7014, // This ability is disabled until Seeds of Destruction. + DISABLED_UNTIL_EXPANSION_UF = 7015, // This ability is disabled until Underfoot. + DISABLED_UNTIL_EXPANSION_HOT = 7016, // This ability is disabled until House of Thule. + DISABLED_UNTIL_EXPANSION_VOA = 7017, // This ability is disabled until Veil of Alaris. + DISABLED_UNTIL_EXPANSION_ROF = 7018, // This ability is disabled until Rain of Fear. + DISABLED_UNTIL_EXPANSION_COF = 7019, // This ability is disabled until Call of the Forsaken. + DISABLED_UNTIL_EXPANSION_TDS = 7020, // This ability is disabled until Darkened Sea. + DISABLED_UNTIL_EXPANSION_TBM = 7021, // This ability is disabled until Broken Mirror. + DISABLED_UNTIL_EXPANSION_EOK = 7022, // This ability is disabled until Empires of Kunark. + DISABLED_UNTIL_EXPANSION_ROS = 7023, // This ability is disabled until Ring of Scale. + DISABLED_UNTIL_EXPANSION_TBL = 7024, // This ability is disabled until The Burning Lands. + DISABLED_UNTIL_EXPANSION_TOV = 7025, // This ability is disabled until Torment of Velious. + DISABLED_UNTIL_EXPANSION_COV = 7026, // This ability is disabled until Claws of Veeshan. + HAS_NO_MANA_BURN_BUFF = 8450, // This spell will not take hold until the effects of the previous Mana Burn have expired. + IS_RACE_FIRST_CUSTOM = 10000, // | custom range to restrict targets or casters by race *not on live* | + IS_RACE_LAST_CUSTOM = 11000, // | custom range to restrict targets or casters by race *not on live* | + IS_CLIENT_AND_MALE_PLATE_USER = 11044, // Your target wouldn't look right as that Jann. + IS_CLEINT_AND_MALE_DRUID_ENCHANTER_MAGICIAN_NECROANCER_SHAMAN_OR_WIZARD = 11090, // Your target wouldn't look right as that Jann. + IS_CLIENT_AND_MALE_BEASTLORD_BERSERKER_MONK_RANGER_OR_ROGUE = 11209, // Your target wouldn't look right as that Jann. + IS_CLIENT_AND_FEMALE_PLATE_USER = 11210, // Your target wouldn't look right as that Jann. + IS_CLIENT_AND_FEMALE_DRUID_ENCHANTER_MAGICIAN_NECROANCER_SHAMAN_OR_WIZARD = 11211, // Your target wouldn't look right as that Jann. + IS_CLIENT_AND_FEMALE_BEASTLORD_BERSERKER_MONK_RANGER_OR_ROGUE = 11248, // Your target wouldn't look right as that Jann. + HAS_TRAVELED_TO_STRATOS = 11260, // You must travel to Stratos at least once before wishing to go there. + HAS_TRAVELED_TO_AALISHAI = 11261, // You must travel to Aalishai at least once before wishing to go there. + HAS_TRAVELED_TO_MEARATS = 11268, // You must travel to Mearatas at least once before wishing to go there. + HAS_NO_ILLUSIONS_OF_GRANDEUR_BUFF = 12519, // + IS_HP_ABOVE_50_PCT = 16010, // + IS_HP_UNDER_50_PCT = 16031, // + IS_OFF_HAND_EQUIPED = 27672, // You must be wielding a weapon or shield in your offhand to use this ability. + HAS_NO_PACT_OF_FATE_RECOURSE_BUFF = 29556, // This spell will not work while Pact of Fate Recourse is active. | caster restriction | + HAS_NO_SHROUD_OF_PRAYER_BUFF = 32339, // Your target cannot receive another Quiet Prayer this soon. + IS_MANA_BELOW_20_PCT = 38311, // This ability requires you to be at or below 20% of your maximum mana. + IS_MANA_ABOVE_50_PCT = 38312, // This ability requires you to be at or above 50% of your maximum mana. + COMPLETED_ACHIEVEMENT_LEGENDARY_ANSWERER = 39281, // You have completed Legendary Answerer. + HAS_NO_ROGUES_FURY_BUFF = 40297, // This spell will not affect anyone that currently has Rogue's Fury active. | caster restriction | + NOT_COMPLETED_ACHIEVEMENT_LEGENDARY_ANSWERER = 42280, // You must complete Legendary Answerer. + IS_SUMMONED_OR_UNDEAD = 49326, // + IS_CLASS_CASTER_PRIEST = 49529, // + IS_END_OR_MANA_ABOVE_20_PCT = 49543, // You must have at least 20% of your maximum mana and endurance to use this ability. //pure melee class check end, other check mana + IS_END_OR_MANA_BELOW_30_PCT = 49573, // Your target already has 30% or more of their maximum mana or endurance. //pure melee class check the, other check more + IS_CLASS_BARD2 = 49574, // + IS_NOT_CLASS_BARD = 49575, // + HAS_NO_FURIOUS_RAMPAGE_BUFF = 49612, // This ability cannot be activated while Furious Rampage is active. + IS_END_OR_MANA_BELOW_30_PCT2 = 49809, // You can only perform this solo if you have less than 30% mana or endurance. + HAS_NO_HARMONIOUS_PRECISION_BUFF = 50003, // This spell will not work if you have the Harmonious Precision line active. + HAS_NO_HARMONIOUS_EXPANSE_BUFF = 50009, // This spell will not work if you have the Harmonious Expanse line active. + UNKNOWN_99999 = 99999, // | caster restriction | works will spell 27672 Strike of Ire +}; + +enum NegateSpellEffectType +{ + NEGATE_SPA_ALL_BONUSES = 0, + NEGATE_SPA_SPELLBONUS = 1, + NEGATE_SPA_ITEMBONUS = 2, + NEGATE_SPA_SPELLBONUS_AND_ITEMBONUS = 3, + NEGATE_SPA_AABONUS = 4, + NEGATE_SPA_SPELLBONUS_AND_AABONUS = 5, + NEGATE_SPA_ITEMBONUS_AND_AABONUS = 6, +}; +//Used for rule RuleI(Spells, ReflectType)) +enum ReflectSpellType +{ + REFLECT_DISABLED = 0, + REFLECT_SINGLE_TARGET_SPELLS_ONLY = 1, + REFLECT_ALL_PLAYER_SPELLS = 2, + RELFECT_ALL_SINGLE_TARGET_SPELLS = 3, + REFLECT_ALL_SPELLS = 4, +}; +//For better organizing in proc effects, not used in spells. +enum ProcType +{ + MELEE_PROC = 1, + RANGED_PROC = 2, + DEFENSIVE_PROC = 3, + SKILL_PROC = 4, + SKILL_PROC_SUCCESS = 5, +}; + enum SpellTypes : uint32 { SpellType_Nuke = (1 << 0), @@ -83,7 +587,7 @@ const uint32 SPELL_TYPES_INNATE = (SpellType_Nuke | SpellType_Lifetap | SpellTyp // These should not be used to determine spell category.. // They are a graphical affects (effects?) index only // TODO: import sai list -enum SpellAffectIndex { +enum spell_affect_index { SAI_Summon_Mount_Unclass = -1, SAI_Direct_Damage = 0, SAI_Heal_Cure = 1, @@ -155,7 +659,7 @@ enum RESISTTYPE //Target Type IDs typedef enum { -/* 01 */ ST_TargetOptional = 0x01, +/* 01 */ ST_TargetOptional = 0x01, //only used for targeted projectile spells /* 02 */ ST_AEClientV1 = 0x02, /* 03 */ ST_GroupTeleport = 0x03, /* 04 */ ST_AECaster = 0x04, @@ -258,7 +762,7 @@ typedef enum { #define SE_PoisonCounter 36 // implemented //#define SE_DetectHostile 37 // not used //#define SE_DetectMagic 38 // not used -//#define SE_DetectPoison 39 // not used +#define SE_TwinCastBlocker 39 // implemented - If present in spell, then the spell can not be twincast. #define SE_DivineAura 40 // implemented #define SE_Destroy 41 // implemented - Disintegrate, Banishment of Shadows #define SE_ShadowStep 42 // implemented @@ -282,7 +786,7 @@ typedef enum { //#define SE_TransferItem 60 // not used #define SE_Identify 61 // implemented //#define SE_ItemID 62 // not used -#define SE_WipeHateList 63 // implemented +#define SE_WipeHateList 63 // implemented, @Memblur, chance to wipe hate list of target, base: pct chance, limit: none, max: ? (not implemented), Note: caster level and CHA add to pct chance #define SE_SpinTarget 64 // implemented - TO DO: Not sure stun portion is working correctly #define SE_InfraVision 65 // implemented #define SE_UltraVision 66 // implemented @@ -292,7 +796,7 @@ typedef enum { //#define SE_CorpseBomb 70 // not used #define SE_NecPet 71 // implemented //#define SE_PreserveCorpse 72 // not used -#define SE_BindSight 73 // implemented +#define SE_BindSight 73 // implemented, @Vision, see through the eyes of your target, click off buff to end effect, base: 1, limit: none, max: none #define SE_FeignDeath 74 // implemented #define SE_VoiceGraft 75 // implemented #define SE_Sentinel 76 // *not implemented?(just seems to send a message) @@ -337,56 +841,56 @@ typedef enum { #define SE_Hunger 115 // implemented - Song of Sustenance #define SE_CurseCounter 116 // implemented #define SE_MagicWeapon 117 // implemented - makes weapon magical -#define SE_Amplification 118 // implemented - Harmonize/Amplification (stacks with other singing mods) +#define SE_Amplification 118 // implemented, @Song, stackable singing mod, base: mod%, limit: none, max: none, Note: Can focus itself. #define SE_AttackSpeed3 119 // implemented #define SE_HealRate 120 // implemented - reduces healing by a % #define SE_ReverseDS 121 // implemented -//#define SE_ReduceSkill 122 // not used +//#define SE_ReduceSkill 122 // not implemented TODO: Now used on live, decreases skills by percent #define SE_Screech 123 // implemented Spell Blocker(If have buff with value +1 will block any effect with -1) #define SE_ImprovedDamage 124 // implemented #define SE_ImprovedHeal 125 // implemented #define SE_SpellResistReduction 126 // implemented -#define SE_IncreaseSpellHaste 127 // implemented -#define SE_IncreaseSpellDuration 128 // implemented -#define SE_IncreaseRange 129 // implemented -#define SE_SpellHateMod 130 // implemented -#define SE_ReduceReagentCost 131 // implemented -#define SE_ReduceManaCost 132 // implemented -#define SE_FcStunTimeMod 133 // implemented - Modify duration of stuns. -#define SE_LimitMaxLevel 134 // implemented -#define SE_LimitResist 135 // implemented -#define SE_LimitTarget 136 // implemented -#define SE_LimitEffect 137 // implemented -#define SE_LimitSpellType 138 // implemented -#define SE_LimitSpell 139 // implemented -#define SE_LimitMinDur 140 // implemented -#define SE_LimitInstant 141 // implemented -#define SE_LimitMinLevel 142 // implemented -#define SE_LimitCastTimeMin 143 // implemented -#define SE_LimitCastTimeMax 144 // implemented (*not used in any known live spell) +#define SE_IncreaseSpellHaste 127 // implemented, @Fc, On Caster, cast time mod pct, base: pct +#define SE_IncreaseSpellDuration 128 // implemented, @Fc, On Caster, spell duration mod pct, base: pct +#define SE_IncreaseRange 129 // implemented, @Fc, On Caster, spell range mod pct, base: pct +#define SE_SpellHateMod 130 // implemented, @Fc, On Caster, spell hate mod pct, base: min pct, limit: max pct +#define SE_ReduceReagentCost 131 // implemented, @Fc, On Caster, do not consume reagent pct chance, base: min pct, limit: max pct +#define SE_ReduceManaCost 132 // implemented, @Fc, On Caster, reduce mana cost by pct, base: min pct, limt: max pct +#define SE_FcStunTimeMod 133 // implemented, @Fc, On Caster, spell range mod pct, base: pct +#define SE_LimitMaxLevel 134 // implemented, @Ff, Max level of spell that can be focused, if base2 then decrease effectiviness by base2 % per level over max, base: lv, base2: effectiveness pct +#define SE_LimitResist 135 // implemented, @Ff, Resist Type(s) that a spell focus can require or exclude, base1: resist type, Include: Positive Exclude: Negative +#define SE_LimitTarget 136 // implemented, @Ff, Target Type(s) that a spell focus can require or exclude, base1: target type, Include: Positive Exclude: Negative +#define SE_LimitEffect 137 // implemented, @Ff, Spell effect(s) that a spell focus can require or exclude, base1: SPA id, Include: Positive Exclude: Negative +#define SE_LimitSpellType 138 // implemented, @Ff, Only allow focus spells that are Beneficial or Detrimental, base1: 0=det 1=bene +#define SE_LimitSpell 139 // implemented, @Ff, Specific spell id(s) that a spell focus can require or exclude, base1: SPA id, Include: Positive Exclude: Negative +#define SE_LimitMinDur 140 // implemented, @Ff, Mininum duration of spell that can be focused, base1: tics +#define SE_LimitInstant 141 // implemented, @Ff, Include or exclude if an isntant cast spell can be focused, base1: 0=Exclude if Instant 1=Allow only if Instant +#define SE_LimitMinLevel 142 // implemented, @Ff, Mininum level of spell that can be focused, base1: lv +#define SE_LimitCastTimeMin 143 // implemented, @Ff, Mininum cast time of spell that can be focused, base1: milliseconds +#define SE_LimitCastTimeMax 144 // implemented, @Ff, Max cast time of spell that can be focused, base1: milliseconds #define SE_Teleport2 145 // implemented - Banishment of the Pantheon -//#define SE_ElectricityResist 146 // *not implemented (Lightning Rod: 23233) +//#define SE_ElectricityResist 146 // *not implemented TODO: Now used on live, xyz for teleport spells? also in temp pets? #define SE_PercentalHeal 147 // implemented #define SE_StackingCommand_Block 148 // implemented? #define SE_StackingCommand_Overwrite 149 // implemented? #define SE_DeathSave 150 // implemented -#define SE_SuspendPet 151 // *not implemented as bonus +#define SE_SuspendPet 151 // implemented, @Pet, allow caster to have an extra suspended pet, base: 0=no buffs/items 1=buffs+items, limit: none, max: none #define SE_TemporaryPets 152 // implemented #define SE_BalanceHP 153 // implemented -#define SE_DispelDetrimental 154 // implemented +#define SE_DispelDetrimental 154 // implemented, @Dispel, removes only detrimental effects on a target, base: pct chance (950=95%), limit: none, max: none #define SE_SpellCritDmgIncrease 155 // implemented - no known live spells use this currently #define SE_IllusionCopy 156 // implemented - Deception -#define SE_SpellDamageShield 157 // implemented - Petrad's Protection -#define SE_Reflect 158 // implemented +#define SE_SpellDamageShield 157 // implemented, @DS, causes non-melee damage on caster of a spell, base: Amt DS (negative), limit: none, max: unknown (same as base but +) +#define SE_Reflect 158 // implemented, @SpellMisc, reflect casted detrimental spell back at caster, base: chance pct, limit: resist modifier (positive value reduces resists), max: pct of base dmg mod (50=50pct of base) #define SE_AllStats 159 // implemented //#define SE_MakeDrunk 160 // *not implemented - Effect works entirely client side (Should check against tolerance) -#define SE_MitigateSpellDamage 161 // implemented - rune with max value +#define SE_MitigateSpellDamage 161 // implemented, @Runes, mitigate incoming spell damage by percentage until rune fades, base: percent mitigation, limit: max dmg absorbed per hit, max: rune amt, Note: If placed on item or AA, will provide stackable percent mitigation. #define SE_MitigateMeleeDamage 162 // implemented - rune with max value #define SE_NegateAttacks 163 // implemented #define SE_AppraiseLDonChest 164 // implemented #define SE_DisarmLDoNTrap 165 // implemented #define SE_UnlockLDoNChest 166 // implemented -#define SE_PetPowerIncrease 167 // implemented +#define SE_PetPowerIncrease 167 // implemented, @Fc, On Caster, pet power mod, base: value #define SE_MeleeMitigation 168 // implemented #define SE_CriticalHitChance 169 // implemented #define SE_SpellCritChance 170 // implemented @@ -398,7 +902,7 @@ typedef enum { #define SE_DualWieldChance 176 // implemented #define SE_DoubleAttackChance 177 // implemented #define SE_MeleeLifetap 178 // implemented -#define SE_AllInstrumentMod 179 // implemented +#define SE_AllInstrumentMod 179 // implemented, @Song, set mod for ALL instrument/singing skills that will be used if higher then item mods, base: mod%, limit: none, max: none #define SE_ResistSpellChance 180 // implemented #define SE_ResistFearChance 181 // implemented #define SE_HundredHands 182 // implemented @@ -412,8 +916,8 @@ typedef enum { #define SE_EndurancePool 190 // implemented #define SE_Amnesia 191 // implemented - Silence vs Melee Effect #define SE_Hate 192 // implemented - Instant and hate over time. -#define SE_SkillAttack 193 // implemented -#define SE_FadingMemories 194 // implemented +#define SE_SkillAttack 193 // implemented, +#define SE_FadingMemories 194 // implemented, @Aggro, Remove from hate lists and make invisible. Can set max level of NPCs that can be affected. base: success chance, limit: max level (ROF2), max: max level (modern client), Note: Support for max level requires Rule (Spells, UseFadingMemoriesMaxLevel) to be true. If used from limit field, then it set as the level, ie. max level of 75 would use limit value of 75. If set from max field, max level 75 would use max value of 1075, if you want to set it so it checks a level range above the spell target then for it to only work on mobs 5 levels or below you set max value to 5. #define SE_StunResist 195 // implemented #define SE_StrikeThrough 196 // implemented #define SE_SkillDamageTaken 197 // implemented @@ -424,12 +928,12 @@ typedef enum { #define SE_IllusionOther 202 // implemented - Project Illusion #define SE_MassGroupBuff 203 // implemented #define SE_GroupFearImmunity 204 // implemented - (Does not use bonus) -#define SE_Rampage 205 // implemented +#define SE_Rampage 205 // implemented, @Combat Instant, Perform a primary slot combat rounds on all creatures within a 40 foot radius, base: number of attack rounds, limit: max entities hit per round, max: none, Note: AE range is 40 by default. Custom: Set field 'aoe_range' to override default. Adding additional attacks and hit count limit. #define SE_AETaunt 206 // implemented #define SE_FleshToBone 207 // implemented //#define SE_PurgePoison 208 // not used -#define SE_DispelBeneficial 209 // implemented -//#define SE_PetShield 210 // *not implemented +#define SE_DispelBeneficial 209 // implemented, @Dispel, removes only beneficial effects on a target, base: pct chance (950=95%), limit: none, max: none +#define SE_PetShield 210 // implmented, @ShieldAbility, allows pet to 'shield' owner for 50 pct of damage taken for a duration, base: Time multiplier 1=12 seconds, 2=24 ect, limit: mitigation on pet owner override (not on live), max: mitigation on pet overide (not on live) #define SE_AEMelee 211 // implemented TO DO: Implement to allow NPC use (client only atm). #define SE_FrenziedDevastation 212 // implemented - increase spell criticals + all DD spells cast 2x mana. #define SE_PetMaxHP 213 // implemented[AA] - increases the maximum hit points of your pet @@ -449,7 +953,7 @@ typedef enum { #define SE_ReduceSkillTimer 227 // implemented #define SE_ReduceFallDamage 228 // implented - reduce the damage that you take from falling #define SE_PersistantCasting 229 // implemented -#define SE_ExtendedShielding 230 // not used as bonus - increase range of /shield ability +#define SE_ExtendedShielding 230 // implemented, @ShieldAbility, extends the range of your /shield ability by an amount of distance, base: distance units, limit: none, max: none #define SE_StunBashChance 231 // implemented - increase chance to stun from bash. #define SE_DivineSave 232 // implemented (base1 == % chance on death to insta-res) (base2 == spell cast on save) #define SE_Metabolism 233 // implemented - Modifies food/drink consumption rates. @@ -461,10 +965,10 @@ typedef enum { #define SE_FeignedCastOnChance 239 // implemented - ability gives you an increasing chance for your feigned deaths to not be revealed by spells cast upon you. //#define SE_StringUnbreakable 240 // not used [Likely related to above - you become immune to feign breaking on a resisted spell and have a good chance of feigning through a spell that successfully lands upon you.] #define SE_ImprovedReclaimEnergy 241 // implemented - increase the amount of mana returned to you when reclaiming your pet. -#define SE_IncreaseChanceMemwipe 242 // implemented - increases the chance to wipe hate with memory blurr +#define SE_IncreaseChanceMemwipe 242 // implemented - @Memblur, increases the chance to wipe hate with memory blurr, base: chance pct, limit: none, max: none, Note: Mods final blur chance after other bonuses added. #define SE_CharmBreakChance 243 // implemented - Total Domination #define SE_RootBreakChance 244 // implemented[AA] reduce the chance that your root will break. -#define SE_TrapCircumvention 245 // *not implemented[AA] - decreases the chance that you will set off a trap when opening a chest +#define SE_TrapCircumvention 245 // implemented, @Traps, decreases the chance that you will set off a trap when opening a chest or other similar container by percentage, base: chance modifer, limit: none, max: none #define SE_SetBreathLevel 246 // *not implemented as bonus #define SE_RaiseSkillCap 247 // implemented[AA] - adds skill over the skill cap. #define SE_SecondaryForte 248 // not implemented as bonus(gives you a 2nd specialize skill that can go past 50 to 100) @@ -474,22 +978,22 @@ typedef enum { #define SE_FrontalBackstabChance 252 // implemented[AA] - chance to perform a full damage backstab from front. #define SE_FrontalBackstabMinDmg 253 // implemented[AA] - allow a frontal backstab for mininum damage. #define SE_Blank 254 // implemented -#define SE_ShieldDuration 255 // not implemented as bonus - increases duration of /shield +#define SE_ShieldDuration 255 // implemented, , @ShieldAbility, extends the duration of your /shield ability, base: seconds, limit: none, max: none #define SE_ShroudofStealth 256 // implemented #define SE_PetDiscipline 257 // not implemented as bonus - /pet hold - official name is GivePetHold #define SE_TripleBackstab 258 // implemented[AA] - chance to perform a triple backstab #define SE_CombatStability 259 // implemented[AA] - damage mitigation -#define SE_AddSingingMod 260 // implemented[AA] - Instrument/Singing Mastery, base1 is the mod, base2 is the ItemType -#define SE_SongModCap 261 // implemented[AA] - Song Mod cap increase (no longer used on live) +#define SE_AddSingingMod 260 // implemented, @Song, set mod for specific instrument/singing skills that will be used if higher then item mods, base: mod%, limit: ItemType ID, max: none +#define SE_SongModCap 261 // implemented, @Song, raise max song modifier cap, base: amt, limit: none, max: none, Note: No longer used on live #define SE_RaiseStatCap 262 // implemented #define SE_TradeSkillMastery 263 // implemented - lets you raise more than one tradeskill above master. #define SE_HastenedAASkill 264 // implemented #define SE_MasteryofPast 265 // implemented[AA] - Spells less than effect values level can not be fizzled -#define SE_ExtraAttackChance 266 // implemented - increase chance to score an extra attack with a 2-Handed Weapon. +#define SE_ExtraAttackChance 266 // implemented, @OffBonus, gives your double attacks a percent chance to perform an extra attack with 2-handed primary weapon, base: chance, limit: amt attacks, max: none #define SE_AddPetCommand 267 // implemented - sets command base2 to base1 #define SE_ReduceTradeskillFail 268 // implemented - reduces chance to fail with given tradeskill by a percent chance #define SE_MaxBindWound 269 // implemented[AA] - Increase max HP you can bind wound. -#define SE_BardSongRange 270 // implemented[AA] - increase range of beneficial bard songs (Sionachie's Crescendo) +#define SE_BardSongRange 270 // implemented, @Song, increase range of beneficial bard songs, base: mod%, limit: none, max: none , Note: example Sionachie's Crescendo #define SE_BaseMovementSpeed 271 // implemented[AA] - mods basemove speed, doesn't stack with other move mods #define SE_CastingLevel2 272 // implemented #define SE_CriticalDoTChance 273 // implemented @@ -500,37 +1004,37 @@ typedef enum { #define SE_FinishingBlow 278 // implemented[AA] - chance to do massive damage under 10% HP (base1 = chance, base2 = damage) #define SE_Flurry 279 // implemented #define SE_PetFlurry 280 // implemented[AA] -#define SE_FeignedMinion 281 // *not implemented[AA] ability allows you to instruct your pet to feign death via the '/pet feign' command. value = succeed chance +#define SE_FeignedMinion 281 // implemented, ability allows you to instruct your pet to feign death via the '/pet feign' command, base: succeed chance, limit: none, max: none, Note: Only implemented as an AA. #define SE_ImprovedBindWound 282 // implemented[AA] - increase bind wound amount by percent. #define SE_DoubleSpecialAttack 283 // implemented[AA] - Chance to perform second special attack as monk //#define SE_LoHSetHeal 284 // not used #define SE_NimbleEvasion 285 // *not implemented - base1 = 100 for max -#define SE_FcDamageAmt 286 // implemented - adds direct spell damage -#define SE_SpellDurationIncByTic 287 // implemented -#define SE_SkillAttackProc 288 // implemented[AA] - Chance to proc spell on skill attack usage (ex. Dragon Punch) +#define SE_FcDamageAmt 286 // implemented, @Fc, On Caster, spell damage mod flat amt, base: amt +#define SE_SpellDurationIncByTic 287 // implemented, @Fc, SPA: 287, SE_SpellDurationIncByTic, On Caster, spell buff duration mod, base: tics +#define SE_SkillAttackProc 288 // implemented, @Procs, chance to cast a spell when using a skill, base: chance, limit: skill, max: spellid, note: if used in AA the spell id is set in aa_ranks spell field, chance is calculated as 100% = value 1000. #define SE_CastOnFadeEffect 289 // implemented - Triggers only if fades after natural duration. #define SE_IncreaseRunSpeedCap 290 // implemented[AA] - increases run speed over the hard cap -#define SE_Purify 291 // implemented - Removes determental effects +#define SE_Purify 291 // implemented, @Dispel, remove up specified amount of detiremental spells, base: amt removed, limit: none, max: none, Note: excluding charm, fear, resurrection, and revival sickness #define SE_StrikeThrough2 292 // implemented[AA] - increasing chance of bypassing an opponent's special defenses, such as dodge, block, parry, and riposte. #define SE_FrontalStunResist 293 // implemented[AA] - Reduce chance to be stunned from front. -- live descriptions sounds like this isn't limited to frontal anymore #define SE_CriticalSpellChance 294 // implemented - increase chance to critical hit and critical damage modifier. //#define SE_ReduceTimerSpecial 295 // not used -#define SE_FcSpellVulnerability 296 // implemented - increase in incoming spell damage -#define SE_FcDamageAmtIncoming 297 // implemented - debuff that adds points damage to spells cast on target (focus effect). +#define SE_FcSpellVulnerability 296 // implemented, @Fc, On Target, spell damage taken mod pct, base: min pct, limit: max pct +#define SE_FcDamageAmtIncoming 297 // implemetned, @Fc, On Target, damage taken flat amt, base: amt #define SE_ChangeHeight 298 // implemented -#define SE_WakeTheDead 299 // implemented +#define SE_WakeTheDead 299 // implemented, @Pets, summon one temporary pet from nearby corpses that last a set duration, base: none, limit: none, max: duration (seconds). Note: max range of corpse is 250. #define SE_Doppelganger 300 // implemented #define SE_ArcheryDamageModifier 301 // implemented[AA] - increase archery damage by percent -#define SE_FcDamagePctCrit 302 // implemented - spell focus that is applied after critical hits has been calculated. -#define SE_FcDamageAmtCrit 303 // implemented - adds direct spell damage +#define SE_FcDamagePctCrit 302 // implemented, @Fc, On Caster, spell damage mod pct, base: min pct, limit: max pct, Note: applied after critical hits has been calculated. +#define SE_FcDamageAmtCrit 303 // implemented, @Fc, On Caster, spell damage mod flat amt, base: amt #define SE_OffhandRiposteFail 304 // implemented as bonus - enemy cannot riposte offhand attacks #define SE_MitigateDamageShield 305 // implemented - off hand attacks only (Shielding Resistance) -//#define SE_ArmyOfTheDead 306 // *not implemented NecroAA - This ability calls up to five shades of nearby corpses back to life to serve the necromancer. The soulless abominations will mindlessly fight the target until called back to the afterlife some time later. The first rank summons up to three shades that serve for 60 seconds, and each additional rank adds one more possible shade and increases their duration by 15 seconds +#define SE_ArmyOfTheDead 306 // implemented, @Pets, summon multiple temporary pets from nearby corpses that last a set duration, base: amount of corpses that a pet can summon from, limit: none, max: duration (seconds). Note: max range of corpse is 250. //#define SE_Appraisal 307 // *not implemented Rogue AA - This ability allows you to estimate the selling price of an item you are holding on your cursor. -#define SE_SuspendMinion 308 // implemented +#define SE_ZoneSuspendMinion 308 // implemented, @Pet, allow suspended pets to be resummoned upon zoning, base: 1, limit: none, max: none, Calc: Bool #define SE_GateCastersBindpoint 309 // implemented - Gate to casters bind point -#define SE_ReduceReuseTimer 310 // implemented -#define SE_LimitCombatSkills 311 // implemented - Excludes focus from procs (except if proc is a memorizable spell) +#define SE_ReduceReuseTimer 310 // implemented, @Fc, On Caster, spell and disc reuse time mod by amount, base: milliseconds +#define SE_LimitCombatSkills 311 // implemented, @Ff, Include or exclude combat skills or procs from being focused, base1: 0=Exclude if proc 1=Allow only if proc. #define SE_Sanctuary 312 // implemented - Places caster at bottom hate list, effect fades if cast cast spell on targets other than self. #define SE_ForageAdditionalItems 313 // implemented[AA] - chance to forage additional items #define SE_Invisibility2 314 // implemented - fixed duration invisible @@ -551,15 +1055,15 @@ typedef enum { #define SE_ManaAbsorbPercentDamage 329 // implemented #define SE_CriticalDamageMob 330 // implemented #define SE_Salvage 331 // implemented - chance to recover items that would be destroyed in failed tradeskill combine -#define SE_SummonToCorpse 332 // *not implemented AA - Call of the Wild (Druid/Shaman Res spell with no exp) +#define SE_SummonToCorpse 332 // *not implemented AA - Call of the Wild (Druid/Shaman Res spell with no exp) TOOD: implement this. #define SE_CastOnRuneFadeEffect 333 // implemented #define SE_BardAEDot 334 // implemented -#define SE_BlockNextSpellFocus 335 // implemented - base1 chance to block next spell ie Puratus (8494) +#define SE_BlockNextSpellFocus 335 // implemented, @Fc, On Caster, chance to block next spell, base: chance //#define SE_IllusionaryTarget 336 // not used #define SE_PercentXPIncrease 337 // implemented #define SE_SummonAndResAllCorpses 338 // implemented -#define SE_TriggerOnCast 339 // implemented -#define SE_SpellTrigger 340 // implemented - chance to trigger spell +#define SE_TriggerOnCast 339 // implemented, @Fc, On Caster, cast on spell use, base: chance pct limit: spellid +#define SE_SpellTrigger 340 // implemented - chance to trigger spell [Share rolls with 469] All base2 spells share roll chance, only 1 cast. #define SE_ItemAttackCapIncrease 341 // implemented[AA] - increases the maximum amount of attack you can gain from items. #define SE_ImmuneFleeing 342 // implemented - stop mob from fleeing #define SE_InterruptCasting 343 // implemented - % chance to interrupt spells being cast every tic. Cacophony (8272) @@ -567,7 +1071,7 @@ typedef enum { #define SE_AssassinateLevel 345 // implemented as bonus - AA Assisination max level to kill #define SE_HeadShotLevel 346 // implemented[AA] - HeadShot max level to kill #define SE_DoubleRangedAttack 347 // implemented - chance at an additional archery attack (consumes arrow) -#define SE_LimitManaMin 348 // implemented +#define SE_LimitManaMin 348 // implemented, @Ff, Mininum mana of spell that can be focused, base1: mana amt #define SE_ShieldEquipDmgMod 349 // implemented[AA] Increase melee base damage (indirectly increasing hate) when wearing a shield. #define SE_ManaBurn 350 // implemented - Drains mana for damage/heal at a defined ratio up to a defined maximum amount of mana. #define SE_PersistentEffect 351 // *not implemented. creates a trap/totem that casts a spell (spell id + base1?) when anything comes near it. can probably make a beacon for this @@ -576,7 +1080,7 @@ typedef enum { //#define SE_DeactivateAllTraps 354 // *not implemented - looks to be some type of invulnerability? Test DAT (8757) //#define SE_LearnTrap 355 // *not implemented - looks to be some type of invulnerability? Test LT (8758) //#define SE_ChangeTriggerType 356 // not used -#define SE_FcMute 357 // implemented - silences casting of spells that contain specific spell effects (focus limited) +#define SE_FcMute 357 // implemented, @Fc, On Caster, prevents spell casting, base: chance pct #define SE_CurrentManaOnce 358 // implemented //#define SE_PassiveSenseTrap 359 // *not implemented - Invulnerability (Brell's Blessing) #define SE_ProcOnKillShot 360 // implemented - a buff that has a base1 % to cast spell base2 when you kill a "challenging foe" base3 min level @@ -585,7 +1089,7 @@ typedef enum { #define SE_BandolierSlots 363 // *not implemented[AA] 'Battle Ready' expands the bandolier by one additional save slot per rank. #define SE_TripleAttackChance 364 // implemented #define SE_ProcOnSpellKillShot 365 // implemented - chance to trigger a spell on kill when the kill is caused by a specific spell with this effect in it (10470 Venin) -#define SE_GroupShielding 366 // *not implemented[AA] This gives you /shieldgroup +//#define SE_GroupShielding 366 // *not implemented[AA] This gives you /shieldgroup #define SE_SetBodyType 367 // implemented - set body type of base1 so it can be affected by spells that are limited to that type (Plant, Animal, Undead, etc) //#define SE_FactionMod 368 // *not implemented - increases faction with base1 (faction id, live won't match up w/ ours) by base2 #define SE_CorruptionCounter 369 // implemented @@ -601,53 +1105,53 @@ typedef enum { #define SE_ShadowStepDirectional 379 // implemented - handled by client #define SE_Knockdown 380 // implemented - small knock back(handled by client) //#define SE_KnockTowardCaster 381 // *not implemented (Call of Hither) knocks you back to caster (value) distance units infront -#define SE_NegateSpellEffect 382 // implemented - negates specific spell bonuses for duration of the debuff. -#define SE_SympatheticProc 383 // implemented - focus on items that has chance to proc a spell when you cast +#define SE_NegateSpellEffect 382 // implemented, @Debuff, negates specific spell effect benefits for the duration of the debuff and prevent non-duration spell effect from working, base: see NegateSpellEffecttype Enum, limit: SPA id, max: none +#define SE_SympatheticProc 383 // implemented, @Fc, On Caster, cast on spell use, base: variable proc chance on cast time, limit: spellid #define SE_Leap 384 // implemented - Leap effect, ie stomping leap -#define SE_LimitSpellGroup 385 // implemented - Limits to spell group(ie type 3 reuse reduction augs that are class specific and thus all share s SG) +#define SE_LimitSpellGroup 385 // implemented, @Ff, Spell group(s) that a spell focus can require or exclude, base1: spellgroup id, Include: Positive Exclude: Negative #define SE_CastOnCurer 386 // implemented - Casts a spell on the person curing #define SE_CastOnCure 387 // implemented - Casts a spell on the cured person #define SE_SummonCorpseZone 388 // implemented - summons a corpse from any zone(nec AA) -#define SE_FcTimerRefresh 389 // implemented - Refresh spell icons -//#define SE_FcTimerLockout 390 // *not implemented - Sets recast timers to specific value, focus limited. -#define SE_LimitManaMax 391 // implemented -#define SE_FcHealAmt 392 // implemented - Adds or removes healing from spells -#define SE_FcHealPctIncoming 393 // implemented - HealRate with focus restrictions. -#define SE_FcHealAmtIncoming 394 // implemented - Adds/Removes amount of healing on target by X value with foucs restrictions. -#define SE_FcHealPctCritIncoming 395 // implemented[AA] - Increases chance of having a heal crit when cast on you. [focus limited] -#define SE_FcHealAmtCrit 396 // implemented - Adds a direct healing amount to spells -#define SE_PetMeleeMitigation 397 // implemented[AA] - additional mitigation to your pets. Adds AC. +#define SE_FcTimerRefresh 389 // implemented, @Fc, On Caster, reset all recast timers, base: 1, Note: Applied from casted spells only +#define SE_FcTimerLockout 390 // implemented, @Fc, On Caster, set a spell to be on recast timer, base: recast duration milliseconds, Note: Applied from casted spells only +#define SE_LimitManaMax 391 // implemented, @Ff, Mininum mana of spell that can be focused, base1: mana amt +#define SE_FcHealAmt 392 // implemented, @Fc, On Caster, spell healing mod flat amt, base: amt +#define SE_FcHealPctIncoming 393 // implemented, @Fc, On Target, heal received mod pct, base: pct, limit: random max pct +#define SE_FcHealAmtIncoming 394 // implemented, @Fc, On Target, heal received mod flat amt, base: amt +#define SE_FcHealPctCritIncoming 395 // implemented, @Fc, On Target, heal received mod pct, base: pct, limit: random max pct +#define SE_FcHealAmtCrit 396 // implemented, @Fc, On Caster, spell healing mod flat amt, base: amt +#define SE_PetMeleeMitigation 397 // implemented[AA] - additional mitigation to your pets. Adds AC #define SE_SwarmPetDuration 398 // implemented - Affects the duration of swarm pets #define SE_FcTwincast 399 // implemented - cast 2 spells for every 1 #define SE_HealGroupFromMana 400 // implemented - Drains mana and heals for each point of mana drained #define SE_ManaDrainWithDmg 401 // implemented - Deals damage based on the amount of mana drained #define SE_EndDrainWithDmg 402 // implemented - Deals damage for the amount of endurance drained -#define SE_LimitSpellClass 403 // implemented - Limits to specific types of spells (see CheckSpellCategory) -#define SE_LimitSpellSubclass 404 // *not implemented - Limits to specific types of spells (see CheckSpellCategory) [Categories NOT defined yet] +#define SE_LimitSpellClass 403 // implemented, @Ff, 'Spell Category' using table field 'spell_class' that a spell focus can require or exclude, base1: category type, Include: Positive Exclude: Negative +#define SE_LimitSpellSubclass 404 // implemented, @Ff, 'Spell Category Subclass' using table field 'spell_subclass' that a spell focus can require or exclude, base1: category type, Include: Positive Exclude: Negative #define SE_TwoHandBluntBlock 405 // implemented - chance to block attacks when using two hand blunt weapons (similiar to shield block) #define SE_CastonNumHitFade 406 // implemented - casts a spell when a buff fades due to its numhits being depleted #define SE_CastonFocusEffect 407 // implemented - casts a spell if focus limits are met (ie triggers when a focus effects is applied) #define SE_LimitHPPercent 408 // implemented - limited to a certain percent of your hp(ie heals up to 50%) #define SE_LimitManaPercent 409 // implemented - limited to a certain percent of your mana #define SE_LimitEndPercent 410 // implemented - limited to a certain percent of your end -#define SE_LimitClass 411 // implemented - Limits to spells of a certain class (Note: The class value in dbase is +1 in relation to item class value) -#define SE_LimitRace 412 // implemented - Limits to spells cast by a certain race (Note: not used in any known live spells) -#define SE_FcBaseEffects 413 // implemented - Increases the power of bard songs, skill attacks, runes, bard allowed foci, damage/heal -#define SE_LimitCastingSkill 414 // implemented - Limit a focus to include spells cast using a specific skill. -//#define SE_FFItemClass 415 // not used - base1 matches ItemType, base2 matches SubType, -1 ignored, max is bitmask of valid slots +#define SE_LimitClass 411 // implemented, @Ff, Class(es) that can use the spell focus, base1: class(es), Note: The class value in dbase is +1 in relation to item class value, set as you would item for multiple classes +#define SE_LimitRace 412 // implemented, @Ff, Race that can use the spell focus, base1: race, Note: not used in any known live spells. Use only single race at a time. +#define SE_FcBaseEffects 413 // implemented, @Fc, On Caster, base spell effectiveness mod pct, base: pct +#define SE_LimitCastingSkill 414 // implemented, @Ff, Spell and singing skills(s) that a spell focus can require or exclude, base1: skill id, Include: Positive Exclude: Negative +#define SE_FFItemClass 415 // implemented, @Ff, Limits focuses to be applied only from item click. base1: item ItemType (-1 to include for all ItemTypes,-1000 to exclude clicks from getting the focus, or exclude specific SubTypes or Slots if set), limit: item SubType (-1 for all SubTypes), max: item Slots (bitmask of valid slots, -1 ALL slots), Note: not used on live. See comments in Mob::CalcFocusEffect for more details. #define SE_ACv2 416 // implemented - New AC spell effect #define SE_ManaRegen_v2 417 // implemented - New mana regen effect #define SE_SkillDamageAmount2 418 // implemented - adds skill damage directly to certain attacks #define SE_AddMeleeProc 419 // implemented - Adds a proc -#define SE_FcLimitUse 420 // implemented - increases numhits count by percent (Note: not used in any known live spells) -#define SE_FcIncreaseNumHits 421 // implemented[AA] - increases number of hits a buff has till fade. (focus) -#define SE_LimitUseMin 422 // implemented - limit a focus to require a min amount of numhits value (used with above) -#define SE_LimitUseType 423 // implemented - limit a focus to require a certain numhits type +#define SE_FcLimitUse 420 // implemented, @Fc, On Caster, numhits mod pct, base: pct, Note: not used in any known live spells +#define SE_FcIncreaseNumHits 421 // implemented, @Fc, On Caster, numhits mod flat amt, base: amt +#define SE_LimitUseMin 422 // implemented, @Ff Minium amount of numhits for a spell to be focused, base: numhit amt +#define SE_LimitUseType 423 // implemented, @Ff Focus will only affect if has this numhits type, base: numhit type #define SE_GravityEffect 424 // implemented - Pulls/pushes you toward/away the mob at a set pace //#define SE_Display 425 // *not implemented - Illusion: Flying Dragon(21626) #define SE_IncreaseExtTargetWindow 426 // *not implmented[AA] - increases the capacity of your extended target window -#define SE_SkillProc 427 // implemented - chance to proc when using a skill(ie taunt) -#define SE_LimitToSkill 428 // implemented - limits what skills will effect a skill proc +#define SE_SkillProcAttempt 427 // implemented - chance to proc when using a skill(ie taunt) +#define SE_LimitToSkill 428 // implemented, @Procs, limits what combat skills will effect a skill proc, base: skill value, limit: none, max: none #define SE_SkillProcSuccess 429 // implemented - chance to proc when tje skill in use successfully fires. //#define SE_PostEffect 430 // *not implemented - Fear of the Dark(27641) - Alters vision //#define SE_PostEffectData 431 // *not implemented - Fear of the Dark(27641) - Alters vision @@ -661,15 +1165,15 @@ typedef enum { #define SE_Assassinate 439 // implemented[AA] - Assassinate damage #define SE_FinishingBlowLvl 440 // implemented[AA] - Sets the level Finishing blow can be triggered on an NPC #define SE_DistanceRemoval 441 // implemented - Buff is removed from target when target moves X amount of distance away from where initially hit. -#define SE_TriggerOnReqTarget 442 // implemented - triggers a spell which a certain criteria are met (below X amount of hp,mana,end, number of pets on hatelist) -#define SE_TriggerOnReqCaster 443 // implemented - triggers a spell which a certain criteria are met (below X amount of hp,mana,end, number of pets on hatelist) +#define SE_TriggerOnReqTarget 442 // implemented, @SpellTrigger, triggers a spell when Target Requirement conditions are met (see enum SpellRestriction for IDs), base: spellid, limit: SpellRestriction ID, max: none, Note: Usually cast on a target +#define SE_TriggerOnReqCaster 443 // implemented, @SpellTrigger, triggers a spell when Caster Requirement conditions are met (see enum SpellRestriction for IDs), base: spellid, limit: SpellRestriction ID, max: none, Note: Usually self only #define SE_ImprovedTaunt 444 // implemented - Locks Aggro On Caster and Decrease other Players Aggro by X% on NPC targets below level Y //#define SE_AddMercSlot 445 // *not implemented[AA] - [Hero's Barracks] Allows you to conscript additional mercs. #define SE_AStacker 446 // implementet - bufff stacking blocker (26219 | Qirik's Watch) #define SE_BStacker 447 // implemented #define SE_CStacker 448 // implemented #define SE_DStacker 449 // implemented -#define SE_MitigateDotDamage 450 // implemented DOT spell mitigation rune with max value +#define SE_MitigateDotDamage 450 // implemented, @Runes, mitigate incoming dot damage by percentage until rune fades, base: percent mitigation, limit: max dmg absorbed per hit, max: rune amt, Note: If placed on item or AA, will provide stackable percent mitigation. #define SE_MeleeThresholdGuard 451 // implemented Partial Melee Rune that only is lowered if melee hits are over X amount of damage #define SE_SpellThresholdGuard 452 // implemented Partial Spell Rune that only is lowered if spell hits are over X amount of damage #define SE_TriggerMeleeThreshold 453 // implemented Trigger effect on X amount of melee damage taken in a single hit @@ -679,18 +1183,73 @@ typedef enum { #define SE_ResourceTap 457 // implemented Coverts a percent of dmg from dmg spells(DD/DoT) to hp/mana/end. #define SE_FactionModPct 458 // implemented Modifies faction gains and losses by percent. #define SE_DamageModifier2 459 // implemented - Modifies melee damage by skill type -//#define SE_Ff_Override_NotFocusable 460 // -#define SE_ImprovedDamage2 461 // implemented - Increase spell damage by percent (SE_Fc_Damage_%2) -#define SE_FcDamageAmt2 462 // implemented - Increase spell damage by flat amount (SE_Fc_Damage_Amt2) -//#define SE_Shield_Target 463 // +#define SE_Ff_Override_NotFocusable 460 // implemented, @Fc, Allow spell to be focused event if flagged with 'not_focusable' in spell table, base: 1 +#define SE_ImprovedDamage2 461 // implemented, @Fc, On Caster, spell damage mod pct, base: min pct, limit: max pct +#define SE_FcDamageAmt2 462 // implemented, @Fc, On Caster, spell damage mod flat amt, base: amt +//#define SE_Shield_Target 463 // #define SE_PC_Pet_Rampage 464 // implemented - Base1 % chance to do rampage for base2 % of damage each melee round -//#define SE_PC_Pet_AE_Rampage 465 // Would assume as above but need to confirm. +#define SE_PC_Pet_AE_Rampage 465 // implemented - Base1 % chance to do AE rampage for base2 % of damage each melee round #define SE_PC_Pet_Flurry_Chance 466 // implemented - Base1 % chance to do flurry from double attack hit. -//#define SE_DS_Mitigation_Amount 467 // -//#define SE_DS_Mitigation_Percentage 468 // -//#define SE_Chance_Best_in_Spell_Grp 469 // -//#define SE_Trigger_Best_in_Spell Grp 470 // -//#define SE_Double_Melee_Round 471 // +#define SE_DS_Mitigation_Amount 467 // implemented - Modify incoming damage shield damage by a flat amount +#define SE_DS_Mitigation_Percentage 468 // implemented - Modify incoming damage shield damage by percentage +#define SE_Chance_Best_in_Spell_Grp 469 // implemented - Chance to cast highest scribed spell within a spell group. All base2 spells share roll chance, only 1 cast. +#define SE_Trigger_Best_in_Spell_Grp 470 // implemented - Chance to cast highest scribed spell within a spell group. Each spell has own chance. +#define SE_Double_Melee_Round 471 // implemented, @OffBonus, percent chance to repeat primary weapon round with a percent damage modifier, base: pct chance repeat, limit: pct dmg mod, max: none +#define SE_Buy_AA_Rank 472 // implemented, @Special, Used in AA abilities that have Enable/Disable toggle. Spell on Disabled Rank has this effect in it, base: 1, limit: none, max: none, Note: This will not just buy an AA +#define SE_Double_Backstab_Front 473 // implemented - Chance to double backstab from front +#define SE_Pet_Crit_Melee_Damage_Pct_Owner 474 // implemenetd - Critical damage mod applied to pets from owner +#define SE_Trigger_Spell_Non_Item 475 // implemented - Trigger spell on cast only if not from item click. +#define SE_Weapon_Stance 476 // implemented, @Misc, Apply a specific spell buffs automatically depending 2Hander, Shield or Duel Wield is equiped, base: spellid, base: 0=2H 1=Shield 2=DW, max: none +#define SE_Hatelist_To_Top_Index 477 // Implemented - Chance to be set to top of rampage list +#define SE_Hatelist_To_Tail_Index 478 // Implemented - Chance to be set to bottom of rampage list +#define SE_Ff_Value_Min 479 // implemented, @Ff, Minimum base value of a spell that can be focused, base: spells to be focused base1 value +#define SE_Ff_Value_Max 480 // implemented, @Ff, Max base value of a spell that can be focused, base: spells to be focused base1 value +#define SE_Fc_Cast_Spell_On_Land 481 // implemented, @Fc, On Target, cast spell if hit by spell, base: chance pct, limit: spellid +#define SE_Skill_Base_Damage_Mod 482 // implemented, @OffBonus, modify base melee damage by percent, base: pct, limit: skill(-1=ALL), max: none +#define SE_Fc_Spell_Damage_Pct_IncomingPC 483 // implemented, @Fc, On Target, spell damage taken mod pct, base: min pct, limit: max pct +#define SE_Fc_Spell_Damage_Amt_IncomingPC 484 // implemented, @Fc, On Target, damage taken flat amt, base: amt +#define SE_Ff_CasterClass 485 // implemented, @Ff, Caster of spell on target with a focus effect that is checked by incoming spells must be specified class(es). base1: class(es), Note: Set multiple classes same as would for items +#define SE_Ff_Same_Caster 486 // implemented, @Ff, Caster of spell on target with a focus effect that is checked by incoming spells, base1: 0=Must be different caster 1=Must be same caster +//#define SE_Extend_Tradeskill_Cap 487 // +//#define SE_Defender_Melee_Force_Pct_PC 488 // +#define SE_Worn_Endurance_Regen_Cap 489 // implemented, modify worn regen cap, base: amt, limit: none, max: none +#define SE_Ff_ReuseTimeMin 490 // implemented, @Ff, Minimum recast time of a spell that can be focused, base: recast time +#define SE_Ff_ReuseTimeMax 491 // implemented, @Ff, Max recast time of a spell that can be focused, base: recast time +#define SE_Ff_Endurance_Min 492 // implemented, @Ff, Minimum endurance cost of a spell that can be focused, base: endurance cost +#define SE_Ff_Endurance_Max 493 // implemented, @Ff, Max endurance cost of a spell that can be focused, base: endurance cost +#define SE_Pet_Add_Atk 494 // implemented - Bonus on pet owner which gives their pet increased attack stat +#define SE_Ff_DurationMax 495 // implemented, @Ff, Max duration of spell that can be focused, base: tics +#define SE_Critical_Melee_Damage_Mod_Max 496 // implemented - increase or decrease by percent critical damage (not stackable) +//#define SE_Ff_FocusCastProcNoBypass 497 // +#define SE_AddExtraAttackPct_1h_Primary 498 // implemented, @OffBonus, gives your double attacks a percent chance to perform an extra attack with 1-handed primary weapon, base: chance, limit: amt attacks, max: none +#define SE_AddExtraAttackPct_1h_Secondary 499 //implemented, @OffBonus, gives your double attacks a percent chance to perform an extra attack with 1-handed secondary weapon, base: chance, limit: amt attacks, max: none +#define SE_Fc_CastTimeMod2 500 // implemented, @Fc, On Caster, cast time mod pct, base: pct, Note: Can reduce to instant cast +#define SE_Fc_CastTimeAmt 501 // implemented, @Fc, On Caster, cast time mod flat amt, base: milliseconds, Note: Can reduce to instant cast +#define SE_Fearstun 502 // implemented - Stun with a max level limit. Normal stun restrictions don't apply. +#define SE_Melee_Damage_Position_Mod 503 // implemented, @OffBonus, modify melee damage by percent if done from Front or Behind opponent, base: pct, limit: 0=back 1=front, max: none +#define SE_Melee_Damage_Position_Amt 504 // implemented, @OffBonus, modify melee damage by flat amount if done from Front or Behind opponent, base: amt, limit: 0=back 1=front, max: none +#define SE_Damage_Taken_Position_Mod 505 // implemented, @DefBonus, modify melee damage by percent if dmg taken from Front or Behind, base: pct, limit: 0=back 1=front, max: none +#define SE_Damage_Taken_Position_Amt 506 // implemented -@DefBonus, modify melee damage by flat amount if dmg taken from your Front or Behind, base: amt, limit: 0=back 1=front, max: none +#define SE_Fc_Amplify_Mod 507 // implemented, @Fc, On Caster, damage-heal-dot mod pct, base: pct +#define SE_Fc_Amplify_Amt 508 // implemented, @Fc, On Caster, damage-heal-dot mod flat amt, base: amt +#define SE_Health_Transfer 509 // implemented - exchange health for damage or healing on a target. ie Lifeburn/Act of Valor +#define SE_Fc_ResistIncoming 510 // implemented, @Fc, On Target, resist modifier, base: amt +#define SE_Ff_FocusTimerMin 511 // implemented, @Ff, sets a recast time until focus can be used again, base: 1, limit: time ms, Note: ie. limit to 1 trigger every 1.5 seconds +#define SE_Proc_Timer_Modifier 512 // implemented - limits procs per amount of a time based on timer value, base: 1, limit: time ms, Note:, ie limit to 1 proc every 55 seconds) +//#define SE_Mana_Max_Percent 513 // +//#define SE_Endurance_Max_Percent 514 // +#define SE_AC_Avoidance_Max_Percent 515 // implemented - stackable avoidance modifier +#define SE_AC_Mitigation_Max_Percent 516 // implemented - stackable defense modifier +//#define SE_Attack_Offense_Max_Percent 517 // +#define SE_Attack_Accuracy_Max_Percent 518 // implemented - stackable accurary modifer +//#define SE_Luck_Amount 519 // +//#define SE_Luck_Percent 520 // +#define SE_Endurance_Absorb_Pct_Damage 521 // implemented - Reduces % of Damage using Endurance, drains endurance at a ratio (ie. 0.05 Endurance per Hit Point) +#define SE_Instant_Mana_Pct 522 // implemented - Increase/Decrease mana by percent of max mana +#define SE_Instant_Endurance_Pct 523 // implemented - Increase/Decrease mana by percent of max endurance +#define SE_Duration_HP_Pct 524 // implemented - Decrease Current Hit Points by % of Total Hit Points per Tick, up to a MAX per tick +#define SE_Duration_Mana_Pct 525 // implemented - Decrease Current Mana by % of Total Mana per Tick, up to a MAX per tick +#define SE_Duration_Endurance_Pct 526 // implemented - Decrease Current Endurance by % of Total Hit Points per Tick, up to a MAX per tick // LAST @@ -719,99 +1278,99 @@ struct SPDat_Spell_Struct /* 007 */ char cast_on_other[64]; // Message when spell is cast on someone else -- CASTEDOTHERTXT /* 008 */ char spell_fades[64]; // Spell fades -- SPELLGONE /* 009 */ float range; // -- RANGE -/* 010 */ float aoerange; // -- IMPACTRANGE -/* 011 */ float pushback; // -- OUTFORCE -/* 012 */ float pushup; // -- UPFORCE +/* 010 */ float aoe_range; // -- IMPACTRANGE +/* 011 */ float push_back; // -- OUTFORCE +/* 012 */ float push_up; // -- UPFORCE /* 013 */ uint32 cast_time; // Cast time -- CASTINGTIME /* 014 */ uint32 recovery_time; // Recovery time -- RECOVERYDELAY /* 015 */ uint32 recast_time; // Recast same spell time -- SPELLDELAY -/* 016 */ uint32 buffdurationformula; // -- DURATIONBASE -/* 017 */ uint32 buffduration; // -- DURATIONCAP -/* 018 */ uint32 AEDuration; // sentinel, rain of something -- IMPACTDURATION +/* 016 */ uint32 buff_duration_formula; // -- DURATIONBASE +/* 017 */ uint32 buff_duration; // -- DURATIONCAP +/* 018 */ uint32 aoe_duration; // sentinel, rain of something -- IMPACTDURATION /* 019 */ uint16 mana; // Mana Used -- MANACOST -/* 020 */ int base[EFFECT_COUNT]; //various purposes -- BASEAFFECT1 .. BASEAFFECT12 -/* 032 */ int base2[EFFECT_COUNT]; //various purposes -- BASE_EFFECT2_1 ... BASE_EFFECT2_12 -/* 044 */ int32 max[EFFECT_COUNT]; // -- AFFECT1CAP ... AFFECT12CAP +/* 020 */ int base_value[EFFECT_COUNT]; //various purposes -- BASEAFFECT1 .. BASEAFFECT12 +/* 032 */ int limit_value[EFFECT_COUNT]; //various purposes -- BASE_EFFECT2_1 ... BASE_EFFECT2_12 +/* 044 */ int32 max_value[EFFECT_COUNT]; // -- AFFECT1CAP ... AFFECT12CAP /* 056 */ //uint16 icon; // Spell icon -- IMAGENUMBER /* 057 */ //uint16 memicon; // Icon on membarthing -- MEMIMAGENUMBER -/* 058 */ int32 components[4]; // reagents -- EXPENDREAGENT1 ... EXPENDREAGENT4 -/* 062 */ int component_counts[4]; // amount of regents used -- EXPENDQTY1 ... EXPENDQTY4 -/* 066 */ int NoexpendReagent[4]; // focus items (Need but not used; Flame Lick has a Fire Beetle Eye focus.) +/* 058 */ int32 component[4]; // reagents -- EXPENDREAGENT1 ... EXPENDREAGENT4 +/* 062 */ int component_count[4]; // amount of regents used -- EXPENDQTY1 ... EXPENDQTY4 +/* 066 */ int no_expend_reagent[4]; // focus items (Need but not used; Flame Lick has a Fire Beetle Eye focus.) // If it is a number between 1-4 it means components[number] is a focus and not to expend it // If it is a valid itemid it means this item is a focus as well // -- NOEXPENDREAGENT1 ... NOEXPENDREAGENT4 /* 070 */ uint16 formula[EFFECT_COUNT]; // Spell's value formula -- LEVELAFFECT1MOD ... LEVELAFFECT12MOD /* 082 */ //int LightType; // probaly another effecttype flag -- LIGHTTYPE -/* 083 */ int8 goodEffect; //0=detrimental, 1=Beneficial, 2=Beneficial, Group Only -- BENEFICIAL -/* 084 */ int Activated; // probably another effecttype flag -- ACTIVATED -/* 085 */ int resisttype; // -- RESISTTYPE -/* 086 */ int effectid[EFFECT_COUNT]; // Spell's effects -- SPELLAFFECT1 ... SPELLAFFECT12 -/* 098 */ SpellTargetType targettype; // Spell's Target -- TYPENUMBER -/* 099 */ int basediff; // base difficulty fizzle adjustment -- BASEDIFFICULTY +/* 083 */ int8 good_effect; //0=detrimental, 1=Beneficial, 2=Beneficial, Group Only -- BENEFICIAL +/* 084 */ int activated; // probably another effecttype flag -- ACTIVATED +/* 085 */ int resist_type; // -- RESISTTYPE +/* 086 */ int effect_id[EFFECT_COUNT]; // Spell's effects -- SPELLAFFECT1 ... SPELLAFFECT12 +/* 098 */ SpellTargetType target_type; // Spell's Target -- TYPENUMBER +/* 099 */ int base_difficulty; // base difficulty fizzle adjustment -- BASEDIFFICULTY /* 100 */ EQ::skills::SkillType skill; // -- CASTINGSKILL -/* 101 */ int8 zonetype; // 01=Outdoors, 02=dungeons, ff=Any -- ZONETYPE -/* 102 */ int8 EnvironmentType; // -- ENVIRONMENTTYPE -/* 103 */ int8 TimeOfDay; // -- TIMEOFDAY +/* 101 */ int8 zone_type; // 01=Outdoors, 02=dungeons, ff=Any -- ZONETYPE +/* 102 */ int8 environment_type; // -- ENVIRONMENTTYPE +/* 103 */ int8 time_of_day; // -- TIMEOFDAY /* 104 */ uint8 classes[PLAYER_CLASS_COUNT]; // Classes, and their min levels -- WARRIORMIN ... BERSERKERMIN -/* 120 */ uint8 CastingAnim; // -- CASTINGANIM +/* 120 */ uint8 casting_animation; // -- CASTINGANIM /* 121 */ //uint8 TargetAnim; // -- TARGETANIM /* 122 */ //uint32 TravelType; // -- TRAVELTYPE -/* 123 */ uint16 SpellAffectIndex; // -- SPELLAFFECTINDEX +/* 123 */ uint16 spell_affect_index; // -- SPELLAFFECTINDEX /* 124 */ int8 disallow_sit; // 124: high-end Yaulp spells (V, VI, VII, VIII [Rk 1, 2, & 3], & Gallenite's Bark of Fury -- CANCELONSIT -/* 125 */ int8 diety_agnostic;// 125: Words of the Skeptic -- DIETY_AGNOSTIC +/* 125 */ int8 deity_agnostic;// 125: Words of the Skeptic -- DEITY_AGNOSTIC /* 126 */ int8 deities[16]; // Deity check. 201 - 216 per http://www.eqemulator.net/wiki/wikka.php?wakka=DeityList // -1: Restrict to Deity; 1: Restrict to Deity, but only used on non-Live (Test Server "Blessing of ...") spells; 0: Don't restrict // the client actually stores deities in a single int32_t - // -- DIETY_BERTOXXULOUS ... DIETY_VEESHAN + // -- DEITY_BERTOXXULOUS ... DEITY_VEESHAN /* 142 */ //int8 npc_no_cast; // 142: between 0 & 100 -- NPC_NO_CAST /* 143 */ //int ai_pt_bonus; // 143: always set to 0, client doesn't save this -- AI_PT_BONUS /* 144 */ int16 new_icon; // Spell icon used by the client in uifiles/default/spells??.tga, both for spell gems & buff window. Looks to depreciate icon & memicon -- NEW_ICON /* 145 */ //int16 spellanim; // Doesn't look like it's the same as #doanim, so not sure what this is, particles I think -- SPELL_EFFECT_INDEX /* 146 */ bool uninterruptable; // Looks like anything != 0 is uninterruptable. Values are mostly -1, 0, & 1 (Fetid Breath = 90?) -- NO_INTERRUPT -/* 147 */ int16 ResistDiff; // -- RESIST_MOD -/* 148 */ bool dot_stacking_exempt; // -- NOT_STACKABLE_DOT +/* 147 */ int16 resist_difficulty; // -- RESIST_MOD +/* 148 */ bool unstackable_dot; // -- NOT_STACKABLE_DOT /* 149 */ //int deletable; // -- DELETE_OK -/* 150 */ uint16 RecourseLink; // -- REFLECT_SPELLINDEX +/* 150 */ uint16 recourse_link; // -- REFLECT_SPELLINDEX /* 151 */ bool no_partial_resist; // 151: -1, 0, or 1 -- NO_PARTIAL_SAVE /* 152 */ //bool small_targets_only; // -- SMALL_TARGETS_ONLY /* 153 */ //bool uses_persistent_particles; // -- USES_PERSISTENT_PARTICLES /* 154 */ int8 short_buff_box; // != 0, goes to short buff box. -- BARD_BUFF_BOX -/* 155 */ int descnum; // eqstr of description of spell -- DESCRIPTION_INDEX -/* 156 */ int typedescnum; // eqstr of type description -- PRIMARY_CATEGORY -/* 157 */ int effectdescnum; // eqstr of effect description -- SECONDARY_CATEGORY_1 +/* 155 */ int description_id; // eqstr of description of spell -- DESCRIPTION_INDEX +/* 156 */ int type_description_id; // eqstr of type description -- PRIMARY_CATEGORY +/* 157 */ int effect_description_id; // eqstr of effect description -- SECONDARY_CATEGORY_1 /* 158 */ //int secondary_category_2; //Category Desc ID 3 -- SECONDARY_CATEGORY_2 /* 159 */ bool npc_no_los; // -- NO_NPC_LOS -/* 160 */ //bool feedbackable; // -- FEEDBACKABLE +/* 160 */ bool feedbackable; // -- FEEDBACKABLE /* 161 */ bool reflectable; // -- REFLECTABLE -/* 162 */ int bonushate; // -- HATE_MOD +/* 162 */ int bonus_hate; // -- HATE_MOD /* 163 */ //int resist_per_level; // -- RESIST_PER_LEVEL /* 164 */ //int resist_cap; // for most spells this appears to mimic ResistDiff -- RESIST_CAP /* 165 */ bool ldon_trap; //Flag found on all LDON trap / chest related spells. -- AFFECT_INANIMATE -/* 166 */ int EndurCost; // -- STAMINA_COST -/* 167 */ int8 EndurTimerIndex; // bad name, used for all spells -- TIMER_INDEX -/* 168 */ bool IsDisciplineBuff; //Will goto the combat window when cast -- IS_SKILL +/* 166 */ int endurance_cost; // -- STAMINA_COST +/* 167 */ int8 timer_id; // bad name, used for all spells -- TIMER_INDEX +/* 168 */ bool is_discipline; //Will goto the combat window when cast -- IS_SKILL /* 169 - 172*/ //These are zero for ALL spells, also removed from live -- ATTACK_OPENING, DEFENSE_OPENING, SKILL_OPENING, NPC_ERROR_OPENING -/* 173 */ int HateAdded; // -- SPELL_HATE_GIVEN -/* 174 */ int EndurUpkeep; // -- ENDUR_UPKEEP -/* 175 */ int numhitstype; // defines which type of behavior will tick down the numhit counter. -- LIMITED_USE_TYPE -/* 176 */ int numhits; // -- LIMITED_USE_COUNT -/* 177 */ int pvpresistbase; // -- PVP_RESIST_MOD -/* 178 */ int pvpresistcalc; // -- PVP_RESIST_PER_LEVEL -/* 179 */ int pvpresistcap; // -- PVP_RESIST_CAP +/* 173 */ int hate_added; // -- SPELL_HATE_GIVEN +/* 174 */ int endurance_upkeep; // -- ENDUR_UPKEEP +/* 175 */ int hit_number_type; // defines which type of behavior will tick down the numhit counter. -- LIMITED_USE_TYPE +/* 176 */ int hit_number; // -- LIMITED_USE_COUNT +/* 177 */ int pvp_resist_base; // -- PVP_RESIST_MOD +/* 178 */ int pvp_resist_per_level; // -- PVP_RESIST_PER_LEVEL +/* 179 */ int pvp_resist_cap; // -- PVP_RESIST_CAP /* 180 */ int spell_category; // -- GLOBAL_GROUP -/* 181 */ //int pvp_duration; // buffdurationformula for PvP -- PVP_DURATION -/* 182 */ //int pvp_duration_cap; // buffduration for PvP -- PVP_DURATION_CAP +/* 181 */ int pvp_duration; // buffdurationformula for PvP -- PVP_DURATION +/* 182 */ int pvp_duration_cap; // buffduration for PvP -- PVP_DURATION_CAP /* 183 */ int pcnpc_only_flag; // valid values are 0, 1 = PCs (and mercs), and 2 = NPCs (and not mercs) -- PCNPC_ONLY_FLAG -/* 184 */ bool cast_not_standing; // this is checked in the client's EQ_Spell::IsCastWhileInvisSpell, this also blocks SE_InterruptCasting from affecting this spell -- CAST_NOT_STANDING +/* 184 */ bool cast_not_standing; // this is checked in the client's EQ_Spell::IsCastWhileInvisSpell, this also blocks SE_InterruptCasting from affecting this spell -- CAST_NOT_STANDING (Allows casting if DA, stun, mezed, charm? fear?, damage to invul targets) /* 185 */ bool can_mgb; // 0=no, -1 or 1 = yes -- CAN_MGB /* 186 */ int dispel_flag; // -- NO_DISPELL /* 187 */ //int npc_category; // -- NPC_MEM_CATEGORY /* 188 */ //int npc_usefulness; // -- NPC_USEFULNESS -/* 189 */ int MinResist; // -- MIN_RESIST -/* 190 */ int MaxResist; // -- MAX_RESIST +/* 189 */ int min_resist; // -- MIN_RESIST +/* 190 */ int max_resist; // -- MAX_RESIST /* 191 */ uint8 viral_targets; // -- MIN_SPREAD_TIME /* 192 */ uint8 viral_timer; // -- MAX_SPREAD_TIME -/* 193 */ int NimbusEffect; // -- DURATION_PARTICLE_EFFECT +/* 193 */ int nimbus_effect; // -- DURATION_PARTICLE_EFFECT /* 194 */ float directional_start; //Cone Start Angle: -- CONE_START_ANGLE /* 195 */ float directional_end; // Cone End Angle: -- CONE_END_ANGLE /* 196 */ bool sneak; // effect can only be used if sneaking (rogue 'Daggerfall' ect) -- SNEAK_ATTACK @@ -820,35 +1379,35 @@ struct SPDat_Spell_Struct /* 199 */ //bool show_wear_off_message; // -- SHOW_WEAR_OFF_MESSAGE /* 200 */ bool suspendable; // buff is suspended in suspended buff zones -- IS_COUNTDOWN_HELD /* 201 */ int viral_range; // -- SPREAD_RADIUS -/* 202 */ int songcap; // individual song cap -- BASE_EFFECTS_FOCUS_CAP +/* 202 */ int song_cap; // individual song cap -- BASE_EFFECTS_FOCUS_CAP /* 203 */ //bool stacks_with_self; // -- STACKS_WITH_SELF /* 204 */ //int not_shown_to_player; // client skips this -- NOT_SHOWN_TO_PLAYER /* 205 */ bool no_block; // -- NO_BUFF_BLOCK /* 206 */ //int8 anim_variation; // -- ANIM_VARIATION -/* 207 */ int spellgroup; // -- SPELL_GROUP +/* 207 */ int spell_group; // -- SPELL_GROUP /* 208 */ int rank; //increments AA effects with same name -- SPELL_GROUP_RANK /* 209 */ int no_resist; //makes spells unresistable, which makes charms unbreakable as well. -- NO_RESIST /* 210 */ // bool allow_spellscribe; // -- ALLOW_SPELLSCRIBE -/* 211 */ int CastRestriction; //Various restriction categories for spells most seem targetable race related but have also seen others for instance only castable if target hp 20% or lower or only if target out of combat -- SPELL_REQ_ASSOCIATION_ID -/* 212 */ bool AllowRest; // -- BYPASS_REGEN_CHECK -/* 213 */ bool InCombat; //Allow spell if target is in combat -- CAN_CAST_IN_COMBAT -/* 214 */ bool OutofCombat; //Allow spell if target is out of combat -- CAN_CAST_OUT_OF_COMBAT +/* 211 */ int cast_restriction; //Various restriction categories for spells most seem targetable race related but have also seen others for instance only castable if target hp 20% or lower or only if target out of combat -- SPELL_REQ_ASSOCIATION_ID +/* 212 */ bool allow_rest; // -- BYPASS_REGEN_CHECK +/* 213 */ bool can_cast_in_combat; //Allow spell if target is in combat -- CAN_CAST_IN_COMBAT +/* 214 */ bool can_cast_out_of_combat; //Allow spell if target is out of combat -- CAN_CAST_OUT_OF_COMBAT /* 215 */ //bool show_dot_message; // -- SHOW_DOT_MESSAGE /* 216 */ //bool invalid; // -- INVALID /* 217 */ int override_crit_chance; //Places a cap on the max chance to critical -- OVERRIDE_CRIT_CHANCE -/* 218 */ int aemaxtargets; //Is used for various AE effects -- MAX_TARGETS +/* 218 */ int aoe_max_targets; //Is used for various AE effects -- MAX_TARGETS /* 219 */ int no_heal_damage_item_mod; // -- NO_HEAL_DAMAGE_ITEM_MOD -/* 220 */ //int caster_requirement_id; // -- CASTER_REQUIREMENT_ID -/* 221 */ //int spell_class; // -- SPELL_CLASS -/* 222 */ //int spell_subclass; // -- SPELL_SUBCLASS +/* 220 */ int caster_requirement_id; // -- CASTER_REQUIREMENT_ID +/* 221 */ int spell_class; // -- SPELL_CLASS +/* 222 */ int spell_subclass; // -- SPELL_SUBCLASS /* 223 */ //int ai_valid_targets; // -- AI_VALID_TARGETS -/* 224 */ bool persistdeath; // buff doesn't get stripped on death -- NO_STRIP_ON_DEATH +/* 224 */ bool persist_death; // buff doesn't get stripped on death -- NO_STRIP_ON_DEATH /* 225 */ //float base_effects_focus_slope; // -- BASE_EFFECTS_FOCUS_SLOPE /* 226 */ //float base_effects_focus_offset; // -- BASE_EFFECTS_FOCUS_OFFSET -/* 227 */ float min_dist; //spell power modified by distance from caster (Min Distance) -- DISTANCE_MOD_CLOSE_DIST -/* 228 */ float min_dist_mod; //spell power modified by distance from caster (Modifier at Min Distance) -- DISTANCE_MOD_CLOSE_MULT -/* 229 */ float max_dist; //spell power modified by distance from caster (Max Distance) -- DISTANCE_MOD_FAR_DIST -/* 230 */ float max_dist_mod; //spell power modified by distance from caster (Modifier at Max Distance) -- DISTANCE_MOD_FAR_MULT +/* 227 */ float min_distance; //spell power modified by distance from caster (Min Distance) -- DISTANCE_MOD_CLOSE_DIST +/* 228 */ float min_distance_mod; //spell power modified by distance from caster (Modifier at Min Distance) -- DISTANCE_MOD_CLOSE_MULT +/* 229 */ float max_distance; //spell power modified by distance from caster (Max Distance) -- DISTANCE_MOD_FAR_DIST +/* 230 */ float max_distance_mod; //spell power modified by distance from caster (Modifier at Max Distance) -- DISTANCE_MOD_FAR_MULT /* The client also does this * v26 = *(float *)&v4->DistanceModFarDist - *(float *)&v4->DistanceModCloseDist; * if ( v26 > -0.00000011920929 && v26 < 0.00000011920929 ) @@ -862,7 +1421,7 @@ struct SPDat_Spell_Struct /* 234 */ //bool only_during_fast_regen; // -- ONLY_DURING_FAST_REGEN /* 235 */ //bool is_beta_only; // -- IS_BETA_ONLY /* 236 */ //int spell_subgroup; // -- SPELL_SUBGROUP - uint8 DamageShieldType; // This field does not exist in spells_us.txt + uint8 damage_shield_type; // This field does not exist in spells_us.txt }; extern const SPDat_Spell_Struct* spells; @@ -880,6 +1439,7 @@ bool IsPercentalHealSpell(uint16 spell_id); bool IsGroupOnlySpell(uint16 spell_id); bool IsBeneficialSpell(uint16 spell_id); bool IsDetrimentalSpell(uint16 spell_id); +bool IsInvisSpell(uint16 spell_id); bool IsInvulnerabilitySpell(uint16 spell_id); bool IsCHDurationSpell(uint16 spell_id); bool IsPoisonCounterSpell(uint16 spell_id); @@ -941,6 +1501,7 @@ bool IsPartialDeathSaveSpell(uint16 spell_id); bool IsShadowStepSpell(uint16 spell_id); bool IsSuccorSpell(uint16 spell_id); bool IsTeleportSpell(uint16 spell_id); +bool IsTranslocateSpell(uint16 spell_id); bool IsGateSpell(uint16 spell_id); bool IsPlayerIllusionSpell(uint16 spell_id); // seveian 2008-09-23 bool IsLDoNObjectSpell(uint16 spell_id); @@ -972,7 +1533,16 @@ bool IsStackableDot(uint16 spell_id); bool IsBardOnlyStackEffect(int effect); bool IsCastWhileInvis(uint16 spell_id); bool IsEffectIgnoredInStacking(int spa); - +bool IsFocusLimit(int spa); +bool SpellRequiresTarget(int target_type); +bool IsVirusSpell(int32 spell_id); +int GetViralMinSpreadTime(int32 spell_id); +int GetViralMaxSpreadTime(int32 spell_id); +int GetViralSpreadRange(int32 spell_id); +bool IsInstrumentModAppliedToSpellEffect(int32 spell_id, int effect); +bool IsPulsingBardSong(int32 spell_id); +uint32 GetProcLimitTimer(int32 spell_id, int proc_type); +bool IgnoreCastingRestriction(int32 spell_id); int CalcPetHp(int levelb, int classb, int STA = 75); int GetSpellEffectDescNum(uint16 spell_id); DmgShieldType GetDamageShieldType(uint16 spell_id, int32 DSType = 0); @@ -982,5 +1552,7 @@ int32 GetFuriousBash(uint16 spell_id); bool IsShortDurationBuff(uint16 spell_id); bool IsSpellUsableThisZoneType(uint16 spell_id, uint8 zone_type); const char *GetSpellName(uint16 spell_id); +int GetSpellStatValue(uint32 spell_id, const char* stat_identifier, uint8 slot = 0); +bool CastRestrictedSpell(int spellid); #endif diff --git a/common/string_util.cpp b/common/string_util.cpp index f267397c5..fe392aedd 100644 --- a/common/string_util.cpp +++ b/common/string_util.cpp @@ -15,35 +15,38 @@ */ #include "string_util.h" +#include #include +#include #ifdef _WINDOWS - #include +#include - #define snprintf _snprintf - #define strncasecmp _strnicmp - #define strcasecmp _stricmp +#define snprintf _snprintf +#define strncasecmp _strnicmp +#define strcasecmp _stricmp #else - #include - #include + +#include +#include #include #endif #ifndef va_copy - #define va_copy(d,s) ((d) = (s)) +#define va_copy(d,s) ((d) = (s)) #endif // original source: // https://github.com/facebook/folly/blob/master/folly/String.cpp // -const std::string vStringFormat(const char* format, va_list args) +const std::string vStringFormat(const char *format, va_list args) { std::string output; - va_list tmpargs; + va_list tmpargs; - va_copy(tmpargs,args); + va_copy(tmpargs, args); int characters_used = vsnprintf(nullptr, 0, format, tmpargs); va_end(tmpargs); @@ -51,7 +54,7 @@ const std::string vStringFormat(const char* format, va_list args) if (characters_used > 0) { output.resize(characters_used + 1); - va_copy(tmpargs,args); + va_copy(tmpargs, args); characters_used = vsnprintf(&output[0], output.capacity(), format, tmpargs); va_end(tmpargs); @@ -59,8 +62,9 @@ const std::string vStringFormat(const char* format, va_list args) // We shouldn't have a format error by this point, but I can't imagine what error we // could have by this point. Still, return empty string; - if (characters_used < 0) + if (characters_used < 0) { output.clear(); + } } return output; } @@ -74,18 +78,6 @@ const std::string str_tolower(std::string s) return s; } -std::vector split(std::string str_to_split, char delimiter) -{ - std::stringstream ss(str_to_split); - std::string item; - std::vector exploded_values; - while (std::getline(ss, item, delimiter)) { - exploded_values.push_back(item); - } - - return exploded_values; -} - const std::string str_toupper(std::string s) { std::transform( @@ -98,8 +90,9 @@ const std::string str_toupper(std::string s) const std::string ucfirst(std::string s) { std::string output = s; - if (!s.empty()) + if (!s.empty()) { output[0] = static_cast(::toupper(s[0])); + } return output; } @@ -113,26 +106,69 @@ const std::string StringFormat(const char *format, ...) return output; } -std::vector SplitString(const std::string &str, char delim) { +std::vector SplitString(const std::string &str, const char delim) +{ std::vector ret; - std::stringstream ss(str); - std::string item; - - while(std::getline(ss, item, delim)) { - ret.push_back(item); - } - + std::string::size_type start = 0; + auto end = str.find(delim); + while (end != std::string::npos) { + ret.emplace_back(str, start, end - start); + start = end + 1; + end = str.find(delim, start); + } + // this will catch the last word since the string is unlikely to end with a delimiter + if (str.length() > start) { + ret.emplace_back(str, start, str.length() - start); + } return ret; } -std::string::size_type search_deliminated_string(const std::string &haystack, const std::string &needle, const char deliminator) +// this one takes delimiter length into consideration +std::vector split_string(std::string s, std::string delimiter) +{ + size_t pos_start = 0, pos_end, delim_len = delimiter.length(); + std::string token; + std::vector res; + + while ((pos_end = s.find(delimiter, pos_start)) != std::string::npos) { + token = s.substr(pos_start, pos_end - pos_start); + pos_start = pos_end + delim_len; + res.push_back(token); + } + + res.push_back(s.substr(pos_start)); + return res; +} + +std::string get_between(const std::string &s, std::string start_delim, std::string stop_delim) +{ + if (s.find(start_delim) == std::string::npos && s.find(stop_delim) == std::string::npos) { + return ""; + } + + auto first_split = split_string(s, start_delim); + if (!first_split.empty()) { + std::string remaining_block = first_split[1]; + auto second_split = split_string(remaining_block, stop_delim); + if (!second_split.empty()) { + std::string between = second_split[0]; + return between; + } + } + + return ""; +} + +std::string::size_type +search_deliminated_string(const std::string &haystack, const std::string &needle, const char deliminator) { // this shouldn't go out of bounds, even without obvious bounds checks auto pos = haystack.find(needle); while (pos != std::string::npos) { auto c = haystack[pos + needle.length()]; - if ((c == '\0' || c == deliminator) && (pos == 0 || haystack[pos - 1] == deliminator)) + if ((c == '\0' || c == deliminator) && (pos == 0 || haystack[pos - 1] == deliminator)) { return pos; + } pos = haystack.find(needle, pos + needle.length()); } return std::string::npos; @@ -152,7 +188,7 @@ std::string implode(std::string glue, std::vector src) } std::string final_output = output.str(); - final_output.resize (output.str().size () - glue.size()); + final_output.resize(output.str().size() - glue.size()); return final_output; } @@ -174,80 +210,83 @@ std::vector wrap(std::vector &src, std::string charact return new_vector; } -std::string EscapeString(const std::string &s) { +std::string EscapeString(const std::string &s) +{ std::string ret; - size_t sz = s.length(); - for(size_t i = 0; i < sz; ++i) { + size_t sz = s.length(); + for (size_t i = 0; i < sz; ++i) { char c = s[i]; - switch(c) { - case '\x00': - ret += "\\x00"; - break; - case '\n': - ret += "\\n"; - break; - case '\r': - ret += "\\r"; - break; - case '\\': - ret += "\\\\"; - break; - case '\'': - ret += "\\'"; - break; - case '\"': - ret += "\\\""; - break; - case '\x1a': - ret += "\\x1a"; - break; - default: - ret.push_back(c); - break; + switch (c) { + case '\x00': + ret += "\\x00"; + break; + case '\n': + ret += "\\n"; + break; + case '\r': + ret += "\\r"; + break; + case '\\': + ret += "\\\\"; + break; + case '\'': + ret += "\\'"; + break; + case '\"': + ret += "\\\""; + break; + case '\x1a': + ret += "\\x1a"; + break; + default: + ret.push_back(c); + break; } } return ret; } -std::string EscapeString(const char *src, size_t sz) { +std::string EscapeString(const char *src, size_t sz) +{ std::string ret; - for(size_t i = 0; i < sz; ++i) { + for (size_t i = 0; i < sz; ++i) { char c = src[i]; - switch(c) { - case '\x00': - ret += "\\x00"; - break; - case '\n': - ret += "\\n"; - break; - case '\r': - ret += "\\r"; - break; - case '\\': - ret += "\\\\"; - break; - case '\'': - ret += "\\'"; - break; - case '\"': - ret += "\\\""; - break; - case '\x1a': - ret += "\\x1a"; - break; - default: - ret.push_back(c); - break; + switch (c) { + case '\x00': + ret += "\\x00"; + break; + case '\n': + ret += "\\n"; + break; + case '\r': + ret += "\\r"; + break; + case '\\': + ret += "\\\\"; + break; + case '\'': + ret += "\\'"; + break; + case '\"': + ret += "\\\""; + break; + case '\x1a': + ret += "\\x1a"; + break; + default: + ret.push_back(c); + break; } } return ret; } -bool StringIsNumber(const std::string &s) { +bool StringIsNumber(const std::string &s) +{ try { auto r = stod(s); return true; @@ -257,15 +296,18 @@ bool StringIsNumber(const std::string &s) { } } -void ToLowerString(std::string &s) { +void ToLowerString(std::string &s) +{ std::transform(s.begin(), s.end(), s.begin(), ::tolower); } -void ToUpperString(std::string &s) { +void ToUpperString(std::string &s) +{ std::transform(s.begin(), s.end(), s.begin(), ::toupper); } -std::string JoinString(const std::vector& ar, const std::string &delim) { +std::string JoinString(const std::vector &ar, const std::string &delim) +{ std::string ret; for (size_t i = 0; i < ar.size(); ++i) { if (i != 0) { @@ -285,11 +327,20 @@ void find_replace(std::string &string_subject, const std::string &search_string, } size_t start_pos = 0; - while((start_pos = string_subject.find(search_string, start_pos)) != std::string::npos) { + while ((start_pos = string_subject.find(search_string, start_pos)) != std::string::npos) { string_subject.replace(start_pos, search_string.length(), replace_string); start_pos += replace_string.length(); } +} +std::string replace_string(std::string subject, const std::string &search, const std::string &replace) +{ + size_t pos = 0; + while ((pos = subject.find(search, pos)) != std::string::npos) { + subject.replace(pos, search.length(), replace); + pos += replace.length(); + } + return subject; } void ParseAccountString(const std::string &s, std::string &account, std::string &loginserver) @@ -297,9 +348,9 @@ void ParseAccountString(const std::string &s, std::string &account, std::string auto split = SplitString(s, ':'); if (split.size() == 2) { loginserver = split[0]; - account = split[1]; + account = split[1]; } - else if(split.size() == 1) { + else if (split.size() == 1) { account = split[0]; } } @@ -308,9 +359,11 @@ void ParseAccountString(const std::string &s, std::string &account, std::string // normal strncpy doesnt put a null term on copied strings, this one does // ref: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wcecrt/htm/_wcecrt_strncpy_wcsncpy.asp -char* strn0cpy(char* dest, const char* source, uint32 size) { - if (!dest) +char *strn0cpy(char *dest, const char *source, uint32 size) +{ + if (!dest) { return 0; + } if (size == 0 || source == 0) { dest[0] = 0; return dest; @@ -322,123 +375,159 @@ char* strn0cpy(char* dest, const char* source, uint32 size) { // String N w/null Copy Truncated? // return value =true if entire string(source) fit, false if it was truncated -bool strn0cpyt(char* dest, const char* source, uint32 size) { - if (!dest) +bool strn0cpyt(char *dest, const char *source, uint32 size) +{ + if (!dest) { return 0; + } if (size == 0 || source == 0) { dest[0] = 0; return false; } strncpy(dest, source, size); dest[size - 1] = 0; - return (bool)(source[strlen(dest)] == 0); + return (bool) (source[strlen(dest)] == 0); } -const char *MakeLowerString(const char *source) { +const char *MakeLowerString(const char *source) +{ static char str[128]; - if (!source) + if (!source) { return nullptr; + } MakeLowerString(source, str); return str; } -void MakeLowerString(const char *source, char *target) { +void MakeLowerString(const char *source, char *target) +{ if (!source || !target) { *target = 0; return; } - while (*source) - { + while (*source) { *target = tolower(*source); - target++; source++; + target++; + source++; } *target = 0; } -uint32 hextoi(const char* num) { - if (num == nullptr) +uint32 hextoi(const char *num) +{ + if (num == nullptr) { return 0; + } int len = strlen(num); - if (len < 3) + if (len < 3) { return 0; + } - if (num[0] != '0' || (num[1] != 'x' && num[1] != 'X')) + if (num[0] != '0' || (num[1] != 'x' && num[1] != 'X')) { return 0; + } - uint32 ret = 0; - int mul = 1; - for (int i = len - 1; i >= 2; i--) { - if (num[i] >= 'A' && num[i] <= 'F') + uint32 ret = 0; + int mul = 1; + for (int i = len - 1; i >= 2; i--) { + if (num[i] >= 'A' && num[i] <= 'F') { ret += ((num[i] - 'A') + 10) * mul; - else if (num[i] >= 'a' && num[i] <= 'f') + } + else if (num[i] >= 'a' && num[i] <= 'f') { ret += ((num[i] - 'a') + 10) * mul; - else if (num[i] >= '0' && num[i] <= '9') + } + else if (num[i] >= '0' && num[i] <= '9') { ret += (num[i] - '0') * mul; - else + } + else { return 0; + } mul *= 16; } return ret; } -uint64 hextoi64(const char* num) { - if (num == nullptr) +uint64 hextoi64(const char *num) +{ + if (num == nullptr) { return 0; + } int len = strlen(num); - if (len < 3) + if (len < 3) { return 0; + } - if (num[0] != '0' || (num[1] != 'x' && num[1] != 'X')) + if (num[0] != '0' || (num[1] != 'x' && num[1] != 'X')) { return 0; + } - uint64 ret = 0; - int mul = 1; - for (int i = len - 1; i >= 2; i--) { - if (num[i] >= 'A' && num[i] <= 'F') + uint64 ret = 0; + int mul = 1; + for (int i = len - 1; i >= 2; i--) { + if (num[i] >= 'A' && num[i] <= 'F') { ret += ((num[i] - 'A') + 10) * mul; - else if (num[i] >= 'a' && num[i] <= 'f') + } + else if (num[i] >= 'a' && num[i] <= 'f') { ret += ((num[i] - 'a') + 10) * mul; - else if (num[i] >= '0' && num[i] <= '9') + } + else if (num[i] >= '0' && num[i] <= '9') { ret += (num[i] - '0') * mul; - else + } + else { return 0; + } mul *= 16; } return ret; } -bool atobool(const char* iBool) { +bool atobool(const char *iBool) +{ - if (iBool == nullptr) + if (iBool == nullptr) { return false; - if (!strcasecmp(iBool, "true")) + } + if (!strcasecmp(iBool, "true")) { return true; - if (!strcasecmp(iBool, "false")) + } + if (!strcasecmp(iBool, "false")) { return false; - if (!strcasecmp(iBool, "yes")) + } + if (!strcasecmp(iBool, "yes")) { return true; - if (!strcasecmp(iBool, "no")) + } + if (!strcasecmp(iBool, "no")) { return false; - if (!strcasecmp(iBool, "on")) + } + if (!strcasecmp(iBool, "on")) { return true; - if (!strcasecmp(iBool, "off")) + } + if (!strcasecmp(iBool, "off")) { return false; - if (!strcasecmp(iBool, "enable")) + } + if (!strcasecmp(iBool, "enable")) { return true; - if (!strcasecmp(iBool, "disable")) + } + if (!strcasecmp(iBool, "disable")) { return false; - if (!strcasecmp(iBool, "enabled")) + } + if (!strcasecmp(iBool, "enabled")) { return true; - if (!strcasecmp(iBool, "disabled")) + } + if (!strcasecmp(iBool, "disabled")) { return false; - if (!strcasecmp(iBool, "y")) + } + if (!strcasecmp(iBool, "y")) { return true; - if (!strcasecmp(iBool, "n")) + } + if (!strcasecmp(iBool, "n")) { return false; - if (atoi(iBool)) + } + if (atoi(iBool)) { return true; + } return false; } @@ -447,21 +536,19 @@ char *CleanMobName(const char *in, char *out) { unsigned i, j; - for (i = j = 0; i < strlen(in); i++) - { + for (i = j = 0; i < strlen(in); i++) { // convert _ to space.. any other conversions like this? I *think* this // is the only non alpha char that's not stripped but converted. - if (in[i] == '_') - { + if (in[i] == '_') { out[j++] = ' '; } - else - { - if (isalpha(in[i]) || (in[i] == '`')) // numbers, #, or any other crap just gets skipped + else { + if (isalpha(in[i]) || (in[i] == '`')) { // numbers, #, or any other crap just gets skipped out[j++] = in[i]; + } } } - out[j] = 0; // terimnate the string before returning it + out[j] = 0; // terimnate the string before returning it return out; } @@ -469,8 +556,9 @@ char *CleanMobName(const char *in, char *out) void RemoveApostrophes(std::string &s) { for (unsigned int i = 0; i < s.length(); ++i) - if (s[i] == '\'') + if (s[i] == '\'') { s[i] = '_'; + } } char *RemoveApostrophes(const char *s) @@ -480,8 +568,9 @@ char *RemoveApostrophes(const char *s) strcpy(NewString, s); for (unsigned int i = 0; i < strlen(NewString); ++i) - if (NewString[i] == '\'') + if (NewString[i] == '\'') { NewString[i] = '_'; + } return NewString; } @@ -500,11 +589,12 @@ const char *ConvertArrayF(float input, char *returnchar) bool isAlphaNumeric(const char *text) { - for (unsigned int charIndex = 0; charIndex 'z') && (text[charIndex] < 'A' || text[charIndex] > 'Z') && - (text[charIndex] < '0' || text[charIndex] > '9')) + (text[charIndex] < '0' || text[charIndex] > '9')) { return false; + } } return true; @@ -559,13 +649,614 @@ std::string numberToWords(unsigned long long int n) } // first letter capitalized and rest made lower case -std::string FormatName(const std::string& char_name) +std::string FormatName(const std::string &char_name) { std::string formatted(char_name); - if (!formatted.empty()) - { + if (!formatted.empty()) { std::transform(formatted.begin(), formatted.end(), formatted.begin(), ::tolower); formatted[0] = ::toupper(formatted[0]); } return formatted; } + +bool IsAllowedWorldServerCharacterList(char c) +{ + const char *valid_characters = ":[](){}.!@#$%^&*-=+<>/\\|'\""; + if (strchr(valid_characters, c)) { + return true; + } + + return false; +} + +void SanitizeWorldServerName(char *name) +{ + std::string server_long_name = name; + + strcpy(name, SanitizeWorldServerName(server_long_name).c_str()); +} + +std::string SanitizeWorldServerName(std::string server_long_name) +{ + server_long_name.erase( + std::remove_if( + server_long_name.begin(), + server_long_name.end(), + [](char c) { + return !(std::isalpha(c) || std::isalnum(c) || std::isspace(c) || IsAllowedWorldServerCharacterList(c)); + } + ), server_long_name.end() + ); + + server_long_name = trim(server_long_name); + + // bad word filter + for (auto &piece: split_string(server_long_name, " ")) { + for (auto &word: GetBadWords()) { + // for shorter words that can actually be part of legitimate words + // make sure that it isn't part of another word by matching on a space + if (str_tolower(piece) == word) { + find_replace( + server_long_name, + piece, + repeat("*", (int) word.length()) + ); + continue; + } + + auto pos = str_tolower(piece).find(word); + if (str_tolower(piece).find(word) != std::string::npos && piece.length() > 4 && word.length() > 4) { + auto found_word = piece.substr(pos, word.length()); + std::string replaced_piece = piece.substr(pos, word.length()); + + find_replace( + server_long_name, + replaced_piece, + repeat("*", (int) word.length()) + ); + } + } + } + + return server_long_name; +} + +std::string repeat(std::string s, int n) +{ + std::string s1 = s; + for (int i = 1; i < n; i++) { + s += s1; + } + + return s; +} + +std::vector GetBadWords() +{ + return std::vector{ + "2g1c", + "acrotomophilia", + "anal", + "anilingus", + "anus", + "apeshit", + "arsehole", + "ass", + "asshole", + "assmunch", + "autoerotic", + "babeland", + "bangbros", + "bangbus", + "bareback", + "barenaked", + "bastard", + "bastardo", + "bastinado", + "bbw", + "bdsm", + "beaner", + "beaners", + "beaver", + "beastiality", + "bestiality", + "bimbos", + "birdlock", + "bitch", + "bitches", + "blowjob", + "blumpkin", + "bollocks", + "bondage", + "boner", + "boob", + "boobs", + "bukkake", + "bulldyke", + "bullshit", + "bung", + "bunghole", + "busty", + "butt", + "buttcheeks", + "butthole", + "camel toe", + "camgirl", + "camslut", + "camwhore", + "carpetmuncher", + "cialis", + "circlejerk", + "clit", + "clitoris", + "clusterfuck", + "cock", + "cocks", + "coprolagnia", + "coprophilia", + "cornhole", + "coon", + "coons", + "creampie", + "cum", + "cumming", + "cumshot", + "cumshots", + "cunnilingus", + "cunt", + "darkie", + "daterape", + "deepthroat", + "dendrophilia", + "dick", + "dildo", + "dingleberry", + "dingleberries", + "doggiestyle", + "doggystyle", + "dolcett", + "domination", + "dominatrix", + "dommes", + "hump", + "dvda", + "ecchi", + "ejaculation", + "erotic", + "erotism", + "escort", + "eunuch", + "fag", + "faggot", + "fecal", + "felch", + "fellatio", + "feltch", + "femdom", + "figging", + "fingerbang", + "fingering", + "fisting", + "footjob", + "frotting", + "fuck", + "fuckin", + "fucking", + "fucktards", + "fudgepacker", + "futanari", + "gangbang", + "gangbang", + "gaysex", + "genitals", + "goatcx", + "goatse", + "gokkun", + "goodpoop", + "goregasm", + "grope", + "g-spot", + "guro", + "handjob", + "hentai", + "homoerotic", + "honkey", + "hooker", + "horny", + "humping", + "incest", + "intercourse", + "jailbait", + "jigaboo", + "jiggaboo", + "jiggerboo", + "jizz", + "juggs", + "kike", + "kinbaku", + "kinkster", + "kinky", + "knobbing", + "livesex", + "lolita", + "lovemaking", + "masturbate", + "masturbating", + "masturbation", + "milf", + "mong", + "motherfucker", + "muffdiving", + "nambla", + "nawashi", + "negro", + "neonazi", + "nigga", + "nigger", + "nimphomania", + "nipple", + "nipples", + "nsfw", + "nude", + "nudity", + "nutten", + "nympho", + "nymphomania", + "octopussy", + "omorashi", + "orgasm", + "orgy", + "paedophile", + "paki", + "panties", + "panty", + "pedobear", + "pedophile", + "pegging", + "penis", + "pikey", + "pissing", + "pisspig", + "playboy", + "ponyplay", + "poof", + "poon", + "poontang", + "punany", + "poopchute", + "porn", + "porno", + "pornography", + "pthc", + "pubes", + "pussy", + "queaf", + "queef", + "quim", + "raghead", + "rape", + "raping", + "rapist", + "rectum", + "rimjob", + "rimming", + "sadism", + "santorum", + "scat", + "schlong", + "scissoring", + "semen", + "sex", + "sexcam", + "sexo", + "sexy", + "sexual", + "sexually", + "sexuality", + "shemale", + "shibari", + "shit", + "shitblimp", + "shitty", + "shota", + "shrimping", + "skeet", + "slanteye", + "slut", + "s&m", + "smut", + "snatch", + "snowballing", + "sodomize", + "sodomy", + "spastic", + "spic", + "splooge", + "spooge", + "spunk", + "strapon", + "strappado", + "suck", + "sucks", + "swastika", + "swinger", + "threesome", + "throating", + "thumbzilla", + "tight white", + "tit", + "tits", + "titties", + "titty", + "topless", + "tosser", + "towelhead", + "tranny", + "tribadism", + "tubgirl", + "tushy", + "twat", + "twink", + "twinkie", + "undressing", + "upskirt", + "urophilia", + "vagina", + "viagra", + "vibrator", + "vorarephilia", + "voyeur", + "voyeurweb", + "voyuer", + "vulva", + "wank", + "wetback", + "whore", + "worldsex", + "xx", + "xxx", + "yaoi", + "yiffy", + "zoophilia" + }; +} + +std::string ConvertSecondsToTime(int duration, bool is_milliseconds) +{ + if (duration <= 0) { + return "Unknown"; + } + + if (is_milliseconds && duration < 1000) { + return fmt::format( + "{} Millisecond{}", + duration, + duration != 1 ? "s" : "" + ); + } + + int timer_length = ( + is_milliseconds ? + static_cast(std::ceil(static_cast(duration) / 1000.0f)) : + duration + ); + + int days = int(timer_length / 86400); + timer_length %= 86400; + int hours = int(timer_length / 3600); + timer_length %= 3600; + int minutes = int(timer_length / 60); + timer_length %= 60; + int seconds = timer_length; + std::string time_string = "Unknown"; + std::string day_string = (days == 1 ? "Day" : "Days"); + std::string hour_string = (hours == 1 ? "Hour" : "Hours"); + std::string minute_string = (minutes == 1 ? "Minute" : "Minutes"); + std::string second_string = (seconds == 1 ? "Second" : "Seconds"); + if (days && hours && minutes && seconds) { // DHMS + time_string = fmt::format( + "{} {}, {} {}, {} {}, and {} {}", + days, + day_string, + hours, + hour_string, + minutes, + minute_string, + seconds, + second_string + ); + } else if (days && hours && minutes && !seconds) { // DHM + time_string = fmt::format( + "{} {}, {} {}, and {} {}", + days, + day_string, + hours, + hour_string, + minutes, + minute_string + ); + } else if (days && hours && !minutes && seconds) { // DHS + time_string = fmt::format( + "{} {}, {} {}, and {} {}", + days, + day_string, + hours, + hour_string, + seconds, + second_string + ); + } else if (days && hours && !minutes && !seconds) { // DH + time_string = fmt::format( + "{} {} and {} {}", + days, + day_string, + hours, + hour_string + ); + } else if (days && !hours && minutes && seconds) { // DMS + time_string = fmt::format( + "{} {}, {} {}, and {} {}", + days, + day_string, + minutes, + minute_string, + seconds, + second_string + ); + } else if (days && !hours && minutes && !seconds) { // DM + time_string = fmt::format( + "{} {} and {} {}", + days, + day_string, + minutes, + minute_string + ); + } else if (days && !hours && !minutes && seconds) { // DS + time_string = fmt::format( + "{} {} and {} {}", + days, + day_string, + seconds, + second_string + ); + } else if (days && !hours && !minutes && !seconds) { // D + time_string = fmt::format( + "{} {}", + days, + day_string + ); + } else if (!days && hours && minutes && seconds) { // HMS + time_string = fmt::format( + "{} {}, {} {}, and {} {}", + hours, + hour_string, + minutes, + minute_string, + seconds, + second_string + ); + } else if (!days && hours && minutes && !seconds) { // HM + time_string = fmt::format( + "{} {} and {} {}", + hours, + hour_string, + minutes, + minute_string + ); + } else if (!days && hours && !minutes && seconds) { // HS + time_string = fmt::format( + "{} {} and {} {}", + hours, + hour_string, + seconds, + second_string + ); + } else if (!days && hours && !minutes && !seconds) { // H + time_string = fmt::format( + "{} {}", + hours, + hour_string + ); + } else if (!days && !hours && minutes && seconds) { // MS + time_string = fmt::format( + "{} {} and {} {}", + minutes, + minute_string, + seconds, + second_string + ); + } else if (!days && !hours && minutes && !seconds) { // M + time_string = fmt::format( + "{} {}", + minutes, + minute_string + ); + } else if (!days && !hours && !minutes && seconds) { // S + time_string = fmt::format( + "{} {}", + seconds, + second_string + ); + } + return time_string; +} + +std::string ConvertMoneyToString(uint32 platinum, uint32 gold, uint32 silver, uint32 copper) +{ + std::string money_string = "Unknown"; + if (copper && silver && gold && platinum) { // CSGP + money_string = fmt::format( + "{} Platinum, {} Gold, {} Silver, and {} Copper", + platinum, + gold, + silver, + copper + ); + } else if (copper && silver && gold && !platinum) { // CSG + money_string = fmt::format( + "{} Gold, {} Silver, and {} Copper", + gold, + silver, + copper + ); + } else if (copper && silver && !gold && !platinum) { // CS + money_string = fmt::format( + "{} Silver and {} Copper", + silver, + copper + ); + } else if (!copper && silver && gold && platinum) { // SGP + money_string = fmt::format( + "{} Platinum, {} Gold, and {} Silver", + platinum, + gold, + silver + ); + } else if (!copper && silver && gold && !platinum) { // SG + money_string = fmt::format( + "{} Gold and {} Silver", + gold, + silver + ); + } else if (copper && !silver && gold && platinum) { // CGP + money_string = fmt::format( + "{} Platinum, {} Gold, and {} Copper", + platinum, + gold, + copper + ); + } else if (copper && !silver && gold && !platinum) { // CG + money_string = fmt::format( + "{} Gold and {} Copper", + gold, + copper + ); + } else if (!copper && !silver && gold && platinum) { // GP + money_string = fmt::format( + "{} Platinum and {} Gold", + platinum, + gold + ); + } else if (!copper && !silver && !gold && platinum) { // P + money_string = fmt::format( + "{} Platinum", + platinum + ); + } else if (!copper && !silver && gold && !platinum) { // G + money_string = fmt::format( + "{} Gold", + gold + ); + } else if (!copper && silver && !gold && !platinum) { // S + money_string = fmt::format( + "{} Silver", + silver + ); + } else if (copper && !silver && !gold && !platinum) { // C + money_string = fmt::format( + "{} Copper", + copper + ); + } + return money_string; +} \ No newline at end of file diff --git a/common/string_util.h b/common/string_util.h index 31d566bde..f6b7e35ce 100644 --- a/common/string_util.h +++ b/common/string_util.h @@ -39,13 +39,17 @@ const std::string str_tolower(std::string s); const std::string str_toupper(std::string s); const std::string ucfirst(std::string s); -std::vector split(std::string str_to_split, char delimiter); const std::string StringFormat(const char* format, ...); const std::string vStringFormat(const char* format, va_list args); std::vector wrap(std::vector &src, std::string character); std::string implode(std::string glue, std::vector src); std::string convert2digit(int n, std::string suffix); std::string numberToWords(unsigned long long int n); +std::string ConvertMoneyToString(uint32 platinum, uint32 gold = 0, uint32 silver = 0, uint32 copper = 0); +std::string ConvertSecondsToTime(int duration, bool is_milliseconds = false); +inline std::string ConvertMillisecondsToTime(int duration) { + return ConvertSecondsToTime(duration, true); +} // For converstion of numerics into English // Used for grid nodes, as NPC names remove numerals. @@ -177,7 +181,9 @@ std::vector join_tuple(const std::string &glue, const std::pair SplitString(const std::string &s, char delim); +std::vector SplitString(const std::string &s, const char delim = ','); +std::vector split_string(std::string s, std::string delimiter); +std::string get_between(const std::string &s, std::string start_delim, std::string stop_delim); std::string::size_type search_deliminated_string(const std::string &haystack, const std::string &needle, const char deliminator = ','); std::string EscapeString(const char *src, size_t sz); std::string EscapeString(const std::string &s); @@ -186,6 +192,7 @@ void ToLowerString(std::string &s); void ToUpperString(std::string &s); std::string JoinString(const std::vector& ar, const std::string &delim); void find_replace(std::string& string_subject, const std::string& search_string, const std::string& replace_string); +std::string replace_string(std::string subject, const std::string &search, const std::string &replace); void ParseAccountString(const std::string &s, std::string &account, std::string &loginserver); //const char based @@ -206,5 +213,23 @@ void RemoveApostrophes(std::string &s); std::string convert2digit(int n, std::string suffix); std::string numberToWords(unsigned long long int n); std::string FormatName(const std::string& char_name); +bool IsAllowedWorldServerCharacterList(char c); +void SanitizeWorldServerName(char *name); +std::string SanitizeWorldServerName(std::string server_long_name); +std::string repeat(std::string s, int n); +std::vector GetBadWords(); + +template +auto CleanMobName(InputIterator first, InputIterator last, OutputIterator result) +{ + for (; first != last; ++first) { + if(*first == '_') { + *result = ' '; + } else if (isalpha(*first) || *first == '`') { + *result = *first; + } + } + return result; +} #endif diff --git a/common/tasks.h b/common/tasks.h new file mode 100644 index 000000000..8d19910e7 --- /dev/null +++ b/common/tasks.h @@ -0,0 +1,445 @@ +#ifndef EQEMU_TASKS_H +#define EQEMU_TASKS_H + +#include "serialize_buffer.h" + +#define MAXTASKS 10000 +#define MAXTASKSETS 1000 +#define MAXACTIVEQUESTS 19 // The Client has a hard cap of 19 active quests, 29 in SoD+ +#define MAXCHOOSERENTRIES 40 // The Max Chooser (Task Selector entries) is capped at 40 in the Titanium Client. +#define MAXACTIVITIESPERTASK 20 // The Client has a hard cap of 20 activities per task. +#define TASKSLOTEMPTY 0 // This is used to determine if a client's active task slot is empty. + +#define TASKSLOTTASK 0 +#define TASKSLOTSHAREDTASK 0 + +// Command Codes for worldserver ServerOP_ReloadTasks +#define RELOADTASKS 0 +#define RELOADTASKGOALLISTS 1 +#define RELOADTASKPROXIMITIES 2 +#define RELOADTASKSETS 3 + +typedef enum { + METHODSINGLEID = 0, + METHODLIST = 1, + METHODQUEST = 2 +} TaskMethodType; + +enum class TaskActivityType : int32_t // task element/objective +{ + Unknown = -1, // hidden + None = 0, + Deliver = 1, + Kill = 2, + Loot = 3, + SpeakWith = 4, + Explore = 5, + TradeSkill = 6, + Fish = 7, + Forage = 8, + CastOn = 9, + SkillOn = 10, + Touch = 11, + Collect = 13, + GiveCash = 100 +}; + +enum class TaskTimerType +{ + Replay = 0, + Request +}; + +struct ActivityInformation { + int step_number; + TaskActivityType activity_type; + std::string target_name; // name mob, location -- default empty, max length 64 + std::string item_list; // likely defaults to empty + std::string skill_list; // IDs ; separated -- default -1 + std::string spell_list; // IDs ; separated -- default 0 + std::string description_override; // overrides auto generated description -- default empty, max length 128 + int skill_id; // older clients, first id from above + int spell_id; // older clients, first id from above + int goal_id; + TaskMethodType goal_method; + int goal_count; + int deliver_to_npc; + std::vector zone_ids; + std::string zones; // IDs ; separated, ZoneID is the first in this list for older clients -- default empty string, max length 64 + bool optional; + + inline bool CheckZone(int zone_id) + { + if (zone_ids.empty()) { + return true; + } + return std::find(zone_ids.begin(), zone_ids.end(), zone_id) != zone_ids.end(); + } + + void SerializeSelector(SerializeBuffer& out, EQ::versions::ClientVersion client_version) const + { + out.WriteInt32(static_cast(activity_type)); + out.WriteInt32(0); // solo/group/raid request type? (no longer in live) + out.WriteString(target_name); // target name used in objective type string (max 64) + + if (client_version >= EQ::versions::ClientVersion::RoF) + { + out.WriteLengthString(item_list); // used in objective type string (can be empty for none) + out.WriteInt32(activity_type == TaskActivityType::GiveCash ? 1 : goal_count); + out.WriteLengthString(skill_list); // used in SkillOn objective type string, "-1" for none + out.WriteLengthString(spell_list); // used in CastOn objective type string, "0" for none + out.WriteString(zones); // used in objective zone column and task select "begins in" (may have multiple, "0" for "unknown zone", empty for "ALL") + } + else + { + out.WriteString(item_list); + out.WriteInt32(activity_type == TaskActivityType::GiveCash ? 1 : goal_count); + out.WriteInt32(skill_id); + out.WriteInt32(spell_id); + out.WriteInt32(zone_ids.empty() ? 0 : zone_ids.front()); + } + + out.WriteString(description_override); + + if (client_version >= EQ::versions::ClientVersion::RoF) { + out.WriteString(zones); // serialized again after description (seems unused) + } + } + + void SerializeObjective(SerializeBuffer& out, EQ::versions::ClientVersion client_version, int done_count) const + { + // cash objectives internally repurpose goal_count to store cash amount and + // done_count as a boolean (should not be sent as actual completed/goal counts) + int real_goal_count = goal_count; + if (activity_type == TaskActivityType::GiveCash) + { + done_count = (done_count >= goal_count) ? 1 : 0; + real_goal_count = 1; + } + + out.WriteInt32(static_cast(activity_type)); + + if (client_version >= EQ::versions::ClientVersion::RoF) { + out.WriteInt8(optional ? 1 : 0); + } else { + out.WriteInt32(optional ? 1 : 0); + } + + out.WriteInt32(0); // solo/group/raid request type? (no longer in live) + out.WriteString(target_name); // target name used in objective type string (max 64) + + if (client_version >= EQ::versions::ClientVersion::RoF) + { + out.WriteLengthString(item_list); // used in objective type string (can be empty for none) + out.WriteInt32(real_goal_count); + out.WriteLengthString(skill_list); // used in SkillOn objective type string, "-1" for none + out.WriteLengthString(spell_list); // used in CastOn objective type string, "0" for none + out.WriteString(zones); // used in objective zone column and task select "begins in" ("0" for "unknown zone", empty for "ALL") + } + else + { + out.WriteString(item_list); + out.WriteInt32(real_goal_count); + out.WriteInt32(skill_id); + out.WriteInt32(spell_id); + out.WriteInt32(zone_ids.empty() ? 0 : zone_ids.front()); + } + + out.WriteInt32(0); // unknown id + out.WriteString(description_override); + out.WriteInt32(done_count); + out.WriteInt8(1); // unknown + + if (client_version >= EQ::versions::ClientVersion::RoF) + { + out.WriteString(zones); // serialized again after description (seems unused) + } + } +}; + +typedef enum { + ActivitiesSequential = 0, + ActivitiesStepped = 1 +} SequenceType; + +enum class TaskType { + Task = 0, // can have at max 1 + Shared = 1, // can have at max 1 + Quest = 2, // can have at max 19 or 29 depending on client + E = 3 // can have at max 19 or 29 depending on client, not present in live anymore +}; + +static const uint8 TASK_TYPE_TASK = 0; +static const uint8 TASK_TYPE_SHARED = 1; +static const uint8 TASK_TYPE_QUEST = 2; + +enum class DurationCode { + None = 0, + Short = 1, + Medium = 2, + Long = 3 +}; + +struct TaskInformation { + TaskType type; + int duration{}; + DurationCode duration_code; // description for time investment for when duration == 0 + std::string title{}; // max length 64 + std::string description{}; // max length 4000, 2048 on Tit + std::string reward{}; + std::string item_link{}; // max length 128 older clients, item link gets own string + std::string completion_emote{}; // emote after completing task, yellow. Maybe should make more generic ... but yellow for now! + int reward_id{}; + int cash_reward{}; // Expressed in copper + int experience_reward{}; + int faction_reward{}; // just a npc_faction_id + TaskMethodType reward_method; + int reward_radiant_crystals; + int reward_ebon_crystals; + int activity_count{}; + SequenceType sequence_mode; + int last_step{}; + short min_level{}; + short max_level{}; + int level_spread; + int min_players; + int max_players; + bool repeatable{}; + int replay_timer_seconds; + int request_timer_seconds; + ActivityInformation activity_information[MAXACTIVITIESPERTASK]; + + void SerializeSelector(SerializeBuffer& out, EQ::versions::ClientVersion client_version) const + { + if (client_version != EQ::versions::ClientVersion::Titanium) { + out.WriteFloat(1.0f); // reward multiplier (affects color, <1.0: yellow-red, 1.0: white, >1.0: lightgreen-green) + } + + out.WriteUInt32(duration); // task duration (seconds) (0: task_duration_code used, "Unlimited" on live) + out.WriteUInt32(static_cast(duration_code)); // 1: Short 2: Medium 3: Long anything else Unlimited (no longer in live) + out.WriteString(title); // max 64 with null + out.WriteString(description); // max 4000 with null + + if (client_version != EQ::versions::ClientVersion::Titanium) { + out.WriteUInt8(0); // 0: no rewards 1: enables "Reward Preview" button + } + + // selector only needs to send the first objective to fill description starting zone + out.WriteUInt32(std::min(activity_count, 1)); // number of task objectives + if (activity_count > 0) + { + out.WriteUInt32(0); // objective index + activity_information[0].SerializeSelector(out, client_version); + } + } +}; + +typedef enum { + ActivityHidden = 0, + ActivityActive = 1, + ActivityCompleted = 2 +} ActivityState; + +struct ClientActivityInformation { + int activity_id; + int done_count; + ActivityState activity_state; + bool updated; // Flag so we know if we need to updated the database +}; + +struct ClientTaskInformation { + int slot; // intrusive, but makes things easier :P + int task_id; + int current_step; + int accepted_time; + bool updated; + ClientActivityInformation activity[MAXACTIVITIESPERTASK]; +}; + +struct CompletedTaskInformation { + int task_id; + int completed_time; + bool activity_done[MAXACTIVITIESPERTASK]; +}; + +namespace Tasks { + + inline int GetActivityStateIdentifier(ActivityState activity_state) + { + switch (activity_state) { + case ActivityHidden: + return 0; + case ActivityActive: + return 1; + case ActivityCompleted: + return 2; + default: + return 0; + } + } + inline std::string GetActivityStateDescription(ActivityState activity_state) + { + switch (activity_state) { + case ActivityHidden: + return "Hidden"; + case ActivityActive: + return "Active"; + case ActivityCompleted: + return "Completed"; + default: + return "Hidden"; + } + } + + inline int GetTaskTypeIdentifier(TaskType task_type) + { + switch (task_type) { + case TaskType::Task: + return 0; + case TaskType::Shared: + return 1; + case TaskType::Quest: + return 2; + case TaskType::E: + return 3; + default: + return 0; + } + } + inline std::string GetTaskTypeDescription(TaskType task_type) + { + switch (task_type) { + case TaskType::Task: + return "Task"; + case TaskType::Shared: + return "Shared"; + case TaskType::Quest: + return "Quest"; + case TaskType::E: + return "E"; + default: + return "Task"; + } + } +} + +namespace SharedTaskMessage { + constexpr uint16 TASK_ASSIGN_WAIT_REPLAY_TIMER = 8017; // This task can not be assigned to you because you must wait %1d:%2h:%3m before you can do another task of this type. + constexpr uint16 COULD_NOT_USE_COMMAND = 8272; // You could not use this command because you are not currently assigned to a shared task. + constexpr uint16 AVG_LVL_LOW = 8553; // You can not be assigned this shared task because your party's average level is too low. + constexpr uint16 AVG_LVL_HIGH = 8889; // You can not be assigned this shared task because your party's average level is too high. + constexpr uint16 LVL_SPREAD_HIGH = 8890; // You can not be assigned this shared task because your party's level spread is too high. + constexpr uint16 PARTY_EXCEED_MAX_PLAYER = 8891; // You can not be assigned this shared task because your party exceeds the maximum allowed number of players. + constexpr uint16 LEADER_NOT_MEET_REQUIREMENTS = 8892; // You can not be assigned this shared task because the leader does not meet the shared task requirements. + constexpr uint16 SHARED_TASK_NOT_MEET_MIN_NUM_PLAYER = 8895; // You can not be assigned this shared task because your party does not contain the minimum required number of players. + constexpr uint16 WILL_REMOVE_ZONE_TWO_MIN_RAID_NOT_MIN_NUM_PLAYER = 8908; // %1 will be removed from their zone in two minutes because your raid does not meet the minimum requirement of qualified players. + constexpr uint16 WILL_REMOVE_ZONE_TWO_MIN_GROUP_NOT_MIN_NUM_PLAYER = 8909; // %1 will be removed from their zone in two minutes because your group does not meet the minimum requirement of qualified players. + constexpr uint16 WILL_REMOVE_AREA_TWO_MIN_RAID_NOT_MIN_NUM_PLAYER = 8910; // %1 will be removed from their area in two minutes because your raid does not meet the minimum requirement of qualified players. + constexpr uint16 WILL_REMOVE_AREA_TWO_MIN_GROUP_NOT_MIN_NUM_PLAYER = 8911; // %1 will be removed from their area in two minutes because your group does not meet the minimum requirement of qualified players. + constexpr uint16 HAS_REMOVED_ZONE_TWO_MIN_RAID_NOT_MIN_NUM_PLAYER = 8912; // %1 has been removed from their zone because your raid does not meet the minimum requirement of qualified players. + constexpr uint16 HAS_REMOVED_ZONE_TWO_MIN_GROUP_NOT_MIN_NUM_PLAYER = 8913; // %1 has been removed from their zone because your group does not meet the minimum requirement of qualified players. + constexpr uint16 HAS_REMOVED_AREA_TWO_MIN_RAID_NOT_MIN_NUM_PLAYER = 8914; // %1 has been removed from their area because your raid does not meet the minimum requirement of qualified players. + constexpr uint16 HAS_REMOVED_AREA_TWO_MIN_GROUP_NOT_MIN_NUM_PLAYER = 8915; // %1 has been removed from their area because your group does not meet the minimum requirement of qualified players. + constexpr uint16 SEND_INVITE_TO = 8916; // Sending a shared task invitation to %1. + constexpr uint16 COULD_NOT_BE_INVITED = 8917; // %1 could not be invited to join you. + constexpr uint16 YOU_ARE_NOT_LEADER_COMMAND_ISSUE = 8919; // You are not the shared task leader. Only %1 can issue this command. + constexpr uint16 SWAP_SENDING_INVITATION_TO = 8920; // Sending an invitation to: %1. They must accept in order to swap party members. + constexpr uint16 SWAP_ACCEPTED_OFFER = 8921; // %1 has accepted your offer to join your shared task. Swapping %1 for %2. + constexpr uint16 IS_NOT_MEMBER = 8922; // %1 is not a member of this shared task. + constexpr uint16 NOT_ALLOW_PLAYER_REMOVE = 8923; // The shared task is not allowing players to be removed from it at this time. + constexpr uint16 PLAYER_HAS_BEEN_REMOVED = 8924; // %1 has been removed from your shared task, '%2'. + constexpr uint16 TRANSFER_LEADERSHIP_NOT_ONLINE = 8925; // %1 is not currently online. You can only transfer leadership to an online member of the shared task. + constexpr uint16 MADE_LEADER = 8926; // %1 has been made the leader for this shared task. + constexpr uint16 YOU_MADE_LEADER = 8927; // You have been made the leader of this shared task. + constexpr uint16 LEADER_PRINT = 8928; // Shared Task Leader: %1 + constexpr uint16 MEMBERS_PRINT = 8929; // Shared Task Members: %1 + constexpr uint16 PLAYER_ACCEPTED_OFFER_JOIN = 8930; // %1 has accepted your offer to join your shared task. + constexpr uint16 PLAYER_HAS_BEEN_ADDED = 8931; // %1 has been added to your shared task, '%2'. + constexpr uint16 ACCEPTED_OFFER_TO_JOIN_BUT_COULD_NOT = 8932; // %1 accepted your offer to join your shared task but could not. + constexpr uint16 PLAYER_DECLINED_OFFER = 8933; // %1 has declined your offer to join your shared task. + constexpr uint16 PLAYER_HAS_ASKED_YOU_TO_JOIN = 8934; // %1 has asked you to join the shared task '%2'. Would you like to join? + constexpr uint16 NO_REQUEST_BECAUSE_HAVE_ONE = 8935; // You may not request a shared task because you already have one. + constexpr uint16 NO_REQUEST_BECAUSE_RAID_HAS_ONE = 8936; // You may not request a shared task because someone in your raid, %1, already has one. + constexpr uint16 NO_REQUEST_BECAUSE_GROUP_HAS_ONE = 8937; // You may not request a shared task because someone in your group, %1, already has one. + constexpr uint16 YOU_DO_NOT_MEET_REQ_AVAILABLE = 8938; // You do not meet the requirements for any available shared tasks. + constexpr uint16 YOUR_RAID_DOES_NOT_MEET_REQ = 8939; // Your raid does not meet the requirements for any available shared tasks. + constexpr uint16 YOUR_GROUP_DOES_NOT_MEET_REQ = 8940; // Your group does not meet the requirements for any available shared tasks. + constexpr uint16 YOUR_GROUP__RAID_DOES_NOT_MEET_REQ = 8941; // You can not be assigned this shared task because the raid or group does not meet the shared task requirements. + constexpr uint16 YOU_NO_LONGER_MEMBER = 8942; // You are no longer a member of the shared task. + constexpr uint16 YOU_MAY_NOT_REQUEST_EXPANSION = 8943; // You may not request this shared task because you do not have the required expansion. + constexpr uint16 PLAYER_MAY_NOT_REQUEST_EXPANSION = 8944; // You may not request this shared task because %1 does not have the required expansion. + constexpr uint16 TWO_MIN_REQ_TASK_TERMINATED = 8945; // If your party does not meet the requirements in two minutes, the shared task will be terminated. + constexpr uint16 YOU_MUST_WAIT_REPLAY_TIMER = 8946; // You may not request this shared task because you must wait %1d:%2h:%3m before you can do another task of this type. + constexpr uint16 PLAYER_MUST_WAIT_REPLAY_TIMER = 8947; // You may not request this shared task because %1 must wait %2d:%3h:%4m before they can do another task of this type. + constexpr uint16 PLAYER_NOW_LEADER = 8948; // %1 is now the leader of your shared task, '%2'. + constexpr uint16 HAS_ENDED = 8951; // Your shared task, '%1', has ended. + constexpr uint16 YOU_ALREADY_LEADER = 8952; // You are already the leader of the shared task. + constexpr uint16 TASK_NO_LONGER_ACTIVE = 8953; // Your shared task, '%1', is no longer active. + constexpr uint16 YOU_HAVE_BEEN_ADDED_TO_TASK = 8954; // You have been added to the shared task '%1'. + constexpr uint16 YOU_ARE_NOW_LEADER = 8955; // You are now the leader of your shared task, '%1'. + constexpr uint16 YOU_HAVE_BEEN_REMOVED = 8956; // You have been removed from the shared task '%1'. + constexpr uint16 YOU_ARE_NO_LONGER_A_MEMBER = 8960; // You are no longer a member of the shared task, '%1'. + constexpr uint16 YOUR_TASK_NOW_LOCKED = 8961; // Your shared task is now locked. You may no longer add or remove players. + constexpr uint16 TASK_NOT_ALLOWING_PLAYERS_AT_TIME = 8962; // The shared task is not allowing players to be added at this time. + constexpr uint16 PLAYER_NOT_ONLINE_TO_ADD = 8963; // %1 is not currently online. A player needs to be online to be added to a shared task. + constexpr uint16 CANT_ADD_PLAYER_ALREADY_MEMBER = 8964; // You can not add %1 because they are already a member of this shared task. + constexpr uint16 CANT_ADD_PLAYER_ALREADY_ASSIGNED = 8965; // You can not add %1 because they are already assigned to another shared task. + constexpr uint16 PLAYER_ALREADY_OUTSTANDING_INVITATION_THIS = 8966; // %1 already has an outstanding invitation to join this shared task. + constexpr uint16 PLAYER_ALREADY_OUTSTANDING_ANOTHER = 8967; // %1 already has an outstanding invitation to join another shared task. Players may only have one invitation outstanding. + constexpr uint16 CANT_ADD_PLAYER_MAX_PLAYERS = 8968; // You can not add another player since you currently have the maximum number of players allowed (%1) in this shared task. + constexpr uint16 CANT_ADD_PLAYER_MAX_LEVEL_SPREAD = 8969; // You can not add this player because you would exceed the maximum level spread (%1) for this shared task. + constexpr uint16 CANT_ADD_PLAYER_MAX_AVERAGE_LEVEL = 8970; // You can not add this player because you would exceed the maximum average level for this shared task. + constexpr uint16 CANT_ADD_PLAYER_FALL_MIN_AVG_LEVEL = 8971; // You can not add this player because you would fall below the minimum average level for this shared task. + constexpr uint16 PLAYER_DOES_NOT_OWN_EXPANSION = 8972; // %1 does not own the expansion needed for this shared task. + constexpr uint16 CANT_ADD_PLAYER_PARTY_FILTER_REQ_FOR_TASK = 8973; // You can not add this player because your party would no longer meet the filter requirements for this shared task. + constexpr uint16 CANT_ADD_PLAYER_ONE_OF_GROUP_RAID_HAS_TASK = 8977; // You can not add %1 because they or one of their group or raid members is in another shared task. + constexpr uint16 CANT_JOIN_GROUP_ACTIVE_TASK = 8978; // You can not join that group because you have an active shared task. + constexpr uint16 CANT_ADD_PLAYER_REPLAY_TIMER = 8979; // You may not add %1 because they must wait %2d:%3h:%4m before they can do another task of this type. + constexpr uint16 CANT_LOOT_BECAUSE_TASK_LOCKED_BELONG = 8980; // You may not loot that corpse because you are not in the shared task the corpse belongs to. + constexpr uint16 CANT_ADD_PLAYER_BECAUSE_GROUP_RAID_BELONG_TASK = 8981; // The player could not be added to the raid because they or one of their group members is in a different shared task. + constexpr uint16 PLAYER_CANT_ADD_GROUP_BECAUSE_DIFF_TASK = 8982; // %1 can not be added to the group because they are in a different shared task. + constexpr uint16 YOU_CANT_ADD_TO_GROUP_BECAUSE_DIFF_TASK = 8983; // You can not be added to the group because you are in a different shared task. + constexpr uint16 PLAYER_CANT_ADD_RAID_BECAUSE_DIFF_TASK = 8984; // %1 can not be added to the raid because they are in a different shared task. + constexpr uint16 YOU_CANT_ADD_RAID_BECAUSE_DIFF_TASK = 8985; // You can not be added to the raid because you are in a different shared task. + constexpr uint16 REPLAY_TIMER_REMAINING = 8987; // '%1' replay timer: %2d:%3h:%4m remaining. + constexpr uint16 YOU_NO_CURRENT_REPLAY_TIMERS = 8989; // You do not currently have any task replay timers. + constexpr uint16 SURE_QUIT_TASK = 8995; // Are you sure you want to quit the task '%1'? + constexpr uint16 SURE_REMOVE_SELF_FROM_TASK = 8996; // Are you sure you want to remove yourself from the shared task '%1' + constexpr uint16 TASK_ASSIGN_WAIT_REQUEST_TIMER = 14506; // This task can not be assigned to you because you must wait %1d:%2h:%3m before you can request another task of this type. + constexpr uint16 REQUEST_TIMER_REMAINING = 14507; // '%1' request timer: %2d:%3h:%4m remaining. + constexpr uint16 YOU_MUST_WAIT_REQUEST_TIMER = 14508; // You may not request this shared task because you must wait %1d:%2h:%3m before you can request another task of this type. + constexpr uint16 RECEIVED_REQUEST_TIMER = 14509; // You have received a request timer for '%1': %2d:%3h:%4m remaining. + constexpr uint16 RECEIVED_REPLAY_TIMER = 14510; // You have received a replay timer for '%1': %2d:%3h:%4m remaining. + constexpr uint16 PLAYER_MUST_WAIT_REQUEST_TIMER = 14511; // You may not request this shared task because %1 must wait %2d:%3h:%4m before they can request another task of this type. + constexpr uint16 CANT_ADD_PLAYER_REQUEST_TIMER = 14512; // You may not add %1 because they must wait %2d:%3h:%4m before they can request another task of this type. + + // for eqstrs not in current emu clients (some are also used by non-shared tasks) + constexpr auto GetEQStr(uint16 eqstr_id) + { + switch (eqstr_id) + { + case SharedTaskMessage::COULD_NOT_USE_COMMAND: + return "You could not use this command because you are not currently assigned to a shared task."; + case SharedTaskMessage::TASK_ASSIGN_WAIT_REQUEST_TIMER: + return "This task can not be assigned to you because you must wait {}d:{}h:{}m before you can request another task of this type."; + case SharedTaskMessage::REQUEST_TIMER_REMAINING: + return "'{}' request timer: {}d:{}h:{}m remaining."; + case SharedTaskMessage::YOU_MUST_WAIT_REQUEST_TIMER: + return "You may not request this shared task because you must wait {}d:{}h:{}m before you can request another task of this type."; + case SharedTaskMessage::RECEIVED_REQUEST_TIMER: + return "You have received a request timer for '{}': {}d:{}h:{}m remaining."; + case SharedTaskMessage::RECEIVED_REPLAY_TIMER: + return "You have received a replay timer for '{}': {}d:{}h:{}m remaining."; + case SharedTaskMessage::PLAYER_MUST_WAIT_REQUEST_TIMER: + return "You may not request this shared task because {} must wait {}d:{}h:{}m before they can request another task of this type."; + case SharedTaskMessage::CANT_ADD_PLAYER_REQUEST_TIMER: + return "You may not add {} because they must wait {}d:{}h:{}m before they can request another task of this type."; + default: + LogTasks("[GetEQStr] Unhandled eqstr id [{}]", eqstr_id); + break; + } + return "Unknown EQStr"; + } +} + +#endif //EQEMU_TASKS_H diff --git a/common/version.h b/common/version.h index 56b4f3b0c..568513041 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 9161 +#define CURRENT_BINARY_DATABASE_VERSION 9175 #ifdef BOTS - #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9027 + #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9028 #else #define CURRENT_BINARY_BOTS_DATABASE_VERSION 0 // must be 0 #endif diff --git a/loginserver/CMakeLists.txt b/loginserver/CMakeLists.txt index 7d60655fc..eec55af2a 100644 --- a/loginserver/CMakeLists.txt +++ b/loginserver/CMakeLists.txt @@ -22,7 +22,7 @@ SET(eqlogin_headers loginserver_command_handler.h loginserver_webserver.h login_server.h - login_structures.h + login_types.h options.h server_manager.h world_server.h diff --git a/loginserver/account_management.cpp b/loginserver/account_management.cpp index 0ed766c9f..2c3506db0 100644 --- a/loginserver/account_management.cpp +++ b/loginserver/account_management.cpp @@ -1,28 +1,9 @@ -/** - * EQEmulator: Everquest Server Emulator - * Copyright (C) 2001-2019 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 "account_management.h" #include "login_server.h" #include "../common/event/task_scheduler.h" #include "../common/event/event_loop.h" #include "../common/net/dns.h" +#include "../common/string_util.h" extern LoginServer server; EQ::Event::TaskScheduler task_runner; @@ -300,6 +281,8 @@ bool AccountManagement::UpdateLoginserverWorldAdminAccountPasswordById( return updated_account; } +constexpr int REQUEST_TIMEOUT_MS = 1500; + /** * @param in_account_username * @param in_account_password @@ -395,19 +378,33 @@ uint32 AccountManagement::CheckExternalLoginserverUserCredentials( } ); - EQ::Net::DNSLookup( - "login.eqemulator.net", 5999, false, [&](const std::string &addr) { - if (addr.empty()) { - ret = 0; - running = false; - } + auto s = SplitString(server.options.GetEQEmuLoginServerAddress(), ':'); + if (s.size() == 2) { + auto address = s[0]; + auto port = std::stoi(s[1]); - mgr.Connect(addr, 5999); - } - ); + EQ::Net::DNSLookup( + address, port, false, [&](const std::string &addr) { + if (addr.empty()) { + ret = 0; + running = false; + } + + mgr.Connect(addr, port); + } + ); + } + + std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); auto &loop = EQ::EventLoop::Get(); while (running) { + std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); + if (std::chrono::duration_cast(end - begin).count() > REQUEST_TIMEOUT_MS) { + LogInfo("[CheckExternalLoginserverUserCredentials] Deadline exceeded [{}]", REQUEST_TIMEOUT_MS); + running = false; + } + loop.Process(); } @@ -416,4 +413,115 @@ uint32 AccountManagement::CheckExternalLoginserverUserCredentials( ); return res.get(); -} \ No newline at end of file +} + +uint32 AccountManagement::HealthCheckUserLogin() +{ + std::string in_account_username = "healthcheckuser"; + std::string in_account_password = "healthcheckpassword"; + + auto res = task_runner.Enqueue( + [&]() -> uint32 { + bool running = true; + uint32 ret = 0; + + EQ::Net::DaybreakConnectionManager mgr; + std::shared_ptr c; + + mgr.OnNewConnection( + [&](std::shared_ptr connection) { + c = connection; + } + ); + + mgr.OnConnectionStateChange( + [&]( + std::shared_ptr conn, + EQ::Net::DbProtocolStatus from, + EQ::Net::DbProtocolStatus to + ) { + if (EQ::Net::StatusConnected == to) { + EQ::Net::DynamicPacket p; + p.PutUInt16(0, 1); //OP_SessionReady + p.PutUInt32(2, 2); + c->QueuePacket(p); + } + else if (EQ::Net::StatusDisconnected == to) { + running = false; + } + } + ); + + mgr.OnPacketRecv( + [&](std::shared_ptr conn, const EQ::Net::Packet &p) { + auto opcode = p.GetUInt16(0); + switch (opcode) { + case 0x0017: //OP_ChatMessage + { + size_t buffer_len = + in_account_username.length() + in_account_password.length() + 2; + + std::unique_ptr buffer(new char[buffer_len]); + + strcpy(&buffer[0], in_account_username.c_str()); + strcpy(&buffer[in_account_username.length() + 1], in_account_password.c_str()); + + size_t encrypted_len = buffer_len; + + if (encrypted_len % 8 > 0) { + encrypted_len = ((encrypted_len / 8) + 1) * 8; + } + + EQ::Net::DynamicPacket p; + p.Resize(12 + encrypted_len); + p.PutUInt16(0, 2); //OP_Login + p.PutUInt32(2, 3); + + eqcrypt_block(&buffer[0], buffer_len, (char *) p.Data() + 12, true); + c->QueuePacket(p); + break; + } + case 0x0018: { + auto encrypt_size = p.Length() - 12; + if (encrypt_size % 8 > 0) { + encrypt_size = (encrypt_size / 8) * 8; + } + + std::unique_ptr decrypted(new char[encrypt_size]); + + eqcrypt_block((char *) p.Data() + 12, encrypt_size, &decrypted[0], false); + + EQ::Net::StaticPacket sp(&decrypted[0], encrypt_size); + auto response_error = sp.GetUInt16(1); + + { + // we only care to see the response code + ret = response_error; + running = false; + } + break; + } + } + } + ); + + mgr.Connect("127.0.0.1", 5999); + + std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); + auto &loop = EQ::EventLoop::Get(); + while (running) { + std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); + if (std::chrono::duration_cast(end - begin).count() > 2000) { + ret = 0; + running = false; + } + + loop.Process(); + } + + return ret; + } + ); + + return res.get(); +} diff --git a/loginserver/account_management.h b/loginserver/account_management.h index 052f2619f..2c92af69d 100644 --- a/loginserver/account_management.h +++ b/loginserver/account_management.h @@ -1,22 +1,3 @@ -/** - * EQEmulator: Everquest Server Emulator - * Copyright (C) 2001-2019 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_ACCOUNT_MANAGEMENT_H #define EQEMU_ACCOUNT_MANAGEMENT_H @@ -108,6 +89,8 @@ public: uint32 in_account_id, const std::string &in_account_password_hash ); + + static uint32 HealthCheckUserLogin(); }; diff --git a/loginserver/client.cpp b/loginserver/client.cpp index 4b116fc1c..01a5abf50 100644 --- a/loginserver/client.cpp +++ b/loginserver/client.cpp @@ -1,29 +1,10 @@ -/** - * EQEmulator: Everquest Server Emulator - * Copyright (C) 2001-2019 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 "client.h" #include "login_server.h" #include "../common/misc_functions.h" #include "../common/eqemu_logsys.h" #include "../common/string_util.h" #include "encryption.h" +#include "account_management.h" extern LoginServer server; @@ -33,17 +14,17 @@ extern LoginServer server; */ Client::Client(std::shared_ptr c, LSClientVersion v) { - connection = c; - version = v; - status = cs_not_sent_session_ready; - account_id = 0; - play_server_id = 0; - play_sequence_id = 0; + m_connection = c; + m_client_version = v; + m_client_status = cs_not_sent_session_ready; + m_account_id = 0; + m_play_server_id = 0; + m_play_sequence_id = 0; } bool Client::Process() { - EQApplicationPacket *app = connection->PopPacket(); + EQApplicationPacket *app = m_connection->PopPacket(); while (app) { if (server.options.IsTraceOn()) { LogDebug("Application packet received from client (size {0})", app->Size()); @@ -53,9 +34,9 @@ bool Client::Process() DumpPacket(app); } - if (status == cs_failed_to_login) { + if (m_client_status == cs_failed_to_login) { delete app; - app = connection->PopPacket(); + app = m_connection->PopPacket(); continue; } @@ -112,7 +93,7 @@ bool Client::Process() } delete app; - app = connection->PopPacket(); + app = m_connection->PopPacket(); } return true; @@ -126,7 +107,7 @@ bool Client::Process() */ void Client::Handle_SessionReady(const char *data, unsigned int size) { - if (status != cs_not_sent_session_ready) { + if (m_client_status != cs_not_sent_session_ready) { LogError("Session ready received again after already being received"); return; } @@ -136,39 +117,23 @@ void Client::Handle_SessionReady(const char *data, unsigned int size) return; } - status = cs_waiting_for_login; + m_client_status = cs_waiting_for_login; /** - * The packets are mostly the same but slightly different between the two versions + * The packets are identical between the two versions */ - if (version == cv_sod) { - auto *outapp = new EQApplicationPacket(OP_ChatMessage, 17); - outapp->pBuffer[0] = 0x02; - outapp->pBuffer[10] = 0x01; - outapp->pBuffer[11] = 0x65; + auto *outapp = new EQApplicationPacket(OP_ChatMessage, sizeof(LoginHandShakeReply_Struct)); + auto buf = reinterpret_cast(outapp->pBuffer); + buf->base_header.sequence = 0x02; + buf->base_reply.success = true; + buf->base_reply.error_str_id = 0x65; // 101 "No Error" - if (server.options.IsDumpOutPacketsOn()) { - DumpPacket(outapp); - } - - connection->QueuePacket(outapp); - delete outapp; + if (server.options.IsDumpOutPacketsOn()) { + DumpPacket(outapp); } - else { - const char *msg = "ChatMessage"; - auto *outapp = new EQApplicationPacket(OP_ChatMessage, 16 + strlen(msg)); - outapp->pBuffer[0] = 0x02; - outapp->pBuffer[10] = 0x01; - outapp->pBuffer[11] = 0x65; - strcpy((char *) (outapp->pBuffer + 15), msg); - if (server.options.IsDumpOutPacketsOn()) { - DumpPacket(outapp); - } - - connection->QueuePacket(outapp); - delete outapp; - } + m_connection->QueuePacket(outapp); + delete outapp; } /** @@ -179,19 +144,23 @@ void Client::Handle_SessionReady(const char *data, unsigned int size) */ void Client::Handle_Login(const char *data, unsigned int size) { - if (status != cs_waiting_for_login) { + if (m_client_status != cs_waiting_for_login) { LogError("Login received after already having logged in"); return; } - if ((size - 12) % 8 != 0) { - LogError("Login received packet of size: {0}, this would cause a block corruption, discarding", size); + // login user/pass are variable length after unencrypted opcode and base message header (size includes opcode) + constexpr int header_size = sizeof(uint16_t) + sizeof(LoginBaseMessage_Struct); + int data_size = size - header_size; + + if (size <= header_size) { + LogError("Login received packet of size: {0}, this would cause a buffer overflow, discarding", size); return; } - if (size < sizeof(LoginLoginRequest_Struct)) { - LogError("Login received packet of size: {0}, this would cause a buffer overflow, discarding", size); + if (data_size % 8 != 0) { + LogError("Login received packet of size: {0}, this would cause a block corruption, discarding", size); return; } @@ -208,13 +177,14 @@ void Client::Handle_Login(const char *data, unsigned int size) std::string db_account_password_hash; std::string outbuffer; - outbuffer.resize(size - 12); + outbuffer.resize(data_size); if (outbuffer.length() == 0) { LogError("Corrupt buffer sent to server, no length"); return; } - auto r = eqcrypt_block(data + 10, size - 12, &outbuffer[0], 0); + // data starts at base message header (opcode not included) + auto r = eqcrypt_block(data + sizeof(LoginBaseMessage_Struct), data_size, &outbuffer[0], 0); if (r == nullptr) { LogError("Failed to decrypt eqcrypt block"); return; @@ -228,7 +198,8 @@ void Client::Handle_Login(const char *data, unsigned int size) return; } - memcpy(&llrs, data, sizeof(LoginLoginRequest_Struct)); + // only need to copy the base header for reply options, ignore login info + memcpy(&m_llrs, data, sizeof(LoginBaseMessage_Struct)); bool result = false; if (outbuffer[0] == 0 && outbuffer[1] == 0) { @@ -236,7 +207,7 @@ void Client::Handle_Login(const char *data, unsigned int size) cred = (&outbuffer[2 + user.length()]); result = server.db->GetLoginTokenDataFromToken( cred, - connection->GetRemoteAddr(), + m_connection->GetRemoteAddr(), db_account_id, db_loginserver, user @@ -252,11 +223,16 @@ void Client::Handle_Login(const char *data, unsigned int size) user = components[1]; } + // health checks + if (ProcessHealthCheck(user)) { + DoFailedLogin(); + return; + } + LogInfo( - "Attempting password based login [{0}] login [{1}] user [{2}]", + "Attempting password based login [{0}] login [{1}]", user, - db_loginserver, - user + db_loginserver ); ParseAccountString(user, user, db_loginserver); @@ -267,7 +243,7 @@ void Client::Handle_Login(const char *data, unsigned int size) LogDebug("[VerifyLoginHash] Success [{0}]", (result ? "true" : "false")); } else { - status = cs_creating_account; + m_client_status = cs_creating_account; AttemptLoginAccountCreation(user, cred, db_loginserver); return; @@ -305,14 +281,14 @@ void Client::Handle_Login(const char *data, unsigned int size) */ void Client::Handle_Play(const char *data) { - if (status != cs_logged_in) { + if (m_client_status != cs_logged_in) { LogError("Client sent a play request when they were not logged in, discarding"); return; } const auto *play = (const PlayEverquestRequest_Struct *) data; - auto server_id_in = (unsigned int) play->ServerNumber; - auto sequence_in = (unsigned int) play->Sequence; + auto server_id_in = (unsigned int) play->server_number; + auto sequence_in = (unsigned int) play->base_header.sequence; if (server.options.IsTraceOn()) { LogInfo( @@ -323,10 +299,10 @@ void Client::Handle_Play(const char *data) ); } - this->play_server_id = (unsigned int) play->ServerNumber; - play_sequence_id = sequence_in; - play_server_id = server_id_in; - server.server_manager->SendUserToWorldRequest(server_id_in, account_id, loginserver_name); + m_play_server_id = (unsigned int) play->server_number; + m_play_sequence_id = sequence_in; + m_play_server_id = server_id_in; + server.server_manager->SendUserToWorldRequest(server_id_in, m_account_id, m_loginserver_name); } /** @@ -334,14 +310,13 @@ void Client::Handle_Play(const char *data) */ void Client::SendServerListPacket(uint32 seq) { - EQApplicationPacket *outapp = server.server_manager->CreateServerListPacket(this, seq); + auto outapp = server.server_manager->CreateServerListPacket(this, seq); if (server.options.IsDumpOutPacketsOn()) { - DumpPacket(outapp); + DumpPacket(outapp.get()); } - connection->QueuePacket(outapp); - delete outapp; + m_connection->QueuePacket(outapp.get()); } void Client::SendPlayResponse(EQApplicationPacket *outapp) @@ -350,12 +325,12 @@ void Client::SendPlayResponse(EQApplicationPacket *outapp) LogDebug("Sending play response for {0}", GetAccountName()); // server_log->LogPacket(log_network_trace, (const char*)outapp->pBuffer, outapp->size); } - connection->QueuePacket(outapp); + m_connection->QueuePacket(outapp); } void Client::GenerateKey() { - key.clear(); + m_key.clear(); int count = 0; while (count < 10) { static const char key_selection[] = @@ -367,7 +342,7 @@ void Client::GenerateKey() '6', '7', '8', '9' }; - key.append((const char *) &key_selection[random.Int(0, 35)], 1); + m_key.append((const char *) &key_selection[m_random.Int(0, 35)], 1); count++; } } @@ -383,6 +358,8 @@ void Client::AttemptLoginAccountCreation( const std::string &loginserver ) { + LogInfo("[AttemptLoginAccountCreation] user [{}] loginserver [{}]", user, loginserver); + #ifdef LSPX if (loginserver == "eqemu") { LogInfo("Attempting login account creation via '{0}'", loginserver); @@ -393,59 +370,19 @@ void Client::AttemptLoginAccountCreation( return; } - if (server.options.GetEQEmuLoginServerAddress().length() == 0) { - DoFailedLogin(); - return; - } - auto addr_components = SplitString(server.options.GetEQEmuLoginServerAddress(), ':'); - if (addr_components.size() != 2) { - DoFailedLogin(); - return; - } - - stored_user = user; - stored_pass = pass; - - auto address = addr_components[0]; - auto port = std::stoi(addr_components[1]); - EQ::Net::DNSLookup( - address, port, false, [=](const std::string &addr) { - if (addr.empty()) { - DoFailedLogin(); - return; - } - - login_connection_manager.reset(new EQ::Net::DaybreakConnectionManager()); - login_connection_manager->OnNewConnection( - std::bind( - &Client::LoginOnNewConnection, - this, - std::placeholders::_1 - ) - ); - login_connection_manager->OnConnectionStateChange( - std::bind( - &Client::LoginOnStatusChange, - this, - std::placeholders::_1, - std::placeholders::_2, - std::placeholders::_3 - ) - ); - login_connection_manager->OnPacketRecv( - std::bind( - &Client::LoginOnPacketRecv, - this, - std::placeholders::_1, - std::placeholders::_2 - ) - ); - - login_connection_manager->Connect(addr, port); - } + uint32 account_id = AccountManagement::CheckExternalLoginserverUserCredentials( + user, + pass ); + if (account_id > 0) { + LogInfo("[AttemptLoginAccountCreation] Found and creating eqemu account [{}]", account_id); + CreateEQEmuAccount(user, pass, account_id); + return; + } + + DoFailedLogin(); return; } #endif @@ -461,26 +398,36 @@ void Client::AttemptLoginAccountCreation( void Client::DoFailedLogin() { - stored_user.clear(); - stored_pass.clear(); + m_stored_user.clear(); + m_stored_pass.clear(); - EQApplicationPacket outapp(OP_LoginAccepted, sizeof(LoginLoginFailed_Struct)); - auto *login_failed = (LoginLoginFailed_Struct *) outapp.pBuffer; + // unencrypted + LoginBaseMessage_Struct base_header{}; + base_header.sequence = m_llrs.sequence; // login (3) + base_header.encrypt_type = m_llrs.encrypt_type; - login_failed->unknown1 = llrs.unknown1; - login_failed->unknown2 = llrs.unknown2; - login_failed->unknown3 = llrs.unknown3; - login_failed->unknown4 = llrs.unknown4; - login_failed->unknown5 = llrs.unknown5; + // encrypted + PlayerLoginReply_Struct login_reply{}; + login_reply.base_reply.success = false; + login_reply.base_reply.error_str_id = 105; // Error - The username and/or password were not valid - memcpy(login_failed->unknown6, FailedLoginResponseData, sizeof(FailedLoginResponseData)); + char encrypted_buffer[80] = {0}; + auto rc = eqcrypt_block((const char*)&login_reply, sizeof(login_reply), encrypted_buffer, 1); + if (rc == nullptr) { + LogDebug("Failed to encrypt eqcrypt block for failed login"); + } + + constexpr int outsize = sizeof(LoginBaseMessage_Struct) + sizeof(encrypted_buffer); + EQApplicationPacket outapp(OP_LoginAccepted, outsize); + outapp.WriteData(&base_header, sizeof(base_header)); + outapp.WriteData(&encrypted_buffer, sizeof(encrypted_buffer)); if (server.options.IsDumpOutPacketsOn()) { DumpPacket(&outapp); } - connection->QueuePacket(&outapp); - status = cs_failed_to_login; + m_connection->QueuePacket(&outapp); + m_client_status = cs_failed_to_login; } /** @@ -576,68 +523,64 @@ void Client::DoSuccessfulLogin( const std::string &db_loginserver ) { - stored_user.clear(); - stored_pass.clear(); + m_stored_user.clear(); + m_stored_pass.clear(); server.client_manager->RemoveExistingClient(db_account_id, db_loginserver); in_addr in{}; - in.s_addr = connection->GetRemoteIP(); + in.s_addr = m_connection->GetRemoteIP(); server.db->UpdateLSAccountData(db_account_id, std::string(inet_ntoa(in))); GenerateKey(); - account_id = db_account_id; - account_name = in_account_name; - loginserver_name = db_loginserver; + m_account_id = db_account_id; + m_account_name = in_account_name; + m_loginserver_name = db_loginserver; - auto *outapp = new EQApplicationPacket(OP_LoginAccepted, 10 + 80); - auto *login_accepted = (LoginAccepted_Struct *) outapp->pBuffer; - login_accepted->unknown1 = llrs.unknown1; - login_accepted->unknown2 = llrs.unknown2; - login_accepted->unknown3 = llrs.unknown3; - login_accepted->unknown4 = llrs.unknown4; - login_accepted->unknown5 = llrs.unknown5; + // unencrypted + LoginBaseMessage_Struct base_header{}; + base_header.sequence = m_llrs.sequence; + base_header.compressed = false; + base_header.encrypt_type = m_llrs.encrypt_type; + base_header.unk3 = m_llrs.unk3; - auto *login_failed_attempts = new LoginFailedAttempts_Struct; - memset(login_failed_attempts, 0, sizeof(LoginFailedAttempts_Struct)); - - login_failed_attempts->failed_attempts = 0; - login_failed_attempts->message = 0x01; - login_failed_attempts->lsid = db_account_id; - login_failed_attempts->unknown3[3] = 0x03; - login_failed_attempts->unknown4[3] = 0x02; - login_failed_attempts->unknown5[0] = 0xe7; - login_failed_attempts->unknown5[1] = 0x03; - login_failed_attempts->unknown6[0] = 0xff; - login_failed_attempts->unknown6[1] = 0xff; - login_failed_attempts->unknown6[2] = 0xff; - login_failed_attempts->unknown6[3] = 0xff; - login_failed_attempts->unknown7[0] = 0xa0; - login_failed_attempts->unknown7[1] = 0x05; - login_failed_attempts->unknown8[3] = 0x02; - login_failed_attempts->unknown9[0] = 0xff; - login_failed_attempts->unknown9[1] = 0x03; - login_failed_attempts->unknown11[0] = 0x63; - login_failed_attempts->unknown12[0] = 0x01; - memcpy(login_failed_attempts->key, key.c_str(), key.size()); + // not serializing any of the variable length strings so just use struct directly + PlayerLoginReply_Struct login_reply{}; + login_reply.base_reply.success = true; + login_reply.base_reply.error_str_id = 101; // No Error + login_reply.unk1 = 0; + login_reply.unk2 = 0; + login_reply.lsid = db_account_id; + login_reply.failed_attempts = 0; + login_reply.show_player_count = server.options.IsShowPlayerCountEnabled(); + login_reply.offer_min_days = 99; + login_reply.offer_min_views = -1; + login_reply.offer_cooldown_minutes = 0; + login_reply.web_offer_number = 0; + login_reply.web_offer_min_days = 99; + login_reply.web_offer_min_views = -1; + login_reply.web_offer_cooldown_minutes = 0; + memcpy(login_reply.key, m_key.c_str(), m_key.size()); char encrypted_buffer[80] = {0}; - auto rc = eqcrypt_block((const char *) login_failed_attempts, 75, encrypted_buffer, 1); + auto rc = eqcrypt_block((const char*)&login_reply, sizeof(login_reply), encrypted_buffer, 1); if (rc == nullptr) { LogDebug("Failed to encrypt eqcrypt block"); } - memcpy(login_accepted->encrypt, encrypted_buffer, 80); + constexpr int outsize = sizeof(LoginBaseMessage_Struct) + sizeof(encrypted_buffer); + auto outapp = std::make_unique(OP_LoginAccepted, outsize); + outapp->WriteData(&base_header, sizeof(base_header)); + outapp->WriteData(&encrypted_buffer, sizeof(encrypted_buffer)); if (server.options.IsDumpOutPacketsOn()) { - DumpPacket(outapp); + DumpPacket(outapp.get()); } - connection->QueuePacket(outapp); - delete outapp; + m_connection->QueuePacket(outapp.get()); - status = cs_logged_in; + m_client_status = cs_logged_in; } /** @@ -689,7 +632,7 @@ void Client::CreateEQEmuAccount( */ void Client::LoginOnNewConnection(std::shared_ptr connection) { - login_connection = connection; + m_login_connection = connection; } /** @@ -751,16 +694,16 @@ void Client::LoginSendSessionReady() p.PutUInt16(0, 1); //OP_SessionReady p.PutUInt32(2, 2); - login_connection->QueuePacket(p); + m_login_connection->QueuePacket(p); } void Client::LoginSendLogin() { - size_t buffer_len = stored_user.length() + stored_pass.length() + 2; + size_t buffer_len = m_stored_user.length() + m_stored_pass.length() + 2; std::unique_ptr buffer(new char[buffer_len]); - strcpy(&buffer[0], stored_user.c_str()); - strcpy(&buffer[stored_user.length() + 1], stored_pass.c_str()); + strcpy(&buffer[0], m_stored_user.c_str()); + strcpy(&buffer[m_stored_user.length() + 1], m_stored_pass.c_str()); size_t encrypted_len = buffer_len; @@ -775,7 +718,7 @@ void Client::LoginSendLogin() eqcrypt_block(&buffer[0], buffer_len, (char *) p.Data() + 12, true); - login_connection->QueuePacket(p); + m_login_connection->QueuePacket(p); } /** @@ -796,7 +739,7 @@ void Client::LoginProcessLoginResponse(const EQ::Net::Packet &p) EQ::Net::StaticPacket sp(&decrypted[0], encrypt_size); auto response_error = sp.GetUInt16(1); - login_connection_manager->OnConnectionStateChange( + m_login_connection_manager->OnConnectionStateChange( std::bind( &Client::LoginOnStatusChangeIgnored, this, @@ -809,18 +752,26 @@ void Client::LoginProcessLoginResponse(const EQ::Net::Packet &p) if (response_error > 101) { LogDebug("response [{0}] failed login", response_error); DoFailedLogin(); - login_connection->Close(); + m_login_connection->Close(); } else { LogDebug( "response [{0}] login succeeded user [{1}]", response_error, - stored_user + m_stored_user ); auto m_dbid = sp.GetUInt32(8); - CreateEQEmuAccount(stored_user, stored_pass, m_dbid); - login_connection->Close(); + CreateEQEmuAccount(m_stored_user, m_stored_pass, m_dbid); + m_login_connection->Close(); } } +bool Client::ProcessHealthCheck(std::string username) +{ + if (username == "healthcheckuser") { + return true; + } + + return false; +} diff --git a/loginserver/client.h b/loginserver/client.h index 9d76e7e78..95aa0cfa7 100644 --- a/loginserver/client.h +++ b/loginserver/client.h @@ -1,23 +1,3 @@ -/** - * EQEmulator: Everquest Server Emulator - * Copyright (C) 2001-2019 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_CLIENT_H #define EQEMU_CLIENT_H @@ -27,22 +7,9 @@ #include "../common/eq_stream_intf.h" #include "../common/net/dns.h" #include "../common/net/daybreak_connection.h" -#include "login_structures.h" +#include "login_types.h" #include -enum LSClientVersion { - cv_titanium, - cv_sod -}; - -enum LSClientStatus { - cs_not_sent_session_ready, - cs_waiting_for_login, - cs_creating_account, - cs_failed_to_login, - cs_logged_in -}; - /** * Client class, controls a single client and it's connection to the login server */ @@ -116,49 +83,49 @@ public: * * @return */ - unsigned int GetAccountID() const { return account_id; } + unsigned int GetAccountID() const { return m_account_id; } /** * Gets the loginserver name of this client * * @return */ - std::string GetLoginServerName() const { return loginserver_name; } + std::string GetLoginServerName() const { return m_loginserver_name; } /** * Gets the account name of this client * * @return */ - std::string GetAccountName() const { return account_name; } + std::string GetAccountName() const { return m_account_name; } /** * Gets the key generated at login for this client * * @return */ - std::string GetKey() const { return key; } + std::string GetKey() const { return m_key; } /** * Gets the server selected to be played on for this client * * @return */ - unsigned int GetPlayServerID() const { return play_server_id; } + unsigned int GetPlayServerID() const { return m_play_server_id; } /** * Gets the play sequence state for this client * * @return */ - unsigned int GetPlaySequence() const { return play_sequence_id; } + unsigned int GetPlaySequence() const { return m_play_sequence_id; } /** * Gets the connection for this client * * @return */ - std::shared_ptr GetConnection() { return connection; } + std::shared_ptr GetConnection() { return m_connection; } /** * Attempts to create a login account @@ -195,24 +162,24 @@ public: void CreateEQEmuAccount(const std::string &in_account_name, const std::string &in_account_password, unsigned int loginserver_account_id); private: - EQ::Random random; - std::shared_ptr connection; - LSClientVersion version; - LSClientStatus status; + EQ::Random m_random; + std::shared_ptr m_connection; + LSClientVersion m_client_version; + LSClientStatus m_client_status; - std::string account_name; - unsigned int account_id; - std::string loginserver_name; - unsigned int play_server_id; - unsigned int play_sequence_id; - std::string key; + std::string m_account_name; + unsigned int m_account_id; + std::string m_loginserver_name; + unsigned int m_play_server_id; + unsigned int m_play_sequence_id; + std::string m_key; - std::unique_ptr login_connection_manager; - std::shared_ptr login_connection; - LoginLoginRequest_Struct llrs; + std::unique_ptr m_login_connection_manager; + std::shared_ptr m_login_connection; + LoginBaseMessage_Struct m_llrs; - std::string stored_user; - std::string stored_pass; + std::string m_stored_user; + std::string m_stored_pass; void LoginOnNewConnection(std::shared_ptr connection); void LoginOnStatusChange( std::shared_ptr conn, @@ -228,6 +195,7 @@ private: void LoginSendSessionReady(); void LoginSendLogin(); void LoginProcessLoginResponse(const EQ::Net::Packet &p); + static bool ProcessHealthCheck(std::string username); }; #endif diff --git a/loginserver/client_manager.cpp b/loginserver/client_manager.cpp index d37ec545f..6b3fc0bce 100644 --- a/loginserver/client_manager.cpp +++ b/loginserver/client_manager.cpp @@ -1,23 +1,3 @@ -/** - * EQEmulator: Everquest Server Emulator - * Copyright (C) 2001-2019 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 "client_manager.h" #include "login_server.h" @@ -25,6 +5,7 @@ extern LoginServer server; extern bool run_server; #include "../common/eqemu_logsys.h" +#include "../common/misc.h" ClientManager::ClientManager() { @@ -52,8 +33,8 @@ ClientManager::ClientManager() titanium_stream->OnNewConnection( [this](std::shared_ptr stream) { LogInfo( - "New Titanium client connection from {0}:{1}", - stream->GetRemoteIP(), + "New Titanium client connection from [{0}:{1}]", + long2ip(stream->GetRemoteIP()), stream->GetRemotePort() ); @@ -87,13 +68,13 @@ ClientManager::ClientManager() sod_stream->OnNewConnection( [this](std::shared_ptr stream) { LogInfo( - "New SoD client connection from {0}:{1}", - stream->GetRemoteIP(), + "New SoD+ client connection from [{0}:{1}]", + long2ip(stream->GetRemoteIP()), stream->GetRemotePort() ); stream->SetOpcodeManager(&sod_ops); - Client *c = new Client(stream, cv_sod); + auto *c = new Client(stream, cv_sod); clients.push_back(c); } ); diff --git a/loginserver/client_manager.h b/loginserver/client_manager.h index 4de290210..6a8c31164 100644 --- a/loginserver/client_manager.h +++ b/loginserver/client_manager.h @@ -1,23 +1,3 @@ -/** - * EQEmulator: Everquest Server Emulator - * Copyright (C) 2001-2019 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_CLIENTMANAGER_H #define EQEMU_CLIENTMANAGER_H diff --git a/loginserver/database.cpp b/loginserver/database.cpp index 35f49e0eb..18612dac6 100644 --- a/loginserver/database.cpp +++ b/loginserver/database.cpp @@ -1,23 +1,3 @@ -/** - * EQEmulator: Everquest Server Emulator - * Copyright (C) 2001-2019 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 "../common/global_define.h" #include "database.h" @@ -45,11 +25,6 @@ Database::Database( std::string name ) { - this->user = user; - this->pass = pass; - this->host = host; - this->name = name; - uint32 errnum = 0; char errbuf[MYSQL_ERRMSG_SIZE]; if (!Open( @@ -75,8 +50,8 @@ Database::Database( */ Database::~Database() { - if (database) { - mysql_close(database); + if (m_database) { + mysql_close(m_database); } } @@ -355,11 +330,13 @@ void Database::UpdateLoginserverAccountPasswordHash( /** * @param short_name + * @param long_name * @param login_world_server_admin_id * @return */ Database::DbWorldRegistration Database::GetWorldRegistration( const std::string &short_name, + const std::string &long_name, uint32 login_world_server_admin_id ) { @@ -375,45 +352,46 @@ Database::DbWorldRegistration Database::GetWorldRegistration( " login_world_servers AS WSR\n" " JOIN login_server_list_types AS SLT ON WSR.login_server_list_type_id = SLT.id\n" "WHERE\n" - " WSR.short_name = '{0}' AND WSR.login_server_admin_id = {1} LIMIT 1", + " WSR.short_name = '{}' AND WSR.long_name = '{}' AND WSR.login_server_admin_id = {} LIMIT 1", EscapeString(short_name), + EscapeString(long_name), login_world_server_admin_id ); - Database::DbWorldRegistration world_registration{}; + Database::DbWorldRegistration r{}; auto results = QueryDatabase(query); if (!results.Success() || results.RowCount() != 1) { - return world_registration; + return r; } auto row = results.begin(); - world_registration.loaded = true; - world_registration.server_id = std::stoi(row[0]); - world_registration.server_description = row[1]; - world_registration.server_list_type = std::stoi(row[3]); - world_registration.is_server_trusted = std::stoi(row[2]) > 0; - world_registration.server_list_description = row[4]; - world_registration.server_admin_id = std::stoi(row[5]); + r.loaded = true; + r.server_id = std::stoi(row[0]); + r.server_description = row[1]; + r.server_list_type = std::stoi(row[3]); + r.is_server_trusted = std::stoi(row[2]) > 0; + r.server_list_description = row[4]; + r.server_admin_id = std::stoi(row[5]); - if (world_registration.server_admin_id <= 0) { - return world_registration; + if (r.server_admin_id <= 0) { + return r; } auto world_registration_query = fmt::format( "SELECT account_name, account_password FROM login_server_admins WHERE id = {0} LIMIT 1", - world_registration.server_admin_id + r.server_admin_id ); auto world_registration_results = QueryDatabase(world_registration_query); if (world_registration_results.Success() && world_registration_results.RowCount() == 1) { auto world_registration_row = world_registration_results.begin(); - world_registration.server_admin_account_name = world_registration_row[0]; - world_registration.server_admin_account_password = world_registration_row[1]; + r.server_admin_account_name = world_registration_row[0]; + r.server_admin_account_password = world_registration_row[1]; } - return world_registration; + return r; } /** @@ -598,95 +576,6 @@ MySQLRequestResult Database::GetLoginserverApiTokens() return QueryDatabase("SELECT token, can_write, can_read FROM login_api_tokens"); } -/** - * @param log_settings - */ -void Database::LoadLogSettings(EQEmuLogSys::LogSettings *log_settings) -{ - std::string query = - "SELECT " - "log_category_id, " - "log_category_description, " - "log_to_console, " - "log_to_file, " - "log_to_gmsay " - "FROM " - "logsys_categories " - "ORDER BY log_category_id"; - - auto results = QueryDatabase(query); - int log_category_id = 0; - - int *categories_in_database = new int[1000]; - - for (auto row = results.begin(); row != results.end(); ++row) { - log_category_id = atoi(row[0]); - if (log_category_id <= Logs::None || log_category_id >= Logs::MaxCategoryID) { - continue; - } - - log_settings[log_category_id].log_to_console = static_cast(atoi(row[2])); - log_settings[log_category_id].log_to_file = static_cast(atoi(row[3])); - log_settings[log_category_id].log_to_gmsay = static_cast(atoi(row[4])); - - /** - * Determine if any output method is enabled for the category - * and set it to 1 so it can used to check if category is enabled - */ - const bool log_to_console = log_settings[log_category_id].log_to_console > 0; - const bool log_to_file = log_settings[log_category_id].log_to_file > 0; - const bool log_to_gmsay = log_settings[log_category_id].log_to_gmsay > 0; - const bool is_category_enabled = log_to_console || log_to_file || log_to_gmsay; - - if (is_category_enabled) { - log_settings[log_category_id].is_category_enabled = 1; - } - - /** - * This determines whether or not the process needs to actually file log anything. - * If we go through this whole loop and nothing is set to any debug level, there is no point to create a file or keep anything open - */ - if (log_settings[log_category_id].log_to_file > 0) { - LogSys.file_logs_enabled = true; - } - - categories_in_database[log_category_id] = 1; - } - - /** - * Auto inject categories that don't exist in the database... - */ - for (int log_index = Logs::AA; log_index != Logs::MaxCategoryID; log_index++) { - if (categories_in_database[log_index] != 1) { - - LogInfo( - "New Log Category [{0}] doesn't exist... Automatically adding to [logsys_categories] table...", - Logs::LogCategoryName[log_index] - ); - - auto inject_query = fmt::format( - "INSERT INTO logsys_categories " - "(log_category_id, " - "log_category_description, " - "log_to_console, " - "log_to_file, " - "log_to_gmsay) " - "VALUES " - "({0}, '{1}', {2}, {3}, {4})", - log_index, - EscapeString(Logs::LogCategoryName[log_index]), - std::to_string(log_settings[log_index].log_to_console), - std::to_string(log_settings[log_index].log_to_file), - std::to_string(log_settings[log_index].log_to_gmsay) - ); - - QueryDatabase(inject_query); - } - } - - delete[] categories_in_database; -} - /** * @param account_name * @param account_password @@ -754,21 +643,21 @@ Database::DbLoginServerAdmin Database::GetLoginServerAdmin(const std::string &ac auto results = QueryDatabase(query); - Database::DbLoginServerAdmin login_server_admin{}; + Database::DbLoginServerAdmin r{}; if (results.RowCount() == 1) { auto row = results.begin(); - login_server_admin.loaded = true; - login_server_admin.id = std::stoi(row[0]); - login_server_admin.account_name = row[1]; - login_server_admin.account_password = row[2]; - login_server_admin.first_name = row[3]; - login_server_admin.last_name = row[4]; - login_server_admin.email = row[5]; - login_server_admin.registration_date = row[7]; - login_server_admin.registration_ip_address = row[8]; + r.loaded = true; + r.id = std::stoi(row[0]); + r.account_name = row[1]; + r.account_password = row[2]; + r.first_name = row[3]; + r.last_name = row[4]; + r.email = row[5]; + r.registration_date = row[7]; + r.registration_ip_address = row[8]; } - return login_server_admin; + return r; } /** @@ -790,20 +679,20 @@ Database::DbLoginServerAccount Database::GetLoginServerAccountByAccountName( auto results = QueryDatabase(query); - Database::DbLoginServerAccount login_server_account{}; + Database::DbLoginServerAccount r{}; if (results.RowCount() == 1) { auto row = results.begin(); - login_server_account.loaded = true; - login_server_account.id = std::stoi(row[0]); - login_server_account.account_name = row[1]; - login_server_account.account_password = row[2]; - login_server_account.account_email = row[3]; - login_server_account.source_loginserver = row[4]; - login_server_account.last_ip_address = row[5]; - login_server_account.last_login_date = row[6]; - login_server_account.created_at = row[7]; - login_server_account.updated_at = row[8]; + r.loaded = true; + r.id = std::stoi(row[0]); + r.account_name = row[1]; + r.account_password = row[2]; + r.account_email = row[3]; + r.source_loginserver = row[4]; + r.last_ip_address = row[5]; + r.last_login_date = row[6]; + r.created_at = row[7]; + r.updated_at = row[8]; } - return login_server_account; -} \ No newline at end of file + return r; +} diff --git a/loginserver/database.h b/loginserver/database.h index 8ffcb187b..b53af1de9 100644 --- a/loginserver/database.h +++ b/loginserver/database.h @@ -1,23 +1,3 @@ -/** - * EQEmulator: Everquest Server Emulator - * Copyright (C) 2001-2019 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_DATABASEMYSQL_H #define EQEMU_DATABASEMYSQL_H @@ -32,7 +12,7 @@ class Database : public DBcore { public: - Database() { database = nullptr; } + Database() { m_database = nullptr; } /** * Constructor, tries to set our database to connect to the supplied options. @@ -49,7 +29,7 @@ public: * Destructor, frees our database if needed. */ ~Database(); - bool IsConnected() { return (database != nullptr); } + bool IsConnected() { return (m_database != nullptr); } /** * Retrieves the login data (password hash and account id) from the account name provided needed for client login procedure. @@ -158,11 +138,13 @@ public: * Returns true if the record was found, false otherwise * * @param short_name + * @param long_name * @param login_world_server_admin_id * @return */ Database::DbWorldRegistration GetWorldRegistration( const std::string &short_name, + const std::string &long_name, uint32 login_world_server_admin_id ); @@ -201,11 +183,6 @@ public: unsigned int &server_admin_id ); - /** - * @param log_settings - */ - void LoadLogSettings(EQEmuLogSys::LogSettings *log_settings); - /** * @param write_mode * @param read_mode @@ -303,8 +280,7 @@ public: ); protected: - std::string user, pass, host, port, name; - MYSQL *database{}; + MYSQL *m_database{}; }; #endif diff --git a/loginserver/encryption.h b/loginserver/encryption.h index 7db61197e..d8337c25b 100644 --- a/loginserver/encryption.h +++ b/loginserver/encryption.h @@ -1,23 +1,3 @@ -/** - * EQEmulator: Everquest Server Emulator - * Copyright (C) 2001-2019 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 - * - */ - #pragma once #include diff --git a/loginserver/eq_crypto_api.h b/loginserver/eq_crypto_api.h index bff7b8444..89d9cbe3c 100644 --- a/loginserver/eq_crypto_api.h +++ b/loginserver/eq_crypto_api.h @@ -1,23 +1,3 @@ -/** - * EQEmulator: Everquest Server Emulator - * Copyright (C) 2001-2019 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 EQEMUCAPI__H #define EQEMUCAPI__H diff --git a/loginserver/login_server.h b/loginserver/login_server.h index c130c41d2..015a35fcc 100644 --- a/loginserver/login_server.h +++ b/loginserver/login_server.h @@ -1,29 +1,7 @@ -#include - -/** - * EQEmulator: Everquest Server Emulator - * Copyright (C) 2001-2019 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_LOGINSERVER_H #define EQEMU_LOGINSERVER_H +#include #include "../common/json_config.h" #include "database.h" #include "encryption.h" diff --git a/loginserver/login_structures.h b/loginserver/login_structures.h deleted file mode 100644 index 099b4b4f0..000000000 --- a/loginserver/login_structures.h +++ /dev/null @@ -1,117 +0,0 @@ -/** - * EQEmulator: Everquest Server Emulator - * Copyright (C) 2001-2019 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_LOGINSTRUCTURES_H -#define EQEMU_LOGINSTRUCTURES_H - -#pragma pack(1) - -struct LoginChatMessage_Struct { - short Unknown0; - uint32 Unknown1; - uint32 Unknown2; - uint32 Unknown3; - uint8 Unknown4; - char ChatMessage[1]; -}; - -struct LoginLoginRequest_Struct { - short unknown1; - short unknown2; - short unknown3; - short unknown4; - short unknown5; - char unknown6[16]; -}; - -struct LoginAccepted_Struct { - short unknown1; - short unknown2; - short unknown3; - short unknown4; - short unknown5; - char encrypt[80]; -}; - -struct LoginFailedAttempts_Struct { - char message; //0x01 - char unknown2[7]; //0x00 - uint32 lsid; - char key[11]; //10 char + null term; - uint32 failed_attempts; - char unknown3[4]; //0x00, 0x00, 0x00, 0x03 - char unknown4[4]; //0x00, 0x00, 0x00, 0x02 - char unknown5[4]; //0xe7, 0x03, 0x00, 0x00 - char unknown6[4]; //0xff, 0xff, 0xff, 0xff - char unknown7[4]; //0xa0, 0x05, 0x00, 0x00 - char unknown8[4]; //0x00, 0x00, 0x00, 0x02 - char unknown9[4]; //0xff, 0x03, 0x00, 0x00 - char unknown10[4]; //0x00, 0x00, 0x00, 0x00 - char unknown11[4]; //0x63, 0x00, 0x00, 0x00 - char unknown12[4]; //0x01, 0x00, 0x00, 0x00 - char unknown13[4]; //0x00, 0x00, 0x00, 0x00 - char unknown14[4]; //0x00, 0x00, 0x00, 0x00 -}; - -struct LoginLoginFailed_Struct { - short unknown1; - short unknown2; - short unknown3; - short unknown4; - short unknown5; - char unknown6[74]; -}; - -struct ServerListHeader_Struct { - - uint32 Unknown1; - uint32 Unknown2; - uint32 Unknown3; - uint32 Unknown4; - uint32 NumberOfServers; -}; - -struct PlayEverquestRequest_Struct { - uint16 Sequence; - uint32 Unknown1; - uint32 Unknown2; - uint32 ServerNumber; -}; - -struct PlayEverquestResponse_Struct { - uint8 Sequence; - uint8 Unknown1[9]; - uint8 Allowed; - uint16 Message; - uint8 Unknown2[3]; - uint32 ServerNumber; -}; - -static const unsigned char FailedLoginResponseData[] = { - 0xf6, 0x85, 0x9c, 0x23, 0x57, 0x7e, 0x3e, 0x55, 0xb3, 0x4c, 0xf8, 0xc8, 0xcb, 0x77, 0xd5, 0x16, - 0x09, 0x7a, 0x63, 0xdc, 0x57, 0x7e, 0x3e, 0x55, 0xb3, 0x4c, 0xf8, 0xc8, 0xcb, 0x77, 0xd5, 0x16, - 0x09, 0x7a, 0x63, 0xdc, 0x57, 0x7e, 0x3e, 0x55, 0xb3 -}; - - -#pragma pack() - -#endif - diff --git a/loginserver/login_types.h b/loginserver/login_types.h new file mode 100644 index 000000000..25e64307e --- /dev/null +++ b/loginserver/login_types.h @@ -0,0 +1,143 @@ +#ifndef EQEMU_LOGINSTRUCTURES_H +#define EQEMU_LOGINSTRUCTURES_H + +#pragma pack(1) + +// unencrypted base message header in all packets +struct LoginBaseMessage_Struct { + int32_t sequence; // request type/login sequence (2: handshake, 3: login, 4: serverlist, ...) + bool compressed; // true: deflated + int8_t encrypt_type; // 1: invert (unused) 2: des (2 for encrypted player logins and order expansions) (client uses what it sent, ignores in reply) + int32_t unk3; // unused? +}; + +struct LoginBaseReplyMessage_Struct { + bool success; // 0: failure (shows error string) 1: success + int32_t error_str_id; // last error eqlsstr id, default: 101 (no error) + char str[1]; // variable length, unknown (may be unused, this struct is a common pattern elsewhere) +}; + +struct LoginHandShakeReply_Struct { + LoginBaseMessage_Struct base_header; + LoginBaseReplyMessage_Struct base_reply; + char unknown[1]; // variable length string +}; + +// for reference, login buffer is variable (minimum size 8 due to encryption) +struct PlayerLogin_Struct { + LoginBaseMessage_Struct base_header; + char username[1]; + char password[1]; +}; + +// variable length, can use directly if not serializing strings +struct PlayerLoginReply_Struct { + // base header excluded to make struct data easier to encrypt + //LoginBaseMessage_Struct base_header; + LoginBaseReplyMessage_Struct base_reply; + + int8_t unk1; // (default: 0) + int8_t unk2; // (default: 0) + int32_t lsid; // (default: -1) + char key[11]; // client reads until null (variable length) + int32_t failed_attempts; + bool show_player_count; // admin flag, enables admin button and shows server player counts (default: false) + int32_t offer_min_days; // guess, needs more investigation, maybe expansion offers (default: 99) + int32_t offer_min_views; // guess (default: -1) + int32_t offer_cooldown_minutes; // guess (default: 0) + int32_t web_offer_number; // web order view number, 0 nothing (default: 0) + int32_t web_offer_min_days; // number of days to show offer (based on first offer time in client eqls ini) (default: 99) + int32_t web_offer_min_views; // mininum views, -1 for no minimum, 0 for never shows (based on client eqls ini) (default: -1) + int32_t web_offer_cooldown_minutes; // minimum minutes between offers (based on last offer time in client eqls ini) (default: 0) + char username[1]; // variable length, if not empty client attempts to re-login to server select when quitting from char select and sends this in a struct + char unknown[1]; // variable length, password unlikely? client doesn't send this on re-login from char select +}; + +// variable length, for reference +struct LoginClientServerData_Struct { + char ip[1]; + int32_t server_type; // legends, preferred, standard + int32_t server_id; + char server_name[1]; + char country_code[1]; // if doesn't match client locale then server is colored dark grey in list and joining is prevented (to block for "us" use one of "kr", "tw", "jp", "de", "fr", or "cn") (ISO 3166-1 alpha-2) + char language_code[1]; + int32_t server_status; // see ServerStatusFlags + int32_t player_count; +}; + +// variable length, for reference +struct ServerListReply_Struct { + LoginBaseMessage_Struct base_header; + LoginBaseReplyMessage_Struct base_reply; + + int32_t server_count; + LoginClientServerData_Struct servers[0]; +}; + +struct PlayEverquestRequest_Struct { + LoginBaseMessage_Struct base_header; + uint32 server_number; +}; + +// SCJoinServerReply +struct PlayEverquestResponse_Struct { + LoginBaseMessage_Struct base_header; + LoginBaseReplyMessage_Struct base_reply; + uint32 server_number; +}; + +#pragma pack() + +enum LSClientVersion { + cv_titanium, + cv_sod +}; + +enum LSClientStatus { + cs_not_sent_session_ready, + cs_waiting_for_login, + cs_creating_account, + cs_failed_to_login, + cs_logged_in +}; + +namespace LS { + namespace ServerStatusFlags { + enum eServerStatusFlags { + Up = 0, // default + Down = 1, + Unused = 2, + Locked = 4 // can be combined with Down to show "Locked (Down)" + }; + } + + namespace ServerTypeFlags { + enum eServerTypeFlags { + None = 0, + Standard = 1, + Unknown2 = 2, + Unknown4 = 4, + Preferred = 8, + Legends = 16 // can be combined with Preferred flag to override color in Legends section with Preferred color (green) + }; + } + + enum ServerType { + Standard = 3, + Preferred = 2, + Legends = 1, + }; + + namespace ErrStr { + constexpr static int ERROR_NONE = 101; // No Error + constexpr static int ERROR_UNKNOWN = 102; // Error - Unknown Error Occurred + constexpr static int ERROR_ACTIVE_CHARACTER = 111; // Error 1018: You currently have an active character on that EverQuest Server, please allow a minute for synchronization and try again. + constexpr static int ERROR_SERVER_UNAVAILABLE = 326; // That server is currently unavailable. Please check the EverQuest webpage for current server status and try again later. + constexpr static int ERROR_ACCOUNT_SUSPENDED = 337; // This account is currently suspended. Please contact customer service for more information. + constexpr static int ERROR_ACCOUNT_BANNED = 338; // This account is currently banned. Please contact customer service for more information. + constexpr static int ERROR_WORLD_MAX_CAPACITY = 339; // The world server is currently at maximum capacity and not allowing further logins until the number of players online decreases. Please try again later. + }; +} + +#endif + diff --git a/loginserver/login_util/login.json b/loginserver/login_util/login.json index 6fbd8f742..c25242f7c 100644 --- a/loginserver/login_util/login.json +++ b/loginserver/login_util/login.json @@ -11,6 +11,9 @@ }, "worldservers": { "unregistered_allowed": true, + "show_player_count": false, + "dev_test_servers_list_bottom": false, + "special_character_start_list_bottom": false, "reject_duplicate_servers": false }, "web_api": { diff --git a/loginserver/login_util/login_schema.sql b/loginserver/login_util/login_schema.sql index b07362830..ad9805cf8 100644 --- a/loginserver/login_util/login_schema.sql +++ b/loginserver/login_util/login_schema.sql @@ -5,7 +5,7 @@ CREATE TABLE `login_accounts` ( `account_password` text NOT NULL, `account_email` varchar(100) NOT NULL, `source_loginserver` varchar(64) DEFAULT NULL, - `last_ip_address` varchar(15) NOT NULL, + `last_ip_address` varchar(80) NOT NULL, `last_login_date` datetime NOT NULL, `created_at` datetime DEFAULT NULL, `updated_at` datetime DEFAULT current_timestamp(), @@ -22,7 +22,7 @@ CREATE TABLE `login_server_admins` ( `last_name` varchar(50) NOT NULL, `email` varchar(100) NOT NULL, `registration_date` datetime NOT NULL, - `registration_ip_address` varchar(15) NOT NULL, + `registration_ip_address` varchar(80) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; @@ -45,7 +45,7 @@ CREATE TABLE `login_world_servers` ( `tag_description` varchar(50) NOT NULL DEFAULT '', `login_server_list_type_id` int(11) NOT NULL, `last_login_date` datetime DEFAULT NULL, - `last_ip_address` varchar(15) DEFAULT NULL, + `last_ip_address` varchar(80) DEFAULT NULL, `login_server_admin_id` int(11) NOT NULL, `is_server_trusted` int(11) NOT NULL, `note` varchar(255) DEFAULT NULL, @@ -61,4 +61,4 @@ CREATE TABLE `login_api_tokens` ( `created_at` datetime DEFAULT NULL, `updated_at` datetime DEFAULT current_timestamp(), PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; \ No newline at end of file +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; diff --git a/loginserver/loginserver_command_handler.cpp b/loginserver/loginserver_command_handler.cpp index 35599e299..152266902 100644 --- a/loginserver/loginserver_command_handler.cpp +++ b/loginserver/loginserver_command_handler.cpp @@ -1,23 +1,3 @@ -/** - * EQEmulator: Everquest Server Emulator - * Copyright (C) 2001-2019 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 "loginserver_command_handler.h" @@ -58,6 +38,7 @@ namespace LoginserverCommandHandler { function_map["web-api-token:list"] = &LoginserverCommandHandler::ListLoginserverApiTokens; function_map["world-admin:create"] = &LoginserverCommandHandler::CreateLoginserverWorldAdminAccount; function_map["world-admin:update"] = &LoginserverCommandHandler::UpdateLoginserverWorldAdminAccountPassword; + function_map["health:check-login"] = &LoginserverCommandHandler::HealthCheckLogin; EQEmuCommand::HandleMenu(function_map, cmd, argc, argv); } @@ -301,4 +282,24 @@ namespace LoginserverCommandHandler { cmd(3).str() ); } + + /** + * @param argc + * @param argv + * @param cmd + * @param description + */ + void HealthCheckLogin(int argc, char **argv, argh::parser &cmd, std::string &description) + { + description = "Checks login health using a test user"; + + std::vector arguments = {}; + std::vector options = {}; + + if (cmd[{"-h", "--help"}]) { + return; + } + + LogInfo("[CLI] [HealthCheck] Response code [{}]", AccountManagement::HealthCheckUserLogin()); + } } diff --git a/loginserver/loginserver_command_handler.h b/loginserver/loginserver_command_handler.h index 2f890f956..fc2f2cbeb 100644 --- a/loginserver/loginserver_command_handler.h +++ b/loginserver/loginserver_command_handler.h @@ -1,23 +1,3 @@ -/** - * EQEmulator: Everquest Server Emulator - * Copyright (C) 2001-2019 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 "iostream" #include "../common/cli/eqemu_command_handler.h" @@ -34,6 +14,7 @@ namespace LoginserverCommandHandler { void UpdateLoginserverUserCredentials(int argc, char **argv, argh::parser &cmd, std::string &description); void CheckExternalLoginserverUserCredentials(int argc, char **argv, argh::parser &cmd, std::string &description); void UpdateLoginserverWorldAdminAccountPassword(int argc, char **argv, argh::parser &cmd, std::string &description); + void HealthCheckLogin(int argc, char **argv, argh::parser &cmd, std::string &description); }; diff --git a/loginserver/loginserver_webserver.cpp b/loginserver/loginserver_webserver.cpp index 38a3508f6..bea7dc8ae 100644 --- a/loginserver/loginserver_webserver.cpp +++ b/loginserver/loginserver_webserver.cpp @@ -1,23 +1,3 @@ -/** - * EQEmulator: Everquest Server Emulator - * Copyright (C) 2001-2019 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 "loginserver_webserver.h" #include "server_manager.h" #include "login_server.h" @@ -51,14 +31,15 @@ namespace LoginserverWebserver { auto iter = server.server_manager->getWorldServers().begin(); while (iter != server.server_manager->getWorldServers().end()) { Json::Value row; - row["server_long_name"] = (*iter)->GetServerLongName(); - row["server_short_name"] = (*iter)->GetServerLongName(); - row["server_list_id"] = (*iter)->GetServerListID(); - row["server_status"] = (*iter)->GetStatus(); - row["zones_booted"] = (*iter)->GetZonesBooted(); - row["local_ip"] = (*iter)->GetLocalIP(); - row["remote_ip"] = (*iter)->GetRemoteIP(); - row["players_online"] = (*iter)->GetPlayersOnline(); + row["server_long_name"] = (*iter)->GetServerLongName(); + row["server_short_name"] = (*iter)->GetServerShortName(); + row["server_list_type_id"] = (*iter)->GetServerListID(); + row["server_status"] = (*iter)->GetStatus(); + row["zones_booted"] = (*iter)->GetZonesBooted(); + row["local_ip"] = (*iter)->GetLocalIP(); + row["remote_ip"] = (*iter)->GetRemoteIP(); + row["players_online"] = (*iter)->GetPlayersOnline(); + row["world_id"] = (*iter)->GetServerId(); response.append(row); ++iter; } @@ -316,6 +297,24 @@ namespace LoginserverWebserver { LoginserverWebserver::SendResponse(response, res); } ); + + api.Get( + "/probes/healthcheck", [](const httplib::Request &request, httplib::Response &res) { + Json::Value response; + uint32 login_response = AccountManagement::HealthCheckUserLogin(); + + response["status"] = login_response; + if (login_response == 0) { + response["message"] = "Process unresponsive, exiting..."; + LogInfo("Probes healthcheck unresponsive, exiting..."); + } + + LoginserverWebserver::SendResponse(response, res); + if (login_response == 0) { + std::exit(0); + } + } + ); } /** diff --git a/loginserver/loginserver_webserver.h b/loginserver/loginserver_webserver.h index b0cdb88c2..7eb852892 100644 --- a/loginserver/loginserver_webserver.h +++ b/loginserver/loginserver_webserver.h @@ -1,23 +1,3 @@ -/** - * EQEmulator: Everquest Server Emulator - * Copyright (C) 2001-2019 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_LOGINSERVER_WEBSERVER_H #define EQEMU_LOGINSERVER_WEBSERVER_H diff --git a/loginserver/main.cpp b/loginserver/main.cpp index 9e6006342..46f2313b3 100644 --- a/loginserver/main.cpp +++ b/loginserver/main.cpp @@ -1,23 +1,3 @@ -/** - * EQEmulator: Everquest Server Emulator - * Copyright (C) 2001-2019 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 "../common/global_define.h" #include "../common/types.h" #include "../common/opcodemgr.h" @@ -30,15 +10,18 @@ #include "login_server.h" #include "loginserver_webserver.h" #include "loginserver_command_handler.h" +#include "../common/string_util.h" #include #include #include #include +#include LoginServer server; EQEmuLogSys LogSys; bool run_server = true; +void ResolveAddresses(); void CatchSignal(int sig_num) { } @@ -79,8 +62,30 @@ void LoadServerConfig() "worldservers", "reject_duplicate_servers", false - )); - server.options.AllowUnregistered(server.config.GetVariableBool("worldservers", "unregistered_allowed", true)); + ) + ); + server.options.SetShowPlayerCount(server.config.GetVariableBool("worldservers", "show_player_count", false)); + server.options.AllowUnregistered( + server.config.GetVariableBool( + "worldservers", + "unregistered_allowed", + true + ) + ); + server.options.SetWorldDevTestServersListBottom( + server.config.GetVariableBool( + "worldservers", + "dev_test_servers_list_bottom", + false + ) + ); + server.options.SetWorldSpecialCharacterStartListBottom( + server.config.GetVariableBool( + "worldservers", + "special_character_start_list_bottom", + false + ) + ); /** * Account @@ -130,6 +135,25 @@ void LoadServerConfig() ); } +void start_web_server() +{ + int web_api_port = server.config.GetVariableInt("web_api", "port", 6000); + LogInfo("Webserver API now listening on port [{0}]", web_api_port); + + httplib::Server api; + + api.set_logger( + [](const auto &req, const auto &res) { + if (!req.path.empty()) { + LogInfo("[API] Request [{}] via [{}:{}]", req.path, req.remote_addr, req.remote_port); + } + } + ); + + LoginserverWebserver::RegisterRoutes(api); + api.listen("0.0.0.0", web_api_port); +} + int main(int argc, char **argv) { RegisterExecutablePlatform(ExePlatformLogin); @@ -165,8 +189,9 @@ int main(int argc, char **argv) LoadDatabaseConnection(); if (argc == 1) { - server.db->LoadLogSettings(LogSys.log_settings); - LogSys.StartFileLogs(); + LogSys.SetDatabase(server.db) + ->LoadLogDatabaseSettings() + ->StartFileLogs(); } /** @@ -194,7 +219,7 @@ int main(int argc, char **argv) * create client manager */ LogInfo("Client Manager Init"); - server.client_manager = new ClientManager(); + server.client_manager = new ClientManager(); if (!server.client_manager) { LogError("Client Manager Failed to Start"); LogInfo("Server Manager Shutdown"); @@ -221,13 +246,10 @@ int main(int argc, char **argv) /** * Web API */ - httplib::Server api; - int web_api_port = server.config.GetVariableInt("web_api", "port", 6000); - bool web_api_enabled = server.config.GetVariableBool("web_api", "enabled", true); + bool web_api_enabled = server.config.GetVariableBool("web_api", "enabled", true); if (web_api_enabled) { - api.bind("0.0.0.0", web_api_port); - LogInfo("Webserver API now listening on port [{0}]", web_api_port); - LoginserverWebserver::RegisterRoutes(api); + std::thread web_api_thread(start_web_server); + web_api_thread.detach(); } LogInfo("[Config] [Logging] IsTraceOn [{0}]", server.options.IsTraceOn()); @@ -240,6 +262,15 @@ int main(int argc, char **argv) #endif LogInfo("[Config] [WorldServer] IsRejectingDuplicateServers [{0}]", server.options.IsRejectingDuplicateServers()); LogInfo("[Config] [WorldServer] IsUnregisteredAllowed [{0}]", server.options.IsUnregisteredAllowed()); + LogInfo("[Config] [WorldServer] ShowPlayerCount [{0}]", server.options.IsShowPlayerCountEnabled()); + LogInfo( + "[Config] [WorldServer] DevAndTestServersListBottom [{0}]", + server.options.IsWorldDevTestServersListBottom() + ); + LogInfo( + "[Config] [WorldServer] SpecialCharactersStartListBottom [{0}]", + server.options.IsWorldSpecialCharacterStartListBottom() + ); LogInfo("[Config] [Security] GetEncryptionMode [{0}]", server.options.GetEncryptionMode()); LogInfo("[Config] [Security] IsTokenLoginAllowed [{0}]", server.options.IsTokenLoginAllowed()); LogInfo("[Config] [Security] IsPasswordLoginAllowed [{0}]", server.options.IsPasswordLoginAllowed()); @@ -250,10 +281,6 @@ int main(int argc, char **argv) server.client_manager->Process(); EQ::EventLoop::Get().Process(); - if (web_api_enabled) { - api.poll(); - } - Sleep(5); } diff --git a/loginserver/options.h b/loginserver/options.h index b774a88a0..cf1de76d3 100644 --- a/loginserver/options.h +++ b/loginserver/options.h @@ -1,23 +1,3 @@ -/** - * EQEmulator: Everquest Server Emulator - * Copyright (C) 2001-2019 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_OPTIONS_H #define EQEMU_OPTIONS_H @@ -133,6 +113,29 @@ public: inline void UpdateInsecurePasswords(bool b) { update_insecure_passwords = b; } inline bool IsUpdatingInsecurePasswords() const { return update_insecure_passwords; } + inline bool IsShowPlayerCountEnabled() const + { + return show_player_count; + } + inline void SetShowPlayerCount(bool show_player_count) + { + Options::show_player_count = show_player_count; + } + inline bool IsWorldDevTestServersListBottom() const { return world_dev_test_servers_list_bottom; } + inline void SetWorldDevTestServersListBottom(bool dev_test_servers_list_bottom) + { + Options::world_dev_test_servers_list_bottom = dev_test_servers_list_bottom; + } + + inline bool IsWorldSpecialCharacterStartListBottom() const + { + return world_special_character_start_list_bottom; + } + inline void SetWorldSpecialCharacterStartListBottom(bool world_special_character_start_list_bottom) + { + Options::world_special_character_start_list_bottom = world_special_character_start_list_bottom; + } + private: bool allow_unregistered; bool trace; @@ -140,8 +143,11 @@ private: bool dump_in_packets; bool dump_out_packets; bool reject_duplicate_servers; + bool world_dev_test_servers_list_bottom; + bool world_special_character_start_list_bottom; bool allow_token_login; bool allow_password_login; + bool show_player_count; bool auto_create_accounts; bool auto_link_accounts; bool update_insecure_passwords; @@ -150,5 +156,6 @@ private: std::string default_loginserver_name; }; + #endif diff --git a/loginserver/server_manager.cpp b/loginserver/server_manager.cpp index 1761e1b15..05758dc07 100644 --- a/loginserver/server_manager.cpp +++ b/loginserver/server_manager.cpp @@ -1,26 +1,6 @@ -/** - * EQEmulator: Everquest Server Emulator - * Copyright (C) 2001-2019 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 "server_manager.h" #include "login_server.h" -#include "login_structures.h" +#include "login_types.h" #include #include "../common/eqemu_logsys.h" @@ -33,15 +13,15 @@ ServerManager::ServerManager() { int listen_port = server.config.GetVariableInt("general", "listen_port", 5998); - server_connection = std::make_unique(); + m_server_connection = std::make_unique(); EQ::Net::ServertalkServerOptions opts; opts.port = listen_port; - opts.ipv6 = false; - server_connection->Listen(opts); + opts.ipv6 = false; + m_server_connection->Listen(opts); LogInfo("Loginserver now listening on port [{0}]", listen_port); - server_connection->OnConnectionIdentified( + m_server_connection->OnConnectionIdentified( "World", [this](std::shared_ptr world_connection) { LogInfo( "New World Server connection from {0}:{1}", @@ -49,8 +29,8 @@ ServerManager::ServerManager() world_connection->Handle()->RemotePort() ); - auto iter = world_servers.begin(); - while (iter != world_servers.end()) { + auto iter = m_world_servers.begin(); + while (iter != m_world_servers.end()) { if ((*iter)->GetConnection()->Handle()->RemoteIP().compare(world_connection->Handle()->RemoteIP()) == 0 && (*iter)->GetConnection()->Handle()->RemotePort() == world_connection->Handle()->RemotePort()) { @@ -61,27 +41,27 @@ ServerManager::ServerManager() world_connection->Handle()->RemotePort() ); - world_servers.erase(iter); + m_world_servers.erase(iter); break; } ++iter; } - world_servers.push_back(std::make_unique(world_connection)); + m_world_servers.push_back(std::make_unique(world_connection)); } ); - server_connection->OnConnectionRemoved( + m_server_connection->OnConnectionRemoved( "World", [this](std::shared_ptr c) { - auto iter = world_servers.begin(); - while (iter != world_servers.end()) { + auto iter = m_world_servers.begin(); + while (iter != m_world_servers.end()) { if ((*iter)->GetConnection()->GetUUID() == c->GetUUID()) { LogInfo( "World server {0} has been disconnected, removing.", (*iter)->GetServerLongName() ); - world_servers.erase(iter); + m_world_servers.erase(iter); return; } @@ -100,8 +80,8 @@ ServerManager::~ServerManager() = default; */ WorldServer *ServerManager::GetServerByAddress(const std::string &ip_address, int port) { - auto iter = world_servers.begin(); - while (iter != world_servers.end()) { + auto iter = m_world_servers.begin(); + while (iter != m_world_servers.end()) { if ((*iter)->GetConnection()->Handle()->RemoteIP() == ip_address && (*iter)->GetConnection()->Handle()->RemotePort()) { return (*iter).get(); @@ -117,9 +97,8 @@ WorldServer *ServerManager::GetServerByAddress(const std::string &ip_address, in * @param sequence * @return */ -EQApplicationPacket *ServerManager::CreateServerListPacket(Client *client, uint32 sequence) +std::unique_ptr ServerManager::CreateServerListPacket(Client *client, uint32 sequence) { - unsigned int packet_size = sizeof(ServerListHeader_Struct); unsigned int server_count = 0; in_addr in{}; in.s_addr = client->GetConnection()->GetRemoteIP(); @@ -127,144 +106,60 @@ EQApplicationPacket *ServerManager::CreateServerListPacket(Client *client, uint3 LogDebug("ServerManager::CreateServerListPacket via client address [{0}]", client_ip); - auto iter = world_servers.begin(); - while (iter != world_servers.end()) { - if (!(*iter)->IsAuthorized()) { + for (const auto& world_server : m_world_servers) + { + if (world_server->IsAuthorized()) { + ++server_count; + } + } + + SerializeBuffer buf; + + // LoginBaseMessage_Struct header + buf.WriteInt32(sequence); + buf.WriteInt8(0); + buf.WriteInt8(0); + buf.WriteInt32(0); + + // LoginBaseReplyMessage_Struct + buf.WriteInt8(true); // success (no error) + buf.WriteInt32(0x65); // 101 "No Error" eqlsstr + buf.WriteString(""); + + // ServerListReply_Struct + buf.WriteInt32(server_count); + + for (const auto& world_server : m_world_servers) + { + if (!world_server->IsAuthorized()) { LogDebug( - "ServerManager::CreateServerListPacket | Server [{0}] via IP [{1}] is not authorized to be listed", - (*iter)->GetServerLongName(), - (*iter)->GetConnection()->Handle()->RemoteIP() + "ServerManager::CreateServerListPacket | Server [{}] via IP [{}] is not authorized to be listed", + world_server->GetServerLongName(), + world_server->GetConnection()->Handle()->RemoteIP() ); - ++iter; continue; } - std::string world_ip = (*iter)->GetConnection()->Handle()->RemoteIP(); - if (world_ip == client_ip) { - packet_size += (*iter)->GetServerLongName().size() + (*iter)->GetLocalIP().size() + 24; + bool use_local_ip = false; - LogDebug( - "CreateServerListPacket | Building list entry | Client [{0}] IP [{1}] Server Long Name [{2}] Server IP [{3}] (Local)", - client->GetAccountName(), - client_ip, - (*iter)->GetServerLongName(), - (*iter)->GetLocalIP() - ); - } - else if (IpUtil::IsIpInPrivateRfc1918(client_ip)) { - packet_size += (*iter)->GetServerLongName().size() + (*iter)->GetLocalIP().size() + 24; - - LogDebug( - "CreateServerListPacket | Building list entry | Client [{0}] IP [{1}] Server Long Name [{2}] Server IP [{3}] (Local)", - client->GetAccountName(), - client_ip, - (*iter)->GetServerLongName(), - (*iter)->GetLocalIP() - ); - } - else { - packet_size += (*iter)->GetServerLongName().size() + (*iter)->GetRemoteIP().size() + 24; - - LogDebug( - "CreateServerListPacket | Building list entry | Client [{0}] IP [{1}] Server Long Name [{2}] Server IP [{3}] (Remote)", - client->GetAccountName(), - client_ip, - (*iter)->GetServerLongName(), - (*iter)->GetRemoteIP() - ); + std::string world_ip = world_server->GetConnection()->Handle()->RemoteIP(); + if (world_ip == client_ip || IpUtil::IsIpInPrivateRfc1918(client_ip)) { + use_local_ip = true; } - server_count++; - ++iter; + LogDebug( + "CreateServerListPacket | Building list entry | Client [{}] IP [{}] Server Long Name [{}] Server IP [{}] ({})", + client->GetAccountName(), + client_ip, + world_server->GetServerLongName(), + use_local_ip ? world_server->GetLocalIP() : world_server->GetRemoteIP(), + use_local_ip ? "Local" : "Remote" + ); + + world_server->SerializeForClientServerList(buf, use_local_ip); } - auto *outapp = new EQApplicationPacket(OP_ServerListResponse, packet_size); - auto *server_list = (ServerListHeader_Struct *) outapp->pBuffer; - - server_list->Unknown1 = sequence; - server_list->Unknown2 = 0x00000000; - server_list->Unknown3 = 0x01650000; - - /** - * Not sure what this is but it should be noted setting it to - * 0xFFFFFFFF crashes the client so: don't do that. - */ - server_list->Unknown4 = 0x00000000; - server_list->NumberOfServers = server_count; - - unsigned char *data_pointer = outapp->pBuffer; - data_pointer += sizeof(ServerListHeader_Struct); - - iter = world_servers.begin(); - while (iter != world_servers.end()) { - if (!(*iter)->IsAuthorized()) { - ++iter; - continue; - } - - std::string world_ip = (*iter)->GetConnection()->Handle()->RemoteIP(); - if (world_ip == client_ip) { - memcpy(data_pointer, (*iter)->GetLocalIP().c_str(), (*iter)->GetLocalIP().size()); - data_pointer += ((*iter)->GetLocalIP().size() + 1); - } - else if (IpUtil::IsIpInPrivateRfc1918(client_ip)) { - memcpy(data_pointer, (*iter)->GetLocalIP().c_str(), (*iter)->GetLocalIP().size()); - data_pointer += ((*iter)->GetLocalIP().size() + 1); - } - else { - memcpy(data_pointer, (*iter)->GetRemoteIP().c_str(), (*iter)->GetRemoteIP().size()); - data_pointer += ((*iter)->GetRemoteIP().size() + 1); - } - - switch ((*iter)->GetServerListID()) { - case 1: { - *(unsigned int *) data_pointer = 0x00000030; - break; - } - case 2: { - *(unsigned int *) data_pointer = 0x00000009; - break; - } - default: { - *(unsigned int *) data_pointer = 0x00000001; - } - } - - data_pointer += 4; - - *(unsigned int *) data_pointer = (*iter)->GetServerId(); - data_pointer += 4; - - memcpy(data_pointer, (*iter)->GetServerLongName().c_str(), (*iter)->GetServerLongName().size()); - data_pointer += ((*iter)->GetServerLongName().size() + 1); - - memcpy(data_pointer, "EN", 2); - data_pointer += 3; - - memcpy(data_pointer, "US", 2); - data_pointer += 3; - - // 0 = Up, 1 = Down, 2 = Up, 3 = down, 4 = locked, 5 = locked(down) - if ((*iter)->GetStatus() < 0) { - if ((*iter)->GetZonesBooted() == 0) { - *(uint32 *) data_pointer = 0x01; - } - else { - *(uint32 *) data_pointer = 0x04; - } - } - else { - *(uint32 *) data_pointer = 0x02; - } - data_pointer += 4; - - *(uint32 *) data_pointer = (*iter)->GetPlayersOnline(); - data_pointer += 4; - - ++iter; - } - - return outapp; + return std::make_unique(OP_ServerListResponse, buf); } /** @@ -278,9 +173,9 @@ void ServerManager::SendUserToWorldRequest( const std::string &client_loginserver ) { - auto iter = world_servers.begin(); + auto iter = m_world_servers.begin(); bool found = false; - while (iter != world_servers.end()) { + while (iter != m_world_servers.end()) { if ((*iter)->GetServerId() == server_id) { EQ::Net::DynamicPacket outapp; outapp.Resize(sizeof(UsertoWorldRequest_Struct)); @@ -316,8 +211,8 @@ bool ServerManager::ServerExists( WorldServer *ignore ) { - auto iter = world_servers.begin(); - while (iter != world_servers.end()) { + auto iter = m_world_servers.begin(); + while (iter != m_world_servers.end()) { if ((*iter).get() == ignore) { ++iter; continue; @@ -343,8 +238,8 @@ void ServerManager::DestroyServerByName( WorldServer *ignore ) { - auto iter = world_servers.begin(); - while (iter != world_servers.end()) { + auto iter = m_world_servers.begin(); + while (iter != m_world_servers.end()) { if ((*iter).get() == ignore) { ++iter; continue; @@ -353,7 +248,7 @@ void ServerManager::DestroyServerByName( if ((*iter)->GetServerLongName().compare(server_long_name) == 0 && (*iter)->GetServerShortName().compare(server_short_name) == 0) { (*iter)->GetConnection()->Handle()->Disconnect(); - iter = world_servers.erase(iter); + iter = m_world_servers.erase(iter); continue; } @@ -366,5 +261,5 @@ void ServerManager::DestroyServerByName( */ const std::list> &ServerManager::getWorldServers() const { - return world_servers; + return m_world_servers; } diff --git a/loginserver/server_manager.h b/loginserver/server_manager.h index 8babf8a56..ac652a2b5 100644 --- a/loginserver/server_manager.h +++ b/loginserver/server_manager.h @@ -1,23 +1,3 @@ -/** - * EQEmulator: Everquest Server Emulator - * Copyright (C) 2001-2019 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_SERVERMANAGER_H #define EQEMU_SERVERMANAGER_H @@ -65,7 +45,7 @@ public: * @param sequence * @return */ - EQApplicationPacket *CreateServerListPacket(Client *client, uint32 sequence); + std::unique_ptr CreateServerListPacket(Client *client, uint32 sequence); /** * Checks to see if there is a server exists with this name, ignoring option @@ -103,8 +83,8 @@ private: */ WorldServer *GetServerByAddress(const std::string &ip_address, int port); - std::unique_ptr server_connection; - std::list> world_servers; + std::unique_ptr m_server_connection; + std::list> m_world_servers; }; diff --git a/loginserver/world_server.cpp b/loginserver/world_server.cpp index 43846af32..24004347d 100644 --- a/loginserver/world_server.cpp +++ b/loginserver/world_server.cpp @@ -1,28 +1,8 @@ -/** - * EQEmulator: Everquest Server Emulator - * Copyright (C) 2001-2019 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 "world_server.h" #include "login_server.h" -#include "login_structures.h" -#include "../common/eqemu_logsys.h" +#include "login_types.h" #include "../common/ip_util.h" +#include "../common/string_util.h" extern LoginServer server; @@ -31,16 +11,16 @@ extern LoginServer server; */ WorldServer::WorldServer(std::shared_ptr worldserver_connection) { - connection = worldserver_connection; - zones_booted = 0; - players_online = 0; - server_status = 0; - server_id = 0; - server_list_type_id = 0; - server_process_type = 0; - is_server_authorized = false; - is_server_trusted = false; - is_server_logged_in = false; + m_connection = worldserver_connection; + m_zones_booted = 0; + m_players_online = 0; + m_server_status = 0; + m_server_id = 0; + m_server_list_type_id = 0; + m_server_process_type = 0; + m_is_server_authorized = false; + m_is_server_trusted = false; + m_is_server_logged_in = false; worldserver_connection->OnMessage( ServerOP_NewLSInfo, @@ -72,21 +52,25 @@ WorldServer::WorldServer(std::shared_ptr wo std::bind(&WorldServer::ProcessLSAccountUpdate, this, std::placeholders::_1, std::placeholders::_2) ); - m_keepalive = std::make_unique(5000, true, std::bind(&WorldServer::OnKeepAlive, this, std::placeholders::_1)); + m_keepalive = std::make_unique( + 1000, + true, + std::bind(&WorldServer::OnKeepAlive, this, std::placeholders::_1) + ); } WorldServer::~WorldServer() = default; void WorldServer::Reset() { - server_id; - zones_booted = 0; - players_online = 0; - server_status = 0; - server_list_type_id = 0; - server_process_type = 0; - is_server_authorized = false; - is_server_logged_in = false; + m_server_id; + m_zones_booted = 0; + m_players_online = 0; + m_server_status = 0; + m_server_list_type_id = 0; + m_server_process_type = 0; + m_is_server_authorized = false; + m_is_server_logged_in = false; } /** @@ -116,9 +100,15 @@ void WorldServer::ProcessNewLSInfo(uint16_t opcode, const EQ::Net::Packet &packe return; } - auto *info = (ServerNewLSInfo_Struct *) packet.Data(); + // if for whatever reason the world server is not sending an address, use the local address it sends + std::string remote_ip_addr = info->remote_ip_address; + std::string local_ip_addr = info->local_ip_address; + if (remote_ip_addr.empty() && !local_ip_addr.empty() && local_ip_addr != "127.0.0.1") { + strcpy(info->remote_ip_address, local_ip_addr.c_str()); + } + LogInfo( "New World Server Info | name [{0}] shortname [{1}] remote_address [{2}] local_address [{3}] account [{4}] password [{5}] server_type [{6}]", info->server_long_name, @@ -208,15 +198,15 @@ void WorldServer::ProcessUserToWorldResponseLegacy(uint16_t opcode, const EQ::Ne LogDebug("User-To-World Response received"); } - auto *user_to_world_response = (UsertoWorldResponseLegacy_Struct *) packet.Data(); + auto *r = (UsertoWorldResponseLegacy_Struct *) packet.Data(); - LogDebug("Trying to find client with user id of [{0}]", user_to_world_response->lsaccountid); - Client *client = server.client_manager->GetClient(user_to_world_response->lsaccountid, "eqemu"); + LogDebug("Trying to find client with user id of [{0}]", r->lsaccountid); + Client *client = server.client_manager->GetClient(r->lsaccountid, "eqemu"); if (client) { LogDebug( "Found client with user id of [{0}] and account name of [{1}]", - user_to_world_response->lsaccountid, + r->lsaccountid, client->GetAccountName() ); @@ -226,11 +216,11 @@ void WorldServer::ProcessUserToWorldResponseLegacy(uint16_t opcode, const EQ::Ne ); auto *per = (PlayEverquestResponse_Struct *) outapp->pBuffer; - per->Sequence = client->GetPlaySequence(); - per->ServerNumber = client->GetPlayServerID(); + per->base_header.sequence = client->GetPlaySequence(); + per->server_number = client->GetPlayServerID(); - if (user_to_world_response->response > 0) { - per->Allowed = 1; + if (r->response > 0) { + per->base_reply.success = true; SendClientAuth( client->GetConnection()->GetRemoteAddr(), client->GetAccountName(), @@ -240,36 +230,37 @@ void WorldServer::ProcessUserToWorldResponseLegacy(uint16_t opcode, const EQ::Ne ); } - switch (user_to_world_response->response) { + switch (r->response) { case UserToWorldStatusSuccess: - per->Message = 101; + per->base_reply.error_str_id = LS::ErrStr::ERROR_NONE; break; case UserToWorldStatusWorldUnavail: - per->Message = 326; + per->base_reply.error_str_id = LS::ErrStr::ERROR_SERVER_UNAVAILABLE; break; case UserToWorldStatusSuspended: - per->Message = 337; + per->base_reply.error_str_id = LS::ErrStr::ERROR_ACCOUNT_SUSPENDED; break; case UserToWorldStatusBanned: - per->Message = 338; + per->base_reply.error_str_id = LS::ErrStr::ERROR_ACCOUNT_BANNED; break; case UserToWorldStatusWorldAtCapacity: - per->Message = 339; + per->base_reply.error_str_id = LS::ErrStr::ERROR_WORLD_MAX_CAPACITY; break; case UserToWorldStatusAlreadyOnline: - per->Message = 111; + per->base_reply.error_str_id = LS::ErrStr::ERROR_ACTIVE_CHARACTER; break; default: - per->Message = 102; + per->base_reply.error_str_id = LS::ErrStr::ERROR_UNKNOWN; + break; } if (server.options.IsWorldTraceOn()) { LogDebug( "Sending play response: allowed [{0}] sequence [{1}] server number [{2}] message [{3}]", - per->Allowed, - per->Sequence, - per->ServerNumber, - per->Message + per->base_reply.success, + per->base_header.sequence, + per->server_number, + per->base_reply.error_str_id ); LogDebug("[Size: [{0}]] {1}", outapp->size, DumpPacketToString(outapp)); @@ -285,7 +276,7 @@ void WorldServer::ProcessUserToWorldResponseLegacy(uint16_t opcode, const EQ::Ne else { LogError( "Received User-To-World Response for [{0}] but could not find the client referenced!", - user_to_world_response->lsaccountid + r->lsaccountid ); } } @@ -344,8 +335,8 @@ void WorldServer::ProcessUserToWorldResponse(uint16_t opcode, const EQ::Net::Pac ); auto *per = (PlayEverquestResponse_Struct *) outapp->pBuffer; - per->Sequence = client->GetPlaySequence(); - per->ServerNumber = client->GetPlayServerID(); + per->base_header.sequence = client->GetPlaySequence(); + per->server_number = client->GetPlayServerID(); LogDebug( "Found sequence and play of [{0}] [{1}]", @@ -356,7 +347,7 @@ void WorldServer::ProcessUserToWorldResponse(uint16_t opcode, const EQ::Net::Pac LogDebug("[Size: [{0}]] {1}", outapp->size, DumpPacketToString(outapp)); if (user_to_world_response->response > 0) { - per->Allowed = 1; + per->base_reply.success = true; SendClientAuth( client->GetConnection()->GetRemoteAddr(), client->GetAccountName(), @@ -368,34 +359,35 @@ void WorldServer::ProcessUserToWorldResponse(uint16_t opcode, const EQ::Net::Pac switch (user_to_world_response->response) { case UserToWorldStatusSuccess: - per->Message = 101; + per->base_reply.error_str_id = LS::ErrStr::ERROR_NONE; break; case UserToWorldStatusWorldUnavail: - per->Message = 326; + per->base_reply.error_str_id = LS::ErrStr::ERROR_SERVER_UNAVAILABLE; break; case UserToWorldStatusSuspended: - per->Message = 337; + per->base_reply.error_str_id = LS::ErrStr::ERROR_ACCOUNT_SUSPENDED; break; case UserToWorldStatusBanned: - per->Message = 338; + per->base_reply.error_str_id = LS::ErrStr::ERROR_ACCOUNT_BANNED; break; case UserToWorldStatusWorldAtCapacity: - per->Message = 339; + per->base_reply.error_str_id = LS::ErrStr::ERROR_WORLD_MAX_CAPACITY; break; case UserToWorldStatusAlreadyOnline: - per->Message = 111; + per->base_reply.error_str_id = LS::ErrStr::ERROR_ACTIVE_CHARACTER; break; default: - per->Message = 102; + per->base_reply.error_str_id = LS::ErrStr::ERROR_UNKNOWN; + break; } if (server.options.IsTraceOn()) { LogDebug( "Sending play response with following data, allowed [{0}], sequence {1}, server number {2}, message {3}", - per->Allowed, - per->Sequence, - per->ServerNumber, - per->Message + per->base_reply.success, + per->base_header.sequence, + per->server_number, + per->base_reply.error_str_id ); LogDebug("[Size: [{0}]] {1}", outapp->size, DumpPacketToString(outapp)); } @@ -443,7 +435,7 @@ void WorldServer::ProcessLSAccountUpdate(uint16_t opcode, const EQ::Net::Packet } if (server.options.IsWorldTraceOn()) { - LogDebug("ServerOP_LSAccountUpdate packet received from [{0}]", short_name); + LogDebug("ServerOP_LSAccountUpdate packet received from [{0}]", m_short_name); } auto *loginserver_update = (ServerLSAccountUpdate_Struct *) packet.Data(); @@ -486,6 +478,8 @@ void WorldServer::Handle_NewLSInfo(ServerNewLSInfo_Struct *new_world_server_info return; } + SanitizeWorldServerName(new_world_server_info_packet->server_long_name); + SetAccountPassword(new_world_server_info_packet->account_password) ->SetLongName(new_world_server_info_packet->server_long_name) ->SetShortName(new_world_server_info_packet->server_short_name) @@ -506,8 +500,8 @@ void WorldServer::Handle_NewLSInfo(ServerNewLSInfo_Struct *new_world_server_info else { if (server.server_manager->ServerExists(GetServerLongName(), GetServerShortName(), this)) { LogInfo("World tried to login but there already exists a server that has that name, destroying [{}]", - long_name); - server.server_manager->DestroyServerByName(long_name, short_name, this); + m_long_name); + server.server_manager->DestroyServerByName(m_long_name, m_short_name, this); } } @@ -551,6 +545,7 @@ void WorldServer::Handle_NewLSInfo(ServerNewLSInfo_Struct *new_world_server_info Database::DbWorldRegistration world_registration = server.db->GetWorldRegistration( GetServerShortName(), + GetServerLongName(), world_server_admin_id ); @@ -578,6 +573,12 @@ void WorldServer::Handle_NewLSInfo(ServerNewLSInfo_Struct *new_world_server_info GetServerLongName(), GetRemoteIp() ); + + WorldServer::FormatWorldServerName( + new_world_server_info_packet->server_long_name, + world_registration.server_list_type + ); + SetLongName(new_world_server_info_packet->server_long_name); } /** @@ -619,7 +620,7 @@ void WorldServer::SendClientAuth( strncpy(client_auth.loginserver_name, &loginserver_name[0], 64); const std::string &client_address(ip); - std::string world_address(connection->Handle()->RemoteIP()); + std::string world_address(m_connection->Handle()->RemoteIP()); if (client_address == world_address) { client_auth.is_client_from_local_network = 1; @@ -655,13 +656,22 @@ void WorldServer::SendClientAuth( ); outapp.PutSerialize(0, client_auth); - connection->Send(ServerOP_LSClientAuth, outapp); + m_connection->Send(ServerOP_LSClientAuth, outapp); if (server.options.IsDumpInPacketsOn()) { DumpPacket(ServerOP_LSClientAuth, outapp); } } +constexpr static int MAX_ACCOUNT_NAME_LENGTH = 30; +constexpr static int MAX_ACCOUNT_PASSWORD_LENGTH = 30; +constexpr static int MAX_SERVER_LONG_NAME_LENGTH = 200; +constexpr static int MAX_SERVER_SHORT_NAME_LENGTH = 50; +constexpr static int MAX_SERVER_LOCAL_ADDRESS_LENGTH = 125; +constexpr static int MAX_SERVER_REMOTE_ADDRESS_LENGTH = 125; +constexpr static int MAX_SERVER_VERSION_LENGTH = 64; +constexpr static int MAX_SERVER_PROTOCOL_VERSION = 25; + /** * @param new_world_server_info_packet * @return @@ -670,41 +680,32 @@ bool WorldServer::HandleNewLoginserverInfoValidation( ServerNewLSInfo_Struct *new_world_server_info_packet ) { - const int max_account_name_length = 30; - const int max_account_password_length = 30; - const int max_server_long_name_length = 200; - const int max_server_short_name_length = 50; - const int max_server_local_address_length = 125; - const int max_server_remote_address_length = 125; - const int max_server_version_length = 64; - const int max_server_protocol_version = 25; - - if (strlen(new_world_server_info_packet->account_name) >= max_account_name_length) { - LogError("Handle_NewLSInfo error [account_name] was too long | max [{0}]", max_account_name_length); + if (strlen(new_world_server_info_packet->account_name) >= MAX_ACCOUNT_NAME_LENGTH) { + LogError("Handle_NewLSInfo error [account_name] was too long | max [{0}]", MAX_ACCOUNT_NAME_LENGTH); return false; } - else if (strlen(new_world_server_info_packet->account_password) >= max_account_password_length) { - LogError("Handle_NewLSInfo error [account_password] was too long | max [{0}]", max_account_password_length); + else if (strlen(new_world_server_info_packet->account_password) >= MAX_ACCOUNT_PASSWORD_LENGTH) { + LogError("Handle_NewLSInfo error [account_password] was too long | max [{0}]", MAX_ACCOUNT_PASSWORD_LENGTH); return false; } - else if (strlen(new_world_server_info_packet->server_long_name) >= max_server_long_name_length) { - LogError("Handle_NewLSInfo error [server_long_name] was too long | max [{0}]", max_server_long_name_length); + else if (strlen(new_world_server_info_packet->server_long_name) >= MAX_SERVER_LONG_NAME_LENGTH) { + LogError("Handle_NewLSInfo error [server_long_name] was too long | max [{0}]", MAX_SERVER_LONG_NAME_LENGTH); return false; } - else if (strlen(new_world_server_info_packet->server_short_name) >= max_server_short_name_length) { - LogError("Handle_NewLSInfo error [server_short_name] was too long | max [{0}]", max_server_short_name_length); + else if (strlen(new_world_server_info_packet->server_short_name) >= MAX_SERVER_SHORT_NAME_LENGTH) { + LogError("Handle_NewLSInfo error [server_short_name] was too long | max [{0}]", MAX_SERVER_SHORT_NAME_LENGTH); return false; } - else if (strlen(new_world_server_info_packet->server_version) >= max_server_short_name_length) { - LogError("Handle_NewLSInfo error [server_version] was too long | max [{0}]", max_server_version_length); + else if (strlen(new_world_server_info_packet->server_version) >= MAX_SERVER_VERSION_LENGTH) { + LogError("Handle_NewLSInfo error [server_version] was too long | max [{0}]", MAX_SERVER_VERSION_LENGTH); return false; } - else if (strlen(new_world_server_info_packet->protocol_version) >= max_server_protocol_version) { - LogError("Handle_NewLSInfo error [protocol_version] was too long | max [{0}]", max_server_protocol_version); + else if (strlen(new_world_server_info_packet->protocol_version) >= MAX_SERVER_PROTOCOL_VERSION) { + LogError("Handle_NewLSInfo error [protocol_version] was too long | max [{0}]", MAX_SERVER_PROTOCOL_VERSION); return false; } - if (strlen(new_world_server_info_packet->local_ip_address) <= max_server_local_address_length) { + if (strlen(new_world_server_info_packet->local_ip_address) <= MAX_SERVER_LOCAL_ADDRESS_LENGTH) { if (strlen(new_world_server_info_packet->local_ip_address) == 0) { LogError("Handle_NewLSInfo error, local address was null, defaulting to localhost"); SetLocalIp("127.0.0.1"); @@ -714,17 +715,17 @@ bool WorldServer::HandleNewLoginserverInfoValidation( } } else { - LogError("Handle_NewLSInfo error, local address was too long | max [{0}]", max_server_local_address_length); + LogError("Handle_NewLSInfo error, local address was too long | max [{0}]", MAX_SERVER_LOCAL_ADDRESS_LENGTH); return false; } - if (strlen(new_world_server_info_packet->remote_ip_address) <= max_server_remote_address_length) { + if (strlen(new_world_server_info_packet->remote_ip_address) <= MAX_SERVER_REMOTE_ADDRESS_LENGTH) { if (strlen(new_world_server_info_packet->remote_ip_address) == 0) { SetRemoteIp(GetConnection()->Handle()->RemoteIP()); LogWarning( "Remote address was null, defaulting to stream address [{0}]", - remote_ip_address + m_remote_ip_address ); } else { @@ -736,7 +737,7 @@ bool WorldServer::HandleNewLoginserverInfoValidation( LogWarning( "Handle_NewLSInfo remote address was too long, defaulting to stream address [{0}]", - remote_ip_address + m_remote_ip_address ); } @@ -796,7 +797,7 @@ bool WorldServer::HandleNewLoginserverRegisteredOnly( if (IsServerTrusted()) { LogDebug("WorldServer::HandleNewLoginserverRegisteredOnly | ServerOP_LSAccountUpdate sent to world"); EQ::Net::DynamicPacket outapp; - connection->Send(ServerOP_LSAccountUpdate, outapp); + m_connection->Send(ServerOP_LSAccountUpdate, outapp); } } else { @@ -847,6 +848,8 @@ bool WorldServer::HandleNewLoginserverInfoUnregisteredAllowed( ->SetIsServerTrusted(world_registration.is_server_trusted) ->SetServerListTypeId(world_registration.server_list_type); + SetIsServerAuthorized(true); + bool does_world_server_pass_authentication_check = ( world_registration.server_admin_account_name == GetAccountName() && WorldServer::ValidateWorldServerAdminLogin( @@ -864,7 +867,7 @@ bool WorldServer::HandleNewLoginserverInfoUnregisteredAllowed( if (does_world_server_have_non_empty_credentials) { if (does_world_server_pass_authentication_check) { - SetIsServerAuthorized(true); + SetIsServerLoggedIn(true); LogInfo( "Server long_name [{0}] short_name [{1}] successfully logged in", @@ -875,16 +878,13 @@ bool WorldServer::HandleNewLoginserverInfoUnregisteredAllowed( if (IsServerTrusted()) { LogDebug("WorldServer::HandleNewLoginserverRegisteredOnly | ServerOP_LSAccountUpdate sent to world"); EQ::Net::DynamicPacket outapp; - connection->Send(ServerOP_LSAccountUpdate, outapp); + m_connection->Send(ServerOP_LSAccountUpdate, outapp); } } else { - - /** - * this is the first of two cases where we should deny access even if unregistered is allowed - */ + // server is authorized to be on the loginserver list but didn't pass login check LogInfo( - "Server long_name [{0}] short_name [{1}] attempted to log in but account and password did not match the entry in the database.", + "Server long_name [{0}] short_name [{1}] attempted to log in but account and password did not match the entry in the database. Unregistered still allowed", GetServerLongName(), GetServerShortName() ); @@ -892,9 +892,7 @@ bool WorldServer::HandleNewLoginserverInfoUnregisteredAllowed( } else { - /** - * this is the second of two cases where we should deny access even if unregistered is allowed - */ + // server is authorized to be on the loginserver list but didn't pass login check if (!GetAccountName().empty() || !GetAccountPassword().empty()) { LogInfo( "Server [{0}] [{1}] did not login but this server required a password to login", @@ -903,7 +901,6 @@ bool WorldServer::HandleNewLoginserverInfoUnregisteredAllowed( ); } else { - SetIsServerAuthorized(true); LogInfo( "Server [{0}] [{1}] did not login but unregistered servers are allowed", GetServerLongName(), @@ -939,14 +936,12 @@ bool WorldServer::HandleNewLoginserverInfoUnregisteredAllowed( } } - /** - * Auto create a registration - */ + // Auto create a registration if (!server.db->CreateWorldRegistration( GetServerLongName(), GetServerShortName(), GetRemoteIp(), - server_id, + m_server_id, server_admin_id )) { return false; @@ -1038,13 +1033,56 @@ bool WorldServer::ValidateWorldServerAdminLogin( return false; } +void WorldServer::SerializeForClientServerList(SerializeBuffer &out, bool use_local_ip) const +{ + // see LoginClientServerData_Struct + if (use_local_ip) { + out.WriteString(GetLocalIP()); + } + else { + out.WriteString(GetRemoteIP()); + } + + switch (GetServerListID()) { + case LS::ServerType::Legends: + out.WriteInt32(LS::ServerTypeFlags::Legends); + break; + case LS::ServerType::Preferred: + out.WriteInt32(LS::ServerTypeFlags::Preferred); + break; + default: + out.WriteInt32(LS::ServerTypeFlags::Standard); + break; + } + + out.WriteUInt32(GetServerId()); + out.WriteString(GetServerLongName()); + out.WriteString("us"); // country code + out.WriteString("en"); // language code + + // 0 = Up, 1 = Down, 2 = Up, 3 = down, 4 = locked, 5 = locked(down) + if (GetStatus() < 0) { + if (GetZonesBooted() == 0) { + out.WriteInt32(LS::ServerStatusFlags::Down); + } + else { + out.WriteInt32(LS::ServerStatusFlags::Locked); + } + } + else { + out.WriteInt32(LS::ServerStatusFlags::Up); + } + + out.WriteUInt32(GetPlayersOnline()); +} + /** * @param in_server_list_id * @return */ WorldServer *WorldServer::SetServerListTypeId(unsigned int in_server_list_id) { - server_list_type_id = in_server_list_id; + m_server_list_type_id = in_server_list_id; return this; } @@ -1054,7 +1092,7 @@ WorldServer *WorldServer::SetServerListTypeId(unsigned int in_server_list_id) */ const std::string &WorldServer::GetServerDescription() const { - return server_description; + return m_server_description; } /** @@ -1062,7 +1100,7 @@ const std::string &WorldServer::GetServerDescription() const */ WorldServer *WorldServer::SetServerDescription(const std::string &in_server_description) { - WorldServer::server_description = in_server_description; + WorldServer::m_server_description = in_server_description; return this; } @@ -1072,7 +1110,7 @@ WorldServer *WorldServer::SetServerDescription(const std::string &in_server_desc */ bool WorldServer::IsServerAuthorized() const { - return is_server_authorized; + return m_is_server_authorized; } /** @@ -1080,7 +1118,7 @@ bool WorldServer::IsServerAuthorized() const */ WorldServer *WorldServer::SetIsServerAuthorized(bool in_is_server_authorized) { - WorldServer::is_server_authorized = in_is_server_authorized; + WorldServer::m_is_server_authorized = in_is_server_authorized; return this; } @@ -1090,7 +1128,7 @@ WorldServer *WorldServer::SetIsServerAuthorized(bool in_is_server_authorized) */ bool WorldServer::IsServerLoggedIn() const { - return is_server_logged_in; + return m_is_server_logged_in; } /** @@ -1098,7 +1136,7 @@ bool WorldServer::IsServerLoggedIn() const */ WorldServer *WorldServer::SetIsServerLoggedIn(bool in_is_server_logged_in) { - WorldServer::is_server_logged_in = in_is_server_logged_in; + WorldServer::m_is_server_logged_in = in_is_server_logged_in; return this; } @@ -1108,7 +1146,7 @@ WorldServer *WorldServer::SetIsServerLoggedIn(bool in_is_server_logged_in) */ bool WorldServer::IsServerTrusted() const { - return is_server_trusted; + return m_is_server_trusted; } /** @@ -1116,7 +1154,7 @@ bool WorldServer::IsServerTrusted() const */ WorldServer *WorldServer::SetIsServerTrusted(bool in_is_server_trusted) { - WorldServer::is_server_trusted = in_is_server_trusted; + WorldServer::m_is_server_trusted = in_is_server_trusted; return this; } @@ -1126,7 +1164,7 @@ WorldServer *WorldServer::SetIsServerTrusted(bool in_is_server_trusted) */ WorldServer *WorldServer::SetZonesBooted(unsigned int in_zones_booted) { - WorldServer::zones_booted = in_zones_booted; + WorldServer::m_zones_booted = in_zones_booted; return this; } @@ -1136,7 +1174,7 @@ WorldServer *WorldServer::SetZonesBooted(unsigned int in_zones_booted) */ WorldServer *WorldServer::SetPlayersOnline(unsigned int in_players_online) { - WorldServer::players_online = in_players_online; + WorldServer::m_players_online = in_players_online; return this; } @@ -1146,7 +1184,7 @@ WorldServer *WorldServer::SetPlayersOnline(unsigned int in_players_online) */ WorldServer *WorldServer::SetServerStatus(int in_server_status) { - WorldServer::server_status = in_server_status; + WorldServer::m_server_status = in_server_status; return this; } @@ -1156,7 +1194,7 @@ WorldServer *WorldServer::SetServerStatus(int in_server_status) */ WorldServer *WorldServer::SetServerProcessType(unsigned int in_server_process_type) { - WorldServer::server_process_type = in_server_process_type; + WorldServer::m_server_process_type = in_server_process_type; return this; } @@ -1166,7 +1204,7 @@ WorldServer *WorldServer::SetServerProcessType(unsigned int in_server_process_ty */ WorldServer *WorldServer::SetLongName(const std::string &in_long_name) { - WorldServer::long_name = in_long_name; + WorldServer::m_long_name = in_long_name; return this; } @@ -1176,7 +1214,7 @@ WorldServer *WorldServer::SetLongName(const std::string &in_long_name) */ WorldServer *WorldServer::SetShortName(const std::string &in_short_name) { - WorldServer::short_name = in_short_name; + WorldServer::m_short_name = in_short_name; return this; } @@ -1186,7 +1224,7 @@ WorldServer *WorldServer::SetShortName(const std::string &in_short_name) */ WorldServer *WorldServer::SetAccountName(const std::string &in_account_name) { - WorldServer::account_name = in_account_name; + WorldServer::m_account_name = in_account_name; return this; } @@ -1196,7 +1234,7 @@ WorldServer *WorldServer::SetAccountName(const std::string &in_account_name) */ WorldServer *WorldServer::SetAccountPassword(const std::string &in_account_password) { - WorldServer::account_password = in_account_password; + WorldServer::m_account_password = in_account_password; return this; } @@ -1206,7 +1244,7 @@ WorldServer *WorldServer::SetAccountPassword(const std::string &in_account_passw */ WorldServer *WorldServer::SetRemoteIp(const std::string &in_remote_ip) { - WorldServer::remote_ip_address = in_remote_ip; + WorldServer::m_remote_ip_address = in_remote_ip; return this; } @@ -1216,7 +1254,7 @@ WorldServer *WorldServer::SetRemoteIp(const std::string &in_remote_ip) */ WorldServer *WorldServer::SetLocalIp(const std::string &in_local_ip) { - WorldServer::local_ip = in_local_ip; + WorldServer::m_local_ip = in_local_ip; return this; } @@ -1226,7 +1264,7 @@ WorldServer *WorldServer::SetLocalIp(const std::string &in_local_ip) */ WorldServer *WorldServer::SetProtocol(const std::string &in_protocol) { - WorldServer::protocol = in_protocol; + WorldServer::m_protocol = in_protocol; return this; } @@ -1236,7 +1274,7 @@ WorldServer *WorldServer::SetProtocol(const std::string &in_protocol) */ WorldServer *WorldServer::SetVersion(const std::string &in_version) { - WorldServer::version = in_version; + WorldServer::m_version = in_version; return this; } @@ -1246,7 +1284,7 @@ WorldServer *WorldServer::SetVersion(const std::string &in_version) */ int WorldServer::GetServerStatus() const { - return server_status; + return m_server_status; } /** @@ -1254,7 +1292,7 @@ int WorldServer::GetServerStatus() const */ unsigned int WorldServer::GetServerListTypeId() const { - return server_list_type_id; + return m_server_list_type_id; } /** @@ -1262,7 +1300,7 @@ unsigned int WorldServer::GetServerListTypeId() const */ unsigned int WorldServer::GetServerProcessType() const { - return server_process_type; + return m_server_process_type; } /** @@ -1270,7 +1308,7 @@ unsigned int WorldServer::GetServerProcessType() const */ const std::string &WorldServer::GetAccountName() const { - return account_name; + return m_account_name; } /** @@ -1278,7 +1316,7 @@ const std::string &WorldServer::GetAccountName() const */ const std::string &WorldServer::GetAccountPassword() const { - return account_password; + return m_account_password; } /** @@ -1286,7 +1324,7 @@ const std::string &WorldServer::GetAccountPassword() const */ const std::string &WorldServer::GetRemoteIp() const { - return remote_ip_address; + return m_remote_ip_address; } /** @@ -1294,7 +1332,7 @@ const std::string &WorldServer::GetRemoteIp() const */ const std::string &WorldServer::GetLocalIp() const { - return local_ip; + return m_local_ip; } /** @@ -1302,7 +1340,7 @@ const std::string &WorldServer::GetLocalIp() const */ const std::string &WorldServer::GetProtocol() const { - return protocol; + return m_protocol; } /** @@ -1310,11 +1348,45 @@ const std::string &WorldServer::GetProtocol() const */ const std::string &WorldServer::GetVersion() const { - return version; + return m_version; } void WorldServer::OnKeepAlive(EQ::Timer *t) { ServerPacket pack(ServerOP_KeepAlive, 0); - connection->SendPacket(&pack); + m_connection->SendPacket(&pack); +} + +void WorldServer::FormatWorldServerName(char *name, int8 server_list_type) +{ + std::string server_long_name = name; + server_long_name = trim(server_long_name); + + bool name_set_to_bottom = false; + if (server_list_type == LS::ServerType::Standard) { + if (server.options.IsWorldDevTestServersListBottom()) { + std::string s = str_tolower(server_long_name); + if (s.find("dev") != std::string::npos) { + server_long_name = fmt::format("|D| {}", server_long_name); + name_set_to_bottom = true; + } + else if (s.find("test") != std::string::npos) { + server_long_name = fmt::format("|T| {}", server_long_name); + name_set_to_bottom = true; + } + else if (s.find("installer") != std::string::npos) { + server_long_name = fmt::format("|I| {}", server_long_name); + name_set_to_bottom = true; + } + } + if (server.options.IsWorldSpecialCharacterStartListBottom() && !name_set_to_bottom) { + auto first_char = server_long_name.c_str()[0]; + if (IsAllowedWorldServerCharacterList(first_char) && first_char != '|') { + server_long_name = fmt::format("|*| {}", server_long_name); + name_set_to_bottom = true; + } + } + } + + strn0cpy(name, server_long_name.c_str(), 201); } diff --git a/loginserver/world_server.h b/loginserver/world_server.h index 04db63924..c4ea5f87d 100644 --- a/loginserver/world_server.h +++ b/loginserver/world_server.h @@ -1,23 +1,3 @@ -/** - * EQEmulator: Everquest Server Emulator - * Copyright (C) 2001-2019 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_WORLDSERVER_H #define EQEMU_WORLDSERVER_H @@ -50,44 +30,44 @@ public: /** * Accesses connection, it is intentional that this is not const (trust me). */ - std::shared_ptr GetConnection() { return connection; } - void SetConnection(std::shared_ptr c) { connection = c; } + std::shared_ptr GetConnection() { return m_connection; } + void SetConnection(std::shared_ptr c) { m_connection = c; } /** * @return */ - unsigned int GetServerId() const { return server_id; } + unsigned int GetServerId() const { return m_server_id; } WorldServer *SetServerId(unsigned int id) { - server_id = id; + m_server_id = id; return this; } /** * @return */ - std::string GetServerLongName() const { return long_name; } - std::string GetServerShortName() const { return short_name; } + std::string GetServerLongName() const { return m_long_name; } + std::string GetServerShortName() const { return m_short_name; } /** * Gets whether the server is authorized to show up on the server list or not * @return */ - bool IsAuthorized() const { return is_server_authorized; } - std::string GetLocalIP() const { return local_ip; } - std::string GetRemoteIP() const { return remote_ip_address; } + bool IsAuthorized() const { return m_is_server_authorized; } + std::string GetLocalIP() const { return m_local_ip; } + std::string GetRemoteIP() const { return m_remote_ip_address; } /** * Gets what kind of server this server is (legends, preferred, normal) * * @return */ - unsigned int GetServerListID() const { return server_list_type_id; } + unsigned int GetServerListID() const { return m_server_list_type_id; } WorldServer *SetServerListTypeId(unsigned int in_server_list_id); - int GetStatus() const { return server_status; } - unsigned int GetZonesBooted() const { return zones_booted; } - unsigned int GetPlayersOnline() const { return players_online; } + int GetStatus() const { return m_server_status; } + unsigned int GetZonesBooted() const { return m_zones_booted; } + unsigned int GetPlayersOnline() const { return m_players_online; } /** * Takes the info struct we received from world and processes it @@ -170,6 +150,8 @@ public: bool HandleNewLoginserverRegisteredOnly(Database::DbWorldRegistration &world_registration); bool HandleNewLoginserverInfoUnregisteredAllowed(Database::DbWorldRegistration &world_registration); + void SerializeForClientServerList(class SerializeBuffer& out, bool use_local_ip) const; + private: /** @@ -184,27 +166,26 @@ private: void ProcessUserToWorldResponse(uint16_t opcode, const EQ::Net::Packet &packet); void ProcessLSAccountUpdate(uint16_t opcode, const EQ::Net::Packet &packet); - std::shared_ptr connection; + std::shared_ptr m_connection; - unsigned int zones_booted; - unsigned int players_online; - int server_status; - unsigned int server_id; - unsigned int server_list_type_id; - unsigned int server_process_type; - std::string server_description; - std::string long_name; - std::string short_name; - std::string account_name; - std::string account_password; - std::string remote_ip_address; - std::string local_ip; - std::string protocol; - std::string version; - - bool is_server_authorized; - bool is_server_logged_in; - bool is_server_trusted; + unsigned int m_zones_booted; + unsigned int m_players_online; + int m_server_status; + unsigned int m_server_id; + unsigned int m_server_list_type_id; + unsigned int m_server_process_type; + std::string m_server_description; + std::string m_long_name; + std::string m_short_name; + std::string m_account_name; + std::string m_account_password; + std::string m_remote_ip_address; + std::string m_local_ip; + std::string m_protocol; + std::string m_version; + bool m_is_server_authorized; + bool m_is_server_logged_in; + bool m_is_server_trusted; /** * Keepalive @@ -213,6 +194,7 @@ private: void OnKeepAlive(EQ::Timer *t); std::unique_ptr m_keepalive; + static void FormatWorldServerName(char *name, int8 server_list_type); }; #endif diff --git a/queryserv/database.cpp b/queryserv/database.cpp index 20465e6b5..8f2de678d 100644 --- a/queryserv/database.cpp +++ b/queryserv/database.cpp @@ -448,89 +448,3 @@ void Database::GeneralQueryReceive(ServerPacket *pack) safe_delete_array(queryBuffer); } - -void Database::LoadLogSettings(EQEmuLogSys::LogSettings *log_settings) -{ - std::string query = - "SELECT " - "log_category_id, " - "log_category_description, " - "log_to_console, " - "log_to_file, " - "log_to_gmsay " - "FROM " - "logsys_categories " - "ORDER BY log_category_id"; - - auto results = QueryDatabase(query); - int log_category_id = 0; - - int *categories_in_database = new int[1000]; - - for (auto row = results.begin(); row != results.end(); ++row) { - log_category_id = atoi(row[0]); - if (log_category_id <= Logs::None || log_category_id >= Logs::MaxCategoryID) { - continue; - } - - log_settings[log_category_id].log_to_console = static_cast(atoi(row[2])); - log_settings[log_category_id].log_to_file = static_cast(atoi(row[3])); - log_settings[log_category_id].log_to_gmsay = static_cast(atoi(row[4])); - - /** - * Determine if any output method is enabled for the category - * and set it to 1 so it can used to check if category is enabled - */ - const bool log_to_console = log_settings[log_category_id].log_to_console > 0; - const bool log_to_file = log_settings[log_category_id].log_to_file > 0; - const bool log_to_gmsay = log_settings[log_category_id].log_to_gmsay > 0; - const bool is_category_enabled = log_to_console || log_to_file || log_to_gmsay; - - if (is_category_enabled) { - log_settings[log_category_id].is_category_enabled = 1; - } - - /** - * This determines whether or not the process needs to actually file log anything. - * If we go through this whole loop and nothing is set to any debug level, there is no point to create a file or keep anything open - */ - if (log_settings[log_category_id].log_to_file > 0) { - LogSys.file_logs_enabled = true; - } - - categories_in_database[log_category_id] = 1; - } - - /** - * Auto inject categories that don't exist in the database... - */ - for (int log_index = Logs::AA; log_index != Logs::MaxCategoryID; log_index++) { - if (categories_in_database[log_index] != 1) { - - LogInfo( - "New Log Category [{0}] doesn't exist... Automatically adding to [logsys_categories] table...", - Logs::LogCategoryName[log_index] - ); - - auto inject_query = fmt::format( - "INSERT INTO logsys_categories " - "(log_category_id, " - "log_category_description, " - "log_to_console, " - "log_to_file, " - "log_to_gmsay) " - "VALUES " - "({0}, '{1}', {2}, {3}, {4})", - log_index, - EscapeString(Logs::LogCategoryName[log_index]), - std::to_string(log_settings[log_index].log_to_console), - std::to_string(log_settings[log_index].log_to_file), - std::to_string(log_settings[log_index].log_to_gmsay) - ); - - QueryDatabase(inject_query); - } - } - - delete[] categories_in_database; -} \ No newline at end of file diff --git a/queryserv/database.h b/queryserv/database.h index 13815c575..9aa6383a3 100644 --- a/queryserv/database.h +++ b/queryserv/database.h @@ -53,8 +53,6 @@ public: void LogMerchantTransaction(QSMerchantLogTransaction_Struct* QS, uint32 Items); void GeneralQueryReceive(ServerPacket *pack); - void LoadLogSettings(EQEmuLogSys::LogSettings* log_settings); - protected: void HandleMysqlError(uint32 errnum); private: diff --git a/queryserv/queryserv.cpp b/queryserv/queryserv.cpp index 67a371a8e..005d54c6a 100644 --- a/queryserv/queryserv.cpp +++ b/queryserv/queryserv.cpp @@ -42,15 +42,15 @@ const queryservconfig *Config; WorldServer *worldserver = 0; EQEmuLogSys LogSys; -void CatchSignal(int sig_num) { - RunLoops = false; +void CatchSignal(int sig_num) { + RunLoops = false; } int main() { RegisterExecutablePlatform(ExePlatformQueryServ); LogSys.LoadLogSettingsDefaults(); - set_exception_handler(); - Timer LFGuildExpireTimer(60000); + set_exception_handler(); + Timer LFGuildExpireTimer(60000); LogInfo("Starting EQEmu QueryServ"); if (!queryservconfig::LoadConfig()) { @@ -58,11 +58,11 @@ int main() { return 1; } - Config = queryservconfig::get(); - WorldShortName = Config->ShortName; + Config = queryservconfig::get(); + WorldShortName = Config->ShortName; LogInfo("Connecting to MySQL"); - + /* MySQL Connection */ if (!database.Connect( Config->QSDatabaseHost.c_str(), @@ -74,9 +74,9 @@ int main() { return 1; } - /* Register Log System and Settings */ - database.LoadLogSettings(LogSys.log_settings); - LogSys.StartFileLogs(); + LogSys.SetDatabase(&database) + ->LoadLogDatabaseSettings() + ->StartFileLogs(); if (signal(SIGINT, CatchSignal) == SIG_ERR) { LogInfo("Could not set signal handler"); @@ -89,13 +89,13 @@ int main() { /* Initial Connection to Worldserver */ worldserver = new WorldServer; - worldserver->Connect(); + worldserver->Connect(); /* Load Looking For Guild Manager */ lfguildmanager.LoadDatabase(); - while(RunLoops) { - Timer::SetCurrentTime(); + while(RunLoops) { + Timer::SetCurrentTime(); if(LFGuildExpireTimer.Check()) lfguildmanager.ExpireEntries(); diff --git a/shared_memory/main.cpp b/shared_memory/main.cpp index 7cf54f946..d6e57d6f8 100644 --- a/shared_memory/main.cpp +++ b/shared_memory/main.cpp @@ -120,9 +120,9 @@ int main(int argc, char **argv) content_db.SetMysql(database.getMySQL()); } - /* Register Log System and Settings */ - database.LoadLogSettings(LogSys.log_settings); - LogSys.StartFileLogs(); + LogSys.SetDatabase(&database) + ->LoadLogDatabaseSettings() + ->StartFileLogs(); std::string shared_mem_directory = Config->SharedMemDir; if (MakeDirectory(shared_mem_directory)) { @@ -167,6 +167,9 @@ int main(int argc, char **argv) content_service.SetCurrentExpansion(RuleI(Expansion, CurrentExpansion)); + content_service.SetDatabase(&database) + ->SetExpansionContext() + ->ReloadContentFlags(); LogInfo( "Current expansion is [{}] ({})", diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e7b97b57f..41c7aa547 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -20,7 +20,7 @@ SET(tests_headers ADD_EXECUTABLE(tests ${tests_sources} ${tests_headers}) -TARGET_LINK_LIBRARIES(tests common cppunit) +TARGET_LINK_LIBRARIES(tests common cppunit fmt) INSTALL(TARGETS tests RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) diff --git a/tests/string_util_test.h b/tests/string_util_test.h index 0333d0391..1fdd325f9 100644 --- a/tests/string_util_test.h +++ b/tests/string_util_test.h @@ -30,6 +30,7 @@ public: TEST_ADD(StringUtilTest::EscapeStringTest); TEST_ADD(StringUtilTest::EscapeStringMemoryTest); TEST_ADD(StringUtilTest::SearchDeliminatedStringTest); + TEST_ADD(StringUtilTest::SplitStringTest); } ~StringUtilTest() { @@ -108,6 +109,15 @@ public: TEST_ASSERT(search_deliminated_string(h, "bef") == std::string::npos); TEST_ASSERT(search_deliminated_string(h, "wwi") == std::string::npos); } + + void SplitStringTest() { + std::string s = "123,456,789,"; + auto v = SplitString(s, ','); + TEST_ASSERT(v.size() == 3); + TEST_ASSERT(v[0] == "123"); + TEST_ASSERT(v[1] == "456"); + TEST_ASSERT(v[2] == "789"); + } }; #endif diff --git a/ucs/database.cpp b/ucs/database.cpp index 108b17871..d3e01b54d 100644 --- a/ucs/database.cpp +++ b/ucs/database.cpp @@ -673,89 +673,3 @@ void Database::GetFriendsAndIgnore(int charID, std::vector &friends } } - -void Database::LoadLogSettings(EQEmuLogSys::LogSettings *log_settings) -{ - std::string query = - "SELECT " - "log_category_id, " - "log_category_description, " - "log_to_console, " - "log_to_file, " - "log_to_gmsay " - "FROM " - "logsys_categories " - "ORDER BY log_category_id"; - - auto results = QueryDatabase(query); - int log_category_id = 0; - - int *categories_in_database = new int[1000]; - - for (auto row = results.begin(); row != results.end(); ++row) { - log_category_id = atoi(row[0]); - if (log_category_id <= Logs::None || log_category_id >= Logs::MaxCategoryID) { - continue; - } - - log_settings[log_category_id].log_to_console = static_cast(atoi(row[2])); - log_settings[log_category_id].log_to_file = static_cast(atoi(row[3])); - log_settings[log_category_id].log_to_gmsay = static_cast(atoi(row[4])); - - /** - * Determine if any output method is enabled for the category - * and set it to 1 so it can used to check if category is enabled - */ - const bool log_to_console = log_settings[log_category_id].log_to_console > 0; - const bool log_to_file = log_settings[log_category_id].log_to_file > 0; - const bool log_to_gmsay = log_settings[log_category_id].log_to_gmsay > 0; - const bool is_category_enabled = log_to_console || log_to_file || log_to_gmsay; - - if (is_category_enabled) { - log_settings[log_category_id].is_category_enabled = 1; - } - - /** - * This determines whether or not the process needs to actually file log anything. - * If we go through this whole loop and nothing is set to any debug level, there is no point to create a file or keep anything open - */ - if (log_settings[log_category_id].log_to_file > 0) { - LogSys.file_logs_enabled = true; - } - - categories_in_database[log_category_id] = 1; - } - - /** - * Auto inject categories that don't exist in the database... - */ - for (int log_index = Logs::AA; log_index != Logs::MaxCategoryID; log_index++) { - if (categories_in_database[log_index] != 1) { - - LogInfo( - "New Log Category [{0}] doesn't exist... Automatically adding to [logsys_categories] table...", - Logs::LogCategoryName[log_index] - ); - - auto inject_query = fmt::format( - "INSERT INTO logsys_categories " - "(log_category_id, " - "log_category_description, " - "log_to_console, " - "log_to_file, " - "log_to_gmsay) " - "VALUES " - "({0}, '{1}', {2}, {3}, {4})", - log_index, - EscapeString(Logs::LogCategoryName[log_index]), - std::to_string(log_settings[log_index].log_to_console), - std::to_string(log_settings[log_index].log_to_file), - std::to_string(log_settings[log_index].log_to_gmsay) - ); - - QueryDatabase(inject_query); - } - } - - delete[] categories_in_database; -} diff --git a/ucs/database.h b/ucs/database.h index ade93ef42..fc9b96d19 100644 --- a/ucs/database.h +++ b/ucs/database.h @@ -57,8 +57,7 @@ public: void ExpireMail(); void AddFriendOrIgnore(int CharID, int Type, std::string Name); void RemoveFriendOrIgnore(int CharID, int Type, std::string Name); - void GetFriendsAndIgnore(int CharID, std::vector &Friends, std::vector &Ignorees); - void LoadLogSettings(EQEmuLogSys::LogSettings* log_settings); + void GetFriendsAndIgnore(int CharID, std::vector &Friends, std::vector &Ignorees); protected: diff --git a/ucs/ucs.cpp b/ucs/ucs.cpp index 11f6ed103..5914b4fcc 100644 --- a/ucs/ucs.cpp +++ b/ucs/ucs.cpp @@ -97,9 +97,9 @@ int main() { return 1; } - /* Register Log System and Settings */ - database.LoadLogSettings(LogSys.log_settings); - LogSys.StartFileLogs(); + LogSys.SetDatabase(&database) + ->LoadLogDatabaseSettings() + ->StartFileLogs(); char tmp[64]; diff --git a/utils/defaults/eqemu_config.json b/utils/defaults/eqemu_config.json deleted file mode 100755 index 73930ab9f..000000000 --- a/utils/defaults/eqemu_config.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "server": { - "world": { - "shortname": "setme", - "longname": "I Forgot To Edit My Config" - } - } -} diff --git a/utils/defaults/eqemu_config.json.full b/utils/defaults/eqemu_config.json.full deleted file mode 100755 index 49cb9a5c6..000000000 --- a/utils/defaults/eqemu_config.json.full +++ /dev/null @@ -1,54 +0,0 @@ -{ - "server": { - "zones": { - "defaultstatus": "20", - "ports": { - "low": "7000", - "high": "7100" - } - }, - "database": { - "password": "eq", - "db": "eq", - "host": "127.0.0.1", - "port": "3306", - "username": "eq" - }, - "world": { - "shortname": "setme", - "longname": "I Forgot To Edit My Config", - "loginserver": { - "password": "", - "host": "login.eqemulator.net", - "port": "5998", - "account": "" - }, - "tcp": { - "port": "9000", - "telnet": "disable", - "ip": "127.0.0.1" - }, - "key": "some long random string", - "http": { - "mimefile": "mime.types", - "port": "9080", - "enabled": "false" - } - }, - "mailserver": { - "host": "channels.eqemulator.net", - "port": "7778" - }, - "chatserver": { - "host": "channels.eqemulator.net", - "port": "7778" - }, - "qsdatabase": { - "host": "127.0.0.1", - "port": "3306", - "username": "eq", - "password": "eq", - "db": "eq" - } - } -} diff --git a/utils/defaults/eqemu_config.xml b/utils/defaults/eqemu_config.xml deleted file mode 100644 index 7357cdb60..000000000 --- a/utils/defaults/eqemu_config.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - setme - I Forgot To Edit My Config - - diff --git a/utils/defaults/eqemu_config.xml.full b/utils/defaults/eqemu_config.xml.full deleted file mode 100644 index 3195352c5..000000000 --- a/utils/defaults/eqemu_config.xml.full +++ /dev/null @@ -1,95 +0,0 @@ - - - - setme - I Forgot To Edit My Config - - - - - - - - login.eqemulator.net - 5998 - - - - - - - - - - - - - some long random string - - - - - - - - channels.eqemulator.net - 7778 - - - - - channels.eqemulator.net - 7778 - - - - 20 - - - - - - - - 127.0.0.1 - 3306 - eq - eq - eq - - - - 127.0.0.1 - 3306 - eq - eq - eq - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/utils/deprecated/player_profile_set/player_profile_set/eq_player_structs.h b/utils/deprecated/player_profile_set/player_profile_set/eq_player_structs.h index e732757a4..3cfd276d6 100644 --- a/utils/deprecated/player_profile_set/player_profile_set/eq_player_structs.h +++ b/utils/deprecated/player_profile_set/player_profile_set/eq_player_structs.h @@ -720,7 +720,7 @@ struct RaidLeadershipAA_Struct { * Size: 20 Octets */ struct BindStruct { - /*000*/ uint32 zoneId; + /*000*/ uint32 zone_id; /*004*/ float x; /*008*/ float y; /*012*/ float z; @@ -1486,7 +1486,7 @@ struct GMZoneRequest_Struct { /*0068*/ float x; /*0072*/ float y; /*0076*/ float z; -/*0080*/ char unknown0080[4]; +/*0080*/ float heading; /*0084*/ int32 success; // 0 if command failed, 1 if succeeded? /*0088*/ // /*072*/ sint8 success; // =0 client->server, =1 server->client, -X=specific error diff --git a/utils/gm_commands/main.go b/utils/gm_commands/main.go new file mode 100644 index 000000000..4ef678d97 --- /dev/null +++ b/utils/gm_commands/main.go @@ -0,0 +1,183 @@ +package main + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "sort" + "strings" +) + +func main() { + // zone/command.cpp + commands, err := os.ReadFile("./zone/command.cpp") + if err != nil { + log.Fatal(err) + } + commandsString := string(commands) + + s := strings.Split(commandsString, "void command_") + commandFiles := []string{} + if len(s) > 1 { + startListing := false + for i, chunk := range s { + if strings.Contains(chunk, "logcommand(Client *c") { + startListing = true + } + + // get function name + functionName := "" + nameSplit := strings.Split(chunk, "(Client") + if len(nameSplit) > 0 { + functionName = strings.TrimSpace(nameSplit[0]) + } + + if startListing && + len(s[i-1]) > 0 && + !strings.Contains(s[i-1], "#ifdef") && + !strings.Contains(chunk, "#ifdef") && + !strings.Contains(chunk, "#ifdef BOTS") && + !strings.Contains(chunk, "#ifdef EQPROFILE") && + !strings.Contains(functionName, "bot") && + !strings.Contains(functionName, "help") && + !strings.Contains(functionName, "findaliases") { + + fmt.Println(functionName) + + // build command file name + commandFile := fmt.Sprintf("zone/gm_commands/%v.cpp", functionName) + + // append command file nam eto list + commandFiles = append(commandFiles, commandFile) + + includes := "" + if strings.Contains(chunk, "Client") { + includes = fmt.Sprintf("%v%v\n", includes, "#include \"../client.h\"") + } + if strings.Contains(chunk, "parse->") { + includes = fmt.Sprintf("%v%v\n", includes, "#include \"../quest_parser_collection.h\"") + } + if strings.Contains(chunk, "worldserver.") { + includes = fmt.Sprintf("%v%v\n", includes, "#include \"../worldserver.h\"") + includes = fmt.Sprintf("%v%v\n", includes, "extern WorldServer worldserver;") + } + if strings.Contains(chunk, "RegionType") { + includes = fmt.Sprintf("%v%v\n", includes, "#include \"../water_map.h\"") + } + if strings.Contains(chunk, "Corpse") { + includes = fmt.Sprintf("%v%v\n", includes, "#include \"../corpse.h\"") + } + if strings.Contains(chunk, "Object") { + includes = fmt.Sprintf("%v%v\n", includes, "#include \"../object.h\"") + } + if strings.Contains(chunk, "DoorManipulation") { + includes = fmt.Sprintf("%v%v\n", includes, "#include \"door_manipulation.h\"") + } + if strings.Contains(chunk, "Group") { + includes = fmt.Sprintf("%v%v\n", includes, "#include \"../groups.h\"") + } + if strings.Contains(chunk, "httplib") { + includes = fmt.Sprintf("%v%v\n", includes, "#include \"../../common/http/httplib.h\"") + } + if strings.Contains(chunk, "guild_mgr") { + includes = fmt.Sprintf("%v%v\n", includes, "#include \"../guild_mgr.h\"") + } + if strings.Contains(chunk, "expedition") { + includes = fmt.Sprintf("%v%v\n", includes, "#include \"../expedition.h\"") + } + if strings.Contains(chunk, "DataBucket::") { + includes = fmt.Sprintf("%v%v\n", includes, "#include \"../data_bucket.h\"") + } + if strings.Contains(chunk, "file_exists") { + includes = fmt.Sprintf("%v%v\n", includes, "#include \"../../common/file_util.h\"") + } + if strings.Contains(chunk, "std::thread") { + includes = fmt.Sprintf("%v%v\n", includes, "#include ") + } + if strings.Contains(chunk, "Door") { + includes = fmt.Sprintf("%v%v\n", includes, "#include \"../doors.h\"") + } + if strings.Contains(chunk, "NOW_INVISIBLE") { + includes = fmt.Sprintf("%v%v\n", includes, "#include \"../string_ids.h\"") + } + if strings.Contains(chunk, "Expansion::") { + includes = fmt.Sprintf("%v%v\n", includes, "#include \"../../common/content/world_content_service.h\"") + } + if strings.Contains(chunk, "MobMovementManager::") { + includes = fmt.Sprintf("%v%v\n", includes, "#include \"../mob_movement_manager.h\"") + } + if strings.Contains(chunk, "MobStuckBehavior::") { + includes = fmt.Sprintf("%v%v\n", includes, "#include \"../mob_movement_manager.h\"") + } + if strings.Contains(chunk, "ReloadAllPatches") { + includes = fmt.Sprintf("%v%v\n", includes, "#include \"../../common/patches/patches.h\"") + } + if strings.Contains(chunk, "ProfanityManager") { + includes = fmt.Sprintf("%v%v\n", includes, "#include \"../../common/profanity_manager.h\"") + } + if strings.Contains(chunk, "npc_scale_manager") { + includes = fmt.Sprintf("%v%v\n", includes, "#include \"../npc_scale_manager.h\"") + } + if strings.Contains(chunk, "g_Math") { + includes = fmt.Sprintf("%v%v\n", includes, "#include \"../fastmath.h\"") + includes = fmt.Sprintf("%v%v\n", includes, "extern FastMath g_Math;") + } + if strings.Contains(chunk, "raid") { + includes = fmt.Sprintf("%v%v\n", includes, "#include \"../raids.h\"") + } + if strings.Contains(chunk, "Raid") { + includes = fmt.Sprintf("%v%v\n", includes, "#include \"../raids.h\"") + } + if strings.Contains(chunk, "GetOS") { + includes = fmt.Sprintf("%v%v\n", includes, "#include \"../../common/serverinfo.h\"") + } + if strings.Contains(chunk, "LANG_") { + includes = fmt.Sprintf("%v%v\n", includes, "#include \"../../common/languages.h\"") + } + if strings.Contains(chunk, "ServerOP_Shared") { + includes = fmt.Sprintf("%v%v\n", includes, "#include \"../../common/shared_tasks.h\"") + } + if strings.Contains(chunk, "title_manager") { + includes = fmt.Sprintf("%v%v\n", includes, "#include \"../titles.h\"") + } + if strings.Contains(chunk, "CatchSignal") { + includes = fmt.Sprintf("%v%v\n", includes, "#include \"../../world/main.h\"") + } + + // build the contents of the command file + commandString := fmt.Sprintf("%v\nvoid command_%v", includes, chunk) + + //write file contents + err := ioutil.WriteFile(commandFile, []byte(commandString), 0777) + if err != nil { + fmt.Println(err) + } + + commandOnly := fmt.Sprintf("void command_%v", chunk) + commandsString = strings.ReplaceAll(commandsString, commandOnly, "") + + } + } + + // rewrite commands.cpp with functions removed + err := ioutil.WriteFile("zone/command.cpp", []byte(commandsString), 0777) + if err != nil { + fmt.Println(err) + } + + fmt.Println("# CmakeLists") + + // sort a-z + sort.Slice(commandFiles, func(i, j int) bool { + return commandFiles[i] < commandFiles[j] + }) + + for _, file := range commandFiles { + file = strings.ReplaceAll(file, "zone/", "") + fmt.Println(file) + } + } + + //fmt.Print(string(commands)) +} diff --git a/utils/mods/legacy_combat.lua b/utils/mods/legacy_combat.lua index c04644a8d..6a031ec86 100644 --- a/utils/mods/legacy_combat.lua +++ b/utils/mods/legacy_combat.lua @@ -962,15 +962,14 @@ function CommonOutgoingHitSuccess(e) ) ); - e.hit.damage_done = e.hit.damage_done + (e.hit.damage_done * e.other:GetSkillDmgTaken(e.hit.skill) / 100) + (e.self:GetSkillDmgAmt(e.hit.skill) + e.other:GetFcDamageAmtIncoming(e.self, 0, true, e.hit.skill)); + e.hit.damage_done = e.hit.damage_done + (e.hit.damage_done * e.other:GetSkillDmgTaken(e.hit.skill) / 100) + e.self:GetSkillDmgAmt(e.hit.skill); eq.log_combat( - string.format("[%s] [Mob::CommonOutgoingHitSuccess] Dmg [%i] SkillDmgTaken [%i] SkillDmgtAmt [%i] FcDmgAmtIncoming [%i] Post DmgCalcs", + string.format("[%s] [Mob::CommonOutgoingHitSuccess] Dmg [%i] SkillDmgTaken [%i] SkillDmgtAmt [%i] Post DmgCalcs", e.self:GetCleanName(), e.hit.damage_done, e.other:GetSkillDmgTaken(e.hit.skill), - e.self:GetSkillDmgAmt(e.hit.skill), - e.other:GetFcDamageAmtIncoming(e.self, 0, true, e.hit.skill) + e.self:GetSkillDmgAmt(e.hit.skill) ) ); @@ -997,4 +996,4 @@ function ApplyMeleeDamageBonus(e) e.hit.damage_done = e.hit.damage_done + (e.hit.damage_done * dmgbonusmod / 100); return e; -end \ No newline at end of file +end diff --git a/utils/patches/opcodes.conf b/utils/patches/opcodes.conf index 562ba3347..3f5e87b5a 100644 --- a/utils/patches/opcodes.conf +++ b/utils/patches/opcodes.conf @@ -307,8 +307,8 @@ OP_RecipeAutoCombine=0x0353 OP_TradeSkillCombine=0x0b40 OP_RequestDuel=0x0000 -OP_DuelResponse=0x0000 -OP_DuelResponse2=0x0000 #when accepted +OP_DuelDecline=0x0000 +OP_DuelAccept=0x0000 #when accepted OP_RezzComplete=0x0000 #packet wrong on this OP_RezzRequest=0x0000 #packet wrong on this diff --git a/utils/patches/patch_RoF.conf b/utils/patches/patch_RoF.conf index 76178bf62..8065ca53c 100644 --- a/utils/patches/patch_RoF.conf +++ b/utils/patches/patch_RoF.conf @@ -288,8 +288,8 @@ OP_YellForHelp=0x0017 OP_LoadSpellSet=0x38b4 OP_Bandolier=0x2b6f OP_PotionBelt=0x2d1b # Was 0x4d3b -OP_DuelResponse=0x0dee -OP_DuelResponse2=0x5e04 +OP_DuelDecline=0x0dee +OP_DuelAccept=0x5e04 OP_SaveOnZoneReq=0x36b1 OP_ReadBook=0x383c OP_Dye=0x62d8 @@ -560,12 +560,14 @@ OP_TaskActivityComplete=0x71cd OP_AcceptNewTask=0x394d OP_CancelTask=0x0c7f OP_TaskMemberList=0x748e # Was 0x1656 -OP_OpenNewTasksWindow=0x436c # Was 0x11de +OP_TaskSelectWindow=0x436c +OP_SharedTaskSelectWindow=0x436c OP_AvaliableTask=0x2bf8 # Was 0x2377 OP_TaskHistoryRequest=0x6cf6 OP_TaskHistoryReply=0x25eb OP_DeclineAllTasks=0x0000 OP_TaskRequestTimer=0x4b76 +OP_SharedTaskQuit=0x0000 # Title opcodes OP_NewTitlesAvailable=0x45d1 diff --git a/utils/patches/patch_RoF2.conf b/utils/patches/patch_RoF2.conf index 96f3258a8..3ecb0df68 100644 --- a/utils/patches/patch_RoF2.conf +++ b/utils/patches/patch_RoF2.conf @@ -287,8 +287,8 @@ OP_YellForHelp=0x4e56 OP_LoadSpellSet=0x261d OP_Bandolier=0x7677 OP_PotionBelt=0x1a3e -OP_DuelResponse=0x6a46 -OP_DuelResponse2=0x68d3 +OP_DuelDecline=0x6a46 +OP_DuelAccept=0x68d3 OP_SaveOnZoneReq=0x600d OP_ReadBook=0x72df OP_Dye=0x23b9 @@ -567,13 +567,26 @@ OP_CompletedTasks=0x4eba OP_TaskActivityComplete=0x5e19 OP_AcceptNewTask=0x0a23 OP_CancelTask=0x39f0 -OP_TaskMemberList=0x5727 -OP_OpenNewTasksWindow=0x48a2 OP_AvaliableTask=0x36e8 OP_TaskHistoryRequest=0x5f1c OP_TaskHistoryReply=0x3d05 OP_DeclineAllTasks=0x0000 OP_TaskRequestTimer=0x7a48 +OP_TaskSelectWindow=0x705b + +# Shared Tasks +OP_SharedTaskMemberList=0x1e7d # +OP_SharedTaskRemovePlayer=0x4865 # /taskremoveplayer +OP_SharedTaskAddPlayer=0x36e8 # /taskaddplayer +OP_SharedTaskMakeLeader=0x37f2 # /taskmakeleader +OP_SharedTaskInvite=0x3444 # Dialog window +OP_SharedTaskInviteResponse=0x7582 # Dialog window response +OP_SharedTaskAcceptNew=0x6646 # +OP_SharedTaskMemberChange=0x0119 # +OP_TaskTimers=0x2b0f # /tasktimers +OP_SharedTaskQuit=0x322e # /taskquit +OP_SharedTaskSelectWindow=0x48a2 +OP_SharedTaskPlayerList=0x5727 # /taskplayerlist # Title opcodes OP_NewTitlesAvailable=0x0d32 diff --git a/utils/patches/patch_SoD.conf b/utils/patches/patch_SoD.conf index 1ef8a138a..69f4c996b 100644 --- a/utils/patches/patch_SoD.conf +++ b/utils/patches/patch_SoD.conf @@ -281,7 +281,7 @@ OP_YellForHelp=0x6f79 # C OP_LoadSpellSet=0x7113 # C OP_Bandolier=0x441c # C OP_PotionBelt=0x5db5 # C -OP_DuelResponse=0x1ebb # C +OP_DuelDecline=0x1ebb # C OP_SaveOnZoneReq=0x6eff # C OP_ReadBook=0x2444 # C OP_Dye=0x3672 # C @@ -299,7 +299,7 @@ OP_DoGroupLeadershipAbility=0x540b # C OP_GroupLeadershipAAUpdate=0x0c33 OP_DelegateAbility=0x0322 # C OP_SetGroupTarget=0x521c # C -OP_DuelResponse2=0x52b5 # C +OP_DuelAccept=0x52b5 # C OP_Charm=0x7108 # C OP_Stun=0x2a6d # C OP_SendFindableNPCs=0x5360 @@ -527,8 +527,6 @@ OP_LDoNOpen=0x6129 # C # Task packets OP_TaskActivityComplete=0x4df0 # C -OP_TaskMemberList=0x34ed # C -OP_OpenNewTasksWindow=0x4dd5 # C OP_AvaliableTask=0x2136 # C OP_AcceptNewTask=0x5832 # C OP_TaskHistoryRequest=0x29d7 # C @@ -536,7 +534,21 @@ OP_TaskHistoryReply=0x3d2a # C OP_CancelTask=0x726b # C OP_DeclineAllTasks=0x0000 # OP_TaskRequestTimer=0x2e70 +OP_TaskSelectWindow=0x009b +# Shared Tasks +OP_SharedTaskMemberList=0x55f4 # +OP_SharedTaskRemovePlayer=0x4a12 # /taskremoveplayer +OP_SharedTaskAddPlayer=0x2136 # /taskaddplayer +OP_SharedTaskMakeLeader=0x1da9 # /taskmakeleader +OP_SharedTaskInvite=0x40c5 # Dialog window +OP_SharedTaskInviteResponse=0x7abb # Dialog window response +OP_SharedTaskAcceptNew=0x4751 # Not sure why this has a separate handler +OP_SharedTaskMemberChange=0x98f6 # Not sure yet? +OP_TaskTimers=0x01cb # /tasktimers +OP_SharedTaskQuit=0x5854 # /taskquit +OP_SharedTaskSelectWindow=0x4dd5 +OP_SharedTaskPlayerList=0x34ed # /taskplayerlist OP_Shroud=0x6d1f OP_ShroudRemove=0x17f6 diff --git a/utils/patches/patch_SoF.conf b/utils/patches/patch_SoF.conf index 1c5fd3eda..56602d8f6 100644 --- a/utils/patches/patch_SoF.conf +++ b/utils/patches/patch_SoF.conf @@ -275,7 +275,7 @@ OP_YellForHelp=0x4F4A #Trevius 03/19/09 OP_LoadSpellSet=0x05B5 #Trevius 03/19/09 OP_Bandolier=0x3FD4 #Trevius 03/19/09 OP_PotionBelt=0x16F3 #Trevius 03/19/09 -OP_DuelResponse=0x5E59 #Derision 2009 +OP_DuelDecline=0x5E59 #Derision 2009 OP_SaveOnZoneReq=0x1103 #Trevius 03/20/09 OP_ReadBook=0x424a #Xinu 03/19/09 OP_Dye=0x3611 #Xinu 03/19/09 @@ -292,7 +292,7 @@ OP_ClearRaidNPCMarks=0x56a9 # OP_DoGroupLeadershipAbility=0x5a64 #Derision 2009 OP_DelegateAbility=0x57e3 #Derision 2009 OP_SetGroupTarget=0x1651 #Derision 2009 -OP_DuelResponse2=0x2A85 #Derision 2009 +OP_DuelAccept=0x2A85 #Derision 2009 OP_Charm=0x2F32 #Derision 2009 OP_Stun=0x55BF #Derision 2009 OP_FindPersonRequest=0x07F0 #Derision 2009 @@ -496,7 +496,8 @@ OP_LDoNOpen=0x4b92 #Xinu 03/19/09 #Task packets OP_TaskActivityComplete=0x7338 # -OP_OpenNewTasksWindow=0x17C3 # +OP_SharedTaskSelectWindow=0x17C3 +OP_TaskSelectWindow=0x17C3 OP_AvaliableTask=0x5d1d #Xinu 03/19/09 OP_AcceptNewTask=0x66A8 # OP_TaskHistoryRequest=0x3035 # @@ -511,7 +512,7 @@ OP_TaskMakeLeader=0x5050 OP_TaskAddPlayer=0x5d1d OP_TaskRemovePlayer=0x516f OP_TaskPlayerList=0x0ad6 -OP_TaskQuit=0x2c8c +OP_SharedTaskQuit=0x2c8c OP_TaskRequestTimer=0x0b08 #Title opcodes diff --git a/utils/patches/patch_Titanium.conf b/utils/patches/patch_Titanium.conf index 32d434e93..28074dcf9 100644 --- a/utils/patches/patch_Titanium.conf +++ b/utils/patches/patch_Titanium.conf @@ -356,8 +356,8 @@ OP_RecipeAutoCombine=0x0353 OP_TradeSkillCombine=0x0b40 OP_RequestDuel=0x28e1 -OP_DuelResponse=0x3bad -OP_DuelResponse2=0x1b09 #when accepted +OP_DuelDecline=0x3bad +OP_DuelAccept=0x1b09 #when accepted OP_RezzComplete=0x4b05 OP_RezzRequest=0x1035 @@ -459,8 +459,7 @@ OP_TaskActivityComplete=0x54eb OP_CompletedTasks=0x76a2 # ShowEQ 10/27/05 OP_TaskDescription=0x5ef7 # ShowEQ 10/27/05 OP_TaskActivity=0x682d # ShowEQ 10/27/05 -OP_TaskMemberList=0x722f #not sure -OP_OpenNewTasksWindow=0x5e7c #combined with OP_AvaliableTask I think +OP_TaskSelectWindow=0x5e7c OP_AvaliableTask=0x0000 OP_AcceptNewTask=0x207f OP_TaskHistoryRequest=0x5df4 @@ -473,11 +472,22 @@ OP_TaskMemberChange=0x5886 OP_TaskMakeLeader=0x1b25 OP_TaskAddPlayer=0x6bc4 OP_TaskRemovePlayer=0x37b9 -OP_TaskPlayerList=0x3961 -OP_TaskQuit=0x35dd OP_TaskRequestTimer=0x6a1d #task complete related: 0x0000 (24 bytes), 0x0000 (8 bytes), 0x0000 (4 bytes) +# Shared Tasks +OP_SharedTaskMemberList=0x722f # +OP_SharedTaskRemovePlayer=0x37b9 # /taskremoveplayer +OP_SharedTaskAddPlayer=0x6934 # /taskaddplayer +OP_SharedTaskMakeLeader=0x1b25 # /taskmakeleader +OP_SharedTaskInvite=0x79b4 # Dialog window +OP_SharedTaskInviteResponse=0x0358 # Dialog window response +OP_SharedTaskAcceptNew=0x194d # Not sure why this has a separate handler +OP_SharedTaskMemberChange=0x5886 # Not sure yet? +OP_TaskTimers=0x6a1d # /tasktimers +OP_SharedTaskQuit=0x35dd # /taskquit +OP_SharedTaskSelectWindow=0x013f +OP_SharedTaskPlayerList=0x3961 # /taskplayerlist OP_RequestClientZoneChange=0x7834 # ShowEQ 10/27/05 diff --git a/utils/patches/patch_UF.conf b/utils/patches/patch_UF.conf index 8efce91f6..6a98e3b02 100644 --- a/utils/patches/patch_UF.conf +++ b/utils/patches/patch_UF.conf @@ -290,8 +290,8 @@ OP_YellForHelp=0x55a8 # C OP_LoadSpellSet=0x6617 # C OP_Bandolier=0x510c # C OP_PotionBelt=0x0651 # C -OP_DuelResponse=0x41a6 # C -OP_DuelResponse2=0x6d60 # C +OP_DuelDecline=0x41a6 # C +OP_DuelAccept=0x6d60 # C OP_SaveOnZoneReq=0x2913 # C OP_ReadBook=0x465e # C OP_Dye=0x2137 # C @@ -551,8 +551,6 @@ OP_LDoNInspect=0x0aaa # Task packets OP_TaskActivityComplete=0x5832 # C -OP_TaskMemberList=0x66ba # C -OP_OpenNewTasksWindow=0x98f6 # C OP_AvaliableTask=0x6255 # C Mispelled? OP_AcceptNewTask=0x17d5 # C OP_TaskHistoryRequest=0x547c # C @@ -560,6 +558,21 @@ OP_TaskHistoryReply=0x4524 # C OP_CancelTask=0x3bf5 # C OP_DeclineAllTasks=0x0000 # OP_TaskRequestTimer=0x719e +OP_TaskSelectWindow=0x7309 + +# Shared Tasks +OP_SharedTaskMemberList=0x584e # +OP_SharedTaskRemovePlayer=0x18e2 # /taskremoveplayer +OP_SharedTaskAddPlayer=0x6255 # /taskaddplayer +OP_SharedTaskMakeLeader=0x5933 # /taskmakeleader +OP_SharedTaskInvite=0x55f4 # Dialog window +OP_SharedTaskInviteResponse=0x26e5 # Dialog window response +OP_SharedTaskAcceptNew=0x6ded # Not sure why this has a separate handler +OP_SharedTaskMemberChange=0x1402 # Not sure yet? +OP_TaskTimers=0x09b4 # /tasktimers +OP_SharedTaskQuit=0x6aba # /taskquit +OP_SharedTaskSelectWindow=0x98f6 +OP_SharedTaskPlayerList=0x66ba # /taskplayerlist # Title opcodes OP_NewTitlesAvailable=0x4b49 # C diff --git a/utils/scripts/eqemu_server.pl b/utils/scripts/eqemu_server.pl index 4c510ff9f..22e2f1de8 100755 --- a/utils/scripts/eqemu_server.pl +++ b/utils/scripts/eqemu_server.pl @@ -62,7 +62,8 @@ if (-e "skip_internet_connection_check.txt") { # skip self update ############################################# my $skip_self_update_check = 0; -if (-e "eqemu_server_skip_update.txt") { +if (-e "eqemu_server_skip_update.txt" || defined($ENV{'EQEMU_SERVER_SKIP_UPDATE'})) { + print "[Info] Skipping self check\n"; $skip_self_update_check = 1; } @@ -70,7 +71,8 @@ if (-e "eqemu_server_skip_update.txt") { # skip maps update ############################################# my $skip_self_maps_update_check = 0; -if (-e "eqemu_server_skip_maps_update.txt") { +if (-e "eqemu_server_skip_maps_update.txt" || defined($ENV{'EQEMU_SERVER_SKIP_MAPS_UPDATE'})) { + print "[Info] Skipping maps update\n"; $skip_self_maps_update_check = 1; } @@ -106,7 +108,7 @@ if (-e "eqemu_update.pl") { print "[Info] For EQEmu Server management utilities - run eqemu_server.pl\n" if $ARGV[0] eq "ran_from_world"; my $skip_checks = 0; -if ($ARGV[0] && $ARGV[0] eq "new_server") { +if ($ARGV[0] && ($ARGV[0] eq "new_server" || $ARGV[0] eq "new_server_with_bots")) { $skip_checks = 1; } @@ -238,10 +240,11 @@ sub show_install_summary_info } if ($OS eq "Linux") { print "[Install] Linux Utility Scripts:\n"; - print " - server_start.sh Starts EQEmu server (Quiet) with 30 dynamic zones, UCS & Queryserv, dynamic zones\n"; - print " - server_start_dev.sh Starts EQEmu server with 10 dynamic zones, UCS & Queryserv, dynamic zones all verbose\n"; - print " - server_stop.sh Stops EQEmu Server (No warning)\n"; - print " - server_status.sh Prints the status of the EQEmu Server processes\n"; + print " - server_start.sh Starts EQEmu server (Quiet) with 30 dynamic zones, UCS & Queryserv, dynamic zones\n"; + print " - server_start_with_login.sh Starts EQEmu server (Quiet) with 30 dynamic zones, UCS & Queryserv, dynamic zones\n"; + print " - server_start_dev.sh Starts EQEmu server with 10 dynamic zones, UCS & Queryserv, dynamic zones all verbose\n"; + print " - server_stop.sh Stops EQEmu Server (No warning)\n"; + print " - server_status.sh Prints the status of the EQEmu Server processes\n"; } print "[Configure] eqemu_config.json Edit to change server settings and name\n"; @@ -251,6 +254,7 @@ sub show_install_summary_info sub new_server { + $build_options = $_[0]; $file_count = 0; opendir(DIR, ".") or die $!; while (my $file = readdir(DIR)) { @@ -265,9 +269,7 @@ sub new_server exit; } - if (-e "install_variables.txt" || -e "../install_variables.txt") { - get_installation_variables(); - } + get_installation_variables(); while (1) { @@ -307,7 +309,7 @@ sub new_server if ($mysql_pass == 1) { - if ((!-e "install_variables.txt" && !-e "../install_variables.txt")) { + if ($database_name eq "" && !-e "install_variables.txt" && !-e "../install_variables.txt") { print "[New Server] Success! We have a database connection\n"; check_for_input("Specify a NEW database name that PEQ will be installed to: "); @@ -323,11 +325,12 @@ sub new_server } analytics_insertion("new_server::install", $database_name); - if ($OS eq "Linux") { - build_linux_source("login"); - } + # This shouldn't be necessary, as we call do_linux_login_server_setup as the last step in do_installer_routines() + # if ($OS eq "Linux") { + # build_linux_source("login"); + # } - do_installer_routines(); + do_installer_routines($build_options); if ($OS eq "Linux") { print `chmod 755 *.sh`; @@ -415,7 +418,6 @@ sub check_xml_to_json_conversion sub build_linux_source { - $build_options = $_[0]; $cmake_options = ""; @@ -434,7 +436,9 @@ sub build_linux_source } } my $eqemu_server_directory = "/home/eqemu"; - my $source_dir = $eqemu_server_directory . '/' . $last_directory . '_source' . $source_folder_post_fix; + # source between bots and not is the same, just different build results, so use the same source folder, different build folders + my $source_dir = $eqemu_server_directory . '/' . $last_directory . '_source'; + my $build_dir = $eqemu_server_directory . '/' . $last_directory . '_build' . $source_folder_post_fix; $current_directory = trim($current_directory); @@ -446,22 +450,22 @@ sub build_linux_source chdir($source_dir); - print `git clone https://github.com/EQEmu/Server.git`; + if (!-d "$source_dir/.git") { + print `git clone --recurse-submodules https://github.com/EQEmu/Server.git $source_dir`; + } + else { + print `git pull --recurse-submodules`; + } - mkdir($source_dir . "/Server/build") if (!-e $source_dir . "/Server/build"); - chdir($source_dir . "/Server"); - - print `git submodule init`; - print `git submodule update`; - - chdir($source_dir . "/Server/build"); + mkdir($build_dir) if (!-e $build_dir); + chdir($build_dir); print "Generating CMake build files...\n"; if ($os_flavor eq "fedora_core") { - print `cmake $cmake_options -DEQEMU_BUILD_LOGIN=ON -DEQEMU_BUILD_LUA=ON -DLUA_INCLUDE_DIR=/usr/include/lua-5.1/ -G "Unix Makefiles" ..`; + print `cmake $cmake_options -DEQEMU_BUILD_LOGIN=ON -DEQEMU_BUILD_LUA=ON -DLUA_INCLUDE_DIR=/usr/include/lua-5.1/ -G "Unix Makefiles" $source_dir`; } else { - print `cmake $cmake_options -DEQEMU_BUILD_LOGIN=ON -DEQEMU_BUILD_LUA=ON -G "Unix Makefiles" ..`; + print `cmake $cmake_options -DEQEMU_BUILD_LOGIN=ON -DEQEMU_BUILD_LUA=ON -G "Unix Makefiles" $source_dir`; } print "Building EQEmu Server code. This will take a while."; @@ -470,21 +474,22 @@ sub build_linux_source chdir($current_directory); - print `ln -s -f $source_dir/Server/build/bin/eqlaunch .`; - print `ln -s -f $source_dir/Server/build/bin/export_client_files .`; - print `ln -s -f $source_dir/Server/build/bin/import_client_files .`; - print `ln -s -f $source_dir/Server/build/bin/libcommon.a .`; - print `ln -s -f $source_dir/Server/build/bin/libluabind.a .`; - print `ln -s -f $source_dir/Server/build/bin/queryserv .`; - print `ln -s -f $source_dir/Server/build/bin/shared_memory .`; - print `ln -s -f $source_dir/Server/build/bin/ucs .`; - print `ln -s -f $source_dir/Server/build/bin/world .`; - print `ln -s -f $source_dir/Server/build/bin/zone .`; - print `ln -s -f $source_dir/Server/build/bin/loginserver .`; + print `ln -s -f $build_dir/bin/eqlaunch .`; + print `ln -s -f $build_dir/bin/export_client_files .`; + print `ln -s -f $build_dir/bin/import_client_files .`; + print `ln -s -f $build_dir/bin/libcommon.a .`; + print `ln -s -f $build_dir/bin/libluabind.a .`; + print `ln -s -f $build_dir/bin/queryserv .`; + print `ln -s -f $build_dir/bin/shared_memory .`; + print `ln -s -f $build_dir/bin/ucs .`; + print `ln -s -f $build_dir/bin/world .`; + print `ln -s -f $build_dir/bin/zone .`; + print `ln -s -f $build_dir/bin/loginserver .`; } sub do_installer_routines { + $build_options = $_[0]; print "[Install] EQEmu Server Installer... LOADING... PLEASE WAIT...\n"; #::: Make some local server directories... @@ -517,9 +522,25 @@ sub do_installer_routines fetch_utility_scripts(); #::: Database Routines + $root_user = $user; + $root_password = $pass; print "[Database] Creating Database '" . $db_name . "'\n"; - print `"$path" --host $host --user $user --password="$pass" -N -B -e "DROP DATABASE IF EXISTS $db_name;"`; - print `"$path" --host $host --user $user --password="$pass" -N -B -e "CREATE DATABASE $db_name"`; + if (defined($ENV{'MYSQL_ROOT_PASSWORD'})) + { + # In the case that the user doesn't have privileges to create databases, support passing in the root password during setup + print "[Database] Using 'root' for database management.\n"; + $root_user = "root"; + $root_password = $ENV{'MYSQL_ROOT_PASSWORD'}; + } + print `"$path" --host $host --user $root_user --password="$root_password" -N -B -e "DROP DATABASE IF EXISTS $db_name;"`; + print `"$path" --host $host --user $root_user --password="$root_password" -N -B -e "CREATE DATABASE $db_name"`; + if (defined($ENV{'MYSQL_ROOT_PASSWORD'})) + { + # If we used root, make sure $user has permissions on db + print "[Database] Assigning ALL PRIVILEGES to $user on $db_name.\n"; + print `"$path" --host $host --user $root_user --password="$root_password" -N -B -e "GRANT ALL PRIVILEGES ON $db_name.* TO '$user.%'"`; + print `"$path" --host $host --user $root_user --password="$root_password" -N -B -e "FLUSH PRIVILEGES"`; + } my $world_path = "world"; if (-e "bin/world") { @@ -548,6 +569,11 @@ sub do_installer_routines print "[Database] Fetching and Applying Latest Database Updates...\n"; main_db_management(); + # if bots + if ($build_options =~ /bots/i) { + bots_db_management(); + } + remove_duplicate_rule_values(); if ($OS eq "Windows") { @@ -555,7 +581,7 @@ sub do_installer_routines do_windows_login_server_setup(); } if ($OS eq "Linux") { - do_linux_login_server_setup(); + do_linux_login_server_setup($build_options); } } @@ -705,7 +731,8 @@ sub get_windows_wget { if (!-d "bin") { mkdir("bin"); } - `powershell -Command "(New-Object Net.WebClient).DownloadFile('https://raw.githubusercontent.com/Akkadius/eqemu-install-v2/master/windows/wget.exe', 'bin\\wget.exe') "` + eval "use LWP::Simple qw(getstore);"; + getstore("https://raw.githubusercontent.com/Akkadius/eqemu-install-v2/master/windows/wget.exe", "bin\\wget.exe"); } } @@ -774,6 +801,12 @@ sub do_self_update_check_routine sub get_installation_variables { + # Read installation variables from the ENV if set, but override them with install_variables.txt + if ($ENV{"MYSQL_HOST"}) { $installation_variables{"mysql_host"} = $ENV{"MYSQL_HOST"}; } + if ($ENV{"MYSQL_DATABASE"}) { $installation_variables{"mysql_eqemu_db_name"} = $ENV{"MYSQL_DATABASE"}; } + if ($ENV{"MYSQL_USER"}) { $installation_variables{"mysql_eqemu_user"} = $ENV{"MYSQL_USER"} } + if ($ENV{"MYSQL_PASSWORD"}) { $installation_variables{"mysql_eqemu_password"} = $ENV{"MYSQL_PASSWORD"} } + #::: Fetch installation variables before building the config if ($OS eq "Linux") { if (-e "../install_variables.txt") { @@ -807,9 +840,9 @@ sub do_install_config_json my $content; open(my $fh, '<', "eqemu_config_template.json") or die "cannot open file $filename"; { - local $/; - $content = <$fh>; -} + local $/; + $content = <$fh>; + } close($fh); $config = $json->decode($content); @@ -825,9 +858,18 @@ sub do_install_config_json $db_name = "peq"; } + if ($installation_variables{"mysql_host"}) { + $host = $installation_variables{"mysql_host"}; + } + else { + $host = "127.0.0.1"; + } + + $config->{"server"}{"database"}{"host"} = $host; $config->{"server"}{"database"}{"username"} = $installation_variables{"mysql_eqemu_user"}; $config->{"server"}{"database"}{"password"} = $installation_variables{"mysql_eqemu_password"}; $config->{"server"}{"database"}{"db"} = $db_name; + $config->{"server"}{"qsdatabase"}{"host"} = $host; $config->{"server"}{"qsdatabase"}{"username"} = $installation_variables{"mysql_eqemu_user"}; $config->{"server"}{"qsdatabase"}{"password"} = $installation_variables{"mysql_eqemu_password"}; $config->{"server"}{"qsdatabase"}{"db"} = $db_name; @@ -853,9 +895,9 @@ sub do_install_config_login_json my $content; open(my $fh, '<', "login_template.json") or die "cannot open file $filename"; { - local $/; - $content = <$fh>; -} + local $/; + $content = <$fh>; + } close($fh); $config = $json->decode($content); @@ -867,7 +909,14 @@ sub do_install_config_login_json $db_name = "peq"; } - $config->{"database"}{"host"} = "127.0.0.1"; + if ($installation_variables{"mysql_host"}) { + $host = $installation_variables{"mysql_host"}; + } + else { + $host = "127.0.0.1"; + } + + $config->{"database"}{"host"} = $host; $config->{"database"}{"user"} = $installation_variables{"mysql_eqemu_user"}; $config->{"database"}{"password"} = $installation_variables{"mysql_eqemu_password"}; $config->{"database"}{"db"} = $db_name; @@ -1086,6 +1135,10 @@ sub show_menu_prompt new_server(); $dc = 1; } + elsif ($input eq "new_server_with_bots") { + new_server("bots"); + $dc = 1; + } elsif ($input eq "setup_bots") { setup_bots(); $dc = 1; @@ -1153,11 +1206,12 @@ sub print_main_menu print "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"; print ">>> EQEmu Server Main Menu >>>>>>>>>>>>\n"; print ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n"; - print " [database] Enter database management menu \n"; - print " [assets] Manage server assets \n"; - print " [new_server] New folder EQEmu/PEQ install - Assumes MySQL/Perl installed \n"; - print " [setup_bots] Enables bots on server - builds code and database requirements \n"; - print " [conversions] Routines used for conversion of scripts/data \n"; + print " [database] Enter database management menu \n"; + print " [assets] Manage server assets \n"; + print " [new_server] New folder EQEmu/PEQ install - Assumes MySQL/Perl installed \n"; + print " [new_server_with_bots] New folder EQEmu/PEQ install with bots enabled - Assumes MySQL/Perl installed \n"; + print " [setup_bots] Enables bots on server - builds code and database requirements \n"; + print " [conversions] Routines used for conversion of scripts/data \n"; print "\n"; print " exit \n"; print "\n"; @@ -1297,12 +1351,12 @@ sub script_exit sub check_db_version_table { if (get_mysql_result("SHOW TABLES LIKE 'db_version'") eq "" && $db) { + print "[Database] Table 'db_version' does not exist.... Creating...\n\n"; print get_mysql_result(" CREATE TABLE db_version ( version int(11) DEFAULT '0' ) ENGINE=InnoDB DEFAULT CHARSET=latin1; INSERT INTO db_version (version) VALUES ('1000');"); - print "[Database] Table 'db_version' does not exist.... Creating...\n\n"; } } @@ -1451,9 +1505,9 @@ sub read_eqemu_config_json my $content; open(my $fh, '<', "eqemu_config.json") or die "cannot open file $filename"; { - local $/; - $content = <$fh>; -} + local $/; + $content = <$fh>; + } close($fh); $config = $json->decode($content); @@ -1695,8 +1749,7 @@ sub do_windows_login_server_setup sub do_linux_login_server_setup { - - build_linux_source(); + build_linux_source($_[0]); for my $file (@files) { $destination_file = $file; @@ -1858,7 +1911,7 @@ sub fetch_peq_db_full sub map_files_fetch_bulk { print "[Install] Fetching Latest Maps... (This could take a few minutes...)\n"; - get_remote_file("http://github.com/Akkadius/EQEmuMaps/archive/master.zip", "maps/maps.zip", 1); + get_remote_file("http://analytics.akkadius.com/maps.zip", "maps/maps.zip", 1); unzip('maps/maps.zip', 'maps/'); my @files; my $start_dir = "maps/EQEmuMaps-master/"; @@ -2525,7 +2578,6 @@ sub run_database_check } } - sub fetch_missing_db_update { $db_update = $_[0]; @@ -2707,7 +2759,6 @@ sub quest_heading_convert print "Total matches: " . $total_matches . "\n"; } - sub quest_faction_convert { diff --git a/utils/scripts/generators/repository-generator.pl b/utils/scripts/generators/repository-generator.pl index 838642b98..bd7514a93 100644 --- a/utils/scripts/generators/repository-generator.pl +++ b/utils/scripts/generators/repository-generator.pl @@ -33,8 +33,9 @@ my $repository_generation_option = $ARGV[2] ? $ARGV[2] : "all"; ############################################# # world path ############################################# -my $world_path = $server_path . "/world"; -my $world_path_bin = $server_path . "/bin/world"; +my $world_binary = ($^O eq "MSWin32") ? "world.exe" : "world"; +my $world_path = $server_path . "/" . $world_binary; +my $world_path_bin = $server_path . "/bin/" . $world_binary; my $found_world_path = ""; if (-e $world_path) { @@ -81,7 +82,8 @@ my $database_name = $config->{"server"}{"database"}{"db"}; my $host = $config->{"server"}{"database"}{"host"}; my $user = $config->{"server"}{"database"}{"username"}; my $pass = $config->{"server"}{"database"}{"password"}; -my $dsn = "dbi:mysql:$database_name:$host:3306"; +my $port = $config->{"server"}{"database"}{"port"}; +my $dsn = "dbi:mysql:$database_name:$host:$port"; my $connect = DBI->connect($dsn, $user, $pass); my @tables = (); @@ -128,7 +130,7 @@ foreach my $table_to_generate (@tables) { # These tables don't have a typical schema my @table_ignore_list = ( "character_enabledtasks", - "grid", # Manually created + "grid", # Manually created "grid_entries", # Manually created # "tradeskill_recipe", # Manually created # "character_recipe_list", # Manually created @@ -155,7 +157,7 @@ foreach my $table_to_generate (@tables) { $table_found_in_schema = 0; } - if ($table_found_in_schema == 0) { + if ($table_found_in_schema == 0 && ($requested_table_to_generate eq "" || $requested_table_to_generate eq "all")) { print "Table [$table_to_generate] not found in schema, skipping\n"; next; } @@ -182,14 +184,15 @@ foreach my $table_to_generate (@tables) { $ex->execute($database_name, $table_to_generate); - my $longest_column_length = 0; - my $longest_data_type_length = 0; - while (my @row = $ex->fetchrow_array()) { - my $column_name = $row[0]; - my $data_type = $row[2]; + my $longest_column_length = 0; + my $longest_data_type_length = 0; + while (my @row = $ex->fetchrow_array()) { + my $column_name = $row[0]; + my $column_name_formatted = format_column_name_for_cpp_var($column_name); + my $data_type = $row[2]; - if ($longest_column_length < length($column_name)) { - $longest_column_length = length($column_name); + if ($longest_column_length < length($column_name_formatted)) { + $longest_column_length = length($column_name_formatted); } my $struct_data_type = translate_mysql_data_type_to_c($data_type); @@ -200,37 +203,42 @@ foreach my $table_to_generate (@tables) { } # 2nd pass - my $default_entries = ""; - my $insert_one_entries = ""; - my $insert_many_entries = ""; - my $find_one_entries = ""; - my $column_names_quoted = ""; - my $table_struct_columns = ""; - my $update_one_entries = ""; - my $all_entries = ""; - my $index = 0; - my %table_data = (); - my %table_primary_key = (); + my $default_entries = ""; + my $insert_one_entries = ""; + my $insert_many_entries = ""; + my $find_one_entries = ""; + my $column_names_quoted = ""; + my $select_column_names_quoted = ""; + my $table_struct_columns = ""; + my $update_one_entries = ""; + my $all_entries = ""; + my $index = 0; + my %table_data = (); + my %table_primary_key = (); $ex->execute($database_name, $table_to_generate); - while (my @row = $ex->fetchrow_array()) { - my $column_name = $row[0]; - my $table_name = $row[1]; - my $data_type = $row[2]; - my $column_type = $row[3]; - my $ordinal_position = $row[4]; - my $column_key = $row[5]; - my $column_default = ($row[6] ? $row[6] : ""); - my $extra = ($row[7] ? $row[7] : ""); + while (my @row = $ex->fetchrow_array()) { + my $column_name = $row[0]; + my $column_name_formatted = format_column_name_for_cpp_var($column_name); + my $table_name = $row[1]; + my $data_type = $row[2]; + my $column_type = $row[3]; + my $ordinal_position = $row[4]; + my $column_key = $row[5]; + my $column_default = ($row[6] ? $row[6] : ""); + my $extra = ($row[7] ? $row[7] : ""); if (!$table_primary_key{$table_name}) { - if (($column_key eq "PRI" && $data_type =~/int/) || ($ordinal_position == 0 && $column_name =~ /id/i)) { + if (($column_key eq "PRI" && $data_type =~ /int/) || ($ordinal_position == 0 && $column_name =~ /id/i)) { $table_primary_key{$table_name} = $column_name; } } my $default_value = 0; - if ($column_default ne "NULL" && $column_default ne "") { + if ($column_default eq "current_timestamp()") { + $default_value = "std::time(nullptr)" + } + elsif ($column_default ne "NULL" && $column_default ne "") { $column_default =~ s/'/"/g; $default_value = $column_default; } @@ -244,19 +252,28 @@ foreach my $table_to_generate (@tables) { my $struct_data_type = translate_mysql_data_type_to_c($data_type); # struct - $table_struct_columns .= sprintf("\t\t\%-${longest_data_type_length}s %s;\n", $struct_data_type, $column_name); + $table_struct_columns .= sprintf("\t\t\%-${longest_data_type_length}s %s;\n", $struct_data_type, $column_name_formatted); # new entity - $default_entries .= sprintf("\t\tentry.%-${longest_column_length}s = %s;\n", $column_name, $default_value); + $default_entries .= sprintf("\t\tentry.%-${longest_column_length}s = %s;\n", $column_name_formatted, $default_value); # column names (string) - $column_names_quoted .= sprintf("\t\t\t\"%s\",\n", $column_name); + $column_names_quoted .= sprintf("\t\t\t\"%s\",\n", format_column_name_for_mysql($column_name)); + if ($data_type =~ /datetime/) { + $select_column_names_quoted .= sprintf("\t\t\t\"UNIX_TIMESTAMP(%s)\",\n", format_column_name_for_mysql($column_name)); + } + else { + $select_column_names_quoted .= sprintf("\t\t\t\"%s\",\n", format_column_name_for_mysql($column_name)); + } # update one if ($extra ne "auto_increment") { - my $query_value = sprintf('\'" + EscapeString(%s_entry.%s) + "\'");', $table_name, $column_name); + my $query_value = sprintf('\'" + EscapeString(%s_entry.%s) + "\'");', $table_name, $column_name_formatted); if ($data_type =~ /int|float|double|decimal/) { - $query_value = sprintf('" + std::to_string(%s_entry.%s));', $table_name, $column_name); + $query_value = sprintf('" + std::to_string(%s_entry.%s));', $table_name, $column_name_formatted); + } + elsif ($data_type =~ /datetime/) { + $query_value = sprintf('FROM_UNIXTIME(" + (%s_entry.%s > 0 ? std::to_string(%s_entry.%s) : "null") + ")");', $table_name, $column_name_formatted, $table_name, $column_name_formatted); } $update_one_entries .= sprintf( @@ -267,26 +284,37 @@ foreach my $table_to_generate (@tables) { } # insert - my $value = sprintf("\"'\" + EscapeString(%s_entry.%s) + \"'\"", $table_name, $column_name); + my $value = sprintf("\"'\" + EscapeString(%s_entry.%s) + \"'\"", $table_name, $column_name_formatted); if ($data_type =~ /int|float|double|decimal/) { - $value = sprintf('std::to_string(%s_entry.%s)', $table_name, $column_name); + $value = sprintf('std::to_string(%s_entry.%s)', $table_name, $column_name_formatted); + } + elsif ($data_type =~ /datetime/) { + $value = sprintf('"FROM_UNIXTIME(" + (%s_entry.%s > 0 ? std::to_string(%s_entry.%s) : "null") + ")"', $table_name, $column_name_formatted, $table_name, $column_name_formatted); } $insert_one_entries .= sprintf("\t\tinsert_values.push_back(%s);\n", $value); $insert_many_entries .= sprintf("\t\t\tinsert_values.push_back(%s);\n", $value); # find one / all (select) - if ($data_type =~ /int/) { - $all_entries .= sprintf("\t\t\tentry.%-${longest_column_length}s = atoi(row[%s]);\n", $column_name, $index); - $find_one_entries .= sprintf("\t\t\tentry.%-${longest_column_length}s = atoi(row[%s]);\n", $column_name, $index); + if ($data_type =~ /bigint/) { + $all_entries .= sprintf("\t\t\tentry.%-${longest_column_length}s = strtoll(row[%s], nullptr, 10);\n", $column_name_formatted, $index); + $find_one_entries .= sprintf("\t\t\tentry.%-${longest_column_length}s = strtoll(row[%s], nullptr, 10);\n", $column_name_formatted, $index); + } + elsif ($data_type =~ /datetime/) { + $all_entries .= sprintf("\t\t\tentry.%-${longest_column_length}s = strtoll(row[%s] ? row[%s] : \"-1\", nullptr, 10);\n", $column_name_formatted, $index, $index); + $find_one_entries .= sprintf("\t\t\tentry.%-${longest_column_length}s = strtoll(row[%s] ? row[%s] : \"-1\", nullptr, 10);\n", $column_name_formatted, $index, $index); + } + elsif ($data_type =~ /int/) { + $all_entries .= sprintf("\t\t\tentry.%-${longest_column_length}s = atoi(row[%s]);\n", $column_name_formatted, $index); + $find_one_entries .= sprintf("\t\t\tentry.%-${longest_column_length}s = atoi(row[%s]);\n", $column_name_formatted, $index); } elsif ($data_type =~ /float|double|decimal/) { - $all_entries .= sprintf("\t\t\tentry.%-${longest_column_length}s = static_cast(atof(row[%s]));\n", $column_name, $index); - $find_one_entries .= sprintf("\t\t\tentry.%-${longest_column_length}s = static_cast(atof(row[%s]));\n", $column_name, $index); + $all_entries .= sprintf("\t\t\tentry.%-${longest_column_length}s = static_cast(atof(row[%s]));\n", $column_name_formatted, $index); + $find_one_entries .= sprintf("\t\t\tentry.%-${longest_column_length}s = static_cast(atof(row[%s]));\n", $column_name_formatted, $index); } else { - $all_entries .= sprintf("\t\t\tentry.%-${longest_column_length}s = row[%s] ? row[%s] : \"\";\n", $column_name, $index, $index); - $find_one_entries .= sprintf("\t\t\tentry.%-${longest_column_length}s = row[%s] ? row[%s] : \"\";\n", $column_name, $index, $index); + $all_entries .= sprintf("\t\t\tentry.%-${longest_column_length}s = row[%s] ? row[%s] : \"\";\n", $column_name_formatted, $index, $index); + $find_one_entries .= sprintf("\t\t\tentry.%-${longest_column_length}s = row[%s] ? row[%s] : \"\";\n", $column_name_formatted, $index, $index); } # print $column_name . "\n"; @@ -353,6 +381,7 @@ foreach my $table_to_generate (@tables) { } chomp($column_names_quoted); + chomp($select_column_names_quoted); chomp($table_struct_columns); chomp($default_entries); chomp($update_one_entries); @@ -378,6 +407,7 @@ foreach my $table_to_generate (@tables) { $new_base_repository =~ s/\{\{DATABASE_CONNECTION}}/$database_connection/g; $new_base_repository =~ s/\{\{DEFAULT_ENTRIES}}/$default_entries/g; $new_base_repository =~ s/\{\{COLUMNS_LIST_QUOTED}}/$column_names_quoted/g; + $new_base_repository =~ s/\{\{SELECT_COLUMNS_LIST_QUOTED}}/$select_column_names_quoted/g; $new_base_repository =~ s/\{\{TABLE_STRUCT_COLUMNS}}/$table_struct_columns/g; $new_base_repository =~ s/\{\{FIND_ONE_ENTRIES}}/$find_one_entries/g; $new_base_repository =~ s/\{\{UPDATE_ONE_ENTRIES}}/$update_one_entries/g; @@ -396,6 +426,7 @@ foreach my $table_to_generate (@tables) { $new_repository =~ s/\{\{DATABASE_CONNECTION}}/$database_connection/g; $new_repository =~ s/\{\{DEFAULT_ENTRIES}}/$default_entries/g; $new_repository =~ s/\{\{COLUMNS_LIST_QUOTED}}/$column_names_quoted/g; + $new_repository =~ s/\{\{SELECT_COLUMNS_LIST_QUOTED}}/$select_column_names_quoted/g; $new_repository =~ s/\{\{TABLE_STRUCT_COLUMNS}}/$table_struct_columns/g; $new_repository =~ s/\{\{FIND_ONE_ENTRIES}}/$find_one_entries/g; $new_repository =~ s/\{\{UPDATE_ONE_ENTRIES}}/$update_one_entries/g; @@ -466,8 +497,7 @@ sub translate_mysql_data_type_to_c { $struct_data_type = 'int'; } elsif ($mysql_data_type =~ /bigint/) { - $struct_data_type = 'int'; - # Use regular int for now until we have 64 support + $struct_data_type = 'int64'; } elsif ($mysql_data_type =~ /int/) { $struct_data_type = 'int'; @@ -475,6 +505,40 @@ sub translate_mysql_data_type_to_c { elsif ($mysql_data_type =~ /float|double|decimal/) { $struct_data_type = 'float'; } + elsif ($mysql_data_type =~ /datetime/) { + $struct_data_type = 'time_t'; + } return $struct_data_type; } + +# This is so we can change reserved words on the cpp side to something that will continue be functional in the compilers +sub get_reserved_cpp_variable_names { + return ( + "class", + "int" + ); +} + +sub format_column_name_for_cpp_var { + my $column_name = $_[0]; + + for my $word (get_reserved_cpp_variable_names()) { + if ($word eq $column_name) { + return $column_name . "_"; + } + } + + return $column_name; +} + +sub format_column_name_for_mysql { + my $column_name = $_[0]; + for my $word (get_reserved_cpp_variable_names()) { + if ($word eq $column_name) { + return "`" . $column_name . "`"; + } + } + + return $column_name; +} diff --git a/utils/sql/db_update_manifest.txt b/utils/sql/db_update_manifest.txt index df613531c..3c210a287 100644 --- a/utils/sql/db_update_manifest.txt +++ b/utils/sql/db_update_manifest.txt @@ -415,6 +415,20 @@ 9159|2020_12_22_expedition_system.sql|SELECT * FROM db_version WHERE version >= 9159|empty| 9160|2021_02_14_npc_exp_mod.sql|SHOW COLUMNS from `npc_types` LIKE 'exp_mod'|empty| 9161|2021_02_15_npc_spell_entries_unsigned.sql|SELECT * FROM db_version WHERE version >= 9161|empty| +9162|2021_02_17_server_scheduled_events.sql|SELECT * FROM db_version WHERE version >= 9162|empty| +9163|2021_04_17_zone_safe_heading_changes.sql|SHOW COLUMNS FROM `zone` LIKE 'safe_heading'|empty| +9164|2021_04_23_character_exp_modifiers.sql|SHOW TABLES LIKE 'character_exp_modifiers'|empty| +9165|2021_04_28_idle_pathing.sql|SHOW COLUMNS FROM `spawn2` LIKE 'path_when_zone_idle'|empty| +9166|2021_02_12_dynamic_zone_members.sql|SHOW TABLES LIKE 'dynamic_zone_members'|empty| +9167|2021_06_06_beastlord_pets.sql|SHOW TABLES LIKE 'pets_beastlord_data'|empty| +9168|2021_08_31_pvp_duration.sql|SHOW COLUMNS FROM `spells_new` LIKE 'pvp_duration'|empty| +9169|2021_06_06_dynamic_zone_moved_columns.sql|SELECT * FROM db_version WHERE version >= 9169|empty| +9170|2021_03_03_instance_safereturns.sql|SHOW TABLES LIKE 'character_instance_safereturns'|empty| +9171|2021_03_30_remove_dz_is_current_member.sql|SHOW COLUMNS FROM `dynamic_zone_members` LIKE 'is_current_member'|not_empty| +9172|2021_05_21_shared_tasks.sql|SHOW TABLES LIKE 'shared_tasks'|empty| +9173|2021_09_14_zone_lava_damage.sql|SHOW COLUMNS FROM `zone` LIKE 'lava_damage'|empty| +9174|2021_10_09_not_null_door_columns.sql|SELECT * FROM db_version WHERE version >= 9174|empty| +9175|2022_01_02_expansion_default_value_all.sql|SHOW COLUMNS FROM `forage` LIKE 'min_expansion'|contains|unsigned # 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 86e7c49dd..221020683 100644 --- a/utils/sql/git/bots/bots_db_update_manifest.txt +++ b/utils/sql/git/bots/bots_db_update_manifest.txt @@ -26,6 +26,7 @@ 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| +9028|2021_06_04_bot_create_combinations.sql|SHOW TABLES LIKE 'bot_create_combinations'|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/2021_06_04_bot_create_combinations.sql b/utils/sql/git/bots/required/2021_06_04_bot_create_combinations.sql new file mode 100644 index 000000000..00974fcda --- /dev/null +++ b/utils/sql/git/bots/required/2021_06_04_bot_create_combinations.sql @@ -0,0 +1,34 @@ +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for bot_create_combinations +-- ---------------------------- +DROP TABLE IF EXISTS `bot_create_combinations`; +CREATE TABLE `bot_create_combinations` ( + `race` int UNSIGNED NOT NULL DEFAULT 0, + `classes` int UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`race`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Compact; + +-- ---------------------------- +-- Records of bot_create_combinations +-- ---------------------------- +INSERT INTO `bot_create_combinations` VALUES (1, 15871); -- Human +INSERT INTO `bot_create_combinations` VALUES (2, 49921); -- Barbarian +INSERT INTO `bot_create_combinations` VALUES (3, 15382); -- Erudite +INSERT INTO `bot_create_combinations` VALUES (4, 425); -- Wood Elf +INSERT INTO `bot_create_combinations` VALUES (5, 14342); -- High Elf +INSERT INTO `bot_create_combinations` VALUES (6, 15635); -- Dark Elf +INSERT INTO `bot_create_combinations` VALUES (7, 429); -- Half Elf +INSERT INTO `bot_create_combinations` VALUES (8, 33031); -- Dwarf +INSERT INTO `bot_create_combinations` VALUES (9, 49681); -- Troll +INSERT INTO `bot_create_combinations` VALUES (10, 49681); -- Ogre +INSERT INTO `bot_create_combinations` VALUES (11, 303); -- Halfling +INSERT INTO `bot_create_combinations` VALUES (12, 15639); -- Gnome +INSERT INTO `bot_create_combinations` VALUES (128, 18001); -- Iksar +INSERT INTO `bot_create_combinations` VALUES (130, 50049); -- Vah Shir +INSERT INTO `bot_create_combinations` VALUES (330, 3863); -- Froglok +INSERT INTO `bot_create_combinations` VALUES (522, 15871); -- Drakkin + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/utils/sql/git/optional/2021_11_13_loginserver_hostname.sql b/utils/sql/git/optional/2021_11_13_loginserver_hostname.sql new file mode 100644 index 000000000..10ba57f68 --- /dev/null +++ b/utils/sql/git/optional/2021_11_13_loginserver_hostname.sql @@ -0,0 +1,3 @@ +ALTER TABLE login_accounts MODIFY COLUMN last_ip_address VARCHAR(80) NOT NULL; +ALTER TABLE login_server_admins MODIFY COLUMN registration_ip_address VARCHAR(80) NOT NULL; +ALTER TABLE login_world_servers MODIFY COLUMN last_ip_address VARCHAR(80) DEFAULT NULL; diff --git a/utils/sql/git/optional/2021_11_28_pot_pick_locks_book.sql b/utils/sql/git/optional/2021_11_28_pot_pick_locks_book.sql new file mode 100644 index 000000000..f7c2c55e3 --- /dev/null +++ b/utils/sql/git/optional/2021_11_28_pot_pick_locks_book.sql @@ -0,0 +1 @@ +UPDATE doors SET lockpick=1 WHERE doorid=46 AND zone="potranquility"; diff --git a/utils/sql/git/required/2021_02_12_dynamic_zone_members.sql b/utils/sql/git/required/2021_02_12_dynamic_zone_members.sql new file mode 100644 index 000000000..767f65012 --- /dev/null +++ b/utils/sql/git/required/2021_02_12_dynamic_zone_members.sql @@ -0,0 +1,11 @@ +CREATE TABLE `dynamic_zone_members` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `dynamic_zone_id` int(10) unsigned NOT NULL DEFAULT 0, + `character_id` int(10) unsigned NOT NULL DEFAULT 0, + `is_current_member` tinyint(3) unsigned NOT NULL DEFAULT 1, + PRIMARY KEY (`id`), + UNIQUE KEY `dynamic_zone_id_character_id` (`dynamic_zone_id`,`character_id`), + KEY `character_id` (`character_id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +DROP TABLE `expedition_members`; diff --git a/utils/sql/git/required/2021_02_17_server_scheduled_events.sql b/utils/sql/git/required/2021_02_17_server_scheduled_events.sql new file mode 100644 index 000000000..deb69423f --- /dev/null +++ b/utils/sql/git/required/2021_02_17_server_scheduled_events.sql @@ -0,0 +1,21 @@ +CREATE TABLE `server_scheduled_events` +( + `id` int(11) NOT NULL AUTO_INCREMENT, + `description` varchar(255) DEFAULT NULL, + `event_type` varchar(100) DEFAULT NULL, + `event_data` text DEFAULT NULL, + `minute_start` int(11) DEFAULT 0, + `hour_start` int(11) DEFAULT 0, + `day_start` int(11) DEFAULT 0, + `month_start` int(11) DEFAULT 0, + `year_start` int(11) DEFAULT 0, + `minute_end` int(11) DEFAULT 0, + `hour_end` int(11) DEFAULT 0, + `day_end` int(11) DEFAULT 0, + `month_end` int(11) DEFAULT 0, + `year_end` int(11) DEFAULT 0, + `cron_expression` varchar(100) DEFAULT NULL, + `created_at` datetime DEFAULT NULL, + `deleted_at` datetime DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; diff --git a/utils/sql/git/required/2021_03_03_instance_safereturns.sql b/utils/sql/git/required/2021_03_03_instance_safereturns.sql new file mode 100644 index 000000000..73c522234 --- /dev/null +++ b/utils/sql/git/required/2021_03_03_instance_safereturns.sql @@ -0,0 +1,13 @@ +CREATE TABLE `character_instance_safereturns` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `character_id` int(10) unsigned NOT NULL, + `instance_zone_id` int(11) NOT NULL DEFAULT 0, + `instance_id` int(11) NOT NULL DEFAULT 0, + `safe_zone_id` int(11) NOT NULL DEFAULT 0, + `safe_x` float NOT NULL DEFAULT 0, + `safe_y` float NOT NULL DEFAULT 0, + `safe_z` float NOT NULL DEFAULT 0, + `safe_heading` float NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + UNIQUE KEY `character_id` (`character_id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; diff --git a/utils/sql/git/required/2021_03_30_remove_dz_is_current_member.sql b/utils/sql/git/required/2021_03_30_remove_dz_is_current_member.sql new file mode 100644 index 000000000..c3293ac2d --- /dev/null +++ b/utils/sql/git/required/2021_03_30_remove_dz_is_current_member.sql @@ -0,0 +1,6 @@ +-- remove any non-current members for new behavior +DELETE FROM `dynamic_zone_members` +WHERE is_current_member = 0; + +ALTER TABLE `dynamic_zone_members` + DROP COLUMN `is_current_member`; diff --git a/utils/sql/git/required/2021_04_17_zone_safe_heading_changes.sql b/utils/sql/git/required/2021_04_17_zone_safe_heading_changes.sql new file mode 100644 index 000000000..c10a5c6ff --- /dev/null +++ b/utils/sql/git/required/2021_04_17_zone_safe_heading_changes.sql @@ -0,0 +1 @@ +ALTER TABLE zone ADD COLUMN safe_heading float NOT NULL DEFAULT 0 AFTER safe_z; diff --git a/utils/sql/git/required/2021_04_23_character_exp_modifiers.sql b/utils/sql/git/required/2021_04_23_character_exp_modifiers.sql new file mode 100644 index 000000000..4a1bcb5de --- /dev/null +++ b/utils/sql/git/required/2021_04_23_character_exp_modifiers.sql @@ -0,0 +1,7 @@ +CREATE TABLE `character_exp_modifiers` ( + `character_id` int NOT NULL, + `zone_id` int NOT NULL, + `aa_modifier` float NOT NULL, + `exp_modifier` float NOT NULL, + PRIMARY KEY (`character_id`, `zone_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Compact; diff --git a/utils/sql/git/required/2021_04_28_idle_pathing.sql b/utils/sql/git/required/2021_04_28_idle_pathing.sql new file mode 100644 index 000000000..2249163fc --- /dev/null +++ b/utils/sql/git/required/2021_04_28_idle_pathing.sql @@ -0,0 +1,13 @@ +-- Add new path_when_zone_idle flag to allow some spawns to path in empty zones +ALTER TABLE spawn2 ADD COLUMN path_when_zone_idle tinyint(1) NOT NULL DEFAULT 0 AFTER pathgrid; + +-- Update spawns that used to path in empty zones because of their grid type +-- to behave the same using the new mechanism. The code that checked path grid +-- types has been removed as it was coincidentally coupled to idle movement. +-- The new flag path_when_zone_idle is the new mechanism, and allows any moving +-- mob, not just those on grids, to path while the zone is idle. +UPDATE spawn2 s +LEFT JOIN zone z ON z.short_name = s.zone +LEFT JOIN grid g ON g.id = s.pathgrid AND g.zoneid = z.zoneidnumber +SET path_when_zone_idle = 1 +WHERE pathgrid != 0 AND g.type IN (4, 6); diff --git a/utils/sql/git/required/2021_05_21_shared_tasks.sql b/utils/sql/git/required/2021_05_21_shared_tasks.sql new file mode 100644 index 000000000..351cb5ba8 --- /dev/null +++ b/utils/sql/git/required/2021_05_21_shared_tasks.sql @@ -0,0 +1,97 @@ +-- shared task tables +CREATE TABLE `shared_tasks` +( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `task_id` int(11) DEFAULT NULL, + `accepted_time` datetime DEFAULT NULL, + `expire_time` datetime DEFAULT NULL, + `completion_time` datetime DEFAULT NULL, + `is_locked` tinyint(1) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; + +CREATE TABLE `shared_task_members` +( + `shared_task_id` bigint(20) NOT NULL, + `character_id` bigint(20) NOT NULL, + `is_leader` tinyint(4) DEFAULT NULL, + PRIMARY KEY (`shared_task_id`, `character_id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +CREATE TABLE `shared_task_activity_state` +( + `shared_task_id` bigint(20) NOT NULL, + `activity_id` int(11) NOT NULL, + `done_count` int(11) DEFAULT NULL, + `updated_time` datetime DEFAULT NULL, + `completed_time` datetime DEFAULT NULL, + PRIMARY KEY (`shared_task_id`, `activity_id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +CREATE TABLE `shared_task_dynamic_zones` +( + `shared_task_id` bigint(20) NOT NULL, + `dynamic_zone_id` int(10) unsigned NOT NULL, + PRIMARY KEY (`shared_task_id`, `dynamic_zone_id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +-- completed shared task tables - simply stores completed for reporting and logging + +CREATE TABLE `completed_shared_tasks` +( + `id` bigint(20) NOT NULL, + `task_id` int(11) DEFAULT NULL, + `accepted_time` datetime DEFAULT NULL, + `expire_time` datetime DEFAULT NULL, + `completion_time` datetime DEFAULT NULL, + `is_locked` tinyint(1) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +CREATE TABLE `completed_shared_task_members` +( + `shared_task_id` bigint(20) NOT NULL, + `character_id` bigint(20) NOT NULL, + `is_leader` tinyint(4) DEFAULT NULL, + PRIMARY KEY (`shared_task_id`, `character_id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +CREATE TABLE `completed_shared_task_activity_state` +( + `shared_task_id` bigint(20) NOT NULL, + `activity_id` int(11) NOT NULL, + `done_count` int(11) DEFAULT NULL, + `updated_time` datetime DEFAULT NULL, + `completed_time` datetime DEFAULT NULL, + PRIMARY KEY (`shared_task_id`, `activity_id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +-- tasks + +ALTER TABLE `tasks` + ADD COLUMN `level_spread` INT UNSIGNED NOT NULL DEFAULT 0 AFTER `maxlevel`, + ADD COLUMN `min_players` INT UNSIGNED NOT NULL DEFAULT 0 AFTER `level_spread`, + ADD COLUMN `max_players` INT UNSIGNED NOT NULL DEFAULT 0 AFTER `min_players`, + ADD COLUMN `replay_timer_seconds` INT UNSIGNED NOT NULL DEFAULT 0 AFTER `completion_emote`, + ADD COLUMN `request_timer_seconds` INT UNSIGNED NOT NULL DEFAULT 0 AFTER `replay_timer_seconds`; + +-- character timers + +CREATE TABLE `character_task_timers` +( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `character_id` int(10) unsigned NOT NULL DEFAULT 0, + `task_id` int(10) unsigned NOT NULL DEFAULT 0, + `timer_type` int(11) NOT NULL DEFAULT 0, + `expire_time` datetime NOT NULL DEFAULT current_timestamp(), + PRIMARY KEY (`id`), + KEY `character_id` (`character_id`), + KEY `task_id` (`task_id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +ALTER TABLE `tasks` + CHANGE COLUMN `completion_emote` `completion_emote` VARCHAR (512) NOT NULL DEFAULT '' COLLATE 'latin1_swedish_ci' AFTER `faction_reward`; + +ALTER TABLE `tasks` + ADD COLUMN `reward_radiant_crystals` INT UNSIGNED NOT NULL DEFAULT '0' AFTER `rewardmethod`, + ADD COLUMN `reward_ebon_crystals` INT UNSIGNED NOT NULL DEFAULT '0' AFTER `reward_radiant_crystals`; diff --git a/utils/sql/git/required/2021_06_06_beastlord_pets.sql b/utils/sql/git/required/2021_06_06_beastlord_pets.sql new file mode 100644 index 000000000..afa9229c0 --- /dev/null +++ b/utils/sql/git/required/2021_06_06_beastlord_pets.sql @@ -0,0 +1,28 @@ +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for pets_beastlord_data +-- ---------------------------- +DROP TABLE IF EXISTS `pets_beastlord_data`; +CREATE TABLE `pets_beastlord_data` ( + `player_race` int UNSIGNED NOT NULL DEFAULT 1, + `pet_race` int UNSIGNED NOT NULL DEFAULT 42, + `texture` tinyint UNSIGNED NOT NULL DEFAULT 0, + `helm_texture` tinyint UNSIGNED NOT NULL DEFAULT 0, + `gender` tinyint UNSIGNED NOT NULL DEFAULT 2, + `size_modifier` float UNSIGNED NULL DEFAULT 1, + `face` tinyint UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`player_race`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Compact; + +-- ---------------------------- +-- Records of pets_beastlord_data +-- ---------------------------- +INSERT INTO `pets_beastlord_data` VALUES (2, 42, 2, 0, 2, 1, 0); -- Barbarian +INSERT INTO `pets_beastlord_data` VALUES (9, 91, 0, 0, 2, 2.5, 0); -- Troll +INSERT INTO `pets_beastlord_data` VALUES (10, 43, 3, 0, 2, 1, 0); -- Ogre +INSERT INTO `pets_beastlord_data` VALUES (128, 42, 0, 0, 1, 2, 0); -- Iksar +INSERT INTO `pets_beastlord_data` VALUES (130, 63, 0, 0, 2, 0.8, 0); -- Vah Shir + +SET FOREIGN_KEY_CHECKS = 1; \ No newline at end of file diff --git a/utils/sql/git/required/2021_06_06_dynamic_zone_moved_columns.sql b/utils/sql/git/required/2021_06_06_dynamic_zone_moved_columns.sql new file mode 100644 index 000000000..29635e8af --- /dev/null +++ b/utils/sql/git/required/2021_06_06_dynamic_zone_moved_columns.sql @@ -0,0 +1,23 @@ +ALTER TABLE `dynamic_zones` + ADD COLUMN `uuid` VARCHAR(36) NOT NULL COLLATE 'latin1_swedish_ci' AFTER `type`, + ADD COLUMN `name` VARCHAR(128) NOT NULL DEFAULT '' COLLATE 'latin1_swedish_ci' AFTER `uuid`, + ADD COLUMN `leader_id` INT UNSIGNED NOT NULL DEFAULT '0' AFTER `name`, + ADD COLUMN `min_players` INT UNSIGNED NOT NULL DEFAULT '0' AFTER `leader_id`, + ADD COLUMN `max_players` INT UNSIGNED NOT NULL DEFAULT '0' AFTER `min_players`; + +-- migrate any currently active expeditions +UPDATE dynamic_zones +INNER JOIN expeditions ON expeditions.dynamic_zone_id = dynamic_zones.id +SET + dynamic_zones.uuid = expeditions.uuid, + dynamic_zones.name = expeditions.expedition_name, + dynamic_zones.leader_id = expeditions.leader_id, + dynamic_zones.min_players = expeditions.min_players, + dynamic_zones.max_players = expeditions.max_players; + +ALTER TABLE `expeditions` + DROP COLUMN `uuid`, + DROP COLUMN `expedition_name`, + DROP COLUMN `leader_id`, + DROP COLUMN `min_players`, + DROP COLUMN `max_players`; diff --git a/utils/sql/git/required/2021_08_31_pvp_duration.sql b/utils/sql/git/required/2021_08_31_pvp_duration.sql new file mode 100644 index 000000000..e0fe00bfc --- /dev/null +++ b/utils/sql/git/required/2021_08_31_pvp_duration.sql @@ -0,0 +1,2 @@ +ALTER TABLE `spells_new` CHANGE `field181` `pvp_duration` int(11) NOT NULL DEFAULT '0'; +ALTER TABLE `spells_new` CHANGE `field182` `pvp_duration_cap` int(11) NOT NULL DEFAULT '0'; diff --git a/utils/sql/git/required/2021_09_14_zone_lava_damage.sql b/utils/sql/git/required/2021_09_14_zone_lava_damage.sql new file mode 100644 index 000000000..f6a5d00d1 --- /dev/null +++ b/utils/sql/git/required/2021_09_14_zone_lava_damage.sql @@ -0,0 +1 @@ +ALTER TABLE zone ADD lava_damage INT(11) NULL DEFAULT '50' AFTER underworld_teleport_index, ADD min_lava_damage INT(11) NOT NULL DEFAULT '10' AFTER lava_damage; diff --git a/utils/sql/git/required/2021_10_09_not_null_door_columns.sql b/utils/sql/git/required/2021_10_09_not_null_door_columns.sql new file mode 100644 index 000000000..7aed58b6f --- /dev/null +++ b/utils/sql/git/required/2021_10_09_not_null_door_columns.sql @@ -0,0 +1,18 @@ +-- update any null columns to non-null value first to avoid data truncation errors +-- this will likely only affect the buffer column +update `doors` set `doors`.`dest_x` = 0 where `doors`.`dest_x` is null; +update `doors` set `doors`.`dest_y` = 0 where `doors`.`dest_y` is null; +update `doors` set `doors`.`dest_z` = 0 where `doors`.`dest_z` is null; +update `doors` set `doors`.`dest_heading` = 0 where `doors`.`dest_heading` is null; +update `doors` set `doors`.`invert_state` = 0 where `doors`.`invert_state` is null; +update `doors` set `doors`.`incline` = 0 where `doors`.`incline` is null; +update `doors` set `doors`.`buffer` = 0 where `doors`.`buffer` is null; + +ALTER TABLE `doors` + CHANGE COLUMN `dest_x` `dest_x` FLOAT NOT NULL DEFAULT '0' AFTER `dest_instance`, + CHANGE COLUMN `dest_y` `dest_y` FLOAT NOT NULL DEFAULT '0' AFTER `dest_x`, + CHANGE COLUMN `dest_z` `dest_z` FLOAT NOT NULL DEFAULT '0' AFTER `dest_y`, + CHANGE COLUMN `dest_heading` `dest_heading` FLOAT NOT NULL DEFAULT '0' AFTER `dest_z`, + CHANGE COLUMN `invert_state` `invert_state` INT(11) NOT NULL DEFAULT '0' AFTER `dest_heading`, + CHANGE COLUMN `incline` `incline` INT(11) NOT NULL DEFAULT '0' AFTER `invert_state`, + CHANGE COLUMN `buffer` `buffer` FLOAT NOT NULL DEFAULT '0' AFTER `size`; diff --git a/utils/sql/git/required/2022_01_02_expansion_default_value_all.sql b/utils/sql/git/required/2022_01_02_expansion_default_value_all.sql new file mode 100644 index 000000000..3b2261014 --- /dev/null +++ b/utils/sql/git/required/2022_01_02_expansion_default_value_all.sql @@ -0,0 +1,117 @@ +-- forage + +ALTER TABLE `forage` CHANGE `max_expansion` `max_expansion` tinyint(4) NOT NULL DEFAULT -1 COMMENT ''; +ALTER TABLE `forage` CHANGE `min_expansion` `min_expansion` tinyint(4) NOT NULL DEFAULT -1 COMMENT ''; +UPDATE forage set min_expansion = -1 where min_expansion = 0; +UPDATE forage set max_expansion = -1 where max_expansion = 0; + +-- tradeskill_recipe + +ALTER TABLE `tradeskill_recipe` CHANGE `max_expansion` `max_expansion` tinyint(4) NOT NULL DEFAULT -1 COMMENT ''; +ALTER TABLE `tradeskill_recipe` CHANGE `min_expansion` `min_expansion` tinyint(4) NOT NULL DEFAULT -1 COMMENT ''; +UPDATE tradeskill_recipe set min_expansion = -1 where min_expansion = 0; +UPDATE tradeskill_recipe set max_expansion = -1 where max_expansion = 0; + +-- fishing + +ALTER TABLE `fishing` CHANGE `max_expansion` `max_expansion` tinyint(4) NOT NULL DEFAULT -1 COMMENT ''; +ALTER TABLE `fishing` CHANGE `min_expansion` `min_expansion` tinyint(4) NOT NULL DEFAULT -1 COMMENT ''; +UPDATE fishing set min_expansion = -1 where min_expansion = 0; +UPDATE fishing set max_expansion = -1 where max_expansion = 0; + +-- zone + +ALTER TABLE `zone` CHANGE `max_expansion` `max_expansion` tinyint(4) NOT NULL DEFAULT -1 COMMENT ''; +ALTER TABLE `zone` CHANGE `min_expansion` `min_expansion` tinyint(4) NOT NULL DEFAULT -1 COMMENT ''; +UPDATE zone set min_expansion = -1 where min_expansion = 0; +UPDATE zone set max_expansion = -1 where max_expansion = 0; + +-- traps + +ALTER TABLE `traps` CHANGE `max_expansion` `max_expansion` tinyint(4) NOT NULL DEFAULT -1 COMMENT ''; +ALTER TABLE `traps` CHANGE `min_expansion` `min_expansion` tinyint(4) NOT NULL DEFAULT -1 COMMENT ''; +UPDATE traps set min_expansion = -1 where min_expansion = 0; +UPDATE traps set max_expansion = -1 where max_expansion = 0; + +-- loottable + +ALTER TABLE `loottable` CHANGE `max_expansion` `max_expansion` tinyint(4) NOT NULL DEFAULT -1 COMMENT ''; +ALTER TABLE `loottable` CHANGE `min_expansion` `min_expansion` tinyint(4) NOT NULL DEFAULT -1 COMMENT ''; +UPDATE loottable set min_expansion = -1 where min_expansion = 0; +UPDATE loottable set max_expansion = -1 where max_expansion = 0; + +-- ground_spawns + +ALTER TABLE `ground_spawns` CHANGE `max_expansion` `max_expansion` tinyint(4) NOT NULL DEFAULT -1 COMMENT ''; +ALTER TABLE `ground_spawns` CHANGE `min_expansion` `min_expansion` tinyint(4) NOT NULL DEFAULT -1 COMMENT ''; +UPDATE ground_spawns set min_expansion = -1 where min_expansion = 0; +UPDATE ground_spawns set max_expansion = -1 where max_expansion = 0; + +-- starting_items + +ALTER TABLE `starting_items` CHANGE `max_expansion` `max_expansion` tinyint(4) NOT NULL DEFAULT -1 COMMENT ''; +ALTER TABLE `starting_items` CHANGE `min_expansion` `min_expansion` tinyint(4) NOT NULL DEFAULT -1 COMMENT ''; +UPDATE starting_items set min_expansion = -1 where min_expansion = 0; +UPDATE starting_items set max_expansion = -1 where max_expansion = 0; + +-- spawn2 + +ALTER TABLE `spawn2` CHANGE `max_expansion` `max_expansion` tinyint(4) NOT NULL DEFAULT -1 COMMENT ''; +ALTER TABLE `spawn2` CHANGE `min_expansion` `min_expansion` tinyint(4) NOT NULL DEFAULT -1 COMMENT ''; +UPDATE spawn2 set min_expansion = -1 where min_expansion = 0; +UPDATE spawn2 set max_expansion = -1 where max_expansion = 0; + +-- zone_points + +ALTER TABLE `zone_points` CHANGE `max_expansion` `max_expansion` tinyint(4) NOT NULL DEFAULT -1 COMMENT ''; +ALTER TABLE `zone_points` CHANGE `min_expansion` `min_expansion` tinyint(4) NOT NULL DEFAULT -1 COMMENT ''; +UPDATE zone_points set min_expansion = -1 where min_expansion = 0; +UPDATE zone_points set max_expansion = -1 where max_expansion = 0; + +-- lootdrop + +ALTER TABLE `lootdrop` CHANGE `max_expansion` `max_expansion` tinyint(4) NOT NULL DEFAULT -1 COMMENT ''; +ALTER TABLE `lootdrop` CHANGE `min_expansion` `min_expansion` tinyint(4) NOT NULL DEFAULT -1 COMMENT ''; +UPDATE lootdrop set min_expansion = -1 where min_expansion = 0; +UPDATE lootdrop set max_expansion = -1 where max_expansion = 0; + +-- global_loot + +ALTER TABLE `global_loot` CHANGE `max_expansion` `max_expansion` tinyint(4) NOT NULL DEFAULT -1 COMMENT ''; +ALTER TABLE `global_loot` CHANGE `min_expansion` `min_expansion` tinyint(4) NOT NULL DEFAULT -1 COMMENT ''; +UPDATE global_loot set min_expansion = -1 where min_expansion = 0; +UPDATE global_loot set max_expansion = -1 where max_expansion = 0; + +-- doors + +ALTER TABLE `doors` CHANGE `max_expansion` `max_expansion` tinyint(4) NOT NULL DEFAULT -1 COMMENT ''; +ALTER TABLE `doors` CHANGE `min_expansion` `min_expansion` tinyint(4) NOT NULL DEFAULT -1 COMMENT ''; +UPDATE doors set min_expansion = -1 where min_expansion = 0; +UPDATE doors set max_expansion = -1 where max_expansion = 0; + +-- object + +ALTER TABLE `object` CHANGE `max_expansion` `max_expansion` tinyint(4) NOT NULL DEFAULT -1 COMMENT ''; +ALTER TABLE `object` CHANGE `min_expansion` `min_expansion` tinyint(4) NOT NULL DEFAULT -1 COMMENT ''; +UPDATE object set min_expansion = -1 where min_expansion = 0; +UPDATE object set max_expansion = -1 where max_expansion = 0; + +-- start_zones + +ALTER TABLE `start_zones` CHANGE `max_expansion` `max_expansion` tinyint(4) NOT NULL DEFAULT -1 COMMENT ''; +ALTER TABLE `start_zones` CHANGE `min_expansion` `min_expansion` tinyint(4) NOT NULL DEFAULT -1 COMMENT ''; +UPDATE start_zones set min_expansion = -1 where min_expansion = 0; +UPDATE start_zones set max_expansion = -1 where max_expansion = 0; + +-- merchantlist + +ALTER TABLE `merchantlist` CHANGE `max_expansion` `max_expansion` tinyint(4) NOT NULL DEFAULT -1 COMMENT ''; +ALTER TABLE `merchantlist` CHANGE `min_expansion` `min_expansion` tinyint(4) NOT NULL DEFAULT -1 COMMENT ''; +UPDATE merchantlist set min_expansion = -1 where min_expansion = 0; +UPDATE merchantlist set max_expansion = -1 where max_expansion = 0; + +-- spawnentry +ALTER TABLE `spawnentry` ADD `min_expansion` tinyint(4) NOT NULL DEFAULT -1; +ALTER TABLE `spawnentry` ADD `max_expansion` tinyint(4) NOT NULL DEFAULT -1; +ALTER TABLE `spawnentry` ADD `content_flags` varchar(100) NULL; +ALTER TABLE `spawnentry` ADD `content_flags_disabled` varchar(100) NULL; diff --git a/world/CMakeLists.txt b/world/CMakeLists.txt index 50210f2f3..650d8f189 100644 --- a/world/CMakeLists.txt +++ b/world/CMakeLists.txt @@ -7,12 +7,12 @@ SET(world_sources cliententry.cpp clientlist.cpp console.cpp + dynamic_zone.cpp + dynamic_zone_manager.cpp eql_config.cpp eqemu_api_world_data_service.cpp - expedition.cpp expedition_database.cpp expedition_message.cpp - expedition_state.cpp launcher_link.cpp launcher_list.cpp lfplist.cpp @@ -20,10 +20,13 @@ SET(world_sources login_server_list.cpp main.cpp queryserv.cpp + shared_task_manager.cpp + shared_task_world_messaging.cpp ucs.cpp web_interface.cpp web_interface_eqw.cpp wguild_mgr.cpp + world_event_scheduler.cpp world_config.cpp world_console_connection.cpp world_server_command_handler.cpp @@ -41,18 +44,20 @@ SET(world_headers cliententry.h clientlist.h console.h + dynamic_zone.h + dynamic_zone_manager.h eql_config.h eqemu_api_world_data_service.h - expedition.h expedition_database.h expedition_message.h - expedition_state.h launcher_link.h launcher_list.h lfplist.h login_server.h login_server_list.h queryserv.h + shared_task_manager.h + shared_task_world_messaging.h sof_char_create_data.h ucs.h web_interface.h @@ -63,6 +68,7 @@ SET(world_headers world_tcp_connection.h world_server_command_handler.h worlddb.h + world_event_scheduler.h world_store.h zonelist.h zoneserver.h diff --git a/world/client.cpp b/world/client.cpp index 963369255..d5305eb67 100644 --- a/world/client.cpp +++ b/world/client.cpp @@ -121,7 +121,7 @@ Client::Client(EQStreamInterface* ieqs) m_ClientVersion = eqs->ClientVersion(); m_ClientVersionBit = EQ::versions::ConvertClientVersionToClientVersionBit(m_ClientVersion); - + numclients++; } @@ -833,19 +833,12 @@ bool Client::HandleEnterWorldPacket(const EQApplicationPacket *app) { if(instance_id > 0) { - if(!database.VerifyInstanceAlive(instance_id, GetCharID())) + if (!database.VerifyInstanceAlive(instance_id, GetCharID()) || + !database.VerifyZoneInstance(zone_id, instance_id)) { - zone_id = database.MoveCharacterToBind(charid); + zone_id = database.MoveCharacterToInstanceSafeReturn(charid, zone_id, instance_id); instance_id = 0; } - else - { - if(!database.VerifyZoneInstance(zone_id, instance_id)) - { - zone_id = database.MoveCharacterToBind(charid); - instance_id = 0; - } - } } if(!is_player_zoning) { @@ -1154,34 +1147,23 @@ void Client::EnterWorld(bool TryBootup) { return; ZoneServer* zone_server = nullptr; - if(instance_id > 0) + if (instance_id > 0) { - if(database.VerifyInstanceAlive(instance_id, GetCharID())) - { - if(database.VerifyZoneInstance(zone_id, instance_id)) - { - zone_server = zoneserver_list.FindByInstanceID(instance_id); - } - else - { - instance_id = 0; - zone_server = nullptr; - database.MoveCharacterToBind(GetCharID()); - TellClientZoneUnavailable(); - return; - } - } - else + if (!database.VerifyInstanceAlive(instance_id, GetCharID()) || + !database.VerifyZoneInstance(zone_id, instance_id)) { instance_id = 0; - zone_server = nullptr; - database.MoveCharacterToBind(GetCharID()); + database.MoveCharacterToInstanceSafeReturn(GetCharID(), zone_id, instance_id); TellClientZoneUnavailable(); return; } + + zone_server = zoneserver_list.FindByInstanceID(instance_id); } else + { zone_server = zoneserver_list.FindByZoneID(zone_id); + } const char *zone_name = ZoneName(zone_id, true); if (zone_server) { @@ -1569,25 +1551,25 @@ bool Client::OPCharCreate(char *name, CharCreate_Struct *cc) } /* Set Home Binds -- yep, all of them */ - pp.binds[1].zoneId = pp.zone_id; + pp.binds[1].zone_id = pp.zone_id; pp.binds[1].x = pp.x; pp.binds[1].y = pp.y; pp.binds[1].z = pp.z; pp.binds[1].heading = pp.heading; - pp.binds[2].zoneId = pp.zone_id; + pp.binds[2].zone_id = pp.zone_id; pp.binds[2].x = pp.x; pp.binds[2].y = pp.y; pp.binds[2].z = pp.z; pp.binds[2].heading = pp.heading; - pp.binds[3].zoneId = pp.zone_id; + pp.binds[3].zone_id = pp.zone_id; pp.binds[3].x = pp.x; pp.binds[3].y = pp.y; pp.binds[3].z = pp.z; pp.binds[3].heading = pp.heading; - pp.binds[4].zoneId = pp.zone_id; + pp.binds[4].zone_id = pp.zone_id; pp.binds[4].x = pp.x; pp.binds[4].y = pp.y; pp.binds[4].z = pp.z; @@ -1601,7 +1583,7 @@ bool Client::OPCharCreate(char *name, CharCreate_Struct *cc) /* Will either be the same as home or tutorial if enabled. */ if(RuleB(World, StartZoneSameAsBindOnCreation)) { - pp.binds[0].zoneId = pp.zone_id; + pp.binds[0].zone_id = pp.zone_id; pp.binds[0].x = pp.x; pp.binds[0].y = pp.y; pp.binds[0].z = pp.z; @@ -1611,9 +1593,9 @@ bool Client::OPCharCreate(char *name, CharCreate_Struct *cc) Log(Logs::Detail, Logs::WorldServer, "Current location: %s (%d) %0.2f, %0.2f, %0.2f, %0.2f", ZoneName(pp.zone_id), pp.zone_id, pp.x, pp.y, pp.z, pp.heading); Log(Logs::Detail, Logs::WorldServer, "Bind location: %s (%d) %0.2f, %0.2f, %0.2f", - ZoneName(pp.binds[0].zoneId), pp.binds[0].zoneId, pp.binds[0].x, pp.binds[0].y, pp.binds[0].z); + ZoneName(pp.binds[0].zone_id), pp.binds[0].zone_id, pp.binds[0].x, pp.binds[0].y, pp.binds[0].z); Log(Logs::Detail, Logs::WorldServer, "Home location: %s (%d) %0.2f, %0.2f, %0.2f", - ZoneName(pp.binds[4].zoneId), pp.binds[4].zoneId, pp.binds[4].x, pp.binds[4].y, pp.binds[4].z); + ZoneName(pp.binds[4].zone_id), pp.binds[4].zone_id, pp.binds[4].x, pp.binds[4].y, pp.binds[4].z); /* Starting Items inventory */ content_db.SetStartingItems(&pp, &inv, pp.race, pp.class_, pp.deity, pp.zone_id, pp.name, GetAdmin()); diff --git a/world/cliententry.cpp b/world/cliententry.cpp index 9aa2b9ae0..68252c802 100644 --- a/world/cliententry.cpp +++ b/world/cliententry.cpp @@ -20,6 +20,7 @@ #include "clientlist.h" #include "login_server.h" #include "login_server_list.h" +#include "shared_task_manager.h" #include "worlddb.h" #include "zoneserver.h" #include "world_config.h" @@ -30,6 +31,7 @@ extern uint32 numplayers; extern LoginServerList loginserverlist; extern ClientList client_list; extern volatile bool RunLoops; +extern SharedTaskManager shared_task_manager; /** * @param in_id @@ -249,6 +251,8 @@ void ClientListEntry::LeavingZone(ZoneServer *iZS, CLE_Status iOnline) } SetOnline(iOnline); + shared_task_manager.RemoveActiveInvitationByCharacterID(CharID()); + if (pzoneserver) { pzoneserver->RemovePlayer(); LSUpdate(pzoneserver); @@ -270,7 +274,7 @@ void ClientListEntry::ClearVars(bool iAll) paccountid = 0; memset(paccountname, 0, sizeof(paccountname)); - padmin = 0; + padmin = AccountStatus::Player; } pzoneserver = 0; pzone = 0; @@ -361,7 +365,7 @@ bool ClientListEntry::CheckAuth(uint32 loginserver_account_id, const char *key_p } std::string lsworldadmin; if (database.GetVariable("honorlsworldadmin", lsworldadmin)) { - if (atoi(lsworldadmin.c_str()) == 1 && pworldadmin != 0 && (padmin < pworldadmin || padmin == 0)) { + if (atoi(lsworldadmin.c_str()) == 1 && pworldadmin != 0 && (padmin < pworldadmin || padmin == AccountStatus::Player)) { padmin = pworldadmin; } } diff --git a/world/cliententry.h b/world/cliententry.h index 769700403..f3ce31dea 100644 --- a/world/cliententry.h +++ b/world/cliententry.h @@ -60,7 +60,7 @@ public: * @param scl * @param iOnline */ - ClientListEntry(uint32 id, uint32 iAccID, const char* iAccName, MD5& iMD5Pass, int16 iAdmin = 0); + ClientListEntry(uint32 id, uint32 iAccID, const char* iAccName, MD5& iMD5Pass, int16 iAdmin = AccountStatus::Player); ClientListEntry(uint32 id, ZoneServer* iZS, ServerClientList_Struct* scl, CLE_Status iOnline); ~ClientListEntry(); bool CheckStale(); diff --git a/world/clientlist.cpp b/world/clientlist.cpp index 008a1115b..1513cb2df 100644 --- a/world/clientlist.cpp +++ b/world/clientlist.cpp @@ -296,7 +296,13 @@ void ClientList::SendCLEList(const int16& admin, const char* to, WorldTCPConnect fmt:format_to(out, "{} CharID: {} CharName: {} Zone: {} ({})", newline, cle->CharID(), cle->name(), ZoneName(cle->zone()), cle->zone()); if (out.size() >= 3072) { auto output = fmt::to_string(out); - connection->SendEmoteMessageRaw(to, 0, 0, 10, output.c_str()); + connection->SendEmoteMessageRaw( + to, + 0, + AccountStatus::Player, + Chat::NPCQuestSay, + output.c_str() + ); addnewline = false; out.clear(); } else { @@ -309,7 +315,13 @@ void ClientList::SendCLEList(const int16& admin, const char* to, WorldTCPConnect } fmt::format_to(out, "{}{} CLEs in memory. {} CLEs listed. numplayers = {}.", newline, x, y, numplayers); auto output = fmt::to_string(out); - connection->SendEmoteMessageRaw(to, 0, 0, 10, output.c_str()); + connection->SendEmoteMessageRaw( + to, + 0, + AccountStatus::Player, + Chat::NPCQuestSay, + output.c_str() + ); } @@ -332,30 +344,91 @@ void ClientList::CLCheckStale() { } } -void ClientList::ClientUpdate(ZoneServer* zoneserver, ServerClientList_Struct* scl) { - LinkedListIterator iterator(clientlist); - ClientListEntry* cle; +void ClientList::ClientUpdate(ZoneServer *zoneserver, ServerClientList_Struct *scl) +{ + LinkedListIterator iterator(clientlist); + ClientListEntry *cle; iterator.Reset(); - while(iterator.MoreElements()) { + while (iterator.MoreElements()) { if (iterator.GetData()->GetID() == scl->wid) { cle = iterator.GetData(); - if (scl->remove == 2){ + if (scl->remove == 2) { cle->LeavingZone(zoneserver, CLE_Status::Offline); } - else if (scl->remove == 1) + else if (scl->remove == 1) { cle->LeavingZone(zoneserver, CLE_Status::Zoning); - else + } + else { cle->Update(zoneserver, scl); + } return; } iterator.Advance(); } - if (scl->remove == 2) + if (scl->remove == 2) { cle = new ClientListEntry(GetNextCLEID(), zoneserver, scl, CLE_Status::Online); - else if (scl->remove == 1) + } + else if (scl->remove == 1) { cle = new ClientListEntry(GetNextCLEID(), zoneserver, scl, CLE_Status::Zoning); - else + } + else { cle = new ClientListEntry(GetNextCLEID(), zoneserver, scl, CLE_Status::InZone); + } + + LogClientListDetail( + "[ClientUpdate] " + " remove [{}]" + " wid [{}]" + " IP [{}]" + " zone [{}]" + " instance_id [{}]" + " Admin [{}]" + " charid [{}]" + " name [{}]" + " AccountID [{}]" + " AccountName [{}]" + " LSAccountID [{}]" + " lskey [{}]" + " race [{}]" + " class_ [{}]" + " level [{}]" + " anon [{}]" + " tellsoff [{}]" + " guild_id [{}]" + " LFG [{}]" + " gm [{}]" + " ClientVersion [{}]" + " LFGFromLevel [{}]" + " LFGToLevel [{}]" + " LFGMatchFilter [{}]" + " LFGComments [{}]", + scl->remove, + scl->wid, + scl->IP, + scl->zone, + scl->instance_id, + scl->Admin, + scl->charid, + scl->name, + scl->AccountID, + scl->AccountName, + scl->LSAccountID, + scl->lskey, + scl->race, + scl->class_, + scl->level, + scl->anon, + scl->tellsoff, + scl->guild_id, + scl->LFG, + scl->gm, + scl->ClientVersion, + scl->LFGFromLevel, + scl->LFGToLevel, + scl->LFGMatchFilter, + scl->LFGComments + ); + clientlist.Insert(cle); zoneserver->ChangeWID(scl->charid, cle->GetID()); } @@ -497,7 +570,7 @@ void ClientList::SendWhoAll(uint32 fromid,const char* to, int16 admin, Who_All_S (countcle->Online() >= CLE_Status::Zoning) && (!countcle->GetGM() || countcle->Anon() != 1 || admin >= countcle->Admin()) && (whom == 0 || ( - ((countcle->Admin() >= 80 && countcle->GetGM()) || whom->gmlookup == 0xFFFF) && + ((countcle->Admin() >= AccountStatus::QuestTroupe && countcle->GetGM()) || whom->gmlookup == 0xFFFF) && (whom->lvllow == 0xFFFF || (countcle->level() >= whom->lvllow && countcle->level() <= whom->lvlhigh && (countcle->Anon()==0 || admin > countcle->Admin()))) && (whom->wclass == 0xFFFF || (countcle->class_() == whom->wclass && (countcle->Anon()==0 || admin > countcle->Admin()))) && (whom->wrace == 0xFFFF || (countcle->race() == whom->wrace && (countcle->Anon()==0 || admin > countcle->Admin()))) && @@ -505,18 +578,18 @@ void ClientList::SendWhoAll(uint32 fromid,const char* to, int16 admin, Who_All_S (tmpZone != 0 && strncasecmp(tmpZone, whom->whom, whomlen) == 0) || strncasecmp(countcle->name(),whom->whom, whomlen) == 0 || (strncasecmp(guild_mgr.GetGuildName(countcle->GuildID()), whom->whom, whomlen) == 0) || - (admin >= 100 && strncasecmp(countcle->AccountName(), whom->whom, whomlen) == 0) + (admin >= AccountStatus::GMAdmin && strncasecmp(countcle->AccountName(), whom->whom, whomlen) == 0) )) )) ) { - if((countcle->Anon()>0 && admin>=countcle->Admin() && admin>0) || countcle->Anon()==0 ){ + if((countcle->Anon()>0 && admin >= countcle->Admin() && admin > AccountStatus::Player) || countcle->Anon()==0 ){ totalusers++; - if(totalusers<=20 || admin>=100) + if(totalusers<=20 || admin >= AccountStatus::GMAdmin) totallength=totallength+strlen(countcle->name())+strlen(countcle->AccountName())+strlen(guild_mgr.GetGuildName(countcle->GuildID()))+5; } else if((countcle->Anon()>0 && admin<=countcle->Admin()) || (countcle->Anon()==0 && !countcle->GetGM())) { totalusers++; - if(totalusers<=20 || admin>=100) + if(totalusers<=20 || admin >= AccountStatus::GMAdmin) totallength=totallength+strlen(countcle->name())+strlen(guild_mgr.GetGuildName(countcle->GuildID()))+5; } } @@ -528,7 +601,7 @@ void ClientList::SendWhoAll(uint32 fromid,const char* to, int16 admin, Who_All_S uint8 unknown35=0x0A; uint32 unknown36=0; uint32 playersinzonestring=5028; - if(totalusers>20 && admin<100){ + if(totalusers>20 && adminOnline() >= CLE_Status::Zoning) && (!cle->GetGM() || cle->Anon() != 1 || admin >= cle->Admin()) && (whom == 0 || ( - ((cle->Admin() >= 80 && cle->GetGM()) || whom->gmlookup == 0xFFFF) && + ((cle->Admin() >= AccountStatus::QuestTroupe && cle->GetGM()) || whom->gmlookup == 0xFFFF) && (whom->lvllow == 0xFFFF || (cle->level() >= whom->lvllow && cle->level() <= whom->lvlhigh && (cle->Anon()==0 || admin>cle->Admin()))) && (whom->wclass == 0xFFFF || (cle->class_() == whom->wclass && (cle->Anon()==0 || admin>cle->Admin()))) && (whom->wrace == 0xFFFF || (cle->race() == whom->wrace && (cle->Anon()==0 || admin>cle->Admin()))) && @@ -585,60 +658,60 @@ void ClientList::SendWhoAll(uint32 fromid,const char* to, int16 admin, Who_All_S (tmpZone != 0 && strncasecmp(tmpZone, whom->whom, whomlen) == 0) || strncasecmp(cle->name(),whom->whom, whomlen) == 0 || (strncasecmp(guild_mgr.GetGuildName(cle->GuildID()), whom->whom, whomlen) == 0) || - (admin >= 100 && strncasecmp(cle->AccountName(), whom->whom, whomlen) == 0) + (admin >= AccountStatus::GMAdmin && strncasecmp(cle->AccountName(), whom->whom, whomlen) == 0) )) )) ) { line[0] = 0; - uint32 rankstring=0xFFFFFFFF; - if((cle->Anon()==1 && cle->GetGM() && cle->Admin()>admin) || (idx>=20 && admin<100)){ //hide gms that are anon from lesser gms and normal players, cut off at 20 - rankstring=0; + uint32 rankstring = 0xFFFFFFFF; + if((cle->Anon()==1 && cle->GetGM() && cle->Admin()>admin) || (idx>=20 && admin < AccountStatus::GMAdmin)){ //hide gms that are anon from lesser gms and normal players, cut off at 20 + rankstring = 0; iterator.Advance(); continue; } else if (cle->GetGM()) { - if (cle->Admin() >=250) - rankstring=5021; - else if (cle->Admin() >= 200) - rankstring=5020; - else if (cle->Admin() >= 180) - rankstring=5019; - else if (cle->Admin() >= 170) - rankstring=5018; - else if (cle->Admin() >= 160) - rankstring=5017; - else if (cle->Admin() >= 150) - rankstring=5016; - else if (cle->Admin() >= 100) - rankstring=5015; - else if (cle->Admin() >= 95) - rankstring=5014; - else if (cle->Admin() >= 90) - rankstring=5013; - else if (cle->Admin() >= 85) - rankstring=5012; - else if (cle->Admin() >= 81) - rankstring=5011; - else if (cle->Admin() >= 80) - rankstring=5010; - else if (cle->Admin() >= 50) - rankstring=5009; - else if (cle->Admin() >= 20) - rankstring=5008; - else if (cle->Admin() >= 10) - rankstring=5007; + if (cle->Admin() >= AccountStatus::GMImpossible) + rankstring = 5021; + else if (cle->Admin() >= AccountStatus::GMMgmt) + rankstring = 5020; + else if (cle->Admin() >= AccountStatus::GMCoder) + rankstring = 5019; + else if (cle->Admin() >= AccountStatus::GMAreas) + rankstring = 5018; + else if (cle->Admin() >= AccountStatus::QuestMaster) + rankstring = 5017; + else if (cle->Admin() >= AccountStatus::GMLeadAdmin) + rankstring = 5016; + else if (cle->Admin() >= AccountStatus::GMAdmin) + rankstring = 5015; + else if (cle->Admin() >= AccountStatus::GMStaff) + rankstring = 5014; + else if (cle->Admin() >= AccountStatus::EQSupport) + rankstring = 5013; + else if (cle->Admin() >= AccountStatus::GMTester) + rankstring = 5012; + else if (cle->Admin() >= AccountStatus::SeniorGuide) + rankstring = 5011; + else if (cle->Admin() >= AccountStatus::QuestTroupe) + rankstring = 5010; + else if (cle->Admin() >= AccountStatus::Guide) + rankstring = 5009; + else if (cle->Admin() >= AccountStatus::ApprenticeGuide) + rankstring = 5008; + else if (cle->Admin() >= AccountStatus::Steward) + rankstring = 5007; } idx++; char guildbuffer[67]={0}; if (cle->GuildID() != GUILD_NONE && cle->GuildID()>0) sprintf(guildbuffer,"<%s>", guild_mgr.GetGuildName(cle->GuildID())); uint32 formatstring=5025; - if(cle->Anon()==1 && (adminAdmin() || admin==0)) + if(cle->Anon()==1 && (adminAdmin() || admin == AccountStatus::Player)) formatstring=5024; - else if(cle->Anon()==1 && admin>=cle->Admin() && admin>0) + else if(cle->Anon()==1 && admin>=cle->Admin() && admin > AccountStatus::Player) formatstring=5022; - else if(cle->Anon()==2 && (adminAdmin() || admin==0)) + else if(cle->Anon()==2 && (adminAdmin() || admin == AccountStatus::Player)) formatstring=5023;//display guild - else if(cle->Anon()==2 && admin>=cle->Admin() && admin>0) + else if(cle->Anon()==2 && admin>=cle->Admin() && admin > AccountStatus::Player) formatstring=5022;//display everything //war* wars2 = (war*)pack2->pBuffer; @@ -650,10 +723,10 @@ void ClientList::SendWhoAll(uint32 fromid,const char* to, int16 admin, Who_All_S uint32 zonestring=0xFFFFFFFF; uint32 plzone=0; uint32 unknown80[2]; - if(cle->Anon()==0 || (admin>=cle->Admin() && admin>0)){ + if(cle->Anon()==0 || (admin>=cle->Admin() && admin> AccountStatus::Player)){ plclass_=cle->class_(); pllevel=cle->level(); - if(admin>=100) + if(admin>=AccountStatus::GMAdmin) pidstring=5003; plrace=cle->race(); zonestring=5006; @@ -661,7 +734,7 @@ void ClientList::SendWhoAll(uint32 fromid,const char* to, int16 admin, Who_All_S } - if(admin>=cle->Admin() && admin>0) + if(admin>=cle->Admin() && admin > AccountStatus::Player) unknown80[0]=cle->Admin(); else unknown80[0]=0xFFFFFFFF; @@ -674,7 +747,7 @@ void ClientList::SendWhoAll(uint32 fromid,const char* to, int16 admin, Who_All_S strcpy(plname,cle->name()); char placcount[30]={0}; - if(admin>=cle->Admin() && admin>0) + if(admin>=cle->Admin() && admin > AccountStatus::Player) strcpy(placcount,cle->AccountName()); memcpy(bufptr,&formatstring, sizeof(uint32)); @@ -959,7 +1032,7 @@ void ClientList::ConsoleSendWhoAll(const char* to, int16 admin, Who_All_Struct* if ( (cle->Online() >= CLE_Status::Zoning) && (whom == 0 || ( - ((cle->Admin() >= 80 && cle->GetGM()) || whom->gmlookup == 0xFFFF) && + ((cle->Admin() >= AccountStatus::QuestTroupe && cle->GetGM()) || whom->gmlookup == 0xFFFF) && (whom->lvllow == 0xFFFF || (cle->level() >= whom->lvllow && cle->level() <= whom->lvlhigh)) && (whom->wclass == 0xFFFF || cle->class_() == whom->wclass) && (whom->wrace == 0xFFFF || cle->race() == whom->wrace) && @@ -967,41 +1040,41 @@ void ClientList::ConsoleSendWhoAll(const char* to, int16 admin, Who_All_Struct* (tmpZone != 0 && strncasecmp(tmpZone, whom->whom, whomlen) == 0) || strncasecmp(cle->name(), whom->whom, whomlen) == 0 || (strncasecmp(guild_mgr.GetGuildName(cle->GuildID()), whom->whom, whomlen) == 0) || - (admin >= 100 && strncasecmp(cle->AccountName(), whom->whom, whomlen) == 0) + (admin >= AccountStatus::GMAdmin && strncasecmp(cle->AccountName(), whom->whom, whomlen) == 0) )) )) ) { line[0] = 0; // MYRA - use new (5.x) Status labels in who for telnet connection - if (cle->Admin() >= 250) + if (cle->Admin() >= AccountStatus::GMImpossible) strcpy(tmpgm, "* GM-Impossible * "); - else if (cle->Admin() >= 200) + else if (cle->Admin() >= AccountStatus::GMMgmt) strcpy(tmpgm, "* GM-Mgmt * "); - else if (cle->Admin() >= 180) + else if (cle->Admin() >= AccountStatus::GMCoder) strcpy(tmpgm, "* GM-Coder * "); - else if (cle->Admin() >= 170) + else if (cle->Admin() >= AccountStatus::GMAreas) strcpy(tmpgm, "* GM-Areas * "); - else if (cle->Admin() >= 160) + else if (cle->Admin() >= AccountStatus::QuestMaster) strcpy(tmpgm, "* QuestMaster * "); - else if (cle->Admin() >= 150) + else if (cle->Admin() >= AccountStatus::GMLeadAdmin) strcpy(tmpgm, "* GM-Lead Admin * "); - else if (cle->Admin() >= 100) + else if (cle->Admin() >= AccountStatus::GMAdmin) strcpy(tmpgm, "* GM-Admin * "); - else if (cle->Admin() >= 95) + else if (cle->Admin() >= AccountStatus::GMStaff) strcpy(tmpgm, "* GM-Staff * "); - else if (cle->Admin() >= 90) + else if (cle->Admin() >= AccountStatus::EQSupport) strcpy(tmpgm, "* EQ Support * "); - else if (cle->Admin() >= 85) + else if (cle->Admin() >= AccountStatus::GMTester) strcpy(tmpgm, "* GM-Tester * "); - else if (cle->Admin() >= 81) + else if (cle->Admin() >= AccountStatus::SeniorGuide) strcpy(tmpgm, "* Senior Guide * "); - else if (cle->Admin() >= 80) + else if (cle->Admin() >= AccountStatus::QuestTroupe) strcpy(tmpgm, "* QuestTroupe * "); - else if (cle->Admin() >= 50) + else if (cle->Admin() >= AccountStatus::Guide) strcpy(tmpgm, "* Guide * "); - else if (cle->Admin() >= 20) + else if (cle->Admin() >= AccountStatus::ApprenticeGuide) strcpy(tmpgm, "* Apprentice Guide * "); - else if (cle->Admin() >= 10) + else if (cle->Admin() >= AccountStatus::Steward) strcpy(tmpgm, "* Steward * "); else tmpgm[0] = 0; @@ -1018,16 +1091,16 @@ void ClientList::ConsoleSendWhoAll(const char* to, int16 admin, Who_All_Struct* else LFG[0] = 0; - if (admin >= 150 && admin >= cle->Admin()) { + if (admin >= AccountStatus::GMLeadAdmin && admin >= cle->Admin()) { sprintf(accinfo, " AccID: %i AccName: %s LSID: %i Status: %i", cle->AccountID(), cle->AccountName(), cle->LSAccountID(), cle->Admin()); } else accinfo[0] = 0; if (cle->Anon() == 2) { // Roleplay - if (admin >= 100 && admin >= cle->Admin()) + if (admin >= AccountStatus::GMAdmin && admin >= cle->Admin()) sprintf(line, " %s[RolePlay %i %s] %s (%s)%s zone: %s%s%s", tmpgm, cle->level(), GetClassIDName(cle->class_(), cle->level()), cle->name(), GetRaceIDName(cle->race()), tmpguild, tmpZone, LFG, accinfo); - else if (cle->Admin() >= 80 && admin < 80 && cle->GetGM()) { + else if (cle->Admin() >= AccountStatus::QuestTroupe && admin < AccountStatus::QuestTroupe && cle->GetGM()) { iterator.Advance(); continue; } @@ -1035,9 +1108,9 @@ void ClientList::ConsoleSendWhoAll(const char* to, int16 admin, Who_All_Struct* sprintf(line, " %s[ANONYMOUS] %s%s%s%s", tmpgm, cle->name(), tmpguild, LFG, accinfo); } else if (cle->Anon() == 1) { // Anon - if (admin >= 100 && admin >= cle->Admin()) + if (admin >= AccountStatus::GMAdmin && admin >= cle->Admin()) sprintf(line, " %s[ANON %i %s] %s (%s)%s zone: %s%s%s", tmpgm, cle->level(), GetClassIDName(cle->class_(), cle->level()), cle->name(), GetRaceIDName(cle->race()), tmpguild, tmpZone, LFG, accinfo); - else if (cle->Admin() >= 80 && cle->GetGM()) { + else if (cle->Admin() >= AccountStatus::QuestTroupe && cle->GetGM()) { iterator.Advance(); continue; } @@ -1050,7 +1123,13 @@ void ClientList::ConsoleSendWhoAll(const char* to, int16 admin, Who_All_Struct* fmt::format_to(out, line); if (out.size() >= 3584) { auto output = fmt::to_string(out); - connection->SendEmoteMessageRaw(to, 0, 0, 10, output.c_str()); + connection->SendEmoteMessageRaw( + to, + 0, + AccountStatus::Player, + Chat::NPCQuestSay, + output.c_str() + ); out.clear(); } else { @@ -1060,17 +1139,17 @@ void ClientList::ConsoleSendWhoAll(const char* to, int16 admin, Who_All_Struct* fmt::format_to(out, "\n"); } x++; - if (x >= 20 && admin < 80) + if (x >= 20 && admin < AccountStatus::QuestTroupe) break; } iterator.Advance(); } - if (x >= 20 && admin < 80) + if (x >= 20 && admin < AccountStatus::QuestTroupe) fmt::format_to(out, "too many results...20 players shown"); else fmt::format_to(out, "{} players online", x); - if (admin >= 150 && (whom == 0 || whom->gmlookup != 0xFFFF)) { + if (admin >= AccountStatus::GMAdmin && (whom == 0 || whom->gmlookup != 0xFFFF)) { if (connection->IsConsole()) fmt::format_to(out, "\r\n"); else @@ -1079,7 +1158,13 @@ void ClientList::ConsoleSendWhoAll(const char* to, int16 admin, Who_All_Struct* //console_list.SendConsoleWho(connection, to, admin, &output, &outsize, &outlen); } auto output = fmt::to_string(out); - connection->SendEmoteMessageRaw(to, 0, 0, 10, output.c_str()); + connection->SendEmoteMessageRaw( + to, + 0, + AccountStatus::Player, + Chat::NPCQuestSay, + output.c_str() + ); } void ClientList::Add(Client* client) { @@ -1244,7 +1329,7 @@ void ClientList::RemoveCLEByLSID(uint32 iLSID) bool ClientList::IsAccountInGame(uint32 iLSID) { LinkedListIterator iterator(clientlist); - + iterator.Reset(); while (iterator.MoreElements()) { if (iterator.GetData()->LSID() == iLSID && iterator.GetData()->Online() == CLE_Status::InZone) { return true; @@ -1282,71 +1367,67 @@ void ClientList::GetClients(const char *zone_name, std::vector unique_ips; + std::map client_count = { + { EQ::versions::ClientVersion::Titanium, 0 }, + { EQ::versions::ClientVersion::SoF, 0 }, + { EQ::versions::ClientVersion::SoD, 0 }, + { EQ::versions::ClientVersion::UF, 0 }, + { EQ::versions::ClientVersion::RoF, 0 }, + { EQ::versions::ClientVersion::RoF2, 0 } + }; LinkedListIterator Iterator(clientlist); - Iterator.Reset(); - - while(Iterator.MoreElements()) - { + while (Iterator.MoreElements()) { ClientListEntry* CLE = Iterator.GetData(); + if (CLE && CLE->zone()) { + auto client_version = CLE->GetClientVersion(); + if ( + client_version >= (uint8) EQ::versions::ClientVersion::Titanium && + client_version <= (uint8) EQ::versions::ClientVersion::RoF2 + ) { + client_count[(EQ::versions::ClientVersion)client_version]++; + } - if(CLE && CLE->zone()) - { - switch(CLE->GetClientVersion()) - { - case 1: - { - break; - } - case 2: - { - ++ClientTitaniumCount; - break; - } - case 3: - { - ++ClientSoFCount; - break; - } - case 4: - { - ++ClientSoDCount; - break; - } - case 5: - { - ++ClientUnderfootCount; - break; - } - case 6: - { - ++ClientRoFCount; - break; - } - case 7: - { - ++ClientRoF2Count; - break; - } - default: - break; + if (std::find(unique_ips.begin(), unique_ips.begin(), CLE->GetIP()) == unique_ips.end()) { + unique_ips.push_back(CLE->GetIP()); } } Iterator.Advance(); - } - zoneserver_list.SendEmoteMessage(Name, 0, 0, 13, "There are %i Titanium, %i SoF, %i SoD, %i UF, %i RoF, %i RoF2 clients currently connected.", - ClientTitaniumCount, ClientSoFCount, ClientSoDCount, ClientUnderfootCount, ClientRoFCount, ClientRoF2Count); + uint32 total_clients = ( + client_count[EQ::versions::ClientVersion::Titanium] + + client_count[EQ::versions::ClientVersion::SoF] + + client_count[EQ::versions::ClientVersion::SoD] + + client_count[EQ::versions::ClientVersion::UF] + + client_count[EQ::versions::ClientVersion::RoF] + + client_count[EQ::versions::ClientVersion::RoF2] + ); + + zoneserver_list.SendEmoteMessage( + Name, + 0, + AccountStatus::Player, + Chat::White, + fmt::format( + "There {} {} Titanium, {} SoF, {} SoD, {} UF, {} RoF, and {} RoF2 Client{} currently connected for a total of {} Client{} and {} Unique IP{} connected.", + (total_clients != 1 ? "are" : "is"), + client_count[EQ::versions::ClientVersion::Titanium], + client_count[EQ::versions::ClientVersion::SoF], + client_count[EQ::versions::ClientVersion::SoD], + client_count[EQ::versions::ClientVersion::UF], + client_count[EQ::versions::ClientVersion::RoF], + client_count[EQ::versions::ClientVersion::RoF2], + (total_clients != 1 ? "s" : ""), + total_clients, + (total_clients != 1 ? "s" : ""), + unique_ips.size(), + (unique_ips.size() != 1 ? "s" : "") + ).c_str() + ); } void ClientList::OnTick(EQ::Timer *t) @@ -1508,3 +1589,75 @@ void ClientList::GetClientList(Json::Value &response) Iterator.Advance(); } } + +void ClientList::SendCharacterMessage(uint32_t character_id, int chat_type, const std::string& message) +{ + auto character = FindCLEByCharacterID(character_id); + SendCharacterMessage(character, chat_type, message); +} + +void ClientList::SendCharacterMessage(const std::string& character_name, int chat_type, const std::string& message) +{ + auto character = FindCharacter(character_name.c_str()); + SendCharacterMessage(character, chat_type, message); +} + +void ClientList::SendCharacterMessage(ClientListEntry* character, int chat_type, const std::string& message) +{ + if (!character || !character->Server()) + { + return; + } + + uint32_t pack_size = sizeof(CZMessage_Struct); + auto pack = std::make_unique(ServerOP_CZMessage, pack_size); + auto buf = reinterpret_cast(pack->pBuffer); + buf->update_type = CZUpdateType_ClientName; + buf->update_identifier = 0; + buf->type = chat_type; + strn0cpy(buf->message, message.c_str(), sizeof(buf->message)); + strn0cpy(buf->client_name, character->name(), sizeof(buf->client_name)); + + character->Server()->SendPacket(pack.get()); +} + +void ClientList::SendCharacterMessageID(uint32_t character_id, + int chat_type, int eqstr_id, std::initializer_list args) +{ + auto character = FindCLEByCharacterID(character_id); + SendCharacterMessageID(character, chat_type, eqstr_id, args); +} + +void ClientList::SendCharacterMessageID(const std::string& character_name, + int chat_type, int eqstr_id, std::initializer_list args) +{ + auto character = FindCharacter(character_name.c_str()); + SendCharacterMessageID(character, chat_type, eqstr_id, args); +} + +void ClientList::SendCharacterMessageID(ClientListEntry* character, + int chat_type, int eqstr_id, std::initializer_list args) +{ + if (!character || !character->Server()) + { + return; + } + + SerializeBuffer serialized_args; + for (const auto& arg : args) + { + serialized_args.WriteString(arg); + } + + uint32_t args_size = static_cast(serialized_args.size()); + uint32_t pack_size = sizeof(CZClientMessageString_Struct) + args_size; + auto pack = std::make_unique(ServerOP_CZClientMessageString, pack_size); + auto buf = reinterpret_cast(pack->pBuffer); + buf->string_id = eqstr_id; + buf->chat_type = chat_type; + strn0cpy(buf->client_name, character->name(), sizeof(buf->client_name)); + buf->args_size = args_size; + memcpy(buf->args, serialized_args.buffer(), serialized_args.size()); + + character->Server()->SendPacket(pack.get()); +} diff --git a/world/clientlist.h b/world/clientlist.h index 3386a6f14..2f9583784 100644 --- a/world/clientlist.h +++ b/world/clientlist.h @@ -62,7 +62,7 @@ public: void DisconnectByIP(uint32 iIP); void CLCheckStale(); void CLEKeepAlive(uint32 numupdates, uint32* wid); - void CLEAdd(uint32 iLSID, const char* iLoginServerName, const char* iLoginName, const char* iLoginKey, int16 iWorldAdmin = 0, uint32 ip = 0, uint8 local=0); + void CLEAdd(uint32 iLSID, const char* iLoginServerName, const char* iLoginName, const char* iLoginKey, int16 iWorldAdmin = AccountStatus::Player, uint32 ip = 0, uint8 local=0); void UpdateClientGuild(uint32 char_id, uint32 guild_id); void RemoveCLEByLSID(uint32 iLSID); bool IsAccountInGame(uint32 iLSID); @@ -72,6 +72,13 @@ public: void GetClientList(Json::Value &response); + void SendCharacterMessage(uint32_t character_id, int chat_type, const std::string& message); + void SendCharacterMessage(const std::string& character_name, int chat_type, const std::string& message); + void SendCharacterMessage(ClientListEntry* character, int chat_type, const std::string& message); + void SendCharacterMessageID(uint32_t character_id, int chat_type, int eqstr_id, std::initializer_list args = {}); + void SendCharacterMessageID(const std::string& character_name, int chat_type, int eqstr_id, std::initializer_list args = {}); + void SendCharacterMessageID(ClientListEntry* character, int chat_type, int eqstr_id, std::initializer_list args = {}); + private: void OnTick(EQ::Timer *t); inline uint32 GetNextCLEID() { return NextCLEID++; } diff --git a/world/console.cpp b/world/console.cpp index 6a4ffeff4..2fa162b98 100644 --- a/world/console.cpp +++ b/world/console.cpp @@ -280,20 +280,33 @@ void ConsoleEmote( join_args.erase(join_args.begin(), join_args.begin() + 2); if (strcasecmp(args[0].c_str(), "world") == 0) { - zoneserver_list.SendEmoteMessageRaw(0, 0, 0, atoi(args[1].c_str()), JoinString(join_args, " ").c_str()); + zoneserver_list.SendEmoteMessageRaw( + 0, + 0, + AccountStatus::Player, + atoi(args[1].c_str()), + JoinString(join_args, " ").c_str() + ); } else { ZoneServer *zs = zoneserver_list.FindByName(args[0].c_str()); if (zs != 0) { - zs->SendEmoteMessageRaw(0, 0, 0, atoi(args[1].c_str()), JoinString(join_args, " ").c_str()); + zs->SendEmoteMessageRaw( + 0, + 0, + AccountStatus::Player, + atoi(args[1].c_str()), + JoinString(join_args, " ").c_str() + ); } else { zoneserver_list.SendEmoteMessageRaw( args[0].c_str(), 0, - 0, + AccountStatus::Player, atoi(args[1].c_str()), - JoinString(join_args, " ").c_str()); + JoinString(join_args, " ").c_str() + ); } } } @@ -637,7 +650,16 @@ void ConsoleZoneLock( uint16 tmp = ZoneID(args[1].c_str()); if (tmp) { if (zoneserver_list.SetLockedZone(tmp, true)) { - zoneserver_list.SendEmoteMessage(0, 0, 80, 15, "Zone locked: %s", ZoneName(tmp)); + zoneserver_list.SendEmoteMessage( + 0, + 0, + AccountStatus::QuestTroupe, + Chat::Yellow, + fmt::format( + "Zone locked: {}", + ZoneName(tmp) + ).c_str() + ); } else { connection->SendLine("Failed to change lock"); @@ -655,7 +677,16 @@ void ConsoleZoneLock( uint16 tmp = ZoneID(args[1].c_str()); if (tmp) { if (zoneserver_list.SetLockedZone(tmp, false)) { - zoneserver_list.SendEmoteMessage(0, 0, 80, 15, "Zone unlocked: %s", ZoneName(tmp)); + zoneserver_list.SendEmoteMessage( + 0, + 0, + AccountStatus::QuestTroupe, + Chat::Yellow, + fmt::format( + "Zone unlocked: {}", + ZoneName(tmp) + ).c_str() + ); } else { connection->SendLine("Failed to change lock"); @@ -778,8 +809,14 @@ void ConsoleWorldShutdown( zoneserver_list.WorldShutDown(0, 0); } else if (strcasecmp(args[0].c_str(), "disable") == 0) { - connection->SendLine(":SYSTEM MSG:World shutdown aborted."); - zoneserver_list.SendEmoteMessage(0, 0, 0, 15, ":SYSTEM MSG:World shutdown aborted."); + connection->SendLine("[SYSTEM] World shutdown has been aborted."); + zoneserver_list.SendEmoteMessage( + 0, + 0, + AccountStatus::Player, + Chat::Yellow, + "[SYSTEM] World shutdown has been aborted." + ); zoneserver_list.shutdowntimer->Disable(); zoneserver_list.reminder->Disable(); } @@ -825,14 +862,15 @@ void ConsoleSignalCharByName( } connection->SendLine(StringFormat("Signal Sent to %s with ID %i", (char *) args[0].c_str(), atoi(args[1].c_str()))); - uint32 message_len = strlen((char *) args[0].c_str()) + 1; - auto pack = new ServerPacket( - ServerOP_CZSignalClientByName, - sizeof(CZClientSignalByName_Struct) + message_len - ); - CZClientSignalByName_Struct *CZSC = (CZClientSignalByName_Struct *) pack->pBuffer; - strn0cpy(CZSC->character_name, (char *) args[0].c_str(), 64); - CZSC->signal = atoi(args[1].c_str()); + uint32 message_len = strlen((char *) args[0].c_str()) + 1; + auto pack = new ServerPacket(ServerOP_CZSignal, sizeof(CZSignal_Struct) + message_len); + CZSignal_Struct* CZS = (CZSignal_Struct*) pack->pBuffer; + uint8 update_type = CZUpdateType_ClientName; + int update_identifier = 0; + CZS->update_type = update_type; + CZS->update_identifier = update_identifier; + CZS->signal = atoi(args[1].c_str()); + strn0cpy(CZS->client_name, (char *) args[0].c_str(), 64); zoneserver_list.SendPacket(pack); safe_delete(pack); } diff --git a/world/console.old.cpp b/world/console.old.cpp index d7589d6eb..0bc08899c 100644 --- a/world/console.old.cpp +++ b/world/console.old.cpp @@ -481,10 +481,10 @@ void Console::ProcessCommand(const char* command) { SendMessage(1, " version"); SendMessage(1, " worldshutdown"); } - if (admin >= 201) { + if (admin >= AccountStatus::GMMgmt) { SendMessage(1, " IPLookup [name]"); } - if (admin >= 100) { + if (admin >= AccountStatus::GMAdmin) { SendMessage(1, " LSReconnect"); SendMessage(1, " signalcharbyname charname ID"); SendMessage(1, " reloadworld"); @@ -750,8 +750,20 @@ void Console::ProcessCommand(const char* command) { zoneserver_list.WorldShutDown(0, 0); } else if(strcasecmp(sep.arg[1], "disable") == 0) { - SendEmoteMessage(0,0,0,15,":SYSTEM MSG:World shutdown aborted."); - zoneserver_list.SendEmoteMessage(0,0,0,15,":SYSTEM MSG:World shutdown aborted."); + SendEmoteMessage( + 0, + 0, + 0, + Chat::Yellow, + "[SYSTEM] World shutdown has been aborted." + ); + zoneserver_list.SendEmoteMessage( + 0, + 0, + 0, + Chat::Yellow, + "[SYSTEM] World shutdown has been aborted." + ); zoneserver_list.shutdowntimer->Disable(); zoneserver_list.reminder->Disable(); } @@ -787,7 +799,7 @@ void Console::ProcessCommand(const char* command) { SendMessage(1, " Compiled on: %s at %s", COMPILE_DATE, COMPILE_TIME); SendMessage(1, " Last modified on: %s", LAST_MODIFIED); } - else if (strcasecmp(sep.arg[0], "serverinfo") == 0 && admin >= 200) { + else if (strcasecmp(sep.arg[0], "serverinfo") == 0 && admin >= AccountStatus::GMMgmt) { if (strcasecmp(sep.arg[1], "os") == 0) { #ifdef _WINDOWS GetOS(); @@ -809,10 +821,10 @@ void Console::ProcessCommand(const char* command) { SendMessage(1, " OS - Operating system version information."); } } - else if (strcasecmp(sep.arg[0], "IPLookup") == 0 && admin >= 201) { + else if (strcasecmp(sep.arg[0], "IPLookup") == 0 && admin >= AccountStatus::GMMgmt) { client_list.SendCLEList(admin, 0, this, sep.argplus[1]); } - else if (strcasecmp(sep.arg[0], "LSReconnect") == 0 && admin >= 100) { + else if (strcasecmp(sep.arg[0], "LSReconnect") == 0 && admin >= AccountStatus::GMAdmin) { #ifdef _WINDOWS _beginthread(AutoInitLoginServer, 0, nullptr); #else @@ -827,7 +839,7 @@ void Console::ProcessCommand(const char* command) { if (strcasecmp(sep.arg[1], "list") == 0) { zoneserver_list.ListLockedZones(0, this); } - else if (strcasecmp(sep.arg[1], "lock") == 0 && admin >= 101) { + else if (strcasecmp(sep.arg[1], "lock") == 0 && admin >= AccountStatus::GMAdmin) { uint16 tmp = ZoneID(sep.arg[2]); if (tmp) { if (zoneserver_list.SetLockedZone(tmp, true)) @@ -838,7 +850,7 @@ void Console::ProcessCommand(const char* command) { else SendMessage(1, "Usage: #zonelock lock [zonename]"); } - else if (strcasecmp(sep.arg[1], "unlock") == 0 && admin >= 101) { + else if (strcasecmp(sep.arg[1], "unlock") == 0 && admin >= AccountStatus::GMAdmin) { uint16 tmp = ZoneID(sep.arg[2]); if (tmp) { if (zoneserver_list.SetLockedZone(tmp, false)) @@ -852,13 +864,13 @@ void Console::ProcessCommand(const char* command) { else { SendMessage(1, "#zonelock sub-commands"); SendMessage(1, " list"); - if (admin >= 101) { + if (admin >= AccountStatus::GMAdmin) { SendMessage(1, " lock [zonename]"); SendMessage(1, " unlock [zonename]"); } } } - else if (strcasecmp(sep.arg[0], "reloadworld") == 0 && admin > 101) + else if (strcasecmp(sep.arg[0], "reloadworld") == 0 && admin > AccountStatus::GMAdmin) { SendEmoteMessage(0,0,0,15,"Reloading World..."); auto pack = new ServerPacket(ServerOP_ReloadWorld, sizeof(ReloadWorld_Struct)); diff --git a/world/dynamic_zone.cpp b/world/dynamic_zone.cpp new file mode 100644 index 000000000..945b41ec9 --- /dev/null +++ b/world/dynamic_zone.cpp @@ -0,0 +1,374 @@ +#include "dynamic_zone.h" +#include "cliententry.h" +#include "clientlist.h" +#include "dynamic_zone_manager.h" +#include "worlddb.h" +#include "zonelist.h" +#include "zoneserver.h" +#include "../common/eqemu_logsys.h" +#include "../common/repositories/instance_list_repository.h" + +extern ClientList client_list; +extern ZSList zoneserver_list; + +Database& DynamicZone::GetDatabase() +{ + return database; +} + +bool DynamicZone::SendServerPacket(ServerPacket* packet) +{ + return zoneserver_list.SendPacket(packet); +} + +DynamicZone* DynamicZone::FindDynamicZoneByID(uint32_t dz_id) +{ + auto dz = dynamic_zone_manager.dynamic_zone_cache.find(dz_id); + if (dz != dynamic_zone_manager.dynamic_zone_cache.end()) + { + return dz->second.get(); + } + return nullptr; +} + +void DynamicZone::ChooseNewLeader() +{ + if (m_members.empty() || !m_choose_leader_cooldown_timer.Check()) + { + m_choose_leader_needed = true; + return; + } + + auto it = std::find_if(m_members.begin(), m_members.end(), [&](const DynamicZoneMember& member) { + if (member.id != GetLeaderID() && member.IsOnline()) { + auto member_cle = client_list.FindCLEByCharacterID(member.id); + return (member_cle && member_cle->GetOnline() == CLE_Status::InZone); + } + return false; + }); + + if (it == m_members.end()) + { + // no online members found, fallback to choosing any member + it = std::find_if(m_members.begin(), m_members.end(), + [&](const DynamicZoneMember& member) { return member.id != GetLeaderID(); }); + } + + if (it != m_members.end() && SetNewLeader(it->id)) + { + m_choose_leader_needed = false; + } +} + +bool DynamicZone::SetNewLeader(uint32_t member_id) +{ + auto new_leader = GetMemberData(member_id); + if (!new_leader.IsValid()) + { + return false; + } + + LogDynamicZonesDetail("Replacing dz [{}] leader [{}] with [{}]", GetID(), GetLeaderName(), new_leader.name); + SetLeader(new_leader, true); + SendZonesLeaderChanged(); + return true; +} + +void DynamicZone::CheckLeader() +{ + if (m_choose_leader_needed) + { + ChooseNewLeader(); + } +} + +DynamicZoneStatus DynamicZone::Process() +{ + DynamicZoneStatus status = DynamicZoneStatus::Normal; + + // force expire if no members + if (!HasMembers() || IsExpired()) + { + status = DynamicZoneStatus::Expired; + + auto dz_zoneserver = zoneserver_list.FindByInstanceID(GetInstanceID()); + if (!dz_zoneserver || dz_zoneserver->NumPlayers() == 0) // no clients inside dz + { + status = DynamicZoneStatus::ExpiredEmpty; + + if (!HasMembers() && !m_is_pending_early_shutdown && RuleB(DynamicZone, EmptyShutdownEnabled)) + { + SetSecondsRemaining(RuleI(DynamicZone, EmptyShutdownDelaySeconds)); + m_is_pending_early_shutdown = true; + } + } + } + + if (GetType() == DynamicZoneType::Expedition && status != DynamicZoneStatus::ExpiredEmpty) + { + CheckExpireWarning(); + CheckLeader(); + } + + return status; +} + +void DynamicZone::SendZonesDynamicZoneDeleted() +{ + uint32_t pack_size = sizeof(ServerDzID_Struct); + auto pack = std::make_unique(ServerOP_DzDeleted, pack_size); + auto buf = reinterpret_cast(pack->pBuffer); + buf->dz_id = GetID(); + zoneserver_list.SendPacket(pack.get()); +} + +void DynamicZone::SetSecondsRemaining(uint32_t seconds_remaining) +{ + auto now = std::chrono::system_clock::now(); + auto new_remaining = std::chrono::seconds(seconds_remaining); + + auto current_remaining = m_expire_time - now; + if (current_remaining > new_remaining) // reduce only + { + LogDynamicZonesDetail("Updating dynamic zone [{}] instance [{}] seconds remaining to [{}]s", + GetID(), GetInstanceID(), seconds_remaining); + + // preserve original start time and adjust duration instead + m_expire_time = now + new_remaining; + m_duration = std::chrono::duration_cast(m_expire_time - m_start_time); + + InstanceListRepository::UpdateDuration(database, + GetInstanceID(), static_cast(m_duration.count())); + + SendZonesDurationUpdate(); // update zone caches and actual instance's timer + } +} + +void DynamicZone::SendZonesDurationUpdate() +{ + constexpr uint32_t packsize = sizeof(ServerDzSetDuration_Struct); + auto pack = std::make_unique(ServerOP_DzDurationUpdate, packsize); + auto packbuf = reinterpret_cast(pack->pBuffer); + packbuf->dz_id = GetID(); + packbuf->seconds = static_cast(m_duration.count()); + zoneserver_list.SendPacket(pack.get()); +} + +void DynamicZone::SendZonesLeaderChanged() +{ + uint32_t pack_size = sizeof(ServerDzLeaderID_Struct); + auto pack = std::make_unique(ServerOP_DzLeaderChanged, pack_size); + auto buf = reinterpret_cast(pack->pBuffer); + buf->dz_id = GetID(); + buf->leader_id = GetLeaderID(); + zoneserver_list.SendPacket(pack.get()); +} + +void DynamicZone::HandleZoneMessage(ServerPacket* pack) +{ + switch (pack->opcode) + { + case ServerOP_DzCreated: + { + dynamic_zone_manager.CacheNewDynamicZone(pack); + break; + } + case ServerOP_DzSetCompass: + case ServerOP_DzSetSafeReturn: + case ServerOP_DzSetZoneIn: + { + auto buf = reinterpret_cast(pack->pBuffer); + auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id); + if (dz) + { + if (pack->opcode == ServerOP_DzSetCompass) + { + dz->SetCompass(buf->zone_id, buf->x, buf->y, buf->z, false); + } + else if (pack->opcode == ServerOP_DzSetSafeReturn) + { + dz->SetSafeReturn(buf->zone_id, buf->x, buf->y, buf->z, buf->heading, false); + } + else if (pack->opcode == ServerOP_DzSetZoneIn) + { + dz->SetZoneInLocation(buf->x, buf->y, buf->z, buf->heading, false); + } + } + zoneserver_list.SendPacket(pack); + break; + } + case ServerOP_DzAddRemoveMember: + { + auto buf = reinterpret_cast(pack->pBuffer); + auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id); + if (dz) + { + auto status = static_cast(buf->character_status); + dz->ProcessMemberAddRemove({ buf->character_id, buf->character_name, status }, buf->removed); + } + zoneserver_list.SendPacket(pack); + break; + } + case ServerOP_DzSwapMembers: + { + auto buf = reinterpret_cast(pack->pBuffer); + auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id); + if (dz) + { + // we add first in world so new member can be chosen if leader is removed + auto status = static_cast(buf->add_character_status); + dz->ProcessMemberAddRemove({ buf->add_character_id, buf->add_character_name, status }, false); + dz->ProcessMemberAddRemove({ buf->remove_character_id, buf->remove_character_name }, true); + } + zoneserver_list.SendPacket(pack); + break; + } + case ServerOP_DzRemoveAllMembers: + { + auto buf = reinterpret_cast(pack->pBuffer); + auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id); + if (dz) + { + dz->ProcessRemoveAllMembers(); + } + zoneserver_list.SendPacket(pack); + break; + } + case ServerOP_DzSetSecondsRemaining: + { + auto buf = reinterpret_cast(pack->pBuffer); + auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id); + if (dz) + { + dz->SetSecondsRemaining(buf->seconds); + } + break; + } + case ServerOP_DzGetMemberStatuses: + { + auto buf = reinterpret_cast(pack->pBuffer); + auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id); + if (dz) + { + dz->SendZoneMemberStatuses(buf->sender_zone_id, buf->sender_instance_id); + } + break; + } + case ServerOP_DzUpdateMemberStatus: + { + auto buf = reinterpret_cast(pack->pBuffer); + auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id); + if (dz) + { + auto status = static_cast(buf->status); + dz->ProcessMemberStatusChange(buf->character_id, status); + } + zoneserver_list.SendPacket(pack); + break; + } + }; +} + +void DynamicZone::ProcessMemberAddRemove(const DynamicZoneMember& member, bool removed) +{ + DynamicZoneBase::ProcessMemberAddRemove(member, removed); + + if (GetType() == DynamicZoneType::Expedition && removed && member.id == GetLeaderID()) + { + ChooseNewLeader(); + } +} + +bool DynamicZone::ProcessMemberStatusChange(uint32_t character_id, DynamicZoneMemberStatus status) +{ + bool changed = DynamicZoneBase::SetInternalMemberStatus(character_id, status); + if (changed && GetType() == DynamicZoneType::Expedition) + { + // any member status update will trigger a leader fix if leader was offline + if (GetLeader().status == DynamicZoneMemberStatus::Offline && GetMemberCount() > 1) + { + ChooseNewLeader(); + } + } + return changed; +} + +void DynamicZone::SendZonesExpireWarning(uint32_t minutes_remaining) +{ + uint32_t pack_size = sizeof(ServerDzExpireWarning_Struct); + auto pack = std::make_unique(ServerOP_DzExpireWarning, pack_size); + auto buf = reinterpret_cast(pack->pBuffer); + buf->dz_id = GetID(); + buf->minutes_remaining = minutes_remaining; + zoneserver_list.SendPacket(pack.get()); +} + +void DynamicZone::SendZoneMemberStatuses(uint16_t zone_id, uint16_t instance_id) +{ + uint32_t members_count = static_cast(m_members.size()); + uint32_t entries_size = sizeof(ServerDzMemberStatusEntry_Struct) * members_count; + uint32_t pack_size = sizeof(ServerDzMemberStatuses_Struct) + entries_size; + auto pack = std::make_unique(ServerOP_DzGetMemberStatuses, pack_size); + auto buf = reinterpret_cast(pack->pBuffer); + buf->dz_id = GetID(); + buf->count = members_count; + + for (int i = 0; i < m_members.size(); ++i) + { + buf->entries[i].character_id = m_members[i].id; + buf->entries[i].online_status = static_cast(m_members[i].status); + } + + zoneserver_list.SendPacket(zone_id, instance_id, pack.get()); +} + +void DynamicZone::CacheMemberStatuses() +{ + if (m_has_member_statuses) + { + return; + } + + // called when a new dz is cached to fill member statuses + std::string zone_name{}; + std::vector all_clients; + all_clients.reserve(client_list.GetClientCount()); + client_list.GetClients(zone_name.c_str(), all_clients); + + for (const auto& member : m_members) + { + auto it = std::find_if(all_clients.begin(), all_clients.end(), + [&](const ClientListEntry* cle) { return (cle && cle->CharID() == member.id); }); + + auto status = DynamicZoneMemberStatus::Offline; + if (it != all_clients.end()) + { + status = DynamicZoneMemberStatus::Online; + if (IsSameDz((*it)->zone(), (*it)->instance())) + { + status = DynamicZoneMemberStatus::InDynamicZone; + } + } + + SetInternalMemberStatus(member.id, status); + } + + m_has_member_statuses = true; +} + +void DynamicZone::CheckExpireWarning() +{ + if (m_warning_cooldown_timer.Check(false)) + { + using namespace std::chrono_literals; + auto remaining = GetDurationRemaining(); + if ((remaining > 14min && remaining < 15min) || + (remaining > 4min && remaining < 5min) || + (remaining > 0min && remaining < 1min)) + { + int minutes = std::chrono::duration_cast(remaining).count() + 1; + SendZonesExpireWarning(minutes); + m_warning_cooldown_timer.Start(120000); // 2 minute cooldown after a warning + } + } +} diff --git a/world/dynamic_zone.h b/world/dynamic_zone.h new file mode 100644 index 000000000..6a3d5c953 --- /dev/null +++ b/world/dynamic_zone.h @@ -0,0 +1,57 @@ +#ifndef WORLD_DYNAMIC_ZONE_H +#define WORLD_DYNAMIC_ZONE_H + +#include "../common/dynamic_zone_base.h" +#include "../common/rulesys.h" +#include "../common/timer.h" + +class Database; +class ServerPacket; + +enum class DynamicZoneStatus +{ + Unknown = 0, + Normal, + Expired, + ExpiredEmpty, +}; + +class DynamicZone : public DynamicZoneBase +{ +public: + using DynamicZoneBase::DynamicZoneBase; // inherit base constructors + + static DynamicZone* FindDynamicZoneByID(uint32_t dz_id); + static void HandleZoneMessage(ServerPacket* pack); + + void SetSecondsRemaining(uint32_t seconds_remaining) override; + + void CacheMemberStatuses(); + DynamicZoneStatus Process(); + bool SetNewLeader(uint32_t member_id); + +protected: + Database& GetDatabase() override; + void ProcessMemberAddRemove(const DynamicZoneMember& member, bool removed) override; + bool ProcessMemberStatusChange(uint32_t member_id, DynamicZoneMemberStatus status) override; + bool SendServerPacket(ServerPacket* packet) override; + +private: + friend class DynamicZoneManager; + + void CheckExpireWarning(); + void CheckLeader(); + void ChooseNewLeader(); + void SendZoneMemberStatuses(uint16_t zone_id, uint16_t instance_id); + void SendZonesDurationUpdate(); + void SendZonesDynamicZoneDeleted(); + void SendZonesExpireWarning(uint32_t minutes_remaining); + void SendZonesLeaderChanged(); + + bool m_is_pending_early_shutdown = false; + bool m_choose_leader_needed = false; + Timer m_choose_leader_cooldown_timer{ static_cast(RuleI(Expedition, ChooseLeaderCooldownTime)) }; + Timer m_warning_cooldown_timer{ 1 }; // non-zero so it's enabled initially +}; + +#endif diff --git a/world/dynamic_zone_manager.cpp b/world/dynamic_zone_manager.cpp new file mode 100644 index 000000000..e71ca5cdc --- /dev/null +++ b/world/dynamic_zone_manager.cpp @@ -0,0 +1,166 @@ +#include "dynamic_zone_manager.h" +#include "dynamic_zone.h" +#include "worlddb.h" +#include "zonelist.h" +#include "zoneserver.h" +#include "../common/rulesys.h" +#include "../common/repositories/expeditions_repository.h" +#include "../common/repositories/expedition_lockouts_repository.h" + +extern ZSList zoneserver_list; + +DynamicZoneManager dynamic_zone_manager; + +DynamicZoneManager::DynamicZoneManager() : + m_process_throttle_timer{ static_cast(RuleI(DynamicZone, WorldProcessRate)) } +{ +} + +void DynamicZoneManager::PurgeExpiredDynamicZones() +{ + // purge when no members, instance is expired, or instance doesn't exist. + // this prevents characters remaining members of dzs that expired while + // server was offline but delayed instance purging hasn't cleaned yet + auto dz_ids = DynamicZonesRepository::GetStaleIDs(database); + + if (!dz_ids.empty()) + { + LogDynamicZones("Purging [{}] dynamic zone(s)", dz_ids.size()); + + DynamicZoneMembersRepository::DeleteWhere(database, + fmt::format("dynamic_zone_id IN ({})", fmt::join(dz_ids, ","))); + DynamicZonesRepository::DeleteWhere(database, + fmt::format("id IN ({})", fmt::join(dz_ids, ","))); + } +} + +DynamicZone* DynamicZoneManager::CreateNew( + DynamicZone& dz_request, const std::vector& members) +{ + // this creates a new dz instance and saves it to both db and cache + uint32_t dz_id = dz_request.Create(); + if (dz_id == 0) + { + LogDynamicZones("Failed to create dynamic zone for zone [{}]", dz_request.GetZoneID()); + return nullptr; + } + + auto dz = std::make_unique(dz_request); + if (!members.empty()) + { + dz->SaveMembers(members); + dz->CacheMemberStatuses(); + } + + LogDynamicZones("Created new dz [{}] for zone [{}]", dz_id, dz_request.GetZoneID()); + + auto pack = dz->CreateServerDzCreatePacket(0, 0); + zoneserver_list.SendPacket(pack.get()); + + auto inserted = dynamic_zone_cache.emplace(dz_id, std::move(dz)); + return inserted.first->second.get(); +} + +void DynamicZoneManager::CacheNewDynamicZone(ServerPacket* pack) +{ + auto buf = reinterpret_cast(pack->pBuffer); + + auto new_dz = std::make_unique(); + new_dz->LoadSerializedDzPacket(buf->cereal_data, buf->cereal_size); + new_dz->CacheMemberStatuses(); + + // reserialize with member statuses cached before forwarding (restore origin zone) + auto repack = new_dz->CreateServerDzCreatePacket(buf->origin_zone_id, buf->origin_instance_id); + + uint32_t dz_id = new_dz->GetID(); + dynamic_zone_cache.emplace(dz_id, std::move(new_dz)); + LogDynamicZones("Cached new dynamic zone [{}]", dz_id); + + zoneserver_list.SendPacket(repack.get()); +} + +void DynamicZoneManager::CacheAllFromDatabase() +{ + BenchTimer bench; + + auto dynamic_zones = DynamicZonesRepository::AllWithInstanceNotExpired(database); + auto dynamic_zone_members = DynamicZoneMembersRepository::GetAllWithNames(database); + + dynamic_zone_cache.clear(); + dynamic_zone_cache.reserve(dynamic_zones.size()); + + for (auto& entry : dynamic_zones) + { + uint32_t dz_id = entry.id; + auto dz = std::make_unique(std::move(entry)); + + for (auto& member : dynamic_zone_members) + { + if (member.dynamic_zone_id == dz_id) + { + dz->AddMemberFromRepositoryResult(std::move(member)); + } + } + + // note leader status won't be updated here until leader is set by owning system (expeditions) + dz->CacheMemberStatuses(); + + dynamic_zone_cache.emplace(dz_id, std::move(dz)); + } + + LogDynamicZones("Caching [{}] dynamic zone(s) took [{}s]", dynamic_zone_cache.size(), bench.elapsed()); +} + +void DynamicZoneManager::Process() +{ + if (!m_process_throttle_timer.Check()) + { + return; + } + + std::vector dynamic_zone_ids; + + for (const auto& dz_iter : dynamic_zone_cache) + { + DynamicZone* dz = dz_iter.second.get(); + + // dynamic zone is not deleted until its zone has no clients to prevent exploits + // clients should be removed by zone-based kick timers if expired but not empty + DynamicZoneStatus status = dz->Process(); + if (status == DynamicZoneStatus::ExpiredEmpty) + { + LogDynamicZones("[{}] expired with [{}] members, notifying zones and deleting", dz->GetID(), dz->GetMemberCount()); + dynamic_zone_ids.emplace_back(dz->GetID()); + dz->SendZonesDynamicZoneDeleted(); // delete dz from zone caches + } + } + + if (!dynamic_zone_ids.empty()) + { + for (const auto& dz_id : dynamic_zone_ids) + { + dynamic_zone_cache.erase(dz_id); + } + + // need to look up expedition ids until lockouts are moved to dynamic zones + std::vector expedition_ids; + auto expeditions = ExpeditionsRepository::GetWhere(database, + fmt::format("dynamic_zone_id IN ({})", fmt::join(dynamic_zone_ids, ","))); + + if (!expeditions.empty()) + { + for (const auto& expedition : expeditions) + { + expedition_ids.emplace_back(expedition.id); + } + ExpeditionLockoutsRepository::DeleteWhere(database, + fmt::format("expedition_id IN ({})", fmt::join(expedition_ids, ","))); + } + + ExpeditionsRepository::DeleteWhere(database, + fmt::format("dynamic_zone_id IN ({})", fmt::join(dynamic_zone_ids, ","))); + DynamicZoneMembersRepository::RemoveAllMembers(database, dynamic_zone_ids); + DynamicZonesRepository::DeleteWhere(database, + fmt::format("id IN ({})", fmt::join(dynamic_zone_ids, ","))); + } +} diff --git a/world/dynamic_zone_manager.h b/world/dynamic_zone_manager.h new file mode 100644 index 000000000..ce59f62ee --- /dev/null +++ b/world/dynamic_zone_manager.h @@ -0,0 +1,32 @@ +#ifndef WORLD_DYNAMIC_ZONE_MANAGER_H +#define WORLD_DYNAMIC_ZONE_MANAGER_H + +#include "../common/timer.h" +#include +#include +#include + +extern class DynamicZoneManager dynamic_zone_manager; + +class DynamicZone; +struct DynamicZoneMember; +class ServerPacket; + +class DynamicZoneManager +{ +public: + DynamicZoneManager(); + + void CacheAllFromDatabase(); + void CacheNewDynamicZone(ServerPacket* pack); + DynamicZone* CreateNew(DynamicZone& dz_request, const std::vector& members); + void Process(); + void PurgeExpiredDynamicZones(); + + std::unordered_map> dynamic_zone_cache; + +private: + Timer m_process_throttle_timer{}; +}; + +#endif diff --git a/world/eqw.cpp b/world/eqw.cpp index 8d997edca..3b480836d 100644 --- a/world/eqw.cpp +++ b/world/eqw.cpp @@ -37,6 +37,7 @@ #include "launcher_list.h" #include "launcher_link.h" #include "wguild_mgr.h" +#include "../common/emu_constants.h" #ifdef seed #undef seed @@ -360,7 +361,13 @@ void EQW::ResolveBug(const char *id) { } void EQW::SendMessage(uint32 type, const char *msg) { - zoneserver_list.SendEmoteMessage(0, 0, 0, type, msg); + zoneserver_list.SendEmoteMessage( + 0, + 0, + AccountStatus::Player, + type, + msg + ); } void EQW::WorldShutDown(uint32 time, uint32 interval) { diff --git a/world/expedition.cpp b/world/expedition.cpp deleted file mode 100644 index c2562b5c2..000000000 --- a/world/expedition.cpp +++ /dev/null @@ -1,211 +0,0 @@ -/** - * 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 "expedition.h" -#include "expedition_database.h" -#include "cliententry.h" -#include "clientlist.h" -#include "zonelist.h" -#include "zoneserver.h" -#include "../common/eqemu_logsys.h" -#include "../common/rulesys.h" - -extern ClientList client_list; -extern ZSList zoneserver_list; - -Expedition::Expedition(uint32_t expedition_id, uint32_t dz_id, uint32_t dz_instance_id, - uint32_t dz_zone_id, uint32_t start_time, uint32_t duration, uint32_t leader_id -) : - m_expedition_id(expedition_id), - m_dz_id(dz_id), - m_dz_instance_id(dz_instance_id), - m_dz_zone_id(dz_zone_id), - m_start_time(std::chrono::system_clock::from_time_t(start_time)), - m_duration(duration), - m_leader_id(leader_id), - m_choose_leader_cooldown_timer{ static_cast(RuleI(Expedition, ChooseLeaderCooldownTime)) } -{ - m_expire_time = m_start_time + m_duration; - m_warning_cooldown_timer.Enable(); -} - -void Expedition::AddMember(uint32_t character_id) -{ - auto it = std::find_if(m_member_ids.begin(), m_member_ids.end(), - [&](uint32_t member_id) { return member_id == character_id; }); - - if (it == m_member_ids.end()) - { - m_member_ids.emplace_back(character_id); - } -} - -bool Expedition::HasMember(uint32_t character_id) -{ - return std::any_of(m_member_ids.begin(), m_member_ids.end(), - [&](uint32_t member_id) { return member_id == character_id; }); -} - -void Expedition::RemoveMember(uint32_t character_id) -{ - m_member_ids.erase(std::remove_if(m_member_ids.begin(), m_member_ids.end(), - [&](uint32_t member_id) { return member_id == character_id; } - ), m_member_ids.end()); - - if (character_id == m_leader_id) - { - ChooseNewLeader(); - } -} - -void Expedition::ChooseNewLeader() -{ - if (m_member_ids.empty() || !m_choose_leader_cooldown_timer.Check()) - { - m_choose_leader_needed = true; - return; - } - - // we don't track expedition member status in world so may choose a linkdead member - // this is fine since it will trigger another change when that member goes offline - auto it = std::find_if(m_member_ids.begin(), m_member_ids.end(), [&](uint32_t member_id) { - auto member_cle = (member_id != m_leader_id) ? client_list.FindCLEByCharacterID(member_id) : nullptr; - return (member_id != m_leader_id && member_cle && member_cle->GetOnline() == CLE_Status::InZone); - }); - - if (it == m_member_ids.end()) - { - // no online members found, fallback to choosing any member - it = std::find_if(m_member_ids.begin(), m_member_ids.end(), - [&](uint32_t member_id) { return (member_id != m_leader_id); }); - } - - if (it != m_member_ids.end() && SetNewLeader(*it)) - { - m_choose_leader_needed = false; - } -} - -bool Expedition::SetNewLeader(uint32_t character_id) -{ - if (!HasMember(character_id)) - { - return false; - } - - LogExpeditionsModerate("Replacing [{}] leader [{}] with [{}]", m_expedition_id, m_leader_id, character_id); - ExpeditionDatabase::UpdateLeaderID(m_expedition_id, character_id); - m_leader_id = character_id; - SendZonesLeaderChanged(); - return true; -} - -void Expedition::SendZonesExpeditionDeleted() -{ - uint32_t pack_size = sizeof(ServerExpeditionID_Struct); - auto pack = std::make_unique(ServerOP_ExpeditionDeleted, pack_size); - auto buf = reinterpret_cast(pack->pBuffer); - buf->expedition_id = GetID(); - zoneserver_list.SendPacket(pack.get()); -} - -void Expedition::SendZonesDurationUpdate() -{ - uint32_t packsize = sizeof(ServerExpeditionUpdateDuration_Struct); - auto pack = std::make_unique(ServerOP_ExpeditionDzDuration, packsize); - auto packbuf = reinterpret_cast(pack->pBuffer); - packbuf->expedition_id = GetID(); - packbuf->new_duration_seconds = static_cast(m_duration.count()); - zoneserver_list.SendPacket(pack.get()); -} - -void Expedition::SendZonesExpireWarning(uint32_t minutes_remaining) -{ - uint32_t pack_size = sizeof(ServerExpeditionExpireWarning_Struct); - auto pack = std::make_unique(ServerOP_ExpeditionExpireWarning, pack_size); - auto buf = reinterpret_cast(pack->pBuffer); - buf->expedition_id = GetID(); - buf->minutes_remaining = minutes_remaining; - zoneserver_list.SendPacket(pack.get()); -} - -void Expedition::SendZonesLeaderChanged() -{ - uint32_t pack_size = sizeof(ServerExpeditionLeaderID_Struct); - auto pack = std::make_unique(ServerOP_ExpeditionLeaderChanged, pack_size); - auto buf = reinterpret_cast(pack->pBuffer); - buf->expedition_id = GetID(); - buf->leader_id = m_leader_id; - zoneserver_list.SendPacket(pack.get()); -} - -void Expedition::UpdateDzSecondsRemaining(uint32_t seconds_remaining) -{ - auto now = std::chrono::system_clock::now(); - auto update_time = std::chrono::seconds(seconds_remaining); - - auto current_remaining = m_expire_time - now; - if (current_remaining > update_time) // reduce only - { - LogExpeditionsDetail( - "Updating expedition [{}] dz instance [{}] seconds remaining to [{}]s", - GetID(), GetInstanceID(), seconds_remaining - ); - - // preserve original start time and adjust duration instead - m_expire_time = now + update_time; - m_duration = std::chrono::duration_cast(m_expire_time - m_start_time); - - ExpeditionDatabase::UpdateDzDuration(GetInstanceID(), static_cast(m_duration.count())); - - // update zone level caches and update the actual dz instance's timer - SendZonesDurationUpdate(); - } -} - -std::chrono::system_clock::duration Expedition::GetRemainingDuration() const -{ - return m_expire_time - std::chrono::system_clock::now(); -} - -void Expedition::CheckExpireWarning() -{ - if (m_warning_cooldown_timer.Check(false)) - { - using namespace std::chrono_literals; - auto remaining = GetRemainingDuration(); - if ((remaining > 14min && remaining < 15min) || - (remaining > 4min && remaining < 5min) || - (remaining > 0min && remaining < 1min)) - { - int minutes = std::chrono::duration_cast(remaining).count() + 1; - SendZonesExpireWarning(minutes); - m_warning_cooldown_timer.Start(70000); // 1 minute 10 seconds - } - } -} - -void Expedition::CheckLeader() -{ - if (m_choose_leader_needed) - { - ChooseNewLeader(); - } -} diff --git a/world/expedition.h b/world/expedition.h deleted file mode 100644 index d166419bb..000000000 --- a/world/expedition.h +++ /dev/null @@ -1,76 +0,0 @@ -/** - * 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 WORLD_EXPEDITION_H -#define WORLD_EXPEDITION_H - -#include "../common/timer.h" -#include -#include -#include - -class Expedition -{ -public: - Expedition() = default; - Expedition(uint32_t expedition_id, uint32_t dz_id, uint32_t dz_instance_id, - uint32_t dz_zone_id, uint32_t expire_time, uint32_t duration, uint32_t leader_id); - - void AddMember(uint32_t character_id); - void RemoveMember(uint32_t character_id); - void RemoveAllMembers() { m_member_ids.clear(); } - void CheckExpireWarning(); - void CheckLeader(); - void ChooseNewLeader(); - uint32_t GetID() const { return m_expedition_id; } - uint16_t GetInstanceID() const { return static_cast(m_dz_instance_id); } - uint16_t GetZoneID() const { return static_cast(m_dz_zone_id); } - bool HasMember(uint32_t character_id); - bool IsEmpty() const { return m_member_ids.empty(); } - bool IsExpired() const { return m_expire_time < std::chrono::system_clock::now(); } - bool IsPendingDelete() const { return m_pending_delete; } - bool IsValid() const { return m_expedition_id != 0; } - void SendZonesDurationUpdate(); - void SendZonesExpeditionDeleted(); - void SendZonesExpireWarning(uint32_t minutes_remaining); - bool SetNewLeader(uint32_t new_leader_id); - void SetPendingDelete(bool pending) { m_pending_delete = pending; } - void UpdateDzSecondsRemaining(uint32_t seconds_remaining); - std::chrono::system_clock::duration GetRemainingDuration() const; - -private: - void SendZonesLeaderChanged(); - - uint32_t m_expedition_id = 0; - uint32_t m_dz_id = 0; - uint32_t m_dz_instance_id = 0; - uint32_t m_dz_zone_id = 0; - uint32_t m_leader_id = 0; - bool m_pending_delete = false; - bool m_choose_leader_needed = false; - Timer m_choose_leader_cooldown_timer; - Timer m_warning_cooldown_timer; - std::vector m_member_ids; - std::chrono::seconds m_duration; - std::chrono::time_point m_start_time; - std::chrono::time_point m_expire_time; -}; - -#endif diff --git a/world/expedition_database.cpp b/world/expedition_database.cpp index f9df67918..a3c4ca4d0 100644 --- a/world/expedition_database.cpp +++ b/world/expedition_database.cpp @@ -19,28 +19,31 @@ */ #include "expedition_database.h" -#include "expedition.h" #include "worlddb.h" +#include "../common/repositories/expeditions_repository.h" +#include "../common/repositories/expedition_lockouts_repository.h" +#include "../common/repositories/dynamic_zone_members_repository.h" void ExpeditionDatabase::PurgeExpiredExpeditions() { std::string query = SQL( SELECT - expeditions.id + expeditions.id, + expeditions.dynamic_zone_id FROM expeditions LEFT JOIN dynamic_zones ON expeditions.dynamic_zone_id = dynamic_zones.id LEFT JOIN instance_list ON dynamic_zones.instance_id = instance_list.id LEFT JOIN ( - SELECT expedition_id, COUNT(IF(is_current_member = TRUE, 1, NULL)) member_count - FROM expedition_members - GROUP BY expedition_id - ) expedition_members - ON expedition_members.expedition_id = expeditions.id + SELECT dynamic_zone_id, COUNT(*) member_count + FROM dynamic_zone_members + GROUP BY dynamic_zone_id + ) dynamic_zone_members + ON dynamic_zone_members.dynamic_zone_id = expeditions.dynamic_zone_id WHERE instance_list.id IS NULL - OR expedition_members.member_count IS NULL - OR expedition_members.member_count = 0 + OR dynamic_zone_members.member_count IS NULL + OR dynamic_zone_members.member_count = 0 OR (instance_list.start_time + instance_list.duration) <= UNIX_TIMESTAMP(); ); @@ -48,15 +51,19 @@ void ExpeditionDatabase::PurgeExpiredExpeditions() if (results.Success()) { std::vector expedition_ids; + std::vector dynamic_zone_ids; for (auto row = results.begin(); row != results.end(); ++row) { expedition_ids.emplace_back(static_cast(strtoul(row[0], nullptr, 10))); + dynamic_zone_ids.emplace_back(static_cast(strtoul(row[1], nullptr, 10))); } if (!expedition_ids.empty()) { - ExpeditionDatabase::MoveMembersToSafeReturn(expedition_ids); - ExpeditionDatabase::DeleteExpeditions(expedition_ids); + auto joined_expedition_ids = fmt::join(expedition_ids, ","); + ExpeditionsRepository::DeleteWhere(database, fmt::format("id IN ({})", joined_expedition_ids)); + ExpeditionLockoutsRepository::DeleteWhere(database, fmt::format("expedition_id IN ({})", joined_expedition_ids)); + DynamicZoneMembersRepository::RemoveAllMembers(database, dynamic_zone_ids); } } } @@ -70,146 +77,3 @@ void ExpeditionDatabase::PurgeExpiredCharacterLockouts() database.QueryDatabase(query); } - -std::vector ExpeditionDatabase::LoadExpeditions(uint32_t select_expedition_id) -{ - std::vector expeditions; - - std::string query = SQL( - SELECT - expeditions.id, - expeditions.dynamic_zone_id, - instance_list.id, - instance_list.zone, - instance_list.start_time, - instance_list.duration, - expeditions.leader_id, - expedition_members.character_id - FROM expeditions - INNER JOIN dynamic_zones ON expeditions.dynamic_zone_id = dynamic_zones.id - INNER JOIN instance_list ON dynamic_zones.instance_id = instance_list.id - INNER JOIN expedition_members ON expedition_members.expedition_id = expeditions.id - AND expedition_members.is_current_member = TRUE - ); - - if (select_expedition_id != 0) - { - query.append(fmt::format(" WHERE expeditions.id = {};", select_expedition_id)); - } - else - { - query.append(" ORDER BY expeditions.id;"); - } - - auto results = database.QueryDatabase(query); - if (results.Success()) - { - uint32_t last_expedition_id = 0; - - for (auto row = results.begin(); row != results.end(); ++row) - { - uint32_t expedition_id = strtoul(row[0], nullptr, 10); - - if (last_expedition_id != expedition_id) - { - expeditions.emplace_back( - static_cast(strtoul(row[0], nullptr, 10)), // expedition_id - static_cast(strtoul(row[1], nullptr, 10)), // dz_id - static_cast(strtoul(row[2], nullptr, 10)), // dz_instance_id - static_cast(strtoul(row[3], nullptr, 10)), // dz_zone_id - static_cast(strtoul(row[4], nullptr, 10)), // start_time - static_cast(strtoul(row[5], nullptr, 10)), // duration - static_cast(strtoul(row[6], nullptr, 10)) // leader_id - ); - } - - last_expedition_id = expedition_id; - - uint32_t member_id = static_cast(strtoul(row[7], nullptr, 10)); - expeditions.back().AddMember(member_id); - } - } - - return expeditions; -} - -Expedition ExpeditionDatabase::LoadExpedition(uint32_t expedition_id) -{ - LogExpeditions("Loading expedition [{}] for world cache", expedition_id); - - Expedition expedition; - - auto expeditions = LoadExpeditions(expedition_id); - if (!expeditions.empty()) - { - expedition = expeditions.front(); - } - - return expedition; -} - -void ExpeditionDatabase::DeleteExpeditions(const std::vector& expedition_ids) -{ - LogExpeditionsDetail("Deleting [{}] expedition(s)", expedition_ids.size()); - - std::string expedition_ids_query = fmt::format("{}", fmt::join(expedition_ids, ",")); - - if (!expedition_ids_query.empty()) - { - auto query = fmt::format("DELETE FROM expeditions WHERE id IN ({});", expedition_ids_query); - database.QueryDatabase(query); - - query = fmt::format("DELETE FROM expedition_members WHERE expedition_id IN ({});", expedition_ids_query); - database.QueryDatabase(query); - - query = fmt::format("DELETE FROM expedition_lockouts WHERE expedition_id IN ({});", expedition_ids_query); - database.QueryDatabase(query); - } -} - -void ExpeditionDatabase::UpdateDzDuration(uint16_t instance_id, uint32_t new_duration) -{ - std::string query = fmt::format( - "UPDATE instance_list SET duration = {} WHERE id = {};", - new_duration, instance_id - ); - - database.QueryDatabase(query); -} - -void ExpeditionDatabase::UpdateLeaderID(uint32_t expedition_id, uint32_t leader_id) -{ - LogExpeditionsDetail("Updating leader [{}] for expedition [{}]", leader_id, expedition_id); - - auto query = fmt::format(SQL( - UPDATE expeditions SET leader_id = {} WHERE id = {}; - ), leader_id, expedition_id); - - database.QueryDatabase(query); -} - -void ExpeditionDatabase::MoveMembersToSafeReturn(const std::vector& expedition_ids) -{ - LogExpeditionsDetail("Moving members from [{}] expedition(s) to safereturn", expedition_ids.size()); - - // only offline members still in expired dz zones should be updated here - std::string query = fmt::format(SQL( - UPDATE character_data - INNER JOIN expedition_members ON character_data.id = expedition_members.character_id - INNER JOIN expeditions ON expedition_members.expedition_id = expeditions.id - INNER JOIN dynamic_zones ON expeditions.dynamic_zone_id = dynamic_zones.id - INNER JOIN instance_list ON dynamic_zones.instance_id = instance_list.id - AND character_data.zone_instance = instance_list.id - AND character_data.zone_id = instance_list.zone - SET - zone_id = IF(safe_return_zone_id > 0, safe_return_zone_id, zone_id), - zone_instance = IF(safe_return_zone_id > 0, 0, zone_instance), - x = IF(safe_return_zone_id > 0, safe_return_x, x), - y = IF(safe_return_zone_id > 0, safe_return_y, y), - z = IF(safe_return_zone_id > 0, safe_return_z, z), - heading = IF(safe_return_zone_id > 0, safe_return_heading, heading) - WHERE expeditions.id IN ({}); - ), fmt::join(expedition_ids, ",")); - - database.QueryDatabase(query); -} diff --git a/world/expedition_database.h b/world/expedition_database.h index 16341210c..a885dfc83 100644 --- a/world/expedition_database.h +++ b/world/expedition_database.h @@ -28,14 +28,8 @@ class Expedition; namespace ExpeditionDatabase { - void DeleteExpeditions(const std::vector& expedition_ids); - std::vector LoadExpeditions(uint32_t select_expedition_id = 0); - Expedition LoadExpedition(uint32_t expedition_id); - void MoveMembersToSafeReturn(const std::vector& expedition_ids); void PurgeExpiredExpeditions(); void PurgeExpiredCharacterLockouts(); - void UpdateDzDuration(uint16_t instance_id, uint32_t new_duration); - void UpdateLeaderID(uint32_t expedition_id, uint32_t leader_id); }; #endif diff --git a/world/expedition_message.cpp b/world/expedition_message.cpp index 646e2272e..93f6ccd6e 100644 --- a/world/expedition_message.cpp +++ b/world/expedition_message.cpp @@ -18,9 +18,8 @@ * */ -#include "expedition.h" +#include "dynamic_zone.h" #include "expedition_message.h" -#include "expedition_state.h" #include "cliententry.h" #include "clientlist.h" #include "zonelist.h" @@ -35,45 +34,11 @@ void ExpeditionMessage::HandleZoneMessage(ServerPacket* pack) { switch (pack->opcode) { - case ServerOP_ExpeditionChooseNewLeader: - { - ExpeditionMessage::ChooseNewLeader(pack); - break; - } case ServerOP_ExpeditionCreate: { - auto buf = reinterpret_cast(pack->pBuffer); - expedition_state.AddExpedition(buf->expedition_id); zoneserver_list.SendPacket(pack); break; } - case ServerOP_ExpeditionMemberChange: - { - auto buf = reinterpret_cast(pack->pBuffer); - expedition_state.MemberChange(buf->expedition_id, buf->char_id, buf->removed); - zoneserver_list.SendPacket(pack); - break; - } - case ServerOP_ExpeditionMemberSwap: - { - auto buf = reinterpret_cast(pack->pBuffer); - expedition_state.MemberChange(buf->expedition_id, buf->add_char_id, false); - expedition_state.MemberChange(buf->expedition_id, buf->remove_char_id, true); - zoneserver_list.SendPacket(pack); - break; - } - case ServerOP_ExpeditionMembersRemoved: - { - auto buf = reinterpret_cast(pack->pBuffer); - expedition_state.RemoveAllMembers(buf->expedition_id); - zoneserver_list.SendPacket(pack); - break; - } - case ServerOP_ExpeditionGetOnlineMembers: - { - ExpeditionMessage::GetOnlineMembers(pack); - break; - } case ServerOP_ExpeditionDzAddPlayer: { ExpeditionMessage::AddPlayer(pack); @@ -104,12 +69,6 @@ void ExpeditionMessage::HandleZoneMessage(ServerPacket* pack) ExpeditionMessage::RequestInvite(pack); break; } - case ServerOP_ExpeditionSecondsRemaining: - { - auto buf = reinterpret_cast(pack->pBuffer); - expedition_state.SetSecondsRemaining(buf->expedition_id, buf->new_duration_seconds); - break; - } } } @@ -144,10 +103,10 @@ void ExpeditionMessage::MakeLeader(ServerPacket* pack) ClientListEntry* new_leader_cle = client_list.FindCharacter(buf->new_leader_name); if (new_leader_cle && new_leader_cle->Server()) { - auto expedition = expedition_state.GetExpedition(buf->expedition_id); - if (expedition) + auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id); + if (dz && dz->GetLeaderID() == buf->requester_id) { - buf->is_success = expedition->SetNewLeader(new_leader_cle->CharID()); + buf->is_success = dz->SetNewLeader(new_leader_cle->CharID()); } buf->is_online = true; @@ -163,33 +122,6 @@ void ExpeditionMessage::MakeLeader(ServerPacket* pack) } } -void ExpeditionMessage::GetOnlineMembers(ServerPacket* pack) -{ - auto buf = reinterpret_cast(pack->pBuffer); - - // not efficient but only requested during caching - char zone_name[64] = {0}; - std::vector all_clients; - all_clients.reserve(client_list.GetClientCount()); - client_list.GetClients(zone_name, all_clients); - - for (uint32_t i = 0; i < buf->count; ++i) - { - auto it = std::find_if(all_clients.begin(), all_clients.end(), [&](const ClientListEntry* cle) { - return (cle && cle->CharID() == buf->entries[i].character_id); - }); - - if (it != all_clients.end()) - { - buf->entries[i].character_zone_id = (*it)->zone(); - buf->entries[i].character_instance_id = (*it)->instance(); - buf->entries[i].character_online = true; - } - } - - zoneserver_list.SendPacket(buf->sender_zone_id, buf->sender_instance_id, pack); -} - void ExpeditionMessage::SaveInvite(ServerPacket* pack) { auto buf = reinterpret_cast(pack->pBuffer); @@ -217,13 +149,3 @@ void ExpeditionMessage::RequestInvite(ServerPacket* pack) } } } - -void ExpeditionMessage::ChooseNewLeader(ServerPacket* pack) -{ - auto buf = reinterpret_cast(pack->pBuffer); - auto expedition = expedition_state.GetExpedition(buf->expedition_id); - if (expedition) - { - expedition->ChooseNewLeader(); - } -} diff --git a/world/expedition_message.h b/world/expedition_message.h index 8789577da..b97651dbc 100644 --- a/world/expedition_message.h +++ b/world/expedition_message.h @@ -26,8 +26,6 @@ class ServerPacket; namespace ExpeditionMessage { void AddPlayer(ServerPacket* pack); - void ChooseNewLeader(ServerPacket* pack); - void GetOnlineMembers(ServerPacket* pack); void HandleZoneMessage(ServerPacket* pack); void MakeLeader(ServerPacket* pack); void RequestInvite(ServerPacket* pack); diff --git a/world/expedition_state.cpp b/world/expedition_state.cpp deleted file mode 100644 index 794cfeb36..000000000 --- a/world/expedition_state.cpp +++ /dev/null @@ -1,159 +0,0 @@ -/** - * 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 "expedition_state.h" -#include "expedition.h" -#include "expedition_database.h" -#include "zonelist.h" -#include "zoneserver.h" -#include "../common/eqemu_logsys.h" -#include - -extern ZSList zoneserver_list; - -ExpeditionState expedition_state; - -Expedition* ExpeditionState::GetExpedition(uint32_t expedition_id) -{ - auto it = std::find_if(m_expeditions.begin(), m_expeditions.end(), - [&](const Expedition& expedition) { return expedition.GetID() == expedition_id; }); - - return (it != m_expeditions.end()) ? &(*it) : nullptr; -} - -void ExpeditionState::LoadActiveExpeditions() -{ - BenchTimer benchmark; - - m_expeditions = ExpeditionDatabase::LoadExpeditions(); - - auto elapsed = benchmark.elapsed(); - LogExpeditions("World caching [{}] expeditions took [{}s]", m_expeditions.size(), elapsed); -} - -void ExpeditionState::AddExpedition(uint32_t expedition_id) -{ - if (expedition_id == 0) - { - return; - } - - auto expedition = ExpeditionDatabase::LoadExpedition(expedition_id); - - if (expedition.IsValid()) - { - auto existing_expedition = GetExpedition(expedition_id); - if (!existing_expedition) - { - m_expeditions.emplace_back(expedition); - } - } -} - -void ExpeditionState::RemoveExpedition(uint32_t expedition_id) -{ - m_expeditions.erase(std::remove_if(m_expeditions.begin(), m_expeditions.end(), - [&](const Expedition& expedition) { - return expedition.GetID() == expedition_id; - } - ), m_expeditions.end()); -} - -void ExpeditionState::MemberChange(uint32_t expedition_id, uint32_t character_id, bool remove) -{ - auto expedition = GetExpedition(expedition_id); - if (expedition) - { - if (remove) { - expedition->RemoveMember(character_id); - } else { - expedition->AddMember(character_id); - } - } -} - -void ExpeditionState::RemoveAllMembers(uint32_t expedition_id) -{ - auto expedition = GetExpedition(expedition_id); - if (expedition) - { - expedition->RemoveAllMembers(); - } -} - -void ExpeditionState::SetSecondsRemaining(uint32_t expedition_id, uint32_t seconds_remaining) -{ - auto expedition = GetExpedition(expedition_id); - if (expedition) - { - expedition->UpdateDzSecondsRemaining(seconds_remaining); - } -} - -void ExpeditionState::Process() -{ - if (!m_process_throttle_timer.Check()) - { - return; - } - - std::vector expedition_ids; - - for (auto it = m_expeditions.begin(); it != m_expeditions.end();) - { - bool is_deleted = false; - - if (it->IsEmpty() || it->IsExpired()) - { - // don't delete expedition until its dz instance is empty. this prevents - // an exploit where all members leave expedition and complete an event - // before being kicked from removal timer. the lockout could never be - // applied because the zone expedition cache was already invalidated. - auto dz_zoneserver = zoneserver_list.FindByInstanceID(it->GetInstanceID()); - if (!dz_zoneserver || dz_zoneserver->NumPlayers() == 0) - { - LogExpeditions("Expedition [{}] expired or empty, notifying zones and deleting", it->GetID()); - expedition_ids.emplace_back(it->GetID()); - it->SendZonesExpeditionDeleted(); - is_deleted = true; - } - - if (it->IsEmpty() && !it->IsPendingDelete() && RuleB(Expedition, EmptyDzShutdownEnabled)) - { - it->UpdateDzSecondsRemaining(RuleI(Expedition, EmptyDzShutdownDelaySeconds)); - } - - it->SetPendingDelete(true); - } - else - { - it->CheckExpireWarning(); - it->CheckLeader(); - } - - it = is_deleted ? m_expeditions.erase(it) : it + 1; - } - - if (!expedition_ids.empty()) - { - ExpeditionDatabase::MoveMembersToSafeReturn(expedition_ids); - ExpeditionDatabase::DeleteExpeditions(expedition_ids); - } -} diff --git a/world/expedition_state.h b/world/expedition_state.h deleted file mode 100644 index 9d54dcec9..000000000 --- a/world/expedition_state.h +++ /dev/null @@ -1,50 +0,0 @@ -/** - * 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 WORLD_EXPEDITION_STATE_H -#define WORLD_EXPEDITION_STATE_H - -#include "../common/rulesys.h" -#include "../common/timer.h" -#include -#include - -extern class ExpeditionState expedition_state; - -class Expedition; - -class ExpeditionState -{ -public: - void AddExpedition(uint32_t expedition_id); - Expedition* GetExpedition(uint32_t expedition_id); - void LoadActiveExpeditions(); - void MemberChange(uint32_t expedition_id, uint32_t character_id, bool remove); - void Process(); - void RemoveAllMembers(uint32_t expedition_id); - void RemoveExpedition(uint32_t expedition_id); - void SetSecondsRemaining(uint32_t expedition_id, uint32_t seconds_remaining); - -private: - std::vector m_expeditions; - Timer m_process_throttle_timer{static_cast(RuleI(Expedition, WorldExpeditionProcessRateMS))}; -}; - -#endif diff --git a/world/login_server.cpp b/world/login_server.cpp index 590a3f0db..edcdacf23 100644 --- a/world/login_server.cpp +++ b/world/login_server.cpp @@ -46,12 +46,12 @@ extern volatile bool RunLoops; LoginServer::LoginServer(const char *iAddress, uint16 iPort, const char *Account, const char *Password, bool legacy) { - strn0cpy(LoginServerAddress, iAddress, 256); - LoginServerPort = iPort; - LoginAccount = Account; - LoginPassword = Password; - CanAccountUpdate = false; - IsLegacy = legacy; + strn0cpy(m_loginserver_address, iAddress, 256); + m_loginserver_port = iPort; + m_login_account = Account; + m_login_password = Password; + m_can_account_update = false; + m_is_legacy = legacy; Connect(); } @@ -92,8 +92,11 @@ void LoginServer::ProcessUsertoWorldReqLeg(uint16_t opcode, EQ::Net::Packet &p) utwrs->response = UserToWorldStatusSuccess; if (Config->Locked) { - if (status < 100) { - LogDebug("[ProcessUsertoWorldReqLeg] Server locked and status is not high enough for account_id [{0}]", utwr->lsaccountid); + if (status < (RuleI(GM, MinStatusToBypassLockedServer))) { + LogDebug( + "[ProcessUsertoWorldReqLeg] Server locked and status is not high enough for account_id [{0}]", + utwr->lsaccountid + ); utwrs->response = UserToWorldStatusWorldUnavail; SendPacket(&outpack); return; @@ -101,7 +104,7 @@ void LoginServer::ProcessUsertoWorldReqLeg(uint16_t opcode, EQ::Net::Packet &p) } int32 x = Config->MaxClients; - if ((int32) numplayers >= x && x != -1 && x != 255 && status < 80) { + if ((int32) numplayers >= x && x != -1 && x != 255 && status < (RuleI(GM, MinStatusToBypassLockedServer))) { LogDebug("[ProcessUsertoWorldReqLeg] World at capacity account_id [{0}]", utwr->lsaccountid); utwrs->response = UserToWorldStatusWorldAtCapacity; SendPacket(&outpack); @@ -170,8 +173,11 @@ void LoginServer::ProcessUsertoWorldReq(uint16_t opcode, EQ::Net::Packet &p) utwrs->response = UserToWorldStatusSuccess; if (Config->Locked == true) { - if (status < 100) { - LogDebug("[ProcessUsertoWorldReq] Server locked and status is not high enough for account_id [{0}]", utwr->lsaccountid); + if (status < (RuleI(GM, MinStatusToBypassLockedServer))) { + LogDebug( + "[ProcessUsertoWorldReq] Server locked and status is not high enough for account_id [{0}]", + utwr->lsaccountid + ); utwrs->response = UserToWorldStatusWorldUnavail; SendPacket(&outpack); return; @@ -179,7 +185,7 @@ void LoginServer::ProcessUsertoWorldReq(uint16_t opcode, EQ::Net::Packet &p) } int32 x = Config->MaxClients; - if ((int32) numplayers >= x && x != -1 && x != 255 && status < 80) { + if ((int32) numplayers >= x && x != -1 && x != 255 && status < (RuleI(GM, MinStatusToBypassLockedServer))) { LogDebug("[ProcessUsertoWorldReq] World at capacity account_id [{0}]", utwr->lsaccountid); utwrs->response = UserToWorldStatusWorldAtCapacity; SendPacket(&outpack); @@ -300,7 +306,13 @@ void LoginServer::ProcessSystemwideMessage(uint16_t opcode, EQ::Net::Packet &p) LogNetcode("Received ServerPacket from LS OpCode {:#04x}", opcode); ServerSystemwideMessage *swm = (ServerSystemwideMessage *) p.Data(); - zoneserver_list.SendEmoteMessageRaw(0, 0, 0, swm->type, swm->message); + zoneserver_list.SendEmoteMessageRaw( + 0, + 0, + AccountStatus::Player, + swm->type, + swm->message + ); } void LoginServer::ProcessLSRemoteAddr(uint16_t opcode, EQ::Net::Packet &p) @@ -320,61 +332,63 @@ void LoginServer::ProcessLSAccountUpdate(uint16_t opcode, EQ::Net::Packet &p) LogNetcode("Received ServerPacket from LS OpCode {:#04x}", opcode); LogNetcode("Received ServerOP_LSAccountUpdate packet from loginserver"); - CanAccountUpdate = true; + m_can_account_update = true; } bool LoginServer::Connect() { char errbuf[1024]; - if ((LoginServerIP = ResolveIP(LoginServerAddress, errbuf)) == 0) { - LogInfo("Unable to resolve [{}] to an IP", LoginServerAddress); + if ((m_loginserver_ip = ResolveIP(m_loginserver_address, errbuf)) == 0) { + LogInfo("Unable to resolve [{}] to an IP", m_loginserver_address); return false; } - if (LoginServerIP == 0 || LoginServerPort == 0) { + if (m_loginserver_ip == 0 || m_loginserver_port == 0) { LogInfo( "Connect info incomplete, cannot connect: [{0}:{1}]", - LoginServerAddress, - LoginServerPort + m_loginserver_address, + m_loginserver_port ); return false; } - if (IsLegacy) { - legacy_client = std::make_unique(LoginServerAddress, LoginServerPort, false); - legacy_client->OnConnect( + if (m_is_legacy) { + m_legacy_client = std::make_unique( + m_loginserver_address, + m_loginserver_port, + false + ); + m_legacy_client->OnConnect( [this](EQ::Net::ServertalkLegacyClient *client) { if (client) { LogInfo( "Connected to Legacy Loginserver: [{0}:{1}]", - LoginServerAddress, - LoginServerPort + m_loginserver_address, + m_loginserver_port ); SendInfo(); SendStatus(); zoneserver_list.SendLSZones(); - statusupdate_timer = std::make_unique( - - LoginServer_StatusUpdateInterval, true, [this](EQ::Timer *t) { - SendStatus(); - } - + m_statusupdate_timer = std::make_unique( + LoginServer_StatusUpdateInterval, true, [this](EQ::Timer *t) { + SendStatus(); + } ); } else { LogInfo( "Could not connect to Legacy Loginserver: [{0}:{1}]", - LoginServerAddress, - LoginServerPort + m_loginserver_address, + m_loginserver_port ); } } ); - legacy_client->OnMessage( + m_legacy_client->OnMessage( ServerOP_UsertoWorldReqLeg, std::bind( &LoginServer::ProcessUsertoWorldReqLeg, @@ -383,7 +397,7 @@ bool LoginServer::Connect() std::placeholders::_2 ) ); - legacy_client->OnMessage( + m_legacy_client->OnMessage( ServerOP_UsertoWorldReq, std::bind( &LoginServer::ProcessUsertoWorldReq, @@ -392,7 +406,7 @@ bool LoginServer::Connect() std::placeholders::_2 ) ); - legacy_client->OnMessage( + m_legacy_client->OnMessage( ServerOP_LSClientAuthLeg, std::bind( &LoginServer::ProcessLSClientAuthLegacy, @@ -401,7 +415,7 @@ bool LoginServer::Connect() std::placeholders::_2 ) ); - legacy_client->OnMessage( + m_legacy_client->OnMessage( ServerOP_LSClientAuth, std::bind( &LoginServer::ProcessLSClientAuth, @@ -410,7 +424,7 @@ bool LoginServer::Connect() std::placeholders::_2 ) ); - legacy_client->OnMessage( + m_legacy_client->OnMessage( ServerOP_LSFatalError, std::bind( &LoginServer::ProcessLSFatalError, @@ -419,7 +433,7 @@ bool LoginServer::Connect() std::placeholders::_2 ) ); - legacy_client->OnMessage( + m_legacy_client->OnMessage( ServerOP_SystemwideMessage, std::bind( &LoginServer::ProcessSystemwideMessage, @@ -428,7 +442,7 @@ bool LoginServer::Connect() std::placeholders::_2 ) ); - legacy_client->OnMessage( + m_legacy_client->OnMessage( ServerOP_LSRemoteAddr, std::bind( &LoginServer::ProcessLSRemoteAddr, @@ -437,7 +451,7 @@ bool LoginServer::Connect() std::placeholders::_2 ) ); - legacy_client->OnMessage( + m_legacy_client->OnMessage( ServerOP_LSAccountUpdate, std::bind( &LoginServer::ProcessLSAccountUpdate, @@ -448,37 +462,43 @@ bool LoginServer::Connect() ); } else { - client = std::make_unique(LoginServerAddress, LoginServerPort, false, "World", ""); - client->OnConnect( + m_client = std::make_unique( + m_loginserver_address, + m_loginserver_port, + false, + "World", + "" + ); + m_client->OnConnect( [this](EQ::Net::ServertalkClient *client) { if (client) { LogInfo( "Connected to Loginserver: [{0}:{1}]", - LoginServerAddress, - LoginServerPort + m_loginserver_address, + m_loginserver_port ); SendInfo(); SendStatus(); zoneserver_list.SendLSZones(); - statusupdate_timer = std::make_unique( + m_statusupdate_timer = std::make_unique( - LoginServer_StatusUpdateInterval, true, [this](EQ::Timer *t) { - SendStatus(); - } - ); + LoginServer_StatusUpdateInterval, true, [this](EQ::Timer *t) { + SendStatus(); + } + ); } else { LogInfo( "Could not connect to Loginserver: [{0}:{1}]", - LoginServerAddress, - LoginServerPort + m_loginserver_address, + m_loginserver_port ); } } ); - client->OnMessage( + m_client->OnMessage( ServerOP_UsertoWorldReqLeg, std::bind( &LoginServer::ProcessUsertoWorldReqLeg, @@ -487,7 +507,7 @@ bool LoginServer::Connect() std::placeholders::_2 ) ); - client->OnMessage( + m_client->OnMessage( ServerOP_UsertoWorldReq, std::bind( &LoginServer::ProcessUsertoWorldReq, @@ -496,7 +516,7 @@ bool LoginServer::Connect() std::placeholders::_2 ) ); - client->OnMessage( + m_client->OnMessage( ServerOP_LSClientAuthLeg, std::bind( &LoginServer::ProcessLSClientAuthLegacy, @@ -505,7 +525,7 @@ bool LoginServer::Connect() std::placeholders::_2 ) ); - client->OnMessage( + m_client->OnMessage( ServerOP_LSClientAuth, std::bind( &LoginServer::ProcessLSClientAuth, @@ -514,7 +534,7 @@ bool LoginServer::Connect() std::placeholders::_2 ) ); - client->OnMessage( + m_client->OnMessage( ServerOP_LSFatalError, std::bind( &LoginServer::ProcessLSFatalError, @@ -523,7 +543,7 @@ bool LoginServer::Connect() std::placeholders::_2 ) ); - client->OnMessage( + m_client->OnMessage( ServerOP_SystemwideMessage, std::bind( &LoginServer::ProcessSystemwideMessage, @@ -532,7 +552,7 @@ bool LoginServer::Connect() std::placeholders::_2 ) ); - client->OnMessage( + m_client->OnMessage( ServerOP_LSRemoteAddr, std::bind( &LoginServer::ProcessLSRemoteAddr, @@ -541,7 +561,7 @@ bool LoginServer::Connect() std::placeholders::_2 ) ); - client->OnMessage( + m_client->OnMessage( ServerOP_LSAccountUpdate, std::bind( &LoginServer::ProcessLSAccountUpdate, @@ -552,7 +572,10 @@ bool LoginServer::Connect() ); } - m_keepalive = std::make_unique(5000, true, std::bind(&LoginServer::OnKeepAlive, this, std::placeholders::_1)); + m_keepalive = std::make_unique( + 1000, + true, + std::bind(&LoginServer::OnKeepAlive, this, std::placeholders::_1)); return true; } @@ -566,28 +589,42 @@ void LoginServer::SendInfo() pack->size = sizeof(ServerNewLSInfo_Struct); pack->pBuffer = new uchar[pack->size]; memset(pack->pBuffer, 0, pack->size); - ServerNewLSInfo_Struct *lsi = (ServerNewLSInfo_Struct *) pack->pBuffer; - strcpy(lsi->protocol_version, EQEMU_PROTOCOL_VERSION); - strcpy(lsi->server_version, LOGIN_VERSION); - strcpy(lsi->server_long_name, Config->LongName.c_str()); - strcpy(lsi->server_short_name, Config->ShortName.c_str()); - strn0cpy(lsi->account_name, LoginAccount.c_str(), 30); - strn0cpy(lsi->account_password, LoginPassword.c_str(), 30); + + auto *l = (ServerNewLSInfo_Struct *) pack->pBuffer; + strcpy(l->protocol_version, EQEMU_PROTOCOL_VERSION); + strcpy(l->server_version, LOGIN_VERSION); + strcpy(l->server_long_name, Config->LongName.c_str()); + strcpy(l->server_short_name, Config->ShortName.c_str()); + strn0cpy(l->account_name, m_login_account.c_str(), 30); + strn0cpy(l->account_password, m_login_password.c_str(), 30); if (Config->WorldAddress.length()) { - strcpy(lsi->remote_ip_address, Config->WorldAddress.c_str()); + strcpy(l->remote_ip_address, Config->WorldAddress.c_str()); } if (Config->LocalAddress.length()) { - strcpy(lsi->local_ip_address, Config->LocalAddress.c_str()); + strcpy(l->local_ip_address, Config->LocalAddress.c_str()); } else { - auto local_addr = IsLegacy ? legacy_client->Handle()->LocalIP() : client->Handle()->LocalIP(); - strcpy(lsi->local_ip_address, local_addr.c_str()); - WorldConfig::SetLocalAddress(lsi->local_ip_address); + auto local_addr = m_is_legacy ? m_legacy_client->Handle()->LocalIP() : m_client->Handle()->LocalIP(); + strcpy(l->local_ip_address, local_addr.c_str()); + WorldConfig::SetLocalAddress(l->local_ip_address); } + + LogInfo( + "[LoginServer::SendInfo] protocol_version [{}] server_version [{}] long_name [{}] short_name [{}] account_name [{}] remote_ip_address [{}] local_ip [{}]", + l->protocol_version, + l->server_version, + l->server_long_name, + l->server_short_name, + l->account_name, + l->remote_ip_address, + l->local_ip_address + ); + SendPacket(pack); delete pack; } + void LoginServer::SendStatus() { auto pack = new ServerPacket; @@ -618,14 +655,14 @@ void LoginServer::SendStatus() */ void LoginServer::SendPacket(ServerPacket *pack) { - if (IsLegacy) { - if (legacy_client) { - legacy_client->SendPacket(pack); + if (m_is_legacy) { + if (m_legacy_client) { + m_legacy_client->SendPacket(pack); } } else { - if (client) { - client->SendPacket(pack); + if (m_client) { + m_client->SendPacket(pack); } } } @@ -636,11 +673,11 @@ void LoginServer::SendAccountUpdate(ServerPacket *pack) if (CanUpdate()) { LogInfo( "Sending ServerOP_LSAccountUpdate packet to loginserver: [{0}]:[{1}]", - LoginServerAddress, - LoginServerPort + m_loginserver_address, + m_loginserver_port ); - strn0cpy(ls_account_update->worldaccount, LoginAccount.c_str(), 30); - strn0cpy(ls_account_update->worldpassword, LoginPassword.c_str(), 30); + strn0cpy(ls_account_update->worldaccount, m_login_account.c_str(), 30); + strn0cpy(ls_account_update->worldpassword, m_login_password.c_str(), 30); SendPacket(pack); } } diff --git a/world/login_server.h b/world/login_server.h index 16fde8e65..21e25870f 100644 --- a/world/login_server.h +++ b/world/login_server.h @@ -1,20 +1,3 @@ -/* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org) - - 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 LOGINSERVER_H #define LOGINSERVER_H @@ -42,20 +25,20 @@ public: void SendAccountUpdate(ServerPacket *pack); bool Connected() { - if (IsLegacy) { - if (legacy_client) { - return legacy_client->Connected(); + if (m_is_legacy) { + if (m_legacy_client) { + return m_legacy_client->Connected(); } } else { - if (client) { - return client->Connected(); + if (m_client) { + return m_client->Connected(); } } return false; } - bool CanUpdate() { return CanAccountUpdate; } + bool CanUpdate() { return m_can_account_update; } private: void ProcessUsertoWorldReqLeg(uint16_t opcode, EQ::Net::Packet &p); @@ -70,15 +53,15 @@ private: void OnKeepAlive(EQ::Timer *t); std::unique_ptr m_keepalive; - std::unique_ptr client; - std::unique_ptr legacy_client; - std::unique_ptr statusupdate_timer; - char LoginServerAddress[256]; - uint32 LoginServerIP; - uint16 LoginServerPort; - std::string LoginAccount; - std::string LoginPassword; - bool CanAccountUpdate; - bool IsLegacy; + std::unique_ptr m_client; + std::unique_ptr m_legacy_client; + std::unique_ptr m_statusupdate_timer; + char m_loginserver_address[256]; + uint32 m_loginserver_ip; + uint16 m_loginserver_port; + std::string m_login_account; + std::string m_login_password; + bool m_can_account_update; + bool m_is_legacy; }; #endif diff --git a/world/main.cpp b/world/main.cpp index c5c67ab87..ee9f12705 100644 --- a/world/main.cpp +++ b/world/main.cpp @@ -46,6 +46,7 @@ #include "../common/crash.h" #include "client.h" #include "worlddb.h" + #ifdef _WINDOWS #include #define snprintf _snprintf @@ -53,12 +54,14 @@ #define strcasecmp _stricmp #include #else + #include #include "../common/unix.h" #include #include #include #include + #if not defined (FREEBSD) && not defined (DARWIN) union semun { int val; @@ -88,39 +91,44 @@ union semun { #include "queryserv.h" #include "web_interface.h" #include "console.h" +#include "dynamic_zone_manager.h" #include "expedition_database.h" -#include "expedition_state.h" #include "../common/net/servertalk_server.h" #include "../zone/data_bucket.h" #include "world_server_command_handler.h" #include "../common/content/world_content_service.h" +#include "../common/repositories/character_task_timers_repository.h" #include "../common/repositories/merchantlist_temp_repository.h" #include "world_store.h" +#include "world_event_scheduler.h" +#include "shared_task_manager.h" -WorldStore world_store; -ClientList client_list; -GroupLFPList LFPGroupList; -ZSList zoneserver_list; -LoginServerList loginserverlist; -UCSConnection UCSLink; +WorldStore world_store; +ClientList client_list; +GroupLFPList LFPGroupList; +ZSList zoneserver_list; +LoginServerList loginserverlist; +UCSConnection UCSLink; QueryServConnection QSLink; -LauncherList launcher_list; -AdventureManager adventure_manager; -EQ::Random emu_random; -volatile bool RunLoops = true; -uint32 numclients = 0; -uint32 numzones = 0; -bool holdzones = false; -const WorldConfig *Config; -EQEmuLogSys LogSys; +LauncherList launcher_list; +AdventureManager adventure_manager; +WorldEventScheduler event_scheduler; +SharedTaskManager shared_task_manager; +EQ::Random emu_random; +volatile bool RunLoops = true; +uint32 numclients = 0; +uint32 numzones = 0; +const WorldConfig *Config; +EQEmuLogSys LogSys; WorldContentService content_service; -WebInterfaceList web_interface; +WebInterfaceList web_interface; void CatchSignal(int sig_num); void CheckForServerScript(bool force_download = false); -inline void UpdateWindowTitle(std::string new_title) { +inline void UpdateWindowTitle(std::string new_title) +{ #ifdef _WINDOWS SetConsoleTitle(new_title.c_str()); #endif @@ -152,7 +160,7 @@ void LoadDatabaseConnections() */ if (!Config->ContentDbHost.empty()) { if (!content_db.Connect( - Config->ContentDbHost.c_str() , + Config->ContentDbHost.c_str(), Config->ContentDbUsername.c_str(), Config->ContentDbPassword.c_str(), Config->ContentDbName.c_str(), @@ -162,7 +170,8 @@ void LoadDatabaseConnections() LogError("Cannot continue without a content database connection"); std::exit(1); } - } else { + } + else { content_db.SetMysql(database.getMySQL()); } @@ -172,7 +181,7 @@ void CheckForXMLConfigUpgrade() { if (!std::ifstream("eqemu_config.json") && std::ifstream("eqemu_config.xml")) { CheckForServerScript(true); - if(system("perl eqemu_server.pl convert_xml")); + if (system("perl eqemu_server.pl convert_xml")) {} } else { CheckForServerScript(); @@ -203,7 +212,7 @@ void RegisterLoginservers() } } else { - LinkedList loginlist = Config->loginlist; + LinkedList loginlist = Config->loginlist; LinkedListIterator iterator(loginlist); iterator.Reset(); while (iterator.MoreElements()) { @@ -227,6 +236,46 @@ void RegisterLoginservers() } } +static void GMSayHookCallBackProcessWorld(uint16 log_category, std::string message) +{ + // Cut messages down to 4000 max to prevent client crash + if (!message.empty()) { + message = message.substr(0, 4000); + } + + // Replace Occurrences of % or MessageStatus will crash + find_replace(message, std::string("%"), std::string(".")); + + if (message.find('\n') != std::string::npos) { + auto message_split = SplitString(message, '\n'); + + for (size_t iter = 0; iter < message_split.size(); ++iter) { + zoneserver_list.SendEmoteMessage( + 0, + 0, + AccountStatus::QuestTroupe, + LogSys.GetGMSayColorFromCategory(log_category), + fmt::format( + " {}{}", + (iter == 0 ? " ---" : ""), + message_split[iter] + ).c_str() + ); + } + + return; + } + + zoneserver_list.SendEmoteMessage( + 0, + 0, + AccountStatus::QuestTroupe, + LogSys.GetGMSayColorFromCategory(log_category), + "%s", + message.c_str() + ); +} + /** * World process entrypoint * @@ -234,7 +283,8 @@ void RegisterLoginservers() * @param argv * @return */ -int main(int argc, char** argv) { +int main(int argc, char **argv) +{ RegisterExecutablePlatform(ExePlatformWorld); LogSys.LoadLogSettingsDefaults(); set_exception_handler(); @@ -295,11 +345,15 @@ int main(int argc, char** argv) { guild_mgr.SetDatabase(&database); - /** - * Logging - */ - database.LoadLogSettings(LogSys.log_settings); - LogSys.StartFileLogs(); + // logging system init + auto logging = LogSys.SetDatabase(&database) + ->LoadLogDatabaseSettings(); + + if (RuleB(Logging, WorldGMSayLogging)) { + logging->SetGMSayHandler(&GMSayHookCallBackProcessWorld); + } + + logging->StartFileLogs(); /** * Parse simple CLI passes @@ -401,7 +455,7 @@ int main(int argc, char** argv) { LogInfo("Loading EQ time of day"); TimeOfDay_Struct eqTime; - time_t realtime; + time_t realtime; eqTime = database.LoadTime(realtime); zoneserver_list.worldclock.SetCurrentEQTimeOfDay(eqTime, realtime); Timer EQTimeTimer(600000); @@ -413,35 +467,55 @@ int main(int argc, char** argv) { LogInfo("Deleted [{}] stale player corpses from database", database.DeleteStalePlayerCorpses()); LogInfo("Loading adventures"); - if (!adventure_manager.LoadAdventureTemplates()) - { + if (!adventure_manager.LoadAdventureTemplates()) { LogInfo("Unable to load adventure templates"); } - if (!adventure_manager.LoadAdventureEntries()) - { + if (!adventure_manager.LoadAdventureEntries()) { LogInfo("Unable to load adventure templates"); } adventure_manager.LoadLeaderboardInfo(); + LogInfo("Purging expired dynamic zones and members"); + dynamic_zone_manager.PurgeExpiredDynamicZones(); + LogInfo("Purging expired expeditions"); ExpeditionDatabase::PurgeExpiredExpeditions(); ExpeditionDatabase::PurgeExpiredCharacterLockouts(); + LogInfo("Purging expired character task timers"); + CharacterTaskTimersRepository::DeleteWhere(database, "expire_time <= NOW()"); + LogInfo("Purging expired instances"); database.PurgeExpiredInstances(); Timer PurgeInstanceTimer(450000); PurgeInstanceTimer.Start(450000); - LogInfo("Loading active expeditions"); - expedition_state.LoadActiveExpeditions(); + LogInfo("Loading dynamic zones"); + dynamic_zone_manager.CacheAllFromDatabase(); LogInfo("Loading char create info"); content_db.LoadCharacterCreateAllocations(); content_db.LoadCharacterCreateCombos(); + LogInfo("Initializing [EventScheduler]"); + event_scheduler.SetDatabase(&database)->LoadScheduledEvents(); + + LogInfo("Initializing [WorldContentService]"); + content_service.SetDatabase(&database) + ->SetExpansionContext() + ->ReloadContentFlags(); + + LogInfo("Initializing [SharedTaskManager]"); + shared_task_manager.SetDatabase(&database) + ->SetContentDatabase(&content_db) + ->LoadTaskData() + ->LoadSharedTaskState(); + + shared_task_manager.PurgeExpiredSharedTasks(); + std::unique_ptr console; if (Config->TelnetEnabled) { LogInfo("Console (TCP) listener started"); @@ -454,8 +528,8 @@ int main(int argc, char** argv) { server_connection = std::make_unique(); EQ::Net::ServertalkServerOptions server_opts; - server_opts.port = Config->WorldTCPPort; - server_opts.ipv6 = false; + server_opts.port = Config->WorldTCPPort; + server_opts.ipv6 = false; server_opts.credentials = Config->SharedKey; server_connection->Listen(server_opts); LogInfo("Server (TCP) listener started"); @@ -470,50 +544,62 @@ int main(int argc, char** argv) { } ); - server_connection->OnConnectionRemoved("Zone", [](std::shared_ptr connection) { - LogInfo("Removed Zone Server connection from [{0}]", - connection->GetUUID()); + server_connection->OnConnectionRemoved( + "Zone", [](std::shared_ptr connection) { + LogInfo("Removed Zone Server connection from [{0}]", + connection->GetUUID()); - numzones--; - zoneserver_list.Remove(connection->GetUUID()); - }); + numzones--; + zoneserver_list.Remove(connection->GetUUID()); + } + ); - server_connection->OnConnectionIdentified("Launcher", [](std::shared_ptr connection) { - LogInfo("New Launcher connection from [{2}] at [{0}:{1}]", - connection->Handle()->RemoteIP(), connection->Handle()->RemotePort(), connection->GetUUID()); + server_connection->OnConnectionIdentified( + "Launcher", [](std::shared_ptr connection) { + LogInfo("New Launcher connection from [{2}] at [{0}:{1}]", + connection->Handle()->RemoteIP(), connection->Handle()->RemotePort(), connection->GetUUID()); - launcher_list.Add(connection); - }); + launcher_list.Add(connection); + } + ); - server_connection->OnConnectionRemoved("Launcher", [](std::shared_ptr connection) { - LogInfo("Removed Launcher connection from [{0}]", - connection->GetUUID()); + server_connection->OnConnectionRemoved( + "Launcher", [](std::shared_ptr connection) { + LogInfo("Removed Launcher connection from [{0}]", + connection->GetUUID()); - launcher_list.Remove(connection); - }); + launcher_list.Remove(connection); + } + ); - server_connection->OnConnectionIdentified("QueryServ", [](std::shared_ptr connection) { - LogInfo("New Query Server connection from [{2}] at [{0}:{1}]", - connection->Handle()->RemoteIP(), connection->Handle()->RemotePort(), connection->GetUUID()); + server_connection->OnConnectionIdentified( + "QueryServ", [](std::shared_ptr connection) { + LogInfo("New Query Server connection from [{2}] at [{0}:{1}]", + connection->Handle()->RemoteIP(), connection->Handle()->RemotePort(), connection->GetUUID()); - QSLink.AddConnection(connection); - }); + QSLink.AddConnection(connection); + } + ); - server_connection->OnConnectionRemoved("QueryServ", [](std::shared_ptr connection) { - LogInfo("Removed Query Server connection from [{0}]", - connection->GetUUID()); + server_connection->OnConnectionRemoved( + "QueryServ", [](std::shared_ptr connection) { + LogInfo("Removed Query Server connection from [{0}]", + connection->GetUUID()); - QSLink.RemoveConnection(connection); - }); + QSLink.RemoveConnection(connection); + } + ); - server_connection->OnConnectionIdentified("UCS", [](std::shared_ptr connection) { - LogInfo("New UCS Server connection from [{2}] at [{0}:{1}]", - connection->Handle()->RemoteIP(), connection->Handle()->RemotePort(), connection->GetUUID()); + server_connection->OnConnectionIdentified( + "UCS", [](std::shared_ptr connection) { + LogInfo("New UCS Server connection from [{2}] at [{0}:{1}]", + connection->Handle()->RemoteIP(), connection->Handle()->RemotePort(), connection->GetUUID()); - UCSLink.SetConnection(connection); + UCSLink.SetConnection(connection); - zoneserver_list.UpdateUCSServerAvailable(); - }); + zoneserver_list.UpdateUCSServerAvailable(); + } + ); server_connection->OnConnectionRemoved( "UCS", [](std::shared_ptr connection) { @@ -529,26 +615,30 @@ int main(int argc, char** argv) { } ); - server_connection->OnConnectionIdentified("WebInterface", [](std::shared_ptr connection) { - LogInfo("New WebInterface Server connection from [{2}] at [{0}:{1}]", - connection->Handle()->RemoteIP(), connection->Handle()->RemotePort(), connection->GetUUID()); + server_connection->OnConnectionIdentified( + "WebInterface", [](std::shared_ptr connection) { + LogInfo("New WebInterface Server connection from [{2}] at [{0}:{1}]", + connection->Handle()->RemoteIP(), connection->Handle()->RemotePort(), connection->GetUUID()); - web_interface.AddConnection(connection); - }); + web_interface.AddConnection(connection); + } + ); - server_connection->OnConnectionRemoved("WebInterface", [](std::shared_ptr connection) { - LogInfo("Removed WebInterface Server connection from [{0}]", - connection->GetUUID()); + server_connection->OnConnectionRemoved( + "WebInterface", [](std::shared_ptr connection) { + LogInfo("Removed WebInterface Server connection from [{0}]", + connection->GetUUID()); - web_interface.RemoveConnection(connection); - }); + web_interface.RemoveConnection(connection); + } + ); EQStreamManagerInterfaceOptions opts(9000, false, false); - opts.daybreak_options.resend_delay_ms = RuleI(Network, ResendDelayBaseMS); + opts.daybreak_options.resend_delay_ms = RuleI(Network, ResendDelayBaseMS); opts.daybreak_options.resend_delay_factor = RuleR(Network, ResendDelayFactor); - opts.daybreak_options.resend_delay_min = RuleI(Network, ResendDelayMinMS); - opts.daybreak_options.resend_delay_max = RuleI(Network, ResendDelayMaxMS); - opts.daybreak_options.outgoing_data_rate = RuleR(Network, ClientDataRate); + opts.daybreak_options.resend_delay_min = RuleI(Network, ResendDelayMinMS); + opts.daybreak_options.resend_delay_max = RuleI(Network, ResendDelayMaxMS); + opts.daybreak_options.outgoing_data_rate = RuleR(Network, ClientDataRate); EQ::Net::EQStreamManager eqsm(opts); @@ -561,14 +651,16 @@ int main(int argc, char** argv) { zoneserver_list.reminder->Disable(); Timer InterserverTimer(INTERSERVER_TIMER); // does MySQL pings and auto-reconnect InterserverTimer.Trigger(); - uint8 ReconnectCounter = 100; + uint8 ReconnectCounter = 100; std::shared_ptr eqs; - EQStreamInterface *eqsi; + EQStreamInterface *eqsi; - eqsm.OnNewConnection([&stream_identifier](std::shared_ptr stream) { - stream_identifier.AddStream(stream); - LogInfo("New connection from IP {0}:{1}", stream->GetRemoteIP(), ntohs(stream->GetRemotePort())); - }); + eqsm.OnNewConnection( + [&stream_identifier](std::shared_ptr stream) { + stream_identifier.AddStream(stream); + LogInfo("New connection from IP {0}:{1}", stream->GetRemoteIP(), ntohs(stream->GetRemotePort())); + } + ); while (RunLoops) { Timer::SetCurrentTime(); @@ -580,7 +672,7 @@ int main(int argc, char** argv) { //check the stream identifier for any now-identified streams while ((eqsi = stream_identifier.PopIdentified())) { //now that we know what patch they are running, start up their client object - struct in_addr in{}; + struct in_addr in{}; in.s_addr = eqsi->GetRemoteIP(); if (RuleB(World, UseBannedIPsTable)) { //Lieka: Check to see if we have the responsibility for blocking IPs. LogInfo("Checking inbound connection [{}] against BannedIPs table", inet_ntoa(in)); @@ -596,19 +688,26 @@ int main(int argc, char** argv) { } } if (!RuleB(World, UseBannedIPsTable)) { - LogInfo("New connection from [{}]:[{}], processing connection", inet_ntoa(in), ntohs(eqsi->GetRemotePort())); + LogInfo( + "New connection from [{}]:[{}], processing connection", + inet_ntoa(in), + ntohs(eqsi->GetRemotePort()) + ); auto client = new Client(eqsi); // @merth: client->zoneattempt=0; client_list.Add(client); } } + event_scheduler.Process(&zoneserver_list); + client_list.Process(); if (PurgeInstanceTimer.Check()) { database.PurgeExpiredInstances(); database.PurgeAllDeletedDataBuckets(); ExpeditionDatabase::PurgeExpiredCharacterLockouts(); + CharacterTaskTimersRepository::DeleteWhere(database, "expire_time <= NOW()"); } if (EQTimeTimer.Check()) { @@ -624,14 +723,18 @@ int main(int argc, char** argv) { launcher_list.Process(); LFPGroupList.Process(); adventure_manager.Process(); - expedition_state.Process(); + dynamic_zone_manager.Process(); if (InterserverTimer.Check()) { InterserverTimer.Start(); database.ping(); content_db.ping(); - std::string window_title = StringFormat("World: %s Clients: %i", Config->LongName.c_str(), client_list.GetClientCount()); + std::string window_title = StringFormat( + "World: %s Clients: %i", + Config->LongName.c_str(), + client_list.GetClientCount() + ); UpdateWindowTitle(window_title); } @@ -648,12 +751,14 @@ int main(int argc, char** argv) { return 0; } -void CatchSignal(int sig_num) { +void CatchSignal(int sig_num) +{ LogInfo("Caught signal [{}]", sig_num); RunLoops = false; } -void UpdateWindowTitle(char* iNewTitle) { +void UpdateWindowTitle(char *iNewTitle) +{ #ifdef _WINDOWS char tmp[500]; if (iNewTitle) { @@ -666,18 +771,23 @@ void UpdateWindowTitle(char* iNewTitle) { #endif } -void CheckForServerScript(bool force_download) { +void CheckForServerScript(bool force_download) +{ /* Fetch EQEmu Server script */ if (!std::ifstream("eqemu_server.pl") || force_download) { - if(force_download) - std::remove("eqemu_server.pl"); /* Delete local before fetch */ + if (force_download) { + std::remove("eqemu_server.pl"); + } /* Delete local before fetch */ std::cout << "Pulling down EQEmu Server Maintenance Script (eqemu_server.pl)..." << std::endl; #ifdef _WIN32 if(system("perl -MLWP::UserAgent -e \"require LWP::UserAgent; my $ua = LWP::UserAgent->new; $ua->timeout(10); $ua->env_proxy; my $response = $ua->get('https://raw.githubusercontent.com/EQEmu/Server/master/utils/scripts/eqemu_server.pl'); if ($response->is_success){ open(FILE, '> eqemu_server.pl'); print FILE $response->decoded_content; close(FILE); }\"")); #else - if(system("wget -N --no-check-certificate --quiet -O eqemu_server.pl https://raw.githubusercontent.com/EQEmu/Server/master/utils/scripts/eqemu_server.pl")); + if (system( + "wget -N --no-check-certificate --quiet -O eqemu_server.pl https://raw.githubusercontent.com/EQEmu/Server/master/utils/scripts/eqemu_server.pl" + )) {} #endif } } + diff --git a/world/shared_task_manager.cpp b/world/shared_task_manager.cpp new file mode 100644 index 000000000..541f35712 --- /dev/null +++ b/world/shared_task_manager.cpp @@ -0,0 +1,1820 @@ +#include "shared_task_manager.h" +#include "cliententry.h" +#include "clientlist.h" +#include "dynamic_zone.h" +#include "dynamic_zone_manager.h" +#include "zonelist.h" +#include "zoneserver.h" +#include "shared_task_world_messaging.h" +#include "../common/repositories/character_data_repository.h" +#include "../common/repositories/character_task_timers_repository.h" +#include "../common/repositories/shared_task_members_repository.h" +#include "../common/repositories/shared_task_activity_state_repository.h" +#include "../common/repositories/completed_shared_tasks_repository.h" +#include "../common/repositories/completed_shared_task_members_repository.h" +#include "../common/repositories/completed_shared_task_activity_state_repository.h" +#include "../common/repositories/shared_task_dynamic_zones_repository.h" +#include + +extern ClientList client_list; +extern ZSList zoneserver_list; + +SharedTaskManager *SharedTaskManager::SetDatabase(Database *db) +{ + SharedTaskManager::m_database = db; + + return this; +} + +SharedTaskManager *SharedTaskManager::SetContentDatabase(Database *db) +{ + SharedTaskManager::m_content_database = db; + + return this; +} + +std::vector SharedTaskManager::GetRequestMembers( + uint32 requestor_character_id, + const std::vector &characters +) +{ + std::vector request_members = {}; + request_members.reserve(characters.size()); + + for (const auto &character : characters) { + SharedTaskMember member = {}; + member.character_id = character.id; + member.character_name = character.name; + + // if the solo/raid/group member is a leader, make sure we tag it as such + if (character.id == requestor_character_id) { + member.is_leader = true; + } + + request_members.emplace_back(member); + } + + return request_members; +} + +void SharedTaskManager::AttemptSharedTaskCreation( + uint32 requested_task_id, + uint32 requested_character_id, + uint32 npc_type_id +) +{ + auto task = GetSharedTaskDataByTaskId(requested_task_id); + if (task.id != 0 && task.type == TASK_TYPE_SHARED) { + LogTasksDetail( + "[AttemptSharedTaskCreation] Found Shared Task ({}) [{}]", + requested_task_id, + task.title + ); + } + + // shared task validation + auto request = SharedTask::GetRequestCharacters(*m_database, requested_character_id); + if (!CanRequestSharedTask(task.id, requested_character_id, request)) { + LogTasksDetail("[AttemptSharedTaskCreation] Shared task validation failed"); + return; + } + + auto request_members = GetRequestMembers(requested_character_id, request.characters); + if (!request_members.empty()) { + for (auto &m: request_members) { + LogTasksDetail( + "[AttemptSharedTaskCreation] Request Members ({})", + m.character_id + ); + } + } + + if (request_members.empty()) { + LogTasksDetail("[AttemptSharedTaskCreation] No additional request members found... Just leader"); + } + + // new shared task instance + auto new_shared_task = SharedTask{}; + auto activities = TaskActivitiesRepository::GetWhere(*m_content_database, fmt::format("taskid = {}", task.id)); + + // new shared task db object + auto shared_task_entity = SharedTasksRepository::NewEntity(); + shared_task_entity.task_id = (int) requested_task_id; + shared_task_entity.accepted_time = static_cast(std::time(nullptr)); + shared_task_entity.expire_time = task.duration > 0 ? (std::time(nullptr) + task.duration) : 0; + + auto created_db_shared_task = SharedTasksRepository::InsertOne(*m_database, shared_task_entity); + + // active record + new_shared_task.SetDbSharedTask(created_db_shared_task); + + // request timer lockouts + std::vector task_timers; + task_timers.reserve(request_members.size()); + + // persist members + std::vector shared_task_db_members = {}; + shared_task_db_members.reserve(request_members.size()); + for (auto &m: request_members) { + auto e = SharedTaskMembersRepository::NewEntity(); + + e.character_id = m.character_id; + e.is_leader = (m.is_leader ? 1 : 0); + e.shared_task_id = new_shared_task.GetDbSharedTask().id; + + shared_task_db_members.emplace_back(e); + + new_shared_task.AddCharacterToMemberHistory(m.character_id); // memory member history + + if (task.request_timer_seconds > 0) { + auto timer = CharacterTaskTimersRepository::NewEntity(); + timer.character_id = m.character_id; + timer.task_id = task.id; + timer.timer_type = static_cast(TaskTimerType::Request); + timer.expire_time = shared_task_entity.accepted_time + task.request_timer_seconds; + + task_timers.emplace_back(timer); + } + } + + SharedTaskMembersRepository::InsertMany(*m_database, shared_task_db_members); + + if (!task_timers.empty()) { + CharacterTaskTimersRepository::InsertMany(*m_database, task_timers); + } + + // activity state (memory) + std::vector shared_task_activity_state = {}; + shared_task_activity_state.reserve(activities.size()); + for (auto &a: activities) { + + // entry + auto e = SharedTaskActivityStateEntry{}; + e.activity_id = a.activityid; + e.done_count = 0; + e.max_done_count = a.goalcount; + + shared_task_activity_state.emplace_back(e); + } + + // activity state (database) + std::vector shared_task_db_activities = {}; + shared_task_db_activities.reserve(activities.size()); + for (auto &a: activities) { + + // entry + auto e = SharedTaskActivityStateRepository::NewEntity(); + e.shared_task_id = new_shared_task.GetDbSharedTask().id; + e.activity_id = a.activityid; + e.done_count = 0; + + shared_task_db_activities.emplace_back(e); + } + + SharedTaskActivityStateRepository::InsertMany(*m_database, shared_task_db_activities); + + // state + new_shared_task.SetSharedTaskActivityState(shared_task_activity_state); + + // set database data in memory to make it easier for any later referencing + new_shared_task.SetTaskData(task); + new_shared_task.SetTaskActivityData(activities); + new_shared_task.SetMembers(request_members); + + // add to shared tasks list + m_shared_tasks.emplace_back(new_shared_task); + + // send accept to members + for (auto &m: request_members) { + // only requester (leader) receives back the npc context to trigger task accept event + uint32_t npc_context_id = m.character_id == requested_character_id ? npc_type_id : 0; + SendAcceptNewSharedTaskPacket( + m.character_id, + requested_task_id, + npc_context_id, + shared_task_entity.accepted_time + ); + } + SendSharedTaskMemberListToAllMembers(&new_shared_task); + + LogTasks( + "[AttemptSharedTaskCreation] shared_task_id [{}] created successfully | task_id [{}] member_count [{}] activity_count [{}] current tasks in state [{}]", + new_shared_task.GetDbSharedTask().id, + task.id, + request_members.size(), + shared_task_activity_state.size(), + m_shared_tasks.size() + ); +} + +void SharedTaskManager::AttemptSharedTaskRemoval( + uint32 requested_task_id, + uint32 requested_character_id, + bool remove_from_db // inherited from zone logic - we're just passing through +) +{ + auto task = GetSharedTaskDataByTaskId(requested_task_id); + if (task.id != 0 && task.type == TASK_TYPE_SHARED) { + LogTasksDetail( + "[AttemptSharedTaskRemoval] Found Shared Task data ({}) [{}]", + requested_task_id, + task.title + ); + } + + auto t = FindSharedTaskByTaskIdAndCharacterId(requested_task_id, requested_character_id); + if (t) { + auto removed = t->FindMemberFromCharacterID(requested_character_id); + + // remove self + RemovePlayerFromSharedTask(t, requested_character_id); + SendRemovePlayerFromSharedTaskPacket( + requested_character_id, + requested_task_id, + remove_from_db + ); + + // inform clients of removal of self + SendSharedTaskMemberRemovedToAllMembers(t, removed.character_name); + + client_list.SendCharacterMessageID( + requested_character_id, Chat::Yellow, + SharedTaskMessage::PLAYER_HAS_BEEN_REMOVED, {removed.character_name, task.title} + ); + + if (removed.is_leader) { + ChooseNewLeader(t); + } + } +} + +void SharedTaskManager::RemoveEveryoneFromSharedTask(SharedTask *t, uint32 requested_character_id) +{ + // caller validates leader + LogTasksDetail("[RemoveEveryoneFromSharedTask] Leader [{}]", requested_character_id); + + // inform clients of removal + for (auto &m: t->GetMembers()) { + LogTasksDetail( + "[RemoveEveryoneFromSharedTask] Sending removal to [{}] task_id [{}]", + m.character_id, + t->GetTaskData().id + ); + + SendRemovePlayerFromSharedTaskPacket(m.character_id, t->GetTaskData().id, true); + + client_list.SendCharacterMessageID( + m.character_id, Chat::Yellow, + SharedTaskMessage::YOU_HAVE_BEEN_REMOVED, {t->GetTaskData().title} + ); + } + + client_list.SendCharacterMessageID( + requested_character_id, + Chat::Red, + SharedTaskMessage::PLAYER_HAS_BEEN_REMOVED, + {"Everyone", t->GetTaskData().title} + ); + + RemoveAllMembersFromDynamicZones(t); + + // persistence + DeleteSharedTask(t->GetDbSharedTask().id); + + PrintSharedTaskState(); +} + +void SharedTaskManager::DeleteSharedTask(int64 shared_task_id) +{ + LogTasksDetail( + "[DeleteSharedTask] shared_task_id [{}]", + shared_task_id + ); + + // remove internally + m_shared_tasks.erase( + std::remove_if( + m_shared_tasks.begin(), + m_shared_tasks.end(), + [&](SharedTask const &s) { + return s.GetDbSharedTask().id == shared_task_id; + } + ), + m_shared_tasks.end() + ); + + // database + SharedTasksRepository::DeleteWhere(*m_database, fmt::format("id = {}", shared_task_id)); + SharedTaskMembersRepository::DeleteWhere(*m_database, fmt::format("shared_task_id = {}", shared_task_id)); + SharedTaskActivityStateRepository::DeleteWhere(*m_database, fmt::format("shared_task_id = {}", shared_task_id)); + SharedTaskDynamicZonesRepository::DeleteWhere(*m_database, fmt::format("shared_task_id = {}", shared_task_id)); +} + +void SharedTaskManager::LoadSharedTaskState() +{ + LogTasksDetail("[LoadSharedTaskState] Restoring state from the database"); + + // load shared tasks + std::vector shared_tasks = {}; + + // eager load all activity state data + auto shared_tasks_activity_state_data = SharedTaskActivityStateRepository::All(*m_database); + + // eager load all member state data + auto shared_task_members_data = SharedTaskMembersRepository::All(*m_database); + + // load character data for member names + std::vector shared_task_character_data; + if (!shared_task_members_data.empty()) { + std::vector character_ids; + for (const auto &m: shared_task_members_data) { + character_ids.emplace_back(m.character_id); + } + + shared_task_character_data = CharacterDataRepository::GetWhere( + *m_database, + fmt::format("id IN ({})", fmt::join(character_ids, ",")) + ); + } + + auto shared_task_dynamic_zones_data = SharedTaskDynamicZonesRepository::All(*m_database); + + // load shared tasks not already completed + auto st = SharedTasksRepository::GetWhere(*m_database, "TRUE"); + shared_tasks.reserve(st.size()); + for (auto &s: st) { + SharedTask ns = {}; + + LogTasksDetail( + "[LoadSharedTaskState] Loading shared_task_id [{}] task_id [{}]", + s.id, + s.task_id + ); + + // shared task db data + ns.SetDbSharedTask(s); + + // set database task data for internal referencing + auto task_data = GetSharedTaskDataByTaskId(s.task_id); + + LogTasksDetail("[LoadSharedTaskState] [GetSharedTaskDataByTaskId] task_id [{}]", task_data.id); + + ns.SetTaskData(task_data); + + // set database task data for internal referencing + auto activities_data = GetSharedTaskActivityDataByTaskId(s.task_id); + ns.SetTaskActivityData(activities_data); + + // load activity state into memory + std::vector shared_task_activity_state = {}; + + // loop through shared task activity state data referencing from memory instead of + // querying inside this loop each time + for (auto &sta: shared_tasks_activity_state_data) { + + // filter by current shared task id + if (sta.shared_task_id == s.id) { + + auto e = SharedTaskActivityStateEntry{}; + e.activity_id = sta.activity_id; + e.done_count = sta.done_count; + + // get max done count from activities data + // loop through activities data in memory and grep on task_id, activity_id to pull goalcount + for (auto &ad: activities_data) { + if (ad.taskid == s.task_id && ad.activityid == sta.activity_id) { + LogTasksDetail( + "[LoadSharedTaskState] shared_task_id [{}] task_id [{}] activity_id [{}] done_count [{}] max_done_count (goalcount) [{}]", + s.id, + s.task_id, + sta.activity_id, + e.done_count, + ad.goalcount + ); + + e.max_done_count = ad.goalcount; + e.completed_time = sta.completed_time; + e.updated_time = sta.updated_time; + } + } + + shared_task_activity_state.emplace_back(e); + } + } + + ns.SetSharedTaskActivityState(shared_task_activity_state); + + // members + std::vector shared_task_members = {}; + for (auto &m: shared_task_members_data) { + if (m.shared_task_id == s.id) { + SharedTaskMember member = {}; + member.character_id = m.character_id; + member.is_leader = (m.is_leader ? 1 : 0); + + auto it = std::find_if( + shared_task_character_data.begin(), shared_task_character_data.end(), + [&](const CharacterDataRepository::CharacterData &character) { + return character.id == m.character_id; + } + ); + + if (it != shared_task_character_data.end()) { + member.character_name = it->name; + } + + shared_task_members.emplace_back(member); + + LogTasksDetail( + "[LoadSharedTaskState] shared_task_id [{}] adding member character_id [{}] character_name [{}] is_leader [{}]", + s.id, + member.character_id, + member.character_name, + member.is_leader + ); + + // add member to history (if restoring state from a world restart we lost real past member history) + ns.AddCharacterToMemberHistory(m.character_id); + } + } + + ns.SetMembers(shared_task_members); + + // dynamic zones + for (const auto &dz_entry : shared_task_dynamic_zones_data) { + if (dz_entry.shared_task_id == s.id) { + ns.dynamic_zone_ids.emplace_back(static_cast(dz_entry.dynamic_zone_id)); + + LogTasksDetail( + "[LoadSharedTaskState] shared_task_id [{}] adding dynamic_zone_id [{}]", + s.id, + dz_entry.dynamic_zone_id + ); + } + } + + LogTasks( + "[LoadSharedTaskState] Loaded shared task state | shared_task_id [{}] task_id [{}] task_title [{}] member_count [{}] state_activity_count [{}]", + s.id, + task_data.id, + task_data.title, + ns.GetMembers().size(), + ns.GetActivityState().size() + ); + + shared_tasks.emplace_back(ns); + } + + SetSharedTasks(shared_tasks); + + LogTasks( + "[LoadSharedTaskState] Loaded [{}] shared tasks", + m_shared_tasks.size() + ); + + PrintSharedTaskState(); +} + +SharedTaskManager *SharedTaskManager::LoadTaskData() +{ + m_task_data = TasksRepository::All(*m_content_database); + m_task_activity_data = TaskActivitiesRepository::All(*m_content_database); + + LogTasks("[LoadTaskData] Loaded tasks [{}] activities [{}]", m_task_data.size(), m_task_activity_data.size()); + + return this; +} + +TasksRepository::Tasks SharedTaskManager::GetSharedTaskDataByTaskId(uint32 task_id) +{ + for (auto &t: m_task_data) { + if (t.id == task_id && t.type == TASK_TYPE_SHARED) { + return t; + } + } + + return TasksRepository::NewEntity(); +} + +std::vector +SharedTaskManager::GetSharedTaskActivityDataByTaskId(uint32 task_id) +{ + std::vector activities = {}; + + for (auto &a: m_task_activity_data) { + if (a.taskid == task_id) { + activities.emplace_back(a); + } + } + + return activities; +} + +void SharedTaskManager::SharedTaskActivityUpdate( + uint32 source_character_id, + uint32 task_id, + uint32 activity_id, + uint32 done_count, + bool ignore_quest_update +) +{ + auto shared_task = FindSharedTaskByTaskIdAndCharacterId(task_id, source_character_id); + if (shared_task) { + LogTasksDetail( + "[SharedTaskActivityUpdate] shared_task_id [{}] character_id [{}] task_id [{}] activity_id [{}] done_count [{}]", + shared_task->GetDbSharedTask().id, + source_character_id, + task_id, + activity_id, + done_count + ); + + for (auto &a : shared_task->m_shared_task_activity_state) { + if (a.activity_id == activity_id) { + + // discard updates out of bounds + if (a.done_count == a.max_done_count) { + LogTasksDetail( + "[SharedTaskActivityUpdate] done_count [{}] is greater than max [{}] discarding...", + done_count, + a.max_done_count + ); + return; + } + + // if we are progressing + if (a.done_count < done_count) { + LogTasksDetail( + "[SharedTaskActivityUpdate] Propagating update for shared_task_id [{}] character_id [{}] task_id [{}] activity_id [{}] old_done_count [{}] new_done_count [{}]", + shared_task->GetDbSharedTask().id, + source_character_id, + task_id, + activity_id, + a.done_count, + done_count + ); + + a.done_count = done_count; + a.updated_time = std::time(nullptr); + + // if the update came in larger than the max for whatever reason, clamp + if (a.done_count > a.max_done_count) { + a.done_count = a.max_done_count; + } + + // if the activity is done, lets mark it as such + if (a.done_count == a.max_done_count) { + a.completed_time = std::time(nullptr); + } + + // sync state as each update comes in (for now) + SaveSharedTaskActivityState( + shared_task->GetDbSharedTask().id, + shared_task->m_shared_task_activity_state + ); + + shared_task->SetSharedTaskActivityState(shared_task->m_shared_task_activity_state); + + LogTasksDetail( + "[SharedTaskActivityUpdate] Debug done_count [{}]", + a.done_count + ); + + // loop through members - send update + for (auto &m: shared_task->GetMembers()) { + + // confirm task update to client(s) + auto p = std::make_unique( + ServerOP_SharedTaskUpdate, + sizeof(ServerSharedTaskActivityUpdate_Struct) + ); + + auto d = reinterpret_cast(p->pBuffer); + d->source_character_id = m.character_id; + d->task_id = task_id; + d->activity_id = activity_id; + d->done_count = done_count; + d->ignore_quest_update = ignore_quest_update; + + // get requested character zone server + ClientListEntry *c = client_list.FindCLEByCharacterID(m.character_id); + if (c && c->Server()) { + c->Server()->SendPacket(p.get()); + } + } + + break; + } + + LogTasksDetail( + "[SharedTaskActivityUpdate] Discarding duplicate update for shared_task_id [{}] character_id [{}] task_id [{}] activity_id [{}] done_count [{}] ignore_quest_update [{}]", + shared_task->GetDbSharedTask().id, + source_character_id, + task_id, + activity_id, + done_count, + (ignore_quest_update ? "true" : "false") + ); + } + } + + // check if completed + bool is_shared_task_completed = true; + for (auto &a : shared_task->m_shared_task_activity_state) { + if (a.done_count != a.max_done_count) { + is_shared_task_completed = false; + } + } + + // mark completed + if (is_shared_task_completed) { + auto t = shared_task->GetDbSharedTask(); + if (t.id > 0) { + LogTasksDetail( + "[SharedTaskActivityUpdate] Marking shared task [{}] completed", + shared_task->GetDbSharedTask().id + ); + + // set record + t.completion_time = std::time(nullptr); + t.is_locked = true; + // update database + SharedTasksRepository::UpdateOne(*m_database, t); + // update internally + shared_task->SetDbSharedTask(t); + // record completion + RecordSharedTaskCompletion(shared_task); + // replay timer lockouts + AddReplayTimers(shared_task); + } + } + } +} + +SharedTask *SharedTaskManager::FindSharedTaskByTaskIdAndCharacterId(uint32 task_id, uint32 character_id) +{ + for (auto &s: m_shared_tasks) { + // grep for task + if (s.GetTaskData().id == task_id) { + // find member in shared task + for (auto &m: s.GetMembers()) { + if (m.character_id == character_id) { + return &s; + } + } + } + } + + return nullptr; +} + +void SharedTaskManager::SaveSharedTaskActivityState( + int64 shared_task_id, + std::vector activity_state +) +{ + // transfer from memory to database + std::vector shared_task_db_activities = {}; + shared_task_db_activities.reserve(activity_state.size()); + + for (auto &a: activity_state) { + + // entry + auto e = SharedTaskActivityStateRepository::NewEntity(); + e.shared_task_id = shared_task_id; + e.activity_id = (int) a.activity_id; + e.done_count = (int) a.done_count; + e.completed_time = (int) a.completed_time; + e.updated_time = (int) a.updated_time; + + shared_task_db_activities.emplace_back(e); + } + + SharedTaskActivityStateRepository::DeleteWhere(*m_database, fmt::format("shared_task_id = {}", shared_task_id)); + SharedTaskActivityStateRepository::InsertMany(*m_database, shared_task_db_activities); +} + +bool SharedTaskManager::IsSharedTaskLeader(SharedTask *s, uint32 character_id) +{ + for (auto &m: s->GetMembers()) { + if (m.character_id == character_id && m.is_leader) { + return true; + } + } + + return false; +} + +void SharedTaskManager::SendAcceptNewSharedTaskPacket( + uint32 character_id, + uint32 task_id, + uint32_t npc_context_id, + int accept_time +) +{ + auto p = std::make_unique( + ServerOP_SharedTaskAcceptNewTask, + sizeof(ServerSharedTaskRequest_Struct) + ); + + auto d = reinterpret_cast(p->pBuffer); + d->requested_character_id = character_id; + d->requested_task_id = task_id; + d->requested_npc_type_id = npc_context_id; + d->accept_time = accept_time; + + // get requested character zone server + ClientListEntry *cle = client_list.FindCLEByCharacterID(character_id); + if (cle && cle->Server()) { + cle->Server()->SendPacket(p.get()); + } +} + +void SharedTaskManager::SendRemovePlayerFromSharedTaskPacket( + uint32 character_id, + uint32 task_id, + bool remove_from_db +) +{ + // confirm shared task request: inform clients + auto p = std::make_unique( + ServerOP_SharedTaskAttemptRemove, + sizeof(ServerSharedTaskAttemptRemove_Struct) + ); + + auto d = reinterpret_cast(p->pBuffer); + d->requested_character_id = character_id; + d->requested_task_id = task_id; + d->remove_from_db = remove_from_db; + + // get requested character zone server + ClientListEntry *cle = client_list.FindCLEByCharacterID(character_id); + if (cle && cle->Server()) { + cle->Server()->SendPacket(p.get()); + } +} + +void SharedTaskManager::SendSharedTaskMemberList(uint32 character_id, const std::vector &members) +{ + EQ::Net::DynamicPacket dyn_pack; + dyn_pack.PutSerialize(0, members); + + SendSharedTaskMemberList(character_id, dyn_pack); +} + +void SharedTaskManager::SendSharedTaskMemberList(uint32 character_id, const EQ::Net::DynamicPacket &serialized_members) +{ + // send member list packet + auto p = std::make_unique( + ServerOP_SharedTaskMemberlist, + sizeof(ServerSharedTaskMemberListPacket_Struct) + serialized_members.Length() + ); + + auto d = reinterpret_cast(p->pBuffer); + d->destination_character_id = character_id; + d->cereal_size = static_cast(serialized_members.Length()); + memcpy(d->cereal_serialized_members, serialized_members.Data(), serialized_members.Length()); + + // send memberlist + ClientListEntry *cle = client_list.FindCLEByCharacterID(character_id); + if (cle && cle->Server()) { + cle->Server()->SendPacket(p.get()); + } +} + +void SharedTaskManager::SendSharedTaskMemberChange( + uint32 character_id, + int64 shared_task_id, + const std::string &player_name, + bool removed +) +{ + uint32_t size = sizeof(ServerSharedTaskMemberChangePacket_Struct); + auto p = std::make_unique(ServerOP_SharedTaskMemberChange, size); + + auto d = reinterpret_cast(p->pBuffer); + d->destination_character_id = character_id; + d->shared_task_id = shared_task_id; + d->removed = removed; + strn0cpy(d->player_name, player_name.c_str(), sizeof(d->player_name)); + + ClientListEntry *cle = client_list.FindCLEByCharacterID(character_id); + if (cle && cle->Server()) { + cle->Server()->SendPacket(p.get()); + } +} + +void SharedTaskManager::RemovePlayerFromSharedTask(SharedTask *s, uint32 character_id) +{ + SharedTaskMembersRepository::DeleteWhere( + *m_database, + fmt::format( + "shared_task_id = {} and character_id = {}", + s->GetDbSharedTask().id, + character_id + ) + ); + + // remove internally + s->m_members.erase( + std::remove_if( + s->m_members.begin(), + s->m_members.end(), + [&](SharedTaskMember const &m) { + return m.character_id == character_id; + } + ), + s->m_members.end() + ); + + for (const auto &dz_id : s->dynamic_zone_ids) { + auto dz = DynamicZone::FindDynamicZoneByID(dz_id); + if (dz) { + dz->RemoveMember(character_id); + } + } +} + +void SharedTaskManager::PrintSharedTaskState() +{ + for (auto &s: m_shared_tasks) { + auto task = GetSharedTaskDataByTaskId(s.GetDbSharedTask().task_id); + + LogTasksDetail("[PrintSharedTaskState] # Shared Task"); + + LogTasksDetail( + "[PrintSharedTaskState] shared_task_id [{}] task_id [{}] task_title [{}] member_count [{}] state_activity_count [{}]", + s.GetDbSharedTask().id, + task.id, + task.title, + s.GetMembers().size(), + s.GetActivityState().size() + ); + + LogTasksDetail("[PrintSharedTaskState] # Activities"); + + // activity state + for (auto &a: s.m_shared_task_activity_state) { + LogTasksDetail( + "[PrintSharedTaskState] -- activity_id [{}] done_count [{}] max_done_count [{}] completed_time [{}]", + a.activity_id, + a.done_count, + a.max_done_count, + a.completed_time + ); + } + + LogTasksDetail("[PrintSharedTaskState] # Members"); + + // members + for (auto &m: s.m_members) { + LogTasksDetail( + "[PrintSharedTaskState] -- character_id [{}] is_leader [{}]", + m.character_id, + m.is_leader + ); + } + + LogTasksDetail("[PrintSharedTaskState] # Dynamic Zones"); + + for (auto &dz_id: s.dynamic_zone_ids) { + LogTasksDetail( + "[PrintSharedTaskState] -- dynamic_zone_id [{}]", + dz_id + ); + } + } +} + +void SharedTaskManager::RemovePlayerFromSharedTaskByPlayerName(SharedTask *s, const std::string &character_name) +{ + auto member = s->FindMemberFromCharacterName(character_name); + if (member.character_id == 0) { + SendLeaderMessageID(s, Chat::Red, SharedTaskMessage::IS_NOT_MEMBER, {character_name}); + return; + } + + LogTasksDetail( + "[RemovePlayerFromSharedTaskByPlayerName] shared_task_id [{}] character_name [{}]", + s->GetDbSharedTask().id, + character_name + ); + + auto leader = s->GetLeader(); // get leader now for msg in case leader is one removed + + RemovePlayerFromSharedTask(s, member.character_id); + SendRemovePlayerFromSharedTaskPacket( + member.character_id, + s->GetDbSharedTask().task_id, + true + ); + + SendSharedTaskMemberRemovedToAllMembers(s, member.character_name); + + // leader and removed player get server messages (leader sees two messages) + // results in double messages if leader removed self (live behavior) + client_list.SendCharacterMessageID( + leader.character_id, Chat::Yellow, + SharedTaskMessage::PLAYER_HAS_BEEN_REMOVED, {member.character_name, s->GetTaskData().title} + ); + + client_list.SendCharacterMessageID( + member.character_id, Chat::Yellow, + SharedTaskMessage::PLAYER_HAS_BEEN_REMOVED, {member.character_name, s->GetTaskData().title} + ); + + if (member.is_leader) { + ChooseNewLeader(s); + } +} + +void SharedTaskManager::SendSharedTaskMemberListToAllMembers(SharedTask *s) +{ + // serialize once so we don't re-serialize it for every member + EQ::Net::DynamicPacket dyn_pack; + dyn_pack.PutSerialize(0, s->GetMembers()); + + for (auto &m: s->GetMembers()) { + SendSharedTaskMemberList( + m.character_id, + dyn_pack + ); + } +} + +void SharedTaskManager::SendSharedTaskMemberAddedToAllMembers(SharedTask *s, const std::string &player_name) +{ + for (const auto &m : s->GetMembers()) { + SendSharedTaskMemberChange(m.character_id, s->GetDbSharedTask().id, player_name, false); + } +} + +void SharedTaskManager::SendSharedTaskMemberRemovedToAllMembers(SharedTask *s, const std::string &player_name) +{ + for (const auto &m : s->GetMembers()) { + SendSharedTaskMemberChange(m.character_id, s->GetDbSharedTask().id, player_name, true); + } +} + +void SharedTaskManager::MakeLeaderByPlayerName(SharedTask *s, const std::string &character_name) +{ + auto new_leader = s->FindMemberFromCharacterName(character_name); + if (new_leader.character_id == 0) { + SendLeaderMessageID(s, Chat::Red, SharedTaskMessage::IS_NOT_MEMBER, {character_name}); + return; + } + + bool found_new_leader = false; + + std::vector members = s->GetMembers(); + for (auto &m: members) { + LogTasksDetail( + "[MakeLeaderByPlayerName] character_id [{}] m.character_id [{}]", + new_leader.character_id, + m.character_id + ); + + m.is_leader = false; + + // destination character is in shared task, make swap + if (new_leader.character_id == m.character_id) { + found_new_leader = true; + LogTasksDetail( + "[MakeLeaderByPlayerName] shared_task_id [{}] character_name [{}]", + s->GetDbSharedTask().id, + character_name + ); + + m.is_leader = true; + } + } + + if (found_new_leader) { + s->SetMembers(members); + SaveMembers(s, members); + SendSharedTaskMemberListToAllMembers(s); + SendMembersMessageID( + s, Chat::Yellow, SharedTaskMessage::PLAYER_NOW_LEADER, + {new_leader.character_name, s->GetTaskData().title} + ); + + for (const auto &dz_id : s->dynamic_zone_ids) { + auto dz = DynamicZone::FindDynamicZoneByID(dz_id); + if (dz) { + dz->SetNewLeader(static_cast(new_leader.character_id)); + } + } + } +} + +void SharedTaskManager::SaveMembers(SharedTask *s, std::vector members) +{ + std::vector dm = {}; + dm.reserve(members.size()); + for (auto &m: members) { + auto e = SharedTaskMembersRepository::NewEntity(); + + e.character_id = m.character_id; + e.is_leader = (m.is_leader ? 1 : 0); + e.shared_task_id = s->GetDbSharedTask().id; + + dm.emplace_back(e); + } + + SharedTaskMembersRepository::DeleteWhere(*m_database, fmt::format("shared_task_id = {}", s->GetDbSharedTask().id)); + SharedTaskMembersRepository::InsertMany(*m_database, dm); +} + +void SharedTaskManager::InvitePlayerByPlayerName(SharedTask *s, const std::string &player_name) +{ + auto character = CharacterDataRepository::GetWhere( + *m_database, + fmt::format("`name` = '{}' LIMIT 1", EscapeString(player_name)) + ); + + auto character_id = !character.empty() ? character.front().id : 0; + + // we call validation even for an invalid player so error messages occur + if (CanAddPlayer(s, character_id, player_name, false)) { + // send dialogue window + SendSharedTaskInvitePacket(s, character_id); + + // keep track of active invitations at world + QueueActiveInvitation(s->GetDbSharedTask().id, character_id); + } +} + +void SharedTaskManager::SendSharedTaskInvitePacket(SharedTask *s, int64 invited_character_id) +{ + auto leader = s->GetLeader(); + + // found leader + if (leader.character_id > 0) { + + // init packet + auto p = std::make_unique( + ServerOP_SharedTaskInvitePlayer, + sizeof(ServerSharedTaskInvitePlayer_Struct) + ); + + // fill + auto d = reinterpret_cast(p->pBuffer); + d->requested_character_id = invited_character_id; + d->invite_shared_task_id = s->GetDbSharedTask().id; + strn0cpy(d->inviter_name, leader.character_name.c_str(), sizeof(d->inviter_name)); + strn0cpy(d->task_name, s->GetTaskData().title.c_str(), sizeof(d->task_name)); + + // get requested character zone server + ClientListEntry *cle = client_list.FindCLEByCharacterID(invited_character_id); + if (cle && cle->Server()) { + SendLeaderMessageID(s, Chat::Yellow, SharedTaskMessage::SEND_INVITE_TO, {cle->name()}); + cle->Server()->SendPacket(p.get()); + } + } +} + +void SharedTaskManager::AddPlayerByCharacterIdAndName( + SharedTask *s, + int64 character_id, + const std::string &character_name +) +{ + // fetch + std::vector members = s->GetMembers(); + + // create + auto new_member = SharedTaskMember{}; + new_member.character_id = character_id; + new_member.character_name = character_name; + + bool does_member_exist = false; + + for (auto &m: s->GetMembers()) { + if (m.character_id == character_id) { + does_member_exist = true; + } + } + + if (!does_member_exist && CanAddPlayer(s, character_id, character_name, true)) { + members.push_back(new_member); + + // add request timer (validation will prevent non-expired duplicates) + if (s->GetTaskData().request_timer_seconds > 0) { + auto expire_time = s->GetDbSharedTask().accepted_time + s->GetTaskData().request_timer_seconds; + if (expire_time > std::time(nullptr)) // not already expired + { + auto timer = CharacterTaskTimersRepository::NewEntity(); + timer.character_id = character_id; + timer.task_id = s->GetDbSharedTask().task_id; + timer.timer_type = static_cast(TaskTimerType::Request); + timer.expire_time = expire_time; + + CharacterTaskTimersRepository::InsertOne(*m_database, timer); + } + } + + // inform client + SendAcceptNewSharedTaskPacket(character_id, s->GetTaskData().id, 0, s->GetDbSharedTask().accepted_time); + + // add to shared task + SendSharedTaskMemberAddedToAllMembers(s, character_name); + s->SetMembers(members); + SaveMembers(s, members); + SendSharedTaskMemberList(character_id, s->GetMembers()); // new member gets full member list + s->AddCharacterToMemberHistory(character_id); + + // add to dzs tied to shared task + for (const auto &dz_id : s->dynamic_zone_ids) { + auto dz = DynamicZone::FindDynamicZoneByID(dz_id); + if (dz) { + auto status = DynamicZoneMemberStatus::Online; + dz->AddMember({static_cast(character_id), character_name, status}); + } + } + } +} + +SharedTask *SharedTaskManager::FindSharedTaskById(int64 shared_task_id) +{ + for (auto &s: m_shared_tasks) { + if (s.GetDbSharedTask().id == shared_task_id) { + return &s; + } + } + + return nullptr; +} + +void SharedTaskManager::QueueActiveInvitation(int64 shared_task_id, int64 character_id) +{ + LogTasksDetail( + "[QueueActiveInvitation] shared_task_id [{}] character_id [{}]", + shared_task_id, + character_id + ); + + auto active_invitation = SharedTaskActiveInvitation{}; + active_invitation.shared_task_id = shared_task_id; + active_invitation.character_id = character_id; + + m_active_invitations.emplace_back(active_invitation); +} + +bool SharedTaskManager::IsInvitationActive(uint32 shared_task_id, uint32 character_id) +{ + LogTasksDetail( + "[IsInvitationActive] shared_task_id [{}] character_id [{}]", + shared_task_id, + character_id + ); + + for (auto &i: m_active_invitations) { + if (i.character_id == character_id && i.shared_task_id == shared_task_id) { + return true; + } + } + + return false; +} + +void SharedTaskManager::RemoveActiveInvitation(int64 shared_task_id, int64 character_id) +{ + LogTasksDetail( + "[RemoveActiveInvitation] shared_task_id [{}] character_id [{}] pre_removal_count [{}]", + shared_task_id, + character_id, + m_active_invitations.size() + ); + + // remove internally + m_active_invitations.erase( + std::remove_if( + m_active_invitations.begin(), + m_active_invitations.end(), + [&](SharedTaskActiveInvitation const &i) { + return i.shared_task_id == shared_task_id && i.character_id == character_id; + } + ), + m_active_invitations.end() + ); + + LogTasksDetail( + "[RemoveActiveInvitation] shared_task_id [{}] character_id [{}] post_removal_count [{}]", + shared_task_id, + character_id, + m_active_invitations.size() + ); +} + +void SharedTaskManager::RemoveActiveInvitationByCharacterID(uint32_t character_id) +{ + m_active_invitations.erase( + std::remove_if( + m_active_invitations.begin(), m_active_invitations.end(), + [&](SharedTaskActiveInvitation const &i) { + return i.character_id == character_id; + } + ), m_active_invitations.end() + ); +} + +void SharedTaskManager::CreateDynamicZone(SharedTask *shared_task, DynamicZone &dz_request) +{ + std::vector dz_members; + for (const auto &member : shared_task->GetMembers()) { + dz_members.emplace_back(member.character_id, member.character_name); + if (member.is_leader) { + dz_request.SetLeader({member.character_id, member.character_name}); + } + } + + auto new_dz = dynamic_zone_manager.CreateNew(dz_request, dz_members); + if (new_dz) { + auto shared_task_dz = SharedTaskDynamicZonesRepository::NewEntity(); + shared_task_dz.shared_task_id = shared_task->GetDbSharedTask().id; + shared_task_dz.dynamic_zone_id = new_dz->GetID(); + + SharedTaskDynamicZonesRepository::InsertOne(*m_database, shared_task_dz); + + shared_task->dynamic_zone_ids.emplace_back(new_dz->GetID()); + } +} + +void SharedTaskManager::SendLeaderMessage(SharedTask *shared_task, int chat_type, const std::string &message) +{ + if (!shared_task) { + return; + } + + for (const auto &member : shared_task->GetMembers()) { + if (member.is_leader) { + client_list.SendCharacterMessage(member.character_id, chat_type, message); + break; + } + } +} + +void SharedTaskManager::SendLeaderMessageID( + SharedTask *shared_task, int chat_type, + int eqstr_id, std::initializer_list args +) +{ + if (!shared_task) { + return; + } + + for (const auto &member : shared_task->GetMembers()) { + if (member.is_leader) { + client_list.SendCharacterMessageID(member.character_id, chat_type, eqstr_id, args); + break; + } + } +} + +void SharedTaskManager::SendMembersMessage(SharedTask *shared_task, int chat_type, const std::string &message) +{ + if (!shared_task) { + return; + } + + for (const auto &member : shared_task->GetMembers()) { + client_list.SendCharacterMessage(member.character_id, chat_type, message); + } +} + +void SharedTaskManager::SendMembersMessageID( + SharedTask *shared_task, + int chat_type, + int eqstr_id, + std::initializer_list args +) +{ + if (!shared_task || shared_task->GetMembers().empty()) { + return; + } + + // serialize here since using client_list methods would re-serialize for every member + SerializeBuffer serialized_args; + for (const auto &arg : args) { + serialized_args.WriteString(arg); + } + + uint32_t args_size = static_cast(serialized_args.size()); + uint32_t pack_size = sizeof(CZClientMessageString_Struct) + args_size; + auto pack = std::make_unique(ServerOP_CZClientMessageString, pack_size); + auto buf = reinterpret_cast(pack->pBuffer); + buf->string_id = eqstr_id; + buf->chat_type = chat_type; + buf->args_size = args_size; + memcpy(buf->args, serialized_args.buffer(), serialized_args.size()); + + for (const auto &member : shared_task->GetMembers()) { + auto character = client_list.FindCLEByCharacterID(member.character_id); + if (character && character->Server()) { + strn0cpy(buf->client_name, character->name(), sizeof(buf->client_name)); + character->Server()->SendPacket(pack.get()); + } + } +} + +bool SharedTaskManager::CanRequestSharedTask( + uint32_t task_id, + uint32_t character_id, + const SharedTaskRequestCharacters &request +) +{ + auto task = GetSharedTaskDataByTaskId(task_id); + if (task.id == 0) { + return false; + } + + // this attempts to follow live validation order + + // check if any party members are already in a shared task + auto shared_task_members = FindCharactersInSharedTasks(request.character_ids); + if (!shared_task_members.empty()) { + // messages for every character already in a shared task + for (const auto &member : shared_task_members) { + auto it = std::find_if( + request.characters.begin(), request.characters.end(), + [&](const CharacterDataRepository::CharacterData &char_data) { + return char_data.id == member; + } + ); + + if (it != request.characters.end()) { + if (it->id == character_id) { + client_list.SendCharacterMessageID( + character_id, + Chat::Red, + SharedTaskMessage::NO_REQUEST_BECAUSE_HAVE_ONE + ); + } + else if (request.group_type == SharedTaskRequestGroupType::Group) { + client_list.SendCharacterMessageID( + character_id, + Chat::Red, + SharedTaskMessage::NO_REQUEST_BECAUSE_GROUP_HAS_ONE, + {it->name} + ); + } + else { + client_list.SendCharacterMessageID( + character_id, + Chat::Red, + SharedTaskMessage::NO_REQUEST_BECAUSE_RAID_HAS_ONE, + {it->name} + ); + } + } + } + + return false; + } + + // check if any party member's minimum level is too low (pre-2014 this was average level) + if (task.minlevel > 0 && request.lowest_level < task.minlevel) { + client_list.SendCharacterMessageID(character_id, Chat::Red, SharedTaskMessage::AVG_LVL_LOW); + return false; + } + + // check if any party member's maximum level is too high (pre-2014 this was average level) + if (task.maxlevel > 0 && request.highest_level > task.maxlevel) { + client_list.SendCharacterMessageID(character_id, Chat::Red, SharedTaskMessage::AVG_LVL_HIGH); + return false; + } + + // allow gm/dev bypass for minimum player count requirements + auto requester = client_list.FindCLEByCharacterID(character_id); + bool is_gm = (requester && requester->GetGM()); + + // check if party member count is below the minimum + if (!is_gm && task.min_players > 0 && request.characters.size() < task.min_players) { + client_list.SendCharacterMessageID( + character_id, + Chat::Red, + SharedTaskMessage::SHARED_TASK_NOT_MEET_MIN_NUM_PLAYER + ); + return false; + } + + // check if party member count is above the maximum + // todo: live creates the shared task but truncates members if it exceeds max (sorted by leader and raid group numbers) + if (task.max_players > 0 && request.characters.size() > task.max_players) { + client_list.SendCharacterMessageID(character_id, Chat::Red, SharedTaskMessage::PARTY_EXCEED_MAX_PLAYER); + return false; + } + + // check if party level spread exceeds task's maximum + if (task.level_spread > 0 && (request.highest_level - request.lowest_level) > task.level_spread) { + client_list.SendCharacterMessageID(character_id, Chat::Red, SharedTaskMessage::LVL_SPREAD_HIGH); + return false; + } + + // check if any party members have a replay or request timer for the task (limit 1, replay checked first) + auto character_task_timers = CharacterTaskTimersRepository::GetWhere( + *m_database, fmt::format( + "character_id IN ({}) AND task_id = {} AND expire_time > NOW() ORDER BY timer_type ASC LIMIT 1", + fmt::join(request.character_ids, ","), task_id + ) + ); + + if (!character_task_timers.empty()) { + auto timer_type = static_cast(character_task_timers.front().timer_type); + auto seconds = character_task_timers.front().expire_time - std::time(nullptr); + auto days = fmt::format_int(seconds / 86400).str(); + auto hours = fmt::format_int((seconds / 3600) % 24).str(); + auto mins = fmt::format_int((seconds / 60) % 60).str(); + + if (character_task_timers.front().character_id == character_id) { + if (timer_type == TaskTimerType::Replay) { + client_list.SendCharacterMessageID( + character_id, + Chat::Red, + SharedTaskMessage::YOU_MUST_WAIT_REPLAY_TIMER, {days, hours, mins} + ); + } + else if (timer_type == TaskTimerType::Request) { + client_list.SendCharacterMessage( + character_id, + Chat::Red, fmt::format( + SharedTaskMessage::GetEQStr(SharedTaskMessage::YOU_MUST_WAIT_REQUEST_TIMER), days, hours, mins + ) + ); + } + } + else { + auto it = std::find_if( + request.characters.begin(), request.characters.end(), + [&](const CharacterDataRepository::CharacterData &char_data) { + return char_data.id == character_task_timers.front().character_id; + } + ); + + if (it != request.characters.end() && timer_type == TaskTimerType::Replay) { + client_list.SendCharacterMessageID( + character_id, + Chat::Red, + SharedTaskMessage::PLAYER_MUST_WAIT_REPLAY_TIMER, + {it->name, days, hours, mins} + ); + } + else if (it != request.characters.end() && timer_type == TaskTimerType::Request) { + client_list.SendCharacterMessage( + character_id, + Chat::Red, + fmt::format( + SharedTaskMessage::GetEQStr(SharedTaskMessage::PLAYER_MUST_WAIT_REQUEST_TIMER), + it->name, + days, + hours, + mins + ) + ); + } + } + + return false; + } + + return true; +} + +bool SharedTaskManager::CanAddPlayer(SharedTask *s, uint32_t character_id, std::string player_name, bool accepted) +{ + // this attempts to follow live validation order + + bool allow_invite = true; + + // check if task is locked + if (s->GetDbSharedTask().is_locked) { + SendLeaderMessageID(s, Chat::Red, SharedTaskMessage::TASK_NOT_ALLOWING_PLAYERS_AT_TIME); + allow_invite = false; + } + + // check if player is online and in cle (other checks require online) + auto cle = client_list.FindCLEByCharacterID(character_id); + if (!cle || !cle->Server()) { + SendLeaderMessageID(s, Chat::Red, SharedTaskMessage::PLAYER_NOT_ONLINE_TO_ADD, {player_name}); + SendLeaderMessageID(s, Chat::Red, SharedTaskMessage::COULD_NOT_BE_INVITED, {player_name}); + return false; + } + + player_name = cle->name(); + + // check if player is already in a shared task + auto shared_task_members = SharedTaskMembersRepository::GetWhere( + *m_database, + fmt::format("character_id = {} LIMIT 1", character_id) + ); + + if (!shared_task_members.empty()) { + auto shared_task_id = shared_task_members.front().shared_task_id; + if (shared_task_id == s->GetDbSharedTask().id) { + SendLeaderMessageID(s, Chat::Red, SharedTaskMessage::CANT_ADD_PLAYER_ALREADY_MEMBER, {player_name}); + } + else { + SendLeaderMessageID(s, Chat::Red, SharedTaskMessage::CANT_ADD_PLAYER_ALREADY_ASSIGNED, {player_name}); + } + allow_invite = false; + } + + // check if player has an outstanding invite + for (const auto &invite : m_active_invitations) { + if (invite.character_id == character_id) { + if (invite.shared_task_id == s->GetDbSharedTask().id) { + SendLeaderMessageID( + s, + Chat::Red, + SharedTaskMessage::PLAYER_ALREADY_OUTSTANDING_INVITATION_THIS, + {player_name} + ); + } + else { + SendLeaderMessageID(s, Chat::Red, SharedTaskMessage::PLAYER_ALREADY_OUTSTANDING_ANOTHER, {player_name}); + } + allow_invite = false; + break; + } + } + + // check if player has a replay or request timer lockout + // todo: live allows characters with a request timer to be re-invited if they quit, but only until they zone? (investigate/edge case) + auto task_timers = CharacterTaskTimersRepository::GetWhere( + *m_database, fmt::format( + "character_id = {} AND task_id = {} AND expire_time > NOW() ORDER BY timer_type ASC LIMIT 1", + character_id, s->GetDbSharedTask().task_id + )); + + if (!task_timers.empty()) { + auto timer_type = static_cast(task_timers.front().timer_type); + auto seconds = task_timers.front().expire_time - std::time(nullptr); + auto days = fmt::format_int(seconds / 86400).str(); + auto hours = fmt::format_int((seconds / 3600) % 24).str(); + auto mins = fmt::format_int((seconds / 60) % 60).str(); + + if (timer_type == TaskTimerType::Replay) { + SendLeaderMessageID( + s, + Chat::Red, + SharedTaskMessage::CANT_ADD_PLAYER_REPLAY_TIMER, {player_name, days, hours, mins} + ); + } + else { + SendLeaderMessage( + s, + Chat::Red, + fmt::format( + SharedTaskMessage::GetEQStr(SharedTaskMessage::CANT_ADD_PLAYER_REQUEST_TIMER), + player_name, + days, + hours, + mins + ) + ); + } + + allow_invite = false; + } + + // check if task has maximum players + if (s->GetTaskData().max_players > 0 && s->GetMembers().size() >= s->GetTaskData().max_players) { + auto max = fmt::format_int(s->GetTaskData().max_players).str(); + SendLeaderMessageID(s, Chat::Red, SharedTaskMessage::CANT_ADD_PLAYER_MAX_PLAYERS, {max}); + allow_invite = false; + } + + // check if task would exceed max level spread + if (s->GetTaskData().level_spread > 0) { + auto characters = CharacterDataRepository::GetWhere( + *m_database, + fmt::format( + "id IN (select character_id from shared_task_members where shared_task_id = {})", + s->GetDbSharedTask().id + ) + ); + + int lowest_level = cle->level(); + int highest_level = cle->level(); + + for (const auto &character : characters) { + lowest_level = std::min(lowest_level, character.level); + highest_level = std::max(highest_level, character.level); + } + + if ((highest_level - lowest_level) > s->GetTaskData().level_spread) { + auto max_spread = fmt::format_int(s->GetTaskData().level_spread).str(); + SendLeaderMessageID(s, Chat::Red, SharedTaskMessage::CANT_ADD_PLAYER_MAX_LEVEL_SPREAD, {max_spread}); + allow_invite = false; + } + } + + // check if player is below minimum level of task (pre-2014 this was average level) + if (s->GetTaskData().minlevel > 0 && cle->level() < s->GetTaskData().minlevel) { + SendLeaderMessageID(s, Chat::Red, SharedTaskMessage::CANT_ADD_PLAYER_FALL_MIN_AVG_LEVEL); + allow_invite = false; + } + + // check if player is above maximum level of task (pre-2014 this was average level) + if (s->GetTaskData().maxlevel > 0 && cle->level() > s->GetTaskData().maxlevel) { + SendLeaderMessageID(s, Chat::Red, SharedTaskMessage::CANT_ADD_PLAYER_MAX_AVERAGE_LEVEL); + allow_invite = false; + } + + if (!allow_invite) { + if (!accepted) { + SendLeaderMessageID(s, Chat::Red, SharedTaskMessage::COULD_NOT_BE_INVITED, {player_name}); + } + else { + SendLeaderMessageID(s, Chat::Red, SharedTaskMessage::ACCEPTED_OFFER_TO_JOIN_BUT_COULD_NOT, {player_name}); + } + } + + return allow_invite; +} + +void SharedTaskManager::RecordSharedTaskCompletion(SharedTask *s) +{ + // shared task + auto t = s->GetDbSharedTask(); + auto ct = CompletedSharedTasksRepository::NewEntity(); + + ct.id = t.id; + ct.task_id = t.task_id; + ct.accepted_time = t.accepted_time; + ct.expire_time = t.expire_time; + ct.completion_time = t.completion_time; + ct.is_locked = t.is_locked; + + CompletedSharedTasksRepository::InsertOne(*m_database, ct); + + // completed members + std::vector completed_members = {}; + + for (auto &m: s->GetMembers()) { + auto cm = CompletedSharedTaskMembersRepository::NewEntity(); + + cm.shared_task_id = t.id; + cm.character_id = m.character_id; + cm.is_leader = m.is_leader; + + completed_members.emplace_back(cm); + } + + CompletedSharedTaskMembersRepository::InsertMany(*m_database, completed_members); + + // activities + std::vector completed_states = {}; + + for (auto &a: s->GetActivityState()) { + auto cs = CompletedSharedTaskActivityStateRepository::NewEntity(); + + cs.shared_task_id = t.id; + cs.activity_id = (int) a.activity_id; + cs.done_count = (int) a.done_count; + cs.updated_time = a.updated_time; + cs.completed_time = a.completed_time; + + completed_states.emplace_back(cs); + } + + CompletedSharedTaskActivityStateRepository::InsertMany(*m_database, completed_states); + +} + +void SharedTaskManager::AddReplayTimers(SharedTask *s) +{ + if (s->GetTaskData().replay_timer_seconds > 0) { + auto expire_time = s->GetDbSharedTask().accepted_time + s->GetTaskData().replay_timer_seconds; + auto seconds = expire_time - std::time(nullptr); + if (seconds > 0) // not already expired + { + std::vector task_timers; + + // on live past members of the shared task also receive lockouts (use member history) + for (const auto &member_id : s->member_id_history) { + auto timer = CharacterTaskTimersRepository::NewEntity(); + timer.character_id = member_id; + timer.task_id = s->GetTaskData().id; + timer.timer_type = static_cast(TaskTimerType::Replay); + timer.expire_time = expire_time; + + task_timers.emplace_back(timer); + + client_list.SendCharacterMessage( + member_id, + Chat::Yellow, + fmt::format( + SharedTaskMessage::GetEQStr(SharedTaskMessage::RECEIVED_REPLAY_TIMER), + s->GetTaskData().title, + fmt::format_int(seconds / 86400).c_str(), // days + fmt::format_int((seconds / 3600) % 24).c_str(), // hours + fmt::format_int((seconds / 60) % 60).c_str() // minutes + ) + ); + } + + if (!task_timers.empty()) { + // replay timers replace any existing timer (even if it expires sooner) + // this can occur if a player has a timer for being a past member of + // a shared task but joined another before the first was completed + CharacterTaskTimersRepository::DeleteWhere( + *m_database, + fmt::format( + "task_id = {} AND character_id IN ({})", + s->GetTaskData().id, fmt::join(s->member_id_history, ",") + ) + ); + + CharacterTaskTimersRepository::InsertMany(*m_database, task_timers); + } + } + } +} + +// memory search +std::vector SharedTaskManager::FindCharactersInSharedTasks(const std::vector &find_characters) +{ + std::vector characters = {}; + + for (auto &s: m_shared_tasks) { + // loop through members + for (auto &m: s.GetMembers()) { + // compare members with requested characters + for (auto &find_character_id: find_characters) { + // found character, add to list + if (find_character_id == m.character_id) { + characters.emplace_back(m.character_id); + } + } + } + } + + return characters; +} + +void SharedTaskManager::PurgeAllSharedTasks() +{ + for (auto &shared_task : m_shared_tasks) { + RemoveAllMembersFromDynamicZones(&shared_task); + } + + SharedTasksRepository::Truncate(*m_database); + SharedTaskMembersRepository::Truncate(*m_database); + SharedTaskActivityStateRepository::Truncate(*m_database); + SharedTaskDynamicZonesRepository::Truncate(*m_database); + CompletedSharedTasksRepository::Truncate(*m_database); + CompletedSharedTaskMembersRepository::Truncate(*m_database); + CompletedSharedTaskActivityStateRepository::Truncate(*m_database); + + LoadSharedTaskState(); +} + +void SharedTaskManager::RemoveAllMembersFromDynamicZones(SharedTask *s) +{ + for (const auto &dz_id : s->dynamic_zone_ids) { + auto dz = DynamicZone::FindDynamicZoneByID(dz_id); + if (dz) { + dz->RemoveAllMembers(); + } + } +} + +void SharedTaskManager::ChooseNewLeader(SharedTask *s) +{ + // live doesn't prioritize choosing an online player here + auto members = s->GetMembers(); + auto it = std::find_if( + members.begin(), members.end(), + [&](const SharedTaskMember &member) { + return !member.is_leader; + } + ); + + if (it != members.end()) { + MakeLeaderByPlayerName(s, it->character_name); + } +} + +const std::vector &SharedTaskManager::GetSharedTasks() const +{ + return m_shared_tasks; +} + +void SharedTaskManager::SetSharedTasks(const std::vector &shared_tasks) +{ + SharedTaskManager::m_shared_tasks = shared_tasks; +} + +SharedTaskManager *SharedTaskManager::PurgeExpiredSharedTasks() +{ + auto now = std::time(nullptr); + for (auto &s: m_shared_tasks) { + if (s.GetDbSharedTask().expire_time > 0 && s.GetDbSharedTask().expire_time <= now) { + LogTasksDetail("[PurgeExpiredSharedTasks] Deleting expired task [{}]", s.GetDbSharedTask().id); + DeleteSharedTask(s.GetDbSharedTask().id); + } + } + + return this; +} diff --git a/world/shared_task_manager.h b/world/shared_task_manager.h new file mode 100644 index 000000000..1bbd071cd --- /dev/null +++ b/world/shared_task_manager.h @@ -0,0 +1,132 @@ +#ifndef EQEMU_SHARED_TASK_MANAGER_H +#define EQEMU_SHARED_TASK_MANAGER_H + +#include "../common/database.h" +#include "../common/shared_tasks.h" + +class DynamicZone; + +namespace EQ { + namespace Net { + class DynamicPacket; + } +} + +struct SharedTaskActiveInvitation { + uint32 shared_task_id; + uint32 character_id; +}; + +class SharedTaskManager { +public: + SharedTaskManager *SetDatabase(Database *db); + SharedTaskManager *SetContentDatabase(Database *db); + + // loads task data into memory + SharedTaskManager *LoadTaskData(); + + // loads shared task state into memory + void LoadSharedTaskState(); + + // helper, references task memory data + TasksRepository::Tasks GetSharedTaskDataByTaskId(uint32 task_id); + std::vector GetSharedTaskActivityDataByTaskId(uint32 task_id); + + // gets group / raid members belonging to requested character + std::vector GetRequestMembers( + uint32 requestor_character_id, + const std::vector &characters + ); + + // client attempting to create a shared task + void AttemptSharedTaskCreation(uint32 requested_task_id, uint32 requested_character_id, uint32 npc_type_id); + void AttemptSharedTaskRemoval(uint32 requested_task_id, uint32 requested_character_id, bool remove_from_db); + + // shared task activity update middleware + void SharedTaskActivityUpdate( + uint32 source_character_id, + uint32 task_id, + uint32 activity_id, + uint32 done_count, + bool ignore_quest_update + ); + + SharedTask *FindSharedTaskByTaskIdAndCharacterId(uint32 task_id, uint32 character_id); + SharedTask *FindSharedTaskById(int64 shared_task_id); + + void DeleteSharedTask(int64 shared_task_id); + void SaveSharedTaskActivityState(int64 shared_task_id, std::vector activity_state); + + bool IsSharedTaskLeader(SharedTask *s, uint32 character_id); + void SendAcceptNewSharedTaskPacket(uint32 character_id, uint32 task_id, uint32_t npc_context_id, int accept_time); + void SendRemovePlayerFromSharedTaskPacket(uint32 character_id, uint32 task_id, bool remove_from_db); + void SendSharedTaskMemberList(uint32 character_id, const std::vector &members); + void SendSharedTaskMemberList(uint32 character_id, const EQ::Net::DynamicPacket &serialized_members); + void SendSharedTaskMemberChange( + uint32 character_id, + int64 shared_task_id, + const std::string &player_name, + bool removed + ); + void RemovePlayerFromSharedTask(SharedTask *s, uint32 character_id); + void PrintSharedTaskState(); + void RemovePlayerFromSharedTaskByPlayerName(SharedTask *s, const std::string &character_name); + void RemoveEveryoneFromSharedTask(SharedTask *s, uint32 requested_character_id); + + void MakeLeaderByPlayerName(SharedTask *s, const std::string &character_name); + void AddPlayerByCharacterIdAndName(SharedTask *s, int64 character_id, const std::string &character_name); + void InvitePlayerByPlayerName(SharedTask *s, const std::string &player_name); + + // invitations + void QueueActiveInvitation(int64 shared_task_id, int64 character_id); + bool IsInvitationActive(uint32 shared_task_id, uint32 character_id); + void RemoveActiveInvitation(int64 shared_task_id, int64 character_id); + void RemoveActiveInvitationByCharacterID(uint32_t character_id); + + // dz + void CreateDynamicZone(SharedTask *s, DynamicZone &dz_request); + + void PurgeAllSharedTasks(); + + // messages + void SendLeaderMessage(SharedTask *s, int chat_type, const std::string &message); + void SendLeaderMessageID(SharedTask *s, int chat_type, int eqstr_id, std::initializer_list args = {}); + void SendMembersMessage(SharedTask *s, int chat_type, const std::string &message); + void SendMembersMessageID(SharedTask *s, int chat_type, int eqstr_id, std::initializer_list args = {}); + + const std::vector &GetSharedTasks() const; + void SetSharedTasks(const std::vector &shared_tasks); + + SharedTaskManager * PurgeExpiredSharedTasks(); +protected: + // reference to database + Database *m_database; + Database *m_content_database; + + // reference to task data (all) + std::vector m_task_data{}; + std::vector m_task_activity_data{}; + + // internal shared tasks list + std::vector m_shared_tasks{}; + + // store a reference of active invitations that have been sent to players + std::vector m_active_invitations{}; + + void AddReplayTimers(SharedTask *s); + bool CanAddPlayer(SharedTask *s, uint32_t character_id, std::string player_name, bool accepted); + bool CanRequestSharedTask(uint32_t task_id, uint32_t character_id, const SharedTaskRequestCharacters &request); + void ChooseNewLeader(SharedTask *s); + void SendSharedTaskMemberListToAllMembers(SharedTask *s); + void SendSharedTaskMemberAddedToAllMembers(SharedTask *s, const std::string &player_name); + void SendSharedTaskMemberRemovedToAllMembers(SharedTask *s, const std::string &player_name); + void SaveMembers(SharedTask *s, std::vector members); + void SendSharedTaskInvitePacket(SharedTask *s, int64 invited_character_id); + void RecordSharedTaskCompletion(SharedTask *s); + void RemoveAllMembersFromDynamicZones(SharedTask *s); + + // memory search + std::vector FindCharactersInSharedTasks(const std::vector &find_characters); +}; + +#endif //EQEMU_SHARED_TASK_MANAGER_H diff --git a/world/shared_task_world_messaging.cpp b/world/shared_task_world_messaging.cpp new file mode 100644 index 000000000..26da007f2 --- /dev/null +++ b/world/shared_task_world_messaging.cpp @@ -0,0 +1,355 @@ +#include "shared_task_world_messaging.h" +#include "cliententry.h" +#include "worlddb.h" +#include "../common/shared_tasks.h" +#include "../common/eqemu_logsys.h" +#include "../common/repositories/tasks_repository.h" +#include "../common/tasks.h" +#include "cliententry.h" +#include "clientlist.h" +#include "zonelist.h" +#include "zoneserver.h" +#include "shared_task_manager.h" +#include "../common/repositories/shared_task_members_repository.h" +#include "../common/repositories/task_activities_repository.h" +#include "dynamic_zone.h" + +extern ClientList client_list; +extern ZSList zoneserver_list; +extern SharedTaskManager shared_task_manager; + +void SharedTaskWorldMessaging::HandleZoneMessage(ServerPacket *pack) +{ + switch (pack->opcode) { + case ServerOP_SharedTaskRequest: { + auto *r = (ServerSharedTaskRequest_Struct *) pack->pBuffer; + LogTasksDetail( + "[ServerOP_SharedTaskRequest] Received request from character [{}] task_id [{}] npc_type_id [{}]", + r->requested_character_id, + r->requested_task_id, + r->requested_npc_type_id + ); + + shared_task_manager.AttemptSharedTaskCreation( + r->requested_task_id, + r->requested_character_id, + r->requested_npc_type_id + ); + + break; + } + case ServerOP_SharedTaskAttemptRemove: { + auto *r = (ServerSharedTaskAttemptRemove_Struct *) pack->pBuffer; + LogTasksDetail( + "[ServerOP_SharedTaskAttemptRemove] Received request from character [{}] task_id [{}] remove_from_db [{}]", + r->requested_character_id, + r->requested_task_id, + r->remove_from_db + ); + + shared_task_manager.AttemptSharedTaskRemoval( + r->requested_task_id, + r->requested_character_id, + r->remove_from_db + ); + + break; + } + case ServerOP_SharedTaskKickPlayers: { + auto r = reinterpret_cast(pack->pBuffer); + LogTasksDetail( + "[ServerOP_SharedTaskKickPlayers] Received request from character [{}] task_id [{}]", + r->source_character_id, + r->task_id + ); + + auto t = shared_task_manager.FindSharedTaskByTaskIdAndCharacterId(r->task_id, r->source_character_id); + if (t) { + auto leader = t->GetLeader(); + if (leader.character_id != r->source_character_id) { + client_list.SendCharacterMessageID( + r->source_character_id, Chat::Red, + SharedTaskMessage::YOU_ARE_NOT_LEADER_COMMAND_ISSUE, {leader.character_name} + ); + } + else { + shared_task_manager.RemoveEveryoneFromSharedTask(t, r->source_character_id); + } + } + + break; + } + case ServerOP_SharedTaskUpdate: { + auto *r = (ServerSharedTaskActivityUpdate_Struct *) pack->pBuffer; + + LogTasksDetail( + "[ServerOP_SharedTaskUpdate] Received request from character [{}] task_id [{}] activity_id [{}] donecount [{}] ignore_quest_update [{}]", + r->source_character_id, + r->task_id, + r->activity_id, + r->done_count, + (r->ignore_quest_update ? "true" : "false") + ); + + shared_task_manager.SharedTaskActivityUpdate( + r->source_character_id, + r->task_id, + r->activity_id, + r->done_count, + r->ignore_quest_update + ); + + break; + } + case ServerOP_SharedTaskRequestMemberlist: { + auto *r = (ServerSharedTaskRequestMemberlist_Struct *) pack->pBuffer; + + LogTasksDetail( + "[ServerOP_SharedTaskRequestMemberlist] Received request from character [{}] task_id [{}]", + r->source_character_id, + r->task_id + ); + + auto t = shared_task_manager.FindSharedTaskByTaskIdAndCharacterId(r->task_id, r->source_character_id); + if (t) { + LogTasksDetail( + "[ServerOP_SharedTaskRequestMemberlist] Found shared task character [{}] shared_task_id [{}]", + r->source_character_id, + t->GetDbSharedTask().id + ); + + shared_task_manager.SendSharedTaskMemberList( + r->source_character_id, + t->GetMembers() + ); + } + + break; + } + case ServerOP_SharedTaskRemovePlayer: { + auto *r = (ServerSharedTaskRemovePlayer_Struct *) pack->pBuffer; + + LogTasksDetail( + "[ServerOP_SharedTaskRemovePlayer] Received request from character [{}] task_id [{}] player_name [{}]", + r->source_character_id, + r->task_id, + r->player_name + ); + + auto t = shared_task_manager.FindSharedTaskByTaskIdAndCharacterId(r->task_id, r->source_character_id); + if (t) { + LogTasksDetail( + "[ServerOP_SharedTaskRemovePlayer] Found shared task character [{}] shared_task_id [{}]", + r->source_character_id, + t->GetDbSharedTask().id + ); + + auto leader = t->GetLeader(); + if (leader.character_id != r->source_character_id) { + client_list.SendCharacterMessageID( + r->source_character_id, Chat::Red, + SharedTaskMessage::YOU_ARE_NOT_LEADER_COMMAND_ISSUE, {leader.character_name} + ); + } + else { + LogTasksDetail( + "[ServerOP_SharedTaskRemovePlayer] character_id [{}] shared_task_id [{}] is_leader", + r->source_character_id, + t->GetDbSharedTask().id + ); + + std::string character_name = r->player_name; + shared_task_manager.RemovePlayerFromSharedTaskByPlayerName(t, character_name); + } + } + + break; + } + case ServerOP_SharedTaskMakeLeader: { + auto *r = (ServerSharedTaskMakeLeader_Struct *) pack->pBuffer; + + LogTasksDetail( + "[ServerOP_SharedTaskMakeLeader] Received request from character [{}] task_id [{}] player_name [{}]", + r->source_character_id, + r->task_id, + r->player_name + ); + + auto t = shared_task_manager.FindSharedTaskByTaskIdAndCharacterId(r->task_id, r->source_character_id); + if (t) { + LogTasksDetail( + "[ServerOP_SharedTaskMakeLeader] Found shared task character [{}] shared_task_id [{}]", + r->source_character_id, + t->GetDbSharedTask().id + ); + + auto leader = t->GetLeader(); + if (leader.character_id != r->source_character_id) { + client_list.SendCharacterMessageID( + r->source_character_id, Chat::Red, + SharedTaskMessage::YOU_ARE_NOT_LEADER_COMMAND_ISSUE, {leader.character_name} + ); + } + else if (strcasecmp(leader.character_name.c_str(), r->player_name) == 0) { + client_list.SendCharacterMessageID( + r->source_character_id, + Chat::Red, + SharedTaskMessage::YOU_ALREADY_LEADER + ); + } + else { + LogTasksDetail( + "[ServerOP_SharedTaskMakeLeader] character_id [{}] shared_task_id [{}] is_leader", + r->source_character_id, + t->GetDbSharedTask().id + ); + + std::string character_name = r->player_name; + shared_task_manager.MakeLeaderByPlayerName(t, character_name); + } + } + + break; + } + case ServerOP_SharedTaskAddPlayer: { + auto *r = (ServerSharedTaskAddPlayer_Struct *) pack->pBuffer; + + LogTasksDetail( + "[ServerOP_SharedTaskAddPlayer] Received request from character [{}] task_id [{}] player_name [{}]", + r->source_character_id, + r->task_id, + r->player_name + ); + + auto t = shared_task_manager.FindSharedTaskByTaskIdAndCharacterId(r->task_id, r->source_character_id); + if (t) { + LogTasksDetail( + "[ServerOP_SharedTaskAddPlayer] Found shared task character [{}] shared_task_id [{}]", + r->source_character_id, + t->GetDbSharedTask().id + ); + + auto leader = t->GetLeader(); + if (leader.character_id != r->source_character_id) { + // taskadd is client sided with System color in newer clients, server side might still be red + client_list.SendCharacterMessageID( + r->source_character_id, Chat::Red, + SharedTaskMessage::YOU_ARE_NOT_LEADER_COMMAND_ISSUE, {leader.character_name} + ); + } + else { + LogTasksDetail( + "[ServerOP_SharedTaskAddPlayer] character_id [{}] shared_task_id [{}] is_leader", + r->source_character_id, + t->GetDbSharedTask().id + ); + + std::string character_name = r->player_name; + shared_task_manager.InvitePlayerByPlayerName(t, character_name); + } + } + + break; + } + case ServerOP_SharedTaskInviteAcceptedPlayer: { + auto *r = (ServerSharedTaskInviteAccepted_Struct *) pack->pBuffer; + + LogTasksDetail( + "[ServerOP_SharedTaskInviteAcceptedPlayer] Received request from source_character_id [{}] shared_task_id [{}] accepted [{}]", + r->source_character_id, + r->shared_task_id, + r->accepted + ); + + auto t = shared_task_manager.FindSharedTaskById(r->shared_task_id); + if (t && shared_task_manager.IsInvitationActive(r->shared_task_id, r->source_character_id)) { + LogTasksDetail( + "[ServerOP_SharedTaskInviteAcceptedPlayer] Found shared task character [{}] shared_task_id [{}]", + r->source_character_id, + t->GetDbSharedTask().id + ); + + shared_task_manager.RemoveActiveInvitation(r->shared_task_id, r->source_character_id); + + if (r->accepted) { + shared_task_manager.AddPlayerByCharacterIdAndName(t, r->source_character_id, r->player_name); + } + else { + shared_task_manager.SendLeaderMessageID( + t, + Chat::Red, + SharedTaskMessage::PLAYER_DECLINED_OFFER, + {r->player_name} + ); + } + } + break; + } + case ServerOP_SharedTaskCreateDynamicZone: { + auto buf = reinterpret_cast(pack->pBuffer); + + LogTasksDetail( + "[ServerOP_SharedTaskCreateDynamicZone] Received request from source_character_id [{}] task_id [{}]", + buf->source_character_id, + buf->task_id + ); + + auto t = shared_task_manager.FindSharedTaskByTaskIdAndCharacterId(buf->task_id, buf->source_character_id); + if (t) { + DynamicZone dz; + dz.LoadSerializedDzPacket(buf->cereal_data, buf->cereal_size); + + shared_task_manager.CreateDynamicZone(t, dz); + } + break; + } + case ServerOP_SharedTaskPurgeAllCommand: { + LogTasksDetail("[ServerOP_SharedTaskPurgeAllCommand] Received request to purge all shared tasks"); + + shared_task_manager.PurgeAllSharedTasks(); + auto p = std::make_unique( + ServerOP_SharedTaskPurgeAllCommand, + 0 + ); + + zoneserver_list.SendPacket(p.get()); + + break; + } + case ServerOP_SharedTaskPlayerList: { + auto buf = reinterpret_cast(pack->pBuffer); + + LogTasksDetail( + "[ServerOP_SharedTaskPlayerList] Received request from source_character_id [{}] task_id [{}]", + buf->source_character_id, + buf->task_id + ); + + auto s = shared_task_manager.FindSharedTaskByTaskIdAndCharacterId(buf->task_id, buf->source_character_id); + if (s) { + std::vector player_names; + + for (const auto &member : s->GetMembers()) { + player_names.emplace_back(member.character_name); + + if (member.is_leader) { + client_list.SendCharacterMessageID( + buf->source_character_id, Chat::Yellow, + SharedTaskMessage::LEADER_PRINT, {member.character_name} + ); + } + } + + std::string player_list = fmt::format("{}", fmt::join(player_names, ", ")); + client_list.SendCharacterMessageID( + buf->source_character_id, Chat::Yellow, + SharedTaskMessage::MEMBERS_PRINT, {player_list} + ); + } + + break; + } + default: + break; + } +} diff --git a/world/shared_task_world_messaging.h b/world/shared_task_world_messaging.h new file mode 100644 index 000000000..e8062e2ec --- /dev/null +++ b/world/shared_task_world_messaging.h @@ -0,0 +1,18 @@ +#ifndef EQEMU_SHARED_TASK_WORLD_MESSAGING_H +#define EQEMU_SHARED_TASK_WORLD_MESSAGING_H + +#include "../common/types.h" +#include "../common/servertalk.h" +#include "../common/shared_tasks.h" +#include "../common/eqemu_logsys.h" +#include "../common/repositories/tasks_repository.h" +#include "../common/tasks.h" + +class SharedTaskWorldMessaging { +public: + static void HandleZoneMessage(ServerPacket *pack); +}; + + +#endif //EQEMU_SHARED_TASK_WORLD_MESSAGING_H + diff --git a/world/world_event_scheduler.cpp b/world/world_event_scheduler.cpp new file mode 100644 index 000000000..9b71a4bbd --- /dev/null +++ b/world/world_event_scheduler.cpp @@ -0,0 +1,71 @@ +#include "world_event_scheduler.h" +#include "../common/servertalk.h" +#include + +void WorldEventScheduler::Process(ZSList *zs_list) +{ + std::time_t time = std::time(nullptr); + std::tm *now = std::localtime(&time); + + // once a minute polling + if (m_last_polled_minute != now->tm_min) { + + // refresh; world polls and tells zones if they should update if there is a change + if (CheckIfEventsChanged()) { + LogSchedulerDetail("Event changes detected, forcing zones to refresh their schedules..."); + auto pack = new ServerPacket(ServerOP_UpdateSchedulerEvents, 0); + zs_list->SendPacket(pack); + safe_delete(pack); + } + + int month = (now->tm_mon + 1); + int year = (now->tm_year + 1900); + + LogSchedulerDetail( + "Polling year [{}] month [{}] day [{}] hour [{}] minute [{}]", + year, + month, + now->tm_mday, + now->tm_hour, + now->tm_min + ); + + for (auto &e: m_events) { + + // discard uninteresting events as its less work to calculate time on events we don't care about + // different processes are interested in different events + if ( + e.event_type != ServerEvents::EVENT_TYPE_BROADCAST && + e.event_type != ServerEvents::EVENT_TYPE_RELOAD_WORLD + ) { + continue; + } + + // validate event is ready to activate and run it + if (ValidateEventReadyToActivate(e)) { + if (e.event_type == ServerEvents::EVENT_TYPE_BROADCAST) { + LogScheduler("Sending broadcast [{}]", e.event_data.c_str()); + zs_list->SendEmoteMessage( + 0, + 0, + AccountStatus::Player, + Chat::Yellow, + e.event_data.c_str() + ); + } + + if (e.event_type == ServerEvents::EVENT_TYPE_RELOAD_WORLD) { + LogScheduler("Sending reload world event [{}]", e.event_data.c_str()); + + auto pack = new ServerPacket(ServerOP_ReloadWorld, sizeof(ReloadWorld_Struct)); + auto *reload_world = (ReloadWorld_Struct *) pack->pBuffer; + reload_world->Option = 1; + zs_list->SendPacket(pack); + safe_delete(pack); + } + } + } + + m_last_polled_minute = now->tm_min; + } +} diff --git a/world/world_event_scheduler.h b/world/world_event_scheduler.h new file mode 100644 index 000000000..b2a6725df --- /dev/null +++ b/world/world_event_scheduler.h @@ -0,0 +1,12 @@ +#ifndef EQEMU_EVENT_SCHEDULER_H +#define EQEMU_EVENT_SCHEDULER_H + +#include "../common/server_event_scheduler.h" +#include "zonelist.h" + +class WorldEventScheduler : public ServerEventScheduler { +public: + void Process(ZSList *zs_list); +}; + +#endif //EQEMU_EVENT_SCHEDULER_H diff --git a/world/world_server_command_handler.cpp b/world/world_server_command_handler.cpp index b5d226664..0eade4f43 100644 --- a/world/world_server_command_handler.cpp +++ b/world/world_server_command_handler.cpp @@ -165,37 +165,37 @@ namespace WorldserverCommandHandler { Json::Value player_tables_json; std::vector player_tables = DatabaseSchema::GetPlayerTables(); - for (const auto &table : player_tables) { + for (const auto &table: player_tables) { player_tables_json.append(table); } Json::Value content_tables_json; std::vector content_tables = DatabaseSchema::GetContentTables(); - for (const auto &table : content_tables) { + for (const auto &table: content_tables) { content_tables_json.append(table); } Json::Value server_tables_json; std::vector server_tables = DatabaseSchema::GetServerTables(); - for (const auto &table : server_tables) { + for (const auto &table: server_tables) { server_tables_json.append(table); } Json::Value login_tables_json; std::vector login_tables = DatabaseSchema::GetLoginTables(); - for (const auto &table : login_tables) { + for (const auto &table: login_tables) { login_tables_json.append(table); } Json::Value state_tables_json; std::vector state_tables = DatabaseSchema::GetStateTables(); - for (const auto &table : state_tables) { + for (const auto &table: state_tables) { state_tables_json.append(table); } Json::Value version_tables_json; std::vector version_tables = DatabaseSchema::GetVersionTables(); - for (const auto &table : version_tables) { + for (const auto &table: version_tables) { version_tables_json.append(table); } @@ -313,11 +313,20 @@ namespace WorldserverCommandHandler { content_service.SetCurrentExpansion(RuleI(Expansion, CurrentExpansion)); - std::vector flags = { + std::vector flags = {}; + auto f = ContentFlagsRepository::NewEntity(); + f.enabled = 1; + + std::vector flag_names = { "hateplane_enabled", "patch_nerf_7077", }; + for (auto &name: flag_names) { + f.flag_name = name; + flags.push_back(f); + } + content_service.SetContentFlags(flags); LogInfo( @@ -469,7 +478,7 @@ namespace WorldserverCommandHandler { "destination_character_name", "destination_account_name" }; - std::vector options = { }; + std::vector options = {}; if (cmd[{"-h", "--help"}]) { return; diff --git a/world/world_store.cpp b/world/world_store.cpp index 5a8201124..62c484172 100644 --- a/world/world_store.cpp +++ b/world/world_store.cpp @@ -85,6 +85,26 @@ std::string WorldStore::GetZoneName(uint32 zone_id) return std::string(); } +/** + * @param zone_id + * @param error_unknown + * @return + */ +const char *WorldStore::GetZoneLongName(uint32 zone_id, bool error_unknown) +{ + for (auto &z: zones) { + if (z.zoneidnumber == zone_id) { + return z.long_name.c_str(); + } + } + + if (error_unknown) { + return "UNKNOWN"; + } + + return nullptr; +} + /** * @param zone_id * @return diff --git a/world/world_store.h b/world/world_store.h index 985419ff0..ecbba4b5e 100644 --- a/world/world_store.h +++ b/world/world_store.h @@ -40,7 +40,7 @@ public: std::string GetZoneName(uint32 zone_id); std::string GetZoneLongName(uint32 zone_id); const char *GetZoneName(uint32 zone_id, bool error_unknown = false); - + const char *GetZoneLongName(uint32 zone_id, bool error_unknown = false); }; extern WorldStore world_store; @@ -57,7 +57,13 @@ inline const char *ZoneName(uint32 zone_id, bool error_unknown = false) error_unknown ); } -inline const char *ZoneLongName(uint32 zone_id) { return world_store.GetZoneLongName(zone_id).c_str(); } +inline const char *ZoneLongName(uint32 zone_id, bool error_unknown = false) +{ + return world_store.GetZoneLongName( + zone_id, + error_unknown + ); +} inline ZoneRepository::Zone GetZone(uint32 zone_id, int version = 0) { return world_store.GetZone(zone_id, version); }; inline ZoneRepository::Zone GetZone(const char *in_zone_name) { return world_store.GetZone(in_zone_name); }; diff --git a/world/worlddb.cpp b/world/worlddb.cpp index 7bc5a801f..7e6490d0b 100644 --- a/world/worlddb.cpp +++ b/world/worlddb.cpp @@ -25,6 +25,7 @@ #include #include #include "sof_char_create_data.h" +#include "../common/repositories/character_instance_safereturns_repository.h" #include "../common/repositories/criteria/content_filter_criteria.h" #include "world_store.h" @@ -54,42 +55,48 @@ void WorldDatabase::GetCharSelectInfo(uint32 account_id, EQApplicationPacket **o character_limit = 8; } - std::string character_list_query = StringFormat( - "SELECT " - "`id`, " // 0 - "name, " // 1 - "gender, " // 2 - "race, " // 3 - "class, " // 4 - "`level`, " // 5 - "deity, " // 6 - "last_login, " // 7 - "time_played, " // 8 - "hair_color, " // 9 - "beard_color, " // 10 - "eye_color_1, " // 11 - "eye_color_2, " // 12 - "hair_style, " // 13 - "beard, " // 14 - "face, " // 15 - "drakkin_heritage, " // 16 - "drakkin_tattoo, " // 17 - "drakkin_details, " // 18 - "zone_id " // 19 - "FROM " - "character_data " - "WHERE `account_id` = %i AND deleted_at IS NULL ORDER BY `name` LIMIT %u", + std::string character_list_query = fmt::format( + SQL( + SELECT + `id`, + `name`, + `gender`, + `race`, + `class`, + `level`, + `deity`, + `last_login`, + `time_played`, + `hair_color`, + `beard_color`, + `eye_color_1`, + `eye_color_2`, + `hair_style`, + `beard`, + `face`, + `drakkin_heritage`, + `drakkin_tattoo`, + `drakkin_details`, + `zone_id` + FROM + `character_data` + WHERE + `account_id` = {} + AND + `deleted_at` IS NULL + ORDER BY `name` + LIMIT {} + ), account_id, character_limit ); auto results = database.QueryDatabase(character_list_query); - size_t character_count = results.RowCount(); if (character_count == 0) { - *out_app = new EQApplicationPacket(OP_SendCharInfo, sizeof(CharacterSelect_Struct)); + *out_app = new EQApplicationPacket(OP_SendCharInfo, sizeof(CharacterSelect_Struct)); CharacterSelect_Struct *cs = (CharacterSelect_Struct *) (*out_app)->pBuffer; - cs->CharCount = 0; + cs->CharCount = 0; cs->TotalChars = character_limit; return; } @@ -97,155 +104,181 @@ void WorldDatabase::GetCharSelectInfo(uint32 account_id, EQApplicationPacket **o size_t packet_size = sizeof(CharacterSelect_Struct) + (sizeof(CharacterSelectEntry_Struct) * character_count); *out_app = new EQApplicationPacket(OP_SendCharInfo, packet_size); - unsigned char *buff_ptr = (*out_app)->pBuffer; - CharacterSelect_Struct *cs = (CharacterSelect_Struct *) buff_ptr; + unsigned char *buff_ptr = (*out_app)->pBuffer; + CharacterSelect_Struct *cs = (CharacterSelect_Struct *) buff_ptr; - cs->CharCount = character_count; + cs->CharCount = character_count; cs->TotalChars = character_limit; buff_ptr += sizeof(CharacterSelect_Struct); for (auto row = results.begin(); row != results.end(); ++row) { CharacterSelectEntry_Struct *p_character_select_entry_struct = (CharacterSelectEntry_Struct *) buff_ptr; - PlayerProfile_Struct player_profile_struct; - EQ::InventoryProfile inventory_profile; + PlayerProfile_Struct player_profile_struct; + EQ::InventoryProfile inventory_profile; player_profile_struct.SetPlayerProfileVersion(EQ::versions::ConvertClientVersionToMobVersion(client_version)); inventory_profile.SetInventoryVersion(client_version); inventory_profile.SetGMInventory(true); // charsel can not interact with items..but, no harm in setting to full expansion support uint32 character_id = (uint32) atoi(row[0]); - uint8 has_home = 0; - uint8 has_bind = 0; + uint8 has_home = 0; + uint8 has_bind = 0; memset(&player_profile_struct, 0, sizeof(PlayerProfile_Struct)); - memset(p_character_select_entry_struct->Name, 0, sizeof(p_character_select_entry_struct->Name)); strcpy(p_character_select_entry_struct->Name, row[1]); - p_character_select_entry_struct->Class = (uint8) atoi(row[4]); - p_character_select_entry_struct->Race = (uint32) atoi(row[3]); - p_character_select_entry_struct->Level = (uint8) atoi(row[5]); + p_character_select_entry_struct->Class = (uint8) atoi(row[4]); + p_character_select_entry_struct->Race = (uint32) atoi(row[3]); + p_character_select_entry_struct->Level = (uint8) atoi(row[5]); p_character_select_entry_struct->ShroudClass = p_character_select_entry_struct->Class; - p_character_select_entry_struct->ShroudRace = p_character_select_entry_struct->Race; - p_character_select_entry_struct->Zone = (uint16) atoi(row[19]); - p_character_select_entry_struct->Instance = 0; - p_character_select_entry_struct->Gender = (uint8) atoi(row[2]); - p_character_select_entry_struct->Face = (uint8) atoi(row[15]); + p_character_select_entry_struct->ShroudRace = p_character_select_entry_struct->Race; + p_character_select_entry_struct->Zone = (uint16) atoi(row[19]); + p_character_select_entry_struct->Instance = 0; + p_character_select_entry_struct->Gender = (uint8) atoi(row[2]); + p_character_select_entry_struct->Face = (uint8) atoi(row[15]); for (uint32 material_slot = 0; material_slot < EQ::textures::materialCount; material_slot++) { - p_character_select_entry_struct->Equip[material_slot].Material = 0; - p_character_select_entry_struct->Equip[material_slot].Unknown1 = 0; - p_character_select_entry_struct->Equip[material_slot].EliteModel = 0; + p_character_select_entry_struct->Equip[material_slot].Material = 0; + p_character_select_entry_struct->Equip[material_slot].Unknown1 = 0; + p_character_select_entry_struct->Equip[material_slot].EliteModel = 0; p_character_select_entry_struct->Equip[material_slot].HerosForgeModel = 0; - p_character_select_entry_struct->Equip[material_slot].Unknown2 = 0; - p_character_select_entry_struct->Equip[material_slot].Color = 0; + p_character_select_entry_struct->Equip[material_slot].Unknown2 = 0; + p_character_select_entry_struct->Equip[material_slot].Color = 0; } - p_character_select_entry_struct->Unknown15 = 0xFF; - p_character_select_entry_struct->Unknown19 = 0xFF; - p_character_select_entry_struct->DrakkinTattoo = (uint32) atoi(row[17]); - p_character_select_entry_struct->DrakkinDetails = (uint32) atoi(row[18]); - p_character_select_entry_struct->Deity = (uint32) atoi(row[6]); - p_character_select_entry_struct->PrimaryIDFile = 0; // Processed Below + p_character_select_entry_struct->Unknown15 = 0xFF; + p_character_select_entry_struct->Unknown19 = 0xFF; + p_character_select_entry_struct->DrakkinTattoo = (uint32) atoi(row[17]); + p_character_select_entry_struct->DrakkinDetails = (uint32) atoi(row[18]); + p_character_select_entry_struct->Deity = (uint32) atoi(row[6]); + p_character_select_entry_struct->PrimaryIDFile = 0; // Processed Below p_character_select_entry_struct->SecondaryIDFile = 0; // Processed Below - p_character_select_entry_struct->HairColor = (uint8) atoi(row[9]); - p_character_select_entry_struct->BeardColor = (uint8) atoi(row[10]); - p_character_select_entry_struct->EyeColor1 = (uint8) atoi(row[11]); - p_character_select_entry_struct->EyeColor2 = (uint8) atoi(row[12]); - p_character_select_entry_struct->HairStyle = (uint8) atoi(row[13]); - p_character_select_entry_struct->Beard = (uint8) atoi(row[14]); - p_character_select_entry_struct->GoHome = 0; // Processed Below - p_character_select_entry_struct->Tutorial = 0; // Processed Below + p_character_select_entry_struct->HairColor = (uint8) atoi(row[9]); + p_character_select_entry_struct->BeardColor = (uint8) atoi(row[10]); + p_character_select_entry_struct->EyeColor1 = (uint8) atoi(row[11]); + p_character_select_entry_struct->EyeColor2 = (uint8) atoi(row[12]); + p_character_select_entry_struct->HairStyle = (uint8) atoi(row[13]); + p_character_select_entry_struct->Beard = (uint8) atoi(row[14]); + p_character_select_entry_struct->GoHome = 0; // Processed Below + p_character_select_entry_struct->Tutorial = 0; // Processed Below p_character_select_entry_struct->DrakkinHeritage = (uint32) atoi(row[16]); - p_character_select_entry_struct->Unknown1 = 0; - p_character_select_entry_struct->Enabled = 1; - p_character_select_entry_struct->LastLogin = (uint32) atoi(row[7]); // RoF2 value: 1212696584 - p_character_select_entry_struct->Unknown2 = 0; + p_character_select_entry_struct->Unknown1 = 0; + p_character_select_entry_struct->Enabled = 1; + p_character_select_entry_struct->LastLogin = (uint32) atoi(row[7]); // RoF2 value: 1212696584 + p_character_select_entry_struct->Unknown2 = 0; if (RuleB(World, EnableReturnHomeButton)) { int now = time(nullptr); - if ((now - atoi(row[7])) >= RuleI(World, MinOfflineTimeToReturnHome)) { + if ((now - atoi(row[7])) >= RuleI(World, MinOfflineTimeToReturnHome)) p_character_select_entry_struct->GoHome = 1; - } } - if (RuleB(World, EnableTutorialButton) && (p_character_select_entry_struct->Level <= RuleI(World, MaxLevelForTutorial))) { + if (RuleB(World, EnableTutorialButton) && (p_character_select_entry_struct->Level <= RuleI(World, MaxLevelForTutorial))) p_character_select_entry_struct->Tutorial = 1; - } /** * Bind */ - character_list_query = StringFormat( - "SELECT `zone_id`, `instance_id`, `x`, `y`, `z`, `heading`, `slot` FROM `character_bind` WHERE `id` = %i LIMIT 5", + character_list_query = fmt::format( + SQL( + SELECT + `zone_id`, `instance_id`, `x`, `y`, `z`, `heading`, `slot` + FROM + `character_bind` + WHERE + `id` = {} + LIMIT 5 + ), character_id ); - auto results_bind = database.QueryDatabase(character_list_query); - auto bind_count = results_bind.RowCount(); - for (auto row_b = results_bind.begin(); row_b != results_bind.end(); ++row_b) { + auto results_bind = database.QueryDatabase(character_list_query); + auto bind_count = results_bind.RowCount(); + for (auto row_b = results_bind.begin(); row_b != results_bind.end(); ++row_b) { if (row_b[6] && atoi(row_b[6]) == 4) { has_home = 1; // If our bind count is less than 5, we need to actually make use of this data so lets parse it if (bind_count < 5) { - player_profile_struct.binds[4].zoneId = atoi(row_b[0]); + player_profile_struct.binds[4].zone_id = atoi(row_b[0]); player_profile_struct.binds[4].instance_id = atoi(row_b[1]); - player_profile_struct.binds[4].x = atof(row_b[2]); - player_profile_struct.binds[4].y = atof(row_b[3]); - player_profile_struct.binds[4].z = atof(row_b[4]); - player_profile_struct.binds[4].heading = atof(row_b[5]); + player_profile_struct.binds[4].x = atof(row_b[2]); + player_profile_struct.binds[4].y = atof(row_b[3]); + player_profile_struct.binds[4].z = atof(row_b[4]); + player_profile_struct.binds[4].heading = atof(row_b[5]); } } - if (row_b[6] && atoi(row_b[6]) == 0) { has_bind = 1; } + + if (row_b[6] && atoi(row_b[6]) == 0) + has_bind = 1; } if (has_home == 0 || has_bind == 0) { - character_list_query = StringFormat( - "SELECT `zone_id`, `bind_id`, `x`, `y`, `z` FROM `start_zones` WHERE `player_class` = %i AND `player_deity` = %i AND `player_race` = %i %s", + std::string character_list_query = fmt::format( + SQL( + SELECT + `zone_id`, `bind_id`, `x`, `y`, `z`, `heading` + FROM + `start_zones` + WHERE + `player_class` = {} + AND + `player_deity` = {} + AND + `player_race` = {} {} + ), p_character_select_entry_struct->Class, p_character_select_entry_struct->Deity, p_character_select_entry_struct->Race, ContentFilterCriteria::apply().c_str() ); - auto results_bind = content_db.QueryDatabase(character_list_query); - for (auto row_d = results_bind.begin(); row_d != results_bind.end(); ++row_d) { + auto results_bind = content_db.QueryDatabase(character_list_query); + for (auto row_d = results_bind.begin(); row_d != results_bind.end(); ++row_d) { /* If a bind_id is specified, make them start there */ if (atoi(row_d[1]) != 0) { - player_profile_struct.binds[4].zoneId = (uint32) atoi(row_d[1]); + player_profile_struct.binds[4].zone_id = (uint32) atoi(row_d[1]); content_db.GetSafePoints( - ZoneName(player_profile_struct.binds[4].zoneId), + ZoneName(player_profile_struct.binds[4].zone_id, true), 0, &player_profile_struct.binds[4].x, &player_profile_struct.binds[4].y, - &player_profile_struct.binds[4].z + &player_profile_struct.binds[4].z, + &player_profile_struct.binds[4].heading ); } /* Otherwise, use the zone and coordinates given */ else { - player_profile_struct.binds[4].zoneId = (uint32) atoi(row_d[0]); + player_profile_struct.binds[4].zone_id = (uint32) atoi(row_d[0]); float x = atof(row_d[2]); float y = atof(row_d[3]); float z = atof(row_d[4]); - if (x == 0 && y == 0 && z == 0) { + float heading = atof(row_d[5]); + if (x == 0 && y == 0 && z == 0 && heading == 0) { content_db.GetSafePoints( - ZoneName(player_profile_struct.binds[4].zoneId), + ZoneName(player_profile_struct.binds[4].zone_id, true), 0, &x, &y, - &z + &z, + &heading ); } player_profile_struct.binds[4].x = x; player_profile_struct.binds[4].y = y; player_profile_struct.binds[4].z = z; + player_profile_struct.binds[4].heading = heading; } } player_profile_struct.binds[0] = player_profile_struct.binds[4]; /* If no home bind set, set it */ if (has_home == 0) { - std::string query = StringFormat( - "REPLACE INTO `character_bind` (id, zone_id, instance_id, x, y, z, heading, slot)" - " VALUES (%u, %u, %u, %f, %f, %f, %f, %i)", + std::string query = fmt::format( + SQL( + REPLACE INTO + `character_bind` + (`id`, `zone_id`, `instance_id`, `x`, `y`, `z`, `heading`, `slot`) + VALUES ({}, {}, {}, {}, {}, {}, {}, {}) + ), character_id, - player_profile_struct.binds[4].zoneId, + player_profile_struct.binds[4].zone_id, 0, player_profile_struct.binds[4].x, player_profile_struct.binds[4].y, @@ -253,15 +286,19 @@ void WorldDatabase::GetCharSelectInfo(uint32 account_id, EQApplicationPacket **o player_profile_struct.binds[4].heading, 4 ); - auto results_bset = QueryDatabase(query); + auto results_bset = QueryDatabase(query); } /* If no regular bind set, set it */ if (has_bind == 0) { - std::string query = StringFormat( - "REPLACE INTO `character_bind` (id, zone_id, instance_id, x, y, z, heading, slot)" - " VALUES (%u, %u, %u, %f, %f, %f, %f, %i)", + std::string query = fmt::format( + SQL( + REPLACE INTO + `character_bind` + (`id`, `zone_id`, `instance_id`, `x`, `y`, `z`, `heading`, `slot`) + VALUES ({}, {}, {}, {}, {}, {}, {}, {}) + ), character_id, - player_profile_struct.binds[0].zoneId, + player_profile_struct.binds[0].zone_id, 0, player_profile_struct.binds[0].x, player_profile_struct.binds[0].y, @@ -269,7 +306,7 @@ void WorldDatabase::GetCharSelectInfo(uint32 account_id, EQApplicationPacket **o player_profile_struct.binds[0].heading, 0 ); - auto results_bset = QueryDatabase(query); + auto results_bset = QueryDatabase(query); } } /* If our bind count is less than 5, then we have null data that needs to be filled in. */ @@ -277,15 +314,18 @@ void WorldDatabase::GetCharSelectInfo(uint32 account_id, EQApplicationPacket **o // we know that home and main bind must be valid here, so we don't check those // we also use home to fill in the null data like live does. for (int i = 1; i < 4; i++) { - if (player_profile_struct.binds[i].zoneId != 0) { // we assume 0 is the only invalid one ... + if (player_profile_struct.binds[i].zone_id != 0) // we assume 0 is the only invalid one ... continue; - } - std::string query = StringFormat( - "REPLACE INTO `character_bind` (id, zone_id, instance_id, x, y, z, heading, slot)" - " VALUES (%u, %u, %u, %f, %f, %f, %f, %i)", + std::string query = fmt::format( + SQL( + REPLACE INTO + `character_bind` + (`id`, `zone_id`, `instance_id`, `x`, `y`, `z`, `heading`, `slot`) + VALUES ({}, {}, {}, {}, {}, {}, {}, {}) + ), character_id, - player_profile_struct.binds[4].zoneId, + player_profile_struct.binds[4].zone_id, 0, player_profile_struct.binds[4].x, player_profile_struct.binds[4].y, @@ -293,40 +333,45 @@ void WorldDatabase::GetCharSelectInfo(uint32 account_id, EQApplicationPacket **o player_profile_struct.binds[4].heading, i ); - auto results_bset = QueryDatabase(query); + auto results_bset = QueryDatabase(query); } } - character_list_query = StringFormat( - "SELECT slot, red, green, blue, use_tint, color FROM `character_material` WHERE `id` = %u", + character_list_query = fmt::format( + SQL( + SELECT + `slot`, `red`, `green`, `blue`, `use_tint`, `color` + FROM + `character_material` + WHERE + `id` = {} + ), character_id ); - auto results_b = database.QueryDatabase(character_list_query); - uint8 slot = 0; - for (auto row_b = results_b.begin(); row_b != results_b.end(); ++row_b) { + auto results_b = database.QueryDatabase(character_list_query); + uint8 slot = 0; + for (auto row_b = results_b.begin(); row_b != results_b.end(); ++row_b) { slot = atoi(row_b[0]); - player_profile_struct.item_tint.Slot[slot].Red = atoi(row_b[1]); - player_profile_struct.item_tint.Slot[slot].Green = atoi(row_b[2]); - player_profile_struct.item_tint.Slot[slot].Blue = atoi(row_b[3]); + player_profile_struct.item_tint.Slot[slot].Red = atoi(row_b[1]); + player_profile_struct.item_tint.Slot[slot].Green = atoi(row_b[2]); + player_profile_struct.item_tint.Slot[slot].Blue = atoi(row_b[3]); player_profile_struct.item_tint.Slot[slot].UseTint = atoi(row_b[4]); } if (GetCharSelInventory(account_id, p_character_select_entry_struct->Name, &inventory_profile)) { - const EQ::ItemData *item = nullptr; - const EQ::ItemInstance *inst = nullptr; + const EQ::ItemData *item = nullptr; + const EQ::ItemInstance *inst = nullptr; int16 inventory_slot = 0; - for (uint32 matslot = EQ::textures::textureBegin; matslot < EQ::textures::materialCount; matslot++) { inventory_slot = EQ::InventoryProfile::CalcSlotFromMaterial(matslot); if (inventory_slot == INVALID_INDEX) { continue; } inst = inventory_profile.GetItem(inventory_slot); - if (inst == nullptr) { + if (inst == nullptr) continue; - } + item = inst->GetItem(); - if (item == nullptr) { + if (item == nullptr) continue; - } if (matslot > 6) { uint32 item_id_file = 0; @@ -334,54 +379,59 @@ void WorldDatabase::GetCharSelectInfo(uint32 account_id, EQApplicationPacket **o if (inst->GetOrnamentationIDFile() != 0) { item_id_file = inst->GetOrnamentationIDFile(); p_character_select_entry_struct->Equip[matslot].Material = item_id_file; - } - else { + } else { if (strlen(item->IDFile) > 2) { item_id_file = atoi(&item->IDFile[2]); p_character_select_entry_struct->Equip[matslot].Material = item_id_file; } } + if (matslot == EQ::textures::weaponPrimary) { p_character_select_entry_struct->PrimaryIDFile = item_id_file; - } - else { + } else { p_character_select_entry_struct->SecondaryIDFile = item_id_file; } - } - else { - uint32 color = 0; - if (player_profile_struct.item_tint.Slot[matslot].UseTint) { - color = player_profile_struct.item_tint.Slot[matslot].Color; - } - else { - color = inst->GetColor(); - } - + } else { // Armor Materials/Models - p_character_select_entry_struct->Equip[matslot].Material = item->Material; - p_character_select_entry_struct->Equip[matslot].EliteModel = item->EliteMaterial; + uint32 color = ( + player_profile_struct.item_tint.Slot[matslot].UseTint ? + player_profile_struct.item_tint.Slot[matslot].Color : + inst->GetColor() + ); + p_character_select_entry_struct->Equip[matslot].Material = item->Material; + p_character_select_entry_struct->Equip[matslot].EliteModel = item->EliteMaterial; p_character_select_entry_struct->Equip[matslot].HerosForgeModel = inst->GetOrnamentHeroModel(matslot); - p_character_select_entry_struct->Equip[matslot].Color = color; + p_character_select_entry_struct->Equip[matslot].Color = color; } } - } - else { + } else { printf("Error loading inventory for %s\n", p_character_select_entry_struct->Name); } - buff_ptr += sizeof(CharacterSelectEntry_Struct); } } -int WorldDatabase::MoveCharacterToBind(int CharID, uint8 bindnum) +int WorldDatabase::MoveCharacterToBind(int character_id, uint8 bind_number) { /* if an invalid bind point is specified, use the primary bind */ - if (bindnum > 4) - { - bindnum = 0; - } + if (bind_number > 4) + bind_number = 0; - std::string query = StringFormat("SELECT zone_id, instance_id, x, y, z FROM character_bind WHERE id = %u AND slot = %u LIMIT 1", CharID, bindnum); + std::string query = fmt::format( + SQL( + SELECT + zone_id, instance_id, x, y, z, heading + FROM + character_bind + WHERE + id = {} + AND + slot = {} + LIMIT 1 + ), + character_id, + bind_number + ); auto results = database.QueryDatabase(query); if(!results.Success() || results.RowCount() == 0) { return 0; @@ -398,8 +448,27 @@ int WorldDatabase::MoveCharacterToBind(int CharID, uint8 bindnum) heading = atof(row[5]); } - query = StringFormat("UPDATE character_data SET zone_id = '%d', zone_instance = '%d', x = '%f', y = '%f', z = '%f', heading = '%f' WHERE id = %u", - zone_id, instance_id, x, y, z, heading, CharID); + query = fmt::format( + SQL( + UPDATE + `character_data` + SET + `zone_id` = {}, + `zone_instance` = {}, + `x` = {}, + `y` = {}, + `z` = {}, + `heading` = {} + WHERE `id` = {} + ), + zone_id, + instance_id, + x, + y, + z, + heading, + character_id + ); results = database.QueryDatabase(query); if(!results.Success()) { @@ -409,6 +478,54 @@ int WorldDatabase::MoveCharacterToBind(int CharID, uint8 bindnum) return zone_id; } +int WorldDatabase::MoveCharacterToInstanceSafeReturn( + int character_id, + int instance_zone_id, + int instance_id +) +{ + int zone_id = 0; + + // only moves if safe return is for specified zone instance + auto entries = CharacterInstanceSafereturnsRepository::GetWhere( + database, + fmt::format( + "character_id = {} AND instance_zone_id = {} AND instance_id = {} AND safe_zone_id > 0", + character_id, instance_zone_id, instance_id + ) + ); + + if (!entries.empty()) { + auto entry = entries.front(); + + auto results = QueryDatabase( + fmt::format( + SQL( + UPDATE character_data + SET zone_id = {}, zone_instance = 0, x = {}, y = {}, z = {}, heading = {} + WHERE id = {}; + ), + entry.safe_zone_id, + entry.safe_x, + entry.safe_y, + entry.safe_z, + entry.safe_heading, + character_id + ) + ); + + if (results.Success() && results.RowsAffected() > 0) { + zone_id = entry.safe_zone_id; + } + } + + if (zone_id == 0) { + zone_id = MoveCharacterToBind(character_id); + } + + return zone_id; +} + bool WorldDatabase::GetStartZone( PlayerProfile_Struct *p_player_profile_struct, CharCreate_Struct *p_char_create_struct, @@ -434,7 +551,7 @@ bool WorldDatabase::GetStartZone( p_player_profile_struct->binds[0].x = 0; p_player_profile_struct->binds[0].y = 0; p_player_profile_struct->binds[0].z = 0; - p_player_profile_struct->binds[0].zoneId = 0; + p_player_profile_struct->binds[0].zone_id = 0; p_player_profile_struct->binds[0].instance_id = 0; // see if we have an entry for start_zone. We can support both titanium & SOF+ by having two entries per class/race/deity combo with different zone_ids @@ -480,35 +597,47 @@ bool WorldDatabase::GetStartZone( else { LogInfo("Found starting location in start_zones"); auto row = results.begin(); - p_player_profile_struct->x = atof(row[0]); - p_player_profile_struct->y = atof(row[1]); - p_player_profile_struct->z = atof(row[2]); - p_player_profile_struct->heading = atof(row[3]); - p_player_profile_struct->zone_id = atoi(row[4]); - p_player_profile_struct->binds[0].zoneId = atoi(row[5]); - p_player_profile_struct->binds[0].x = atof(row[6]); - p_player_profile_struct->binds[0].y = atof(row[7]); - p_player_profile_struct->binds[0].z = atof(row[8]); + p_player_profile_struct->x = atof(row[0]); + p_player_profile_struct->y = atof(row[1]); + p_player_profile_struct->z = atof(row[2]); + p_player_profile_struct->heading = atof(row[3]); + p_player_profile_struct->zone_id = atoi(row[4]); + p_player_profile_struct->binds[0].zone_id = atoi(row[5]); + p_player_profile_struct->binds[0].x = atof(row[6]); + p_player_profile_struct->binds[0].y = atof(row[7]); + p_player_profile_struct->binds[0].z = atof(row[8]); + p_player_profile_struct->binds[0].heading = atof(row[3]); } - if (p_player_profile_struct->x == 0 && p_player_profile_struct->y == 0 && p_player_profile_struct->z == 0) { + if ( + p_player_profile_struct->x == 0 && + p_player_profile_struct->y == 0 && + p_player_profile_struct->z == 0 && + p_player_profile_struct->heading == 0 + ) { content_db.GetSafePoints( - ZoneName(p_player_profile_struct->zone_id), + ZoneName(p_player_profile_struct->zone_id, true), 0, &p_player_profile_struct->x, &p_player_profile_struct->y, - &p_player_profile_struct->z + &p_player_profile_struct->z, + &p_player_profile_struct->heading ); } - if (p_player_profile_struct->binds[0].x == 0 && p_player_profile_struct->binds[0].y == 0 && - p_player_profile_struct->binds[0].z == 0) { + if ( + p_player_profile_struct->binds[0].x == 0 && + p_player_profile_struct->binds[0].y == 0 && + p_player_profile_struct->binds[0].z == 0 && + p_player_profile_struct->binds[0].heading == 0 + ) { content_db.GetSafePoints( - ZoneName(p_player_profile_struct->binds[0].zoneId), + ZoneName(p_player_profile_struct->binds[0].zone_id, true), 0, &p_player_profile_struct->binds[0].x, &p_player_profile_struct->binds[0].y, - &p_player_profile_struct->binds[0].z + &p_player_profile_struct->binds[0].z, + &p_player_profile_struct->binds[0].heading ); } @@ -516,112 +645,124 @@ bool WorldDatabase::GetStartZone( } void WorldDatabase::SetSoFDefaultStartZone(PlayerProfile_Struct* in_pp, CharCreate_Struct* in_cc){ - if (in_cc->start_zone == RuleI(World, TutorialZoneID)) { + int sof_start_zone_id = RuleI(World, SoFStartZoneID); + if (sof_start_zone_id > 0) { + in_pp->zone_id = sof_start_zone_id; + in_cc->start_zone = in_pp->zone_id; + } + else if (in_cc->start_zone == RuleI(World, TutorialZoneID)) { in_pp->zone_id = in_cc->start_zone; } else { - in_pp->x = in_pp->binds[0].x = -51; - in_pp->y = in_pp->binds[0].y = -20; - in_pp->z = in_pp->binds[0].z = 0.79; - in_pp->zone_id = in_pp->binds[0].zoneId = 394; // Crescent Reach. + in_pp->x = in_pp->binds[0].x = -51.0f; + in_pp->y = in_pp->binds[0].y = -20.0f; + in_pp->z = in_pp->binds[0].z = 0.79f; + in_pp->heading = in_pp->binds[0].heading = 0.0f; + in_pp->zone_id = in_pp->binds[0].zone_id = Zones::CRESCENT; // Crescent Reach. } } void WorldDatabase::SetTitaniumDefaultStartZone(PlayerProfile_Struct* in_pp, CharCreate_Struct* in_cc) { - switch (in_cc->start_zone) - { - case 0: + int titanium_start_zone_id = RuleI(World, TitaniumStartZoneID); + if (titanium_start_zone_id > 0) { + in_pp->zone_id = titanium_start_zone_id; + in_pp->binds[0].zone_id = titanium_start_zone_id; + } else { + switch (in_cc->start_zone) { - if (in_cc->deity == 203) // Cazic-Thule Erudites go to Paineel + case StartZoneIndex::Odus: { - in_pp->zone_id = 75; // paineel - in_pp->binds[0].zoneId = 75; + if (in_cc->deity == EQ::deity::DeityCazicThule) // Cazic-Thule Erudites go to Paineel + { + in_pp->zone_id = Zones::PAINEEL; // paineel + in_pp->binds[0].zone_id = Zones::PAINEEL; + } + else + { + in_pp->zone_id = Zones::ERUDNEXT; // erudnext + in_pp->binds[0].zone_id = Zones::TOX; // tox + } + break; } - else + case StartZoneIndex::Qeynos: { - in_pp->zone_id = 24; // erudnext - in_pp->binds[0].zoneId = 38; // tox + in_pp->zone_id = Zones::QEYNOS2; // qeynos2 + in_pp->binds[0].zone_id = Zones::QEYNOS2; // qeynos2 + break; + } + case StartZoneIndex::Halas: + { + in_pp->zone_id = Zones::HALAS; // halas + in_pp->binds[0].zone_id = Zones::EVERFROST; // everfrost + break; + } + case StartZoneIndex::Rivervale: + { + in_pp->zone_id = Zones::RIVERVALE; // rivervale + in_pp->binds[0].zone_id = Zones::KITHICOR; // kithicor + break; + } + case StartZoneIndex::Freeport: + { + in_pp->zone_id = Zones::FREPORTW; // freportw + in_pp->binds[0].zone_id = Zones::FREPORTW; // freportw + break; + } + case StartZoneIndex::Neriak: + { + in_pp->zone_id = Zones::NERIAKA; // neriaka + in_pp->binds[0].zone_id = Zones::NEKTULOS; // nektulos + break; + } + case StartZoneIndex::Grobb: + { + in_pp->zone_id = Zones::GROBB; // grobb + in_pp->binds[0].zone_id = Zones::INNOTHULE; // innothule + break; + } + case StartZoneIndex::Oggok: + { + in_pp->zone_id = Zones::OGGOK; // oggok + in_pp->binds[0].zone_id = Zones::FEERROTT; // feerrott + break; + } + case StartZoneIndex::Kaladim: + { + in_pp->zone_id = Zones::KALADIMA; // kaladima + in_pp->binds[0].zone_id = Zones::BUTCHER; // butcher + break; + } + case StartZoneIndex::GreaterFaydark: + { + in_pp->zone_id = Zones::GFAYDARK; // gfaydark + in_pp->binds[0].zone_id = Zones::GFAYDARK; // gfaydark + break; + } + case StartZoneIndex::Felwithe: + { + in_pp->zone_id = Zones::FELWITHEA; // felwithea + in_pp->binds[0].zone_id = Zones::GFAYDARK; // gfaydark + break; + } + case StartZoneIndex::Akanon: + { + in_pp->zone_id = Zones::AKANON; // akanon + in_pp->binds[0].zone_id = Zones::STEAMFONT; // steamfont + break; + } + case StartZoneIndex::Cabilis: + { + in_pp->zone_id = Zones::CABWEST; // cabwest + in_pp->binds[0].zone_id = Zones::FIELDOFBONE; // fieldofbone + break; + } + case StartZoneIndex::SharVahl: + { + in_pp->zone_id = Zones::SHARVAHL; // sharvahl + in_pp->binds[0].zone_id = Zones::SHARVAHL; // sharvahl + break; } - break; - } - case 1: - { - in_pp->zone_id = 2; // qeynos2 - in_pp->binds[0].zoneId = 2; // qeynos2 - break; - } - case 2: - { - in_pp->zone_id = 29; // halas - in_pp->binds[0].zoneId = 30; // everfrost - break; - } - case 3: - { - in_pp->zone_id = 19; // rivervale - in_pp->binds[0].zoneId = 20; // kithicor - break; - } - case 4: - { - in_pp->zone_id = 9; // freportw - in_pp->binds[0].zoneId = 9; // freportw - break; - } - case 5: - { - in_pp->zone_id = 40; // neriaka - in_pp->binds[0].zoneId = 25; // nektulos - break; - } - case 6: - { - in_pp->zone_id = 52; // gukta - in_pp->binds[0].zoneId = 46; // innothule - break; - } - case 7: - { - in_pp->zone_id = 49; // oggok - in_pp->binds[0].zoneId = 47; // feerrott - break; - } - case 8: - { - in_pp->zone_id = 60; // kaladima - in_pp->binds[0].zoneId = 68; // butcher - break; - } - case 9: - { - in_pp->zone_id = 54; // gfaydark - in_pp->binds[0].zoneId = 54; // gfaydark - break; - } - case 10: - { - in_pp->zone_id = 61; // felwithea - in_pp->binds[0].zoneId = 54; // gfaydark - break; - } - case 11: - { - in_pp->zone_id = 55; // akanon - in_pp->binds[0].zoneId = 56; // steamfont - break; - } - case 12: - { - in_pp->zone_id = 82; // cabwest - in_pp->binds[0].zoneId = 78; // fieldofbone - break; - } - case 13: - { - in_pp->zone_id = 155; // sharvahl - in_pp->binds[0].zoneId = 155; // sharvahl - break; } } } diff --git a/world/worlddb.h b/world/worlddb.h index 42fbf96b6..ae26b0ff7 100644 --- a/world/worlddb.h +++ b/world/worlddb.h @@ -31,7 +31,8 @@ class WorldDatabase : public SharedDatabase { public: bool GetStartZone(PlayerProfile_Struct* p_player_profile_struct, CharCreate_Struct* p_char_create_struct, bool is_titanium); void GetCharSelectInfo(uint32 account_id, EQApplicationPacket **out_app, uint32 client_version_bit); - int MoveCharacterToBind(int CharID, uint8 bindnum = 0); + int MoveCharacterToBind(int character_id, uint8 bind_number = 0); + int MoveCharacterToInstanceSafeReturn(int character_id, int instance_zone_id, int instance_id); void GetLauncherList(std::vector &result); bool GetCharacterLevel(const char *name, int &level); diff --git a/world/zonelist.cpp b/world/zonelist.cpp index 51acd0c01..b548fa94a 100644 --- a/world/zonelist.cpp +++ b/world/zonelist.cpp @@ -30,7 +30,6 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "world_store.h" extern uint32 numzones; -extern bool holdzones; extern EQ::Random emu_random; extern WebInterfaceList web_interface; volatile bool UCSServerAvailable_ = false; @@ -43,7 +42,7 @@ ZSList::ZSList() memset(pLockedZones, 0, sizeof(pLockedZones)); m_tick = std::make_unique(5000, true, std::bind(&ZSList::OnTick, this, std::placeholders::_1)); - m_keepalive = std::make_unique(2500, true, std::bind(&ZSList::OnKeepAlive, this, std::placeholders::_1)); + m_keepalive = std::make_unique(1000, true, std::bind(&ZSList::OnKeepAlive, this, std::placeholders::_1)); } ZSList::~ZSList() { @@ -51,19 +50,17 @@ ZSList::~ZSList() { void ZSList::ShowUpTime(WorldTCPConnection* con, const char* adminname) { uint32 ms = Timer::GetCurrentTime(); - uint32 d = ms / 86400000; - ms -= d * 86400000; - uint32 h = ms / 3600000; - ms -= h * 3600000; - uint32 m = ms / 60000; - ms -= m * 60000; - uint32 s = ms / 1000; - if (d) - con->SendEmoteMessage(adminname, 0, 0, 0, "Worldserver Uptime: %02id %02ih %02im %02is", d, h, m, s); - else if (h) - con->SendEmoteMessage(adminname, 0, 0, 0, "Worldserver Uptime: %02ih %02im %02is", h, m, s); - else - con->SendEmoteMessage(adminname, 0, 0, 0, "Worldserver Uptime: %02im %02is", m, s); + std::string time_string = ConvertMillisecondsToTime(ms); + con->SendEmoteMessage( + adminname, + 0, + AccountStatus::Player, + Chat::White, + fmt::format( + "Worldserver Uptime | {}", + time_string + ).c_str() + ); } void ZSList::Add(ZoneServer* zoneserver) { @@ -110,7 +107,16 @@ void ZSList::Process() { } if (reminder && reminder->Check() && shutdowntimer) { - SendEmoteMessage(0, 0, 0, 15, ":SYSTEM MSG:World coming down, everyone log out now. World will shut down in %i minutes...", ((shutdowntimer->GetRemainingTime() / 1000) / 60)); + SendEmoteMessage( + 0, + 0, + AccountStatus::Player, + Chat::Yellow, + fmt::format( + "[SYSTEM] World will be shutting down in {} minutes.", + ((shutdowntimer->GetRemainingTime() / 1000) / 60) + ).c_str() + ); } } @@ -261,14 +267,39 @@ bool ZSList::IsZoneLocked(uint16 iZoneID) { } void ZSList::ListLockedZones(const char* to, WorldTCPConnection* connection) { - int x = 0; - for (auto &zone : pLockedZones) { - if (zone) { - connection->SendEmoteMessageRaw(to, 0, 0, 0, ZoneName(zone, true)); - x++; + int zone_count = 0; + for (const auto& zone_id : pLockedZones) { + if (zone_id) { + int zone_number = (zone_count + 1); + connection->SendEmoteMessageRaw( + to, + 0, + AccountStatus::Player, + Chat::White, + fmt::format( + "Zone {} | Name: {} ({}) ID: {}", + zone_number, + ZoneLongName(zone_id), + ZoneName(zone_id), + zone_id + ).c_str() + ); + zone_count++; } } - connection->SendEmoteMessage(to, 0, 0, 0, "%i zones locked.", x); + + std::string zone_message = ( + zone_count ? + fmt::format("{} Zones are locked.", zone_count) : + "There are no zones locked." + ); + connection->SendEmoteMessage( + to, + 0, + AccountStatus::Player, + Chat::White, + zone_message.c_str() + ); } void ZSList::SendZoneStatus(const char* to, int16 admin, WorldTCPConnection* connection) { @@ -321,7 +352,7 @@ void ZSList::SendZoneStatus(const char* to, int16 admin, WorldTCPConnection* con else is_static_string[0] = 'D'; - if (admin >= 150) { + if (admin >= AccountStatus::GMLeadAdmin) { if (zone_server_data->GetZoneID()) { snprintf(zone_data_string, sizeof(zone_data_string), "%s (%i)", zone_server_data->GetZoneName(), zone_server_data->GetZoneID()); } @@ -347,7 +378,13 @@ void ZSList::SendZoneStatus(const char* to, int16 admin, WorldTCPConnection* con if (out.size() >= 3584) { auto output = fmt::to_string(out); - connection->SendEmoteMessageRaw(to, 0, 0, 10, output.c_str()); + connection->SendEmoteMessageRaw( + to, + 0, + AccountStatus::Player, + Chat::NPCQuestSay, + output.c_str() + ); out.clear(); } else { @@ -366,7 +403,13 @@ void ZSList::SendZoneStatus(const char* to, int16 admin, WorldTCPConnection* con fmt::format_to(out, " #{} {} {}", zone_server_data->GetID(), is_static_string, zone_data_string); if (out.size() >= 3584) { auto output = fmt::to_string(out); - connection->SendEmoteMessageRaw(to, 0, 0, 10, output.c_str()); + connection->SendEmoteMessageRaw( + to, + 0, + AccountStatus::Player, + Chat::NPCQuestSay, + output.c_str() + ); out.clear(); } else { @@ -393,7 +436,13 @@ void ZSList::SendZoneStatus(const char* to, int16 admin, WorldTCPConnection* con fmt::format_to(out, "{} zones are static zones, {} zones are booted zones, {} zones available.", z, w, v); auto output = fmt::to_string(out); - connection->SendEmoteMessageRaw(to, 0, 0, 10, output.c_str()); + connection->SendEmoteMessageRaw( + to, + 0, + AccountStatus::Player, + Chat::NPCQuestSay, + output.c_str() + ); } void ZSList::SendChannelMessage(const char* from, const char* to, uint8 chan_num, uint8 language, const char* message, ...) { @@ -524,20 +573,52 @@ void ZSList::SOPZoneBootup(const char* adminname, uint32 ZoneServerID, const cha ZoneServer* zs = 0; ZoneServer* zs2 = 0; uint32 zoneid; - if (!(zoneid = ZoneID(zonename))) - SendEmoteMessage(adminname, 0, 0, 0, "Error: SOP_ZoneBootup: zone '%s' not found in 'zone' table. Typo protection=ON.", zonename); - else { - if (ZoneServerID != 0) + if (!(zoneid = ZoneID(zonename))) { + SendEmoteMessage( + adminname, + 0, + AccountStatus::Player, + Chat::White, + fmt::format( + "Error: SOP_ZoneBootup: Zone '{}' not found in 'zone' table.", + zonename + ).c_str() + ); + } else { + if (ZoneServerID != 0) { zs = FindByID(ZoneServerID); - else - SendEmoteMessage(adminname, 0, 0, 0, "Error: SOP_ZoneBootup: ServerID must be specified"); + } else { + SendEmoteMessage( + adminname, + 0, + AccountStatus::Player, + Chat::White, + "Error: SOP_ZoneBootup: Server ID must be specified." + ); + } - if (zs == 0) - SendEmoteMessage(adminname, 0, 0, 0, "Error: SOP_ZoneBootup: zoneserver not found"); - else { + if (!zs) { + SendEmoteMessage( + adminname, + 0, + AccountStatus::Player, + Chat::White, + "Error: SOP_ZoneBootup: Zoneserver not found." + ); + } else { zs2 = FindByName(zonename); if (zs2 != 0) - SendEmoteMessage(adminname, 0, 0, 0, "Error: SOP_ZoneBootup: zone '%s' already being hosted by ZoneServer #%i", zonename, zs2->GetID()); + SendEmoteMessage( + adminname, + 0, + AccountStatus::Player, + Chat::White, + fmt::format( + "Error: SOP_ZoneBootup: Zone '{}' already being hosted by Zoneserver ID {}.", + zonename, + zs2->GetID() + ).c_str() + ); else { zs->TriggerBootup(zoneid, 0, adminname, iMakeStatic); } @@ -677,7 +758,16 @@ void ZSList::UpdateUCSServerAvailable(bool ucss_available) { void ZSList::WorldShutDown(uint32 time, uint32 interval) { if (time > 0) { - SendEmoteMessage(0, 0, 0, 15, ":SYSTEM MSG:World coming down in %i minutes, everyone log out before this time.", (time / 60)); + SendEmoteMessage( + 0, + 0, + AccountStatus::Player, + Chat::Yellow, + fmt::format( + "[SYSTEM] World will be shutting down in {} minutes.", + (time / 60) + ).c_str() + ); time *= 1000; interval *= 1000; @@ -690,7 +780,13 @@ void ZSList::WorldShutDown(uint32 time, uint32 interval) reminder->Start(); } else { - SendEmoteMessage(0, 0, 0, 15, ":SYSTEM MSG:World coming down, everyone log out now."); + SendEmoteMessage( + 0, + 0, + AccountStatus::Player, + Chat::Yellow, + "[SYSTEM] World is shutting down." + ); auto pack = new ServerPacket; pack->opcode = ServerOP_ShutdownAll; pack->size = 0; diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index 008c98073..8326c0657 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -36,7 +36,12 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "ucs.h" #include "queryserv.h" #include "world_store.h" +#include "dynamic_zone.h" #include "expedition_message.h" +#include "shared_task_world_messaging.h" +#include "../common/shared_tasks.h" +#include "shared_task_manager.h" +#include "../common/content/world_content_service.h" extern ClientList client_list; extern GroupLFPList LFPGroupList; @@ -47,6 +52,8 @@ extern volatile bool UCSServerAvailable_; extern AdventureManager adventure_manager; extern UCSConnection UCSLink; extern QueryServConnection QSLink; +extern SharedTaskManager shared_task_manager; + void CatchSignal(int sig_num); ZoneServer::ZoneServer(std::shared_ptr connection, EQ::Net::ConsoleServer *console) @@ -432,7 +439,16 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { safe_delete(pack); }))) && (!scm->noreply)) { - zoneserver_list.SendEmoteMessage(scm->from, 0, 0, 0, "%s is not online at this time.", scm->to); + zoneserver_list.SendEmoteMessage( + scm->from, + 0, + AccountStatus::Player, + Chat::White, + fmt::format( + "{} is not online at this time.", + scm->to + ).c_str() + ); } } break; @@ -440,13 +456,14 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { ClientListEntry* cle = client_list.FindCharacter(scm->deliverto); if (cle == 0 || cle->Online() < CLE_Status::Zoning || - (cle->TellsOff() && ((cle->Anon() == 1 && scm->fromadmin < cle->Admin()) || scm->fromadmin < 80))) { + (cle->TellsOff() && ((cle->Anon() == 1 && scm->fromadmin < cle->Admin()) || scm->fromadmin < AccountStatus::QuestTroupe))) { if (!scm->noreply) { ClientListEntry* sender = client_list.FindCharacter(scm->from); if (!sender || !sender->Server()) break; scm->noreply = true; scm->queued = 3; // offline + scm->chan_num = ChatChannel_TellEcho; strcpy(scm->deliverto, scm->from); // ideally this would be trimming off the message too, oh well sender->Server()->SendPacket(pack); @@ -460,6 +477,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { break; scm->noreply = true; scm->queued = 2; // queue full + scm->chan_num = ChatChannel_TellEcho; strcpy(scm->deliverto, scm->from); sender->Server()->SendPacket(pack); } @@ -475,6 +493,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { break; scm->noreply = true; scm->queued = 1; // queued + scm->chan_num = ChatChannel_TellEcho; strcpy(scm->deliverto, scm->from); sender->Server()->SendPacket(pack); } @@ -482,7 +501,17 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { } else if (cle->Server() == 0) { if (!scm->noreply) - zoneserver_list.SendEmoteMessage(scm->from, 0, 0, 0, "You told %s, '%s is not contactable at this time'", scm->to, scm->to); + zoneserver_list.SendEmoteMessage( + scm->from, + 0, + AccountStatus::Player, + Chat::White, + fmt::format( + "You told {}, '{} is not contactable at this time'", + scm->to, + scm->to + ).c_str() + ); } else cle->Server()->SendPacket(pack); @@ -509,29 +538,36 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { } case ServerOP_EmoteMessage: { ServerEmoteMessage_Struct* sem = (ServerEmoteMessage_Struct*)pack->pBuffer; - zoneserver_list.SendEmoteMessageRaw(sem->to, sem->guilddbid, sem->minstatus, sem->type, sem->message); + zoneserver_list.SendEmoteMessageRaw( + sem->to, + sem->guilddbid, + sem->minstatus, + sem->type, + sem->message + ); break; } case ServerOP_VoiceMacro: { - ServerVoiceMacro_Struct* svm = (ServerVoiceMacro_Struct*)pack->pBuffer; - if (svm->Type == VoiceMacroTell) { - ClientListEntry* cle = client_list.FindCharacter(svm->To); - if (!cle || (cle->Online() < CLE_Status::Zoning) || !cle->Server()) { - - zoneserver_list.SendEmoteMessage(svm->From, 0, 0, 0, "'%s is not online at this time'", svm->To); - + zoneserver_list.SendEmoteMessage( + svm->From, + 0, + AccountStatus::Player, + Chat::White, + fmt::format( + "'{} is not online at this time'", + svm->To + ).c_str() + ); break; } - cle->Server()->SendPacket(pack); - } - else + } else { zoneserver_list.SendPacket(pack); - + } break; } @@ -605,7 +641,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { if (sci->local_address[0]) { strn0cpy(client_local_address, sci->local_address, 250); - LogInfo("Zone specified local address [{}]", sci->address); + LogInfo("Zone specified local address [{}]", sci->local_address); } if (sci->process_id) { @@ -647,17 +683,31 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { case ServerOP_ZoneShutdown: { ServerZoneStateChange_struct* s = (ServerZoneStateChange_struct *)pack->pBuffer; ZoneServer* zs = 0; - if (s->ZoneServerID != 0) + if (s->ZoneServerID != 0) { zs = zoneserver_list.FindByID(s->ZoneServerID); - else if (s->zoneid != 0) + } else if (s->zoneid != 0) { zs = zoneserver_list.FindByName(ZoneName(s->zoneid)); - else - zoneserver_list.SendEmoteMessage(s->adminname, 0, 0, 0, "Error: SOP_ZoneShutdown: neither ID nor name specified"); + } else { + zoneserver_list.SendEmoteMessage( + s->adminname, + 0, + AccountStatus::Player, + Chat::White, + "Error: SOP_ZoneShutdown: neither ID nor name specified" + ); + } - if (zs == 0) - zoneserver_list.SendEmoteMessage(s->adminname, 0, 0, 0, "Error: SOP_ZoneShutdown: zoneserver not found"); - else + if (!zs) { + zoneserver_list.SendEmoteMessage( + s->adminname, + 0, + AccountStatus::Player, + Chat::White, + "Error: SOP_ZoneShutdown: zoneserver not found" + ); + } else { zs->SendPacket(pack); + } break; } case ServerOP_ZoneBootup: { @@ -708,7 +758,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { if (GetZoneID() == ztz->current_zone_id && GetInstanceID() == ztz->current_instance_id) { LogInfo("Processing ZTZ for egress from zone for client [{}]", ztz->name); - if (ztz->admin < 80 && ztz->ignorerestrictions < 2 && zoneserver_list.IsZoneLocked(ztz->requested_zone_id)) { + if (ztz->admin < AccountStatus::QuestTroupe && ztz->ignorerestrictions < 2 && zoneserver_list.IsZoneLocked(ztz->requested_zone_id)) { ztz->response = 0; SendPacket(pack); break; @@ -811,7 +861,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { } case ServerOP_ReloadLogs: { zoneserver_list.SendPacket(pack); - database.LoadLogSettings(LogSys.log_settings); + LogSys.LoadLogDatabaseSettings(); break; } case ServerOP_ReloadRules: { @@ -819,6 +869,11 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { RuleManager::Instance()->LoadRules(&database, "default", true); break; } + case ServerOP_ReloadContentFlags: { + zoneserver_list.SendPacket(pack); + content_service.SetExpansionContext()->ReloadContentFlags(); + break; + } case ServerOP_ReloadRulesWorld: { RuleManager::Instance()->LoadRules(&database, "default", true); @@ -900,15 +955,43 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { ServerGMGoto_Struct* gmg = (ServerGMGoto_Struct*)pack->pBuffer; ClientListEntry* cle = client_list.FindCharacter(gmg->gotoname); if (cle != 0) { - if (cle->Server() == 0) - this->SendEmoteMessage(gmg->myname, 0, 0, 13, "Error: Cannot identify %s's zoneserver.", gmg->gotoname); - else if (cle->Anon() == 1 && cle->Admin() > gmg->admin) // no snooping for anon GMs - this->SendEmoteMessage(gmg->myname, 0, 0, 13, "Error: %s not found", gmg->gotoname); - else + if (!cle->Server()) { + SendEmoteMessage( + gmg->myname, + 0, + AccountStatus::Player, + Chat::Red, + fmt::format( + "Error: Cannot identify {}'s zoneserver.", + gmg->gotoname + ).c_str() + ); + } else if (cle->Anon() == 1 && cle->Admin() > gmg->admin) { // no snooping for anon GMs + SendEmoteMessage( + gmg->myname, + 0, + AccountStatus::Player, + Chat::Red, + fmt::format( + "Error: {} not found.", + gmg->gotoname + ).c_str() + ); + } else { cle->Server()->SendPacket(pack); + } } else { - this->SendEmoteMessage(gmg->myname, 0, 0, 13, "Error: %s not found", gmg->gotoname); + SendEmoteMessage( + gmg->myname, + 0, + AccountStatus::Player, + Chat::Red, + fmt::format( + "Error: {} not found", + gmg->gotoname + ).c_str() + ); } break; } @@ -924,16 +1007,28 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { WorldConfig::UnlockWorld(); if (loginserverlist.Connected()) { loginserverlist.SendStatus(); - if (slock->mode >= 1) - this->SendEmoteMessage(slock->myname, 0, 0, 13, "World locked"); - else - this->SendEmoteMessage(slock->myname, 0, 0, 13, "World unlocked"); + SendEmoteMessage( + slock->myname, + 0, + AccountStatus::Player, + Chat::Red, + fmt::format( + "World {}.", + slock->mode ? "locked" : "unlocked" + ).c_str() + ); } else { - if (slock->mode >= 1) - this->SendEmoteMessage(slock->myname, 0, 0, 13, "World locked, but login server not connected."); - else - this->SendEmoteMessage(slock->myname, 0, 0, 13, "World unlocked, but login server not conencted."); + SendEmoteMessage( + slock->myname, + 0, + AccountStatus::Player, + Chat::Red, + fmt::format( + "World {}, but login server not connected.", + slock->mode ? "locked" : "unlocked" + ).c_str() + ); } break; } @@ -944,7 +1039,6 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { } ServerMotd_Struct* smotd = (ServerMotd_Struct*)pack->pBuffer; database.SetVariable("MOTD", smotd->motd); - //this->SendEmoteMessage(smotd->myname, 0, 0, 13, "Updated Motd."); zoneserver_list.SendPacket(pack); break; } @@ -1013,22 +1107,44 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { LogInfo("Wrong size on ServerOP_LockZone. Got: [{}], Expected: [{}]", pack->size, sizeof(ServerLockZone_Struct)); break; } - ServerLockZone_Struct* s = (ServerLockZone_Struct*)pack->pBuffer; - switch (s->op) { - case 0: - zoneserver_list.ListLockedZones(s->adminname, this); + + ServerLockZone_Struct* lock_zone = (ServerLockZone_Struct*) pack->pBuffer; + if (lock_zone->op == ServerLockType::List) { + zoneserver_list.ListLockedZones(lock_zone->adminname, this); break; - case 1: - if (zoneserver_list.SetLockedZone(s->zoneID, true)) - zoneserver_list.SendEmoteMessage(0, 0, 80, 15, "Zone locked: %s", ZoneName(s->zoneID)); - else - this->SendEmoteMessageRaw(s->adminname, 0, 0, 0, "Failed to change lock"); - break; - case 2: - if (zoneserver_list.SetLockedZone(s->zoneID, false)) - zoneserver_list.SendEmoteMessage(0, 0, 80, 15, "Zone unlocked: %s", ZoneName(s->zoneID)); - else - this->SendEmoteMessageRaw(s->adminname, 0, 0, 0, "Failed to change lock"); + } else if ( + lock_zone->op == ServerLockType::Lock || + lock_zone->op == ServerLockType::Unlock + ) { + if (zoneserver_list.SetLockedZone(lock_zone->zoneID, lock_zone->op == ServerLockType::Lock)) { + zoneserver_list.SendEmoteMessage( + 0, + 0, + AccountStatus::QuestTroupe, + Chat::White, + fmt::format( + "Zone {} | Name: {} ({}) ID: {}", + lock_zone->op == ServerLockType::Lock ? "Locked" : "Unlocked", + ZoneLongName(lock_zone->zoneID), + ZoneName(lock_zone->zoneID), + lock_zone->zoneID + ).c_str() + ); + } else { + SendEmoteMessageRaw( + lock_zone->adminname, + 0, + AccountStatus::Player, + Chat::White, + fmt::format( + "Zone Failed to {} | Name: {} ({}) ID: {}", + lock_zone->op == ServerLockType::Lock ? "Lock" : "Unlock", + ZoneLongName(lock_zone->zoneID), + ZoneName(lock_zone->zoneID), + lock_zone->zoneID + ).c_str() + ); + } break; } break; @@ -1180,7 +1296,6 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { adventure_manager.IncrementAssassinationCount(*((uint16*)pack->pBuffer)); break; } - case ServerOP_AdventureZoneData: { adventure_manager.GetZoneData(*((uint16*)pack->pBuffer)); @@ -1238,97 +1353,42 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { QSLink.SendPacket(pack); break; } - case ServerOP_CZCastSpellPlayer: - case ServerOP_CZCastSpellGroup: - case ServerOP_CZCastSpellRaid: - case ServerOP_CZCastSpellGuild: - case ServerOP_CZMarqueePlayer: - case ServerOP_CZMarqueeGroup: - case ServerOP_CZMarqueeRaid: - case ServerOP_CZMarqueeGuild: - case ServerOP_CZMessagePlayer: - case ServerOP_CZMessageGroup: - case ServerOP_CZMessageRaid: - case ServerOP_CZMessageGuild: - case ServerOP_CZMovePlayer: - case ServerOP_CZMoveGroup: - case ServerOP_CZMoveRaid: - case ServerOP_CZMoveGuild: - case ServerOP_CZMoveInstancePlayer: - case ServerOP_CZMoveInstanceGroup: - case ServerOP_CZMoveInstanceRaid: - case ServerOP_CZMoveInstanceGuild: - case ServerOP_CZRemoveSpellPlayer: - case ServerOP_CZRemoveSpellGroup: - case ServerOP_CZRemoveSpellRaid: - case ServerOP_CZRemoveSpellGuild: - case ServerOP_CZSetEntityVariableByClientName: - case ServerOP_CZSetEntityVariableByNPCTypeID: - case ServerOP_CZSetEntityVariableByGroupID: - case ServerOP_CZSetEntityVariableByRaidID: - case ServerOP_CZSetEntityVariableByGuildID: - case ServerOP_CZSignalNPC: - case ServerOP_CZSignalClient: - case ServerOP_CZSignalClientByName: - case ServerOP_CZSignalGroup: - case ServerOP_CZSignalRaid: - case ServerOP_CZSignalGuild: - case ServerOP_CZTaskActivityResetPlayer: - case ServerOP_CZTaskActivityResetGroup: - case ServerOP_CZTaskActivityResetRaid: - case ServerOP_CZTaskActivityResetGuild: - case ServerOP_CZTaskActivityUpdatePlayer: - case ServerOP_CZTaskActivityUpdateGroup: - case ServerOP_CZTaskActivityUpdateRaid: - case ServerOP_CZTaskActivityUpdateGuild: - case ServerOP_CZTaskAssignPlayer: - case ServerOP_CZTaskAssignGroup: - case ServerOP_CZTaskAssignRaid: - case ServerOP_CZTaskAssignGuild: - case ServerOP_CZTaskDisablePlayer: - case ServerOP_CZTaskDisableGroup: - case ServerOP_CZTaskDisableRaid: - case ServerOP_CZTaskDisableGuild: - case ServerOP_CZTaskEnablePlayer: - case ServerOP_CZTaskEnableGroup: - case ServerOP_CZTaskEnableRaid: - case ServerOP_CZTaskEnableGuild: - case ServerOP_CZTaskFailPlayer: - case ServerOP_CZTaskFailGroup: - case ServerOP_CZTaskFailRaid: - case ServerOP_CZTaskFailGuild: - case ServerOP_CZTaskRemovePlayer: - case ServerOP_CZTaskRemoveGroup: - case ServerOP_CZTaskRemoveRaid: - case ServerOP_CZTaskRemoveGuild: - case ServerOP_WWAssignTask: - case ServerOP_WWCastSpell: - case ServerOP_WWDisableTask: - case ServerOP_WWEnableTask: - case ServerOP_WWFailTask: + case ServerOP_CZDialogueWindow: + case ServerOP_CZLDoNUpdate: + case ServerOP_CZMarquee: + case ServerOP_CZMessage: + case ServerOP_CZMove: + case ServerOP_CZSetEntityVariable: + case ServerOP_CZSignal: + case ServerOP_CZSpell: + case ServerOP_CZTaskUpdate: + case ServerOP_WWDialogueWindow: + case ServerOP_WWLDoNUpdate: case ServerOP_WWMarquee: case ServerOP_WWMessage: case ServerOP_WWMove: - case ServerOP_WWMoveInstance: - case ServerOP_WWRemoveSpell: - case ServerOP_WWRemoveTask: - case ServerOP_WWResetActivity: - case ServerOP_WWSetEntityVariableClient: - case ServerOP_WWSetEntityVariableNPC: - case ServerOP_WWSignalClient: - case ServerOP_WWSignalNPC: - case ServerOP_WWUpdateActivity: + case ServerOP_WWSetEntityVariable: + case ServerOP_WWSignal: + case ServerOP_WWSpell: + case ServerOP_WWTaskUpdate: case ServerOP_DepopAllPlayersCorpses: case ServerOP_DepopPlayerCorpse: case ServerOP_ReloadTitles: case ServerOP_SpawnStatusChange: - case ServerOP_ReloadTasks: case ServerOP_ReloadWorld: case ServerOP_UpdateSpawn: { zoneserver_list.SendPacket(pack); break; } + case ServerOP_ReloadTasks: + { + // world needs to update its copy of task data as well + shared_task_manager.LoadTaskData(); + + zoneserver_list.SendPacket(pack); + break; + } case ServerOP_ChangeSharedMem: { std::string hotfix_name = std::string((char*)pack->pBuffer); @@ -1359,47 +1419,56 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { case ServerOP_CZClientMessageString: { auto buf = reinterpret_cast(pack->pBuffer); - client_list.SendPacket(buf->character_name, pack); + client_list.SendPacket(buf->client_name, pack); break; } case ServerOP_ExpeditionLockout: case ServerOP_ExpeditionLockoutDuration: case ServerOP_ExpeditionLockState: - case ServerOP_ExpeditionMemberStatus: case ServerOP_ExpeditionReplayOnJoin: - case ServerOP_ExpeditionDzCompass: - case ServerOP_ExpeditionDzSafeReturn: - case ServerOP_ExpeditionDzZoneIn: - case ServerOP_ExpeditionExpireWarning: { zoneserver_list.SendPacket(pack); break; } - case ServerOP_ExpeditionChooseNewLeader: + case ServerOP_SharedTaskRequest: + case ServerOP_SharedTaskAddPlayer: + case ServerOP_SharedTaskAttemptRemove: + case ServerOP_SharedTaskUpdate: + case ServerOP_SharedTaskRequestMemberlist: + case ServerOP_SharedTaskRemovePlayer: + case ServerOP_SharedTaskInviteAcceptedPlayer: + case ServerOP_SharedTaskMakeLeader: + case ServerOP_SharedTaskCreateDynamicZone: + case ServerOP_SharedTaskPurgeAllCommand: + case ServerOP_SharedTaskPlayerList: + case ServerOP_SharedTaskKickPlayers: + { + SharedTaskWorldMessaging::HandleZoneMessage(pack); + break; + } + case ServerOP_ExpeditionCreate: - case ServerOP_ExpeditionGetOnlineMembers: - case ServerOP_ExpeditionMemberChange: - case ServerOP_ExpeditionMemberSwap: - case ServerOP_ExpeditionMembersRemoved: case ServerOP_ExpeditionDzAddPlayer: case ServerOP_ExpeditionDzMakeLeader: case ServerOP_ExpeditionCharacterLockout: case ServerOP_ExpeditionSaveInvite: case ServerOP_ExpeditionRequestInvite: - case ServerOP_ExpeditionSecondsRemaining: { ExpeditionMessage::HandleZoneMessage(pack); break; } - case ServerOP_DzCharacterChange: - case ServerOP_DzRemoveAllCharacters: + case ServerOP_DzCreated: + case ServerOP_DzAddRemoveMember: + case ServerOP_DzSwapMembers: + case ServerOP_DzRemoveAllMembers: + case ServerOP_DzGetMemberStatuses: + case ServerOP_DzSetSecondsRemaining: + case ServerOP_DzSetCompass: + case ServerOP_DzSetSafeReturn: + case ServerOP_DzSetZoneIn: + case ServerOP_DzUpdateMemberStatus: { - auto buf = reinterpret_cast(pack->pBuffer); - ZoneServer* instance_zs = zoneserver_list.FindByInstanceID(buf->instance_id); - if (instance_zs) - { - instance_zs->SendPacket(pack); - } + DynamicZone::HandleZoneMessage(pack); break; } default: @@ -1420,7 +1489,13 @@ void ZoneServer::SendEmoteMessage(const char* to, uint32 to_guilddbid, int16 to_ va_start(argptr, message); vsnprintf(buffer, sizeof(buffer), message, argptr); va_end(argptr); - SendEmoteMessageRaw(to, to_guilddbid, to_minstatus, type, buffer); + SendEmoteMessageRaw( + to, + to_guilddbid, + to_minstatus, + type, + buffer + ); } void ZoneServer::SendEmoteMessageRaw(const char* to, uint32 to_guilddbid, int16 to_minstatus, uint32 type, const char* message) { diff --git a/world/zoneserver.h b/world/zoneserver.h index 8bb1a6da8..98f60dcdb 100644 --- a/world/zoneserver.h +++ b/world/zoneserver.h @@ -22,6 +22,7 @@ #include "../common/net/servertalk_server.h" #include "../common/event/timer.h" #include "../common/timer.h" +#include "../common/emu_constants.h" #include "console.h" #include #include @@ -29,7 +30,6 @@ class Client; class ServerPacket; - class ZoneServer : public WorldTCPConnection { public: ZoneServer(std::shared_ptr connection, EQ::Net::ConsoleServer *console); diff --git a/zone/CMakeLists.txt b/zone/CMakeLists.txt index d28c8b85b..3c24da07a 100644 --- a/zone/CMakeLists.txt +++ b/zone/CMakeLists.txt @@ -1,277 +1,586 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.2) SET(zone_sources - aa.cpp - aa_ability.cpp - aggro.cpp - aggromanager.cpp - api_service.cpp - attack.cpp - aura.cpp - beacon.cpp - bonuses.cpp - bot.cpp - bot_command.cpp - bot_database.cpp - botspellsai.cpp - client.cpp - client_mods.cpp - client_packet.cpp - client_process.cpp - command.cpp - corpse.cpp - data_bucket.cpp - doors.cpp - dynamic_zone.cpp - effects.cpp - embparser.cpp - embparser_api.cpp - embperl.cpp - embxs.cpp - encounter.cpp - entity.cpp - exp.cpp - expedition.cpp - expedition_database.cpp - expedition_request.cpp - fastmath.cpp - fearpath.cpp - forage.cpp - groups.cpp - guild.cpp - guild_mgr.cpp - hate_list.cpp - heal_rotation.cpp - horse.cpp - inventory.cpp - loottables.cpp - lua_bit.cpp - lua_corpse.cpp - lua_client.cpp - lua_door.cpp - lua_encounter.cpp - lua_entity.cpp - lua_entity_list.cpp - lua_expedition.cpp - lua_general.cpp - lua_group.cpp - lua_hate_list.cpp - lua_inventory.cpp - lua_item.cpp - lua_iteminst.cpp - lua_mob.cpp - lua_mod.cpp - lua_npc.cpp - lua_object.cpp - lua_packet.cpp - lua_parser.cpp - lua_parser_events.cpp - lua_raid.cpp - lua_spawn.cpp - lua_spell.cpp - lua_stat_bonuses.cpp - embperl.cpp - embxs.cpp - entity.cpp - exp.cpp - fearpath.cpp - forage.cpp - global_loot_manager.cpp - groups.cpp - guild.cpp - guild_mgr.cpp - hate_list.cpp - horse.cpp - inventory.cpp - loottables.cpp - main.cpp - map.cpp - merc.cpp - mob.cpp - mob_ai.cpp - mob_appearance.cpp - mob_movement_manager.cpp - mob_info.cpp - mod_functions.cpp - npc.cpp - npc_ai.cpp - npc_scale_manager.cpp - object.cpp - oriented_bounding_box.cpp - pathfinder_interface.cpp - pathfinder_nav_mesh.cpp - pathfinder_null.cpp - pathing.cpp - perl_client.cpp - perl_doors.cpp - perl_entity.cpp - perl_expedition.cpp - perl_groups.cpp - perl_hateentry.cpp - perl_inventory.cpp - perl_mob.cpp - perl_npc.cpp - perl_object.cpp - perl_perlpacket.cpp - perl_player_corpse.cpp - perl_questitem.cpp - perl_raids.cpp - perlpacket.cpp - petitions.cpp - pets.cpp - position.cpp - qglobals.cpp - queryserv.cpp - questmgr.cpp - quest_parser_collection.cpp - raids.cpp - raycast_mesh.cpp - spawn2.cpp - spawn2.h - spawngroup.cpp - special_attacks.cpp - spell_effects.cpp - spells.cpp - task_client_state.cpp - task_goal_list_manager.cpp - task_manager.cpp - task_proximity_manager.cpp - tasks.cpp - titles.cpp - tradeskills.cpp - trading.cpp - trap.cpp - tribute.cpp - tune.cpp - water_map.cpp - water_map_v1.cpp - water_map_v2.cpp - waypoints.cpp - worldserver.cpp - xtargetautohaters.cpp - zone.cpp - zone_config.cpp - zonedb.cpp - zone_reload.cpp - zone_store.cpp - zoning.cpp) + aa.cpp + aa_ability.cpp + aggro.cpp + aggromanager.cpp + api_service.cpp + attack.cpp + aura.cpp + beacon.cpp + bonuses.cpp + bot.cpp + bot_command.cpp + bot_database.cpp + botspellsai.cpp + cheat_manager.cpp + client.cpp + client_mods.cpp + client_packet.cpp + client_process.cpp + command.cpp + corpse.cpp + data_bucket.cpp + doors.cpp + dialogue_window.cpp + dynamic_zone.cpp + effects.cpp + embparser.cpp + embparser_api.cpp + embperl.cpp + embxs.cpp + encounter.cpp + entity.cpp + exp.cpp + expedition.cpp + expedition_database.cpp + expedition_request.cpp + fastmath.cpp + fearpath.cpp + forage.cpp + groups.cpp + guild.cpp + guild_mgr.cpp + hate_list.cpp + heal_rotation.cpp + horse.cpp + inventory.cpp + loottables.cpp + lua_bot.cpp + lua_bit.cpp + lua_corpse.cpp + lua_client.cpp + lua_door.cpp + lua_encounter.cpp + lua_entity.cpp + lua_entity_list.cpp + lua_expedition.cpp + lua_general.cpp + lua_group.cpp + lua_hate_list.cpp + lua_inventory.cpp + lua_item.cpp + lua_iteminst.cpp + lua_mob.cpp + lua_mod.cpp + lua_npc.cpp + lua_object.cpp + lua_packet.cpp + lua_parser.cpp + lua_parser_events.cpp + lua_raid.cpp + lua_spawn.cpp + lua_spell.cpp + lua_stat_bonuses.cpp + embperl.cpp + embxs.cpp + entity.cpp + exp.cpp + fearpath.cpp + forage.cpp + global_loot_manager.cpp + gm_commands/door_manipulation.cpp + groups.cpp + guild.cpp + guild_mgr.cpp + hate_list.cpp + horse.cpp + inventory.cpp + loottables.cpp + main.cpp + map.cpp + merc.cpp + mob.cpp + mob_ai.cpp + mob_appearance.cpp + mob_movement_manager.cpp + mob_info.cpp + mod_functions.cpp + npc.cpp + npc_ai.cpp + npc_scale_manager.cpp + object.cpp + oriented_bounding_box.cpp + pathfinder_interface.cpp + pathfinder_nav_mesh.cpp + pathfinder_null.cpp + pathing.cpp + perl_bot.cpp + perl_client.cpp + perl_doors.cpp + perl_entity.cpp + perl_expedition.cpp + perl_groups.cpp + perl_hateentry.cpp + perl_inventory.cpp + perl_mob.cpp + perl_npc.cpp + perl_object.cpp + perl_perlpacket.cpp + perl_player_corpse.cpp + perl_questitem.cpp + perl_raids.cpp + perl_spell.cpp + perlpacket.cpp + petitions.cpp + pets.cpp + position.cpp + qglobals.cpp + queryserv.cpp + questmgr.cpp + quest_parser_collection.cpp + raids.cpp + raycast_mesh.cpp + shared_task_zone_messaging.cpp + spawn2.cpp + spawn2.h + spawngroup.cpp + special_attacks.cpp + spell_effects.cpp + spells.cpp + task_client_state.cpp + task_goal_list_manager.cpp + task_manager.cpp + task_proximity_manager.cpp + tasks.cpp + titles.cpp + tradeskills.cpp + trading.cpp + trap.cpp + tribute.cpp + tune.cpp + water_map.cpp + water_map_v1.cpp + water_map_v2.cpp + waypoints.cpp + worldserver.cpp + xtargetautohaters.cpp + zone.cpp + zone_config.cpp + zonedb.cpp + zone_event_scheduler.cpp + zone_reload.cpp + zone_store.cpp + zoning.cpp + ) SET(zone_headers - aa.h - aa_ability.h - aggromanager.h - api_service.h - aura.h - basic_functions.h - beacon.h - bot.h - bot_command.h - bot_database.h - bot_structs.h - client.h - client_packet.h - command.h - common.h - corpse.h - data_bucket.h - doors.h - dynamic_zone.h - embparser.h - embperl.h - embxs.h - encounter.h - entity.h - errmsg.h - event_codes.h - expedition.h - expedition_database.h - expedition_request.h - fastmath.h - forage.h - global_loot_manager.h - groups.h - guild_mgr.h - hate_list.h - heal_rotation.h - horse.h - lua_bit.h - lua_client.h - lua_corpse.h - lua_door.h - lua_encounter.h - lua_entity.h - lua_entity_list.h - lua_expedition.h - lua_general.h - lua_group.h - lua_hate_list.h - lua_inventory.h - lua_item.h - lua_iteminst.h - lua_mob.h - lua_mod.h - lua_npc.h - lua_object.h - lua_packet.h - lua_parser.h - lua_parser_events.h - lua_ptr.h - lua_raid.h - lua_spawn.h - lua_spell.h - lua_stat_bonuses.h - map.h - masterentity.h - maxskill.h - message.h - merc.h - mob.h - mob_movement_manager.h - npc.h - npc_ai.h - npc_scale_manager.h - object.h - oriented_bounding_box.h - pathfinder_interface.h - pathfinder_nav_mesh.h - pathfinder_null.h - perlpacket.h - petitions.h - pets.h - position.h - qglobals.h - quest_interface.h - queryserv.h - quest_interface.h - questmgr.h - quest_parser_collection.h - raids.h - raycast_mesh.h - skills.h - spawn2.cpp - spawn2.h - spawngroup.h - string_ids.h - task_client_state.h - task_goal_list_manager.h - task_manager.h - task_proximity_manager.h - tasks.h - titles.h - trap.h - water_map.h - water_map_v1.h - water_map_v2.h - worldserver.h - xtargetautohaters.h - zone.h - zone_config.h - zonedb.h - zonedump.h - zone_reload.h - zone_store.h) + aa.h + aa_ability.h + aggromanager.h + api_service.h + aura.h + basic_functions.h + beacon.h + bot.h + bot_command.h + bot_database.h + bot_structs.h + cheat_manager.h + client.h + client_packet.h + command.h + common.h + corpse.h + data_bucket.h + doors.h + dialogue_window.h + dynamic_zone.h + embparser.h + embperl.h + embxs.h + encounter.h + entity.h + errmsg.h + event_codes.h + expedition.h + expedition_database.h + expedition_request.h + fastmath.h + forage.h + global_loot_manager.h + gm_commands/door_manipulation.h + groups.h + guild_mgr.h + hate_list.h + heal_rotation.h + horse.h + lua_bot.h + lua_bit.h + lua_client.h + lua_corpse.h + lua_door.h + lua_encounter.h + lua_entity.h + lua_entity_list.h + lua_expedition.h + lua_general.h + lua_group.h + lua_hate_list.h + lua_inventory.h + lua_item.h + lua_iteminst.h + lua_mob.h + lua_mod.h + lua_npc.h + lua_object.h + lua_packet.h + lua_parser.h + lua_parser_events.h + lua_ptr.h + lua_raid.h + lua_spawn.h + lua_spell.h + lua_stat_bonuses.h + map.h + masterentity.h + maxskill.h + message.h + merc.h + mob.h + mob_movement_manager.h + npc.h + npc_ai.h + npc_scale_manager.h + object.h + oriented_bounding_box.h + pathfinder_interface.h + pathfinder_nav_mesh.h + pathfinder_null.h + perlpacket.h + petitions.h + pets.h + position.h + qglobals.h + quest_interface.h + queryserv.h + quest_interface.h + questmgr.h + quest_parser_collection.h + raids.h + raycast_mesh.h + skills.h + shared_task_zone_messaging.h + spawn2.cpp + spawn2.h + spawngroup.h + string_ids.h + task_client_state.h + task_goal_list_manager.h + task_manager.h + task_proximity_manager.h + tasks.h + titles.h + trap.h + water_map.h + water_map_v1.h + water_map_v2.h + worldserver.h + xtargetautohaters.h + zone.h + zone_event_scheduler.h + zone_config.h + zonedb.h + zonedump.h + zone_reload.h + zone_store.h + ) -ADD_EXECUTABLE(zone ${zone_sources} ${zone_headers}) +SET(gm_commands + gm_commands/acceptrules.cpp + gm_commands/advnpcspawn.cpp + gm_commands/aggro.cpp + gm_commands/aggrozone.cpp + gm_commands/ai.cpp + gm_commands/appearance.cpp + gm_commands/appearanceeffects.cpp + gm_commands/attack.cpp + gm_commands/augmentitem.cpp + gm_commands/ban.cpp + gm_commands/beard.cpp + gm_commands/beardcolor.cpp + gm_commands/bestz.cpp + gm_commands/bind.cpp + gm_commands/camerashake.cpp + gm_commands/castspell.cpp + gm_commands/chat.cpp + gm_commands/checklos.cpp + gm_commands/copycharacter.cpp + gm_commands/corpse.cpp + gm_commands/corpsefix.cpp + gm_commands/countitem.cpp + gm_commands/cvs.cpp + gm_commands/damage.cpp + gm_commands/databuckets.cpp + gm_commands/date.cpp + gm_commands/dbspawn2.cpp + gm_commands/delacct.cpp + gm_commands/deletegraveyard.cpp + gm_commands/delpetition.cpp + gm_commands/depop.cpp + gm_commands/depopzone.cpp + gm_commands/details.cpp + gm_commands/devtools.cpp + gm_commands/disablerecipe.cpp + gm_commands/disarmtrap.cpp + gm_commands/distance.cpp + gm_commands/doanim.cpp + gm_commands/door.cpp + gm_commands/dye.cpp + gm_commands/dz.cpp + gm_commands/dzkickplayers.cpp + gm_commands/editmassrespawn.cpp + gm_commands/emote.cpp + gm_commands/emotesearch.cpp + gm_commands/emoteview.cpp + gm_commands/enablerecipe.cpp + gm_commands/endurance.cpp + gm_commands/equipitem.cpp + gm_commands/face.cpp + gm_commands/faction.cpp + gm_commands/findclass.cpp + gm_commands/findfaction.cpp + gm_commands/findnpctype.cpp + gm_commands/findrace.cpp + gm_commands/findskill.cpp + gm_commands/findspell.cpp + gm_commands/findtask.cpp + gm_commands/findzone.cpp + gm_commands/fixmob.cpp + gm_commands/flag.cpp + gm_commands/flagedit.cpp + gm_commands/flags.cpp + gm_commands/flymode.cpp + gm_commands/fov.cpp + gm_commands/freeze.cpp + gm_commands/gassign.cpp + gm_commands/gearup.cpp + gm_commands/gender.cpp + gm_commands/getplayerburiedcorpsecount.cpp + gm_commands/getvariable.cpp + gm_commands/ginfo.cpp + gm_commands/giveitem.cpp + gm_commands/givemoney.cpp + gm_commands/globalview.cpp + gm_commands/gm.cpp + gm_commands/gmspeed.cpp + gm_commands/gmzone.cpp + gm_commands/goto.cpp + gm_commands/grid.cpp + gm_commands/guild.cpp + gm_commands/guildapprove.cpp + gm_commands/guildcreate.cpp + gm_commands/guildlist.cpp + gm_commands/hair.cpp + gm_commands/haircolor.cpp + gm_commands/haste.cpp + gm_commands/hatelist.cpp + gm_commands/heal.cpp + gm_commands/helm.cpp + gm_commands/heritage.cpp + gm_commands/heromodel.cpp + gm_commands/hideme.cpp + gm_commands/hp.cpp + gm_commands/incstat.cpp + gm_commands/instance.cpp + gm_commands/interrogateinv.cpp + gm_commands/interrupt.cpp + gm_commands/invsnapshot.cpp + gm_commands/invul.cpp + gm_commands/ipban.cpp + gm_commands/iplookup.cpp + gm_commands/iteminfo.cpp + gm_commands/itemsearch.cpp + gm_commands/kick.cpp + gm_commands/kill.cpp + gm_commands/killallnpcs.cpp + gm_commands/lastname.cpp + gm_commands/list.cpp + gm_commands/listpetition.cpp + gm_commands/loc.cpp + gm_commands/lock.cpp + gm_commands/logcommand.cpp + gm_commands/logs.cpp + gm_commands/makepet.cpp + gm_commands/mana.cpp + gm_commands/max_all_skills.cpp + gm_commands/memspell.cpp + gm_commands/merchantcloseshop.cpp + gm_commands/merchantopenshop.cpp + gm_commands/modifynpcstat.cpp + gm_commands/motd.cpp + gm_commands/movechar.cpp + gm_commands/movement.cpp + gm_commands/myskills.cpp + gm_commands/mysql.cpp + gm_commands/mystats.cpp + gm_commands/name.cpp + gm_commands/netstats.cpp + gm_commands/network.cpp + gm_commands/npccast.cpp + gm_commands/npcedit.cpp + gm_commands/npceditmass.cpp + gm_commands/npcemote.cpp + gm_commands/npcloot.cpp + gm_commands/npcsay.cpp + gm_commands/npcshout.cpp + gm_commands/npcspawn.cpp + gm_commands/npcspecialattk.cpp + gm_commands/npcstats.cpp + gm_commands/npctype_cache.cpp + gm_commands/npctypespawn.cpp + gm_commands/nudge.cpp + gm_commands/nukebuffs.cpp + gm_commands/nukeitem.cpp + gm_commands/object.cpp + gm_commands/oocmute.cpp + gm_commands/opcode.cpp + gm_commands/path.cpp + gm_commands/peekinv.cpp + gm_commands/peqzone.cpp + gm_commands/permaclass.cpp + gm_commands/permagender.cpp + gm_commands/permarace.cpp + gm_commands/petitems.cpp + gm_commands/petitioninfo.cpp + gm_commands/petname.cpp + gm_commands/pf.cpp + gm_commands/picklock.cpp + gm_commands/profanity.cpp + gm_commands/proximity.cpp + gm_commands/push.cpp + gm_commands/pvp.cpp + gm_commands/qglobal.cpp + gm_commands/questerrors.cpp + gm_commands/race.cpp + gm_commands/raidloot.cpp + gm_commands/randomfeatures.cpp + gm_commands/refreshgroup.cpp + gm_commands/reloadaa.cpp + gm_commands/reloadallrules.cpp + gm_commands/reloadcontentflags.cpp + gm_commands/reloademote.cpp + gm_commands/reloadlevelmods.cpp + gm_commands/reloadmerchants.cpp + gm_commands/reloadperlexportsettings.cpp + gm_commands/reloadqst.cpp + gm_commands/reloadstatic.cpp + gm_commands/reloadtitles.cpp + gm_commands/reloadtraps.cpp + gm_commands/reloadworld.cpp + gm_commands/reloadworldrules.cpp + gm_commands/reloadzps.cpp + gm_commands/removeitem.cpp + gm_commands/repop.cpp + gm_commands/resetaa.cpp + gm_commands/resetaa_timer.cpp + gm_commands/resetdisc_timer.cpp + gm_commands/revoke.cpp + gm_commands/roambox.cpp + gm_commands/rules.cpp + gm_commands/save.cpp + gm_commands/scale.cpp + gm_commands/scribespell.cpp + gm_commands/scribespells.cpp + gm_commands/sendzonespawns.cpp + gm_commands/sensetrap.cpp + gm_commands/serverinfo.cpp + gm_commands/serverrules.cpp + gm_commands/set_adventure_points.cpp + gm_commands/setaapts.cpp + gm_commands/setaaxp.cpp + gm_commands/setaltcurrency.cpp + gm_commands/setanim.cpp + gm_commands/setcrystals.cpp + gm_commands/setendurance.cpp + gm_commands/setfaction.cpp + gm_commands/setgraveyard.cpp + gm_commands/sethp.cpp + gm_commands/setlanguage.cpp + gm_commands/setlsinfo.cpp + gm_commands/setmana.cpp + gm_commands/setpass.cpp + gm_commands/setpvppoints.cpp + gm_commands/setskill.cpp + gm_commands/setskillall.cpp + gm_commands/setstartzone.cpp + gm_commands/setstat.cpp + gm_commands/setxp.cpp + gm_commands/showbonusstats.cpp + gm_commands/showbuffs.cpp + gm_commands/shownpcgloballoot.cpp + gm_commands/shownumhits.cpp + gm_commands/showskills.cpp + gm_commands/showspellslist.cpp + gm_commands/showstats.cpp + gm_commands/showzonegloballoot.cpp + gm_commands/showzonepoints.cpp + gm_commands/shutdown.cpp + gm_commands/size.cpp + gm_commands/spawn.cpp + gm_commands/spawnfix.cpp + gm_commands/spawnstatus.cpp + gm_commands/spellinfo.cpp + gm_commands/stun.cpp + gm_commands/summon.cpp + gm_commands/summonburiedplayercorpse.cpp + gm_commands/summonitem.cpp + gm_commands/suspend.cpp + gm_commands/task.cpp + gm_commands/tattoo.cpp + gm_commands/tempname.cpp + gm_commands/texture.cpp + gm_commands/time.cpp + gm_commands/timers.cpp + gm_commands/timezone.cpp + gm_commands/title.cpp + gm_commands/titlesuffix.cpp + gm_commands/traindisc.cpp + gm_commands/trapinfo.cpp + gm_commands/tune.cpp + gm_commands/ucs.cpp + gm_commands/undye.cpp + gm_commands/undyeme.cpp + gm_commands/unfreeze.cpp + gm_commands/unlock.cpp + gm_commands/unmemspell.cpp + gm_commands/unmemspells.cpp + gm_commands/unscribespell.cpp + gm_commands/unscribespells.cpp + gm_commands/untraindisc.cpp + gm_commands/untraindiscs.cpp + gm_commands/uptime.cpp + gm_commands/version.cpp + gm_commands/viewcurrencies.cpp + gm_commands/viewnpctype.cpp + gm_commands/viewpetition.cpp + gm_commands/viewzoneloot.cpp + gm_commands/wc.cpp + gm_commands/weather.cpp + gm_commands/who.cpp + gm_commands/worldshutdown.cpp + gm_commands/worldwide.cpp + gm_commands/wp.cpp + gm_commands/wpadd.cpp + gm_commands/wpinfo.cpp + gm_commands/xtargets.cpp + gm_commands/zclip.cpp + gm_commands/zcolor.cpp + gm_commands/zheader.cpp + gm_commands/zonebootup.cpp + gm_commands/zonelock.cpp + gm_commands/zoneshutdown.cpp + gm_commands/zonestatus.cpp + gm_commands/zopp.cpp + gm_commands/zsafecoords.cpp + gm_commands/zsave.cpp + gm_commands/zsky.cpp + gm_commands/zstats.cpp + gm_commands/zunderworld.cpp +) + +ADD_EXECUTABLE(zone ${zone_sources} ${zone_headers} ${gm_commands}) INSTALL(TARGETS zone RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) diff --git a/zone/aa.cpp b/zone/aa.cpp index 0b19065bf..3607385d2 100644 --- a/zone/aa.cpp +++ b/zone/aa.cpp @@ -69,10 +69,10 @@ void Mob::TemporaryPets(uint16 spell_id, Mob *targ, const char *name_override, u for (int x = 0; x < MAX_SWARM_PETS; x++) { - if (spells[spell_id].effectid[x] == SE_TemporaryPets) + if (spells[spell_id].effect_id[x] == SE_TemporaryPets) { - pet.count = spells[spell_id].base[x]; - pet.duration = spells[spell_id].max[x]; + pet.count = spells[spell_id].base_value[x]; + pet.duration = spells[spell_id].max_value[x]; } } @@ -151,10 +151,14 @@ void Mob::TemporaryPets(uint16 spell_id, Mob *targ, const char *name_override, u //give the pets somebody to "love" if (targ != nullptr) { swarm_pet_npc->AddToHateList(targ, 1000, 1000); - if (RuleB(Spells, SwarmPetTargetLock) || sticktarg) + if (RuleB(Spells, SwarmPetTargetLock) || sticktarg) { swarm_pet_npc->GetSwarmInfo()->target = targ->GetID(); - else + swarm_pet_npc->SetPetTargetLockID(targ->GetID()); + swarm_pet_npc->SetSpecialAbility(IMMUNE_AGGRO, 1); + } + else { swarm_pet_npc->GetSwarmInfo()->target = 0; + } } //we allocated a new NPC type object, give the NPC ownership of that memory @@ -213,7 +217,7 @@ void Mob::TypesTemporaryPets(uint32 typesid, Mob *targ, const char *name_overrid glm::vec2(5, 5), glm::vec2(-5, 5), glm::vec2(5, -5), glm::vec2(-5, -5), glm::vec2(10, 10), glm::vec2(-10, 10), glm::vec2(10, -10), glm::vec2(-10, -10), glm::vec2(8, 8), glm::vec2(-8, 8), glm::vec2(8, -8), glm::vec2(-8, -8) - };; + }; while(summon_count > 0) { int pet_duration = pet.duration; @@ -255,10 +259,14 @@ void Mob::TypesTemporaryPets(uint32 typesid, Mob *targ, const char *name_overrid if(targ != nullptr){ swarm_pet_npc->AddToHateList(targ, 1000, 1000); - if (RuleB(Spells, SwarmPetTargetLock) || sticktarg) + if (RuleB(Spells, SwarmPetTargetLock) || sticktarg) { swarm_pet_npc->GetSwarmInfo()->target = targ->GetID(); - else + swarm_pet_npc->SetPetTargetLockID(targ->GetID()); + swarm_pet_npc->SetSpecialAbility(IMMUNE_AGGRO, 1); + } + else { swarm_pet_npc->GetSwarmInfo()->target = 0; + } } //we allocated a new NPC type object, give the NPC ownership of that memory @@ -273,63 +281,94 @@ void Mob::TypesTemporaryPets(uint32 typesid, Mob *targ, const char *name_overrid delete made_npc; } -void Mob::WakeTheDead(uint16 spell_id, Mob *target, uint32 duration) -{ - Corpse *CorpseToUse = nullptr; - CorpseToUse = entity_list.GetClosestCorpse(this, nullptr); +void Mob::WakeTheDead(uint16 spell_id, Corpse *corpse_to_use, Mob *target, uint32 duration) { - if(!CorpseToUse) + /* + SPA 299 Wake The Dead, 'animateDead' should be temp pet, always spawns 1 pet from corpse, max value is duration + SPA 306 Wake The Dead, 'animateDead#' should be temp pet, base is amount of pets from indivual corpses, max value is duration + Max range for closet corpse is 250 units. + TODO: Should use temp pets + */ + + if (!corpse_to_use) { return; + } - //assuming we have pets in our table; we take the first pet as a base type. - const NPCType *base_type = content_db.LoadNPCTypesData(500); - auto make_npc = new NPCType; - memcpy(make_npc, base_type, sizeof(NPCType)); + /* TODO: Does WTD use pet focus? + int act_power = 0; - //combat stats - make_npc->AC = ((GetLevel() * 7) + 550); - make_npc->ATK = GetLevel(); - make_npc->max_dmg = (GetLevel() * 4) + 2; - make_npc->min_dmg = 1; + if (IsClient()) { + act_power = CastToClient()->GetFocusEffect(focusPetPower, spell_id); + act_power = CastToClient()->mod_pet_power(act_power, spell_id); + } + */ - //base stats - make_npc->current_hp = (GetLevel() * 55); - make_npc->max_hp = (GetLevel() * 55); - make_npc->STR = 85 + (GetLevel() * 3); - make_npc->STA = 85 + (GetLevel() * 3); - make_npc->DEX = 85 + (GetLevel() * 3); - make_npc->AGI = 85 + (GetLevel() * 3); - make_npc->INT = 85 + (GetLevel() * 3); - make_npc->WIS = 85 + (GetLevel() * 3); - make_npc->CHA = 85 + (GetLevel() * 3); - make_npc->MR = 25; - make_npc->FR = 25; - make_npc->CR = 25; - make_npc->DR = 25; - make_npc->PR = 25; + SwarmPet_Struct pet; + pet.count = 1; + pet.duration = 1; + + //pet.duration += GetFocusEffect(focusSwarmPetDuration, spell_id) / 1000; //TODO: Does WTD use pet focus? + + pet.npc_id = WAKE_THE_DEAD_NPCTYPEID; + + NPCType *made_npc = nullptr; + + const NPCType *npc_type = content_db.LoadNPCTypesData(WAKE_THE_DEAD_NPCTYPEID); + if (npc_type == nullptr) { + //log write + LogError("Unknown npc type for 'Wake the Dead' swarm pet spell id: [{}]", spell_id); + Message(0, "Unable to find pet!"); + return; + } + + made_npc = new NPCType; + memcpy(made_npc, npc_type, sizeof(NPCType)); - //level class and gender - make_npc->level = GetLevel(); - make_npc->class_ = CorpseToUse->class_; - make_npc->race = CorpseToUse->race; - make_npc->gender = CorpseToUse->gender; - make_npc->loottable_id = 0; - //name char NewName[64]; sprintf(NewName, "%s`s Animated Corpse", GetCleanName()); - strcpy(make_npc->name, NewName); + strcpy(made_npc->name, NewName); + npc_type = made_npc; + + //combat stats + made_npc->AC = ((GetLevel() * 7) + 550); + made_npc->ATK = GetLevel(); + made_npc->max_dmg = (GetLevel() * 4) + 2; + made_npc->min_dmg = 1; + + //base stats + made_npc->current_hp = (GetLevel() * 55); + made_npc->max_hp = (GetLevel() * 55); + made_npc->STR = 85 + (GetLevel() * 3); + made_npc->STA = 85 + (GetLevel() * 3); + made_npc->DEX = 85 + (GetLevel() * 3); + made_npc->AGI = 85 + (GetLevel() * 3); + made_npc->INT = 85 + (GetLevel() * 3); + made_npc->WIS = 85 + (GetLevel() * 3); + made_npc->CHA = 85 + (GetLevel() * 3); + made_npc->MR = 25; + made_npc->FR = 25; + made_npc->CR = 25; + made_npc->DR = 25; + made_npc->PR = 25; + + //level class and gender + made_npc->level = GetLevel(); + made_npc->class_ = corpse_to_use->class_; + made_npc->race = corpse_to_use->race; + made_npc->gender = corpse_to_use->gender; + made_npc->loottable_id = 0; //appearance - make_npc->beard = CorpseToUse->beard; - make_npc->beardcolor = CorpseToUse->beardcolor; - make_npc->eyecolor1 = CorpseToUse->eyecolor1; - make_npc->eyecolor2 = CorpseToUse->eyecolor2; - make_npc->haircolor = CorpseToUse->haircolor; - make_npc->hairstyle = CorpseToUse->hairstyle; - make_npc->helmtexture = CorpseToUse->helmtexture; - make_npc->luclinface = CorpseToUse->luclinface; - make_npc->size = CorpseToUse->size; - make_npc->texture = CorpseToUse->texture; + made_npc->beard = corpse_to_use->beard; + made_npc->beardcolor = corpse_to_use->beardcolor; + made_npc->eyecolor1 = corpse_to_use->eyecolor1; + made_npc->eyecolor2 = corpse_to_use->eyecolor2; + made_npc->haircolor = corpse_to_use->haircolor; + made_npc->hairstyle = corpse_to_use->hairstyle; + made_npc->helmtexture = corpse_to_use->helmtexture; + made_npc->luclinface = corpse_to_use->luclinface; + made_npc->size = corpse_to_use->size; + made_npc->texture = corpse_to_use->texture; //cast stuff.. based off of PEQ's if you want to change //it you'll have to mod this code, but most likely @@ -337,130 +376,144 @@ void Mob::WakeTheDead(uint16 spell_id, Mob *target, uint32 duration) //part of their spell list; can't think of any smooth //way to do this //some basic combat mods here too since it's convienent - switch(CorpseToUse->class_) + switch (corpse_to_use->class_) { case CLERIC: - make_npc->npc_spells_id = 1; + made_npc->npc_spells_id = 1; break; case WIZARD: - make_npc->npc_spells_id = 2; + made_npc->npc_spells_id = 2; break; case NECROMANCER: - make_npc->npc_spells_id = 3; + made_npc->npc_spells_id = 3; break; case MAGICIAN: - make_npc->npc_spells_id = 4; + made_npc->npc_spells_id = 4; break; case ENCHANTER: - make_npc->npc_spells_id = 5; + made_npc->npc_spells_id = 5; break; case SHAMAN: - make_npc->npc_spells_id = 6; + made_npc->npc_spells_id = 6; break; case DRUID: - make_npc->npc_spells_id = 7; + made_npc->npc_spells_id = 7; break; case PALADIN: //SPECATK_TRIPLE - strcpy(make_npc->special_abilities, "6,1"); - make_npc->current_hp = make_npc->current_hp * 150 / 100; - make_npc->max_hp = make_npc->max_hp * 150 / 100; - make_npc->npc_spells_id = 8; + strcpy(made_npc->special_abilities, "6,1"); + made_npc->current_hp = made_npc->current_hp * 150 / 100; + made_npc->max_hp = made_npc->max_hp * 150 / 100; + made_npc->npc_spells_id = 8; break; case SHADOWKNIGHT: - strcpy(make_npc->special_abilities, "6,1"); - make_npc->current_hp = make_npc->current_hp * 150 / 100; - make_npc->max_hp = make_npc->max_hp * 150 / 100; - make_npc->npc_spells_id = 9; + strcpy(made_npc->special_abilities, "6,1"); + made_npc->current_hp = made_npc->current_hp * 150 / 100; + made_npc->max_hp = made_npc->max_hp * 150 / 100; + made_npc->npc_spells_id = 9; break; case RANGER: - strcpy(make_npc->special_abilities, "7,1"); - make_npc->current_hp = make_npc->current_hp * 135 / 100; - make_npc->max_hp = make_npc->max_hp * 135 / 100; - make_npc->npc_spells_id = 10; + strcpy(made_npc->special_abilities, "7,1"); + made_npc->current_hp = made_npc->current_hp * 135 / 100; + made_npc->max_hp = made_npc->max_hp * 135 / 100; + made_npc->npc_spells_id = 10; break; case BARD: - strcpy(make_npc->special_abilities, "6,1"); - make_npc->current_hp = make_npc->current_hp * 110 / 100; - make_npc->max_hp = make_npc->max_hp * 110 / 100; - make_npc->npc_spells_id = 11; + strcpy(made_npc->special_abilities, "6,1"); + made_npc->current_hp = made_npc->current_hp * 110 / 100; + made_npc->max_hp = made_npc->max_hp * 110 / 100; + made_npc->npc_spells_id = 11; break; case BEASTLORD: - strcpy(make_npc->special_abilities, "7,1"); - make_npc->current_hp = make_npc->current_hp * 110 / 100; - make_npc->max_hp = make_npc->max_hp * 110 / 100; - make_npc->npc_spells_id = 12; + strcpy(made_npc->special_abilities, "7,1"); + made_npc->current_hp = made_npc->current_hp * 110 / 100; + made_npc->max_hp = made_npc->max_hp * 110 / 100; + made_npc->npc_spells_id = 12; break; case ROGUE: - strcpy(make_npc->special_abilities, "7,1"); - make_npc->max_dmg = make_npc->max_dmg * 150 /100; - make_npc->current_hp = make_npc->current_hp * 110 / 100; - make_npc->max_hp = make_npc->max_hp * 110 / 100; + strcpy(made_npc->special_abilities, "7,1"); + made_npc->max_dmg = made_npc->max_dmg * 150 / 100; + made_npc->current_hp = made_npc->current_hp * 110 / 100; + made_npc->max_hp = made_npc->max_hp * 110 / 100; break; case MONK: - strcpy(make_npc->special_abilities, "7,1"); - make_npc->max_dmg = make_npc->max_dmg * 150 /100; - make_npc->current_hp = make_npc->current_hp * 135 / 100; - make_npc->max_hp = make_npc->max_hp * 135 / 100; + strcpy(made_npc->special_abilities, "7,1"); + made_npc->max_dmg = made_npc->max_dmg * 150 / 100; + made_npc->current_hp = made_npc->current_hp * 135 / 100; + made_npc->max_hp = made_npc->max_hp * 135 / 100; break; case WARRIOR: case BERSERKER: - strcpy(make_npc->special_abilities, "7,1"); - make_npc->max_dmg = make_npc->max_dmg * 150 /100; - make_npc->current_hp = make_npc->current_hp * 175 / 100; - make_npc->max_hp = make_npc->max_hp * 175 / 100; + strcpy(made_npc->special_abilities, "7,1"); + made_npc->max_dmg = made_npc->max_dmg * 150 / 100; + made_npc->current_hp = made_npc->current_hp * 175 / 100; + made_npc->max_hp = made_npc->max_hp * 175 / 100; break; default: - make_npc->npc_spells_id = 0; + made_npc->npc_spells_id = 0; break; } - make_npc->loottable_id = 0; - make_npc->merchanttype = 0; - make_npc->d_melee_texture1 = 0; - make_npc->d_melee_texture2 = 0; + made_npc->loottable_id = 0; + made_npc->merchanttype = 0; + made_npc->d_melee_texture1 = 0; + made_npc->d_melee_texture2 = 0; - auto npca = new NPC(make_npc, 0, GetPosition(), GravityBehavior::Water); - if(!npca->GetSwarmInfo()){ - auto nSI = new SwarmPet; - npca->SetSwarmInfo(nSI); - npca->GetSwarmInfo()->duration = new Timer(duration*1000); - } - else{ - npca->GetSwarmInfo()->duration->Start(duration*1000); - } + int summon_count = 0; + summon_count = pet.count; - npca->StartSwarmTimer(duration * 1000); - npca->GetSwarmInfo()->owner_id = GetID(); + NPC* swarm_pet_npc = nullptr; + //TODO: potenitally add support for multiple pets per corpse + while (summon_count > 0) { + int pet_duration = duration; - //give the pet somebody to "love" - if(target != nullptr){ - npca->AddToHateList(target, 100000); - npca->GetSwarmInfo()->target = target->GetID(); - } - - //gear stuff, need to make sure there's - //no situation where this stuff can be duped - for (int x = EQ::invslot::EQUIPMENT_BEGIN; x <= EQ::invslot::EQUIPMENT_END; x++) - { - uint32 sitem = 0; - sitem = CorpseToUse->GetWornItem(x); - if(sitem){ - const EQ::ItemData * itm = database.GetItem(sitem); - npca->AddLootDrop(itm, &npca->itemlist, NPC::NewLootDropEntry(), true); + NPCType *npc_dup = nullptr; + if (made_npc != nullptr) { + npc_dup = new NPCType; + memcpy(npc_dup, made_npc, sizeof(NPCType)); } + + swarm_pet_npc = new NPC( + (npc_dup != nullptr) ? npc_dup : npc_type, + 0, corpse_to_use->GetPosition(),GravityBehavior::Water); + + swarm_pet_npc->SetFollowID(GetID()); + + if (!swarm_pet_npc->GetSwarmInfo()) { + auto nSI = new SwarmPet; + swarm_pet_npc->SetSwarmInfo(nSI); + swarm_pet_npc->GetSwarmInfo()->duration = new Timer(pet_duration * 1000); + } + else { + swarm_pet_npc->GetSwarmInfo()->duration->Start(pet_duration * 1000); + } + + swarm_pet_npc->StartSwarmTimer(pet_duration * 1000); + + //removing this prevents the pet from attacking + swarm_pet_npc->GetSwarmInfo()->owner_id = GetID(); + + //give the pets somebody to "love" + if (target != nullptr) { + swarm_pet_npc->AddToHateList(target, 10000, 1000); + swarm_pet_npc->GetSwarmInfo()->target = 0; + } + + //we allocated a new NPC type object, give the NPC ownership of that memory + if (npc_dup != nullptr) + swarm_pet_npc->GiveNPCTypeData(npc_dup); + + entity_list.AddNPC(swarm_pet_npc, true, true); + summon_count--; } - //we allocated a new NPC type object, give the NPC ownership of that memory - if(make_npc != nullptr) - npca->GiveNPCTypeData(make_npc); - - entity_list.AddNPC(npca, true, true); - //the target of these swarm pets will take offense to being cast on... - if(target != nullptr) + if (target != nullptr) target->AddToHateList(this, 1, 0); + + // The other pointers we make are handled elsewhere. + delete made_npc; } void Client::ResetAA() { @@ -492,15 +545,26 @@ void Client::ResetAA() { m_pp.raid_leadership_points = 0; m_pp.group_leadership_exp = 0; m_pp.raid_leadership_exp = 0; - + + database.DeleteCharacterAAs(CharacterID()); database.DeleteCharacterLeadershipAAs(CharacterID()); } void Client::SendClearAA() { - auto outapp = new EQApplicationPacket(OP_ClearLeadershipAbilities, 0); + SendClearLeadershipAA(); + SendClearPlayerAA(); +} + +void Client::SendClearPlayerAA() +{ + auto outapp = new EQApplicationPacket(OP_ClearAA, 0); FastQueuePacket(&outapp); - outapp = new EQApplicationPacket(OP_ClearAA, 0); +} + +void Client::SendClearLeadershipAA() +{ + auto outapp = new EQApplicationPacket(OP_ClearLeadershipAbilities, 0); FastQueuePacket(&outapp); } @@ -764,7 +828,7 @@ void Client::InspectBuffs(Client* Inspector, int Rank) continue; ib->spell_id[packet_index] = buffs[i].spellid; if (Rank > 1) - ib->tics_remaining[packet_index] = spells[buffs[i].spellid].buffdurationformula == DF_Permanent ? 0xFFFFFFFF : buffs[i].ticsremaining; + ib->tics_remaining[packet_index] = spells[buffs[i].spellid].buff_duration_formula == DF_Permanent ? 0xFFFFFFFF : buffs[i].ticsremaining; packet_index++; } @@ -904,8 +968,8 @@ void Client::SendAlternateAdvancementRank(int aa_id, int level) { outapp->SetWritePosition(sizeof(AARankInfo_Struct)); for(auto &effect : rank->effects) { outapp->WriteSInt32(effect.effect_id); - outapp->WriteSInt32(effect.base1); - outapp->WriteSInt32(effect.base2); + outapp->WriteSInt32(effect.base_value); + outapp->WriteSInt32(effect.limit_value); outapp->WriteSInt32(effect.slot); } @@ -1162,25 +1226,28 @@ void Client::IncrementAlternateAdvancementRank(int rank_id) { void Client::ActivateAlternateAdvancementAbility(int rank_id, int target_id) { AA::Rank *rank = zone->GetAlternateAdvancementRank(rank_id); - if(!rank) { + + if (!rank) { return; } AA::Ability *ability = rank->base_ability; - if(!ability) { + if (!ability) { return; } - if(!IsValidSpell(rank->spell)) { + if (!IsValidSpell(rank->spell)) { return; } - if(!CanUseAlternateAdvancementRank(rank)) { + if (!CanUseAlternateAdvancementRank(rank)) { return; } + bool use_toggle_passive_hotkey = UseTogglePassiveHotkey(*rank); + //make sure it is not a passive - if(!rank->effects.empty()) { + if (!rank->effects.empty() && !use_toggle_passive_hotkey) { return; } @@ -1188,34 +1255,32 @@ void Client::ActivateAlternateAdvancementAbility(int rank_id, int target_id) { // We don't have the AA if (!GetAA(rank_id, &charges)) return; - //if expendable make sure we have charges - if(ability->charges > 0 && charges < 1) + if (ability->charges > 0 && charges < 1) return; //check cooldown - if(!p_timers.Expired(&database, rank->spell_type + pTimerAAStart, false)) { + if (!p_timers.Expired(&database, rank->spell_type + pTimerAAStart, false)) { uint32 aaremain = p_timers.GetRemainingTime(rank->spell_type + pTimerAAStart); uint32 aaremain_hr = aaremain / (60 * 60); uint32 aaremain_min = (aaremain / 60) % 60; uint32 aaremain_sec = aaremain % 60; - if(aaremain_hr >= 1) { + if (aaremain_hr >= 1) { Message(Chat::Red, "You can use this ability again in %u hour(s) %u minute(s) %u seconds", - aaremain_hr, aaremain_min, aaremain_sec); + aaremain_hr, aaremain_min, aaremain_sec); } else { Message(Chat::Red, "You can use this ability again in %u minute(s) %u seconds", - aaremain_min, aaremain_sec); + aaremain_min, aaremain_sec); } return; } - //calculate cooldown - int cooldown = rank->recast_time - GetAlternateAdvancementCooldownReduction(rank); - if(cooldown < 0) { - cooldown = 0; + int timer_duration = rank->recast_time - GetAlternateAdvancementCooldownReduction(rank); + if (timer_duration < 0) { + timer_duration = 0; } if (!IsCastWhileInvis(rank->spell)) @@ -1227,11 +1292,11 @@ void Client::ActivateAlternateAdvancementAbility(int rank_id, int target_id) { } // // Modern clients don't require pet targeted for AA casts that are ST_Pet - if (spells[rank->spell].targettype == ST_Pet || spells[rank->spell].targettype == ST_SummonedPet) + if (spells[rank->spell].target_type == ST_Pet || spells[rank->spell].target_type == ST_SummonedPet) target_id = GetPetID(); // extra handling for cast_not_standing spells - if (!spells[rank->spell].cast_not_standing) { + if (!IgnoreCastingRestriction(rank->spell)) { if (GetAppearance() == eaSitting) // we need to stand! SetAppearance(eaStanding, false); @@ -1241,20 +1306,27 @@ void Client::ActivateAlternateAdvancementAbility(int rank_id, int target_id) { } } - // Bards can cast instant cast AAs while they are casting another song - if(spells[rank->spell].cast_time == 0 && GetClass() == BARD && IsBardSong(casting_spell_id)) { - if(!SpellFinished(rank->spell, entity_list.GetMob(target_id), EQ::spells::CastingSlot::AltAbility, spells[rank->spell].mana, -1, spells[rank->spell].ResistDiff, false)) { - return; + if (use_toggle_passive_hotkey) { + TogglePassiveAlternativeAdvancement(*rank, ability->id); + } + else { + // Bards can cast instant cast AAs while they are casting or channeling item cast. + if (GetClass() == BARD && IsCasting() && spells[rank->spell].cast_time == 0) { + if (!DoCastingChecksOnCaster(rank->spell)) { + return; + } + + if (!SpellFinished(rank->spell, entity_list.GetMob(target_id), EQ::spells::CastingSlot::AltAbility, spells[rank->spell].mana, -1, spells[rank->spell].resist_difficulty, false, -1, + rank->spell_type + pTimerAAStart, timer_duration, false, rank->id)) { + return; + } } - ExpendAlternateAdvancementCharge(ability->id); - } else { - if(!CastSpell(rank->spell, target_id, EQ::spells::CastingSlot::AltAbility, -1, -1, 0, -1, rank->spell_type + pTimerAAStart, cooldown, nullptr, rank->id)) { - return; + else { + if (!CastSpell(rank->spell, target_id, EQ::spells::CastingSlot::AltAbility, -1, -1, 0, -1, rank->spell_type + pTimerAAStart, timer_duration, nullptr, rank->id)) { + return; + } } } - - CastToClient()->GetPTimers().Start(rank->spell_type + pTimerAAStart, cooldown); - SendAlternateAdvancementTimer(rank->spell_type, 0, 0); } int Mob::GetAlternateAdvancementCooldownReduction(AA::Rank *rank_in) { @@ -1267,6 +1339,7 @@ int Mob::GetAlternateAdvancementCooldownReduction(AA::Rank *rank_in) { return 0; } + int total_reduction = 0; for(auto &aa : aa_ranks) { auto ability_rank = zone->GetAlternateAdvancementAbilityAndRank(aa.first, aa.second.first); auto ability = ability_rank.first; @@ -1277,26 +1350,26 @@ int Mob::GetAlternateAdvancementCooldownReduction(AA::Rank *rank_in) { } for(auto &effect : rank->effects) { - if(effect.effect_id == SE_HastenedAASkill && effect.base2 == ability_in->id) { - return effect.base1; + if(effect.effect_id == SE_HastenedAASkill && effect.limit_value == ability_in->id) { + total_reduction += effect.base_value; } } } - return 0; + return total_reduction; } void Mob::ExpendAlternateAdvancementCharge(uint32 aa_id) { - for(auto &iter : aa_ranks) { + for (auto &iter : aa_ranks) { AA::Ability *ability = zone->GetAlternateAdvancementAbility(iter.first); - if(ability && aa_id == ability->id) { - if(iter.second.second > 0) { + if (ability && aa_id == ability->id) { + if (iter.second.second > 0) { iter.second.second -= 1; - if(iter.second.second == 0) { - if(IsClient()) { + if (iter.second.second == 0) { + if (IsClient()) { AA::Rank *r = ability->GetRankByPointsSpent(iter.second.first); - if(r) { + if (r) { CastToClient()->GetEPP().expended_aa += r->cost; } } @@ -1307,7 +1380,7 @@ void Mob::ExpendAlternateAdvancementCharge(uint32 aa_id) { aa_ranks.erase(iter.first); } - if(IsClient()) { + if (IsClient()) { Client *c = CastToClient(); c->SaveAA(); c->SendAlternateAdvancementPoints(); @@ -1691,11 +1764,18 @@ bool ZoneDatabase::LoadAlternateAdvancementAbilities(std::unordered_map= 0) { + query = fmt::format("SELECT id, upper_hotkey_sid, lower_hotkey_sid, title_sid, desc_sid, cost, level_req, spell, spell_type, recast_time, " + "next_id, expansion FROM aa_ranks WHERE expansion <= {}", expansion); + } else { + query = "SELECT id, upper_hotkey_sid, lower_hotkey_sid, title_sid, desc_sid, cost, level_req, spell, spell_type, recast_time, " "next_id, expansion FROM aa_ranks"; + } results = QueryDatabase(query); if(results.Success()) { for(auto row = results.begin(); row != results.end(); ++row) { @@ -1736,8 +1816,8 @@ bool ZoneDatabase::LoadAlternateAdvancementAbilities(std::unordered_map 0 (this is what you buy), Set hotkeys, MUST SET A SPELL CONTAINING EFFECT SE_Buy_AA_Rank(SPA 472), set a short recast timer. + [Enabled rank] Second rank, should have a cost = 0, Set hotkeys, Set any valid spell ID you want (it has to exist but does nothing), set a short recast timer. + *Recommend if doing custom, just make the hotkey titled 'Toggle ' and use for both. + + - aa_rank_effects table : [Disabled rank] No data needed in the aa_ranks_effect table + [Enabled rank] Second rank set effect_id = 457 (weapon stance), slot 1,2,3, base1= spell triggers, base= weapon type (0=2H,1=SH,2=DW), for slot 1,2,3 + + Example SQL -Disabled + DO NOT ADD any data to the aa_rank_effects for this rank_id + + -Enabled + INSERT INTO aa_rank_effects (rank_id, slot, effect_id, base1, base2) VALUES (20003, 1, 476, 145,0); + INSERT INTO aa_rank_effects (rank_id, slot, effect_id, base1, base2) VALUES (20003, 2, 476, 174,1); + INSERT INTO aa_rank_effects (rank_id, slot, effect_id, base1, base2) VALUES (20003, 3, 476, 172,2); + + Warning: If you want to design an AA that only uses one weapon type to trigger, like will only apply buff if Shield. Do not include data for other types. Never have a base value=0 + in the Enabled rank. + + */ + + bool enable_next_rank = IsEffectInSpell(rank.spell, SE_Buy_AA_Rank); + + if (enable_next_rank) { + + //Enable + TogglePurchaseAlternativeAdvancementRank(rank.next_id); + Message(Chat::Spells, "You enable an ability."); //Message live gives you. Should come from spell. + + AA::Rank *rank_next = zone->GetAlternateAdvancementRank(rank.next_id); + + //Add checks for any special cases for toggle. + if (IsEffectinAlternateAdvancementRankEffects(*rank_next, SE_Weapon_Stance)) { + weaponstance.aabonus_enabled = true; + ApplyWeaponsStance(); + } + return; + } + else { + + //Disable + ResetAlternateAdvancementRank(ability_id); + TogglePurchaseAlternativeAdvancementRank(rank.prev_id); + Message(Chat::Spells, "You disable an ability."); //Message live gives you. Should come from spell. + + //Add checks for any special cases for toggle. + if (IsEffectinAlternateAdvancementRankEffects(rank, SE_Weapon_Stance)) { + weaponstance.aabonus_enabled = false; + BuffFadeBySpellID(weaponstance.aabonus_buff_spell_id); + } + return; + } +} + +bool Client::UseTogglePassiveHotkey(const AA::Rank &rank) { + + /* + Disabled rank needs a rank spell containing the SE_Buy_AA_Rank effect to return true. + Enabled rank checks to see if the prior rank contains a rank spell with SE_Buy_AA_Rank, if so true. + + Note: On live the enabled rank is Expendable with Charge 1. + + We have already confirmed the rank spell is valid before this function is called. + */ + + + if (IsEffectInSpell(rank.spell, SE_Buy_AA_Rank)) {//Checked when is Disabled. + return true; + } + else if (rank.prev_id != -1) {//Check when effect is Enabled. + AA::Rank *rank_prev = zone->GetAlternateAdvancementRank(rank.prev_id); + + if (IsEffectInSpell(rank_prev->spell, SE_Buy_AA_Rank)) { + return true; + } + } + return false; +} + +bool Client::IsEffectinAlternateAdvancementRankEffects(const AA::Rank &rank, int effect_id) { + + for (const auto &e : rank.effects) { + + if (e.effect_id == effect_id) { + return true; + } + } + return false; +} + +void Client::ResetAlternateAdvancementRank(uint32 aa_id) { + + /* + Resets your AA to baseline + */ + + for(auto &iter : aa_ranks) { + + AA::Ability *ability = zone->GetAlternateAdvancementAbility(iter.first); + + if(ability && aa_id == ability->id) { + RemoveExpendedAA(ability->first_rank_id); + aa_ranks.erase(iter.first); + SaveAA(); + SendAlternateAdvancementPoints(); + return; + } + } +} + +void Client::TogglePurchaseAlternativeAdvancementRank(int rank_id){ + + /* + Stripped down version of purchasing AA. Will give no messages. + Used with toggle hotkey functions. + */ + + AA::Rank *rank = zone->GetAlternateAdvancementRank(rank_id); + if (!rank) { + return; + } + + if (!rank->base_ability) { + return; + } + + if (!CanPurchaseAlternateAdvancementRank(rank, false, false)) { + return; + } + + rank_id = rank->base_ability->first_rank_id; + SetAA(rank_id, rank->current_value, 0); + + if (rank->next) { + SendAlternateAdvancementRank(rank->base_ability->id, rank->next->current_value); + } + + SaveAA(); + SendAlternateAdvancementPoints(); + SendAlternateAdvancementStats(); + CalcBonuses(); +} + diff --git a/zone/aa.h b/zone/aa.h index 0ce16a787..4d9047e39 100644 --- a/zone/aa.h +++ b/zone/aa.h @@ -2,6 +2,7 @@ #define AA_H #define MAX_SWARM_PETS 12 //this can change as long as you make more coords (swarm_pet_x/swarm_pet_y) +#define WAKE_THE_DEAD_NPCTYPEID 500 //We use first pet in pets table as a template typedef enum { aaActionNone = 0, diff --git a/zone/aa_rank_effects.h b/zone/aa_rank_effects.h index d68937078..716c44b6d 100644 --- a/zone/aa_rank_effects.h +++ b/zone/aa_rank_effects.h @@ -29,8 +29,8 @@ struct RankEffect { int slot; int effect_id; - int base1; - int base2; + int base_value; + int limit_value; }; } diff --git a/zone/aggro.cpp b/zone/aggro.cpp index 33221b63a..7d3b81554 100644 --- a/zone/aggro.cpp +++ b/zone/aggro.cpp @@ -38,190 +38,359 @@ extern Zone* zone; //#define LOSDEBUG 6 void EntityList::DescribeAggro(Client *towho, NPC *from_who, float d, bool verbose) { - float d2 = d*d; + float distance_squared = (d * d); - towho->Message(Chat::White, "Describing aggro for %s", from_who->GetName()); + towho->Message( + Chat::White, + fmt::format( + "Describing aggro for {} ({}).", + from_who->GetCleanName(), + from_who->GetID() + ).c_str() + ); - bool engaged = from_who->IsEngaged(); - if(engaged) { + bool is_engaged = from_who->IsEngaged(); + bool will_aggro_npcs = from_who->WillAggroNPCs(); + if (is_engaged) { Mob *top = from_who->GetHateTop(); - towho->Message(Chat::White, ".. I am currently fighting with %s", top == nullptr?"(nullptr)":top->GetName()); + towho->Message( + Chat::White, + fmt::format( + "I am currently engaged with {}.", + ( + !top ? + "nothing" : + fmt::format( + "{} ({})", + top->GetCleanName(), + top->GetID() + ) + ) + ).c_str() + ); } - bool check_npcs = from_who->WillAggroNPCs(); - if(verbose) { - char namebuf[256]; - - int my_primary = from_who->GetPrimaryFaction(); - Mob *own = from_who->GetOwner(); - if(own != nullptr) - my_primary = own->GetPrimaryFaction(); - - if(my_primary == 0) { - strcpy(namebuf, "(No faction)"); - } else if(my_primary < 0) { - strcpy(namebuf, "(Special faction)"); - } else { - if(!content_db.GetFactionName(my_primary, namebuf, sizeof(namebuf))) - strcpy(namebuf, "(Unknown)"); + if (verbose) { + int faction_id = from_who->GetPrimaryFaction(); + Mob *owner = from_who->GetOwner(); + if(owner) { + faction_id = owner->GetPrimaryFaction(); } - towho->Message(Chat::White, ".. I am on faction %s (%d)\n", namebuf, my_primary); + + std::string faction_name = ( + faction_id > 0 ? + content_db.GetFactionName(faction_id) : + ( + faction_id == 0 ? + "None" : + fmt::format( + "Special Faction {}", + faction_id + ) + ) + ); + + towho->Message( + Chat::White, + fmt::format( + "{} ({}) is on Faction {} ({}).", + from_who->GetCleanName(), + from_who->GetID(), + faction_name, + faction_id + ).c_str() + ); } - for (auto it = mob_list.begin(); it != mob_list.end(); ++it) { - Mob *mob = it->second; - if (mob->IsClient()) //also ensures that mob != around + for (const auto& npc_entity : entity_list.GetNPCList()) { + auto entity_id = npc_entity.first; + auto npc = npc_entity.second; + if (npc == from_who) { continue; + } - if (DistanceSquared(mob->GetPosition(), from_who->GetPosition()) > d2) + if (DistanceSquared(npc->GetPosition(), from_who->GetPosition()) > distance_squared) { continue; + } - if (engaged) { - uint32 amm = from_who->GetHateAmount(mob); - if (amm == 0) - towho->Message(Chat::White, "... %s is not on my hate list.", mob->GetName()); - else - towho->Message(Chat::White, "... %s is on my hate list with value %lu", mob->GetName(), (unsigned long)amm); - } else if (!check_npcs && mob->IsNPC()) { - towho->Message(Chat::White, "... %s is an NPC and my npc_aggro is disabled.", mob->GetName()); + if (is_engaged) { + uint32 hate_amount = from_who->GetHateAmount(npc); + towho->Message( + Chat::White, + fmt::format( + "{} ({}) is {}on my hate list{}.", + npc->GetCleanName(), + npc->GetID(), + !hate_amount ? "not " : "", + ( + !hate_amount ? + "" : + fmt::format( + " with a hate amount of {}.", + hate_amount + ) + ) + ).c_str() + ); + } else if (!will_aggro_npcs) { + towho->Message( + Chat::White, + fmt::format( + "{} ({}) is an NPC and I cannot aggro NPCs.", + npc->GetCleanName(), + npc->GetID() + ).c_str() + ); } else { - from_who->DescribeAggro(towho, mob, verbose); + from_who->DescribeAggro(towho, npc, verbose); } } } -void NPC::DescribeAggro(Client *towho, Mob *mob, bool verbose) { +void NPC::DescribeAggro(Client *towho, Mob *mob, bool verbose) { //this logic is duplicated from below, try to keep it up to date. - float iAggroRange = GetAggroRange(); - - float t1, t2, t3; - t1 = std::abs(mob->GetX() - GetX()); - t2 = std::abs(mob->GetY() - GetY()); - t3 = std::abs(mob->GetZ() - GetZ()); - - if(( t1 > iAggroRange) - || ( t2 > iAggroRange) - || ( t3 > iAggroRange) ) { - towho->Message(Chat::White, "...%s is out of range (fast). distances (%.3f,%.3f,%.3f), range %.3f", mob->GetName(), - t1, t2, t3, iAggroRange); + float aggro_range = GetAggroRange(); + float x_range = std::abs(mob->GetX() - GetX()); + float y_range = std::abs(mob->GetY() - GetY()); + float z_range = std::abs(mob->GetZ() - GetZ()); + if ( + x_range > aggro_range || + y_range > aggro_range || + z_range > aggro_range + ) { + towho->Message( + Chat::White, + fmt::format( + "{} ({}) is out of range. X Range: {} Y Range: {} Z Range: {} Aggro Range: {}", + mob->GetCleanName(), + mob->GetID(), + x_range, + y_range, + z_range, + aggro_range + ).c_str() + ); return; } - if(mob->IsInvisible(this)) { - towho->Message(Chat::White, "...%s is invisible to me. ", mob->GetName()); + if (mob->IsInvisible(this)) { + towho->Message( + Chat::White, + fmt::format( + "{} ({}) is invisible to me. ", + mob->GetCleanName(), + mob->GetID() + ).c_str() + ); return; } - if((mob->IsClient() && - (!mob->CastToClient()->Connected() - || mob->CastToClient()->IsLD() - || mob->CastToClient()->IsBecomeNPC() - || mob->CastToClient()->GetGM() + + if ( + mob->IsClient() && + ( + !mob->CastToClient()->Connected() || + mob->CastToClient()->IsLD() || + mob->CastToClient()->IsBecomeNPC() || + mob->CastToClient()->GetGM() ) - )) - { - towho->Message(Chat::White, "...%s is my owner. ", mob->GetName()); + ) { + towho->Message( + Chat::White, + fmt::format( + "{} ({}) is a GM or is not connected. ", + mob->GetCleanName(), + mob->GetID() + ).c_str() + ); return; } - if(mob == GetOwner()) { - towho->Message(Chat::White, "...%s a GM or is not connected. ", mob->GetName()); + if (mob == GetOwner()) { + towho->Message( + Chat::White, + fmt::format( + "{} ({}) is my owner. ", + mob->GetCleanName(), + mob->GetID() + ).c_str() + ); return; } - float dist2 = DistanceSquared(mob->GetPosition(), m_Position); - - float iAggroRange2 = iAggroRange*iAggroRange; - if( dist2 > iAggroRange2 ) { - towho->Message(Chat::White, "...%s is out of range. %.3f > %.3f ", mob->GetName(), - dist2, iAggroRange2); + float distance_squared = DistanceSquared(mob->GetPosition(), m_Position); + float aggro_range_squared = (aggro_range * aggro_range); + if (distance_squared > aggro_range_squared) { + towho->Message( + Chat::White, + fmt::format( + "{} ({}) is out of range. Distance: {:.2f} Aggro Range: {:.2f}", + mob->GetCleanName(), + mob->GetID(), + distance_squared, + aggro_range_squared + ).c_str() + ); return; } - if (RuleB(Aggro, UseLevelAggro)) - { - if (GetLevel() < RuleI(Aggro, MinAggroLevel) && mob->GetLevelCon(GetLevel()) == CON_GRAY && GetBodyType() != 3 && !AlwaysAggro()) - { - towho->Message(Chat::White, "...%s is red to me (basically)", mob->GetName(), dist2, iAggroRange2); + if (RuleB(Aggro, UseLevelAggro)) { + if ( + GetLevel() < RuleI(Aggro, MinAggroLevel) && + mob->GetLevelCon(GetLevel()) == CON_GRAY && + GetBodyType() != BT_Undead && + !AlwaysAggro() + ) { + towho->Message( + Chat::White, + fmt::format( + "{} ({}) considers Red to me.", + mob->GetCleanName(), + mob->GetID() + ).c_str() + ); return; } - } - else - { - if(GetINT() > RuleI(Aggro, IntAggroThreshold) && mob->GetLevelCon(GetLevel()) == CON_GRAY && !AlwaysAggro()) { - towho->Message(Chat::White, "...%s is red to me (basically)", mob->GetName(), - dist2, iAggroRange2); + } else { + if ( + GetINT() > RuleI(Aggro, IntAggroThreshold) && + mob->GetLevelCon(GetLevel()) == CON_GRAY && + !AlwaysAggro() + ) { + towho->Message( + Chat::White, + fmt::format( + "{} ({}) considers Red to me.", + mob->GetCleanName(), + mob->GetID() + ).c_str() + ); return; } } - if(verbose) { - int my_primary = GetPrimaryFaction(); - int mob_primary = mob->GetPrimaryFaction(); - Mob *own = GetOwner(); - if(own != nullptr) - my_primary = own->GetPrimaryFaction(); - own = mob->GetOwner(); - if(mob_primary > 0 && own != nullptr) - mob_primary = own->GetPrimaryFaction(); + if (verbose) { + int faction_id = GetPrimaryFaction(); + int mob_faction_id = mob->GetPrimaryFaction(); + Mob *owner = GetOwner(); + if (owner) { + faction_id = owner->GetPrimaryFaction(); + } - if(mob_primary == 0) { - towho->Message(Chat::White, "...%s has no primary faction", mob->GetName()); - } else if(mob_primary < 0) { - towho->Message(Chat::White, "...%s is on special faction %d", mob->GetName(), mob_primary); + owner = mob->GetOwner(); + if (mob_faction_id && owner) { + mob_faction_id = owner->GetPrimaryFaction(); + } + + if (!mob_faction_id) { + towho->Message( + Chat::White, + fmt::format( + "{} ({}) has no primary Faction.", + mob->GetCleanName(), + mob->GetID() + ).c_str() + ); + } else if (mob_faction_id < 0) { + towho->Message( + Chat::White, + fmt::format( + "{} ({}) is on special Faction {}.", + mob->GetCleanName(), + mob->GetID(), + mob_faction_id + ).c_str() + ); } else { - char namebuf[256]; - if(!content_db.GetFactionName(mob_primary, namebuf, sizeof(namebuf))) - strcpy(namebuf, "(Unknown)"); - std::list::iterator cur,end; - cur = faction_list.begin(); - end = faction_list.end(); - bool res = false; - for(; cur != end; ++cur) { - struct NPCFaction* fac = *cur; - if ((int32)fac->factionID == mob_primary) { - if (fac->npc_value > 0) { - towho->Message(Chat::White, "...%s is on ALLY faction %s (%d) with %d", mob->GetName(), namebuf, mob_primary, fac->npc_value); - res = true; - break; - } else if (fac->npc_value < 0) { - towho->Message(Chat::White, "...%s is on ENEMY faction %s (%d) with %d", mob->GetName(), namebuf, mob_primary, fac->npc_value); - res = true; - break; - } else { - towho->Message(Chat::White, "...%s is on NEUTRAL faction %s (%d) with 0", mob->GetName(), namebuf, mob_primary); - res = true; - break; - } + auto faction_name = content_db.GetFactionName(mob_faction_id); + bool has_entry = false; + for (auto faction : faction_list) { + if (static_cast(faction->factionID) == mob_faction_id) { + towho->Message( + Chat::White, + fmt::format( + "{} ({}) has {} standing with Faction {} ({}) with their Faction Level of {}", + mob->GetCleanName(), + mob->GetID(), + ( + faction->npc_value != 0 ? + ( + faction->npc_value > 0 ? + "positive" : + "negative" + ) : + "neutral" + ), + faction_name, + faction->factionID, + faction->npc_value + ).c_str() + ); + has_entry = true; + break; } } - if(!res) { - towho->Message(Chat::White, "...%s is on faction %s (%d), which I have no entry for.", mob->GetName(), namebuf, mob_primary); + + if (!has_entry) { + towho->Message( + Chat::White, + fmt::format( + "{} ({}) is on Faction {} ({}), for which I do not have an entry.", + mob->GetCleanName(), + mob->GetID(), + faction_name, + mob_faction_id + ).c_str() + ); } } } - FACTION_VALUE fv = mob->GetReverseFactionCon(this); + auto faction_value = mob->GetReverseFactionCon(this); - if(!( - fv == FACTION_SCOWLS - || - (mob->GetPrimaryFaction() != GetPrimaryFaction() && mob->GetPrimaryFaction() == -4 && GetOwner() == nullptr) - || - fv == FACTION_THREATENLY - )) { - towho->Message(Chat::White, "...%s faction not low enough. value='%s'", mob->GetName(), FactionValueToString(fv)); + if( + !( + faction_value == FACTION_THREATENINGLY || + faction_value == FACTION_SCOWLS || + ( + mob->GetPrimaryFaction() != GetPrimaryFaction() && + mob->GetPrimaryFaction() == -4 && + !GetOwner() + ) + ) + ) { + towho->Message( + Chat::White, + fmt::format( + "{} ({}) does not have low enough faction, their Faction Level is {} ({}).", + mob->GetCleanName(), + mob->GetID(), + FactionValueToString(faction_value), + faction_value + ).c_str() + ); return; } - if(fv == FACTION_THREATENLY) { - towho->Message(Chat::White, "...%s threatening to me, so they only have a %d chance per check of attacking.", mob->GetName()); - } if(!CheckLosFN(mob)) { - towho->Message(Chat::White, "...%s is out of sight.", mob->GetName()); + towho->Message( + Chat::White, + fmt::format( + "{} ({}) is out of sight.", + mob->GetCleanName(), + mob->GetID() + ).c_str() + ); } - towho->Message(Chat::White, "...%s meets all conditions, I should be attacking them.", mob->GetName()); + towho->Message( + Chat::White, + fmt::format( + "{} ({}) meets all conditions, I should be attacking them.", + mob->GetCleanName(), + mob->GetID() + ).c_str() + ); } /* @@ -229,65 +398,87 @@ void NPC::DescribeAggro(Client *towho, Mob *mob, bool verbose) { to keep the #aggro command accurate. */ bool Mob::CheckWillAggro(Mob *mob) { - if(!mob) + if(!mob) { return false; + } //sometimes if a client has some lag while zoning into a dangerous place while either invis or a GM //they will aggro mobs even though it's supposed to be impossible, to lets make sure we've finished connecting if (mob->IsClient()) { - if (!mob->CastToClient()->ClientFinishedLoading() || mob->CastToClient()->IsHoveringForRespawn() || mob->CastToClient()->bZoning) + 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()) { + if ( + mob->IsClient() && + mob->CastToClient()->GetLastRegion() != RegionTypeWater && + IsUnderwaterOnly() + ) { return false; } /** * Pets shouldn't scan for aggro */ - if (this->GetOwner()) { + if (GetOwner()) { return false; } Mob *pet_owner = mob->GetOwner(); - if (pet_owner && pet_owner->IsClient()) { + if ( + pet_owner && + pet_owner->IsClient() && + ( + !RuleB(Aggro, AggroPlayerPets) || + pet_owner->CastToClient()->GetGM() + ) + ) { return false; } - float iAggroRange = GetAggroRange(); - // Check If it's invisible and if we can see invis // Check if it's a client, and that the client is connected and not linkdead, // and that the client isn't Playing an NPC, with thier gm flag on // Check if it's not a Interactive NPC // Trumpcard: The 1st 3 checks are low cost calcs to filter out unnessecary distance checks. Leave them at the beginning, they are the most likely occurence. // Image: I moved this up by itself above faction and distance checks because if one of these return true, theres no reason to go through the other information + + float aggro_range = GetAggroRange(); + float x_range = std::abs(mob->GetX() - GetX()); + float y_range = std::abs(mob->GetY() - GetY()); + float z_range = std::abs(mob->GetZ() - GetZ()); - float t1, t2, t3; - t1 = std::abs(mob->GetX() - GetX()); - t2 = std::abs(mob->GetY() - GetY()); - t3 = std::abs(mob->GetZ() - GetZ()); - - if(( t1 > iAggroRange) - || ( t2 > iAggroRange) - || ( t3 > iAggroRange) - || (mob->IsInvisible(this)) - || (mob->IsClient() && - (!mob->CastToClient()->Connected() + if ( + x_range > aggro_range || + y_range > aggro_range || + z_range > aggro_range || + mob->IsInvisible(this) || + ( + mob->IsClient() && + ( + !mob->CastToClient()->Connected() || mob->CastToClient()->IsLD() || mob->CastToClient()->IsBecomeNPC() || mob->CastToClient()->GetGM() ) - )) - { - return(false); + ) + ) { + return false; } // Don't aggro new clients if we are already engaged unless PROX_AGGRO is set if (IsEngaged() && (!GetSpecialAbility(PROX_AGGRO) || (GetSpecialAbility(PROX_AGGRO) && !CombatRange(mob)))) { - LogAggro("[{}] is in combat, and does not have prox_aggro, or does and is out of combat range with [{}]", GetName(), mob->GetName()); + LogAggro( + "[{}] is in combat, and does not have prox_aggro, or does and is out of combat range with [{}]", + GetName(), + mob->GetName() + ); return false; } @@ -296,105 +487,100 @@ bool Mob::CheckWillAggro(Mob *mob) { //aggro this mob...??? //changed to be 'if I have an owner and this is it' if(mob == GetOwner()) { - return(false); + return false; } - float dist2 = DistanceSquared(mob->GetPosition(), m_Position); - float iAggroRange2 = iAggroRange*iAggroRange; + float distance_squared = DistanceSquared(mob->GetPosition(), m_Position); + float aggro_range_squared = (aggro_range * aggro_range); - if( dist2 > iAggroRange2 ) { + if (distance_squared > aggro_range_squared ) { // Skip it, out of range - return(false); + return false; } //Image: Get their current target and faction value now that its required //this function call should seem backwards - FACTION_VALUE fv = mob->GetReverseFactionCon(this); + FACTION_VALUE faction_value = mob->GetReverseFactionCon(this); // Make sure they're still in the zone // Are they in range? // Are they kos? // Are we stupid or are they green // and they don't have their gm flag on - int heroicCHA_mod = mob->itembonuses.HeroicCHA/25; // 800 Heroic CHA cap - if(heroicCHA_mod > THREATENLY_ARRGO_CHANCE) - heroicCHA_mod = THREATENLY_ARRGO_CHANCE; - if (RuleB(Aggro, UseLevelAggro) && - ( - //old InZone check taken care of above by !mob->CastToClient()->Connected() - ( - ( GetLevel() >= RuleI(Aggro, MinAggroLevel)) - ||(GetBodyType() == 3) || AlwaysAggro() - ||( mob->IsClient() && mob->CastToClient()->IsSitting() ) - ||( mob->GetLevelCon(GetLevel()) != CON_GRAY) + int heroic_cha_mod = (mob->itembonuses.HeroicCHA / 25); // 800 Heroic CHA cap + if(heroic_cha_mod > THREATENINGLY_AGGRO_CHANCE) { + heroic_cha_mod = THREATENINGLY_AGGRO_CHANCE; + } - ) - && - ( + if ( + RuleB(Aggro, UseLevelAggro) && ( - fv == FACTION_SCOWLS - || - (mob->GetPrimaryFaction() != GetPrimaryFaction() && mob->GetPrimaryFaction() == -4 && GetOwner() == nullptr) - || + GetLevel() >= RuleI(Aggro, MinAggroLevel) || + GetBodyType() == BT_Undead || + AlwaysAggro() || ( - fv == FACTION_THREATENLY - && zone->random.Roll(THREATENLY_ARRGO_CHANCE - heroicCHA_mod) + mob->IsClient() && + mob->CastToClient()->IsSitting() + ) || + mob->GetLevelCon(GetLevel()) != CON_GRAY + ) && + ( + faction_value == FACTION_SCOWLS || + ( + mob->GetPrimaryFaction() != GetPrimaryFaction() && + mob->GetPrimaryFaction() == -4 && + !GetOwner() + ) || + ( + faction_value == FACTION_THREATENINGLY && + zone->random.Roll(THREATENINGLY_AGGRO_CHANCE - heroic_cha_mod) ) ) - ) - ) - ) - { - //FatherNiwtit: make sure we can see them. last since it is very expensive + ) { if(CheckLosFN(mob)) { LogAggro("Check aggro for [{}] target [{}]", GetName(), mob->GetName()); - return( mod_will_aggro(mob, this) ); + return mod_will_aggro(mob, this); } - } - else - { - if - ( - //old InZone check taken care of above by !mob->CastToClient()->Connected() - ( - ( GetINT() <= RuleI(Aggro, IntAggroThreshold) ) - || AlwaysAggro() - ||( mob->IsClient() && mob->CastToClient()->IsSitting() ) - ||( mob->GetLevelCon(GetLevel()) != CON_GRAY) - - ) - && - ( + } else { + if ( ( - fv == FACTION_SCOWLS - || - (mob->GetPrimaryFaction() != GetPrimaryFaction() && mob->GetPrimaryFaction() == -4 && GetOwner() == nullptr) - || + GetINT() <= RuleI(Aggro, IntAggroThreshold) || + AlwaysAggro() || ( - fv == FACTION_THREATENLY - && zone->random.Roll(THREATENLY_ARRGO_CHANCE - heroicCHA_mod) + mob->IsClient() && + mob->CastToClient()->IsSitting() + ) || + mob->GetLevelCon(GetLevel()) != CON_GRAY + ) && + ( + faction_value == FACTION_SCOWLS || + ( + mob->GetPrimaryFaction() != GetPrimaryFaction() && + mob->GetPrimaryFaction() == -4 && + !GetOwner() + ) || + ( + faction_value == FACTION_THREATENINGLY + && zone->random.Roll(THREATENINGLY_AGGRO_CHANCE - heroic_cha_mod) ) ) - ) - ) - { - //FatherNiwtit: make sure we can see them. last since it is very expensive + ) { if(CheckLosFN(mob)) { LogAggro("Check aggro for [{}] target [{}]", GetName(), mob->GetName()); - return( mod_will_aggro(mob, this) ); + return mod_will_aggro(mob, this); } } } LogAggro("Is In zone?:[{}]\n", mob->InZone()); - LogAggro("Dist^2: [{}]\n", dist2); - LogAggro("Range^2: [{}]\n", iAggroRange2); - LogAggro("Faction: [{}]\n", fv); + LogAggro("Dist^2: [{}]\n", distance_squared); + LogAggro("Range^2: [{}]\n", aggro_range_squared); + LogAggro("Faction: [{}]\n", faction_value); LogAggro("AlwaysAggroFlag: [{}]\n", AlwaysAggro()); LogAggro("Int: [{}]\n", GetINT()); LogAggro("Con: [{}]\n", GetLevelCon(mob->GetLevel())); - return(false); + return false; } int EntityList::GetHatedCount(Mob *attacker, Mob *exclude, bool inc_gray_con) @@ -474,6 +660,9 @@ bool Mob::IsAttackAllowed(Mob *target, bool isSpellAttack) if (target->GetSpecialAbility(IMMUNE_DAMAGE_NPC) && IsNPC()) return false; + if (target->IsHorse()) + return false; + // can't damage own pet (applies to everthing) Mob *target_owner = target->GetOwner(); Mob *our_owner = GetOwner(); @@ -851,6 +1040,17 @@ bool Mob::CombatRange(Mob* other, float fixed_size_mod, bool aeRampage) size_mod *= RuleR(Combat,HitBoxMod); // used for testing sizemods on different races. size_mod *= fixed_size_mod; // used to extend the size_mod + // Melee chasing fleeing mobs is borked. The client updates don't + // come to the server quickly enough, especially when mob is running + // and/or PC has good run speed. This change is a hack, but it greatly + // improved playability and "you are too far away" while chasing + // a fleeing mob. The Blind check is to make sure that this does not + // apply to disoriented fleeing mobs who need proximity to turn and fight. + if (other->currently_fleeing && !other->IsBlind()) + { + size_mod *= 3; + } + // prevention of ridiculously sized hit boxes if (size_mod > 10000) size_mod = size_mod / 7; @@ -863,7 +1063,7 @@ bool Mob::CombatRange(Mob* other, float fixed_size_mod, bool aeRampage) bool DoLoSCheck = true; float max_dist = static_cast(GetSpecialAbilityParam(NPC_CHASE_DISTANCE, 0)); - float min_dist = static_cast(GetSpecialAbilityParam(NPC_CHASE_DISTANCE, 1)); + float min_distance = static_cast(GetSpecialAbilityParam(NPC_CHASE_DISTANCE, 1)); if (GetSpecialAbilityParam(NPC_CHASE_DISTANCE, 2)) DoLoSCheck = false; //Ignore line of sight check @@ -873,12 +1073,12 @@ bool Mob::CombatRange(Mob* other, float fixed_size_mod, bool aeRampage) max_dist = max_dist * max_dist; - if (!min_dist) - min_dist = size_mod; //Default to melee range + if (!min_distance) + min_distance = size_mod; //Default to melee range else - min_dist = min_dist * min_dist; + min_distance = min_distance * min_distance; - if ((DoLoSCheck && CheckLastLosState()) && (_DistNoRoot >= min_dist && _DistNoRoot <= max_dist)) + if ((DoLoSCheck && CheckLastLosState()) && (_DistNoRoot >= min_distance && _DistNoRoot <= max_dist)) SetPseudoRoot(true); else SetPseudoRoot(false); @@ -901,6 +1101,7 @@ bool Mob::CombatRange(Mob* other, float fixed_size_mod, bool aeRampage) } return true; } + return false; } @@ -991,16 +1192,16 @@ int32 Mob::CheckAggroAmount(uint16 spell_id, Mob *target, bool isproc) default_aggro = target_hp / 15; for (int o = 0; o < EFFECT_COUNT; o++) { - switch (spells[spell_id].effectid[o]) { + switch (spells[spell_id].effect_id[o]) { case SE_CurrentHPOnce: case SE_CurrentHP: { - int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); + int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base_value[o], spells[spell_id].max_value[o], slevel, spell_id); if(val < 0) AggroAmount -= val; break; } case SE_MovementSpeed: { - int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); + int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base_value[o], spells[spell_id].max_value[o], slevel, spell_id); if (val < 0) AggroAmount += default_aggro; break; @@ -1008,7 +1209,7 @@ int32 Mob::CheckAggroAmount(uint16 spell_id, Mob *target, bool isproc) case SE_AttackSpeed: case SE_AttackSpeed2: case SE_AttackSpeed3: { - int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); + int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base_value[o], spells[spell_id].max_value[o], slevel, spell_id); if (val < 100) AggroAmount += default_aggro; break; @@ -1018,6 +1219,7 @@ int32 Mob::CheckAggroAmount(uint16 spell_id, Mob *target, bool isproc) case SE_Mez: case SE_Charm: case SE_Fear: + case SE_Fearstun: AggroAmount += default_aggro; break; case SE_Root: @@ -1025,7 +1227,7 @@ int32 Mob::CheckAggroAmount(uint16 spell_id, Mob *target, bool isproc) break; case SE_ACv2: case SE_ArmorClass: { - int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); + int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base_value[o], spells[spell_id].max_value[o], slevel, spell_id); if (val < 0) AggroAmount += default_aggro; break; @@ -1043,19 +1245,19 @@ int32 Mob::CheckAggroAmount(uint16 spell_id, Mob *target, bool isproc) case SE_INT: case SE_WIS: case SE_CHA: { - int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); + int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base_value[o], spells[spell_id].max_value[o], slevel, spell_id); if (val < 0) AggroAmount += 10; break; } case SE_ResistAll: { - int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); + int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base_value[o], spells[spell_id].max_value[o], slevel, spell_id); if (val < 0) AggroAmount += 50; break; } case SE_AllStats: { - int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); + int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base_value[o], spells[spell_id].max_value[o], slevel, spell_id); if (val < 0) AggroAmount += 70; break; @@ -1097,18 +1299,19 @@ int32 Mob::CheckAggroAmount(uint16 spell_id, Mob *target, bool isproc) case SE_ManaRegen_v2: case SE_ManaPool: case SE_CurrentEndurance: { - int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); + int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base_value[o], spells[spell_id].max_value[o], slevel, spell_id); if (val < 0) AggroAmount -= val * 2; break; } case SE_CancelMagic: case SE_DispelDetrimental: + case SE_DispelBeneficial: dispel = true; break; case SE_ReduceHate: case SE_InstantHate: - nonModifiedAggro = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); + nonModifiedAggro = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base_value[o], spells[spell_id].max_value[o], slevel, spell_id); break; } } @@ -1119,10 +1322,10 @@ int32 Mob::CheckAggroAmount(uint16 spell_id, Mob *target, bool isproc) if (dispel && target && target->GetHateAmount(this) < 100) AggroAmount += 50; - if (spells[spell_id].HateAdded > 0) // overrides the hate (ex. tash) - AggroAmount = spells[spell_id].HateAdded; + if (spells[spell_id].hate_added != 0) // overrides the hate (ex. tash), can be negative. + AggroAmount = spells[spell_id].hate_added; - if (GetOwner() && IsPet()) + if (GetOwner() && IsPet() && AggroAmount > 0) AggroAmount = AggroAmount * RuleI(Aggro, PetSpellAggroMod) / 100; // hate focus ignored on first action for some reason @@ -1135,10 +1338,10 @@ int32 Mob::CheckAggroAmount(uint16 spell_id, Mob *target, bool isproc) // initial aggro gets a bonus 100 besides for dispel or hate override // We add this 100 in AddToHateList so we need to account for the oddities here - if (dispel && spells[spell_id].HateAdded > 0 && !on_hatelist) + if (dispel && spells[spell_id].hate_added > 0 && !on_hatelist) AggroAmount -= 100; - return AggroAmount + spells[spell_id].bonushate + nonModifiedAggro; + return AggroAmount + spells[spell_id].bonus_hate + nonModifiedAggro; } //healing and buffing aggro @@ -1149,7 +1352,7 @@ int32 Mob::CheckHealAggroAmount(uint16 spell_id, Mob *target, uint32 heal_possib bool ignore_default_buff = false; // rune/hot don't use the default 9, HP buffs that heal (virtue) do use the default for (int o = 0; o < EFFECT_COUNT; o++) { - switch (spells[spell_id].effectid[o]) { + switch (spells[spell_id].effect_id[o]) { case SE_CurrentHP: case SE_PercentalHeal: { @@ -1159,7 +1362,7 @@ int32 Mob::CheckHealAggroAmount(uint16 spell_id, Mob *target, uint32 heal_possib } // hate based on base healing power of the spell int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], - spells[spell_id].base[o], spells[spell_id].max[o], GetLevel(), spell_id); + spells[spell_id].base_value[o], spells[spell_id].max_value[o], GetLevel(), spell_id); if (val > 0) { if (heal_possible < val) val = heal_possible; // capped to amount healed @@ -1175,7 +1378,7 @@ int32 Mob::CheckHealAggroAmount(uint16 spell_id, Mob *target, uint32 heal_possib } case SE_Rune: AggroAmount += CalcSpellEffectValue_formula(spells[spell_id].formula[o], - spells[spell_id].base[o], spells[spell_id].max[o], GetLevel(), spell_id) * 2; + spells[spell_id].base_value[o], spells[spell_id].max_value[o], GetLevel(), spell_id) * 2; ignore_default_buff = true; break; case SE_HealOverTime: @@ -1202,45 +1405,62 @@ int32 Mob::CheckHealAggroAmount(uint16 spell_id, Mob *target, uint32 heal_possib return std::max(0, AggroAmount); } -void Mob::AddFeignMemory(Client* attacker) { - if(feign_memory_list.empty() && AI_feign_remember_timer != nullptr) +void Mob::AddFeignMemory(Mob* attacker) { + if (feign_memory_list.empty() && AI_feign_remember_timer != nullptr) { AI_feign_remember_timer->Start(AIfeignremember_delay); - feign_memory_list.insert(attacker->CharacterID()); + } + + if (attacker) { + feign_memory_list.insert(attacker->GetID()); + } } -void Mob::RemoveFromFeignMemory(Client* attacker) { - feign_memory_list.erase(attacker->CharacterID()); - if(feign_memory_list.empty() && AI_feign_remember_timer != nullptr) +void Mob::RemoveFromFeignMemory(Mob* attacker) { + + if (!attacker) { + return; + } + + feign_memory_list.erase(attacker->GetID()); + if (feign_memory_list.empty() && AI_feign_remember_timer != nullptr) { AI_feign_remember_timer->Disable(); + } if(feign_memory_list.empty()) { minLastFightingDelayMoving = RuleI(NPC, LastFightingDelayMovingMin); maxLastFightingDelayMoving = RuleI(NPC, LastFightingDelayMovingMax); - if(AI_feign_remember_timer != nullptr) + if (AI_feign_remember_timer != nullptr) { AI_feign_remember_timer->Disable(); + } } } void Mob::ClearFeignMemory() { - auto RememberedCharID = feign_memory_list.begin(); - while (RememberedCharID != feign_memory_list.end()) + auto remembered_feigned_mobid = feign_memory_list.begin(); + while (remembered_feigned_mobid != feign_memory_list.end()) { - Client* remember_client = entity_list.GetClientByCharID(*RememberedCharID); - if(remember_client != nullptr) //Still in zone - remember_client->RemoveXTarget(this, false); - ++RememberedCharID; + Mob* remembered_mob = entity_list.GetMob(*remembered_feigned_mobid); + if (remembered_mob->IsClient() && remembered_mob != nullptr) { //Still in zone + remembered_mob->CastToClient()->RemoveXTarget(this, false); + } + ++remembered_feigned_mobid; } feign_memory_list.clear(); minLastFightingDelayMoving = RuleI(NPC, LastFightingDelayMovingMin); maxLastFightingDelayMoving = RuleI(NPC, LastFightingDelayMovingMax); - if(AI_feign_remember_timer != nullptr) + if (AI_feign_remember_timer != nullptr) { AI_feign_remember_timer->Disable(); + } } -bool Mob::IsOnFeignMemory(Client *attacker) const +bool Mob::IsOnFeignMemory(Mob *attacker) const { - return feign_memory_list.find(attacker->CharacterID()) != feign_memory_list.end(); + if (!attacker) { + return 0; + } + + return feign_memory_list.find(attacker->GetID()) != feign_memory_list.end(); } bool Mob::PassCharismaCheck(Mob* caster, uint16 spell_id) { @@ -1255,7 +1475,7 @@ bool Mob::PassCharismaCheck(Mob* caster, uint16 spell_id) { if(!caster) return false; - if(spells[spell_id].ResistDiff <= -600) + if(spells[spell_id].resist_difficulty <= -600) return true; float resist_check = 0; @@ -1270,9 +1490,9 @@ bool Mob::PassCharismaCheck(Mob* caster, uint16 spell_id) { return true; if (RuleB(Spells, CharismaCharmDuration)) - resist_check = ResistSpell(spells[spell_id].resisttype, spell_id, caster,false,0,true,true); + resist_check = ResistSpell(spells[spell_id].resist_type, spell_id, caster,false,0,true,true); else - resist_check = ResistSpell(spells[spell_id].resisttype, spell_id, caster, false,0, false, true); + resist_check = ResistSpell(spells[spell_id].resist_type, spell_id, caster, false,0, false, true); //2: The mob makes a resistance check against the charm if (resist_check == 100) @@ -1296,7 +1516,7 @@ bool Mob::PassCharismaCheck(Mob* caster, uint16 spell_id) { { // Assume this is a harmony/pacify spell // If 'Lull' spell resists, do a second resist check with a charisma modifier AND regular resist checks. If resists agian you gain aggro. - resist_check = ResistSpell(spells[spell_id].resisttype, spell_id, caster, false,0,true); + resist_check = ResistSpell(spells[spell_id].resist_type, spell_id, caster, false,0,true); if (resist_check == 100) return true; } diff --git a/zone/api_service.cpp b/zone/api_service.cpp index f97c451b1..109ef3de8 100644 --- a/zone/api_service.cpp +++ b/zone/api_service.cpp @@ -436,7 +436,6 @@ Json::Value ApiGetMobListDetail(EQ::Net::WebsocketServerConnection *connection, row["cwp"] = mob->GetCWP(); row["cwpp"] = mob->GetCWPP(); row["divine_aura"] = mob->DivineAura(); - row["do_casting_checks"] = mob->DoCastingChecks(); row["dont_buff_me_before"] = mob->DontBuffMeBefore(); row["dont_cure_me_before"] = mob->DontCureMeBefore(); row["dont_dot_me_before"] = mob->DontDotMeBefore(); @@ -478,7 +477,6 @@ Json::Value ApiGetMobListDetail(EQ::Net::WebsocketServerConnection *connection, row["has_temp_pets_active"] = mob->HasTempPetsActive(); row["has_two_hand_blunt_equiped"] = mob->HasTwoHandBluntEquiped(); row["has_two_hander_equipped"] = mob->HasTwoHanderEquipped(); - row["has_virus"] = mob->HasVirus(); row["hate_summon"] = mob->HateSummon(); row["helm_texture"] = mob->GetHelmTexture(); row["hp"] = mob->GetHP(); diff --git a/zone/attack.cpp b/zone/attack.cpp index b2bd97395..b031e4036 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -60,6 +60,7 @@ extern bool Critical; extern EntityList entity_list; extern Zone* zone; +//SYNC WITH: tune.cpp, mob.h TuneAttackAnimation EQ::skills::SkillType Mob::AttackAnimation(int Hand, const EQ::ItemInstance* weapon, EQ::skills::SkillType skillinuse) { // Determine animation @@ -88,7 +89,7 @@ EQ::skills::SkillType Mob::AttackAnimation(int Hand, const EQ::ItemInstance* wea break; case EQ::item::ItemType2HBlunt: // 2H Blunt skillinuse = EQ::skills::Skill2HBlunt; - type = anim2HSlashing; //anim2HWeapon + type = RuleB(Combat, Classic2HBAnimation) ? anim2HWeapon : anim2HSlashing; break; case EQ::item::ItemType2HPiercing: // 2H Piercing if (IsClient() && CastToClient()->ClientVersion() < EQ::versions::ClientVersion::RoF2) @@ -141,14 +142,30 @@ EQ::skills::SkillType Mob::AttackAnimation(int Hand, const EQ::ItemInstance* wea } // If we're attacking with the secondary hand, play the dual wield anim - if (Hand == EQ::invslot::slotSecondary) // DW anim + if (Hand == EQ::invslot::slotSecondary) {// DW anim type = animDualWield; + //allow animation chance to fire to be similar to your dw chance + if (GetDualWieldingSameDelayWeapons() == 2) { + SetDualWieldingSameDelayWeapons(3); + } + } + + //If both weapons have same delay this allows a chance for DW animation + if (GetDualWieldingSameDelayWeapons() && Hand == EQ::invslot::slotPrimary) { + + if (GetDualWieldingSameDelayWeapons() == 3 && zone->random.Roll(50)) { + type = animDualWield; + SetDualWieldingSameDelayWeapons(2);//Don't roll again till you do another dw attack. + } + SetDualWieldingSameDelayWeapons(2);//Ensures first attack is always primary. + } + DoAnim(type, 0, false); return skillinuse; } - +//SYNC WITH: tune.cpp, mob.h Tunecompute_tohit int Mob::compute_tohit(EQ::skills::SkillType skillinuse) { int tohit = GetSkill(EQ::skills::SkillOffense) + 7; @@ -169,6 +186,7 @@ int Mob::compute_tohit(EQ::skills::SkillType skillinuse) } // return -1 in cases that always hit +//SYNC WITH: tune.cpp, mob.h TuneGetTotalToHit int Mob::GetTotalToHit(EQ::skills::SkillType skill, int chance_mod) { if (chance_mod >= 10000) // override for stuff like SE_SkillAttack @@ -189,6 +207,11 @@ int Mob::GetTotalToHit(EQ::skills::SkillType skill, int chance_mod) if (skill != EQ::skills::SkillArchery && skill != EQ::skills::SkillThrowing) accuracy += itembonuses.HitChance; + //518 Increase ATK accuracy by percentage, stackable + auto atkhit_bonus = itembonuses.Attack_Accuracy_Max_Percent + aabonuses.Attack_Accuracy_Max_Percent + spellbonuses.Attack_Accuracy_Max_Percent; + if (atkhit_bonus) + accuracy += round(static_cast(accuracy) * static_cast(atkhit_bonus) * 0.0001); + // 216 Melee Accuracy Amt aka SE_Accuracy -- flat bonus accuracy += itembonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1] + aabonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1] + @@ -227,6 +250,7 @@ int Mob::GetTotalToHit(EQ::skills::SkillType skill, int chance_mod) // based on dev quotes // the AGI bonus has actually drastically changed from classic +//SYNC WITH: tune.cpp, mob.h Tunecompute_defense int Mob::compute_defense() { int defense = GetSkill(EQ::skills::SkillDefense) * 400 / 225; @@ -234,6 +258,11 @@ int Mob::compute_defense() if (IsClient()) defense += CastToClient()->GetHeroicAGI() / 10; + //516 SE_AC_Mitigation_Max_Percent + auto ac_bonus = itembonuses.AC_Mitigation_Max_Percent + aabonuses.AC_Mitigation_Max_Percent + spellbonuses.AC_Mitigation_Max_Percent; + if (ac_bonus) + defense += round(static_cast(defense) * static_cast(ac_bonus) * 0.0001); + defense += itembonuses.AvoidMeleeChance; // item mod2 if (IsNPC()) defense += CastToNPC()->GetAvoidanceRating(); @@ -250,24 +279,24 @@ int Mob::compute_defense() } // return -1 in cases that always miss +// SYNC WITH : tune.cpp, mob.h TuneGetTotalDefense() int Mob::GetTotalDefense() { auto avoidance = compute_defense() + 10; // add 10 in case the NPC's stats are fucked auto evasion_bonus = spellbonuses.AvoidMeleeChanceEffect; // we check this first since it has a special case if (evasion_bonus >= 10000) return -1; - // + + // 515 SE_AC_Avoidance_Max_Percent + auto ac_aviodance_bonus = itembonuses.AC_Avoidance_Max_Percent + aabonuses.AC_Avoidance_Max_Percent + spellbonuses.AC_Avoidance_Max_Percent; + if (ac_aviodance_bonus) + avoidance += round(static_cast(avoidance) * static_cast(ac_aviodance_bonus) * 0.0001); + // 172 Evasion aka SE_AvoidMeleeChance evasion_bonus += itembonuses.AvoidMeleeChanceEffect + aabonuses.AvoidMeleeChanceEffect; // item bonus here isn't mod2 avoidance - Mob *owner = nullptr; - if (IsPet()) - owner = GetOwner(); - else if (IsNPC() && CastToNPC()->GetSwarmOwner()) - owner = entity_list.GetMobID(CastToNPC()->GetSwarmOwner()); - - if (owner) // 215 Pet Avoidance % aka SE_PetAvoidance - evasion_bonus += owner->aabonuses.PetAvoidance + owner->spellbonuses.PetAvoidance + owner->itembonuses.PetAvoidance; + // 215 Pet Avoidance % aka SE_PetAvoidance + evasion_bonus += GetPetAvoidanceBonusFromOwner(); // Evasion is a percentage bonus according to AA descriptions if (evasion_bonus) @@ -278,6 +307,7 @@ int Mob::GetTotalDefense() // called when a mob is attacked, does the checks to see if it's a hit // and does other mitigation checks. 'this' is the mob being attacked. +// SYNC WITH : tune.cpp, mob.h TuneCheckHitChance() bool Mob::CheckHitChance(Mob* other, DamageHitInfo &hit) { #ifdef LUA_EQEMU @@ -374,22 +404,51 @@ bool Mob::AvoidDamage(Mob *other, DamageHitInfo &hit) ultimately end up being more useful as fields in npc_types. */ - int counter_all = 0; + int counter_all = 0; int counter_riposte = 0; - int counter_block = 0; - int counter_parry = 0; - int counter_dodge = 0; + int counter_block = 0; + int counter_parry = 0; + int counter_dodge = 0; if (attacker->GetSpecialAbility(COUNTER_AVOID_DAMAGE)) { - counter_all = attacker->GetSpecialAbilityParam(COUNTER_AVOID_DAMAGE, 0); + counter_all = attacker->GetSpecialAbilityParam(COUNTER_AVOID_DAMAGE, 0); counter_riposte = attacker->GetSpecialAbilityParam(COUNTER_AVOID_DAMAGE, 1); - counter_block = attacker->GetSpecialAbilityParam(COUNTER_AVOID_DAMAGE, 2); - counter_parry = attacker->GetSpecialAbilityParam(COUNTER_AVOID_DAMAGE, 3); - counter_dodge = attacker->GetSpecialAbilityParam(COUNTER_AVOID_DAMAGE, 4); + counter_block = attacker->GetSpecialAbilityParam(COUNTER_AVOID_DAMAGE, 2); + counter_parry = attacker->GetSpecialAbilityParam(COUNTER_AVOID_DAMAGE, 3); + counter_dodge = attacker->GetSpecialAbilityParam(COUNTER_AVOID_DAMAGE, 4); + } + + int modify_all = 0; + int modify_riposte = 0; + int modify_block = 0; + int modify_parry = 0; + int modify_dodge = 0; + + if (GetSpecialAbility(MODIFY_AVOID_DAMAGE)) { + modify_all = GetSpecialAbilityParam(MODIFY_AVOID_DAMAGE, 0); + modify_riposte = GetSpecialAbilityParam(MODIFY_AVOID_DAMAGE, 1); + modify_block = GetSpecialAbilityParam(MODIFY_AVOID_DAMAGE, 2); + modify_parry = GetSpecialAbilityParam(MODIFY_AVOID_DAMAGE, 3); + modify_dodge = GetSpecialAbilityParam(MODIFY_AVOID_DAMAGE, 4); } // riposte -- it may seem crazy, but if the attacker has SPA 173 on them, they are immune to Ripo - bool ImmuneRipo = attacker->aabonuses.RiposteChance || attacker->spellbonuses.RiposteChance || attacker->itembonuses.RiposteChance || attacker->IsEnraged(); + bool ImmuneRipo = false; + if (!RuleB(Combat, UseLiveRiposteMechanics)) { + ImmuneRipo = attacker->aabonuses.RiposteChance || attacker->spellbonuses.RiposteChance || attacker->itembonuses.RiposteChance || attacker->IsEnraged(); + } + /* + Live Riposte Mechanics (~Kayen updated 1/22) + -Ripostes can not trigger another riposte. (Ie. Riposte from defender can't then trigger the attacker to riposte) + -Ripostes can not be 'avoided', only hit or miss. + -Attacker with SPA 173 is not immune to riposte. The defender can riposte against the attackers melee hits. + + Legacy Riposte Mechanics + -Ripostes can trigger another riposte + -Attacker with SPA 173 is immune to riposte + -Attacker that is enraged is immune to riposte + */ + // Need to check if we have something in MainHand to actually attack with (or fists) if (hit.hand != EQ::invslot::slotRange && (CanThisClassRiposte() || IsEnraged()) && InFront && !ImmuneRipo) { if (IsEnraged()) { @@ -412,6 +471,10 @@ bool Mob::AvoidDamage(Mob *other, DamageHitInfo &hit) float counter = (counter_riposte + counter_all) / 100.0f; chance -= chance * counter; } + if (modify_riposte || modify_all) { + float npc_modifier = (modify_riposte + modify_all) / 100.0f; + chance += chance * npc_modifier; + } // AA Slippery Attacks if (hit.hand == EQ::invslot::slotSecondary) { int slip = aabonuses.OffhandRiposteFail + itembonuses.OffhandRiposteFail + spellbonuses.OffhandRiposteFail; @@ -451,6 +514,10 @@ bool Mob::AvoidDamage(Mob *other, DamageHitInfo &hit) float counter = (counter_block + counter_all) / 100.0f; chance -= chance * counter; } + if (modify_block || modify_all) { + float npc_modifier = (modify_block + modify_all) / 100.0f; + chance += chance * npc_modifier; + } if (zone->random.Roll(chance)) { hit.damage_done = DMG_BLOCKED; return true; @@ -474,6 +541,10 @@ bool Mob::AvoidDamage(Mob *other, DamageHitInfo &hit) float counter = (counter_parry + counter_all) / 100.0f; chance -= chance * counter; } + if (modify_parry || modify_all) { + float npc_modifier = (modify_parry + modify_all) / 100.0f; + chance += chance * npc_modifier; + } if (zone->random.Roll(chance)) { hit.damage_done = DMG_PARRIED; return true; @@ -497,6 +568,10 @@ bool Mob::AvoidDamage(Mob *other, DamageHitInfo &hit) float counter = (counter_dodge + counter_all) / 100.0f; chance -= chance * counter; } + if (modify_dodge || modify_all) { + float npc_modifier = (modify_dodge + modify_all) / 100.0f; + chance += chance * npc_modifier; + } if (zone->random.Roll(chance)) { hit.damage_done = DMG_DODGED; return true; @@ -780,7 +855,7 @@ int Mob::GetClassRaceACBonus() return ac_bonus; } - +//SYNC WITH: tune.cpp, mob.h TuneACSum int Mob::ACSum(bool skip_caps) { int ac = 0; // this should be base AC whenever shrouds come around @@ -809,13 +884,7 @@ int Mob::ACSum(bool skip_caps) // According to the guild hall Combat Dummies, a level 50 classic EQ mob it should be ~115 // For a 60 PoP mob ~120, 70 OoW ~120 ac += GetAC(); - Mob *owner = nullptr; - if (IsPet()) - owner = GetOwner(); - else if (CastToNPC()->GetSwarmOwner()) - owner = entity_list.GetMobID(CastToNPC()->GetSwarmOwner()); - if (owner) - ac += owner->aabonuses.PetAvoidance + owner->spellbonuses.PetAvoidance + owner->itembonuses.PetAvoidance; + ac += GetPetACBonusFromOwner(); auto spell_aa_ac = aabonuses.AC + spellbonuses.AC; ac += GetSkill(EQ::skills::SkillDefense) / 5; if (EQ::ValueWithin(static_cast(GetClass()), NECROMANCER, ENCHANTER)) @@ -861,14 +930,14 @@ int Mob::ACSum(bool skip_caps) } int Mob::GetBestMeleeSkill() - { +{ int bestSkill=0; EQ::skills::SkillType meleeSkills[]= { EQ::skills::Skill1HBlunt, EQ::skills::Skill1HSlashing, EQ::skills::Skill2HBlunt, - EQ::skills::Skill2HSlashing, + EQ::skills::Skill2HSlashing, EQ::skills::SkillHandtoHand, EQ::skills::Skill1HPiercing, EQ::skills::Skill2HPiercing, @@ -883,8 +952,8 @@ int Mob::GetBestMeleeSkill() } return bestSkill; - } - +} +//SYNC WITH: tune.cpp, mob.h Tuneoffense int Mob::offense(EQ::skills::SkillType skill) { int offense = GetSkill(skill); @@ -911,7 +980,7 @@ int Mob::offense(EQ::skills::SkillType skill) if (stat_bonus >= 75) offense += (2 * stat_bonus - 150) / 3; - offense += GetATK(); + offense += GetATK() + GetPetATKBonusFromOwner(); return offense; } @@ -939,7 +1008,7 @@ double Mob::RollD20(int offense, int mitigation) return mods[index]; } - +//SYNC WITH: tune.cpp, mob.h TuneMeleeMitigation void Mob::MeleeMitigation(Mob *attacker, DamageHitInfo &hit, ExtraAttackOptions *opts) { #ifdef LUA_EQEMU @@ -988,32 +1057,35 @@ int Mob::GetWeaponDamage(Mob *against, const EQ::ItemData *weapon_item) { //check to see if our weapons or fists are magical. if (against->GetSpecialAbility(IMMUNE_MELEE_NONMAGICAL)) { - if (weapon_item) { + if (GetSpecialAbility(SPECATK_MAGICAL)) { + dmg = 1; + } + //On live this occurs for ALL NPC's >= 10 + else if (IsNPC() && GetLevel() >= RuleI(Combat, NPCAttackMagicLevel)) { + dmg = 1; + } + else if (weapon_item) { if (weapon_item->Magic) { - dmg = weapon_item->Damage; - - //this is more for non weapon items, ex: boots for kick - //they don't have a dmg but we should be able to hit magical - dmg = dmg <= 0 ? 1 : dmg; + if (weapon_item->Damage && (weapon_item->IsType1HWeapon() || weapon_item->IsType2HWeapon())) { + dmg = weapon_item->Damage; + } + //Non weapon items, ie. boots for kick. + else if (weapon_item->ItemType == EQ::item::ItemTypeArmor) { + dmg = 1; + } + else { + return 0; + } } - else + else { return 0; + } + } + else if ((GetClass() == MONK || GetClass() == BEASTLORD) && GetLevel() >= 30) { + dmg = GetHandToHandDamage(); } else { - if ((GetClass() == MONK || GetClass() == BEASTLORD) && GetLevel() >= 30) { - dmg = GetHandToHandDamage(); - } - else if (GetOwner() && GetLevel() >= RuleI(Combat, PetAttackMagicLevel)) { - //pets wouldn't actually use this but... - //it gives us an idea if we can hit due to the dual nature of this function - dmg = 1; - } - else if (GetSpecialAbility(SPECATK_MAGICAL)) - { - dmg = 1; - } - else - return 0; + return 0; } } else { @@ -1312,25 +1384,28 @@ int Client::DoDamageCaps(int base_damage) } // other is the defender, this is the attacker -void Mob::DoAttack(Mob *other, DamageHitInfo &hit, ExtraAttackOptions *opts) +//SYNC WITH: tune.cpp, mob.h TuneDoAttack +void Mob::DoAttack(Mob *other, DamageHitInfo &hit, ExtraAttackOptions *opts, bool FromRiposte) { if (!other) return; LogCombat("[{}]::DoAttack vs [{}] base [{}] min [{}] offense [{}] tohit [{}] skill [{}]", GetName(), other->GetName(), hit.base_damage, hit.min_damage, hit.offense, hit.tohit, hit.skill); - // check to see if we hit.. - if (other->AvoidDamage(this, hit)) { + if (!RuleB(Combat, UseLiveRiposteMechanics)) { + FromRiposte = false; + } + + // check to see if we hit.. + if (!FromRiposte && other->AvoidDamage(this, hit)) { int strike_through = itembonuses.StrikeThrough + spellbonuses.StrikeThrough + aabonuses.StrikeThrough; if (strike_through && zone->random.Roll(strike_through)) { MessageString(Chat::StrikeThrough, STRIKETHROUGH_STRING); // You strike through your opponents defenses! hit.damage_done = 1; // set to one, we will check this to continue } - // I'm pretty sure you can riposte a riposte if (hit.damage_done == DMG_RIPOSTED) { DoRiposte(other); - //if (IsDead()) return; } LogCombat("Avoided/strikethrough damage with code [{}]", hit.damage_done); @@ -1369,6 +1444,7 @@ void Mob::DoAttack(Mob *other, DamageHitInfo &hit, ExtraAttackOptions *opts) //note: throughout this method, setting `damage` to a negative is a way to //stop the attack calculations // IsFromSpell added to allow spell effects to use Attack. (Mainly for the Rampage AA right now.) +//SYNC WITH: tune.cpp, mob.h TuneClientAttack bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts) { if (!other) { @@ -1512,7 +1588,7 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b my_hit.tohit = GetTotalToHit(my_hit.skill, hit_chance_bonus); - DoAttack(other, my_hit, opts); + DoAttack(other, my_hit, opts, bRiposte); } else { my_hit.damage_done = DMG_INVULNERABLE; @@ -1523,35 +1599,48 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b other->AddToHateList(this, hate); + //Guard Assist Code + if (RuleB(Character, PVPEnableGuardFactionAssist)) { + if (IsClient() && other->IsClient() || (HasOwner() && GetOwner()->IsClient() && other->IsClient() )) { + auto& mob_list = entity_list.GetCloseMobList(other); + for (auto& e : mob_list) { + auto mob = e.second; + if (mob->IsNPC() && mob->CastToNPC()->IsGuard()) { + float distance = Distance(other->CastToClient()->m_Position, mob->GetPosition()); + if ((mob->CheckLosFN(other) || mob->CheckLosFN(this)) && distance <= 70) { + auto petorowner = GetOwnerOrSelf(); + if (other->GetReverseFactionCon(mob) <= petorowner->GetReverseFactionCon(mob)) { + mob->AddToHateList(this); + } + } + } + } + } + } + /////////////////////////////////////////////////////////// ////// Send Attack Damage /////////////////////////////////////////////////////////// - if (my_hit.damage_done > 0 && aabonuses.SkillAttackProc[0] && aabonuses.SkillAttackProc[1] == my_hit.skill && - IsValidSpell(aabonuses.SkillAttackProc[2])) { - float chance = aabonuses.SkillAttackProc[0] / 1000.0f; - if (zone->random.Roll(chance)) - SpellFinished(aabonuses.SkillAttackProc[2], other, EQ::spells::CastingSlot::Item, 0, -1, - spells[aabonuses.SkillAttackProc[2]].ResistDiff); - } other->Damage(this, my_hit.damage_done, SPELL_UNKNOWN, my_hit.skill, true, -1, false, m_specialattacks); - if (IsDead()) return false; + if (IsDead()) { + return false; + } MeleeLifeTap(my_hit.damage_done); - if (my_hit.damage_done > 0 && HasSkillProcSuccess() && other && other->GetHP() > 0) - TrySkillProc(other, my_hit.skill, 0, true, Hand); - CommonBreakInvisibleFromCombat(); - if (GetTarget()) + if (GetTarget()) { TriggerDefensiveProcs(other, Hand, true, my_hit.damage_done); + } - if (my_hit.damage_done > 0) + if (my_hit.damage_done > 0) { return true; - - else + } + else { return false; + } } //used by complete heal and #heal @@ -1607,9 +1696,14 @@ bool Client::Death(Mob* killerMob, int32 damage, uint16 spell, EQ::skills::Skill if (!spell) spell = SPELL_UNKNOWN; - char buffer[48] = { 0 }; - snprintf(buffer, 47, "%d %d %d %d", killerMob ? killerMob->GetID() : 0, damage, spell, static_cast(attack_skill)); - if (parse->EventPlayer(EVENT_DEATH, this, buffer, 0) != 0) { + std::string export_string = fmt::format( + "{} {} {} {}", + killerMob ? killerMob->GetID() : 0, + damage, + spell, + static_cast(attack_skill) + ); + if (parse->EventPlayer(EVENT_DEATH, this, export_string, 0) != 0) { if (GetHP() < 0) { SetHP(0); } @@ -1634,9 +1728,7 @@ bool Client::Death(Mob* killerMob, int32 damage, uint16 spell, EQ::skills::Skill int exploss = 0; LogCombat("Fatal blow dealt by [{}] with [{}] damage, spell [{}], skill [{}]", killerMob ? killerMob->GetName() : "Unknown", damage, spell, attack_skill); - /* - #1: Send death packet to everyone - */ + // #1: Send death packet to everyone uint8 killed_level = GetLevel(); SendLogoutPackets(); @@ -1656,22 +1748,27 @@ bool Client::Death(Mob* killerMob, int32 damage, uint16 spell, EQ::skills::Skill d->spawn_id = GetID(); d->killer_id = killerMob ? killerMob->GetID() : 0; d->corpseid = GetID(); - d->bindzoneid = m_pp.binds[0].zoneId; + d->bindzoneid = m_pp.binds[0].zone_id; d->spell_id = spell == SPELL_UNKNOWN ? 0xffffffff : spell; d->attack_skill = spell != SPELL_UNKNOWN ? 0xe7 : attack_skill; d->damage = damage; app.priority = 6; entity_list.QueueClients(this, &app); - /* - #2: figure out things that affect the player dying and mark them dead - */ + // #2: figure out things that affect the player dying and mark them dead InterruptSpell(); + + Mob* m_pet = GetPet(); SetPet(0); SetHorseId(0); + ShieldAbilityClearVariables(); dead = true; + if (m_pet && m_pet->IsCharmed()) { + m_pet->BuffFadeByEffect(SE_Charm); + } + if (GetMerc()) { GetMerc()->Suspend(); } @@ -1795,6 +1892,8 @@ bool Client::Death(Mob* killerMob, int32 damage, uint16 spell, EQ::skills::Skill //m_epp.perAA = 0; //reset to no AA exp on death. } + int32 illusion_spell_id = spellbonuses.Illusion; + //this generates a lot of 'updates' to the client that the client does not need BuffFadeNonPersistDeath(); if (RuleB(Character, UnmemSpellsOnDeath)) { @@ -1840,13 +1939,12 @@ bool Client::Death(Mob* killerMob, int32 damage, uint16 spell, EQ::skills::Skill } } } - entity_list.AddCorpse(new_corpse, GetID()); SetID(0); //send the become corpse packet to everybody else in the zone. entity_list.QueueClients(this, &app2, true); - + ApplyIllusionToCorpse(illusion_spell_id, new_corpse); LeftCorpse = true; } } @@ -1893,7 +1991,7 @@ bool Client::Death(Mob* killerMob, int32 damage, uint16 spell, EQ::skills::Skill r->MemberZoned(this); dead_timer.Start(5000, true); - m_pp.zone_id = m_pp.binds[0].zoneId; + m_pp.zone_id = m_pp.binds[0].zone_id; m_pp.zoneInstance = m_pp.binds[0].instance_id; database.MoveCharacterToZone(this->CharacterID(), m_pp.zone_id); Save(); @@ -1908,10 +2006,10 @@ bool Client::Death(Mob* killerMob, int32 damage, uint16 spell, EQ::skills::Skill QServ->PlayerLogEvent(Player_Log_Deaths, this->CharacterID(), event_desc); } - parse->EventPlayer(EVENT_DEATH_COMPLETE, this, buffer, 0); + parse->EventPlayer(EVENT_DEATH_COMPLETE, this, export_string, 0); return true; } - +//SYNC WITH: tune.cpp, mob.h TuneNPCAttack bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts) { if (!other) { @@ -2003,6 +2101,24 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool } } + //Guard Assist Code + if (RuleB(Character, PVPEnableGuardFactionAssist)) { + if (IsClient() && other->IsClient() || (HasOwner() && GetOwner()->IsClient() && other->IsClient())) { + auto& mob_list = entity_list.GetCloseMobList(other); + for (auto& e : mob_list) { + auto mob = e.second; + if (mob->IsNPC() && mob->CastToNPC()->IsGuard()) { + float distance = Distance(other->GetPosition(), mob->GetPosition()); + if ((mob->CheckLosFN(other) || mob->CheckLosFN(this)) && distance <= 70) { + if (other->GetReverseFactionCon(mob) <= GetOwner()->GetReverseFactionCon(mob)) { + mob->AddToHateList(this); + } + } + } + } + } + } + int weapon_damage = GetWeaponDamage(other, weapon); //do attack animation regardless of whether or not we can hit below @@ -2065,7 +2181,7 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool my_hit.offense = offense(my_hit.skill); my_hit.tohit = GetTotalToHit(my_hit.skill, hit_chance_bonus); - DoAttack(other, my_hit, opts); + DoAttack(other, my_hit, opts, bRiposte); other->AddToHateList(this, hate); @@ -2145,7 +2261,7 @@ void NPC::Damage(Mob* other, int32 damage, uint16 spell_id, EQ::skills::SkillTyp if (IsLDoNTrapped()) { MessageString(Chat::Red, LDON_ACCIDENT_SETOFF2); - SpellFinished(GetLDoNTrapSpellID(), other, EQ::spells::CastingSlot::Item, 0, -1, spells[GetLDoNTrapSpellID()].ResistDiff, false); + SpellFinished(GetLDoNTrapSpellID(), other, EQ::spells::CastingSlot::Item, 0, -1, spells[GetLDoNTrapSpellID()].resist_difficulty, false); SetLDoNTrapSpellID(0); SetLDoNTrapped(false); SetLDoNTrapDetected(false); @@ -2170,11 +2286,8 @@ bool NPC::Death(Mob* killer_mob, int32 damage, uint16 spell, EQ::skills::SkillTy Mob *oos = nullptr; if (killer_mob) { oos = killer_mob->GetOwnerOrSelf(); - - char buffer[48] = { 0 }; - snprintf(buffer, 47, "%d %d %d %d", killer_mob->GetID(), damage, spell, static_cast(attack_skill)); - - if (parse->EventNPC(EVENT_DEATH, this, oos, buffer, 0) != 0) { + std::string buffer = fmt::format("{} {} {} {}", killer_mob->GetID(), damage, spell, static_cast(attack_skill)); + if (parse->EventNPC(EVENT_DEATH, this, oos, buffer.c_str(), 0) != 0) { if (GetHP() < 0) { SetHP(0); } @@ -2197,10 +2310,8 @@ bool NPC::Death(Mob* killer_mob, int32 damage, uint16 spell, EQ::skills::SkillTy } } else { - char buffer[48] = { 0 }; - snprintf(buffer, 47, "%d %d %d %d", 0, damage, spell, static_cast(attack_skill)); - - if (parse->EventNPC(EVENT_DEATH, this, nullptr, buffer, 0) != 0) { + std::string buffer = fmt::format("{} {} {} {}", 0, damage, spell, static_cast(attack_skill)); + if (parse->EventNPC(EVENT_DEATH, this, nullptr, buffer.c_str(), 0) != 0) { if (GetHP() < 0) { SetHP(0); } @@ -2213,6 +2324,8 @@ bool NPC::Death(Mob* killer_mob, int32 damage, uint16 spell, EQ::skills::SkillTy Log(Logs::Detail, Logs::Attack, "%s Mobs currently Aggro %i", __FUNCTION__, zone->MobsAggroCount()); } + ShieldAbilityClearVariables(); + SetHP(0); SetPet(0); @@ -2229,6 +2342,8 @@ bool NPC::Death(Mob* killer_mob, int32 damage, uint16 spell, EQ::skills::SkillTy if (p_depop == true) return false; + int32 illusion_spell_id = spellbonuses.Illusion; + HasAISpellEffects = false; BuffFadeAll(); uint8 killed_level = GetLevel(); @@ -2300,7 +2415,7 @@ bool NPC::Death(Mob* killer_mob, int32 damage, uint16 spell, EQ::skills::SkillTy give_exp_client = give_exp->CastToClient(); //do faction hits even if we are a merchant, so long as a player killed us - if (give_exp_client && !RuleB(NPC, EnableMeritBasedFaction)) + if (!IsCharmed() && give_exp_client && !RuleB(NPC, EnableMeritBasedFaction)) hate_list.DoFactionHits(GetNPCFactionID()); bool IsLdonTreasure = (this->GetClass() == LDON_TREASURE); @@ -2312,6 +2427,16 @@ bool NPC::Death(Mob* killer_mob, int32 damage, uint16 spell, EQ::skills::SkillTy int32 finalxp = give_exp_client->GetExperienceForKill(this); finalxp = give_exp_client->mod_client_xp(finalxp, this); + // handle task credit on behalf of the killer + if (RuleB(TaskSystem, EnableTaskSystem)) { + LogTasksDetail( + "[NPC::Death] Triggering HandleUpdateTasksOnKill for [{}] npc [{}]", + give_exp_client->GetCleanName(), + GetNPCTypeID() + ); + task_manager->HandleUpdateTasksOnKill(give_exp_client, GetNPCTypeID()); + } + if (kr) { if (!IsLdonTreasure && MerchantType == 0) { kr->SplitExp((finalxp), this); @@ -2330,8 +2455,6 @@ bool NPC::Death(Mob* killer_mob, int32 damage, uint16 spell, EQ::skills::SkillTy mod_npc_killed_merit(kr->members[i].member); - if (RuleB(TaskSystem, EnableTaskSystem)) - kr->members[i].member->UpdateTasksOnKill(GetNPCTypeID()); PlayerCount++; } } @@ -2379,9 +2502,6 @@ bool NPC::Death(Mob* killer_mob, int32 damage, uint16 spell, EQ::skills::SkillTy mod_npc_killed_merit(c); - if (RuleB(TaskSystem, EnableTaskSystem)) - c->UpdateTasksOnKill(GetNPCTypeID()); - PlayerCount++; } } @@ -2430,9 +2550,6 @@ bool NPC::Death(Mob* killer_mob, int32 damage, uint16 spell, EQ::skills::SkillTy mod_npc_killed_merit(give_exp_client); - if (RuleB(TaskSystem, EnableTaskSystem)) - give_exp_client->UpdateTasksOnKill(GetNPCTypeID()); - // QueryServ Logging - Solo if (RuleB(QueryServ, PlayerLogNPCKills)) { auto pack = new ServerPacket(ServerOP_QSPlayerLogNPCKills, @@ -2469,6 +2586,7 @@ bool NPC::Death(Mob* killer_mob, int32 damage, uint16 spell, EQ::skills::SkillTy } entity_list.RemoveFromAutoXTargets(this); + uint16 emoteid = this->GetEmoteID(); auto corpse = new Corpse(this, &itemlist, GetNPCTypeID(), &NPCTypedata, level > 54 ? RuleI(NPC, MajorNPCCorpseDecayTimeMS) @@ -2481,8 +2599,8 @@ bool NPC::Death(Mob* killer_mob, int32 damage, uint16 spell, EQ::skills::SkillTy // entity_list.RemoveMobFromCloseLists(this); close_mobs.clear(); - this->SetID(0); + ApplyIllusionToCorpse(illusion_spell_id, corpse); if (killer != 0 && emoteid != 0) corpse->CastToNPC()->DoNPCEmote(AFTERDEATH, emoteid); @@ -2587,16 +2705,15 @@ bool NPC::Death(Mob* killer_mob, int32 damage, uint16 spell, EQ::skills::SkillTy entity_list.UpdateFindableNPCState(this, true); - char buffer[48] = { 0 }; - snprintf(buffer, 47, "%d %d %d %d", killer_mob ? killer_mob->GetID() : 0, damage, spell, static_cast(attack_skill)); - parse->EventNPC(EVENT_DEATH_COMPLETE, this, oos, buffer, 0); + std::string buffer = fmt::format("{} {} {} {}", killer_mob ? killer_mob->GetID() : 0, damage, spell, static_cast(attack_skill)); + parse->EventNPC(EVENT_DEATH_COMPLETE, this, oos, buffer.c_str(), 0); /* Zone controller process EVENT_DEATH_ZONE (Death events) */ if (RuleB(Zone, UseZoneController)) { - if (entity_list.GetNPCByNPCTypeID(ZONE_CONTROLLER_NPC_ID) && this->GetNPCTypeID() != ZONE_CONTROLLER_NPC_ID) { - char data_pass[100] = { 0 }; - snprintf(data_pass, 99, "%d %d %d %d %d", killer_mob ? killer_mob->GetID() : 0, damage, spell, static_cast(attack_skill), this->GetNPCTypeID()); - parse->EventNPC(EVENT_DEATH_ZONE, entity_list.GetNPCByNPCTypeID(ZONE_CONTROLLER_NPC_ID)->CastToNPC(), nullptr, data_pass, 0); + auto controller = entity_list.GetNPCByNPCTypeID(ZONE_CONTROLLER_NPC_ID); + if (controller && GetNPCTypeID() != ZONE_CONTROLLER_NPC_ID) { + std::string data_pass = fmt::format("{} {} {} {} {}", killer_mob ? killer_mob->GetID() : 0, damage, spell, static_cast(attack_skill), GetNPCTypeID()); + parse->EventNPC(EVENT_DEATH_ZONE, controller, nullptr, data_pass.c_str(), 0); } } @@ -2659,8 +2776,9 @@ void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, b } } - if (other->IsNPC() && (other->IsPet() || other->CastToNPC()->GetSwarmOwner() > 0)) - TryTriggerOnValueAmount(false, false, false, true); + if (other->IsNPC() && (other->IsPet() || other->CastToNPC()->GetSwarmOwner() > 0)) { + TryTriggerOnCastRequirement(); + } if (IsClient() && !IsAIControlled()) return; @@ -2707,13 +2825,13 @@ void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, b if (damage > GetHP()) damage = GetHP(); - if (spellbonuses.ImprovedTaunt[1] && (GetLevel() < spellbonuses.ImprovedTaunt[0]) - && other && (buffs[spellbonuses.ImprovedTaunt[2]].casterid != other->GetID())) - hate = (hate*spellbonuses.ImprovedTaunt[1]) / 100; + if (spellbonuses.ImprovedTaunt[SBIndex::IMPROVED_TAUNT_AGGRO_MOD] && (GetLevel() < spellbonuses.ImprovedTaunt[SBIndex::IMPROVED_TAUNT_MAX_LVL]) + && other && (buffs[spellbonuses.ImprovedTaunt[SBIndex::IMPROVED_TAUNT_BUFFSLOT]].casterid != other->GetID())) + hate = (hate*spellbonuses.ImprovedTaunt[SBIndex::IMPROVED_TAUNT_AGGRO_MOD]) / 100; hate_list.AddEntToHateList(other, hate, damage, bFrenzy, !iBuffTic); - if (other->IsClient() && !on_hatelist && !IsOnFeignMemory(other->CastToClient())) + if (other->IsClient() && !on_hatelist && !IsOnFeignMemory(other)) other->CastToClient()->AddAutoXTarget(this); #ifdef BOTS @@ -2755,7 +2873,7 @@ void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, b } } //MERC - // then add pet owner if there's one + //if I am a pet, then add pet owner if there's one if (owner) { // Other is a pet, add him and it // EverHood 6/12/06 // Can't add a feigned owner to hate list @@ -2767,11 +2885,12 @@ void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, b // owner must get on list, but he's not actually gained any hate yet if ( !owner->GetSpecialAbility(IMMUNE_AGGRO) && - !(this->GetSpecialAbility(IMMUNE_AGGRO_CLIENT) && owner->IsClient()) && + !(this->GetSpecialAbility(IMMUNE_AGGRO_CLIENT) && owner->IsClient()) && !(this->GetSpecialAbility(IMMUNE_AGGRO_NPC) && owner->IsNPC()) ) { - if (owner->IsClient() && !CheckAggro(owner)) + if (owner->IsClient() && !CheckAggro(owner)) { owner->CastToClient()->AddAutoXTarget(this); + } hate_list.AddEntToHateList(owner, 0, 0, false, !iBuffTic); } } @@ -2779,9 +2898,9 @@ void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, b if (mypet && !mypet->IsHeld() && !mypet->IsPetStop()) { // I have a pet, add other to it if ( - !mypet->IsFamiliar() && + !mypet->IsFamiliar() && !mypet->GetSpecialAbility(IMMUNE_AGGRO) && - !(mypet->GetSpecialAbility(IMMUNE_AGGRO_CLIENT) && this->IsClient()) && + !(mypet->GetSpecialAbility(IMMUNE_AGGRO_CLIENT) && this->IsClient()) && !(mypet->GetSpecialAbility(IMMUNE_AGGRO_NPC) && this->IsNPC()) ) { mypet->hate_list.AddEntToHateList(other, 0, 0, bFrenzy); @@ -2789,17 +2908,19 @@ void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, b } else if (myowner) { // I am a pet, add other to owner if it's NPC/LD if ( - myowner->IsAIControlled() && + myowner->IsAIControlled() && !myowner->GetSpecialAbility(IMMUNE_AGGRO) && - !(this->GetSpecialAbility(IMMUNE_AGGRO_CLIENT) && myowner->IsClient()) && + !(this->GetSpecialAbility(IMMUNE_AGGRO_CLIENT) && myowner->IsClient()) && !(this->GetSpecialAbility(IMMUNE_AGGRO_NPC) && myowner->IsNPC()) ) { myowner->hate_list.AddEntToHateList(other, 0, 0, bFrenzy); } } - if (other->GetTempPetCount()) - entity_list.AddTempPetsToHateList(other, this, bFrenzy); + //I have a swarm pet, add other to it. + if (GetTempPetCount()) { + entity_list.AddTempPetsToHateList(this, other, bFrenzy); + } if (!wasengaged) { if (IsNPC() && other->IsClient() && other->CastToClient()) @@ -2834,10 +2955,15 @@ void Mob::DamageShield(Mob* attacker, bool spell_ds) { spellid = spellbonuses.DamageShieldSpellID; } else { - DS = spellbonuses.SpellDamageShield; + DS = spellbonuses.SpellDamageShield + itembonuses.SpellDamageShield + aabonuses.SpellDamageShield; rev_ds = 0; // This ID returns "you are burned", seemed most appropriate for spell DS spellid = 2166; + /* + Live Message - not yet used on emu + Feedback onto you "YOUR mind burns from TARGETS NAME's feedback for %i points of non-melee damage." + Feedback onto other "TARGETS NAME's mind burns from YOUR feedback for %i points of non-melee damage." + */ } if (DS == 0 && rev_ds == 0) @@ -2851,6 +2977,10 @@ void Mob::DamageShield(Mob* attacker, bool spell_ds) { DS += aabonuses.DamageShield; //Live AA - coat of thistles. (negative value) DS -= itembonuses.DamageShield; //+Damage Shield should only work when you already have a DS spell + DS -= attacker->aabonuses.DS_Mitigation_Amount + attacker->itembonuses.DS_Mitigation_Amount + attacker->spellbonuses.DS_Mitigation_Amount; //Negative value to reduce + //Do not allow flat amount reductions to reduce past 0. + if (DS >= 0) + return; //Spell data for damage shield mitigation shows a negative value for spells for clients and positive //value for spells that effect pets. Unclear as to why. For now will convert all positive to be consistent. @@ -2860,8 +2990,14 @@ void Mob::DamageShield(Mob* attacker, bool spell_ds) { attacker->aabonuses.DSMitigationOffHand; DS -= DS*mitigation / 100; } - DS -= DS * attacker->itembonuses.DSMitigation / 100; + + int ds_mitigation = attacker->itembonuses.DSMitigation; + // Subtract mitigations because DS_Mitigation_Percentage is a negative value when reducing total, thus final value will be positive + ds_mitigation -= attacker->aabonuses.DS_Mitigation_Percentage + attacker->itembonuses.DS_Mitigation_Percentage + attacker->spellbonuses.DS_Mitigation_Percentage; //Negative value to reduce + + DS -= DS * ds_mitigation / 100; } + attacker->Damage(this, -DS, spellid, EQ::skills::SkillAbjuration/*hackish*/, false); //we can assume there is a spell now auto outapp = new EQApplicationPacket(OP_Damage, sizeof(CombatDamage_Struct)); @@ -3015,9 +3151,10 @@ int Mob::GetHandToHandDelay(void) epic = 280; else if (GetRace() == IKSAR) iksar = 1; - if (epic > skill) - skill = epic; - return iksar - skill / 21 + 38; + // the delay bonus from the monk epic scales up to a skill of 280 + if (epic >= skill) + epic = skill; + return iksar - epic / 21 + 38; } int delay = 35; @@ -3073,30 +3210,30 @@ int32 Mob::ReduceDamage(int32 damage) int32 slot = -1; bool DisableMeleeRune = false; - if (spellbonuses.NegateAttacks[0]) { - slot = spellbonuses.NegateAttacks[1]; + if (spellbonuses.NegateAttacks[SBIndex::NEGATE_ATK_EXISTS]) { + slot = spellbonuses.NegateAttacks[SBIndex::NEGATE_ATK_BUFFSLOT]; if (slot >= 0) { - if (--buffs[slot].numhits == 0) { + if (--buffs[slot].hit_number == 0) { if (!TryFadeEffect(slot)) BuffFadeBySlot(slot, true); } - if (spellbonuses.NegateAttacks[2] && (damage > spellbonuses.NegateAttacks[2])) - damage -= spellbonuses.NegateAttacks[2]; + if (spellbonuses.NegateAttacks[SBIndex::NEGATE_ATK_MAX_DMG_ABSORB_PER_HIT] && (damage > spellbonuses.NegateAttacks[SBIndex::NEGATE_ATK_MAX_DMG_ABSORB_PER_HIT])) + damage -= spellbonuses.NegateAttacks[SBIndex::NEGATE_ATK_MAX_DMG_ABSORB_PER_HIT]; else return DMG_RUNE; } } //Only mitigate if damage is above the minimium specified. - if (spellbonuses.MeleeThresholdGuard[0]) { - slot = spellbonuses.MeleeThresholdGuard[1]; + if (spellbonuses.MeleeThresholdGuard[SBIndex::THRESHOLDGUARD_MITIGATION_PERCENT]) { + slot = spellbonuses.MeleeThresholdGuard[SBIndex::THRESHOLDGUARD_BUFFSLOT]; - if (slot >= 0 && (damage > spellbonuses.MeleeThresholdGuard[2])) + if (slot >= 0 && (damage > spellbonuses.MeleeThresholdGuard[SBIndex::THRESHOLDGUARD_MIN_DMG_TO_TRIGGER])) { DisableMeleeRune = true; - int damage_to_reduce = damage * spellbonuses.MeleeThresholdGuard[0] / 100; + int damage_to_reduce = damage * spellbonuses.MeleeThresholdGuard[SBIndex::THRESHOLDGUARD_MITIGATION_PERCENT] / 100; if (damage_to_reduce >= buffs[slot].melee_rune) { LogSpells("Mob::ReduceDamage SE_MeleeThresholdGuard [{}] damage negated, [{}] damage remaining, fading buff", damage_to_reduce, buffs[slot].melee_rune); @@ -3113,16 +3250,16 @@ int32 Mob::ReduceDamage(int32 damage) } } - if (spellbonuses.MitigateMeleeRune[0] && !DisableMeleeRune) { - slot = spellbonuses.MitigateMeleeRune[1]; + if (spellbonuses.MitigateMeleeRune[SBIndex::MITIGATION_RUNE_PERCENT] && !DisableMeleeRune) { + slot = spellbonuses.MitigateMeleeRune[SBIndex::MITIGATION_RUNE_BUFFSLOT]; if (slot >= 0) { - int damage_to_reduce = damage * spellbonuses.MitigateMeleeRune[0] / 100; + int damage_to_reduce = damage * spellbonuses.MitigateMeleeRune[SBIndex::MITIGATION_RUNE_PERCENT] / 100; - if (spellbonuses.MitigateMeleeRune[2] && (damage_to_reduce > spellbonuses.MitigateMeleeRune[2])) - damage_to_reduce = spellbonuses.MitigateMeleeRune[2]; + if (spellbonuses.MitigateMeleeRune[SBIndex::MITIGATION_RUNE_MAX_DMG_ABSORB_PER_HIT] && (damage_to_reduce > spellbonuses.MitigateMeleeRune[SBIndex::MITIGATION_RUNE_MAX_DMG_ABSORB_PER_HIT])) + damage_to_reduce = spellbonuses.MitigateMeleeRune[SBIndex::MITIGATION_RUNE_MAX_DMG_ABSORB_PER_HIT]; - if (spellbonuses.MitigateMeleeRune[3] && (damage_to_reduce >= buffs[slot].melee_rune)) + if (spellbonuses.MitigateMeleeRune[SBIndex::MITIGATION_RUNE_MAX_HP_AMT] && (damage_to_reduce >= buffs[slot].melee_rune)) { LogSpells("Mob::ReduceDamage SE_MitigateMeleeDamage [{}] damage negated, [{}] damage remaining, fading buff", damage_to_reduce, buffs[slot].melee_rune); damage -= buffs[slot].melee_rune; @@ -3133,7 +3270,7 @@ int32 Mob::ReduceDamage(int32 damage) { LogSpells("Mob::ReduceDamage SE_MitigateMeleeDamage [{}] damage negated, [{}] damage remaining", damage_to_reduce, buffs[slot].melee_rune); - if (spellbonuses.MitigateMeleeRune[3]) + if (spellbonuses.MitigateMeleeRune[SBIndex::MITIGATION_RUNE_MAX_HP_AMT]) buffs[slot].melee_rune = (buffs[slot].melee_rune - damage_to_reduce); damage -= damage_to_reduce; @@ -3144,7 +3281,7 @@ int32 Mob::ReduceDamage(int32 damage) if (damage < 1) return DMG_RUNE; - if (spellbonuses.MeleeRune[0] && spellbonuses.MeleeRune[1] >= 0) + if (spellbonuses.MeleeRune[SBIndex::RUNE_AMOUNT] && spellbonuses.MeleeRune[SBIndex::RUNE_BUFFSLOT] >= 0) damage = RuneAbsorb(damage, SE_Rune); if (damage < 1) @@ -3162,17 +3299,17 @@ int32 Mob::AffectMagicalDamage(int32 damage, uint16 spell_id, const bool iBuffTi int32 slot = -1; // See if we block the spell outright first - if (!iBuffTic && spellbonuses.NegateAttacks[0]) { - slot = spellbonuses.NegateAttacks[1]; + if (!iBuffTic && spellbonuses.NegateAttacks[SBIndex::NEGATE_ATK_EXISTS]) { + slot = spellbonuses.NegateAttacks[SBIndex::NEGATE_ATK_BUFFSLOT]; if (slot >= 0) { - if (--buffs[slot].numhits == 0) { + if (--buffs[slot].hit_number == 0) { if (!TryFadeEffect(slot)) BuffFadeBySlot(slot, true); } - if (spellbonuses.NegateAttacks[2] && (damage > spellbonuses.NegateAttacks[2])) - damage -= spellbonuses.NegateAttacks[2]; + if (spellbonuses.NegateAttacks[SBIndex::NEGATE_ATK_MAX_DMG_ABSORB_PER_HIT] && (damage > spellbonuses.NegateAttacks[SBIndex::NEGATE_ATK_MAX_DMG_ABSORB_PER_HIT])) + damage -= spellbonuses.NegateAttacks[SBIndex::NEGATE_ATK_MAX_DMG_ABSORB_PER_HIT]; else return 0; } @@ -3180,18 +3317,19 @@ int32 Mob::AffectMagicalDamage(int32 damage, uint16 spell_id, const bool iBuffTi // If this is a DoT, use DoT Shielding... if (iBuffTic) { - damage -= (damage * itembonuses.DoTShielding / 100); + int total_dotshielding = itembonuses.DoTShielding + itembonuses.MitigateDotRune[SBIndex::MITIGATION_RUNE_PERCENT] + aabonuses.MitigateDotRune[SBIndex::MITIGATION_RUNE_PERCENT]; + damage -= (damage * total_dotshielding / 100); - if (spellbonuses.MitigateDotRune[0]) { - slot = spellbonuses.MitigateDotRune[1]; + if (spellbonuses.MitigateDotRune[SBIndex::MITIGATION_RUNE_PERCENT]) { + slot = spellbonuses.MitigateDotRune[SBIndex::MITIGATION_RUNE_BUFFSLOT]; if (slot >= 0) { - int damage_to_reduce = damage * spellbonuses.MitigateDotRune[0] / 100; + int damage_to_reduce = damage * spellbonuses.MitigateDotRune[SBIndex::MITIGATION_RUNE_PERCENT] / 100; - if (spellbonuses.MitigateDotRune[2] && (damage_to_reduce > spellbonuses.MitigateDotRune[2])) - damage_to_reduce = spellbonuses.MitigateDotRune[2]; + if (spellbonuses.MitigateDotRune[SBIndex::MITIGATION_RUNE_MAX_DMG_ABSORB_PER_HIT] && (damage_to_reduce > spellbonuses.MitigateDotRune[SBIndex::MITIGATION_RUNE_MAX_DMG_ABSORB_PER_HIT])) + damage_to_reduce = spellbonuses.MitigateDotRune[SBIndex::MITIGATION_RUNE_MAX_DMG_ABSORB_PER_HIT]; - if (spellbonuses.MitigateDotRune[3] && (damage_to_reduce >= buffs[slot].dot_rune)) + if (spellbonuses.MitigateDotRune[SBIndex::MITIGATION_RUNE_MAX_HP_AMT] && (damage_to_reduce >= buffs[slot].dot_rune)) { damage -= buffs[slot].dot_rune; if (!TryFadeEffect(slot)) @@ -3199,7 +3337,7 @@ int32 Mob::AffectMagicalDamage(int32 damage, uint16 spell_id, const bool iBuffTi } else { - if (spellbonuses.MitigateDotRune[3]) + if (spellbonuses.MitigateDotRune[SBIndex::MITIGATION_RUNE_MAX_HP_AMT]) buffs[slot].dot_rune = (buffs[slot].dot_rune - damage_to_reduce); damage -= damage_to_reduce; @@ -3212,16 +3350,17 @@ int32 Mob::AffectMagicalDamage(int32 damage, uint16 spell_id, const bool iBuffTi else { // Reduce damage by the Spell Shielding first so that the runes don't take the raw damage. - damage -= (damage * itembonuses.SpellShield / 100); - + int total_spellshielding = itembonuses.SpellShield + itembonuses.MitigateSpellRune[SBIndex::MITIGATION_RUNE_PERCENT] + aabonuses.MitigateSpellRune[SBIndex::MITIGATION_RUNE_PERCENT]; + damage -= (damage * total_spellshielding / 100); + //Only mitigate if damage is above the minimium specified. - if (spellbonuses.SpellThresholdGuard[0]) { - slot = spellbonuses.SpellThresholdGuard[1]; + if (spellbonuses.SpellThresholdGuard[SBIndex::THRESHOLDGUARD_MITIGATION_PERCENT]) { + slot = spellbonuses.SpellThresholdGuard[SBIndex::THRESHOLDGUARD_BUFFSLOT]; - if (slot >= 0 && (damage > spellbonuses.MeleeThresholdGuard[2])) + if (slot >= 0 && (damage > spellbonuses.MeleeThresholdGuard[SBIndex::THRESHOLDGUARD_MIN_DMG_TO_TRIGGER])) { DisableSpellRune = true; - int damage_to_reduce = damage * spellbonuses.SpellThresholdGuard[0] / 100; + int damage_to_reduce = damage * spellbonuses.SpellThresholdGuard[SBIndex::THRESHOLDGUARD_MITIGATION_PERCENT] / 100; if (damage_to_reduce >= buffs[slot].magic_rune) { damage -= buffs[slot].magic_rune; @@ -3237,16 +3376,16 @@ int32 Mob::AffectMagicalDamage(int32 damage, uint16 spell_id, const bool iBuffTi } // Do runes now. - if (spellbonuses.MitigateSpellRune[0] && !DisableSpellRune) { - slot = spellbonuses.MitigateSpellRune[1]; + if (spellbonuses.MitigateSpellRune[SBIndex::MITIGATION_RUNE_PERCENT] && !DisableSpellRune) { + slot = spellbonuses.MitigateSpellRune[SBIndex::MITIGATION_RUNE_BUFFSLOT]; if (slot >= 0) { - int damage_to_reduce = damage * spellbonuses.MitigateSpellRune[0] / 100; + int damage_to_reduce = damage * spellbonuses.MitigateSpellRune[SBIndex::MITIGATION_RUNE_PERCENT] / 100; - if (spellbonuses.MitigateSpellRune[2] && (damage_to_reduce > spellbonuses.MitigateSpellRune[2])) - damage_to_reduce = spellbonuses.MitigateSpellRune[2]; + if (spellbonuses.MitigateSpellRune[SBIndex::MITIGATION_RUNE_MAX_DMG_ABSORB_PER_HIT] && (damage_to_reduce > spellbonuses.MitigateSpellRune[SBIndex::MITIGATION_RUNE_MAX_DMG_ABSORB_PER_HIT])) + damage_to_reduce = spellbonuses.MitigateSpellRune[SBIndex::MITIGATION_RUNE_MAX_DMG_ABSORB_PER_HIT]; - if (spellbonuses.MitigateSpellRune[3] && (damage_to_reduce >= buffs[slot].magic_rune)) + if (spellbonuses.MitigateSpellRune[SBIndex::MITIGATION_RUNE_MAX_HP_AMT] && (damage_to_reduce >= buffs[slot].magic_rune)) { LogSpells("Mob::ReduceDamage SE_MitigateSpellDamage [{}] damage negated, [{}] damage remaining, fading buff", damage_to_reduce, buffs[slot].magic_rune); damage -= buffs[slot].magic_rune; @@ -3257,7 +3396,7 @@ int32 Mob::AffectMagicalDamage(int32 damage, uint16 spell_id, const bool iBuffTi { LogSpells("Mob::ReduceDamage SE_MitigateMeleeDamage [{}] damage negated, [{}] damage remaining", damage_to_reduce, buffs[slot].magic_rune); - if (spellbonuses.MitigateSpellRune[3]) + if (spellbonuses.MitigateSpellRune[SBIndex::MITIGATION_RUNE_MAX_HP_AMT]) buffs[slot].magic_rune = (buffs[slot].magic_rune - damage_to_reduce); damage -= damage_to_reduce; @@ -3269,10 +3408,10 @@ int32 Mob::AffectMagicalDamage(int32 damage, uint16 spell_id, const bool iBuffTi return 0; //Regular runes absorb spell damage (except dots) - Confirmed on live. - if (spellbonuses.MeleeRune[0] && spellbonuses.MeleeRune[1] >= 0) + if (spellbonuses.MeleeRune[SBIndex::RUNE_AMOUNT] && spellbonuses.MeleeRune[SBIndex::RUNE_BUFFSLOT] >= 0) damage = RuneAbsorb(damage, SE_Rune); - if (spellbonuses.AbsorbMagicAtt[0] && spellbonuses.AbsorbMagicAtt[1] >= 0) + if (spellbonuses.AbsorbMagicAtt[SBIndex::RUNE_AMOUNT] && spellbonuses.AbsorbMagicAtt[SBIndex::RUNE_BUFFSLOT] >= 0) damage = RuneAbsorb(damage, SE_AbsorbMagicAtt); if (damage < 1) @@ -3286,12 +3425,25 @@ int32 Mob::ReduceAllDamage(int32 damage) if (damage <= 0) return damage; - if (spellbonuses.ManaAbsorbPercentDamage[0]) { - int32 mana_reduced = damage * spellbonuses.ManaAbsorbPercentDamage[0] / 100; + if (spellbonuses.ManaAbsorbPercentDamage) { + int32 mana_reduced = damage * spellbonuses.ManaAbsorbPercentDamage / 100; if (GetMana() >= mana_reduced) { damage -= mana_reduced; SetMana(GetMana() - mana_reduced); - TryTriggerOnValueAmount(false, true); + TryTriggerOnCastRequirement(); + } + } + + if (spellbonuses.EnduranceAbsorbPercentDamage[SBIndex::ENDURANCE_ABSORD_MITIGIATION]) { + int32 damage_reduced = damage * spellbonuses.EnduranceAbsorbPercentDamage[SBIndex::ENDURANCE_ABSORD_MITIGIATION] / 10000; //If hit for 1000, at 10% then lower damage by 100; + int32 endurance_drain = damage_reduced * spellbonuses.EnduranceAbsorbPercentDamage[SBIndex::ENDURANCE_ABSORD_DRAIN_PER_HP] / 10000; //Reduce endurance by 0.05% per HP loss + if (endurance_drain < 1) + endurance_drain = 1; + + if (IsClient() && CastToClient()->GetEndurance() >= endurance_drain) { + damage -= damage_reduced; + CastToClient()->SetEndurance(CastToClient()->GetEndurance() - endurance_drain); + TryTriggerOnCastRequirement(); } } @@ -3302,23 +3454,42 @@ int32 Mob::ReduceAllDamage(int32 damage) bool Mob::HasProcs() const { - for (int i = 0; i < MAX_PROCS; i++) - if (PermaProcs[i].spellID != SPELL_UNKNOWN || SpellProcs[i].spellID != SPELL_UNKNOWN) + for (int i = 0; i < MAX_PROCS; i++) { + if (PermaProcs[i].spellID != SPELL_UNKNOWN || SpellProcs[i].spellID != SPELL_UNKNOWN) { return true; + } + } + + if (IsClient()) { + for (int i = 0; i < MAX_AA_PROCS; i += 4) { + if (aabonuses.SpellProc[i]) { + return true; + } + } + } return false; } bool Mob::HasDefensiveProcs() const { - for (int i = 0; i < MAX_PROCS; i++) - if (DefensiveProcs[i].spellID != SPELL_UNKNOWN) + for (int i = 0; i < MAX_PROCS; i++) { + if (DefensiveProcs[i].spellID != SPELL_UNKNOWN) { return true; + } + } + + if (IsClient()) { + for (int i = 0; i < MAX_AA_PROCS; i += 4) { + if (aabonuses.DefensiveProc[i]) { + return true; + } + } + } return false; } bool Mob::HasSkillProcs() const { - for (int i = 0; i < MAX_SKILL_PROCS; i++) { if (spellbonuses.SkillProc[i] || itembonuses.SkillProc[i] || aabonuses.SkillProc[i]) return true; @@ -3337,9 +3508,19 @@ bool Mob::HasSkillProcSuccess() const bool Mob::HasRangedProcs() const { - for (int i = 0; i < MAX_PROCS; i++) - if (RangedProcs[i].spellID != SPELL_UNKNOWN) + for (int i = 0; i < MAX_PROCS; i++){ + if (RangedProcs[i].spellID != SPELL_UNKNOWN) { return true; + } + } + + if (IsClient()) { + for (int i = 0; i < MAX_AA_PROCS; i += 4) { + if (aabonuses.RangedProc[i]) { + return true; + } + } + } return false; } @@ -3407,7 +3588,7 @@ void Mob::CommonDamage(Mob* attacker, int& damage, const uint16 spell_id, const bool FromDamageShield = (skill_used == EQ::skills::SkillAbjuration); bool ignore_invul = false; if (IsValidSpell(spell_id)) - ignore_invul = spell_id == 982 || spells[spell_id].cast_not_standing; // cazic touch + ignore_invul = spell_id == SPELL_CAZIC_TOUCH || spells[spell_id].cast_not_standing; if (!ignore_invul && (GetInvul() || DivineAura())) { LogCombat("Avoiding [{}] damage due to invulnerability", damage); @@ -3459,7 +3640,7 @@ void Mob::CommonDamage(Mob* attacker, int& damage, const uint16 spell_id, const if (spell_id != SPELL_UNKNOWN && IsLifetapSpell(spell_id)) { int healed = damage; - healed = attacker->GetActSpellHealing(spell_id, healed); + healed = RuleB(Spells, CompoundLifetapHeals) ? attacker->GetActSpellHealing(spell_id, healed) : healed; LogCombat("Applying lifetap heal of [{}] to [{}]", healed, attacker->GetName()); attacker->HealDamage(healed); @@ -3522,6 +3703,10 @@ void Mob::CommonDamage(Mob* attacker, int& damage, const uint16 spell_id, const } } + if (GetTempPetCount()) { + entity_list.AddTempPetsToHateListOnOwnerDamage(this, attacker, spell_id); + } + //see if any runes want to reduce this damage if (spell_id == SPELL_UNKNOWN) { damage = ReduceDamage(damage); @@ -3581,7 +3766,7 @@ void Mob::CommonDamage(Mob* attacker, int& damage, const uint16 spell_id, const TryDeathSave(); } - TryTriggerOnValueAmount(true); + TryTriggerOnCastRequirement(); //fade mez if we are mezzed if (IsMezzed() && attacker) { @@ -3678,7 +3863,7 @@ void Mob::CommonDamage(Mob* attacker, int& damage, const uint16 spell_id, const //send an HP update if we are hurt if (GetHP() < GetMaxHP()) - SendHPUpdate(!iBuffTic); // the OP_Damage actually updates the client in these cases, so we skip the HP update for them + SendHPUpdate(); // the OP_Damage actually updates the client in these cases, so we skip the HP update for them } //end `if damage was done` //send damage packet... @@ -4073,40 +4258,69 @@ void Mob::TryDefensiveProc(Mob *on, uint16 hand) { return; } - if (!HasDefensiveProcs()) + if (!HasDefensiveProcs()) { return; + } if (!on->HasDied() && on->GetHP() > 0) { float ProcChance, ProcBonus; on->GetDefensiveProcChances(ProcBonus, ProcChance, hand, this); - if (hand != EQ::invslot::slotPrimary) + if (hand == EQ::invslot::slotSecondary) { ProcChance /= 2; + } int level_penalty = 0; int level_diff = GetLevel() - on->GetLevel(); - if (level_diff > 6)//10% penalty per level if > 6 levels over target. + if (level_diff > 6) {//10% penalty per level if > 6 levels over target. level_penalty = (level_diff - 6) * 10; + } ProcChance -= ProcChance*level_penalty / 100; - if (ProcChance < 0) + if (ProcChance < 0) { return; + } + //Spell Procs and Quest added procs for (int i = 0; i < MAX_PROCS; i++) { if (IsValidSpell(DefensiveProcs[i].spellID)) { - float chance = ProcChance * (static_cast(DefensiveProcs[i].chance) / 100.0f); - if (zone->random.Roll(chance)) { - ExecWeaponProc(nullptr, DefensiveProcs[i].spellID, on); - CheckNumHitsRemaining(NumHit::DefensiveSpellProcs, 0, DefensiveProcs[i].base_spellID); + if (!IsProcLimitTimerActive(DefensiveProcs[i].base_spellID, DefensiveProcs[i].proc_reuse_time, ProcType::DEFENSIVE_PROC)) { + float chance = ProcChance * (static_cast(DefensiveProcs[i].chance) / 100.0f); + if (zone->random.Roll(chance)) { + ExecWeaponProc(nullptr, DefensiveProcs[i].spellID, on); + CheckNumHitsRemaining(NumHit::DefensiveSpellProcs, 0, DefensiveProcs[i].base_spellID); + SetProcLimitTimer(DefensiveProcs[i].base_spellID, DefensiveProcs[i].proc_reuse_time, ProcType::DEFENSIVE_PROC); + } + } + } + } + + //AA Procs + if (IsClient()){ + for (int i = 0; i < MAX_AA_PROCS; i += 4) { + int32 aa_rank_id = aabonuses.DefensiveProc[i + +SBIndex::COMBAT_PROC_ORIGIN_ID]; + int32 aa_spell_id = aabonuses.DefensiveProc[i + SBIndex::COMBAT_PROC_SPELL_ID]; + int32 aa_proc_chance = 100 + aabonuses.DefensiveProc[i + SBIndex::COMBAT_PROC_RATE_MOD]; + uint32 aa_proc_reuse_timer = aabonuses.DefensiveProc[i + SBIndex::COMBAT_PROC_REUSE_TIMER]; + + if (aa_rank_id) { + if (!IsProcLimitTimerActive(-aa_rank_id, aa_proc_reuse_timer, ProcType::DEFENSIVE_PROC)) { + float chance = ProcChance * (static_cast(aa_proc_chance) / 100.0f); + if (zone->random.Roll(chance) && IsValidSpell(aa_spell_id)) { + ExecWeaponProc(nullptr, aa_spell_id, on); + SetProcLimitTimer(-aa_rank_id, aa_proc_reuse_timer, ProcType::DEFENSIVE_PROC); + } + } } } } } } -void Mob::TryWeaponProc(const EQ::ItemInstance* weapon_g, Mob *on, uint16 hand) { +void Mob::TryCombatProcs(const EQ::ItemInstance* weapon_g, Mob *on, uint16 hand, const EQ::ItemData* weapon_data) { + if (!on) { SetTarget(nullptr); LogError("A null Mob object was passed to Mob::TryWeaponProc for evaluation!"); @@ -4123,6 +4337,13 @@ void Mob::TryWeaponProc(const EQ::ItemInstance* weapon_g, Mob *on, uint16 hand) return; } + //used for special case when checking last ammo item on projectile hit. + if (!weapon_g && weapon_data) { + TryWeaponProc(nullptr, weapon_data, on, hand); + TrySpellProc(nullptr, weapon_data, on, hand); + return; + } + if (!weapon_g) { TrySpellProc(nullptr, (const EQ::ItemData*)nullptr, on); return; @@ -4144,7 +4365,9 @@ void Mob::TryWeaponProc(const EQ::ItemInstance* weapon_g, Mob *on, uint16 hand) void Mob::TryWeaponProc(const EQ::ItemInstance *inst, const EQ::ItemData *weapon, Mob *on, uint16 hand) { - + if (!on) { + return; + } if (!weapon) return; uint16 skillinuse = 28; @@ -4154,13 +4377,13 @@ void Mob::TryWeaponProc(const EQ::ItemInstance *inst, const EQ::ItemData *weapon ProcBonus += static_cast(itembonuses.ProcChance) / 10.0f; // Combat Effects float ProcChance = GetProcChances(ProcBonus, hand); - if (hand != EQ::invslot::slotPrimary) //Is Archery intened to proc at 50% rate? + if (hand == EQ::invslot::slotSecondary) ProcChance /= 2; // Try innate proc on weapon // We can proc once here, either weapon or one aug bool proced = false; // silly bool to prevent augs from going if weapon does - skillinuse = GetSkillByItemType(weapon->ItemType); + if (weapon->Proc.Type == EQ::item::ItemEffectCombatProc && IsValidSpell(weapon->Proc.Effect)) { float WPC = ProcChance * (100.0f + // Proc chance for this weapon static_cast(weapon->ProcRate)) / 100.0f; @@ -4228,13 +4451,25 @@ void Mob::TryWeaponProc(const EQ::ItemInstance *inst, const EQ::ItemData *weapon void Mob::TrySpellProc(const EQ::ItemInstance *inst, const EQ::ItemData *weapon, Mob *on, uint16 hand) { + if (!on) { + return; + } + float ProcBonus = static_cast(spellbonuses.SpellProcChance + itembonuses.SpellProcChance + aabonuses.SpellProcChance); float ProcChance = 0.0f; ProcChance = GetProcChances(ProcBonus, hand); - if (hand != EQ::invslot::slotPrimary) //Is Archery intened to proc at 50% rate? + bool passed_skill_limit_check = true; + EQ::skills::SkillType skillinuse = EQ::skills::SkillHandtoHand; + + if (weapon){ + skillinuse = GetSkillByItemType(weapon->ItemType); + } + + if (hand == EQ::invslot::slotSecondary) { ProcChance /= 2; + } bool rangedattk = false; if (weapon && hand == EQ::invslot::slotRange) { @@ -4246,8 +4481,9 @@ void Mob::TrySpellProc(const EQ::ItemInstance *inst, const EQ::ItemData *weapon, } } - if (!weapon && hand == EQ::invslot::slotRange && GetSpecialAbility(SPECATK_RANGED_ATK)) + if (!weapon && hand == EQ::invslot::slotRange && GetSpecialAbility(SPECATK_RANGED_ATK)) { rangedattk = true; + } int16 poison_slot=-1; @@ -4256,12 +4492,13 @@ void Mob::TrySpellProc(const EQ::ItemInstance *inst, const EQ::ItemData *weapon, continue; // If pets ever can proc from off hand, this will need to change if (SpellProcs[i].base_spellID == POISON_PROC && - (!weapon || weapon->ItemType != EQ::item::ItemType1HPiercing)) + (!weapon || weapon->ItemType != EQ::item::ItemType1HPiercing)) { continue; // Old school poison will only proc with 1HP equipped. + } // Not ranged if (!rangedattk) { - // Perma procs (AAs) + // Perma procs (Not used for AA, they are handled below) if (PermaProcs[i].spellID != SPELL_UNKNOWN) { if (zone->random.Roll(PermaProcs[i].chance)) { // TODO: Do these get spell bonus? LogCombat("Permanent proc [{}] procing spell [{}] ([{}] percent chance)", i, PermaProcs[i].spellID, PermaProcs[i].chance); @@ -4279,31 +4516,85 @@ void Mob::TrySpellProc(const EQ::ItemInstance *inst, const EQ::ItemData *weapon, continue; // Process the poison proc last per @mackal } - float chance = ProcChance * (static_cast(SpellProcs[i].chance) / 100.0f); - if (zone->random.Roll(chance)) { - LogCombat("Spell proc [{}] procing spell [{}] ([{}] percent chance)", i, SpellProcs[i].spellID, chance); - SendBeginCast(SpellProcs[i].spellID, 0); - ExecWeaponProc(nullptr, SpellProcs[i].spellID, on, SpellProcs[i].level_override); - CheckNumHitsRemaining(NumHit::OffensiveSpellProcs, 0, - SpellProcs[i].base_spellID); - } - else { - LogCombat("Spell proc [{}] failed to proc [{}] ([{}] percent chance)", i, SpellProcs[i].spellID, chance); + passed_skill_limit_check = PassLimitToSkill(skillinuse, SpellProcs[i].base_spellID, ProcType::MELEE_PROC); + + if (passed_skill_limit_check && !IsProcLimitTimerActive(SpellProcs[i].base_spellID, SpellProcs[i].proc_reuse_time, ProcType::MELEE_PROC)) { + float chance = ProcChance * (static_cast(SpellProcs[i].chance) / 100.0f); + if (zone->random.Roll(chance)) { + LogCombat("Spell proc [{}] procing spell [{}] ([{}] percent chance)", i, SpellProcs[i].spellID, chance); + SendBeginCast(SpellProcs[i].spellID, 0); + ExecWeaponProc(nullptr, SpellProcs[i].spellID, on, SpellProcs[i].level_override); + SetProcLimitTimer(SpellProcs[i].base_spellID, SpellProcs[i].proc_reuse_time, ProcType::MELEE_PROC); + CheckNumHitsRemaining(NumHit::OffensiveSpellProcs, 0, SpellProcs[i].base_spellID); + } + else { + LogCombat("Spell proc [{}] failed to proc [{}] ([{}] percent chance)", i, SpellProcs[i].spellID, chance); + } } } } else if (rangedattk) { // ranged only // ranged spell procs (buffs) if (RangedProcs[i].spellID != SPELL_UNKNOWN) { - float chance = ProcChance * (static_cast(RangedProcs[i].chance) / 100.0f); - if (zone->random.Roll(chance)) { - LogCombat("Ranged proc [{}] procing spell [{}] ([{}] percent chance)", i, RangedProcs[i].spellID, chance); - ExecWeaponProc(nullptr, RangedProcs[i].spellID, on); - CheckNumHitsRemaining(NumHit::OffensiveSpellProcs, 0, - RangedProcs[i].base_spellID); + + passed_skill_limit_check = PassLimitToSkill(skillinuse, RangedProcs[i].base_spellID, ProcType::RANGED_PROC); + + if (passed_skill_limit_check && !IsProcLimitTimerActive(RangedProcs[i].base_spellID, RangedProcs[i].proc_reuse_time, ProcType::RANGED_PROC)) { + float chance = ProcChance * (static_cast(RangedProcs[i].chance) / 100.0f); + if (zone->random.Roll(chance)) { + LogCombat("Ranged proc [{}] procing spell [{}] ([{}] percent chance)", i, RangedProcs[i].spellID, chance); + ExecWeaponProc(nullptr, RangedProcs[i].spellID, on); + CheckNumHitsRemaining(NumHit::OffensiveSpellProcs, 0, RangedProcs[i].base_spellID); + SetProcLimitTimer(RangedProcs[i].base_spellID, RangedProcs[i].proc_reuse_time, ProcType::RANGED_PROC); + } + else { + LogCombat("Ranged proc [{}] failed to proc [{}] ([{}] percent chance)", i, RangedProcs[i].spellID, chance); + } } - else { - LogCombat("Ranged proc [{}] failed to proc [{}] ([{}] percent chance)", i, RangedProcs[i].spellID, chance); + } + } + } + + //AA Melee and Ranged Procs + if (IsClient()) { + for (int i = 0; i < MAX_AA_PROCS; i += 4) { + + int32 aa_rank_id = 0; + int32 aa_spell_id = SPELL_UNKNOWN; + int32 aa_proc_chance = 100; + uint32 aa_proc_reuse_timer = 0; + int proc_type = 0; //used to deterimne which timer array is used. + + if (!rangedattk) { + + aa_rank_id = aabonuses.SpellProc[i + SBIndex::COMBAT_PROC_ORIGIN_ID]; + aa_spell_id = aabonuses.SpellProc[i + SBIndex::COMBAT_PROC_SPELL_ID]; + aa_proc_chance += aabonuses.SpellProc[i + SBIndex::COMBAT_PROC_RATE_MOD]; + aa_proc_reuse_timer = aabonuses.SpellProc[i + SBIndex::COMBAT_PROC_RATE_MOD]; + proc_type = ProcType::MELEE_PROC; + } + else { + aa_rank_id = aabonuses.RangedProc[i + SBIndex::COMBAT_PROC_ORIGIN_ID]; + aa_spell_id = aabonuses.RangedProc[i + SBIndex::COMBAT_PROC_SPELL_ID]; + aa_proc_chance += aabonuses.RangedProc[i + SBIndex::COMBAT_PROC_RATE_MOD]; + aa_proc_reuse_timer = aabonuses.RangedProc[i + SBIndex::COMBAT_PROC_RATE_MOD]; + proc_type = ProcType::RANGED_PROC; + } + + if (aa_rank_id) { + + passed_skill_limit_check = PassLimitToSkill(skillinuse, 0, proc_type, aa_rank_id); + + if (passed_skill_limit_check && !IsProcLimitTimerActive(-aa_rank_id, aa_proc_reuse_timer, proc_type)) { + float chance = ProcChance * (static_cast(aa_proc_chance) / 100.0f); + if (zone->random.Roll(chance) && IsValidSpell(aa_spell_id)) { + LogCombat("AA proc [{}] procing spell [{}] ([{}] percent chance)", aa_rank_id, aa_spell_id, chance); + ExecWeaponProc(nullptr, aa_spell_id, on); + SetProcLimitTimer(-aa_rank_id, aa_proc_reuse_timer, proc_type); + } + else { + LogCombat("AA proc [{}] failed to proc [{}] ([{}] percent chance)", aa_rank_id, aa_spell_id, chance); + } } } } @@ -4324,14 +4615,15 @@ void Mob::TrySpellProc(const EQ::ItemInstance *inst, const EQ::ItemData *weapon, } } - if (HasSkillProcs() && hand != EQ::invslot::slotRange) { //We check ranged skill procs within the attack functions. - uint16 skillinuse = 28; - if (weapon) - skillinuse = GetSkillByItemType(weapon->ItemType); + TryCastOnSkillUse(on, skillinuse); + if (HasSkillProcs() && hand != EQ::invslot::slotRange) { //We check ranged skill procs within the attack functions. TrySkillProc(on, skillinuse, 0, false, hand); } + if (HasSkillProcSuccess() && hand != EQ::invslot::slotRange) { //We check ranged skill procs within the attack functions. + TrySkillProc(on, skillinuse, 0, true, hand); + } return; } @@ -4369,7 +4661,7 @@ void Mob::TryPetCriticalHit(Mob *defender, DamageHitInfo &hit) if (critChance > 0) { if (zone->random.Roll(critChance)) { - critMod += GetCritDmgMod(hit.skill); + critMod += GetCritDmgMod(hit.skill, owner); hit.damage_done += 5; hit.damage_done = (hit.damage_done * critMod) / 100; @@ -4422,12 +4714,12 @@ void Mob::TryCriticalHit(Mob *defender, DamageHitInfo &hit, ExtraAttackOptions * // 1: Try Slay Undead if (defender->GetBodyType() == BT_Undead || defender->GetBodyType() == BT_SummonedUndead || defender->GetBodyType() == BT_Vampire) { - int SlayRateBonus = aabonuses.SlayUndead[0] + itembonuses.SlayUndead[0] + spellbonuses.SlayUndead[0]; + int SlayRateBonus = aabonuses.SlayUndead[SBIndex::SLAYUNDEAD_RATE_MOD] + itembonuses.SlayUndead[SBIndex::SLAYUNDEAD_RATE_MOD] + spellbonuses.SlayUndead[SBIndex::SLAYUNDEAD_RATE_MOD]; if (SlayRateBonus) { float slayChance = static_cast(SlayRateBonus) / 10000.0f; if (zone->random.Roll(slayChance)) { int SlayDmgBonus = std::max( - { aabonuses.SlayUndead[1], itembonuses.SlayUndead[1], spellbonuses.SlayUndead[1] }); + {aabonuses.SlayUndead[SBIndex::SLAYUNDEAD_DMG_MOD], itembonuses.SlayUndead[SBIndex::SLAYUNDEAD_DMG_MOD], spellbonuses.SlayUndead[SBIndex::SLAYUNDEAD_DMG_MOD] }); hit.damage_done = std::max(hit.damage_done, hit.base_damage) + 5; hit.damage_done = (hit.damage_done * SlayDmgBonus) / 100; @@ -4594,22 +4886,27 @@ void Mob::TryCriticalHit(Mob *defender, DamageHitInfo &hit, ExtraAttackOptions * bool Mob::TryFinishingBlow(Mob *defender, int &damage) { - // base2 of FinishingBlowLvl is the HP limit (cur / max) * 1000, 10% is listed as 100 - if (defender && !defender->IsClient() && defender->GetHPRatio() < 10) { + float hp_limit = 10.0f; + auto fb_hp_limit = std::max({ aabonuses.FinishingBlowLvl[SBIndex::FINISHING_BLOW_LEVEL_HP_RATIO], spellbonuses.FinishingBlowLvl[SBIndex::FINISHING_BLOW_LEVEL_HP_RATIO], itembonuses.FinishingBlowLvl[SBIndex::FINISHING_BLOW_LEVEL_HP_RATIO] }); + + if (fb_hp_limit) { + hp_limit = fb_hp_limit/10.0f; + } + if (defender && !defender->IsClient() && defender->GetHPRatio() < hp_limit) { uint32 FB_Dmg = - aabonuses.FinishingBlow[1] + spellbonuses.FinishingBlow[1] + itembonuses.FinishingBlow[1]; + aabonuses.FinishingBlow[SBIndex::FINISHING_EFFECT_DMG] + spellbonuses.FinishingBlow[SBIndex::FINISHING_EFFECT_DMG] + itembonuses.FinishingBlow[SBIndex::FINISHING_EFFECT_DMG]; uint32 FB_Level = 0; - FB_Level = aabonuses.FinishingBlowLvl[0]; - if (FB_Level < spellbonuses.FinishingBlowLvl[0]) - FB_Level = spellbonuses.FinishingBlowLvl[0]; - else if (FB_Level < itembonuses.FinishingBlowLvl[0]) - FB_Level = itembonuses.FinishingBlowLvl[0]; + FB_Level = aabonuses.FinishingBlowLvl[SBIndex::FINISHING_EFFECT_LEVEL_MAX]; + if (FB_Level < spellbonuses.FinishingBlowLvl[SBIndex::FINISHING_EFFECT_LEVEL_MAX]) + FB_Level = spellbonuses.FinishingBlowLvl[SBIndex::FINISHING_EFFECT_LEVEL_MAX]; + else if (FB_Level < itembonuses.FinishingBlowLvl[SBIndex::FINISHING_EFFECT_LEVEL_MAX]) + FB_Level = itembonuses.FinishingBlowLvl[SBIndex::FINISHING_EFFECT_LEVEL_MAX]; // modern AA description says rank 1 (500) is 50% chance int ProcChance = - aabonuses.FinishingBlow[0] + spellbonuses.FinishingBlow[0] + spellbonuses.FinishingBlow[0]; + aabonuses.FinishingBlow[SBIndex::FINISHING_EFFECT_PROC_CHANCE] + spellbonuses.FinishingBlow[SBIndex::FINISHING_EFFECT_PROC_CHANCE] + spellbonuses.FinishingBlow[SBIndex::FINISHING_EFFECT_PROC_CHANCE]; if (FB_Level && FB_Dmg && (defender->GetLevel() <= FB_Level) && (ProcChance >= zone->random.Int(1, 1000))) { @@ -4646,6 +4943,7 @@ void Mob::DoRiposte(Mob *defender) } defender->Attack(this, EQ::invslot::slotPrimary, true); + if (HasDied()) return; @@ -4660,8 +4958,8 @@ void Mob::DoRiposte(Mob *defender) return; } - DoubleRipChance = defender->aabonuses.GiveDoubleRiposte[0] + defender->spellbonuses.GiveDoubleRiposte[0] + - defender->itembonuses.GiveDoubleRiposte[0]; + DoubleRipChance = defender->aabonuses.GiveDoubleRiposte[SBIndex::DOUBLE_RIPOSTE_CHANCE] + defender->spellbonuses.GiveDoubleRiposte[SBIndex::DOUBLE_RIPOSTE_CHANCE] + + defender->itembonuses.GiveDoubleRiposte[SBIndex::DOUBLE_RIPOSTE_CHANCE]; // Live AA - Double Riposte if (DoubleRipChance && zone->random.Roll(DoubleRipChance)) { @@ -4674,15 +4972,15 @@ void Mob::DoRiposte(Mob *defender) // Double Riposte effect, allows for a chance to do RIPOSTE with a skill specific special attack (ie Return Kick). // Coded narrowly: Limit to one per client. Limit AA only. [1 = Skill Attack Chance, 2 = Skill] - DoubleRipChance = defender->aabonuses.GiveDoubleRiposte[1]; + DoubleRipChance = defender->aabonuses.GiveDoubleRiposte[SBIndex::DOUBLE_RIPOSTE_SKILL_ATK_CHANCE]; if (DoubleRipChance && zone->random.Roll(DoubleRipChance)) { LogCombat("Preforming a return SPECIAL ATTACK ([{}] percent chance)", DoubleRipChance); if (defender->GetClass() == MONK) - defender->MonkSpecialAttack(this, defender->aabonuses.GiveDoubleRiposte[2]); + defender->MonkSpecialAttack(this, defender->aabonuses.GiveDoubleRiposte[SBIndex::DOUBLE_RIPOSTE_SKILL]); else if (defender->IsClient()) // so yeah, even if you don't have the skill you can still do the attack :P (and we don't crash anymore) - defender->CastToClient()->DoClassAttacks(this, defender->aabonuses.GiveDoubleRiposte[2], true); + defender->CastToClient()->DoClassAttacks(this, defender->aabonuses.GiveDoubleRiposte[SBIndex::DOUBLE_RIPOSTE_SKILL], true); } } @@ -4691,6 +4989,7 @@ void Mob::ApplyMeleeDamageMods(uint16 skill, int &damage, Mob *defender, ExtraAt int dmgbonusmod = 0; dmgbonusmod += GetMeleeDamageMod_SE(skill); + dmgbonusmod += GetMeleeDmgPositionMod(defender); if (opts) dmgbonusmod += opts->melee_damage_bonus_flat; @@ -4895,7 +5194,7 @@ void Mob::ApplyDamageTable(DamageHitInfo &hit) Log(Logs::Detail, Logs::Attack, "Damage table applied %d (max %d)", percent, damage_table.max_extra); } -void Mob::TrySkillProc(Mob *on, uint16 skill, uint16 ReuseTime, bool Success, uint16 hand, bool IsDefensive) +void Mob::TrySkillProc(Mob *on, EQ::skills::SkillType skill, uint16 ReuseTime, bool Success, uint16 hand, bool IsDefensive) { if (!on) { @@ -4904,12 +5203,19 @@ void Mob::TrySkillProc(Mob *on, uint16 skill, uint16 ReuseTime, bool Success, ui return; } - if (!spellbonuses.LimitToSkill[skill] && !itembonuses.LimitToSkill[skill] && !aabonuses.LimitToSkill[skill]) + if (on->HasDied()) { return; + } - /*Allow one proc from each (Spell/Item/AA) - Kayen: Due to limited avialability of effects on live it is too difficult - to confirm how they stack at this time, will adjust formula when more data is avialablle to test.*/ + if (!spellbonuses.LimitToSkill[skill] && !itembonuses.LimitToSkill[skill] && !aabonuses.LimitToSkill[skill]) { + return; + } + + /* + Allow one proc from each (Spell/Item/AA) + Kayen: Due to limited avialability of effects on live it is too difficult + to confirm how they stack at this time, will adjust formula when more data is avialablle to test. + */ bool CanProc = true; uint16 base_spell_id = 0; @@ -4924,34 +5230,35 @@ void Mob::TrySkillProc(Mob *on, uint16 skill, uint16 ReuseTime, bool Success, ui if (spellbonuses.LimitToSkill[skill]) { - for (int e = 0; e < MAX_SKILL_PROCS; e++) { + for (int i = 0; i < MAX_SKILL_PROCS; i++) { if (CanProc && - ((!Success && spellbonuses.SkillProc[e] && IsValidSpell(spellbonuses.SkillProc[e])) - || (Success && spellbonuses.SkillProcSuccess[e] && IsValidSpell(spellbonuses.SkillProcSuccess[e])))) { + ((!Success && spellbonuses.SkillProc[i] && IsValidSpell(spellbonuses.SkillProc[i])) + || (Success && spellbonuses.SkillProcSuccess[i] && IsValidSpell(spellbonuses.SkillProcSuccess[i])))) { - if (Success) - base_spell_id = spellbonuses.SkillProcSuccess[e]; - else - base_spell_id = spellbonuses.SkillProc[e]; + if (Success) { + base_spell_id = spellbonuses.SkillProcSuccess[i]; + } + else { + base_spell_id = spellbonuses.SkillProc[i]; + } proc_spell_id = 0; ProcMod = 0; for (int i = 0; i < EFFECT_COUNT; i++) { - if (spells[base_spell_id].effectid[i] == SE_SkillProc || spells[base_spell_id].effectid[i] == SE_SkillProcSuccess) { - proc_spell_id = spells[base_spell_id].base[i]; - ProcMod = static_cast(spells[base_spell_id].base2[i]); + if (spells[base_spell_id].effect_id[i] == SE_SkillProcAttempt || spells[base_spell_id].effect_id[i] == SE_SkillProcSuccess) { + proc_spell_id = spells[base_spell_id].base_value[i]; + ProcMod = static_cast(spells[base_spell_id].limit_value[i]); } - else if (spells[base_spell_id].effectid[i] == SE_LimitToSkill && spells[base_spell_id].base[i] <= EQ::skills::HIGHEST_SKILL) { + else if (spells[base_spell_id].effect_id[i] == SE_LimitToSkill && spells[base_spell_id].base_value[i] <= EQ::skills::HIGHEST_SKILL) { - if (CanProc && spells[base_spell_id].base[i] == skill && IsValidSpell(proc_spell_id)) { + if (CanProc && spells[base_spell_id].base_value[i] == skill && IsValidSpell(proc_spell_id)) { float final_chance = chance * (ProcMod / 100.0f); if (zone->random.Roll(final_chance)) { ExecWeaponProc(nullptr, proc_spell_id, on); - CheckNumHitsRemaining(NumHit::OffensiveSpellProcs, 0, - base_spell_id); + CheckNumHitsRemaining(NumHit::OffensiveSpellProcs, 0, base_spell_id); CanProc = false; break; } @@ -4969,28 +5276,30 @@ void Mob::TrySkillProc(Mob *on, uint16 skill, uint16 ReuseTime, bool Success, ui if (itembonuses.LimitToSkill[skill]) { CanProc = true; - for (int e = 0; e < MAX_SKILL_PROCS; e++) { + for (int i = 0; i < MAX_SKILL_PROCS; i++) { if (CanProc && - ((!Success && itembonuses.SkillProc[e] && IsValidSpell(itembonuses.SkillProc[e])) - || (Success && itembonuses.SkillProcSuccess[e] && IsValidSpell(itembonuses.SkillProcSuccess[e])))) { + ((!Success && itembonuses.SkillProc[i] && IsValidSpell(itembonuses.SkillProc[i])) + || (Success && itembonuses.SkillProcSuccess[i] && IsValidSpell(itembonuses.SkillProcSuccess[i])))) { - if (Success) - base_spell_id = itembonuses.SkillProcSuccess[e]; - else - base_spell_id = itembonuses.SkillProc[e]; + if (Success) { + base_spell_id = itembonuses.SkillProcSuccess[i]; + } + else { + base_spell_id = itembonuses.SkillProc[i]; + } proc_spell_id = 0; ProcMod = 0; for (int i = 0; i < EFFECT_COUNT; i++) { - if (spells[base_spell_id].effectid[i] == SE_SkillProc || spells[base_spell_id].effectid[i] == SE_SkillProcSuccess) { - proc_spell_id = spells[base_spell_id].base[i]; - ProcMod = static_cast(spells[base_spell_id].base2[i]); + if (spells[base_spell_id].effect_id[i] == SE_SkillProcAttempt || spells[base_spell_id].effect_id[i] == SE_SkillProcSuccess) { + proc_spell_id = spells[base_spell_id].base_value[i]; + ProcMod = static_cast(spells[base_spell_id].limit_value[i]); } - else if (spells[base_spell_id].effectid[i] == SE_LimitToSkill && spells[base_spell_id].base[i] <= EQ::skills::HIGHEST_SKILL) { + else if (spells[base_spell_id].effect_id[i] == SE_LimitToSkill && spells[base_spell_id].base_value[i] <= EQ::skills::HIGHEST_SKILL) { - if (CanProc && spells[base_spell_id].base[i] == skill && IsValidSpell(proc_spell_id)) { + if (CanProc && spells[base_spell_id].base_value[i] == skill && IsValidSpell(proc_spell_id)) { float final_chance = chance * (ProcMod / 100.0f); if (zone->random.Roll(final_chance)) { ExecWeaponProc(nullptr, proc_spell_id, on); @@ -5012,20 +5321,20 @@ void Mob::TrySkillProc(Mob *on, uint16 skill, uint16 ReuseTime, bool Success, ui CanProc = true; uint32 effect_id = 0; - int32 base1 = 0; - int32 base2 = 0; + int32 base_value = 0; + int32 limit_value = 0; uint32 slot = 0; - for (int e = 0; e < MAX_SKILL_PROCS; e++) { + for (int i = 0; i < MAX_SKILL_PROCS; i++) { if (CanProc && - ((!Success && aabonuses.SkillProc[e]) - || (Success && aabonuses.SkillProcSuccess[e]))) { + ((!Success && aabonuses.SkillProc[i]) + || (Success && aabonuses.SkillProcSuccess[i]))) { int aaid = 0; if (Success) - base_spell_id = aabonuses.SkillProcSuccess[e]; + base_spell_id = aabonuses.SkillProcSuccess[i]; else - base_spell_id = aabonuses.SkillProc[e]; + base_spell_id = aabonuses.SkillProc[i]; proc_spell_id = 0; ProcMod = 0; @@ -5041,17 +5350,17 @@ void Mob::TrySkillProc(Mob *on, uint16 skill, uint16 ReuseTime, bool Success, ui for (auto &effect : rank->effects) { effect_id = effect.effect_id; - base1 = effect.base1; - base2 = effect.base2; + base_value = effect.base_value; + limit_value = effect.limit_value; slot = effect.slot; - if (effect_id == SE_SkillProc || effect_id == SE_SkillProcSuccess) { - proc_spell_id = base1; - ProcMod = static_cast(base2); + if (effect_id == SE_SkillProcAttempt || effect_id == SE_SkillProcSuccess) { + proc_spell_id = base_value; + ProcMod = static_cast(limit_value); } - else if (effect_id == SE_LimitToSkill && base1 <= EQ::skills::HIGHEST_SKILL) { + else if (effect_id == SE_LimitToSkill && base_value <= EQ::skills::HIGHEST_SKILL) { - if (CanProc && base1 == skill && IsValidSpell(proc_spell_id)) { + if (CanProc && base_value == skill && IsValidSpell(proc_spell_id)) { float final_chance = chance * (ProcMod / 100.0f); if (zone->random.Roll(final_chance)) { @@ -5080,16 +5389,65 @@ float Mob::GetSkillProcChances(uint16 ReuseTime, uint16 hand) { if (!ReuseTime && hand) { weapon_speed = GetWeaponSpeedbyHand(hand); ProcChance = static_cast(weapon_speed) * (RuleR(Combat, AvgProcsPerMinute) / 60000.0f); - if (hand != EQ::invslot::slotPrimary) + if (hand == EQ::invslot::slotSecondary) { ProcChance /= 2; + } } - else + else { ProcChance = static_cast(ReuseTime) * (RuleR(Combat, AvgProcsPerMinute) / 60000.0f); + } return ProcChance; } +void Mob::TryCastOnSkillUse(Mob *on, EQ::skills::SkillType skill) { + + if (!spellbonuses.HasSkillAttackProc[skill] && !itembonuses.HasSkillAttackProc[skill] && !aabonuses.HasSkillAttackProc[skill]) { + return; + } + + if (!on) { + SetTarget(nullptr); + LogError("A null Mob object was passed to Mob::TryCastOnSkillUse for evaluation!"); + return; + } + + if (on->HasDied()) { + return; + } + + if (spellbonuses.HasSkillAttackProc[skill]) { + for (int i = 0; i < MAX_CAST_ON_SKILL_USE; i += 3) { + if (spellbonuses.SkillAttackProc[i + SBIndex::SKILLATK_PROC_SPELL_ID] && skill == spellbonuses.SkillAttackProc[i + SBIndex::SKILLATK_PROC_SKILL]) { + if (IsValidSpell(spellbonuses.SkillAttackProc[i + SBIndex::SKILLATK_PROC_SPELL_ID]) && zone->random.Int(1, 1000) <= spellbonuses.SkillAttackProc[i + SBIndex::SKILLATK_PROC_CHANCE]) { + SpellFinished(spellbonuses.SkillAttackProc[i + SBIndex::SKILLATK_PROC_SPELL_ID], on, EQ::spells::CastingSlot::Item, 0, -1, spells[spellbonuses.SkillAttackProc[i + SBIndex::SKILLATK_PROC_SPELL_ID]].resist_difficulty); + } + } + } + } + + if (itembonuses.HasSkillAttackProc[skill]) { + for (int i = 0; i < MAX_CAST_ON_SKILL_USE; i += 3) { + if (itembonuses.SkillAttackProc[i + SBIndex::SKILLATK_PROC_SPELL_ID] && skill == itembonuses.SkillAttackProc[i + SBIndex::SKILLATK_PROC_SKILL]) { + if (IsValidSpell(itembonuses.SkillAttackProc[i + SBIndex::SKILLATK_PROC_SPELL_ID]) && zone->random.Int(1, 1000) <= spellbonuses.SkillAttackProc[i + SBIndex::SKILLATK_PROC_CHANCE]) { + SpellFinished(itembonuses.SkillAttackProc[i + SBIndex::SKILLATK_PROC_SPELL_ID], on, EQ::spells::CastingSlot::Item, 0, -1, spells[itembonuses.SkillAttackProc[i + SBIndex::SKILLATK_PROC_SPELL_ID]].resist_difficulty); + } + } + } + } + + if (aabonuses.HasSkillAttackProc[skill]) { + for (int i = 0; i < MAX_CAST_ON_SKILL_USE; i += 3) { + if (IsValidSpell(aabonuses.SkillAttackProc[i + SBIndex::SKILLATK_PROC_SPELL_ID]) && aabonuses.SkillAttackProc[i + SBIndex::SKILLATK_PROC_SPELL_ID] && skill == aabonuses.SkillAttackProc[i + SBIndex::SKILLATK_PROC_SKILL]) { + if (zone->random.Int(1, 1000) <= aabonuses.SkillAttackProc[i + SBIndex::SKILLATK_PROC_CHANCE]) { + SpellFinished(aabonuses.SkillAttackProc[i + SBIndex::SKILLATK_PROC_SPELL_ID], on, EQ::spells::CastingSlot::Item, 0, -1, spells[aabonuses.SkillAttackProc[i + SBIndex::SKILLATK_PROC_SPELL_ID]].resist_difficulty); + } + } + } + } +} + bool Mob::TryRootFadeByDamage(int buffslot, Mob* attacker) { /*Dev Quote 2010: http://forums.station.sony.com/eq/posts/list.m?topic_id=161443 @@ -5104,33 +5462,36 @@ bool Mob::TryRootFadeByDamage(int buffslot, Mob* attacker) { - Root break chance values obtained from live parses. */ - if (!attacker || !spellbonuses.Root[0] || spellbonuses.Root[1] < 0) + if (!attacker || !spellbonuses.Root[SBIndex::ROOT_EXISTS] || spellbonuses.Root[SBIndex::ROOT_BUFFSLOT] < 0) { return false; + } + + if (IsDetrimentalSpell(buffs[spellbonuses.Root[SBIndex::ROOT_BUFFSLOT]].spellid) && spellbonuses.Root[SBIndex::ROOT_BUFFSLOT] != buffslot) { - if (IsDetrimentalSpell(spellbonuses.Root[1]) && spellbonuses.Root[1] != buffslot) { int BreakChance = RuleI(Spells, RootBreakFromSpells); - - BreakChance -= BreakChance*buffs[spellbonuses.Root[1]].RootBreakChance / 100; + BreakChance -= BreakChance * buffs[spellbonuses.Root[SBIndex::ROOT_BUFFSLOT]].RootBreakChance / 100; int level_diff = attacker->GetLevel() - GetLevel(); //Use baseline if level difference <= 1 (ie. If target is (1) level less than you, or equal or greater level) - if (level_diff == 2) + if (level_diff == 2) { BreakChance = (BreakChance * 80) / 100; //Decrease by 20%; - - else if (level_diff >= 3 && level_diff <= 20) + } + else if (level_diff >= 3 && level_diff <= 20) { BreakChance = (BreakChance * 60) / 100; //Decrease by 40%; - - else if (level_diff > 21) + } + else if (level_diff > 21) { BreakChance = (BreakChance * 20) / 100; //Decrease by 80%; + } - if (BreakChance < 1) + if (BreakChance < 1) { BreakChance = 1; + } if (zone->random.Roll(BreakChance)) { - if (!TryFadeEffect(spellbonuses.Root[1])) { - BuffFadeBySlot(spellbonuses.Root[1]); + if (!TryFadeEffect(spellbonuses.Root[SBIndex::ROOT_BUFFSLOT])) { + BuffFadeBySlot(spellbonuses.Root[SBIndex::ROOT_BUFFSLOT]); LogCombat("Spell broke root! BreakChance percent chance"); return true; } @@ -5146,7 +5507,7 @@ int32 Mob::RuneAbsorb(int32 damage, uint16 type) uint32 buff_max = GetMaxTotalSlots(); if (type == SE_Rune) { for (uint32 slot = 0; slot < buff_max; slot++) { - if (slot == spellbonuses.MeleeRune[1] && spellbonuses.MeleeRune[0] && buffs[slot].melee_rune && IsValidSpell(buffs[slot].spellid)) { + if (slot == spellbonuses.MeleeRune[SBIndex::RUNE_BUFFSLOT] && spellbonuses.MeleeRune[SBIndex::RUNE_AMOUNT] && buffs[slot].melee_rune && IsValidSpell(buffs[slot].spellid)) { int melee_rune_left = buffs[slot].melee_rune; if (melee_rune_left > damage) @@ -5170,7 +5531,7 @@ int32 Mob::RuneAbsorb(int32 damage, uint16 type) else { for (uint32 slot = 0; slot < buff_max; slot++) { - if (slot == spellbonuses.AbsorbMagicAtt[1] && spellbonuses.AbsorbMagicAtt[0] && buffs[slot].magic_rune && IsValidSpell(buffs[slot].spellid)) { + if (slot == spellbonuses.AbsorbMagicAtt[SBIndex::RUNE_BUFFSLOT] && spellbonuses.AbsorbMagicAtt[SBIndex::RUNE_AMOUNT] && buffs[slot].magic_rune && IsValidSpell(buffs[slot].spellid)) { int magic_rune_left = buffs[slot].magic_rune; if (magic_rune_left > damage) { @@ -5256,7 +5617,7 @@ void Mob::CommonOutgoingHitSuccess(Mob* defender, DamageHitInfo &hit, ExtraAttac // Seems the crit message is generated before some of them :P // worn item +skill dmg, SPA 220, 418. Live has a normalized version that should be here too - hit.min_damage += GetSkillDmgAmt(hit.skill); + hit.min_damage += GetSkillDmgAmt(hit.skill) + GetPositionalDmgAmt(defender); // shielding mod2 if (defender->itembonuses.MeleeMitigation) @@ -5291,24 +5652,78 @@ void Mob::CommonOutgoingHitSuccess(Mob* defender, DamageHitInfo &hit, ExtraAttac if (mod > 0) spec_mod = mod; if ((IsPet() || IsTempPet()) && IsPetOwnerClient()) { - int spell = spellbonuses.PC_Pet_Rampage[1] + itembonuses.PC_Pet_Rampage[1] + aabonuses.PC_Pet_Rampage[1]; - if (spell > spec_mod) - spec_mod = spell; + //SE_PC_Pet_Rampage SPA 464 on pet, damage modifier + int spell_mod = spellbonuses.PC_Pet_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] + itembonuses.PC_Pet_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] + aabonuses.PC_Pet_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD]; + if (spell_mod > spec_mod) + spec_mod = spell_mod; } } else if (IsSpecialAttack(eSpecialAttacks::AERampage)) { int mod = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 2); if (mod > 0) spec_mod = mod; + if ((IsPet() || IsTempPet()) && IsPetOwnerClient()) { + //SE_PC_Pet_AE_Rampage SPA 465 on pet, damage modifier + int spell_mod = spellbonuses.PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] + itembonuses.PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] + aabonuses.PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD]; + if (spell_mod > spec_mod) + spec_mod = spell_mod; + } } if (spec_mod > 0) hit.damage_done = (hit.damage_done * spec_mod) / 100; - hit.damage_done += (hit.damage_done * defender->GetSkillDmgTaken(hit.skill, opts) / 100) + (defender->GetFcDamageAmtIncoming(this, 0, true, hit.skill)); + int pct_damage_reduction = defender->GetSkillDmgTaken(hit.skill, opts) + defender->GetPositionalDmgTaken(this); + + hit.damage_done += (hit.damage_done * pct_damage_reduction / 100) + defender->GetPositionalDmgTakenAmt(this); + + if (defender->GetShielderID()) { + DoShieldDamageOnShielder(defender, hit.damage_done, hit.skill); + hit.damage_done -= hit.damage_done * defender->GetShieldTargetMitigation() / 100; //Default shielded takes 50 pct damage + } CheckNumHitsRemaining(NumHit::OutgoingHitSuccess); } +void Mob::DoShieldDamageOnShielder(Mob *shield_target, int hit_damage_done, EQ::skills::SkillType skillInUse) +{ + if (!shield_target) { + return; + } + + Mob *shielder = entity_list.GetMob(shield_target->GetShielderID()); + if (!shielder) { + shield_target->SetShielderID(0); + shield_target->SetShieldTargetMitigation(0); + return; + } + + if (shield_target->CalculateDistance(shielder->GetX(), shielder->GetY(), shielder->GetZ()) > static_cast(shielder->GetMaxShielderDistance())) { + shielder->SetShieldTargetID(0); + shielder->SetShielderMitigation(0); + shielder->SetShielderMaxDistance(0); + shielder->shield_timer.Disable(); + shield_target->SetShielderID(0); + shield_target->SetShieldTargetMitigation(0); + return; //Too far away, no message is given thoughh. + } + + int mitigation = shielder->GetShielderMitigation(); //Default shielder mitigates 25 pct of damage taken, this can be increased up to max 50 by equiping a shield item + if (shielder->IsClient() && shielder->HasShieldEquiped()) { + EQ::ItemInstance* inst = shielder->CastToClient()->GetInv().GetItem(EQ::invslot::slotSecondary); + if (inst) { + const EQ::ItemData* shield = inst->GetItem(); + if (shield && shield->ItemType == EQ::item::ItemTypeShield) { + mitigation += shield->AC * 50 / 100; //1% increase per 2 AC + std::min(50, mitigation);//50 pct max mitigation bonus from /shield + } + } + } + + hit_damage_done -= hit_damage_done * mitigation / 100; + shielder->Damage(this, hit_damage_done, SPELL_UNKNOWN, skillInUse, true, -1, false, m_specialattacks); + shielder->CheckNumHitsRemaining(NumHit::OutgoingHitSuccess); +} + void Mob::CommonBreakInvisibleFromCombat() { //break invis when you attack @@ -5367,6 +5782,8 @@ void Mob::SetAttackTimer() void Client::SetAttackTimer() { float haste_mod = GetHaste() * 0.01f; + int primary_speed = 0; + int secondary_speed = 0; //default value for attack timer in case they have //an invalid weapon equipped: @@ -5444,6 +5861,21 @@ void Client::SetAttackTimer() speed = static_cast(speed + ((hhe / 100.0f) * delay)); } TimerToUse->SetAtTrigger(std::max(RuleI(Combat, MinHastedDelay), speed), true, true); + + if (i == EQ::invslot::slotPrimary) { + primary_speed = speed; + } + else if (i == EQ::invslot::slotSecondary) { + secondary_speed = speed; + } + } + + //To allow for duel wield animation to display correctly if both weapons have same delay + if (primary_speed == secondary_speed) { + SetDualWieldingSameDelayWeapons(1); + } + else { + SetDualWieldingSameDelayWeapons(0); } } @@ -5512,12 +5944,39 @@ void Client::DoAttackRounds(Mob *target, int hand, bool IsFromSpell) if (CheckDoubleAttack()) { Attack(target, hand, false, false, IsFromSpell); - // Modern AA description: Increases your chance of ... performing one additional hit with a 2-handed weapon when double attacking by 2%. if (hand == EQ::invslot::slotPrimary) { - auto extraattackchance = aabonuses.ExtraAttackChance + spellbonuses.ExtraAttackChance + - itembonuses.ExtraAttackChance; - if (extraattackchance && HasTwoHanderEquipped() && zone->random.Roll(extraattackchance)) - Attack(target, hand, false, false, IsFromSpell); + + if (HasTwoHanderEquipped()) { + auto extraattackchance = aabonuses.ExtraAttackChance[SBIndex::EXTRA_ATTACK_CHANCE] + spellbonuses.ExtraAttackChance[SBIndex::EXTRA_ATTACK_CHANCE] + + itembonuses.ExtraAttackChance[SBIndex::EXTRA_ATTACK_CHANCE]; + if (extraattackchance && zone->random.Roll(extraattackchance)) { + auto extraattackamt = std::max({aabonuses.ExtraAttackChance[SBIndex::EXTRA_ATTACK_NUM_ATKS], spellbonuses.ExtraAttackChance[SBIndex::EXTRA_ATTACK_NUM_ATKS], itembonuses.ExtraAttackChance[SBIndex::EXTRA_ATTACK_NUM_ATKS] }); + for (int i = 0; i < extraattackamt; i++) { + Attack(target, hand, false, false, IsFromSpell); + } + } + } + else { + auto extraattackchance_primary = aabonuses.ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_CHANCE] + spellbonuses.ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_CHANCE] + + itembonuses.ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_CHANCE]; + if (extraattackchance_primary && zone->random.Roll(extraattackchance_primary)) { + auto extraattackamt_primary = std::max({aabonuses.ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_NUM_ATKS], spellbonuses.ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_NUM_ATKS], itembonuses.ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_NUM_ATKS] }); + for (int i = 0; i < extraattackamt_primary; i++) { + Attack(target, hand, false, false, IsFromSpell); + } + } + } + } + + if (hand == EQ::invslot::slotSecondary) { + auto extraattackchance_secondary = aabonuses.ExtraAttackChanceSecondary[SBIndex::EXTRA_ATTACK_CHANCE] + spellbonuses.ExtraAttackChanceSecondary[SBIndex::EXTRA_ATTACK_CHANCE] + + itembonuses.ExtraAttackChanceSecondary[SBIndex::EXTRA_ATTACK_CHANCE]; + if (extraattackchance_secondary && zone->random.Roll(extraattackchance_secondary)) { + auto extraattackamt_secondary = std::max({aabonuses.ExtraAttackChanceSecondary[SBIndex::EXTRA_ATTACK_NUM_ATKS], spellbonuses.ExtraAttackChanceSecondary[SBIndex::EXTRA_ATTACK_NUM_ATKS], itembonuses.ExtraAttackChanceSecondary[SBIndex::EXTRA_ATTACK_NUM_ATKS] }); + for (int i = 0; i < extraattackamt_secondary; i++) { + Attack(target, hand, false, false, IsFromSpell); + } + } } // you can only triple from the main hand @@ -5648,6 +6107,48 @@ void Mob::DoOffHandAttackRounds(Mob *target, ExtraAttackOptions *opts) } } + +int Mob::GetPetAvoidanceBonusFromOwner() +{ + Mob *owner = nullptr; + if (IsPet()) + owner = GetOwner(); + else if (IsNPC() && CastToNPC()->GetSwarmOwner()) + owner = entity_list.GetMobID(CastToNPC()->GetSwarmOwner()); + + if (owner) + return owner->aabonuses.PetAvoidance + owner->spellbonuses.PetAvoidance + owner->itembonuses.PetAvoidance; + + return 0; +} +int Mob::GetPetACBonusFromOwner() +{ + Mob *owner = nullptr; + if (IsPet()) + owner = GetOwner(); + else if (IsNPC() && CastToNPC()->GetSwarmOwner()) + owner = entity_list.GetMobID(CastToNPC()->GetSwarmOwner()); + + if (owner) + return owner->aabonuses.PetMeleeMitigation + owner->spellbonuses.PetMeleeMitigation + owner->itembonuses.PetMeleeMitigation; + + return 0; +} +int Mob::GetPetATKBonusFromOwner() +{ + Mob *owner = nullptr; + if (IsPet()) + owner = GetOwner(); + else if (IsNPC() && CastToNPC()->GetSwarmOwner()) + owner = entity_list.GetMobID(CastToNPC()->GetSwarmOwner()); + + if (owner) + return owner->aabonuses.Pet_Add_Atk + owner->spellbonuses.Pet_Add_Atk + owner->itembonuses.Pet_Add_Atk; + + return 0; +} + + bool Mob::GetWasSpawnedInWater() const { return spawned_in_water; } diff --git a/zone/beacon.cpp b/zone/beacon.cpp index 11b0de0fd..60b572dd2 100644 --- a/zone/beacon.cpp +++ b/zone/beacon.cpp @@ -98,7 +98,7 @@ bool Beacon::Process() { // NPCs should never be affected by an AE they cast. PB AEs shouldn't affect caster either // I don't think any other cases that get here matter - bool affect_caster = (!caster->IsNPC() && !caster->IsAIControlled()) && spells[spell_id].targettype != ST_AECaster; + bool affect_caster = (!caster->IsNPC() && !caster->IsAIControlled()) && spells[spell_id].target_type != ST_AECaster; entity_list.AESpell(caster, this, spell_id, affect_caster, resist_adjust, &max_targets); } else @@ -127,10 +127,10 @@ void Beacon::AELocationSpell(Mob *caster, uint16 cast_spell_id, int16 resist_adj caster_id = caster->GetID(); spell_id = cast_spell_id; this->resist_adjust = resist_adjust; - spell_iterations = spells[spell_id].AEDuration / 2500; + spell_iterations = spells[spell_id].aoe_duration / 2500; spell_iterations = spell_iterations < 1 ? 1 : spell_iterations; // at least 1 - if (spells[spell_id].aemaxtargets) - max_targets = spells[spell_id].aemaxtargets; + if (spells[spell_id].aoe_max_targets) + max_targets = spells[spell_id].aoe_max_targets; spell_timer.Start(2500); spell_timer.Trigger(); } diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index fc513577d..e799271aa 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -48,7 +48,7 @@ void Mob::CalcBonuses() SetAttackTimer(); CalcAC(); - /* Fast walking NPC's are prone to disappear into walls/hills + /* Fast walking NPC's are prone to disappear into walls/hills We set this here because NPC's can cast spells to change walkspeed/runspeed */ float get_walk_speed = static_cast(0.025f * this->GetWalkspeed()); @@ -154,6 +154,7 @@ void Client::CalcItemBonuses(StatBonuses* newbon) { SetShieldEquiped(false); SetTwoHandBluntEquiped(false); SetTwoHanderEquipped(false); + SetDuelWeaponsEquiped(false); unsigned int i; // Update: MainAmmo should only calc skill mods (TODO: Check for other cases) @@ -171,8 +172,13 @@ void Client::CalcItemBonuses(StatBonuses* newbon) { SetTwoHandBluntEquiped(true); SetTwoHanderEquipped(true); } - else if (i == EQ::invslot::slotPrimary && (item && (item->ItemType == EQ::item::ItemType2HSlash || item->ItemType == EQ::item::ItemType2HPiercing))) + else if (i == EQ::invslot::slotPrimary && (item && (item->ItemType == EQ::item::ItemType2HSlash || item->ItemType == EQ::item::ItemType2HPiercing))) { SetTwoHanderEquipped(true); + } + } + + if (CanThisClassDualWield()) { + SetDuelWeaponsEquiped(true); } //tribute items @@ -204,7 +210,7 @@ void Client::ProcessItemCaps() // The Sleeper Tomb Avatar proc counts towards item ATK // The client uses a 100 here, so using a 100 here the client and server will agree // For example, if you set the effect to be 200 it will get 100 item ATK and 100 spell ATK - if (IsValidSpell(2434) && FindBuff(2434)) { + if (IsValidSpell(SPELL_AVATAR_ST_PROC) && FindBuff(SPELL_AVATAR_ST_PROC)) { itembonuses.ATK += 100; spellbonuses.ATK -= 100; } @@ -232,7 +238,7 @@ void Client::AddItemBonuses(const EQ::ItemInstance *inst, StatBonuses *newbon, b if (GetLevel() < inst->GetItemRequiredLevel(true)) { return; } - + // So there isn't a very nice way to get the real rec level from the aug's inst, so we just pass it in, only // used for augs auto rec_level = isAug ? rec_override : inst->GetItemRecommendedLevel(true); @@ -476,7 +482,7 @@ void Client::AddItemBonuses(const EQ::ItemInstance *inst, StatBonuses *newbon, b newbon->percussionMod = item->BardValue; break; } - + // Add Item Faction Mods if (item->FactionMod1) { if (item->FactionAmt1 > 0 && item->FactionAmt1 > GetItemFactionBonus(item->FactionMod1)) { @@ -644,405 +650,404 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) return; uint32 effect = 0; - int32 base1 = 0; - int32 base2 = 0; // only really used for SE_RaiseStatCap & SE_ReduceSkillTimer in aa_effects table + int32 base_value = 0; + int32 limit_value = 0; // only really used for SE_RaiseStatCap & SE_ReduceSkillTimer in aa_effects table uint32 slot = 0; for (const auto &e : rank.effects) { effect = e.effect_id; - base1 = e.base1; - base2 = e.base2; + base_value = e.base_value; + limit_value = e.limit_value; slot = e.slot; // we default to 0 (SE_CurrentHP) for the effect, so if there aren't any base1/2 values, we'll just skip it - if (effect == 0 && base1 == 0 && base2 == 0) + if (effect == 0 && base_value == 0 && limit_value == 0) continue; // IsBlankSpellEffect() - if (effect == SE_Blank || (effect == SE_CHA && base1 == 0) || effect == SE_StackingCommand_Block || + if (effect == SE_Blank || (effect == SE_CHA && base_value == 0) || effect == SE_StackingCommand_Block || effect == SE_StackingCommand_Overwrite) continue; LogAA("Applying Effect [{}] from AA [{}] in slot [{}] (base1: [{}], base2: [{}]) on [{}]", - effect, rank.id, slot, base1, base2, GetCleanName()); + effect, rank.id, slot, base_value, limit_value, GetCleanName()); uint8 focus = IsFocusEffect(0, 0, true, effect); if (focus) { - newbon->FocusEffects[focus] = static_cast(effect); + newbon->FocusEffects[focus] = effect; continue; } switch (effect) { case SE_ACv2: case SE_ArmorClass: - newbon->AC += base1; + newbon->AC += base_value; break; // Note: AA effects that use accuracy are skill limited, while spell effect is not. case SE_Accuracy: // Bad data or unsupported new skill - if (base2 > EQ::skills::HIGHEST_SKILL) + if (limit_value > EQ::skills::HIGHEST_SKILL) break; - if ((base2 == ALL_SKILLS) && (newbon->Accuracy[EQ::skills::HIGHEST_SKILL + 1] < base1)) - newbon->Accuracy[EQ::skills::HIGHEST_SKILL + 1] = base1; - else if (newbon->Accuracy[base2] < base1) - newbon->Accuracy[base2] += base1; + if ((limit_value == ALL_SKILLS) && (newbon->Accuracy[EQ::skills::HIGHEST_SKILL + 1] < base_value)) + newbon->Accuracy[EQ::skills::HIGHEST_SKILL + 1] = base_value; + else if (newbon->Accuracy[limit_value] < base_value) + newbon->Accuracy[limit_value] += base_value; break; case SE_CurrentHP: // regens - newbon->HPRegen += base1; + newbon->HPRegen += base_value; break; case SE_CurrentEndurance: - newbon->EnduranceRegen += base1; + newbon->EnduranceRegen += base_value; break; case SE_MovementSpeed: - newbon->movementspeed += base1; // should we let these stack? + newbon->movementspeed += base_value; // should we let these stack? /*if (base1 > newbon->movementspeed) //or should we use a total value? newbon->movementspeed = base1;*/ break; case SE_STR: - newbon->STR += base1; + newbon->STR += base_value; break; case SE_DEX: - newbon->DEX += base1; + newbon->DEX += base_value; break; case SE_AGI: - newbon->AGI += base1; + newbon->AGI += base_value; break; case SE_STA: - newbon->STA += base1; + newbon->STA += base_value; break; case SE_INT: - newbon->INT += base1; + newbon->INT += base_value; break; case SE_WIS: - newbon->WIS += base1; + newbon->WIS += base_value; break; case SE_CHA: - newbon->CHA += base1; + newbon->CHA += base_value; break; case SE_WaterBreathing: // handled by client break; case SE_CurrentMana: - newbon->ManaRegen += base1; + newbon->ManaRegen += base_value; break; case SE_ManaPool: - newbon->Mana += base1; + newbon->Mana += base_value; break; case SE_ItemManaRegenCapIncrease: - newbon->ItemManaRegenCap += base1; + newbon->ItemManaRegenCap += base_value; break; case SE_ResistFire: - newbon->FR += base1; + newbon->FR += base_value; break; case SE_ResistCold: - newbon->CR += base1; + newbon->CR += base_value; break; case SE_ResistPoison: - newbon->PR += base1; + newbon->PR += base_value; break; case SE_ResistDisease: - newbon->DR += base1; + newbon->DR += base_value; break; case SE_ResistMagic: - newbon->MR += base1; + newbon->MR += base_value; break; case SE_ResistCorruption: - newbon->Corrup += base1; + newbon->Corrup += base_value; break; case SE_IncreaseSpellHaste: break; case SE_IncreaseRange: break; case SE_MaxHPChange: - newbon->MaxHP += base1; + newbon->MaxHP += base_value; break; case SE_Packrat: - newbon->Packrat += base1; + newbon->Packrat += base_value; break; case SE_TwoHandBash: break; case SE_SetBreathLevel: break; case SE_RaiseStatCap: - switch (base2) { + switch (limit_value) { // are these #define'd somewhere? case 0: // str - newbon->STRCapMod += base1; + newbon->STRCapMod += base_value; break; case 1: // sta - newbon->STACapMod += base1; + newbon->STACapMod += base_value; break; case 2: // agi - newbon->AGICapMod += base1; + newbon->AGICapMod += base_value; break; case 3: // dex - newbon->DEXCapMod += base1; + newbon->DEXCapMod += base_value; break; case 4: // wis - newbon->WISCapMod += base1; + newbon->WISCapMod += base_value; break; case 5: // int - newbon->INTCapMod += base1; + newbon->INTCapMod += base_value; break; case 6: // cha - newbon->CHACapMod += base1; + newbon->CHACapMod += base_value; break; case 7: // mr - newbon->MRCapMod += base1; + newbon->MRCapMod += base_value; break; case 8: // cr - newbon->CRCapMod += base1; + newbon->CRCapMod += base_value; break; case 9: // fr - newbon->FRCapMod += base1; + newbon->FRCapMod += base_value; break; case 10: // pr - newbon->PRCapMod += base1; + newbon->PRCapMod += base_value; break; case 11: // dr - newbon->DRCapMod += base1; + newbon->DRCapMod += base_value; break; case 12: // corruption - newbon->CorrupCapMod += base1; + newbon->CorrupCapMod += base_value; break; } break; case SE_SpellSlotIncrease: break; case SE_MysticalAttune: - newbon->BuffSlotIncrease += base1; + newbon->BuffSlotIncrease += base_value; break; case SE_TotalHP: - newbon->HP += base1; + newbon->HP += base_value; break; case SE_StunResist: - newbon->StunResist += base1; + newbon->StunResist += base_value; break; case SE_SpellCritChance: - newbon->CriticalSpellChance += base1; + newbon->CriticalSpellChance += base_value; break; case SE_SpellCritDmgIncrease: - newbon->SpellCritDmgIncrease += base1; + newbon->SpellCritDmgIncrease += base_value; break; case SE_DotCritDmgIncrease: - newbon->DotCritDmgIncrease += base1; + newbon->DotCritDmgIncrease += base_value; break; case SE_ResistSpellChance: - newbon->ResistSpellChance += base1; + newbon->ResistSpellChance += base_value; break; case SE_CriticalHealChance: - newbon->CriticalHealChance += base1; + newbon->CriticalHealChance += base_value; break; case SE_CriticalHealOverTime: - newbon->CriticalHealOverTime += base1; + newbon->CriticalHealOverTime += base_value; break; case SE_CriticalDoTChance: - newbon->CriticalDoTChance += base1; + newbon->CriticalDoTChance += base_value; break; case SE_ReduceSkillTimer: - newbon->SkillReuseTime[base2] += base1; + newbon->SkillReuseTime[limit_value] += base_value; break; case SE_Fearless: newbon->Fearless = true; break; case SE_PersistantCasting: - newbon->PersistantCasting += base1; + newbon->PersistantCasting += base_value; break; case SE_DelayDeath: - newbon->DelayDeath += base1; + newbon->DelayDeath += base_value; break; case SE_FrontalStunResist: - newbon->FrontalStunResist += base1; + newbon->FrontalStunResist += base_value; break; case SE_ImprovedBindWound: - newbon->BindWound += base1; + newbon->BindWound += base_value; break; case SE_MaxBindWound: - newbon->MaxBindWound += base1; - break; - case SE_ExtraAttackChance: - newbon->ExtraAttackChance += base1; + newbon->MaxBindWound += base_value; break; case SE_SeeInvis: - newbon->SeeInvis = base1; + newbon->SeeInvis = base_value; break; case SE_BaseMovementSpeed: - newbon->BaseMovementSpeed += base1; + newbon->BaseMovementSpeed += base_value; break; case SE_IncreaseRunSpeedCap: - newbon->IncreaseRunSpeedCap += base1; + newbon->IncreaseRunSpeedCap += base_value; break; case SE_ConsumeProjectile: - newbon->ConsumeProjectile += base1; + newbon->ConsumeProjectile += base_value; break; case SE_ForageAdditionalItems: - newbon->ForageAdditionalItems += base1; + newbon->ForageAdditionalItems += base_value; break; case SE_Salvage: - newbon->SalvageChance += base1; + newbon->SalvageChance += base_value; break; case SE_ArcheryDamageModifier: - newbon->ArcheryDamageModifier += base1; + newbon->ArcheryDamageModifier += base_value; break; case SE_DoubleRangedAttack: - newbon->DoubleRangedAttack += base1; + newbon->DoubleRangedAttack += base_value; break; case SE_DamageShield: - newbon->DamageShield += base1; + newbon->DamageShield += base_value; break; case SE_CharmBreakChance: - newbon->CharmBreakChance += base1; + newbon->CharmBreakChance += base_value; break; case SE_OffhandRiposteFail: - newbon->OffhandRiposteFail += base1; + newbon->OffhandRiposteFail += base_value; break; case SE_ItemAttackCapIncrease: - newbon->ItemATKCap += base1; + newbon->ItemATKCap += base_value; break; case SE_GivePetGroupTarget: newbon->GivePetGroupTarget = true; break; case SE_ItemHPRegenCapIncrease: - newbon->ItemHPRegenCap += base1; + newbon->ItemHPRegenCap += base_value; break; case SE_Ambidexterity: - newbon->Ambidexterity += base1; + newbon->Ambidexterity += base_value; break; case SE_PetMaxHP: - newbon->PetMaxHP += base1; + newbon->PetMaxHP += base_value; break; case SE_AvoidMeleeChance: - newbon->AvoidMeleeChanceEffect += base1; + newbon->AvoidMeleeChanceEffect += base_value; break; case SE_CombatStability: - newbon->CombatStability += base1; + newbon->CombatStability += base_value; break; case SE_AddSingingMod: - switch (base2) { + switch (limit_value) { case EQ::item::ItemTypeWindInstrument: - newbon->windMod += base1; + newbon->windMod += base_value; break; case EQ::item::ItemTypeStringedInstrument: - newbon->stringedMod += base1; + newbon->stringedMod += base_value; break; case EQ::item::ItemTypeBrassInstrument: - newbon->brassMod += base1; + newbon->brassMod += base_value; break; case EQ::item::ItemTypePercussionInstrument: - newbon->percussionMod += base1; + newbon->percussionMod += base_value; break; case EQ::item::ItemTypeSinging: - newbon->singingMod += base1; + newbon->singingMod += base_value; break; } break; case SE_SongModCap: - newbon->songModCap += base1; + newbon->songModCap += base_value; break; case SE_PetCriticalHit: - newbon->PetCriticalHit += base1; + newbon->PetCriticalHit += base_value; break; case SE_PetAvoidance: - newbon->PetAvoidance += base1; + newbon->PetAvoidance += base_value; break; case SE_ShieldBlock: - newbon->ShieldBlock += base1; + newbon->ShieldBlock += base_value; break; case SE_ShieldEquipDmgMod: - newbon->ShieldEquipDmgMod += base1; + newbon->ShieldEquipDmgMod += base_value; break; case SE_SecondaryDmgInc: newbon->SecondaryDmgInc = true; break; case SE_ChangeAggro: - newbon->hatemod += base1; + newbon->hatemod += base_value; break; case SE_EndurancePool: - newbon->Endurance += base1; + newbon->Endurance += base_value; break; case SE_ChannelChanceItems: - newbon->ChannelChanceItems += base1; + newbon->ChannelChanceItems += base_value; break; case SE_ChannelChanceSpells: - newbon->ChannelChanceSpells += base1; + newbon->ChannelChanceSpells += base_value; break; case SE_DoubleSpecialAttack: - newbon->DoubleSpecialAttack += base1; + newbon->DoubleSpecialAttack += base_value; break; case SE_TripleBackstab: - newbon->TripleBackstab += base1; + newbon->TripleBackstab += base_value; break; case SE_FrontalBackstabMinDmg: newbon->FrontalBackstabMinDmg = true; break; case SE_FrontalBackstabChance: - newbon->FrontalBackstabChance += base1; + newbon->FrontalBackstabChance += base_value; + break; + case SE_Double_Backstab_Front: + newbon->Double_Backstab_Front += base_value; break; case SE_BlockBehind: - newbon->BlockBehind += base1; + newbon->BlockBehind += base_value; break; - case SE_StrikeThrough: case SE_StrikeThrough2: - newbon->StrikeThrough += base1; + newbon->StrikeThrough += base_value; break; case SE_DoubleAttackChance: - newbon->DoubleAttackChance += base1; + newbon->DoubleAttackChance += base_value; break; case SE_GiveDoubleAttack: - newbon->GiveDoubleAttack += base1; + newbon->GiveDoubleAttack += base_value; break; case SE_ProcChance: - newbon->ProcChanceSPA += base1; + newbon->ProcChanceSPA += base_value; break; case SE_RiposteChance: - newbon->RiposteChance += base1; + newbon->RiposteChance += base_value; break; case SE_DodgeChance: - newbon->DodgeChance += base1; + newbon->DodgeChance += base_value; break; case SE_ParryChance: - newbon->ParryChance += base1; + newbon->ParryChance += base_value; break; case SE_IncreaseBlockChance: - newbon->IncreaseBlockChance += base1; + newbon->IncreaseBlockChance += base_value; break; case SE_Flurry: - newbon->FlurryChance += base1; + newbon->FlurryChance += base_value; break; case SE_PetFlurry: - newbon->PetFlurry += base1; + newbon->PetFlurry += base_value; break; case SE_BardSongRange: - newbon->SongRange += base1; + newbon->SongRange += base_value; break; case SE_RootBreakChance: - newbon->RootBreakChance += base1; + newbon->RootBreakChance += base_value; break; case SE_UnfailingDivinity: - newbon->UnfailingDivinity += base1; + newbon->UnfailingDivinity += base_value; break; case SE_CrippBlowChance: - newbon->CrippBlowChance += base1; + newbon->CrippBlowChance += base_value; break; case SE_HitChance: { // Bad data or unsupported new skill - if (base2 > EQ::skills::HIGHEST_SKILL) + if (limit_value > EQ::skills::HIGHEST_SKILL) break; - if (base2 == ALL_SKILLS) - newbon->HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1] += base1; + if (limit_value == ALL_SKILLS) + newbon->HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1] += base_value; else - newbon->HitChanceEffect[base2] += base1; + newbon->HitChanceEffect[limit_value] += base_value; } case SE_ProcOnKillShot: for (int i = 0; i < MAX_SPELL_TRIGGER * 3; i += 3) { if (!newbon->SpellOnKill[i] || - ((newbon->SpellOnKill[i] == base2) && (newbon->SpellOnKill[i + 1] < base1))) { + ((newbon->SpellOnKill[i] == limit_value) && (newbon->SpellOnKill[i + 1] < base_value))) { // base1 = chance, base2 = SpellID to be triggered, base3 = min npc level - newbon->SpellOnKill[i] = base2; - newbon->SpellOnKill[i + 1] = base1; + newbon->SpellOnKill[i] = limit_value; + newbon->SpellOnKill[i + 1] = base_value; if (GetLevel() > 15) newbon->SpellOnKill[i + 2] = @@ -1059,130 +1064,218 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) for (int i = 0; i < MAX_SPELL_TRIGGER * 2; i += 2) { if (!newbon->SpellOnDeath[i]) { // base1 = SpellID to be triggered, base2 = chance to fire - newbon->SpellOnDeath[i] = base1; - newbon->SpellOnDeath[i + 1] = base2; + newbon->SpellOnDeath[i] = base_value; + newbon->SpellOnDeath[i + 1] = limit_value; break; } } break; - case SE_TriggerOnCast: - - for (int i = 0; i < MAX_SPELL_TRIGGER; i++) { - if (newbon->SpellTriggers[i] == rank.id) - break; - - if (!newbon->SpellTriggers[i]) { - // Save the 'rank.id' of each triggerable effect to an array - newbon->SpellTriggers[i] = rank.id; + case SE_WeaponProc: + case SE_AddMeleeProc: + for (int i = 0; i < MAX_AA_PROCS; i += 4) { + if (!newbon->SpellProc[i + SBIndex::COMBAT_PROC_ORIGIN_ID]) { + newbon->SpellProc[i + SBIndex::COMBAT_PROC_ORIGIN_ID] = rank.id; //aa rank id + newbon->SpellProc[i + SBIndex::COMBAT_PROC_SPELL_ID] = base_value; //proc spell id + newbon->SpellProc[i + SBIndex::COMBAT_PROC_RATE_MOD] = limit_value; //proc rate modifer + newbon->SpellProc[i + SBIndex::COMBAT_PROC_REUSE_TIMER] = 0; //Lock out Timer break; } } break; + case SE_RangedProc: + for (int i = 0; i < MAX_AA_PROCS; i += 4) { + if (!newbon->RangedProc[i + SBIndex::COMBAT_PROC_ORIGIN_ID]) { + newbon->RangedProc[i + SBIndex::COMBAT_PROC_ORIGIN_ID] = rank.id; //aa rank id + newbon->RangedProc[i + SBIndex::COMBAT_PROC_SPELL_ID] = base_value; //proc spell id + newbon->RangedProc[i + SBIndex::COMBAT_PROC_RATE_MOD] = limit_value; //proc rate modifer + newbon->RangedProc[i + SBIndex::COMBAT_PROC_REUSE_TIMER] = 0; //Lock out Timer + break; + } + } + break; + + case SE_DefensiveProc: + for (int i = 0; i < MAX_AA_PROCS; i += 4) { + if (!newbon->DefensiveProc[i + SBIndex::COMBAT_PROC_ORIGIN_ID]) { + newbon->DefensiveProc[i + SBIndex::COMBAT_PROC_ORIGIN_ID] = rank.id; //aa rank id + newbon->DefensiveProc[i + SBIndex::COMBAT_PROC_SPELL_ID] = base_value; //proc spell id + newbon->DefensiveProc[i + SBIndex::COMBAT_PROC_RATE_MOD] = limit_value; //proc rate modifer + newbon->DefensiveProc[i + SBIndex::COMBAT_PROC_REUSE_TIMER] = 0; //Lock out Timer + break; + } + } + break; + + case SE_Proc_Timer_Modifier: { + /* + AA can multiples of this in a single effect, proc should use the timer + that comes after the respective proc spell effect, thus rank.id will be already set + when this is checked. + */ + + newbon->Proc_Timer_Modifier = true; + + for (int i = 0; i < MAX_AA_PROCS; i += 4) { + if (newbon->SpellProc[i + SBIndex::COMBAT_PROC_ORIGIN_ID] == rank.id) { + if (!newbon->SpellProc[i + SBIndex::COMBAT_PROC_REUSE_TIMER]) { + newbon->SpellProc[i + SBIndex::COMBAT_PROC_REUSE_TIMER] = limit_value;//Lock out Timer + break; + } + } + + if (newbon->RangedProc[i + SBIndex::COMBAT_PROC_ORIGIN_ID] == rank.id) { + if (!newbon->RangedProc[i + SBIndex::COMBAT_PROC_REUSE_TIMER]) { + newbon->RangedProc[i + SBIndex::COMBAT_PROC_REUSE_TIMER] = limit_value;//Lock out Timer + break; + } + } + + if (newbon->DefensiveProc[i + SBIndex::COMBAT_PROC_ORIGIN_ID] == rank.id) { + if (!newbon->DefensiveProc[i + SBIndex::COMBAT_PROC_REUSE_TIMER]) { + newbon->DefensiveProc[i + SBIndex::COMBAT_PROC_REUSE_TIMER] = limit_value;//Lock out Timer + break; + } + } + } + break; + } + case SE_CriticalHitChance: { // Bad data or unsupported new skill - if (base2 > EQ::skills::HIGHEST_SKILL) + if (limit_value > EQ::skills::HIGHEST_SKILL) break; - if (base2 == ALL_SKILLS) - newbon->CriticalHitChance[EQ::skills::HIGHEST_SKILL + 1] += base1; + if (limit_value == ALL_SKILLS) + newbon->CriticalHitChance[EQ::skills::HIGHEST_SKILL + 1] += base_value; else - newbon->CriticalHitChance[base2] += base1; + newbon->CriticalHitChance[limit_value] += base_value; } break; case SE_CriticalDamageMob: { // Bad data or unsupported new skill - if (base2 > EQ::skills::HIGHEST_SKILL) + if (limit_value > EQ::skills::HIGHEST_SKILL) break; // base1 = effect value, base2 = skill restrictions(-1 for all) - if (base2 == ALL_SKILLS) - newbon->CritDmgMod[EQ::skills::HIGHEST_SKILL + 1] += base1; + if (limit_value == ALL_SKILLS) + newbon->CritDmgMod[EQ::skills::HIGHEST_SKILL + 1] += base_value; else - newbon->CritDmgMod[base2] += base1; + newbon->CritDmgMod[limit_value] += base_value; + break; + } + + case SE_Critical_Melee_Damage_Mod_Max: + { + // Bad data or unsupported new skill + if (limit_value > EQ::skills::HIGHEST_SKILL) + break; + int skill = limit_value == ALL_SKILLS ? EQ::skills::HIGHEST_SKILL + 1 : limit_value; + if (base_value < 0 && newbon->CritDmgModNoStack[skill] > base_value) + newbon->CritDmgModNoStack[skill] = base_value; + else if (base_value > 0 && newbon->CritDmgModNoStack[skill] < base_value) + newbon->CritDmgModNoStack[skill] = base_value; break; } case SE_CriticalSpellChance: { - newbon->CriticalSpellChance += base1; + newbon->CriticalSpellChance += base_value; - if (base2 > newbon->SpellCritDmgIncNoStack) - newbon->SpellCritDmgIncNoStack = base2; + if (limit_value > newbon->SpellCritDmgIncNoStack) + newbon->SpellCritDmgIncNoStack = limit_value; break; } case SE_ResistFearChance: { - if (base1 == 100) // If we reach 100% in a single spell/item then we should be immune to + if (base_value == 100) // If we reach 100% in a single spell/item then we should be immune to // negative fear resist effects until our immunity is over newbon->Fearless = true; - newbon->ResistFearChance += base1; // these should stack + newbon->ResistFearChance += base_value; // these should stack break; } case SE_SkillDamageAmount: { // Bad data or unsupported new skill - if (base2 > EQ::skills::HIGHEST_SKILL) + if (limit_value > EQ::skills::HIGHEST_SKILL) break; - if (base2 == ALL_SKILLS) - newbon->SkillDamageAmount[EQ::skills::HIGHEST_SKILL + 1] += base1; + if (limit_value == ALL_SKILLS) + newbon->SkillDamageAmount[EQ::skills::HIGHEST_SKILL + 1] += base_value; else - newbon->SkillDamageAmount[base2] += base1; + newbon->SkillDamageAmount[limit_value] += base_value; break; } case SE_SkillAttackProc: { - // You can only have one of these per client. [AA Dragon Punch] - newbon->SkillAttackProc[0] = base1; // Chance base 1000 = 100% proc rate - newbon->SkillAttackProc[1] = base2; // Skill to Proc Off - newbon->SkillAttackProc[2] = rank.spell; // spell to proc - break; + for (int i = 0; i < MAX_CAST_ON_SKILL_USE; i += 3) { + if (!newbon->SkillAttackProc[i + SBIndex::SKILLATK_PROC_SPELL_ID]) { // spell id + newbon->SkillAttackProc[i + SBIndex::SKILLATK_PROC_SPELL_ID] = rank.spell; // spell to proc + newbon->SkillAttackProc[i + SBIndex::SKILLATK_PROC_CHANCE] = base_value; // Chance base 1000 = 100% proc rate + newbon->SkillAttackProc[i + SBIndex::SKILLATK_PROC_SKILL] = limit_value; // Skill to Proc Offr + + if (limit_value < EQ::skills::HIGHEST_SKILL) { + newbon->HasSkillAttackProc[limit_value] = true; //check first before looking for any effects. + } + break; + } + } } case SE_DamageModifier: { // Bad data or unsupported new skill - if (base2 > EQ::skills::HIGHEST_SKILL) + if (limit_value > EQ::skills::HIGHEST_SKILL) break; - if (base2 == ALL_SKILLS) - newbon->DamageModifier[EQ::skills::HIGHEST_SKILL + 1] += base1; + if (limit_value == ALL_SKILLS) + newbon->DamageModifier[EQ::skills::HIGHEST_SKILL + 1] += base_value; else - newbon->DamageModifier[base2] += base1; + newbon->DamageModifier[limit_value] += base_value; break; } case SE_DamageModifier2: { // Bad data or unsupported new skill - if (base2 > EQ::skills::HIGHEST_SKILL) + if (limit_value > EQ::skills::HIGHEST_SKILL) break; - if (base2 == ALL_SKILLS) - newbon->DamageModifier2[EQ::skills::HIGHEST_SKILL + 1] += base1; + if (limit_value == ALL_SKILLS) + newbon->DamageModifier2[EQ::skills::HIGHEST_SKILL + 1] += base_value; else - newbon->DamageModifier2[base2] += base1; + newbon->DamageModifier2[limit_value] += base_value; + break; + } + + case SE_Skill_Base_Damage_Mod: { + // Bad data or unsupported new skill + if (limit_value > EQ::skills::HIGHEST_SKILL) + break; + if (limit_value == ALL_SKILLS) + newbon->DamageModifier3[EQ::skills::HIGHEST_SKILL + 1] += base_value; + else + newbon->DamageModifier3[limit_value] += base_value; break; } case SE_SlayUndead: { - if (newbon->SlayUndead[1] < base1) - newbon->SlayUndead[0] = base1; // Rate - newbon->SlayUndead[1] = base2; // Damage Modifier + if (newbon->SlayUndead[SBIndex::SLAYUNDEAD_DMG_MOD] < base_value) + newbon->SlayUndead[SBIndex::SLAYUNDEAD_RATE_MOD] = base_value; // Rate + newbon->SlayUndead[SBIndex::SLAYUNDEAD_DMG_MOD] = limit_value; // Damage Modifier break; } case SE_DoubleRiposte: { - newbon->DoubleRiposte += base1; + newbon->DoubleRiposte += base_value; break; } case SE_GiveDoubleRiposte: { // 0=Regular Riposte 1=Skill Attack Riposte 2=Skill - if (base2 == 0) { - if (newbon->GiveDoubleRiposte[0] < base1) - newbon->GiveDoubleRiposte[0] = base1; + if (limit_value == 0) { + if (newbon->GiveDoubleRiposte[SBIndex::DOUBLE_RIPOSTE_CHANCE] < base_value) + newbon->GiveDoubleRiposte[SBIndex::DOUBLE_RIPOSTE_CHANCE] = base_value; } // Only for special attacks. - else if (base2 > 0 && (newbon->GiveDoubleRiposte[1] < base1)) { - newbon->GiveDoubleRiposte[1] = base1; - newbon->GiveDoubleRiposte[2] = base2; + else if (limit_value > 0 && (newbon->GiveDoubleRiposte[SBIndex::DOUBLE_RIPOSTE_SKILL_ATK_CHANCE] < base_value)) { + newbon->GiveDoubleRiposte[SBIndex::DOUBLE_RIPOSTE_SKILL_ATK_CHANCE] = base_value; + newbon->GiveDoubleRiposte[SBIndex::DOUBLE_RIPOSTE_SKILL] = limit_value; } break; @@ -1191,48 +1284,48 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) // Physically raises skill cap ie if 55/55 it will raise to 55/60 case SE_RaiseSkillCap: { - if (base2 > EQ::skills::HIGHEST_SKILL) + if (limit_value > EQ::skills::HIGHEST_SKILL) break; - if (newbon->RaiseSkillCap[base2] < base1) - newbon->RaiseSkillCap[base2] = base1; + if (newbon->RaiseSkillCap[limit_value] < base_value) + newbon->RaiseSkillCap[limit_value] = base_value; break; } case SE_MasteryofPast: { - if (newbon->MasteryofPast < base1) - newbon->MasteryofPast = base1; + if (newbon->MasteryofPast < base_value) + newbon->MasteryofPast = base_value; break; } case SE_CastingLevel: { - newbon->adjusted_casting_skill += base1; + newbon->adjusted_casting_skill += base_value; break; } case SE_CastingLevel2: { - newbon->effective_casting_level += base1; + newbon->effective_casting_level += base_value; break; } case SE_DivineSave: { - if (newbon->DivineSaveChance[0] < base1) { - newbon->DivineSaveChance[0] = base1; - newbon->DivineSaveChance[1] = base2; + if (newbon->DivineSaveChance[SBIndex::DIVINE_SAVE_CHANCE] < base_value) { + newbon->DivineSaveChance[SBIndex::DIVINE_SAVE_CHANCE] = base_value; + newbon->DivineSaveChance[SBIndex::DIVINE_SAVE_SPELL_TRIGGER_ID] = limit_value; } break; } case SE_SpellEffectResistChance: { for (int e = 0; e < MAX_RESISTABLE_EFFECTS * 2; e += 2) { - if (newbon->SEResist[e + 1] && (newbon->SEResist[e] == base2) && - (newbon->SEResist[e + 1] < base1)) { - newbon->SEResist[e] = base2; // Spell Effect ID - newbon->SEResist[e + 1] = base1; // Resist Chance + if (newbon->SEResist[e + 1] && (newbon->SEResist[e] == limit_value) && + (newbon->SEResist[e + 1] < base_value)) { + newbon->SEResist[e] = limit_value; // Spell Effect ID + newbon->SEResist[e + 1] = base_value; // Resist Chance break; } else if (!newbon->SEResist[e + 1]) { - newbon->SEResist[e] = base2; // Spell Effect ID - newbon->SEResist[e + 1] = base1; // Resist Chance + newbon->SEResist[e] = limit_value; // Spell Effect ID + newbon->SEResist[e + 1] = base_value; // Resist Chance break; } } @@ -1240,67 +1333,70 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) } case SE_MitigateDamageShield: { - if (base1 < 0) - base1 = base1 * (-1); + + //AA that increase mitigation are set to negative. + if (base_value < 0) { + base_value = base_value * (-1); + } - newbon->DSMitigationOffHand += base1; + newbon->DSMitigationOffHand += base_value; break; } case SE_FinishingBlow: { // base1 = chance, base2 = damage - if (newbon->FinishingBlow[1] < base2) { - newbon->FinishingBlow[0] = base1; - newbon->FinishingBlow[1] = base2; + if (newbon->FinishingBlow[SBIndex::FINISHING_EFFECT_DMG] < limit_value) { + newbon->FinishingBlow[SBIndex::FINISHING_EFFECT_PROC_CHANCE] = base_value; + newbon->FinishingBlow[SBIndex::FINISHING_EFFECT_DMG] = limit_value; } break; } case SE_FinishingBlowLvl: { // base1 = level, base2 = ??? (Set to 200 in AA data, possible proc rate mod?) - if (newbon->FinishingBlowLvl[0] < base1) { - newbon->FinishingBlowLvl[0] = base1; - newbon->FinishingBlowLvl[1] = base2; + if (newbon->FinishingBlowLvl[SBIndex::FINISHING_EFFECT_LEVEL_MAX] < base_value) { + newbon->FinishingBlowLvl[SBIndex::FINISHING_EFFECT_LEVEL_MAX] = base_value; + newbon->FinishingBlowLvl[SBIndex::FINISHING_BLOW_LEVEL_HP_RATIO] = limit_value; } break; } case SE_StunBashChance: - newbon->StunBashChance += base1; + newbon->StunBashChance += base_value; break; case SE_IncreaseChanceMemwipe: - newbon->IncreaseChanceMemwipe += base1; + newbon->IncreaseChanceMemwipe += base_value; break; case SE_CriticalMend: - newbon->CriticalMend += base1; + newbon->CriticalMend += base_value; break; case SE_HealRate: - newbon->HealRate += base1; + newbon->HealRate += base_value; break; case SE_MeleeLifetap: { - if ((base1 < 0) && (newbon->MeleeLifetap > base1)) - newbon->MeleeLifetap = base1; + if ((base_value < 0) && (newbon->MeleeLifetap > base_value)) + newbon->MeleeLifetap = base_value; - else if (newbon->MeleeLifetap < base1) - newbon->MeleeLifetap = base1; + else if (newbon->MeleeLifetap < base_value) + newbon->MeleeLifetap = base_value; break; } case SE_Vampirism: - newbon->Vampirism += base1; + newbon->Vampirism += base_value; break; case SE_FrenziedDevastation: - newbon->FrenziedDevastation += base2; + newbon->FrenziedDevastation += limit_value; break; case SE_SpellProcChance: - newbon->SpellProcChance += base1; + newbon->SpellProcChance += base_value; break; case SE_Berserk: @@ -1308,76 +1404,84 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; case SE_Metabolism: - newbon->Metabolism += base1; + newbon->Metabolism += base_value; break; case SE_ImprovedReclaimEnergy: { - if ((base1 < 0) && (newbon->ImprovedReclaimEnergy > base1)) - newbon->ImprovedReclaimEnergy = base1; + if ((base_value < 0) && (newbon->ImprovedReclaimEnergy > base_value)) + newbon->ImprovedReclaimEnergy = base_value; - else if (newbon->ImprovedReclaimEnergy < base1) - newbon->ImprovedReclaimEnergy = base1; + else if (newbon->ImprovedReclaimEnergy < base_value) + newbon->ImprovedReclaimEnergy = base_value; break; } case SE_HeadShot: { - if (newbon->HeadShot[1] < base2) { - newbon->HeadShot[0] = base1; - newbon->HeadShot[1] = base2; + if (newbon->HeadShot[SBIndex::FINISHING_EFFECT_DMG] < limit_value) { + newbon->HeadShot[SBIndex::FINISHING_EFFECT_PROC_CHANCE] = base_value; + newbon->HeadShot[SBIndex::FINISHING_EFFECT_DMG] = limit_value; } break; } case SE_HeadShotLevel: { - if (newbon->HSLevel[0] < base1) - newbon->HSLevel[0] = base1; - newbon->HSLevel[1] = base2; + if (newbon->HSLevel[SBIndex::FINISHING_EFFECT_LEVEL_MAX] < base_value) + newbon->HSLevel[SBIndex::FINISHING_EFFECT_LEVEL_MAX] = base_value; + newbon->HSLevel[SBIndex::FINISHING_EFFECT_LEVEL_CHANCE_BONUS] = limit_value; break; } case SE_Assassinate: { - if (newbon->Assassinate[1] < base2) { - newbon->Assassinate[0] = base1; - newbon->Assassinate[1] = base2; + if (newbon->Assassinate[SBIndex::FINISHING_EFFECT_DMG] < limit_value) { + newbon->Assassinate[SBIndex::FINISHING_EFFECT_PROC_CHANCE] = base_value; + newbon->Assassinate[SBIndex::FINISHING_EFFECT_DMG] = limit_value; } break; } case SE_AssassinateLevel: { - if (newbon->AssassinateLevel[0] < base1) { - newbon->AssassinateLevel[0] = base1; - newbon->AssassinateLevel[1] = base2; + if (newbon->AssassinateLevel[SBIndex::FINISHING_EFFECT_LEVEL_MAX] < base_value) { + newbon->AssassinateLevel[SBIndex::FINISHING_EFFECT_LEVEL_MAX] = base_value; + newbon->AssassinateLevel[SBIndex::FINISHING_EFFECT_LEVEL_CHANCE_BONUS] = limit_value; } break; } case SE_PetMeleeMitigation: - newbon->PetMeleeMitigation += base1; + newbon->PetMeleeMitigation += base_value; break; case SE_FactionModPct: { - if ((base1 < 0) && (newbon->FactionModPct > base1)) - newbon->FactionModPct = base1; + if ((base_value < 0) && (newbon->FactionModPct > base_value)) + newbon->FactionModPct = base_value; - else if (newbon->FactionModPct < base1) - newbon->FactionModPct = base1; + else if (newbon->FactionModPct < base_value) + newbon->FactionModPct = base_value; break; } + case SE_Illusion: + newbon->Illusion = true; + break; + case SE_IllusionPersistence: - newbon->IllusionPersistence = true; + newbon->IllusionPersistence = base_value; break; case SE_LimitToSkill: { + // Bad data or unsupported new skill - if (base2 > EQ::skills::HIGHEST_SKILL) + if (base_value > EQ::skills::HIGHEST_SKILL) { break; - if (base1 <= EQ::skills::HIGHEST_SKILL) - newbon->LimitToSkill[base1] = true; + } + if (base_value <= EQ::skills::HIGHEST_SKILL) { + newbon->LimitToSkill[base_value] = true; + newbon->LimitToSkill[EQ::skills::HIGHEST_SKILL + 3] = true; //Used as a general exists check + } break; } - case SE_SkillProc: { + case SE_SkillProcAttempt: { for (int e = 0; e < MAX_SKILL_PROCS; e++) { if (newbon->SkillProc[e] && newbon->SkillProc[e] == rank.id) break; // Do not use the same aa id more than once. @@ -1405,25 +1509,32 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) } case SE_MeleeMitigation: - newbon->MeleeMitigationEffect += base1; + newbon->MeleeMitigationEffect += base_value; break; case SE_ATK: - newbon->ATK += base1; + newbon->ATK += base_value; break; case SE_IncreaseExtTargetWindow: - newbon->extra_xtargets += base1; + newbon->extra_xtargets += base_value; break; case SE_PC_Pet_Rampage: { - newbon->PC_Pet_Rampage[0] += base1; //Chance to rampage - if (newbon->PC_Pet_Rampage[1] < base2) - newbon->PC_Pet_Rampage[1] = base2; //Damage modifer - take highest + newbon->PC_Pet_Rampage[SBIndex::PET_RAMPAGE_CHANCE] += base_value; //Chance to rampage + if (newbon->PC_Pet_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] < limit_value) + newbon->PC_Pet_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] = limit_value; //Damage modifer - take highest break; } - case SE_PC_Pet_Flurry_Chance: - newbon->PC_Pet_Flurry += base1; //Chance to Flurry + case SE_PC_Pet_AE_Rampage: { + newbon->PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_CHANCE] += base_value; //Chance to rampage + if (newbon->PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] < limit_value) + newbon->PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] = limit_value; //Damage modifer - take highest + break; + } + + case SE_PC_Pet_Flurry_Chance: + newbon->PC_Pet_Flurry += base_value; //Chance to Flurry break; case SE_ShroudofStealth: @@ -1431,53 +1542,55 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; case SE_ReduceFallDamage: - newbon->ReduceFallDamage += base1; + newbon->ReduceFallDamage += base_value; break; case SE_ReduceTradeskillFail:{ - if (base2 > EQ::skills::HIGHEST_SKILL) + if (limit_value > EQ::skills::HIGHEST_SKILL) break; - newbon->ReduceTradeskillFail[base2] += base1; + newbon->ReduceTradeskillFail[limit_value] += base_value; break; } case SE_TradeSkillMastery: - if (newbon->TradeSkillMastery < base1) - newbon->TradeSkillMastery = base1; + if (newbon->TradeSkillMastery < base_value) + newbon->TradeSkillMastery = base_value; break; case SE_NoBreakAESneak: - if (newbon->NoBreakAESneak < base1) - newbon->NoBreakAESneak = base1; + if (newbon->NoBreakAESneak < base_value) + newbon->NoBreakAESneak = base_value; break; case SE_FeignedCastOnChance: - if (newbon->FeignedCastOnChance < base1) - newbon->FeignedCastOnChance = base1; + if (newbon->FeignedCastOnChance < base_value) + newbon->FeignedCastOnChance = base_value; break; case SE_AddPetCommand: - if (base1 && base2 < PET_MAXCOMMANDS) - newbon->PetCommands[base2] = true; + if (base_value && limit_value < PET_MAXCOMMANDS) + newbon->PetCommands[limit_value] = true; break; case SE_FeignedMinion: - if (newbon->FeignedMinionChance < base1) - newbon->FeignedMinionChance = base1; + if (newbon->FeignedMinionChance < base_value) { + newbon->FeignedMinionChance = base_value; + } + newbon->PetCommands[PET_FEIGN] = true; break; case SE_AdditionalAura: - newbon->aura_slots += base1; + newbon->aura_slots += base_value; break; case SE_IncreaseTrapCount: - newbon->trap_slots += base1; + newbon->trap_slots += base_value; break; case SE_ForageSkill: - newbon->GrantForage += base1; + newbon->GrantForage += base_value; // we need to grant a skill point here // I'd rather not do this here, but whatever, probably fine if (IsClient()) { @@ -1487,6 +1600,200 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) } break; + case SE_Attack_Accuracy_Max_Percent: + newbon->Attack_Accuracy_Max_Percent += base_value; + break; + + case SE_AC_Mitigation_Max_Percent: + newbon->AC_Mitigation_Max_Percent += base_value; + break; + + case SE_AC_Avoidance_Max_Percent: + newbon->AC_Avoidance_Max_Percent += base_value; + break; + + case SE_Damage_Taken_Position_Mod: + { + //Mitigate if damage taken from behind base2 = 0, from front base2 = 1 + if (limit_value < 0 || limit_value > 2) + break; + else if (base_value < 0 && newbon->Damage_Taken_Position_Mod[limit_value] > base_value) + newbon->Damage_Taken_Position_Mod[limit_value] = base_value; + else if (base_value > 0 && newbon->Damage_Taken_Position_Mod[limit_value] < base_value) + newbon->Damage_Taken_Position_Mod[limit_value] = base_value; + break; + } + + case SE_Melee_Damage_Position_Mod: + { + if (limit_value < 0 || limit_value > 2) + break; + else if (base_value < 0 && newbon->Melee_Damage_Position_Mod[limit_value] > base_value) + newbon->Melee_Damage_Position_Mod[limit_value] = base_value; + else if (base_value > 0 && newbon->Melee_Damage_Position_Mod[limit_value] < base_value) + newbon->Melee_Damage_Position_Mod[limit_value] = base_value; + break; + } + + case SE_Damage_Taken_Position_Amt: + { + //Mitigate if damage taken from behind base2 = 0, from front base2 = 1 + if (limit_value < 0 || limit_value > 2) + break; + + newbon->Damage_Taken_Position_Amt[limit_value] += base_value; + break; + } + + case SE_Melee_Damage_Position_Amt: + { + //Mitigate if damage taken from behind base2 = 0, from front base2 = 1 + if (limit_value < 0 || limit_value > 2) + break; + + newbon->Melee_Damage_Position_Amt[limit_value] += base_value; + break; + } + + case SE_DS_Mitigation_Amount: + newbon->DS_Mitigation_Amount += base_value; + break; + + case SE_DS_Mitigation_Percentage: + newbon->DS_Mitigation_Percentage += base_value; + break; + + case SE_Pet_Crit_Melee_Damage_Pct_Owner: + newbon->Pet_Crit_Melee_Damage_Pct_Owner += base_value; + break; + + case SE_Pet_Add_Atk: + newbon->Pet_Add_Atk += base_value; + break; + + case SE_Weapon_Stance: + { + if (IsValidSpell(base_value)) { //base1 is the spell_id of buff + if (limit_value <= WEAPON_STANCE_TYPE_MAX) { //0=2H, 1=Shield, 2=DW + if (IsValidSpell(newbon->WeaponStance[limit_value])) { //Check if we already a spell_id saved for this effect + if (spells[newbon->WeaponStance[limit_value]].rank < spells[base_value].rank) { //If so, check if any new spellids with higher rank exist (live spells for this are ranked). + newbon->WeaponStance[limit_value] = base_value; //Overwrite with new effect + SetWeaponStanceEnabled(true); + } + } + else { + newbon->WeaponStance[limit_value] = base_value; //If no prior effect exists, then apply + SetWeaponStanceEnabled(true); + } + } + } + break; + } + + case SE_ExtraAttackChance: + { + if (newbon->ExtraAttackChance[SBIndex::EXTRA_ATTACK_CHANCE] < base_value) { + newbon->ExtraAttackChance[SBIndex::EXTRA_ATTACK_CHANCE] = base_value; + newbon->ExtraAttackChance[SBIndex::EXTRA_ATTACK_NUM_ATKS] = limit_value ? limit_value : 1; + } + break; + } + + case SE_AddExtraAttackPct_1h_Primary: + { + if (newbon->ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_CHANCE] < base_value) { + newbon->ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_CHANCE] = base_value; + newbon->ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_NUM_ATKS] = limit_value ? limit_value : 1; + } + break; + } + + case SE_AddExtraAttackPct_1h_Secondary: + { + + if (newbon->ExtraAttackChanceSecondary[SBIndex::EXTRA_ATTACK_CHANCE] < base_value) { + newbon->ExtraAttackChanceSecondary[SBIndex::EXTRA_ATTACK_CHANCE] = base_value; + newbon->ExtraAttackChanceSecondary[SBIndex::EXTRA_ATTACK_NUM_ATKS] = limit_value ? limit_value : 1; + } + break; + } + + case SE_Double_Melee_Round: + { + if (newbon->DoubleMeleeRound[SBIndex::DOUBLE_MELEE_ROUND_CHANCE] < base_value) { + newbon->DoubleMeleeRound[SBIndex::DOUBLE_MELEE_ROUND_CHANCE] = base_value; + newbon->DoubleMeleeRound[SBIndex::DOUBLE_MELEE_ROUND_DMG_BONUS] = limit_value; + + } + break; + } + + case SE_ExtendedShielding: + { + if (newbon->ExtendedShielding < base_value) { + newbon->ExtendedShielding = base_value; + } + break; + } + + case SE_ShieldDuration: + { + if (newbon->ShieldDuration < base_value) { + newbon->ShieldDuration = base_value; + } + break; + } + + case SE_Worn_Endurance_Regen_Cap: + newbon->ItemEnduranceRegenCap += base_value; + break; + + + case SE_SecondaryForte: + if (newbon->SecondaryForte < base_value) { + newbon->SecondaryForte = base_value; + } + break; + + case SE_ZoneSuspendMinion: + newbon->ZoneSuspendMinion = base_value; + break; + + + case SE_Reflect: + + if (newbon->reflect[SBIndex::REFLECT_CHANCE] < base_value) { + newbon->reflect[SBIndex::REFLECT_CHANCE] = base_value; + } + if (newbon->reflect[SBIndex::REFLECT_RESISTANCE_MOD] < limit_value) { + newbon->reflect[SBIndex::REFLECT_RESISTANCE_MOD] = limit_value; + } + break; + + case SE_SpellDamageShield: + newbon->SpellDamageShield += base_value; + break; + + case SE_Amplification: + newbon->Amplification += base_value; + break; + + case SE_MitigateSpellDamage: + { + newbon->MitigateSpellRune[SBIndex::MITIGATION_RUNE_PERCENT] += base_value; + break; + } + + case SE_MitigateDotDamage: + { + newbon->MitigateDotRune[SBIndex::MITIGATION_RUNE_PERCENT] += base_value; + break; + } + + case SE_TrapCircumvention: + newbon->TrapCircumvention += base_value; + break; + // to do case SE_PetDiscipline: break; @@ -1494,18 +1801,10 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; case SE_BandolierSlots: break; - case SE_SecondaryForte: - break; - case SE_ExtendedShielding: - break; - case SE_ShieldDuration: - break; case SE_ReduceApplyPoisonTime: break; case SE_NimbleEvasion: break; - case SE_TrapCircumvention: - break; // not handled here case SE_HastenedAASkill: @@ -1557,7 +1856,7 @@ void Mob::CalcSpellBonuses(StatBonuses* newbon) if(buffs[i].spellid != SPELL_UNKNOWN){ ApplySpellsBonuses(buffs[i].spellid, buffs[i].casterlevel, newbon, buffs[i].casterid, 0, buffs[i].ticsremaining, i, buffs[i].instrument_mod); - if (buffs[i].numhits > 0) + if (buffs[i].hit_number > 0) Numhits(true); } } @@ -1566,11 +1865,11 @@ void Mob::CalcSpellBonuses(StatBonuses* newbon) if (IsNPC()) CastToNPC()->ApplyAISpellEffects(newbon); - //Removes the spell bonuses that are effected by a 'negate' debuff. + //Disables a specific spell effect bonus completely, can also be limited to negate only item, AA or spell bonuses. if (spellbonuses.NegateEffects){ for(i = 0; i < buff_count; i++) { if( (buffs[i].spellid != SPELL_UNKNOWN) && (IsEffectInSpell(buffs[i].spellid, SE_NegateSpellEffect)) ) - NegateSpellsBonuses(buffs[i].spellid); + NegateSpellEffectBonuses(buffs[i].spellid); } } @@ -1582,7 +1881,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne uint8 WornType, int32 ticsremaining, int buffslot, int instrument_mod, bool IsAISpellEffect, uint16 effect_id, int32 se_base, int32 se_limit, int32 se_max) { - int i, effect_value, base2, max, effectid; + int i, effect_value, limit_value, max_value, spell_effect_id; bool AdditiveWornBonus = false; if(!IsAISpellEffect && !IsValidSpell(spell_id)) @@ -1600,34 +1899,35 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne if (focus) { if (WornType){ - if (RuleB(Spells, UseAdditiveFocusFromWornSlot)) - new_bonus->FocusEffectsWorn[focus] += spells[spell_id].base[i]; + if (RuleB(Spells, UseAdditiveFocusFromWornSlot)) { + new_bonus->FocusEffectsWorn[focus] += spells[spell_id].base_value[i]; + } + } + else { + new_bonus->FocusEffects[focus] = spells[spell_id].effect_id[i]; } - - else - new_bonus->FocusEffects[focus] = static_cast(spells[spell_id].effectid[i]); - continue; } - if (WornType && (RuleI(Spells, AdditiveBonusWornType) == WornType)) + if (WornType && (RuleI(Spells, AdditiveBonusWornType) == WornType)) { AdditiveWornBonus = true; + } - effectid = spells[spell_id].effectid[i]; + spell_effect_id = spells[spell_id].effect_id[i]; effect_value = CalcSpellEffectValue(spell_id, i, casterlevel, instrument_mod, nullptr, ticsremaining, casterId); - base2 = spells[spell_id].base2[i]; - max = spells[spell_id].max[i]; + limit_value = spells[spell_id].limit_value[i]; + max_value = spells[spell_id].max_value[i]; } //Use AISpellEffects else { - effectid = effect_id; + spell_effect_id = effect_id; effect_value = se_base; - base2 = se_limit; - max = se_max; + limit_value = se_limit; + max_value = se_max; i = EFFECT_COUNT; //End the loop } - switch (effectid) + switch (spell_effect_id) { case SE_CurrentHP: //regens if(effect_value > 0) { @@ -1641,7 +1941,9 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne case SE_ChangeFrenzyRad: { - // redundant to have level check here + if (max_value != 0 && GetLevel() > max_value) + break; + if(new_bonus->AggroRange == -1 || effect_value < new_bonus->AggroRange) { new_bonus->AggroRange = static_cast(effect_value); @@ -1651,6 +1953,8 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne case SE_Harmony: { + if (max_value != 0 && GetLevel() > max_value) + break; // Harmony effect as buff - kinda tricky // harmony could stack with a lull spell, which has better aggro range // take the one with less range in any case @@ -1806,7 +2110,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne case SE_CHA: { - if (spells[spell_id].base[i] != 0) { + if (spells[spell_id].base_value[i] != 0) { new_bonus->CHA += effect_value; } break; @@ -1872,7 +2176,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne case SE_RaiseStatCap: { - switch(spells[spell_id].base2[i]) + switch(spells[spell_id].limit_value[i]) { //are these #define'd somewhere? case 0: //str @@ -1943,8 +2247,8 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne new_bonus->DamageShield += effect_value; new_bonus->DamageShieldSpellID = spell_id; //When using npc_spells_effects MAX value can be set to determine DS Type - if (IsAISpellEffect && max) - new_bonus->DamageShieldType = GetDamageShieldType(spell_id, max); + if (IsAISpellEffect && max_value) + new_bonus->DamageShieldType = GetDamageShieldType(spell_id, max_value); else new_bonus->DamageShieldType = GetDamageShieldType(spell_id); @@ -1956,15 +2260,24 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne new_bonus->ReverseDamageShield += effect_value; new_bonus->ReverseDamageShieldSpellID = spell_id; - if (IsAISpellEffect && max) - new_bonus->ReverseDamageShieldType = GetDamageShieldType(spell_id, max); + if (IsAISpellEffect && max_value) + new_bonus->ReverseDamageShieldType = GetDamageShieldType(spell_id, max_value); else new_bonus->ReverseDamageShieldType = GetDamageShieldType(spell_id); break; } case SE_Reflect: - new_bonus->reflect_chance += effect_value; + + if (AdditiveWornBonus) { + new_bonus->reflect[SBIndex::REFLECT_CHANCE] += effect_value; + } + + else if (new_bonus->reflect[SBIndex::REFLECT_CHANCE] < effect_value) { + new_bonus->reflect[SBIndex::REFLECT_CHANCE] = effect_value; + new_bonus->reflect[SBIndex::REFLECT_RESISTANCE_MOD] = limit_value; + new_bonus->reflect[SBIndex::REFLECT_DMG_EFFECTIVENESS] = max_value; + } break; case SE_Amplification: @@ -1983,28 +2296,28 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne case SE_CriticalHitChance: { // Bad data or unsupported new skill - if (base2 > EQ::skills::HIGHEST_SKILL) + if (limit_value > EQ::skills::HIGHEST_SKILL) break; if (AdditiveWornBonus) { - if(base2 == ALL_SKILLS) + if(limit_value == ALL_SKILLS) new_bonus->CriticalHitChance[EQ::skills::HIGHEST_SKILL + 1] += effect_value; else - new_bonus->CriticalHitChance[base2] += effect_value; + new_bonus->CriticalHitChance[limit_value] += effect_value; } else if(effect_value < 0) { - if (base2 == ALL_SKILLS && new_bonus->CriticalHitChance[EQ::skills::HIGHEST_SKILL + 1] > effect_value) + if (limit_value == ALL_SKILLS && new_bonus->CriticalHitChance[EQ::skills::HIGHEST_SKILL + 1] > effect_value) new_bonus->CriticalHitChance[EQ::skills::HIGHEST_SKILL + 1] = effect_value; - else if(base2 != ALL_SKILLS && new_bonus->CriticalHitChance[base2] > effect_value) - new_bonus->CriticalHitChance[base2] = effect_value; + else if(limit_value != ALL_SKILLS && new_bonus->CriticalHitChance[limit_value] > effect_value) + new_bonus->CriticalHitChance[limit_value] = effect_value; } - else if (base2 == ALL_SKILLS && new_bonus->CriticalHitChance[EQ::skills::HIGHEST_SKILL + 1] < effect_value) + else if (limit_value == ALL_SKILLS && new_bonus->CriticalHitChance[EQ::skills::HIGHEST_SKILL + 1] < effect_value) new_bonus->CriticalHitChance[EQ::skills::HIGHEST_SKILL + 1] = effect_value; - else if(base2 != ALL_SKILLS && new_bonus->CriticalHitChance[base2] < effect_value) - new_bonus->CriticalHitChance[base2] = effect_value; + else if(limit_value != ALL_SKILLS && new_bonus->CriticalHitChance[limit_value] < effect_value) + new_bonus->CriticalHitChance[limit_value] = effect_value; break; } @@ -2119,7 +2432,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne case SE_MeleeLifetap: { if (AdditiveWornBonus) - new_bonus->MeleeLifetap += spells[spell_id].base[i]; + new_bonus->MeleeLifetap += spells[spell_id].base_value[i]; else if((effect_value < 0) && (new_bonus->MeleeLifetap > effect_value)) new_bonus->MeleeLifetap = effect_value; @@ -2181,7 +2494,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne { if(new_bonus->MeleeSkillCheck < effect_value) { new_bonus->MeleeSkillCheck = effect_value; - new_bonus->MeleeSkillCheckSkill = base2==ALL_SKILLS?255:base2; + new_bonus->MeleeSkillCheckSkill = limit_value==ALL_SKILLS?255:limit_value; } break; } @@ -2189,17 +2502,17 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne case SE_HitChance: { // Bad data or unsupported new skill - if (base2 > EQ::skills::HIGHEST_SKILL) + if (limit_value > EQ::skills::HIGHEST_SKILL) break; if (AdditiveWornBonus){ - if(base2 == ALL_SKILLS) + if(limit_value == ALL_SKILLS) new_bonus->HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1] += effect_value; else - new_bonus->HitChanceEffect[base2] += effect_value; + new_bonus->HitChanceEffect[limit_value] += effect_value; } - else if(base2 == ALL_SKILLS){ + else if(limit_value == ALL_SKILLS){ if ((effect_value < 0) && (new_bonus->HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1] > effect_value)) new_bonus->HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1] = effect_value; @@ -2211,12 +2524,12 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne else { - if ((effect_value < 0) && (new_bonus->HitChanceEffect[base2] > effect_value)) - new_bonus->HitChanceEffect[base2] = effect_value; + if ((effect_value < 0) && (new_bonus->HitChanceEffect[limit_value] > effect_value)) + new_bonus->HitChanceEffect[limit_value] = effect_value; - else if (!new_bonus->HitChanceEffect[base2] || - ((new_bonus->HitChanceEffect[base2] > 0) && (new_bonus->HitChanceEffect[base2] < effect_value))) - new_bonus->HitChanceEffect[base2] = effect_value; + else if (!new_bonus->HitChanceEffect[limit_value] || + ((new_bonus->HitChanceEffect[limit_value] > 0) && (new_bonus->HitChanceEffect[limit_value] < effect_value))) + new_bonus->HitChanceEffect[limit_value] = effect_value; } break; @@ -2226,9 +2539,9 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne case SE_DamageModifier: { // Bad data or unsupported new skill - if (base2 > EQ::skills::HIGHEST_SKILL) + if (limit_value > EQ::skills::HIGHEST_SKILL) break; - int skill = base2 == ALL_SKILLS ? EQ::skills::HIGHEST_SKILL + 1 : base2; + int skill = limit_value == ALL_SKILLS ? EQ::skills::HIGHEST_SKILL + 1 : limit_value; if (effect_value < 0 && new_bonus->DamageModifier[skill] > effect_value) new_bonus->DamageModifier[skill] = effect_value; else if (effect_value > 0 && new_bonus->DamageModifier[skill] < effect_value) @@ -2239,9 +2552,9 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne case SE_DamageModifier2: { // Bad data or unsupported new skill - if (base2 > EQ::skills::HIGHEST_SKILL) + if (limit_value > EQ::skills::HIGHEST_SKILL) break; - int skill = base2 == ALL_SKILLS ? EQ::skills::HIGHEST_SKILL + 1 : base2; + int skill = limit_value == ALL_SKILLS ? EQ::skills::HIGHEST_SKILL + 1 : limit_value; if (effect_value < 0 && new_bonus->DamageModifier2[skill] > effect_value) new_bonus->DamageModifier2[skill] = effect_value; else if (effect_value > 0 && new_bonus->DamageModifier2[skill] < effect_value) @@ -2249,12 +2562,25 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } + case SE_Skill_Base_Damage_Mod: + { + // Bad data or unsupported new skill + if (limit_value > EQ::skills::HIGHEST_SKILL) + break; + int skill = limit_value == ALL_SKILLS ? EQ::skills::HIGHEST_SKILL + 1 : limit_value; + if (effect_value < 0 && new_bonus->DamageModifier3[skill] > effect_value) + new_bonus->DamageModifier3[skill] = effect_value; + else if (effect_value > 0 && new_bonus->DamageModifier3[skill] < effect_value) + new_bonus->DamageModifier3[skill] = effect_value; + break; + } + case SE_MinDamageModifier: { // Bad data or unsupported new skill - if (base2 > EQ::skills::HIGHEST_SKILL) + if (limit_value > EQ::skills::HIGHEST_SKILL) break; - int skill = base2 == ALL_SKILLS ? EQ::skills::HIGHEST_SKILL + 1 : base2; + int skill = limit_value == ALL_SKILLS ? EQ::skills::HIGHEST_SKILL + 1 : limit_value; if (effect_value < 0 && new_bonus->MinDamageModifier[skill] > effect_value) new_bonus->MinDamageModifier[skill] = effect_value; else if (effect_value > 0 && new_bonus->MinDamageModifier[skill] < effect_value) @@ -2284,8 +2610,59 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne } case SE_ExtraAttackChance: - new_bonus->ExtraAttackChance += effect_value; + { + if (AdditiveWornBonus) { + new_bonus->ExtraAttackChance[SBIndex::EXTRA_ATTACK_CHANCE] += effect_value; + new_bonus->ExtraAttackChance[SBIndex::EXTRA_ATTACK_NUM_ATKS] = limit_value ? limit_value : 1; + } + if (new_bonus->ExtraAttackChance[SBIndex::EXTRA_ATTACK_CHANCE] < effect_value) { + new_bonus->ExtraAttackChance[SBIndex::EXTRA_ATTACK_CHANCE] = effect_value; + new_bonus->ExtraAttackChance[SBIndex::EXTRA_ATTACK_NUM_ATKS] = limit_value ? limit_value : 1; + } break; + } + + case SE_AddExtraAttackPct_1h_Primary: + { + if (AdditiveWornBonus) { + new_bonus->ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_CHANCE] += effect_value; + new_bonus->ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_NUM_ATKS] = limit_value ? limit_value : 1; + } + + if (new_bonus->ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_CHANCE] < effect_value) { + new_bonus->ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_CHANCE] = effect_value; + new_bonus->ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_NUM_ATKS] = limit_value ? limit_value : 1; + } + break; + } + + case SE_AddExtraAttackPct_1h_Secondary: + { + if (AdditiveWornBonus) { + new_bonus->ExtraAttackChanceSecondary[SBIndex::EXTRA_ATTACK_CHANCE] += effect_value; + new_bonus->ExtraAttackChanceSecondary[SBIndex::EXTRA_ATTACK_NUM_ATKS] = limit_value ? limit_value : 1; + } + + if (new_bonus->ExtraAttackChanceSecondary[SBIndex::EXTRA_ATTACK_CHANCE] < effect_value) { + new_bonus->ExtraAttackChanceSecondary[SBIndex::EXTRA_ATTACK_CHANCE] = effect_value; + new_bonus->ExtraAttackChanceSecondary[SBIndex::EXTRA_ATTACK_NUM_ATKS] = limit_value ? limit_value : 1; + } + break; + } + + case SE_Double_Melee_Round: + { + if (AdditiveWornBonus) { + new_bonus->DoubleMeleeRound[SBIndex::DOUBLE_MELEE_ROUND_CHANCE] += effect_value; + new_bonus->DoubleMeleeRound[SBIndex::DOUBLE_MELEE_ROUND_DMG_BONUS] += limit_value; + } + + if (new_bonus->DoubleMeleeRound[SBIndex::DOUBLE_MELEE_ROUND_CHANCE] < effect_value) { + new_bonus->DoubleMeleeRound[SBIndex::DOUBLE_MELEE_ROUND_CHANCE] = effect_value; + new_bonus->DoubleMeleeRound[SBIndex::DOUBLE_MELEE_ROUND_DMG_BONUS] = limit_value; + } + break; + } case SE_PercentXPIncrease: { @@ -2296,13 +2673,13 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne case SE_DeathSave: { - if(new_bonus->DeathSave[0] < effect_value) + if(new_bonus->DeathSave[SBIndex::DEATH_SAVE_TYPE] < effect_value) { - new_bonus->DeathSave[0] = effect_value; //1='Partial' 2='Full' - new_bonus->DeathSave[1] = buffslot; + new_bonus->DeathSave[SBIndex::DEATH_SAVE_TYPE] = effect_value; //1='Partial' 2='Full' + new_bonus->DeathSave[SBIndex::DEATH_SAVE_BUFFSLOT] = buffslot; //These are used in later expansion spell effects. - new_bonus->DeathSave[2] = base2;//Min level for HealAmt - new_bonus->DeathSave[3] = max;//HealAmt + new_bonus->DeathSave[SBIndex::DEATH_SAVE_MIN_LEVEL_FOR_HEAL] = limit_value;//Min level for HealAmt + new_bonus->DeathSave[SBIndex::DEATH_SAVE_HEAL_AMT] = max_value;//HealAmt } break; } @@ -2310,14 +2687,14 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne case SE_DivineSave: { if (AdditiveWornBonus) { - new_bonus->DivineSaveChance[0] += effect_value; - new_bonus->DivineSaveChance[1] = 0; + new_bonus->DivineSaveChance[SBIndex::DIVINE_SAVE_CHANCE] += effect_value; + new_bonus->DivineSaveChance[SBIndex::DIVINE_SAVE_SPELL_TRIGGER_ID] = 0; } - else if(new_bonus->DivineSaveChance[0] < effect_value) + else if(new_bonus->DivineSaveChance[SBIndex::DIVINE_SAVE_CHANCE] < effect_value) { - new_bonus->DivineSaveChance[0] = effect_value; - new_bonus->DivineSaveChance[1] = base2; + new_bonus->DivineSaveChance[SBIndex::DIVINE_SAVE_CHANCE] = effect_value; + new_bonus->DivineSaveChance[SBIndex::DIVINE_SAVE_SPELL_TRIGGER_ID] = limit_value; } break; } @@ -2329,7 +2706,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne case SE_Accuracy: { // Bad data or unsupported new skill - if (base2 > EQ::skills::HIGHEST_SKILL) + if (limit_value > EQ::skills::HIGHEST_SKILL) break; if ((effect_value < 0) && (new_bonus->Accuracy[EQ::skills::HIGHEST_SKILL + 1] > effect_value)) new_bonus->Accuracy[EQ::skills::HIGHEST_SKILL + 1] = effect_value; @@ -2355,39 +2732,26 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne case SE_SkillDamageTaken: { // Bad data or unsupported new skill - if (base2 > EQ::skills::HIGHEST_SKILL) + if (limit_value > EQ::skills::HIGHEST_SKILL) break; //When using npc_spells_effects if MAX value set, use stackable quest based modifier. - if (IsAISpellEffect && max){ - if(base2 == ALL_SKILLS) + if (IsAISpellEffect && max_value){ + if(limit_value == ALL_SKILLS) SkillDmgTaken_Mod[EQ::skills::HIGHEST_SKILL + 1] = effect_value; else - SkillDmgTaken_Mod[base2] = effect_value; + SkillDmgTaken_Mod[limit_value] = effect_value; } else { - if(base2 == ALL_SKILLS) + if(limit_value == ALL_SKILLS) new_bonus->SkillDmgTaken[EQ::skills::HIGHEST_SKILL + 1] += effect_value; else - new_bonus->SkillDmgTaken[base2] += effect_value; + new_bonus->SkillDmgTaken[limit_value] += effect_value; } break; } - case SE_TriggerOnCast: - { - for(int e = 0; e < MAX_SPELL_TRIGGER; e++) - { - if(!new_bonus->SpellTriggers[e]) - { - new_bonus->SpellTriggers[e] = spell_id; - break; - } - } - break; - } - case SE_SpellCritChance: new_bonus->CriticalSpellChance += effect_value; break; @@ -2396,8 +2760,8 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne { new_bonus->CriticalSpellChance += effect_value; - if (base2 > new_bonus->SpellCritDmgIncNoStack) - new_bonus->SpellCritDmgIncNoStack = base2; + if (limit_value > new_bonus->SpellCritDmgIncNoStack) + new_bonus->SpellCritDmgIncNoStack = limit_value; break; } @@ -2431,8 +2795,15 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne case SE_MitigateDamageShield: { - if (effect_value < 0) - effect_value = effect_value*-1; + /* + Bard songs have identical negative base value and positive max + The effect for the songs should increase mitigation. There are + spells that do decrease the mitigation with just negative base values. + To be consistent all values that increase mitigation will be set to positives + */ + if (max_value > 0 && effect_value < 0) { + effect_value = max_value; + } new_bonus->DSMitigationOffHand += effect_value; break; @@ -2449,9 +2820,9 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne if(!new_bonus->SpellOnKill[e]) { // Base2 = Spell to fire | Base1 = % chance | Base3 = min level - new_bonus->SpellOnKill[e] = base2; + new_bonus->SpellOnKill[e] = limit_value; new_bonus->SpellOnKill[e+1] = effect_value; - new_bonus->SpellOnKill[e+2] = max; + new_bonus->SpellOnKill[e+2] = max_value; break; } } @@ -2465,7 +2836,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne if(!new_bonus->SpellOnDeath[e]) { // Base2 = Spell to fire | Base1 = % chance - new_bonus->SpellOnDeath[e] = base2; + new_bonus->SpellOnDeath[e] = limit_value; new_bonus->SpellOnDeath[e+1] = effect_value; break; } @@ -2476,31 +2847,45 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne case SE_CriticalDamageMob: { // Bad data or unsupported new skill - if (base2 > EQ::skills::HIGHEST_SKILL) + if (limit_value > EQ::skills::HIGHEST_SKILL) break; - if(base2 == ALL_SKILLS) + if(limit_value == ALL_SKILLS) new_bonus->CritDmgMod[EQ::skills::HIGHEST_SKILL + 1] += effect_value; else - new_bonus->CritDmgMod[base2] += effect_value; + new_bonus->CritDmgMod[limit_value] += effect_value; + break; + } + + case SE_Critical_Melee_Damage_Mod_Max: + { + // Bad data or unsupported new skill + if (limit_value > EQ::skills::HIGHEST_SKILL) + break; + int skill = limit_value == ALL_SKILLS ? EQ::skills::HIGHEST_SKILL + 1 : limit_value; + if (effect_value < 0 && new_bonus->CritDmgModNoStack[skill] > effect_value) + new_bonus->CritDmgModNoStack[skill] = effect_value; + else if (effect_value > 0 && new_bonus->CritDmgModNoStack[skill] < effect_value) { + new_bonus->CritDmgModNoStack[skill] = effect_value; + } break; } case SE_ReduceSkillTimer: { - if(new_bonus->SkillReuseTime[base2] < effect_value) - new_bonus->SkillReuseTime[base2] = effect_value; + if(new_bonus->SkillReuseTime[limit_value] < effect_value) + new_bonus->SkillReuseTime[limit_value] = effect_value; break; } case SE_SkillDamageAmount: { // Bad data or unsupported new skill - if (base2 > EQ::skills::HIGHEST_SKILL) + if (limit_value > EQ::skills::HIGHEST_SKILL) break; - if(base2 == ALL_SKILLS) + if(limit_value == ALL_SKILLS) new_bonus->SkillDamageAmount[EQ::skills::HIGHEST_SKILL + 1] += effect_value; else - new_bonus->SkillDamageAmount[base2] += effect_value; + new_bonus->SkillDamageAmount[limit_value] += effect_value; break; } @@ -2535,48 +2920,44 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne case SE_LimitHPPercent: { - if(new_bonus->HPPercCap[0] != 0 && new_bonus->HPPercCap[0] > effect_value){ - new_bonus->HPPercCap[0] = effect_value; - new_bonus->HPPercCap[1] = base2; + if(new_bonus->HPPercCap[SBIndex::RESOURCE_PERCENT_CAP] != 0 && new_bonus->HPPercCap[SBIndex::RESOURCE_PERCENT_CAP] > effect_value){ + new_bonus->HPPercCap[SBIndex::RESOURCE_PERCENT_CAP] = effect_value; + new_bonus->HPPercCap[SBIndex::RESOURCE_AMOUNT_CAP] = limit_value; } - else if(new_bonus->HPPercCap[0] == 0){ - new_bonus->HPPercCap[0] = effect_value; - new_bonus->HPPercCap[1] = base2; + else if(new_bonus->HPPercCap[SBIndex::RESOURCE_PERCENT_CAP] == 0){ + new_bonus->HPPercCap[SBIndex::RESOURCE_PERCENT_CAP] = effect_value; + new_bonus->HPPercCap[SBIndex::RESOURCE_AMOUNT_CAP] = limit_value; } break; } case SE_LimitManaPercent: { - if(new_bonus->ManaPercCap[0] != 0 && new_bonus->ManaPercCap[0] > effect_value){ - new_bonus->ManaPercCap[0] = effect_value; - new_bonus->ManaPercCap[1] = base2; + if(new_bonus->ManaPercCap[SBIndex::RESOURCE_PERCENT_CAP] != 0 && new_bonus->ManaPercCap[SBIndex::RESOURCE_PERCENT_CAP] > effect_value){ + new_bonus->ManaPercCap[SBIndex::RESOURCE_PERCENT_CAP] = effect_value; + new_bonus->ManaPercCap[SBIndex::RESOURCE_AMOUNT_CAP] = limit_value; } - else if(new_bonus->ManaPercCap[0] == 0) { - new_bonus->ManaPercCap[0] = effect_value; - new_bonus->ManaPercCap[1] = base2; + else if(new_bonus->ManaPercCap[SBIndex::RESOURCE_PERCENT_CAP] == 0) { + new_bonus->ManaPercCap[SBIndex::RESOURCE_PERCENT_CAP] = effect_value; + new_bonus->ManaPercCap[SBIndex::RESOURCE_AMOUNT_CAP] = limit_value; } break; } case SE_LimitEndPercent: { - if(new_bonus->EndPercCap[0] != 0 && new_bonus->EndPercCap[0] > effect_value) { - new_bonus->EndPercCap[0] = effect_value; - new_bonus->EndPercCap[1] = base2; + if(new_bonus->EndPercCap[SBIndex::RESOURCE_PERCENT_CAP] != 0 && new_bonus->EndPercCap[SBIndex::RESOURCE_PERCENT_CAP] > effect_value) { + new_bonus->EndPercCap[SBIndex::RESOURCE_PERCENT_CAP] = effect_value; + new_bonus->EndPercCap[SBIndex::RESOURCE_AMOUNT_CAP] = limit_value; } - else if(new_bonus->EndPercCap[0] == 0){ - new_bonus->EndPercCap[0] = effect_value; - new_bonus->EndPercCap[1] = base2; + else if(new_bonus->EndPercCap[SBIndex::RESOURCE_PERCENT_CAP] == 0){ + new_bonus->EndPercCap[SBIndex::RESOURCE_PERCENT_CAP] = effect_value; + new_bonus->EndPercCap[SBIndex::RESOURCE_AMOUNT_CAP] = limit_value; } break; } - case SE_BlockNextSpellFocus: - new_bonus->BlockNextSpell = true; - break; - case SE_NegateSpellEffect: new_bonus->NegateEffects = true; break; @@ -2607,40 +2988,40 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne { //Lower the ratio the more favorable if((!new_bonus->HPToManaConvert) || (new_bonus->HPToManaConvert >= effect_value)) - new_bonus->HPToManaConvert = spells[spell_id].base[i]; + new_bonus->HPToManaConvert = spells[spell_id].base_value[i]; break; } case SE_SkillDamageAmount2: { // Bad data or unsupported new skill - if (base2 > EQ::skills::HIGHEST_SKILL) + if (limit_value > EQ::skills::HIGHEST_SKILL) break; - if(base2 == ALL_SKILLS) + if(limit_value == ALL_SKILLS) new_bonus->SkillDamageAmount2[EQ::skills::HIGHEST_SKILL + 1] += effect_value; else - new_bonus->SkillDamageAmount2[base2] += effect_value; + new_bonus->SkillDamageAmount2[limit_value] += effect_value; break; } case SE_NegateAttacks: { - if (!new_bonus->NegateAttacks[0] || - ((new_bonus->NegateAttacks[0] && new_bonus->NegateAttacks[2]) && (new_bonus->NegateAttacks[2] < max))){ - new_bonus->NegateAttacks[0] = 1; - new_bonus->NegateAttacks[1] = buffslot; - new_bonus->NegateAttacks[2] = max; + if (!new_bonus->NegateAttacks[SBIndex::NEGATE_ATK_EXISTS] || + ((new_bonus->NegateAttacks[SBIndex::NEGATE_ATK_EXISTS] && new_bonus->NegateAttacks[SBIndex::NEGATE_ATK_MAX_DMG_ABSORB_PER_HIT]) && (new_bonus->NegateAttacks[SBIndex::NEGATE_ATK_MAX_DMG_ABSORB_PER_HIT] < max_value))){ + new_bonus->NegateAttacks[SBIndex::NEGATE_ATK_EXISTS] = 1; + new_bonus->NegateAttacks[SBIndex::NEGATE_ATK_BUFFSLOT] = buffslot; + new_bonus->NegateAttacks[SBIndex::NEGATE_ATK_MAX_DMG_ABSORB_PER_HIT] = max_value; } break; } case SE_MitigateMeleeDamage: { - if (new_bonus->MitigateMeleeRune[0] < effect_value){ - new_bonus->MitigateMeleeRune[0] = effect_value; - new_bonus->MitigateMeleeRune[1] = buffslot; - new_bonus->MitigateMeleeRune[2] = base2; - new_bonus->MitigateMeleeRune[3] = max; + if (new_bonus->MitigateMeleeRune[SBIndex::MITIGATION_RUNE_PERCENT] < effect_value){ + new_bonus->MitigateMeleeRune[SBIndex::MITIGATION_RUNE_PERCENT] = effect_value; + new_bonus->MitigateMeleeRune[SBIndex::MITIGATION_RUNE_BUFFSLOT] = buffslot; + new_bonus->MitigateMeleeRune[SBIndex::MITIGATION_RUNE_MAX_DMG_ABSORB_PER_HIT] = limit_value; + new_bonus->MitigateMeleeRune[SBIndex::MITIGATION_RUNE_MAX_HP_AMT] = max_value; } break; } @@ -2648,51 +3029,67 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne case SE_MeleeThresholdGuard: { - if (new_bonus->MeleeThresholdGuard[0] < effect_value){ - new_bonus->MeleeThresholdGuard[0] = effect_value; - new_bonus->MeleeThresholdGuard[1] = buffslot; - new_bonus->MeleeThresholdGuard[2] = base2; + if (new_bonus->MeleeThresholdGuard[SBIndex::THRESHOLDGUARD_MITIGATION_PERCENT] < effect_value){ + new_bonus->MeleeThresholdGuard[SBIndex::THRESHOLDGUARD_MITIGATION_PERCENT] = effect_value; + new_bonus->MeleeThresholdGuard[SBIndex::THRESHOLDGUARD_BUFFSLOT] = buffslot; + new_bonus->MeleeThresholdGuard[SBIndex::THRESHOLDGUARD_MIN_DMG_TO_TRIGGER] = limit_value; } break; } case SE_SpellThresholdGuard: { - if (new_bonus->SpellThresholdGuard[0] < effect_value){ - new_bonus->SpellThresholdGuard[0] = effect_value; - new_bonus->SpellThresholdGuard[1] = buffslot; - new_bonus->SpellThresholdGuard[2] = base2; + if (new_bonus->SpellThresholdGuard[SBIndex::THRESHOLDGUARD_MITIGATION_PERCENT] < effect_value){ + new_bonus->SpellThresholdGuard[SBIndex::THRESHOLDGUARD_MITIGATION_PERCENT] = effect_value; + new_bonus->SpellThresholdGuard[SBIndex::THRESHOLDGUARD_BUFFSLOT] = buffslot; + new_bonus->SpellThresholdGuard[SBIndex::THRESHOLDGUARD_MIN_DMG_TO_TRIGGER] = limit_value; } break; } case SE_MitigateSpellDamage: { - if (new_bonus->MitigateSpellRune[0] < effect_value){ - new_bonus->MitigateSpellRune[0] = effect_value; - new_bonus->MitigateSpellRune[1] = buffslot; - new_bonus->MitigateSpellRune[2] = base2; - new_bonus->MitigateSpellRune[3] = max; + if (WornType) { + new_bonus->MitigateSpellRune[SBIndex::MITIGATION_RUNE_PERCENT] += effect_value; + } + + if (new_bonus->MitigateSpellRune[SBIndex::MITIGATION_RUNE_PERCENT] < effect_value){ + new_bonus->MitigateSpellRune[SBIndex::MITIGATION_RUNE_PERCENT] = effect_value; + new_bonus->MitigateSpellRune[SBIndex::MITIGATION_RUNE_BUFFSLOT] = buffslot; + new_bonus->MitigateSpellRune[SBIndex::MITIGATION_RUNE_MAX_DMG_ABSORB_PER_HIT] = limit_value; + new_bonus->MitigateSpellRune[SBIndex::MITIGATION_RUNE_MAX_HP_AMT] = max_value; } break; } case SE_MitigateDotDamage: { - if (new_bonus->MitigateDotRune[0] < effect_value){ - new_bonus->MitigateDotRune[0] = effect_value; - new_bonus->MitigateDotRune[1] = buffslot; - new_bonus->MitigateDotRune[2] = base2; - new_bonus->MitigateDotRune[3] = max; + if (WornType) { + new_bonus->MitigateDotRune[SBIndex::MITIGATION_RUNE_PERCENT] += effect_value; + } + + if (new_bonus->MitigateDotRune[SBIndex::MITIGATION_RUNE_PERCENT] < effect_value){ + new_bonus->MitigateDotRune[SBIndex::MITIGATION_RUNE_PERCENT] = effect_value; + new_bonus->MitigateDotRune[SBIndex::MITIGATION_RUNE_BUFFSLOT] = buffslot; + new_bonus->MitigateDotRune[SBIndex::MITIGATION_RUNE_MAX_DMG_ABSORB_PER_HIT] = limit_value; + new_bonus->MitigateDotRune[SBIndex::MITIGATION_RUNE_MAX_HP_AMT] = max_value; } break; } case SE_ManaAbsorbPercentDamage: { - if (new_bonus->ManaAbsorbPercentDamage[0] < effect_value){ - new_bonus->ManaAbsorbPercentDamage[0] = effect_value; - new_bonus->ManaAbsorbPercentDamage[1] = buffslot; + if (new_bonus->ManaAbsorbPercentDamage < effect_value){ + new_bonus->ManaAbsorbPercentDamage = effect_value; + } + break; + } + + case SE_Endurance_Absorb_Pct_Damage: + { + if (new_bonus->EnduranceAbsorbPercentDamage[SBIndex::ENDURANCE_ABSORD_MITIGIATION] < effect_value) { + new_bonus->EnduranceAbsorbPercentDamage[SBIndex::ENDURANCE_ABSORD_MITIGIATION] = effect_value; + new_bonus->EnduranceAbsorbPercentDamage[SBIndex::ENDURANCE_ABSORD_DRAIN_PER_HP] = limit_value; } break; } @@ -2762,6 +3159,10 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne new_bonus->FrontalBackstabChance += effect_value; break; + case SE_Double_Backstab_Front: + new_bonus->Double_Backstab_Front += effect_value; + break; + case SE_ConsumeProjectile: new_bonus->ConsumeProjectile += effect_value; break; @@ -2804,7 +3205,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; case SE_AddSingingMod: - switch (base2) { + switch (limit_value) { case EQ::item::ItemTypeWindInstrument: new_bonus->windMod += effect_value; break; @@ -2898,13 +3299,13 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne { for(int e = 0; e < MAX_RESISTABLE_EFFECTS*2; e+=2) { - if(new_bonus->SEResist[e+1] && (new_bonus->SEResist[e] == base2) && (new_bonus->SEResist[e+1] < effect_value)){ - new_bonus->SEResist[e] = base2; //Spell Effect ID + if(new_bonus->SEResist[e+1] && (new_bonus->SEResist[e] == limit_value) && (new_bonus->SEResist[e+1] < effect_value)){ + new_bonus->SEResist[e] = limit_value; //Spell Effect ID new_bonus->SEResist[e+1] = effect_value; //Resist Chance break; } else if (!new_bonus->SEResist[e+1]){ - new_bonus->SEResist[e] = base2; //Spell Effect ID + new_bonus->SEResist[e] = limit_value; //Spell Effect ID new_bonus->SEResist[e+1] = effect_value; //Resist Chance break; } @@ -2927,24 +3328,24 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne case SE_GiveDoubleRiposte: { //Only allow for regular double riposte chance. - if(new_bonus->GiveDoubleRiposte[base2] == 0){ - if(new_bonus->GiveDoubleRiposte[0] < effect_value) - new_bonus->GiveDoubleRiposte[0] = effect_value; + if(new_bonus->GiveDoubleRiposte[limit_value] == 0){ + if(new_bonus->GiveDoubleRiposte[SBIndex::DOUBLE_RIPOSTE_CHANCE] < effect_value) + new_bonus->GiveDoubleRiposte[SBIndex::DOUBLE_RIPOSTE_CHANCE] = effect_value; } break; } case SE_SlayUndead: { - if(new_bonus->SlayUndead[1] < effect_value) - new_bonus->SlayUndead[0] = effect_value; // Rate - new_bonus->SlayUndead[1] = base2; // Damage Modifier + if(new_bonus->SlayUndead[SBIndex::SLAYUNDEAD_DMG_MOD] < effect_value) + new_bonus->SlayUndead[SBIndex::SLAYUNDEAD_RATE_MOD] = effect_value; // Rate + new_bonus->SlayUndead[SBIndex::SLAYUNDEAD_DMG_MOD] = limit_value; // Damage Modifier break; } case SE_TriggerOnReqTarget: case SE_TriggerOnReqCaster: - new_bonus->TriggerOnValueAmount = true; + new_bonus->TriggerOnCastRequirement = true; break; case SE_DivineAura: @@ -2952,10 +3353,10 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; case SE_ImprovedTaunt: - if (new_bonus->ImprovedTaunt[0] < effect_value) { - new_bonus->ImprovedTaunt[0] = effect_value; - new_bonus->ImprovedTaunt[1] = base2; - new_bonus->ImprovedTaunt[2] = buffslot; + if (new_bonus->ImprovedTaunt[SBIndex::IMPROVED_TAUNT_MAX_LVL] < effect_value) { + new_bonus->ImprovedTaunt[SBIndex::IMPROVED_TAUNT_MAX_LVL] = effect_value; + new_bonus->ImprovedTaunt[SBIndex::IMPROVED_TAUNT_AGGRO_MOD] = limit_value; + new_bonus->ImprovedTaunt[SBIndex::IMPROVED_TAUNT_BUFFSLOT] = buffslot; } break; @@ -2965,42 +3366,42 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; case SE_FrenziedDevastation: - new_bonus->FrenziedDevastation += base2; + new_bonus->FrenziedDevastation += limit_value; break; case SE_Root: - if (new_bonus->Root[0] && (new_bonus->Root[1] > buffslot)){ - new_bonus->Root[0] = 1; - new_bonus->Root[1] = buffslot; + if (new_bonus->Root[SBIndex::ROOT_EXISTS] && (new_bonus->Root[SBIndex::ROOT_BUFFSLOT] > buffslot)){ + new_bonus->Root[SBIndex::ROOT_EXISTS] = 1; + new_bonus->Root[SBIndex::ROOT_BUFFSLOT] = buffslot; } - else if (!new_bonus->Root[0]){ - new_bonus->Root[0] = 1; - new_bonus->Root[1] = buffslot; + else if (!new_bonus->Root[SBIndex::ROOT_EXISTS]){ + new_bonus->Root[SBIndex::ROOT_EXISTS] = 1; + new_bonus->Root[SBIndex::ROOT_BUFFSLOT] = buffslot; } break; case SE_Rune: - if (new_bonus->MeleeRune[0] && (new_bonus->MeleeRune[1] > buffslot)){ + if (new_bonus->MeleeRune[SBIndex::RUNE_AMOUNT] && (new_bonus->MeleeRune[SBIndex::RUNE_BUFFSLOT] > buffslot)){ - new_bonus->MeleeRune[0] = effect_value; - new_bonus->MeleeRune[1] = buffslot; + new_bonus->MeleeRune[SBIndex::RUNE_AMOUNT] = effect_value; + new_bonus->MeleeRune[SBIndex::RUNE_BUFFSLOT] = buffslot; } - else if (!new_bonus->MeleeRune[0]){ - new_bonus->MeleeRune[0] = effect_value; - new_bonus->MeleeRune[1] = buffslot; + else if (!new_bonus->MeleeRune[SBIndex::RUNE_AMOUNT]){ + new_bonus->MeleeRune[SBIndex::RUNE_AMOUNT] = effect_value; + new_bonus->MeleeRune[SBIndex::RUNE_BUFFSLOT] = buffslot; } break; case SE_AbsorbMagicAtt: - if (new_bonus->AbsorbMagicAtt[0] && (new_bonus->AbsorbMagicAtt[1] > buffslot)){ - new_bonus->AbsorbMagicAtt[0] = effect_value; - new_bonus->AbsorbMagicAtt[1] = buffslot; + if (new_bonus->AbsorbMagicAtt[SBIndex::RUNE_AMOUNT] && (new_bonus->AbsorbMagicAtt[SBIndex::RUNE_BUFFSLOT] > buffslot)){ + new_bonus->AbsorbMagicAtt[SBIndex::RUNE_AMOUNT] = effect_value; + new_bonus->AbsorbMagicAtt[SBIndex::RUNE_BUFFSLOT] = buffslot; } - else if (!new_bonus->AbsorbMagicAtt[0]){ - new_bonus->AbsorbMagicAtt[0] = effect_value; - new_bonus->AbsorbMagicAtt[1] = buffslot; + else if (!new_bonus->AbsorbMagicAtt[SBIndex::RUNE_AMOUNT]){ + new_bonus->AbsorbMagicAtt[SBIndex::RUNE_AMOUNT] = effect_value; + new_bonus->AbsorbMagicAtt[SBIndex::RUNE_BUFFSLOT] = buffslot; } break; @@ -3033,23 +3434,23 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; case SE_AStacker: - new_bonus->AStacker[0] = 1; - new_bonus->AStacker[1] = effect_value; + new_bonus->AStacker[SBIndex::BUFFSTACKER_EXISTS] = 1; + new_bonus->AStacker[SBIndex::BUFFSTACKER_VALUE] = effect_value; break; case SE_BStacker: - new_bonus->BStacker[0] = 1; - new_bonus->BStacker[1] = effect_value; + new_bonus->BStacker[SBIndex::BUFFSTACKER_EXISTS] = 1; + new_bonus->BStacker[SBIndex::BUFFSTACKER_VALUE] = effect_value; break; case SE_CStacker: - new_bonus->CStacker[0] = 1; - new_bonus->CStacker[1] = effect_value; + new_bonus->CStacker[SBIndex::BUFFSTACKER_EXISTS] = 1; + new_bonus->CStacker[SBIndex::BUFFSTACKER_VALUE] = effect_value; break; case SE_DStacker: - new_bonus->DStacker[0] = 1; - new_bonus->DStacker[1] = effect_value; + new_bonus->DStacker[SBIndex::BUFFSTACKER_EXISTS] = 1; + new_bonus->DStacker[SBIndex::BUFFSTACKER_VALUE] = effect_value; break; case SE_Berserk: @@ -3073,36 +3474,36 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne case SE_HeadShot: { - if(new_bonus->HeadShot[1] < base2){ - new_bonus->HeadShot[0] = effect_value; - new_bonus->HeadShot[1] = base2; + if(new_bonus->HeadShot[SBIndex::FINISHING_EFFECT_DMG] < limit_value){ + new_bonus->HeadShot[SBIndex::FINISHING_EFFECT_PROC_CHANCE] = effect_value; + new_bonus->HeadShot[SBIndex::FINISHING_EFFECT_DMG] = limit_value; } break; } case SE_HeadShotLevel: { - if(new_bonus->HSLevel[0] < effect_value) { - new_bonus->HSLevel[0] = effect_value; - new_bonus->HSLevel[1] = base2; + if(new_bonus->HSLevel[SBIndex::FINISHING_EFFECT_LEVEL_MAX] < effect_value) { + new_bonus->HSLevel[SBIndex::FINISHING_EFFECT_LEVEL_MAX] = effect_value; + new_bonus->HSLevel[SBIndex::FINISHING_EFFECT_LEVEL_CHANCE_BONUS] = limit_value; } break; } case SE_Assassinate: { - if(new_bonus->Assassinate[1] < base2){ - new_bonus->Assassinate[0] = effect_value; - new_bonus->Assassinate[1] = base2; + if(new_bonus->Assassinate[SBIndex::FINISHING_EFFECT_DMG] < limit_value){ + new_bonus->Assassinate[SBIndex::FINISHING_EFFECT_PROC_CHANCE] = effect_value; + new_bonus->Assassinate[SBIndex::FINISHING_EFFECT_DMG] = limit_value; } break; } case SE_AssassinateLevel: { - if(new_bonus->AssassinateLevel[0] < effect_value) { - new_bonus->AssassinateLevel[0] = effect_value; - new_bonus->AssassinateLevel[1] = base2; + if(new_bonus->AssassinateLevel[SBIndex::FINISHING_EFFECT_LEVEL_MAX] < effect_value) { + new_bonus->AssassinateLevel[SBIndex::FINISHING_EFFECT_LEVEL_MAX] = effect_value; + new_bonus->AssassinateLevel[SBIndex::FINISHING_EFFECT_LEVEL_CHANCE_BONUS] = limit_value; } break; } @@ -3110,19 +3511,18 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne case SE_FinishingBlow: { //base1 = chance, base2 = damage - if (new_bonus->FinishingBlow[1] < base2){ - new_bonus->FinishingBlow[0] = effect_value; - new_bonus->FinishingBlow[1] = base2; + if (new_bonus->FinishingBlow[SBIndex::FINISHING_EFFECT_DMG] < limit_value){ + new_bonus->FinishingBlow[SBIndex::FINISHING_EFFECT_PROC_CHANCE] = effect_value; + new_bonus->FinishingBlow[SBIndex::FINISHING_EFFECT_DMG] = limit_value; } break; } case SE_FinishingBlowLvl: { - //base1 = level, base2 = ??? (Set to 200 in AA data, possible proc rate mod?) - if (new_bonus->FinishingBlowLvl[0] < effect_value){ - new_bonus->FinishingBlowLvl[0] = effect_value; - new_bonus->FinishingBlowLvl[1] = base2; + if (new_bonus->FinishingBlowLvl[SBIndex::FINISHING_EFFECT_LEVEL_MAX] < effect_value){ + new_bonus->FinishingBlowLvl[SBIndex::FINISHING_EFFECT_LEVEL_MAX] = effect_value; + new_bonus->FinishingBlowLvl[SBIndex::FINISHING_BLOW_LEVEL_HP_RATIO] = limit_value; } break; } @@ -3145,21 +3545,27 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } + case SE_Illusion: + new_bonus->Illusion = spell_id; + break; + case SE_IllusionPersistence: - new_bonus->IllusionPersistence = true; + new_bonus->IllusionPersistence = effect_value; break; case SE_LimitToSkill:{ // Bad data or unsupported new skill - if (base2 > EQ::skills::HIGHEST_SKILL) + if (effect_value > EQ::skills::HIGHEST_SKILL) { break; + } if (effect_value <= EQ::skills::HIGHEST_SKILL){ new_bonus->LimitToSkill[effect_value] = true; + new_bonus->LimitToSkill[EQ::skills::HIGHEST_SKILL + 3] = true; //Used as a general exists check } break; } - case SE_SkillProc:{ + case SE_SkillProcAttempt:{ for(int e = 0; e < MAX_SKILL_PROCS; e++) { @@ -3189,14 +3595,36 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } + case SE_SkillAttackProc: { + for (int i = 0; i < MAX_CAST_ON_SKILL_USE; i += 3) { + if (!new_bonus->SkillAttackProc[i + SBIndex::SKILLATK_PROC_SPELL_ID]) { // spell id + new_bonus->SkillAttackProc[i + SBIndex::SKILLATK_PROC_SPELL_ID] = max_value; // spell to proc + new_bonus->SkillAttackProc[i + SBIndex::SKILLATK_PROC_CHANCE] = effect_value; // Chance base 1000 = 100% proc rate + new_bonus->SkillAttackProc[i + SBIndex::SKILLATK_PROC_SKILL] = limit_value; // Skill to Proc Offr + + if (limit_value < EQ::skills::HIGHEST_SKILL) { + new_bonus->HasSkillAttackProc[limit_value] = true; //check first before looking for any effects. + } + break; + } + } + } + case SE_PC_Pet_Rampage: { - new_bonus->PC_Pet_Rampage[0] += effect_value; //Chance to rampage - if (new_bonus->PC_Pet_Rampage[1] < base2) - new_bonus->PC_Pet_Rampage[1] = base2; //Damage modifer - take highest + new_bonus->PC_Pet_Rampage[SBIndex::PET_RAMPAGE_CHANCE] += effect_value; //Chance to rampage + if (new_bonus->PC_Pet_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] < limit_value) + new_bonus->PC_Pet_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] = limit_value; //Damage modifer - take highest break; } - case SE_PC_Pet_Flurry_Chance: + case SE_PC_Pet_AE_Rampage: { + new_bonus->PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_CHANCE] += effect_value; //Chance to rampage + if (new_bonus->PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] < limit_value) + new_bonus->PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] = limit_value; //Damage modifer - take highest + break; + } + + case SE_PC_Pet_Flurry_Chance: new_bonus->PC_Pet_Flurry += effect_value; //Chance to Flurry break; @@ -3210,10 +3638,10 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne case SE_ReduceTradeskillFail:{ - if (base2 > EQ::skills::HIGHEST_SKILL) + if (limit_value > EQ::skills::HIGHEST_SKILL) break; - new_bonus->ReduceTradeskillFail[base2] += effect_value; + new_bonus->ReduceTradeskillFail[limit_value] += effect_value; break; } @@ -3223,11 +3651,11 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; case SE_RaiseSkillCap: { - if (base2 > EQ::skills::HIGHEST_SKILL) + if (limit_value > EQ::skills::HIGHEST_SKILL) break; - - if (new_bonus->RaiseSkillCap[base2] < effect_value) - new_bonus->RaiseSkillCap[base2] = effect_value; + + if (new_bonus->RaiseSkillCap[limit_value] < effect_value) + new_bonus->RaiseSkillCap[limit_value] = effect_value; break; } @@ -3250,13 +3678,170 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne if (new_bonus->trap_slots < effect_value) new_bonus->trap_slots = effect_value; break; - + + case SE_Attack_Accuracy_Max_Percent: + new_bonus->Attack_Accuracy_Max_Percent += effect_value; + break; + + + case SE_AC_Mitigation_Max_Percent: + new_bonus->AC_Mitigation_Max_Percent += effect_value; + break; + + case SE_AC_Avoidance_Max_Percent: + new_bonus->AC_Avoidance_Max_Percent += effect_value; + break; + + case SE_Damage_Taken_Position_Mod: + { + //Mitigate if damage taken from behind base2 = 0, from front base2 = 1 + if (limit_value < 0 || limit_value > 2) + break; + if (AdditiveWornBonus) + new_bonus->Damage_Taken_Position_Mod[limit_value] += effect_value; + else if (effect_value < 0 && new_bonus->Damage_Taken_Position_Mod[limit_value] > effect_value) + new_bonus->Damage_Taken_Position_Mod[limit_value] = effect_value; + else if (effect_value > 0 && new_bonus->Damage_Taken_Position_Mod[limit_value] < effect_value) + new_bonus->Damage_Taken_Position_Mod[limit_value] = effect_value; + break; + } + + case SE_Melee_Damage_Position_Mod: + { + //Increase damage by percent from behind base2 = 0, from front base2 = 1 + if (limit_value < 0 || limit_value > 2) + break; + if (AdditiveWornBonus) + new_bonus->Melee_Damage_Position_Mod[limit_value] += effect_value; + else if (effect_value < 0 && new_bonus->Melee_Damage_Position_Mod[limit_value] > effect_value) + new_bonus->Melee_Damage_Position_Mod[limit_value] = effect_value; + else if (effect_value > 0 && new_bonus->Melee_Damage_Position_Mod[limit_value] < effect_value) + new_bonus->Melee_Damage_Position_Mod[limit_value] = effect_value; + break; + } + + case SE_Damage_Taken_Position_Amt: + { + //Mitigate if damage taken from behind base2 = 0, from front base2 = 1 + if (limit_value < 0 || limit_value > 2) + break; + + new_bonus->Damage_Taken_Position_Amt[limit_value] += effect_value; + break; + } + + case SE_Melee_Damage_Position_Amt: + { + //Mitigate if damage taken from behind base2 = 0, from front base2 = 1 + if (limit_value < 0 || limit_value > 2) + break; + + new_bonus->Melee_Damage_Position_Amt[limit_value] += effect_value; + break; + } + + case SE_DS_Mitigation_Amount: + new_bonus->DS_Mitigation_Amount += effect_value; + break; + + case SE_DS_Mitigation_Percentage: + new_bonus->DS_Mitigation_Percentage += effect_value; + break; + + case SE_Pet_Crit_Melee_Damage_Pct_Owner: + new_bonus->Pet_Crit_Melee_Damage_Pct_Owner += effect_value; + break; + + case SE_Pet_Add_Atk: + new_bonus->Pet_Add_Atk += effect_value; + break; + + case SE_ExtendedShielding: + { + if (AdditiveWornBonus) { + new_bonus->ExtendedShielding += effect_value; + } + else if (effect_value < 0 && new_bonus->ExtendedShielding > effect_value){ + new_bonus->ExtendedShielding = effect_value; + } + else if (effect_value > 0 && new_bonus->ExtendedShielding < effect_value){ + new_bonus->ExtendedShielding = effect_value; + } + break; + } + + case SE_ShieldDuration: + { + if (AdditiveWornBonus) { + new_bonus->ShieldDuration += effect_value; + } + else if (effect_value < 0 && new_bonus->ShieldDuration > effect_value){ + new_bonus->ShieldDuration = effect_value; + } + else if (effect_value > 0 && new_bonus->ShieldDuration < effect_value){ + new_bonus->ShieldDuration = effect_value; + } + break; + } + + case SE_Worn_Endurance_Regen_Cap: + new_bonus->ItemEnduranceRegenCap += effect_value; + break; + + case SE_ItemManaRegenCapIncrease: + new_bonus->ItemManaRegenCap += effect_value; + break; + + case SE_Weapon_Stance: { + if (IsValidSpell(effect_value)) { //base1 is the spell_id of buff + if (limit_value <= WEAPON_STANCE_TYPE_MAX) { //0=2H, 1=Shield, 2=DW + if (IsValidSpell(new_bonus->WeaponStance[limit_value])) { //Check if we already a spell_id saved for this effect + if (spells[new_bonus->WeaponStance[limit_value]].rank < spells[effect_value].rank) { //If so, check if any new spellids with higher rank exist (live spells for this are ranked). + new_bonus->WeaponStance[limit_value] = effect_value; //Overwrite with new effect + SetWeaponStanceEnabled(true); + + if (WornType) { + weaponstance.itembonus_enabled = true; + } + else { + weaponstance.spellbonus_enabled = true; + } + } + } + else { + new_bonus->WeaponStance[limit_value] = effect_value; //If no prior effect exists, then apply + SetWeaponStanceEnabled(true); + + if (WornType) { + weaponstance.itembonus_enabled = true; + } + else { + weaponstance.spellbonus_enabled = true; + } + } + } + } + break; + } + + case SE_ZoneSuspendMinion: + new_bonus->ZoneSuspendMinion = effect_value; + break; + + case SE_CompleteHeal: + new_bonus->CompleteHealBuffBlocker = true; + break; + + case SE_TrapCircumvention: + new_bonus->TrapCircumvention += effect_value; + break; + //Special custom cases for loading effects on to NPC from 'npc_spels_effects' table if (IsAISpellEffect) { //Non-Focused Effect to modify incoming spell damage by resist type. case SE_FcSpellVulnerability: - ModVulnerability(base2, effect_value); + ModVulnerability(limit_value, effect_value); break; } } @@ -3303,7 +3888,7 @@ void NPC::CalcItemBonuses(StatBonuses *newbon) newbon->DamageShield += cur->DamageShield; } if(cur->SpellShield > 0) { - newbon->SpellDamageShield += cur->SpellShield; + newbon->SpellShield += cur->SpellShield; } if(cur->Shielding > 0) { newbon->MeleeMitigation += cur->Shielding; @@ -3382,7 +3967,7 @@ bool Client::CalcItemScale(uint32 slot_x, uint32 slot_y) { if (Trader) if (i >= EQ::invbag::GENERAL_BAGS_BEGIN && i <= EQ::invbag::GENERAL_BAGS_END) { EQ::ItemInstance* parent_item = m_inv.GetItem(EQ::InventoryProfile::CalcSlotId(i)); - if (parent_item && parent_item->GetItem()->ID == 17899) // trader satchel + if (parent_item && parent_item->GetItem()->BagType == EQ::item::BagTypeTradersSatchel) continue; } @@ -3468,7 +4053,7 @@ bool Client::DoItemEnterZone(uint32 slot_x, uint32 slot_y) { if (Trader) if (i >= EQ::invbag::GENERAL_BAGS_BEGIN && i <= EQ::invbag::GENERAL_BAGS_END) { EQ::ItemInstance* parent_item = m_inv.GetItem(EQ::InventoryProfile::CalcSlotId(i)); - if (parent_item && parent_item->GetItem()->ID == 17899) // trader satchel + if (parent_item && parent_item->GetItem()->BagType == EQ::item::BagTypeTradersSatchel) continue; } @@ -3532,7 +4117,7 @@ uint8 Mob::IsFocusEffect(uint16 spell_id,int effect_index, bool AA,uint32 aa_eff uint16 effect = 0; if (!AA) - effect = spells[spell_id].effectid[effect_index]; + effect = spells[spell_id].effect_id[effect_index]; else effect = aa_effect; @@ -3562,18 +4147,24 @@ uint8 Mob::IsFocusEffect(uint16 spell_id,int effect_index, bool AA,uint32 aa_eff return focusPetPower; case SE_SpellResistReduction: return focusResistRate; + case SE_Fc_ResistIncoming: + focusFcResistIncoming; + case SE_Fc_Amplify_Mod: + focusFcAmplifyMod; + case SE_Fc_Amplify_Amt: + focusFcAmplifyAmt; case SE_SpellHateMod: return focusSpellHateMod; case SE_ReduceReuseTimer: return focusReduceRecastTime; case SE_TriggerOnCast: - //return focusTriggerOnCast; - return 0; //This is calculated as an actual bonus + return focusTriggerOnCast; case SE_FcSpellVulnerability: return focusSpellVulnerability; + case SE_Fc_Spell_Damage_Pct_IncomingPC: + return focusFcSpellDamagePctIncomingPC; case SE_BlockNextSpellFocus: - //return focusBlockNextSpell; - return 0; //This is calculated as an actual bonus + return focusBlockNextSpell; case SE_FcTwincast: return focusTwincast; case SE_SympatheticProc: @@ -3588,6 +4179,8 @@ uint8 Mob::IsFocusEffect(uint16 spell_id,int effect_index, bool AA,uint32 aa_eff return focusFcDamagePctCrit; case SE_FcDamageAmtIncoming: return focusFcDamageAmtIncoming; + case SE_Fc_Spell_Damage_Amt_IncomingPC: + return focusFcSpellDamageAmtIncomingPC; case SE_FcHealAmtIncoming: return focusFcHealAmtIncoming; case SE_FcHealPctIncoming: @@ -3602,439 +4195,487 @@ uint8 Mob::IsFocusEffect(uint16 spell_id,int effect_index, bool AA,uint32 aa_eff return focusFcMute; case SE_FcTimerRefresh: return focusFcTimerRefresh; + case SE_FcTimerLockout: + return focusFcTimerLockout; + case SE_Fc_Cast_Spell_On_Land: + return focusFcCastSpellOnLand; case SE_FcStunTimeMod: return focusFcStunTimeMod; + case SE_Fc_CastTimeMod2: + return focusFcCastTimeMod2; + case SE_Fc_CastTimeAmt: + return focusFcCastTimeAmt; case SE_FcHealPctCritIncoming: return focusFcHealPctCritIncoming; case SE_FcHealAmt: return focusFcHealAmt; case SE_FcHealAmtCrit: return focusFcHealAmtCrit; + } return 0; } -void Mob::NegateSpellsBonuses(uint16 spell_id) +void Mob::NegateSpellEffectBonuses(uint16 spell_id) { - if(!IsValidSpell(spell_id)) + if (!IsValidSpell(spell_id)) return; int effect_value = 0; for (int i = 0; i < EFFECT_COUNT; i++) { - if (spells[spell_id].effectid[i] == SE_NegateSpellEffect){ + bool negate_spellbonus = false; + bool negate_aabonus = false; + bool negate_itembonus = false; + + if (spells[spell_id].effect_id[i] == SE_NegateSpellEffect) { + + //Set negate types + switch (spells[spell_id].base_value[i]) + { + case NEGATE_SPA_ALL_BONUSES: + negate_spellbonus = true; + negate_aabonus = true; + negate_itembonus = true; + break; + + case NEGATE_SPA_SPELLBONUS: + negate_spellbonus = true; + break; + + case NEGATE_SPA_ITEMBONUS: + negate_itembonus = true; + break; + + case NEGATE_SPA_AABONUS: + negate_aabonus = true; + break; + + case NEGATE_SPA_ITEMBONUS_AND_AABONUS: + negate_aabonus = true; + negate_itembonus = true; + break; + + case NEGATE_SPA_SPELLBONUS_AND_AABONUS: + negate_aabonus = true; + negate_spellbonus = true; + break; + + case NEGATE_SPA_SPELLBONUS_AND_ITEMBONUS: + negate_spellbonus = true; + negate_itembonus = true; + break; + } //Negate focus effects - for(int e = 0; e < HIGHEST_FOCUS+1; e++) + for (int e = 0; e < HIGHEST_FOCUS + 1; e++) { - if (spellbonuses.FocusEffects[e] == spells[spell_id].base2[i]) + if (spellbonuses.FocusEffects[e] == spells[spell_id].limit_value[i]) { - spellbonuses.FocusEffects[e] = effect_value; + if (negate_spellbonus) { spellbonuses.FocusEffects[e] = effect_value; } continue; } } //Negate bonuses - switch (spells[spell_id].base2[i]) + switch (spells[spell_id].limit_value[i]) { case SE_CurrentHP: - if(spells[spell_id].base[i] == 1) { - spellbonuses.HPRegen = effect_value; - aabonuses.HPRegen = effect_value; - itembonuses.HPRegen = effect_value; - } + if (negate_spellbonus) { spellbonuses.HPRegen = effect_value; } + if (negate_aabonus) { aabonuses.HPRegen = effect_value; } + if (negate_itembonus) { itembonuses.HPRegen = effect_value; } break; case SE_CurrentEndurance: - spellbonuses.EnduranceRegen = effect_value; - aabonuses.EnduranceRegen = effect_value; - itembonuses.EnduranceRegen = effect_value; + if (negate_spellbonus) { spellbonuses.EnduranceRegen = effect_value; } + if (negate_aabonus) { aabonuses.EnduranceRegen = effect_value; } + if (negate_itembonus) { itembonuses.EnduranceRegen = effect_value; } break; case SE_ChangeFrenzyRad: - spellbonuses.AggroRange = static_cast(effect_value); - aabonuses.AggroRange = static_cast(effect_value); - itembonuses.AggroRange = static_cast(effect_value); + if (negate_spellbonus) { spellbonuses.AggroRange = static_cast(effect_value); } + if (negate_aabonus) { aabonuses.AggroRange = static_cast(effect_value); } + if (negate_itembonus) { itembonuses.AggroRange = static_cast(effect_value); } break; case SE_Harmony: - spellbonuses.AssistRange = static_cast(effect_value); - aabonuses.AssistRange = static_cast(effect_value); - itembonuses.AssistRange = static_cast(effect_value); + if (negate_spellbonus) { spellbonuses.AssistRange = static_cast(effect_value); } + if (negate_aabonus) { aabonuses.AssistRange = static_cast(effect_value); } + if (negate_itembonus) { itembonuses.AssistRange = static_cast(effect_value); } break; case SE_AttackSpeed: - spellbonuses.haste = effect_value; - aabonuses.haste = effect_value; - itembonuses.haste = effect_value; + if (negate_spellbonus) { spellbonuses.haste = effect_value; } + if (negate_aabonus) { aabonuses.haste = effect_value; } + if (negate_itembonus) { itembonuses.haste = effect_value; } break; case SE_AttackSpeed2: - spellbonuses.hastetype2 = effect_value; - aabonuses.hastetype2 = effect_value; - itembonuses.hastetype2 = effect_value; + if (negate_spellbonus) { spellbonuses.hastetype2 = effect_value; } + if (negate_aabonus) { aabonuses.hastetype2 = effect_value; } + if (negate_itembonus) { itembonuses.hastetype2 = effect_value; } break; case SE_AttackSpeed3: { if (effect_value > 0) { - spellbonuses.hastetype3 = effect_value; - aabonuses.hastetype3 = effect_value; - itembonuses.hastetype3 = effect_value; + if (negate_spellbonus) { spellbonuses.hastetype3 = effect_value; } + if (negate_aabonus) { aabonuses.hastetype3 = effect_value; } + if (negate_itembonus) { itembonuses.hastetype3 = effect_value; } } break; } case SE_AttackSpeed4: - spellbonuses.inhibitmelee = effect_value; - aabonuses.inhibitmelee = effect_value; - itembonuses.inhibitmelee = effect_value; + if (negate_spellbonus) { spellbonuses.inhibitmelee = effect_value; } + if (negate_aabonus) { aabonuses.inhibitmelee = effect_value; } + if (negate_itembonus) { itembonuses.inhibitmelee = effect_value; } break; case SE_TotalHP: - spellbonuses.HP = effect_value; - aabonuses.HP = effect_value; - itembonuses.HP = effect_value; + if (negate_spellbonus) { spellbonuses.HP = effect_value; } + if (negate_aabonus) { aabonuses.HP = effect_value; } + if (negate_itembonus) { itembonuses.HP = effect_value; } break; case SE_ManaRegen_v2: case SE_CurrentMana: - spellbonuses.ManaRegen = effect_value; - aabonuses.ManaRegen = effect_value; - itembonuses.ManaRegen = effect_value; + if (negate_spellbonus) { spellbonuses.ManaRegen = effect_value; } + if (negate_aabonus) { aabonuses.ManaRegen = effect_value; } + if (negate_itembonus) { itembonuses.ManaRegen = effect_value; } break; case SE_ManaPool: - spellbonuses.Mana = effect_value; - itembonuses.Mana = effect_value; - aabonuses.Mana = effect_value; + if (negate_spellbonus) { spellbonuses.Mana = effect_value; } + if (negate_itembonus) { itembonuses.Mana = effect_value; } + if (negate_aabonus) { aabonuses.Mana = effect_value; } break; case SE_Stamina: - spellbonuses.EnduranceReduction = effect_value; - itembonuses.EnduranceReduction = effect_value; - aabonuses.EnduranceReduction = effect_value; + if (negate_spellbonus) { spellbonuses.EnduranceReduction = effect_value; } + if (negate_itembonus) { itembonuses.EnduranceReduction = effect_value; } + if (negate_aabonus) { aabonuses.EnduranceReduction = effect_value; } break; case SE_ACv2: case SE_ArmorClass: - spellbonuses.AC = effect_value; - aabonuses.AC = effect_value; - itembonuses.AC = effect_value; + if (negate_spellbonus) { spellbonuses.AC = effect_value; } + if (negate_aabonus) { aabonuses.AC = effect_value; } + if (negate_itembonus) { itembonuses.AC = effect_value; } break; case SE_ATK: - spellbonuses.ATK = effect_value; - aabonuses.ATK = effect_value; - itembonuses.ATK = effect_value; + if (negate_spellbonus) { spellbonuses.ATK = effect_value; } + if (negate_aabonus) { aabonuses.ATK = effect_value; } + if (negate_itembonus) { itembonuses.ATK = effect_value; } break; case SE_STR: - spellbonuses.STR = effect_value; - itembonuses.STR = effect_value; - aabonuses.STR = effect_value; + if (negate_spellbonus) { spellbonuses.STR = effect_value; } + if (negate_itembonus) { itembonuses.STR = effect_value; } + if (negate_aabonus) { aabonuses.STR = effect_value; } break; case SE_DEX: - spellbonuses.DEX = effect_value; - aabonuses.DEX = effect_value; - itembonuses.DEX = effect_value; + if (negate_spellbonus) { spellbonuses.DEX = effect_value; } + if (negate_aabonus) { aabonuses.DEX = effect_value; } + if (negate_itembonus) { itembonuses.DEX = effect_value; } break; case SE_AGI: - itembonuses.AGI = effect_value; - aabonuses.AGI = effect_value; - spellbonuses.AGI = effect_value; + if (negate_itembonus) { itembonuses.AGI = effect_value; } + if (negate_aabonus) { aabonuses.AGI = effect_value; } + if (negate_spellbonus) { spellbonuses.AGI = effect_value; } break; case SE_STA: - spellbonuses.STA = effect_value; - itembonuses.STA = effect_value; - aabonuses.STA = effect_value; + if (negate_spellbonus) { spellbonuses.STA = effect_value; } + if (negate_itembonus) { itembonuses.STA = effect_value; } + if (negate_aabonus) { aabonuses.STA = effect_value; } break; case SE_INT: - spellbonuses.INT = effect_value; - aabonuses.INT = effect_value; - itembonuses.INT = effect_value; + if (negate_spellbonus) { spellbonuses.INT = effect_value; } + if (negate_aabonus) { aabonuses.INT = effect_value; } + if (negate_itembonus) { itembonuses.INT = effect_value; } break; case SE_WIS: - spellbonuses.WIS = effect_value; - aabonuses.WIS = effect_value; - itembonuses.WIS = effect_value; + if (negate_spellbonus) { spellbonuses.WIS = effect_value; } + if (negate_aabonus) { aabonuses.WIS = effect_value; } + if (negate_itembonus) { itembonuses.WIS = effect_value; } break; case SE_CHA: - itembonuses.CHA = effect_value; - spellbonuses.CHA = effect_value; - aabonuses.CHA = effect_value; + if (negate_itembonus) { itembonuses.CHA = effect_value; } + if (negate_spellbonus) { spellbonuses.CHA = effect_value; } + if (negate_aabonus) { aabonuses.CHA = effect_value; } break; case SE_AllStats: { - spellbonuses.STR = effect_value; - spellbonuses.DEX = effect_value; - spellbonuses.AGI = effect_value; - spellbonuses.STA = effect_value; - spellbonuses.INT = effect_value; - spellbonuses.WIS = effect_value; - spellbonuses.CHA = effect_value; + if (negate_spellbonus) { spellbonuses.STR = effect_value; } + if (negate_spellbonus) { spellbonuses.DEX = effect_value; } + if (negate_spellbonus) { spellbonuses.AGI = effect_value; } + if (negate_spellbonus) { spellbonuses.STA = effect_value; } + if (negate_spellbonus) { spellbonuses.INT = effect_value; } + if (negate_spellbonus) { spellbonuses.WIS = effect_value; } + if (negate_spellbonus) { spellbonuses.CHA = effect_value; } - itembonuses.STR = effect_value; - itembonuses.DEX = effect_value; - itembonuses.AGI = effect_value; - itembonuses.STA = effect_value; - itembonuses.INT = effect_value; - itembonuses.WIS = effect_value; - itembonuses.CHA = effect_value; + if (negate_itembonus) { itembonuses.STR = effect_value; } + if (negate_itembonus) { itembonuses.DEX = effect_value; } + if (negate_itembonus) { itembonuses.AGI = effect_value; } + if (negate_itembonus) { itembonuses.STA = effect_value; } + if (negate_itembonus) { itembonuses.INT = effect_value; } + if (negate_itembonus) { itembonuses.WIS = effect_value; } + if (negate_itembonus) { itembonuses.CHA = effect_value; } - aabonuses.STR = effect_value; - aabonuses.DEX = effect_value; - aabonuses.AGI = effect_value; - aabonuses.STA = effect_value; - aabonuses.INT = effect_value; - aabonuses.WIS = effect_value; - aabonuses.CHA = effect_value; + if (negate_aabonus) { aabonuses.STR = effect_value; } + if (negate_aabonus) { aabonuses.DEX = effect_value; } + if (negate_aabonus) { aabonuses.AGI = effect_value; } + if (negate_aabonus) { aabonuses.STA = effect_value; } + if (negate_aabonus) { aabonuses.INT = effect_value; } + if (negate_aabonus) { aabonuses.WIS = effect_value; } + if (negate_aabonus) { aabonuses.CHA = effect_value; } break; } case SE_ResistFire: - spellbonuses.FR = effect_value; - itembonuses.FR = effect_value; - aabonuses.FR = effect_value; + if (negate_spellbonus) { spellbonuses.FR = effect_value; } + if (negate_itembonus) { itembonuses.FR = effect_value; } + if (negate_aabonus) { aabonuses.FR = effect_value; } break; case SE_ResistCold: - spellbonuses.CR = effect_value; - aabonuses.CR = effect_value; - itembonuses.CR = effect_value; + if (negate_spellbonus) { spellbonuses.CR = effect_value; } + if (negate_aabonus) { aabonuses.CR = effect_value; } + if (negate_itembonus) { itembonuses.CR = effect_value; } break; case SE_ResistPoison: - spellbonuses.PR = effect_value; - aabonuses.PR = effect_value; - itembonuses.PR = effect_value; + if (negate_spellbonus) { spellbonuses.PR = effect_value; } + if (negate_aabonus) { aabonuses.PR = effect_value; } + if (negate_itembonus) { itembonuses.PR = effect_value; } break; case SE_ResistDisease: - spellbonuses.DR = effect_value; - itembonuses.DR = effect_value; - aabonuses.DR = effect_value; + if (negate_spellbonus) { spellbonuses.DR = effect_value; } + if (negate_itembonus) { itembonuses.DR = effect_value; } + if (negate_aabonus) { aabonuses.DR = effect_value; } break; case SE_ResistMagic: - spellbonuses.MR = effect_value; - aabonuses.MR = effect_value; - itembonuses.MR = effect_value; + if (negate_spellbonus) { spellbonuses.MR = effect_value; } + if (negate_aabonus) { aabonuses.MR = effect_value; } + if (negate_itembonus) { itembonuses.MR = effect_value; } break; case SE_ResistAll: { - spellbonuses.MR = effect_value; - spellbonuses.DR = effect_value; - spellbonuses.PR = effect_value; - spellbonuses.CR = effect_value; - spellbonuses.FR = effect_value; + if (negate_spellbonus) { spellbonuses.MR = effect_value; } + if (negate_spellbonus) { spellbonuses.DR = effect_value; } + if (negate_spellbonus) { spellbonuses.PR = effect_value; } + if (negate_spellbonus) { spellbonuses.CR = effect_value; } + if (negate_spellbonus) { spellbonuses.FR = effect_value; } - aabonuses.MR = effect_value; - aabonuses.DR = effect_value; - aabonuses.PR = effect_value; - aabonuses.CR = effect_value; - aabonuses.FR = effect_value; + if (negate_aabonus) { aabonuses.MR = effect_value; } + if (negate_aabonus) { aabonuses.DR = effect_value; } + if (negate_aabonus) { aabonuses.PR = effect_value; } + if (negate_aabonus) { aabonuses.CR = effect_value; } + if (negate_aabonus) { aabonuses.FR = effect_value; } - itembonuses.MR = effect_value; - itembonuses.DR = effect_value; - itembonuses.PR = effect_value; - itembonuses.CR = effect_value; - itembonuses.FR = effect_value; + if (negate_itembonus) { itembonuses.MR = effect_value; } + if (negate_itembonus) { itembonuses.DR = effect_value; } + if (negate_itembonus) { itembonuses.PR = effect_value; } + if (negate_itembonus) { itembonuses.CR = effect_value; } + if (negate_itembonus) { itembonuses.FR = effect_value; } break; } case SE_ResistCorruption: - spellbonuses.Corrup = effect_value; - itembonuses.Corrup = effect_value; - aabonuses.Corrup = effect_value; + if (negate_spellbonus) { spellbonuses.Corrup = effect_value; } + if (negate_itembonus) { itembonuses.Corrup = effect_value; } + if (negate_aabonus) { aabonuses.Corrup = effect_value; } break; case SE_CastingLevel: // Brilliance of Ro - spellbonuses.adjusted_casting_skill = effect_value; - aabonuses.adjusted_casting_skill = effect_value; - itembonuses.adjusted_casting_skill = effect_value; + if (negate_spellbonus) { spellbonuses.adjusted_casting_skill = effect_value; } + if (negate_aabonus) { aabonuses.adjusted_casting_skill = effect_value; } + if (negate_itembonus) { itembonuses.adjusted_casting_skill = effect_value; } break; case SE_CastingLevel2: - spellbonuses.effective_casting_level = effect_value; - aabonuses.effective_casting_level = effect_value; - itembonuses.effective_casting_level = effect_value; + if (negate_spellbonus) { spellbonuses.effective_casting_level = effect_value; } + if (negate_aabonus) { aabonuses.effective_casting_level = effect_value; } + if (negate_itembonus) { itembonuses.effective_casting_level = effect_value; } break; case SE_MovementSpeed: - spellbonuses.movementspeed = effect_value; - aabonuses.movementspeed = effect_value; - itembonuses.movementspeed = effect_value; + if (negate_spellbonus) { spellbonuses.movementspeed = effect_value; } + if (negate_aabonus) { aabonuses.movementspeed = effect_value; } + if (negate_itembonus) { itembonuses.movementspeed = effect_value; } break; case SE_SpellDamageShield: - spellbonuses.SpellDamageShield = effect_value; - aabonuses.SpellDamageShield = effect_value; - itembonuses.SpellDamageShield = effect_value; + if (negate_spellbonus) { spellbonuses.SpellDamageShield = effect_value; } + if (negate_aabonus) { aabonuses.SpellDamageShield = effect_value; } + if (negate_itembonus) { itembonuses.SpellDamageShield = effect_value; } break; case SE_DamageShield: - spellbonuses.DamageShield = effect_value; - aabonuses.DamageShield = effect_value; - itembonuses.DamageShield = effect_value; + if (negate_spellbonus) { spellbonuses.DamageShield = effect_value; } + if (negate_aabonus) { aabonuses.DamageShield = effect_value; } + if (negate_itembonus) { itembonuses.DamageShield = effect_value; } break; case SE_ReverseDS: - spellbonuses.ReverseDamageShield = effect_value; - aabonuses.ReverseDamageShield = effect_value; - itembonuses.ReverseDamageShield = effect_value; + if (negate_spellbonus) { spellbonuses.ReverseDamageShield = effect_value; } + if (negate_aabonus) { aabonuses.ReverseDamageShield = effect_value; } + if (negate_itembonus) { itembonuses.ReverseDamageShield = effect_value; } break; case SE_Reflect: - spellbonuses.reflect_chance = effect_value; - aabonuses.reflect_chance = effect_value; - itembonuses.reflect_chance = effect_value; + if (negate_spellbonus) { spellbonuses.reflect[SBIndex::REFLECT_CHANCE] = effect_value; } + if (negate_aabonus) { aabonuses.reflect[SBIndex::REFLECT_CHANCE] = effect_value; } + if (negate_itembonus) { itembonuses.reflect[SBIndex::REFLECT_CHANCE] = effect_value; } break; case SE_Amplification: - spellbonuses.Amplification = effect_value; - itembonuses.Amplification = effect_value; - aabonuses.Amplification = effect_value; + if (negate_spellbonus) { spellbonuses.Amplification = effect_value; } + if (negate_itembonus) { itembonuses.Amplification = effect_value; } + if (negate_aabonus) { aabonuses.Amplification = effect_value; } break; case SE_ChangeAggro: - spellbonuses.hatemod = effect_value; - itembonuses.hatemod = effect_value; - aabonuses.hatemod = effect_value; + if (negate_spellbonus) { spellbonuses.hatemod = effect_value; } + if (negate_itembonus) { itembonuses.hatemod = effect_value; } + if (negate_aabonus) { aabonuses.hatemod = effect_value; } break; case SE_MeleeMitigation: - spellbonuses.MeleeMitigationEffect = effect_value; - itembonuses.MeleeMitigationEffect = effect_value; - aabonuses.MeleeMitigationEffect = effect_value; + if (negate_spellbonus) { spellbonuses.MeleeMitigationEffect = effect_value; } + if (negate_itembonus) { itembonuses.MeleeMitigationEffect = effect_value; } + if (negate_aabonus) { aabonuses.MeleeMitigationEffect = effect_value; } break; case SE_CriticalHitChance: { for (int e = 0; e < EQ::skills::HIGHEST_SKILL + 1; e++) { - spellbonuses.CriticalHitChance[e] = effect_value; - aabonuses.CriticalHitChance[e] = effect_value; - itembonuses.CriticalHitChance[e] = effect_value; + if (negate_spellbonus) { spellbonuses.CriticalHitChance[e] = effect_value; } + if (negate_aabonus) { aabonuses.CriticalHitChance[e] = effect_value; } + if (negate_itembonus) { itembonuses.CriticalHitChance[e] = effect_value; } } } case SE_CrippBlowChance: - spellbonuses.CrippBlowChance = effect_value; - aabonuses.CrippBlowChance = effect_value; - itembonuses.CrippBlowChance = effect_value; + if (negate_spellbonus) { spellbonuses.CrippBlowChance = effect_value; } + if (negate_aabonus) { aabonuses.CrippBlowChance = effect_value; } + if (negate_itembonus) { itembonuses.CrippBlowChance = effect_value; } break; case SE_AvoidMeleeChance: - spellbonuses.AvoidMeleeChanceEffect = effect_value; - aabonuses.AvoidMeleeChanceEffect = effect_value; - itembonuses.AvoidMeleeChanceEffect = effect_value; + if (negate_spellbonus) { spellbonuses.AvoidMeleeChanceEffect = effect_value; } + if (negate_aabonus) { aabonuses.AvoidMeleeChanceEffect = effect_value; } + if (negate_itembonus) { itembonuses.AvoidMeleeChanceEffect = effect_value; } break; case SE_RiposteChance: - spellbonuses.RiposteChance = effect_value; - aabonuses.RiposteChance = effect_value; - itembonuses.RiposteChance = effect_value; + if (negate_spellbonus) { spellbonuses.RiposteChance = effect_value; } + if (negate_aabonus) { aabonuses.RiposteChance = effect_value; } + if (negate_itembonus) { itembonuses.RiposteChance = effect_value; } break; case SE_DodgeChance: - spellbonuses.DodgeChance = effect_value; - aabonuses.DodgeChance = effect_value; - itembonuses.DodgeChance = effect_value; + if (negate_spellbonus) { spellbonuses.DodgeChance = effect_value; } + if (negate_aabonus) { aabonuses.DodgeChance = effect_value; } + if (negate_itembonus) { itembonuses.DodgeChance = effect_value; } break; case SE_ParryChance: - spellbonuses.ParryChance = effect_value; - aabonuses.ParryChance = effect_value; - itembonuses.ParryChance = effect_value; + if (negate_spellbonus) { spellbonuses.ParryChance = effect_value; } + if (negate_aabonus) { aabonuses.ParryChance = effect_value; } + if (negate_itembonus) { itembonuses.ParryChance = effect_value; } break; case SE_DualWieldChance: - spellbonuses.DualWieldChance = effect_value; - aabonuses.DualWieldChance = effect_value; - itembonuses.DualWieldChance = effect_value; + if (negate_spellbonus) { spellbonuses.DualWieldChance = effect_value; } + if (negate_aabonus) { aabonuses.DualWieldChance = effect_value; } + if (negate_itembonus) { itembonuses.DualWieldChance = effect_value; } break; case SE_DoubleAttackChance: - spellbonuses.DoubleAttackChance = effect_value; - aabonuses.DoubleAttackChance = effect_value; - itembonuses.DoubleAttackChance = effect_value; + if (negate_spellbonus) { spellbonuses.DoubleAttackChance = effect_value; } + if (negate_aabonus) { aabonuses.DoubleAttackChance = effect_value; } + if (negate_itembonus) { itembonuses.DoubleAttackChance = effect_value; } break; case SE_TripleAttackChance: - spellbonuses.TripleAttackChance = effect_value; - aabonuses.TripleAttackChance = effect_value; - itembonuses.TripleAttackChance = effect_value; + if (negate_spellbonus) { spellbonuses.TripleAttackChance = effect_value; } + if (negate_aabonus) { aabonuses.TripleAttackChance = effect_value; } + if (negate_itembonus) { itembonuses.TripleAttackChance = effect_value; } break; case SE_MeleeLifetap: - spellbonuses.MeleeLifetap = effect_value; - aabonuses.MeleeLifetap = effect_value; - itembonuses.MeleeLifetap = effect_value; + if (negate_spellbonus) { spellbonuses.MeleeLifetap = effect_value; } + if (negate_aabonus) { aabonuses.MeleeLifetap = effect_value; } + if (negate_itembonus) { itembonuses.MeleeLifetap = effect_value; } break; case SE_AllInstrumentMod: { - spellbonuses.singingMod = effect_value; - spellbonuses.brassMod = effect_value; - spellbonuses.percussionMod = effect_value; - spellbonuses.windMod = effect_value; - spellbonuses.stringedMod = effect_value; + if (negate_spellbonus) { spellbonuses.singingMod = effect_value; } + if (negate_spellbonus) { spellbonuses.brassMod = effect_value; } + if (negate_spellbonus) { spellbonuses.percussionMod = effect_value; } + if (negate_spellbonus) { spellbonuses.windMod = effect_value; } + if (negate_spellbonus) { spellbonuses.stringedMod = effect_value; } - itembonuses.singingMod = effect_value; - itembonuses.brassMod = effect_value; - itembonuses.percussionMod = effect_value; - itembonuses.windMod = effect_value; - itembonuses.stringedMod = effect_value; + if (negate_itembonus) { itembonuses.singingMod = effect_value; } + if (negate_itembonus) { itembonuses.brassMod = effect_value; } + if (negate_itembonus) { itembonuses.percussionMod = effect_value; } + if (negate_itembonus) { itembonuses.windMod = effect_value; } + if (negate_itembonus) { itembonuses.stringedMod = effect_value; } - aabonuses.singingMod = effect_value; - aabonuses.brassMod = effect_value; - aabonuses.percussionMod = effect_value; - aabonuses.windMod = effect_value; - aabonuses.stringedMod = effect_value; + if (negate_aabonus) { aabonuses.singingMod = effect_value; } + if (negate_aabonus) { aabonuses.brassMod = effect_value; } + if (negate_aabonus) { aabonuses.percussionMod = effect_value; } + if (negate_aabonus) { aabonuses.windMod = effect_value; } + if (negate_aabonus) { aabonuses.stringedMod = effect_value; } break; } case SE_ResistSpellChance: - spellbonuses.ResistSpellChance = effect_value; - aabonuses.ResistSpellChance = effect_value; - itembonuses.ResistSpellChance = effect_value; + if (negate_spellbonus) { spellbonuses.ResistSpellChance = effect_value; } + if (negate_aabonus) { aabonuses.ResistSpellChance = effect_value; } + if (negate_itembonus) { itembonuses.ResistSpellChance = effect_value; } break; case SE_ResistFearChance: - spellbonuses.Fearless = false; - spellbonuses.ResistFearChance = effect_value; - aabonuses.ResistFearChance = effect_value; - itembonuses.ResistFearChance = effect_value; + if (negate_spellbonus) { spellbonuses.Fearless = false; } + if (negate_spellbonus) { spellbonuses.ResistFearChance = effect_value; } + if (negate_aabonus) { aabonuses.ResistFearChance = effect_value; } + if (negate_itembonus) { itembonuses.ResistFearChance = effect_value; } break; case SE_Fearless: - spellbonuses.Fearless = false; - aabonuses.Fearless = false; - itembonuses.Fearless = false; + if (negate_spellbonus) { spellbonuses.Fearless = false; } + if (negate_aabonus) { aabonuses.Fearless = false; } + if (negate_itembonus) { itembonuses.Fearless = false; } break; case SE_HundredHands: - spellbonuses.HundredHands = effect_value; - aabonuses.HundredHands = effect_value; - itembonuses.HundredHands = effect_value; + if (negate_spellbonus) { spellbonuses.HundredHands = effect_value; } + if (negate_aabonus) { aabonuses.HundredHands = effect_value; } + if (negate_itembonus) { itembonuses.HundredHands = effect_value; } break; case SE_MeleeSkillCheck: { - spellbonuses.MeleeSkillCheck = effect_value; - spellbonuses.MeleeSkillCheckSkill = effect_value; + if (negate_spellbonus) { spellbonuses.MeleeSkillCheck = effect_value; } + if (negate_spellbonus) { spellbonuses.MeleeSkillCheckSkill = effect_value; } break; } @@ -4042,9 +4683,9 @@ void Mob::NegateSpellsBonuses(uint16 spell_id) { for (int e = 0; e < EQ::skills::HIGHEST_SKILL + 1; e++) { - spellbonuses.HitChanceEffect[e] = effect_value; - aabonuses.HitChanceEffect[e] = effect_value; - itembonuses.HitChanceEffect[e] = effect_value; + if (negate_spellbonus) { spellbonuses.HitChanceEffect[e] = effect_value; } + if (negate_aabonus) { aabonuses.HitChanceEffect[e] = effect_value; } + if (negate_itembonus) { itembonuses.HitChanceEffect[e] = effect_value; } } break; } @@ -4053,9 +4694,9 @@ void Mob::NegateSpellsBonuses(uint16 spell_id) { for (int e = 0; e < EQ::skills::HIGHEST_SKILL + 1; e++) { - spellbonuses.DamageModifier[e] = effect_value; - aabonuses.DamageModifier[e] = effect_value; - itembonuses.DamageModifier[e] = effect_value; + if (negate_spellbonus) { spellbonuses.DamageModifier[e] = effect_value; } + if (negate_aabonus) { aabonuses.DamageModifier[e] = effect_value; } + if (negate_itembonus) { itembonuses.DamageModifier[e] = effect_value; } } break; } @@ -4064,173 +4705,192 @@ void Mob::NegateSpellsBonuses(uint16 spell_id) { for (int e = 0; e < EQ::skills::HIGHEST_SKILL + 1; e++) { - spellbonuses.DamageModifier2[e] = effect_value; - aabonuses.DamageModifier2[e] = effect_value; - itembonuses.DamageModifier2[e] = effect_value; + if (negate_spellbonus) { spellbonuses.DamageModifier2[e] = effect_value; } + if (negate_aabonus) { aabonuses.DamageModifier2[e] = effect_value; } + if (negate_itembonus) { itembonuses.DamageModifier2[e] = effect_value; } } break; } + case SE_Skill_Base_Damage_Mod: + { + for (int e = 0; e < EQ::skills::HIGHEST_SKILL + 1; e++) + { + if (negate_spellbonus) { spellbonuses.DamageModifier3[e] = effect_value; } + if (negate_aabonus) { aabonuses.DamageModifier3[e] = effect_value; } + if (negate_itembonus) { itembonuses.DamageModifier3[e] = effect_value; } + } + break; + } + + case SE_MinDamageModifier: { for (int e = 0; e < EQ::skills::HIGHEST_SKILL + 1; e++) { - spellbonuses.MinDamageModifier[e] = effect_value; - aabonuses.MinDamageModifier[e] = effect_value; - itembonuses.MinDamageModifier[e] = effect_value; + if (negate_spellbonus) { spellbonuses.MinDamageModifier[e] = effect_value; } + if (negate_aabonus) { aabonuses.MinDamageModifier[e] = effect_value; } + if (negate_itembonus) { itembonuses.MinDamageModifier[e] = effect_value; } } break; } case SE_StunResist: - spellbonuses.StunResist = effect_value; - aabonuses.StunResist = effect_value; - itembonuses.StunResist = effect_value; + if (negate_spellbonus) { spellbonuses.StunResist = effect_value; } + if (negate_aabonus) { aabonuses.StunResist = effect_value; } + if (negate_itembonus) { itembonuses.StunResist = effect_value; } break; case SE_ProcChance: - spellbonuses.ProcChanceSPA = effect_value; - aabonuses.ProcChanceSPA = effect_value; - itembonuses.ProcChanceSPA = effect_value; + if (negate_spellbonus) { spellbonuses.ProcChanceSPA = effect_value; } + if (negate_aabonus) { aabonuses.ProcChanceSPA = effect_value; } + if (negate_itembonus) { itembonuses.ProcChanceSPA = effect_value; } break; case SE_ExtraAttackChance: - spellbonuses.ExtraAttackChance = effect_value; - aabonuses.ExtraAttackChance = effect_value; - itembonuses.ExtraAttackChance = effect_value; + if (negate_spellbonus) { spellbonuses.ExtraAttackChance[SBIndex::EXTRA_ATTACK_CHANCE] = effect_value; } + if (negate_aabonus) { aabonuses.ExtraAttackChance[SBIndex::EXTRA_ATTACK_CHANCE] = effect_value; } + if (negate_itembonus) { itembonuses.ExtraAttackChance[SBIndex::EXTRA_ATTACK_CHANCE] = effect_value; } + break; + + case SE_AddExtraAttackPct_1h_Primary: + if (negate_spellbonus) { spellbonuses.ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_CHANCE] = effect_value; } + if (negate_aabonus) { aabonuses.ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_CHANCE] = effect_value; } + if (negate_itembonus) { itembonuses.ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_CHANCE] = effect_value; } + break; + + case SE_AddExtraAttackPct_1h_Secondary: + if (negate_spellbonus) { spellbonuses.ExtraAttackChanceSecondary[SBIndex::EXTRA_ATTACK_CHANCE] = effect_value; } + if (negate_aabonus) { aabonuses.ExtraAttackChanceSecondary[SBIndex::EXTRA_ATTACK_CHANCE] = effect_value; } + if (negate_itembonus) { itembonuses.ExtraAttackChanceSecondary[SBIndex::EXTRA_ATTACK_CHANCE] = effect_value; } + break; + + case SE_Double_Melee_Round: + if (negate_spellbonus) { spellbonuses.DoubleMeleeRound[SBIndex::DOUBLE_MELEE_ROUND_CHANCE] = effect_value; } + if (negate_aabonus) { aabonuses.DoubleMeleeRound[SBIndex::DOUBLE_MELEE_ROUND_CHANCE] = effect_value; } + if (negate_itembonus) { itembonuses.DoubleMeleeRound[SBIndex::DOUBLE_MELEE_ROUND_CHANCE] = effect_value; } break; case SE_PercentXPIncrease: - spellbonuses.XPRateMod = effect_value; - aabonuses.XPRateMod = effect_value; - itembonuses.XPRateMod = effect_value; + if (negate_spellbonus) { spellbonuses.XPRateMod = effect_value; } + if (negate_aabonus) { aabonuses.XPRateMod = effect_value; } + if (negate_itembonus) { itembonuses.XPRateMod = effect_value; } break; case SE_Flurry: - spellbonuses.FlurryChance = effect_value; - aabonuses.FlurryChance = effect_value; - itembonuses.FlurryChance = effect_value; + if (negate_spellbonus) { spellbonuses.FlurryChance = effect_value; } + if (negate_aabonus) { aabonuses.FlurryChance = effect_value; } + if (negate_itembonus) { itembonuses.FlurryChance = effect_value; } break; case SE_Accuracy: { - spellbonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1] = effect_value; - itembonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1] = effect_value; + if (negate_spellbonus) { spellbonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1] = effect_value; } + if (negate_itembonus) { itembonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1] = effect_value; } for (int e = 0; e < EQ::skills::HIGHEST_SKILL + 1; e++) - { - aabonuses.Accuracy[e] = effect_value; - } + { + if (negate_aabonus) { aabonuses.Accuracy[e] = effect_value; } + } break; } case SE_MaxHPChange: - spellbonuses.MaxHPChange = effect_value; - aabonuses.MaxHPChange = effect_value; - itembonuses.MaxHPChange = effect_value; + if (negate_spellbonus) { spellbonuses.MaxHPChange = effect_value; } + if (negate_aabonus) { aabonuses.MaxHPChange = effect_value; } + if (negate_itembonus) { itembonuses.MaxHPChange = effect_value; } break; case SE_EndurancePool: - spellbonuses.Endurance = effect_value; - aabonuses.Endurance = effect_value; - itembonuses.Endurance = effect_value; + if (negate_spellbonus) { spellbonuses.Endurance = effect_value; } + if (negate_aabonus) { aabonuses.Endurance = effect_value; } + if (negate_itembonus) { itembonuses.Endurance = effect_value; } break; case SE_HealRate: - spellbonuses.HealRate = effect_value; - aabonuses.HealRate = effect_value; - itembonuses.HealRate = effect_value; + if (negate_spellbonus) { spellbonuses.HealRate = effect_value; } + if (negate_aabonus) { aabonuses.HealRate = effect_value; } + if (negate_itembonus) { itembonuses.HealRate = effect_value; } break; case SE_SkillDamageTaken: { for (int e = 0; e < EQ::skills::HIGHEST_SKILL + 1; e++) { - spellbonuses.SkillDmgTaken[e] = effect_value; - aabonuses.SkillDmgTaken[e] = effect_value; - itembonuses.SkillDmgTaken[e] = effect_value; + if (negate_spellbonus) { spellbonuses.SkillDmgTaken[e] = effect_value; } + if (negate_aabonus) { aabonuses.SkillDmgTaken[e] = effect_value; } + if (negate_itembonus) { itembonuses.SkillDmgTaken[e] = effect_value; } } break; } - case SE_TriggerOnCast: - { - for(int e = 0; e < MAX_SPELL_TRIGGER; e++) - { - spellbonuses.SpellTriggers[e] = effect_value; - aabonuses.SpellTriggers[e] = effect_value; - itembonuses.SpellTriggers[e] = effect_value; - } - break; - } - case SE_SpellCritChance: - spellbonuses.CriticalSpellChance = effect_value; - aabonuses.CriticalSpellChance = effect_value; - itembonuses.CriticalSpellChance = effect_value; + if (negate_spellbonus) { spellbonuses.CriticalSpellChance = effect_value; } + if (negate_aabonus) { aabonuses.CriticalSpellChance = effect_value; } + if (negate_itembonus) { itembonuses.CriticalSpellChance = effect_value; } break; case SE_CriticalSpellChance: - spellbonuses.CriticalSpellChance = effect_value; - spellbonuses.SpellCritDmgIncrease = effect_value; - aabonuses.CriticalSpellChance = effect_value; - aabonuses.SpellCritDmgIncrease = effect_value; - itembonuses.CriticalSpellChance = effect_value; - itembonuses.SpellCritDmgIncrease = effect_value; + if (negate_spellbonus) { spellbonuses.CriticalSpellChance = effect_value; } + if (negate_spellbonus) { spellbonuses.SpellCritDmgIncrease = effect_value; } + if (negate_aabonus) { aabonuses.CriticalSpellChance = effect_value; } + if (negate_aabonus) { aabonuses.SpellCritDmgIncrease = effect_value; } + if (negate_itembonus) { itembonuses.CriticalSpellChance = effect_value; } + if (negate_itembonus) { itembonuses.SpellCritDmgIncrease = effect_value; } break; case SE_SpellCritDmgIncrease: - spellbonuses.SpellCritDmgIncrease = effect_value; - aabonuses.SpellCritDmgIncrease = effect_value; - itembonuses.SpellCritDmgIncrease = effect_value; + if (negate_spellbonus) { spellbonuses.SpellCritDmgIncrease = effect_value; } + if (negate_aabonus) { aabonuses.SpellCritDmgIncrease = effect_value; } + if (negate_itembonus) { itembonuses.SpellCritDmgIncrease = effect_value; } break; case SE_DotCritDmgIncrease: - spellbonuses.DotCritDmgIncrease = effect_value; - aabonuses.DotCritDmgIncrease = effect_value; - itembonuses.DotCritDmgIncrease = effect_value; + if (negate_spellbonus) { spellbonuses.DotCritDmgIncrease = effect_value; } + if (negate_aabonus) { aabonuses.DotCritDmgIncrease = effect_value; } + if (negate_itembonus) { itembonuses.DotCritDmgIncrease = effect_value; } break; case SE_CriticalHealChance: - spellbonuses.CriticalHealChance = effect_value; - aabonuses.CriticalHealChance = effect_value; - itembonuses.CriticalHealChance = effect_value; + if (negate_spellbonus) { spellbonuses.CriticalHealChance = effect_value; } + if (negate_aabonus) { aabonuses.CriticalHealChance = effect_value; } + if (negate_itembonus) { itembonuses.CriticalHealChance = effect_value; } break; case SE_CriticalHealOverTime: - spellbonuses.CriticalHealOverTime = effect_value; - aabonuses.CriticalHealOverTime = effect_value; - itembonuses.CriticalHealOverTime = effect_value; + if (negate_spellbonus) { spellbonuses.CriticalHealOverTime = effect_value; } + if (negate_aabonus) { aabonuses.CriticalHealOverTime = effect_value; } + if (negate_itembonus) { itembonuses.CriticalHealOverTime = effect_value; } break; case SE_MitigateDamageShield: - spellbonuses.DSMitigationOffHand = effect_value; - itembonuses.DSMitigationOffHand = effect_value; - aabonuses.DSMitigationOffHand = effect_value; + if (negate_spellbonus) { spellbonuses.DSMitigationOffHand = effect_value; } + if (negate_itembonus) { itembonuses.DSMitigationOffHand = effect_value; } + if (negate_aabonus) { aabonuses.DSMitigationOffHand = effect_value; } break; case SE_CriticalDoTChance: - spellbonuses.CriticalDoTChance = effect_value; - aabonuses.CriticalDoTChance = effect_value; - itembonuses.CriticalDoTChance = effect_value; + if (negate_spellbonus) { spellbonuses.CriticalDoTChance = effect_value; } + if (negate_aabonus) { aabonuses.CriticalDoTChance = effect_value; } + if (negate_itembonus) { itembonuses.CriticalDoTChance = effect_value; } break; case SE_ProcOnKillShot: { - for(int e = 0; e < MAX_SPELL_TRIGGER*3; e=3) + for (int e = 0; e < MAX_SPELL_TRIGGER * 3; e = 3) { - spellbonuses.SpellOnKill[e] = effect_value; - spellbonuses.SpellOnKill[e+1] = effect_value; - spellbonuses.SpellOnKill[e+2] = effect_value; + if (negate_spellbonus) { spellbonuses.SpellOnKill[e] = effect_value; } + if (negate_spellbonus) { spellbonuses.SpellOnKill[e + 1] = effect_value; } + if (negate_spellbonus) { spellbonuses.SpellOnKill[e + 2] = effect_value; } - aabonuses.SpellOnKill[e] = effect_value; - aabonuses.SpellOnKill[e+1] = effect_value; - aabonuses.SpellOnKill[e+2] = effect_value; + if (negate_aabonus) { aabonuses.SpellOnKill[e] = effect_value; } + if (negate_aabonus) { aabonuses.SpellOnKill[e + 1] = effect_value; } + if (negate_aabonus) { aabonuses.SpellOnKill[e + 2] = effect_value; } - itembonuses.SpellOnKill[e] = effect_value; - itembonuses.SpellOnKill[e+1] = effect_value; - itembonuses.SpellOnKill[e+2] = effect_value; + if (negate_itembonus) { itembonuses.SpellOnKill[e] = effect_value; } + if (negate_itembonus) { itembonuses.SpellOnKill[e + 1] = effect_value; } + if (negate_itembonus) { itembonuses.SpellOnKill[e + 2] = effect_value; } } break; } @@ -4240,8 +4900,8 @@ void Mob::NegateSpellsBonuses(uint16 spell_id) { for(int e = 0; e < MAX_SPELL_TRIGGER; e=2) { - spellbonuses.SpellOnDeath[e] = SPELL_UNKNOWN; - spellbonuses.SpellOnDeath[e+1] = effect_value; + if (negate_spellbonus) { spellbonuses.SpellOnDeath[e] = SPELL_UNKNOWN; + if (negate_spellbonus) { spellbonuses.SpellOnDeath[e+1] = effect_value; } } break; } @@ -4251,9 +4911,20 @@ void Mob::NegateSpellsBonuses(uint16 spell_id) { for (int e = 0; e < EQ::skills::HIGHEST_SKILL + 1; e++) { - spellbonuses.CritDmgMod[e] = effect_value; - aabonuses.CritDmgMod[e] = effect_value; - itembonuses.CritDmgMod[e] = effect_value; + if (negate_spellbonus) { spellbonuses.CritDmgMod[e] = effect_value; } + if (negate_aabonus) { aabonuses.CritDmgMod[e] = effect_value; } + if (negate_itembonus) { itembonuses.CritDmgMod[e] = effect_value; } + } + break; + } + + case SE_Critical_Melee_Damage_Mod_Max: + { + for (int e = 0; e < EQ::skills::HIGHEST_SKILL + 1; e++) + { + if (negate_spellbonus) { spellbonuses.CritDmgModNoStack[e] = effect_value; } + if (negate_aabonus) { aabonuses.CritDmgModNoStack[e] = effect_value; } + if (negate_itembonus) { itembonuses.CritDmgModNoStack[e] = effect_value; } } break; } @@ -4262,533 +4933,647 @@ void Mob::NegateSpellsBonuses(uint16 spell_id) { for (int e = 0; e < EQ::skills::HIGHEST_SKILL + 1; e++) { - spellbonuses.SkillDamageAmount[e] = effect_value; - aabonuses.SkillDamageAmount[e] = effect_value; - itembonuses.SkillDamageAmount[e] = effect_value; + if (negate_spellbonus) { spellbonuses.SkillDamageAmount[e] = effect_value; } + if (negate_aabonus) { aabonuses.SkillDamageAmount[e] = effect_value; } + if (negate_itembonus) { itembonuses.SkillDamageAmount[e] = effect_value; } } break; } case SE_IncreaseBlockChance: - spellbonuses.IncreaseBlockChance = effect_value; - aabonuses.IncreaseBlockChance = effect_value; - itembonuses.IncreaseBlockChance = effect_value; + if (negate_spellbonus) { spellbonuses.IncreaseBlockChance = effect_value; } + if (negate_aabonus) { aabonuses.IncreaseBlockChance = effect_value; } + if (negate_itembonus) { itembonuses.IncreaseBlockChance = effect_value; } break; case SE_PersistantCasting: - spellbonuses.PersistantCasting = effect_value; - itembonuses.PersistantCasting = effect_value; - aabonuses.PersistantCasting = effect_value; + if (negate_spellbonus) { spellbonuses.PersistantCasting = effect_value; } + if (negate_itembonus) { itembonuses.PersistantCasting = effect_value; } + if (negate_aabonus) { aabonuses.PersistantCasting = effect_value; } break; case SE_ImmuneFleeing: - spellbonuses.ImmuneToFlee = false; + if (negate_spellbonus) { spellbonuses.ImmuneToFlee = false; } break; case SE_DelayDeath: - spellbonuses.DelayDeath = effect_value; - aabonuses.DelayDeath = effect_value; - itembonuses.DelayDeath = effect_value; + if (negate_spellbonus) { spellbonuses.DelayDeath = effect_value; } + if (negate_aabonus) { aabonuses.DelayDeath = effect_value; } + if (negate_itembonus) { itembonuses.DelayDeath = effect_value; } break; case SE_SpellProcChance: - spellbonuses.SpellProcChance = effect_value; - itembonuses.SpellProcChance = effect_value; - aabonuses.SpellProcChance = effect_value; + if (negate_spellbonus) { spellbonuses.SpellProcChance = effect_value; } + if (negate_itembonus) { itembonuses.SpellProcChance = effect_value; } + if (negate_aabonus) { aabonuses.SpellProcChance = effect_value; } break; case SE_CharmBreakChance: - spellbonuses.CharmBreakChance = effect_value; - aabonuses.CharmBreakChance = effect_value; - itembonuses.CharmBreakChance = effect_value; + if (negate_spellbonus) { spellbonuses.CharmBreakChance = effect_value; } + if (negate_aabonus) { aabonuses.CharmBreakChance = effect_value; } + if (negate_itembonus) { itembonuses.CharmBreakChance = effect_value; } break; case SE_BardSongRange: - spellbonuses.SongRange = effect_value; - aabonuses.SongRange = effect_value; - itembonuses.SongRange = effect_value; + if (negate_spellbonus) { spellbonuses.SongRange = effect_value; } + if (negate_aabonus) { aabonuses.SongRange = effect_value; } + if (negate_itembonus) { itembonuses.SongRange = effect_value; } break; case SE_SkillDamageAmount2: { for (int e = 0; e < EQ::skills::HIGHEST_SKILL + 1; e++) { - spellbonuses.SkillDamageAmount2[e] = effect_value; - aabonuses.SkillDamageAmount2[e] = effect_value; - itembonuses.SkillDamageAmount2[e] = effect_value; + if (negate_spellbonus) { spellbonuses.SkillDamageAmount2[e] = effect_value; } + if (negate_aabonus) { aabonuses.SkillDamageAmount2[e] = effect_value; } + if (negate_itembonus) { itembonuses.SkillDamageAmount2[e] = effect_value; } } break; } case SE_NegateAttacks: - spellbonuses.NegateAttacks[0] = effect_value; - spellbonuses.NegateAttacks[1] = effect_value; + if (negate_spellbonus) { spellbonuses.NegateAttacks[SBIndex::NEGATE_ATK_EXISTS] = effect_value; } + if (negate_spellbonus) { spellbonuses.NegateAttacks[SBIndex::NEGATE_ATK_BUFFSLOT] = effect_value; } break; case SE_MitigateMeleeDamage: - spellbonuses.MitigateMeleeRune[0] = effect_value; - spellbonuses.MitigateMeleeRune[1] = -1; + if (negate_spellbonus) { spellbonuses.MitigateMeleeRune[SBIndex::MITIGATION_RUNE_PERCENT] = effect_value; } + if (negate_spellbonus) { spellbonuses.MitigateMeleeRune[SBIndex::MITIGATION_RUNE_BUFFSLOT] = -1; } break; case SE_MeleeThresholdGuard: - spellbonuses.MeleeThresholdGuard[0] = effect_value; - spellbonuses.MeleeThresholdGuard[1] = -1; - spellbonuses.MeleeThresholdGuard[1] = effect_value; + if (negate_spellbonus) { spellbonuses.MeleeThresholdGuard[SBIndex::THRESHOLDGUARD_MITIGATION_PERCENT] = effect_value; } + if (negate_spellbonus) { spellbonuses.MeleeThresholdGuard[SBIndex::THRESHOLDGUARD_BUFFSLOT] = -1; } + if (negate_spellbonus) { spellbonuses.MeleeThresholdGuard[SBIndex::THRESHOLDGUARD_BUFFSLOT] = effect_value; } break; case SE_SpellThresholdGuard: - spellbonuses.SpellThresholdGuard[0] = effect_value; - spellbonuses.SpellThresholdGuard[1] = -1; - spellbonuses.SpellThresholdGuard[1] = effect_value; + if (negate_spellbonus) { spellbonuses.SpellThresholdGuard[SBIndex::THRESHOLDGUARD_MITIGATION_PERCENT] = effect_value; } + if (negate_spellbonus) { spellbonuses.SpellThresholdGuard[SBIndex::THRESHOLDGUARD_BUFFSLOT] = -1; } + if (negate_spellbonus) { spellbonuses.SpellThresholdGuard[SBIndex::THRESHOLDGUARD_BUFFSLOT] = effect_value; } break; case SE_MitigateSpellDamage: - spellbonuses.MitigateSpellRune[0] = effect_value; - spellbonuses.MitigateSpellRune[1] = -1; + if (negate_spellbonus) { spellbonuses.MitigateSpellRune[SBIndex::MITIGATION_RUNE_PERCENT] = effect_value; } + if (negate_spellbonus) { spellbonuses.MitigateSpellRune[SBIndex::MITIGATION_RUNE_BUFFSLOT] = -1; } break; case SE_MitigateDotDamage: - spellbonuses.MitigateDotRune[0] = effect_value; - spellbonuses.MitigateDotRune[1] = -1; + if (negate_spellbonus) { spellbonuses.MitigateDotRune[SBIndex::MITIGATION_RUNE_PERCENT] = effect_value; } + if (negate_spellbonus) { spellbonuses.MitigateDotRune[SBIndex::MITIGATION_RUNE_BUFFSLOT] = -1; } break; case SE_ManaAbsorbPercentDamage: - spellbonuses.ManaAbsorbPercentDamage[0] = effect_value; - spellbonuses.ManaAbsorbPercentDamage[1] = -1; + if (negate_spellbonus) { spellbonuses.ManaAbsorbPercentDamage = effect_value; } + break; + + case SE_Endurance_Absorb_Pct_Damage: + if (negate_spellbonus) { spellbonuses.EnduranceAbsorbPercentDamage[SBIndex::ENDURANCE_ABSORD_MITIGIATION] = effect_value; } + if (negate_spellbonus) { spellbonuses.EnduranceAbsorbPercentDamage[SBIndex::ENDURANCE_ABSORD_DRAIN_PER_HP] = effect_value; } break; case SE_ShieldBlock: - spellbonuses.ShieldBlock = effect_value; - aabonuses.ShieldBlock = effect_value; - itembonuses.ShieldBlock = effect_value; + if (negate_spellbonus) { spellbonuses.ShieldBlock = effect_value; } + if (negate_aabonus) { aabonuses.ShieldBlock = effect_value; } + if (negate_itembonus) { itembonuses.ShieldBlock = effect_value; } case SE_BlockBehind: - spellbonuses.BlockBehind = effect_value; - aabonuses.BlockBehind = effect_value; - itembonuses.BlockBehind = effect_value; + if (negate_spellbonus) { spellbonuses.BlockBehind = effect_value; } + if (negate_aabonus) { aabonuses.BlockBehind = effect_value; } + if (negate_itembonus) { itembonuses.BlockBehind = effect_value; } break; case SE_Blind: - spellbonuses.IsBlind = false; + if (negate_spellbonus) { spellbonuses.IsBlind = false; } break; case SE_Fear: - spellbonuses.IsFeared = false; + if (negate_spellbonus) { spellbonuses.IsFeared = false; } break; case SE_FrontalStunResist: - spellbonuses.FrontalStunResist = effect_value; - aabonuses.FrontalStunResist = effect_value; - itembonuses.FrontalStunResist = effect_value; + if (negate_spellbonus) { spellbonuses.FrontalStunResist = effect_value; } + if (negate_aabonus) { aabonuses.FrontalStunResist = effect_value; } + if (negate_itembonus) { itembonuses.FrontalStunResist = effect_value; } break; case SE_ImprovedBindWound: - aabonuses.BindWound = effect_value; - itembonuses.BindWound = effect_value; - spellbonuses.BindWound = effect_value; + if (negate_aabonus) { aabonuses.BindWound = effect_value; } + if (negate_itembonus) { itembonuses.BindWound = effect_value; } + if (negate_spellbonus) { spellbonuses.BindWound = effect_value; } break; case SE_MaxBindWound: - spellbonuses.MaxBindWound = effect_value; - aabonuses.MaxBindWound = effect_value; - itembonuses.MaxBindWound = effect_value; + if (negate_spellbonus) { spellbonuses.MaxBindWound = effect_value; } + if (negate_aabonus) { aabonuses.MaxBindWound = effect_value; } + if (negate_itembonus) { itembonuses.MaxBindWound = effect_value; } break; case SE_BaseMovementSpeed: - spellbonuses.BaseMovementSpeed = effect_value; - aabonuses.BaseMovementSpeed = effect_value; - itembonuses.BaseMovementSpeed = effect_value; + if (negate_spellbonus) { spellbonuses.BaseMovementSpeed = effect_value; } + if (negate_aabonus) { aabonuses.BaseMovementSpeed = effect_value; } + if (negate_itembonus) { itembonuses.BaseMovementSpeed = effect_value; } break; case SE_IncreaseRunSpeedCap: - itembonuses.IncreaseRunSpeedCap = effect_value; - aabonuses.IncreaseRunSpeedCap = effect_value; - spellbonuses.IncreaseRunSpeedCap = effect_value; + if (negate_itembonus) { itembonuses.IncreaseRunSpeedCap = effect_value; } + if (negate_aabonus) { aabonuses.IncreaseRunSpeedCap = effect_value; } + if (negate_spellbonus) { spellbonuses.IncreaseRunSpeedCap = effect_value; } break; case SE_DoubleSpecialAttack: - spellbonuses.DoubleSpecialAttack = effect_value; - aabonuses.DoubleSpecialAttack = effect_value; - itembonuses.DoubleSpecialAttack = effect_value; + if (negate_spellbonus) { spellbonuses.DoubleSpecialAttack = effect_value; } + if (negate_aabonus) { aabonuses.DoubleSpecialAttack = effect_value; } + if (negate_itembonus) { itembonuses.DoubleSpecialAttack = effect_value; } break; case SE_TripleBackstab: - spellbonuses.TripleBackstab = effect_value; - aabonuses.TripleBackstab = effect_value; - itembonuses.TripleBackstab = effect_value; + if (negate_spellbonus) { spellbonuses.TripleBackstab = effect_value; } + if (negate_aabonus) { aabonuses.TripleBackstab = effect_value; } + if (negate_itembonus) { itembonuses.TripleBackstab = effect_value; } break; case SE_FrontalBackstabMinDmg: - spellbonuses.FrontalBackstabMinDmg = false; + if (negate_spellbonus) { spellbonuses.FrontalBackstabMinDmg = false; } break; case SE_FrontalBackstabChance: - spellbonuses.FrontalBackstabChance = effect_value; - aabonuses.FrontalBackstabChance = effect_value; - itembonuses.FrontalBackstabChance = effect_value; + if (negate_spellbonus) { spellbonuses.FrontalBackstabChance = effect_value; } + if (negate_aabonus) { aabonuses.FrontalBackstabChance = effect_value; } + if (negate_itembonus) { itembonuses.FrontalBackstabChance = effect_value; } + break; + + case SE_Double_Backstab_Front: + if (negate_spellbonus) { spellbonuses.Double_Backstab_Front = effect_value; } + if (negate_aabonus) { aabonuses.Double_Backstab_Front = effect_value; } + if (negate_itembonus) { itembonuses.Double_Backstab_Front = effect_value; } break; case SE_ConsumeProjectile: - spellbonuses.ConsumeProjectile = effect_value; - aabonuses.ConsumeProjectile = effect_value; - itembonuses.ConsumeProjectile = effect_value; + if (negate_spellbonus) { spellbonuses.ConsumeProjectile = effect_value; } + if (negate_aabonus) { aabonuses.ConsumeProjectile = effect_value; } + if (negate_itembonus) { itembonuses.ConsumeProjectile = effect_value; } break; case SE_ForageAdditionalItems: - spellbonuses.ForageAdditionalItems = effect_value; - aabonuses.ForageAdditionalItems = effect_value; - itembonuses.ForageAdditionalItems = effect_value; + if (negate_spellbonus) { spellbonuses.ForageAdditionalItems = effect_value; } + if (negate_aabonus) { aabonuses.ForageAdditionalItems = effect_value; } + if (negate_itembonus) { itembonuses.ForageAdditionalItems = effect_value; } break; case SE_Salvage: - spellbonuses.SalvageChance = effect_value; - aabonuses.SalvageChance = effect_value; - itembonuses.SalvageChance = effect_value; + if (negate_spellbonus) { spellbonuses.SalvageChance = effect_value; } + if (negate_aabonus) { aabonuses.SalvageChance = effect_value; } + if (negate_itembonus) { itembonuses.SalvageChance = effect_value; } break; case SE_ArcheryDamageModifier: - spellbonuses.ArcheryDamageModifier = effect_value; - aabonuses.ArcheryDamageModifier = effect_value; - itembonuses.ArcheryDamageModifier = effect_value; + if (negate_spellbonus) { spellbonuses.ArcheryDamageModifier = effect_value; } + if (negate_aabonus) { aabonuses.ArcheryDamageModifier = effect_value; } + if (negate_itembonus) { itembonuses.ArcheryDamageModifier = effect_value; } break; case SE_SecondaryDmgInc: - spellbonuses.SecondaryDmgInc = false; - aabonuses.SecondaryDmgInc = false; - itembonuses.SecondaryDmgInc = false; + if (negate_spellbonus) { spellbonuses.SecondaryDmgInc = false; } + if (negate_aabonus) { aabonuses.SecondaryDmgInc = false; } + if (negate_itembonus) { itembonuses.SecondaryDmgInc = false; } break; case SE_StrikeThrough: - spellbonuses.StrikeThrough = effect_value; - aabonuses.StrikeThrough = effect_value; - itembonuses.StrikeThrough = effect_value; + if (negate_spellbonus) { spellbonuses.StrikeThrough = effect_value; } + if (negate_aabonus) { aabonuses.StrikeThrough = effect_value; } + if (negate_itembonus) { itembonuses.StrikeThrough = effect_value; } break; case SE_StrikeThrough2: - spellbonuses.StrikeThrough = effect_value; - aabonuses.StrikeThrough = effect_value; - itembonuses.StrikeThrough = effect_value; + if (negate_spellbonus) { spellbonuses.StrikeThrough = effect_value; } + if (negate_aabonus) { aabonuses.StrikeThrough = effect_value; } + if (negate_itembonus) { itembonuses.StrikeThrough = effect_value; } break; case SE_GiveDoubleAttack: - spellbonuses.GiveDoubleAttack = effect_value; - aabonuses.GiveDoubleAttack = effect_value; - itembonuses.GiveDoubleAttack = effect_value; + if (negate_spellbonus) { spellbonuses.GiveDoubleAttack = effect_value; } + if (negate_aabonus) { aabonuses.GiveDoubleAttack = effect_value; } + if (negate_itembonus) { itembonuses.GiveDoubleAttack = effect_value; } break; case SE_PetCriticalHit: - spellbonuses.PetCriticalHit = effect_value; - aabonuses.PetCriticalHit = effect_value; - itembonuses.PetCriticalHit = effect_value; + if (negate_spellbonus) { spellbonuses.PetCriticalHit = effect_value; } + if (negate_aabonus) { aabonuses.PetCriticalHit = effect_value; } + if (negate_itembonus) { itembonuses.PetCriticalHit = effect_value; } break; case SE_CombatStability: - spellbonuses.CombatStability = effect_value; - aabonuses.CombatStability = effect_value; - itembonuses.CombatStability = effect_value; + if (negate_spellbonus) { spellbonuses.CombatStability = effect_value; } + if (negate_aabonus) { aabonuses.CombatStability = effect_value; } + if (negate_itembonus) { itembonuses.CombatStability = effect_value; } break; case SE_PetAvoidance: - spellbonuses.PetAvoidance = effect_value; - aabonuses.PetAvoidance = effect_value; - itembonuses.PetAvoidance = effect_value; + if (negate_spellbonus) { spellbonuses.PetAvoidance = effect_value; } + if (negate_aabonus) { aabonuses.PetAvoidance = effect_value; } + if (negate_itembonus) { itembonuses.PetAvoidance = effect_value; } break; case SE_Ambidexterity: - spellbonuses.Ambidexterity = effect_value; - aabonuses.Ambidexterity = effect_value; - itembonuses.Ambidexterity = effect_value; + if (negate_spellbonus) { spellbonuses.Ambidexterity = effect_value; } + if (negate_aabonus) { aabonuses.Ambidexterity = effect_value; } + if (negate_itembonus) { itembonuses.Ambidexterity = effect_value; } break; case SE_PetMaxHP: - spellbonuses.PetMaxHP = effect_value; - aabonuses.PetMaxHP = effect_value; - itembonuses.PetMaxHP = effect_value; + if (negate_spellbonus) { spellbonuses.PetMaxHP = effect_value; } + if (negate_aabonus) { aabonuses.PetMaxHP = effect_value; } + if (negate_itembonus) { itembonuses.PetMaxHP = effect_value; } break; case SE_PetFlurry: - spellbonuses.PetFlurry = effect_value; - aabonuses.PetFlurry = effect_value; - itembonuses.PetFlurry = effect_value; + if (negate_spellbonus) { spellbonuses.PetFlurry = effect_value; } + if (negate_aabonus) { aabonuses.PetFlurry = effect_value; } + if (negate_itembonus) { itembonuses.PetFlurry = effect_value; } break; case SE_GivePetGroupTarget: - spellbonuses.GivePetGroupTarget = false; - aabonuses.GivePetGroupTarget = false; - itembonuses.GivePetGroupTarget = false; + if (negate_spellbonus) { spellbonuses.GivePetGroupTarget = false; } + if (negate_aabonus) { aabonuses.GivePetGroupTarget = false; } + if (negate_itembonus) { itembonuses.GivePetGroupTarget = false; } break; case SE_PetMeleeMitigation: - spellbonuses.PetMeleeMitigation = effect_value; - itembonuses.PetMeleeMitigation = effect_value; - aabonuses.PetMeleeMitigation = effect_value; + if (negate_spellbonus) { spellbonuses.PetMeleeMitigation = effect_value; } + if (negate_itembonus) { itembonuses.PetMeleeMitigation = effect_value; } + if (negate_aabonus) { aabonuses.PetMeleeMitigation = effect_value; } break; case SE_RootBreakChance: - spellbonuses.RootBreakChance = effect_value; - aabonuses.RootBreakChance = effect_value; - itembonuses.RootBreakChance = effect_value; + if (negate_spellbonus) { spellbonuses.RootBreakChance = effect_value; } + if (negate_aabonus) { aabonuses.RootBreakChance = effect_value; } + if (negate_itembonus) { itembonuses.RootBreakChance = effect_value; } break; case SE_ChannelChanceItems: - spellbonuses.ChannelChanceItems = effect_value; - aabonuses.ChannelChanceItems = effect_value; - itembonuses.ChannelChanceItems = effect_value; + if (negate_spellbonus) { spellbonuses.ChannelChanceItems = effect_value; } + if (negate_aabonus) { aabonuses.ChannelChanceItems = effect_value; } + if (negate_itembonus) { itembonuses.ChannelChanceItems = effect_value; } break; case SE_ChannelChanceSpells: - spellbonuses.ChannelChanceSpells = effect_value; - aabonuses.ChannelChanceSpells = effect_value; - itembonuses.ChannelChanceSpells = effect_value; + if (negate_spellbonus) { spellbonuses.ChannelChanceSpells = effect_value; } + if (negate_aabonus) { aabonuses.ChannelChanceSpells = effect_value; } + if (negate_itembonus) { itembonuses.ChannelChanceSpells = effect_value; } break; case SE_UnfailingDivinity: - spellbonuses.UnfailingDivinity = effect_value; - aabonuses.UnfailingDivinity = effect_value; - itembonuses.UnfailingDivinity = effect_value; + if (negate_spellbonus) { spellbonuses.UnfailingDivinity = effect_value; } + if (negate_aabonus) { aabonuses.UnfailingDivinity = effect_value; } + if (negate_itembonus) { itembonuses.UnfailingDivinity = effect_value; } break; case SE_ItemHPRegenCapIncrease: - spellbonuses.ItemHPRegenCap = effect_value; - aabonuses.ItemHPRegenCap = effect_value; - itembonuses.ItemHPRegenCap = effect_value; + if (negate_spellbonus) { spellbonuses.ItemHPRegenCap = effect_value; } + if (negate_aabonus) { aabonuses.ItemHPRegenCap = effect_value; } + if (negate_itembonus) { itembonuses.ItemHPRegenCap = effect_value; } + break; + + case SE_Worn_Endurance_Regen_Cap: + if (negate_spellbonus) { spellbonuses.ItemEnduranceRegenCap = effect_value; } + if (negate_aabonus) { aabonuses.ItemEnduranceRegenCap = effect_value; } + if (negate_itembonus) { itembonuses.ItemEnduranceRegenCap = effect_value; } break; case SE_OffhandRiposteFail: - spellbonuses.OffhandRiposteFail = effect_value; - aabonuses.OffhandRiposteFail = effect_value; - itembonuses.OffhandRiposteFail = effect_value; + if (negate_spellbonus) { spellbonuses.OffhandRiposteFail = effect_value; } + if (negate_aabonus) { aabonuses.OffhandRiposteFail = effect_value; } + if (negate_itembonus) { itembonuses.OffhandRiposteFail = effect_value; } break; case SE_ItemAttackCapIncrease: - aabonuses.ItemATKCap = effect_value; - itembonuses.ItemATKCap = effect_value; - spellbonuses.ItemATKCap = effect_value; + if (negate_aabonus) { aabonuses.ItemATKCap = effect_value; } + if (negate_itembonus) { itembonuses.ItemATKCap = effect_value; } + if (negate_spellbonus) { spellbonuses.ItemATKCap = effect_value; } break; case SE_SpellEffectResistChance: { - for(int e = 0; e < MAX_RESISTABLE_EFFECTS*2; e+=2) + for (int e = 0; e < MAX_RESISTABLE_EFFECTS * 2; e += 2) { - spellbonuses.SEResist[e] = effect_value; - spellbonuses.SEResist[e+1] = effect_value; + if (negate_spellbonus) { spellbonuses.SEResist[e] = effect_value; } + if (negate_spellbonus) { spellbonuses.SEResist[e + 1] = effect_value; } } break; } case SE_MasteryofPast: - spellbonuses.MasteryofPast = effect_value; - aabonuses.MasteryofPast = effect_value; - itembonuses.MasteryofPast = effect_value; + if (negate_spellbonus) { spellbonuses.MasteryofPast = effect_value; } + if (negate_aabonus) { aabonuses.MasteryofPast = effect_value; } + if (negate_itembonus) { itembonuses.MasteryofPast = effect_value; } break; case SE_DoubleRiposte: - spellbonuses.DoubleRiposte = effect_value; - itembonuses.DoubleRiposte = effect_value; - aabonuses.DoubleRiposte = effect_value; + if (negate_spellbonus) { spellbonuses.DoubleRiposte = effect_value; } + if (negate_itembonus) { itembonuses.DoubleRiposte = effect_value; } + if (negate_aabonus) { aabonuses.DoubleRiposte = effect_value; } break; case SE_GiveDoubleRiposte: - spellbonuses.GiveDoubleRiposte[0] = effect_value; - itembonuses.GiveDoubleRiposte[0] = effect_value; - aabonuses.GiveDoubleRiposte[0] = effect_value; + if (negate_spellbonus) { spellbonuses.GiveDoubleRiposte[SBIndex::DOUBLE_RIPOSTE_CHANCE] = effect_value; } + if (negate_itembonus) { itembonuses.GiveDoubleRiposte[SBIndex::DOUBLE_RIPOSTE_CHANCE] = effect_value; } + if (negate_aabonus) { aabonuses.GiveDoubleRiposte[SBIndex::DOUBLE_RIPOSTE_CHANCE] = effect_value; } break; case SE_SlayUndead: - spellbonuses.SlayUndead[0] = effect_value; - spellbonuses.SlayUndead[1] = effect_value; - itembonuses.SlayUndead[0] = effect_value; - itembonuses.SlayUndead[1] = effect_value; - aabonuses.SlayUndead[0] = effect_value; - aabonuses.SlayUndead[1] = effect_value; + if (negate_spellbonus) { spellbonuses.SlayUndead[SBIndex::SLAYUNDEAD_RATE_MOD] = effect_value; } + if (negate_spellbonus) { spellbonuses.SlayUndead[SBIndex::SLAYUNDEAD_DMG_MOD] = effect_value; } + if (negate_itembonus) { itembonuses.SlayUndead[SBIndex::SLAYUNDEAD_RATE_MOD] = effect_value; } + if (negate_itembonus) { itembonuses.SlayUndead[SBIndex::SLAYUNDEAD_DMG_MOD] = effect_value; } + if (negate_aabonus) { aabonuses.SlayUndead[SBIndex::SLAYUNDEAD_RATE_MOD] = effect_value; } + if (negate_aabonus) { aabonuses.SlayUndead[SBIndex::SLAYUNDEAD_DMG_MOD] = effect_value; } break; case SE_DoubleRangedAttack: - spellbonuses.DoubleRangedAttack = effect_value; - aabonuses.DoubleRangedAttack = effect_value; - itembonuses.DoubleRangedAttack = effect_value; + if (negate_spellbonus) { spellbonuses.DoubleRangedAttack = effect_value; } + if (negate_aabonus) { aabonuses.DoubleRangedAttack = effect_value; } + if (negate_itembonus) { itembonuses.DoubleRangedAttack = effect_value; } break; case SE_ShieldEquipDmgMod: - spellbonuses.ShieldEquipDmgMod = effect_value; - aabonuses.ShieldEquipDmgMod = effect_value; - itembonuses.ShieldEquipDmgMod = effect_value; + if (negate_spellbonus) { spellbonuses.ShieldEquipDmgMod = effect_value; } + if (negate_aabonus) { aabonuses.ShieldEquipDmgMod = effect_value; } + if (negate_itembonus) { itembonuses.ShieldEquipDmgMod = effect_value; } break; case SE_TriggerMeleeThreshold: - spellbonuses.TriggerMeleeThreshold = false; + if (negate_spellbonus) { spellbonuses.TriggerMeleeThreshold = false; } break; case SE_TriggerSpellThreshold: - spellbonuses.TriggerSpellThreshold = false; + if (negate_spellbonus) { spellbonuses.TriggerSpellThreshold = false; } break; case SE_DivineAura: - spellbonuses.DivineAura = false; + if (negate_spellbonus) { spellbonuses.DivineAura = false; } break; case SE_StunBashChance: - spellbonuses.StunBashChance = effect_value; - itembonuses.StunBashChance = effect_value; - aabonuses.StunBashChance = effect_value; + if (negate_spellbonus) { spellbonuses.StunBashChance = effect_value; } + if (negate_itembonus) { itembonuses.StunBashChance = effect_value; } + if (negate_aabonus) { aabonuses.StunBashChance = effect_value; } break; case SE_IncreaseChanceMemwipe: - spellbonuses.IncreaseChanceMemwipe = effect_value; - itembonuses.IncreaseChanceMemwipe = effect_value; - aabonuses.IncreaseChanceMemwipe = effect_value; + if (negate_spellbonus) { spellbonuses.IncreaseChanceMemwipe = effect_value; } + if (negate_itembonus) { itembonuses.IncreaseChanceMemwipe = effect_value; } + if (negate_aabonus) { aabonuses.IncreaseChanceMemwipe = effect_value; } break; case SE_CriticalMend: - spellbonuses.CriticalMend = effect_value; - itembonuses.CriticalMend = effect_value; - aabonuses.CriticalMend = effect_value; + if (negate_spellbonus) { spellbonuses.CriticalMend = effect_value; } + if (negate_itembonus) { itembonuses.CriticalMend = effect_value; } + if (negate_aabonus) { aabonuses.CriticalMend = effect_value; } break; case SE_DistanceRemoval: - spellbonuses.DistanceRemoval = false; + if (negate_spellbonus) { spellbonuses.DistanceRemoval = false; } break; case SE_ImprovedTaunt: - spellbonuses.ImprovedTaunt[0] = effect_value; - spellbonuses.ImprovedTaunt[1] = effect_value; - spellbonuses.ImprovedTaunt[2] = -1; + if (negate_spellbonus) { spellbonuses.ImprovedTaunt[SBIndex::IMPROVED_TAUNT_MAX_LVL] = effect_value; } + if (negate_spellbonus) { spellbonuses.ImprovedTaunt[SBIndex::IMPROVED_TAUNT_AGGRO_MOD] = effect_value; } + if (negate_spellbonus) { spellbonuses.ImprovedTaunt[SBIndex::IMPROVED_TAUNT_BUFFSLOT] = -1; } break; case SE_FrenziedDevastation: - spellbonuses.FrenziedDevastation = effect_value; - aabonuses.FrenziedDevastation = effect_value; - itembonuses.FrenziedDevastation = effect_value; + if (negate_spellbonus) { spellbonuses.FrenziedDevastation = effect_value; } + if (negate_aabonus) { aabonuses.FrenziedDevastation = effect_value; } + if (negate_itembonus) { itembonuses.FrenziedDevastation = effect_value; } break; case SE_Root: - spellbonuses.Root[0] = effect_value; - spellbonuses.Root[1] = -1; + if (negate_spellbonus) { spellbonuses.Root[SBIndex::ROOT_EXISTS] = effect_value; } + if (negate_spellbonus) { spellbonuses.Root[SBIndex::ROOT_BUFFSLOT] = -1; } break; case SE_Rune: - spellbonuses.MeleeRune[0] = effect_value; - spellbonuses.MeleeRune[1] = -1; + if (negate_spellbonus) { spellbonuses.MeleeRune[SBIndex::RUNE_AMOUNT] = effect_value; } + if (negate_spellbonus) { spellbonuses.MeleeRune[SBIndex::RUNE_BUFFSLOT] = -1; } break; case SE_AbsorbMagicAtt: - spellbonuses.AbsorbMagicAtt[0] = effect_value; - spellbonuses.AbsorbMagicAtt[1] = -1; + if (negate_spellbonus) { spellbonuses.AbsorbMagicAtt[SBIndex::RUNE_AMOUNT] = effect_value; } + if (negate_spellbonus) { spellbonuses.AbsorbMagicAtt[SBIndex::RUNE_BUFFSLOT] = -1; } break; case SE_Berserk: - spellbonuses.BerserkSPA = false; - aabonuses.BerserkSPA = false; - itembonuses.BerserkSPA = false; + if (negate_spellbonus) { spellbonuses.BerserkSPA = false; } + if (negate_aabonus) { aabonuses.BerserkSPA = false; } + if (negate_itembonus) { itembonuses.BerserkSPA = false; } break; case SE_Vampirism: - spellbonuses.Vampirism = effect_value; - aabonuses.Vampirism = effect_value; - itembonuses.Vampirism = effect_value; + if (negate_spellbonus) { spellbonuses.Vampirism = effect_value; } + if (negate_aabonus) { aabonuses.Vampirism = effect_value; } + if (negate_itembonus) { itembonuses.Vampirism = effect_value; } break; case SE_Metabolism: - spellbonuses.Metabolism = effect_value; - aabonuses.Metabolism = effect_value; - itembonuses.Metabolism = effect_value; + if (negate_spellbonus) { spellbonuses.Metabolism = effect_value; } + if (negate_aabonus) { aabonuses.Metabolism = effect_value; } + if (negate_itembonus) { itembonuses.Metabolism = effect_value; } break; case SE_ImprovedReclaimEnergy: - spellbonuses.ImprovedReclaimEnergy = effect_value; - aabonuses.ImprovedReclaimEnergy = effect_value; - itembonuses.ImprovedReclaimEnergy = effect_value; + if (negate_spellbonus) { spellbonuses.ImprovedReclaimEnergy = effect_value; } + if (negate_aabonus) { aabonuses.ImprovedReclaimEnergy = effect_value; } + if (negate_itembonus) { itembonuses.ImprovedReclaimEnergy = effect_value; } break; case SE_HeadShot: - spellbonuses.HeadShot[0] = effect_value; - aabonuses.HeadShot[0] = effect_value; - itembonuses.HeadShot[0] = effect_value; - spellbonuses.HeadShot[1] = effect_value; - aabonuses.HeadShot[1] = effect_value; - itembonuses.HeadShot[1] = effect_value; + if (negate_spellbonus) { spellbonuses.HeadShot[SBIndex::FINISHING_EFFECT_PROC_CHANCE] = effect_value; } + if (negate_aabonus) { aabonuses.HeadShot[SBIndex::FINISHING_EFFECT_PROC_CHANCE] = effect_value; } + if (negate_itembonus) { itembonuses.HeadShot[SBIndex::FINISHING_EFFECT_PROC_CHANCE] = effect_value; } + if (negate_spellbonus) { spellbonuses.HeadShot[SBIndex::FINISHING_EFFECT_DMG] = effect_value; } + if (negate_aabonus) { aabonuses.HeadShot[SBIndex::FINISHING_EFFECT_DMG] = effect_value; } + if (negate_itembonus) { itembonuses.HeadShot[SBIndex::FINISHING_EFFECT_DMG] = effect_value; } break; case SE_HeadShotLevel: - spellbonuses.HSLevel[0] = effect_value; - aabonuses.HSLevel[0] = effect_value; - itembonuses.HSLevel[0] = effect_value; - spellbonuses.HSLevel[1] = effect_value; - aabonuses.HSLevel[1] = effect_value; - itembonuses.HSLevel[1] = effect_value; + if (negate_spellbonus) { spellbonuses.HSLevel[SBIndex::FINISHING_EFFECT_LEVEL_MAX] = effect_value; } + if (negate_aabonus) { aabonuses.HSLevel[SBIndex::FINISHING_EFFECT_LEVEL_MAX] = effect_value; } + if (negate_itembonus) { itembonuses.HSLevel[SBIndex::FINISHING_EFFECT_LEVEL_MAX] = effect_value; } + if (negate_spellbonus) { spellbonuses.HSLevel[SBIndex::FINISHING_EFFECT_LEVEL_CHANCE_BONUS] = effect_value; } + if (negate_aabonus) { aabonuses.HSLevel[SBIndex::FINISHING_EFFECT_LEVEL_CHANCE_BONUS] = effect_value; } + if (negate_itembonus) { itembonuses.HSLevel[SBIndex::FINISHING_EFFECT_LEVEL_CHANCE_BONUS] = effect_value; } break; case SE_Assassinate: - spellbonuses.Assassinate[0] = effect_value; - aabonuses.Assassinate[0] = effect_value; - itembonuses.Assassinate[0] = effect_value; - spellbonuses.Assassinate[1] = effect_value; - aabonuses.Assassinate[1] = effect_value; - itembonuses.Assassinate[1] = effect_value; + if (negate_spellbonus) { spellbonuses.Assassinate[SBIndex::FINISHING_EFFECT_PROC_CHANCE] = effect_value; } + if (negate_aabonus) { aabonuses.Assassinate[SBIndex::FINISHING_EFFECT_PROC_CHANCE] = effect_value; } + if (negate_itembonus) { itembonuses.Assassinate[SBIndex::FINISHING_EFFECT_PROC_CHANCE] = effect_value; } + if (negate_spellbonus) { spellbonuses.Assassinate[SBIndex::FINISHING_EFFECT_DMG] = effect_value; } + if (negate_aabonus) { aabonuses.Assassinate[SBIndex::FINISHING_EFFECT_DMG] = effect_value; } + if (negate_itembonus) { itembonuses.Assassinate[SBIndex::FINISHING_EFFECT_DMG] = effect_value; } break; case SE_AssassinateLevel: - spellbonuses.AssassinateLevel[0] = effect_value; - aabonuses.AssassinateLevel[0] = effect_value; - itembonuses.AssassinateLevel[0] = effect_value; - spellbonuses.AssassinateLevel[1] = effect_value; - aabonuses.AssassinateLevel[1] = effect_value; - itembonuses.AssassinateLevel[1] = effect_value; + if (negate_spellbonus) { spellbonuses.AssassinateLevel[SBIndex::FINISHING_EFFECT_LEVEL_MAX] = effect_value; } + if (negate_aabonus) { aabonuses.AssassinateLevel[SBIndex::FINISHING_EFFECT_LEVEL_MAX] = effect_value; } + if (negate_itembonus) { itembonuses.AssassinateLevel[SBIndex::FINISHING_EFFECT_LEVEL_MAX] = effect_value; } + if (negate_spellbonus) { spellbonuses.AssassinateLevel[SBIndex::FINISHING_EFFECT_LEVEL_CHANCE_BONUS] = effect_value; } + if (negate_aabonus) { aabonuses.AssassinateLevel[SBIndex::FINISHING_EFFECT_LEVEL_CHANCE_BONUS] = effect_value; } + if (negate_itembonus) { itembonuses.AssassinateLevel[SBIndex::FINISHING_EFFECT_LEVEL_CHANCE_BONUS] = effect_value; } break; case SE_FinishingBlow: - spellbonuses.FinishingBlow[0] = effect_value; - aabonuses.FinishingBlow[0] = effect_value; - itembonuses.FinishingBlow[0] = effect_value; - spellbonuses.FinishingBlow[1] = effect_value; - aabonuses.FinishingBlow[1] = effect_value; - itembonuses.FinishingBlow[1] = effect_value; + if (negate_spellbonus) { spellbonuses.FinishingBlow[SBIndex::FINISHING_EFFECT_PROC_CHANCE] = effect_value; } + if (negate_aabonus) { aabonuses.FinishingBlow[SBIndex::FINISHING_EFFECT_PROC_CHANCE] = effect_value; } + if (negate_itembonus) { itembonuses.FinishingBlow[SBIndex::FINISHING_EFFECT_PROC_CHANCE] = effect_value; } + if (negate_spellbonus) { spellbonuses.FinishingBlow[SBIndex::FINISHING_EFFECT_DMG] = effect_value; } + if (negate_aabonus) { aabonuses.FinishingBlow[SBIndex::FINISHING_EFFECT_DMG] = effect_value; } + if (negate_itembonus) { itembonuses.FinishingBlow[SBIndex::FINISHING_EFFECT_DMG] = effect_value; } break; case SE_FinishingBlowLvl: - spellbonuses.FinishingBlowLvl[0] = effect_value; - aabonuses.FinishingBlowLvl[0] = effect_value; - itembonuses.FinishingBlowLvl[0] = effect_value; - spellbonuses.FinishingBlowLvl[1] = effect_value; - aabonuses.FinishingBlowLvl[1] = effect_value; - itembonuses.FinishingBlowLvl[1] = effect_value; + if (negate_spellbonus) { spellbonuses.FinishingBlowLvl[SBIndex::FINISHING_EFFECT_LEVEL_MAX] = effect_value; } + if (negate_aabonus) { aabonuses.FinishingBlowLvl[SBIndex::FINISHING_EFFECT_LEVEL_MAX] = effect_value; } + if (negate_itembonus) { itembonuses.FinishingBlowLvl[SBIndex::FINISHING_EFFECT_LEVEL_MAX] = effect_value; } + if (negate_spellbonus) { spellbonuses.FinishingBlowLvl[SBIndex::FINISHING_BLOW_LEVEL_HP_RATIO] = effect_value; } + if (negate_aabonus) { aabonuses.FinishingBlowLvl[SBIndex::FINISHING_BLOW_LEVEL_HP_RATIO] = effect_value; } + if (negate_itembonus) { itembonuses.FinishingBlowLvl[SBIndex::FINISHING_BLOW_LEVEL_HP_RATIO] = effect_value; } break; case SE_Sanctuary: - spellbonuses.Sanctuary = false; + if (negate_spellbonus) { spellbonuses.Sanctuary = false; } break; case SE_FactionModPct: - spellbonuses.FactionModPct = effect_value; - itembonuses.FactionModPct = effect_value; - aabonuses.FactionModPct = effect_value; + if (negate_spellbonus) { spellbonuses.FactionModPct = effect_value; } + if (negate_itembonus) { itembonuses.FactionModPct = effect_value; } + if (negate_aabonus) { aabonuses.FactionModPct = effect_value; } break; case SE_IllusionPersistence: - spellbonuses.IllusionPersistence = false; - itembonuses.IllusionPersistence = false; - aabonuses.IllusionPersistence = false; + if (negate_spellbonus) { spellbonuses.IllusionPersistence = effect_value; } + if (negate_itembonus) { itembonuses.IllusionPersistence = effect_value; } + if (negate_aabonus) { aabonuses.IllusionPersistence = effect_value; } break; - case SE_SkillProcSuccess:{ - for(int e = 0; e < MAX_SKILL_PROCS; e++) - { - spellbonuses.SkillProcSuccess[e] = effect_value; - itembonuses.SkillProcSuccess[e] = effect_value; - aabonuses.SkillProcSuccess[e] = effect_value; - } - } + case SE_Attack_Accuracy_Max_Percent: + if (negate_spellbonus) { spellbonuses.Attack_Accuracy_Max_Percent = effect_value; } + if (negate_itembonus) { itembonuses.Attack_Accuracy_Max_Percent = effect_value; } + if (negate_aabonus) { aabonuses.Attack_Accuracy_Max_Percent = effect_value; } + break; - case SE_SkillProc:{ - for(int e = 0; e < MAX_SKILL_PROCS; e++) + + case SE_AC_Mitigation_Max_Percent: + if (negate_spellbonus) { spellbonuses.AC_Mitigation_Max_Percent = effect_value; } + if (negate_itembonus) { itembonuses.AC_Mitigation_Max_Percent = effect_value; } + if (negate_aabonus) { aabonuses.AC_Mitigation_Max_Percent = effect_value; } + break; + + case SE_AC_Avoidance_Max_Percent: + if (negate_spellbonus) { spellbonuses.AC_Avoidance_Max_Percent = effect_value; } + if (negate_itembonus) { itembonuses.AC_Avoidance_Max_Percent = effect_value; } + if (negate_aabonus) { aabonuses.AC_Avoidance_Max_Percent = effect_value; } + break; + + case SE_Melee_Damage_Position_Mod: + if (negate_spellbonus) { spellbonuses.Melee_Damage_Position_Mod[SBIndex::POSITION_BACK] = effect_value; } + if (negate_aabonus) { aabonuses.Melee_Damage_Position_Mod[SBIndex::POSITION_BACK] = effect_value; } + if (negate_itembonus) { itembonuses.Melee_Damage_Position_Mod[SBIndex::POSITION_BACK] = effect_value; } + if (negate_spellbonus) { spellbonuses.Melee_Damage_Position_Mod[SBIndex::POSITION_FRONT] = effect_value; } + if (negate_aabonus) { aabonuses.Melee_Damage_Position_Mod[SBIndex::POSITION_FRONT] = effect_value; } + if (negate_itembonus) { itembonuses.Melee_Damage_Position_Mod[SBIndex::POSITION_FRONT] = effect_value; } + break; + + case SE_Damage_Taken_Position_Mod: + if (negate_spellbonus) { spellbonuses.Damage_Taken_Position_Mod[SBIndex::POSITION_BACK] = effect_value; } + if (negate_aabonus) { aabonuses.Damage_Taken_Position_Mod[SBIndex::POSITION_BACK] = effect_value; } + if (negate_itembonus) { itembonuses.Damage_Taken_Position_Mod[SBIndex::POSITION_BACK] = effect_value; } + if (negate_spellbonus) { spellbonuses.Damage_Taken_Position_Mod[SBIndex::POSITION_FRONT] = effect_value; } + if (negate_aabonus) { aabonuses.Damage_Taken_Position_Mod[SBIndex::POSITION_FRONT] = effect_value; } + if (negate_itembonus) { itembonuses.Damage_Taken_Position_Mod[SBIndex::POSITION_FRONT] = effect_value; } + break; + + case SE_Melee_Damage_Position_Amt: + if (negate_spellbonus) { spellbonuses.Melee_Damage_Position_Amt[SBIndex::POSITION_BACK] = effect_value; } + if (negate_aabonus) { aabonuses.Melee_Damage_Position_Amt[SBIndex::POSITION_BACK] = effect_value; } + if (negate_itembonus) { itembonuses.Melee_Damage_Position_Amt[SBIndex::POSITION_BACK] = effect_value; } + if (negate_spellbonus) { spellbonuses.Melee_Damage_Position_Amt[SBIndex::POSITION_FRONT] = effect_value; } + if (negate_aabonus) { aabonuses.Melee_Damage_Position_Amt[SBIndex::POSITION_FRONT] = effect_value; } + if (negate_itembonus) { itembonuses.Melee_Damage_Position_Amt[SBIndex::POSITION_FRONT] = effect_value; } + break; + + case SE_Damage_Taken_Position_Amt: + if (negate_spellbonus) { spellbonuses.Damage_Taken_Position_Amt[SBIndex::POSITION_BACK] = effect_value; } + if (negate_aabonus) { aabonuses.Damage_Taken_Position_Amt[SBIndex::POSITION_BACK] = effect_value; } + if (negate_itembonus) { itembonuses.Damage_Taken_Position_Amt[SBIndex::POSITION_BACK] = effect_value; } + if (negate_spellbonus) { spellbonuses.Damage_Taken_Position_Amt[SBIndex::POSITION_FRONT] = effect_value; } + if (negate_aabonus) { aabonuses.Damage_Taken_Position_Amt[SBIndex::POSITION_FRONT] = effect_value; } + if (negate_itembonus) { itembonuses.Damage_Taken_Position_Amt[SBIndex::POSITION_FRONT] = effect_value; } + break; + + + case SE_DS_Mitigation_Amount: + if (negate_spellbonus) { spellbonuses.DS_Mitigation_Amount = effect_value; } + if (negate_itembonus) { itembonuses.DS_Mitigation_Amount = effect_value; } + if (negate_aabonus) { aabonuses.DS_Mitigation_Amount = effect_value; } + break; + + case SE_DS_Mitigation_Percentage: + if (negate_spellbonus) { spellbonuses.DS_Mitigation_Percentage = effect_value; } + if (negate_itembonus) { itembonuses.DS_Mitigation_Percentage = effect_value; } + if (negate_aabonus) { aabonuses.DS_Mitigation_Percentage = effect_value; } + break; + + case SE_Pet_Crit_Melee_Damage_Pct_Owner: + if (negate_spellbonus) { spellbonuses.Pet_Crit_Melee_Damage_Pct_Owner = effect_value; } + if (negate_itembonus) { itembonuses.Pet_Crit_Melee_Damage_Pct_Owner = effect_value; } + if (negate_aabonus) { aabonuses.Pet_Crit_Melee_Damage_Pct_Owner = effect_value; } + break; + + case SE_Pet_Add_Atk: + if (negate_spellbonus) { spellbonuses.Pet_Add_Atk = effect_value; } + if (negate_itembonus) { itembonuses.Pet_Add_Atk = effect_value; } + if (negate_aabonus) { aabonuses.Pet_Add_Atk = effect_value; } + break; + + case SE_PC_Pet_Rampage: + if (negate_spellbonus) { spellbonuses.PC_Pet_Rampage[SBIndex::PET_RAMPAGE_CHANCE] = effect_value; } + if (negate_itembonus) { itembonuses.PC_Pet_Rampage[SBIndex::PET_RAMPAGE_CHANCE] = effect_value; } + if (negate_aabonus) { aabonuses.PC_Pet_Rampage[SBIndex::PET_RAMPAGE_CHANCE] = effect_value; } + if (negate_spellbonus) { spellbonuses.PC_Pet_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] = effect_value; } + if (negate_itembonus) { itembonuses.PC_Pet_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] = effect_value; } + if (negate_aabonus) { aabonuses.PC_Pet_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] = effect_value; } + break; + + case SE_PC_Pet_AE_Rampage: + if (negate_spellbonus) { spellbonuses.PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_CHANCE] = effect_value; } + if (negate_itembonus) { itembonuses.PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_CHANCE] = effect_value; } + if (negate_aabonus) { aabonuses.PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_CHANCE] = effect_value; } + if (negate_spellbonus) { spellbonuses.PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] = effect_value; } + if (negate_itembonus) { itembonuses.PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] = effect_value; } + if (negate_aabonus) { aabonuses.PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] = effect_value; } + break; + + + case SE_SkillProcSuccess: { + for (int e = 0; e < MAX_SKILL_PROCS; e++) { - spellbonuses.SkillProc[e] = effect_value; - itembonuses.SkillProc[e] = effect_value; - aabonuses.SkillProc[e] = effect_value; + if (negate_spellbonus) { spellbonuses.SkillProcSuccess[e] = effect_value; } + if (negate_itembonus) { itembonuses.SkillProcSuccess[e] = effect_value; } + if (negate_aabonus) { aabonuses.SkillProcSuccess[e] = effect_value; } } - } + } + + case SE_SkillProcAttempt: { + for (int e = 0; e < MAX_SKILL_PROCS; e++) + { + if (negate_spellbonus) { spellbonuses.SkillProc[e] = effect_value; } + if (negate_itembonus) { itembonuses.SkillProc[e] = effect_value; } + if (negate_aabonus) { aabonuses.SkillProc[e] = effect_value; } + } + } } } } } - diff --git a/zone/bot.cpp b/zone/bot.cpp index d508b7a42..a7c9bd6f5 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -259,30 +259,30 @@ Bot::Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double to } for (int x1 = 0; x1 < EFFECT_COUNT; x1++) { - switch (spell.effectid[x1]) { + switch (spell.effect_id[x1]) { case SE_IllusionCopy: case SE_Illusion: { - if (spell.base[x1] == -1) { + if (spell.base_value[x1] == -1) { if (gender == 1) gender = 0; else if (gender == 0) gender = 1; SendIllusionPacket(GetRace(), gender, 0xFF, 0xFF); } - else if (spell.base[x1] == -2) // WTF IS THIS + else if (spell.base_value[x1] == -2) // WTF IS THIS { if (GetRace() == 128 || GetRace() == 130 || GetRace() <= 12) - SendIllusionPacket(GetRace(), GetGender(), spell.base2[x1], spell.max[x1]); + SendIllusionPacket(GetRace(), GetGender(), spell.limit_value[x1], spell.max_value[x1]); } - else if (spell.max[x1] > 0) + else if (spell.max_value[x1] > 0) { - SendIllusionPacket(spell.base[x1], 0xFF, spell.base2[x1], spell.max[x1]); + SendIllusionPacket(spell.base_value[x1], 0xFF, spell.limit_value[x1], spell.max_value[x1]); } else { - SendIllusionPacket(spell.base[x1], 0xFF, 0xFF, 0xFF); + SendIllusionPacket(spell.base_value[x1], 0xFF, 0xFF, 0xFF); } - switch (spell.base[x1]) { + switch (spell.base_value[x1]) { case OGRE: SendAppearancePacket(AT_Size, 9); break; @@ -369,17 +369,17 @@ Bot::Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double to case SE_AddMeleeProc: case SE_WeaponProc: { - AddProcToWeapon(GetProcID(buffs[j1].spellid, x1), false, 100 + spells[buffs[j1].spellid].base2[x1], buffs[j1].spellid, buffs[j1].casterlevel); + AddProcToWeapon(GetProcID(buffs[j1].spellid, x1), false, 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid, buffs[j1].casterlevel); break; } case SE_DefensiveProc: { - AddDefensiveProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].base2[x1], buffs[j1].spellid); + AddDefensiveProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid); break; } case SE_RangedProc: { - AddRangedProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].base2[x1], buffs[j1].spellid); + AddRangedProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid); break; } } @@ -400,10 +400,26 @@ Bot::Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double to current_hp = max_hp; if(current_hp <= 0) { - SetHP(max_hp/5); - SetMana(0); - BuffFadeAll(); - SpellOnTarget(756, this); // Rezz effects + BuffFadeNonPersistDeath(); + if (RuleB(Bots, ResurrectionSickness)) { + int resurrection_sickness_spell_id = ( + RuleB(Bots, OldRaceRezEffects) && + ( + GetRace() == BARBARIAN || + GetRace() == DWARF || + GetRace() == TROLL || + GetRace() == OGRE + ) ? + RuleI(Bots, OldResurrectionSicknessSpell) : + RuleI(Bots, ResurrectionSicknessSpell) + ); + SetHP(max_hp / 5); + SetMana(0); + SpellOnTarget(resurrection_sickness_spell_id, this); // Rezz effects + } else { + SetHP(GetMaxHP()); + SetMana(GetMaxMana()); + } } if(current_mana > max_mana) @@ -1703,206 +1719,15 @@ bool Bot::IsValidRaceClassCombo() return Bot::IsValidRaceClassCombo(GetRace(), GetClass()); } -bool Bot::IsValidRaceClassCombo(uint16 r, uint8 c) +bool Bot::IsValidRaceClassCombo(uint16 bot_race, uint8 bot_class) { - switch (r) { - case HUMAN: - switch (c) { - case WARRIOR: - case CLERIC: - case PALADIN: - case RANGER: - case SHADOWKNIGHT: - case DRUID: - case MONK: - case BARD: - case ROGUE: - case NECROMANCER: - case WIZARD: - case MAGICIAN: - case ENCHANTER: - return true; - } - break; - case BARBARIAN: - switch (c) { - case WARRIOR: - case ROGUE: - case SHAMAN: - case BEASTLORD: - case BERSERKER: - return true; - } - break; - case ERUDITE: - switch (c) { - case CLERIC: - case PALADIN: - case SHADOWKNIGHT: - case NECROMANCER: - case WIZARD: - case MAGICIAN: - case ENCHANTER: - return true; - } - break; - case WOOD_ELF: - switch (c) { - case WARRIOR: - case RANGER: - case DRUID: - case BARD: - case ROGUE: - return true; - } - break; - case HIGH_ELF: - switch (c) { - case CLERIC: - case PALADIN: - case WIZARD: - case MAGICIAN: - case ENCHANTER: - return true; - } - break; - case DARK_ELF: - switch (c) { - case WARRIOR: - case CLERIC: - case SHADOWKNIGHT: - case ROGUE: - case NECROMANCER: - case WIZARD: - case MAGICIAN: - case ENCHANTER: - return true; - } - break; - case HALF_ELF: - switch (c) { - case WARRIOR: - case PALADIN: - case RANGER: - case DRUID: - case BARD: - case ROGUE: - return true; - } - break; - case DWARF: - switch (c) { - case WARRIOR: - case CLERIC: - case PALADIN: - case ROGUE: - case BERSERKER: - return true; - } - break; - case TROLL: - switch (c) { - case WARRIOR: - case SHADOWKNIGHT: - case SHAMAN: - case BEASTLORD: - case BERSERKER: - return true; - } - break; - case OGRE: - switch (c) { - case WARRIOR: - case SHADOWKNIGHT: - case SHAMAN: - case BEASTLORD: - case BERSERKER: - return true; - } - break; - case HALFLING: - switch (c) { - case WARRIOR: - case CLERIC: - case PALADIN: - case RANGER: - case DRUID: - case ROGUE: - return true; - } - break; - case GNOME: - switch (c) { - case WARRIOR: - case CLERIC: - case PALADIN: - case SHADOWKNIGHT: - case ROGUE: - case NECROMANCER: - case WIZARD: - case MAGICIAN: - case ENCHANTER: - return true; - } - break; - case IKSAR: - switch (c) { - case WARRIOR: - case SHADOWKNIGHT: - case MONK: - case SHAMAN: - case NECROMANCER: - case BEASTLORD: - return true; - } - break; - case VAHSHIR: - switch (c) { - case WARRIOR: - case BARD: - case ROGUE: - case SHAMAN: - case BEASTLORD: - case BERSERKER: - return true; - } - break; - case FROGLOK: - switch (c) { - case WARRIOR: - case CLERIC: - case PALADIN: - case SHADOWKNIGHT: - case ROGUE: - case SHAMAN: - case NECROMANCER: - case WIZARD: - return true; - } - break; - case DRAKKIN: - switch (c) { - case WARRIOR: - case CLERIC: - case PALADIN: - case RANGER: - case SHADOWKNIGHT: - case DRUID: - case MONK: - case BARD: - case ROGUE: - case NECROMANCER: - case WIZARD: - case MAGICIAN: - case ENCHANTER: - return true; - } - break; - default: - break; + bool is_valid = false; + auto classes = database.botdb.GetRaceClassBitmask(bot_race); + auto bot_class_bitmask = GetPlayerClassBit(bot_class); + if (classes & bot_class_bitmask) { + is_valid = true; } - - return false; + return is_valid; } bool Bot::IsValidName() @@ -3434,7 +3259,7 @@ void Bot::AI_Process() TriggerDefensiveProcs(tar, EQ::invslot::slotPrimary, false); TEST_COMBATANTS(); - TryWeaponProc(p_item, tar, EQ::invslot::slotPrimary); + TryCombatProcs(p_item, tar, EQ::invslot::slotPrimary); // bool tripleSuccess = false; @@ -3474,7 +3299,9 @@ void Bot::AI_Process() } TEST_COMBATANTS(); - int32 ExtraAttackChanceBonus = (spellbonuses.ExtraAttackChance + itembonuses.ExtraAttackChance + aabonuses.ExtraAttackChance); + auto ExtraAttackChanceBonus = + (spellbonuses.ExtraAttackChance[0] + itembonuses.ExtraAttackChance[0] + + aabonuses.ExtraAttackChance[0]); if (ExtraAttackChanceBonus) { if (p_item && p_item->GetItem()->IsType2HWeapon()) { @@ -3521,7 +3348,7 @@ void Bot::AI_Process() Attack(tar, EQ::invslot::slotSecondary); // Single attack with offhand TEST_COMBATANTS(); - TryWeaponProc(s_item, tar, EQ::invslot::slotSecondary); + TryCombatProcs(s_item, tar, EQ::invslot::slotSecondary); TEST_COMBATANTS(); if (CanThisClassDoubleAttack() && CheckBotDoubleAttack()) { @@ -4281,124 +4108,6 @@ void Bot::LevelBotWithClient(Client* client, uint8 level, bool sendlvlapp) { } } -std::string Bot::ClassIdToString(uint16 classId) { - std::string Result; - - if(classId > 0 && classId < 17) { - switch(classId) { - case 1: - Result = std::string("Warrior"); - break; - case 2: - Result = std::string("Cleric"); - break; - case 3: - Result = std::string("Paladin"); - break; - case 4: - Result = std::string("Ranger"); - break; - case 5: - Result = std::string("Shadowknight"); - break; - case 6: - Result = std::string("Druid"); - break; - case 7: - Result = std::string("Monk"); - break; - case 8: - Result = std::string("Bard"); - break; - case 9: - Result = std::string("Rogue"); - break; - case 10: - Result = std::string("Shaman"); - break; - case 11: - Result = std::string("Necromancer"); - break; - case 12: - Result = std::string("Wizard"); - break; - case 13: - Result = std::string("Magician"); - break; - case 14: - Result = std::string("Enchanter"); - break; - case 15: - Result = std::string("Beastlord"); - break; - case 16: - Result = std::string("Berserker"); - break; - } - } - - return Result; -} - -std::string Bot::RaceIdToString(uint16 raceId) { - std::string Result; - - if(raceId > 0) { - switch(raceId) { - case 1: - Result = std::string("Human"); - break; - case 2: - Result = std::string("Barbarian"); - break; - case 3: - Result = std::string("Erudite"); - break; - case 4: - Result = std::string("Wood Elf"); - break; - case 5: - Result = std::string("High Elf"); - break; - case 6: - Result = std::string("Dark Elf"); - break; - case 7: - Result = std::string("Half Elf"); - break; - case 8: - Result = std::string("Dwarf"); - break; - case 9: - Result = std::string("Troll"); - break; - case 10: - Result = std::string("Ogre"); - break; - case 11: - Result = std::string("Halfling"); - break; - case 12: - Result = std::string("Gnome"); - break; - case 128: - Result = std::string("Iksar"); - break; - case 130: - Result = std::string("Vah Shir"); - break; - case 330: - Result = std::string("Froglok"); - break; - case 522: - Result = std::string("Drakkin"); - break; - } - } - - return Result; -} - void Bot::SendBotArcheryWearChange(uint8 material_slot, uint32 material, uint32 color) { EQApplicationPacket* outapp = new EQApplicationPacket(OP_WearChange, sizeof(WearChange_Struct)); WearChange_Struct* wc = (WearChange_Struct*)outapp->pBuffer; @@ -5231,7 +4940,7 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b my_hit.tohit = GetTotalToHit(my_hit.skill, hit_chance_bonus); - DoAttack(other, my_hit, opts); + DoAttack(other, my_hit, opts, FromRiposte); LogCombat("Final damage after all reductions: [{}]", my_hit.damage_done); } else { @@ -5278,8 +4987,8 @@ int32 Bot::CalcBotAAFocus(focusType type, uint32 aa_ID, uint32 points, uint16 sp bool LimitSpellSkill = false; bool SpellSkill_Found = false; uint32 effect = 0; - int32 base1 = 0; - int32 base2 = 0; + int32 base_value = 0; + int32 limit_value = 0; uint32 slot = 0; bool LimitFound = false; int FocusCount = 0; @@ -5294,8 +5003,8 @@ int32 Bot::CalcBotAAFocus(focusType type, uint32 aa_ID, uint32 points, uint16 sp for(auto &eff : rank->effects) { effect = eff.effect_id; - base1 = eff.base1; - base2 = eff.base2; + base_value = eff.base_value; + limit_value = eff.limit_value; slot = eff.slot; //AA Foci's can contain multiple focus effects within the same AA. @@ -5320,22 +5029,22 @@ int32 Bot::CalcBotAAFocus(focusType type, uint32 aa_ID, uint32 points, uint16 sp case SE_Blank: break; case SE_LimitResist: - if(base1) { - if(spell.resisttype != base1) + if(base_value) { + if(spell.resist_type != base_value) LimitFound = true; } break; case SE_LimitInstant: - if(spell.buffduration) + if(spell.buff_duration) LimitFound = true; break; case SE_LimitMaxLevel: spell_level = spell.classes[(GetClass() % 17) - 1]; - lvldiff = spell_level - base1; + lvldiff = spell_level - base_value; //every level over cap reduces the effect by base2 percent unless from a clicky when ItemCastsUseFocus is true if(lvldiff > 0 && (spell_level <= RuleI(Character, MaxLevel) || RuleB(Character, ItemCastsUseFocus) == false)) { - if(base2 > 0) { - lvlModifier -= (base2 * lvldiff); + if(limit_value > 0) { + lvlModifier -= (limit_value * lvldiff); if(lvlModifier < 1) LimitFound = true; } @@ -5344,37 +5053,37 @@ int32 Bot::CalcBotAAFocus(focusType type, uint32 aa_ID, uint32 points, uint16 sp } break; case SE_LimitMinLevel: - if((spell.classes[(GetClass() % 17) - 1]) < base1) + if((spell.classes[(GetClass() % 17) - 1]) < base_value) LimitFound = true; break; case SE_LimitCastTimeMin: - if (spell.cast_time < base1) + if (spell.cast_time < base_value) LimitFound = true; break; case SE_LimitSpell: - if(base1 < 0) { - if (spell_id == (base1*-1)) + if(base_value < 0) { + if (spell_id == (base_value*-1)) LimitFound = true; } else { - if (spell_id != base1) + if (spell_id != base_value) LimitFound = true; } break; case SE_LimitMinDur: - if (base1 > CalcBuffDuration_formula(GetLevel(), spell.buffdurationformula, spell.buffduration)) + if (base_value > CalcBuffDuration_formula(GetLevel(), spell.buff_duration_formula, spell.buff_duration)) LimitFound = true; break; case SE_LimitEffect: - if(base1 < 0) { - if(IsEffectInSpell(spell_id,(base1*-1))) + if(base_value < 0) { + if(IsEffectInSpell(spell_id,(base_value*-1))) LimitFound = true; } else { - if(!IsEffectInSpell(spell_id,base1)) + if(!IsEffectInSpell(spell_id,base_value)) LimitFound = true; } break; case SE_LimitSpellType: - switch(base1) { + switch(base_value) { case 0: if (!IsDetrimentalSpell(spell_id)) LimitFound = true; @@ -5387,110 +5096,110 @@ int32 Bot::CalcBotAAFocus(focusType type, uint32 aa_ID, uint32 points, uint16 sp break; case SE_LimitManaMin: - if(spell.mana < base1) + if(spell.mana < base_value) LimitFound = true; break; case SE_LimitTarget: - if(base1 < 0) { - if(-base1 == spell.targettype) + if(base_value < 0) { + if(-base_value == spell.target_type) LimitFound = true; } else { - if(base1 != spell.targettype) + if(base_value != spell.target_type) LimitFound = true; } break; case SE_LimitCombatSkills: - if((base1 == 1 && !IsDiscipline(spell_id)) || (base1 == 0 && IsDiscipline(spell_id))) + if((base_value == 1 && !IsDiscipline(spell_id)) || (base_value == 0 && IsDiscipline(spell_id))) LimitFound = true; break; case SE_LimitSpellGroup: - if((base1 > 0 && base1 != spell.spellgroup) || (base1 < 0 && base1 == spell.spellgroup)) + if((base_value > 0 && base_value != spell.spell_group) || (base_value < 0 && base_value == spell.spell_group)) LimitFound = true; break; case SE_LimitCastingSkill: LimitSpellSkill = true; - if(base1 == spell.skill) + if(base_value == spell.skill) SpellSkill_Found = true; break; case SE_LimitClass: //Do not use this limit more then once per spell. If multiple class, treat value like items would. - if (!PassLimitClass(base1, GetClass())) + if (!PassLimitClass(base_value, GetClass())) LimitFound = true; break; //Handle Focus Effects case SE_ImprovedDamage: - if (type == focusImprovedDamage && base1 > value) - value = base1; + if (type == focusImprovedDamage && base_value > value) + value = base_value; break; case SE_ImprovedDamage2: - if (type == focusImprovedDamage2 && base1 > value) - value = base1; + if (type == focusImprovedDamage2 && base_value > value) + value = base_value; break; case SE_ImprovedHeal: - if (type == focusImprovedHeal && base1 > value) - value = base1; + if (type == focusImprovedHeal && base_value > value) + value = base_value; break; case SE_ReduceManaCost: if (type == focusManaCost) - value = base1; + value = base_value; break; case SE_IncreaseSpellHaste: - if (type == focusSpellHaste && base1 > value) - value = base1; + if (type == focusSpellHaste && base_value > value) + value = base_value; break; case SE_IncreaseSpellDuration: - if (type == focusSpellDuration && base1 > value) - value = base1; + if (type == focusSpellDuration && base_value > value) + value = base_value; break; case SE_SpellDurationIncByTic: - if (type == focusSpellDurByTic && base1 > value) - value = base1; + if (type == focusSpellDurByTic && base_value > value) + value = base_value; break; case SE_SwarmPetDuration: - if (type == focusSwarmPetDuration && base1 > value) - value = base1; + if (type == focusSwarmPetDuration && base_value > value) + value = base_value; break; case SE_IncreaseRange: - if (type == focusRange && base1 > value) - value = base1; + if (type == focusRange && base_value > value) + value = base_value; break; case SE_ReduceReagentCost: - if (type == focusReagentCost && base1 > value) - value = base1; + if (type == focusReagentCost && base_value > value) + value = base_value; break; case SE_PetPowerIncrease: - if (type == focusPetPower && base1 > value) - value = base1; + if (type == focusPetPower && base_value > value) + value = base_value; break; case SE_SpellResistReduction: - if (type == focusResistRate && base1 > value) - value = base1; + if (type == focusResistRate && base_value > value) + value = base_value; break; case SE_SpellHateMod: if (type == focusSpellHateMod) { if(value != 0) { if(value > 0) { - if(base1 > value) - value = base1; + if(base_value > value) + value = base_value; } else { - if(base1 < value) - value = base1; + if(base_value < value) + value = base_value; } } else - value = base1; + value = base_value; } break; case SE_ReduceReuseTimer: { if(type == focusReduceRecastTime) - value = (base1 / 1000); + value = (base_value / 1000); break; } case SE_TriggerOnCast: { if(type == focusTriggerOnCast) { - if(zone->random.Int(0, 100) <= base1) - value = base2; + if(zone->random.Int(0, 100) <= base_value) + value = limit_value; else { value = 0; LimitFound = true; @@ -5500,19 +5209,19 @@ int32 Bot::CalcBotAAFocus(focusType type, uint32 aa_ID, uint32 points, uint16 sp } case SE_FcSpellVulnerability: { if(type == focusSpellVulnerability) - value = base1; + value = base_value; break; } case SE_BlockNextSpellFocus: { if(type == focusBlockNextSpell) { - if(zone->random.Int(1, 100) <= base1) + if(zone->random.Int(1, 100) <= base_value) value = 1; } break; } case SE_FcTwincast: { if(type == focusTwincast) - value = base1; + value = base_value; break; } //case SE_SympatheticProc: @@ -5534,57 +5243,57 @@ int32 Bot::CalcBotAAFocus(focusType type, uint32 aa_ID, uint32 points, uint16 sp //} case SE_FcDamageAmt: { if(type == focusFcDamageAmt) - value = base1; + value = base_value; break; } case SE_FcDamageAmt2: { if(type == focusFcDamageAmt2) - value = base1; + value = base_value; break; } case SE_FcDamageAmtCrit: { if(type == focusFcDamageAmtCrit) - value = base1; + value = base_value; break; } case SE_FcDamageAmtIncoming: { if(type == focusFcDamageAmtIncoming) - value = base1; + value = base_value; break; } case SE_FcHealAmtIncoming: if(type == focusFcHealAmtIncoming) - value = base1; + value = base_value; break; case SE_FcHealPctCritIncoming: if (type == focusFcHealPctCritIncoming) - value = base1; + value = base_value; break; case SE_FcHealAmtCrit: if(type == focusFcHealAmtCrit) - value = base1; + value = base_value; break; case SE_FcHealAmt: if(type == focusFcHealAmt) - value = base1; + value = base_value; break; case SE_FcHealPctIncoming: if(type == focusFcHealPctIncoming) - value = base1; + value = base_value; break; case SE_FcBaseEffects: { if (type == focusFcBaseEffects) - value = base1; + value = base_value; break; } case SE_FcDamagePctCrit: { if(type == focusFcDamagePctCrit) - value = base1; + value = base_value; break; } case SE_FcIncreaseNumHits: { if(type == focusIncreaseNumHits) - value = base1; + value = base_value; break; } @@ -5716,7 +5425,7 @@ int32 Bot::GetBotFocusEffect(focusType bottype, uint16 spell_id) { realTotal2 = CalcBotFocusEffect(bottype, focusspell_tracker, spell_id); // For effects like gift of mana that only fire once, save the spellid into an array that consists of all available buff slots. - if(buff_tracker >= 0 && buffs[buff_tracker].numhits > 0) + if(buff_tracker >= 0 && buffs[buff_tracker].hit_number > 0) m_spellHitsLeft[buff_tracker] = focusspell_tracker; } @@ -5773,18 +5482,18 @@ int32 Bot::CalcBotFocusEffect(focusType bottype, uint16 focus_id, uint16 spell_i bool LimitSpellSkill = false; bool SpellSkill_Found = false; for (int i = 0; i < EFFECT_COUNT; i++) { - switch (focus_spell.effectid[i]) { + switch (focus_spell.effect_id[i]) { case SE_Blank: break; case SE_LimitResist:{ - if(focus_spell.base[i]) { - if(spell.resisttype != focus_spell.base[i]) + if(focus_spell.base_value[i]) { + if(spell.resist_type != focus_spell.base_value[i]) return 0; } break; } case SE_LimitInstant: { - if(spell.buffduration) + if(spell.buff_duration) return 0; break; } @@ -5792,10 +5501,10 @@ int32 Bot::CalcBotFocusEffect(focusType bottype, uint16 focus_id, uint16 spell_i if (IsNPC()) break; spell_level = spell.classes[(GetClass() % 17) - 1]; - lvldiff = (spell_level - focus_spell.base[i]); + lvldiff = (spell_level - focus_spell.base_value[i]); if(lvldiff > 0 && (spell_level <= RuleI(Character, MaxLevel) || RuleB(Character, ItemCastsUseFocus) == false)) { - if(focus_spell.base2[i] > 0) { - lvlModifier -= (focus_spell.base2[i] * lvldiff); + if(focus_spell.limit_value[i] > 0) { + lvlModifier -= (focus_spell.limit_value[i] * lvldiff); if(lvlModifier < 1) return 0; } @@ -5807,44 +5516,44 @@ int32 Bot::CalcBotFocusEffect(focusType bottype, uint16 focus_id, uint16 spell_i case SE_LimitMinLevel: if (IsNPC()) break; - if (spell.classes[(GetClass() % 17) - 1] < focus_spell.base[i]) + if (spell.classes[(GetClass() % 17) - 1] < focus_spell.base_value[i]) return 0; break; case SE_LimitCastTimeMin: - if (spells[spell_id].cast_time < (uint32)focus_spell.base[i]) + if (spells[spell_id].cast_time < (uint32)focus_spell.base_value[i]) return 0; break; case SE_LimitSpell: - if(focus_spell.base[i] < 0) { - if (spell_id == (focus_spell.base[i] * -1)) + if(focus_spell.base_value[i] < 0) { + if (spell_id == (focus_spell.base_value[i] * -1)) return 0; } else { - if (spell_id != focus_spell.base[i]) + if (spell_id != focus_spell.base_value[i]) return 0; } break; case SE_LimitMinDur: - if (focus_spell.base[i] > CalcBuffDuration_formula(GetLevel(), spell.buffdurationformula, spell.buffduration)) + if (focus_spell.base_value[i] > CalcBuffDuration_formula(GetLevel(), spell.buff_duration_formula, spell.buff_duration)) return 0; break; case SE_LimitEffect: - if(focus_spell.base[i] < 0) { - if(IsEffectInSpell(spell_id,focus_spell.base[i])) + if(focus_spell.base_value[i] < 0) { + if(IsEffectInSpell(spell_id,focus_spell.base_value[i])) return 0; } else { - if(focus_spell.base[i] == SE_SummonPet) { + if(focus_spell.base_value[i] == SE_SummonPet) { if(!IsEffectInSpell(spell_id, SE_SummonPet) && !IsEffectInSpell(spell_id, SE_NecPet) && !IsEffectInSpell(spell_id, SE_SummonBSTPet)) { return 0; } - } else if(!IsEffectInSpell(spell_id,focus_spell.base[i])) + } else if(!IsEffectInSpell(spell_id,focus_spell.base_value[i])) return 0; } break; case SE_LimitSpellType: - switch(focus_spell.base[i]) { + switch(focus_spell.base_value[i]) { case 0: if (!IsDetrimentalSpell(spell_id)) return 0; @@ -5854,153 +5563,153 @@ int32 Bot::CalcBotFocusEffect(focusType bottype, uint16 focus_id, uint16 spell_i return 0; break; default: - LogInfo("CalcFocusEffect: unknown limit spelltype [{}]", focus_spell.base[i]); + LogInfo("CalcFocusEffect: unknown limit spelltype [{}]", focus_spell.base_value[i]); } break; case SE_LimitManaMin: - if(spell.mana < focus_spell.base[i]) + if(spell.mana < focus_spell.base_value[i]) return 0; break; case SE_LimitTarget: - if((focus_spell.base[i] < 0) && -focus_spell.base[i] == spell.targettype) + if((focus_spell.base_value[i] < 0) && -focus_spell.base_value[i] == spell.target_type) return 0; - else if (focus_spell.base[i] > 0 && focus_spell.base[i] != spell.targettype) + else if (focus_spell.base_value[i] > 0 && focus_spell.base_value[i] != spell.target_type) return 0; break; case SE_LimitCombatSkills: - if(focus_spell.base[i] == 1 && !IsDiscipline(spell_id)) + if(focus_spell.base_value[i] == 1 && !IsDiscipline(spell_id)) return 0; - else if(focus_spell.base[i] == 0 && IsDiscipline(spell_id)) + else if(focus_spell.base_value[i] == 0 && IsDiscipline(spell_id)) return 0; break; case SE_LimitSpellGroup: - if(focus_spell.base[i] > 0 && focus_spell.base[i] != spell.spellgroup) + if(focus_spell.base_value[i] > 0 && focus_spell.base_value[i] != spell.spell_group) return 0; - else if(focus_spell.base[i] < 0 && focus_spell.base[i] == spell.spellgroup) + else if(focus_spell.base_value[i] < 0 && focus_spell.base_value[i] == spell.spell_group) return 0; break; case SE_LimitCastingSkill: LimitSpellSkill = true; - if(focus_spell.base[i] == spell.skill) + if(focus_spell.base_value[i] == spell.skill) SpellSkill_Found = true; break; case SE_LimitClass: - if (!PassLimitClass(focus_spell.base[i], GetClass())) + if (!PassLimitClass(focus_spell.base_value[i], GetClass())) return 0; break; case SE_ImprovedDamage: if (bottype == focusImprovedDamage) { if(best_focus) { - if (focus_spell.base2[i] != 0) - value = focus_spell.base2[i]; + if (focus_spell.limit_value[i] != 0) + value = focus_spell.limit_value[i]; else - value = focus_spell.base[i]; + value = focus_spell.base_value[i]; } - else if (focus_spell.base2[i] == 0 || focus_spell.base[i] == focus_spell.base2[i]) - value = focus_spell.base[i]; + else if (focus_spell.limit_value[i] == 0 || focus_spell.base_value[i] == focus_spell.limit_value[i]) + value = focus_spell.base_value[i]; else - value = zone->random.Int(focus_spell.base[i], focus_spell.base2[i]); + value = zone->random.Int(focus_spell.base_value[i], focus_spell.limit_value[i]); } break; case SE_ImprovedDamage2: if (bottype == focusImprovedDamage2) { if(best_focus) { - if (focus_spell.base2[i] != 0) - value = focus_spell.base2[i]; + if (focus_spell.limit_value[i] != 0) + value = focus_spell.limit_value[i]; else - value = focus_spell.base[i]; + value = focus_spell.base_value[i]; } - else if (focus_spell.base2[i] == 0 || focus_spell.base[i] == focus_spell.base2[i]) - value = focus_spell.base[i]; + else if (focus_spell.limit_value[i] == 0 || focus_spell.base_value[i] == focus_spell.limit_value[i]) + value = focus_spell.base_value[i]; else - value = zone->random.Int(focus_spell.base[i], focus_spell.base2[i]); + value = zone->random.Int(focus_spell.base_value[i], focus_spell.limit_value[i]); } break; case SE_ImprovedHeal: if (bottype == focusImprovedHeal) { if(best_focus) { - if (focus_spell.base2[i] != 0) - value = focus_spell.base2[i]; + if (focus_spell.limit_value[i] != 0) + value = focus_spell.limit_value[i]; else - value = focus_spell.base[i]; + value = focus_spell.base_value[i]; } - else if (focus_spell.base2[i] == 0 || focus_spell.base[i] == focus_spell.base2[i]) - value = focus_spell.base[i]; + else if (focus_spell.limit_value[i] == 0 || focus_spell.base_value[i] == focus_spell.limit_value[i]) + value = focus_spell.base_value[i]; else - value = zone->random.Int(focus_spell.base[i], focus_spell.base2[i]); + value = zone->random.Int(focus_spell.base_value[i], focus_spell.limit_value[i]); } break; case SE_ReduceManaCost: if (bottype == focusManaCost) { if(best_focus) { - if (focus_spell.base2[i] != 0) - value = focus_spell.base2[i]; + if (focus_spell.limit_value[i] != 0) + value = focus_spell.limit_value[i]; else - value = focus_spell.base[i]; + value = focus_spell.base_value[i]; } - else if (focus_spell.base2[i] == 0 || focus_spell.base[i] == focus_spell.base2[i]) - value = focus_spell.base[i]; + else if (focus_spell.limit_value[i] == 0 || focus_spell.base_value[i] == focus_spell.limit_value[i]) + value = focus_spell.base_value[i]; else - value = zone->random.Int(focus_spell.base[i], focus_spell.base2[i]); + value = zone->random.Int(focus_spell.base_value[i], focus_spell.limit_value[i]); } break; case SE_IncreaseSpellHaste: - if (bottype == focusSpellHaste && focus_spell.base[i] > value) - value = focus_spell.base[i]; + if (bottype == focusSpellHaste && focus_spell.base_value[i] > value) + value = focus_spell.base_value[i]; break; case SE_IncreaseSpellDuration: - if (bottype == focusSpellDuration && focus_spell.base[i] > value) - value = focus_spell.base[i]; + if (bottype == focusSpellDuration && focus_spell.base_value[i] > value) + value = focus_spell.base_value[i]; break; case SE_SpellDurationIncByTic: - if (bottype == focusSpellDurByTic && focus_spell.base[i] > value) - value = focus_spell.base[i]; + if (bottype == focusSpellDurByTic && focus_spell.base_value[i] > value) + value = focus_spell.base_value[i]; break; case SE_SwarmPetDuration: - if (bottype == focusSwarmPetDuration && focus_spell.base[i] > value) - value = focus_spell.base[i]; + if (bottype == focusSwarmPetDuration && focus_spell.base_value[i] > value) + value = focus_spell.base_value[i]; break; case SE_IncreaseRange: - if (bottype == focusRange && focus_spell.base[i] > value) - value = focus_spell.base[i]; + if (bottype == focusRange && focus_spell.base_value[i] > value) + value = focus_spell.base_value[i]; break; case SE_ReduceReagentCost: - if (bottype == focusReagentCost && focus_spell.base[i] > value) - value = focus_spell.base[i]; + if (bottype == focusReagentCost && focus_spell.base_value[i] > value) + value = focus_spell.base_value[i]; break; case SE_PetPowerIncrease: - if (bottype == focusPetPower && focus_spell.base[i] > value) - value = focus_spell.base[i]; + if (bottype == focusPetPower && focus_spell.base_value[i] > value) + value = focus_spell.base_value[i]; break; case SE_SpellResistReduction: - if (bottype == focusResistRate && focus_spell.base[i] > value) - value = focus_spell.base[i]; + if (bottype == focusResistRate && focus_spell.base_value[i] > value) + value = focus_spell.base_value[i]; break; case SE_SpellHateMod: if (bottype == focusSpellHateMod) { if(value != 0) { if(value > 0) { - if(focus_spell.base[i] > value) - value = focus_spell.base[i]; + if(focus_spell.base_value[i] > value) + value = focus_spell.base_value[i]; } else { - if(focus_spell.base[i] < value) - value = focus_spell.base[i]; + if(focus_spell.base_value[i] < value) + value = focus_spell.base_value[i]; } } else - value = focus_spell.base[i]; + value = focus_spell.base_value[i]; } break; case SE_ReduceReuseTimer: { if(bottype == focusReduceRecastTime) - value = (focus_spell.base[i] / 1000); + value = (focus_spell.base_value[i] / 1000); break; } case SE_TriggerOnCast: { if(bottype == focusTriggerOnCast) { - if(zone->random.Int(0, 100) <= focus_spell.base[i]) - value = focus_spell.base2[i]; + if(zone->random.Int(0, 100) <= focus_spell.base_value[i]) + value = focus_spell.limit_value[i]; else value = 0; } @@ -6008,24 +5717,24 @@ int32 Bot::CalcBotFocusEffect(focusType bottype, uint16 focus_id, uint16 spell_i } case SE_FcSpellVulnerability: { if(bottype == focusSpellVulnerability) - value = focus_spell.base[i]; + value = focus_spell.base_value[i]; break; } case SE_BlockNextSpellFocus: { if(bottype == focusBlockNextSpell) { - if(zone->random.Int(1, 100) <= focus_spell.base[i]) + if(zone->random.Int(1, 100) <= focus_spell.base_value[i]) value = 1; } break; } case SE_FcTwincast: { if(bottype == focusTwincast) - value = focus_spell.base[i]; + value = focus_spell.base_value[i]; break; } case SE_SympatheticProc: { if(bottype == focusSympatheticProc) { - float ProcChance = GetSympatheticProcChances(spell_id, focus_spell.base[i]); + float ProcChance = GetSympatheticProcChances(spell_id, focus_spell.base_value[i]); if(zone->random.Real(0, 1) <= ProcChance) value = focus_id; else @@ -6035,59 +5744,59 @@ int32 Bot::CalcBotFocusEffect(focusType bottype, uint16 focus_id, uint16 spell_i } case SE_FcDamageAmt: { if(bottype == focusFcDamageAmt) - value = focus_spell.base[i]; + value = focus_spell.base_value[i]; break; } case SE_FcDamageAmt2: { if(bottype == focusFcDamageAmt2) - value = focus_spell.base[i]; + value = focus_spell.base_value[i]; break; } case SE_FcDamageAmtCrit: { if(bottype == focusFcDamageAmtCrit) - value = focus_spell.base[i]; + value = focus_spell.base_value[i]; break; } case SE_FcHealAmtIncoming: if(bottype == focusFcHealAmtIncoming) - value = focus_spell.base[i]; + value = focus_spell.base_value[i]; break; case SE_FcHealPctCritIncoming: if (bottype == focusFcHealPctCritIncoming) - value = focus_spell.base[i]; + value = focus_spell.base_value[i]; break; case SE_FcHealAmtCrit: if(bottype == focusFcHealAmtCrit) - value = focus_spell.base[i]; + value = focus_spell.base_value[i]; break; case SE_FcHealAmt: if(bottype == focusFcHealAmt) - value = focus_spell.base[i]; + value = focus_spell.base_value[i]; break; case SE_FcHealPctIncoming: if(bottype == focusFcHealPctIncoming) - value = focus_spell.base[i]; + value = focus_spell.base_value[i]; break; case SE_FcBaseEffects: { if (bottype == focusFcBaseEffects) - value = focus_spell.base[i]; + value = focus_spell.base_value[i]; break; } case SE_FcDamagePctCrit: { if(bottype == focusFcDamagePctCrit) - value = focus_spell.base[i]; + value = focus_spell.base_value[i]; break; } case SE_FcIncreaseNumHits: { if(bottype == focusIncreaseNumHits) - value = focus_spell.base[i]; + value = focus_spell.base_value[i]; break; } default: - LogSpells("CalcFocusEffect: unknown effectid [{}]", focus_spell.effectid[i]); + LogSpells("CalcFocusEffect: unknown effectid [{}]", focus_spell.effect_id[i]); break; } } @@ -7014,7 +6723,7 @@ void Bot::SetAttackTimer() { } int32 Bot::GetActSpellDamage(uint16 spell_id, int32 value, Mob* target) { - if (spells[spell_id].targettype == ST_Self) + if (spells[spell_id].target_type == ST_Self) return value; Critical = false; @@ -7099,7 +6808,7 @@ int32 Bot::GetActSpellHealing(uint16 spell_id, int32 value, Mob* target) { value_BaseEffect = (value + (value*GetBotFocusEffect(focusFcBaseEffects, spell_id) / 100)); value = value_BaseEffect; value += int(value_BaseEffect*GetBotFocusEffect(focusImprovedHeal, spell_id) / 100); - if(spells[spell_id].buffduration < 1) { + if(spells[spell_id].buff_duration < 1) { chance += (itembonuses.CriticalHealChance + spellbonuses.CriticalHealChance + aabonuses.CriticalHealChance); chance += target->GetFocusIncoming(focusFcHealPctCritIncoming, SE_FcHealPctCritIncoming, this, spell_id); if (spellbonuses.CriticalHealDecay) @@ -7118,7 +6827,7 @@ int32 Bot::GetActSpellHealing(uint16 spell_id, int32 value, Mob* target) { if(itembonuses.HealAmt && spells[spell_id].classes[(GetClass() % 17) - 1] >= GetLevel() - 5) value += (GetExtraSpellAmt(spell_id, itembonuses.HealAmt, value) * modifier); - value += (value * target->GetHealRate(spell_id, this) / 100); + value += (value * target->GetHealRate() / 100); if (Critical) entity_list.MessageClose(this, false, 100, Chat::SpellCrit, "%s performs an exceptional heal! (%d)", GetName(), value); @@ -7136,12 +6845,15 @@ int32 Bot::GetActSpellHealing(uint16 spell_id, int32 value, Mob* target) { } int32 Bot::GetActSpellCasttime(uint16 spell_id, int32 casttime) { - int32 cast_reducer = 0; - cast_reducer += GetBotFocusEffect(focusSpellHaste, spell_id); + int32 cast_reducer = GetBotFocusEffect(focusSpellHaste, spell_id); + auto min_cap = casttime / 2; uint8 botlevel = GetLevel(); uint8 botclass = GetClass(); - if (botlevel >= 51 && casttime >= 3000 && !BeneficialSpell(spell_id) && (botclass == SHADOWKNIGHT || botclass == RANGER || botclass == PALADIN || botclass == BEASTLORD )) - cast_reducer += ((GetLevel() - 50) * 3); + if (botlevel >= 51 && casttime >= 3000 && !spells[spell_id].good_effect && + (botclass == SHADOWKNIGHT || botclass == RANGER || botclass == PALADIN || botclass == BEASTLORD)) { + int level_mod = std::min(15, botlevel - 50); + cast_reducer += level_mod * 3; + } if((casttime >= 4000) && BeneficialSpell(spell_id) && IsBuffSpell(spell_id)) { switch (GetAA(aaSpellCastingDeftness)) { @@ -7211,11 +6923,8 @@ int32 Bot::GetActSpellCasttime(uint16 spell_id, int32 casttime) { } } - if (cast_reducer > RuleI(Spells, MaxCastTimeReduction)) - cast_reducer = RuleI(Spells, MaxCastTimeReduction); - - casttime = (casttime * (100 - cast_reducer) / 100); - return casttime; + casttime = casttime * (100 - cast_reducer) / 100; + return std::max(casttime, min_cap); } int32 Bot::GetActSpellCost(uint16 spell_id, int32 cost) { @@ -7336,7 +7045,7 @@ int32 Bot::GetActSpellDuration(uint16 spell_id, int32 duration) { float Bot::GetAOERange(uint16 spell_id) { float range; - range = spells[spell_id].aoerange; + range = spells[spell_id].aoe_range; if(range == 0) range = spells[spell_id].range; @@ -7430,7 +7139,8 @@ bool Bot::CastSpell(uint16 spell_id, uint16 target_id, EQ::spells::CastingSlot s bardsong_timer.Disable(); } - Result = DoCastSpell(spell_id, target_id, slot, cast_time, mana_cost, oSpellWillFinish, item_slot, aa_id); + Result = Mob::CastSpell(spell_id, target_id, slot, cast_time, mana_cost, oSpellWillFinish, item_slot, 0xFFFFFFFF, 0, resist_adjust, aa_id); + } return Result; } @@ -7441,7 +7151,7 @@ bool Bot::SpellOnTarget(uint16 spell_id, Mob* spelltar) { return false; if(spelltar) { - if(spelltar->IsBot() && (spells[spell_id].targettype == ST_GroupTeleport)) { + if(spelltar->IsBot() && (spells[spell_id].target_type == ST_GroupTeleport)) { switch(spell_id) { // Paladin case 3577: // Wave of Life @@ -7517,7 +7227,7 @@ bool Bot::SpellOnTarget(uint16 spell_id, Mob* spelltar) { if(spelltar->IsPet()) { for(int i= 0; i < EFFECT_COUNT; ++i) { - if(spells[spell_id].effectid[i] == SE_Illusion) + if(spells[spell_id].effect_id[i] == SE_Illusion) return false; } } @@ -7535,13 +7245,13 @@ bool Bot::IsImmuneToSpell(uint16 spell_id, Mob *caster) { Result = Mob::IsImmuneToSpell(spell_id, caster); if(!Result) { if(caster->IsBot()) { - if(spells[spell_id].targettype == ST_Undead) { + if(spells[spell_id].target_type == ST_Undead) { if((GetBodyType() != BT_SummonedUndead) && (GetBodyType() != BT_Undead) && (GetBodyType() != BT_Vampire)) { LogSpells("Bot's target is not an undead"); return true; } } - if(spells[spell_id].targettype == ST_Summoned) { + if(spells[spell_id].target_type == ST_Summoned) { if((GetBodyType() != BT_SummonedUndead) && (GetBodyType() != BT_Summoned) && (GetBodyType() != BT_Summoned2) && (GetBodyType() != BT_Summoned3)) { LogSpells("Bot's target is not a summoned creature"); return true; @@ -7558,7 +7268,7 @@ bool Bot::IsImmuneToSpell(uint16 spell_id, Mob *caster) { bool Bot::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_center, CastAction_type &CastAction, EQ::spells::CastingSlot slot) { bool Result = false; - SpellTargetType targetType = spells[spell_id].targettype; + SpellTargetType targetType = spells[spell_id].target_type; if(targetType == ST_GroupClientAndPet) { if((spell_id == 1768 && zone->GetZoneID() == 202) || (!IsDetrimentalSpell(spell_id))) { CastAction = SingleTarget; @@ -7675,7 +7385,7 @@ void Bot::GenerateSpecialAttacks() { bool Bot::DoFinishedSpellAETarget(uint16 spell_id, Mob* spellTarget, EQ::spells::CastingSlot slot, bool& stopLogic) { if(GetClass() == BARD) { - if(!ApplyNextBardPulse(bardsong, this, bardsong_slot)) + if(!ApplyBardPulse(bardsong, this, bardsong_slot)) InterruptSpell(SONG_ENDS_ABRUPTLY, 0x121, bardsong); stopLogic = true; @@ -7693,13 +7403,13 @@ bool Bot::DoFinishedSpellSingleTarget(uint16 spell_id, Mob* spellTarget, EQ::spe int spelltype = BotGetSpellType(i); bool spellequal = (j == thespell); bool spelltypeequal = ((spelltype == 2) || (spelltype == 16) || (spelltype == 32)); - bool spelltypetargetequal = ((spelltype == 8) && (spells[thespell].targettype == ST_Self)); + bool spelltypetargetequal = ((spelltype == 8) && (spells[thespell].target_type == ST_Self)); bool spelltypeclassequal = ((spelltype == 1024) && (GetClass() == SHAMAN)); bool slotequal = (slot == EQ::spells::CastingSlot::Item); if(spellequal || slotequal) { if((spelltypeequal || spelltypetargetequal) || spelltypeclassequal || slotequal) { - if(((spells[thespell].effectid[0] == 0) && (spells[thespell].base[0] < 0)) && - (spellTarget->GetHP() < ((spells[thespell].base[0] * (-1)) + 100))) { + if(((spells[thespell].effect_id[0] == 0) && (spells[thespell].base_value[0] < 0)) && + (spellTarget->GetHP() < ((spells[thespell].base_value[0] * (-1)) + 100))) { LogSpells("Bot::DoFinishedSpellSingleTarget - GroupBuffing failure"); return false; } @@ -8398,7 +8108,7 @@ void Bot::DoEnduranceUpkeep() { uint32 buff_count = GetMaxTotalSlots(); for (buffs_i = 0; buffs_i < buff_count; buffs_i++) { if (buffs[buffs_i].spellid != SPELL_UNKNOWN) { - int upkeep = spells[buffs[buffs_i].spellid].EndurUpkeep; + int upkeep = spells[buffs[buffs_i].spellid].endurance_upkeep; if(upkeep > 0) { if(cost_redux > 0) { if(upkeep <= cost_redux) @@ -10043,18 +9753,25 @@ bool Bot::UseDiscipline(uint32 spell_id, uint32 target) { return false; } - if(GetEndurance() > spell.EndurCost) - SetEndurance(GetEndurance() - spell.EndurCost); + if(GetEndurance() > spell.endurance_cost) + SetEndurance(GetEndurance() - spell.endurance_cost); else return false; if(spell.recast_time > 0) { - if(CheckDisciplineRecastTimers(this, spells[spell_id].EndurTimerIndex)) { - if(spells[spell_id].EndurTimerIndex > 0 && spells[spell_id].EndurTimerIndex < MAX_DISCIPLINE_TIMERS) - SetDisciplineRecastTimer(spells[spell_id].EndurTimerIndex, spell.recast_time); + if(CheckDisciplineRecastTimers(this, spells[spell_id].timer_id)) { + if(spells[spell_id].timer_id > 0 && spells[spell_id].timer_id < MAX_DISCIPLINE_TIMERS) + SetDisciplineRecastTimer(spells[spell_id].timer_id, spell.recast_time); } else { - uint32 remain = (GetDisciplineRemainingTime(this, spells[spell_id].EndurTimerIndex) / 1000); - GetOwner()->Message(Chat::White, "%s can use this discipline in %d minutes %d seconds.", GetCleanName(), (remain / 60), (remain % 60)); + uint32 remaining_time = (GetDisciplineRemainingTime(this, spells[spell_id].timer_id) / 1000); + GetOwner()->Message( + Chat::White, + fmt::format( + "{} can use this discipline in {}.", + GetCleanName(), + ConvertSecondsToTime(remaining_time) + ).c_str() + ); return false; } } diff --git a/zone/bot.h b/zone/bot.h index df8361bab..6cd8eb519 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -359,8 +359,6 @@ public: static uint32 SpawnedBotCount(uint32 botOwnerCharacterID); static void LevelBotWithClient(Client* client, uint8 level, bool sendlvlapp); //static bool SetBotOwnerCharacterID(uint32 botID, uint32 botOwnerCharacterID, std::string* errorMessage); - static std::string ClassIdToString(uint16 classId); - static std::string RaceIdToString(uint16 raceId); static bool IsBotAttackAllowed(Mob* attacker, Mob* target, bool& hasRuleDefined); static Bot* GetBotByBotClientOwnerAndBotName(Client* c, std::string botName); static void ProcessBotGroupInvite(Client* c, std::string botName); diff --git a/zone/bot_command.cpp b/zone/bot_command.cpp index 10b869d9c..fd476e83b 100644 --- a/zone/bot_command.cpp +++ b/zone/bot_command.cpp @@ -130,11 +130,11 @@ public: for (int spell_id = 2; spell_id < SPDAT_RECORDS; ++spell_id) { if (spells[spell_id].player_1[0] == '\0') continue; - if (spells[spell_id].targettype != ST_Target && spells[spell_id].CastRestriction != 0) // watch + if (spells[spell_id].target_type != ST_Target && spells[spell_id].cast_restriction != 0) // watch continue; auto target_type = BCEnum::TT_None; - switch (spells[spell_id].targettype) { + switch (spells[spell_id].target_type) { case ST_GroupTeleport: target_type = BCEnum::TT_GroupV1; break; @@ -147,7 +147,7 @@ public: //target_type = BCEnum::TT_AEBard; break; case ST_Target: - switch (spells[spell_id].CastRestriction) { + switch (spells[spell_id].cast_restriction) { case 0: target_type = BCEnum::TT_Single; break; @@ -213,15 +213,15 @@ public: STBaseEntry* entry_prototype = nullptr; while (true) { - switch (spells[spell_id].effectid[EFFECTIDTOINDEX(1)]) { + switch (spells[spell_id].effect_id[EFFECTIDTOINDEX(1)]) { case SE_BindAffinity: entry_prototype = new STBaseEntry(BCEnum::SpT_BindAffinity); break; case SE_Charm: - if (spells[spell_id].SpellAffectIndex != 12) + if (spells[spell_id].spell_affect_index != 12) break; entry_prototype = new STCharmEntry(); - if (spells[spell_id].ResistDiff <= -1000) + if (spells[spell_id].resist_difficulty <= -1000) entry_prototype->SafeCastToCharm()->dire = true; break; case SE_Teleport: @@ -248,11 +248,11 @@ public: } break; case SE_ModelSize: - if (spells[spell_id].base[EFFECTIDTOINDEX(1)] > 100) { + if (spells[spell_id].base_value[EFFECTIDTOINDEX(1)] > 100) { entry_prototype = new STSizeEntry; entry_prototype->SafeCastToSize()->size_type = BCEnum::SzT_Enlarge; } - else if (spells[spell_id].base[EFFECTIDTOINDEX(1)] > 0 && spells[spell_id].base[EFFECTIDTOINDEX(1)] < 100) { + else if (spells[spell_id].base_value[EFFECTIDTOINDEX(1)] > 0 && spells[spell_id].base_value[EFFECTIDTOINDEX(1)] < 100) { entry_prototype = new STSizeEntry; entry_prototype->SafeCastToSize()->size_type = BCEnum::SzT_Reduce; } @@ -261,42 +261,42 @@ public: entry_prototype = new STBaseEntry(BCEnum::SpT_Identify); break; case SE_Invisibility: - if (spells[spell_id].SpellAffectIndex != 9) + if (spells[spell_id].spell_affect_index != 9) break; entry_prototype = new STInvisibilityEntry; entry_prototype->SafeCastToInvisibility()->invis_type = BCEnum::IT_Living; break; case SE_SeeInvis: - if (spells[spell_id].SpellAffectIndex != 5) + if (spells[spell_id].spell_affect_index != 5) break; entry_prototype = new STInvisibilityEntry; entry_prototype->SafeCastToInvisibility()->invis_type = BCEnum::IT_See; break; case SE_InvisVsUndead: - if (spells[spell_id].SpellAffectIndex != 9) + if (spells[spell_id].spell_affect_index != 9) break; entry_prototype = new STInvisibilityEntry; entry_prototype->SafeCastToInvisibility()->invis_type = BCEnum::IT_Undead; break; case SE_InvisVsAnimals: - if (spells[spell_id].SpellAffectIndex != 9) + if (spells[spell_id].spell_affect_index != 9) break; entry_prototype = new STInvisibilityEntry; entry_prototype->SafeCastToInvisibility()->invis_type = BCEnum::IT_Animal; break; case SE_Mez: - if (spells[spell_id].SpellAffectIndex != 12) + if (spells[spell_id].spell_affect_index != 12) break; entry_prototype = new STBaseEntry(BCEnum::SpT_Mesmerize); break; case SE_Revive: - if (spells[spell_id].SpellAffectIndex != 1) + if (spells[spell_id].spell_affect_index != 1) break; entry_prototype = new STResurrectEntry(); entry_prototype->SafeCastToResurrect()->aoe = BCSpells::IsCasterCentered(target_type); break; case SE_Rune: - if (spells[spell_id].SpellAffectIndex != 2) + if (spells[spell_id].spell_affect_index != 2) break; entry_prototype = new STBaseEntry(BCEnum::SpT_Rune); break; @@ -312,7 +312,7 @@ public: if (entry_prototype) break; - switch (spells[spell_id].effectid[EFFECTIDTOINDEX(2)]) { + switch (spells[spell_id].effect_id[EFFECTIDTOINDEX(2)]) { case SE_Succor: entry_prototype = new STEscapeEntry; std::string is_lesser = spells[spell_id].name; @@ -323,7 +323,7 @@ public: if (entry_prototype) break; - switch (spells[spell_id].effectid[EFFECTIDTOINDEX(3)]) { + switch (spells[spell_id].effect_id[EFFECTIDTOINDEX(3)]) { case SE_Lull: entry_prototype = new STBaseEntry(BCEnum::SpT_Lull); break; @@ -336,8 +336,8 @@ public: if (entry_prototype) break; - while (spells[spell_id].typedescnum == 27) { - if (!spells[spell_id].goodEffect) + while (spells[spell_id].type_description_id == 27) { + if (!spells[spell_id].good_effect) break; if (spells[spell_id].skill != EQ::skills::SkillOffense && spells[spell_id].skill != EQ::skills::SkillDefense) break; @@ -353,38 +353,38 @@ public: if (entry_prototype) break; - switch (spells[spell_id].SpellAffectIndex) { + switch (spells[spell_id].spell_affect_index) { case 1: { bool valid_spell = false; entry_prototype = new STCureEntry; for (int i = EffectIDFirst; i <= EffectIDLast; ++i) { int effect_index = EFFECTIDTOINDEX(i); - if (spells[spell_id].effectid[effect_index] != SE_Blind && spells[spell_id].base[effect_index] >= 0) + if (spells[spell_id].effect_id[effect_index] != SE_Blind && spells[spell_id].base_value[effect_index] >= 0) continue; - else if (spells[spell_id].effectid[effect_index] == SE_Blind && !spells[spell_id].goodEffect) + else if (spells[spell_id].effect_id[effect_index] == SE_Blind && !spells[spell_id].good_effect) continue; - switch (spells[spell_id].effectid[effect_index]) { + switch (spells[spell_id].effect_id[effect_index]) { case SE_Blind: - entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX(BCEnum::AT_Blindness)] += spells[spell_id].base[effect_index]; + entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX(BCEnum::AT_Blindness)] += spells[spell_id].base_value[effect_index]; break; case SE_DiseaseCounter: - entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX(BCEnum::AT_Disease)] += spells[spell_id].base[effect_index]; + entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX(BCEnum::AT_Disease)] += spells[spell_id].base_value[effect_index]; break; case SE_PoisonCounter: - entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX(BCEnum::AT_Poison)] += spells[spell_id].base[effect_index]; + entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX(BCEnum::AT_Poison)] += spells[spell_id].base_value[effect_index]; break; case SE_CurseCounter: - entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX(BCEnum::AT_Curse)] += spells[spell_id].base[effect_index]; + entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX(BCEnum::AT_Curse)] += spells[spell_id].base_value[effect_index]; break; case SE_CorruptionCounter: - entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX(BCEnum::AT_Corruption)] += spells[spell_id].base[effect_index]; + entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX(BCEnum::AT_Corruption)] += spells[spell_id].base_value[effect_index]; break; default: continue; } - entry_prototype->SafeCastToCure()->cure_total += spells[spell_id].base[effect_index]; + entry_prototype->SafeCastToCure()->cure_total += spells[spell_id].base_value[effect_index]; valid_spell = true; } if (!valid_spell) { @@ -400,32 +400,32 @@ public: for (int i = EffectIDFirst; i <= EffectIDLast; ++i) { int effect_index = EFFECTIDTOINDEX(i); - if (spells[spell_id].base[effect_index] <= 0) + if (spells[spell_id].base_value[effect_index] <= 0) continue; - switch (spells[spell_id].effectid[effect_index]) { + switch (spells[spell_id].effect_id[effect_index]) { case SE_ResistFire: - entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX(BCEnum::RT_Fire)] += spells[spell_id].base[effect_index]; + entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX(BCEnum::RT_Fire)] += spells[spell_id].base_value[effect_index]; break; case SE_ResistCold: - entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX(BCEnum::RT_Cold)] += spells[spell_id].base[effect_index]; + entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX(BCEnum::RT_Cold)] += spells[spell_id].base_value[effect_index]; break; case SE_ResistPoison: - entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX(BCEnum::RT_Poison)] += spells[spell_id].base[effect_index]; + entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX(BCEnum::RT_Poison)] += spells[spell_id].base_value[effect_index]; break; case SE_ResistDisease: - entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX(BCEnum::RT_Disease)] += spells[spell_id].base[effect_index]; + entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX(BCEnum::RT_Disease)] += spells[spell_id].base_value[effect_index]; break; case SE_ResistMagic: - entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX(BCEnum::RT_Magic)] += spells[spell_id].base[effect_index]; + entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX(BCEnum::RT_Magic)] += spells[spell_id].base_value[effect_index]; break; case SE_ResistCorruption: - entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX(BCEnum::RT_Corruption)] += spells[spell_id].base[effect_index]; + entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX(BCEnum::RT_Corruption)] += spells[spell_id].base_value[effect_index]; break; default: continue; } - entry_prototype->SafeCastToResistance()->resist_total += spells[spell_id].base[effect_index]; + entry_prototype->SafeCastToResistance()->resist_total += spells[spell_id].base_value[effect_index]; valid_spell = true; } if (!valid_spell) { @@ -437,7 +437,7 @@ public: } case 7: case 10: - if (spells[spell_id].effectdescnum != 65) + if (spells[spell_id].effect_description_id != 65) break; if (IsEffectInSpell(spell_id, SE_NegateIfCombat)) break; @@ -622,18 +622,18 @@ private: if (RuleI(Bots, CommandSpellRank) == 1) { spells_list->sort([](STBaseEntry* l, STBaseEntry* r) { - if (spells[l->spell_id].spellgroup < spells[r->spell_id].spellgroup) + if (spells[l->spell_id].spell_group < spells[r->spell_id].spell_group) return true; - if (spells[l->spell_id].spellgroup == spells[r->spell_id].spellgroup && l->caster_class < r->caster_class) + if (spells[l->spell_id].spell_group == spells[r->spell_id].spell_group && l->caster_class < r->caster_class) return true; - if (spells[l->spell_id].spellgroup == spells[r->spell_id].spellgroup && l->caster_class == r->caster_class && spells[l->spell_id].rank < spells[r->spell_id].rank) + if (spells[l->spell_id].spell_group == spells[r->spell_id].spell_group && l->caster_class == r->caster_class && spells[l->spell_id].rank < spells[r->spell_id].rank) return true; return false; }); spells_list->unique([removed_spells_list](STBaseEntry* l, STBaseEntry* r) { std::string r_name = spells[r->spell_id].name; - if (spells[l->spell_id].spellgroup == spells[r->spell_id].spellgroup && l->caster_class == r->caster_class && spells[l->spell_id].rank < spells[r->spell_id].rank) { + if (spells[l->spell_id].spell_group == spells[r->spell_id].spell_group && l->caster_class == r->caster_class && spells[l->spell_id].rank < spells[r->spell_id].rank) { removed_spells_list->push_back(r); return true; } @@ -673,18 +673,18 @@ private: // needs rework if (RuleI(Bots, CommandSpellRank) == 2 || RuleI(Bots, CommandSpellRank) == 3) { spells_list->sort([](STBaseEntry* l, STBaseEntry* r) { - if (spells[l->spell_id].spellgroup < spells[r->spell_id].spellgroup) + if (spells[l->spell_id].spell_group < spells[r->spell_id].spell_group) return true; - if (spells[l->spell_id].spellgroup == spells[r->spell_id].spellgroup && l->caster_class < r->caster_class) + if (spells[l->spell_id].spell_group == spells[r->spell_id].spell_group && l->caster_class < r->caster_class) return true; - if (spells[l->spell_id].spellgroup == spells[r->spell_id].spellgroup && l->caster_class == r->caster_class && spells[l->spell_id].rank > spells[r->spell_id].rank) + if (spells[l->spell_id].spell_group == spells[r->spell_id].spell_group && l->caster_class == r->caster_class && spells[l->spell_id].rank > spells[r->spell_id].rank) return true; return false; }); spells_list->unique([removed_spells_list](STBaseEntry* l, STBaseEntry* r) { std::string l_name = spells[l->spell_id].name; - if (spells[l->spell_id].spellgroup == spells[r->spell_id].spellgroup && l->caster_class == r->caster_class && spells[l->spell_id].rank > spells[r->spell_id].rank) { + if (spells[l->spell_id].spell_group == spells[r->spell_id].spell_group && l->caster_class == r->caster_class && spells[l->spell_id].rank > spells[r->spell_id].rank) { removed_spells_list->push_back(r); return true; } @@ -786,15 +786,15 @@ private: continue; case BCEnum::SpT_Charm: spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { - if (LT_SPELLS(l, r, ResistDiff)) + if (LT_SPELLS(l, r, resist_difficulty)) return true; - if (EQ_SPELLS(l, r, ResistDiff) && LT_STBASE(l, r, target_type)) + if (EQ_SPELLS(l, r, resist_difficulty) && LT_STBASE(l, r, target_type)) return true; - if (EQ_SPELLS(l, r, ResistDiff) && EQ_STBASE(l, r, target_type) && GT_SPELLS_EFFECT_ID(l, r, max, 1)) + if (EQ_SPELLS(l, r, resist_difficulty) && EQ_STBASE(l, r, target_type) && GT_SPELLS_EFFECT_ID(l, r, max_value, 1)) return true; - if (EQ_SPELLS(l, r, ResistDiff) && EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max, 1) && LT_STBASE(l, r, spell_level)) + if (EQ_SPELLS(l, r, resist_difficulty) && EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max_value, 1) && LT_STBASE(l, r, spell_level)) return true; - if (EQ_SPELLS(l, r, ResistDiff) && EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max, 1) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) + if (EQ_SPELLS(l, r, resist_difficulty) && EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max_value, 1) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) return true; return false; @@ -883,11 +883,11 @@ private: spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { if (LT_STBASE(l, r, target_type)) return true; - if (EQ_STBASE(l, r, target_type) && LT_SPELLS(l, r, zonetype)) + if (EQ_STBASE(l, r, target_type) && LT_SPELLS(l, r, zone_type)) return true; - if (EQ_STBASE(l, r, target_type) && EQ_SPELLS(l, r, zonetype) && GT_STBASE(l, r, spell_level)) + if (EQ_STBASE(l, r, target_type) && EQ_SPELLS(l, r, zone_type) && GT_STBASE(l, r, spell_level)) return true; - if (EQ_STBASE(l, r, target_type) && EQ_SPELLS(l, r, zonetype) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) + if (EQ_STBASE(l, r, target_type) && EQ_SPELLS(l, r, zone_type) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) return true; return false; @@ -895,15 +895,15 @@ private: continue; case BCEnum::SpT_Lull: spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { - if (LT_SPELLS(l, r, ResistDiff)) + if (LT_SPELLS(l, r, resist_difficulty)) return true; - if (EQ_SPELLS(l, r, ResistDiff) && LT_STBASE(l, r, target_type)) + if (EQ_SPELLS(l, r, resist_difficulty) && LT_STBASE(l, r, target_type)) return true; - if (EQ_SPELLS(l, r, ResistDiff) && EQ_STBASE(l, r, target_type) && GT_SPELLS_EFFECT_ID(l, r, max, 3)) + if (EQ_SPELLS(l, r, resist_difficulty) && EQ_STBASE(l, r, target_type) && GT_SPELLS_EFFECT_ID(l, r, max_value, 3)) return true; - if (EQ_SPELLS(l, r, ResistDiff) && EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max, 3) && LT_STBASE(l, r, spell_level)) + if (EQ_SPELLS(l, r, resist_difficulty) && EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max_value, 3) && LT_STBASE(l, r, spell_level)) return true; - if (EQ_SPELLS(l, r, ResistDiff) && EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max, 3) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) + if (EQ_SPELLS(l, r, resist_difficulty) && EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max_value, 3) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) return true; return false; @@ -911,15 +911,15 @@ private: continue; case BCEnum::SpT_Mesmerize: spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { - if (GT_SPELLS(l, r, ResistDiff)) + if (GT_SPELLS(l, r, resist_difficulty)) return true; - if (EQ_SPELLS(l, r, ResistDiff) && LT_STBASE(l, r, target_type)) + if (EQ_SPELLS(l, r, resist_difficulty) && LT_STBASE(l, r, target_type)) return true; - if (EQ_SPELLS(l, r, ResistDiff) && EQ_STBASE(l, r, target_type) && GT_SPELLS_EFFECT_ID(l, r, max, 1)) + if (EQ_SPELLS(l, r, resist_difficulty) && EQ_STBASE(l, r, target_type) && GT_SPELLS_EFFECT_ID(l, r, max_value, 1)) return true; - if (EQ_SPELLS(l, r, ResistDiff) && EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max, 1) && GT_STBASE(l, r, spell_level)) + if (EQ_SPELLS(l, r, resist_difficulty) && EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max_value, 1) && GT_STBASE(l, r, spell_level)) return true; - if (EQ_SPELLS(l, r, ResistDiff) && EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max, 1) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) + if (EQ_SPELLS(l, r, resist_difficulty) && EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max_value, 1) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) return true; return false; @@ -929,11 +929,11 @@ private: spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { if (LT_STBASE(l, r, target_type)) return true; - if (EQ_STBASE(l, r, target_type) && GT_SPELLS_EFFECT_ID(l, r, base, 2)) + if (EQ_STBASE(l, r, target_type) && GT_SPELLS_EFFECT_ID(l, r, base_value, 2)) return true; - if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, base, 2) && LT_STBASE(l, r, spell_level)) + if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, base_value, 2) && LT_STBASE(l, r, spell_level)) return true; - if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, base, 2) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) + if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, base_value, 2) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) return true; return false; @@ -951,13 +951,13 @@ private: continue; case BCEnum::SpT_Resurrect: spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { - if (GT_SPELLS_EFFECT_ID(l, r, base, 1)) + if (GT_SPELLS_EFFECT_ID(l, r, base_value, 1)) return true; - if (EQ_SPELLS_EFFECT_ID(l, r, base, 1) && LT_STBASE(l, r, target_type)) + if (EQ_SPELLS_EFFECT_ID(l, r, base_value, 1) && LT_STBASE(l, r, target_type)) return true; - if (EQ_SPELLS_EFFECT_ID(l, r, base, 1) && EQ_STBASE(l, r, target_type) && LT_STBASE(l, r, spell_level)) + if (EQ_SPELLS_EFFECT_ID(l, r, base_value, 1) && EQ_STBASE(l, r, target_type) && LT_STBASE(l, r, spell_level)) return true; - if (EQ_SPELLS_EFFECT_ID(l, r, base, 1) && EQ_STBASE(l, r, target_type) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) + if (EQ_SPELLS_EFFECT_ID(l, r, base_value, 1) && EQ_STBASE(l, r, target_type) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) return true; return false; @@ -967,11 +967,11 @@ private: spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { if (LT_STBASE(l, r, target_type)) return true; - if (EQ_STBASE(l, r, target_type) && GT_SPELLS_EFFECT_ID(l, r, max, 1)) + if (EQ_STBASE(l, r, target_type) && GT_SPELLS_EFFECT_ID(l, r, max_value, 1)) return true; - if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max, 1) && LT_STBASE(l, r, spell_level)) + if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max_value, 1) && LT_STBASE(l, r, spell_level)) return true; - if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max, 1) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) + if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max_value, 1) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) return true; return false; @@ -999,19 +999,19 @@ private: if (l_size_type < r_size_type) return true; if (l_size_type == BCEnum::SzT_Enlarge && r_size_type == BCEnum::SzT_Enlarge) { - if (EQ_STBASE(l, r, target_type) && GT_SPELLS_EFFECT_ID(l, r, base, 1)) + if (EQ_STBASE(l, r, target_type) && GT_SPELLS_EFFECT_ID(l, r, base_value, 1)) return true; - if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, base, 1) && GT_STBASE(l, r, spell_level)) + if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, base_value, 1) && GT_STBASE(l, r, spell_level)) return true; - if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, base, 1) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) + if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, base_value, 1) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) return true; } if (l_size_type == BCEnum::SzT_Reduce && r_size_type == BCEnum::SzT_Reduce) { - if (EQ_STBASE(l, r, target_type) && LT_SPELLS_EFFECT_ID(l, r, base, 1)) + if (EQ_STBASE(l, r, target_type) && LT_SPELLS_EFFECT_ID(l, r, base_value, 1)) return true; - if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, base, 1) && GT_STBASE(l, r, spell_level)) + if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, base_value, 1) && GT_STBASE(l, r, spell_level)) return true; - if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, base, 1) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) + if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, base_value, 1) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) return true; } @@ -1028,11 +1028,11 @@ private: continue; case BCEnum::SpT_SummonCorpse: spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { - if (GT_SPELLS_EFFECT_ID(l, r, base, 1)) + if (GT_SPELLS_EFFECT_ID(l, r, base_value, 1)) return true; - if (EQ_SPELLS_EFFECT_ID(l, r, base, 1) && LT_STBASE(l, r, spell_level)) + if (EQ_SPELLS_EFFECT_ID(l, r, base_value, 1) && LT_STBASE(l, r, spell_level)) return true; - if (EQ_SPELLS_EFFECT_ID(l, r, base, 1) && EQ_STBASE(l, r, spell_level) && EQ_STBASE(l, r, caster_class)) + if (EQ_SPELLS_EFFECT_ID(l, r, base_value, 1) && EQ_STBASE(l, r, spell_level) && EQ_STBASE(l, r, caster_class)) return true; return false; @@ -1119,7 +1119,7 @@ private: for (bcst_levels::iterator levels_iter = bot_levels.begin(); levels_iter != bot_levels.end(); ++levels_iter) { if (levels_iter->second < test_iter->second) test_iter = levels_iter; - if (strcasecmp(Bot::ClassIdToString(levels_iter->first).c_str(), Bot::ClassIdToString(test_iter->first).c_str()) < 0 && levels_iter->second <= test_iter->second) + if (strcasecmp(GetClassIDName(levels_iter->first), GetClassIDName(test_iter->first)) < 0 && levels_iter->second <= test_iter->second) test_iter = levels_iter; } @@ -1131,8 +1131,8 @@ private: else bot_segment = " or %s(%u)"; - required_bots_map[type_index].append(StringFormat(bot_segment.c_str(), Bot::ClassIdToString(test_iter->first).c_str(), test_iter->second)); - required_bots_map_by_class[type_index][test_iter->first] = StringFormat("%s(%u)", Bot::ClassIdToString(test_iter->first).c_str(), test_iter->second); + required_bots_map[type_index].append(StringFormat(bot_segment.c_str(), GetClassIDName(test_iter->first), test_iter->second)); + required_bots_map_by_class[type_index][test_iter->first] = StringFormat("%s(%u)", GetClassIDName(test_iter->first), test_iter->second); bot_levels.erase(test_iter); } } @@ -1167,18 +1167,18 @@ private: spell_dump << StringFormat(" /mn:%05u/RD:%06i/zt:%02i/d#:%06i/td#:%05i/ed#:%05i/SAI:%03u", spells[spell_id].mana, - spells[spell_id].ResistDiff, - spells[spell_id].zonetype, - spells[spell_id].descnum, - spells[spell_id].typedescnum, - spells[spell_id].effectdescnum, - spells[spell_id].SpellAffectIndex + spells[spell_id].resist_difficulty, + spells[spell_id].zone_type, + spells[spell_id].description_id, + spells[spell_id].type_description_id, + spells[spell_id].effect_description_id, + spells[spell_id].spell_affect_index ); for (int i = EffectIDFirst; i <= 3/*EffectIDLast*/; ++i) { int effect_index = EFFECTIDTOINDEX(i); spell_dump << StringFormat(" /e%02i:%04i/b%02i:%06i/m%02i:%06i", - i, spells[spell_id].effectid[effect_index], i, spells[spell_id].base[effect_index], i, spells[spell_id].max[effect_index]); + i, spells[spell_id].effect_id[effect_index], i, spells[spell_id].base_value[effect_index], i, spells[spell_id].max_value[effect_index]); } switch (list_entry->BCST()) { @@ -1428,6 +1428,7 @@ int bot_command_init(void) bot_command_add("suspend", "Suspends a bot's AI processing until released", 0, bot_command_suspend) || bot_command_add("taunt", "Toggles taunt use by a bot", 0, bot_command_taunt) || bot_command_add("track", "Orders a capable bot to track enemies", 0, bot_command_track) || + bot_command_add("viewcombos", "Views bot race class combinations", 0, bot_command_view_combos) || bot_command_add("waterbreathing", "Orders a bot to cast a water breathing spell", 0, bot_command_water_breathing) ) { bot_command_deinit(); @@ -1651,47 +1652,68 @@ int bot_command_real_dispatch(Client *c, const char *message) void bot_command_log_command(Client *c, const char *message) { - int admin = c->Admin(); +int admin = c->Admin(); bool continueevents = false; - switch (zone->loglevelvar) { //catch failsafe - case 9: // log only LeadGM - if ((admin >= 150) && (admin <200)) + switch (zone->loglevelvar){ //catch failsafe + case 9: { // log only LeadGM + if ( + admin >= AccountStatus::GMLeadAdmin && + admin < AccountStatus::GMMgmt + ) { + continueevents = true; + } + break; + } + case 8: { // log only GM + if ( + admin >= AccountStatus::GMAdmin && + admin < AccountStatus::GMLeadAdmin + ) { + continueevents = true; + } + break; + } + case 1: { + if (admin >= AccountStatus::GMMgmt) { + continueevents = true; + } + break; + } + case 2: { + if (admin >= AccountStatus::GMLeadAdmin) { + continueevents = true; + } + break; + } + case 3: { + if (admin >= AccountStatus::GMAdmin) { + continueevents = true; + } + break; + } + case 4: { + if (admin >= AccountStatus::QuestTroupe) { + continueevents = true; + } + break; + } + case 5: { + if (admin >= AccountStatus::ApprenticeGuide) { + continueevents = true; + } + break; + } + case 6: { + if (admin >= AccountStatus::Steward) { + continueevents = true; + } + break; + } + case 7: { continueevents = true; - break; - case 8: // log only GM - if ((admin >= 100) && (admin <150)) - continueevents = true; - break; - case 1: - if ((admin >= 200)) - continueevents = true; - break; - case 2: - if ((admin >= 150)) - continueevents = true; - break; - case 3: - if ((admin >= 100)) - continueevents = true; - break; - case 4: - if ((admin >= 80)) - continueevents = true; - break; - case 5: - if ((admin >= 20)) - continueevents = true; - break; - case 6: - if ((admin >= 10)) - continueevents = true; - break; - case 7: - continueevents = true; - break; - default: - break; + break; + } } if (continueevents) @@ -2934,7 +2956,7 @@ void bot_command_charm(Client *c, const Seperator *sep) return; } - if (spells[local_entry->spell_id].max[EFFECTIDTOINDEX(1)] < target_mob->GetLevel()) + if (spells[local_entry->spell_id].max_value[EFFECTIDTOINDEX(1)] < target_mob->GetLevel()) continue; my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob, true); @@ -3381,7 +3403,7 @@ void bot_command_heal_rotation(Client *c, const Seperator *sep) return; #if (EQDEBUG >= 12) - while (c->Admin() >= 250) { + while (c->Admin() >= AccountStatus::GMImpossible) { if (strcasecmp(sep->arg[1], "shone")) { break; } Bot* my_bot = ActionableBots::AsTarget_ByBot(c); if (!my_bot || !(my_bot->IsHealRotationMember())) { break; } @@ -3789,7 +3811,7 @@ void bot_command_mesmerize(Client *c, const Seperator *sep) if (!target_mob) continue; - if (spells[local_entry->spell_id].max[EFFECTIDTOINDEX(1)] < target_mob->GetLevel()) + if (spells[local_entry->spell_id].max_value[EFFECTIDTOINDEX(1)] < target_mob->GetLevel()) continue; my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); @@ -4704,7 +4726,7 @@ void bot_command_summon_corpse(Client *c, const Seperator *sep) if (!target_mob) continue; - if (spells[local_entry->spell_id].base[EFFECTIDTOINDEX(1)] < target_mob->GetLevel()) + if (spells[local_entry->spell_id].base_value[EFFECTIDTOINDEX(1)] < target_mob->GetLevel()) continue; my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); @@ -4788,12 +4810,25 @@ void bot_command_taunt(Client *c, const Seperator *sep) ++taunting_count; } + for (auto bot_iter : sbl) { + if (!bot_iter->HasPet()) + continue; + if (!bot_iter->GetPet()->GetSkill(EQ::skills::SkillTaunt)) + continue; + if (toggle_taunt) + bot_iter->GetPet()->CastToNPC()->SetTaunting(!bot_iter->GetPet()->CastToNPC()->IsTaunting()); + else + bot_iter->GetPet()->CastToNPC()->SetTaunting(taunt_state); + if (sbl.size() == 1) + Bot::BotGroupSay(bot_iter, "My Pet is %s taunting", bot_iter->GetPet()->CastToNPC()->IsTaunting() ? "now" : "no longer"); + ++taunting_count; + } if (taunting_count) { if (toggle_taunt) - c->Message(m_action, "%i of your bots %s toggled their taunting state", taunting_count, ((taunting_count != 1) ? ("have") : ("has"))); + c->Message(m_action, "%i of your bots and their pets %s toggled their taunting state", taunting_count, ((taunting_count != 1) ? ("have") : ("has"))); else - c->Message(m_action, "%i of your bots %s %s taunting", taunting_count, ((taunting_count != 1) ? ("have") : ("has")), ((taunt_state) ? ("started") : ("stopped"))); + c->Message(m_action, "%i of your bots and their pets %s %s taunting", taunting_count, ((taunting_count != 1) ? ("have") : ("has")), ((taunt_state) ? ("started") : ("stopped"))); } else { c->Message(m_fail, "None of your bots are capable of taunting"); @@ -5107,6 +5142,68 @@ void bot_subcommand_bot_clone(Client *c, const Seperator *sep) c->Message(m_action, "Bot '%s' was successfully cloned to bot '%s'", my_bot->GetCleanName(), bot_name.c_str()); } +void bot_command_view_combos(Client *c, const Seperator *sep) +{ + const std::string class_substrs[17] = { "", + "%u (WAR)", "%u (CLR)", "%u (PAL)", "%u (RNG)", + "%u (SHD)", "%u (DRU)", "%u (MNK)", "%u (BRD)", + "%u (ROG)", "%u (SHM)", "%u (NEC)", "%u (WIZ)", + "%u (MAG)", "%u (ENC)", "%u (BST)", "%u (BER)" + }; + + const std::string race_substrs[17] = { "", + "%u (HUM)", "%u (BAR)", "%u (ERU)", "%u (ELF)", + "%u (HIE)", "%u (DEF)", "%u (HEF)", "%u (DWF)", + "%u (TRL)", "%u (OGR)", "%u (HFL)", "%u (GNM)", + "%u (IKS)", "%u (VAH)", "%u (FRG)", "%u (DRK)" + }; + + const uint16 race_values[17] = { 0, + HUMAN, BARBARIAN, ERUDITE, WOOD_ELF, + HIGH_ELF, DARK_ELF, HALF_ELF, DWARF, + TROLL, OGRE, HALFLING, GNOME, + IKSAR, VAHSHIR, FROGLOK, DRAKKIN + }; + if (helper_command_alias_fail(c, "bot_command_view_combos", sep->arg[0], "viewcombos")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + std::string window_title = "Bot Races"; + std::string window_text; + std::string message_separator = " "; + c->Message(m_usage, "Usage: %s [bot_race]", sep->arg[0]); + window_text.append("Races:"); + for (int race_id = 0; race_id <= 15; ++race_id) { + window_text.append(message_separator); + window_text.append(StringFormat(race_substrs[race_id + 1].c_str(), race_values[race_id + 1])); + message_separator = ", "; + } + c->SendPopupToClient(window_title.c_str(), window_text.c_str()); + return; + } + + if (sep->arg[1][0] == '\0' || !sep->IsNumber(1)) { + c->Message(m_fail, "Invalid Race!"); + return; + } + uint16 bot_race = atoi(sep->arg[1]); + auto classes_bitmask = database.botdb.GetRaceClassBitmask(bot_race); + auto race_name = GetRaceIDName(bot_race); + std::string window_title = "Bot Classes"; + std::string window_text; + std::string message_separator = " "; + c->Message(m_usage, "%s can be these classes.", race_name); + window_text.append("Classes:"); + for (int class_id = 0; class_id <= 15; ++class_id) { + if (classes_bitmask & GetPlayerClassBit(class_id)) { + window_text.append(message_separator); + window_text.append(StringFormat(class_substrs[class_id].c_str(), class_id)); + message_separator = ", "; + } + } + c->SendPopupToClient(window_title.c_str(), window_text.c_str()); + return; +} + void bot_subcommand_bot_create(Client *c, const Seperator *sep) { const std::string class_substrs[17] = { "", @@ -5148,10 +5245,7 @@ void bot_subcommand_bot_create(Client *c, const Seperator *sep) message_separator = " "; object_count = 1; for (int i = 0; i <= 15; ++i) { - if (((1 << i) & RuleI(Bots, AllowedClasses)) == 0) - continue; - - window_text.append(const_cast(message_separator)); + window_text.append(message_separator); if (object_count >= object_max) { window_text.append("
"); object_count = 0; @@ -5166,10 +5260,7 @@ void bot_subcommand_bot_create(Client *c, const Seperator *sep) message_separator = " "; object_count = 1; for (int i = 0; i <= 15; ++i) { - if (((1 << i) & RuleI(Bots, AllowedRaces)) == 0) - continue; - - window_text.append(const_cast(message_separator)); + window_text.append(message_separator); if (object_count >= object_max) { window_text.append("
"); object_count = 0; @@ -5183,12 +5274,8 @@ void bot_subcommand_bot_create(Client *c, const Seperator *sep) window_text.append("Genders:"); message_separator = " "; for (int i = 0; i <= 1; ++i) { - if (((1 << i) & RuleI(Bots, AllowedGenders)) == 0) - continue; - - window_text.append(const_cast(message_separator)); + window_text.append(message_separator); window_text.append(StringFormat(gender_substrs[i].c_str(), i)); - message_separator = ", "; } @@ -5802,9 +5889,9 @@ void bot_subcommand_bot_list(Client *c, const Seperator *sep) c->Message(Chat::White, "[%s] is a level %u %s %s %s who is owned by %s", ((c->CharacterID() == bots_iter.Owner_ID) && (!botCheckNotOnline) ? (EQ::SayLinkEngine::GenerateQuestSaylink(botspawn_saylink, false, bots_iter.Name).c_str()) : (bots_iter.Name)), bots_iter.Level, - Bot::RaceIdToString(bots_iter.Race).c_str(), + GetRaceIDName(bots_iter.Race), ((bots_iter.Gender == FEMALE) ? ("Female") : ((bots_iter.Gender == MALE) ? ("Male") : ("Neuter"))), - Bot::ClassIdToString(bots_iter.Class).c_str(), + GetClassIDName(bots_iter.Class), bots_iter.Owner ); if (c->CharacterID() == bots_iter.Owner_ID) { ++bots_owned; } @@ -5977,7 +6064,7 @@ void bot_subcommand_bot_report(Client *c, const Seperator *sep) if (!bot_iter) continue; - std::string report_msg = StringFormat("%s %s reports", Bot::ClassIdToString(bot_iter->GetClass()).c_str(), bot_iter->GetCleanName()); + std::string report_msg = StringFormat("%s %s reports", GetClassIDName(bot_iter->GetClass()), bot_iter->GetCleanName()); report_msg.append(StringFormat(": %3.1f%% health", bot_iter->GetHPRatio())); if (!IsNonSpellFighterClass(bot_iter->GetClass())) report_msg.append(StringFormat(": %3.1f%% mana", bot_iter->GetManaRatio())); @@ -8672,33 +8759,25 @@ uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_clas return bot_id; } - auto class_bit = GetPlayerClassBit(bot_class); - if ((class_bit & RuleI(Bots, AllowedClasses)) == PLAYER_CLASS_UNKNOWN_BIT) { - bot_owner->Message(m_fail, "Class '%s' bots are not allowed on this server", GetPlayerClassName(bot_class)); - return bot_id; - } - - auto race_bit = GetPlayerRaceBit(bot_race); - if ((race_bit & RuleI(Bots, AllowedRaces)) == PLAYER_RACE_UNKNOWN_BIT) { - bot_owner->Message(m_fail, "Race '%s' bots are not allowed on this server", GetPlayerRaceName(bot_class)); - return bot_id; - } - if (!Bot::IsValidRaceClassCombo(bot_race, bot_class)) { - bot_owner->Message(m_fail, "'%s'(%u):'%s'(%u) is an invalid race-class combination", - Bot::RaceIdToString(bot_race).c_str(), bot_race, Bot::ClassIdToString(bot_class).c_str(), bot_class); + const char* bot_race_name = GetRaceIDName(bot_race); + const char* bot_class_name = GetClassIDName(bot_class); + std::string view_saylink = EQ::SayLinkEngine::GenerateQuestSaylink(fmt::format("^viewcombos {}", bot_race), false, "view"); + bot_owner->Message( + m_fail, + fmt::format( + "{} {} is an invalid race-class combination, would you like to {} proper combinations for {}?", + bot_race_name, + bot_class_name, + view_saylink, + bot_race_name + ).c_str() + ); return bot_id; } - if (bot_gender > FEMALE || (((1 << bot_gender) & RuleI(Bots, AllowedGenders)) == 0)) { - if (RuleI(Bots, AllowedGenders) == 3) - bot_owner->Message(m_fail, "gender: %u(M), %u(F)", MALE, FEMALE); - else if (RuleI(Bots, AllowedGenders) == 2) - bot_owner->Message(m_fail, "gender: %u(F)", FEMALE); - else if (RuleI(Bots, AllowedGenders) == 1) - bot_owner->Message(m_fail, "gender: %u(M)", MALE); - else - bot_owner->Message(m_fail, "gender: ERROR - No valid genders exist"); + if (bot_gender > FEMALE) { + bot_owner->Message(m_fail, "gender: %u (M), %u (F)", MALE, FEMALE); return bot_id; } @@ -8710,7 +8789,7 @@ uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_clas return bot_id; } if (bot_count >= max_bot_count) { - bot_owner->Message(m_fail, "You have reached the maximum limit of %i bots", max_bot_count); + bot_owner->Message(m_fail, "You have reached the maximum limit of %i bots.", max_bot_count); return bot_id; } @@ -8971,7 +9050,7 @@ bool helper_spell_check_fail(STBaseEntry* local_entry) { if (!local_entry) return true; - if (spells[local_entry->spell_id].zonetype && zone->GetZoneType() && !(spells[local_entry->spell_id].zonetype & zone->GetZoneType())) + if (spells[local_entry->spell_id].zone_type && zone->GetZoneType() && !(spells[local_entry->spell_id].zone_type & zone->GetZoneType())) return true; return false; diff --git a/zone/bot_command.h b/zone/bot_command.h index 84a56f239..34ced3872 100644 --- a/zone/bot_command.h +++ b/zone/bot_command.h @@ -593,6 +593,7 @@ void bot_command_summon_corpse(Client *c, const Seperator *sep); void bot_command_suspend(Client *c, const Seperator *sep); void bot_command_taunt(Client *c, const Seperator *sep); void bot_command_track(Client *c, const Seperator *sep); +void bot_command_view_combos(Client *c, const Seperator *sep); void bot_command_water_breathing(Client *c, const Seperator *sep); diff --git a/zone/bot_database.cpp b/zone/bot_database.cpp index b05fa7e98..5427b3397 100644 --- a/zone/bot_database.cpp +++ b/zone/bot_database.cpp @@ -766,7 +766,7 @@ bool BotDatabase::LoadBuffs(Bot* bot_inst) else if (CalculateCorruptionCounters(bot_buffs[buff_count].spellid) > 0) bot_buffs[buff_count].counters = atoi(row[7]); - bot_buffs[buff_count].numhits = atoi(row[8]); + bot_buffs[buff_count].hit_number = atoi(row[8]); bot_buffs[buff_count].melee_rune = atoi(row[9]); bot_buffs[buff_count].magic_rune = atoi(row[10]); bot_buffs[buff_count].dot_rune = atoi(row[11]); @@ -843,13 +843,13 @@ bool BotDatabase::SaveBuffs(Bot* bot_inst) bot_inst->GetBotID(), bot_buffs[buff_index].spellid, bot_buffs[buff_index].casterlevel, - spells[bot_buffs[buff_index].spellid].buffdurationformula, + spells[bot_buffs[buff_index].spellid].buff_duration_formula, bot_buffs[buff_index].ticsremaining, ((CalculatePoisonCounters(bot_buffs[buff_index].spellid) > 0) ? (bot_buffs[buff_index].counters) : (0)), ((CalculateDiseaseCounters(bot_buffs[buff_index].spellid) > 0) ? (bot_buffs[buff_index].counters) : (0)), ((CalculateCurseCounters(bot_buffs[buff_index].spellid) > 0) ? (bot_buffs[buff_index].counters) : (0)), ((CalculateCorruptionCounters(bot_buffs[buff_index].spellid) > 0) ? (bot_buffs[buff_index].counters) : (0)), - bot_buffs[buff_index].numhits, + bot_buffs[buff_index].hit_number, bot_buffs[buff_index].melee_rune, bot_buffs[buff_index].magic_rune, bot_buffs[buff_index].dot_rune, @@ -2953,6 +2953,20 @@ uint8 BotDatabase::GetSpellCastingChance(uint8 spell_type_index, uint8 class_ind return Bot::spell_casting_chances[spell_type_index][class_index][stance_index][conditional_index]; } +uint16 BotDatabase::GetRaceClassBitmask(uint16 bot_race) +{ + std::string query = fmt::format( + "SELECT `classes` FROM `bot_create_combinations` WHERE `race` = {}", + bot_race + ); + auto results = database.QueryDatabase(query); + uint16 classes = 0; + if (results.RowCount() == 1) { + auto row = results.begin(); + classes = atoi(row[0]); + } + return classes; +} /* fail::Bot functions */ const char* BotDatabase::fail::QueryNameAvailablity() { return "Failed to query name availability"; } diff --git a/zone/bot_database.h b/zone/bot_database.h index 57cf185ce..9ebb2b0c5 100644 --- a/zone/bot_database.h +++ b/zone/bot_database.h @@ -186,6 +186,7 @@ public: /* Bot miscellaneous functions */ uint8 GetSpellCastingChance(uint8 spell_type_index, uint8 class_index, uint8 stance_index, uint8 conditional_index); + uint16 GetRaceClassBitmask(uint16 bot_race); class fail { public: diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index 94b119e71..87835c6e6 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -239,7 +239,7 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { break; // Can we cast this spell on this target? - if(!(spells[botSpell.SpellId].targettype==ST_GroupTeleport || spells[botSpell.SpellId].targettype == ST_Target || tar == this) + if(!(spells[botSpell.SpellId].target_type==ST_GroupTeleport || spells[botSpell.SpellId].target_type == ST_Target || tar == this) && !(tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0)) break; @@ -338,14 +338,14 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { continue; // can not cast buffs for your own pet only on another pet that isn't yours - if((spells[selectedBotSpell.SpellId].targettype == ST_Pet) && (tar != this->GetPet())) + if((spells[selectedBotSpell.SpellId].target_type == ST_Pet) && (tar != this->GetPet())) continue; // Validate target - if(!((spells[selectedBotSpell.SpellId].targettype == ST_Target || spells[selectedBotSpell.SpellId].targettype == ST_Pet || tar == this || - spells[selectedBotSpell.SpellId].targettype == ST_Group || spells[selectedBotSpell.SpellId].targettype == ST_GroupTeleport || - (botClass == BARD && spells[selectedBotSpell.SpellId].targettype == ST_AEBard)) + if(!((spells[selectedBotSpell.SpellId].target_type == ST_Target || spells[selectedBotSpell.SpellId].target_type == ST_Pet || tar == this || + spells[selectedBotSpell.SpellId].target_type == ST_Group || spells[selectedBotSpell.SpellId].target_type == ST_GroupTeleport || + (botClass == BARD && spells[selectedBotSpell.SpellId].target_type == ST_AEBard)) && !tar->IsImmuneToSpell(selectedBotSpell.SpellId, this) && (tar->CanBuffStack(selectedBotSpell.SpellId, botLevel, true) >= 0))) { continue; @@ -613,7 +613,7 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { if(CheckSpellRecastTimers(this, itr->SpellIndex)) { - if(!(!tar->IsImmuneToSpell(selectedBotSpell.SpellId, this) && (spells[selectedBotSpell.SpellId].buffduration < 1 || tar->CanBuffStack(selectedBotSpell.SpellId, botLevel, true) >= 0))) + if(!(!tar->IsImmuneToSpell(selectedBotSpell.SpellId, this) && (spells[selectedBotSpell.SpellId].buff_duration < 1 || tar->CanBuffStack(selectedBotSpell.SpellId, botLevel, true) >= 0))) continue; //short duration buffs or other buffs only to be cast during combat. @@ -651,14 +651,14 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { continue; // can not cast buffs for your own pet only on another pet that isn't yours - if((spells[selectedBotSpell.SpellId].targettype == ST_Pet) && (tar != this->GetPet())) + if((spells[selectedBotSpell.SpellId].target_type == ST_Pet) && (tar != this->GetPet())) continue; // Validate target - if(!((spells[selectedBotSpell.SpellId].targettype == ST_Target || spells[selectedBotSpell.SpellId].targettype == ST_Pet || tar == this || - spells[selectedBotSpell.SpellId].targettype == ST_Group || spells[selectedBotSpell.SpellId].targettype == ST_GroupTeleport || - (botClass == BARD && spells[selectedBotSpell.SpellId].targettype == ST_AEBard)) + if(!((spells[selectedBotSpell.SpellId].target_type == ST_Target || spells[selectedBotSpell.SpellId].target_type == ST_Pet || tar == this || + spells[selectedBotSpell.SpellId].target_type == ST_Group || spells[selectedBotSpell.SpellId].target_type == ST_GroupTeleport || + (botClass == BARD && spells[selectedBotSpell.SpellId].target_type == ST_AEBard)) && !tar->IsImmuneToSpell(selectedBotSpell.SpellId, this) && (tar->CanBuffStack(selectedBotSpell.SpellId, botLevel, true) >= 0))) { continue; @@ -853,9 +853,9 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { continue; if (!CheckSpellRecastTimers(this, iter.SpellIndex)) continue; - if (spells[iter.SpellId].zonetype != -1 && zone->GetZoneType() != -1 && spells[iter.SpellId].zonetype != zone->GetZoneType()) // is this bit or index? + if (spells[iter.SpellId].zone_type != -1 && zone->GetZoneType() != -1 && spells[iter.SpellId].zone_type != zone->GetZoneType()) // is this bit or index? continue; - if (spells[iter.SpellId].targettype != ST_Target) + if (spells[iter.SpellId].target_type != ST_Target) continue; if (tar->CanBuffStack(iter.SpellId, botLevel, true) < 0) continue; @@ -875,7 +875,7 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { case BEASTLORD: { botSpell = GetBestBotSpellForDiseaseBasedSlow(this); - if(botSpell.SpellId == 0 || ((tar->GetMR() - 50) < (tar->GetDR() + spells[botSpell.SpellId].ResistDiff))) + if(botSpell.SpellId == 0 || ((tar->GetMR() - 50) < (tar->GetDR() + spells[botSpell.SpellId].resist_difficulty))) botSpell = GetBestBotSpellForMagicBasedSlow(this); break; } @@ -969,9 +969,9 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { continue; if (!CheckSpellRecastTimers(this, iter.SpellIndex)) continue; - if (spells[iter.SpellId].zonetype != -1 && zone->GetZoneType() != -1 && spells[iter.SpellId].zonetype != zone->GetZoneType()) // is this bit or index? + if (spells[iter.SpellId].zone_type != -1 && zone->GetZoneType() != -1 && spells[iter.SpellId].zone_type != zone->GetZoneType()) // is this bit or index? continue; - if (spells[iter.SpellId].targettype != ST_Target) + if (spells[iter.SpellId].target_type != ST_Target) continue; if (tar->CanBuffStack(iter.SpellId, botLevel, true) < 0) continue; @@ -996,9 +996,9 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { continue; if (!CheckSpellRecastTimers(this, iter.SpellIndex)) continue; - if (spells[iter.SpellId].zonetype != -1 && zone->GetZoneType() != -1 && spells[iter.SpellId].zonetype != zone->GetZoneType()) // is this bit or index? + if (spells[iter.SpellId].zone_type != -1 && zone->GetZoneType() != -1 && spells[iter.SpellId].zone_type != zone->GetZoneType()) // is this bit or index? continue; - switch (spells[iter.SpellId].targettype) { + switch (spells[iter.SpellId].target_type) { case ST_AEBard: case ST_AECaster: case ST_GroupTeleport: @@ -1028,9 +1028,9 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { continue; if (!CheckSpellRecastTimers(this, iter.SpellIndex)) continue; - if (spells[iter.SpellId].zonetype != -1 && zone->GetZoneType() != -1 && spells[iter.SpellId].zonetype != zone->GetZoneType()) // is this bit or index? + if (spells[iter.SpellId].zone_type != -1 && zone->GetZoneType() != -1 && spells[iter.SpellId].zone_type != zone->GetZoneType()) // is this bit or index? continue; - switch (spells[iter.SpellId].targettype) { + switch (spells[iter.SpellId].target_type) { case ST_AEBard: case ST_AECaster: case ST_GroupTeleport: @@ -1095,11 +1095,11 @@ bool Bot::AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain } else dist2 = DistanceSquared(m_Position, tar->GetPosition()); - if (((((spells[AIspells[i].spellid].targettype==ST_GroupTeleport && AIspells[i].type==2) - || spells[AIspells[i].spellid].targettype==ST_AECaster - || spells[AIspells[i].spellid].targettype==ST_Group - || spells[AIspells[i].spellid].targettype==ST_AEBard) - && dist2 <= spells[AIspells[i].spellid].aoerange*spells[AIspells[i].spellid].aoerange) + if (((((spells[AIspells[i].spellid].target_type==ST_GroupTeleport && AIspells[i].type==2) + || spells[AIspells[i].spellid].target_type==ST_AECaster + || spells[AIspells[i].spellid].target_type==ST_Group + || spells[AIspells[i].spellid].target_type==ST_AEBard) + && dist2 <= spells[AIspells[i].spellid].aoe_range*spells[AIspells[i].spellid].aoe_range) || dist2 <= GetActSpellRange(AIspells[i].spellid, spells[AIspells[i].spellid].range)*GetActSpellRange(AIspells[i].spellid, spells[AIspells[i].spellid].range)) && (mana_cost <= GetMana() || GetMana() == GetMaxMana())) { result = NPC::AIDoSpellCast(i, tar, mana_cost, oDontDoAgainBefore); @@ -1126,8 +1126,8 @@ bool Bot::AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain AIspells[i].time_cancast = Timer::GetCurrentTime() + spells[AIspells[i].spellid].recast_time; - if(spells[AIspells[i].spellid].EndurTimerIndex > 0) { - SetSpellRecastTimer(spells[AIspells[i].spellid].EndurTimerIndex, spells[AIspells[i].spellid].recast_time); + if(spells[AIspells[i].spellid].timer_id > 0) { + SetSpellRecastTimer(spells[AIspells[i].spellid].timer_id, spells[AIspells[i].spellid].recast_time); } } @@ -1582,7 +1582,7 @@ bool Bot::AIHealRotation(Mob* tar, bool useFastHeals) { return false; // Can we cast this spell on this target? - if (!(spells[botSpell.SpellId].targettype == ST_GroupTeleport || spells[botSpell.SpellId].targettype == ST_Target || tar == this) + if (!(spells[botSpell.SpellId].target_type == ST_GroupTeleport || spells[botSpell.SpellId].target_type == ST_Target || tar == this) && !(tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0)) return false; @@ -1637,7 +1637,7 @@ std::list Bot::GetBotSpellsForSpellEffectAndTargetType(Bot* botCaster, } if(IsEffectInSpell(botSpellList[i].spellid, spellEffect)) { - if(spells[botSpellList[i].spellid].targettype == targetType) { + if(spells[botSpellList[i].spellid].target_type == targetType) { BotSpell botSpell; botSpell.SpellId = botSpellList[i].spellid; botSpell.SpellIndex = i; @@ -2006,7 +2006,7 @@ BotSpell Bot::GetBestBotSpellForMagicBasedSlow(Bot* botCaster) { for (std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { // Assuming all the spells have been loaded into this list by level and in descending order - if (IsSlowSpell(botSpellListItr->SpellId) && spells[botSpellListItr->SpellId].resisttype == RESIST_MAGIC && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) { + if (IsSlowSpell(botSpellListItr->SpellId) && spells[botSpellListItr->SpellId].resist_type == RESIST_MAGIC && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) { result.SpellId = botSpellListItr->SpellId; result.SpellIndex = botSpellListItr->SpellIndex; result.ManaCost = botSpellListItr->ManaCost; @@ -2031,7 +2031,7 @@ BotSpell Bot::GetBestBotSpellForDiseaseBasedSlow(Bot* botCaster) { for(std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { // Assuming all the spells have been loaded into this list by level and in descending order - if(IsSlowSpell(botSpellListItr->SpellId) && spells[botSpellListItr->SpellId].resisttype == RESIST_DISEASE && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) { + if(IsSlowSpell(botSpellListItr->SpellId) && spells[botSpellListItr->SpellId].resist_type == RESIST_DISEASE && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) { result.SpellId = botSpellListItr->SpellId; result.SpellIndex = botSpellListItr->SpellIndex; result.ManaCost = botSpellListItr->ManaCost; @@ -2272,26 +2272,26 @@ BotSpell Bot::GetBestBotWizardNukeSpellByTargetResists(Bot* botCaster, Mob* targ bool spellSelected = false; if(CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) { - if(selectLureNuke && (spells[botSpellListItr->SpellId].ResistDiff < lureResisValue)) { + if(selectLureNuke && (spells[botSpellListItr->SpellId].resist_difficulty < lureResisValue)) { spellSelected = true; } else if(IsPureNukeSpell(botSpellListItr->SpellId)) { if(((target->GetMR() < target->GetCR()) || (target->GetMR() < target->GetFR())) && (GetSpellResistType(botSpellListItr->SpellId) == RESIST_MAGIC) - && (spells[botSpellListItr->SpellId].ResistDiff > lureResisValue)) + && (spells[botSpellListItr->SpellId].resist_difficulty > lureResisValue)) { spellSelected = true; } else if(((target->GetCR() < target->GetMR()) || (target->GetCR() < target->GetFR())) && (GetSpellResistType(botSpellListItr->SpellId) == RESIST_COLD) - && (spells[botSpellListItr->SpellId].ResistDiff > lureResisValue)) + && (spells[botSpellListItr->SpellId].resist_difficulty > lureResisValue)) { spellSelected = true; } else if(((target->GetFR() < target->GetCR()) || (target->GetFR() < target->GetMR())) && (GetSpellResistType(botSpellListItr->SpellId) == RESIST_FIRE) - && (spells[botSpellListItr->SpellId].ResistDiff > lureResisValue)) + && (spells[botSpellListItr->SpellId].resist_difficulty > lureResisValue)) { spellSelected = true; } - else if((GetSpellResistType(botSpellListItr->SpellId) == RESIST_MAGIC) && (spells[botSpellListItr->SpellId].ResistDiff > lureResisValue) && !IsStunSpell(botSpellListItr->SpellId)) { + else if((GetSpellResistType(botSpellListItr->SpellId) == RESIST_MAGIC) && (spells[botSpellListItr->SpellId].resist_difficulty > lureResisValue) && !IsStunSpell(botSpellListItr->SpellId)) { firstWizardMagicNukeSpellFound.SpellId = botSpellListItr->SpellId; firstWizardMagicNukeSpellFound.SpellIndex = botSpellListItr->SpellIndex; firstWizardMagicNukeSpellFound.ManaCost = botSpellListItr->ManaCost; @@ -2534,7 +2534,7 @@ int32 Bot::GetSpellRecastTimer(Bot *caster, int timer_index) { bool Bot::CheckSpellRecastTimers(Bot *caster, int SpellIndex) { if(caster) { if(caster->AIspells[SpellIndex].time_cancast < Timer::GetCurrentTime()) { //checks spell recast - if(GetSpellRecastTimer(caster, spells[caster->AIspells[SpellIndex].spellid].EndurTimerIndex) < Timer::GetCurrentTime()) { //checks for spells on the same timer + if(GetSpellRecastTimer(caster, spells[caster->AIspells[SpellIndex].spellid].timer_id) < Timer::GetCurrentTime()) { //checks for spells on the same timer return true; //can cast spell } } diff --git a/zone/cheat_manager.cpp b/zone/cheat_manager.cpp new file mode 100644 index 000000000..30ec922be --- /dev/null +++ b/zone/cheat_manager.cpp @@ -0,0 +1,411 @@ +#include "cheat_manager.h" +#include "client.h" +#include "quest_parser_collection.h" + +void CheatManager::SetClient(Client *cli) +{ + m_target = cli; +} + +void CheatManager::SetExemptStatus(ExemptionType type, bool v) +{ + if (v) { + MovementCheck(); + } + m_exemption[type] = v; +} + +bool CheatManager::GetExemptStatus(ExemptionType type) +{ + return m_exemption[type]; +} + +void CheatManager::CheatDetected(CheatTypes type, glm::vec3 position1, glm::vec3 position2) +{ + switch (type) { + case MQWarp: + if (m_time_since_last_warp_detection.GetRemainingTime() == 0 && RuleB(Cheat, EnableMQWarpDetector) && + ((m_target->Admin() < RuleI(Cheat, MQWarpExemptStatus) || (RuleI(Cheat, MQWarpExemptStatus)) == -1))) { + std::string message = fmt::format( + "/MQWarp (large warp detection) with location from x [{:.2f}] y [{:.2f}] z [{:.2f}] to x [{:.2f}] y [{:.2f}] z [{:.2f}] Distance [{:.2f}]", + position1.x, + position1.y, + position1.z, + position2.x, + position2.y, + position2.z, + Distance(position1, position2) + ); + database.SetMQDetectionFlag( + m_target->AccountName(), + m_target->GetName(), + message.c_str(), + zone->GetShortName() + ); + LogCheat(message); + std::string export_string = fmt::format( + "{} {} {}", + position1.x, + position1.y, + position1.z + ); + parse->EventPlayer(EVENT_WARP, m_target, export_string, 0); + } + break; + case MQWarpAbsolute: + if (RuleB(Cheat, EnableMQWarpDetector) && + ((m_target->Admin() < RuleI(Cheat, MQWarpExemptStatus) || (RuleI(Cheat, MQWarpExemptStatus)) == -1))) { + std::string message = fmt::format( + "/MQWarp (Absolute) with location from x [{:.2f}] y [{:.2f}] z [{:.2f}] to x [{:.2f}] y [{:.2f}] z [{:.2f}] Distance [{:.2f}]", + position1.x, + position1.y, + position1.z, + position2.x, + position2.y, + position2.z, + Distance(position1, position2) + ); + database.SetMQDetectionFlag( + m_target->AccountName(), + m_target->GetName(), + message.c_str(), + zone->GetShortName() + ); + LogCheat(message); + std::string export_string = fmt::format( + "{} {} {}", + position1.x, + position1.y, + position1.z + ); + parse->EventPlayer(EVENT_WARP, m_target, export_string, 0); + m_time_since_last_warp_detection.Start(2500); + } + break; + case MQWarpShadowStep: + if (RuleB(Cheat, EnableMQWarpDetector) && + ((m_target->Admin() < RuleI(Cheat, MQWarpExemptStatus) || (RuleI(Cheat, MQWarpExemptStatus)) == -1))) { + std::string message = fmt::format( + "/MQWarp (ShadowStep) with location from x [{:.2f}] y [{:.2f}] z [{:.2f}] the target was shadow step exempt but we still found this suspicious.", + position1.x, + position1.y, + position1.z + ); + database.SetMQDetectionFlag( + m_target->AccountName(), + m_target->GetName(), + message.c_str(), + zone->GetShortName() + ); + LogCheat(message); + } + break; + case MQWarpKnockBack: + if (RuleB(Cheat, EnableMQWarpDetector) && + ((m_target->Admin() < RuleI(Cheat, MQWarpExemptStatus) || (RuleI(Cheat, MQWarpExemptStatus)) == -1))) { + std::string message = fmt::format( + "/MQWarp (Knockback) with location from x [{:.2f}] y [{:.2f}] z [{:.2f}] the target was Knock Back exempt but we still found this suspicious.", + position1.x, + position1.y, + position1.z + ); + database.SetMQDetectionFlag( + m_target->AccountName(), + m_target->GetName(), + message.c_str(), + zone->GetShortName() + ); + LogCheat(message); + } + break; + + case MQWarpLight: + if (RuleB(Cheat, EnableMQWarpDetector) && + ((m_target->Admin() < RuleI(Cheat, MQWarpExemptStatus) || (RuleI(Cheat, MQWarpExemptStatus)) == -1))) { + if (RuleB(Cheat, MarkMQWarpLT)) { + std::string message = fmt::format( + "/MQWarp(Knockback) with location from x [{:.2f}] y [{:.2f}] z [{:.2f}] running fast but not fast enough to get killed, possibly: small warp, speed hack, excessive lag, marked as suspicious.", + position1.x, + position1.y, + position1.z + ); + database.SetMQDetectionFlag( + m_target->AccountName(), + m_target->GetName(), + message.c_str(), + zone->GetShortName() + ); + LogCheat(message); + } + } + break; + + case MQZone: + if (RuleB(Cheat, EnableMQZoneDetector) && + ((m_target->Admin() < RuleI(Cheat, MQZoneExemptStatus) || (RuleI(Cheat, MQZoneExemptStatus)) == -1))) { + std::string message = fmt::format( + "/MQZone used at x [{:.2f}] y [{:.2f}] z [{:.2f}]", + position1.x, + position1.y, + position1.z + ); + database.SetMQDetectionFlag( + m_target->AccountName(), + m_target->GetName(), + message.c_str(), + zone->GetShortName() + ); + LogCheat(message); + } + break; + case MQZoneUnknownDest: + if (RuleB(Cheat, EnableMQZoneDetector) && + ((m_target->Admin() < RuleI(Cheat, MQZoneExemptStatus) || (RuleI(Cheat, MQZoneExemptStatus)) == -1))) { + std::string message = fmt::format( + "/MQZone used at x [{:.2f}] y [{:.2f}] z [{:.2f}] with Unknown Destination", + position1.x, + position1.y, + position1.z + ); + database.SetMQDetectionFlag( + m_target->AccountName(), + m_target->GetName(), + message.c_str(), + zone->GetShortName() + ); + LogCheat(message); + } + break; + case MQGate: + if (RuleB(Cheat, EnableMQGateDetector) && + ((m_target->Admin() < RuleI(Cheat, MQGateExemptStatus) || (RuleI(Cheat, MQGateExemptStatus)) == -1))) { + std::string message = fmt::format( + "/MQGate used at x [{:.2f}] y [{:.2f}] z [{:.2f}]", + position1.x, + position1.y, + position1.z + ); + database.SetMQDetectionFlag( + m_target->AccountName(), + m_target->GetName(), + message.c_str(), + zone->GetShortName() + ); + LogCheat(message); + } + break; + case MQGhost: + // this isn't just for ghost, its also for if a person isn't sending their MovementHistory packet also. + if (RuleB(Cheat, EnableMQGhostDetector) && + ((m_target->Admin() < RuleI(Cheat, MQGhostExemptStatus) || + (RuleI(Cheat, MQGhostExemptStatus)) == -1))) { + database.SetMQDetectionFlag( + m_target->AccountName(), + m_target->GetName(), + "Packet blocking detected.", + zone->GetShortName() + ); + LogCheat( + "[MQGhost] [{}] [{}] was caught not sending the proper packets as regularly as they were suppose to.", + m_target->AccountName(), + m_target->GetName() + ); + } + break; + case MQFastMem: + if (RuleB(Cheat, EnableMQFastMemDetector) && + ((m_target->Admin() < RuleI(Cheat, MQFastMemExemptStatus) || + (RuleI(Cheat, MQFastMemExemptStatus)) == -1))) { + std::string message = fmt::format( + "/MQFastMem used at x [{:.2f}] y [{:.2f}] z [{:.2f}]", + position1.x, + position1.y, + position1.z + ); + database.SetMQDetectionFlag( + m_target->AccountName(), + m_target->GetName(), + message.c_str(), + zone->GetShortName() + ); + LogCheat(message); + } + break; + default: + std::string message = fmt::format( + "Unhandled HackerDetection flag with location from x [{:.2f}] y [{:.2f}] z [{:.2f}]", + position1.x, + position1.y, + position1.z + ); + database.SetMQDetectionFlag( + m_target->AccountName(), + m_target->GetName(), + message.c_str(), + zone->GetShortName() + ); + LogCheat(message); + break; + } +} + +void CheatManager::MovementCheck(glm::vec3 updated_position) +{ + if (m_time_since_last_movement_history.GetRemainingTime() == 0) { + CheatDetected(MQGhost, updated_position); + } + + float dist = DistanceNoZ(m_target->GetPosition(), updated_position); + uint32 cur_time = Timer::GetCurrentTime(); + if (dist == 0) { + if (m_distance_since_last_position_check > 0.0f) { + MovementCheck(0); + } + else { + m_time_since_last_position_check = cur_time; + m_cheat_detect_moved = false; + } + } + else { + m_distance_since_last_position_check += dist; + m_cheat_detect_moved = true; + if (m_time_since_last_position_check == 0) { + m_time_since_last_position_check = cur_time; + } + else { + MovementCheck(2500); + } + } +} + +void CheatManager::MovementCheck(uint32 time_between_checks) +{ + uint32 cur_time = Timer::GetCurrentTime(); + if ((cur_time - m_time_since_last_position_check) > time_between_checks) { + float estimated_speed = + (m_distance_since_last_position_check * 100) / (float) (cur_time - m_time_since_last_position_check); + + // MQWarpDetection shouldn't go below 1.0f so we can't end up dividing by 0. + float run_speed = m_target->GetRunspeed() / + std::min( + RuleR(Cheat, MQWarpDetectionDistanceFactor), + 1.0f + ); + if (estimated_speed > run_speed) { + bool using_gm_speed = m_target->GetGMSpeed(); + bool is_immobile = m_target->GetRunspeed() == 0; // this covers stuns, roots, mez, and pseudorooted. + if (!using_gm_speed && !is_immobile) { + if (GetExemptStatus(ShadowStep)) { + if (m_distance_since_last_position_check > 800) { + CheatDetected( + MQWarpShadowStep, + glm::vec3( + m_target->GetX(), + m_target->GetY(), + m_target->GetZ() + ) + ); + } + } + else if (GetExemptStatus(KnockBack)) { + if (estimated_speed > 30.0f) { + CheatDetected(MQWarpKnockBack, glm::vec3(m_target->GetX(), m_target->GetY(), m_target->GetZ())); + } + } + else if (!GetExemptStatus(Port)) { + if (estimated_speed > (run_speed * 1.5)) { + CheatDetected(MQWarp, glm::vec3(m_target->GetX(), m_target->GetY(), m_target->GetZ())); + m_time_since_last_position_check = cur_time; + m_distance_since_last_position_check = 0.0f; + } + else { + CheatDetected(MQWarpLight, glm::vec3(m_target->GetX(), m_target->GetY(), m_target->GetZ())); + } + } + } + } + if (time_between_checks != 1000) { + SetExemptStatus(ShadowStep, false); + SetExemptStatus(KnockBack, false); + SetExemptStatus(Port, false); + } + m_time_since_last_position_check = cur_time; + m_distance_since_last_position_check = 0.0f; + } +} + +void CheatManager::CheckMemTimer() +{ + if (m_target == nullptr) { + return; + } + if (m_time_since_last_memorization - Timer::GetCurrentTime() <= 1) { + glm::vec3 pos = m_target->GetPosition(); + CheatDetected(MQFastMem, pos); + } + m_time_since_last_memorization = Timer::GetCurrentTime(); +} + +void CheatManager::ProcessMovementHistory(const EQApplicationPacket *app) +{ + // if they haven't sent sent the packet within this time... they are probably spoofing... + // linux users reported that they don't send this packet at all but i can't prove they don't so i'm not sure if thats a fake or not. + m_time_since_last_movement_history.Start(70000); + if (GetExemptStatus(Port)) { + return; + } + auto *m_MovementHistory = (UpdateMovementEntry *) app->pBuffer; + if (app->size < sizeof(UpdateMovementEntry)) { + LogDebug( + "Size mismatch in OP_MovementHistoryList, expected {}, got [{}]", + sizeof(UpdateMovementEntry), + app->size + ); + DumpPacket(app); + return; + } + + for (int index = 0; index < (app->size) / sizeof(UpdateMovementEntry); index++) { + glm::vec3 to = glm::vec3(m_MovementHistory[index].X, m_MovementHistory[index].Y, m_MovementHistory[index].Z); + switch (m_MovementHistory[index].type) { + case UpdateMovementType::ZoneLine: + SetExemptStatus(Port, true); + break; + case UpdateMovementType::TeleportA: + if (index != 0) { + glm::vec3 from = glm::vec3( + m_MovementHistory[index - 1].X, + m_MovementHistory[index - 1].Y, + m_MovementHistory[index - 1].Z + ); + CheatDetected(MQWarpAbsolute, from, to); + } + SetExemptStatus(Port, false); + break; + } + } +} + +void CheatManager::ProcessSpawnApperance(uint16 spawn_id, uint16 type, uint32 parameter) +{ + if (type == AT_Anim && parameter == ANIM_SIT) { + m_time_since_last_memorization = Timer::GetCurrentTime(); + } + else if (spawn_id == 0 && type == AT_AntiCheat) { + m_time_since_last_action = parameter; + } +} + +void CheatManager::ProcessItemVerifyRequest(int32 slot_id, uint32 target_id) +{ + if (slot_id == -1 && m_warp_counter != target_id) { + m_warp_counter = target_id; + } +} + +void CheatManager::ClientProcess() +{ + if (!m_cheat_detect_moved) { + m_time_since_last_position_check = Timer::GetCurrentTime(); + } +} diff --git a/zone/cheat_manager.h b/zone/cheat_manager.h new file mode 100644 index 000000000..78aa89ba6 --- /dev/null +++ b/zone/cheat_manager.h @@ -0,0 +1,89 @@ +#ifndef ANTICHEAT_H +#define ANTICHEAT_H +class CheatManager; +class Client; + +#include "../common/timer.h" +#include "../common/rulesys.h" +#include +#include "../common/eq_packet_structs.h" +#include "../common/eq_packet.h" + +typedef enum { + Collision = 1, + TeleportB, + TeleportA, + ZoneLine, + Unknown0x5, + Unknown0x6, + SpellA, // Titanium - UF + Unknown0x8, + SpellB // Used in RoF+ +} UpdateMovementType; + +typedef enum { + ShadowStep, + KnockBack, + Port, + Assist, + Sense, + MAX_EXEMPTIONS +} ExemptionType; + +typedef enum { + MQWarp, + MQWarpShadowStep, + MQWarpKnockBack, + MQWarpLight, + MQZone, + MQZoneUnknownDest, + MQGate, + MQGhost, + MQFastMem, + MQWarpAbsolute +} CheatTypes; + +class CheatManager { +public: + CheatManager() + { + SetExemptStatus(ShadowStep, false); + SetExemptStatus(KnockBack, false); + SetExemptStatus(Port, false); + SetExemptStatus(Assist, false); + SetExemptStatus(Sense, false); + m_distance_since_last_position_check = 0.0f; + m_cheat_detect_moved = false; + m_target = nullptr; + m_time_since_last_memorization = 0; + m_time_since_last_position_check = 0; + m_time_since_last_warp_detection.Start(); + m_time_since_last_movement_history.Start(70000); + m_warp_counter = 0; + } + void SetClient(Client *cli); + void SetExemptStatus(ExemptionType type, bool v); + bool GetExemptStatus(ExemptionType type); + void CheatDetected(CheatTypes type, glm::vec3 position1, glm::vec3 position2 = glm::vec3(0, 0, 0)); + void MovementCheck(glm::vec3 updated_position); + void MovementCheck(uint32 time_between_checks = 1000); + void CheckMemTimer(); + void ProcessMovementHistory(const EQApplicationPacket *app); + void ProcessSpawnApperance(uint16 spawn_id, uint16 type, uint32 parameter); + void ProcessItemVerifyRequest(int32 slot_id, uint32 target_id); + void ClientProcess(); +private: + bool m_exemption[ExemptionType::MAX_EXEMPTIONS]{}; + float m_distance_since_last_position_check; + bool m_cheat_detect_moved; + + Client *m_target; + uint32 m_time_since_last_position_check; + uint32 m_time_since_last_memorization; + uint32 m_time_since_last_action{}; + Timer m_time_since_last_warp_detection; + Timer m_time_since_last_movement_history; + uint32 m_warp_counter; +}; + +#endif //ANTICHEAT_H diff --git a/zone/client.cpp b/zone/client.cpp index 7a34a142a..f2a831a45 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -61,6 +61,11 @@ extern volatile bool RunLoops; #include "mob_movement_manager.h" #include "../common/content/world_content_service.h" #include "../common/expedition_lockout_timer.h" +#include "cheat_manager.h" + +#include "../common/repositories/character_spells_repository.h" +#include "../common/repositories/character_disciplines_repository.h" + extern QueryServ* QServ; extern EntityList entity_list; @@ -138,10 +143,8 @@ Client::Client(EQStreamInterface* ieqs) linkdead_timer(RuleI(Zone,ClientLinkdeadMS)), dead_timer(2000), global_channel_timer(1000), - shield_timer(500), fishing_timer(8000), endupkeep_timer(1000), - forget_timer(0), autosave_timer(RuleI(Character, AutosaveIntervalS) * 1000), client_scan_npc_aggro_timer(RuleI(Aggro, ClientAggroCheckIdleInterval)), client_zone_wide_full_position_update_timer(5 * 60 * 1000), @@ -150,6 +153,7 @@ Client::Client(EQStreamInterface* ieqs) TaskPeriodic_Timer(RuleI(TaskSystem, PeriodicCheckTimer) * 1000), charm_update_timer(6000), rest_timer(1), + pick_lock_timer(1000), charm_class_attacks_timer(3000), charm_cast_timer(3500), qglobal_purge_timer(30000), @@ -163,14 +167,12 @@ Client::Client(EQStreamInterface* ieqs) helm_toggle_timer(250), aggro_meter_timer(AGGRO_METER_UPDATE_MS), m_Proximity(FLT_MAX, FLT_MAX, FLT_MAX), //arbitrary large number - m_ZoneSummonLocation(-2.0f,-2.0f,-2.0f), + m_ZoneSummonLocation(-2.0f,-2.0f,-2.0f,-2.0f), m_AutoAttackPosition(0.0f, 0.0f, 0.0f, 0.0f), m_AutoAttackTargetLocation(0.0f, 0.0f, 0.0f), last_region_type(RegionTypeUnsupported), m_dirtyautohaters(false), mob_close_scan_timer(6000), - hp_self_update_throttle_timer(300), - hp_other_update_throttle_timer(500), position_update_timer(10000), consent_throttle_timer(2000), tmSitting(0) @@ -178,12 +180,11 @@ Client::Client(EQStreamInterface* ieqs) for (int client_filter = 0; client_filter < _FilterCount; client_filter++) ClientFilters[client_filter] = FilterShow; - + cheat_manager.SetClient(this); mMovementManager->AddClient(this); character_id = 0; conn_state = NoPacketsReceived; client_data_loaded = false; - feigned = false; berserk = false; dead = false; eqs = ieqs; @@ -198,9 +199,8 @@ Client::Client(EQStreamInterface* ieqs) TrackingID = 0; WID = 0; account_id = 0; - admin = 0; + admin = AccountStatus::Player; lsaccountid = 0; - shield_target = nullptr; guild_id = GUILD_NONE; guildrank = 0; GuildBanker = false; @@ -236,7 +236,6 @@ Client::Client(EQStreamInterface* ieqs) pQueuedSaveWorkID = 0; position_update_same_count = 0; fishing_timer.Disable(); - shield_timer.Disable(); dead_timer.Disable(); camp_timer.Disable(); autosave_timer.Disable(); @@ -270,6 +269,8 @@ Client::Client(EQStreamInterface* ieqs) if (RuleI(World, PVPMinLevel) > 0 && level >= RuleI(World, PVPMinLevel) && m_pp.pvp == 0) SetPVP(true, false); dynamiczone_removal_timer.Disable(); + heroforge_wearchange_timer.Disable(); + //for good measure: memset(&m_pp, 0, sizeof(m_pp)); memset(&m_epp, 0, sizeof(m_epp)); @@ -322,7 +323,7 @@ Client::Client(EQStreamInterface* ieqs) adventure_stats_timer = nullptr; adventure_leaderboard_timer = nullptr; adv_data = nullptr; - adv_requested_theme = 0; + adv_requested_theme = LDoNThemes::Unused; adv_requested_id = 0; adv_requested_member_count = 0; @@ -352,9 +353,10 @@ Client::Client(EQStreamInterface* ieqs) temp_pvp = false; is_client_moving = false; - /** - * GM - */ + // rate limiter + m_list_task_timers_rate_limit.Start(1000); + + // gm SetDisplayMobInfoWindow(true); SetDevToolsEnabled(true); @@ -420,16 +422,6 @@ Client::~Client() { } } - if (shield_target) { - for (int y = 0; y < 2; y++) { - if (shield_target->shielder[y].shielder_id == GetID()) { - shield_target->shielder[y].shielder_id = 0; - shield_target->shielder[y].shielder_bonus = 0; - } - } - shield_target = nullptr; - } - if(GetTarget()) GetTarget()->IsTargeted(-1); @@ -441,7 +433,7 @@ Client::~Client() { if(IsHoveringForRespawn()) { - m_pp.zone_id = m_pp.binds[0].zoneId; + m_pp.zone_id = m_pp.binds[0].zone_id; m_pp.zoneInstance = m_pp.binds[0].instance_id; m_Position.x = m_pp.binds[0].x; m_Position.y = m_pp.binds[0].y; @@ -658,7 +650,7 @@ bool Client::Save(uint8 iCommitNow) { /* Save Current Bind Points */ for (int i = 0; i < 5; i++) - if (m_pp.binds[i].zoneId) + if (m_pp.binds[i].zone_id) database.SaveCharacterBindPoint(CharacterID(), m_pp.binds[i], i); /* Save Character Buffs */ @@ -1023,7 +1015,7 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s else return; } - if(worldserver.IsOOCMuted() && admin < 100) + if(worldserver.IsOOCMuted() && admin < AccountStatus::GMAdmin) { Message(0,"OOC has been muted. Try again later."); return; @@ -1062,7 +1054,7 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s } case ChatChannel_Broadcast: /* Broadcast */ case ChatChannel_GMSAY: { /* GM Say */ - if (!(admin >= 80)) + if (!(admin >= AccountStatus::QuestTroupe)) Message(0, "Error: Only GMs can use this channel"); else if (!worldserver.SendChannelMessage(this, targetname, chan_num, 0, language, lang_skill, message)) Message(0, "Error: World server disconnected"); @@ -1240,7 +1232,7 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s } void Client::ChannelMessageSend(const char* from, const char* to, uint8 chan_num, uint8 language, uint8 lang_skill, const char* message, ...) { - if ((chan_num==11 && !(this->GetGM())) || (chan_num==10 && this->Admin()<80)) // dont need to send /pr & /petition to everybody + if ((chan_num==11 && !(this->GetGM())) || (chan_num==10 && this->Admin() < AccountStatus::QuestTroupe)) // dont need to send /pr & /petition to everybody return; va_list argptr; char buffer[4096]; @@ -1375,8 +1367,7 @@ void Client::SetMaxHP() { Save(); } -bool Client::UpdateLDoNPoints(int32 points, uint32 theme) -{ +bool Client::UpdateLDoNPoints(uint32 theme_id, int points) { /* make sure total stays in sync with individual buckets m_pp.ldon_points_available = m_pp.ldon_points_guk @@ -1385,121 +1376,108 @@ bool Client::UpdateLDoNPoints(int32 points, uint32 theme) +m_pp.ldon_points_ruj +m_pp.ldon_points_tak; */ - if(points < 0) - { - if(m_pp.ldon_points_available < (0-points)) + if(points < 0) { + if(m_pp.ldon_points_available < (0 - points)) return false; } - switch(theme) - { - // handle generic points (theme=0) - case 0: - { // no theme, so distribute evenly across all - int splitpts=points/5; - int gukpts=splitpts+(points%5); - int mirpts=splitpts; - int mmcpts=splitpts; - int rujpts=splitpts; - int takpts=splitpts; - splitpts=0; - - if(points < 0) - { - if(m_pp.ldon_points_available < (0-points)) - { + switch (theme_id) { + case LDoNThemes::Unused: { // No theme, so distribute evenly across all + int split_points = (points / 5); + int guk_points = (split_points + (points % 5)); + int mir_points = split_points; + int mmc_points = split_points; + int ruj_points = split_points; + int tak_points = split_points; + split_points = 0; + if(points < 0) { + if(m_pp.ldon_points_available < (0 - points)) { return false; } - if(m_pp.ldon_points_guk < (0-gukpts)) - { - mirpts+=gukpts+m_pp.ldon_points_guk; - gukpts=0-m_pp.ldon_points_guk; + + if(m_pp.ldon_points_guk < (0 - guk_points)) { + mir_points += (guk_points + m_pp.ldon_points_guk); + guk_points = (0 - m_pp.ldon_points_guk); } - if(m_pp.ldon_points_mir < (0-mirpts)) - { - mmcpts+=mirpts+m_pp.ldon_points_mir; - mirpts=0-m_pp.ldon_points_mir; + + if(m_pp.ldon_points_mir < (0 - mir_points)) { + mmc_points += (mir_points + m_pp.ldon_points_mir); + mir_points = (0 - m_pp.ldon_points_mir); } - if(m_pp.ldon_points_mmc < (0-mmcpts)) - { - rujpts+=mmcpts+m_pp.ldon_points_mmc; - mmcpts=0-m_pp.ldon_points_mmc; + + if(m_pp.ldon_points_mmc < (0 - mmc_points)) { + ruj_points += (mmc_points + m_pp.ldon_points_mmc); + mmc_points = (0 - m_pp.ldon_points_mmc); } - if(m_pp.ldon_points_ruj < (0-rujpts)) - { - takpts+=rujpts+m_pp.ldon_points_ruj; - rujpts=0-m_pp.ldon_points_ruj; + + if(m_pp.ldon_points_ruj < (0 - ruj_points)) { + tak_points += (ruj_points + m_pp.ldon_points_ruj); + ruj_points = (0 - m_pp.ldon_points_ruj); } - if(m_pp.ldon_points_tak < (0-takpts)) - { - splitpts=takpts+m_pp.ldon_points_tak; - takpts=0-m_pp.ldon_points_tak; + + if(m_pp.ldon_points_tak < (0 - tak_points)) { + split_points = (tak_points + m_pp.ldon_points_tak); + tak_points = (0 - m_pp.ldon_points_tak); } } - m_pp.ldon_points_guk += gukpts; - m_pp.ldon_points_mir+=mirpts; - m_pp.ldon_points_mmc += mmcpts; - m_pp.ldon_points_ruj += rujpts; - m_pp.ldon_points_tak += takpts; - points-=splitpts; - // if anything left, recursively loop thru again - if (splitpts !=0) - UpdateLDoNPoints(splitpts,0); + m_pp.ldon_points_guk += guk_points; + m_pp.ldon_points_mir += mir_points; + m_pp.ldon_points_mmc += mmc_points; + m_pp.ldon_points_ruj += ruj_points; + m_pp.ldon_points_tak += tak_points; + points -= split_points; + if (split_points != 0) { // if anything left, recursively loop thru again + UpdateLDoNPoints(0, split_points); + } break; } - case 1: - { - if(points < 0) - { - if(m_pp.ldon_points_guk < (0-points)) + case LDoNThemes::GUK: { + if(points < 0) { + if(m_pp.ldon_points_guk < (0 - points)) { return false; + } } m_pp.ldon_points_guk += points; break; } - case 2: - { - if(points < 0) - { - if(m_pp.ldon_points_mir < (0-points)) + case LDoNThemes::MIR: { + if(points < 0) { + if(m_pp.ldon_points_mir < (0 - points)) { return false; + } } m_pp.ldon_points_mir += points; break; } - case 3: - { - if(points < 0) - { - if(m_pp.ldon_points_mmc < (0-points)) + case LDoNThemes::MMC: { + if(points < 0) { + if(m_pp.ldon_points_mmc < (0 - points)) { return false; + } } m_pp.ldon_points_mmc += points; break; } - case 4: - { - if(points < 0) - { - if(m_pp.ldon_points_ruj < (0-points)) + case LDoNThemes::RUJ: { + if(points < 0) { + if(m_pp.ldon_points_ruj < (0 - points)) { return false; + } } m_pp.ldon_points_ruj += points; break; } - case 5: - { - if(points < 0) - { - if(m_pp.ldon_points_tak < (0-points)) + case LDoNThemes::TAK: { + if(points < 0) { + if(m_pp.ldon_points_tak < (0 - points)) { return false; + } } m_pp.ldon_points_tak += points; break; } } m_pp.ldon_points_available += points; - auto outapp = new EQApplicationPacket(OP_AdventurePointsUpdate, sizeof(AdventurePoints_Update_Struct)); AdventurePoints_Update_Struct* apus = (AdventurePoints_Update_Struct*)outapp->pBuffer; apus->ldon_available_points = m_pp.ldon_points_available; @@ -1512,8 +1490,6 @@ bool Client::UpdateLDoNPoints(int32 points, uint32 theme) QueuePacket(outapp); safe_delete(outapp); return true; - - return(false); } void Client::SetSkill(EQ::skills::SkillType skillid, uint16 value) { @@ -1585,46 +1561,56 @@ void Client::SendSound(){//Makes a sound. safe_delete(outapp); } -void Client::UpdateWho(uint8 remove) { - if (account_id == 0) +void Client::UpdateWho(uint8 remove) +{ + if (account_id == 0) { return; - if (!worldserver.Connected()) + } + if (!worldserver.Connected()) { return; + } + auto pack = new ServerPacket(ServerOP_ClientList, sizeof(ServerClientList_Struct)); - ServerClientList_Struct* scl = (ServerClientList_Struct*) pack->pBuffer; - scl->remove = remove; - scl->wid = this->GetWID(); - scl->IP = this->GetIP(); - scl->charid = this->CharacterID(); - strcpy(scl->name, this->GetName()); + auto *s = (ServerClientList_Struct *) pack->pBuffer; + s->remove = remove; + s->wid = this->GetWID(); + s->IP = this->GetIP(); + s->charid = this->CharacterID(); + strcpy(s->name, this->GetName()); - scl->gm = GetGM(); - scl->Admin = this->Admin(); - scl->AccountID = this->AccountID(); - strcpy(scl->AccountName, this->AccountName()); - scl->LSAccountID = this->LSAccountID(); - strn0cpy(scl->lskey, lskey, sizeof(scl->lskey)); - scl->zone = zone->GetZoneID(); - scl->instance_id = zone->GetInstanceID(); - scl->race = this->GetRace(); - scl->class_ = GetClass(); - scl->level = GetLevel(); - if (m_pp.anon == 0) - scl->anon = 0; - else if (m_pp.anon == 1) - scl->anon = 1; - else if (m_pp.anon >= 2) - scl->anon = 2; + s->gm = GetGM(); + s->Admin = this->Admin(); + s->AccountID = this->AccountID(); + strcpy(s->AccountName, this->AccountName()); - scl->ClientVersion = static_cast(ClientVersion()); - scl->tellsoff = tellsoff; - scl->guild_id = guild_id; - scl->LFG = LFG; - if(LFG) { - scl->LFGFromLevel = LFGFromLevel; - scl->LFGToLevel = LFGToLevel; - scl->LFGMatchFilter = LFGMatchFilter; - memcpy(scl->LFGComments, LFGComments, sizeof(scl->LFGComments)); + s->LSAccountID = this->LSAccountID(); + strn0cpy(s->lskey, lskey, sizeof(s->lskey)); + + s->zone = zone->GetZoneID(); + s->instance_id = zone->GetInstanceID(); + s->race = this->GetRace(); + s->class_ = GetClass(); + s->level = GetLevel(); + + if (m_pp.anon == 0) { + s->anon = 0; + } + else if (m_pp.anon == 1) { + s->anon = 1; + } + else if (m_pp.anon >= 2) { + s->anon = 2; + } + + s->ClientVersion = static_cast(ClientVersion()); + s->tellsoff = tellsoff; + s->guild_id = guild_id; + s->LFG = LFG; + if (LFG) { + s->LFGFromLevel = LFGFromLevel; + s->LFGToLevel = LFGToLevel; + s->LFGMatchFilter = LFGMatchFilter; + memcpy(s->LFGComments, LFGComments, sizeof(s->LFGComments)); } worldserver.SendPacket(pack); @@ -1885,9 +1871,7 @@ void Client::CheckManaEndUpdate() { mana_change->stamina = current_endurance; mana_change->spell_id = casting_spell_id; mana_change->keepcasting = 1; - mana_change->padding[0] = 0; - mana_change->padding[1] = 0; - mana_change->padding[2] = 0; + mana_change->slot = -1; outapp->priority = 6; QueuePacket(outapp); safe_delete(outapp); @@ -2031,6 +2015,10 @@ void Client::Stand() { SetAppearance(eaStanding, false); } +void Client::Sit() { + SetAppearance(eaSitting, false); +} + void Client::ChangeLastName(const char* in_lastname) { memset(m_pp.last_name, 0, sizeof(m_pp.last_name)); strn0cpy(m_pp.last_name, in_lastname, sizeof(m_pp.last_name)); @@ -2088,7 +2076,13 @@ bool Client::ChangeFirstName(const char* in_firstname, const char* gmname) void Client::SetGM(bool toggle) { m_pp.gm = toggle ? 1 : 0; m_inv.SetGMInventory((bool)m_pp.gm); - Message(Chat::Red, "You are %s a GM.", m_pp.gm ? "now" : "no longer"); + Message( + Chat::White, + fmt::format( + "You are {} flagged as a GM.", + m_pp.gm ? "now" : "no longer" + ).c_str() + ); SendAppearancePacket(AT_GM, m_pp.gm); Save(); UpdateWho(); @@ -2429,16 +2423,19 @@ bool Client::CheckIncreaseSkill(EQ::skills::SkillType skillid, Mob *against_who, return false; if (skillid > EQ::skills::HIGHEST_SKILL) return false; - int skillval = GetRawSkill(skillid); + int skillval = GetRawSkill(skillid); int maxskill = GetMaxSkillAfterSpecializationRules(skillid, MaxSkill(skillid)); - char buffer[24] = { 0 }; - snprintf(buffer, 23, "%d %d", skillid, skillval); - parse->EventPlayer(EVENT_USE_SKILL, this, buffer, 0); + std::string export_string = fmt::format( + "{} {}", + skillid, + skillval + ); + parse->EventPlayer(EVENT_USE_SKILL, this, export_string, 0); if (against_who) { if ( - against_who->GetSpecialAbility(IMMUNE_AGGRO) || - against_who->GetSpecialAbility(IMMUNE_AGGRO_CLIENT) || - against_who->IsClient() || + against_who->GetSpecialAbility(IMMUNE_AGGRO) || + against_who->GetSpecialAbility(IMMUNE_AGGRO_CLIENT) || + against_who->IsClient() || GetLevelCon(against_who->GetLevel()) == CON_GRAY ) { //false by default @@ -2451,23 +2448,26 @@ bool Client::CheckIncreaseSkill(EQ::skills::SkillType skillid, Mob *against_who, // Make sure we're not already at skill cap if (skillval < maxskill) { - // the higher your current skill level, the harder it is - int32 Chance = 10 + chancemodi + ((252 - skillval) / 20); - - Chance = (Chance * RuleI(Character, SkillUpModifier) / 100); - - Chance = mod_increase_skill_chance(Chance, against_who); - - if(Chance < 1) - Chance = 1; // Make it always possible + double Chance = 0; + if (RuleI(Character, SkillUpMaximumChancePercentage) + chancemodi - RuleI(Character, SkillUpMinimumChancePercentage) <= RuleI(Character, SkillUpMinimumChancePercentage)) { + Chance = RuleI(Character, SkillUpMinimumChancePercentage); + } + else { + // f(x) = (max - min + modification) * .99^skillval + min + // This results in a exponential decay where as you skill up, you lose a slight chance to skill up, ranging from your modified maximum to approaching your minimum + // This result is increased by the existing SkillUpModifier rule + double working_chance = (((RuleI(Character, SkillUpMaximumChancePercentage) - RuleI(Character, SkillUpMinimumChancePercentage) + chancemodi) * (pow(0.99, skillval))) + RuleI(Character, SkillUpMinimumChancePercentage)); + Chance = (working_chance * RuleI(Character, SkillUpModifier) / 100); + Chance = mod_increase_skill_chance(Chance, against_who); + } if(zone->random.Real(0, 99) < Chance) { SetSkill(skillid, GetRawSkill(skillid) + 1); - LogSkills("Skill [{}] at value [{}] successfully gain with [{}]% chance (mod [{}])", skillid, skillval, Chance, chancemodi); + LogSkills("Skill [{}] at value [{}] successfully gain with [{}] chance (mod [{}])", skillid, skillval, Chance, chancemodi); return true; } else { - LogSkills("Skill [{}] at value [{}] failed to gain with [{}]% chance (mod [{}])", skillid, skillval, Chance, chancemodi); + LogSkills("Skill [{}] at value [{}] failed to gain with [{}] chance (mod [{}])", skillid, skillval, Chance, chancemodi); } } else { LogSkills("Skill [{}] at value [{}] cannot increase due to maxmum [{}]", skillid, skillval, maxskill); @@ -2533,7 +2533,7 @@ uint16 Client::GetMaxSkillAfterSpecializationRules(EQ::skills::SkillType skillid uint16 PrimarySkillValue = 0, SecondarySkillValue = 0; - uint16 MaxSpecializations = GetAA(aaSecondaryForte) ? 2 : 1; + uint16 MaxSpecializations = aabonuses.SecondaryForte ? 2 : 1; if (skillid >= EQ::skills::SkillSpecializeAbjure && skillid <= EQ::skills::SkillSpecializeEvocation) { @@ -2622,10 +2622,11 @@ void Client::SetPVP(bool toggle, bool message) { m_pp.pvp = toggle ? 1 : 0; if (message) { - if(GetPVP()) - this->MessageString(Chat::Shout,PVP_ON); - else - Message(Chat::Red, "You no longer follow the ways of discord."); + if(GetPVP()) { + MessageString(Chat::Shout, PVP_ON); + } else { + Message(Chat::Shout, "You now follow the ways of Order."); + } } SendAppearancePacket(AT_PVP, GetPVP()); @@ -2656,44 +2657,28 @@ void Client::GMKill() { } bool Client::CheckAccess(int16 iDBLevel, int16 iDefaultLevel) { - if ((admin >= iDBLevel) || (iDBLevel == 255 && admin >= iDefaultLevel)) + if ((admin >= iDBLevel) || (iDBLevel == AccountStatus::Max && admin >= iDefaultLevel)) return true; else return false; } -void Client::MemorizeSpell(uint32 slot,uint32 spellid,uint32 scribing){ +void Client::MemorizeSpell(uint32 slot,uint32 spellid,uint32 scribing, uint32 reduction){ if (slot < 0 || slot >= EQ::spells::DynamicLookup(ClientVersion(), GetGM())->SpellbookSize) return; if ((spellid < 3 || spellid > EQ::spells::DynamicLookup(ClientVersion(), GetGM())->SpellIdMax) && spellid != 0xFFFFFFFF) return; - auto outapp = new EQApplicationPacket(OP_MemorizeSpell, sizeof(MemorizeSpell_Struct)); MemorizeSpell_Struct* mss=(MemorizeSpell_Struct*)outapp->pBuffer; mss->scribing=scribing; mss->slot=slot; mss->spell_id=spellid; + mss->reduction = reduction; outapp->priority = 5; QueuePacket(outapp); safe_delete(outapp); } -void Client::SetFeigned(bool in_feigned) { - if (in_feigned) - { - if(RuleB(Character, FeignKillsPet)) - { - SetPet(0); - } - SetHorseId(0); - entity_list.ClearFeignAggro(this); - forget_timer.Start(FeignMemoryDuration); - } else { - forget_timer.Disable(); - } - feigned=in_feigned; - } - void Client::LogMerchant(Client* player, Mob* merchant, uint32 quantity, uint32 price, const EQ::ItemData* item, bool buying) { if(!player || !merchant || !item) @@ -3162,53 +3147,37 @@ void Client::MessageString(uint32 type, uint32 string_id, const char* message1, if (GetFilter(FilterDamageShields) == FilterHide && type == Chat::DamageShield) return; - int i = 0, argcount = 0, length = 0; - char *bufptr = nullptr; - const char *message_arg[10] = {0}; + if (type == Chat::Emote) + type = 4; - if(type==Chat::Emote) - type=4; - - if(!message1) - { + if (!message1) { MessageString(type, string_id); // use the simple message instead return; } - message_arg[i++] = message1; - message_arg[i++] = message2; - message_arg[i++] = message3; - message_arg[i++] = message4; - message_arg[i++] = message5; - message_arg[i++] = message6; - message_arg[i++] = message7; - message_arg[i++] = message8; - message_arg[i++] = message9; + const char *message_arg[] = { + message1, message2, message3, message4, message5, + message6, message7, message8, message9 + }; - for(; message_arg[argcount]; ++argcount) - length += strlen(message_arg[argcount]) + 1; - - length += 1; - - auto outapp = new EQApplicationPacket(OP_FormattedMessage, sizeof(FormattedMessage_Struct) + length); - FormattedMessage_Struct *fm = (FormattedMessage_Struct *)outapp->pBuffer; - fm->string_id = string_id; - fm->type = type; - bufptr = fm->message; - for(i = 0; i < argcount; i++) - { - strcpy(bufptr, message_arg[i]); - bufptr += strlen(message_arg[i]) + 1; + SerializeBuffer buf(20); + buf.WriteInt32(0); // unknown + buf.WriteInt32(string_id); + buf.WriteInt32(type); + for (auto &m : message_arg) { + if (m == nullptr) + break; + buf.WriteString(m); } - // since we're moving the pointer the 0 offset is correct - bufptr[0] = '\0'; + buf.WriteInt8(0); // prevent oob in packet translation, maybe clean that up sometime - if(distance>0) - entity_list.QueueCloseClients(this,outapp,false,distance); + auto outapp = std::make_unique(OP_FormattedMessage, buf); + + if (distance > 0) + entity_list.QueueCloseClients(this, outapp.get(), false, distance); else - QueuePacket(outapp); - safe_delete(outapp); + QueuePacket(outapp.get()); } void Client::MessageString(const CZClientMessageString_Struct* msg) @@ -3298,10 +3267,6 @@ void Client::FilteredMessageString(Mob *sender, uint32 type, eqFilterType filter if (!FilteredMessageCheck(sender, filter)) return; - int i = 0, argcount = 0, length = 0; - char *bufptr = nullptr; - const char *message_arg[10] = {0}; - if (type == Chat::Emote) type = 4; @@ -3310,36 +3275,26 @@ void Client::FilteredMessageString(Mob *sender, uint32 type, eqFilterType filter return; } - message_arg[i++] = message1; - message_arg[i++] = message2; - message_arg[i++] = message3; - message_arg[i++] = message4; - message_arg[i++] = message5; - message_arg[i++] = message6; - message_arg[i++] = message7; - message_arg[i++] = message8; - message_arg[i++] = message9; + const char *message_arg[] = { + message1, message2, message3, message4, message5, + message6, message7, message8, message9 + }; - for (; message_arg[argcount]; ++argcount) - length += strlen(message_arg[argcount]) + 1; - - length += 1; - - auto outapp = new EQApplicationPacket(OP_FormattedMessage, sizeof(FormattedMessage_Struct) + length); - FormattedMessage_Struct *fm = (FormattedMessage_Struct *)outapp->pBuffer; - fm->string_id = string_id; - fm->type = type; - bufptr = fm->message; - for (i = 0; i < argcount; i++) { - strcpy(bufptr, message_arg[i]); - bufptr += strlen(message_arg[i]) + 1; + SerializeBuffer buf(20); + buf.WriteInt32(0); // unknown + buf.WriteInt32(string_id); + buf.WriteInt32(type); + for (auto &m : message_arg) { + if (m == nullptr) + break; + buf.WriteString(m); } - // since we're moving the pointer the 0 offset is correct - bufptr[0] = '\0'; + buf.WriteInt8(0); // prevent oob in packet translation, maybe clean that up sometime - QueuePacket(outapp); - safe_delete(outapp); + auto outapp = std::make_unique(OP_FormattedMessage, buf); + + QueuePacket(outapp.get()); } void Client::Tell_StringID(uint32 string_id, const char *who, const char *message) @@ -3429,11 +3384,7 @@ void Client::LinkDead() raid->MemberZoned(this); } - Expedition* expedition = GetExpedition(); - if (expedition) - { - expedition->SetMemberStatus(this, ExpeditionMemberStatus::LinkDead); - } + SetDynamicZoneMemberStatus(DynamicZoneMemberStatus::LinkDead); // save_timer.Start(2500); linkdead_timer.Start(RuleI(Zone,ClientLinkdeadMS)); @@ -3503,7 +3454,7 @@ uint8 Client::SlotConvert2(uint8 slot){ void Client::Escape() { entity_list.RemoveFromTargets(this, true); - SetInvisible(1); + SetInvisible(Invisibility::Invisible); MessageString(Chat::Skills, ESCAPE); } @@ -3514,7 +3465,7 @@ float Client::CalcPriceMod(Mob* other, bool reverse) if (other) { int factionlvl = GetFactionLevel(CharacterID(), other->CastToNPC()->GetNPCTypeID(), GetFactionRace(), GetClass(), GetDeity(), other->CastToNPC()->GetPrimaryFaction(), other); - if (factionlvl >= FACTION_APPREHENSIVE) // Apprehensive or worse. + if (factionlvl >= FACTION_APPREHENSIVELY) // Apprehensive or worse. { if (GetCHA() > 103) { @@ -3529,7 +3480,7 @@ float Client::CalcPriceMod(Mob* other, bool reverse) chaformula = 1*(RuleI(Merchant, PricePenaltyPct)); } } - if (factionlvl <= FACTION_INDIFFERENT) // Indifferent or better. + if (factionlvl <= FACTION_INDIFFERENTLY) // Indifferent or better. { if (GetCHA() > 75) { @@ -3990,7 +3941,7 @@ void Client::Sacrifice(Client *caster) Death_Struct *d = (Death_Struct *)app.pBuffer; d->spawn_id = GetID(); d->killer_id = caster ? caster->GetID() : 0; - d->bindzoneid = GetPP().binds[0].zoneId; + d->bindzoneid = GetPP().binds[0].zone_id; d->spell_id = SPELL_UNKNOWN; d->attack_skill = 0xe7; d->damage = 0; @@ -4038,7 +3989,7 @@ void Client::SendOPTranslocateConfirm(Mob *Caster, uint16 SpellID) { PendingTranslocateData.spell_id = ts->SpellID = SpellID; if((SpellID == 1422) || (SpellID == 1334) || (SpellID == 3243)) { - PendingTranslocateData.zone_id = ts->ZoneID = m_pp.binds[0].zoneId; + PendingTranslocateData.zone_id = ts->ZoneID = m_pp.binds[0].zone_id; PendingTranslocateData.instance_id = m_pp.binds[0].instance_id; PendingTranslocateData.x = ts->x = m_pp.binds[0].x; PendingTranslocateData.y = ts->y = m_pp.binds[0].y; @@ -4048,9 +3999,9 @@ void Client::SendOPTranslocateConfirm(Mob *Caster, uint16 SpellID) { else { PendingTranslocateData.zone_id = ts->ZoneID = ZoneID(Spell.teleport_zone); PendingTranslocateData.instance_id = 0; - PendingTranslocateData.y = ts->y = Spell.base[0]; - PendingTranslocateData.x = ts->x = Spell.base[1]; - PendingTranslocateData.z = ts->z = Spell.base[2]; + PendingTranslocateData.y = ts->y = Spell.base_value[0]; + PendingTranslocateData.x = ts->x = Spell.base_value[1]; + PendingTranslocateData.z = ts->z = Spell.base_value[2]; PendingTranslocateData.heading = 0.0; } @@ -4923,7 +4874,7 @@ void Client::SendRespawnBinds() BindStruct* b = &m_pp.binds[0]; RespawnOption opt; opt.name = "Bind Location"; - opt.zone_id = b->zoneId; + opt.zone_id = b->zone_id; opt.instance_id = b->instance_id; opt.x = b->x; opt.y = b->y; @@ -5008,7 +4959,7 @@ void Client::HandleLDoNOpen(NPC *target) if(target->GetLDoNTrapSpellID() != 0) { MessageString(Chat::Red, LDON_ACCIDENT_SETOFF2); - target->SpellFinished(target->GetLDoNTrapSpellID(), this, EQ::spells::CastingSlot::Item, 0, -1, spells[target->GetLDoNTrapSpellID()].ResistDiff); + target->SpellFinished(target->GetLDoNTrapSpellID(), this, EQ::spells::CastingSlot::Item, 0, -1, spells[target->GetLDoNTrapSpellID()].resist_difficulty); target->SetLDoNTrapSpellID(0); target->SetLDoNTrapped(false); target->SetLDoNTrapDetected(false); @@ -5130,7 +5081,7 @@ void Client::HandleLDoNDisarm(NPC *target, uint16 skill, uint8 type) break; case -1: MessageString(Chat::Red, LDON_ACCIDENT_SETOFF2); - target->SpellFinished(target->GetLDoNTrapSpellID(), this, EQ::spells::CastingSlot::Item, 0, -1, spells[target->GetLDoNTrapSpellID()].ResistDiff); + target->SpellFinished(target->GetLDoNTrapSpellID(), this, EQ::spells::CastingSlot::Item, 0, -1, spells[target->GetLDoNTrapSpellID()].resist_difficulty); target->SetLDoNTrapSpellID(0); target->SetLDoNTrapped(false); target->SetLDoNTrapDetected(false); @@ -5149,7 +5100,7 @@ void Client::HandleLDoNPickLock(NPC *target, uint16 skill, uint8 type) if(target->IsLDoNTrapped()) { MessageString(Chat::Red, LDON_ACCIDENT_SETOFF2); - target->SpellFinished(target->GetLDoNTrapSpellID(), this, EQ::spells::CastingSlot::Item, 0, -1, spells[target->GetLDoNTrapSpellID()].ResistDiff); + target->SpellFinished(target->GetLDoNTrapSpellID(), this, EQ::spells::CastingSlot::Item, 0, -1, spells[target->GetLDoNTrapSpellID()].resist_difficulty); target->SetLDoNTrapSpellID(0); target->SetLDoNTrapped(false); target->SetLDoNTrapDetected(false); @@ -5320,11 +5271,11 @@ void Client::NotifyNewTitlesAvailable() } -void Client::SetStartZone(uint32 zoneid, float x, float y, float z) +void Client::SetStartZone(uint32 zoneid, float x, float y, float z, float heading) { // setting city to zero allows the player to use /setstartcity to set the city themselves if(zoneid == 0) { - m_pp.binds[4].zoneId = 0; + m_pp.binds[4].zone_id = 0; this->Message(Chat::Yellow,"Your starting city has been reset. Use /setstartcity to choose a new one"); return; } @@ -5334,61 +5285,108 @@ void Client::SetStartZone(uint32 zoneid, float x, float y, float z) if(target_zone_name == nullptr) return; - m_pp.binds[4].zoneId = zoneid; + m_pp.binds[4].zone_id = zoneid; if(zone->GetInstanceID() != 0 && zone->IsInstancePersistent()) { m_pp.binds[4].instance_id = zone->GetInstanceID(); } if (x == 0 && y == 0 && z == 0) { - content_db.GetSafePoints(ZoneName(m_pp.binds[4].zoneId), 0, &m_pp.binds[4].x, &m_pp.binds[4].y, &m_pp.binds[4].z); + content_db.GetSafePoints( + ZoneName(m_pp.binds[4].zone_id), + 0, + &m_pp.binds[4].x, + &m_pp.binds[4].y, + &m_pp.binds[4].z, + &m_pp.binds[4].heading + ); } else { m_pp.binds[4].x = x; m_pp.binds[4].y = y; m_pp.binds[4].z = z; + m_pp.binds[4].heading = heading; } } uint32 Client::GetStartZone() { - return m_pp.binds[4].zoneId; + return m_pp.binds[4].zone_id; } void Client::ShowSkillsWindow() { - const char *WindowTitle = "Skills"; - std::string WindowText; - std::map Skills = EQ::skills::GetSkillTypeMap(); + std::string popup_text; + std::map skills_map = EQ::skills::GetSkillTypeMap(); - if (ClientVersion() < EQ::versions::ClientVersion::RoF2) - Skills[EQ::skills::Skill1HPiercing] = "Piercing"; - - // print out all available skills - for (auto skills_iter : Skills) { - if (skills_iter.first == EQ::skills::Skill2HPiercing && ClientVersion() < EQ::versions::ClientVersion::RoF2) - continue; - if (!GetSkill(skills_iter.first) && !MaxSkill(skills_iter.first)) - continue; - - WindowText += skills_iter.second; - // line up the values - WindowText += "      "; - WindowText += itoa(this->GetSkill(skills_iter.first)); - if (MaxSkill(skills_iter.first) > 0) { - WindowText += "/"; - WindowText += itoa(this->GetMaxSkillAfterSpecializationRules(skills_iter.first, this->MaxSkill(skills_iter.first))); - } - WindowText += "
"; + if (ClientVersion() < EQ::versions::ClientVersion::RoF2) { + skills_map[EQ::skills::Skill1HPiercing] = "Piercing"; } - this->SendPopupToClient(WindowTitle, WindowText.c_str()); + + // Table Start + popup_text += ""; + + for (const auto& skill : skills_map) { + auto skill_id = skill.first; + auto skill_name = skill.second; + auto can_have_skill = CanHaveSkill(skill_id); + auto current_skill = GetSkill(skill_id); + auto max_skill = MaxSkill(skill_id); + auto skill_maxed = current_skill >= max_skill; + if ( + skill_id == EQ::skills::Skill2HPiercing && + ClientVersion() < EQ::versions::ClientVersion::RoF2 + ) { + continue; + } + + if ( + !can_have_skill || + !current_skill || + !max_skill + ) { + continue; + } + + // Row Start + popup_text += ""; + + // Skill Name + popup_text += fmt::format( + "", + skill_name + ); + + // Current Skill Level out of Max Skill Level or a Check Mark for Maxed + popup_text += fmt::format( + "", + ( + skill_maxed ? + "" : + fmt::format( + "{}/{}", + current_skill, + max_skill + ) + ) + ); + + // Row End + popup_text += ""; + } + + // Table End + popup_text += "
{}{}
"; + + SendPopupToClient( + "Skills", + popup_text.c_str() + ); } void Client::Signal(uint32 data) { - char buf[32]; - snprintf(buf, 31, "%d", data); - buf[31] = '\0'; - parse->EventPlayer(EVENT_SIGNAL, this, buf, 0); + std::string export_string = fmt::format("{}", data); + parse->EventPlayer(EVENT_SIGNAL, this, export_string, 0); } void Client::SendRewards() @@ -5536,15 +5534,15 @@ uint32 Client::GetLDoNPointsTheme(uint32 t) { switch(t) { - case 1: + case LDoNThemes::GUK: return m_pp.ldon_points_guk; - case 2: + case LDoNThemes::MIR: return m_pp.ldon_points_mir; - case 3: + case LDoNThemes::MMC: return m_pp.ldon_points_mmc; - case 4: + case LDoNThemes::RUJ: return m_pp.ldon_points_ruj; - case 5: + case LDoNThemes::TAK: return m_pp.ldon_points_tak; default: return 0; @@ -5555,15 +5553,15 @@ uint32 Client::GetLDoNWinsTheme(uint32 t) { switch(t) { - case 1: + case LDoNThemes::GUK: return m_pp.ldon_wins_guk; - case 2: + case LDoNThemes::MIR: return m_pp.ldon_wins_mir; - case 3: + case LDoNThemes::MMC: return m_pp.ldon_wins_mmc; - case 4: + case LDoNThemes::RUJ: return m_pp.ldon_wins_ruj; - case 5: + case LDoNThemes::TAK: return m_pp.ldon_wins_tak; default: return 0; @@ -5574,82 +5572,79 @@ uint32 Client::GetLDoNLossesTheme(uint32 t) { switch(t) { - case 1: + case LDoNThemes::GUK: return m_pp.ldon_losses_guk; - case 2: + case LDoNThemes::MIR: return m_pp.ldon_losses_mir; - case 3: + case LDoNThemes::MMC: return m_pp.ldon_losses_mmc; - case 4: + case LDoNThemes::RUJ: return m_pp.ldon_losses_ruj; - case 5: + case LDoNThemes::TAK: return m_pp.ldon_losses_tak; default: return 0; } } -void Client::UpdateLDoNWins(uint32 t, int32 n) -{ - switch(t) - { - case 1: - m_pp.ldon_wins_guk = n; - break; - case 2: - m_pp.ldon_wins_mir = n; - break; - case 3: - m_pp.ldon_wins_mmc = n; - break; - case 4: - m_pp.ldon_wins_ruj = n; - break; - case 5: - m_pp.ldon_wins_tak = n; - break; - default: - return; - } -} - -void Client::UpdateLDoNLosses(uint32 t, int32 n) -{ - switch(t) - { - case 1: - m_pp.ldon_losses_guk = n; - break; - case 2: - m_pp.ldon_losses_mir = n; - break; - case 3: - m_pp.ldon_losses_mmc = n; - break; - case 4: - m_pp.ldon_losses_ruj = n; - break; - case 5: - m_pp.ldon_losses_tak = n; - break; - default: - return; +void Client::UpdateLDoNWinLoss(uint32 theme_id, bool win, bool remove) { + switch (theme_id) { + case LDoNThemes::GUK: + if (win) { + m_pp.ldon_wins_guk += (remove ? -1 : 1); + } else { + m_pp.ldon_losses_guk += (remove ? -1 : 1); + } + break; + case LDoNThemes::MIR: + if (win) { + m_pp.ldon_wins_mir += (remove ? -1 : 1); + } else { + m_pp.ldon_losses_mir += (remove ? -1 : 1); + } + break; + case LDoNThemes::MMC: + if (win) { + m_pp.ldon_wins_mmc += (remove ? -1 : 1); + } else { + m_pp.ldon_losses_mmc += (remove ? -1 : 1); + } + break; + case LDoNThemes::RUJ: + if (win) { + m_pp.ldon_wins_ruj += (remove ? -1 : 1); + } else { + m_pp.ldon_losses_ruj += (remove ? -1 : 1); + } + break; + case LDoNThemes::TAK: + if (win) { + m_pp.ldon_wins_tak += (remove ? -1 : 1); + } else { + m_pp.ldon_losses_tak += (remove ? -1 : 1); + } + break; + default: + return; } + database.UpdateAdventureStatsEntry(CharacterID(), theme_id, win, remove); } -void Client::SuspendMinion() +void Client::SuspendMinion(int value) { + /* + SPA 151 Allows an extra pet to be saved and resummoned later. + Casting with a pet but without a suspended pet will suspend the pet + Casting without a pet and with a suspended pet will unsuspend the pet + effect value 0 = save pet with no buffs or equipment + effect value 1 = save pet with buffs and equipment + effect value 2 = unknown + Note: SPA 308 allows for suspended pets to be resummoned after zoning. + */ + NPC *CurrentPet = GetPet()->CastToNPC(); - int AALevel = GetAA(aaSuspendedMinion); - - if(AALevel == 0) - return; - - if(GetLevel() < 62) - return; - if(!CurrentPet) { if(m_suspendedminion.SpellID > 0) @@ -5671,7 +5666,7 @@ void Client::SuspendMinion() return; } - if(AALevel >= 2) + if(value >= 1) { CurrentPet->SetPetState(m_suspendedminion.Buffs, m_suspendedminion.Items); @@ -5740,7 +5735,7 @@ void Client::SuspendMinion() m_suspendedminion.petpower = CurrentPet->GetPetPower(); m_suspendedminion.size = CurrentPet->GetSize(); - if(AALevel >= 2) + if(value >= 1) CurrentPet->GetPetState(m_suspendedminion.Buffs, m_suspendedminion.Items, m_suspendedminion.Name); else strn0cpy(m_suspendedminion.Name, CurrentPet->GetName(), 64); // Name stays even at rank 1 @@ -5771,16 +5766,27 @@ void Client::AddPVPPoints(uint32 Points) SendPVPStats(); } -void Client::AddCrystals(uint32 Radiant, uint32 Ebon) +void Client::AddCrystals(uint32 radiant, uint32 ebon) { - m_pp.currentRadCrystals += Radiant; - m_pp.careerRadCrystals += Radiant; - m_pp.currentEbonCrystals += Ebon; - m_pp.careerEbonCrystals += Ebon; + m_pp.currentRadCrystals += radiant; + m_pp.careerRadCrystals += radiant; + m_pp.currentEbonCrystals += ebon; + m_pp.careerEbonCrystals += ebon; SaveCurrency(); SendCrystalCounts(); + + // newer clients handle message client side (older clients likely used eqstr 5967 and 5968, this matches live) + if (radiant > 0) + { + MessageString(Chat::Yellow, YOU_RECEIVE, fmt::format("{} Radiant Crystals", radiant).c_str()); + } + + if (ebon > 0) + { + MessageString(Chat::Yellow, YOU_RECEIVE, fmt::format("{} Ebon Crystals", ebon).c_str()); + } } void Client::SetEbonCrystals(uint32 value) { @@ -6004,7 +6010,7 @@ void Client::NewAdventure(int id, int theme, const char *text, int member_count, void Client::ClearPendingAdventureData() { adv_requested_id = 0; - adv_requested_theme = 0; + adv_requested_theme = LDoNThemes::Unused; safe_delete_array(adv_requested_data); adv_requested_member_count = 0; } @@ -6066,7 +6072,7 @@ void Client::ClearCurrentAdventure() void Client::AdventureFinish(bool win, int theme, int points) { - UpdateLDoNPoints(points, theme); + UpdateLDoNPoints(theme, points); auto outapp = new EQApplicationPacket(OP_AdventureFinish, sizeof(AdventureFinish_Struct)); AdventureFinish_Struct *af = (AdventureFinish_Struct*)outapp->pBuffer; af->win_lose = win ? 1 : 0; @@ -6257,36 +6263,44 @@ void Client::LocateCorpse() void Client::NPCSpawn(NPC *target_npc, const char *identifier, uint32 extra) { - if (!target_npc || !identifier) + if (!target_npc || !identifier) { return; - - std::string id = identifier; - for(int i = 0; i < id.length(); ++i) - { - id[i] = tolower(id[i]); } - if (id == "create") { - // extra tries to create the npc_type ID within the range for the current zone (zone_id * 1000) - content_db.NPCSpawnDB(0, zone->GetShortName(), zone->GetInstanceVersion(), this, target_npc->CastToNPC(), extra); - } - else if (id == "add") { - // extra sets the respawn timer for add - content_db.NPCSpawnDB(1, zone->GetShortName(), zone->GetInstanceVersion(), this, target_npc->CastToNPC(), extra); - } - else if (id == "update") { - content_db.NPCSpawnDB(2, zone->GetShortName(), zone->GetInstanceVersion(), this, target_npc->CastToNPC()); - } - else if (id == "remove") { - content_db.NPCSpawnDB(3, zone->GetShortName(), zone->GetInstanceVersion(), this, target_npc->CastToNPC()); - target_npc->Depop(false); - } - else if (id == "delete") { - content_db.NPCSpawnDB(4, zone->GetShortName(), zone->GetInstanceVersion(), this, target_npc->CastToNPC()); - target_npc->Depop(false); - } - else { - return; + std::string spawn_type = str_tolower(identifier); + bool is_add = spawn_type.find("add") != std::string::npos; + bool is_create = spawn_type.find("create") != std::string::npos; + bool is_delete = spawn_type.find("delete") != std::string::npos; + bool is_remove = spawn_type.find("remove") != std::string::npos; + bool is_update = spawn_type.find("update") != std::string::npos; + if (is_add || is_create) { + // Add: extra tries to create the NPC ID within the range for the current Zone (Zone ID * 1000) + // Create: extra sets the Respawn Timer for add + content_db.NPCSpawnDB( + is_add ? NPCSpawnTypes::AddNewSpawngroup : NPCSpawnTypes::CreateNewSpawn, + zone->GetShortName(), + zone->GetInstanceVersion(), + this, + target_npc->CastToNPC(), + extra + ); + } else if (is_delete || is_remove || is_update) { + uint8 spawn_update_type = ( + is_delete ? + NPCSpawnTypes::DeleteSpawn : + ( + is_remove ? + NPCSpawnTypes::RemoveSpawn : + NPCSpawnTypes::UpdateAppearance + ) + ); + content_db.NPCSpawnDB( + spawn_update_type, + zone->GetShortName(), + zone->GetInstanceVersion(), + this, + target_npc->CastToNPC() + ); } } @@ -6472,11 +6486,12 @@ void Client::Doppelganger(uint16 spell_id, Mob *target, const char *name_overrid swarm_pet_npc->StartSwarmTimer(pet_duration * 1000); swarm_pet_npc->GetSwarmInfo()->owner_id = GetID(); + swarm_pet_npc->SetFollowID(GetID()); // Give the pets alittle more agro than the caster and then agro them on the target target->AddToHateList(swarm_pet_npc, (target->GetHateAmount(this) + 100), (target->GetDamageAmount(this) + 100)); swarm_pet_npc->AddToHateList(target, 1000, 1000); - swarm_pet_npc->GetSwarmInfo()->target = target->GetID(); + swarm_pet_npc->GetSwarmInfo()->target = 0; //we allocated a new NPC type object, give the NPC ownership of that memory if(npc_dup != nullptr) @@ -6911,7 +6926,7 @@ void Client::SendStatsWindow(Client* client, bool use_window) for (auto iter = item_faction_bonuses.begin(); iter != item_faction_bonuses.end(); ++iter) { memset(&faction_buf, 0, sizeof(faction_buf)); - if(!content_db.GetFactionName((int32)((*iter).first), faction_buf, sizeof(faction_buf))) + if(!content_db.GetFactionName((int)((*iter).first), faction_buf, sizeof(faction_buf))) strcpy(faction_buf, "Not in DB"); if((*iter).second > 0) { @@ -7028,7 +7043,7 @@ void Client::SendStatsWindow(Client* client, bool use_window) Extra_Info: client->Message(Chat::White, " BaseRace: %i Gender: %i BaseGender: %i Texture: %i HelmTexture: %i", GetBaseRace(), GetGender(), GetBaseGender(), GetTexture(), GetHelmTexture()); - if (client->Admin() >= 100) { + if (client->Admin() >= AccountStatus::GMAdmin) { client->Message(Chat::White, " CharID: %i EntityID: %i PetID: %i OwnerID: %i AIControlled: %i Targetted: %i", CharacterID(), GetID(), GetPetID(), GetOwnerID(), IsAIControlled(), targeted); } } @@ -7047,23 +7062,16 @@ void Client::SendAltCurrencies() { altc->opcode = ALT_CURRENCY_OP_POPULATE; altc->count = count; - uint32 i = 0; - auto iter = zone->AlternateCurrencies.begin(); - while(iter != zone->AlternateCurrencies.end()) { - const EQ::ItemData* item = database.GetItem((*iter).item_id); - altc->entries[i].currency_number = (*iter).id; - altc->entries[i].unknown00 = 1; - altc->entries[i].currency_number2 = (*iter).id; - altc->entries[i].item_id = (*iter).item_id; - if(item) { - altc->entries[i].item_icon = item->Icon; - altc->entries[i].stack_size = item->StackSize; - } else { - altc->entries[i].item_icon = 1000; - altc->entries[i].stack_size = 1000; - } - i++; - ++iter; + uint32 currency_id = 0; + for (const auto& alternate_currency : zone->AlternateCurrencies) { + const EQ::ItemData* item = database.GetItem(alternate_currency.item_id); + altc->entries[currency_id].currency_number = alternate_currency.id; + altc->entries[currency_id].unknown00 = 1; + altc->entries[currency_id].currency_number2 = alternate_currency.id; + altc->entries[currency_id].item_id = alternate_currency.item_id; + altc->entries[currency_id].item_icon = item ? item->Icon : 1000; + altc->entries[currency_id].stack_size = item ? item->StackSize : 1000; + currency_id++; } FastQueuePacket(&outapp); @@ -7118,10 +7126,8 @@ void Client::AddAlternateCurrencyValue(uint32 currency_id, int32 amount, int8 me void Client::SendAlternateCurrencyValues() { - auto iter = zone->AlternateCurrencies.begin(); - while(iter != zone->AlternateCurrencies.end()) { - SendAlternateCurrencyValue((*iter).id, false); - ++iter; + for (const auto& alternate_currency : zone->AlternateCurrencies) { + SendAlternateCurrencyValue(alternate_currency.id, false); } } @@ -7425,7 +7431,7 @@ void Client::ProcessXTargetAutoHaters() std::queue empty_slots; for (int i = 0; i < GetMaxXTargets(); ++i) { if (XTargets[i].Type != Auto) - continue; + continue; if (XTargets[i].ID != 0 && !GetXTargetAutoMgr()->contains_mob(XTargets[i].ID)) { XTargets[i].ID = 0; @@ -7464,6 +7470,7 @@ void Client::ProcessXTargetAutoHaters() break; } } + m_dirtyautohaters = false; SendXTargetUpdates(); } @@ -7830,7 +7837,7 @@ FACTION_VALUE Client::GetReverseFactionCon(Mob* iOther) { return GetSpecialFactionCon(iOther); if (iOther->GetPrimaryFaction() == 0) - return FACTION_INDIFFERENT; + return FACTION_INDIFFERENTLY; return GetFactionLevel(CharacterID(), 0, GetFactionRace(), GetClass(), GetDeity(), iOther->GetPrimaryFaction(), iOther); } @@ -7853,25 +7860,25 @@ FACTION_VALUE Client::GetFactionLevel(uint32 char_id, uint32 npc_id, uint32 p_ra { if (pFaction < 0) return GetSpecialFactionCon(tnpc); - FACTION_VALUE fac = FACTION_INDIFFERENT; + FACTION_VALUE fac = FACTION_INDIFFERENTLY; int32 tmpFactionValue; FactionMods fmods; // few optimizations if (GetFeigned()) - return FACTION_INDIFFERENT; + return FACTION_INDIFFERENTLY; if(!zone->CanDoCombat()) - return FACTION_INDIFFERENT; + return FACTION_INDIFFERENTLY; if (invisible_undead && tnpc && !tnpc->SeeInvisibleUndead()) - return FACTION_INDIFFERENT; + return FACTION_INDIFFERENTLY; if (IsInvisible(tnpc)) - return FACTION_INDIFFERENT; + return FACTION_INDIFFERENTLY; if (tnpc && tnpc->GetOwnerID() != 0) // pets con amiably to owner and indiff to rest { if (char_id == tnpc->GetOwner()->CastToClient()->CharacterID()) - return FACTION_AMIABLE; + return FACTION_AMIABLY; else - return FACTION_INDIFFERENT; + return FACTION_INDIFFERENTLY; } //First get the NPC's Primary faction @@ -7891,15 +7898,15 @@ FACTION_VALUE Client::GetFactionLevel(uint32 char_id, uint32 npc_id, uint32 p_ra } else { - return(FACTION_INDIFFERENT); + return(FACTION_INDIFFERENTLY); } // merchant fix - if (tnpc && tnpc->IsNPC() && tnpc->CastToNPC()->MerchantType && (fac == FACTION_THREATENLY || fac == FACTION_SCOWLS)) - fac = FACTION_DUBIOUS; + if (tnpc && tnpc->IsNPC() && tnpc->CastToNPC()->MerchantType && (fac == FACTION_THREATENINGLY || fac == FACTION_SCOWLS)) + fac = FACTION_DUBIOUSLY; if (tnpc != 0 && fac != FACTION_SCOWLS && tnpc->CastToNPC()->CheckAggro(this)) - fac = FACTION_THREATENLY; + fac = FACTION_THREATENINGLY; return fac; } @@ -8522,7 +8529,7 @@ void Client::Consume(const EQ::ItemData *item, uint8 type, int16 slot, bool auto LogFood("Consuming food, points added to hunger_level: [{}] - current_hunger: [{}]", increase, m_pp.hunger_level); - DeleteItemInInventory(slot, 1, false); + DeleteItemInInventory(slot, 1); if (!auto_consume) // no message if the client consumed for us entity_list.MessageCloseString(this, true, 50, 0, EATING_MESSAGE, GetName(), item->Name); @@ -8537,7 +8544,7 @@ void Client::Consume(const EQ::ItemData *item, uint8 type, int16 slot, bool auto m_pp.thirst_level += increase; - DeleteItemInInventory(slot, 1, false); + DeleteItemInInventory(slot, 1); LogFood("Consuming drink, points added to thirst_level: [{}] current_thirst: [{}]", increase, m_pp.thirst_level); @@ -8587,14 +8594,25 @@ void Client::ExpeditionSay(const char *str, int ExpID) { return; if(results.RowCount() == 0) { - this->Message(Chat::Lime, "You say to the expedition, '%s'", str); + Message(Chat::Lime, "You say to the expedition, '%s'", str); return; } for(auto row = results.begin(); row != results.end(); ++row) { const char* charName = row[0]; - if(strcmp(charName, this->GetCleanName()) != 0) - worldserver.SendEmoteMessage(charName, 0, 0, 14, "%s says to the expedition, '%s'", this->GetCleanName(), str); + if(strcmp(charName, GetCleanName()) != 0) { + worldserver.SendEmoteMessage( + charName, + 0, + AccountStatus::Player, + Chat::Lime, + fmt::format( + "{} says to the expedition, '{}'", + GetCleanName(), + str + ).c_str() + ); + } // ChannelList->CreateChannel(ChannelName, ChannelOwner, ChannelPassword, true, atoi(row[3])); } @@ -8606,8 +8624,8 @@ void Client::ShowNumHits() uint32 buffcount = GetMaxTotalSlots(); for (uint32 buffslot = 0; buffslot < buffcount; buffslot++) { const Buffs_Struct &curbuff = buffs[buffslot]; - if (curbuff.spellid != SPELL_UNKNOWN && curbuff.numhits) - Message(0, "You have %d hits left on %s", curbuff.numhits, GetSpellName(curbuff.spellid)); + if (curbuff.spellid != SPELL_UNKNOWN && curbuff.hit_number) + Message(0, "You have %d hits left on %s", curbuff.hit_number, GetSpellName(curbuff.spellid)); } return; } @@ -8664,7 +8682,7 @@ void Client::QuestReward(Mob* target, uint32 copper, uint32 silver, uint32 gold, if (faction) { - if (target && target->IsNPC()) + if (target && target->IsNPC() && !target->IsCharmed()) { int32 nfl_id = target->CastToNPC()->GetNPCFactionID(); SetFactionLevel(CharacterID(), nfl_id, GetBaseClass(), GetBaseRace(), GetDeity(), true); @@ -8700,7 +8718,7 @@ void Client::QuestReward(Mob* target, const QuestReward_Struct &reward, bool fac if (faction) { - if (target && target->IsNPC()) + if (target && target->IsNPC() && !target->IsCharmed()) { int32 nfl_id = target->CastToNPC()->GetNPCFactionID(); SetFactionLevel(CharacterID(), nfl_id, GetBaseClass(), GetBaseRace(), GetDeity(), true); @@ -9214,7 +9232,7 @@ void Client::SetDisplayMobInfoWindow(bool display_mob_info_window) bool Client::IsDevToolsEnabled() const { - return dev_tools_enabled && RuleB(World, EnableDevTools); + return dev_tools_enabled && GetGM() && RuleB(World, EnableDevTools); } void Client::SetDevToolsEnabled(bool in_dev_tools_enabled) @@ -9417,7 +9435,7 @@ void Client::CheckVirtualZoneLines() LogZonePoints( "Virtual Zone Box Sending player [{}] to [{}]", GetCleanName(), - zone_store.GetZoneLongName(virtual_zone_point.target_zone_id) + ZoneLongName(virtual_zone_point.target_zone_id) ); } } @@ -9504,12 +9522,16 @@ void Client::SendCrossZoneMessage( } else if (!character_name.empty() && !message.empty()) { - uint32_t pack_size = sizeof(CZMessagePlayer_Struct); - auto pack = std::make_unique(ServerOP_CZMessagePlayer, pack_size); - auto buf = reinterpret_cast(pack->pBuffer); + uint32_t pack_size = sizeof(CZMessage_Struct); + auto pack = std::make_unique(ServerOP_CZMessage, pack_size); + auto buf = reinterpret_cast(pack->pBuffer); + uint8 update_type = CZUpdateType_Character; + int update_identifier = 0; + buf->update_type = update_type; + buf->update_identifier = update_identifier; buf->type = chat_type; - strn0cpy(buf->character_name, character_name.c_str(), sizeof(buf->character_name)); strn0cpy(buf->message, message.c_str(), sizeof(buf->message)); + strn0cpy(buf->client_name, character_name.c_str(), sizeof(buf->client_name)); worldserver.SendPacket(pack.get()); } @@ -9542,7 +9564,7 @@ void Client::SendCrossZoneMessageString( auto buf = reinterpret_cast(pack->pBuffer); buf->string_id = string_id; buf->chat_type = chat_type; - strn0cpy(buf->character_name, character_name.c_str(), sizeof(buf->character_name)); + strn0cpy(buf->client_name, character_name.c_str(), sizeof(buf->client_name)); buf->args_size = args_size; memcpy(buf->args, argument_buffer.buffer(), argument_buffer.size()); @@ -9556,28 +9578,25 @@ void Client::SendCrossZoneMessageString( } } -void Client::UpdateExpeditionInfoAndLockouts() +void Client::SendDynamicZoneUpdates() { - // this is processed by client after entering a zone + // bit inefficient since each do lookups but it avoids duplicating code here SendDzCompassUpdate(); + SetDynamicZoneMemberStatus(DynamicZoneMemberStatus::Online); m_expedition_lockouts = ExpeditionDatabase::LoadCharacterLockouts(CharacterID()); + // expeditions are the only dz type that keep the window updated auto expedition = GetExpedition(); if (expedition) { - expedition->SendClientExpeditionInfo(this); + expedition->GetDynamicZone()->SendClientWindowUpdate(this); // live synchronizes lockouts obtained during the active expedition to // members once they zone into the expedition's dynamic zone instance - if (expedition->GetDynamicZone().IsCurrentZoneDzInstance()) + if (expedition->GetDynamicZone()->IsCurrentZoneDzInstance()) { expedition->SyncCharacterLockouts(CharacterID(), m_expedition_lockouts); - expedition->SetMemberStatus(this, ExpeditionMemberStatus::InDynamicZone); - } - else - { - expedition->SetMemberStatus(this, ExpeditionMemberStatus::Online); } } @@ -9587,18 +9606,29 @@ void Client::UpdateExpeditionInfoAndLockouts() RequestPendingExpeditionInvite(); } -Expedition* Client::CreateExpedition(DynamicZone& dz_instance, ExpeditionRequest& request) +Expedition* Client::CreateExpedition(DynamicZone& dz, bool disable_messages) { - return Expedition::TryCreate(this, dz_instance, request); + return Expedition::TryCreate(this, dz, disable_messages); } Expedition* Client::CreateExpedition( const std::string& zone_name, uint32 version, uint32 duration, const std::string& expedition_name, uint32 min_players, uint32 max_players, bool disable_messages) { - DynamicZone dz_instance{ zone_name, version, duration, DynamicZoneType::Expedition }; - ExpeditionRequest request{ expedition_name, min_players, max_players, disable_messages }; - return Expedition::TryCreate(this, dz_instance, request); + DynamicZone dz{ ZoneID(zone_name), version, duration, DynamicZoneType::Expedition }; + dz.SetName(expedition_name); + dz.SetMinPlayers(min_players); + dz.SetMaxPlayers(max_players); + + return Expedition::TryCreate(this, dz, disable_messages); +} + +void Client::CreateTaskDynamicZone(int task_id, DynamicZone& dz_request) +{ + if (task_state) + { + task_state->CreateTaskDynamicZone(this, task_id, dz_request); + } } Expedition* Client::GetExpedition() const @@ -9918,24 +9948,59 @@ void Client::GoToDzSafeReturnOrBind(const DynamicZone* dynamic_zone) GoToBind(); } +void Client::AddDynamicZoneID(uint32_t dz_id) +{ + auto it = std::find_if(m_dynamic_zone_ids.begin(), m_dynamic_zone_ids.end(), + [&](uint32_t current_dz_id) { return current_dz_id == dz_id; }); + + if (it == m_dynamic_zone_ids.end()) + { + LogDynamicZonesDetail("Adding dz [{}] to client [{}]", dz_id, GetName()); + m_dynamic_zone_ids.push_back(dz_id); + } +} + +void Client::RemoveDynamicZoneID(uint32_t dz_id) +{ + LogDynamicZonesDetail("Removing dz [{}] from client [{}]", dz_id, GetName()); + m_dynamic_zone_ids.erase(std::remove_if(m_dynamic_zone_ids.begin(), m_dynamic_zone_ids.end(), + [&](uint32_t current_dz_id) { return current_dz_id == dz_id; } + ), m_dynamic_zone_ids.end()); +} + std::vector Client::GetDynamicZones(uint32_t zone_id, int zone_version) { std::vector client_dzs; - // check client systems for any associated dynamic zones optionally filtered by zone - Expedition* expedition = GetExpedition(); - if (expedition && - (zone_id == 0 || expedition->GetDynamicZone().GetZoneID() == zone_id) && - (zone_version < 0 || expedition->GetDynamicZone().GetZoneVersion() == zone_version)) + for (uint32_t dz_id : m_dynamic_zone_ids) { - client_dzs.emplace_back(&expedition->GetDynamicZone()); + auto dz = DynamicZone::FindDynamicZoneByID(dz_id); + if (dz && + (zone_id == 0 || dz->GetZoneID() == zone_id) && + (zone_version < 0 || dz->GetZoneVersion() == zone_version)) + { + client_dzs.emplace_back(dz); + } } - // todo: tasks, missions (shared tasks), and quests with an associated dz to zone_id - return client_dzs; } +void Client::SetDynamicZoneMemberStatus(DynamicZoneMemberStatus status) +{ + // sets status on all associated dzs client may have. if client is online + // inside a dz, only that dz has the "In Dynamic Zone" status set + for (auto& dz : GetDynamicZones()) + { + // the rule to disable this status is handled internally by the dz + if (status == DynamicZoneMemberStatus::Online && dz->IsCurrentZoneDzInstance()) + { + status = DynamicZoneMemberStatus::InDynamicZone; + } + dz->SetMemberStatus(CharacterID(), status); + } +} + void Client::MovePCDynamicZone(uint32 zone_id, int zone_version, bool msg_if_invalid) { if (zone_id == 0) @@ -9995,7 +10060,7 @@ void Client::MovePCDynamicZone(const std::string& zone_name, int zone_version, b MovePCDynamicZone(zone_id, zone_version, msg_if_invalid); } -void Client::Fling(float value, float target_x, float target_y, float target_z, bool ignore_los, bool clipping) { +void Client::Fling(float value, float target_x, float target_y, float target_z, bool ignore_los, bool clipping) { BuffFadeByEffect(SE_Levitate); if (CheckLosFN(target_x, target_y, target_z, 6.0f) || ignore_los) { auto outapp_fling = new EQApplicationPacket(OP_Fling, sizeof(fling_struct)); @@ -10004,7 +10069,7 @@ void Client::Fling(float value, float target_x, float target_y, float target_z, flingTo->collision = 0; else flingTo->collision = -1; - + flingTo->travel_time = -1; flingTo->unk3 = 1; flingTo->disable_fall_damage = 1; @@ -10037,7 +10102,7 @@ std::vector Client::GetLearnableDisciplines(uint8 min_level, uint8 max_leve continue; if (spells[spell_id].skill == 52) continue; - if (RuleB(Spells, UseCHAScribeHack) && spells[spell_id].effectid[EFFECT_COUNT - 1] == 10) + if (RuleB(Spells, UseCHAScribeHack) && spells[spell_id].effect_id[EFFECT_COUNT - 1] == 10) continue; if (HasDisciplineLearned(spell_id)) continue; @@ -10059,7 +10124,7 @@ std::vector Client::GetLearnableDisciplines(uint8 min_level, uint8 max_leve if (learnable) { learnable_disciplines.push_back(spell_id); } - } + } return learnable_disciplines; } @@ -10069,7 +10134,7 @@ std::vector Client::GetLearnedDisciplines() { if (IsValidSpell(m_pp.disciplines.values[index])) { learned_disciplines.push_back(m_pp.disciplines.values[index]); } - } + } return learned_disciplines; } @@ -10079,7 +10144,7 @@ std::vector Client::GetMemmedSpells() { if (IsValidSpell(m_pp.mem_spells[index])) { memmed_spells.push_back(m_pp.mem_spells[index]); } - } + } return memmed_spells; } @@ -10103,7 +10168,7 @@ std::vector Client::GetScribeableSpells(uint8 min_level, uint8 max_level) { continue; if (spells[spell_id].skill == 52) continue; - if (RuleB(Spells, UseCHAScribeHack) && spells[spell_id].effectid[EFFECT_COUNT - 1] == 10) + if (RuleB(Spells, UseCHAScribeHack) && spells[spell_id].effect_id[EFFECT_COUNT - 1] == 10) continue; if (HasSpellScribed(spell_id)) continue; @@ -10125,7 +10190,7 @@ std::vector Client::GetScribeableSpells(uint8 min_level, uint8 max_level) { if (scribeable) { scribeable_spells.push_back(spell_id); } - } + } return scribeable_spells; } @@ -10135,7 +10200,7 @@ std::vector Client::GetScribedSpells() { if (IsValidSpell(m_pp.spell_book[index])) { scribed_spells.push_back(m_pp.spell_book[index]); } - } + } return scribed_spells; } @@ -10162,3 +10227,676 @@ void Client::SetAFK(uint8 afk_flag) { entity_list.QueueClients(this, outapp); safe_delete(outapp); } + +void Client::SendToInstance(std::string instance_type, std::string zone_short_name, uint32 instance_version, float x, float y, float z, float heading, std::string instance_identifier, uint32 duration) { + uint32 zone_id = ZoneID(zone_short_name); + std::string current_instance_type = str_tolower(instance_type); + std::string instance_type_name = "public"; + if (current_instance_type.find("solo") != std::string::npos) { + instance_type_name = GetCleanName(); + } else if (current_instance_type.find("group") != std::string::npos) { + uint32 group_id = (GetGroup() ? GetGroup()->GetID() : 0); + instance_type_name = itoa(group_id); + } else if (current_instance_type.find("raid") != std::string::npos) { + uint32 raid_id = (GetRaid() ? GetRaid()->GetID() : 0); + instance_type_name = itoa(raid_id); + } else if (current_instance_type.find("guild") != std::string::npos) { + uint32 guild_id = (GuildID() > 0 ? GuildID() : 0); + instance_type_name = itoa(guild_id); + } + + std::string full_bucket_name = fmt::format( + "{}_{}_{}_{}", + current_instance_type, + instance_type_name, + instance_identifier, + zone_short_name + ); + std::string current_bucket_value = DataBucket::GetData(full_bucket_name); + uint16 instance_id = 0; + + if (current_bucket_value.length() > 0) { + instance_id = atoi(current_bucket_value.c_str()); + } else { + if(!database.GetUnusedInstanceID(instance_id)) { + Message(Chat::White, "Server was unable to find a free instance id."); + return; + } + + if(!database.CreateInstance(instance_id, zone_id, instance_version, duration)) { + Message(Chat::White, "Server was unable to create a new instance."); + return; + } + + DataBucket::SetData(full_bucket_name, itoa(instance_id), itoa(duration)); + } + + AssignToInstance(instance_id); + MovePC(zone_id, instance_id, x, y, z, heading); +} + +int Client::CountItem(uint32 item_id) +{ + int quantity = 0; + EQ::ItemInstance *item = nullptr; + static const int16 slots[][2] = { + { EQ::invslot::POSSESSIONS_BEGIN, EQ::invslot::POSSESSIONS_END }, + { EQ::invbag::GENERAL_BAGS_BEGIN, EQ::invbag::GENERAL_BAGS_END }, + { EQ::invbag::CURSOR_BAG_BEGIN, EQ::invbag::CURSOR_BAG_END}, + { EQ::invslot::BANK_BEGIN, EQ::invslot::BANK_END }, + { EQ::invbag::BANK_BAGS_BEGIN, EQ::invbag::BANK_BAGS_END }, + { EQ::invslot::SHARED_BANK_BEGIN, EQ::invslot::SHARED_BANK_END }, + { EQ::invbag::SHARED_BANK_BAGS_BEGIN, EQ::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 = GetInv().GetItem(slot_id); + if (item && item->GetID() == item_id) { + quantity += (item->IsStackable() ? item->GetCharges() : 1); + } + } + } + + return quantity; +} + +void Client::RemoveItem(uint32 item_id, uint32 quantity) +{ + EQ::ItemInstance *item = nullptr; + static const int16 slots[][2] = { + { EQ::invslot::POSSESSIONS_BEGIN, EQ::invslot::POSSESSIONS_END }, + { EQ::invbag::GENERAL_BAGS_BEGIN, EQ::invbag::GENERAL_BAGS_END }, + { EQ::invbag::CURSOR_BAG_BEGIN, EQ::invbag::CURSOR_BAG_END}, + { EQ::invslot::BANK_BEGIN, EQ::invslot::BANK_END }, + { EQ::invbag::BANK_BAGS_BEGIN, EQ::invbag::BANK_BAGS_END }, + { EQ::invslot::SHARED_BANK_BEGIN, EQ::invslot::SHARED_BANK_END }, + { EQ::invbag::SHARED_BANK_BAGS_BEGIN, EQ::invbag::SHARED_BANK_BAGS_END }, + }; + int16 removed_count = 0; + 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) { + if (removed_count == quantity) { + break; + } + + item = GetInv().GetItem(slot_id); + if (item && item->GetID() == item_id) { + int16 charges = item->IsStackable() ? item->GetCharges() : 0; + int16 stack_size = std::max(charges, static_cast(1)); + if ((removed_count + stack_size) <= quantity) { + removed_count += stack_size; + DeleteItemInInventory(slot_id, charges, true); + } else { + int16 amount_left = (quantity - removed_count); + if (amount_left > 0 && stack_size >= amount_left) { + removed_count += amount_left; + DeleteItemInInventory(slot_id, amount_left, true); + } + } + } + } + } +} + +void Client::SetGMStatus(int newStatus) { + if (this->Admin() != newStatus) + database.UpdateGMStatus(this->AccountID(), newStatus); +} + +void Client::ApplyWeaponsStance() +{ + /* + + If you have a weapons stance bonus from at least one bonus type, each time you change weapons this function will ensure the correct + associated buffs are applied, and previous buff is removed. If your weapon stance bonus is completely removed it will, ensure buff is + also removed (ie, removing an item that has worn effect with weapon stance, or clicking off a buff). If client no longer has/never had + any spells/item/aa bonuses with weapon stance effect this function will only do a simple bool check. + + Note: Live like behavior is once you have the triggered buff you can manually click it off to remove it. Swaping any items in inventory will + reapply it automatically. + + Only buff spells should be used as triggered spell effect. IsBuffSpell function also checks spell id validity. + WeaponStance bonus arrary: 0=2H Weapon 1=Shield 2=Dualweild + + Toggling ON or OFF + - From spells, just remove the Primary buff that contains the WeaponStance effect in it. + - For items with worn effect, unequip the item. + - For AA abilities, a hotkey is used to Enable and Disable the effect. See. Client::TogglePassiveAlternativeAdvancement in aa.cpp for extensive details. + + Rank + - Most important for AA, but if you have more than one of WeaponStance effect for a given type, the spell trigger buff will apply whatever has the highest + 'rank' value from the spells table. AA's on live for this effect naturally do this. Be awere of this if making custom spells/worn effects/AA. + + When creating weapon stance effects, you do not need to use all three types. For example, can make an effect where you only get a buff from equiping shield. + + */ + + if (!IsWeaponStanceEnabled()) { + return; + } + + bool enabled = false; + bool item_bonus_exists = false; + bool aa_bonus_exists = false; + + if (weaponstance.spellbonus_enabled) { + + if (spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_2H] || spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD] || + spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD]) { + + enabled = true; + + // Check if no longer has correct combination of weapon type and buff, if so remove buff. + if (!HasTwoHanderEquipped() && IsBuffSpell(spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_2H]) && + FindBuff(spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_2H])) { + BuffFadeBySpellID(spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_2H]); + } + else if (!HasShieldEquiped() && IsBuffSpell(spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD]) && + FindBuff(spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD])) { + BuffFadeBySpellID(spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD]); + } + else if (!HasDualWeaponsEquiped() && + IsBuffSpell(spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD]) && + FindBuff(spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD])) { + BuffFadeBySpellID(spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD]); + } + // If you have correct combination of weapon type and bonus, and do not already have buff, then apply buff. + if (HasTwoHanderEquipped() && IsBuffSpell(spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_2H])) { + if (!FindBuff(spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_2H])) { + SpellOnTarget(spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_2H], this); + } + weaponstance.spellbonus_buff_spell_id = spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_2H]; + } + else if (HasShieldEquiped() && IsBuffSpell(spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD])) { + + if (!FindBuff(spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD])) { + SpellOnTarget(spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD], this); + } + weaponstance.spellbonus_buff_spell_id = spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD]; + } + else if (HasDualWeaponsEquiped() && IsBuffSpell(spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD])) { + + if (!FindBuff(spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD])) { + SpellOnTarget(spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD], this); + } + weaponstance.spellbonus_buff_spell_id = spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD]; + } + } + } + + // Spellbonus effect removal is checked in BuffFadeBySlot(int slot, bool iRecalcBonuses) in spell_effects.cpp when the buff is clicked off or fades. + + if (weaponstance.itembonus_enabled) { + + if (itembonuses.WeaponStance[WEAPON_STANCE_TYPE_2H] || itembonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD] || + itembonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD]) { + + enabled = true; + item_bonus_exists = true; + + + // Edge case check if have multiple items with WeaponStance worn effect. Make sure correct buffs are applied if items are removed but others left on. + if (weaponstance.itembonus_buff_spell_id) { + + bool buff_desync = true; + if (weaponstance.itembonus_buff_spell_id == itembonuses.WeaponStance[WEAPON_STANCE_TYPE_2H] || + weaponstance.itembonus_buff_spell_id == itembonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD] || + (weaponstance.itembonus_buff_spell_id == itembonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD])) { + buff_desync = false; + } + + if (buff_desync) { + int fade_spell = weaponstance.itembonus_buff_spell_id; + weaponstance.itembonus_buff_spell_id = 0; //Need to zero this before we fade to prevent any recursive loops. + BuffFadeBySpellID(fade_spell); + } + } + + // Check if no longer has correct combination of weapon type and buff, if so remove buff. + if (!HasTwoHanderEquipped() && IsBuffSpell(itembonuses.WeaponStance[WEAPON_STANCE_TYPE_2H]) && + FindBuff(itembonuses.WeaponStance[WEAPON_STANCE_TYPE_2H])) { + BuffFadeBySpellID(itembonuses.WeaponStance[WEAPON_STANCE_TYPE_2H]); + } + else if (!HasShieldEquiped() && IsBuffSpell(itembonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD]) && + FindBuff(itembonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD])) { + BuffFadeBySpellID(itembonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD]); + } + else if (!HasDualWeaponsEquiped() && IsBuffSpell(itembonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD]) && + FindBuff(itembonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD])) { + BuffFadeBySpellID(itembonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD]); + } + + // If you have correct combination of weapon type and bonus, and do not already have buff, then apply buff. + if (HasTwoHanderEquipped() && IsBuffSpell(itembonuses.WeaponStance[WEAPON_STANCE_TYPE_2H])) { + + if (!FindBuff(itembonuses.WeaponStance[WEAPON_STANCE_TYPE_2H])) { + SpellOnTarget(itembonuses.WeaponStance[WEAPON_STANCE_TYPE_2H], this); + } + weaponstance.itembonus_buff_spell_id = itembonuses.WeaponStance[WEAPON_STANCE_TYPE_2H]; + } + else if (HasShieldEquiped() && IsBuffSpell(itembonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD])) { + + if (!FindBuff(itembonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD])) { + SpellOnTarget(itembonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD], this); + } + weaponstance.itembonus_buff_spell_id = itembonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD]; + } + else if (HasDualWeaponsEquiped() && IsBuffSpell(itembonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD])) { + if (!FindBuff(itembonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD])) { + SpellOnTarget(itembonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD], this); + } + weaponstance.itembonus_buff_spell_id = itembonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD]; + } + } + } + + // Itembonus effect removal when item is removed + if (!item_bonus_exists && weaponstance.itembonus_enabled) { + weaponstance.itembonus_enabled = false; + + if (weaponstance.itembonus_buff_spell_id) { + BuffFadeBySpellID(weaponstance.itembonus_buff_spell_id); + weaponstance.itembonus_buff_spell_id = 0; + } + } + + if (weaponstance.aabonus_enabled) { + + if (aabonuses.WeaponStance[WEAPON_STANCE_TYPE_2H] || aabonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD] || + aabonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD]) { + + enabled = true; + aa_bonus_exists = true; + + //Check if no longer has correct combination of weapon type and buff, if so remove buff. + if (!HasTwoHanderEquipped() && IsBuffSpell(aabonuses.WeaponStance[WEAPON_STANCE_TYPE_2H]) && + FindBuff(aabonuses.WeaponStance[WEAPON_STANCE_TYPE_2H])) { + BuffFadeBySpellID(aabonuses.WeaponStance[WEAPON_STANCE_TYPE_2H]); + } + + else if (!HasShieldEquiped() && IsBuffSpell(aabonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD]) && + FindBuff(aabonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD])) { + BuffFadeBySpellID(aabonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD]); + } + + else if (!HasDualWeaponsEquiped() && IsBuffSpell(aabonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD]) && + FindBuff(aabonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD])) { + BuffFadeBySpellID(aabonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD]); + } + + //If you have correct combination of weapon type and bonus, and do not already have buff, then apply buff. + if (HasTwoHanderEquipped() && IsBuffSpell(aabonuses.WeaponStance[WEAPON_STANCE_TYPE_2H])) { + if (!FindBuff(aabonuses.WeaponStance[WEAPON_STANCE_TYPE_2H])) { + SpellOnTarget(aabonuses.WeaponStance[WEAPON_STANCE_TYPE_2H], this); + } + weaponstance.aabonus_buff_spell_id = aabonuses.WeaponStance[WEAPON_STANCE_TYPE_2H]; + } + + else if (HasShieldEquiped() && IsBuffSpell(aabonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD])) { + if (!FindBuff(aabonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD])) { + SpellOnTarget(aabonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD], this); + } + weaponstance.aabonus_buff_spell_id = aabonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD]; + } + + else if (HasDualWeaponsEquiped() && IsBuffSpell(aabonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD])) { + + if (!FindBuff(aabonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD])) { + SpellOnTarget(aabonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD], this); + } + weaponstance.aabonus_buff_spell_id = aabonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD]; + } + } + } + + // AA bonus removal is checked in TogglePassiveAA in aa.cpp. when the hot key is toggled. + + // If no bonuses remain present, prevent additional future checks until new bonus is applied. + if (!enabled) { + SetWeaponStanceEnabled(false); + weaponstance.aabonus_enabled = false; + weaponstance.itembonus_enabled = false; + weaponstance.spellbonus_enabled = false; + } +} + +uint16 Client::GetDoorToolEntityId() const +{ + return m_door_tool_entity_id; +} + +void Client::SetDoorToolEntityId(uint16 door_tool_entity_id) +{ + Client::m_door_tool_entity_id = door_tool_entity_id; +} + +int Client::GetIPExemption() +{ + return database.GetIPExemption(GetIPString()); +} + +std::string Client::GetIPString() +{ + in_addr client_ip{}; + client_ip.s_addr = GetIP(); + return inet_ntoa(client_ip); +} + +void Client::SetIPExemption(int exemption_amount) +{ + database.SetIPExemption(GetIPString(), exemption_amount); +} + +void Client::ReadBookByName(std::string book_name, uint8 book_type) +{ + int16 book_language = 0; + std::string book_text = content_db.GetBook(book_name.c_str(), &book_language); + int length = book_text.length(); + + if (book_text[0] != '\0') { + LogDebug("Client::ReadBookByName() Book Name: [{}] Text: [{}]", book_name, book_text.c_str()); + auto outapp = new EQApplicationPacket(OP_ReadBook, length + sizeof(BookText_Struct)); + BookText_Struct *out = (BookText_Struct *) outapp->pBuffer; + out->window = 0xFF; + out->type = book_type; + out->invslot = 0; + + memcpy(out->booktext, book_text.c_str(), length); + + if (book_language > 0 && book_language < MAX_PP_LANGUAGE) { + if (m_pp.languages[book_language] < 100) { + GarbleMessage(out->booktext, (100 - m_pp.languages[book_language])); + } + } + + QueuePacket(outapp); + safe_delete(outapp); + } +} + +// this will fetch raid clients if exists +// fallback to group if raid doesn't exist +// fallback to self if group doesn't exist +std::vector Client::GetPartyMembers() +{ + // get clients to update + std::vector clients_to_update = {}; + + // raid + Raid *raid = entity_list.GetRaidByClient(this); + if (raid) { + for (auto &e : raid->members) { + if (e.member && e.member->IsClient()) { + clients_to_update.push_back(e.member->CastToClient()); + } + } + } + + // group + if (clients_to_update.empty()) { + Group *group = entity_list.GetGroupByClient(this); + if (group) { + for (auto &m : group->members) { + if (m && m->IsClient()) { + clients_to_update.push_back(m->CastToClient()); + } + } + } + } + + // solo + if (clients_to_update.empty()) { + clients_to_update.push_back(this); + } + + return clients_to_update; +} + +void Client::SummonBaggedItems(uint32 bag_item_id, const std::vector& bag_items) +{ + if (bag_items.empty()) + { + return; + } + + // todo: maybe some common functions for SE_SummonItem and SE_SummonItemIntoBag + + const EQ::ItemData* bag_item = database.GetItem(bag_item_id); + if (!bag_item) + { + Message(Chat::Red, fmt::format("Unable to summon item [{}]. Item not found.", bag_item_id).c_str()); + return; + } + + if (CheckLoreConflict(bag_item)) + { + DuplicateLoreMessage(bag_item_id); + return; + } + + int bag_item_charges = 1; // just summoning a single bag + EQ::ItemInstance* summoned_bag = database.CreateItem(bag_item_id, bag_item_charges); + if (!summoned_bag || !summoned_bag->IsClassBag()) + { + Message(Chat::Red, fmt::format("Failed to summon bag item [{}]", bag_item_id).c_str()); + safe_delete(summoned_bag); + return; + } + + for (const auto& item : bag_items) + { + uint8 open_slot = summoned_bag->FirstOpenSlot(); + if (open_slot == 0xff) + { + Message(Chat::Red, "Attempting to summon item in to bag, but there is no room in the summoned bag!"); + break; + } + + const EQ::ItemData* current_item = database.GetItem(item.item_id); + + if (CheckLoreConflict(current_item)) + { + DuplicateLoreMessage(item.item_id); + } + else + { + EQ::ItemInstance* summoned_bag_item = database.CreateItem( + item.item_id, + item.charges, + item.aug_1, + item.aug_2, + item.aug_3, + item.aug_4, + item.aug_5, + item.aug_6, + item.attuned + ); + if (summoned_bag_item) + { + summoned_bag->PutItem(open_slot, *summoned_bag_item); + safe_delete(summoned_bag_item); + } + } + } + + PushItemOnCursor(*summoned_bag); + SendItemPacket(EQ::invslot::slotCursor, summoned_bag, ItemPacketLimbo); + safe_delete(summoned_bag); +} + +void Client::SaveSpells() +{ + std::vector character_spells = {}; + + for (int index = 0; index < EQ::spells::SPELLBOOK_SIZE; index++) { + if (IsValidSpell(m_pp.spell_book[index])) { + auto spell = CharacterSpellsRepository::NewEntity(); + spell.id = CharacterID(); + spell.slot_id = index; + spell.spell_id = m_pp.spell_book[index]; + character_spells.emplace_back(spell); + } + } + + CharacterSpellsRepository::DeleteWhere(database, fmt::format("id = {}", CharacterID())); + + if (!character_spells.empty()) { + CharacterSpellsRepository::InsertMany(database, character_spells); + } +} + +void Client::SaveDisciplines() +{ + std::vector character_discs = {}; + + for (int index = 0; index < MAX_PP_DISCIPLINES; index++) { + if (IsValidSpell(m_pp.disciplines.values[index])) { + auto discipline = CharacterDisciplinesRepository::NewEntity(); + discipline.id = CharacterID(); + discipline.slot_id = index; + discipline.disc_id = m_pp.disciplines.values[index]; + character_discs.emplace_back(discipline); + } + } + + CharacterDisciplinesRepository::DeleteWhere(database, fmt::format("id = {}", CharacterID())); + + if (!character_discs.empty()) { + CharacterDisciplinesRepository::InsertMany(database, character_discs); + } +} + +uint16 Client::ScribeSpells(uint8 min_level, uint8 max_level) +{ + int available_book_slot = GetNextAvailableSpellBookSlot(); + std::vector spell_ids = GetScribeableSpells(min_level, max_level); + uint16 spell_count = spell_ids.size(); + uint16 scribed_spells = 0; + if (spell_count > 0) { + for (auto spell_id : spell_ids) { + if (available_book_slot == -1) { + Message( + Chat::Red, + fmt::format( + "Unable to scribe {} ({}) to Spell Book because your Spell Book is full.", + spells[spell_id].name, + spell_id + ).c_str() + ); + break; + } + + if (HasSpellScribed(spell_id)) { + continue; + } + + // defer saving per spell and bulk save at the end + ScribeSpell(spell_id, available_book_slot, true, true); + available_book_slot = GetNextAvailableSpellBookSlot(available_book_slot); + scribed_spells++; + } + } + + if (scribed_spells > 0) { + std::string spell_message = ( + scribed_spells == 1 ? + "a new spell" : + fmt::format("{} new spells", scribed_spells) + ); + Message(Chat::White, fmt::format("You have learned {}!", spell_message).c_str()); + + // bulk insert spells + SaveSpells(); + } + return scribed_spells; +} + +uint16 Client::LearnDisciplines(uint8 min_level, uint8 max_level) +{ + int available_discipline_slot = GetNextAvailableDisciplineSlot(); + int character_id = CharacterID(); + std::vector spell_ids = GetLearnableDisciplines(min_level, max_level); + uint16 discipline_count = spell_ids.size(); + uint16 learned_disciplines = 0; + if (discipline_count > 0) { + for (auto spell_id : spell_ids) { + if (available_discipline_slot == -1) { + Message( + Chat::Red, + fmt::format( + "Unable to learn {} ({}) because your Discipline slots are full.", + spells[spell_id].name, + spell_id + ).c_str() + ); + break; + } + + if (HasDisciplineLearned(spell_id)) { + continue; + } + + GetPP().disciplines.values[available_discipline_slot] = spell_id; + available_discipline_slot = GetNextAvailableDisciplineSlot(available_discipline_slot); + learned_disciplines++; + } + } + + if (learned_disciplines > 0) { + std::string discipline_message = ( + learned_disciplines == 1 ? + "a new discipline" : + fmt::format("{} new disciplines", learned_disciplines) + ); + Message(Chat::White, fmt::format("You have learned {}!", discipline_message).c_str()); + SendDisciplineUpdate(); + SaveDisciplines(); + } + + return learned_disciplines; +} + +uint16 Client::GetClassTrackingDistanceMultiplier(uint16 class_) { + switch (class_) { + case WARRIOR: + return RuleI(Character, WarriorTrackingDistanceMultiplier); + case CLERIC: + return RuleI(Character, ClericTrackingDistanceMultiplier); + case PALADIN: + return RuleI(Character, PaladinTrackingDistanceMultiplier); + case RANGER: + return RuleI(Character, RangerTrackingDistanceMultiplier); + case SHADOWKNIGHT: + return RuleI(Character, ShadowKnightTrackingDistanceMultiplier); + case DRUID: + return RuleI(Character, DruidTrackingDistanceMultiplier); + case MONK: + return RuleI(Character, MonkTrackingDistanceMultiplier); + case BARD: + return RuleI(Character, BardTrackingDistanceMultiplier); + case ROGUE: + return RuleI(Character, RogueTrackingDistanceMultiplier); + case SHAMAN: + return RuleI(Character, ShamanTrackingDistanceMultiplier); + case NECROMANCER: + return RuleI(Character, NecromancerTrackingDistanceMultiplier); + case WIZARD: + return RuleI(Character, WizardTrackingDistanceMultiplier); + case MAGICIAN: + return RuleI(Character, MagicianTrackingDistanceMultiplier); + case ENCHANTER: + return RuleI(Character, EnchanterTrackingDistanceMultiplier); + case BEASTLORD: + return RuleI(Character, BeastlordTrackingDistanceMultiplier); + case BERSERKER: + return RuleI(Character, BerserkerTrackingDistanceMultiplier); + default: + return 0; + } +} + +bool Client::CanThisClassTrack() { + return (GetClassTrackingDistanceMultiplier(GetClass()) > 0) ? true : false; +} diff --git a/zone/client.h b/zone/client.h index d2abf3efb..6b8c1e1a1 100644 --- a/zone/client.h +++ b/zone/client.h @@ -66,6 +66,7 @@ namespace EQ #include "zone_store.h" #include "task_manager.h" #include "task_client_state.h" +#include "cheat_manager.h" #ifdef _WINDOWS // since windows defines these within windef.h (which windows.h include) @@ -79,6 +80,7 @@ namespace EQ #include #include #include +#include #define CLIENT_TIMEOUT 90000 @@ -120,17 +122,6 @@ typedef enum { EvacToSafeCoords } ZoneMode; -typedef enum { - MQWarp, - MQWarpShadowStep, - MQWarpKnockBack, - MQWarpLight, - MQZone, - MQZoneUnknownDest, - MQGate, - MQGhost -} CheatTypes; - enum { HideCorpseNone = 0, HideCorpseAll = 1, @@ -212,7 +203,11 @@ enum eInnateSkill { InnateDisabled = 255 }; -const uint32 POPUPID_UPDATE_SHOWSTATSWINDOW = 1000000; +const std::string DIAWIND_RESPONSE_ONE_KEY = "diawind_npc_response_one"; +const std::string DIAWIND_RESPONSE_TWO_KEY = "diawind_npc_response_two"; +const uint32 POPUPID_DIAWIND_ONE = 99999; +const uint32 POPUPID_DIAWIND_TWO = 100000; +const uint32 POPUPID_UPDATE_SHOWSTATSWINDOW = 1000000; struct ClientReward { @@ -306,8 +301,8 @@ public: uint16 FindTraderItem(int32 SerialNumber,uint16 Quantity); uint32 FindTraderItemSerialNumber(int32 ItemID); EQ::ItemInstance* FindTraderItemBySerialNumber(int32 SerialNumber); - void FindAndNukeTraderItem(int32 item_id,uint16 quantity,Client* customer,uint16 traderslot); - void NukeTraderItem(uint16 slot, int16 charges, uint16 quantity, Client* customer, uint16 traderslot, int32 uniqueid, int32 itemid = 0); + void FindAndNukeTraderItem(int32 item_id,int16 quantity,Client* customer,uint16 traderslot); + void NukeTraderItem(uint16 slot, int16 charges, int16 quantity, Client* customer, uint16 traderslot, int32 uniqueid, int32 itemid = 0); void ReturnTraderReq(const EQApplicationPacket* app,int16 traderitemcharges, uint32 itemid = 0); void TradeRequestFailed(const EQApplicationPacket* app); void BuyTraderItem(TraderBuy_Struct* tbs,Client* trader,const EQApplicationPacket* app); @@ -345,6 +340,9 @@ public: bool GetRevoked() const { return revoked; } void SetRevoked(bool rev) { revoked = rev; } inline uint32 GetIP() const { return ip; } + std::string GetIPString(); + int GetIPExemption(); + void SetIPExemption(int exemption_amount); inline bool GetHideMe() const { return gm_hide_me; } void SetHideMe(bool hm); inline uint16 GetPort() const { return port; } @@ -394,6 +392,7 @@ public: void Duck(); void Stand(); + void Sit(); virtual void SetMaxHP(); int32 LevelRegen(); @@ -426,7 +425,7 @@ public: inline const float GetBindY(uint32 index = 0) const { return m_pp.binds[index].y; } inline const float GetBindZ(uint32 index = 0) const { return m_pp.binds[index].z; } inline const float GetBindHeading(uint32 index = 0) const { return m_pp.binds[index].heading; } - inline uint32 GetBindZoneID(uint32 index = 0) const { return m_pp.binds[index].zoneId; } + inline uint32 GetBindZoneID(uint32 index = 0) const { return m_pp.binds[index].zone_id; } inline uint32 GetBindInstanceID(uint32 index = 0) const { return m_pp.binds[index].instance_id; } int32 CalcMaxMana(); int32 CalcBaseMana(); @@ -550,7 +549,6 @@ public: inline virtual int32 GetDelayDeath() const { return aabonuses.DelayDeath + spellbonuses.DelayDeath + itembonuses.DelayDeath + 11; } int32 GetActSpellCost(uint16 spell_id, int32); - int32 GetActSpellCasttime(uint16 spell_id, int32); virtual bool CheckFizzle(uint16 spell_id); virtual bool CheckSpellLevelRestriction(uint16 spell_id); virtual int GetCurrentBuffSlots() const; @@ -600,7 +598,12 @@ public: inline uint32 GetEXP() const { return m_pp.exp; } - bool UpdateLDoNPoints(int32 points, uint32 theme); + inline double GetAAEXPModifier(uint32 zone_id) const { return database.GetAAEXPModifier(CharacterID(), zone_id); }; + inline double GetEXPModifier(uint32 zone_id) const { return database.GetEXPModifier(CharacterID(), zone_id); }; + inline void SetAAEXPModifier(uint32 zone_id, double aa_modifier) { database.SetAAEXPModifier(CharacterID(), zone_id, aa_modifier); }; + inline void SetEXPModifier(uint32 zone_id, double exp_modifier) { database.SetEXPModifier(CharacterID(), zone_id, exp_modifier); }; + + bool UpdateLDoNPoints(uint32 theme_id, int points); void SetPVPPoints(uint32 Points) { m_pp.PVPCurrentPoints = Points; } uint32 GetPVPPoints() { return m_pp.PVPCurrentPoints; } void AddPVPPoints(uint32 Points); @@ -644,7 +647,8 @@ public: void GoToSafeCoords(uint16 zone_id, uint16 instance_id); void Gate(uint8 bindnum = 0); void SetBindPoint(int bind_num = 0, int to_zone = -1, int to_instance = 0, const glm::vec3& location = glm::vec3()); - void SetStartZone(uint32 zoneid, float x = 0.0f, float y =0.0f, float z = 0.0f); + void SetBindPoint2(int bind_num = 0, int to_zone = -1, int to_instance = 0, const glm::vec4& location = glm::vec4()); + void SetStartZone(uint32 zoneid, float x = 0.0f, float y =0.0f, float z = 0.0f, float heading = 0.0f); uint32 GetStartZone(void); void MovePC(const char* zonename, float x, float y, float z, float heading, uint8 ignorerestrictions = 0, ZoneMode zm = ZoneSolicited); void MovePC(uint32 zoneID, float x, float y, float z, float heading, uint8 ignorerestrictions = 0, ZoneMode zm = ZoneSolicited); @@ -657,6 +661,7 @@ public: void MoveZoneInstanceGroup(uint16 instance_id); void MoveZoneInstanceRaid(uint16 instance_id); void SendToGuildHall(); + void SendToInstance(std::string instance_type, std::string zone_short_name, uint32 instance_version, float x, float y, float z, float heading, std::string instance_identifier, uint32 duration); void AssignToInstance(uint16 instance_id); void RemoveFromInstance(uint16 instance_id); void WhoAll(); @@ -726,6 +731,7 @@ public: void Stun(int duration); void UnStun(); void ReadBook(BookRequest_Struct *book); + void ReadBookByName(std::string book_name, uint8 book_type); void QuestReadBook(const char* text, uint8 type); void SendClientMoneyUpdate(uint8 type,uint32 amount); void SendMoneyUpdate(); @@ -777,27 +783,44 @@ public: void GMKill(); inline bool IsMedding() const {return medding;} - inline uint16 GetDuelTarget() const { return duel_target; } + inline uint32 GetDuelTarget() const { return duel_target; } inline bool IsDueling() const { return duelaccepted; } - inline void SetDuelTarget(uint16 set_id) { duel_target=set_id; } + inline void SetDuelTarget(uint32 set_id) { duel_target = set_id; } inline void SetDueling(bool duel) { duelaccepted = duel; } // use this one instead void MemSpell(uint16 spell_id, int slot, bool update_client = true); void UnmemSpell(int slot, bool update_client = true); void UnmemSpellBySpellID(int32 spell_id); void UnmemSpellAll(bool update_client = true); + int FindEmptyMemSlot(); uint16 FindMemmedSpellBySlot(int slot); + int FindMemmedSpellBySpellID(uint16 spell_id); int MemmedCount(); std::vector GetLearnableDisciplines(uint8 min_level = 1, uint8 max_level = 0); std::vector GetLearnedDisciplines(); std::vector GetMemmedSpells(); std::vector GetScribeableSpells(uint8 min_level = 1, uint8 max_level = 0); std::vector GetScribedSpells(); - void ScribeSpell(uint16 spell_id, int slot, bool update_client = true); - void UnscribeSpell(int slot, bool update_client = true); + // defer save used when bulk saving + void ScribeSpell(uint16 spell_id, int slot, bool update_client = true, bool defer_save = false); + void SaveSpells(); + void SaveDisciplines(); + + // Bulk Scribe/Learn + uint16 ScribeSpells(uint8 min_level, uint8 max_level); + uint16 LearnDisciplines(uint8 min_level, uint8 max_level); + + // Configurable Tracking Skill + uint16 GetClassTrackingDistanceMultiplier(uint16 class_); + + bool CanThisClassTrack(); + + // defer save used when bulk saving + void UnscribeSpell(int slot, bool update_client = true, bool defer_save = false); void UnscribeSpellAll(bool update_client = true); - void UntrainDisc(int slot, bool update_client = true); + void UntrainDisc(int slot, bool update_client = true, bool defer_save = false); void UntrainDiscAll(bool update_client = true); + void UntrainDiscBySpellID(uint16 spell_id, bool update_client = true); bool SpellGlobalCheck(uint16 spell_id, uint32 char_id); bool SpellBucketCheck(uint16 spell_id, uint32 char_id); uint32 GetCharMaxLevelFromQGlobal(); @@ -812,9 +835,6 @@ public: inline uint8 GetBecomeNPCLevel() const { return npclevel; } inline void SetBecomeNPC(bool flag) { npcflag = flag; } inline void SetBecomeNPCLevel(uint8 level) { npclevel = level; } - void SetFeigned(bool in_feigned); - /// this cures timing issues cuz dead animation isn't done but server side feigning is? - inline bool GetFeigned() const { return(feigned); } EQStreamInterface* Connection() { return eqs; } #ifdef PACKET_PROFILER void DumpPacketProfile() { if(eqs) eqs->DumpPacketProfile(); } @@ -831,6 +851,7 @@ public: void SummonHorse(uint16 spell_id); void SetHorseId(uint16 horseid_in); inline void SetControlledMobId(uint16 mob_id_in) { controlled_mob_id = mob_id_in; } + uint16 GetControlledMobId() const{ return controlled_mob_id; } uint16 GetHorseId() const { return horseId; } bool CanMedOnHorse(); @@ -881,12 +902,14 @@ public: void ResetAA(); void RefundAA(); void SendClearAA(); + void SendClearLeadershipAA(); + void SendClearPlayerAA(); inline uint32 GetAAXP() const { return m_pp.expAA; } inline uint32 GetAAPercent() const { return m_epp.perAA; } - int16 CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id); + int32 CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id); void SetAATitle(const char *Title); void SetTitleSuffix(const char *txt); - void MemorizeSpell(uint32 slot, uint32 spellid, uint32 scribing); + void MemorizeSpell(uint32 slot, uint32 spellid, uint32 scribing, uint32 reduction = 0); // Item methods void EVENT_ITEM_ScriptStopReturn(); @@ -901,13 +924,16 @@ public: bool PutItemInInventory(int16 slot_id, const EQ::ItemInstance& inst, bool client_update = false); bool PushItemOnCursor(const EQ::ItemInstance& inst, bool client_update = false); void SendCursorBuffer(); - void DeleteItemInInventory(int16 slot_id, int8 quantity = 0, bool client_update = false, bool update_db = true); + void DeleteItemInInventory(int16 slot_id, int16 quantity = 0, bool client_update = false, bool update_db = true); + int CountItem(uint32 item_id); + void RemoveItem(uint32 item_id, uint32 quantity = 1); bool SwapItem(MoveItem_Struct* move_in); void SwapItemResync(MoveItem_Struct* move_slots); void QSSwapItemAuditor(MoveItem_Struct* move_in, bool postaction_call = false); void PutLootInInventory(int16 slot_id, const EQ::ItemInstance &inst, ServerLootItem_Struct** bag_item_data = 0); bool AutoPutLootInInventory(EQ::ItemInstance& inst, bool try_worn = false, bool try_cursor = true, ServerLootItem_Struct** bag_item_data = 0); bool SummonItem(uint32 item_id, int16 charges = -1, uint32 aug1 = 0, uint32 aug2 = 0, uint32 aug3 = 0, uint32 aug4 = 0, uint32 aug5 = 0, uint32 aug6 = 0, bool attuned = false, uint16 to_slot = EQ::invslot::slotCursor, uint32 ornament_icon = 0, uint32 ornament_idfile = 0, uint32 ornament_hero_model = 0); + void SummonBaggedItems(uint32 bag_item_id, const std::vector& bag_items); void SetStats(uint8 type,int16 set_val); void IncStats(uint8 type,int16 increase_val); void DropItem(int16 slot_id, bool recurse = true); @@ -952,9 +978,9 @@ public: //remove charges/multiple objects from inventory: //bool DecreaseByType(uint32 type, uint8 amt); - bool DecreaseByID(uint32 type, uint8 amt); + bool DecreaseByID(uint32 type, int16 quantity); uint8 SlotConvert2(uint8 slot); //Maybe not needed. - void Escape(); //AA Escape + void Escape(); //keep or quest function void DisenchantSummonedBags(bool client_update = true); void RemoveNoRent(bool client_update = true); void RemoveDuplicateLore(bool client_update = true); @@ -977,8 +1003,10 @@ public: void ResetTrade(); void DropInst(const EQ::ItemInstance* inst); bool TrainDiscipline(uint32 itemid); + bool MemorizeSpellFromItem(uint32 item_id); void TrainDiscBySpellID(int32 spell_id); uint32 GetDisciplineTimer(uint32 timer_id); + void ResetAllDisciplineTimers(); int GetDiscSlotBySpellID(int32 spellid); void ResetDisciplineTimer(uint32 timer_id); void SendDisciplineUpdate(); @@ -988,6 +1016,10 @@ public: void SetLinkedSpellReuseTimer(uint32 timer_id, uint32 duration); bool IsLinkedSpellReuseTimerReady(uint32 timer_id); + + void ResetCastbarCooldownBySlot(int slot); + void ResetAllCastbarCooldowns(); + void ResetCastbarCooldownBySpellID(uint32 spell_id); bool CheckTitle(int titleset); void EnableTitle(int titleset); @@ -1009,8 +1041,10 @@ public: int FindSpellBookSlotBySpellID(uint16 spellid); uint32 GetSpellIDByBookSlot(int book_slot); int GetNextAvailableSpellBookSlot(int starting_slot = 0); + int GetNextAvailableDisciplineSlot(int starting_slot = 0); inline uint32 GetSpellByBookSlot(int book_slot) { return m_pp.spell_book[book_slot]; } inline bool HasSpellScribed(int spellid) { return FindSpellBookSlotBySpellID(spellid) != -1; } + uint32 GetHighestScribedSpellinSpellGroup(uint32 spell_group); uint16 GetMaxSkillAfterSpecializationRules(EQ::skills::SkillType skillid, uint16 maxSkill); void SendPopupToClient(const char *Title, const char *Text, uint32 PopupID = 0, uint32 Buttons = 0, uint32 Duration = 0); void SendFullPopup(const char *Title, const char *Text, uint32 PopupID = 0, uint32 NegativeID = 0, uint32 Buttons = 0, uint32 Duration = 0, const char *ButtonName0 = 0, const char *ButtonName1 = 0, uint32 SoundControls = 0); @@ -1028,7 +1062,11 @@ public: void SendTaskActivityComplete(int task_id, int activity_id, int task_index, TaskType task_type, int task_incomplete=1); void SendTaskFailed(int task_id, int task_index, TaskType task_type); void SendTaskComplete(int task_index); + bool HasTaskRequestCooldownTimer(); + void SendTaskRequestCooldownTimerMessage(); + void StartTaskRequestCooldownTimer(); inline ClientTaskState *GetTaskState() const { return task_state; } + inline bool HasTaskState() { if (task_state) { return true; } return false; } inline void CancelTask(int task_index, TaskType task_type) { if (task_state) { @@ -1083,17 +1121,8 @@ public: ); } } - inline void UpdateTasksOnKill(int npc_type_id) - { - if (task_state) { - task_state->UpdateTasksOnKill( - this, - npc_type_id - ); - } - } inline void UpdateTasksForItem( - ActivityType activity_type, + TaskActivityType activity_type, int item_id, int count = 1 ) @@ -1200,7 +1229,7 @@ public: bool enforce_level_requirement = false ) { if (task_state) { - task_state->AcceptNewTask(this, task_id, npc_id, enforce_level_requirement); + task_state->AcceptNewTask(this, task_id, npc_id, std::time(nullptr), enforce_level_requirement); } } inline int ActiveSpeakTask(int npc_type_id) @@ -1260,6 +1289,18 @@ public: { return (task_state ? task_state->CompletedTasksInSet(task_set_id) : 0); } + void PurgeTaskTimers(); + + // shared task shims / middleware + // these variables are used as a shim to intercept normal localized task functionality + // and pipe it into zone -> world and back to world -> zone + // world is authoritative + bool m_requesting_shared_task = false; + bool m_shared_task_update = false; + bool m_requested_shared_task_removal = false; + + std::vector GetPartyMembers(); + void HandleUpdateTasksOnKill(uint32 npc_type_id); inline const EQ::versions::ClientVersion ClientVersion() const { return m_ClientVersion; } inline const uint32 ClientVersionBit() const { return m_ClientVersionBit; } @@ -1303,8 +1344,7 @@ public: uint32 GetLDoNWinsTheme(uint32 t); uint32 GetLDoNLossesTheme(uint32 t); uint32 GetLDoNPointsTheme(uint32 t); - void UpdateLDoNWins(uint32 t, int32 n); - void UpdateLDoNLosses(uint32 t, int32 n); + void UpdateLDoNWinLoss(uint32 theme_id, bool win = false, bool remove = false); void CheckLDoNHail(Mob *target); void CheckEmoteHail(Mob *target, const char* message); @@ -1328,9 +1368,9 @@ public: const std::string& event_Name, int seconds, const std::string& uuid = {}, bool update_db = false); void AddNewExpeditionLockout(const std::string& expedition_name, const std::string& event_name, uint32_t duration, std::string uuid = {}); - Expedition* CreateExpedition(DynamicZone& dz_instance, ExpeditionRequest& request); - Expedition* CreateExpedition( - const std::string& zone_name, uint32 version, uint32 duration, const std::string& expedition_name, + Expedition* CreateExpedition(DynamicZone& dz, bool disable_messages = false); + Expedition* CreateExpedition(const std::string& zone_name, + uint32 version, uint32 duration, const std::string& expedition_name, uint32 min_players, uint32 max_players, bool disable_messages = false); Expedition* GetExpedition() const; uint32 GetExpeditionID() const { return m_expedition_id; } @@ -1348,7 +1388,6 @@ public: void SendExpeditionLockoutTimers(); void SetExpeditionID(uint32 expedition_id) { m_expedition_id = expedition_id; }; void SetPendingExpeditionInvite(ExpeditionInvite&& invite) { m_pending_expedition_invite = invite; } - void UpdateExpeditionInfoAndLockouts(); void DzListTimers(); void SetDzRemovalTimer(bool enable_timer); void SendDzCompassUpdate(); @@ -1358,6 +1397,11 @@ public: std::vector GetDynamicZones(uint32_t zone_id = 0, int zone_version = -1); std::unique_ptr CreateDzSwitchListPacket(const std::vector& dzs); std::unique_ptr CreateCompassPacket(const std::vector& entries); + void AddDynamicZoneID(uint32_t dz_id); + void RemoveDynamicZoneID(uint32_t dz_id); + void SendDynamicZoneUpdates(); + void SetDynamicZoneMemberStatus(DynamicZoneMemberStatus status); + void CreateTaskDynamicZone(int task_id, DynamicZone& dz_request); void CalcItemScale(); bool CalcItemScale(uint32 slot_x, uint32 slot_y); // behavior change: 'slot_y' is now [RANGE]_END and not [RANGE]_END + 1 @@ -1371,7 +1415,7 @@ public: uint32 GetCorpseCount() { return database.GetCharacterCorpseCount(CharacterID()); } uint32 GetCorpseID(int corpse) { return database.GetCharacterCorpseID(CharacterID(), corpse); } uint32 GetCorpseItemAt(int corpse_id, int slot_id) { return database.GetCharacterCorpseItemAt(corpse_id, slot_id); } - void SuspendMinion(); + void SuspendMinion(int value); void Doppelganger(uint16 spell_id, Mob *target, const char *name_override, int pet_count, int pet_duration); void NotifyNewTitlesAvailable(); void Signal(uint32 data); @@ -1448,6 +1492,10 @@ public: bool GroupFollow(Client* inviter); inline bool GetRunMode() const { return runmode; } + void SendItemRecastTimer(int32 recast_type, uint32 recast_delay = 0); + void SetItemRecastTimer(int32 spell_id, uint32 inventory_slot); + bool HasItemRecastTimer(int32 spell_id, uint32 inventory_slot); + inline bool AggroMeterAvailable() const { return ((m_ClientVersionBit & EQ::versions::maskRoF2AndLater)) && RuleB(Character, EnableAggroMeter); } // RoF untested inline void SetAggroMeterLock(int in) { m_aggrometer.set_lock_id(in); } @@ -1486,6 +1534,7 @@ public: void UpdateMercLevel(); void CheckMercSuspendTimer(); Timer* GetMercTimer() { return &merc_timer; }; + Timer* GetPickLockTimer() { return &pick_lock_timer; }; const char* GetRacePlural(Client* client); const char* GetClassPlural(Client* client); @@ -1512,6 +1561,7 @@ public: void LoadAccountFlags(); void SetAccountFlag(std::string flag, std::string val); std::string GetAccountFlag(std::string flag); + void SetGMStatus(int newStatus); float GetDamageMultiplier(EQ::skills::SkillType how_long_has_this_been_missing); void Consume(const EQ::ItemData *item, uint8 type, int16 slot, bool auto_consume); void PlayMP3(const char* fname); @@ -1519,7 +1569,7 @@ public: int mod_client_damage(int damage, EQ::skills::SkillType skillinuse, int hand, const EQ::ItemInstance* weapon, Mob* other); bool mod_client_message(char* message, uint8 chan_num); bool mod_can_increase_skill(EQ::skills::SkillType skillid, Mob* against_who); - int16 mod_increase_skill_chance(int16 chance, Mob* against_who); + double mod_increase_skill_chance(double chance, Mob* against_who); int mod_bindwound_percent(int max_percent, Mob* bindmob); int mod_bindwound_hp(int bindhps, Mob* bindmob); int mod_client_haste(int h); @@ -1541,6 +1591,13 @@ public: void ShowNumHits(); // work around function for numhits not showing on buffs + void ApplyWeaponsStance(); + void TogglePassiveAlternativeAdvancement(const AA::Rank &rank, uint32 ability_id); + bool UseTogglePassiveHotkey(const AA::Rank &rank); + void TogglePurchaseAlternativeAdvancementRank(int rank_id); + void ResetAlternateAdvancementRank(uint32 aa_id); + bool IsEffectinAlternateAdvancementRankEffects(const AA::Rank &rank, int effect_id); + void TripInterrogateInvState() { interrogateinv_flag = true; } bool GetInterrogateInvState() { return interrogateinv_flag; } @@ -1553,10 +1610,6 @@ public: uint32 GetLastInvSnapshotTime() { return m_epp.last_invsnapshot_time; } uint32 GetNextInvSnapshotTime() { return m_epp.next_invsnapshot_time; } - //Command #Tune functions - virtual int32 Tune_GetMeleeMitDmg(Mob* GM, Mob *attacker, int32 damage, int32 minhit, float mit_rating, float atk_rating); - int32 GetMeleeDamage(Mob* other, bool GetMinDamage = false); - void QuestReward(Mob* target, uint32 copper = 0, uint32 silver = 0, uint32 gold = 0, uint32 platinum = 0, uint32 itemid = 0, uint32 exp = 0, bool faction = false); void QuestReward(Mob* target, const QuestReward_Struct &reward, bool faction); // TODO: Fix faction processing @@ -1578,6 +1631,10 @@ public: Raid *p_raid_instance; void ShowDevToolsMenu(); + CheatManager cheat_manager; + + // rate limit + Timer m_list_task_timers_rate_limit = {}; protected: friend class Mob; @@ -1590,7 +1647,7 @@ protected: void MakeBuffFadePacket(uint16 spell_id, int slot_id, bool send_message = true); bool client_data_loaded; - int16 GetFocusEffect(focusType type, uint16 spell_id); + int32 GetFocusEffect(focusType type, uint16 spell_id, Mob *caster = nullptr); uint16 GetSympatheticFocusEffect(focusType type, uint16 spell_id); void FinishAlternateAdvancementPurchase(AA::Rank *rank, bool ignore_cost); @@ -1731,9 +1788,17 @@ private: int Haste; //precalced value uint32 tmSitting; // time stamp started sitting, used for HP regen bonus added on MAY 5, 2004 + + // dev tools bool display_mob_info_window; bool dev_tools_enabled; + uint16 m_door_tool_entity_id; +public: + uint16 GetDoorToolEntityId() const; + void SetDoorToolEntityId(uint16 door_tool_entity_id); +private: + int32 max_end; int32 current_endurance; @@ -1765,7 +1830,7 @@ private: void ZonePC(uint32 zoneID, uint32 instance_id, float x, float y, float z, float heading, uint8 ignorerestrictions, ZoneMode zm); void ProcessMovePC(uint32 zoneID, uint32 instance_id, float x, float y, float z, float heading, uint8 ignorerestrictions = 0, ZoneMode zm = ZoneSolicited); - glm::vec3 m_ZoneSummonLocation; + glm::vec4 m_ZoneSummonLocation; uint16 zonesummon_id; uint8 zonesummon_ignorerestrictions; ZoneMode zone_mode; @@ -1784,10 +1849,8 @@ private: Timer linkdead_timer; Timer dead_timer; Timer global_channel_timer; - Timer shield_timer; Timer fishing_timer; Timer endupkeep_timer; - Timer forget_timer; // our 2 min everybody forgets you timer Timer autosave_timer; Timer client_scan_npc_aggro_timer; Timer client_zone_wide_full_position_update_timer; @@ -1808,12 +1871,14 @@ private: Timer helm_toggle_timer; Timer aggro_meter_timer; Timer mob_close_scan_timer; - Timer hp_self_update_throttle_timer; /* This is to prevent excessive packet sending under trains/fast combat */ - Timer hp_other_update_throttle_timer; /* This is to keep clients from DOSing the server with macros that change client targets constantly */ Timer position_update_timer; /* Timer used when client hasn't updated within a 10 second window */ Timer consent_throttle_timer; Timer dynamiczone_removal_timer; + Timer task_request_timer; + Timer pick_lock_timer; + Timer heroforge_wearchange_timer; + glm::vec3 m_Proximity; glm::vec4 last_position_before_bulk_update; @@ -1825,7 +1890,6 @@ private: bool npcflag; uint8 npclevel; - bool feigned; bool bZoning; bool tgb; bool instalog; @@ -1847,6 +1911,14 @@ private: ClientTaskState *task_state; int TotalSecondsPlayed; + // we use this very sparingly at the zone level + // used for keeping clients in donecount sync before world sends absolute confirmations of state + int64 m_shared_task_id = 0; +public: + void SetSharedTaskId(int64 shared_task_id); + int64 GetSharedTaskId() const; +private: + //Anti Spam Stuff Timer *KarmaUpdateTimer; uint32 TotalKarma; @@ -1919,6 +1991,7 @@ private: std::vector m_expedition_lockouts; glm::vec3 m_quest_compass; bool m_has_quest_compass = false; + std::vector m_dynamic_zone_ids; #ifdef BOTS diff --git a/zone/client_mods.cpp b/zone/client_mods.cpp index ec01a00b5..307623b0f 100644 --- a/zone/client_mods.cpp +++ b/zone/client_mods.cpp @@ -328,18 +328,15 @@ int32 Client::CalcMaxHP() if (current_hp > max_hp) { current_hp = max_hp; } - int hp_perc_cap = spellbonuses.HPPercCap[0]; + int hp_perc_cap = spellbonuses.HPPercCap[SBIndex::RESOURCE_PERCENT_CAP]; if (hp_perc_cap) { int curHP_cap = (max_hp * hp_perc_cap) / 100; - if (current_hp > curHP_cap || (spellbonuses.HPPercCap[1] && current_hp > spellbonuses.HPPercCap[1])) { + if (current_hp > curHP_cap || (spellbonuses.HPPercCap[SBIndex::RESOURCE_AMOUNT_CAP] && current_hp > spellbonuses.HPPercCap[SBIndex::RESOURCE_AMOUNT_CAP])) { current_hp = curHP_cap; } } - // hack fix for client health not reflecting server value - last_max_hp = 0; - return max_hp; } @@ -591,10 +588,10 @@ int32 Client::CalcMaxMana() if (current_mana > max_mana) { current_mana = max_mana; } - int mana_perc_cap = spellbonuses.ManaPercCap[0]; + int mana_perc_cap = spellbonuses.ManaPercCap[SBIndex::RESOURCE_PERCENT_CAP]; if (mana_perc_cap) { int curMana_cap = (max_mana * mana_perc_cap) / 100; - if (current_mana > curMana_cap || (spellbonuses.ManaPercCap[1] && current_mana > spellbonuses.ManaPercCap[1])) { + if (current_mana > curMana_cap || (spellbonuses.ManaPercCap[SBIndex::RESOURCE_AMOUNT_CAP] && current_mana > spellbonuses.ManaPercCap[SBIndex::RESOURCE_AMOUNT_CAP])) { current_mana = curMana_cap; } } @@ -781,7 +778,7 @@ int32 Client::CalcManaRegen(bool bCombat) int32 Client::CalcManaRegenCap() { - int32 cap = RuleI(Character, ItemManaRegenCap) + aabonuses.ItemManaRegenCap; + int32 cap = RuleI(Character, ItemManaRegenCap) + aabonuses.ItemManaRegenCap + itembonuses.ItemManaRegenCap + spellbonuses.ItemManaRegenCap; return (cap * RuleI(Character, ManaRegenMultiplier) / 100); } @@ -1054,7 +1051,12 @@ int Client::CalcHaste() } // 51+ 25 (despite there being higher spells...), 1-50 10 if (level > 50) { // 51+ - h += spellbonuses.hastetype3 > 25 ? 25 : spellbonuses.hastetype3; + cap = RuleI(Character, Hastev3Cap); + if (spellbonuses.hastetype3 > cap) { + h += cap; + } else { + h += spellbonuses.hastetype3; + } } else { // 1-50 h += spellbonuses.hastetype3 > 10 ? 10 : spellbonuses.hastetype3; @@ -1506,22 +1508,30 @@ int32 Client::CalcATK() return (ATK); } -uint32 Mob::GetInstrumentMod(uint16 spell_id) const +uint32 Mob::GetInstrumentMod(uint16 spell_id) { - if (GetClass() != BARD || spells[spell_id].IsDisciplineBuff) // Puretone is Singing but doesn't get any mod + if (GetClass() != BARD) { + //Other classes can get a base effects mod using SPA 413 + if (HasBaseEffectFocus()) { + return (10 + (GetFocusEffect(focusFcBaseEffects, spell_id) / 10));//TODO: change action->instrument mod to float to support < 10% focus values + } return 10; + } + //AA's click effects that use instrument/singing skills don't apply modifiers (Confirmed on live 11/24/21 ~Kayen) + if (casting_spell_aa_id) { + return 10; + } + uint32 effectmod = 10; int effectmodcap = 0; - bool nocap = false; if (RuleB(Character, UseSpellFileSongCap)) { - effectmodcap = spells[spell_id].songcap / 10; - // this looks a bit weird, but easiest way I could think to keep both systems working - if (effectmodcap == 0) - nocap = true; - else - effectmodcap += 10; - } else { + effectmodcap = spells[spell_id].song_cap / 10; + if (effectmodcap) { + effectmodcap += 10; //Actual calculated cap is 100 greater than songcap value. + } + } + else { effectmodcap = RuleI(Character, BaseInstrumentSoftCap); } // this should never use spell modifiers... @@ -1530,6 +1540,39 @@ uint32 Mob::GetInstrumentMod(uint16 spell_id) const // item mods are in 10ths of percent increases // clickies (Symphony of Battle) that have a song skill don't get AA bonus for some reason // but clickies that are songs (selo's on Composers Greaves) do get AA mod as well + + /*Mechanics: updated 10/19/21 ~Kayen + Bard Spell Effects + + Mod uses the highest bonus from either of these for each instrument + SPA 179 SE_AllInstrumentMod is used for instrument spellbonus.______Mod. This applies to ALL instrument mods (Puretones Discipline) + SPA 260 SE_AddSingingMod is used for instrument spellbonus.______Mod. This applies to indiviual instrument mods. (Instrument mastery AA) + -Example usage: From AA a value of 4 = 40% + + SPA 118 SE_Amplification is a stackable singing mod, on live it exists as both spell and AA bonus (stackable) + - Live Behavior: Amplifcation can be modified by singing mods and amplification itself, thus on the second cast of Amplification you will recieve + the mod from the first cast, this continues until you reach the song mod cap. + + SPA 261 SE_SongModCap raises song focus cap (No longer used on live) + SPA 270 SE_BardSongRange increase range of beneficial bard songs (Sionachie's Crescendo) + + SPA 413 SE_FcBaseEffects focus effect that replaced item instrument mods + + Issues 10-15-21: + Bonuses are not applied, unless song is stopped and restarted due to pulse keeping it continues. -> Need to recode songs to recast when duration ends. + + Formula Live Bards: + mod = (10 + (aabonus.____Mod [SPA 260 AA Instrument Mastery]) + (SE_FcBaseEffect[SPA 413])/10 + (spellbonus.______Mod [SPA 179 Puretone Disc]) + (Amplication [SPA 118])/10 + + TODO: Spell Table Fields that need to be implemented + Field 225 //float base_effects_focus_slope; // -- BASE_EFFECTS_FOCUS_SLOPE + Field 226 //float base_effects_focus_offset; // -- BASE_EFFECTS_FOCUS_OFFSET (35161 Ruaabri's Reckless Renewal -120) + Based on description possibly works as a way to quickly balance instrument mods to a song. + Using a standard slope formula: y = mx + b + modified_base_value = (base_effects_focus_slope x effectmod)(base_value) + (base_effects_focus_offset) + Will need to confirm on live before implementing. + */ + switch (spells[spell_id].skill) { case EQ::skills::SkillPercussionInstruments: if (itembonuses.percussionMod == 0 && spellbonuses.percussionMod == 0) @@ -1587,18 +1630,34 @@ uint32 Mob::GetInstrumentMod(uint16 spell_id) const else effectmod = spellbonuses.singingMod; if (IsBardSong(spell_id)) - effectmod += aabonuses.singingMod + spellbonuses.Amplification; + effectmod += aabonuses.singingMod + (spellbonuses.Amplification + itembonuses.Amplification + aabonuses.Amplification); //SPA 118 SE_Amplification break; default: effectmod = 10; return effectmod; } - if (!RuleB(Character, UseSpellFileSongCap)) - effectmodcap += aabonuses.songModCap + spellbonuses.songModCap + itembonuses.songModCap; - if (effectmod < 10) + + if (HasBaseEffectFocus()) { + effectmod += (GetFocusEffect(focusFcBaseEffects, spell_id) / 10); + } + + if (effectmod < 10) { effectmod = 10; - if (!nocap && effectmod > effectmodcap) // if the cap is calculated to be 0 using new rules, no cap. - effectmod = effectmodcap; + } + + if (effectmodcap) { + + effectmodcap += aabonuses.songModCap + spellbonuses.songModCap + itembonuses.songModCap; //SPA 261 SE_SongModCap (not used on live) + + //Incase a negative modifier is used. + if (effectmodcap <= 0) { + effectmodcap = 10; + } + + if (effectmod > effectmodcap) { // if the cap is calculated to be 0 using new rules, no cap. + effectmod = effectmodcap; + } + } LogSpells("[{}]::GetInstrumentMod() spell=[{}] mod=[{}] modcap=[{}]\n", GetName(), spell_id, effectmod, effectmodcap); @@ -1614,10 +1673,10 @@ void Client::CalcMaxEndurance() if (current_endurance > max_end) { current_endurance = max_end; } - int end_perc_cap = spellbonuses.EndPercCap[0]; + int end_perc_cap = spellbonuses.EndPercCap[SBIndex::RESOURCE_PERCENT_CAP]; if (end_perc_cap) { int curEnd_cap = (max_end * end_perc_cap) / 100; - if (current_endurance > curEnd_cap || (spellbonuses.EndPercCap[1] && current_endurance > spellbonuses.EndPercCap[1])) { + if (current_endurance > curEnd_cap || (spellbonuses.EndPercCap[SBIndex::RESOURCE_AMOUNT_CAP] && current_endurance > spellbonuses.EndPercCap[SBIndex::RESOURCE_AMOUNT_CAP])) { current_endurance = curEnd_cap; } } @@ -1751,7 +1810,7 @@ int32 Client::CalcEnduranceRegen(bool bCombat) int32 Client::CalcEnduranceRegenCap() { - int cap = RuleI(Character, ItemEnduranceRegenCap); + int cap = RuleI(Character, ItemEnduranceRegenCap) + aabonuses.ItemEnduranceRegenCap + itembonuses.ItemEnduranceRegenCap + spellbonuses.ItemEnduranceRegenCap; return (cap * RuleI(Character, EnduranceRegenMultiplier) / 100); } diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 2fb9f99b8..d07920f4d 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -64,7 +64,12 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "worldserver.h" #include "zone.h" #include "mob_movement_manager.h" +#include "../common/repositories/character_instance_safereturns_repository.h" #include "../common/repositories/criteria/content_filter_criteria.h" +#include "../common/shared_tasks.h" +#include "gm_commands/door_manipulation.h" +#include "client.h" + #ifdef BOTS #include "bot.h" @@ -191,8 +196,8 @@ void MapOpcodes() ConnectedOpcodes[OP_Disarm] = &Client::Handle_OP_Disarm; ConnectedOpcodes[OP_DisarmTraps] = &Client::Handle_OP_DisarmTraps; ConnectedOpcodes[OP_DoGroupLeadershipAbility] = &Client::Handle_OP_DoGroupLeadershipAbility; - ConnectedOpcodes[OP_DuelResponse] = &Client::Handle_OP_DuelResponse; - ConnectedOpcodes[OP_DuelResponse2] = &Client::Handle_OP_DuelResponse2; + ConnectedOpcodes[OP_DuelDecline] = &Client::Handle_OP_DuelDecline; + ConnectedOpcodes[OP_DuelAccept] = &Client::Handle_OP_DuelAccept; ConnectedOpcodes[OP_DumpName] = &Client::Handle_OP_DumpName; ConnectedOpcodes[OP_Dye] = &Client::Handle_OP_Dye; ConnectedOpcodes[OP_DzAddPlayer] = &Client::Handle_OP_DzAddPlayer; @@ -211,7 +216,7 @@ void MapOpcodes() ConnectedOpcodes[OP_FeignDeath] = &Client::Handle_OP_FeignDeath; ConnectedOpcodes[OP_FindPersonRequest] = &Client::Handle_OP_FindPersonRequest; ConnectedOpcodes[OP_Fishing] = &Client::Handle_OP_Fishing; - ConnectedOpcodes[OP_FloatListThing] = &Client::Handle_OP_Ignore; + ConnectedOpcodes[OP_FloatListThing] = &Client::Handle_OP_MovementHistoryList; ConnectedOpcodes[OP_Forage] = &Client::Handle_OP_Forage; ConnectedOpcodes[OP_FriendsWho] = &Client::Handle_OP_FriendsWho; ConnectedOpcodes[OP_GetGuildMOTD] = &Client::Handle_OP_GetGuildMOTD; @@ -383,6 +388,7 @@ void MapOpcodes() ConnectedOpcodes[OP_TargetCommand] = &Client::Handle_OP_TargetCommand; ConnectedOpcodes[OP_TargetMouse] = &Client::Handle_OP_TargetMouse; ConnectedOpcodes[OP_TaskHistoryRequest] = &Client::Handle_OP_TaskHistoryRequest; + ConnectedOpcodes[OP_TaskTimers] = &Client::Handle_OP_TaskTimers; ConnectedOpcodes[OP_Taunt] = &Client::Handle_OP_Taunt; ConnectedOpcodes[OP_TestBuff] = &Client::Handle_OP_TestBuff; ConnectedOpcodes[OP_TGB] = &Client::Handle_OP_TGB; @@ -415,6 +421,16 @@ void MapOpcodes() ConnectedOpcodes[OP_YellForHelp] = &Client::Handle_OP_YellForHelp; ConnectedOpcodes[OP_ZoneChange] = &Client::Handle_OP_ZoneChange; ConnectedOpcodes[OP_ResetAA] = &Client::Handle_OP_ResetAA; + ConnectedOpcodes[OP_UnderWorld] = &Client::Handle_OP_UnderWorld; + + // shared tasks + ConnectedOpcodes[OP_SharedTaskRemovePlayer] = &Client::Handle_OP_SharedTaskRemovePlayer; + ConnectedOpcodes[OP_SharedTaskAddPlayer] = &Client::Handle_OP_SharedTaskAddPlayer; + ConnectedOpcodes[OP_SharedTaskMakeLeader] = &Client::Handle_OP_SharedTaskMakeLeader; + ConnectedOpcodes[OP_SharedTaskInviteResponse] = &Client::Handle_OP_SharedTaskInviteResponse; + ConnectedOpcodes[OP_SharedTaskAcceptNew] = &Client::Handle_OP_SharedTaskAccept; + ConnectedOpcodes[OP_SharedTaskQuit] = &Client::Handle_OP_SharedTaskQuit; + ConnectedOpcodes[OP_SharedTaskPlayerList] = &Client::Handle_OP_SharedTaskPlayerList; } void ClearMappedOpcode(EmuOpcode op) @@ -527,6 +543,9 @@ void Client::CompleteConnect() /* Sets GM Flag if needed & Sends Petition Queue */ UpdateAdmin(false); + // Task Packets + LoadClientTaskState(); + if (IsInAGuild()) { uint8 rank = GuildRank(); if (ClientVersion() >= EQ::versions::ClientVersion::RoF) @@ -659,56 +678,11 @@ void Client::CompleteConnect() } for (int x1 = 0; x1 < EFFECT_COUNT; x1++) { - switch (spell.effectid[x1]) { - case SE_IllusionCopy: + switch (spell.effect_id[x1]) { case SE_Illusion: { - if (spell.base[x1] == -1) { - if (gender == 1) - gender = 0; - else if (gender == 0) - gender = 1; - SendIllusionPacket(GetRace(), gender, 0xFF, 0xFF); - } - else if (spell.base[x1] == -2) // WTF IS THIS - { - if (GetRace() == 128 || GetRace() == 130 || GetRace() <= 12) - SendIllusionPacket(GetRace(), GetGender(), spell.base2[x1], spell.max[x1]); - } - else if (spell.max[x1] > 0) - { - SendIllusionPacket(spell.base[x1], 0xFF, spell.base2[x1], spell.max[x1]); - } - else - { - SendIllusionPacket(spell.base[x1], 0xFF, 0xFF, 0xFF); - } - switch (spell.base[x1]) { - case OGRE: - SendAppearancePacket(AT_Size, 9); - break; - case TROLL: - SendAppearancePacket(AT_Size, 8); - break; - case VAHSHIR: - case BARBARIAN: - SendAppearancePacket(AT_Size, 7); - break; - case HALF_ELF: - case WOOD_ELF: - case DARK_ELF: - case FROGLOK: - SendAppearancePacket(AT_Size, 5); - break; - case DWARF: - SendAppearancePacket(AT_Size, 4); - break; - case HALFLING: - case GNOME: - SendAppearancePacket(AT_Size, 3); - break; - default: - SendAppearancePacket(AT_Size, 6); - break; + if (buffs[j1].persistant_buff) { + Mob *caster = entity_list.GetMobID(buffs[j1].casterid); + ApplySpellEffectIllusion(spell.id, caster, j1, spell.base_value[x1], spell.limit_value[x1], spell.max_value[x1]); } break; } @@ -751,7 +725,12 @@ void Client::CompleteConnect() } } else { - SendAppearancePacket(AT_Levitate, 2); + if (spell.limit_value[x1] == 1) { + SendAppearancePacket(AT_Levitate, EQ::constants::GravityBehavior::LevitateWhileRunning, true, true); + } + else { + SendAppearancePacket(AT_Levitate, EQ::constants::GravityBehavior::Levitating, true, true); + } } break; } @@ -769,17 +748,17 @@ void Client::CompleteConnect() case SE_AddMeleeProc: case SE_WeaponProc: { - AddProcToWeapon(GetProcID(buffs[j1].spellid, x1), false, 100 + spells[buffs[j1].spellid].base2[x1], buffs[j1].spellid, buffs[j1].casterlevel); + AddProcToWeapon(GetProcID(buffs[j1].spellid, x1), false, 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid, buffs[j1].casterlevel, GetProcLimitTimer(buffs[j1].spellid, ProcType::MELEE_PROC)); break; } case SE_DefensiveProc: { - AddDefensiveProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].base2[x1], buffs[j1].spellid); + AddDefensiveProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid, GetProcLimitTimer(buffs[j1].spellid, ProcType::DEFENSIVE_PROC)); break; } case SE_RangedProc: { - AddRangedProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].base2[x1], buffs[j1].spellid); + AddRangedProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid, GetProcLimitTimer(buffs[j1].spellid, ProcType::RANGED_PROC)); break; } } @@ -793,28 +772,15 @@ void Client::CompleteConnect() entity_list.SendUntargetable(this); - int x; - for (x = EQ::textures::textureBegin; x <= EQ::textures::LastTexture; x++) { - SendWearChange(x); - } - // added due to wear change above - UpdateActiveLight(); - SendAppearancePacket(AT_Light, GetActiveLightType()); + entity_list.SendAppearanceEffects(this); - Mob *pet = GetPet(); - if (pet != nullptr) { - for (x = EQ::textures::textureBegin; x <= EQ::textures::LastTexture; x++) { - pet->SendWearChange(x); - } - // added due to wear change above - pet->UpdateActiveLight(); - pet->SendAppearancePacket(AT_Light, pet->GetActiveLightType()); - } + entity_list.SendIllusionWearChange(this); entity_list.SendTraders(this); - if (GetPet()) { - GetPet()->SendPetBuffsToClient(); + Mob *pet = GetPet(); + if (pet) { + pet->SendPetBuffsToClient(); } if (GetGroup()) @@ -865,39 +831,18 @@ void Client::CompleteConnect() } 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 - ); - } - + bool is_permanent = false; + uint32 remaining_time = database.GetTimeRemainingInstance(zone->GetInstanceID(), is_permanent); + auto time_string = ConvertSecondsToTime(remaining_time); + Message( + Chat::Yellow, + fmt::format( + "{} ({}) will expire in {}.", + zone->GetLongName(), + zone->GetInstanceID(), + time_string + ).c_str() + ); } SendRewards(); @@ -925,7 +870,7 @@ void Client::CompleteConnect() guild_mgr.RequestOnlineGuildMembers(this->CharacterID(), this->GuildID()); } - UpdateExpeditionInfoAndLockouts(); + SendDynamicZoneUpdates(); /** Request adventure info **/ auto pack = new ServerPacket(ServerOP_AdventureDataRequest, 64); @@ -966,6 +911,27 @@ void Client::CompleteConnect() ShowDevToolsMenu(); } + // shared tasks memberlist + if (RuleB(TaskSystem, EnableTaskSystem) && GetTaskState()->HasActiveSharedTask()) { + + // struct + auto p = new ServerPacket( + ServerOP_SharedTaskRequestMemberlist, + sizeof(ServerSharedTaskRequestMemberlist_Struct) + ); + + auto *r = (ServerSharedTaskRequestMemberlist_Struct *) p->pBuffer; + + // fill + r->source_character_id = CharacterID(); + r->task_id = GetTaskState()->GetActiveSharedTask().task_id; + + // send + worldserver.SendPacket(p); + safe_delete(p); + } + + heroforge_wearchange_timer.Start(250); } // connecting opcode handlers @@ -1247,11 +1213,8 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) uint32 pplen = 0; EQApplicationPacket* outapp = nullptr; - MYSQL_RES* result = nullptr; bool loaditems = 0; - uint32 i; std::string query; - unsigned long* lengths = nullptr; uint32 cid = CharacterID(); character_id = cid; /* Global character_id reference */ @@ -1294,7 +1257,7 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) m_pp.platinum_shared = database.GetSharedPlatinum(this->AccountID()); 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... + // 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); loaditems = database.GetInventory(cid, &m_inv); /* Load Character Inventory */ @@ -1359,10 +1322,11 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) strcpy(lastname, m_pp.last_name); /* If PP is set to weird coordinates */ if ((m_pp.x == -1 && m_pp.y == -1 && m_pp.z == -1) || (m_pp.x == -2 && m_pp.y == -2 && m_pp.z == -2)) { - auto safePoint = zone->GetSafePoint(); - m_pp.x = safePoint.x; - m_pp.y = safePoint.y; - m_pp.z = safePoint.z; + auto zone_safe_point = zone->GetSafePoint(); + m_pp.x = zone_safe_point.x; + m_pp.y = zone_safe_point.y; + m_pp.z = zone_safe_point.z; + m_pp.heading = zone_safe_point.w; } /* If too far below ground, then fix */ // float ground_z = GetGroundZ(m_pp.x, m_pp.y, m_pp.z); @@ -1501,7 +1465,7 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) m_pp.buffs[i].unknown003 = 0; m_pp.buffs[i].duration = buffs[i].ticsremaining; m_pp.buffs[i].counters = buffs[i].counters; - m_pp.buffs[i].num_hits = buffs[i].numhits; + m_pp.buffs[i].num_hits = buffs[i].hit_number; } else { m_pp.buffs[i].spellid = SPELLBOOK_UNKNOWN; @@ -1685,8 +1649,9 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) m_petinfo.SpellID = 0; } /* Moved here so it's after where we load the pet data. */ - if (!GetAA(aaPersistentMinion)) + if (!aabonuses.ZoneSuspendMinion && !spellbonuses.ZoneSuspendMinion && !itembonuses.ZoneSuspendMinion) { memset(&m_suspendedminion, 0, sizeof(PetInfo)); + } /* Server Zone Entry Packet */ outapp = new EQApplicationPacket(OP_ZoneEntry, sizeof(ServerZoneEntry_Struct)); @@ -1722,12 +1687,12 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) Character Inventory Packet this is not quite where live sends inventory, they do it after tribute */ - if (loaditems) { /* Dont load if a length error occurs */ + if (loaditems) { /* Don't load if a length error occurs */ if (admin >= minStatusToBeGM) m_inv.SetGMInventory(true); // set to true to allow expansion-restricted packets through BulkSendInventoryItems(); - /* Send stuff on the cursor which isnt sent in bulk */ + /* Send stuff on the cursor which isn't sent in bulk */ for (auto iter = m_inv.cursor_cbegin(); iter != m_inv.cursor_cend(); ++iter) { /* First item cursor is sent in bulk inventory packet */ if (iter == m_inv.cursor_cbegin()) @@ -1740,10 +1705,40 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) m_inv.SetGMInventory((bool)m_pp.gm); // reset back to current gm state } - /* Task Packets */ - LoadClientTaskState(); + ApplyWeaponsStance(); - m_expedition_id = ExpeditionDatabase::GetExpeditionIDFromCharacterID(CharacterID()); + auto dynamic_zone_member_entries = DynamicZoneMembersRepository::GetWhere(database, + fmt::format("character_id = {}", CharacterID())); + + for (const auto& entry : dynamic_zone_member_entries) + { + m_dynamic_zone_ids.emplace_back(entry.dynamic_zone_id); + } + + m_expedition_id = ExpeditionsRepository::GetIDByMemberID(database, CharacterID()); + + auto dz = zone->GetDynamicZone(); + if (dz && dz->GetSafeReturnLocation().zone_id != 0) + { + auto safereturn = dz->GetSafeReturnLocation(); + + auto safereturn_entry = CharacterInstanceSafereturnsRepository::NewEntity(); + safereturn_entry.character_id = CharacterID(); + safereturn_entry.instance_zone_id = zone->GetZoneID(); + safereturn_entry.instance_id = zone->GetInstanceID(); + safereturn_entry.safe_zone_id = safereturn.zone_id; + safereturn_entry.safe_x = safereturn.x; + safereturn_entry.safe_y = safereturn.y; + safereturn_entry.safe_z = safereturn.z; + safereturn_entry.safe_heading = safereturn.heading; + + CharacterInstanceSafereturnsRepository::InsertOneOrUpdate(database, safereturn_entry); + } + else + { + CharacterInstanceSafereturnsRepository::DeleteWhere(database, + fmt::format("character_id = {}", character_id)); + } /** * DevTools Load Settings @@ -1764,7 +1759,7 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) /* Weather Packet - This shouldent be moved, this seems to be what the client + This shouldn't be moved, this seems to be what the client uses to advance to the next state (sending ReqNewZone) */ outapp = new EQApplicationPacket(OP_Weather, 12); @@ -1850,7 +1845,7 @@ void Client::Handle_OP_AcceptNewTask(const EQApplicationPacket *app) AcceptNewTask_Struct *ant = (AcceptNewTask_Struct*)app->pBuffer; if (ant->task_id > 0 && RuleB(TaskSystem, EnableTaskSystem) && task_state) - task_state->AcceptNewTask(this, ant->task_id, ant->task_master_id); + task_state->AcceptNewTask(this, ant->task_id, ant->task_master_id, std::time(nullptr)); } void Client::Handle_OP_AdventureInfoRequest(const EQApplicationPacket *app) @@ -1917,14 +1912,6 @@ void Client::Handle_OP_AdventureMerchantPurchase(const EQApplicationPacket *app) } Adventure_Purchase_Struct* aps = (Adventure_Purchase_Struct*)app->pBuffer; - /* - Get item apc->itemid (can check NPC if thats necessary), ldon point theme check only if theme is not 0 (I am not sure what 1-5 are though for themes) - if(ldon_points_available >= item ldonpointcost) - { - give item (67 00 00 00 for the packettype using opcode 0x02c5) - ldon_points_available -= ldonpointcost; - } - */ uint32 merchantid = 0; Mob* tmp = entity_list.GetMob(aps->npcid); if (tmp == 0 || !tmp->IsNPC() || ((tmp->GetClass() != ADVENTUREMERCHANT) && @@ -1939,136 +1926,144 @@ void Client::Handle_OP_AdventureMerchantPurchase(const EQApplicationPacket *app) const EQ::ItemData* item = nullptr; bool found = false; - std::list merlist = zone->merchanttable[merchantid]; - std::list::const_iterator itr; - - for (itr = merlist.begin(); itr != merlist.end(); ++itr) { - MerchantList ml = *itr; - if (GetLevel() < ml.level_required) { + std::list merchantlists = zone->merchanttable[merchantid]; + for (auto merchantlist : merchantlists) { + if (GetLevel() < merchantlist.level_required) { continue; } - int32 fac = tmp->GetPrimaryFaction(); - if (fac != 0 && GetModCharacterFactionLevel(fac) < ml.faction_required) { + int faction_id = tmp->GetPrimaryFaction(); + if (faction_id != 0 && GetModCharacterFactionLevel(faction_id) < merchantlist.faction_required) { continue; } - item = database.GetItem(ml.item); - if (!item) + item = database.GetItem(merchantlist.item); + if (!item) { continue; + } + if (item->ID == aps->itemid) { //This check to make sure that the item is actually on the NPC, people attempt to inject packets to get items summoned... found = true; break; } } + if (!item || !found) { Message(Chat::Red, "Error: The item you purchased does not exist!"); return; } - if (aps->Type == LDoNMerchant) - { - if (m_pp.ldon_points_available < int32(item->LDoNPrice)) { + auto item_cost = item->LDoNPrice; + bool cannot_afford = false; + std::string merchant_type; + if (item_cost < 0) { + Message(Chat::Red, "This item cannot be bought."); + return; + } + + if (aps->Type == LDoNMerchant) { + if (m_pp.ldon_points_available < item_cost) { Message(Chat::Red, "You cannot afford that item."); return; } - if (item->LDoNTheme <= 16) - { - if (item->LDoNTheme & 16) - { - if (m_pp.ldon_points_tak < int32(item->LDoNPrice)) - { - Message(Chat::Red, "You need at least %u points in tak to purchase this item.", int32(item->LDoNPrice)); - return; - } - } - else if (item->LDoNTheme & 8) - { - if (m_pp.ldon_points_ruj < int32(item->LDoNPrice)) - { - Message(Chat::Red, "You need at least %u points in ruj to purchase this item.", int32(item->LDoNPrice)); - return; - } - } - else if (item->LDoNTheme & 4) - { - if (m_pp.ldon_points_mmc < int32(item->LDoNPrice)) - { - Message(Chat::Red, "You need at least %u points in mmc to purchase this item.", int32(item->LDoNPrice)); - return; - } - } - else if (item->LDoNTheme & 2) - { - if (m_pp.ldon_points_mir < int32(item->LDoNPrice)) - { - Message(Chat::Red, "You need at least %u points in mir to purchase this item.", int32(item->LDoNPrice)); - return; - } - } - else if (item->LDoNTheme & 1) - { - if (m_pp.ldon_points_guk < int32(item->LDoNPrice)) - { - Message(Chat::Red, "You need at least %u points in guk to purchase this item.", int32(item->LDoNPrice)); - return; + if (item->LDoNTheme <= LDoNThemeBits::TAKBit) { + uint32 ldon_theme; + if (item->LDoNTheme & LDoNThemeBits::TAKBit) { + if (m_pp.ldon_points_tak < item_cost) { + cannot_afford = true; + ldon_theme = LDoNThemes::TAK; + } + } else if (item->LDoNTheme & LDoNThemeBits::RUJBit) { + if (m_pp.ldon_points_ruj < item_cost) { + cannot_afford = true; + ldon_theme = LDoNThemes::RUJ; + } + } else if (item->LDoNTheme & LDoNThemeBits::MMCBit) { + if (m_pp.ldon_points_mmc < item_cost) { + cannot_afford = true; + ldon_theme = LDoNThemes::MMC; + } + } else if (item->LDoNTheme & LDoNThemeBits::MIRBit) { + if (m_pp.ldon_points_mir < item_cost) { + cannot_afford = true; + ldon_theme = LDoNThemes::MIR; + } + } else if (item->LDoNTheme & LDoNThemeBits::GUKBit) { + if (m_pp.ldon_points_guk < item_cost) { + cannot_afford = true; + ldon_theme = LDoNThemes::GUK; } } + + merchant_type = fmt::format( + "{} Point{}", + EQ::constants::GetLDoNThemeName(ldon_theme), + item_cost != 1 ? "s" : "" + ); + } + } else if (aps->Type == DiscordMerchant) { + if (GetPVPPoints() < item_cost) { + cannot_afford = true; + merchant_type = fmt::format( + "PVP Point{}", + item_cost != 1 ? "s" : "" + ); + } + } else if (aps->Type == NorrathsKeepersMerchant) { + if (GetRadiantCrystals() < item_cost) { + cannot_afford = true; + merchant_type = database.CreateItemLink(RuleI(Zone, RadiantCrystalItemID)); + } + } else if (aps->Type == DarkReignMerchant) { + if (GetEbonCrystals() < item_cost) { + cannot_afford = true; + merchant_type = database.CreateItemLink(RuleI(Zone, EbonCrystalItemID)); + } + } else { + Message(Chat::Red, "Unknown Adventure Merchant Type."); + return; + } + + if (cannot_afford && !merchant_type.empty()) { + Message( + Chat::Red, + fmt::format( + "You need at least {} {} to purchase this item.", + item_cost, + merchant_type + ).c_str() + ); + } + + + if (CheckLoreConflict(item)) { + Message( + Chat::Yellow, + fmt::format( + "You already have a lore {} ({}) in your inventory.", + database.CreateItemLink(item->ID), + item->ID + ).c_str() + ); + return; + } + + if (aps->Type == LDoNMerchant) { + int required_points = item_cost * -1; + + if (!UpdateLDoNPoints(6, required_points)) { + return; } } else if (aps->Type == DiscordMerchant) { - if (GetPVPPoints() < item->LDoNPrice) - { - Message(Chat::Red, "You need at least %u PVP points to purchase this item.", int32(item->LDoNPrice)); - return; - } - } - else if (aps->Type == NorrathsKeepersMerchant) - { - if (GetRadiantCrystals() < item->LDoNPrice) - { - Message(Chat::Red, "You need at least %u Radiant Crystals to purchase this item.", int32(item->LDoNPrice)); - return; - } - } - else if (aps->Type == DarkReignMerchant) - { - if (GetEbonCrystals() < item->LDoNPrice) - { - Message(Chat::Red, "You need at least %u Ebon Crystals to purchase this item.", int32(item->LDoNPrice)); - return; - } - } - else - { - Message(Chat::Red, "Unknown Adventure Merchant type."); - return; - } - - - if (CheckLoreConflict(item)) - { - Message(Chat::Yellow, "You can only have one of a lore item."); - return; - } - - if (aps->Type == LDoNMerchant) - { - int32 requiredpts = (int32)item->LDoNPrice*-1; - - if (!UpdateLDoNPoints(requiredpts, 6)) - return; - } - else if (aps->Type == DiscordMerchant) - { - SetPVPPoints(GetPVPPoints() - (int32)item->LDoNPrice); + SetPVPPoints(GetPVPPoints() - item_cost); SendPVPStats(); } else if (aps->Type == NorrathsKeepersMerchant) { - SetRadiantCrystals(GetRadiantCrystals() - (int32)item->LDoNPrice); + SetRadiantCrystals(GetRadiantCrystals() - item_cost); } else if (aps->Type == DarkReignMerchant) { @@ -2125,36 +2120,20 @@ void Client::Handle_OP_AdventureMerchantRequest(const EQApplicationPacket *app) } item = database.GetItem(ml.item); - if (item) - { - uint32 theme; - if (item->LDoNTheme > 16) - { - theme = 0; - } - else if (item->LDoNTheme & 16) - { - theme = 5; - } - else if (item->LDoNTheme & 8) - { - theme = 4; - } - else if (item->LDoNTheme & 4) - { - theme = 3; - } - else if (item->LDoNTheme & 2) - { - theme = 2; - } - else if (item->LDoNTheme & 1) - { - theme = 1; - } - else - { - theme = 0; + if (item) { + uint32 theme = LDoNThemes::Unused; + if (item->LDoNTheme > LDoNThemeBits::TAKBit) { + theme = LDoNThemes::Unused; + } else if (item->LDoNTheme & LDoNThemeBits::TAKBit) { + theme = LDoNThemes::TAK; + } else if (item->LDoNTheme & LDoNThemeBits::RUJBit) { + theme = LDoNThemes::RUJ; + } else if (item->LDoNTheme & LDoNThemeBits::MMCBit) { + theme = LDoNThemes::MMC; + } else if (item->LDoNTheme & LDoNThemeBits::RUJBit) { + theme = LDoNThemes::MIR; + } else if (item->LDoNTheme & LDoNThemeBits::GUKBit) { + theme = LDoNThemes::GUK; } ss << "^" << item->Name << "|"; ss << item->ID << "|"; @@ -2254,7 +2233,7 @@ void Client::Handle_OP_AdventureMerchantSell(const EQApplicationPacket *app) if (!inst->IsStackable()) { - DeleteItemInInventory(ams_in->slot, 0, false); + DeleteItemInInventory(ams_in->slot); } else { @@ -2269,7 +2248,7 @@ void Client::Handle_OP_AdventureMerchantSell(const EQApplicationPacket *app) return; } - DeleteItemInInventory(ams_in->slot, ams_in->charges, false); + DeleteItemInInventory(ams_in->slot, ams_in->charges); price *= ams_in->charges; } @@ -2286,7 +2265,7 @@ void Client::Handle_OP_AdventureMerchantSell(const EQApplicationPacket *app) { case ADVENTUREMERCHANT: { - UpdateLDoNPoints(price, 6); + UpdateLDoNPoints(6, price); break; } case NORRATHS_KEEPERS_MERCHANT: @@ -2462,39 +2441,31 @@ void Client::Handle_OP_AltCurrencyMerchantRequest(const EQApplicationPacket *app { VERIFY_PACKET_LENGTH(OP_AltCurrencyMerchantRequest, app, uint32); - NPC* tar = entity_list.GetNPCByID(*((uint32*)app->pBuffer)); - if (tar) { - if (DistanceSquared(m_Position, tar->GetPosition()) > USE_NPC_RANGE2) - return; - - if (tar->GetClass() != ALT_CURRENCY_MERCHANT) { + auto target = entity_list.GetNPCByID(*((uint32*)app->pBuffer)); + if (target) { + if (DistanceSquared(m_Position, target->GetPosition()) > USE_NPC_RANGE2) { return; } - uint32 alt_cur_id = tar->GetAltCurrencyType(); - if (alt_cur_id == 0) { + if (target->GetClass() != ALT_CURRENCY_MERCHANT) { return; } - auto altc_iter = zone->AlternateCurrencies.begin(); - bool found = false; - while (altc_iter != zone->AlternateCurrencies.end()) { - if ((*altc_iter).id == alt_cur_id) { - found = true; - break; - } - ++altc_iter; + uint32 currency_id = target->GetAltCurrencyType(); + if (!currency_id) { + return; } - if (!found) { + auto currency_item_id = zone->GetCurrencyItemID(currency_id); + if (!currency_item_id) { return; } std::stringstream ss(std::stringstream::in | std::stringstream::out); std::stringstream item_ss(std::stringstream::in | std::stringstream::out); - ss << alt_cur_id << "|1|" << alt_cur_id; + ss << currency_id << "|1|" << currency_id; uint32 count = 0; - uint32 merchant_id = tar->MerchantType; + uint32 merchant_id = target->MerchantType; const EQ::ItemData *item = nullptr; std::list merlist = zone->merchanttable[merchant_id]; @@ -2505,14 +2476,16 @@ void Client::Handle_OP_AltCurrencyMerchantRequest(const EQApplicationPacket *app continue; } - int32 fac = tar->GetPrimaryFaction(); - if (fac != 0 && GetModCharacterFactionLevel(fac) < ml.faction_required) { + auto faction_id = target->GetPrimaryFaction(); + if ( + faction_id && + GetModCharacterFactionLevel(faction_id) < ml.faction_required + ) { continue; } item = database.GetItem(ml.item); - if (item) - { + if (item) { item_ss << "^" << item->Name << "|"; item_ss << item->ID << "|"; item_ss << ml.alt_currency_cost << "|"; @@ -2526,8 +2499,7 @@ void Client::Handle_OP_AltCurrencyMerchantRequest(const EQApplicationPacket *app if (count > 0) { ss << "|" << count << item_ss.str(); - } - else { + } else { ss << "|0"; } @@ -2625,16 +2597,9 @@ void Client::Handle_OP_AltCurrencyReclaim(const EQApplicationPacket *app) { VERIFY_PACKET_LENGTH(OP_AltCurrencyReclaim, app, AltCurrencyReclaim_Struct); AltCurrencyReclaim_Struct *reclaim = (AltCurrencyReclaim_Struct*)app->pBuffer; - uint32 item_id = 0; - auto iter = zone->AlternateCurrencies.begin(); - while (iter != zone->AlternateCurrencies.end()) { - if ((*iter).id == reclaim->currency_id) { - item_id = (*iter).item_id; - } - ++iter; - } + uint32 item_id = zone->GetCurrencyItemID(reclaim->currency_id); - if (item_id == 0) { + if (!item_id) { return; } @@ -2740,7 +2705,7 @@ void Client::Handle_OP_AltCurrencySell(const EQApplicationPacket *app) if (!inst->IsStackable()) { - DeleteItemInInventory(sell->slot_id, 0, false); + DeleteItemInInventory(sell->slot_id); } else { @@ -2755,7 +2720,7 @@ void Client::Handle_OP_AltCurrencySell(const EQApplicationPacket *app) return; } - DeleteItemInInventory(sell->slot_id, sell->charges, false); + DeleteItemInInventory(sell->slot_id, sell->charges); cost *= sell->charges; } @@ -2945,6 +2910,7 @@ void Client::Handle_OP_Assist(const EQApplicationPacket *app) Mob *new_target = assistee->GetTarget(); if (new_target && (GetGM() || Distance(m_Position, assistee->GetPosition()) <= TARGETING_RANGE)) { + cheat_manager.SetExemptStatus(Assist, true); eid->entity_id = new_target->GetID(); } else { eid->entity_id = 0; @@ -2992,18 +2958,16 @@ void Client::Handle_OP_AugmentInfo(const EQApplicationPacket *app) void Client::Handle_OP_AugmentItem(const EQApplicationPacket *app) { if (app->size != sizeof(AugmentItem_Struct)) { - LogError("Invalid size for AugmentItem_Struct: Expected: [{}], Got: [{}]", - sizeof(AugmentItem_Struct), app->size); + LogError("Invalid size for AugmentItem_Struct: Expected: [{}], Got: [{}]", sizeof(AugmentItem_Struct), app->size); return; } AugmentItem_Struct* in_augment = (AugmentItem_Struct*)app->pBuffer; - bool deleteItems = false; - if (ClientVersion() >= EQ::versions::ClientVersion::RoF) - { - if ((in_augment->container_slot < EQ::invslot::EQUIPMENT_BEGIN || in_augment->container_slot > EQ::invslot::GENERAL_END) && - (in_augment->container_slot < EQ::invbag::GENERAL_BAGS_BEGIN || in_augment->container_slot > EQ::invbag::GENERAL_BAGS_END)) - { + if (ClientVersion() >= EQ::versions::ClientVersion::RoF) { + if ( + (in_augment->container_slot < EQ::invslot::EQUIPMENT_BEGIN || in_augment->container_slot > EQ::invslot::GENERAL_END) && + (in_augment->container_slot < EQ::invbag::GENERAL_BAGS_BEGIN || in_augment->container_slot > EQ::invbag::GENERAL_BAGS_END) + ) { Message(Chat::Red, "The server does not allow augmentation actions from this slot."); auto cursor_item = m_inv[EQ::invslot::slotCursor]; auto augmented_item = m_inv[in_augment->container_slot]; @@ -3013,20 +2977,16 @@ void Client::Handle_OP_AugmentItem(const EQApplicationPacket *app) return; } - EQ::ItemInstance *itemOneToPush = nullptr, *itemTwoToPush = nullptr; - - //Log(Logs::DebugLevel::Moderate, Logs::Debug, "cslot: [{}] aslot: [{}] cidx: [{}] aidx: [{}] act: [{}] dest: [{}]", - // in_augment->container_slot, in_augment->augment_slot, in_augment->container_index, in_augment->augment_index, in_augment->augment_action, in_augment->dest_inst_id); + EQ::ItemInstance *item_one_to_push = nullptr, *item_two_to_push = nullptr; EQ::ItemInstance *tobe_auged = nullptr, *old_aug = nullptr, *new_aug = nullptr, *aug = nullptr, *solvent = nullptr; EQ::InventoryProfile& user_inv = GetInv(); uint16 item_slot = in_augment->container_slot; uint16 solvent_slot = in_augment->augment_slot; - uint8 mat = EQ::InventoryProfile::CalcMaterialFromSlot(item_slot); // for when player is augging a piece of equipment while they're wearing it + uint8 material = EQ::InventoryProfile::CalcMaterialFromSlot(item_slot); // for when player is augging a piece of equipment while they're wearing it - if (item_slot == INVALID_INDEX || solvent_slot == INVALID_INDEX) - { + if (item_slot == INVALID_INDEX || solvent_slot == INVALID_INDEX) { Message(Chat::Red, "Error: Invalid Aug Index."); return; } @@ -3034,272 +2994,231 @@ void Client::Handle_OP_AugmentItem(const EQApplicationPacket *app) tobe_auged = user_inv.GetItem(item_slot); solvent = user_inv.GetItem(solvent_slot); - if (!tobe_auged) - { + if (!tobe_auged) { Message(Chat::Red, "Error: Invalid item passed for augmenting."); return; } - if ((in_augment->augment_action == 1) || (in_augment->augment_action == 2)) - { - // Check for valid distiller if safely removing / swapping an augmentation - - if (!solvent) - { + if (in_augment->augment_action == AugmentActions::Remove || in_augment->augment_action == AugmentActions::Swap) { + if (!solvent) { // Check for valid distiller if safely removing / swapping an augmentation old_aug = tobe_auged->GetAugment(in_augment->augment_index); if (!old_aug || old_aug->GetItem()->AugDistiller != 0) { LogError("Player tried to safely remove an augment without a distiller"); Message(Chat::Red, "Error: Missing an augmentation distiller for safely removing this augment."); return; } - } - else if (solvent->GetItem()->ItemType == EQ::item::ItemTypeAugmentationDistiller) - { + } else if (solvent->GetItem()->ItemType == EQ::item::ItemTypeAugmentationDistiller) { old_aug = tobe_auged->GetAugment(in_augment->augment_index); - if (!old_aug) - { + if (!old_aug) { LogError("Player tried to safely remove a nonexistent augment"); Message(Chat::Red, "Error: No augment found in slot %i for safely removing.", in_augment->augment_index); return; - } - else if (solvent->GetItem()->ID != old_aug->GetItem()->AugDistiller) - { + } else if (solvent->GetItem()->ID != old_aug->GetItem()->AugDistiller) { LogError("Player tried to safely remove an augment with the wrong distiller (item [{}] vs expected [{}])", solvent->GetItem()->ID, old_aug->GetItem()->AugDistiller); Message(Chat::Red, "Error: Wrong augmentation distiller for safely removing this augment."); return; } - } - else if (solvent->GetItem()->ItemType != EQ::item::ItemTypePerfectedAugmentationDistiller) - { + } else if (solvent->GetItem()->ItemType != EQ::item::ItemTypePerfectedAugmentationDistiller) { LogError("Player tried to safely remove an augment with a non-distiller item"); Message(Chat::Red, "Error: Invalid augmentation distiller for safely removing this augment."); return; } } - switch (in_augment->augment_action) - { - case 0: // Adding an augment - case 2: // Swapping augment - new_aug = user_inv.GetItem(EQ::invslot::slotCursor); + switch (in_augment->augment_action) { + case AugmentActions::Insert: + case AugmentActions::Swap: + new_aug = user_inv.GetItem(EQ::invslot::slotCursor); - if (!new_aug) // Shouldn't get the OP code without the augment on the user's cursor, but maybe it's h4x. - { - LogError("AugmentItem OpCode with 'Insert' or 'Swap' action received, but no augment on client's cursor"); - Message(Chat::Red, "Error: No augment found on cursor for inserting."); - return; - } - else - { - if (((tobe_auged->IsAugmentSlotAvailable(new_aug->GetAugmentType(), in_augment->augment_index)) != -1) && - (tobe_auged->AvailableWearSlot(new_aug->GetItem()->Slots))) - { - old_aug = tobe_auged->RemoveAugment(in_augment->augment_index); - if (old_aug) - { - // An old augment was removed in order to be replaced with the new one (augment_action 2) - - CalcBonuses(); - - std::vector args; - args.push_back(old_aug); - parse->EventItem(EVENT_UNAUGMENT_ITEM, this, tobe_auged, nullptr, "", in_augment->augment_index, &args); - - args.assign(1, tobe_auged); - args.push_back(false); - parse->EventItem(EVENT_AUGMENT_REMOVE, this, old_aug, nullptr, "", in_augment->augment_index, &args); - } - - tobe_auged->PutAugment(in_augment->augment_index, *new_aug); - tobe_auged->UpdateOrnamentationInfo(); - - aug = tobe_auged->GetAugment(in_augment->augment_index); - if (aug) - { - std::vector args; - args.push_back(aug); - parse->EventItem(EVENT_AUGMENT_ITEM, this, tobe_auged, nullptr, "", in_augment->augment_index, &args); - - args.assign(1, tobe_auged); - parse->EventItem(EVENT_AUGMENT_INSERT, this, aug, nullptr, "", in_augment->augment_index, &args); - } - else - { - Message(Chat::Red, "Error: Could not properly insert augmentation into augment slot %i. Aborting.", in_augment->augment_index); + if (!new_aug) { // Shouldn't get the OP code without the augment on the user's cursor, but maybe it's h4x. + LogError("AugmentItem OpCode with 'Insert' or 'Swap' action received, but no augment on client's cursor"); + Message(Chat::Red, "Error: No augment found on cursor for inserting."); + return; + } else { + if (!RuleB(Inventory, AllowMultipleOfSameAugment) && tobe_auged->ContainsAugmentByID(new_aug->GetID())) { + Message(Chat::Red, "Error: Cannot put multiple of the same augment in an item."); return; } - itemOneToPush = tobe_auged->Clone(); - if (old_aug) - { - itemTwoToPush = old_aug->Clone(); - } - - // Must push items after the items in inventory are deleted - necessary due to lore items... - if (itemOneToPush) - { - DeleteItemInInventory(item_slot, 0, true); - DeleteItemInInventory(EQ::invslot::slotCursor, new_aug->IsStackable() ? 1 : 0, true); - - if (solvent) - { - // Consume the augment distiller - DeleteItemInInventory(solvent_slot, solvent->IsStackable() ? 1 : 0, true); - } - - if (itemTwoToPush) - { - // This is a swap. Return the old aug to the player's cursor. - if (!PutItemInInventory(EQ::invslot::slotCursor, *itemTwoToPush, true)) - { - LogError("Problem returning old augment to player's cursor after augmentation swap"); - Message(Chat::Yellow, "Error: Failed to retrieve old augment after augmentation swap!"); - } - } - - if (PutItemInInventory(item_slot, *itemOneToPush, true)) - { - // Successfully added an augment to the item - + if ( + ((tobe_auged->IsAugmentSlotAvailable(new_aug->GetAugmentType(), in_augment->augment_index)) != -1) && + tobe_auged->AvailableWearSlot(new_aug->GetItem()->Slots) + ) { + old_aug = tobe_auged->RemoveAugment(in_augment->augment_index); + if (old_aug) { // An old augment was removed in order to be replaced with the new one (augment_action 2) CalcBonuses(); - if (mat != EQ::textures::materialInvalid) - { - SendWearChange(mat); // Visible item augged while equipped. Send WC in case ornamentation changed. + std::vector args; + args.push_back(old_aug); + parse->EventItem(EVENT_UNAUGMENT_ITEM, this, tobe_auged, nullptr, "", in_augment->augment_index, &args); + + args.assign(1, tobe_auged); + args.push_back(false); + parse->EventItem(EVENT_AUGMENT_REMOVE, this, old_aug, nullptr, "", in_augment->augment_index, &args); + } + + tobe_auged->PutAugment(in_augment->augment_index, *new_aug); + tobe_auged->UpdateOrnamentationInfo(); + + aug = tobe_auged->GetAugment(in_augment->augment_index); + if (aug) { + std::vector args; + args.push_back(aug); + parse->EventItem(EVENT_AUGMENT_ITEM, this, tobe_auged, nullptr, "", in_augment->augment_index, &args); + + args.assign(1, tobe_auged); + parse->EventItem(EVENT_AUGMENT_INSERT, this, aug, nullptr, "", in_augment->augment_index, &args); + } else { + Message( + Chat::Red, + fmt::format( + "Error: Could not properly insert augmentation into augment slot {}. Aborting.", + in_augment->augment_index + ).c_str() + ); + return; + } + + item_one_to_push = tobe_auged->Clone(); + if (old_aug) { + item_two_to_push = old_aug->Clone(); + } + + if (item_one_to_push) { // Must push items after the items in inventory are deleted - necessary due to lore items... + DeleteItemInInventory(item_slot, 0, true); + DeleteItemInInventory(EQ::invslot::slotCursor, new_aug->IsStackable() ? 1 : 0, true); + + if (solvent) { // Consume the augment distiller + DeleteItemInInventory(solvent_slot, solvent->IsStackable() ? 1 : 0, true); } + + if (item_two_to_push) { // This is a swap. Return the old aug to the player's cursor. + if (!PutItemInInventory(EQ::invslot::slotCursor, *item_two_to_push, true)) { + LogError("Problem returning old augment to player's cursor after augmentation swap"); + Message(Chat::Yellow, "Error: Failed to retrieve old augment after augmentation swap!"); + } + } + + if (PutItemInInventory(item_slot, *item_one_to_push, true)) { // Successfully added an augment to the item + CalcBonuses(); + if (material != EQ::textures::materialInvalid) { // Visible item augged while equipped. Send WC in case ornamentation changed. + SendWearChange(material); + } + } else { + Message(Chat::Red, "Error: No available slot for end result. Please free up the augment slot."); + } + } else { + Message(Chat::Red, "Error in cloning item for augment. Aborted."); } - else - { - Message(Chat::Red, "Error: No available slot for end result. Please free up the augment slot."); - } - } - else - { - Message(Chat::Red, "Error in cloning item for augment. Aborted."); + } else { + Message(Chat::Red, "Error: No available slot for augment in that item."); } } - else - { - Message(Chat::Red, "Error: No available slot for augment in that item."); + break; + case AugmentActions::Remove: + aug = tobe_auged->GetAugment(in_augment->augment_index); + if (aug) { + std::vector args; + args.push_back(aug); + parse->EventItem(EVENT_UNAUGMENT_ITEM, this, tobe_auged, nullptr, "", in_augment->augment_index, &args); + args.assign(1, tobe_auged); + args.push_back(false); + parse->EventItem(EVENT_AUGMENT_REMOVE, this, aug, nullptr, "", in_augment->augment_index, &args); + } else { + Message(Chat::Red, "Error: Could not find augmentation to remove at index %i. Aborting.", in_augment->augment_index); + return; } - } - break; - case 1: // Removing augment safely (distiller) - aug = tobe_auged->GetAugment(in_augment->augment_index); - if (aug) - { - std::vector args; - args.push_back(aug); - parse->EventItem(EVENT_UNAUGMENT_ITEM, this, tobe_auged, nullptr, "", in_augment->augment_index, &args); - args.assign(1, tobe_auged); + old_aug = tobe_auged->RemoveAugment(in_augment->augment_index); + tobe_auged->UpdateOrnamentationInfo(); - args.push_back(false); + item_one_to_push = tobe_auged->Clone(); + if (old_aug) { + item_two_to_push = old_aug->Clone(); + } - parse->EventItem(EVENT_AUGMENT_REMOVE, this, aug, nullptr, "", in_augment->augment_index, &args); - } - else - { - Message(Chat::Red, "Error: Could not find augmentation to remove at index %i. Aborting.", in_augment->augment_index); - return; - } + if (item_one_to_push && item_two_to_push) { + if (solvent) { + DeleteItemInInventory(solvent_slot, solvent->IsStackable() ? 1 : 0, true); // Consume the augment distiller + } - old_aug = tobe_auged->RemoveAugment(in_augment->augment_index); - tobe_auged->UpdateOrnamentationInfo(); + DeleteItemInInventory(item_slot, 0, true); // Remove the augmented item - itemOneToPush = tobe_auged->Clone(); - if (old_aug) - itemTwoToPush = old_aug->Clone(); + if (!PutItemInInventory(item_slot, *item_one_to_push, true)) { // Replace it with the unaugmented item + LogError("Problem returning equipment item to player's inventory after safe augment removal"); + Message(Chat::Yellow, "Error: Failed to return item after de-augmentation!"); + } - if (itemOneToPush && itemTwoToPush) - { - // Consume the augment distiller - if (solvent) - DeleteItemInInventory(solvent_slot, solvent->IsStackable() ? 1 : 0, true); + CalcBonuses(); - // Remove the augmented item - DeleteItemInInventory(item_slot, 0, true); + if (material != EQ::textures::materialInvalid) { + SendWearChange(material); // Visible item augged while equipped. Send WC in case ornamentation changed. + } - // Replace it with the unaugmented item - if (!PutItemInInventory(item_slot, *itemOneToPush, true)) - { - LogError("Problem returning equipment item to player's inventory after safe augment removal"); - Message(Chat::Yellow, "Error: Failed to return item after de-augmentation!"); + // Drop the removed augment on the player's cursor + if (!PutItemInInventory(EQ::invslot::slotCursor, *item_two_to_push, true)) { + LogError("Problem returning augment to player's cursor after safe removal"); + Message(Chat::Yellow, "Error: Failed to return augment after removal from item!"); + return; + } + } + break; + case AugmentActions::Destroy: + // RoF client does not require an augmentation solvent for destroying an augmentation in an item. + // Augments can be destroyed with a right click -> Destroy at any time. + aug = tobe_auged->GetAugment(in_augment->augment_index); + if (aug) { + std::vector args; + args.push_back(aug); + parse->EventItem(EVENT_UNAUGMENT_ITEM, this, tobe_auged, nullptr, "", in_augment->augment_index, &args); + args.assign(1, tobe_auged); + args.push_back(true); + parse->EventItem(EVENT_AUGMENT_REMOVE, this, aug, nullptr, "", in_augment->augment_index, &args); + } else { + Message( + Chat::Red, + fmt::format( + "Error: Could not find augmentation to remove at index {}. Aborting.", + in_augment->augment_index + ).c_str() + ); + return; + } + + tobe_auged->DeleteAugment(in_augment->augment_index); + tobe_auged->UpdateOrnamentationInfo(); + + item_one_to_push = tobe_auged->Clone(); + if (item_one_to_push) { + DeleteItemInInventory(item_slot, 0, true); + + if (!PutItemInInventory(item_slot, *item_one_to_push, true)) { + LogError("Problem returning equipment item to player's inventory after augment deletion"); + Message(Chat::Yellow, "Error: Failed to return item after destroying augment!"); + } } CalcBonuses(); - if (mat != EQ::textures::materialInvalid) - { - SendWearChange(mat); // Visible item augged while equipped. Send WC in case ornamentation changed. + if (material != EQ::textures::materialInvalid) { + SendWearChange(material); } - - // Drop the removed augment on the player's cursor - if (!PutItemInInventory(EQ::invslot::slotCursor, *itemTwoToPush, true)) - { - LogError("Problem returning augment to player's cursor after safe removal"); - Message(Chat::Yellow, "Error: Failed to return augment after removal from item!"); - return; - } - } - break; - case 3: // Destroying augment (formerly done in birdbath/sealer with a solvent) - - // RoF client does not require an augmentation solvent for destroying an augmentation in an item. - // Augments can be destroyed with a right click -> Destroy at any time. - - aug = tobe_auged->GetAugment(in_augment->augment_index); - if (aug) - { - std::vector args; - args.push_back(aug); - parse->EventItem(EVENT_UNAUGMENT_ITEM, this, tobe_auged, nullptr, "", in_augment->augment_index, &args); - - args.assign(1, tobe_auged); - - args.push_back(true); - - parse->EventItem(EVENT_AUGMENT_REMOVE, this, aug, nullptr, "", in_augment->augment_index, &args); - } - else - { - Message(Chat::Red, "Error: Could not find augmentation to remove at index %i. Aborting."); - return; - } - - tobe_auged->DeleteAugment(in_augment->augment_index); - tobe_auged->UpdateOrnamentationInfo(); - - itemOneToPush = tobe_auged->Clone(); - if (itemOneToPush) - { - DeleteItemInInventory(item_slot, 0, true); - - if (!PutItemInInventory(item_slot, *itemOneToPush, true)) - { - LogError("Problem returning equipment item to player's inventory after augment deletion"); - Message(Chat::Yellow, "Error: Failed to return item after destroying augment!"); - } - } - - CalcBonuses(); - - if (mat != EQ::textures::materialInvalid) - { - SendWearChange(mat); - } - break; - default: // Unknown - LogInventory("Unrecognized augmentation action - cslot: [{}] aslot: [{}] cidx: [{}] aidx: [{}] act: [{}] dest: [{}]", - in_augment->container_slot, in_augment->augment_slot, in_augment->container_index, in_augment->augment_index, in_augment->augment_action, in_augment->dest_inst_id); - break; + break; + default: // Unknown + LogInventory( + "Unrecognized augmentation action - cslot: [{}] aslot: [{}] cidx: [{}] aidx: [{}] act: [{}] dest: [{}]", + in_augment->container_slot, + in_augment->augment_slot, + in_augment->container_index, + in_augment->augment_index, + in_augment->augment_action, + in_augment->dest_inst_id + ); + break; } - } - else - { - // Delegate to tradeskill object to perform combine - Object::HandleAugmentation(this, in_augment, m_tradeskill_object); + } else { + Object::HandleAugmentation(this, in_augment, m_tradeskill_object); // Delegate to tradeskill object to perform combine } return; } @@ -3361,8 +3280,18 @@ void Client::Handle_OP_AutoFire(const EQApplicationPacket *app) DumpPacket(app); return; } + + if (GetTarget() == this) { + MessageString(Chat::TooFarAway, TRY_ATTACKING_SOMEONE); + auto_fire = false; + return; + } + bool *af = (bool*)app->pBuffer; auto_fire = *af; + if(!RuleB(Character, EnableRangerAutoFire)) + auto_fire = false; + auto_attack = false; SetAttackTimer(); } @@ -3931,7 +3860,7 @@ void Client::Handle_OP_Buff(const EQApplicationPacket *app) //something about IsDetrimentalSpell() crashes this portion of code.. //tbh we shouldn't use it anyway since this is a simple red vs blue buff check and //isdetrimentalspell() is much more complex - if (spid == 0xFFFF || (IsValidSpell(spid) && (spells[spid].goodEffect == 0))) + if (spid == 0xFFFF || (IsValidSpell(spid) && (spells[spid].good_effect == 0))) QueuePacket(app); else BuffFadeBySpellID(spid); @@ -3956,6 +3885,10 @@ void Client::Handle_OP_BuffRemoveRequest(const EQApplicationPacket *app) else if (brrs->EntityID == GetPetID()) { m = GetPet(); } + else if (GetGM()) + { + m = entity_list.GetMobID(brrs->EntityID); + } #ifdef BOTS else { Mob* bot_test = entity_list.GetMob(brrs->EntityID); @@ -3972,8 +3905,9 @@ void Client::Handle_OP_BuffRemoveRequest(const EQApplicationPacket *app) uint16 SpellID = m->GetSpellIDFromSlot(brrs->SlotID); - if (SpellID && IsBeneficialSpell(SpellID) && !spells[SpellID].no_remove) + if (SpellID && (GetGM() || ((IsBeneficialSpell(SpellID) || IsEffectInSpell(SpellID, SE_BindSight)) && !spells[SpellID].no_remove))) { m->BuffFadeBySlot(brrs->SlotID, true); + } } void Client::Handle_OP_Bug(const EQApplicationPacket *app) @@ -4113,7 +4047,12 @@ void Client::Handle_OP_CastSpell(const EQApplicationPacket *app) return; } - CastSpell(spell_to_cast, castspell->target_id, slot); + if (IsValidSpell(spell_to_cast)) { + CastSpell(spell_to_cast, castspell->target_id, slot); + } + else { + InterruptSpell(); + } } /* Spell Slot or Potion Belt Slot */ else if (slot == CastingSlot::Item || slot == CastingSlot::PotionBelt) // ITEM or POTION cast @@ -4141,7 +4080,6 @@ void Client::Handle_OP_CastSpell(const EQApplicationPacket *app) { EQ::ItemInstance* p_inst = (EQ::ItemInstance*)inst; int i = parse->EventItem(EVENT_ITEM_CLICK_CAST, this, p_inst, nullptr, "", castspell->inventoryslot); - if (i == 0) { CastSpell(item->Click.Effect, castspell->target_id, slot, item->CastTime, 0, 0, castspell->inventoryslot); } @@ -4240,7 +4178,7 @@ void Client::Handle_OP_ChannelMessage(const EQApplicationPacket *app) std::cout << "Wrong size " << app->size << ", should be " << sizeof(ChannelMessage_Struct) << "+ on 0x" << std::hex << std::setfill('0') << std::setw(4) << app->GetOpcode() << std::dec << std::endl; return; } - if (IsAIControlled()) { + if (IsAIControlled() && !GetGM()) { Message(Chat::Red, "You try to speak but cant move your mouth!"); return; } @@ -4315,12 +4253,24 @@ void Client::Handle_OP_ClickDoor(const EQApplicationPacket *app) return; } - char buf[20]; - snprintf(buf, 19, "%u", cd->doorid); - buf[19] = '\0'; + // set door selected + if (IsDevToolsEnabled()) { + SetDoorToolEntityId(currentdoor->GetEntityID()); + DoorManipulation::CommandHeader(this); + Message( + Chat::White, + fmt::format( + "Door ({}) [{}]", + currentdoor->GetEntityID(), + EQ::SayLinkEngine::GenerateQuestSaylink("#door edit", false, "#door edit") + ).c_str() + ); + } + + std::string export_string = fmt::format("{}", cd->doorid); std::vector args; args.push_back(currentdoor); - parse->EventPlayer(EVENT_CLICK_DOOR, this, buf, 0, &args); + parse->EventPlayer(EVENT_CLICK_DOOR, this, export_string, 0, &args); currentdoor->HandleClick(this, 0); return; @@ -4344,10 +4294,8 @@ void Client::Handle_OP_ClickObject(const EQApplicationPacket *app) std::vector args; args.push_back(object); - char buf[10]; - snprintf(buf, 9, "%u", click_object->drop_id); - buf[9] = '\0'; - parse->EventPlayer(EVENT_CLICK_OBJECT, this, buf, GetID(), &args); + std::string export_string = fmt::format("{}", click_object->drop_id); + parse->EventPlayer(EVENT_CLICK_OBJECT, this, export_string, GetID(), &args); } // Observed in RoF after OP_ClickObjectAction: @@ -4438,27 +4386,27 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) { PlayerPositionUpdateClient_Struct *ppu = (PlayerPositionUpdateClient_Struct *) app->pBuffer; - /* Boat handling */ - if (ppu->spawn_id != GetID()) { - /* If player is controlling boat */ - if (ppu->spawn_id && ppu->spawn_id == controlling_boat_id) { - Mob *boat = entity_list.GetMob(controlling_boat_id); - if (boat == 0) { - controlling_boat_id = 0; - return; - } + /* Non PC handling like boats and eye of zomm */ + if (ppu->spawn_id && ppu->spawn_id != GetID()) { + Mob *cmob = entity_list.GetMob(ppu->spawn_id); + if (!cmob) { + return; + } + + if (cmob->IsControllableBoat()) { + // Controllable boats auto boat_delta = glm::vec4(ppu->delta_x, ppu->delta_y, ppu->delta_z, EQ10toFloat(ppu->delta_heading)); - boat->SetDelta(boat_delta); + cmob->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); + cmob->MakeSpawnUpdate(ppus); + entity_list.QueueCloseClients(cmob, 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); + cmob->GMMove(ppu->x_pos, ppu->y_pos, ppu->z_pos, EQ12toFloat(ppu->heading), false); return; } else { @@ -4466,16 +4414,13 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) { // so that other clients see it. I could add a check here for eye of zomm // race, to limit this code, but this should handle any client controlled // mob that gets updates from OP_ClientUpdate - if (ppu->spawn_id == controlled_mob_id) { - Mob *cmob = entity_list.GetMob(ppu->spawn_id); - 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, - 0.0, 0, ClientRangeAny, nullptr, this); - cmob->CastToNPC()->SaveGuardSpot(glm::vec4(ppu->x_pos, - ppu->y_pos, ppu->z_pos, EQ12toFloat(ppu->heading))); - } + if (!cmob->IsControllableBoat() && ppu->spawn_id == controlled_mob_id) { + 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, + 0.0, 0, ClientRangeAny, nullptr, this); + cmob->CastToNPC()->SaveGuardSpot(glm::vec4(ppu->x_pos, + ppu->y_pos, ppu->z_pos, EQ12toFloat(ppu->heading))); } } return; @@ -4503,13 +4448,31 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) { LogError("Can't find boat for client position offset."); } else { - cx += boat->GetX(); - cy += boat->GetY(); + if (boat->turning) return; + + // Calculate angle from boat heading to EQ heading + double theta = std::fmod(((boat->GetHeading() * 360.0) / 512.0),360.0); + double thetar = (theta * M_PI) / 180.0; + + // Boat cx is inverted (positive to left) + // Boat cy is normal (positive toward heading) + double cosine = std::cos(thetar); + double sine = std::sin(thetar); + + double normalizedx, normalizedy; + normalizedx = cx * cosine - -cy * sine; + normalizedy = -cx * sine + cy * cosine; + + cx = boat->GetX() + normalizedx; + cy = boat->GetY() + normalizedy; cz += boat->GetZ(); + new_heading += boat->GetHeading(); } } + cheat_manager.MovementCheck(glm::vec3(cx, cy, cz)); + if (IsDraggingCorpse()) DragCorpses(); @@ -4703,7 +4666,7 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) { MakeSpawnUpdate(position_update); if (gm_hide_me) { - entity_list.QueueClientsStatus(this, outapp, true, Admin(), 255); + entity_list.QueueClientsStatus(this, outapp, true, Admin(), AccountStatus::Max); } else { entity_list.QueueCloseClients(this, outapp, true, RuleI(Range, ClientPositionUpdates), nullptr, true); } @@ -4783,6 +4746,11 @@ void Client::Handle_OP_Consider(const EQApplicationPacket *app) if (tmob == 0) return; + std::string export_string = fmt::format("{}", conin->targetid); + if (parse->EventPlayer(EVENT_CONSIDER, this, export_string, 0) == 1) { + return; + } + if (tmob->GetClass() == LDON_TREASURE) { Message(Chat::Yellow, "%s", tmob->GetCleanName()); @@ -4817,7 +4785,7 @@ void Client::Handle_OP_Consider(const EQApplicationPacket *app) if (tmob->IsNPC()) { if (GetFeigned()) - con->faction = FACTION_INDIFFERENT; + con->faction = FACTION_INDIFFERENTLY; } if (!(con->faction == FACTION_SCOWLS)) @@ -4825,21 +4793,21 @@ void Client::Handle_OP_Consider(const EQApplicationPacket *app) if (tmob->IsNPC()) { if (tmob->CastToNPC()->IsOnHatelist(this)) - con->faction = FACTION_THREATENLY; + con->faction = FACTION_THREATENINGLY; } } - if (con->faction == FACTION_APPREHENSIVE) { + if (con->faction == FACTION_APPREHENSIVELY) { con->faction = FACTION_SCOWLS; } - else if (con->faction == FACTION_DUBIOUS) { - con->faction = FACTION_THREATENLY; + else if (con->faction == FACTION_DUBIOUSLY) { + con->faction = FACTION_THREATENINGLY; } else if (con->faction == FACTION_SCOWLS) { - con->faction = FACTION_APPREHENSIVE; + con->faction = FACTION_APPREHENSIVELY; } - else if (con->faction == FACTION_THREATENLY) { - con->faction = FACTION_DUBIOUS; + else if (con->faction == FACTION_THREATENINGLY) { + con->faction = FACTION_DUBIOUSLY; } mod_consider(tmob, con); @@ -4898,61 +4866,44 @@ void Client::Handle_OP_Consider(const EQApplicationPacket *app) void Client::Handle_OP_ConsiderCorpse(const EQApplicationPacket *app) { - if (app->size != sizeof(Consider_Struct)) - { + if (app->size != sizeof(Consider_Struct)) { LogDebug("Size mismatch in Consider corpse expected [{}] got [{}]", sizeof(Consider_Struct), app->size); return; } + Consider_Struct* conin = (Consider_Struct*)app->pBuffer; - Corpse* tcorpse = entity_list.GetCorpseByID(conin->targetid); - if (tcorpse && tcorpse->IsNPCCorpse()) { - uint32 min; uint32 sec; uint32 ttime; - if ((ttime = tcorpse->GetDecayTime()) != 0) { - sec = (ttime / 1000) % 60; // Total seconds - min = (ttime / 60000) % 60; // Total seconds / 60 drop .00 - char val1[20] = { 0 }; - char val2[20] = { 0 }; - MessageString(Chat::NPCQuestSay, CORPSE_DECAY1, ConvertArray(min, val1), ConvertArray(sec, val2)); - } - else { - MessageString(Chat::NPCQuestSay, CORPSE_DECAY_NOW); - } + Corpse* target = entity_list.GetCorpseByID(conin->targetid); + std::string export_string = fmt::format("{}", conin->targetid); + if (!target) { + return; } - else if (tcorpse && tcorpse->IsPlayerCorpse()) { - uint32 day, hour, min, sec, ttime; - if ((ttime = tcorpse->GetDecayTime()) != 0) { - sec = (ttime / 1000) % 60; // Total seconds - min = (ttime / 60000) % 60; // Total seconds - hour = (ttime / 3600000) % 24; // Total hours - day = ttime / 86400000; // Total Days - if (day) - Message(0, "This corpse will decay in %i days, %i hours, %i minutes and %i seconds.", day, hour, min, sec); - else if (hour) - Message(0, "This corpse will decay in %i hours, %i minutes and %i seconds.", hour, min, sec); - else - Message(0, "This corpse will decay in %i minutes and %i seconds.", min, sec); - Message(0, "This corpse %s be resurrected.", tcorpse->IsRezzed() ? "cannot" : "can"); - /* - hour = 0; + if (parse->EventPlayer(EVENT_CONSIDER_CORPSE, this, export_string, 0)) { + return; + } - if((ttime = tcorpse->GetResTime()) != 0) { - sec = (ttime/1000)%60; // Total seconds - min = (ttime/60000)%60; // Total seconds - hour = (ttime/3600000)%24; // Total hours - if(hour) - Message(0, "This corpse can be resurrected for %i hours, %i minutes and %i seconds.", hour, min, sec); - else - Message(0, "This corpse can be resurrected for %i minutes and %i seconds.", min, sec); - } - else { - MessageString(Chat::White, CORPSE_TOO_OLD); - } - */ - } - else { - MessageString(Chat::NPCQuestSay, CORPSE_DECAY_NOW); + uint32 decay_time = target->GetDecayTime(); + if (decay_time) { + auto time_string = ConvertSecondsToTime(decay_time, true); + Message( + Chat::NPCQuestSay, + fmt::format( + "This corpse will decay in {}.", + time_string + ).c_str() + ); + + if (target->IsPlayerCorpse()) { + Message( + Chat::NPCQuestSay, + fmt::format( + "This corpse {} be resurrected.", + target->IsRezzed() ? "cannot" : "can" + ).c_str() + ); } + } else { + MessageString(Chat::NPCQuestSay, CORPSE_DECAY_NOW); } } @@ -5488,8 +5439,11 @@ void Client::Handle_OP_DisarmTraps(const EQApplicationPacket *app) } else { + int fail_rate = 25; + int trap_circumvention = spellbonuses.TrapCircumvention + itembonuses.TrapCircumvention + aabonuses.TrapCircumvention; + fail_rate -= fail_rate * trap_circumvention / 100; MessageString(Chat::Skills, FAIL_DISARM_DETECTED_TRAP); - if (zone->random.Int(0, 99) < 25) { + if (zone->random.Int(0, 99) < fail_rate) { trap->Trigger(this); } } @@ -5570,37 +5524,57 @@ void Client::Handle_OP_DoGroupLeadershipAbility(const EQApplicationPacket *app) } } -void Client::Handle_OP_DuelResponse(const EQApplicationPacket *app) +void Client::Handle_OP_DuelDecline(const EQApplicationPacket *app) { - if (app->size != sizeof(DuelResponse_Struct)) + if (app->size != sizeof(DuelResponse_Struct)) { return; + } + DuelResponse_Struct* ds = (DuelResponse_Struct*)app->pBuffer; + if (!ds->target_id || !ds->entity_id) { + return; + } + Entity* entity = entity_list.GetID(ds->target_id); Entity* initiator = entity_list.GetID(ds->entity_id); - if (!entity->IsClient() || !initiator->IsClient()) + if (!entity->IsClient() || !initiator->IsClient()) { return; + } entity->CastToClient()->SetDuelTarget(0); entity->CastToClient()->SetDueling(false); initiator->CastToClient()->SetDuelTarget(0); initiator->CastToClient()->SetDueling(false); - if (GetID() == initiator->GetID()) + if (GetID() == initiator->GetID()) { entity->CastToClient()->MessageString(Chat::NPCQuestSay, DUEL_DECLINE, initiator->GetName()); - else + } else { initiator->CastToClient()->MessageString(Chat::NPCQuestSay, DUEL_DECLINE, entity->GetName()); + } return; } -void Client::Handle_OP_DuelResponse2(const EQApplicationPacket *app) +void Client::Handle_OP_DuelAccept(const EQApplicationPacket *app) { - if (app->size != sizeof(Duel_Struct)) + if (app->size != sizeof(Duel_Struct)) { return; + } Duel_Struct* ds = (Duel_Struct*)app->pBuffer; + if (!ds->duel_initiator || !ds->duel_target) { + return; + } + Entity* entity = entity_list.GetID(ds->duel_target); Entity* initiator = entity_list.GetID(ds->duel_initiator); + if (!entity || !initiator) { + return; + } - if (entity && initiator && entity == this && initiator->IsClient()) { + if (GetDuelTarget() != ds->duel_initiator || IsDueling()) { + return; + } + + if (entity == this && initiator->IsClient()) { auto outapp = new EQApplicationPacket(OP_RequestDuel, sizeof(Duel_Struct)); Duel_Struct* ds2 = (Duel_Struct*)outapp->pBuffer; @@ -5608,7 +5582,7 @@ void Client::Handle_OP_DuelResponse2(const EQApplicationPacket *app) ds2->duel_target = entity->GetID(); initiator->CastToClient()->QueuePacket(outapp); - outapp->SetOpcode(OP_DuelResponse2); + outapp->SetOpcode(OP_DuelAccept); ds2->duel_initiator = initiator->GetID(); initiator->CastToClient()->QueuePacket(outapp); @@ -5619,10 +5593,13 @@ void Client::Handle_OP_DuelResponse2(const EQApplicationPacket *app) SetDuelTarget(ds->duel_initiator); safe_delete(outapp); - if (IsCasting()) + if (IsCasting()) { InterruptSpell(); - if (initiator->CastToClient()->IsCasting()) + } + + if (initiator->CastToClient()->IsCasting()) { initiator->CastToClient()->InterruptSpell(); + } } return; } @@ -5834,8 +5811,7 @@ void Client::Handle_OP_EndLootRequest(const EQApplicationPacket *app) void Client::Handle_OP_EnvDamage(const EQApplicationPacket *app) { - if (!ClientFinishedLoading()) - { + if (!ClientFinishedLoading()) { SetHP(GetHP() - 1); return; } @@ -5845,42 +5821,54 @@ void Client::Handle_OP_EnvDamage(const EQApplicationPacket *app) DumpPacket(app); return; } + EnvDamage2_Struct* ed = (EnvDamage2_Struct*)app->pBuffer; - - int damage = ed->damage; - - if (ed->dmgtype == 252) { - - int mod = spellbonuses.ReduceFallDamage + itembonuses.ReduceFallDamage + aabonuses.ReduceFallDamage; - + auto damage = ed->damage; + if (ed->dmgtype == EQ::constants::EnvironmentalDamage::Falling) { + uint32 mod = spellbonuses.ReduceFallDamage + itembonuses.ReduceFallDamage + aabonuses.ReduceFallDamage; damage -= damage * mod / 100; } - if (damage < 0) + if (damage < 0) { damage = 31337; + } if (admin >= minStatusToAvoidFalling && GetGM()) { - Message(Chat::Red, "Your GM status protects you from %i points of type %i environmental damage.", ed->damage, ed->dmgtype); + Message( + Chat::Red, + fmt::format( + "Your GM status protects you from {} points of {} (Type {}) damage.", + ed->damage, + EQ::constants::GetEnvironmentalDamageName(ed->dmgtype), + ed->dmgtype + ).c_str() + ); SetHP(GetHP() - 1);//needed or else the client wont acknowledge return; - } - else if (GetInvul()) { - Message(Chat::Red, "Your invuln status protects you from %i points of type %i environmental damage.", ed->damage, ed->dmgtype); + } else if (GetInvul()) { + Message( + Chat::Red, + fmt::format( + "Your invulnerability protects you from {} points of {} (Type {}) damage.", + ed->damage, + EQ::constants::GetEnvironmentalDamageName(ed->dmgtype), + ed->dmgtype + ).c_str() + ); SetHP(GetHP() - 1);//needed or else the client wont acknowledge return; - } - else if (zone->GetZoneID() == 183 || zone->GetZoneID() == 184) { - // Hard coded tutorial and load zones for no fall damage + } else if (zone->GetZoneID() == Zones::TUTORIAL || zone->GetZoneID() == Zones::LOAD) { // Hard coded tutorial and load zones for no fall damage return; - } - else { + } else { SetHP(GetHP() - (damage * RuleR(Character, EnvironmentDamageMulipliter))); - - /* EVENT_ENVIRONMENTAL_DAMAGE */ int final_damage = (damage * RuleR(Character, EnvironmentDamageMulipliter)); - char buf[24]; - snprintf(buf, 23, "%u %u %i", ed->damage, ed->dmgtype, final_damage); - parse->EventPlayer(EVENT_ENVIRONMENTAL_DAMAGE, this, buf, 0); + std::string export_string = fmt::format( + "{} {} {}", + ed->damage, + ed->dmgtype, + final_damage + ); + parse->EventPlayer(EVENT_ENVIRONMENTAL_DAMAGE, this, export_string, 0); } if (GetHP() <= 0) { @@ -5921,8 +5909,10 @@ void Client::Handle_OP_FaceChange(const EQApplicationPacket *app) void Client::Handle_OP_FeignDeath(const EQApplicationPacket *app) { - if (GetClass() != MONK) + if (!HasSkill(EQ::skills::SkillFeignDeath)) { return; + } + if (!p_timers.Expired(&database, pTimerFeignDeath, false)) { Message(Chat::Red, "Ability recovery time not yet met."); return; @@ -6610,34 +6600,45 @@ void Client::Handle_OP_GMZoneRequest(const EQApplicationPacket *app) } GMZoneRequest_Struct* gmzr = (GMZoneRequest_Struct*)app->pBuffer; - float tarx = -1, tary = -1, tarz = -1; + float target_x = -1, target_y = -1, target_z = -1, target_heading; - int16 minstatus = 0; - uint8 minlevel = 0; - char tarzone[32]; - uint16 zid = gmzr->zone_id; + int16 min_status = AccountStatus::Player; + uint8 min_level = 0; + char target_zone[32]; + uint16 zone_id = gmzr->zone_id; if (gmzr->zone_id == 0) - zid = zonesummon_id; - const char * zname = ZoneName(zid); - if (zname == nullptr) - tarzone[0] = 0; + zone_id = zonesummon_id; + + const char* zone_short_name = ZoneName(zone_id); + if (zone_short_name == nullptr) + target_zone[0] = 0; else - strcpy(tarzone, zname); + strcpy(target_zone, zone_short_name); // this both loads the safe points and does a sanity check on zone name - if (!content_db.GetSafePoints(tarzone, 0, &tarx, &tary, &tarz, &minstatus, &minlevel)) { - tarzone[0] = 0; + if (!content_db.GetSafePoints( + target_zone, + 0, + &target_x, + &target_y, + &target_z, + &target_heading, + &min_status, + &min_level + )) { + target_zone[0] = 0; } auto outapp = new EQApplicationPacket(OP_GMZoneRequest, sizeof(GMZoneRequest_Struct)); GMZoneRequest_Struct* gmzr2 = (GMZoneRequest_Struct*)outapp->pBuffer; strcpy(gmzr2->charname, this->GetName()); gmzr2->zone_id = gmzr->zone_id; - gmzr2->x = tarx; - gmzr2->y = tary; - gmzr2->z = tarz; + gmzr2->x = target_x; + gmzr2->y = target_y; + gmzr2->z = target_z; + gmzr2->heading = target_heading; // Next line stolen from ZoneChange as well... - This gives us a nicer message than the normal "zone is down" message... - if (tarzone[0] != 0 && admin >= minstatus && GetLevel() >= minlevel) + if (target_zone[0] != 0 && admin >= min_status && GetLevel() >= min_level) gmzr2->success = 1; else { std::cout << "GetZoneSafeCoords failed. zoneid = " << gmzr->zone_id << "; czone = " << zone->GetZoneID() << std::endl; @@ -6986,6 +6987,13 @@ void Client::Handle_OP_GroupInvite2(const EQApplicationPacket *app) Invitee->CastToClient()->QueuePacket(app); } } + else { + if (RuleB(Character, OnInviteReceiveAlreadyinGroupMessage)) { + if (!Invitee->CastToClient()->MercOnlyOrNoGroup()) { + Message(Chat::LightGray, "%s is already in another group.", Invitee->GetCleanName()); + } + } + } } #ifdef BOTS else if (Invitee->IsBot()) { @@ -7736,16 +7744,30 @@ void Client::Handle_OP_GuildInviteAccept(const EQApplicationPacket *app) { //dont care if the check fails (since we dont know the rank), just want to clear the entry. guild_mgr.VerifyAndClearInvite(CharacterID(), gj->guildeqid, gj->response); - worldserver.SendEmoteMessage(gj->inviter, 0, 0, "%s has declined to join the guild.", this->GetName()); + worldserver.SendEmoteMessage( + gj->inviter, + 0, + Chat::White, + fmt::format( + "{} has declined to join the guild.", + GetCleanName() + ).c_str() + ); return; } } if (gj->response == 5 || gj->response == 4) { //dont care if the check fails (since we dont know the rank), just want to clear the entry. guild_mgr.VerifyAndClearInvite(CharacterID(), gj->guildeqid, gj->response); - - worldserver.SendEmoteMessage(gj->inviter, 0, 0, "%s has declined to join the guild.", this->GetName()); - + worldserver.SendEmoteMessage( + gj->inviter, + 0, + Chat::White, + fmt::format( + "{} has declined to join the guild.", + GetCleanName() + ).c_str() + ); return; } @@ -7777,7 +7799,15 @@ void Client::Handle_OP_GuildInviteAccept(const EQApplicationPacket *app) //we dont really care a lot about what this packet means, as long as //it has been authorized with the guild manager if (!guild_mgr.VerifyAndClearInvite(CharacterID(), gj->guildeqid, guildrank)) { - worldserver.SendEmoteMessage(gj->inviter, 0, 0, "%s has sent an invalid response to your invite!", GetName()); + worldserver.SendEmoteMessage( + gj->inviter, + 0, + Chat::White, + fmt::format( + "{} has sent an invalid response to your invite!", + GetCleanName() + ).c_str() + ); Message(Chat::Red, "Invalid invite response packet!"); return; } @@ -8483,18 +8513,18 @@ void Client::Handle_OP_ItemLinkClick(const EQApplicationPacket *app) if (GetTarget() && GetTarget()->IsNPC()) { if (silentsaylink) { - parse->EventNPC(EVENT_SAY, GetTarget()->CastToNPC(), this, response.c_str(), 0); + parse->EventNPC(EVENT_SAY, GetTarget()->CastToNPC(), this, response, 0); if (response[0] == '#' && parse->PlayerHasQuestSub(EVENT_COMMAND)) { - parse->EventPlayer(EVENT_COMMAND, this, response.c_str(), 0); + parse->EventPlayer(EVENT_COMMAND, this, response, 0); } #ifdef BOTS else if (response[0] == '^' && parse->PlayerHasQuestSub(EVENT_BOT_COMMAND)) { - parse->EventPlayer(EVENT_BOT_COMMAND, this, response.c_str(), 0); + parse->EventPlayer(EVENT_BOT_COMMAND, this, response, 0); } #endif else { - parse->EventPlayer(EVENT_SAY, this, response.c_str(), 0); + parse->EventPlayer(EVENT_SAY, this, response, 0); } } else { @@ -8506,15 +8536,15 @@ void Client::Handle_OP_ItemLinkClick(const EQApplicationPacket *app) else { if (silentsaylink) { if (response[0] == '#' && parse->PlayerHasQuestSub(EVENT_COMMAND)) { - parse->EventPlayer(EVENT_COMMAND, this, response.c_str(), 0); + parse->EventPlayer(EVENT_COMMAND, this, response, 0); } #ifdef BOTS else if (response[0] == '^' && parse->PlayerHasQuestSub(EVENT_BOT_COMMAND)) { - parse->EventPlayer(EVENT_BOT_COMMAND, this, response.c_str(), 0); + parse->EventPlayer(EVENT_BOT_COMMAND, this, response, 0); } #endif else { - parse->EventPlayer(EVENT_SAY, this, response.c_str(), 0); + parse->EventPlayer(EVENT_SAY, this, response, 0); } } else { @@ -8769,6 +8799,7 @@ void Client::Handle_OP_ItemVerifyRequest(const EQApplicationPacket *app) slot_id = request->slot; target_id = request->target; + cheat_manager.ProcessItemVerifyRequest(request->slot, request->target); EQApplicationPacket *outapp = nullptr; outapp = new EQApplicationPacket(OP_ItemVerifyReply, sizeof(ItemVerifyReply_Struct)); @@ -8805,33 +8836,46 @@ void Client::Handle_OP_ItemVerifyRequest(const EQApplicationPacket *app) } spell_id = item->Click.Effect; + bool is_casting_bard_song = false; - if - ( - spell_id > 0 && - ( - !IsValidSpell(spell_id) || - casting_spell_id || - delaytimer || - spellend_timer.Enabled() || - IsStunned() || - IsFeared() || - IsMezzed() || - DivineAura() || - (spells[spell_id].targettype == ST_Ring) || - (IsSilenced() && !IsDiscipline(spell_id)) || - (IsAmnesiad() && IsDiscipline(spell_id)) || - (IsDetrimentalSpell(spell_id) && !zone->CanDoCombat()) || - (inst->IsScaling() && inst->GetExp() <= 0) // charms don't have spells when less than 0 - ) - ) - { - SendSpellBarEnable(spell_id); - return; + if (spell_id > 0) { + + if (!IsValidSpell(spell_id) || + IsStunned() || + IsFeared() || + IsMezzed() || + DivineAura() || + (spells[spell_id].target_type == ST_Ring) || + (IsSilenced() && !IsDiscipline(spell_id)) || + (IsAmnesiad() && IsDiscipline(spell_id)) || + (IsDetrimentalSpell(spell_id) && !zone->CanDoCombat()) || + (inst->IsScaling() && inst->GetExp() <= 0)) { // charms don't have spells when less than 0 + + SendSpellBarEnable(spell_id); + return; + } + + if (casting_spell_id || + delaytimer || + spellend_timer.Enabled()) { + + /* + Bards on live can click items while casting spell gems, it stops that song cast and replaces it with item click cast. + Can not click while casting other items. + */ + if (GetClass() == BARD && IsCasting() && casting_spell_slot < CastingSlot::MaxGems) + { + is_casting_bard_song = true; + } + else + { + SendSpellBarEnable(spell_id); + return; + } + } } - // Modern clients don't require pet targeted for item clicks that are ST_Pet - if (spell_id > 0 && (spells[spell_id].targettype == ST_Pet || spells[spell_id].targettype == ST_SummonedPet)) + if (spell_id > 0 && (spells[spell_id].target_type == ST_Pet || spells[spell_id].target_type == ST_SummonedPet)) target_id = GetPetID(); LogDebug("OP ItemVerifyRequest: spell=[{}], target=[{}], inv=[{}]", spell_id, target_id, slot_id); @@ -8883,7 +8927,12 @@ void Client::Handle_OP_ItemVerifyRequest(const EQApplicationPacket *app) } else if (item->ItemType == EQ::item::ItemTypeSpell) { - return; + if (RuleB(Spells, AllowSpellMemorizeFromItem)) { + DeleteItemInInventory(slot_id, 1, true); + MemorizeSpellFromItem(item->ID); + } else { + return; + } } else if ((item->Click.Type == EQ::item::ItemEffectClick) || (item->Click.Type == EQ::item::ItemEffectExpendable) || (item->Click.Type == EQ::item::ItemEffectEquipClick) || (item->Click.Type == EQ::item::ItemEffectClick2)) { @@ -8895,17 +8944,33 @@ void Client::Handle_OP_ItemVerifyRequest(const EQApplicationPacket *app) } if (GetLevel() >= item->Click.Level2) { + if (item->RecastDelay > 0) + { + if (!GetPTimers().Expired(&database, (pTimerItemStart + item->RecastType), false)) { + SendItemRecastTimer(item->RecastType); //Problem: When you loot corpse, recast display is not present. This causes it to display again. Could not get to display when sending from looting. + MessageString(Chat::Red, SPELL_RECAST); + SendSpellBarEnable(item->Click.Effect); + LogSpells("Casting of [{}] canceled: item spell reuse timer not expired", spell_id); + return; + } + } + int i = parse->EventItem(EVENT_ITEM_CLICK_CAST, this, p_inst, nullptr, "", slot_id); inst = m_inv[slot_id]; if (!inst) { return; } - if (i == 0) { - if (!IsCastWhileInvis(item->Click.Effect)) + if (!IsCastWhileInvis(item->Click.Effect)) { CommonBreakInvisible(); // client can't do this for us :( - CastSpell(item->Click.Effect, target_id, CastingSlot::Item, item->CastTime, 0, 0, slot_id); + } + if (GetClass() == BARD){ + DoBardCastingFromItemClick(is_casting_bard_song, item->CastTime, item->Click.Effect, target_id, CastingSlot::Item, slot_id, item->RecastType, item->RecastDelay); + } + else { + CastSpell(item->Click.Effect, target_id, CastingSlot::Item, item->CastTime, 0, 0, slot_id); + } } } else @@ -8924,6 +8989,16 @@ void Client::Handle_OP_ItemVerifyRequest(const EQApplicationPacket *app) } if (GetLevel() >= augitem->Click.Level2) { + if (augitem->RecastDelay > 0) + { + if (!GetPTimers().Expired(&database, (pTimerItemStart + augitem->RecastType), false)) { + LogSpells("Casting of [{}] canceled: item spell reuse timer from augment not expired", spell_id); + MessageString(Chat::Red, SPELL_RECAST); + SendSpellBarEnable(augitem->Click.Effect); + return; + } + } + int i = parse->EventItem(EVENT_ITEM_CLICK_CAST, this, clickaug, nullptr, "", slot_id); inst = m_inv[slot_id]; if (!inst) @@ -8932,9 +9007,15 @@ void Client::Handle_OP_ItemVerifyRequest(const EQApplicationPacket *app) } if (i == 0) { - if (!IsCastWhileInvis(augitem->Click.Effect)) + if (!IsCastWhileInvis(augitem->Click.Effect)) { CommonBreakInvisible(); // client can't do this for us :( - CastSpell(augitem->Click.Effect, target_id, CastingSlot::Item, augitem->CastTime, 0, 0, slot_id); + } + if (GetClass() == BARD) { + DoBardCastingFromItemClick(is_casting_bard_song, augitem->CastTime, augitem->Click.Effect, target_id, CastingSlot::Item, slot_id, augitem->RecastType, augitem->RecastDelay); + } + else { + CastSpell(augitem->Click.Effect, target_id, CastingSlot::Item, augitem->CastTime, 0, 0, slot_id); + } } } else @@ -8951,48 +9032,6 @@ void Client::Handle_OP_ItemVerifyRequest(const EQApplicationPacket *app) { LogDebug("Error: unknown item->Click.Type ([{}])", item->Click.Type); } - else - { - - /* - //This is food/drink - consume it - if (item->ItemType == EQ::item::ItemTypeFood && m_pp.hunger_level < 5000) - { - Consume(item, item->ItemType, slot_id, false); - } - else if (item->ItemType == EQ::item::ItemTypeDrink && m_pp.thirst_level < 5000) - { - Consume(item, item->ItemType, slot_id, false); - } - else if (item->ItemType == EQ::item::ItemTypeAlcohol) - { -#if EQDEBUG >= 1 - LogDebug("Drinking Alcohol from slot:[{}]", slot_id); -#endif - // This Seems to be handled in OP_DeleteItem handling - //DeleteItemInInventory(slot_id, 1, false); - //entity_list.MessageCloseString(this, true, 50, 0, DRINKING_MESSAGE, GetName(), item->Name); - //Should add intoxication level to the PP at some point - //CheckIncreaseSkill(ALCOHOL_TOLERANCE, nullptr, 25); - } - - EQApplicationPacket *outapp2 = nullptr; - outapp2 = new EQApplicationPacket(OP_Stamina, sizeof(Stamina_Struct)); - Stamina_Struct* sta = (Stamina_Struct*)outapp2->pBuffer; - - if (m_pp.hunger_level > 6000) - sta->food = 6000; - if (m_pp.thirst_level > 6000) - sta->water = 6000; - - sta->food = m_pp.hunger_level; - sta->water = m_pp.thirst_level; - - QueuePacket(outapp2); - safe_delete(outapp2); - */ - } - } else { @@ -9035,9 +9074,9 @@ void Client::Handle_OP_KickPlayers(const EQApplicationPacket *app) expedition->DzKickPlayers(this); } } - else if (buf->kick_task) + else if (buf->kick_task && GetTaskState() && GetTaskState()->HasActiveSharedTask()) { - // todo: shared tasks + GetTaskState()->KickPlayersSharedTask(this); } } @@ -9084,7 +9123,7 @@ void Client::Handle_OP_LDoNButton(const EQApplicationPacket *app) void Client::Handle_OP_LDoNDisarmTraps(const EQApplicationPacket *app) { Mob * target = GetTarget(); - if (target->IsNPC()) + if (target && target->IsNPC() && !target->IsAura()) { if (HasSkill(EQ::skills::SkillDisarmTraps)) { @@ -9103,21 +9142,22 @@ void Client::Handle_OP_LDoNDisarmTraps(const EQApplicationPacket *app) void Client::Handle_OP_LDoNInspect(const EQApplicationPacket *app) { Mob * target = GetTarget(); - if (target && target->GetClass() == LDON_TREASURE) + if (target && target->GetClass() == LDON_TREASURE && !target->IsAura()) Message(Chat::Yellow, "%s", target->GetCleanName()); } void Client::Handle_OP_LDoNOpen(const EQApplicationPacket *app) { Mob * target = GetTarget(); - if (target && target->IsNPC()) + if (target && target->IsNPC() && !target->IsAura()) { HandleLDoNOpen(target->CastToNPC()); + } } void Client::Handle_OP_LDoNPickLock(const EQApplicationPacket *app) { Mob * target = GetTarget(); - if (target->IsNPC()) + if (target && target->IsNPC() && !target->IsAura()) { if (HasSkill(EQ::skills::SkillPickLock)) { @@ -9136,7 +9176,7 @@ void Client::Handle_OP_LDoNPickLock(const EQApplicationPacket *app) void Client::Handle_OP_LDoNSenseTraps(const EQApplicationPacket *app) { Mob * target = GetTarget(); - if (target->IsNPC()) + if (target && target->IsNPC() && !target->IsAura()) { if (HasSkill(EQ::skills::SkillSenseTraps)) { @@ -9581,16 +9621,20 @@ void Client::Handle_OP_ManaChange(const EQApplicationPacket *app) { if (app->size == 0) { // i think thats the sign to stop the songs - if (IsBardSong(casting_spell_id) || bardsong != 0) - InterruptSpell(SONG_ENDS, 0x121); - else + if (IsBardSong(casting_spell_id) || HasActiveSong()) { + InterruptSpell(SONG_ENDS, 0x121); //Live doesn't send song end message anymore (~Kayen 1/26/22) + } + else { InterruptSpell(INTERRUPT_SPELL, 0x121); - + } return; } - else // I don't think the client sends proper manachanges - { // with a length, just the 0 len ones for stopping songs - //ManaChange_Struct* p = (ManaChange_Struct*)app->pBuffer; + /* + I don't think the client sends proper manachanges + with a length, just the 0 len ones for stopping songs + ManaChange_Struct* p = (ManaChange_Struct*)app->pBuffer; + */ + else{ printf("OP_ManaChange from client:\n"); DumpPacket(app); } @@ -9613,6 +9657,7 @@ return; void Client::Handle_OP_MemorizeSpell(const EQApplicationPacket *app) { + cheat_manager.CheckMemTimer(); OPMemorizeSpell(app); return; } @@ -10272,6 +10317,7 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { if (target != this && DistanceSquaredNoZ(mypet->GetPosition(), target->GetPosition()) <= (RuleR(Pets, AttackCommandRange)*RuleR(Pets, AttackCommandRange))) { + mypet->SetFeigned(false); if (mypet->IsPetStop()) { mypet->SetPetStop(false); SetPetCommandState(PET_BUTTON_STOP, 0); @@ -10318,6 +10364,7 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { if (GetTarget() != this && DistanceSquaredNoZ(mypet->GetPosition(), GetTarget()->GetPosition()) <= (RuleR(Pets, AttackCommandRange)*RuleR(Pets, AttackCommandRange))) { + mypet->SetFeigned(false); if (mypet->IsPetStop()) { mypet->SetPetStop(false); SetPetCommandState(PET_BUTTON_STOP, 0); @@ -10393,6 +10440,7 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) if (mypet->IsNPC()) { // Set Sit button to unpressed - send stand anim/end hpregen + mypet->SetFeigned(false); SetPetCommandState(PET_BUTTON_SIT, 0); mypet->SendAppearancePacket(AT_Anim, ANIM_STAND); @@ -10413,6 +10461,7 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) if (mypet->IsFeared()) break; //could be exploited like PET_BACKOFF if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { + mypet->SetFeigned(false); mypet->SayString(this, Chat::PetResponse, PET_FOLLOWING); mypet->SetPetOrder(SPO_Follow); @@ -10460,6 +10509,7 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) if (mypet->IsFeared()) break; //could be exploited like PET_BACKOFF if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { + mypet->SetFeigned(false); mypet->SayString(this, Chat::PetResponse, PET_GUARDME_STRING); mypet->SetPetOrder(SPO_Follow); @@ -10480,12 +10530,14 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { if (mypet->GetPetOrder() == SPO_Sit) { + mypet->SetFeigned(false); mypet->SayString(this, Chat::PetResponse, PET_SIT_STRING); mypet->SetPetOrder(SPO_Follow); mypet->SendAppearancePacket(AT_Anim, ANIM_STAND); } else { + mypet->SetFeigned(false); mypet->SayString(this, Chat::PetResponse, PET_SIT_STRING); mypet->SetPetOrder(SPO_Sit); mypet->SetRunAnimSpeed(0); @@ -10500,6 +10552,7 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) if (mypet->IsFeared()) break; //could be exploited like PET_BACKOFF if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { + mypet->SetFeigned(false); mypet->SayString(this, Chat::PetResponse, PET_SIT_STRING); SetPetCommandState(PET_BUTTON_SIT, 0); mypet->SetPetOrder(SPO_Follow); @@ -10511,6 +10564,7 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) if (mypet->IsFeared()) break; //could be exploited like PET_BACKOFF if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { + mypet->SetFeigned(false); mypet->SayString(this, Chat::PetResponse, PET_SIT_STRING); SetPetCommandState(PET_BUTTON_SIT, 1); mypet->SetPetOrder(SPO_Sit); @@ -10704,6 +10758,38 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) } break; } + + case PET_FEIGN: { + if (aabonuses.PetCommands[PetCommand] && mypet->IsNPC()) { + if (mypet->IsFeared()) + break; + + int pet_fd_chance = aabonuses.FeignedMinionChance; + if (zone->random.Int(0, 99) > pet_fd_chance) { + mypet->SetFeigned(false); + entity_list.MessageCloseString(this, false, 200, 10, STRING_FEIGNFAILED, mypet->GetCleanName()); + } + else { + bool immune_aggro = GetSpecialAbility(IMMUNE_AGGRO); + mypet->SetSpecialAbility(IMMUNE_AGGRO, 1); + mypet->WipeHateList(); + mypet->SetPetOrder(SPO_FeignDeath); + mypet->SetRunAnimSpeed(0); + mypet->StopNavigation(); + mypet->SendAppearancePacket(AT_Anim, ANIM_DEATH); + mypet->SetFeigned(true); + mypet->SetTarget(nullptr); + if (!mypet->UseBardSpellLogic()) { + mypet->InterruptSpell(); + } + + if (!immune_aggro) { + mypet->SetSpecialAbility(IMMUNE_AGGRO, 0); + } + } + } + break; + } case PET_STOP: { if (mypet->IsFeared()) break; //could be exploited like PET_BACKOFF @@ -10828,7 +10914,17 @@ void Client::Handle_OP_Petition(const EQApplicationPacket *app) database.InsertPetitionToDB(pet); petition_list.UpdateGMQueue(); petition_list.UpdateZoneListQueue(); - worldserver.SendEmoteMessage(0, 0, 80, 15, "%s has made a petition. #%i", GetName(), pet->GetID()); + worldserver.SendEmoteMessage( + 0, + 0, + AccountStatus::QuestTroupe, + Chat::Yellow, + fmt::format( + "{} has made a petition. ID: {}", + GetCleanName(), + pet->GetID() + ).c_str() + ); } return; } @@ -11004,48 +11100,35 @@ void Client::Handle_OP_PickPocket(const EQApplicationPacket *app) return; p_timers.Start(pTimerBeggingPickPocket, 8); + auto outapp = new EQApplicationPacket(OP_PickPocket, sizeof(sPickPocket_Struct)); + sPickPocket_Struct* pick_out = (sPickPocket_Struct*)outapp->pBuffer; + pick_out->coin = 0; + pick_out->from = victim->GetID(); + pick_out->to = GetID(); + pick_out->myskill = GetSkill(EQ::skills::SkillPickPockets); + pick_out->type = 0; + //if we do not send this packet the client will lock up and require the player to relog. + if (victim == this) { Message(0, "You catch yourself red-handed."); - auto outapp = new EQApplicationPacket(OP_PickPocket, sizeof(sPickPocket_Struct)); - sPickPocket_Struct* pick_out = (sPickPocket_Struct*)outapp->pBuffer; - pick_out->coin = 0; - pick_out->from = victim->GetID(); - pick_out->to = GetID(); - pick_out->myskill = GetSkill(EQ::skills::SkillPickPockets); - pick_out->type = 0; - //if we do not send this packet the client will lock up and require the player to relog. - QueuePacket(outapp); - safe_delete(outapp); } else if (victim->GetOwnerID()) { Message(0, "You cannot steal from pets!"); - auto outapp = new EQApplicationPacket(OP_PickPocket, sizeof(sPickPocket_Struct)); - sPickPocket_Struct* pick_out = (sPickPocket_Struct*)outapp->pBuffer; - pick_out->coin = 0; - pick_out->from = victim->GetID(); - pick_out->to = GetID(); - pick_out->myskill = GetSkill(EQ::skills::SkillPickPockets); - pick_out->type = 0; - //if we do not send this packet the client will lock up and require the player to relog. - QueuePacket(outapp); - safe_delete(outapp); + } + else if (Distance(GetPosition(), victim->GetPosition()) > 20) { + Message(Chat::Red, "Attempt to pickpocket out of range detected."); + database.SetMQDetectionFlag(this->AccountName(), this->GetName(), "OP_PickPocket was sent from outside combat range.", zone->GetShortName()); } else if (victim->IsNPC()) { + safe_delete(outapp); victim->CastToNPC()->PickPocket(this); + return; } else { Message(0, "Stealing from clients not yet supported."); - auto outapp = new EQApplicationPacket(OP_PickPocket, sizeof(sPickPocket_Struct)); - sPickPocket_Struct* pick_out = (sPickPocket_Struct*)outapp->pBuffer; - pick_out->coin = 0; - pick_out->from = victim->GetID(); - pick_out->to = GetID(); - pick_out->myskill = GetSkill(EQ::skills::SkillPickPockets); - pick_out->type = 0; - //if we do not send this packet the client will lock up and require the player to relog. - QueuePacket(outapp); - safe_delete(outapp); } + QueuePacket(outapp); + safe_delete(outapp); } void Client::Handle_OP_PopupResponse(const EQApplicationPacket *app) @@ -11063,6 +11146,7 @@ void Client::Handle_OP_PopupResponse(const EQApplicationPacket *app) /** * Handle any EQEmu defined popup Ids first */ + std::string response; switch (popup_response->popupid) { case POPUPID_UPDATE_SHOWSTATSWINDOW: if (GetTarget() && GetTarget()->IsClient()) { @@ -11074,6 +11158,24 @@ void Client::Handle_OP_PopupResponse(const EQApplicationPacket *app) return; break; + case POPUPID_DIAWIND_ONE: + if (EntityVariableExists(DIAWIND_RESPONSE_ONE_KEY.c_str())) { + response = GetEntityVariable(DIAWIND_RESPONSE_ONE_KEY.c_str()); + if (!response.empty()) { + ChannelMessageReceived(8, 0, 100, response.c_str()); + } + } + break; + + case POPUPID_DIAWIND_TWO: + if (EntityVariableExists(DIAWIND_RESPONSE_TWO_KEY.c_str())) { + response = GetEntityVariable(DIAWIND_RESPONSE_TWO_KEY.c_str()); + if (!response.empty()) { + ChannelMessageReceived(8, 0, 100, response.c_str()); + } + } + break; + case EQ::popupresponse::MOB_INFO_DISMISS: SetDisplayMobInfoWindow(false); Message(Chat::Yellow, "[DevTools] Window snoozed in this zone..."); @@ -11082,14 +11184,13 @@ void Client::Handle_OP_PopupResponse(const EQApplicationPacket *app) break; } - char buf[16]; - sprintf(buf, "%d", popup_response->popupid); + std::string export_string = fmt::format("{}", popup_response->popupid); - parse->EventPlayer(EVENT_POPUP_RESPONSE, this, buf, 0); + parse->EventPlayer(EVENT_POPUP_RESPONSE, this, export_string, 0); Mob *Target = GetTarget(); if (Target && Target->IsNPC()) { - parse->EventNPC(EVENT_POPUP_RESPONSE, Target->CastToNPC(), this, buf, 0); + parse->EventNPC(EVENT_POPUP_RESPONSE, Target->CastToNPC(), this, export_string, 0); } } @@ -12275,7 +12376,8 @@ void Client::Handle_OP_RecipesFavorite(const EQApplicationPacket *app) tr.name, tr.trivial, SUM(tre.componentcount), - tr.tradeskill + tr.tradeskill, + tr.must_learn FROM tradeskill_recipe AS tr LEFT JOIN tradeskill_recipe_entries AS tre ON tr.id = tre.recipe_id @@ -12375,7 +12477,8 @@ void Client::Handle_OP_RecipesSearch(const EQApplicationPacket *app) tr.name, tr.trivial, SUM(tre.componentcount), - tr.tradeskill + tr.tradeskill, + tr.must_learn FROM tradeskill_recipe AS tr LEFT JOIN tradeskill_recipe_entries AS tre ON tr.id = tre.recipe_id @@ -12572,34 +12675,54 @@ void Client::Handle_OP_Report(const EQApplicationPacket *app) void Client::Handle_OP_RequestDuel(const EQApplicationPacket *app) { - if (app->size != sizeof(Duel_Struct)) + if (app->size != sizeof(Duel_Struct)) { return; + } EQApplicationPacket* outapp = app->Copy(); Duel_Struct* ds = (Duel_Struct*)outapp->pBuffer; + if (!ds->duel_initiator || !ds->duel_target) { + return; + } + uint32 duel = ds->duel_initiator; ds->duel_initiator = ds->duel_target; ds->duel_target = duel; Entity* entity = entity_list.GetID(ds->duel_target); - if (GetID() != ds->duel_target && entity->IsClient() && (entity->CastToClient()->IsDueling() && entity->CastToClient()->GetDuelTarget() != 0)) { + + if ( + GetID() != ds->duel_target && + entity->IsClient() && + entity->CastToClient()->IsDueling() && + entity->CastToClient()->GetDuelTarget() + ) { MessageString(Chat::NPCQuestSay, DUEL_CONSIDERING, entity->GetName()); return; } + if (IsDueling()) { MessageString(Chat::NPCQuestSay, DUEL_INPROGRESS); return; } - if (GetID() != ds->duel_target && entity->IsClient() && GetDuelTarget() == 0 && !IsDueling() && !entity->CastToClient()->IsDueling() && entity->CastToClient()->GetDuelTarget() == 0) { + if ( + GetID() != ds->duel_target && + entity->IsClient() && + !GetDuelTarget() && + !IsDueling() && + !entity->CastToClient()->IsDueling() && + !entity->CastToClient()->GetDuelTarget() + ) { SetDuelTarget(ds->duel_target); entity->CastToClient()->SetDuelTarget(GetID()); ds->duel_target = ds->duel_initiator; entity->CastToClient()->FastQueuePacket(&outapp); entity->CastToClient()->SetDueling(false); SetDueling(false); - } - else + } else { safe_delete(outapp); + } + return; } @@ -12860,8 +12983,8 @@ void Client::Handle_OP_SetServerFilter(const EQApplicationPacket *app) void Client::Handle_OP_SetStartCity(const EQApplicationPacket *app) { // if the character has a start city, don't let them use the command - if (m_pp.binds[4].zoneId != 0 && m_pp.binds[4].zoneId != 189) { - Message(Chat::Yellow, "Your home city has already been set.", m_pp.binds[4].zoneId, ZoneName(m_pp.binds[4].zoneId)); + if (m_pp.binds[4].zone_id != 0 && m_pp.binds[4].zone_id != 189) { + Message(Chat::Yellow, "Your home city has already been set.", m_pp.binds[4].zone_id, ZoneName(m_pp.binds[4].zone_id)); return; } @@ -12871,13 +12994,22 @@ void Client::Handle_OP_SetStartCity(const EQApplicationPacket *app) return; } - float x(0), y(0), z(0); - uint32 zoneid = 0; - uint32 startCity = (uint32)strtol((const char*)app->pBuffer, nullptr, 10); - - std::string query = StringFormat( - "SELECT zone_id, bind_id, x, y, z FROM start_zones " - "WHERE player_class=%i AND player_deity=%i AND player_race=%i %s", + float x = 0.0f, y = 0.0f, z = 0.0f, heading = 0.0f; + uint32 zone_id = 0; + uint32 start_city = (uint32)strtol((const char*)app->pBuffer, nullptr, 10); + std::string query = fmt::format( + SQL( + SELECT + `zone_id`, `bind_id`, `x`, `y`, `z`, `heading` + FROM + `start_zones` + WHERE + player_class = {} + AND + player_deity = {} + AND + player_race = {} {} + ), m_pp.class_, m_pp.deity, m_pp.race, @@ -12889,31 +13021,46 @@ void Client::Handle_OP_SetStartCity(const EQApplicationPacket *app) return; } - bool validCity = false; + bool valid_city = false; for (auto row = results.begin(); row != results.end(); ++row) { if (atoi(row[1]) != 0) - zoneid = atoi(row[1]); + zone_id = atoi(row[1]); else - zoneid = atoi(row[0]); + zone_id = atoi(row[0]); - if (zoneid != startCity) + if (zone_id != start_city) continue; - validCity = true; + valid_city = true; x = atof(row[2]); y = atof(row[3]); z = atof(row[4]); + heading = atof(row[5]); } - if (validCity) { + if (valid_city) { Message(Chat::Yellow, "Your home city has been set"); - SetStartZone(startCity, x, y, z); + SetStartZone(start_city, x, y, z, heading); return; } - query = StringFormat("SELECT zone_id, bind_id FROM start_zones " - "WHERE player_class=%i AND player_deity=%i AND player_race=%i", - m_pp.class_, m_pp.deity, m_pp.race); + query = fmt::format( + SQL( + SELECT + `zone_id`, `bind_id` + FROM + `start_zones` + WHERE + player_class = {} + AND + player_deity = {} + AND + player_race = {} + ), + m_pp.class_, + m_pp.deity, + m_pp.race + ); results = content_db.QueryDatabase(query); if (!results.Success()) return; @@ -12922,13 +13069,12 @@ void Client::Handle_OP_SetStartCity(const EQApplicationPacket *app) for (auto row = results.begin(); row != results.end(); ++row) { if (atoi(row[1]) != 0) - zoneid = atoi(row[1]); + zone_id = atoi(row[1]); else - zoneid = atoi(row[0]); + zone_id = atoi(row[0]); - char* name = nullptr; - content_db.GetZoneLongName(ZoneName(zoneid), &name); - Message(Chat::Yellow, "%d - %s", zoneid, name); + std::string zone_long_name = ZoneLongName(zone_id); + Message(Chat::Yellow, "%d - %s", zone_id, zone_long_name.c_str()); } } @@ -12943,103 +13089,72 @@ void Client::Handle_OP_SetTitle(const EQApplicationPacket *app) SetTitle_Struct *sts = (SetTitle_Struct *)app->pBuffer; - std::string Title; - - if (!sts->is_suffix) - { - Title = title_manager.GetPrefix(sts->title_id); - SetAATitle(Title.c_str()); + if (!title_manager.HasTitle(this, sts->title_id)) { + return; } - else - { - Title = title_manager.GetSuffix(sts->title_id); - SetTitleSuffix(Title.c_str()); + + std::string title = !sts->is_suffix ? title_manager.GetPrefix(sts->title_id) : title_manager.GetSuffix(sts->title_id); + if (!sts->is_suffix) { + SetAATitle(title.c_str()); + } else { + SetTitleSuffix(title.c_str()); } } void Client::Handle_OP_Shielding(const EQApplicationPacket *app) { + /* + /shield command mechanics + Warriors get this skill at level 30 + Used by typing /shield while targeting a player + While active for the duration of 12 seconds baseline. The 'shield target' will take 50 pct less damage and + the 'shielder' will be hit with the damage taken by the 'shield target' after all applicable mitigiont is calculated, + the damage on the 'shielder' will be reduced by 25 percent, this reduction can be increased to 50 pct if equiping a shield. + You receive a 1% increase in mitigation for every 2 AC on the shield. + Shielder must stay with in a close distance (15 units) to your 'shield target'. If either move out of range, shield ends, no message given. + Both duration and shield range can be modified by AA. + Recast is 3 minutes. + + For custom use cases, Mob::ShieldAbility can be used in quests with all parameters being altered. This functional + is also used for SPA 201 SE_PetShield, which functions in a simalar manner with pet shielding owner. + + Note: If either the shielder or the shield target die all variables are reset on both. + + */ + if (app->size != sizeof(Shielding_Struct)) { LogError("OP size error: OP_Shielding expected:[{}] got:[{}]", sizeof(Shielding_Struct), app->size); return; } - if (GetClass() != WARRIOR) - { + + if (GetLevel() < 30) { //Client gives message return; } - if (shield_target) - { - entity_list.MessageCloseString( - this, false, 100, 0, - END_SHIELDING, GetName(), shield_target->GetName()); - for (int y = 0; y < 2; y++) - { - if (shield_target->shielder[y].shielder_id == GetID()) - { - shield_target->shielder[y].shielder_id = 0; - shield_target->shielder[y].shielder_bonus = 0; - } - } + if (GetClass() != WARRIOR){ + return; } + + pTimerType timer = pTimerShieldAbility; + + if (!p_timers.Expired(&database, timer, false)) { + uint32 remaining_time = p_timers.GetRemainingTime(timer); + Message( + Chat::White, + fmt::format( + "You can use the ability /shield in {}.", + ConvertSecondsToTime(remaining_time) + ).c_str() + ); + return; + } + Shielding_Struct* shield = (Shielding_Struct*)app->pBuffer; - shield_target = entity_list.GetMob(shield->target_id); - bool ack = false; - EQ::ItemInstance* inst = GetInv().GetItem(EQ::invslot::slotSecondary); - if (!shield_target) - return; - if (inst) - { - const EQ::ItemData* shield = inst->GetItem(); - if (shield && shield->ItemType == EQ::item::ItemTypeShield) - { - for (int x = 0; x < 2; x++) - { - if (shield_target->shielder[x].shielder_id == 0) - { - entity_list.MessageCloseString( - this, false, 100, 0, - START_SHIELDING, GetName(), shield_target->GetName()); - shield_target->shielder[x].shielder_id = GetID(); - int shieldbonus = shield->AC * 2; - switch (GetAA(197)) - { - case 1: - shieldbonus = shieldbonus * 115 / 100; - break; - case 2: - shieldbonus = shieldbonus * 125 / 100; - break; - case 3: - shieldbonus = shieldbonus * 150 / 100; - break; - } - shield_target->shielder[x].shielder_bonus = shieldbonus; - shield_timer.Start(); - ack = true; - break; - } - } - } - else - { - Message(0, "You must have a shield equipped to shield a target!"); - shield_target = 0; - return; - } - } - else - { - Message(0, "You must have a shield equipped to shield a target!"); - shield_target = 0; - return; - } - if (!ack) - { - MessageString(Chat::White, ALREADY_SHIELDED); - shield_target = 0; - return; + + if (ShieldAbility(shield->target_id, 15, 12000, 50, 25, true, false)) { + p_timers.Start(timer, SHIELD_ABILITY_RECAST_TIME); } + return; } @@ -13150,9 +13265,9 @@ void Client::Handle_OP_ShopPlayerBuy(const EQApplicationPacket *app) int16 freeslotid = INVALID_INDEX; int16 charges = 0; - if (item->Stackable || item->MaxCharges > 1) + if (item->Stackable || tmpmer_used) charges = mp->quantity; - else + else if ( item->MaxCharges >= 1) charges = item->MaxCharges; EQ::ItemInstance* inst = database.CreateItem(item, charges); @@ -13398,12 +13513,9 @@ void Client::Handle_OP_ShopPlayerSell(const EQApplicationPacket *app) LogMerchant(this, vendor, mp->quantity, price, item, false); int charges = mp->quantity; - //Hack workaround so usable items with 0 charges aren't simply deleted - if (charges == 0 && item->ItemType != 11 && item->ItemType != 17 && item->ItemType != 19 && item->ItemType != 21) - charges = 1; int freeslot = 0; - if (charges > 0 && (freeslot = zone->SaveTempItem(vendor->CastToNPC()->MerchantType, vendor->GetNPCTypeID(), itemid, charges, true)) > 0) { + if ((freeslot = zone->SaveTempItem(vendor->CastToNPC()->MerchantType, vendor->GetNPCTypeID(), itemid, charges, true)) > 0) { EQ::ItemInstance* inst2 = inst->Clone(); while (true) { @@ -13467,26 +13579,14 @@ void Client::Handle_OP_ShopPlayerSell(const EQApplicationPacket *app) // end QS code // Now remove the item from the player, this happens regardless of outcome - if (!inst->IsStackable()) - this->DeleteItemInInventory(mp->itemslot, 0, false); - else { - // HACK: DeleteItemInInventory uses int8 for quantity type. There is no consistent use of types in code in this path so for now iteratively delete from inventory. - if (mp->quantity > 255) { - uint32 temp = mp->quantity; - while (temp > 255 && temp != 0) { - // Delete chunks of 255 - this->DeleteItemInInventory(mp->itemslot, 255, false); - temp -= 255; - } - if (temp != 0) { - // Delete remaining - this->DeleteItemInInventory(mp->itemslot, temp, false); - } - } - else { - this->DeleteItemInInventory(mp->itemslot, mp->quantity, false); - } - } + DeleteItemInInventory( + mp->itemslot, + ( + !inst->IsStackable() ? + 0 : + mp->quantity + ) + ); //This forces the price to show up correctly for charged items. @@ -13655,6 +13755,8 @@ void Client::Handle_OP_SpawnAppearance(const EQApplicationPacket *app) } SpawnAppearance_Struct* sa = (SpawnAppearance_Struct*)app->pBuffer; + cheat_manager.ProcessSpawnApperance(sa->spawn_id, sa->type, sa->parameter); + if (sa->spawn_id != GetID()) return; @@ -14107,6 +14209,11 @@ void Client::Handle_OP_TargetCommand(const EQApplicationPacket *app) GetTarget()->IsTargeted(1); return; } + else if (cheat_manager.GetExemptStatus(Assist)) { + GetTarget()->IsTargeted(1); + cheat_manager.SetExemptStatus(Assist, false); + return; + } else if (GetTarget()->IsClient()) { //make sure this client is in our raid/group @@ -14122,6 +14229,15 @@ void Client::Handle_OP_TargetCommand(const EQApplicationPacket *app) SetTarget((Mob*)nullptr); return; } + else if (cheat_manager.GetExemptStatus(Port)) { + GetTarget()->IsTargeted(1); + return; + } + else if (cheat_manager.GetExemptStatus(Sense)) { + GetTarget()->IsTargeted(1); + cheat_manager.SetExemptStatus(Sense, false); + return; + } else if (IsXTarget(GetTarget())) { GetTarget()->IsTargeted(1); @@ -14196,6 +14312,10 @@ void Client::Handle_OP_Taunt(const EQApplicationPacket *app) std::cout << "Wrong size on OP_Taunt. Got: " << app->size << ", Expected: " << sizeof(ClientTarget_Struct) << std::endl; return; } + + if (!HasSkill(EQ::skills::SkillTaunt)) { + return; + } if (!p_timers.Expired(&database, pTimerTaunt, false)) { Message(Chat::Red, "Ability recovery time not yet met."); @@ -14217,6 +14337,10 @@ void Client::Handle_OP_Taunt(const EQApplicationPacket *app) void Client::Handle_OP_TestBuff(const EQApplicationPacket *app) { + if (!RuleB(Character, EnableTestBuff)) { + return; + } + parse->EventPlayer(EVENT_TEST_BUFF, this, "", 0); return; } @@ -14228,8 +14352,9 @@ void Client::Handle_OP_TGB(const EQApplicationPacket *app) void Client::Handle_OP_Track(const EQApplicationPacket *app) { - if (GetClass() != RANGER && GetClass() != DRUID && GetClass() != BARD) + if (!CanThisClassTrack()) { return; + } if (GetSkill(EQ::skills::SkillTracking) == 0) SetSkill(EQ::skills::SkillTracking, 1); @@ -14244,10 +14369,9 @@ void Client::Handle_OP_Track(const EQApplicationPacket *app) void Client::Handle_OP_TrackTarget(const EQApplicationPacket *app) { - int PlayerClass = GetClass(); - - if ((PlayerClass != RANGER) && (PlayerClass != DRUID) && (PlayerClass != BARD)) + if (!CanThisClassTrack()) { return; + } if (app->size != sizeof(TrackTarget_Struct)) { @@ -14849,38 +14973,44 @@ void Client::Handle_OP_TradeSkillCombine(const EQApplicationPacket *app) void Client::Handle_OP_Translocate(const EQApplicationPacket *app) { - if (app->size != sizeof(Translocate_Struct)) { LogDebug("Size mismatch in OP_Translocate expected [{}] got [{}]", sizeof(Translocate_Struct), app->size); DumpPacket(app); return; } + Translocate_Struct *its = (Translocate_Struct*)app->pBuffer; - if (!PendingTranslocate) + if (!PendingTranslocate) { return; + } - if ((RuleI(Spells, TranslocateTimeLimit) > 0) && (time(nullptr) > (TranslocateTime + RuleI(Spells, TranslocateTimeLimit)))) { + auto translocate_time_limit = RuleI(Spells, TranslocateTimeLimit); + if ( + translocate_time_limit && + time(nullptr) > (TranslocateTime + translocate_time_limit) + ) { Message(Chat::Red, "You did not accept the Translocate within the required time limit."); PendingTranslocate = false; return; } if (its->Complete == 1) { + uint32 spell_id = PendingTranslocateData.spell_id; + bool in_translocate_zone = ( + zone->GetZoneID() == PendingTranslocateData.zone_id && + zone->GetInstanceID() == PendingTranslocateData.instance_id + ); - int SpellID = PendingTranslocateData.spell_id; - int i = parse->EventSpell(EVENT_SPELL_EFFECT_TRANSLOCATE_COMPLETE, nullptr, this, SpellID, 0); - - if (i == 0) - { + if (parse->EventSpell(EVENT_SPELL_EFFECT_TRANSLOCATE_COMPLETE, nullptr, this, spell_id, "", 0) == 0) { // If the spell has a translocate to bind effect, AND we are already in the zone the client // is bound in, use the GoToBind method. If we send OP_Translocate in this case, the client moves itself // to the bind coords it has from the PlayerProfile, but with the X and Y reversed. I suspect they are // reversed in the pp, and since spells like Gate are handled serverside, this has not mattered before. - if (((SpellID == 1422) || (SpellID == 1334) || (SpellID == 3243)) && - (zone->GetZoneID() == PendingTranslocateData.zone_id && - zone->GetInstanceID() == PendingTranslocateData.instance_id)) - { + if ( + IsTranslocateSpell(spell_id) && + in_translocate_zone + ) { PendingTranslocate = false; GoToBind(); return; @@ -14888,9 +15018,16 @@ void Client::Handle_OP_Translocate(const EQApplicationPacket *app) ////Was sending the packet back to initiate client zone... ////but that could be abusable, so lets go through proper channels - MovePC(PendingTranslocateData.zone_id, PendingTranslocateData.instance_id, - PendingTranslocateData.x, PendingTranslocateData.y, - PendingTranslocateData.z, PendingTranslocateData.heading, 0, ZoneSolicited); + MovePC( + PendingTranslocateData.zone_id, + PendingTranslocateData.instance_id, + PendingTranslocateData.x, + PendingTranslocateData.y, + PendingTranslocateData.z, + PendingTranslocateData.heading, + 0, + ZoneSolicited + ); } } @@ -15353,9 +15490,273 @@ void Client::Handle_OP_YellForHelp(const EQApplicationPacket *app) void Client::Handle_OP_ResetAA(const EQApplicationPacket *app) { - if (Admin() >= 50) { + if (Admin() >= AccountStatus::Guide) { Message(0, "Resetting AA points."); ResetAA(); } return; } + +void Client::Handle_OP_MovementHistoryList(const EQApplicationPacket *app) +{ + cheat_manager.ProcessMovementHistory(app); +} + +void Client::Handle_OP_UnderWorld(const EQApplicationPacket *app) +{ + UnderWorld *m_UnderWorld = (UnderWorld *) app->pBuffer; + if (app->size != sizeof(UnderWorld)) { + LogDebug("Size mismatch in OP_UnderWorld, expected {}, got [{}]", sizeof(UnderWorld), app->size); + DumpPacket(app); + return; + } + auto dist = Distance( + glm::vec3(m_UnderWorld->x, m_UnderWorld->y, zone->newzone_data.underworld), + glm::vec3(m_UnderWorld->x, m_UnderWorld->y, m_UnderWorld->z)); + cheat_manager.MovementCheck(glm::vec3(m_UnderWorld->x, m_UnderWorld->y, m_UnderWorld->z)); + if (m_UnderWorld->spawn_id == GetID() && dist <= 5.0f && zone->newzone_data.underworld_teleport_index != 0) { + cheat_manager.SetExemptStatus(Port, true); + } +} + +void Client::Handle_OP_SharedTaskRemovePlayer(const EQApplicationPacket *app) +{ + if (app->size != sizeof(SharedTaskRemovePlayer_Struct)) { + LogPacketClientServer( + "Wrong size on Handle_OP_SharedTaskRemovePlayer | got [{}] expected [{}]", + app->size, + sizeof(SharedTaskRemovePlayer_Struct) + ); + return; + } + auto *r = (SharedTaskRemovePlayer_Struct *) app->pBuffer; + + LogTasks( + "[Handle_OP_SharedTaskRemovePlayer] field1 [{}] field2 [{}] player_name [{}]", + r->field1, + r->field2, + r->player_name + ); + + // live no-ops this command if not in a shared task + if (GetTaskState()->HasActiveSharedTask()) { + // struct + auto p = new ServerPacket( + ServerOP_SharedTaskRemovePlayer, + sizeof(ServerSharedTaskRemovePlayer_Struct) + ); + + auto *rp = (ServerSharedTaskRemovePlayer_Struct *) p->pBuffer; + + // fill + rp->source_character_id = CharacterID(); + rp->task_id = GetTaskState()->GetActiveSharedTask().task_id; + strn0cpy(rp->player_name, r->player_name, sizeof(r->player_name)); + + LogTasks( + "[Handle_OP_SharedTaskRemovePlayer] source_character_id [{}] task_id [{}] player_name [{}]", + rp->source_character_id, + rp->task_id, + rp->player_name + ); + + // send + worldserver.SendPacket(p); + safe_delete(p); + } +} + +void Client::Handle_OP_SharedTaskAddPlayer(const EQApplicationPacket *app) +{ + if (app->size != sizeof(SharedTaskAddPlayer_Struct)) { + LogPacketClientServer( + "Wrong size on Handle_OP_SharedTaskAddPlayer | got [{}] expected [{}]", + app->size, + sizeof(SharedTaskAddPlayer_Struct) + ); + return; + } + auto *r = (SharedTaskAddPlayer_Struct *) app->pBuffer; + + LogTasks( + "[SharedTaskAddPlayer_Struct] field1 [{}] field2 [{}] player_name [{}]", + r->field1, + r->field2, + r->player_name + ); + + if (!GetTaskState()->HasActiveSharedTask()) { + // this message is generated client-side in newer clients + Message(Chat::System, SharedTaskMessage::GetEQStr(SharedTaskMessage::COULD_NOT_USE_COMMAND)); + } + else { + // struct + auto p = new ServerPacket( + ServerOP_SharedTaskAddPlayer, + sizeof(ServerSharedTaskAddPlayer_Struct) + ); + + auto *rp = (ServerSharedTaskAddPlayer_Struct *) p->pBuffer; + + // fill + rp->source_character_id = CharacterID(); + rp->task_id = GetTaskState()->GetActiveSharedTask().task_id; + strn0cpy(rp->player_name, r->player_name, sizeof(r->player_name)); + + LogTasks( + "[Handle_OP_SharedTaskRemovePlayer] source_character_id [{}] task_id [{}] player_name [{}]", + rp->source_character_id, + rp->task_id, + rp->player_name + ); + + // send + worldserver.SendPacket(p); + safe_delete(p); + } +} + +void Client::Handle_OP_SharedTaskMakeLeader(const EQApplicationPacket *app) +{ + if (app->size != sizeof(SharedTaskMakeLeader_Struct)) { + LogPacketClientServer( + "Wrong size on Handle_OP_SharedTaskMakeLeader | got [{}] expected [{}]", + app->size, + sizeof(SharedTaskMakeLeader_Struct) + ); + return; + } + + auto *r = (SharedTaskMakeLeader_Struct *) app->pBuffer; + LogTasks( + "[SharedTaskMakeLeader_Struct] field1 [{}] field2 [{}] player_name [{}]", + r->field1, + r->field2, + r->player_name + ); + + // live no-ops this command if not in a shared task + if (GetTaskState()->HasActiveSharedTask()) { + // struct + auto p = new ServerPacket( + ServerOP_SharedTaskMakeLeader, + sizeof(ServerSharedTaskMakeLeader_Struct) + ); + + auto *rp = (ServerSharedTaskMakeLeader_Struct *) p->pBuffer; + + // fill + rp->source_character_id = CharacterID(); + rp->task_id = GetTaskState()->GetActiveSharedTask().task_id; + strn0cpy(rp->player_name, r->player_name, sizeof(r->player_name)); + + LogTasks( + "[Handle_OP_SharedTaskRemovePlayer] source_character_id [{}] task_id [{}] player_name [{}]", + rp->source_character_id, + rp->task_id, + rp->player_name + ); + + // send + worldserver.SendPacket(p); + safe_delete(p); + } +} + +void Client::Handle_OP_SharedTaskInviteResponse(const EQApplicationPacket *app) +{ + if (app->size != sizeof(SharedTaskInviteResponse_Struct)) { + LogPacketClientServer( + "Wrong size on SharedTaskInviteResponse | got [{}] expected [{}]", + app->size, + sizeof(SharedTaskInviteResponse_Struct) + ); + return; + } + + auto *r = (SharedTaskInviteResponse_Struct *) app->pBuffer; + LogTasks( + "[SharedTaskInviteResponse] unknown00 [{}] invite_id [{}] accepted [{}]", + r->unknown00, + r->invite_id, + r->accepted + ); + + // struct + auto p = new ServerPacket( + ServerOP_SharedTaskInviteAcceptedPlayer, + sizeof(ServerSharedTaskInviteAccepted_Struct) + ); + + auto *c = (ServerSharedTaskInviteAccepted_Struct *) p->pBuffer; + + // fill + c->source_character_id = CharacterID(); + c->shared_task_id = r->invite_id; + c->accepted = r->accepted; + strn0cpy(c->player_name, GetName(), sizeof(c->player_name)); + + LogTasks( + "[ServerOP_SharedTaskInviteAcceptedPlayer] source_character_id [{}] shared_task_id [{}]", + c->source_character_id, + c->shared_task_id + ); + + // send + worldserver.SendPacket(p); + safe_delete(p); +} + +void Client::Handle_OP_SharedTaskAccept(const EQApplicationPacket* app) +{ + auto buf = reinterpret_cast(app->pBuffer); + + LogTasksDetail( + "[OP_SharedTaskAccept] unknown00 [{}] unknown04 [{}] npc_entity_id [{}] task_id [{}]", + buf->unknown00, + buf->unknown04, + buf->npc_entity_id, + buf->task_id + ); + + if (buf->task_id > 0 && RuleB(TaskSystem, EnableTaskSystem) && task_state) { + task_state->AcceptNewTask(this, buf->task_id, buf->npc_entity_id, std::time(nullptr)); + } +} + +void Client::Handle_OP_SharedTaskQuit(const EQApplicationPacket* app) +{ + if (GetTaskState()->HasActiveSharedTask()) + { + CancelTask(TASKSLOTSHAREDTASK, TaskType::Shared); + } +} + +void Client::Handle_OP_TaskTimers(const EQApplicationPacket* app) +{ + GetTaskState()->ListTaskTimers(this); +} + +void Client::Handle_OP_SharedTaskPlayerList(const EQApplicationPacket* app) +{ + if (GetTaskState()->HasActiveSharedTask()) + { + uint32_t size = sizeof(ServerSharedTaskPlayerList_Struct); + auto pack = std::make_unique(ServerOP_SharedTaskPlayerList, size); + auto buf = reinterpret_cast(pack->pBuffer); + buf->source_character_id = CharacterID(); + buf->task_id = GetTaskState()->GetActiveSharedTask().task_id; + + worldserver.SendPacket(pack.get()); + } +} + +int64 Client::GetSharedTaskId() const +{ + return m_shared_task_id; +} + +void Client::SetSharedTaskId(int64 shared_task_id) +{ + Client::m_shared_task_id = shared_task_id; +} diff --git a/zone/client_packet.h b/zone/client_packet.h index 3951a1761..887372cde 100644 --- a/zone/client_packet.h +++ b/zone/client_packet.h @@ -97,8 +97,8 @@ void Handle_OP_Disarm(const EQApplicationPacket *app); void Handle_OP_DisarmTraps(const EQApplicationPacket *app); void Handle_OP_DoGroupLeadershipAbility(const EQApplicationPacket *app); - void Handle_OP_DuelResponse(const EQApplicationPacket *app); - void Handle_OP_DuelResponse2(const EQApplicationPacket *app); + void Handle_OP_DuelDecline(const EQApplicationPacket *app); + void Handle_OP_DuelAccept(const EQApplicationPacket *app); void Handle_OP_DumpName(const EQApplicationPacket *app); void Handle_OP_Dye(const EQApplicationPacket *app); void Handle_OP_DzAddPlayer(const EQApplicationPacket *app); @@ -282,6 +282,7 @@ void Handle_OP_TargetCommand(const EQApplicationPacket *app); void Handle_OP_TargetMouse(const EQApplicationPacket *app); void Handle_OP_TaskHistoryRequest(const EQApplicationPacket *app); + void Handle_OP_TaskTimers(const EQApplicationPacket *app); void Handle_OP_Taunt(const EQApplicationPacket *app); void Handle_OP_TestBuff(const EQApplicationPacket *app); void Handle_OP_TGB(const EQApplicationPacket *app); @@ -313,3 +314,14 @@ void Handle_OP_YellForHelp(const EQApplicationPacket *app); void Handle_OP_ZoneChange(const EQApplicationPacket *app); void Handle_OP_ResetAA(const EQApplicationPacket *app); + void Handle_OP_MovementHistoryList(const EQApplicationPacket* app); + void Handle_OP_UnderWorld(const EQApplicationPacket* app); + + // shared tasks + void Handle_OP_SharedTaskRemovePlayer(const EQApplicationPacket *app); + void Handle_OP_SharedTaskAddPlayer(const EQApplicationPacket *app); + void Handle_OP_SharedTaskMakeLeader(const EQApplicationPacket *app); + void Handle_OP_SharedTaskInviteResponse(const EQApplicationPacket *app); + void Handle_OP_SharedTaskAccept(const EQApplicationPacket *app); + void Handle_OP_SharedTaskQuit(const EQApplicationPacket *app); + void Handle_OP_SharedTaskPlayerList(const EQApplicationPacket *app); diff --git a/zone/client_process.cpp b/zone/client_process.cpp index bc8e2fd1e..454787517 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -119,8 +119,9 @@ bool Client::Process() { // SendHPUpdate calls hpupdate_timer.Start so it can delay this timer, so lets not reset with the check // since the function will anyways - if (hpupdate_timer.Check(false)) + if (hpupdate_timer.Check(false)) { SendHPUpdate(); + } /* I haven't naturally updated my position in 10 seconds, updating manually */ if (!is_client_moving && position_update_timer.Check()) { @@ -131,9 +132,9 @@ bool Client::Process() { CheckManaEndUpdate(); if (dead && dead_timer.Check()) { - database.MoveCharacterToZone(GetName(), m_pp.binds[0].zoneId); + database.MoveCharacterToZone(GetName(), m_pp.binds[0].zone_id); - m_pp.zone_id = m_pp.binds[0].zoneId; + m_pp.zone_id = m_pp.binds[0].zone_id; m_pp.zoneInstance = m_pp.binds[0].instance_id; m_pp.x = m_pp.binds[0].x; m_pp.y = m_pp.binds[0].y; @@ -180,11 +181,9 @@ bool Client::Process() { myraid->MemberZoned(this); } - Expedition* expedition = GetExpedition(); - if (expedition) - { - expedition->SetMemberStatus(this, ExpeditionMemberStatus::Offline); - } + SetDynamicZoneMemberStatus(DynamicZoneMemberStatus::Offline); + + parse->EventPlayer(EVENT_DISCONNECT, this, "", 0); return false; //delete client } @@ -200,9 +199,30 @@ bool Client::Process() { instalog = true; } + if (heroforge_wearchange_timer.Check()) { + /* + This addresses bug where on zone in heroforge models would not be sent to other clients when this was + in Client::CompleteConnect(). Sending after a small 250 ms delay after that function resolves the issue. + Unclear the underlying reason for this, if a better solution can be found then can move this back. + */ + if (queue_wearchange_slot >= 0) { //Resend slot from Client::SwapItem if heroforge item is swapped. + SendWearChange(static_cast(queue_wearchange_slot)); + } + else { //Send from Client::CompleteConnect() + SendWearChangeAndLighting(EQ::textures::LastTexture); + Mob *pet = GetPet(); + if (pet) { + pet->SendWearChangeAndLighting(EQ::textures::LastTexture); + } + } + heroforge_wearchange_timer.Disable(); + } + if (IsStunned() && stunned_timer.Check()) Mob::UnStun(); + cheat_manager.ClientProcess(); + if (bardsong_timer.Check() && bardsong != 0) { //NOTE: this is kinda a heavy-handed check to make sure the mob still exists before //doing the next pulse on them... @@ -218,9 +238,9 @@ bool Client::Process() { InterruptSpell(SONG_ENDS_ABRUPTLY, 0x121, bardsong); } else { - if (!ApplyNextBardPulse(bardsong, song_target, bardsong_slot)) + if (!ApplyBardPulse(bardsong, song_target, bardsong_slot)) { InterruptSpell(SONG_ENDS_ABRUPTLY, 0x121, bardsong); - //SpellFinished(bardsong, bardsong_target, bardsong_slot, spells[bardsong].mana); + } } } @@ -301,6 +321,10 @@ bool Client::Process() { } if (AutoFireEnabled()) { + if (GetTarget() == this) { + MessageString(Chat::TooFarAway, TRY_ATTACKING_SOMEONE); + auto_fire = false; + } EQ::ItemInstance *ranged = GetInv().GetItem(EQ::invslot::slotRange); if (ranged) { @@ -390,12 +414,17 @@ bool Client::Process() { else if (auto_attack_target->GetHP() > -10) // -10 so we can watch people bleed in PvP { EQ::ItemInstance *wpn = GetInv().GetItem(EQ::invslot::slotPrimary); - TryWeaponProc(wpn, auto_attack_target, EQ::invslot::slotPrimary); + TryCombatProcs(wpn, auto_attack_target, EQ::invslot::slotPrimary); TriggerDefensiveProcs(auto_attack_target, EQ::invslot::slotPrimary, false); DoAttackRounds(auto_attack_target, EQ::invslot::slotPrimary); + + if (TryDoubleMeleeRoundEffect()) { + DoAttackRounds(auto_attack_target, EQ::invslot::slotPrimary); + } + if (CheckAATimer(aaTimerRampage)) { - entity_list.AEAttack(this, 30); + entity_list.AEAttack(this, 40); } } } @@ -431,26 +460,15 @@ bool Client::Process() { CheckIncreaseSkill(EQ::skills::SkillDualWield, auto_attack_target, -10); if (CheckDualWield()) { EQ::ItemInstance *wpn = GetInv().GetItem(EQ::invslot::slotSecondary); - TryWeaponProc(wpn, auto_attack_target, EQ::invslot::slotSecondary); + TryCombatProcs(wpn, auto_attack_target, EQ::invslot::slotSecondary); DoAttackRounds(auto_attack_target, EQ::invslot::slotSecondary); } } } - if (HasVirus()) { - if (viral_timer.Check()) { - viral_timer_counter++; - for (int i = 0; i < MAX_SPELL_TRIGGER * 2; i += 2) { - if (viral_spells[i]) { - if (viral_timer_counter % spells[viral_spells[i]].viral_timer == 0) { - SpreadVirus(viral_spells[i], viral_spells[i + 1]); - } - } - } - } - if (viral_timer_counter > 999) - viral_timer_counter = 0; + if (viral_timer.Check() && !dead) { + VirusEffectProcess(); } ProjectileAttack(); @@ -460,32 +478,8 @@ bool Client::Process() { DoGravityEffect(); } - if (shield_timer.Check()) - { - if (shield_target) - { - if (!CombatRange(shield_target)) - { - entity_list.MessageCloseString( - this, false, 100, 0, - END_SHIELDING, GetCleanName(), shield_target->GetCleanName()); - for (int y = 0; y < 2; y++) - { - if (shield_target->shielder[y].shielder_id == GetID()) - { - shield_target->shielder[y].shielder_id = 0; - shield_target->shielder[y].shielder_bonus = 0; - } - } - shield_target = 0; - shield_timer.Disable(); - } - } - else - { - shield_target = 0; - shield_timer.Disable(); - } + if (shield_timer.Check()) { + ShieldAbilityFinish(); } SpellProcess(); @@ -562,7 +556,7 @@ bool Client::Process() { OnDisconnect(true); LogInfo("Client linkdead: {}", name); - if (Admin() > 100) { + if (Admin() > AccountStatus::GMAdmin) { if (GetMerc()) { GetMerc()->Save(); GetMerc()->Depop(); @@ -575,11 +569,7 @@ bool Client::Process() { AI_Start(CLIENT_LD_TIMEOUT); SendAppearancePacket(AT_Linkdead, 1); - Expedition* expedition = GetExpedition(); - if (expedition) - { - expedition->SetMemberStatus(this, ExpeditionMemberStatus::LinkDead); - } + SetDynamicZoneMemberStatus(DynamicZoneMemberStatus::LinkDead); } } @@ -711,10 +701,9 @@ void Client::OnDisconnect(bool hard_disconnect) { } } - Expedition* expedition = GetExpedition(); - if (expedition && !bZoning) + if (!bZoning) { - expedition->SetMemberStatus(this, ExpeditionMemberStatus::Offline); + SetDynamicZoneMemberStatus(DynamicZoneMemberStatus::Offline); } RemoveAllAuras(); @@ -1012,31 +1001,38 @@ void Client::OPRezzAnswer(uint32 Action, uint32 SpellID, uint16 ZoneID, uint16 I // corpse is in has shutdown since the rez spell was cast. database.MarkCorpseAsRezzed(PendingRezzDBID); LogSpells("Player [{}] got a [{}] Rezz, spellid [{}] in zone[{}], instance id [{}]", - this->name, (uint16)spells[SpellID].base[0], + this->name, (uint16)spells[SpellID].base_value[0], SpellID, ZoneID, InstanceID); - this->BuffFadeNonPersistDeath(); + BuffFadeNonPersistDeath(); int SpellEffectDescNum = GetSpellEffectDescNum(SpellID); // Rez spells with Rez effects have this DescNum (first is Titanium, second is 6.2 Client) - if((SpellEffectDescNum == 82) || (SpellEffectDescNum == 39067)) { + if(RuleB(Character, UseResurrectionSickness) && SpellEffectDescNum == 82 || SpellEffectDescNum == 39067) { + SetHP(GetMaxHP() / 5); SetMana(0); - SetHP(GetMaxHP()/5); - int rez_eff = 756; - if (RuleB(Character, UseOldRaceRezEffects) && - (GetRace() == BARBARIAN || GetRace() == DWARF || GetRace() == TROLL || GetRace() == OGRE)) - rez_eff = 757; - SpellOnTarget(rez_eff, this); // Rezz effects + int resurrection_sickness_spell_id = ( + RuleB(Character, UseOldRaceRezEffects) && + ( + GetRace() == BARBARIAN || + GetRace() == DWARF || + GetRace() == TROLL || + GetRace() == OGRE + ) ? + RuleI(Character, OldResurrectionSicknessSpellID) : + RuleI(Character, ResurrectionSicknessSpellID) + ); + SpellOnTarget(resurrection_sickness_spell_id, this); // Rezz effects } else { - SetMana(GetMaxMana()); SetHP(GetMaxHP()); + SetMana(GetMaxMana()); } - if(spells[SpellID].base[0] < 100 && spells[SpellID].base[0] > 0 && PendingRezzXP > 0) + if(spells[SpellID].base_value[0] < 100 && spells[SpellID].base_value[0] > 0 && PendingRezzXP > 0) { - SetEXP(((int)(GetEXP()+((float)((PendingRezzXP / 100) * spells[SpellID].base[0])))), + SetEXP(((int)(GetEXP()+((float)((PendingRezzXP / 100) * spells[SpellID].base_value[0])))), GetAAXP(),true); } - else if (spells[SpellID].base[0] == 100 && PendingRezzXP > 0) { + else if (spells[SpellID].base_value[0] == 100 && PendingRezzXP > 0) { SetEXP((GetEXP() + PendingRezzXP), GetAAXP(), true); } @@ -1053,6 +1049,11 @@ void Client::OPTGB(const EQApplicationPacket *app) { if(!app) return; if(!app->pBuffer) return; + + if(!RuleB(Character, EnableTGB)) + { + return; + } uint32 tgb_flag = *(uint32 *)app->pBuffer; if(tgb_flag == 2) @@ -1450,7 +1451,12 @@ void Client::OPMoveCoin(const EQApplicationPacket* app) } else{ if (to_bucket == &m_pp.platinum_shared || from_bucket == &m_pp.platinum_shared){ - this->Message(Chat::Red, "::: WARNING! ::: SHARED BANK IS DISABLED AND YOUR PLATINUM WILL BE DESTROYED IF YOU PUT IT HERE"); + this->SendPopupToClient( + "Shared Bank Warning", + "::: WARNING! :::
" + "SHARED BANK IS DISABLED AND YOUR PLATINUM WILL BE DESTROYED IF YOU PUT IT HERE!
" + ); + this->Message(Chat::Red, "::: WARNING! ::: SHARED BANK IS DISABLED AND YOUR PLATINUM WILL BE DESTROYED IF YOU PUT IT HERE!"); } } } @@ -1746,7 +1752,7 @@ void Client::OPGMSummon(const EQApplicationPacket *app) } else { - if(admin < 80) + if(admin < AccountStatus::QuestTroupe) { return; } @@ -1861,7 +1867,7 @@ void Client::DoEnduranceUpkeep() { uint32 buff_count = GetMaxTotalSlots(); for (buffs_i = 0; buffs_i < buff_count; buffs_i++) { if (buffs[buffs_i].spellid != SPELL_UNKNOWN) { - int upkeep = spells[buffs[buffs_i].spellid].EndurUpkeep; + int upkeep = spells[buffs[buffs_i].spellid].endurance_upkeep; if(upkeep > 0) { has_effect = true; if(cost_redux > 0) { @@ -1881,7 +1887,7 @@ void Client::DoEnduranceUpkeep() { if(upkeep_sum != 0){ SetEndurance(GetEndurance() - upkeep_sum); - TryTriggerOnValueAmount(false, false, true); + TryTriggerOnCastRequirement(); } if (!has_effect) @@ -1996,7 +2002,7 @@ void Client::HandleRespawnFromHover(uint32 Option) BindStruct* b = &m_pp.binds[0]; default_to_bind = new RespawnOption; default_to_bind->name = "Bind Location"; - default_to_bind->zone_id = b->zoneId; + default_to_bind->zone_id = b->zone_id; default_to_bind->instance_id = b->instance_id; default_to_bind->x = b->x; default_to_bind->y = b->y; @@ -2089,7 +2095,8 @@ void Client::HandleRespawnFromHover(uint32 Option) } //After they've respawned into the same zone, trigger EVENT_RESPAWN - parse->EventPlayer(EVENT_RESPAWN, this, static_cast(itoa(Option)), is_rez ? 1 : 0); + std::string export_string = fmt::format("{}", Option); + parse->EventPlayer(EVENT_RESPAWN, this, export_string, is_rez ? 1 : 0); //Pop Rez option from the respawn options list; //easiest way to make sure it stays at the end and diff --git a/zone/command.cpp b/zone/command.cpp index 62757f2b2..36da9fc95 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -1,36 +1,3 @@ -/* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.org) - - 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 -*/ - -/* - - To add a new command 3 things must be done: - - 1. At the bottom of command.h you must add a prototype for it. - 2. Add the function in this file. - 3. In the command_init function you must add a call to command_add - for your function. - - Notes: If you want an alias for your command, add an entry to the - `command_settings` table in your database. The access level you - set with command_add is the default setting if the command isn't - listed in the `command_settings` db table. - -*/ #include #include @@ -47,33 +14,28 @@ #include "../common/global_define.h" #include "../common/eq_packet.h" #include "../common/features.h" -#include "../common/guilds.h" -#include "../common/patches/patches.h" #include "../common/ptimer.h" #include "../common/rulesys.h" -#include "../common/serverinfo.h" #include "../common/string_util.h" #include "../common/say_link.h" -#include "../common/eqemu_logsys.h" -#include "../common/profanity_manager.h" #include "../common/net/eqstream.h" +#include "../common/file_util.h" +#include "../common/repositories/dynamic_zones_repository.h" #include "data_bucket.h" #include "command.h" +#include "dynamic_zone.h" #include "expedition.h" #include "guild_mgr.h" -#include "map.h" #include "qglobals.h" #include "queryserv.h" #include "quest_parser_collection.h" -#include "string_ids.h" #include "titles.h" #include "water_map.h" #include "worldserver.h" #include "fastmath.h" #include "mob_movement_manager.h" #include "npc_scale_manager.h" -#include "../common/content/world_content_service.h" extern QueryServ* QServ; extern WorldServer worldserver; @@ -88,10 +50,6 @@ int commandcount; // how many commands we have // init has been performed to point at the real function int (*command_dispatch)(Client *,char const *)=command_notavail; - -void command_bestz(Client *c, const Seperator *message); -void command_pf(Client *c, const Seperator *message); - std::map commandlist; std::map commandaliases; @@ -156,308 +114,310 @@ int command_init(void) commandaliases.clear(); if ( - command_add("acceptrules", "[acceptrules] - Accept the EQEmu Agreement", 0, command_acceptrules) || - command_add("advnpcspawn", "[maketype|makegroup|addgroupentry|addgroupspawn][removegroupspawn|movespawn|editgroupbox|cleargroupbox]", 150, command_advnpcspawn) || - command_add("aggro", "(range) [-v] - Display aggro information for all mobs 'range' distance from your target. -v is verbose faction info.", 80, command_aggro) || - command_add("aggrozone", "[aggro] - Aggro every mob in the zone with X aggro. Default is 0. Not recommend if you're not invulnerable.", 100, command_aggrozone) || - command_add("ai", "[factionid/spellslist/con/guard/roambox/stop/start] - Modify AI on NPC target", 100, command_ai) || - command_add("appearance", "[type] [value] - Send an appearance packet for you or your target", 150, command_appearance) || - command_add("apply_shared_memory", "[shared_memory_name] - Tells every zone and world to apply a specific shared memory segment by name.", 250, command_apply_shared_memory) || - command_add("attack", "[targetname] - Make your NPC target attack targetname", 150, command_attack) || - command_add("augmentitem", "Force augments an item. Must have the augment item window open.", 250, command_augmentitem) || - command_add("ban", "[name] [reason]- Ban by character name", 150, command_ban) || - command_add("beard", "- Change the beard of your target", 80, command_beard) || - command_add("beardcolor", "- Change the beard color of your target", 80, command_beardcolor) || - command_add("bestz", "- Ask map for a good Z coord for your x,y coords.", 0, command_bestz) || - command_add("bind", "- Sets your targets bind spot to their current location", 200, command_bind) || + command_add("acceptrules", "[acceptrules] - Accept the EQEmu Agreement", AccountStatus::Player, command_acceptrules) || + command_add("advnpcspawn", "[maketype|makegroup|addgroupentry|addgroupspawn][removegroupspawn|movespawn|editgroupbox|cleargroupbox]", AccountStatus::GMLeadAdmin, command_advnpcspawn) || + command_add("aggro", "[Distance] [-v] - Display aggro information for all mobs 'Distance' distance from your target. (-v is verbose Faction Information)", AccountStatus::QuestTroupe, command_aggro) || + command_add("aggrozone", "[aggro] - Aggro every mob in the zone with X aggro. Default is 0. Not recommend if you're not invulnerable.", AccountStatus::GMAdmin, command_aggrozone) || + command_add("ai", "[factionid/spellslist/con/guard/roambox/stop/start] - Modify AI on NPC target", AccountStatus::GMAdmin, command_ai) || + command_add("appearance", "[type] [value] - Send an appearance packet for you or your target", AccountStatus::GMLeadAdmin, command_appearance) || + command_add("appearanceeffects", "- [view] [set] [remove] appearance effects.", AccountStatus::GMAdmin, command_appearanceeffects) || + command_add("apply_shared_memory", "[shared_memory_name] - Tells every zone and world to apply a specific shared memory segment by name.", AccountStatus::GMImpossible, command_apply_shared_memory) || + command_add("attack", "[targetname] - Make your NPC target attack targetname", AccountStatus::GMLeadAdmin, command_attack) || + command_add("augmentitem", "Force augments an item. Must have the augment item window open.", AccountStatus::GMImpossible, command_augmentitem) || + command_add("ban", "[name] [reason]- Ban by character name", AccountStatus::GMLeadAdmin, command_ban) || + command_add("beard", "- Change the beard of your target", AccountStatus::QuestTroupe, command_beard) || + command_add("beardcolor", "- Change the beard color of your target", AccountStatus::QuestTroupe, command_beardcolor) || + command_add("bestz", "- Ask map for a good Z coord for your x,y coords.", AccountStatus::Player, command_bestz) || + command_add("bind", "- Sets your targets bind spot to their current location", AccountStatus::GMMgmt, command_bind) || #ifdef BOTS - command_add("bot", "- Type \"#bot help\" or \"^help\" to the see the list of available commands for bots.", 0, command_bot) || + command_add("bot", "- Type \"#bot help\" or \"^help\" to the see the list of available commands for bots.", AccountStatus::Player, command_bot) || #endif - command_add("camerashake", "Shakes the camera on everyone's screen globally.", 80, command_camerashake) || - command_add("castspell", "[spellid] - Cast a spell", 50, command_castspell) || - command_add("chat", "[channel num] [message] - Send a channel message to all zones", 200, command_chat) || - command_add("checklos", "- Check for line of sight to your target", 50, command_checklos) || - command_add("copycharacter", "[source_char_name] [dest_char_name] [dest_account_name] Copies character to destination account", 250, command_copycharacter) || - command_add("corpse", "- Manipulate corpses, use with no arguments for help", 50, command_corpse) || - command_add("corpsefix", "Attempts to bring corpses from underneath the ground within close proximity of the player", 0, command_corpsefix) || - command_add("crashtest", "- Crash the zoneserver", 255, command_crashtest) || - command_add("cvs", "- Summary of client versions currently online.", 200, command_cvs) || - command_add("damage", "[amount] - Damage your target", 100, command_damage) || - command_add("databuckets", "View|Delete [key] [limit]- View data buckets, limit 50 default or Delete databucket by key", 80, command_databuckets) || - command_add("date", "[yyyy] [mm] [dd] [HH] [MM] - Set EQ time", 90, command_date) || - command_add("dbspawn2", "[spawngroup] [respawn] [variance] - Spawn an NPC from a predefined row in the spawn2 table", 100, command_dbspawn2) || - command_add("delacct", "[accountname] - Delete an account", 150, command_delacct) || - command_add("deletegraveyard", "[zone name] - Deletes the graveyard for the specified zone.", 200, command_deletegraveyard) || - command_add("delpetition", "[petition number] - Delete a petition", 20, command_delpetition) || - command_add("depop", "- Depop your NPC target", 50, command_depop) || - command_add("depopzone", "- Depop the zone", 100, command_depopzone) || - command_add("devtools", "- Manages devtools", 200, command_devtools) || - command_add("details", "- Change the details of your target (Drakkin Only)", 80, command_details) || - command_add("disablerecipe", "[recipe_id] - Disables a recipe using the recipe id.", 80, command_disablerecipe) || - 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("dz", "Manage expeditions and dynamic zone instances", 80, command_dz) || - command_add("dzkickplayers", "Removes all players from current expedition. (/kickplayers alternative for pre-RoF clients)", 0, command_dzkickplayers) || - 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) || - command_add("enablerecipe", "[recipe_id] - Enables a recipe using the recipe id.", 80, command_enablerecipe) || - command_add("endurance", "Restores you or your target's endurance.", 50, command_endurance) || - command_add("equipitem", "[slotid(0-21)] - Equip the item on your cursor into the specified slot", 50, command_equipitem) || - command_add("face", "- Change the face of your target", 80, command_face) || - command_add("faction", "[Find (criteria | all ) | Review (criteria | all) | Reset (id)] - Resets Player's Faction", 80, command_faction) || - command_add("findaliases", "[search criteria]- Searches for available command aliases, by alias or command", 0, command_findaliases) || - command_add("findnpctype", "[search criteria] - Search database NPC types", 100, command_findnpctype) || - command_add("findrace", "[search criteria] - Search for a race", 50, command_findrace) || - command_add("findspell", "[search criteria] - Search for a spell", 50, command_findspell) || - command_add("findzone", "[search criteria] - Search database zones", 100, command_findzone) || - command_add("fixmob", "[race|gender|texture|helm|face|hair|haircolor|beard|beardcolor|heritage|tattoo|detail] [next|prev] - Manipulate appearance of your target", 80, command_fixmob) || - command_add("flag", "[status] [acctname] - Refresh your admin status, or set an account's admin status if arguments provided", 0, command_flag) || - command_add("flagedit", "- Edit zone flags on your target", 100, command_flagedit) || - command_add("flags", "- displays the flags of you or your target", 0, command_flags) || - command_add("flymode", "[0/1/2/3/4/5] - Set your or your player target's flymode to ground/flying/levitate/water/floating/levitate_running", 50, command_flymode) || - command_add("fov", "- Check wether you're behind or in your target's field of view", 80, command_fov) || - command_add("freeze", "- Freeze your target", 80, command_freeze) || - command_add("gassign", "[id] - Assign targetted NPC to predefined wandering grid id", 100, command_gassign) || - command_add("gearup", "Developer tool to quickly equip a character", 200, command_gearup) || - command_add("gender", "[0/1/2] - Change your or your target's gender to male/female/neuter", 50, command_gender) || - command_add("getplayerburiedcorpsecount", "- Get the target's total number of buried player corpses.", 100, command_getplayerburiedcorpsecount) || - command_add("getvariable", "[varname] - Get the value of a variable from the database", 200, command_getvariable) || - command_add("ginfo", "- get group info on target.", 20, command_ginfo) || - command_add("giveitem", "[itemid] [charges] - Summon an item onto your target's cursor. Charges are optional.", 200, command_giveitem) || - command_add("givemoney", "[pp] [gp] [sp] [cp] - Gives specified amount of money to the target player.", 200, command_givemoney) || - command_add("globalview", "Lists all qglobals in cache if you were to do a quest with this target.", 80, command_globalview) || - command_add("gm", "- Turn player target's or your GM flag on or off", 80, command_gm) || - command_add("gmspeed", "[on/off] - Turn GM speed hack on/off for you or your player target", 100, command_gmspeed) || - command_add("gmzone", "[zone_short_name] [zone_version=0] [identifier=gmzone] - Zones to a private GM instance", 100, command_gmzone) || - command_add("goto", "[playername] or [x y z] [h] - Teleport to the provided coordinates or to your target", 10, command_goto) || - command_add("grid", "[add/delete] [grid_num] [wandertype] [pausetype] - Create/delete a wandering grid", 170, command_grid) || - command_add("guild", "- Guild manipulation commands. Use argument help for more info.", 10, command_guild) || - command_add("guildapprove", "[guildapproveid] - Approve a guild with specified ID (guild creator receives the id)", 0, command_guildapprove) || - command_add("guildcreate", "[guildname] - Creates an approval setup for guild name specified", 0, command_guildcreate) || - command_add("guildlist", "[guildapproveid] - Lists character names who have approved the guild specified by the approve id", 0, command_guildlist) || - command_add("hair", "- Change the hair style of your target", 80, command_hair) || - command_add("haircolor", "- Change the hair color of your target", 80, command_haircolor) || - command_add("haste", "[percentage] - Set your haste percentage", 100, command_haste) || - command_add("hatelist", " - Display hate list for target.", 80, command_hatelist) || - command_add("heal", "- Completely heal your target", 10, command_heal) || - command_add("helm", "- Change the helm of your target", 80, command_helm) || - command_add("help", "[search term] - List available commands and their description, specify partial command as argument to search", 0, command_help) || - command_add("heritage", "- Change the heritage of your target (Drakkin Only)", 80, command_heritage) || - command_add("heromodel", "[hero model] [slot] - Full set of Hero's Forge Armor appearance. If slot is set, sends exact model just to slot.", 200, command_heromodel) || - command_add("hideme", "[on/off] - Hide yourself from spawn lists.", 80, command_hideme) || - command_add("hotfix", "[hotfix_name] - Reloads shared memory into a hotfix, equiv to load_shared_memory followed by apply_shared_memory", 250, command_hotfix) || - command_add("hp", "- Refresh your HP bar from the server.", 0, command_hp) || - command_add("incstat", "- Increases or Decreases a client's stats permanently.", 200, command_incstat) || - command_add("instance", "- Modify Instances", 200, command_instance) || - command_add("interrogateinv", "- use [help] argument for available options", 0, command_interrogateinv) || - command_add("interrupt", "[message id] [color] - Interrupt your casting. Arguments are optional.", 50, command_interrupt) || - command_add("invsnapshot", "- Manipulates inventory snapshots for your current target", 80, command_invsnapshot) || - command_add("invul", "[on/off] - Turn player target's or your invulnerable flag on or off", 80, command_invul) || - command_add("ipban", "[IP address] - Ban IP by character name", 200, command_ipban) || - command_add("iplookup", "[charname] - Look up IP address of charname", 200, command_iplookup) || - command_add("iteminfo", "- Get information about the item on your cursor", 10, command_iteminfo) || - command_add("itemsearch", "[search criteria] - Search for an item", 10, command_itemsearch) || - command_add("kick", "[charname] - Disconnect charname", 150, command_kick) || - command_add("kill", "- Kill your target", 100, command_kill) || - command_add("killallnpcs", " [npc_name] Kills all npcs by search name, leave blank for all attackable NPC's", 200, command_killallnpcs) || - command_add("lastname", "[new lastname] - Set your or your player target's lastname", 50, command_lastname) || - command_add("level", "[level] - Set your or your target's level", 10, command_level) || - command_add("listnpcs", "[name/range] - Search NPCs", 20, command_listnpcs) || - command_add("list", "[npcs|players|corpses|doors|objects] [search] - Search entities", 20, command_list) || - command_add("listpetition", "- List petitions", 50, command_listpetition) || - command_add("load_shared_memory", "[shared_memory_name] - Reloads shared memory and uses the input as output", 250, command_load_shared_memory) || - command_add("loc", "- Print out your or your target's current location and heading", 0, command_loc) || - command_add("lock", "- Lock the worldserver", 150, command_lock) || - command_add("logs", "Manage anything to do with logs", 250, command_logs) || - command_add("logtest", "Performs log performance testing.", 250, command_logtest) || - command_add("makepet", "[level] [class] [race] [texture] - Make a pet", 50, command_makepet) || - command_add("mana", "- Fill your or your target's mana", 50, command_mana) || - command_add("maxskills", "Maxes skills for you.", 200, command_max_all_skills) || - command_add("memspell", "[slotid] [spellid] - Memorize spellid in the specified slot", 50, command_memspell) || - command_add("merchant_close_shop", "Closes a merchant shop", 100, command_merchantcloseshop) || - command_add("merchant_open_shop", "Opens a merchants shop", 100, command_merchantopenshop) || - command_add("modifynpcstat", "- Modifys a NPC's stats", 150, command_modifynpcstat) || - command_add("motd", "[new motd] - Set message of the day", 150, command_motd) || - command_add("movechar", "[charname] [zonename] - Move charname to zonename", 50, command_movechar) || - command_add("movement", "Various movement commands", 200, command_movement) || - command_add("myskills", "- Show details about your current skill levels", 0, command_myskills) || - command_add("mysqltest", "Akkadius MySQL Bench Test", 250, command_mysqltest) || - command_add("mysql", "Mysql CLI, see 'help' for options.", 250, command_mysql) || - command_add("mystats", "- Show details about you or your pet", 50, command_mystats) || - command_add("name", "[newname] - Rename your player target", 150, command_name) || - command_add("netstats", "- Gets the network stats for a stream.", 200, command_netstats) || - command_add("network", "- Admin commands for the udp network interface.", 250, command_network) || - command_add("npccast", "[targetname/entityid] [spellid] - Causes NPC target to cast spellid on targetname/entityid", 80, command_npccast) || - command_add("npcedit", "[column] [value] - Mega NPC editing command", 100, command_npcedit) || - command_add("npceditmass", "[name-search] [column] [value] - Mass (Zone wide) NPC data editing command", 100, command_npceditmass) || - command_add("npcemote", "[message] - Make your NPC target emote a message.", 150, command_npcemote) || - command_add("npcloot", "[show/money/add/remove] [itemid/all/money: pp gp sp cp] - Manipulate the loot an NPC is carrying", 80, command_npcloot) || - command_add("npcsay", "[message] - Make your NPC target say a message.", 150, command_npcsay) || - command_add("npcshout", "[message] - Make your NPC target shout a message.", 150, command_npcshout) || - command_add("npcspawn", "[create/add/update/remove/delete] - Manipulate spawn DB", 170, command_npcspawn) || - command_add("npcspecialattk", "[flagchar] [perm] - Set NPC special attack flags. Flags are E(nrage) F(lurry) R(ampage) S(ummon).", 80, command_npcspecialattk) || - command_add("npcstats", "- Show stats about target NPC", 80, command_npcstats) || - command_add("npctype_cache", "[id] or all - Clears the npc type cache for either the id or all npcs.", 250, command_npctype_cache) || - command_add("npctypespawn", "[npctypeid] [factionid] - Spawn an NPC from the db", 10, command_npctypespawn) || - command_add("nudge", "- Nudge your target's current position by specific values", 80, command_nudge) || - command_add("nukebuffs", "- Strip all buffs on you or your target", 50, command_nukebuffs) || - command_add("nukeitem", "[itemid] - Remove itemid from your player target's inventory", 150, command_nukeitem) || - command_add("object", "List|Add|Edit|Move|Rotate|Copy|Save|Undo|Delete - Manipulate static and tradeskill objects within the zone", 100, command_object) || - command_add("oocmute", "[1/0] - Mutes OOC chat", 200, command_oocmute) || - command_add("opcode", "- opcode management", 250, command_opcode) || - -#ifdef PACKET_PROFILER - command_add("packetprofile", "- Dump packet profile for target or self.", 250, command_packetprofile) || -#endif - - command_add("path", "- view and edit pathing", 200, command_path) || - command_add("peekinv", "[equip/gen/cursor/poss/limbo/curlim/trib/bank/shbank/allbank/trade/world/all] - Print out contents of your player target's inventory", 100, command_peekinv) || - command_add("peqzone", "[zonename] - Go to specified zone, if you have > 75% health", 0, command_peqzone) || - command_add("permaclass", "[classnum] - Change your or your player target's class (target is disconnected)", 80, command_permaclass) || - command_add("permagender", "[gendernum] - Change your or your player target's gender (zone to take effect)", 80, command_permagender) || - command_add("permarace", "[racenum] - Change your or your player target's race (zone to take effect)", 80, command_permarace) || - command_add("petitioninfo", "[petition number] - Get info about a petition", 20, command_petitioninfo) || - command_add("pf", "- Display additional mob coordinate and wandering data", 0, command_pf) || - command_add("picklock", "Analog for ldon pick lock for the newer clients since we still don't have it working.", 0, command_picklock) || - command_add("profanity", "Manage censored language.", 150, command_profanity) || - -#ifdef EQPROFILE - command_add("profiledump", "- Dump profiling info to logs", 250, command_profiledump) || - command_add("profilereset", "- Reset profiling info", 250, command_profilereset) || -#endif - - command_add("push", "Lets you do spell push", 150, command_push) || - command_add("proximity", "Shows NPC proximity", 150, command_proximity) || - command_add("pvp", "[on/off] - Set your or your player target's PVP status", 100, command_pvp) || - command_add("qglobal", "[on/off/view] - Toggles qglobal functionality on an NPC", 100, command_qglobal) || - command_add("questerrors", "Shows quest errors.", 100, command_questerrors) || - command_add("race", "[racenum] - Change your or your target's race. Use racenum 0 to return to normal", 50, command_race) || - command_add("raidloot", "LEADER|GROUPLEADER|SELECTED|ALL - Sets your raid loot settings if you have permission to do so.", 0, command_raidloot) || - command_add("randomfeatures", "- Temporarily randomizes the Facial Features of your target", 80, command_randomfeatures) || - command_add("refreshgroup", "- Refreshes Group.", 0, command_refreshgroup) || - command_add("reloadaa", "Reloads AA data", 200, command_reloadaa) || - command_add("reloadallrules", "Executes a reload of all rules.", 80, command_reloadallrules) || - command_add("reloademote", "Reloads NPC Emotes", 80, command_reloademote) || - command_add("reloadlevelmods", nullptr, 255, command_reloadlevelmods) || - command_add("reloadmerchants", nullptr, 255, command_reloadmerchants) || - command_add("reloadperlexportsettings", nullptr, 255, command_reloadperlexportsettings) || - command_add("reloadqst", " - Clear quest cache (any argument causes it to also stop all timers)", 150, command_reloadqst) || - command_add("reloadrulesworld", "Executes a reload of all rules in world specifically.", 80, command_reloadworldrules) || - command_add("reloadstatic", "- Reload Static Zone Data", 150, command_reloadstatic) || - command_add("reloadtraps", "- Repops all traps in the current zone.", 80, command_reloadtraps) || - command_add("reloadtitles", "- Reload player titles from the database", 150, command_reloadtitles) || - command_add("reloadworld", "[0|1] - Clear quest cache (0 - no repop, 1 - repop)", 255, command_reloadworld) || - command_add("reloadzps", "- Reload zone points from database", 150, command_reloadzps) || - command_add("repop", "[delay] - Repop the zone with optional delay", 100, command_repop) || - command_add("resetaa", "- Resets a Player's AA in their profile and refunds spent AA's to unspent, may disconnect player.", 200, command_resetaa) || - command_add("resetaa_timer", "Command to reset AA cooldown timers.", 200, command_resetaa_timer) || - command_add("revoke", "[charname] [1/0] - Makes charname unable to talk on OOC", 200, command_revoke) || - command_add("roambox", "Manages roambox settings for an NPC", 200, command_roambox) || - command_add("rules", "(subcommand) - Manage server rules", 250, command_rules) || - command_add("save", "- Force your player or player corpse target to be saved to the database", 50, command_save) || - command_add("scale", "- Handles npc scaling", 150, command_scale) || - command_add("scribespell", "[spellid] - Scribe specified spell in your target's spell book.", 180, command_scribespell) || - command_add("scribespells", "[max level] [min level] - Scribe all spells for you or your player target that are usable by them, up to level specified. (may freeze client for a few seconds)", 150, command_scribespells) || - command_add("sendzonespawns", "- Refresh spawn list for all clients in zone", 150, command_sendzonespawns) || - command_add("sensetrap", "Analog for ldon sense trap for the newer clients since we still don't have it working.", 0, command_sensetrap) || - command_add("serverinfo", "- Get OS info about server host", 200, command_serverinfo) || - command_add("serverrules", "- Read this server's rules", 0, command_serverrules) || - command_add("setaapts", "[value] - Set your or your player target's available AA points", 100, command_setaapts) || - command_add("setaaxp", "[value] - Set your or your player target's AA experience", 100, command_setaaxp) || - command_add("setadventurepoints", "- Set your or your player target's available adventure points", 150, command_set_adventure_points) || - command_add("setanim", "[animnum] - Set target's appearance to animnum", 200, command_setanim) || - command_add("setcrystals", "[value] - Set your or your player target's available radiant or ebon crystals", 100, command_setcrystals) || - command_add("setfaction", "[faction number] - Sets targeted NPC's faction in the database", 170, command_setfaction) || - command_add("setgraveyard", "[zone name] - Creates a graveyard for the specified zone based on your target's LOC.", 200, command_setgraveyard) || - command_add("setlanguage", "[language ID] [value] - Set your target's language skillnum to value", 50, command_setlanguage) || - command_add("setlsinfo", "[email] [password] - Set login server email address and password (if supported by login server)", 10, command_setlsinfo) || - command_add("setpass", "[accountname] [password] - Set local password for accountname", 150, command_setpass) || - command_add("setpvppoints", "[value] - Set your or your player target's PVP points", 100, command_setpvppoints) || - command_add("setskill", "[skillnum] [value] - Set your target's skill skillnum to value", 50, command_setskill) || - command_add("setskillall", "[value] - Set all of your target's skills to value", 50, command_setskillall) || - command_add("setstartzone", "[zoneid] - Set target's starting zone. Set to zero to allow the player to use /setstartcity", 80, command_setstartzone) || - command_add("setstat", "- Sets the stats to a specific value.", 255, command_setstat) || - command_add("setxp", "[value] - Set your or your player target's experience", 100, command_setxp) || - command_add("showbonusstats", "[item|spell|all] Shows bonus stats for target from items or spells. Shows both by default.", 50, command_showbonusstats) || - command_add("showbuffs", "- List buffs active on your target or you if no target", 50, command_showbuffs) || - command_add("shownumhits", "Shows buffs numhits for yourself.", 0, command_shownumhits) || - command_add("shownpcgloballoot", "Show GlobalLoot entires on this npc", 50, command_shownpcgloballoot) || - command_add("showskills", "- Show the values of your or your player target's skills", 50, command_showskills) || - command_add("showspellslist", "Shows spell list of targeted NPC", 100, command_showspellslist) || - command_add("showstats", "- Show details about you or your target", 50, command_showstats) || - command_add("showzonegloballoot", "Show GlobalLoot entires on this zone", 50, command_showzonegloballoot) || - command_add("showzonepoints", "Show zone points for current zone", 50, command_showzonepoints) || - command_add("shutdown", "- Shut this zone process down", 150, command_shutdown) || - command_add("size", "[size] - Change size of you or your target", 50, command_size) || - command_add("spawn", "[name] [race] [level] [material] [hp] [gender] [class] [priweapon] [secweapon] [merchantid] - Spawn an NPC", 10, command_spawn) || - command_add("spawneditmass", "Mass editing spawn command", 150, command_spawneditmass) || - command_add("spawnfix", "- Find targeted NPC in database based on its X/Y/heading and update the database to make it spawn at your current location/heading.", 170, command_spawnfix) || - command_add("spawnstatus", "- Show respawn timer status", 100, command_spawnstatus) || - command_add("spellinfo", "[spellid] - Get detailed info about a spell", 10, command_spellinfo) || - command_add("spoff", "- Sends OP_ManaChange", 80, command_spoff) || - command_add("spon", "- Sends OP_MemorizeSpell", 80, command_spon) || - command_add("stun", "[duration] - Stuns you or your target for duration", 100, command_stun) || - command_add("summon", "[charname] - Summons your player/npc/corpse target, or charname if specified", 80, command_summon) || - command_add("summonburiedplayercorpse", "- Summons the target's oldest buried corpse, if any exist.", 100, command_summonburiedplayercorpse) || - command_add("summonitem", "[itemid] [charges] - Summon an item onto your cursor. Charges are optional.", 200, command_summonitem) || - command_add("suspend", "[name] [days] [reason] - Suspend by character name and for specificed number of days", 150, command_suspend) || - command_add("task", "(subcommand) - Task system commands", 150, command_task) || - command_add("tattoo", "- Change the tattoo of your target (Drakkin Only)", 80, command_tattoo) || - command_add("tempname", "[newname] - Temporarily renames your target. Leave name blank to restore the original name.", 100, command_tempname) || - command_add("petname", "[newname] - Temporarily renames your pet. Leave name blank to restore the original name.", 100, command_petname) || - command_add("test", "Test command", 200, command_test) || - command_add("texture", "[texture] [helmtexture] - Change your or your target's appearance, use 255 to show equipment", 10, command_texture) || - command_add("time", "[HH] [MM] - Set EQ time", 90, command_time) || - command_add("timers", "- Display persistent timers for target", 200, command_timers) || - command_add("timezone", "[HH] [MM] - Set timezone. Minutes are optional", 90, command_timezone) || - command_add("title", "[text] [1 = create title table row] - Set your or your player target's title", 50, command_title) || - command_add("titlesuffix", "[text] [1 = create title table row] - Set your or your player target's title suffix", 50, command_titlesuffix) || - command_add("traindisc", "[level] - Trains all the disciplines usable by the target, up to level specified. (may freeze client for a few seconds)", 150, command_traindisc) || - command_add("trapinfo", "- Gets infomation about the traps currently spawned in the zone.", 81, command_trapinfo) || - command_add("tune", "Calculate ideal statical values related to combat.", 100, command_tune) || - command_add("ucs", "- Attempts to reconnect to the UCS server", 0, command_ucs) || - command_add("undyeme", "- Remove dye from all of your armor slots", 0, command_undyeme) || - command_add("unfreeze", "- Unfreeze your target", 80, command_unfreeze) || - command_add("unlock", "- Unlock the worldserver", 150, command_unlock) || - command_add("unscribespell", "[spellid] - Unscribe specified spell from your target's spell book.", 180, command_unscribespell) || - command_add("unscribespells", "- Clear out your or your player target's spell book.", 180, command_unscribespells) || - command_add("untraindisc", "[spellid] - Untrain specified discipline from your target.", 180, command_untraindisc) || - command_add("untraindiscs", "- Untrains all disciplines from your target.", 180, command_untraindiscs) || - command_add("uptime", "[zone server id] - Get uptime of worldserver, or zone server if argument provided", 10, command_uptime) || - command_add("version", "- Display current version of EQEmu server", 0, command_version) || - command_add("viewnpctype", "[npctype id] - Show info about an npctype", 100, command_viewnpctype) || - command_add("viewpetition", "[petition number] - View a petition", 20, command_viewpetition) || - command_add("wc", "[wear slot] [material] - Sends an OP_WearChange for your target", 200, command_wc) || - command_add("weather", "[0/1/2/3] (Off/Rain/Snow/Manual) - Change the weather", 80, command_weather) || - command_add("who", "[search]", 20, command_who) || - command_add("worldshutdown", "- Shut down world and all zones", 200, command_worldshutdown) || - command_add("wp", "[add/delete] [grid_num] [pause] [wp_num] [-h] - Add/delete a waypoint to/from a wandering grid", 170, command_wp) || - command_add("wpadd", "[pause] [-h] - Add your current location as a waypoint to your NPC target's AI path", 170, command_wpadd) || - command_add("wpinfo", "- Show waypoint info about your NPC target", 170, command_wpinfo) || - command_add("worldwide", "Performs world-wide GM functions such as cast (can be extended for other commands). Use caution", 250, command_worldwide) || - command_add("xtargets", "Show your targets Extended Targets and optionally set how many xtargets they can have.", 250, command_xtargets) || - command_add("zclip", "[min] [max] - modifies and resends zhdr packet", 80, command_zclip) || - command_add("zcolor", "[red] [green] [blue] - Change sky color", 80, command_zcolor) || - command_add("zheader", "[zonename] - Load zheader for zonename from the database", 80, command_zheader) || - command_add("zone", "[zonename] [x] [y] [z] - Go to specified zone (coords optional)", 50, command_zone) || - command_add("zonebootup", "[ZoneServerID] [shortname] - Make a zone server boot a specific zone", 150, command_zonebootup) || - command_add("zoneinstance", "[instanceid] [x] [y] [z] - Go to specified instance zone (coords optional)", 50, command_zone_instance) || - command_add("zonelock", "[list/lock/unlock] - Set/query lock flag for zoneservers", 100, command_zonelock) || - command_add("zoneshutdown", "[shortname] - Shut down a zone server", 150, command_zoneshutdown) || - command_add("zonespawn", "- Not implemented", 250, command_zonespawn) || - command_add("zonestatus", "- Show connected zoneservers, synonymous with /servers", 150, command_zonestatus) || - command_add("zopp", "Troubleshooting command - Sends a fake item packet to you. No server reference is created.", 250, command_zopp) || - command_add("zsafecoords", "[x] [y] [z] - Set safe coords", 80, command_zsafecoords) || - command_add("zsave", " - Saves zheader to the database", 80, command_zsave) || - command_add("zsky", "[skytype] - Change zone sky type", 80, command_zsky) || - command_add("zstats", "- Show info about zone header", 80, command_zstats) || - command_add("zunderworld", "[zcoord] - Sets the underworld using zcoord", 80, command_zunderworld) || - command_add("zuwcoords", "[z coord] - Set underworld coord", 80, command_zuwcoords) + command_add("camerashake", "[Duration (Milliseconds)] [Intensity (1-10)] - Shakes the camera on everyone's screen globally.", AccountStatus::QuestTroupe, command_camerashake) || + command_add("castspell", "[Spell ID] [Instant (0 = False, 1 = True, Default is 1 if Unused)] - Cast a spell", AccountStatus::Guide, command_castspell) || + command_add("chat", "[channel num] [message] - Send a channel message to all zones", AccountStatus::GMMgmt, command_chat) || + command_add("checklos", "- Check for line of sight to your target", AccountStatus::Guide, command_checklos) || + command_add("copycharacter", "[source_char_name] [dest_char_name] [dest_account_name] Copies character to destination account", AccountStatus::GMImpossible, command_copycharacter) || + command_add("corpse", "- Manipulate corpses, use with no arguments for help", AccountStatus::Guide, command_corpse) || + command_add("corpsefix", "Attempts to bring corpses from underneath the ground within close proximity of the player", AccountStatus::Player, command_corpsefix) || + command_add("countitem", "[Item ID] - Counts the specified Item ID in your or your target's inventory", AccountStatus::GMLeadAdmin, command_countitem) || + command_add("cvs", "- Summary of client versions currently online.", AccountStatus::GMMgmt, command_cvs) || + command_add("damage", "[Amount] - Damage yourself or your target", AccountStatus::GMAdmin, command_damage) || + command_add("databuckets", "View|Delete [key] [limit]- View data buckets, limit 50 default or Delete databucket by key", AccountStatus::QuestTroupe, command_databuckets) || + command_add("date", "[yyyy] [mm] [dd] [HH] [MM] - Set EQ time", AccountStatus::EQSupport, command_date) || + command_add("dbspawn2", "[spawngroup] [respawn] [variance] - Spawn an NPC from a predefined row in the spawn2 table", AccountStatus::GMAdmin, command_dbspawn2) || + command_add("delacct", "[accountname] - Delete an account", AccountStatus::GMLeadAdmin, command_delacct) || + command_add("deletegraveyard", "[zone name] - Deletes the graveyard for the specified zone.", AccountStatus::GMMgmt, command_deletegraveyard) || + command_add("delpetition", "[petition number] - Delete a petition", AccountStatus::ApprenticeGuide, command_delpetition) || + command_add("depop", "- Depop your NPC target", AccountStatus::Guide, command_depop) || + command_add("depopzone", "- Depop the zone", AccountStatus::GMAdmin, command_depopzone) || + command_add("devtools", "- Manages devtools", AccountStatus::GMMgmt, command_devtools) || + command_add("details", "- Change the details of your target (Drakkin Only)", AccountStatus::QuestTroupe, command_details) || + command_add("disablerecipe", "[Recipe ID] - Disables a Recipe", AccountStatus::QuestTroupe, command_disablerecipe) || + command_add("disarmtrap", "Analog for ldon disarm trap for the newer clients since we still don't have it working.", AccountStatus::QuestTroupe, command_disarmtrap) || + command_add("distance", "- Reports the distance between you and your target.", AccountStatus::QuestTroupe, command_distance) || + command_add("door", "Door editing command", AccountStatus::QuestTroupe, command_door) || + command_add("doanim", "[animnum] [type] - Send an EmoteAnim for you or your target", AccountStatus::Guide, command_doanim) || + command_add("dye", "[slot|'help'] [red] [green] [blue] [use_tint] - Dyes the specified armor slot to Red, Green, and Blue provided, allows you to bypass darkness limits.", AccountStatus::ApprenticeGuide, command_dye) || + command_add("dz", "Manage expeditions and dynamic zone instances", AccountStatus::QuestTroupe, command_dz) || + command_add("dzkickplayers", "Removes all players from current expedition. (/kickplayers alternative for pre-RoF clients)", AccountStatus::Player, command_dzkickplayers) || + command_add("editmassrespawn", "[name-search] [second-value] - Mass (Zone wide) NPC respawn timer editing command", AccountStatus::GMAdmin, command_editmassrespawn) || + command_add("emote", "['name'/'world'/'zone'] [type] [message] - Send an emote message", AccountStatus::QuestTroupe, command_emote) || + command_add("emotesearch", "Searches NPC Emotes", AccountStatus::QuestTroupe, command_emotesearch) || + command_add("emoteview", "Lists all NPC Emotes", AccountStatus::QuestTroupe, command_emoteview) || + command_add("emptyinventory", "- Clears your or your target's entire inventory (Equipment, General, Bank, and Shared Bank)", AccountStatus::GMImpossible, command_emptyinventory) || + command_add("enablerecipe", "[Recipe ID] - Enables a Recipe", AccountStatus::QuestTroupe, command_enablerecipe) || + command_add("endurance", "Restores your or your target's endurance.", AccountStatus::Guide, command_endurance) || + command_add("equipitem", "[slotid(0-21)] - Equip the item on your cursor into the specified slot", AccountStatus::Guide, command_equipitem) || + command_add("face", "- Change the face of your target", AccountStatus::QuestTroupe, command_face) || + command_add("faction", "[Find (criteria | all ) | Review (criteria | all) | Reset (id)] - Resets Player's Faction", AccountStatus::QuestTroupe, command_faction) || + command_add("findaliases", "[search criteria]- Searches for available command aliases, by alias or command", AccountStatus::Player, command_findaliases) || + command_add("findclass", "[search criteria] - Search for a class", AccountStatus::Guide, command_findclass) || + command_add("findfaction", "[search criteria] - Search for a faction", AccountStatus::Guide, command_findfaction) || + command_add("findnpctype", "[search criteria] - Search database NPC types", AccountStatus::GMAdmin, command_findnpctype) || + command_add("findrace", "[search criteria] - Search for a race", AccountStatus::Guide, command_findrace) || + command_add("findskill", "[search criteria] - Search for a skill", AccountStatus::Guide, command_findskill) || + command_add("findspell", "[search criteria] - Search for a spell", AccountStatus::Guide, command_findspell) || + command_add("findtask", "[search criteria] - Search for a task", AccountStatus::Guide, command_findtask) || + command_add("findzone", "[search criteria] - Search database zones", AccountStatus::GMAdmin, command_findzone) || + command_add("fixmob", "[race|gender|texture|helm|face|hair|haircolor|beard|beardcolor|heritage|tattoo|detail] [next|prev] - Manipulate appearance of your target", AccountStatus::QuestTroupe, command_fixmob) || + command_add("flag", "[status] [acctname] - Refresh your admin status, or set an account's admin status if arguments provided", AccountStatus::Player, command_flag) || + command_add("flagedit", "- Edit zone flags on your target. Use #flagedit help for more info.", AccountStatus::GMAdmin, command_flagedit) || + command_add("flags", "- displays the flags of you or your target", AccountStatus::Player, command_flags) || + command_add("flymode", "[0/1/2/3/4/5] - Set your or your player target's flymode to ground/flying/levitate/water/floating/levitate_running", AccountStatus::Guide, command_flymode) || + command_add("fov", "- Check wether you're behind or in your target's field of view", AccountStatus::QuestTroupe, command_fov) || + command_add("freeze", "- Freeze your target", AccountStatus::QuestTroupe, command_freeze) || + command_add("gassign", "[id] - Assign targetted NPC to predefined wandering grid id", AccountStatus::GMAdmin, command_gassign) || + command_add("gearup", "Developer tool to quickly equip a character", AccountStatus::GMMgmt, command_gearup) || + command_add("gender", "[0/1/2] - Change your or your target's gender to male/female/neuter", AccountStatus::Guide, command_gender) || + command_add("getplayerburiedcorpsecount", "- Get your or your target's total number of buried player corpses.", AccountStatus::GMAdmin, command_getplayerburiedcorpsecount) || + command_add("getvariable", "[varname] - Get the value of a variable from the database", AccountStatus::GMMgmt, command_getvariable) || + command_add("ginfo", "- get group info on target.", AccountStatus::ApprenticeGuide, command_ginfo) || + command_add("giveitem", "[itemid] [charges] - Summon an item onto your target's cursor. Charges are optional.", AccountStatus::GMMgmt, command_giveitem) || + command_add("givemoney", "[Platinum] [Gold] [Silver] [Copper] - Gives specified amount of money to you or your player target", AccountStatus::GMMgmt, command_givemoney) || + command_add("globalview", "Lists all qglobals in cache if you were to do a quest with this target.", AccountStatus::QuestTroupe, command_globalview) || + command_add("gm", "[On|Off] - Modify your or your target's GM Flag", AccountStatus::QuestTroupe, command_gm) || + command_add("gmspeed", "[On|Off] - Turn GM Speed On or Off for you or your player target", AccountStatus::GMAdmin, command_gmspeed) || + command_add("gmzone", "[Zone ID|Zone Short Name] [Version] [Instance Identifier] - Zones to a private GM instance (Version defaults to 0 and Instance Identifier defaults to 'gmzone' if not used)", AccountStatus::GMAdmin, command_gmzone) || + command_add("goto", "[playername] or [x y z] [h] - Teleport to the provided coordinates or to your target", AccountStatus::Steward, command_goto) || + command_add("grid", "[add/delete] [grid_num] [wandertype] [pausetype] - Create/delete a wandering grid", AccountStatus::GMAreas, command_grid) || + command_add("guild", "- Guild manipulation commands. Use argument help for more info.", AccountStatus::Steward, command_guild) || + command_add("guildapprove", "[guildapproveid] - Approve a guild with specified ID (guild creator receives the id)", AccountStatus::Player, command_guildapprove) || + command_add("guildcreate", "[guildname] - Creates an approval setup for guild name specified", AccountStatus::Player, command_guildcreate) || + command_add("guildlist", "[guildapproveid] - Lists character names who have approved the guild specified by the approve id", AccountStatus::Player, command_guildlist) || + command_add("hair", "- Change the hair style of your target", AccountStatus::QuestTroupe, command_hair) || + command_add("haircolor", "- Change the hair color of your target", AccountStatus::QuestTroupe, command_haircolor) || + command_add("haste", "[percentage] - Set your haste percentage", AccountStatus::GMAdmin, command_haste) || + command_add("hatelist", "- Display hate list for NPC.", AccountStatus::QuestTroupe, command_hatelist) || + command_add("heal", "- Completely heal your target", AccountStatus::Steward, command_heal) || + command_add("helm", "- Change the helm of your target", AccountStatus::QuestTroupe, command_helm) || + command_add("help", "[search term] - List available commands and their description, specify partial command as argument to search", AccountStatus::Player, command_help) || + command_add("heritage", "- Change the heritage of your target (Drakkin Only)", AccountStatus::QuestTroupe, command_heritage) || + command_add("heromodel", "[hero model] [slot] - Full set of Hero's Forge Armor appearance. If slot is set, sends exact model just to slot.", AccountStatus::GMMgmt, command_heromodel) || + command_add("hideme", "[on/off] - Hide yourself from spawn lists.", AccountStatus::QuestTroupe, command_hideme) || + command_add("hotfix", "[hotfix_name] - Reloads shared memory into a hotfix, equiv to load_shared_memory followed by apply_shared_memory", AccountStatus::GMImpossible, command_hotfix) || + command_add("hp", "- Refresh your HP bar from the server.", AccountStatus::Player, command_hp) || + command_add("incstat", "- Increases or Decreases a client's stats permanently.", AccountStatus::GMMgmt, command_incstat) || + command_add("instance", "- Modify Instances", AccountStatus::GMMgmt, command_instance) || + command_add("interrogateinv", "- use [help] argument for available options", AccountStatus::Player, command_interrogateinv) || + command_add("interrupt", "[message id] [color] - Interrupt your casting. Arguments are optional.", AccountStatus::Guide, command_interrupt) || + command_add("invsnapshot", "- Manipulates inventory snapshots for your current target", AccountStatus::QuestTroupe, command_invsnapshot) || + command_add("invul", "[On|Off]] - Turn player target's or your invulnerable flag on or off", AccountStatus::QuestTroupe, command_invul) || + command_add("ipban", "[IP address] - Ban IP by character name", AccountStatus::GMMgmt, command_ipban) || + command_add("iplookup", "[charname] - Look up IP address of charname", AccountStatus::GMMgmt, command_iplookup) || + command_add("iteminfo", "- Get information about the item on your cursor", AccountStatus::Steward, command_iteminfo) || + command_add("itemsearch", "[search criteria] - Search for an item", AccountStatus::Steward, command_itemsearch) || + command_add("kick", "[charname] - Disconnect charname", AccountStatus::GMLeadAdmin, command_kick) || + command_add("kill", "- Kill your target", AccountStatus::GMAdmin, command_kill) || + command_add("killallnpcs", " [npc_name] Kills all npcs by search name, leave blank for all attackable NPC's", AccountStatus::GMMgmt, command_killallnpcs) || + command_add("lastname", "[Last Name] - Set you or your player target's lastname", AccountStatus::Guide, command_lastname) || + command_add("level", "[level] - Set your or your target's level", AccountStatus::Steward, command_level) || + command_add("list", "[npcs|players|corpses|doors|objects] [search] - Search entities", AccountStatus::ApprenticeGuide, command_list) || + command_add("listpetition", "- List petitions", AccountStatus::Guide, command_listpetition) || + command_add("load_shared_memory", "[shared_memory_name] - Reloads shared memory and uses the input as output", AccountStatus::GMImpossible, command_load_shared_memory) || + command_add("loc", "- Print out your or your target's current location and heading", AccountStatus::Player, command_loc) || + command_add("lock", "- Lock the worldserver", AccountStatus::GMLeadAdmin, command_lock) || + command_add("logs", "Manage anything to do with logs", AccountStatus::GMImpossible, command_logs) || + command_add("makepet", "[level] [class] [race] [texture] - Make a pet", AccountStatus::Guide, command_makepet) || + command_add("mana", "- Fill your or your target's mana", AccountStatus::Guide, command_mana) || + command_add("maxskills", "Maxes skills for you.", AccountStatus::GMMgmt, command_max_all_skills) || + command_add("memspell", "[Spell ID] [Spell Gem] - Memorize a Spell by ID to the specified Spell Gem for you or your target", AccountStatus::Guide, command_memspell) || + command_add("merchant_close_shop", "Closes a merchant shop", AccountStatus::GMAdmin, command_merchantcloseshop) || + command_add("merchant_open_shop", "Opens a merchants shop", AccountStatus::GMAdmin, command_merchantopenshop) || + command_add("modifynpcstat", "- Modifys a NPC's stats", AccountStatus::GMLeadAdmin, command_modifynpcstat) || + command_add("motd", "[new motd] - Set message of the day", AccountStatus::GMLeadAdmin, command_motd) || + command_add("movechar", "[Character ID|Character Name] [Zone ID|Zone Short Name] - Move an offline character to the specified zone", AccountStatus::Guide, command_movechar) || + command_add("movement", "Various movement commands", AccountStatus::GMMgmt, command_movement) || + command_add("myskills", "- Show details about your current skill levels", AccountStatus::Player, command_myskills) || + command_add("mysql", "[Help|Query] [SQL Query] - Mysql CLI, see 'Help' for options.", AccountStatus::GMImpossible, command_mysql) || + command_add("mystats", "- Show details about you or your pet", AccountStatus::Guide, command_mystats) || + command_add("name", "[New Name] - Rename your player target", AccountStatus::GMLeadAdmin, command_name) || + command_add("netstats", "- Gets the network stats for a stream.", AccountStatus::GMMgmt, command_netstats) || + command_add("network", "- Admin commands for the udp network interface.", AccountStatus::GMImpossible, command_network) || + command_add("npccast", "[targetname/entityid] [spellid] - Causes NPC target to cast spellid on targetname/entityid", AccountStatus::QuestTroupe, command_npccast) || + command_add("npcedit", "[column] [value] - Mega NPC editing command", AccountStatus::GMAdmin, command_npcedit) || + command_add("npceditmass", "[name-search] [column] [value] - Mass (Zone wide) NPC data editing command", AccountStatus::GMAdmin, command_npceditmass) || + command_add("npcemote", "[message] - Make your NPC target emote a message.", AccountStatus::GMLeadAdmin, command_npcemote) || + command_add("npcloot", "- Manipulate the loot an NPC is carrying. Use #npcloot help for more information.", AccountStatus::QuestTroupe, command_npcloot) || + command_add("npcsay", "[message] - Make your NPC target say a message.", AccountStatus::GMLeadAdmin, command_npcsay) || + command_add("npcshout", "[message] - Make your NPC target shout a message.", AccountStatus::GMLeadAdmin, command_npcshout) || + command_add("npcspawn", "[create/add/update/remove/delete] - Manipulate spawn DB", AccountStatus::GMAreas, command_npcspawn) || + command_add("npcspecialattk", "[flagchar] [perm] - Set NPC special attack flags. Flags are E(nrage) F(lurry) R(ampage) S(ummon).", AccountStatus::QuestTroupe, command_npcspecialattk) || + command_add("npcstats", "- Show stats about target NPC", AccountStatus::QuestTroupe, command_npcstats) || + command_add("npctype_cache", "[id] or all - Clears the npc type cache for either the id or all npcs.", AccountStatus::GMImpossible, command_npctype_cache) || + command_add("npctypespawn", "[npctypeid] [factionid] - Spawn an NPC from the db", AccountStatus::Steward, command_npctypespawn) || + command_add("nudge", "- Nudge your target's current position by specific values", AccountStatus::QuestTroupe, command_nudge) || + command_add("nukebuffs", "[Beneficial|Detrimental|Help] - Strip all buffs by type on you or your target (no argument to remove all buffs)", AccountStatus::Guide, command_nukebuffs) || + command_add("nukeitem", "[Item ID] - Removes the specified Item ID from you or your player target's inventory", AccountStatus::GMLeadAdmin, command_nukeitem) || + command_add("object", "List|Add|Edit|Move|Rotate|Copy|Save|Undo|Delete - Manipulate static and tradeskill objects within the zone", AccountStatus::GMAdmin, command_object) || + command_add("oocmute", "[1/0] - Mutes OOC chat", AccountStatus::GMMgmt, command_oocmute) || + command_add("opcode", "- opcode management", AccountStatus::GMImpossible, command_opcode) || + command_add("path", "- view and edit pathing", AccountStatus::GMMgmt, command_path) || + command_add("peekinv", "[equip/gen/cursor/poss/limbo/curlim/trib/bank/shbank/allbank/trade/world/all] - Print out contents of your player target's inventory", AccountStatus::GMAdmin, command_peekinv) || + command_add("peqzone", "[Zone ID|Zone Short Name] - Teleports you to the specified zone if you meet the requirements.", AccountStatus::Player, command_peqzone) || + command_add("permaclass", "[Class ID] - Change your or your player target's class, changed client is disconnected", AccountStatus::QuestTroupe, command_permaclass) || + command_add("permagender", "[Gender ID] - Change your or your player target's gender", AccountStatus::QuestTroupe, command_permagender) || + command_add("permarace", "[Race ID] - Change your or your player target's race", AccountStatus::QuestTroupe, command_permarace) || + command_add("petitems", "- View your pet's items if you have one", AccountStatus::ApprenticeGuide, command_petitems) || + command_add("petitioninfo", "[petition number] - Get info about a petition", AccountStatus::ApprenticeGuide, command_petitioninfo) || + command_add("pf", "- Display additional mob coordinate and wandering data", AccountStatus::Player, command_pf) || + command_add("picklock", "Analog for ldon pick lock for the newer clients since we still don't have it working.", AccountStatus::Player, command_picklock) || + command_add("profanity", "Manage censored language.", AccountStatus::GMLeadAdmin, command_profanity) || + command_add("push", "Lets you do spell push", AccountStatus::GMLeadAdmin, command_push) || + command_add("proximity", "Shows NPC proximity", AccountStatus::GMLeadAdmin, command_proximity) || + command_add("pvp", "[On|Off] - Set you or your player target's PVP status", AccountStatus::GMAdmin, command_pvp) || + command_add("qglobal", "[on/off/view] - Toggles qglobal functionality on an NPC", AccountStatus::GMAdmin, command_qglobal) || + command_add("questerrors", "Shows quest errors.", AccountStatus::GMAdmin, command_questerrors) || + command_add("race", "[racenum] - Change your or your target's race. Use racenum 0 to return to normal", AccountStatus::Guide, command_race) || + command_add("raidloot", "[All|GroupLeader|RaidLeader|Selected] - Sets your Raid Loot Type if you have permission to do so.", AccountStatus::Player, command_raidloot) || + command_add("randomfeatures", "- Temporarily randomizes the Facial Features of your target", AccountStatus::QuestTroupe, command_randomfeatures) || + command_add("refreshgroup", "- Refreshes Group.", AccountStatus::Player, command_refreshgroup) || + command_add("reloadaa", "Reloads AA data", AccountStatus::GMMgmt, command_reloadaa) || + command_add("reloadallrules", "Executes a reload of all rules.", AccountStatus::QuestTroupe, command_reloadallrules) || + command_add("reloadcontentflags", "Executes a reload of all expansion and content flags", AccountStatus::QuestTroupe, command_reloadcontentflags) || + command_add("reloademote", "Reloads NPC Emotes", AccountStatus::QuestTroupe, command_reloademote) || + command_add("reloadlevelmods", nullptr, AccountStatus::Max, command_reloadlevelmods) || + command_add("reloadmerchants", nullptr, AccountStatus::Max, command_reloadmerchants) || + command_add("reloadperlexportsettings", nullptr, AccountStatus::Max, command_reloadperlexportsettings) || + command_add("reloadqst", " - Clear quest cache (any argument causes it to also stop all timers)", AccountStatus::GMLeadAdmin, command_reloadqst) || + command_add("reloadrulesworld", "Executes a reload of all rules in world specifically.", AccountStatus::QuestTroupe, command_reloadworldrules) || + command_add("reloadstatic", "- Reload Static Zone Data", AccountStatus::GMLeadAdmin, command_reloadstatic) || + command_add("reloadtraps", "- Repops all traps in the current zone.", AccountStatus::QuestTroupe, command_reloadtraps) || + command_add("reloadtitles", "- Reload player titles from the database", AccountStatus::GMLeadAdmin, command_reloadtitles) || + command_add("reloadworld", "[0|1] - Clear quest cache (0 - no repop, 1 - repop)", AccountStatus::Max, command_reloadworld) || + command_add("reloadzps", "- Reload zone points from database", AccountStatus::GMLeadAdmin, command_reloadzps) || + command_add("removeitem", "[Item ID] [Amount] - Removes the specified Item ID by Amount from you or your player target's inventory (Amount defaults to 1 if not used)", AccountStatus::GMAdmin, command_removeitem) || + command_add("repop", "[delay] - Repop the zone with optional delay", AccountStatus::GMAdmin, command_repop) || + command_add("resetaa", "- Resets a Player's AA in their profile and refunds spent AA's to unspent, may disconnect player.", AccountStatus::GMMgmt, command_resetaa) || + command_add("resetaa_timer", "Command to reset AA cooldown timers.", AccountStatus::GMMgmt, command_resetaa_timer) || + command_add("resetdisc_timer", "Command to reset all discipline cooldown timers.", AccountStatus::GMMgmt, command_resetdisc_timer) || + command_add("revoke", "[charname] [1/0] - Makes charname unable to talk on OOC", AccountStatus::GMMgmt, command_revoke) || + command_add("roambox", "Manages roambox settings for an NPC", AccountStatus::GMMgmt, command_roambox) || + command_add("rules", "(subcommand) - Manage server rules", AccountStatus::GMImpossible, command_rules) || + command_add("save", "- Force your player or player corpse target to be saved to the database", AccountStatus::Guide, command_save) || + command_add("scale", "- Handles npc scaling", AccountStatus::GMLeadAdmin, command_scale) || + command_add("scribespell", "[spellid] - Scribe specified spell in your target's spell book.", AccountStatus::GMCoder, command_scribespell) || + command_add("scribespells", "[max level] [min level] - Scribe all spells for you or your player target that are usable by them, up to level specified. (may freeze client for a few seconds)", AccountStatus::GMLeadAdmin, command_scribespells) || + command_add("sendzonespawns", "- Refresh spawn list for all clients in zone", AccountStatus::GMLeadAdmin, command_sendzonespawns) || + command_add("sensetrap", "Analog for ldon sense trap for the newer clients since we still don't have it working.", AccountStatus::Player, command_sensetrap) || + command_add("serverinfo", "- Get OS info about server host", AccountStatus::GMMgmt, command_serverinfo) || + command_add("serverrules", "- Read this server's rules", AccountStatus::Player, command_serverrules) || + command_add("setaapts", "[AA|Group|Raid] [AA Amount] - Set your or your player target's Available AA Points by Type", AccountStatus::GMAdmin, command_setaapts) || + command_add("setaaxp", "[AA|Group|Raid] [AA Experience] - Set your or your player target's AA Experience by Type", AccountStatus::GMAdmin, command_setaaxp) || + command_add("setadventurepoints", "[Theme] [Points] - Set your or your player target's available Adventure Points by Theme", AccountStatus::GMLeadAdmin, command_set_adventure_points) || + command_add("setaltcurrency", "[Currency ID] [Amount] - Set your or your target's available Alternate Currency by Currency ID", AccountStatus::GMAdmin, command_setaltcurrency) || + command_add("setanim", "[Animation ID (IDs are 0 to 4)] - Set target's appearance to Animation ID", AccountStatus::GMMgmt, command_setanim) || + command_add("setcrystals", "[value] - Set your or your player target's available radiant or ebon crystals", AccountStatus::GMAdmin, command_setcrystals) || + command_add("setendurance", "[Endurance] - Set your or your target's Endurance", AccountStatus::GMAdmin, command_setendurance) || + command_add("setfaction", "[Faction ID] - Sets targeted NPC's faction in the database", AccountStatus::GMAreas, command_setfaction) || + command_add("setgraveyard", "[zone name] - Creates a graveyard for the specified zone based on your target's LOC.", AccountStatus::GMMgmt, command_setgraveyard) || + command_add("sethp", "[Health] - Set your or your target's Health", AccountStatus::GMAdmin, command_sethp) || + command_add("setlanguage", "[language ID] [value] - Set your target's language skillnum to value", AccountStatus::Guide, command_setlanguage) || + command_add("setlsinfo", "[email] [password] - Set login server email address and password (if supported by login server)", AccountStatus::Steward, command_setlsinfo) || + command_add("setmana", "[Mana] - Set your or your target's Mana", AccountStatus::GMAdmin, command_setmana) || + command_add("setpass", "[accountname] [password] - Set local password for accountname", AccountStatus::GMLeadAdmin, command_setpass) || + command_add("setpvppoints", "[Amount] - Set your or your player target's PVP points", AccountStatus::GMAdmin, command_setpvppoints) || + command_add("setskill", "[skillnum] [value] - Set your target's skill skillnum to value", AccountStatus::Guide, command_setskill) || + command_add("setskillall", "[value] - Set all of your target's skills to value", AccountStatus::Guide, command_setskillall) || + command_add("setstartzone", "[Zone ID|Zone Short Name] - Sets your or your target's starting zone (Use '0' or 'Reset' to allow the player use of /setstartcity)", AccountStatus::QuestTroupe, command_setstartzone) || + command_add("setstat", "- Sets the stats to a specific value.", AccountStatus::Max, command_setstat) || + command_add("setxp", "[value] - Set your or your player target's experience", AccountStatus::GMAdmin, command_setxp) || + command_add("showbonusstats", "[item|spell|all] Shows bonus stats for target from items or spells. Shows both by default.", AccountStatus::Guide, command_showbonusstats) || + command_add("showbuffs", "- List buffs active on your target or you if no target", AccountStatus::Guide, command_showbuffs) || + command_add("shownumhits", "Shows buffs numhits for yourself.", AccountStatus::Player, command_shownumhits) || + command_add("shownpcgloballoot", "Show GlobalLoot entires on this npc", AccountStatus::Guide, command_shownpcgloballoot) || + command_add("showskills", "- Show the values of your or your player target's skills", AccountStatus::Guide, command_showskills) || + command_add("showspellslist", "Shows spell list of targeted NPC", AccountStatus::GMAdmin, command_showspellslist) || + command_add("showstats", "- Show details about you or your target", AccountStatus::Guide, command_showstats) || + command_add("showzonegloballoot", "Show GlobalLoot entires on this zone", AccountStatus::Guide, command_showzonegloballoot) || + command_add("showzonepoints", "Show zone points for current zone", AccountStatus::Guide, command_showzonepoints) || + command_add("shutdown", "- Shut this zone process down", AccountStatus::GMLeadAdmin, command_shutdown) || + command_add("size", "[size] - Change size of you or your target", AccountStatus::Guide, command_size) || + command_add("spawn", "[name] [race] [level] [material] [hp] [gender] [class] [priweapon] [secweapon] [merchantid] - Spawn an NPC", AccountStatus::Steward, command_spawn) || + command_add("spawneditmass", "Mass editing spawn command", AccountStatus::GMLeadAdmin, command_spawneditmass) || + command_add("spawnfix", "- Find targeted NPC in database based on its X/Y/heading and update the database to make it spawn at your current location/heading.", AccountStatus::GMAreas, command_spawnfix) || + command_add("spawnstatus", "- Show respawn timer status", AccountStatus::GMAdmin, command_spawnstatus) || + command_add("spellinfo", "[spellid] - Get detailed info about a spell", AccountStatus::Steward, command_spellinfo) || + command_add("stun", "[duration] - Stuns you or your target for duration", AccountStatus::GMAdmin, command_stun) || + command_add("summon", "[charname] - Summons your player/npc/corpse target, or charname if specified", AccountStatus::QuestTroupe, command_summon) || + command_add("summonburiedplayercorpse", "- Summons the target's oldest buried corpse, if any exist.", AccountStatus::GMAdmin, command_summonburiedplayercorpse) || + command_add("summonitem", "[itemid] [charges] - Summon an item onto your cursor. Charges are optional.", AccountStatus::GMMgmt, command_summonitem) || + command_add("suspend", "[name] [days] [reason] - Suspend by character name and for specificed number of days", AccountStatus::GMLeadAdmin, command_suspend) || + command_add("task", "(subcommand) - Task system commands", AccountStatus::GMLeadAdmin, command_task) || + command_add("tattoo", "- Change the tattoo of your target (Drakkin Only)", AccountStatus::QuestTroupe, command_tattoo) || + command_add("tempname", "[newname] - Temporarily renames your target. Leave name blank to restore the original name.", AccountStatus::GMAdmin, command_tempname) || + command_add("petname", "[newname] - Temporarily renames your pet. Leave name blank to restore the original name.", AccountStatus::GMAdmin, command_petname) || + command_add("texture", "[Texture] [Helmet Texture] - Change your or your target's texture (Helmet Texture defaults to 0 if not used)", AccountStatus::Steward, command_texture) || + command_add("time", "[HH] [MM] - Set EQ time", AccountStatus::EQSupport, command_time) || + command_add("timers", "- Display persistent timers for target", AccountStatus::GMMgmt, command_timers) || + command_add("timezone", "[HH] [MM] - Set timezone. Minutes are optional", AccountStatus::EQSupport, command_timezone) || + command_add("title", "[Remove|Title] [Save (0 = False, 1 = True)] - Set your or your player target's title (use remove to remove title, Save defaults to false if not used)", AccountStatus::Guide, command_title) || + command_add("titlesuffix", "[Remove|Title Suffix] [Save (0 = False, 1 = True)] - Set your or your player target's title suffix (use remove to remove title suffix, Save defaults to false if not used)", AccountStatus::Guide, command_titlesuffix) || + command_add("traindisc", "[level] - Trains all the disciplines usable by the target, up to level specified. (may freeze client for a few seconds)", AccountStatus::GMLeadAdmin, command_traindisc) || + command_add("trapinfo", "- Gets infomation about the traps currently spawned in the zone.", AccountStatus::QuestTroupe, command_trapinfo) || + command_add("tune", "Calculate statistical values related to combat.", AccountStatus::GMAdmin, command_tune) || + command_add("ucs", "- Attempts to reconnect to the UCS server", AccountStatus::Player, command_ucs) || + command_add("undye", "- Remove dye from all of your or your target's armor slots", AccountStatus::GMAdmin, command_undye) || + command_add("undyeme", "- Remove dye from all of your armor slots", AccountStatus::Player, command_undyeme) || + command_add("unfreeze", "- Unfreeze your target", AccountStatus::QuestTroupe, command_unfreeze) || + command_add("unlock", "- Unlock the worldserver", AccountStatus::GMLeadAdmin, command_unlock) || + command_add("unmemspell", "[Spell ID] - Unmemorize a Spell by ID for you or your target", AccountStatus::Guide, command_unmemspell) || + command_add("unmemspells", " - Unmemorize all spells for you or your target", AccountStatus::Guide, command_unmemspells) || + command_add("unscribespell", "[spellid] - Unscribe specified spell from your target's spell book.", AccountStatus::GMCoder, command_unscribespell) || + command_add("unscribespells", "- Clear out your or your player target's spell book.", AccountStatus::GMCoder, command_unscribespells) || + command_add("untraindisc", "[spellid] - Untrain specified discipline from your target.", AccountStatus::GMCoder, command_untraindisc) || + command_add("untraindiscs", "- Untrains all disciplines from your target.", AccountStatus::GMCoder, command_untraindiscs) || + command_add("uptime", "[zone server id] - Get uptime of worldserver, or zone server if argument provided", AccountStatus::Steward, command_uptime) || + command_add("version", "- Display current version of EQEmu server", AccountStatus::Player, command_version) || + command_add("viewcurrencies", "- View your or your target's currencies", AccountStatus::GMAdmin, command_viewcurrencies) || + command_add("viewnpctype", "[NPC ID] - Show stats for an NPC by NPC ID", AccountStatus::GMAdmin, command_viewnpctype) || + command_add("viewpetition", "[petition number] - View a petition", AccountStatus::ApprenticeGuide, command_viewpetition) || + command_add("viewzoneloot", "[item id] - Allows you to search a zone's loot for a specific item ID. (0 shows all loot in the zone)", AccountStatus::QuestTroupe, command_viewzoneloot) || + command_add("wc", "[wear slot] [material] - Sends an OP_WearChange for your target", AccountStatus::GMMgmt, command_wc) || + command_add("weather", "[0/1/2/3] (Off/Rain/Snow/Manual) - Change the weather", AccountStatus::QuestTroupe, command_weather) || + command_add("who", "[search]", AccountStatus::ApprenticeGuide, command_who) || + command_add("worldshutdown", "- Shut down world and all zones", AccountStatus::GMMgmt, command_worldshutdown) || + command_add("wp", "[add|delete] [grid_id] [pause] [waypoint_id] [-h] - Add or delete a waypoint by grid ID. (-h to use current heading)", AccountStatus::GMAreas, command_wp) || + command_add("wpadd", "[pause] [-h] - Add your current location as a waypoint to your NPC target's AI path. (-h to use current heading)", AccountStatus::GMAreas, command_wpadd) || + command_add("wpinfo", "- Show waypoint info about your NPC target", AccountStatus::GMAreas, command_wpinfo) || + command_add("worldwide", "Performs world-wide GM functions such as cast (can be extended for other commands). Use caution", AccountStatus::GMImpossible, command_worldwide) || + command_add("xtargets", "Show your targets Extended Targets and optionally set how many xtargets they can have.", AccountStatus::GMImpossible, command_xtargets) || + command_add("zclip", "[Minimum Clip] [Maximum Clip] [Fog Minimum Clip] [Fog Maximum Clip] [Permanent (0 = False, 1 = True)] - Change zone clipping", AccountStatus::QuestTroupe, command_zclip) || + command_add("zcolor", "[Red] [Green] [Blue] [Permanent (0 = False, 1 = True)] - Change sky color", AccountStatus::QuestTroupe, command_zcolor) || + command_add("zheader", "[Zone ID|Zone Short Name] [Version] - Load a zone header from the database", AccountStatus::QuestTroupe, command_zheader) || + command_add("zone", "[zonename] [x] [y] [z] - Go to specified zone (coords optional)", AccountStatus::Guide, command_zone) || + command_add("zonebootup", "[ZoneServerID] [shortname] - Make a zone server boot a specific zone", AccountStatus::GMLeadAdmin, command_zonebootup) || + command_add("zoneinstance", "[instanceid] [x] [y] [z] - Go to specified instance zone (coords optional)", AccountStatus::Guide, command_zone_instance) || + command_add("zonelock", "[List|Lock|Unlock] [Zone ID|Zone Short Name] - Set or get lock status of a Zone by ID or Short Name", AccountStatus::GMAdmin, command_zonelock) || + command_add("zoneshutdown", "[shortname] - Shut down a zone server", AccountStatus::GMLeadAdmin, command_zoneshutdown) || + command_add("zonestatus", "- Show connected zoneservers, synonymous with /servers", AccountStatus::GMLeadAdmin, command_zonestatus) || + command_add("zopp", "Troubleshooting command - Sends a fake item packet to you. No server reference is created.", AccountStatus::GMImpossible, command_zopp) || + command_add("zsafecoords", "[X] [Y] [Z] [Heading] [Permanent (0 = False, 1 = True)] - Set the current zone's safe coordinates", AccountStatus::QuestTroupe, command_zsafecoords) || + command_add("zsave", " - Saves zheader to the database", AccountStatus::QuestTroupe, command_zsave) || + command_add("zsky", "[Sky Type] [Permanent (0 = False, 1 = True)] - Change zone sky type", AccountStatus::QuestTroupe, command_zsky) || + command_add("zstats", "- Show info about zone header", AccountStatus::QuestTroupe, command_zstats) || + command_add("zunderworld", "[Z] [Permanent (0 = False, 1 = True)] - Change zone underworld Z", AccountStatus::QuestTroupe, command_zunderworld) ) { command_deinit(); return -1; @@ -671,142 +631,6 @@ int command_realdispatch(Client *c, const char *message) } -void command_logcommand(Client *c, const char *message) -{ - int admin=c->Admin(); - - bool continueevents=false; - switch (zone->loglevelvar){ //catch failsafe - case 9: { // log only LeadGM - if ((admin>= 150) && (admin <200)) - continueevents=true; - break; - } - case 8: { // log only GM - if ((admin>= 100) && (admin <150)) - continueevents=true; - break; - } - case 1: { - if ((admin>= 200)) - continueevents=true; - break; - } - case 2: { - if ((admin>= 150)) - continueevents=true; - break; - } - case 3: { - if ((admin>= 100)) - continueevents=true; - break; - } - case 4: { - if ((admin>= 80)) - continueevents=true; - break; - } - case 5: { - if ((admin>= 20)) - continueevents=true; - break; - } - case 6: { - if ((admin>= 10)) - continueevents=true; - break; - } - case 7: { - continueevents=true; - break; - } - } - - if (continueevents) - database.logevents( - c->AccountName(), - c->AccountID(), - admin,c->GetName(), - c->GetTarget()?c->GetTarget()->GetName():"None", - "Command", - message, - 1 - ); -} - - -/* - * commands go below here - */ -void command_worldwide(Client *c, const Seperator *sep) -{ - std::string sub_command; - if (sep->arg[1]) { - sub_command = sep->arg[1]; - } - - if (sub_command == "cast") { - if (sep->arg[2][0] && Seperator::IsNumber(sep->arg[2])) { - int spell_id = atoi(sep->arg[2]); - quest_manager.WorldWideCastSpell(spell_id, 0, 0); - worldserver.SendEmoteMessage(0, 0, 15, fmt::format(" A GM has cast [{}] world-wide!", GetSpellName(spell_id)).c_str()); - } - else { - c->Message(Chat::Yellow, "Usage: #worldwide cast [spellid]"); - } - } - - if (!sep->arg[1]) { - c->Message(Chat::White, "This command is used to perform world-wide tasks"); - c->Message(Chat::White, "Usage: #worldwide cast [spellid]"); - } -} -void command_endurance(Client *c, const Seperator *sep) -{ - Mob *t; - - t = c->GetTarget() ? c->GetTarget() : c; - - if (t->IsClient()) - t->CastToClient()->SetEndurance(t->CastToClient()->GetMaxEndurance()); - else - t->SetEndurance(t->GetMaxEndurance()); - - t->Message(Chat::White, "Your endurance has been refilled."); -} -void command_setstat(Client* c, const Seperator* sep){ - if(sep->arg[1][0] && sep->arg[2][0] && c->GetTarget()!=0 && c->GetTarget()->IsClient()){ - c->GetTarget()->CastToClient()->SetStats(atoi(sep->arg[1]),atoi(sep->arg[2])); - } - else{ - c->Message(Chat::White,"This command is used to permanently increase or decrease a players stats."); - c->Message(Chat::White,"Usage: #setstat {type} {value the stat should be}"); - c->Message(Chat::White,"Types: Str: 0, Sta: 1, Agi: 2, Dex: 3, Int: 4, Wis: 5, Cha: 6"); - } -} - -void command_incstat(Client* c, const Seperator* sep){ - if(sep->arg[1][0] && sep->arg[2][0] && c->GetTarget()!=0 && c->GetTarget()->IsClient()){ - c->GetTarget()->CastToClient()->IncStats(atoi(sep->arg[1]),atoi(sep->arg[2])); - } - else{ - c->Message(Chat::White,"This command is used to permanently increase or decrease a players stats."); - c->Message(Chat::White,"Usage: #setstat {type} {value by which to increase or decrease}"); - c->Message(Chat::White,"Note: The value is in increments of 2, so a value of 3 will actually increase the stat by 6"); - c->Message(Chat::White,"Types: Str: 0, Sta: 1, Agi: 2, Dex: 3, Int: 4, Wis: 5, Cha: 6"); - } -} - -void command_resetaa(Client* c,const Seperator *sep) { - if(c->GetTarget() && c->GetTarget()->IsClient()){ - c->GetTarget()->CastToClient()->ResetAA(); - c->Message(Chat::Red,"Successfully reset %s's AAs", c->GetTarget()->GetName()); - } - else - c->Message(Chat::White,"Usage: Target a client and use #resetaa to reset the AA data in their Profile."); -} - void command_help(Client *c, const Seperator *sep) { int commands_shown=0; @@ -839,351 +663,6 @@ void command_help(Client *c, const Seperator *sep) } -void command_version(Client *c, const Seperator *sep) -{ - c->Message(Chat::White, "Current version information."); - c->Message(Chat::White, " %s", CURRENT_VERSION); - c->Message(Chat::White, " Compiled on: %s at %s", COMPILE_DATE, COMPILE_TIME); - c->Message(Chat::White, " Last modified on: %s", LAST_MODIFIED); -} - -void command_setfaction(Client *c, const Seperator *sep) -{ - if((sep->arg[1][0] == 0 || strcasecmp(sep->arg[1],"*")==0) || ((c->GetTarget()==0) || (c->GetTarget()->IsClient()))) { - c->Message(Chat::White, "Usage: #setfaction [faction number]"); - return; - } - - auto npcTypeID = c->GetTarget()->CastToNPC()->GetNPCTypeID(); - c->Message(Chat::Yellow,"Setting NPC %u to faction %i", npcTypeID, atoi(sep->argplus[1])); - - std::string query = StringFormat("UPDATE npc_types SET npc_faction_id = %i WHERE id = %i", - atoi(sep->argplus[1]), npcTypeID); - content_db.QueryDatabase(query); -} - -void command_serversidename(Client *c, const Seperator *sep) -{ - if(c->GetTarget()) - c->Message(Chat::White, c->GetTarget()->GetName()); - else - c->Message(Chat::White, "Error: no target"); -} - -void command_wc(Client *c, const Seperator *sep) -{ - if (sep->argnum < 2) { - c->Message( - 0, - "Usage: #wc [wear slot] [material] [ [hero_forge_model] [elite_material] [unknown06] [unknown18] ]" - ); - } - else if (c->GetTarget() == nullptr) { - c->Message(Chat::Red, "You must have a target to do a wear change."); - } - else { - uint32 hero_forge_model = 0; - uint32 wearslot = atoi(sep->arg[1]); - - // Hero Forge - if (sep->argnum > 2) { - hero_forge_model = atoi(sep->arg[3]); - - if (hero_forge_model != 0 && hero_forge_model < 1000) { - // Shorthand Hero Forge ID. Otherwise use the value the user entered. - hero_forge_model = (hero_forge_model * 100) + wearslot; - } - } - /* - // Leaving here to add color option to the #wc command eventually - uint32 Color; - if (c->GetTarget()->IsClient()) - Color = c->GetTarget()->GetEquipmentColor(atoi(sep->arg[1])); - else - Color = c->GetTarget()->GetArmorTint(atoi(sep->arg[1])); - */ - c->GetTarget()->SendTextureWC( - wearslot, - atoi(sep->arg[2]), - hero_forge_model, - atoi(sep->arg[4]), - atoi(sep->arg[5]), - atoi(sep->arg[6])); - } -} - -void command_heromodel(Client *c, const Seperator *sep) -{ - if (sep->argnum < 1) { - c->Message(Chat::White, "Usage: #heromodel [hero forge model] [ [slot] ] (example: #heromodel 63)"); - } - else if (c->GetTarget() == nullptr) { - c->Message(Chat::Red, "You must have a target to do a wear change for Hero's Forge Models."); - } - else { - uint32 hero_forge_model = atoi(sep->arg[1]); - - if (sep->argnum > 1) { - uint8 wearslot = (uint8) atoi(sep->arg[2]); - c->GetTarget()->SendTextureWC(wearslot, 0, hero_forge_model, 0, 0, 0); - } - else { - if (hero_forge_model > 0) { - // Conversion to simplify the command arguments - // Hero's Forge model is actually model * 1000 + texture * 100 + wearslot - // Hero's Forge Model slot 7 is actually for Robes, but it still needs to use wearslot 1 in the packet - hero_forge_model *= 100; - - for (uint8 wearslot = 0; wearslot < 7; wearslot++) { - c->GetTarget()->SendTextureWC(wearslot, 0, (hero_forge_model + wearslot), 0, 0, 0); - } - } - else { - c->Message(Chat::Red, "Hero's Forge Model must be greater than 0."); - } - } - } -} - -void command_setanim(Client *c, const Seperator *sep) -{ - if (c->GetTarget() && sep->IsNumber(1)) { - int num = atoi(sep->arg[1]); - if (num < 0 || num >= _eaMaxAppearance) { - c->Message(Chat::White, "Invalid animation number, between 0 and %d", _eaMaxAppearance - 1); - } - c->GetTarget()->SetAppearance(EmuAppearance(num)); - } - else { - c->Message(Chat::White, "Usage: #setanim [animnum]"); - } -} - -void command_serverinfo(Client *c, const Seperator *sep) -{ - auto os = EQ::GetOS(); - auto cpus = EQ::GetCPUs(); - auto pid = EQ::GetPID(); - auto rss = EQ::GetRSS(); - auto uptime = EQ::GetUptime(); - - c->Message(Chat::White, "Operating System Information"); - c->Message(Chat::White, "=================================================="); - c->Message(Chat::White, "System: %s", os.sysname.c_str()); - c->Message(Chat::White, "Release: %s", os.release.c_str()); - c->Message(Chat::White, "Version: %s", os.version.c_str()); - c->Message(Chat::White, "Machine: %s", os.machine.c_str()); - c->Message(Chat::White, "Uptime: %.2f seconds", uptime); - c->Message(Chat::White, "=================================================="); - c->Message(Chat::White, "CPU Information"); - c->Message(Chat::White, "=================================================="); - for (size_t i = 0; i < cpus.size(); ++i) { - auto &cp = cpus[i]; - c->Message(Chat::White, "CPU #%i: %s, Speed: %.2fGhz", i, cp.model.c_str(), cp.speed); - } - c->Message(Chat::White, "=================================================="); - c->Message(Chat::White, "Process Information"); - c->Message(Chat::White, "=================================================="); - c->Message(Chat::White, "PID: %u", pid); - c->Message(Chat::White, "RSS: %.2f MB", rss / 1048576.0); - c->Message(Chat::White, "=================================================="); -} - -void command_getvariable(Client *c, const Seperator *sep) -{ - std::string tmp; - if (database.GetVariable(sep->argplus[1], tmp)) - c->Message(Chat::White, "%s = %s", sep->argplus[1], tmp.c_str()); - else - c->Message(Chat::White, "GetVariable(%s) returned false", sep->argplus[1]); -} - -void command_chat(Client *c, const Seperator *sep) -{ - if (sep->arg[2][0] == 0) - c->Message(Chat::White, "Usage: #chat [channum] [message]"); - else - if (!worldserver.SendChannelMessage(0, 0, (uint8) atoi(sep->arg[1]), 0, 0, 100, sep->argplus[2])) - c->Message(Chat::White, "Error: World server disconnected"); -} - -void command_npcloot(Client *c, const Seperator *sep) -{ - if (c->GetTarget() == 0) - c->Message(Chat::White, "Error: No target"); - // #npcloot show - else if (strcasecmp(sep->arg[1], "show") == 0) - { - if (c->GetTarget()->IsNPC()) - c->GetTarget()->CastToNPC()->QueryLoot(c); - else if (c->GetTarget()->IsCorpse()) - c->GetTarget()->CastToCorpse()->QueryLoot(c); - else - c->Message(Chat::White, "Error: Target's type doesnt have loot"); - } - // These 2 types are *BAD* for the next few commands - else if (c->GetTarget()->IsClient() || c->GetTarget()->IsCorpse()) - c->Message(Chat::White, "Error: Invalid target type, try a NPC =)."); - // #npcloot add - else if (strcasecmp(sep->arg[1], "add") == 0) - { - // #npcloot add item - if (c->GetTarget()->IsNPC() && sep->IsNumber(2)) - { - uint32 item = atoi(sep->arg[2]); - if (database.GetItem(item)) - { - if (sep->arg[3][0] != 0 && sep->IsNumber(3)) - c->GetTarget()->CastToNPC()->AddItem(item, atoi(sep->arg[3]), 0); - else - c->GetTarget()->CastToNPC()->AddItem(item, 1, 0); - c->Message(Chat::White, "Added item(%i) to the %s's loot.", item, c->GetTarget()->GetName()); - } - else - c->Message(Chat::White, "Error: #npcloot add: Item(%i) does not exist!", item); - } - else if (!sep->IsNumber(2)) - c->Message(Chat::White, "Error: #npcloot add: Itemid must be a number."); - else - c->Message(Chat::White, "Error: #npcloot add: This is not a valid target."); - } - // #npcloot remove - else if (strcasecmp(sep->arg[1], "remove") == 0) - { - //#npcloot remove all - if (strcasecmp(sep->arg[2], "all") == 0) - c->Message(Chat::White, "Error: #npcloot remove all: Not yet implemented."); - //#npcloot remove itemid - else - { - if(c->GetTarget()->IsNPC() && sep->IsNumber(2)) - { - uint32 item = atoi(sep->arg[2]); - c->GetTarget()->CastToNPC()->RemoveItem(item); - c->Message(Chat::White, "Removed item(%i) from the %s's loot.", item, c->GetTarget()->GetName()); - } - else if (!sep->IsNumber(2)) - c->Message(Chat::White, "Error: #npcloot remove: Item must be a number."); - else - c->Message(Chat::White, "Error: #npcloot remove: This is not a valid target."); - } - } - // #npcloot money - else if (strcasecmp(sep->arg[1], "money") == 0) - { - if (c->GetTarget()->IsNPC() && sep->IsNumber(2) && sep->IsNumber(3) && sep->IsNumber(4) && sep->IsNumber(5)) - { - if ((atoi(sep->arg[2]) < 34465 && atoi(sep->arg[2]) >= 0) && (atoi(sep->arg[3]) < 34465 && atoi(sep->arg[3]) >= 0) && (atoi(sep->arg[4]) < 34465 && atoi(sep->arg[4]) >= 0) && (atoi(sep->arg[5]) < 34465 && atoi(sep->arg[5]) >= 0)) - { - c->GetTarget()->CastToNPC()->AddCash(atoi(sep->arg[5]), atoi(sep->arg[4]), atoi(sep->arg[3]), atoi(sep->arg[2])); - c->Message(Chat::White, "Set %i Platinum, %i Gold, %i Silver, and %i Copper as %s's money.", atoi(sep->arg[2]), atoi(sep->arg[3]), atoi(sep->arg[4]), atoi(sep->arg[5]), c->GetTarget()->GetName()); - } - else - c->Message(Chat::White, "Error: #npcloot money: Values must be between 0-34465."); - } - else - c->Message(Chat::White, "Usage: #npcloot money platinum gold silver copper"); - } - else - c->Message(Chat::White, "Usage: #npcloot [show/money/add/remove] [itemid/all/money: pp gp sp cp]"); -} - -void command_gm(Client *c, const Seperator *sep) -{ - bool state=atobool(sep->arg[1]); - Client *t=c; - - if(c->GetTarget() && c->GetTarget()->IsClient()) - t=c->GetTarget()->CastToClient(); - - if(sep->arg[1][0] != 0) { - t->SetGM(state); - c->Message(Chat::White, "%s is %s a GM.", t->GetName(), state?"now":"no longer"); - } - else - c->Message(Chat::White, "Usage: #gm [on/off]"); -} - -// there's no need for this, as /summon already takes care of it -// this command is here for reference but it is not added to the -// list above - -//To whoever wrote the above: And what about /kill, /zone, /zoneserver, etc? -//There is a reason for the # commands: so that admins can specifically enable certain -//commands for their users. Some might want users to #summon but not to /kill. Cant do that if they are a GM -void command_summon(Client *c, const Seperator *sep) -{ - Mob *t; - - if(sep->arg[1][0] != 0) // arg specified - { - Client* client = entity_list.GetClientByName(sep->arg[1]); - if (client != 0) // found player in zone - t=client->CastToMob(); - else - { - if (!worldserver.Connected()) - c->Message(Chat::White, "Error: World server disconnected."); - else - { // player is in another zone - //Taking this command out until we test the factor of 8 in ServerOP_ZonePlayer - //c->Message(Chat::White, "Summoning player from another zone not yet implemented."); - //return; - - auto pack = new ServerPacket(ServerOP_ZonePlayer, sizeof(ServerZonePlayer_Struct)); - ServerZonePlayer_Struct* szp = (ServerZonePlayer_Struct*) pack->pBuffer; - strcpy(szp->adminname, c->GetName()); - szp->adminrank = c->Admin(); - szp->ignorerestrictions = 2; - strcpy(szp->name, sep->arg[1]); - strcpy(szp->zone, zone->GetShortName()); - szp->x_pos = c->GetX(); // May need to add a factor of 8 in here.. - szp->y_pos = c->GetY(); - szp->z_pos = c->GetZ(); - szp->instance_id = zone->GetInstanceID(); - worldserver.SendPacket(pack); - safe_delete(pack); - } - return; - } - } - else if(c->GetTarget()) // have target - t=c->GetTarget(); - else - { - /*if(c->Admin() < 150) - c->Message(Chat::White, "You need a NPC/corpse target for this command"); - else*/ - c->Message(Chat::White, "Usage: #summon [charname] Either target or charname is required"); - return; - } - - if(!t) - return; - - if (t->IsNPC()) - { // npc target - c->Message(Chat::White, "Summoning NPC %s to %1.1f, %1.1f, %1.1f", t->GetName(), c->GetX(), c->GetY(), c->GetZ()); - t->CastToNPC()->GMMove(c->GetX(), c->GetY(), c->GetZ(), c->GetHeading()); - t->CastToNPC()->SaveGuardSpot(glm::vec4(0.0f)); - } - else if (t->IsCorpse()) - { // corpse target - c->Message(Chat::White, "Summoning corpse %s to %1.1f, %1.1f, %1.1f", t->GetName(), c->GetX(), c->GetY(), c->GetZ()); - t->CastToCorpse()->GMMove(c->GetX(), c->GetY(), c->GetZ(), c->GetHeading()); - } - else if (t->IsClient()) - { - /*if(c->Admin() < 150) - { - c->Message(Chat::White, "You may not summon a player."); - return; - }*/ - c->Message(Chat::White, "Summoning player %s to %1.1f, %1.1f, %1.1f", t->GetName(), c->GetX(), c->GetY(), c->GetZ()); - t->CastToClient()->MovePC(zone->GetZoneID(), zone->GetInstanceID(), c->GetX(), c->GetY(), c->GetZ(), c->GetHeading(), 2, GMSummon); - } -} - void command_zone(Client *c, const Seperator *sep) { if(c->Admin() < commandZoneToCoords && @@ -1295,1777 +774,6 @@ void command_zone_instance(Client *c, const Seperator *sep) } } -void command_showbuffs(Client *c, const Seperator *sep) -{ - if (c->GetTarget() == 0) - c->CastToMob()->ShowBuffs(c); - else - c->GetTarget()->CastToMob()->ShowBuffs(c); -} - -void command_peqzone(Client *c, const Seperator *sep) -{ - uint32 timeleft = c->GetPTimers().GetRemainingTime(pTimerPeqzoneReuse)/60; - - if(!c->GetPTimers().Expired(&database, pTimerPeqzoneReuse, false)) { - c->Message(Chat::Red,"You must wait %i minute(s) before using this ability again.", timeleft); - return; - } - - if(c->GetHPRatio() < 75) { - c->Message(Chat::White, "You cannot use this command with less than 75 percent health."); - return; - } - - //this isnt perfect, but its better... - if( - c->IsInvisible(c) - || c->IsRooted() - || c->IsStunned() - || c->IsMezzed() - || c->AutoAttackEnabled() - || c->GetInvul() - ) { - c->Message(Chat::White, "You cannot use this command in your current state. Settle down and wait."); - return; - } - - uint16 zoneid = 0; - uint8 destzone = 0; - if (sep->IsNumber(1)) - { - zoneid = atoi(sep->arg[1]); - destzone = content_db.GetPEQZone(zoneid, 0); - if(destzone == 0){ - c->Message(Chat::Red, "You cannot use this command to enter that zone!"); - return; - } - if(zoneid == zone->GetZoneID()) { - c->Message(Chat::Red, "You cannot use this command on the zone you are in!"); - return; - } - } - else if (sep->arg[1][0] == 0 || sep->IsNumber(2) || sep->IsNumber(3) || sep->IsNumber(4) || sep->IsNumber(5)) - { - c->Message(Chat::White, "Usage: #peqzone [zonename]"); - c->Message(Chat::White, "Optional Usage: #peqzone [zoneid]"); - return; - } - else { - zoneid = ZoneID(sep->arg[1]); - destzone = content_db.GetPEQZone(zoneid, 0); - if(zoneid == 0) { - c->Message(Chat::White, "Unable to locate zone '%s'", sep->arg[1]); - return; - } - if(destzone == 0){ - c->Message(Chat::Red, "You cannot use this command to enter that zone!"); - return; - } - if(zoneid == zone->GetZoneID()) { - c->Message(Chat::Red, "You cannot use this command on the zone you are in!"); - return; - } - } - - if(RuleB (Zone, UsePEQZoneDebuffs)){ - c->SpellOnTarget(RuleI(Zone, PEQZoneDebuff1), c); - c->SpellOnTarget(RuleI(Zone, PEQZoneDebuff2), c); - } - - //zone to safe coords - c->GetPTimers().Start(pTimerPeqzoneReuse, RuleI(Zone, PEQZoneReuseTime)); - c->MovePC(zoneid, 0.0f, 0.0f, 0.0f, 0.0f, 0, ZoneToSafeCoords); -} - -void command_movechar(Client *c, const Seperator *sep) -{ - if(sep->arg[1][0]==0 || sep->arg[2][0] == 0) - c->Message(Chat::White, "Usage: #movechar [charactername] [zonename]"); - else if (c->Admin() < commandMovecharToSpecials && strcasecmp(sep->arg[2], "cshome") == 0 || strcasecmp(sep->arg[2], "load") == 0 || strcasecmp(sep->arg[2], "load2") == 0) - c->Message(Chat::White, "Invalid zone name"); - else - { - uint32 tmp = database.GetAccountIDByChar(sep->arg[1]); - if (tmp) - { - if (c->Admin() >= commandMovecharSelfOnly || tmp == c->AccountID()) - if (!database.MoveCharacterToZone((char*) sep->arg[1], ZoneID(sep->arg[2]))) - c->Message(Chat::White, "Character Move Failed!"); - else - c->Message(Chat::White, "Character has been moved."); - else - c->Message(Chat::Red,"You cannot move characters that are not on your account."); - } - else - c->Message(Chat::White, "Character Does Not Exist"); - } -} - -void command_movement(Client *c, const Seperator *sep) -{ - auto &mgr = MobMovementManager::Get(); - - if (sep->arg[1][0] == 0) { - c->Message(Chat::White, "Usage: #movement stats/clearstats/walkto/runto/rotateto/stop/packet"); - return; - } - - if (strcasecmp(sep->arg[1], "stats") == 0) - { - mgr.DumpStats(c); - } - else if (strcasecmp(sep->arg[1], "clearstats") == 0) - { - mgr.ClearStats(); - } - else if (strcasecmp(sep->arg[1], "walkto") == 0) - { - auto target = c->GetTarget(); - if (target == nullptr) { - c->Message(Chat::White, "No target found."); - return; - } - - target->WalkTo(c->GetX(), c->GetY(), c->GetZ()); - } - else if (strcasecmp(sep->arg[1], "runto") == 0) - { - auto target = c->GetTarget(); - if (target == nullptr) { - c->Message(Chat::White, "No target found."); - return; - } - - target->RunTo(c->GetX(), c->GetY(), c->GetZ()); - } - else if (strcasecmp(sep->arg[1], "rotateto") == 0) - { - auto target = c->GetTarget(); - if (target == nullptr) { - c->Message(Chat::White, "No target found."); - return; - } - - target->RotateToWalking(target->CalculateHeadingToTarget(c->GetX(), c->GetY())); - } - else if (strcasecmp(sep->arg[1], "stop") == 0) - { - auto target = c->GetTarget(); - if (target == nullptr) { - c->Message(Chat::White, "No target found."); - return; - } - - target->StopNavigation(); - } - else if (strcasecmp(sep->arg[1], "packet") == 0) - { - auto target = c->GetTarget(); - if (target == nullptr) { - c->Message(Chat::White, "No target found."); - return; - } - - mgr.SendCommandToClients(target, atof(sep->arg[2]), atof(sep->arg[3]), atof(sep->arg[4]), atof(sep->arg[5]), atoi(sep->arg[6]), ClientRangeAny); - } - else { - c->Message(Chat::White, "Usage: #movement stats/clearstats/walkto/runto/rotateto/stop/packet"); - } -} - -void command_viewpetition(Client *c, const Seperator *sep) -{ - if (sep->arg[1][0] == 0) { - c->Message(Chat::White, "Usage: #viewpetition (petition number) Type #listpetition for a list"); - return; - } - - c->Message(Chat::Red," ID : Character Name , Petition Text"); - - std::string query = "SELECT petid, charname, petitiontext FROM petitions ORDER BY petid"; - auto results = database.QueryDatabase(query); - if (!results.Success()) - return; - - LogInfo("View petition request from [{}], petition number: [{}]", c->GetName(), atoi(sep->argplus[1]) ); - - if (results.RowCount() == 0) { - c->Message(Chat::Red,"There was an error in your request: ID not found! Please check the Id and try again."); - return; - } - - for (auto row = results.begin(); row != results.end(); ++row) - if (strcasecmp(row[0], sep->argplus[1]) == 0) - c->Message(Chat::Yellow, " %s: %s , %s ", row[0], row[1], row[2]); - -} - -void command_petitioninfo(Client *c, const Seperator *sep) -{ - if (sep->arg[1][0] == 0) { - c->Message(Chat::White, "Usage: #petitioninfo (petition number) Type #listpetition for a list"); - return; - } - - std::string query = "SELECT petid, charname, accountname, zone, charclass, charrace, charlevel FROM petitions ORDER BY petid"; - auto results = database.QueryDatabase(query); - if (!results.Success()) - return; - - LogInfo("Petition information request from [{}], petition number:", c->GetName(), atoi(sep->argplus[1]) ); - - if (results.RowCount() == 0) { - c->Message(Chat::Red,"There was an error in your request: ID not found! Please check the Id and try again."); - return; - } - - for (auto row = results.begin(); row != results.end(); ++row) - if (strcasecmp(row[0],sep->argplus[1])== 0) - c->Message(Chat::Red," ID : %s Character Name: %s Account Name: %s Zone: %s Character Class: %s Character Race: %s Character Level: %s", row[0],row[1],row[2],row[3],row[4],row[5],row[6]); - -} - -void command_delpetition(Client *c, const Seperator *sep) -{ - if (sep->arg[1][0] == 0 || strcasecmp(sep->arg[1],"*") == 0) { - c->Message(Chat::White, "Usage: #delpetition (petition number) Type #listpetition for a list"); - return; - } - - c->Message(Chat::Red,"Attempting to delete petition number: %i", atoi(sep->argplus[1])); - std::string query = StringFormat("DELETE FROM petitions WHERE petid = %i", atoi(sep->argplus[1])); - auto results = database.QueryDatabase(query); - if (!results.Success()) - return; - - LogInfo("Delete petition request from [{}], petition number:", c->GetName(), atoi(sep->argplus[1]) ); - -} - -void command_listnpcs(Client *c, const Seperator *sep) -{ - c->Message(Chat::White, "Deprecated, use the #list command (#list npcs )"); -} - -void command_list(Client *c, const Seperator *sep) -{ - std::string search_type; - if (strcasecmp(sep->arg[1], "npcs") == 0) { - search_type = "npcs"; - } - - if (strcasecmp(sep->arg[1], "players") == 0) { - search_type = "players"; - } - - if (strcasecmp(sep->arg[1], "corpses") == 0) { - search_type = "corpses"; - } - - if (strcasecmp(sep->arg[1], "doors") == 0) { - search_type = "doors"; - } - - if (strcasecmp(sep->arg[1], "objects") == 0) { - search_type = "objects"; - } - - if (search_type.length() > 0) { - - int entity_count = 0; - int found_count = 0; - - std::string search_string; - - if (sep->arg[2]) { - search_string = sep->arg[2]; - } - - /** - * NPC - */ - if (search_type.find("npcs") != std::string::npos) { - auto &entity_list_search = entity_list.GetMobList(); - - for (auto &itr : entity_list_search) { - Mob *entity = itr.second; - if (!entity->IsNPC()) { - continue; - } - - entity_count++; - - std::string entity_name = entity->GetName(); - - /** - * Filter by name - */ - if (search_string.length() > 0 && entity_name.find(search_string) == std::string::npos) { - continue; - } - - std::string saylink = StringFormat( - "#goto %.0f %0.f %.0f", - entity->GetX(), - entity->GetY(), - entity->GetZ()); - - c->Message( - 0, - "| %s | ID %5d | %s | x %.0f | y %0.f | z %.0f", - EQ::SayLinkEngine::GenerateQuestSaylink(saylink, false, "Goto").c_str(), - entity->GetID(), - entity->GetName(), - entity->GetX(), - entity->GetY(), - entity->GetZ() - ); - - found_count++; - } - } - - /** - * Client - */ - if (search_type.find("players") != std::string::npos) { - auto &entity_list_search = entity_list.GetClientList(); - - for (auto &itr : entity_list_search) { - Client *entity = itr.second; - - entity_count++; - - std::string entity_name = entity->GetName(); - - /** - * Filter by name - */ - if (search_string.length() > 0 && entity_name.find(search_string) == std::string::npos) { - continue; - } - - std::string saylink = StringFormat( - "#goto %.0f %0.f %.0f", - entity->GetX(), - entity->GetY(), - entity->GetZ()); - - c->Message( - 0, - "| %s | ID %5d | %s | x %.0f | y %0.f | z %.0f", - EQ::SayLinkEngine::GenerateQuestSaylink(saylink, false, "Goto").c_str(), - entity->GetID(), - entity->GetName(), - entity->GetX(), - entity->GetY(), - entity->GetZ() - ); - - found_count++; - } - } - - /** - * Corpse - */ - if (search_type.find("corpses") != std::string::npos) { - auto &entity_list_search = entity_list.GetCorpseList(); - - for (auto &itr : entity_list_search) { - Corpse *entity = itr.second; - - entity_count++; - - std::string entity_name = entity->GetName(); - - /** - * Filter by name - */ - if (search_string.length() > 0 && entity_name.find(search_string) == std::string::npos) { - continue; - } - - std::string saylink = StringFormat( - "#goto %.0f %0.f %.0f", - entity->GetX(), - entity->GetY(), - entity->GetZ()); - - c->Message( - 0, - "| %s | ID %5d | %s | x %.0f | y %0.f | z %.0f", - EQ::SayLinkEngine::GenerateQuestSaylink(saylink, false, "Goto").c_str(), - entity->GetID(), - entity->GetName(), - entity->GetX(), - entity->GetY(), - entity->GetZ() - ); - - found_count++; - } - } - - /** - * Doors - */ - if (search_type.find("doors") != std::string::npos) { - auto &entity_list_search = entity_list.GetDoorsList(); - - for (auto &itr : entity_list_search) { - Doors * entity = itr.second; - - entity_count++; - - std::string entity_name = entity->GetDoorName(); - - /** - * Filter by name - */ - if (search_string.length() > 0 && entity_name.find(search_string) == std::string::npos) { - continue; - } - - std::string saylink = StringFormat( - "#goto %.0f %0.f %.0f", - entity->GetX(), - entity->GetY(), - entity->GetZ()); - - c->Message( - 0, - "| %s | Entity ID %5d | Door ID %i | %s | x %.0f | y %0.f | z %.0f", - EQ::SayLinkEngine::GenerateQuestSaylink(saylink, false, "Goto").c_str(), - entity->GetID(), - entity->GetDoorID(), - entity->GetDoorName(), - entity->GetX(), - entity->GetY(), - entity->GetZ() - ); - - found_count++; - } - } - - /** - * Objects - */ - if (search_type.find("objects") != std::string::npos) { - auto &entity_list_search = entity_list.GetObjectList(); - - for (auto &itr : entity_list_search) { - Object * entity = itr.second; - - entity_count++; - - std::string entity_name = entity->GetModelName(); - - /** - * Filter by name - */ - if (search_string.length() > 0 && entity_name.find(search_string) == std::string::npos) { - continue; - } - - std::string saylink = StringFormat( - "#goto %.0f %0.f %.0f", - entity->GetX(), - entity->GetY(), - entity->GetZ()); - - c->Message( - 0, - "| %s | Entity ID %5d | Object DBID %i | %s | x %.0f | y %0.f | z %.0f", - EQ::SayLinkEngine::GenerateQuestSaylink(saylink, false, "Goto").c_str(), - entity->GetID(), - entity->GetDBID(), - entity->GetModelName(), - entity->GetX(), - entity->GetY(), - entity->GetZ() - ); - - found_count++; - } - } - - if (found_count) { - c->Message( - 0, "Found (%i) of type (%s) in zone (%i) total", - found_count, - search_type.c_str(), - entity_count - ); - } - } - else { - c->Message(Chat::White, "Usage of #list"); - c->Message(Chat::White, "- #list [npcs|players|corpses|doors|objects] [search]"); - c->Message(Chat::White, "- Example: #list npcs (Blank for all)"); - } -} - -void command_date(Client *c, const Seperator *sep) -{ - //yyyy mm dd hh mm local - if(sep->arg[3][0]==0 || !sep->IsNumber(1) || !sep->IsNumber(2) || !sep->IsNumber(3)) { - c->Message(Chat::Red, "Usage: #date yyyy mm dd [HH MM]"); - } - else { - int h=0, m=0; - TimeOfDay_Struct eqTime; - zone->zone_time.GetCurrentEQTimeOfDay( time(0), &eqTime); - if(!sep->IsNumber(4)) - h=eqTime.hour; - else - h=atoi(sep->arg[4]); - if(!sep->IsNumber(5)) - m=eqTime.minute; - else - m=atoi(sep->arg[5]); - c->Message(Chat::Red, "Setting world time to %s-%s-%s %i:%i...", sep->arg[1], sep->arg[2], sep->arg[3], h, m); - zone->SetDate(atoi(sep->arg[1]), atoi(sep->arg[2]), atoi(sep->arg[3]), h, m); - } -} - -void command_timezone(Client *c, const Seperator *sep) -{ - if(sep->arg[1][0]==0 && !sep->IsNumber(1)) { - c->Message(Chat::Red, "Usage: #timezone HH [MM]"); - c->Message(Chat::Red, "Current timezone is: %ih %im", zone->zone_time.getEQTimeZoneHr(), zone->zone_time.getEQTimeZoneMin()); - } - else { - uint8 hours = atoi(sep->arg[1]); - uint8 minutes = atoi(sep->arg[2]); - if(!sep->IsNumber(2)) - minutes = 0; - c->Message(Chat::Red, "Setting timezone to %i h %i m", hours, minutes); - uint32 ntz=(hours*60)+minutes; - zone->zone_time.setEQTimeZone(ntz); - content_db.SetZoneTZ(zone->GetZoneID(), zone->GetInstanceVersion(), ntz); - - // Update all clients with new TZ. - auto outapp = new EQApplicationPacket(OP_TimeOfDay, sizeof(TimeOfDay_Struct)); - TimeOfDay_Struct* tod = (TimeOfDay_Struct*)outapp->pBuffer; - zone->zone_time.GetCurrentEQTimeOfDay(time(0), tod); - entity_list.QueueClients(c, outapp); - safe_delete(outapp); - } -} - -void command_invul(Client *c, const Seperator *sep) -{ - bool state=atobool(sep->arg[1]); - Client *t=c; - - if(c->GetTarget() && c->GetTarget()->IsClient()) - t=c->GetTarget()->CastToClient(); - - if(sep->arg[1][0] != 0) { - t->SetInvul(state); - c->Message(Chat::White, "%s is %s invulnerable from attack.", t->GetName(), state?"now":"no longer"); - } - else - c->Message(Chat::White, "Usage: #invulnerable [on/off]"); -} - -void command_hideme(Client *c, const Seperator *sep) -{ - bool state=atobool(sep->arg[1]); - - if(sep->arg[1][0]==0) - c->Message(Chat::White, "Usage: #hideme [on/off]"); - else - { - c->SetHideMe(state); - c->MessageString(Chat::Broadcasts, c->GetHideMe() ? NOW_INVISIBLE : NOW_VISIBLE, c->GetName()); - } -} - -void command_emote(Client *c, const Seperator *sep) -{ - if (sep->arg[3][0] == 0) - c->Message(Chat::White, "Usage: #emote [name | world | zone] type# message"); - else { - if (strcasecmp(sep->arg[1], "zone") == 0){ - char* newmessage=0; - if(strstr(sep->arg[3],"^")==0) - entity_list.Message(0, atoi(sep->arg[2]), sep->argplus[3]); - else{ - for(newmessage = strtok((char*)sep->arg[3],"^");newmessage!=nullptr;newmessage=strtok(nullptr, "^")) - entity_list.Message(0, atoi(sep->arg[2]), newmessage); - } - } - else if (!worldserver.Connected()) - c->Message(Chat::White, "Error: World server disconnected"); - else if (strcasecmp(sep->arg[1], "world") == 0) - worldserver.SendEmoteMessage(0, 0, atoi(sep->arg[2]), sep->argplus[3]); - else - worldserver.SendEmoteMessage(sep->arg[1], 0, atoi(sep->arg[2]), sep->argplus[3]); - } -} - -void command_fov(Client *c, const Seperator *sep) -{ - if(c->GetTarget()) - if(c->BehindMob(c->GetTarget(), c->GetX(), c->GetY())) - c->Message(Chat::White, "You are behind mob %s, it is looking to %d", c->GetTarget()->GetName(), c->GetTarget()->GetHeading()); - else - c->Message(Chat::White, "You are NOT behind mob %s, it is looking to %d", c->GetTarget()->GetName(), c->GetTarget()->GetHeading()); - else - c->Message(Chat::White, "I Need a target!"); -} - -void command_npcstats(Client *c, const Seperator *sep) -{ - if (c->GetTarget() == 0) - c->Message(Chat::White, "ERROR: No target!"); - else if (!c->GetTarget()->IsNPC()) - c->Message(Chat::White, "ERROR: Target is not a NPC!"); - else { - auto target_npc = c->GetTarget()->CastToNPC(); - c->Message(Chat::White, "# NPC Stats"); - c->Message(Chat::White, "- Name: %s NpcID: %u", target_npc->GetName(), target_npc->GetNPCTypeID()); - c->Message(Chat::White, "- Race: %i Level: %i Class: %i Material: %i", target_npc->GetRace(), target_npc->GetLevel(), target_npc->GetClass(), target_npc->GetTexture()); - c->Message(Chat::White, "- Current HP: %i Max HP: %i", target_npc->GetHP(), target_npc->GetMaxHP()); - //c->Message(Chat::White, "Weapon Item Number: %s", target_npc->GetWeapNo()); - c->Message(Chat::White, "- Gender: %i Size: %f Bodytype: %d", target_npc->GetGender(), target_npc->GetSize(), target_npc->GetBodyType()); - c->Message(Chat::White, "- Runspeed: %.3f Walkspeed: %.3f", static_cast(0.025f * target_npc->GetRunspeed()), static_cast(0.025f * target_npc->GetWalkspeed())); - c->Message(Chat::White, "- Spawn Group: %i Grid: %i", target_npc->GetSpawnGroupId(), target_npc->GetGrid()); - if (target_npc->proximity) { - c->Message(Chat::White, "- Proximity: Enabled"); - c->Message(Chat::White, "-- Cur_X: %1.3f, Cur_Y: %1.3f, Cur_Z: %1.3f", target_npc->GetX(), target_npc->GetY(), target_npc->GetZ()); - c->Message(Chat::White, "-- Min_X: %1.3f(%1.3f), Max_X: %1.3f(%1.3f), X_Range: %1.3f", target_npc->proximity->min_x, (target_npc->proximity->min_x - target_npc->GetX()), target_npc->proximity->max_x, (target_npc->proximity->max_x - target_npc->GetX()), (target_npc->proximity->max_x - target_npc->proximity->min_x)); - c->Message(Chat::White, "-- Min_Y: %1.3f(%1.3f), Max_Y: %1.3f(%1.3f), Y_Range: %1.3f", target_npc->proximity->min_y, (target_npc->proximity->min_y - target_npc->GetY()), target_npc->proximity->max_y, (target_npc->proximity->max_y - target_npc->GetY()), (target_npc->proximity->max_y - target_npc->proximity->min_y)); - c->Message(Chat::White, "-- Min_Z: %1.3f(%1.3f), Max_Z: %1.3f(%1.3f), Z_Range: %1.3f", target_npc->proximity->min_z, (target_npc->proximity->min_z - target_npc->GetZ()), target_npc->proximity->max_z, (target_npc->proximity->max_z - target_npc->GetZ()), (target_npc->proximity->max_z - target_npc->proximity->min_z)); - c->Message(Chat::White, "-- Say: %s", (target_npc->proximity->say ? "Enabled" : "Disabled")); - } - else { - c->Message(Chat::White, "-Proximity: Disabled"); - } - c->Message(Chat::White, ""); - c->Message(Chat::White, "EmoteID: %i", target_npc->GetEmoteID()); - target_npc->QueryLoot(c); - } -} - -void command_zclip(Client *c, const Seperator *sep) -{ - // modifys and resends zhdr packet - if(sep->arg[2][0]==0) - c->Message(Chat::White, "Usage: #zclip "); - else if(atoi(sep->arg[1])<=0) - c->Message(Chat::White, "ERROR: Min clip can not be zero or less!"); - else if(atoi(sep->arg[2])<=0) - c->Message(Chat::White, "ERROR: Max clip can not be zero or less!"); - else if(atoi(sep->arg[1])>atoi(sep->arg[2])) - c->Message(Chat::White, "ERROR: Min clip is greater than max clip!"); - else { - zone->newzone_data.minclip = atof(sep->arg[1]); - zone->newzone_data.maxclip = atof(sep->arg[2]); - if(sep->arg[3][0]!=0) - zone->newzone_data.fog_minclip[0]=atof(sep->arg[3]); - if(sep->arg[4][0]!=0) - zone->newzone_data.fog_minclip[1]=atof(sep->arg[4]); - if(sep->arg[5][0]!=0) - zone->newzone_data.fog_maxclip[0]=atof(sep->arg[5]); - if(sep->arg[6][0]!=0) - zone->newzone_data.fog_maxclip[1]=atof(sep->arg[6]); - auto outapp = new EQApplicationPacket(OP_NewZone, sizeof(NewZone_Struct)); - memcpy(outapp->pBuffer, &zone->newzone_data, outapp->size); - entity_list.QueueClients(c, outapp); - safe_delete(outapp); - } -} - -void command_npccast(Client *c, const Seperator *sep) -{ - if (c->GetTarget() && c->GetTarget()->IsNPC() && !sep->IsNumber(1) && sep->arg[1] != 0 && sep->IsNumber(2)) { - Mob* spelltar = entity_list.GetMob(sep->arg[1]); - if (spelltar) - c->GetTarget()->CastSpell(atoi(sep->arg[2]), spelltar->GetID()); - else - c->Message(Chat::White, "Error: %s not found", sep->arg[1]); - } - else if (c->GetTarget() && c->GetTarget()->IsNPC() && sep->IsNumber(1) && sep->IsNumber(2) ) { - Mob* spelltar = entity_list.GetMob(atoi(sep->arg[1])); - if (spelltar) - c->GetTarget()->CastSpell(atoi(sep->arg[2]), spelltar->GetID()); - else - c->Message(Chat::White, "Error: target ID %i not found", atoi(sep->arg[1])); - } - else - c->Message(Chat::White, "Usage: (needs NPC targeted) #npccast targetname/entityid spellid"); -} - -void command_zstats(Client *c, const Seperator *sep) -{ - c->Message(Chat::White, "Zone Header Data:"); - c->Message(Chat::White, "Sky Type: %i", zone->newzone_data.sky); - c->Message(Chat::White, "Fog Colour: Red: %i; Blue: %i; Green %i", zone->newzone_data.fog_red[0], zone->newzone_data.fog_green[0], zone->newzone_data.fog_blue[0]); - c->Message(Chat::White, "Safe Coords: %f, %f, %f", zone->newzone_data.safe_x, zone->newzone_data.safe_y, zone->newzone_data.safe_z); - c->Message(Chat::White, "Underworld Coords: %f", zone->newzone_data.underworld); - c->Message(Chat::White, "Clip Plane: %f - %f", zone->newzone_data.minclip, zone->newzone_data.maxclip); -} - -void command_permaclass(Client *c, const Seperator *sep) -{ - Client *t=c; - - if(c->GetTarget() && c->GetTarget()->IsClient()) - t=c->GetTarget()->CastToClient(); - - if(sep->arg[1][0]==0) { - c->Message(Chat::White,"Usage: #permaclass "); - } - else if(!t->IsClient()) - c->Message(Chat::White,"Target is not a client."); - else { - c->Message(Chat::White, "Setting %s's class...Sending to char select.", t->GetName()); - LogInfo("Class change request from [{}] for [{}], requested class:[{}]", c->GetName(), t->GetName(), atoi(sep->arg[1]) ); - t->SetBaseClass(atoi(sep->arg[1])); - t->Save(); - t->Kick("Class was changed."); - } -} - -void command_permarace(Client *c, const Seperator *sep) -{ - Client *t=c; - - if(c->GetTarget() && c->GetTarget()->IsClient()) - t=c->GetTarget()->CastToClient(); - - if(sep->arg[1][0]==0) { - c->Message(Chat::White,"Usage: #permarace "); - c->Message(Chat::White,"NOTE: Not all models are global. If a model is not global, it will appear as a human on character select and in zones without the model."); - } - else if(!t->IsClient()) - c->Message(Chat::White,"Target is not a client."); - else { - c->Message(Chat::White, "Setting %s's race - zone to take effect", t->GetName()); - LogInfo("Permanant race change request from [{}] for [{}], requested race:[{}]", c->GetName(), t->GetName(), atoi(sep->arg[1]) ); - uint32 tmp = Mob::GetDefaultGender(atoi(sep->arg[1]), t->GetBaseGender()); - t->SetBaseRace(atoi(sep->arg[1])); - t->SetBaseGender(tmp); - t->Save(); - t->SendIllusionPacket(atoi(sep->arg[1])); - } -} - -void command_permagender(Client *c, const Seperator *sep) -{ - Client *t=c; - - if(c->GetTarget() && c->GetTarget()->IsClient()) - t=c->GetTarget()->CastToClient(); - - if(sep->arg[1][0]==0) { - c->Message(Chat::White,"Usage: #permagender "); - c->Message(Chat::White,"Gender Numbers: 0=Male, 1=Female, 2=Neuter"); - } - else if(!t->IsClient()) - c->Message(Chat::White,"Target is not a client."); - else { - c->Message(Chat::White, "Setting %s's gender - zone to take effect", t->GetName()); - LogInfo("Permanant gender change request from [{}] for [{}], requested gender:[{}]", c->GetName(), t->GetName(), atoi(sep->arg[1]) ); - t->SetBaseGender(atoi(sep->arg[1])); - t->Save(); - t->SendIllusionPacket(atoi(sep->arg[1])); - } -} - -void command_weather(Client *c, const Seperator *sep) -{ - if (!(sep->arg[1][0] == '0' || sep->arg[1][0] == '1' || sep->arg[1][0] == '2' || sep->arg[1][0] == '3')) { - c->Message(Chat::White, "Usage: #weather <0/1/2/3> - Off/Rain/Snow/Manual."); - } - else if(zone->zone_weather == 0) { - if(sep->arg[1][0] == '3') { // Put in modifications here because it had a very good chance at screwing up the client's weather system if rain was sent during snow -T7 - if(sep->arg[2][0] != 0 && sep->arg[3][0] != 0) { - c->Message(Chat::White, "Sending weather packet... TYPE=%s, INTENSITY=%s", sep->arg[2], sep->arg[3]); - zone->zone_weather = atoi(sep->arg[2]); - auto outapp = new EQApplicationPacket(OP_Weather, 8); - outapp->pBuffer[0] = atoi(sep->arg[2]); - outapp->pBuffer[4] = atoi(sep->arg[3]); // This number changes in the packets, intensity? - entity_list.QueueClients(c, outapp); - safe_delete(outapp); - } - else { - c->Message(Chat::White, "Manual Usage: #weather 3 "); - } - } - else if(sep->arg[1][0] == '2') { - entity_list.Message(0, 0, "Snowflakes begin to fall from the sky."); - zone->zone_weather = 2; - auto outapp = new EQApplicationPacket(OP_Weather, 8); - outapp->pBuffer[0] = 0x01; - outapp->pBuffer[4] = 0x02; // This number changes in the packets, intensity? - entity_list.QueueClients(c, outapp); - safe_delete(outapp); - } - else if(sep->arg[1][0] == '1') { - entity_list.Message(0, 0, "Raindrops begin to fall from the sky."); - zone->zone_weather = 1; - auto outapp = new EQApplicationPacket(OP_Weather, 8); - outapp->pBuffer[4] = 0x01; // This is how it's done in Fear, and you can see a decent distance with it at this value - entity_list.QueueClients(c, outapp); - safe_delete(outapp); - } - } - else { - if(zone->zone_weather == 1) { // Doing this because if you have rain/snow on, you can only turn one off. - entity_list.Message(0, 0, "The sky clears as the rain ceases to fall."); - zone->zone_weather = 0; - auto outapp = new EQApplicationPacket(OP_Weather, 8); - // To shutoff weather you send an empty 8 byte packet (You get this everytime you zone even if the sky is clear) - entity_list.QueueClients(c, outapp); - safe_delete(outapp); - } - else if(zone->zone_weather == 2) { - entity_list.Message(0, 0, "The sky clears as the snow stops falling."); - zone->zone_weather = 0; - auto outapp = new EQApplicationPacket(OP_Weather, 8); - // To shutoff weather you send an empty 8 byte packet (You get this everytime you zone even if the sky is clear) - outapp->pBuffer[0] = 0x01; // Snow has it's own shutoff packet - entity_list.QueueClients(c, outapp); - safe_delete(outapp); - } - else { - entity_list.Message(0, 0, "The sky clears."); - zone->zone_weather = 0; - auto outapp = new EQApplicationPacket(OP_Weather, 8); - // To shutoff weather you send an empty 8 byte packet (You get this everytime you zone even if the sky is clear) - entity_list.QueueClients(c, outapp); - safe_delete(outapp); - } - } -} - -void command_zheader(Client *c, const Seperator *sep) -{ - // sends zhdr packet - if(sep->arg[1][0]==0) { - c->Message(Chat::White, "Usage: #zheader "); - } - else if(ZoneID(sep->argplus[1])==0) - c->Message(Chat::White, "Invalid Zone Name: %s", sep->argplus[1]); - else { - - if (zone->LoadZoneCFG(sep->argplus[1], 0)) - c->Message(Chat::White, "Successfully loaded zone header for %s from database.", sep->argplus[1]); - else - c->Message(Chat::White, "Failed to load zone header %s from database", sep->argplus[1]); - auto outapp = new EQApplicationPacket(OP_NewZone, sizeof(NewZone_Struct)); - memcpy(outapp->pBuffer, &zone->newzone_data, outapp->size); - entity_list.QueueClients(c, outapp); - safe_delete(outapp); - } -} - -void command_zsky(Client *c, const Seperator *sep) -{ - // modifys and resends zhdr packet - if(sep->arg[1][0]==0) - c->Message(Chat::White, "Usage: #zsky "); - else if(atoi(sep->arg[1])<0||atoi(sep->arg[1])>255) - c->Message(Chat::White, "ERROR: Sky type can not be less than 0 or greater than 255!"); - else { - zone->newzone_data.sky = atoi(sep->arg[1]); - auto outapp = new EQApplicationPacket(OP_NewZone, sizeof(NewZone_Struct)); - memcpy(outapp->pBuffer, &zone->newzone_data, outapp->size); - entity_list.QueueClients(c, outapp); - safe_delete(outapp); - } -} - -void command_zcolor(Client *c, const Seperator *sep) -{ - // modifys and resends zhdr packet - if (sep->arg[3][0]==0) - c->Message(Chat::White, "Usage: #zcolor "); - else if (atoi(sep->arg[1])<0||atoi(sep->arg[1])>255) - c->Message(Chat::White, "ERROR: Red can not be less than 0 or greater than 255!"); - else if (atoi(sep->arg[2])<0||atoi(sep->arg[2])>255) - c->Message(Chat::White, "ERROR: Green can not be less than 0 or greater than 255!"); - else if (atoi(sep->arg[3])<0||atoi(sep->arg[3])>255) - c->Message(Chat::White, "ERROR: Blue can not be less than 0 or greater than 255!"); - else { - for (int z=0; z<4; z++) { - zone->newzone_data.fog_red[z] = atoi(sep->arg[1]); - zone->newzone_data.fog_green[z] = atoi(sep->arg[2]); - zone->newzone_data.fog_blue[z] = atoi(sep->arg[3]); - } - auto outapp = new EQApplicationPacket(OP_NewZone, sizeof(NewZone_Struct)); - memcpy(outapp->pBuffer, &zone->newzone_data, outapp->size); - entity_list.QueueClients(c, outapp); - safe_delete(outapp); - } -} - -void command_spon(Client *c, const Seperator *sep) -{ - c->MemorizeSpell(0, SPELLBAR_UNLOCK, memSpellSpellbar); -} - -void command_spoff(Client *c, const Seperator *sep) -{ - auto outapp = new EQApplicationPacket(OP_ManaChange, 0); - outapp->priority = 5; - c->QueuePacket(outapp); - safe_delete(outapp); -} - -void command_gassign(Client *c, const Seperator *sep) -{ - if (sep->IsNumber(1) && c->GetTarget() && c->GetTarget()->IsNPC() && c->GetTarget()->CastToNPC()->GetSpawnPointID() > 0) { - int spawn2id = c->GetTarget()->CastToNPC()->GetSpawnPointID(); - database.AssignGrid(c, atoi(sep->arg[1]), spawn2id); - } - else - c->Message(Chat::White, "Usage: #gassign [num] - must have an npc target!"); -} - -void command_ai(Client *c, const Seperator *sep) -{ - Mob *target=c->GetTarget(); - - if (strcasecmp(sep->arg[1], "factionid") == 0) { - if (target && sep->IsNumber(2)) { - if (target->IsNPC()) - target->CastToNPC()->SetNPCFactionID(atoi(sep->arg[2])); - else - c->Message(Chat::White, "%s is not an NPC.", target->GetName()); - } - else - c->Message(Chat::White, "Usage: (targeted) #ai factionid [factionid]"); - } - else if (strcasecmp(sep->arg[1], "spellslist") == 0) { - if (target && sep->IsNumber(2) && atoi(sep->arg[2]) >= 0) { - if (target->IsNPC()) - target->CastToNPC()->AI_AddNPCSpells(atoi(sep->arg[2])); - else - c->Message(Chat::White, "%s is not an NPC.", target->GetName()); - } - else - c->Message(Chat::White, "Usage: (targeted) #ai spellslist [npc_spells_id]"); - } - else if (strcasecmp(sep->arg[1], "con") == 0) { - if (target && sep->arg[2][0] != 0) { - Mob* tar2 = entity_list.GetMob(sep->arg[2]); - if (tar2) - c->Message(Chat::White, "%s considering %s: %i", target->GetName(), tar2->GetName(), tar2->GetReverseFactionCon(target)); - else - c->Message(Chat::White, "Error: %s not found.", sep->arg[2]); - } - else - c->Message(Chat::White, "Usage: (targeted) #ai con [mob name]"); - } - else if (strcasecmp(sep->arg[1], "guard") == 0) { - if (target && target->IsNPC()) - target->CastToNPC()->SaveGuardSpot(target->GetPosition()); - else - c->Message(Chat::White, "Usage: (targeted) #ai guard - sets npc to guard the current location (use #summon to move)"); - } - else if (strcasecmp(sep->arg[1], "roambox") == 0) { - if (target && target->IsAIControlled() && target->IsNPC()) { - if ((sep->argnum == 6 || sep->argnum == 7 || sep->argnum == 8) && sep->IsNumber(2) && sep->IsNumber(3) && sep->IsNumber(4) && sep->IsNumber(5) && sep->IsNumber(6)) { - uint32 tmp = 2500; - uint32 tmp2 = 2500; - if (sep->IsNumber(7)) - tmp = atoi(sep->arg[7]); - if (sep->IsNumber(8)) - tmp2 = atoi(sep->arg[8]); - target->CastToNPC()->AI_SetRoambox(atof(sep->arg[2]), atof(sep->arg[3]), atof(sep->arg[4]), atof(sep->arg[5]), atof(sep->arg[6]), tmp, tmp2); - } - else if ((sep->argnum == 3 || sep->argnum == 4) && sep->IsNumber(2) && sep->IsNumber(3)) { - uint32 tmp = 2500; - uint32 tmp2 = 2500; - if (sep->IsNumber(4)) - tmp = atoi(sep->arg[4]); - if (sep->IsNumber(5)) - tmp2 = atoi(sep->arg[5]); - target->CastToNPC()->AI_SetRoambox(atof(sep->arg[2]), atof(sep->arg[3]), tmp, tmp2); - } - else { - c->Message(Chat::White, "Usage: #ai roambox dist max_x min_x max_y min_y [delay] [mindelay]"); - c->Message(Chat::White, "Usage: #ai roambox dist roamdist [delay] [mindelay]"); - } - } - else - c->Message(Chat::White, "You need a AI NPC targeted"); - } - else if (strcasecmp(sep->arg[1], "stop") == 0 && c->Admin() >= commandToggleAI) { - if (target) { - if (target->IsAIControlled()) - target->AI_Stop(); - else - c->Message(Chat::White, "Error: Target is not AI controlled"); - } - else - c->Message(Chat::White, "Usage: Target a Mob with AI enabled and use this to turn off their AI."); - } - else if (strcasecmp(sep->arg[1], "start") == 0 && c->Admin() >= commandToggleAI) { - if (target) { - if (!target->IsAIControlled()) - target->AI_Start(); - else - c->Message(Chat::White, "Error: Target is already AI controlled"); - } - else - c->Message(Chat::White, "Usage: Target a Mob with AI disabled and use this to turn on their AI."); - } - else { - c->Message(Chat::White, "#AI Sub-commands"); - c->Message(Chat::White, " factionid"); - c->Message(Chat::White, " spellslist"); - c->Message(Chat::White, " con"); - c->Message(Chat::White, " guard"); - } -} - -void command_worldshutdown(Client *c, const Seperator *sep) -{ - // GM command to shutdown world server and all zone servers - uint32 time=0; - uint32 interval=0; - if (worldserver.Connected()) { - if(sep->IsNumber(1) && sep->IsNumber(2) && ((time=atoi(sep->arg[1]))>0) && ((interval=atoi(sep->arg[2]))>0)) { - worldserver.SendEmoteMessage(0,0,15,":SYSTEM MSG:World coming down in %i minutes, everyone log out before this time.", (time / 60 )); - c->Message(Chat::White, "Sending shutdown packet now, World will shutdown in: %i minutes with an interval of: %i seconds", (time / 60), interval); - auto pack = new ServerPacket(ServerOP_ShutdownAll, sizeof(WorldShutDown_Struct)); - WorldShutDown_Struct* wsd = (WorldShutDown_Struct*)pack->pBuffer; - wsd->time=time*1000; - wsd->interval=(interval*1000); - worldserver.SendPacket(pack); - safe_delete(pack); - } - else if(strcasecmp(sep->arg[1], "now") == 0){ - worldserver.SendEmoteMessage(0,0,15,":SYSTEM MSG:World coming down, everyone log out now."); - c->Message(Chat::White, "Sending shutdown packet"); - auto pack = new ServerPacket; - pack->opcode = ServerOP_ShutdownAll; - pack->size=0; - worldserver.SendPacket(pack); - safe_delete(pack); - } - else if(strcasecmp(sep->arg[1], "disable") == 0){ - c->Message(Chat::White, "Shutdown prevented, next time I may not be so forgiving..."); - auto pack = new ServerPacket(ServerOP_ShutdownAll, sizeof(WorldShutDown_Struct)); - WorldShutDown_Struct* wsd = (WorldShutDown_Struct*)pack->pBuffer; - wsd->time=0; - wsd->interval=0; - worldserver.SendPacket(pack); - safe_delete(pack); - } - else{ - c->Message(Chat::White,"#worldshutdown - Shuts down the server and all zones."); - c->Message(Chat::White,"Usage: #worldshutdown now - Shuts down the server and all zones immediately."); - c->Message(Chat::White,"Usage: #worldshutdown disable - Stops the server from a previously scheduled shut down."); - c->Message(Chat::White,"Usage: #worldshutdown [timer] [interval] - Shuts down the server and all zones after [timer] seconds and sends warning every [interval] seconds."); - } - } - else - c->Message(Chat::White, "Error: World server disconnected"); -} - -void command_sendzonespawns(Client *c, const Seperator *sep) -{ - entity_list.SendZoneSpawns(c); -} - -void command_zsave(Client *c, const Seperator *sep) -{ - if (zone->SaveZoneCFG()) { - c->Message(Chat::Red, "Zone header saved successfully."); - } - else { - c->Message(Chat::Red, "ERROR: Zone header data was NOT saved."); - } -} - -void command_dbspawn2(Client *c, const Seperator *sep) -{ - - if (sep->IsNumber(1) && sep->IsNumber(2) && sep->IsNumber(3)) { - LogInfo("Spawning database spawn"); - uint16 cond = 0; - int16 cond_min = 0; - if(sep->IsNumber(4)) { - cond = atoi(sep->arg[4]); - if(sep->IsNumber(5)) - cond_min = atoi(sep->arg[5]); - } - database.CreateSpawn2(c, atoi(sep->arg[1]), zone->GetShortName(), c->GetPosition(), atoi(sep->arg[2]), atoi(sep->arg[3]), cond, cond_min); - } - else { - c->Message(Chat::White, "Usage: #dbspawn2 spawngroup respawn variance [condition_id] [condition_min]"); - } -} - -void command_shutdown(Client *c, const Seperator *sep) -{ - CatchSignal(2); -} - -void command_delacct(Client *c, const Seperator *sep) -{ - if (sep->arg[1][0] == 0) - c->Message(Chat::White, "Format: #delacct accountname"); - else { - std::string user; - std::string loginserver; - ParseAccountString(sep->arg[1], user, loginserver); - - if (database.DeleteAccount(user.c_str(), loginserver.c_str())) - c->Message(Chat::White, "The account was deleted."); - else - c->Message(Chat::White, "Unable to delete account."); - } -} - -void command_setpass(Client *c, const Seperator *sep) -{ - if(sep->argnum != 2) - c->Message(Chat::White, "Format: #setpass accountname password"); - else { - std::string user; - std::string loginserver; - ParseAccountString(sep->arg[1], user, loginserver); - - int16 tmpstatus = 0; - uint32 tmpid = database.GetAccountIDByName(user.c_str(), loginserver.c_str(), &tmpstatus); - if (!tmpid) - c->Message(Chat::White, "Error: Account not found"); - else if (tmpstatus > c->Admin()) - c->Message(Chat::White, "Cannot change password: Account's status is higher than yours"); - else if (database.SetLocalPassword(tmpid, sep->arg[2])) - c->Message(Chat::White, "Password changed."); - else - c->Message(Chat::White, "Error changing password."); - } -} - -void command_setlsinfo(Client *c, const Seperator *sep) -{ - if(sep->argnum != 2) - c->Message(Chat::White, "Format: #setlsinfo email password"); - else { - auto pack = new ServerPacket(ServerOP_LSAccountUpdate, sizeof(ServerLSAccountUpdate_Struct)); - ServerLSAccountUpdate_Struct* s = (ServerLSAccountUpdate_Struct *) pack->pBuffer; - s->useraccountid = c->LSAccountID(); - strn0cpy(s->useraccount, c->AccountName(), 30); - strn0cpy(s->user_email, sep->arg[1], 100); - strn0cpy(s->userpassword, sep->arg[2], 50); - worldserver.SendPacket(pack); - c->Message(Chat::White, "Login Server update packet sent."); - } -} - -void command_grid(Client *c, const Seperator *sep) -{ - if (strcasecmp("max", sep->arg[1]) == 0) { - c->Message(Chat::White, "Highest grid ID in this zone: %d", content_db.GetHighestGrid(zone->GetZoneID())); - } - else if (strcasecmp("add", sep->arg[1]) == 0) { - content_db.ModifyGrid(c, false, atoi(sep->arg[2]), atoi(sep->arg[3]), atoi(sep->arg[4]), zone->GetZoneID()); - } - else if (strcasecmp("show", sep->arg[1]) == 0) { - - Mob *target = c->GetTarget(); - - if (!target || !target->IsNPC()) { - c->Message(Chat::White, "You need a NPC target!"); - return; - } - - std::string query = StringFormat( - "SELECT `x`, `y`, `z`, `heading`, `number` " - "FROM `grid_entries` " - "WHERE `zoneid` = %u and `gridid` = %i " - "ORDER BY `number`", - zone->GetZoneID(), - target->CastToNPC()->GetGrid() - ); - - auto results = content_db.QueryDatabase(query); - if (!results.Success()) { - c->Message(Chat::White, "Error querying database."); - c->Message(Chat::White, query.c_str()); - } - - if (results.RowCount() == 0) { - c->Message(Chat::White, "No grid found"); - return; - } - - /** - * Depop any node npc's already spawned - */ - auto &mob_list = entity_list.GetMobList(); - for (auto itr = mob_list.begin(); itr != mob_list.end(); ++itr) { - Mob *mob = itr->second; - if (mob->IsNPC() && mob->GetRace() == 2254) { - mob->Depop(); - } - } - - /** - * Spawn grid nodes - */ - std::map, int32> zoffset; - - for (auto row = results.begin(); row != results.end(); ++row) { - glm::vec4 node_position = glm::vec4(atof(row[0]), atof(row[1]), atof(row[2]), atof(row[3])); - - std::vector node_loc { - node_position.x, - node_position.y, - node_position.z - }; - - // If we already have a node at this location, set the z offset - // higher from the existing one so we can see it. Adjust so if - // there is another at the same spot we adjust again. - auto search = zoffset.find(node_loc); - if (search != zoffset.end()) { - search->second = search->second + 3; - } - else { - zoffset[node_loc] = 0.0; - } - - node_position.z += zoffset[node_loc]; - - NPC::SpawnGridNodeNPC(node_position,atoi(row[4]),zoffset[node_loc]); - } - } - else if (strcasecmp("delete", sep->arg[1]) == 0) { - content_db.ModifyGrid(c, true, atoi(sep->arg[2]), 0, 0, zone->GetZoneID()); - } - else { - c->Message(Chat::White, "Usage: #grid add/delete grid_num wandertype pausetype"); - c->Message(Chat::White, "Usage: #grid max - displays the highest grid ID used in this zone (for add)"); - c->Message(Chat::White, "Usage: #grid show - displays wp nodes as boxes"); - } -} - -void command_wp(Client *c, const Seperator *sep) -{ - int wp = atoi(sep->arg[4]); - - if (strcasecmp("add", sep->arg[1]) == 0) { - if (wp == 0) //default to highest if it's left blank, or we enter 0 - wp = content_db.GetHighestWaypoint(zone->GetZoneID(), atoi(sep->arg[2])) + 1; - if (strcasecmp("-h", sep->arg[5]) == 0) { - content_db.AddWP(c, atoi(sep->arg[2]),wp, c->GetPosition(), atoi(sep->arg[3]),zone->GetZoneID()); - } - else { - auto position = c->GetPosition(); - position.w = -1; - content_db.AddWP(c, atoi(sep->arg[2]),wp, position, atoi(sep->arg[3]),zone->GetZoneID()); - } - } - else if (strcasecmp("delete", sep->arg[1]) == 0) - content_db.DeleteWaypoint(c, atoi(sep->arg[2]),wp,zone->GetZoneID()); - else - c->Message(Chat::White,"Usage: #wp add/delete grid_num pause wp_num [-h]"); -} - -void command_iplookup(Client *c, const Seperator *sep) -{ - auto pack = - new ServerPacket(ServerOP_IPLookup, sizeof(ServerGenericWorldQuery_Struct) + strlen(sep->argplus[1]) + 1); - ServerGenericWorldQuery_Struct* s = (ServerGenericWorldQuery_Struct *) pack->pBuffer; - strcpy(s->from, c->GetName()); - s->admin = c->Admin(); - if (sep->argplus[1][0] != 0) - strcpy(s->query, sep->argplus[1]); - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void command_size(Client *c, const Seperator *sep) -{ - Mob *target=c->GetTarget(); - if (!sep->IsNumber(1)) - c->Message(Chat::White, "Usage: #size [0 - 255] (Decimal increments are allowed)"); - else { - float newsize = atof(sep->arg[1]); - if (newsize > 255) - c->Message(Chat::White, "Error: #size: Size can not be greater than 255."); - else if (newsize < 0) - c->Message(Chat::White, "Error: #size: Size can not be less than 0."); - else if (!target) - c->Message(Chat::White,"Error: this command requires a target"); - else { - uint16 Race = target->GetModel(); - uint8 Gender = target->GetGender(); - uint8 Texture = 0xFF; - uint8 HelmTexture = 0xFF; - uint8 HairColor = target->GetHairColor(); - uint8 BeardColor = target->GetBeardColor(); - uint8 EyeColor1 = target->GetEyeColor1(); - uint8 EyeColor2 = target->GetEyeColor2(); - uint8 HairStyle = target->GetHairStyle(); - uint8 LuclinFace = target->GetLuclinFace(); - uint8 Beard = target->GetBeard(); - uint32 DrakkinHeritage = target->GetDrakkinHeritage(); - uint32 DrakkinTattoo = target->GetDrakkinTattoo(); - uint32 DrakkinDetails = target->GetDrakkinDetails(); - - target->SendIllusionPacket(Race, Gender, Texture, HelmTexture, HairColor, BeardColor, - EyeColor1, EyeColor2, HairStyle, LuclinFace, Beard, 0xFF, - DrakkinHeritage, DrakkinTattoo, DrakkinDetails, newsize); - - c->Message(Chat::White,"Size = %f", atof(sep->arg[1])); - } - } -} - -void command_mana(Client *c, const Seperator *sep) -{ - Mob *t; - - t = c->GetTarget() ? c->GetTarget() : c; - - if(t->IsClient()) - t->CastToClient()->SetMana(t->CastToClient()->CalcMaxMana()); - else - t->SetMana(t->CalcMaxMana()); -} - -void command_flymode(Client *c, const Seperator *sep) -{ - Mob *t = c; - - if (strlen(sep->arg[1]) == 1 && sep->IsNumber(1) && atoi(sep->arg[1]) >= 0 && atoi(sep->arg[1]) <= 5) { - if (c->GetTarget()) { - t = c->GetTarget(); - } - - int fm = atoi(sep->arg[1]); - - t->SetFlyMode(static_cast(fm)); - t->SendAppearancePacket(AT_Levitate, fm); - if (sep->arg[1][0] == '0') { - c->Message(Chat::White, "Setting %s to Grounded", t->GetName()); - } - else if (sep->arg[1][0] == '1') { - c->Message(Chat::White, "Setting %s to Flying", t->GetName()); - } - else if (sep->arg[1][0] == '2') { - c->Message(Chat::White, "Setting %s to Levitating", t->GetName()); - } - else if (sep->arg[1][0] == '3') { - c->Message(Chat::White, "Setting %s to In Water", t->GetName()); - } - else if (sep->arg[1][0] == '4') { - c->Message(Chat::White, "Setting %s to Floating(Boat)", t->GetName()); - } - else if (sep->arg[1][0] == '5') { - c->Message(Chat::White, "Setting %s to Levitating While Running", t->GetName()); - } - } else { - c->Message(Chat::White, "#flymode [0/1/2/3/4/5]"); - } -} - - -void command_showskills(Client *c, const Seperator *sep) -{ - Client *t=c; - - if(c->GetTarget() && c->GetTarget()->IsClient()) - t=c->GetTarget()->CastToClient(); - - c->Message(Chat::White, "Skills for %s", t->GetName()); - for (EQ::skills::SkillType i = EQ::skills::Skill1HBlunt; i <= EQ::skills::HIGHEST_SKILL; i = (EQ::skills::SkillType)(i + 1)) - c->Message(Chat::White, "Skill [%d] is at [%d] - %u", i, t->GetSkill(i), t->GetRawSkill(i)); -} - -void command_findrace(Client *c, const Seperator *sep) -{ - if (sep->arg[1][0] == 0) { - c->Message(Chat::White, "Usage: #findrace [race name]"); - } else if (Seperator::IsNumber(sep->argplus[1])) { - int search_id = atoi(sep->argplus[1]); - std::string race_name = GetRaceIDName(search_id); - if (race_name != std::string("")) { - c->Message(Chat::White, "Race %d: %s", search_id, race_name.c_str()); - return; - } - } else { - const char *search_criteria = sep->argplus[1]; - int found_count = 0; - char race_name[64]; - char search_string[65]; - strn0cpy(search_string, search_criteria, sizeof(search_string)); - strupr(search_string); - char *string_location; - for (int race_id = RACE_HUMAN_1; race_id <= RT_PEGASUS_3; race_id++) { - strn0cpy(race_name, GetRaceIDName(race_id), sizeof(race_name)); - strupr(race_name); - string_location = strstr(race_name, search_string); - if (string_location != nullptr) { - c->Message(Chat::White, "Race %d: %s", race_id, GetRaceIDName(race_id)); - found_count++; - } - - if (found_count == 20) { - break; - } - } - if (found_count == 20) { - c->Message(Chat::White, "20 Races found... max reached."); - } else { - c->Message(Chat::White, "%i Race(s) found.", found_count); - } - } -} - -void command_findspell(Client *c, const Seperator *sep) -{ - if (sep->arg[1][0] == 0) - c->Message(Chat::White, "Usage: #FindSpell [spellname]"); - else if (SPDAT_RECORDS <= 0) - c->Message(Chat::White, "Spells not loaded"); - else if (Seperator::IsNumber(sep->argplus[1])) { - int spellid = atoi(sep->argplus[1]); - if (spellid <= 0 || spellid >= SPDAT_RECORDS) { - c->Message(Chat::White, "Error: Number out of range"); - } - else { - c->Message(Chat::White, " %i: %s", spellid, spells[spellid].name); - } - } - else { - int count=0; - //int iSearchLen = strlen(sep->argplus[1])+1; - char sName[64]; - char sCriteria[65]; - strn0cpy(sCriteria, sep->argplus[1], 64); - strupr(sCriteria); - for (int i=0; iMessage(Chat::White, " %i: %s", i, spells[i].name); - count++; - } - else if (count > 20) - break; - } - } - if (count > 20) - c->Message(Chat::White, "20 spells found... max reached."); - else - c->Message(Chat::White, "%i spells found.", count); - } -} - -void command_castspell(Client *c, const Seperator *sep) -{ - if (!sep->IsNumber(1)) - c->Message(Chat::White, "Usage: #CastSpell spellid"); - else { - uint16 spellid = atoi(sep->arg[1]); - /* - Spell restrictions. - */ - if (((spellid == 2859) || (spellid == 841) || (spellid == 300) || (spellid == 2314) || - (spellid == 3716) || (spellid == 911) || (spellid == 3014) || (spellid == 982) || - (spellid == 905) || (spellid == 2079) || (spellid == 1218) || (spellid == 819) || - ((spellid >= 780) && (spellid <= 785)) || ((spellid >= 1200) && (spellid <= 1205)) || - ((spellid >= 1342) && (spellid <= 1348)) || (spellid == 1923) || (spellid == 1924) || - (spellid == 3355)) && - c->Admin() < commandCastSpecials) - c->Message(Chat::Red, "Unable to cast spell."); - else if (spellid >= SPDAT_RECORDS) - c->Message(Chat::White, "Error: #CastSpell: Argument out of range"); - else - if (c->GetTarget() == 0) - if(c->Admin() >= commandInstacast) - c->SpellFinished(spellid, 0, EQ::spells::CastingSlot::Item, 0, -1, spells[spellid].ResistDiff); - else - c->CastSpell(spellid, 0, EQ::spells::CastingSlot::Item, 0); - else - if(c->Admin() >= commandInstacast) - c->SpellFinished(spellid, c->GetTarget(), EQ::spells::CastingSlot::Item, 0, -1, spells[spellid].ResistDiff); - else - c->CastSpell(spellid, c->GetTarget()->GetID(), EQ::spells::CastingSlot::Item, 0); - } -} - -void command_setlanguage(Client *c, const Seperator *sep) -{ - if (strcasecmp(sep->arg[1], "list" ) == 0 ) - { - c->Message(Chat::White, "Languages:"); - c->Message(Chat::White, "(0) Common Tongue"); - c->Message(Chat::White, "(1) Barbarian"); - c->Message(Chat::White, "(2) Erudian"); - c->Message(Chat::White, "(3) Elvish"); - c->Message(Chat::White, "(4) Dark Elvish"); - c->Message(Chat::White, "(5) Dwarvish"); - c->Message(Chat::White, "(6) Troll"); - c->Message(Chat::White, "(7) Ogre"); - c->Message(Chat::White, "(8) Gnomish"); - c->Message(Chat::White, "(9) Halfling"); - c->Message(Chat::White, "(10) Thieves Cant"); - c->Message(Chat::White, "(11) Old Erudian"); - c->Message(Chat::White, "(12) Elder Elvish"); - c->Message(Chat::White, "(13) Froglok"); - c->Message(Chat::White, "(14) Goblin"); - c->Message(Chat::White, "(15) Gnoll"); - c->Message(Chat::White, "(16) Combine Tongue"); - c->Message(Chat::White, "(17) Elder Teir`Dal"); - c->Message(Chat::White, "(18) Lizardman"); - c->Message(Chat::White, "(19) Orcish"); - c->Message(Chat::White, "(20) Faerie"); - c->Message(Chat::White, "(21) Dragon"); - c->Message(Chat::White, "(22) Elder Dragon"); - c->Message(Chat::White, "(23) Dark Speech"); - c->Message(Chat::White, "(24) Vah Shir"); - c->Message(Chat::White, "(25) Alaran"); - c->Message(Chat::White, "(26) Hadal"); - c->Message(Chat::White, "(27) Unknown1"); - } - else if( c->GetTarget() == 0 ) - { - c->Message(Chat::White, "Error: #setlanguage: No target."); - } - else if( !c->GetTarget()->IsClient() ) - { - c->Message(Chat::White, "Error: Target must be a player."); - } - else if ( - !sep->IsNumber(1) || atoi(sep->arg[1]) < 0 || atoi(sep->arg[1]) > 27 || - !sep->IsNumber(2) || atoi(sep->arg[2]) < 0 || atoi(sep->arg[2]) > 100 - ) - { - c->Message(Chat::White, "Usage: #setlanguage [language ID] [value] (0-27, 0-100)"); - c->Message(Chat::White, "Try #setlanguage list for a list of language IDs"); - } - else - { - LogInfo("Set language request from [{}], target:[{}] lang_id:[{}] value:[{}]", c->GetName(), c->GetTarget()->GetName(), atoi(sep->arg[1]), atoi(sep->arg[2]) ); - uint8 langid = (uint8)atoi(sep->arg[1]); - uint8 value = (uint8)atoi(sep->arg[2]); - c->GetTarget()->CastToClient()->SetLanguageSkill( langid, value ); - } -} - -void command_setskill(Client *c, const Seperator *sep) -{ - if (c->GetTarget() == nullptr) { - c->Message(Chat::White, "Error: #setskill: No target."); - } - else if (!c->GetTarget()->IsClient()) { - c->Message(Chat::White, "Error: #setskill: Target must be a client."); - } - else if ( - !sep->IsNumber(1) || atoi(sep->arg[1]) < 0 || atoi(sep->arg[1]) > EQ::skills::HIGHEST_SKILL || - !sep->IsNumber(2) || atoi(sep->arg[2]) < 0 || atoi(sep->arg[2]) > HIGHEST_CAN_SET_SKILL - ) - { - c->Message(Chat::White, "Usage: #setskill skill x "); - c->Message(Chat::White, " skill = 0 to %d", EQ::skills::HIGHEST_SKILL); - c->Message(Chat::White, " x = 0 to %d", HIGHEST_CAN_SET_SKILL); - } - else { - LogInfo("Set skill request from [{}], target:[{}] skill_id:[{}] value:[{}]", c->GetName(), c->GetTarget()->GetName(), atoi(sep->arg[1]), atoi(sep->arg[2]) ); - int skill_num = atoi(sep->arg[1]); - uint16 skill_value = atoi(sep->arg[2]); - if (skill_num <= EQ::skills::HIGHEST_SKILL) - c->GetTarget()->CastToClient()->SetSkill((EQ::skills::SkillType)skill_num, skill_value); - } -} - -void command_setskillall(Client *c, const Seperator *sep) -{ - if (c->GetTarget() == 0) - c->Message(Chat::White, "Error: #setallskill: No target."); - else if (!c->GetTarget()->IsClient()) - c->Message(Chat::White, "Error: #setskill: Target must be a client."); - else if (!sep->IsNumber(1) || atoi(sep->arg[1]) < 0 || atoi(sep->arg[1]) > HIGHEST_CAN_SET_SKILL) { - c->Message(Chat::White, "Usage: #setskillall value "); - c->Message(Chat::White, " value = 0 to %d", HIGHEST_CAN_SET_SKILL); - } - else { - if (c->Admin() >= commandSetSkillsOther || c->GetTarget()==c || c->GetTarget()==0) { - LogInfo("Set ALL skill request from [{}], target:[{}]", c->GetName(), c->GetTarget()->GetName()); - uint16 level = atoi(sep->arg[1]); - for (EQ::skills::SkillType skill_num = EQ::skills::Skill1HBlunt; skill_num <= EQ::skills::HIGHEST_SKILL; skill_num = (EQ::skills::SkillType)(skill_num + 1)) { - c->GetTarget()->CastToClient()->SetSkill(skill_num, level); - } - } - else - c->Message(Chat::White, "Error: Your status is not high enough to set anothers skills"); - } -} - -void command_race(Client *c, const Seperator *sep) -{ - Mob *target = c->CastToMob(); - - if (sep->IsNumber(1)) { - auto race = atoi(sep->arg[1]); - if ((race >= 0 && race <= 732) || (race >= 2253 && race <= 2259)) { - if ((c->GetTarget()) && c->Admin() >= commandRaceOthers) { - target = c->GetTarget(); - } - target->SendIllusionPacket(race); - } - else { - c->Message(Chat::White, "Usage: #race [0-732, 2253-2259] (0 for back to normal)"); - } - } - else { - c->Message(Chat::White, "Usage: #race [0-732, 2253-2259] (0 for back to normal)"); - } -} - -void command_gearup(Client *c, const Seperator *sep) -{ - std::string tool_table_name = "tool_gearup_armor_sets"; - - if (!database.DoesTableExist(tool_table_name)) { - c->Message( - Chat::Red, - fmt::format( - "Table [{}] does not exist, please source in the optional SQL required for this tool", - tool_table_name - ).c_str() - ); - return; - } - - std::string expansion_arg = sep->arg[1]; - std::string expansion_filter; - if (expansion_arg.length() > 0) { - expansion_filter = fmt::format("and `expansion` = {}", expansion_arg); - } - - auto results = database.QueryDatabase( - fmt::format( - SQL ( - select - item_id, - slot - from - {} - where - `class` = {} - and `level` = {} - {} - order by score desc, expansion desc - ), - tool_table_name, - c->GetClass(), - c->GetLevel(), - expansion_filter - ) - ); - - std::set equipped; - for (auto row = results.begin(); row != results.end(); ++row) { - int item_id = atoi(row[0]); - int slot_id = atoi(row[1]); - - if (equipped.find(slot_id) != equipped.end()) { - if (slot_id == EQ::invslot::slotEar1) { - slot_id = EQ::invslot::slotEar2; - } - if (slot_id == EQ::invslot::slotFinger1) { - slot_id = EQ::invslot::slotFinger2; - } - if (slot_id == EQ::invslot::slotWrist1) { - slot_id = EQ::invslot::slotWrist2; - } - } - - if (equipped.find(slot_id) == equipped.end()) { - if (c->CastToMob()->CanClassEquipItem(item_id)) { - equipped.insert(slot_id); - c->SummonItem( - item_id, - 0, 0, 0, 0, 0, 0, 0, 0, - slot_id - ); - } - } - } - - if (expansion_arg.empty()) { - results = database.QueryDatabase( - fmt::format( - SQL ( - select - expansion - from - {} - where - class = {} - and level = {} - group by - expansion; - ), - tool_table_name, - c->GetClass(), - c->GetLevel() - ) - ); - - c->Message(Chat::White, "Choose armor from a specific era"); - std::string message; - for (auto row = results.begin(); row != results.end(); ++row) { - int expansion = atoi(row[0]); - message += "[" + EQ::SayLinkEngine::GenerateQuestSaylink( - fmt::format("#gearup {}", expansion), - false, - Expansion::ExpansionName[expansion] - ) + "] "; - - if (message.length() > 2000) { - c->Message(Chat::White, message.c_str()); - message = ""; - } - } - if (message.length() > 0) { - c->Message(Chat::White, message.c_str()); - } - } - -} - -void command_gender(Client *c, const Seperator *sep) -{ - Mob *t=c->CastToMob(); - - if (sep->IsNumber(1) && atoi(sep->arg[1]) >= 0 && atoi(sep->arg[1]) <= 500) { - if ((c->GetTarget()) && c->Admin() >= commandGenderOthers) - t=c->GetTarget(); - t->SendIllusionPacket(t->GetRace(), atoi(sep->arg[1])); - } - else - c->Message(Chat::White, "Usage: #gender [0/1/2]"); -} - -void command_makepet(Client *c, const Seperator *sep) -{ - if (sep->arg[1][0] == '\0') - c->Message(Chat::White, "Usage: #makepet pet_type_name (will not survive across zones)"); - else - c->MakePet(0, sep->arg[1]); -} - void command_level(Client *c, const Seperator *sep) { uint16 level = atoi(sep->arg[1]); @@ -3230,4035 +938,6 @@ void command_spawneditmass(Client *c, const Seperator *sep) } } -void command_spawn(Client *c, const Seperator *sep) -{ - if (sep->arg[1][0] != 0){ - Client* client = entity_list.GetClientByName(sep->arg[1]); - if(client){ - c->Message(Chat::White,"You cannot spawn a mob with the same name as a character!"); - return; - } - } - - NPC* npc = NPC::SpawnNPC(sep->argplus[1], c->GetPosition(), c); - if (!npc) { - c->Message(Chat::White, "Format: #spawn name race level material hp gender class priweapon secweapon merchantid bodytype - spawns a npc those parameters."); - c->Message(Chat::White, "Name Format: NPCFirstname_NPCLastname - All numbers in a name are stripped and \"_\" characters become a space."); - c->Message(Chat::White, "Note: Using \"-\" for gender will autoselect the gender for the race. Using \"-\" for HP will use the calculated maximum HP."); - } -} - -void command_test(Client *c, const Seperator *sep) -{ - c->Message(Chat::Yellow, "Triggering test command"); - - if (sep->arg[1]) { - c->SetPrimaryWeaponOrnamentation(atoi(sep->arg[1])); - } - if (sep->arg[2]) { - c->SetSecondaryWeaponOrnamentation(atoi(sep->arg[2])); - } -} - -void command_texture(Client *c, const Seperator *sep) -{ - - uint16 texture; - - if (sep->IsNumber(1) && atoi(sep->arg[1]) >= 0 && atoi(sep->arg[1]) <= 255) { - texture = atoi(sep->arg[1]); - uint8 helm = 0xFF; - - // Player Races Wear Armor, so Wearchange is sent instead - int i; - if (!c->GetTarget()) - for (i = EQ::textures::textureBegin; i <= EQ::textures::LastTintableTexture; i++) - { - c->SendTextureWC(i, texture); - } - else if ((c->GetTarget()->GetModel() > 0 && c->GetTarget()->GetModel() <= 12) || - c->GetTarget()->GetModel() == 128 || c->GetTarget()->GetModel() == 130 || - c->GetTarget()->GetModel() == 330 || c->GetTarget()->GetModel() == 522) { - for (i = EQ::textures::textureBegin; i <= EQ::textures::LastTintableTexture; i++) - { - c->GetTarget()->SendTextureWC(i, texture); - } - } - else // Non-Player Races only need Illusion Packets to be sent for texture - { - if (sep->IsNumber(2) && atoi(sep->arg[2]) >= 0 && atoi(sep->arg[2]) <= 255) - helm = atoi(sep->arg[2]); - else - helm = texture; - - if (texture == 255) { - texture = 0xFFFF; // Should be pulling these from the database instead - helm = 0xFF; - } - - if ((c->GetTarget()) && (c->Admin() >= commandTextureOthers)) - c->GetTarget()->SendIllusionPacket(c->GetTarget()->GetModel(), 0xFF, texture, helm); - else - c->SendIllusionPacket(c->GetRace(), 0xFF, texture, helm); - } - } - else - c->Message(Chat::White, "Usage: #texture [texture] [helmtexture] (0-255, 255 for show equipment)"); -} - -void command_npctypespawn(Client *c, const Seperator *sep) -{ - if (sep->IsNumber(1)) { - const NPCType* tmp = 0; - if ((tmp = content_db.LoadNPCTypesData(atoi(sep->arg[1])))) { - //tmp->fixedZ = 1; - auto npc = new NPC(tmp, 0, c->GetPosition(), GravityBehavior::Water); - if (npc && sep->IsNumber(2)) - npc->SetNPCFactionID(atoi(sep->arg[2])); - - npc->AddLootTable(); - if (npc->DropsGlobalLoot()) - npc->CheckGlobalLootTables(); - entity_list.AddNPC(npc); - } - else - c->Message(Chat::White, "NPC Type %i not found", atoi(sep->arg[1])); - } - else - c->Message(Chat::White, "Usage: #npctypespawn npctypeid factionid"); - -} - -void command_nudge(Client* c, const Seperator* sep) -{ - if (sep->arg[1][0] == 0) { - c->Message(Chat::White, "Usage: #nudge [x=f] [y=f] [z=f] [h=f] (partial/mixed arguments allowed)"); - } - else { - - auto target = c->GetTarget(); - if (!target) { - - c->Message(Chat::Yellow, "This command requires a target."); - return; - } - if (target->IsMoving()) { - - c->Message(Chat::Yellow, "This command requires a stationary target."); - return; - } - - glm::vec4 position_offset(0.0f, 0.0f, 0.0f, 0.0f); - for (auto index = 1; index <= 4; ++index) { - - if (!sep->arg[index]) { - continue; - } - - Seperator argsep(sep->arg[index], '='); - if (!argsep.arg[1][0]) { - continue; - } - - switch (argsep.arg[0][0]) { - case 'x': - position_offset.x = atof(argsep.arg[1]); - break; - case 'y': - position_offset.y = atof(argsep.arg[1]); - break; - case 'z': - position_offset.z = atof(argsep.arg[1]); - break; - case 'h': - position_offset.w = atof(argsep.arg[1]); - break; - default: - break; - } - } - - const auto& current_position = target->GetPosition(); - glm::vec4 new_position( - (current_position.x + position_offset.x), - (current_position.y + position_offset.y), - (current_position.z + position_offset.z), - (current_position.w + position_offset.w) - ); - - target->GMMove(new_position.x, new_position.y, new_position.z, new_position.w); - - c->Message( - Chat::White, - "Nudging '%s' to {%1.3f, %1.3f, %1.3f, %1.2f} (adjustment: {%1.3f, %1.3f, %1.3f, %1.2f})", - target->GetName(), - new_position.x, - new_position.y, - new_position.z, - new_position.w, - position_offset.x, - position_offset.y, - position_offset.z, - position_offset.w - ); - } -} - -void command_heal(Client *c, const Seperator *sep) -{ - if (c->GetTarget()==0) - c->Message(Chat::White, "Error: #Heal: No Target."); - else - c->GetTarget()->Heal(); -} - -void command_appearance(Client *c, const Seperator *sep) -{ - Mob *t=c->CastToMob(); - - // sends any appearance packet - // Dev debug command, for appearance types - if (sep->arg[2][0] == 0) - c->Message(Chat::White, "Usage: #appearance type value"); - else { - if ((c->GetTarget())) - t=c->GetTarget(); - t->SendAppearancePacket(atoi(sep->arg[1]), atoi(sep->arg[2])); - c->Message(Chat::White, "Sending appearance packet: target=%s, type=%s, value=%s", t->GetName(), sep->arg[1], sep->arg[2]); - } -} - -void command_nukeitem(Client *c, const Seperator *sep) -{ - int numitems, itemid; - - if (c->GetTarget() && c->GetTarget()->IsClient() && (sep->IsNumber(1) || sep->IsHexNumber(1))) { - itemid=sep->IsNumber(1)?atoi(sep->arg[1]):hextoi(sep->arg[1]); - numitems = c->GetTarget()->CastToClient()->NukeItem(itemid); - c->Message(Chat::White, " %u items deleted", numitems); - } - else - c->Message(Chat::White, "Usage: (targted) #nukeitem itemnum - removes the item from the player's inventory"); -} - -void command_peekinv(Client *c, const Seperator *sep) -{ - // this can be cleaned up once inventory is cleaned up - enum { - peekNone = 0x0000, - peekEquip = 0x0001, - peekGen = 0x0002, - peekCursor = 0x0004, - peekLimbo = 0x0008, - peekTrib = 0x0010, - peekBank = 0x0020, - peekShBank = 0x0040, - peekTrade = 0x0080, - peekWorld = 0x0100, - peekOutOfScope = (peekWorld * 2) // less than - }; - - static const char* scope_prefix[] = { "equip", "gen", "cursor", "limbo", "trib", "bank", "shbank", "trade", "world" }; - - static const int16 scope_range[][2] = { - { EQ::invslot::EQUIPMENT_BEGIN, EQ::invslot::EQUIPMENT_END }, - { EQ::invslot::GENERAL_BEGIN, EQ::invslot::GENERAL_END }, - { EQ::invslot::slotCursor, EQ::invslot::slotCursor }, - { EQ::invslot::SLOT_INVALID, EQ::invslot::SLOT_INVALID }, - { EQ::invslot::TRIBUTE_BEGIN, EQ::invslot::TRIBUTE_END }, - { EQ::invslot::BANK_BEGIN, EQ::invslot::BANK_END }, - { EQ::invslot::SHARED_BANK_BEGIN, EQ::invslot::SHARED_BANK_END }, - { EQ::invslot::TRADE_BEGIN, EQ::invslot::TRADE_END }, - { EQ::invslot::SLOT_BEGIN, (EQ::invtype::WORLD_SIZE - 1) } - }; - - static const bool scope_bag[] = { false, true, true, true, false, true, true, true, true }; - - if (!c) - return; - - if (c->GetTarget() && !c->GetTarget()->IsClient()) { - c->Message(Chat::White, "You must target a PC for this command."); - return; - } - - int scopeMask = peekNone; - - if (strcasecmp(sep->arg[1], "all") == 0) { scopeMask = (peekOutOfScope - 1); } - else if (strcasecmp(sep->arg[1], "equip") == 0) { scopeMask |= peekEquip; } - else if (strcasecmp(sep->arg[1], "gen") == 0) { scopeMask |= peekGen; } - else if (strcasecmp(sep->arg[1], "cursor") == 0) { scopeMask |= peekCursor; } - else if (strcasecmp(sep->arg[1], "poss") == 0) { scopeMask |= (peekEquip | peekGen | peekCursor); } - else if (strcasecmp(sep->arg[1], "limbo") == 0) { scopeMask |= peekLimbo; } - else if (strcasecmp(sep->arg[1], "curlim") == 0) { scopeMask |= (peekCursor | peekLimbo); } - else if (strcasecmp(sep->arg[1], "trib") == 0) { scopeMask |= peekTrib; } - else if (strcasecmp(sep->arg[1], "bank") == 0) { scopeMask |= peekBank; } - else if (strcasecmp(sep->arg[1], "shbank") == 0) { scopeMask |= peekShBank; } - else if (strcasecmp(sep->arg[1], "allbank") == 0) { scopeMask |= (peekBank | peekShBank); } - else if (strcasecmp(sep->arg[1], "trade") == 0) { scopeMask |= peekTrade; } - else if (strcasecmp(sep->arg[1], "world") == 0) { scopeMask |= peekWorld; } - - if (!scopeMask) { - c->Message(Chat::White, "Usage: #peekinv [equip|gen|cursor|poss|limbo|curlim|trib|bank|shbank|allbank|trade|world|all]"); - c->Message(Chat::White, "- Displays a portion of the targeted user's inventory"); - c->Message(Chat::White, "- Caution: 'all' is a lot of information!"); - return; - } - - Client* targetClient = c; - if (c->GetTarget()) - targetClient = c->GetTarget()->CastToClient(); - - const EQ::ItemInstance* inst_main = nullptr; - const EQ::ItemInstance* inst_sub = nullptr; - const EQ::ItemInstance* inst_aug = nullptr; - const EQ::ItemData* item_data = nullptr; - - EQ::SayLinkEngine linker; - linker.SetLinkType(EQ::saylink::SayLinkItemInst); - - c->Message(Chat::White, "Displaying inventory for %s...", targetClient->GetName()); - - Object* objectTradeskill = targetClient->GetTradeskillObject(); - - bool itemsFound = false; - - for (int scopeIndex = 0, scopeBit = peekEquip; scopeBit < peekOutOfScope; ++scopeIndex, scopeBit <<= 1) { - if (scopeBit & ~scopeMask) - continue; - - if (scopeBit & peekWorld) { - if (objectTradeskill == nullptr) { - c->Message(Chat::Default, "No world tradeskill object selected..."); - continue; - } - else { - c->Message(Chat::White, "[WorldObject DBID: %i (entityid: %i)]", objectTradeskill->GetDBID(), objectTradeskill->GetID()); - } - } - - for (int16 indexMain = scope_range[scopeIndex][0]; indexMain <= scope_range[scopeIndex][1]; ++indexMain) { - if (indexMain == EQ::invslot::SLOT_INVALID) - continue; - - inst_main = ((scopeBit & peekWorld) ? objectTradeskill->GetItem(indexMain) : targetClient->GetInv().GetItem(indexMain)); - if (inst_main) { - itemsFound = true; - item_data = inst_main->GetItem(); - } - else { - item_data = nullptr; - } - - linker.SetItemInst(inst_main); - - c->Message( - (item_data == nullptr), - "%sSlot: %i, Item: %i (%s), Charges: %i", - scope_prefix[scopeIndex], - ((scopeBit & peekWorld) ? (EQ::invslot::WORLD_BEGIN + indexMain) : indexMain), - ((item_data == nullptr) ? 0 : item_data->ID), - linker.GenerateLink().c_str(), - ((inst_main == nullptr) ? 0 : inst_main->GetCharges()) - ); - - if (inst_main && inst_main->IsClassCommon()) { - for (uint8 indexAug = EQ::invaug::SOCKET_BEGIN; indexAug <= EQ::invaug::SOCKET_END; ++indexAug) { - inst_aug = inst_main->GetItem(indexAug); - if (!inst_aug) // extant only - continue; - - item_data = inst_aug->GetItem(); - linker.SetItemInst(inst_aug); - - c->Message( - (item_data == nullptr), - ".%sAugSlot: %i (Slot #%i, Aug idx #%i), Item: %i (%s), Charges: %i", - scope_prefix[scopeIndex], - INVALID_INDEX, - ((scopeBit & peekWorld) ? (EQ::invslot::WORLD_BEGIN + indexMain) : indexMain), - indexAug, - ((item_data == nullptr) ? 0 : item_data->ID), - linker.GenerateLink().c_str(), - ((inst_sub == nullptr) ? 0 : inst_sub->GetCharges()) - ); - } - } - - if (!scope_bag[scopeIndex] || !(inst_main && inst_main->IsClassBag())) - continue; - - for (uint8 indexSub = EQ::invbag::SLOT_BEGIN; indexSub <= EQ::invbag::SLOT_END; ++indexSub) { - inst_sub = inst_main->GetItem(indexSub); - if (!inst_sub) // extant only - continue; - - item_data = inst_sub->GetItem(); - linker.SetItemInst(inst_sub); - - c->Message( - (item_data == nullptr), - "..%sBagSlot: %i (Slot #%i, Bag idx #%i), Item: %i (%s), Charges: %i", - scope_prefix[scopeIndex], - ((scopeBit & peekWorld) ? INVALID_INDEX : EQ::InventoryProfile::CalcSlotId(indexMain, indexSub)), - ((scopeBit & peekWorld) ? (EQ::invslot::WORLD_BEGIN + indexMain) : indexMain), - indexSub, - ((item_data == nullptr) ? 0 : item_data->ID), - linker.GenerateLink().c_str(), - ((inst_sub == nullptr) ? 0 : inst_sub->GetCharges()) - ); - - if (inst_sub->IsClassCommon()) { - for (uint8 indexAug = EQ::invaug::SOCKET_BEGIN; indexAug <= EQ::invaug::SOCKET_END; ++indexAug) { - inst_aug = inst_sub->GetItem(indexAug); - if (!inst_aug) // extant only - continue; - - item_data = inst_aug->GetItem(); - linker.SetItemInst(inst_aug); - - c->Message( - (item_data == nullptr), - "...%sAugSlot: %i (Slot #%i, Sub idx #%i, Aug idx #%i), Item: %i (%s), Charges: %i", - scope_prefix[scopeIndex], - INVALID_INDEX, - ((scopeBit & peekWorld) ? INVALID_INDEX : EQ::InventoryProfile::CalcSlotId(indexMain, indexSub)), - indexSub, - indexAug, - ((item_data == nullptr) ? 0 : item_data->ID), - linker.GenerateLink().c_str(), - ((inst_sub == nullptr) ? 0 : inst_sub->GetCharges()) - ); - } - } - } - } - - if (scopeBit & peekLimbo) { - int limboIndex = 0; - for (auto it = targetClient->GetInv().cursor_cbegin(); (it != targetClient->GetInv().cursor_cend()); ++it, ++limboIndex) { - if (it == targetClient->GetInv().cursor_cbegin()) - continue; - - inst_main = *it; - if (inst_main) { - itemsFound = true; - item_data = inst_main->GetItem(); - } - else { - item_data = nullptr; - } - - linker.SetItemInst(inst_main); - - c->Message( - (item_data == nullptr), - "%sSlot: %i, Item: %i (%s), Charges: %i", - scope_prefix[scopeIndex], - (8000 + limboIndex), - ((item_data == nullptr) ? 0 : item_data->ID), - linker.GenerateLink().c_str(), - ((inst_main == nullptr) ? 0 : inst_main->GetCharges()) - ); - - if (inst_main && inst_main->IsClassCommon()) { - for (uint8 indexAug = EQ::invaug::SOCKET_BEGIN; indexAug <= EQ::invaug::SOCKET_END; ++indexAug) { - inst_aug = inst_main->GetItem(indexAug); - if (!inst_aug) // extant only - continue; - - item_data = inst_aug->GetItem(); - linker.SetItemInst(inst_aug); - - c->Message( - (item_data == nullptr), - ".%sAugSlot: %i (Slot #%i, Aug idx #%i), Item: %i (%s), Charges: %i", - scope_prefix[scopeIndex], - INVALID_INDEX, - (8000 + limboIndex), - indexAug, - ((item_data == nullptr) ? 0 : item_data->ID), - linker.GenerateLink().c_str(), - ((inst_sub == nullptr) ? 0 : inst_sub->GetCharges()) - ); - } - } - - if (!scope_bag[scopeIndex] || !(inst_main && inst_main->IsClassBag())) - continue; - - for (uint8 indexSub = EQ::invbag::SLOT_BEGIN; indexSub <= EQ::invbag::SLOT_END; ++indexSub) { - inst_sub = inst_main->GetItem(indexSub); - if (!inst_sub) - continue; - - item_data = (inst_sub == nullptr) ? nullptr : inst_sub->GetItem(); - - linker.SetItemInst(inst_sub); - - c->Message( - (item_data == nullptr), - "..%sBagSlot: %i (Slot #%i, Bag idx #%i), Item: %i (%s), Charges: %i", - scope_prefix[scopeIndex], - INVALID_INDEX, - (8000 + limboIndex), - indexSub, - ((item_data == nullptr) ? 0 : item_data->ID), - linker.GenerateLink().c_str(), - ((inst_sub == nullptr) ? 0 : inst_sub->GetCharges()) - ); - - if (inst_sub->IsClassCommon()) { - for (uint8 indexAug = EQ::invaug::SOCKET_BEGIN; indexAug <= EQ::invaug::SOCKET_END; ++indexAug) { - inst_aug = inst_sub->GetItem(indexAug); - if (!inst_aug) // extant only - continue; - - item_data = inst_aug->GetItem(); - linker.SetItemInst(inst_aug); - - c->Message( - (item_data == nullptr), - "...%sAugSlot: %i (Slot #%i, Sub idx #%i, Aug idx #%i), Item: %i (%s), Charges: %i", - scope_prefix[scopeIndex], - INVALID_INDEX, - (8000 + limboIndex), - indexSub, - indexAug, - ((item_data == nullptr) ? 0 : item_data->ID), - linker.GenerateLink().c_str(), - ((inst_sub == nullptr) ? 0 : inst_sub->GetCharges()) - ); - } - } - } - } - } - } - - if (!itemsFound) - c->Message(Chat::White, "No items found."); -} - -void command_interrogateinv(Client *c, const Seperator *sep) -{ - // 'command_interrogateinv' is an in-memory inventory interrogation tool only. - // - // it does not verify against actual database entries..but, the output can be - // used to verify that something has been corrupted in a player's inventory. - // any error condition should be assumed that the item in question will be - // lost when the player logs out or zones (or incurrs any action that will - // consume the Client-Inventory object instance in question.) - // - // any item instances located at a greater depth than a reported error should - // be treated as an error themselves regardless of whether they report as the - // same or not. - - if (strcasecmp(sep->arg[1], "help") == 0) { - if (c->Admin() < commandInterrogateInv) { - c->Message(Chat::White, "Usage: #interrogateinv"); - c->Message(Chat::White, " Displays your inventory's current in-memory nested storage references"); - } - else { - c->Message(Chat::White, "Usage: #interrogateinv [log] [silent]"); - c->Message(Chat::White, " Displays your or your Player target inventory's current in-memory nested storage references"); - c->Message(Chat::White, " [log] - Logs interrogation to file"); - c->Message(Chat::White, " [silent] - Omits the in-game message portion of the interrogation"); - } - return; - } - - Client* target = nullptr; - std::map instmap; - bool log = false; - bool silent = false; - bool error = false; - bool allowtrip = false; - - if (c->Admin() < commandInterrogateInv) { - if (c->GetInterrogateInvState()) { - c->Message(Chat::Red, "The last use of #interrogateinv on this inventory instance discovered an error..."); - c->Message(Chat::Red, "Logging out, zoning or re-arranging items at this point will result in item loss!"); - return; - } - target = c; - allowtrip = true; - } - else { - if (c->GetTarget() == nullptr) { - target = c; - } - else if (c->GetTarget()->IsClient()) { - target = c->GetTarget()->CastToClient(); - } - else { - c->Message(Chat::Default, "Use of this command is limited to Client entities"); - return; - } - - if (strcasecmp(sep->arg[1], "log") == 0) - log = true; - if (strcasecmp(sep->arg[2], "silent") == 0) - silent = true; - } - - bool success = target->InterrogateInventory(c, log, silent, allowtrip, error); - - if (!success) - c->Message(Chat::Red, "An unknown error occurred while processing Client::InterrogateInventory()"); -} - -void command_invsnapshot(Client *c, const Seperator *sep) -{ - if (!c) - return; - - if (sep->argnum == 0 || strcmp(sep->arg[1], "help") == 0) { - std::string window_title = "Inventory Snapshot Argument Help Menu"; - - std::string window_text = - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - ""; - - if (c->Admin() >= commandInvSnapshot) - window_text.append( - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - ); - - window_text.append( - "
Usage:#invsnapshot arguments
(required optional)
helpthis menu
capturetakes snapshot of character inventory
gcountreturns global snapshot count
gclear
now
delete all snapshots - rule
delete all snapshots - now
countreturns character snapshot count
clear
now
delete character snapshots - rule
delete character snapshots - now
list
count
lists entry ids for current character
limits to count
parsetstmpdisplays slots and items in snapshot
comparetstmpcompares inventory against snapshot
restoretstmprestores slots and items in snapshot
" - ); - - c->SendPopupToClient(window_title.c_str(), window_text.c_str()); - - return; - } - - if (c->Admin() >= commandInvSnapshot) { // global arguments - - if (strcmp(sep->arg[1], "gcount") == 0) { - auto is_count = database.CountInvSnapshots(); - c->Message(Chat::White, "There %s %i inventory snapshot%s.", (is_count == 1 ? "is" : "are"), is_count, (is_count == 1 ? "" : "s")); - - return; - } - - if (strcmp(sep->arg[1], "gclear") == 0) { - if (strcmp(sep->arg[2], "now") == 0) { - database.ClearInvSnapshots(true); - c->Message(Chat::White, "Inventory snapshots cleared using current time."); - } - else { - database.ClearInvSnapshots(); - c->Message(Chat::White, "Inventory snapshots cleared using RuleI(Character, InvSnapshotHistoryD) (%i day%s).", - RuleI(Character, InvSnapshotHistoryD), (RuleI(Character, InvSnapshotHistoryD) == 1 ? "" : "s")); - } - - return; - } - } - - if (!c->GetTarget() || !c->GetTarget()->IsClient()) { - c->Message(Chat::White, "Target must be a client."); - return; - } - - auto tc = (Client*)c->GetTarget(); - - if (strcmp(sep->arg[1], "capture") == 0) { - if (database.SaveCharacterInvSnapshot(tc->CharacterID())) { - tc->SetNextInvSnapshot(RuleI(Character, InvSnapshotMinIntervalM)); - c->Message(Chat::White, "Successful inventory snapshot taken of %s - setting next interval for %i minute%s.", - tc->GetName(), RuleI(Character, InvSnapshotMinIntervalM), (RuleI(Character, InvSnapshotMinIntervalM) == 1 ? "" : "s")); - } - else { - tc->SetNextInvSnapshot(RuleI(Character, InvSnapshotMinRetryM)); - c->Message(Chat::White, "Failed to take inventory snapshot of %s - retrying in %i minute%s.", - tc->GetName(), RuleI(Character, InvSnapshotMinRetryM), (RuleI(Character, InvSnapshotMinRetryM) == 1 ? "" : "s")); - } - - return; - } - - if (c->Admin() >= commandInvSnapshot) { - if (strcmp(sep->arg[1], "count") == 0) { - auto is_count = database.CountCharacterInvSnapshots(tc->CharacterID()); - c->Message(Chat::White, "%s (id: %u) has %i inventory snapshot%s.", tc->GetName(), tc->CharacterID(), is_count, (is_count == 1 ? "" : "s")); - - return; - } - - if (strcmp(sep->arg[1], "clear") == 0) { - if (strcmp(sep->arg[2], "now") == 0) { - database.ClearCharacterInvSnapshots(tc->CharacterID(), true); - c->Message(Chat::White, "%s\'s (id: %u) inventory snapshots cleared using current time.", tc->GetName(), tc->CharacterID()); - } - else { - database.ClearCharacterInvSnapshots(tc->CharacterID()); - c->Message(Chat::White, "%s\'s (id: %u) inventory snapshots cleared using RuleI(Character, InvSnapshotHistoryD) (%i day%s).", - tc->GetName(), tc->CharacterID(), RuleI(Character, InvSnapshotHistoryD), (RuleI(Character, InvSnapshotHistoryD) == 1 ? "" : "s")); - } - - return; - } - - if (strcmp(sep->arg[1], "list") == 0) { - std::list> is_list; - database.ListCharacterInvSnapshots(tc->CharacterID(), is_list); - - if (is_list.empty()) { - c->Message(Chat::White, "No inventory snapshots for %s (id: %u)", tc->GetName(), tc->CharacterID()); - return; - } - - auto list_count = 0; - if (sep->IsNumber(2)) - list_count = atoi(sep->arg[2]); - if (list_count < 1 || list_count > is_list.size()) - list_count = is_list.size(); - - std::string window_title = StringFormat("Snapshots for %s", tc->GetName()); - - std::string window_text = - "" - "" - "" - "" - ""; - - for (auto iter : is_list) { - if (!list_count) - break; - - window_text.append(StringFormat( - "" - "" - "" - "", - iter.first, - iter.second - )); - - --list_count; - } - - window_text.append( - "
TimestampEntry Count
%u%i
" - ); - - c->SendPopupToClient(window_title.c_str(), window_text.c_str()); - - return; - } - - if (strcmp(sep->arg[1], "parse") == 0) { - if (!sep->IsNumber(2)) { - c->Message(Chat::White, "A timestamp is required to use this option."); - return; - } - - uint32 timestamp = atoul(sep->arg[2]); - - if (!database.ValidateCharacterInvSnapshotTimestamp(tc->CharacterID(), timestamp)) { - c->Message(Chat::White, "No inventory snapshots for %s (id: %u) exist at %u.", tc->GetName(), tc->CharacterID(), timestamp); - return; - } - - std::list> parse_list; - database.ParseCharacterInvSnapshot(tc->CharacterID(), timestamp, parse_list); - - std::string window_title = StringFormat("Snapshot Parse for %s @ %u", tc->GetName(), timestamp); - - std::string window_text = "Slot: ItemID - Description
"; - - for (auto iter : parse_list) { - auto item_data = database.GetItem(iter.second); - std::string window_line = StringFormat("%i: %u - %s
", iter.first, iter.second, (item_data ? item_data->Name : "[error]")); - - if (window_text.length() + window_line.length() < 4095) { - window_text.append(window_line); - } - else { - c->Message(Chat::White, "Too many snapshot entries to list..."); - break; - } - } - - c->SendPopupToClient(window_title.c_str(), window_text.c_str()); - - return; - } - - if (strcmp(sep->arg[1], "compare") == 0) { - if (!sep->IsNumber(2)) { - c->Message(Chat::White, "A timestamp is required to use this option."); - return; - } - - uint32 timestamp = atoul(sep->arg[2]); - - if (!database.ValidateCharacterInvSnapshotTimestamp(tc->CharacterID(), timestamp)) { - c->Message(Chat::White, "No inventory snapshots for %s (id: %u) exist at %u.", tc->GetName(), tc->CharacterID(), timestamp); - return; - } - - std::list> inv_compare_list; - database.DivergeCharacterInventoryFromInvSnapshot(tc->CharacterID(), timestamp, inv_compare_list); - - std::list> iss_compare_list; - database.DivergeCharacterInvSnapshotFromInventory(tc->CharacterID(), timestamp, iss_compare_list); - - std::string window_title = StringFormat("Snapshot Comparison for %s @ %u", tc->GetName(), timestamp); - - std::string window_text = "Slot: (action) Snapshot -> Inventory
"; - - auto inv_iter = inv_compare_list.begin(); - auto iss_iter = iss_compare_list.begin(); - - while (true) { - std::string window_line; - - if (inv_iter == inv_compare_list.end() && iss_iter == iss_compare_list.end()) { - break; - } - else if (inv_iter != inv_compare_list.end() && iss_iter == iss_compare_list.end()) { - window_line = StringFormat("%i: (delete) [empty] -> %u
", inv_iter->first, inv_iter->second); - ++inv_iter; - } - else if (inv_iter == inv_compare_list.end() && iss_iter != iss_compare_list.end()) { - window_line = StringFormat("%i: (insert) %u -> [empty]
", iss_iter->first, iss_iter->second); - ++iss_iter; - } - else { - if (inv_iter->first < iss_iter->first) { - window_line = StringFormat("%i: (delete) [empty] -> %u
", inv_iter->first, inv_iter->second); - ++inv_iter; - } - else if (inv_iter->first > iss_iter->first) { - window_line = StringFormat("%i: (insert) %u -> [empty]
", iss_iter->first, iss_iter->second); - ++iss_iter; - } - else { - window_line = StringFormat("%i: (replace) %u -> %u
", iss_iter->first, iss_iter->second, inv_iter->second); - ++inv_iter; - ++iss_iter; - } - } - - if (window_text.length() + window_line.length() < 4095) { - window_text.append(window_line); - } - else { - c->Message(Chat::White, "Too many comparison entries to list..."); - break; - } - } - - c->SendPopupToClient(window_title.c_str(), window_text.c_str()); - - return; - } - - if (strcmp(sep->arg[1], "restore") == 0) { - if (!sep->IsNumber(2)) { - c->Message(Chat::White, "A timestamp is required to use this option."); - return; - } - - uint32 timestamp = atoul(sep->arg[2]); - - if (!database.ValidateCharacterInvSnapshotTimestamp(tc->CharacterID(), timestamp)) { - c->Message(Chat::White, "No inventory snapshots for %s (id: %u) exist at %u.", tc->GetName(), tc->CharacterID(), timestamp); - return; - } - - if (database.SaveCharacterInvSnapshot(tc->CharacterID())) { - tc->SetNextInvSnapshot(RuleI(Character, InvSnapshotMinIntervalM)); - } - else { - c->Message(Chat::Red, "Failed to take pre-restore inventory snapshot of %s (id: %u).", - tc->GetName(), tc->CharacterID()); - return; - } - - if (database.RestoreCharacterInvSnapshot(tc->CharacterID(), timestamp)) { - // cannot delete all valid item slots from client..so, we worldkick - tc->WorldKick(); // self restores update before the 'kick' is processed - - c->Message(Chat::White, "Successfully applied snapshot %u to %s's (id: %u) inventory.", - timestamp, tc->GetName(), tc->CharacterID()); - } - else { - c->Message(Chat::Red, "Failed to apply snapshot %u to %s's (id: %u) inventory.", - timestamp, tc->GetName(), tc->CharacterID()); - } - - return; - } - } -} - -void command_findnpctype(Client *c, const Seperator *sep) -{ - if(sep->arg[1][0] == 0) { - c->Message(Chat::White, "Usage: #findnpctype [search criteria]"); - return; - } - - std::string query; - - int id = atoi((const char *)sep->arg[1]); - if (id == 0) // If id evaluates to 0, then search as if user entered a string. - query = StringFormat("SELECT id, name FROM npc_types WHERE name LIKE '%%%s%%'", sep->arg[1]); - else // Otherwise, look for just that npc id. - query = StringFormat("SELECT id, name FROM npc_types WHERE id = %i", id); - - auto results = content_db.QueryDatabase(query); - if (!results.Success()) { - c->Message (0, "Error querying database."); - c->Message (0, query.c_str()); - } - - if (results.RowCount() == 0) // No matches found. - c->Message (0, "No matches found for %s.", sep->arg[1]); - - // If query runs successfully. - int count = 0; - const int maxrows = 20; - - // Process each row returned. - for (auto row = results.begin(); row != results.end(); ++row) { - // Limit to returning maxrows rows. - if (++count > maxrows) { - c->Message (0, "%i npc types shown. Too many results.", maxrows); - break; - } - - c->Message (0, " %s: %s", row[0], row[1]); - } - - // If we did not hit the maxrows limit. - if (count <= maxrows) - c->Message (0, "Query complete. %i rows shown.", count); - -} - -void command_faction(Client *c, const Seperator *sep) -{ - if (sep->arg[1][0] == 0) { - c->Message(Chat::White, "Usage: #faction -- Displays Target NPC's Primary faction"); - c->Message(Chat::White, "Usage: #faction Find [criteria | all] -- Displays factions name & id"); - c->Message(Chat::White, "Usage: #faction Review [criteria | all] -- Review Targeted Players faction hits"); - c->Message(Chat::White, "Usage: #faction Reset [id] -- Reset Targeted Players specified faction to base"); - uint32 npcfac; - std::string npcname; - if (c->GetTarget() && c->GetTarget()->IsNPC()) { - npcfac = c->GetTarget()->CastToNPC()->GetPrimaryFaction(); - npcname = c->GetTarget()->CastToNPC()->GetCleanName(); - std::string blurb = fmt::format("( Target Npc: {} : has primary faction id: {} )", npcname, npcfac); - c->Message(Chat::Yellow, blurb.c_str()); - c->Message(Chat::White, "Use: #setfaction [id] - to alter an NPC's faction"); - } - return; - } - - std::string faction_filter; - if (sep->arg[2]) { - faction_filter = str_tolower(sep->arg[2]); - } - if (strcasecmp(sep->arg[1], "find") == 0) { - std::string query; - if (strcasecmp(sep->arg[2], "all") == 0) { - - query = "SELECT `id`,`name` FROM `faction_list`"; - } - else { - query = fmt::format("SELECT `id`,`name` FROM `faction_list` WHERE `name` LIKE '%{}%'", faction_filter.c_str()); - } - auto results = content_db.QueryDatabase(query); - if (!results.Success()) - return; - if (results.RowCount() == 0) { - c->Message(Chat::Yellow, "No factions found with specified criteria"); - return; - } - int _ctr = 0; - for (auto row = results.begin(); row != results.end(); ++row) { - auto id = static_cast(atoi(row[0])); - std::string name = row[1]; - _ctr++; - c->Message(Chat::Yellow, "%s : id: %s", name.c_str(), std::to_string(id).c_str()); - } - std::string response = _ctr > 0 ? fmt::format("Found {} matching factions", _ctr).c_str() : "No factions found."; - c->Message(Chat::Yellow, response.c_str()); - } - if (strcasecmp(sep->arg[1], "review") == 0) { - if (!(c->GetTarget() && c->GetTarget()->IsClient())) { - c->Message(Chat::Red, "Player Target Required for faction review"); - return; - } - uint32 charid = c->GetTarget()->CastToClient()->CharacterID(); - std::string revquery; - if (strcasecmp(sep->arg[2], "all") == 0) { - revquery = fmt::format( - "SELECT id,`name`, current_value FROM faction_list INNER JOIN faction_values ON faction_list.id = faction_values.faction_id WHERE char_id = {}", charid); - } - else - { - revquery = fmt::format( - "SELECT id,`name`, current_value FROM faction_list INNER JOIN faction_values ON faction_list.id = faction_values.faction_id WHERE `name` like '%{}%' and char_id = {}", faction_filter.c_str(), charid); - } - auto revresults = content_db.QueryDatabase(revquery); - if (!revresults.Success()) - return; - if (revresults.RowCount() == 0) { - c->Message(Chat::Yellow, "No faction hits found. All are at base level"); - return; - } - int _ctr2 = 0; - for (auto rrow = revresults.begin(); rrow != revresults.end(); ++rrow) { - auto f_id = static_cast(atoi(rrow[0])); - std::string cname = rrow[1]; - std::string fvalue = rrow[2]; - _ctr2++; - std::string resetlink = fmt::format("#faction reset {}", f_id); - c->Message(Chat::Yellow, "Reset: %s id: %s (%s)", EQ::SayLinkEngine::GenerateQuestSaylink(resetlink, false, cname.c_str()).c_str(), std::to_string(f_id).c_str(), fvalue.c_str()); - } - std::string response = _ctr2 > 0 ? fmt::format("Found {} matching factions", _ctr2).c_str() : "No faction hits found."; - c->Message(Chat::Yellow, response.c_str()); - } - else if (strcasecmp(sep->arg[1], "reset") == 0) - { - if (!(faction_filter == "")) { - if (c->GetTarget() && c->GetTarget()->IsClient()) - { - if (!c->CastToClient()->GetFeigned() && c->CastToClient()->GetAggroCount() == 0) - { - uint32 charid = c->GetTarget()->CastToClient()->CharacterID(); - uint32 factionid = atoi(faction_filter.c_str()); - - if (c->GetTarget()->CastToClient()->ReloadCharacterFaction(c->GetTarget()->CastToClient(), factionid, charid)) - c->Message(Chat::Yellow, "faction %u was cleared.", factionid); - else - c->Message(Chat::Red, "An error occurred clearing faction %u", factionid); - } - else - { - c->Message(Chat::Red, "Cannot be in Combat"); - return; - } - } - else { - c->Message(Chat::Red, "Player Target Required (whose not feigning death)"); - return; - } - } - else - c->Message(Chat::Red, "No faction id entered"); - } -} - -void command_findzone(Client *c, const Seperator *sep) -{ - if (sep->arg[1][0] == 0) { - c->Message(Chat::White, "Usage: #findzone [search criteria]"); - c->Message(Chat::White, "Usage: #findzone expansion [expansion number]"); - return; - } - - std::string query; - int id = atoi((const char *) sep->arg[1]); - - std::string arg1 = sep->arg[1]; - - if (arg1 == "expansion") { - query = fmt::format( - "SELECT zoneidnumber, short_name, long_name, version FROM zone WHERE expansion = {}", - sep->arg[2] - ); - } - else { - - /** - * If id evaluates to 0, then search as if user entered a string - */ - if (id == 0) { - query = fmt::format( - "SELECT zoneidnumber, short_name, long_name, version FROM zone WHERE long_name LIKE '%{}%' OR `short_name` LIKE '%{}%'", - EscapeString(sep->arg[1]), - EscapeString(sep->arg[1]) - ); - } - else { - query = fmt::format( - "SELECT zoneidnumber, short_name, long_name, version FROM zone WHERE zoneidnumber = {}", - id - ); - } - } - - auto results = content_db.QueryDatabase(query); - if (!results.Success()) { - c->Message(Chat::White, "Error querying database."); - c->Message(Chat::White, query.c_str()); - return; - } - - int count = 0; - const int maxrows = 100; - - for (auto row = results.begin(); row != results.end(); ++row) { - std::string zone_id = row[0]; - std::string short_name = row[1]; - std::string long_name = row[2]; - int version = atoi(row[3]); - - if (++count > maxrows) { - c->Message(Chat::White, "%i zones shown. Too many results.", maxrows); - break; - } - - std::string command_zone = EQ::SayLinkEngine::GenerateQuestSaylink("#zone " + short_name, false, "zone"); - std::string command_gmzone = EQ::SayLinkEngine::GenerateQuestSaylink( - fmt::format("#gmzone {} {}", short_name, version), - false, - "gmzone" - ); - - c->Message( - Chat::White, - fmt::format( - "[{}] [{}] [{}] ID ({}) Version ({}) [{}]", - (version == 0 ? command_zone : "zone"), - command_gmzone, - short_name, - zone_id, - version, - long_name - ).c_str() - ); - } - - if (count <= maxrows) { - c->Message( - Chat::White, - "Query complete. %i rows shown. %s", - count, - (arg1 == "expansion" ? "(expansion search)" : "")); - } - else if (count == 0) { - c->Message(Chat::White, "No matches found for %s.", sep->arg[1]); - } -} - -void command_viewnpctype(Client *c, const Seperator *sep) -{ - if (!sep->IsNumber(1)) - c->Message(Chat::White, "Usage: #viewnpctype [npctype id]"); - else - { - uint32 npctypeid=atoi(sep->arg[1]); - const NPCType* npct = content_db.LoadNPCTypesData(npctypeid); - if (npct) { - c->Message(Chat::White, " NPCType Info, "); - c->Message(Chat::White, " NPCTypeID: %u", npct->npc_id); - c->Message(Chat::White, " Name: %s", npct->name); - c->Message(Chat::White, " Level: %i", npct->level); - c->Message(Chat::White, " Race: %i", npct->race); - c->Message(Chat::White, " Class: %i", npct->class_); - c->Message(Chat::White, " MinDmg: %i", npct->min_dmg); - c->Message(Chat::White, " MaxDmg: %i", npct->max_dmg); - c->Message(Chat::White, " Special Abilities: %s", npct->special_abilities); - c->Message(Chat::White, " Spells: %i", npct->npc_spells_id); - c->Message(Chat::White, " Loot Table: %i", npct->loottable_id); - c->Message(Chat::White, " NPCFactionID: %i", npct->npc_faction_id); - } - else - c->Message(Chat::White, "NPC #%d not found", npctypeid); - } -} - -void command_reloadqst(Client *c, const Seperator *sep) -{ - if (sep->arg[1][0] == 0) - { - c->Message(Chat::White, "Clearing quest memory cache."); - entity_list.ClearAreas(); - parse->ReloadQuests(); - } - else - { - c->Message(Chat::White, "Clearing quest memory cache and stopping timers."); - entity_list.ClearAreas(); - parse->ReloadQuests(true); - } - -} - -void command_corpsefix(Client *c, const Seperator *sep) -{ - entity_list.CorpseFix(c); -} - -void command_reloadworld(Client *c, const Seperator *sep) -{ - int world_repop = atoi(sep->arg[1]); - if (world_repop == 0) - c->Message(Chat::White, "Reloading quest cache worldwide."); - else - c->Message(Chat::White, "Reloading quest cache and repopping zones worldwide."); - - auto pack = new ServerPacket(ServerOP_ReloadWorld, sizeof(ReloadWorld_Struct)); - ReloadWorld_Struct* RW = (ReloadWorld_Struct*) pack->pBuffer; - RW->Option = world_repop; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void command_reloadmerchants(Client *c, const Seperator *sep) { - entity_list.ReloadMerchants(); - c->Message(Chat::Yellow, "Reloading merchants."); -} - -void command_reloadlevelmods(Client *c, const Seperator *sep) -{ - if (sep->arg[1][0] == 0) - { - if(RuleB(Zone, LevelBasedEXPMods)){ - zone->LoadLevelEXPMods(); - c->Message(Chat::Yellow, "Level based EXP Mods have been reloaded zonewide"); - }else{ - c->Message(Chat::Yellow, "Level based EXP Mods are disabled in rules!"); - } - } -} - -void command_reloadzps(Client *c, const Seperator *sep) -{ - content_db.LoadStaticZonePoints(&zone->zone_point_list, zone->GetShortName(), zone->GetInstanceVersion()); - c->Message(Chat::White, "Reloading server zone_points."); -} - -void command_zoneshutdown(Client *c, const Seperator *sep) -{ - if (!worldserver.Connected()) - c->Message(Chat::White, "Error: World server disconnected"); - else if (sep->arg[1][0] == 0) - c->Message(Chat::White, "Usage: #zoneshutdown zoneshortname"); - else { - auto pack = new ServerPacket(ServerOP_ZoneShutdown, sizeof(ServerZoneStateChange_struct)); - ServerZoneStateChange_struct* s = (ServerZoneStateChange_struct *) pack->pBuffer; - strcpy(s->adminname, c->GetName()); - if (sep->arg[1][0] >= '0' && sep->arg[1][0] <= '9') - s->ZoneServerID = atoi(sep->arg[1]); - else - s->zoneid = ZoneID(sep->arg[1]); - worldserver.SendPacket(pack); - safe_delete(pack); - } -} - -void command_zonebootup(Client *c, const Seperator *sep) -{ - if (!worldserver.Connected()) - c->Message(Chat::White, "Error: World server disconnected"); - else if (sep->arg[2][0] == 0) { - c->Message(Chat::White, "Usage: #zonebootup ZoneServerID# zoneshortname"); - } - else { - auto pack = new ServerPacket(ServerOP_ZoneBootup, sizeof(ServerZoneStateChange_struct)); - ServerZoneStateChange_struct* s = (ServerZoneStateChange_struct *) pack->pBuffer; - s->ZoneServerID = atoi(sep->arg[1]); - strcpy(s->adminname, c->GetName()); - s->zoneid = ZoneID(sep->arg[2]); - s->makestatic = (bool) (strcasecmp(sep->arg[3], "static") == 0); - worldserver.SendPacket(pack); - safe_delete(pack); - } -} - -void command_kick(Client *c, const Seperator *sep) -{ - if (sep->arg[1][0] == 0) - c->Message(Chat::White, "Usage: #kick [charname]"); - else { - Client* client = entity_list.GetClientByName(sep->arg[1]); - if (client != 0) { - if (client->Admin() <= c->Admin()) { - client->Message(Chat::White, "You have been kicked by %s", c->GetName()); - auto outapp = new EQApplicationPacket(OP_GMKick, 0); - client->QueuePacket(outapp); - client->Kick("Ordered kicked by command"); - c->Message(Chat::White, "Kick: local: kicking %s", sep->arg[1]); - } - } - else if (!worldserver.Connected()) - c->Message(Chat::White, "Error: World server disconnected"); - else { - auto pack = new ServerPacket(ServerOP_KickPlayer, sizeof(ServerKickPlayer_Struct)); - ServerKickPlayer_Struct* skp = (ServerKickPlayer_Struct*) pack->pBuffer; - strcpy(skp->adminname, c->GetName()); - strcpy(skp->name, sep->arg[1]); - skp->adminrank = c->Admin(); - worldserver.SendPacket(pack); - safe_delete(pack); - } - } -} - -void command_attack(Client *c, const Seperator *sep) -{ - if (c->GetTarget() && c->GetTarget()->IsNPC() && sep->arg[1] != 0) { - Mob* sictar = entity_list.GetMob(sep->argplus[1]); - if (sictar) - c->GetTarget()->CastToNPC()->AddToHateList(sictar, 1, 0); - else - c->Message(Chat::White, "Error: %s not found", sep->arg[1]); - } - else - c->Message(Chat::White, "Usage: (needs NPC targeted) #attack targetname"); -} - -void command_lock(Client *c, const Seperator *sep) -{ - auto outpack = new ServerPacket(ServerOP_Lock, sizeof(ServerLock_Struct)); - ServerLock_Struct* lss = (ServerLock_Struct*) outpack->pBuffer; - strcpy(lss->myname, c->GetName()); - lss->mode = 1; - worldserver.SendPacket(outpack); - safe_delete(outpack); -} - -void command_unlock(Client *c, const Seperator *sep) -{ - auto outpack = new ServerPacket(ServerOP_Lock, sizeof(ServerLock_Struct)); - ServerLock_Struct* lss = (ServerLock_Struct*) outpack->pBuffer; - strcpy(lss->myname, c->GetName()); - lss->mode = 0; - worldserver.SendPacket(outpack); - safe_delete(outpack); -} - -void command_motd(Client *c, const Seperator *sep) -{ - auto outpack = new ServerPacket(ServerOP_Motd, sizeof(ServerMotd_Struct)); - ServerMotd_Struct* mss = (ServerMotd_Struct*) outpack->pBuffer; - strn0cpy(mss->myname, c->GetName(),64); - strn0cpy(mss->motd, sep->argplus[1],512); - worldserver.SendPacket(outpack); - safe_delete(outpack); -} - -void command_listpetition(Client *c, const Seperator *sep) -{ - std::string query = "SELECT petid, charname, accountname FROM petitions ORDER BY petid"; - auto results = database.QueryDatabase(query); - if (!results.Success()) - return; - - LogInfo("Petition list requested by [{}]", c->GetName()); - - if (results.RowCount() == 0) - return; - - c->Message(Chat::Red," ID : Character Name , Account Name"); - - for (auto row = results.begin(); row != results.end(); ++row) - c->Message(Chat::Yellow, " %s: %s , %s ", row[0],row[1],row[2]); -} - -void command_equipitem(Client *c, const Seperator *sep) -{ - uint32 slot_id = atoi(sep->arg[1]); - if (sep->IsNumber(1) && (slot_id >= EQ::invslot::EQUIPMENT_BEGIN && slot_id <= EQ::invslot::EQUIPMENT_END)) { - const EQ::ItemInstance* from_inst = c->GetInv().GetItem(EQ::invslot::slotCursor); - const EQ::ItemInstance* to_inst = c->GetInv().GetItem(slot_id); // added (desync issue when forcing stack to stack) - bool partialmove = false; - int16 movecount; - - if (from_inst && from_inst->IsClassCommon()) { - auto outapp = new EQApplicationPacket(OP_MoveItem, sizeof(MoveItem_Struct)); - MoveItem_Struct* mi = (MoveItem_Struct*)outapp->pBuffer; - mi->from_slot = EQ::invslot::slotCursor; - mi->to_slot = slot_id; - // mi->number_in_stack = from_inst->GetCharges(); // replaced with con check for stacking - - // crude stackable check to only 'move' the difference count on client instead of entire stack when applicable - if (to_inst && to_inst->IsStackable() && - (to_inst->GetItem()->ID == from_inst->GetItem()->ID) && - (to_inst->GetCharges() < to_inst->GetItem()->StackSize) && - (from_inst->GetCharges() > to_inst->GetItem()->StackSize - to_inst->GetCharges())) { - movecount = to_inst->GetItem()->StackSize - to_inst->GetCharges(); - mi->number_in_stack = (uint32)movecount; - partialmove = true; - } - else - mi->number_in_stack = from_inst->GetCharges(); - - // Save move changes - // Added conditional check to packet send..would have sent change even on a swap failure..whoops! - - if (partialmove) { // remove this con check if someone can figure out removing charges from cursor stack issue below - // mi->number_in_stack is always from_inst->GetCharges() when partialmove is false - c->Message(Chat::Red, "Error: Partial stack added to existing stack exceeds allowable stacksize"); - safe_delete(outapp); - return; - } - else if(c->SwapItem(mi)) { - c->FastQueuePacket(&outapp); - - // if the below code is still needed..just send an an item trade packet to each slot..it should overwrite the client instance - - // below code has proper logic, but client does not like to have cursor charges changed - // (we could delete the cursor item and resend, but issues would arise if there are queued items) - //if (partialmove) { - // EQApplicationPacket* outapp2 = new EQApplicationPacket(OP_DeleteItem, sizeof(DeleteItem_Struct)); - // DeleteItem_Struct* di = (DeleteItem_Struct*)outapp2->pBuffer; - // di->from_slot = SLOT_CURSOR; - // di->to_slot = 0xFFFFFFFF; - // di->number_in_stack = 0xFFFFFFFF; - - // c->Message(Chat::White, "Deleting %i charges from stack", movecount); // debug line..delete - - // for (int16 deletecount=0; deletecount < movecount; deletecount++) - // have to use 'movecount' because mi->number_in_stack is 'ENCODED' at this point (i.e., 99 charges returns 22...) - // c->QueuePacket(outapp2); - - // safe_delete(outapp2); - //} - } - else { - c->Message(Chat::Red, "Error: Unable to equip current item"); - } - safe_delete(outapp); - - // also send out a wear change packet? - } - else if (from_inst == nullptr) - c->Message(Chat::Red, "Error: There is no item on your cursor"); - else - c->Message(Chat::Red, "Error: Item on your cursor cannot be equipped"); - } - else - c->Message(Chat::White, "Usage: #equipitem slotid[0-21] - equips the item on your cursor to the position"); -} - -void command_zonelock(Client *c, const Seperator *sep) -{ - auto pack = new ServerPacket(ServerOP_LockZone, sizeof(ServerLockZone_Struct)); - ServerLockZone_Struct* s = (ServerLockZone_Struct*) pack->pBuffer; - strn0cpy(s->adminname, c->GetName(), sizeof(s->adminname)); - if (strcasecmp(sep->arg[1], "list") == 0) { - s->op = 0; - worldserver.SendPacket(pack); - } - else if (strcasecmp(sep->arg[1], "lock") == 0 && c->Admin() >= commandLockZones) { - uint16 tmp = ZoneID(sep->arg[2]); - if (tmp) { - s->op = 1; - s->zoneID = tmp; - worldserver.SendPacket(pack); - } - else - c->Message(Chat::White, "Usage: #zonelock lock [zonename]"); - } - else if (strcasecmp(sep->arg[1], "unlock") == 0 && c->Admin() >= commandLockZones) { - uint16 tmp = ZoneID(sep->arg[2]); - if (tmp) { - s->op = 2; - s->zoneID = tmp; - worldserver.SendPacket(pack); - } - else - c->Message(Chat::White, "Usage: #zonelock unlock [zonename]"); - } - else { - c->Message(Chat::White, "#zonelock sub-commands"); - c->Message(Chat::White, " list"); - if(c->Admin() >= commandLockZones) - { - c->Message(Chat::White, " lock [zonename]"); - c->Message(Chat::White, " unlock [zonename]"); - } - } - safe_delete(pack); -} - -void command_copycharacter(Client *c, const Seperator *sep) -{ - if (sep->argnum < 3) { - c->Message( - Chat::White, - "Usage: [source_character_name] [destination_character_name] [destination_account_name]" - ); - return; - } - - std::string source_character_name = sep->arg[1]; - std::string destination_character_name = sep->arg[2]; - std::string destination_account_name = sep->arg[3]; - - bool result = database.CopyCharacter( - source_character_name, - destination_character_name, - destination_account_name - ); - - c->Message( - Chat::Yellow, - fmt::format( - "Character Copy [{}] to [{}] via account [{}] [{}]", - source_character_name, - destination_character_name, - destination_account_name, - result ? "Success" : "Failed" - ).c_str() - ); -} - -void command_corpse(Client *c, const Seperator *sep) -{ - Mob *target=c->GetTarget(); - - if (strcasecmp(sep->arg[1], "DeletePlayerCorpses") == 0 && c->Admin() >= commandEditPlayerCorpses) { - int32 tmp = entity_list.DeletePlayerCorpses(); - if (tmp >= 0) - c->Message(Chat::White, "%i corpses deleted.", tmp); - else - c->Message(Chat::White, "DeletePlayerCorpses Error #%i", tmp); - } - else if (strcasecmp(sep->arg[1], "delete") == 0) { - if (target == 0 || !target->IsCorpse()) - c->Message(Chat::White, "Error: Target the corpse you wish to delete"); - else if (target->IsNPCCorpse()) { - - c->Message(Chat::White, "Depoping %s.", target->GetName()); - target->CastToCorpse()->Delete(); - } - else if (c->Admin() >= commandEditPlayerCorpses) { - c->Message(Chat::White, "Deleting %s.", target->GetName()); - target->CastToCorpse()->Delete(); - } - else - c->Message(Chat::White, "Insufficient status to delete player corpse."); - } - else if (strcasecmp(sep->arg[1], "ListNPC") == 0) { - entity_list.ListNPCCorpses(c); - } - else if (strcasecmp(sep->arg[1], "ListPlayer") == 0) { - entity_list.ListPlayerCorpses(c); - } - else if (strcasecmp(sep->arg[1], "DeleteNPCCorpses") == 0) { - int32 tmp = entity_list.DeleteNPCCorpses(); - if (tmp >= 0) - c->Message(Chat::White, "%d corpses deleted.", tmp); - else - c->Message(Chat::White, "DeletePlayerCorpses Error #%d", tmp); - } - else if (strcasecmp(sep->arg[1], "charid") == 0 && c->Admin() >= commandEditPlayerCorpses) { - if (target == 0 || !target->IsPlayerCorpse()) - c->Message(Chat::White, "Error: Target must be a player corpse."); - else if (!sep->IsNumber(2)) - c->Message(Chat::White, "Error: charid must be a number."); - else - c->Message(Chat::White, "Setting CharID=%u on PlayerCorpse '%s'", target->CastToCorpse()->SetCharID(atoi(sep->arg[2])), target->GetName()); - } - else if (strcasecmp(sep->arg[1], "ResetLooter") == 0) { - if (target == 0 || !target->IsCorpse()) - c->Message(Chat::White, "Error: Target the corpse you wish to reset"); - else - target->CastToCorpse()->ResetLooter(); - } - else if (strcasecmp(sep->arg[1], "RemoveCash") == 0) { - if (target == 0 || !target->IsCorpse()) - c->Message(Chat::White, "Error: Target the corpse you wish to remove the cash from"); - else if (!target->IsPlayerCorpse() || c->Admin() >= commandEditPlayerCorpses) { - c->Message(Chat::White, "Removing Cash from %s.", target->GetName()); - target->CastToCorpse()->RemoveCash(); - } - else - c->Message(Chat::White, "Insufficient status to modify player corpse."); - } - else if (strcasecmp(sep->arg[1], "InspectLoot") == 0) { - if (target == 0 || !target->IsCorpse()) - c->Message(Chat::White, "Error: Target must be a corpse."); - else - target->CastToCorpse()->QueryLoot(c); - } - else if (strcasecmp(sep->arg[1], "lock") == 0) { - if (target == 0 || !target->IsCorpse()) - c->Message(Chat::White, "Error: Target must be a corpse."); - else { - target->CastToCorpse()->Lock(); - c->Message(Chat::White, "Locking %s...", target->GetName()); - } - } - else if (strcasecmp(sep->arg[1], "unlock") == 0) { - if (target == 0 || !target->IsCorpse()) - c->Message(Chat::White, "Error: Target must be a corpse."); - else { - target->CastToCorpse()->UnLock(); - c->Message(Chat::White, "Unlocking %s...", target->GetName()); - } - } - else if (strcasecmp(sep->arg[1], "depop") == 0) { - if (target == 0 || !target->IsPlayerCorpse()) - c->Message(Chat::White, "Error: Target must be a player corpse."); - else if (c->Admin() >= commandEditPlayerCorpses && target->IsPlayerCorpse()) { - c->Message(Chat::White, "Depoping %s.", target->GetName()); - target->CastToCorpse()->DepopPlayerCorpse(); - if(!sep->arg[2][0] || atoi(sep->arg[2]) != 0) - target->CastToCorpse()->Bury(); - } - else - c->Message(Chat::White, "Insufficient status to depop player corpse."); - } - else if (strcasecmp(sep->arg[1], "depopall") == 0) { - if (target == 0 || !target->IsClient()) - c->Message(Chat::White, "Error: Target must be a player."); - else if (c->Admin() >= commandEditPlayerCorpses && target->IsClient()) { - c->Message(Chat::White, "Depoping %s\'s corpses.", target->GetName()); - target->CastToClient()->DepopAllCorpses(); - if(!sep->arg[2][0] || atoi(sep->arg[2]) != 0) - target->CastToClient()->BuryPlayerCorpses(); - } - else - c->Message(Chat::White, "Insufficient status to depop player corpse."); - - } - else if (strcasecmp(sep->arg[1], "moveallgraveyard") == 0) { - int count = entity_list.MovePlayerCorpsesToGraveyard(true); - c->Message(Chat::White, "Moved [%d] player corpse(s) to zone graveyard", count); - } - else if (sep->arg[1][0] == 0 || strcasecmp(sep->arg[1], "help") == 0) { - c->Message(Chat::White, "#Corpse Sub-Commands:"); - c->Message(Chat::White, " DeleteNPCCorpses"); - c->Message(Chat::White, " Delete - Delete targetted corpse"); - c->Message(Chat::White, " ListNPC"); - c->Message(Chat::White, " ListPlayer"); - c->Message(Chat::White, " Lock - GM locks the corpse - cannot be looted by non-GM"); - c->Message(Chat::White, " MoveAllGraveyard - move all player corpses to zone's graveyard or non-instance"); - c->Message(Chat::White, " UnLock"); - c->Message(Chat::White, " RemoveCash"); - c->Message(Chat::White, " InspectLoot"); - c->Message(Chat::White, " [to remove items from corpses, loot them]"); - c->Message(Chat::White, "Lead-GM status required to delete/modify player corpses"); - c->Message(Chat::White, " DeletePlayerCorpses"); - c->Message(Chat::White, " CharID [charid] - change player corpse's owner"); - c->Message(Chat::White, " Depop [bury] - Depops single target corpse."); - c->Message(Chat::White, " Depopall [bury] - Depops all target player's corpses."); - c->Message(Chat::White, "Set bury to 0 to skip burying the corpses."); - } - else - c->Message(Chat::White, "Error, #corpse sub-command not found"); -} - -void command_fixmob(Client *c, const Seperator *sep) -{ - Mob *target=c->GetTarget(); - const char* Usage = "Usage: #fixmob [race|gender|texture|helm|face|hair|haircolor|beard|beardcolor|heritage|tattoo|detail] [next|prev]"; - - if (!sep->arg[1]) - c->Message(Chat::White,Usage); - else if (!target) - c->Message(Chat::White,"Error: this command requires a target"); - else - { - - uint32 Adjustment = 1; // Previous or Next - char codeMove = 0; - - if (sep->arg[2]) - { - char* command2 = sep->arg[2]; - codeMove = (command2[0] | 0x20); // First character, lower-cased - if (codeMove == 'n') - Adjustment = 1; - else if (codeMove == 'p') - Adjustment = -1; - } - - uint16 Race = target->GetRace(); - uint8 Gender = target->GetGender(); - uint8 Texture = 0xFF; - uint8 HelmTexture = 0xFF; - uint8 HairColor = target->GetHairColor(); - uint8 BeardColor = target->GetBeardColor(); - uint8 EyeColor1 = target->GetEyeColor1(); - uint8 EyeColor2 = target->GetEyeColor2(); - uint8 HairStyle = target->GetHairStyle(); - uint8 LuclinFace = target->GetLuclinFace(); - uint8 Beard = target->GetBeard(); - uint32 DrakkinHeritage = target->GetDrakkinHeritage(); - uint32 DrakkinTattoo = target->GetDrakkinTattoo(); - uint32 DrakkinDetails = target->GetDrakkinDetails(); - - const char* ChangeType = nullptr; // If it's still nullptr after processing, they didn't send a valid command - uint32 ChangeSetting; - char* command = sep->arg[1]; - - if (strcasecmp(command, "race") == 0) - { - if (Race == 1 && codeMove == 'p') - Race = 724; - else if (Race >= 724 && codeMove != 'p') - Race = 1; - else - Race += Adjustment; - ChangeType = "Race"; - ChangeSetting = Race; - } - else if (strcasecmp(command, "gender") == 0) - { - if (Gender == 0 && codeMove == 'p') - Gender = 2; - else if (Gender >= 2 && codeMove != 'p') - Gender = 0; - else - Gender += Adjustment; - ChangeType = "Gender"; - ChangeSetting = Gender; - } - else if (strcasecmp(command, "texture") == 0) - { - Texture = target->GetTexture(); - - if (Texture == 0 && codeMove == 'p') - Texture = 25; - else if (Texture >= 25 && codeMove != 'p') - Texture = 0; - else - Texture += Adjustment; - ChangeType = "Texture"; - ChangeSetting = Texture; - } - else if (strcasecmp(command, "helm") == 0) - { - HelmTexture = target->GetHelmTexture(); - if (HelmTexture == 0 && codeMove == 'p') - HelmTexture = 25; - else if (HelmTexture >= 25 && codeMove != 'p') - HelmTexture = 0; - else - HelmTexture += Adjustment; - ChangeType = "HelmTexture"; - ChangeSetting = HelmTexture; - } - else if (strcasecmp(command, "face") == 0) - { - if (LuclinFace == 0 && codeMove == 'p') - LuclinFace = 87; - else if (LuclinFace >= 87 && codeMove != 'p') - LuclinFace = 0; - else - LuclinFace += Adjustment; - ChangeType = "LuclinFace"; - ChangeSetting = LuclinFace; - } - else if (strcasecmp(command, "hair") == 0) - { - if (HairStyle == 0 && codeMove == 'p') - HairStyle = 8; - else if (HairStyle >= 8 && codeMove != 'p') - HairStyle = 0; - else - HairStyle += Adjustment; - ChangeType = "HairStyle"; - ChangeSetting = HairStyle; - } - else if (strcasecmp(command, "haircolor") == 0) - { - if (HairColor == 0 && codeMove == 'p') - HairColor = 24; - else if (HairColor >= 24 && codeMove != 'p') - HairColor = 0; - else - HairColor += Adjustment; - ChangeType = "HairColor"; - ChangeSetting = HairColor; - } - else if (strcasecmp(command, "beard") == 0) - { - if (Beard == 0 && codeMove == 'p') - Beard = 11; - else if (Beard >= 11 && codeMove != 'p') - Beard = 0; - else - Beard += Adjustment; - ChangeType = "Beard"; - ChangeSetting = Beard; - } - else if (strcasecmp(command, "beardcolor") == 0) - { - if (BeardColor == 0 && codeMove == 'p') - BeardColor = 24; - else if (BeardColor >= 24 && codeMove != 'p') - BeardColor = 0; - else - BeardColor += Adjustment; - ChangeType = "BeardColor"; - ChangeSetting = BeardColor; - } - else if (strcasecmp(command, "heritage") == 0) - { - if (DrakkinHeritage == 0 && codeMove == 'p') - DrakkinHeritage = 6; - else if (DrakkinHeritage >= 6 && codeMove != 'p') - DrakkinHeritage = 0; - else - DrakkinHeritage += Adjustment; - ChangeType = "DrakkinHeritage"; - ChangeSetting = DrakkinHeritage; - } - else if (strcasecmp(command, "tattoo") == 0) - { - if (DrakkinTattoo == 0 && codeMove == 'p') - DrakkinTattoo = 8; - else if (DrakkinTattoo >= 8 && codeMove != 'p') - DrakkinTattoo = 0; - else - DrakkinTattoo += Adjustment; - ChangeType = "DrakkinTattoo"; - ChangeSetting = DrakkinTattoo; - } - else if (strcasecmp(command, "detail") == 0) - { - if (DrakkinDetails == 0 && codeMove == 'p') - DrakkinDetails = 7; - else if (DrakkinDetails >= 7 && codeMove != 'p') - DrakkinDetails = 0; - else - DrakkinDetails += Adjustment; - ChangeType = "DrakkinDetails"; - ChangeSetting = DrakkinDetails; - } - - // Hack to fix some races that base features from face - switch (Race) - { - case 2: // Barbarian - if (LuclinFace > 10) { - LuclinFace -= ((DrakkinTattoo - 1) * 10); - } - LuclinFace += (DrakkinTattoo * 10); - break; - case 3: // Erudite - if (LuclinFace > 10) { - LuclinFace -= ((HairStyle - 1) * 10); - } - LuclinFace += (HairStyle * 10); - break; - case 5: // HighElf - case 6: // DarkElf - case 7: // HalfElf - if (LuclinFace > 10) { - LuclinFace -= ((Beard - 1) * 10); - } - LuclinFace += (Beard * 10); - break; - default: - break; - } - - - if (ChangeType == nullptr) - { - c->Message(Chat::White,Usage); - } - else - { - target->SendIllusionPacket(Race, Gender, Texture, HelmTexture, HairColor, BeardColor, - EyeColor1, EyeColor2, HairStyle, LuclinFace, Beard, 0xFF, - DrakkinHeritage, DrakkinTattoo, DrakkinDetails); - - c->Message(Chat::White, "%s=%i", ChangeType, ChangeSetting); - } - } -} - -void command_gmspeed(Client *c, const Seperator *sep) -{ - bool state = atobool(sep->arg[1]); - Client *t = c; - - if (c->GetTarget() && c->GetTarget()->IsClient()) { - t = c->GetTarget()->CastToClient(); - } - - if (sep->arg[1][0] != 0) { - database.SetGMSpeed(t->AccountID(), state ? 1 : 0); - c->Message(Chat::White, "Turning GMSpeed %s for %s (zone to take effect)", state ? "On" : "Off", t->GetName()); - } - else { - c->Message(Chat::White, "Usage: #gmspeed [on/off]"); - } -} - -void command_gmzone(Client *c, const Seperator *sep) -{ - if (!sep->arg[1]) { - c->Message(Chat::White, "Usage"); - c->Message(Chat::White, "-------"); - c->Message(Chat::White, "#gmzone [zone_short_name] [zone_version=0]"); - return; - } - - std::string zone_short_name_string = sep->arg[1]; - const char *zone_short_name = sep->arg[1]; - auto zone_version = static_cast(sep->arg[2] ? atoi(sep->arg[2]) : 0); - std::string identifier = "gmzone"; - uint32 zone_id = ZoneID(zone_short_name); - uint32 duration = 100000000; - uint16 instance_id = 0; - - if (zone_id == 0) { - c->Message(Chat::Red, "Invalid zone specified"); - return; - } - - if (sep->arg[3] && sep->arg[3][0]) { - identifier = sep->arg[3]; - } - - std::string bucket_key = StringFormat("%s-%s-%u-instance", zone_short_name, identifier.c_str(), zone_version); - std::string existing_zone_instance = DataBucket::GetData(bucket_key); - - if (existing_zone_instance.length() > 0) { - instance_id = std::stoi(existing_zone_instance); - - c->Message(Chat::Yellow, "Found already created instance (%s) (%u)", zone_short_name, instance_id); - } - - if (instance_id == 0) { - if (!database.GetUnusedInstanceID(instance_id)) { - c->Message(Chat::Red, "Server was unable to find a free instance id."); - return; - } - - if (!database.CreateInstance(instance_id, zone_id, zone_version, duration)) { - c->Message(Chat::Red, "Server was unable to create a new instance."); - return; - } - - c->Message(Chat::Yellow, "New private GM instance %s was created with id %lu.", zone_short_name, (unsigned long) instance_id); - DataBucket::SetData(bucket_key, std::to_string(instance_id)); - } - - if (instance_id > 0) { - float target_x = -1, target_y = -1, target_z = -1; - int16 min_status = 0; - uint8 min_level = 0; - - if (!content_db.GetSafePoints( - zone_short_name, - zone_version, - &target_x, - &target_y, - &target_z, - &min_status, - &min_level - )) { - c->Message(Chat::Red, "Failed to find safe coordinates for specified zone"); - } - - c->Message(Chat::Yellow, "Zoning to private GM instance (%s) (%u)", zone_short_name, instance_id); - - c->AssignToInstance(instance_id); - c->MovePC(zone_id, instance_id, target_x, target_y, target_z, 0, 1); - } -} - -void command_title(Client *c, const Seperator *sep) -{ - if (sep->arg[1][0]==0) - c->Message(Chat::White, "Usage: #title [remove|text] [1 = Create row in title table] - remove or set title to 'text'"); - else { - bool Save = (atoi(sep->arg[2]) == 1); - - Mob *target_mob = c->GetTarget(); - if(!target_mob) - target_mob = c; - if(!target_mob->IsClient()) { - c->Message(Chat::Red, "#title only works on players."); - return; - } - Client *t = target_mob->CastToClient(); - - if(strlen(sep->arg[1]) > 31) { - c->Message(Chat::Red, "Title must be 31 characters or less."); - return; - } - - bool removed = false; - if(!strcasecmp(sep->arg[1], "remove")) { - t->SetAATitle(""); - removed = true; - } else { - for(unsigned int i=0; iarg[1]); i++) - if(sep->arg[1][i]=='_') - sep->arg[1][i] = ' '; - if(!Save) - t->SetAATitle(sep->arg[1]); - else - title_manager.CreateNewPlayerTitle(t, sep->arg[1]); - } - - t->Save(); - - if(removed) { - c->Message(Chat::Red, "%s's title has been removed.", t->GetName(), sep->arg[1]); - if(t != c) - t->Message(Chat::Red, "Your title has been removed.", sep->arg[1]); - } else { - c->Message(Chat::Red, "%s's title has been changed to '%s'.", t->GetName(), sep->arg[1]); - if(t != c) - t->Message(Chat::Red, "Your title has been changed to '%s'.", sep->arg[1]); - } - } -} - - -void command_titlesuffix(Client *c, const Seperator *sep) -{ - if (sep->arg[1][0]==0) - c->Message(Chat::White, "Usage: #titlesuffix [remove|text] [1 = create row in title table] - remove or set title suffix to 'text'"); - else { - bool Save = (atoi(sep->arg[2]) == 1); - - Mob *target_mob = c->GetTarget(); - if(!target_mob) - target_mob = c; - if(!target_mob->IsClient()) { - c->Message(Chat::Red, "#titlesuffix only works on players."); - return; - } - Client *t = target_mob->CastToClient(); - - if(strlen(sep->arg[1]) > 31) { - c->Message(Chat::Red, "Title suffix must be 31 characters or less."); - return; - } - - bool removed = false; - if(!strcasecmp(sep->arg[1], "remove")) { - t->SetTitleSuffix(""); - removed = true; - } else { - for(unsigned int i=0; iarg[1]); i++) - if(sep->arg[1][i]=='_') - sep->arg[1][i] = ' '; - - if(!Save) - t->SetTitleSuffix(sep->arg[1]); - else - title_manager.CreateNewPlayerSuffix(t, sep->arg[1]); - } - - t->Save(); - - if(removed) { - c->Message(Chat::Red, "%s's title suffix has been removed.", t->GetName(), sep->arg[1]); - if(t != c) - t->Message(Chat::Red, "Your title suffix has been removed.", sep->arg[1]); - } else { - c->Message(Chat::Red, "%s's title suffix has been changed to '%s'.", t->GetName(), sep->arg[1]); - if(t != c) - t->Message(Chat::Red, "Your title suffix has been changed to '%s'.", sep->arg[1]); - } - } -} - -void command_spellinfo(Client *c, const Seperator *sep) -{ - if(sep->arg[1][0]==0) - c->Message(Chat::White, "Usage: #spellinfo [spell_id]"); - else { - short int spell_id=atoi(sep->arg[1]); - const struct SPDat_Spell_Struct *s=&spells[spell_id]; - c->Message(Chat::White, "Spell info for spell #%d:", spell_id); - c->Message(Chat::White, " name: %s", s->name); - c->Message(Chat::White, " player_1: %s", s->player_1); - c->Message(Chat::White, " teleport_zone: %s", s->teleport_zone); - c->Message(Chat::White, " you_cast: %s", s->you_cast); - c->Message(Chat::White, " other_casts: %s", s->other_casts); - c->Message(Chat::White, " cast_on_you: %s", s->cast_on_you); - c->Message(Chat::White, " spell_fades: %s", s->spell_fades); - c->Message(Chat::White, " range: %f", s->range); - c->Message(Chat::White, " aoerange: %f", s->aoerange); - c->Message(Chat::White, " pushback: %f", s->pushback); - c->Message(Chat::White, " pushup: %f", s->pushup); - c->Message(Chat::White, " cast_time: %d", s->cast_time); - c->Message(Chat::White, " recovery_time: %d", s->recovery_time); - c->Message(Chat::White, " recast_time: %d", s->recast_time); - c->Message(Chat::White, " buffdurationformula: %d", s->buffdurationformula); - c->Message(Chat::White, " buffduration: %d", s->buffduration); - c->Message(Chat::White, " AEDuration: %d", s->AEDuration); - c->Message(Chat::White, " mana: %d", s->mana); - c->Message(Chat::White, " base[12]: %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d", s->base[0], s->base[1], s->base[2], s->base[3], s->base[4], s->base[5], s->base[6], s->base[7], s->base[8], s->base[9], s->base[10], s->base[11]); - c->Message(Chat::White, " base22[12]: %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d", s->base2[0], s->base2[1], s->base2[2], s->base2[3], s->base2[4], s->base2[5], s->base2[6], s->base2[7], s->base2[8], s->base2[9], s->base2[10], s->base2[11]); - c->Message(Chat::White, " max[12]: %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d", s->max[0], s->max[1], s->max[2], s->max[3], s->max[4], s->max[5], s->max[6], s->max[7], s->max[8], s->max[9], s->max[10], s->max[11]); - c->Message(Chat::White, " components[4]: %d, %d, %d, %d", s->components[0], s->components[1], s->components[2], s->components[3]); - c->Message(Chat::White, " component_counts[4]: %d, %d, %d, %d", s->component_counts[0], s->component_counts[1], s->component_counts[2], s->component_counts[3]); - c->Message(Chat::White, " NoexpendReagent[4]: %d, %d, %d, %d", s->NoexpendReagent[0], s->NoexpendReagent[1], s->NoexpendReagent[2], s->NoexpendReagent[3]); - c->Message(Chat::White, " formula[12]: 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x", s->formula[0], s->formula[1], s->formula[2], s->formula[3], s->formula[4], s->formula[5], s->formula[6], s->formula[7], s->formula[8], s->formula[9], s->formula[10], s->formula[11]); - c->Message(Chat::White, " goodEffect: %d", s->goodEffect); - c->Message(Chat::White, " Activated: %d", s->Activated); - c->Message(Chat::White, " resisttype: %d", s->resisttype); - c->Message(Chat::White, " effectid[12]: 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x", s->effectid[0], s->effectid[1], s->effectid[2], s->effectid[3], s->effectid[4], s->effectid[5], s->effectid[6], s->effectid[7], s->effectid[8], s->effectid[9], s->effectid[10], s->effectid[11]); - c->Message(Chat::White, " targettype: %d", s->targettype); - c->Message(Chat::White, " basediff: %d", s->basediff); - c->Message(Chat::White, " skill: %d", s->skill); - c->Message(Chat::White, " zonetype: %d", s->zonetype); - c->Message(Chat::White, " EnvironmentType: %d", s->EnvironmentType); - c->Message(Chat::White, " TimeOfDay: %d", s->TimeOfDay); - c->Message(Chat::White, " classes[15]: %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d", - s->classes[0], s->classes[1], s->classes[2], s->classes[3], s->classes[4], - s->classes[5], s->classes[6], s->classes[7], s->classes[8], s->classes[9], - s->classes[10], s->classes[11], s->classes[12], s->classes[13], s->classes[14]); - c->Message(Chat::White, " CastingAnim: %d", s->CastingAnim); - c->Message(Chat::White, " SpellAffectIndex: %d", s->SpellAffectIndex); - c->Message(Chat::White, " RecourseLink: %d", s->RecourseLink); - } -} - -void command_lastname(Client *c, const Seperator *sep) -{ - Client *t=c; - - if(c->GetTarget() && c->GetTarget()->IsClient()) - t=c->GetTarget()->CastToClient(); - LogInfo("#lastname request from [{}] for [{}]", c->GetName(), t->GetName()); - - if(strlen(sep->arg[1]) <= 70) - t->ChangeLastName(sep->arg[1]); - else - c->Message(Chat::White, "Usage: #lastname where is less than 70 chars long"); -} - -void command_memspell(Client *c, const Seperator *sep) -{ - uint32 slot; - uint16 spell_id; - - if (!(sep->IsNumber(1) && sep->IsNumber(2))) - { - c->Message(Chat::White, "Usage: #MemSpell slotid spellid"); - } - else - { - slot = atoi(sep->arg[1]) - 1; - spell_id = atoi(sep->arg[2]); - if (slot > EQ::spells::SPELL_GEM_COUNT || spell_id >= SPDAT_RECORDS) - { - c->Message(Chat::White, "Error: #MemSpell: Arguement out of range"); - } - else - { - c->MemSpell(spell_id, slot); - c->Message(Chat::White, "Spell slot changed, have fun!"); - } - } -} -void command_save(Client *c, const Seperator *sep) -{ - if (c->GetTarget() == 0) - c->Message(Chat::White, "Error: no target"); - else if (c->GetTarget()->IsClient()) { - if (c->GetTarget()->CastToClient()->Save(2)) - c->Message(Chat::White, "%s successfully saved.", c->GetTarget()->GetName()); - else - c->Message(Chat::White, "Manual save for %s failed.", c->GetTarget()->GetName()); - } - else if (c->GetTarget()->IsPlayerCorpse()) { - if (c->GetTarget()->CastToMob()->Save()) - c->Message(Chat::White, "%s successfully saved. (dbid=%u)", c->GetTarget()->GetName(), c->GetTarget()->CastToCorpse()->GetCorpseDBID()); - else - c->Message(Chat::White, "Manual save for %s failed.", c->GetTarget()->GetName()); - } - else - c->Message(Chat::White, "Error: target not a Client/PlayerCorpse"); -} - -void command_showstats(Client *c, const Seperator *sep) -{ - if (c->GetTarget() != 0 ) - c->GetTarget()->ShowStats(c); - else - c->ShowStats(c); -} - -void command_showzonegloballoot(Client *c, const Seperator *sep) -{ - c->Message(Chat::White, "GlobalLoot for %s (%d:%d)", zone->GetShortName(), zone->GetZoneID(), zone->GetInstanceVersion()); - zone->ShowZoneGlobalLoot(c); -} - -void command_showzonepoints(Client *c, const Seperator *sep) -{ - auto &mob_list = entity_list.GetMobList(); - for (auto itr : mob_list) { - Mob *mob = itr.second; - if (mob->IsNPC() && mob->GetRace() == 2254) { - mob->Depop(); - } - } - - int found_zone_points = 0; - - c->Message(Chat::White, "Listing zone points..."); - c->SendChatLineBreak(); - - for (auto &virtual_zone_point : zone->virtual_zone_point_list) { - std::string zone_long_name = zone_store.GetZoneLongName(virtual_zone_point.target_zone_id); - - c->Message( - Chat::White, - fmt::format( - "Virtual Zone Point x [{}] y [{}] z [{}] h [{}] width [{}] height [{}] | To [{}] ({}) x [{}] y [{}] z [{}] h [{}]", - virtual_zone_point.x, - virtual_zone_point.y, - virtual_zone_point.z, - virtual_zone_point.heading, - virtual_zone_point.width, - virtual_zone_point.height, - zone_long_name.c_str(), - virtual_zone_point.target_zone_id, - virtual_zone_point.target_x, - virtual_zone_point.target_y, - virtual_zone_point.target_z, - virtual_zone_point.target_heading - ).c_str() - ); - - std::string node_name = fmt::format("ZonePoint To [{}]", zone_long_name); - - float half_width = ((float) virtual_zone_point.width / 2); - - NPC::SpawnZonePointNodeNPC(node_name, glm::vec4( - (float) virtual_zone_point.x + half_width, - (float) virtual_zone_point.y + half_width, - virtual_zone_point.z, - virtual_zone_point.heading - )); - - NPC::SpawnZonePointNodeNPC(node_name, glm::vec4( - (float) virtual_zone_point.x + half_width, - (float) virtual_zone_point.y - half_width, - virtual_zone_point.z, - virtual_zone_point.heading - )); - - NPC::SpawnZonePointNodeNPC(node_name, glm::vec4( - (float) virtual_zone_point.x - half_width, - (float) virtual_zone_point.y - half_width, - virtual_zone_point.z, - virtual_zone_point.heading - )); - - NPC::SpawnZonePointNodeNPC(node_name, glm::vec4( - (float) virtual_zone_point.x - half_width, - (float) virtual_zone_point.y + half_width, - virtual_zone_point.z, - virtual_zone_point.heading - )); - - NPC::SpawnZonePointNodeNPC(node_name, glm::vec4( - (float) virtual_zone_point.x + half_width, - (float) virtual_zone_point.y + half_width, - (float) virtual_zone_point.z + (float) virtual_zone_point.height, - virtual_zone_point.heading - )); - - NPC::SpawnZonePointNodeNPC(node_name, glm::vec4( - (float) virtual_zone_point.x + half_width, - (float) virtual_zone_point.y - half_width, - (float) virtual_zone_point.z + (float) virtual_zone_point.height, - virtual_zone_point.heading - )); - - NPC::SpawnZonePointNodeNPC(node_name, glm::vec4( - (float) virtual_zone_point.x - half_width, - (float) virtual_zone_point.y - half_width, - (float) virtual_zone_point.z + (float) virtual_zone_point.height, - virtual_zone_point.heading - )); - - NPC::SpawnZonePointNodeNPC(node_name, glm::vec4( - (float) virtual_zone_point.x - half_width, - (float) virtual_zone_point.y + half_width, - (float) virtual_zone_point.z + (float) virtual_zone_point.height, - virtual_zone_point.heading - )); - - found_zone_points++; - } - - LinkedListIterator iterator(zone->zone_point_list); - iterator.Reset(); - while (iterator.MoreElements()) { - ZonePoint *zone_point = iterator.GetData(); - std::string zone_long_name = zone_store.GetZoneLongName(zone_point->target_zone_id); - std::string node_name = fmt::format("ZonePoint To [{}]", zone_long_name); - - NPC::SpawnZonePointNodeNPC( - node_name, glm::vec4( - zone_point->x, - zone_point->y, - zone_point->z, - zone_point->heading - ) - ); - - c->Message( - Chat::White, - fmt::format( - "Client Side Zone Point x [{}] y [{}] z [{}] h [{}] number [{}] | To [{}] ({}) x [{}] y [{}] z [{}] h [{}]", - zone_point->x, - zone_point->y, - zone_point->z, - zone_point->heading, - zone_point->number, - zone_long_name.c_str(), - zone_point->target_zone_id, - zone_point->target_x, - zone_point->target_y, - zone_point->target_z, - zone_point->target_heading - ).c_str() - ); - - iterator.Advance(); - - found_zone_points++; - } - - if (found_zone_points == 0) { - c->Message(Chat::White, "There were no zone points found..."); - } - - c->SendChatLineBreak(); - -} - -void command_mystats(Client *c, const Seperator *sep) -{ - if (c->GetTarget() && c->GetPet()) { - if (c->GetTarget()->IsPet() && c->GetTarget() == c->GetPet()) - c->GetTarget()->ShowStats(c); - else - c->ShowStats(c); - } - else - c->ShowStats(c); -} - -void command_myskills(Client *c, const Seperator *sep) -{ - c->ShowSkillsWindow(); -} - -void command_bind(Client *c, const Seperator *sep) -{ - if (c->GetTarget() != 0 ) { - if (c->GetTarget()->IsClient()) - c->GetTarget()->CastToClient()->SetBindPoint(); - else - c->Message(Chat::White, "Error: target not a Player"); - } else - c->SetBindPoint(); -} - -void command_depop(Client *c, const Seperator *sep) -{ - if (c->GetTarget() == 0 || !(c->GetTarget()->IsNPC() || c->GetTarget()->IsNPCCorpse())) - c->Message(Chat::White, "You must have a NPC target for this command. (maybe you meant #depopzone?)"); - else { - c->Message(Chat::White, "Depoping '%s'.", c->GetTarget()->GetName()); - c->GetTarget()->Depop(); - } -} - -void command_depopzone(Client *c, const Seperator *sep) -{ - zone->Depop(); - c->Message(Chat::White, "Zone depoped."); -} - -void command_devtools(Client *c, const Seperator *sep) -{ - std::string dev_tools_key = StringFormat("%i-dev-tools-disabled", c->AccountID()); - - /** - * Handle window toggle - */ - if (strcasecmp(sep->arg[1], "disable") == 0) { - DataBucket::SetData(dev_tools_key, "true"); - c->SetDevToolsEnabled(false); - } - if (strcasecmp(sep->arg[1], "enable") == 0) { - DataBucket::DeleteData(dev_tools_key); - c->SetDevToolsEnabled(true); - } - - c->ShowDevToolsMenu(); -} - -void command_repop(Client *c, const Seperator *sep) -{ - int timearg = 1; - int delay = 0; - - if (sep->arg[1] && strcasecmp(sep->arg[1], "force") == 0) { - timearg++; - - LinkedListIterator iterator(zone->spawn2_list); - iterator.Reset(); - while (iterator.MoreElements()) { - std::string query = StringFormat( - "DELETE FROM respawn_times WHERE id = %lu AND instance_id = %lu", - (unsigned long)iterator.GetData()->GetID(), - (unsigned long)zone->GetInstanceID() - ); - auto results = database.QueryDatabase(query); - iterator.Advance(); - } - c->Message(Chat::White, "Zone depop: Force resetting spawn timers."); - } - - if (!sep->IsNumber(timearg)) { - c->Message(Chat::White, "Zone depopped - repopping now."); - - zone->Repop(); - - /* Force a spawn2 timer trigger so we don't delay actually spawning the NPC's */ - zone->spawn2_timer.Trigger(); - return; - } - - c->Message(Chat::White, "Zone depoped. Repop in %i seconds", atoi(sep->arg[timearg])); - zone->Repop(atoi(sep->arg[timearg]) * 1000); - - zone->spawn2_timer.Trigger(); -} - -void command_spawnstatus(Client *c, const Seperator *sep) -{ - if((sep->arg[1][0] == 'e') | (sep->arg[1][0] == 'E')) - { - // show only enabled spawns - zone->ShowEnabledSpawnStatus(c); - } - else if((sep->arg[1][0] == 'd') | (sep->arg[1][0] == 'D')) - { - // show only disabled spawns - zone->ShowDisabledSpawnStatus(c); - } - else if((sep->arg[1][0] == 'a') | (sep->arg[1][0] == 'A')) - { - // show all spawn staus with no filters - zone->SpawnStatus(c); - } - else if(sep->IsNumber(1)) - { - // show spawn status by spawn2 id - zone->ShowSpawnStatusByID(c, atoi(sep->arg[1])); - } - else if(strcmp(sep->arg[1], "help") == 0) - { - c->Message(Chat::White, "Usage: #spawnstatus <[a]ll | [d]isabled | [e]nabled | {Spawn2 ID}>"); - } - else { - zone->SpawnStatus(c); - } -} - -void command_nukebuffs(Client *c, const Seperator *sep) -{ - if (c->GetTarget() == 0) - c->BuffFadeAll(); - else - c->GetTarget()->BuffFadeAll(); -} - -void command_zuwcoords(Client *c, const Seperator *sep) -{ - // modifys and resends zhdr packet - if(sep->arg[1][0]==0) - c->Message(Chat::White, "Usage: #zuwcoords "); - else { - zone->newzone_data.underworld = atof(sep->arg[1]); - //float newdata = atof(sep->arg[1]); - //memcpy(&zone->zone_header_data[130], &newdata, sizeof(float)); - auto outapp = new EQApplicationPacket(OP_NewZone, sizeof(NewZone_Struct)); - memcpy(outapp->pBuffer, &zone->newzone_data, outapp->size); - entity_list.QueueClients(c, outapp); - safe_delete(outapp); - } -} - -void command_zunderworld(Client *c, const Seperator *sep) -{ - if(sep->arg[1][0]==0) - c->Message(Chat::White, "Usage: #zunderworld "); - else { - zone->newzone_data.underworld = atof(sep->arg[1]); - } -} - -void command_zsafecoords(Client *c, const Seperator *sep) -{ - // modifys and resends zhdr packet - if(sep->arg[3][0]==0) - c->Message(Chat::White, "Usage: #zsafecoords "); - else { - zone->newzone_data.safe_x = atof(sep->arg[1]); - zone->newzone_data.safe_y = atof(sep->arg[2]); - zone->newzone_data.safe_z = atof(sep->arg[3]); - //float newdatax = atof(sep->arg[1]); - //float newdatay = atof(sep->arg[2]); - //float newdataz = atof(sep->arg[3]); - //memcpy(&zone->zone_header_data[114], &newdatax, sizeof(float)); - //memcpy(&zone->zone_header_data[118], &newdatay, sizeof(float)); - //memcpy(&zone->zone_header_data[122], &newdataz, sizeof(float)); - //zone->SetSafeCoords(); - auto outapp = new EQApplicationPacket(OP_NewZone, sizeof(NewZone_Struct)); - memcpy(outapp->pBuffer, &zone->newzone_data, outapp->size); - entity_list.QueueClients(c, outapp); - safe_delete(outapp); - } -} - -void command_freeze(Client *c, const Seperator *sep) -{ - if (c->GetTarget() != 0) - c->GetTarget()->SendAppearancePacket(AT_Anim, ANIM_FREEZE); - else - c->Message(Chat::White, "ERROR: Freeze requires a target."); -} - -void command_unfreeze(Client *c, const Seperator *sep) -{ - if (c->GetTarget() != 0) - c->GetTarget()->SendAppearancePacket(AT_Anim, ANIM_STAND); - else - c->Message(Chat::White, "ERROR: Unfreeze requires a target."); -} - -void command_push(Client *c, const Seperator *sep) -{ - Mob *t = c; - if (c->GetTarget() != nullptr) - t = c->GetTarget(); - - if (!sep->arg[1] || !sep->IsNumber(1)) { - c->Message(Chat::White, "ERROR: Must provide at least a push back."); - return; - } - - float back = atof(sep->arg[1]); - float up = 0.0f; - - if (sep->arg[2] && sep->IsNumber(2)) - up = atof(sep->arg[2]); - - if (t->IsNPC()) { - t->IncDeltaX(back * g_Math.FastSin(c->GetHeading())); - t->IncDeltaY(back * g_Math.FastCos(c->GetHeading())); - t->IncDeltaZ(up); - t->SetForcedMovement(6); - } else if (t->IsClient()) { - // TODO: send packet to push - } -} - -void command_proximity(Client *c, const Seperator *sep) -{ - if (!c->GetTarget() && !c->GetTarget()->IsNPC()) { - c->Message(Chat::White, "You must target an NPC"); - return; - } - - for (auto &iter : entity_list.GetNPCList()) { - auto npc = iter.second; - std::string name = npc->GetName(); - - if (name.find("Proximity") != std::string::npos) { - npc->Depop(); - } - } - - NPC *npc = c->GetTarget()->CastToNPC(); - - std::vector points; - - FindPerson_Point p{}; - - if (npc->IsProximitySet()) { - glm::vec4 position; - position.w = npc->GetHeading(); - position.x = npc->GetProximityMinX(); - position.y = npc->GetProximityMinY(); - position.z = npc->GetZ(); - - position.x = npc->GetProximityMinX(); - position.y = npc->GetProximityMinY(); - NPC::SpawnNodeNPC("Proximity", "", position); - - position.x = npc->GetProximityMinX(); - position.y = npc->GetProximityMaxY(); - NPC::SpawnNodeNPC("Proximity", "", position); - - position.x = npc->GetProximityMaxX(); - position.y = npc->GetProximityMinY(); - NPC::SpawnNodeNPC("Proximity", "", position); - - position.x = npc->GetProximityMaxX(); - position.y = npc->GetProximityMaxY(); - NPC::SpawnNodeNPC("Proximity", "", position); - - p.x = npc->GetProximityMinX(); - p.y = npc->GetProximityMinY(); - p.z = npc->GetZ(); - points.push_back(p); - - p.x = npc->GetProximityMinX(); - p.y = npc->GetProximityMaxY(); - points.push_back(p); - - p.x = npc->GetProximityMaxX(); - p.y = npc->GetProximityMaxY(); - points.push_back(p); - - p.x = npc->GetProximityMaxX(); - p.y = npc->GetProximityMinY(); - points.push_back(p); - - p.x = npc->GetProximityMinX(); - p.y = npc->GetProximityMinY(); - points.push_back(p); - } - - if (c->ClientVersion() >= EQ::versions::ClientVersion::RoF) { - c->SendPathPacket(points); - } -} - -void command_pvp(Client *c, const Seperator *sep) -{ - bool state=atobool(sep->arg[1]); - Client *t=c; - - if(c->GetTarget() && c->GetTarget()->IsClient()) - t=c->GetTarget()->CastToClient(); - - if(sep->arg[1][0] != 0) { - t->SetPVP(state); - c->Message(Chat::White, "%s now follows the ways of %s.", t->GetName(), state?"discord":"order"); - } - else - c->Message(Chat::White, "Usage: #pvp [on/off]"); -} - -void command_setxp(Client *c, const Seperator *sep) -{ - Client *t=c; - - if(c->GetTarget() && c->GetTarget()->IsClient()) - t=c->GetTarget()->CastToClient(); - - if (sep->IsNumber(1)) { - if (atoi(sep->arg[1]) > 9999999) - c->Message(Chat::White, "Error: Value too high."); - else - t->AddEXP(atoi(sep->arg[1])); - } - else - c->Message(Chat::White, "Usage: #setxp number"); -} - -void command_setpvppoints(Client *c, const Seperator *sep) -{ - Client *t=c; - - if(c->GetTarget() && c->GetTarget()->IsClient()) - t=c->GetTarget()->CastToClient(); - - if (sep->IsNumber(1)) { - if (atoi(sep->arg[1]) > 9999999) - c->Message(Chat::White, "Error: Value too high."); - else - { - t->SetPVPPoints(atoi(sep->arg[1])); - t->Save(); - t->SendPVPStats(); - } - } - else - c->Message(Chat::White, "Usage: #setpvppoints number"); -} - -void command_name(Client *c, const Seperator *sep) -{ - Client *target; - - if( (strlen(sep->arg[1]) == 0) || (!(c->GetTarget() && c->GetTarget()->IsClient())) ) - c->Message(Chat::White, "Usage: #name newname (requires player target)"); - else - { - target = c->GetTarget()->CastToClient(); - char *oldname = strdup(target->GetName()); - if(target->ChangeFirstName(sep->arg[1], c->GetName())) - { - c->Message(Chat::White, "Successfully renamed %s to %s", oldname, sep->arg[1]); - // until we get the name packet working right this will work - c->Message(Chat::White, "Sending player to char select."); - target->Kick("Name was changed"); - } - else - c->Message(Chat::Red, "ERROR: Unable to rename %s. Check that the new name '%s' isn't already taken.", oldname, sep->arg[2]); - free(oldname); - } -} - -void command_tempname(Client *c, const Seperator *sep) -{ - Mob *target; - target = c->GetTarget(); - - if(!target) - c->Message(Chat::White, "Usage: #tempname newname (requires a target)"); - else if(strlen(sep->arg[1]) > 0) - { - char *oldname = strdup(target->GetName()); - target->TempName(sep->arg[1]); - c->Message(Chat::White, "Renamed %s to %s", oldname, sep->arg[1]); - free(oldname); - } - else { - target->TempName(); - c->Message(Chat::White, "Restored the original name"); - } -} - -void command_petname(Client *c, const Seperator *sep) -{ - Mob *target; - target = c->GetTarget(); - - if(!target) - c->Message(Chat::White, "Usage: #petname newname (requires a target)"); - else if(target->IsPet() && (target->GetOwnerID() == c->GetID()) && strlen(sep->arg[1]) > 0) - { - char *oldname = strdup(target->GetName()); - target->TempName(sep->arg[1]); - c->Message(Chat::White, "Renamed %s to %s", oldname, sep->arg[1]); - free(oldname); - } - else { - target->TempName(); - c->Message(Chat::White, "Restored the original name"); - } -} - -void command_npcspecialattk(Client *c, const Seperator *sep) -{ - if (c->GetTarget()==0 || c->GetTarget()->IsClient() || strlen(sep->arg[1]) <= 0 || strlen(sep->arg[2]) <= 0) - c->Message(Chat::White, "Usage: #npcspecialattk *flagchar* *permtag* (Flags are E(nrage) F(lurry) R(ampage) S(ummon), permtag is 1 = True, 0 = False)."); - else { - c->GetTarget()->CastToNPC()->NPCSpecialAttacks(sep->arg[1],atoi(sep->arg[2])); - c->Message(Chat::White, "NPC Special Attack set."); - } -} - -void command_kill(Client *c, const Seperator *sep) -{ - if (!c->GetTarget()) { - c->Message(Chat::White, "Error: #Kill: No target."); - } - else - if (!c->GetTarget()->IsClient() || c->GetTarget()->CastToClient()->Admin() <= c->Admin()) - c->GetTarget()->Kill(); -} - -void command_killallnpcs(Client *c, const Seperator *sep) -{ - std::string search_string; - if (sep->arg[1]) { - search_string = sep->arg[1]; - } - - int count = 0; - for (auto &itr : entity_list.GetMobList()) { - Mob *entity = itr.second; - if (!entity->IsNPC()) { - continue; - } - - std::string entity_name = entity->GetName(); - - /** - * Filter by name - */ - if (search_string.length() > 0 && entity_name.find(search_string) == std::string::npos) { - continue; - } - - bool is_not_attackable = - ( - entity->IsInvisible() || - !entity->IsAttackAllowed(c) || - entity->GetRace() == 127 || - entity->GetRace() == 240 - ); - - if (is_not_attackable) { - continue; - } - - entity->Damage(c, 1000000000, 0, EQ::skills::SkillDragonPunch); - - count++; - } - - c->Message(Chat::Yellow, "Killed (%i) npc(s)", count); -} - -void command_haste(Client *c, const Seperator *sep) -{ - // #haste command to set client attack speed. Takes a percentage (100 = twice normal attack speed) - if(sep->arg[1][0] != 0) { - uint16 Haste = atoi(sep->arg[1]); - if(Haste > 85) - Haste = 85; - c->SetExtraHaste(Haste); - // SetAttackTimer must be called to make this take effect, so player needs to change - // the primary weapon. - c->Message(Chat::White, "Haste set to %d%% - Need to re-equip primary weapon before it takes effect", Haste); - } - else - c->Message(Chat::White, "Usage: #haste [percentage]"); -} - -void command_damage(Client *c, const Seperator *sep) -{ - if (c->GetTarget()==0) - c->Message(Chat::White, "Error: #Damage: No Target."); - else if (!sep->IsNumber(1)) { - c->Message(Chat::White, "Usage: #damage x"); - } - else { - int32 nkdmg = atoi(sep->arg[1]); - if (nkdmg > 2100000000) - c->Message(Chat::White, "Enter a value less then 2,100,000,000."); - else - c->GetTarget()->Damage(c, nkdmg, SPELL_UNKNOWN, EQ::skills::SkillHandtoHand, false); - } -} - -void command_zonespawn(Client *c, const Seperator *sep) -{ - c->Message(Chat::White, "This command is not yet implemented."); - return; - -/* this was kept from client.cpp verbatim (it was commented out) */ - // if (target && target->IsNPC()) { - // Message(0, "Inside main if."); - // if (strcasecmp(sep->arg[1], "add")==0) { - // Message(0, "Inside add if."); - // database.DBSpawn(1, StaticGetZoneName(this->GetPP().current_zone), target->CastToNPC()); - // } - // else if (strcasecmp(sep->arg[1], "update")==0) { - // database.DBSpawn(2, StaticGetZoneName(this->GetPP().current_zone), target->CastToNPC()); - // } - // else if (strcasecmp(sep->arg[1], "remove")==0) { - // if (strcasecmp(sep->arg[2], "all")==0) { - // database.DBSpawn(4, StaticGetZoneName(this->GetPP().current_zone)); - // } - // else { - // if (database.DBSpawn(3, StaticGetZoneName(this->GetPP().current_zone), target->CastToNPC())) { - // Message(0, "#zonespawn: %s removed successfully!", target->GetName()); - // target->CastToNPC()->Death(target, target->GetHP()); - // } - // } - // } - // else - // Message(0, "Error: #dbspawn: Invalid command. (Note: EDIT and REMOVE are NOT in yet.)"); - // if (target->CastToNPC()->GetNPCTypeID() > 0) { - // Message(0, "Spawn is type %i", target->CastToNPC()->GetNPCTypeID()); - // } - // } - // else if(!target || !target->IsNPC()) - // Message(0, "Error: #zonespawn: You must have a NPC targeted!"); - // else - // Message(0, "Usage: #zonespawn [add|edit|remove|remove all]"); -} - -void command_npcspawn(Client *c, const Seperator *sep) -{ - Mob *target=c->GetTarget(); - uint32 extra = 0; - - if (target && target->IsNPC()) { - if (strcasecmp(sep->arg[1], "create") == 0) { - if (atoi(sep->arg[2])) - { - // Option to try to create the npc_type ID within the range for the current zone (zone_id * 1000) - extra = 1; - } - content_db.NPCSpawnDB(0, zone->GetShortName(), zone->GetInstanceVersion(), c, target->CastToNPC(), extra); - c->Message(Chat::White, "%s created successfully!", target->GetName()); - } - else if (strcasecmp(sep->arg[1], "add") == 0) { - if (atoi(sep->arg[2])) - { - extra = atoi(sep->arg[2]); - } - else - { - // Respawn Timer default if not set - extra = 1200; - } - content_db.NPCSpawnDB(1, zone->GetShortName(), zone->GetInstanceVersion(), c, target->CastToNPC(), extra); - c->Message(Chat::White, "%s added successfully!", target->GetName()); - } - else if (strcasecmp(sep->arg[1], "update") == 0) { - content_db.NPCSpawnDB(2, zone->GetShortName(), zone->GetInstanceVersion(), c, target->CastToNPC()); - c->Message(Chat::White, "%s updated!", target->GetName()); - } - else if (strcasecmp(sep->arg[1], "remove") == 0) { - content_db.NPCSpawnDB(3, zone->GetShortName(), zone->GetInstanceVersion(), c, target->CastToNPC()); - c->Message(Chat::White, "%s removed successfully from database!", target->GetName()); - target->Depop(false); - } - else if (strcasecmp(sep->arg[1], "delete") == 0) { - content_db.NPCSpawnDB(4, zone->GetShortName(), zone->GetInstanceVersion(), c, target->CastToNPC()); - c->Message(Chat::White, "%s deleted from database!", target->GetName()); - target->Depop(false); - } - else { - c->Message(Chat::White, "Error: #npcspawn: Invalid command."); - c->Message(Chat::White, "Usage: #npcspawn [create|add|update|remove|delete]"); - } - } - else - c->Message(Chat::White, "Error: #npcspawn: You must have a NPC targeted!"); -} - -void command_spawnfix(Client *c, const Seperator *sep) { - Mob *targetMob = c->GetTarget(); - if (!targetMob || !targetMob->IsNPC()) { - c->Message(Chat::White, "Error: #spawnfix: Need an NPC target."); - return; - } - - Spawn2* s2 = targetMob->CastToNPC()->respawn2; - - if(!s2) { - c->Message(Chat::White, "#spawnfix FAILED -- cannot determine which spawn entry in the database this mob came from."); - return; - } - - std::string query = StringFormat("UPDATE spawn2 SET x = '%f', y = '%f', z = '%f', heading = '%f' WHERE id = '%i'", - c->GetX(), c->GetY(), c->GetZ(), c->GetHeading(),s2->GetID()); - auto results = content_db.QueryDatabase(query); - if (!results.Success()) { - c->Message(Chat::Red, "Update failed! MySQL gave the following error:"); - c->Message(Chat::Red, results.ErrorMessage().c_str()); - return; - } - - c->Message(Chat::White, "Updating coordinates successful."); - targetMob->Depop(false); -} - -void command_loc(Client *c, const Seperator *sep) -{ - Mob *t=c->GetTarget()?c->GetTarget():c->CastToMob(); - - c->Message(Chat::White, "%s's Location (XYZ): %1.2f, %1.2f, %1.2f; heading=%1.1f", t->GetName(), t->GetX(), t->GetY(), t->GetZ(), t->GetHeading()); -} - -void command_goto(Client *c, const Seperator *sep) -{ - std::string arg1 = sep->arg[1]; - - bool goto_via_target_no_args = sep->arg[1][0] == '\0' && c->GetTarget(); - bool goto_via_player_name = !sep->IsNumber(1) && !arg1.empty(); - bool goto_via_x_y_z = sep->IsNumber(1) && sep->IsNumber(2) && sep->IsNumber(3); - - if (goto_via_target_no_args) { - c->MovePC( - zone->GetZoneID(), - zone->GetInstanceID(), - c->GetTarget()->GetX(), - c->GetTarget()->GetY(), - c->GetTarget()->GetZ(), - c->GetTarget()->GetHeading() - ); - } - else if (goto_via_player_name) { - - /** - * Find them in zone first - */ - const char *player_name = sep->arg[1]; - std::string player_name_string = sep->arg[1]; - Client *client = entity_list.GetClientByName(player_name); - if (client) { - c->MovePC( - zone->GetZoneID(), - zone->GetInstanceID(), - client->GetX(), - client->GetY(), - client->GetZ(), - client->GetHeading() - ); - - c->Message(Chat::Yellow, "Goto player '%s' same zone", player_name_string.c_str()); - } - else if (c->GotoPlayer(player_name_string)) { - c->Message(Chat::Yellow, "Goto player '%s' different zone", player_name_string.c_str()); - } - else { - c->Message(Chat::Yellow, "Player '%s' not found", player_name_string.c_str()); - } - } - else if (goto_via_x_y_z) { - c->MovePC( - zone->GetZoneID(), - zone->GetInstanceID(), - atof(sep->arg[1]), - atof(sep->arg[2]), - atof(sep->arg[3]), - (sep->arg[4] ? atof(sep->arg[4]) : c->GetHeading()) - ); - } - else { - c->Message(Chat::White, "Usage: #goto [x y z] [h]"); - c->Message(Chat::White, "Usage: #goto [player_name]"); - } -} - -void command_iteminfo(Client *c, const Seperator *sep) -{ - auto inst = c->GetInv()[EQ::invslot::slotCursor]; - if (!inst) { - c->Message(Chat::Red, "Error: You need an item on your cursor for this command"); - return; - } - auto item = inst->GetItem(); - if (!item) { - LogInventory("([{}]) Command #iteminfo processed an item with no data pointer"); - c->Message(Chat::Red, "Error: This item has no data reference"); - return; - } - - EQ::SayLinkEngine linker; - linker.SetLinkType(EQ::saylink::SayLinkItemInst); - linker.SetItemInst(inst); - - c->Message(Chat::White, "*** Item Info for [%s] ***", linker.GenerateLink().c_str()); - c->Message(Chat::White, ">> ID: %u, ItemUseType: %u, ItemClassType: %u", item->ID, item->ItemType, item->ItemClass); - c->Message(Chat::White, ">> IDFile: '%s', IconID: %u", item->IDFile, item->Icon); - c->Message(Chat::White, ">> Size: %u, Weight: %u, Price: %u, LDoNPrice: %u", item->Size, item->Weight, item->Price, item->LDoNPrice); - c->Message(Chat::White, ">> Material: 0x%02X, Color: 0x%08X, Tint: 0x%08X, Light: 0x%02X", item->Material, item->Color, inst->GetColor(), item->Light); - c->Message(Chat::White, ">> IsLore: %s, LoreGroup: %u, Lore: '%s'", (item->LoreFlag ? "TRUE" : "FALSE"), item->LoreGroup, item->Lore); - c->Message(Chat::White, ">> NoDrop: %u, NoRent: %u, NoPet: %u, NoTransfer: %u, FVNoDrop: %u", - item->NoDrop, item->NoRent, (uint8)item->NoPet, (uint8)item->NoTransfer, item->FVNoDrop); - - if (item->IsClassBook()) { - c->Message(Chat::White, "*** This item is a Book (filename:'%s') ***", item->Filename); - } - else if (item->IsClassBag()) { - c->Message(Chat::White, "*** This item is a Container (%u slots) ***", item->BagSlots); - } - else { - c->Message(Chat::White, "*** This item is Common ***"); - c->Message(Chat::White, ">> Classes: %u, Races: %u, Slots: %u", item->Classes, item->Races, item->Slots); - c->Message(Chat::White, ">> ReqSkill: %u, ReqLevel: %u, RecLevel: %u", item->RecSkill, item->ReqLevel, item->RecLevel); - c->Message(Chat::White, ">> SkillModType: %u, SkillModValue: %i", item->SkillModType, item->SkillModValue); - c->Message(Chat::White, ">> BaneRaceType: %u, BaneRaceDamage: %u, BaneBodyType: %u, BaneBodyDamage: %i", - item->BaneDmgRace, item->BaneDmgRaceAmt, item->BaneDmgBody, item->BaneDmgAmt); - c->Message(Chat::White, ">> Magic: %s, SpellID: %i, ProcLevel: %u, Charges: %u, MaxCharges: %u", - (item->Magic ? "TRUE" : "FALSE"), item->Click.Effect, item->Click.Level, inst->GetCharges(), item->MaxCharges); - c->Message(Chat::White, ">> EffectType: 0x%02X, CastTime: %.2f", (uint8)item->Click.Type, ((double)item->CastTime / 1000)); - } - - if (c->Admin() >= 200) - c->Message(Chat::White, ">> MinStatus: %u", item->MinStatus); -} - -void command_uptime(Client *c, const Seperator *sep) -{ - if (!worldserver.Connected()) - c->Message(Chat::White, "Error: World server disconnected"); - else - { - auto pack = new ServerPacket(ServerOP_Uptime, sizeof(ServerUptime_Struct)); - ServerUptime_Struct* sus = (ServerUptime_Struct*) pack->pBuffer; - strcpy(sus->adminname, c->GetName()); - if (sep->IsNumber(1) && atoi(sep->arg[1]) > 0) - sus->zoneserverid = atoi(sep->arg[1]); - worldserver.SendPacket(pack); - safe_delete(pack); - } -} - -void command_flag(Client *c, const Seperator *sep) -{ - if(sep->arg[2][0] == 0) { - if (!c->GetTarget() || (c->GetTarget() && c->GetTarget() == c)) { - c->UpdateAdmin(); - c->Message(Chat::White, "Refreshed your admin flag from DB."); - } else if (c->GetTarget() && c->GetTarget() != c && c->GetTarget()->IsClient()) { - c->GetTarget()->CastToClient()->UpdateAdmin(); - c->Message(Chat::White, "%s's admin flag has been refreshed.", c->GetTarget()->GetName()); - c->GetTarget()->Message(Chat::White, "%s refreshed your admin flag.", c->GetName()); - } - } - else if (!sep->IsNumber(1) || atoi(sep->arg[1]) < -2 || atoi(sep->arg[1]) > 255 || strlen(sep->arg[2]) == 0) - c->Message(Chat::White, "Usage: #flag [status] [acctname]"); - - else if (c->Admin() < commandChangeFlags) { - //this check makes banning players by less than this level - //impossible, but i'll leave it in anyways - c->Message(Chat::White, "You may only refresh your own flag, doing so now."); - c->UpdateAdmin(); - } - else { - if (atoi(sep->arg[1]) > c->Admin()) - c->Message(Chat::White, "You cannot set people's status to higher than your own"); - else if (atoi(sep->arg[1]) < 0 && c->Admin() < commandBanPlayers) - c->Message(Chat::White, "You have too low of status to suspend/ban"); - else if (!database.SetAccountStatus(sep->argplus[2], atoi(sep->arg[1]))) - c->Message(Chat::White, "Unable to set GM Flag."); - else { - c->Message(Chat::White, "Set GM Flag on account."); - - std::string user; - std::string loginserver; - ParseAccountString(sep->argplus[2], user, loginserver); - - ServerPacket pack(ServerOP_FlagUpdate, 6); - *((uint32*) pack.pBuffer) = database.GetAccountIDByName(user.c_str(), loginserver.c_str()); - *((int16*) &pack.pBuffer[4]) = atoi(sep->arg[1]); - worldserver.SendPacket(&pack); - } - } -} - -void command_time(Client *c, const Seperator *sep) -{ - char timeMessage[255]; - int minutes=0; - if(sep->IsNumber(1)) { - if(sep->IsNumber(2)) { - minutes=atoi(sep->arg[2]); - } - c->Message(Chat::Red, "Setting world time to %s:%i (Timezone: 0)...", sep->arg[1], minutes); - zone->SetTime(atoi(sep->arg[1])+1, minutes); - LogInfo("{} :: Setting world time to {}:{} (Timezone: 0)...", c->GetCleanName(), sep->arg[1], minutes); - } - else { - c->Message(Chat::Red, "To set the Time: #time HH [MM]"); - TimeOfDay_Struct eqTime; - zone->zone_time.GetCurrentEQTimeOfDay( time(0), &eqTime); - sprintf(timeMessage,"%02d:%s%d %s (Timezone: %ih %im)", - ((eqTime.hour - 1) % 12) == 0 ? 12 : ((eqTime.hour - 1) % 12), - (eqTime.minute < 10) ? "0" : "", - eqTime.minute, - (eqTime.hour >= 13) ? "pm" : "am", - zone->zone_time.getEQTimeZoneHr(), - zone->zone_time.getEQTimeZoneMin() - ); - c->Message(Chat::Red, "It is now %s.", timeMessage); - LogInfo("Current Time is: {}", timeMessage); - } -} - -void command_guild(Client *c, const Seperator *sep) -{ - int admin=c->Admin(); - Mob *target=c->GetTarget(); - - if (strcasecmp(sep->arg[1], "help") == 0) { - c->Message(Chat::White, "GM Guild commands:"); - c->Message(Chat::White, " #guild list - lists all guilds on the server"); - c->Message(Chat::White, " #guild create {guildleader charname or CharID} guildname"); - c->Message(Chat::White, " #guild delete guildID"); - c->Message(Chat::White, " #guild rename guildID newname"); - c->Message(Chat::White, " #guild set charname guildID (0=no guild)"); - c->Message(Chat::White, " #guild setrank charname rank"); - c->Message(Chat::White, " #guild setleader guildID {guildleader charname or CharID}"); - } - else if (strcasecmp(sep->arg[1], "status") == 0 || strcasecmp(sep->arg[1], "stat") == 0) { - Client* client = 0; - if (sep->arg[2][0] != 0) - client = entity_list.GetClientByName(sep->argplus[2]); - else if (target != 0 && target->IsClient()) - client = target->CastToClient(); - if (client == 0) - c->Message(Chat::White, "You must target someone or specify a character name"); - else if ((client->Admin() >= minStatusToEditOtherGuilds && admin < minStatusToEditOtherGuilds) && client->GuildID() != c->GuildID()) // no peeping for GMs, make sure tell message stays the same - c->Message(Chat::White, "You must target someone or specify a character name."); - else { - if (client->IsInAGuild()) - c->Message(Chat::White, "%s is not in a guild.", client->GetName()); - else if (guild_mgr.IsGuildLeader(client->GuildID(), client->CharacterID())) - c->Message(Chat::White, "%s is the leader of <%s> rank: %s", client->GetName(), guild_mgr.GetGuildName(client->GuildID()), guild_mgr.GetRankName(client->GuildID(), client->GuildRank())); - else - c->Message(Chat::White, "%s is a member of <%s> rank: %s", client->GetName(), guild_mgr.GetGuildName(client->GuildID()), guild_mgr.GetRankName(client->GuildID(), client->GuildRank())); - } - } - else if (strcasecmp(sep->arg[1], "info") == 0) { - if (sep->arg[2][0] == 0 && c->IsInAGuild()) { - if (admin >= minStatusToEditOtherGuilds) - c->Message(Chat::White, "Usage: #guildinfo guild_id"); - else - c->Message(Chat::White, "You're not in a guild"); - } - else { - uint32 tmp = GUILD_NONE; - if (sep->arg[2][0] == 0) - tmp = c->GuildID(); - else if (admin >= minStatusToEditOtherGuilds) - tmp = atoi(sep->arg[2]); - - if(tmp != GUILD_NONE) - guild_mgr.DescribeGuild(c, tmp); - } - } - /* - else if (strcasecmp(sep->arg[1], "edit") == 0) { - if (c->GuildDBID() == 0) - c->Message(Chat::White, "You arent in a guild!"); - else if (!sep->IsNumber(2)) - c->Message(Chat::White, "Error: invalid rank #."); - else if (atoi(sep->arg[2]) < 0 || atoi(sep->arg[2]) > GUILD_MAX_RANK) - c->Message(Chat::White, "Error: invalid rank #."); - else if (!c->GuildRank() == 0) - c->Message(Chat::White, "You must be rank %s to use edit.", guilds[c->GuildEQID()].rank[0].rankname); - else if (!worldserver.Connected()) - c->Message(Chat::White, "Error: World server dirconnected"); - else { - if (!helper_guild_edit(c, c->GuildDBID(), c->GuildEQID(), atoi(sep->arg[2]), sep->arg[3], sep->argplus[4])) { - c->Message(Chat::White, " #guild edit rank title newtitle"); - c->Message(Chat::White, " #guild edit rank permission 0/1"); - } - else { - ServerPacket* pack = new ServerPacket(ServerOP_RefreshGuild, 5); - int32 geqid=c->GuildEQID(); - memcpy(pack->pBuffer, &geqid, 4); - worldserver.SendPacket(pack); - safe_delete(pack); - } - } - } - else if (strcasecmp(sep->arg[1], "gmedit") == 0 && admin >= 100) { - if (!sep->IsNumber(2)) - c->Message(Chat::White, "Error: invalid guilddbid."); - else if (!sep->IsNumber(3)) - c->Message(Chat::White, "Error: invalid rank #."); - else if (atoi(sep->arg[3]) < 0 || atoi(sep->arg[3]) > GUILD_MAX_RANK) - c->Message(Chat::White, "Error: invalid rank #."); - else if (!worldserver.Connected()) - c->Message(Chat::White, "Error: World server dirconnected"); - else { - uint32 eqid = database.GetGuildEQID(atoi(sep->arg[2])); - if (eqid == GUILD_NONE) - c->Message(Chat::White, "Error: Guild not found"); - else if (!helper_guild_edit(c, atoi(sep->arg[2]), eqid, atoi(sep->arg[3]), sep->arg[4], sep->argplus[5])) { - c->Message(Chat::White, " #guild gmedit guilddbid rank title newtitle"); - c->Message(Chat::White, " #guild gmedit guilddbid rank permission 0/1"); - } - else { - ServerPacket* pack = new ServerPacket(ServerOP_RefreshGuild, 5); - memcpy(pack->pBuffer, &eqid, 4); - worldserver.SendPacket(pack); - safe_delete(pack); - } - } - } - */ - else if (strcasecmp(sep->arg[1], "set") == 0) { - if (!sep->IsNumber(3)) - c->Message(Chat::White, "Usage: #guild set charname guildgbid (0 = clear guildtag)"); - else { - uint32 guild_id = atoi(sep->arg[3]); - - if(guild_id == 0) - guild_id = GUILD_NONE; - else if(!guild_mgr.GuildExists(guild_id)) { - c->Message(Chat::Red, "Guild %d does not exist.", guild_id); - return; - } - - uint32 charid = database.GetCharacterID(sep->arg[2]); - if(charid == 0) { - c->Message(Chat::Red, "Unable to find character '%s'", charid); - return; - } - - //we could do the checking we need for guild_mgr.CheckGMStatus, but im lazy right now - if(admin < minStatusToEditOtherGuilds) { - c->Message(Chat::Red, "Access denied."); - return; - } - - if(guild_id == GUILD_NONE) { - LogGuilds("[{}]: Removing [{}] ([{}]) from guild with GM command", c->GetName(), sep->arg[2], charid); - } else { - LogGuilds("[{}]: Putting [{}] ([{}]) into guild [{}] ([{}]) with GM command", c->GetName(), sep->arg[2], charid, guild_mgr.GetGuildName(guild_id), guild_id); - } - - if(!guild_mgr.SetGuild(charid, guild_id, GUILD_MEMBER)) { - c->Message(Chat::Red, "Error putting '%s' into guild %d", sep->arg[2], guild_id); - } else { - c->Message(Chat::White, "%s has been put into guild %d", sep->arg[2], guild_id); - } - } - } - /*else if (strcasecmp(sep->arg[1], "setdoor") == 0 && admin >= minStatusToEditOtherGuilds) { - - if (!sep->IsNumber(2)) - c->Message(Chat::White, "Usage: #guild setdoor guildEQid (0 = delete guilddoor)"); - else { -// guild doors - if((!guilds[atoi(sep->arg[2])].databaseID) && (atoi(sep->arg[2])!=0) ) - { - - c->Message(Chat::White, "These is no guild with this guildEQid"); - } - else { - c->SetIsSettingGuildDoor(true); - c->Message(Chat::White, "Click on a door you want to become a guilddoor"); - c->SetSetGuildDoorID(atoi(sep->arg[2])); - } - } - }*/ - else if (strcasecmp(sep->arg[1], "setrank") == 0) { - int rank = atoi(sep->arg[3]); - if (!sep->IsNumber(3)) - c->Message(Chat::White, "Usage: #guild setrank charname rank"); - else if (rank < 0 || rank > GUILD_MAX_RANK) - c->Message(Chat::White, "Error: invalid rank #."); - else { - uint32 charid = database.GetCharacterID(sep->arg[2]); - if(charid == 0) { - c->Message(Chat::Red, "Unable to find character '%s'", charid); - return; - } - - //we could do the checking we need for guild_mgr.CheckGMStatus, but im lazy right now - if(admin < minStatusToEditOtherGuilds) { - c->Message(Chat::Red, "Access denied."); - return; - } - - LogGuilds("[{}]: Setting [{}] ([{}])'s guild rank to [{}] with GM command", c->GetName(), sep->arg[2], charid, rank); - - if(!guild_mgr.SetGuildRank(charid, rank)) - c->Message(Chat::Red, "Error while setting rank %d on '%s'.", rank, sep->arg[2]); - else - c->Message(Chat::White, "%s has been set to rank %d", sep->arg[2], rank); - } - } - else if (strcasecmp(sep->arg[1], "create") == 0) { - if (sep->arg[3][0] == 0) - c->Message(Chat::White, "Usage: #guild create {guildleader charname or CharID} guild name"); - else if (!worldserver.Connected()) - c->Message(Chat::White, "Error: World server dirconnected"); - else { - uint32 leader = 0; - if (sep->IsNumber(2)) { - leader = atoi(sep->arg[2]); - } else if((leader=database.GetCharacterID(sep->arg[2])) != 0) { - //got it from the db.. - } else { - c->Message(Chat::Red, "Unable to find char '%s'", sep->arg[2]); - return; - } - if (leader == 0) { - c->Message(Chat::White, "Guild leader not found."); - return; - } - - uint32 tmp = guild_mgr.FindGuildByLeader(leader); - if (tmp != GUILD_NONE) { - c->Message(Chat::White, "Error: %s already is the leader of DB# %i '%s'.", sep->arg[2], tmp, guild_mgr.GetGuildName(tmp)); - } - else { - - if(admin < minStatusToEditOtherGuilds) { - c->Message(Chat::Red, "Access denied."); - return; - } - - uint32 id = guild_mgr.CreateGuild(sep->argplus[3], leader); - - LogGuilds("[{}]: Creating guild [{}] with leader [{}] with GM command. It was given id [{}]", c->GetName(), - sep->argplus[3], leader, (unsigned long)id); - - if (id == GUILD_NONE) - c->Message(Chat::White, "Guild creation failed."); - else { - c->Message(Chat::White, "Guild created: Leader: %i, number %i: %s", leader, id, sep->argplus[3]); - - if(!guild_mgr.SetGuild(leader, id, GUILD_LEADER)) - c->Message(Chat::White, "Unable to set guild leader's guild in the database. Your going to have to run #guild set"); - } - - } - } - } - else if (strcasecmp(sep->arg[1], "delete") == 0) { - if (!sep->IsNumber(2)) - c->Message(Chat::White, "Usage: #guild delete guildID"); - else if (!worldserver.Connected()) - c->Message(Chat::White, "Error: World server dirconnected"); - else { - uint32 id = atoi(sep->arg[2]); - - if(!guild_mgr.GuildExists(id)) { - c->Message(Chat::White, "Guild %d does not exist!", id); - return; - } - - if(admin < minStatusToEditOtherGuilds) { - //this person is not allowed to just edit any guild, check this guild's min status. - if(c->GuildID() != id) { - c->Message(Chat::Red, "Access denied to edit other people's guilds"); - return; - } else if(!guild_mgr.CheckGMStatus(id, admin)) { - c->Message(Chat::Red, "Access denied to edit your guild with GM commands."); - return; - } - } - - LogGuilds("[{}]: Deleting guild [{}] ([{}]) with GM command", c->GetName(), - guild_mgr.GetGuildName(id), id); - - if (!guild_mgr.DeleteGuild(id)) - c->Message(Chat::White, "Guild delete failed."); - else { - c->Message(Chat::White, "Guild %d deleted.", id); - } - } - } - else if (strcasecmp(sep->arg[1], "rename") == 0) { - if ((!sep->IsNumber(2)) || sep->arg[3][0] == 0) - c->Message(Chat::White, "Usage: #guild rename guildID newname"); - else if (!worldserver.Connected()) - c->Message(Chat::White, "Error: World server dirconnected"); - else { - uint32 id = atoi(sep->arg[2]); - - if(!guild_mgr.GuildExists(id)) { - c->Message(Chat::White, "Guild %d does not exist!", id); - return; - } - - if(admin < minStatusToEditOtherGuilds) { - //this person is not allowed to just edit any guild, check this guild's min status. - if(c->GuildID() != id) { - c->Message(Chat::Red, "Access denied to edit other people's guilds"); - return; - } else if(!guild_mgr.CheckGMStatus(id, admin)) { - c->Message(Chat::Red, "Access denied to edit your guild with GM commands."); - return; - } - } - - LogGuilds("[{}]: Renaming guild [{}] ([{}]) to [{}] with GM command", c->GetName(), - guild_mgr.GetGuildName(id), id, sep->argplus[3]); - - if (!guild_mgr.RenameGuild(id, sep->argplus[3])) - c->Message(Chat::White, "Guild rename failed."); - else { - c->Message(Chat::White, "Guild %d renamed to %s", id, sep->argplus[3]); - } - } - } - else if (strcasecmp(sep->arg[1], "setleader") == 0) { - if (sep->arg[3][0] == 0 || !sep->IsNumber(2)) - c->Message(Chat::White, "Usage: #guild setleader guild_id {guildleader charname or CharID}"); - else if (!worldserver.Connected()) - c->Message(Chat::White, "Error: World server dirconnected"); - else { - uint32 leader = 0; - if (sep->IsNumber(2)) { - leader = atoi(sep->arg[2]); - } else if((leader=database.GetCharacterID(sep->arg[2])) != 0) { - //got it from the db.. - } else { - c->Message(Chat::Red, "Unable to find char '%s'", sep->arg[2]); - return; - } - - uint32 tmpdb = guild_mgr.FindGuildByLeader(leader); - if (leader == 0) - c->Message(Chat::White, "New leader not found."); - else if (tmpdb != 0) { - c->Message(Chat::White, "Error: %s already is the leader of guild # %i", sep->arg[2], tmpdb); - } - else { - uint32 id = atoi(sep->arg[2]); - - if(!guild_mgr.GuildExists(id)) { - c->Message(Chat::White, "Guild %d does not exist!", id); - return; - } - - if(admin < minStatusToEditOtherGuilds) { - //this person is not allowed to just edit any guild, check this guild's min status. - if(c->GuildID() != id) { - c->Message(Chat::Red, "Access denied to edit other people's guilds"); - return; - } else if(!guild_mgr.CheckGMStatus(id, admin)) { - c->Message(Chat::Red, "Access denied to edit your guild with GM commands."); - return; - } - } - - LogGuilds("[{}]: Setting leader of guild [{}] ([{}]) to [{}] with GM command", c->GetName(), - guild_mgr.GetGuildName(id), id, leader); - - if(!guild_mgr.SetGuildLeader(id, leader)) - c->Message(Chat::White, "Guild leader change failed."); - else { - c->Message(Chat::White, "Guild leader changed: guild # %d, Leader: %s", id, sep->argplus[3]); - } - } - } - } - else if (strcasecmp(sep->arg[1], "list") == 0) { - if(admin < minStatusToEditOtherGuilds) { - c->Message(Chat::Red, "Access denied."); - return; - } - guild_mgr.ListGuilds(c); - } - else { - c->Message(Chat::White, "Unknown guild command, try #guild help"); - } -} -/* -bool helper_guild_edit(Client *c, uint32 dbid, uint32 eqid, uint8 rank, const char* what, const char* value) { - struct GuildRankLevel_Struct grl; - strcpy(grl.rankname, guild_mgr.GetRankName(eqid, rank)); - grl.demote = guilds[eqid].rank[rank].demote; - grl.heargu = guilds[eqid].rank[rank].heargu; - grl.invite = guilds[eqid].rank[rank].invite; - grl.motd = guilds[eqid].rank[rank].motd; - grl.promote = guilds[eqid].rank[rank].promote; - grl.remove = guilds[eqid].rank[rank].remove; - grl.speakgu = guilds[eqid].rank[rank].speakgu; - grl.warpeace = guilds[eqid].rank[rank].warpeace; - - if (strcasecmp(what, "title") == 0) { - if (strlen(value) > 100) - c->Message(Chat::White, "Error: Title has a maxium length of 100 characters."); - else - strcpy(grl.rankname, value); - } - else if (rank == 0) - c->Message(Chat::White, "Error: Rank 0's permissions can not be changed."); - else { - if (!(strlen(value) == 1 && (value[0] == '0' || value[0] == '1'))) - - return false; - if (strcasecmp(what, "demote") == 0) - grl.demote = (value[0] == '1'); - else if (strcasecmp(what, "heargu") == 0) - grl.heargu = (value[0] == '1'); - else if (strcasecmp(what, "invite") == 0) - grl.invite = (value[0] == '1'); - else if (strcasecmp(what, "motd") == 0) - grl.motd = (value[0] == '1'); - else if (strcasecmp(what, "promote") == 0) - grl.promote = (value[0] == '1'); - else if (strcasecmp(what, "remove") == 0) - - grl.remove = (value[0] == '1'); - else if (strcasecmp(what, "speakgu") == 0) - grl.speakgu = (value[0] == '1'); - else if (strcasecmp(what, "warpeace") == 0) - grl.warpeace = (value[0] == '1'); - else - c->Message(Chat::White, "Error: Permission name not recognized."); - } - if (!database.EditGuild(dbid, rank, &grl)) - c->Message(Chat::White, "Error: database.EditGuild() failed"); - return true; -}*/ - -void command_zonestatus(Client *c, const Seperator *sep) -{ - if (!worldserver.Connected()) - c->Message(Chat::White, "Error: World server disconnected"); - else { - auto pack = new ServerPacket(ServerOP_ZoneStatus, strlen(c->GetName()) + 2); - memset(pack->pBuffer, (uint8) c->Admin(), 1); - strcpy((char *) &pack->pBuffer[1], c->GetName()); - worldserver.SendPacket(pack); - delete pack; - } -} - -void command_doanim(Client *c, const Seperator *sep) -{ - if (!sep->IsNumber(1)) - c->Message(Chat::White, "Usage: #DoAnim [number]"); - else - if (c->Admin() >= commandDoAnimOthers) - if (c->GetTarget() == 0) - c->Message(Chat::White, "Error: You need a target."); - else - c->GetTarget()->DoAnim(atoi(sep->arg[1]),atoi(sep->arg[2])); - else - c->DoAnim(atoi(sep->arg[1]),atoi(sep->arg[2])); -} - -void command_dz(Client* c, const Seperator* sep) -{ - if (!c || !zone) { - return; - } - - if (strcasecmp(sep->arg[1], "expedition") == 0) - { - if (strcasecmp(sep->arg[2], "list") == 0) - { - std::vector expeditions; - for (const auto& expedition : zone->expedition_cache) - { - expeditions.emplace_back(expedition.second.get()); - } - - std::sort(expeditions.begin(), expeditions.end(), - [](const Expedition* lhs, const Expedition* rhs) { - return lhs->GetID() < rhs->GetID(); - }); - - c->Message(Chat::White, fmt::format("Total Active Expeditions: [{}]", expeditions.size()).c_str()); - for (const auto& expedition : expeditions) - { - auto leader_saylink = EQ::SayLinkEngine::GenerateQuestSaylink(fmt::format( - "#goto {}", expedition->GetLeaderName()), false, expedition->GetLeaderName()); - auto zone_saylink = EQ::SayLinkEngine::GenerateQuestSaylink(fmt::format( - "#zoneinstance {}", expedition->GetInstanceID()), false, "zone"); - - auto seconds = expedition->GetDynamicZone().GetSecondsRemaining(); - - c->Message(Chat::White, fmt::format( - "expedition id: [{}] dz id: [{}] name: [{}] leader: [{}] {}: [{}]:[{}]:[{}]:[{}] members: [{}] remaining: [{:02}:{:02}:{:02}]", - expedition->GetID(), - expedition->GetDynamicZoneID(), - expedition->GetName(), - leader_saylink, - zone_saylink, - ZoneName(expedition->GetDynamicZone().GetZoneID()), - expedition->GetDynamicZone().GetZoneID(), - expedition->GetInstanceID(), - expedition->GetDynamicZone().GetZoneVersion(), - expedition->GetMemberCount(), - seconds / 3600, // hours - (seconds / 60) % 60, // minutes - seconds % 60 // seconds - ).c_str()); - } - } - else if (strcasecmp(sep->arg[2], "reload") == 0) - { - Expedition::CacheAllFromDatabase(); - c->Message(Chat::White, fmt::format( - "Reloaded [{}] expeditions to cache from database.", zone->expedition_cache.size() - ).c_str()); - } - else if (strcasecmp(sep->arg[2], "destroy") == 0 && sep->IsNumber(3)) - { - auto expedition_id = std::strtoul(sep->arg[3], nullptr, 10); - auto expedition = Expedition::FindCachedExpeditionByID(expedition_id); - if (expedition) - { - c->Message(Chat::White, fmt::format("Destroying expedition [{}] ({})", - expedition_id, expedition->GetName()).c_str()); - expedition->RemoveAllMembers(); - } - else - { - c->Message(Chat::Red, fmt::format("Failed to destroy expedition [{}]", sep->arg[3]).c_str()); - } - } - else if (strcasecmp(sep->arg[2], "unlock") == 0 && sep->IsNumber(3)) - { - auto expedition_id = std::strtoul(sep->arg[3], nullptr, 10); - auto expedition = Expedition::FindCachedExpeditionByID(expedition_id); - if (expedition) - { - c->Message(Chat::White, fmt::format("Unlocking expedition [{}]", expedition_id).c_str()); - expedition->SetLocked(false, ExpeditionLockMessage::None, true); - } - else - { - c->Message(Chat::Red, fmt::format("Failed to find expedition [{}]", sep->arg[3]).c_str()); - } - } - } - else if (strcasecmp(sep->arg[1], "list") == 0) - { - std::string query = SQL( - SELECT - dynamic_zones.id, - dynamic_zones.type, - instance_list.id, - instance_list.zone, - instance_list.version, - instance_list.start_time, - instance_list.duration, - COUNT(instance_list_player.id) member_count - FROM dynamic_zones - INNER JOIN instance_list ON dynamic_zones.instance_id = instance_list.id - LEFT JOIN instance_list_player ON instance_list.id = instance_list_player.id - GROUP BY instance_list.id - ORDER BY dynamic_zones.id; - ); - - auto results = database.QueryDatabase(query); - if (results.Success()) - { - c->Message(Chat::White, fmt::format("Total Dynamic Zones: [{}]", results.RowCount()).c_str()); - for (auto row = results.begin(); row != results.end(); ++row) - { - auto start_time = strtoul(row[5], nullptr, 10); - auto duration = strtoul(row[6], nullptr, 10); - auto expire_time = std::chrono::system_clock::from_time_t(start_time + duration); - - auto now = std::chrono::system_clock::now(); - auto remaining = std::chrono::duration_cast(expire_time - now); - auto seconds = std::max(0, static_cast(remaining.count())); - - bool is_expired = now > expire_time; - if (!is_expired || strcasecmp(sep->arg[2], "all") == 0) - { - uint32_t instance_id = strtoul(row[2], nullptr, 10); - auto zone_saylink = is_expired ? "zone" : EQ::SayLinkEngine::GenerateQuestSaylink( - fmt::format("#zoneinstance {}", instance_id), false, "zone"); - - c->Message(Chat::White, fmt::format( - "dz id: [{}] type: [{}] {}: [{}]:[{}]:[{}] members: [{}] remaining: [{:02}:{:02}:{:02}]", - strtoul(row[0], nullptr, 10), // dynamic_zone_id - strtoul(row[1], nullptr, 10), // dynamic_zone_type - zone_saylink, - strtoul(row[3], nullptr, 10), // instance_zone_id - instance_id, // instance_id - strtoul(row[4], nullptr, 10), // instance_zone_version - strtoul(row[7], nullptr, 10), // instance member_count - seconds / 3600, // hours - (seconds / 60) % 60, // minutes - seconds % 60 // seconds - ).c_str()); - } - } - } - } - else if (strcasecmp(sep->arg[1], "lockouts") == 0) - { - if (strcasecmp(sep->arg[2], "remove") == 0 && sep->arg[3][0] != '\0') - { - if (sep->arg[5][0] == '\0') - { - c->Message(Chat::White, fmt::format( - "Removing [{}] lockouts on [{}].", sep->arg[4][0] ? sep->arg[4] : "all", sep->arg[3] - ).c_str()); - } - else - { - c->Message(Chat::White, fmt::format( - "Removing [{}]:[{}] lockout on [{}].", sep->arg[4], sep->arg[5], sep->arg[3] - ).c_str()); - } - Expedition::RemoveLockoutsByCharacterName(sep->arg[3], sep->arg[4], sep->arg[5]); - } - } - else if (strcasecmp(sep->arg[1], "makeleader") == 0 && sep->IsNumber(2) && sep->arg[3][0] != '\0') - { - auto expedition_id = std::strtoul(sep->arg[2], nullptr, 10); - auto expedition = Expedition::FindCachedExpeditionByID(expedition_id); - if (expedition) - { - auto char_name = FormatName(sep->arg[3]); - c->Message(Chat::White, fmt::format("Setting expedition [{}] leader to [{}]", expedition_id, char_name).c_str()); - expedition->SendWorldMakeLeaderRequest(c->CharacterID(), char_name); - } - else - { - c->Message(Chat::Red, fmt::format("Failed to find expedition [{}]", expedition_id).c_str()); - } - } - else - { - c->Message(Chat::White, "#dz usage:"); - c->Message(Chat::White, "#dz expedition list - list expeditions in current zone cache"); - c->Message(Chat::White, "#dz expedition reload - reload expedition zone cache from database"); - c->Message(Chat::White, "#dz expedition destroy - destroy expedition globally (must be in cache)"); - c->Message(Chat::White, "#dz expedition unlock - unlock expedition"); - c->Message(Chat::White, "#dz list [all] - list dynamic zone instances from database -- 'all' includes expired"); - c->Message(Chat::White, "#dz lockouts remove - delete all of character's expedition lockouts"); - c->Message(Chat::White, "#dz lockouts remove \"\" - delete lockouts by expedition"); - c->Message(Chat::White, "#dz lockouts remove \"\" \"\" - delete lockout by expedition event"); - c->Message(Chat::White, "#dz makeleader - set new expedition leader"); - } -} - -void command_dzkickplayers(Client* c, const Seperator* sep) -{ - if (c) - { - auto expedition = c->GetExpedition(); - if (expedition) - { - expedition->DzKickPlayers(c); - } - } -} - -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 = content_db.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 = content_db.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]", - EQ::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(); - if (!target) - c->Message(Chat::White,"Error: This command requires a target"); - else - { - if (target->RandomizeFeatures()) - c->Message(Chat::White,"Features Randomized"); - else - c->Message(Chat::White,"This command requires a Playable Race as the target"); - } -} - -void command_face(Client *c, const Seperator *sep) -{ - Mob *target=c->GetTarget(); - if (!sep->IsNumber(1)) - c->Message(Chat::White,"Usage: #face [number of face]"); - else if (!target) - c->Message(Chat::White,"Error: this command requires a target"); - else { - uint16 Race = target->GetRace(); - uint8 Gender = target->GetGender(); - uint8 Texture = 0xFF; - uint8 HelmTexture = 0xFF; - uint8 HairColor = target->GetHairColor(); - uint8 BeardColor = target->GetBeardColor(); - uint8 EyeColor1 = target->GetEyeColor1(); - uint8 EyeColor2 = target->GetEyeColor2(); - uint8 HairStyle = target->GetHairStyle(); - uint8 LuclinFace = atoi(sep->arg[1]); - uint8 Beard = target->GetBeard(); - uint32 DrakkinHeritage = target->GetDrakkinHeritage(); - uint32 DrakkinTattoo = target->GetDrakkinTattoo(); - uint32 DrakkinDetails = target->GetDrakkinDetails(); - - target->SendIllusionPacket(Race, Gender, Texture, HelmTexture, HairColor, BeardColor, - EyeColor1, EyeColor2, HairStyle, LuclinFace, Beard, 0xFF, - DrakkinHeritage, DrakkinTattoo, DrakkinDetails); - - c->Message(Chat::White,"Face = %i", atoi(sep->arg[1])); - } -} - void command_findaliases(Client *c, const Seperator *sep) { if (!sep->arg[1][0]) { @@ -7291,6147 +970,6 @@ void command_findaliases(Client *c, const Seperator *sep) c->Message(Chat::White, "%d command alias%s listed.", commandaliasesshown, commandaliasesshown != 1 ? "es" : ""); } -void command_details(Client *c, const Seperator *sep) -{ - Mob *target=c->GetTarget(); - if (!sep->IsNumber(1)) - c->Message(Chat::White,"Usage: #details [number of drakkin detail]"); - else if (!target) - c->Message(Chat::White,"Error: this command requires a target"); - else { - uint16 Race = target->GetRace(); - uint8 Gender = target->GetGender(); - uint8 Texture = 0xFF; - uint8 HelmTexture = 0xFF; - uint8 HairColor = target->GetHairColor(); - uint8 BeardColor = target->GetBeardColor(); - uint8 EyeColor1 = target->GetEyeColor1(); - uint8 EyeColor2 = target->GetEyeColor2(); - uint8 HairStyle = target->GetHairStyle(); - uint8 LuclinFace = target->GetLuclinFace(); - uint8 Beard = target->GetBeard(); - uint32 DrakkinHeritage = target->GetDrakkinHeritage(); - uint32 DrakkinTattoo = target->GetDrakkinTattoo(); - uint32 DrakkinDetails = atoi(sep->arg[1]); - - target->SendIllusionPacket(Race, Gender, Texture, HelmTexture, HairColor, BeardColor, - EyeColor1, EyeColor2, HairStyle, LuclinFace, Beard, 0xFF, - DrakkinHeritage, DrakkinTattoo, DrakkinDetails); - - c->Message(Chat::White,"Details = %i", atoi(sep->arg[1])); - } -} - -void command_heritage(Client *c, const Seperator *sep) -{ - Mob *target=c->GetTarget(); - if (!sep->IsNumber(1)) - c->Message(Chat::White,"Usage: #heritage [number of Drakkin heritage]"); - else if (!target) - c->Message(Chat::White,"Error: this command requires a target"); - else { - uint16 Race = target->GetRace(); - uint8 Gender = target->GetGender(); - uint8 Texture = 0xFF; - uint8 HelmTexture = 0xFF; - uint8 HairColor = target->GetHairColor(); - uint8 BeardColor = target->GetBeardColor(); - uint8 EyeColor1 = target->GetEyeColor1(); - uint8 EyeColor2 = target->GetEyeColor2(); - uint8 HairStyle = target->GetHairStyle(); - uint8 LuclinFace = target->GetLuclinFace(); - uint8 Beard = target->GetBeard(); - uint32 DrakkinHeritage = atoi(sep->arg[1]); - uint32 DrakkinTattoo = target->GetDrakkinTattoo(); - uint32 DrakkinDetails = target->GetDrakkinDetails(); - - target->SendIllusionPacket(Race, Gender, Texture, HelmTexture, HairColor, BeardColor, - EyeColor1, EyeColor2, HairStyle, LuclinFace, Beard, 0xFF, - DrakkinHeritage, DrakkinTattoo, DrakkinDetails); - - c->Message(Chat::White,"Heritage = %i", atoi(sep->arg[1])); - } -} - -void command_tattoo(Client *c, const Seperator *sep) -{ - Mob *target=c->GetTarget(); - if (!sep->IsNumber(1)) - c->Message(Chat::White,"Usage: #tattoo [number of Drakkin tattoo]"); - else if (!target) - c->Message(Chat::White,"Error: this command requires a target"); - else { - uint16 Race = target->GetRace(); - uint8 Gender = target->GetGender(); - uint8 Texture = 0xFF; - uint8 HelmTexture = 0xFF; - uint8 HairColor = target->GetHairColor(); - uint8 BeardColor = target->GetBeardColor(); - uint8 EyeColor1 = target->GetEyeColor1(); - uint8 EyeColor2 = target->GetEyeColor2(); - uint8 HairStyle = target->GetHairStyle(); - uint8 LuclinFace = target->GetLuclinFace(); - uint8 Beard = target->GetBeard(); - uint32 DrakkinHeritage = target->GetDrakkinHeritage(); - uint32 DrakkinTattoo = atoi(sep->arg[1]); - uint32 DrakkinDetails = target->GetDrakkinDetails(); - - target->SendIllusionPacket(Race, Gender, Texture, HelmTexture, HairColor, BeardColor, - EyeColor1, EyeColor2, HairStyle, LuclinFace, Beard, 0xFF, - DrakkinHeritage, DrakkinTattoo, DrakkinDetails); - - c->Message(Chat::White,"Tattoo = %i", atoi(sep->arg[1])); - } -} - -void command_helm(Client *c, const Seperator *sep) -{ - Mob *target=c->GetTarget(); - if (!sep->IsNumber(1)) - c->Message(Chat::White,"Usage: #helm [number of helm texture]"); - else if (!target) - c->Message(Chat::White,"Error: this command requires a target"); - else { - uint16 Race = target->GetRace(); - uint8 Gender = target->GetGender(); - uint8 Texture = 0xFF; - uint8 HelmTexture = atoi(sep->arg[1]); - uint8 HairColor = target->GetHairColor(); - uint8 BeardColor = target->GetBeardColor(); - uint8 EyeColor1 = target->GetEyeColor1(); - uint8 EyeColor2 = target->GetEyeColor2(); - uint8 HairStyle = target->GetHairStyle(); - uint8 LuclinFace = target->GetLuclinFace(); - uint8 Beard = target->GetBeard(); - uint32 DrakkinHeritage = target->GetDrakkinHeritage(); - uint32 DrakkinTattoo = target->GetDrakkinTattoo(); - uint32 DrakkinDetails = target->GetDrakkinDetails(); - - target->SendIllusionPacket(Race, Gender, Texture, HelmTexture, HairColor, BeardColor, - EyeColor1, EyeColor2, HairStyle, LuclinFace, Beard, 0xFF, - DrakkinHeritage, DrakkinTattoo, DrakkinDetails); - - c->Message(Chat::White,"Helm = %i", atoi(sep->arg[1])); - } -} - -void command_hair(Client *c, const Seperator *sep) -{ - Mob *target=c->GetTarget(); - if (!sep->IsNumber(1)) - c->Message(Chat::White,"Usage: #hair [number of hair style]"); - else if (!target) - c->Message(Chat::White,"Error: this command requires a target"); - else { - uint16 Race = target->GetRace(); - uint8 Gender = target->GetGender(); - uint8 Texture = 0xFF; - uint8 HelmTexture = 0xFF; - uint8 HairColor = target->GetHairColor(); - uint8 BeardColor = target->GetBeardColor(); - uint8 EyeColor1 = target->GetEyeColor1(); - uint8 EyeColor2 = target->GetEyeColor2(); - uint8 HairStyle = atoi(sep->arg[1]); - uint8 LuclinFace = target->GetLuclinFace(); - uint8 Beard = target->GetBeard(); - uint32 DrakkinHeritage = target->GetDrakkinHeritage(); - uint32 DrakkinTattoo = target->GetDrakkinTattoo(); - uint32 DrakkinDetails = target->GetDrakkinDetails(); - - target->SendIllusionPacket(Race, Gender, Texture, HelmTexture, HairColor, BeardColor, - EyeColor1, EyeColor2, HairStyle, LuclinFace, Beard, 0xFF, - DrakkinHeritage, DrakkinTattoo, DrakkinDetails); - - c->Message(Chat::White,"Hair = %i", atoi(sep->arg[1])); - } -} - -void command_haircolor(Client *c, const Seperator *sep) -{ - Mob *target=c->GetTarget(); - if (!sep->IsNumber(1)) - c->Message(Chat::White,"Usage: #haircolor [number of hair color]"); - else if (!target) - c->Message(Chat::White,"Error: this command requires a target"); - else { - uint16 Race = target->GetRace(); - uint8 Gender = target->GetGender(); - uint8 Texture = 0xFF; - uint8 HelmTexture = 0xFF; - uint8 HairColor = atoi(sep->arg[1]); - uint8 BeardColor = target->GetBeardColor(); - uint8 EyeColor1 = target->GetEyeColor1(); - uint8 EyeColor2 = target->GetEyeColor2(); - uint8 HairStyle = target->GetHairStyle(); - uint8 LuclinFace = target->GetLuclinFace(); - uint8 Beard = target->GetBeard(); - uint32 DrakkinHeritage = target->GetDrakkinHeritage(); - uint32 DrakkinTattoo = target->GetDrakkinTattoo(); - uint32 DrakkinDetails = target->GetDrakkinDetails(); - - target->SendIllusionPacket(Race, Gender, Texture, HelmTexture, HairColor, BeardColor, - EyeColor1, EyeColor2, HairStyle, LuclinFace, Beard, 0xFF, - DrakkinHeritage, DrakkinTattoo, DrakkinDetails); - - c->Message(Chat::White,"Hair Color = %i", atoi(sep->arg[1])); - } -} - -void command_beard(Client *c, const Seperator *sep) -{ - Mob *target=c->GetTarget(); - if (!sep->IsNumber(1)) - c->Message(Chat::White,"Usage: #beard [number of beard style]"); - else if (!target) - c->Message(Chat::White,"Error: this command requires a target"); - else { - uint16 Race = target->GetRace(); - uint8 Gender = target->GetGender(); - uint8 Texture = 0xFF; - uint8 HelmTexture = 0xFF; - uint8 HairColor = target->GetHairColor(); - uint8 BeardColor = target->GetBeardColor(); - uint8 EyeColor1 = target->GetEyeColor1(); - uint8 EyeColor2 = target->GetEyeColor2(); - uint8 HairStyle = target->GetHairStyle(); - uint8 LuclinFace = target->GetLuclinFace(); - uint8 Beard = atoi(sep->arg[1]); - uint32 DrakkinHeritage = target->GetDrakkinHeritage(); - uint32 DrakkinTattoo = target->GetDrakkinTattoo(); - uint32 DrakkinDetails = target->GetDrakkinDetails(); - - target->SendIllusionPacket(Race, Gender, Texture, HelmTexture, HairColor, BeardColor, - EyeColor1, EyeColor2, HairStyle, LuclinFace, Beard, 0xFF, - DrakkinHeritage, DrakkinTattoo, DrakkinDetails); - - c->Message(Chat::White,"Beard = %i", atoi(sep->arg[1])); - } -} - -void command_beardcolor(Client *c, const Seperator *sep) -{ - Mob *target=c->GetTarget(); - if (!sep->IsNumber(1)) - c->Message(Chat::White,"Usage: #beardcolor [number of beard color]"); - else if (!target) - c->Message(Chat::White,"Error: this command requires a target"); - else { - uint16 Race = target->GetRace(); - uint8 Gender = target->GetGender(); - uint8 Texture = 0xFF; - uint8 HelmTexture = 0xFF; - uint8 HairColor = target->GetHairColor(); - uint8 BeardColor = atoi(sep->arg[1]); - uint8 EyeColor1 = target->GetEyeColor1(); - uint8 EyeColor2 = target->GetEyeColor2(); - uint8 HairStyle = target->GetHairStyle(); - uint8 LuclinFace = target->GetLuclinFace(); - uint8 Beard = target->GetBeard(); - uint32 DrakkinHeritage = target->GetDrakkinHeritage(); - uint32 DrakkinTattoo = target->GetDrakkinTattoo(); - uint32 DrakkinDetails = target->GetDrakkinDetails(); - - target->SendIllusionPacket(Race, Gender, Texture, HelmTexture, HairColor, BeardColor, - EyeColor1, EyeColor2, HairStyle, LuclinFace, Beard, 0xFF, - DrakkinHeritage, DrakkinTattoo, DrakkinDetails); - - c->Message(Chat::White,"Beard Color = %i", atoi(sep->arg[1])); - } -} - -void command_scribespells(Client *c, const Seperator *sep) -{ - Client *t = c; - if (c->GetTarget() && c->GetTarget()->IsClient() && c->GetGM()) - t = c->GetTarget()->CastToClient(); - - if(sep->argnum < 1 || !sep->IsNumber(1)) { - c->Message(Chat::White, "FORMAT: #scribespells "); - return; - } - - uint8 max_level = (uint8)atol(sep->arg[1]); - if (!c->GetGM() && max_level > (uint8)RuleI(Character, MaxLevel)) - max_level = (uint8)RuleI(Character, MaxLevel); // default to Character:MaxLevel if we're not a GM & it's higher than the max level - - uint8 min_level = (sep->IsNumber(2) ? (uint8)atol(sep->arg[2]) : 1); // default to 1 if there isn't a 2nd argument - if (!c->GetGM() && min_level > (uint8)RuleI(Character, MaxLevel)) - min_level = (uint8)RuleI(Character, MaxLevel); // default to Character:MaxLevel if we're not a GM & it's higher than the max level - - if(max_level < 1 || min_level < 1) { - c->Message(Chat::White, "ERROR: Level must be greater than 1."); - return; - } - if (min_level > max_level) { - c->Message(Chat::White, "ERROR: Min Level must be less than or equal to Max Level."); - return; - } - - t->Message(Chat::White, "Scribing spells to spellbook."); - if(t != c) - c->Message(Chat::White, "Scribing spells for %s.", t->GetName()); - LogInfo("Scribe spells request for [{}] from [{}], levels: [{}] -> [{}]", t->GetName(), c->GetName(), min_level, max_level); - - int book_slot = t->GetNextAvailableSpellBookSlot(); - int spell_id = 0; - int count = 0; - - for ( ; spell_id < SPDAT_RECORDS && book_slot < EQ::spells::SPELLBOOK_SIZE; ++spell_id) { - if (book_slot == -1) { - t->Message( - 13, - "Unable to scribe spell %s (%i) to spellbook: no more spell book slots available.", - ((spell_id >= 0 && spell_id < SPDAT_RECORDS) ? spells[spell_id].name : "Out-of-range"), - spell_id - ); - if (t != c) - c->Message( - 13, - "Error scribing spells: %s ran out of spell book slots on spell %s (%i)", - t->GetName(), - ((spell_id >= 0 && spell_id < SPDAT_RECORDS) ? spells[spell_id].name : "Out-of-range"), - spell_id - ); - - break; - } - if (spell_id < 0 || spell_id >= SPDAT_RECORDS) { - c->Message(Chat::Red, "FATAL ERROR: Spell id out-of-range (id: %i, min: 0, max: %i)", spell_id, SPDAT_RECORDS); - return; - } - if (book_slot < 0 || book_slot >= EQ::spells::SPELLBOOK_SIZE) { - c->Message(Chat::Red, "FATAL ERROR: Book slot out-of-range (slot: %i, min: 0, max: %i)", book_slot, EQ::spells::SPELLBOOK_SIZE); - return; - } - - while (true) { - if (spells[spell_id].classes[WARRIOR] == 0) // check if spell exists - break; - if (spells[spell_id].classes[t->GetPP().class_ - 1] > max_level) // maximum level - break; - if (spells[spell_id].classes[t->GetPP().class_ - 1] < min_level) // minimum level - break; - if (spells[spell_id].skill == 52) - break; - - uint16 spell_id_ = (uint16)spell_id; - if ((spell_id_ != spell_id) || (spell_id != spell_id_)) { - c->Message(Chat::Red, "FATAL ERROR: Type conversion data loss with spell_id (%i != %u)", spell_id, spell_id_); - return; - } - - if (!IsDiscipline(spell_id_) && !t->HasSpellScribed(spell_id)) { // isn't a discipline & we don't already have it scribed - t->ScribeSpell(spell_id_, book_slot); - ++count; - } - - break; - } - - book_slot = t->GetNextAvailableSpellBookSlot(book_slot); - } - - if (count > 0) { - t->Message(Chat::White, "Successfully scribed %i spells.", count); - if (t != c) - c->Message(Chat::White, "Successfully scribed %i spells for %s.", count, t->GetName()); - } - else { - t->Message(Chat::White, "No spells scribed."); - if (t != c) - c->Message(Chat::White, "No spells scribed for %s.", t->GetName()); - } -} - -void command_scribespell(Client *c, const Seperator *sep) { - uint16 spell_id = 0; - uint16 book_slot = -1; - Client *t=c; - - if(c->GetTarget() && c->GetTarget()->IsClient() && c->GetGM()) - t=c->GetTarget()->CastToClient(); - - if(!sep->arg[1][0]) { - c->Message(Chat::White, "FORMAT: #scribespell "); - return; - } - - spell_id = atoi(sep->arg[1]); - - if(IsValidSpell(spell_id)) { - t->Message(Chat::White, "Scribing spell: %s (%i) to spellbook.", spells[spell_id].name, spell_id); - - if(t != c) - c->Message(Chat::White, "Scribing spell: %s (%i) for %s.", spells[spell_id].name, spell_id, t->GetName()); - - LogInfo("Scribe spell: [{}] ([{}]) request for [{}] from [{}]", spells[spell_id].name, spell_id, t->GetName(), c->GetName()); - - if (spells[spell_id].classes[WARRIOR] != 0 && spells[spell_id].skill != 52 && spells[spell_id].classes[t->GetPP().class_ - 1] > 0 && !IsDiscipline(spell_id)) { - book_slot = t->GetNextAvailableSpellBookSlot(); - - if(book_slot >= 0 && t->FindSpellBookSlotBySpellID(spell_id) < 0) - t->ScribeSpell(spell_id, book_slot); - else { - t->Message(Chat::Red, "Unable to scribe spell: %s (%i) to your spellbook.", spells[spell_id].name, spell_id); - - if(t != c) - c->Message(Chat::Red, "Unable to scribe spell: %s (%i) for %s.", spells[spell_id].name, spell_id, t->GetName()); - } - } - else - c->Message(Chat::Red, "Your target can not scribe this spell."); - } - else - c->Message(Chat::Red, "Spell ID: %i is an unknown spell and cannot be scribed.", spell_id); -} - -void command_unscribespell(Client *c, const Seperator *sep) { - uint16 spell_id = 0; - uint16 book_slot = -1; - Client *t=c; - - if(c->GetTarget() && c->GetTarget()->IsClient() && c->GetGM()) - t=c->GetTarget()->CastToClient(); - - if(!sep->arg[1][0]) { - c->Message(Chat::White, "FORMAT: #unscribespell "); - return; - } - - spell_id = atoi(sep->arg[1]); - - if(IsValidSpell(spell_id)) { - book_slot = t->FindSpellBookSlotBySpellID(spell_id); - - if(book_slot >= 0) { - t->UnscribeSpell(book_slot); - - t->Message(Chat::White, "Unscribing spell: %s (%i) from spellbook.", spells[spell_id].name, spell_id); - - if(t != c) - c->Message(Chat::White, "Unscribing spell: %s (%i) for %s.", spells[spell_id].name, spell_id, t->GetName()); - - LogInfo("Unscribe spell: [{}] ([{}]) request for [{}] from [{}]", spells[spell_id].name, spell_id, t->GetName(), c->GetName()); - } - else { - t->Message(Chat::Red, "Unable to unscribe spell: %s (%i) from your spellbook. This spell is not scribed.", spells[spell_id].name, spell_id); - - if(t != c) - c->Message(Chat::Red, "Unable to unscribe spell: %s (%i) for %s due to spell not scribed.", spells[spell_id].name, spell_id, t->GetName()); - } - } -} - -void command_unscribespells(Client *c, const Seperator *sep) -{ - Client *t=c; - - if(c->GetTarget() && c->GetTarget()->IsClient() && c->GetGM()) - t=c->GetTarget()->CastToClient(); - - t->UnscribeSpellAll(); -} - -void command_untraindisc(Client *c, const Seperator *sep) { - Client *t = c; - if (c->GetTarget() && c->GetTarget()->IsClient() && c->GetGM()) - t = c->GetTarget()->CastToClient(); - - for (int i = 0; i < MAX_PP_DISCIPLINES; i++) { - if (t->GetPP().disciplines.values[i] == atoi(sep->arg[1])) { - t->UntrainDisc(i, 1); - return; - } - } -} - -void command_untraindiscs(Client *c, const Seperator *sep) { - Client *t = c; - if (c->GetTarget() && c->GetTarget()->IsClient() && c->GetGM()) - t = c->GetTarget()->CastToClient(); - - t->UntrainDiscAll(); -} - -void command_wpinfo(Client *c, const Seperator *sep) -{ - Mob *t=c->GetTarget(); - - if (t == nullptr || !t->IsNPC()) { - c->Message(Chat::White,"You must target an NPC to use this."); - return; - } - - NPC *n = t->CastToNPC(); - n->DisplayWaypointInfo(c); -} - -void command_wpadd(Client *c, const Seperator *sep) -{ - int type1 = 0, - type2 = 0, - pause = 0; // Defaults for a new grid - - Mob *target = c->GetTarget(); - if (target && target->IsNPC()) { - Spawn2 *s2info = target->CastToNPC()->respawn2; - - if (s2info == - nullptr) // Can't figure out where this mob's spawn came from... maybe a dynamic mob created by #spawn - { - c->Message( - Chat::White, - "#wpadd FAILED -- Can't determine which spawn record in the database this mob came from!" - ); - return; - } - - if (sep->arg[1][0]) { - if (atoi(sep->arg[1]) >= 0) { - pause = atoi(sep->arg[1]); - } - else { - c->Message(Chat::White, "Usage: #wpadd [pause] [-h]"); - return; - } - } - auto position = c->GetPosition(); - if (strcmp("-h", sep->arg[2]) != 0) { - position.w = -1; - } - - uint32 tmp_grid = content_db.AddWPForSpawn(c, s2info->GetID(), position, pause, type1, type2, zone->GetZoneID()); - if (tmp_grid) { - target->CastToNPC()->SetGrid(tmp_grid); - } - - target->CastToNPC()->AssignWaypoints(target->CastToNPC()->GetGrid()); - c->Message( - Chat::White, - "Waypoint added. Use #wpinfo to see waypoints for this NPC (may need to #repop first)." - ); - } - else { - c->Message(Chat::White, "You must target an NPC to use this."); - } -} - -void command_interrupt(Client *c, const Seperator *sep) -{ - uint16 ci_message=0x01b7, ci_color=0x0121; - - if(sep->arg[1][0]) - ci_message=atoi(sep->arg[1]); - if(sep->arg[2][0]) - ci_color=atoi(sep->arg[2]); - - c->InterruptSpell(ci_message, ci_color); -} - -void command_summonitem(Client *c, const Seperator *sep) -{ - uint32 itemid = 0; - - std::string cmd_msg = sep->msg; - size_t link_open = cmd_msg.find('\x12'); - size_t link_close = cmd_msg.find_last_of('\x12'); - if (link_open != link_close && (cmd_msg.length() - link_open) > EQ::constants::SAY_LINK_BODY_SIZE) { - EQ::SayLinkBody_Struct link_body; - EQ::saylink::DegenerateLinkBody(link_body, cmd_msg.substr(link_open + 1, EQ::constants::SAY_LINK_BODY_SIZE)); - itemid = link_body.item_id; - } - else if (!sep->IsNumber(1)) { - c->Message(Chat::White, "Usage: #summonitem [item id | link] [charges], charges are optional"); - return; - } - else { - itemid = atoi(sep->arg[1]); - } - if (!itemid) { - c->Message(Chat::White, "A valid item id number is required (derived: 0)"); - return; - } - - int16 item_status = 0; - const EQ::ItemData* item = database.GetItem(itemid); - if (item) { - item_status = static_cast(item->MinStatus); - } - - if (item_status > c->Admin()) { - c->Message(Chat::Red, "Error: Insufficient status to summon this item."); - } - else if (sep->argnum == 2 && sep->IsNumber(2)) { - c->SummonItem(itemid, atoi(sep->arg[2])); - } - else if (sep->argnum == 3) { - c->SummonItem(itemid, atoi(sep->arg[2]), atoi(sep->arg[3])); - } - else if (sep->argnum == 4) { - c->SummonItem(itemid, atoi(sep->arg[2]), atoi(sep->arg[3]), atoi(sep->arg[4])); - } - else if (sep->argnum == 5) { - c->SummonItem(itemid, atoi(sep->arg[2]), atoi(sep->arg[3]), atoi(sep->arg[4]), atoi(sep->arg[5])); - } - else if (sep->argnum == 6) { - c->SummonItem( - itemid, - atoi(sep->arg[2]), - atoi(sep->arg[3]), - atoi(sep->arg[4]), - atoi(sep->arg[5]), - atoi(sep->arg[6])); - } - else if (sep->argnum == 7) { - c->SummonItem( - itemid, - atoi(sep->arg[2]), - atoi(sep->arg[3]), - atoi(sep->arg[4]), - atoi(sep->arg[5]), - atoi(sep->arg[6]), - atoi(sep->arg[7])); - } - else if (sep->argnum == 8) { - c->SummonItem( - itemid, - atoi(sep->arg[2]), - atoi(sep->arg[3]), - atoi(sep->arg[4]), - atoi(sep->arg[5]), - atoi(sep->arg[6]), - atoi(sep->arg[7]), - atoi(sep->arg[8])); - } - else { - c->SummonItem(itemid); - } - -} - -void command_giveitem(Client *c, const Seperator *sep) -{ - if (!sep->IsNumber(1)) { - c->Message(Chat::Red, "Usage: #summonitem [item id] [charges], charges are optional"); - } else if(c->GetTarget() == nullptr) { - c->Message(Chat::Red, "You must target a client to give the item to."); - } else if(!c->GetTarget()->IsClient()) { - c->Message(Chat::Red, "You can only give items to players with this command."); - } else { - Client *t = c->GetTarget()->CastToClient(); - uint32 itemid = atoi(sep->arg[1]); - int16 item_status = 0; - const EQ::ItemData* item = database.GetItem(itemid); - if(item) { - item_status = static_cast(item->MinStatus); - } - - if (item_status > c->Admin()) - c->Message(Chat::Red, "Error: Insufficient status to summon this item."); - else if (sep->argnum==2 && sep->IsNumber(2)) - t->SummonItem(itemid, atoi(sep->arg[2])); - else if (sep->argnum==3) - t->SummonItem(itemid, atoi(sep->arg[2]), atoi(sep->arg[3])); - else if (sep->argnum==4) - t->SummonItem(itemid, atoi(sep->arg[2]), atoi(sep->arg[3]), atoi(sep->arg[4])); - else if (sep->argnum==5) - t->SummonItem(itemid, atoi(sep->arg[2]), atoi(sep->arg[3]), atoi(sep->arg[4]), atoi(sep->arg[5])); - else if (sep->argnum==6) - t->SummonItem(itemid, atoi(sep->arg[2]), atoi(sep->arg[3]), atoi(sep->arg[4]), atoi(sep->arg[5]), atoi(sep->arg[6])); - else if (sep->argnum==7) - t->SummonItem(itemid, atoi(sep->arg[2]), atoi(sep->arg[3]), atoi(sep->arg[4]), atoi(sep->arg[5]), atoi(sep->arg[6]), atoi(sep->arg[7])); - else if (sep->argnum == 7) - t->SummonItem(itemid, atoi(sep->arg[2]), atoi(sep->arg[3]), atoi(sep->arg[4]), atoi(sep->arg[5]), atoi(sep->arg[6]), atoi(sep->arg[7]), atoi(sep->arg[8])); - else { - t->SummonItem(itemid); - } - } -} - -void command_givemoney(Client *c, const Seperator *sep) -{ - if (!sep->IsNumber(1)) { //as long as the first one is a number, we'll just let atoi convert the rest to 0 or a number - c->Message(Chat::Red, "Usage: #Usage: #givemoney [pp] [gp] [sp] [cp]"); - } - else if(c->GetTarget() == nullptr) { - c->Message(Chat::Red, "You must target a player to give money to."); - } - else if(!c->GetTarget()->IsClient()) { - c->Message(Chat::Red, "You can only give money to players with this command."); - } - else { - //TODO: update this to the client, otherwise the client doesn't show any weight change until you zone, move an item, etc - c->GetTarget()->CastToClient()->AddMoneyToPP(atoi(sep->arg[4]), atoi(sep->arg[3]), atoi(sep->arg[2]), atoi(sep->arg[1]), true); - c->Message(Chat::White, "Added %i Platinum, %i Gold, %i Silver, and %i Copper to %s's inventory.", atoi(sep->arg[1]), atoi(sep->arg[2]), atoi(sep->arg[3]), atoi(sep->arg[4]), c->GetTarget()->GetName()); - } -} - -void command_itemsearch(Client *c, const Seperator *sep) -{ - if (sep->arg[1][0] == 0) - c->Message(Chat::White, "Usage: #itemsearch [search string]"); - else - { - const char *search_criteria=sep->argplus[1]; - - const EQ::ItemData* item = nullptr; - EQ::SayLinkEngine linker; - linker.SetLinkType(EQ::saylink::SayLinkItemData); - - if (Seperator::IsNumber(search_criteria)) { - item = database.GetItem(atoi(search_criteria)); - if (item) { - linker.SetItemData(item); - - c->Message(Chat::White, "%u: %s", item->ID, linker.GenerateLink().c_str()); - } - else { - c->Message(Chat::White, "Item #%s not found", search_criteria); - } - - return; - } - - int count = 0; - char sName[64]; - char sCriteria[255]; - strn0cpy(sCriteria, search_criteria, sizeof(sCriteria)); - strupr(sCriteria); - char* pdest; - uint32 it = 0; - while ((item = database.IterateItems(&it))) { - strn0cpy(sName, item->Name, sizeof(sName)); - strupr(sName); - pdest = strstr(sName, sCriteria); - if (pdest != nullptr) { - linker.SetItemData(item); - std::string item_id = std::to_string(item->ID); - std::string saylink_commands = - "[" + - EQ::SayLinkEngine::GenerateQuestSaylink( - "#si " + item_id, - false, - "X" - ) + - "] "; - if (item->Stackable && item->StackSize > 1) { - std::string stack_size = std::to_string(item->StackSize); - saylink_commands += - "[" + - EQ::SayLinkEngine::GenerateQuestSaylink( - "#si " + item_id + " " + stack_size, - false, - stack_size - ) + - "]"; - } - - c->Message( - Chat::White, - fmt::format( - " Summon {} [{}] [{}]", - saylink_commands, - linker.GenerateLink(), - item->ID - ).c_str() - ); - - ++count; - } - - if (count == 50) - break; - } - - if (count == 50) - c->Message(Chat::White, "50 items shown...too many results."); - else - c->Message(Chat::White, "%i items found", count); - - } -} - -void command_setaaxp(Client *c, const Seperator *sep) -{ - Client *t=c; - - if(c->GetTarget() && c->GetTarget()->IsClient()) - t=c->GetTarget()->CastToClient(); - - if (sep->IsNumber(1)) { - t->SetEXP(t->GetEXP(), atoi(sep->arg[1]), false); - if(sep->IsNumber(2) && sep->IsNumber(3)) { - t->SetLeadershipEXP(atoi(sep->arg[2]), atoi(sep->arg[3])); - } - } else - c->Message(Chat::White, "Usage: #setaaxp ( )"); -} - -void command_setaapts(Client *c, const Seperator *sep) -{ - Client *t=c; - - if(c->GetTarget() && c->GetTarget()->IsClient()) - t=c->GetTarget()->CastToClient(); - - if(sep->arg[1][0] == '\0' || sep->arg[2][0] == '\0') - c->Message(Chat::White, "Usage: #setaapts "); - else if(atoi(sep->arg[2]) <= 0 || atoi(sep->arg[2]) > 5000) - c->Message(Chat::White, "You must have a number greater than 0 for points and no more than 5000."); - else if(!strcasecmp(sep->arg[1], "group")) { - t->GetPP().group_leadership_points = atoi(sep->arg[2]); - t->GetPP().group_leadership_exp = 0; - t->Message(Chat::Experience, "Setting Group AA points to %u", t->GetPP().group_leadership_points); - t->SendLeadershipEXPUpdate(); - } else if(!strcasecmp(sep->arg[1], "raid")) { - t->GetPP().raid_leadership_points = atoi(sep->arg[2]); - t->GetPP().raid_leadership_exp = 0; - t->Message(Chat::Experience, "Setting Raid AA points to %u", t->GetPP().raid_leadership_points); - t->SendLeadershipEXPUpdate(); - } else { - t->GetPP().aapoints = atoi(sep->arg[2]); - t->GetPP().expAA = 0; - t->Message(Chat::Experience, "Setting personal AA points to %u", t->GetPP().aapoints); - t->SendAlternateAdvancementStats(); - } -} - -void command_setcrystals(Client *c, const Seperator *sep) -{ - Client *t=c; - - if(c->GetTarget() && c->GetTarget()->IsClient()) - t=c->GetTarget()->CastToClient(); - - if(sep->arg[1][0] == '\0' || sep->arg[2][0] == '\0') - c->Message(Chat::White, "Usage: #setcrystals "); - else if(atoi(sep->arg[2]) <= 0 || atoi(sep->arg[2]) > 100000) - c->Message(Chat::White, "You must have a number greater than 0 for crystals and no more than 100000."); - else if(!strcasecmp(sep->arg[1], "radiant")) - { - t->SetRadiantCrystals(atoi(sep->arg[2])); - } - else if(!strcasecmp(sep->arg[1], "ebon")) - { - t->SetEbonCrystals(atoi(sep->arg[2])); - } - else - { - c->Message(Chat::White, "Usage: #setcrystals "); - } -} - -void command_stun(Client *c, const Seperator *sep) -{ - Mob *t=c->CastToMob(); - uint32 duration; - - if(sep->arg[1][0]) - { - duration = atoi(sep->arg[1]); - if(c->GetTarget()) - t=c->GetTarget(); - if(t->IsClient()) - t->CastToClient()->Stun(duration); - else - t->CastToNPC()->Stun(duration); - } - else - c->Message(Chat::White, "Usage: #stun [duration]"); -} - - -void command_ban(Client *c, const Seperator *sep) -{ -if(sep->arg[1][0] == 0 || sep->arg[2][0] == 0) { - c->Message(Chat::White, "Usage: #ban "); - return; - } - - auto account_id = database.GetAccountIDByChar(sep->arg[1]); - - std::string message; - int i = 2; - while(1) { - if(sep->arg[i][0] == 0) { - break; - } - - if(message.length() > 0) { - message.push_back(' '); - } - - message += sep->arg[i]; - ++i; - } - - if(message.length() == 0) { - c->Message(Chat::White, "Usage: #ban "); - return; - } - - if(account_id == 0) { - c->Message(Chat::Red, "Character does not exist."); - return; - } - - std::string query = StringFormat("UPDATE account SET status = -2, ban_reason = '%s' " - "WHERE id = %i", EscapeString(message).c_str(), account_id); - auto results = database.QueryDatabase(query); - - c->Message(Chat::Red, "Account number %i with the character %s has been banned with message: \"%s\"", account_id, sep->arg[1], message.c_str()); - - ServerPacket flagUpdatePack(ServerOP_FlagUpdate, 6); - *((uint32*)&flagUpdatePack.pBuffer[0]) = account_id; - *((int16*)&flagUpdatePack.pBuffer[4]) = -2; - worldserver.SendPacket(&flagUpdatePack); - - Client *client = nullptr; - client = entity_list.GetClientByName(sep->arg[1]); - if(client) { - client->WorldKick(); - return; - } - - ServerPacket kickPlayerPack(ServerOP_KickPlayer, sizeof(ServerKickPlayer_Struct)); - ServerKickPlayer_Struct* skp = (ServerKickPlayer_Struct*)kickPlayerPack.pBuffer; - strcpy(skp->adminname, c->GetName()); - strcpy(skp->name, sep->arg[1]); - skp->adminrank = c->Admin(); - worldserver.SendPacket(&kickPlayerPack); -} - -void command_suspend(Client *c, const Seperator *sep) -{ - if((sep->arg[1][0] == 0) || (sep->arg[2][0] == 0)) { - c->Message(Chat::White, "Usage: #suspend (Specify 0 days to lift the suspension immediately) "); - return; - } - - int duration = atoi(sep->arg[2]); - - if(duration < 0) - duration = 0; - - std::string message; - - if(duration > 0) { - int i = 3; - while(1) { - if(sep->arg[i][0] == 0) { - break; - } - - if(message.length() > 0) { - message.push_back(' '); - } - - message += sep->arg[i]; - ++i; - } - - if(message.length() == 0) { - c->Message(Chat::White, "Usage: #suspend (Specify 0 days to lift the suspension immediately) "); - return; - } - } - - auto escName = new char[strlen(sep->arg[1]) * 2 + 1]; - database.DoEscapeString(escName, sep->arg[1], strlen(sep->arg[1])); - int accountID = database.GetAccountIDByChar(escName); - safe_delete_array(escName); - - if (accountID <= 0) { - c->Message(Chat::Red,"Character does not exist."); - return; - } - - std::string query = StringFormat("UPDATE `account` SET `suspendeduntil` = DATE_ADD(NOW(), INTERVAL %i DAY), " - "suspend_reason = '%s' WHERE `id` = %i", - duration, EscapeString(message).c_str(), accountID); - auto results = database.QueryDatabase(query); - - if(duration) - c->Message(Chat::Red,"Account number %i with the character %s has been temporarily suspended for %i day(s).", accountID, sep->arg[1], duration); - else - c->Message(Chat::Red,"Account number %i with the character %s is no longer suspended.", accountID, sep->arg[1]); - - Client *bannedClient = entity_list.GetClientByName(sep->arg[1]); - - if(bannedClient) { - bannedClient->WorldKick(); - return; - } - - auto pack = new ServerPacket(ServerOP_KickPlayer, sizeof(ServerKickPlayer_Struct)); - ServerKickPlayer_Struct *sks = (ServerKickPlayer_Struct *)pack->pBuffer; - - strn0cpy(sks->adminname, c->GetName(), sizeof(sks->adminname)); - strn0cpy(sks->name, sep->arg[1], sizeof(sks->name)); - sks->adminrank = c->Admin(); - - worldserver.SendPacket(pack); - - safe_delete(pack); -} - -void command_ipban(Client *c, const Seperator *sep) -{ - if(sep->arg[1] == 0) - { - c->Message(Chat::White, "Usage: #ipban [xxx.xxx.xxx.xxx]"); - } else { - if(database.AddBannedIP(sep->arg[1], c->GetName())) { - c->Message(Chat::White, "%s has been successfully added to the banned_ips table by %s", sep->arg[1], c->GetName()); - } else { - c->Message(Chat::White, "IPBan Failed (IP address is possibly already in the table?)"); - } - } -} - -void command_revoke(Client *c, const Seperator *sep) -{ - if(sep->arg[1][0] == 0 || sep->arg[2][0] == 0) { - c->Message(Chat::White, "Usage: #revoke [charname] [1/0]"); - return; - } - - uint32 characterID = database.GetAccountIDByChar(sep->arg[1]); - if(characterID == 0) { - c->Message(Chat::Red,"Character does not exist."); - return; - } - - int flag = sep->arg[2][0] == '1' ? true : false; - std::string query = StringFormat("UPDATE account SET revoked = %d WHERE id = %i", flag, characterID); - auto results = database.QueryDatabase(query); - - c->Message(Chat::Red,"%s account number %i with the character %s.", flag? "Revoking": "Unrevoking", characterID, sep->arg[1]); - - Client* revokee = entity_list.GetClientByAccID(characterID); - if(revokee) { - c->Message(Chat::White, "Found %s in this zone.", revokee->GetName()); - revokee->SetRevoked(flag); - return; - } - - c->Message(Chat::Red, "#revoke: Couldn't find %s in this zone, passing request to worldserver.", sep->arg[1]); - - auto outapp = new ServerPacket(ServerOP_Revoke, sizeof(RevokeStruct)); - RevokeStruct *revoke = (RevokeStruct *)outapp->pBuffer; - strn0cpy(revoke->adminname, c->GetName(), 64); - strn0cpy(revoke->name, sep->arg[1], 64); - revoke->toggle = flag; - worldserver.SendPacket(outapp); - safe_delete(outapp); -} - -void command_roambox(Client *c, const Seperator *sep) -{ - std::string arg1 = sep->arg[1]; - - Mob *target = c->GetTarget(); - if (!target || !target->IsNPC()) { - c->Message(Chat::Red, "You need a valid NPC target for this command"); - return; - } - - NPC *npc = dynamic_cast(target); - int spawn_group_id = npc->GetSpawnGroupId(); - if (spawn_group_id <= 0) { - c->Message(Chat::Red, "NPC needs a valid SpawnGroup!"); - return; - } - - if (arg1 == "set") { - int box_size = (sep->arg[2] ? atoi(sep->arg[2]) : 0); - int delay = (sep->arg[3] ? atoi(sep->arg[3]) : 15000); - if (box_size > 0) { - std::string query = fmt::format( - SQL( - UPDATE spawngroup SET - dist = {}, - min_x = {}, - max_x = {}, - min_y = {}, - max_y = {}, - delay = {} - WHERE id = {} - ), - (box_size / 2), - npc->GetX() - (box_size / 2), - npc->GetX() + (box_size / 2), - npc->GetY() - (box_size / 2), - npc->GetY() + (box_size / 2), - delay, - spawn_group_id - ); - - database.QueryDatabase(query); - - c->Message( - Chat::Yellow, - "NPC (%s) Roam Box set to box size of [%i] SpawnGroupId [%i] delay [%i]", - npc->GetCleanName(), - box_size, - spawn_group_id, - delay - ); - - return; - } - - c->Message(Chat::Red, "Box size must be set!"); - } - - if (arg1 == "remove") { - std::string query = fmt::format( - SQL( - UPDATE spawngroup SET - dist = 0, - min_x = 0, - max_x = 0, - min_y = 0, - max_y = 0, - delay = 0 - WHERE id = {} - ), - spawn_group_id - ); - - database.QueryDatabase(query); - - c->Message( - Chat::Yellow, - "NPC (%s) Roam Box has been removed from SpawnGroupID [%i]", - npc->GetCleanName(), - spawn_group_id - ); - - return; - } - - c->Message(Chat::Yellow, "> Command Usage"); - c->Message(Chat::Yellow, "#roambox set box_size [delay = 0]"); - c->Message(Chat::Yellow, "#roambox remove"); -} - -void command_oocmute(Client *c, const Seperator *sep) -{ - if(sep->arg[1][0] == 0 || !(sep->arg[1][0] == '1' || sep->arg[1][0] == '0')) - c->Message(Chat::White, "Usage: #oocmute [1/0]"); - else { - auto outapp = new ServerPacket(ServerOP_OOCMute, 1); - *(outapp->pBuffer) = atoi(sep->arg[1]); - worldserver.SendPacket(outapp); - safe_delete(outapp); - } -} - -void command_checklos(Client *c, const Seperator *sep) -{ - if (c->GetTarget()) { - if (c->CheckLosFN(c->GetTarget())) { - c->Message(Chat::White, "You have LOS to %s", c->GetTarget()->GetName()); - } - else { - c->Message(Chat::White, "You do not have LOS to %s", c->GetTarget()->GetName()); - } - } - else { - c->Message(Chat::White, "ERROR: Target required"); - } -} - -void command_set_adventure_points(Client *c, const Seperator *sep) -{ - Client *t=c; - - if(c->GetTarget() && c->GetTarget()->IsClient()) - t=c->GetTarget()->CastToClient(); - - if(!sep->arg[1][0]) - { - c->Message(Chat::White, "Usage: #setadventurepoints [points] [theme]"); - return; - } - - if(!sep->IsNumber(1) || !sep->IsNumber(2)) - { - c->Message(Chat::White, "Usage: #setadventurepoints [points] [theme]"); - return; - } - - c->Message(Chat::White, "Updating adventure points for %s", t->GetName()); - t->UpdateLDoNPoints(atoi(sep->arg[1]), atoi(sep->arg[2])); -} - -void command_npcsay(Client *c, const Seperator *sep) -{ - if(c->GetTarget() && c->GetTarget()->IsNPC() && sep->arg[1][0]) - { - c->GetTarget()->Say(sep->argplus[1]); - } - else - { - c->Message(Chat::White, "Usage: #npcsay message (requires NPC target"); - } -} - -void command_npcshout(Client *c, const Seperator *sep) -{ - if(c->GetTarget() && c->GetTarget()->IsNPC() && sep->arg[1][0]) - { - c->GetTarget()->Shout(sep->argplus[1]); - } - else - { - c->Message(Chat::White, "Usage: #npcshout message (requires NPC target"); - } -} - -void command_timers(Client *c, const Seperator *sep) { - if(!c->GetTarget() || !c->GetTarget()->IsClient()) { - c->Message(Chat::White,"Need a player target for timers."); - return; - } - Client *them = c->GetTarget()->CastToClient(); - - std::vector< std::pair > res; - them->GetPTimers().ToVector(res); - - c->Message(Chat::White,"Timers for target:"); - - int r; - int l = res.size(); - for(r = 0; r < l; r++) { - c->Message(Chat::White,"Timer %d: %d seconds remain.", res[r].first, res[r].second->GetRemainingTime()); - } -} - -void command_npcemote(Client *c, const Seperator *sep) -{ - if(c->GetTarget() && c->GetTarget()->IsNPC() && sep->arg[1][0]) - { - c->GetTarget()->Emote(sep->argplus[1]); - } - else - { - c->Message(Chat::White, "Usage: #npcemote message (requires NPC target"); - } -} - -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 (apply)"); - return; - } - - std::string query = SQL( - SELECT - COLUMN_NAME - FROM - INFORMATION_SCHEMA.COLUMNS - WHERE - table_name = 'npc_types' - AND - COLUMN_NAME != 'id' - ); - - std::string search_column, search_value, change_column, change_value; - if (sep->arg[1]) { - search_column = sep->arg[1]; - } - if (sep->arg[2]) { - search_value = sep->arg[2]; - } - if (sep->arg[3]) { - change_column = sep->arg[3]; - } - if (sep->arg[4]) { - change_value = sep->arg[4]; - } - - bool valid_change_column = false; - bool valid_search_column = false; - auto results = content_db.QueryDatabase(query); - - std::vector possible_column_options; - - for (auto row = results.begin(); row != results.end(); ++row) { - if (row[0] == change_column) { - valid_change_column = true; - } - if (row[0] == search_column) { - valid_search_column = true; - } - - possible_column_options.push_back(row[0]); - } - - std::string options_glue = ", "; - - if (!valid_search_column) { - c->Message(Chat::Red, "You must specify a valid search column. [%s] is not valid", search_column.c_str()); - c->Message(Chat::Yellow, "Possible columns [%s]", implode(options_glue, possible_column_options).c_str()); - return; - } - - if (!valid_change_column) { - c->Message(Chat::Red, "You must specify a valid change column. [%s] is not valid", change_column.c_str()); - c->Message(Chat::Yellow, "Possible columns [%s]", implode(options_glue, possible_column_options).c_str()); - return; - } - - if (!valid_search_column || !valid_change_column) { - c->Message(Chat::Red, "One requested column is invalid"); - return; - } - - query = fmt::format( - SQL( - select - id, - name, - {0}, - {1} - from - npc_types - where - id IN( - select - spawnentry.npcID - from - spawnentry - join spawn2 on spawn2.spawngroupID = spawnentry.spawngroupID - where - spawn2.zone = '{2}' and spawn2.version = {3} - ) - ), - search_column, - change_column, - zone->GetShortName(), - zone->GetInstanceVersion() - ); - - std::string status = "(Searching)"; - - if (strcasecmp(sep->arg[5], "apply") == 0) { - status = "(Applying)"; - } - - std::vector npc_ids; - - bool exact_match = false; - if (search_value[0] == '=') { - exact_match = true; - search_value = search_value.substr(1); - } - - int found_count = 0; - results = content_db.QueryDatabase(query); - for (auto row = results.begin(); row != results.end(); ++row) { - - std::string npc_id = row[0]; - std::string npc_name = row[1]; - std::string search_column_value = str_tolower(row[2]); - std::string change_column_current_value = row[3]; - - if (exact_match) { - if (search_column_value.compare(search_value) != 0) { - continue; - } - } - else { - if (search_column_value.find(search_value) == std::string::npos) { - continue; - } - } - - c->Message( - Chat::Yellow, - fmt::format( - "NPC ({0}) [{1}] ({2}) [{3}] Current ({4}) [{5}] New [{6}] {7}", - npc_id, - npc_name, - search_column, - search_column_value, - change_column, - change_column_current_value, - change_value, - status - ).c_str() - ); - - npc_ids.push_back(npc_id); - - found_count++; - } - - std::string saylink = fmt::format( - "#npceditmass {} {}{} {} {} apply", - search_column, - (exact_match ? "=" : ""), - search_value, - change_column, - change_value - ); - - if (strcasecmp(sep->arg[5], "apply") == 0) { - std::string npc_ids_string = implode(",", npc_ids); - if (npc_ids_string.empty()) { - c->Message(Chat::Red, "Error: Ran into an unknown error compiling NPC IDs"); - return; - } - - content_db.QueryDatabase( - fmt::format( - "UPDATE `npc_types` SET {} = '{}' WHERE id IN ({})", - change_column, - change_value, - npc_ids_string - ) - ); - - c->Message(Chat::Yellow, "Changes applied to (%i) NPC's", found_count); - zone->Repop(); - } - else { - c->Message(Chat::Yellow, "Found (%i) NPC's that match this search...", found_count); - - if (found_count > 0) { - c->Message( - Chat::Yellow, "To apply these changes, click <%s> or type [%s]", - EQ::SayLinkEngine::GenerateQuestSaylink(saylink, false, "Apply").c_str(), - saylink.c_str() - ); - } - } -} - -void command_npcedit(Client *c, const Seperator *sep) -{ if (!c->GetTarget() || !c->GetTarget()->IsNPC()) { - c->Message(Chat::White, "Error: Must have NPC targeted"); - return; - } - - if (strcasecmp(sep->arg[1], "help") == 0) { - - c->Message(Chat::White, "Help File for #npcedit. Syntax for commands are:"); - c->Message(Chat::White, "#npcedit Name - Sets an NPC's name"); - c->Message(Chat::White, "#npcedit Lastname - Sets an NPC's lastname"); - c->Message(Chat::White, "#npcedit Level - Sets an NPC's level"); - c->Message(Chat::White, "#npcedit Maxlevel - Sets an NPC's maximum level"); - c->Message(Chat::White, "#npcedit Race - Sets an NPC's race"); - c->Message(Chat::White, "#npcedit Class - Sets an NPC's class"); - c->Message(Chat::White, "#npcedit Bodytype - Sets an NPC's bodytype"); - c->Message(Chat::White, "#npcedit HP - Sets an NPC's hitpoints"); - c->Message(Chat::White, "#npcedit Gender - Sets an NPC's gender"); - c->Message(Chat::White, "#npcedit Texture - Sets an NPC's texture"); - c->Message(Chat::White, "#npcedit Helmtexture - Sets an NPC's helmet texture"); - c->Message(Chat::White, "#npcedit Armtexture - Sets an NPC's arm texture"); - c->Message(Chat::White, "#npcedit Bracertexture - Sets an NPC's bracer texture"); - c->Message(Chat::White, "#npcedit Handtexture - Sets an NPC's hand texture"); - c->Message(Chat::White, "#npcedit Legtexture - Sets an NPC's leg texture"); - c->Message(Chat::White, "#npcedit Feettexture - Sets an NPC's feettexture"); - c->Message(Chat::White, "#npcedit Herosforgemodel - Sets an NPC's Hero's Forge Model"); - c->Message(Chat::White, "#npcedit Size - Sets an NPC's size"); - c->Message(Chat::White, "#npcedit Hpregen - Sets an NPC's hitpoint regen rate per tick"); - c->Message(Chat::White, "#npcedit Manaregen - Sets an NPC's mana regen rate per tick"); - c->Message(Chat::White, "#npcedit Loottable - Sets the loottable ID for an NPC "); - c->Message(Chat::White, "#npcedit Merchantid - Sets the merchant ID for an NPC"); - c->Message(Chat::White, "#npcedit alt_currency_id - Sets the Alternate Currency ID for an alterative currency Merchant"); - c->Message(Chat::White, "#npcedit npc_spells_effects_id - Sets the NPC Spell Effects ID"); - c->Message(Chat::White, "#npcedit adventure_template_id - Sets the NPC's Adventure Template ID"); - c->Message(Chat::White, "#npcedit trap_template - Sets the NPC's Trap Template ID"); - c->Message(Chat::White, "#npcedit special_abilities - Sets the NPC's Special Abilities"); - c->Message(Chat::White, "#npcedit Spell - Sets the npc spells list ID for an NPC"); - c->Message(Chat::White, "#npcedit Faction - Sets the NPC's faction id"); - c->Message(Chat::White, "#npcedit Damage - Sets an NPC's damage"); - c->Message(Chat::White, "#npcedit Meleetype - Sets an NPC's melee types"); - c->Message(Chat::White, "#npcedit Rangedtype - Sets an NPC's ranged type"); - c->Message(Chat::White, "#npcedit Ammoidfile - Sets an NPC's ammo id file"); - c->Message(Chat::White, "#npcedit Aggroradius - Sets an NPC's aggro radius"); - c->Message(Chat::White, "#npcedit Assistradius - Sets an NPC's assist radius"); - c->Message(Chat::White, "#npcedit Social - Set to 1 if an NPC should assist others on its faction"); - c->Message(Chat::White, "#npcedit Runspeed - Sets an NPC's run speed"); - c->Message(Chat::White, "#npcedit Walkspeed - Sets an NPC's walk speed"); - c->Message(Chat::White, "#npcedit AGI - Sets an NPC's Agility"); - c->Message(Chat::White, "#npcedit CHA - Sets an NPC's Charisma"); - c->Message(Chat::White, "#npcedit DEX - Sets an NPC's Dexterity"); - c->Message(Chat::White, "#npcedit INT - Sets an NPC's Intelligence"); - c->Message(Chat::White, "#npcedit STA - Sets an NPC's Stamina"); - c->Message(Chat::White, "#npcedit STR - Sets an NPC's Strength"); - c->Message(Chat::White, "#npcedit WIS - Sets an NPC's Wisdom"); - c->Message(Chat::White, "#npcedit MR - Sets an NPC's Magic Resistance"); - c->Message(Chat::White, "#npcedit PR - Sets an NPC's Poison Resistance"); - c->Message(Chat::White, "#npcedit DR - Sets an NPC's Disease Resistance"); - c->Message(Chat::White, "#npcedit FR - Sets an NPC's Fire Resistance"); - c->Message(Chat::White, "#npcedit CR - Sets an NPC's Cold Resistance"); - c->Message(Chat::White, "#npcedit Corrup - Sets an NPC's Corruption Resistance"); - c->Message(Chat::White, "#npcedit PhR - Sets and NPC's Physical Resistance"); - c->Message(Chat::White, "#npcedit Seeinvis - Sets an NPC's ability to see invis"); - c->Message(Chat::White, "#npcedit Seeinvisundead - Sets an NPC's ability to see through invis vs. undead"); - c->Message(Chat::White, "#npcedit Seehide - Sets an NPC's ability to see through hide"); - c->Message(Chat::White, "#npcedit Seeimprovedhide - Sets an NPC's ability to see through improved hide"); - c->Message(Chat::White, "#npcedit AC - Sets an NPC's Armor Class"); - c->Message(Chat::White, "#npcedit ATK - Sets an NPC's Attack"); - c->Message(Chat::White, "#npcedit Accuracy - Sets an NPC's Accuracy"); - c->Message(Chat::White, "#npcedit Avoidance - Sets an NPC's Avoidance"); - c->Message(Chat::White, "#npcedit npcaggro - Sets an NPC's npc_aggro flag"); - c->Message(Chat::White, "#npcedit qglobal - Sets an NPC's quest global flag"); - c->Message(Chat::White, "#npcedit spawn_limit - Sets an NPC's spawn limit counter"); - c->Message(Chat::White, "#npcedit Attackspeed - Sets an NPC's attack speed modifier"); - c->Message(Chat::White, "#npcedit Attackdelay - Sets an NPC's attack delay"); - c->Message(Chat::White, "#npcedit Attackcount - Sets an NPC's attack count"); - c->Message(Chat::White, "#npcedit findable - Sets an NPC's findable flag"); - c->Message(Chat::White, "#npcedit trackable - Sets an NPC's trackable flag"); - c->Message(Chat::White, "#npcedit weapon - Sets an NPC's primary and secondary weapon model"); - c->Message(Chat::White, "#npcedit featuresave - Saves all current facial features to the database"); - c->Message(Chat::White, "#npcedit color - Sets an NPC's red, green, and blue armor tint"); - c->Message(Chat::White, "#npcedit armortint_id - Set an NPC's Armor tint ID"); - c->Message(Chat::White, "#npcedit setanimation - Set an NPC's animation on spawn (Stored in spawn2 table)"); - c->Message(Chat::White, "#npcedit scalerate - Set an NPC's scaling rate"); - c->Message(Chat::White, "#npcedit healscale - Set an NPC's heal scaling rate"); - c->Message(Chat::White, "#npcedit spellscale - Set an NPC's spell scaling rate"); - c->Message(Chat::White, "#npcedit no_target - Set an NPC's ability to be targeted with the target hotkey"); - c->Message(Chat::White, "#npcedit version - Set an NPC's version"); - c->Message(Chat::White, "#npcedit slow_mitigation - Set an NPC's slow mitigation"); - c->Message(Chat::White, "#npcedit flymode - Set an NPC's flymode [0 = ground, 1 = flying, 2 = levitate, 3 = water, 4 = floating]"); - c->Message(Chat::White, "#npcedit raidtarget - Set an NPCs raid_target field"); - c->Message(Chat::White, "#npcedit respawntime - Set an NPCs respawn timer in seconds"); - - } - - uint32 npcTypeID = c->GetTarget()->CastToNPC()->GetNPCTypeID(); - if (strcasecmp(sep->arg[1], "name") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has the name %s.", npcTypeID, sep->argplus[2]); - std::string query = StringFormat("UPDATE npc_types SET name = '%s' WHERE id = %i", sep->argplus[2],npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "lastname") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has the lastname %s.", npcTypeID, sep->argplus[2]); - std::string query = StringFormat("UPDATE npc_types SET lastname = '%s' WHERE id = %i", sep->argplus[2],npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "flymode") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has flymode [%s]", npcTypeID, sep->argplus[2]); - std::string query = StringFormat("UPDATE npc_types SET flymode = '%s' WHERE id = %i", sep->argplus[2],npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "race") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has the race %i.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET race = %i WHERE id = %i", atoi(sep->argplus[2]),npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "class") == 0) { - c->Message(Chat::Yellow,"NPCID %u is now class %i.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET class = %i WHERE id = %i", atoi(sep->argplus[2]),npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "bodytype") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has type %i bodytype.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET bodytype = %i WHERE id = %i", atoi(sep->argplus[2]),npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "hp") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has %i Hitpoints.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET hp = %i WHERE id = %i", atoi(sep->argplus[2]),npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "gender") == 0) { - c->Message(Chat::Yellow,"NPCID %u is now gender %i.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET gender = %i WHERE id = %i", atoi(sep->argplus[2]),npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "texture") == 0) { - c->Message(Chat::Yellow,"NPCID %u now uses texture %i.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET texture = %i WHERE id = %i", atoi(sep->argplus[2]),npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "helmtexture") == 0) { - c->Message(Chat::Yellow,"NPCID %u now uses helmtexture %i.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET helmtexture = %i WHERE id = %i", atoi(sep->argplus[2]),npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "armtexture") == 0) { - c->Message(Chat::Yellow,"NPCID %u now uses armtexture %i.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET armtexture = %i WHERE id = %i", atoi(sep->argplus[2]),npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "bracertexture") == 0) { - c->Message(Chat::Yellow,"NPCID %u now uses bracertexture %i.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET bracertexture = %i WHERE id = %i", atoi(sep->argplus[2]),npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "handtexture") == 0) { - c->Message(Chat::Yellow,"NPCID %u now uses handtexture %i.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET handtexture = %i WHERE id = %i", atoi(sep->argplus[2]),npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "legtexture") == 0) { - c->Message(Chat::Yellow,"NPCID %u now uses legtexture %i.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET legtexture = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "feettexture") == 0) { - c->Message(Chat::Yellow,"NPCID %u now uses feettexture %i.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET feettexture = %i WHERE id = %i", atoi(sep->argplus[2]),npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "herosforgemodel") == 0) { - c->Message(Chat::Yellow,"NPCID %u now uses herosforgemodel %i.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET herosforgemodel = %i WHERE id = %i", atoi(sep->argplus[2]),npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "size") == 0) { - c->Message(Chat::Yellow,"NPCID %u is now size %i.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET size = %i WHERE id = %i", atoi(sep->argplus[2]),npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "hpregen") == 0) { - c->Message(Chat::Yellow,"NPCID %u now regens %i hitpoints per tick.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET hp_regen_rate = %i WHERE id = %i", atoi(sep->argplus[2]),npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "manaregen") == 0) { - c->Message(Chat::Yellow,"NPCID %u now regens %i mana per tick.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET mana_regen_rate = %i WHERE id = %i", atoi(sep->argplus[2]),npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "loottable") == 0) { - c->Message(Chat::Yellow,"NPCID %u is now on loottable_id %i.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET loottable_id = %i WHERE id = %i", atoi(sep->argplus[2]),npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "merchantid") == 0) { - c->Message(Chat::Yellow,"NPCID %u is now merchant_id %i.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET merchant_id = %i WHERE id = %i", atoi(sep->argplus[2]),npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "alt_currency_id") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has field 'alt_currency_id' set to %s.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET alt_currency_id = '%s' WHERE id = %i", sep->argplus[2],npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "npc_spells_effects_id") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has field 'npc_spells_effects_id' set to %s.", npcTypeID, sep->argplus[2]); - std::string query = StringFormat("UPDATE npc_types SET npc_spells_effects_id = '%s' WHERE id = %i", sep->argplus[2],npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "adventure_template_id") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has field 'adventure_template_id' set to %s.", npcTypeID, sep->argplus[2]); - std::string query = StringFormat("UPDATE npc_types SET adventure_template_id = '%s' WHERE id = %i", sep->argplus[2],npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "trap_template") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has field 'trap_template' set to %s.", npcTypeID, sep->argplus[2]); - std::string query = StringFormat("UPDATE npc_types SET trap_template = '%s' WHERE id = %i", sep->argplus[2],npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "special_abilities") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has field 'special_abilities' set to %s.", npcTypeID, sep->argplus[2]); - std::string query = StringFormat("UPDATE npc_types SET special_abilities = '%s' WHERE id = %i", sep->argplus[2],npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "spell") == 0) { - c->Message(Chat::Yellow,"NPCID %u now uses spell list %i", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET npc_spells_id = %i WHERE id = %i", atoi(sep->argplus[2]),npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "faction") == 0) { - c->Message(Chat::Yellow,"NPCID %u is now faction %i", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET npc_faction_id = %i WHERE id = %i", atoi(sep->argplus[2]),npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "damage") == 0) { - c->Message(Chat::Yellow,"NPCID %u now hits from %i to %i", npcTypeID, atoi(sep->arg[2]), atoi(sep->arg[3])); - std::string query = StringFormat("UPDATE npc_types SET mindmg = %i, maxdmg = %i WHERE id = %i", atoi(sep->arg[2]), atoi(sep->arg[3]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "meleetype") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has a primary melee type of %i and a secondary melee type of %i.", npcTypeID, atoi(sep->arg[2]), atoi(sep->arg[3])); - std::string query = StringFormat("UPDATE npc_types SET prim_melee_type = %i, sec_melee_type = %i WHERE id = %i", atoi(sep->arg[2]), atoi(sep->arg[3]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "rangedtype") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has a ranged type of %i.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET ranged_type = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "ammoidfile") == 0) { - c->Message(Chat::Yellow,"NPCID %u's ammo id file is now %i", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET ammoidfile = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "aggroradius") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has an aggro radius of %i", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET aggroradius = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "assistradius") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has an assist radius of %i", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET assistradius = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "social") == 0) { - c->Message(Chat::Yellow,"NPCID %u social status is now %i", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET social = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "runspeed") == 0) { - c->Message(Chat::Yellow,"NPCID %u now runs at %f", npcTypeID, atof(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET runspeed = %f WHERE id = %i", atof(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "walkspeed") == 0) { - c->Message(Chat::Yellow,"NPCID %u now walks at %f", npcTypeID, atof(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET walkspeed = %f WHERE id = %i", atof(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "AGI") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has %i Agility.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET AGI = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "CHA") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has %i Charisma.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET CHA = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "DEX") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has %i Dexterity.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET DEX = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "INT") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has %i Intelligence.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET _INT = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "STA") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has %i Stamina.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET STA = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "STR") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has %i Strength.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET STR = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "WIS") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has a Magic Resistance of %i.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET WIS = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "MR") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has a Magic Resistance of %i.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET MR = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "DR") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has a Disease Resistance of %i.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET DR = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "CR") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has a Cold Resistance of %i.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET CR = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "FR") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has a Fire Resistance of %i.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET FR = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "PR") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has a Poison Resistance of %i.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET PR = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "Corrup") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has a Corruption Resistance of %i.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET corrup = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "PhR") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has a Physical Resistance of %i.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET PhR = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "seeinvis") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has seeinvis set to %i.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET see_invis = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "seeinvisundead") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has seeinvisundead set to %i.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET see_invis_undead = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "seehide") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has seehide set to %i.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET see_hide = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "seeimprovedhide") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has seeimprovedhide set to %i.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET see_improved_hide = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "AC") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has %i Armor Class.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET ac = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "ATK") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has %i Attack.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET atk = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "Accuracy") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has %i Accuracy.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET accuracy = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "Avoidance") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has %i Avoidance.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET avoidance = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "level") == 0) { - c->Message(Chat::Yellow,"NPCID %u is now level %i.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET level = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "maxlevel") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has a maximum level of %i.", npcTypeID, atoi(sep->argplus[2])); - std::string query = StringFormat("UPDATE npc_types SET maxlevel = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "qglobal") == 0) { - c->Message(Chat::Yellow,"Quest globals have been %s for NPCID %u", atoi(sep->arg[2]) == 0 ? "disabled" : "enabled", npcTypeID); - std::string query = StringFormat("UPDATE npc_types SET qglobal = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "npcaggro") == 0) { - c->Message(Chat::Yellow,"NPCID %u will now %s other NPCs with negative faction npc_value", npcTypeID, atoi(sep->arg[2]) == 0? "not aggro": "aggro"); - std::string query = StringFormat("UPDATE npc_types SET npc_aggro = %i WHERE id = %i", atoi(sep->argplus[2]) == 0? 0: 1, npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "spawn_limit") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has a spawn limit of %i", npcTypeID, atoi(sep->arg[2])); - std::string query = StringFormat("UPDATE npc_types SET spawn_limit = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "Attackspeed") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has attack_speed set to %f", npcTypeID, atof(sep->arg[2])); - std::string query = StringFormat("UPDATE npc_types SET attack_speed = %f WHERE id = %i", atof(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "Attackdelay") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has attack_delay set to %i", npcTypeID,atoi(sep->arg[2])); - std::string query = StringFormat("UPDATE npc_types SET attack_delay = %i WHERE id = %i", atoi(sep->argplus[2]),npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "Attackcount") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has attack_count set to %i", npcTypeID,atoi(sep->arg[2])); - std::string query = StringFormat("UPDATE npc_types SET attack_count = %i WHERE id = %i", atoi(sep->argplus[2]),npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "findable") == 0) { - c->Message(Chat::Yellow,"NPCID %u is now %s", npcTypeID, atoi(sep->arg[2]) == 0? "not findable": "findable"); - std::string query = StringFormat("UPDATE npc_types SET findable = %i WHERE id = %i", atoi(sep->argplus[2]) == 0? 0: 1, npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "trackable") == 0) { - c->Message(Chat::Yellow,"NPCID %u is now %s", npcTypeID, atoi(sep->arg[2]) == 0? "not trackable": "trackable"); - std::string query = StringFormat("UPDATE npc_types SET trackable = %i WHERE id = %i", atoi(sep->argplus[2]) == 0? 0: 1, npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "weapon") == 0) { - c->Message(Chat::Yellow,"NPCID %u will have item graphic %i set to his primary and item graphic %i set to his secondary on repop.", npcTypeID, atoi(sep->arg[2]), atoi(sep->arg[3])); - std::string query = StringFormat("UPDATE npc_types SET d_melee_texture1 = %i, d_melee_texture2 = %i WHERE id = %i", atoi(sep->arg[2]), atoi(sep->arg[3]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "featuresave") == 0) { - c->Message(Chat::Yellow,"NPCID %u saved with all current facial feature settings", npcTypeID); - Mob* target = c->GetTarget(); - std::string query = StringFormat("UPDATE npc_types " - "SET luclin_haircolor = %i, luclin_beardcolor = %i, " - "luclin_hairstyle = %i, luclin_beard = %i, " - "face = %i, drakkin_heritage = %i, " - "drakkin_tattoo = %i, drakkin_details = %i " - "WHERE id = %i", - target->GetHairColor(), target->GetBeardColor(), - target->GetHairStyle(), target->GetBeard(), - target->GetLuclinFace(), target->GetDrakkinHeritage(), - target->GetDrakkinTattoo(), target->GetDrakkinDetails(), - npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "color") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has %i red, %i green, and %i blue tinting on their armor.", npcTypeID, atoi(sep->arg[2]), atoi(sep->arg[3]), atoi(sep->arg[4])); - std::string query = StringFormat("UPDATE npc_types SET armortint_red = %i, armortint_green = %i, armortint_blue = %i WHERE id = %i", atoi(sep->arg[2]), atoi(sep->arg[3]), atoi(sep->arg[4]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "armortint_id") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has field 'armortint_id' set to %s", npcTypeID, sep->arg[2]); - std::string query = StringFormat("UPDATE npc_types SET armortint_id = '%s' WHERE id = %i", sep->argplus[2], npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "setanimation") == 0) { - int animation = 0; - if(sep->arg[2] && atoi(sep->arg[2]) <= 4) { - if((strcasecmp(sep->arg[2], "stand" ) == 0) || atoi(sep->arg[2]) == 0) - animation = 0; //Stand - if((strcasecmp(sep->arg[2], "sit" ) == 0) || atoi(sep->arg[2]) == 1) - animation = 1; //Sit - if((strcasecmp(sep->arg[2], "crouch" ) == 0) || atoi(sep->arg[2]) == 2) - animation = 2; //Crouch - if((strcasecmp(sep->arg[2], "dead" ) == 0) || atoi(sep->arg[2]) == 3) - animation = 3; //Dead - if((strcasecmp(sep->arg[2], "loot" ) == 0) || atoi(sep->arg[2]) == 4) - animation = 4; //Looting Animation - } else { - c->Message(Chat::White, "You must specifiy an animation stand, sit, crouch, dead, loot (0-4)"); - c->Message(Chat::White, "Example: #npcedit setanimation sit"); - c->Message(Chat::White, "Example: #npcedit setanimation 0"); - return; - } - - c->Message(Chat::Yellow,"NPCID %u now has the animation set to %i on spawn with spawngroup %i", npcTypeID, animation, - c->GetTarget()->CastToNPC()->GetSpawnGroupId() ); - std::string query = StringFormat("UPDATE spawn2 SET animation = %i " "WHERE spawngroupID = %i", animation, - c->GetTarget()->CastToNPC()->GetSpawnGroupId()); - content_db.QueryDatabase(query); - - c->GetTarget()->SetAppearance(EmuAppearance(animation)); - return; - } - - if (strcasecmp(sep->arg[1], "scalerate") == 0) { - c->Message(Chat::Yellow,"NPCID %u now has a scaling rate of %i.", npcTypeID, atoi(sep->arg[2])); - std::string query = StringFormat("UPDATE npc_types SET scalerate = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "healscale") == 0) { - c->Message(Chat::Yellow, "NPCID %u now has a heal scaling rate of %i.", npcTypeID, atoi(sep->arg[2])); - std::string query = StringFormat("UPDATE npc_types SET healscale = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "spellscale") == 0) { - c->Message(Chat::Yellow, "NPCID %u now has a spell scaling rate of %i.", npcTypeID, atoi(sep->arg[2])); - std::string query = StringFormat("UPDATE npc_types SET spellscale = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "no_target") == 0) { - c->Message(Chat::Yellow, "NPCID %u is now %s.", npcTypeID, atoi(sep->arg[2]) == 0? "targetable": "untargetable"); - std::string query = StringFormat("UPDATE npc_types SET no_target_hotkey = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "version") == 0) { - c->Message(Chat::Yellow, "NPCID %u is now version %i.", npcTypeID, atoi(sep->arg[2])); - std::string query = StringFormat("UPDATE npc_types SET version = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "slow_mitigation") == 0) { - c->Message(Chat::Yellow, "NPCID %u's slow mitigation limit is now %i.", npcTypeID, atoi(sep->arg[2])); - std::string query = StringFormat("UPDATE npc_types SET slow_mitigation = %i WHERE id = %i", atoi(sep->argplus[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - - if (strcasecmp(sep->arg[1], "raidtarget") == 0) { - if (sep->arg[2][0] && sep->IsNumber(sep->arg[2]) && atoi(sep->arg[2]) >= 0) { - c->Message(Chat::Yellow, "NPCID %u is %s as a raid target.", npcTypeID, atoi(sep->arg[2]) == 0 ? "no longer designated" : "now designated"); - std::string query = StringFormat("UPDATE npc_types SET raid_target = %i WHERE id = %i", atoi(sep->arg[2]), npcTypeID); - content_db.QueryDatabase(query); - return; - } - } - - if (strcasecmp(sep->arg[1], "respawntime") == 0) { - if (sep->arg[2][0] && sep->IsNumber(sep->arg[2]) && atoi(sep->arg[2]) > 0) { - c->Message(Chat::Yellow, "NPCID %u (spawngroup %i) respawn time set to %i.", npcTypeID, c->GetTarget()->CastToNPC()->GetSpawnGroupId(), atoi(sep->arg[2])); - std::string query = StringFormat("UPDATE spawn2 SET respawntime = %i WHERE spawngroupID = %i AND version = %i", atoi(sep->arg[2]), c->GetTarget()->CastToNPC()->GetSpawnGroupId(), zone->GetInstanceVersion()); - content_db.QueryDatabase(query); - return; - } - } - - if((sep->arg[1][0] == 0 || strcasecmp(sep->arg[1],"*")==0) || ((c->GetTarget()==0) || (c->GetTarget()->IsClient()))) - c->Message(Chat::White, "Type #npcedit help for more info"); - -} - -#ifdef PACKET_PROFILER -void command_packetprofile(Client *c, const Seperator *sep) { - Client *t = c; - if(c->GetTarget() && c->GetTarget()->IsClient()) { - t = c->GetTarget()->CastToClient(); - } - c->DumpPacketProfile(); -} -#endif - -#ifdef EQPROFILE -void command_profiledump(Client *c, const Seperator *sep) { - DumpZoneProfile(); -} - -void command_profilereset(Client *c, const Seperator *sep) { - ResetZoneProfile(); -} -#endif - -void command_opcode(Client *c, const Seperator *sep) { - if(!strcasecmp(sep->arg[1], "reload" )) { - ReloadAllPatches(); - c->Message(Chat::White, "Opcodes for all patches have been reloaded"); - } -} - -void command_qglobal(Client *c, const Seperator *sep) { - //In-game switch for qglobal column - if(sep->arg[1][0] == 0) { - c->Message(Chat::White, "Syntax: #qglobal [on/off/view]. Requires NPC target."); - return; - } - - Mob *target = c->GetTarget(); - - if(!target || !target->IsNPC()) { - c->Message(Chat::Red, "NPC Target Required!"); - return; - } - - if(!strcasecmp(sep->arg[1], "on")) { - std::string query = StringFormat("UPDATE npc_types SET qglobal = 1 WHERE id = '%i'", - target->GetNPCTypeID()); - auto results = content_db.QueryDatabase(query); - if(!results.Success()) { - c->Message(Chat::Yellow, "Could not update database."); - return; - } - - c->Message(Chat::Yellow, "Success! Changes take effect on zone reboot."); - return; - } - - if(!strcasecmp(sep->arg[1], "off")) { - std::string query = StringFormat("UPDATE npc_types SET qglobal = 0 WHERE id = '%i'", - target->GetNPCTypeID()); - auto results = content_db.QueryDatabase(query); - if(!results.Success()) { - c->Message(Chat::Yellow, "Could not update database."); - return; - } - - c->Message(Chat::Yellow, "Success! Changes take effect on zone reboot."); - return; - } - - if(!strcasecmp(sep->arg[1], "view")) { - const NPCType *type = content_db.LoadNPCTypesData(target->GetNPCTypeID()); - if(!type) - c->Message(Chat::Yellow, "Invalid NPC type."); - else if(type->qglobal) - c->Message(Chat::Yellow, "This NPC has quest globals active."); - else - c->Message(Chat::Yellow, "This NPC has quest globals disabled."); - return; - } - - c->Message(Chat::Yellow, "Invalid action specified."); -} - -void command_path(Client *c, const Seperator *sep) -{ - if (zone->pathing) { - zone->pathing->DebugCommand(c, sep); - } -} - -void Client::Undye() { - for (int cur_slot = EQ::textures::textureBegin; cur_slot <= EQ::textures::LastTexture; cur_slot++) { - uint8 slot2=SlotConvert(cur_slot); - EQ::ItemInstance* inst = m_inv.GetItem(slot2); - - if(inst != nullptr) { - inst->SetColor(inst->GetItem()->Color); - database.SaveInventory(CharacterID(), inst, slot2); - } - - m_pp.item_tint.Slot[cur_slot].Color = 0; - SendWearChange(cur_slot); - } - - database.DeleteCharacterDye(this->CharacterID()); -} - -void command_undye(Client *c, const Seperator *sep) -{ - if(c->GetTarget() && c->GetTarget()->IsClient()) - { - c->GetTarget()->CastToClient()->Undye(); - } - else - { - c->Message(Chat::White, "ERROR: Client target required"); - } -} - -void command_ucs(Client *c, const Seperator *sep) -{ - if (!c) - return; - - LogInfo("Character [{}] attempting ucs reconnect while ucs server is [{}] available", - c->GetName(), (zone->IsUCSServerAvailable() ? "" : "un")); - - if (zone->IsUCSServerAvailable()) { - EQApplicationPacket* outapp = nullptr; - std::string buffer; - - std::string MailKey = database.GetMailKey(c->CharacterID(), true); - EQ::versions::UCSVersion ConnectionType = EQ::versions::ucsUnknown; - - // chat server packet - switch (c->ClientVersion()) { - case EQ::versions::ClientVersion::Titanium: - ConnectionType = EQ::versions::ucsTitaniumChat; - break; - case EQ::versions::ClientVersion::SoF: - ConnectionType = EQ::versions::ucsSoFCombined; - break; - case EQ::versions::ClientVersion::SoD: - ConnectionType = EQ::versions::ucsSoDCombined; - break; - case EQ::versions::ClientVersion::UF: - ConnectionType = EQ::versions::ucsUFCombined; - break; - case EQ::versions::ClientVersion::RoF: - ConnectionType = EQ::versions::ucsRoFCombined; - break; - case EQ::versions::ClientVersion::RoF2: - ConnectionType = EQ::versions::ucsRoF2Combined; - break; - default: - ConnectionType = EQ::versions::ucsUnknown; - break; - } - - buffer = StringFormat("%s,%i,%s.%s,%c%s", - Config->ChatHost.c_str(), - Config->ChatPort, - Config->ShortName.c_str(), - c->GetName(), - ConnectionType, - MailKey.c_str() - ); - - outapp = new EQApplicationPacket(OP_SetChatServer, (buffer.length() + 1)); - memcpy(outapp->pBuffer, buffer.c_str(), buffer.length()); - outapp->pBuffer[buffer.length()] = '\0'; - - c->QueuePacket(outapp); - safe_delete(outapp); - - // mail server packet - switch (c->ClientVersion()) { - case EQ::versions::ClientVersion::Titanium: - ConnectionType = EQ::versions::ucsTitaniumMail; - break; - default: - // retain value from previous switch - break; - } - - buffer = StringFormat("%s,%i,%s.%s,%c%s", - Config->MailHost.c_str(), - Config->MailPort, - Config->ShortName.c_str(), - c->GetName(), - ConnectionType, - MailKey.c_str() - ); - - outapp = new EQApplicationPacket(OP_SetChatServer2, (buffer.length() + 1)); - memcpy(outapp->pBuffer, buffer.c_str(), buffer.length()); - outapp->pBuffer[buffer.length()] = '\0'; - - c->QueuePacket(outapp); - safe_delete(outapp); - } -} - -void command_undyeme(Client *c, const Seperator *sep) -{ - if(c) { - c->Undye(); - c->Message(Chat::Red, "Dye removed from all slots. Please zone for the process to complete."); - } -} - -void command_ginfo(Client *c, const Seperator *sep) -{ - Client *t; - - if(c->GetTarget() && c->GetTarget()->IsClient()) - t = c->GetTarget()->CastToClient(); - else - t = c; - - Group *g = t->GetGroup(); - if(!g) { - c->Message(Chat::White, "This client is not in a group"); - return; - } - - c->Message(Chat::White, "Player: %s is in Group #%lu: with %i members", t->GetName(), (unsigned long)g->GetID(), g->GroupCount()); - - uint32 r; - for(r = 0; r < MAX_GROUP_MEMBERS; r++) { - if(g->members[r] == nullptr) { - if(g->membername[r][0] == '\0') - continue; - c->Message(Chat::White, "...Zoned Member: %s, Roles: %s %s %s", g->membername[r], - (g->MemberRoles[r] & RoleAssist) ? "Assist" : "", - (g->MemberRoles[r] & RoleTank) ? "Tank" : "", - (g->MemberRoles[r] & RolePuller) ? "Puller" : ""); - } else { - c->Message(Chat::White, "...In-Zone Member: %s (0x%x) Roles: %s %s %s", g->membername[r], g->members[r], - (g->MemberRoles[r] & RoleAssist) ? "Assist" : "", - (g->MemberRoles[r] & RoleTank) ? "Tank" : "", - (g->MemberRoles[r] & RolePuller) ? "Puller" : ""); - - } - } -} - -void command_hp(Client *c, const Seperator *sep) -{ - c->SendHPUpdate(); - c->CheckManaEndUpdate(); -} - -void command_aggro(Client *c, const Seperator *sep) -{ - if(c->GetTarget() == nullptr || !c->GetTarget()->IsNPC()) { - c->Message(Chat::White, "Error: you must have an NPC target."); - return; - } - float d = atof(sep->arg[1]); - if(d == 0.0f) { - c->Message(Chat::Red, "Error: distance argument required."); - return; - } - bool verbose = false; - if(sep->arg[2][0] == '-' && sep->arg[2][1] == 'v' && sep->arg[2][2] == '\0') { - verbose = true; - } - - entity_list.DescribeAggro(c, c->GetTarget()->CastToNPC(), d, verbose); -} - -void command_pf(Client *c, const Seperator *sep) -{ - if(c->GetTarget()) - { - Mob *who = c->GetTarget(); - c->Message(Chat::White, "POS: (%.2f, %.2f, %.2f)", who->GetX(), who->GetY(), who->GetZ()); - c->Message(Chat::White, "WP: %s (%d/%d)", to_string(who->GetCurrentWayPoint()).c_str(), who->IsNPC()?who->CastToNPC()->GetMaxWp():-1); - c->Message(Chat::White, "pause=%d RAspeed=%d", who->GetCWPP(), who->GetRunAnimSpeed()); - //who->DumpMovement(c); - } else { - c->Message(Chat::White, "ERROR: target required"); - } -} - -void command_bestz(Client *c, const Seperator *sep) { - if (zone->zonemap == nullptr) { - c->Message(Chat::White,"Map not loaded for this zone"); - } else { - glm::vec3 me; - me.x = c->GetX(); - me.y = c->GetY(); - me.z = c->GetZ() + (c->GetSize() == 0.0 ? 6 : c->GetSize()) * HEAD_POSITION; - glm::vec3 hit; - glm::vec3 bme(me); - bme.z -= 500; - - float best_z = zone->zonemap->FindBestZ(me, &hit); - - if (best_z != BEST_Z_INVALID) - { - c->Message(Chat::White, "Z is %.3f at (%.3f, %.3f).", best_z, me.x, me.y); - } - else - { - c->Message(Chat::White, "Found no Z."); - } - } - - if(zone->watermap == nullptr) { - c->Message(Chat::White,"Water Region Map not loaded for this zone"); - } else { - WaterRegionType RegionType; - float z; - - if(c->GetTarget()) { - z=c->GetTarget()->GetZ(); - auto position = glm::vec3(c->GetTarget()->GetX(), c->GetTarget()->GetY(), z); - RegionType = zone->watermap->ReturnRegionType(position); - c->Message(Chat::White,"InWater returns %d", zone->watermap->InWater(position)); - c->Message(Chat::White,"InLava returns %d", zone->watermap->InLava(position)); - - } - else { - z=c->GetZ(); - auto position = glm::vec3(c->GetX(), c->GetY(), z); - RegionType = zone->watermap->ReturnRegionType(position); - c->Message(Chat::White,"InWater returns %d", zone->watermap->InWater(position)); - c->Message(Chat::White,"InLava returns %d", zone->watermap->InLava(position)); - - } - - switch(RegionType) { - case RegionTypeNormal: { c->Message(Chat::White,"There is nothing special about the region you are in!"); break; } - case RegionTypeWater: { c->Message(Chat::White,"You/your target are in Water."); break; } - case RegionTypeLava: { c->Message(Chat::White,"You/your target are in Lava."); break; } - case RegionTypeVWater: { c->Message(Chat::White,"You/your target are in VWater (Icy Water?)."); break; } - case RegionTypePVP: { c->Message(Chat::White, "You/your target are in a pvp enabled area."); break; } - case RegionTypeSlime: { c->Message(Chat::White, "You/your target are in slime."); break; } - case RegionTypeIce: { c->Message(Chat::White, "You/your target are in ice."); break; } - default: c->Message(Chat::White,"You/your target are in an unknown region type."); - } - } - - -} - - -void command_reloadstatic(Client *c, const Seperator *sep) { - c->Message(Chat::White, "Reloading zone static data..."); - zone->ReloadStaticData(); -} - -void command_flags(Client *c, const Seperator *sep) { - Client *t = c; - - if(c->Admin() >= minStatusToSeeOthersZoneFlags) { - Mob *tgt = c->GetTarget(); - if(tgt != nullptr && tgt->IsClient()) - t = tgt->CastToClient(); - } - - t->SendZoneFlagInfo(c); -} - -void command_flagedit(Client *c, const Seperator *sep) { - //super-command for editing zone flags - if(sep->arg[1][0] == '\0' || !strcasecmp(sep->arg[1], "help")) { - c->Message(Chat::White, "Syntax: #flagedit [lockzone|unlockzone|listzones|give|take]."); - c->Message(Chat::White, "...lockzone [zone id/short] [flag name] - Set the specified flag name on the zone, locking the zone"); - c->Message(Chat::White, "...unlockzone [zone id/short] - Removes the flag requirement from the specified zone"); - c->Message(Chat::White, "...listzones - List all zones which require a flag, and their flag's name"); - c->Message(Chat::White, "...give [zone id/short] - Give your target the zone flag for the specified zone."); - c->Message(Chat::White, "...take [zone id/short] - Take the zone flag for the specified zone away from your target"); - c->Message(Chat::White, "...Note: use #flags to view flags on a person"); - return; - } - - if(!strcasecmp(sep->arg[1], "lockzone")) { - uint32 zoneid = 0; - if(sep->arg[2][0] != '\0') { - zoneid = atoi(sep->arg[2]); - if(zoneid < 1) { - zoneid = ZoneID(sep->arg[2]); - } - } - if(zoneid < 1) { - c->Message(Chat::Red, "zone required. see help."); - return; - } - - char flag_name[128]; - if(sep->argplus[3][0] == '\0') { - c->Message(Chat::Red, "flag name required. see help."); - return; - } - database.DoEscapeString(flag_name, sep->argplus[3], 64); - flag_name[127] = '\0'; - - std::string query = StringFormat("UPDATE zone SET flag_needed = '%s' " - "WHERE zoneidnumber = %d AND version = %d", - flag_name, zoneid, zone->GetInstanceVersion()); - auto results = content_db.QueryDatabase(query); - if(!results.Success()) { - c->Message(Chat::Red, "Error updating zone: %s", results.ErrorMessage().c_str()); - return; - } - - c->Message(Chat::Yellow, "Success! Zone %s now requires a flag, named %s", ZoneName(zoneid), flag_name); - return; - } - - if(!strcasecmp(sep->arg[1], "unlockzone")) { - uint32 zoneid = 0; - if(sep->arg[2][0] != '\0') { - zoneid = atoi(sep->arg[2]); - if(zoneid < 1) { - zoneid = ZoneID(sep->arg[2]); - } - } - - if(zoneid < 1) { - c->Message(Chat::Red, "zone required. see help."); - return; - } - - std::string query = StringFormat("UPDATE zone SET flag_needed = '' " - "WHERE zoneidnumber = %d AND version = %d", - zoneid, zone->GetInstanceVersion()); - auto results = content_db.QueryDatabase(query); - if(!results.Success()) { - c->Message(Chat::Yellow, "Error updating zone: %s", results.ErrorMessage().c_str()); - return; - } - - c->Message(Chat::Yellow, "Success! Zone %s no longer requires a flag.", ZoneName(zoneid)); - return; - } - - if(!strcasecmp(sep->arg[1], "listzones")) { - std::string query = "SELECT zoneidnumber, short_name, long_name, version, flag_needed " - "FROM zone WHERE flag_needed != ''"; - auto results = content_db.QueryDatabase(query); - if (!results.Success()) { - return; - } - - c->Message(Chat::White, "Zones which require flags:"); - for (auto row = results.begin(); row != results.end(); ++row) - c->Message(Chat::White, "Zone %s (%s,%s) version %s requires key %s", row[2], row[0], row[1], row[3], row[4]); - - return; - } - - if(!strcasecmp(sep->arg[1], "give")) { - uint32 zoneid = 0; - if(sep->arg[2][0] != '\0') { - zoneid = atoi(sep->arg[2]); - if(zoneid < 1) { - zoneid = ZoneID(sep->arg[2]); - } - } - if(zoneid < 1) { - c->Message(Chat::Red, "zone required. see help."); - return; - } - - Mob *t = c->GetTarget(); - if(t == nullptr || !t->IsClient()) { - c->Message(Chat::Red, "client target required"); - return; - } - - t->CastToClient()->SetZoneFlag(zoneid); - return; - } - - if(!strcasecmp(sep->arg[1], "give")) { - uint32 zoneid = 0; - if(sep->arg[2][0] != '\0') { - zoneid = atoi(sep->arg[2]); - if(zoneid < 1) { - zoneid = ZoneID(sep->arg[2]); - } - } - if(zoneid < 1) { - c->Message(Chat::Red, "zone required. see help."); - return; - } - - Mob *t = c->GetTarget(); - if(t == nullptr || !t->IsClient()) { - c->Message(Chat::Red, "client target required"); - return; - } - - t->CastToClient()->ClearZoneFlag(zoneid); - return; - } - - c->Message(Chat::Yellow, "Invalid action specified. use '#flagedit help' for help"); -} - -void command_serverrules(Client *c, const Seperator *sep) -{ - c->SendRules(c); -} - -void command_acceptrules(Client *c, const Seperator *sep) -{ - if(!database.GetAgreementFlag(c->AccountID())) - { - database.SetAgreementFlag(c->AccountID()); - c->SendAppearancePacket(AT_Anim, ANIM_STAND); - c->Message(Chat::White,"It is recorded you have agreed to the rules."); - } -} - -void command_guildcreate(Client *c, const Seperator *sep) -{ - if(strlen(sep->argplus[1])>4 && strlen(sep->argplus[1])<16) - { - guild_mgr.AddGuildApproval(sep->argplus[1],c); - } - else - { - c->Message(Chat::White,"Guild name must be more than 4 characters and less than 16."); - } -} - -void command_guildapprove(Client *c, const Seperator *sep) -{ - guild_mgr.AddMemberApproval(atoi(sep->arg[1]),c); -} - -void command_guildlist(Client *c, const Seperator *sep) -{ - GuildApproval* tmp = guild_mgr.FindGuildByIDApproval(atoi(sep->arg[1])); - if(tmp) - { - tmp->ApprovedMembers(c); - } - else - c->Message(Chat::White,"Could not find reference id."); -} - -void command_hatelist(Client *c, const Seperator *sep) { - Mob *target = c->GetTarget(); - if(target == nullptr) { - c->Message(Chat::White, "Error: you must have a target."); - return; - } - - c->Message(Chat::White, "Display hate list for %s..", target->GetName()); - target->PrintHateListToClient(c); -} - - -void command_rules(Client *c, const Seperator *sep) { - //super-command for managing rules settings - if(sep->arg[1][0] == '\0' || !strcasecmp(sep->arg[1], "help")) { - c->Message(Chat::White, "Syntax: #rules [subcommand]."); - c->Message(Chat::White, "-- Rule Set Manipulation --"); - c->Message(Chat::White, "...listsets - List avaliable rule sets"); - c->Message(Chat::White, "...current - gives the name of the ruleset currently running in this zone"); - c->Message(Chat::White, "...reload - Reload the selected ruleset in this zone"); - c->Message(Chat::White, "...switch (ruleset name) - Change the selected ruleset and load it"); - c->Message(Chat::White, "...load (ruleset name) - Load a ruleset in just this zone without changing the selected set"); -//too lazy to write this right now: -// c->Message(Chat::White, "...wload (ruleset name) - Load a ruleset in all zones without changing the selected set"); - c->Message(Chat::White, "...store [ruleset name] - Store the running ruleset as the specified name"); - c->Message(Chat::White, "---------------------"); - c->Message(Chat::White, "-- Running Rule Manipulation --"); - c->Message(Chat::White, "...reset - Reset all rules to their default values"); - c->Message(Chat::White, "...get [rule] - Get the specified rule's local value"); - c->Message(Chat::White, "...set (rule) (value) - Set the specified rule to the specified value locally only"); - c->Message(Chat::White, "...setdb (rule) (value) - Set the specified rule to the specified value locally and in the DB"); - c->Message(Chat::White, "...list [catname] - List all rules in the specified category (or all categiries if omitted)"); - c->Message(Chat::White, "...values [catname] - List the value of all rules in the specified category"); - return; - } - - if(!strcasecmp(sep->arg[1], "current")) { - c->Message(Chat::White, "Currently running ruleset '%s' (%d)", RuleManager::Instance()->GetActiveRuleset(), - RuleManager::Instance()->GetActiveRulesetID()); - } else if(!strcasecmp(sep->arg[1], "listsets")) { - std::map sets; - if(!RuleManager::Instance()->ListRulesets(&database, sets)) { - c->Message(Chat::Red, "Failed to list rule sets!"); - return; - } - - c->Message(Chat::White, "Avaliable rule sets:"); - std::map::iterator cur, end; - cur = sets.begin(); - end = sets.end(); - for(; cur != end; ++cur) { - c->Message(Chat::White, "(%d) %s", cur->first, cur->second.c_str()); - } - } else if(!strcasecmp(sep->arg[1], "reload")) { - RuleManager::Instance()->LoadRules(&database, RuleManager::Instance()->GetActiveRuleset(), true); - c->Message(Chat::White, "The active ruleset (%s (%d)) has been reloaded", RuleManager::Instance()->GetActiveRuleset(), - RuleManager::Instance()->GetActiveRulesetID()); - } else if(!strcasecmp(sep->arg[1], "switch")) { - //make sure this is a valid rule set.. - int rsid = RuleManager::Instance()->GetRulesetID(&database, sep->arg[2]); - if(rsid < 0) { - c->Message(Chat::Red, "Unknown rule set '%s'", sep->arg[2]); - return; - } - if(!database.SetVariable("RuleSet", sep->arg[2])) { - c->Message(Chat::Red, "Failed to update variables table to change selected rule set"); - return; - } - - //TODO: we likely want to reload this ruleset everywhere... - RuleManager::Instance()->LoadRules(&database, sep->arg[2], true); - - c->Message(Chat::White, "The selected ruleset has been changed to (%s (%d)) and reloaded locally", sep->arg[2], rsid); - } else if(!strcasecmp(sep->arg[1], "load")) { - //make sure this is a valid rule set.. - int rsid = RuleManager::Instance()->GetRulesetID(&database, sep->arg[2]); - if(rsid < 0) { - c->Message(Chat::Red, "Unknown rule set '%s'", sep->arg[2]); - return; - } - RuleManager::Instance()->LoadRules(&database, sep->arg[2], true); - c->Message(Chat::White, "Loaded ruleset '%s' (%d) locally", sep->arg[2], rsid); - } else if(!strcasecmp(sep->arg[1], "store")) { - if(sep->argnum == 1) { - //store current rule set. - RuleManager::Instance()->SaveRules(&database); - c->Message(Chat::White, "Rules saved"); - } else if(sep->argnum == 2) { - RuleManager::Instance()->SaveRules(&database, sep->arg[2]); - int prersid = RuleManager::Instance()->GetActiveRulesetID(); - int rsid = RuleManager::Instance()->GetRulesetID(&database, sep->arg[2]); - if(rsid < 0) { - c->Message(Chat::Red, "Unable to query ruleset ID after store, it most likely failed."); - } else { - c->Message(Chat::White, "Stored rules as ruleset '%s' (%d)", sep->arg[2], rsid); - if(prersid != rsid) { - c->Message(Chat::White, "Rule set %s (%d) is now active in this zone", sep->arg[2], rsid); - } - } - } else { - c->Message(Chat::Red, "Invalid argument count, see help."); - return; - } - } else if(!strcasecmp(sep->arg[1], "reset")) { - RuleManager::Instance()->ResetRules(true); - c->Message(Chat::White, "The running ruleset has been set to defaults"); - - } else if(!strcasecmp(sep->arg[1], "get")) { - if(sep->argnum != 2) { - c->Message(Chat::Red, "Invalid argument count, see help."); - return; - } - std::string value; - if(!RuleManager::Instance()->GetRule(sep->arg[2], value)) - c->Message(Chat::Red, "Unable to find rule %s", sep->arg[2]); - else - c->Message(Chat::White, "%s - %s", sep->arg[2], value.c_str()); - - } else if(!strcasecmp(sep->arg[1], "set")) { - if(sep->argnum != 3) { - c->Message(Chat::Red, "Invalid argument count, see help."); - return; - } - if(!RuleManager::Instance()->SetRule(sep->arg[2], sep->arg[3], nullptr, false, true)) { - c->Message(Chat::Red, "Failed to modify rule"); - } else { - c->Message(Chat::White, "Rule modified locally."); - } - } else if(!strcasecmp(sep->arg[1], "setdb")) { - if(sep->argnum != 3) { - c->Message(Chat::Red, "Invalid argument count, see help."); - return; - } - if(!RuleManager::Instance()->SetRule(sep->arg[2], sep->arg[3], &database, true, true)) { - c->Message(Chat::Red, "Failed to modify rule"); - } else { - c->Message(Chat::White, "Rule modified locally and in the database."); - } - } else if(!strcasecmp(sep->arg[1], "list")) { - if(sep->argnum == 1) { - std::vector rule_list; - if(!RuleManager::Instance()->ListCategories(rule_list)) { - c->Message(Chat::Red, "Failed to list categories!"); - return; - } - c->Message(Chat::White, "Rule Categories:"); - std::vector::iterator cur, end; - cur = rule_list.begin(); - end = rule_list.end(); - for(; cur != end; ++cur) { - c->Message(Chat::White, " %s", *cur); - } - } else if(sep->argnum == 2) { - const char *catfilt = nullptr; - if(std::string("all") != sep->arg[2]) - catfilt = sep->arg[2]; - std::vector rule_list; - if(!RuleManager::Instance()->ListRules(catfilt, rule_list)) { - c->Message(Chat::Red, "Failed to list rules!"); - return; - } - c->Message(Chat::White, "Rules in category %s:", sep->arg[2]); - std::vector::iterator cur, end; - cur = rule_list.begin(); - end = rule_list.end(); - for(; cur != end; ++cur) { - c->Message(Chat::White, " %s", *cur); - } - } else { - c->Message(Chat::Red, "Invalid argument count, see help."); - } - } else if(!strcasecmp(sep->arg[1], "values")) { - if(sep->argnum != 2) { - c->Message(Chat::Red, "Invalid argument count, see help."); - return; - } else { - const char *catfilt = nullptr; - if(std::string("all") != sep->arg[2]) - catfilt = sep->arg[2]; - std::vector rule_list; - if(!RuleManager::Instance()->ListRules(catfilt, rule_list)) { - c->Message(Chat::Red, "Failed to list rules!"); - return; - } - c->Message(Chat::White, "Rules & values in category %s:", sep->arg[2]); - std::vector::iterator cur, end; - cur = rule_list.begin(); - end = rule_list.end(); - for(std::string tmp_value; cur != end; ++cur) { - if (RuleManager::Instance()->GetRule(*cur, tmp_value)) - c->Message(Chat::White, " %s - %s", *cur, tmp_value.c_str()); - } - } - - } else { - c->Message(Chat::Yellow, "Invalid action specified. use '#rules help' for help"); - } -} - - -void command_task(Client *c, const Seperator *sep) { - //super-command for managing tasks - if(sep->arg[1][0] == '\0' || !strcasecmp(sep->arg[1], "help")) { - c->Message(Chat::White, "Syntax: #task [subcommand]"); - c->Message(Chat::White, "------------------------------------------------"); - c->Message(Chat::White, "# Task System Commands"); - c->Message(Chat::White, "------------------------------------------------"); - c->Message( - Chat::White, - fmt::format( - "--- [{}] List active tasks for a client", - EQ::SayLinkEngine::GenerateQuestSaylink("#task show", false, "show") - ).c_str() - ); - c->Message(Chat::White, "--- update [count] | Updates task"); - c->Message(Chat::White, "--- assign | Assigns task to client"); - c->Message( - Chat::White, - fmt::format( - "--- [{}] Reload all Task information from the database", - EQ::SayLinkEngine::GenerateQuestSaylink("#task reloadall", false, "reloadall") - ).c_str() - ); - c->Message( - Chat::White, - fmt::format( - "--- [{}] Reload Task and Activity information for a single task", - EQ::SayLinkEngine::GenerateQuestSaylink("#task reload task", false, "reload task") - ).c_str() - ); - c->Message( - Chat::White, - fmt::format( - "--- [{}] Reload goal/reward list information", - EQ::SayLinkEngine::GenerateQuestSaylink("#task reload lists", false, "reload lists") - ).c_str() - ); - c->Message( - Chat::White, - fmt::format( - "--- [{}] Reload proximity information", - EQ::SayLinkEngine::GenerateQuestSaylink("#task reload prox", false, "reload prox") - ).c_str() - ); - c->Message( - Chat::White, - fmt::format( - "--- [{}] Reload task set information", - EQ::SayLinkEngine::GenerateQuestSaylink("#task reload sets", false, "reload sets") - ).c_str() - ); - return; - } - - Client *client_target = c; - if (c->GetTarget() && c->GetTarget()->IsClient()) { - client_target = c->GetTarget()->CastToClient(); - } - - if (!strcasecmp(sep->arg[1], "show")) { - c->ShowClientTasks(client_target); - return; - } - - if (!strcasecmp(sep->arg[1], "update")) { - if (sep->argnum >= 3) { - int task_id = atoi(sep->arg[2]); - int activity_id = atoi(sep->arg[3]); - int count = 1; - - if (sep->argnum >= 4) { - count = atoi(sep->arg[4]); - if (count <= 0) { - count = 1; - } - } - c->Message( - Chat::Yellow, - "Updating Task [%i] Activity [%i] Count [%i] for client [%s]", - task_id, - activity_id, - count, - client_target->GetCleanName() - ); - client_target->UpdateTaskActivity(task_id, activity_id, count); - c->ShowClientTasks(client_target); - } - return; - } - - if (!strcasecmp(sep->arg[1], "assign")) { - int task_id = atoi(sep->arg[2]); - if ((task_id > 0) && (task_id < MAXTASKS)) { - client_target->AssignTask(task_id, 0, false); - c->Message(Chat::Yellow, "Assigned task [%i] to [%s]", task_id, client_target->GetCleanName()); - } - return; - } - - if (!strcasecmp(sep->arg[1], "reloadall")) { - c->Message(Chat::Yellow, "Sending reloadtasks to world"); - worldserver.SendReloadTasks(RELOADTASKS); - c->Message(Chat::Yellow, "Back again"); - return; - } - - if (!strcasecmp(sep->arg[1], "reload")) { - if (sep->arg[2][0] != '\0') { - if (!strcasecmp(sep->arg[2], "lists")) { - c->Message(Chat::Yellow, "Sending reload lists to world"); - worldserver.SendReloadTasks(RELOADTASKGOALLISTS); - c->Message(Chat::Yellow, "Reloaded"); - return; - } - if (!strcasecmp(sep->arg[2], "prox")) { - c->Message(Chat::Yellow, "Sending reload proximities to world"); - worldserver.SendReloadTasks(RELOADTASKPROXIMITIES); - c->Message(Chat::Yellow, "Reloaded"); - return; - } - if (!strcasecmp(sep->arg[2], "sets")) { - c->Message(Chat::Yellow, "Sending reload task sets to world"); - worldserver.SendReloadTasks(RELOADTASKSETS); - c->Message(Chat::Yellow, "Reloaded"); - return; - } - if (!strcasecmp(sep->arg[2], "task") && (sep->arg[3][0] != '\0')) { - int task_id = atoi(sep->arg[3]); - if ((task_id > 0) && (task_id < MAXTASKS)) { - c->Message(Chat::Yellow, "Sending reload task %i to world", task_id); - worldserver.SendReloadTasks(RELOADTASKS, task_id); - c->Message(Chat::Yellow, "Reloaded"); - return; - } - } - } - - } - c->Message(Chat::White, "Unable to interpret command. Type #task help"); - -} -void command_reloadtitles(Client *c, const Seperator *sep) -{ - auto pack = new ServerPacket(ServerOP_ReloadTitles, 0); - worldserver.SendPacket(pack); - safe_delete(pack); - c->Message(Chat::Yellow, "Player Titles Reloaded."); - -} - -void command_traindisc(Client *c, const Seperator *sep) -{ - Client *t = c; - if (c->GetTarget() && c->GetTarget()->IsClient() && c->GetGM()) - t = c->GetTarget()->CastToClient(); - - if (sep->argnum < 1 || !sep->IsNumber(1)) { - c->Message(Chat::White, "FORMAT: #traindisc "); - return; - } - - uint8 max_level = (uint8)atol(sep->arg[1]); - if (!c->GetGM() && max_level >(uint8)RuleI(Character, MaxLevel)) - max_level = (uint8)RuleI(Character, MaxLevel); // default to Character:MaxLevel if we're not a GM & it's higher than the max level - - uint8 min_level = (sep->IsNumber(2) ? (uint8)atol(sep->arg[2]) : 1); // default to 1 if there isn't a 2nd argument - if (!c->GetGM() && min_level > (uint8)RuleI(Character, MaxLevel)) - min_level = (uint8)RuleI(Character, MaxLevel); // default to Character:MaxLevel if we're not a GM & it's higher than the max level - - if(max_level < 1 || min_level < 1) { - c->Message(Chat::White, "ERROR: Level must be greater than 1."); - return; - } - if (min_level > max_level) { - c->Message(Chat::White, "Error: Min Level must be less than or equal to Max Level."); - return; - } - - t->Message(Chat::White, "Training disciplines"); - if(t != c) - c->Message(Chat::White, "Training disciplines for %s.", t->GetName()); - LogInfo("Train disciplines request for [{}] from [{}], levels: [{}] -> [{}]", t->GetName(), c->GetName(), min_level, max_level); - - int spell_id = 0; - int count = 0; - - bool change = false; - - for( ; spell_id < SPDAT_RECORDS; ++spell_id) { - if (spell_id < 0 || spell_id >= SPDAT_RECORDS) { - c->Message(Chat::Red, "FATAL ERROR: Spell id out-of-range (id: %i, min: 0, max: %i)", spell_id, SPDAT_RECORDS); - return; - } - - while (true) { - if (spells[spell_id].classes[WARRIOR] == 0) // check if spell exists - break; - if (spells[spell_id].classes[t->GetPP().class_ - 1] > max_level) // maximum level - break; - if (spells[spell_id].classes[t->GetPP().class_ - 1] < min_level) // minimum level - break; - if (spells[spell_id].skill == 52) - break; - - uint16 spell_id_ = (uint16)spell_id; - if ((spell_id_ != spell_id) || (spell_id != spell_id_)) { - c->Message(Chat::Red, "FATAL ERROR: Type conversion data loss with spell_id (%i != %u)", spell_id, spell_id_); - return; - } - - if (!IsDiscipline(spell_id_)) - break; - - for (uint32 r = 0; r < MAX_PP_DISCIPLINES; ++r) { - if (t->GetPP().disciplines.values[r] == spell_id_) { - t->Message(Chat::Red, "You already know this discipline."); - break; // continue the 1st loop - } - else if (t->GetPP().disciplines.values[r] == 0) { - t->GetPP().disciplines.values[r] = spell_id_; - database.SaveCharacterDisc(t->CharacterID(), r, spell_id_); - change = true; - t->Message(Chat::White, "You have learned a new discipline!"); - ++count; // success counter - break; // continue the 1st loop - } // if we get to this point, there's already a discipline in this slot, so we continue onto the next slot - } - - break; - } - } - - if (change) - t->SendDisciplineUpdate(); - - if (count > 0) { - t->Message(Chat::White, "Successfully trained %u disciplines.", count); - if (t != c) - c->Message(Chat::White, "Successfully trained %u disciplines for %s.", count, t->GetName()); - } else { - t->Message(Chat::White, "No disciplines trained."); - if (t != c) - c->Message(Chat::White, "No disciplines trained for %s.", t->GetName()); - } -} - -void command_setgraveyard(Client *c, const Seperator *sep) -{ - uint32 zoneid = 0; - uint32 graveyard_id = 0; - Client *t=c; - - if(c->GetTarget() && c->GetTarget()->IsClient() && c->GetGM()) - t=c->GetTarget()->CastToClient(); - - if(!sep->arg[1][0]) { - c->Message(Chat::White, "Usage: #setgraveyard [zonename]"); - return; - } - - zoneid = ZoneID(sep->arg[1]); - - if(zoneid > 0) { - graveyard_id = content_db.CreateGraveyardRecord(zoneid, t->GetPosition()); - - if(graveyard_id > 0) { - c->Message(Chat::White, "Successfuly added a new record for this graveyard!"); - if(content_db.AddGraveyardIDToZone(zoneid, graveyard_id) > 0) { - c->Message(Chat::White, "Successfuly added this new graveyard for the zone %s.", sep->arg[1]); - // TODO: Set graveyard data to the running zone process. - c->Message(Chat::White, "Done!"); - } - else - c->Message(Chat::White, "Unable to add this new graveyard to the zone %s.", sep->arg[1]); - } - else { - c->Message(Chat::White, "Unable to create a new graveyard record in the database."); - } - } - else { - c->Message(Chat::White, "Unable to retrieve a ZoneID for the zone: %s", sep->arg[1]); - } - - return; -} - -void command_deletegraveyard(Client *c, const Seperator *sep) -{ - uint32 zoneid = 0; - uint32 graveyard_id = 0; - - if(!sep->arg[1][0]) { - c->Message(Chat::White, "Usage: #deletegraveyard [zonename]"); - return; - } - - zoneid = ZoneID(sep->arg[1]); - graveyard_id = content_db.GetZoneGraveyardID(zoneid, 0); - - if(zoneid > 0 && graveyard_id > 0) { - if(content_db.DeleteGraveyard(zoneid, graveyard_id)) - c->Message(Chat::White, "Successfuly deleted graveyard %u for zone %s.", graveyard_id, sep->arg[1]); - else - c->Message(Chat::White, "Unable to delete graveyard %u for zone %s.", graveyard_id, sep->arg[1]); - } - else { - if(zoneid <= 0) - c->Message(Chat::White, "Unable to retrieve a ZoneID for the zone: %s", sep->arg[1]); - else if(graveyard_id <= 0) - c->Message(Chat::White, "Unable to retrieve a valid GraveyardID for the zone: %s", sep->arg[1]); - } - - return; -} - -void command_summonburiedplayercorpse(Client *c, const Seperator *sep) -{ - Client *t=c; - - if(c->GetTarget() && c->GetTarget()->IsClient() && c->GetGM()) - t = c->GetTarget()->CastToClient(); - else { - c->Message(Chat::White, "You must first select a target!"); - return; - } - - Corpse* PlayerCorpse = database.SummonBuriedCharacterCorpses(t->CharacterID(), t->GetZoneID(), zone->GetInstanceID(), t->GetPosition()); - - if(!PlayerCorpse) - c->Message(Chat::White, "Your target doesn't have any buried corpses."); - - return; -} - -void command_getplayerburiedcorpsecount(Client *c, const Seperator *sep) -{ - Client *t=c; - - if(c->GetTarget() && c->GetTarget()->IsClient() && c->GetGM()) - t = c->GetTarget()->CastToClient(); - else { - c->Message(Chat::White, "You must first select a target!"); - return; - } - - uint32 CorpseCount = database.GetCharacterBuriedCorpseCount(t->CharacterID()); - - if(CorpseCount > 0) - c->Message(Chat::White, "Your target has a total of %u buried corpses.", CorpseCount); - else - c->Message(Chat::White, "Your target doesn't have any buried corpses."); - - return; -} - -void command_refreshgroup(Client *c, const Seperator *sep) -{ - if(!c) - return; - - Group *g = c->GetGroup(); - - if(!g) - return; - - database.RefreshGroupFromDB(c); - //g->SendUpdate(7, c); -} - -void command_advnpcspawn(Client *c, const Seperator *sep) -{ - Mob *target=c->GetTarget(); - - if (strcasecmp(sep->arg[1], "maketype") == 0) { - if(!target || !target->IsNPC()) { - c->Message(Chat::White, "Target Required!"); - return; - } - - content_db.NPCSpawnDB(6, zone->GetShortName(), zone->GetInstanceVersion(), c, target->CastToNPC()); - return; - } - - if (strcasecmp(sep->arg[1], "makegroup") == 0) { - if(!sep->arg[2]) { - c->Message(Chat::White, "Format: #advnpdspawn makegroup [spawn limit] [dist] [max x] [min x] [max y] [min y] [delay]"); - return; - } - - std::string query = StringFormat("INSERT INTO spawngroup " - "(name, spawn_limit, dist, max_x, min_x, max_y, min_y, delay) " - "VALUES (\"%s\", %i, %f, %f, %f, %f, %f, %i)", - sep->arg[2], - (sep->arg[3]? atoi(sep->arg[3]): 0), - (sep->arg[4]? atof(sep->arg[4]): 0), - (sep->arg[5]? atof(sep->arg[5]): 0), - (sep->arg[6]? atof(sep->arg[6]): 0), - (sep->arg[7]? atof(sep->arg[7]): 0), - (sep->arg[8]? atof(sep->arg[8]): 0), - (sep->arg[9]? atoi(sep->arg[9]): 0)); - auto results = content_db.QueryDatabase(query); - if (!results.Success()) { - c->Message(Chat::White, "Invalid Arguments -- MySQL gave the following error:"); - c->Message(Chat::Red, results.ErrorMessage().c_str()); - return; - } - - c->Message(Chat::White, "Group ID %i created successfully!", results.LastInsertedID()); - return; - } - - if (strcasecmp(sep->arg[1], "addgroupentry") == 0) { - if(!atoi(sep->arg[2]) || !atoi(sep->arg[3]) || !atoi(sep->arg[4])) { - c->Message(Chat::White, "Format: #advnpdspawn addgroupentry "); - return; - } - - std::string query = StringFormat("INSERT INTO spawnentry (spawngroupID, npcID, chance) " - "VALUES (%i, %i, %i)", - atoi(sep->arg[2]), atoi(sep->arg[3]), atoi(sep->arg[4])); - auto results = content_db.QueryDatabase(query); - if (!results.Success()) { - c->Message(Chat::White, "Invalid Arguments -- MySQL gave the following error:"); - c->Message(Chat::Red, results.ErrorMessage().c_str()); - return; - } - - c->Message(Chat::White, "NPC %i added to group %i with %i chance!", atoi(sep->arg[3]), atoi(sep->arg[2]), atoi(sep->arg[4]) ); - - return; - } - - if (strcasecmp(sep->arg[1], "editgroupbox") == 0) { - if(!atof(sep->arg[2]) || !atof(sep->arg[3]) || !atof(sep->arg[4]) || !atof(sep->arg[5]) || !atof(sep->arg[6]) || !atof(sep->arg[7]) || !atof(sep->arg[8])) { - c->Message(Chat::White, "Format: #advnpdspawn editgroupbox "); - return; - } - - std::string query = StringFormat("UPDATE spawngroup SET dist = '%f', max_x = '%f', min_x = '%f', " - "max_y = '%f', min_y = '%f', delay = '%i' WHERE id = '%i'", - atof(sep->arg[3]), atof(sep->arg[4]), atof(sep->arg[5]), - atof(sep->arg[6]), atof(sep->arg[7]), atoi(sep->arg[8]), - atoi(sep->arg[2])); - auto results = content_db.QueryDatabase(query); - if (!results.Success()) { - c->Message(Chat::White, "Invalid Arguments -- MySQL gave the following error:"); - c->Message(Chat::Red, results.ErrorMessage().c_str()); - return; - } - - c->Message(Chat::White, "Group ID %i created successfully!", results.LastInsertedID()); - - return; - } - - if (strcasecmp(sep->arg[1], "cleargroupbox") == 0) { - if(!atoi(sep->arg[2])) { - c->Message(Chat::White, "Format: #advnpdspawn cleargroupbox "); - return; - } - - std::string query = StringFormat("UPDATE spawngroup " - "SET dist = '0', max_x = '0', min_x = '0', " - "max_y = '0', min_y = '0', delay = '0' " - "WHERE id = '%i' ", atoi(sep->arg[2])); - auto results = content_db.QueryDatabase(query); - if (!results.Success()) { - c->Message(Chat::White, "Invalid Arguments -- MySQL gave the following error:"); - c->Message(Chat::Red, results.ErrorMessage().c_str()); - return; - } - - c->Message(Chat::White, "Group ID %i created successfully!", results.LastInsertedID()); - - return; - } - - if (strcasecmp(sep->arg[1], "addgroupspawn") == 0 && atoi(sep->arg[2])!=0) { - content_db.NPCSpawnDB(5, zone->GetShortName(), zone->GetInstanceVersion(), c, 0, atoi(sep->arg[2])); - c->Message(Chat::White, "Mob of group %i added successfully!", atoi(sep->arg[2])); - return; - } - - if (strcasecmp(sep->arg[1], "removegroupspawn") == 0) { - if (!target || !target->IsNPC()) { - c->Message(Chat::White, "Error: Need an NPC target."); - return; - } - - Spawn2* s2 = target->CastToNPC()->respawn2; - - if(!s2) { - c->Message(Chat::White, "removegroupspawn FAILED -- cannot determine which spawn entry in the database this mob came from."); - return; - } - - std::string query = StringFormat("DELETE FROM spawn2 WHERE id = '%i'", s2->GetID()); - auto results = content_db.QueryDatabase(query); - if(!results.Success()) { - c->Message(Chat::Red, "Update failed! MySQL gave the following error:"); - c->Message(Chat::Red, results.ErrorMessage().c_str()); - return; - } - - c->Message(Chat::White, "Spawnpoint Removed successfully."); - target->Depop(false); - - return; - } - - if (strcasecmp(sep->arg[1], "movespawn") == 0) { - if (!target || !target->IsNPC()) { - c->Message(Chat::White, "Error: Need an NPC target."); - return; - } - - Spawn2* s2 = target->CastToNPC()->respawn2; - - if(!s2) { - c->Message(Chat::White, "movespawn FAILED -- cannot determine which spawn entry in the database this mob came from."); - return; - } - - std::string query = StringFormat("UPDATE spawn2 SET x = '%f', y = '%f', z = '%f', heading = '%f' " - "WHERE id = '%i'", - c->GetX(), c->GetY(), c->GetZ(), c->GetHeading(),s2->GetID()); - auto results = content_db.QueryDatabase(query); - if (!results.Success()) { - c->Message(Chat::Red, "Update failed! MySQL gave the following error:"); - c->Message(Chat::Red, results.ErrorMessage().c_str()); - return; - } - - c->Message(Chat::White, "Updating coordinates successful."); - target->GMMove(c->GetX(), c->GetY(), c->GetZ(), c->GetHeading()); - - return; - } - - if (strcasecmp(sep->arg[1], "editrespawn") == 0) { - if (!target || !target->IsNPC()) { - c->Message(Chat::White, "Error: Need an NPC target."); - return; - } - - Spawn2* s2 = target->CastToNPC()->respawn2; - - uint32 new_rs = 0; - uint32 new_var = s2->GetVariance(); - if(!sep->IsNumber(2)) { - c->Message(Chat::White, "editrespawn FAILED -- cannot set respawn to be 0"); - return; - } - - new_rs = atoi(sep->arg[2]); - - if(sep->IsNumber(3)) - new_var = atoi(sep->arg[3]); - - if(!s2) { - c->Message(Chat::White, "editrespawn FAILED -- cannot determine which spawn entry in the database this mob came from."); - return; - } - - std::string query = StringFormat("UPDATE spawn2 SET respawntime = %u, variance = %u " - "WHERE id = '%i'", new_rs, new_var, s2->GetID()); - auto results = content_db.QueryDatabase(query); - if (!results.Success()) { - c->Message(Chat::Red, "Update failed! MySQL gave the following error:"); - c->Message(Chat::Red, results.ErrorMessage().c_str()); - return; - } - - c->Message(Chat::White, "Updating respawn timer successful."); - s2->SetRespawnTimer(new_rs); - s2->SetVariance(new_var); - - return; - } - - if (strcasecmp(sep->arg[1], "setversion") == 0) { - if (!target || !target->IsNPC()) { - c->Message(Chat::White, "Error: Need an NPC target."); - return; - } - - if(!sep->IsNumber(2)) { - c->Message(Chat::White, "setversion FAILED -- You must set a version number"); - return; - } - - int16 version = atoi(sep->arg[2]); - std::string query = StringFormat("UPDATE spawn2 SET version = %i " - "WHERE spawngroupID = '%i'", - version, c->GetTarget()->CastToNPC()->GetSpawnGroupId()); - auto results = content_db.QueryDatabase(query); - if (!results.Success()) { - c->Message(Chat::Red, "Update failed! MySQL gave the following error:"); - c->Message(Chat::Red, results.ErrorMessage().c_str()); - return; - } - - c->Message(Chat::White, "Version change to %i was successful from SpawnGroupID %i", version, - c->GetTarget()->CastToNPC()->GetSpawnGroupId()); - c->GetTarget()->Depop(false); - - return; - } - - if (strcasecmp(sep->arg[1], "testload") == 0 && atoi(sep->arg[2])!=0) { - content_db.LoadSpawnGroupsByID(atoi(sep->arg[2]),&zone->spawn_group_list); - c->Message(Chat::White, "Group %i loaded successfully!", atoi(sep->arg[2])); - return; - } - - c->Message(Chat::White, "Error: #advnpcspawn: Invalid command."); - c->Message(Chat::White, "Usage: #advnpcspawn [maketype|makegroup|addgroupentry|addgroupspawn|setversion]"); - c->Message(Chat::White, "Usage: #advnpcspawn [removegroupspawn|movespawn|editrespawn|editgroupbox|cleargroupbox]"); -} - -void command_aggrozone(Client *c, const Seperator *sep) { - if(!c) - return; - - Mob *m = c->CastToMob(); - - if (!m) - return; - - uint32 hate = atoi(sep->arg[1]); //should default to 0 if we don't enter anything - entity_list.AggroZone(m, hate); - c->Message(Chat::White, "Train to you! Last chance to go invulnerable..."); -} - -void command_modifynpcstat(Client *c, const Seperator *sep) -{ - if(!c) - return; - - if(sep->arg[1][0] == '\0') - { - c->Message(Chat::White, "usage #modifynpcstat arg value"); - c->Message(Chat::White, "Args: ac, str, sta, agi, dex, wis, _int, cha, max_hp, mr, fr, cr, pr, dr, runspeed, special_attacks, " - "attack_speed, atk, accuracy, trackable, min_hit, max_hit, see_invis_undead, see_hide, see_improved_hide, " - "hp_regen, mana_regen, aggro, assist, slow_mitigation, loottable_id, healscale, spellscale"); - return; - } - - if(!c->GetTarget()) - return; - - if(!c->GetTarget()->IsNPC()) - return; - - c->GetTarget()->CastToNPC()->ModifyNPCStat(sep->arg[1], sep->arg[2]); -} - -void command_instance(Client *c, const Seperator *sep) -{ - if(!c) - return; - - //options: - //help - //create [zone_id] [version] - //destroy [instance_id] - //add [instance_id] [player_name] - //remove [instance_id] [player_name] - //list [player_name] - - if(strcasecmp(sep->arg[1], "help") == 0) - { - c->Message(Chat::White, "#instance usage:"); - c->Message(Chat::White, "#instance create zone_id version duration - Creates an instance of version 'version' in the " - "zone with id matching zone_id, will last for duration seconds."); - c->Message(Chat::White, "#instance destroy instance_id - Destroys the instance with id matching instance_id."); - c->Message(Chat::White, "#instance add instance_id player_name - adds the player 'player_name' to the instance " - "with id matching instance_id."); - c->Message(Chat::White, "#instance remove instance_id player_name - removes the player 'player_name' from the " - "instance with id matching instance_id."); - c->Message(Chat::White, "#instance list player_name - lists all the instances 'player_name' is apart of."); - return; - } - else if(strcasecmp(sep->arg[1], "create") == 0) - { - if(!sep->IsNumber(3) || !sep->IsNumber(4)) - { - c->Message(Chat::White, "#instance create zone_id version duration - Creates an instance of version 'version' in the " - "zone with id matching zone_id, will last for duration seconds."); - return; - } - - const char * zn = nullptr; - uint32 zone_id = 0; - - if(sep->IsNumber(2)) - { - zone_id = atoi(sep->arg[2]); - } - else - { - zone_id = ZoneID(sep->arg[2]); - } - - uint32 version = atoi(sep->arg[3]); - uint32 duration = atoi(sep->arg[4]); - zn = ZoneName(zone_id); - - if(!zn) - { - c->Message(Chat::White, "Zone with id %lu was not found by the server.", (unsigned long)zone_id); - return; - } - - uint16 id = 0; - if(!database.GetUnusedInstanceID(id)) - { - c->Message(Chat::White, "Server was unable to find a free instance id."); - return; - } - - if(!database.CreateInstance(id, zone_id, version, duration)) - { - c->Message(Chat::White, "Server was unable to create a new instance."); - return; - } - - c->Message(Chat::White, "New instance %s was created with id %lu.", zn, (unsigned long)id); - } - else if(strcasecmp(sep->arg[1], "destroy") == 0) - { - if(!sep->IsNumber(2)) - { - c->Message(Chat::White, "#instance destroy instance_id - Destroys the instance with id matching instance_id."); - return; - } - - uint16 id = atoi(sep->arg[2]); - database.DeleteInstance(id); - c->Message(Chat::White, "Destroyed instance with id %lu.", (unsigned long)id); - } - else if(strcasecmp(sep->arg[1], "add") == 0) - { - if(!sep->IsNumber(2)) - { - c->Message(Chat::White, "#instance add instance_id player_name - adds the player 'player_name' to the instance " - "with id matching instance_id."); - return; - } - - uint16 id = atoi(sep->arg[2]); - uint32 charid = database.GetCharacterID(sep->arg[3]); - - if(id <= 0 || charid <= 0) - { - c->Message(Chat::White, "Must enter a valid instance id and player name."); - return; - } - - if(!database.CheckInstanceExists(id)) - { - c->Message(Chat::White, "Instance does not exist."); - return; - } - - uint32 zone_id = database.ZoneIDFromInstanceID(id); - uint32 version = database.VersionFromInstanceID(id); - uint32 cur_id = database.GetInstanceID(zone_id, charid, version); - if(cur_id == 0) - { - if(database.AddClientToInstance(id, charid)) - { - c->Message(Chat::White, "Added client to instance."); - } - else - { - c->Message(Chat::White, "Failed to add client to instance."); - } - } - else - { - c->Message(Chat::White, "Client was already saved to %u which has uses the same zone and version as that instance.", cur_id); - } - } - else if(strcasecmp(sep->arg[1], "remove") == 0) - { - if(!sep->IsNumber(2)) - { - c->Message(Chat::White, "#instance remove instance_id player_name - removes the player 'player_name' from the " - "instance with id matching instance_id."); - return; - } - - uint16 id = atoi(sep->arg[2]); - uint32 charid = database.GetCharacterID(sep->arg[3]); - - if(id <= 0 || charid <= 0) - { - c->Message(Chat::White, "Must enter a valid instance id and player name."); - } - - if(database.RemoveClientFromInstance(id, charid)) - { - c->Message(Chat::White, "Removed client from instance."); - } - else - { - c->Message(Chat::White, "Failed to remove client from instance."); - } - } - else if(strcasecmp(sep->arg[1], "list") == 0) - { - uint32 charid = database.GetCharacterID(sep->arg[2]); - if(charid <= 0) - { - if(c->GetTarget() == nullptr || (c->GetTarget() && !c->GetTarget()->IsClient())) - { - c->Message(Chat::White, "Character not found."); - return; - } - else - charid = c->GetTarget()->CastToClient()->CharacterID(); - } - - database.ListAllInstances(c, charid); - } - else - { - c->Message(Chat::White, "Invalid Argument."); - c->Message(Chat::White, "#instance usage:"); - c->Message(Chat::White, "#instance create zone_id version duration - Creates an instance of version 'version' in the " - "zone with id matching zone_id, will last for duration seconds."); - c->Message(Chat::White, "#instance destroy instance_id - Destroys the instance with id matching instance_id."); - c->Message(Chat::White, "#instance add instance_id player_name - adds the player 'player_name' to the instance " - "with id matching instance_id."); - c->Message(Chat::White, "#instance remove instance_id player_name - removes the player 'player_name' from the " - "instance with id matching instance_id."); - c->Message(Chat::White, "#instance list player_name - lists all the instances 'player_name' is apart of."); - return; - } -} - -void command_setstartzone(Client *c, const Seperator *sep) -{ - uint32 startzone = 0; - Client* target = nullptr; - if(c->GetTarget() && c->GetTarget()->IsClient() && sep->arg[1][0] != 0) - target = c->GetTarget()->CastToClient(); - else { - c->Message(Chat::White, "Usage: (needs PC target) #setstartzone zonename"); - c->Message(Chat::White, "Optional Usage: Use '#setstartzone reset' or '#setstartzone 0' to clear a starting zone. Player can select a starting zone using /setstartcity"); - return; - } - - if(sep->IsNumber(1)) { - startzone = atoi(sep->arg[1]); - } - else if(strcasecmp(sep->arg[1],"reset") == 0) { - startzone = 0; - } - else { - startzone = ZoneID(sep->arg[1]); - if(startzone == 0) { - c->Message(Chat::White, "Unable to locate zone '%s'", sep->arg[1]); - return; - } - } - - target->SetStartZone(startzone); -} - -void command_netstats(Client *c, const Seperator *sep) -{ - if(c) - { - auto client = c; - if (c->GetTarget() && c->GetTarget()->IsClient()) { - client = c->GetTarget()->CastToClient(); - } - - if (strcasecmp(sep->arg[1], "reset") == 0) { - auto connection = c->Connection(); - c->Message(Chat::White, "Resetting client stats (packet loss will not read correctly after reset)."); - connection->ResetStats(); - return; - } - - auto connection = c->Connection(); - auto opts = connection->GetManager()->GetOptions(); - auto eqs_stats = connection->GetStats(); - auto &stats = eqs_stats.DaybreakStats; - auto now = EQ::Net::Clock::now(); - auto sec_since_stats_reset = std::chrono::duration_cast>(now - stats.created).count(); - - c->Message(Chat::White, "Netstats:"); - c->Message(Chat::White, "--------------------------------------------------------------------"); - c->Message(Chat::White, "Sent Bytes: %u (%.2f/sec)", stats.sent_bytes, stats.sent_bytes / sec_since_stats_reset); - c->Message(Chat::White, "Recv Bytes: %u (%.2f/sec)", stats.recv_bytes, stats.recv_bytes / sec_since_stats_reset); - c->Message(Chat::White, "Bytes Before Encode (Sent): %u, Compression Rate: %.2f%%", stats.bytes_before_encode, - static_cast(stats.bytes_before_encode - stats.sent_bytes) / static_cast(stats.bytes_before_encode) * 100.0); - c->Message(Chat::White, "Bytes After Decode (Recv): %u, Compression Rate: %.2f%%", stats.bytes_after_decode, - static_cast(stats.bytes_after_decode - stats.recv_bytes) / static_cast(stats.bytes_after_decode) * 100.0); - c->Message(Chat::White, "Min Ping: %u", stats.min_ping); - c->Message(Chat::White, "Max Ping: %u", stats.max_ping); - c->Message(Chat::White, "Last Ping: %u", stats.last_ping); - c->Message(Chat::White, "Average Ping: %u", stats.avg_ping); - c->Message(Chat::White, "--------------------------------------------------------------------"); - c->Message(Chat::White, "(Realtime) Recv Packets: %u (%.2f/sec)", stats.recv_packets, stats.recv_packets / sec_since_stats_reset); - c->Message(Chat::White, "(Realtime) Sent Packets: %u (%.2f/sec)", stats.sent_packets, stats.sent_packets / sec_since_stats_reset); - c->Message(Chat::White, "(Sync) Recv Packets: %u", stats.sync_recv_packets); - c->Message(Chat::White, "(Sync) Sent Packets: %u", stats.sync_sent_packets); - c->Message(Chat::White, "(Sync) Remote Recv Packets: %u", stats.sync_remote_recv_packets); - c->Message(Chat::White, "(Sync) Remote Sent Packets: %u", stats.sync_remote_sent_packets); - c->Message(Chat::White, "Packet Loss In: %.2f%%", 100.0 * (1.0 - static_cast(stats.sync_recv_packets) / static_cast(stats.sync_remote_sent_packets))); - c->Message(Chat::White, "Packet Loss Out: %.2f%%", 100.0 * (1.0 - static_cast(stats.sync_remote_recv_packets) / static_cast(stats.sync_sent_packets))); - c->Message(Chat::White, "--------------------------------------------------------------------"); - c->Message(Chat::White, "Resent Packets: %u (%.2f/sec)", stats.resent_packets, stats.resent_packets / sec_since_stats_reset); - c->Message(Chat::White, "Resent Fragments: %u (%.2f/sec)", stats.resent_fragments, stats.resent_fragments / sec_since_stats_reset); - c->Message(Chat::White, "Resent Non-Fragments: %u (%.2f/sec)", stats.resent_full, stats.resent_full / sec_since_stats_reset); - c->Message(Chat::White, "Dropped Datarate Packets: %u (%.2f/sec)", stats.dropped_datarate_packets, stats.dropped_datarate_packets / sec_since_stats_reset); - - if (opts.daybreak_options.outgoing_data_rate > 0.0) { - c->Message(Chat::White, "Outgoing Link Saturation %.2f%% (%.2fkb/sec)", 100.0 * (1.0 - ((opts.daybreak_options.outgoing_data_rate - stats.datarate_remaining) / opts.daybreak_options.outgoing_data_rate)), opts.daybreak_options.outgoing_data_rate); - } - - if (strcasecmp(sep->arg[1], "full") == 0) { - c->Message(Chat::White, "--------------------------------------------------------------------"); - c->Message(Chat::White, "Sent Packet Types"); - for (auto i = 0; i < _maxEmuOpcode; ++i) { - auto cnt = eqs_stats.SentCount[i]; - if (cnt > 0) { - c->Message(Chat::White, "%s: %u (%.2f / sec)", OpcodeNames[i], cnt, cnt / sec_since_stats_reset); - } - } - - c->Message(Chat::White, "--------------------------------------------------------------------"); - c->Message(Chat::White, "Recv Packet Types"); - for (auto i = 0; i < _maxEmuOpcode; ++i) { - auto cnt = eqs_stats.RecvCount[i]; - if (cnt > 0) { - c->Message(Chat::White, "%s: %u (%.2f / sec)", OpcodeNames[i], cnt, cnt / sec_since_stats_reset); - } - } - } - - c->Message(Chat::White, "--------------------------------------------------------------------"); - } -} - -void command_object(Client *c, const Seperator *sep) -{ - if (!c) - return; // Crash Suppressant: No client. How did we get here? - - // Save it here. We sometimes have need to refer to it in multiple places. - const char *usage_string = "Usage: #object List|Add|Edit|Move|Rotate|Save|Copy|Delete|Undo"; - - if ((!sep) || (sep->argnum == 0)) { - c->Message(Chat::White, usage_string); - return; - } - - Object *o = nullptr; - Object_Struct od; - Door door; - Doors *doors; - Door_Struct *ds; - uint32 id = 0; - uint32 itemid = 0; - uint32 icon = 0; - uint32 instance = 0; - uint32 newid = 0; - uint16 radius; - EQApplicationPacket *app; - - bool bNewObject = false; - - float x2; - float y2; - - // Temporary object type for static objects to allow manipulation - // NOTE: Zone::LoadZoneObjects() currently loads this as an uint8, so max value is 255! - static const uint32 staticType = 255; - - // Case insensitive commands (List == list == LIST) - strlwr(sep->arg[1]); - - if (strcasecmp(sep->arg[1], "list") == 0) { - // Insufficient or invalid args - if ((sep->argnum < 2) || (sep->arg[2][0] < '0') || - ((sep->arg[2][0] > '9') && ((sep->arg[2][0] & 0xDF) != 'A'))) { - c->Message(Chat::White, "Usage: #object List All|(radius)"); - return; - } - - if ((sep->arg[2][0] & 0xDF) == 'A') - radius = 0; // List All - else if ((radius = atoi(sep->arg[2])) <= 0) - radius = 500; // Invalid radius. Default to 500 units. - - if (radius == 0) - c->Message(Chat::White, "Objects within this zone:"); - else - c->Message(Chat::White, "Objects within %u units of your current location:", radius); - - std::string query; - if (radius) - query = StringFormat( - "SELECT id, xpos, ypos, zpos, heading, itemid, " - "objectname, type, icon, unknown08, unknown10, unknown20 " - "FROM object WHERE zoneid = %u AND version = %u " - "AND (xpos BETWEEN %.1f AND %.1f) " - "AND (ypos BETWEEN %.1f AND %.1f) " - "AND (zpos BETWEEN %.1f AND %.1f) " - "ORDER BY id", - zone->GetZoneID(), zone->GetInstanceVersion(), - c->GetX() - radius, // Yes, we're actually using a bounding box instead of a radius. - c->GetX() + radius, // Much less processing power used this way. - c->GetY() - radius, c->GetY() + radius, c->GetZ() - radius, c->GetZ() + radius); - else - query = StringFormat("SELECT id, xpos, ypos, zpos, heading, itemid, " - "objectname, type, icon, unknown08, unknown10, unknown20 " - "FROM object WHERE zoneid = %u AND version = %u " - "ORDER BY id", - zone->GetZoneID(), zone->GetInstanceVersion()); - - auto results = content_db.QueryDatabase(query); - if (!results.Success()) { - c->Message(Chat::White, "Error in objects query"); - return; - } - - for (auto row = results.begin(); row != results.end(); ++row) { - id = atoi(row[0]); - od.x = atof(row[1]); - od.y = atof(row[2]); - od.z = atof(row[3]); - od.heading = atof(row[4]); - itemid = atoi(row[5]); - strn0cpy(od.object_name, row[6], sizeof(od.object_name)); - od.object_name[sizeof(od.object_name) - 1] = - '\0'; // Required if strlen(row[col++]) exactly == sizeof(object_name) - - od.object_type = atoi(row[7]); - icon = atoi(row[8]); - od.size = atoi(row[9]); - od.solidtype = atoi(row[10]); - od.unknown020 = atoi(row[11]); - - switch (od.object_type) { - case 0: // Static Object - case staticType: // Static Object unlocked for changes - if (od.size == 0) // Unknown08 field is optional Size parameter for static objects - od.size = 100; // Static object default Size is 100% - - c->Message(Chat::White, "- STATIC Object (%s): id %u, x %.1f, y %.1f, z %.1f, h %.1f, model %s, " - "size %u, solidtype %u, incline %u", - (od.object_type == 0) ? "locked" : "unlocked", id, od.x, od.y, od.z, - od.heading, od.object_name, od.size, od.solidtype, od.unknown020); - break; - - case OT_DROPPEDITEM: // Ground Spawn - c->Message(Chat::White, "- TEMPORARY Object: id %u, x %.1f, y %.1f, z %.1f, h %.1f, itemid %u, " - "model %s, icon %u", - id, od.x, od.y, od.z, od.heading, itemid, od.object_name, icon); - break; - - default: // All others == Tradeskill Objects - c->Message(Chat::White, "- TRADESKILL Object: id %u, x %.1f, y %.1f, z %.1f, h %.1f, model %s, " - "type %u, icon %u", - id, od.x, od.y, od.z, od.heading, od.object_name, od.object_type, icon); - break; - } - } - - c->Message(Chat::White, "%u object%s found", results.RowCount(), (results.RowCount() == 1) ? "" : "s"); - return; - } - - if (strcasecmp(sep->arg[1], "add") == 0) { - // Insufficient or invalid arguments - if ((sep->argnum < 3) || - ((sep->arg[3][0] == '\0') && (sep->arg[4][0] < '0') && (sep->arg[4][0] > '9'))) { - c->Message(Chat::White, "Usage: (Static Object): #object Add [ObjectID] 0 Model [SizePercent] " - "[SolidType] [Incline]"); - c->Message(Chat::White, "Usage: (Tradeskill Object): #object Add [ObjectID] TypeNum Model Icon"); - c->Message(Chat::White, "- Notes: Model must start with a letter, max length 16. SolidTypes = 0 (Solid), " - "1 (Sometimes Non-Solid)"); - return; - } - - int col; - - if (sep->argnum > 3) { // Model name in arg3? - if ((sep->arg[3][0] <= '9') && (sep->arg[3][0] >= '0')) { - // Nope, user must have specified ObjectID. Extract it. - id = atoi(sep->arg[2]); - col = 1; // Bump all other arguments one to the right. Model is in arg4. - } else { - // Yep, arg3 is non-numeric, ObjectID must be omitted and model must be arg3 - id = 0; - col = 0; - } - } else { - // Nope, only 3 args. Object ID must be omitted and arg3 must be model. - id = 0; - col = 0; - } - - memset(&od, 0, sizeof(od)); - - od.object_type = atoi(sep->arg[2 + col]); - - switch (od.object_type) { - case 0: // Static Object - if ((sep->argnum - col) > 3) { - od.size = atoi(sep->arg[4 + col]); // Size specified - - if ((sep->argnum - col) > 4) { - od.solidtype = atoi(sep->arg[5 + col]); // SolidType specified - - if ((sep->argnum - col) > 5) - od.unknown020 = atoi(sep->arg[6 + col]); // Incline specified - } - } - break; - - case 1: // Ground Spawn - c->Message(Chat::White, "ERROR: Object Type 1 is used for temporarily spawned ground spawns and dropped " - "items, which are not supported with #object. See the 'ground_spawns' table in " - "the database."); - return; - - default: // Everything else == Tradeskill Object - icon = ((sep->argnum - col) > 3) ? atoi(sep->arg[4 + col]) : 0; - - if (icon == 0) { - c->Message(Chat::White, "ERROR: Required property 'Icon' not specified for Tradeskill Object"); - return; - } - - break; - } - - od.x = c->GetX(); - od.y = c->GetY(); - od.z = c->GetZ() - (c->GetSize() * 0.625f); - od.heading = c->GetHeading(); - - std::string query; - if (id) { - // ID specified. Verify that it doesn't already exist. - query = StringFormat("SELECT COUNT(*) FROM object WHERE ID = %u", id); - auto results = content_db.QueryDatabase(query); - if (results.Success() && results.RowCount() != 0) { - auto row = results.begin(); - if (atoi(row[0]) > 0) // Yep, in database already. - id = 0; - } - - // Not in database. Already spawned, just not saved? - // Yep, already spawned. - if (id && entity_list.FindObject(id)) - id = 0; - - if (id == 0) { - c->Message(Chat::White, "ERROR: An object already exists with the id %u", atoi(sep->arg[2])); - return; - } - } - - int objectsFound = 0; - // Verify no other objects already in this spot (accidental double-click of Hotkey?) - query = StringFormat( - "SELECT COUNT(*) FROM object WHERE zoneid = %u " - "AND version=%u AND (xpos BETWEEN %.1f AND %.1f) " - "AND (ypos BETWEEN %.1f AND %.1f) " - "AND (zpos BETWEEN %.1f AND %.1f)", - zone->GetZoneID(), zone->GetInstanceVersion(), od.x - 0.2f, - od.x + 0.2f, // Yes, we're actually using a bounding box instead of a radius. - od.y - 0.2f, od.y + 0.2f, // Much less processing power used this way. - od.z - 0.2f, od.z + 0.2f); // It's pretty forgiving, though, allowing for close-proximity objects - - auto results = content_db.QueryDatabase(query); - if (results.Success() && results.RowCount() != 0) { - auto row = results.begin(); - objectsFound = atoi(row[0]); // Number of nearby objects from database - } - - // No objects found in database too close. How about spawned but not yet saved? - if (objectsFound == 0 && entity_list.FindNearbyObject(od.x, od.y, od.z, 0.2f)) - objectsFound = 1; - - if (objectsFound) { - c->Message(Chat::White, "ERROR: Object already at this location."); - return; - } - - // Strip any single quotes from objectname (SQL injection FTL!) - strn0cpy(od.object_name, sep->arg[3 + col], sizeof(od.object_name)); - - uint32 len = strlen(od.object_name); - for (col = 0; col < (uint32)len; col++) { - if (od.object_name[col] != '\'') - continue; - - // Uh oh, 1337 h4x0r monkeying around! Strip that apostrophe! - memcpy(&od.object_name[col], &od.object_name[col + 1], len - col); - len--; - col--; - } - - strupr(od.object_name); // Model names are always upper-case. - - if ((od.object_name[0] < 'A') || (od.object_name[0] > 'Z')) { - c->Message(Chat::White, "ERROR: Model name must start with a letter."); - return; - } - - if (id == 0) { - // No ID specified. Get a best-guess next number from the database - // If there's a problem retrieving an ID from the database, it'll end up being object # 1. No - // biggie. - - query = "SELECT MAX(id) FROM object"; - results = content_db.QueryDatabase(query); - if (results.Success() && results.RowCount() != 0) { - auto row = results.begin(); - id = atoi(row[0]); - } - - id++; - } - - // Make sure not to overwrite already-spawned objects that haven't been saved yet. - while (o = entity_list.FindObject(id)) - id++; - - // Static object - if (od.object_type == 0) - od.object_type = staticType; // Temporary. We'll make it 0 when we Save - - od.zone_id = zone->GetZoneID(); - od.zone_instance = zone->GetInstanceVersion(); - - o = new Object(id, od.object_type, icon, od, nullptr); - - // Add to our zone entity list and spawn immediately for all clients - entity_list.AddObject(o, true); - - // Bump player back to avoid getting stuck inside new object - - x2 = 10.0f * sin(c->GetHeading() / 256.0f * 3.14159265f); - y2 = 10.0f * cos(c->GetHeading() / 256.0f * 3.14159265f); - c->MovePC(c->GetX() - x2, c->GetY() - y2, c->GetZ(), c->GetHeading()); - - c->Message(Chat::White, "Spawning object with tentative id %u at location (%.1f, %.1f, %.1f heading %.1f). Use " - "'#object Save' to save to database when satisfied with placement.", - id, od.x, od.y, od.z, od.heading); - - // Temporary Static Object - if (od.object_type == staticType) - c->Message(Chat::White, "- Note: Static Object will act like a tradeskill container and will not reflect " - "size, solidtype, or incline values until you commit with '#object Save', after " - "which it will be unchangeable until you use '#object Edit' and zone back in."); - - return; - } - - if (strcasecmp(sep->arg[1], "edit") == 0) { - - if ((sep->argnum < 2) || ((id = atoi(sep->arg[2])) < 1)) { - c->Message(Chat::White, "Usage: #object Edit (ObjectID) [PropertyName] [NewValue]"); - c->Message(Chat::White, "- Static Object (Type 0) Properties: model, type, size, solidtype, incline"); - c->Message(Chat::White, "- Tradeskill Object (Type 2+) Properties: model, type, icon"); - - return; - } - - o = entity_list.FindObject(id); - - // Object already available in-zone? - if (o) { - // Yep, looks like we can make real-time changes. - if (sep->argnum < 4) { - // Or not. '#object Edit (ObjectID)' called without PropertyName and NewValue - c->Message(Chat::White, "Note: Object %u already unlocked and ready for changes", id); - return; - } - } else { - // Object not found in-zone in a modifiable form. Check for valid matching circumstances. - std::string query = StringFormat("SELECT zoneid, version, type FROM object WHERE id = %u", id); - auto results = content_db.QueryDatabase(query); - if (!results.Success() || results.RowCount() == 0) { - c->Message(Chat::White, "ERROR: Object %u not found", id); - return; - } - - auto row = results.begin(); - od.zone_id = atoi(row[0]); - od.zone_instance = atoi(row[1]); - od.object_type = atoi(row[2]); - uint32 objectsFound = 1; - - // Object not in this zone? - if (od.zone_id != zone->GetZoneID()) { - c->Message(Chat::White, "ERROR: Object %u not in this zone.", id); - return; - } - - // Object not in this instance? - if (od.zone_instance != zone->GetInstanceVersion()) { - c->Message(Chat::White, "ERROR: Object %u not part of this instance version.", id); - return; - } - - switch (od.object_type) { - case 0: // Static object needing unlocking - // Convert to tradeskill object temporarily for changes - query = StringFormat("UPDATE object SET type = %u WHERE id = %u", staticType, id); - - content_db.QueryDatabase(query); - - c->Message(Chat::White, "Static Object %u unlocked for editing. You must zone out and back in to " - "make your changes, then commit them with '#object Save'.", - id); - if (sep->argnum >= 4) - c->Message(Chat::White, "NOTE: The change you specified has not been applied, since the " - "static object had not been unlocked for editing yet."); - return; - - case OT_DROPPEDITEM: - c->Message(Chat::White, "ERROR: Object %u is a temporarily spawned ground spawn or dropped item, " - "which cannot be manipulated with #object. See the 'ground_spawns' table " - "in the database.", - id); - return; - - case staticType: - c->Message(Chat::White, "ERROR: Object %u has been unlocked for editing, but you must zone out " - "and back in for your client to refresh its object table before you can " - "make changes to it.", - id); - return; - - default: - // Unknown error preventing us from seeing the object in the zone. - c->Message(Chat::White, "ERROR: Unknown problem attempting to manipulate object %u", id); - return; - } - } - - // If we're here, we have a manipulable object ready for changes. - strlwr(sep->arg[3]); // Case insensitive PropertyName - strupr(sep->arg[4]); // In case it's model name, which should always be upper-case - - // Read current object info for reference - icon = o->GetIcon(); - o->GetObjectData(&od); - - // We'll be a little more picky with property names, to prevent errors. Check against the whole word. - if (strcmp(sep->arg[3], "model") == 0) { - - if ((sep->arg[4][0] < 'A') || (sep->arg[4][0] > 'Z')) { - c->Message(Chat::White, "ERROR: Model names must begin with a letter."); - return; - } - - strn0cpy(od.object_name, sep->arg[4], sizeof(od.object_name)); - - o->SetObjectData(&od); - - c->Message(Chat::White, "Object %u now being rendered with model '%s'", id, od.object_name); - } else if (strcmp(sep->arg[3], "type") == 0) { - if ((sep->arg[4][0] < '0') || (sep->arg[4][0] > '9')) { - c->Message(Chat::White, "ERROR: Invalid type number"); - return; - } - - od.object_type = atoi(sep->arg[4]); - - switch (od.object_type) { - case 0: - // Convert Static Object to temporary changeable type - od.object_type = staticType; - c->Message(Chat::White, "Note: Static Object will still act like tradeskill object and will not " - "reflect size, solidtype, or incline settings until committed to the " - "database with '#object Save', after which it will be unchangeable until " - "it is unlocked again with '#object Edit'."); - break; - - case OT_DROPPEDITEM: - c->Message(Chat::White, "ERROR: Object Type 1 is used for temporarily spawned ground spawns and " - "dropped items, which are not supported with #object. See the " - "'ground_spawns' table in the database."); - return; - - default: - c->Message(Chat::White, "Object %u changed to Tradeskill Object Type %u", id, od.object_type); - break; - } - - o->SetType(od.object_type); - } else if (strcmp(sep->arg[3], "size") == 0) { - if (od.object_type != staticType) { - c->Message( - 0, "ERROR: Object %u is not a Static Object and does not support the Size property", - id); - return; - } - - if ((sep->arg[4][0] < '0') || (sep->arg[4][0] > '9')) { - c->Message(Chat::White, "ERROR: Invalid size specified. Please enter a number."); - return; - } - - od.size = atoi(sep->arg[4]); - o->SetObjectData(&od); - - if (od.size == 0) // 0 == unspecified == 100% - od.size = 100; - - c->Message(Chat::White, "Static Object %u set to %u%% size. Size will take effect when you commit to the " - "database with '#object Save', after which the object will be unchangeable until " - "you unlock it again with '#object Edit' and zone out and back in.", - id, od.size); - } else if (strcmp(sep->arg[3], "solidtype") == 0) { - - if (od.object_type != staticType) { - c->Message(Chat::White, "ERROR: Object %u is not a Static Object and does not support the " - "SolidType property", - id); - return; - } - - if ((sep->arg[4][0] < '0') || (sep->arg[4][0] > '9')) { - c->Message(Chat::White, "ERROR: Invalid solidtype specified. Please enter a number."); - return; - } - - od.solidtype = atoi(sep->arg[4]); - o->SetObjectData(&od); - - c->Message(Chat::White, "Static Object %u set to SolidType %u. Change will take effect when you commit " - "to the database with '#object Save'. Support for this property is on a " - "per-model basis, mostly seen in smaller objects such as chests and tables.", - id, od.solidtype); - } else if (strcmp(sep->arg[3], "icon") == 0) { - - if ((od.object_type < 2) || (od.object_type == staticType)) { - c->Message(Chat::White, "ERROR: Object %u is not a Tradeskill Object and does not support the " - "Icon property", - id); - return; - } - - if ((icon = atoi(sep->arg[4])) == 0) { - c->Message(Chat::White, "ERROR: Invalid Icon specified. Please enter an icon number."); - return; - } - - o->SetIcon(icon); - c->Message(Chat::White, "Tradeskill Object %u icon set to %u", id, icon); - } else if (strcmp(sep->arg[3], "incline") == 0) { - if (od.object_type != staticType) { - c->Message( - 0, - "ERROR: Object %u is not a Static Object and does not support the Incline property", - id); - return; - } - - if ((sep->arg[4][0] < '0') || (sep->arg[4][0] > '9')) { - c->Message( - 0, - "ERROR: Invalid Incline specified. Please enter a number. Normal range is 0-512."); - return; - } - - od.unknown020 = atoi(sep->arg[4]); - o->SetObjectData(&od); - - c->Message(Chat::White, "Static Object %u set to %u incline. Incline will take effect when you commit to " - "the database with '#object Save', after which the object will be unchangeable " - "until you unlock it again with '#object Edit' and zone out and back in.", - id, od.unknown020); - } else { - c->Message(Chat::White, "ERROR: Unrecognized property name: %s", sep->arg[3]); - return; - } - - // Repop object to have it reflect the change. - app = new EQApplicationPacket(); - o->CreateDeSpawnPacket(app); - entity_list.QueueClients(0, app); - safe_delete(app); - - app = new EQApplicationPacket(); - o->CreateSpawnPacket(app); - entity_list.QueueClients(0, app); - safe_delete(app); - return; - } - - if (strcasecmp(sep->arg[1], "move") == 0) { - - if ((sep->argnum < 2) || // Not enough arguments - ((id = atoi(sep->arg[2])) == 0) || // ID not specified - (((sep->arg[3][0] < '0') || (sep->arg[3][0] > '9')) && ((sep->arg[3][0] & 0xDF) != 'T') && - (sep->arg[3][0] != '-') && (sep->arg[3][0] != '.'))) { // Location argument not specified correctly - c->Message(Chat::White, "Usage: #object Move (ObjectID) ToMe|(x y z [h])"); - return; - } - - if (!(o = entity_list.FindObject(id))) { - std::string query = StringFormat("SELECT zoneid, version, type FROM object WHERE id = %u", id); - auto results = content_db.QueryDatabase(query); - if (!results.Success() || results.RowCount() == 0) { - c->Message(Chat::White, "ERROR: Object %u not found", id); - return; - } - - auto row = results.begin(); - od.zone_id = atoi(row[0]); - od.zone_instance = atoi(row[1]); - od.object_type = atoi(row[2]); - - if (od.zone_id != zone->GetZoneID()) { - c->Message(Chat::White, "ERROR: Object %u is not in this zone", id); - return; - } - - if (od.zone_instance != zone->GetInstanceVersion()) { - c->Message(Chat::White, "ERROR: Object %u is not in this instance version", id); - return; - } - - switch (od.object_type) { - case 0: - c->Message(Chat::White, "ERROR: Object %u is not yet unlocked for editing. Use '#object Edit' " - "then zone out and back in to move it.", - id); - return; - - case staticType: - c->Message(Chat::White, "ERROR: Object %u has been unlocked for editing, but you must zone out " - "and back in before your client sees the change and will allow you to " - "move it.", - id); - return; - - case 1: - c->Message(Chat::White, "ERROR: Object %u is a temporary spawned object and cannot be " - "manipulated with #object. See the 'ground_spawns' table in the " - "database.", - id); - return; - - default: - c->Message(Chat::White, "ERROR: Object %u not located in zone.", id); - return; - } - } - - // Move To Me - if ((sep->arg[3][0] & 0xDF) == 'T') { - od.x = c->GetX(); - od.y = c->GetY(); - od.z = c->GetZ() - - (c->GetSize() * - 0.625f); // Compensate for #loc bumping up Z coordinate by 62.5% of character's size. - - o->SetHeading(c->GetHeading()); - - // Bump player back to avoid getting stuck inside object - - x2 = 10.0f * std::sin(c->GetHeading() / 256.0f * 3.14159265f); - y2 = 10.0f * std::cos(c->GetHeading() / 256.0f * 3.14159265f); - c->MovePC(c->GetX() - x2, c->GetY() - y2, c->GetZ(), c->GetHeading()); - } // Move to x, y, z [h] - else { - od.x = atof(sep->arg[3]); - if (sep->argnum > 3) - od.y = atof(sep->arg[4]); - else - o->GetLocation(nullptr, &od.y, nullptr); - - if (sep->argnum > 4) - od.z = atof(sep->arg[5]); - else - o->GetLocation(nullptr, nullptr, &od.z); - - if (sep->argnum > 5) - o->SetHeading(atof(sep->arg[6])); - } - - o->SetLocation(od.x, od.y, od.z); - - // Despawn and respawn object to reflect change - app = new EQApplicationPacket(); - o->CreateDeSpawnPacket(app); - entity_list.QueueClients(0, app); - safe_delete(app); - - app = new EQApplicationPacket(); - o->CreateSpawnPacket(app); - entity_list.QueueClients(0, app); - safe_delete(app); - return; - } - - if (strcasecmp(sep->arg[1], "rotate") == 0) { - // Insufficient or invalid arguments - if ((sep->argnum < 3) || ((id = atoi(sep->arg[2])) == 0)) { - c->Message(Chat::White, "Usage: #object Rotate (ObjectID) (Heading, 0-512)"); - return; - } - - if ((o = entity_list.FindObject(id)) == nullptr) { - c->Message(Chat::White, "ERROR: Object %u not found in zone, or is a static object not yet unlocked with " - "'#object Edit' for editing.", - id); - return; - } - - o->SetHeading(atof(sep->arg[3])); - - // Despawn and respawn object to reflect change - app = new EQApplicationPacket(); - o->CreateDeSpawnPacket(app); - entity_list.QueueClients(0, app); - safe_delete(app); - - app = new EQApplicationPacket(); - o->CreateSpawnPacket(app); - entity_list.QueueClients(0, app); - safe_delete(app); - return; - } - - if (strcasecmp(sep->arg[1], "save") == 0) { - // Insufficient or invalid arguments - if ((sep->argnum < 2) || ((id = atoi(sep->arg[2])) == 0)) { - c->Message(Chat::White, "Usage: #object Save (ObjectID)"); - return; - } - - o = entity_list.FindObject(id); - - od.zone_id = 0; - od.zone_instance = 0; - od.object_type = 0; - - // If this ID isn't in the database yet, it's a new object - bNewObject = true; - std::string query = StringFormat("SELECT zoneid, version, type FROM object WHERE id = %u", id); - auto results = content_db.QueryDatabase(query); - if (results.Success() && results.RowCount() != 0) { - auto row = results.begin(); - od.zone_id = atoi(row[0]); - od.zone_instance = atoi(row[1]); - od.object_type = atoi(row[2]); - - // ID already in database. Not a new object. - bNewObject = false; - } - - if (!o) { - // Object not found in zone. Can't save an object we can't see. - - if (bNewObject) { - c->Message(Chat::White, "ERROR: Object %u not found", id); - return; - } - - if (od.zone_id != zone->GetZoneID()) { - c->Message(Chat::White, "ERROR: Wrong Object ID. %u is not part of this zone.", id); - return; - } - - if (od.zone_instance != zone->GetInstanceVersion()) { - c->Message(Chat::White, "ERROR: Wrong Object ID. %u is not part of this instance version.", id); - return; - } - - if (od.object_type == 0) { - c->Message(Chat::White, "ERROR: Static Object %u has already been committed. Use '#object Edit " - "%u' and zone out and back in to make changes.", - id, id); - return; - } - - if (od.object_type == 1) { - c->Message(Chat::White, "ERROR: Object %u is a temporarily spawned ground spawn or dropped item, " - "which is not supported with #object. See the 'ground_spawns' table in " - "the database.", - id); - return; - } - - c->Message(Chat::White, "ERROR: Object %u not found.", id); - return; - } - - // Oops! Another GM already saved an object with our id from another zone. - // We'll have to get a new one. - if ((od.zone_id > 0) && (od.zone_id != zone->GetZoneID())) - id = 0; - - // Oops! Another GM already saved an object with our id from another instance. - // We'll have to get a new one. - if ((id > 0) && (od.zone_instance != zone->GetInstanceVersion())) - id = 0; - - // If we're asking for a new ID, it's a new object. - bNewObject |= (id == 0); - - o->GetObjectData(&od); - od.object_type = o->GetType(); - icon = o->GetIcon(); - - // We're committing to the database now. Return temporary object type to actual. - if (od.object_type == staticType) - od.object_type = 0; - - if (!bNewObject) - query = StringFormat("UPDATE object SET zoneid = %u, version = %u, " - "xpos = %.1f, ypos=%.1f, zpos=%.1f, heading=%.1f, " - "objectname = '%s', type = %u, icon = %u, " - "unknown08 = %u, unknown10 = %u, unknown20 = %u " - "WHERE ID = %u", - zone->GetZoneID(), zone->GetInstanceVersion(), od.x, od.y, od.z, - od.heading, od.object_name, od.object_type, icon, od.size, - od.solidtype, od.unknown020, id); - else if (id == 0) - query = StringFormat("INSERT INTO object " - "(zoneid, version, xpos, ypos, zpos, heading, objectname, " - "type, icon, unknown08, unknown10, unknown20) " - "VALUES (%u, %u, %.1f, %.1f, %.1f, %.1f, '%s', %u, %u, %u, %u, %u)", - zone->GetZoneID(), zone->GetInstanceVersion(), od.x, od.y, od.z, - od.heading, od.object_name, od.object_type, icon, od.size, - od.solidtype, od.unknown020); - else - query = StringFormat("INSERT INTO object " - "(id, zoneid, version, xpos, ypos, zpos, heading, objectname, " - "type, icon, unknown08, unknown10, unknown20) " - "VALUES (%u, %u, %u, %.1f, %.1f, %.1f, %.1f, '%s', %u, %u, %u, %u, %u)", - id, zone->GetZoneID(), zone->GetInstanceVersion(), od.x, od.y, od.z, - od.heading, od.object_name, od.object_type, icon, od.size, - od.solidtype, od.unknown020); - - results = content_db.QueryDatabase(query); - if (!results.Success()) { - c->Message(Chat::White, "Database Error: %s", results.ErrorMessage().c_str()); - return; - } - - if (results.RowsAffected() == 0) { - // No change made, but no error message given - c->Message(Chat::White, "Database Error: Could not save change to Object %u", id); - return; - } - - if (bNewObject) { - if (newid == results.LastInsertedID()) { - c->Message(Chat::White, "Saved new Object %u to database", id); - return; - } - - c->Message(Chat::White, "Saved Object. NOTE: Database returned a new ID number for object: %u", newid); - id = newid; - return; - } - - c->Message(Chat::White, "Saved changes to Object %u", id); - newid = id; - - if (od.object_type == 0) { - // Static Object - Respawn as nonfunctional door - - app = new EQApplicationPacket(); - o->CreateDeSpawnPacket(app); - entity_list.QueueClients(0, app); - safe_delete(app); - - entity_list.RemoveObject(o->GetID()); - - memset(&door, 0, sizeof(door)); - - strn0cpy(door.zone_name, zone->GetShortName(), sizeof(door.zone_name)); - - door.db_id = 1000000000 + id; // Out of range of normal use for doors.id - door.door_id = -1; // Client doesn't care if these are all the same door_id - door.pos_x = od.x; // xpos - door.pos_y = od.y; // ypos - door.pos_z = od.z; // zpos - door.heading = od.heading; // heading - - strn0cpy(door.door_name, od.object_name, sizeof(door.door_name)); // objectname - - // Strip trailing "_ACTORDEF" if present. Client won't accept it for doors. - uint32 len = strlen(door.door_name); - if ((len > 9) && (memcmp(&door.door_name[len - 9], "_ACTORDEF", 10) == 0)) - door.door_name[len - 9] = '\0'; - - memcpy(door.dest_zone, "NONE", 5); - - if ((door.size = od.size) == 0) // unknown08 = optional size percentage - door.size = 100; - - switch ( - door.opentype = - od.solidtype) // unknown10 = optional request_nonsolid (0 or 1 or experimental number) - { - case 0: - door.opentype = 31; - break; - - case 1: - door.opentype = 9; - break; - } - - door.incline = od.unknown020; // unknown20 = optional incline value - door.client_version_mask = 0xFFFFFFFF; - - doors = new Doors(&door); - entity_list.AddDoor(doors); - - app = new EQApplicationPacket(OP_SpawnDoor, sizeof(Door_Struct)); - ds = (Door_Struct *)app->pBuffer; - - memset(ds, 0, sizeof(Door_Struct)); - memcpy(ds->name, door.door_name, 32); - ds->xPos = door.pos_x; - ds->yPos = door.pos_y; - ds->zPos = door.pos_z; - ds->heading = door.heading; - ds->incline = door.incline; - ds->size = door.size; - ds->doorId = door.door_id; - ds->opentype = door.opentype; - ds->unknown0052[9] = 1; // *ptr-1 and *ptr-3 from EntityList::MakeDoorSpawnPacket() - ds->unknown0052[11] = 1; - - entity_list.QueueClients(0, app); - safe_delete(app); - - c->Message(Chat::White, "NOTE: Object %u is now a static object, and is unchangeable. To make future " - "changes, use '#object Edit' to convert it to a changeable form, then zone out " - "and back in.", - id); - } - return; - } - - if (strcasecmp(sep->arg[1], "copy") == 0) { - // Insufficient or invalid arguments - if ((sep->argnum < 3) || - (((sep->arg[2][0] & 0xDF) != 'A') && ((sep->arg[2][0] < '0') || (sep->arg[2][0] > '9')))) { - c->Message(Chat::White, "Usage: #object Copy All|(ObjectID) (InstanceVersion)"); - c->Message(Chat::White, "- Note: Only objects saved in the database can be copied to another instance."); - return; - } - - od.zone_instance = atoi(sep->arg[3]); - - if (od.zone_instance == zone->GetInstanceVersion()) { - c->Message(Chat::White, "ERROR: Source and destination instance versions are the same."); - return; - } - - if ((sep->arg[2][0] & 0xDF) == 'A') { - // Copy All - - std::string query = - StringFormat("INSERT INTO object " - "(zoneid, version, xpos, ypos, zpos, heading, itemid, " - "objectname, type, icon, unknown08, unknown10, unknown20) " - "SELECT zoneid, %u, xpos, ypos, zpos, heading, itemid, " - "objectname, type, icon, unknown08, unknown10, unknown20 " - "FROM object WHERE zoneid = %u) AND version = %u", - od.zone_instance, zone->GetZoneID(), zone->GetInstanceVersion()); - auto results = content_db.QueryDatabase(query); - if (!results.Success()) { - c->Message(Chat::White, "Database Error: %s", results.ErrorMessage().c_str()); - return; - } - - c->Message(Chat::White, "Copied %u object%s into instance version %u", results.RowCount(), - (results.RowCount() == 1) ? "" : "s", od.zone_instance); - return; - } - - id = atoi(sep->arg[2]); - - std::string query = StringFormat("INSERT INTO object " - "(zoneid, version, xpos, ypos, zpos, heading, itemid, " - "objectname, type, icon, unknown08, unknown10, unknown20) " - "SELECT zoneid, %u, xpos, ypos, zpos, heading, itemid, " - "objectname, type, icon, unknown08, unknown10, unknown20 " - "FROM object WHERE id = %u AND zoneid = %u AND version = %u", - od.zone_instance, id, zone->GetZoneID(), zone->GetInstanceVersion()); - auto results = content_db.QueryDatabase(query); - if (results.Success() && results.RowsAffected() > 0) { - c->Message(Chat::White, "Copied Object %u into instance version %u", id, od.zone_instance); - return; - } - - // Couldn't copy the object. - - // got an error message - if (!results.Success()) { - c->Message(Chat::White, "Database Error: %s", results.ErrorMessage().c_str()); - return; - } - - // No database error returned. See if we can figure out why. - - query = StringFormat("SELECT zoneid, version FROM object WHERE id = %u", id); - results = content_db.QueryDatabase(query); - if (!results.Success()) - return; - - if (results.RowCount() == 0) { - c->Message(Chat::White, "ERROR: Object %u not found", id); - return; - } - - auto row = results.begin(); - // Wrong ZoneID? - if (atoi(row[0]) != zone->GetZoneID()) { - c->Message(Chat::White, "ERROR: Object %u is not part of this zone.", id); - return; - } - - // Wrong Instance Version? - if (atoi(row[1]) != zone->GetInstanceVersion()) { - c->Message(Chat::White, "ERROR: Object %u is not part of this instance version.", id); - return; - } - - // Well, NO clue at this point. Just let 'em know something screwed up. - c->Message(Chat::White, "ERROR: Unknown database error copying Object %u to instance version %u", id, - od.zone_instance); - return; - } - - if (strcasecmp(sep->arg[1], "delete") == 0) { - - if ((sep->argnum < 2) || ((id = atoi(sep->arg[2])) <= 0)) { - c->Message(Chat::White, "Usage: #object Delete (ObjectID) -- NOTE: Object deletions are permanent and " - "cannot be undone!"); - return; - } - - o = entity_list.FindObject(id); - - if (o) { - // Object found in zone. - - app = new EQApplicationPacket(); - o->CreateDeSpawnPacket(app); - entity_list.QueueClients(nullptr, app); - - entity_list.RemoveObject(o->GetID()); - - // Verifying ZoneID and Version in case someone else ended up adding an object with our ID - // from a different zone/version. Don't want to delete someone else's work. - std::string query = StringFormat("DELETE FROM object " - "WHERE id = %u AND zoneid = %u " - "AND version = %u LIMIT 1", - id, zone->GetZoneID(), zone->GetInstanceVersion()); - auto results = content_db.QueryDatabase(query); - - c->Message(Chat::White, "Object %u deleted", id); - return; - } - - // Object not found in zone. - std::string query = StringFormat("SELECT type FROM object " - "WHERE id = %u AND zoneid = %u " - "AND version = %u LIMIT 1", - id, zone->GetZoneID(), zone->GetInstanceVersion()); - auto results = content_db.QueryDatabase(query); - if (!results.Success()) - return; - - if (results.RowCount() == 0) { - c->Message(Chat::White, "ERROR: Object %u not found in this zone or instance!", id); - return; - } - - auto row = results.begin(); - - switch (atoi(row[0])) { - case 0: // Static Object - query = StringFormat("DELETE FROM object WHERE id = %u " - "AND zoneid = %u AND version = %u LIMIT 1", - id, zone->GetZoneID(), zone->GetInstanceVersion()); - results = content_db.QueryDatabase(query); - - c->Message(Chat::White, "Object %u deleted. NOTE: This static object will remain for anyone currently in " - "the zone until they next zone out and in.", - id); - return; - - case 1: // Temporary Spawn - c->Message(Chat::White, "ERROR: Object %u is a temporarily spawned ground spawn or dropped item, which " - "is not supported with #object. See the 'ground_spawns' table in the database.", - id); - return; - } - - return; - } - - if (strcasecmp(sep->arg[1], "undo") == 0) { - // Insufficient or invalid arguments - if ((sep->argnum < 2) || ((id = atoi(sep->arg[2])) == 0)) { - c->Message(Chat::White, "Usage: #object Undo (ObjectID) -- Reload object from database, undoing any " - "changes you have made"); - return; - } - - o = entity_list.FindObject(id); - - if (!o) { - c->Message(Chat::White, "ERROR: Object %u not found in zone in a manipulable form. No changes to undo.", - id); - return; - } - - if (o->GetType() == OT_DROPPEDITEM) { - c->Message(Chat::White, "ERROR: Object %u is a temporary spawned item and cannot be manipulated with " - "#object. See the 'ground_spawns' table in the database.", - id); - return; - } - - // Despawn current item for reloading from database - app = new EQApplicationPacket(); - o->CreateDeSpawnPacket(app); - entity_list.QueueClients(0, app); - entity_list.RemoveObject(o->GetID()); - safe_delete(app); - - std::string query = StringFormat("SELECT xpos, ypos, zpos, " - "heading, objectname, type, icon, " - "unknown08, unknown10, unknown20 " - "FROM object WHERE id = %u", - id); - auto results = content_db.QueryDatabase(query); - if (!results.Success() || results.RowCount() == 0) { - c->Message(Chat::White, "Database Error: %s", results.ErrorMessage().c_str()); - return; - } - - memset(&od, 0, sizeof(od)); - - auto row = results.begin(); - - od.x = atof(row[0]); - od.y = atof(row[1]); - od.z = atof(row[2]); - od.heading = atof(row[3]); - strn0cpy(od.object_name, row[4], sizeof(od.object_name)); - od.object_type = atoi(row[5]); - icon = atoi(row[6]); - od.size = atoi(row[7]); - od.solidtype = atoi(row[8]); - od.unknown020 = atoi(row[9]); - - if (od.object_type == 0) - od.object_type = staticType; - - o = new Object(id, od.object_type, icon, od, nullptr); - entity_list.AddObject(o, true); - - c->Message(Chat::White, "Object %u reloaded from database.", id); - return; - } - - c->Message(Chat::White, usage_string); -} - -void command_showspellslist(Client *c, const Seperator *sep) -{ - Mob *target = c->GetTarget(); - - if (!target) { - c->Message(Chat::White, "Must target an NPC."); - return; - } - - if (!target->IsNPC()) { - c->Message(Chat::White, "%s is not an NPC.", target->GetName()); - return; - } - - target->CastToNPC()->AISpellsList(c); - - return; -} - -void command_raidloot(Client *c, const Seperator *sep) -{ - if(!sep->arg[1][0]) { - c->Message(Chat::White, "Usage: #raidloot [LEADER/GROUPLEADER/SELECTED/ALL]"); - return; - } - - Raid *r = c->GetRaid(); - if(r) - { - for(int x = 0; x < 72; ++x) - { - if(r->members[x].member == c) - { - if(r->members[x].IsRaidLeader == 0) - { - c->Message(Chat::White, "You must be the raid leader to use this command."); - } - else - { - break; - } - } - } - - if(strcasecmp(sep->arg[1], "LEADER") == 0) - { - c->Message(Chat::Yellow, "Loot type changed to: 1"); - r->ChangeLootType(1); - } - else if(strcasecmp(sep->arg[1], "GROUPLEADER") == 0) - { - c->Message(Chat::Yellow, "Loot type changed to: 2"); - r->ChangeLootType(2); - } - else if(strcasecmp(sep->arg[1], "SELECTED") == 0) - { - c->Message(Chat::Yellow, "Loot type changed to: 3"); - r->ChangeLootType(3); - } - else if(strcasecmp(sep->arg[1], "ALL") == 0) - { - c->Message(Chat::Yellow, "Loot type changed to: 4"); - r->ChangeLootType(4); - } - else - { - c->Message(Chat::White, "Usage: #raidloot [LEADER/GROUPLEADER/SELECTED/ALL]"); - } - } - else - { - c->Message(Chat::White, "You must be in a raid to use that command."); - } -} - -void command_emoteview(Client *c, const Seperator *sep) -{ - if(!c->GetTarget() || !c->GetTarget()->IsNPC()) - { - c->Message(Chat::White, "You must target a NPC to view their emotes."); - return; - } - - if(c->GetTarget() && c->GetTarget()->IsNPC()) - { - int count=0; - int emoteid = c->GetTarget()->CastToNPC()->GetEmoteID(); - - LinkedListIterator iterator(zone->NPCEmoteList); - iterator.Reset(); - while(iterator.MoreElements()) - { - NPC_Emote_Struct* nes = iterator.GetData(); - if(emoteid == nes->emoteid) - { - c->Message(Chat::White, "EmoteID: %i Event: %i Type: %i Text: %s", nes->emoteid, nes->event_, nes->type, nes->text); - count++; - } - iterator.Advance(); - } - if (count == 0) - c->Message(Chat::White, "No emotes found."); - else - c->Message(Chat::White, "%i emote(s) found", count); - } -} - -void command_emotesearch(Client *c, const Seperator *sep) -{ - if (sep->arg[1][0] == 0) - c->Message(Chat::White, "Usage: #emotesearch [search string or emoteid]"); - else - { - const char *search_criteria=sep->argplus[1]; - int count=0; - - if (Seperator::IsNumber(search_criteria)) - { - uint16 emoteid = atoi(search_criteria); - LinkedListIterator iterator(zone->NPCEmoteList); - iterator.Reset(); - while(iterator.MoreElements()) - { - NPC_Emote_Struct* nes = iterator.GetData(); - if(emoteid == nes->emoteid) - { - c->Message(Chat::White, "EmoteID: %i Event: %i Type: %i Text: %s", nes->emoteid, nes->event_, nes->type, nes->text); - count++; - } - iterator.Advance(); - } - if (count == 0) - c->Message(Chat::White, "No emotes found."); - else - c->Message(Chat::White, "%i emote(s) found", count); - } - else - { - char sText[64]; - char sCriteria[515]; - strn0cpy(sCriteria, search_criteria, sizeof(sCriteria)); - strupr(sCriteria); - char* pdest; - - LinkedListIterator iterator(zone->NPCEmoteList); - iterator.Reset(); - while(iterator.MoreElements()) - { - NPC_Emote_Struct* nes = iterator.GetData(); - strn0cpy(sText, nes->text, sizeof(sText)); - strupr(sText); - pdest = strstr(sText, sCriteria); - if (pdest != nullptr) - { - c->Message(Chat::White, "EmoteID: %i Event: %i Type: %i Text: %s", nes->emoteid, nes->event_, nes->type, nes->text); - count++; - } - if (count == 50) - break; - - iterator.Advance(); - } - if (count == 50) - c->Message(Chat::White, "50 emotes shown...too many results."); - else - c->Message(Chat::White, "%i emote(s) found", count); - } - } -} - -void command_reloademote(Client *c, const Seperator *sep) -{ - zone->NPCEmoteList.Clear(); - zone->LoadNPCEmotes(&zone->NPCEmoteList); - c->Message(Chat::White, "NPC emotes reloaded."); -} - -void command_globalview(Client *c, const Seperator *sep) -{ - NPC * npcmob = nullptr; - - if(c->GetTarget() && c->GetTarget()->IsNPC()) - { - npcmob = c->GetTarget()->CastToNPC(); - QGlobalCache *npc_c = nullptr; - QGlobalCache *char_c = nullptr; - QGlobalCache *zone_c = nullptr; - - if(npcmob) - npc_c = npcmob->GetQGlobals(); - - char_c = c->GetQGlobals(); - zone_c = zone->GetQGlobals(); - - std::list globalMap; - uint32 ntype = 0; - - if(npcmob) - ntype = npcmob->GetNPCTypeID(); - - if(npc_c) - { - QGlobalCache::Combine(globalMap, npc_c->GetBucket(), ntype, c->CharacterID(), zone->GetZoneID()); - } - - if(char_c) - { - QGlobalCache::Combine(globalMap, char_c->GetBucket(), ntype, c->CharacterID(), zone->GetZoneID()); - } - - if(zone_c) - { - QGlobalCache::Combine(globalMap, zone_c->GetBucket(), ntype, c->CharacterID(), zone->GetZoneID()); - } - - auto iter = globalMap.begin(); - uint32 gcount = 0; - - c->Message(Chat::White, "Name, Value"); - while(iter != globalMap.end()) - { - c->Message(Chat::White, "%s %s", (*iter).name.c_str(), (*iter).value.c_str()); - ++iter; - ++gcount; - } - c->Message(Chat::White, "%u globals loaded.", gcount); - } - else - { - QGlobalCache *char_c = nullptr; - QGlobalCache *zone_c = nullptr; - - char_c = c->GetQGlobals(); - zone_c = zone->GetQGlobals(); - - std::list globalMap; - uint32 ntype = 0; - - if(char_c) - { - QGlobalCache::Combine(globalMap, char_c->GetBucket(), ntype, c->CharacterID(), zone->GetZoneID()); - } - - if(zone_c) - { - QGlobalCache::Combine(globalMap, zone_c->GetBucket(), ntype, c->CharacterID(), zone->GetZoneID()); - } - - auto iter = globalMap.begin(); - uint32 gcount = 0; - - c->Message(Chat::White, "Name, Value"); - while(iter != globalMap.end()) - { - c->Message(Chat::White, "%s %s", (*iter).name.c_str(), (*iter).value.c_str()); - ++iter; - ++gcount; - } - c->Message(Chat::White, "%u globals loaded.", gcount); - } -} - -void command_distance(Client *c, const Seperator *sep) { - if(c && c->GetTarget()) { - Mob* target = c->GetTarget(); - - c->Message(Chat::White, "Your target, %s, is %1.1f units from you.", c->GetTarget()->GetName(), Distance(c->GetPosition(), target->GetPosition())); - } -} - -void command_cvs(Client *c, const Seperator *sep) -{ - if(c) - { - auto pack = - new ServerPacket(ServerOP_ClientVersionSummary, sizeof(ServerRequestClientVersionSummary_Struct)); - - ServerRequestClientVersionSummary_Struct *srcvss = (ServerRequestClientVersionSummary_Struct*)pack->pBuffer; - - strn0cpy(srcvss->Name, c->GetName(), sizeof(srcvss->Name)); - - worldserver.SendPacket(pack); - - safe_delete(pack); - - } -} - -void command_max_all_skills(Client *c, const Seperator *sep) -{ - if(c) - { - for (int i = 0; i <= EQ::skills::HIGHEST_SKILL; ++i) - { - if (i >= EQ::skills::SkillSpecializeAbjure && i <= EQ::skills::SkillSpecializeEvocation) - { - c->SetSkill((EQ::skills::SkillType)i, 50); - } - else - { - int max_skill_level = content_db.GetSkillCap(c->GetClass(), (EQ::skills::SkillType)i, c->GetLevel()); - c->SetSkill((EQ::skills::SkillType)i, max_skill_level); - } - } - } -} - -void command_showbonusstats(Client *c, const Seperator *sep) -{ - if (c->GetTarget() == 0) - c->Message(Chat::White, "ERROR: No target!"); - else if (!c->GetTarget()->IsMob() && !c->GetTarget()->IsClient()) - c->Message(Chat::White, "ERROR: Target is not a Mob or Player!"); - else { - bool bAll = false; - if(sep->arg[1][0] == '\0' || strcasecmp(sep->arg[1], "all") == 0) - bAll = true; - if (bAll || (strcasecmp(sep->arg[1], "item")==0)) { - c->Message(Chat::White, "Target Item Bonuses:"); - c->Message(Chat::White, " Accuracy: %i%% Divine Save: %i%%", c->GetTarget()->GetItemBonuses().Accuracy, c->GetTarget()->GetItemBonuses().DivineSaveChance); - c->Message(Chat::White, " Flurry: %i%% HitChance: %i%%", c->GetTarget()->GetItemBonuses().FlurryChance, c->GetTarget()->GetItemBonuses().HitChance / 15); - } - if (bAll || (strcasecmp(sep->arg[1], "spell")==0)) { - c->Message(Chat::White, " Target Spell Bonuses:"); - c->Message(Chat::White, " Accuracy: %i%% Divine Save: %i%%", c->GetTarget()->GetSpellBonuses().Accuracy, c->GetTarget()->GetSpellBonuses().DivineSaveChance); - c->Message(Chat::White, " Flurry: %i%% HitChance: %i%% ", c->GetTarget()->GetSpellBonuses().FlurryChance, c->GetTarget()->GetSpellBonuses().HitChance / 15); - } - c->Message(Chat::White, " Effective Casting Level: %i", c->GetTarget()->GetCasterLevel(0)); - } -} - -void command_reloadallrules(Client *c, const Seperator *sep) -{ - if(c) - { - auto pack = new ServerPacket(ServerOP_ReloadRules, 0); - worldserver.SendPacket(pack); - c->Message(Chat::Red, "Successfully sent the packet to world to reload rules globally. (including world)"); - safe_delete(pack); - - } -} - -void command_reloadworldrules(Client *c, const Seperator *sep) -{ - if(c) - { - auto pack = new ServerPacket(ServerOP_ReloadRulesWorld, 0); - worldserver.SendPacket(pack); - c->Message(Chat::Red, "Successfully sent the packet to world to reload rules. (only world)"); - safe_delete(pack); - } -} - -void command_camerashake(Client *c, const Seperator *sep) -{ - if(c) - { - if(sep->arg[1][0] && sep->arg[2][0]) - { - auto pack = new ServerPacket(ServerOP_CameraShake, sizeof(ServerCameraShake_Struct)); - ServerCameraShake_Struct* scss = (ServerCameraShake_Struct*) pack->pBuffer; - scss->duration = atoi(sep->arg[1]); - scss->intensity = atoi(sep->arg[2]); - worldserver.SendPacket(pack); - c->Message(Chat::Red, "Successfully sent the packet to world! Shake it, world, shake it!"); - safe_delete(pack); - } - else { - c->Message(Chat::Red, "Usage -- #camerashake [duration], [intensity [1-10])"); - } - } - return; -} - -void command_disarmtrap(Client *c, const Seperator *sep) -{ - Mob *target = c->GetTarget(); - - if(!target) - { - c->Message(Chat::Red, "You must have a target."); - return; - } - - if(target->IsNPC()) - { - if (c->HasSkill(EQ::skills::SkillDisarmTraps)) - { - if(DistanceSquaredNoZ(c->GetPosition(), target->GetPosition()) > RuleI(Adventure, LDoNTrapDistanceUse)) - { - c->Message(Chat::Red, "%s is too far away.", target->GetCleanName()); - return; - } - c->HandleLDoNDisarm(target->CastToNPC(), c->GetSkill(EQ::skills::SkillDisarmTraps), LDoNTypeMechanical); - } - else - c->Message(Chat::Red, "You do not have the disarm trap skill."); - } -} - -void command_sensetrap(Client *c, const Seperator *sep) -{ - Mob * target = c->GetTarget(); - if(!target) - { - c->Message(Chat::Red, "You must have a target."); - return; - } - - if(target->IsNPC()) - { - if (c->HasSkill(EQ::skills::SkillSenseTraps)) - { - if(DistanceSquaredNoZ(c->GetPosition(), target->GetPosition()) > RuleI(Adventure, LDoNTrapDistanceUse)) - { - c->Message(Chat::Red, "%s is too far away.", target->GetCleanName()); - return; - } - c->HandleLDoNSenseTraps(target->CastToNPC(), c->GetSkill(EQ::skills::SkillSenseTraps), LDoNTypeMechanical); - } - else - c->Message(Chat::Red, "You do not have the sense traps skill."); - } -} - -void command_picklock(Client *c, const Seperator *sep) -{ - Mob * target = c->GetTarget(); - if(!target) - { - c->Message(Chat::Red, "You must have a target."); - return; - } - - if(target->IsNPC()) - { - if (c->HasSkill(EQ::skills::SkillPickLock)) - { - if(DistanceSquaredNoZ(c->GetPosition(), target->GetPosition()) > RuleI(Adventure, LDoNTrapDistanceUse)) - { - c->Message(Chat::Red, "%s is too far away.", target->GetCleanName()); - return; - } - c->HandleLDoNPickLock(target->CastToNPC(), c->GetSkill(EQ::skills::SkillPickLock), LDoNTypeMechanical); - } - else - c->Message(Chat::Red, "You do not have the pick locks skill."); - } -} - -void command_profanity(Client *c, const Seperator *sep) -{ - std::string arg1(sep->arg[1]); - - while (true) { - if (arg1.compare("list") == 0) { - // do nothing - } - else if (arg1.compare("clear") == 0) { - EQ::ProfanityManager::DeleteProfanityList(&database); - auto pack = new ServerPacket(ServerOP_RefreshCensorship); - worldserver.SendPacket(pack); - safe_delete(pack); - } - else if (arg1.compare("add") == 0) { - if (!EQ::ProfanityManager::AddProfanity(&database, sep->arg[2])) - c->Message(Chat::Red, "Could not add '%s' to the profanity list.", sep->arg[2]); - auto pack = new ServerPacket(ServerOP_RefreshCensorship); - worldserver.SendPacket(pack); - safe_delete(pack); - } - else if (arg1.compare("del") == 0) { - if (!EQ::ProfanityManager::RemoveProfanity(&database, sep->arg[2])) - c->Message(Chat::Red, "Could not delete '%s' from the profanity list.", sep->arg[2]); - auto pack = new ServerPacket(ServerOP_RefreshCensorship); - worldserver.SendPacket(pack); - safe_delete(pack); - } - else if (arg1.compare("reload") == 0) { - if (!EQ::ProfanityManager::UpdateProfanityList(&database)) - c->Message(Chat::Red, "Could not reload the profanity list."); - auto pack = new ServerPacket(ServerOP_RefreshCensorship); - worldserver.SendPacket(pack); - safe_delete(pack); - } - else { - break; - } - - std::string popup; - const auto &list = EQ::ProfanityManager::GetProfanityList(); - for (const auto &iter : list) { - popup.append(iter); - popup.append("
"); - } - if (list.empty()) - popup.append("** Censorship Inactive **
"); - else - popup.append("** End of List **
"); - - c->SendPopupToClient("Profanity List", popup.c_str()); - - return; - } - - c->Message(Chat::White, "Usage: #profanity [list] - shows profanity list"); - c->Message(Chat::White, "Usage: #profanity [clear] - deletes all entries"); - c->Message(Chat::White, "Usage: #profanity [add] [] - adds entry"); - c->Message(Chat::White, "Usage: #profanity [del] [] - deletes entry"); - c->Message(Chat::White, "Usage: #profanity [reload] - reloads profanity list"); -} - -void command_mysql(Client *c, const Seperator *sep) -{ - if(!sep->arg[1][0] || !sep->arg[2][0]) { - c->Message(Chat::White, "Usage: #mysql query \"Query here\""); - return; - } - - if (strcasecmp(sep->arg[1], "help") == 0) { - c->Message(Chat::White, "MYSQL In-Game CLI Interface:"); - c->Message(Chat::White, "Example: #mysql query \"Query goes here quoted\" -s -h"); - c->Message(Chat::White, "To use 'like \"%%something%%\" replace the %% with #"); - c->Message(Chat::White, "Example: #mysql query \"select * from table where name like \"#something#\""); - c->Message(Chat::White, "-s - Spaces select entries apart"); - c->Message(Chat::White, "-h - Colors every other select result"); - return; - } - - if (strcasecmp(sep->arg[1], "query") == 0) { - ///Parse switches here - int argnum = 3; - bool optionS = false; - bool optionH = false; - while(sep->arg[argnum] && strlen(sep->arg[argnum]) > 1){ - switch(sep->arg[argnum][1]){ - case 's': optionS = true; break; - case 'h': optionH = true; break; - default: - c->Message(Chat::Yellow, "%s, there is no option '%c'", c->GetName(), sep->arg[argnum][1]); - return; - } - ++argnum; - } - - int highlightTextIndex = 0; - std::string query(sep->arg[2]); - //swap # for % so like queries can work - std::replace(query.begin(), query.end(), '#', '%'); - auto results = database.QueryDatabase(query); - if (!results.Success()) { - return; - } - - //Using sep->arg[2] again, replace # with %% so it doesn't screw up when sent through vsnprintf in Message - query = sep->arg[2]; - int pos = query.find('#'); - while(pos != std::string::npos) { - query.erase(pos,1); - query.insert(pos, "%%"); - pos = query.find('#'); - } - c->Message(Chat::Yellow, "---Running query: '%s'", query.c_str()); - - for (auto row = results.begin(); row != results.end(); ++row) { - std::stringstream lineText; - std::vector lineVec; - for(int i = 0; i < results.RowCount(); i++) { - //split lines that could overflow the buffer in Client::Message and get cut off - //This will crash MQ2 @ 4000 since their internal buffer is only 2048. - //Reducing it to 2000 fixes that but splits more results from tables with a lot of columns. - if(lineText.str().length() > 4000) { - lineVec.push_back(lineText.str()); - lineText.str(""); - } - lineText << results.FieldName(i) << ":" << "[" << (row[i] ? row[i] : "nullptr") << "] "; - } - - lineVec.push_back(lineText.str()); - - if(optionS) //This provides spacing for the space switch - c->Message(Chat::White, " "); - if(optionH) //This option will highlight every other row - highlightTextIndex = 1 - highlightTextIndex; - - for(int lineNum = 0; lineNum < lineVec.size(); ++lineNum) - c->Message(highlightTextIndex, lineVec[lineNum].c_str()); - } - } -} - -void command_xtargets(Client *c, const Seperator *sep) -{ - Client *t; - - if(c->GetTarget() && c->GetTarget()->IsClient()) - t = c->GetTarget()->CastToClient(); - else - t = c; - - if(sep->arg[1][0]) - { - uint8 NewMax = atoi(sep->arg[1]); - - if((NewMax < 5) || (NewMax > XTARGET_HARDCAP)) - { - c->Message(Chat::Red, "Number of XTargets must be between 5 and %i", XTARGET_HARDCAP); - return; - } - t->SetMaxXTargets(NewMax); - c->Message(Chat::White, "Max number of XTargets set to %i", NewMax); - } - else - t->ShowXTargets(c); -} - -void command_zopp(Client *c, const Seperator *sep) -{ // - Owner only command..non-targetable to eliminate malicious or mischievious activities. - if (!c) - return; - else if (sep->argnum < 3 || sep->argnum > 4) - c->Message(Chat::White, "Usage: #zopp [trade/summon] [slot id] [item id] [*charges]"); - else if (!strcasecmp(sep->arg[1], "trade") == 0 && !strcasecmp(sep->arg[1], "t") == 0 && !strcasecmp(sep->arg[1], "summon") == 0 && !strcasecmp(sep->arg[1], "s") == 0) - c->Message(Chat::White, "Usage: #zopp [trade/summon] [slot id] [item id] [*charges]"); - else if (!sep->IsNumber(2) || !sep->IsNumber(3) || (sep->argnum == 4 && !sep->IsNumber(4))) - c->Message(Chat::White, "Usage: #zopp [trade/summon] [slot id] [item id] [*charges]"); - else { - ItemPacketType packettype; - - if (strcasecmp(sep->arg[1], "trade") == 0 || strcasecmp(sep->arg[1], "t") == 0) { - packettype = ItemPacketTrade; - } - else { - packettype = ItemPacketLimbo; - } - - int16 slotid = atoi(sep->arg[2]); - uint32 itemid = atoi(sep->arg[3]); - int16 charges = sep->argnum == 4 ? atoi(sep->arg[4]) : 1; // defaults to 1 charge if not specified - - const EQ::ItemData* FakeItem = database.GetItem(itemid); - - if (!FakeItem) { - c->Message(Chat::Red, "Error: Item [%u] is not a valid item id.", itemid); - return; - } - - int16 item_status = 0; - const EQ::ItemData* item = database.GetItem(itemid); - if(item) { - item_status = static_cast(item->MinStatus); - } - if (item_status > c->Admin()) { - c->Message(Chat::Red, "Error: Insufficient status to use this command."); - return; - } - - if (charges < 0 || charges > FakeItem->StackSize) { - c->Message(Chat::Red, "Warning: The specified charge count does not meet expected criteria!"); - c->Message(Chat::White, "Processing request..results may cause unpredictable behavior."); - } - - EQ::ItemInstance* FakeItemInst = database.CreateItem(FakeItem, charges); - c->SendItemPacket(slotid, FakeItemInst, packettype); - c->Message(Chat::White, "Sending zephyr op packet to client - [%s] %s (%u) with %i %s to slot %i.", - packettype == ItemPacketTrade ? "Trade" : "Summon", FakeItem->Name, itemid, charges, - std::abs(charges == 1) ? "charge" : "charges", slotid); - safe_delete(FakeItemInst); - } -} - -void command_augmentitem(Client *c, const Seperator *sep) -{ - if (!c) - return; - - auto in_augment = new AugmentItem_Struct[sizeof(AugmentItem_Struct)]; - in_augment->container_slot = 1000; // - in_augment->augment_slot = -1; - if (c->GetTradeskillObject() != nullptr) - Object::HandleAugmentation(c, in_augment, c->GetTradeskillObject()); - safe_delete_array(in_augment); -} - -void command_questerrors(Client *c, const Seperator *sep) -{ - std::list err; - parse->GetErrors(err); - c->Message(Chat::White, "Current Quest Errors:"); - - auto iter = err.begin(); - int i = 0; - while(iter != err.end()) { - if(i >= 30) { - c->Message(Chat::White, "Maximum of 30 Errors shown..."); - break; - } - - c->Message(Chat::White, iter->c_str()); - ++i; - ++iter; - } -} - -void command_enablerecipe(Client *c, const Seperator *sep) -{ - uint32 recipe_id = 0; - bool success = false; - if (c) { - if (sep->argnum == 1) { - recipe_id = atoi(sep->arg[1]); - } - else { - c->Message(Chat::White, "Invalid number of arguments.\nUsage: #enablerecipe recipe_id"); - return; - } - if (recipe_id > 0) { - success = content_db.EnableRecipe(recipe_id); - if (success) { - c->Message(Chat::White, "Recipe enabled."); - } - else { - c->Message(Chat::White, "Recipe not enabled."); - } - } - else { - c->Message(Chat::White, "Invalid recipe id.\nUsage: #enablerecipe recipe_id"); - } - } -} - -void command_disablerecipe(Client *c, const Seperator *sep) -{ - uint32 recipe_id = 0; - bool success = false; - if (c) { - if (sep->argnum == 1) { - recipe_id = atoi(sep->arg[1]); - } - else { - c->Message(Chat::White, "Invalid number of arguments.\nUsage: #disablerecipe recipe_id"); - return; - } - if (recipe_id > 0) { - success = content_db.DisableRecipe(recipe_id); - if (success) { - c->Message(Chat::White, "Recipe disabled."); - } - else { - c->Message(Chat::White, "Recipe not disabled."); - } - } - else { - c->Message(Chat::White, "Invalid recipe id.\nUsage: #disablerecipe recipe_id"); - } - } -} - -void command_npctype_cache(Client *c, const Seperator *sep) -{ - if (sep->argnum > 0) { - for (int i = 0; i < sep->argnum; ++i) { - if (strcasecmp(sep->arg[i + 1], "all") == 0) { - c->Message(Chat::White, "Clearing all npc types from the cache."); - zone->ClearNPCTypeCache(-1); - } - else { - int id = atoi(sep->arg[i + 1]); - if (id > 0) { - c->Message(Chat::White, "Clearing npc type %d from the cache.", id); - zone->ClearNPCTypeCache(id); - return; - } - } - } - } - else { - c->Message(Chat::White, "Usage:"); - c->Message(Chat::White, "#npctype_cache [npctype_id] ..."); - c->Message(Chat::White, "#npctype_cache all"); - } -} - -void command_merchantopenshop(Client *c, const Seperator *sep) -{ - Mob *merchant = c->GetTarget(); - if (!merchant || merchant->GetClass() != MERCHANT) { - c->Message(Chat::White, "You must target a merchant to open their shop."); - return; - } - - merchant->CastToNPC()->MerchantOpenShop(); -} - -void command_merchantcloseshop(Client *c, const Seperator *sep) -{ - Mob *merchant = c->GetTarget(); - if (!merchant || merchant->GetClass() != MERCHANT) { - c->Message(Chat::White, "You must target a merchant to close their shop."); - return; - } - - merchant->CastToNPC()->MerchantCloseShop(); -} - -void command_shownumhits(Client *c, const Seperator *sep) -{ - c->ShowNumHits(); - return; -} - -void command_shownpcgloballoot(Client *c, const Seperator *sep) -{ - auto tar = c->GetTarget(); - - if (!tar || !tar->IsNPC()) { - c->Message(Chat::White, "You must target an NPC to use this command."); - return; - } - - auto npc = tar->CastToNPC(); - c->Message(Chat::White, "GlobalLoot for %s (%d)", npc->GetName(), npc->GetNPCTypeID()); - zone->ShowNPCGlobalLoot(c, npc); -} - -void command_tune(Client *c, const Seperator *sep) -{ - //Work in progress - Kayen - - if(sep->arg[1][0] == '\0' || !strcasecmp(sep->arg[1], "help")) { - c->Message(Chat::White, "Syntax: #tune [subcommand]."); - c->Message(Chat::White, "-- Tune System Commands --"); - c->Message(Chat::White, "-- Usage: Returning recommended combat statistical values based on a desired outcome."); - c->Message(Chat::White, "-- Note: If targeted mob does not have a target (ie not engaged in combat), YOU will be considered the target."); - c->Message(Chat::White, "-- Warning: The calculations done in this process are intense and can potentially cause zone crashes depending on parameters set, use with caution!"); - c->Message(Chat::White, "-- Below are OPTIONAL parameters."); - c->Message(Chat::White, "-- Note: [interval] Determines how fast the stat being checked increases/decreases till it finds the best result. Default [ATK/AC 50][Acc/Avoid 10] "); - c->Message(Chat::White, "-- Note: [loop_max] Determines how many iterations are done to increases/decreases the stat till it finds the best result. Default [ATK/AC 100][Acc/Avoid 1000]"); - c->Message(Chat::White, "-- Note: [Stat Override] Will override that stat on mob being checkd with the specified value. Default=0"); - c->Message(Chat::White, "-- Note: [Info Level] How much statistical detail is displayed[0 - 3]. Default=0 "); - c->Message(Chat::White, "-- Note: Results are only approximations usually accurate to +/- 2 intervals."); - - c->Message(Chat::White, "... "); - c->Message(Chat::White, "...### Category A ### Target = ATTACKER ### YOU or Target's Target = DEFENDER ###"); - c->Message(Chat::White, "...### Category B ### Target = DEFENDER ### YOU or Target's Target = ATTACKER ###"); - c->Message(Chat::White, "... "); - c->Message(Chat::White, "...#Returns recommended ATK adjustment +/- on ATTACKER that will result in an average mitigation pct on DEFENDER. "); - c->Message(Chat::White, "...tune FindATK [A/B] [pct mitigation] [interval][loop_max][AC Overwride][Info Level]"); - c->Message(Chat::White, "... "); - c->Message(Chat::White, "...#Returns recommended AC adjustment +/- on DEFENDER for an average mitigation pct from ATTACKER. "); - c->Message(Chat::White, "...tune FindAC [A/B] [pct mitigation] [interval][loop_max][ATK Overwride][Info Level] "); - c->Message(Chat::White, "... "); - c->Message(Chat::White, "...#Returns recommended Accuracy adjustment +/- on ATTACKER that will result in a hit chance pct on DEFENDER. "); - c->Message(Chat::White, "...tune FindAccuracy [A/B] [hit chance] [interval][loop_max][Avoidance Overwride][Info Level]"); - c->Message(Chat::White, "... "); - c->Message(Chat::White, "...#Returns recommended Avoidance adjustment +/- on DEFENDER for in a hit chance pct from ATTACKER. "); - c->Message(Chat::White, "...tune FindAvoidance [A/B] [pct mitigation] [interval][loop_max][Accuracy Overwride][Info Level] "); - - return; - } - //Default is category A for attacker/defender settings, which then are swapped under category B. - Mob* defender = c; - Mob* attacker = c->GetTarget(); - - if (!attacker) - { - c->Message(Chat::White, "#Tune - Error no target selected. [#Tune help]"); - return; - } - - Mob* ttarget = attacker->GetTarget(); - - if (ttarget) - defender = ttarget; - - if(!strcasecmp(sep->arg[1], "FindATK")) - { - float pct_mitigation = atof(sep->arg[3]); - int interval = atoi(sep->arg[4]); - int max_loop = atoi(sep->arg[5]); - int ac_override = atoi(sep->arg[6]); - int info_level = atoi(sep->arg[7]); - - if (!pct_mitigation) - { - c->Message(Chat::Red, "#Tune - Error must enter the desired percent mitigation on defender. Ie. Defender to mitigate on average 20 pct of max damage."); - return; - } - - if (!interval) - interval = 50; - if (!max_loop) - max_loop = 100; - if(!ac_override) - ac_override = 0; - if (!info_level) - info_level = 1; - - if(!strcasecmp(sep->arg[2], "A")) - c->Tune_FindATKByPctMitigation(defender, attacker, pct_mitigation, interval, max_loop,ac_override,info_level); - else if(!strcasecmp(sep->arg[2], "B")) - c->Tune_FindATKByPctMitigation(attacker,defender, pct_mitigation, interval, max_loop,ac_override,info_level); - else { - c->Message(Chat::White, "#Tune - Error no category selcted. [#Tune help]"); - c->Message(Chat::White, "Usage #tune FindATK [A/B] [pct mitigation] [interval][loop_max][AC Overwride][Info Level] "); - c->Message(Chat::White, "Example #tune FindATK A 60"); - } - return; - } - - if(!strcasecmp(sep->arg[1], "FindAC")) - { - float pct_mitigation = atof(sep->arg[3]); - int interval = atoi(sep->arg[4]); - int max_loop = atoi(sep->arg[5]); - int atk_override = atoi(sep->arg[6]); - int info_level = atoi(sep->arg[7]); - - if (!pct_mitigation) - { - c->Message(Chat::Red, "#Tune - Error must enter the desired percent mitigation on defender. Ie. Defender to mitigate on average 20 pct of max damage."); - return; - } - - if (!interval) - interval = 50; - if (!max_loop) - max_loop = 100; - if(!atk_override) - atk_override = 0; - if (!info_level) - info_level = 1; - - if(!strcasecmp(sep->arg[2], "A")) - c->Tune_FindACByPctMitigation(defender, attacker, pct_mitigation, interval, max_loop,atk_override,info_level); - else if(!strcasecmp(sep->arg[2], "B")) - c->Tune_FindACByPctMitigation(attacker, defender, pct_mitigation, interval, max_loop,atk_override,info_level); - else { - c->Message(Chat::White, "#Tune - Error no category selcted. [#Tune help]"); - c->Message(Chat::White, "Usage #tune FindAC [A/B] [pct mitigation] [interval][loop_max][ATK Overwride][Info Level] "); - c->Message(Chat::White, "Example #tune FindAC A 60"); - } - - return; - } - - if(!strcasecmp(sep->arg[1], "FindAccuracy")) - { - float hit_chance = atof(sep->arg[3]); - int interval = atoi(sep->arg[4]); - int max_loop = atoi(sep->arg[5]); - int avoid_override = atoi(sep->arg[6]); - int info_level = atoi(sep->arg[7]); - - if (!hit_chance) - { - c->Message(Chat::NPCQuestSay, "#Tune - Error must enter the desired percent mitigation on defender. Ie. Defender to mitigate on average 20 pct of max damage."); - return; - } - - if (!interval) - interval = 10; - if (!max_loop) - max_loop = 1000; - if(!avoid_override) - avoid_override = 0; - if (!info_level) - info_level = 1; - - if (hit_chance > RuleR(Combat,MaxChancetoHit) || hit_chance < RuleR(Combat,MinChancetoHit)) - { - c->Message(Chat::NPCQuestSay, "#Tune - Error hit chance out of bounds. [Max %.2f Min .2f]", RuleR(Combat,MaxChancetoHit),RuleR(Combat,MinChancetoHit)); - return; - } - - if(!strcasecmp(sep->arg[2], "A")) - c->Tune_FindAccuaryByHitChance(defender, attacker, hit_chance, interval, max_loop,avoid_override,info_level); - else if(!strcasecmp(sep->arg[2], "B")) - c->Tune_FindAccuaryByHitChance(attacker, defender, hit_chance, interval, max_loop,avoid_override,info_level); - else { - c->Message(Chat::White, "#Tune - Error no category selcted. [#Tune help]"); - c->Message(Chat::White, "Usage #tune FindAcccuracy [A/B] [hit chance] [interval][loop_max][Avoidance Overwride][Info Level]"); - c->Message(Chat::White, "Exampled #tune FindAccuracy B 30"); - } - - return; - } - - if(!strcasecmp(sep->arg[1], "FindAvoidance")) - { - float hit_chance = atof(sep->arg[3]); - int interval = atoi(sep->arg[4]); - int max_loop = atoi(sep->arg[5]); - int acc_override = atoi(sep->arg[6]); - int info_level = atoi(sep->arg[7]); - - if (!hit_chance) - { - c->Message(Chat::White, "#Tune - Error must enter the desired hit chance on defender. Ie. Defender to have hit chance of 40 pct."); - return; - } - - if (!interval) - interval = 10; - if (!max_loop) - max_loop = 1000; - if(!acc_override) - acc_override = 0; - if (!info_level) - info_level = 1; - - if (hit_chance > RuleR(Combat,MaxChancetoHit) || hit_chance < RuleR(Combat,MinChancetoHit)) - { - c->Message(Chat::NPCQuestSay, "#Tune - Error hit chance out of bounds. [Max %.2f Min .2f]", RuleR(Combat,MaxChancetoHit),RuleR(Combat,MinChancetoHit)); - return; - } - - if(!strcasecmp(sep->arg[2], "A")) - c->Tune_FindAvoidanceByHitChance(defender, attacker, hit_chance, interval, max_loop,acc_override, info_level); - else if(!strcasecmp(sep->arg[2], "B")) - c->Tune_FindAvoidanceByHitChance(attacker, defender, hit_chance, interval, max_loop,acc_override, info_level); - else { - c->Message(Chat::White, "#Tune - Error no category selcted. [#Tune help]"); - c->Message(Chat::White, "Usage #tune FindAvoidance [A/B] [hit chance] [interval][loop_max][Accuracy Overwride][Info Level]"); - c->Message(Chat::White, "Exampled #tune FindAvoidance B 30"); - } - - return; - } - - - return; -} - -void command_logtest(Client *c, const Seperator *sep){ - clock_t t = std::clock(); /* Function timer start */ - if (sep->IsNumber(1)){ - uint32 i = 0; - t = std::clock(); - for (i = 0; i < atoi(sep->arg[1]); i++){ - LogDebug("[[{}]] Test #2 Took [{}] seconds", i, ((float)(std::clock() - t)) / CLOCKS_PER_SEC); - } - - } -} - -void command_crashtest(Client *c, const Seperator *sep) -{ - c->Message(Chat::White, "Alright, now we get an GPF ;) "); - char* gpf = 0; - memcpy(gpf, "Ready to crash", 30); -} - -void command_logs(Client *c, const Seperator *sep){ - int logs_set = 0; - if (sep->argnum > 0) { - /* #logs reload_all */ - if (strcasecmp(sep->arg[1], "reload_all") == 0){ - auto pack = new ServerPacket(ServerOP_ReloadLogs, 0); - worldserver.SendPacket(pack); - c->Message(Chat::Red, "Successfully sent the packet to world to reload log settings from the database for all zones"); - safe_delete(pack); - } - /* #logs list_settings */ - if (strcasecmp(sep->arg[1], "list_settings") == 0 || - (strcasecmp(sep->arg[1], "set") == 0 && strcasecmp(sep->arg[3], "") == 0)) { - c->Message(Chat::White, "[Category ID | console | file | gmsay | Category Description]"); - int redisplay_columns = 0; - for (int i = 0; i < Logs::LogCategory::MaxCategoryID; i++) { - if (redisplay_columns == 10) { - c->Message(Chat::White, "[Category ID | console | file | gmsay | Category Description]"); - redisplay_columns = 0; - } - c->Message( - 0, - StringFormat( - "--- %i | %u | %u | %u | %s", - i, - LogSys.log_settings[i].log_to_console, - LogSys.log_settings[i].log_to_file, - LogSys.log_settings[i].log_to_gmsay, - Logs::LogCategoryName[i] - ).c_str()); - redisplay_columns++; - } - } - /* #logs set */ - if (strcasecmp(sep->arg[1], "set") == 0){ - if (strcasecmp(sep->arg[2], "console") == 0){ - LogSys.log_settings[atoi(sep->arg[3])].log_to_console = atoi(sep->arg[4]); - logs_set = 1; - } - else if (strcasecmp(sep->arg[2], "file") == 0){ - LogSys.log_settings[atoi(sep->arg[3])].log_to_file = atoi(sep->arg[4]); - logs_set = 1; - } - else if (strcasecmp(sep->arg[2], "gmsay") == 0){ - LogSys.log_settings[atoi(sep->arg[3])].log_to_gmsay = atoi(sep->arg[4]); - logs_set = 1; - } - else{ - c->Message(Chat::White, "--- #logs set [console|file|gmsay] - Sets log settings during the lifetime of the zone"); - c->Message(Chat::White, "--- #logs set gmsay 20 1 - Would output Quest errors to gmsay"); - } - if (logs_set == 1){ - c->Message(Chat::Yellow, "Your Log Settings have been applied"); - c->Message(Chat::Yellow, "Output Method: %s :: Debug Level: %i - Category: %s", sep->arg[2], atoi(sep->arg[4]), Logs::LogCategoryName[atoi(sep->arg[3])]); - } - /* We use a general 'is_category_enabled' now, let's update when we update any output settings - This is used in hot places of code to check if its enabled in any way before triggering logs - */ - if (atoi(sep->arg[4]) > 0){ - LogSys.log_settings[atoi(sep->arg[3])].is_category_enabled = 1; - } - else{ - LogSys.log_settings[atoi(sep->arg[3])].is_category_enabled = 0; - } - } - } - else { - c->Message(Chat::White, "#logs usage:"); - c->Message(Chat::White, "--- #logs reload_all - Reload all settings in world and all zone processes with what is defined in the database"); - c->Message(Chat::White, "--- #logs list_settings - Shows current log settings and categories loaded into the current process' memory"); - c->Message(Chat::White, "--- #logs set [console|file|gmsay] - Sets log settings during the lifetime of the zone"); - } -} - -void command_mysqltest(Client *c, const Seperator *sep) -{ - clock_t t = std::clock(); /* Function timer start */ - if (sep->IsNumber(1)){ - uint32 i = 0; - t = std::clock(); - for (i = 0; i < atoi(sep->arg[1]); i++){ - std::string query = "SELECT * FROM `zone`"; - auto results = content_db.QueryDatabase(query); - } - } - LogDebug("MySQL Test Took [{}] seconds", ((float)(std::clock() - t)) / CLOCKS_PER_SEC); -} - -void command_resetaa_timer(Client *c, const Seperator *sep) { - Client *target = nullptr; - if(!c->GetTarget() || !c->GetTarget()->IsClient()) { - target = c; - } else { - target = c->GetTarget()->CastToClient(); - } - - if(sep->IsNumber(1)) - { - int timer_id = atoi(sep->arg[1]); - c->Message(Chat::White, "Reset of timer %i for %s", timer_id, c->GetName()); - c->ResetAlternateAdvancementTimer(timer_id); - } - else if(!strcasecmp(sep->arg[1], "all")) - { - c->Message(Chat::White, "Reset all timers for %s", c->GetName()); - c->ResetAlternateAdvancementTimers(); - } - else - { - c->Message(Chat::White, "usage: #resetaa_timer [all | timer_id]"); - } -} - -void command_reloadaa(Client *c, const Seperator *sep) { - c->Message(Chat::White, "Reloading Alternate Advancement Data..."); - zone->LoadAlternateAdvancement(); - c->Message(Chat::White, "Alternate Advancement Data Reloaded"); - entity_list.SendAlternateAdvancementStats(); -} - -inline bool file_exists(const std::string& name) { - std::ifstream f(name.c_str()); - return f.good(); -} - void command_hotfix(Client *c, const Seperator *sep) { std::string hotfix; @@ -13543,586 +1081,69 @@ void command_apply_shared_memory(Client *c, const Seperator *sep) { worldserver.SendPacket(&pack); } -void command_reloadperlexportsettings(Client *c, const Seperator *sep) +void command_emptyinventory(Client *c, const Seperator *sep) { - if (c) - { - auto pack = new ServerPacket(ServerOP_ReloadPerlExportSettings, 0); - worldserver.SendPacket(pack); - c->Message(Chat::Red, "Successfully sent the packet to world to reload Perl Export settings"); - safe_delete(pack); - - } -} - -void command_trapinfo(Client *c, const Seperator *sep) -{ - entity_list.GetTrapInfo(c); -} - -void command_reloadtraps(Client *c, const Seperator *sep) -{ - entity_list.UpdateAllTraps(true, true); - c->Message(Chat::Default, "Traps reloaded for %s.", zone->GetShortName()); -} - -void command_scale(Client *c, const Seperator *sep) -{ - if (sep->argnum == 0) { - c->Message(Chat::Yellow, "# Usage # "); - c->Message(Chat::Yellow, "#scale [static/dynamic] (With targeted NPC)"); - c->Message(Chat::Yellow, "#scale [npc_name_search] [static/dynamic] (To make zone-wide changes)"); - c->Message(Chat::Yellow, "#scale all [static/dynamic]"); - return; + Client *target = c; + if (c->GetGM() && c->GetTarget() && c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); } - /** - * Targeted changes - */ - if (c->GetTarget() && c->GetTarget()->IsNPC() && sep->argnum < 2) { - NPC * npc = c->GetTarget()->CastToNPC(); - - bool apply_status = false; - if (strcasecmp(sep->arg[1], "dynamic") == 0) { - c->Message(Chat::Yellow, "Applying global base scaling to npc dynamically (All stats set to zeroes)..."); - apply_status = npc_scale_manager->ApplyGlobalBaseScalingToNPCDynamically(npc); - } - else if (strcasecmp(sep->arg[1], "static") == 0) { - c->Message(Chat::Yellow, "Applying global base scaling to npc statically (Copying base stats onto NPC)..."); - apply_status = npc_scale_manager->ApplyGlobalBaseScalingToNPCStatically(npc); - } - else { - return; - } - - if (apply_status) { - c->Message(Chat::Yellow, "Applied to NPC '%s' successfully!", npc->GetName()); - } - else { - c->Message(Chat::Yellow, "Failed to load scaling data from the database " - "for this npc / type, see 'NPCScaling' log for more info"); - } - } - else if (c->GetTarget() && sep->argnum < 2) { - c->Message(Chat::Yellow, "Target must be an npc!"); - } - - /** - * Zonewide - */ - if (sep->argnum > 1) { - - std::string scale_type; - if (strcasecmp(sep->arg[2], "dynamic") == 0) { - scale_type = "dynamic"; - } - else if (strcasecmp(sep->arg[2], "static") == 0) { - scale_type = "static"; - } - - if (scale_type.length() <= 0) { - c->Message(Chat::Yellow, "You must first set if you intend on using static versus dynamic for these changes"); - c->Message(Chat::Yellow, "#scale [npc_name_search] [static/dynamic]"); - c->Message(Chat::Yellow, "#scale all [static/dynamic]"); - return; - } - - std::string search_string = sep->arg[1]; - - auto &entity_list_search = entity_list.GetNPCList(); - - int found_count = 0; - for (auto &itr : entity_list_search) { - NPC *entity = itr.second; - - std::string entity_name = entity->GetName(); - - /** - * Filter by name - */ - if (search_string.length() > 0 && entity_name.find(search_string) == std::string::npos && strcasecmp(sep->arg[1], "all") != 0) { - continue; - } - - std::string status = "(Searching)"; - - if (strcasecmp(sep->arg[3], "apply") == 0) { - status = "(Applying)"; - - if (strcasecmp(sep->arg[2], "dynamic") == 0) { - npc_scale_manager->ApplyGlobalBaseScalingToNPCDynamically(entity); - } - if (strcasecmp(sep->arg[2], "static") == 0) { - npc_scale_manager->ApplyGlobalBaseScalingToNPCStatically(entity); - } - } - - c->Message( - 15, - "| ID %5d | %s | x %.0f | y %0.f | z %.0f | DBID %u %s", - entity->GetID(), - entity->GetName(), - entity->GetX(), - entity->GetY(), - entity->GetZ(), - entity->GetNPCTypeID(), - status.c_str() - ); - - found_count++; - } - - if (strcasecmp(sep->arg[3], "apply") == 0) { - c->Message(Chat::Yellow, "%s scaling applied against (%i) NPC's", sep->arg[2], found_count); - } - else { - - std::string saylink = StringFormat( - "#scale %s %s apply", - sep->arg[1], - sep->arg[2] - ); - - c->Message(Chat::Yellow, "Found (%i) NPC's that match this search...", found_count); - c->Message( - Chat::Yellow, "To apply these changes, click <%s> or type %s", - EQ::SayLinkEngine::GenerateQuestSaylink(saylink, false, "Apply").c_str(), - saylink.c_str() - ); - } - } -} - -void command_databuckets(Client *c, const Seperator *sep) - { - if (sep->arg[1][0] == 0) { - c->Message(Chat::Yellow, "Usage: #databuckets view (partial key)|(limit) OR #databuckets delete (key)"); - return; - } - if (strcasecmp(sep->arg[1], "view") == 0) { - - std::string key_filter; - uint8 limit = 50; - for (int i = 2; i < 4; i++) { - if (sep->arg[i][0] == '\0') - break; - if (strcasecmp(sep->arg[i], "limit") == 0) { - limit = (uint8)atoi(sep->arg[i + 1]); - continue; + EQ::ItemInstance *item = nullptr; + static const int16 slots[][2] = { + { EQ::invslot::POSSESSIONS_BEGIN, EQ::invslot::POSSESSIONS_END }, + { EQ::invbag::GENERAL_BAGS_BEGIN, EQ::invbag::GENERAL_BAGS_END }, + { EQ::invbag::CURSOR_BAG_BEGIN, EQ::invbag::CURSOR_BAG_END}, + { EQ::invslot::BANK_BEGIN, EQ::invslot::BANK_END }, + { EQ::invbag::BANK_BAGS_BEGIN, EQ::invbag::BANK_BAGS_END }, + { EQ::invslot::SHARED_BANK_BEGIN, EQ::invslot::SHARED_BANK_END }, + { EQ::invbag::SHARED_BANK_BAGS_BEGIN, EQ::invbag::SHARED_BANK_BAGS_END }, + }; + int removed_count = 0; + 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 = target->GetInv().GetItem(slot_id); + if (item) { + int stack_size = std::max(static_cast(item->GetCharges()), 1); + removed_count += stack_size; + target->DeleteItemInInventory(slot_id, 0, true); } } - if (sep->arg[2]) { - key_filter = str_tolower(sep->arg[2]); - } - std::string query = "SELECT `id`, `key`, `value`, `expires` FROM data_buckets"; - if (!key_filter.empty()) query += StringFormat(" WHERE `key` LIKE '%%%s%%'", key_filter.c_str()); - query += StringFormat(" LIMIT %u", limit); - auto results = database.QueryDatabase(query); - if (!results.Success()) - return; - if (results.RowCount() == 0) { - c->Message(Chat::Yellow, "No data_buckets found"); - return; - } - int _ctr = 0; - // put in window for easier readability in case want command line for something else - std::string window_title = "Data Buckets"; - std::string window_text = - "" - "" - "" - "" - "" - "" - ""; - for (auto row = results.begin(); row != results.end(); ++row) { - auto id = static_cast(atoi(row[0])); - std::string key = row[1]; - std::string value = row[2]; - std::string expires = row[3]; - window_text.append(StringFormat( - "" - "" - "" - "" - "" - "", - id, - expires.c_str(), - key.c_str(), - value.c_str() - )); - _ctr++; - std::string del_saylink = StringFormat("#databuckets delete %s", key.c_str()); - c->Message(Chat::White, "%s : %s", - EQ::SayLinkEngine::GenerateQuestSaylink(del_saylink, false, "Delete").c_str(), key.c_str(), " Value: ", value.c_str()); - } - window_text.append("
IDExpiresKeyValue
%u%s%s%s
"); - c->SendPopupToClient(window_title.c_str(), window_text.c_str()); - std::string response = _ctr > 0 ? StringFormat("Found %i matching data buckets", _ctr).c_str() : "No Databuckets found."; - c->Message(Chat::Yellow, response.c_str()); } - else if (strcasecmp(sep->arg[1], "delete") == 0) - { - if (DataBucket::DeleteData(sep->argplus[2])) - c->Message(Chat::Yellow, "data bucket %s deleted.", sep->argplus[2]); - else - c->Message(Chat::Red, "An error occurred deleting data bucket %s", sep->argplus[2]); - return; - } -} -void command_who(Client *c, const Seperator *sep) -{ - std::string query = - SQL ( - SELECT - character_data.account_id, - character_data.name, - character_data.zone_id, - character_data.zone_instance, - COALESCE( + if (removed_count) { + c->Message( + Chat::White, + fmt::format( + "Inventory cleared for {}, {} items deleted.", ( - select - guilds.name - from - guilds - where - id = ( - ( - select - guild_id - from - guild_members - where - char_id = character_data.id - ) + c == target ? + "yourself" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() ) ), - "" - ) as guild_name, - character_data.level, - character_data.race, - character_data.class, - COALESCE( - ( - select - account.status - from - account - where - account.id = character_data.account_id - LIMIT - 1 - ), 0 - ) as account_status, - COALESCE( - ( - select - account.name - from - account - where - account.id = character_data.account_id - LIMIT - 1 - ), - 0 - ) as account_name, - COALESCE( - ( - select - account_ip.ip - from - account_ip - where - account_ip.accid = character_data.account_id - ORDER BY - account_ip.lastused DESC - LIMIT - 1 - ), - "" - ) as account_ip - FROM - character_data - WHERE - last_login > (UNIX_TIMESTAMP() - 600) - ORDER BY - character_data.name; - ) - ; - - auto results = database.QueryDatabase(query); - if (!results.Success()) - return; - - if (results.RowCount() == 0) { - c->Message(Chat::Yellow, "No results found"); - return; - } - - std::string search_string; - - if (sep->arg[1]) { - search_string = str_tolower(sep->arg[1]); - } - - int found_count = 0; - - c->Message(Chat::Magenta, "Players in EverQuest"); - c->Message(Chat::Magenta, "--------------------"); - - for (auto row = results.begin(); row != results.end(); ++row) { - auto account_id = static_cast(atoi(row[0])); - std::string player_name = row[1]; - auto zone_id = static_cast(atoi(row[2])); - std::string zone_short_name = ZoneName(zone_id); - auto zone_instance = static_cast(atoi(row[3])); - std::string guild_name = row[4]; - auto player_level = static_cast(atoi(row[5])); - auto player_race = static_cast(atoi(row[6])); - auto player_class = static_cast(atoi(row[7])); - auto account_status = static_cast(atoi(row[8])); - std::string account_name = row[9]; - std::string account_ip = row[10]; - std::string base_class_name = GetClassIDName(static_cast(player_class), 1); - std::string displayed_race_name = GetRaceIDName(static_cast(player_race)); - - if (search_string.length() > 0) { - bool found_search_term = - ( - str_tolower(player_name).find(search_string) != std::string::npos || - str_tolower(zone_short_name).find(search_string) != std::string::npos || - str_tolower(displayed_race_name).find(search_string) != std::string::npos || - str_tolower(base_class_name).find(search_string) != std::string::npos || - str_tolower(guild_name).find(search_string) != std::string::npos || - str_tolower(account_name).find(search_string) != std::string::npos || - str_tolower(account_ip).find(search_string) != std::string::npos - ); - - if (!found_search_term) { - continue; - } - } - - std::string displayed_guild_name; - if (guild_name.length() > 0) { - displayed_guild_name = EQ::SayLinkEngine::GenerateQuestSaylink( - StringFormat( - "#who \"%s\"", - guild_name.c_str()), - false, - StringFormat("<%s>", guild_name.c_str()) - ); - } - - std::string goto_saylink = EQ::SayLinkEngine::GenerateQuestSaylink( - StringFormat("#goto %s", player_name.c_str()), false, "Goto" + removed_count + ).c_str() ); - - std::string display_class_name = GetClassIDName(static_cast(player_class), static_cast(player_level)); - + } else { c->Message( - 5, "%s[%u %s] %s (%s) %s ZONE: %s (%u) (%s) (%s) (%s)", - (account_status > 0 ? "* GM * " : ""), - player_level, - EQ::SayLinkEngine::GenerateQuestSaylink(StringFormat("#who %s", base_class_name.c_str()), false, display_class_name).c_str(), - player_name.c_str(), - EQ::SayLinkEngine::GenerateQuestSaylink(StringFormat("#who %s", displayed_race_name.c_str()), false, displayed_race_name).c_str(), - displayed_guild_name.c_str(), - EQ::SayLinkEngine::GenerateQuestSaylink(StringFormat("#who %s", zone_short_name.c_str()), false, zone_short_name).c_str(), - zone_instance, - goto_saylink.c_str(), - EQ::SayLinkEngine::GenerateQuestSaylink(StringFormat("#who %s", account_name.c_str()), false, account_name).c_str(), - EQ::SayLinkEngine::GenerateQuestSaylink(StringFormat("#who %s", account_ip.c_str()), false, account_ip).c_str() + Chat::White, + fmt::format( + "{} no items to delete.", + ( + c == target ? + "You have" : + fmt::format( + "{} ({}) has", + target->GetCleanName(), + target->GetID() + ) + ) + ).c_str() ); - - found_count++; - } - - std::string message = ( - found_count > 0 ? - StringFormat("There is %i player(s) in EverQuest", found_count).c_str() : - "There are no players in EverQuest that match those who filters." - ); - - c->Message(Chat::Magenta, message.c_str()); -} - -void command_network(Client *c, const Seperator *sep) -{ - if (!strcasecmp(sep->arg[1], "getopt")) - { - auto eqsi = c->Connection(); - auto manager = eqsi->GetManager(); - auto opts = manager->GetOptions(); - - if (!strcasecmp(sep->arg[2], "all")) - { - c->Message(Chat::White, "max_packet_size: %llu", (uint64_t)opts.daybreak_options.max_packet_size); - c->Message(Chat::White, "max_connection_count: %llu", (uint64_t)opts.daybreak_options.max_connection_count); - c->Message(Chat::White, "keepalive_delay_ms: %llu", (uint64_t)opts.daybreak_options.keepalive_delay_ms); - c->Message(Chat::White, "resend_delay_factor: %.2f", opts.daybreak_options.resend_delay_factor); - c->Message(Chat::White, "resend_delay_ms: %llu", (uint64_t)opts.daybreak_options.resend_delay_ms); - c->Message(Chat::White, "resend_delay_min: %llu", (uint64_t)opts.daybreak_options.resend_delay_min); - c->Message(Chat::White, "resend_delay_max: %llu", (uint64_t)opts.daybreak_options.resend_delay_max); - c->Message(Chat::White, "connect_delay_ms: %llu", (uint64_t)opts.daybreak_options.connect_delay_ms); - c->Message(Chat::White, "connect_stale_ms: %llu", (uint64_t)opts.daybreak_options.connect_stale_ms); - c->Message(Chat::White, "stale_connection_ms: %llu", (uint64_t)opts.daybreak_options.stale_connection_ms); - c->Message(Chat::White, "crc_length: %llu", (uint64_t)opts.daybreak_options.crc_length); - c->Message(Chat::White, "hold_size: %llu", (uint64_t)opts.daybreak_options.hold_size); - c->Message(Chat::White, "hold_length_ms: %llu", (uint64_t)opts.daybreak_options.hold_length_ms); - c->Message(Chat::White, "simulated_in_packet_loss: %llu", (uint64_t)opts.daybreak_options.simulated_in_packet_loss); - c->Message(Chat::White, "simulated_out_packet_loss: %llu", (uint64_t)opts.daybreak_options.simulated_out_packet_loss); - c->Message(Chat::White, "tic_rate_hertz: %.2f", opts.daybreak_options.tic_rate_hertz); - c->Message(Chat::White, "resend_timeout: %llu", (uint64_t)opts.daybreak_options.resend_timeout); - c->Message(Chat::White, "connection_close_time: %llu", (uint64_t)opts.daybreak_options.connection_close_time); - c->Message(Chat::White, "encode_passes[0]: %llu", (uint64_t)opts.daybreak_options.encode_passes[0]); - c->Message(Chat::White, "encode_passes[1]: %llu", (uint64_t)opts.daybreak_options.encode_passes[1]); - c->Message(Chat::White, "port: %llu", (uint64_t)opts.daybreak_options.port); - } - else { - c->Message(Chat::White, "Unknown get option: %s", sep->arg[2]); - c->Message(Chat::White, "Available options:"); - //Todo the rest of these when im less lazy. - //c->Message(Chat::White, "max_packet_size"); - //c->Message(Chat::White, "max_connection_count"); - //c->Message(Chat::White, "keepalive_delay_ms"); - //c->Message(Chat::White, "resend_delay_factor"); - //c->Message(Chat::White, "resend_delay_ms"); - //c->Message(Chat::White, "resend_delay_min"); - //c->Message(Chat::White, "resend_delay_max"); - //c->Message(Chat::White, "connect_delay_ms"); - //c->Message(Chat::White, "connect_stale_ms"); - //c->Message(Chat::White, "stale_connection_ms"); - //c->Message(Chat::White, "crc_length"); - //c->Message(Chat::White, "hold_size"); - //c->Message(Chat::White, "hold_length_ms"); - //c->Message(Chat::White, "simulated_in_packet_loss"); - //c->Message(Chat::White, "simulated_out_packet_loss"); - //c->Message(Chat::White, "tic_rate_hertz"); - //c->Message(Chat::White, "resend_timeout"); - //c->Message(Chat::White, "connection_close_time"); - //c->Message(Chat::White, "encode_passes[0]"); - //c->Message(Chat::White, "encode_passes[1]"); - //c->Message(Chat::White, "port"); - c->Message(Chat::White, "all"); - } - } - else if (!strcasecmp(sep->arg[1], "setopt")) - { - auto eqsi = c->Connection(); - auto manager = eqsi->GetManager(); - auto opts = manager->GetOptions(); - - if (!strcasecmp(sep->arg[3], "")) - { - c->Message(Chat::White, "Missing value for set"); - return; - } - - std::string value = sep->arg[3]; - if (!strcasecmp(sep->arg[2], "max_connection_count")) - { - opts.daybreak_options.max_connection_count = std::stoull(value); - manager->SetOptions(opts); - } - else if (!strcasecmp(sep->arg[2], "keepalive_delay_ms")) - { - opts.daybreak_options.keepalive_delay_ms = std::stoull(value); - manager->SetOptions(opts); - } - else if (!strcasecmp(sep->arg[2], "resend_delay_factor")) - { - opts.daybreak_options.resend_delay_factor = std::stod(value); - manager->SetOptions(opts); - } - else if (!strcasecmp(sep->arg[2], "resend_delay_ms")) - { - opts.daybreak_options.resend_delay_ms = std::stoull(value); - manager->SetOptions(opts); - } - else if (!strcasecmp(sep->arg[2], "resend_delay_min")) - { - opts.daybreak_options.resend_delay_min = std::stoull(value); - manager->SetOptions(opts); - } - else if (!strcasecmp(sep->arg[2], "resend_delay_max")) - { - opts.daybreak_options.resend_delay_max = std::stoull(value); - manager->SetOptions(opts); - } - else if (!strcasecmp(sep->arg[2], "connect_delay_ms")) - { - opts.daybreak_options.connect_delay_ms = std::stoull(value); - manager->SetOptions(opts); - } - else if (!strcasecmp(sep->arg[2], "connect_stale_ms")) - { - opts.daybreak_options.connect_stale_ms = std::stoull(value); - manager->SetOptions(opts); - } - else if (!strcasecmp(sep->arg[2], "stale_connection_ms")) - { - opts.daybreak_options.stale_connection_ms = std::stoull(value); - manager->SetOptions(opts); - } - else if (!strcasecmp(sep->arg[2], "hold_size")) - { - opts.daybreak_options.hold_size = std::stoull(value); - manager->SetOptions(opts); - } - else if (!strcasecmp(sep->arg[2], "hold_length_ms")) - { - opts.daybreak_options.hold_length_ms = std::stoull(value); - manager->SetOptions(opts); - } - else if (!strcasecmp(sep->arg[2], "simulated_in_packet_loss")) - { - opts.daybreak_options.simulated_in_packet_loss = std::stoull(value); - manager->SetOptions(opts); - } - else if (!strcasecmp(sep->arg[2], "simulated_out_packet_loss")) - { - opts.daybreak_options.simulated_out_packet_loss = std::stoull(value); - manager->SetOptions(opts); - } - else if (!strcasecmp(sep->arg[2], "resend_timeout")) - { - opts.daybreak_options.resend_timeout = std::stoull(value); - manager->SetOptions(opts); - } - else if (!strcasecmp(sep->arg[2], "connection_close_time")) - { - opts.daybreak_options.connection_close_time = std::stoull(value); - manager->SetOptions(opts); - } - else { - c->Message(Chat::White, "Unknown set option: %s", sep->arg[2]); - c->Message(Chat::White, "Available options:"); - c->Message(Chat::White, "max_connection_count"); - c->Message(Chat::White, "keepalive_delay_ms"); - c->Message(Chat::White, "resend_delay_factor"); - c->Message(Chat::White, "resend_delay_ms"); - c->Message(Chat::White, "resend_delay_min"); - c->Message(Chat::White, "resend_delay_max"); - c->Message(Chat::White, "connect_delay_ms"); - c->Message(Chat::White, "connect_stale_ms"); - c->Message(Chat::White, "stale_connection_ms"); - c->Message(Chat::White, "hold_size"); - c->Message(Chat::White, "hold_length_ms"); - c->Message(Chat::White, "simulated_in_packet_loss"); - c->Message(Chat::White, "simulated_out_packet_loss"); - c->Message(Chat::White, "resend_timeout"); - c->Message(Chat::White, "connection_close_time"); - } - } - else { - c->Message(Chat::White, "Unknown command: %s", sep->arg[1]); - c->Message(Chat::White, "Network commands avail:"); - c->Message(Chat::White, "getopt optname - Retrieve the current option value set."); - c->Message(Chat::White, "setopt optname - Set the current option allowed."); } } diff --git a/zone/command.h b/zone/command.h index 303e0f132..dc64da7b2 100644 --- a/zone/command.h +++ b/zone/command.h @@ -1,22 +1,3 @@ -/* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.org) - - 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 COMMAND_H #define COMMAND_H @@ -25,18 +6,18 @@ class Seperator; #include "../common/types.h" -#define COMMAND_CHAR '#' +#define COMMAND_CHAR '#' -typedef void (*CmdFuncPtr)(Client *,const Seperator *); +typedef void (*CmdFuncPtr)(Client *, const Seperator *); typedef struct { - int access; - const char *desc; // description of command - CmdFuncPtr function; // null means perl function -} CommandRecord; + int access; + const char *desc; // description of command + CmdFuncPtr function; // null means perl function +} CommandRecord; -extern int (*command_dispatch)(Client *,char const*); -extern int commandcount; // number of commands loaded +extern int (*command_dispatch)(Client *, char const *); +extern int commandcount; // number of commands loaded // the command system: int command_init(void); @@ -53,30 +34,23 @@ void command_aggro(Client *c, const Seperator *sep); void command_aggrozone(Client *c, const Seperator *sep); void command_ai(Client *c, const Seperator *sep); void command_appearance(Client *c, const Seperator *sep); +void command_appearanceeffects(Client *c, const Seperator *sep); void command_apply_shared_memory(Client *c, const Seperator *sep); void command_attack(Client *c, const Seperator *sep); void command_augmentitem(Client *c, const Seperator *sep); void command_ban(Client *c, const Seperator *sep); void command_beard(Client *c, const Seperator *sep); void command_beardcolor(Client *c, const Seperator *sep); -void command_bind(Client* c, const Seperator *sep); - -#ifdef BUGTRACK -void command_bug(Client *c, const Seperator *sep); -#endif - +void command_bind(Client *c, const Seperator *sep); void command_camerashake(Client *c, const Seperator *sep); void command_castspell(Client *c, const Seperator *sep); void command_chat(Client *c, const Seperator *sep); void command_checklos(Client *c, const Seperator *sep); -void command_clearinvsnapshots(Client *c, const Seperator *sep); -void command_connectworldserver(Client *c, const Seperator *sep); void command_copycharacter(Client *c, const Seperator *sep); void command_corpse(Client *c, const Seperator *sep); void command_corpsefix(Client *c, const Seperator *sep); -void command_crashtest(Client *c, const Seperator *sep); +void command_countitem(Client *c, const Seperator *sep); void command_cvs(Client *c, const Seperator *sep); -void command_d1(Client *c, const Seperator *sep); void command_damage(Client *c, const Seperator *sep); void command_databuckets(Client *c, const Seperator *sep); void command_date(Client *c, const Seperator *sep); @@ -90,23 +64,30 @@ void command_devtools(Client *c, const Seperator *sep); void command_details(Client *c, const Seperator *sep); void command_disablerecipe(Client *c, const Seperator *sep); void command_disarmtrap(Client *c, const Seperator *sep); +void command_door(Client *c, const Seperator *sep); void command_distance(Client *c, const Seperator *sep); void command_doanim(Client *c, const Seperator *sep); +void command_dye(Client *c, const Seperator *sep); void command_dz(Client *c, const Seperator *sep); void command_dzkickplayers(Client *c, const Seperator *sep); -void command_editmassrespawn(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); +void command_emotesearch(Client *c, const Seperator *sep); +void command_emoteview(Client *c, const Seperator *sep); +void command_emptyinventory(Client *c, const Seperator *sep); void command_enablerecipe(Client *c, const Seperator *sep); void command_endurance(Client *c, const Seperator *sep); void command_equipitem(Client *c, const Seperator *sep); void command_face(Client *c, const Seperator *sep); void command_faction(Client *c, const Seperator *sep); void command_findaliases(Client *c, const Seperator *sep); +void command_findclass(Client *c, const Seperator *sep); +void command_findfaction(Client *c, const Seperator *sep); void command_findnpctype(Client *c, const Seperator *sep); void command_findrace(Client *c, const Seperator *sep); +void command_findskill(Client *c, const Seperator *sep); void command_findspell(Client *c, const Seperator *sep); +void command_findtask(Client *c, const Seperator *sep); void command_findzone(Client *c, const Seperator *sep); void command_fixmob(Client *c, const Seperator *sep); void command_flag(Client *c, const Seperator *sep); @@ -123,14 +104,14 @@ void command_getvariable(Client *c, const Seperator *sep); void command_ginfo(Client *c, const Seperator *sep); void command_giveitem(Client *c, const Seperator *sep); void command_givemoney(Client *c, const Seperator *sep); -void command_globalview(Client* c, const Seperator *sep); +void command_globalview(Client *c, const Seperator *sep); void command_gm(Client *c, const Seperator *sep); void command_gmspeed(Client *c, const Seperator *sep); void command_gmzone(Client *c, const Seperator *sep); void command_goto(Client *c, const Seperator *sep); void command_grid(Client *c, const Seperator *sep); void command_guild(Client *c, const Seperator *sep); -bool helper_guild_edit(Client *c, uint32 dbid, uint32 eqid, uint8 rank, const char* what, const char* value); +bool helper_guild_edit(Client *c, uint32 dbid, uint32 eqid, uint8 rank, const char *what, const char *value); void command_guildapprove(Client *c, const Seperator *sep); void command_guildcreate(Client *c, const Seperator *sep); void command_guildlist(Client *c, const Seperator *sep); @@ -153,7 +134,6 @@ void command_interrupt(Client *c, const Seperator *sep); void command_invsnapshot(Client *c, const Seperator *sep); void command_invul(Client *c, const Seperator *sep); void command_ipban(Client *c, const Seperator *sep); -void command_ipc(Client *c, const Seperator *sep); void command_iplookup(Client *c, const Seperator *sep); void command_iteminfo(Client *c, const Seperator *sep); void command_itemsearch(Client *c, const Seperator *sep); @@ -162,17 +142,14 @@ void command_killallnpcs(Client *c, const Seperator *sep); void command_kill(Client *c, const Seperator *sep); void command_lastname(Client *c, const Seperator *sep); void command_level(Client *c, const Seperator *sep); -void command_listnpcs(Client *c, const Seperator *sep); void command_list(Client *c, const Seperator *sep); void command_listpetition(Client *c, const Seperator *sep); void command_load_shared_memory(Client *c, const Seperator *sep); void command_loc(Client *c, const Seperator *sep); void command_lock(Client *c, const Seperator *sep); void command_logs(Client *c, const Seperator *sep); -void command_logtest(Client *c, const Seperator *sep); void command_makepet(Client *c, const Seperator *sep); void command_mana(Client *c, const Seperator *sep); -void command_manastat(Client *c, const Seperator *sep); void command_max_all_skills(Client *c, const Seperator *sep); void command_memspell(Client *c, const Seperator *sep); void command_merchantcloseshop(Client *c, const Seperator *sep); @@ -183,7 +160,6 @@ void command_movechar(Client *c, const Seperator *sep); void command_movement(Client *c, const Seperator *sep); void command_myskills(Client *c, const Seperator *sep); void command_mysql(Client *c, const Seperator *sep); -void command_mysqltest(Client *c, const Seperator *sep); void command_mystats(Client *c, const Seperator *sep); void command_name(Client *c, const Seperator *sep); void command_netstats(Client *c, const Seperator *sep); @@ -200,14 +176,14 @@ void command_npcspecialattk(Client *c, const Seperator *sep); void command_npcstats(Client *c, const Seperator *sep); void command_npctype_cache(Client *c, const Seperator *sep); void command_npctypespawn(Client *c, const Seperator *sep); -void command_nudge(Client* c, const Seperator* sep); +void command_nudge(Client *c, const Seperator *sep); void command_nukebuffs(Client *c, const Seperator *sep); void command_nukeitem(Client *c, const Seperator *sep); -void command_numauths(Client *c, const Seperator *sep); -void command_object(Client* c, const Seperator *sep); +void command_object(Client *c, const Seperator *sep); void command_oocmute(Client *c, const Seperator *sep); void command_opcode(Client *c, const Seperator *sep); -void command_optest(Client *c, const Seperator *sep); +void command_bestz(Client *c, const Seperator *message); +void command_pf(Client *c, const Seperator *message); #ifdef PACKET_PROFILER void command_packetprofile(Client *c, const Seperator *sep); @@ -219,6 +195,7 @@ void command_peqzone(Client *c, const Seperator *sep); void command_permaclass(Client *c, const Seperator *sep); void command_permagender(Client *c, const Seperator *sep); void command_permarace(Client *c, const Seperator *sep); +void command_petitems(Client *c, const Seperator *sep); void command_petitioninfo(Client *c, const Seperator *sep); void command_picklock(Client *c, const Seperator *sep); void command_profanity(Client *c, const Seperator *sep); @@ -232,29 +209,30 @@ void command_proximity(Client *c, const Seperator *sep); void command_push(Client *c, const Seperator *sep); void command_pvp(Client *c, const Seperator *sep); void command_qglobal(Client *c, const Seperator *sep); -void command_qtest(Client *c, const Seperator *sep); void command_questerrors(Client *c, const Seperator *sep); void command_race(Client *c, const Seperator *sep); -void command_raidloot(Client* c, const Seperator *sep); +void command_raidloot(Client *c, const Seperator *sep); void command_randomfeatures(Client *c, const Seperator *sep); void command_refreshgroup(Client *c, const Seperator *sep); -void command_refundaa(Client *c, const Seperator *sep); void command_reloadaa(Client *c, const Seperator *sep); void command_reloadallrules(Client *c, const Seperator *sep); -void command_reloademote(Client* c, const Seperator *sep); +void command_reloadcontentflags(Client *c, const Seperator *sep); +void command_reloademote(Client *c, const Seperator *sep); void command_reloadlevelmods(Client *c, const Seperator *sep); void command_reloadmerchants(Client *c, const Seperator *sep); void command_reloadperlexportsettings(Client *c, const Seperator *sep); void command_reloadqst(Client *c, const Seperator *sep); void command_reloadstatic(Client *c, const Seperator *sep); void command_reloadtitles(Client *c, const Seperator *sep); -void command_reloadtraps(Client* c, const Seperator *sep); +void command_reloadtraps(Client *c, const Seperator *sep); void command_reloadworld(Client *c, const Seperator *sep); void command_reloadworldrules(Client *c, const Seperator *sep); void command_reloadzps(Client *c, const Seperator *sep); +void command_removeitem(Client *c, const Seperator *sep); void command_repop(Client *c, const Seperator *sep); -void command_resetaa(Client* c,const Seperator *sep); +void command_resetaa(Client *c, const Seperator *sep); void command_resetaa_timer(Client *c, const Seperator *sep); +void command_resetdisc_timer(Client *c, const Seperator *sep); void command_revoke(Client *c, const Seperator *sep); void command_roambox(Client *c, const Seperator *sep); void command_rules(Client *c, const Seperator *sep); @@ -262,21 +240,23 @@ void command_save(Client *c, const Seperator *sep); void command_scale(Client *c, const Seperator *sep); void command_scribespell(Client *c, const Seperator *sep); void command_scribespells(Client *c, const Seperator *sep); -void command_sendop(Client *c, const Seperator *sep); void command_sendzonespawns(Client *c, const Seperator *sep); void command_sensetrap(Client *c, const Seperator *sep); void command_serverinfo(Client *c, const Seperator *sep); void command_serverrules(Client *c, const Seperator *sep); -void command_serversidename(Client *c, const Seperator *sep); void command_set_adventure_points(Client *c, const Seperator *sep); void command_setaapts(Client *c, const Seperator *sep); void command_setaaxp(Client *c, const Seperator *sep); +void command_setaltcurrency(Client *c, const Seperator *sep); void command_setanim(Client *c, const Seperator *sep); void command_setcrystals(Client *c, const Seperator *sep); +void command_setendurance(Client *c, const Seperator *sep); void command_setfaction(Client *c, const Seperator *sep); void command_setgraveyard(Client *c, const Seperator *sep); +void command_sethp(Client *c, const Seperator *sep); void command_setlanguage(Client *c, const Seperator *sep); void command_setlsinfo(Client *c, const Seperator *sep); +void command_setmana(Client *c, const Seperator *sep); void command_setpass(Client *c, const Seperator *sep); void command_setpvppoints(Client *c, const Seperator *sep); void command_setskill(Client *c, const Seperator *sep); @@ -301,21 +281,15 @@ void command_spawneditmass(Client *c, const Seperator *sep); void command_spawnfix(Client *c, const Seperator *sep); void command_spawnstatus(Client *c, const Seperator *sep); void command_spellinfo(Client *c, const Seperator *sep); -void command_spoff(Client *c, const Seperator *sep); -void command_spon(Client *c, const Seperator *sep); void command_stun(Client *c, const Seperator *sep); void command_summon(Client *c, const Seperator *sep); void command_summonburiedplayercorpse(Client *c, const Seperator *sep); void command_summonitem(Client *c, const Seperator *sep); void command_suspend(Client *c, const Seperator *sep); -void command_synctod(Client *c, const Seperator *sep); void command_task(Client *c, const Seperator *sep); void command_tattoo(Client *c, const Seperator *sep); void command_tempname(Client *c, const Seperator *sep); void command_petname(Client *c, const Seperator *sep); -void command_test(Client *c, const Seperator *sep); -void command_testspawn(Client *c, const Seperator *sep); -void command_testspawnkill(Client *c, const Seperator *sep); void command_texture(Client *c, const Seperator *sep); void command_time(Client *c, const Seperator *sep); void command_timers(Client *c, const Seperator *sep); @@ -323,21 +297,25 @@ void command_timezone(Client *c, const Seperator *sep); void command_title(Client *c, const Seperator *sep); void command_titlesuffix(Client *c, const Seperator *sep); void command_traindisc(Client *c, const Seperator *sep); -void command_trapinfo(Client* c, const Seperator *sep); +void command_trapinfo(Client *c, const Seperator *sep); void command_tune(Client *c, const Seperator *sep); void command_ucs(Client *c, const Seperator *sep); void command_undye(Client *c, const Seperator *sep); void command_undyeme(Client *c, const Seperator *sep); void command_unfreeze(Client *c, const Seperator *sep); void command_unlock(Client *c, const Seperator *sep); +void command_unmemspell(Client *c, const Seperator *sep); +void command_unmemspells(Client *c, const Seperator *sep); void command_unscribespell(Client *c, const Seperator *sep); void command_unscribespells(Client *c, const Seperator *sep); void command_untraindisc(Client *c, const Seperator *sep); void command_untraindiscs(Client *c, const Seperator *sep); void command_uptime(Client *c, const Seperator *sep); void command_version(Client *c, const Seperator *sep); +void command_viewcurrencies(Client *c, const Seperator *sep); void command_viewnpctype(Client *c, const Seperator *sep); void command_viewpetition(Client *c, const Seperator *sep); +void command_viewzoneloot(Client *c, const Seperator *sep); void command_wc(Client *c, const Seperator *sep); void command_weather(Client *c, const Seperator *sep); void command_who(Client *c, const Seperator *sep); @@ -355,7 +333,6 @@ void command_zone_instance(Client *c, const Seperator *sep); void command_zonebootup(Client *c, const Seperator *sep); void command_zonelock(Client *c, const Seperator *sep); void command_zoneshutdown(Client *c, const Seperator *sep); -void command_zonespawn(Client *c, const Seperator *sep); void command_zonestatus(Client *c, const Seperator *sep); void command_zopp(Client *c, const Seperator *sep); void command_zsafecoords(Client *c, const Seperator *sep); @@ -363,7 +340,6 @@ void command_zsave(Client *c, const Seperator *sep); void command_zsky(Client *c, const Seperator *sep); void command_zstats(Client *c, const Seperator *sep); void command_zunderworld(Client *c, const Seperator *sep); -void command_zuwcoords(Client *c, const Seperator *sep); #ifdef BOTS #include "bot.h" diff --git a/zone/common.h b/zone/common.h index fd513bdde..31299d3b4 100644 --- a/zone/common.h +++ b/zone/common.h @@ -22,8 +22,6 @@ #define SEE_POSITION 0.5f //ratio of GetSize() where NPCs try to see for LOS #define CHECK_LOS_STEP 1.0f -#define MAX_SHIELDERS 2 //I dont know if this is based on a client limit - #define ARCHETYPE_HYBRID 1 #define ARCHETYPE_CASTER 2 #define ARCHETYPE_MELEE 3 @@ -106,45 +104,56 @@ #define PET_BUTTON_SPELLHOLD 9 #define AURA_HARDCAP 2 +#define WEAPON_STANCE_TYPE_MAX 2 + + +#define SHIELD_ABILITY_RECAST_TIME 180 typedef enum { //focus types - focusSpellHaste = 1, - focusSpellDuration, - focusRange, - focusReagentCost, - focusManaCost, - focusImprovedHeal, - focusImprovedDamage, - focusImprovedDamage2, - focusImprovedDOT, //i dont know about this... - focusFcDamagePctCrit, - focusImprovedUndeadDamage, - focusPetPower, - focusResistRate, - focusSpellHateMod, - focusTriggerOnCast, - focusSpellVulnerability, - focusTwincast, - focusSympatheticProc, - focusFcDamageAmt, - focusFcDamageAmt2, - focusFcDamageAmtCrit, - focusSpellDurByTic, - focusSwarmPetDuration, - focusReduceRecastTime, - focusBlockNextSpell, - focusFcHealPctIncoming, - focusFcDamageAmtIncoming, - focusFcHealAmtIncoming, - focusFcBaseEffects, - focusIncreaseNumHits, - focusFcLimitUse, - focusFcMute, - focusFcTimerRefresh, - focusFcStunTimeMod, - focusFcHealPctCritIncoming, - focusFcHealAmt, - focusFcHealAmtCrit, + focusSpellHaste = 1, //@Fc, SPA: 127, SE_IncreaseSpellHaste, On Caster, cast time mod pct, base: pct + focusSpellDuration, //@Fc, SPA: 128, SE_IncreaseSpellDuration, On Caster, spell duration mod pct, base: pct + focusRange, //@Fc, SPA: 129, SE_IncreaseRange, On Caster, spell range mod pct, base: pct + focusReagentCost, //@Fc, SPA: 131, SE_ReduceReagentCost, On Caster, do not consume reagent pct chance, base: min pct, limit: max pct + focusManaCost, //@Fc, SPA: 132, SE_ReduceManaCost, On Caster, reduce mana cost by pct, base: min pct, limt: max pct + focusImprovedHeal, //@Fc, SPA: 125, SE_ImprovedHeal, On Caster, spell healing mod pct, base: min pct, limit: max pct + focusImprovedDamage, //@Fc, SPA: 124, SE_ImprovedDamage, On Caster, spell damage mod pct, base: min pct, limit: max pct + focusImprovedDamage2, //@Fc, SPA: 461, SE_ImprovedDamage2, On Caster, spell damage mod pct, base: min pct, limit: max pct + focusFcDamagePctCrit, //@Fc, SPA: 302, SE_FcDamagePctCrit, On Caster, spell damage mod pct, base: min pct, limit: max pct + focusPetPower, //@Fc, SPA: 167, SE_PetPowerIncrease, On Caster, pet power mod, base: value + focusResistRate, //@Fc, SPA: 126, SE_SpellResistReduction, On Caster, casted spell resist mod pct, base: min pct, limit: max pct + focusSpellHateMod, //@Fc, SPA: 130, SE_SpellHateMod, On Caster, spell hate mod pct, base: min pct, limit: max pct + focusTriggerOnCast, //@Fc, SPA: 339, SE_TriggerOnCast, On Caster, cast on spell use, base: chance pct limit: spellid + focusSpellVulnerability, //@Fc, SPA: 296, SE_FcSpellVulnerability, On Target, spell damage taken mod pct, base: min pct, limit: max pct + focusFcSpellDamagePctIncomingPC, //@Fc, SPA: 483, SE_Fc_Spell_Damage_Pct_IncomingPC, On Target, spell damage taken mod pct, base: min pct, limit: max pct + focusTwincast, //@Fc, SPA: 399, SE_FcTwincast, On Caster, chance cast spell twice, base: chance pct + focusSympatheticProc, //@Fc, SPA: 383, SE_SympatheticProc, On Caster, cast on spell use, base: variable proc chance on cast time, limit: spellid + focusFcDamageAmt, //@Fc, SPA: 286, SE_FcDamageAmt, On Caster, spell damage mod flat amt, base: amt + focusFcDamageAmt2, //@Fc, SPA: 462, SE_FcDamageAmt2, On Caster, spell damage mod flat amt, base: amt + focusFcDamageAmtCrit, //@Fc, SPA: 303, SE_FFcDamageAmtCrit, On Caster, spell damage mod flat amt, base: amt + focusSpellDurByTic, //@Fc, SPA: 287, SE_SpellDurationIncByTic, On Caster, spell buff duration mod, base: tics + focusSwarmPetDuration, //@Fc, SPA: 398, SE_SwarmPetDuration, On Caster, swarm pet duration mod, base: milliseconds + focusReduceRecastTime, //@Fc, SPA: 310, SE_ReduceReuseTimer, On Caster, disc reuse time mod, base: milliseconds + focusBlockNextSpell, //@Fc, SPA: 335, SE_BlockNextSpellFocus, On Caster, chance to block next spell, base: chance + focusFcHealPctIncoming, //@Fc, SPA: 393, SE_FcHealPctIncoming, On Target, heal received mod pct, base: pct + focusFcDamageAmtIncoming, //@Fc, SPA: 297, SE_FcDamageAmtIncoming, On Target, damage taken flat amt, base: amt + focusFcSpellDamageAmtIncomingPC, //@Fc, SPA: 484, SE_Fc_Spell_Damage_Amt_IncomingPC, On Target, damage taken flat amt, base: amt + focusFcCastSpellOnLand, //@Fc, SPA: 481, SE_Fc_Cast_Spell_On_Land, On Target, cast spell if hit by spell, base: chance pct, limit: spellid + focusFcHealAmtIncoming, //@Fc, SPA: 394, SE_FcHealAmtIncoming, On Target, heal received mod flat amt, base: amt + focusFcBaseEffects, //@Fc, SPA: 413, SE_FcBaseEffects, On Caster, base spell effectiveness mod pct, base: pct + focusIncreaseNumHits, //@Fc, SPA: 421, SE_FcIncreaseNumHits, On Caster, numhits mod flat amt, base: amt + focusFcLimitUse, //@Fc, SPA: 420, SE_FcLimitUse, On Caster, numhits mod pct, base: pct + focusFcMute, //@Fc, SPA: 357, SE_FcMute, On Caster, prevents spell casting, base: chance pct + focusFcTimerRefresh, //@Fc, SPA: 389, SE_FcTimerRefresh, On Caster, reset spell recast timer, base: 1 + focusFcTimerLockout, //@Fc, SPA: 390, SE_FcTimerLockout, On Caster, set a spell to be on recast timer, base: recast duration milliseconds + focusFcStunTimeMod, //@Fc, SPA: 133, SE_FcStunTimeMod, On Caster, stun time mod pct, base: chance pct + focusFcResistIncoming, //@Fc, SPA: 510, SE_Fc_Resist_Incoming, On Target, resist modifier, base: amt + focusFcAmplifyMod, //@Fc, SPA: 507, SE_Fc_Amplify_Mod, On Caster, damage-heal-dot mod pct, base: pct + focusFcAmplifyAmt, //@Fc, SPA: 508, SE_Fc_Amplify_Amt, On Caster, damage-heal-dot mod flat amt, base: amt + focusFcCastTimeMod2, //@Fc, SPA: 500, SE_Fc_CastTimeMod2, On Caster, cast time mod pct, base: pct + focusFcCastTimeAmt, //@Fc, SPA: 501, SE_Fc_CastTimeAmt, On Caster, cast time mod flat amt, base: milliseconds + focusFcHealPctCritIncoming, //@Fc, SPA: 395, SE_FcHealPctCritIncoming, On Target, spell healing mod pct, base: pct + focusFcHealAmt, //@Fc, SPA: 392, SE_FcHealAmt, On Caster, spell healing mod flat amt, base: amt + focusFcHealAmtCrit, //@Fc, SPA: 396, SE_FcHealAmtCrit, On Caster, spell healing mod flat amt, base: amt } focusType; //Any new FocusType needs to be added to the Mob::IsFocus function #define HIGHEST_FOCUS focusFcHealAmtCrit //Should always be last focusType in enum @@ -192,14 +201,16 @@ enum { ALLOW_TO_TANK = 41, IGNORE_ROOT_AGGRO_RULES = 42, CASTING_RESIST_DIFF = 43, - COUNTER_AVOID_DAMAGE = 44, + COUNTER_AVOID_DAMAGE = 44, //Modify by percent NPC's opponents chance to riposte, block, parry or dodge individually, or for all skills PROX_AGGRO = 45, IMMUNE_RANGED_ATTACKS = 46, IMMUNE_DAMAGE_CLIENT = 47, IMMUNE_DAMAGE_NPC = 48, IMMUNE_AGGRO_CLIENT = 49, IMMUNE_AGGRO_NPC = 50, - MAX_SPECIAL_ATTACK = 51 + MODIFY_AVOID_DAMAGE = 51, //Modify by percent the NPCs chance to riposte, block, parry or dodge individually, or for all skills + IMMUNE_FADING_MEMORIES = 52, + MAX_SPECIAL_ATTACK = 53 }; typedef enum { //fear states @@ -309,7 +320,7 @@ struct Buffs_Struct { char caster_name[64]; int32 ticsremaining; uint32 counters; - uint32 numhits; //the number of physical hits this buff can take before it fades away, lots of druid armor spells take advantage of this mixed with powerful effects + uint32 hit_number; //the number of physical hits this buff can take before it fades away, lots of druid armor spells take advantage of this mixed with powerful effects uint32 melee_rune; uint32 magic_rune; uint32 dot_rune; @@ -319,6 +330,7 @@ struct Buffs_Struct { int32 ExtraDIChance; int16 RootBreakChance; //Not saved to dbase uint32 instrument_mod; + int32 virus_spread_time; //time till next attempted viral spread bool persistant_buff; bool client; //True if the caster is a client bool UpdateClient; @@ -393,7 +405,7 @@ struct StatBonuses { int32 skillmodmax[EQ::skills::HIGHEST_SKILL + 1]; int effective_casting_level; int adjusted_casting_skill; // SPA 112 for fizzles - int reflect_chance; // chance to reflect incoming spell + int reflect[3]; // chance to reflect incoming spell [0]=Chance [1]=Resist Mod [2]= % of Base Dmg uint32 singingMod; uint32 Amplification; // stacks with singingMod uint32 brassMod; @@ -437,10 +449,14 @@ struct StatBonuses { int32 HitChanceEffect[EQ::skills::HIGHEST_SKILL + 2]; //Spell effect Chance to Hit, straight percent increase int32 DamageModifier[EQ::skills::HIGHEST_SKILL + 2]; //i int32 DamageModifier2[EQ::skills::HIGHEST_SKILL + 2]; //i + int32 DamageModifier3[EQ::skills::HIGHEST_SKILL + 2]; //i int32 MinDamageModifier[EQ::skills::HIGHEST_SKILL + 2]; //i int32 ProcChance; // ProcChance/10 == % increase i = CombatEffects int32 ProcChanceSPA; // ProcChance from spell effects - int32 ExtraAttackChance; + int32 ExtraAttackChance[2]; // base chance(w/ 2H weapon)=0, amt of extra attacks=1 + int32 ExtraAttackChancePrimary[2]; // base chance=0, , amt of extra attacks=1 + int32 ExtraAttackChanceSecondary[2]; // base chance=0, , amt of extra attacks=1 + int32 DoubleMeleeRound[2]; // base chance=0, damage mod=1 int32 DoTShielding; int32 DivineSaveChance[2]; // Second Chance (base1 = chance, base2 = spell on trigger) uint32 DeathSave[4]; // Death Pact [0](value = 1 partial 2 = full) [1]=slot [2]=LvLimit [3]=HealAmt @@ -461,6 +477,7 @@ struct StatBonuses { uint32 SpellOnKill[MAX_SPELL_TRIGGER*3]; // Chance to proc after killing a mob uint32 SpellOnDeath[MAX_SPELL_TRIGGER*2]; // Chance to have effect cast when you die int32 CritDmgMod[EQ::skills::HIGHEST_SKILL + 2]; // All Skills + -1 + int32 CritDmgModNoStack[EQ::skills::HIGHEST_SKILL + 2];// Critical melee damage modifier by percent, does not stack. int32 SkillReuseTime[EQ::skills::HIGHEST_SKILL + 1]; // Reduces skill timers int32 SkillDamageAmount[EQ::skills::HIGHEST_SKILL + 2]; // All Skills + -1 int32 TwoHandBluntBlock; // chance to block when wielding two hand blunt weapon @@ -474,15 +491,13 @@ struct StatBonuses { int HPPercCap[2]; //Spell effect that limits you to being healed/regening beyond a % of your max int ManaPercCap[2]; // ^^ 0 = % Cap 1 = Flat Amount Cap int EndPercCap[2]; // ^^ - bool BlockNextSpell; // Indicates whether the client can block a spell or not - //uint16 BlockSpellEffect[EFFECT_COUNT]; // Prevents spells with certain effects from landing on you *no longer used bool ImmuneToFlee; // Bypass the fleeing flag uint32 VoiceGraft; // Stores the ID of the mob with which to talk through int32 SpellProcChance; // chance to proc from sympathetic spell effects int32 CharmBreakChance; // chance to break charm int32 SongRange; // increases range of beneficial bard songs uint32 HPToManaConvert; // Uses HP to cast spells at specific conversion - uint8 FocusEffects[HIGHEST_FOCUS+1]; // Stores the focus effectid for each focustype you have. + int32 FocusEffects[HIGHEST_FOCUS+1]; // Stores the focus effectid for each focustype you have. int16 FocusEffectsWorn[HIGHEST_FOCUS+1]; // Optional to allow focus effects to be applied additively from worn slot bool NegateEffects; // Check if you contain a buff with negate effect. (only spellbonuses) int32 SkillDamageAmount2[EQ::skills::HIGHEST_SKILL + 2]; // Adds skill specific damage @@ -494,7 +509,8 @@ struct StatBonuses { uint32 MitigateDotRune[4]; // 0 = Mitigation value 1 = Buff Slot 2 = Max mitigation per tick 3 = Rune Amt bool TriggerMeleeThreshold; // Has Melee Threshhold bool TriggerSpellThreshold; // Has Spell Threshhold - uint32 ManaAbsorbPercentDamage[2]; // 0 = Mitigation value 1 = Buff Slot + uint32 ManaAbsorbPercentDamage; // 0 = Mitigation value + int32 EnduranceAbsorbPercentDamage[2]; // 0 = Mitigation value 1 = Percent Endurance drain per HP lost int32 ShieldBlock; // Chance to Shield Block int32 BlockBehind; // Chance to Block Behind (with our without shield) bool CriticalRegenDecay; // increase critical regen chance, decays based on spell level cast @@ -502,7 +518,7 @@ struct StatBonuses { bool CriticalDotDecay; // increase critical dot chance, decays based on spell level cast bool DivineAura; // invulnerability bool DistanceRemoval; // Check if Cancle if Moved effect is present - int32 ImprovedTaunt[3]; // 0 = Max Level 1 = Aggro modifier 2 = buffid + int32 ImprovedTaunt[3]; // 0 = Max Level 1 = Aggro modifier 2 = buff slot int8 Root[2]; // The lowest buff slot a root can be found. [0] = Bool if has root [1] = buff slot int32 FrenziedDevastation; // base1= AArank(used) base2= chance increase spell criticals + all DD spells 2x mana. uint32 AbsorbMagicAtt[2]; // 0 = magic rune value 1 = buff slot @@ -518,20 +534,47 @@ struct StatBonuses { int32 Metabolism; // Food/drink consumption rates. bool Sanctuary; // Sanctuary effect, lowers place on hate list until cast on others. int32 FactionModPct; // Modifies amount of faction gained. - bool LimitToSkill[EQ::skills::HIGHEST_SKILL + 2]; // Determines if we need to search for a skill proc. + bool LimitToSkill[EQ::skills::HIGHEST_SKILL + 3]; // Determines if we need to search for a skill proc. uint32 SkillProc[MAX_SKILL_PROCS]; // Max number of spells containing skill_procs. uint32 SkillProcSuccess[MAX_SKILL_PROCS]; // Max number of spells containing skill_procs_success. + int32 SpellProc[MAX_AA_PROCS]; // Max number of spells containing melee spell procs. + int32 RangedProc[MAX_AA_PROCS]; // Max number of spells containing ranged spell procs. + int32 DefensiveProc[MAX_AA_PROCS]; // Max number of spells containing defensive spell procs. + bool Proc_Timer_Modifier; // Used to check if this exists, to avoid any further unnncessary checks. uint32 PC_Pet_Rampage[2]; // 0= % chance to rampage, 1=damage modifier + uint32 PC_Pet_AE_Rampage[2]; // 0= % chance to AE rampage, 1=damage modifier uint32 PC_Pet_Flurry; // Percent chance flurry from double attack + int32 Attack_Accuracy_Max_Percent; // Increase ATK accuracy by percent. + int32 AC_Mitigation_Max_Percent; // Increase AC mitigation by percent + int32 AC_Avoidance_Max_Percent; // Increase AC avoidance by percent + int32 Damage_Taken_Position_Mod[2]; // base = percent melee damage reduction base2 0=back 1=front. [0]Back[1]Front + int32 Melee_Damage_Position_Mod[2]; // base = percent melee damage increase base2 0=back 1=front. [0]Back[1]Front + int32 Damage_Taken_Position_Amt[2]; // base = flat amt melee damage reduction base2 0=back 1=front. [0]Back[1]Front + int32 Melee_Damage_Position_Amt[2]; // base = flat amt melee damage increase base2 0=back 1=front. [0]Back[1]Front + int32 Double_Backstab_Front; // base = percent chance to double back stab front + int32 DS_Mitigation_Amount; // base = flat amt DS mitigation. Negative value to reduce + int32 DS_Mitigation_Percentage; // base = percent amt of DS mitigation. Negative value to reduce + int32 Pet_Crit_Melee_Damage_Pct_Owner; // base = percent mod for pet critcal damage from owner + int32 Pet_Add_Atk; // base = Pet ATK bonus from owner + int32 ItemEnduranceRegenCap; // modify endurance regen cap + int32 WeaponStance[WEAPON_STANCE_TYPE_MAX +1];// base = trigger spell id, base2 = 0 is 2h, 1 is shield, 2 is dual wield, [0]spid 2h, [1]spid shield, [2]spid DW + bool ZoneSuspendMinion; // base 1 allows suspended minions to zone + bool CompleteHealBuffBlocker; // Use in SPA 101 to prevent recast of complete heal from this effect till blocker buff is removed. + int32 Illusion; // illusion spell id // AAs - int8 Packrat; //weight reduction for items, 1 point = 10% + int32 TrapCircumvention; // reduce chance to trigger a trap. + uint16 SecondaryForte; // allow a second skill to be specialized with a cap of this value. + int32 ShieldDuration; // extends duration of /shield ability + int32 ExtendedShielding; // extends range of /shield ability + int8 Packrat; // weight reduction for items, 1 point = 10% uint8 BuffSlotIncrease; // Increases number of available buff slots uint32 DelayDeath; // how far below 0 hp you can go int8 BaseMovementSpeed; // Adjust base run speed, does not stack with other movement bonuses. uint8 IncreaseRunSpeedCap; // Increase max run speed above cap. int32 DoubleSpecialAttack; // Chance to to perform a double special attack (ie flying kick 2x) int32 SkillAttackProc[3]; // [0] chance to proc [2] spell on [1] skill usage + bool HasSkillAttackProc[EQ::skills::HIGHEST_SKILL + 1]; //check if any skill proc is present before assessing for all skill procs uint8 FrontalStunResist; // Chance to resist a frontal stun int32 BindWound; // Increase amount of HP by percent. int32 MaxBindWound; // Increase max amount of HP you can bind wound. @@ -566,9 +609,9 @@ struct StatBonuses { int32 OffhandRiposteFail; // chance for opponent to fail riposte with offhand attack. int32 ItemATKCap; // Raise item attack cap int32 FinishingBlow[2]; // Chance to do a finishing blow for specified damage amount. - uint32 FinishingBlowLvl[2]; // Sets max level an NPC can be affected by FB. (base1 = lv, base2= ???) + uint32 FinishingBlowLvl[2]; // Sets max level an NPC can be affected by FB. (base1 = lv, base2= hit point ratio) int32 ShieldEquipDmgMod; // Increases weapon's base damage by base1 % when shield is equipped (indirectly increasing hate) - bool TriggerOnValueAmount; // Triggers off various different conditions, bool to check if client has effect. + bool TriggerOnCastRequirement; // Triggers off various different conditions defined as emum SpellRestrictions int8 StunBashChance; // chance to stun with bash. int8 IncreaseChanceMemwipe; // increases chance to memory wipe int8 CriticalMend; // chance critical monk mend @@ -578,7 +621,7 @@ struct StatBonuses { uint32 Assassinate[2]; // Assassinate AA (Massive dmg vs humaniod w/ assassinate) 0= ? 1= Dmg uint8 AssassinateLevel[2]; // Max Level Assassinate will be effective at. int32 PetMeleeMitigation; // Add AC to owner's pet. - bool IllusionPersistence; // Causes illusions not to fade. + int IllusionPersistence; // 1=Causes illusions not to fade when zoning 2=Allow to persist after death. uint16 extra_xtargets; // extra xtarget entries bool ShroudofStealth; // rogue improved invisiblity uint16 ReduceFallDamage; // reduce fall damage by percent @@ -594,19 +637,92 @@ struct StatBonuses { bool hunger; // Song of Sustenance -- min caps to 3500 }; +// StatBonus Indexes +namespace SBIndex { + constexpr uint16 BUFFSTACKER_EXISTS = 0; // SPA 446-449 + constexpr uint16 BUFFSTACKER_VALUE = 1; // SPA 446-449 + constexpr uint16 EXTRA_ATTACK_CHANCE = 0; // SPA 266,498,499 + constexpr uint16 EXTRA_ATTACK_NUM_ATKS = 1; // SPA 266,498,499 + constexpr uint16 DIVINE_SAVE_CHANCE = 0; // SPA 232 + constexpr uint16 DIVINE_SAVE_SPELL_TRIGGER_ID = 1; // SPA 232 + constexpr uint16 DEATH_SAVE_TYPE = 0; // SPA 150 + constexpr uint16 DEATH_SAVE_BUFFSLOT = 1; // SPA 150 + constexpr uint16 DEATH_SAVE_MIN_LEVEL_FOR_HEAL = 2; // SPA 150 + constexpr uint16 DEATH_SAVE_HEAL_AMT = 3; // SPA 150 + constexpr uint16 RESOURCE_PERCENT_CAP = 0; // SPA 408-410 + constexpr uint16 RESOURCE_AMOUNT_CAP = 1; // SPA 408-419 + constexpr uint16 NEGATE_ATK_EXISTS = 0; // SPA 163 + constexpr uint16 NEGATE_ATK_BUFFSLOT = 1; // SPA 163 + constexpr uint16 NEGATE_ATK_MAX_DMG_ABSORB_PER_HIT = 2; // SPA 163 + constexpr uint16 MITIGATION_RUNE_PERCENT = 0; // SPA 161,162,450 + constexpr uint16 MITIGATION_RUNE_BUFFSLOT = 1; // SPA 161,162,450 + constexpr uint16 MITIGATION_RUNE_MAX_DMG_ABSORB_PER_HIT = 2; // SPA 161,162,450 + constexpr uint16 MITIGATION_RUNE_MAX_HP_AMT = 3; // SPA 161,162,450 + constexpr uint16 THRESHOLDGUARD_MITIGATION_PERCENT = 0; // SPA 451,452 + constexpr uint16 THRESHOLDGUARD_BUFFSLOT = 1; // SPA 451,452 + constexpr uint16 THRESHOLDGUARD_MIN_DMG_TO_TRIGGER = 2; // SPA 451,452 + constexpr uint16 ENDURANCE_ABSORD_MITIGIATION = 0; // SPA 521 + constexpr uint16 ENDURANCE_ABSORD_DRAIN_PER_HP = 1; // SPA 521 + constexpr uint16 IMPROVED_TAUNT_MAX_LVL = 0; // SPA 444 + constexpr uint16 IMPROVED_TAUNT_AGGRO_MOD = 1; // SPA 444 + constexpr uint16 IMPROVED_TAUNT_BUFFSLOT = 2; // SPA 444 + constexpr uint16 ROOT_EXISTS = 0; // SPA 99 + constexpr uint16 ROOT_BUFFSLOT = 1; // SPA 99 + constexpr uint16 RUNE_AMOUNT = 0; // SPA 55 + constexpr uint16 RUNE_BUFFSLOT = 1; // SPA 78 + constexpr uint16 POSITION_BACK = 0; // SPA 503-506 + constexpr uint16 POSITION_FRONT = 1; // SPA 503-506 + constexpr uint16 PET_RAMPAGE_CHANCE = 0; // SPA 464,465 + constexpr uint16 PET_RAMPAGE_DMG_MOD = 1; // SPA 465,465 + constexpr uint16 SKILLATK_PROC_SPELL_ID = 0; // SPA 288 + constexpr uint16 SKILLATK_PROC_CHANCE = 1; // SPA 288 + constexpr uint16 SKILLATK_PROC_SKILL = 2; // SPA 288 + constexpr uint16 SLAYUNDEAD_RATE_MOD = 0; // SPA 219 + constexpr uint16 SLAYUNDEAD_DMG_MOD = 1; // SPA 219 + constexpr uint16 DOUBLE_RIPOSTE_CHANCE = 0; // SPA 223 + constexpr uint16 DOUBLE_RIPOSTE_SKILL_ATK_CHANCE = 1; // SPA 223 + constexpr uint16 DOUBLE_RIPOSTE_SKILL = 2; // SPA 223 + constexpr uint16 FINISHING_EFFECT_PROC_CHANCE = 0; // SPA 278, 439, 217 + constexpr uint16 FINISHING_EFFECT_DMG = 1; // SPA 278, 439, 217 + constexpr uint16 FINISHING_EFFECT_LEVEL_MAX = 0; // SPA 440, 345, 346 + constexpr uint16 FINISHING_EFFECT_LEVEL_CHANCE_BONUS = 1; // SPA 345, 346 + constexpr uint16 FINISHING_BLOW_LEVEL_HP_RATIO = 1; // SPA 440 + constexpr uint16 DOUBLE_MELEE_ROUND_CHANCE = 0; // SPA 471 + constexpr uint16 DOUBLE_MELEE_ROUND_DMG_BONUS = 1; // SPA 471 + constexpr uint16 REFLECT_CHANCE = 0; // SPA 158 + constexpr uint16 REFLECT_RESISTANCE_MOD = 1; // SPA 158 + constexpr uint16 REFLECT_DMG_EFFECTIVENESS = 2; // SPA 158 + constexpr uint16 COMBAT_PROC_ORIGIN_ID = 0; // SPA + constexpr uint16 COMBAT_PROC_SPELL_ID = 1; // SPA + constexpr uint16 COMBAT_PROC_RATE_MOD = 2; // SPA + constexpr uint16 COMBAT_PROC_REUSE_TIMER = 3; // SPA +}; + + typedef struct { - uint16 spellID; + int32 spellID; uint16 chance; - uint16 base_spellID; + int32 base_spellID; int level_override; + uint32 proc_reuse_time; } tProc; -struct Shielders_Struct { - uint32 shielder_id; - uint16 shielder_bonus; + +struct WeaponStance_Struct { + bool enabled; + bool spellbonus_enabled; + bool itembonus_enabled; + bool aabonus_enabled; + int spellbonus_buff_spell_id; + int itembonus_buff_spell_id; + int aabonus_buff_spell_id; }; +constexpr uint16 WEAPON_STANCE_TYPE_2H = 0; +constexpr uint16 WEAPON_STANCE_TYPE_SHIELD = 1; +constexpr uint16 WEAPON_STANCE_TYPE_DUAL_WIELD = 2; + typedef struct { uint16 increment; @@ -623,6 +739,7 @@ typedef struct int ammo_slot; uint8 skill; float speed_mod; + bool disable_procs; } tProjatk; //eventually turn this into a typedef and diff --git a/zone/corpse.cpp b/zone/corpse.cpp index 5113b6fdf..bfedf47d1 100644 --- a/zone/corpse.cpp +++ b/zone/corpse.cpp @@ -791,6 +791,45 @@ void Corpse::RemoveItem(ServerLootItem_Struct* item_data) } } +void Corpse::RemoveItemByID(uint32 item_id, int quantity) { + if (!database.GetItem(item_id)) { + return; + } + + if (!HasItem(item_id)) { + return; + } + + int removed_count = 0; + for (auto current_item = itemlist.begin(); current_item != itemlist.end(); ++current_item) { + ServerLootItem_Struct* sitem = *current_item; + if (removed_count == quantity) { + break; + } + + if (sitem && sitem->item_id == item_id) { + int stack_size = sitem->charges > 1 ? sitem->charges : 1; + if ((removed_count + stack_size) <= quantity) { + removed_count += stack_size; + is_corpse_changed = true; + itemlist.erase(current_item); + } else { + int amount_left = (quantity - removed_count); + if (amount_left > 0) { + if (stack_size > amount_left) { + removed_count += amount_left; + sitem->charges -= amount_left; + is_corpse_changed = true; + } else if (stack_size == amount_left) { + removed_count += amount_left; + itemlist.erase(current_item); + } + } + } + } + } +} + void Corpse::SetCash(uint32 in_copper, uint32 in_silver, uint32 in_gold, uint32 in_platinum) { this->copper = in_copper; this->silver = in_silver; @@ -914,7 +953,7 @@ void Corpse::MakeLootRequestPackets(Client* client, const EQApplicationPacket* a // return; } - if(is_locked && client->Admin() < 100) { + if(is_locked && client->Admin() < AccountStatus::GMAdmin) { SendLootReqErrorPacket(client, LootResponse::SomeoneElse); client->Message(Chat::Red, "Error: Corpse locked by GM."); return; @@ -938,7 +977,7 @@ void Corpse::MakeLootRequestPackets(Client* client, const EQApplicationPacket* a // loot_request_type is scoped to class Corpse and reset on a per-loot session basis if (client->GetGM()) { - if (client->Admin() >= 100) + if (client->Admin() >= AccountStatus::GMAdmin) loot_request_type = LootRequestType::GMAllowed; else loot_request_type = LootRequestType::GMPeek; @@ -1164,14 +1203,14 @@ void Corpse::LootItem(Client *client, const EQApplicationPacket *app) } if (IsPlayerCorpse() && !CanPlayerLoot(client->CharacterID()) && !become_npc && - (char_id != client->CharacterID() && client->Admin() < 150)) { + (char_id != client->CharacterID() && client->Admin() < AccountStatus::GMLeadAdmin)) { client->Message(Chat::Red, "Error: This is a player corpse and you dont own it."); client->QueuePacket(app); SendEndLootErrorPacket(client); return; } - if (is_locked && client->Admin() < 100) { + if (is_locked && client->Admin() < AccountStatus::GMAdmin) { client->QueuePacket(app); SendLootReqErrorPacket(client, LootResponse::SomeoneElse); client->Message(Chat::Red, "Error: Corpse locked by GM."); @@ -1244,40 +1283,54 @@ void Corpse::LootItem(Client *client, const EQApplicationPacket *app) } } - char buf[88]; - char q_corpse_name[64]; - strcpy(q_corpse_name, corpse_name); - snprintf(buf, 87, "%d %d %s", inst->GetItem()->ID, inst->GetCharges(), - EntityList::RemoveNumbers(q_corpse_name)); - buf[87] = '\0'; + std::string export_string = fmt::format( + "{} {} {} {}", + inst->GetItem()->ID, + inst->GetCharges(), + EntityList::RemoveNumbers(corpse_name), + GetID() + ); std::vector args; args.push_back(inst); args.push_back(this); - if (parse->EventPlayer(EVENT_LOOT, client, buf, 0, &args) != 0) { - lootitem->auto_loot = -1; - client->MessageString(Chat::Red, LOOT_NOT_ALLOWED, inst->GetItem()->Name); - client->QueuePacket(app); - delete inst; - return; + bool prevent_loot = false; + if (RuleB(Zone, UseZoneController)) { + auto controller = entity_list.GetNPCByNPCTypeID(ZONE_CONTROLLER_NPC_ID); + if (controller){ + if (parse->EventNPC(EVENT_LOOT_ZONE, controller, client, export_string, 0, &args) != 0) { + prevent_loot = true; + } + } + } + + if (parse->EventPlayer(EVENT_LOOT, client, export_string, 0, &args) != 0) { + prevent_loot = true; } - if (zone && zone->GetInstanceID() != 0) + if (!IsPlayerCorpse()) { - // expeditions may prevent looting based on client's lockouts - auto expedition = Expedition::FindCachedExpeditionByZoneInstance(zone->GetZoneID(), zone->GetInstanceID()); - if (expedition && !expedition->CanClientLootCorpse(client, GetNPCTypeID(), GetID())) + // dynamic zones may prevent looting by non-members or based on lockouts + auto dz = zone->GetDynamicZone(); + if (dz && !dz->CanClientLootCorpse(client, GetNPCTypeID(), GetID())) { - client->MessageString(Chat::Red, LOOT_NOT_ALLOWED, inst->GetItem()->Name); - client->QueuePacket(app); - SendEndLootErrorPacket(client); - ResetLooter(); - delete inst; - return; + prevent_loot = true; + // note on live this message is only sent once on the first loot attempt of an open corpse + client->MessageString(Chat::Loot, LOOT_NOT_ALLOWED, inst->GetItem()->Name); } } - // do we want this to have a fail option too? - parse->EventItem(EVENT_LOOT, client, inst, this, buf, 0); + // do we want this to have a fail option too? Sure? + if (parse->EventItem(EVENT_LOOT, client, inst, this, export_string, 0) != 0) { + prevent_loot = true; + } + + if (prevent_loot) { + lootitem->auto_loot = -1; + client->QueuePacket(app); + safe_delete(inst); + return; + } + // safe to ACK now client->QueuePacket(app); @@ -1307,7 +1360,7 @@ void Corpse::LootItem(Client *client, const EQApplicationPacket *app) /* Update any tasks that have an activity to loot this item */ if (RuleB(TaskSystem, EnableTaskSystem)) - client->UpdateTasksForItem(ActivityLoot, item->ID); + client->UpdateTasksForItem(TaskActivityType::Loot, item->ID); /* Remove it from Corpse */ if (item_data) { @@ -1403,62 +1456,151 @@ void Corpse::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) { } void Corpse::QueryLoot(Client* to) { - int x = 0, y = 0; // x = visible items, y = total items - to->Message(Chat::White, "Coin: %ip, %ig, %is, %ic", platinum, gold, silver, copper); + if (itemlist.size() > 0) { + int player_corpse_limit = to->GetInv().GetLookup()->InventoryTypeSize.Corpse; + to->Message( + Chat::White, + fmt::format( + "Loot | Name: {} ID: {}", + GetName(), + GetNPCTypeID() + ).c_str() + ); - ItemList::iterator cur,end; - cur = itemlist.begin(); - end = itemlist.end(); + int item_count = 0; + for (auto current_item : itemlist) { + int item_number = (item_count + 1); + if (!current_item) { + LogError("Corpse::QueryLoot() - ItemList error, null item."); + continue; + } - int corpselootlimit = to->GetInv().GetLookup()->InventoryTypeSize.Corpse; + if (!current_item->item_id || !database.GetItem(current_item->item_id)) { + LogError("Corpse::QueryLoot() - Database error, invalid item."); + continue; + } - for(; cur != end; ++cur) { - ServerLootItem_Struct* sitem = *cur; + EQ::SayLinkEngine linker; + linker.SetLinkType(EQ::saylink::SayLinkLootItem); + linker.SetLootData(current_item); - if (IsPlayerCorpse()) { - if (sitem->equip_slot >= EQ::invbag::GENERAL_BAGS_BEGIN && sitem->equip_slot <= EQ::invbag::CURSOR_BAG_END) - sitem->lootslot = 0xFFFF; - else - x < corpselootlimit ? sitem->lootslot = x : sitem->lootslot = 0xFFFF; - - const EQ::ItemData* item = database.GetItem(sitem->item_id); - - if (item) - to->Message((sitem->lootslot == 0xFFFF), "LootSlot: %i (EquipSlot: %i) Item: %s (%d), Count: %i", static_cast(sitem->lootslot), sitem->equip_slot, item->Name, item->ID, sitem->charges); - else - to->Message((sitem->lootslot == 0xFFFF), "Error: 0x%04x", sitem->item_id); - - if (sitem->lootslot != 0xFFFF) - x++; - - y++; - } - else { - sitem->lootslot=y; - const EQ::ItemData* item = database.GetItem(sitem->item_id); - - if (item) - to->Message(Chat::White, "LootSlot: %i Item: %s (%d), Count: %i", sitem->lootslot, item->Name, item->ID, sitem->charges); - else - to->Message(Chat::White, "Error: 0x%04x", sitem->item_id); - - y++; + to->Message( + Chat::White, + fmt::format( + "Item {} | Name: {} ({}){}", + item_number, + linker.GenerateLink().c_str(), + current_item->item_id, + ( + current_item->charges > 1 ? + fmt::format( + " Amount: {}", + current_item->charges + ) : + "" + ) + ).c_str() + ); + item_count++; } } - if (IsPlayerCorpse()) { - to->Message(Chat::White, "%i visible %s (%i total) on %s (DBID: %i).", x, x==1?"item":"items", y, this->GetName(), this->GetCorpseDBID()); + bool has_money = ( + platinum > 0 || + gold > 0 || + silver > 0 || + copper > 0 + ); + if (has_money) { + to->Message( + Chat::White, + fmt::format( + "Money | {}", + ConvertMoneyToString( + platinum, + gold, + silver, + copper + ) + ).c_str() + ); } - else { - to->Message(Chat::White, "%i %s on %s.", y, y==1?"item":"items", this->GetName()); +} + +bool Corpse::HasItem(uint32 item_id) { + if (!database.GetItem(item_id)) { + return false; } + + for (auto current_item = itemlist.begin(); current_item != itemlist.end(); ++current_item) { + ServerLootItem_Struct* loot_item = *current_item; + if (!loot_item) { + LogError("Corpse::HasItem() - ItemList error, null item"); + continue; + } + + if (!loot_item->item_id || !database.GetItem(loot_item->item_id)) { + LogError("Corpse::HasItem() - Database error, invalid item"); + continue; + } + + if (loot_item->item_id == item_id) { + return true; + } + } + return false; +} + +uint16 Corpse::CountItem(uint32 item_id) { + uint16 item_count = 0; + if (!database.GetItem(item_id)) { + return item_count; + } + + for (auto current_item = itemlist.begin(); current_item != itemlist.end(); ++current_item) { + ServerLootItem_Struct* loot_item = *current_item; + if (!loot_item) { + LogError("Corpse::CountItem() - ItemList error, null item"); + continue; + } + + if (!loot_item->item_id || !database.GetItem(loot_item->item_id)) { + LogError("Corpse::CountItem() - Database error, invalid item"); + continue; + } + + if (loot_item->item_id == item_id) { + item_count += loot_item->charges > 0 ? loot_item->charges : 1; + } + } + return item_count; +} + +uint32 Corpse::GetItemIDBySlot(uint16 loot_slot) { + for (auto current_item = itemlist.begin(); current_item != itemlist.end(); ++current_item) { + ServerLootItem_Struct* loot_item = *current_item; + if (loot_item->lootslot == loot_slot) { + return loot_item->item_id; + } + } + return 0; +} + +uint16 Corpse::GetFirstSlotByItemID(uint32 item_id) { + for (auto current_item = itemlist.begin(); current_item != itemlist.end(); ++current_item) { + ServerLootItem_Struct* loot_item = *current_item; + if (loot_item->item_id == item_id) { + return loot_item->lootslot; + } + } + return 0; } bool Corpse::Summon(Client* client, bool spell, bool CheckDistance) { uint32 dist2 = 10000; // pow(100, 2); if (!spell) { if (this->GetCharID() == client->CharacterID()) { - if (IsLocked() && client->Admin() < 100) { + if (IsLocked() && client->Admin() < AccountStatus::GMAdmin) { client->Message(Chat::Red, "That corpse is locked by a GM."); return false; } @@ -1678,3 +1820,21 @@ bool Corpse::MovePlayerCorpseToNonInstance() return false; } + +std::vector Corpse::GetLootList() { + std::vector corpse_items; + for (auto current_item = itemlist.begin(); current_item != itemlist.end(); ++current_item) { + ServerLootItem_Struct* loot_item = *current_item; + if (!loot_item) { + LogError("Corpse::GetLootList() - ItemList error, null item"); + continue; + } + + if (std::find(corpse_items.begin(), corpse_items.end(), loot_item->item_id) != corpse_items.end()) { + continue; + } + + corpse_items.push_back(loot_item->item_id); + } + return corpse_items; +} diff --git a/zone/corpse.h b/zone/corpse.h index cd564e80d..a395c1956 100644 --- a/zone/corpse.h +++ b/zone/corpse.h @@ -95,6 +95,7 @@ class Corpse : public Mob { int32 GetPlayerKillItem() { return player_kill_item; } void RemoveItem(uint16 lootslot); void RemoveItem(ServerLootItem_Struct* item_data); + void RemoveItemByID(uint32 item_id, int quantity = 1); void AddItem(uint32 itemnum, uint16 charges, int16 slot = 0, uint32 aug1 = 0, uint32 aug2 = 0, uint32 aug3 = 0, uint32 aug4 = 0, uint32 aug5 = 0, uint32 aug6 = 0, uint8 attuned = 0); /* Corpse: Coin */ @@ -113,6 +114,11 @@ class Corpse : public Mob { /* Corpse: Loot */ void QueryLoot(Client* to); + bool HasItem(uint32 item_id); + uint16 CountItem(uint32 item_id); + uint32 GetItemIDBySlot(uint16 loot_slot); + uint16 GetFirstSlotByItemID(uint32 item_id); + std::vector GetLootList(); void LootItem(Client* client, const EQApplicationPacket* app); void EndLoot(Client* client, const EQApplicationPacket* app); void MakeLootRequestPackets(Client* client, const EQApplicationPacket* app); diff --git a/zone/dialogue_window.cpp b/zone/dialogue_window.cpp new file mode 100644 index 000000000..d719f72a8 --- /dev/null +++ b/zone/dialogue_window.cpp @@ -0,0 +1,505 @@ +#include "dialogue_window.h" + +void DialogueWindow::Render(Client *c, std::string markdown) +{ + std::string output = markdown; + + if (!c->ClientDataLoaded()) { + return; + } + + // this is the NPC that the client is interacting with if there is dialogue going on + Mob *target = c->GetTarget() ? c->GetTarget() : c; + + // zero this out + c->SetEntityVariable(DIAWIND_RESPONSE_ONE_KEY.c_str(), ""); + c->SetEntityVariable(DIAWIND_RESPONSE_TWO_KEY.c_str(), ""); + + // simple find and replace for the markdown + find_replace(output, "~", "
"); + find_replace(output, "{y}", ""); + find_replace(output, "{lb}", ""); + find_replace(output, "{r}", ""); + find_replace(output, "{g}", ""); + find_replace(output, "{gold}", ""); + find_replace(output, "{orange}", ""); + find_replace(output, "{gray}", ""); + find_replace(output, "{tan}", ""); + find_replace(output, "{bullet}", "•"); + find_replace(output, "{name}", fmt::format("{}", c->GetCleanName())); + find_replace(output, "{linebreak}", "--------------------------------------------------------------------"); + find_replace(output, "{rowpad}", R"({tdpad}<"td>{tdpad}<"td><"tr>)"); + find_replace(output, "{tdpad}", "----------------------"); + find_replace(output, "{in}", "        "); + + // mysterious voice + bool render_mysterious_voice = false; + if (markdown.find("{mysterious}") != std::string::npos) { + render_mysterious_voice = true; + LogDiaWind("Client [{}] Rendering mysterious voice", c->GetCleanName()); + find_replace(output, "{mysterious}", ""); + } + + // noquotes + bool render_noquotes = false; + if (markdown.find("noquotes") != std::string::npos) { + render_noquotes = true; + LogDiaWind("Client [{}] Rendering noquotes", c->GetCleanName()); + find_replace(output, "noquotes", ""); + } + + // nobracket + bool render_nobracket = false; + if (markdown.find("nobracket") != std::string::npos) { + render_nobracket = true; + LogDiaWind("Client [{}] Rendering nobracket", c->GetCleanName()); + find_replace(output, "nobracket", ""); + } + + bool render_hiddenresponse = false; + if (markdown.find("hiddenresponse") != std::string::npos) { + render_hiddenresponse = true; + LogDiaWind("Client [{}] Rendering hiddenresponse", c->GetCleanName()); + find_replace(output, "hiddenresponse", ""); + } + + // animations + std::string animation = get_between(output, "+", "+"); + if (!animation.empty()) { + LogDiaWind("Client [{}] Animation is not empty, contents are [{}]", c->GetCleanName(), animation); + find_replace(output, fmt::format("+{}+", animation), ""); + + // we treat the animation field differently if it is a number + if (StringIsNumber(animation)) { + LogDiaWindDetail("Client [{}] Animation is a number, firing animation [{}]", c->GetCleanName(), animation); + target->DoAnim(std::stoi(animation)); + } + else { + for (auto &a: animations) { + if (a.first.find(str_tolower(animation)) != std::string::npos) { + LogDiaWindDetail( + "Client [{}] Animation is a string, firing animation [{}] [{}]", + c->GetCleanName(), + a.second, + a.first + ); + target->DoAnim(a.second); + } + } + } + } + + if (animation.empty() && RuleB(Chat, DialogueWindowAnimatesNPCsIfNoneSet)) { + std::vector greet_animations = { + 29, // wave + 48, // nodyes + 64, // point + 67, // salute + 69, // tapfoot + 70, // bowto + }; + + int random_animation = rand() % (greet_animations.size() - 1) + 0; + + target->DoAnim(greet_animations[random_animation]); + } + + // window expire time + std::string expire_time = get_between(output, "=", "="); + uint32 window_expire_seconds = 0; + if (!expire_time.empty()) { + LogDiaWind("Client [{}] Window expire time is not empty, contents are [{}]", c->GetCleanName(), expire_time); + find_replace(output, fmt::format("={}=", expire_time), ""); + + // we treat the animation field differently if it is a number + if (StringIsNumber(expire_time)) { + LogDiaWindDetail( + "Client [{}] Window expire time is a number, setting expiration to [{}]", + c->GetCleanName(), + expire_time + ); + window_expire_seconds = std::stoi(expire_time); + } + } + + uint32 popup_id = POPUPID_DIAWIND_ONE; + uint32 negative_id = POPUPID_DIAWIND_TWO; + std::string button_one_name; + std::string button_two_name; + uint32 sound_controls = 0; + + // window type + std::string wintype; + if (markdown.find("wintype:") != std::string::npos) { + LogDiaWind("Client [{}] Rendering wintype option", c->GetCleanName()); + + auto first_split = split_string(output, "wintype:"); + if (!first_split.empty()) { + + // assumed that there is more after the wintype declaration + // wintype:0 +animation+ etc. + auto second_split = split_string(first_split[1], " "); + if (!second_split.empty()) { + wintype = second_split[0]; + LogDiaWindDetail("Client [{}] Rendering wintype option wintype [{}]", c->GetCleanName(), wintype); + } + + // if we're dealing with a string that is at the end + // example wintype:0"); + if (first_split[1].length() == 1) { + wintype = first_split[1]; + LogDiaWindDetail( + "Client [{}] Rendering wintype (end) option wintype [{}]", + c->GetCleanName(), + wintype + ); + } + + find_replace(output, fmt::format("wintype:{}", wintype), ""); + } + } + + // popupid + std::string popupid; + if (markdown.find("popupid:") != std::string::npos) { + LogDiaWind("Client [{}] Rendering popupid option", c->GetCleanName()); + + auto first_split = split_string(output, "popupid:"); + if (!first_split.empty()) { + + // assumed that there is more after the popupid declaration + // popupid:0 +animation+ etc. + auto second_split = split_string(first_split[1], " "); + if (!second_split.empty()) { + popupid = second_split[0]; + LogDiaWindDetail("Client [{}] Rendering popupid option popupid [{}]", c->GetCleanName(), popupid); + } + + // if we're dealing with a string that is at the end + // example popupid:0"); + if (first_split[1].length() == 1) { + popupid = first_split[1]; + LogDiaWindDetail( + "Client [{}] Rendering popupid (end) option popupid [{}]", + c->GetCleanName(), + popupid + ); + } + + find_replace(output, fmt::format("popupid:{}", popupid), ""); + + // set the popup id + if (!popupid.empty()) { + popup_id = (StringIsNumber(popupid) ? std::atoi(popupid.c_str()) : 0); + } + } + } + + // secondresponseid + std::string secondresponseid; + if (markdown.find("secondresponseid:") != std::string::npos) { + LogDiaWind("Client [{}] Rendering secondresponseid option", c->GetCleanName()); + + auto first_split = split_string(output, "secondresponseid:"); + if (!first_split.empty()) { + auto second_split = split_string(first_split[1], " "); + if (!second_split.empty()) { + secondresponseid = second_split[0]; + LogDiaWindDetail("Client [{}] Rendering secondresponseid option secondresponseid [{}]", + c->GetCleanName(), + secondresponseid); + } + + if (first_split[1].length() == 1) { + secondresponseid = first_split[1]; + LogDiaWindDetail( + "Client [{}] Rendering secondresponseid (end) option secondresponseid [{}]", + c->GetCleanName(), + secondresponseid + ); + } + + find_replace(output, fmt::format("secondresponseid:{}", secondresponseid), ""); + + if (!secondresponseid.empty()) { + negative_id = (StringIsNumber(secondresponseid) ? std::atoi(secondresponseid.c_str()) : 0); + } + } + } + + // Buttons Text + std::string button_one; + std::string button_two; + if ( + markdown.find("{button_one:") != std::string::npos && + markdown.find("{button_two:") != std::string::npos + ) { + + LogDiaWind("Client [{}] Rendering button_one option.", c->GetCleanName()); + + button_one = get_between(output, "{button_one:", "}"); + LogDiaWind("Client [{}] button_one [{}]", c->GetCleanName(), button_one); + + if (!button_one.empty()) { + find_replace(output, fmt::format("{{button_one:{}}}", button_one), ""); + button_one_name = trim(button_one); + } + + button_two = get_between(output, "{button_two:", "}"); + LogDiaWind("Client [{}] button_two [{}]", c->GetCleanName(), button_two); + + if (!button_two.empty()) { + find_replace(output, fmt::format("{{button_two:{}}}", button_two), ""); + button_two_name = trim(button_two); + } + + LogDiaWind( + "Client [{}] Rendering buttons button_one [{}] button_two [{}]", + c->GetCleanName(), + button_one, + button_two + ); + } + + // bracket responses + std::vector responses; + std::vector bracket_responses; + if (markdown.find('[') != std::string::npos && markdown.find(']') != std::string::npos) { + + // record any saylinks that may be in saylink form + std::string strip_saylinks = output; + std::map replacements = {}; + while (strip_saylinks.find('[') != std::string::npos && strip_saylinks.find(']') != std::string::npos) { + std::string bracket_message = get_between(strip_saylinks, "[", "]"); + + // strip saylinks and normalize to [regular message] + size_t link_open = bracket_message.find('\x12'); + size_t link_close = bracket_message.find_last_of('\x12'); + if (link_open != link_close && (bracket_message.length() - link_open) > EQ::constants::SAY_LINK_BODY_SIZE) { + replacements.insert( + std::pair( + bracket_message, + bracket_message.substr(EQ::constants::SAY_LINK_BODY_SIZE + 1) + ) + ); + } + + find_replace(strip_saylinks, fmt::format("[{}]", bracket_message), ""); + } + + // write replacement strips + for (auto &replacement: replacements) { + find_replace(output, replacement.first, replacement.second.substr(0, replacement.second.size() - 1)); + } + + // copy + std::string content = output; + + // while brackets still exist + int response_index = 0; + while (content.find('[') != std::string::npos && content.find(']') != std::string::npos) { + std::string bracket_message = get_between(content, "[", "]"); + + LogDiaWindDetail( + "Client [{}] Rendering responses ({}) [{}]", + c->GetCleanName(), + response_index, + bracket_message + ); + + // pop message onto responses + responses.emplace_back(bracket_message); + + // pop the response off of the message + find_replace(content, fmt::format("[{}]", bracket_message), ""); + + // too many iterations / safety net + if (response_index > 100) { + break; + } + + response_index++; + } + } + + // build saylinks + if (responses.size() > 1) { + for (auto &r: responses) { + bracket_responses.emplace_back( + fmt::format("[{}]", EQ::SayLinkEngine::GenerateQuestSaylink(r, false, r)) + ); + } + } + + // Placed here to allow silent message or other message to override default for custom values. + if (!button_one_name.empty() && !button_two_name.empty()) { + c->SetEntityVariable( + DIAWIND_RESPONSE_ONE_KEY.c_str(), + button_one_name.c_str() + ); + c->SetEntityVariable( + DIAWIND_RESPONSE_TWO_KEY.c_str(), + button_two_name.c_str() + ); + } + + // handle silent prompts from the [> silent syntax + std::string silent_message; + if (responses.empty() && markdown.find('[') != std::string::npos && markdown.find('>') != std::string::npos) { + silent_message = get_between(output, "[", ">"); + + // temporary and used during the response + c->SetEntityVariable(DIAWIND_RESPONSE_ONE_KEY.c_str(), silent_message.c_str()); + + // pop the silent message off + find_replace(output, fmt::format("[{}>", silent_message), ""); + } + else if (!responses.empty()) { + // handle silent prompts from the single respond bracket syntax [] + silent_message = responses[0]; + + // temporary and used during the response + c->SetEntityVariable(DIAWIND_RESPONSE_ONE_KEY.c_str(), silent_message.c_str()); + + // pop the silent message off + find_replace(output, fmt::format("[{}]", silent_message), ""); + } + + // strip brackets + if (render_nobracket) { + find_replace(output, "[", ""); + find_replace(output, "]", ""); + } + + // render title + std::string title; + std::string speaking; + if (target) { + speaking = fmt::format("{} says", target->GetCleanName()); + } + + if (render_mysterious_voice) { + speaking = "A Mysterious Voice says"; + } + + // title + std::string popup_title; + if (markdown.find("{title:") != std::string::npos) { + popup_title = get_between(output, "{title:", "}"); + + LogDiaWind("Client [{}] Rendering title option title [{}]", c->GetCleanName(), popup_title); + + if (!popup_title.empty()) { + find_replace(output, fmt::format("{{title:{}}}", popup_title), ""); + title = trim(popup_title); + } + } + + if (title.empty()) { + title = fmt::format("Dialogue [{}]", speaking); + } + + // render quotes + std::string quote_string = "'"; + if (render_noquotes) { + quote_string = ""; + } + + // click response + // window type response + uint32 window_type = (StringIsNumber(wintype) ? std::atoi(wintype.c_str()) : 0); + std::string click_response_button = (window_type == 1 ? "Yes" : "OK"); + std::string click_response = fmt::format( + "Click [{}] to continue...", + click_response_button + ); + + // different response when a timer is set + if (window_expire_seconds > 0) { + click_response = fmt::format( + "This message will disappear in {} second(s)...", + window_expire_seconds + ); + } + + // respond with silent message + if (!silent_message.empty()) { + click_response = fmt::format( + "Click [{}] to respond with [{}]...", + click_response_button, + silent_message + ); + } + + if (!button_one_name.empty() && !button_two_name.empty()) { + click_response = fmt::format( + "Click [{}] to respond with [{}]...
" + "Click [{}] to respond with [{}]...
", + button_one_name, + button_one_name, + button_two_name, + button_two_name + ); + } + + // post processing of color markdowns + // {spring_green_1} = + if (markdown.find('{') != std::string::npos && markdown.find('}') != std::string::npos) { + + // while brackets still exist + int tag_index = 0; + while (output.find('{') != std::string::npos && output.find('}') != std::string::npos) { + std::string color_tag = get_between(output, "{", "}"); + + LogDiaWindDetail( + "Client [{}] Rendering color tags ({}) [{}]", + c->GetCleanName(), + tag_index, + color_tag + ); + + std::string html_tag; + for (const auto &color : html_colors) { + if (color_tag.find(color.first) != std::string::npos) { + // build html tag + html_tag = fmt::format("", color.second); + // pop the response off of the message + find_replace(output, fmt::format("{{{}}}", color.first), html_tag); + } + } + + // too many iterations / safety net + if (tag_index > 100) { + break; + } + + tag_index++; + } + } + + // build the final output string + std::string final_output; + final_output = fmt::format("{}{}{}

{}", quote_string, trim(output), quote_string, click_response); + if (render_hiddenresponse) { + final_output = fmt::format("{}{}{}", quote_string, trim(output), quote_string); + } + + // send popup + c->SendFullPopup( + title.c_str(), + final_output.c_str(), + popup_id, + negative_id, + window_type, + window_expire_seconds, + button_one_name.c_str(), + button_two_name.c_str(), + sound_controls + ); + + // if multiple brackets are presented, send message + if (!bracket_responses.empty()) { + c->Message(Chat::White, " --- Select Response from Options --- "); + c->Message(Chat::White, implode(" ", bracket_responses).c_str()); + } +} diff --git a/zone/dialogue_window.h b/zone/dialogue_window.h new file mode 100644 index 000000000..7b0137550 --- /dev/null +++ b/zone/dialogue_window.h @@ -0,0 +1,394 @@ +#ifndef EQEMU_DIALOGUE_WINDOW_H +#define EQEMU_DIALOGUE_WINDOW_H + + +#include +#include "client.h" + +static const std::map html_colors = { + {"black", "#000000"}, + {"brown", "#804000"}, + {"burgundy", "#800000"}, + {"cadet_blue", "#77BFC7"}, + {"cadet_blue_1", "#4C787E"}, + {"chartreuse", "#8AFB17"}, + {"chartreuse_1", "#7FE817"}, + {"chartreuse_2", "#6CC417"}, + {"chartreuse_3", "#437C17"}, + {"chocolate", "#C85A17"}, + {"coral", "#F76541"}, + {"coral_1", "#E55B3C"}, + {"coral_2", "#C34A2C"}, + {"cornflower_blue", "#151B8D"}, + {"cyan", "#00FFFF"}, + {"cyan_1", "#57FEFF"}, + {"cyan_2", "#50EBEC"}, + {"cyan_3", "#46C7C7"}, + {"cyan_4", "#307D7E"}, + {"dark_blue", "#0000A0"}, + {"dark_goldenrod", "#AF7817"}, + {"dark_goldenrod_1", "#FBB117"}, + {"dark_goldenrod_2", "#E8A317"}, + {"dark_goldenrod_3", "#C58917"}, + {"dark_goldenrod_4", "#7F5217"}, + {"dark_green", "#254117"}, + {"dark_grey", "#808080"}, + {"dark_olive_green", "#CCFB5D"}, + {"dark_olive_green_2", "#BCE954"}, + {"dark_olive_green_3", "#A0C544"}, + {"dark_olive_green_4", "#667C26"}, + {"dark_orange", "#F88017"}, + {"dark_orange_1", "#F87217"}, + {"dark_orange_2", "#E56717"}, + {"dark_orange_3", "#7E3117"}, + {"dark_orange_3", "#C35617"}, + {"dark_orchid", "#7D1B7E"}, + {"dark_orchid_1", "#B041FF"}, + {"dark_orchid_2", "#A23BEC"}, + {"dark_orchid_3", "#8B31C7"}, + {"dark_orchid_4", "#571B7e"}, + {"dark_purple", "#800080"}, + {"dark_salmon", "#E18B6B"}, + {"dark_sea_green", "#8BB381"}, + {"dark_sea_green_1", "#C3FDB8"}, + {"dark_sea_green_2", "#B5EAAA"}, + {"dark_sea_green_3", "#99C68E"}, + {"dark_sea_green_4", "#617C58"}, + {"dark_slate_blue", "#2B3856"}, + {"dark_slate_gray", "#25383C"}, + {"dark_slate_gray_1", "#9AFEFF"}, + {"dark_slate_gray_2", "#8EEBEC"}, + {"dark_slate_gray_3", "#78c7c7"}, + {"dark_slate_gray_4", "#4C7D7E"}, + {"dark_turquoise", "#3B9C9C"}, + {"dark_violet", "#842DCE"}, + {"deep_pink", "#F52887"}, + {"deep_pink_1", "#E4287C"}, + {"deep_pink_2", "#C12267"}, + {"deep_pink_3", "#7D053F"}, + {"deep_sky_blue", "#3BB9FF"}, + {"deep_sky_blue_1", "#38ACEC"}, + {"deep_sky_blue_2", "#3090C7"}, + {"deep_sky_blue_3", "#25587E"}, + {"dim_gray", "#463E41"}, + {"dodger_blue", "#1589FF"}, + {"dodger_blue_1", "#157DEC"}, + {"dodger_blue_2", "#1569C7"}, + {"dodger_blue_3", "#153E7E"}, + {"firebrick", "#800517"}, + {"firebrick_1", "#F62817"}, + {"firebrick_2", "#E42217"}, + {"firebrick_3", "#C11B17"}, + {"forest_green", "#4E9258"}, + {"forest_green_1", "#808000"}, + {"gold", "#D4A017"}, + {"gold_1", "#FDD017"}, + {"gold_2", "#EAC117"}, + {"gold_3", "#C7A317"}, + {"gold_4", "#806517"}, + {"goldenrod", "#EDDA74"}, + {"goldenrod_1", "#FBB917"}, + {"goldenrod_2", "#E9AB17"}, + {"goldenrod_3", "#C68E17"}, + {"goldenrod_4", "#805817"}, + {"grass_green", "#408080"}, + {"gray", "#736F6E"}, + {"gray_1", "#150517"}, + {"gray_2", "#250517"}, + {"gray_3", "#2B1B17"}, + {"gray_4", "#302217"}, + {"gray_5", "#302226"}, + {"gray_6", "#342826"}, + {"gray_7", "#34282C"}, + {"gray_8", "#382D2C"}, + {"gray_9", "#3b3131"}, + {"gray_10", "#3E3535"}, + {"gray_11", "#413839"}, + {"gray_12", "#41383C"}, + {"gray_13", "#463E3F"}, + {"gray_14", "#4A4344"}, + {"gray_15", "#4C4646"}, + {"gray_16", "#4E4848"}, + {"gray_17", "#504A4B"}, + {"gray_18", "#544E4F"}, + {"gray_19", "#565051"}, + {"gray_19", "#595454"}, + {"gray_20", "#5C5858"}, + {"gray_21", "#5F5A59"}, + {"gray_22", "#625D5D"}, + {"gray_23", "#646060"}, + {"gray_24", "#666362"}, + {"gray_25", "#696565"}, + {"gray_26", "#6D6968"}, + {"gray_27", "#6E6A6B"}, + {"gray_28", "#726E6D"}, + {"gray_29", "#747170"}, + {"green", "#00FF00"}, + {"green_1", "#5FFB17"}, + {"green_2", "#59E817"}, + {"green_3", "#4CC417"}, + {"green_4", "#347C17"}, + {"green_yellow", "#B1FB17"}, + {"hot_pink", "#F660AB"}, + {"hot_pink_1", "#F665AB"}, + {"hot_pink_2", "#E45E9D"}, + {"hot_pink_3", "#C25283"}, + {"hot_pink_4", "#7D2252"}, + {"indian_red", "#F75D59"}, + {"indian_red_2", "#E55451"}, + {"indian_red_3", "#C24641"}, + {"indian_red_4", "#7E2217"}, + {"khaki", "#ADA96E"}, + {"khaki_1", "#FFF380"}, + {"khaki_2", "#EDE275"}, + {"khaki_3", "#C9BE62"}, + {"khaki_4", "#827839"}, + {"lavender", "#E3E4FA"}, + {"lavender_blush", "#FDEEF4"}, + {"lavender_blush_1", "#EBDDE2"}, + {"lavender_blush_2", "#C8BBBE"}, + {"lavender_blush_3", "#817679"}, + {"lawn_green", "#87F717"}, + {"lemon_chiffon", "#FFF8C6"}, + {"lemon_chiffon_1", "#ECE5B6"}, + {"lemon_chiffon_2", "#C9C299"}, + {"lemon_chiffon_3", "#827B60"}, + {"light_blue", "#0000FF"}, + {"light_blue_1", "#ADDFFF"}, + {"light_blue_2", "#BDEDFF"}, + {"light_blue_3", "#AFDCEC"}, + {"light_blue_4", "#95B9C7"}, + {"light_blue_5", "#5E767E"}, + {"light_coral", "#E77471"}, + {"light_cyan", "#E0FFFF"}, + {"light_cyan_1", "#CFECEC"}, + {"light_cyan_2", "#AFC7C7"}, + {"light_cyan_3", "#717D7D"}, + {"light_golden", "#ECD672"}, + {"light_goldenrod", "#ECD872"}, + {"light_goldenrod_1", "#FFE87C"}, + {"light_goldenrod_2", "#C8B560"}, + {"light_goldenrod_3", "#817339"}, + {"light_goldenrod_yellow", "#FAF8CC"}, + {"light_grey", "#C0C0C0"}, + {"light_pink", "#FAAFBA"}, + {"light_pink_1", "#F9A7B0"}, + {"light_pink_2", "#E799A3"}, + {"light_pink_3", "#C48189"}, + {"light_pink_4", "#7F4E52"}, + {"light_purple", "#FF0080"}, + {"light_salmon", "#F9966B"}, + {"light_salmon_1", "#E78A61"}, + {"light_salmon_2", "#C47451"}, + {"light_salmon_3", "#7F462C"}, + {"light_sea_green", "#3EA99F"}, + {"light_sky_blue", "#82CAFA"}, + {"light_sky_blue_1", "#A0CFEC"}, + {"light_sky_blue_2", "#87AFC7"}, + {"light_sky_blue_3", "#566D7E"}, + {"light_slate_blue", "#736AFF"}, + {"light_slate_gray", "#6D7B8D"}, + {"light_steel_blue", "#728FCE"}, + {"light_steel_blue_1", "#C6DEFF"}, + {"light_steel_blue_2", "#B7CEEC"}, + {"light_steel_blue_3", "#646D7E"}, + {"lime_green", "#41A317"}, + {"magenta", "#FF00FF"}, + {"magenta_1", "#F433FF"}, + {"magenta_2", "#E238EC"}, + {"magenta_3", "#C031C7"}, + {"maroon", "#810541"}, + {"maroon_1", "#F535AA"}, + {"maroon_2", "#E3319D"}, + {"maroon_3", "#C12283"}, + {"maroon_4", "#7D0552"}, + {"medium_aquamarine", "#348781"}, + {"medium_forest_green", "#347235"}, + {"medium_orchid", "#B048B5"}, + {"medium_orchid_1", "#D462FF"}, + {"medium_orchid_2", "#C45AEC"}, + {"medium_orchid_3", "#A74AC7"}, + {"medium_orchid_4", "#6A287E"}, + {"medium_purple", "#8467D7"}, + {"medium_purple_1", "#9E7BFF"}, + {"medium_purple_2", "#9172EC"}, + {"medium_purple_3", "#7A5DC7"}, + {"medium_purple_4", "#4E387E"}, + {"medium_sea_green", "#306754"}, + {"medium_slate_blue", "#5E5A80"}, + {"medium_spring_green", "#348017"}, + {"medium_turquoise", "#48CCCD"}, + {"medium_violet_red", "#CA226B"}, + {"midnight_blue", "#151B54"}, + {"orange", "#FF8040"}, + {"pale_turquoise", "#92C7C7"}, + {"pale_turquoise_1", "#5E7D7E"}, + {"pale_violet_red", "#D16587"}, + {"pale_violet_red_1", "#F778A1"}, + {"pale_violet_red_2", "#E56E94"}, + {"pale_violet_red_3", "#C25A7C"}, + {"pale_violet_red_4", "#7E354D"}, + {"pastel_green", "#00FF00"}, + {"pink", "#FAAFBE"}, + {"pink_1", "#FF00FF"}, + {"pink_2", "#E7A1B0"}, + {"pink_3", "#C48793"}, + {"pink_4", "#7F525D"}, + {"plum", "#B93B8F"}, + {"plum_1", "#F9B7FF"}, + {"plum_2", "#E6A9EC"}, + {"plum_3", "#C38EC7"}, + {"plum_4", "#7E587E"}, + {"purple", "#8E35EF"}, + {"purple_1", "#893BFF"}, + {"purple_2", "#7F38EC"}, + {"purple_3", "#6C2DC7"}, + {"purple_4", "#461B7E"}, + {"red", "#FF0000"}, + {"red_1", "#F62217"}, + {"red_2", "#E41B17"}, + {"rosy_brown", "#B38481"}, + {"rosy_brown_1", "#FBBBB9"}, + {"rosy_brown_2", "#E8ADAA"}, + {"rosy_brown_3", "#C5908E"}, + {"rosy_brown_4", "#7F5A58"}, + {"royal_blue", "#2B60DE"}, + {"royal_blue_1", "#306EFF"}, + {"royal_blue_2", "#2B65EC"}, + {"royal_blue_3", "#2554C7"}, + {"royal_blue_4", "#15317E"}, + {"salmon_1", "#F88158"}, + {"salmon_2", "#E67451"}, + {"salmon_3", "#C36241"}, + {"salmon_4", "#7E3817"}, + {"sandy_brown", "#EE9A4D"}, + {"sea_green", "#4E8975"}, + {"sea_green_1", "#6AFB92"}, + {"sea_green_2", "#64E986"}, + {"sea_green_3", "#54C571"}, + {"sea_green_4", "#387C44"}, + {"sienna", "#8A4117"}, + {"sienna_1", "#F87431"}, + {"sienna_2", "#E66C2C"}, + {"sienna_3", "#C35817"}, + {"sienna_4", "#7E3517"}, + {"sky_blue", "#82CAFF"}, + {"sky_blue_1", "#6698FF"}, + {"sky_blue_2", "#79BAEC"}, + {"sky_blue_3", "#659EC7"}, + {"sky_blue_4", "#41627E"}, + {"slate_blue", "#357EC7"}, + {"slate_blue_1", "#737CA1"}, + {"slate_blue_2", "#6960EC"}, + {"slate_blue_3", "#342D7E"}, + {"slate_gray", "#657383"}, + {"slate_gray_1", "#C2DFFF"}, + {"slate_gray_2", "#B4CFEC"}, + {"slate_gray_3", "#98AFC7"}, + {"slate_gray_4", "#616D7E"}, + {"spring_green", "#4AA02C"}, + {"spring_green_1", "#5EFB6E"}, + {"spring_green_2", "#57E964"}, + {"spring_green_3", "#4CC552"}, + {"spring_green_4", "#347C2C"}, + {"steel_blue", "#4863A0"}, + {"steel_blue_1", "#5CB3FF"}, + {"steel_blue_2", "#56A5EC"}, + {"steel_blue_3", "#488AC7"}, + {"steel_blue_4", "#2B547E"}, + {"thistle", "#D2B9D3"}, + {"thistle_1", "#FCDFFF"}, + {"thistle_2", "#E9CFEC"}, + {"thistle_3", "#C6AEC7"}, + {"thistle_4", "#806D7E"}, + {"turquoise", "#00FFFF"}, + {"turquoise_1", "#43C6DB"}, + {"turquoise_2", "#52F3FF"}, + {"turquoise_3", "#4EE2EC"}, + {"turquoise_4", "#43BFC7"}, + {"violet", "#8D38C9"}, + {"violet_red", "#F6358A"}, + {"violet_red_1", "#F6358A"}, + {"violet_red_2", "#E4317F"}, + {"violet_red_3", "#C12869"}, + {"violet_red_4", "#7D0541"}, + {"white", "#FFFFFF"}, + {"yellow", "#FFFF00"}, + {"yellow_1", "#FFFC17"}, + {"yellow_green", "#52D017"} +}; + +const std::map animations = { + {"kick", 1}, + {"pierce", 2}, + {"2hslash", 3}, + {"2hblunt", 4}, + {"2hpierce", 4}, + {"throw", 5}, + {"offhand", 6}, + {"bash", 7}, + {"mainhand", 8}, + {"bow", 9}, + {"swim", 10}, + {"roundkick", 11}, + {"gethit", 12}, + {"gethit2", 13}, + {"falling", 14}, + {"drowning", 15}, + {"death", 16}, + {"standby", 17}, + {"standby2", 18}, + {"lunge", 19}, + {"jump", 20}, + {"falling2", 21}, + {"duckwalk", 22}, + {"ladderclimb", 23}, + {"crouch", 24}, + {"swim2", 25}, + {"idle", 26}, + {"cheer", 27}, + {"disgusted", 28}, + {"wave", 29}, + {"rude", 30}, + {"yawn", 31}, + {"movetoside", 33}, + {"iceslide", 35}, + {"kneel", 36}, + {"swim3", 37}, + {"sit", 38}, + {"cast", 42}, + {"cast2", 43}, + {"cast3", 44}, + {"flykick", 45}, + {"tigerclaw", 46}, + {"eaglestrike", 47}, + {"nodyes", 48}, + {"shakeno", 49}, + {"plead", 50}, + {"clap", 51}, + {"blush", 52}, + {"chuckle", 54}, + {"headtilt", 57}, + {"dance", 58}, + {"disagree", 59}, + {"glare", 60}, + {"peer", 61}, + {"kneel", 62}, + {"laugh", 63}, + {"point", 64}, + {"shrug", 65}, + {"handraise", 66}, + {"salute", 67}, + {"shiver", 68}, + {"tapfoot", 69}, + {"bowto", 70}, + }; + + +class DialogueWindow { +public: + static void Render(Client *c, std::string markdown); +}; + + +#endif //EQEMU_DIALOGUE_WINDOW_H diff --git a/zone/doors.cpp b/zone/doors.cpp index ba3d36d21..ef785b8d7 100644 --- a/zone/doors.cpp +++ b/zone/doors.cpp @@ -42,38 +42,38 @@ extern EntityList entity_list; extern WorldServer worldserver; -Doors::Doors(const Door *door) : +Doors::Doors(const DoorsRepository::Doors& door) : close_timer(5000), - m_Position(door->pos_x, door->pos_y, door->pos_z, door->heading), - m_Destination(door->dest_x, door->dest_y, door->dest_z, door->dest_heading) { + m_Position(door.pos_x, door.pos_y, door.pos_z, door.heading), + m_Destination(door.dest_x, door.dest_y, door.dest_z, door.dest_heading) +{ + strn0cpy(zone_name, door.zone.c_str(), sizeof(zone_name)); + strn0cpy(door_name, door.name.c_str(), sizeof(door_name)); + strn0cpy(destination_zone_name, door.dest_zone.c_str(), sizeof(destination_zone_name)); - strn0cpy(zone_name, door->zone_name, 32); - strn0cpy(door_name, door->door_name, 32); - strn0cpy(destination_zone_name, door->dest_zone, 16); - - this->database_id = door->db_id; - this->door_id = door->door_id; - this->incline = door->incline; - this->open_type = door->opentype; - this->guild_id = door->guild_id; - this->lockpick = door->lock_pick; - this->key_item_id = door->keyitem; - this->no_key_ring = door->nokeyring; - this->trigger_door = door->trigger_door; - this->trigger_type = door->trigger_type; - this->triggered = false; - this->door_param = door->door_param; - this->size = door->size; - this->invert_state = door->invert_state; - this->destination_instance_id = door->dest_instance_id; - this->is_ldon_door = door->is_ldon_door; - this->client_version_mask = door->client_version_mask; + database_id = door.id; + door_id = door.doorid; + incline = door.incline; + open_type = door.opentype; + guild_id = door.guild; + lockpick = door.lockpick; + key_item_id = door.keyitem; + no_key_ring = door.nokeyring; + trigger_door = door.triggerdoor; + trigger_type = door.triggertype; + triggered = false; + door_param = door.door_param; + size = door.size; + invert_state = door.invert_state; + destination_instance_id = door.dest_instance; + is_ldon_door = door.is_ldon_door; + client_version_mask = door.client_version_mask; SetOpenState(false); close_timer.Disable(); - disable_timer = (door->disable_timer == 1 ? true : false); + disable_timer = (door.disable_timer == 1 ? true : false); } Doors::Doors(const char *model, const glm::vec4 &position, uint8 open_type, uint16 size) : @@ -211,13 +211,23 @@ void Doors::HandleClick(Client* sender, uint8 trigger) { uint32 required_key_item = GetKeyItem(); uint8 disable_add_to_key_ring = GetNoKeyring(); - uint32 player_has_key = 0; + bool player_has_key = false; uint32 player_key = 0; const EQ::ItemInstance *lock_pick_item = sender->GetInv().GetItem(EQ::invslot::slotCursor); - player_has_key = static_cast(sender->GetInv().HasItem(required_key_item, 1)); - if (player_has_key != INVALID_INDEX) { + // If classic key on cursor rule, check for it, otherwise owning it ok. + if (RuleB(Doors, RequireKeyOnCursor)) { + if (lock_pick_item != nullptr && + lock_pick_item->GetItem()->ID == required_key_item) { + player_has_key = true; + } + } + else if (sender->GetInv().HasItem(required_key_item, 1) != INVALID_INDEX) { + player_has_key = true; + } + + if (player_has_key) { player_key = required_key_item; } @@ -278,8 +288,11 @@ void Doors::HandleClick(Client* sender, uint8 trigger) { /** * Key required + * If using a lock_pick_item leave messaging to that code below */ - sender->Message(Chat::LightBlue, "This is locked..."); + if (lock_pick_item == nullptr && !IsDoorOpen()) { + sender->Message(Chat::LightBlue, "This is locked..."); + } /** * GM can always open locks @@ -325,19 +338,30 @@ void Doors::HandleClick(Client* sender, uint8 trigger) { */ else if (lock_pick_item != nullptr) { if (sender->GetSkill(EQ::skills::SkillPickLock)) { + Timer* pick_lock_timer = sender->GetPickLockTimer(); if (lock_pick_item->GetItem()->ItemType == EQ::item::ItemTypeLockPick) { + if (!pick_lock_timer->Check()) { + // Stop full scale mad spamming + safe_delete(outapp); + return; + } + float player_pick_lock_skill = sender->GetSkill(EQ::skills::SkillPickLock); - sender->CheckIncreaseSkill(EQ::skills::SkillPickLock, nullptr, 1); LogSkills("Client has lockpicks: skill=[{}]", player_pick_lock_skill); if (GetLockpick() <= player_pick_lock_skill) { + + // Stop full scale spamming + pick_lock_timer->Start(1000, true); + if (!IsDoorOpen()) { + sender->CheckIncreaseSkill(EQ::skills::SkillPickLock, nullptr, 1); move_door_packet->action = static_cast(invert_state == 0 ? OPEN_DOOR : OPEN_INVDOOR); + sender->MessageString(Chat::LightBlue, DOORS_SUCCESSFUL_PICK); } else { move_door_packet->action = static_cast(invert_state == 0 ? CLOSE_DOOR : CLOSE_INVDOOR); } - sender->MessageString(Chat::LightBlue, DOORS_SUCCESSFUL_PICK); } else { sender->MessageString(Chat::LightBlue, DOORS_INSUFFICIENT_SKILL); safe_delete(outapp); @@ -383,7 +407,9 @@ void Doors::HandleClick(Client* sender, uint8 trigger) { if (!IsDoorOpen() || (open_type == 58)) { if (!disable_timer) close_timer.Start(); - SetOpenState(true); + + if(strncmp(destination_zone_name, "NONE", strlen("NONE")) == 0) + SetOpenState(true); } else { close_timer.Disable(); if (!disable_timer) @@ -682,106 +708,19 @@ int32 ZoneDatabase::GetDoorsDBCountPlusOne(const char *zone_name, int16 version) return atoi(row[0]) + 1; } -bool ZoneDatabase::LoadDoors(int32 door_count, Door *into, const char *zone_name, int16 version) { +std::vector ZoneDatabase::LoadDoors(const std::string& zone_name, int16 version) +{ LogInfo("Loading Doors from database"); - std::string query = StringFormat( - " SELECT " - " id, " - " doorid, " - " zone, " - " NAME, " - " pos_x, " - " pos_y, " - " pos_z, " - " heading, " - " opentype, " - " guild, " - " lockpick, " - " keyitem, " - " nokeyring, " - " triggerdoor, " - " triggertype, " - " dest_zone, " - " dest_instance, " - " dest_x, " - " dest_y, " - " dest_z, " - " dest_heading, " - " door_param, " - " invert_state, " - " incline, " - " size, " - " is_ldon_door, " - " client_version_mask, " - " disable_timer " - " FROM " - " doors " - " WHERE " - " zone = '%s' " - " AND ( version = % u OR version = - 1 ) " - " %s " - " ORDER BY " - " doorid ASC ", - zone_name, - version, - ContentFilterCriteria::apply().c_str() - ); - auto results = QueryDatabase(query); - if (!results.Success()) { - return false; - } + auto door_entries = DoorsRepository::GetWhere(*this, fmt::format( + "zone = '{}' AND (version = {} OR version = -1) {} ORDER BY doorid ASC", + zone_name, version, ContentFilterCriteria::apply())); - int32 row_index = 0; - for (auto row = results.begin(); row != results.end(); ++row, ++row_index) { - if (row_index >= door_count) { - std::cerr << "Error, Door Count of " << door_count << " exceeded." << std::endl; - break; - } + LogDoors("Loaded [{}] doors for [{}] version [{}]", door_entries.size(), zone_name, version); - memset(&into[row_index], 0, sizeof(Door)); - - strn0cpy(into[row_index].zone_name, row[2], 32); - strn0cpy(into[row_index].door_name, row[3], 32); - strn0cpy(into[row_index].dest_zone, row[15], 32); - - into[row_index].db_id = static_cast(atoi(row[0])); - into[row_index].door_id = static_cast(atoi(row[1])); - into[row_index].pos_x = (float) atof(row[4]); - into[row_index].pos_y = (float) atof(row[5]); - into[row_index].pos_z = (float) atof(row[6]); - into[row_index].heading = (float) atof(row[7]); - into[row_index].opentype = static_cast(atoi(row[8])); - into[row_index].guild_id = static_cast(atoi(row[9])); - into[row_index].lock_pick = static_cast(atoi(row[10])); - into[row_index].keyitem = static_cast(atoi(row[11])); - into[row_index].nokeyring = static_cast(atoi(row[12])); - into[row_index].trigger_door = static_cast(atoi(row[13])); - into[row_index].trigger_type = static_cast(atoi(row[14])); - into[row_index].dest_instance_id = static_cast(atoi(row[16])); - into[row_index].dest_x = (float) atof(row[17]); - into[row_index].dest_y = (float) atof(row[18]); - into[row_index].dest_z = (float) atof(row[19]); - into[row_index].dest_heading = (float) atof(row[20]); - into[row_index].door_param = static_cast(atoi(row[21])); - into[row_index].invert_state = atoi(row[22]); - into[row_index].incline = atoi(row[23]); - into[row_index].size = static_cast(atoi(row[24])); - into[row_index].is_ldon_door = static_cast(atoi(row[25])); - into[row_index].client_version_mask = (uint32) strtoul(row[26], nullptr, 10); - into[row_index].disable_timer = static_cast(atoi(row[27])); - - Log(Logs::Detail, Logs::Doors, "Door Load: db id: %u, door_id %u disable_timer: %i", - into[row_index].db_id, - into[row_index].door_id, - into[row_index].disable_timer - ); - } - - return true; + return door_entries; } - void Doors::SetLocation(float x, float y, float z) { entity_list.DespawnAllDoors(); @@ -801,6 +740,12 @@ void Doors::SetIncline(int in) { entity_list.RespawnAllDoors(); } +void Doors::SetInvertState(int in) { + entity_list.DespawnAllDoors(); + invert_state = in; + entity_list.RespawnAllDoors(); +} + void Doors::SetOpenType(uint8 in) { entity_list.DespawnAllDoors(); open_type = in; diff --git a/zone/doors.h b/zone/doors.h index d7fbfc021..a0d9c6364 100644 --- a/zone/doors.h +++ b/zone/doors.h @@ -1,12 +1,8 @@ #ifndef DOORS_H #define DOORS_H -#include "../common/emu_opcodes.h" -#include "../common/eq_packet_structs.h" -#include "../common/linked_list.h" - #include "mob.h" -#include "zonedump.h" +#include "../common/repositories/doors_repository.h" class Client; class Mob; @@ -19,7 +15,7 @@ public: ~Doors(); Doors(const char *model, const glm::vec4& position, uint8 open_type = 58, uint16 size = 100); - Doors(const Door* door); + Doors(const DoorsRepository::Doors& door); bool GetDisableTimer() { return disable_timer; } bool IsDoor() const { return true; } @@ -54,6 +50,7 @@ public: void SetDoorName(const char *name); void SetEntityID(uint32 entity) { entity_id = entity; } void SetIncline(int in); + void SetInvertState(int in); void SetKeyItem(uint32 in) { key_item_id = in; } void SetLocation(float x, float y, float z); void SetLockpick(uint16 in) { lockpick = in; } diff --git a/zone/dynamic_zone.cpp b/zone/dynamic_zone.cpp index 72b42abba..a7f2bb822 100644 --- a/zone/dynamic_zone.cpp +++ b/zone/dynamic_zone.cpp @@ -20,454 +20,167 @@ #include "dynamic_zone.h" #include "client.h" +#include "expedition.h" +#include "string_ids.h" #include "worldserver.h" -#include "zonedb.h" #include "../common/eqemu_logsys.h" extern WorldServer worldserver; -DynamicZone::DynamicZone( - uint32_t zone_id, uint32_t version, uint32_t duration, DynamicZoneType type -) : - m_zone_id(zone_id), - m_version(version), - m_duration(duration), - m_type(type) -{ -} +// message string 8312 added in September 08 2020 Test patch (used by both dz and shared tasks) +const char* const CREATE_NOT_ALL_ADDED = "Not all players in your {} were added to the {}. The {} can take a maximum of {} players, and your {} has {}."; DynamicZone::DynamicZone( - std::string zone_shortname, uint32_t version, uint32_t duration, DynamicZoneType type -) : - m_version(version), - m_duration(duration), - m_type(type) + uint32_t zone_id, uint32_t version, uint32_t duration, DynamicZoneType type) { - m_zone_id = ZoneID(zone_shortname.c_str()); - - if (!m_zone_id) - { - LogDynamicZones("Failed to get zone id for zone [{}]", zone_shortname); - } + m_zone_id = zone_id; + m_zone_version = version; + m_duration = std::chrono::seconds(duration); + m_type = type; } -std::unordered_map DynamicZone::LoadMultipleDzFromDatabase( - const std::vector& dynamic_zone_ids) +Database& DynamicZone::GetDatabase() { - LogDynamicZonesDetail("Loading dynamic zone data for [{}] instances", dynamic_zone_ids.size()); - - std::string in_dynamic_zone_ids_query = fmt::format("{}", fmt::join(dynamic_zone_ids, ",")); - - std::unordered_map dynamic_zones; - - if (!in_dynamic_zone_ids_query.empty()) - { - std::string query = fmt::format(SQL( - {} WHERE dynamic_zones.id IN ({}); - ), DynamicZoneSelectQuery(), in_dynamic_zone_ids_query); - - auto results = database.QueryDatabase(query); - if (results.Success()) - { - for (auto row = results.begin(); row != results.end(); ++row) - { - DynamicZone dz; - dz.LoadDatabaseResult(row); - dynamic_zones.emplace(dz.GetID(), dz); - } - } - } - - return dynamic_zones; + return database; } -uint32_t DynamicZone::Create() +bool DynamicZone::SendServerPacket(ServerPacket* packet) { - if (m_id != 0) - { - return m_id; - } - - if (GetInstanceID() == 0) - { - CreateInstance(); - } - - m_id = SaveToDatabase(); - - return m_id; + return worldserver.SendPacket(packet); } -uint32_t DynamicZone::CreateInstance() +uint16_t DynamicZone::GetCurrentInstanceID() { - if (m_instance_id) + return zone ? static_cast(zone->GetInstanceID()) : 0; +} + +uint16_t DynamicZone::GetCurrentZoneID() +{ + return zone ? static_cast(zone->GetZoneID()) : 0; +} + +DynamicZone* DynamicZone::CreateNew(DynamicZone& dz_request, const std::vector& members) +{ + if (!zone || dz_request.GetID() != 0) { - LogDynamicZones("CreateInstance failed, instance id [{}] already created", m_instance_id); - return 0; + return nullptr; } - if (!m_zone_id) + // this creates a new dz instance and saves it to both db and cache + uint32_t dz_id = dz_request.Create(); + if (dz_id == 0) { - LogDynamicZones("CreateInstance failed, invalid zone id [{}]", m_zone_id); - return 0; + LogDynamicZones("Failed to create dynamic zone for zone [{}]", dz_request.GetZoneID()); + return nullptr; } - uint16_t instance_id = 0; - if (!database.GetUnusedInstanceID(instance_id)) // todo: doesn't this race with insert? + auto dz = std::make_unique(dz_request); + if (!members.empty()) { - LogDynamicZones("Failed to find unused instance id"); - return 0; + dz->SaveMembers(members); } - m_start_time = std::chrono::system_clock::now(); - auto start_time = std::chrono::system_clock::to_time_t(m_start_time); + LogDynamicZones("Created new dz [{}] for zone [{}]", dz_id, dz_request.GetZoneID()); - std::string query = fmt::format(SQL( - INSERT INTO instance_list - (id, zone, version, start_time, duration) - VALUES - ({}, {}, {}, {}, {}) - ), instance_id, m_zone_id, m_version, start_time, m_duration.count()); + // world must be notified before we request async member updates + auto pack = dz->CreateServerDzCreatePacket(zone->GetZoneID(), zone->GetInstanceID()); + worldserver.SendPacket(pack.get()); - auto results = database.QueryDatabase(query); - if (!results.Success()) + auto inserted = zone->dynamic_zone_cache.emplace(dz_id, std::move(dz)); + + // expeditions invoke their own updates after installing client update callbacks + if (inserted.first->second->GetType() != DynamicZoneType::Expedition) { - LogDynamicZones("Failed to create instance [{}] for Dynamic Zone [{}]", instance_id, m_zone_id); - return 0; + inserted.first->second->DoAsyncZoneMemberUpdates(); } - m_instance_id = instance_id; - m_never_expires = false; - m_expire_time = m_start_time + m_duration; - - return m_instance_id; + return inserted.first->second.get(); } -std::string DynamicZone::DynamicZoneSelectQuery() +void DynamicZone::CacheNewDynamicZone(ServerPacket* pack) { - return std::string(SQL( - SELECT - instance_list.id, - instance_list.zone, - instance_list.version, - instance_list.start_time, - instance_list.duration, - instance_list.never_expires, - dynamic_zones.id, - dynamic_zones.type, - dynamic_zones.compass_zone_id, - dynamic_zones.compass_x, - dynamic_zones.compass_y, - dynamic_zones.compass_z, - dynamic_zones.safe_return_zone_id, - dynamic_zones.safe_return_x, - dynamic_zones.safe_return_y, - dynamic_zones.safe_return_z, - dynamic_zones.safe_return_heading, - dynamic_zones.zone_in_x, - dynamic_zones.zone_in_y, - dynamic_zones.zone_in_z, - dynamic_zones.zone_in_heading, - dynamic_zones.has_zone_in - FROM dynamic_zones - INNER JOIN instance_list ON dynamic_zones.instance_id = instance_list.id - )); -} + auto buf = reinterpret_cast(pack->pBuffer); -void DynamicZone::LoadDatabaseResult(MySQLRequestRow& row) -{ - m_instance_id = strtoul(row[0], nullptr, 10); - m_zone_id = strtoul(row[1], nullptr, 10); - m_version = strtoul(row[2], nullptr, 10); - m_start_time = std::chrono::system_clock::from_time_t(strtoul(row[3], nullptr, 10)); - m_duration = std::chrono::seconds(strtoul(row[4], nullptr, 10)); - m_expire_time = m_start_time + m_duration; - m_never_expires = (strtoul(row[5], nullptr, 10) != 0); - m_id = strtoul(row[6], nullptr, 10); - m_type = static_cast(strtoul(row[7], nullptr, 10)); - m_compass.zone_id = strtoul(row[8], nullptr, 10); - m_compass.x = strtof(row[9], nullptr); - m_compass.y = strtof(row[10], nullptr); - m_compass.z = strtof(row[11], nullptr); - m_safereturn.zone_id = strtoul(row[12], nullptr, 10); - m_safereturn.x = strtof(row[13], nullptr); - m_safereturn.y = strtof(row[14], nullptr); - m_safereturn.z = strtof(row[15], nullptr); - m_safereturn.heading = strtof(row[16], nullptr); - m_zonein.x = strtof(row[17], nullptr); - m_zonein.y = strtof(row[18], nullptr); - m_zonein.z = strtof(row[19], nullptr); - m_zonein.heading = strtof(row[20], nullptr); - m_has_zonein = (strtoul(row[21], nullptr, 10) != 0); -} + // caching new dz created in world or another zone (has member statuses set by world) + auto dz = std::make_unique(); + dz->LoadSerializedDzPacket(buf->cereal_data, buf->cereal_size); -uint32_t DynamicZone::SaveToDatabase() -{ - LogDynamicZonesDetail("Saving dz instance [{}] to database", m_instance_id); + uint32_t dz_id = dz->GetID(); + auto inserted = zone->dynamic_zone_cache.emplace(dz_id, std::move(dz)); - if (m_instance_id != 0) + // expeditions invoke their own updates after installing client update callbacks + if (inserted.first->second->GetType() != DynamicZoneType::Expedition) { - std::string query = fmt::format(SQL( - INSERT INTO dynamic_zones - ( - instance_id, - type, - compass_zone_id, - compass_x, - compass_y, - compass_z, - safe_return_zone_id, - safe_return_x, - safe_return_y, - safe_return_z, - safe_return_heading, - zone_in_x, - zone_in_y, - zone_in_z, - zone_in_heading, - has_zone_in - ) - VALUES - ({}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}); - ), - m_instance_id, - static_cast(m_type), - m_compass.zone_id, - m_compass.x, - m_compass.y, - m_compass.z, - m_safereturn.zone_id, - m_safereturn.x, - m_safereturn.y, - m_safereturn.z, - m_safereturn.heading, - m_zonein.x, - m_zonein.y, - m_zonein.z, - m_zonein.heading, - m_has_zonein - ); - - auto results = database.QueryDatabase(query); - if (results.Success()) - { - return results.LastInsertedID(); - } + inserted.first->second->DoAsyncZoneMemberUpdates(); } - return 0; + + LogDynamicZones("Cached new dynamic zone [{}]", dz_id); } -void DynamicZone::SaveCompassToDatabase() +void DynamicZone::CacheAllFromDatabase() { - LogDynamicZonesDetail( - "Instance [{}] saving compass zone: [{}] xyz: ([{}], [{}], [{}])", - m_instance_id, m_compass.zone_id, m_compass.x, m_compass.y, m_compass.z - ); - - if (m_instance_id != 0) - { - std::string query = fmt::format(SQL( - UPDATE dynamic_zones SET - compass_zone_id = {}, - compass_x = {}, - compass_y = {}, - compass_z = {} - WHERE instance_id = {}; - ), - m_compass.zone_id, - m_compass.x, - m_compass.y, - m_compass.z, - m_instance_id - ); - - database.QueryDatabase(query); - } -} - -void DynamicZone::SaveSafeReturnToDatabase() -{ - LogDynamicZonesDetail( - "Instance [{}] saving safereturn zone: [{}] xyzh: ([{}], [{}], [{}], [{}])", - m_instance_id, m_safereturn.zone_id, m_safereturn.x, m_safereturn.y, m_safereturn.z, m_safereturn.heading - ); - - if (m_instance_id != 0) - { - std::string query = fmt::format(SQL( - UPDATE dynamic_zones SET - safe_return_zone_id = {}, - safe_return_x = {}, - safe_return_y = {}, - safe_return_z = {}, - safe_return_heading = {} - WHERE instance_id = {}; - ), - m_safereturn.zone_id, - m_safereturn.x, - m_safereturn.y, - m_safereturn.z, - m_safereturn.heading, - m_instance_id - ); - - database.QueryDatabase(query); - } -} - -void DynamicZone::SaveZoneInLocationToDatabase() -{ - LogDynamicZonesDetail( - "Instance [{}] saving zonein zone: [{}] xyzh: ([{}], [{}], [{}], [{}]) has: [{}]", - m_instance_id, m_zone_id, m_zonein.x, m_zonein.y, m_zonein.z, m_zonein.heading, m_has_zonein - ); - - if (m_instance_id != 0) - { - std::string query = fmt::format(SQL( - UPDATE dynamic_zones SET - zone_in_x = {}, - zone_in_y = {}, - zone_in_z = {}, - zone_in_heading = {}, - has_zone_in = {} - WHERE instance_id = {}; - ), - m_zonein.x, - m_zonein.y, - m_zonein.z, - m_zonein.heading, - m_has_zonein, - m_instance_id - ); - - database.QueryDatabase(query); - } -} - -void DynamicZone::AddCharacter(uint32_t character_id) -{ - database.AddClientToInstance(m_instance_id, character_id); - SendInstanceCharacterChange(character_id, false); // stops client kick timer -} - -void DynamicZone::RemoveCharacter(uint32_t character_id) -{ - database.RemoveClientFromInstance(m_instance_id, character_id); - SendInstanceCharacterChange(character_id, true); // start client kick timer -} - -void DynamicZone::RemoveAllCharacters(bool enable_removal_timers) -{ - if (GetInstanceID() == 0) + if (!zone) { return; } - if (enable_removal_timers) + BenchTimer bench; + + auto dynamic_zones = DynamicZonesRepository::AllWithInstanceNotExpired(database); + auto dynamic_zone_members = DynamicZoneMembersRepository::GetAllWithNames(database); + + zone->dynamic_zone_cache.clear(); + zone->dynamic_zone_cache.reserve(dynamic_zones.size()); + + for (auto& entry : dynamic_zones) { - // just remove all clients in bulk instead of only characters assigned to the instance - if (IsCurrentZoneDzInstance()) + uint32_t dz_id = entry.id; + auto dz = std::make_unique(std::move(entry)); + + for (auto& member : dynamic_zone_members) { - for (const auto& client_iter : entity_list.GetClientList()) + if (member.dynamic_zone_id == dz_id) { - if (client_iter.second) - { - client_iter.second->SetDzRemovalTimer(true); - } + dz->AddMemberFromRepositoryResult(std::move(member)); } } - else if (GetInstanceID() != 0) + + zone->dynamic_zone_cache.emplace(dz_id, std::move(dz)); + } + + LogDynamicZones("Caching [{}] dynamic zone(s) took [{}s]", zone->dynamic_zone_cache.size(), bench.elapsed()); +} + +DynamicZone* DynamicZone::FindDynamicZoneByID(uint32_t dz_id) +{ + if (!zone) + { + return nullptr; + } + + auto dz = zone->dynamic_zone_cache.find(dz_id); + if (dz != zone->dynamic_zone_cache.end()) + { + return dz->second.get(); + } + + return nullptr; +} + +void DynamicZone::RegisterOnClientAddRemove(std::function on_client_addremove) +{ + m_on_client_addremove = std::move(on_client_addremove); +} + +void DynamicZone::StartAllClientRemovalTimers() +{ + for (const auto& client_iter : entity_list.GetClientList()) + { + if (client_iter.second) { - uint32_t packsize = sizeof(ServerDzCharacter_Struct); - auto pack = std::make_unique(ServerOP_DzRemoveAllCharacters, packsize); - auto packbuf = reinterpret_cast(pack->pBuffer); - packbuf->zone_id = GetZoneID(); - packbuf->instance_id = GetInstanceID(); - packbuf->remove = true; - packbuf->character_id = 0; - worldserver.SendPacket(pack.get()); + client_iter.second->SetDzRemovalTimer(true); } } - - database.RemoveClientsFromInstance(GetInstanceID()); -} - -void DynamicZone::SaveInstanceMembersToDatabase(const std::vector& character_ids) -{ - LogDynamicZonesDetail("Saving [{}] instance members to database", character_ids.size()); - - std::string insert_values; - for (const auto& character_id : character_ids) - { - fmt::format_to(std::back_inserter(insert_values), "({}, {}),", m_instance_id, character_id); - } - - if (!insert_values.empty()) - { - insert_values.pop_back(); // trailing comma - - std::string query = fmt::format(SQL( - REPLACE INTO instance_list_player (id, charid) VALUES {}; - ), insert_values); - - database.QueryDatabase(query); - } -} - -void DynamicZone::SendInstanceCharacterChange(uint32_t character_id, bool removed) -{ - // if removing, sets removal timer on client inside the instance - if (IsCurrentZoneDzInstance()) - { - Client* client = entity_list.GetClientByCharID(character_id); - if (client) - { - client->SetDzRemovalTimer(removed); - } - } - else if (GetInstanceID() != 0) - { - uint32_t packsize = sizeof(ServerDzCharacter_Struct); - auto pack = std::make_unique(ServerOP_DzCharacterChange, packsize); - auto packbuf = reinterpret_cast(pack->pBuffer); - packbuf->zone_id = GetZoneID(); - packbuf->instance_id = GetInstanceID(); - packbuf->remove = removed; - packbuf->character_id = character_id; - worldserver.SendPacket(pack.get()); - } -} - -void DynamicZone::SetCompass(const DynamicZoneLocation& location, bool update_db) -{ - m_compass = location; - - if (update_db) - { - SaveCompassToDatabase(); - } -} - -void DynamicZone::SetSafeReturn(const DynamicZoneLocation& location, bool update_db) -{ - m_safereturn = location; - - if (update_db) - { - SaveSafeReturnToDatabase(); - } -} - -void DynamicZone::SetZoneInLocation(const DynamicZoneLocation& location, bool update_db) -{ - m_zonein = location; - m_has_zonein = true; - - if (update_db) - { - SaveZoneInLocationToDatabase(); - } } bool DynamicZone::IsCurrentZoneDzInstance() const @@ -475,25 +188,15 @@ bool DynamicZone::IsCurrentZoneDzInstance() const return (zone && zone->GetInstanceID() != 0 && zone->GetInstanceID() == GetInstanceID()); } -bool DynamicZone::IsInstanceID(uint32_t instance_id) const +void DynamicZone::SetSecondsRemaining(uint32_t seconds_remaining) { - return (GetInstanceID() != 0 && GetInstanceID() == instance_id); -} - -bool DynamicZone::IsSameDz(uint32_t zone_id, uint32_t instance_id) const -{ - return zone_id == m_zone_id && instance_id == m_instance_id; -} - -uint32_t DynamicZone::GetSecondsRemaining() const -{ - auto now = std::chrono::system_clock::now(); - if (m_expire_time > now) - { - auto remaining = m_expire_time - now; - return static_cast(std::chrono::duration_cast(remaining).count()); - } - return 0; + // async + constexpr uint32_t pack_size = sizeof(ServerDzSetDuration_Struct); + auto pack = std::make_unique(ServerOP_DzSetSecondsRemaining, pack_size); + auto buf = reinterpret_cast(pack->pBuffer); + buf->dz_id = GetID(); + buf->seconds = seconds_remaining; + worldserver.SendPacket(pack.get()); } void DynamicZone::SetUpdatedDuration(uint32_t new_duration) @@ -502,8 +205,8 @@ void DynamicZone::SetUpdatedDuration(uint32_t new_duration) m_duration = std::chrono::seconds(new_duration); m_expire_time = m_start_time + m_duration; - LogDynamicZones("Updated zone [{}]:[{}] seconds remaining: [{}]", - m_zone_id, m_instance_id, GetSecondsRemaining()); + LogDynamicZones("Updated dz [{}] zone [{}]:[{}] seconds remaining: [{}]", + m_id, m_zone_id, m_instance_id, GetSecondsRemaining()); if (zone && IsCurrentZoneDzInstance()) { @@ -515,30 +218,562 @@ void DynamicZone::HandleWorldMessage(ServerPacket* pack) { switch (pack->opcode) { - case ServerOP_DzCharacterChange: + case ServerOP_DzCreated: { - auto buf = reinterpret_cast(pack->pBuffer); - Client* client = entity_list.GetClientByCharID(buf->character_id); - if (client) + auto buf = reinterpret_cast(pack->pBuffer); + if (zone && !zone->IsZone(buf->origin_zone_id, buf->origin_instance_id)) { - client->SetDzRemovalTimer(buf->remove); // instance kick timer + DynamicZone::CacheNewDynamicZone(pack); } break; } - case ServerOP_DzRemoveAllCharacters: + case ServerOP_DzDeleted: { - auto buf = reinterpret_cast(pack->pBuffer); - if (buf->remove) + // sent by world when it deletes an expired or empty dz + // any system that held a reference to the dz should have already been notified + auto buf = reinterpret_cast(pack->pBuffer); + auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id); + if (zone && dz) { - for (const auto& client_list_iter : entity_list.GetClientList()) + dz->SendUpdatesToZoneMembers(true, true); // members silently removed + + // manually handle expeditions to remove any references before the dz is deleted + if (dz->GetType() == DynamicZoneType::Expedition) { - if (client_list_iter.second) + auto expedition = Expedition::FindCachedExpeditionByDynamicZoneID(dz->GetID()); + if (expedition) { - client_list_iter.second->SetDzRemovalTimer(true); + LogExpeditionsModerate("Deleting expedition [{}] from zone cache", expedition->GetID()); + zone->expedition_cache.erase(expedition->GetID()); + } + } + + LogDynamicZonesDetail("Deleting dynamic zone [{}] from zone cache", buf->dz_id); + zone->dynamic_zone_cache.erase(buf->dz_id); + } + break; + } + case ServerOP_DzAddRemoveMember: + { + auto buf = reinterpret_cast(pack->pBuffer); + if (zone && !zone->IsZone(buf->sender_zone_id, buf->sender_instance_id)) + { + auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id); + if (dz) + { + auto status = static_cast(buf->character_status); + dz->ProcessMemberAddRemove({ buf->character_id, buf->character_name, status }, buf->removed); + } + } + + if (zone && zone->IsZone(buf->dz_zone_id, buf->dz_instance_id)) + { + // cache independent redundancy to kick removed members from dz's instance + Client* client = entity_list.GetClientByCharID(buf->character_id); + if (client) + { + client->SetDzRemovalTimer(buf->removed); + } + } + break; + } + case ServerOP_DzSwapMembers: + { + auto buf = reinterpret_cast(pack->pBuffer); + if (zone && !zone->IsZone(buf->sender_zone_id, buf->sender_instance_id)) + { + auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id); + if (dz) + { + auto status = static_cast(buf->add_character_status); + dz->ProcessMemberAddRemove({ buf->remove_character_id, buf->remove_character_name }, true); + dz->ProcessMemberAddRemove({ buf->add_character_id, buf->add_character_name, status }, false); + } + } + + if (zone && zone->IsZone(buf->dz_zone_id, buf->dz_instance_id)) + { + // cache independent redundancy to kick removed members from dz's instance + Client* removed_client = entity_list.GetClientByCharID(buf->remove_character_id); + if (removed_client) + { + removed_client->SetDzRemovalTimer(true); + } + + Client* added_client = entity_list.GetClientByCharID(buf->add_character_id); + if (added_client) + { + added_client->SetDzRemovalTimer(false); + } + } + break; + } + case ServerOP_DzRemoveAllMembers: + { + auto buf = reinterpret_cast(pack->pBuffer); + if (zone && !zone->IsZone(buf->sender_zone_id, buf->sender_instance_id)) + { + auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id); + if (dz) + { + dz->ProcessRemoveAllMembers(); + } + } + + if (zone && zone->IsZone(buf->dz_zone_id, buf->dz_instance_id)) + { + // cache independent redundancy to kick removed members from dz's instance + DynamicZone::StartAllClientRemovalTimers(); + } + break; + } + case ServerOP_DzDurationUpdate: + { + auto buf = reinterpret_cast(pack->pBuffer); + auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id); + if (dz) + { + dz->SetUpdatedDuration(buf->seconds); + } + break; + } + case ServerOP_DzSetCompass: + case ServerOP_DzSetSafeReturn: + case ServerOP_DzSetZoneIn: + { + auto buf = reinterpret_cast(pack->pBuffer); + if (zone && !zone->IsZone(buf->sender_zone_id, buf->sender_instance_id)) + { + auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id); + if (dz) + { + if (pack->opcode == ServerOP_DzSetCompass) + { + dz->SetCompass(buf->zone_id, buf->x, buf->y, buf->z, false); + } + else if (pack->opcode == ServerOP_DzSetSafeReturn) + { + dz->SetSafeReturn(buf->zone_id, buf->x, buf->y, buf->z, buf->heading, false); + } + else if (pack->opcode == ServerOP_DzSetZoneIn) + { + dz->SetZoneInLocation(buf->x, buf->y, buf->z, buf->heading, false); } } } break; } + case ServerOP_DzGetMemberStatuses: + { + // reply from world for online member statuses request for async zone member updates + auto buf = reinterpret_cast(pack->pBuffer); + auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id); + if (dz) + { + for (uint32_t i = 0; i < buf->count; ++i) + { + auto status = static_cast(buf->entries[i].online_status); + dz->SetInternalMemberStatus(buf->entries[i].character_id, status); + } + dz->m_has_member_statuses = true; + dz->SendUpdatesToZoneMembers(false, true); + } + break; + } + case ServerOP_DzUpdateMemberStatus: + { + auto buf = reinterpret_cast(pack->pBuffer); + if (zone && !zone->IsZone(buf->sender_zone_id, buf->sender_instance_id)) + { + auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id); + if (dz) + { + auto status = static_cast(buf->status); + dz->ProcessMemberStatusChange(buf->character_id, status); + } + } + break; + } + case ServerOP_DzLeaderChanged: + { + auto buf = reinterpret_cast(pack->pBuffer); + auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id); + if (dz) + { + dz->ProcessLeaderChanged(buf->leader_id); + } + break; + } + case ServerOP_DzExpireWarning: + { + auto buf = reinterpret_cast(pack->pBuffer); + auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id); + if (dz) + { + dz->SendMembersExpireWarning(buf->minutes_remaining); + } + break; + } } } + +std::unique_ptr DynamicZone::CreateExpireWarningPacket(uint32_t minutes_remaining) +{ + uint32_t outsize = sizeof(ExpeditionExpireWarning); + auto outapp = std::make_unique(OP_DzExpeditionEndsWarning, outsize); + auto buf = reinterpret_cast(outapp->pBuffer); + buf->minutes_remaining = minutes_remaining; + return outapp; +} + +std::unique_ptr DynamicZone::CreateInfoPacket(bool clear) +{ + constexpr uint32_t outsize = sizeof(DynamicZoneInfo_Struct); + auto outapp = std::make_unique(OP_DzExpeditionInfo, outsize); + if (!clear) + { + auto info = reinterpret_cast(outapp->pBuffer); + info->assigned = true; + strn0cpy(info->dz_name, m_name.c_str(), sizeof(info->dz_name)); + strn0cpy(info->leader_name, m_leader.name.c_str(), sizeof(info->leader_name)); + info->max_players = m_max_players; + } + return outapp; +} + +std::unique_ptr DynamicZone::CreateMemberListPacket(bool clear) +{ + uint32_t member_count = clear ? 0 : static_cast(m_members.size()); + uint32_t member_entries_size = sizeof(DynamicZoneMemberEntry_Struct) * member_count; + uint32_t outsize = sizeof(DynamicZoneMemberList_Struct) + member_entries_size; + auto outapp = std::make_unique(OP_DzMemberList, outsize); + auto buf = reinterpret_cast(outapp->pBuffer); + + buf->member_count = member_count; + + if (!clear) + { + for (auto i = 0; i < m_members.size(); ++i) + { + strn0cpy(buf->members[i].name, m_members[i].name.c_str(), sizeof(buf->members[i].name)); + buf->members[i].online_status = static_cast(m_members[i].status); + } + } + + return outapp; +} + +std::unique_ptr DynamicZone::CreateMemberListNamePacket( + const std::string& name, bool remove_name) +{ + constexpr uint32_t outsize = sizeof(DynamicZoneMemberListName_Struct); + auto outapp = std::make_unique(OP_DzMemberListName, outsize); + auto buf = reinterpret_cast(outapp->pBuffer); + buf->add_name = !remove_name; + strn0cpy(buf->name, name.c_str(), sizeof(buf->name)); + return outapp; +} + +std::unique_ptr DynamicZone::CreateMemberListStatusPacket( + const std::string& name, DynamicZoneMemberStatus status) +{ + // member list status uses member list struct with a single entry + constexpr uint32_t outsize = sizeof(DynamicZoneMemberList_Struct) + sizeof(DynamicZoneMemberEntry_Struct); + auto outapp = std::make_unique(OP_DzMemberListStatus, outsize); + auto buf = reinterpret_cast(outapp->pBuffer); + buf->member_count = 1; + + auto entry = static_cast(buf->members); + strn0cpy(entry->name, name.c_str(), sizeof(entry->name)); + entry->online_status = static_cast(status); + + return outapp; +} + +std::unique_ptr DynamicZone::CreateLeaderNamePacket() +{ + constexpr uint32_t outsize = sizeof(DynamicZoneLeaderName_Struct); + auto outapp = std::make_unique(OP_DzSetLeaderName, outsize); + auto buf = reinterpret_cast(outapp->pBuffer); + strn0cpy(buf->leader_name, m_leader.name.c_str(), sizeof(buf->leader_name)); + return outapp; +} + +void DynamicZone::ProcessCompassChange(const DynamicZoneLocation& location) +{ + DynamicZoneBase::ProcessCompassChange(location); + SendCompassUpdateToZoneMembers(); +} + +void DynamicZone::SendCompassUpdateToZoneMembers() +{ + for (const auto& member : m_members) + { + Client* member_client = entity_list.GetClientByCharID(member.id); + if (member_client) + { + member_client->SendDzCompassUpdate(); + } + } +} + +void DynamicZone::SendLeaderNameToZoneMembers() +{ + auto outapp_leader = CreateLeaderNamePacket(); + + for (const auto& member : m_members) + { + Client* member_client = entity_list.GetClientByCharID(member.id); + if (member_client) + { + member_client->QueuePacket(outapp_leader.get()); + + if (member.id == m_leader.id && RuleB(Expedition, AlwaysNotifyNewLeaderOnChange)) + { + member_client->MessageString(Chat::Yellow, DZMAKELEADER_YOU); + } + } + } +} + +void DynamicZone::SendMembersExpireWarning(uint32_t minutes_remaining) +{ + // expeditions warn members in all zones not just the dz + auto outapp = CreateExpireWarningPacket(minutes_remaining); + for (const auto& member : GetMembers()) + { + Client* member_client = entity_list.GetClientByCharID(member.id); + if (member_client) + { + member_client->QueuePacket(outapp.get()); + + // live doesn't actually send the chat message with it + member_client->MessageString(Chat::Yellow, EXPEDITION_MIN_REMAIN, + fmt::format_int(minutes_remaining).c_str()); + } + } +} + +void DynamicZone::SendMemberListToZoneMembers() +{ + auto outapp_members = CreateMemberListPacket(false); + + for (const auto& member : m_members) + { + Client* member_client = entity_list.GetClientByCharID(member.id); + if (member_client) + { + member_client->QueuePacket(outapp_members.get()); + } + } +} + +void DynamicZone::SendMemberListNameToZoneMembers(const std::string& char_name, bool remove) +{ + auto outapp_member_name = CreateMemberListNamePacket(char_name, remove); + + for (const auto& member : m_members) + { + Client* member_client = entity_list.GetClientByCharID(member.id); + if (member_client) + { + member_client->QueuePacket(outapp_member_name.get()); + } + } +} + +void DynamicZone::SendMemberListStatusToZoneMembers(const DynamicZoneMember& update_member) +{ + auto outapp_member_status = CreateMemberListStatusPacket(update_member.name, update_member.status); + + for (const auto& member : m_members) + { + Client* member_client = entity_list.GetClientByCharID(member.id); + if (member_client) + { + member_client->QueuePacket(outapp_member_status.get()); + } + } +} + +void DynamicZone::SendClientWindowUpdate(Client* client) +{ + if (client) + { + client->QueuePacket(CreateInfoPacket().get()); + client->QueuePacket(CreateMemberListPacket().get()); + } +} + +void DynamicZone::SendUpdatesToZoneMembers(bool removing_all, bool silent) +{ + // performs a full update on all members (usually for dz creation or removing all) + if (!HasMembers()) + { + return; + } + + std::unique_ptr outapp_info = nullptr; + std::unique_ptr outapp_members = nullptr; + + // only expeditions use the dz window. on live the window is filled by non + // expeditions when first created but never kept updated. that behavior could + // be replicated in the future by flagging this as a creation update + if (m_type == DynamicZoneType::Expedition) + { + // clearing info also clears member list, no need to send both when removing + outapp_info = CreateInfoPacket(removing_all); + outapp_members = removing_all ? nullptr : CreateMemberListPacket(); + } + + for (const auto& member : GetMembers()) + { + Client* client = entity_list.GetClientByCharID(member.id); + if (client) + { + if (removing_all) { + client->RemoveDynamicZoneID(GetID()); + } else { + client->AddDynamicZoneID(GetID()); + } + + client->SendDzCompassUpdate(); + + if (outapp_info) + { + client->QueuePacket(outapp_info.get()); + } + + if (outapp_members) + { + client->QueuePacket(outapp_members.get()); + } + + // callback to the dz system so it can perform any messages or set client data + if (m_on_client_addremove) + { + m_on_client_addremove(client, removing_all, silent); + } + } + } +} + +void DynamicZone::ProcessMemberAddRemove(const DynamicZoneMember& member, bool removed) +{ + DynamicZoneBase::ProcessMemberAddRemove(member, removed); + + // the affected client always gets a full compass update. for expeditions + // client also gets window info update and all members get a member list update + Client* client = entity_list.GetClientByCharID(member.id); + if (client) + { + if (!removed) { + client->AddDynamicZoneID(GetID()); + } else { + client->RemoveDynamicZoneID(GetID()); + } + + client->SendDzCompassUpdate(); + + if (m_type == DynamicZoneType::Expedition) + { + // sending clear info also clears member list for removed members + client->QueuePacket(CreateInfoPacket(removed).get()); + } + + if (m_on_client_addremove) + { + m_on_client_addremove(client, removed, false); + } + } + + if (m_type == DynamicZoneType::Expedition) + { + // send full list when adding (MemberListName adds with "unknown" status) + if (!removed) { + SendMemberListToZoneMembers(); + } else { + SendMemberListNameToZoneMembers(member.name, true); + } + } +} + +void DynamicZone::ProcessRemoveAllMembers(bool silent) +{ + SendUpdatesToZoneMembers(true, silent); + DynamicZoneBase::ProcessRemoveAllMembers(silent); +} + +void DynamicZone::DoAsyncZoneMemberUpdates() +{ + // gets member statuses from world and performs zone member updates on reply + // if we've already received member statuses we can just update immediately + if (m_has_member_statuses) + { + SendUpdatesToZoneMembers(); + return; + } + + constexpr uint32_t pack_size = sizeof(ServerDzID_Struct); + auto pack = std::make_unique(ServerOP_DzGetMemberStatuses, pack_size); + auto buf = reinterpret_cast(pack->pBuffer); + buf->dz_id = GetID(); + buf->sender_zone_id = zone ? zone->GetZoneID() : 0; + buf->sender_instance_id = zone ? zone->GetInstanceID() : 0; + worldserver.SendPacket(pack.get()); +} + +bool DynamicZone::ProcessMemberStatusChange(uint32_t member_id, DynamicZoneMemberStatus status) +{ + bool changed = DynamicZoneBase::ProcessMemberStatusChange(member_id, status); + + if (changed && m_type == DynamicZoneType::Expedition) + { + auto member = GetMemberData(member_id); + if (member.IsValid()) + { + SendMemberListStatusToZoneMembers(member); + } + } + + return changed; +} + +void DynamicZone::ProcessLeaderChanged(uint32_t new_leader_id) +{ + auto new_leader = GetMemberData(new_leader_id); + if (!new_leader.IsValid()) + { + LogDynamicZones("Processed invalid new leader id [{}] for dz [{}]", new_leader_id, m_id); + return; + } + + LogDynamicZones("Replaced [{}] leader [{}] with [{}]", m_id, GetLeaderName(), new_leader.name); + + SetLeader(new_leader); + if (GetType() == DynamicZoneType::Expedition) + { + SendLeaderNameToZoneMembers(); + } +} + +bool DynamicZone::CanClientLootCorpse(Client* client, uint32_t npc_type_id, uint32_t entity_id) +{ + // non-members of a dz cannot loot corpses inside the dz + if (!HasMember(client->CharacterID())) + { + return false; + } + + // expeditions may prevent looting based on client's lockouts + if (GetType() == DynamicZoneType::Expedition) + { + auto expedition = Expedition::FindCachedExpeditionByZoneInstance(zone->GetZoneID(), zone->GetInstanceID()); + if (expedition && !expedition->CanClientLootCorpse(client, npc_type_id, entity_id)) + { + return false; + } + } + + return true; +} diff --git a/zone/dynamic_zone.h b/zone/dynamic_zone.h index 19d186bba..de23ce238 100644 --- a/zone/dynamic_zone.h +++ b/zone/dynamic_zone.h @@ -21,107 +21,72 @@ #ifndef DYNAMIC_ZONE_H #define DYNAMIC_ZONE_H -#include +#include "../common/dynamic_zone_base.h" #include #include #include #include -class MySQLRequestRow; +class Client; +class Database; +class EQApplicationPacket; class ServerPacket; -enum class DynamicZoneType : uint8_t -{ - None = 0, - Expedition, - Tutorial, - Task, - Mission, // Shared Task - Quest -}; +extern const char* const CREATE_NOT_ALL_ADDED; -struct DynamicZoneLocation -{ - uint32_t zone_id = 0; - float x = 0.0f; - float y = 0.0f; - float z = 0.0f; - float heading = 0.0f; - - DynamicZoneLocation() = default; - DynamicZoneLocation(uint32_t zone_id_, float x_, float y_, float z_, float heading_) - : zone_id(zone_id_), x(x_), y(y_), z(z_), heading(heading_) {} -}; - -class DynamicZone +class DynamicZone : public DynamicZoneBase { public: + using DynamicZoneBase::DynamicZoneBase; // inherit base constructors + DynamicZone() = default; DynamicZone(uint32_t zone_id, uint32_t version, uint32_t duration, DynamicZoneType type); - DynamicZone(std::string zone_shortname, uint32_t version, uint32_t duration, DynamicZoneType type); - DynamicZone(uint32_t dz_id) : m_id(dz_id) {} - DynamicZone(DynamicZoneType type) : m_type(type) {} - static std::unordered_map LoadMultipleDzFromDatabase( - const std::vector& dynamic_zone_ids); + static void CacheAllFromDatabase(); + static void CacheNewDynamicZone(ServerPacket* pack); + static DynamicZone* CreateNew(DynamicZone& dz_details, const std::vector& members); + static DynamicZone* FindDynamicZoneByID(uint32_t dz_id); static void HandleWorldMessage(ServerPacket* pack); - uint64_t GetExpireTime() const { return std::chrono::system_clock::to_time_t(m_expire_time); } - uint32_t GetID() const { return m_id; } - uint16_t GetInstanceID() const { return static_cast(m_instance_id); } - uint32_t GetSecondsRemaining() const; - uint16_t GetZoneID() const { return static_cast(m_zone_id); } - uint32_t GetZoneIndex() const { return (m_instance_id << 16) | (m_zone_id & 0xffff); } - uint32_t GetZoneVersion() const { return m_version; } - const std::string& GetLeaderName() const { return m_leader_name; } - const std::string& GetName() const { return m_name; } - DynamicZoneType GetType() const { return m_type; } - DynamicZoneLocation GetCompassLocation() const { return m_compass; } - DynamicZoneLocation GetSafeReturnLocation() const { return m_safereturn; } - DynamicZoneLocation GetZoneInLocation() const { return m_zonein; } + void SetSecondsRemaining(uint32_t seconds_remaining) override; - void AddCharacter(uint32_t character_id); - uint32_t Create(); - uint32_t CreateInstance(); - bool HasZoneInLocation() const { return m_has_zonein; } - bool IsCurrentZoneDzInstance() const; - bool IsInstanceID(uint32_t instance_id) const; - bool IsValid() const { return m_instance_id != 0; } - bool IsSameDz(uint32_t zone_id, uint32_t instance_id) const; - void RemoveAllCharacters(bool enable_removal_timers = true); - void RemoveCharacter(uint32_t character_id); - void SaveInstanceMembersToDatabase(const std::vector& character_ids); - void SendInstanceCharacterChange(uint32_t character_id, bool removed); - void SetCompass(const DynamicZoneLocation& location, bool update_db = false); - void SetLeaderName(const std::string& leader_name) { m_leader_name = leader_name; } - void SetName(const std::string& name) { m_name = name; } - void SetSafeReturn(const DynamicZoneLocation& location, bool update_db = false); - void SetZoneInLocation(const DynamicZoneLocation& location, bool update_db = false); - void SetUpdatedDuration(uint32_t seconds); + void DoAsyncZoneMemberUpdates(); + bool CanClientLootCorpse(Client* client, uint32_t npc_type_id, uint32_t entity_id); + bool IsCurrentZoneDzInstance() const; + void RegisterOnClientAddRemove(std::function on_client_addremove); + void SendClientWindowUpdate(Client* client); + void SendLeaderNameToZoneMembers(); + void SendMemberListToZoneMembers(); + void SendMemberListNameToZoneMembers(const std::string& char_name, bool remove); + void SendMemberListStatusToZoneMembers(const DynamicZoneMember& member); + void SendRemoveAllMembersToZoneMembers(bool silent) { ProcessRemoveAllMembers(silent); } + + std::unique_ptr CreateExpireWarningPacket(uint32_t minutes_remaining); + std::unique_ptr CreateInfoPacket(bool clear = false); + std::unique_ptr CreateLeaderNamePacket(); + std::unique_ptr CreateMemberListPacket(bool clear = false); + std::unique_ptr CreateMemberListNamePacket(const std::string& name, bool remove_name); + std::unique_ptr CreateMemberListStatusPacket(const std::string& name, DynamicZoneMemberStatus status); + +protected: + uint16_t GetCurrentInstanceID() override; + uint16_t GetCurrentZoneID() override; + Database& GetDatabase() override; + void ProcessCompassChange(const DynamicZoneLocation& location) override; + void ProcessMemberAddRemove(const DynamicZoneMember& member, bool removed) override; + bool ProcessMemberStatusChange(uint32_t member_id, DynamicZoneMemberStatus status) override; + void ProcessRemoveAllMembers(bool silent = false) override; + bool SendServerPacket(ServerPacket* packet) override; private: - static std::string DynamicZoneSelectQuery(); - void LoadDatabaseResult(MySQLRequestRow& row); - void SaveCompassToDatabase(); - void SaveSafeReturnToDatabase(); - void SaveZoneInLocationToDatabase(); - uint32_t SaveToDatabase(); + static void StartAllClientRemovalTimers(); + void ProcessLeaderChanged(uint32_t new_leader_id); + void SendCompassUpdateToZoneMembers(); + void SendMembersExpireWarning(uint32_t minutes); + void SendUpdatesToZoneMembers(bool removing_all = false, bool silent = true); + void SetUpdatedDuration(uint32_t seconds); - uint32_t m_id = 0; - uint32_t m_zone_id = 0; - uint32_t m_instance_id = 0; - uint32_t m_version = 0; - bool m_never_expires = false; - bool m_has_zonein = false; - std::string m_name; - std::string m_leader_name; - DynamicZoneType m_type{ DynamicZoneType::None }; - DynamicZoneLocation m_compass; - DynamicZoneLocation m_safereturn; - DynamicZoneLocation m_zonein; - std::chrono::seconds m_duration; - std::chrono::time_point m_start_time; - std::chrono::time_point m_expire_time; + std::function m_on_client_addremove; }; #endif diff --git a/zone/effects.cpp b/zone/effects.cpp index f7f5f9863..1499e4fd6 100644 --- a/zone/effects.cpp +++ b/zone/effects.cpp @@ -44,18 +44,16 @@ float Mob::GetActSpellRange(uint16 spell_id, float range, bool IsBard) int32 Mob::GetActSpellDamage(uint16 spell_id, int32 value, Mob* target) { - if (spells[spell_id].targettype == ST_Self) + if (spells[spell_id].target_type == ST_Self) return value; if (IsNPC()) value += value*CastToNPC()->GetSpellFocusDMG()/100; - Critical = false; //Mitch removed bool - int32 value_BaseEffect = 0; + bool Critical = false; + int32 base_value = value; int chance = 0; - value_BaseEffect = value + (value*GetFocusEffect(focusFcBaseEffects, spell_id)/100); - // Need to scale HT damage differently after level 40! It no longer scales by the constant value in the spell file. It scales differently, instead of 10 more damage per level, it does 30 more damage per level. So we multiply the level minus 40 times 20 if they are over level 40. if ((spell_id == SPELL_HARM_TOUCH || spell_id == SPELL_HARM_TOUCH2 || spell_id == SPELL_IMP_HARM_TOUCH ) && GetLevel() > 40) value -= (GetLevel() - 40) * 20; @@ -99,15 +97,16 @@ int32 Mob::GetActSpellDamage(uint16 spell_id, int32 value, Mob* target) { if (Critical){ - value = value_BaseEffect*ratio/100; + value = base_value*ratio/100; - value += value_BaseEffect*GetFocusEffect(focusImprovedDamage, spell_id)/100; - value += value_BaseEffect*GetFocusEffect(focusImprovedDamage2, spell_id)/100; + value += base_value*GetFocusEffect(focusImprovedDamage, spell_id)/100; + value += base_value*GetFocusEffect(focusImprovedDamage2, spell_id)/100; - value += int(value_BaseEffect*GetFocusEffect(focusFcDamagePctCrit, spell_id)/100)*ratio/100; + value += int(base_value*GetFocusEffect(focusFcDamagePctCrit, spell_id)/100)*ratio/100; + value += int(base_value*GetFocusEffect(focusFcAmplifyMod, spell_id) / 100)*ratio / 100; if (target) { - value += int(value_BaseEffect*target->GetVulnerability(this, spell_id, 0)/100)*ratio/100; + value += int(base_value*target->GetVulnerability(this, spell_id, 0)/100)*ratio/100; value -= target->GetFcDamageAmtIncoming(this, spell_id); } @@ -115,12 +114,13 @@ int32 Mob::GetActSpellDamage(uint16 spell_id, int32 value, Mob* target) { value -= GetFocusEffect(focusFcDamageAmt, spell_id); value -= GetFocusEffect(focusFcDamageAmt2, spell_id); + value -= GetFocusEffect(focusFcAmplifyAmt, spell_id); if (RuleB(Spells, IgnoreSpellDmgLvlRestriction) && !spells[spell_id].no_heal_damage_item_mod && itembonuses.SpellDmg) - value -= GetExtraSpellAmt(spell_id, itembonuses.SpellDmg, value)*ratio / 100; + value -= GetExtraSpellAmt(spell_id, itembonuses.SpellDmg, base_value)*ratio / 100; else if(!spells[spell_id].no_heal_damage_item_mod && itembonuses.SpellDmg && spells[spell_id].classes[(GetClass() % 17) - 1] >= GetLevel() - 5) - value -= GetExtraSpellAmt(spell_id, itembonuses.SpellDmg, value)*ratio/100; + value -= GetExtraSpellAmt(spell_id, itembonuses.SpellDmg, base_value)*ratio/100; else if (IsNPC() && CastToNPC()->GetSpellScale()) value = int(static_cast(value) * CastToNPC()->GetSpellScale() / 100.0f); @@ -136,15 +136,16 @@ int32 Mob::GetActSpellDamage(uint16 spell_id, int32 value, Mob* target) { } } //Non Crtical Hit Calculation pathway - value = value_BaseEffect; + value = base_value; - value += value_BaseEffect*GetFocusEffect(focusImprovedDamage, spell_id)/100; - value += value_BaseEffect*GetFocusEffect(focusImprovedDamage2, spell_id)/100; + value += base_value*GetFocusEffect(focusImprovedDamage, spell_id)/100; + value += base_value*GetFocusEffect(focusImprovedDamage2, spell_id)/100; - value += value_BaseEffect*GetFocusEffect(focusFcDamagePctCrit, spell_id)/100; + value += base_value*GetFocusEffect(focusFcDamagePctCrit, spell_id)/100; + value += base_value*GetFocusEffect(focusFcAmplifyMod, spell_id)/100; if (target) { - value += value_BaseEffect*target->GetVulnerability(this, spell_id, 0)/100; + value += base_value*target->GetVulnerability(this, spell_id, 0)/100; value -= target->GetFcDamageAmtIncoming(this, spell_id); } @@ -152,12 +153,13 @@ int32 Mob::GetActSpellDamage(uint16 spell_id, int32 value, Mob* target) { value -= GetFocusEffect(focusFcDamageAmt, spell_id); value -= GetFocusEffect(focusFcDamageAmt2, spell_id); + value -= GetFocusEffect(focusFcAmplifyAmt, spell_id); if (RuleB(Spells, IgnoreSpellDmgLvlRestriction) && !spells[spell_id].no_heal_damage_item_mod && itembonuses.SpellDmg) - value -= GetExtraSpellAmt(spell_id, itembonuses.SpellDmg, value); + value -= GetExtraSpellAmt(spell_id, itembonuses.SpellDmg, base_value); else if (!spells[spell_id].no_heal_damage_item_mod && itembonuses.SpellDmg && spells[spell_id].classes[(GetClass() % 17) - 1] >= GetLevel() - 5) - value -= GetExtraSpellAmt(spell_id, itembonuses.SpellDmg, value); + value -= GetExtraSpellAmt(spell_id, itembonuses.SpellDmg, base_value); if (IsNPC() && CastToNPC()->GetSpellScale()) value = int(static_cast(value) * CastToNPC()->GetSpellScale() / 100.0f); @@ -165,15 +167,43 @@ int32 Mob::GetActSpellDamage(uint16 spell_id, int32 value, Mob* target) { return value; } +int32 Mob::GetActReflectedSpellDamage(int32 spell_id, int32 value, int effectiveness) { + /* + Reflected spells use the spells base damage before any modifiers or formulas applied. + That value can then be modifier by the reflect spells 'max' value, defined here as effectiveness + Default effectiveness is set at 100. + Extra Spell Damage stat from the with the reflect effect will be applied to reflected damage + with no level limitation, this was confirmed with extensive parsing ~Kayen + */ + if (IsNPC()) { + value += value * CastToNPC()->GetSpellFocusDMG() / 100; + + if (CastToNPC()->GetSpellScale()) { + value = int(static_cast(value) * CastToNPC()->GetSpellScale() / 100.0f); + } + } + + int32 base_spell_dmg = value; + + value = value * effectiveness / 100; + + if (!spells[spell_id].no_heal_damage_item_mod && itembonuses.SpellDmg) { + value -= GetExtraSpellAmt(spell_id, itembonuses.SpellDmg, base_spell_dmg); + } + + return value; +} + int32 Mob::GetActDoTDamage(uint16 spell_id, int32 value, Mob* target) { if (target == nullptr) return value; + + if (IsNPC()) { + value += value * CastToNPC()->GetSpellFocusDMG() / 100; + } - if (IsNPC()) - value += value*CastToNPC()->GetSpellFocusDMG()/100; - - int32 value_BaseEffect = 0; + int32 base_value = value; int32 extra_dmg = 0; int16 chance = 0; chance += itembonuses.CriticalDoTChance + spellbonuses.CriticalDoTChance + aabonuses.CriticalDoTChance; @@ -184,23 +214,32 @@ int32 Mob::GetActDoTDamage(uint16 spell_id, int32 value, Mob* target) { if (spells[spell_id].override_crit_chance > 0 && chance > spells[spell_id].override_crit_chance) chance = spells[spell_id].override_crit_chance; - value_BaseEffect = value + (value*GetFocusEffect(focusFcBaseEffects, spell_id)/100); - - if (chance > 0 && (zone->random.Roll(chance))) { + if (!spells[spell_id].good_effect && chance > 0 && (zone->random.Roll(chance))) { int32 ratio = 200; ratio += itembonuses.DotCritDmgIncrease + spellbonuses.DotCritDmgIncrease + aabonuses.DotCritDmgIncrease; - value = value_BaseEffect*ratio/100; - value += int(value_BaseEffect*GetFocusEffect(focusImprovedDamage, spell_id)/100)*ratio/100; - value += int(value_BaseEffect*GetFocusEffect(focusImprovedDamage2, spell_id)/100)*ratio/100; - value += int(value_BaseEffect*GetFocusEffect(focusFcDamagePctCrit, spell_id)/100)*ratio/100; - value += int(value_BaseEffect*target->GetVulnerability(this, spell_id, 0)/100)*ratio/100; + value = base_value*ratio/100; + value += int(base_value*GetFocusEffect(focusImprovedDamage, spell_id)/100)*ratio/100; + value += int(base_value*GetFocusEffect(focusImprovedDamage2, spell_id)/100)*ratio/100; + value += int(base_value*GetFocusEffect(focusFcDamagePctCrit, spell_id)/100)*ratio/100; + value += int(base_value*GetFocusEffect(focusFcAmplifyMod, spell_id) / 100)*ratio/100; + value += int(base_value*target->GetVulnerability(this, spell_id, 0)/100)*ratio/100; extra_dmg = target->GetFcDamageAmtIncoming(this, spell_id) + int(GetFocusEffect(focusFcDamageAmtCrit, spell_id)*ratio/100) + GetFocusEffect(focusFcDamageAmt, spell_id) + - GetFocusEffect(focusFcDamageAmt2, spell_id); + GetFocusEffect(focusFcDamageAmt2, spell_id) + + GetFocusEffect(focusFcAmplifyAmt, spell_id); + + if (RuleB(Spells, DOTsScaleWithSpellDmg)) { + if (RuleB(Spells, IgnoreSpellDmgLvlRestriction) && !spells[spell_id].no_heal_damage_item_mod && itembonuses.SpellDmg) { + extra_dmg += GetExtraSpellAmt(spell_id, itembonuses.SpellDmg, base_value)*ratio/100; + } + else if(!spells[spell_id].no_heal_damage_item_mod && itembonuses.SpellDmg && spells[spell_id].classes[(GetClass() % 17) - 1] >= GetLevel() - 5) { + extra_dmg += GetExtraSpellAmt(spell_id, itembonuses.SpellDmg, base_value)*ratio/100; + } + } if (extra_dmg) { - int duration = CalcBuffDuration(this, this, spell_id); + int duration = CalcBuffDuration(this, target, spell_id); if (duration > 0) extra_dmg /= duration; } @@ -209,18 +248,29 @@ int32 Mob::GetActDoTDamage(uint16 spell_id, int32 value, Mob* target) { } else { - value = value_BaseEffect; - value += value_BaseEffect*GetFocusEffect(focusImprovedDamage, spell_id)/100; - value += value_BaseEffect*GetFocusEffect(focusImprovedDamage2, spell_id)/100; - value += value_BaseEffect*GetFocusEffect(focusFcDamagePctCrit, spell_id)/100; - value += value_BaseEffect*target->GetVulnerability(this, spell_id, 0)/100; + value = base_value; + value += base_value*GetFocusEffect(focusImprovedDamage, spell_id)/100; + value += base_value*GetFocusEffect(focusImprovedDamage2, spell_id)/100; + value += base_value*GetFocusEffect(focusFcDamagePctCrit, spell_id)/100; + value += base_value*GetFocusEffect(focusFcAmplifyMod, spell_id)/100; + value += base_value*target->GetVulnerability(this, spell_id, 0)/100; extra_dmg = target->GetFcDamageAmtIncoming(this, spell_id) + GetFocusEffect(focusFcDamageAmtCrit, spell_id) + GetFocusEffect(focusFcDamageAmt, spell_id) + - GetFocusEffect(focusFcDamageAmt2, spell_id); + GetFocusEffect(focusFcDamageAmt2, spell_id) + + GetFocusEffect(focusFcAmplifyAmt, spell_id); + + if (RuleB(Spells, DOTsScaleWithSpellDmg)) { + if (RuleB(Spells, IgnoreSpellDmgLvlRestriction) && !spells[spell_id].no_heal_damage_item_mod && itembonuses.SpellDmg) { + extra_dmg += GetExtraSpellAmt(spell_id, itembonuses.SpellDmg, base_value); + } + else if(!spells[spell_id].no_heal_damage_item_mod && itembonuses.SpellDmg && spells[spell_id].classes[(GetClass() % 17) - 1] >= GetLevel() - 5) { + extra_dmg += GetExtraSpellAmt(spell_id, itembonuses.SpellDmg, base_value); + } + } if (extra_dmg) { - int duration = CalcBuffDuration(this, this, spell_id); + int duration = CalcBuffDuration(this, target, spell_id); if (duration > 0) extra_dmg /= duration; } @@ -253,69 +303,104 @@ int32 Mob::GetExtraSpellAmt(uint16 spell_id, int32 extra_spell_amt, int32 base_s else extra_spell_amt = extra_spell_amt * total_cast_time / 7000; - if(extra_spell_amt*2 < base_spell_dmg) - return 0; + //Confirmed with parsing 10/9/21 ~Kayen + if (extra_spell_amt * 2 > abs(base_spell_dmg)) { + extra_spell_amt = abs(base_spell_dmg) / 2; + } - return extra_spell_amt; + return extra_spell_amt; } int32 Mob::GetActSpellHealing(uint16 spell_id, int32 value, Mob* target) { - if (target == nullptr) - target = this; - if (IsNPC()) - value += value*CastToNPC()->GetSpellFocusHeal()/100; + if (IsNPC()) { + value += value * CastToNPC()->GetSpellFocusHeal() / 100; + } - int32 value_BaseEffect = 0; - int16 chance = 0; - int8 modifier = 1; - Critical = false; //mitch + Critical = false; + int32 base_value = value; + int16 critical_chance = 0; + int8 critical_modifier = 1; - value_BaseEffect = value + (value*GetFocusEffect(focusFcBaseEffects, spell_id)/100); + if (spells[spell_id].buff_duration < 1) { + critical_chance += itembonuses.CriticalHealChance + spellbonuses.CriticalHealChance + aabonuses.CriticalHealChance; - value = value_BaseEffect; + if (spellbonuses.CriticalHealDecay) { + critical_chance += GetDecayEffectValue(spell_id, SE_CriticalHealDecay); + } + } + else { + critical_chance = itembonuses.CriticalHealOverTime + spellbonuses.CriticalHealOverTime + aabonuses.CriticalHealOverTime; - value += int(value_BaseEffect*GetFocusEffect(focusImprovedHeal, spell_id)/100); + if (spellbonuses.CriticalRegenDecay) { + critical_chance += GetDecayEffectValue(spell_id, SE_CriticalRegenDecay); + } + } - // Instant Heals - if(spells[spell_id].buffduration < 1) { + if (critical_chance) { - chance += itembonuses.CriticalHealChance + spellbonuses.CriticalHealChance + aabonuses.CriticalHealChance; - - chance += target->GetFocusIncoming(focusFcHealPctCritIncoming, SE_FcHealPctCritIncoming, this, spell_id); - - if (spellbonuses.CriticalHealDecay) - chance += GetDecayEffectValue(spell_id, SE_CriticalHealDecay); - - if (spells[spell_id].override_crit_chance > 0 && chance > spells[spell_id].override_crit_chance) - chance = spells[spell_id].override_crit_chance; - - if(chance && (zone->random.Roll(chance))) { - Critical = true; - modifier = 2; //At present time no critical heal amount modifier SPA exists. + if (spells[spell_id].override_crit_chance > 0 && critical_chance > spells[spell_id].override_crit_chance) { + critical_chance = spells[spell_id].override_crit_chance; } - value *= modifier; - value += GetFocusEffect(focusFcHealAmtCrit, spell_id) * modifier; - value += GetFocusEffect(focusFcHealAmt, spell_id); - value += target->GetFocusIncoming(focusFcHealAmtIncoming, SE_FcHealAmtIncoming, this, spell_id); + if (zone->random.Roll(critical_chance)) { + critical_modifier = 2; //At present time no critical heal amount modifier SPA exists. + } + } - if(!spells[spell_id].no_heal_damage_item_mod && itembonuses.HealAmt && spells[spell_id].classes[(GetClass()%17) - 1] >= GetLevel() - 5) - value += GetExtraSpellAmt(spell_id, itembonuses.HealAmt, value) * modifier; + if (GetClass() == CLERIC) { + value += int(base_value*RuleI(Spells, ClericInnateHealFocus) / 100); //confirmed on live parsing clerics get an innate 5 pct heal focus + } + value += int(base_value*GetFocusEffect(focusImprovedHeal, spell_id) / 100); + value += int(base_value*GetFocusEffect(focusFcAmplifyMod, spell_id) / 100); - value += value*target->GetHealRate(spell_id, this)/100; + // Instant Heals + if (spells[spell_id].buff_duration < 1) { - if (IsNPC() && CastToNPC()->GetHealScale()) + if (target) { + value += int(base_value * target->GetFocusEffect(focusFcHealPctIncoming, spell_id, this)/100); //SPA 393 Add before critical + value += int(base_value * target->GetFocusEffect(focusFcHealPctCritIncoming, spell_id, this)/100); //SPA 395 Add before critical (?) + } + + value += GetFocusEffect(focusFcHealAmtCrit, spell_id); //SPA 396 Add before critical + + //Using IgnoreSpellDmgLvlRestriction to also allow healing to scale + if (RuleB(Spells, IgnoreSpellDmgLvlRestriction) && !spells[spell_id].no_heal_damage_item_mod && itembonuses.HealAmt) { + value += GetExtraSpellAmt(spell_id, itembonuses.HealAmt, base_value);//Item Heal Amt Add before critical + } + else if (!spells[spell_id].no_heal_damage_item_mod && itembonuses.HealAmt && spells[spell_id].classes[(GetClass() % 17) - 1] >= GetLevel() - 5) { + value += GetExtraSpellAmt(spell_id, itembonuses.HealAmt, base_value);//Item Heal Amt Add before critical + } + + if (target) { + value += value * target->GetHealRate() / 100; //SPA 120 modifies value after Focus Applied but before critical + } + + /* + Apply critical hit modifier + */ + + value *= critical_modifier; + value += GetFocusEffect(focusFcHealAmt, spell_id); //SPA 392 Add after critical + value += GetFocusEffect(focusFcAmplifyAmt, spell_id); //SPA 508 ? Add after critical + + if (target) { + value += target->GetFocusEffect(focusFcHealAmtIncoming, spell_id, this); //SPA 394 Add after critical + } + + if (IsNPC() && CastToNPC()->GetHealScale()) { value = int(static_cast(value) * CastToNPC()->GetHealScale() / 100.0f); + } - if (Critical) { + if (critical_modifier > 1) { entity_list.MessageCloseString( this, true, 100, Chat::SpellCrit, OTHER_CRIT_HEAL, GetName(), itoa(value)); - if (IsClient()) + if (IsClient()) { MessageString(Chat::SpellCrit, YOU_CRIT_HEAL, itoa(value)); + } } return value; @@ -323,20 +408,32 @@ int32 Mob::GetActSpellHealing(uint16 spell_id, int32 value, Mob* target) { //Heal over time spells. [Heal Rate and Additional Healing effects do not increase this value] else { + //Using IgnoreSpellDmgLvlRestriction to also allow healing to scale + int32 extra_heal = 0; + if (RuleB(Spells, HOTsScaleWithHealAmt)) { + if (RuleB(Spells, IgnoreSpellDmgLvlRestriction) && !spells[spell_id].no_heal_damage_item_mod && itembonuses.HealAmt) { + extra_heal += GetExtraSpellAmt(spell_id, itembonuses.HealAmt, base_value); + } + else if(!spells[spell_id].no_heal_damage_item_mod && itembonuses.HealAmt && spells[spell_id].classes[(GetClass() % 17) - 1] >= GetLevel() - 5) { + extra_heal += GetExtraSpellAmt(spell_id, itembonuses.HealAmt, base_value); + } + } + + if (extra_heal) { + int duration = CalcBuffDuration(this, target, spell_id); + if (duration > 0) { + extra_heal /= duration; + value += extra_heal; + } + } - chance = itembonuses.CriticalHealOverTime + spellbonuses.CriticalHealOverTime + aabonuses.CriticalHealOverTime; - - chance += target->GetFocusIncoming(focusFcHealPctCritIncoming, SE_FcHealPctCritIncoming, this, spell_id); - - if (spellbonuses.CriticalRegenDecay) - chance += GetDecayEffectValue(spell_id, SE_CriticalRegenDecay); - - if(chance && zone->random.Roll(chance)) - value *= 2; + if (critical_chance && zone->random.Roll(critical_chance)) + value *= critical_modifier; } - if (IsNPC() && CastToNPC()->GetHealScale()) + if (IsNPC() && CastToNPC()->GetHealScale()) { value = int(static_cast(value) * CastToNPC()->GetHealScale() / 100.0f); + } return value; } @@ -409,29 +506,6 @@ int32 Mob::GetActSpellDuration(uint16 spell_id, int32 duration) return ifocused + 1; } -int32 Client::GetActSpellCasttime(uint16 spell_id, int32 casttime) -{ - int32 cast_reducer = 0; - cast_reducer += GetFocusEffect(focusSpellHaste, spell_id); - - //this function loops through the effects of spell_id many times - //could easily be consolidated. - - if (GetLevel() >= 51 && casttime >= 3000 && !BeneficialSpell(spell_id) - && (GetClass() == SHADOWKNIGHT || GetClass() == RANGER - || GetClass() == PALADIN || GetClass() == BEASTLORD )) - cast_reducer += (GetLevel()-50)*3; - - //LIVE AA SpellCastingDeftness, QuickBuff, QuickSummoning, QuickEvacuation, QuickDamage - - if (cast_reducer > RuleI(Spells, MaxCastTimeReduction)) - cast_reducer = RuleI(Spells, MaxCastTimeReduction); - - casttime = (casttime*(100 - cast_reducer)/100); - - return casttime; -} - bool Client::TrainDiscipline(uint32 itemid) { //get the item info @@ -534,6 +608,90 @@ bool Client::TrainDiscipline(uint32 itemid) { return(false); } +bool Client::MemorizeSpellFromItem(uint32 item_id) { + const EQ::ItemData *item = database.GetItem(item_id); + if(item == nullptr) { + Message(Chat::Red, "Unable to find the scroll!"); + LogError("Unable to find scroll id [{}]\n", (unsigned long)item_id); + return false; + } + + if (!item->IsClassCommon() || item->ItemType != EQ::item::ItemTypeSpell) { + Message(Chat::Red, "Invalid item type, you cannot learn from this item."); + SummonItem(item_id); + return false; + } + + if(!( + item->Name[0] == 'S' && + item->Name[1] == 'p' && + item->Name[2] == 'e' && + item->Name[3] == 'l' && + item->Name[4] == 'l' && + item->Name[5] == ':' && + item->Name[6] == ' ' + )) { + Message(Chat::Red, "This item is not a scroll."); + SummonItem(item_id); + return false; + } + int player_class = GetClass(); + uint32 cbit = 1 << (player_class - 1); + if(!(item->Classes & cbit)) { + Message(Chat::Red, "Your class cannot learn from this scroll."); + SummonItem(item_id); + return false; + } + + uint32 spell_id = item->Scroll.Effect; + if(!IsValidSpell(spell_id)) { + Message(Chat::Red, "This scroll contains invalid knowledge."); + return false; + } + + const SPDat_Spell_Struct &spell = spells[spell_id]; + uint8 level_to_use = spell.classes[player_class - 1]; + if(level_to_use == 255) { + Message(Chat::Red, "Your class cannot learn from this scroll."); + SummonItem(item_id); + return false; + } + + if(level_to_use > GetLevel()) { + Message(Chat::Red, "You must be at least level %d to learn this spell.", level_to_use); + SummonItem(item_id); + return false; + } + + for (int index = 0; index < EQ::spells::SPELLBOOK_SIZE; index++) { + if (!HasSpellScribed(spell_id)) { + auto next_slot = GetNextAvailableSpellBookSlot(); + if (next_slot != -1) { + ScribeSpell(spell_id, next_slot); + return true; + } + else { + Message( + Chat::Red, + "Unable to scribe spell %s (%i) to spellbook: no more spell book slots available.", + ((spell_id >= 0 && spell_id < SPDAT_RECORDS) ? spells[spell_id].name : "Out-of-range"), + spell_id + ); + SummonItem(item_id); + return false; + } + } + else { + Message(Chat::Red, "You already know this spell."); + SummonItem(item_id); + return false; + } + } + + Message(Chat::Red, "You have learned too many spells and can learn no more."); + return false; +} + void Client::TrainDiscBySpellID(int32 spell_id) { int i; @@ -570,8 +728,15 @@ void Client::SendDisciplineUpdate() { bool Client::UseDiscipline(uint32 spell_id, uint32 target) { // Dont let client waste a reuse timer if they can't use the disc - if (IsStunned() || IsFeared() || IsMezzed() || IsAmnesiad() || IsPet()) + if ((IsStunned() && !IgnoreCastingRestriction(spell_id))|| + IsFeared() || + (IsMezzed() && !IgnoreCastingRestriction(spell_id)) || + IsAmnesiad() || + IsPet()) { + if (IsAmnesiad()) { + MessageString(Chat::Red, MELEE_SILENCE); + } return(false); } @@ -590,6 +755,10 @@ bool Client::UseDiscipline(uint32 spell_id, uint32 target) { return(false); } + if (DivineAura() && !IgnoreCastingRestriction(spell_id)) { + return false; + } + //can we use the spell? const SPDat_Spell_Struct &spell = spells[spell_id]; uint8 level_to_use = spell.classes[GetClass() - 1]; @@ -605,7 +774,7 @@ bool Client::UseDiscipline(uint32 spell_id, uint32 target) { return(false); } - if(GetEndurance() < spell.EndurCost) { + if(GetEndurance() < spell.endurance_cost) { Message(11, "You are too fatigued to use this skill right now."); return(false); } @@ -617,22 +786,26 @@ bool Client::UseDiscipline(uint32 spell_id, uint32 target) { } // the client does this check before calling CastSpell, should prevent discs being eaten - if (spell.buffdurationformula != 0 && spell.targettype == ST_Self && HasDiscBuff()) + if (spell.buff_duration_formula != 0 && spell.target_type == ST_Self && HasDiscBuff()) return false; //Check the disc timer - pTimerType DiscTimer = pTimerDisciplineReuseStart + spell.EndurTimerIndex; + pTimerType DiscTimer = pTimerDisciplineReuseStart + spell.timer_id; if(!p_timers.Expired(&database, DiscTimer, false)) { // lets not set the reuse timer in case CastSpell fails (or we would have to turn off the timer, but CastSpell will set it as well) - /*char val1[20]={0};*/ //unused - /*char val2[20]={0};*/ //unused - uint32 remain = p_timers.GetRemainingTime(DiscTimer); - //MessageString(Chat::White, DISCIPLINE_CANUSEIN, ConvertArray((remain)/60,val1), ConvertArray(remain%60,val2)); - Message(0, "You can use this discipline in %d minutes %d seconds.", ((remain)/60), (remain%60)); - return(false); + uint32 remaining_time = p_timers.GetRemainingTime(DiscTimer); + Message( + Chat::White, + fmt::format( + "You can use this discipline in {}.", + ConvertSecondsToTime(remaining_time) + ).c_str() + ); + return false; } - if(spell.recast_time > 0) - { + bool instant_recast = true; + + if (spell.recast_time > 0) { uint32 reduced_recast = spell.recast_time / 1000; auto focus = GetFocusEffect(focusReduceRecastTime, spell_id); // do stupid stuff because custom servers. @@ -643,22 +816,36 @@ bool Client::UseDiscipline(uint32 spell_id, uint32 target) { reduced_recast = 0; if (GetPTimers().Enabled((uint32)DiscTimer)) GetPTimers().Clear(&database, (uint32)DiscTimer); - } else { + } + else { reduced_recast -= focus; } - if (reduced_recast > 0) - CastSpell(spell_id, target, EQ::spells::CastingSlot::Discipline, -1, -1, 0, -1, (uint32)DiscTimer, reduced_recast); - else{ - CastSpell(spell_id, target, EQ::spells::CastingSlot::Discipline); - return true; - } + if (reduced_recast > 0) { + instant_recast = false; - SendDisciplineTimer(spells[spell_id].EndurTimerIndex, reduced_recast); + if (GetClass() == BARD && IsCasting() && spells[spell_id].cast_time == 0) { + if (DoCastingChecksOnCaster(spell_id)) { + SpellFinished(spell_id, entity_list.GetMob(target), EQ::spells::CastingSlot::Discipline, 0, -1, spells[spell_id].resist_difficulty, false, -1, (uint32)DiscTimer, reduced_recast, false); + } + } + else { + CastSpell(spell_id, target, EQ::spells::CastingSlot::Discipline, -1, -1, 0, -1, (uint32)DiscTimer, reduced_recast); + } + + SendDisciplineTimer(spells[spell_id].timer_id, reduced_recast); + } } - else - { - CastSpell(spell_id, target, EQ::spells::CastingSlot::Discipline); + + if (instant_recast) { + if (GetClass() == BARD && IsCasting() && spells[spell_id].cast_time == 0) { + if (DoCastingChecksOnCaster(spell_id)) { + SpellFinished(spell_id, entity_list.GetMob(target), EQ::spells::CastingSlot::Discipline, 0, -1, spells[spell_id].resist_difficulty, false, -1, 0xFFFFFFFF, 0, false); + } + } + else { + CastSpell(spell_id, target, EQ::spells::CastingSlot::Discipline); + } } return(true); } @@ -666,7 +853,7 @@ bool Client::UseDiscipline(uint32 spell_id, uint32 target) { uint32 Client::GetDisciplineTimer(uint32 timer_id) { pTimerType disc_timer_id = pTimerDisciplineReuseStart + timer_id; uint32 disc_timer = 0; - if (GetPTimers().Enabled((uint32)disc_timer_id)) { + if (GetPTimers().Enabled(disc_timer_id)) { disc_timer = GetPTimers().GetRemainingTime(disc_timer_id); } return disc_timer; @@ -674,12 +861,22 @@ uint32 Client::GetDisciplineTimer(uint32 timer_id) { void Client::ResetDisciplineTimer(uint32 timer_id) { pTimerType disc_timer_id = pTimerDisciplineReuseStart + timer_id; - if (GetPTimers().Enabled((uint32)disc_timer_id)) { - GetPTimers().Clear(&database, (uint32)disc_timer_id); + if (GetPTimers().Enabled(disc_timer_id)) { + GetPTimers().Clear(&database, disc_timer_id); } SendDisciplineTimer(timer_id, 0); } +void Client::ResetAllDisciplineTimers() { + for (pTimerType disc_timer_id = pTimerDisciplineReuseStart; disc_timer_id <= pTimerDisciplineReuseEnd; disc_timer_id++) { + uint32 current_timer_id = (disc_timer_id - pTimerDisciplineReuseStart); + if (GetPTimers().Enabled(disc_timer_id)) { + GetPTimers().Clear(&database, disc_timer_id); + } + SendDisciplineTimer(current_timer_id, 0); + } +} + bool Client::HasDisciplineLearned(uint16 spell_id) { bool has_learned = false; for (auto index = 0; index < MAX_PP_DISCIPLINES; ++index) { @@ -763,7 +960,7 @@ void EntityList::AESpell( ) { const auto &cast_target_position = - spells[spell_id].targettype == ST_Ring ? + spells[spell_id].target_type == ST_Ring ? caster_mob->GetTargetRingLocation() : static_cast(center_mob->GetPosition()); @@ -786,14 +983,14 @@ void EntityList::AESpell( /** * Max AOE targets */ - int max_targets_allowed = 0; // unlimited + int max_targets_allowed = RuleI(Range, AOEMaxTargets); // unlimited if (max_targets) { // rains pass this in since they need to preserve the count through waves max_targets_allowed = *max_targets; } - else if (spells[spell_id].aemaxtargets) { - max_targets_allowed = spells[spell_id].aemaxtargets; + else if (spells[spell_id].aoe_max_targets) { + max_targets_allowed = spells[spell_id].aoe_max_targets; } - else if (IsTargetableAESpell(spell_id) && is_detrimental_spell && !is_npc) { + else if (IsTargetableAESpell(spell_id) && is_detrimental_spell && !is_npc && !IsEffectInSpell(spell_id, SE_Lull) && !IsEffectInSpell(spell_id, SE_Mez)) { max_targets_allowed = 4; } @@ -823,15 +1020,15 @@ void EntityList::AESpell( continue; } - if (spells[spell_id].targettype == ST_TargetAENoPlayersPets && current_mob->IsPetOwnerClient()) { + if (spells[spell_id].target_type == ST_TargetAENoPlayersPets && current_mob->IsPetOwnerClient()) { continue; } - if (spells[spell_id].targettype == ST_AreaClientOnly && !current_mob->IsClient()) { + if (spells[spell_id].target_type == ST_AreaClientOnly && !current_mob->IsClient()) { continue; } - if (spells[spell_id].targettype == ST_AreaNPCOnly && !current_mob->IsNPC()) { + if (spells[spell_id].target_type == ST_AreaNPCOnly && !current_mob->IsNPC()) { continue; } @@ -865,21 +1062,21 @@ void EntityList::AESpell( } if (is_npc && current_mob->IsNPC() && - spells[spell_id].targettype != ST_AreaNPCOnly) { //check npc->npc casting + spells[spell_id].target_type != ST_AreaNPCOnly) { //check npc->npc casting FACTION_VALUE faction_value = current_mob->GetReverseFactionCon(caster_mob); if (is_detrimental_spell) { //affect mobs that are on our hate list, or //which have bad faction with us if ( !(caster_mob->CheckAggro(current_mob) || - faction_value == FACTION_THREATENLY || + faction_value == FACTION_THREATENINGLY || faction_value == FACTION_SCOWLS)) { continue; } } else { //only affect mobs we would assist. - if (!(faction_value <= FACTION_AMIABLE)) { + if (!(faction_value <= FACTION_AMIABLY)) { continue; } } @@ -919,6 +1116,9 @@ void EntityList::AESpell( } } + current_mob->CalcSpellPowerDistanceMod(spell_id, distance_to_target); + caster_mob->SpellOnTarget(spell_id, current_mob, 0, true, resist_adjust); + /** * Increment hit count if max targets */ @@ -928,9 +1128,6 @@ void EntityList::AESpell( break; } } - - current_mob->CalcSpellPowerDistanceMod(spell_id, distance_to_target); - caster_mob->SpellOnTarget(spell_id, current_mob, false, true, resist_adjust); } LogAoeCast("Done iterating [{}]", caster_mob->GetCleanName()); @@ -1001,90 +1198,6 @@ void EntityList::MassGroupBuff( } } -/** - * Causes caster to hit every mob within dist range of center with a bard pulse of spell_id - * NPC spells will only affect other NPCs with compatible faction - * - * @param caster - * @param center - * @param spell_id - * @param affect_caster - */ -void EntityList::AEBardPulse( - Mob *caster, - Mob *center, - uint16 spell_id, - bool affect_caster) -{ - Mob *current_mob = nullptr; - float distance = caster->GetAOERange(spell_id); - float distance_squared = distance * distance; - bool is_detrimental_spell = IsDetrimentalSpell(spell_id); - bool is_npc = caster->IsNPC(); - - for (auto &it : entity_list.GetCloseMobList(caster, distance)) { - current_mob = it.second; - - /** - * Skip self - */ - if (current_mob == center) { - continue; - } - - if (current_mob == caster && !affect_caster) { - continue; - } - - if (DistanceSquared(center->GetPosition(), current_mob->GetPosition()) > distance_squared) { //make sure they are in range - continue; - } - - /** - * check npc->npc casting - */ - if (is_npc && current_mob->IsNPC()) { - FACTION_VALUE faction = current_mob->GetReverseFactionCon(caster); - if (is_detrimental_spell) { - //affect mobs that are on our hate list, or - //which have bad faction with us - if (!(caster->CheckAggro(current_mob) || faction == FACTION_THREATENLY || faction == FACTION_SCOWLS)) { - continue; - } - } - else { - //only affect mobs we would assist. - if (!(faction <= FACTION_AMIABLE)) { - continue; - } - } - } - - /** - * LOS - */ - if (is_detrimental_spell) { - if (!center->CheckLosFN(current_mob)) { - continue; - } - } - else { // check to stop casting beneficial ae buffs (to wit: bard songs) on enemies... - // See notes in AESpell() above for more info. - if (caster->IsAttackAllowed(current_mob, true)) { - continue; - } - if (caster->CheckAggro(current_mob)) { - continue; - } - } - - current_mob->BardPulse(spell_id, caster); - } - if (caster->IsClient()) { - caster->CastToClient()->CheckSongSkillIncrease(spell_id); - } -} - /** * Rampage - Normal and Duration rampages * NPCs handle it differently in Mob::Rampage @@ -1100,7 +1213,8 @@ void EntityList::AEAttack( float distance, int Hand, int count, - bool is_from_spell) + bool is_from_spell, + int attack_rounds) { Mob *current_mob = nullptr; float distance_squared = distance * distance; @@ -1112,15 +1226,18 @@ void EntityList::AEAttack( if (current_mob->IsNPC() && current_mob != attacker //this is not needed unless NPCs can use this && (attacker->IsAttackAllowed(current_mob)) - && current_mob->GetRace() != 216 && current_mob->GetRace() != 472 /* dont attack horses */ + && !current_mob->IsHorse() /* dont attack mounts */ && (DistanceSquared(current_mob->GetPosition(), attacker->GetPosition()) <= distance_squared) ) { - if (!attacker->IsClient() || attacker->GetClass() == MONK || attacker->GetClass() == RANGER) { - attacker->Attack(current_mob, Hand, false, false, is_from_spell); - } - else { - attacker->CastToClient()->DoAttackRounds(current_mob, Hand, is_from_spell); + for (int i = 0; i < attack_rounds; i++) { + + if (!attacker->IsClient() || attacker->GetClass() == MONK || attacker->GetClass() == RANGER) { + attacker->Attack(current_mob, Hand, false, false, is_from_spell); + } + else { + attacker->CastToClient()->DoAttackRounds(current_mob, Hand, is_from_spell); + } } hit_count++; diff --git a/zone/embparser.cpp b/zone/embparser.cpp index c19ac6671..c74b7de42 100644 --- a/zone/embparser.cpp +++ b/zone/embparser.cpp @@ -119,7 +119,13 @@ const char *QuestEventSubroutines[_LargestEventID] = { "EVENT_DEATH_ZONE", "EVENT_USE_SKILL", "EVENT_COMBINE_VALIDATE", - "EVENT_BOT_COMMAND" + "EVENT_BOT_COMMAND", + "EVENT_WARP", + "EVENT_TEST_BUFF", + "EVENT_COMBINE", + "EVENT_CONSIDER", + "EVENT_CONSIDER_CORPSE", + "EVENT_LOOT_ZONE" }; PerlembParser::PerlembParser() : perl(nullptr) @@ -165,7 +171,7 @@ void PerlembParser::ReloadQuests() } int PerlembParser::EventCommon( - QuestEventID event, uint32 objid, const char *data, NPC *npcmob, EQ::ItemInstance *item_inst, Mob *mob, + QuestEventID event, uint32 objid, const char *data, NPC *npcmob, EQ::ItemInstance *item_inst, const SPDat_Spell_Struct* spell, Mob *mob, uint32 extradata, bool global, std::vector *extra_pointers ) { @@ -246,21 +252,17 @@ int PerlembParser::EventCommon( } if (isPlayerQuest || isGlobalPlayerQuest) { - return SendCommands(package_name.c_str(), sub_name, 0, mob, mob, nullptr); - } - else if (isItemQuest) { - return SendCommands(package_name.c_str(), sub_name, 0, mob, mob, item_inst); - } - else if (isSpellQuest) { + return SendCommands(package_name.c_str(), sub_name, 0, mob, mob, nullptr, nullptr); + } else if (isItemQuest) { + return SendCommands(package_name.c_str(), sub_name, 0, mob, mob, item_inst, nullptr); + } else if (isSpellQuest) { if (mob) { - return SendCommands(package_name.c_str(), sub_name, 0, mob, mob, nullptr); + return SendCommands(package_name.c_str(), sub_name, 0, mob, mob, nullptr, spell); + } else { + return SendCommands(package_name.c_str(), sub_name, 0, npcmob, mob, nullptr, spell); } - else { - return SendCommands(package_name.c_str(), sub_name, 0, npcmob, mob, nullptr); - } - } - else { - return SendCommands(package_name.c_str(), sub_name, objid, npcmob, mob, nullptr); + } else { + return SendCommands(package_name.c_str(), sub_name, objid, npcmob, mob, nullptr, nullptr); } } @@ -269,7 +271,7 @@ int PerlembParser::EventNPC( std::vector *extra_pointers ) { - return EventCommon(evt, npc->GetNPCTypeID(), data.c_str(), npc, nullptr, init, extra_data, false, extra_pointers); + return EventCommon(evt, npc->GetNPCTypeID(), data.c_str(), npc, nullptr, nullptr, init, extra_data, false, extra_pointers); } int PerlembParser::EventGlobalNPC( @@ -277,7 +279,7 @@ int PerlembParser::EventGlobalNPC( std::vector *extra_pointers ) { - return EventCommon(evt, npc->GetNPCTypeID(), data.c_str(), npc, nullptr, init, extra_data, true, extra_pointers); + return EventCommon(evt, npc->GetNPCTypeID(), data.c_str(), npc, nullptr, nullptr, init, extra_data, true, extra_pointers); } int PerlembParser::EventPlayer( @@ -285,7 +287,7 @@ int PerlembParser::EventPlayer( std::vector *extra_pointers ) { - return EventCommon(evt, 0, data.c_str(), nullptr, nullptr, client, extra_data, false, extra_pointers); + return EventCommon(evt, 0, data.c_str(), nullptr, nullptr, nullptr, client, extra_data, false, extra_pointers); } int PerlembParser::EventGlobalPlayer( @@ -293,7 +295,7 @@ int PerlembParser::EventGlobalPlayer( std::vector *extra_pointers ) { - return EventCommon(evt, 0, data.c_str(), nullptr, nullptr, client, extra_data, true, extra_pointers); + return EventCommon(evt, 0, data.c_str(), nullptr, nullptr, nullptr, client, extra_data, true, extra_pointers); } int PerlembParser::EventItem( @@ -302,15 +304,15 @@ int PerlembParser::EventItem( ) { // needs pointer validation on 'item' argument - return EventCommon(evt, item->GetID(), nullptr, nullptr, item, client, extra_data, false, extra_pointers); + return EventCommon(evt, item->GetID(), nullptr, nullptr, item, nullptr, client, extra_data, false, extra_pointers); } int PerlembParser::EventSpell( - QuestEventID evt, NPC *npc, Client *client, uint32 spell_id, uint32 extra_data, + QuestEventID evt, NPC *npc, Client *client, uint32 spell_id, std::string data, uint32 extra_data, std::vector *extra_pointers ) { - return EventCommon(evt, 0, itoa(spell_id), npc, nullptr, client, extra_data, false, extra_pointers); + return EventCommon(evt, spell_id, data.c_str(), npc, nullptr, &spells[spell_id], client, extra_data, false, extra_pointers); } bool PerlembParser::HasQuestSub(uint32 npcid, QuestEventID evt) @@ -773,10 +775,11 @@ void PerlembParser::ExportVar(const char *pkgprefix, const char *varname, const int PerlembParser::SendCommands( const char *pkgprefix, const char *event, - uint32 npcid, + uint32 object_id, Mob *other, Mob *mob, - EQ::ItemInstance *item_inst + EQ::ItemInstance *item_inst, + const SPDat_Spell_Struct *spell ) { if (!perl) { @@ -785,10 +788,10 @@ int PerlembParser::SendCommands( int ret_value = 0; if (mob && mob->IsClient()) { - quest_manager.StartQuest(other, mob->CastToClient(), item_inst); + quest_manager.StartQuest(other, mob->CastToClient(), item_inst, spell); } else { - quest_manager.StartQuest(other, nullptr, nullptr); + quest_manager.StartQuest(other); } try { @@ -802,6 +805,7 @@ int PerlembParser::SendCommands( std::string cl = (std::string) "$" + (std::string) pkgprefix + (std::string) "::client"; std::string np = (std::string) "$" + (std::string) pkgprefix + (std::string) "::npc"; std::string qi = (std::string) "$" + (std::string) pkgprefix + (std::string) "::questitem"; + std::string sp = (std::string) "$" + (std::string) pkgprefix + (std::string) "::spell"; std::string enl = (std::string) "$" + (std::string) pkgprefix + (std::string) "::entity_list"; if (clear_vars_.find(cl) != clear_vars_.end()) { std::string eval_str = cl; @@ -821,6 +825,12 @@ int PerlembParser::SendCommands( perl->eval(eval_str.c_str()); } + if (clear_vars_.find(sp) != clear_vars_.end()) { + std::string eval_str = sp; + eval_str += " = undef;"; + perl->eval(eval_str.c_str()); + } + if (clear_vars_.find(enl) != clear_vars_.end()) { std::string eval_str = enl; eval_str += " = undef;"; @@ -858,6 +868,14 @@ int PerlembParser::SendCommands( sv_setref_pv(questitem, "QuestItem", curi); } + if (spell) { + const SPDat_Spell_Struct* current_spell = quest_manager.GetQuestSpell(); + SPDat_Spell_Struct* real_spell = const_cast(current_spell); + snprintf(namebuf, 64, "%s::spell", pkgprefix); + SV *spell = get_sv(namebuf, true); + sv_setref_pv(spell, "Spell", (void *) real_spell); + } + snprintf(namebuf, 64, "%s::entity_list", pkgprefix); SV *el = get_sv(namebuf, true); sv_setref_pv(el, "EntityList", &entity_list); @@ -871,10 +889,12 @@ int PerlembParser::SendCommands( std::string cl = (std::string) "$" + (std::string) pkgprefix + (std::string) "::client"; std::string np = (std::string) "$" + (std::string) pkgprefix + (std::string) "::npc"; std::string qi = (std::string) "$" + (std::string) pkgprefix + (std::string) "::questitem"; + std::string sp = (std::string) "$" + (std::string) pkgprefix + (std::string) "::spell"; std::string enl = (std::string) "$" + (std::string) pkgprefix + (std::string) "::entity_list"; clear_vars_[cl] = 1; clear_vars_[np] = 1; clear_vars_[qi] = 1; + clear_vars_[sp] = 1; clear_vars_[enl] = 1; } #endif @@ -965,6 +985,9 @@ void PerlembParser::MapFunctions() "package QuestItem;" "&boot_QuestItem;" // load quest Item XS + "package Spell;" + "&boot_Spell;" // load quest Spell XS + "package HateEntry;" "&boot_HateEntry;" // load quest Hate XS @@ -977,6 +1000,14 @@ void PerlembParser::MapFunctions() "package Expedition;" "&boot_Expedition;" +#ifdef BOTS + "package Bot;" + "our @ISA = qw(NPC);" // Bot inherits NPC + "&boot_Mob;" // load our Mob XS + "&boot_NPC;" // load our NPC XS + "&boot_Bot;" // load our Bot XS +#endif + #endif "package main;" "}" @@ -990,8 +1021,8 @@ void PerlembParser::GetQuestTypes( { if (event == EVENT_SPELL_EFFECT_CLIENT || event == EVENT_SPELL_EFFECT_NPC || - event == EVENT_SPELL_BUFF_TIC_CLIENT || - event == EVENT_SPELL_BUFF_TIC_NPC || + event == EVENT_SPELL_EFFECT_BUFF_TIC_CLIENT || + event == EVENT_SPELL_EFFECT_BUFF_TIC_NPC || event == EVENT_SPELL_FADE || event == EVENT_SPELL_EFFECT_TRANSLOCATE_COMPLETE) { isSpellQuest = true; @@ -1028,31 +1059,31 @@ void PerlembParser::GetQuestPackageName( bool global ) { - if (!isPlayerQuest && !isGlobalPlayerQuest && !isItemQuest && !isSpellQuest) { + if ( + !isPlayerQuest && + !isGlobalPlayerQuest && + !isItemQuest && + !isSpellQuest + ) { if (global) { isGlobalNPC = true; package_name = "qst_global_npc"; - } - else { + } else { package_name = "qst_npc_"; - package_name += itoa(npcmob->GetNPCTypeID()); + package_name += std::to_string(npcmob->GetNPCTypeID()); } - } - else if (isItemQuest) { + } else if (isItemQuest) { // need a valid EQ::ItemInstance pointer check here..unsure how to cancel this process const EQ::ItemData *item = item_inst->GetItem(); package_name = "qst_item_"; - package_name += itoa(item->ID); - } - else if (isPlayerQuest) { + package_name += std::to_string(item->ID); + } else if (isPlayerQuest) { package_name = "qst_player"; - } - else if (isGlobalPlayerQuest) { + } else if (isGlobalPlayerQuest) { package_name = "qst_global_player"; - } - else { + } else { package_name = "qst_spell_"; - package_name += data; + package_name += std::to_string(objid); } } @@ -1398,12 +1429,14 @@ void PerlembParser::ExportEventVariables( ExportVar(package_name.c_str(), "version", zone->GetInstanceVersion()); break; } - + + case EVENT_LOOT_ZONE: case EVENT_LOOT: { Seperator sep(data); ExportVar(package_name.c_str(), "looted_id", sep.arg[0]); ExportVar(package_name.c_str(), "looted_charges", sep.arg[1]); ExportVar(package_name.c_str(), "corpse", sep.arg[2]); + ExportVar(package_name.c_str(), "corpse_id", sep.arg[3]); break; } @@ -1509,11 +1542,17 @@ void PerlembParser::ExportEventVariables( break; } + case EVENT_SPELL_EFFECT_BUFF_TIC_CLIENT: + case EVENT_SPELL_EFFECT_BUFF_TIC_NPC: case EVENT_SPELL_EFFECT_CLIENT: case EVENT_SPELL_EFFECT_NPC: - case EVENT_SPELL_BUFF_TIC_CLIENT: - case EVENT_SPELL_BUFF_TIC_NPC: { - ExportVar(package_name.c_str(), "caster_id", extradata); + case EVENT_SPELL_FADE: { + Seperator sep(data); + ExportVar(package_name.c_str(), "spell_id", objid); + ExportVar(package_name.c_str(), "caster_id", sep.arg[0]); + ExportVar(package_name.c_str(), "tics_remaining", sep.arg[1]); + ExportVar(package_name.c_str(), "caster_level", sep.arg[2]); + ExportVar(package_name.c_str(), "buff_slot", sep.arg[3]); break; } @@ -1627,6 +1666,28 @@ void PerlembParser::ExportEventVariables( ExportVar(package_name.c_str(), "langid", extradata); break; } + case EVENT_WARP: { + Seperator sep(data); + ExportVar(package_name.c_str(), "from_x", sep.arg[0]); + ExportVar(package_name.c_str(), "from_y", sep.arg[1]); + ExportVar(package_name.c_str(), "from_z", sep.arg[2]); + break; + } + + case EVENT_CONSIDER: { + ExportVar(package_name.c_str(), "entity_id", std::stoi(data)); + break; + } + + case EVENT_CONSIDER_CORPSE: { + ExportVar(package_name.c_str(), "corpse_entity_id", std::stoi(data)); + break; + } + + case EVENT_COMBINE: { + ExportVar(package_name.c_str(), "container_slot", std::stoi(data)); + break; + } default: { break; diff --git a/zone/embparser.h b/zone/embparser.h index ed7989a99..52bba1589 100644 --- a/zone/embparser.h +++ b/zone/embparser.h @@ -58,7 +58,7 @@ public: std::vector *extra_pointers); virtual int EventItem(QuestEventID evt, Client *client, EQ::ItemInstance *item, Mob *mob, std::string data, uint32 extra_data, std::vector *extra_pointers); - virtual int EventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, uint32 extra_data, + virtual int EventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, std::string data, uint32 extra_data, std::vector *extra_pointers); virtual bool HasQuestSub(uint32 npcid, QuestEventID evt); @@ -90,9 +90,9 @@ private: void ExportVar(const char *pkgprefix, const char *varname, float value); void ExportVarComplex(const char *pkgprefix, const char *varname, const char *value); - int EventCommon(QuestEventID event, uint32 objid, const char * data, NPC* npcmob, EQ::ItemInstance* item_inst, Mob* mob, + int EventCommon(QuestEventID event, uint32 objid, const char * data, NPC* npcmob, EQ::ItemInstance* item_inst, const SPDat_Spell_Struct* spell, Mob* mob, uint32 extradata, bool global, std::vector *extra_pointers); - int SendCommands(const char *pkgprefix, const char *event, uint32 npcid, Mob* other, Mob* mob, EQ::ItemInstance *item_inst); + int SendCommands(const char *pkgprefix, const char *event, uint32 spell_id, Mob* other, Mob* mob, EQ::ItemInstance *item_inst, const SPDat_Spell_Struct *spell); void MapFunctions(); void GetQuestTypes(bool &isPlayerQuest, bool &isGlobalPlayerQuest, bool &isGlobalNPC, bool &isItemQuest, diff --git a/zone/embparser_api.cpp b/zone/embparser_api.cpp index 2ffc56c23..04a8654f1 100644 --- a/zone/embparser_api.cpp +++ b/zone/embparser_api.cpp @@ -138,6 +138,21 @@ XS(XS_QuestItem_new) { XSRETURN(1); } +//Any creation of new Spells gets the current Spell +XS(XS_Spell_new); +XS(XS_Spell_new) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::Spell::new()"); + + const SPDat_Spell_Struct* spell = quest_manager.GetQuestSpell(); + ST(0) = sv_newmortal(); + if (spell) + sv_setref_pv(ST(0), "Spell", (void *) spell); + + XSRETURN(1); +} + //Any creation of new quest items gets the current quest item XS(XS_MobList_new); XS(XS_MobList_new) { @@ -805,9 +820,9 @@ XS(XS__changedeity) { if (items != 1) Perl_croak(aTHX_ "Usage: quest::changedeity(int deity_id)"); - int diety_id = (int) SvIV(ST(0)); + int deity_id = (int) SvIV(ST(0)); - quest_manager.changedeity(diety_id); + quest_manager.changedeity(deity_id); XSRETURN_EMPTY; } @@ -911,9 +926,9 @@ XS(XS__get_spell_level) { uint8 spell_level = IsValidSpell(spell_id) ? GetSpellLevel(spell_id, class_id) : 0; uint8 server_max_level = RuleI(Character, MaxLevel); - if (spell_level && spell_level > server_max_level) + if (spell_level && spell_level > server_max_level) spell_level = 0; - + XSprePUSH; PUSHu((UV)spell_level); @@ -1495,14 +1510,19 @@ XS(XS__ding) { XS(XS__rebind); XS(XS__rebind) { dXSARGS; - if (items != 4) - Perl_croak(aTHX_ "Usage: quest::rebind(int zone_id, float x, float y, float z)"); - - int zone_id = (int) SvIV(ST(0)); - auto location = glm::vec3((float) SvNV(ST(1)), (float) SvNV(ST(2)), (float) SvNV(ST(3))); - - quest_manager.rebind(zone_id, location); + if (items < 4 || items > 5) + Perl_croak(aTHX_ "Usage: quest::rebind(int zone_id, float x, float y, float z, [float heading])"); + int zone_id = (int) SvIV(ST(0)); + float target_x = (float) SvNV(ST(1)); + float target_y = (float) SvNV(ST(2)); + float target_z = (float) SvNV(ST(3)); + if (items > 4) { + float target_heading = (float) SvNV(ST(4)); + quest_manager.rebind(zone_id, glm::vec4(target_x, target_y, target_z, target_heading)); + } else { + quest_manager.rebind(zone_id, glm::vec3(target_x, target_y, target_z)); + } XSRETURN_EMPTY; } @@ -1587,27 +1607,22 @@ XS(XS__addldonpoints); XS(XS__addldonpoints) { dXSARGS; if (items != 2) - Perl_croak(aTHX_ "Usage: quest::addldonpoints(int points, int theme_id)"); + Perl_croak(aTHX_ "Usage: quest::addldonpoints(uint32 theme_id, int points)"); -long points = (long)SvIV(ST(0)); -unsigned long theme_id = (unsigned long)SvUV(ST(1)); - -quest_manager.addldonpoints(points, theme_id); - -XSRETURN_EMPTY; + uint32 theme_id = (uint32) SvUV(ST(0)); + int points = (int) SvIV(ST(1)); + quest_manager.addldonpoints(theme_id, points); + XSRETURN_EMPTY; } XS(XS__addldonwin); XS(XS__addldonwin) { dXSARGS; - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::addldonwin(int wins, int theme_id)"); - - long wins = (long)SvIV(ST(0)); - unsigned long theme_id = (unsigned long)SvUV(ST(1)); - - quest_manager.addldonwin(wins, theme_id); + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::addldonwin(uint32 theme_id)"); + uint32 theme_id = (uint32) SvUV(ST(0)); + quest_manager.addldonwin(theme_id); XSRETURN_EMPTY; } @@ -1615,13 +1630,10 @@ XS(XS__addldonloss); XS(XS__addldonloss) { dXSARGS; if (items != 2) - Perl_croak(aTHX_ "Usage: quest::addldonloss(int losses, int theme_id)"); - - long losses = (long)SvIV(ST(0)); - unsigned long theme_id = (unsigned long)SvUV(ST(1)); - - quest_manager.addldonloss(losses, theme_id); + Perl_croak(aTHX_ "Usage: quest::addldonloss(uint32 theme_id)"); + uint32 theme_id = (uint32) SvUV(ST(0)); + quest_manager.addldonloss(theme_id); XSRETURN_EMPTY; } @@ -2953,7 +2965,7 @@ XS(XS__ModifyNPCStat); XS(XS__ModifyNPCStat) { dXSARGS; if (items != 2) - Perl_croak(aTHX_ "Usage: quest::ModifyNPCStat(string key, string value)"); + Perl_croak(aTHX_ "Usage: quest::modifynpcstat(string key, string value)"); quest_manager.ModifyNPCStat(SvPV_nolen(ST(0)), SvPV_nolen(ST(1))); @@ -3028,9 +3040,8 @@ XS(XS__getnpcnamebyid) { dXSTARG; uint32 npc_id = (int) SvIV(ST(0)); - const char *npc_name = quest_manager.getnpcnamebyid(npc_id); - - sv_setpv(TARG, npc_name); + auto npc_name = quest_manager.getnpcnamebyid(npc_id); + sv_setpv(TARG, npc_name.c_str()); XSprePUSH; PUSHTARG; XSRETURN(1); @@ -3040,7 +3051,7 @@ XS(XS__UpdateSpawnTimer); XS(XS__UpdateSpawnTimer) { dXSARGS; if (items != 2) - Perl_croak(aTHX_ "Usage: quest::UpdateSpawnTimer(uint32 spawn2_id, uint32 updated_time_till_repop)"); + Perl_croak(aTHX_ "Usage: quest::updatespawntimer(uint32 spawn2_id, uint32 updated_time_till_repop)"); uint32 spawn2_id = (int) SvIV(ST(0)); uint32 updated_time_till_repop = (int) SvIV(ST(1)); @@ -3418,8 +3429,9 @@ XS(XS__getcharnamebyid) { Const_char *RETVAL; uint32 char_id = (int) SvUV(ST(0)); + auto name = quest_manager.getcharnamebyid(char_id); - RETVAL = quest_manager.getcharnamebyid(char_id); + RETVAL = name.c_str(); sv_setpv(TARG, RETVAL); XSprePUSH; @@ -3471,7 +3483,7 @@ XS(XS__getcurrencyitemid) { dXSTARG; int RETVAL; - int currency_id = (int) SvUV(ST(0)); + uint32 currency_id = (uint32) SvUV(ST(0)); RETVAL = quest_manager.getcurrencyitemid(currency_id); @@ -3487,8 +3499,8 @@ XS(XS__getcurrencyid) { Perl_croak(aTHX_ "Usage: quest::getcurrencyid(uint32 item_id)"); dXSTARG; - int RETVAL; - uint32 item_id = (int) SvUV(ST(0)); + uint32 RETVAL; + uint32 item_id = (uint32) SvUV(ST(0)); RETVAL = quest_manager.getcurrencyid(item_id); XSprePUSH; @@ -3821,6 +3833,36 @@ XS(XS__GetZoneLongName) { XSRETURN(1); } +XS(XS__GetZoneLongNameByID); +XS(XS__GetZoneLongNameByID) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::GetZoneLongNameByID(uint32 zone_id)"); + + dXSTARG; + uint32 zone_id = (uint32) SvUV(ST(0)); + std::string zone_long_name = quest_manager.GetZoneLongNameByID(zone_id); + sv_setpv(TARG, zone_long_name.c_str()); + XSprePUSH; + PUSHTARG; + XSRETURN(1); +} + +XS(XS__GetZoneShortName); +XS(XS__GetZoneShortName) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::GetZoneShortName(uint32 zone_id)"); + + dXSTARG; + uint32 zone_id = (uint32) SvUV(ST(0)); + std::string zone_short_name = quest_manager.GetZoneShortName(zone_id); + sv_setpv(TARG, zone_short_name.c_str()); + XSprePUSH; + PUSHTARG; + XSRETURN(1); +} + XS(XS__GetTimeSeconds); XS(XS__GetTimeSeconds) { dXSARGS; @@ -3834,1363 +3876,6 @@ XS(XS__GetTimeSeconds) { XSRETURN_UV(seconds); } -XS(XS__crosszoneassigntaskbycharid); -XS(XS__crosszoneassigntaskbycharid) { - dXSARGS; - if (items < 2 || items > 3) - Perl_croak(aTHX_ "Usage: quest::crosszoneassigntaskbycharid(int character_id, uint32 task_id, [bool enforce_level_requirement = false])"); - { - int character_id = (int) SvIV(ST(0)); - uint32 task_id = (uint32) SvIV(ST(1)); - bool enforce_level_requirement = false; - - if (items == 3) { - enforce_level_requirement = (bool) SvTRUE(ST(2)); - } - quest_manager.CrossZoneAssignTaskByCharID(character_id, task_id, enforce_level_requirement); - } - - XSRETURN_EMPTY; -} - -XS(XS__crosszoneassigntaskbygroupid); -XS(XS__crosszoneassigntaskbygroupid) { - dXSARGS; - if (items < 2 || items > 3) - Perl_croak(aTHX_ "Usage: quest::crosszoneassigntaskbygroupid(int group_id, uint32 task_id, [bool enforce_level_requirement = false])"); - { - int group_id = (int) SvIV(ST(0)); - uint32 task_id = (uint32) SvIV(ST(1)); - bool enforce_level_requirement = false; - - if (items == 3) { - enforce_level_requirement = (bool) SvTRUE(ST(2)); - } - quest_manager.CrossZoneAssignTaskByGroupID(group_id, task_id, enforce_level_requirement); - } - XSRETURN_EMPTY; -} - -XS(XS__crosszoneassigntaskbyraidid); -XS(XS__crosszoneassigntaskbyraidid) { - dXSARGS; - if (items < 2 || items > 3) - Perl_croak(aTHX_ "Usage: quest::crosszoneassigntaskbyraidid(int raid_id, uint32 task_id, [bool enforce_level_requirement = false])");\ - { - int raid_id = (int) SvIV(ST(0)); - uint32 task_id = (uint32) SvIV(ST(1)); - bool enforce_level_requirement = false; - - if (items == 3) { - enforce_level_requirement = (bool) SvTRUE(ST(2)); - } - quest_manager.CrossZoneAssignTaskByRaidID(raid_id, task_id, enforce_level_requirement); - } - XSRETURN_EMPTY; -} - -XS(XS__crosszoneassigntaskbyguildid); -XS(XS__crosszoneassigntaskbyguildid) { - dXSARGS; - if (items < 2 || items > 3) - Perl_croak(aTHX_ "Usage: quest::crosszoneassigntaskbyguildid(int guild_id, uint32 task_id, [bool enforce_level_requirement = false])"); - { - int guild_id = (int) SvIV(ST(0)); - uint32 task_id = (uint32) SvIV(ST(1)); - bool enforce_level_requirement = false; - - if (items == 3) { - enforce_level_requirement = (bool) SvTRUE(ST(2)); - } - quest_manager.CrossZoneAssignTaskByGuildID(guild_id, task_id, enforce_level_requirement); - } - XSRETURN_EMPTY; -} - -XS(XS__crosszonecastspellbycharid); -XS(XS__crosszonecastspellbycharid) { - dXSARGS; - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszonecastspellbycharid(int character_id, uint32 spell_id)"); - { - int character_id = (int) SvIV(ST(0)); - uint32 spell_id = (uint32) SvIV(ST(1)); - quest_manager.CrossZoneCastSpellByCharID(character_id, spell_id); - } - XSRETURN_EMPTY; -} - -XS(XS__crosszonecastspellbygroupid); -XS(XS__crosszonecastspellbygroupid) { - dXSARGS; - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszonecastspellbygroupid(int group_id, uint32 spell_id)"); - { - int group_id = (int) SvIV(ST(0)); - uint32 spell_id = (uint32) SvIV(ST(1)); - quest_manager.CrossZoneCastSpellByGroupID(group_id, spell_id); - } - XSRETURN_EMPTY; -} - -XS(XS__crosszonecastspellbyraidid); -XS(XS__crosszonecastspellbyraidid) { - dXSARGS; - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszonecastspellbyraidid(int raid_id, uint32 spell_id)"); - { - int raid_id = (int) SvIV(ST(0)); - uint32 spell_id = (uint32) SvIV(ST(1)); - quest_manager.CrossZoneCastSpellByRaidID(raid_id, spell_id); - } - XSRETURN_EMPTY; -} - -XS(XS__crosszonecastspellbyguildid); -XS(XS__crosszonecastspellbyguildid) { - dXSARGS; - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszonecastspellbyguildid(int guild_id, uint32 spell_id)"); - { - int guild_id = (int) SvIV(ST(0)); - uint32 spell_id = (uint32) SvUV(ST(1)); - quest_manager.CrossZoneCastSpellByGuildID(guild_id, spell_id); - } - XSRETURN_EMPTY; -} - -XS(XS__crosszonedisabletaskbycharid); -XS(XS__crosszonedisabletaskbycharid) { - dXSARGS; - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszonedisabletaskbycharid(int character_id, uint32 task_id)"); - { - int char_id = (int) SvIV(ST(0)); - uint32 task_id = (uint32) SvUV(ST(1)); - quest_manager.CrossZoneDisableTaskByCharID(char_id, task_id); - } - XSRETURN_EMPTY; -} - -XS(XS__crosszonedisabletaskbygroupid); -XS(XS__crosszonedisabletaskbygroupid) { - dXSARGS; - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszonedisabletaskbygroupid(int group_id, uint32 task_id)"); - { - int group_id = (int) SvIV(ST(0)); - uint32 task_id = (uint32) SvUV(ST(1)); - quest_manager.CrossZoneDisableTaskByGroupID(group_id, task_id); - } - XSRETURN_EMPTY; -} - -XS(XS__crosszonedisabletaskbyraidid); -XS(XS__crosszonedisabletaskbyraidid) { - dXSARGS; - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszonedisabletaskbyraidid(int raid_id, uint32 task_id)"); - { - int raid_id = (int) SvIV(ST(0)); - uint32 task_id = (uint32) SvUV(ST(1)); - quest_manager.CrossZoneDisableTaskByRaidID(raid_id, task_id); - } - XSRETURN_EMPTY; -} - -XS(XS__crosszonedisabletaskbyguildid); -XS(XS__crosszonedisabletaskbyguildid) { - dXSARGS; - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszonedisabletaskbyguildid(int guild_id, uint32 task_id)"); - { - int guild_id = (int) SvIV(ST(0)); - uint32 task_id = (uint32) SvUV(ST(1)); - quest_manager.CrossZoneDisableTaskByGuildID(guild_id, task_id); - } - XSRETURN_EMPTY; -} - -XS(XS__crosszoneenabletaskbycharid); -XS(XS__crosszoneenabletaskbycharid) { - dXSARGS; - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszoneenabletaskbycharid(int character_id, uint32 task_id)"); - { - int char_id = (int) SvIV(ST(0)); - uint32 task_id = (uint32) SvUV(ST(1)); - quest_manager.CrossZoneEnableTaskByCharID(char_id, task_id); - } - XSRETURN_EMPTY; -} - -XS(XS__crosszoneenabletaskbygroupid); -XS(XS__crosszoneenabletaskbygroupid) { - dXSARGS; - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszoneenabletaskbygroupid(int group_id, uint32 task_id)"); - { - int group_id = (int) SvIV(ST(0)); - uint32 task_id = (uint32) SvUV(ST(1)); - quest_manager.CrossZoneEnableTaskByGroupID(group_id, task_id); - } - XSRETURN_EMPTY; -} - -XS(XS__crosszoneenabletaskbyraidid); -XS(XS__crosszoneenabletaskbyraidid) { - dXSARGS; - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszoneenabletaskbyraidid(int raid_id, uint32 task_id)"); - { - int raid_id = (int) SvIV(ST(0)); - uint32 task_id = (uint32) SvUV(ST(1)); - quest_manager.CrossZoneEnableTaskByRaidID(raid_id, task_id); - } - XSRETURN_EMPTY; -} - -XS(XS__crosszoneenabletaskbyguildid); -XS(XS__crosszoneenabletaskbyguildid) { - dXSARGS; - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszoneenabletaskbyguildid(int guild_id, uint32 task_id)"); - { - int guild_id = (int) SvIV(ST(0)); - uint32 task_id = (uint32) SvUV(ST(1)); - quest_manager.CrossZoneEnableTaskByGuildID(guild_id, task_id); - } - XSRETURN_EMPTY; -} - -XS(XS__crosszonefailtaskbycharid); -XS(XS__crosszonefailtaskbycharid) { - dXSARGS; - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszonefailtaskbycharid(int character_id, uint32 task_id)"); - { - int char_id = (int) SvIV(ST(0)); - uint32 task_id = (uint32) SvUV(ST(1)); - quest_manager.CrossZoneFailTaskByCharID(char_id, task_id); - } - XSRETURN_EMPTY; -} - -XS(XS__crosszonefailtaskbygroupid); -XS(XS__crosszonefailtaskbygroupid) { - dXSARGS; - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszonefailtaskbygroupid(int group_id, uint32 task_id)"); - { - int group_id = (int) SvIV(ST(0)); - uint32 task_id = (uint32) SvUV(ST(1)); - quest_manager.CrossZoneFailTaskByGroupID(group_id, task_id); - } - XSRETURN_EMPTY; -} - -XS(XS__crosszonefailtaskbyraidid); -XS(XS__crosszonefailtaskbyraidid) { - dXSARGS; - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszonefailtaskbyraidid(int raid_id, uint32 task_id)"); - { - int raid_id = (int) SvIV(ST(0)); - uint32 task_id = (uint32) SvUV(ST(1)); - quest_manager.CrossZoneFailTaskByRaidID(raid_id, task_id); - } - XSRETURN_EMPTY; -} - -XS(XS__crosszonefailtaskbyguildid); -XS(XS__crosszonefailtaskbyguildid) { - dXSARGS; - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszonefailtaskbyguildid(int guild_id, uint32 task_id)"); - { - int guild_id = (int) SvIV(ST(0)); - uint32 task_id = (uint32) SvUV(ST(1)); - quest_manager.CrossZoneFailTaskByGuildID(guild_id, task_id); - } - XSRETURN_EMPTY; -} - -XS(XS__crosszonemarqueebycharid); -XS(XS__crosszonemarqueebycharid) { - dXSARGS; - - if (items != 7) - Perl_croak(aTHX_ "Usage: quest::crosszonemarqueebycharid(int character_id, uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char *message)"); - - if (items == 7) { - int character_id = (int) SvIV(ST(0)); - int type = (int) SvIV(ST(1)); - int priority = (int) SvIV(ST(2)); - int fade_in = (int) SvIV(ST(3)); - int fade_out = (int) SvIV(ST(4)); - int duration = (int) SvIV(ST(5)); - char *message = (char *) SvPV_nolen(ST(6)); - quest_manager.CrossZoneMarqueeByCharID(character_id, type, priority, fade_in, fade_out, duration, message); - } - - XSRETURN_EMPTY; -} - -XS(XS__crosszonemarqueebygroupid); -XS(XS__crosszonemarqueebygroupid) { - dXSARGS; - - if (items != 7) - Perl_croak(aTHX_ "Usage: quest::crosszonemarqueebygroupid(int group_id, uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char *message)"); - - if (items == 7) { - int group_id = (int) SvIV(ST(0)); - int type = (int) SvIV(ST(1)); - int priority = (int) SvIV(ST(2)); - int fade_in = (int) SvIV(ST(3)); - int fade_out = (int) SvIV(ST(4)); - int duration = (int) SvIV(ST(5)); - char *message = (char *) SvPV_nolen(ST(6)); - quest_manager.CrossZoneMarqueeByGroupID(group_id, type, priority, fade_in, fade_out, duration, message); - } - - XSRETURN_EMPTY; -} - -XS(XS__crosszonemarqueebyraidid); -XS(XS__crosszonemarqueebyraidid) { - dXSARGS; - - if (items != 7) - Perl_croak(aTHX_ "Usage: quest::crosszonemarqueebyraidid(int raid_id, uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char *message)"); - - if (items == 7) { - int raid_id = (int) SvIV(ST(0)); - int type = (int) SvIV(ST(1)); - int priority = (int) SvIV(ST(2)); - int fade_in = (int) SvIV(ST(3)); - int fade_out = (int) SvIV(ST(4)); - int duration = (int) SvIV(ST(5)); - char *message = (char *) SvPV_nolen(ST(6)); - quest_manager.CrossZoneMarqueeByRaidID(raid_id, type, priority, fade_in, fade_out, duration, message); - } - - XSRETURN_EMPTY; -} - -XS(XS__crosszonemarqueebyguildid); -XS(XS__crosszonemarqueebyguildid) { - dXSARGS; - - if (items != 7) - Perl_croak(aTHX_ "Usage: quest::crosszonemarqueebyguildid(int guild_id, uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char *message)"); - - if (items == 7) { - int guild_id = (int) SvIV(ST(0)); - int type = (int) SvIV(ST(1)); - int priority = (int) SvIV(ST(2)); - int fade_in = (int) SvIV(ST(3)); - int fade_out = (int) SvIV(ST(4)); - int duration = (int) SvIV(ST(5)); - char *message = (char *) SvPV_nolen(ST(6)); - quest_manager.CrossZoneMarqueeByGuildID(guild_id, type, priority, fade_in, fade_out, duration, message); - } - - XSRETURN_EMPTY; -} - -XS(XS__crosszonemessageplayerbyname); -XS(XS__crosszonemessageplayerbyname) { - dXSARGS; - - if (items != 3) - Perl_croak(aTHX_ "Usage: quest::crosszonemessageplayerbyname(uint32 type, string name, string message)"); - - if (items == 3) { - uint32 type = (uint32) SvUV(ST(0)); - char *name = (char *) SvPV_nolen(ST(1)); - char *message = (char *) SvPV_nolen(ST(2)); - quest_manager.CrossZoneMessagePlayerByName(type, name, message); - } - - XSRETURN_EMPTY; -} - -XS(XS__crosszonemessageplayerbygroupid); -XS(XS__crosszonemessageplayerbygroupid) { - dXSARGS; - - if (items != 3) - Perl_croak(aTHX_ "Usage: quest::crosszonemessageplayerbygroupid(uint32 type, int group_id, string message)"); - - if (items == 3) { - uint32 type = (uint32) SvUV(ST(0)); - int group_id = (int) SvIV(ST(1)); - char *message = (char *) SvPV_nolen(ST(2)); - quest_manager.CrossZoneMessagePlayerByGroupID(type, group_id, message); - } - - XSRETURN_EMPTY; -} - -XS(XS__crosszonemessageplayerbyraidid); -XS(XS__crosszonemessageplayerbyraidid) { - dXSARGS; - - if (items != 3) - Perl_croak(aTHX_ "Usage: quest::crosszonemessageplayerbyraidid(uint32 type, int raid_id, string message)"); - - if (items == 3) { - uint32 type = (uint32) SvUV(ST(0)); - int raid_id = (int) SvIV(ST(1)); - char *message = (char *) SvPV_nolen(ST(2)); - quest_manager.CrossZoneMessagePlayerByRaidID(type, raid_id, message); - } - - XSRETURN_EMPTY; -} - -XS(XS__crosszonemessageplayerbyguildid); -XS(XS__crosszonemessageplayerbyguildid) { - dXSARGS; - - if (items != 3) - Perl_croak(aTHX_ "Usage: quest::crosszonemessageplayerbyguildid(uint32 type, int guild_id, string message)"); - - if (items == 3) { - uint32 type = (uint32) SvUV(ST(0)); - int guild_id = (int) SvIV(ST(1)); - char *message = (char *) SvPV_nolen(ST(2)); - quest_manager.CrossZoneMessagePlayerByGuildID(type, guild_id, message); - } - - XSRETURN_EMPTY; -} - -XS(XS__crosszonemoveplayerbycharid); -XS(XS__crosszonemoveplayerbycharid) { - dXSARGS; - - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszonemoveplayerbycharid(int character_id, string zone_short_name)"); - - if (items == 2) { - int character_id = (int) SvIV(ST(0)); - char *zone_short_name = (char *) SvPV_nolen(ST(1)); - quest_manager.CrossZoneMovePlayerByCharID(character_id, zone_short_name); - } - - XSRETURN_EMPTY; -} - -XS(XS__crosszonemoveplayerbygroupid); -XS(XS__crosszonemoveplayerbygroupid) { - dXSARGS; - - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszonemoveplayerbygroupid(int group_id, string zone_short_name)"); - - if (items == 2) { - int group_id = (int) SvIV(ST(0)); - char *zone_short_name = (char *) SvPV_nolen(ST(1)); - quest_manager.CrossZoneMovePlayerByGroupID(group_id, zone_short_name); - } - - XSRETURN_EMPTY; -} - -XS(XS__crosszonemoveplayerbyraidid); -XS(XS__crosszonemoveplayerbyraidid) { - dXSARGS; - - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszonemoveplayerbyraidid(int raid_id, string zone_short_name)"); - - if (items == 2) { - int raid_id = (int) SvIV(ST(0)); - char *zone_short_name = (char *) SvPV_nolen(ST(1)); - quest_manager.CrossZoneMovePlayerByRaidID(raid_id, zone_short_name); - } - - XSRETURN_EMPTY; -} - -XS(XS__crosszonemoveplayerbyguildid); -XS(XS__crosszonemoveplayerbyguildid) { - dXSARGS; - - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszonemoveplayerbyguildid(int guild_id, string zone_short_name)"); - - if (items == 2) { - int guild_id = (int) SvIV(ST(0)); - char *zone_short_name = (char *) SvPV_nolen(ST(1)); - quest_manager.CrossZoneMovePlayerByGuildID(guild_id, zone_short_name); - } - - XSRETURN_EMPTY; -} - -XS(XS__crosszonemoveinstancebycharid); -XS(XS__crosszonemoveinstancebycharid) { - dXSARGS; - - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszonemoveinstancebycharid(int character_id, uint16 instance_id)"); - - if (items == 2) { - int character_id = (int) SvIV(ST(0)); - uint16 instance_id = (uint16) SvUV(ST(1)); - quest_manager.CrossZoneMoveInstanceByCharID(character_id, instance_id); - } - - XSRETURN_EMPTY; -} - -XS(XS__crosszonemoveinstancebygroupid); -XS(XS__crosszonemoveinstancebygroupid) { - dXSARGS; - - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszonemoveinstancebygroupid(int group_id, uint16 instance_id)"); - - if (items == 2) { - int group_id = (int) SvIV(ST(0)); - uint16 instance_id = (uint16) SvUV(ST(1)); - quest_manager.CrossZoneMoveInstanceByGroupID(group_id, instance_id); - } - - XSRETURN_EMPTY; -} - -XS(XS__crosszonemoveinstancebyraidid); -XS(XS__crosszonemoveinstancebyraidid) { - dXSARGS; - - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszonemoveinstancebyraidid(int raid_id, uint16 instance_id)"); - - if (items == 2) { - int raid_id = (int) SvIV(ST(0)); - uint16 instance_id = (uint16) SvUV(ST(1)); - quest_manager.CrossZoneMoveInstanceByRaidID(raid_id, instance_id); - } - - XSRETURN_EMPTY; -} - -XS(XS__crosszonemoveinstancebyguildid); -XS(XS__crosszonemoveinstancebyguildid) { - dXSARGS; - - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszonemoveinstancebyguildid(int guild_id, uint16 instance_id)"); - - if (items == 2) { - int guild_id = (int) SvIV(ST(0)); - uint16 instance_id = (uint16) SvUV(ST(1)); - quest_manager.CrossZoneMoveInstanceByGuildID(guild_id, instance_id); - } - - XSRETURN_EMPTY; -} - -XS(XS__crosszoneremovespellbycharid); -XS(XS__crosszoneremovespellbycharid) { - dXSARGS; - - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszoneremovespellbycharid(int character_id, uint32 spell_id)"); - - if (items == 2) { - int character_id = (int) SvIV(ST(0)); - uint32 spell_id = (uint32) SvUV(ST(1)); - quest_manager.CrossZoneRemoveSpellByCharID(character_id, spell_id); - } - - XSRETURN_EMPTY; -} - -XS(XS__crosszoneremovespellbygroupid); -XS(XS__crosszoneremovespellbygroupid) { - dXSARGS; - - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszoneremovespellbygroupid(int group_id, uint32 spell_id)"); - - if (items == 2) { - int group_id = (int) SvIV(ST(0)); - uint32 spell_id = (uint32) SvUV(ST(1)); - quest_manager.CrossZoneRemoveSpellByGroupID(group_id, spell_id); - } - - XSRETURN_EMPTY; -} - -XS(XS__crosszoneremovespellbyraidid); -XS(XS__crosszoneremovespellbyraidid) { - dXSARGS; - - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszoneremovespellbyraidid(int raid_id, uint32 spell_id)"); - - if (items == 2) { - int raid_id = (int) SvIV(ST(0)); - uint32 spell_id = (uint32) SvUV(ST(1)); - quest_manager.CrossZoneRemoveSpellByRaidID(raid_id, spell_id); - } - - XSRETURN_EMPTY; -} - -XS(XS__crosszoneremovespellbyguildid); -XS(XS__crosszoneremovespellbyguildid) { - dXSARGS; - - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszoneremovespellbyguildid(int guild_id, uint32 spell_id)"); - - if (items == 2) { - int guild_id = (int) SvIV(ST(0)); - uint32 spell_id = (uint32) SvUV(ST(1)); - quest_manager.CrossZoneRemoveSpellByGuildID(guild_id, spell_id); - } - - XSRETURN_EMPTY; -} - -XS(XS__crosszoneremovetaskbycharid); -XS(XS__crosszoneremovetaskbycharid) { - dXSARGS; - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszoneremovetaskbycharid(int character_id, uint32 task_id)"); - { - int char_id = (int) SvIV(ST(0)); - uint32 task_id = (uint32) SvUV(ST(1)); - quest_manager.CrossZoneRemoveTaskByCharID(char_id, task_id); - } - XSRETURN_EMPTY; -} - -XS(XS__crosszoneremovetaskbygroupid); -XS(XS__crosszoneremovetaskbygroupid) { - dXSARGS; - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszoneremovetaskbygroupid(int group_id, uint32 task_id)"); - { - int group_id = (int) SvIV(ST(0)); - uint32 task_id = (uint32) SvUV(ST(1)); - quest_manager.CrossZoneRemoveTaskByGroupID(group_id, task_id); - } - XSRETURN_EMPTY; -} - -XS(XS__crosszoneremovetaskbyraidid); -XS(XS__crosszoneremovetaskbyraidid) { - dXSARGS; - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszoneremovetaskbyraidid(int raid_id, uint32 task_id)"); - { - int raid_id = (int) SvIV(ST(0)); - uint32 task_id = (uint32) SvUV(ST(1)); - quest_manager.CrossZoneRemoveTaskByRaidID(raid_id, task_id); - } - XSRETURN_EMPTY; -} - -XS(XS__crosszoneremovetaskbyguildid); -XS(XS__crosszoneremovetaskbyguildid) { - dXSARGS; - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszoneremovetaskbyguildid(int guild_id, uint32 task_id)"); - { - int guild_id = (int) SvIV(ST(0)); - uint32 task_id = (uint32) SvUV(ST(1)); - quest_manager.CrossZoneRemoveTaskByGuildID(guild_id, task_id); - } - XSRETURN_EMPTY; -} - -XS(XS__crosszoneresetactivitybycharid); -XS(XS__crosszoneresetactivitybycharid) { - dXSARGS; - if (items != 3) - Perl_croak(aTHX_ "Usage: quest::crosszoneresetactivitybycharid(int char_id, uint32 task_id, int activity_id)"); - { - int char_id = (int) SvIV(ST(0)); - uint32 task_id = (uint32) SvUV(ST(1)); - int activity_id = (int) SvIV(ST(2)); - quest_manager.CrossZoneResetActivityByCharID(char_id, task_id, activity_id); - } - XSRETURN_EMPTY; -} - -XS(XS__crosszoneresetactivitybygroupid); -XS(XS__crosszoneresetactivitybygroupid) { - dXSARGS; - if (items != 3) - Perl_croak(aTHX_ "Usage: quest::crosszoneresetactivitybygroupid(int group_id, uint32 task_id, int activity_id)"); - { - int group_id = (int) SvIV(ST(0)); - uint32 task_id = (uint32) SvUV(ST(1)); - int activity_id = (int) SvIV(ST(2)); - quest_manager.CrossZoneResetActivityByGroupID(group_id, task_id, activity_id); - } - XSRETURN_EMPTY; -} - -XS(XS__crosszoneresetactivitybyraidid); -XS(XS__crosszoneresetactivitybyraidid) { - dXSARGS; - if (items != 3) - Perl_croak(aTHX_ "Usage: quest::crosszoneresetactivitybyraidid(int raid_id, uint32 task_id, int activity_id)"); - { - int raid_id = (int) SvIV(ST(0)); - uint32 task_id = (uint32) SvUV(ST(1)); - int activity_id = (int) SvIV(ST(2)); - quest_manager.CrossZoneResetActivityByRaidID(raid_id, task_id, activity_id); - } - XSRETURN_EMPTY; -} - -XS(XS__crosszoneresetactivitybyguildid); -XS(XS__crosszoneresetactivitybyguildid) { - dXSARGS; - if (items != 3) - Perl_croak(aTHX_ "Usage: quest::crosszoneresetactivitybyguildid(int guild_id, uint32 task_id, int activity_id)"); - { - int guild_id = (int) SvIV(ST(0)); - uint32 task_id = (uint32) SvUV(ST(1)); - int activity_id = (int) SvIV(ST(2)); - quest_manager.CrossZoneResetActivityByGuildID(guild_id, task_id, activity_id); - } - XSRETURN_EMPTY; -} - -XS(XS__crosszonesetentityvariablebynpctypeid); -XS(XS__crosszonesetentityvariablebynpctypeid) { - dXSARGS; - - if (items != 3) - Perl_croak(aTHX_ "Usage: quest::crosszonesetentityvariablebynpctypeid(int npc_type_id, string key, string value)"); - - if (items == 3) { - uint32 npc_type_id = (uint32) SvUV(ST(0)); - const char *key = (const char *) SvPV_nolen(ST(1)); - const char *str_value = (const char *) SvPV_nolen(ST(2)); - quest_manager.CrossZoneSetEntityVariableByNPCTypeID(npc_type_id, key, str_value); - } - - XSRETURN_EMPTY; -} - -XS(XS__crosszonesetentityvariablebyclientname); -XS(XS__crosszonesetentityvariablebyclientname) { - dXSARGS; - - if (items != 3) - Perl_croak(aTHX_ "Usage: quest::crosszonesetentityvariablebyclientname(string client_name, string key, string value)"); - - if (items == 3) { - const char *client_name = (const char *) SvPV_nolen(ST(0)); - const char *key = (const char *) SvPV_nolen(ST(1)); - const char *str_value = (const char *) SvPV_nolen(ST(2)); - quest_manager.CrossZoneSetEntityVariableByClientName(client_name, key, str_value); - } - - XSRETURN_EMPTY; -} - -XS(XS__crosszonesetentityvariablebygroupid); -XS(XS__crosszonesetentityvariablebygroupid) { - dXSARGS; - - if (items != 3) - Perl_croak(aTHX_ "Usage: quest::crosszonesetentityvariablebygroupid(int group_id, string key, string value)"); - - if (items == 3) { - int group_id = SvIV(ST(0)); - const char *key = (const char *) SvPV_nolen(ST(1)); - const char *str_value = (const char *) SvPV_nolen(ST(2)); - quest_manager.CrossZoneSetEntityVariableByGroupID(group_id, key, str_value); - } - - XSRETURN_EMPTY; -} - -XS(XS__crosszonesetentityvariablebyraidid); -XS(XS__crosszonesetentityvariablebyraidid) { - dXSARGS; - - if (items != 3) - Perl_croak(aTHX_ "Usage: quest::crosszonesetentityvariablebyraidid(int raid_id, string key, string value)"); - - if (items == 3) { - int raid_id = SvIV(ST(0)); - const char *key = (const char *) SvPV_nolen(ST(1)); - const char *str_value = (const char *) SvPV_nolen(ST(2)); - quest_manager.CrossZoneSetEntityVariableByRaidID(raid_id, key, str_value); - } - - XSRETURN_EMPTY; -} - -XS(XS__crosszonesetentityvariablebyguildid); -XS(XS__crosszonesetentityvariablebyguildid) { - dXSARGS; - - if (items != 3) - Perl_croak(aTHX_ "Usage: quest::crosszonesetentityvariablebyguildid(int guild_id, string key, string value)"); - - if (items == 3) { - int guild_id = SvIV(ST(0)); - const char *key = (const char *) SvPV_nolen(ST(1)); - const char *str_value = (const char *) SvPV_nolen(ST(2)); - quest_manager.CrossZoneSetEntityVariableByGuildID(guild_id, key, str_value); - } - - XSRETURN_EMPTY; -} - -XS(XS__crosszonesignalclientbycharid); -XS(XS__crosszonesignalclientbycharid) { - dXSARGS; - - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszonesignalclientbycharid(int character_id, uint32 signal)"); - - if (items == 2) { - int char_id = (int) SvIV(ST(0)); - uint32 signal = (uint32) SvUV(ST(1)); - quest_manager.CrossZoneSignalPlayerByCharID(char_id, signal); - } - - XSRETURN_EMPTY; -} - -XS(XS__crosszonesignalclientbygroupid); -XS(XS__crosszonesignalclientbygroupid) { - dXSARGS; - - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszonesignalclientbygroupid(int group_id, uint32 signal)"); - - if (items == 2) { - int group_id = (int) SvIV(ST(0)); - uint32 signal = (uint32) SvUV(ST(1)); - quest_manager.CrossZoneSignalPlayerByGroupID(group_id, signal); - } - - XSRETURN_EMPTY; -} - -XS(XS__crosszonesignalclientbyraidid); -XS(XS__crosszonesignalclientbyraidid) { - dXSARGS; - - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszonesignalclientbyraidid(int raid_id, uint32 signal)"); - - if (items == 2) { - int raid_id = (int) SvIV(ST(0)); - uint32 signal = (uint32) SvUV(ST(1)); - quest_manager.CrossZoneSignalPlayerByRaidID(raid_id, signal); - } - - XSRETURN_EMPTY; -} - -XS(XS__crosszonesignalclientbyguildid); -XS(XS__crosszonesignalclientbyguildid) { - dXSARGS; - - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszonesignalclientbyguildid(int guild_id, uint32 signal)"); - - if (items == 2) { - int guild_id = (int) SvIV(ST(0)); - uint32 signal = (uint32) SvUV(ST(1)); - quest_manager.CrossZoneSignalPlayerByGuildID(guild_id, signal); - } - - XSRETURN_EMPTY; -} - -XS(XS__crosszonesignalclientbyname); -XS(XS__crosszonesignalclientbyname) { - dXSARGS; - - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszonesignalclientbyname(string name, uint32 signal)"); - - if (items == 2) { - char *name = (char *) SvPV_nolen(ST(0)); - uint32 signal = (uint32) SvUV(ST(1)); - quest_manager.CrossZoneSignalPlayerByName(name, signal); - } - - XSRETURN_EMPTY; -} - -XS(XS__crosszonesignalnpcbynpctypeid); -XS(XS__crosszonesignalnpcbynpctypeid) { - dXSARGS; - - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::crosszonesignalnpcbynpctypeid(uint32 npc_type_id, uint32 value)"); - - if (items == 2) { - uint32 npc_type_id = (uint32) SvUV(ST(0)); - uint32 int_value = (uint32) SvUV(ST(1)); - quest_manager.CrossZoneSignalNPCByNPCTypeID(npc_type_id, int_value); - } - - XSRETURN_EMPTY; -} - -XS(XS__crosszoneupdateactivitybycharid); -XS(XS__crosszoneupdateactivitybycharid) { - dXSARGS; - if (items < 3 || items > 4) - Perl_croak(aTHX_ "Usage: quest::crosszoneupdateactivitybycharid(int char_id, uint32 task_id, int activity_id, [int activity_count = 1])"); - { - int char_id = (int) SvIV(ST(0)); - uint32 task_id = (uint32) SvUV(ST(1)); - int activity_id = (int) SvIV(ST(2)); - int activity_count = 1; - if (items == 4) { - activity_count = (int) SvIV(ST(3)); - } - quest_manager.CrossZoneUpdateActivityByCharID(char_id, task_id, activity_id, activity_count); - } - XSRETURN_EMPTY; -} - -XS(XS__crosszoneupdateactivitybygroupid); -XS(XS__crosszoneupdateactivitybygroupid) { - dXSARGS; - if (items < 3 || items > 4) - Perl_croak(aTHX_ "Usage: quest::crosszoneupdateactivitybygroupid(int group_id, uint32 task_id, int activity_id, [int activity_count = 1])"); - { - int group_id = (int) SvIV(ST(0)); - uint32 task_id = (uint32) SvUV(ST(1)); - int activity_id = (int) SvIV(ST(2)); - int activity_count = 1; - if (items == 4) { - activity_count = (int) SvIV(ST(3)); - } - quest_manager.CrossZoneUpdateActivityByGroupID(group_id, task_id, activity_id, activity_count); - } - XSRETURN_EMPTY; -} - -XS(XS__crosszoneupdateactivitybyraidid); -XS(XS__crosszoneupdateactivitybyraidid) { - dXSARGS; - if (items < 3 || items > 4) - Perl_croak(aTHX_ "Usage: quest::crosszoneupdateactivitybyraidid(int raid_id, uint32 task_id, int activity_id, [int activity_count = 1])"); - { - int raid_id = (int) SvIV(ST(0)); - uint32 task_id = (uint32) SvUV(ST(1)); - int activity_id = (int) SvIV(ST(2)); - int activity_count = 1; - if (items == 4) { - activity_count = (int) SvIV(ST(3)); - } - quest_manager.CrossZoneUpdateActivityByRaidID(raid_id, task_id, activity_id, activity_count); - } - XSRETURN_EMPTY; -} - -XS(XS__crosszoneupdateactivitybyguildid); -XS(XS__crosszoneupdateactivitybyguildid) { - dXSARGS; - if (items < 3 || items > 4) - Perl_croak(aTHX_ "Usage: quest::crosszoneupdateactivitybyguildid(int guild_id, uint32 task_id, int activity_id, [int activity_count = 1])"); - { - int guild_id = (int) SvIV(ST(0)); - uint32 task_id = (uint32) SvUV(ST(1)); - int activity_id = (int) SvIV(ST(2)); - int activity_count = 1; - if (items == 4) { - activity_count = (int) SvIV(ST(3)); - } - quest_manager.CrossZoneUpdateActivityByGuildID(guild_id, task_id, activity_id, activity_count); - } - XSRETURN_EMPTY; -} - -XS(XS__worldwideassigntask); -XS(XS__worldwideassigntask) { - dXSARGS; - if (items < 1 || items > 3) - Perl_croak(aTHX_ "Usage: quest::worldwideassigntask(uint32 task_id, [uint8 min_status = 0, uint8 max_status = 0])"); - { - uint32 task_id = (uint32) SvUV(ST(0)); - uint8 min_status = 0; - uint8 max_status = 0; - if (items == 2) { - min_status = (uint8) SvUV(ST(1)); - } - - if (items == 3) { - max_status = (uint8) SvUV(ST(2)); - } - quest_manager.WorldWideAssignTask(task_id, min_status, max_status); - } - - XSRETURN_EMPTY; -} - -XS(XS__worldwidecastspell); -XS(XS__worldwidecastspell) { - dXSARGS; - if (items < 1 || items > 3) - Perl_croak(aTHX_ "Usage: quest::worldwidecastspell(uint32 spell_id, [uint8 min_status = 0, uint8 max_status = 0])"); - { - uint32 spell_id = (uint32) SvUV(ST(0)); - uint8 min_status = 0; - uint8 max_status = 0; - if (items == 2) { - min_status = (uint8) SvUV(ST(1)); - } - - if (items == 3) { - max_status = (uint8) SvUV(ST(2)); - } - quest_manager.WorldWideCastSpell(spell_id, min_status, max_status); - } - - XSRETURN_EMPTY; -} - -XS(XS__worldwidedisabletask); -XS(XS__worldwidedisabletask) { - dXSARGS; - if (items < 1 || items > 3) - Perl_croak(aTHX_ "Usage: quest::worldwidedisabletask(uint32 task_id, [uint8 min_status = 0, uint8 max_status = 0])"); - { - uint32 task_id = (uint32) SvUV(ST(0)); - uint8 min_status = 0; - uint8 max_status = 0; - if (items == 2) { - min_status = (uint8) SvUV(ST(1)); - } - - if (items == 3) { - max_status = (uint8) SvUV(ST(2)); - } - quest_manager.WorldWideDisableTask(task_id, min_status, max_status); - } - - XSRETURN_EMPTY; -} - -XS(XS__worldwideenabletask); -XS(XS__worldwideenabletask) { - dXSARGS; - if (items < 1 || items > 3) - Perl_croak(aTHX_ "Usage: quest::worldwideenabletask(uint32 task_id, [uint8 min_status = 0, uint8 max_status = 0])"); - { - uint32 task_id = (uint32) SvUV(ST(0)); - uint8 min_status = 0; - uint8 max_status = 0; - if (items == 2) { - min_status = (uint8) SvUV(ST(1)); - } - - if (items == 3) { - max_status = (uint8) SvUV(ST(2)); - } - quest_manager.WorldWideEnableTask(task_id, min_status, max_status); - } - - XSRETURN_EMPTY; -} - -XS(XS__worldwidefailtask); -XS(XS__worldwidefailtask) { - dXSARGS; - if (items < 1 || items > 3) - Perl_croak(aTHX_ "Usage: quest::worldwidefailtask(uint32 task_id, [uint8 min_status = 0, uint8 max_status = 0])"); - { - uint32 task_id = (uint32) SvUV(ST(0)); - uint8 min_status = 0; - uint8 max_status = 0; - if (items == 2) { - min_status = (uint8) SvUV(ST(1)); - } - - if (items == 3) { - max_status = (uint8) SvUV(ST(2)); - } - quest_manager.WorldWideFailTask(task_id, min_status, max_status); - } - - XSRETURN_EMPTY; -} - -XS(XS__worldwidemarquee); -XS(XS__worldwidemarquee) { - dXSARGS; - if (items < 6 || items > 8) - Perl_croak(aTHX_ "Usage: quest::worldwidemarquee(uint32 color_id, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, string message, [uint8 min_status = 0, uint8 max_status = 0])"); - { - uint32 color_id = (uint32) SvUV(ST(0)); - uint32 priority = (uint32) SvUV(ST(1)); - uint32 fade_in = (uint32) SvUV(ST(2)); - uint32 fade_out = (uint32) SvUV(ST(3)); - uint32 duration = (uint32) SvUV(ST(4)); - char *message = (char *) SvPV_nolen(ST(5)); - uint8 min_status = 0; - uint8 max_status = 0; - if (items == 7) { - min_status = (uint8) SvUV(ST(6)); - } - - if (items == 8) { - max_status = (uint8) SvUV(ST(7)); - } - quest_manager.WorldWideMarquee(color_id, priority, fade_in, fade_out, duration, message, min_status, max_status); - } - - XSRETURN_EMPTY; -} - -XS(XS__worldwidemessage); -XS(XS__worldwidemessage) { - dXSARGS; - if (items < 2 || items > 4) - Perl_croak(aTHX_ "Usage: quest::worldwidemessage(uint32 type, string message, [uint8 min_status = 0, uint8 max_status = 0])"); - { - uint32 type = (uint32)SvUV(ST(0)); - const char *message = (const char*) SvPV_nolen(ST(1)); - uint8 min_status = 0; - uint8 max_status = 0; - if (items == 3) { - min_status = (uint8) SvUV(ST(2)); - } - - if (items == 4) { - max_status = (uint8) SvUV(ST(3)); - } - quest_manager.WorldWideMessage(type, message, min_status, max_status); - } - - XSRETURN_EMPTY; -} - -XS(XS__worldwidemove); -XS(XS__worldwidemove) { - dXSARGS; - if (items < 1 || items > 3) - Perl_croak(aTHX_ "Usage: quest::worldwidemove(string zone_short_name, [uint8 min_status = 0, uint8 max_status = 0])"); - - if (items == 1) { - const char *zone_short_name = (const char*) SvPV_nolen(ST(0)); - uint8 min_status = 0; - uint8 max_status = 0; - if (items == 2) { - min_status = (uint8) SvUV(ST(1)); - } - - if (items == 3) { - max_status = (uint8) SvUV(ST(2)); - } - quest_manager.WorldWideMove(zone_short_name, min_status, max_status); - } - - XSRETURN_EMPTY; -} - -XS(XS__worldwidemoveinstance); -XS(XS__worldwidemoveinstance) { - dXSARGS; - if (items < 1 || items > 3) - Perl_croak(aTHX_ "Usage: quest::worldwidemoveinstance(uint16 instance_id, [uint8 min_status = 0, uint max_status = 0])"); - { - uint16 instance_id = (uint16) SvUV(ST(0)); - uint8 min_status = 0; - uint8 max_status = 0; - if (items == 2) { - min_status = (uint8) SvUV(ST(1)); - } - - if (items == 3) { - max_status = (uint8) SvUV(ST(2)); - } - quest_manager.WorldWideMoveInstance(instance_id, min_status, max_status); - } - - XSRETURN_EMPTY; -} - -XS(XS__worldwideremovespell); -XS(XS__worldwideremovespell) { - dXSARGS; - if (items < 1 || items > 3) - Perl_croak(aTHX_ "Usage: quest::worldwideremovespell(uint32 spell_id, [uint8 min_status = 0, uint max_status = 0])"); - { - uint32 spell_id = (uint32) SvUV(ST(0)); - uint8 min_status = 0; - uint8 max_status = 0; - if (items == 2) { - min_status = (uint8) SvUV(ST(1)); - } - - if (items == 3) { - max_status = (uint8) SvUV(ST(2)); - } - quest_manager.WorldWideRemoveSpell(spell_id, min_status, max_status); - } - - XSRETURN_EMPTY; -} - -XS(XS__worldwideremovetask); -XS(XS__worldwideremovetask) { - dXSARGS; - if (items < 1 || items > 3) - Perl_croak(aTHX_ "Usage: quest::worldwideremovetask(uint32 task_id, [uint8 min_status = 0, uint max_status = 0])"); - { - uint32 task_id = (uint32) SvUV(ST(0)); - uint8 min_status = 0; - uint8 max_status = 0; - if (items == 2) { - min_status = (uint8) SvUV(ST(1)); - } - - if (items == 3) { - max_status = (uint8) SvUV(ST(2)); - } - quest_manager.WorldWideRemoveTask(task_id, min_status, max_status); - } - - XSRETURN_EMPTY; -} - -XS(XS__worldwideresetactivity); -XS(XS__worldwideresetactivity) { - dXSARGS; - if (items < 2 || items > 4) - Perl_croak(aTHX_ "Usage: quest::worldwideresetactivity(uint32 task_id, int activity_id, [uint8 min_status = 0, uint max_status = 0])"); - { - uint32 task_id = (uint32) SvUV(ST(0)); - int activity_id = (int) SvIV(ST(1)); - uint8 min_status = 0; - uint8 max_status = 0; - if (items == 3) { - min_status = (uint8) SvUV(ST(2)); - } - - if (items == 4) { - max_status = (uint8) SvUV(ST(3)); - } - quest_manager.WorldWideResetActivity(task_id, activity_id, min_status, max_status); - } - - XSRETURN_EMPTY; -} - -XS(XS__worldwidesetentityvariableclient); -XS(XS__worldwidesetentityvariableclient) { - dXSARGS; - if (items < 2 || items > 4) - Perl_croak(aTHX_ "Usage: quest::worldwidesetentityvariableclient(string variable_name, string variable_value, [uint8 min_status = 0, uint max_status = 0])"); - { - const char *variable_name = (const char*) SvPV_nolen(ST(0)); - const char *variable_value = (const char*) SvPV_nolen(ST(1)); - uint8 min_status = 0; - uint8 max_status = 0; - if (items == 3) { - min_status = (uint8) SvUV(ST(2)); - } - - if (items == 4) { - max_status = (uint8) SvUV(ST(3)); - } - quest_manager.WorldWideSetEntityVariableClient(variable_name, variable_value, min_status, max_status); - } - - XSRETURN_EMPTY; -} - -XS(XS__worldwidesetentityvariablenpc); -XS(XS__worldwidesetentityvariablenpc) { - dXSARGS; - if (items != 2) - Perl_croak(aTHX_ "Usage: quest::worldwidesetentityvariablenpc(string variable_name, string variable_value)"); - { - const char *variable_name = (const char*) SvPV_nolen(ST(0)); - const char *variable_value = (const char*) SvPV_nolen(ST(1)); - quest_manager.WorldWideSetEntityVariableNPC(variable_name, variable_value); - } - - XSRETURN_EMPTY; -} - -XS(XS__worldwidesignalnpc); -XS(XS__worldwidesignalnpc) { - dXSARGS; - if (items != 1) - Perl_croak(aTHX_ "Usage: quest::worldwidesignalnpc(uint32 signal)"); - { - uint32 signal = (uint32) SvUV(ST(0)); - quest_manager.WorldWideSignalNPC(signal); - } - - XSRETURN_EMPTY; -} - -XS(XS__worldwidesignalclient); -XS(XS__worldwidesignalclient) { - dXSARGS; - if (items < 1 || items > 3) - Perl_croak(aTHX_ "Usage: quest::worldwidesignalclient(uint32 signal, [uint8 min_status = 0, uint max_status = 0])"); - { - uint32 signal = (uint32) SvUV(ST(0)); - uint8 min_status = 0; - uint8 max_status = 0; - if (items == 2) { - min_status = (uint8) SvUV(ST(1)); - } - - if (items == 3) { - max_status = (uint8) SvUV(ST(1)); - } - quest_manager.WorldWideSignalClient(signal, min_status, max_status); - } - - XSRETURN_EMPTY; -} -XS(XS__worldwideupdateactivity); -XS(XS__worldwideupdateactivity) { - dXSARGS; - if (items < 2 || items > 5) - Perl_croak(aTHX_ "Usage: quest::worldwideupdateactivity(uint32 task_id, int activity_id, [int activity_count = 1, uint8 min_status = 0, uint max_status = 0])"); - { - uint32 task_id = (uint32) SvUV(ST(0)); - int activity_id = (int) SvIV(ST(1)); - int activity_count = 1; - uint8 min_status = 0; - uint8 max_status = 0; - if (items == 3) { - activity_count = (int) SvIV(ST(2)); - } - - if (items == 4) { - min_status = (uint8) SvUV(ST(3)); - } - - if (items == 5) { - max_status = (uint8) SvUV(ST(4)); - } - quest_manager.WorldWideUpdateActivity(task_id, activity_id, activity_count, min_status, max_status); - } - - XSRETURN_EMPTY; -} - XS(XS__enablerecipe); XS(XS__enablerecipe) { dXSARGS; @@ -6117,7 +4802,8 @@ XS(XS__SetContentFlag) std::string flag_name = (std::string) SvPV_nolen(ST(0)); bool enabled = (int) SvIV(ST(1)) != 0; - ZoneStore::SetContentFlag(flag_name, enabled); + + content_service.SetContentFlag(flag_name, enabled); XSRETURN_EMPTY; } @@ -6439,7 +5125,3004 @@ XS(XS__secondstotime) { sv_setpv(TARG, time_string.c_str()); XSprePUSH; PUSHTARG; - XSRETURN(1); + XSRETURN(1); +} + +XS(XS__gethexcolorcode); +XS(XS__gethexcolorcode) { + dXSARGS; + if (items != 1) { + Perl_croak(aTHX_ "Usage: quest::gethexcolorcode(std::string color_name)"); + } + + dXSTARG; + std::string hex_color_code; + std::string color_name = SvPV_nolen(ST(0)); + hex_color_code = quest_manager.gethexcolorcode(color_name); + sv_setpv(TARG, hex_color_code.c_str()); + XSprePUSH; + PUSHTARG; + XSRETURN(1); +} + +XS(XS__getaaexpmodifierbycharid); +XS(XS__getaaexpmodifierbycharid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::getaaexpmodifierbycharid(uint32 character_id, uint32 zone_id)"); + + dXSTARG; + double aa_modifier; + uint32 character_id = (uint32) SvUV(ST(0)); + uint32 zone_id = (uint32) SvUV(ST(1)); + aa_modifier = quest_manager.GetAAEXPModifierByCharID(character_id, zone_id); + XSprePUSH; + PUSHn((double) aa_modifier); + XSRETURN(1); +} + +XS(XS__getexpmodifierbycharid); +XS(XS__getexpmodifierbycharid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::getexpmodifierbycharid(uint32 character_id, uint32 zone_id)"); + + dXSTARG; + double exp_modifier; + uint32 character_id = (uint32) SvUV(ST(0)); + uint32 zone_id = (uint32) SvUV(ST(1)); + exp_modifier = quest_manager.GetEXPModifierByCharID(character_id, zone_id); + XSprePUSH; + PUSHn((double) exp_modifier); + XSRETURN(1); +} + +XS(XS__setaaexpmodifierbycharid); +XS(XS__setaaexpmodifierbycharid) { + dXSARGS; + if (items != 3) { + Perl_croak(aTHX_ "Usage: quest::setaaexpmodifierbycharid(uint32 character_id, uint32 zone_id, float aa_modifier)"); + } + uint32 character_id = (uint32) SvUV(ST(0)); + uint32 zone_id = (uint32) SvUV(ST(1)); + double aa_modifier = (double) SvNV(ST(2)); + quest_manager.SetAAEXPModifierByCharID(character_id, zone_id, aa_modifier); + XSRETURN_EMPTY; +} + +XS(XS__setexpmodifierbycharid); +XS(XS__setexpmodifierbycharid) { + dXSARGS; + if (items != 3) { + Perl_croak(aTHX_ "Usage: quest::setexpmodifierbycharid(uint32 character_id, uint32 zone_id, float exp_modifier)"); + } + uint32 character_id = (uint32) SvUV(ST(0)); + uint32 zone_id = (uint32) SvUV(ST(1)); + double exp_modifier = (double) SvNV(ST(2)); + quest_manager.SetEXPModifierByCharID(character_id, zone_id, exp_modifier); + XSRETURN_EMPTY; +} + +XS(XS__getcleannpcnamebyid); +XS(XS__getcleannpcnamebyid) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::getcleannpcnamebyid(uint32 npc_id)"); + + dXSTARG; + uint32 npc_id = (uint32) SvUV(ST(0)); + auto npc_name = quest_manager.getcleannpcnamebyid(npc_id); + sv_setpv(TARG, npc_name.c_str()); + XSprePUSH; + PUSHTARG; + XSRETURN(1); +} + +XS(XS__getgendername); +XS(XS__getgendername) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::getgendername(uint32 gender_id)"); + + dXSTARG; + uint32 gender_id = (uint32) SvUV(ST(0)); + auto gender_name = quest_manager.getgendername(gender_id); + sv_setpv(TARG, gender_name.c_str()); +} + +XS(XS__getdeityname); +XS(XS__getdeityname) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::getdeityname(uint32 deity_id)"); + + dXSTARG; + uint32 deity_id = (uint32) SvUV(ST(0)); + auto deity_name = quest_manager.getdeityname(deity_id); + sv_setpv(TARG, deity_name.c_str()); +} + +XS(XS__getinventoryslotname); +XS(XS__getinventoryslotname) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::getinventoryslotname(int16 slot_id)"); + + dXSTARG; + int16 slot_id = (int16) SvIV(ST(0)); + auto slot_name = quest_manager.getinventoryslotname(slot_id); + sv_setpv(TARG, slot_name.c_str()); + XSprePUSH; + PUSHTARG; + XSRETURN(1); +} + +XS(XS__rename); +XS(XS__rename) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::rename(string name)"); + + std::string name = (std::string) SvPV_nolen(ST(0)); + quest_manager.rename(name); + XSRETURN_EMPTY; +} + +XS(XS__get_data_remaining); +XS(XS__get_data_remaining) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::get_data_remaining(string bucket_name)"); + + dXSTARG; + std::string bucket_name = (std::string) SvPV_nolen(ST(0)); + + sv_setpv(TARG, DataBucket::GetDataRemaining(bucket_name).c_str()); + XSprePUSH; + PUSHTARG; + XSRETURN(1); +} + +XS(XS__getitemstat); +XS(XS__getitemstat) { + dXSARGS; + int stat_value; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::getitemstat(uint32 item_id, string stat_identifier)"); + + dXSTARG; + uint32 item_id = (uint32) SvUV(ST(0)); + std::string stat_identifier = (std::string) SvPV_nolen(ST(1)); + stat_value = quest_manager.getitemstat(item_id, stat_identifier); + + XSprePUSH; + PUSHi((IV)stat_value); + + XSRETURN(1); +} + +XS(XS__getspellstat); +XS(XS__getspellstat) { + dXSARGS; + int stat_value; + if (items != 2 && items != 3) + Perl_croak(aTHX_ "Usage: quest::getspellstat(uint32 spell_id, string stat_identifier, uint8 slot)"); + + dXSTARG; + uint32 spell_id = (uint32) SvUV(ST(0)); + std::string stat_identifier = (std::string) SvPV_nolen(ST(1)); + uint8 slot = 0; + if (items == 3) + slot = (uint8) SvUV(ST(2)); + + stat_value = quest_manager.getspellstat(spell_id, stat_identifier, slot); + + XSprePUSH; + PUSHi((IV)stat_value); + + XSRETURN(1); +} + +XS(XS__crosszoneaddldonlossbycharid); +XS(XS__crosszoneaddldonlossbycharid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneaddldonlossbycharid(int character_id, uint32 theme_id)"); + { + uint8 update_type = CZUpdateType_Character; + uint8 update_subtype = CZLDoNUpdateSubtype_AddLoss; + int character_id = (int) SvIV(ST(0)); + uint32 theme_id = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, character_id, theme_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneaddldonlossbygroupid); +XS(XS__crosszoneaddldonlossbygroupid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneaddldonlossbygroupid(int group_id, uint32 theme_id)"); + { + uint8 update_type = CZUpdateType_Group; + uint8 update_subtype = CZLDoNUpdateSubtype_AddLoss; + int group_id = (int) SvIV(ST(0)); + uint32 theme_id = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, group_id, theme_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneaddldonlossbyraidid); +XS(XS__crosszoneaddldonlossbyraidid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneaddldonlossbyraidid(int raid_id, uint32 theme_id)"); + { + uint8 update_type = CZUpdateType_Raid; + uint8 update_subtype = CZLDoNUpdateSubtype_AddLoss; + int raid_id = (int) SvIV(ST(0)); + uint32 theme_id = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, raid_id, theme_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneaddldonlossbyguildid); +XS(XS__crosszoneaddldonlossbyguildid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneaddldonlossbyguildid(int guild_id, uint32 theme_id)"); + { + uint8 update_type = CZUpdateType_Guild; + uint8 update_subtype = CZLDoNUpdateSubtype_AddLoss; + int guild_id = (int) SvIV(ST(0)); + uint32 theme_id = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, guild_id, theme_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneaddldonlossbyexpeditionid); +XS(XS__crosszoneaddldonlossbyexpeditionid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneaddldonlossbyexpeditionid(uint32 expedition_id, uint32 theme_id)"); + { + uint8 update_type = CZUpdateType_Expedition; + uint8 update_subtype = CZLDoNUpdateSubtype_AddLoss; + uint32 expedition_id = (uint32) SvUV(ST(0)); + uint32 theme_id = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, expedition_id, theme_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneaddldonlossbyclientname); +XS(XS__crosszoneaddldonlossbyclientname) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneaddldonlossbyclientname(const char* client_name, uint32 theme_id)"); + { + uint8 update_type = CZUpdateType_ClientName; + uint8 update_subtype = CZLDoNUpdateSubtype_AddLoss; + int update_identifier = 0; + int points = 1; + const char* client_name = (const char*) SvPV_nolen(ST(0)); + uint32 theme_id = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, update_identifier, theme_id, points, client_name); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneaddldonpointsbycharid); +XS(XS__crosszoneaddldonpointsbycharid) { + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: quest::crosszoneaddldonpointsbycharid(int character_id, uint32 theme_id, int points)"); + { + uint8 update_type = CZUpdateType_Character; + uint8 update_subtype = CZLDoNUpdateSubtype_AddPoints; + int character_id = (int) SvIV(ST(0)); + uint32 theme_id = (uint32) SvUV(ST(1)); + int points = (int) SvIV(ST(2)); + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, character_id, theme_id, points); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneaddldonpointsbygroupid); +XS(XS__crosszoneaddldonpointsbygroupid) { + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: quest::crosszoneaddldonpointsbygroupid(int group_id, uint32 theme_id, int points)"); + { + uint8 update_type = CZUpdateType_Group; + uint8 update_subtype = CZLDoNUpdateSubtype_AddPoints; + int group_id = (int) SvIV(ST(0)); + uint32 theme_id = (uint32) SvUV(ST(1)); + int points = (int) SvIV(ST(2)); + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, group_id, theme_id, points); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneaddldonpointsbyraidid); +XS(XS__crosszoneaddldonpointsbyraidid) { + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: quest::crosszoneaddldonpointsbyraidid(int raid_id, uint32 theme_id, int points)"); + { + uint8 update_type = CZUpdateType_Raid; + uint8 update_subtype = CZLDoNUpdateSubtype_AddPoints; + int raid_id = (int) SvIV(ST(0)); + uint32 theme_id = (uint32) SvUV(ST(1)); + int points = (int) SvIV(ST(2)); + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, raid_id, theme_id, points); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneaddldonpointsbyguildid); +XS(XS__crosszoneaddldonpointsbyguildid) { + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: quest::crosszoneaddldonpointsbyguildid(int guild_id, uint32 theme_id, int points)"); + { + uint8 update_type = CZUpdateType_Guild; + uint8 update_subtype = CZLDoNUpdateSubtype_AddPoints; + int guild_id = (int) SvIV(ST(0)); + uint32 theme_id = (uint32) SvUV(ST(1)); + int points = (int) SvIV(ST(2)); + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, guild_id, theme_id, points); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneaddldonpointsbyexpeditionid); +XS(XS__crosszoneaddldonpointsbyexpeditionid) { + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: quest::crosszoneaddldonpointsbyexpeditionid(uint32 expedition_id, uint32 theme_id, int points)"); + { + uint8 update_type = CZUpdateType_Expedition; + uint8 update_subtype = CZLDoNUpdateSubtype_AddPoints; + uint32 expedition_id = (uint32) SvUV(ST(0)); + uint32 theme_id = (uint32) SvUV(ST(1)); + int points = (int) SvIV(ST(2)); + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, expedition_id, theme_id, points); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneaddldonpointsbyclientname); +XS(XS__crosszoneaddldonpointsbyclientname) { + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: quest::crosszoneaddldonpointsbyclientname(const char* client_name, uint32 theme_id, int points)"); + { + uint8 update_type = CZUpdateType_ClientName; + uint8 update_subtype = CZLDoNUpdateSubtype_AddPoints; + int update_identifier = 0; + const char* client_name = (const char*) SvPV_nolen(ST(0)); + uint32 theme_id = (uint32) SvUV(ST(1)); + int points = (int) SvIV(ST(2)); + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, update_identifier, theme_id, points, client_name); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneaddldonwinbycharid); +XS(XS__crosszoneaddldonwinbycharid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneaddldonwinbycharid(int character_id, uint32 theme_id)"); + { + uint8 update_type = CZUpdateType_Character; + uint8 update_subtype = CZLDoNUpdateSubtype_AddWin; + int character_id = (int) SvIV(ST(0)); + uint32 theme_id = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, character_id, theme_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneaddldonwinbygroupid); +XS(XS__crosszoneaddldonwinbygroupid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneaddldonwinbygroupid(int group_id, uint32 theme_id)"); + { + uint8 update_type = CZUpdateType_Group; + uint8 update_subtype = CZLDoNUpdateSubtype_AddWin; + int group_id = (int) SvIV(ST(0)); + uint32 theme_id = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, group_id, theme_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneaddldonwinbyraidid); +XS(XS__crosszoneaddldonwinbyraidid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneaddldonwinbyraidid(int raid_id, uint32 theme_id)"); + { + uint8 update_type = CZUpdateType_Raid; + uint8 update_subtype = CZLDoNUpdateSubtype_AddWin; + int raid_id = (int) SvIV(ST(0)); + uint32 theme_id = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, raid_id, theme_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneaddldonwinbyguildid); +XS(XS__crosszoneaddldonwinbyguildid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneaddldonwinbyguildid(int guild_id, uint32 theme_id)"); + { + uint8 update_type = CZUpdateType_Guild; + uint8 update_subtype = CZLDoNUpdateSubtype_AddWin; + int guild_id = (int) SvIV(ST(0)); + uint32 theme_id = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, guild_id, theme_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneaddldonwinbyexpeditionid); +XS(XS__crosszoneaddldonwinbyexpeditionid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneaddldonwinbyexpeditionid(uint32 expedition_id, uint32 theme_id)"); + { + uint8 update_type = CZUpdateType_Expedition; + uint8 update_subtype = CZLDoNUpdateSubtype_AddWin; + uint32 expedition_id = (uint32) SvUV(ST(0)); + uint32 theme_id = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, expedition_id, theme_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneaddldonwinbyclientname); +XS(XS__crosszoneaddldonwinbyclientname) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneaddldonwinbyclientname(const char* client_name, uint32 theme_id)"); + { + uint8 update_type = CZUpdateType_ClientName; + uint8 update_subtype = CZLDoNUpdateSubtype_AddWin; + int update_identifier = 0; + int points = 1; + const char* client_name = (const char*) SvPV_nolen(ST(0)); + uint32 theme_id = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, update_identifier, theme_id, points, client_name); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneassigntaskbycharid); +XS(XS__crosszoneassigntaskbycharid) { + dXSARGS; + if (items < 2 || items > 3) + Perl_croak(aTHX_ "Usage: quest::crosszoneassigntaskbycharid(int character_id, uint32 task_identifier, [bool enforce_level_requirement = false])"); + { + uint8 update_type = CZUpdateType_Character; + uint8 update_subtype = CZTaskUpdateSubtype_AssignTask; + int character_id = (int) SvIV(ST(0)); + uint32 task_identifier = (uint32) SvIV(ST(1)); + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + if (items == 3) + enforce_level_requirement = (bool) SvTRUE(ST(2)); + + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, character_id, task_identifier, task_subidentifier, update_count, enforce_level_requirement); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneassigntaskbygroupid); +XS(XS__crosszoneassigntaskbygroupid) { + dXSARGS; + if (items < 2 || items > 3) + Perl_croak(aTHX_ "Usage: quest::crosszoneassigntaskbygroupid(int group_id, uint32 task_identifier, [bool enforce_level_requirement = false])"); + { + uint8 update_type = CZUpdateType_Group; + uint8 update_subtype = CZTaskUpdateSubtype_AssignTask; + int group_id = (int) SvIV(ST(0)); + uint32 task_identifier = (uint32) SvIV(ST(1)); + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + if (items == 3) + enforce_level_requirement = (bool) SvTRUE(ST(2)); + + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, group_id, task_identifier, task_subidentifier, update_count, enforce_level_requirement); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneassigntaskbyraidid); +XS(XS__crosszoneassigntaskbyraidid) { + dXSARGS; + if (items < 2 || items > 3) + Perl_croak(aTHX_ "Usage: quest::crosszoneassigntaskbyraidid(int raid_id, uint32 task_identifier, [bool enforce_level_requirement = false])");\ + { + uint8 update_type = CZUpdateType_Raid; + uint8 update_subtype = CZTaskUpdateSubtype_AssignTask; + int raid_id = (int) SvIV(ST(0)); + uint32 task_identifier = (uint32) SvIV(ST(1)); + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + if (items == 3) + enforce_level_requirement = (bool) SvTRUE(ST(2)); + + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, raid_id, task_identifier, task_subidentifier, update_count, enforce_level_requirement); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneassigntaskbyguildid); +XS(XS__crosszoneassigntaskbyguildid) { + dXSARGS; + if (items < 2 || items > 3) + Perl_croak(aTHX_ "Usage: quest::crosszoneassigntaskbyguildid(int guild_id, uint32 task_identifier, [bool enforce_level_requirement = false])"); + { + uint8 update_type = CZUpdateType_Guild; + uint8 update_subtype = CZTaskUpdateSubtype_AssignTask; + int guild_id = (int) SvIV(ST(0)); + uint32 task_identifier = (uint32) SvIV(ST(1)); + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + if (items == 3) + enforce_level_requirement = (bool) SvTRUE(ST(2)); + + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, guild_id, task_identifier, task_subidentifier, update_count, enforce_level_requirement); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneassigntaskbyexpeditionid); +XS(XS__crosszoneassigntaskbyexpeditionid) { + dXSARGS; + if (items < 2 || items > 3) + Perl_croak(aTHX_ "Usage: quest::crosszoneassigntaskbyexpeditionid(uint32 expedition_id, uint32 task_identifier, [bool enforce_level_requirement = false])"); + { + uint8 update_type = CZUpdateType_Expedition; + uint8 update_subtype = CZTaskUpdateSubtype_AssignTask; + uint32 expedition_id = (uint32) SvUV(ST(0)); + uint32 task_identifier = (uint32) SvIV(ST(1)); + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + if (items == 3) + enforce_level_requirement = (bool) SvTRUE(ST(2)); + + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, expedition_id, task_identifier, task_subidentifier, update_count, enforce_level_requirement); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneassigntaskbyclientname); +XS(XS__crosszoneassigntaskbyclientname) { + dXSARGS; + if (items < 2 || items > 3) + Perl_croak(aTHX_ "Usage: quest::crosszoneassigntaskbyclientname(const char* client_name, uint32 task_identifier, [bool enforce_level_requirement = false])"); + { + uint8 update_type = CZUpdateType_ClientName; + uint8 update_subtype = CZTaskUpdateSubtype_AssignTask; + int update_identifier = 0; + const char* client_name = (const char*) SvPV_nolen(ST(0)); + uint32 task_identifier = (uint32) SvIV(ST(1)); + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + if (items == 3) + enforce_level_requirement = (bool) SvTRUE(ST(2)); + + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, update_identifier, task_identifier, task_subidentifier, update_count, enforce_level_requirement, client_name); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonecastspellbycharid); +XS(XS__crosszonecastspellbycharid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonecastspellbycharid(int character_id, uint32 spell_id)"); + { + uint8 update_type = CZUpdateType_Character; + uint8 update_subtype = CZSpellUpdateSubtype_Cast; + int character_id = (int) SvIV(ST(0)); + uint32 spell_id = (uint32) SvIV(ST(1)); + quest_manager.CrossZoneSpell(update_type, update_subtype, character_id, spell_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonecastspellbygroupid); +XS(XS__crosszonecastspellbygroupid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonecastspellbygroupid(int group_id, uint32 spell_id)"); + { + uint8 update_type = CZUpdateType_Group; + uint8 update_subtype = CZSpellUpdateSubtype_Cast; + int group_id = (int) SvIV(ST(0)); + uint32 spell_id = (uint32) SvIV(ST(1)); + quest_manager.CrossZoneSpell(update_type, update_subtype, group_id, spell_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonecastspellbyraidid); +XS(XS__crosszonecastspellbyraidid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonecastspellbyraidid(int raid_id, uint32 spell_id)"); + { + uint8 update_type = CZUpdateType_Raid; + uint8 update_subtype = CZSpellUpdateSubtype_Cast; + int raid_id = (int) SvIV(ST(0)); + uint32 spell_id = (uint32) SvIV(ST(1)); + quest_manager.CrossZoneSpell(update_type, update_subtype, raid_id, spell_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonecastspellbyguildid); +XS(XS__crosszonecastspellbyguildid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonecastspellbyguildid(int guild_id, uint32 spell_id)"); + { + uint8 update_type = CZUpdateType_Guild; + uint8 update_subtype = CZSpellUpdateSubtype_Cast; + int guild_id = (int) SvIV(ST(0)); + uint32 spell_id = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneSpell(update_type, update_subtype, guild_id, spell_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonecastspellbyexpeditionid); +XS(XS__crosszonecastspellbyexpeditionid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonecastspellbyexpeditionid(uint32 expedition_id, uint32 spell_id)"); + { + uint8 update_type = CZUpdateType_Expedition; + uint8 update_subtype = CZSpellUpdateSubtype_Cast; + uint32 expedition_id = (uint32) SvUV(ST(0)); + uint32 spell_id = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneSpell(update_type, update_subtype, expedition_id, spell_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonecastspellbyclientname); +XS(XS__crosszonecastspellbyclientname) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonecastspellbyclientname(const char* client_name, uint32 spell_id)"); + { + uint8 update_type = CZUpdateType_ClientName; + uint8 update_subtype = CZSpellUpdateSubtype_Cast; + int update_identifier = 0; + const char* client_name = (const char*) SvPV_nolen(ST(0)); + uint32 spell_id = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneSpell(update_type, update_subtype, update_identifier, spell_id, client_name); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonedialoguewindowbycharid); +XS(XS__crosszonedialoguewindowbycharid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonedialoguewindowbycharid(int character_id, string message)"); + { + uint8 update_type = CZUpdateType_Character; + int character_id = (int) SvIV(ST(0)); + const char* message = (const char*) SvPV_nolen(ST(1)); + quest_manager.CrossZoneDialogueWindow(update_type, character_id, message); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonedialoguewindowbygroupid); +XS(XS__crosszonedialoguewindowbygroupid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonedialoguewindowbygroupid(int group_id, string message)"); + { + uint8 update_type = CZUpdateType_Group; + int group_id = (int) SvIV(ST(0)); + const char* message = (const char*) SvPV_nolen(ST(1)); + quest_manager.CrossZoneDialogueWindow(update_type, group_id, message); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonedialoguewindowbyraidid); +XS(XS__crosszonedialoguewindowbyraidid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonedialoguewindowbyraidid(int raid_id, string message)"); + { + uint8 update_type = CZUpdateType_Raid; + int raid_id = (int) SvIV(ST(0)); + const char* message = (const char*) SvPV_nolen(ST(1)); + quest_manager.CrossZoneDialogueWindow(update_type, raid_id, message); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonedialoguewindowbyguildid); +XS(XS__crosszonedialoguewindowbyguildid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonedialoguewindowbyguildid(int guild_id, string message)"); + { + uint8 update_type = CZUpdateType_Guild; + int guild_id = (int) SvIV(ST(0)); + const char* message = (const char*) SvPV_nolen(ST(1)); + quest_manager.CrossZoneDialogueWindow(update_type, guild_id, message); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonedialoguewindowbyexpeditionid); +XS(XS__crosszonedialoguewindowbyexpeditionid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonedialoguewindowbyexpeditionid(uint32 expedition_id, string message)"); + { + uint8 update_type = CZUpdateType_Expedition; + uint32 expedition_id = (uint32) SvUV(ST(0)); + const char* message = (const char*) SvPV_nolen(ST(1)); + quest_manager.CrossZoneDialogueWindow(update_type, expedition_id, message); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonedialoguewindowbyclientname); +XS(XS__crosszonedialoguewindowbyclientname) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonedialoguewindowbyclientname(const char* client_name, string message)"); + { + uint8 update_type = CZUpdateType_ClientName; + int update_identifier = 0; + const char* client_name = (const char*) SvPV_nolen(ST(0)); + const char* message = (const char*) SvPV_nolen(ST(1)); + quest_manager.CrossZoneDialogueWindow(update_type, update_identifier, message, client_name); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonedisabletaskbycharid); +XS(XS__crosszonedisabletaskbycharid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonedisabletaskbycharid(int character_id, uint32 task_identifier)"); + { + uint8 update_type = CZUpdateType_Character; + uint8 update_subtype = CZTaskUpdateSubtype_DisableTask; + int character_id = (int) SvIV(ST(0)); + uint32 task_identifier = (uint32) SvUV(ST(1)); + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, character_id, task_identifier, task_subidentifier, update_count, enforce_level_requirement); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonedisabletaskbygroupid); +XS(XS__crosszonedisabletaskbygroupid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonedisabletaskbygroupid(int group_id, uint32 task_identifier)"); + { + uint8 update_type = CZUpdateType_Group; + uint8 update_subtype = CZTaskUpdateSubtype_DisableTask; + int group_id = (int) SvIV(ST(0)); + uint32 task_identifier = (uint32) SvUV(ST(1)); + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, group_id, task_identifier, task_subidentifier, update_count, enforce_level_requirement); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonedisabletaskbyraidid); +XS(XS__crosszonedisabletaskbyraidid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonedisabletaskbyraidid(int raid_id, uint32 task_identifier)"); + { + uint8 update_type = CZUpdateType_Raid; + uint8 update_subtype = CZTaskUpdateSubtype_DisableTask; + int raid_id = (int) SvIV(ST(0)); + uint32 task_identifier = (uint32) SvUV(ST(1)); + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, raid_id, task_identifier, task_subidentifier, update_count, enforce_level_requirement); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonedisabletaskbyguildid); +XS(XS__crosszonedisabletaskbyguildid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonedisabletaskbyguildid(int guild_id, uint32 task_identifier)"); + { + uint8 update_type = CZUpdateType_Guild; + uint8 update_subtype = CZTaskUpdateSubtype_DisableTask; + int guild_id = (int) SvIV(ST(0)); + uint32 task_identifier = (uint32) SvUV(ST(1)); + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, guild_id, task_identifier, task_subidentifier, update_count, enforce_level_requirement); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonedisabletaskbyexpeditionid); +XS(XS__crosszonedisabletaskbyexpeditionid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonedisabletaskbyexpeditionid(uint32 expedition_id, uint32 task_identifier)"); + { + uint8 update_type = CZUpdateType_Expedition; + uint8 update_subtype = CZTaskUpdateSubtype_DisableTask; + uint32 expedition_id = (uint32) SvUV(ST(0)); + uint32 task_identifier = (uint32) SvUV(ST(1)); + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, expedition_id, task_identifier, task_subidentifier, update_count, enforce_level_requirement); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonedisabletaskbyclientname); +XS(XS__crosszonedisabletaskbyclientname) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonedisabletaskbyclientname(const char* client_name, uint32 task_identifier)"); + { + uint8 update_type = CZUpdateType_ClientName; + uint8 update_subtype = CZTaskUpdateSubtype_DisableTask; + int update_identifier = 0; + const char* client_name = (const char*) SvPV_nolen(ST(0)); + uint32 task_identifier = (uint32) SvUV(ST(1)); + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, update_identifier, task_identifier, task_subidentifier, update_count, enforce_level_requirement, client_name); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneenabletaskbycharid); +XS(XS__crosszoneenabletaskbycharid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneenabletaskbycharid(int character_id, uint32 task_identifier)"); + { + uint8 update_type = CZUpdateType_Character; + uint8 update_subtype = CZTaskUpdateSubtype_EnableTask; + int character_id = (int) SvIV(ST(0)); + uint32 task_identifier = (uint32) SvUV(ST(1)); + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, character_id, task_identifier, task_subidentifier, update_count, enforce_level_requirement); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneenabletaskbygroupid); +XS(XS__crosszoneenabletaskbygroupid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneenabletaskbygroupid(int group_id, uint32 task_identifier)"); + { + uint8 update_type = CZUpdateType_Group; + uint8 update_subtype = CZTaskUpdateSubtype_EnableTask; + int group_id = (int) SvIV(ST(0)); + uint32 task_identifier = (uint32) SvUV(ST(1)); + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, group_id, task_identifier, task_subidentifier, update_count, enforce_level_requirement); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneenabletaskbyraidid); +XS(XS__crosszoneenabletaskbyraidid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneenabletaskbyraidid(int raid_id, uint32 task_identifier)"); + { + uint8 update_type = CZUpdateType_Raid; + uint8 update_subtype = CZTaskUpdateSubtype_EnableTask; + int raid_id = (int) SvIV(ST(0)); + uint32 task_identifier = (uint32) SvUV(ST(1)); + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, raid_id, task_identifier, task_subidentifier, update_count, enforce_level_requirement); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneenabletaskbyguildid); +XS(XS__crosszoneenabletaskbyguildid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneenabletaskbyguildid(int guild_id, uint32 task_identifier)"); + { + uint8 update_type = CZUpdateType_Guild; + uint8 update_subtype = CZTaskUpdateSubtype_EnableTask; + int guild_id = (int) SvIV(ST(0)); + uint32 task_identifier = (uint32) SvUV(ST(1)); + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, guild_id, task_identifier, task_subidentifier, update_count, enforce_level_requirement); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneenabletaskbyexpeditionid); +XS(XS__crosszoneenabletaskbyexpeditionid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneenabletaskbyexpeditionid(uint32 expedition_id, uint32 task_identifier)"); + { + uint8 update_type = CZUpdateType_Expedition; + uint8 update_subtype = CZTaskUpdateSubtype_EnableTask; + uint32 expedition_id = (uint32) SvUV(ST(0)); + uint32 task_identifier = (uint32) SvUV(ST(1)); + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, expedition_id, task_identifier, task_subidentifier, update_count, enforce_level_requirement); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneenabletaskbyclientname); +XS(XS__crosszoneenabletaskbyclientname) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneenabletaskbyclientname(const char* client_name, uint32 task_identifier)"); + { + uint8 update_type = CZUpdateType_ClientName; + uint8 update_subtype = CZTaskUpdateSubtype_EnableTask; + int update_identifier = 0; + const char* client_name = (const char*) SvPV_nolen(ST(0)); + uint32 task_identifier = (uint32) SvUV(ST(1)); + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, update_identifier, task_identifier, task_subidentifier, update_count, enforce_level_requirement, client_name); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonefailtaskbycharid); +XS(XS__crosszonefailtaskbycharid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonefailtaskbycharid(int character_id, uint32 task_identifier)"); + { + uint8 update_type = CZUpdateType_Character; + uint8 update_subtype = CZTaskUpdateSubtype_FailTask; + int character_id = (int) SvIV(ST(0)); + uint32 task_identifier = (uint32) SvUV(ST(1)); + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, character_id, task_identifier, task_subidentifier, update_count, enforce_level_requirement); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonefailtaskbygroupid); +XS(XS__crosszonefailtaskbygroupid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonefailtaskbygroupid(int group_id, uint32 task_identifier)"); + { + uint8 update_type = CZUpdateType_Group; + uint8 update_subtype = CZTaskUpdateSubtype_FailTask; + int group_id = (int) SvIV(ST(0)); + uint32 task_identifier = (uint32) SvUV(ST(1)); + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, group_id, task_identifier, task_subidentifier, update_count, enforce_level_requirement); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonefailtaskbyraidid); +XS(XS__crosszonefailtaskbyraidid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonefailtaskbyraidid(int raid_id, uint32 task_identifier)"); + { + uint8 update_type = CZUpdateType_Raid; + uint8 update_subtype = CZTaskUpdateSubtype_FailTask; + int raid_id = (int) SvIV(ST(0)); + uint32 task_identifier = (uint32) SvUV(ST(1)); + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, raid_id, task_identifier, task_subidentifier, update_count, enforce_level_requirement); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonefailtaskbyguildid); +XS(XS__crosszonefailtaskbyguildid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonefailtaskbyguildid(int guild_id, uint32 task_identifier)"); + { + uint8 update_type = CZUpdateType_Guild; + uint8 update_subtype = CZTaskUpdateSubtype_FailTask; + int guild_id = (int) SvIV(ST(0)); + uint32 task_identifier = (uint32) SvUV(ST(1)); + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, guild_id, task_identifier, task_subidentifier, update_count, enforce_level_requirement); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonefailtaskbyexpeditionid); +XS(XS__crosszonefailtaskbyexpeditionid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonefailtaskbyexpeditionid(uint32 expedition_id, uint32 task_identifier)"); + { + uint8 update_type = CZUpdateType_Expedition; + uint8 update_subtype = CZTaskUpdateSubtype_FailTask; + uint32 expedition_id = (uint32) SvUV(ST(0)); + uint32 task_identifier = (uint32) SvUV(ST(1)); + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, expedition_id, task_identifier, task_subidentifier, update_count, enforce_level_requirement); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonefailtaskbyclientname); +XS(XS__crosszonefailtaskbyclientname) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonefailtaskbyclientname(const char* client_name, uint32 task_identifier)"); + { + uint8 update_type = CZUpdateType_ClientName; + uint8 update_subtype = CZTaskUpdateSubtype_FailTask; + int update_identifier = 0; + const char* client_name = (const char*) SvPV_nolen(ST(0)); + uint32 task_identifier = (uint32) SvUV(ST(1)); + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, update_identifier, task_identifier, task_subidentifier, update_count, enforce_level_requirement, client_name); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonemarqueebycharid); +XS(XS__crosszonemarqueebycharid) { + dXSARGS; + if (items != 7) + Perl_croak(aTHX_ "Usage: quest::crosszonemarqueebycharid(int character_id, uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char *message)"); + { + uint8 update_type = CZUpdateType_Character; + int character_id = (int) SvIV(ST(0)); + uint32 type = (uint32) SvUV(ST(1)); + int priority = (int) SvIV(ST(2)); + int fade_in = (int) SvIV(ST(3)); + int fade_out = (int) SvIV(ST(4)); + int duration = (int) SvIV(ST(5)); + char *message = (char *) SvPV_nolen(ST(6)); + quest_manager.CrossZoneMarquee(update_type, character_id, type, priority, fade_in, fade_out, duration, message); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonemarqueebygroupid); +XS(XS__crosszonemarqueebygroupid) { + dXSARGS; + if (items != 7) + Perl_croak(aTHX_ "Usage: quest::crosszonemarqueebygroupid(int group_id, uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char *message)"); + { + uint8 update_type = CZUpdateType_Group; + int group_id = (int) SvIV(ST(0)); + uint32 type = (uint32) SvUV(ST(1)); + int priority = (int) SvIV(ST(2)); + int fade_in = (int) SvIV(ST(3)); + int fade_out = (int) SvIV(ST(4)); + int duration = (int) SvIV(ST(5)); + char *message = (char *) SvPV_nolen(ST(6)); + quest_manager.CrossZoneMarquee(update_type, group_id, type, priority, fade_in, fade_out, duration, message); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonemarqueebyraidid); +XS(XS__crosszonemarqueebyraidid) { + dXSARGS; + if (items != 7) + Perl_croak(aTHX_ "Usage: quest::crosszonemarqueebyraidid(int raid_id, uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char *message)"); + { + uint8 update_type = CZUpdateType_Raid; + int raid_id = (int) SvIV(ST(0)); + uint32 type = (uint32) SvUV(ST(1)); + int priority = (int) SvIV(ST(2)); + int fade_in = (int) SvIV(ST(3)); + int fade_out = (int) SvIV(ST(4)); + int duration = (int) SvIV(ST(5)); + char *message = (char *) SvPV_nolen(ST(6)); + quest_manager.CrossZoneMarquee(update_type, raid_id, type, priority, fade_in, fade_out, duration, message); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonemarqueebyguildid); +XS(XS__crosszonemarqueebyguildid) { + dXSARGS; + if (items != 7) + Perl_croak(aTHX_ "Usage: quest::crosszonemarqueebyguildid(int guild_id, uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char *message)"); + { + uint8 update_type = CZUpdateType_Guild; + int guild_id = (int) SvIV(ST(0)); + uint32 type = (uint32) SvUV(ST(1)); + int priority = (int) SvIV(ST(2)); + int fade_in = (int) SvIV(ST(3)); + int fade_out = (int) SvIV(ST(4)); + int duration = (int) SvIV(ST(5)); + char *message = (char *) SvPV_nolen(ST(6)); + quest_manager.CrossZoneMarquee(update_type, guild_id, type, priority, fade_in, fade_out, duration, message); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonemarqueebyexpeditionid); +XS(XS__crosszonemarqueebyexpeditionid) { + dXSARGS; + if (items != 7) + Perl_croak(aTHX_ "Usage: quest::crosszonemarqueebyexpeditionid(uint32 expedition_id, uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char *message)"); + { + uint8 update_type = CZUpdateType_Expedition; + uint32 expedition_id = (uint32) SvUV(ST(0)); + uint32 type = (uint32) SvUV(ST(1)); + int priority = (int) SvIV(ST(2)); + int fade_in = (int) SvIV(ST(3)); + int fade_out = (int) SvIV(ST(4)); + int duration = (int) SvIV(ST(5)); + char *message = (char *) SvPV_nolen(ST(6)); + quest_manager.CrossZoneMarquee(update_type, expedition_id, type, priority, fade_in, fade_out, duration, message); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonemarqueebyclientname); +XS(XS__crosszonemarqueebyclientname) { + dXSARGS; + if (items != 7) + Perl_croak(aTHX_ "Usage: quest::crosszonemarqueebyclientname(const char* client_name, uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char *message)"); + { + uint8 update_type = CZUpdateType_ClientName; + int update_identifier = 0; + const char* client_name = (const char*) SvPV_nolen(ST(0)); + uint32 type = (uint32) SvUV(ST(1)); + int priority = (int) SvIV(ST(2)); + int fade_in = (int) SvIV(ST(3)); + int fade_out = (int) SvIV(ST(4)); + int duration = (int) SvIV(ST(5)); + char *message = (char *) SvPV_nolen(ST(6)); + quest_manager.CrossZoneMarquee(update_type, update_identifier, type, priority, fade_in, fade_out, duration, message, client_name); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonemessageplayerbycharid); +XS(XS__crosszonemessageplayerbycharid) { + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: quest::crosszonemessageplayerbycharid(int character_id, uint32 type, const char* message)"); + { + uint8 update_type = CZUpdateType_Character; + int character_id = (int) SvIV(ST(0)); + uint32 type = (uint32) SvUV(ST(1)); + const char* message = (const char*) SvPV_nolen(ST(2)); + quest_manager.CrossZoneMessage(update_type, character_id, type, message); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonemessageplayerbygroupid); +XS(XS__crosszonemessageplayerbygroupid) { + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: quest::crosszonemessageplayerbygroupid(int group_id, uint32 type, const char* message)"); + { + uint8 update_type = CZUpdateType_Group; + int group_id = (int) SvIV(ST(0)); + uint32 type = (uint32) SvUV(ST(1)); + const char* message = (const char*) SvPV_nolen(ST(2)); + quest_manager.CrossZoneMessage(update_type, group_id, type, message); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonemessageplayerbyraidid); +XS(XS__crosszonemessageplayerbyraidid) { + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: quest::crosszonemessageplayerbyraidid(int raid_id, uint32 type, const char* message)"); + { + uint8 update_type = CZUpdateType_Raid; + int raid_id = (int) SvIV(ST(0)); + uint32 type = (uint32) SvUV(ST(1)); + const char* message = (const char*) SvPV_nolen(ST(2)); + quest_manager.CrossZoneMessage(update_type, raid_id, type, message); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonemessageplayerbyguildid); +XS(XS__crosszonemessageplayerbyguildid) { + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: quest::crosszonemessageplayerbyguildid(int guild_id, uint32 type, const char* message)"); + { + uint8 update_type = CZUpdateType_Guild; + int guild_id = (int) SvIV(ST(0)); + uint32 type = (uint32) SvUV(ST(1)); + const char* message = (const char*) SvPV_nolen(ST(2)); + quest_manager.CrossZoneMessage(update_type, guild_id, type, message); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonemessageplayerbyexpeditionid); +XS(XS__crosszonemessageplayerbyexpeditionid) { + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: quest::crosszonemessageplayerbyexpeditionid(uint32 expedition_id, uint32 type, const char* message)"); + { + uint8 update_type = CZUpdateType_Expedition; + uint32 expedition_id = (uint32) SvUV(ST(0)); + uint32 type = (uint32) SvUV(ST(1)); + const char* message = (const char*) SvPV_nolen(ST(2)); + quest_manager.CrossZoneMessage(update_type, expedition_id, type, message); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonemessageplayerbyname); +XS(XS__crosszonemessageplayerbyname) { + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: quest::crosszonemessageplayerbyname(const char* client_name, uint32 type, const char* message)"); + { + uint8 update_type = CZUpdateType_ClientName; + int update_identifier = 0; + const char* client_name = (const char*) SvPV_nolen(ST(0)); + uint32 type = (uint32) SvUV(ST(1)); + const char* message = (const char*) SvPV_nolen(ST(2)); + quest_manager.CrossZoneMessage(update_type, update_identifier, type, message, client_name); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonemoveplayerbycharid); +XS(XS__crosszonemoveplayerbycharid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonemoveplayerbycharid(int character_id, string zone_short_name)"); + { + uint8 update_type = CZUpdateType_Character; + uint8 update_subtype = CZMoveUpdateSubtype_MoveZone; + int character_id = (int) SvIV(ST(0)); + const char* zone_short_name = (const char*) SvPV_nolen(ST(1)); + uint16 instance_id = 0; + quest_manager.CrossZoneMove(update_type, update_subtype, character_id, zone_short_name, instance_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonemoveplayerbygroupid); +XS(XS__crosszonemoveplayerbygroupid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonemoveplayerbygroupid(int group_id, string zone_short_name)"); + { + uint8 update_type = CZUpdateType_Group; + uint8 update_subtype = CZMoveUpdateSubtype_MoveZone; + int group_id = (int) SvIV(ST(0)); + const char* zone_short_name = (const char*) SvPV_nolen(ST(1)); + uint16 instance_id = 0; + quest_manager.CrossZoneMove(update_type, update_subtype, group_id, zone_short_name, instance_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonemoveplayerbyraidid); +XS(XS__crosszonemoveplayerbyraidid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonemoveplayerbyraidid(int raid_id, string zone_short_name)"); + { + uint8 update_type = CZUpdateType_Raid; + uint8 update_subtype = CZMoveUpdateSubtype_MoveZone; + int raid_id = (int) SvIV(ST(0)); + const char* zone_short_name = (const char*) SvPV_nolen(ST(1)); + uint16 instance_id = 0; + quest_manager.CrossZoneMove(update_type, update_subtype, raid_id, zone_short_name, instance_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonemoveplayerbyguildid); +XS(XS__crosszonemoveplayerbyguildid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonemoveplayerbyguildid(int guild_id, string zone_short_name)"); + { + uint8 update_type = CZUpdateType_Guild; + uint8 update_subtype = CZMoveUpdateSubtype_MoveZone; + int guild_id = (int) SvIV(ST(0)); + const char* zone_short_name = (const char*) SvPV_nolen(ST(1)); + uint16 instance_id = 0; + quest_manager.CrossZoneMove(update_type, update_subtype, guild_id, zone_short_name, instance_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonemoveplayerbyexpeditionid); +XS(XS__crosszonemoveplayerbyexpeditionid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonemoveplayerbyexpeditionid(uint32 expedition_id, string zone_short_name)"); + { + uint8 update_type = CZUpdateType_Expedition; + uint8 update_subtype = CZMoveUpdateSubtype_MoveZone; + uint32 expedition_id = (uint32) SvUV(ST(0)); + const char* zone_short_name = (const char*) SvPV_nolen(ST(1)); + uint16 instance_id = 0; + quest_manager.CrossZoneMove(update_type, update_subtype, expedition_id, zone_short_name, instance_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonemoveplayerbyname); +XS(XS__crosszonemoveplayerbyname) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonemoveplayerbyname(const char* client_name, string zone_short_name)"); + { + uint8 update_type = CZUpdateType_ClientName; + uint8 update_subtype = CZMoveUpdateSubtype_MoveZone; + int update_identifier = 0; + const char* client_name = (const char*) SvPV_nolen(ST(0)); + const char* zone_short_name = (const char*) SvPV_nolen(ST(1)); + uint16 instance_id = 0; + quest_manager.CrossZoneMove(update_type, update_subtype, update_identifier, zone_short_name, instance_id, client_name); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonemoveinstancebycharid); +XS(XS__crosszonemoveinstancebycharid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonemoveinstancebycharid(int character_id, uint16 instance_id)"); + { + uint8 update_type = CZUpdateType_Character; + uint8 update_subtype = CZMoveUpdateSubtype_MoveZoneInstance; + const char* zone_short_name = ""; + int character_id = (int) SvIV(ST(0)); + uint16 instance_id = (uint16) SvUV(ST(1)); + quest_manager.CrossZoneMove(update_type, update_subtype, character_id, zone_short_name, instance_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonemoveinstancebygroupid); +XS(XS__crosszonemoveinstancebygroupid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonemoveinstancebygroupid(int group_id, uint16 instance_id)"); + { + uint8 update_type = CZUpdateType_Group; + uint8 update_subtype = CZMoveUpdateSubtype_MoveZoneInstance; + const char* zone_short_name = ""; + int group_id = (int) SvIV(ST(0)); + uint16 instance_id = (uint16) SvUV(ST(1)); + quest_manager.CrossZoneMove(update_type, update_subtype, group_id, zone_short_name, instance_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonemoveinstancebyraidid); +XS(XS__crosszonemoveinstancebyraidid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonemoveinstancebyraidid(int raid_id, uint16 instance_id)"); + { + uint8 update_type = CZUpdateType_Raid; + uint8 update_subtype = CZMoveUpdateSubtype_MoveZoneInstance; + const char* zone_short_name = ""; + int raid_id = (int) SvIV(ST(0)); + uint16 instance_id = (uint16) SvUV(ST(1)); + quest_manager.CrossZoneMove(update_type, update_subtype, raid_id, zone_short_name, instance_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonemoveinstancebyguildid); +XS(XS__crosszonemoveinstancebyguildid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonemoveinstancebyguildid(int guild_id, uint16 instance_id)"); + { + uint8 update_type = CZUpdateType_Guild; + uint8 update_subtype = CZMoveUpdateSubtype_MoveZoneInstance; + const char* zone_short_name = ""; + int guild_id = (int) SvIV(ST(0)); + uint16 instance_id = (uint16) SvUV(ST(1)); + quest_manager.CrossZoneMove(update_type, update_subtype, guild_id, zone_short_name, instance_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonemoveinstancebyexpeditionid); +XS(XS__crosszonemoveinstancebyexpeditionid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonemoveinstancebyexpeditionid(uint32 expedition_id, uint16 instance_id)"); + { + uint8 update_type = CZUpdateType_Expedition; + uint8 update_subtype = CZMoveUpdateSubtype_MoveZoneInstance; + const char* zone_short_name = ""; + uint32 expedition_id = (uint32) SvUV(ST(0)); + uint16 instance_id = (uint16) SvUV(ST(1)); + quest_manager.CrossZoneMove(update_type, update_subtype, expedition_id, zone_short_name, instance_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonemoveinstancebyclientname); +XS(XS__crosszonemoveinstancebyclientname) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonemoveinstancebyclientname(const char* client_name, uint16 instance_id)"); + { + uint8 update_type = CZUpdateType_ClientName; + uint8 update_subtype = CZMoveUpdateSubtype_MoveZoneInstance; + const char* zone_short_name = ""; + int update_identifier = 0; + const char* client_name = (const char*) SvPV_nolen(ST(0)); + uint16 instance_id = (uint16) SvUV(ST(1)); + quest_manager.CrossZoneMove(update_type, update_subtype, update_identifier, zone_short_name, instance_id, client_name); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneremoveldonlossbycharid); +XS(XS__crosszoneremoveldonlossbycharid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneremoveldonlossbycharid(int character_id, uint32 theme_id)"); + { + uint8 update_type = CZUpdateType_Character; + uint8 update_subtype = CZLDoNUpdateSubtype_RemoveLoss; + int character_id = (int) SvIV(ST(0)); + uint32 theme_id = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, character_id, theme_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneremoveldonlossbygroupid); +XS(XS__crosszoneremoveldonlossbygroupid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneremoveldonlossbygroupid(int group_id, uint32 theme_id)"); + { + uint8 update_type = CZUpdateType_Group; + uint8 update_subtype = CZLDoNUpdateSubtype_RemoveLoss; + int group_id = (int) SvIV(ST(0)); + uint32 theme_id = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, group_id, theme_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneremoveldonlossbyraidid); +XS(XS__crosszoneremoveldonlossbyraidid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneremoveldonlossbyraidid(int raid_id, uint32 theme_id)"); + { + uint8 update_type = CZUpdateType_Raid; + uint8 update_subtype = CZLDoNUpdateSubtype_RemoveLoss; + int raid_id = (int) SvIV(ST(0)); + uint32 theme_id = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, raid_id, theme_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneremoveldonlossbyguildid); +XS(XS__crosszoneremoveldonlossbyguildid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneremoveldonlossbyguildid(int guild_id, uint32 theme_id)"); + { + uint8 update_type = CZUpdateType_Guild; + uint8 update_subtype = CZLDoNUpdateSubtype_RemoveLoss; + int guild_id = (int) SvIV(ST(0)); + uint32 theme_id = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, guild_id, theme_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneremoveldonlossbyexpeditionid); +XS(XS__crosszoneremoveldonlossbyexpeditionid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneremoveldonlossbyexpeditionid(uint32 expedition_id, uint32 theme_id)"); + { + uint8 update_type = CZUpdateType_Expedition; + uint8 update_subtype = CZLDoNUpdateSubtype_RemoveLoss; + uint32 expedition_id = (uint32) SvUV(ST(0)); + uint32 theme_id = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, expedition_id, theme_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneremoveldonlossbyclientname); +XS(XS__crosszoneremoveldonlossbyclientname) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneremoveldonlossbyclientname(const char* client_name, uint32 theme_id)"); + { + uint8 update_type = CZUpdateType_ClientName; + uint8 update_subtype = CZLDoNUpdateSubtype_RemoveLoss; + int update_identifier = 0; + int points = 1; + const char* client_name = (const char*) SvPV_nolen(ST(0)); + uint32 theme_id = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, update_identifier, theme_id, points, client_name); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneremoveldonwinbycharid); +XS(XS__crosszoneremoveldonwinbycharid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneremoveldonwinbycharid(int character_id, uint32 theme_id)"); + { + uint8 update_type = CZUpdateType_Character; + uint8 update_subtype = CZLDoNUpdateSubtype_RemoveWin; + int character_id = (int) SvIV(ST(0)); + uint32 theme_id = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, character_id, theme_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneremoveldonwinbygroupid); +XS(XS__crosszoneremoveldonwinbygroupid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneremoveldonwinbygroupid(int group_id, uint32 theme_id)"); + { + uint8 update_type = CZUpdateType_Group; + uint8 update_subtype = CZLDoNUpdateSubtype_RemoveWin; + int group_id = (int) SvIV(ST(0)); + uint32 theme_id = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, group_id, theme_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneremoveldonwinbyraidid); +XS(XS__crosszoneremoveldonwinbyraidid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneremoveldonwinbyraidid(int raid_id, uint32 theme_id)"); + { + uint8 update_type = CZUpdateType_Raid; + uint8 update_subtype = CZLDoNUpdateSubtype_RemoveWin; + int raid_id = (int) SvIV(ST(0)); + uint32 theme_id = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, raid_id, theme_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneremoveldonwinbyguildid); +XS(XS__crosszoneremoveldonwinbyguildid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneremoveldonwinbyguildid(int guild_id, uint32 theme_id)"); + { + uint8 update_type = CZUpdateType_Guild; + uint8 update_subtype = CZLDoNUpdateSubtype_RemoveWin; + int guild_id = (int) SvIV(ST(0)); + uint32 theme_id = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, guild_id, theme_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneremoveldonwinbyexpeditionid); +XS(XS__crosszoneremoveldonwinbyexpeditionid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneremoveldonwinbyexpeditionid(uint32 expedition_id, uint32 theme_id)"); + { + uint8 update_type = CZUpdateType_Expedition; + uint8 update_subtype = CZLDoNUpdateSubtype_RemoveWin; + uint32 expedition_id = (uint32) SvUV(ST(0)); + uint32 theme_id = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, expedition_id, theme_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneremoveldonwinbyclientname); +XS(XS__crosszoneremoveldonwinbyclientname) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneremoveldonwinbyclientname(const char* client_name, uint32 theme_id)"); + { + uint8 update_type = CZUpdateType_ClientName; + uint8 update_subtype = CZLDoNUpdateSubtype_RemoveWin; + int update_identifier = 0; + int points = 1; + const char* client_name = (const char*) SvPV_nolen(ST(0)); + uint32 theme_id = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, update_identifier, theme_id, points, client_name); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneremovespellbycharid); +XS(XS__crosszoneremovespellbycharid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneremovespellbycharid(int character_id, uint32 spell_id)"); + { + uint8 update_type = CZUpdateType_Character; + uint8 update_subtype = CZSpellUpdateSubtype_Remove; + int character_id = (int) SvIV(ST(0)); + uint32 spell_id = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneSpell(update_type, update_subtype, character_id, spell_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneremovespellbygroupid); +XS(XS__crosszoneremovespellbygroupid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneremovespellbygroupid(int group_id, uint32 spell_id)"); + { + uint8 update_type = CZUpdateType_Group; + uint8 update_subtype = CZSpellUpdateSubtype_Remove; + int group_id = (int) SvIV(ST(0)); + uint32 spell_id = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneSpell(update_type, update_subtype, group_id, spell_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneremovespellbyraidid); +XS(XS__crosszoneremovespellbyraidid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneremovespellbyraidid(int raid_id, uint32 spell_id)"); + { + uint8 update_type = CZUpdateType_Raid; + uint8 update_subtype = CZSpellUpdateSubtype_Remove; + int raid_id = (int) SvIV(ST(0)); + uint32 spell_id = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneSpell(update_type, update_subtype, raid_id, spell_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneremovespellbyguildid); +XS(XS__crosszoneremovespellbyguildid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneremovespellbyguildid(int guild_id, uint32 spell_id)"); + { + uint8 update_type = CZUpdateType_Guild; + uint8 update_subtype = CZSpellUpdateSubtype_Remove; + int guild_id = (int) SvIV(ST(0)); + uint32 spell_id = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneSpell(update_type, update_subtype, guild_id, spell_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneremovespellbyexpeditionid); +XS(XS__crosszoneremovespellbyexpeditionid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneremovespellbyexpeditionid(uint32 expedition_id, uint32 spell_id)"); + { + uint8 update_type = CZUpdateType_Expedition; + uint8 update_subtype = CZSpellUpdateSubtype_Remove; + uint32 expedition_id = (uint32) SvUV(ST(0)); + uint32 spell_id = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneSpell(update_type, update_subtype, expedition_id, spell_id); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneremovespellbyclientname); +XS(XS__crosszoneremovespellbyclientname) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneremovespellbyclientname(const char* client_name, uint32 spell_id)"); + { + uint8 update_type = CZUpdateType_ClientName; + uint8 update_subtype = CZSpellUpdateSubtype_Remove; + int update_identifier = 0; + const char* client_name = (const char*) SvPV_nolen(ST(0)); + uint32 spell_id = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneSpell(update_type, update_subtype, update_identifier, spell_id, client_name); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneremovetaskbycharid); +XS(XS__crosszoneremovetaskbycharid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneremovetaskbycharid(int character_id, uint32 task_identifier)"); + { + uint8 update_type = CZUpdateType_Character; + uint8 update_subtype = CZTaskUpdateSubtype_RemoveTask; + int character_id = (int) SvIV(ST(0)); + uint32 task_identifier = (uint32) SvUV(ST(1)); + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, character_id, task_identifier, task_subidentifier, update_count, enforce_level_requirement); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneremovetaskbygroupid); +XS(XS__crosszoneremovetaskbygroupid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneremovetaskbygroupid(int group_id, uint32 task_identifier)"); + { + uint8 update_type = CZUpdateType_Group; + uint8 update_subtype = CZTaskUpdateSubtype_RemoveTask; + int group_id = (int) SvIV(ST(0)); + uint32 task_identifier = (uint32) SvUV(ST(1)); + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, group_id, task_identifier, task_subidentifier, update_count, enforce_level_requirement); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneremovetaskbyraidid); +XS(XS__crosszoneremovetaskbyraidid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneremovetaskbyraidid(int raid_id, uint32 task_identifier)"); + { + uint8 update_type = CZUpdateType_Raid; + uint8 update_subtype = CZTaskUpdateSubtype_RemoveTask; + int raid_id = (int) SvIV(ST(0)); + uint32 task_identifier = (uint32) SvUV(ST(1)); + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, raid_id, task_identifier, task_subidentifier, update_count, enforce_level_requirement); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneremovetaskbyguildid); +XS(XS__crosszoneremovetaskbyguildid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneremovetaskbyguildid(int guild_id, uint32 task_identifier)"); + { + uint8 update_type = CZUpdateType_Guild; + uint8 update_subtype = CZTaskUpdateSubtype_RemoveTask; + int guild_id = (int) SvIV(ST(0)); + uint32 task_identifier = (uint32) SvUV(ST(1)); + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, guild_id, task_identifier, task_subidentifier, update_count, enforce_level_requirement); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneremovetaskbyexpeditionid); +XS(XS__crosszoneremovetaskbyexpeditionid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneremovetaskbyexpeditionid(uint32 expedition_id, uint32 task_identifier)"); + { + uint8 update_type = CZUpdateType_Expedition; + uint8 update_subtype = CZTaskUpdateSubtype_RemoveTask; + uint32 expedition_id = (uint32) SvUV(ST(0)); + uint32 task_identifier = (uint32) SvUV(ST(1)); + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, expedition_id, task_identifier, task_subidentifier, update_count, enforce_level_requirement); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneremovetaskbyclientname); +XS(XS__crosszoneremovetaskbyclientname) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszoneremovetaskbyclientname(const char* client_name, uint32 task_identifier)"); + { + uint8 update_type = CZUpdateType_ClientName; + uint8 update_subtype = CZTaskUpdateSubtype_RemoveTask; + int update_identifier = 0; + const char* client_name = (const char*) SvPV_nolen(ST(0)); + uint32 task_identifier = (uint32) SvUV(ST(1)); + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, update_identifier, task_identifier, task_subidentifier, update_count, enforce_level_requirement, client_name); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneresetactivitybycharid); +XS(XS__crosszoneresetactivitybycharid) { + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: quest::crosszoneresetactivitybycharid(int character_id, uint32 task_identifier, int activity_id)"); + { + uint8 update_type = CZUpdateType_Character; + uint8 update_subtype = CZTaskUpdateSubtype_ActivityReset; + int character_id = (int) SvIV(ST(0)); + uint32 task_identifier = (uint32) SvUV(ST(1)); + int activity_id = (int) SvIV(ST(2)); + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, character_id, task_identifier, task_subidentifier, update_count, enforce_level_requirement); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneresetactivitybygroupid); +XS(XS__crosszoneresetactivitybygroupid) { + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: quest::crosszoneresetactivitybygroupid(int group_id, uint32 task_identifier, int activity_id)"); + { + uint8 update_type = CZUpdateType_Group; + uint8 update_subtype = CZTaskUpdateSubtype_ActivityReset; + int group_id = (int) SvIV(ST(0)); + uint32 task_identifier = (uint32) SvUV(ST(1)); + int activity_id = (int) SvIV(ST(2)); + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, group_id, task_identifier, task_subidentifier, update_count, enforce_level_requirement); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneresetactivitybyraidid); +XS(XS__crosszoneresetactivitybyraidid) { + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: quest::crosszoneresetactivitybyraidid(int raid_id, uint32 task_identifier, int activity_id)"); + { + uint8 update_type = CZUpdateType_Raid; + uint8 update_subtype = CZTaskUpdateSubtype_ActivityReset; + int raid_id = (int) SvIV(ST(0)); + uint32 task_identifier = (uint32) SvUV(ST(1)); + int activity_id = (int) SvIV(ST(2)); + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, raid_id, task_identifier, task_subidentifier, update_count, enforce_level_requirement); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneresetactivitybyguildid); +XS(XS__crosszoneresetactivitybyguildid) { + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: quest::crosszoneresetactivitybyguildid(int guild_id, uint32 task_identifier, int activity_id)"); + { + uint8 update_type = CZUpdateType_Guild; + uint8 update_subtype = CZTaskUpdateSubtype_ActivityReset; + int guild_id = (int) SvIV(ST(0)); + uint32 task_identifier = (uint32) SvUV(ST(1)); + int activity_id = (int) SvIV(ST(2)); + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, guild_id, task_identifier, task_subidentifier, update_count, enforce_level_requirement); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneresetactivitybyexpeditionid); +XS(XS__crosszoneresetactivitybyexpeditionid) { + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: quest::crosszoneresetactivitybyexpeditionid(uint32 expedition_id, uint32 task_identifier, int activity_id)"); + { + uint8 update_type = CZUpdateType_Expedition; + uint8 update_subtype = CZTaskUpdateSubtype_ActivityReset; + uint32 expedition_id = (uint32) SvUV(ST(0)); + uint32 task_identifier = (uint32) SvUV(ST(1)); + int activity_id = (int) SvIV(ST(2)); + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, expedition_id, task_identifier, task_subidentifier, update_count, enforce_level_requirement); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneresetactivitybyclientname); +XS(XS__crosszoneresetactivitybyclientname) { + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: quest::crosszoneresetactivitybyclientname(const char* client_name, uint32 task_identifier, int activity_id)"); + { + uint8 update_type = CZUpdateType_ClientName; + uint8 update_subtype = CZTaskUpdateSubtype_ActivityReset; + int update_identifier = 0; + const char* client_name = (const char*) SvPV_nolen(ST(0)); + uint32 task_identifier = (uint32) SvUV(ST(1)); + int activity_id = (int) SvIV(ST(2)); + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, update_identifier, task_identifier, task_subidentifier, update_count, enforce_level_requirement, client_name); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonesetentityvariablebycharid); +XS(XS__crosszonesetentityvariablebycharid) { + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: quest::crosszonesetentityvariablebycharid(int character_id, const char* variable_name, const char* variable_value)"); + { + uint8 update_type = CZUpdateType_Character; + int character_id = (int) SvIV(ST(0)); + const char* variable_name = (const char*) SvPV_nolen(ST(1)); + const char* variable_value = (const char*) SvPV_nolen(ST(2)); + quest_manager.CrossZoneSetEntityVariable(update_type, character_id, variable_name, variable_value); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonesetentityvariablebygroupid); +XS(XS__crosszonesetentityvariablebygroupid) { + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: quest::crosszonesetentityvariablebygroupid(int group_id, const char* variable_name, const char* variable_value)"); + { + uint8 update_type = CZUpdateType_Group; + int group_id = (int) SvIV(ST(0)); + const char* variable_name = (const char*) SvPV_nolen(ST(1)); + const char* variable_value = (const char*) SvPV_nolen(ST(2)); + quest_manager.CrossZoneSetEntityVariable(update_type, group_id, variable_name, variable_value); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonesetentityvariablebyraidid); +XS(XS__crosszonesetentityvariablebyraidid) { + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: quest::crosszonesetentityvariablebyraidid(int raid_id, const char* variable_name, const char* variable_value)"); + { + uint8 update_type = CZUpdateType_Raid; + int raid_id = (int) SvIV(ST(0)); + const char* variable_name = (const char*) SvPV_nolen(ST(1)); + const char* variable_value = (const char*) SvPV_nolen(ST(2)); + quest_manager.CrossZoneSetEntityVariable(update_type, raid_id, variable_name, variable_value); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonesetentityvariablebyguildid); +XS(XS__crosszonesetentityvariablebyguildid) { + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: quest::crosszonesetentityvariablebyguildid(int guild_id, const char* variable_name, const char* variable_value)"); + { + uint8 update_type = CZUpdateType_Guild; + int guild_id = (int) SvIV(ST(0)); + const char* variable_name = (const char*) SvPV_nolen(ST(1)); + const char* variable_value = (const char*) SvPV_nolen(ST(2)); + quest_manager.CrossZoneSetEntityVariable(update_type, guild_id, variable_name, variable_value); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonesetentityvariablebyexpeditionid); +XS(XS__crosszonesetentityvariablebyexpeditionid) { + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: quest::crosszonesetentityvariablebyexpeditionid(uint32 expedition_id, const char* variable_name, const char* variable_value)"); + { + uint8 update_type = CZUpdateType_Expedition; + uint32 expedition_id = (uint32) SvUV(ST(0)); + const char* variable_name = (const char*) SvPV_nolen(ST(1)); + const char* variable_value = (const char*) SvPV_nolen(ST(2)); + quest_manager.CrossZoneSetEntityVariable(update_type, expedition_id, variable_name, variable_value); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonesetentityvariablebyclientname); +XS(XS__crosszonesetentityvariablebyclientname) { + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: quest::crosszonesetentityvariablebyclientname(const char* client_name, const char* variable_name, const char* variable_value)"); + { + uint8 update_type = CZUpdateType_ClientName; + int update_identifier = 0; + const char* client_name = (const char*) SvPV_nolen(ST(0)); + const char* variable_name = (const char*) SvPV_nolen(ST(1)); + const char* variable_value = (const char*) SvPV_nolen(ST(2)); + quest_manager.CrossZoneSetEntityVariable(update_type, update_identifier, variable_name, variable_value, client_name); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonesetentityvariablebynpctypeid); +XS(XS__crosszonesetentityvariablebynpctypeid) { + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: quest::crosszonesetentityvariablebynpctypeid(int npc_id, const char* variable_name, const char* variable_value)"); + { + uint8 update_type = CZUpdateType_NPC; + int npc_id = (int) SvIV(ST(0)); + const char* variable_name = (const char*) SvPV_nolen(ST(1)); + const char* variable_value = (const char*) SvPV_nolen(ST(2)); + quest_manager.CrossZoneSetEntityVariable(update_type, npc_id, variable_name, variable_value); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonesignalclientbycharid); +XS(XS__crosszonesignalclientbycharid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonesignalclientbycharid(int character_id, uint32 signal)"); + { + uint8 update_type = CZUpdateType_Character; + int character_id = (int) SvIV(ST(0)); + uint32 signal = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneSignal(update_type, character_id, signal); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonesignalclientbygroupid); +XS(XS__crosszonesignalclientbygroupid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonesignalclientbygroupid(int group_id, uint32 signal)"); + { + uint8 update_type = CZUpdateType_Group; + int group_id = (int) SvIV(ST(0)); + uint32 signal = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneSignal(update_type, group_id, signal); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonesignalclientbyraidid); +XS(XS__crosszonesignalclientbyraidid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonesignalclientbyraidid(int raid_id, uint32 signal)"); + { + uint8 update_type = CZUpdateType_Raid; + int raid_id = (int) SvIV(ST(0)); + uint32 signal = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneSignal(update_type, raid_id, signal); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonesignalclientbyguildid); +XS(XS__crosszonesignalclientbyguildid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonesignalclientbyguildid(int guild_id, uint32 signal)"); + { + uint8 update_type = CZUpdateType_Guild; + int guild_id = (int) SvIV(ST(0)); + uint32 signal = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneSignal(update_type, guild_id, signal); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonesignalclientbyexpeditionid); +XS(XS__crosszonesignalclientbyexpeditionid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonesignalclientbyexpeditionid(uint32 expedition_id, uint32 signal)"); + { + uint8 update_type = CZUpdateType_Expedition; + uint32 expedition_id = (uint32) SvUV(ST(0)); + uint32 signal = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneSignal(update_type, expedition_id, signal); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonesignalclientbyname); +XS(XS__crosszonesignalclientbyname) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonesignalclientbyname(const char* client_name, uint32 signal)"); + { + uint8 update_type = CZUpdateType_Expedition; + int update_identifier = 0; + const char* client_name = (const char*) SvPV_nolen(ST(0)); + uint32 signal = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneSignal(update_type, update_identifier, signal, client_name); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszonesignalnpcbynpctypeid); +XS(XS__crosszonesignalnpcbynpctypeid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::crosszonesignalnpcbynpctypeid(uint32 npc_id, uint32 signal)"); + { + uint8 update_type = CZUpdateType_NPC; + uint32 npc_id = (uint32) SvUV(ST(0)); + uint32 signal = (uint32) SvUV(ST(1)); + quest_manager.CrossZoneSignal(update_type, npc_id, signal); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneupdateactivitybycharid); +XS(XS__crosszoneupdateactivitybycharid) { + dXSARGS; + if (items < 3 || items > 4) + Perl_croak(aTHX_ "Usage: quest::crosszoneupdateactivitybycharid(int character_id, uint32 task_identifier, int activity_id, [int update_count = 1])"); + { + uint8 update_type = CZUpdateType_Character; + uint8 update_subtype = CZTaskUpdateSubtype_ActivityUpdate; + int character_id = (int) SvIV(ST(0)); + uint32 task_identifier = (uint32) SvUV(ST(1)); + int task_subidentifier = (int) SvIV(ST(2)); + int update_count = 1; + bool enforce_level_requirement = false; + if (items == 4) + update_count = (int) SvIV(ST(3)); + + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, character_id, task_identifier, task_subidentifier, update_count, enforce_level_requirement); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneupdateactivitybygroupid); +XS(XS__crosszoneupdateactivitybygroupid) { + dXSARGS; + if (items < 3 || items > 4) + Perl_croak(aTHX_ "Usage: quest::crosszoneupdateactivitybygroupid(int group_id, uint32 task_identifier, int activity_id, [int update_count = 1])"); + { + uint8 update_type = CZUpdateType_Group; + uint8 update_subtype = CZTaskUpdateSubtype_ActivityUpdate; + int group_id = (int) SvIV(ST(0)); + uint32 task_identifier = (uint32) SvUV(ST(1)); + int task_subidentifier = (int) SvIV(ST(2)); + int update_count = 1; + bool enforce_level_requirement = false; + if (items == 4) + update_count = (int) SvIV(ST(3)); + + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, group_id, task_identifier, task_subidentifier, update_count, enforce_level_requirement); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneupdateactivitybyraidid); +XS(XS__crosszoneupdateactivitybyraidid) { + dXSARGS; + if (items < 3 || items > 4) + Perl_croak(aTHX_ "Usage: quest::crosszoneupdateactivitybyraidid(int raid_id, uint32 task_identifier, int activity_id, [int update_count = 1])"); + { + uint8 update_type = CZUpdateType_Raid; + uint8 update_subtype = CZTaskUpdateSubtype_ActivityUpdate; + int raid_id = (int) SvIV(ST(0)); + uint32 task_identifier = (uint32) SvUV(ST(1)); + int task_subidentifier = (int) SvIV(ST(2)); + int update_count = 1; + bool enforce_level_requirement = false; + if (items == 4) + update_count = (int) SvIV(ST(3)); + + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, raid_id, task_identifier, task_subidentifier, update_count, enforce_level_requirement); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneupdateactivitybyguildid); +XS(XS__crosszoneupdateactivitybyguildid) { + dXSARGS; + if (items < 3 || items > 4) + Perl_croak(aTHX_ "Usage: quest::crosszoneupdateactivitybyguildid(int guild_id, uint32 task_identifier, int activity_id, [int update_count = 1])"); + { + uint8 update_type = CZUpdateType_Guild; + uint8 update_subtype = CZTaskUpdateSubtype_ActivityUpdate; + int guild_id = (int) SvIV(ST(0)); + uint32 task_identifier = (uint32) SvUV(ST(1)); + int task_subidentifier = (int) SvIV(ST(2)); + int update_count = 1; + bool enforce_level_requirement = false; + if (items == 4) + update_count = (int) SvIV(ST(3)); + + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, guild_id, task_identifier, task_subidentifier, update_count, enforce_level_requirement); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneupdateactivitybyexpeditionid); +XS(XS__crosszoneupdateactivitybyexpeditionid) { + dXSARGS; + if (items < 3 || items > 4) + Perl_croak(aTHX_ "Usage: quest::crosszoneupdateactivitybyexpeditionid(uint32 expedition_id, uint32 task_identifier, int activity_id, [int update_count = 1])"); + { + uint8 update_type = CZUpdateType_Expedition; + uint8 update_subtype = CZTaskUpdateSubtype_ActivityUpdate; + uint32 expedition_id = (uint32) SvUV(ST(0)); + uint32 task_identifier = (uint32) SvUV(ST(1)); + int task_subidentifier = (int) SvIV(ST(2)); + int update_count = 1; + bool enforce_level_requirement = false; + if (items == 4) + update_count = (int) SvIV(ST(3)); + + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, expedition_id, task_identifier, task_subidentifier, update_count, enforce_level_requirement); + } + XSRETURN_EMPTY; +} + +XS(XS__crosszoneupdateactivitybyclientname); +XS(XS__crosszoneupdateactivitybyclientname) { + dXSARGS; + if (items < 3 || items > 4) + Perl_croak(aTHX_ "Usage: quest::crosszoneupdateactivitybyclientname(const char* client_name, uint32 task_identifier, int activity_id, [int update_count = 1])"); + { + uint8 update_type = CZUpdateType_ClientName; + uint8 update_subtype = CZTaskUpdateSubtype_ActivityUpdate; + int update_identifier = 0; + const char* client_name = (const char*) SvPV_nolen(ST(0)); + uint32 task_identifier = (uint32) SvUV(ST(1)); + int task_subidentifier = (int) SvIV(ST(2)); + int update_count = 1; + bool enforce_level_requirement = false; + if (items == 4) + update_count = (int) SvIV(ST(3)); + + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, update_identifier, task_identifier, task_subidentifier, update_count, enforce_level_requirement, client_name); + } + XSRETURN_EMPTY; +} + +XS(XS__worldwideaddldonloss); +XS(XS__worldwideaddldonloss) { + dXSARGS; + if (items < 1 || items > 3) + Perl_croak(aTHX_ "Usage: quest::worldwideaddldonloss(uint32 theme_id, [min_status = 0, max_status = 0])"); + { + uint8 update_type = CZLDoNUpdateSubtype_AddLoss; + uint32 theme_id = (uint32)SvUV(ST(0)); + int points = 1; + uint8 min_status = AccountStatus::Player; + uint8 max_status = AccountStatus::Player; + if (items == 2) + min_status = (uint8)SvUV(ST(1)); + + if (items == 3) + max_status = (uint8)SvUV(ST(2)); + + quest_manager.WorldWideLDoNUpdate(update_type, theme_id, points, min_status, max_status); + } + XSRETURN_EMPTY; +} + +XS(XS__worldwideaddldonpoints); +XS(XS__worldwideaddldonpoints) { + dXSARGS; + if (items < 1 || items > 4) + Perl_croak(aTHX_ "Usage: quest::worldwideaddldonpoints(uint32 theme_id. [int points = 1, min_status = 0, max_status = 0])"); + { + uint8 update_type = CZLDoNUpdateSubtype_AddPoints; + uint32 theme_id = (uint32)SvUV(ST(0)); + int points = 1; + uint8 min_status = AccountStatus::Player; + uint8 max_status = AccountStatus::Player; + if (items == 2) + points = (int)SvIV(ST(1)); + + if (items == 3) + min_status = (uint8)SvUV(ST(2)); + + if (items == 4) + max_status = (uint8)SvUV(ST(3)); + + quest_manager.WorldWideLDoNUpdate(update_type, theme_id, points, min_status, max_status); + } + XSRETURN_EMPTY; +} + +XS(XS__worldwideaddldonwin); +XS(XS__worldwideaddldonwin) { + dXSARGS; + if (items < 1 || items > 3) + Perl_croak(aTHX_ "Usage: quest::worldwideaddldonwin(uint32 theme_id, [min_status = 0, max_status = 0])"); + { + uint8 update_type = CZLDoNUpdateSubtype_AddWin; + uint32 theme_id = (uint32)SvUV(ST(0)); + int points = 1; + uint8 min_status = AccountStatus::Player; + uint8 max_status = AccountStatus::Player; + if (items == 2) + min_status = (uint8)SvUV(ST(1)); + + if (items == 3) + max_status = (uint8)SvUV(ST(2)); + + quest_manager.WorldWideLDoNUpdate(update_type, theme_id, points, min_status, max_status); + } + XSRETURN_EMPTY; +} + +XS(XS__worldwideassigntask); +XS(XS__worldwideassigntask) { + dXSARGS; + if (items < 1 || items > 3) + Perl_croak(aTHX_ "Usage: quest::worldwideassigntask(uint32 task_identifier, [uint8 min_status = 0, uint8 max_status = 0])"); + { + uint8 update_type = WWTaskUpdateType_AssignTask; + uint32 task_identifier = (uint32) SvUV(ST(0)); + uint8 min_status = AccountStatus::Player; + uint8 max_status = AccountStatus::Player; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + if (items == 2) + min_status = (uint8) SvUV(ST(1)); + + if (items == 3) + max_status = (uint8) SvUV(ST(2)); + + quest_manager.WorldWideTaskUpdate(update_type, task_identifier, task_subidentifier, update_count, enforce_level_requirement, min_status, max_status); + } + XSRETURN_EMPTY; +} + +XS(XS__worldwidecastspell); +XS(XS__worldwidecastspell) { + dXSARGS; + if (items < 1 || items > 3) + Perl_croak(aTHX_ "Usage: quest::worldwidecastspell(uint32 spell_id, [uint8 min_status = 0, uint8 max_status = 0])"); + { + uint8 update_type = WWSpellUpdateType_Cast; + uint32 spell_id = (uint32) SvUV(ST(0)); + uint8 min_status = AccountStatus::Player; + uint8 max_status = AccountStatus::Player; + if (items == 2) + min_status = (uint8) SvUV(ST(1)); + + if (items == 3) + max_status = (uint8) SvUV(ST(2)); + + quest_manager.WorldWideSpell(update_type, spell_id, min_status, max_status); + } + XSRETURN_EMPTY; +} + +XS(XS__worldwidedialoguewindow); +XS(XS__worldwidedialoguewindow) { + dXSARGS; + if (items < 1 || items > 3) + Perl_croak(aTHX_ "Usage: quest::worldwidedialoguewindow(string message, [uint8 min_status = 0, uint8 max_status = 0])"); + { + const char* message = (const char*) SvPV_nolen(ST(0)); + uint8 min_status = AccountStatus::Player; + uint8 max_status = AccountStatus::Player; + if (items == 2) + min_status = (uint8)SvUV(ST(1)); + + if (items == 3) + max_status = (uint8)SvUV(ST(2)); + + quest_manager.WorldWideDialogueWindow(message, min_status, max_status); + } + XSRETURN_EMPTY; +} + +XS(XS__worldwidedisabletask); +XS(XS__worldwidedisabletask) { + dXSARGS; + if (items < 1 || items > 3) + Perl_croak(aTHX_ "Usage: quest::worldwidedisabletask(uint32 task_id, [uint8 min_status = 0, uint8 max_status = 0])"); + { + uint8 update_type = WWTaskUpdateType_DisableTask; + uint32 task_identifier = (uint32) SvUV(ST(0)); + uint8 min_status = AccountStatus::Player; + uint8 max_status = AccountStatus::Player; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + if (items == 2) + min_status = (uint8) SvUV(ST(1)); + + if (items == 3) + max_status = (uint8) SvUV(ST(2)); + + quest_manager.WorldWideTaskUpdate(update_type, task_identifier, task_subidentifier, update_count, enforce_level_requirement, min_status, max_status); + } + XSRETURN_EMPTY; +} + +XS(XS__worldwideenabletask); +XS(XS__worldwideenabletask) { + dXSARGS; + if (items < 1 || items > 3) + Perl_croak(aTHX_ "Usage: quest::worldwideenabletask(uint32 task_id, [uint8 min_status = 0, uint8 max_status = 0])"); + { + uint8 update_type = WWTaskUpdateType_EnableTask; + uint32 task_identifier = (uint32) SvUV(ST(0)); + uint8 min_status = AccountStatus::Player; + uint8 max_status = AccountStatus::Player; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + if (items == 2) + min_status = (uint8) SvUV(ST(1)); + + if (items == 3) + max_status = (uint8) SvUV(ST(2)); + + quest_manager.WorldWideTaskUpdate(update_type, task_identifier, task_subidentifier, update_count, enforce_level_requirement, min_status, max_status); + } + XSRETURN_EMPTY; +} + +XS(XS__worldwidefailtask); +XS(XS__worldwidefailtask) { + dXSARGS; + if (items < 1 || items > 3) + Perl_croak(aTHX_ "Usage: quest::worldwidefailtask(uint32 task_id, [uint8 min_status = 0, uint8 max_status = 0])"); + { + uint8 update_type = WWTaskUpdateType_FailTask; + uint32 task_identifier = (uint32) SvUV(ST(0)); + uint8 min_status = AccountStatus::Player; + uint8 max_status = AccountStatus::Player; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + if (items == 2) + min_status = (uint8) SvUV(ST(1)); + + if (items == 3) + max_status = (uint8) SvUV(ST(2)); + + quest_manager.WorldWideTaskUpdate(update_type, task_identifier, task_subidentifier, update_count, enforce_level_requirement, min_status, max_status); + } + XSRETURN_EMPTY; +} + +XS(XS__worldwidemarquee); +XS(XS__worldwidemarquee) { + dXSARGS; + if (items < 6 || items > 8) + Perl_croak(aTHX_ "Usage: quest::worldwidemarquee(uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char* message, [uint8 min_status = 0, uint8 max_status = 0])"); + { + uint32 type = (uint32) SvUV(ST(0)); + uint32 priority = (uint32) SvUV(ST(1)); + uint32 fade_in = (uint32) SvUV(ST(2)); + uint32 fade_out = (uint32) SvUV(ST(3)); + uint32 duration = (uint32) SvUV(ST(4)); + const char* message = (const char*) SvPV_nolen(ST(5)); + uint8 min_status = AccountStatus::Player; + uint8 max_status = AccountStatus::Player; + if (items == 7) + min_status = (uint8) SvUV(ST(6)); + + if (items == 8) + max_status = (uint8) SvUV(ST(7)); + + quest_manager.WorldWideMarquee(type, priority, fade_in, fade_out, duration, message, min_status, max_status); + } + XSRETURN_EMPTY; +} + +XS(XS__worldwidemessage); +XS(XS__worldwidemessage) { + dXSARGS; + if (items < 2 || items > 4) + Perl_croak(aTHX_ "Usage: quest::worldwidemessage(uint32 type, const char* message, [uint8 min_status = 0, uint8 max_status = 0])"); + { + uint32 type = (uint32) SvUV(ST(0)); + const char* message = (const char*) SvPV_nolen(ST(1)); + uint8 min_status = AccountStatus::Player; + uint8 max_status = AccountStatus::Player; + if (items == 3) + min_status = (uint8) SvUV(ST(2)); + + if (items == 4) + max_status = (uint8) SvUV(ST(3)); + + quest_manager.WorldWideMessage(type, message, min_status, max_status); + } + XSRETURN_EMPTY; +} + +XS(XS__worldwidemove); +XS(XS__worldwidemove) { + dXSARGS; + if (items < 1 || items > 3) + Perl_croak(aTHX_ "Usage: quest::worldwidemove(const char* zone_short_name, [uint8 min_status = 0, uint8 max_status = 0])"); + { + uint8 update_type = WWMoveUpdateType_MoveZone; + const char* zone_short_name = (const char*) SvPV_nolen(ST(0)); + uint16 instance_id = 0; + uint8 min_status = AccountStatus::Player; + uint8 max_status = AccountStatus::Player; + if (items == 2) + min_status = (uint8) SvUV(ST(1)); + + if (items == 3) + max_status = (uint8) SvUV(ST(2)); + + quest_manager.WorldWideMove(update_type, zone_short_name, instance_id, min_status, max_status); + } + XSRETURN_EMPTY; +} + +XS(XS__worldwidemoveinstance); +XS(XS__worldwidemoveinstance) { + dXSARGS; + if (items < 1 || items > 3) + Perl_croak(aTHX_ "Usage: quest::worldwidemoveinstance(uint16 instance_id, [uint8 min_status = 0, uint max_status = 0])"); + { + uint8 update_type = WWMoveUpdateType_MoveZoneInstance; + const char* zone_short_name = ""; + uint16 instance_id = (uint16) SvUV(ST(0)); + uint8 min_status = AccountStatus::Player; + uint8 max_status = AccountStatus::Player; + if (items == 2) + min_status = (uint8) SvUV(ST(1)); + + if (items == 3) + max_status = (uint8) SvUV(ST(2)); + + quest_manager.WorldWideMove(update_type, zone_short_name, instance_id, min_status, max_status); + } + XSRETURN_EMPTY; +} + +XS(XS__worldwideremoveldonloss); +XS(XS__worldwideremoveldonloss) { + dXSARGS; + if (items < 1 || items > 3) + Perl_croak(aTHX_ "Usage: quest::worldwideremoveldonloss(uint32 theme_id, [min_status = 0, max_status = 0])"); + { + uint8 update_type = CZLDoNUpdateSubtype_RemoveLoss; + uint32 theme_id = (uint32)SvUV(ST(0)); + int points = 1; + uint8 min_status = AccountStatus::Player; + uint8 max_status = AccountStatus::Player; + if (items == 2) + min_status = (uint8)SvUV(ST(1)); + + if (items == 3) + max_status = (uint8)SvUV(ST(2)); + + quest_manager.WorldWideLDoNUpdate(update_type, theme_id, points, min_status, max_status); + } + XSRETURN_EMPTY; +} + +XS(XS__worldwideremoveldonwin); +XS(XS__worldwideremoveldonwin) { + dXSARGS; + if (items < 1 || items > 3) + Perl_croak(aTHX_ "Usage: quest::worldwideremoveldonwin(uint32 theme_id, [min_status = 0, max_status = 0])"); + { + uint8 update_type = CZLDoNUpdateSubtype_RemoveWin; + uint32 theme_id = (uint32)SvUV(ST(0)); + int points = 1; + uint8 min_status = AccountStatus::Player; + uint8 max_status = AccountStatus::Player; + if (items == 2) + min_status = (uint8)SvUV(ST(1)); + + if (items == 3) + max_status = (uint8)SvUV(ST(2)); + + quest_manager.WorldWideLDoNUpdate(update_type, theme_id, points, min_status, max_status); + } + XSRETURN_EMPTY; +} + +XS(XS__worldwideremovespell); +XS(XS__worldwideremovespell) { + dXSARGS; + if (items < 1 || items > 3) + Perl_croak(aTHX_ "Usage: quest::worldwideremovespell(uint32 spell_id, [uint8 min_status = 0, uint max_status = 0])"); + { + uint8 update_type = WWSpellUpdateType_Remove; + uint32 spell_id = (uint32) SvUV(ST(0)); + uint8 min_status = AccountStatus::Player; + uint8 max_status = AccountStatus::Player; + if (items == 2) + min_status = (uint8) SvUV(ST(1)); + + if (items == 3) + max_status = (uint8) SvUV(ST(2)); + + quest_manager.WorldWideSpell(update_type, spell_id, min_status, max_status); + } + XSRETURN_EMPTY; +} + +XS(XS__worldwideremovetask); +XS(XS__worldwideremovetask) { + dXSARGS; + if (items < 1 || items > 3) + Perl_croak(aTHX_ "Usage: quest::worldwideremovetask(uint32 task_identifier, [uint8 min_status = 0, uint max_status = 0])"); + { + uint8 update_type = WWTaskUpdateType_RemoveTask; + uint32 task_identifier = (uint32) SvUV(ST(0)); + uint8 min_status = AccountStatus::Player; + uint8 max_status = AccountStatus::Player; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + if (items == 2) + min_status = (uint8) SvUV(ST(1)); + + if (items == 3) + max_status = (uint8) SvUV(ST(2)); + + quest_manager.WorldWideTaskUpdate(update_type, task_identifier, task_subidentifier, update_count, enforce_level_requirement, min_status, max_status); + } + + XSRETURN_EMPTY; +} + +XS(XS__worldwideresetactivity); +XS(XS__worldwideresetactivity) { + dXSARGS; + if (items < 2 || items > 4) + Perl_croak(aTHX_ "Usage: quest::worldwideresetactivity(uint32 task_identifier, int activity_id, [uint8 min_status = 0, uint max_status = 0])"); + { + uint8 update_type = WWTaskUpdateType_ActivityReset; + uint32 task_identifier = (uint32) SvUV(ST(0)); + int task_subidentifier = (int) SvIV(ST(1)); + uint8 min_status = AccountStatus::Player; + uint8 max_status = AccountStatus::Player; + int update_count = 1; + bool enforce_level_requirement = false; + if (items == 3) + min_status = (uint8) SvUV(ST(2)); + + if (items == 4) + max_status = (uint8) SvUV(ST(3)); + + quest_manager.WorldWideTaskUpdate(update_type, task_identifier, task_subidentifier, update_count, enforce_level_requirement, min_status, max_status); + } + XSRETURN_EMPTY; +} + +XS(XS__worldwidesetentityvariableclient); +XS(XS__worldwidesetentityvariableclient) { + dXSARGS; + if (items < 2 || items > 4) + Perl_croak(aTHX_ "Usage: quest::worldwidesetentityvariableclient(const char* variable_name, const char* variable_value, [uint8 min_status = 0, uint max_status = 0])"); + { + uint8 update_type = WWSetEntityVariableUpdateType_Character; + const char* variable_name = (const char*) SvPV_nolen(ST(0)); + const char* variable_value = (const char*) SvPV_nolen(ST(1)); + uint8 min_status = AccountStatus::Player; + uint8 max_status = AccountStatus::Player; + if (items == 3) + min_status = (uint8) SvUV(ST(2)); + + if (items == 4) + max_status = (uint8) SvUV(ST(3)); + + quest_manager.WorldWideSetEntityVariable(update_type, variable_name, variable_value, min_status, max_status); + } + XSRETURN_EMPTY; +} + +XS(XS__worldwidesetentityvariablenpc); +XS(XS__worldwidesetentityvariablenpc) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::worldwidesetentityvariablenpc(const char* variable_name, const char* variable_value)"); + { + uint8 update_type = WWSetEntityVariableUpdateType_NPC; + const char* variable_name = (const char*) SvPV_nolen(ST(0)); + const char* variable_value = (const char*) SvPV_nolen(ST(1)); + quest_manager.WorldWideSetEntityVariable(update_type, variable_name, variable_value); + } + XSRETURN_EMPTY; +} + +XS(XS__worldwidesignalnpc); +XS(XS__worldwidesignalnpc) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::worldwidesignalnpc(uint32 signal)"); + { + uint8 update_type = WWSignalUpdateType_NPC; + uint32 signal = (uint32) SvUV(ST(0)); + quest_manager.WorldWideSignal(update_type, signal); + } + XSRETURN_EMPTY; +} + +XS(XS__worldwidesignalclient); +XS(XS__worldwidesignalclient) { + dXSARGS; + if (items < 1 || items > 3) + Perl_croak(aTHX_ "Usage: quest::worldwidesignalclient(uint32 signal, [uint8 min_status = 0, uint max_status = 0])"); + { + uint8 update_type = WWSignalUpdateType_Character; + uint32 signal = (uint32) SvUV(ST(0)); + uint8 min_status = AccountStatus::Player; + uint8 max_status = AccountStatus::Player; + if (items == 2) + min_status = (uint8) SvUV(ST(1)); + + if (items == 3) + max_status = (uint8) SvUV(ST(2)); + + quest_manager.WorldWideSignal(update_type, signal, min_status, max_status); + } + XSRETURN_EMPTY; +} + +XS(XS__worldwideupdateactivity); +XS(XS__worldwideupdateactivity) { + dXSARGS; + if (items < 2 || items > 5) + Perl_croak(aTHX_ "Usage: quest::worldwideupdateactivity(uint32 task_identifier, int activity_id, [int update_count = 1, uint8 min_status = 0, uint max_status = 0])"); + { + uint8 update_type = WWTaskUpdateType_ActivityUpdate; + uint32 task_identifier = (uint32) SvUV(ST(0)); + int task_subidentifier = (int) SvIV(ST(1)); + uint8 min_status = AccountStatus::Player; + uint8 max_status = AccountStatus::Player; + int update_count = 1; + bool enforce_level_requirement = false; + if (items == 3) + update_count = (int) SvIV(ST(2)); + + if (items == 4) + min_status = (uint8) SvUV(ST(3)); + + if (items == 5) + max_status = (uint8) SvUV(ST(4)); + + quest_manager.WorldWideTaskUpdate(update_type, task_identifier, task_subidentifier, update_count, enforce_level_requirement, min_status, max_status); + } + XSRETURN_EMPTY; +} + +XS(XS__isnpcspawned); +XS(XS__isnpcspawned) { + dXSARGS; + if (items < 1) + Perl_croak(aTHX_ "Usage: quest::isnpcspawned(npc_id_one, npc_id_two, npc_idthree, npc_id_four, npc_id_five...[no limit])"); + { + std::vector npc_ids; + bool is_spawned = false; + for (int index = 0; index < items; index++) { + npc_ids.push_back((uint32)SvUV(ST(index))); + } + is_spawned = entity_list.IsNPCSpawned(npc_ids); + ST(0) = boolSV(is_spawned); + sv_2mortal(ST(0)); + XSRETURN(1); + } +} + +XS(XS__countspawnednpcs); +XS(XS__countspawnednpcs) { + dXSARGS; + if (items < 1) + Perl_croak(aTHX_ "Usage: quest::countspawnednpcs(npc_id_one, npc_id_two, npc_idthree, npc_id_four, npc_id_five...[no limit])"); + { + dXSTARG; + std::vector npc_ids; + uint32 npc_count = 0; + for (int index = 0; index < items; index++) { + npc_ids.push_back((uint32)SvUV(ST(index))); + } + npc_count = entity_list.CountSpawnedNPCs(npc_ids); + XSprePUSH; + PUSHu((UV)npc_count); + XSRETURN(1); + } +} + +XS(XS__getspell); +XS(XS__getspell) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::getspell(uint32 spell_id)"); + { + dXSTARG; + uint32 spell_id = (uint32) SvUV(ST(0)); + const SPDat_Spell_Struct* spell = quest_manager.getspell(spell_id); + ST(0) = sv_newmortal(); + sv_setref_pv(ST(0), "Spell", (void *) spell); + XSRETURN(1); + } +} + +XS(XS__getldonthemename); +XS(XS__getldonthemename) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::getldonthemename(uint32 theme_id)"); + { + dXSTARG; + uint32 theme_id = (uint32) SvUV(ST(0)); + std::string theme_name = quest_manager.getldonthemename(theme_id); + + sv_setpv(TARG, theme_name.c_str()); + XSprePUSH; + PUSHTARG; + XSRETURN(1); + } +} + +XS(XS__getfactionname); +XS(XS__getfactionname) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::getfactionname(int faction_id)"); + { + dXSTARG; + int faction_id = (int) SvIV(ST(0)); + std::string faction_name = quest_manager.getfactionname(faction_id); + + sv_setpv(TARG, faction_name.c_str()); + XSprePUSH; + PUSHTARG; + XSRETURN(1); + } +} + +XS(XS__getlanguagename); +XS(XS__getlanguagename) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::getlanguagename(int language_id)"); + { + dXSTARG; + int language_id = (int) SvIV(ST(0)); + std::string language_name = quest_manager.getlanguagename(language_id); + + sv_setpv(TARG, language_name.c_str()); + XSprePUSH; + PUSHTARG; + XSRETURN(1); + } +} + +XS(XS__getbodytypename); +XS(XS__getbodytypename) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::getbodytypename(uint32 bodytype_id)"); + { + dXSTARG; + uint32 bodytype_id = (uint32) SvUV(ST(0)); + std::string bodytype_name = quest_manager.getbodytypename(bodytype_id); + + sv_setpv(TARG, bodytype_name.c_str()); + XSprePUSH; + PUSHTARG; + XSRETURN(1); + } +} + +XS(XS__getconsiderlevelname); +XS(XS__getconsiderlevelname) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::getconsiderlevelname(uint8 consider_level)"); + { + dXSTARG; + uint8 consider_level = (uint8) SvUV(ST(0)); + std::string consider_level_name = quest_manager.getconsiderlevelname(consider_level); + + sv_setpv(TARG, consider_level_name.c_str()); + XSprePUSH; + PUSHTARG; + XSRETURN(1); + } +} + +XS(XS__getenvironmentaldamagename); +XS(XS__getenvironmentaldamagename) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::getenvironmentaldamagename(uint8 damage_type)"); + + dXSTARG; + uint8 damage_type = (uint8) SvIV(ST(0)); + std::string environmental_damage_name = quest_manager.getenvironmentaldamagename(damage_type); + + sv_setpv(TARG, environmental_damage_name.c_str()); + XSprePUSH; + PUSHTARG; + XSRETURN(1); } /* @@ -6490,10 +8173,13 @@ EXTERN_C XS(boot_quest) { newXS(strcpy(buf, "GetTimeSeconds"), XS__GetTimeSeconds, file); newXS(strcpy(buf, "GetZoneID"), XS__GetZoneID, file); newXS(strcpy(buf, "GetZoneLongName"), XS__GetZoneLongName, file); + newXS(strcpy(buf, "GetZoneLongNameByID"), XS__GetZoneLongNameByID, file); + newXS(strcpy(buf, "GetZoneShortName"), XS__GetZoneShortName, file); newXS(strcpy(buf, "set_rule"), XS__set_rule, file); newXS(strcpy(buf, "get_rule"), XS__get_rule, file); newXS(strcpy(buf, "get_data"), XS__get_data, file); newXS(strcpy(buf, "get_data_expires"), XS__get_data_expires, file); + newXS(strcpy(buf, "get_data_remaining"), XS__get_data_remaining, file); newXS(strcpy(buf, "set_data"), XS__set_data, file); newXS(strcpy(buf, "delete_data"), XS__delete_data, file); newXS(strcpy(buf, "IsBeneficialSpell"), XS__IsBeneficialSpell, file); @@ -6514,9 +8200,9 @@ EXTERN_C XS(boot_quest) { newXS(strcpy(buf, "activetasksinset"), XS__activetasksinset, file); newXS(strcpy(buf, "add_expedition_lockout_all_clients"), XS__add_expedition_lockout_all_clients, file); newXS(strcpy(buf, "add_expedition_lockout_by_char_id"), XS__add_expedition_lockout_by_char_id, file); - newXS(strcpy(buf, "addldonloss"), XS__addldonpoints, file); + newXS(strcpy(buf, "addldonloss"), XS__addldonloss, file); newXS(strcpy(buf, "addldonpoints"), XS__addldonpoints, file); - newXS(strcpy(buf, "addldonwin"), XS__addldonpoints, file); + newXS(strcpy(buf, "addldonwin"), XS__addldonwin, file); newXS(strcpy(buf, "addloot"), XS__addloot, file); newXS(strcpy(buf, "addskill"), XS__addskill, file); newXS(strcpy(buf, "assigntask"), XS__assigntask, file); @@ -6534,75 +8220,145 @@ EXTERN_C XS(boot_quest) { newXS(strcpy(buf, "collectitems"), XS__collectitems, file); newXS(strcpy(buf, "completedtasksinset"), XS__completedtasksinset, file); newXS(strcpy(buf, "countitem"), XS__countitem, file); + newXS(strcpy(buf, "countspawnednpcs"), XS__countspawnednpcs, file); newXS(strcpy(buf, "createdoor"), XS__CreateDoor, file); newXS(strcpy(buf, "creategroundobject"), XS__CreateGroundObject, file); newXS(strcpy(buf, "creategroundobjectfrommodel"), XS__CreateGroundObjectFromModel, file); newXS(strcpy(buf, "createguild"), XS__createguild, file); newXS(strcpy(buf, "createitem"), XS__createitem, file); + newXS(strcpy(buf, "crosszoneaddldonlossbycharid"), XS__crosszoneaddldonlossbycharid, file); + newXS(strcpy(buf, "crosszoneaddldonlossbygroupid"), XS__crosszoneaddldonlossbygroupid, file); + newXS(strcpy(buf, "crosszoneaddldonlossbyraidid"), XS__crosszoneaddldonlossbyraidid, file); + newXS(strcpy(buf, "crosszoneaddldonlossbyguildid"), XS__crosszoneaddldonlossbyguildid, file); + newXS(strcpy(buf, "crosszoneaddldonlossbyexpeditionid"), XS__crosszoneaddldonlossbyexpeditionid, file); + newXS(strcpy(buf, "crosszoneaddldonlossbyclientname"), XS__crosszoneaddldonlossbyclientname, file); + newXS(strcpy(buf, "crosszoneaddldonpointsbycharid"), XS__crosszoneaddldonpointsbycharid, file); + newXS(strcpy(buf, "crosszoneaddldonpointsbygroupid"), XS__crosszoneaddldonpointsbygroupid, file); + newXS(strcpy(buf, "crosszoneaddldonpointsbyraidid"), XS__crosszoneaddldonpointsbyraidid, file); + newXS(strcpy(buf, "crosszoneaddldonpointsbyguildid"), XS__crosszoneaddldonpointsbyguildid, file); + newXS(strcpy(buf, "crosszoneaddldonpointsbyexpeditionid"), XS__crosszoneaddldonpointsbyexpeditionid, file); + newXS(strcpy(buf, "crosszoneaddldonpointsbyclientname"), XS__crosszoneaddldonpointsbyclientname, file); + newXS(strcpy(buf, "crosszoneaddldonwinbycharid"), XS__crosszoneaddldonwinbycharid, file); + newXS(strcpy(buf, "crosszoneaddldonwinbygroupid"), XS__crosszoneaddldonwinbygroupid, file); + newXS(strcpy(buf, "crosszoneaddldonwinbyraidid"), XS__crosszoneaddldonwinbyraidid, file); + newXS(strcpy(buf, "crosszoneaddldonwinbyguildid"), XS__crosszoneaddldonwinbyguildid, file); + newXS(strcpy(buf, "crosszoneaddldonwinbyexpeditionid"), XS__crosszoneaddldonwinbyexpeditionid, file); + newXS(strcpy(buf, "crosszoneaddldonwinbyclientname"), XS__crosszoneaddldonwinbyclientname, file); newXS(strcpy(buf, "crosszoneassigntaskbycharid"), XS__crosszoneassigntaskbycharid, file); newXS(strcpy(buf, "crosszoneassigntaskbygroupid"), XS__crosszoneassigntaskbygroupid, file); newXS(strcpy(buf, "crosszoneassigntaskbyraidid"), XS__crosszoneassigntaskbyraidid, file); newXS(strcpy(buf, "crosszoneassigntaskbyguildid"), XS__crosszoneassigntaskbyguildid, file); + newXS(strcpy(buf, "crosszoneassigntaskbyexpeditionid"), XS__crosszoneassigntaskbyexpeditionid, file); + newXS(strcpy(buf, "crosszoneassigntaskbyclientname"), XS__crosszoneassigntaskbyclientname, file); newXS(strcpy(buf, "crosszonecastspellbycharid"), XS__crosszonecastspellbycharid, file); newXS(strcpy(buf, "crosszonecastspellbygroupid"), XS__crosszonecastspellbygroupid, file); newXS(strcpy(buf, "crosszonecastspellbyraidid"), XS__crosszonecastspellbyraidid, file); newXS(strcpy(buf, "crosszonecastspellbyguildid"), XS__crosszonecastspellbyguildid, file); + newXS(strcpy(buf, "crosszonecastspellbyexpeditionid"), XS__crosszonecastspellbyexpeditionid, file); + newXS(strcpy(buf, "crosszonecastspellbyclientname"), XS__crosszonecastspellbyclientname, file); + newXS(strcpy(buf, "crosszonedialoguewindowbycharid"), XS__crosszonedialoguewindowbycharid, file); + newXS(strcpy(buf, "crosszonedialoguewindowbygroupid"), XS__crosszonedialoguewindowbygroupid, file); + newXS(strcpy(buf, "crosszonedialoguewindowbyraidid"), XS__crosszonedialoguewindowbyraidid, file); + newXS(strcpy(buf, "crosszonedialoguewindowbyguildid"), XS__crosszonedialoguewindowbyguildid, file); + newXS(strcpy(buf, "crosszonedialoguewindowbyexpeditionid"), XS__crosszonedialoguewindowbyexpeditionid, file); + newXS(strcpy(buf, "crosszonedialoguewindowbyclientname"), XS__crosszonedialoguewindowbyclientname, file); newXS(strcpy(buf, "crosszonedisabletaskbycharid"), XS__crosszonedisabletaskbycharid, file); newXS(strcpy(buf, "crosszonedisabletaskbygroupid"), XS__crosszonedisabletaskbygroupid, file); newXS(strcpy(buf, "crosszonedisabletaskbyraidid"), XS__crosszonedisabletaskbyraidid, file); newXS(strcpy(buf, "crosszonedisabletaskbyguildid"), XS__crosszonedisabletaskbyguildid, file); + newXS(strcpy(buf, "crosszonedisabletaskbyexpeditionid"), XS__crosszonedisabletaskbyexpeditionid, file); + newXS(strcpy(buf, "crosszonedisabletaskbyclientname"), XS__crosszonedisabletaskbyclientname, file); newXS(strcpy(buf, "crosszoneenabletaskbycharid"), XS__crosszoneenabletaskbycharid, file); newXS(strcpy(buf, "crosszoneenabletaskbygroupid"), XS__crosszoneenabletaskbygroupid, file); newXS(strcpy(buf, "crosszoneenabletaskbyraidid"), XS__crosszoneenabletaskbyraidid, file); newXS(strcpy(buf, "crosszoneenabletaskbyguildid"), XS__crosszoneenabletaskbyguildid, file); + newXS(strcpy(buf, "crosszoneenabletaskbyexpeditionid"), XS__crosszoneenabletaskbyexpeditionid, file); + newXS(strcpy(buf, "crosszoneenabletaskbyclientname"), XS__crosszoneenabletaskbyclientname, file); newXS(strcpy(buf, "crosszonefailtaskbycharid"), XS__crosszonefailtaskbycharid, file); newXS(strcpy(buf, "crosszonefailtaskbygroupid"), XS__crosszonefailtaskbygroupid, file); newXS(strcpy(buf, "crosszonefailtaskbyraidid"), XS__crosszonefailtaskbyraidid, file); newXS(strcpy(buf, "crosszonefailtaskbyguildid"), XS__crosszonefailtaskbyguildid, file); + newXS(strcpy(buf, "crosszonefailtaskbyexpeditionid"), XS__crosszonefailtaskbyexpeditionid, file); + newXS(strcpy(buf, "crosszonefailtaskbyclientname"), XS__crosszonefailtaskbyclientname, file); newXS(strcpy(buf, "crosszonemarqueebycharid"), XS__crosszonemarqueebycharid, file); newXS(strcpy(buf, "crosszonemarqueebygroupid"), XS__crosszonemarqueebygroupid, file); newXS(strcpy(buf, "crosszonemarqueebyraidid"), XS__crosszonemarqueebyraidid, file); newXS(strcpy(buf, "crosszonemarqueebyguildid"), XS__crosszonemarqueebyguildid, file); - newXS(strcpy(buf, "crosszonemessageplayerbyname"), XS__crosszonemessageplayerbyname, file); + newXS(strcpy(buf, "crosszonemarqueebyexpeditionid"), XS__crosszonemarqueebyexpeditionid, file); + newXS(strcpy(buf, "crosszonemarqueebyclientname"), XS__crosszonemarqueebyclientname, file); + newXS(strcpy(buf, "crosszonemessageplayerbycharid"), XS__crosszonemessageplayerbycharid, file); newXS(strcpy(buf, "crosszonemessageplayerbygroupid"), XS__crosszonemessageplayerbygroupid, file); newXS(strcpy(buf, "crosszonemessageplayerbyraidid"), XS__crosszonemessageplayerbyraidid, file); newXS(strcpy(buf, "crosszonemessageplayerbyguildid"), XS__crosszonemessageplayerbyguildid, file); + newXS(strcpy(buf, "crosszonemessageplayerbyexpeditionid"), XS__crosszonemessageplayerbyexpeditionid, file); + newXS(strcpy(buf, "crosszonemessageplayerbyname"), XS__crosszonemessageplayerbyname, file); newXS(strcpy(buf, "crosszonemoveplayerbycharid"), XS__crosszonemoveplayerbycharid, file); newXS(strcpy(buf, "crosszonemoveplayerbygroupid"), XS__crosszonemoveplayerbygroupid, file); newXS(strcpy(buf, "crosszonemoveplayerbyraidid"), XS__crosszonemoveplayerbyraidid, file); newXS(strcpy(buf, "crosszonemoveplayerbyguildid"), XS__crosszonemoveplayerbyguildid, file); + newXS(strcpy(buf, "crosszonemoveplayerbyexpeditionid"), XS__crosszonemoveplayerbyexpeditionid, file); + newXS(strcpy(buf, "crosszonemoveplayerbyname"), XS__crosszonemoveplayerbyname, file); newXS(strcpy(buf, "crosszonemoveinstancebycharid"), XS__crosszonemoveinstancebycharid, file); newXS(strcpy(buf, "crosszonemoveinstancebygroupid"), XS__crosszonemoveinstancebygroupid, file); newXS(strcpy(buf, "crosszonemoveinstancebyraidid"), XS__crosszonemoveinstancebyraidid, file); newXS(strcpy(buf, "crosszonemoveinstancebyguildid"), XS__crosszonemoveinstancebyguildid, file); + newXS(strcpy(buf, "crosszonemoveinstancebyexpeditionid"), XS__crosszonemoveinstancebyexpeditionid, file); + newXS(strcpy(buf, "crosszonemoveinstancebyclientname"), XS__crosszonemoveinstancebyclientname, file); + newXS(strcpy(buf, "crosszoneremoveldonlossbycharid"), XS__crosszoneremoveldonlossbycharid, file); + newXS(strcpy(buf, "crosszoneremoveldonlossbygroupid"), XS__crosszoneremoveldonlossbygroupid, file); + newXS(strcpy(buf, "crosszoneremoveldonlossbyraidid"), XS__crosszoneremoveldonlossbyraidid, file); + newXS(strcpy(buf, "crosszoneremoveldonlossbyguildid"), XS__crosszoneremoveldonlossbyguildid, file); + newXS(strcpy(buf, "crosszoneremoveldonlossbyexpeditionid"), XS__crosszoneremoveldonlossbyexpeditionid, file); + newXS(strcpy(buf, "crosszoneremoveldonlossbyclientname"), XS__crosszoneremoveldonlossbyclientname, file); + newXS(strcpy(buf, "crosszoneremoveldonwinbycharid"), XS__crosszoneremoveldonwinbycharid, file); + newXS(strcpy(buf, "crosszoneremoveldonwinbygroupid"), XS__crosszoneremoveldonwinbygroupid, file); + newXS(strcpy(buf, "crosszoneremoveldonwinbyraidid"), XS__crosszoneremoveldonwinbyraidid, file); + newXS(strcpy(buf, "crosszoneremoveldonwinbyguildid"), XS__crosszoneremoveldonwinbyguildid, file); + newXS(strcpy(buf, "crosszoneremoveldonwinbyexpeditionid"), XS__crosszoneremoveldonwinbyexpeditionid, file); + newXS(strcpy(buf, "crosszoneremoveldonwinbyclientname"), XS__crosszoneremoveldonwinbyclientname, file); newXS(strcpy(buf, "crosszoneremovespellbycharid"), XS__crosszoneremovespellbycharid, file); newXS(strcpy(buf, "crosszoneremovespellbygroupid"), XS__crosszoneremovespellbygroupid, file); newXS(strcpy(buf, "crosszoneremovespellbyraidid"), XS__crosszoneremovespellbyraidid, file); newXS(strcpy(buf, "crosszoneremovespellbyguildid"), XS__crosszoneremovespellbyguildid, file); + newXS(strcpy(buf, "crosszoneremovespellbyexpeditionid"), XS__crosszoneremovespellbyexpeditionid, file); + newXS(strcpy(buf, "crosszoneremovespellbyclientname"), XS__crosszoneremovespellbyclientname, file); newXS(strcpy(buf, "crosszoneremovetaskbycharid"), XS__crosszoneremovetaskbycharid, file); newXS(strcpy(buf, "crosszoneremovetaskbygroupid"), XS__crosszoneremovetaskbygroupid, file); newXS(strcpy(buf, "crosszoneremovetaskbyraidid"), XS__crosszoneremovetaskbyraidid, file); newXS(strcpy(buf, "crosszoneremovetaskbyguildid"), XS__crosszoneremovetaskbyguildid, file); + newXS(strcpy(buf, "crosszoneremovetaskbyexpeditionid"), XS__crosszoneremovetaskbyexpeditionid, file); + newXS(strcpy(buf, "crosszoneremovetaskbyclientname"), XS__crosszoneremovetaskbyclientname, file); newXS(strcpy(buf, "crosszoneresetactivitybycharid"), XS__crosszoneresetactivitybycharid, file); newXS(strcpy(buf, "crosszoneresetactivitybygroupid"), XS__crosszoneresetactivitybygroupid, file); newXS(strcpy(buf, "crosszoneresetactivitybyraidid"), XS__crosszoneresetactivitybyraidid, file); newXS(strcpy(buf, "crosszoneresetactivitybyguildid"), XS__crosszoneresetactivitybyguildid, file); - newXS(strcpy(buf, "crosszonesetentityvariablebynpctypeid"), XS__crosszonesetentityvariablebynpctypeid, file); - newXS(strcpy(buf, "crosszonesetentityvariablebyclientname"), XS__crosszonesetentityvariablebyclientname, file); + newXS(strcpy(buf, "crosszoneresetactivitybyexpeditionid"), XS__crosszoneresetactivitybyexpeditionid, file); + newXS(strcpy(buf, "crosszoneresetactivitybyclientname"), XS__crosszoneresetactivitybyclientname, file); + newXS(strcpy(buf, "crosszonesetentityvariablebycharid"), XS__crosszonesetentityvariablebycharid, file); newXS(strcpy(buf, "crosszonesetentityvariablebygroupid"), XS__crosszonesetentityvariablebygroupid, file); newXS(strcpy(buf, "crosszonesetentityvariablebyraidid"), XS__crosszonesetentityvariablebyraidid, file); newXS(strcpy(buf, "crosszonesetentityvariablebyguildid"), XS__crosszonesetentityvariablebyguildid, file); + newXS(strcpy(buf, "crosszonesetentityvariablebyexpeditionid"), XS__crosszonesetentityvariablebyexpeditionid, file); + newXS(strcpy(buf, "crosszonesetentityvariablebyclientname"), XS__crosszonesetentityvariablebyclientname, file); + newXS(strcpy(buf, "crosszonesetentityvariablebynpctypeid"), XS__crosszonesetentityvariablebynpctypeid, file); newXS(strcpy(buf, "crosszonesignalclientbycharid"), XS__crosszonesignalclientbycharid, file); newXS(strcpy(buf, "crosszonesignalclientbygroupid"), XS__crosszonesignalclientbygroupid, file); newXS(strcpy(buf, "crosszonesignalclientbyraidid"), XS__crosszonesignalclientbyraidid, file); newXS(strcpy(buf, "crosszonesignalclientbyguildid"), XS__crosszonesignalclientbyguildid, file); + newXS(strcpy(buf, "crosszonesignalclientbyexpeditionid"), XS__crosszonesignalclientbyexpeditionid, file); newXS(strcpy(buf, "crosszonesignalclientbyname"), XS__crosszonesignalclientbyname, file); newXS(strcpy(buf, "crosszonesignalnpcbynpctypeid"), XS__crosszonesignalnpcbynpctypeid, file); newXS(strcpy(buf, "crosszoneupdateactivitybycharid"), XS__crosszoneupdateactivitybycharid, file); newXS(strcpy(buf, "crosszoneupdateactivitybygroupid"), XS__crosszoneupdateactivitybygroupid, file); newXS(strcpy(buf, "crosszoneupdateactivitybyraidid"), XS__crosszoneupdateactivitybyraidid, file); newXS(strcpy(buf, "crosszoneupdateactivitybyguildid"), XS__crosszoneupdateactivitybyguildid, file); + newXS(strcpy(buf, "crosszoneupdateactivitybyexpeditionid"), XS__crosszoneupdateactivitybyexpeditionid, file); + newXS(strcpy(buf, "crosszoneupdateactivitybyclientname"), XS__crosszoneupdateactivitybyclientname, file); + newXS(strcpy(buf, "worldwideaddldonloss"), XS__worldwideaddldonloss, file); + newXS(strcpy(buf, "worldwideaddldonpoints"), XS__worldwideaddldonpoints, file); + newXS(strcpy(buf, "worldwideaddldonwin"), XS__worldwideaddldonwin, file); newXS(strcpy(buf, "worldwidecastspell"), XS__worldwidecastspell, file); + newXS(strcpy(buf, "worldwidedialoguewindow"), XS__worldwidedialoguewindow, file); newXS(strcpy(buf, "worldwidedisabletask"), XS__worldwidedisabletask, file); newXS(strcpy(buf, "worldwideenabletask"), XS__worldwideenabletask, file); newXS(strcpy(buf, "worldwidefailtask"), XS__worldwidefailtask, file); @@ -6610,6 +8366,8 @@ EXTERN_C XS(boot_quest) { newXS(strcpy(buf, "worldwidemessage"), XS__worldwidemessage, file); newXS(strcpy(buf, "worldwidemove"), XS__worldwidemove, file); newXS(strcpy(buf, "worldwidemoveinstance"), XS__worldwidemoveinstance, file); + newXS(strcpy(buf, "worldwideremoveldonloss"), XS__worldwideremoveldonloss, file); + newXS(strcpy(buf, "worldwideremoveldonwin"), XS__worldwideremoveldonwin, file); newXS(strcpy(buf, "worldwideremovespell"), XS__worldwideremovespell, file); newXS(strcpy(buf, "worldwideremovetask"), XS__worldwideremovetask, file); newXS(strcpy(buf, "worldwideresetactivity"), XS__worldwideresetactivity, file); @@ -6646,29 +8404,45 @@ 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, "getaaexpmodifierbycharid"), XS__getaaexpmodifierbycharid, file); + newXS(strcpy(buf, "getbodytypename"), XS__getbodytypename, file); newXS(strcpy(buf, "getcharidbyname"), XS__getcharidbyname, file); newXS(strcpy(buf, "getclassname"), XS__getclassname, file); + newXS(strcpy(buf, "getcleannpcnamebyid"), XS__getcleannpcnamebyid, file); + newXS(strcpy(buf, "getconsiderlevelname"), XS__getconsiderlevelname, file); + newXS(strcpy(buf, "gethexcolorcode"), XS__gethexcolorcode, file); newXS(strcpy(buf, "getcurrencyid"), XS__getcurrencyid, file); + newXS(strcpy(buf, "getexpmodifierbycharid"), XS__getexpmodifierbycharid, file); newXS(strcpy(buf, "get_expedition"), XS__get_expedition, file); newXS(strcpy(buf, "get_expedition_by_char_id"), XS__get_expedition_by_char_id, file); newXS(strcpy(buf, "get_expedition_by_dz_id"), XS__get_expedition_by_dz_id, file); newXS(strcpy(buf, "get_expedition_by_zone_instance"), XS__get_expedition_by_zone_instance, file); newXS(strcpy(buf, "get_expedition_lockout_by_char_id"), XS__get_expedition_lockout_by_char_id, file); newXS(strcpy(buf, "get_expedition_lockouts_by_char_id"), XS__get_expedition_lockouts_by_char_id, file); + newXS(strcpy(buf, "getfactionname"), XS__getfactionname, 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, "getitemstat"), XS__getitemstat, file); + newXS(strcpy(buf, "getlanguagename"), XS__getlanguagename, file); + newXS(strcpy(buf, "getldonthemename"), XS__getldonthemename, 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, "getgendername"), XS__getgendername, file); + newXS(strcpy(buf, "getdeityname"), XS__getdeityname, file); + newXS(strcpy(buf, "getenvironmentaldamagename"), XS__getenvironmentaldamagename, 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, "getinventoryslotname"), XS__getinventoryslotname, file); newXS(strcpy(buf, "getraididbycharid"), XS__getraididbycharid, file); newXS(strcpy(buf, "getracename"), XS__getracename, file); + newXS(strcpy(buf, "getspell"), XS__getspell, file); newXS(strcpy(buf, "getspellname"), XS__getspellname, file); newXS(strcpy(buf, "get_spell_level"), XS__get_spell_level, file); + newXS(strcpy(buf, "getspellstat"), XS__getspellstat, file); newXS(strcpy(buf, "getskillname"), XS__getskillname, file); newXS(strcpy(buf, "getlevel"), XS__getlevel, file); newXS(strcpy(buf, "getplayerburiedcorpsecount"), XS__getplayerburiedcorpsecount, file); @@ -6683,6 +8457,7 @@ EXTERN_C XS(boot_quest) { newXS(strcpy(buf, "incstat"), XS__incstat, file); newXS(strcpy(buf, "isdisctome"), XS__isdisctome, file); newXS(strcpy(buf, "isdooropen"), XS__isdooropen, file); + newXS(strcpy(buf, "isnpcspawned"), XS__isnpcspawned, file); newXS(strcpy(buf, "istaskactive"), XS__istaskactive, file); newXS(strcpy(buf, "istaskactivityactive"), XS__istaskactivityactive, file); newXS(strcpy(buf, "istaskappropriate"), XS__istaskappropriate, file); @@ -6726,6 +8501,7 @@ EXTERN_C XS(boot_quest) { newXS(strcpy(buf, "remove_expedition_lockout_by_char_id"), XS__remove_expedition_lockout_by_char_id, file); newXS(strcpy(buf, "removeitem"), XS__removeitem, file); newXS(strcpy(buf, "removetitle"), XS__removetitle, file); + newXS(strcpy(buf, "rename"), XS__rename, file); newXS(strcpy(buf, "repopzone"), XS__repopzone, file); newXS(strcpy(buf, "resettaskactivity"), XS__resettaskactivity, file); newXS(strcpy(buf, "respawn"), XS__respawn, file); @@ -6737,10 +8513,12 @@ EXTERN_C XS(boot_quest) { newXS(strcpy(buf, "scribespells"), XS__scribespells, file); newXS(strcpy(buf, "secondstotime"), XS__secondstotime, file); newXS(strcpy(buf, "selfcast"), XS__selfcast, file); + newXS(strcpy(buf, "setaaexpmodifierbycharid"), XS__setaaexpmodifierbycharid, file); newXS(strcpy(buf, "set_proximity"), XS__set_proximity, file); newXS(strcpy(buf, "set_zone_flag"), XS__set_zone_flag, file); newXS(strcpy(buf, "setallskill"), XS__setallskill, file); newXS(strcpy(buf, "setanim"), XS__setanim, file); + newXS(strcpy(buf, "setexpmodifierbycharid"), XS__setexpmodifierbycharid, file); newXS(strcpy(buf, "setglobal"), XS__setglobal, file); newXS(strcpy(buf, "setguild"), XS__setguild, file); newXS(strcpy(buf, "sethp"), XS__sethp, file); diff --git a/zone/embperl.cpp b/zone/embperl.cpp index b05a429a1..9be785566 100644 --- a/zone/embperl.cpp +++ b/zone/embperl.cpp @@ -34,11 +34,15 @@ EXTERN_C XS(boot_Group); EXTERN_C XS(boot_Raid); EXTERN_C XS(boot_Inventory); EXTERN_C XS(boot_QuestItem); +EXTERN_C XS(boot_Spell); EXTERN_C XS(boot_HateEntry); EXTERN_C XS(boot_Object); EXTERN_C XS(boot_Doors); EXTERN_C XS(boot_PerlPacket); EXTERN_C XS(boot_Expedition); +#ifdef BOTS +EXTERN_C XS(boot_Bot); +#endif #endif #endif @@ -87,10 +91,16 @@ EXTERN_C void xs_init(pTHX) newXS(strcpy(buf, "Raid::boot_Raid"), boot_Raid, file); newXS(strcpy(buf, "Inventory::boot_Inventory"), boot_Inventory, file); newXS(strcpy(buf, "QuestItem::boot_QuestItem"), boot_QuestItem, file); + newXS(strcpy(buf, "Spell::boot_Spell"), boot_Spell, file); newXS(strcpy(buf, "HateEntry::boot_HateEntry"), boot_HateEntry, file); newXS(strcpy(buf, "Object::boot_Object"), boot_Object, file); newXS(strcpy(buf, "Doors::boot_Doors"), boot_Doors, file); newXS(strcpy(buf, "Expedition::boot_Expedition"), boot_Expedition, file); +#ifdef BOTS + newXS(strcpy(buf, "Bot::boot_Mob"), boot_Mob, file); + newXS(strcpy(buf, "Bot::boot_NPC"), boot_NPC, file); + newXS(strcpy(buf, "Bot::boot_Bot"), boot_Bot, file); +#endif ; #endif #endif diff --git a/zone/entity.cpp b/zone/entity.cpp index 328f2f2ee..e1270a49c 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -43,6 +43,7 @@ #include "water_map.h" #include "npc_scale_manager.h" #include "../common/say_link.h" +#include "dialogue_window.h" #ifdef _WINDOWS #define snprintf _snprintf @@ -517,13 +518,15 @@ void EntityList::MobProcess() mob_settle_timer->Disable(); } + Spawn2* s2 = mob->CastToNPC()->respawn2; + + // Perform normal mob processing if any of these are true: + // -- zone is not empty + // -- a quest has turned it on for this zone while zone is idle + // -- the entity's spawn2 point is marked as path_while_zone_idle + // -- the zone is newly empty and we're allowing mobs to settle if (zone->process_mobs_while_empty || numclients > 0 || - mob->GetWanderType() == 4 || mob->GetWanderType() == 6 || - mob_settle_timer->Enabled()) { - // Normal processing, or assuring that spawns that should - // path and depop do that. Otherwise all of these type mobs - // will be up and at starting positions, or waiting at the zoneline - // if they chased the PCs when idle zone wakes up. + (s2 && s2->PathWhenZoneIdle()) || mob_settle_timer->Enabled()) { mob_dead = !mob->Process(); } else { @@ -686,6 +689,14 @@ void EntityList::AddNPC(NPC *npc, bool SendSpawnPacket, bool dontqueue) { npc->SetID(GetFreeID()); + //If this is not set here we will despawn pets from new AC changes + auto owner_id = npc->GetOwnerID(); + if(owner_id) { + auto owner = entity_list.GetMob(owner_id); + if (owner) { + owner->SetPetID(npc->GetID()); + } + } parse->EventNPC(EVENT_SPAWN, npc, nullptr, "", 0); uint16 emoteid = npc->GetEmoteID(); @@ -720,10 +731,10 @@ void EntityList::AddNPC(NPC *npc, bool SendSpawnPacket, bool dontqueue) /* Zone controller process EVENT_SPAWN_ZONE */ if (RuleB(Zone, UseZoneController)) { - if (entity_list.GetNPCByNPCTypeID(ZONE_CONTROLLER_NPC_ID) && npc->GetNPCTypeID() != ZONE_CONTROLLER_NPC_ID){ - char data_pass[100] = { 0 }; - snprintf(data_pass, 99, "%d %d", npc->GetID(), npc->GetNPCTypeID()); - parse->EventNPC(EVENT_SPAWN_ZONE, entity_list.GetNPCByNPCTypeID(ZONE_CONTROLLER_NPC_ID)->CastToNPC(), nullptr, data_pass, 0); + auto controller = entity_list.GetNPCByNPCTypeID(ZONE_CONTROLLER_NPC_ID); + if (controller && npc->GetNPCTypeID() != ZONE_CONTROLLER_NPC_ID){ + std::string data_pass = fmt::format("{} {}", npc->GetID(), npc->GetNPCTypeID()); + parse->EventNPC(EVENT_SPAWN_ZONE, controller, nullptr, data_pass.c_str(), 0); } } @@ -1198,6 +1209,34 @@ bool EntityList::IsMobSpawnedByNpcTypeID(uint32 get_id) return false; } +bool EntityList::IsNPCSpawned(std::vector npc_ids) +{ + return CountSpawnedNPCs(npc_ids) != 0; +} + +uint32 EntityList::CountSpawnedNPCs(std::vector npc_ids) +{ + uint32 npc_count = 0; + if (npc_list.empty() || npc_ids.empty()) { + return npc_count; + } + + for (auto current_npc : npc_list) { + if ( + std::find( + npc_ids.begin(), + npc_ids.end(), + current_npc.second->GetNPCTypeID() + ) != npc_ids.end() && + current_npc.second->GetID() != 0 + ) { + npc_count++; + } + } + + return npc_count; +} + Object *EntityList::GetObjectByDBID(uint32 id) { if (id == 0 || object_list.empty()) @@ -1475,11 +1514,11 @@ void EntityList::RemoveFromTargets(Mob *mob, bool RemoveFromXTargets) if (!m) continue; - if (RemoveFromXTargets) { - if (m->IsClient() && (mob->CheckAggro(m) || mob->IsOnFeignMemory(m->CastToClient()))) + if (RemoveFromXTargets && mob) { + if (m->IsClient() && (mob->CheckAggro(m) || mob->IsOnFeignMemory(m))) m->CastToClient()->RemoveXTarget(mob, false); // FadingMemories calls this function passing the client. - else if (mob->IsClient() && (m->CheckAggro(mob) || m->IsOnFeignMemory(mob->CastToClient()))) + else if (mob->IsClient() && (m->CheckAggro(mob) || m->IsOnFeignMemory(mob))) mob->CastToClient()->RemoveXTarget(m, false); } @@ -1487,6 +1526,32 @@ void EntityList::RemoveFromTargets(Mob *mob, bool RemoveFromXTargets) } } +void EntityList::RemoveFromTargetsFadingMemories(Mob *spell_target, bool RemoveFromXTargets, uint32 max_level) +{ + for (auto &e : mob_list) { + auto &mob = e.second; + + if (!mob) { + continue; + } + + if (max_level && mob->GetLevel() > max_level) + continue; + + if (mob->GetSpecialAbility(IMMUNE_FADING_MEMORIES)) + continue; + + if (RemoveFromXTargets && spell_target) { + if (mob->IsClient() && (spell_target->CheckAggro(mob) || spell_target->IsOnFeignMemory(mob))) + mob->CastToClient()->RemoveXTarget(spell_target, false); + else if (spell_target->IsClient() && (mob->CheckAggro(spell_target) || mob->IsOnFeignMemory(spell_target))) + spell_target->CastToClient()->RemoveXTarget(mob, false); + } + + mob->RemoveFromHateList(spell_target); + } +} + void EntityList::RemoveFromXTargets(Mob *mob) { auto it = client_list.begin(); @@ -3163,50 +3228,75 @@ char *EntityList::RemoveNumbers(char *name) void EntityList::ListNPCCorpses(Client *client) { - uint32 x = 0; - - auto it = corpse_list.begin(); - client->Message(Chat::White, "NPC Corpses in the zone:"); - while (it != corpse_list.end()) { - if (it->second->IsNPCCorpse()) { - client->Message(Chat::White, " %5d: %s", it->first, it->second->GetName()); - x++; + uint32 corpse_count = 0; + for (const auto& corpse : corpse_list) { + uint32 corpse_number = (corpse_count + 1); + if (corpse.second->IsNPCCorpse()) { + client->Message( + Chat::White, + fmt::format( + "Corpse {} | Name: {} ({})", + corpse_number, + corpse.second->GetName(), + corpse.second->GetID() + ).c_str() + ); + corpse_count++; } - ++it; } - client->Message(Chat::White, "%d npc corpses listed.", x); + + if (corpse_count > 0) { + client->Message( + Chat::White, + fmt::format( + "{} NPC corpses listed.", + corpse_count + ).c_str() + ); + } } void EntityList::ListPlayerCorpses(Client *client) { - uint32 x = 0; - - auto it = corpse_list.begin(); - client->Message(Chat::White, "Player Corpses in the zone:"); - while (it != corpse_list.end()) { - if (it->second->IsPlayerCorpse()) { - client->Message(Chat::White, " %5d: %s", it->first, it->second->GetName()); - x++; + uint32 corpse_count = 0; + for (const auto& corpse : corpse_list) { + uint32 corpse_number = (corpse_count + 1); + if (corpse.second->IsPlayerCorpse()) { + client->Message( + Chat::White, + fmt::format( + "Corpse {} | Name: {} ({})", + corpse_number, + corpse.second->GetName(), + corpse.second->GetID() + ).c_str() + ); + corpse_count++; } - ++it; } - client->Message(Chat::White, "%d player corpses listed.", x); + + if (corpse_count > 0) { + client->Message( + Chat::White, + fmt::format( + "{} Player corpses listed.", + corpse_count + ).c_str() + ); + } } // returns the number of corpses deleted. A negative number indicates an error code. -int32 EntityList::DeleteNPCCorpses() +uint32 EntityList::DeleteNPCCorpses() { - int32 x = 0; - - auto it = corpse_list.begin(); - while (it != corpse_list.end()) { - if (it->second->IsNPCCorpse()) { - it->second->DepopNPCCorpse(); - x++; + uint32 corpse_count = 0; + for (const auto& corpse : corpse_list) { + if (corpse.second->IsNPCCorpse()) { + corpse.second->DepopNPCCorpse(); + corpse_count++; } - ++it; } - return x; + return corpse_count; } void EntityList::CorpseFix(Client* c) @@ -3226,19 +3316,16 @@ void EntityList::CorpseFix(Client* c) } // returns the number of corpses deleted. A negative number indicates an error code. -int32 EntityList::DeletePlayerCorpses() +uint32 EntityList::DeletePlayerCorpses() { - int32 x = 0; - - auto it = corpse_list.begin(); - while (it != corpse_list.end()) { - if (it->second->IsPlayerCorpse()) { - it->second->CastToCorpse()->Delete(); - x++; + uint32 corpse_count = 0; + for (const auto& corpse : corpse_list) { + if (corpse.second->IsPlayerCorpse()) { + corpse.second->Delete(); + corpse_count++; } - ++it; } - return x; + return corpse_count; } void EntityList::SendPetitionToAdmins() @@ -3254,7 +3341,7 @@ void EntityList::SendPetitionToAdmins() pcus->quetotal=0; auto it = client_list.begin(); while (it != client_list.end()) { - if (it->second->CastToClient()->Admin() >= 80) + if (it->second->CastToClient()->Admin() >= AccountStatus::QuestTroupe) it->second->CastToClient()->QueuePacket(outapp); ++it; } @@ -3282,7 +3369,7 @@ void EntityList::SendPetitionToAdmins(Petition *pet) pcus->quetotal = petition_list.GetTotalPetitions(); auto it = client_list.begin(); while (it != client_list.end()) { - if (it->second->CastToClient()->Admin() >= 80) { + if (it->second->CastToClient()->Admin() >= AccountStatus::QuestTroupe) { if (pet->CheckedOut()) strcpy(pcus->gmsenttoo, ""); else @@ -3307,7 +3394,7 @@ void EntityList::ClearClientPetitionQueue() pet->quetotal = petition_list.GetTotalPetitions(); auto it = client_list.begin(); while (it != client_list.end()) { - if (it->second->CastToClient()->Admin() >= 100) { + if (it->second->CastToClient()->Admin() >= AccountStatus::GMAdmin) { int x = 0; for (x = 0; x < 64; x++) { pet->petnumber = x; @@ -3469,7 +3556,7 @@ void EntityList::ClearFeignAggro(Mob *targ) auto it = npc_list.begin(); while (it != npc_list.end()) { // add Feign Memory check because sometimes weird stuff happens - if (it->second->CheckAggro(targ) || (targ->IsClient() && it->second->IsOnFeignMemory(targ->CastToClient()))) { + if (it->second->CheckAggro(targ) || (targ->IsClient() && it->second->IsOnFeignMemory(targ))) { if (it->second->GetSpecialAbility(IMMUNE_FEIGN_DEATH)) { ++it; continue; @@ -3495,22 +3582,31 @@ void EntityList::ClearFeignAggro(Mob *targ) it->second->RemoveFromHateList(targ); if (targ->IsClient()) { - if (it->second->GetLevel() >= 35 && zone->random.Roll(60)) - it->second->AddFeignMemory(targ->CastToClient()); - else + if (it->second->GetLevel() >= 35 && zone->random.Roll(60)) { + it->second->AddFeignMemory(targ); + } + else { targ->CastToClient()->RemoveXTarget(it->second, false); + } + } + else if (targ->IsPet()){ + if (it->second->GetLevel() >= 35 && zone->random.Roll(60)) { + it->second->AddFeignMemory(targ); + } } } ++it; } } -void EntityList::ClearZoneFeignAggro(Client *targ) +void EntityList::ClearZoneFeignAggro(Mob *targ) { auto it = npc_list.begin(); while (it != npc_list.end()) { it->second->RemoveFromFeignMemory(targ); - targ->CastToClient()->RemoveXTarget(it->second, false); + if (targ && targ->IsClient()) { + targ->CastToClient()->RemoveXTarget(it->second, false); + } ++it; } } @@ -3542,12 +3638,8 @@ bool EntityList::MakeTrackPacket(Client *client) uint32 distance = 0; float MobDistance; - if (client->GetClass() == DRUID) - distance = (client->GetSkill(EQ::skills::SkillTracking) * 10); - else if (client->GetClass() == RANGER) - distance = (client->GetSkill(EQ::skills::SkillTracking) * 12); - else if (client->GetClass() == BARD) - distance = (client->GetSkill(EQ::skills::SkillTracking) * 7); + distance = (client->GetSkill(EQ::skills::SkillTracking) * client->GetClassTrackingDistanceMultiplier(client->GetClass())); + if (distance <= 0) return false; if (distance < 300) @@ -3682,7 +3774,7 @@ void EntityList::SendAlarm(Trap *trap, Mob *currenttarget, uint8 kos) if (kos) { uint8 factioncon = currenttarget->GetReverseFactionCon(cur); - if (factioncon == FACTION_THREATENLY || factioncon == FACTION_SCOWLS) { + if (factioncon == FACTION_THREATENINGLY || factioncon == FACTION_SCOWLS) { cur->AddToHateList(currenttarget,1); } } @@ -4184,7 +4276,7 @@ void EntityList::AddTempPetsToHateList(Mob *owner, Mob* other, bool bFrenzy) auto it = npc_list.begin(); while (it != npc_list.end()) { NPC* n = it->second; - if (n->GetSwarmInfo()) { + if (n && n->GetSwarmInfo()) { if (n->GetSwarmInfo()->owner_id == owner->GetID()) { if ( !n->GetSpecialAbility(IMMUNE_AGGRO) && @@ -4199,6 +4291,35 @@ void EntityList::AddTempPetsToHateList(Mob *owner, Mob* other, bool bFrenzy) } } +void EntityList::AddTempPetsToHateListOnOwnerDamage(Mob *owner, Mob* attacker, int32 spell_id) +{ + if (!attacker || !owner) + return; + + auto it = npc_list.begin(); + while (it != npc_list.end()) { + NPC* n = it->second; + if (n && n->GetSwarmInfo()) { + if (n->GetSwarmInfo()->owner_id == owner->GetID()) { + if ( + attacker && + attacker != n && + !n->IsEngaged() && + !n->GetSpecialAbility(IMMUNE_AGGRO) && + !(n->GetSpecialAbility(IMMUNE_AGGRO_CLIENT) && attacker->IsClient()) && + !(n->GetSpecialAbility(IMMUNE_AGGRO_NPC) && attacker->IsNPC()) && + !attacker->IsTrap() && + !attacker->IsCorpse() + ) { + n->AddToHateList(attacker, 1, 0, true, false, false, spell_id); + n->SetTarget(attacker); + } + } + } + ++it; + } +} + bool Entity::CheckCoordLosNoZLeaps(float cur_x, float cur_y, float cur_z, float trg_x, float trg_y, float trg_z, float perwalk) { @@ -4225,8 +4346,10 @@ bool Entity::CheckCoordLosNoZLeaps(float cur_x, float cur_y, float cur_z, return false; } -void EntityList::QuestJournalledSayClose(Mob *sender, float dist, const char *mobname, const char *message, - Journal::Options &opts) +void EntityList::QuestJournalledSayClose( + Mob *sender, float dist, const char *mobname, const char *message, + Journal::Options &opts +) { SerializeBuffer buf(sizeof(SpecialMesgHeader_Struct) + 12 + 64 + 64); @@ -4239,7 +4362,32 @@ void EntityList::QuestJournalledSayClose(Mob *sender, float dist, const char *mo buf.WriteInt32(0); // location, client doesn't seem to do anything with this buf.WriteInt32(0); buf.WriteInt32(0); - buf.WriteString(message); + + if (RuleB(Chat, QuestDialogueUsesDialogueWindow)) { + for (auto &e : GetCloseMobList(sender, (dist * dist))) { + Mob *mob = e.second; + + if (!mob->IsClient()) { + continue; + } + + Client *client = mob->CastToClient(); + + if (client->GetTarget() && client->GetTarget()->IsMob() && client->GetTarget()->CastToMob() == sender) { + std::string window_markdown = message; + DialogueWindow::Render(client, window_markdown); + } + } + + return; + } + else if (RuleB(Chat, AutoInjectSaylinksToSay)) { + std::string new_message = EQ::SayLinkEngine::InjectSaylinksIfNotExist(message); + buf.WriteString(new_message); + } + else { + buf.WriteString(message); + } auto outapp = new EQApplicationPacket(OP_SpecialMesg, buf); @@ -4278,6 +4426,56 @@ Corpse *EntityList::GetClosestCorpse(Mob *sender, const char *Name) return ClosestCorpse; } +void EntityList::TryWakeTheDead(Mob *sender, Mob *target, int32 spell_id, uint32 max_distance, uint32 duration, uint32 amount_pets) +{ + if (!sender) { + return; + } + + std::vector used_corpse_list; + + for (int i = 0; i < amount_pets; i++) + { + uint32 CurrentDistance, ClosestDistance = 4294967295u; + Corpse *CurrentCorpse, *ClosestCorpse = nullptr; + + auto it = corpse_list.begin(); + while (it != corpse_list.end()) { + CurrentCorpse = it->second; + + ++it; + + bool corpse_already_used = false; + for (auto itr = used_corpse_list.begin(); itr != used_corpse_list.end(); ++itr) { + if ((*itr) && (*itr) == CurrentCorpse->GetID()) { + corpse_already_used = true; + continue; + } + } + + if (corpse_already_used) { + continue; + } + + CurrentDistance = static_cast(sender->CalculateDistance(CurrentCorpse->GetX(), CurrentCorpse->GetY(), CurrentCorpse->GetZ())); + + if (max_distance && CurrentDistance > max_distance) { + continue; + } + + if (CurrentDistance < ClosestDistance) { + ClosestDistance = CurrentDistance; + ClosestCorpse = CurrentCorpse; + } + } + + if (ClosestCorpse) { + sender->WakeTheDead(spell_id, ClosestCorpse, target, duration); + used_corpse_list.push_back(ClosestCorpse->GetID()); + } + } +} + void EntityList::ForceGroupUpdate(uint32 gid) { auto it = client_list.begin(); @@ -4536,6 +4734,45 @@ void EntityList::SendUntargetable(Client *c) } } +void EntityList::SendAppearanceEffects(Client *c) +{ + if (!c) + return; + + auto it = mob_list.begin(); + while (it != mob_list.end()) { + Mob *cur = it->second; + + if (cur) { + if (cur == c) { + ++it; + continue; + } + cur->SendSavedAppearenceEffects(c); + } + ++it; + } +} + +void EntityList::SendIllusionWearChange(Client *c) +{ + if (!c) { + return; + } + + for (auto &e : mob_list) { + auto &mob = e.second; + + if (mob) { + if (mob == c) { + continue; + } + + mob->SendIllusionWearChange(c); + } + } +} + void EntityList::ZoneWho(Client *c, Who_All_Struct *Who) { // This is only called for SoF clients, as regular /who is now handled server-side for that client. @@ -4670,7 +4907,7 @@ void EntityList::ZoneWho(Client *c, Who_All_Struct *Who) WAPP2->RankMSGID = 12315; else if (ClientEntry->IsBuyer()) WAPP2->RankMSGID = 6056; - else if (ClientEntry->Admin() >= 10 && ClientEntry->GetGM()) + else if (ClientEntry->Admin() >= AccountStatus::Steward && ClientEntry->GetGM()) WAPP2->RankMSGID = 12312; else WAPP2->RankMSGID = 0xFFFFFFFF; @@ -4809,6 +5046,16 @@ void EntityList::GetClientList(std::list &c_list) } } +#ifdef BOTS +void EntityList::GetBotList(std::list &b_list) +{ + b_list.clear(); + for (auto bot_iterator : bot_list) { + b_list.push_back(bot_iterator); + } +} +#endif + void EntityList::GetCorpseList(std::list &c_list) { c_list.clear(); @@ -5120,7 +5367,7 @@ void EntityList::ExpeditionWarning(uint32 minutes_left) safe_delete(outapp); } -Mob *EntityList::GetClosestMobByBodyType(Mob *sender, bodyType BodyType) +Mob *EntityList::GetClosestMobByBodyType(Mob *sender, bodyType BodyType, bool skip_client_pets) { if (!sender) @@ -5137,6 +5384,10 @@ Mob *EntityList::GetClosestMobByBodyType(Mob *sender, bodyType BodyType) if (CurrentMob->GetBodyType() != BodyType) continue; + // Do not detect client pets + if (skip_client_pets && CurrentMob->IsPet() && CurrentMob->IsPetOwnerClient()) + continue; + CurrentDistance = ((CurrentMob->GetY() - sender->GetY()) * (CurrentMob->GetY() - sender->GetY())) + ((CurrentMob->GetX() - sender->GetX()) * (CurrentMob->GetX() - sender->GetX())); @@ -5192,59 +5443,103 @@ Client *EntityList::FindCorpseDragger(uint16 CorpseID) return nullptr; } -Mob *EntityList::GetTargetForVirus(Mob *spreader, int range) +std::vector EntityList::GetTargetsForVirusEffect(Mob *spreader, Mob *original_caster, int range, int pcnpc, int32 spell_id) { - int max_spread_range = RuleI(Spells, VirusSpreadDistance); + /* + Live Mechanics + Virus spreader does NOT need LOS + There is no max target limit + */ + if (!spreader) { + return {}; + } - if (range) - max_spread_range = range; + std::vector spreader_list = {}; + bool is_detrimental_spell = IsDetrimentalSpell(spell_id); + for (auto &it : entity_list.GetCloseMobList(spreader, range)) { + Mob *mob = it.second; - std::vector TargetsInRange; + if (!mob) { + continue; + } - auto it = mob_list.begin(); - while (it != mob_list.end()) { - Mob *cur = it->second; - // Make sure the target is in range, has los and is not the mob doing the spreading - if ((cur->GetID() != spreader->GetID()) && - (cur->CalculateDistance(spreader->GetX(), spreader->GetY(), - spreader->GetZ()) <= max_spread_range) && - (spreader->CheckLosFN(cur))) { - // If the spreader is an npc it can only spread to other npc controlled mobs - if (spreader->IsNPC() && !spreader->IsPet() && !spreader->CastToNPC()->GetSwarmOwner() && cur->IsNPC()) { - TargetsInRange.push_back(cur); + if (mob == spreader) { + continue; + } + + // check PC/NPC only flag 1 = PCs, 2 = NPCs + if (pcnpc == 1 && !mob->IsClient() && !mob->IsMerc() && !mob->IsBot()) { + continue; + } + else if (pcnpc == 2 && (mob->IsClient() || mob->IsMerc() || mob->IsBot())) { + continue; + } + if (mob->IsClient() && !mob->CastToClient()->ClientFinishedLoading()) { + continue; + } + + if (mob->IsAura() || mob->IsTrap()) { + continue; + } + + // Make sure the target is in range + if (mob->CalculateDistance(spreader->GetX(), spreader->GetY(), spreader->GetZ()) <= range) { + + if (!original_caster) { + continue; } - // If the spreader is an npc controlled pet it can spread to any other npc or an npc controlled pet - else if (spreader->IsNPC() && spreader->IsPet() && spreader->GetOwner()->IsNPC()) { - if (cur->IsNPC() && !cur->IsPet()) { - TargetsInRange.push_back(cur); - } else if (cur->IsNPC() && cur->IsPet() && cur->GetOwner()->IsNPC()) { - TargetsInRange.push_back(cur); + + //Do not allow detrimental spread to anything the original caster couldn't normally attack. + if (is_detrimental_spell && !original_caster->IsAttackAllowed(mob, true)) { + continue; + } + + //For non-NPCs, do not allow beneficial spread to anything the original caster could normally attack. + if (!is_detrimental_spell && !original_caster->IsNPC() && original_caster->IsAttackAllowed(mob, true)) { + continue; + } + + // If the spreader is an npc and NOT a PET, then spread to other npc controlled mobs that are not pets + if (spreader->IsNPC() && !spreader->IsPet() && !spreader->IsTempPet() && mob->IsNPC() && !mob->IsPet() && !mob->IsTempPet()) { + spreader_list.push_back(mob); + } + // If the spreader is an npc and NOT a PET, then spread to npc controlled pet + else if (spreader->IsNPC() && !spreader->IsPet() && !spreader->IsTempPet() && mob->IsNPC() && (mob->IsPet() || mob->IsTempPet()) && mob->IsPetOwnerNPC()) { + spreader_list.push_back(mob); + } + // If the spreader is an npc controlled PET it can spread to any other npc or an npc controlled pet + else if (spreader->IsNPC() && (spreader->IsPet() || spreader->IsTempPet()) && spreader->IsPetOwnerNPC()) { + if (mob->IsNPC() && (!mob->IsPet() || !mob->IsTempPet())) { + spreader_list.push_back(mob); } - else if (cur->IsNPC() && cur->CastToNPC()->GetSwarmOwner() && cur->GetOwner()->IsNPC()) { - TargetsInRange.push_back(cur); + else if (mob->IsNPC() && (mob->IsPet() || mob->IsTempPet()) && mob->IsPetOwnerNPC()) { + spreader_list.push_back(mob); } } // if the spreader is anything else(bot, pet, etc) then it should spread to everything but non client controlled npcs - else if (!spreader->IsNPC() && !cur->IsNPC()) { - TargetsInRange.push_back(cur); + else if (!spreader->IsNPC() && !mob->IsNPC()) { + spreader_list.push_back(mob); } - // if its a pet we need to determine appropriate targets(pet to client, pet to pet, pet to bot, etc) - else if (spreader->IsNPC() && (spreader->IsPet() || spreader->CastToNPC()->GetSwarmOwner()) && !spreader->GetOwner()->IsNPC()) { - if (!cur->IsNPC()) { - TargetsInRange.push_back(cur); + // if spreader is not an NPC, and Target is an NPC, then spread to non-NPC controlled pets + else if (!spreader->IsNPC() && mob->IsNPC() && (mob->IsPet() || mob->IsTempPet()) && !mob->IsPetOwnerNPC()) { + spreader_list.push_back(mob); + } + + // if spreader is a non-NPC controlled pet we need to determine appropriate targets(pet to client, pet to pet, pet to bot, etc) + else if (spreader->IsNPC() && (spreader->IsPet() || spreader->IsTempPet()) && !spreader->IsPetOwnerNPC()) { + //Spread to non-NPCs + if (!mob->IsNPC()) { + spreader_list.push_back(mob); } - else if (cur->IsNPC() && (cur->IsPet() || cur->CastToNPC()->GetSwarmOwner()) && !cur->GetOwner()->IsNPC()) { - TargetsInRange.push_back(cur); + //Spread to other non-NPC Pets + else if (mob->IsNPC() && (mob->IsPet() || mob->IsTempPet()) && !mob->IsPetOwnerNPC()) { + spreader_list.push_back(mob); } } } - ++it; } - if(TargetsInRange.empty()) - return nullptr; - - return TargetsInRange[zone->random.Int(0, TargetsInRange.size() - 1)]; + return spreader_list; } void EntityList::StopMobAI() @@ -5257,6 +5552,7 @@ void EntityList::StopMobAI() void EntityList::SendAlternateAdvancementStats() { for(auto &c : client_list) { + c.second->SendClearPlayerAA(); c.second->SendAlternateAdvancementTable(); c.second->SendAlternateAdvancementStats(); c.second->SendAlternateAdvancementPoints(); @@ -5344,3 +5640,12 @@ int EntityList::MovePlayerCorpsesToGraveyard(bool force_move_from_instance) return moved_count; } + +void EntityList::DespawnGridNodes(int32 grid_id) { + for (auto mob_iterator : mob_list) { + Mob *mob = mob_iterator.second; + if (mob->IsNPC() && mob->GetRace() == 2254 && mob->EntityVariableExists("grid_id") && atoi(mob->GetEntityVariable("grid_id")) == grid_id) { + mob->Depop(); + } + } +} diff --git a/zone/entity.h b/zone/entity.h index ddc796319..540ec1c19 100644 --- a/zone/entity.h +++ b/zone/entity.h @@ -158,7 +158,8 @@ public: Mob *GetMob(const char* name); Mob *GetMobByNpcTypeID(uint32 get_id); bool IsMobSpawnedByNpcTypeID(uint32 get_id); - Mob *GetTargetForVirus(Mob* spreader, int range); + bool IsNPCSpawned(std::vector npc_ids); + uint32 CountSpawnedNPCs(std::vector npc_ids); inline NPC *GetNPCByID(uint16 id) { auto it = npc_list.find(id); @@ -238,6 +239,7 @@ public: void RemoveAllCorpsesByCharID(uint32 charid); void RemoveCorpseByDBID(uint32 dbid); int RezzAllCorpsesByCharID(uint32 charid); + void DespawnGridNodes(int32 grid_id); bool IsMobInZone(Mob *who); void ClearClientPetitionQueue(); bool CanAddHateForMob(Mob *p); @@ -317,6 +319,7 @@ public: void DestroyTempPets(Mob *owner); int16 CountTempPets(Mob *owner); void AddTempPetsToHateList(Mob *owner, Mob* other, bool bFrenzy = false); + void AddTempPetsToHateListOnOwnerDamage(Mob *owner, Mob* attacker, int32 spell_id); Entity *GetEntityMob(uint16 id); Entity *GetEntityMerc(uint16 id); Entity *GetEntityDoor(uint16 id); @@ -382,18 +385,21 @@ public: void SendZoneAppearance(Client *c); void SendNimbusEffects(Client *c); void SendUntargetable(Client *c); + void SendAppearanceEffects(Client *c); + void SendIllusionWearChange(Client *c); void DuelMessage(Mob* winner, Mob* loser, bool flee); void QuestJournalledSayClose(Mob *sender, float dist, const char* mobname, const char* message, Journal::Options &opts); void GroupMessage(uint32 gid, const char *from, const char *message); void ExpeditionWarning(uint32 minutes_left); void RemoveFromTargets(Mob* mob, bool RemoveFromXTargets = false); + void RemoveFromTargetsFadingMemories(Mob* spell_target, bool RemoveFromXTargets = false, uint32 max_level = 0); void RemoveFromXTargets(Mob* mob); void RemoveFromAutoXTargets(Mob* mob); void ReplaceWithTarget(Mob* pOldMob, Mob*pNewTarget); void QueueCloseClients(Mob* sender, const EQApplicationPacket* app, bool ignore_sender=false, float distance=200, Mob* skipped_mob = 0, bool is_ack_required = true, eqFilterType filter=FilterNone); void QueueClients(Mob* sender, const EQApplicationPacket* app, bool ignore_sender=false, bool ackreq = true); - void QueueClientsStatus(Mob* sender, const EQApplicationPacket* app, bool ignore_sender = false, uint8 minstatus = 0, uint8 maxstatus = 0); + void QueueClientsStatus(Mob* sender, const EQApplicationPacket* app, bool ignore_sender = false, uint8 minstatus = AccountStatus::Player, uint8 maxstatus = AccountStatus::Player); void QueueClientsGuild(Mob* sender, const EQApplicationPacket* app, bool ignore_sender = false, uint32 guildeqid = 0); void QueueClientsGuildBankItemUpdate(const GuildBankItemUpdate_Struct *gbius, uint32 GuildID); void QueueClientsByTarget(Mob* sender, const EQApplicationPacket* app, bool iSendToSender = true, Mob* SkipThisMob = 0, bool ackreq = true, @@ -408,7 +414,8 @@ public: float distance, int Hand = EQ::invslot::slotPrimary, int count = 0, - bool is_from_spell = false + bool is_from_spell = false, + int attack_rounds = 1 ); void AETaunt(Client *caster, float range = 0, int32 bonus_hate = 0); void AESpell( @@ -420,7 +427,6 @@ public: int *max_targets = nullptr ); void MassGroupBuff(Mob *caster, Mob *center, uint16 spell_id, bool affect_caster = true); - void AEBardPulse(Mob *caster, Mob *center, uint16 spell_id, bool affect_caster = true); //trap stuff Mob* GetTrapTrigger(Trap* trap); @@ -442,8 +448,8 @@ public: void ListNPCCorpses(Client* client); void ListPlayerCorpses(Client* client); - int32 DeleteNPCCorpses(); - int32 DeletePlayerCorpses(); + uint32 DeleteNPCCorpses(); + uint32 DeletePlayerCorpses(); void CorpseFix(Client* c); void WriteEntityIDs(); void HalveAggro(Mob* who); @@ -455,7 +461,7 @@ public: void ClearAggro(Mob* targ); void ClearWaterAggro(Mob* targ); void ClearFeignAggro(Mob* targ); - void ClearZoneFeignAggro(Client* targ); + void ClearZoneFeignAggro(Mob* targ); void AggroZone(Mob* who, uint32 hate = 0); bool Fighting(Mob* targ); @@ -478,9 +484,10 @@ public: uint32 CheckNPCsClose(Mob *center); Corpse* GetClosestCorpse(Mob* sender, const char *Name); + void TryWakeTheDead(Mob* sender, Mob* target, int32 spell_id, uint32 max_distance, uint32 duration, uint32 amount_pets); NPC* GetClosestBanker(Mob* sender, uint32 &distance); void CameraEffect(uint32 duration, uint32 intensity); - Mob* GetClosestMobByBodyType(Mob* sender, bodyType BodyType); + Mob* GetClosestMobByBodyType(Mob* sender, bodyType BodyType, bool skip_client_pets=false); void ForceGroupUpdate(uint32 gid); void SendGroupLeave(uint32 gid, const char *name); void SendGroupJoin(uint32 gid, const char *name); @@ -513,11 +520,15 @@ public: void GetDoorsList(std::list &d_list); void GetSpawnList(std::list &d_list); void GetTargetsForConeArea(Mob *start, float min_radius, float radius, float height, int pcnpc, std::list &m_list); + std::vector GetTargetsForVirusEffect(Mob *spreader, Mob *orginal_caster, int range, int pcnpc, int32 spell_id); inline const std::unordered_map &GetMobList() { return mob_list; } inline const std::unordered_map &GetNPCList() { return npc_list; } inline const std::unordered_map &GetMercList() { return merc_list; } inline const std::unordered_map &GetClientList() { return client_list; } +#ifdef BOTS + inline const std::list &GetBotList() { return bot_list; } +#endif inline const std::unordered_map &GetCorpseList() { return corpse_list; } inline const std::unordered_map &GetObjectList() { return object_list; } inline const std::unordered_map &GetDoorsList() { return door_list; } @@ -597,6 +608,8 @@ private: void ShowSpawnWindow(Client* client, int Distance, bool NamedOnly); // TODO: Implement ShowSpawnWindow in the bot class but it needs entity list stuff void ScanCloseClientMobs(std::unordered_map& close_mobs, Mob* scanning_mob); + + void GetBotList(std::list &b_list); private: std::list bot_list; #endif diff --git a/zone/event_codes.h b/zone/event_codes.h index 110101cf9..8aebee1a7 100644 --- a/zone/event_codes.h +++ b/zone/event_codes.h @@ -43,8 +43,8 @@ typedef enum { EVENT_HATE_LIST, EVENT_SPELL_EFFECT_CLIENT, EVENT_SPELL_EFFECT_NPC, - EVENT_SPELL_BUFF_TIC_CLIENT, - EVENT_SPELL_BUFF_TIC_NPC, + EVENT_SPELL_EFFECT_BUFF_TIC_CLIENT, + EVENT_SPELL_EFFECT_BUFF_TIC_NPC, EVENT_SPELL_FADE, EVENT_SPELL_EFFECT_TRANSLOCATE_COMPLETE, EVENT_COMBINE_SUCCESS, //PC successfully combined a recipe @@ -88,6 +88,12 @@ typedef enum { EVENT_USE_SKILL, EVENT_COMBINE_VALIDATE, EVENT_BOT_COMMAND, + EVENT_WARP, + EVENT_TEST_BUFF, + EVENT_COMBINE, + EVENT_CONSIDER, + EVENT_CONSIDER_CORPSE, + EVENT_LOOT_ZONE, _LargestEventID } QuestEventID; diff --git a/zone/exp.cpp b/zone/exp.cpp index b625935e6..045a9bba6 100644 --- a/zone/exp.cpp +++ b/zone/exp.cpp @@ -327,6 +327,10 @@ void Client::CalculateStandardAAExp(uint32 &add_aaxp, uint8 conlevel, bool resex add_aaxp *= RuleR(Character, FinalExpMultiplier); } + if (RuleB(Character, EnableCharacterEXPMods)) { + add_aaxp *= GetAAEXPModifier(this->GetZoneID()); + } + add_aaxp = (uint32)(RuleR(Character, AAExpMultiplier) * add_aaxp * aatotalmod); } @@ -486,6 +490,10 @@ void Client::CalculateExp(uint32 in_add_exp, uint32 &add_exp, uint32 &add_aaxp, add_exp *= RuleR(Character, FinalExpMultiplier); } + if (RuleB(Character, EnableCharacterEXPMods)) { + add_exp *= GetEXPModifier(this->GetZoneID()); + } + add_exp = GetEXP() + add_exp; } @@ -785,7 +793,7 @@ void Client::SetEXP(uint32 set_exp, uint32 set_aaxp, bool isrezzexp) { FastQueuePacket(&outapp); } - if (admin>=100 && GetGM()) { + if (admin >= AccountStatus::GMAdmin && GetGM()) { char val1[20]={0}; char val2[20]={0}; char val3[20]={0}; diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 96ff0d45f..8c1b50dfd 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -27,7 +27,8 @@ #include "zonedb.h" #include "../common/eqemu_logsys.h" #include "../common/expedition_lockout_timer.h" -#include "../common/util/uuid.h" +#include "../common/repositories/dynamic_zone_members_repository.h" +#include "../common/repositories/expedition_lockouts_repository.h" extern WorldServer worldserver; extern Zone* zone; @@ -39,9 +40,6 @@ const char* const EXPEDITION_OTHER_BELONGS = "{} attempted to create an expedi // lockout warnings were added to live in March 11 2020 patch const char* const DZADD_INVITE_WARNING = "Warning! You will be given replay timers for the following events if you enter %s:"; const char* const DZADD_INVITE_WARNING_TIMER = "%s - %sD:%sH:%sM"; -const char* const KICKPLAYERS_EVERYONE = "Everyone"; -// message string 8312 added in September 08 2020 Test patch (used by both dz and shared tasks) -const char* const CREATE_NOT_ALL_ADDED = "Not all players in your {} were added to the {}. The {} can take a maximum of {} players, and your {} has {}."; // various expeditions re-use these strings when locking constexpr char LOCK_CLOSE[] = "Your expedition is nearing its close. You cannot bring any additional people into your expedition at this time."; constexpr char LOCK_BEGIN[] = "The trial has begun. You cannot bring any additional people into your expedition at this time."; @@ -49,36 +47,44 @@ constexpr char LOCK_BEGIN[] = "The trial has begun. You cannot const int32_t Expedition::REPLAY_TIMER_ID = -1; const int32_t Expedition::EVENT_TIMER_ID = 1; -Expedition::Expedition( - uint32_t id, const std::string& uuid, DynamicZone&& dz, const std::string& expedition_name, - const ExpeditionMember& leader, uint32_t min_players, uint32_t max_players -) : +Expedition::Expedition(DynamicZone* dz, uint32_t id, uint32_t dz_id) : + m_dynamic_zone(dz), m_id(id), - m_uuid(uuid), - m_expedition_name(expedition_name), - m_leader(leader), - m_min_players(min_players), - m_max_players(max_players) + m_dynamic_zone_id(dz_id) { - SetDynamicZone(std::move(dz)); + assert(m_dynamic_zone != nullptr); // dz must remain valid for lifetime of expedition } -void Expedition::SetDynamicZone(DynamicZone&& dz) +void Expedition::LoadRepositoryResult(const ExpeditionsRepository::Expeditions& entry) { - dz.SetName(GetName()); - dz.SetLeaderName(GetLeaderName()); - - m_dynamiczone = std::move(dz); + m_id = entry.id; + m_dynamic_zone_id = entry.dynamic_zone_id; + m_add_replay_on_join = entry.add_replay_on_join; + m_is_locked = entry.is_locked; } -Expedition* Expedition::TryCreate( - Client* requester, DynamicZone& dynamiczone, ExpeditionRequest& request) +void Expedition::RegisterDynamicZoneCallbacks() +{ + uint32_t expedition_id = GetID(); + + GetDynamicZone()->RegisterOnClientAddRemove( + [expedition_id](Client* client, bool removed, bool silent) { + auto expedition = Expedition::FindCachedExpeditionByID(expedition_id); + if (expedition) { + expedition->OnClientAddRemove(client, removed, silent); + } + }); +} + +Expedition* Expedition::TryCreate(Client* requester, DynamicZone& dz_request, bool disable_messages) { if (!requester || !zone) { return nullptr; } + ExpeditionRequest request{ dz_request, disable_messages }; + // request parses leader, members list, and lockouts while validating if (!request.Validate(requester)) { @@ -86,8 +92,10 @@ Expedition* Expedition::TryCreate( return nullptr; } - auto dynamic_zone_id = dynamiczone.Create(); - if (dynamic_zone_id == 0) + dz_request.SetLeader({ request.GetLeaderID(), request.GetLeaderName(), DynamicZoneMemberStatus::Online }); + + auto dz = DynamicZone::CreateNew(dz_request, request.GetMembers()); + if (!dz) { // live uses this message when trying to enter an instance that isn't ready // we can use it as the client error message if instance creation fails @@ -96,46 +104,29 @@ Expedition* Expedition::TryCreate( return nullptr; } - std::string expedition_uuid = EQ::Util::UUID::Generate().ToString(); - // unique expedition ids are created from database via auto-increment column - auto expedition_id = ExpeditionDatabase::InsertExpedition( - expedition_uuid, - dynamiczone.GetID(), - request.GetExpeditionName(), - request.GetLeaderID(), - request.GetMinPlayers(), - request.GetMaxPlayers() - ); + auto expedition_id = ExpeditionDatabase::InsertExpedition(dz->GetID()); if (expedition_id) { - auto expedition = std::make_unique( - expedition_id, - expedition_uuid, - std::move(dynamiczone), - request.GetExpeditionName(), - ExpeditionMember{ request.GetLeaderID(), request.GetLeaderName() }, - request.GetMinPlayers(), - request.GetMaxPlayers() - ); + auto expedition = std::make_unique(dz, expedition_id, dz->GetID()); LogExpeditions( - "Created [{}] [{}] instance id: [{}] leader: [{}] minplayers: [{}] maxplayers: [{}]", + "Created [{}] [{}] dz: [{}] instance id: [{}] leader: [{}] minplayers: [{}] maxplayers: [{}]", expedition->GetID(), expedition->GetName(), - expedition->GetInstanceID(), + dz->GetID(), + dz->GetInstanceID(), expedition->GetLeaderName(), - expedition->GetMinPlayers(), - expedition->GetMaxPlayers() + dz->GetMinPlayers(), + dz->GetMaxPlayers() ); - expedition->SaveMembers(request); expedition->SaveLockouts(request); + expedition->RegisterDynamicZoneCallbacks(); auto inserted = zone->expedition_cache.emplace(expedition_id, std::move(expedition)); - inserted.first->second->SendUpdatesToZoneMembers(); inserted.first->second->SendWorldExpeditionUpdate(ServerOP_ExpeditionCreate); // cache in other zones inserted.first->second->SendLeaderMessage(request.GetLeaderClient(), Chat::System, EXPEDITION_AVAILABLE, { request.GetExpeditionName() }); @@ -146,119 +137,78 @@ Expedition* Expedition::TryCreate( Chat::System, request.GetNotAllAddedMessage()); } + dz->DoAsyncZoneMemberUpdates(); + return inserted.first->second.get(); } return nullptr; } -void Expedition::CacheExpeditions(MySQLRequestResult& results) +void Expedition::CacheExpeditions( + std::vector&& expedition_entries) { - if (!results.Success() || !zone) + if (!zone) { return; } + // bulk load expedition internal lockouts before caching std::vector expedition_ids; - std::vector dynamic_zone_ids;; - std::vector> expedition_character_ids; - - using col = LoadExpeditionColumns::eLoadExpeditionColumns; - - uint32_t last_expedition_id = 0; - - for (auto row = results.begin(); row != results.end(); ++row) + for (const auto& entry : expedition_entries) { - auto expedition_id = strtoul(row[col::id], nullptr, 10); - - if (expedition_id != last_expedition_id) - { - expedition_ids.emplace_back(expedition_id); - - uint32_t leader_id = strtoul(row[col::leader_id], nullptr, 10); - uint32_t dynamic_zone_id = strtoul(row[col::dz_id], nullptr, 10); - - dynamic_zone_ids.emplace_back(dynamic_zone_id); - - std::unique_ptr expedition = std::make_unique( - expedition_id, - row[col::uuid], // expedition uuid - DynamicZone{ dynamic_zone_id }, - row[col::expedition_name], // expedition name - ExpeditionMember{ leader_id, row[col::leader_name] }, // expedition leader id, name - strtoul(row[col::min_players], nullptr, 10), // min_players - strtoul(row[col::max_players], nullptr, 10) // max_players - ); - - bool add_replay_on_join = (strtoul(row[col::add_replay_on_join], nullptr, 10) != 0); - bool is_locked = (strtoul(row[col::is_locked], nullptr, 10) != 0); - - expedition->SetReplayLockoutOnMemberJoin(add_replay_on_join); - expedition->SetLocked(is_locked, ExpeditionLockMessage::None); - - zone->expedition_cache.emplace(expedition_id, std::move(expedition)); - } - - last_expedition_id = expedition_id; - - // looping expedition members - auto current_expedition = Expedition::FindCachedExpeditionByID(last_expedition_id); - if (current_expedition) - { - auto member_id = strtoul(row[col::member_id], nullptr, 10); - current_expedition->AddInternalMember( - row[col::member_name], member_id, ExpeditionMemberStatus::Offline); - expedition_character_ids.emplace_back(expedition_id, member_id); - } + expedition_ids.emplace_back(entry.id); } - // ask world for online members from all cached expeditions at once - Expedition::SendWorldGetOnlineMembers(expedition_character_ids); + // todo: lockouts need be moved to dynamic zone + auto expedition_lockouts = ExpeditionLockoutsRepository::GetWithTimestamp(database, expedition_ids); - // bulk load dynamic zone data and expedition lockouts for cached expeditions - auto dynamic_zones = DynamicZone::LoadMultipleDzFromDatabase(dynamic_zone_ids); - auto expedition_lockouts = ExpeditionDatabase::LoadMultipleExpeditionLockouts(expedition_ids); - - for (const auto& expedition_id : expedition_ids) + for (auto& entry : expedition_entries) { - auto expedition = Expedition::FindCachedExpeditionByID(expedition_id); - if (expedition) + // the expedition's dynamic zone and members should already be in cache + auto dynamic_zone = DynamicZone::FindDynamicZoneByID(entry.dynamic_zone_id); + if (!dynamic_zone) { - auto dz_iter = dynamic_zones.find(expedition->GetDynamicZoneID()); - if (dz_iter != dynamic_zones.end()) - { - expedition->SetDynamicZone(std::move(dz_iter->second)); - } - - auto lockout_iter = expedition_lockouts.find(expedition->GetID()); - if (lockout_iter != expedition_lockouts.end()) - { - expedition->m_lockouts = lockout_iter->second; - } - - // send member updates now that all data is loaded for the cached expedition - expedition->SendUpdatesToZoneMembers(); + LogExpeditions("[Warning] Expedition [{}] dz [{}] not found during caching", entry.id, entry.dynamic_zone_id); + continue; } + + auto expedition = std::make_unique(dynamic_zone); + expedition->LoadRepositoryResult(entry); + expedition->RegisterDynamicZoneCallbacks(); + + for (auto& lockout_entry : expedition_lockouts) + { + if (lockout_entry.expedition_id == expedition->GetID()) + { + ExpeditionLockoutTimer lockout{ + std::move(lockout_entry.from_expedition_uuid), + expedition->GetName(), + std::move(lockout_entry.event_name), + static_cast(lockout_entry.expire_time), + static_cast(lockout_entry.duration) + }; + + std::string event_name = lockout.GetEventName(); // copy for key since we're moving it + expedition->m_lockouts.emplace(std::move(event_name), std::move(lockout)); + } + } + + zone->expedition_cache.emplace(entry.id, std::move(expedition)); + dynamic_zone->DoAsyncZoneMemberUpdates(); } } void Expedition::CacheFromDatabase(uint32_t expedition_id) { - if (zone) + if (zone && expedition_id != 0) { BenchTimer benchmark; - auto results = ExpeditionDatabase::LoadExpedition(expedition_id); - if (!results.Success()) - { - LogExpeditions("Failed to load Expedition [{}] for zone cache", expedition_id); - return; - } + auto expedition = ExpeditionsRepository::GetWhere(database, fmt::format("id = {}", expedition_id)); + CacheExpeditions({ std::move(expedition) }); - CacheExpeditions(results); - - auto elapsed = benchmark.elapsed(); - LogExpeditions("Caching new expedition [{}] took [{}s]", expedition_id, elapsed); + LogExpeditions("Caching new expedition [{}] took [{}s]", expedition_id, benchmark.elapsed()); } } @@ -271,20 +221,13 @@ bool Expedition::CacheAllFromDatabase() BenchTimer benchmark; + auto expeditions = ExpeditionsRepository::All(database); zone->expedition_cache.clear(); + zone->expedition_cache.reserve(expeditions.size()); - // load all active expeditions and members to current zone cache - auto results = ExpeditionDatabase::LoadAllExpeditions(); - if (!results.Success()) - { - LogExpeditions("Failed to load Expeditions for zone cache"); - return false; - } + CacheExpeditions(std::move(expeditions)); - CacheExpeditions(results); - - auto elapsed = benchmark.elapsed(); - LogExpeditions("Caching [{}] expedition(s) took [{}s]", zone->expedition_cache.size(), elapsed); + LogExpeditions("Caching [{}] expedition(s) took [{}s]", zone->expedition_cache.size(), benchmark.elapsed()); return true; } @@ -295,27 +238,14 @@ void Expedition::SaveLockouts(ExpeditionRequest& request) ExpeditionDatabase::InsertLockouts(m_id, m_lockouts); } -void Expedition::SaveMembers(ExpeditionRequest& request) -{ - m_members = request.GetMembers(); - - std::vector member_ids; - for (const auto& member : m_members) - { - member_ids.emplace_back(member.char_id); - } - - ExpeditionDatabase::InsertMembers(m_id, m_members); - m_dynamiczone.SaveInstanceMembersToDatabase(member_ids); -} - Expedition* Expedition::FindCachedExpeditionByCharacterID(uint32_t character_id) { if (zone) { for (const auto& expedition : zone->expedition_cache) { - if (expedition.second->HasMember(character_id)) + auto expedition_dz = expedition.second->GetDynamicZone(); + if (expedition_dz && expedition_dz->HasMember(character_id)) { return expedition.second.get(); } @@ -330,7 +260,8 @@ Expedition* Expedition::FindCachedExpeditionByCharacterName(const std::string& c { for (const auto& expedition : zone->expedition_cache) { - if (expedition.second->HasMember(char_name)) + auto expedition_dz = expedition.second->GetDynamicZone(); + if (expedition_dz && expedition_dz->HasMember(char_name)) { return expedition.second.get(); } @@ -345,7 +276,7 @@ Expedition* Expedition::FindCachedExpeditionByDynamicZoneID(uint32_t dz_id) { for (const auto& cached_expedition : zone->expedition_cache) { - if (cached_expedition.second->GetDynamicZone().GetID() == dz_id) + if (cached_expedition.second->GetDynamicZoneID() == dz_id) { return cached_expedition.second.get(); } @@ -373,8 +304,8 @@ Expedition* Expedition::FindCachedExpeditionByZoneInstance(uint32_t zone_id, uin { for (const auto& cached_expedition : zone->expedition_cache) { - if (cached_expedition.second->GetDynamicZone().GetZoneID() == zone_id && - cached_expedition.second->GetDynamicZone().GetInstanceID() == instance_id) + auto expedition_dz = cached_expedition.second->GetDynamicZone(); + if (expedition_dz && expedition_dz->IsSameDz(zone_id, instance_id)) { return cached_expedition.second.get(); } @@ -393,48 +324,6 @@ bool Expedition::HasReplayLockout() return HasLockout(DZ_REPLAY_TIMER_NAME); } -bool Expedition::HasMember(uint32_t character_id) -{ - return std::any_of(m_members.begin(), m_members.end(), [&](const ExpeditionMember& member) { - return member.char_id == character_id; - }); -} - -bool Expedition::HasMember(const std::string& character_name) -{ - return std::any_of(m_members.begin(), m_members.end(), [&](const ExpeditionMember& member) { - return (strcasecmp(member.name.c_str(), character_name.c_str()) == 0); - }); -} - -ExpeditionMember Expedition::GetMemberData(uint32_t character_id) -{ - auto it = std::find_if(m_members.begin(), m_members.end(), [&](const ExpeditionMember& member) { - return member.char_id == character_id; - }); - - ExpeditionMember member_data; - if (it != m_members.end()) - { - member_data = *it; - } - return member_data; -} - -ExpeditionMember Expedition::GetMemberData(const std::string& character_name) -{ - auto it = std::find_if(m_members.begin(), m_members.end(), [&](const ExpeditionMember& member) { - return (strcasecmp(member.name.c_str(), character_name.c_str()) == 0); - }); - - ExpeditionMember member_data; - if (it != m_members.end()) - { - member_data = *it; - } - return member_data; -} - void Expedition::SetReplayLockoutOnMemberJoin(bool add_on_join, bool update_db) { m_add_replay_on_join = add_on_join; @@ -453,7 +342,8 @@ void Expedition::AddReplayLockout(uint32_t seconds) void Expedition::AddLockout(const std::string& event_name, uint32_t seconds) { - auto lockout = ExpeditionLockoutTimer::CreateLockout(m_expedition_name, event_name, seconds, m_uuid); + auto lockout = ExpeditionLockoutTimer::CreateLockout(GetName(), + event_name, seconds, GetDynamicZone()->GetUUID()); AddLockout(lockout); } @@ -463,7 +353,7 @@ void Expedition::AddLockout(const ExpeditionLockoutTimer& lockout, bool members_ { ExpeditionDatabase::InsertLockout(m_id, lockout); } - ExpeditionDatabase::InsertMembersLockout(m_members, lockout); + ExpeditionDatabase::InsertMembersLockout(GetDynamicZone()->GetMembers(), lockout); ProcessLockoutUpdate(lockout, false, members_only); SendWorldLockoutUpdate(lockout, false, members_only); @@ -473,8 +363,8 @@ void Expedition::AddLockoutDuration(const std::string& event_name, int seconds, { // lockout timers use unsigned durations to define intent but we may need // to insert a new lockout while still supporting timer reductions - auto lockout = ExpeditionLockoutTimer::CreateLockout( - m_expedition_name, event_name, std::max(0, seconds), m_uuid); + auto lockout = ExpeditionLockoutTimer::CreateLockout(GetName(), + event_name, std::max(0, seconds), GetDynamicZone()->GetUUID()); if (!members_only) { @@ -493,7 +383,7 @@ void Expedition::AddLockoutDuration(const std::string& event_name, int seconds, // processing lockout duration applies multiplier again in client methods, // update database with modified value now but pass original on int modified_seconds = static_cast(seconds * RuleR(Expedition, LockoutDurationMultiplier)); - ExpeditionDatabase::AddLockoutDuration(m_members, lockout, modified_seconds); + ExpeditionDatabase::AddLockoutDuration(GetDynamicZone()->GetMembers(), lockout, modified_seconds); ProcessLockoutDuration(lockout, seconds, members_only); SendWorldLockoutDuration(lockout, seconds, members_only); @@ -514,163 +404,20 @@ void Expedition::UpdateLockoutDuration( seconds = static_cast(seconds * RuleR(Expedition, LockoutDurationMultiplier)); uint64_t expire_time = it->second.GetStartTime() + seconds; - AddLockout({ m_uuid, m_expedition_name, event_name, expire_time, seconds }, members_only); + AddLockout({ GetDynamicZone()->GetUUID(), GetName(), event_name, expire_time, seconds }, members_only); } } void Expedition::RemoveLockout(const std::string& event_name) { ExpeditionDatabase::DeleteLockout(m_id, event_name); - ExpeditionDatabase::DeleteMembersLockout(m_members, m_expedition_name, event_name); + ExpeditionDatabase::DeleteMembersLockout(GetDynamicZone()->GetMembers(), GetName(), event_name); - ExpeditionLockoutTimer lockout{m_uuid, m_expedition_name, event_name, 0, 0}; + ExpeditionLockoutTimer lockout{GetDynamicZone()->GetUUID(), GetName(), event_name, 0, 0}; ProcessLockoutUpdate(lockout, true); SendWorldLockoutUpdate(lockout, true); } -void Expedition::AddInternalMember( - const std::string& char_name, uint32_t character_id, ExpeditionMemberStatus status) -{ - auto it = std::find_if(m_members.begin(), m_members.end(), - [character_id](const ExpeditionMember& member) { - return member.char_id == character_id; - }); - - if (it == m_members.end()) - { - m_members.emplace_back(character_id, char_name, status); - } -} - -bool Expedition::AddMember(const std::string& add_char_name, uint32_t add_char_id) -{ - if (HasMember(add_char_id)) - { - return false; - } - - ExpeditionDatabase::InsertMember(m_id, add_char_id); - m_dynamiczone.AddCharacter(add_char_id); - - ProcessMemberAdded(add_char_name, add_char_id); - SendWorldMemberChanged(add_char_name, add_char_id, false); - - return true; -} - -void Expedition::RemoveAllMembers(bool enable_removal_timers) -{ - m_dynamiczone.RemoveAllCharacters(enable_removal_timers); - - ExpeditionDatabase::DeleteAllMembers(m_id); - - SendUpdatesToZoneMembers(true); - SendWorldExpeditionUpdate(ServerOP_ExpeditionMembersRemoved); - - m_members.clear(); -} - -bool Expedition::RemoveMember(const std::string& remove_char_name) -{ - auto member = GetMemberData(remove_char_name); - if (!member.IsValid()) - { - return false; - } - - ExpeditionDatabase::DeleteMember(m_id, member.char_id); - m_dynamiczone.RemoveCharacter(member.char_id); - - ProcessMemberRemoved(member.name, member.char_id); - SendWorldMemberChanged(member.name, member.char_id, true); - - return true; -} - -void Expedition::SwapMember(Client* add_client, const std::string& remove_char_name) -{ - if (!add_client || remove_char_name.empty()) - { - return; - } - - auto member = GetMemberData(remove_char_name); - if (!member.IsValid()) - { - return; - } - - // make remove and add atomic to avoid racing with separate world messages - ExpeditionDatabase::DeleteMember(m_id, member.char_id); - ExpeditionDatabase::InsertMember(m_id, add_client->CharacterID()); - m_dynamiczone.RemoveCharacter(member.char_id); - m_dynamiczone.AddCharacter(add_client->CharacterID()); - - ProcessMemberRemoved(member.name, member.char_id); - ProcessMemberAdded(add_client->GetName(), add_client->CharacterID()); - SendWorldMemberSwapped(member.name, member.char_id, add_client->GetName(), add_client->CharacterID()); -} - -void Expedition::SetMemberStatus(Client* client, ExpeditionMemberStatus status) -{ - if (client) - { - UpdateMemberStatus(client->CharacterID(), status); - SendWorldMemberStatus(client->CharacterID(), status); - - // world could detect this itself but it'd have to process member status updates - // a member coming online will trigger a leader change if all members were offline - if (m_leader.status == ExpeditionMemberStatus::Offline) - { - SendWorldExpeditionUpdate(ServerOP_ExpeditionChooseNewLeader); - } - } -} - -void Expedition::UpdateMemberStatus(uint32_t update_member_id, ExpeditionMemberStatus status) -{ - auto member_data = GetMemberData(update_member_id); - if (!member_data.IsValid()) - { - return; - } - - if (status == ExpeditionMemberStatus::InDynamicZone && !RuleB(Expedition, EnableInDynamicZoneStatus)) - { - status = ExpeditionMemberStatus::Online; - } - - if (update_member_id == m_leader.char_id) - { - m_leader.status = status; - } - - // if zone already had this member status cached avoid packet update to clients - auto it = std::find_if(m_members.begin(), m_members.end(), - [&](const ExpeditionMember& member) { return member.char_id == update_member_id; }); - - if (it != m_members.end() && it->status == status) - { - return; - } - - auto outapp_member_status = CreateMemberListStatusPacket(member_data.name, status); - - for (auto& member : m_members) - { - if (member.char_id == update_member_id) - { - member.status = status; - } - - Client* member_client = entity_list.GetClientByCharID(member.char_id); - if (member_client) - { - member_client->QueuePacket(outapp_member_status.get()); - } - } -} - void Expedition::SendClientExpeditionInvite( Client* client, const std::string& inviter_name, const std::string& swap_remove_name) { @@ -687,7 +434,7 @@ void Expedition::SendClientExpeditionInvite( client->SetPendingExpeditionInvite({ m_id, inviter_name, swap_remove_name }); client->MessageString(Chat::System, EXPEDITION_ASKED_TO_JOIN, - m_leader.name.c_str(), m_expedition_name.c_str()); + GetLeaderName().c_str(), GetName().c_str()); // live (as of March 11 2020 patch) sends warnings for lockouts added // during current expedition that client would receive on entering dz @@ -696,12 +443,13 @@ void Expedition::SendClientExpeditionInvite( { // live doesn't issue a warning for the dz's replay timer const ExpeditionLockoutTimer& lockout = lockout_iter.second; - if (!lockout.IsReplayTimer() && !lockout.IsExpired() && lockout.IsFromExpedition(m_uuid) && - !client->HasExpeditionLockout(m_expedition_name, lockout.GetEventName())) + if (!lockout.IsReplayTimer() && !lockout.IsExpired() && + lockout.IsFromExpedition(GetDynamicZone()->GetUUID()) && + !client->HasExpeditionLockout(GetName(), lockout.GetEventName())) { if (!warned) { - client->Message(Chat::System, DZADD_INVITE_WARNING, m_expedition_name.c_str()); + client->Message(Chat::System, DZADD_INVITE_WARNING, GetName().c_str()); warned = true; } @@ -723,7 +471,7 @@ void Expedition::SendClientExpeditionInvite( void Expedition::SendLeaderMessage( Client* leader_client, uint16_t chat_type, uint32_t string_id, const std::initializer_list& args) { - Client::SendCrossZoneMessageString(leader_client, m_leader.name, chat_type, string_id, args); + Client::SendCrossZoneMessageString(leader_client, GetLeaderName(), chat_type, string_id, args); } bool Expedition::ProcessAddConflicts(Client* leader_client, Client* add_client, bool swapping) @@ -735,7 +483,7 @@ bool Expedition::ProcessAddConflicts(Client* leader_client, Client* add_client, bool has_conflict = false; - if (m_dynamiczone.IsCurrentZoneDzInstance()) + if (GetDynamicZone()->IsCurrentZoneDzInstance()) { SendLeaderMessage(leader_client, Chat::Red, DZADD_LEAVE_ZONE_FIRST, { add_client->GetName() }); has_conflict = true; @@ -750,13 +498,13 @@ bool Expedition::ProcessAddConflicts(Client* leader_client, Client* add_client, } // check any extra event lockouts for this expedition that the client has and expedition doesn't - auto client_lockouts = add_client->GetExpeditionLockouts(m_expedition_name); + auto client_lockouts = add_client->GetExpeditionLockouts(GetName()); for (const auto& client_lockout : client_lockouts) { if (client_lockout.IsReplayTimer()) { // client with a replay lockout is allowed only if the replay timer was from this expedition - if (client_lockout.GetExpeditionUUID() != GetUUID()) + if (client_lockout.GetExpeditionUUID() != GetDynamicZone()->GetUUID()) { has_conflict = true; @@ -792,14 +540,15 @@ bool Expedition::ProcessAddConflicts(Client* leader_client, Client* add_client, // member swapping integrity is handled by invite response if (!swapping) { - auto member_count = ExpeditionDatabase::GetMemberCount(m_id); + auto member_count = GetDynamicZone()->GetDatabaseMemberCount(); if (member_count == 0) { has_conflict = true; } - else if (member_count >= m_max_players) + else if (member_count >= GetDynamicZone()->GetMaxPlayers()) { - SendLeaderMessage(leader_client, Chat::Red, DZADD_EXCEED_MAX, { fmt::format_int(m_max_players).str() }); + SendLeaderMessage(leader_client, Chat::Red, DZADD_EXCEED_MAX, { + fmt::format_int(GetDynamicZone()->GetMaxPlayers()).str() }); has_conflict = true; } } @@ -822,14 +571,12 @@ void Expedition::DzInviteResponse(Client* add_client, bool accepted, const std:: return; } - LogExpeditionsModerate( - "Invite response by [{}] accepted [{}] swap_name [{}]", - add_client->GetName(), accepted, swap_remove_name - ); + LogExpeditionsModerate("Invite response by [{}] accepted [{}] swap_name [{}]", + add_client->GetName(), accepted, swap_remove_name); // a null leader_client is handled by SendLeaderMessage fallbacks // note current leader receives invite reply messages (if leader changed) - Client* leader_client = entity_list.GetClientByCharID(m_leader.char_id); + Client* leader_client = entity_list.GetClientByCharID(GetLeaderID()); if (!accepted) { @@ -852,8 +599,8 @@ void Expedition::DzInviteResponse(Client* add_client, bool accepted, const std:: // error if swapping and character was already removed before the accept if (was_swap_invite) { - auto swap_member = GetMemberData(swap_remove_name); - if (!swap_member.IsValid() || !ExpeditionDatabase::HasMember(m_id, swap_member.char_id)) + auto swap_member = GetDynamicZone()->GetMemberData(swap_remove_name); + if (!swap_member.IsValid() || !GetDynamicZone()->HasDatabaseMember(swap_member.id)) { has_conflicts = true; } @@ -872,8 +619,8 @@ void Expedition::DzInviteResponse(Client* add_client, bool accepted, const std:: { auto replay_lockout = m_lockouts.find(DZ_REPLAY_TIMER_NAME); if (replay_lockout != m_lockouts.end() && - replay_lockout->second.IsFromExpedition(m_uuid) && - !add_client->HasExpeditionLockout(m_expedition_name, DZ_REPLAY_TIMER_NAME)) + replay_lockout->second.IsFromExpedition(GetDynamicZone()->GetUUID()) && + !add_client->HasExpeditionLockout(GetName(), DZ_REPLAY_TIMER_NAME)) { ExpeditionLockoutTimer replay_timer = replay_lockout->second; // copy replay_timer.Reset(); @@ -881,13 +628,20 @@ void Expedition::DzInviteResponse(Client* add_client, bool accepted, const std:: } } - if (was_swap_invite) - { - SwapMember(add_client, swap_remove_name); + auto status = DynamicZoneMemberStatus::Online; + DynamicZoneMember add_member{ add_client->CharacterID(), add_client->GetName(), status }; + + bool success = false; + if (was_swap_invite) { + success = GetDynamicZone()->SwapMember(add_member, swap_remove_name); + } else { + success = GetDynamicZone()->AddMember(add_member); } - else + + if (success) { - AddMember(add_client->GetName(), add_client->CharacterID()); + SendLeaderMessage(leader_client, Chat::Yellow, EXPEDITION_MEMBER_ADDED, + { add_client->GetName(), GetName().c_str() }); } } } @@ -899,15 +653,15 @@ bool Expedition::ConfirmLeaderCommand(Client* requester) return false; } - if (!m_leader.IsValid()) + if (!GetLeader().IsValid()) { requester->MessageString(Chat::Red, UNABLE_RETRIEVE_LEADER); // unconfirmed message return false; } - if (m_leader.char_id != requester->CharacterID()) + if (GetLeaderID() != requester->CharacterID()) { - requester->MessageString(Chat::System, EXPEDITION_NOT_LEADER, m_leader.name.c_str()); + requester->MessageString(Chat::System, EXPEDITION_NOT_LEADER, GetLeaderName().c_str()); return false; } @@ -970,11 +724,11 @@ void Expedition::DzAddPlayer( } else { - auto member_data = GetMemberData(add_char_name); + auto member_data = GetDynamicZone()->GetMemberData(add_char_name); if (member_data.IsValid()) { // live prioritizes offline message before already a member message - if (member_data.status == ExpeditionMemberStatus::Offline) + if (member_data.status == DynamicZoneMemberStatus::Offline) { requester->MessageString(Chat::Red, DZADD_NOT_ONLINE, add_char_name.c_str()); } @@ -1041,14 +795,16 @@ void Expedition::DzRemovePlayer(Client* requester, std::string char_name) } // live only seems to enforce min_players for requesting expeditions, no need to check here - bool removed = RemoveMember(char_name); + // note: on live members removed when inside a dz instance remain "temporary" + // members for kick timer duration and still receive lockouts across zones (unimplemented) + bool removed = GetDynamicZone()->RemoveMember(char_name); if (!removed) { requester->MessageString(Chat::Red, EXPEDITION_NOT_MEMBER, FormatName(char_name).c_str()); } else { - requester->MessageString(Chat::Yellow, EXPEDITION_REMOVED, FormatName(char_name).c_str(), m_expedition_name.c_str()); + requester->MessageString(Chat::Yellow, EXPEDITION_REMOVED, FormatName(char_name).c_str(), GetName().c_str()); } } @@ -1056,7 +812,7 @@ void Expedition::DzQuit(Client* requester) { if (requester) { - RemoveMember(requester->GetName()); + GetDynamicZone()->RemoveMember(requester->GetName()); } } @@ -1068,7 +824,7 @@ void Expedition::DzSwapPlayer( return; } - if (remove_char_name.empty() || !HasMember(remove_char_name)) + if (remove_char_name.empty() || !GetDynamicZone()->HasMember(remove_char_name)) { requester->MessageString(Chat::Red, DZSWAP_CANNOT_REMOVE, FormatName(remove_char_name).c_str()); return; @@ -1081,10 +837,10 @@ void Expedition::DzPlayerList(Client* requester) { if (requester) { - requester->MessageString(Chat::Yellow, EXPEDITION_LEADER, m_leader.name.c_str()); + requester->MessageString(Chat::Yellow, EXPEDITION_LEADER, GetLeaderName().c_str()); std::string member_names; - for (const auto& member : m_members) + for (const auto& member : GetDynamicZone()->GetMembers()) { fmt::format_to(std::back_inserter(member_names), "{}, ", member.name); } @@ -1105,8 +861,8 @@ void Expedition::DzKickPlayers(Client* requester) return; } - RemoveAllMembers(); - requester->MessageString(Chat::Red, EXPEDITION_REMOVED, KICKPLAYERS_EVERYONE, m_expedition_name.c_str()); + GetDynamicZone()->RemoveAllMembers(); + requester->MessageString(Chat::Red, EXPEDITION_REMOVED, "Everyone", GetName().c_str()); } void Expedition::SetLocked( @@ -1114,7 +870,7 @@ void Expedition::SetLocked( { m_is_locked = lock_expedition; - if (m_is_locked && lock_msg != ExpeditionLockMessage::None && m_dynamiczone.IsCurrentZoneDzInstance()) + if (m_is_locked && lock_msg != ExpeditionLockMessage::None && GetDynamicZone()->IsCurrentZoneDzInstance()) { auto msg = (lock_msg == ExpeditionLockMessage::Close) ? LOCK_CLOSE : LOCK_BEGIN; for (const auto& client_iter : entity_list.GetClientList()) @@ -1133,127 +889,20 @@ void Expedition::SetLocked( } } -void Expedition::ProcessLeaderChanged(uint32_t new_leader_id) +void Expedition::OnClientAddRemove(Client* client, bool removed, bool silent) { - auto new_leader = GetMemberData(new_leader_id); - if (!new_leader.IsValid()) + if (client) { - LogExpeditions("Processed invalid new leader id [{}] for expedition [{}]", new_leader_id, m_id); - return; - } + client->SetExpeditionID(removed ? 0 : GetID()); - LogExpeditionsModerate("Replaced [{}] leader [{}] with [{}]", m_id, m_leader.name, new_leader.name); - - m_leader = new_leader; - m_dynamiczone.SetLeaderName(m_leader.name); - - // update each client's expedition window in this zone - auto outapp_leader = CreateLeaderNamePacket(); - for (const auto& member : m_members) - { - Client* member_client = entity_list.GetClientByCharID(member.char_id); - if (member_client) + if (!silent) { - member_client->QueuePacket(outapp_leader.get()); - - if (member.char_id == new_leader_id && RuleB(Expedition, AlwaysNotifyNewLeaderOnChange)) - { - member_client->MessageString(Chat::Yellow, DZMAKELEADER_YOU); - } + auto string_id = removed ? EXPEDITION_REMOVED : EXPEDITION_MEMBER_ADDED; + client->MessageString(Chat::Yellow, string_id, client->GetCleanName(), GetName().c_str()); } } } -void Expedition::ProcessMakeLeader(Client* old_leader_client, Client* new_leader_client, - const std::string& new_leader_name, bool is_success, bool is_online) -{ - if (old_leader_client) - { - // success flag is set by world to indicate new leader set to an online member - if (is_success) - { - old_leader_client->MessageString(Chat::Yellow, DZMAKELEADER_NAME, new_leader_name.c_str()); - } - else if (!is_online) - { - old_leader_client->MessageString(Chat::Red, DZMAKELEADER_NOT_ONLINE, new_leader_name.c_str()); - } - else - { - old_leader_client->MessageString(Chat::Red, EXPEDITION_NOT_MEMBER, new_leader_name.c_str()); - } - } - - if (is_success && new_leader_client && !RuleB(Expedition, AlwaysNotifyNewLeaderOnChange)) - { - new_leader_client->MessageString(Chat::Yellow, DZMAKELEADER_YOU); - } -} - -void Expedition::ProcessMemberAdded(const std::string& char_name, uint32_t added_char_id) -{ - // adds the member to this expedition and notifies both leader and new member - Client* leader_client = entity_list.GetClientByCharID(m_leader.char_id); - if (leader_client) - { - leader_client->MessageString(Chat::Yellow, EXPEDITION_MEMBER_ADDED, char_name.c_str(), m_expedition_name.c_str()); - } - - AddInternalMember(char_name, added_char_id, ExpeditionMemberStatus::Online); - - Client* member_client = entity_list.GetClientByCharID(added_char_id); - if (member_client) - { - member_client->SetExpeditionID(GetID()); - member_client->SendDzCompassUpdate(); - SendClientExpeditionInfo(member_client); - member_client->MessageString(Chat::Yellow, EXPEDITION_MEMBER_ADDED, char_name.c_str(), m_expedition_name.c_str()); - } - - SendNewMemberAddedToZoneMembers(char_name); -} - -void Expedition::ProcessMemberRemoved(const std::string& removed_char_name, uint32_t removed_char_id) -{ - if (m_members.empty()) - { - return; - } - - auto outapp_member_name = CreateMemberListNamePacket(removed_char_name, true); - - for (auto it = m_members.begin(); it != m_members.end();) - { - bool is_removed = (it->name == removed_char_name); - - Client* member_client = entity_list.GetClientByCharID(it->char_id); - if (member_client) - { - // all members receive the removed player name packet - member_client->QueuePacket(outapp_member_name.get()); - - if (is_removed) - { - // live doesn't clear expedition info on clients removed while inside dz. - // it instead let's the dz kick timer do it even if character zones out - // before it triggers. for simplicity we'll always clear immediately - member_client->SetExpeditionID(0); - member_client->SendDzCompassUpdate(); - member_client->QueuePacket(CreateInfoPacket(true).get()); - member_client->MessageString(Chat::Yellow, EXPEDITION_REMOVED, - it->name.c_str(), m_expedition_name.c_str()); - } - } - - it = is_removed ? m_members.erase(it) : it + 1; - } - - LogExpeditionsDetail( - "Processed member [{}] ({}) removal from [{}], cache member count: [{}]", - removed_char_name, removed_char_id, m_id, m_members.size() - ); -} - void Expedition::ProcessLockoutDuration( const ExpeditionLockoutTimer& lockout, int seconds, bool members_only) { @@ -1270,17 +919,17 @@ void Expedition::ProcessLockoutDuration( } } - for (const auto& member : m_members) + for (const auto& member : GetDynamicZone()->GetMembers()) { - Client* member_client = entity_list.GetClientByCharID(member.char_id); + Client* member_client = entity_list.GetClientByCharID(member.id); if (member_client) { - member_client->AddExpeditionLockoutDuration(m_expedition_name, - lockout.GetEventName(), seconds, m_uuid); + member_client->AddExpeditionLockoutDuration(GetName(), + lockout.GetEventName(), seconds, GetDynamicZone()->GetUUID()); } } - if (m_dynamiczone.IsCurrentZoneDzInstance()) + if (GetDynamicZone()->IsCurrentZoneDzInstance()) { AddLockoutDurationClients(lockout, seconds, GetID()); } @@ -1289,15 +938,15 @@ void Expedition::ProcessLockoutDuration( void Expedition::AddLockoutDurationClients( const ExpeditionLockoutTimer& lockout, int seconds, uint32_t exclude_id) { - std::vector lockout_clients; + std::vector lockout_clients; for (const auto& client_iter : entity_list.GetClientList()) { Client* client = client_iter.second; if (client && (exclude_id == 0 || client->GetExpeditionID() != exclude_id)) { lockout_clients.emplace_back(client->CharacterID(), client->GetName()); - client->AddExpeditionLockoutDuration(m_expedition_name, - lockout.GetEventName(), seconds, m_uuid); + client->AddExpeditionLockoutDuration(GetName(), + lockout.GetEventName(), seconds, GetDynamicZone()->GetUUID()); } } @@ -1323,9 +972,9 @@ void Expedition::ProcessLockoutUpdate( } } - for (const auto& member : m_members) + for (const auto& member : GetDynamicZone()->GetMembers()) { - Client* member_client = entity_list.GetClientByCharID(member.char_id); + Client* member_client = entity_list.GetClientByCharID(member.id); if (member_client) { if (!remove) @@ -1334,7 +983,7 @@ void Expedition::ProcessLockoutUpdate( } else { - member_client->RemoveExpeditionLockout(m_expedition_name, lockout.GetEventName()); + member_client->RemoveExpeditionLockout(GetName(), lockout.GetEventName()); } } } @@ -1342,7 +991,7 @@ void Expedition::ProcessLockoutUpdate( // if this is the expedition's dz instance, all clients inside the zone need // to receive added lockouts. this is done on live to avoid exploits where // members leave the expedition but haven't been kicked from zone yet - if (!remove && m_dynamiczone.IsCurrentZoneDzInstance()) + if (!remove && GetDynamicZone()->IsCurrentZoneDzInstance()) { AddLockoutClients(lockout, GetID()); } @@ -1351,7 +1000,7 @@ void Expedition::ProcessLockoutUpdate( void Expedition::AddLockoutClients( const ExpeditionLockoutTimer& lockout, uint32_t exclude_expedition_id) { - std::vector lockout_clients; + std::vector lockout_clients; for (const auto& client_iter : entity_list.GetClientList()) { Client* client = client_iter.second; @@ -1368,63 +1017,6 @@ void Expedition::AddLockoutClients( } } -void Expedition::SendNewMemberAddedToZoneMembers(const std::string& added_name) -{ - // live only sends MemberListName when members are added from a swap, otherwise - // it sends expedition info (unnecessary) and the full member list - // we send a full member list update for both cases since MemberListName adds as - // "unknown" status (either due to unknown packet fields or future client change) - auto outapp_members = CreateMemberListPacket(false); - - for (const auto& member : m_members) - { - if (member.name != added_name) // new member already updated - { - Client* member_client = entity_list.GetClientByCharID(member.char_id); - if (member_client) - { - member_client->QueuePacket(outapp_members.get()); - } - } - } -} - -void Expedition::SendUpdatesToZoneMembers(bool clear, bool message_on_clear) -{ - if (!m_members.empty()) - { - auto outapp_info = CreateInfoPacket(clear); - auto outapp_members = CreateMemberListPacket(clear); - - for (const auto& member : m_members) - { - Client* member_client = entity_list.GetClientByCharID(member.char_id); - if (member_client) - { - member_client->SetExpeditionID(clear ? 0 : GetID()); - member_client->SendDzCompassUpdate(); - member_client->QueuePacket(outapp_info.get()); - member_client->QueuePacket(outapp_members.get()); - member_client->SendExpeditionLockoutTimers(); - if (clear && message_on_clear) - { - member_client->MessageString(Chat::Yellow, EXPEDITION_REMOVED, - member_client->GetName(), m_expedition_name.c_str()); - } - } - } - } -} - -void Expedition::SendClientExpeditionInfo(Client* client) -{ - if (client) - { - client->QueuePacket(CreateInfoPacket().get()); - client->QueuePacket(CreateMemberListPacket().get()); - } -} - void Expedition::SendWorldPendingInvite(const ExpeditionInvite& invite, const std::string& add_name) { LogExpeditions( @@ -1435,30 +1027,6 @@ void Expedition::SendWorldPendingInvite(const ExpeditionInvite& invite, const st SendWorldAddPlayerInvite(invite.inviter_name, invite.swap_remove_name, add_name, true); } -std::unique_ptr Expedition::CreateExpireWarningPacket(uint32_t minutes_remaining) -{ - uint32_t outsize = sizeof(ExpeditionExpireWarning); - auto outapp = std::make_unique(OP_DzExpeditionEndsWarning, outsize); - auto buf = reinterpret_cast(outapp->pBuffer); - buf->minutes_remaining = minutes_remaining; - return outapp; -} - -std::unique_ptr Expedition::CreateInfoPacket(bool clear) -{ - uint32_t outsize = sizeof(ExpeditionInfo_Struct); - auto outapp = std::make_unique(OP_DzExpeditionInfo, outsize); - auto info = reinterpret_cast(outapp->pBuffer); - if (!clear) - { - info->assigned = true; - strn0cpy(info->expedition_name, m_expedition_name.c_str(), sizeof(info->expedition_name)); - strn0cpy(info->leader_name, m_leader.name.c_str(), sizeof(info->leader_name)); - info->max_players = m_max_players; - } - return outapp; -} - std::unique_ptr Expedition::CreateInvitePacket( const std::string& inviter_name, const std::string& swap_remove_name) { @@ -1466,69 +1034,11 @@ std::unique_ptr Expedition::CreateInvitePacket( auto outapp = std::make_unique(OP_DzExpeditionInvite, outsize); auto outbuf = reinterpret_cast(outapp->pBuffer); strn0cpy(outbuf->inviter_name, inviter_name.c_str(), sizeof(outbuf->inviter_name)); - strn0cpy(outbuf->expedition_name, m_expedition_name.c_str(), sizeof(outbuf->expedition_name)); + strn0cpy(outbuf->expedition_name, GetName().c_str(), sizeof(outbuf->expedition_name)); strn0cpy(outbuf->swap_name, swap_remove_name.c_str(), sizeof(outbuf->swap_name)); outbuf->swapping = !swap_remove_name.empty(); - outbuf->dz_zone_id = m_dynamiczone.GetZoneID(); - outbuf->dz_instance_id = m_dynamiczone.GetInstanceID(); - return outapp; -} - -std::unique_ptr Expedition::CreateMemberListPacket(bool clear) -{ - uint32_t member_count = clear ? 0 : static_cast(m_members.size()); - uint32_t member_entries_size = sizeof(ExpeditionMemberEntry_Struct) * member_count; - uint32_t outsize = sizeof(ExpeditionMemberList_Struct) + member_entries_size; - auto outapp = std::make_unique(OP_DzMemberList, outsize); - auto buf = reinterpret_cast(outapp->pBuffer); - - buf->member_count = member_count; - - if (!clear) - { - for (auto i = 0; i < m_members.size(); ++i) - { - strn0cpy(buf->members[i].name, m_members[i].name.c_str(), sizeof(buf->members[i].name)); - buf->members[i].expedition_status = static_cast(m_members[i].status); - } - } - - return outapp; -} - -std::unique_ptr Expedition::CreateMemberListNamePacket( - const std::string& name, bool remove_name) -{ - uint32_t outsize = sizeof(ExpeditionMemberListName_Struct); - auto outapp = std::make_unique(OP_DzMemberListName, outsize); - auto buf = reinterpret_cast(outapp->pBuffer); - buf->add_name = !remove_name; - strn0cpy(buf->name, name.c_str(), sizeof(buf->name)); - return outapp; -} - -std::unique_ptr Expedition::CreateMemberListStatusPacket( - const std::string& name, ExpeditionMemberStatus status) -{ - // member list status uses member list struct with a single entry - uint32_t outsize = sizeof(ExpeditionMemberList_Struct) + sizeof(ExpeditionMemberEntry_Struct); - auto outapp = std::make_unique(OP_DzMemberListStatus, outsize); - auto buf = reinterpret_cast(outapp->pBuffer); - buf->member_count = 1; - - auto entry = reinterpret_cast(buf->members); - strn0cpy(entry->name, name.c_str(), sizeof(entry->name)); - entry->expedition_status = static_cast(status); - - return outapp; -} - -std::unique_ptr Expedition::CreateLeaderNamePacket() -{ - uint32_t outsize = sizeof(ExpeditionSetLeaderName_Struct); - auto outapp = std::make_unique(OP_DzSetLeaderName, outsize); - auto buf = reinterpret_cast(outapp->pBuffer); - strn0cpy(buf->leader_name, m_leader.name.c_str(), sizeof(buf->leader_name)); + outbuf->dz_zone_id = GetDynamicZone()->GetZoneID(); + outbuf->dz_instance_id = GetDynamicZone()->GetInstanceID(); return outapp; } @@ -1597,74 +1107,12 @@ void Expedition::SendWorldMakeLeaderRequest(uint32_t requester_id, const std::st uint32_t pack_size = sizeof(ServerDzCommandMakeLeader_Struct); auto pack = std::make_unique(ServerOP_ExpeditionDzMakeLeader, pack_size); auto buf = reinterpret_cast(pack->pBuffer); - buf->expedition_id = GetID(); + buf->dz_id = GetDynamicZone()->GetID(); buf->requester_id = requester_id; strn0cpy(buf->new_leader_name, new_leader_name.c_str(), sizeof(buf->new_leader_name)); worldserver.SendPacket(pack.get()); } -void Expedition::SendWorldMemberChanged(const std::string& char_name, uint32_t char_id, bool remove) -{ - // notify other zones of added or removed member - uint32_t pack_size = sizeof(ServerExpeditionMemberChange_Struct); - auto pack = std::make_unique(ServerOP_ExpeditionMemberChange, pack_size); - auto buf = reinterpret_cast(pack->pBuffer); - buf->expedition_id = GetID(); - buf->sender_zone_id = zone ? zone->GetZoneID() : 0; - buf->sender_instance_id = zone ? zone->GetInstanceID() : 0; - buf->removed = remove; - buf->char_id = char_id; - strn0cpy(buf->char_name, char_name.c_str(), sizeof(buf->char_name)); - worldserver.SendPacket(pack.get()); -} - -void Expedition::SendWorldMemberStatus(uint32_t character_id, ExpeditionMemberStatus status) -{ - uint32_t pack_size = sizeof(ServerExpeditionMemberStatus_Struct); - auto pack = std::make_unique(ServerOP_ExpeditionMemberStatus, pack_size); - auto buf = reinterpret_cast(pack->pBuffer); - buf->expedition_id = GetID(); - buf->sender_zone_id = zone ? zone->GetZoneID() : 0; - buf->sender_instance_id = zone ? zone->GetInstanceID() : 0; - buf->status = static_cast(status); - buf->character_id = character_id; - worldserver.SendPacket(pack.get()); -} - -void Expedition::SendWorldDzLocationUpdate(uint16_t server_opcode, const DynamicZoneLocation& location) -{ - uint32_t pack_size = sizeof(ServerDzLocation_Struct); - auto pack = std::make_unique(server_opcode, pack_size); - auto buf = reinterpret_cast(pack->pBuffer); - buf->owner_id = GetID(); - buf->dz_zone_id = m_dynamiczone.GetZoneID(); - buf->dz_instance_id = m_dynamiczone.GetInstanceID(); - buf->sender_zone_id = zone ? zone->GetZoneID() : 0; - buf->sender_instance_id = zone ? zone->GetInstanceID() : 0; - buf->zone_id = location.zone_id; - buf->x = location.x; - buf->y = location.y; - buf->z = location.z; - buf->heading = location.heading; - worldserver.SendPacket(pack.get()); -} - -void Expedition::SendWorldMemberSwapped( - const std::string& remove_char_name, uint32_t remove_char_id, const std::string& add_char_name, uint32_t add_char_id) -{ - uint32_t pack_size = sizeof(ServerExpeditionMemberSwap_Struct); - auto pack = std::make_unique(ServerOP_ExpeditionMemberSwap, pack_size); - auto buf = reinterpret_cast(pack->pBuffer); - buf->expedition_id = GetID(); - buf->sender_zone_id = zone ? zone->GetZoneID() : 0; - buf->sender_instance_id = zone ? zone->GetInstanceID() : 0; - buf->add_char_id = add_char_id; - buf->remove_char_id = remove_char_id; - strn0cpy(buf->add_char_name, add_char_name.c_str(), sizeof(buf->add_char_name)); - strn0cpy(buf->remove_char_name, remove_char_name.c_str(), sizeof(buf->remove_char_name)); - worldserver.SendPacket(pack.get()); -} - void Expedition::SendWorldSettingChanged(uint16_t server_opcode, bool setting_value) { uint32_t pack_size = sizeof(ServerExpeditionSetting_Struct); @@ -1677,29 +1125,6 @@ void Expedition::SendWorldSettingChanged(uint16_t server_opcode, bool setting_va worldserver.SendPacket(pack.get()); } -void Expedition::SendWorldGetOnlineMembers( - const std::vector>& expedition_character_ids) -{ - // request online status of characters - uint32_t count = static_cast(expedition_character_ids.size()); - uint32_t entries_size = sizeof(ServerExpeditionCharacterEntry_Struct) * count; - uint32_t pack_size = sizeof(ServerExpeditionCharacters_Struct) + entries_size; - auto pack = std::make_unique(ServerOP_ExpeditionGetOnlineMembers, pack_size); - auto buf = reinterpret_cast(pack->pBuffer); - buf->sender_zone_id = zone ? zone->GetZoneID() : 0; - buf->sender_instance_id = zone ? zone->GetInstanceID() : 0; - buf->count = count; - for (uint32_t i = 0; i < buf->count; ++i) - { - buf->entries[i].expedition_id = expedition_character_ids[i].first; - buf->entries[i].character_id = expedition_character_ids[i].second; - buf->entries[i].character_zone_id = 0; - buf->entries[i].character_instance_id = 0; - buf->entries[i].character_online = false; - } - worldserver.SendPacket(pack.get()); -} - void Expedition::SendWorldCharacterLockout( uint32_t character_id, const ExpeditionLockoutTimer& lockout, bool remove) { @@ -1716,16 +1141,6 @@ void Expedition::SendWorldCharacterLockout( worldserver.SendPacket(pack.get()); } -void Expedition::SendWorldSetSecondsRemaining(uint32_t seconds_remaining) -{ - uint32_t pack_size = sizeof(ServerExpeditionUpdateDuration_Struct); - auto pack = std::make_unique(ServerOP_ExpeditionSecondsRemaining, pack_size); - auto buf = reinterpret_cast(pack->pBuffer); - buf->expedition_id = GetID(); - buf->new_duration_seconds = seconds_remaining; - worldserver.SendPacket(pack.get()); -} - void Expedition::AddLockoutByCharacterID( uint32_t character_id, const std::string& expedition_name, const std::string& event_name, uint32_t seconds, const std::string& uuid) @@ -1815,44 +1230,6 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) } break; } - case ServerOP_ExpeditionDeleted: - { - // sent by world when it deletes expired or empty expeditions - auto buf = reinterpret_cast(pack->pBuffer); - auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); - if (zone && expedition) - { - expedition->SendUpdatesToZoneMembers(true, false); // any members silently removed - - LogExpeditionsModerate("Deleting expedition [{}] from zone cache", buf->expedition_id); - zone->expedition_cache.erase(buf->expedition_id); - } - break; - } - case ServerOP_ExpeditionMembersRemoved: - { - auto buf = reinterpret_cast(pack->pBuffer); - if (zone && !zone->IsZone(buf->sender_zone_id, buf->sender_instance_id)) - { - auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); - if (expedition) - { - expedition->SendUpdatesToZoneMembers(true); - expedition->m_members.clear(); - } - } - break; - } - case ServerOP_ExpeditionLeaderChanged: - { - auto buf = reinterpret_cast(pack->pBuffer); - auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); - if (expedition) - { - expedition->ProcessLeaderChanged(buf->leader_id); - } - break; - } case ServerOP_ExpeditionLockout: case ServerOP_ExpeditionLockoutDuration: { @@ -1862,7 +1239,7 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); if (expedition) { - ExpeditionLockoutTimer lockout{ expedition->GetUUID(), expedition->GetName(), + ExpeditionLockoutTimer lockout{ expedition->GetDynamicZone()->GetUUID(), expedition->GetName(), buf->event_name, buf->expire_time, buf->duration }; if (pack->opcode == ServerOP_ExpeditionLockout) @@ -1877,53 +1254,6 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) } break; } - case ServerOP_ExpeditionMemberChange: - { - auto buf = reinterpret_cast(pack->pBuffer); - if (zone && !zone->IsZone(buf->sender_zone_id, buf->sender_instance_id)) - { - auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); - if (expedition) - { - if (buf->removed) - { - expedition->ProcessMemberRemoved(buf->char_name, buf->char_id); - } - else - { - expedition->ProcessMemberAdded(buf->char_name, buf->char_id); - } - } - } - break; - } - case ServerOP_ExpeditionMemberSwap: - { - auto buf = reinterpret_cast(pack->pBuffer); - if (zone && !zone->IsZone(buf->sender_zone_id, buf->sender_instance_id)) - { - auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); - if (expedition) - { - expedition->ProcessMemberRemoved(buf->remove_char_name, buf->remove_char_id); - expedition->ProcessMemberAdded(buf->add_char_name, buf->add_char_id); - } - } - break; - } - case ServerOP_ExpeditionMemberStatus: - { - auto buf = reinterpret_cast(pack->pBuffer); - if (zone && !zone->IsZone(buf->sender_zone_id, buf->sender_instance_id)) - { - auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); - if (expedition) - { - expedition->UpdateMemberStatus(buf->character_id, static_cast(buf->status)); - } - } - break; - } case ServerOP_ExpeditionLockState: { auto buf = reinterpret_cast(pack->pBuffer); @@ -1950,27 +1280,6 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) } break; } - case ServerOP_ExpeditionGetOnlineMembers: - { - // reply from world for online member statuses request (for multiple expeditions) - auto buf = reinterpret_cast(pack->pBuffer); - for (uint32_t i = 0; i < buf->count; ++i) - { - auto member = reinterpret_cast(&buf->entries[i]); - auto expedition = Expedition::FindCachedExpeditionByID(member->expedition_id); - if (expedition) - { - auto is_online = member->character_online; - auto status = is_online ? ExpeditionMemberStatus::Online : ExpeditionMemberStatus::Offline; - if (is_online && expedition->GetDynamicZone().IsInstanceID(member->character_instance_id)) - { - status = ExpeditionMemberStatus::InDynamicZone; - } - expedition->UpdateMemberStatus(member->character_id, status); - } - } - break; - } case ServerOP_ExpeditionDzAddPlayer: { auto buf = reinterpret_cast(pack->pBuffer); @@ -1997,40 +1306,25 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) case ServerOP_ExpeditionDzMakeLeader: { auto buf = reinterpret_cast(pack->pBuffer); - auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); - if (expedition) + auto old_leader_client = entity_list.GetClientByCharID(buf->requester_id); + auto new_leader_client = entity_list.GetClientByName(buf->new_leader_name); + + if (old_leader_client) { - auto old_leader_client = entity_list.GetClientByCharID(buf->requester_id); - auto new_leader_client = entity_list.GetClientByName(buf->new_leader_name); - expedition->ProcessMakeLeader(old_leader_client, new_leader_client, - buf->new_leader_name, buf->is_success, buf->is_online); - } - break; - } - case ServerOP_ExpeditionDzCompass: - case ServerOP_ExpeditionDzSafeReturn: - case ServerOP_ExpeditionDzZoneIn: - { - auto buf = reinterpret_cast(pack->pBuffer); - if (zone && !zone->IsZone(buf->sender_zone_id, buf->sender_instance_id)) - { - auto expedition = Expedition::FindCachedExpeditionByID(buf->owner_id); - if (expedition) - { - if (pack->opcode == ServerOP_ExpeditionDzCompass) - { - expedition->SetDzCompass(buf->zone_id, buf->x, buf->y, buf->z, false); - } - else if (pack->opcode == ServerOP_ExpeditionDzSafeReturn) - { - expedition->SetDzSafeReturn(buf->zone_id, buf->x, buf->y, buf->z, buf->heading, false); - } - else if (pack->opcode == ServerOP_ExpeditionDzZoneIn) - { - expedition->SetDzZoneInLocation(buf->x, buf->y, buf->z, buf->heading, false); - } + // success flag is set by world to indicate new leader set to an online member + if (buf->is_success) { + old_leader_client->MessageString(Chat::Yellow, DZMAKELEADER_NAME, buf->new_leader_name); + } else if (!buf->is_online) { + old_leader_client->MessageString(Chat::Red, DZMAKELEADER_NOT_ONLINE, buf->new_leader_name); + } else { + old_leader_client->MessageString(Chat::Red, EXPEDITION_NOT_MEMBER, buf->new_leader_name); } } + + if (buf->is_success && new_leader_client && !RuleB(Expedition, AlwaysNotifyNewLeaderOnChange)) + { + new_leader_client->MessageString(Chat::Yellow, DZMAKELEADER_YOU); + } break; } case ServerOP_ExpeditionCharacterLockout: @@ -2056,93 +1350,12 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) } break; } - case ServerOP_ExpeditionDzDuration: - { - auto buf = reinterpret_cast(pack->pBuffer); - auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); - if (expedition) - { - expedition->UpdateDzDuration(buf->new_duration_seconds); - } - break; - } - case ServerOP_ExpeditionExpireWarning: - { - auto buf = reinterpret_cast(pack->pBuffer); - auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); - if (expedition) - { - expedition->SendMembersExpireWarning(buf->minutes_remaining); - } - break; - } - } -} - -void Expedition::SetDzCompass(uint32_t zone_id, float x, float y, float z, bool update_db) -{ - DynamicZoneLocation location{ zone_id, x, y, z, 0.0f }; - m_dynamiczone.SetCompass(location, update_db); - - for (const auto& member : m_members) - { - Client* member_client = entity_list.GetClientByCharID(member.char_id); - if (member_client) - { - member_client->SendDzCompassUpdate(); - } - } - - if (update_db) - { - SendWorldDzLocationUpdate(ServerOP_ExpeditionDzCompass, location); - } -} - -void Expedition::SetDzCompass(const std::string& zone_name, float x, float y, float z, bool update_db) -{ - auto zone_id = ZoneID(zone_name.c_str()); - SetDzCompass(zone_id, x, y, z, update_db); -} - -void Expedition::SetDzSafeReturn(uint32_t zone_id, float x, float y, float z, float heading, bool update_db) -{ - DynamicZoneLocation location{ zone_id, x, y, z, heading }; - - m_dynamiczone.SetSafeReturn(location, update_db); - - if (update_db) - { - SendWorldDzLocationUpdate(ServerOP_ExpeditionDzSafeReturn, location); - } -} - -void Expedition::SetDzSafeReturn(const std::string& zone_name, float x, float y, float z, float heading, bool update_db) -{ - auto zone_id = ZoneID(zone_name.c_str()); - SetDzSafeReturn(zone_id, x, y, z, heading, update_db); -} - -void Expedition::SetDzSecondsRemaining(uint32_t seconds_remaining) -{ - SendWorldSetSecondsRemaining(seconds_remaining); // async -} - -void Expedition::SetDzZoneInLocation(float x, float y, float z, float heading, bool update_db) -{ - DynamicZoneLocation location{ 0, x, y, z, heading }; - - m_dynamiczone.SetZoneInLocation(location, update_db); - - if (update_db) - { - SendWorldDzLocationUpdate(ServerOP_ExpeditionDzZoneIn, location); } } bool Expedition::CanClientLootCorpse(Client* client, uint32_t npc_type_id, uint32_t spawn_id) { - if (client && m_dynamiczone.IsCurrentZoneDzInstance()) + if (client && GetDynamicZone()->IsCurrentZoneDzInstance()) { // entity id takes priority, falls back to checking by npc type if not set std::string event_name = GetLootEventBySpawnID(spawn_id); @@ -2154,7 +1367,7 @@ bool Expedition::CanClientLootCorpse(Client* client, uint32_t npc_type_id, uint3 if (!event_name.empty()) { auto client_lockout = client->GetExpeditionLockout(GetName(), event_name); - if (!client_lockout || client_lockout->GetExpeditionUUID() != GetUUID()) + if (!client_lockout || client_lockout->GetExpeditionUUID() != GetDynamicZone()->GetUUID()) { // client lockout not received in this expedition, prevent looting LogExpeditions( @@ -2171,7 +1384,7 @@ bool Expedition::CanClientLootCorpse(Client* client, uint32_t npc_type_id, uint3 void Expedition::SetLootEventByNPCTypeID(uint32_t npc_type_id, const std::string& event_name) { - if (npc_type_id && m_dynamiczone.IsCurrentZoneDzInstance()) + if (npc_type_id && GetDynamicZone()->IsCurrentZoneDzInstance()) { LogExpeditions("Setting loot event [{}] for npc type id [{}]", event_name, npc_type_id); m_npc_loot_events[npc_type_id] = event_name; @@ -2180,7 +1393,7 @@ void Expedition::SetLootEventByNPCTypeID(uint32_t npc_type_id, const std::string void Expedition::SetLootEventBySpawnID(uint32_t spawn_id, const std::string& event_name) { - if (spawn_id && m_dynamiczone.IsCurrentZoneDzInstance()) + if (spawn_id && GetDynamicZone()->IsCurrentZoneDzInstance()) { LogExpeditions("Setting loot event [{}] for entity id [{}]", event_name, spawn_id); m_spawn_loot_events[spawn_id] = event_name; @@ -2191,7 +1404,7 @@ std::string Expedition::GetLootEventByNPCTypeID(uint32_t npc_type_id) { std::string event_name; - if (npc_type_id && m_dynamiczone.IsCurrentZoneDzInstance()) + if (npc_type_id && GetDynamicZone()->IsCurrentZoneDzInstance()) { auto it = m_npc_loot_events.find(npc_type_id); if (it != m_npc_loot_events.end()) @@ -2207,7 +1420,7 @@ std::string Expedition::GetLootEventBySpawnID(uint32_t spawn_id) { std::string event_name; - if (spawn_id && m_dynamiczone.IsCurrentZoneDzInstance()) + if (spawn_id && GetDynamicZone()->IsCurrentZoneDzInstance()) { auto it = m_spawn_loot_events.find(spawn_id); if (it != m_spawn_loot_events.end()) @@ -2240,22 +1453,6 @@ std::vector Expedition::GetExpeditionLockoutsByCharacter return lockouts; } -void Expedition::SendMembersExpireWarning(uint32_t minutes_remaining) -{ - // expeditions warn members in all zones not just the dz - auto outapp = CreateExpireWarningPacket(minutes_remaining); - for (const auto& member : m_members) - { - Client* member_client = entity_list.GetClientByCharID(member.char_id); - if (member_client) - { - member_client->QueuePacket(outapp.get()); - member_client->MessageString(Chat::Yellow, EXPEDITION_MIN_REMAIN, - fmt::format_int(minutes_remaining).c_str()); - } - } -} - void Expedition::SyncCharacterLockouts( uint32_t character_id, std::vector& client_lockouts) { @@ -2268,7 +1465,8 @@ void Expedition::SyncCharacterLockouts( for (const auto& lockout_iter : m_lockouts) { const ExpeditionLockoutTimer& lockout = lockout_iter.second; - if (lockout.IsReplayTimer() || lockout.IsExpired() || lockout.GetExpeditionUUID() != m_uuid) + if (lockout.IsReplayTimer() || lockout.IsExpired() || + lockout.GetExpeditionUUID() != GetDynamicZone()->GetUUID()) { continue; } @@ -2284,7 +1482,7 @@ void Expedition::SyncCharacterLockouts( client_lockouts.emplace_back(lockout); // insert missing } else if (client_lockout_iter->GetSecondsRemaining() < lockout.GetSecondsRemaining() && - client_lockout_iter->GetExpeditionUUID() != m_uuid) + client_lockout_iter->GetExpeditionUUID() != GetDynamicZone()->GetUUID()) { // only update lockout timer not uuid so loot event apis still work modified = true; diff --git a/zone/expedition.h b/zone/expedition.h index 4b9b1e940..73b63ade1 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -22,8 +22,8 @@ #define EXPEDITION_H #include "dynamic_zone.h" -#include "../common/eq_constants.h" #include "../common/expedition_lockout_timer.h" +#include "../common/repositories/expeditions_repository.h" #include #include #include @@ -34,21 +34,10 @@ class Client; class EQApplicationPacket; struct ExpeditionInvite; class ExpeditionRequest; -class MySQLRequestResult; class ServerPacket; extern const char* const DZ_YOU_NOT_ASSIGNED; extern const char* const EXPEDITION_OTHER_BELONGS; -extern const char* const CREATE_NOT_ALL_ADDED; - -enum class ExpeditionMemberStatus : uint8_t -{ - Unknown = 0, - Online, - Offline, - InDynamicZone, - LinkDead -}; enum class ExpeditionLockMessage : uint8_t { @@ -57,29 +46,14 @@ enum class ExpeditionLockMessage : uint8_t Begin }; -struct ExpeditionMember -{ - uint32_t char_id = 0; - std::string name; - ExpeditionMemberStatus status = ExpeditionMemberStatus::Online; - - ExpeditionMember() = default; - ExpeditionMember(uint32_t char_id_, const std::string& name_) - : char_id(char_id_), name(name_) {} - ExpeditionMember(uint32_t char_id_, const std::string& name_, ExpeditionMemberStatus status_) - : char_id(char_id_), name(name_), status(status_) {} - - bool IsValid() const { return char_id != 0 && !name.empty(); } -}; - class Expedition { public: Expedition() = delete; - Expedition(uint32_t id, const std::string& uuid, DynamicZone&& dz, const std::string& expedition_name, - const ExpeditionMember& leader, uint32_t min_players, uint32_t max_players); + Expedition(DynamicZone* dz) : m_dynamic_zone(dz) { assert(m_dynamic_zone != nullptr); } + Expedition(DynamicZone* dz, uint32_t id, uint32_t dz_id); - static Expedition* TryCreate(Client* requester, DynamicZone& dynamiczone, ExpeditionRequest& request); + static Expedition* TryCreate(Client* requester, DynamicZone& dynamiczone, bool disable_messages); static void CacheFromDatabase(uint32_t expedition_id); static bool CacheAllFromDatabase(); @@ -104,27 +78,15 @@ public: const std::string& expedition_name = {}, const std::string& event_name = {}); static void AddLockoutClients(const ExpeditionLockoutTimer& lockout, uint32_t exclude_id = 0); - uint32_t GetDynamicZoneID() const { return m_dynamiczone.GetID(); } uint32_t GetID() const { return m_id; } - uint16_t GetInstanceID() const { return m_dynamiczone.GetInstanceID(); } - uint32_t GetLeaderID() const { return m_leader.char_id; } - uint32_t GetMinPlayers() const { return m_min_players; } - uint32_t GetMaxPlayers() const { return m_max_players; } - uint32_t GetMemberCount() const { return static_cast(m_members.size()); } - DynamicZone& GetDynamicZone() { return m_dynamiczone; } - const std::string& GetName() const { return m_expedition_name; } - const std::string& GetLeaderName() const { return m_leader.name; } - const std::string& GetUUID() const { return m_uuid; } + uint32_t GetDynamicZoneID() const { return m_dynamic_zone_id; } + DynamicZone* GetDynamicZone() const { return m_dynamic_zone; } + const DynamicZoneMember& GetLeader() { return GetDynamicZone()->GetLeader(); } + uint32_t GetLeaderID() { return GetDynamicZone()->GetLeaderID(); } + const std::string& GetLeaderName() { return GetDynamicZone()->GetLeaderName(); } const std::unordered_map& GetLockouts() const { return m_lockouts; } - const std::vector& GetMembers() const { return m_members; } - - bool AddMember(const std::string& add_char_name, uint32_t add_char_id); - bool HasMember(const std::string& character_name); - bool HasMember(uint32_t character_id); - void RemoveAllMembers(bool enable_removal_timers = true); - bool RemoveMember(const std::string& remove_char_name); - void SetMemberStatus(Client* client, ExpeditionMemberStatus status); - void SwapMember(Client* add_client, const std::string& remove_char_name); + const std::string& GetName() { return GetDynamicZone()->GetName(); } + void RegisterDynamicZoneCallbacks(); bool IsLocked() const { return m_is_locked; } void SetLocked(bool lock_expedition, ExpeditionLockMessage lock_msg, @@ -147,7 +109,6 @@ public: void SetLootEventByNPCTypeID(uint32_t npc_type_id, const std::string& event_name); void SetLootEventBySpawnID(uint32_t spawn_id, const std::string& event_name); - void SendClientExpeditionInfo(Client* client); void SendWorldMakeLeaderRequest(uint32_t requester_id, const std::string& new_leader_name); void SendWorldPendingInvite(const ExpeditionInvite& invite, const std::string& add_name); @@ -161,43 +122,26 @@ public: void DzQuit(Client* requester); void DzKickPlayers(Client* requester); - void SetDzCompass(uint32_t zone_id, float x, float y, float z, bool update_db = false); - void SetDzCompass(const std::string& zone_name, float x, float y, float z, bool update_db = false); - void SetDzSafeReturn(uint32_t zone_id, float x, float y, float z, float heading, bool update_db = false); - void SetDzSafeReturn(const std::string& zone_name, float x, float y, float z, float heading, bool update_db = false); - void SetDzSecondsRemaining(uint32_t seconds_remaining); - void SetDzZoneInLocation(float x, float y, float z, float heading, bool update_db = false); - static const int32_t REPLAY_TIMER_ID; static const int32_t EVENT_TIMER_ID; private: - static void CacheExpeditions(MySQLRequestResult& results); - static void SendWorldGetOnlineMembers(const std::vector>& expedition_character_ids); + static void CacheExpeditions(std::vector&& expeditions); static void SendWorldCharacterLockout(uint32_t character_id, const ExpeditionLockoutTimer& lockout, bool remove); void AddLockout(const ExpeditionLockoutTimer& lockout, bool members_only = false); void AddLockoutDurationClients(const ExpeditionLockoutTimer& lockout, int seconds, uint32_t exclude_id = 0); - void AddInternalMember(const std::string& char_name, uint32_t char_id, ExpeditionMemberStatus status); bool ConfirmLeaderCommand(Client* requester); + void OnClientAddRemove(Client* client, bool removed, bool silent); + void LoadRepositoryResult(const ExpeditionsRepository::Expeditions& entry); bool ProcessAddConflicts(Client* leader_client, Client* add_client, bool swapping); - void ProcessLeaderChanged(uint32_t new_leader_id); void ProcessLockoutDuration(const ExpeditionLockoutTimer& lockout, int seconds, bool members_only = false); void ProcessLockoutUpdate(const ExpeditionLockoutTimer& lockout, bool remove, bool members_only = false); - void ProcessMakeLeader(Client* old_leader, Client* new_leader, - const std::string& new_leader_name, bool is_success, bool is_online); - void ProcessMemberAdded(const std::string& added_char_name, uint32_t added_char_id); - void ProcessMemberRemoved(const std::string& removed_char_name, uint32_t removed_char_id); void SaveLockouts(ExpeditionRequest& request); - void SaveMembers(ExpeditionRequest& request); void SendClientExpeditionInvite( Client* client, const std::string& inviter_name, const std::string& swap_remove_name); void SendLeaderMessage(Client* leader_client, uint16_t chat_type, uint32_t string_id, const std::initializer_list& args = {}); - void SendMembersExpireWarning(uint32_t minutes); - void SendNewMemberAddedToZoneMembers(const std::string& added_name); - void SendUpdatesToZoneMembers(bool clear = false, bool message_on_clear = true); - void SendWorldDzLocationUpdate(uint16_t server_opcode, const DynamicZoneLocation& location); void SendWorldExpeditionUpdate(uint16_t server_opcode); void SendWorldAddPlayerInvite(const std::string& inviter_name, const std::string& swap_remove_name, const std::string& add_name, bool pending = false); @@ -205,38 +149,18 @@ private: const ExpeditionLockoutTimer& lockout, int seconds, bool members_only = false); void SendWorldLockoutUpdate( const ExpeditionLockoutTimer& lockout, bool remove, bool members_only = false); - void SendWorldMemberChanged(const std::string& char_name, uint32_t char_id, bool remove); - void SendWorldMemberStatus(uint32_t character_id, ExpeditionMemberStatus status); - void SendWorldMemberSwapped(const std::string& remove_char_name, uint32_t remove_char_id, - const std::string& add_char_name, uint32_t add_char_id); - void SendWorldSetSecondsRemaining(uint32_t seconds_remaining); void SendWorldSettingChanged(uint16_t server_opcode, bool setting_value); void SetDynamicZone(DynamicZone&& dz); void TryAddClient(Client* add_client, const std::string& inviter_name, const std::string& swap_remove_name, Client* leader_client = nullptr); - void UpdateDzDuration(uint32_t new_duration) { m_dynamiczone.SetUpdatedDuration(new_duration); } - void UpdateMemberStatus(uint32_t update_character_id, ExpeditionMemberStatus status); - ExpeditionMember GetMemberData(uint32_t character_id); - ExpeditionMember GetMemberData(const std::string& character_name); - std::unique_ptr CreateExpireWarningPacket(uint32_t minutes_remaining); - std::unique_ptr CreateInfoPacket(bool clear = false); std::unique_ptr CreateInvitePacket(const std::string& inviter_name, const std::string& swap_remove_name); - std::unique_ptr CreateMemberListPacket(bool clear = false); - std::unique_ptr CreateMemberListNamePacket(const std::string& name, bool remove_name); - std::unique_ptr CreateMemberListStatusPacket(const std::string& name, ExpeditionMemberStatus status); - std::unique_ptr CreateLeaderNamePacket(); - uint32_t m_id = 0; - uint32_t m_min_players = 0; - uint32_t m_max_players = 0; - bool m_is_locked = false; - bool m_add_replay_on_join = true; - std::string m_uuid; - std::string m_expedition_name; - DynamicZone m_dynamiczone { DynamicZoneType::Expedition }; - ExpeditionMember m_leader; - std::vector m_members; + uint32_t m_id = 0; + uint32_t m_dynamic_zone_id = 0; + bool m_is_locked = false; + bool m_add_replay_on_join = true; + DynamicZone* m_dynamic_zone = nullptr; // should never be null, will exist for lifetime of expedition std::unordered_map m_lockouts; std::unordered_map m_npc_loot_events; // only valid inside dz zone std::unordered_map m_spawn_loot_events; // only valid inside dz zone diff --git a/zone/expedition_database.cpp b/zone/expedition_database.cpp index d628d8fd1..9a3bfb837 100644 --- a/zone/expedition_database.cpp +++ b/zone/expedition_database.cpp @@ -26,77 +26,27 @@ #include "../common/string_util.h" #include -uint32_t ExpeditionDatabase::InsertExpedition( - const std::string& uuid, uint32_t dz_id, const std::string& expedition_name, - uint32_t leader_id, uint32_t min_players, uint32_t max_players) +uint32_t ExpeditionDatabase::InsertExpedition(uint32_t dz_id) { - LogExpeditionsDetail( - "Inserting new expedition [{}] leader [{}] uuid [{}]", expedition_name, leader_id, uuid - ); + LogExpeditionsDetail("Inserting new expedition dz [{}]", dz_id); std::string query = fmt::format(SQL( INSERT INTO expeditions - (uuid, dynamic_zone_id, expedition_name, leader_id, min_players, max_players) + (dynamic_zone_id) VALUES - ('{}', {}, '{}', {}, {}, {}); - ), uuid, dz_id, EscapeString(expedition_name), leader_id, min_players, max_players); + ({}); + ), dz_id); auto results = database.QueryDatabase(query); if (!results.Success()) { - LogExpeditions("Failed to obtain an expedition id for [{}]", expedition_name); + LogExpeditions("Failed to obtain an expedition id for dz [{}]", dz_id); return 0; } return results.LastInsertedID(); } -std::string ExpeditionDatabase::LoadExpeditionsSelectQuery() -{ - return std::string(SQL( - SELECT - expeditions.id, - expeditions.uuid, - expeditions.dynamic_zone_id, - expeditions.expedition_name, - expeditions.leader_id, - expeditions.min_players, - expeditions.max_players, - expeditions.add_replay_on_join, - expeditions.is_locked, - character_data.name leader_name, - expedition_members.character_id, - member_data.name - FROM expeditions - INNER JOIN character_data ON expeditions.leader_id = character_data.id - INNER JOIN expedition_members ON expeditions.id = expedition_members.expedition_id - AND expedition_members.is_current_member = TRUE - INNER JOIN character_data member_data ON expedition_members.character_id = member_data.id - )); -} - -MySQLRequestResult ExpeditionDatabase::LoadExpedition(uint32_t expedition_id) -{ - LogExpeditionsDetail("Loading expedition [{}]", expedition_id); - - std::string query = fmt::format(SQL( - {} WHERE expeditions.id = {}; - ), LoadExpeditionsSelectQuery(), expedition_id); - - return database.QueryDatabase(query); -} - -MySQLRequestResult ExpeditionDatabase::LoadAllExpeditions() -{ - LogExpeditionsDetail("Loading all expeditions from database"); - - std::string query = fmt::format(SQL( - {} ORDER BY expeditions.id; - ), LoadExpeditionsSelectQuery()); - - return database.QueryDatabase(query); -} - std::vector ExpeditionDatabase::LoadCharacterLockouts(uint32_t character_id) { LogExpeditionsDetail("Loading character [{}] lockouts", character_id); @@ -170,54 +120,6 @@ std::vector ExpeditionDatabase::LoadCharacterLockouts( return lockouts; } -std::unordered_map> -ExpeditionDatabase::LoadMultipleExpeditionLockouts( - const std::vector& expedition_ids) -{ - LogExpeditionsDetail("Loading internal lockouts for [{}] expeditions", expedition_ids.size()); - - std::string in_expedition_ids_query = fmt::format("{}", fmt::join(expedition_ids, ",")); - - // these are loaded into the same container type expeditions use to store lockouts - std::unordered_map> lockouts; - - if (!in_expedition_ids_query.empty()) - { - std::string query = fmt::format(SQL( - SELECT - expedition_lockouts.expedition_id, - expedition_lockouts.from_expedition_uuid, - expeditions.expedition_name, - expedition_lockouts.event_name, - UNIX_TIMESTAMP(expedition_lockouts.expire_time), - expedition_lockouts.duration - FROM expedition_lockouts - INNER JOIN expeditions ON expedition_lockouts.expedition_id = expeditions.id - WHERE expedition_id IN ({}) - ORDER BY expedition_id; - ), in_expedition_ids_query); - - auto results = database.QueryDatabase(query); - - if (results.Success()) - { - for (auto row = results.begin(); row != results.end(); ++row) - { - auto expedition_id = strtoul(row[0], nullptr, 10); - lockouts[expedition_id].emplace(row[3], ExpeditionLockoutTimer{ - row[1], // expedition_uuid - row[2], // expedition_name - row[3], // event_name - strtoull(row[4], nullptr, 10), // expire_time - static_cast(strtoul(row[5], nullptr, 10)) // original duration - }); - } - } - } - - return lockouts; -} - void ExpeditionDatabase::DeleteAllCharacterLockouts(uint32_t character_id) { LogExpeditionsDetail("Deleting all character [{}] lockouts", character_id); @@ -268,7 +170,7 @@ void ExpeditionDatabase::DeleteCharacterLockout( } void ExpeditionDatabase::DeleteMembersLockout( - const std::vector& members, + const std::vector& members, const std::string& expedition_name, const std::string& event_name) { LogExpeditionsDetail("Deleting members lockout: [{}]:[{}]", expedition_name, event_name); @@ -276,7 +178,7 @@ void ExpeditionDatabase::DeleteMembersLockout( std::string query_character_ids; for (const auto& member : members) { - fmt::format_to(std::back_inserter(query_character_ids), "{},", member.char_id); + fmt::format_to(std::back_inserter(query_character_ids), "{},", member.id); } if (!query_character_ids.empty()) @@ -307,67 +209,6 @@ void ExpeditionDatabase::DeleteLockout(uint32_t expedition_id, const std::string database.QueryDatabase(query); } -uint32_t ExpeditionDatabase::GetExpeditionIDFromCharacterID(uint32_t character_id) -{ - LogExpeditionsDetail("Getting expedition id for character [{}]", character_id); - - uint32_t expedition_id = 0; - auto query = fmt::format(SQL( - SELECT expedition_id FROM expedition_members - WHERE character_id = {} AND is_current_member = TRUE; - ), character_id); - - auto results = database.QueryDatabase(query); - if (results.Success() && results.RowCount() > 0) - { - auto row = results.begin(); - expedition_id = strtoul(row[0], nullptr, 10); - } - return expedition_id; -} - -uint32_t ExpeditionDatabase::GetMemberCount(uint32_t expedition_id) -{ - LogExpeditionsDetail("Getting expedition [{}] member count from db", expedition_id); - - uint32_t member_count = 0; - if (expedition_id != 0) - { - auto query = fmt::format(SQL( - SELECT COUNT(*) - FROM expedition_members - WHERE expedition_id = {} AND is_current_member = TRUE; - ), expedition_id); - - auto results = database.QueryDatabase(query); - if (results.Success() && results.RowCount() > 0) - { - auto row = results.begin(); - member_count = strtoul(row[0], nullptr, 10); - } - } - return member_count; -} - -bool ExpeditionDatabase::HasMember(uint32_t expedition_id, uint32_t character_id) -{ - LogExpeditionsDetail("Checking db expedition [{}] for character [{}]", expedition_id, character_id); - - if (expedition_id == 0 || character_id == 0) - { - return false; - } - - auto query = fmt::format(SQL( - SELECT id - FROM expedition_members - WHERE expedition_id = {} AND character_id = {} AND is_current_member = TRUE; - ), expedition_id, character_id); - - auto results = database.QueryDatabase(query); - return (results.Success() && results.RowCount() > 0); -} - void ExpeditionDatabase::InsertCharacterLockouts(uint32_t character_id, const std::vector& lockouts) { @@ -406,7 +247,7 @@ void ExpeditionDatabase::InsertCharacterLockouts(uint32_t character_id, } void ExpeditionDatabase::InsertMembersLockout( - const std::vector& members, const ExpeditionLockoutTimer& lockout) + const std::vector& members, const ExpeditionLockoutTimer& lockout) { LogExpeditionsDetail( "Inserting members lockout [{}]:[{}] with expire time [{}]", @@ -418,7 +259,7 @@ void ExpeditionDatabase::InsertMembersLockout( { fmt::format_to(std::back_inserter(insert_values), "({}, FROM_UNIXTIME({}), {}, '{}', '{}', '{}'),", - member.char_id, + member.id, lockout.GetExpireTime(), lockout.GetDuration(), lockout.GetExpeditionUUID(), @@ -509,50 +350,6 @@ void ExpeditionDatabase::InsertLockouts( } } -void ExpeditionDatabase::InsertMember(uint32_t expedition_id, uint32_t character_id) -{ - LogExpeditionsDetail("Inserting character [{}] into expedition [{}]", character_id, expedition_id); - - auto query = fmt::format(SQL( - INSERT INTO expedition_members - (expedition_id, character_id) - VALUES - ({}, {}) - ON DUPLICATE KEY UPDATE is_current_member = TRUE; - ), expedition_id, character_id); - - database.QueryDatabase(query); -} - -void ExpeditionDatabase::InsertMembers( - uint32_t expedition_id, const std::vector& members) -{ - LogExpeditionsDetail("Inserting characters into expedition [{}]", expedition_id); - - std::string insert_values; - for (const auto& member : members) - { - fmt::format_to(std::back_inserter(insert_values), - "({}, {}),", - expedition_id, member.char_id - ); - } - - if (!insert_values.empty()) - { - insert_values.pop_back(); // trailing comma - - auto query = fmt::format(SQL( - INSERT INTO expedition_members - (expedition_id, character_id) - VALUES {} - ON DUPLICATE KEY UPDATE is_current_member = TRUE; - ), insert_values); - - database.QueryDatabase(query); - } -} - void ExpeditionDatabase::UpdateLockState(uint32_t expedition_id, bool is_locked) { LogExpeditionsDetail("Updating lock state [{}] for expedition [{}]", is_locked, expedition_id); @@ -564,29 +361,6 @@ void ExpeditionDatabase::UpdateLockState(uint32_t expedition_id, bool is_locked) database.QueryDatabase(query); } -void ExpeditionDatabase::DeleteMember(uint32_t expedition_id, uint32_t character_id) -{ - LogExpeditionsDetail("Removing member [{}] from expedition [{}]", character_id, expedition_id); - - auto query = fmt::format(SQL( - UPDATE expedition_members SET is_current_member = FALSE - WHERE expedition_id = {} AND character_id = {}; - ), expedition_id, character_id); - - database.QueryDatabase(query); -} - -void ExpeditionDatabase::DeleteAllMembers(uint32_t expedition_id) -{ - LogExpeditionsDetail("Removing all members of expedition [{}]", expedition_id); - - auto query = fmt::format(SQL( - UPDATE expedition_members SET is_current_member = FALSE WHERE expedition_id = {}; - ), expedition_id); - - database.QueryDatabase(query); -} - void ExpeditionDatabase::UpdateReplayLockoutOnJoin(uint32_t expedition_id, bool add_on_join) { LogExpeditionsDetail("Updating replay lockout on join [{}] for expedition [{}]", add_on_join, expedition_id); @@ -598,7 +372,7 @@ void ExpeditionDatabase::UpdateReplayLockoutOnJoin(uint32_t expedition_id, bool database.QueryDatabase(query); } -void ExpeditionDatabase::AddLockoutDuration(const std::vector& members, +void ExpeditionDatabase::AddLockoutDuration(const std::vector& members, const ExpeditionLockoutTimer& lockout, int seconds) { LogExpeditionsDetail( @@ -610,7 +384,7 @@ void ExpeditionDatabase::AddLockoutDuration(const std::vector& { fmt::format_to(std::back_inserter(insert_values), "({}, FROM_UNIXTIME({}), {}, '{}', '{}', '{}'),", - member.char_id, + member.id, lockout.GetExpireTime(), lockout.GetDuration(), lockout.GetExpeditionUUID(), diff --git a/zone/expedition_database.h b/zone/expedition_database.h index 2c72ee9ad..5e35fb7b3 100644 --- a/zone/expedition_database.h +++ b/zone/expedition_database.h @@ -30,66 +30,33 @@ class Expedition; class ExpeditionLockoutTimer; -struct ExpeditionMember; +struct DynamicZoneMember; class MySQLRequestResult; namespace ExpeditionDatabase { - uint32_t InsertExpedition( - const std::string& uuid, uint32_t instance_id, const std::string& expedition_name, - uint32_t leader_id, uint32_t min_players, uint32_t max_players); - std::string LoadExpeditionsSelectQuery(); - MySQLRequestResult LoadExpedition(uint32_t expedition_id); - MySQLRequestResult LoadAllExpeditions(); + uint32_t InsertExpedition(uint32_t dz_id); std::vector LoadCharacterLockouts(uint32_t character_id); std::vector LoadCharacterLockouts(uint32_t character_id, const std::string& expedition_name); - std::unordered_map> - LoadMultipleExpeditionLockouts(const std::vector& expedition_ids); - void DeleteAllMembers(uint32_t expedition_id); - void DeleteMember(uint32_t expedition_id, uint32_t character_id); void DeleteAllCharacterLockouts(uint32_t character_id); void DeleteAllCharacterLockouts(uint32_t character_id, const std::string& expedition_name); void DeleteCharacterLockout(uint32_t character_id, const std::string& expedition_name, const std::string& event_name); void DeleteLockout(uint32_t expedition_id, const std::string& event_name); - void DeleteMembersLockout(const std::vector& members, + void DeleteMembersLockout(const std::vector& members, const std::string& expedition_name, const std::string& event_name); - uint32_t GetExpeditionIDFromCharacterID(uint32_t character_id); - uint32_t GetMemberCount(uint32_t expedition_id); - bool HasMember(uint32_t expedition_id, uint32_t character_id); void InsertCharacterLockouts(uint32_t character_id, const std::vector& lockouts); - void InsertMembersLockout(const std::vector& members, + void InsertMembersLockout(const std::vector& members, const ExpeditionLockoutTimer& lockout); void InsertLockout(uint32_t expedition_id, const ExpeditionLockoutTimer& lockout); void InsertLockouts(uint32_t expedition_id, const std::unordered_map& lockouts); - void InsertMember(uint32_t expedition_id, uint32_t character_id); - void InsertMembers(uint32_t expedition_id, const std::vector& members); void UpdateLockState(uint32_t expedition_id, bool is_locked); void UpdateReplayLockoutOnJoin(uint32_t expedition_id, bool add_on_join); - void AddLockoutDuration(const std::vector& members, + void AddLockoutDuration(const std::vector& members, const ExpeditionLockoutTimer& lockout, int seconds); }; -namespace LoadExpeditionColumns -{ - enum eLoadExpeditionColumns - { - id = 0, - uuid, - dz_id, - expedition_name, - leader_id, - min_players, - max_players, - add_replay_on_join, - is_locked, - leader_name, - member_id, - member_name - }; -}; - #endif diff --git a/zone/expedition_request.cpp b/zone/expedition_request.cpp index 465859df5..98cad6050 100644 --- a/zone/expedition_request.cpp +++ b/zone/expedition_request.cpp @@ -30,12 +30,10 @@ constexpr char SystemName[] = "expedition"; -ExpeditionRequest::ExpeditionRequest(std::string expedition_name, - uint32_t min_players, uint32_t max_players, bool disable_messages -) : - m_expedition_name(std::move(expedition_name)), - m_min_players(min_players), - m_max_players(max_players), +ExpeditionRequest::ExpeditionRequest(const DynamicZone& dz, bool disable_messages) : + m_expedition_name(dz.GetName()), + m_min_players(dz.GetMinPlayers()), + m_max_players(dz.GetMaxPlayers()), m_disable_messages(disable_messages) { } @@ -221,7 +219,7 @@ bool ExpeditionRequest::CheckMembersForConflicts(const std::vector& return true; } - m_members.emplace_back(character.id, character.name, ExpeditionMemberStatus::Online); + m_members.emplace_back(character.id, character.name, DynamicZoneMemberStatus::Online); character_ids.emplace_back(character.id); } diff --git a/zone/expedition_request.h b/zone/expedition_request.h index b148d3132..6f1396a42 100644 --- a/zone/expedition_request.h +++ b/zone/expedition_request.h @@ -35,8 +35,7 @@ class Raid; class ExpeditionRequest { public: - ExpeditionRequest(std::string expedition_name, uint32_t min_players, - uint32_t max_players, bool disable_messages = false); + ExpeditionRequest(const DynamicZone& dz, bool disable_messages = false); bool Validate(Client* requester); @@ -47,8 +46,8 @@ public: const std::string& GetNotAllAddedMessage() const { return m_not_all_added_msg; } uint32_t GetMinPlayers() const { return m_min_players; } uint32_t GetMaxPlayers() const { return m_max_players; } - std::vector GetMembers() const { return m_members; } - std::unordered_map GetLockouts() const { return m_lockouts; } + const std::vector& GetMembers() const { return m_members; } + const std::unordered_map& GetLockouts() const { return m_lockouts; } private: bool CanMembersJoin(const std::vector& member_names); @@ -72,7 +71,7 @@ private: std::string m_expedition_name; std::string m_leader_name; std::string m_not_all_added_msg; - std::vector m_members; + std::vector m_members; std::unordered_map m_lockouts; }; diff --git a/zone/forage.cpp b/zone/forage.cpp index 75ae00197..375788420 100644 --- a/zone/forage.cpp +++ b/zone/forage.cpp @@ -367,7 +367,7 @@ void Client::GoFish() PushItemOnCursor(*inst); SendItemPacket(EQ::invslot::slotCursor, inst, ItemPacketLimbo); if (RuleB(TaskSystem, EnableTaskSystem)) - UpdateTasksForItem(ActivityFish, food_id); + UpdateTasksForItem(TaskActivityType::Fish, food_id); safe_delete(inst); inst = m_inv.GetItem(EQ::invslot::slotCursor); @@ -487,7 +487,7 @@ void Client::ForageItem(bool guarantee) { PushItemOnCursor(*inst); SendItemPacket(EQ::invslot::slotCursor, inst, ItemPacketLimbo); if(RuleB(TaskSystem, EnableTaskSystem)) - UpdateTasksForItem(ActivityForage, foragedfood); + UpdateTasksForItem(TaskActivityType::Forage, foragedfood); safe_delete(inst); inst = m_inv.GetItem(EQ::invslot::slotCursor); diff --git a/zone/gm_commands/acceptrules.cpp b/zone/gm_commands/acceptrules.cpp new file mode 100755 index 000000000..a73e098a2 --- /dev/null +++ b/zone/gm_commands/acceptrules.cpp @@ -0,0 +1,11 @@ +#include "../client.h" + +void command_acceptrules(Client *c, const Seperator *sep) +{ + if (!database.GetAgreementFlag(c->AccountID())) { + database.SetAgreementFlag(c->AccountID()); + c->SendAppearancePacket(AT_Anim, ANIM_STAND); + c->Message(Chat::White, "It is recorded you have agreed to the rules."); + } +} + diff --git a/zone/gm_commands/advnpcspawn.cpp b/zone/gm_commands/advnpcspawn.cpp new file mode 100755 index 000000000..7545ded35 --- /dev/null +++ b/zone/gm_commands/advnpcspawn.cpp @@ -0,0 +1,534 @@ +#include "../client.h" +#include "../groups.h" + +void command_advnpcspawn(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments) { + c->Message( + Chat::White, + "Usage: #advnpcspawn addentry [Spawngroup ID] [NPC ID] [Spawn Chance] - Adds a new Spawngroup Entry" + ); + c->Message( + Chat::White, + "Usage: #advnpcspawn addspawn [Spawngroup ID] - Adds a new Spawngroup Entry from an existing Spawngroup" + ); + c->Message(Chat::White, "Usage: #advnpcspawn clearbox [Spawngroup ID] - Clears the roambox of a Spawngroup"); + c->Message(Chat::White, "Usage: #advnpcspawn deletespawn - Deletes a Spawngroup"); + c->Message( + Chat::White, + "Usage: #advnpcspawn editbox [Spawngroup ID] [Distance] [Minimum X] [Maximum X] [Minimum Y] [Maximum Y] [Delay] - Edit the roambox of a Spawngroup" + ); + c->Message( + Chat::White, + "Usage: #advnpcspawn editrespawn [Respawn Timer] [Variance] - Edit the Respawn Timer of a Spawngroup" + ); + c->Message( + Chat::White, + "Usage: #advnpcspawn makegroup [Spawn Group Name] [Spawn Limit] [Distance] [Minimum X] [Maximum X] [Minimum Y] [Maximum Y] [Delay] - Makes a new Spawngroup" + ); + c->Message(Chat::White, "Usage: #advnpcspawn makenpc - Makes a new NPC"); + c->Message(Chat::White, "Usage: #advnpcspawn movespawn - Moves a Spawngroup to your current location"); + c->Message(Chat::White, "Usage: #advnpcspawn setversion [Version] - Sets a Spawngroup's Version"); + return; + } + + std::string spawn_command = str_tolower(sep->arg[1]); + bool is_add_entry = spawn_command.find("addentry") != std::string::npos; + bool is_add_spawn = spawn_command.find("addspawn") != std::string::npos; + bool is_clear_box = spawn_command.find("clearbox") != std::string::npos; + bool is_delete_spawn = spawn_command.find("deletespawn") != std::string::npos; + bool is_edit_box = spawn_command.find("editgroup") != std::string::npos; + bool is_edit_respawn = spawn_command.find("editrespawn") != std::string::npos; + bool is_make_group = spawn_command.find("makegroup") != std::string::npos; + bool is_make_npc = spawn_command.find("makenpc") != std::string::npos; + bool is_move_spawn = spawn_command.find("movespawn") != std::string::npos; + bool is_set_version = spawn_command.find("setversion") != std::string::npos; + if ( + !is_add_entry && + !is_add_spawn && + !is_clear_box && + !is_delete_spawn && + !is_edit_box && + !is_edit_respawn && + !is_make_group && + !is_make_npc && + !is_move_spawn && + !is_set_version + ) { + c->Message( + Chat::White, + "Usage: #advnpcspawn addentry [Spawngroup ID] [NPC ID] [Spawn Chance] - Adds a new Spawngroup Entry" + ); + c->Message( + Chat::White, + "Usage: #advnpcspawn addspawn [Spawngroup ID] - Adds a new Spawngroup Entry from an existing Spawngroup" + ); + c->Message(Chat::White, "Usage: #advnpcspawn clearbox [Spawngroup ID] - Clears the roambox of a Spawngroup"); + c->Message(Chat::White, "Usage: #advnpcspawn deletespawn - Deletes a Spawngroup"); + c->Message( + Chat::White, + "Usage: #advnpcspawn editbox [Spawngroup ID] [Distance] [Minimum X] [Maximum X] [Minimum Y] [Maximum Y] [Delay] - Edit the roambox of a Spawngroup" + ); + c->Message( + Chat::White, + "Usage: #advnpcspawn editrespawn [Respawn Timer] [Variance] - Edit the Respawn Timer of a Spawngroup" + ); + c->Message( + Chat::White, + "Usage: #advnpcspawn makegroup [Spawn Group Name] [Spawn Limit] [Distance] [Minimum X] [Maximum X] [Minimum Y] [Maximum Y] [Delay] - Makes a new Spawngroup" + ); + c->Message(Chat::White, "Usage: #advnpcspawn makenpc - Makes a new NPC"); + c->Message(Chat::White, "Usage: #advnpcspawn movespawn - Moves a Spawngroup to your current location"); + c->Message(Chat::White, "Usage: #advnpcspawn setversion [Version] - Sets a Spawngroup's Version"); + return; + } + + + if (is_add_entry) { + if (arguments < 4) { + c->Message(Chat::White, "Usage: #advnpcspawn addentry [Spawngroup ID] [NPC ID] [Spawn Chance]"); + return; + } + + auto spawngroup_id = std::stoi(sep->arg[2]); + auto npc_id = std::stoi(sep->arg[2]); + auto spawn_chance = std::stoi(sep->arg[2]); + + std::string query = fmt::format( + SQL( + INSERT INTO spawnentry(spawngroupID, npcID, chance) + VALUES({}, {}, {}) + ), + spawngroup_id, + npc_id, + spawn_chance + ); + auto results = content_db.QueryDatabase(query); + if (!results.Success()) { + c->Message(Chat::White, "Failed to add entry to Spawngroup."); + return; + } + + c->Message( + Chat::White, + fmt::format( + "{} ({}) added to Spawngroup {}, its spawn chance is {}%%.", + database.GetCleanNPCNameByID(npc_id), + npc_id, + spawngroup_id, + spawn_chance + ).c_str() + ); + return; + } + else if (is_add_spawn) { + content_db.NPCSpawnDB( + NPCSpawnTypes::AddSpawnFromSpawngroup, + zone->GetShortName(), + zone->GetInstanceVersion(), + c, + 0, + std::stoi(sep->arg[2]) + ); + c->Message( + Chat::White, + fmt::format( + "Spawn Added | Added spawn from Spawngroup ID {}.", + std::stoi(sep->arg[2]) + ).c_str() + ); + return; + } + else if (is_clear_box) { + if (arguments != 2 || !sep->IsNumber(2)) { + c->Message(Chat::White, "Usage: #advnpcspawn clearbox [Spawngroup ID]"); + return; + } + + std::string query = fmt::format( + "UPDATE spawngroup SET dist = 0, min_x = 0, max_x = 0, min_y = 0, max_y = 0, delay = 0 WHERE id = {}", + std::stoi(sep->arg[2]) + ); + auto results = content_db.QueryDatabase(query); + if (!results.Success()) { + c->Message(Chat::White, "Failed to clear Spawngroup box."); + return; + } + + c->Message( + Chat::White, + fmt::format( + "Spawngroup {} Roambox Cleared | Delay: 0 Distance: 0.00", + std::stoi(sep->arg[2]) + ).c_str() + ); + c->Message( + Chat::White, + fmt::format( + "Spawngroup {} Roambox Cleared | Minimum X: 0.00 Maximum X: 0.00", + std::stoi(sep->arg[2]) + ).c_str() + ); + c->Message( + Chat::White, + fmt::format( + "Spawngroup {} Roambox Cleared | Minimum Y: 0.00 Maximum Y: 0.00", + std::stoi(sep->arg[2]) + ).c_str() + ); + return; + } + else if (is_delete_spawn) { + if (!c->GetTarget() || !c->GetTarget()->IsNPC()) { + c->Message(Chat::White, "You must target an NPC to use this command."); + return; + } + + NPC *target = c->GetTarget()->CastToNPC(); + Spawn2 *spawn2 = target->respawn2; + if (!spawn2) { + c->Message(Chat::White, "Failed to delete spawn because NPC has no Spawn2."); + return; + } + + auto spawn2_id = spawn2->GetID(); + std::string query = fmt::format( + "DELETE FROM spawn2 WHERE id = {}", + spawn2_id + ); + auto results = content_db.QueryDatabase(query); + if (!results.Success()) { + c->Message(Chat::White, "Failed to delete spawn."); + return; + } + + c->Message( + Chat::White, + fmt::format( + "Spawn2 {} Deleted | Name: {} ({})", + spawn2_id, + target->GetCleanName(), + target->GetID() + ).c_str() + ); + target->Depop(false); + return; + } + else if (is_edit_box) { + if ( + arguments != 8 || + !sep->IsNumber(3) || + !sep->IsNumber(4) || + !sep->IsNumber(5) || + !sep->IsNumber(6) || + !sep->IsNumber(7) || + !sep->IsNumber(8) + ) { + c->Message( + Chat::White, + "Usage: #advnpcspawn editbox [Spawngroup ID] [Distance] [Minimum X] [Maximum X] [Minimum Y] [Maximum Y] [Delay]" + ); + return; + } + auto spawngroup_id = std::stoi(sep->arg[2]); + auto distance = std::stof(sep->arg[3]); + auto minimum_x = std::stof(sep->arg[4]); + auto maximum_x = std::stof(sep->arg[5]); + auto minimum_y = std::stof(sep->arg[6]); + auto maximum_y = std::stof(sep->arg[7]); + auto delay = std::stoi(sep->arg[8]); + + std::string query = fmt::format( + "UPDATE spawngroup SET dist = {:.2f}, min_x = {:.2f}, max_x = {:.2f}, max_y = {:.2f}, min_y = {:.2f}, delay = {} WHERE id = {}", + distance, + minimum_x, + maximum_x, + minimum_y, + maximum_y, + delay, + spawngroup_id + ); + auto results = content_db.QueryDatabase(query); + if (!results.Success()) { + c->Message(Chat::White, "Failed to edit Spawngroup box."); + return; + } + + c->Message( + Chat::White, + fmt::format( + "Spawngroup {} Roambox Edited | Delay: {} Distance: {:.2f}", + spawngroup_id, + delay, + distance + ).c_str() + ); + c->Message( + Chat::White, + fmt::format( + "Spawngroup {} Roambox Edited | Minimum X: {:.2f} Maximum X: {:.2f}", + spawngroup_id, + minimum_x, + maximum_x + ).c_str() + ); + c->Message( + Chat::White, + fmt::format( + "Spawngroup {} Roambox Edited | Minimum Y: {:.2f} Maximum Y: {:.2f}", + spawngroup_id, + minimum_y, + maximum_y + ).c_str() + ); + return; + } + else if (is_edit_respawn) { + if (arguments < 2 || !sep->IsNumber(2)) { + c->Message(Chat::White, "Usage: #advnpcspawn editrespawn [Respawn Timer] [Variance]"); + return; + } + + if (!c->GetTarget() || !c->GetTarget()->IsNPC()) { + c->Message(Chat::White, "You must target an NPC to use this command."); + return; + } + + NPC *target = c->GetTarget()->CastToNPC(); + Spawn2 *spawn2 = target->respawn2; + if (!spawn2) { + c->Message(Chat::White, "Failed to edit respawn because NPC has no Spawn2."); + return; + } + + auto spawn2_id = spawn2->GetID(); + uint32 respawn_timer = std::stoi(sep->arg[2]); + uint32 variance = ( + sep->IsNumber(3) ? + std::stoi(sep->arg[3]) : + spawn2->GetVariance() + ); + std::string query = fmt::format( + "UPDATE spawn2 SET respawntime = {}, variance = {} WHERE id = {}", + respawn_timer, + variance, + spawn2_id + ); + auto results = content_db.QueryDatabase(query); + if (!results.Success()) { + c->Message(Chat::White, "Failed to edit respawn."); + return; + } + + c->Message( + Chat::White, + fmt::format( + "Spawn2 {} Respawn Modified | Name: {} ({}) Respawn Timer: {} Variance: {}", + spawn2_id, + target->GetCleanName(), + target->GetID(), + respawn_timer, + variance + ).c_str() + ); + spawn2->SetRespawnTimer(respawn_timer); + spawn2->SetVariance(variance); + return; + } + else if (is_make_group) { + if ( + arguments != 9 || + !sep->IsNumber(3) || + !sep->IsNumber(4) || + !sep->IsNumber(5) || + !sep->IsNumber(6) || + !sep->IsNumber(7) || + !sep->IsNumber(8) || + !sep->IsNumber(9) + ) { + c->Message( + Chat::White, + "Usage: #advncspawn makegroup [Spawn Group Name] [Spawn Limit] [Distance] [Minimum X] [Maximum X] [Minimum Y] [Maximum Y] [Delay]" + ); + return; + } + std::string spawngroup_name = sep->arg[2]; + auto spawn_limit = std::stoi(sep->arg[3]); + auto distance = std::stof(sep->arg[4]); + auto minimum_x = std::stof(sep->arg[5]); + auto maximum_x = std::stof(sep->arg[6]); + auto minimum_y = std::stof(sep->arg[7]); + auto maximum_y = std::stof(sep->arg[8]); + auto delay = std::stoi(sep->arg[9]); + + std::string query = fmt::format( + "INSERT INTO spawngroup" + "(name, spawn_limit, dist, min_x, max_x, min_y, max_y, delay)" + "VALUES ('{}', {}, {:.2f}, {:.2f}, {:.2f}, {:.2f}, {:.2f}, {})", + spawngroup_name, + spawn_limit, + distance, + minimum_x, + maximum_x, + minimum_y, + maximum_y, + delay + ); + auto results = content_db.QueryDatabase(query); + if (!results.Success()) { + c->Message(Chat::White, "Failed to make Spawngroup."); + return; + } + + auto spawngroup_id = results.LastInsertedID(); + c->Message( + Chat::White, + fmt::format( + "Spawngroup {} Created | Name: {} Spawn Limit: {}", + spawngroup_id, + spawngroup_name, + spawn_limit + ).c_str() + ); + c->Message( + Chat::White, + fmt::format( + "Spawngroup {} Created | Delay: {} Distance: {:.2f}", + spawngroup_id, + delay, + distance + ).c_str() + ); + c->Message( + Chat::White, + fmt::format( + "Spawngroup {} Created | Minimum X: {:.2f} Maximum X: {:.2f}", + spawngroup_id, + minimum_x, + maximum_x + ).c_str() + ); + c->Message( + Chat::White, + fmt::format( + "Spawngroup {} Created | Minimum Y: {:.2f} Maximum Y: {:.2f}", + spawngroup_id, + minimum_y, + maximum_y + ).c_str() + ); + return; + } + else if (is_make_npc) { + if (!c->GetTarget() || !c->GetTarget()->IsNPC()) { + c->Message(Chat::White, "You must target an NPC to use this command."); + return; + } + + NPC *target = c->GetTarget()->CastToNPC(); + content_db.NPCSpawnDB( + NPCSpawnTypes::CreateNewNPC, + zone->GetShortName(), + zone->GetInstanceVersion(), + c, + target + ); + return; + } + else if (is_move_spawn) { + if (!c->GetTarget() || !c->GetTarget()->IsNPC()) { + c->Message(Chat::White, "You must target an NPC to use this command."); + return; + } + + NPC *target = c->GetTarget()->CastToNPC(); + Spawn2 *spawn2 = target->respawn2; + if (!spawn2) { + c->Message(Chat::White, "Failed to move spawn because NPC has no Spawn2."); + return; + } + + auto client_position = c->GetPosition(); + auto spawn2_id = spawn2->GetID(); + std::string query = fmt::format( + "UPDATE spawn2 SET x = {:.2f}, y = {:.2f}, z = {:.2f}, heading = {:.2f} WHERE id = {}", + client_position.x, + client_position.y, + client_position.z, + client_position.w, + spawn2_id + ); + auto results = content_db.QueryDatabase(query); + if (!results.Success()) { + c->Message(Chat::White, "Failed to move spawn."); + return; + } + + c->Message( + Chat::White, + fmt::format( + "Spawn2 {} Moved | Name: {} ({})", + spawn2_id, + target->GetCleanName(), + target->GetID() + ).c_str() + ); + c->Message( + Chat::White, + fmt::format( + "Spawn2 {} Moved | XYZ: {}, {}, {} Heading: {}", + spawn2_id, + client_position.x, + client_position.y, + client_position.z, + client_position.w + ).c_str() + ); + target->GMMove( + client_position.x, + client_position.y, + client_position.z, + client_position.w + ); + return; + } + else if (is_set_version) { + if (arguments != 2 || !sep->IsNumber(2)) { + c->Message(Chat::White, "Usage: #advnpcspawn setversion [Version]"); + return; + } + + if (!c->GetTarget() || !c->GetTarget()->IsNPC()) { + c->Message(Chat::White, "You must target an NPC to use this command."); + return; + } + + NPC *target = c->GetTarget()->CastToNPC(); + auto version = std::stoi(sep->arg[2]); + std::string query = fmt::format( + "UPDATE spawn2 SET version = {} WHERE spawngroupID = {}", + version, + target->GetSpawnGroupId() + ); + auto results = content_db.QueryDatabase(query); + if (!results.Success()) { + c->Message(Chat::White, "Failed to set version."); + return; + } + + c->Message( + Chat::White, + fmt::format( + "Spawngroup {} Version Modified | Name: {} ({}) Version: {}", + target->GetSpawnGroupId(), + target->GetCleanName(), + target->GetID(), + version + ).c_str() + ); + target->Depop(false); + return; + } +} + diff --git a/zone/gm_commands/aggro.cpp b/zone/gm_commands/aggro.cpp new file mode 100755 index 000000000..90fe9e335 --- /dev/null +++ b/zone/gm_commands/aggro.cpp @@ -0,0 +1,28 @@ +#include "../client.h" + +void command_aggro(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments || !sep->IsNumber(1)) { + c->Message(Chat::White, "Usage: #aggro [Distance] [-v] (-v is verbose Faction Information)"); + return; + } + + if ( + !c->GetTarget() || + ( + c->GetTarget() && + !c->GetTarget()->IsNPC() + ) + ) { + c->Message(Chat::White, "You must target an NPC to use this command."); + return; + } + + NPC* target = c->GetTarget()->CastToNPC(); + + float distance = std::stof(sep->arg[1]); + bool verbose = !strcasecmp("-v", sep->arg[2]); + entity_list.DescribeAggro(c, target, distance, verbose); +} + diff --git a/zone/gm_commands/aggrozone.cpp b/zone/gm_commands/aggrozone.cpp new file mode 100755 index 000000000..797ed2128 --- /dev/null +++ b/zone/gm_commands/aggrozone.cpp @@ -0,0 +1,31 @@ +#include "../client.h" + +void command_aggrozone(Client *c, const Seperator *sep) +{ + Mob *target = c; + if (c->GetTarget()) { + target = c->GetTarget(); + } + + uint32 hate = 0; + if (sep->IsNumber(1)) { + hate = std::stoul(sep->arg[1]); + } + + entity_list.AggroZone(target, hate); + c->Message( + Chat::White, + fmt::format( + "Aggroing zone on {}.", + ( + c == target ? + "yourself" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ) + ).c_str() + ); +} diff --git a/zone/gm_commands/ai.cpp b/zone/gm_commands/ai.cpp new file mode 100755 index 000000000..e7f686307 --- /dev/null +++ b/zone/gm_commands/ai.cpp @@ -0,0 +1,263 @@ +#include "../client.h" + +void command_ai(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments) { + c->Message(Chat::White, "Usage: #ai consider [Mob Name] - Show how an NPC considers to a mob"); + c->Message(Chat::White, "Usage: #ai faction [Faction ID] - Set an NPC's Faction ID"); + c->Message(Chat::White, "Usage: #ai guard - Save an NPC's guard spot to their current location"); + c->Message(Chat::White, "Usage: #ai roambox [Distance] [Min X] [Max X] [Min Y] [Max Y] [Delay] [Minimum Delay] - Set an NPC's roambox using X and Y coordinates"); + c->Message(Chat::White, "Usage: #ai roambox [Distance] [Roam Distance] [Delay] [Minimum Delay] - Set an NPC's roambox using roam distance"); + c->Message(Chat::White, "Usage: #ai spells [Spell List ID] - Set an NPC's Spell List ID"); + return; + } + + if (!c->GetTarget() || !c->GetTarget()->IsNPC()) { + c->Message(Chat::White, "You must target an NPC to use this command."); + return; + } + + auto target = c->GetTarget()->CastToNPC(); + + bool is_consider = !strcasecmp(sep->arg[1], "consider"); + bool is_faction = !strcasecmp(sep->arg[1], "faction"); + bool is_guard = !strcasecmp(sep->arg[1], "guard"); + bool is_roambox = !strcasecmp(sep->arg[1], "roambox"); + bool is_spells = !strcasecmp(sep->arg[1], "spells"); + + if ( + !is_consider && + !is_faction && + !is_guard && + !is_roambox && + !is_spells + ) { + c->Message(Chat::White, "Usage: #ai consider [Mob Name] - Show how an NPC considers to a mob"); + c->Message(Chat::White, "Usage: #ai faction [Faction ID] - Set an NPC's Faction ID"); + c->Message(Chat::White, "Usage: #ai guard - Save an NPC's guard spot to their current location"); + c->Message(Chat::White, "Usage: #ai roambox [Distance] [Min X] [Max X] [Min Y] [Max Y] [Delay] [Minimum Delay] - Set an NPC's roambox using X and Y coordinates"); + c->Message(Chat::White, "Usage: #ai roambox [Distance] [Roam Distance] [Delay] [Minimum Delay] - Set an NPC's roambox using roam distance"); + c->Message(Chat::White, "Usage: #ai spells [Spell List ID] - Set an NPC's Spell List ID"); + return; + } + + if (is_consider) { + if (arguments == 2) { + auto mob_name = sep->arg[2]; + auto mob_to_consider = entity_list.GetMob(mob_name); + if (mob_to_consider) { + auto consider_level = static_cast(mob_to_consider->GetReverseFactionCon(target)); + c->Message( + Chat::White, + fmt::format( + "{} ({}) considers {} ({}) as {} ({}).", + target->GetCleanName(), + target->GetID(), + mob_to_consider->GetCleanName(), + mob_to_consider->GetID(), + EQ::constants::GetConsiderLevelName(consider_level), + consider_level + ).c_str() + ); + } + } else { + c->Message(Chat::White, "Usage: #ai consider [Mob Name] - Show how an NPC considers a mob"); + } + } else if (is_faction) { + if (sep->IsNumber(2)) { + auto faction_id = std::stoi(sep->arg[2]); + auto faction_name = content_db.GetFactionName(faction_id); + target->SetNPCFactionID(faction_id); + c->Message( + Chat::White, + fmt::format( + "{} ({}) is now on Faction {}.", + target->GetCleanName(), + target->GetID(), + ( + faction_name.empty() ? + std::to_string(faction_id) : + fmt::format( + "{} ({})", + faction_name, + faction_id + ) + ) + ).c_str() + ); + } else { + c->Message(Chat::White, "Usage: #ai faction [Faction ID] - Set an NPC's Faction ID"); + } + } else if (is_guard) { + auto target_position = target->GetPosition(); + + target->SaveGuardSpot(target_position); + + c->Message( + Chat::White, + fmt::format( + "{} ({}) now has a guard spot of {:.2f}, {:.2f}, {:.2f} with a heading of {:.2f}.", + target->GetCleanName(), + target->GetID(), + target_position.x, + target_position.y, + target_position.z, + target_position.w + ).c_str() + ); + } else if (is_roambox) { + if (target->IsAIControlled()) { + if ( + arguments >= 6 && + arguments <= 8 && + sep->IsNumber(2) && + sep->IsNumber(3) && + sep->IsNumber(4) && + sep->IsNumber(5) && + sep->IsNumber(6) + ) { + auto distance = std::stof(sep->arg[2]); + auto min_x = std::stof(sep->arg[3]); + auto max_x = std::stof(sep->arg[4]); + auto min_y = std::stof(sep->arg[5]); + auto max_y = std::stof(sep->arg[6]); + + uint32 delay = 2500; + uint32 minimum_delay = 2500; + + if (sep->IsNumber(7)) { + delay = std::stoul(sep->arg[7]); + } + + if (sep->IsNumber(8)) { + minimum_delay = std::stoul(sep->arg[8]); + } + + target->CastToNPC()->AI_SetRoambox( + distance, + max_x, + min_x, + max_y, + min_y, + delay, + minimum_delay + ); + + c->Message( + Chat::White, + fmt::format( + "{} ({}) now has a roambox from {}, {} to {}, {} with {} and {} and a distance of {}.", + target->GetCleanName(), + target->GetID(), + min_x, + min_y, + max_x, + max_y, + ( + delay ? + fmt::format( + "a delay of {} ({})", + ConvertMillisecondsToTime(delay), + delay + ): + "no delay" + ), + ( + minimum_delay ? + fmt::format( + "a minimum delay of {} ({})", + ConvertMillisecondsToTime(minimum_delay), + minimum_delay + ): + "no minimum delay" + ), + distance + ).c_str() + ); + } else if ( + arguments >= 3 && + arguments <= 4 && + sep->IsNumber(2) && + sep->IsNumber(3) + ) { + auto max_distance = std::stof(sep->arg[2]); + auto roam_distance_variance = std::stof(sep->arg[3]); + + uint32 delay = 2500; + uint32 minimum_delay = 2500; + + if (sep->IsNumber(4)) { + delay = std::stoul(sep->arg[4]); + } + + if (sep->IsNumber(5)) { + minimum_delay = std::stoul(sep->arg[5]); + } + + target->CastToNPC()->AI_SetRoambox( + max_distance, + roam_distance_variance, + delay, + minimum_delay + ); + + c->Message( + Chat::White, + fmt::format( + "{} ({}) now has a roambox with a max distance of {} and a roam distance variance of {} with {} and {}.", + target->GetCleanName(), + target->GetID(), + max_distance, + roam_distance_variance, + ( + delay ? + fmt::format( + "a delay of {} ({})", + delay, + ConvertMillisecondsToTime(delay) + ): + "no delay" + ), + ( + minimum_delay ? + fmt::format( + "a minimum delay of {} ({})", + minimum_delay, + ConvertMillisecondsToTime(delay) + ): + "no minimum delay" + ) + ).c_str() + ); + } else { + c->Message(Chat::White, "Usage: #ai roambox [Distance] [Min X] [Max X] [Min Y] [Max Y] [Delay] [Minimum Delay] - Set an NPC's roambox using X and Y coordinates"); + c->Message(Chat::White, "Usage: #ai roambox [Distance] [Roam Distance] [Delay] [Minimum Delay] - Set an NPC's roambox using roam distance"); + } + } else { + c->Message(Chat::White, "You must target an NPC with AI."); + } + } else if (is_spells) { + if (sep->IsNumber(2)) { + auto spell_list_id = std::stoul(sep->arg[2]); + if (spell_list_id >= 0) { + target->CastToNPC()->AI_AddNPCSpells(spell_list_id); + + c->Message( + Chat::White, + fmt::format( + "{} ({}) is now using Spell List {}.", + target->GetCleanName(), + target->GetID(), + spell_list_id + ).c_str() + ); + } else { + c->Message(Chat::White, "Spell List ID must be greater than or equal to 0."); + } + } else { + c->Message(Chat::White, "Usage: #ai spells [Spell List ID] - Set an NPC's Spell List ID"); + } + } +} + diff --git a/zone/gm_commands/appearance.cpp b/zone/gm_commands/appearance.cpp new file mode 100755 index 000000000..4f28fc50f --- /dev/null +++ b/zone/gm_commands/appearance.cpp @@ -0,0 +1,26 @@ +#include "../client.h" + +void command_appearance(Client *c, const Seperator *sep) +{ + Mob *t = c->CastToMob(); + + // sends any appearance packet + // Dev debug command, for appearance types + if (sep->arg[2][0] == 0) { + c->Message(Chat::White, "Usage: #appearance type value"); + } + else { + if ((c->GetTarget())) { + t = c->GetTarget(); + } + t->SendAppearancePacket(atoi(sep->arg[1]), atoi(sep->arg[2])); + c->Message( + Chat::White, + "Sending appearance packet: target=%s, type=%s, value=%s", + t->GetName(), + sep->arg[1], + sep->arg[2] + ); + } +} + diff --git a/zone/gm_commands/appearanceeffects.cpp b/zone/gm_commands/appearanceeffects.cpp new file mode 100644 index 000000000..8a9a5eb44 --- /dev/null +++ b/zone/gm_commands/appearanceeffects.cpp @@ -0,0 +1,44 @@ +#include "../client.h" + +void command_appearanceeffects(Client *c, const Seperator *sep) +{ + if (sep->arg[1][0] == '\0' || !strcasecmp(sep->arg[1], "help")) { + c->Message(Chat::White, "Syntax: #appearanceeffects [subcommand]."); + c->Message(Chat::White, "[view] Display all appearance effects saved to your target. #appearanceffects view"); + c->Message(Chat::White, "[set] Set an appearance effects saved to your target. #appearanceffects set [app_effectid] [slotid]"); + c->Message(Chat::White, "[remove] Remove all appearance effects saved to your target. #appearanceffects remove"); + } + + if (!strcasecmp(sep->arg[1], "view")) { + Mob* m_target = c->GetTarget(); + if (m_target) { + m_target->GetAppearenceEffects(); + } + return; + } + + + if (!strcasecmp(sep->arg[1], "set")) { + int32 app_effectid = atof(sep->arg[2]); + int32 slot = atoi(sep->arg[3]); + + Mob* m_target = c->GetTarget(); + if (m_target) { + m_target->SendAppearanceEffect(app_effectid, 0, 0, 0, 0, nullptr, slot, 0, 0, 0, 0, 0, 0, 0, 0, 0); + c->Message(Chat::White, "Appearance Effect ID %i for slot %i has been set.", app_effectid, slot); + } + } + + if (!strcasecmp(sep->arg[1], "remove")) { + Mob* m_target = c->GetTarget(); + if (m_target) { + m_target->SendIllusionPacket(m_target->GetRace(), m_target->GetGender(), m_target->GetTexture(), m_target->GetHelmTexture(), + m_target->GetHairColor(), m_target->GetBeardColor(), m_target->GetEyeColor1(), m_target->GetEyeColor2(), + m_target->GetHairStyle(), m_target->GetLuclinFace(), m_target->GetBeard(), 0xFF, + m_target->GetDrakkinHeritage(), m_target->GetDrakkinTattoo(), m_target->GetDrakkinDetails(), m_target->GetSize(), false); + m_target->ClearAppearenceEffects(); + c->Message(Chat::White, "All Appearance Effects have been removed."); + } + return; + } +} diff --git a/zone/gm_commands/attack.cpp b/zone/gm_commands/attack.cpp new file mode 100755 index 000000000..c4a483aa9 --- /dev/null +++ b/zone/gm_commands/attack.cpp @@ -0,0 +1,18 @@ +#include "../client.h" + +void command_attack(Client *c, const Seperator *sep) +{ + if (c->GetTarget() && c->GetTarget()->IsNPC() && sep->arg[1] != 0) { + Mob *sictar = entity_list.GetMob(sep->argplus[1]); + if (sictar) { + c->GetTarget()->CastToNPC()->AddToHateList(sictar, 1, 0); + } + else { + c->Message(Chat::White, "Error: %s not found", sep->arg[1]); + } + } + else { + c->Message(Chat::White, "Usage: (needs NPC targeted) #attack targetname"); + } +} + diff --git a/zone/gm_commands/augmentitem.cpp b/zone/gm_commands/augmentitem.cpp new file mode 100755 index 000000000..81b5523aa --- /dev/null +++ b/zone/gm_commands/augmentitem.cpp @@ -0,0 +1,18 @@ +#include "../client.h" +#include "../object.h" + +void command_augmentitem(Client *c, const Seperator *sep) +{ + if (!c) { + return; + } + + auto in_augment = new AugmentItem_Struct[sizeof(AugmentItem_Struct)]; + in_augment->container_slot = 1000; // + in_augment->augment_slot = -1; + if (c->GetTradeskillObject() != nullptr) { + Object::HandleAugmentation(c, in_augment, c->GetTradeskillObject()); + } + safe_delete_array(in_augment); +} + diff --git a/zone/gm_commands/ban.cpp b/zone/gm_commands/ban.cpp new file mode 100755 index 000000000..3582aca19 --- /dev/null +++ b/zone/gm_commands/ban.cpp @@ -0,0 +1,72 @@ +#include "../client.h" +#include "../worldserver.h" + +extern WorldServer worldserver; + +void command_ban(Client *c, const Seperator *sep) +{ + if (sep->arg[1][0] == 0 || sep->arg[2][0] == 0) { + c->Message(Chat::White, "Usage: #ban "); + return; + } + + auto account_id = database.GetAccountIDByChar(sep->arg[1]); + + std::string message; + int i = 2; + while (1) { + if (sep->arg[i][0] == 0) { + break; + } + + if (message.length() > 0) { + message.push_back(' '); + } + + message += sep->arg[i]; + ++i; + } + + if (message.length() == 0) { + c->Message(Chat::White, "Usage: #ban "); + return; + } + + if (account_id == 0) { + c->Message(Chat::Red, "Character does not exist."); + return; + } + + std::string query = StringFormat( + "UPDATE account SET status = -2, ban_reason = '%s' " + "WHERE id = %i", EscapeString(message).c_str(), account_id + ); + auto results = database.QueryDatabase(query); + + c->Message( + Chat::Red, + "Account number %i with the character %s has been banned with message: \"%s\"", + account_id, + sep->arg[1], + message.c_str()); + + ServerPacket flagUpdatePack(ServerOP_FlagUpdate, 6); + *((uint32 *) &flagUpdatePack.pBuffer[0]) = account_id; + *((int16 *) &flagUpdatePack.pBuffer[4]) = -2; + worldserver.SendPacket(&flagUpdatePack); + + Client *client = nullptr; + client = entity_list.GetClientByName(sep->arg[1]); + if (client) { + client->WorldKick(); + return; + } + + ServerPacket kickPlayerPack(ServerOP_KickPlayer, sizeof(ServerKickPlayer_Struct)); + ServerKickPlayer_Struct *skp = (ServerKickPlayer_Struct *) kickPlayerPack.pBuffer; + strcpy(skp->adminname, c->GetName()); + strcpy(skp->name, sep->arg[1]); + skp->adminrank = c->Admin(); + worldserver.SendPacket(&kickPlayerPack); +} + diff --git a/zone/gm_commands/beard.cpp b/zone/gm_commands/beard.cpp new file mode 100755 index 000000000..b43b7b9d4 --- /dev/null +++ b/zone/gm_commands/beard.cpp @@ -0,0 +1,37 @@ +#include "../client.h" + +void command_beard(Client *c, const Seperator *sep) +{ + Mob *target = c->GetTarget(); + if (!sep->IsNumber(1)) { + c->Message(Chat::White, "Usage: #beard [number of beard style]"); + } + else if (!target) { + c->Message(Chat::White, "Error: this command requires a target"); + } + else { + uint16 Race = target->GetRace(); + uint8 Gender = target->GetGender(); + uint8 Texture = 0xFF; + uint8 HelmTexture = 0xFF; + uint8 HairColor = target->GetHairColor(); + uint8 BeardColor = target->GetBeardColor(); + uint8 EyeColor1 = target->GetEyeColor1(); + uint8 EyeColor2 = target->GetEyeColor2(); + uint8 HairStyle = target->GetHairStyle(); + uint8 LuclinFace = target->GetLuclinFace(); + uint8 Beard = atoi(sep->arg[1]); + uint32 DrakkinHeritage = target->GetDrakkinHeritage(); + uint32 DrakkinTattoo = target->GetDrakkinTattoo(); + uint32 DrakkinDetails = target->GetDrakkinDetails(); + + target->SendIllusionPacket( + Race, Gender, Texture, HelmTexture, HairColor, BeardColor, + EyeColor1, EyeColor2, HairStyle, LuclinFace, Beard, 0xFF, + DrakkinHeritage, DrakkinTattoo, DrakkinDetails + ); + + c->Message(Chat::White, "Beard = %i", atoi(sep->arg[1])); + } +} + diff --git a/zone/gm_commands/beardcolor.cpp b/zone/gm_commands/beardcolor.cpp new file mode 100755 index 000000000..85eccf390 --- /dev/null +++ b/zone/gm_commands/beardcolor.cpp @@ -0,0 +1,37 @@ +#include "../client.h" + +void command_beardcolor(Client *c, const Seperator *sep) +{ + Mob *target = c->GetTarget(); + if (!sep->IsNumber(1)) { + c->Message(Chat::White, "Usage: #beardcolor [number of beard color]"); + } + else if (!target) { + c->Message(Chat::White, "Error: this command requires a target"); + } + else { + uint16 Race = target->GetRace(); + uint8 Gender = target->GetGender(); + uint8 Texture = 0xFF; + uint8 HelmTexture = 0xFF; + uint8 HairColor = target->GetHairColor(); + uint8 BeardColor = atoi(sep->arg[1]); + uint8 EyeColor1 = target->GetEyeColor1(); + uint8 EyeColor2 = target->GetEyeColor2(); + uint8 HairStyle = target->GetHairStyle(); + uint8 LuclinFace = target->GetLuclinFace(); + uint8 Beard = target->GetBeard(); + uint32 DrakkinHeritage = target->GetDrakkinHeritage(); + uint32 DrakkinTattoo = target->GetDrakkinTattoo(); + uint32 DrakkinDetails = target->GetDrakkinDetails(); + + target->SendIllusionPacket( + Race, Gender, Texture, HelmTexture, HairColor, BeardColor, + EyeColor1, EyeColor2, HairStyle, LuclinFace, Beard, 0xFF, + DrakkinHeritage, DrakkinTattoo, DrakkinDetails + ); + + c->Message(Chat::White, "Beard Color = %i", atoi(sep->arg[1])); + } +} + diff --git a/zone/gm_commands/bestz.cpp b/zone/gm_commands/bestz.cpp new file mode 100755 index 000000000..809e63ec1 --- /dev/null +++ b/zone/gm_commands/bestz.cpp @@ -0,0 +1,89 @@ +#include "../client.h" +#include "../water_map.h" + +void command_bestz(Client *c, const Seperator *sep) +{ + if (zone->zonemap == nullptr) { + c->Message(Chat::White, "Map not loaded for this zone"); + } + else { + glm::vec3 me; + me.x = c->GetX(); + me.y = c->GetY(); + me.z = c->GetZ() + (c->GetSize() == 0.0 ? 6 : c->GetSize()) * HEAD_POSITION; + glm::vec3 hit; + glm::vec3 bme(me); + bme.z -= 500; + + float best_z = zone->zonemap->FindBestZ(me, &hit); + + if (best_z != BEST_Z_INVALID) { + c->Message(Chat::White, "Z is %.3f at (%.3f, %.3f).", best_z, me.x, me.y); + } + else { + c->Message(Chat::White, "Found no Z."); + } + } + + if (zone->watermap == nullptr) { + c->Message(Chat::White, "Water Region Map not loaded for this zone"); + } + else { + WaterRegionType RegionType; + float z; + + if (c->GetTarget()) { + z = c->GetTarget()->GetZ(); + auto position = glm::vec3(c->GetTarget()->GetX(), c->GetTarget()->GetY(), z); + RegionType = zone->watermap->ReturnRegionType(position); + c->Message(Chat::White, "InWater returns %d", zone->watermap->InWater(position)); + c->Message(Chat::White, "InLava returns %d", zone->watermap->InLava(position)); + + } + else { + z = c->GetZ(); + auto position = glm::vec3(c->GetX(), c->GetY(), z); + RegionType = zone->watermap->ReturnRegionType(position); + c->Message(Chat::White, "InWater returns %d", zone->watermap->InWater(position)); + c->Message(Chat::White, "InLava returns %d", zone->watermap->InLava(position)); + + } + + switch (RegionType) { + case RegionTypeNormal: { + c->Message(Chat::White, "There is nothing special about the region you are in!"); + break; + } + case RegionTypeWater: { + c->Message(Chat::White, "You/your target are in Water."); + break; + } + case RegionTypeLava: { + c->Message(Chat::White, "You/your target are in Lava."); + break; + } + case RegionTypeVWater: { + c->Message(Chat::White, "You/your target are in VWater (Icy Water?)."); + break; + } + case RegionTypePVP: { + c->Message(Chat::White, "You/your target are in a pvp enabled area."); + break; + } + case RegionTypeSlime: { + c->Message(Chat::White, "You/your target are in slime."); + break; + } + case RegionTypeIce: { + c->Message(Chat::White, "You/your target are in ice."); + break; + } + default: + c->Message(Chat::White, "You/your target are in an unknown region type."); + } + } + + +} + + diff --git a/zone/gm_commands/bind.cpp b/zone/gm_commands/bind.cpp new file mode 100755 index 000000000..f9a68502e --- /dev/null +++ b/zone/gm_commands/bind.cpp @@ -0,0 +1,56 @@ +#include "../client.h" + +void command_bind(Client *c, const Seperator *sep) +{ + Client *target = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); + } + + target->SetBindPoint(); + + bool in_persistent_instance = ( + zone->GetInstanceID() != 0 && + zone->IsInstancePersistent() + ); + + auto target_string = ( + c == target ? + "Yourself" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ); + + c->Message( + Chat::White, + fmt::format( + "Set Bind Point for {} | Zone: {} ({}) ID: {} {}", + target_string, + zone->GetLongName(), + zone->GetShortName(), + zone->GetZoneID(), + ( + in_persistent_instance ? + fmt::format( + " Instance ID: {}", + zone->GetInstanceID() + ) : + "" + ) + ).c_str() + ); + + c->Message( + Chat::White, + fmt::format( + "Set Bind Point for {} | XYZ: {:.2f}, {:.2f}, {:.2f}", + target_string, + target->GetX(), + target->GetY(), + target->GetZ() + ).c_str() + ); +} diff --git a/zone/gm_commands/camerashake.cpp b/zone/gm_commands/camerashake.cpp new file mode 100755 index 000000000..cb781a99a --- /dev/null +++ b/zone/gm_commands/camerashake.cpp @@ -0,0 +1,33 @@ +#include "../client.h" +#include "../worldserver.h" + +extern WorldServer worldserver; + +void command_camerashake(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments || !sep->IsNumber(1) || !sep->IsNumber(2)) { + c->Message(Chat::Red, "Usage: #camerashake [Duration (Milliseconds)] [Intensity (1-10)]"); + return; + } + + auto duration = std::stoi(sep->arg[1]); + auto intensity = std::stoi(sep->arg[2]); + + auto pack = new ServerPacket(ServerOP_CameraShake, sizeof(ServerCameraShake_Struct)); + ServerCameraShake_Struct *camera_shake = (ServerCameraShake_Struct *) pack->pBuffer; + camera_shake->duration = duration; + camera_shake->intensity = intensity; + worldserver.SendPacket(pack); + c->Message( + Chat::White, + fmt::format( + "Sending camera shake to world with a duration of {} ({}) and an intensity of {}.", + ConvertMillisecondsToTime(duration), + duration, + intensity + ).c_str() + ); + safe_delete(pack); +} + diff --git a/zone/gm_commands/castspell.cpp b/zone/gm_commands/castspell.cpp new file mode 100755 index 000000000..68e2e1801 --- /dev/null +++ b/zone/gm_commands/castspell.cpp @@ -0,0 +1,72 @@ +#include "../client.h" + +void command_castspell(Client *c, const Seperator *sep) +{ + if (SPDAT_RECORDS <= 0) { + c->Message(Chat::White, "Spells not loaded."); + return; + } + + Mob *target = c; + if (c->GetTarget()) { + target = c->GetTarget(); + } + + if (!sep->IsNumber(1)) { + c->Message( + Chat::White, + "Usage: #castspell [Spell ID] [Instant (0 = False, 1 = True, Default is 1 if Unused)]" + ); + } + else { + uint16 spell_id = std::stoul(sep->arg[1]); + + if (CastRestrictedSpell(spell_id) && c->Admin() < commandCastSpecials) { + c->Message(Chat::Red, "Unable to cast spell."); + } + else if (spell_id >= SPDAT_RECORDS) { + c->Message(Chat::White, "Invalid Spell ID."); + } + else { + bool instant_cast = (c->Admin() >= commandInstacast ? true : false); + if (instant_cast && sep->IsNumber(2)) { + instant_cast = std::stoi(sep->arg[2]) ? true : false; + c->Message(Chat::White, fmt::format("{}", std::stoi(sep->arg[2])).c_str()); + } + + if (c->Admin() >= commandInstacast && instant_cast) { + c->SpellFinished( + spell_id, + target, + EQ::spells::CastingSlot::Item, + 0, + -1, + spells[spell_id].resist_difficulty + ); + } + else { + c->CastSpell(spell_id, target->GetID(), EQ::spells::CastingSlot::Item, spells[spell_id].cast_time); + } + + c->Message( + Chat::White, + fmt::format( + "Cast {} ({}) on {}{}.", + GetSpellName(spell_id), + spell_id, + ( + c == target ? + "yourself" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ), + instant_cast ? " instantly" : "" + ).c_str() + ); + } + } +} + diff --git a/zone/gm_commands/chat.cpp b/zone/gm_commands/chat.cpp new file mode 100755 index 000000000..4b511ec84 --- /dev/null +++ b/zone/gm_commands/chat.cpp @@ -0,0 +1,15 @@ +#include "../client.h" +#include "../worldserver.h" + +extern WorldServer worldserver; + +void command_chat(Client *c, const Seperator *sep) +{ + if (sep->arg[2][0] == 0) { + c->Message(Chat::White, "Usage: #chat [channum] [message]"); + } + else if (!worldserver.SendChannelMessage(0, 0, (uint8) atoi(sep->arg[1]), 0, 0, 100, sep->argplus[2])) { + c->Message(Chat::White, "Error: World server disconnected"); + } +} + diff --git a/zone/gm_commands/checklos.cpp b/zone/gm_commands/checklos.cpp new file mode 100755 index 000000000..fa54a56ef --- /dev/null +++ b/zone/gm_commands/checklos.cpp @@ -0,0 +1,20 @@ +#include "../client.h" + +void command_checklos(Client *c, const Seperator *sep) +{ + if (!c->GetTarget()) { + c->Message(Chat::White, "You must have a target to use this command."); + } + + bool has_los = c->CheckLosFN(c->GetTarget()); + c->Message( + Chat::White, + fmt::format( + "You {}have line of sight to {} ({}).", + has_los ? "" : "do not ", + c->GetTarget()->GetCleanName(), + c->GetTarget()->GetID() + ).c_str() + ); +} + diff --git a/zone/gm_commands/copycharacter.cpp b/zone/gm_commands/copycharacter.cpp new file mode 100755 index 000000000..136093005 --- /dev/null +++ b/zone/gm_commands/copycharacter.cpp @@ -0,0 +1,34 @@ +#include "../client.h" + +void command_copycharacter(Client *c, const Seperator *sep) +{ + if (sep->argnum < 3) { + c->Message( + Chat::White, + "Usage: [source_character_name] [destination_character_name] [destination_account_name]" + ); + return; + } + + std::string source_character_name = sep->arg[1]; + std::string destination_character_name = sep->arg[2]; + std::string destination_account_name = sep->arg[3]; + + bool result = database.CopyCharacter( + source_character_name, + destination_character_name, + destination_account_name + ); + + c->Message( + Chat::Yellow, + fmt::format( + "Character Copy [{}] to [{}] via account [{}] [{}]", + source_character_name, + destination_character_name, + destination_account_name, + result ? "Success" : "Failed" + ).c_str() + ); +} + diff --git a/zone/gm_commands/corpse.cpp b/zone/gm_commands/corpse.cpp new file mode 100755 index 000000000..43c760fc4 --- /dev/null +++ b/zone/gm_commands/corpse.cpp @@ -0,0 +1,356 @@ +#include "../client.h" +#include "../corpse.h" + +void command_corpse(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments) { + c->Message(Chat::White, "Usage: #corpse delete - Delete targeted corpse"); + c->Message(Chat::White, "Usage: #corpse deletenpccorpses - Deletes all NPC corpses"); + c->Message(Chat::White, "Usage: #corpse inspectloot - Inspects the loot on a corpse"); + c->Message(Chat::White, "Usage: #corpse listnpc - Lists all NPC corpses"); + c->Message(Chat::White, "Usage: #corpse lock - Locks the corpse, only GMs can loot the corpse when it is locked"); + c->Message(Chat::White, "Usage: #corpse removecash - Removes the cash from a corpse"); + c->Message(Chat::White, "Usage: #corpse unlock - Unlocks the corpses, allowing non-GMs to loot the corpse"); + if (c->Admin() >= commandEditPlayerCorpses) { + c->Message(Chat::White, "Usage: #corpse charid [Character ID] - Change player corpse's owner"); + c->Message(Chat::White, "Usage: #corpse deleteplayercorpses - Deletes all player corpses"); + c->Message(Chat::White, "Usage: #corpse depop [Bury] - Depops single target corpse."); + c->Message(Chat::White, "Usage: #corpse depopall [Bury] - Depops all target player's corpses."); + c->Message(Chat::White, "Usage: #corpse listplayer - Lists all player corpses"); + c->Message(Chat::White, "Usage: #corpse moveallgraveyard - Moves all player corpses to the current zone's graveyard or non-instance"); + c->Message(Chat::White, "Note: Set bury to 0 to skip burying the corpses."); + } + return; + } + + Mob *target = c->GetTarget(); + bool is_character_id = !strcasecmp(sep->arg[1], "charid"); + bool is_delete = !strcasecmp(sep->arg[1], "delete"); + bool is_delete_npc_corpses = !strcasecmp(sep->arg[1], "deletenpccorpses"); + bool is_delete_player_corpses = !strcasecmp(sep->arg[1], "deleteplayercorpses"); + bool is_depop = !strcasecmp(sep->arg[1], "depop"); + bool is_depop_all = !strcasecmp(sep->arg[1], "depopall"); + bool is_inspect_loot = !strcasecmp(sep->arg[1], "inspectloot"); + bool is_list_npc = !strcasecmp(sep->arg[1], "listnpc"); + bool is_list_player = !strcasecmp(sep->arg[1], "listplayer"); + bool is_lock = !strcasecmp(sep->arg[1], "lock"); + bool is_move_all_to_graveyard = !strcasecmp(sep->arg[1], "moveallgraveyard"); + bool is_remove_cash = !strcasecmp(sep->arg[1], "removecash"); + bool is_reset_looter = !strcasecmp(sep->arg[1], "resetlooter"); + bool is_unlock = !strcasecmp(sep->arg[1], "unlock"); + if ( + !is_character_id && + !is_delete && + !is_delete_npc_corpses && + !is_delete_player_corpses && + !is_depop && + !is_depop_all && + !is_inspect_loot && + !is_list_npc && + !is_list_player && + !is_lock && + !is_move_all_to_graveyard && + !is_remove_cash && + !is_reset_looter && + !is_unlock + ) { + c->Message(Chat::White, "Usage: #corpse delete - Delete targeted corpse"); + c->Message(Chat::White, "Usage: #corpse deletenpccorpses - Deletes all NPC corpses"); + c->Message(Chat::White, "Usage: #corpse inspectloot - Inspects the loot on a corpse"); + c->Message(Chat::White, "Usage: #corpse listnpc - Lists all NPC corpses"); + c->Message(Chat::White, "Usage: #corpse lock - Locks the corpse, only GMs can loot the corpse when it is locked"); + c->Message(Chat::White, "Usage: #corpse removecash - Removes the cash from a corpse"); + c->Message(Chat::White, "Usage: #corpse unlock - Unlocks the corpses, allowing non-GMs to loot the corpse"); + if (c->Admin() >= commandEditPlayerCorpses) { + c->Message(Chat::White, "Usage: #corpse charid [Character ID] - Change player corpse's owner"); + c->Message(Chat::White, "Usage: #corpse deleteplayercorpses - Deletes all player corpses"); + c->Message(Chat::White, "Usage: #corpse depop [Bury] - Depops single target corpse."); + c->Message(Chat::White, "Usage: #corpse depopall [Bury] - Depops all target player's corpses."); + c->Message(Chat::White, "Usage: #corpse listplayer - Lists all player corpses"); + c->Message(Chat::White, "Usage: #corpse moveallgraveyard - Moves all player corpses to the current zone's graveyard or non-instance"); + c->Message(Chat::White, "Note: Set bury to 0 to skip burying the corpses."); + } + return; + } + + + if (is_delete_player_corpses) { + if (c->Admin() >= commandEditPlayerCorpses) { + auto corpses_deleted = entity_list.DeletePlayerCorpses(); + auto deleted_string = ( + corpses_deleted ? + fmt::format( + "{} Player corpse{} deleted.", + corpses_deleted, + corpses_deleted != 1 ? "s" : "" + ) : + "There are no player corpses to delete." + ); + c->Message(Chat::White, deleted_string.c_str()); + } else { + c->Message(Chat::White, "Your status is not high enough to delete player corpses."); + return; + } + } else if (is_delete) { + if (!target || !target->IsCorpse()) { + c->Message(Chat::White, "You must target a corpse to use this command."); + return; + } + + if (target->IsPlayerCorpse() && c->Admin() < commandEditPlayerCorpses) { + c->Message(Chat::White, "Your status is not high enough to delete a player corpse."); + return; + } + + if ( + target->IsNPCCorpse() || + c->Admin() >= commandEditPlayerCorpses + ) { + c->Message( + Chat::White, + fmt::format( + "Deleting {} corpse {} ({}).", + target->IsNPCCorpse() ? "NPC" : "player", + target->GetName(), + target->GetID() + ).c_str() + ); + target->CastToCorpse()->Delete(); + } + } else if (is_list_npc) { + entity_list.ListNPCCorpses(c); + } else if (is_list_player) { + if (c->Admin() < commandEditPlayerCorpses) { + c->Message(Chat::White, "Your status is not high enough to list player corpses."); + return; + } + + entity_list.ListPlayerCorpses(c); + } else if (is_delete_npc_corpses) { + auto corpses_deleted = entity_list.DeleteNPCCorpses(); + auto deleted_string = ( + corpses_deleted ? + fmt::format( + "{} NPC corpse{} deleted.", + corpses_deleted, + corpses_deleted != 1 ? "s" : "" + ) : + "There are no NPC corpses to delete." + ); + c->Message(Chat::White, deleted_string.c_str()); + } else if (is_character_id) { + if (c->Admin() >= commandEditPlayerCorpses) { + if (!target || !target->IsPlayerCorpse()) { + c->Message(Chat::White, "You must target a player corpse to use this command."); + return; + } + + if (!sep->IsNumber(2)) { + c->Message(Chat::White, "Usage: #corpse charid [Character ID] - Change player corpse's owner"); + return; + } + + auto character_id = std::stoi(sep->arg[2]); + c->Message( + Chat::White, + fmt::format( + "Setting the owner to {} ({}) for the player corpse {} ({}).", + database.GetCharNameByID(character_id), + target->CastToCorpse()->SetCharID(character_id), + target->GetName(), + target->GetID() + ).c_str() + ); + } else { + c->Message(Chat::White, "Your status is not high enough to modify a player corpse's owner."); + return; + } + } else if (is_reset_looter) { + if (!target || !target->IsCorpse()) { + c->Message(Chat::White, "You must target a corpse to use this command."); + return; + } + + if (target->IsPlayerCorpse() && c->Admin() < commandEditPlayerCorpses) { + c->Message(Chat::White, "Your status is not high enough to reset looter on a player corpse."); + return; + } + + target->CastToCorpse()->ResetLooter(); + c->Message( + Chat::White, + fmt::format( + "Reset looter for {} corpse {} ({}).", + target->IsNPCCorpse() ? "NPC" : "player", + target->GetName(), + target->GetID() + ).c_str() + ); + } else if (is_remove_cash) { + if (!target || !target->IsCorpse()) { + c->Message(Chat::White, "You must target a corpse to use this command."); + return; + } + + if (target->IsPlayerCorpse() && c->Admin() < commandEditPlayerCorpses) { + c->Message(Chat::White, "Your status is not high enough to remove cash from a player corpse."); + return; + } + + if ( + target->IsNPCCorpse() || + c->Admin() >= commandEditPlayerCorpses + ) { + target->CastToCorpse()->RemoveCash(); + c->Message( + Chat::White, + fmt::format( + "Removed cash from {} corpse {} ({}).", + target->IsNPCCorpse() ? "NPC" : "player", + target->GetName(), + target->GetID() + ).c_str() + ); + } + } else if (is_inspect_loot) { + if (!target || !target->IsCorpse()) { + c->Message(Chat::White, "You must target a corpse to use this command."); + return; + } + + if (target->IsPlayerCorpse() && c->Admin() < commandEditPlayerCorpses) { + c->Message(Chat::White, "Your status is not high enough to inspect the loot of a player corpse."); + return; + } + + target->CastToCorpse()->QueryLoot(c); + } else if (is_lock) { + if (!target || !target->IsCorpse()) { + c->Message(Chat::White, "You must target a corpse to use this command."); + return; + } + + if (target->IsPlayerCorpse() && c->Admin() < commandEditPlayerCorpses) { + c->Message(Chat::White, "Your status is not high enough to lock player corpses."); + return; + } + + target->CastToCorpse()->Lock(); + c->Message( + Chat::White, + fmt::format( + "Locking {} corpse {} ({}).", + target->IsNPCCorpse() ? "NPC" : "player", + target->GetName(), + target->GetID() + ).c_str() + ); + } else if (is_unlock) { + if (!target || !target->IsCorpse()) { + c->Message(Chat::White, "You must target a corpse to use this command."); + return; + } + + if (target->IsPlayerCorpse() && c->Admin() < commandEditPlayerCorpses) { + c->Message(Chat::White, "Your status is not high enough to unlock player corpses."); + return; + } + + target->CastToCorpse()->UnLock(); + c->Message( + Chat::White, + fmt::format( + "Unlocking {} corpse {} ({}).", + target->IsNPCCorpse() ? "NPC" : "player", + target->GetName(), + target->GetID() + ).c_str() + ); + } else if (is_depop) { + if (!target || !target->IsPlayerCorpse()) { + c->Message(Chat::White, "You must target a player corpse to use this command."); + return; + } + + if (c->Admin() >= commandEditPlayerCorpses) { + bool bury_corpse = ( + sep->IsNumber(2) ? + ( + std::stoi(sep->arg[2]) != 0 ? + true : + false + ) : + false + ); + c->Message( + Chat::White, + fmt::format( + "Depopping player corpse {} ({}).", + target->GetName(), + target->GetID() + ).c_str() + ); + target->CastToCorpse()->DepopPlayerCorpse(); + if (bury_corpse) { + target->CastToCorpse()->Bury(); + } + } else { + c->Message(Chat::White, "Your status is not high enough to depop a player corpse."); + return; + } + } else if (is_depop_all) { + if (!target || !target->IsClient()) { + c->Message(Chat::White, "You must target a player to use this command."); + return; + } + + if (c->Admin() >= commandEditPlayerCorpses) { + bool bury_corpse = ( + sep->IsNumber(2) ? + ( + std::stoi(sep->arg[2]) != 0 ? + true : + false + ) : + false + ); + c->Message( + Chat::White, + fmt::format( + "Depopping all player corpses for {} ({}).", + target->GetName(), + target->GetID() + ).c_str() + ); + target->CastToClient()->DepopAllCorpses(); + if (bury_corpse) { + target->CastToClient()->BuryPlayerCorpses(); + } + } else { + c->Message(Chat::White, "Your status is not high enough to depop all of a player's corpses."); + return; + } + } else if (is_move_all_to_graveyard) { + int moved_count = entity_list.MovePlayerCorpsesToGraveyard(true); + if (c->Admin() >= commandEditPlayerCorpses) { + if (moved_count) { + c->Message( + Chat::White, + fmt::format( + "Moved {} player corpse{} to graveyard in {} ({}).", + moved_count, + moved_count != 1 ? "s" : "", + ZoneLongName(zone->GetZoneID()), + ZoneName(zone->GetZoneID()) + ).c_str() + ); + } else { + c->Message(Chat::White, "There are no player corpses to move to the graveyard."); + } + } else { + c->Message(Chat::White, "Your status is not high enough to move all player corpses to the graveyard."); + } + } +} + diff --git a/zone/gm_commands/corpsefix.cpp b/zone/gm_commands/corpsefix.cpp new file mode 100755 index 000000000..0de35a8d3 --- /dev/null +++ b/zone/gm_commands/corpsefix.cpp @@ -0,0 +1,8 @@ +#include "../client.h" +#include "../corpse.h" + +void command_corpsefix(Client *c, const Seperator *sep) +{ + entity_list.CorpseFix(c); +} + diff --git a/zone/gm_commands/countitem.cpp b/zone/gm_commands/countitem.cpp new file mode 100644 index 000000000..01e507081 --- /dev/null +++ b/zone/gm_commands/countitem.cpp @@ -0,0 +1,64 @@ +#include "../client.h" + +void command_countitem(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments || !sep->IsNumber(1)) { + c->Message(Chat::White, "Usage: #countitem [Item ID]"); + return; + } + + Mob* target = c; + if ( + c->GetTarget() && + ( + c->GetTarget()->IsClient() || + c->GetTarget()->IsNPC() + ) + ) { + target = c->GetTarget(); + } + + auto item_id = std::stoul(sep->arg[1]); + if (!database.GetItem(item_id)) { + c->Message( + Chat::White, + fmt::format( + "Item ID {} could not be found.", + item_id + ).c_str() + ); + return; + } + + uint16 item_count = 0; + if (target->IsClient()) { + item_count = target->CastToClient()->CountItem(item_id); + } else if (target->IsNPC()) { + item_count = target->CastToNPC()->CountItem(item_id); + } + + c->Message( + Chat::White, + fmt::format( + "{} {} {} {}.", + ( + c == target ? + "You" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ), + c == target ? "have" : "has", + ( + item_count ? + std::to_string(item_count) : + "no" + ), + database.CreateItemLink(item_id) + ).c_str() + ); +} + diff --git a/zone/gm_commands/cvs.cpp b/zone/gm_commands/cvs.cpp new file mode 100755 index 000000000..1632c8663 --- /dev/null +++ b/zone/gm_commands/cvs.cpp @@ -0,0 +1,17 @@ +#include "../client.h" +#include "../worldserver.h" + +extern WorldServer worldserver; + +void command_cvs(Client *c, const Seperator *sep) +{ + auto pack = new ServerPacket( + ServerOP_ClientVersionSummary, + sizeof(ServerRequestClientVersionSummary_Struct) + ); + auto srcvss = (ServerRequestClientVersionSummary_Struct *) pack->pBuffer; + strn0cpy(srcvss->Name, c->GetName(), sizeof(srcvss->Name)); + worldserver.SendPacket(pack); + safe_delete(pack); +} + diff --git a/zone/gm_commands/damage.cpp b/zone/gm_commands/damage.cpp new file mode 100755 index 000000000..e2262bfaa --- /dev/null +++ b/zone/gm_commands/damage.cpp @@ -0,0 +1,18 @@ +#include "../client.h" + +void command_damage(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments || !sep->IsNumber(1)) { + c->Message(Chat::White, "Usage: #damage [Amount]"); + return; + } + + Mob* target = c; + if (c->GetTarget()) { + target = c->GetTarget(); + } + + int damage = static_cast(std::min(std::stoll(sep->arg[1]), (long long) 2000000000)); + target->Damage(c, damage, SPELL_UNKNOWN, EQ::skills::SkillHandtoHand, false); +} diff --git a/zone/gm_commands/databuckets.cpp b/zone/gm_commands/databuckets.cpp new file mode 100755 index 000000000..82df39342 --- /dev/null +++ b/zone/gm_commands/databuckets.cpp @@ -0,0 +1,92 @@ +#include "../client.h" +#include "../data_bucket.h" + +void command_databuckets(Client *c, const Seperator *sep) +{ + if (sep->arg[1][0] == 0) { + c->Message(Chat::Yellow, "Usage: #databuckets view (partial key)|(limit) OR #databuckets delete (key)"); + return; + } + if (strcasecmp(sep->arg[1], "view") == 0) { + + std::string key_filter; + uint8 limit = 50; + for (int i = 2; i < 4; i++) { + if (sep->arg[i][0] == '\0') { + break; + } + if (strcasecmp(sep->arg[i], "limit") == 0) { + limit = (uint8) atoi(sep->arg[i + 1]); + continue; + } + } + if (sep->arg[2]) { + key_filter = str_tolower(sep->arg[2]); + } + std::string query = "SELECT `id`, `key`, `value`, `expires` FROM data_buckets"; + if (!key_filter.empty()) { query += StringFormat(" WHERE `key` LIKE '%%%s%%'", key_filter.c_str()); } + query += StringFormat(" LIMIT %u", limit); + auto results = database.QueryDatabase(query); + if (!results.Success()) { + return; + } + if (results.RowCount() == 0) { + c->Message(Chat::Yellow, "No data_buckets found"); + return; + } + int _ctr = 0; + // put in window for easier readability in case want command line for something else + std::string window_title = "Data Buckets"; + std::string window_text = + "" + "" + "" + "" + "" + "" + ""; + for (auto row = results.begin(); row != results.end(); ++row) { + auto id = static_cast(atoi(row[0])); + std::string key = row[1]; + std::string value = row[2]; + std::string expires = row[3]; + window_text.append( + StringFormat( + "" + "" + "" + "" + "" + "", + id, + expires.c_str(), + key.c_str(), + value.c_str() + )); + _ctr++; + std::string del_saylink = StringFormat("#databuckets delete %s", key.c_str()); + c->Message( + Chat::White, + "%s : %s", + EQ::SayLinkEngine::GenerateQuestSaylink(del_saylink, false, "Delete").c_str(), + key.c_str(), + " Value: ", + value.c_str()); + } + window_text.append("
IDExpiresKeyValue
%u%s%s%s
"); + c->SendPopupToClient(window_title.c_str(), window_text.c_str()); + std::string response = _ctr > 0 ? StringFormat("Found %i matching data buckets", _ctr).c_str() + : "No Databuckets found."; + c->Message(Chat::Yellow, response.c_str()); + } + else if (strcasecmp(sep->arg[1], "delete") == 0) { + if (DataBucket::DeleteData(sep->argplus[2])) { + c->Message(Chat::Yellow, "data bucket %s deleted.", sep->argplus[2]); + } + else { + c->Message(Chat::Red, "An error occurred deleting data bucket %s", sep->argplus[2]); + } + return; + } +} + diff --git a/zone/gm_commands/date.cpp b/zone/gm_commands/date.cpp new file mode 100755 index 000000000..3e8aa2455 --- /dev/null +++ b/zone/gm_commands/date.cpp @@ -0,0 +1,29 @@ +#include "../client.h" + +void command_date(Client *c, const Seperator *sep) +{ + //yyyy mm dd hh mm local + if (sep->arg[3][0] == 0 || !sep->IsNumber(1) || !sep->IsNumber(2) || !sep->IsNumber(3)) { + c->Message(Chat::Red, "Usage: #date yyyy mm dd [HH MM]"); + } + else { + int h = 0, m = 0; + TimeOfDay_Struct eqTime; + zone->zone_time.GetCurrentEQTimeOfDay(time(0), &eqTime); + if (!sep->IsNumber(4)) { + h = eqTime.hour; + } + else { + h = atoi(sep->arg[4]); + } + if (!sep->IsNumber(5)) { + m = eqTime.minute; + } + else { + m = atoi(sep->arg[5]); + } + c->Message(Chat::Red, "Setting world time to %s-%s-%s %i:%i...", sep->arg[1], sep->arg[2], sep->arg[3], h, m); + zone->SetDate(atoi(sep->arg[1]), atoi(sep->arg[2]), atoi(sep->arg[3]), h, m); + } +} + diff --git a/zone/gm_commands/dbspawn2.cpp b/zone/gm_commands/dbspawn2.cpp new file mode 100755 index 000000000..2f31e2f19 --- /dev/null +++ b/zone/gm_commands/dbspawn2.cpp @@ -0,0 +1,31 @@ +#include "../client.h" + +void command_dbspawn2(Client *c, const Seperator *sep) +{ + + if (sep->IsNumber(1) && sep->IsNumber(2) && sep->IsNumber(3)) { + LogInfo("Spawning database spawn"); + uint16 cond = 0; + int16 cond_min = 0; + if (sep->IsNumber(4)) { + cond = atoi(sep->arg[4]); + if (sep->IsNumber(5)) { + cond_min = atoi(sep->arg[5]); + } + } + database.CreateSpawn2( + c, + atoi(sep->arg[1]), + zone->GetShortName(), + c->GetPosition(), + atoi(sep->arg[2]), + atoi(sep->arg[3]), + cond, + cond_min + ); + } + else { + c->Message(Chat::White, "Usage: #dbspawn2 spawngroup respawn variance [condition_id] [condition_min]"); + } +} + diff --git a/zone/gm_commands/delacct.cpp b/zone/gm_commands/delacct.cpp new file mode 100755 index 000000000..959fe7404 --- /dev/null +++ b/zone/gm_commands/delacct.cpp @@ -0,0 +1,21 @@ +#include "../client.h" + +void command_delacct(Client *c, const Seperator *sep) +{ + if (sep->arg[1][0] == 0) { + c->Message(Chat::White, "Format: #delacct accountname"); + } + else { + std::string user; + std::string loginserver; + ParseAccountString(sep->arg[1], user, loginserver); + + if (database.DeleteAccount(user.c_str(), loginserver.c_str())) { + c->Message(Chat::White, "The account was deleted."); + } + else { + c->Message(Chat::White, "Unable to delete account."); + } + } +} + diff --git a/zone/gm_commands/deletegraveyard.cpp b/zone/gm_commands/deletegraveyard.cpp new file mode 100755 index 000000000..43560eb6f --- /dev/null +++ b/zone/gm_commands/deletegraveyard.cpp @@ -0,0 +1,35 @@ +#include "../client.h" + +void command_deletegraveyard(Client *c, const Seperator *sep) +{ + uint32 zoneid = 0; + uint32 graveyard_id = 0; + + if (!sep->arg[1][0]) { + c->Message(Chat::White, "Usage: #deletegraveyard [zonename]"); + return; + } + + zoneid = ZoneID(sep->arg[1]); + graveyard_id = content_db.GetZoneGraveyardID(zoneid, 0); + + if (zoneid > 0 && graveyard_id > 0) { + if (content_db.DeleteGraveyard(zoneid, graveyard_id)) { + c->Message(Chat::White, "Successfuly deleted graveyard %u for zone %s.", graveyard_id, sep->arg[1]); + } + else { + c->Message(Chat::White, "Unable to delete graveyard %u for zone %s.", graveyard_id, sep->arg[1]); + } + } + else { + if (zoneid <= 0) { + c->Message(Chat::White, "Unable to retrieve a ZoneID for the zone: %s", sep->arg[1]); + } + else if (graveyard_id <= 0) { + c->Message(Chat::White, "Unable to retrieve a valid GraveyardID for the zone: %s", sep->arg[1]); + } + } + + return; +} + diff --git a/zone/gm_commands/delpetition.cpp b/zone/gm_commands/delpetition.cpp new file mode 100755 index 000000000..556e33866 --- /dev/null +++ b/zone/gm_commands/delpetition.cpp @@ -0,0 +1,20 @@ +#include "../client.h" + +void command_delpetition(Client *c, const Seperator *sep) +{ + if (sep->arg[1][0] == 0 || strcasecmp(sep->arg[1], "*") == 0) { + c->Message(Chat::White, "Usage: #delpetition (petition number) Type #listpetition for a list"); + return; + } + + c->Message(Chat::Red, "Attempting to delete petition number: %i", atoi(sep->argplus[1])); + std::string query = StringFormat("DELETE FROM petitions WHERE petid = %i", atoi(sep->argplus[1])); + auto results = database.QueryDatabase(query); + if (!results.Success()) { + return; + } + + LogInfo("Delete petition request from [{}], petition number:", c->GetName(), atoi(sep->argplus[1])); + +} + diff --git a/zone/gm_commands/depop.cpp b/zone/gm_commands/depop.cpp new file mode 100755 index 000000000..3a1693205 --- /dev/null +++ b/zone/gm_commands/depop.cpp @@ -0,0 +1,14 @@ +#include "../client.h" +#include "../corpse.h" + +void command_depop(Client *c, const Seperator *sep) +{ + if (c->GetTarget() == 0 || !(c->GetTarget()->IsNPC() || c->GetTarget()->IsNPCCorpse())) { + c->Message(Chat::White, "You must have a NPC target for this command. (maybe you meant #depopzone?)"); + } + else { + c->Message(Chat::White, "Depoping '%s'.", c->GetTarget()->GetName()); + c->GetTarget()->Depop(); + } +} + diff --git a/zone/gm_commands/depopzone.cpp b/zone/gm_commands/depopzone.cpp new file mode 100755 index 000000000..518ef42db --- /dev/null +++ b/zone/gm_commands/depopzone.cpp @@ -0,0 +1,8 @@ +#include "../client.h" + +void command_depopzone(Client *c, const Seperator *sep) +{ + zone->Depop(); + c->Message(Chat::White, "Zone depoped."); +} + diff --git a/zone/gm_commands/details.cpp b/zone/gm_commands/details.cpp new file mode 100755 index 000000000..f8b5dee8f --- /dev/null +++ b/zone/gm_commands/details.cpp @@ -0,0 +1,37 @@ +#include "../client.h" + +void command_details(Client *c, const Seperator *sep) +{ + Mob *target = c->GetTarget(); + if (!sep->IsNumber(1)) { + c->Message(Chat::White, "Usage: #details [number of drakkin detail]"); + } + else if (!target) { + c->Message(Chat::White, "Error: this command requires a target"); + } + else { + uint16 Race = target->GetRace(); + uint8 Gender = target->GetGender(); + uint8 Texture = 0xFF; + uint8 HelmTexture = 0xFF; + uint8 HairColor = target->GetHairColor(); + uint8 BeardColor = target->GetBeardColor(); + uint8 EyeColor1 = target->GetEyeColor1(); + uint8 EyeColor2 = target->GetEyeColor2(); + uint8 HairStyle = target->GetHairStyle(); + uint8 LuclinFace = target->GetLuclinFace(); + uint8 Beard = target->GetBeard(); + uint32 DrakkinHeritage = target->GetDrakkinHeritage(); + uint32 DrakkinTattoo = target->GetDrakkinTattoo(); + uint32 DrakkinDetails = atoi(sep->arg[1]); + + target->SendIllusionPacket( + Race, Gender, Texture, HelmTexture, HairColor, BeardColor, + EyeColor1, EyeColor2, HairStyle, LuclinFace, Beard, 0xFF, + DrakkinHeritage, DrakkinTattoo, DrakkinDetails + ); + + c->Message(Chat::White, "Details = %i", atoi(sep->arg[1])); + } +} + diff --git a/zone/gm_commands/devtools.cpp b/zone/gm_commands/devtools.cpp new file mode 100755 index 000000000..da46b4f77 --- /dev/null +++ b/zone/gm_commands/devtools.cpp @@ -0,0 +1,22 @@ +#include "../client.h" +#include "../data_bucket.h" + +void command_devtools(Client *c, const Seperator *sep) +{ + std::string dev_tools_key = StringFormat("%i-dev-tools-disabled", c->AccountID()); + + /** + * Handle window toggle + */ + if (strcasecmp(sep->arg[1], "disable") == 0) { + DataBucket::SetData(dev_tools_key, "true"); + c->SetDevToolsEnabled(false); + } + if (strcasecmp(sep->arg[1], "enable") == 0) { + DataBucket::DeleteData(dev_tools_key); + c->SetDevToolsEnabled(true); + } + + c->ShowDevToolsMenu(); +} + diff --git a/zone/gm_commands/disablerecipe.cpp b/zone/gm_commands/disablerecipe.cpp new file mode 100755 index 000000000..b09a6317c --- /dev/null +++ b/zone/gm_commands/disablerecipe.cpp @@ -0,0 +1,29 @@ +#include "../client.h" + +void command_disablerecipe(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments || !sep->IsNumber(1)) { + c->Message(Chat::White, "Usage: #disablerecipe [Recipe ID]"); + return; + } + + auto recipe_id = std::stoul(sep->arg[1]); + if (!recipe_id) { + c->Message(Chat::White, "Usage: #disablerecipe [Recipe ID]"); + return; + } + + c->Message( + Chat::White, + fmt::format( + "Recipe ID {} {} disabled.", + recipe_id, + ( + content_db.DisableRecipe(recipe_id) ? + "successfully" : + "failed to be" + ) + ).c_str() + ); +} diff --git a/zone/gm_commands/disarmtrap.cpp b/zone/gm_commands/disarmtrap.cpp new file mode 100755 index 000000000..84efe93cf --- /dev/null +++ b/zone/gm_commands/disarmtrap.cpp @@ -0,0 +1,25 @@ +#include "../client.h" + +void command_disarmtrap(Client *c, const Seperator *sep) +{ + Mob *target = c->GetTarget(); + + if (!target) { + c->Message(Chat::Red, "You must have a target."); + return; + } + + if (target->IsNPC()) { + if (c->HasSkill(EQ::skills::SkillDisarmTraps)) { + if (DistanceSquaredNoZ(c->GetPosition(), target->GetPosition()) > RuleI(Adventure, LDoNTrapDistanceUse)) { + c->Message(Chat::Red, "%s is too far away.", target->GetCleanName()); + return; + } + c->HandleLDoNDisarm(target->CastToNPC(), c->GetSkill(EQ::skills::SkillDisarmTraps), LDoNTypeMechanical); + } + else { + c->Message(Chat::Red, "You do not have the disarm trap skill."); + } + } +} + diff --git a/zone/gm_commands/distance.cpp b/zone/gm_commands/distance.cpp new file mode 100755 index 000000000..578f94bb5 --- /dev/null +++ b/zone/gm_commands/distance.cpp @@ -0,0 +1,23 @@ +#include "../client.h" + +void command_distance(Client *c, const Seperator *sep) +{ + if (c->GetTarget()) { + Mob *target = c->GetTarget(); + if (c != target) { + c->Message( + Chat::White, + fmt::format( + "{} ({}) is {:.2f} units from you.", + target->GetCleanName(), + target->GetID(), + Distance( + c->GetPosition(), + target->GetPosition() + ) + ).c_str() + ); + } + } +} + diff --git a/zone/gm_commands/doanim.cpp b/zone/gm_commands/doanim.cpp new file mode 100755 index 000000000..d48725a97 --- /dev/null +++ b/zone/gm_commands/doanim.cpp @@ -0,0 +1,20 @@ +#include "../client.h" + +void command_doanim(Client *c, const Seperator *sep) +{ + if (!sep->IsNumber(1)) { + c->Message(Chat::White, "Usage: #DoAnim [number]"); + } + else if (c->Admin() >= commandDoAnimOthers) { + if (c->GetTarget() == 0) { + c->Message(Chat::White, "Error: You need a target."); + } + else { + c->GetTarget()->DoAnim(atoi(sep->arg[1]), atoi(sep->arg[2])); + } + } + else { + c->DoAnim(atoi(sep->arg[1]), atoi(sep->arg[2])); + } +} + diff --git a/zone/gm_commands/door.cpp b/zone/gm_commands/door.cpp new file mode 100755 index 000000000..5a92e5f62 --- /dev/null +++ b/zone/gm_commands/door.cpp @@ -0,0 +1,9 @@ +#include "../client.h" +#include "door_manipulation.h" +#include "../doors.h" + +void command_door(Client *c, const Seperator *sep) +{ + DoorManipulation::CommandHandler(c, sep); +} + diff --git a/zone/gm_commands/door_manipulation.cpp b/zone/gm_commands/door_manipulation.cpp new file mode 100644 index 000000000..a724fb61f --- /dev/null +++ b/zone/gm_commands/door_manipulation.cpp @@ -0,0 +1,786 @@ +#include "door_manipulation.h" +#include "../doors.h" +#include "../../common/misc_functions.h" + +#define MAX_CLIENT_MESSAGE_LENGTH 2000 + +void DoorManipulation::CommandHandler(Client *c, const Seperator *sep) +{ + // this should never happen + if (!c) { + return; + } + + // args + std::string arg1(sep->arg[1]); + std::string arg2(sep->arg[2]); + std::string arg3(sep->arg[3]); + + // table check + std::string table_name = "tool_game_objects"; + std::string url = "https://raw.githubusercontent.com/EQEmu/database-tool-sqls/main/tool_game_objects.sql"; + if (!database.DoesTableExist(table_name)) { + c->Message( + Chat::White, + fmt::format( + "Table [{}] does not exist. Downloading from [{}] and installing locally", + table_name, + url + ).c_str() + ); + database.SourceDatabaseTableFromUrl( + table_name, + url + ); + } + + // option + if (arg1.empty()) { + DoorManipulation::CommandHeader(c); + c->Message( + Chat::White, + "#door create | Creates a door from a model. (Example IT78 creates a campfire)" + ); + c->Message(Chat::White, "#door setinvertstate [0|1] | Sets selected door invert state"); + c->Message(Chat::White, "#door setincline | Sets selected door incline"); + c->Message(Chat::White, "#door opentype | Sets selected door opentype"); + c->Message( + Chat::White, + fmt::format( + "#door model | Changes door model for selected door or select from [{}] or [{}]", + EQ::SayLinkEngine::GenerateQuestSaylink("#door showmodelszone", false, "local zone"), + EQ::SayLinkEngine::GenerateQuestSaylink("#door showmodelsglobal", false, "global") + ).c_str() + ); + c->Message( + Chat::White, + "#door showmodelsfromfile | Shows models from s3d or eqg file. Example tssequip.eqg or wallet01.eqg" + ); + + c->Message( + Chat::White, + fmt::format( + "{} | Shows available models in the current zone that you are in", + EQ::SayLinkEngine::GenerateQuestSaylink("#door showmodelszone", false, "#door showmodelszone") + ).c_str() + ); + + c->Message( + Chat::White, + fmt::format( + "{} | Shows available models globally by first listing all global model files", + EQ::SayLinkEngine::GenerateQuestSaylink("#door showmodelsglobal", false, "#door showmodelsglobal") + ).c_str() + ); + + c->Message(Chat::White, "#door save | Creates database entry for selected door"); + c->Message( + Chat::White, + fmt::format( + "{} - Brings up editing interface for selected door", + EQ::SayLinkEngine::GenerateQuestSaylink("#door edit", false, "#door edit") + ).c_str() + ); + c->Message( + Chat::White, + fmt::format( + "{} - lists doors in zone", + EQ::SayLinkEngine::GenerateQuestSaylink("#list doors", false, "#list doors") + ).c_str() + ); + + return; + } + + // edit menu + if (arg1 == "edit") { + Doors *door = entity_list.GetDoorsByID(c->GetDoorToolEntityId()); + if (door) { + c->Message( + Chat::White, + fmt::format( + "Door Selected ID [{}] Name [{}] OpenType [{}] Invertstate [{} | {}/{}] ", + c->GetDoorToolEntityId(), + door->GetDoorName(), + door->GetOpenType(), + door->GetInvertState(), + EQ::SayLinkEngine::GenerateQuestSaylink("#door setinvertstate 0", false, "0"), + EQ::SayLinkEngine::GenerateQuestSaylink("#door setinvertstate 1", false, "1") + ).c_str() + ); + + const std::string move_x_action = "move_x"; + const std::string move_y_action = "move_y"; + const std::string move_z_action = "move_z"; + const std::string move_h_action = "move_h"; + const std::string set_size_action = "set_size"; + + std::vector move_options = { + move_x_action, + move_y_action, + move_z_action, + move_h_action, + set_size_action + }; + std::vector move_x_options_positive; + std::vector move_x_options_negative; + std::vector move_y_options_positive; + std::vector move_y_options_negative; + std::vector move_z_options_positive; + std::vector move_z_options_negative; + std::vector move_h_options_positive; + std::vector move_h_options_negative; + std::vector set_size_options_positive; + std::vector set_size_options_negative; + for (const auto &move_option : move_options) { + if (move_option == move_x_action) { + move_x_options_positive.emplace_back( + EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format("#door edit {} .25", move_option), + false, + ".25" + ) + ); + + for (int move_index = 0; move_index <= 15; move_index += 5) { + int value = (move_index == 0 ? 1 : move_index); + move_x_options_positive.emplace_back( + EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format("#door edit {} {}", move_option, value), + false, + fmt::format("{}", std::abs(value)) + ) + ); + } + + for (int move_index = -15; move_index <= 0; move_index += 5) { + int value = (move_index == 0 ? 1 : move_index); + move_x_options_negative.emplace_back( + EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format("#door edit {} {}", move_option, value), + false, + fmt::format("{}", std::abs(value)) + ) + ); + } + + move_x_options_negative.emplace_back( + EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format("#door edit {} -.25", move_option), + false, + ".25" + ) + ); + } + else if (move_option == move_y_action) { + move_y_options_positive.emplace_back( + EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format("#door edit {} .25", move_option), + false, + ".25" + ) + ); + + for (int move_index = 0; move_index <= 15; move_index += 5) { + int value = (move_index == 0 ? 1 : move_index); + move_y_options_positive.emplace_back( + EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format("#door edit {} {}", move_option, value), + false, + fmt::format("{}", std::abs(value)) + ) + ); + } + + for (int move_index = -15; move_index <= 0; move_index += 5) { + int value = (move_index == 0 ? -1 : move_index); + move_y_options_negative.emplace_back( + EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format("#door edit {} {}", move_option, value), + false, + fmt::format("{}", std::abs(value)) + ) + ); + } + + move_y_options_negative.emplace_back( + EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format("#door edit {} -.25", move_option), + false, + ".25" + ) + ); + } + else if (move_option == move_z_action) { + move_z_options_positive.emplace_back( + EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format("#door edit {} .25", move_option), + false, + ".25" + ) + ); + + for (int move_index = 0; move_index <= 15; move_index += 5) { + int value = (move_index == 0 ? 1 : move_index); + move_z_options_positive.emplace_back( + EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format("#door edit {} {}", move_option, value), + false, + fmt::format("{}", std::abs(value)) + ) + ); + } + + for (int move_index = -15; move_index <= 0; move_index += 5) { + int value = (move_index == 0 ? -1 : move_index); + move_z_options_negative.emplace_back( + EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format("#door edit {} {}", move_option, value), + false, + fmt::format("{}", std::abs(value)) + ) + ); + } + + move_z_options_negative.emplace_back( + EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format("#door edit {} -.25", move_option), + false, + ".25" + ) + ); + } + else if (move_option == move_h_action) { + for (int move_index = 0; move_index <= 50; move_index += 5) { + int value = (move_index == 0 ? 1 : move_index); + move_h_options_positive.emplace_back( + EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format("#door edit {} {}", move_option, value), + false, + fmt::format("{}", std::abs(value)) + ) + ); + } + + for (int move_index = -50; move_index <= 0; move_index += 5) { + int value = (move_index == 0 ? -1 : move_index); + move_h_options_negative.emplace_back( + EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format("#door edit {} {}", move_option, value), + false, + fmt::format("{}", std::abs(value)) + ) + ); + } + } + else if (move_option == set_size_action) { + for (int move_index = 0; move_index <= 100; move_index += 10) { + int value = (move_index == 0 ? 1 : move_index); + set_size_options_positive.emplace_back( + EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format("#door edit {} {}", move_option, value), + false, + fmt::format("{}", std::abs(value)) + ) + ); + } + + for (int move_index = -100; move_index <= 0; move_index += 10) { + int value = (move_index == 0 ? -1 : move_index); + set_size_options_negative.emplace_back( + EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format("#door edit {} {}", move_option, value), + false, + fmt::format("{}", std::abs(value)) + ) + ); + } + } + } + + // we're passing a move action here + if (!arg3.empty() && StringIsNumber(arg3)) { + float x_move = 0.0f; + float y_move = 0.0f; + float z_move = 0.0f; + float h_move = 0.0f; + float set_size = 0.0f; + + if (arg2 == move_x_action) { + x_move = std::atof(arg3.c_str()); + } + + if (arg2 == move_y_action) { + y_move = std::atof(arg3.c_str()); + } + + if (arg2 == move_z_action) { + z_move = std::atof(arg3.c_str()); + } + + if (arg2 == move_h_action) { + h_move = std::atof(arg3.c_str()); + } + + if (arg2 == set_size_action) { + set_size = std::atof(arg3.c_str()); + } + + door->SetLocation( + door->GetX() + x_move, + door->GetY() + y_move, + door->GetZ() + z_move + ); + + glm::vec4 door_position = door->GetPosition(); + door_position.w = door_position.w + h_move; + door->SetPosition(door_position); + door->SetSize(door->GetSize() + set_size); + } + + // spawn and move helpers + uint16 helper_mob_x_negative = 0; + uint16 helper_mob_x_positive = 0; + uint16 helper_mob_y_positive = 0; + uint16 helper_mob_y_negative = 0; + + for (auto &n: entity_list.GetNPCList()) { + NPC *npc = n.second; + std::string npc_name = npc->GetName(); + if (npc_name.find("-X") != std::string::npos) { + helper_mob_x_negative = npc->GetID(); + } + if (npc_name.find("-Y") != std::string::npos) { + helper_mob_y_negative = npc->GetID(); + } + if (npc_name.find("+X") != std::string::npos) { + helper_mob_x_positive = npc->GetID(); + } + if (npc_name.find("+Y") != std::string::npos) { + helper_mob_y_positive = npc->GetID(); + } + } + + // -X + glm::vec4 door_position = door->GetPosition(); + if (helper_mob_x_negative == 0) { + door_position.x = door_position.x - 15; + helper_mob_x_negative = NPC::SpawnNodeNPC("-X", "", door_position)->GetID(); + } + else { + auto n = entity_list.GetNPCByID(helper_mob_x_negative); + n->GMMove(door->GetX() - 15, door->GetY(), door->GetZ(), n->GetHeading()); + } + + // +X + door_position = door->GetPosition(); + if (helper_mob_x_positive == 0) { + door_position.x = door_position.x + 15; + helper_mob_x_positive = NPC::SpawnNodeNPC("+X", "", door_position)->GetID(); + } + else { + auto n = entity_list.GetNPCByID(helper_mob_x_positive); + n->GMMove(door->GetX() + 15, door->GetY(), door->GetZ(), n->GetHeading()); + } + + // -Y + door_position = door->GetPosition(); + if (helper_mob_y_negative == 0) { + door_position.y = door_position.y - 15; + helper_mob_y_negative = NPC::SpawnNodeNPC("-Y", "", door_position)->GetID(); + } + else { + auto n = entity_list.GetNPCByID(helper_mob_y_negative); + n->GMMove(door->GetX(), door->GetY() - 15, door->GetZ(), n->GetHeading()); + } + + // +Y + door_position = door->GetPosition(); + if (helper_mob_y_positive == 0) { + door_position.y = door_position.y + 15; + helper_mob_y_positive = NPC::SpawnNodeNPC("+Y", "", door_position)->GetID(); + } + else { + auto n = entity_list.GetNPCByID(helper_mob_y_positive); + n->GMMove(door->GetX(), door->GetY() + 15, door->GetZ(), n->GetHeading()); + } + + c->Message( + Chat::White, + fmt::format( + "Name [{}] [{}] [{}] [{}]", + door->GetDoorName(), + EQ::SayLinkEngine::GenerateQuestSaylink( + "#door save", + false, + "Save" + ), + EQ::SayLinkEngine::GenerateQuestSaylink( + "#door changemodelqueue", + false, + "Change Model" + ), + EQ::SayLinkEngine::GenerateQuestSaylink( + "#door setinclineinc", + false, + "Incline" + ) + ).c_str() + ); + c->Message( + Chat::White, + fmt::format( + "[{}] - [X] + [{}]", + implode(" | ", move_x_options_negative), + implode(" | ", move_x_options_positive) + ).c_str() + ); + c->Message( + Chat::White, + fmt::format( + "[{}] - [Y] + [{}]", + implode(" | ", move_y_options_negative), + implode(" | ", move_y_options_positive) + ).c_str() + ); + c->Message( + Chat::White, + fmt::format( + "[{}] - [Z] + [{}]", + implode(" | ", move_z_options_negative), + implode(" | ", move_z_options_positive) + ).c_str() + ); + c->Message( + Chat::White, + fmt::format( + "[{}] - [H] + [{}]", + implode(" | ", move_h_options_negative), + implode(" | ", move_h_options_positive) + ).c_str() + ); + c->Message( + Chat::White, + fmt::format( + "[{}] - [Size] + [{}]", + implode(" | ", set_size_options_negative), + implode(" | ", set_size_options_positive) + ).c_str() + ); + + return; + } + + c->Message(Chat::Red, "Door selection invalid..."); + } + + // create + if (arg1 == "create") { + std::string model = str_toupper(arg2); + uint16 entity_id = entity_list.CreateDoor( + model.c_str(), + c->GetPosition(), + 58, + 100 + ); + + c->Message( + Chat::White, + fmt::format("Creating door entity_id [{}] with model [{}]", entity_id, model).c_str()); + c->SetDoorToolEntityId(entity_id); + } + + // set model + if (arg1 == "model") { + Doors *door = entity_list.GetDoorsByID(c->GetDoorToolEntityId()); + std::string model = str_toupper(arg2); + if (door) { + door->SetDoorName(model.c_str()); + } + } + + // change model queue + if (arg1 == "changemodelqueue") { + c->Message( + Chat::White, + fmt::format( + "#door model | Changes door model for selected door or select from [{}] or [{}]", + EQ::SayLinkEngine::GenerateQuestSaylink("#door showmodelszone", false, "local zone"), + EQ::SayLinkEngine::GenerateQuestSaylink("#door showmodelsglobal", false, "global") + ).c_str() + ); + } + + // open type + if (arg1 == "opentype" && !arg2.empty() && StringIsNumber(arg2)) { + Doors *door = entity_list.GetDoorsByID(c->GetDoorToolEntityId()); + if (door) { + door->SetOpenType(std::atoi(arg2.c_str())); + } + } + + // incline + if (arg1 == "setincline" && !arg2.empty() && StringIsNumber(arg2)) { + Doors *door = entity_list.GetDoorsByID(c->GetDoorToolEntityId()); + if (door) { + door->SetIncline(std::atoi(arg2.c_str())); + } + } + + // incline + if (arg1 == "setinvertstate" && !arg2.empty() && StringIsNumber(arg2)) { + Doors *door = entity_list.GetDoorsByID(c->GetDoorToolEntityId()); + if (door) { + door->SetInvertState(std::atoi(arg2.c_str())); + } + } + + // save + if (arg1 == "save") { + Doors *door = entity_list.GetDoorsByID(c->GetDoorToolEntityId()); + if (door) { + door->CreateDatabaseEntry(); + c->Message(Chat::White, "Door saved"); + } + } + + // incline incremental + if (arg1 == "setinclineinc" && !arg2.empty() && StringIsNumber(arg2)) { + Doors *door = entity_list.GetDoorsByID(c->GetDoorToolEntityId()); + if (door) { + door->SetIncline(door->GetIncline() + std::atoi(arg2.c_str())); + } + } + if (arg1 == "setinclineinc") { + std::map incline_values = { + {.01, "Upright"}, + {63.75, "45 Degrees",}, + {130, "90 Degrees"}, + {192.5, "135 Degrees"}, + {255, "180 Degrees"}, + {321.25, "225 Degrees"}, + {385, "270 Degrees"}, + {448.75, "315 Degrees"}, + {512.5, "360 Degrees"} + }; + + std::vector incline_normal_options; + std::vector incline_positive_options; + std::vector incline_negative_options; + for (auto incline_value : incline_values) { + incline_normal_options.emplace_back( + EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format( + "#door setincline {}", + incline_value.first + ), + false, + incline_value.second + ) + ); + } + + for (int incline_index = 0; incline_index <= 100; incline_index += 10) { + int incline_value = (incline_index == 0 ? 1 : incline_index); + incline_positive_options.emplace_back( + EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format( + "#door setinclineinc {}", + incline_value + ), + false, + itoa(std::abs(incline_value)) + ) + ); + } + + for (int incline_index = -100; incline_index <= 1; incline_index += 10) { + int incline_value = (incline_index == 0 ? -1 : incline_index); + incline_negative_options.emplace_back( + EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format( + "#door setinclineinc {}", + incline_value + ), + false, + itoa(std::abs(incline_value)) + ) + ); + } + + c->Message( + Chat::White, + fmt::format( + "[Incline] [{}]", + implode(" | ", incline_normal_options) + ).c_str() + ); + + c->Message( + Chat::White, + fmt::format( + "[Incline Increments] [{}] - | + [{}]", + implode(" | ", incline_negative_options), + implode(" | ", incline_positive_options) + ).c_str() + ); + } + + // show models in zone + if (arg1 == "showmodelsglobal") { + auto game_objects = ToolGameObjectsRepository::GetWhere( + database, + "object_name LIKE '%IT%' AND zoneid = 0 AND object_name NOT LIKE '%OBJ%' GROUP by file_from" + ); + + if (game_objects.empty()) { + c->Message(Chat::White, "There are no models to display..."); + } + + c->Message(Chat::White, "------------------------------------------------"); + c->Message(Chat::White, "# Models (Global)"); + c->Message(Chat::White, "------------------------------------------------"); + + DisplayModelsFromFileResults(c, game_objects); + } + + // show models in zone + if (arg1 == "showmodelszone") { + auto game_objects = ToolGameObjectsRepository::GetWhere( + database, + fmt::format("zoneid = {}", zone->GetZoneID()) + ); + + if (game_objects.empty()) { + c->Message(Chat::White, "There are no models for this zone..."); + } + + c->Message(Chat::White, "------------------------------------------------"); + c->Message(Chat::White, "# Models from zone"); + c->Message(Chat::White, "------------------------------------------------"); + + DisplayObjectResultToClient(c, game_objects); + } + + // show models from file name + if (arg1 == "showmodelsfromfile" && !arg2.empty()) { + const std::string &file_name = arg2; + auto game_objects = ToolGameObjectsRepository::GetWhere( + database, + fmt::format("file_from = '{}'", file_name) + ); + + if (game_objects.empty()) { + c->Message(Chat::White, "There are no models for this zone..."); + } + + c->Message(Chat::White, "------------------------------------------------"); + c->Message(Chat::White, fmt::format("# Models from file name [{}]", file_name).c_str()); + c->Message(Chat::White, "------------------------------------------------"); + + DisplayObjectResultToClient(c, game_objects); + } +} + +void DoorManipulation::CommandHeader(Client *c) +{ + c->Message(Chat::White, "------------------------------------------------"); + c->Message(Chat::White, "# Door Commands"); + c->Message(Chat::White, "------------------------------------------------"); +} + +void DoorManipulation::DisplayObjectResultToClient( + Client *c, + std::vector game_objects +) +{ + std::vector say_links; + + for (auto &g: game_objects) { + say_links.emplace_back( + fmt::format( + "[{}] ", + EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format("#door model {}", g.object_name), + false, + g.object_name + ) + ) + ); + } + + int character_length = 0; + std::vector buffered_links; + + for (auto &links: say_links) { + buffered_links.emplace_back(links); + character_length += links.length(); + + // empty buffer + if (character_length > MAX_CLIENT_MESSAGE_LENGTH) { + std::string message_buffer; + + for (auto &buffered_link: buffered_links) { + message_buffer += buffered_link; + } + + c->Message(Chat::White, message_buffer.c_str()); + + // reset + character_length = 0; + buffered_links = {}; + } + } + + if (!buffered_links.empty()) { + c->Message(Chat::White, implode(" ", buffered_links).c_str()); + } +} + +void DoorManipulation::DisplayModelsFromFileResults( + Client *c, + std::vector game_objects +) +{ + std::vector say_links; + + for (auto &g: game_objects) { + say_links.emplace_back( + fmt::format( + "[{}] ", + EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format("#door showmodelsfromfile {}", g.file_from), + false, + g.file_from + ) + ) + ); + } + + int character_length = 0; + std::vector buffered_links; + + for (auto &links: say_links) { + buffered_links.emplace_back(links); + character_length += links.length(); + + // empty buffer + if (character_length > MAX_CLIENT_MESSAGE_LENGTH) { + std::string message_buffer; + + for (auto &buffered_link: buffered_links) { + message_buffer += buffered_link; + } + + c->Message(Chat::White, message_buffer.c_str()); + + // reset + character_length = 0; + buffered_links = {}; + } + } + + if (!buffered_links.empty()) { + c->Message(Chat::White, implode(" ", buffered_links).c_str()); + } +} diff --git a/zone/gm_commands/door_manipulation.h b/zone/gm_commands/door_manipulation.h new file mode 100644 index 000000000..d448c3bc7 --- /dev/null +++ b/zone/gm_commands/door_manipulation.h @@ -0,0 +1,23 @@ +#ifndef EQEMU_DOOR_MANIPULATION_H +#define EQEMU_DOOR_MANIPULATION_H + +#include "../client.h" +#include "../../common/repositories/tool_game_objects_repository.h" + +class DoorManipulation { + +public: + static void CommandHandler(Client *c, const Seperator *sep); + static void CommandHeader(Client *c); + static void DisplayObjectResultToClient( + Client *c, + std::vector game_objects + ); + static void DisplayModelsFromFileResults( + Client *c, + std::vector game_objects + ); +}; + + +#endif //EQEMU_DOOR_MANIPULATION_H diff --git a/zone/gm_commands/dye.cpp b/zone/gm_commands/dye.cpp new file mode 100755 index 000000000..ecc04f7ca --- /dev/null +++ b/zone/gm_commands/dye.cpp @@ -0,0 +1,87 @@ +#include "../client.h" + +void command_dye(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + + if (arguments == 0) { + c->Message(Chat::White, "Command Syntax: #dye help | #dye [slot] [red] [green] [blue] [use_tint]"); + return; + } + + uint8 slot = 0; + uint8 red = 255; + uint8 green = 255; + uint8 blue = 255; + uint8 use_tint = 255; + + std::vector dye_slots = { + "Helmet", + "Chest", + "Arms", + "Wrist", + "Hands", + "Legs", + "Feet" + }; + + if (arguments == 1 && !strcasecmp(sep->arg[1], "help")) { + int slot_id = 0; + std::vector slot_messages; + c->Message(Chat::White, "Command Syntax: #dye help | #dye [slot] [red] [green] [blue] [use_tint]"); + c->Message(Chat::White, "Red, Green, and Blue go from 0 to 255."); + + for (const auto &slot : dye_slots) { + slot_messages.push_back(fmt::format("({}) {}", slot_id, slot)); + slot_id++; + } + + c->Message( + Chat::White, + fmt::format( + "{} {}", + "Slots are as follows:", + implode(", ", slot_messages) + ).c_str() + ); + return; + } + + if (arguments >= 1 && sep->IsNumber(1)) { + slot = atoi(sep->arg[1]); + } + + if (arguments >= 2 && sep->IsNumber(2)) { + red = atoi(sep->arg[2]); + } + + if (arguments >= 3 && sep->IsNumber(3)) { + green = atoi(sep->arg[3]); + } + + if (arguments >= 4 && sep->IsNumber(4)) { + blue = atoi(sep->arg[4]); + } + + if (arguments >= 5 && sep->IsNumber(5)) { + use_tint = atoi(sep->arg[5]); + } + + if (RuleB(Command, DyeCommandRequiresDyes)) { + uint32 dye_item_id = 32557; + if (c->CountItem(dye_item_id) >= 1) { + c->RemoveItem(dye_item_id); + } + else { + EQ::SayLinkEngine linker; + linker.SetLinkType(EQ::saylink::SayLinkItemData); + const EQ::ItemData *dye_item = database.GetItem(dye_item_id); + linker.SetItemData(dye_item); + c->Message(Chat::White, fmt::format("This command requires a {} to use.", linker.GenerateLink()).c_str()); + return; + } + } + + c->DyeArmorBySlot(slot, red, green, blue, use_tint); +} + diff --git a/zone/gm_commands/dz.cpp b/zone/gm_commands/dz.cpp new file mode 100755 index 000000000..506a203fb --- /dev/null +++ b/zone/gm_commands/dz.cpp @@ -0,0 +1,244 @@ +#include "../client.h" +#include "../expedition.h" + +void command_dz(Client *c, const Seperator *sep) +{ + if (!c || !zone) { + return; + } + + if (strcasecmp(sep->arg[1], "cache") == 0) { + if (strcasecmp(sep->arg[2], "reload") == 0) { + DynamicZone::CacheAllFromDatabase(); + Expedition::CacheAllFromDatabase(); + c->Message( + Chat::White, fmt::format( + "Reloaded [{}] dynamic zone(s) and [{}] expedition(s) from database", + zone->dynamic_zone_cache.size(), zone->expedition_cache.size() + ).c_str()); + } + } + else if (strcasecmp(sep->arg[1], "expedition") == 0) { + if (strcasecmp(sep->arg[2], "list") == 0) { + std::vector expeditions; + for (const auto &expedition : zone->expedition_cache) { + expeditions.emplace_back(expedition.second.get()); + } + + std::sort( + expeditions.begin(), expeditions.end(), + [](const Expedition *lhs, const Expedition *rhs) { + return lhs->GetID() < rhs->GetID(); + } + ); + + c->Message(Chat::White, fmt::format("Total Active Expeditions: [{}]", expeditions.size()).c_str()); + for (const auto &expedition : expeditions) { + auto dz = expedition->GetDynamicZone(); + if (!dz) { + LogExpeditions("Expedition [{}] has an invalid dz [{}] in cache", + expedition->GetID(), + expedition->GetDynamicZoneID()); + continue; + } + + auto leader_saylink = EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format( + "#goto {}", expedition->GetLeaderName()), false, expedition->GetLeaderName()); + auto zone_saylink = EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format( + "#zoneinstance {}", dz->GetInstanceID()), false, "zone" + ); + + auto seconds = dz->GetSecondsRemaining(); + + c->Message( + Chat::White, fmt::format( + "expedition id: [{}] dz id: [{}] name: [{}] leader: [{}] {}: [{}]:[{}]:[{}]:[{}] members: [{}] remaining: [{:02}:{:02}:{:02}]", + expedition->GetID(), + expedition->GetDynamicZoneID(), + expedition->GetName(), + leader_saylink, + zone_saylink, + ZoneName(dz->GetZoneID()), + dz->GetZoneID(), + dz->GetInstanceID(), + dz->GetZoneVersion(), + dz->GetMemberCount(), + seconds / 3600, // hours + (seconds / 60) % 60, // minutes + seconds % 60 // seconds + ).c_str()); + } + } + else if (strcasecmp(sep->arg[2], "reload") == 0) { + Expedition::CacheAllFromDatabase(); + c->Message( + Chat::White, fmt::format( + "Reloaded [{}] expeditions to cache from database.", zone->expedition_cache.size() + ).c_str()); + } + else if (strcasecmp(sep->arg[2], "destroy") == 0 && sep->IsNumber(3)) { + auto expedition_id = std::strtoul(sep->arg[3], nullptr, 10); + auto expedition = Expedition::FindCachedExpeditionByID(expedition_id); + if (expedition) { + c->Message( + Chat::White, fmt::format( + "Destroying expedition [{}] ({})", + expedition_id, expedition->GetName()).c_str()); + expedition->GetDynamicZone()->RemoveAllMembers(); + } + else { + c->Message(Chat::Red, fmt::format("Failed to destroy expedition [{}]", sep->arg[3]).c_str()); + } + } + else if (strcasecmp(sep->arg[2], "unlock") == 0 && sep->IsNumber(3)) { + auto expedition_id = std::strtoul(sep->arg[3], nullptr, 10); + auto expedition = Expedition::FindCachedExpeditionByID(expedition_id); + if (expedition) { + c->Message(Chat::White, fmt::format("Unlocking expedition [{}]", expedition_id).c_str()); + expedition->SetLocked(false, ExpeditionLockMessage::None, true); + } + else { + c->Message(Chat::Red, fmt::format("Failed to find expedition [{}]", sep->arg[3]).c_str()); + } + } + } + else if (strcasecmp(sep->arg[1], "list") == 0) { + c->Message( + Chat::White, + fmt::format("Total Dynamic Zones (cache): [{}]", zone->dynamic_zone_cache.size()).c_str()); + + std::vector dynamic_zones; + for (const auto &dz : zone->dynamic_zone_cache) { + dynamic_zones.emplace_back(dz.second.get()); + } + + std::sort( + dynamic_zones.begin(), dynamic_zones.end(), + [](const DynamicZone *lhs, const DynamicZone *rhs) { + return lhs->GetID() < rhs->GetID(); + } + ); + + for (const auto &dz : dynamic_zones) { + auto seconds = dz->GetSecondsRemaining(); + auto zone_saylink = EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format("#zoneinstance {}", dz->GetInstanceID()), false, "zone" + ); + + std::string aligned_type = fmt::format( + "[{}]", + DynamicZone::GetDynamicZoneTypeName(static_cast(dz->GetType()))); + c->Message( + Chat::White, fmt::format( + "id: [{}] type: {:>10} {}: [{}]:[{}]:[{}] members: [{}] remaining: [{:02}:{:02}:{:02}]", + dz->GetID(), + aligned_type, + zone_saylink, + dz->GetZoneID(), + dz->GetInstanceID(), + dz->GetZoneVersion(), + dz->GetMemberCount(), + seconds / 3600, // hours + (seconds / 60) % 60, // minutes + seconds % 60 // seconds + ).c_str()); + } + } + else if (strcasecmp(sep->arg[1], "listdb") == 0) { + auto dz_list = DynamicZonesRepository::AllDzInstancePlayerCounts(database); + c->Message(Chat::White, fmt::format("Total Dynamic Zones (database): [{}]", dz_list.size()).c_str()); + + auto now = std::chrono::system_clock::now(); + + for (const auto &dz : dz_list) { + auto expire_time = std::chrono::system_clock::from_time_t(dz.start_time + dz.duration); + auto remaining = std::chrono::duration_cast(expire_time - now); + auto seconds = std::max(0, static_cast(remaining.count())); + bool is_expired = now > expire_time; + + if (!is_expired || strcasecmp(sep->arg[2], "all") == 0) { + auto zone_saylink = is_expired ? "zone" : EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format("#zoneinstance {}", dz.instance), false, "zone" + ); + + c->Message( + Chat::White, fmt::format( + "id: [{}] type: [{}] {}: [{}]:[{}]:[{}] members: [{}] remaining: [{:02}:{:02}:{:02}]", + dz.id, + DynamicZone::GetDynamicZoneTypeName(static_cast(dz.type)), + zone_saylink, + dz.zone, + dz.instance, + dz.version, + dz.member_count, + seconds / 3600, // hours + (seconds / 60) % 60, // minutes + seconds % 60 // seconds + ).c_str()); + } + } + } + else if (strcasecmp(sep->arg[1], "lockouts") == 0) { + if (strcasecmp(sep->arg[2], "remove") == 0 && sep->arg[3][0] != '\0') { + if (sep->arg[5][0] == '\0') { + c->Message( + Chat::White, fmt::format( + "Removing [{}] lockouts on [{}].", sep->arg[4][0] ? sep->arg[4] : "all", sep->arg[3] + ).c_str()); + } + else { + c->Message( + Chat::White, fmt::format( + "Removing [{}]:[{}] lockout on [{}].", sep->arg[4], sep->arg[5], sep->arg[3] + ).c_str()); + } + Expedition::RemoveLockoutsByCharacterName(sep->arg[3], sep->arg[4], sep->arg[5]); + } + } + else if (strcasecmp(sep->arg[1], "makeleader") == 0 && sep->IsNumber(2) && sep->arg[3][0] != '\0') { + auto expedition_id = std::strtoul(sep->arg[2], nullptr, 10); + auto expedition = Expedition::FindCachedExpeditionByID(expedition_id); + if (expedition) { + auto char_name = FormatName(sep->arg[3]); + c->Message( + Chat::White, + fmt::format("Setting expedition [{}] leader to [{}]", expedition_id, char_name).c_str()); + expedition->SendWorldMakeLeaderRequest(c->CharacterID(), char_name); + } + else { + c->Message(Chat::Red, fmt::format("Failed to find expedition [{}]", expedition_id).c_str()); + } + } + else { + c->Message(Chat::White, "#dz usage:"); + c->Message( + Chat::White, + "#dz cache reload - reload the current zone cache from db (also reloads expedition cache dependency)" + ); + c->Message(Chat::White, "#dz expedition list - list expeditions in current zone cache"); + c->Message(Chat::White, "#dz expedition reload - reload expedition zone cache from database"); + c->Message( + Chat::White, + "#dz expedition destroy - destroy expedition globally (must be in cache)" + ); + c->Message(Chat::White, "#dz expedition unlock - unlock expedition"); + c->Message(Chat::White, "#dz list - list all dynamic zone instances from current zone cache"); + c->Message( + Chat::White, + "#dz listdb [all] - list dynamic zone instances from database -- 'all' includes expired" + ); + c->Message(Chat::White, "#dz lockouts remove - delete all of character's expedition lockouts"); + c->Message( + Chat::White, + "#dz lockouts remove \"\" - delete lockouts by expedition" + ); + c->Message( + Chat::White, + "#dz lockouts remove \"\" \"\" - delete lockout by expedition event" + ); + c->Message(Chat::White, "#dz makeleader - set new expedition leader"); + } +} + diff --git a/zone/gm_commands/dzkickplayers.cpp b/zone/gm_commands/dzkickplayers.cpp new file mode 100755 index 000000000..149ccc0af --- /dev/null +++ b/zone/gm_commands/dzkickplayers.cpp @@ -0,0 +1,13 @@ +#include "../client.h" +#include "../expedition.h" + +void command_dzkickplayers(Client *c, const Seperator *sep) +{ + if (c) { + auto expedition = c->GetExpedition(); + if (expedition) { + expedition->DzKickPlayers(c); + } + } +} + diff --git a/zone/gm_commands/editmassrespawn.cpp b/zone/gm_commands/editmassrespawn.cpp new file mode 100755 index 000000000..41fcc6613 --- /dev/null +++ b/zone/gm_commands/editmassrespawn.cpp @@ -0,0 +1,140 @@ +#include "../client.h" + +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 = content_db.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 = content_db.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]", + EQ::SayLinkEngine::GenerateQuestSaylink(saylink, false, "Apply").c_str(), + saylink.c_str() + ); + } + } + } + else { + + c->Message(Chat::Yellow, "Found (0) NPC's that match this search..."); + } +} + diff --git a/zone/gm_commands/emote.cpp b/zone/gm_commands/emote.cpp new file mode 100755 index 000000000..6deda54b5 --- /dev/null +++ b/zone/gm_commands/emote.cpp @@ -0,0 +1,45 @@ +#include "../client.h" +#include "../worldserver.h" + +extern WorldServer worldserver; + +void command_emote(Client *c, const Seperator *sep) +{ + if (sep->arg[3][0] == 0) { + c->Message(Chat::White, "Usage: #emote [name | world | zone] type# message"); + } + else { + if (strcasecmp(sep->arg[1], "zone") == 0) { + char *newmessage = 0; + if (strstr(sep->arg[3], "^") == 0) { + entity_list.Message(0, atoi(sep->arg[2]), sep->argplus[3]); + } + else { + for (newmessage = strtok((char *) sep->arg[3], "^"); + newmessage != nullptr; + newmessage = strtok(nullptr, "^")) + entity_list.Message(0, atoi(sep->arg[2]), newmessage); + } + } + else if (!worldserver.Connected()) { + c->Message(Chat::White, "Error: World server disconnected"); + } + else if (!strcasecmp(sep->arg[1], "world")) { + worldserver.SendEmoteMessage( + 0, + 0, + atoi(sep->arg[2]), + sep->argplus[3] + ); + } + else { + worldserver.SendEmoteMessage( + sep->arg[1], + 0, + atoi(sep->arg[2]), + sep->argplus[3] + ); + } + } +} + diff --git a/zone/gm_commands/emotesearch.cpp b/zone/gm_commands/emotesearch.cpp new file mode 100755 index 000000000..40d5bfac6 --- /dev/null +++ b/zone/gm_commands/emotesearch.cpp @@ -0,0 +1,78 @@ +#include "../client.h" + +void command_emotesearch(Client *c, const Seperator *sep) +{ + if (sep->arg[1][0] == 0) { + c->Message(Chat::White, "Usage: #emotesearch [search string or emoteid]"); + } + else { + const char *search_criteria = sep->argplus[1]; + int count = 0; + + if (Seperator::IsNumber(search_criteria)) { + uint16 emoteid = atoi(search_criteria); + LinkedListIterator iterator(zone->NPCEmoteList); + iterator.Reset(); + while (iterator.MoreElements()) { + NPC_Emote_Struct *nes = iterator.GetData(); + if (emoteid == nes->emoteid) { + c->Message( + Chat::White, + "EmoteID: %i Event: %i Type: %i Text: %s", + nes->emoteid, + nes->event_, + nes->type, + nes->text + ); + count++; + } + iterator.Advance(); + } + if (count == 0) { + c->Message(Chat::White, "No emotes found."); + } + else { + c->Message(Chat::White, "%i emote(s) found", count); + } + } + else { + char sText[64]; + char sCriteria[515]; + strn0cpy(sCriteria, search_criteria, sizeof(sCriteria)); + strupr(sCriteria); + char *pdest; + + LinkedListIterator iterator(zone->NPCEmoteList); + iterator.Reset(); + while (iterator.MoreElements()) { + NPC_Emote_Struct *nes = iterator.GetData(); + strn0cpy(sText, nes->text, sizeof(sText)); + strupr(sText); + pdest = strstr(sText, sCriteria); + if (pdest != nullptr) { + c->Message( + Chat::White, + "EmoteID: %i Event: %i Type: %i Text: %s", + nes->emoteid, + nes->event_, + nes->type, + nes->text + ); + count++; + } + if (count == 50) { + break; + } + + iterator.Advance(); + } + if (count == 50) { + c->Message(Chat::White, "50 emotes shown...too many results."); + } + else { + c->Message(Chat::White, "%i emote(s) found", count); + } + } + } +} + diff --git a/zone/gm_commands/emoteview.cpp b/zone/gm_commands/emoteview.cpp new file mode 100755 index 000000000..afbec21aa --- /dev/null +++ b/zone/gm_commands/emoteview.cpp @@ -0,0 +1,39 @@ +#include "../client.h" + +void command_emoteview(Client *c, const Seperator *sep) +{ + if (!c->GetTarget() || !c->GetTarget()->IsNPC()) { + c->Message(Chat::White, "You must target a NPC to view their emotes."); + return; + } + + if (c->GetTarget() && c->GetTarget()->IsNPC()) { + int count = 0; + int emoteid = c->GetTarget()->CastToNPC()->GetEmoteID(); + + LinkedListIterator iterator(zone->NPCEmoteList); + iterator.Reset(); + while (iterator.MoreElements()) { + NPC_Emote_Struct *nes = iterator.GetData(); + if (emoteid == nes->emoteid) { + c->Message( + Chat::White, + "EmoteID: %i Event: %i Type: %i Text: %s", + nes->emoteid, + nes->event_, + nes->type, + nes->text + ); + count++; + } + iterator.Advance(); + } + if (count == 0) { + c->Message(Chat::White, "No emotes found."); + } + else { + c->Message(Chat::White, "%i emote(s) found", count); + } + } +} + diff --git a/zone/gm_commands/enablerecipe.cpp b/zone/gm_commands/enablerecipe.cpp new file mode 100755 index 000000000..088907a75 --- /dev/null +++ b/zone/gm_commands/enablerecipe.cpp @@ -0,0 +1,29 @@ +#include "../client.h" + +void command_enablerecipe(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments || !sep->IsNumber(1)) { + c->Message(Chat::White, "Usage: #enablerecipe [Recipe ID]"); + return; + } + + auto recipe_id = std::stoul(sep->arg[1]); + if (!recipe_id) { + c->Message(Chat::White, "Usage: #enablerecipe [Recipe ID]"); + return; + } + + c->Message( + Chat::White, + fmt::format( + "Recipe ID {} {} enabled.", + recipe_id, + ( + content_db.EnableRecipe(recipe_id) ? + "successfully" : + "failed to be" + ) + ).c_str() + ); +} diff --git a/zone/gm_commands/endurance.cpp b/zone/gm_commands/endurance.cpp new file mode 100755 index 000000000..9ae34a477 --- /dev/null +++ b/zone/gm_commands/endurance.cpp @@ -0,0 +1,36 @@ +#include "../client.h" + +void command_endurance(Client *c, const Seperator *sep) +{ + Mob* target = c; + if (c->GetTarget()) { + target = c->GetTarget(); + } + + int endurance = 0; + if (target->IsClient()) { + endurance = target->CastToClient()->GetMaxEndurance(); + target->CastToClient()->SetEndurance(endurance); + } else { + endurance = target->GetMaxEndurance(); + target->SetEndurance(endurance); + } + + c->Message( + Chat::White, + fmt::format( + "Set {} to full Endurance ({}).", + ( + c == target ? + "yourself" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ), + endurance + ).c_str() + ); +} + diff --git a/zone/gm_commands/equipitem.cpp b/zone/gm_commands/equipitem.cpp new file mode 100755 index 000000000..7ffe8ad84 --- /dev/null +++ b/zone/gm_commands/equipitem.cpp @@ -0,0 +1,82 @@ +#include "../client.h" + +void command_equipitem(Client *c, const Seperator *sep) +{ + uint32 slot_id = atoi(sep->arg[1]); + if (sep->IsNumber(1) && (slot_id >= EQ::invslot::EQUIPMENT_BEGIN && slot_id <= EQ::invslot::EQUIPMENT_END)) { + const EQ::ItemInstance *from_inst = c->GetInv().GetItem(EQ::invslot::slotCursor); + const EQ::ItemInstance *to_inst = c->GetInv().GetItem(slot_id); // added (desync issue when forcing stack to stack) + bool partialmove = false; + int16 movecount; + + if (from_inst && from_inst->IsClassCommon()) { + auto outapp = new EQApplicationPacket(OP_MoveItem, sizeof(MoveItem_Struct)); + MoveItem_Struct *mi = (MoveItem_Struct *) outapp->pBuffer; + mi->from_slot = EQ::invslot::slotCursor; + mi->to_slot = slot_id; + // mi->number_in_stack = from_inst->GetCharges(); // replaced with con check for stacking + + // crude stackable check to only 'move' the difference count on client instead of entire stack when applicable + if (to_inst && to_inst->IsStackable() && + (to_inst->GetItem()->ID == from_inst->GetItem()->ID) && + (to_inst->GetCharges() < to_inst->GetItem()->StackSize) && + (from_inst->GetCharges() > to_inst->GetItem()->StackSize - to_inst->GetCharges())) { + movecount = to_inst->GetItem()->StackSize - to_inst->GetCharges(); + mi->number_in_stack = (uint32) movecount; + partialmove = true; + } + else { + mi->number_in_stack = from_inst->GetCharges(); + } + + // Save move changes + // Added conditional check to packet send..would have sent change even on a swap failure..whoops! + + if (partialmove) { // remove this con check if someone can figure out removing charges from cursor stack issue below + // mi->number_in_stack is always from_inst->GetCharges() when partialmove is false + c->Message(Chat::Red, "Error: Partial stack added to existing stack exceeds allowable stacksize"); + safe_delete(outapp); + return; + } + else if (c->SwapItem(mi)) { + c->FastQueuePacket(&outapp); + + // if the below code is still needed..just send an an item trade packet to each slot..it should overwrite the client instance + + // below code has proper logic, but client does not like to have cursor charges changed + // (we could delete the cursor item and resend, but issues would arise if there are queued items) + //if (partialmove) { + // EQApplicationPacket* outapp2 = new EQApplicationPacket(OP_DeleteItem, sizeof(DeleteItem_Struct)); + // DeleteItem_Struct* di = (DeleteItem_Struct*)outapp2->pBuffer; + // di->from_slot = SLOT_CURSOR; + // di->to_slot = 0xFFFFFFFF; + // di->number_in_stack = 0xFFFFFFFF; + + // c->Message(Chat::White, "Deleting %i charges from stack", movecount); // debug line..delete + + // for (int16 deletecount=0; deletecount < movecount; deletecount++) + // have to use 'movecount' because mi->number_in_stack is 'ENCODED' at this point (i.e., 99 charges returns 22...) + // c->QueuePacket(outapp2); + + // safe_delete(outapp2); + //} + } + else { + c->Message(Chat::Red, "Error: Unable to equip current item"); + } + safe_delete(outapp); + + // also send out a wear change packet? + } + else if (from_inst == nullptr) { + c->Message(Chat::Red, "Error: There is no item on your cursor"); + } + else { + c->Message(Chat::Red, "Error: Item on your cursor cannot be equipped"); + } + } + else { + c->Message(Chat::White, "Usage: #equipitem slotid[0-21] - equips the item on your cursor to the position"); + } +} + diff --git a/zone/gm_commands/face.cpp b/zone/gm_commands/face.cpp new file mode 100755 index 000000000..274d5a300 --- /dev/null +++ b/zone/gm_commands/face.cpp @@ -0,0 +1,37 @@ +#include "../client.h" + +void command_face(Client *c, const Seperator *sep) +{ + Mob *target = c->GetTarget(); + if (!sep->IsNumber(1)) { + c->Message(Chat::White, "Usage: #face [number of face]"); + } + else if (!target) { + c->Message(Chat::White, "Error: this command requires a target"); + } + else { + uint16 Race = target->GetRace(); + uint8 Gender = target->GetGender(); + uint8 Texture = 0xFF; + uint8 HelmTexture = 0xFF; + uint8 HairColor = target->GetHairColor(); + uint8 BeardColor = target->GetBeardColor(); + uint8 EyeColor1 = target->GetEyeColor1(); + uint8 EyeColor2 = target->GetEyeColor2(); + uint8 HairStyle = target->GetHairStyle(); + uint8 LuclinFace = atoi(sep->arg[1]); + uint8 Beard = target->GetBeard(); + uint32 DrakkinHeritage = target->GetDrakkinHeritage(); + uint32 DrakkinTattoo = target->GetDrakkinTattoo(); + uint32 DrakkinDetails = target->GetDrakkinDetails(); + + target->SendIllusionPacket( + Race, Gender, Texture, HelmTexture, HairColor, BeardColor, + EyeColor1, EyeColor2, HairStyle, LuclinFace, Beard, 0xFF, + DrakkinHeritage, DrakkinTattoo, DrakkinDetails + ); + + c->Message(Chat::White, "Face = %i", atoi(sep->arg[1])); + } +} + diff --git a/zone/gm_commands/faction.cpp b/zone/gm_commands/faction.cpp new file mode 100755 index 000000000..3a704c81a --- /dev/null +++ b/zone/gm_commands/faction.cpp @@ -0,0 +1,174 @@ +#include "../client.h" + +void command_faction(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments) { + c->Message( + Chat::White, + "Usage: #faction review [Search Criteria | All] - Review Targeted Player's Faction Hits" + ); + c->Message( + Chat::White, + "Usage: #faction reset [Faction ID] - Reset Targeted Player's Faction to Base Faction Value" + ); + c->Message(Chat::White, "Usage: #faction view - Displays Target NPC's Primary Faction"); + return; + } + + std::string faction_filter; + if (sep->arg[2]) { + faction_filter = str_tolower(sep->arg[2]); + } + + if (!strcasecmp(sep->arg[1], "review")) { + if (!(c->GetTarget() && c->GetTarget()->IsClient())) { + c->Message(Chat::Red, "Player Target Required for faction review"); + return; + } + + Client *target = c->GetTarget()->CastToClient(); + uint32 character_id = target->CharacterID(); + std::string query; + if (!strcasecmp(faction_filter.c_str(), "all")) { + query = fmt::format( + "SELECT id, `name`, current_value FROM faction_list INNER JOIN faction_values ON faction_list.id = faction_values.faction_id WHERE char_id = {}", + character_id + ); + } + else { + query = fmt::format( + "SELECT id, `name`, current_value FROM faction_list INNER JOIN faction_values ON faction_list.id = faction_values.faction_id WHERE `name` like '%{}%' and char_id = {}", + faction_filter.c_str(), + character_id + ); + } + + auto results = content_db.QueryDatabase(query); + if (!results.Success() || !results.RowCount()) { + c->Message(Chat::Yellow, "No faction hits found. All are at base level."); + return; + } + + uint32 found_count = 0; + for (auto row : results) { + uint32 faction_number = (found_count + 1); + auto faction_id = std::stoul(row[0]); + std::string faction_name = row[1]; + std::string faction_value = row[2]; + std::string reset_link = EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format("#faction reset {}", faction_id), + false, + "Reset" + ); + + c->Message( + Chat::White, + fmt::format( + "Faction {} | Name: {} ({}) Value: {} [{}]", + faction_number, + faction_name, + faction_id, + faction_value, + reset_link + ).c_str() + ); + found_count++; + } + + auto faction_message = ( + found_count > 0 ? + ( + found_count == 1 ? + "A Faction was" : + fmt::format("{} Factions were", found_count) + ) : + "No Factions were" + ); + c->Message( + Chat::White, + fmt::format( + "{} found.", + faction_message + ).c_str() + ); + } + else if (!strcasecmp(sep->arg[1], "reset")) { + if (strlen(faction_filter.c_str()) > 0) { + if (c->GetTarget() && c->GetTarget()->IsClient()) { + Client *target = c->GetTarget()->CastToClient(); + if ( + ( + !c->GetFeigned() && + c->GetAggroCount() == 0 + ) || + ( + !target->GetFeigned() && + target->GetAggroCount() == 0 + ) + ) { + uint32 character_id = target->CharacterID(); + uint32 faction_id = std::stoul(faction_filter.c_str()); + if (target->ReloadCharacterFaction(target, faction_id, character_id)) { + c->Message( + Chat::White, + fmt::format( + "Faction Reset | {} ({}) was reset for {}.", + content_db.GetFactionName(faction_id), + faction_id, + target->GetCleanName() + ).c_str() + ); + } + else { + c->Message( + Chat::White, + fmt::format( + "Faction Reset Failed | {} ({}) was unable to be reset for {}.", + content_db.GetFactionName(faction_id), + faction_id, + target->GetCleanName() + ).c_str() + ); + } + } + else { + c->Message( + Chat::White, + "You cannot reset factions while you or your target is in combat or feigned." + ); + return; + } + } + else { + c->Message(Chat::White, "You must target a PC for this command."); + return; + } + } + else { + c->Message( + Chat::White, + "Usage: #faction reset [Faction ID] - Reset Targeted Player's Faction to Base Faction Value" + ); + } + } + else if (!strcasecmp(sep->arg[1], "view")) { + if (c->GetTarget() && c->GetTarget()->IsNPC()) { + Mob *target = c->GetTarget(); + uint32 npc_id = target->GetNPCTypeID(); + uint32 npc_faction_id = target->CastToNPC()->GetPrimaryFaction(); + std::string npc_name = target->GetCleanName(); + c->Message( + Chat::White, + fmt::format( + "{} ({}) has a Primary Faction of {} ({}).", + npc_name, + npc_id, + content_db.GetFactionName(npc_faction_id), + npc_faction_id + ).c_str() + ); + } + } +} + diff --git a/zone/gm_commands/findclass.cpp b/zone/gm_commands/findclass.cpp new file mode 100755 index 000000000..a220f5ded --- /dev/null +++ b/zone/gm_commands/findclass.cpp @@ -0,0 +1,84 @@ +#include "../client.h" + +void command_findclass(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + + if (arguments == 0) { + c->Message(Chat::White, "Command Syntax: #findclass [search criteria]"); + return; + } + + if (sep->IsNumber(1)) { + int class_id = std::stoi(sep->arg[1]); + if (class_id >= WARRIOR && class_id <= MERCERNARY_MASTER) { + std::string class_name = GetClassIDName(class_id); + c->Message( + Chat::White, + fmt::format( + "Class {}: {}", + class_id, + class_name + ).c_str() + ); + } + else { + c->Message( + Chat::White, + fmt::format( + "Class ID {} was not found.", + class_id + ).c_str() + ); + } + } + else { + std::string search_criteria = str_tolower(sep->argplus[1]); + int found_count = 0; + for (int class_id = WARRIOR; class_id <= MERCERNARY_MASTER; class_id++) { + std::string class_name = GetClassIDName(class_id); + std::string class_name_lower = str_tolower(class_name); + if (search_criteria.length() > 0 && class_name_lower.find(search_criteria) == std::string::npos) { + continue; + } + + c->Message( + Chat::White, + fmt::format( + "Class {}: {}", + class_id, + class_name + ).c_str() + ); + found_count++; + + if (found_count == 20) { + break; + } + } + + if (found_count == 20) { + c->Message(Chat::White, "20 Classes found... max reached."); + } + else { + auto class_message = ( + found_count > 0 ? + ( + found_count == 1 ? + "A Class was" : + fmt::format("{} Classes were", found_count) + ) : + "No Classes were" + ); + + c->Message( + Chat::White, + fmt::format( + "{} found.", + class_message + ).c_str() + ); + } + } +} + diff --git a/zone/gm_commands/findfaction.cpp b/zone/gm_commands/findfaction.cpp new file mode 100755 index 000000000..29b72f441 --- /dev/null +++ b/zone/gm_commands/findfaction.cpp @@ -0,0 +1,89 @@ +#include "../client.h" + +void command_findfaction(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + + if (arguments == 0) { + c->Message(Chat::White, "Command Syntax: #findfaction [search criteria]"); + return; + } + + if (sep->IsNumber(1)) { + int faction_id = std::stoi(sep->arg[1]); + auto faction_name = content_db.GetFactionName(faction_id); + if (!faction_name.empty()) { + c->Message( + Chat::White, + fmt::format( + "Faction {}: {}", + faction_id, + faction_name + ).c_str() + ); + } + else { + c->Message( + Chat::White, + fmt::format( + "Faction ID {} was not found.", + faction_id + ).c_str() + ); + } + } + else { + std::string search_criteria = str_tolower(sep->argplus[1]); + int found_count = 0; + int max_faction_id = content_db.GetMaxFaction(); + for (int faction_id = 0; faction_id < max_faction_id; faction_id++) { + std::string faction_name = content_db.GetFactionName(faction_id); + std::string faction_name_lower = str_tolower(faction_name); + if (faction_name.empty()) { + continue; + } + + if (faction_name.find(search_criteria) == std::string::npos) { + continue; + } + + c->Message( + Chat::White, + fmt::format( + "Faction {}: {}", + faction_id, + faction_name + ).c_str() + ); + found_count++; + + if (found_count == 20) { + break; + } + } + + if (found_count == 20) { + c->Message(Chat::White, "20 Factions found... max reached."); + } + else { + auto faction_message = ( + found_count > 0 ? + ( + found_count == 1 ? + "A Faction was" : + fmt::format("{} Factions were", found_count) + ) : + "No Factions were" + ); + + c->Message( + Chat::White, + fmt::format( + "{} found.", + faction_message + ).c_str() + ); + } + } +} + diff --git a/zone/gm_commands/findnpctype.cpp b/zone/gm_commands/findnpctype.cpp new file mode 100755 index 000000000..991bd8f18 --- /dev/null +++ b/zone/gm_commands/findnpctype.cpp @@ -0,0 +1,77 @@ +#include "../client.h" + +void command_findnpctype(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments) { + c->Message(Chat::White, "Usage: #findnpctype [Search Criteria]"); + return; + } + + std::string query; + std::string search_criteria = sep->arg[1]; + if (sep->IsNumber(1)) { + query = fmt::format( + "SELECT id, name FROM npc_types WHERE id = {}", + search_criteria + ); + } + else { + query = fmt::format( + "SELECT id, name FROM npc_types WHERE name LIKE '%%{}%%'", + search_criteria + ); + } + + auto results = content_db.QueryDatabase(query); + if (!results.Success() || !results.RowCount()) { + c->Message( + Chat::White, + fmt::format( + "No matches found for '{}'.", + search_criteria + ).c_str() + ); + return; + } + + int found_count = 0; + + for (auto row : results) { + int found_number = (found_count + 1); + if (found_count == 20) { + break; + } + + c->Message( + Chat::White, + fmt::format( + "NPC {} | {} ({})", + found_number, + row[1], + row[0] + ).c_str() + ); + found_count++; + } + + if (found_count == 20) { + c->Message(Chat::White, "20 NPCs were found, max reached."); + } + else { + auto npc_message = ( + found_count == 1 ? + "An NPC was" : + fmt::format("{} NPCs were", found_count) + ); + + c->Message( + Chat::White, + fmt::format( + "{} found.", + npc_message + ).c_str() + ); + } +} + diff --git a/zone/gm_commands/findrace.cpp b/zone/gm_commands/findrace.cpp new file mode 100755 index 000000000..413687160 --- /dev/null +++ b/zone/gm_commands/findrace.cpp @@ -0,0 +1,84 @@ +#include "../client.h" + +void command_findrace(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + + if (arguments == 0) { + c->Message(Chat::White, "Command Syntax: #findrace [search criteria]"); + return; + } + + if (sep->IsNumber(1)) { + int race_id = std::stoi(sep->arg[1]); + std::string race_name = GetRaceIDName(race_id); + if (race_id >= RACE_HUMAN_1 && race_id <= RACE_PEGASUS_732) { + c->Message( + Chat::White, + fmt::format( + "Race {}: {}", + race_id, + race_name + ).c_str() + ); + } + else { + c->Message( + Chat::White, + fmt::format( + "Race ID {} was not found.", + race_id + ).c_str() + ); + } + } + else { + std::string search_criteria = str_tolower(sep->argplus[1]); + int found_count = 0; + for (int race_id = RACE_HUMAN_1; race_id <= RACE_PEGASUS_732; race_id++) { + std::string race_name = GetRaceIDName(race_id); + std::string race_name_lower = str_tolower(race_name); + if (search_criteria.length() > 0 && race_name_lower.find(search_criteria) == std::string::npos) { + continue; + } + + c->Message( + Chat::White, + fmt::format( + "Race {}: {}", + race_id, + race_name + ).c_str() + ); + found_count++; + + if (found_count == 20) { + break; + } + } + + if (found_count == 20) { + c->Message(Chat::White, "20 Races found... max reached."); + } + else { + auto race_message = ( + found_count > 0 ? + ( + found_count == 1 ? + "A Race was" : + fmt::format("{} Races were", found_count) + ) : + "No Races were" + ); + + c->Message( + Chat::White, + fmt::format( + "{} found.", + race_message + ).c_str() + ); + } + } +} + diff --git a/zone/gm_commands/findskill.cpp b/zone/gm_commands/findskill.cpp new file mode 100755 index 000000000..b26ad3ab1 --- /dev/null +++ b/zone/gm_commands/findskill.cpp @@ -0,0 +1,90 @@ +#include "../client.h" + +void command_findskill(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + + if (arguments == 0) { + c->Message(Chat::White, "Command Syntax: #findskill [search criteria]"); + return; + } + + std::map skills = EQ::skills::GetSkillTypeMap(); + if (sep->IsNumber(1)) { + int skill_id = std::stoi(sep->arg[1]); + if (skill_id >= EQ::skills::Skill1HBlunt && skill_id < EQ::skills::SkillCount) { + for (auto skill : skills) { + if (skill_id == skill.first) { + c->Message( + Chat::White, + fmt::format( + "Skill {}: {}", + skill.first, + skill.second + ).c_str() + ); + break; + } + } + } + else { + c->Message( + Chat::White, + fmt::format( + "Skill ID {} was not found.", + skill_id + ).c_str() + ); + } + } + else { + std::string search_criteria = str_tolower(sep->argplus[1]); + if (!search_criteria.empty()) { + int found_count = 0; + for (auto skill : skills) { + std::string skill_name_lower = str_tolower(skill.second); + if (skill_name_lower.find(search_criteria) == std::string::npos) { + continue; + } + + c->Message( + Chat::White, + fmt::format( + "Skill {}: {}", + skill.first, + skill.second + ).c_str() + ); + found_count++; + + if (found_count == 20) { + break; + } + } + + if (found_count == 20) { + c->Message(Chat::White, "20 Skills were found, max reached."); + } + else { + auto skill_message = ( + found_count > 0 ? + ( + found_count == 1 ? + "A Skill was" : + fmt::format("{} Skills were", found_count) + ) : + "No Skills were" + ); + + c->Message( + Chat::White, + fmt::format( + "{} found.", + skill_message + ).c_str() + ); + } + } + } +} + diff --git a/zone/gm_commands/findspell.cpp b/zone/gm_commands/findspell.cpp new file mode 100755 index 000000000..b17febac4 --- /dev/null +++ b/zone/gm_commands/findspell.cpp @@ -0,0 +1,129 @@ +#include "../client.h" + +void command_findspell(Client *c, const Seperator *sep) +{ + if (SPDAT_RECORDS <= 0) { + c->Message(Chat::White, "Spells not loaded"); + return; + } + + int arguments = sep->argnum; + + if (arguments == 0) { + c->Message(Chat::White, "Command Syntax: #findspell [search criteria]"); + return; + } + + if (sep->IsNumber(1)) { + int spell_id = std::stoi(sep->arg[1]); + if (!IsValidSpell(spell_id)) { + c->Message( + Chat::White, + fmt::format( + "Spell ID {} was not found.", + spell_id + ).c_str() + ); + } + else { + c->Message( + Chat::White, + fmt::format( + "Spell {}: {}", + spell_id, + spells[spell_id].name + ).c_str() + ); + } + } + else { + std::string search_criteria = str_tolower(sep->argplus[1]); + int found_count = 0; + for (int spell_id = 0; spell_id < SPDAT_RECORDS; spell_id++) { + auto current_spell = spells[spell_id]; + if (current_spell.name[0] != 0) { + std::string spell_name = current_spell.name; + std::string spell_name_lower = str_tolower(spell_name); + if (search_criteria.length() > 0 && spell_name_lower.find(search_criteria) == std::string::npos) { + continue; + } + + c->Message( + Chat::White, + fmt::format( + "Spell {}: {}", + spell_id, + spell_name + ).c_str() + ); + found_count++; + + if (found_count == 20) { + break; + } + } + } + + if (found_count == 20) { + c->Message(Chat::White, "20 Spells found... max reached."); + } + else { + auto spell_message = ( + found_count > 0 ? + ( + found_count == 1 ? + "A Spell was" : + fmt::format("{} Spells were", found_count) + ) : + "No Spells were" + ); + + c->Message( + Chat::White, + fmt::format( + "{} found.", + spell_message + ).c_str() + ); + } + } +} + +inline bool CastRestrictedSpell(int spellid) +{ + switch (spellid) { + case SPELL_TOUCH_OF_VINITRAS: + case SPELL_DESPERATE_HOPE: + case SPELL_CHARM: + case SPELL_METAMORPHOSIS65: + case SPELL_JT_BUFF: + case SPELL_CAN_O_WHOOP_ASS: + case SPELL_PHOENIX_CHARM: + case SPELL_CAZIC_TOUCH: + case SPELL_AVATAR_KNOCKBACK: + case SPELL_SHAPECHANGE65: + case SPELL_SUNSET_HOME1218: + case SPELL_SUNSET_HOME819: + case SPELL_SHAPECHANGE75: + case SPELL_SHAPECHANGE80: + case SPELL_SHAPECHANGE85: + case SPELL_SHAPECHANGE90: + case SPELL_SHAPECHANGE95: + case SPELL_SHAPECHANGE100: + case SPELL_SHAPECHANGE25: + case SPELL_SHAPECHANGE30: + case SPELL_SHAPECHANGE35: + case SPELL_SHAPECHANGE40: + case SPELL_SHAPECHANGE45: + case SPELL_SHAPECHANGE50: + case SPELL_NPC_AEGOLISM: + case SPELL_SHAPECHANGE55: + case SPELL_SHAPECHANGE60: + case SPELL_COMMAND_OF_DRUZZIL: + case SPELL_SHAPECHANGE70: + return true; + default: + return false; + } +} + diff --git a/zone/gm_commands/findtask.cpp b/zone/gm_commands/findtask.cpp new file mode 100755 index 000000000..5edf0dcde --- /dev/null +++ b/zone/gm_commands/findtask.cpp @@ -0,0 +1,89 @@ +#include "../client.h" + +void command_findtask(Client *c, const Seperator *sep) +{ + if (RuleB(TaskSystem, EnableTaskSystem)) { + int arguments = sep->argnum; + + if (arguments == 0) { + c->Message(Chat::White, "Command Syntax: #findtask [search criteria]"); + return; + } + + if (sep->IsNumber(1)) { + auto task_id = std::stoul(sep->arg[1]); + auto task_name = task_manager->GetTaskName(task_id); + auto task_message = ( + !task_name.empty() ? + fmt::format( + "Task {}: {}", + task_id, + task_name + ).c_str() : + fmt::format( + "Task ID {} was not found.", + task_id + ).c_str() + ); + + c->Message( + Chat::White, + task_message + ); + } + else { + std::string search_criteria = str_tolower(sep->argplus[1]); + if (!search_criteria.empty()) { + int found_count = 0; + for (uint32 task_id = 1; task_id <= MAXTASKS; task_id++) { + auto task_name = task_manager->GetTaskName(task_id); + std::string task_name_lower = str_tolower(task_name); + if (task_name_lower.find(search_criteria) == std::string::npos) { + continue; + } + + c->Message( + Chat::White, + fmt::format( + "Task {}: {}", + task_id, + task_name + ).c_str() + ); + found_count++; + + if (found_count == 20) { + break; + } + } + + if (found_count == 20) { + c->Message(Chat::White, "20 Tasks were found, max reached."); + } + else { + auto task_message = ( + found_count > 0 ? + ( + found_count == 1 ? + "A Task was" : + fmt::format("{} Tasks were", found_count) + ) : + "No Tasks were" + ); + + c->Message( + Chat::White, + fmt::format( + "{} found.", + task_message + ).c_str() + ); + } + } + } + } + else { + c->Message(Chat::White, "This command cannot be used while the Task system is disabled."); + } +} + diff --git a/zone/gm_commands/findzone.cpp b/zone/gm_commands/findzone.cpp new file mode 100755 index 000000000..53dacc52d --- /dev/null +++ b/zone/gm_commands/findzone.cpp @@ -0,0 +1,95 @@ +#include "../client.h" + +void command_findzone(Client *c, const Seperator *sep) +{ + if (sep->arg[1][0] == 0) { + c->Message(Chat::White, "Usage: #findzone [search criteria]"); + c->Message(Chat::White, "Usage: #findzone expansion [expansion number]"); + return; + } + + std::string query; + int id = atoi((const char *) sep->arg[1]); + + std::string arg1 = sep->arg[1]; + + if (arg1 == "expansion") { + query = fmt::format( + "SELECT zoneidnumber, short_name, long_name, version FROM zone WHERE expansion = {}", + sep->arg[2] + ); + } + else { + + /** + * If id evaluates to 0, then search as if user entered a string + */ + if (id == 0) { + query = fmt::format( + "SELECT zoneidnumber, short_name, long_name, version FROM zone WHERE long_name LIKE '%{}%' OR `short_name` LIKE '%{}%'", + EscapeString(sep->arg[1]), + EscapeString(sep->arg[1]) + ); + } + else { + query = fmt::format( + "SELECT zoneidnumber, short_name, long_name, version FROM zone WHERE zoneidnumber = {}", + id + ); + } + } + + auto results = content_db.QueryDatabase(query); + if (!results.Success()) { + c->Message(Chat::White, "Error querying database."); + c->Message(Chat::White, query.c_str()); + return; + } + + int count = 0; + const int maxrows = 100; + + for (auto row = results.begin(); row != results.end(); ++row) { + std::string zone_id = row[0]; + std::string short_name = row[1]; + std::string long_name = row[2]; + int version = atoi(row[3]); + + if (++count > maxrows) { + c->Message(Chat::White, "%i zones shown. Too many results.", maxrows); + break; + } + + std::string command_zone = EQ::SayLinkEngine::GenerateQuestSaylink("#zone " + short_name, false, "zone"); + std::string command_gmzone = EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format("#gmzone {} {}", short_name, version), + false, + "gmzone" + ); + + c->Message( + Chat::White, + fmt::format( + "[{}] [{}] [{}] ID ({}) Version ({}) [{}]", + (version == 0 ? command_zone : "zone"), + command_gmzone, + short_name, + zone_id, + version, + long_name + ).c_str() + ); + } + + if (count <= maxrows) { + c->Message( + Chat::White, + "Query complete. %i rows shown. %s", + count, + (arg1 == "expansion" ? "(expansion search)" : "")); + } + else if (count == 0) { + c->Message(Chat::White, "No matches found for %s.", sep->arg[1]); + } +} + diff --git a/zone/gm_commands/fixmob.cpp b/zone/gm_commands/fixmob.cpp new file mode 100755 index 000000000..00e2edf11 --- /dev/null +++ b/zone/gm_commands/fixmob.cpp @@ -0,0 +1,250 @@ +#include "../client.h" + +void command_fixmob(Client *c, const Seperator *sep) +{ + Mob *target = c->GetTarget(); + const char *Usage = "Usage: #fixmob [race|gender|texture|helm|face|hair|haircolor|beard|beardcolor|heritage|tattoo|detail] [next|prev]"; + + if (!sep->arg[1]) { + c->Message(Chat::White, Usage); + } + else if (!target) { + c->Message(Chat::White, "Error: this command requires a target"); + } + else { + + uint32 Adjustment = 1; // Previous or Next + char codeMove = 0; + + if (sep->arg[2]) { + char *command2 = sep->arg[2]; + codeMove = (command2[0] | 0x20); // First character, lower-cased + if (codeMove == 'n') { + Adjustment = 1; + } + else if (codeMove == 'p') { + Adjustment = -1; + } + } + + uint16 Race = target->GetRace(); + uint8 Gender = target->GetGender(); + uint8 Texture = 0xFF; + uint8 HelmTexture = 0xFF; + uint8 HairColor = target->GetHairColor(); + uint8 BeardColor = target->GetBeardColor(); + uint8 EyeColor1 = target->GetEyeColor1(); + uint8 EyeColor2 = target->GetEyeColor2(); + uint8 HairStyle = target->GetHairStyle(); + uint8 LuclinFace = target->GetLuclinFace(); + uint8 Beard = target->GetBeard(); + uint32 DrakkinHeritage = target->GetDrakkinHeritage(); + uint32 DrakkinTattoo = target->GetDrakkinTattoo(); + uint32 DrakkinDetails = target->GetDrakkinDetails(); + + const char *ChangeType = nullptr; // If it's still nullptr after processing, they didn't send a valid command + uint32 ChangeSetting; + char *command = sep->arg[1]; + + if (strcasecmp(command, "race") == 0) { + if (Race == 1 && codeMove == 'p') { + Race = RuleI(NPC, MaxRaceID); + } + else if (Race >= RuleI(NPC, MaxRaceID) && codeMove != 'p') { + Race = 1; + } + else { + Race += Adjustment; + } + ChangeType = "Race"; + ChangeSetting = Race; + } + else if (strcasecmp(command, "gender") == 0) { + if (Gender == 0 && codeMove == 'p') { + Gender = 2; + } + else if (Gender >= 2 && codeMove != 'p') { + Gender = 0; + } + else { + Gender += Adjustment; + } + ChangeType = "Gender"; + ChangeSetting = Gender; + } + else if (strcasecmp(command, "texture") == 0) { + Texture = target->GetTexture(); + + if (Texture == 0 && codeMove == 'p') { + Texture = 25; + } + else if (Texture >= 25 && codeMove != 'p') { + Texture = 0; + } + else { + Texture += Adjustment; + } + ChangeType = "Texture"; + ChangeSetting = Texture; + } + else if (strcasecmp(command, "helm") == 0) { + HelmTexture = target->GetHelmTexture(); + if (HelmTexture == 0 && codeMove == 'p') { + HelmTexture = 25; + } + else if (HelmTexture >= 25 && codeMove != 'p') { + HelmTexture = 0; + } + else { + HelmTexture += Adjustment; + } + ChangeType = "HelmTexture"; + ChangeSetting = HelmTexture; + } + else if (strcasecmp(command, "face") == 0) { + if (LuclinFace == 0 && codeMove == 'p') { + LuclinFace = 87; + } + else if (LuclinFace >= 87 && codeMove != 'p') { + LuclinFace = 0; + } + else { + LuclinFace += Adjustment; + } + ChangeType = "LuclinFace"; + ChangeSetting = LuclinFace; + } + else if (strcasecmp(command, "hair") == 0) { + if (HairStyle == 0 && codeMove == 'p') { + HairStyle = 8; + } + else if (HairStyle >= 8 && codeMove != 'p') { + HairStyle = 0; + } + else { + HairStyle += Adjustment; + } + ChangeType = "HairStyle"; + ChangeSetting = HairStyle; + } + else if (strcasecmp(command, "haircolor") == 0) { + if (HairColor == 0 && codeMove == 'p') { + HairColor = 24; + } + else if (HairColor >= 24 && codeMove != 'p') { + HairColor = 0; + } + else { + HairColor += Adjustment; + } + ChangeType = "HairColor"; + ChangeSetting = HairColor; + } + else if (strcasecmp(command, "beard") == 0) { + if (Beard == 0 && codeMove == 'p') { + Beard = 11; + } + else if (Beard >= 11 && codeMove != 'p') { + Beard = 0; + } + else { + Beard += Adjustment; + } + ChangeType = "Beard"; + ChangeSetting = Beard; + } + else if (strcasecmp(command, "beardcolor") == 0) { + if (BeardColor == 0 && codeMove == 'p') { + BeardColor = 24; + } + else if (BeardColor >= 24 && codeMove != 'p') { + BeardColor = 0; + } + else { + BeardColor += Adjustment; + } + ChangeType = "BeardColor"; + ChangeSetting = BeardColor; + } + else if (strcasecmp(command, "heritage") == 0) { + if (DrakkinHeritage == 0 && codeMove == 'p') { + DrakkinHeritage = 6; + } + else if (DrakkinHeritage >= 6 && codeMove != 'p') { + DrakkinHeritage = 0; + } + else { + DrakkinHeritage += Adjustment; + } + ChangeType = "DrakkinHeritage"; + ChangeSetting = DrakkinHeritage; + } + else if (strcasecmp(command, "tattoo") == 0) { + if (DrakkinTattoo == 0 && codeMove == 'p') { + DrakkinTattoo = 8; + } + else if (DrakkinTattoo >= 8 && codeMove != 'p') { + DrakkinTattoo = 0; + } + else { + DrakkinTattoo += Adjustment; + } + ChangeType = "DrakkinTattoo"; + ChangeSetting = DrakkinTattoo; + } + else if (strcasecmp(command, "detail") == 0) { + if (DrakkinDetails == 0 && codeMove == 'p') { + DrakkinDetails = 7; + } + else if (DrakkinDetails >= 7 && codeMove != 'p') { + DrakkinDetails = 0; + } + else { + DrakkinDetails += Adjustment; + } + ChangeType = "DrakkinDetails"; + ChangeSetting = DrakkinDetails; + } + + // Hack to fix some races that base features from face + switch (Race) { + case 2: // Barbarian + if (LuclinFace > 10) { + LuclinFace -= ((DrakkinTattoo - 1) * 10); + } + LuclinFace += (DrakkinTattoo * 10); + break; + case 3: // Erudite + if (LuclinFace > 10) { + LuclinFace -= ((HairStyle - 1) * 10); + } + LuclinFace += (HairStyle * 10); + break; + case 5: // HighElf + case 6: // DarkElf + case 7: // HalfElf + if (LuclinFace > 10) { + LuclinFace -= ((Beard - 1) * 10); + } + LuclinFace += (Beard * 10); + break; + default: + break; + } + + + if (ChangeType == nullptr) { + c->Message(Chat::White, Usage); + } + else { + target->SendIllusionPacket( + Race, Gender, Texture, HelmTexture, HairColor, BeardColor, + EyeColor1, EyeColor2, HairStyle, LuclinFace, Beard, 0xFF, + DrakkinHeritage, DrakkinTattoo, DrakkinDetails + ); + + c->Message(Chat::White, "%s=%i", ChangeType, ChangeSetting); + } + } +} + diff --git a/zone/gm_commands/flag.cpp b/zone/gm_commands/flag.cpp new file mode 100755 index 000000000..8f02c8624 --- /dev/null +++ b/zone/gm_commands/flag.cpp @@ -0,0 +1,53 @@ +#include "../client.h" +#include "../worldserver.h" + +extern WorldServer worldserver; + +void command_flag(Client *c, const Seperator *sep) +{ + if (sep->arg[2][0] == 0) { + if (!c->GetTarget() || (c->GetTarget() && c->GetTarget() == c)) { + c->UpdateAdmin(); + c->Message(Chat::White, "Refreshed your admin flag from DB."); + } + else if (c->GetTarget() && c->GetTarget() != c && c->GetTarget()->IsClient()) { + c->GetTarget()->CastToClient()->UpdateAdmin(); + c->Message(Chat::White, "%s's admin flag has been refreshed.", c->GetTarget()->GetName()); + c->GetTarget()->Message(Chat::White, "%s refreshed your admin flag.", c->GetName()); + } + } + else if (!sep->IsNumber(1) || atoi(sep->arg[1]) < -2 || atoi(sep->arg[1]) > 255 || strlen(sep->arg[2]) == 0) { + c->Message(Chat::White, "Usage: #flag [status] [acctname]"); + } + + else if (c->Admin() < commandChangeFlags) { + //this check makes banning players by less than this level + //impossible, but i'll leave it in anyways + c->Message(Chat::White, "You may only refresh your own flag, doing so now."); + c->UpdateAdmin(); + } + else { + if (atoi(sep->arg[1]) > c->Admin()) { + c->Message(Chat::White, "You cannot set people's status to higher than your own"); + } + else if (atoi(sep->arg[1]) < 0 && c->Admin() < commandBanPlayers) { + c->Message(Chat::White, "You have too low of status to suspend/ban"); + } + else if (!database.SetAccountStatus(sep->argplus[2], atoi(sep->arg[1]))) { + c->Message(Chat::White, "Unable to set GM Flag."); + } + else { + c->Message(Chat::White, "Set GM Flag on account."); + + std::string user; + std::string loginserver; + ParseAccountString(sep->argplus[2], user, loginserver); + + ServerPacket pack(ServerOP_FlagUpdate, 6); + *((uint32 *) pack.pBuffer) = database.GetAccountIDByName(user.c_str(), loginserver.c_str()); + *((int16 *) &pack.pBuffer[4]) = atoi(sep->arg[1]); + worldserver.SendPacket(&pack); + } + } +} + diff --git a/zone/gm_commands/flagedit.cpp b/zone/gm_commands/flagedit.cpp new file mode 100755 index 000000000..2b3cf746b --- /dev/null +++ b/zone/gm_commands/flagedit.cpp @@ -0,0 +1,290 @@ +#include "../client.h" + +void command_flagedit(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments) { + auto flags_link = EQ::SayLinkEngine::GenerateQuestSaylink("#flags", false, "#flags"); + c->Message( + Chat::White, + "Usage: #flagedit lock [Zone ID|Zone Short Name] [Flag Name] - Set the specified flag name on the zone, locking the zone" + ); + c->Message( + Chat::White, + "Usage: #flagedit unlock [Zone ID|Zone Short Name] - Removes the flag requirement from the specified zone" + ); + c->Message( + Chat::White, + "Usage: #flagedit list - List all zones which require a flag, and their flag's name" + ); + c->Message( + Chat::White, + "Usage: #flagedit give [Zone ID|Zone Short Name] - Give your target the zone flag for the specified zone." + ); + c->Message( + Chat::White, + "Usage: #flagedit take [Zone ID|Zone Short Name] - Take the zone flag for the specified zone away from your target" + ); + c->Message( + Chat::White, + fmt::format( + "Note: Use {} to view the flags a player has.", + flags_link + ).c_str() + ); + return; + } + + bool is_give = !strcasecmp(sep->arg[1], "give"); + bool is_list = !strcasecmp(sep->arg[1], "list"); + bool is_lock = !strcasecmp(sep->arg[1], "lock"); + bool is_take = !strcasecmp(sep->arg[1], "take"); + bool is_unlock = !strcasecmp(sep->arg[1], "unlock"); + + if ( + !is_give && + !is_list && + !is_lock && + !is_take && + !is_unlock + ) { + auto flags_link = EQ::SayLinkEngine::GenerateQuestSaylink("#flags", false, "#flags"); + c->Message( + Chat::White, + "Usage: #flagedit lock [Zone ID|Zone Short Name] [Flag Name] - Set the specified flag name on the zone, locking the zone" + ); + c->Message( + Chat::White, + "Usage: #flagedit unlock [Zone ID|Zone Short Name] - Removes the flag requirement from the specified zone" + ); + c->Message( + Chat::White, + "Usage: #flagedit list - List all zones which require a flag, and their flag's name" + ); + c->Message( + Chat::White, + "Usage: #flagedit give [Zone ID|Zone Short Name] - Give your target the zone flag for the specified zone." + ); + c->Message( + Chat::White, + "Usage: #flagedit take [Zone ID|Zone Short Name] - Take the zone flag for the specified zone away from your target" + ); + c->Message( + Chat::White, + fmt::format( + "Note: Use {} to view the flags a player has.", + flags_link + ).c_str() + ); + return; + } + + if (is_give) { + uint32 zone_id = ( + sep->IsNumber(2) ? + std::stoul(sep->arg[2]) : + ZoneID(sep->arg[2]) + ); + std::string zone_short_name = str_tolower(ZoneName(zone_id, true)); + bool is_unknown_zone = zone_short_name.find("unknown") != std::string::npos; + if (zone_id && !is_unknown_zone) { + std::string zone_long_name = ZoneLongName(zone_id); + auto target = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); + } + + target->SetZoneFlag(zone_id); + c->Message( + Chat::White, + fmt::format( + "{} now {} the flag for {} ({}).", + c == target ? + "You" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ), + c == target ? "have" : "has", + zone_long_name, + zone_id + ).c_str() + ); + return; + } + } else if (is_list) { + std::string query = SQL( + SELECT long_name, zoneidnumber, version, flag_needed + FROM zone + WHERE flag_needed != '' + ORDER BY long_name ASC + ); + auto results = content_db.QueryDatabase(query); + if (!results.Success()) { + return; + } + + std::string popup_text = ""; + + popup_text += ""; + + for (auto row : results) { + popup_text += fmt::format( + "", + row[0], + row[1], + ( + std::stoi(row[2]) != 0 ? + fmt::format( + "[Version {}]", + row[2] + ) : + "" + ), + row[3] + ); + } + + popup_text += "
ZoneFlag Required
{} ({}){}{}
"; + + c->SendPopupToClient( + "Zone Flags", + popup_text.c_str() + ); + + return; + } else if (is_lock) { + uint32 zone_id = ( + sep->IsNumber(2) ? + std::stoul(sep->arg[2]) : + ZoneID(sep->arg[2]) + ); + std::string zone_short_name = str_tolower(ZoneName(zone_id, true)); + bool is_unknown_zone = zone_short_name.find("unknown") != std::string::npos; + if (zone_id && !is_unknown_zone) { + if (arguments < 3) { + c->Message( + Chat::White, + "Usage: #flagedit lock [Zone ID|Zone Short Name] [Flag Name] - Set the specified flag name on the zone, locking the zone" + ); + return; + } + + std::string flag_name = EscapeString(sep->argplus[3]); + std::string zone_long_name = ZoneLongName(zone_id); + + auto query = fmt::format( + SQL( + UPDATE zone + SET flag_needed = '{}' + WHERE zoneidnumber = {} AND version = {} + ), + flag_name, + zone_id, + zone->GetInstanceVersion() + ); + + auto results = content_db.QueryDatabase(query); + if (!results.Success()) { + c->Message( + Chat::White, + fmt::format( + "Error updating zone flag for {} ({}).", + zone_long_name, + zone_id + ).c_str() + ); + return; + } + + c->Message( + Chat::White, + fmt::format( + "{} ({}) now requires a flag, named {}.", + zone_long_name, + zone_id, + flag_name + ).c_str() + ); + return; + } + } else if (is_take) { + uint32 zone_id = ( + sep->IsNumber(2) ? + std::stoul(sep->arg[2]) : + ZoneID(sep->arg[2]) + ); + std::string zone_short_name = str_tolower(ZoneName(zone_id, true)); + bool is_unknown_zone = zone_short_name.find("unknown") != std::string::npos; + if (zone_id && !is_unknown_zone) { + std::string zone_long_name = ZoneLongName(zone_id); + auto target = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); + } + + target->ClearZoneFlag(zone_id); + c->Message( + Chat::White, + fmt::format( + "{} no longer {} the flag for {} ({}).", + c == target ? + "You" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ), + c == target ? "have" : "has", + zone_long_name, + zone_id + ).c_str() + ); + return; + } + } else if (is_unlock) { + uint32 zone_id = ( + sep->IsNumber(2) ? + std::stoul(sep->arg[2]) : + ZoneID(sep->arg[2]) + ); + std::string zone_short_name = str_tolower(ZoneName(zone_id, true)); + bool is_unknown_zone = zone_short_name.find("unknown") != std::string::npos; + if (zone_id && !is_unknown_zone) { + std::string zone_long_name = ZoneLongName(zone_id); + auto query = fmt::format( + SQL( + UPDATE zone + SET flag_needed = '' + WHERE zoneidnumber = {} AND version = {} + ), + zone_id, + zone->GetInstanceVersion() + ); + auto results = content_db.QueryDatabase(query); + if (!results.Success()) { + c->Message( + Chat::White, + fmt::format( + "Error updating zone flag for {} ({}).", + zone_long_name, + zone_id + ).c_str() + ); + return; + } + + c->Message( + Chat::White, + fmt::format( + "{} ({}) no longer requires a flag.", + zone_long_name, + zone_id + ).c_str() + ); + return; + } + } +} + diff --git a/zone/gm_commands/flags.cpp b/zone/gm_commands/flags.cpp new file mode 100755 index 000000000..8b28c7d69 --- /dev/null +++ b/zone/gm_commands/flags.cpp @@ -0,0 +1,17 @@ +#include "../client.h" + +void command_flags(Client *c, const Seperator *sep) +{ + Client *target = c; + + if ( + c->GetTarget() && + c->GetTarget()->IsClient() && + c->Admin() >= minStatusToSeeOthersZoneFlags + ) { + target = c->GetTarget()->CastToClient(); + } + + target->SendZoneFlagInfo(c); +} + diff --git a/zone/gm_commands/flymode.cpp b/zone/gm_commands/flymode.cpp new file mode 100755 index 000000000..c9c76fd98 --- /dev/null +++ b/zone/gm_commands/flymode.cpp @@ -0,0 +1,46 @@ +#include "../client.h" + +void command_flymode(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments || !sep->IsNumber(1)) { + return; + } + + Mob *target = c; + if (c->GetTarget()) { + target = c->GetTarget(); + } + + auto flymode_id = std::stoul(sep->arg[1]); + if ( + flymode_id < EQ::constants::GravityBehavior::Ground && + flymode_id > EQ::constants::GravityBehavior::LevitateWhileRunning + ) { + c->Message(Chat::White, "Usage:: #flymode [Flymode ID]"); + c->Message(Chat::White, "0 = Ground, 1 = Flying, 2 = Levitating, 3 = Water, 4 = Floating, 5 = Levitating While Running"); + return; + } + + target->SetFlyMode(static_cast(flymode_id)); + target->SendAppearancePacket(AT_Levitate, flymode_id); + c->Message( + Chat::White, + fmt::format( + "Fly Mode for {} is now {} ({}).", + ( + c == target ? + "yourself" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ), + EQ::constants::GetFlyModeName(flymode_id), + flymode_id + ).c_str() + ); +} + + diff --git a/zone/gm_commands/fov.cpp b/zone/gm_commands/fov.cpp new file mode 100755 index 000000000..50447a326 --- /dev/null +++ b/zone/gm_commands/fov.cpp @@ -0,0 +1,42 @@ +#include "../client.h" + +void command_fov(Client *c, const Seperator *sep) +{ + if (c->GetTarget()) { + auto target = c->GetTarget(); + std::string behind_message = ( + c->BehindMob( + target, + c->GetX(), + c->GetY() + ) ? + "behind" : + "not behind" + ); + std::string gender_message = ( + target->GetGender() == MALE ? + "he" : + ( + target->GetGender() == FEMALE ? + "she" : + "it" + ) + ); + + c->Message( + Chat::White, + fmt::format( + "You are {} {} ({}), {} has a heading of {}.", + behind_message, + target->GetCleanName(), + target->GetID(), + gender_message, + target->GetHeading() + ).c_str() + ); + } + else { + c->Message(Chat::White, "You must have a target to use this command."); + } +} + diff --git a/zone/gm_commands/freeze.cpp b/zone/gm_commands/freeze.cpp new file mode 100755 index 000000000..8cce36ba2 --- /dev/null +++ b/zone/gm_commands/freeze.cpp @@ -0,0 +1,12 @@ +#include "../client.h" + +void command_freeze(Client *c, const Seperator *sep) +{ + if (c->GetTarget() != 0) { + c->GetTarget()->SendAppearancePacket(AT_Anim, ANIM_FREEZE); + } + else { + c->Message(Chat::White, "ERROR: Freeze requires a target."); + } +} + diff --git a/zone/gm_commands/gassign.cpp b/zone/gm_commands/gassign.cpp new file mode 100755 index 000000000..1a830b2fd --- /dev/null +++ b/zone/gm_commands/gassign.cpp @@ -0,0 +1,14 @@ +#include "../client.h" + +void command_gassign(Client *c, const Seperator *sep) +{ + if (sep->IsNumber(1) && c->GetTarget() && c->GetTarget()->IsNPC() && + c->GetTarget()->CastToNPC()->GetSpawnPointID() > 0) { + int spawn2id = c->GetTarget()->CastToNPC()->GetSpawnPointID(); + database.AssignGrid(c, atoi(sep->arg[1]), spawn2id); + } + else { + c->Message(Chat::White, "Usage: #gassign [num] - must have an npc target!"); + } +} + diff --git a/zone/gm_commands/gearup.cpp b/zone/gm_commands/gearup.cpp new file mode 100755 index 000000000..e02ba600d --- /dev/null +++ b/zone/gm_commands/gearup.cpp @@ -0,0 +1,181 @@ +#include "../client.h" +#include "../../common/http/httplib.h" +#include "../../common/content/world_content_service.h" + +void command_gearup(Client *c, const Seperator *sep) +{ + std::string tool_table_name = "tool_gearup_armor_sets"; + if (!database.DoesTableExist(tool_table_name)) { + c->Message( + Chat::Yellow, + fmt::format( + "Table [{}] does not exist. Downloading from Github and installing...", + tool_table_name + ).c_str() + ); + + // http get request + httplib::Client cli("https://raw.githubusercontent.com"); + cli.set_connection_timeout(0, 15000000); // 15 sec + cli.set_read_timeout(15, 0); // 15 seconds + cli.set_write_timeout(15, 0); // 15 seconds + + int sourced_queries = 0; + std::string url = "/EQEmu/Server/master/utils/sql/git/optional/2020_07_20_tool_gearup_armor_sets.sql"; + + if (auto res = cli.Get(url.c_str())) { + if (res->status == 200) { + for (auto &s: SplitString(res->body, ';')) { + if (!trim(s).empty()) { + auto results = database.QueryDatabase(s); + if (!results.ErrorMessage().empty()) { + c->Message( + Chat::Yellow, + fmt::format( + "Error sourcing SQL [{}]", results.ErrorMessage() + ).c_str() + ); + return; + } + sourced_queries++; + } + } + } + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Error retrieving URL [{}]", + url + ).c_str() + ); + } + + c->Message( + Chat::Yellow, + fmt::format( + "Table [{}] installed. Sourced [{}] queries", + tool_table_name, sourced_queries + ).c_str() + ); + } + + std::string expansion_arg = sep->arg[1]; + std::string expansion_filter; + if (expansion_arg.length() > 0) { + expansion_filter = fmt::format("and `expansion` = {}", expansion_arg); + } + + auto results = database.QueryDatabase( + fmt::format( + SQL ( + select + item_id, + slot + from + {} + where + `class` = {} + and `level` = {} + {} + order by score desc, expansion desc + ), + tool_table_name, + c->GetClass(), + c->GetLevel(), + expansion_filter + ) + ); + + int items_equipped = 0; + int items_already_have = 0; + std::set equipped; + + for (auto row = results.begin(); row != results.end(); ++row) { + int item_id = atoi(row[0]); + int slot_id = atoi(row[1]); + + if (equipped.find(slot_id) != equipped.end()) { + if (slot_id == EQ::invslot::slotEar1) { + slot_id = EQ::invslot::slotEar2; + } + if (slot_id == EQ::invslot::slotFinger1) { + slot_id = EQ::invslot::slotFinger2; + } + if (slot_id == EQ::invslot::slotWrist1) { + slot_id = EQ::invslot::slotWrist2; + } + } + + if (equipped.find(slot_id) == equipped.end()) { + const EQ::ItemData *item = database.GetItem(item_id); + bool has_item = (c->GetInv().HasItem(item_id, 1, invWhereWorn) != INVALID_INDEX); + bool can_wear_item = !c->CheckLoreConflict(item) && !has_item; + if (!can_wear_item) { + items_already_have++; + } + + if (c->CastToMob()->CanClassEquipItem(item_id) && can_wear_item) { + equipped.insert(slot_id); + c->SummonItem( + item_id, + 0, 0, 0, 0, 0, 0, 0, 0, + slot_id + ); + items_equipped++; + } + } + } + + c->Message( + Chat::White, + fmt::format( + "Equipped items [{}] already had [{}] items equipped", + items_equipped, + items_already_have + ).c_str() + ); + + if (expansion_arg.empty()) { + results = database.QueryDatabase( + fmt::format( + SQL ( + select + expansion + from + {} + where + class = {} + and level = {} + group by + expansion; + ), + tool_table_name, + c->GetClass(), + c->GetLevel() + ) + ); + + c->Message(Chat::White, "Choose armor from a specific era"); + std::string message; + for (auto row = results.begin(); row != results.end(); ++row) { + int expansion = atoi(row[0]); + message += "[" + EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format("#gearup {}", expansion), + false, + Expansion::ExpansionName[expansion] + ) + "] "; + + if (message.length() > 2000) { + c->Message(Chat::White, message.c_str()); + message = ""; + } + } + if (message.length() > 0) { + c->Message(Chat::White, message.c_str()); + } + } + +} + diff --git a/zone/gm_commands/gender.cpp b/zone/gm_commands/gender.cpp new file mode 100755 index 000000000..000d65f23 --- /dev/null +++ b/zone/gm_commands/gender.cpp @@ -0,0 +1,46 @@ +#include "../client.h" + +void command_gender(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments || !sep->IsNumber(1)) { + c->Message(Chat::White, "Usage: #gender [Gender ID]"); + c->Message(Chat::White, "Genders: 0 = Male, 1 = Female, 2 = Neuter"); + return; + } + + Mob *target = c; + if (c->GetTarget() && c->Admin() >= commandGenderOthers) { + target = c->GetTarget(); + } + + auto gender_id = std::stoi(sep->arg[1]); + if (gender_id < 0 || gender_id > 2) { + c->Message(Chat::White, "Usage: #gender [Gender ID]"); + c->Message(Chat::White, "Genders: 0 = Male, 1 = Female, 2 = Neuter"); + return; + } + + target->SendIllusionPacket( + target->GetRace(), + gender_id + ); + + c->Message( + Chat::White, + fmt::format( + "Gender changed for {} to {} ({}).", + ( + c == target ? + "yourself" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ), + GetGenderName(gender_id), + gender_id + ).c_str() + ); +} diff --git a/zone/gm_commands/getplayerburiedcorpsecount.cpp b/zone/gm_commands/getplayerburiedcorpsecount.cpp new file mode 100755 index 000000000..20e82bac4 --- /dev/null +++ b/zone/gm_commands/getplayerburiedcorpsecount.cpp @@ -0,0 +1,33 @@ +#include "../client.h" +#include "../corpse.h" + +void command_getplayerburiedcorpsecount(Client *c, const Seperator *sep) +{ + Client *target = c; + if (c->GetTarget() && c->GetTarget()->IsClient() && c->GetGM()) { + target = c->GetTarget()->CastToClient(); + } + + uint32 corpse_count = database.GetCharacterBuriedCorpseCount(target->CharacterID()); + c->Message( + Chat::White, + fmt::format( + "{} {} buried corpse{}.", + ( + c == target ? + "You have" : + fmt::format( + "{} ({}) has", + target->GetCleanName(), + target->GetID() + ) + ), + ( + corpse_count ? + std::to_string(corpse_count) : + "no" + ), + corpse_count != 1 ? "s" : "" + ).c_str() + ); +} diff --git a/zone/gm_commands/getvariable.cpp b/zone/gm_commands/getvariable.cpp new file mode 100755 index 000000000..23c90219e --- /dev/null +++ b/zone/gm_commands/getvariable.cpp @@ -0,0 +1,13 @@ +#include "../client.h" + +void command_getvariable(Client *c, const Seperator *sep) +{ + std::string tmp; + if (database.GetVariable(sep->argplus[1], tmp)) { + c->Message(Chat::White, "%s = %s", sep->argplus[1], tmp.c_str()); + } + else { + c->Message(Chat::White, "GetVariable(%s) returned false", sep->argplus[1]); + } +} + diff --git a/zone/gm_commands/ginfo.cpp b/zone/gm_commands/ginfo.cpp new file mode 100755 index 000000000..65874bfd0 --- /dev/null +++ b/zone/gm_commands/ginfo.cpp @@ -0,0 +1,91 @@ +#include "../client.h" +#include "../groups.h" + +void command_ginfo(Client *c, const Seperator *sep) +{ + Client *target = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); + } + + auto target_group = target->GetGroup(); + if (!target_group) { + c->Message( + Chat::White, + fmt::format( + "{} not in a group.", + ( + c == target ? + "You are" : + fmt::format( + "{} ({}) is", + target->GetCleanName(), + target->GetID() + ) + ) + ).c_str() + ); + return; + } + + std::string popup_title = fmt::format( + "Group Info for {}", + c == target ? + "Yourself" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ); + std::string popup_text = ""; + popup_text += fmt::format( + "", + target_group->GetID(), + target_group->GroupCount() + ); + popup_text += "

"; + popup_text += ""; + popup_text += ""; + popup_text += ""; + popup_text += ""; + popup_text += ""; + popup_text += ""; + popup_text += ""; + popup_text += ""; + + for (int group_member = 0; group_member < MAX_GROUP_MEMBERS; group_member++) { + if (target_group->membername[group_member][0] == '\0') { + continue; + } + + bool is_assist = target_group->MemberRoles[group_member] & RoleAssist; + bool is_puller = target_group->MemberRoles[group_member] & RolePuller; + bool is_tank = target_group->MemberRoles[group_member] & RoleTank; + + popup_text += fmt::format( + "", + group_member, + ( + strcmp(target_group->membername[group_member], c->GetCleanName()) ? + target_group->membername[group_member] : + fmt::format( + "{} (You)", + target_group->membername[group_member] + ) + ), + target_group->members[group_member] ? "" : "", + is_assist ? "" : "", + is_puller ? "" : "", + is_tank ? "" : "" + ); + } + + popup_text += "
Group ID{}Members{}
IndexNameIn ZoneAssistPullerTank
{}{}{}{}{}{}
"; + + c->SendPopupToClient( + popup_title.c_str(), + popup_text.c_str() + ); +} + diff --git a/zone/gm_commands/giveitem.cpp b/zone/gm_commands/giveitem.cpp new file mode 100755 index 000000000..17458182b --- /dev/null +++ b/zone/gm_commands/giveitem.cpp @@ -0,0 +1,111 @@ +#include "../client.h" + +void command_giveitem(Client *c, const Seperator *sep) +{ + uint32 item_id = 0; + int16 charges = -1; + uint32 augment_one = 0; + uint32 augment_two = 0; + uint32 augment_three = 0; + uint32 augment_four = 0; + uint32 augment_five = 0; + uint32 augment_six = 0; + int arguments = sep->argnum; + std::string cmd_msg = sep->msg; + size_t link_open = cmd_msg.find('\x12'); + size_t link_close = cmd_msg.find_last_of('\x12'); + if (c->GetTarget()) { + if (!c->GetTarget()->IsClient()) { + c->Message(Chat::Red, "You can only give items to players with this command."); + return; + } + + if (link_open != link_close && (cmd_msg.length() - link_open) > EQ::constants::SAY_LINK_BODY_SIZE) { + EQ::SayLinkBody_Struct link_body; + EQ::saylink::DegenerateLinkBody( + link_body, + cmd_msg.substr(link_open + 1, EQ::constants::SAY_LINK_BODY_SIZE)); + item_id = link_body.item_id; + augment_one = link_body.augment_1; + augment_two = link_body.augment_2; + augment_three = link_body.augment_3; + augment_four = link_body.augment_4; + augment_five = link_body.augment_5; + augment_six = link_body.augment_6; + } + else if (sep->IsNumber(1)) { + item_id = atoi(sep->arg[1]); + } + else if (!sep->IsNumber(1)) { + c->Message( + Chat::Red, + "Usage: #giveitem [item id | link] [charges] [augment_one_id] [augment_two_id] [augment_three_id] [augment_four_id] [augment_five_id] [augment_six_id] (Charges are optional.)" + ); + return; + } + + Client *client_target = c->GetTarget()->CastToClient(); + uint8 item_status = 0; + uint8 current_status = c->Admin(); + const EQ::ItemData *item = database.GetItem(item_id); + if (item) { + item_status = item->MinStatus; + } + + if (item_status > current_status) { + c->Message( + Chat::White, + fmt::format( + "Insufficient status to summon this item, current status is {}, required status is {}.", + current_status, + item_status + ).c_str() + ); + return; + } + + if (arguments >= 2 && sep->IsNumber(2)) { + charges = atoi(sep->arg[2]); + } + + if (arguments >= 3 && sep->IsNumber(3)) { + augment_one = atoi(sep->arg[3]); + } + + if (arguments >= 4 && sep->IsNumber(4)) { + augment_two = atoi(sep->arg[4]); + } + + if (arguments >= 5 && sep->IsNumber(5)) { + augment_three = atoi(sep->arg[5]); + } + + if (arguments >= 6 && sep->IsNumber(6)) { + augment_four = atoi(sep->arg[6]); + } + + if (arguments >= 7 && sep->IsNumber(7)) { + augment_five = atoi(sep->arg[7]); + } + + if (arguments == 8 && sep->IsNumber(8)) { + augment_six = atoi(sep->arg[8]); + } + + client_target->SummonItem( + item_id, + charges, + augment_one, + augment_two, + augment_three, + augment_four, + augment_five, + augment_six + ); + } + else { + c->Message(Chat::Red, "You must target a client to give the item to."); + return; + } +} + diff --git a/zone/gm_commands/givemoney.cpp b/zone/gm_commands/givemoney.cpp new file mode 100755 index 000000000..705ab7461 --- /dev/null +++ b/zone/gm_commands/givemoney.cpp @@ -0,0 +1,50 @@ +#include "../client.h" + +void command_givemoney(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments || !sep->IsNumber(1)) { //as long as the first one is a number, we'll just let atoi convert the rest to 0 or a number + c->Message(Chat::Red, "Usage: #Usage: #givemoney [Platinum] [Gold] [Silver] [Copper]"); + return; + } + + + Client *target = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); + } + + uint32 platinum = std::stoul(sep->arg[1]); + uint32 gold = sep->IsNumber(2) ? std::stoul(sep->arg[2]) : 0; + uint32 silver = sep->IsNumber(3) ? std::stoul(sep->arg[3]) : 0; + uint32 copper = sep->IsNumber(4) ? std::stoul(sep->arg[4]) : 0; + if (!platinum && !gold && !silver && !copper) { + c->Message(Chat::Red, "Usage: #Usage: #givemoney [Platinum] [Gold] [Silver] [Copper]"); + return; + } + + target->AddMoneyToPP( + copper, + silver, + gold, + platinum, + true + ); + + c->Message( + Chat::White, + fmt::format( + "Added {} to {}.", + ConvertMoneyToString(platinum, gold, silver, copper), + ( + c == target ? + "yourself" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ) + ).c_str() + ); +} diff --git a/zone/gm_commands/globalview.cpp b/zone/gm_commands/globalview.cpp new file mode 100755 index 000000000..948ecd353 --- /dev/null +++ b/zone/gm_commands/globalview.cpp @@ -0,0 +1,80 @@ +#include "../client.h" + +void command_globalview(Client *c, const Seperator *sep) +{ + NPC *npcmob = nullptr; + + if (c->GetTarget() && c->GetTarget()->IsNPC()) { + npcmob = c->GetTarget()->CastToNPC(); + QGlobalCache *npc_c = nullptr; + QGlobalCache *char_c = nullptr; + QGlobalCache *zone_c = nullptr; + + if (npcmob) { + npc_c = npcmob->GetQGlobals(); + } + + char_c = c->GetQGlobals(); + zone_c = zone->GetQGlobals(); + + std::list globalMap; + uint32 ntype = 0; + + if (npcmob) { + ntype = npcmob->GetNPCTypeID(); + } + + if (npc_c) { + QGlobalCache::Combine(globalMap, npc_c->GetBucket(), ntype, c->CharacterID(), zone->GetZoneID()); + } + + if (char_c) { + QGlobalCache::Combine(globalMap, char_c->GetBucket(), ntype, c->CharacterID(), zone->GetZoneID()); + } + + if (zone_c) { + QGlobalCache::Combine(globalMap, zone_c->GetBucket(), ntype, c->CharacterID(), zone->GetZoneID()); + } + + auto iter = globalMap.begin(); + uint32 gcount = 0; + + c->Message(Chat::White, "Name, Value"); + while (iter != globalMap.end()) { + c->Message(Chat::White, "%s %s", (*iter).name.c_str(), (*iter).value.c_str()); + ++iter; + ++gcount; + } + c->Message(Chat::White, "%u globals loaded.", gcount); + } + else { + QGlobalCache *char_c = nullptr; + QGlobalCache *zone_c = nullptr; + + char_c = c->GetQGlobals(); + zone_c = zone->GetQGlobals(); + + std::list globalMap; + uint32 ntype = 0; + + if (char_c) { + QGlobalCache::Combine(globalMap, char_c->GetBucket(), ntype, c->CharacterID(), zone->GetZoneID()); + } + + if (zone_c) { + QGlobalCache::Combine(globalMap, zone_c->GetBucket(), ntype, c->CharacterID(), zone->GetZoneID()); + } + + auto iter = globalMap.begin(); + uint32 gcount = 0; + + c->Message(Chat::White, "Name, Value"); + while (iter != globalMap.end()) { + c->Message(Chat::White, "%s %s", (*iter).name.c_str(), (*iter).value.c_str()); + ++iter; + ++gcount; + } + c->Message(Chat::White, "%u globals loaded.", gcount); + } +} + diff --git a/zone/gm_commands/gm.cpp b/zone/gm_commands/gm.cpp new file mode 100755 index 000000000..d781bb93a --- /dev/null +++ b/zone/gm_commands/gm.cpp @@ -0,0 +1,29 @@ +#include "../client.h" + +void command_gm(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments) { + c->Message(Chat::White, "Usage: #gm [On|Off]"); + return; + } + + bool gm_flag = atobool(sep->arg[1]); + Client *target = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); + } + + target->SetGM(gm_flag); + if (c != target) { + c->Message( + Chat::White, + fmt::format( + "{} ({}) is {} flagged as a GM.", + target->GetCleanName(), + target->GetID(), + gm_flag ? "now" : "no longer" + ).c_str() + ); + } +} diff --git a/zone/gm_commands/gmspeed.cpp b/zone/gm_commands/gmspeed.cpp new file mode 100755 index 000000000..30b5eccfa --- /dev/null +++ b/zone/gm_commands/gmspeed.cpp @@ -0,0 +1,55 @@ +#include "../client.h" + +void command_gmspeed(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments) { + c->Message(Chat::White, "Usage: #gmspeed [On|Off]"); + return; + } + + bool gm_speed_flag = atobool(sep->arg[1]); + Client *target = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); + } + + database.SetGMSpeed( + target->AccountID(), + gm_speed_flag ? 1 : 0 + ); + + c->Message( + Chat::White, + fmt::format( + "Turning GM Speed {} for {}.", + gm_speed_flag ? "on" : "off", + ( + c == target ? + "yourself" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ) + ).c_str() + ); + + c->Message( + Chat::White, + fmt::format( + "Note: {} must zone for it to take effect.", + ( + c == target ? + "You" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ) + ).c_str() + ); +} + diff --git a/zone/gm_commands/gmzone.cpp b/zone/gm_commands/gmzone.cpp new file mode 100755 index 000000000..223f04fb3 --- /dev/null +++ b/zone/gm_commands/gmzone.cpp @@ -0,0 +1,158 @@ +#include "../client.h" +#include "../data_bucket.h" + +void command_gmzone(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments) { + c->Message(Chat::White, "Usage: #gmzone [Zone ID|Zone Short Name] [Version] [Instance Identifier]"); + return; + } + + std::string zone_short_name = str_tolower( + sep->IsNumber(1) ? + ZoneName(std::stoul(sep->arg[1]), true) : + sep->arg[1] + ); + bool is_unknown_zone = zone_short_name.find("unknown") != std::string::npos; + if (is_unknown_zone) { + c->Message( + Chat::White, + fmt::format( + "Zone {} could not be found.", + zone_short_name + ).c_str() + ); + } + + auto zone_id = ZoneID(zone_short_name); + if (!zone_id) { + c->Message( + Chat::White, + fmt::format( + "Zone ID {} could not be found.", + zone_id + ).c_str() + ); + return; + } + + std::string zone_long_name = ZoneLongName(zone_id); + + auto zone_version = ( + sep->IsNumber(2) ? + std::stoul(sep->arg[2]) : + 0 + ); + + std::string instance_identifier = ( + sep->arg[3] ? + sep->arg[3] : + "gmzone" + ); + + auto bucket_key = fmt::format( + "{}-{}-{}-instance", + zone_short_name, + instance_identifier, + zone_version + ); + + auto existing_zone_instance = DataBucket::GetData(bucket_key); + uint16 instance_id = 0; + uint32 duration = 100000000; + + if (!existing_zone_instance.empty()) { + instance_id = std::stoi(existing_zone_instance); + c->Message( + Chat::White, + fmt::format( + "You already have an Instance ID of {} for Version {} of {} ({}).", + instance_id, + zone_version, + zone_long_name, + zone_short_name + ).c_str() + ); + } + + if (!instance_id) { + if (!database.GetUnusedInstanceID(instance_id)) { + c->Message(Chat::White, "Failed to find an unused Instance ID."); + return; + } + + if (!database.CreateInstance(instance_id, zone_id, zone_version, duration)) { + c->Message(Chat::White, "Failed to create an Instance."); + return; + } + + c->Message( + Chat::White, + fmt::format( + "New GM Instance ID {} for Version {} of {} ({}) was created.", + instance_id, + zone_version, + zone_long_name, + zone_short_name + ).c_str() + ); + + DataBucket::SetData( + bucket_key, + std::to_string(instance_id) + ); + } + + if (instance_id) { + float target_x = -1, target_y = -1, target_z = -1, target_heading = -1; + int16 min_status = AccountStatus::Player; + uint8 min_level = 0; + + if ( + !content_db.GetSafePoints( + zone_short_name.c_str(), + zone_version, + &target_x, + &target_y, + &target_z, + &target_heading, + &min_status, + &min_level + ) + ) { + c->Message( + Chat::White, + fmt::format( + "Failed to find the safe coordinates for Version {} of {} ({}).", + zone_version, + zone_long_name, + zone_short_name + ).c_str() + ); + } + + c->Message( + Chat::White, + fmt::format( + "Zoning to GM Instance ID {} for Version {} of {} ({}).", + instance_id, + zone_version, + zone_long_name, + zone_short_name + ).c_str() + ); + + c->AssignToInstance(instance_id); + c->MovePC( + zone_id, + instance_id, + target_x, + target_y, + target_z, + target_heading, + 1 + ); + } +} + diff --git a/zone/gm_commands/goto.cpp b/zone/gm_commands/goto.cpp new file mode 100755 index 000000000..cdc8da2e2 --- /dev/null +++ b/zone/gm_commands/goto.cpp @@ -0,0 +1,63 @@ +#include "../client.h" + +void command_goto(Client *c, const Seperator *sep) +{ + std::string arg1 = sep->arg[1]; + + bool goto_via_target_no_args = sep->arg[1][0] == '\0' && c->GetTarget(); + bool goto_via_player_name = !sep->IsNumber(1) && !arg1.empty(); + bool goto_via_x_y_z = sep->IsNumber(1) && sep->IsNumber(2) && sep->IsNumber(3); + + if (goto_via_target_no_args) { + c->MovePC( + zone->GetZoneID(), + zone->GetInstanceID(), + c->GetTarget()->GetX(), + c->GetTarget()->GetY(), + c->GetTarget()->GetZ(), + c->GetTarget()->GetHeading() + ); + } + else if (goto_via_player_name) { + + /** + * Find them in zone first + */ + const char *player_name = sep->arg[1]; + std::string player_name_string = sep->arg[1]; + Client *client = entity_list.GetClientByName(player_name); + if (client) { + c->MovePC( + zone->GetZoneID(), + zone->GetInstanceID(), + client->GetX(), + client->GetY(), + client->GetZ(), + client->GetHeading() + ); + + c->Message(Chat::Yellow, "Goto player '%s' same zone", player_name_string.c_str()); + } + else if (c->GotoPlayer(player_name_string)) { + c->Message(Chat::Yellow, "Goto player '%s' different zone", player_name_string.c_str()); + } + else { + c->Message(Chat::Yellow, "Player '%s' not found", player_name_string.c_str()); + } + } + else if (goto_via_x_y_z) { + c->MovePC( + zone->GetZoneID(), + zone->GetInstanceID(), + atof(sep->arg[1]), + atof(sep->arg[2]), + atof(sep->arg[3]), + (sep->arg[4] ? atof(sep->arg[4]) : c->GetHeading()) + ); + } + else { + c->Message(Chat::White, "Usage: #goto [x y z] [h]"); + c->Message(Chat::White, "Usage: #goto [player_name]"); + } +} + diff --git a/zone/gm_commands/grid.cpp b/zone/gm_commands/grid.cpp new file mode 100755 index 000000000..3ff62539c --- /dev/null +++ b/zone/gm_commands/grid.cpp @@ -0,0 +1,143 @@ +#include "../client.h" + +void command_grid(Client *c, const Seperator *sep) +{ + auto command_type = sep->arg[1]; + auto zone_id = zone->GetZoneID(); + if (strcasecmp("max", command_type) == 0) { + c->Message( + Chat::White, + fmt::format( + "Highest grid ID in this zone is {}.", + content_db.GetHighestGrid(zone_id) + ).c_str() + ); + } + else if (strcasecmp("add", command_type) == 0) { + auto grid_id = atoi(sep->arg[2]); + auto wander_type = atoi(sep->arg[3]); + auto pause_type = atoi(sep->arg[4]); + if (!content_db.GridExistsInZone(zone_id, grid_id)) { + content_db.ModifyGrid(c, false, grid_id, wander_type, pause_type, zone_id); + c->Message( + Chat::White, + fmt::format( + "Grid {} added to zone ID {} with wander type {} and pause type {}.", + grid_id, + zone_id, + wander_type, + pause_type + ).c_str() + ); + } + else { + c->Message( + Chat::White, + fmt::format( + "Grid {} already exists in zone ID {}.", + grid_id, + zone_id + ).c_str() + ); + return; + } + } + else if (strcasecmp("show", command_type) == 0) { + Mob *target = c->GetTarget(); + if (!target || !target->IsNPC()) { + c->Message(Chat::White, "You need to target an NPC!"); + return; + } + + auto grid_id = target->CastToNPC()->GetGrid(); + std::string query = fmt::format( + "SELECT `x`, `y`, `z`, `heading`, `number` " + "FROM `grid_entries` " + "WHERE `zoneid` = {} AND `gridid` = {} " + "ORDER BY `number`", + zone_id, + grid_id + ); + + auto results = content_db.QueryDatabase(query); + if (!results.Success()) { + c->Message(Chat::White, "Error querying database."); + c->Message(Chat::White, query.c_str()); + } + + if (results.RowCount() == 0) { + c->Message(Chat::White, "No grid found."); + return; + } + + // Depop any node npc's already spawned + entity_list.DespawnGridNodes(grid_id); + + // Spawn grid nodes + std::map, int32> zoffset; + for (auto row : results) { + glm::vec4 node_position = glm::vec4(atof(row[0]), atof(row[1]), atof(row[2]), atof(row[3])); + std::vector node_loc{ + node_position.x, + node_position.y, + node_position.z + }; + + // If we already have a node at this location, set the z offset + // higher from the existing one so we can see it. Adjust so if + // there is another at the same spot we adjust again. + auto search = zoffset.find(node_loc); + if (search != zoffset.end()) { + search->second = search->second + 3; + } + else { + zoffset[node_loc] = 0.0; + } + + node_position.z += zoffset[node_loc]; + NPC::SpawnGridNodeNPC(node_position, grid_id, atoi(row[4]), zoffset[node_loc]); + } + c->Message( + Chat::White, + fmt::format( + "Spawning nodes for grid {}.", + grid_id + ).c_str() + ); + } + else if (strcasecmp("hide", command_type) == 0) { + Mob *target = c->GetTarget(); + if (!target || !target->IsNPC()) { + c->Message(Chat::White, "You need to target an NPC!"); + return; + } + + auto grid_id = target->CastToNPC()->GetGrid(); + entity_list.DespawnGridNodes(grid_id); + c->Message( + Chat::White, + fmt::format( + "Depawning nodes for grid {}.", + grid_id + ).c_str() + ); + } + else if (strcasecmp("delete", command_type) == 0) { + auto grid_id = atoi(sep->arg[2]); + content_db.ModifyGrid(c, true, grid_id, 0, 0, zone_id); + c->Message( + Chat::White, + fmt::format( + "Grid {} deleted from zone ID {}.", + grid_id, + zone_id + ).c_str() + ); + } + else { + c->Message(Chat::White, "Usage: #grid [add|delete] [grid_id] [wander_type] [pause_type]"); + c->Message(Chat::White, "Usage: #grid [max] - displays the highest grid ID used in this zone (for add)"); + c->Message(Chat::White, "Usage: #grid [show] - displays wp nodes as boxes"); + } +} + diff --git a/zone/gm_commands/guild.cpp b/zone/gm_commands/guild.cpp new file mode 100755 index 000000000..aea407f7a --- /dev/null +++ b/zone/gm_commands/guild.cpp @@ -0,0 +1,548 @@ +#include "../client.h" +#include "../worldserver.h" + +extern WorldServer worldserver; + +#include "../guild_mgr.h" +#include "../doors.h" + +void command_guild(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments) { + c->Message(Chat::White, "#guild create [Character ID|Character Name] [Guild Name]"); + c->Message(Chat::White, "#guild delete [Guild ID]"); + c->Message(Chat::White, "#guild help"); + c->Message(Chat::White, "#guild info [Guild ID]"); + c->Message(Chat::White, "#guild list"); + c->Message(Chat::White, "#guild rename [Guild ID] [New Name]"); + c->Message(Chat::White, "#guild set [Character ID|Character Name] [Guild ID] (Guild ID 0 is Guildless)"); + c->Message(Chat::White, "#guild setleader [Guild ID] [Character ID|Character Name]"); + c->Message(Chat::White, "#guild setrank [Character ID|Character Name] [Rank]"); + c->Message(Chat::White, "#guild status [Character ID|Character Name]"); + return; + } + + auto target = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); + } + + bool is_create = !strcasecmp(sep->arg[1], "create"); + bool is_delete = !strcasecmp(sep->arg[1], "delete"); + bool is_help = !strcasecmp(sep->arg[1], "help"); + bool is_info = !strcasecmp(sep->arg[1], "info"); + bool is_list = !strcasecmp(sep->arg[1], "list"); + bool is_rename = !strcasecmp(sep->arg[1], "rename"); + bool is_set = !strcasecmp(sep->arg[1], "set"); + bool is_set_leader = !strcasecmp(sep->arg[1], "setleader"); + bool is_set_rank = !strcasecmp(sep->arg[1], "setrank"); + bool is_status = !strcasecmp(sep->arg[1], "status"); + if ( + !is_create && + !is_delete && + !is_help && + !is_info && + !is_list && + !is_rename && + !is_set && + !is_set_leader && + !is_set_rank && + !is_status + ) { + c->Message(Chat::White, "#guild create [Character ID|Character Name] [Guild Name]"); + c->Message(Chat::White, "#guild delete [Guild ID]"); + c->Message(Chat::White, "#guild help"); + c->Message(Chat::White, "#guild info [Guild ID]"); + c->Message(Chat::White, "#guild list"); + c->Message(Chat::White, "#guild rename [Guild ID] [New Name]"); + c->Message(Chat::White, "#guild set [Character ID|Character Name] [Guild ID] (Guild ID 0 is Guildless)"); + c->Message(Chat::White, "#guild setleader [Guild ID] [Character ID|Character Name]"); + c->Message(Chat::White, "#guild setrank [Character ID|Character Name] [Rank]"); + c->Message(Chat::White, "#guild status [Character ID|Character Name]"); + return; + } + + if (is_create) { + if (arguments < 3) { + c->Message(Chat::White, "Usage: #guild create [Character ID|Character Name] [Guild Name]"); + } else { + auto leader_id = ( + sep->IsNumber(2) ? + std::stoul(sep->arg[2]) : + database.GetCharacterID(sep->arg[2]) + ); + auto leader_name = database.GetCharNameByID(leader_id); + if (!leader_id || leader_name.empty()) { + c->Message( + Chat::White, + fmt::format( + "Character ID {} could not be found.", + leader_id + ).c_str() + ); + return; + } + + auto guild_id = guild_mgr.FindGuildByLeader(leader_id); + if (guild_id != GUILD_NONE) { + c->Message( + Chat::White, + fmt::format( + "{} ({}) is already the leader of {} ({}).", + leader_name, + leader_id, + guild_mgr.GetGuildNameByID(guild_id), + guild_id + ).c_str() + ); + } else { + if (c->Admin() < minStatusToEditOtherGuilds) { + c->Message(Chat::White, "You cannot edit other peoples' guilds."); + return; + } + + auto guild_name = sep->argplus[3]; + auto guild_id = guild_mgr.CreateGuild(sep->argplus[3], leader_id); + + LogGuilds( + "[{}]: Creating guild [{}] with leader [{}] with GM command. It was given id [{}]", + c->GetName(), + guild_name, + leader_id, + guild_id + ); + + if (guild_id == GUILD_NONE) { + c->Message(Chat::White, "Guild creation failed."); + } else { + c->Message( + Chat::White, + fmt::format( + "Guild Created | Name: {} ({}) Leader: {} ({})", + guild_name, + guild_id, + leader_name, + leader_id + ).c_str() + ); + + if (!guild_mgr.SetGuild(leader_id, guild_id, GUILD_LEADER)) { + c->Message( + Chat::White, + fmt::format( + "Unable to put {} ({}) in to {} ({}) in the database.", + leader_name, + leader_id, + guild_name, + guild_id + ).c_str() + ); + + c->Message(Chat::White, "Note: Run #guild set to resolve this."); + } + } + } + } + } else if (is_delete) { + if (!sep->IsNumber(2)) { + c->Message(Chat::White, "Usage: #guild delete [Guild ID]"); + } else { + auto guild_id = std::stoul(sep->arg[2]); + if (!guild_mgr.GuildExists(guild_id)) { + c->Message( + Chat::White, + fmt::format( + "Guild ID {} could not be found.", + guild_id + ).c_str() + ); + return; + } + + if (c->Admin() < minStatusToEditOtherGuilds) { + if (c->GuildID() != guild_id) { + c->Message(Chat::White, "You cannot edit other peoples' guilds."); + return; + } else if (!guild_mgr.CheckGMStatus(guild_id, c->Admin())) { + c->Message(Chat::White, "You cannot edit your current guild, your status is not high enough."); + return; + } + } + + LogGuilds( + "[{}]: Deleting guild [{}] ([{}]) with GM command", + c->GetName(), + guild_mgr.GetGuildNameByID(guild_id), + guild_id + ); + + c->Message( + Chat::White, + fmt::format( + "Guild {} Deleted | Name: {} ({})", + guild_mgr.DeleteGuild(guild_id) ? "Successfully" : "Unsuccessfully", + guild_mgr.GetGuildNameByID(guild_id), + guild_id + ).c_str() + ); + } + } else if (is_help) { + c->Message(Chat::White, "#guild create [Character ID|Character Name] [Guild Name]"); + c->Message(Chat::White, "#guild delete [Guild ID]"); + c->Message(Chat::White, "#guild help"); + c->Message(Chat::White, "#guild info [Guild ID]"); + c->Message(Chat::White, "#guild list"); + c->Message(Chat::White, "#guild rename [Guild ID] [New Name]"); + c->Message(Chat::White, "#guild set [Character ID|Character Name] [Guild ID] (Guild ID 0 is Guildless)"); + c->Message(Chat::White, "#guild setleader [Guild ID] [Character ID|Character Name]"); + c->Message(Chat::White, "#guild setrank [Character ID|Character Name] [Rank]"); + c->Message(Chat::White, "#guild status [Character ID|Character Name]"); + } else if (is_info) { + if (arguments != 2 && c->IsInAGuild()) { + if (c->Admin() >= minStatusToEditOtherGuilds) { + c->Message(Chat::White, "#guild info [Guild ID]"); + } else { + c->Message(Chat::White, "You cannot edit other peoples' guilds."); + } + } else { + auto guild_id = GUILD_NONE; + if (arguments != 2 || !sep->IsNumber(2)) { + guild_id = c->GuildID(); + } else if (c->Admin() >= minStatusToEditOtherGuilds) { + guild_id = std::stoul(sep->arg[2]); + } + + if (guild_id != GUILD_NONE) { + guild_mgr.DescribeGuild(c, guild_id); + } + } + } else if (is_list) { + if (c->Admin() < minStatusToEditOtherGuilds) { + c->Message(Chat::White, "You cannot edit other peoples' guilds."); + return; + } + + guild_mgr.ListGuilds(c); + } else if (is_rename) { + if (!sep->IsNumber(2)) { + c->Message(Chat::White, "Usage: #guild rename [Guild ID] [New Guild Name]"); + } else { + auto guild_id = std::stoul(sep->arg[2]); + if (!guild_mgr.GuildExists(guild_id)) { + c->Message( + Chat::White, + fmt::format( + "Guild ID {} could not be found.", + guild_id + ).c_str() + ); + return; + } + + if (c->Admin() < minStatusToEditOtherGuilds) { + if (c->GuildID() != guild_id) { + c->Message(Chat::White, "You cannot edit other peoples' guilds."); + return; + } else if (!guild_mgr.CheckGMStatus(guild_id, c->Admin())) { + c->Message(Chat::White, "You cannot edit your current guild, your status is not high enough."); + return; + } + } + + auto new_guild_name = sep->argplus[3]; + LogGuilds( + "[{}]: Renaming guild [{}] ([{}]) to [{}] with GM command", + c->GetName(), + guild_mgr.GetGuildNameByID(guild_id), + guild_id, + new_guild_name + ); + + c->Message( + Chat::White, + fmt::format( + "Guild {} Renamed | Name: {} ({})", + guild_mgr.RenameGuild(guild_id, new_guild_name) ? "Successfully" : "Unsuccessfully", + new_guild_name, + guild_id + ).c_str() + ); + } + } else if (is_set) { + if ( + arguments != 3 || + !sep->IsNumber(3) + ) { + c->Message(Chat::White, "#guild set [Character ID|Character Name] [Guild ID] (Guild ID 0 is Guildless)"); + return; + } else { + auto guild_id = std::stoul(sep->arg[3]); + if (!guild_id) { + guild_id = GUILD_NONE; + } else if (!guild_mgr.GuildExists(guild_id)) { + c->Message( + Chat::White, + fmt::format( + "Guild ID {} could not be found.", + guild_id + ).c_str() + ); + return; + } + + auto character_id = ( + sep->IsNumber(2) ? + std::stoul(sep->arg[2]) : + database.GetCharacterID(sep->arg[2]) + ); + auto character_name = database.GetCharNameByID(character_id); + if (!character_id || character_name.empty()) { + c->Message( + Chat::White, + fmt::format( + "Character ID {} could not be found.", + character_id + ).c_str() + ); + return; + } + + if (c->Admin() < minStatusToEditOtherGuilds && guild_id != c->GuildID()) { + c->Message(Chat::White, "You cannot edit other peoples' guilds."); + return; + } + + if (!guild_id || guild_id == GUILD_NONE) { + LogGuilds( + "[{}]: Removing [{}] ([{}]) from guild with GM command", + c->GetName(), + character_name, + character_id + ); + } else { + LogGuilds( + "[{}]: Putting [{}] ([{}]) into guild [{}] ([{}]) with GM command", + c->GetName(), + character_name, + character_id, + guild_mgr.GetGuildNameByID(guild_id), + guild_id + ); + } + + if (guild_id && guild_id != GUILD_NONE) { + c->Message( + Chat::White, + fmt::format( + "{} ({}) has {} put into {} ({}).", + character_name, + character_id, + guild_mgr.SetGuild(character_id, guild_id, GUILD_MEMBER) ? "been" : "failed to be", + guild_mgr.GetGuildNameByID(guild_id), + guild_id + ).c_str() + ); + } else { + c->Message( + Chat::White, + fmt::format( + "{} ({}) is now Guildless.", + character_name, + character_id + ).c_str() + ); + } + } + } else if (is_set_leader) { + if ( + arguments != 3 || + !sep->IsNumber(2) + ) { + c->Message(Chat::White, "Usage: #guild setleader [Guild ID] [Character ID|Character Name]"); + } else { + auto leader_id = ( + sep->IsNumber(2) ? + std::stoul(sep->arg[2]) : + database.GetCharacterID(sep->arg[2]) + ); + auto leader_name = database.GetCharNameByID(leader_id); + if (!leader_id || leader_name.empty()) { + c->Message( + Chat::White, + fmt::format( + "Character ID {} could not be found.", + leader_id + ).c_str() + ); + return; + } + + auto guild_id = guild_mgr.FindGuildByLeader(leader_id); + if (guild_id != 0) { + c->Message( + Chat::White, + fmt::format( + "{} ({}) is already the leader of {} ({}).", + leader_name, + leader_id, + guild_mgr.GetGuildNameByID(guild_id), + guild_id + ).c_str() + ); + } + else { + auto guild_id = std::stoul(sep->arg[2]); + if (!guild_mgr.GuildExists(guild_id)) { + c->Message( + Chat::White, + fmt::format( + "Guild ID {} could not be found.", + guild_id + ).c_str() + ); + return; + } + + if (c->Admin() < minStatusToEditOtherGuilds) { + if (c->GuildID() != guild_id) { + c->Message(Chat::White, "You cannot edit other peoples' guilds."); + return; + } else if (!guild_mgr.CheckGMStatus(guild_id, c->Admin())) { + c->Message(Chat::White, "You cannot edit your current guild, your status is not high enough."); + return; + } + } + + LogGuilds( + "[{}]: Setting leader of guild [{}] ([{}]) to [{}] with GM command", + c->GetName(), + guild_mgr.GetGuildNameByID(guild_id), + guild_id, + leader_id + ); + + c->Message( + Chat::White, + fmt::format( + "Guild Leader {} Changed | Name: {} ({})", + guild_mgr.SetGuildLeader(guild_id, leader_id) ? "Successfully" : "Unsuccessfully", + guild_mgr.GetGuildNameByID(guild_id), + guild_id + ).c_str() + ); + } + } + } else if (is_set_rank) { + auto rank = static_cast(std::stoul(sep->arg[3])); + if (!sep->IsNumber(3)) { + c->Message(Chat::White, "#guild setrank [Character ID|Character Name] [Rank]"); + } else if (rank < 0 || rank > GUILD_MAX_RANK) { + c->Message( + Chat::White, + fmt::format( + "Guild Ranks are from 0 to {}.", + GUILD_MAX_RANK + ).c_str() + ); + } else { + auto character_id = ( + sep->IsNumber(2) ? + std::stoul(sep->arg[2]) : + database.GetCharacterID(sep->arg[2]) + ); + auto character_name = database.GetCharNameByID(character_id); + if (!character_id || character_name.empty()) { + c->Message( + Chat::White, + fmt::format( + "Character ID {} could not be found.", + character_id + ).c_str() + ); + return; + } + + if (!guild_mgr.IsCharacterInGuild(character_id)) { + c->Message( + Chat::White, + fmt::format( + "{} ({}) is not in a guild.", + character_name, + character_id + ).c_str() + ); + return; + } + + if (c->Admin() < minStatusToEditOtherGuilds && character_id != c->CharacterID()) { + c->Message(Chat::White, "You cannot edit other peoples' guilds."); + return; + } + + LogGuilds( + "[{}]: Setting [{}] ([{}])'s guild rank to [{}] with GM command", + c->GetName(), + sep->arg[2], + character_id, + rank + ); + + c->Message( + Chat::White, + fmt::format( + "{} ({}) has {} to rank {} ({}).", + character_name, + character_id, + guild_mgr.SetGuildRank(character_id, rank) ? "been set" : "failed to be set", + !guild_mgr.GetGuildRankName(guild_mgr.GetGuildIDByCharacterID(character_id), rank).empty() ? guild_mgr.GetGuildRankName(guild_mgr.GetGuildIDByCharacterID(character_id), rank) : "Nameless", + rank + ).c_str() + ); + } + } else if (is_status) { + auto client = ( + target ? + target : + ( + arguments == 2 ? + entity_list.GetClientByName(sep->arg[2]) : + c + ) + ); + if (!client) { + c->Message(Chat::White, "You must target someone or specify a character name."); + } else if (c->Admin() < minStatusToEditOtherGuilds && client->GuildID() != c->GuildID()) { + c->Message(Chat::White, "You cannot edit other peoples' guilds."); + } else { + if (!client->IsInAGuild()) { + c->Message( + Chat::White, + fmt::format( + "{} is not in a guild.", + client->GetCleanName() + ).c_str() + ); + } else if (guild_mgr.IsGuildLeader(client->GuildID(), client->CharacterID())) { + c->Message( + Chat::White, + fmt::format( + "{} is the leader of {}.", + client->GetName(), + guild_mgr.GetGuildNameByID(client->GuildID()) + ).c_str() + ); + } else { + c->Message( + Chat::White, + fmt::format( + "{} is a(n) {} of {}.", + client->GetName(), + guild_mgr.GetRankName(client->GuildID(), client->GuildRank()), + guild_mgr.GetGuildNameByID(client->GuildID()) + ).c_str() + ); + } + } + } +} diff --git a/zone/gm_commands/guildapprove.cpp b/zone/gm_commands/guildapprove.cpp new file mode 100755 index 000000000..5c7805224 --- /dev/null +++ b/zone/gm_commands/guildapprove.cpp @@ -0,0 +1,8 @@ +#include "../client.h" +#include "../guild_mgr.h" + +void command_guildapprove(Client *c, const Seperator *sep) +{ + guild_mgr.AddMemberApproval(atoi(sep->arg[1]), c); +} + diff --git a/zone/gm_commands/guildcreate.cpp b/zone/gm_commands/guildcreate.cpp new file mode 100755 index 000000000..48e90d3ef --- /dev/null +++ b/zone/gm_commands/guildcreate.cpp @@ -0,0 +1,13 @@ +#include "../client.h" +#include "../guild_mgr.h" + +void command_guildcreate(Client *c, const Seperator *sep) +{ + if (strlen(sep->argplus[1]) > 4 && strlen(sep->argplus[1]) < 16) { + guild_mgr.AddGuildApproval(sep->argplus[1], c); + } + else { + c->Message(Chat::White, "Guild name must be more than 4 characters and less than 16."); + } +} + diff --git a/zone/gm_commands/guildlist.cpp b/zone/gm_commands/guildlist.cpp new file mode 100755 index 000000000..3ec40ef02 --- /dev/null +++ b/zone/gm_commands/guildlist.cpp @@ -0,0 +1,14 @@ +#include "../client.h" +#include "../guild_mgr.h" + +void command_guildlist(Client *c, const Seperator *sep) +{ + GuildApproval *tmp = guild_mgr.FindGuildByIDApproval(atoi(sep->arg[1])); + if (tmp) { + tmp->ApprovedMembers(c); + } + else { + c->Message(Chat::White, "Could not find reference id."); + } +} + diff --git a/zone/gm_commands/hair.cpp b/zone/gm_commands/hair.cpp new file mode 100755 index 000000000..56f80aaed --- /dev/null +++ b/zone/gm_commands/hair.cpp @@ -0,0 +1,37 @@ +#include "../client.h" + +void command_hair(Client *c, const Seperator *sep) +{ + Mob *target = c->GetTarget(); + if (!sep->IsNumber(1)) { + c->Message(Chat::White, "Usage: #hair [number of hair style]"); + } + else if (!target) { + c->Message(Chat::White, "Error: this command requires a target"); + } + else { + uint16 Race = target->GetRace(); + uint8 Gender = target->GetGender(); + uint8 Texture = 0xFF; + uint8 HelmTexture = 0xFF; + uint8 HairColor = target->GetHairColor(); + uint8 BeardColor = target->GetBeardColor(); + uint8 EyeColor1 = target->GetEyeColor1(); + uint8 EyeColor2 = target->GetEyeColor2(); + uint8 HairStyle = atoi(sep->arg[1]); + uint8 LuclinFace = target->GetLuclinFace(); + uint8 Beard = target->GetBeard(); + uint32 DrakkinHeritage = target->GetDrakkinHeritage(); + uint32 DrakkinTattoo = target->GetDrakkinTattoo(); + uint32 DrakkinDetails = target->GetDrakkinDetails(); + + target->SendIllusionPacket( + Race, Gender, Texture, HelmTexture, HairColor, BeardColor, + EyeColor1, EyeColor2, HairStyle, LuclinFace, Beard, 0xFF, + DrakkinHeritage, DrakkinTattoo, DrakkinDetails + ); + + c->Message(Chat::White, "Hair = %i", atoi(sep->arg[1])); + } +} + diff --git a/zone/gm_commands/haircolor.cpp b/zone/gm_commands/haircolor.cpp new file mode 100755 index 000000000..2767808ac --- /dev/null +++ b/zone/gm_commands/haircolor.cpp @@ -0,0 +1,37 @@ +#include "../client.h" + +void command_haircolor(Client *c, const Seperator *sep) +{ + Mob *target = c->GetTarget(); + if (!sep->IsNumber(1)) { + c->Message(Chat::White, "Usage: #haircolor [number of hair color]"); + } + else if (!target) { + c->Message(Chat::White, "Error: this command requires a target"); + } + else { + uint16 Race = target->GetRace(); + uint8 Gender = target->GetGender(); + uint8 Texture = 0xFF; + uint8 HelmTexture = 0xFF; + uint8 HairColor = atoi(sep->arg[1]); + uint8 BeardColor = target->GetBeardColor(); + uint8 EyeColor1 = target->GetEyeColor1(); + uint8 EyeColor2 = target->GetEyeColor2(); + uint8 HairStyle = target->GetHairStyle(); + uint8 LuclinFace = target->GetLuclinFace(); + uint8 Beard = target->GetBeard(); + uint32 DrakkinHeritage = target->GetDrakkinHeritage(); + uint32 DrakkinTattoo = target->GetDrakkinTattoo(); + uint32 DrakkinDetails = target->GetDrakkinDetails(); + + target->SendIllusionPacket( + Race, Gender, Texture, HelmTexture, HairColor, BeardColor, + EyeColor1, EyeColor2, HairStyle, LuclinFace, Beard, 0xFF, + DrakkinHeritage, DrakkinTattoo, DrakkinDetails + ); + + c->Message(Chat::White, "Hair Color = %i", atoi(sep->arg[1])); + } +} + diff --git a/zone/gm_commands/haste.cpp b/zone/gm_commands/haste.cpp new file mode 100755 index 000000000..bf5be4d26 --- /dev/null +++ b/zone/gm_commands/haste.cpp @@ -0,0 +1,20 @@ +#include "../client.h" + +void command_haste(Client *c, const Seperator *sep) +{ + // #haste command to set client attack speed. Takes a percentage (100 = twice normal attack speed) + if (sep->arg[1][0] != 0) { + uint16 Haste = atoi(sep->arg[1]); + if (Haste > 85) { + Haste = 85; + } + c->SetExtraHaste(Haste); + // SetAttackTimer must be called to make this take effect, so player needs to change + // the primary weapon. + c->Message(Chat::White, "Haste set to %d%% - Need to re-equip primary weapon before it takes effect", Haste); + } + else { + c->Message(Chat::White, "Usage: #haste [percentage]"); + } +} + diff --git a/zone/gm_commands/hatelist.cpp b/zone/gm_commands/hatelist.cpp new file mode 100755 index 000000000..83fe75e20 --- /dev/null +++ b/zone/gm_commands/hatelist.cpp @@ -0,0 +1,13 @@ +#include "../client.h" + +void command_hatelist(Client *c, const Seperator *sep) +{ + if (!c->GetTarget() || !c->GetTarget()->IsNPC()) { + c->Message(Chat::White, "You must target an NPC to use this command."); + return; + } + + auto target = c->GetTarget(); + + target->PrintHateListToClient(c); +} diff --git a/zone/gm_commands/heal.cpp b/zone/gm_commands/heal.cpp new file mode 100755 index 000000000..5a68a7c55 --- /dev/null +++ b/zone/gm_commands/heal.cpp @@ -0,0 +1,29 @@ +#include "../client.h" + +void command_heal(Client *c, const Seperator *sep) +{ + Mob* target = c; + if (c->GetTarget()) { + target = c->GetTarget(); + } + + target->Heal(); + + c->Message( + Chat::White, + fmt::format( + "Set {} to full Health ({}).", + ( + c == target ? + "yourself" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ), + target->GetMaxHP() + ).c_str() + ); +} + diff --git a/zone/gm_commands/helm.cpp b/zone/gm_commands/helm.cpp new file mode 100755 index 000000000..8d0a36407 --- /dev/null +++ b/zone/gm_commands/helm.cpp @@ -0,0 +1,37 @@ +#include "../client.h" + +void command_helm(Client *c, const Seperator *sep) +{ + Mob *target = c->GetTarget(); + if (!sep->IsNumber(1)) { + c->Message(Chat::White, "Usage: #helm [number of helm texture]"); + } + else if (!target) { + c->Message(Chat::White, "Error: this command requires a target"); + } + else { + uint16 Race = target->GetRace(); + uint8 Gender = target->GetGender(); + uint8 Texture = 0xFF; + uint8 HelmTexture = atoi(sep->arg[1]); + uint8 HairColor = target->GetHairColor(); + uint8 BeardColor = target->GetBeardColor(); + uint8 EyeColor1 = target->GetEyeColor1(); + uint8 EyeColor2 = target->GetEyeColor2(); + uint8 HairStyle = target->GetHairStyle(); + uint8 LuclinFace = target->GetLuclinFace(); + uint8 Beard = target->GetBeard(); + uint32 DrakkinHeritage = target->GetDrakkinHeritage(); + uint32 DrakkinTattoo = target->GetDrakkinTattoo(); + uint32 DrakkinDetails = target->GetDrakkinDetails(); + + target->SendIllusionPacket( + Race, Gender, Texture, HelmTexture, HairColor, BeardColor, + EyeColor1, EyeColor2, HairStyle, LuclinFace, Beard, 0xFF, + DrakkinHeritage, DrakkinTattoo, DrakkinDetails + ); + + c->Message(Chat::White, "Helm = %i", atoi(sep->arg[1])); + } +} + diff --git a/zone/gm_commands/heritage.cpp b/zone/gm_commands/heritage.cpp new file mode 100755 index 000000000..0f4e1d9e9 --- /dev/null +++ b/zone/gm_commands/heritage.cpp @@ -0,0 +1,37 @@ +#include "../client.h" + +void command_heritage(Client *c, const Seperator *sep) +{ + Mob *target = c->GetTarget(); + if (!sep->IsNumber(1)) { + c->Message(Chat::White, "Usage: #heritage [number of Drakkin heritage]"); + } + else if (!target) { + c->Message(Chat::White, "Error: this command requires a target"); + } + else { + uint16 Race = target->GetRace(); + uint8 Gender = target->GetGender(); + uint8 Texture = 0xFF; + uint8 HelmTexture = 0xFF; + uint8 HairColor = target->GetHairColor(); + uint8 BeardColor = target->GetBeardColor(); + uint8 EyeColor1 = target->GetEyeColor1(); + uint8 EyeColor2 = target->GetEyeColor2(); + uint8 HairStyle = target->GetHairStyle(); + uint8 LuclinFace = target->GetLuclinFace(); + uint8 Beard = target->GetBeard(); + uint32 DrakkinHeritage = atoi(sep->arg[1]); + uint32 DrakkinTattoo = target->GetDrakkinTattoo(); + uint32 DrakkinDetails = target->GetDrakkinDetails(); + + target->SendIllusionPacket( + Race, Gender, Texture, HelmTexture, HairColor, BeardColor, + EyeColor1, EyeColor2, HairStyle, LuclinFace, Beard, 0xFF, + DrakkinHeritage, DrakkinTattoo, DrakkinDetails + ); + + c->Message(Chat::White, "Heritage = %i", atoi(sep->arg[1])); + } +} + diff --git a/zone/gm_commands/heromodel.cpp b/zone/gm_commands/heromodel.cpp new file mode 100755 index 000000000..33ea72c2c --- /dev/null +++ b/zone/gm_commands/heromodel.cpp @@ -0,0 +1,35 @@ +#include "../client.h" + +void command_heromodel(Client *c, const Seperator *sep) +{ + if (sep->argnum < 1) { + c->Message(Chat::White, "Usage: #heromodel [hero forge model] [ [slot] ] (example: #heromodel 63)"); + } + else if (c->GetTarget() == nullptr) { + c->Message(Chat::Red, "You must have a target to do a wear change for Hero's Forge Models."); + } + else { + uint32 hero_forge_model = atoi(sep->arg[1]); + + if (sep->argnum > 1) { + uint8 wearslot = (uint8) atoi(sep->arg[2]); + c->GetTarget()->SendTextureWC(wearslot, 0, hero_forge_model, 0, 0, 0); + } + else { + if (hero_forge_model > 0) { + // Conversion to simplify the command arguments + // Hero's Forge model is actually model * 1000 + texture * 100 + wearslot + // Hero's Forge Model slot 7 is actually for Robes, but it still needs to use wearslot 1 in the packet + hero_forge_model *= 100; + + for (uint8 wearslot = 0; wearslot < 7; wearslot++) { + c->GetTarget()->SendTextureWC(wearslot, 0, (hero_forge_model + wearslot), 0, 0, 0); + } + } + else { + c->Message(Chat::Red, "Hero's Forge Model must be greater than 0."); + } + } + } +} + diff --git a/zone/gm_commands/hideme.cpp b/zone/gm_commands/hideme.cpp new file mode 100755 index 000000000..d936a7fdc --- /dev/null +++ b/zone/gm_commands/hideme.cpp @@ -0,0 +1,16 @@ +#include "../client.h" +#include "../string_ids.h" + +void command_hideme(Client *c, const Seperator *sep) +{ + bool state = atobool(sep->arg[1]); + + if (sep->arg[1][0] == 0) { + c->Message(Chat::White, "Usage: #hideme [on/off]"); + } + else { + c->SetHideMe(state); + c->MessageString(Chat::Broadcasts, c->GetHideMe() ? NOW_INVISIBLE : NOW_VISIBLE, c->GetName()); + } +} + diff --git a/zone/gm_commands/hp.cpp b/zone/gm_commands/hp.cpp new file mode 100755 index 000000000..178ef9890 --- /dev/null +++ b/zone/gm_commands/hp.cpp @@ -0,0 +1,8 @@ +#include "../client.h" + +void command_hp(Client *c, const Seperator *sep) +{ + c->SendHPUpdate(); + c->CheckManaEndUpdate(); +} + diff --git a/zone/gm_commands/incstat.cpp b/zone/gm_commands/incstat.cpp new file mode 100755 index 000000000..6a09c1785 --- /dev/null +++ b/zone/gm_commands/incstat.cpp @@ -0,0 +1,18 @@ +#include "../client.h" + +void command_incstat(Client *c, const Seperator *sep) +{ + if (sep->arg[1][0] && sep->arg[2][0] && c->GetTarget() != 0 && c->GetTarget()->IsClient()) { + c->GetTarget()->CastToClient()->IncStats(atoi(sep->arg[1]), atoi(sep->arg[2])); + } + else { + c->Message(Chat::White, "This command is used to permanently increase or decrease a players stats."); + c->Message(Chat::White, "Usage: #setstat {type} {value by which to increase or decrease}"); + c->Message( + Chat::White, + "Note: The value is in increments of 2, so a value of 3 will actually increase the stat by 6" + ); + c->Message(Chat::White, "Types: Str: 0, Sta: 1, Agi: 2, Dex: 3, Int: 4, Wis: 5, Cha: 6"); + } +} + diff --git a/zone/gm_commands/instance.cpp b/zone/gm_commands/instance.cpp new file mode 100755 index 000000000..f9bd357e3 --- /dev/null +++ b/zone/gm_commands/instance.cpp @@ -0,0 +1,328 @@ +#include "../client.h" + +void command_instance(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments) { + c->Message( + Chat::White, + "Usage: #instance create [Zone ID|Zone Short Name] [Version] [Duration]" + ); + c->Message( + Chat::White, + "Usage: #instance destroy [Instance ID]" + ); + c->Message( + Chat::White, + "Usage: #instance add [Instance ID] [Name]" + ); + c->Message( + Chat::White, + "Usage: #instance remove [Instance ID] [Name]" + ); + c->Message( + Chat::White, + "Usage: #instance list [Name]" + ); + return; + } + + Client* target = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); + } + + bool is_add = !strcasecmp(sep->arg[1], "add"); + bool is_create = !strcasecmp(sep->arg[1], "create"); + bool is_destroy = !strcasecmp(sep->arg[1], "destroy"); + bool is_help = !strcasecmp(sep->arg[1], "help"); + bool is_list = !strcasecmp(sep->arg[1], "list"); + bool is_remove = !strcasecmp(sep->arg[1], "remove"); + if ( + !is_add && + !is_create && + !is_destroy && + !is_help && + !is_list && + !is_remove + ) { + c->Message( + Chat::White, + "Usage: #instance create [Zone ID|Zone Short Name] [Version] [Duration]" + ); + c->Message( + Chat::White, + "Usage: #instance destroy [Instance ID]" + ); + c->Message( + Chat::White, + "Usage: #instance add [Instance ID] [Name]" + ); + c->Message( + Chat::White, + "Usage: #instance remove [Instance ID] [Name]" + ); + c->Message( + Chat::White, + "Usage: #instance list [Name]" + ); + return; + } + + if (is_add) { + if (!sep->IsNumber(2)) { + c->Message( + Chat::White, + "#instance add [Instance ID] [Name]" + ); + return; + } + + std::string character_name = sep->arg[3]; + uint16 instance_id = static_cast(std::stoul(sep->arg[2])); + uint32 character_id = database.GetCharacterID(character_name.c_str()); + if (instance_id <= 0 || character_id <= 0) { + c->Message(Chat::White, "You must enter a valid Instance ID and player name."); + return; + } + + if (!database.CheckInstanceExists(instance_id)) { + c->Message( + Chat::White, + fmt::format( + "Instance ID {} does not exist.", + instance_id + ).c_str() + ); + return; + } + + uint32 zone_id = database.ZoneIDFromInstanceID(instance_id); + uint32 version = database.VersionFromInstanceID(instance_id); + uint32 current_id = database.GetInstanceID(zone_id, character_id, version); + if (!current_id) { + std::string target_string = ( + c == target ? + "yourself" : + fmt::format( + "{} ({})", + character_name, + character_id + ) + ); + + c->Message( + Chat::White, + ( + database.AddClientToInstance(instance_id, character_id) ? + fmt::format( + "Added {} to Instance ID {}.", + target_string, + instance_id + ) : + fmt::format( + "Failed to add {} to Instance ID {}.", + target_string, + instance_id + ) + ).c_str() + ); + } + else { + c->Message( + Chat::White, + fmt::format( + "Client was already saved to Instance ID {}{}.", + current_id, + ( + current_id != instance_id ? + fmt::format( + "which has the same zone and version as Instance ID {}", + instance_id + ) : + "" + ) + ).c_str() + ); + } + } else if (is_create) { + if (!sep->IsNumber(3) || !sep->IsNumber(4)) { + c->Message( + Chat::White, + "Usage: #instance create [Zone ID|Zone Short Name] [Version] [Duration]" + ); + return; + } + + uint32 zone_id = ( + sep->IsNumber(2) ? + std::stoul(sep->arg[2]) : + ZoneID(sep->arg[2]) + ); + uint32 version = std::stoul(sep->arg[3]); + uint32 duration = std::stoul(sep->arg[4]); + std::string zone_short_name = ZoneName(zone_id); + if (zone_short_name.empty()) { + c->Message( + Chat::White, + fmt::format( + "Zone ID {} was not found by the server.", + zone_id + ).c_str() + ); + return; + } + + uint16 instance_id = 0; + if (!database.GetUnusedInstanceID(instance_id)) { + c->Message(Chat::White, "Server was unable to find a free instance id."); + return; + } + + if (!database.CreateInstance(instance_id, zone_id, version, duration)) { + c->Message(Chat::White, "Server was unable to create a new instance."); + return; + } + + c->Message( + Chat::White, + fmt::format( + "Instance {} Created | Zone: {} ({}){}", + instance_id, + ZoneLongName(zone_id), + zone_id, + ( + version ? + fmt::format( + " Version: {}", + version + ) : + "" + ) + ).c_str() + ); + + c->Message( + Chat::White, + fmt::format( + "Instance {} Created | Duration: {} ({})", + instance_id, + ConvertSecondsToTime(duration), + duration + ).c_str() + ); + } else if (is_destroy) { + if (!sep->IsNumber(2)) { + c->Message( + Chat::White, + "#instance destroy [Instance ID]" + ); + return; + } + + uint16 instance_id = std::stoul(sep->arg[2]); + if (!database.CheckInstanceExists(instance_id)) { + c->Message( + Chat::White, + fmt::format( + "Instance ID {} does not exist.", + instance_id + ).c_str() + ); + return; + } + + database.DeleteInstance(instance_id); + c->Message( + Chat::White, + fmt::format( + "Instance ID {} Deleted.", + instance_id + ).c_str() + ); + } else if (is_help) { + c->Message( + Chat::White, + "Usage: #instance create [Zone ID|Zone Short Name] [Version] [Duration]" + ); + c->Message( + Chat::White, + "Usage: #instance destroy [Instance ID]" + ); + c->Message( + Chat::White, + "Usage: #instance add [Instance ID] [Name]" + ); + c->Message( + Chat::White, + "Usage: #instance remove [Instance ID] [Name]" + ); + c->Message( + Chat::White, + "Usage: #instance list [Name]" + ); + return; + } else if (is_list) { + uint32 character_id = database.GetCharacterID(sep->arg[2]); + if (character_id <= 0) { + character_id = target->CharacterID(); + } + + database.ListAllInstances(c, character_id); + } else if (is_remove) { + if (!sep->IsNumber(2)) { + c->Message( + Chat::White, + "#instance remove [Instance ID] [Name]" + ); + return; + } + + std::string character_name = sep->arg[3]; + uint16 instance_id = static_cast(std::stoul(sep->arg[2])); + uint32 character_id = database.GetCharacterID(character_name.c_str()); + if (instance_id <= 0 || character_id <= 0) { + c->Message(Chat::White, "You must enter a valid Instance ID and player name."); + return; + } + + if (!database.CheckInstanceExists(instance_id)) { + c->Message( + Chat::White, + fmt::format( + "Instance ID {} does not exist.", + instance_id + ).c_str() + ); + return; + } + + std::string target_string = ( + c->CharacterID() == character_id ? + "yourself" : + fmt::format( + "{} ({})", + character_name, + character_id + ) + ); + + c->Message( + Chat::White, + ( + database.RemoveClientFromInstance(instance_id, character_id) ? + fmt::format( + "Removed {} from Instance ID {}.", + target_string, + instance_id + ) : + fmt::format( + "Failed to remove {} from Instance ID {}.", + target_string, + instance_id + ) + ).c_str() + ); + } +} + diff --git a/zone/gm_commands/interrogateinv.cpp b/zone/gm_commands/interrogateinv.cpp new file mode 100755 index 000000000..58451e05c --- /dev/null +++ b/zone/gm_commands/interrogateinv.cpp @@ -0,0 +1,76 @@ +#include "../client.h" + +void command_interrogateinv(Client *c, const Seperator *sep) +{ + // 'command_interrogateinv' is an in-memory inventory interrogation tool only. + // + // it does not verify against actual database entries..but, the output can be + // used to verify that something has been corrupted in a player's inventory. + // any error condition should be assumed that the item in question will be + // lost when the player logs out or zones (or incurrs any action that will + // consume the Client-Inventory object instance in question.) + // + // any item instances located at a greater depth than a reported error should + // be treated as an error themselves regardless of whether they report as the + // same or not. + + if (strcasecmp(sep->arg[1], "help") == 0) { + if (c->Admin() < commandInterrogateInv) { + c->Message(Chat::White, "Usage: #interrogateinv"); + c->Message(Chat::White, " Displays your inventory's current in-memory nested storage references"); + } + else { + c->Message(Chat::White, "Usage: #interrogateinv [log] [silent]"); + c->Message( + Chat::White, + " Displays your or your Player target inventory's current in-memory nested storage references" + ); + c->Message(Chat::White, " [log] - Logs interrogation to file"); + c->Message(Chat::White, " [silent] - Omits the in-game message portion of the interrogation"); + } + return; + } + + Client *target = nullptr; + std::map instmap; + bool log = false; + bool silent = false; + bool error = false; + bool allowtrip = false; + + if (c->Admin() < commandInterrogateInv) { + if (c->GetInterrogateInvState()) { + c->Message(Chat::Red, "The last use of #interrogateinv on this inventory instance discovered an error..."); + c->Message(Chat::Red, "Logging out, zoning or re-arranging items at this point will result in item loss!"); + return; + } + target = c; + allowtrip = true; + } + else { + if (c->GetTarget() == nullptr) { + target = c; + } + else if (c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); + } + else { + c->Message(Chat::Default, "Use of this command is limited to Client entities"); + return; + } + + if (strcasecmp(sep->arg[1], "log") == 0) { + log = true; + } + if (strcasecmp(sep->arg[2], "silent") == 0) { + silent = true; + } + } + + bool success = target->InterrogateInventory(c, log, silent, allowtrip, error); + + if (!success) { + c->Message(Chat::Red, "An unknown error occurred while processing Client::InterrogateInventory()"); + } +} + diff --git a/zone/gm_commands/interrupt.cpp b/zone/gm_commands/interrupt.cpp new file mode 100755 index 000000000..1126a850c --- /dev/null +++ b/zone/gm_commands/interrupt.cpp @@ -0,0 +1,16 @@ +#include "../client.h" + +void command_interrupt(Client *c, const Seperator *sep) +{ + uint16 ci_message = 0x01b7, ci_color = 0x0121; + + if (sep->arg[1][0]) { + ci_message = atoi(sep->arg[1]); + } + if (sep->arg[2][0]) { + ci_color = atoi(sep->arg[2]); + } + + c->InterruptSpell(ci_message, ci_color); +} + diff --git a/zone/gm_commands/invsnapshot.cpp b/zone/gm_commands/invsnapshot.cpp new file mode 100755 index 000000000..6e818524a --- /dev/null +++ b/zone/gm_commands/invsnapshot.cpp @@ -0,0 +1,418 @@ +#include "../client.h" + +void command_invsnapshot(Client *c, const Seperator *sep) +{ + if (!c) { + return; + } + + if (sep->argnum == 0 || strcmp(sep->arg[1], "help") == 0) { + std::string window_title = "Inventory Snapshot Argument Help Menu"; + + std::string window_text = + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + ""; + + if (c->Admin() >= commandInvSnapshot) { + window_text.append( + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + ); + } + + window_text.append( + "
Usage:#invsnapshot arguments
(required optional)
helpthis menu
capturetakes snapshot of character inventory
gcountreturns global snapshot count
gclear
now
delete all snapshots - rule
delete all snapshots - now
countreturns character snapshot count
clear
now
delete character snapshots - rule
delete character snapshots - now
list
count
lists entry ids for current character
limits to count
parsetstmpdisplays slots and items in snapshot
comparetstmpcompares inventory against snapshot
restoretstmprestores slots and items in snapshot
" + ); + + c->SendPopupToClient(window_title.c_str(), window_text.c_str()); + + return; + } + + if (c->Admin() >= commandInvSnapshot) { // global arguments + + if (strcmp(sep->arg[1], "gcount") == 0) { + auto is_count = database.CountInvSnapshots(); + c->Message( + Chat::White, + "There %s %i inventory snapshot%s.", + (is_count == 1 ? "is" : "are"), + is_count, + (is_count == 1 ? "" : "s")); + + return; + } + + if (strcmp(sep->arg[1], "gclear") == 0) { + if (strcmp(sep->arg[2], "now") == 0) { + database.ClearInvSnapshots(true); + c->Message(Chat::White, "Inventory snapshots cleared using current time."); + } + else { + database.ClearInvSnapshots(); + c->Message( + Chat::White, "Inventory snapshots cleared using RuleI(Character, InvSnapshotHistoryD) (%i day%s).", + RuleI(Character, InvSnapshotHistoryD), (RuleI(Character, InvSnapshotHistoryD) == 1 ? "" : "s")); + } + + return; + } + } + + if (!c->GetTarget() || !c->GetTarget()->IsClient()) { + c->Message(Chat::White, "Target must be a client."); + return; + } + + auto tc = (Client *) c->GetTarget(); + + if (strcmp(sep->arg[1], "capture") == 0) { + if (database.SaveCharacterInvSnapshot(tc->CharacterID())) { + tc->SetNextInvSnapshot(RuleI(Character, InvSnapshotMinIntervalM)); + c->Message( + Chat::White, + "Successful inventory snapshot taken of %s - setting next interval for %i minute%s.", + tc->GetName(), + RuleI(Character, InvSnapshotMinIntervalM), + (RuleI(Character, InvSnapshotMinIntervalM) == 1 ? "" : "s")); + } + else { + tc->SetNextInvSnapshot(RuleI(Character, InvSnapshotMinRetryM)); + c->Message( + Chat::White, + "Failed to take inventory snapshot of %s - retrying in %i minute%s.", + tc->GetName(), + RuleI(Character, InvSnapshotMinRetryM), + (RuleI(Character, InvSnapshotMinRetryM) == 1 ? "" : "s")); + } + + return; + } + + if (c->Admin() >= commandInvSnapshot) { + if (strcmp(sep->arg[1], "count") == 0) { + auto is_count = database.CountCharacterInvSnapshots(tc->CharacterID()); + c->Message( + Chat::White, + "%s (id: %u) has %i inventory snapshot%s.", + tc->GetName(), + tc->CharacterID(), + is_count, + (is_count == 1 ? "" : "s")); + + return; + } + + if (strcmp(sep->arg[1], "clear") == 0) { + if (strcmp(sep->arg[2], "now") == 0) { + database.ClearCharacterInvSnapshots(tc->CharacterID(), true); + c->Message( + Chat::White, + "%s\'s (id: %u) inventory snapshots cleared using current time.", + tc->GetName(), + tc->CharacterID()); + } + else { + database.ClearCharacterInvSnapshots(tc->CharacterID()); + c->Message( + Chat::White, + "%s\'s (id: %u) inventory snapshots cleared using RuleI(Character, InvSnapshotHistoryD) (%i day%s).", + tc->GetName(), + tc->CharacterID(), + RuleI(Character, InvSnapshotHistoryD), + (RuleI(Character, InvSnapshotHistoryD) == 1 ? "" : "s")); + } + + return; + } + + if (strcmp(sep->arg[1], "list") == 0) { + std::list> is_list; + database.ListCharacterInvSnapshots(tc->CharacterID(), is_list); + + if (is_list.empty()) { + c->Message(Chat::White, "No inventory snapshots for %s (id: %u)", tc->GetName(), tc->CharacterID()); + return; + } + + auto list_count = 0; + if (sep->IsNumber(2)) { + list_count = atoi(sep->arg[2]); + } + if (list_count < 1 || list_count > is_list.size()) { + list_count = is_list.size(); + } + + std::string window_title = StringFormat("Snapshots for %s", tc->GetName()); + + std::string window_text = + "" + "" + "" + "" + ""; + + for (auto iter : is_list) { + if (!list_count) { + break; + } + + window_text.append( + StringFormat( + "" + "" + "" + "", + iter.first, + iter.second + )); + + --list_count; + } + + window_text.append( + "
TimestampEntry Count
%u%i
" + ); + + c->SendPopupToClient(window_title.c_str(), window_text.c_str()); + + return; + } + + if (strcmp(sep->arg[1], "parse") == 0) { + if (!sep->IsNumber(2)) { + c->Message(Chat::White, "A timestamp is required to use this option."); + return; + } + + uint32 timestamp = atoul(sep->arg[2]); + + if (!database.ValidateCharacterInvSnapshotTimestamp(tc->CharacterID(), timestamp)) { + c->Message( + Chat::White, + "No inventory snapshots for %s (id: %u) exist at %u.", + tc->GetName(), + tc->CharacterID(), + timestamp + ); + return; + } + + std::list> parse_list; + database.ParseCharacterInvSnapshot(tc->CharacterID(), timestamp, parse_list); + + std::string window_title = StringFormat("Snapshot Parse for %s @ %u", tc->GetName(), timestamp); + + std::string window_text = "Slot: ItemID - Description
"; + + for (auto iter : parse_list) { + auto item_data = database.GetItem(iter.second); + std::string window_line = StringFormat( + "%i: %u - %s
", + iter.first, + iter.second, + (item_data ? item_data->Name : "[error]")); + + if (window_text.length() + window_line.length() < 4095) { + window_text.append(window_line); + } + else { + c->Message(Chat::White, "Too many snapshot entries to list..."); + break; + } + } + + c->SendPopupToClient(window_title.c_str(), window_text.c_str()); + + return; + } + + if (strcmp(sep->arg[1], "compare") == 0) { + if (!sep->IsNumber(2)) { + c->Message(Chat::White, "A timestamp is required to use this option."); + return; + } + + uint32 timestamp = atoul(sep->arg[2]); + + if (!database.ValidateCharacterInvSnapshotTimestamp(tc->CharacterID(), timestamp)) { + c->Message( + Chat::White, + "No inventory snapshots for %s (id: %u) exist at %u.", + tc->GetName(), + tc->CharacterID(), + timestamp + ); + return; + } + + std::list> inv_compare_list; + database.DivergeCharacterInventoryFromInvSnapshot(tc->CharacterID(), timestamp, inv_compare_list); + + std::list> iss_compare_list; + database.DivergeCharacterInvSnapshotFromInventory(tc->CharacterID(), timestamp, iss_compare_list); + + std::string window_title = StringFormat("Snapshot Comparison for %s @ %u", tc->GetName(), timestamp); + + std::string window_text = "Slot: (action) Snapshot -> Inventory
"; + + auto inv_iter = inv_compare_list.begin(); + auto iss_iter = iss_compare_list.begin(); + + while (true) { + std::string window_line; + + if (inv_iter == inv_compare_list.end() && iss_iter == iss_compare_list.end()) { + break; + } + else if (inv_iter != inv_compare_list.end() && iss_iter == iss_compare_list.end()) { + window_line = StringFormat("%i: (delete) [empty] -> %u
", inv_iter->first, inv_iter->second); + ++inv_iter; + } + else if (inv_iter == inv_compare_list.end() && iss_iter != iss_compare_list.end()) { + window_line = StringFormat("%i: (insert) %u -> [empty]
", iss_iter->first, iss_iter->second); + ++iss_iter; + } + else { + if (inv_iter->first < iss_iter->first) { + window_line = StringFormat( + "%i: (delete) [empty] -> %u
", + inv_iter->first, + inv_iter->second + ); + ++inv_iter; + } + else if (inv_iter->first > iss_iter->first) { + window_line = StringFormat( + "%i: (insert) %u -> [empty]
", + iss_iter->first, + iss_iter->second + ); + ++iss_iter; + } + else { + window_line = StringFormat( + "%i: (replace) %u -> %u
", + iss_iter->first, + iss_iter->second, + inv_iter->second + ); + ++inv_iter; + ++iss_iter; + } + } + + if (window_text.length() + window_line.length() < 4095) { + window_text.append(window_line); + } + else { + c->Message(Chat::White, "Too many comparison entries to list..."); + break; + } + } + + c->SendPopupToClient(window_title.c_str(), window_text.c_str()); + + return; + } + + if (strcmp(sep->arg[1], "restore") == 0) { + if (!sep->IsNumber(2)) { + c->Message(Chat::White, "A timestamp is required to use this option."); + return; + } + + uint32 timestamp = atoul(sep->arg[2]); + + if (!database.ValidateCharacterInvSnapshotTimestamp(tc->CharacterID(), timestamp)) { + c->Message( + Chat::White, + "No inventory snapshots for %s (id: %u) exist at %u.", + tc->GetName(), + tc->CharacterID(), + timestamp + ); + return; + } + + if (database.SaveCharacterInvSnapshot(tc->CharacterID())) { + tc->SetNextInvSnapshot(RuleI(Character, InvSnapshotMinIntervalM)); + } + else { + c->Message( + Chat::Red, "Failed to take pre-restore inventory snapshot of %s (id: %u).", + tc->GetName(), tc->CharacterID()); + return; + } + + if (database.RestoreCharacterInvSnapshot(tc->CharacterID(), timestamp)) { + // cannot delete all valid item slots from client..so, we worldkick + tc->WorldKick(); // self restores update before the 'kick' is processed + + c->Message( + Chat::White, "Successfully applied snapshot %u to %s's (id: %u) inventory.", + timestamp, tc->GetName(), tc->CharacterID()); + } + else { + c->Message( + Chat::Red, "Failed to apply snapshot %u to %s's (id: %u) inventory.", + timestamp, tc->GetName(), tc->CharacterID()); + } + + return; + } + } +} + diff --git a/zone/gm_commands/invul.cpp b/zone/gm_commands/invul.cpp new file mode 100755 index 000000000..a6db1f3c1 --- /dev/null +++ b/zone/gm_commands/invul.cpp @@ -0,0 +1,28 @@ +#include "../client.h" + +void command_invul(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments) { + c->Message(Chat::White, "Usage: #invul [On|Off]"); + return; + } + + bool invul_flag = atobool(sep->arg[1]); + Client *target = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); + } + + target->SetInvul(invul_flag); + c->Message( + Chat::White, + fmt::format( + "{} {} now {}.", + c == target ? "You" : target->GetCleanName(), + c == target ? "are" : "is", + invul_flag ? "invulnerable" : "vulnerable" + ).c_str() + ); +} + diff --git a/zone/gm_commands/ipban.cpp b/zone/gm_commands/ipban.cpp new file mode 100755 index 000000000..5dd63a0fb --- /dev/null +++ b/zone/gm_commands/ipban.cpp @@ -0,0 +1,21 @@ +#include "../client.h" + +void command_ipban(Client *c, const Seperator *sep) +{ + if (sep->arg[1] == 0) { + c->Message(Chat::White, "Usage: #ipban [xxx.xxx.xxx.xxx]"); + } + else { + if (database.AddBannedIP(sep->arg[1], c->GetName())) { + c->Message( + Chat::White, + "%s has been successfully added to the banned_ips table by %s", + sep->arg[1], + c->GetName()); + } + else { + c->Message(Chat::White, "IPBan Failed (IP address is possibly already in the table?)"); + } + } +} + diff --git a/zone/gm_commands/iplookup.cpp b/zone/gm_commands/iplookup.cpp new file mode 100755 index 000000000..1595a5ee6 --- /dev/null +++ b/zone/gm_commands/iplookup.cpp @@ -0,0 +1,23 @@ +#include "../client.h" +#include "../worldserver.h" + +extern WorldServer worldserver; + +void command_iplookup(Client *c, const Seperator *sep) +{ + auto pack = + new ServerPacket( + ServerOP_IPLookup, + sizeof(ServerGenericWorldQuery_Struct) + + strlen(sep->argplus[1]) + 1 + ); + ServerGenericWorldQuery_Struct *s = (ServerGenericWorldQuery_Struct *) pack->pBuffer; + strcpy(s->from, c->GetName()); + s->admin = c->Admin(); + if (sep->argplus[1][0] != 0) { + strcpy(s->query, sep->argplus[1]); + } + worldserver.SendPacket(pack); + safe_delete(pack); +} + diff --git a/zone/gm_commands/iteminfo.cpp b/zone/gm_commands/iteminfo.cpp new file mode 100755 index 000000000..683c23075 --- /dev/null +++ b/zone/gm_commands/iteminfo.cpp @@ -0,0 +1,94 @@ +#include "../client.h" +#include "../groups.h" + +void command_iteminfo(Client *c, const Seperator *sep) +{ + auto inst = c->GetInv()[EQ::invslot::slotCursor]; + if (!inst) { + c->Message(Chat::Red, "Error: You need an item on your cursor for this command"); + return; + } + auto item = inst->GetItem(); + if (!item) { + LogInventory("([{}]) Command #iteminfo processed an item with no data pointer"); + c->Message(Chat::Red, "Error: This item has no data reference"); + return; + } + + EQ::SayLinkEngine linker; + linker.SetLinkType(EQ::saylink::SayLinkItemInst); + linker.SetItemInst(inst); + + c->Message(Chat::White, "*** Item Info for [%s] ***", linker.GenerateLink().c_str()); + c->Message(Chat::White, ">> ID: %u, ItemUseType: %u, ItemClassType: %u", item->ID, item->ItemType, item->ItemClass); + c->Message(Chat::White, ">> IDFile: '%s', IconID: %u", item->IDFile, item->Icon); + c->Message( + Chat::White, + ">> Size: %u, Weight: %u, Price: %u, LDoNPrice: %u", + item->Size, + item->Weight, + item->Price, + item->LDoNPrice + ); + c->Message( + Chat::White, + ">> Material: 0x%02X, Color: 0x%08X, Tint: 0x%08X, Light: 0x%02X", + item->Material, + item->Color, + inst->GetColor(), + item->Light + ); + c->Message( + Chat::White, + ">> IsLore: %s, LoreGroup: %u, Lore: '%s'", + (item->LoreFlag ? "TRUE" : "FALSE"), + item->LoreGroup, + item->Lore + ); + c->Message( + Chat::White, ">> NoDrop: %u, NoRent: %u, NoPet: %u, NoTransfer: %u, FVNoDrop: %u", + item->NoDrop, item->NoRent, (uint8) item->NoPet, (uint8) item->NoTransfer, item->FVNoDrop + ); + + if (item->IsClassBook()) { + c->Message(Chat::White, "*** This item is a Book (filename:'%s') ***", item->Filename); + } + else if (item->IsClassBag()) { + c->Message(Chat::White, "*** This item is a Container (%u slots) ***", item->BagSlots); + } + else { + c->Message(Chat::White, "*** This item is Common ***"); + c->Message(Chat::White, ">> Classes: %u, Races: %u, Slots: %u", item->Classes, item->Races, item->Slots); + c->Message( + Chat::White, + ">> ReqSkill: %u, ReqLevel: %u, RecLevel: %u", + item->RecSkill, + item->ReqLevel, + item->RecLevel + ); + c->Message(Chat::White, ">> SkillModType: %u, SkillModValue: %i", item->SkillModType, item->SkillModValue); + c->Message( + Chat::White, ">> BaneRaceType: %u, BaneRaceDamage: %u, BaneBodyType: %u, BaneBodyDamage: %i", + item->BaneDmgRace, item->BaneDmgRaceAmt, item->BaneDmgBody, item->BaneDmgAmt + ); + c->Message( + Chat::White, + ">> Magic: %s, SpellID: %i, ProcLevel: %u, Charges: %u, MaxCharges: %u", + (item->Magic ? "TRUE" : "FALSE"), + item->Click.Effect, + item->Click.Level, + inst->GetCharges(), + item->MaxCharges + ); + c->Message( + Chat::White, + ">> EffectType: 0x%02X, CastTime: %.2f", + (uint8) item->Click.Type, + ((double) item->CastTime / 1000)); + } + + if (c->Admin() >= AccountStatus::GMMgmt) { + c->Message(Chat::White, ">> MinStatus: %u", item->MinStatus); + } +} + diff --git a/zone/gm_commands/itemsearch.cpp b/zone/gm_commands/itemsearch.cpp new file mode 100755 index 000000000..4617e2397 --- /dev/null +++ b/zone/gm_commands/itemsearch.cpp @@ -0,0 +1,125 @@ +#include "../client.h" + +void command_itemsearch(Client *c, const Seperator *sep) +{ + if (sep->arg[1][0] == 0) { + c->Message(Chat::White, "Usage: #itemsearch [search string]"); + } + else { + const char *search_criteria = sep->argplus[1]; + + const EQ::ItemData *item = nullptr; + EQ::SayLinkEngine linker; + linker.SetLinkType(EQ::saylink::SayLinkItemData); + + if (Seperator::IsNumber(search_criteria)) { + item = database.GetItem(atoi(search_criteria)); + if (item) { + linker.SetItemData(item); + std::string item_id = std::to_string(item->ID); + std::string saylink_commands = + "[" + + EQ::SayLinkEngine::GenerateQuestSaylink( + "#si " + item_id, + false, + "X" + ) + + "] "; + + if (item->Stackable && item->StackSize > 1) { + std::string stack_size = std::to_string(item->StackSize); + saylink_commands += + "[" + + EQ::SayLinkEngine::GenerateQuestSaylink( + "#si " + item_id + " " + stack_size, + false, + stack_size + ) + + "]"; + } + + c->Message( + Chat::White, + fmt::format( + " Summon {} [{}] [{}]", + saylink_commands, + linker.GenerateLink(), + item->ID + ).c_str() + ); + } + else { + c->Message( + Chat::White, + fmt::format( + "Item {} not found", + search_criteria + ).c_str() + ); + } + + return; + } + + int count = 0; + char sName[64]; + char sCriteria[255]; + strn0cpy(sCriteria, search_criteria, sizeof(sCriteria)); + strupr(sCriteria); + char *pdest; + uint32 it = 0; + while ((item = database.IterateItems(&it))) { + strn0cpy(sName, item->Name, sizeof(sName)); + strupr(sName); + pdest = strstr(sName, sCriteria); + if (pdest != nullptr) { + linker.SetItemData(item); + std::string item_id = std::to_string(item->ID); + std::string saylink_commands = + "[" + + EQ::SayLinkEngine::GenerateQuestSaylink( + "#si " + item_id, + false, + "X" + ) + + "] "; + if (item->Stackable && item->StackSize > 1) { + std::string stack_size = std::to_string(item->StackSize); + saylink_commands += + "[" + + EQ::SayLinkEngine::GenerateQuestSaylink( + "#si " + item_id + " " + stack_size, + false, + stack_size + ) + + "]"; + } + + c->Message( + Chat::White, + fmt::format( + " Summon {} [{}] [{}]", + saylink_commands, + linker.GenerateLink(), + item->ID + ).c_str() + ); + + ++count; + } + + if (count == 50) { + break; + } + } + + if (count == 50) { + c->Message(Chat::White, "50 items shown...too many results."); + } + else { + c->Message(Chat::White, "%i items found", count); + } + + } +} + diff --git a/zone/gm_commands/kick.cpp b/zone/gm_commands/kick.cpp new file mode 100755 index 000000000..a6a4644c1 --- /dev/null +++ b/zone/gm_commands/kick.cpp @@ -0,0 +1,36 @@ +#include "../client.h" +#include "../worldserver.h" + +extern WorldServer worldserver; + +void command_kick(Client *c, const Seperator *sep) +{ + if (sep->arg[1][0] == 0) { + c->Message(Chat::White, "Usage: #kick [charname]"); + } + else { + Client *client = entity_list.GetClientByName(sep->arg[1]); + if (client != 0) { + if (client->Admin() <= c->Admin()) { + client->Message(Chat::White, "You have been kicked by %s", c->GetName()); + auto outapp = new EQApplicationPacket(OP_GMKick, 0); + client->QueuePacket(outapp); + client->Kick("Ordered kicked by command"); + c->Message(Chat::White, "Kick: local: kicking %s", sep->arg[1]); + } + } + else if (!worldserver.Connected()) { + c->Message(Chat::White, "Error: World server disconnected"); + } + else { + auto pack = new ServerPacket(ServerOP_KickPlayer, sizeof(ServerKickPlayer_Struct)); + ServerKickPlayer_Struct *skp = (ServerKickPlayer_Struct *) pack->pBuffer; + strcpy(skp->adminname, c->GetName()); + strcpy(skp->name, sep->arg[1]); + skp->adminrank = c->Admin(); + worldserver.SendPacket(pack); + safe_delete(pack); + } + } +} + diff --git a/zone/gm_commands/kill.cpp b/zone/gm_commands/kill.cpp new file mode 100755 index 000000000..d54245662 --- /dev/null +++ b/zone/gm_commands/kill.cpp @@ -0,0 +1,12 @@ +#include "../client.h" + +void command_kill(Client *c, const Seperator *sep) +{ + if (!c->GetTarget()) { + c->Message(Chat::White, "Error: #Kill: No target."); + } + else if (!c->GetTarget()->IsClient() || c->GetTarget()->CastToClient()->Admin() <= c->Admin()) { + c->GetTarget()->Kill(); + } +} + diff --git a/zone/gm_commands/killallnpcs.cpp b/zone/gm_commands/killallnpcs.cpp new file mode 100755 index 000000000..5ae74c3bb --- /dev/null +++ b/zone/gm_commands/killallnpcs.cpp @@ -0,0 +1,76 @@ +#include "../client.h" + +void command_killallnpcs(Client *c, const Seperator *sep) +{ + std::string search_string; + if (sep->arg[1]) { + search_string = str_tolower(sep->arg[1]); + } + + int killed_count = 0; + for (auto& npc_entity : entity_list.GetNPCList()) { + auto entity_id = npc_entity.first; + if (!entity_id) { + continue; + } + + auto npc = npc_entity.second; + if (!npc) { + continue; + } + + std::string entity_name = str_tolower(npc->GetName()); + if ( + ( + !search_string.empty() && + entity_name.find(search_string) == std::string::npos + ) || + !npc->IsAttackAllowed(c) + ) { + continue; + } + + npc->Damage( + c, + npc->GetHP(), + SPELL_UNKNOWN, + EQ::skills::SkillDragonPunch + ); + + killed_count++; + } + + if (killed_count) { + c->Message( + Chat::White, + fmt::format( + "Killed {} NPC{}{}.", + killed_count, + killed_count != 1 ? "s" : "", + ( + !search_string.empty() ? + fmt::format( + " that matched '{}'", + search_string + ) : + "" + ) + ).c_str() + ); + } else { + c->Message( + Chat::White, + fmt::format( + "There were no NPCs to kill{}.", + ( + !search_string.empty() ? + fmt::format( + " that matched '{}'", + search_string + ) : + "" + ) + ).c_str() + ); + } +} diff --git a/zone/gm_commands/lastname.cpp b/zone/gm_commands/lastname.cpp new file mode 100755 index 000000000..a990785d9 --- /dev/null +++ b/zone/gm_commands/lastname.cpp @@ -0,0 +1,37 @@ +#include "../client.h" + +void command_lastname(Client *c, const Seperator *sep) +{ + Client *target = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); + } + + LogInfo("#lastname request from [{}] for [{}]", c->GetCleanName(), target->GetCleanName()); + + std::string last_name = sep->arg[1]; + if (last_name.size() > 64) { + c->Message(Chat::White, "Usage: #lastname [Last Name] (Last Name must be 64 characters or less)"); + return; + } + + target->ChangeLastName(last_name.c_str()); + c->Message( + Chat::White, + fmt::format( + "{} now {} a last name of '{}'.", + ( + c == target ? + "You" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ), + c == target ? "have" : "has", + last_name + ).c_str() + ); +} + diff --git a/zone/gm_commands/list.cpp b/zone/gm_commands/list.cpp new file mode 100755 index 000000000..a38b3ddc3 --- /dev/null +++ b/zone/gm_commands/list.cpp @@ -0,0 +1,265 @@ +#include "../client.h" +#include "../corpse.h" +#include "../object.h" +#include "../doors.h" + +void command_list(Client *c, const Seperator *sep) +{ + std::string search_type; + if (strcasecmp(sep->arg[1], "npcs") == 0) { + search_type = "npcs"; + } + + if (strcasecmp(sep->arg[1], "players") == 0) { + search_type = "players"; + } + + if (strcasecmp(sep->arg[1], "corpses") == 0) { + search_type = "corpses"; + } + + if (strcasecmp(sep->arg[1], "doors") == 0) { + search_type = "doors"; + } + + if (strcasecmp(sep->arg[1], "objects") == 0) { + search_type = "objects"; + } + + if (search_type.length() > 0) { + + int entity_count = 0; + int found_count = 0; + + std::string search_string; + + if (sep->arg[2]) { + search_string = sep->arg[2]; + } + + /** + * NPC + */ + if (search_type.find("npcs") != std::string::npos) { + auto &entity_list_search = entity_list.GetMobList(); + + for (auto &itr : entity_list_search) { + Mob *entity = itr.second; + if (!entity->IsNPC()) { + continue; + } + + entity_count++; + + std::string entity_name = entity->GetName(); + + /** + * Filter by name + */ + if (search_string.length() > 0 && entity_name.find(search_string) == std::string::npos) { + continue; + } + + std::string saylink = StringFormat( + "#goto %.0f %0.f %.0f", + entity->GetX(), + entity->GetY(), + entity->GetZ() + (entity->IsBoat() ? 50 : 0)); + + c->Message( + 0, + "| %s | ID %5d | %s | x %.0f | y %0.f | z %.0f", + EQ::SayLinkEngine::GenerateQuestSaylink(saylink, false, "Goto").c_str(), + entity->GetID(), + entity->GetName(), + entity->GetX(), + entity->GetY(), + entity->GetZ() + ); + + found_count++; + } + } + + /** + * Client + */ + if (search_type.find("players") != std::string::npos) { + auto &entity_list_search = entity_list.GetClientList(); + + for (auto &itr : entity_list_search) { + Client *entity = itr.second; + + entity_count++; + + std::string entity_name = entity->GetName(); + + /** + * Filter by name + */ + if (search_string.length() > 0 && entity_name.find(search_string) == std::string::npos) { + continue; + } + + std::string saylink = StringFormat( + "#goto %.0f %0.f %.0f", + entity->GetX(), + entity->GetY(), + entity->GetZ()); + + c->Message( + 0, + "| %s | ID %5d | %s | x %.0f | y %0.f | z %.0f", + EQ::SayLinkEngine::GenerateQuestSaylink(saylink, false, "Goto").c_str(), + entity->GetID(), + entity->GetName(), + entity->GetX(), + entity->GetY(), + entity->GetZ() + ); + + found_count++; + } + } + + /** + * Corpse + */ + if (search_type.find("corpses") != std::string::npos) { + auto &entity_list_search = entity_list.GetCorpseList(); + + for (auto &itr : entity_list_search) { + Corpse *entity = itr.second; + + entity_count++; + + std::string entity_name = entity->GetName(); + + /** + * Filter by name + */ + if (search_string.length() > 0 && entity_name.find(search_string) == std::string::npos) { + continue; + } + + std::string saylink = StringFormat( + "#goto %.0f %0.f %.0f", + entity->GetX(), + entity->GetY(), + entity->GetZ()); + + c->Message( + 0, + "| %s | ID %5d | %s | x %.0f | y %0.f | z %.0f", + EQ::SayLinkEngine::GenerateQuestSaylink(saylink, false, "Goto").c_str(), + entity->GetID(), + entity->GetName(), + entity->GetX(), + entity->GetY(), + entity->GetZ() + ); + + found_count++; + } + } + + /** + * Doors + */ + if (search_type.find("doors") != std::string::npos) { + auto &entity_list_search = entity_list.GetDoorsList(); + + for (auto &itr : entity_list_search) { + Doors *entity = itr.second; + + entity_count++; + + std::string entity_name = entity->GetDoorName(); + + /** + * Filter by name + */ + if (search_string.length() > 0 && entity_name.find(search_string) == std::string::npos) { + continue; + } + + std::string saylink = StringFormat( + "#goto %.0f %0.f %.0f", + entity->GetX(), + entity->GetY(), + entity->GetZ()); + + c->Message( + 0, + "| %s | Entity ID %5d | Door ID %i | %s | x %.0f | y %0.f | z %.0f", + EQ::SayLinkEngine::GenerateQuestSaylink(saylink, false, "Goto").c_str(), + entity->GetID(), + entity->GetDoorID(), + entity->GetDoorName(), + entity->GetX(), + entity->GetY(), + entity->GetZ() + ); + + found_count++; + } + } + + /** + * Objects + */ + if (search_type.find("objects") != std::string::npos) { + auto &entity_list_search = entity_list.GetObjectList(); + + for (auto &itr : entity_list_search) { + Object *entity = itr.second; + + entity_count++; + + std::string entity_name = entity->GetModelName(); + + /** + * Filter by name + */ + if (search_string.length() > 0 && entity_name.find(search_string) == std::string::npos) { + continue; + } + + std::string saylink = StringFormat( + "#goto %.0f %0.f %.0f", + entity->GetX(), + entity->GetY(), + entity->GetZ()); + + c->Message( + 0, + "| %s | Entity ID %5d | Object DBID %i | %s | x %.0f | y %0.f | z %.0f", + EQ::SayLinkEngine::GenerateQuestSaylink(saylink, false, "Goto").c_str(), + entity->GetID(), + entity->GetDBID(), + entity->GetModelName(), + entity->GetX(), + entity->GetY(), + entity->GetZ() + ); + + found_count++; + } + } + + if (found_count) { + c->Message( + 0, "Found (%i) of type (%s) in zone (%i) total", + found_count, + search_type.c_str(), + entity_count + ); + } + } + else { + c->Message(Chat::White, "Usage of #list"); + c->Message(Chat::White, "- #list [npcs|players|corpses|doors|objects] [search]"); + c->Message(Chat::White, "- Example: #list npcs (Blank for all)"); + } +} + diff --git a/zone/gm_commands/listpetition.cpp b/zone/gm_commands/listpetition.cpp new file mode 100755 index 000000000..110809deb --- /dev/null +++ b/zone/gm_commands/listpetition.cpp @@ -0,0 +1,22 @@ +#include "../client.h" + +void command_listpetition(Client *c, const Seperator *sep) +{ + std::string query = "SELECT petid, charname, accountname FROM petitions ORDER BY petid"; + auto results = database.QueryDatabase(query); + if (!results.Success()) { + return; + } + + LogInfo("Petition list requested by [{}]", c->GetName()); + + if (results.RowCount() == 0) { + return; + } + + c->Message(Chat::Red, " ID : Character Name , Account Name"); + + for (auto row = results.begin(); row != results.end(); ++row) + c->Message(Chat::Yellow, " %s: %s , %s ", row[0], row[1], row[2]); +} + diff --git a/zone/gm_commands/loc.cpp b/zone/gm_commands/loc.cpp new file mode 100755 index 000000000..c45513dc9 --- /dev/null +++ b/zone/gm_commands/loc.cpp @@ -0,0 +1,32 @@ +#include "../client.h" + +void command_loc(Client *c, const Seperator *sep) +{ + Mob *target = c; + if (c->GetTarget()) { + target = c->GetTarget(); + } + + auto target_position = target->GetPosition(); + + c->Message( + Chat::White, + fmt::format( + "Location for {} | XYZ: {:.2f}, {:.2f}, {:.2f} Heading: {:.2f}", + ( + c == target ? + "Yourself" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ), + target_position.x, + target_position.y, + target_position.z, + target_position.w + ).c_str() + ); +} + diff --git a/zone/gm_commands/lock.cpp b/zone/gm_commands/lock.cpp new file mode 100755 index 000000000..1fda53841 --- /dev/null +++ b/zone/gm_commands/lock.cpp @@ -0,0 +1,15 @@ +#include "../client.h" +#include "../worldserver.h" + +extern WorldServer worldserver; + +void command_lock(Client *c, const Seperator *sep) +{ + auto outpack = new ServerPacket(ServerOP_Lock, sizeof(ServerLock_Struct)); + ServerLock_Struct *lss = (ServerLock_Struct *) outpack->pBuffer; + strcpy(lss->myname, c->GetName()); + lss->mode = 1; + worldserver.SendPacket(outpack); + safe_delete(outpack); +} + diff --git a/zone/gm_commands/logcommand.cpp b/zone/gm_commands/logcommand.cpp new file mode 100755 index 000000000..3b85e2ff3 --- /dev/null +++ b/zone/gm_commands/logcommand.cpp @@ -0,0 +1,85 @@ +#include "../client.h" + +void command_logcommand(Client *c, const char *message) +{ + int admin = c->Admin(); + + bool continueevents = false; + switch (zone->loglevelvar) { //catch failsafe + case 9: { // log only LeadGM + if ( + admin >= AccountStatus::GMLeadAdmin && + admin < AccountStatus::GMMgmt + ) { + continueevents = true; + } + break; + } + case 8: { // log only GM + if ( + admin >= AccountStatus::GMAdmin && + admin < AccountStatus::GMLeadAdmin + ) { + continueevents = true; + } + break; + } + case 1: { + if (admin >= AccountStatus::GMMgmt) { + continueevents = true; + } + break; + } + case 2: { + if (admin >= AccountStatus::GMLeadAdmin) { + continueevents = true; + } + break; + } + case 3: { + if (admin >= AccountStatus::GMAdmin) { + continueevents = true; + } + break; + } + case 4: { + if (admin >= AccountStatus::QuestTroupe) { + continueevents = true; + } + break; + } + case 5: { + if (admin >= AccountStatus::ApprenticeGuide) { + continueevents = true; + } + break; + } + case 6: { + if (admin >= AccountStatus::Steward) { + continueevents = true; + } + break; + } + case 7: { + continueevents = true; + break; + } + } + + if (continueevents) { + database.logevents( + c->AccountName(), + c->AccountID(), + admin, c->GetName(), + c->GetTarget() ? c->GetTarget()->GetName() : "None", + "Command", + message, + 1 + ); + } +} + + +/* + * commands go below here + */ diff --git a/zone/gm_commands/logs.cpp b/zone/gm_commands/logs.cpp new file mode 100755 index 000000000..bda01a63a --- /dev/null +++ b/zone/gm_commands/logs.cpp @@ -0,0 +1,202 @@ +#include "../client.h" +#include "../worldserver.h" + +extern WorldServer worldserver; + +void command_logs(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments) { + c->Message( + Chat::White, + "#logs list - Shows current log settings and categories loaded into the current process' memory for the first 50 log categories" + ); + c->Message( + Chat::White, + "#logs list [Start Category ID] - Shows current log settings and categories loaded into the current process' memory, only shows 50 at a time starting at specified Category ID" + ); + c->Message( + Chat::White, + "#logs reload - Reload all settings in world and all zone processes with what is defined in the database" + ); + c->Message( + Chat::White, + "#logs set [console|file|gmsay] [Category ID] [Debug Level (1-3)] - Sets log settings during the lifetime of the zone" + ); + return; + } + + bool is_list = !strcasecmp(sep->arg[1], "list"); + bool is_reload = !strcasecmp(sep->arg[1], "reload"); + bool is_set = !strcasecmp(sep->arg[1], "set"); + + if (!is_list && !is_reload && !is_set) { + c->Message( + Chat::White, + "#logs list - Shows current log settings and categories loaded into the current process' memory for the first 50 log categories" + ); + c->Message( + Chat::White, + "#logs list [Start Category ID] - Shows current log settings and categories loaded into the current process' memory, only shows 50 at a time starting at specified Category ID" + ); + c->Message( + Chat::White, + "#logs reload - Reload all settings in world and all zone processes with what is defined in the database" + ); + c->Message( + Chat::White, + "#logs set [console|file|gmsay] [Category ID] [Debug Level (1-3)] - Sets log settings during the lifetime of the zone" + ); + return; + } + + if (is_list) { + uint32 start_category_id = 1; + if (sep->IsNumber(2)) { + start_category_id = std::stoul(sep->arg[2]); + } + + uint32 max_category_id = (start_category_id + 49); + + std::string popup_text = ""; + + popup_text += ""; + popup_text += ""; + popup_text += ""; + popup_text += ""; + popup_text += ""; + popup_text += ""; + popup_text += ""; + + for (int index = start_category_id; index <= max_category_id; index++) { + if (index >= Logs::LogCategory::MaxCategoryID) { + max_category_id = (Logs::LogCategory::MaxCategoryID - 1); + break; + } + + popup_text += fmt::format( + "", + index, + Logs::LogCategoryName[index], + LogSys.log_settings[index].log_to_console, + LogSys.log_settings[index].log_to_file, + LogSys.log_settings[index].log_to_gmsay + ); + } + + popup_text += "
IDNameConsoleFileGM Say
{}{}{}{}{}
"; + + std::string popup_title = fmt::format( + "Log Settings [{} to {}]", + start_category_id, + max_category_id + ); + + c->SendPopupToClient( + popup_title.c_str(), + popup_text.c_str() + ); + + c->Message( + Chat::White, + fmt::format( + "Viewing log category settings from {} ({}) to {} ({}).", + Logs::LogCategoryName[start_category_id], + start_category_id, + Logs::LogCategoryName[max_category_id], + max_category_id + ).c_str() + ); + + int next_category_id = (max_category_id + 1); + if (next_category_id < Logs::LogCategory::MaxCategoryID) { + auto next_list_string = fmt::format( + "#logs list {}", + next_category_id + ); + + auto next_list_link = EQ::SayLinkEngine::GenerateQuestSaylink( + next_list_string, + false, + next_list_string + ); + + c->Message( + Chat::White, + fmt::format( + "To view the next 50 log settings, you can use {}.", + next_list_link + ).c_str() + ); + } + } else if (is_reload) { + auto pack = new ServerPacket(ServerOP_ReloadLogs, 0); + worldserver.SendPacket(pack); + c->Message( + Chat::White, + "Reloaded log settings worldwide." + ); + safe_delete(pack); + } else if (is_set) { + auto logs_set = false; + bool is_console = !strcasecmp(sep->arg[2], "console"); + bool is_file = !strcasecmp(sep->arg[2], "file"); + bool is_gmsay = !strcasecmp(sep->arg[2], "gmsay"); + + if (!is_console && !is_file && !is_gmsay) { + c->Message( + Chat::White, + "#logs set [console|file|gmsay] [Category ID] [Debug Level (1-3)] - Sets log settings during the lifetime of the zone" + ); + c->Message(Chat::White, "Example: #logs set gmsay 20 1 - Would output Quest errors to gmsay"); + return; + } + + logs_set = true; + + auto category_id = std::stoul(sep->arg[3]); + auto setting = std::stoul(sep->arg[4]); + + if (is_console) { + LogSys.log_settings[category_id].log_to_console = setting; + } else if (is_file) { + LogSys.log_settings[category_id].log_to_file = setting; + } else if (is_gmsay) { + LogSys.log_settings[category_id].log_to_gmsay = setting; + } + + if (logs_set) { + std::string popup_text = ""; + + popup_text += fmt::format( + "", + category_id + ); + + popup_text += fmt::format( + "", + Logs::LogCategoryName[category_id] + ); + + popup_text += fmt::format( + "", + sep->arg[2] + ); + + popup_text += fmt::format( + "", + setting + ); + + popup_text += "
ID{}
Category{}
Method{}
Setting{}
"; + + c->SendPopupToClient( + "Log Settings Applied", + popup_text.c_str() + ); + } + + LogSys.log_settings[category_id].is_category_enabled = setting ? 1 : 0; + } +} + diff --git a/zone/gm_commands/makepet.cpp b/zone/gm_commands/makepet.cpp new file mode 100755 index 000000000..d83411404 --- /dev/null +++ b/zone/gm_commands/makepet.cpp @@ -0,0 +1,12 @@ +#include "../client.h" + +void command_makepet(Client *c, const Seperator *sep) +{ + if (sep->arg[1][0] == '\0') { + c->Message(Chat::White, "Usage: #makepet pet_type_name (will not survive across zones)"); + } + else { + c->MakePet(0, sep->arg[1]); + } +} + diff --git a/zone/gm_commands/mana.cpp b/zone/gm_commands/mana.cpp new file mode 100755 index 000000000..dc75a0aa3 --- /dev/null +++ b/zone/gm_commands/mana.cpp @@ -0,0 +1,33 @@ +#include "../client.h" + +void command_mana(Client *c, const Seperator *sep) +{ + auto target = c->GetTarget() ? c->GetTarget() : c; + int mana = 0; + if (target->IsClient()) { + mana = target->CastToClient()->CalcMaxMana(); + target->CastToClient()->SetMana(mana); + } + else { + mana = target->CalcMaxMana(); + target->SetMana(mana); + } + + c->Message( + Chat::White, + fmt::format( + "Set {} to full Mana ({}).", + ( + c == target ? + "yourself" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ), + mana + ).c_str() + ); +} + diff --git a/zone/gm_commands/max_all_skills.cpp b/zone/gm_commands/max_all_skills.cpp new file mode 100755 index 000000000..6c63c60ca --- /dev/null +++ b/zone/gm_commands/max_all_skills.cpp @@ -0,0 +1,20 @@ +#include "../client.h" + +void command_max_all_skills(Client *c, const Seperator *sep) +{ + if (c) { + Client *client_target = (c->GetTarget() ? (c->GetTarget()->IsClient() ? c->GetTarget()->CastToClient() : c) + : c); + auto Skills = EQ::skills::GetSkillTypeMap(); + for (auto &skills_iter : Skills) { + auto skill_id = skills_iter.first; + auto current_skill_value = ( + (EQ::skills::IsSpecializedSkill(skill_id)) ? + 50 : + content_db.GetSkillCap(client_target->GetClass(), skill_id, client_target->GetLevel()) + ); + client_target->SetSkill(skill_id, current_skill_value); + } + } +} + diff --git a/zone/gm_commands/memspell.cpp b/zone/gm_commands/memspell.cpp new file mode 100755 index 000000000..673d2efd0 --- /dev/null +++ b/zone/gm_commands/memspell.cpp @@ -0,0 +1,80 @@ +#include "../client.h" + +void command_memspell(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if ( + !arguments || + !sep->IsNumber(1) + ) { + c->Message(Chat::White, "Usage: #memspell [Spell ID] [Spell Gem]"); + return; + } + + auto target = c; + if (c->GetTarget() && c->GetTarget()->IsClient() && c->GetGM()) { + target = c->GetTarget()->CastToClient(); + } + + auto spell_id = static_cast(std::stoul(sep->arg[1])); + if (!IsValidSpell(spell_id)) { + c->Message( + Chat::White, + fmt::format( + "Spell ID {} could not be found.", + spell_id + ).c_str() + ); + return; + } + + auto empty_slot = target->FindEmptyMemSlot(); + if (empty_slot == -1) { + c->Message( + Chat::White, + fmt::format( + "{} not have a place to memorize {} ({}).", + ( + c == target ? + "You do" : + fmt::format( + "{} ({}) does", + target->GetCleanName(), + target->GetID() + ) + ), + GetSpellName(spell_id), + spell_id + ).c_str() + ); + return; + } + + auto spell_gem = sep->IsNumber(2) ? std::stoul(sep->arg[2]) : empty_slot; + if (spell_gem > EQ::spells::SPELL_GEM_COUNT) { + c->Message( + Chat::White, + fmt::format( + "Spell Gems range from 0 to {}.", + EQ::spells::SPELL_GEM_COUNT + ).c_str() + ); + return; + } + + target->MemSpell(spell_id, spell_gem); + + if (c != target) { + c->Message( + Chat::White, + fmt::format( + "{} ({}) memorized to spell gem {} for {} ({}).", + GetSpellName(spell_id), + spell_id, + spell_gem, + target->GetCleanName(), + target->GetID() + ).c_str() + ); + } +} diff --git a/zone/gm_commands/merchantcloseshop.cpp b/zone/gm_commands/merchantcloseshop.cpp new file mode 100755 index 000000000..571d80d46 --- /dev/null +++ b/zone/gm_commands/merchantcloseshop.cpp @@ -0,0 +1,13 @@ +#include "../client.h" + +void command_merchantcloseshop(Client *c, const Seperator *sep) +{ + Mob *merchant = c->GetTarget(); + if (!merchant || merchant->GetClass() != MERCHANT) { + c->Message(Chat::White, "You must target a merchant to close their shop."); + return; + } + + merchant->CastToNPC()->MerchantCloseShop(); +} + diff --git a/zone/gm_commands/merchantopenshop.cpp b/zone/gm_commands/merchantopenshop.cpp new file mode 100755 index 000000000..cd05465ba --- /dev/null +++ b/zone/gm_commands/merchantopenshop.cpp @@ -0,0 +1,13 @@ +#include "../client.h" + +void command_merchantopenshop(Client *c, const Seperator *sep) +{ + Mob *merchant = c->GetTarget(); + if (!merchant || merchant->GetClass() != MERCHANT) { + c->Message(Chat::White, "You must target a merchant to open their shop."); + return; + } + + merchant->CastToNPC()->MerchantOpenShop(); +} + diff --git a/zone/gm_commands/modifynpcstat.cpp b/zone/gm_commands/modifynpcstat.cpp new file mode 100755 index 000000000..5cde6e8f3 --- /dev/null +++ b/zone/gm_commands/modifynpcstat.cpp @@ -0,0 +1,30 @@ +#include "../client.h" + +void command_modifynpcstat(Client *c, const Seperator *sep) +{ + if (!c) { + return; + } + + if (sep->arg[1][0] == '\0') { + c->Message(Chat::White, "usage #modifynpcstat arg value"); + c->Message( + Chat::White, + "Args: ac, str, sta, agi, dex, wis, _int, cha, max_hp, mr, fr, cr, pr, dr, runspeed, special_attacks, " + "attack_speed, atk, accuracy, trackable, min_hit, max_hit, see_invis_undead, see_hide, see_improved_hide, " + "hp_regen, mana_regen, aggro, assist, slow_mitigation, loottable_id, healscale, spellscale" + ); + return; + } + + if (!c->GetTarget()) { + return; + } + + if (!c->GetTarget()->IsNPC()) { + return; + } + + c->GetTarget()->CastToNPC()->ModifyNPCStat(sep->arg[1], sep->arg[2]); +} + diff --git a/zone/gm_commands/motd.cpp b/zone/gm_commands/motd.cpp new file mode 100755 index 000000000..fdfba84b4 --- /dev/null +++ b/zone/gm_commands/motd.cpp @@ -0,0 +1,15 @@ +#include "../client.h" +#include "../worldserver.h" + +extern WorldServer worldserver; + +void command_motd(Client *c, const Seperator *sep) +{ + auto outpack = new ServerPacket(ServerOP_Motd, sizeof(ServerMotd_Struct)); + ServerMotd_Struct *mss = (ServerMotd_Struct *) outpack->pBuffer; + strn0cpy(mss->myname, c->GetName(), 64); + strn0cpy(mss->motd, sep->argplus[1], 512); + worldserver.SendPacket(outpack); + safe_delete(outpack); +} + diff --git a/zone/gm_commands/movechar.cpp b/zone/gm_commands/movechar.cpp new file mode 100755 index 000000000..cec371b83 --- /dev/null +++ b/zone/gm_commands/movechar.cpp @@ -0,0 +1,102 @@ +#include "../client.h" + +void command_movechar(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (arguments < 2) { + c->Message(Chat::White, "Usage: #movechar [Character ID|Character Name] [Zone ID|Zone Short Name]"); + return; + } + + std::string character_name = ( + sep->IsNumber(1) ? + database.GetCharNameByID(std::stoul(sep->arg[1])) : + sep->arg[1] + ); + auto character_id = database.GetCharacterID(character_name.c_str()); + if (!character_id) { + c->Message( + Chat::White, + fmt::format( + "Character {} could not be found.", + character_name + ).c_str() + ); + return; + } + + auto account_id = database.GetAccountIDByChar(character_name.c_str()); + + std::string zone_short_name = str_tolower( + sep->IsNumber(2) ? + ZoneName(std::stoul(sep->arg[2]), true) : + sep->arg[2] + ); + + bool is_unknown_zone = zone_short_name.find("unknown") != std::string::npos; + if (is_unknown_zone) { + c->Message( + Chat::White, + fmt::format( + "Zone ID {} could not be found.", + std::stoul(sep->arg[2]) + ).c_str() + ); + return; + } + + auto zone_id = ZoneID(zone_short_name); + std::string zone_long_name = ZoneLongName(zone_id); + + bool is_special_zone = ( + zone_short_name.find("cshome") != std::string::npos || + zone_short_name.find("load") != std::string::npos || + zone_short_name.find("load2") != std::string::npos + ); + + if (c->Admin() < commandMovecharToSpecials && is_special_zone) { + c->Message( + Chat::White, + fmt::format( + "{} ({}) is a special zone and you cannot move someone there.", + zone_long_name, + zone_short_name + ).c_str() + ); + return; + } + + if ( + c->Admin() >= commandMovecharSelfOnly || + account_id == c->AccountID() + ) { + bool moved = database.MoveCharacterToZone(character_name.c_str(), zone_id); + std::string moved_string = ( + moved ? + "Succeeded" : + "Failed" + ); + c->Message( + Chat::White, + fmt::format( + "Character Move {} | Character: {} ({})", + moved_string, + character_name, + character_id + ).c_str() + ); + + c->Message( + Chat::White, + fmt::format( + "Character Move {} | Zone: {} ({}) ID: {}", + moved_string, + zone_long_name, + zone_short_name, + zone_id + ).c_str() + ); + } else { + c->Message(Chat::White, "You cannot move characters that are not on your account."); + } +} diff --git a/zone/gm_commands/movement.cpp b/zone/gm_commands/movement.cpp new file mode 100755 index 000000000..f934f900c --- /dev/null +++ b/zone/gm_commands/movement.cpp @@ -0,0 +1,76 @@ +#include "../client.h" +#include "../mob_movement_manager.h" + +void command_movement(Client *c, const Seperator *sep) +{ + auto &mgr = MobMovementManager::Get(); + + if (sep->arg[1][0] == 0) { + c->Message(Chat::White, "Usage: #movement stats/clearstats/walkto/runto/rotateto/stop/packet"); + return; + } + + if (strcasecmp(sep->arg[1], "stats") == 0) { + mgr.DumpStats(c); + } + else if (strcasecmp(sep->arg[1], "clearstats") == 0) { + mgr.ClearStats(); + } + else if (strcasecmp(sep->arg[1], "walkto") == 0) { + auto target = c->GetTarget(); + if (target == nullptr) { + c->Message(Chat::White, "No target found."); + return; + } + + target->WalkTo(c->GetX(), c->GetY(), c->GetZ()); + } + else if (strcasecmp(sep->arg[1], "runto") == 0) { + auto target = c->GetTarget(); + if (target == nullptr) { + c->Message(Chat::White, "No target found."); + return; + } + + target->RunTo(c->GetX(), c->GetY(), c->GetZ()); + } + else if (strcasecmp(sep->arg[1], "rotateto") == 0) { + auto target = c->GetTarget(); + if (target == nullptr) { + c->Message(Chat::White, "No target found."); + return; + } + + target->RotateToWalking(target->CalculateHeadingToTarget(c->GetX(), c->GetY())); + } + else if (strcasecmp(sep->arg[1], "stop") == 0) { + auto target = c->GetTarget(); + if (target == nullptr) { + c->Message(Chat::White, "No target found."); + return; + } + + target->StopNavigation(); + } + else if (strcasecmp(sep->arg[1], "packet") == 0) { + auto target = c->GetTarget(); + if (target == nullptr) { + c->Message(Chat::White, "No target found."); + return; + } + + mgr.SendCommandToClients( + target, + atof(sep->arg[2]), + atof(sep->arg[3]), + atof(sep->arg[4]), + atof(sep->arg[5]), + atoi(sep->arg[6]), + ClientRangeAny + ); + } + else { + c->Message(Chat::White, "Usage: #movement stats/clearstats/walkto/runto/rotateto/stop/packet"); + } +} + diff --git a/zone/gm_commands/myskills.cpp b/zone/gm_commands/myskills.cpp new file mode 100755 index 000000000..d781f50ca --- /dev/null +++ b/zone/gm_commands/myskills.cpp @@ -0,0 +1,7 @@ +#include "../client.h" + +void command_myskills(Client *c, const Seperator *sep) +{ + c->ShowSkillsWindow(); +} + diff --git a/zone/gm_commands/mysql.cpp b/zone/gm_commands/mysql.cpp new file mode 100755 index 000000000..3c2bb8724 --- /dev/null +++ b/zone/gm_commands/mysql.cpp @@ -0,0 +1,85 @@ +#include "../client.h" + +void command_mysql(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments) { + c->Message(Chat::White, "Usage: #mysql [Help|Query] [SQL Query]"); + return; + } + + bool is_help = !strcasecmp(sep->arg[1], "help"); + bool is_query = !strcasecmp(sep->arg[1], "query"); + if ( + !is_help && + !is_query + ) { + c->Message(Chat::White, "Usage: #mysql [Help|Query] [SQL Query]"); + return; + } + + if (is_help) { + c->Message(Chat::White, "Usage: #mysql query \"Query goes here quoted\""); + c->Message(Chat::White, "Note: To use 'LIKE \"%%something%%\" replace the %% with a #"); + c->Message(Chat::White, "Example: #mysql query \"SELECT * FROM items WHERE `name` LIKE \"#Apple#\""); + return; + } else if (is_query) { + if (arguments < 2) { + c->Message(Chat::White, "Usage: #mysql query \"Query goes here quoted\""); + c->Message(Chat::White, "Note: To use 'LIKE \"%%something%%\" replace the %% with a #"); + c->Message(Chat::White, "Example: #mysql query \"SELECT * FROM items WHERE `name` LIKE \"#Apple#\""); + return; + } + + std::string query = sep->arg[2]; + find_replace(query, "#", "%"); + auto results = database.QueryDatabase(query); + if (!results.Success()) { + return; + } + + query = sep->arg[2]; + find_replace(query, "#", "%%"); + + c->Message( + Chat::White, + fmt::format( + "Running Query: '{}'", + query + ).c_str() + ); + + std::vector lines; + for (auto row : results) { + for ( + int row_index = 0; + row_index < results.ColumnCount(); + row_index++ + ) { + lines.push_back( + fmt::format( + "{} | {} ", + results.FieldName(row_index), + ( + row[row_index] ? + ( + strlen(row[row_index]) ? + row[row_index] : + "Empty String" + ) : + "NULL" + ) + ) + ); + } + } + + for (auto line : lines) { + c->Message( + Chat::White, + line.c_str() + ); + } + } +} + diff --git a/zone/gm_commands/mystats.cpp b/zone/gm_commands/mystats.cpp new file mode 100755 index 000000000..e5a781d8a --- /dev/null +++ b/zone/gm_commands/mystats.cpp @@ -0,0 +1,17 @@ +#include "../client.h" + +void command_mystats(Client *c, const Seperator *sep) +{ + if (c->GetTarget() && c->GetPet()) { + if (c->GetTarget()->IsPet() && c->GetTarget() == c->GetPet()) { + c->GetTarget()->ShowStats(c); + } + else { + c->ShowStats(c); + } + } + else { + c->ShowStats(c); + } +} + diff --git a/zone/gm_commands/name.cpp b/zone/gm_commands/name.cpp new file mode 100755 index 000000000..58dda8e75 --- /dev/null +++ b/zone/gm_commands/name.cpp @@ -0,0 +1,42 @@ +#include "../client.h" + +void command_name(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments) { + c->Message(Chat::White, "Usage: #name [New Name] - Rename your player target"); + return; + } + + if (c->GetTarget() && c->GetTarget()->IsClient()) { + auto target = c->GetTarget()->CastToClient(); + + std::string new_name = sep->arg[1]; + std::string old_name = target->GetCleanName(); + + if (target->ChangeFirstName(new_name.c_str(), c->GetCleanName())) { + c->Message( + Chat::White, + fmt::format( + "Successfully renamed {} to {}", + old_name, + new_name + ).c_str() + ); + + c->Message(Chat::White, "Sending player to char select."); + + target->Kick("Name was changed"); + } else { + c->Message( + Chat::White, + fmt::format( + "Unable to rename {}. Check that the new name '{}' isn't already taken.", + old_name, + new_name + ).c_str() + ); + } + } +} + diff --git a/zone/gm_commands/netstats.cpp b/zone/gm_commands/netstats.cpp new file mode 100755 index 000000000..87fd11a4c --- /dev/null +++ b/zone/gm_commands/netstats.cpp @@ -0,0 +1,169 @@ +#include "../client.h" + +void command_netstats(Client *c, const Seperator *sep) +{ + bool is_full = !strcasecmp(sep->arg[1], "full"); + bool is_reset = !strcasecmp(sep->arg[1], "reset"); + + if (is_reset) { + auto connection = c->Connection(); + c->Message(Chat::White, "Resetting client stats (packet loss will not read correctly after reset)."); + connection->ResetStats(); + return; + } + + auto connection = c->Connection(); + auto opts = connection->GetManager()->GetOptions(); + auto eqs_stats = connection->GetStats(); + auto &stats = eqs_stats.DaybreakStats; + auto now = EQ::Net::Clock::now(); + auto sec_since_stats_reset = std::chrono::duration_cast>( + now - stats.created + ).count(); + + std::string popup_text = ""; + + popup_text += fmt::format( + "", + stats.sent_bytes, + stats.sent_bytes / sec_since_stats_reset + ); + + popup_text += fmt::format( + "", + stats.recv_bytes, + stats.recv_bytes / sec_since_stats_reset + ); + + popup_text += "

"; + + popup_text += fmt::format( + "", + stats.bytes_before_encode, + static_cast(stats.bytes_before_encode - stats.sent_bytes) / + static_cast(stats.bytes_before_encode) * 100.0 + ); + + popup_text += fmt::format( + "", + stats.bytes_after_decode, + static_cast(stats.bytes_after_decode - stats.recv_bytes) / + static_cast(stats.bytes_after_decode) * 100.0 + ); + + popup_text += "

"; + + popup_text += fmt::format("
", stats.min_ping); + popup_text += fmt::format("", stats.max_ping); + popup_text += fmt::format("", stats.last_ping); + popup_text += fmt::format("", stats.avg_ping); + + popup_text += "

"; + + popup_text += fmt::format( + "", + stats.recv_packets, + stats.recv_packets / sec_since_stats_reset + ); + + popup_text += fmt::format( + "", + stats.sent_packets, + stats.sent_packets / sec_since_stats_reset + ); + + popup_text += "

"; + + popup_text += fmt::format("", stats.sync_recv_packets); + popup_text += fmt::format("", stats.sync_sent_packets); + popup_text += fmt::format("", stats.sync_remote_recv_packets); + popup_text += fmt::format("", stats.sync_remote_sent_packets); + + popup_text += "

"; + + popup_text += fmt::format( + "", + (100.0 * (1.0 - static_cast(stats.sync_recv_packets) / static_cast(stats.sync_remote_sent_packets))) + ); + + popup_text += fmt::format( + "", + (100.0 * (1.0 - static_cast(stats.sync_remote_recv_packets) / static_cast(stats.sync_sent_packets))) + ); + + popup_text += "

"; + + popup_text += fmt::format( + "
", + stats.resent_packets, + stats.resent_packets / sec_since_stats_reset + ); + + popup_text += fmt::format( + "", + stats.resent_fragments, + stats.resent_fragments / sec_since_stats_reset + ); + + popup_text += fmt::format( + "", + stats.resent_full, + stats.resent_full / sec_since_stats_reset + ); + + popup_text += "

"; + + popup_text += fmt::format( + "", + stats.dropped_datarate_packets, + stats.dropped_datarate_packets / sec_since_stats_reset + ); + + if (opts.daybreak_options.outgoing_data_rate > 0.0) { + popup_text += fmt::format( + "", + (100.0 * (1.0 - ((opts.daybreak_options.outgoing_data_rate - stats.datarate_remaining) / opts.daybreak_options.outgoing_data_rate))), + opts.daybreak_options.outgoing_data_rate + ); + } + + if (is_full) { + popup_text += "

"; + + popup_text += ""; + for (auto i = 0; i < _maxEmuOpcode; ++i) { + auto cnt = eqs_stats.SentCount[i]; + if (cnt > 0) { + popup_text += fmt::format( + "", + OpcodeNames[i], + cnt, + cnt / sec_since_stats_reset + ); + } + } + + popup_text += "

"; + + popup_text += ""; + for (auto i = 0; i < _maxEmuOpcode; ++i) { + auto cnt = eqs_stats.RecvCount[i]; + if (cnt > 0) { + popup_text += fmt::format( + "", + OpcodeNames[i], + cnt, + cnt / sec_since_stats_reset + ); + } + } + } + + popup_text += "
Sent Bytes{} ({:.2f} Per Second)
Received Bytes{} ({:.2f} Per Second)
Bytes Before Encode (Sent){}Compression Rate{:.2f}%%
Bytes After Decode (Received){}Compression Rate{:.2f}%%
Min Ping{}
Max Ping{}
Last Ping{}
Average Ping{}
(Realtime) Received Packets{} ({:.2f} Per Second)
(Realtime) Sent Packets{} ({:.2f} Per Second)
(Sync) Received Packets{}
(Sync) Sent Packets{}
(Sync) Remote Received Packets{}
(Sync) Remote Sent Packets{}
Packet Loss In{:.2f}%%
Packet Loss Out{:.2f}%%
Resent Packets{} ({:.2f} Per Second)
Resent Fragments{} ({:.2f} Per Second)
Resent Non-Fragments{} ({:.2f} Per Second)
Dropped Datarate Packets{} ({:.2f} Per Second)
Outgoing Link Saturation{:.2f}%% ({:.2f}kb Per Second)
Sent Packet Types
{}{} ({:.2f} Per Second)
Received Packet Types
{}{} ({:.2f} Per Second)
"; + + c->SendPopupToClient( + "Network Statistics", + popup_text.c_str() + ); +} + diff --git a/zone/gm_commands/network.cpp b/zone/gm_commands/network.cpp new file mode 100755 index 000000000..1c35d28ec --- /dev/null +++ b/zone/gm_commands/network.cpp @@ -0,0 +1,175 @@ +#include "../client.h" + +void command_network(Client *c, const Seperator *sep) +{ + if (!strcasecmp(sep->arg[1], "getopt")) { + auto eqsi = c->Connection(); + auto manager = eqsi->GetManager(); + auto opts = manager->GetOptions(); + + if (!strcasecmp(sep->arg[2], "all")) { + c->Message(Chat::White, "max_packet_size: %llu", (uint64_t) opts.daybreak_options.max_packet_size); + c->Message( + Chat::White, + "max_connection_count: %llu", + (uint64_t) opts.daybreak_options.max_connection_count + ); + c->Message(Chat::White, "keepalive_delay_ms: %llu", (uint64_t) opts.daybreak_options.keepalive_delay_ms); + c->Message(Chat::White, "resend_delay_factor: %.2f", opts.daybreak_options.resend_delay_factor); + c->Message(Chat::White, "resend_delay_ms: %llu", (uint64_t) opts.daybreak_options.resend_delay_ms); + c->Message(Chat::White, "resend_delay_min: %llu", (uint64_t) opts.daybreak_options.resend_delay_min); + c->Message(Chat::White, "resend_delay_max: %llu", (uint64_t) opts.daybreak_options.resend_delay_max); + c->Message(Chat::White, "connect_delay_ms: %llu", (uint64_t) opts.daybreak_options.connect_delay_ms); + c->Message(Chat::White, "connect_stale_ms: %llu", (uint64_t) opts.daybreak_options.connect_stale_ms); + c->Message(Chat::White, "stale_connection_ms: %llu", (uint64_t) opts.daybreak_options.stale_connection_ms); + c->Message(Chat::White, "crc_length: %llu", (uint64_t) opts.daybreak_options.crc_length); + c->Message(Chat::White, "hold_size: %llu", (uint64_t) opts.daybreak_options.hold_size); + c->Message(Chat::White, "hold_length_ms: %llu", (uint64_t) opts.daybreak_options.hold_length_ms); + c->Message( + Chat::White, + "simulated_in_packet_loss: %llu", + (uint64_t) opts.daybreak_options.simulated_in_packet_loss + ); + c->Message( + Chat::White, + "simulated_out_packet_loss: %llu", + (uint64_t) opts.daybreak_options.simulated_out_packet_loss + ); + c->Message(Chat::White, "tic_rate_hertz: %.2f", opts.daybreak_options.tic_rate_hertz); + c->Message(Chat::White, "resend_timeout: %llu", (uint64_t) opts.daybreak_options.resend_timeout); + c->Message( + Chat::White, + "connection_close_time: %llu", + (uint64_t) opts.daybreak_options.connection_close_time + ); + c->Message(Chat::White, "encode_passes[0]: %llu", (uint64_t) opts.daybreak_options.encode_passes[0]); + c->Message(Chat::White, "encode_passes[1]: %llu", (uint64_t) opts.daybreak_options.encode_passes[1]); + c->Message(Chat::White, "port: %llu", (uint64_t) opts.daybreak_options.port); + } + else { + c->Message(Chat::White, "Unknown get option: %s", sep->arg[2]); + c->Message(Chat::White, "Available options:"); + //Todo the rest of these when im less lazy. + //c->Message(Chat::White, "max_packet_size"); + //c->Message(Chat::White, "max_connection_count"); + //c->Message(Chat::White, "keepalive_delay_ms"); + //c->Message(Chat::White, "resend_delay_factor"); + //c->Message(Chat::White, "resend_delay_ms"); + //c->Message(Chat::White, "resend_delay_min"); + //c->Message(Chat::White, "resend_delay_max"); + //c->Message(Chat::White, "connect_delay_ms"); + //c->Message(Chat::White, "connect_stale_ms"); + //c->Message(Chat::White, "stale_connection_ms"); + //c->Message(Chat::White, "crc_length"); + //c->Message(Chat::White, "hold_size"); + //c->Message(Chat::White, "hold_length_ms"); + //c->Message(Chat::White, "simulated_in_packet_loss"); + //c->Message(Chat::White, "simulated_out_packet_loss"); + //c->Message(Chat::White, "tic_rate_hertz"); + //c->Message(Chat::White, "resend_timeout"); + //c->Message(Chat::White, "connection_close_time"); + //c->Message(Chat::White, "encode_passes[0]"); + //c->Message(Chat::White, "encode_passes[1]"); + //c->Message(Chat::White, "port"); + c->Message(Chat::White, "all"); + } + } + else if (!strcasecmp(sep->arg[1], "setopt")) { + auto eqsi = c->Connection(); + auto manager = eqsi->GetManager(); + auto opts = manager->GetOptions(); + + if (!strcasecmp(sep->arg[3], "")) { + c->Message(Chat::White, "Missing value for set"); + return; + } + + std::string value = sep->arg[3]; + if (!strcasecmp(sep->arg[2], "max_connection_count")) { + opts.daybreak_options.max_connection_count = std::stoull(value); + manager->SetOptions(opts); + } + else if (!strcasecmp(sep->arg[2], "keepalive_delay_ms")) { + opts.daybreak_options.keepalive_delay_ms = std::stoull(value); + manager->SetOptions(opts); + } + else if (!strcasecmp(sep->arg[2], "resend_delay_factor")) { + opts.daybreak_options.resend_delay_factor = std::stod(value); + manager->SetOptions(opts); + } + else if (!strcasecmp(sep->arg[2], "resend_delay_ms")) { + opts.daybreak_options.resend_delay_ms = std::stoull(value); + manager->SetOptions(opts); + } + else if (!strcasecmp(sep->arg[2], "resend_delay_min")) { + opts.daybreak_options.resend_delay_min = std::stoull(value); + manager->SetOptions(opts); + } + else if (!strcasecmp(sep->arg[2], "resend_delay_max")) { + opts.daybreak_options.resend_delay_max = std::stoull(value); + manager->SetOptions(opts); + } + else if (!strcasecmp(sep->arg[2], "connect_delay_ms")) { + opts.daybreak_options.connect_delay_ms = std::stoull(value); + manager->SetOptions(opts); + } + else if (!strcasecmp(sep->arg[2], "connect_stale_ms")) { + opts.daybreak_options.connect_stale_ms = std::stoull(value); + manager->SetOptions(opts); + } + else if (!strcasecmp(sep->arg[2], "stale_connection_ms")) { + opts.daybreak_options.stale_connection_ms = std::stoull(value); + manager->SetOptions(opts); + } + else if (!strcasecmp(sep->arg[2], "hold_size")) { + opts.daybreak_options.hold_size = std::stoull(value); + manager->SetOptions(opts); + } + else if (!strcasecmp(sep->arg[2], "hold_length_ms")) { + opts.daybreak_options.hold_length_ms = std::stoull(value); + manager->SetOptions(opts); + } + else if (!strcasecmp(sep->arg[2], "simulated_in_packet_loss")) { + opts.daybreak_options.simulated_in_packet_loss = std::stoull(value); + manager->SetOptions(opts); + } + else if (!strcasecmp(sep->arg[2], "simulated_out_packet_loss")) { + opts.daybreak_options.simulated_out_packet_loss = std::stoull(value); + manager->SetOptions(opts); + } + else if (!strcasecmp(sep->arg[2], "resend_timeout")) { + opts.daybreak_options.resend_timeout = std::stoull(value); + manager->SetOptions(opts); + } + else if (!strcasecmp(sep->arg[2], "connection_close_time")) { + opts.daybreak_options.connection_close_time = std::stoull(value); + manager->SetOptions(opts); + } + else { + c->Message(Chat::White, "Unknown set option: %s", sep->arg[2]); + c->Message(Chat::White, "Available options:"); + c->Message(Chat::White, "max_connection_count"); + c->Message(Chat::White, "keepalive_delay_ms"); + c->Message(Chat::White, "resend_delay_factor"); + c->Message(Chat::White, "resend_delay_ms"); + c->Message(Chat::White, "resend_delay_min"); + c->Message(Chat::White, "resend_delay_max"); + c->Message(Chat::White, "connect_delay_ms"); + c->Message(Chat::White, "connect_stale_ms"); + c->Message(Chat::White, "stale_connection_ms"); + c->Message(Chat::White, "hold_size"); + c->Message(Chat::White, "hold_length_ms"); + c->Message(Chat::White, "simulated_in_packet_loss"); + c->Message(Chat::White, "simulated_out_packet_loss"); + c->Message(Chat::White, "resend_timeout"); + c->Message(Chat::White, "connection_close_time"); + } + } + else { + c->Message(Chat::White, "Unknown command: %s", sep->arg[1]); + c->Message(Chat::White, "Network commands avail:"); + c->Message(Chat::White, "getopt optname - Retrieve the current option value set."); + c->Message(Chat::White, "setopt optname - Set the current option allowed."); + } +} + diff --git a/zone/gm_commands/npccast.cpp b/zone/gm_commands/npccast.cpp new file mode 100755 index 000000000..4abef226f --- /dev/null +++ b/zone/gm_commands/npccast.cpp @@ -0,0 +1,94 @@ +#include "../client.h" + +void command_npccast(Client *c, const Seperator *sep) +{ + if (c->GetTarget() && c->GetTarget()->IsNPC()) { + NPC *target = c->GetTarget()->CastToNPC(); + if (!sep->IsNumber(1) && sep->arg[1] && sep->IsNumber(2)) { + const char *entity_name = sep->arg[1] ? sep->arg[1] : 0; + auto spell_id = sep->arg[2] ? std::stoul(sep->arg[2]) : 0; + Mob *spell_target = entity_list.GetMob(entity_name); + if (spell_target && IsValidSpell(spell_id) && spell_id < SPDAT_RECORDS) { + c->Message( + Chat::White, + fmt::format( + "{} ({}) casting {} ({}) on {} ({}).", + target->GetCleanName(), + target->GetID(), + GetSpellName(static_cast(spell_id)), + spell_id, + spell_target->GetCleanName(), + spell_target->GetID() + ).c_str() + ); + + target->CastSpell(spell_id, spell_target->GetID()); + } + else { + if (!spell_target) { + c->Message( + Chat::White, + fmt::format( + "Entity {} was not found", + entity_name + ).c_str() + ); + } + else if (!spell_id || !IsValidSpell(spell_id)) { + c->Message( + Chat::White, + fmt::format( + "Spell ID {} was not found", + spell_id + ).c_str() + ); + } + } + } + else if (sep->IsNumber(1) && sep->IsNumber(2)) { + uint16 entity_id = sep->arg[1] ? std::stoul(sep->arg[1]) : 0; + auto spell_id = sep->arg[2] ? std::stoul(sep->arg[2]) : 0; + Mob *spell_target = entity_list.GetMob(entity_id); + if (spell_target && IsValidSpell(spell_id) && spell_id < SPDAT_RECORDS) { + c->Message( + Chat::White, + fmt::format( + "{} ({}) casting {} ({}) on {} ({}).", + target->GetCleanName(), + target->GetID(), + GetSpellName(static_cast(spell_id)), + spell_id, + spell_target->GetCleanName(), + spell_target->GetID() + ).c_str() + ); + + target->CastSpell(spell_id, spell_target->GetID()); + } + else { + if (!spell_target) { + c->Message( + Chat::White, + fmt::format( + "Entity ID {} was not found", + entity_id + ).c_str() + ); + } + else if (!spell_id || !IsValidSpell(spell_id)) { + c->Message( + Chat::White, + fmt::format( + "Spell ID {} was not found", + spell_id + ).c_str() + ); + } + } + } + } + else { + c->Message(Chat::White, "You must target an NPC to use this command."); + } +} + diff --git a/zone/gm_commands/npcedit.cpp b/zone/gm_commands/npcedit.cpp new file mode 100755 index 000000000..66b483384 --- /dev/null +++ b/zone/gm_commands/npcedit.cpp @@ -0,0 +1,1406 @@ +#include "../client.h" +#include "../groups.h" +#include "../mob_movement_manager.h" +#include "../raids.h" +#include "../raids.h" + +void command_npcedit(Client *c, const Seperator *sep) +{ + if (!c->GetTarget() || !c->GetTarget()->IsNPC()) { + c->Message(Chat::White, "Error: Must have NPC targeted"); + return; + } + + if (strcasecmp(sep->arg[1], "help") == 0) { + + c->Message(Chat::White, "Help File for #npcedit. Syntax for commands are:"); + c->Message(Chat::White, "#npcedit name - Sets an NPC's Name"); + c->Message(Chat::White, "#npcedit lastname - Sets an NPC's Lastname"); + c->Message(Chat::White, "#npcedit level - Sets an NPC's Level"); + c->Message(Chat::White, "#npcedit race - Sets an NPC's Race"); + c->Message(Chat::White, "#npcedit class - Sets an NPC's Class"); + c->Message(Chat::White, "#npcedit bodytype - Sets an NPC's Bodytype"); + c->Message(Chat::White, "#npcedit hp - Sets an NPC's Hitpoints"); + c->Message(Chat::White, "#npcedit mana - Sets an NPC's Mana"); + c->Message(Chat::White, "#npcedit gender - Sets an NPC's Gender"); + c->Message(Chat::White, "#npcedit texture - Sets an NPC's Texture"); + c->Message(Chat::White, "#npcedit helmtexture - Sets an NPC's Helmet Texture"); + c->Message(Chat::White, "#npcedit herosforgemodel - Sets an NPC's Hero's Forge Model"); + c->Message(Chat::White, "#npcedit size - Sets an NPC's Size"); + c->Message(Chat::White, "#npcedit hpregen - Sets an NPC's Hitpoints Regeneration Rate Per Tick"); + c->Message(Chat::White, "#npcedit manaregen - Sets an NPC's Mana Regeneration Rate Per Tick"); + c->Message(Chat::White, "#npcedit loottable - Sets an NPC's Loottable ID"); + c->Message(Chat::White, "#npcedit merchantid - Sets an NPC's Merchant ID"); + c->Message(Chat::White, "#npcedit alt_currency_id - Sets an NPC's Alternate Currency ID"); + c->Message(Chat::White, "#npcedit spell - Sets an NPC's Spells List ID"); + c->Message(Chat::White, "#npcedit npc_spells_effects_id - Sets an NPC's Spell Effects ID"); + c->Message(Chat::White, "#npcedit faction - Sets an NPC's Faction ID"); + c->Message(Chat::White, "#npcedit adventure_template_id - Sets an NPC's Adventure Template ID"); + c->Message(Chat::White, "#npcedit trap_template - Sets an NPC's Trap Template ID"); + c->Message(Chat::White, "#npcedit damage [minimum] [maximum] - Sets an NPC's Damage"); + c->Message(Chat::White, "#npcedit attackcount - Sets an NPC's Attack Count"); + c->Message(Chat::White, "#npcedit special_attacks - Sets an NPC's Special Attacks"); + c->Message(Chat::White, "#npcedit special_abilities - Sets an NPC's Special Abilities"); + c->Message(Chat::White, "#npcedit aggroradius - Sets an NPC's Aggro Radius"); + c->Message(Chat::White, "#npcedit assistradius - Sets an NPC's Assist Radius"); + c->Message(Chat::White, "#npcedit featuresave - Saves an NPC's current facial features to the database"); + c->Message(Chat::White, "#npcedit armortint_id - Sets an NPC's Armor Tint ID"); + c->Message(Chat::White, "#npcedit color [red] [green] [blue] - Sets an NPC's Red, Green, and Blue armor tint"); + c->Message(Chat::White, "#npcedit ammoidfile - Sets an NPC's Ammo ID File"); + c->Message( + Chat::White, + "#npcedit weapon [primary_model] [secondary_model] - Sets an NPC's Primary and Secondary Weapon Model" + ); + c->Message(Chat::White, "#npcedit meleetype [primary_type] [secondary_type] - Sets an NPC's Melee Types"); + c->Message(Chat::White, "#npcedit rangedtype - Sets an NPC's Ranged Type"); + c->Message(Chat::White, "#npcedit runspeed - Sets an NPC's Run Speed"); + c->Message(Chat::White, "#npcedit mr - Sets an NPC's Magic Resistance"); + c->Message(Chat::White, "#npcedit pr - Sets an NPC's Poison Resistance"); + c->Message(Chat::White, "#npcedit dr - Sets an NPC's Disease Resistance"); + c->Message(Chat::White, "#npcedit fr - Sets an NPC's Fire Resistance"); + c->Message(Chat::White, "#npcedit cr - Sets an NPC's Cold Resistance"); + c->Message(Chat::White, "#npcedit corrup - Sets an NPC's Corruption Resistance"); + c->Message(Chat::White, "#npcedit phr - Sets and NPC's Physical Resistance"); + c->Message( + Chat::White, + "#npcedit seeinvis - Sets an NPC's See Invisible Flag [0 = Cannot See Invisible, 1 = Can See Invisible]" + ); + c->Message( + Chat::White, + "#npcedit seeinvisundead - Sets an NPC's See Invisible vs. Undead Flag [0 = Cannot See Invisible vs. Undead, 1 = Can See Invisible vs. Undead]" + ); + c->Message( + Chat::White, + "#npcedit qglobal - Sets an NPC's Quest Global Flag [0 = Quest Globals Off, 1 = Quest Globals On]" + ); + c->Message(Chat::White, "#npcedit ac - Sets an NPC's Armor Class"); + c->Message( + Chat::White, + "#npcedit npcaggro - Sets an NPC's NPC Aggro Flag [0 = Aggro NPCs Off, 1 = Aggro NPCs On]" + ); + c->Message(Chat::White, "#npcedit spawn_limit - Sets an NPC's Spawn Limit Counter"); + c->Message(Chat::White, "#npcedit attackspeed - Sets an NPC's Attack Speed Modifier"); + c->Message(Chat::White, "#npcedit attackdelay - Sets an NPC's Attack Delay"); + c->Message(Chat::White, "#npcedit findable - Sets an NPC's Findable Flag [0 = Not Findable, 1 = Findable]"); + c->Message(Chat::White, "#npcedit str - Sets an NPC's Strength"); + c->Message(Chat::White, "#npcedit sta - Sets an NPC's Stamina"); + c->Message(Chat::White, "#npcedit dex - Sets an NPC's Dexterity"); + c->Message(Chat::White, "#npcedit agi - Sets an NPC's Agility"); + c->Message(Chat::White, "#npcedit int - Sets an NPC's Intelligence"); + c->Message(Chat::White, "#npcedit wis - Sets an NPC's Wisdom"); + c->Message(Chat::White, "#npcedit cha - Sets an NPC's Charisma"); + c->Message( + Chat::White, + "#npcedit seehide - Sets an NPC's See Hide Flag [0 = Cannot See Hide, 1 = Can See Hide]" + ); + c->Message( + Chat::White, + "#npcedit seeimprovedhide - Sets an NPC's See Improved Hide Flag [0 = Cannot See Improved Hide, 1 = Can See Improved Hide]" + ); + c->Message(Chat::White, "#npcedit trackable - Sets an NPC's Trackable Flag [0 = Not Trackable, 1 = Trackable]"); + c->Message(Chat::White, "#npcedit atk - Sets an NPC's Attack"); + c->Message(Chat::White, "#npcedit accuracy - Sets an NPC's Accuracy"); + c->Message(Chat::White, "#npcedit avoidance - Sets an NPC's Avoidance"); + c->Message(Chat::White, "#npcedit slow_mitigation - Sets an NPC's Slow Mitigation"); + c->Message(Chat::White, "#npcedit version - Sets an NPC's Version"); + c->Message(Chat::White, "#npcedit maxlevel - Sets an NPC's Maximum Level"); + c->Message(Chat::White, "#npcedit scalerate - Sets an NPC's Scaling Rate [50 = 50%, 100 = 100%, 200 = 200%]"); + c->Message( + Chat::White, + "#npcedit spellscale - Sets an NPC's Spell Scaling Rate [50 = 50%, 100 = 100%, 200 = 200%]" + ); + c->Message( + Chat::White, + "#npcedit healscale - Sets an NPC's Heal Scaling Rate [50 = 50%, 100 = 100%, 200 = 200%]" + ); + c->Message( + Chat::White, + "#npcedit no_target - Sets an NPC's No Target Hotkey Flag [0 = Not Targetable with Target Hotkey, 1 = Targetable with Target Hotkey]" + ); + c->Message( + Chat::White, + "#npcedit raidtarget - Sets an NPC's Raid Target Flag [0 = Not a Raid Target, 1 = Raid Target]" + ); + c->Message(Chat::White, "#npcedit armtexture - Sets an NPC's Arm Texture"); + c->Message(Chat::White, "#npcedit bracertexture - Sets an NPC's Bracer Texture"); + c->Message(Chat::White, "#npcedit handtexture - Sets an NPC's Hand Texture"); + c->Message(Chat::White, "#npcedit legtexture - Sets an NPC's Leg Texture"); + c->Message(Chat::White, "#npcedit feettexture - Sets an NPC's Feet Texture"); + c->Message(Chat::White, "#npcedit walkspeed - Sets an NPC's Walk Speed"); + c->Message(Chat::White, "#npcedit show_name - Sets an NPC's Show Name Flag [0 = Hidden, 1 = Shown]"); + c->Message( + Chat::White, + "#npcedit untargetable - Sets an NPC's Untargetable Flag [0 = Targetable, 1 = Untargetable]" + ); + c->Message(Chat::White, "#npcedit charm_ac - Sets an NPC's Armor Class while Charmed"); + c->Message(Chat::White, "#npcedit charm_min_dmg - Sets an NPC's Minimum Damage while Charmed"); + c->Message(Chat::White, "#npcedit charm_max_dmg - Sets an NPC's Max Damage while Charmed"); + c->Message(Chat::White, "#npcedit charm_attack_delay - Sets an NPC's Attack Delay while Charmed"); + c->Message(Chat::White, "#npcedit charm_accuracy_rating - Sets an NPC's Accuracy Rating while Charmed"); + c->Message(Chat::White, "#npcedit charm_avoidance_rating - Sets an NPC's Avoidance Rating while Charmed"); + c->Message(Chat::White, "#npcedit charm_atk - Sets an NPC's Attack while Charmed"); + c->Message( + Chat::White, + "#npcedit skip_global_loot - Sets an NPC's Skip Global Loot Flag [0 = Don't Skip, 1 = Skip" + ); + c->Message( + Chat::White, + "#npcedit rarespawn - Sets an NPC's Rare Spawn Flag [0 = Not a Rare Spawn, 1 = Rare Spawn]" + ); + c->Message( + Chat::White, + "#npcedit stuck_behavior - Sets an NPC's Stuck Behavior [0 = Run to Target, 1 = Warp to Target, 2 = Take No Action, 3 = Evade Combat]" + ); + c->Message( + Chat::White, + "#npcedit flymode - Sets an NPC's Fly Mode [0 = Ground, 1 = Flying, 2 = Levitating, 3 = Water, 4 = Floating, 5 = Levitating While Running]" + ); + c->Message( + Chat::White, + "#npcedit always_aggro - Sets an NPC's Always Aggro Flag [0 = Does not Always Aggro, 1 = Always Aggro]" + ); + c->Message( + Chat::White, + "#npcedit exp_mod - Sets an NPC's Experience Modifier [50 = 50%, 100 = 100%, 200 = 200%]" + ); + c->Message(Chat::White, "#npcedit setanimation - Sets an NPC's Animation on Spawn (Stored in spawn2 table)"); + c->Message( + Chat::White, + "#npcedit respawntime - Sets an NPC's Respawn Timer in Seconds (Stored in spawn2 table)" + ); + } + + uint32 npc_id = c->GetTarget()->CastToNPC()->GetNPCTypeID(); + if (strcasecmp(sep->arg[1], "name") == 0) { + c->Message(Chat::Yellow, fmt::format("NPC ID {} now has the name '{}'.", npc_id, sep->arg[2]).c_str()); + std::string query = fmt::format("UPDATE npc_types SET name = '{}' WHERE id = {}", sep->arg[2], npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "lastname") == 0) { + c->Message(Chat::Yellow, fmt::format("NPC ID {} now has the lastname '{}'.", npc_id, sep->arg[2]).c_str()); + std::string query = fmt::format("UPDATE npc_types SET lastname = '{}' WHERE id = {}", sep->arg[2], npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "level") == 0) { + c->Message(Chat::Yellow, fmt::format("NPC ID {} is now level {}.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format("UPDATE npc_types SET level = {} WHERE id = {}", atoi(sep->arg[2]), npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "race") == 0) { + auto race_id = atoi(sep->arg[2]); + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} is now a {} ({}).", npc_id, GetRaceIDName(race_id), race_id).c_str()); + std::string query = fmt::format("UPDATE npc_types SET race = {} WHERE id = {}", race_id, npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "class") == 0) { + auto class_id = atoi(sep->arg[2]); + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} is now a {} ({}).", npc_id, GetClassIDName(class_id), class_id).c_str()); + std::string query = fmt::format("UPDATE npc_types SET class = {} WHERE id = {}", class_id, npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "bodytype") == 0) { + c->Message( + Chat::Yellow, + fmt::format( + "NPC ID {} is now using Bodytype {} ({}).", + npc_id, + EQ::constants::GetBodyTypeName(static_cast(std::stoul(sep->arg[2]))), + std::stoul(sep->arg[2]) + ).c_str() + ); + std::string query = fmt::format( + "UPDATE npc_types SET bodytype = {} WHERE id = {}", + std::stoul(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "hp") == 0) { + c->Message(Chat::Yellow, fmt::format("NPC ID {} now has {} Health.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format("UPDATE npc_types SET hp = {} WHERE id = {}", atoi(sep->arg[2]), npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "mana") == 0) { + c->Message(Chat::Yellow, fmt::format("NPC ID {} now has {} Mana.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format("UPDATE npc_types SET mana = {} WHERE id = {}", atoi(sep->arg[2]), npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "gender") == 0) { + auto gender_id = atoi(sep->arg[2]); + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} is now a {} ({}).", npc_id, gender_id, GetGenderName(gender_id)).c_str()); + std::string query = fmt::format("UPDATE npc_types SET gender = {} WHERE id = {}", gender_id, npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "texture") == 0) { + c->Message(Chat::Yellow, fmt::format("NPC ID {} is now using Texture {}.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format("UPDATE npc_types SET texture = {} WHERE id = {}", atoi(sep->arg[2]), npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "helmtexture") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} is now using Helmet Texture {}.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET helmtexture = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "herosforgemodel") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} is now using Hero's Forge Model {}.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET herosforgemodel = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "size") == 0) { + c->Message(Chat::Yellow, fmt::format("NPC ID {} is now Size {:.2f}.", npc_id, atof(sep->arg[2])).c_str()); + std::string query = fmt::format("UPDATE npc_types SET size = {:.2f} WHERE id = {}", atof(sep->arg[2]), npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "hpregen") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} now regenerates {} Health per Tick.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET hp_regen_rate = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "manaregen") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} now regenerates {} Mana per Tick.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET mana_regen_rate = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "loottable") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} is now using loottable ID {}.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET loottable_id = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "merchantid") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} is now using merchant ID {}.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET merchant_id = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "alt_currency_id") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} is now using Alternate Currency ID {}.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET alt_currency_id = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "spell") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} is now using Spell List ID {}.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET npc_spells_id = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "npc_spells_effects_id") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} is now using NPC Spells Effects ID {}.", npc_id, sep->arg[2]).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET npc_spells_effects_id = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "faction") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} is now using Faction ID {}.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET npc_faction_id = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "adventure_template_id") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} is now using Adventure Template ID {}.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET adventure_template_id = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "trap_template") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} is now using Trap Template ID {}.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET trap_template = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "damage") == 0) { + c->Message( + Chat::Yellow, + fmt::format( + "NPC ID {} now hits from {} to {} damage.", + npc_id, + atoi(sep->arg[2]), + atoi(sep->arg[3])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET mindmg = {}, maxdmg = {} WHERE id = {}", + atoi(sep->arg[2]), + atoi(sep->arg[3]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "attackcount") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} now has an Attack Count of {}.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET attack_count = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "special_attacks") == 0) { + c->Message( + Chat::Yellow, + fmt::format( + "NPC ID {} is now using the following Special Attacks '{}'.", + npc_id, + sep->arg[2] + ).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET npcspecialattks = '{}' WHERE id = {}", + sep->arg[2], + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "special_abilities") == 0) { + c->Message( + Chat::Yellow, + fmt::format( + "NPC ID {} is now using the following Special Abilities '{}'.", + npc_id, + sep->arg[2] + ).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET special_abilities = '{}' WHERE id = {}", + sep->arg[2], + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "aggroradius") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} now has an Aggro Radius of {}.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET aggroradius = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "assistradius") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} now has an Assist Radius of {}", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET assistradius = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "featuresave") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} saved with all current facial feature settings.", npc_id).c_str()); + Mob *target = c->GetTarget(); + std::string query = fmt::format( + "UPDATE npc_types " + "SET luclin_haircolor = {}, luclin_beardcolor = {}, " + "luclin_hairstyle = {}, luclin_beard = {}, " + "face = {}, drakkin_heritage = {}, " + "drakkin_tattoo = {}, drakkin_details = {} " + "WHERE id = {}", + target->GetHairColor(), target->GetBeardColor(), + target->GetHairStyle(), target->GetBeard(), + target->GetLuclinFace(), target->GetDrakkinHeritage(), + target->GetDrakkinTattoo(), target->GetDrakkinDetails(), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "armortint_id") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} is now using Armor Tint ID {}.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET armortint_id = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "color") == 0) { + c->Message( + Chat::Yellow, + fmt::format( + "NPC ID {} now has {} Red, {} Green, and {} Blue tinting on their armor.", + npc_id, + atoi(sep->arg[2]), + atoi(sep->arg[3]), + atoi(sep->arg[4])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET armortint_red = {}, armortint_green = {}, armortint_blue = {} WHERE id = {}", + atoi(sep->arg[2]), + atoi(sep->arg[3]), + atoi(sep->arg[4]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "ammoidfile") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} is now using Ammo ID File {}.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET ammo_idfile = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "weapon") == 0) { + c->Message( + Chat::Yellow, + fmt::format( + "NPC ID {} will have Model {} set to their Primary and Model {} set to their Secondary on repop.", + npc_id, + atoi(sep->arg[2]), + atoi(sep->arg[3])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET d_melee_texture1 = {}, d_melee_texture2 = {} WHERE id = {}", + atoi(sep->arg[2]), + atoi(sep->arg[3]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "meleetype") == 0) { + c->Message( + Chat::Yellow, + fmt::format( + "NPC ID {} now has a Primary Melee Type of {} and a Secondary Melee Type of {}.", + npc_id, + atoi(sep->arg[2]), + atoi(sep->arg[3])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET prim_melee_type = {}, sec_melee_type = {} WHERE id = {}", + atoi(sep->arg[2]), + atoi(sep->arg[3]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "rangedtype") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} now has a Ranged Type of {}.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET ranged_type = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "runspeed") == 0) { + c->Message(Chat::Yellow, fmt::format("NPC ID {} now runs at {:.2f}.", npc_id, atof(sep->arg[2])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET runspeed = {:.2f} WHERE id = {}", + atof(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "mr") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} now has a Magic Resistance of {}.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format("UPDATE npc_types SET MR = {} WHERE id = {}", atoi(sep->arg[2]), npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "pr") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} now has a Poison Resistance of {}.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format("UPDATE npc_types SET PR = {} WHERE id = {}", atoi(sep->arg[2]), npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "dr") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} now has a Disease Resistance of {}.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format("UPDATE npc_types SET DR = {} WHERE id = {}", atoi(sep->arg[2]), npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "fr") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} now has a Fire Resistance of {}.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format("UPDATE npc_types SET FR = {} WHERE id = {}", atoi(sep->arg[2]), npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "cr") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} now has a Cold Resistance of {}.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format("UPDATE npc_types SET CR = {} WHERE id = {}", atoi(sep->arg[2]), npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "corrup") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} now has a Corruption Resistance of {}.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format("UPDATE npc_types SET corrup = {} WHERE id = {}", atoi(sep->arg[2]), npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "phr") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} now has a Physical Resistance of {}.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format("UPDATE npc_types SET PhR = {} WHERE id = {}", atoi(sep->arg[2]), npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "seeinvis") == 0) { + c->Message( + Chat::Yellow, + fmt::format( + "NPC ID {} can {} See Invisible.", + npc_id, + (atoi(sep->arg[2]) == 1 ? "now" : "no longer")).c_str()); + std::string query = fmt::format("UPDATE npc_types SET see_invis = {} WHERE id = {}", atoi(sep->arg[2]), npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "seeinvisundead") == 0) { + c->Message( + Chat::Yellow, + fmt::format( + "NPC ID {} can {} See Invisible vs. Undead.", + npc_id, + (atoi(sep->arg[2]) == 1 ? "now" : "no longer")).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET see_invis_undead = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "qglobal") == 0) { + c->Message( + Chat::Yellow, + fmt::format( + "NPC ID {} can {} use Quest Globals.", + npc_id, + (atoi(sep->arg[2]) == 1 ? "now" : "no longer")).c_str()); + std::string query = fmt::format("UPDATE npc_types SET qglobal = {} WHERE id = {}", atoi(sep->arg[2]), npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "ac") == 0) { + c->Message(Chat::Yellow, fmt::format("NPC ID {} now has {} Armor Class.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format("UPDATE npc_types SET ac = {} WHERE id = {}", atoi(sep->arg[2]), npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "npcaggro") == 0) { + c->Message( + Chat::Yellow, + fmt::format( + "NPC ID {} will {} aggro other NPCs that have a hostile faction.", + npc_id, + (atoi(sep->arg[2]) == 1 ? "now" : "no longer")).c_str()); + std::string query = fmt::format("UPDATE npc_types SET npc_aggro = {} WHERE id = {}", atoi(sep->arg[2]), npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "spawn_limit") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} now has a Spawn Limit of {}.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET spawn_limit = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "attackspeed") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} now has an Attack Speed of {:.2f}.", npc_id, atof(sep->arg[2])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET attack_speed = {:.2f} WHERE id = {}", + atof(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "attackdelay") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} now has an Attack Delay of {}.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET attack_delay = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "findable") == 0) { + c->Message( + Chat::Yellow, + fmt::format( + "NPC ID {} is {} Findable.", + npc_id, + (atoi(sep->arg[2]) == 1 ? "now" : "no longer")).c_str()); + std::string query = fmt::format("UPDATE npc_types SET findable = {} WHERE id = {}", atoi(sep->arg[2]), npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "str") == 0) { + c->Message(Chat::Yellow, fmt::format("NPC ID {} now has {} Strength.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format("UPDATE npc_types SET STR = {} WHERE id = {}", atoi(sep->arg[2]), npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "sta") == 0) { + c->Message(Chat::Yellow, fmt::format("NPC ID {} now has {} Stamina.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format("UPDATE npc_types SET STA = {} WHERE id = {}", atoi(sep->arg[2]), npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "agi") == 0) { + c->Message(Chat::Yellow, fmt::format("NPC ID {} now has {} Agility.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format("UPDATE npc_types SET AGI = {} WHERE id = {}", atoi(sep->arg[2]), npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "dex") == 0) { + c->Message(Chat::Yellow, fmt::format("NPC ID {} now has {} Dexterity.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format("UPDATE npc_types SET DEX = {} WHERE id = {}", atoi(sep->arg[2]), npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "int") == 0) { + c->Message(Chat::Yellow, fmt::format("NPC ID {} now has {} Intelligence.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format("UPDATE npc_types SET _INT = {} WHERE id = {}", atoi(sep->arg[2]), npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "wis") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} now has a Magic Resistance of {}.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format("UPDATE npc_types SET WIS = {} WHERE id = {}", atoi(sep->arg[2]), npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "cha") == 0) { + c->Message(Chat::Yellow, fmt::format("NPC ID {} now has {} Charisma.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format("UPDATE npc_types SET CHA = {} WHERE id = {}", atoi(sep->arg[2]), npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "seehide") == 0) { + c->Message( + Chat::Yellow, + fmt::format( + "NPC ID {} can {} See Hide.", + npc_id, + (atoi(sep->arg[2]) == 1 ? "now" : "no longer")).c_str()); + std::string query = fmt::format("UPDATE npc_types SET see_hide = {} WHERE id = {}", atoi(sep->arg[2]), npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "seeimprovedhide") == 0) { + c->Message( + Chat::Yellow, + fmt::format( + "NPC ID {} can {} See Improved Hide.", + npc_id, + (atoi(sep->arg[2]) == 1 ? "now" : "no longer")).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET see_improved_hide = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "trackable") == 0) { + c->Message( + Chat::Yellow, + fmt::format( + "NPC ID {} is {} Trackable.", + npc_id, + (atoi(sep->arg[2]) == 1 ? "now" : "no longer")).c_str()); + std::string query = fmt::format("UPDATE npc_types SET trackable = {} WHERE id = {}", atoi(sep->arg[2]), npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "atk") == 0) { + c->Message(Chat::Yellow, fmt::format("NPC ID {} now has {} Attack.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format("UPDATE npc_types SET atk = {} WHERE id = {}", atoi(sep->arg[2]), npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "accuracy") == 0) { + c->Message(Chat::Yellow, fmt::format("NPC ID {} now has {} Accuracy.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format("UPDATE npc_types SET accuracy = {} WHERE id = {}", atoi(sep->arg[2]), npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "avoidance") == 0) { + c->Message(Chat::Yellow, fmt::format("NPC ID {} now has {} Avoidance.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format("UPDATE npc_types SET avoidance = {} WHERE id = {}", atoi(sep->arg[2]), npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "slow_mitigation") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} now has {} Slow Mitigation.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET slow_mitigation = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "version") == 0) { + c->Message(Chat::Yellow, fmt::format("NPC ID {} is now using Version {}.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format("UPDATE npc_types SET version = {} WHERE id = {}", atoi(sep->arg[2]), npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "maxlevel") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} now has a Maximum Level of {}.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format("UPDATE npc_types SET maxlevel = {} WHERE id = {}", atoi(sep->arg[2]), npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "scalerate") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} now has a Scaling Rate of {}%%.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format("UPDATE npc_types SET scalerate = {} WHERE id = {}", atoi(sep->arg[2]), npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "spellscale") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} now has a Spell Scaling Rate of {}%%.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET spellscale = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "healscale") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} now has a Heal Scaling Rate of {}%%.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format("UPDATE npc_types SET healscale = {} WHERE id = {}", atoi(sep->arg[2]), npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "no_target") == 0) { + c->Message( + Chat::Yellow, + fmt::format( + "NPC ID {} is {} Targetable with Target Hotkey.", + npc_id, + (atoi(sep->arg[2]) == 1 ? "now" : "no longer")).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET no_target_hotkey = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "raidtarget") == 0) { + c->Message( + Chat::Yellow, + fmt::format( + "NPC ID {} is {} designated as a Raid Target.", + npc_id, + (atoi(sep->arg[2]) == 1 ? "now" : "no longer")).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET raid_target = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "armtexture") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} is now using Arm Texture {}.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET armtexture = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "bracertexture") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} is now using Bracer Texture {}.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET bracertexture = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "handtexture") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} is now using Hand Texture {}.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET handtexture = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "legtexture") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} is now using Leg Texture {}.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET legtexture = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "feettexture") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} is now using Feet Texture {}.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET feettexture = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "walkspeed") == 0) { + c->Message(Chat::Yellow, fmt::format("NPC ID {} now walks at {:.2f}.", npc_id, atof(sep->arg[2])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET walkspeed = {:.2f} WHERE id = {}", + atof(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "show_name") == 0) { + c->Message( + Chat::Yellow, + fmt::format( + "NPC ID {} will {} show their name.", + npc_id, + (atoi(sep->arg[2]) == 1 ? "now" : "no longer")).c_str()); + std::string query = fmt::format("UPDATE npc_types SET show_name = {} WHERE id = {}", atoi(sep->arg[2]), npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "untargetable") == 0) { + c->Message( + Chat::Yellow, + fmt::format( + "NPC ID {} will {} be untargetable.", + npc_id, + (atoi(sep->arg[2]) == 1 ? "now" : "no longer")).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET untargetable = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "charm_ac") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} now has {} Armor Class while Charmed.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format("UPDATE npc_types SET charm_ac = {} WHERE id = {}", atoi(sep->arg[2]), npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "charm_min_dmg") == 0) { + c->Message( + Chat::Yellow, + fmt::format( + "NPC ID {} now does {} Minimum Damage while Charmed.", + npc_id, + atoi(sep->arg[2])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET charm_min_dmg = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "charm_max_dmg") == 0) { + c->Message( + Chat::Yellow, + fmt::format( + "NPC ID {} now does {} Maximum Damage while Charmed.", + npc_id, + atoi(sep->arg[2])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET charm_max_dmg = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "charm_attack_delay") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} now has {} Attack Delay while Charmed.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET charm_attack_delay = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "charm_accuracy_rating") == 0) { + c->Message( + Chat::Yellow, + fmt::format( + "NPC ID {} now has {} Accuracy Rating while Charmed.", + npc_id, + atoi(sep->arg[2])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET charm_accuracy_rating = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "charm_avoidance_rating") == 0) { + c->Message( + Chat::Yellow, + fmt::format( + "NPC ID {} now has {} Avoidance Rating while Charmed.", + npc_id, + atoi(sep->arg[2])).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET charm_avoidance_rating = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "charm_atk") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} now has {} Attack while Charmed.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format("UPDATE npc_types SET charm_atk = {} WHERE id = {}", atoi(sep->arg[2]), npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "skip_global_loot") == 0) { + c->Message( + Chat::Yellow, + fmt::format( + "NPC ID {} will {} skip Global Loot.", + npc_id, + (atoi(sep->arg[2]) == 1 ? "now" : "no longer")).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET skip_global_loot = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "rarespawn") == 0) { + c->Message( + Chat::Yellow, + fmt::format( + "NPC ID {} is {} designated as a Rare Spawn.", + npc_id, + (atoi(sep->arg[2]) == 1 ? "now" : "no longer")).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET rare_spawn = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "stuck_behavior") == 0) { + auto behavior_id = atoi(sep->arg[2]); + std::string behavior_name = "Unknown"; + if (behavior_id == MobStuckBehavior::RunToTarget) { + behavior_name = "Run To Target"; + } + else if (behavior_id == MobStuckBehavior::WarpToTarget) { + behavior_name = "Warp To Target"; + } + else if (behavior_id == MobStuckBehavior::TakeNoAction) { + behavior_name = "Take No Action"; + } + else if (behavior_id == MobStuckBehavior::EvadeCombat) { + behavior_name = "Evade Combat"; + } + c->Message( + Chat::Yellow, + fmt::format( + "NPC ID {} is now using Stuck Behavior {} ({}).", + npc_id, + behavior_name, + behavior_id + ).c_str()); + std::string query = fmt::format("UPDATE npc_types SET stuck_behavior = {} WHERE id = {}", behavior_id, npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "flymode") == 0) { + auto flymode_id = static_cast(std::stoul(sep->arg[2])); + std::string flymode_name = EQ::constants::GetFlyModeName(flymode_id); + c->Message( + Chat::Yellow, + fmt::format( + "NPC ID {} is now using Fly Mode {} ({}).", + npc_id, + flymode_name, + flymode_id + ).c_str() + ); + std::string query = fmt::format("UPDATE npc_types SET flymode = {} WHERE id = {}", flymode_id, npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "always_aggro") == 0) { + c->Message( + Chat::Yellow, + fmt::format( + "NPC ID {} will {} Always Aggro.", + npc_id, + (atoi(sep->arg[2]) == 1 ? "now" : "no longer")).c_str()); + std::string query = fmt::format( + "UPDATE npc_types SET always_aggro = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "exp_mod") == 0) { + c->Message( + Chat::Yellow, + fmt::format("NPC ID {} now has an Experience Modifier of {}%%.", npc_id, atoi(sep->arg[2])).c_str()); + std::string query = fmt::format("UPDATE npc_types SET exp_mod = {} WHERE id = {}", atoi(sep->arg[2]), npc_id); + content_db.QueryDatabase(query); + return; + } + + if (strcasecmp(sep->arg[1], "setanimation") == 0) { + int animation = 0; + std::string animation_name = "Unknown"; + if (sep->arg[2] && atoi(sep->arg[2]) <= 4) { + if (strcasecmp(sep->arg[2], "stand") == 0 || atoi(sep->arg[2]) == 0) { // Stand + animation = 0; + animation_name = "Standing"; + } + else if (strcasecmp(sep->arg[2], "sit") == 0 || atoi(sep->arg[2]) == 1) { // Sit + animation = 1; + animation_name = "Sitting"; + } + else if (strcasecmp(sep->arg[2], "crouch") == 0 || atoi(sep->arg[2]) == 2) { // Crouch + animation = 2; + animation_name = "Crouching"; + } + else if (strcasecmp(sep->arg[2], "dead") == 0 || atoi(sep->arg[2]) == 3) { // Dead + animation = 3; + animation_name = "Dead"; + } + else if (strcasecmp(sep->arg[2], "loot") == 0 || atoi(sep->arg[2]) == 4) { // Looting Animation + animation = 4; + animation_name = "Looting"; + } + } + else { + c->Message( + Chat::White, + "You must specify an Animation (0 = Stand, 1 = Sit, 2 = Crouch, 3 = Dead, 4 = Loot)" + ); + c->Message(Chat::White, "Example: #npcedit setanimation sit"); + c->Message(Chat::White, "Example: #npcedit setanimation 0"); + return; + } + + c->Message( + Chat::Yellow, + fmt::format( + "NPC ID {} now has their Spawn Animation set to {} ({}) on Spawn Group ID {}.", + npc_id, + animation_name, + animation, + c->GetTarget()->CastToNPC()->GetSpawnGroupId() + ).c_str() + ); + std::string query = fmt::format( + "UPDATE spawn2 SET animation = {} WHERE spawngroupID = {}", + animation, + c->GetTarget()->CastToNPC()->GetSpawnGroupId() + ); + content_db.QueryDatabase(query); + + c->GetTarget()->SetAppearance(EmuAppearance(animation)); + return; + } + + if (strcasecmp(sep->arg[1], "respawntime") == 0) { + if (sep->arg[2][0] && sep->IsNumber(sep->arg[2]) && atoi(sep->arg[2]) > 0) { + c->Message( + Chat::Yellow, + fmt::format( + "NPC ID {} now has a Respawn Timer of {} Seconds on Spawn Group ID {}.", + npc_id, + atoi(sep->arg[2]), + c->GetTarget()->CastToNPC()->GetSpawnGroupId()).c_str()); + std::string query = fmt::format( + "UPDATE spawn2 SET respawntime = {} WHERE spawngroupID = {} AND version = {}", + atoi(sep->arg[2]), + c->GetTarget()->CastToNPC()->GetSpawnGroupId(), + zone->GetInstanceVersion()); + content_db.QueryDatabase(query); + return; + } + } + + if ((sep->arg[1][0] == 0 || strcasecmp(sep->arg[1], "*") == 0) || + ((c->GetTarget() == 0) || (c->GetTarget()->IsClient()))) { + c->Message(Chat::White, "Type #npcedit help for more info"); + } + +} + diff --git a/zone/gm_commands/npceditmass.cpp b/zone/gm_commands/npceditmass.cpp new file mode 100755 index 000000000..c76f209e0 --- /dev/null +++ b/zone/gm_commands/npceditmass.cpp @@ -0,0 +1,242 @@ +#include "../client.h" + +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 (apply)" + ); + return; + } + + std::string query = SQL( + SELECT + COLUMN_NAME + FROM + INFORMATION_SCHEMA.COLUMNS + WHERE + table_name = 'npc_types' + AND + COLUMN_NAME != 'id' + ); + + std::string search_column, search_value, change_column, change_value; + + if (sep->arg[1]) { + search_column = sep->arg[1]; + } + + if (sep->arg[2]) { + search_value = sep->arg[2]; + } + + if (sep->arg[3]) { + change_column = sep->arg[3]; + } + + if (sep->arg[4]) { + change_value = sep->arg[4]; + } + + bool valid_change_column = false; + bool valid_search_column = false; + auto results = content_db.QueryDatabase(query); + + std::vector possible_column_options; + + for (auto row : results) { + if (row[0] == change_column) { + valid_change_column = true; + } + + if (row[0] == search_column) { + valid_search_column = true; + } + + possible_column_options.push_back(row[0]); + } + + std::string options_glue = ", "; + + if (!valid_search_column) { + c->Message( + Chat::Red, + fmt::format( + "You must specify a valid search column. [{}] is not valid", + search_column + ).c_str() + ); + + c->Message( + Chat::Yellow, + fmt::format( + "Possible columns [{}]", + implode(options_glue, possible_column_options) + ).c_str() + ); + return; + } + + if (!valid_change_column) { + c->Message( + Chat::Red, + fmt::format( + "You must specify a valid change column. [{}] is not valid", + change_column + ).c_str() + ); + + c->Message( + Chat::Yellow, + fmt::format( + "Possible columns [{}]", + implode(options_glue, possible_column_options) + ).c_str() + ); + return; + } + + if (!valid_search_column || !valid_change_column) { + c->Message(Chat::Red, "One requested column is invalid."); + return; + } + + query = fmt::format( + SQL( + SELECT id, name, {}, {} + FROM npc_types + WHERE id IN( + SELECT spawnentry.npcID + FROM spawnentry + JOIN spawn2 + ON spawn2.spawngroupID = spawnentry.spawngroupID + WHERE spawn2.zone = '{}' AND spawn2.version = {} + ) + ), + search_column, + change_column, + zone->GetShortName(), + zone->GetInstanceVersion() + ); + + std::string status = "(Searching)"; + + if (!strcasecmp(sep->arg[5], "apply")) { + status = "(Applying)"; + } + + std::vector npc_ids; + + bool exact_match = false; + if (search_value[0] == '=') { + exact_match = true; + search_value = search_value.substr(1); + } + + int found_count = 0; + results = content_db.QueryDatabase(query); + for (auto row : results) { + std::string npc_id = row[0]; + std::string npc_name = row[1]; + std::string search_column_value = str_tolower(row[2]); + std::string change_column_current_value = row[3]; + + if (exact_match) { + if (search_column_value.compare(search_value)) { + continue; + } + } + else { + if (search_column_value.find(search_value) == std::string::npos) { + continue; + } + } + + c->Message( + Chat::Yellow, + fmt::format( + "NPC ({}) [{}] ({}) [{}] Current ({}) [{}] New [{}] {}", + npc_id, + npc_name, + search_column, + search_column_value, + change_column, + change_column_current_value, + change_value, + status + ).c_str() + ); + + npc_ids.push_back(npc_id); + + found_count++; + } + + std::string saylink = fmt::format( + "#npceditmass {} {}{} {} {} apply", + search_column, + (exact_match ? "=" : ""), + search_value, + change_column, + change_value + ); + + if (strcasecmp(sep->arg[5], "apply") == 0) { + std::string npc_ids_string = implode(",", npc_ids); + if (npc_ids_string.empty()) { + c->Message(Chat::Red, "Error: Ran into an unknown error compiling NPC IDs"); + return; + } + + content_db.QueryDatabase( + fmt::format( + "UPDATE `npc_types` SET {} = '{}' WHERE id IN ({})", + change_column, + change_value, + npc_ids_string + ) + ); + + c->Message( + Chat::Yellow, + fmt::format( + "Changes applied to {} NPC{}.", + found_count, + found_count != 1 ? "s" : "" + ).c_str() + ); + zone->Repop(); + } + else { + if (found_count > 0) { + c->Message( + Chat::Yellow, + fmt::format( + "{} NPC{} match your search.", + found_count, + found_count != 1 ? "s" : "" + ).c_str() + ); + + c->Message( + Chat::Yellow, + fmt::format( + "Would you like to {} these changes?", + EQ::SayLinkEngine::GenerateQuestSaylink(saylink, false, "apply") + ).c_str() + ); + + c->Message( + Chat::Yellow, + fmt::format( + "You can also use '{}'.", + saylink + ).c_str() + ); + } else { + c->Message(Chat::Yellow, "No NPCs match your search."); + } + } +} + diff --git a/zone/gm_commands/npcemote.cpp b/zone/gm_commands/npcemote.cpp new file mode 100755 index 000000000..c4ba83388 --- /dev/null +++ b/zone/gm_commands/npcemote.cpp @@ -0,0 +1,12 @@ +#include "../client.h" + +void command_npcemote(Client *c, const Seperator *sep) +{ + if (c->GetTarget() && c->GetTarget()->IsNPC() && sep->arg[1][0]) { + c->GetTarget()->Emote(sep->argplus[1]); + } + else { + c->Message(Chat::White, "Usage: #npcemote message (requires NPC target"); + } +} + diff --git a/zone/gm_commands/npcloot.cpp b/zone/gm_commands/npcloot.cpp new file mode 100755 index 000000000..9f95f56ff --- /dev/null +++ b/zone/gm_commands/npcloot.cpp @@ -0,0 +1,249 @@ +#include "../client.h" +#include "../corpse.h" +#include "../../common/data_verification.h" + +void command_npcloot(Client *c, const Seperator *sep) +{ + if (!c->GetTarget() || (!c->GetTarget()->IsNPC() && !c->GetTarget()->IsCorpse())) { + c->Message(Chat::White, "You must target an NPC or a Corpse to use this command."); + return; + } + + int arguments = sep->argnum; + if (!arguments) { + c->Message(Chat::White, "Usage: #npcloot add [Item ID] [Charges] [Equip] [Augment 1 ID] [Augment 2 ID] [Augment 3 ID] [Augment 4 ID] [Augment 5 ID] [Augment 6 ID] - Adds the specified item to an NPC's loot"); + c->Message(Chat::White, "Usage: #npcloot money [Platinum] [Gold] [Silver] [Copper] - Set an NPC's current money"); + c->Message(Chat::White, "Usage: #npcloot remove [All|Item ID] - Remove loot from an NPC by ID or remove all loot"); + c->Message(Chat::White, "Usage: #npcloot show - Shows target NPC's or Corpse's current loot"); + return; + } + + bool is_add = !strcasecmp(sep->arg[1], "add"); + bool is_money = !strcasecmp(sep->arg[1], "money"); + bool is_remove = !strcasecmp(sep->arg[1], "remove"); + bool is_show = !strcasecmp(sep->arg[1], "show"); + + if ( + !is_add && + !is_money && + !is_remove && + !is_show + ) { + c->Message(Chat::White, "Usage: #npcloot add [Item ID] [Charges] [Equip] [Augment 1 ID] [Augment 2 ID] [Augment 3 ID] [Augment 4 ID] [Augment 5 ID] [Augment 6 ID] - Adds the specified item to an NPC's loot"); + c->Message(Chat::White, "Usage: #npcloot money [Platinum] [Gold] [Silver] [Copper] - Set an NPC's current money"); + c->Message(Chat::White, "Usage: #npcloot remove [All|Item ID] - Remove loot from an NPC by ID or remove all loot"); + c->Message(Chat::White, "Usage: #npcloot show - Shows target NPC's or Corpse's current loot"); + return; + } + + if (is_add) { + if (!c->GetTarget()->IsNPC() || !sep->IsNumber(2)) { + c->Message(Chat::White, "Usage: #npcloot add [Item ID] [Charges] [Equip] [Augment 1 ID] [Augment 2 ID] [Augment 3 ID] [Augment 4 ID] [Augment 5 ID] [Augment 6 ID] - Adds the specified item to an NPC's loot"); + return; + } + + auto item_id = std::stoul(sep->arg[2]); + auto item_charges = sep->IsNumber(3) ? static_cast(std::stoul(sep->arg[3])) : 1; + bool equip_item = arguments >= 4 ? atobool(sep->arg[4]) : false; + auto augment_one_id = sep->IsNumber(5) ? std::stoul(sep->arg[5]) : 0; + auto augment_two_id = sep->IsNumber(6) ? std::stoul(sep->arg[6]) : 0; + auto augment_three_id = sep->IsNumber(7) ? std::stoul(sep->arg[7]) : 0; + auto augment_four_id = sep->IsNumber(8) ? std::stoul(sep->arg[8]) : 0; + auto augment_five_id = sep->IsNumber(9) ? std::stoul(sep->arg[9]) : 0; + auto augment_six_id = sep->IsNumber(10) ? std::stoul(sep->arg[10]) : 0; + + auto item_data = database.GetItem(item_id); + + if (!item_data) { + c->Message( + Chat::White, + fmt::format( + "Item ID {} could not be found", + item_id + ).c_str() + ); + return; + } + + c->GetTarget()->CastToNPC()->AddItem( + item_id, + item_charges, + equip_item, + augment_one_id, + augment_two_id, + augment_three_id, + augment_four_id, + augment_five_id, + augment_six_id + ); + + auto item = database.CreateItem( + item_id, + item_charges, + augment_one_id, + augment_two_id, + augment_three_id, + augment_four_id, + augment_five_id, + augment_six_id + ); + + EQ::SayLinkEngine linker; + linker.SetLinkType(EQ::saylink::SayLinkItemInst); + linker.SetItemInst(item); + + auto item_link = linker.GenerateLink(); + + c->Message( + Chat::White, + fmt::format( + "Added {} ({}) to {} ({}).", + item_link, + item_id, + c->GetTarget()->GetCleanName(), + c->GetTarget()->GetID() + ).c_str() + ); + } else if (is_money) { + if (!c->GetTarget()->IsNPC()) { + c->Message(Chat::White, "You must target an NPC to use this command."); + return; + } + + auto target = c->GetTarget()->CastToNPC(); + + if (sep->IsNumber(2)) { + uint16 platinum = EQ::Clamp(std::stoi(sep->arg[2]), 0, 65535); + uint16 gold = sep->IsNumber(3) ? EQ::Clamp(std::stoi(sep->arg[3]), 0, 65535) : 0; + uint16 silver = sep->IsNumber(4) ? EQ::Clamp(std::stoi(sep->arg[4]), 0, 65535) : 0; + uint16 copper = sep->IsNumber(5) ? EQ::Clamp(std::stoi(sep->arg[5]), 0, 65535) : 0; + target->AddCash( + copper, + silver, + gold, + platinum + ); + + auto money_string = ( + ( + copper || + silver || + gold || + platinum + ) ? + ConvertMoneyToString( + platinum, + gold, + silver, + copper + ) : + "no money" + ); + + c->Message( + Chat::White, + fmt::format( + "{} ({}) now has {}.", + target->GetCleanName(), + target->GetID(), + money_string + ).c_str() + ); + } else { + c->Message(Chat::White, "Usage: #npcloot money [Platinum] [Gold] [Silver] [Copper] - Set an NPC's current money"); + } + } else if (is_remove) { + if (!c->GetTarget()->IsNPC()) { + c->Message(Chat::White, "You must target an NPC to use this command."); + return; + } + + auto target = c->GetTarget()->CastToNPC(); + + bool is_remove_all = !strcasecmp(sep->arg[2], "all"); + if (is_remove_all) { + auto loot_list = target->GetLootList(); + auto total_item_count = 0; + for (const auto& item_id : loot_list) { + auto item_count = target->CountItem(item_id); + + target->RemoveItem(item_id); + + c->Message( + Chat::White, + fmt::format( + "Removed {} {} ({}) from {} ({}).", + item_count, + database.CreateItemLink(item_id), + item_id, + target->GetCleanName(), + target->GetID() + ).c_str() + ); + + total_item_count += item_count; + } + + if (!total_item_count) { + c->Message( + Chat::White, + fmt::format( + "{} ({}) has no items to remove.", + target->GetCleanName(), + target->GetID() + ).c_str() + ); + } else { + c->Message( + Chat::White, + fmt::format( + "{} Item{} removed from {} ({}).", + total_item_count, + total_item_count != 1 ? "s" : "", + target->GetCleanName(), + target->GetID() + ).c_str() + ); + } + } else { + if (sep->IsNumber(2)) { + auto item_id = std::stoul(sep->arg[2]); + auto item_count = target->CountItem(item_id); + if (item_count) { + target->RemoveItem(item_id); + c->Message( + Chat::White, + fmt::format( + "Removed {} {} ({}) from {} ({}).", + item_count, + database.CreateItemLink(item_id), + item_id, + target->GetCleanName(), + target->GetID() + ).c_str() + ); + } else { + c->Message( + Chat::White, + fmt::format( + "{} ({}) does not have any {} ({}).", + target->GetCleanName(), + target->GetID(), + database.CreateItemLink(item_id), + item_id + ).c_str() + ); + } + } else { + c->Message(Chat::White, "Usage: #npcloot remove [All|Item ID] - Remove loot from an NPC by ID or remove all loot"); + } + } + } else if (is_show) { + if (c->GetTarget()->IsNPC()) { + c->GetTarget()->CastToNPC()->QueryLoot(c); + } else if (c->GetTarget()->IsCorpse()) { + c->GetTarget()->CastToCorpse()->QueryLoot(c); + } + } +} + diff --git a/zone/gm_commands/npcsay.cpp b/zone/gm_commands/npcsay.cpp new file mode 100755 index 000000000..96d79232f --- /dev/null +++ b/zone/gm_commands/npcsay.cpp @@ -0,0 +1,12 @@ +#include "../client.h" + +void command_npcsay(Client *c, const Seperator *sep) +{ + if (c->GetTarget() && c->GetTarget()->IsNPC() && sep->arg[1][0]) { + c->GetTarget()->Say(sep->argplus[1]); + } + else { + c->Message(Chat::White, "Usage: #npcsay message (requires NPC target"); + } +} + diff --git a/zone/gm_commands/npcshout.cpp b/zone/gm_commands/npcshout.cpp new file mode 100755 index 000000000..f04d74a9f --- /dev/null +++ b/zone/gm_commands/npcshout.cpp @@ -0,0 +1,12 @@ +#include "../client.h" + +void command_npcshout(Client *c, const Seperator *sep) +{ + if (c->GetTarget() && c->GetTarget()->IsNPC() && sep->arg[1][0]) { + c->GetTarget()->Shout(sep->argplus[1]); + } + else { + c->Message(Chat::White, "Usage: #npcshout message (requires NPC target"); + } +} + diff --git a/zone/gm_commands/npcspawn.cpp b/zone/gm_commands/npcspawn.cpp new file mode 100755 index 000000000..dd88bbece --- /dev/null +++ b/zone/gm_commands/npcspawn.cpp @@ -0,0 +1,97 @@ +#include "../client.h" + +void command_npcspawn(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments) { + c->Message(Chat::White, "Command Syntax: #npcspawn [Add|Create|Delete|Remove|Update]"); + return; + } + + if (!(c->GetTarget() && c->GetTarget()->IsNPC())) { + c->Message(Chat::White, "You must target an NPC to use this command."); + return; + } + + NPC *target = c->GetTarget()->CastToNPC(); + std::string spawn_type = str_tolower(sep->arg[1]); + uint32 extra = 0; + bool is_add = spawn_type.find("add") != std::string::npos; + bool is_create = spawn_type.find("create") != std::string::npos; + bool is_delete = spawn_type.find("delete") != std::string::npos; + bool is_remove = spawn_type.find("remove") != std::string::npos; + bool is_update = spawn_type.find("update") != std::string::npos; + if (!is_add && !is_create && !is_delete && !is_remove && !is_update) { + c->Message(Chat::White, "Command Syntax: #npcspawn [Add|Create|Delete|Remove|Update]"); + return; + } + + if (is_add || is_create) { + extra = ( + sep->IsNumber(2) ? + ( + is_add ? + std::stoi(sep->arg[2]) : + 1 + ) : ( + is_add ? + 1200 : + 0 + ) + ); // Default to 1200 for Add, 0 for Create if not set + content_db.NPCSpawnDB( + is_add ? NPCSpawnTypes::AddNewSpawngroup : NPCSpawnTypes::CreateNewSpawn, + zone->GetShortName(), + zone->GetInstanceVersion(), + c, + target, + extra + ); + c->Message( + Chat::White, + fmt::format( + "Spawn {} | Name: {} ({})", + is_add ? "Added" : "Created", + target->GetCleanName(), + target->GetID() + ).c_str() + ); + } + else if (is_delete || is_remove || is_update) { + uint8 spawn_update_type = ( + is_delete ? + NPCSpawnTypes::DeleteSpawn : + ( + is_remove ? + NPCSpawnTypes::RemoveSpawn : + NPCSpawnTypes::UpdateAppearance + ) + ); + std::string spawn_message = ( + is_delete ? + "Deleted" : + ( + is_remove ? + "Removed" : + "Updated" + ) + ); + content_db.NPCSpawnDB( + spawn_update_type, + zone->GetShortName(), + zone->GetInstanceVersion(), + c, + target + ); + c->Message( + Chat::White, + fmt::format( + "Spawn {} | Name: {} ({})", + spawn_message, + target->GetCleanName(), + target->GetID() + ).c_str() + ); + } +} + diff --git a/zone/gm_commands/npcspecialattk.cpp b/zone/gm_commands/npcspecialattk.cpp new file mode 100755 index 000000000..4d2a63dc8 --- /dev/null +++ b/zone/gm_commands/npcspecialattk.cpp @@ -0,0 +1,16 @@ +#include "../client.h" + +void command_npcspecialattk(Client *c, const Seperator *sep) +{ + if (c->GetTarget() == 0 || c->GetTarget()->IsClient() || strlen(sep->arg[1]) <= 0 || strlen(sep->arg[2]) <= 0) { + c->Message( + Chat::White, + "Usage: #npcspecialattk *flagchar* *permtag* (Flags are E(nrage) F(lurry) R(ampage) S(ummon), permtag is 1 = True, 0 = False)." + ); + } + else { + c->GetTarget()->CastToNPC()->NPCSpecialAttacks(sep->arg[1], atoi(sep->arg[2])); + c->Message(Chat::White, "NPC Special Attack set."); + } +} + diff --git a/zone/gm_commands/npcstats.cpp b/zone/gm_commands/npcstats.cpp new file mode 100755 index 000000000..9f903d1f7 --- /dev/null +++ b/zone/gm_commands/npcstats.cpp @@ -0,0 +1,18 @@ +#include "../client.h" + +void command_npcstats(Client *c, const Seperator *sep) +{ + if (c->GetTarget() && c->GetTarget()->IsNPC()) { + NPC *target = c->GetTarget()->CastToNPC(); + + // Stats + target->ShowStats(c); + + // Loot Data + target->QueryLoot(c); + } + else { + c->Message(Chat::White, "You must target an NPC to use this command."); + } +} + diff --git a/zone/gm_commands/npctype_cache.cpp b/zone/gm_commands/npctype_cache.cpp new file mode 100755 index 000000000..27cc45429 --- /dev/null +++ b/zone/gm_commands/npctype_cache.cpp @@ -0,0 +1,27 @@ +#include "../client.h" + +void command_npctype_cache(Client *c, const Seperator *sep) +{ + if (sep->argnum > 0) { + for (int i = 0; i < sep->argnum; ++i) { + if (strcasecmp(sep->arg[i + 1], "all") == 0) { + c->Message(Chat::White, "Clearing all npc types from the cache."); + zone->ClearNPCTypeCache(-1); + } + else { + int id = atoi(sep->arg[i + 1]); + if (id > 0) { + c->Message(Chat::White, "Clearing npc type %d from the cache.", id); + zone->ClearNPCTypeCache(id); + return; + } + } + } + } + else { + c->Message(Chat::White, "Usage:"); + c->Message(Chat::White, "#npctype_cache [npctype_id] ..."); + c->Message(Chat::White, "#npctype_cache all"); + } +} + diff --git a/zone/gm_commands/npctypespawn.cpp b/zone/gm_commands/npctypespawn.cpp new file mode 100755 index 000000000..18c757aa6 --- /dev/null +++ b/zone/gm_commands/npctypespawn.cpp @@ -0,0 +1,29 @@ +#include "../client.h" + +void command_npctypespawn(Client *c, const Seperator *sep) +{ + if (sep->IsNumber(1)) { + const NPCType *tmp = 0; + if ((tmp = content_db.LoadNPCTypesData(atoi(sep->arg[1])))) { + //tmp->fixedZ = 1; + auto npc = new NPC(tmp, 0, c->GetPosition(), GravityBehavior::Water); + if (npc && sep->IsNumber(2)) { + npc->SetNPCFactionID(atoi(sep->arg[2])); + } + + npc->AddLootTable(); + if (npc->DropsGlobalLoot()) { + npc->CheckGlobalLootTables(); + } + entity_list.AddNPC(npc); + } + else { + c->Message(Chat::White, "NPC Type %i not found", atoi(sep->arg[1])); + } + } + else { + c->Message(Chat::White, "Usage: #npctypespawn npctypeid factionid"); + } + +} + diff --git a/zone/gm_commands/nudge.cpp b/zone/gm_commands/nudge.cpp new file mode 100755 index 000000000..e8f4bfaa0 --- /dev/null +++ b/zone/gm_commands/nudge.cpp @@ -0,0 +1,77 @@ +#include "../client.h" + +void command_nudge(Client *c, const Seperator *sep) +{ + if (sep->arg[1][0] == 0) { + c->Message(Chat::White, "Usage: #nudge [x=f] [y=f] [z=f] [h=f] (partial/mixed arguments allowed)"); + } + else { + + auto target = c->GetTarget(); + if (!target) { + + c->Message(Chat::Yellow, "This command requires a target."); + return; + } + if (target->IsMoving()) { + + c->Message(Chat::Yellow, "This command requires a stationary target."); + return; + } + + glm::vec4 position_offset(0.0f, 0.0f, 0.0f, 0.0f); + for (auto index = 1; index <= 4; ++index) { + + if (!sep->arg[index]) { + continue; + } + + Seperator argsep(sep->arg[index], '='); + if (!argsep.arg[1][0]) { + continue; + } + + switch (argsep.arg[0][0]) { + case 'x': + position_offset.x = atof(argsep.arg[1]); + break; + case 'y': + position_offset.y = atof(argsep.arg[1]); + break; + case 'z': + position_offset.z = atof(argsep.arg[1]); + break; + case 'h': + position_offset.w = atof(argsep.arg[1]); + break; + default: + break; + } + } + + const auto ¤t_position = target->GetPosition(); + glm::vec4 new_position( + (current_position.x + position_offset.x), + (current_position.y + position_offset.y), + (current_position.z + position_offset.z), + (current_position.w + position_offset.w) + ); + + target->GMMove(new_position.x, new_position.y, new_position.z, new_position.w); + + c->Message( + Chat::White, + "Nudging '%s' to {%1.3f, %1.3f, %1.3f, %1.2f} (adjustment: {%1.3f, %1.3f, %1.3f, %1.2f})", + target->GetName(), + new_position.x, + new_position.y, + new_position.z, + new_position.w, + position_offset.x, + position_offset.y, + position_offset.z, + position_offset.w + ); + } +} + diff --git a/zone/gm_commands/nukebuffs.cpp b/zone/gm_commands/nukebuffs.cpp new file mode 100755 index 000000000..bb6542ea0 --- /dev/null +++ b/zone/gm_commands/nukebuffs.cpp @@ -0,0 +1,47 @@ +#include "../client.h" + +void command_nukebuffs(Client *c, const Seperator *sep) +{ + Mob* target = c; + if (c->GetTarget()) { + target = c->GetTarget(); + } + + std::string buff_identifier = str_tolower(sep->arg[1]); + std::string buff_type; + bool is_beneficial = buff_identifier.find("beneficial") != std::string::npos; + bool is_detrimental = buff_identifier.find("detrimental") != std::string::npos; + bool is_help = buff_identifier.find("help") != std::string::npos; + if (is_beneficial) { + target->BuffFadeBeneficial(); + buff_type = " beneficial"; + } else if (is_detrimental) { + target->BuffFadeDetrimental(); + buff_type = " detrimental"; + } else if (is_help) { + c->Message(Chat::White, "Usage: #nukebuffs"); + c->Message(Chat::White, "Usage: #nukebuffs beneficial"); + c->Message(Chat::White, "Usage: #nukebuffs detrimental"); + return; + } else { + target->BuffFadeAll(); + } + + c->Message( + Chat::White, + fmt::format( + "Faded all{} buffs for {}.", + buff_type, + ( + c == target ? + "yourself" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ) + ).c_str() + ); +} + diff --git a/zone/gm_commands/nukeitem.cpp b/zone/gm_commands/nukeitem.cpp new file mode 100755 index 000000000..1856b9b1f --- /dev/null +++ b/zone/gm_commands/nukeitem.cpp @@ -0,0 +1,57 @@ +#include "../client.h" + +void command_nukeitem(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments || !sep->IsNumber(1)) { + c->Message(Chat::White, "Usage: #nukeitem [Item ID] - Removes the specified Item ID from you or your player target's inventory"); + return; + } + + Client *target = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); + } + + auto item_id = std::stoi(sep->arg[1]); + auto deleted_count = target->NukeItem(item_id); + if (deleted_count) { + c->Message( + Chat::White, + fmt::format( + "{} {} ({}) deleted from {}.", + deleted_count, + database.CreateItemLink(item_id), + item_id, + ( + c == target ? + "yourself" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ) + ).c_str() + ); + } else { + c->Message( + Chat::White, + fmt::format( + "Could not find any {} ({}) to delete from {}.", + database.CreateItemLink(item_id), + item_id, + ( + c == target ? + "yourself" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ) + ).c_str() + ); + } +} + diff --git a/zone/gm_commands/object.cpp b/zone/gm_commands/object.cpp new file mode 100755 index 000000000..9c233cd80 --- /dev/null +++ b/zone/gm_commands/object.cpp @@ -0,0 +1,1263 @@ +#include "../client.h" +#include "../object.h" +#include "../doors.h" + +void command_object(Client *c, const Seperator *sep) +{ + if (!c) { + return; + } // Crash Suppressant: No client. How did we get here? + + // Save it here. We sometimes have need to refer to it in multiple places. + const char *usage_string = "Usage: #object List|Add|Edit|Move|Rotate|Save|Copy|Delete|Undo"; + + if ((!sep) || (sep->argnum == 0)) { + c->Message(Chat::White, usage_string); + return; + } + + Object *o = nullptr; + Object_Struct od; + Door_Struct *ds; + uint32 id = 0; + uint32 itemid = 0; + uint32 icon = 0; + uint32 instance = 0; + uint32 newid = 0; + uint16 radius; + EQApplicationPacket *app; + + bool bNewObject = false; + + float x2; + float y2; + + // Temporary object type for static objects to allow manipulation + // NOTE: Zone::LoadZoneObjects() currently loads this as an uint8, so max value is 255! + static const uint32 staticType = 255; + + // Case insensitive commands (List == list == LIST) + strlwr(sep->arg[1]); + + if (strcasecmp(sep->arg[1], "list") == 0) { + // Insufficient or invalid args + if ((sep->argnum < 2) || (sep->arg[2][0] < '0') || + ((sep->arg[2][0] > '9') && ((sep->arg[2][0] & 0xDF) != 'A'))) { + c->Message(Chat::White, "Usage: #object List All|(radius)"); + return; + } + + if ((sep->arg[2][0] & 0xDF) == 'A') { + radius = 0; // List All + } + else if ((radius = atoi(sep->arg[2])) <= 0) { + radius = 500; + } // Invalid radius. Default to 500 units. + + if (radius == 0) + c->Message(Chat::White, "Objects within this zone:"); + else + c->Message(Chat::White, "Objects within %u units of your current location:", radius); + + std::string query; + if (radius) + query = StringFormat( + "SELECT id, xpos, ypos, zpos, heading, itemid, " + "objectname, type, icon, unknown08, unknown10, unknown20 " + "FROM object WHERE zoneid = %u AND version = %u " + "AND (xpos BETWEEN %.1f AND %.1f) " + "AND (ypos BETWEEN %.1f AND %.1f) " + "AND (zpos BETWEEN %.1f AND %.1f) " + "ORDER BY id", + zone->GetZoneID(), zone->GetInstanceVersion(), + c->GetX() - radius, // Yes, we're actually using a bounding box instead of a radius. + c->GetX() + radius, // Much less processing power used this way. + c->GetY() - radius, c->GetY() + radius, c->GetZ() - radius, c->GetZ() + radius + ); + else + query = StringFormat( + "SELECT id, xpos, ypos, zpos, heading, itemid, " + "objectname, type, icon, unknown08, unknown10, unknown20 " + "FROM object WHERE zoneid = %u AND version = %u " + "ORDER BY id", + zone->GetZoneID(), zone->GetInstanceVersion()); + + auto results = content_db.QueryDatabase(query); + if (!results.Success()) { + c->Message(Chat::White, "Error in objects query"); + return; + } + + for (auto row = results.begin(); row != results.end(); ++row) { + id = atoi(row[0]); + od.x = atof(row[1]); + od.y = atof(row[2]); + od.z = atof(row[3]); + od.heading = atof(row[4]); + itemid = atoi(row[5]); + strn0cpy(od.object_name, row[6], sizeof(od.object_name)); + od.object_name[sizeof(od.object_name) - 1] = + '\0'; // Required if strlen(row[col++]) exactly == sizeof(object_name) + + od.object_type = atoi(row[7]); + icon = atoi(row[8]); + od.size = atoi(row[9]); + od.solidtype = atoi(row[10]); + od.unknown020 = atoi(row[11]); + + switch (od.object_type) { + case 0: // Static Object + case staticType: // Static Object unlocked for changes + if (od.size == 0) // Unknown08 field is optional Size parameter for static objects + od.size = 100; // Static object default Size is 100% + + c->Message( + Chat::White, "- STATIC Object (%s): id %u, x %.1f, y %.1f, z %.1f, h %.1f, model %s, " + "size %u, solidtype %u, incline %u", + (od.object_type == 0) ? "locked" : "unlocked", id, od.x, od.y, od.z, + od.heading, od.object_name, od.size, od.solidtype, od.unknown020 + ); + break; + + case OT_DROPPEDITEM: // Ground Spawn + c->Message( + Chat::White, "- TEMPORARY Object: id %u, x %.1f, y %.1f, z %.1f, h %.1f, itemid %u, " + "model %s, icon %u", + id, od.x, od.y, od.z, od.heading, itemid, od.object_name, icon + ); + break; + + default: // All others == Tradeskill Objects + c->Message( + Chat::White, "- TRADESKILL Object: id %u, x %.1f, y %.1f, z %.1f, h %.1f, model %s, " + "type %u, icon %u", + id, od.x, od.y, od.z, od.heading, od.object_name, od.object_type, icon + ); + break; + } + } + + c->Message(Chat::White, "%u object%s found", results.RowCount(), (results.RowCount() == 1) ? "" : "s"); + return; + } + + if (strcasecmp(sep->arg[1], "add") == 0) { + // Insufficient or invalid arguments + if ((sep->argnum < 3) || + ((sep->arg[3][0] == '\0') && (sep->arg[4][0] < '0') && (sep->arg[4][0] > '9'))) { + c->Message( + Chat::White, "Usage: (Static Object): #object Add [ObjectID] 0 Model [SizePercent] " + "[SolidType] [Incline]" + ); + c->Message(Chat::White, "Usage: (Tradeskill Object): #object Add [ObjectID] TypeNum Model Icon"); + c->Message( + Chat::White, "- Notes: Model must start with a letter, max length 16. SolidTypes = 0 (Solid), " + "1 (Sometimes Non-Solid)" + ); + return; + } + + int col; + + if (sep->argnum > 3) { // Model name in arg3? + if ((sep->arg[3][0] <= '9') && (sep->arg[3][0] >= '0')) { + // Nope, user must have specified ObjectID. Extract it. + id = atoi(sep->arg[2]); + col = 1; // Bump all other arguments one to the right. Model is in arg4. + } + else { + // Yep, arg3 is non-numeric, ObjectID must be omitted and model must be arg3 + id = 0; + col = 0; + } + } + else { + // Nope, only 3 args. Object ID must be omitted and arg3 must be model. + id = 0; + col = 0; + } + + memset(&od, 0, sizeof(od)); + + od.object_type = atoi(sep->arg[2 + col]); + + switch (od.object_type) { + case 0: // Static Object + if ((sep->argnum - col) > 3) { + od.size = atoi(sep->arg[4 + col]); // Size specified + + if ((sep->argnum - col) > 4) { + od.solidtype = atoi(sep->arg[5 + col]); // SolidType specified + + if ((sep->argnum - col) > 5) { + od.unknown020 = atoi(sep->arg[6 + col]); + } // Incline specified + } + } + break; + + case 1: // Ground Spawn + c->Message( + Chat::White, "ERROR: Object Type 1 is used for temporarily spawned ground spawns and dropped " + "items, which are not supported with #object. See the 'ground_spawns' table in " + "the database." + ); + return; + + default: // Everything else == Tradeskill Object + icon = ((sep->argnum - col) > 3) ? atoi(sep->arg[4 + col]) : 0; + + if (icon == 0) { + c->Message(Chat::White, "ERROR: Required property 'Icon' not specified for Tradeskill Object"); + return; + } + + break; + } + + od.x = c->GetX(); + od.y = c->GetY(); + od.z = c->GetZ() - (c->GetSize() * 0.625f); + od.heading = c->GetHeading(); + + std::string query; + if (id) { + // ID specified. Verify that it doesn't already exist. + query = StringFormat("SELECT COUNT(*) FROM object WHERE ID = %u", id); + auto results = content_db.QueryDatabase(query); + if (results.Success() && results.RowCount() != 0) { + auto row = results.begin(); + if (atoi(row[0]) > 0) { // Yep, in database already. + id = 0; + } + } + + // Not in database. Already spawned, just not saved? + // Yep, already spawned. + if (id && entity_list.FindObject(id)) { + id = 0; + } + + if (id == 0) { + c->Message(Chat::White, "ERROR: An object already exists with the id %u", atoi(sep->arg[2])); + return; + } + } + + int objectsFound = 0; + // Verify no other objects already in this spot (accidental double-click of Hotkey?) + query = StringFormat( + "SELECT COUNT(*) FROM object WHERE zoneid = %u " + "AND version=%u AND (xpos BETWEEN %.1f AND %.1f) " + "AND (ypos BETWEEN %.1f AND %.1f) " + "AND (zpos BETWEEN %.1f AND %.1f)", + zone->GetZoneID(), zone->GetInstanceVersion(), od.x - 0.2f, + od.x + 0.2f, // Yes, we're actually using a bounding box instead of a radius. + od.y - 0.2f, od.y + 0.2f, // Much less processing power used this way. + od.z - 0.2f, od.z + 0.2f + ); // It's pretty forgiving, though, allowing for close-proximity objects + + auto results = content_db.QueryDatabase(query); + if (results.Success() && results.RowCount() != 0) { + auto row = results.begin(); + objectsFound = atoi(row[0]); // Number of nearby objects from database + } + + // No objects found in database too close. How about spawned but not yet saved? + if (objectsFound == 0 && entity_list.FindNearbyObject(od.x, od.y, od.z, 0.2f)) { + objectsFound = 1; + } + + if (objectsFound) { + c->Message(Chat::White, "ERROR: Object already at this location."); + return; + } + + // Strip any single quotes from objectname (SQL injection FTL!) + strn0cpy(od.object_name, sep->arg[3 + col], sizeof(od.object_name)); + + uint32 len = strlen(od.object_name); + for (col = 0; col < (uint32) len; col++) { + if (od.object_name[col] != '\'') { + continue; + } + + // Uh oh, 1337 h4x0r monkeying around! Strip that apostrophe! + memcpy(&od.object_name[col], &od.object_name[col + 1], len - col); + len--; + col--; + } + + strupr(od.object_name); // Model names are always upper-case. + + if ((od.object_name[0] < 'A') || (od.object_name[0] > 'Z')) { + c->Message(Chat::White, "ERROR: Model name must start with a letter."); + return; + } + + if (id == 0) { + // No ID specified. Get a best-guess next number from the database + // If there's a problem retrieving an ID from the database, it'll end up being object # 1. No + // biggie. + + query = "SELECT MAX(id) FROM object"; + results = content_db.QueryDatabase(query); + if (results.Success() && results.RowCount() != 0) { + auto row = results.begin(); + id = atoi(row[0]); + } + + id++; + } + + // Make sure not to overwrite already-spawned objects that haven't been saved yet. + while (o = entity_list.FindObject(id)) + id++; + + // Static object + if (od.object_type == 0) { + od.object_type = staticType; + } // Temporary. We'll make it 0 when we Save + + od.zone_id = zone->GetZoneID(); + od.zone_instance = zone->GetInstanceVersion(); + + o = new Object(id, od.object_type, icon, od, nullptr); + + // Add to our zone entity list and spawn immediately for all clients + entity_list.AddObject(o, true); + + // Bump player back to avoid getting stuck inside new object + + x2 = 10.0f * sin(c->GetHeading() / 256.0f * 3.14159265f); + y2 = 10.0f * cos(c->GetHeading() / 256.0f * 3.14159265f); + c->MovePC(c->GetX() - x2, c->GetY() - y2, c->GetZ(), c->GetHeading()); + + c->Message( + Chat::White, "Spawning object with tentative id %u at location (%.1f, %.1f, %.1f heading %.1f). Use " + "'#object Save' to save to database when satisfied with placement.", + id, od.x, od.y, od.z, od.heading + ); + + // Temporary Static Object + if (od.object_type == staticType) + c->Message( + Chat::White, "- Note: Static Object will act like a tradeskill container and will not reflect " + "size, solidtype, or incline values until you commit with '#object Save', after " + "which it will be unchangeable until you use '#object Edit' and zone back in." + ); + + return; + } + + if (strcasecmp(sep->arg[1], "edit") == 0) { + + if ((sep->argnum < 2) || ((id = atoi(sep->arg[2])) < 1)) { + c->Message(Chat::White, "Usage: #object Edit (ObjectID) [PropertyName] [NewValue]"); + c->Message(Chat::White, "- Static Object (Type 0) Properties: model, type, size, solidtype, incline"); + c->Message(Chat::White, "- Tradeskill Object (Type 2+) Properties: model, type, icon"); + + return; + } + + o = entity_list.FindObject(id); + + // Object already available in-zone? + if (o) { + // Yep, looks like we can make real-time changes. + if (sep->argnum < 4) { + // Or not. '#object Edit (ObjectID)' called without PropertyName and NewValue + c->Message(Chat::White, "Note: Object %u already unlocked and ready for changes", id); + return; + } + } + else { + // Object not found in-zone in a modifiable form. Check for valid matching circumstances. + std::string query = StringFormat("SELECT zoneid, version, type FROM object WHERE id = %u", id); + auto results = content_db.QueryDatabase(query); + if (!results.Success() || results.RowCount() == 0) { + c->Message(Chat::White, "ERROR: Object %u not found", id); + return; + } + + auto row = results.begin(); + od.zone_id = atoi(row[0]); + od.zone_instance = atoi(row[1]); + od.object_type = atoi(row[2]); + uint32 objectsFound = 1; + + // Object not in this zone? + if (od.zone_id != zone->GetZoneID()) { + c->Message(Chat::White, "ERROR: Object %u not in this zone.", id); + return; + } + + // Object not in this instance? + if (od.zone_instance != zone->GetInstanceVersion()) { + c->Message(Chat::White, "ERROR: Object %u not part of this instance version.", id); + return; + } + + switch (od.object_type) { + case 0: // Static object needing unlocking + // Convert to tradeskill object temporarily for changes + query = StringFormat("UPDATE object SET type = %u WHERE id = %u", staticType, id); + + content_db.QueryDatabase(query); + + c->Message( + Chat::White, "Static Object %u unlocked for editing. You must zone out and back in to " + "make your changes, then commit them with '#object Save'.", + id + ); + if (sep->argnum >= 4) { + c->Message( + Chat::White, "NOTE: The change you specified has not been applied, since the " + "static object had not been unlocked for editing yet." + ); + } + return; + + case OT_DROPPEDITEM: + c->Message( + Chat::White, "ERROR: Object %u is a temporarily spawned ground spawn or dropped item, " + "which cannot be manipulated with #object. See the 'ground_spawns' table " + "in the database.", + id + ); + return; + + case staticType: + c->Message( + Chat::White, "ERROR: Object %u has been unlocked for editing, but you must zone out " + "and back in for your client to refresh its object table before you can " + "make changes to it.", + id + ); + return; + + default: + // Unknown error preventing us from seeing the object in the zone. + c->Message(Chat::White, "ERROR: Unknown problem attempting to manipulate object %u", id); + return; + } + } + + // If we're here, we have a manipulable object ready for changes. + strlwr(sep->arg[3]); // Case insensitive PropertyName + strupr(sep->arg[4]); // In case it's model name, which should always be upper-case + + // Read current object info for reference + icon = o->GetIcon(); + o->GetObjectData(&od); + + // We'll be a little more picky with property names, to prevent errors. Check against the whole word. + if (strcmp(sep->arg[3], "model") == 0) { + + if ((sep->arg[4][0] < 'A') || (sep->arg[4][0] > 'Z')) { + c->Message(Chat::White, "ERROR: Model names must begin with a letter."); + return; + } + + strn0cpy(od.object_name, sep->arg[4], sizeof(od.object_name)); + + o->SetObjectData(&od); + + c->Message(Chat::White, "Object %u now being rendered with model '%s'", id, od.object_name); + } + else if (strcmp(sep->arg[3], "type") == 0) { + if ((sep->arg[4][0] < '0') || (sep->arg[4][0] > '9')) { + c->Message(Chat::White, "ERROR: Invalid type number"); + return; + } + + od.object_type = atoi(sep->arg[4]); + + switch (od.object_type) { + case 0: + // Convert Static Object to temporary changeable type + od.object_type = staticType; + c->Message( + Chat::White, "Note: Static Object will still act like tradeskill object and will not " + "reflect size, solidtype, or incline settings until committed to the " + "database with '#object Save', after which it will be unchangeable until " + "it is unlocked again with '#object Edit'." + ); + break; + + case OT_DROPPEDITEM: + c->Message( + Chat::White, "ERROR: Object Type 1 is used for temporarily spawned ground spawns and " + "dropped items, which are not supported with #object. See the " + "'ground_spawns' table in the database." + ); + return; + + default: + c->Message(Chat::White, "Object %u changed to Tradeskill Object Type %u", id, od.object_type); + break; + } + + o->SetType(od.object_type); + } + else if (strcmp(sep->arg[3], "size") == 0) { + if (od.object_type != staticType) { + c->Message( + 0, "ERROR: Object %u is not a Static Object and does not support the Size property", + id + ); + return; + } + + if ((sep->arg[4][0] < '0') || (sep->arg[4][0] > '9')) { + c->Message(Chat::White, "ERROR: Invalid size specified. Please enter a number."); + return; + } + + od.size = atoi(sep->arg[4]); + o->SetObjectData(&od); + + if (od.size == 0) { // 0 == unspecified == 100% + od.size = 100; + } + + c->Message( + Chat::White, "Static Object %u set to %u%% size. Size will take effect when you commit to the " + "database with '#object Save', after which the object will be unchangeable until " + "you unlock it again with '#object Edit' and zone out and back in.", + id, od.size + ); + } + else if (strcmp(sep->arg[3], "solidtype") == 0) { + + if (od.object_type != staticType) { + c->Message( + Chat::White, "ERROR: Object %u is not a Static Object and does not support the " + "SolidType property", + id + ); + return; + } + + if ((sep->arg[4][0] < '0') || (sep->arg[4][0] > '9')) { + c->Message(Chat::White, "ERROR: Invalid solidtype specified. Please enter a number."); + return; + } + + od.solidtype = atoi(sep->arg[4]); + o->SetObjectData(&od); + + c->Message( + Chat::White, "Static Object %u set to SolidType %u. Change will take effect when you commit " + "to the database with '#object Save'. Support for this property is on a " + "per-model basis, mostly seen in smaller objects such as chests and tables.", + id, od.solidtype + ); + } + else if (strcmp(sep->arg[3], "icon") == 0) { + + if ((od.object_type < 2) || (od.object_type == staticType)) { + c->Message( + Chat::White, "ERROR: Object %u is not a Tradeskill Object and does not support the " + "Icon property", + id + ); + return; + } + + if ((icon = atoi(sep->arg[4])) == 0) { + c->Message(Chat::White, "ERROR: Invalid Icon specified. Please enter an icon number."); + return; + } + + o->SetIcon(icon); + c->Message(Chat::White, "Tradeskill Object %u icon set to %u", id, icon); + } + else if (strcmp(sep->arg[3], "incline") == 0) { + if (od.object_type != staticType) { + c->Message( + 0, + "ERROR: Object %u is not a Static Object and does not support the Incline property", + id + ); + return; + } + + if ((sep->arg[4][0] < '0') || (sep->arg[4][0] > '9')) { + c->Message( + 0, + "ERROR: Invalid Incline specified. Please enter a number. Normal range is 0-512." + ); + return; + } + + od.unknown020 = atoi(sep->arg[4]); + o->SetObjectData(&od); + + c->Message( + Chat::White, "Static Object %u set to %u incline. Incline will take effect when you commit to " + "the database with '#object Save', after which the object will be unchangeable " + "until you unlock it again with '#object Edit' and zone out and back in.", + id, od.unknown020 + ); + } + else { + c->Message(Chat::White, "ERROR: Unrecognized property name: %s", sep->arg[3]); + return; + } + + // Repop object to have it reflect the change. + app = new EQApplicationPacket(); + o->CreateDeSpawnPacket(app); + entity_list.QueueClients(0, app); + safe_delete(app); + + app = new EQApplicationPacket(); + o->CreateSpawnPacket(app); + entity_list.QueueClients(0, app); + safe_delete(app); + return; + } + + if (strcasecmp(sep->arg[1], "move") == 0) { + + if ((sep->argnum < 2) || // Not enough arguments + ((id = atoi(sep->arg[2])) == 0) || // ID not specified + (((sep->arg[3][0] < '0') || (sep->arg[3][0] > '9')) && ((sep->arg[3][0] & 0xDF) != 'T') && + (sep->arg[3][0] != '-') && (sep->arg[3][0] != '.'))) { // Location argument not specified correctly + c->Message(Chat::White, "Usage: #object Move (ObjectID) ToMe|(x y z [h])"); + return; + } + + if (!(o = entity_list.FindObject(id))) { + std::string query = StringFormat("SELECT zoneid, version, type FROM object WHERE id = %u", id); + auto results = content_db.QueryDatabase(query); + if (!results.Success() || results.RowCount() == 0) { + c->Message(Chat::White, "ERROR: Object %u not found", id); + return; + } + + auto row = results.begin(); + od.zone_id = atoi(row[0]); + od.zone_instance = atoi(row[1]); + od.object_type = atoi(row[2]); + + if (od.zone_id != zone->GetZoneID()) { + c->Message(Chat::White, "ERROR: Object %u is not in this zone", id); + return; + } + + if (od.zone_instance != zone->GetInstanceVersion()) { + c->Message(Chat::White, "ERROR: Object %u is not in this instance version", id); + return; + } + + switch (od.object_type) { + case 0: + c->Message( + Chat::White, "ERROR: Object %u is not yet unlocked for editing. Use '#object Edit' " + "then zone out and back in to move it.", + id + ); + return; + + case staticType: + c->Message( + Chat::White, "ERROR: Object %u has been unlocked for editing, but you must zone out " + "and back in before your client sees the change and will allow you to " + "move it.", + id + ); + return; + + case 1: + c->Message( + Chat::White, "ERROR: Object %u is a temporary spawned object and cannot be " + "manipulated with #object. See the 'ground_spawns' table in the " + "database.", + id + ); + return; + + default: + c->Message(Chat::White, "ERROR: Object %u not located in zone.", id); + return; + } + } + + // Move To Me + if ((sep->arg[3][0] & 0xDF) == 'T') { + od.x = c->GetX(); + od.y = c->GetY(); + od.z = c->GetZ() - + (c->GetSize() * + 0.625f); // Compensate for #loc bumping up Z coordinate by 62.5% of character's size. + + o->SetHeading(c->GetHeading()); + + // Bump player back to avoid getting stuck inside object + + x2 = 10.0f * std::sin(c->GetHeading() / 256.0f * 3.14159265f); + y2 = 10.0f * std::cos(c->GetHeading() / 256.0f * 3.14159265f); + c->MovePC(c->GetX() - x2, c->GetY() - y2, c->GetZ(), c->GetHeading()); + } // Move to x, y, z [h] + else { + od.x = atof(sep->arg[3]); + if (sep->argnum > 3) { + od.y = atof(sep->arg[4]); + } + else { + o->GetLocation(nullptr, &od.y, nullptr); + } + + if (sep->argnum > 4) { + od.z = atof(sep->arg[5]); + } + else { + o->GetLocation(nullptr, nullptr, &od.z); + } + + if (sep->argnum > 5) { + o->SetHeading(atof(sep->arg[6])); + } + } + + o->SetLocation(od.x, od.y, od.z); + + // Despawn and respawn object to reflect change + app = new EQApplicationPacket(); + o->CreateDeSpawnPacket(app); + entity_list.QueueClients(0, app); + safe_delete(app); + + app = new EQApplicationPacket(); + o->CreateSpawnPacket(app); + entity_list.QueueClients(0, app); + safe_delete(app); + return; + } + + if (strcasecmp(sep->arg[1], "rotate") == 0) { + // Insufficient or invalid arguments + if ((sep->argnum < 3) || ((id = atoi(sep->arg[2])) == 0)) { + c->Message(Chat::White, "Usage: #object Rotate (ObjectID) (Heading, 0-512)"); + return; + } + + if ((o = entity_list.FindObject(id)) == nullptr) { + c->Message( + Chat::White, "ERROR: Object %u not found in zone, or is a static object not yet unlocked with " + "'#object Edit' for editing.", + id + ); + return; + } + + o->SetHeading(atof(sep->arg[3])); + + // Despawn and respawn object to reflect change + app = new EQApplicationPacket(); + o->CreateDeSpawnPacket(app); + entity_list.QueueClients(0, app); + safe_delete(app); + + app = new EQApplicationPacket(); + o->CreateSpawnPacket(app); + entity_list.QueueClients(0, app); + safe_delete(app); + return; + } + + if (strcasecmp(sep->arg[1], "save") == 0) { + // Insufficient or invalid arguments + if ((sep->argnum < 2) || ((id = atoi(sep->arg[2])) == 0)) { + c->Message(Chat::White, "Usage: #object Save (ObjectID)"); + return; + } + + o = entity_list.FindObject(id); + + od.zone_id = 0; + od.zone_instance = 0; + od.object_type = 0; + + // If this ID isn't in the database yet, it's a new object + bNewObject = true; + std::string query = StringFormat("SELECT zoneid, version, type FROM object WHERE id = %u", id); + auto results = content_db.QueryDatabase(query); + if (results.Success() && results.RowCount() != 0) { + auto row = results.begin(); + od.zone_id = atoi(row[0]); + od.zone_instance = atoi(row[1]); + od.object_type = atoi(row[2]); + + // ID already in database. Not a new object. + bNewObject = false; + } + + if (!o) { + // Object not found in zone. Can't save an object we can't see. + + if (bNewObject) { + c->Message(Chat::White, "ERROR: Object %u not found", id); + return; + } + + if (od.zone_id != zone->GetZoneID()) { + c->Message(Chat::White, "ERROR: Wrong Object ID. %u is not part of this zone.", id); + return; + } + + if (od.zone_instance != zone->GetInstanceVersion()) { + c->Message(Chat::White, "ERROR: Wrong Object ID. %u is not part of this instance version.", id); + return; + } + + if (od.object_type == 0) { + c->Message( + Chat::White, "ERROR: Static Object %u has already been committed. Use '#object Edit " + "%u' and zone out and back in to make changes.", + id, id + ); + return; + } + + if (od.object_type == 1) { + c->Message( + Chat::White, "ERROR: Object %u is a temporarily spawned ground spawn or dropped item, " + "which is not supported with #object. See the 'ground_spawns' table in " + "the database.", + id + ); + return; + } + + c->Message(Chat::White, "ERROR: Object %u not found.", id); + return; + } + + // Oops! Another GM already saved an object with our id from another zone. + // We'll have to get a new one. + if ((od.zone_id > 0) && (od.zone_id != zone->GetZoneID())) { + id = 0; + } + + // Oops! Another GM already saved an object with our id from another instance. + // We'll have to get a new one. + if ((id > 0) && (od.zone_instance != zone->GetInstanceVersion())) { + id = 0; + } + + // If we're asking for a new ID, it's a new object. + bNewObject |= (id == 0); + + o->GetObjectData(&od); + od.object_type = o->GetType(); + icon = o->GetIcon(); + + // We're committing to the database now. Return temporary object type to actual. + if (od.object_type == staticType) { + od.object_type = 0; + } + + if (!bNewObject) { + query = StringFormat( + "UPDATE object SET zoneid = %u, version = %u, " + "xpos = %.1f, ypos=%.1f, zpos=%.1f, heading=%.1f, " + "objectname = '%s', type = %u, icon = %u, " + "unknown08 = %u, unknown10 = %u, unknown20 = %u " + "WHERE ID = %u", + zone->GetZoneID(), zone->GetInstanceVersion(), od.x, od.y, od.z, + od.heading, od.object_name, od.object_type, icon, od.size, + od.solidtype, od.unknown020, id + ); + } + else if (id == 0) { + query = StringFormat( + "INSERT INTO object " + "(zoneid, version, xpos, ypos, zpos, heading, objectname, " + "type, icon, unknown08, unknown10, unknown20) " + "VALUES (%u, %u, %.1f, %.1f, %.1f, %.1f, '%s', %u, %u, %u, %u, %u)", + zone->GetZoneID(), zone->GetInstanceVersion(), od.x, od.y, od.z, + od.heading, od.object_name, od.object_type, icon, od.size, + od.solidtype, od.unknown020 + ); + } + else { + query = StringFormat( + "INSERT INTO object " + "(id, zoneid, version, xpos, ypos, zpos, heading, objectname, " + "type, icon, unknown08, unknown10, unknown20) " + "VALUES (%u, %u, %u, %.1f, %.1f, %.1f, %.1f, '%s', %u, %u, %u, %u, %u)", + id, zone->GetZoneID(), zone->GetInstanceVersion(), od.x, od.y, od.z, + od.heading, od.object_name, od.object_type, icon, od.size, + od.solidtype, od.unknown020 + ); + } + + results = content_db.QueryDatabase(query); + if (!results.Success()) { + c->Message(Chat::White, "Database Error: %s", results.ErrorMessage().c_str()); + return; + } + + if (results.RowsAffected() == 0) { + // No change made, but no error message given + c->Message(Chat::White, "Database Error: Could not save change to Object %u", id); + return; + } + + if (bNewObject) { + if (newid == results.LastInsertedID()) { + c->Message(Chat::White, "Saved new Object %u to database", id); + return; + } + + c->Message(Chat::White, "Saved Object. NOTE: Database returned a new ID number for object: %u", newid); + id = newid; + return; + } + + c->Message(Chat::White, "Saved changes to Object %u", id); + newid = id; + + if (od.object_type == 0) { + // Static Object - Respawn as nonfunctional door + + app = new EQApplicationPacket(); + o->CreateDeSpawnPacket(app); + entity_list.QueueClients(0, app); + safe_delete(app); + + entity_list.RemoveObject(o->GetID()); + + auto door = DoorsRepository::NewEntity(); + + door.zone = zone->GetShortName(); + + door.id = 1000000000 + id; // Out of range of normal use for doors.id + door.doorid = -1; // Client doesn't care if these are all the same door_id + door.pos_x = od.x; + door.pos_y = od.y; + door.pos_z = od.z; + door.heading = od.heading; + + door.name = od.object_name; + + // Strip trailing "_ACTORDEF" if present. Client won't accept it for doors. + int pos = door.name.size() - strlen("_ACTORDEF"); + if (pos > 0 && door.name.compare(pos, std::string::npos, "_ACTORDEF") == 0) { + door.name.erase(pos); + } + + door.dest_zone = "NONE"; + + if ((door.size = od.size) == 0) { // unknown08 = optional size percentage + door.size = 100; + } + + door.opentype = od.solidtype; + + switch (door.opentype) // unknown10 = optional request_nonsolid (0 or 1 or experimental number) + { + case 0: + door.opentype = 31; + break; + + case 1: + door.opentype = 9; + break; + } + + door.incline = od.unknown020; // unknown20 = optional incline value + door.client_version_mask = 0xFFFFFFFF; + + Doors *doors = new Doors(door); + + entity_list.AddDoor(doors); + + app = new EQApplicationPacket(OP_SpawnDoor, sizeof(Door_Struct)); + ds = (Door_Struct *) app->pBuffer; + + memset(ds, 0, sizeof(Door_Struct)); + memcpy(ds->name, door.name.c_str(), 32); + ds->xPos = door.pos_x; + ds->yPos = door.pos_y; + ds->zPos = door.pos_z; + ds->heading = door.heading; + ds->incline = door.incline; + ds->size = door.size; + ds->doorId = door.doorid; + ds->opentype = door.opentype; + ds->unknown0052[9] = 1; // *ptr-1 and *ptr-3 from EntityList::MakeDoorSpawnPacket() + ds->unknown0052[11] = 1; + + entity_list.QueueClients(0, app); + safe_delete(app); + + c->Message( + Chat::White, "NOTE: Object %u is now a static object, and is unchangeable. To make future " + "changes, use '#object Edit' to convert it to a changeable form, then zone out " + "and back in.", + id + ); + } + return; + } + + if (strcasecmp(sep->arg[1], "copy") == 0) { + // Insufficient or invalid arguments + if ((sep->argnum < 3) || + (((sep->arg[2][0] & 0xDF) != 'A') && ((sep->arg[2][0] < '0') || (sep->arg[2][0] > '9')))) { + c->Message(Chat::White, "Usage: #object Copy All|(ObjectID) (InstanceVersion)"); + c->Message(Chat::White, "- Note: Only objects saved in the database can be copied to another instance."); + return; + } + + od.zone_instance = atoi(sep->arg[3]); + + if (od.zone_instance == zone->GetInstanceVersion()) { + c->Message(Chat::White, "ERROR: Source and destination instance versions are the same."); + return; + } + + if ((sep->arg[2][0] & 0xDF) == 'A') { + // Copy All + + std::string query = + StringFormat( + "INSERT INTO object " + "(zoneid, version, xpos, ypos, zpos, heading, itemid, " + "objectname, type, icon, unknown08, unknown10, unknown20) " + "SELECT zoneid, %u, xpos, ypos, zpos, heading, itemid, " + "objectname, type, icon, unknown08, unknown10, unknown20 " + "FROM object WHERE zoneid = %u) AND version = %u", + od.zone_instance, zone->GetZoneID(), zone->GetInstanceVersion()); + auto results = content_db.QueryDatabase(query); + if (!results.Success()) { + c->Message(Chat::White, "Database Error: %s", results.ErrorMessage().c_str()); + return; + } + + c->Message( + Chat::White, "Copied %u object%s into instance version %u", results.RowCount(), + (results.RowCount() == 1) ? "" : "s", od.zone_instance + ); + return; + } + + id = atoi(sep->arg[2]); + + std::string query = StringFormat( + "INSERT INTO object " + "(zoneid, version, xpos, ypos, zpos, heading, itemid, " + "objectname, type, icon, unknown08, unknown10, unknown20) " + "SELECT zoneid, %u, xpos, ypos, zpos, heading, itemid, " + "objectname, type, icon, unknown08, unknown10, unknown20 " + "FROM object WHERE id = %u AND zoneid = %u AND version = %u", + od.zone_instance, id, zone->GetZoneID(), zone->GetInstanceVersion()); + auto results = content_db.QueryDatabase(query); + if (results.Success() && results.RowsAffected() > 0) { + c->Message(Chat::White, "Copied Object %u into instance version %u", id, od.zone_instance); + return; + } + + // Couldn't copy the object. + + // got an error message + if (!results.Success()) { + c->Message(Chat::White, "Database Error: %s", results.ErrorMessage().c_str()); + return; + } + + // No database error returned. See if we can figure out why. + + query = StringFormat("SELECT zoneid, version FROM object WHERE id = %u", id); + results = content_db.QueryDatabase(query); + if (!results.Success()) { + return; + } + + if (results.RowCount() == 0) { + c->Message(Chat::White, "ERROR: Object %u not found", id); + return; + } + + auto row = results.begin(); + // Wrong ZoneID? + if (atoi(row[0]) != zone->GetZoneID()) { + c->Message(Chat::White, "ERROR: Object %u is not part of this zone.", id); + return; + } + + // Wrong Instance Version? + if (atoi(row[1]) != zone->GetInstanceVersion()) { + c->Message(Chat::White, "ERROR: Object %u is not part of this instance version.", id); + return; + } + + // Well, NO clue at this point. Just let 'em know something screwed up. + c->Message( + Chat::White, "ERROR: Unknown database error copying Object %u to instance version %u", id, + od.zone_instance + ); + return; + } + + if (strcasecmp(sep->arg[1], "delete") == 0) { + + if ((sep->argnum < 2) || ((id = atoi(sep->arg[2])) <= 0)) { + c->Message( + Chat::White, "Usage: #object Delete (ObjectID) -- NOTE: Object deletions are permanent and " + "cannot be undone!" + ); + return; + } + + o = entity_list.FindObject(id); + + if (o) { + // Object found in zone. + + app = new EQApplicationPacket(); + o->CreateDeSpawnPacket(app); + entity_list.QueueClients(nullptr, app); + + entity_list.RemoveObject(o->GetID()); + + // Verifying ZoneID and Version in case someone else ended up adding an object with our ID + // from a different zone/version. Don't want to delete someone else's work. + std::string query = StringFormat( + "DELETE FROM object " + "WHERE id = %u AND zoneid = %u " + "AND version = %u LIMIT 1", + id, zone->GetZoneID(), zone->GetInstanceVersion()); + auto results = content_db.QueryDatabase(query); + + c->Message(Chat::White, "Object %u deleted", id); + return; + } + + // Object not found in zone. + std::string query = StringFormat( + "SELECT type FROM object " + "WHERE id = %u AND zoneid = %u " + "AND version = %u LIMIT 1", + id, zone->GetZoneID(), zone->GetInstanceVersion()); + auto results = content_db.QueryDatabase(query); + if (!results.Success()) { + return; + } + + if (results.RowCount() == 0) { + c->Message(Chat::White, "ERROR: Object %u not found in this zone or instance!", id); + return; + } + + auto row = results.begin(); + + switch (atoi(row[0])) { + case 0: // Static Object + query = StringFormat( + "DELETE FROM object WHERE id = %u " + "AND zoneid = %u AND version = %u LIMIT 1", + id, zone->GetZoneID(), zone->GetInstanceVersion()); + results = content_db.QueryDatabase(query); + + c->Message( + Chat::White, "Object %u deleted. NOTE: This static object will remain for anyone currently in " + "the zone until they next zone out and in.", + id + ); + return; + + case 1: // Temporary Spawn + c->Message( + Chat::White, "ERROR: Object %u is a temporarily spawned ground spawn or dropped item, which " + "is not supported with #object. See the 'ground_spawns' table in the database.", + id + ); + return; + } + + return; + } + + if (strcasecmp(sep->arg[1], "undo") == 0) { + // Insufficient or invalid arguments + if ((sep->argnum < 2) || ((id = atoi(sep->arg[2])) == 0)) { + c->Message( + Chat::White, "Usage: #object Undo (ObjectID) -- Reload object from database, undoing any " + "changes you have made" + ); + return; + } + + o = entity_list.FindObject(id); + + if (!o) { + c->Message( + Chat::White, "ERROR: Object %u not found in zone in a manipulable form. No changes to undo.", + id + ); + return; + } + + if (o->GetType() == OT_DROPPEDITEM) { + c->Message( + Chat::White, "ERROR: Object %u is a temporary spawned item and cannot be manipulated with " + "#object. See the 'ground_spawns' table in the database.", + id + ); + return; + } + + // Despawn current item for reloading from database + app = new EQApplicationPacket(); + o->CreateDeSpawnPacket(app); + entity_list.QueueClients(0, app); + entity_list.RemoveObject(o->GetID()); + safe_delete(app); + + std::string query = StringFormat( + "SELECT xpos, ypos, zpos, " + "heading, objectname, type, icon, " + "unknown08, unknown10, unknown20 " + "FROM object WHERE id = %u", + id + ); + auto results = content_db.QueryDatabase(query); + if (!results.Success() || results.RowCount() == 0) { + c->Message(Chat::White, "Database Error: %s", results.ErrorMessage().c_str()); + return; + } + + memset(&od, 0, sizeof(od)); + + auto row = results.begin(); + + od.x = atof(row[0]); + od.y = atof(row[1]); + od.z = atof(row[2]); + od.heading = atof(row[3]); + strn0cpy(od.object_name, row[4], sizeof(od.object_name)); + od.object_type = atoi(row[5]); + icon = atoi(row[6]); + od.size = atoi(row[7]); + od.solidtype = atoi(row[8]); + od.unknown020 = atoi(row[9]); + + if (od.object_type == 0) { + od.object_type = staticType; + } + + o = new Object(id, od.object_type, icon, od, nullptr); + entity_list.AddObject(o, true); + + c->Message(Chat::White, "Object %u reloaded from database.", id); + return; + } + + c->Message(Chat::White, usage_string); +} + diff --git a/zone/gm_commands/oocmute.cpp b/zone/gm_commands/oocmute.cpp new file mode 100755 index 000000000..a1e1199a2 --- /dev/null +++ b/zone/gm_commands/oocmute.cpp @@ -0,0 +1,18 @@ +#include "../client.h" +#include "../worldserver.h" + +extern WorldServer worldserver; + +void command_oocmute(Client *c, const Seperator *sep) +{ + if (sep->arg[1][0] == 0 || !(sep->arg[1][0] == '1' || sep->arg[1][0] == '0')) { + c->Message(Chat::White, "Usage: #oocmute [1/0]"); + } + else { + auto outapp = new ServerPacket(ServerOP_OOCMute, 1); + *(outapp->pBuffer) = atoi(sep->arg[1]); + worldserver.SendPacket(outapp); + safe_delete(outapp); + } +} + diff --git a/zone/gm_commands/opcode.cpp b/zone/gm_commands/opcode.cpp new file mode 100755 index 000000000..8c8601e96 --- /dev/null +++ b/zone/gm_commands/opcode.cpp @@ -0,0 +1,11 @@ +#include "../client.h" +#include "../../common/patches/patches.h" + +void command_opcode(Client *c, const Seperator *sep) +{ + if (!strcasecmp(sep->arg[1], "reload")) { + ReloadAllPatches(); + c->Message(Chat::White, "Opcodes for all patches have been reloaded"); + } +} + diff --git a/zone/gm_commands/path.cpp b/zone/gm_commands/path.cpp new file mode 100755 index 000000000..69604e271 --- /dev/null +++ b/zone/gm_commands/path.cpp @@ -0,0 +1,27 @@ +#include "../client.h" + +void command_path(Client *c, const Seperator *sep) +{ + if (zone->pathing) { + zone->pathing->DebugCommand(c, sep); + } +} + +void Client::Undye() +{ + for (int cur_slot = EQ::textures::textureBegin; cur_slot <= EQ::textures::LastTexture; cur_slot++) { + uint8 slot2 = SlotConvert(cur_slot); + EQ::ItemInstance *inst = m_inv.GetItem(slot2); + + if (inst != nullptr) { + inst->SetColor(inst->GetItem()->Color); + database.SaveInventory(CharacterID(), inst, slot2); + } + + m_pp.item_tint.Slot[cur_slot].Color = 0; + SendWearChange(cur_slot); + } + + database.DeleteCharacterDye(this->CharacterID()); +} + diff --git a/zone/gm_commands/peekinv.cpp b/zone/gm_commands/peekinv.cpp new file mode 100755 index 000000000..fe6956191 --- /dev/null +++ b/zone/gm_commands/peekinv.cpp @@ -0,0 +1,332 @@ +#include "../client.h" +#include "../object.h" + +void command_peekinv(Client *c, const Seperator *sep) +{ + // this can be cleaned up once inventory is cleaned up + enum { + peekNone = 0x0000, + peekEquip = 0x0001, + peekGen = 0x0002, + peekCursor = 0x0004, + peekLimbo = 0x0008, + peekTrib = 0x0010, + peekBank = 0x0020, + peekShBank = 0x0040, + peekTrade = 0x0080, + peekWorld = 0x0100, + peekOutOfScope = (peekWorld * 2) // less than + }; + + static const char *scope_prefix[] = {"equip", "gen", "cursor", "limbo", "trib", "bank", "shbank", "trade", "world"}; + + static const int16 scope_range[][2] = { + {EQ::invslot::EQUIPMENT_BEGIN, EQ::invslot::EQUIPMENT_END}, + {EQ::invslot::GENERAL_BEGIN, EQ::invslot::GENERAL_END}, + {EQ::invslot::slotCursor, EQ::invslot::slotCursor}, + {EQ::invslot::SLOT_INVALID, EQ::invslot::SLOT_INVALID}, + {EQ::invslot::TRIBUTE_BEGIN, EQ::invslot::TRIBUTE_END}, + {EQ::invslot::BANK_BEGIN, EQ::invslot::BANK_END}, + {EQ::invslot::SHARED_BANK_BEGIN, EQ::invslot::SHARED_BANK_END}, + {EQ::invslot::TRADE_BEGIN, EQ::invslot::TRADE_END}, + {EQ::invslot::SLOT_BEGIN, (EQ::invtype::WORLD_SIZE - 1)} + }; + + static const bool scope_bag[] = {false, true, true, true, false, true, true, true, true}; + + if (!c) { + return; + } + + if (c->GetTarget() && !c->GetTarget()->IsClient()) { + c->Message(Chat::White, "You must target a PC for this command."); + return; + } + + int scopeMask = peekNone; + + if (strcasecmp(sep->arg[1], "all") == 0) { scopeMask = (peekOutOfScope - 1); } + else if (strcasecmp(sep->arg[1], "equip") == 0) { scopeMask |= peekEquip; } + else if (strcasecmp(sep->arg[1], "gen") == 0) { scopeMask |= peekGen; } + else if (strcasecmp(sep->arg[1], "cursor") == 0) { scopeMask |= peekCursor; } + else if (strcasecmp(sep->arg[1], "poss") == 0) { scopeMask |= (peekEquip | peekGen | peekCursor); } + else if (strcasecmp(sep->arg[1], "limbo") == 0) { scopeMask |= peekLimbo; } + else if (strcasecmp(sep->arg[1], "curlim") == 0) { scopeMask |= (peekCursor | peekLimbo); } + else if (strcasecmp(sep->arg[1], "trib") == 0) { scopeMask |= peekTrib; } + else if (strcasecmp(sep->arg[1], "bank") == 0) { scopeMask |= peekBank; } + else if (strcasecmp(sep->arg[1], "shbank") == 0) { scopeMask |= peekShBank; } + else if (strcasecmp(sep->arg[1], "allbank") == 0) { scopeMask |= (peekBank | peekShBank); } + else if (strcasecmp(sep->arg[1], "trade") == 0) { scopeMask |= peekTrade; } + else if (strcasecmp(sep->arg[1], "world") == 0) { scopeMask |= peekWorld; } + + if (!scopeMask) { + c->Message( + Chat::White, + "Usage: #peekinv [equip|gen|cursor|poss|limbo|curlim|trib|bank|shbank|allbank|trade|world|all]" + ); + c->Message(Chat::White, "- Displays a portion of the targeted user's inventory"); + c->Message(Chat::White, "- Caution: 'all' is a lot of information!"); + return; + } + + Client *targetClient = c; + if (c->GetTarget()) { + targetClient = c->GetTarget()->CastToClient(); + } + + const EQ::ItemInstance *inst_main = nullptr; + const EQ::ItemInstance *inst_sub = nullptr; + const EQ::ItemInstance *inst_aug = nullptr; + const EQ::ItemData *item_data = nullptr; + + EQ::SayLinkEngine linker; + linker.SetLinkType(EQ::saylink::SayLinkItemInst); + + c->Message(Chat::White, "Displaying inventory for %s...", targetClient->GetName()); + + Object *objectTradeskill = targetClient->GetTradeskillObject(); + + bool itemsFound = false; + + for (int scopeIndex = 0, scopeBit = peekEquip; scopeBit < peekOutOfScope; ++scopeIndex, scopeBit <<= 1) { + if (scopeBit & ~scopeMask) { + continue; + } + + if (scopeBit & peekWorld) { + if (objectTradeskill == nullptr) { + c->Message(Chat::Default, "No world tradeskill object selected..."); + continue; + } + else { + c->Message( + Chat::White, + "[WorldObject DBID: %i (entityid: %i)]", + objectTradeskill->GetDBID(), + objectTradeskill->GetID()); + } + } + + for (int16 indexMain = scope_range[scopeIndex][0]; indexMain <= scope_range[scopeIndex][1]; ++indexMain) { + if (indexMain == EQ::invslot::SLOT_INVALID) { + continue; + } + + inst_main = ((scopeBit & peekWorld) ? objectTradeskill->GetItem(indexMain) : targetClient->GetInv().GetItem( + indexMain + )); + if (inst_main) { + itemsFound = true; + item_data = inst_main->GetItem(); + } + else { + item_data = nullptr; + } + + linker.SetItemInst(inst_main); + + c->Message( + (item_data == nullptr), + "%sSlot: %i, Item: %i (%s), Charges: %i", + scope_prefix[scopeIndex], + ((scopeBit & peekWorld) ? (EQ::invslot::WORLD_BEGIN + indexMain) : indexMain), + ((item_data == nullptr) ? 0 : item_data->ID), + linker.GenerateLink().c_str(), + ((inst_main == nullptr) ? 0 : inst_main->GetCharges()) + ); + + if (inst_main && inst_main->IsClassCommon()) { + for (uint8 indexAug = EQ::invaug::SOCKET_BEGIN; indexAug <= EQ::invaug::SOCKET_END; ++indexAug) { + inst_aug = inst_main->GetItem(indexAug); + if (!inst_aug) { // extant only + continue; + } + + item_data = inst_aug->GetItem(); + linker.SetItemInst(inst_aug); + + c->Message( + (item_data == nullptr), + ".%sAugSlot: %i (Slot #%i, Aug idx #%i), Item: %i (%s), Charges: %i", + scope_prefix[scopeIndex], + INVALID_INDEX, + ((scopeBit & peekWorld) ? (EQ::invslot::WORLD_BEGIN + indexMain) : indexMain), + indexAug, + ((item_data == nullptr) ? 0 : item_data->ID), + linker.GenerateLink().c_str(), + ((inst_sub == nullptr) ? 0 : inst_sub->GetCharges()) + ); + } + } + + if (!scope_bag[scopeIndex] || !(inst_main && inst_main->IsClassBag())) { + continue; + } + + for (uint8 indexSub = EQ::invbag::SLOT_BEGIN; indexSub <= EQ::invbag::SLOT_END; ++indexSub) { + inst_sub = inst_main->GetItem(indexSub); + if (!inst_sub) { // extant only + continue; + } + + item_data = inst_sub->GetItem(); + linker.SetItemInst(inst_sub); + + c->Message( + (item_data == nullptr), + "..%sBagSlot: %i (Slot #%i, Bag idx #%i), Item: %i (%s), Charges: %i", + scope_prefix[scopeIndex], + ((scopeBit & peekWorld) ? INVALID_INDEX : EQ::InventoryProfile::CalcSlotId(indexMain, indexSub)), + ((scopeBit & peekWorld) ? (EQ::invslot::WORLD_BEGIN + indexMain) : indexMain), + indexSub, + ((item_data == nullptr) ? 0 : item_data->ID), + linker.GenerateLink().c_str(), + ((inst_sub == nullptr) ? 0 : inst_sub->GetCharges()) + ); + + if (inst_sub->IsClassCommon()) { + for (uint8 indexAug = EQ::invaug::SOCKET_BEGIN; indexAug <= EQ::invaug::SOCKET_END; ++indexAug) { + inst_aug = inst_sub->GetItem(indexAug); + if (!inst_aug) { // extant only + continue; + } + + item_data = inst_aug->GetItem(); + linker.SetItemInst(inst_aug); + + c->Message( + (item_data == nullptr), + "...%sAugSlot: %i (Slot #%i, Sub idx #%i, Aug idx #%i), Item: %i (%s), Charges: %i", + scope_prefix[scopeIndex], + INVALID_INDEX, + ((scopeBit & peekWorld) ? INVALID_INDEX : EQ::InventoryProfile::CalcSlotId( + indexMain, + indexSub + )), + indexSub, + indexAug, + ((item_data == nullptr) ? 0 : item_data->ID), + linker.GenerateLink().c_str(), + ((inst_sub == nullptr) ? 0 : inst_sub->GetCharges()) + ); + } + } + } + } + + if (scopeBit & peekLimbo) { + int limboIndex = 0; + for (auto it = targetClient->GetInv().cursor_cbegin(); + (it != targetClient->GetInv().cursor_cend()); + ++it, ++limboIndex) { + if (it == targetClient->GetInv().cursor_cbegin()) { + continue; + } + + inst_main = *it; + if (inst_main) { + itemsFound = true; + item_data = inst_main->GetItem(); + } + else { + item_data = nullptr; + } + + linker.SetItemInst(inst_main); + + c->Message( + (item_data == nullptr), + "%sSlot: %i, Item: %i (%s), Charges: %i", + scope_prefix[scopeIndex], + (8000 + limboIndex), + ((item_data == nullptr) ? 0 : item_data->ID), + linker.GenerateLink().c_str(), + ((inst_main == nullptr) ? 0 : inst_main->GetCharges()) + ); + + if (inst_main && inst_main->IsClassCommon()) { + for (uint8 indexAug = EQ::invaug::SOCKET_BEGIN; indexAug <= EQ::invaug::SOCKET_END; ++indexAug) { + inst_aug = inst_main->GetItem(indexAug); + if (!inst_aug) { // extant only + continue; + } + + item_data = inst_aug->GetItem(); + linker.SetItemInst(inst_aug); + + c->Message( + (item_data == nullptr), + ".%sAugSlot: %i (Slot #%i, Aug idx #%i), Item: %i (%s), Charges: %i", + scope_prefix[scopeIndex], + INVALID_INDEX, + (8000 + limboIndex), + indexAug, + ((item_data == nullptr) ? 0 : item_data->ID), + linker.GenerateLink().c_str(), + ((inst_sub == nullptr) ? 0 : inst_sub->GetCharges()) + ); + } + } + + if (!scope_bag[scopeIndex] || !(inst_main && inst_main->IsClassBag())) { + continue; + } + + for (uint8 indexSub = EQ::invbag::SLOT_BEGIN; indexSub <= EQ::invbag::SLOT_END; ++indexSub) { + inst_sub = inst_main->GetItem(indexSub); + if (!inst_sub) { + continue; + } + + item_data = (inst_sub == nullptr) ? nullptr : inst_sub->GetItem(); + + linker.SetItemInst(inst_sub); + + c->Message( + (item_data == nullptr), + "..%sBagSlot: %i (Slot #%i, Bag idx #%i), Item: %i (%s), Charges: %i", + scope_prefix[scopeIndex], + INVALID_INDEX, + (8000 + limboIndex), + indexSub, + ((item_data == nullptr) ? 0 : item_data->ID), + linker.GenerateLink().c_str(), + ((inst_sub == nullptr) ? 0 : inst_sub->GetCharges()) + ); + + if (inst_sub->IsClassCommon()) { + for (uint8 indexAug = EQ::invaug::SOCKET_BEGIN; + indexAug <= EQ::invaug::SOCKET_END; + ++indexAug) { + inst_aug = inst_sub->GetItem(indexAug); + if (!inst_aug) { // extant only + continue; + } + + item_data = inst_aug->GetItem(); + linker.SetItemInst(inst_aug); + + c->Message( + (item_data == nullptr), + "...%sAugSlot: %i (Slot #%i, Sub idx #%i, Aug idx #%i), Item: %i (%s), Charges: %i", + scope_prefix[scopeIndex], + INVALID_INDEX, + (8000 + limboIndex), + indexSub, + indexAug, + ((item_data == nullptr) ? 0 : item_data->ID), + linker.GenerateLink().c_str(), + ((inst_sub == nullptr) ? 0 : inst_sub->GetCharges()) + ); + } + } + } + } + } + } + + if (!itemsFound) { + c->Message(Chat::White, "No items found."); + } +} + diff --git a/zone/gm_commands/peqzone.cpp b/zone/gm_commands/peqzone.cpp new file mode 100755 index 000000000..3c94965fc --- /dev/null +++ b/zone/gm_commands/peqzone.cpp @@ -0,0 +1,110 @@ +#include "../client.h" + +void command_peqzone(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments) { + c->Message(Chat::White, "Usage: #peqzone [Zone ID] or #peqzone [Zone Short Name]"); + return; + } + + auto reuse_timer = RuleI(Zone, PEQZoneReuseTime); + if (reuse_timer) { + uint32 time_left = c->GetPTimers().GetRemainingTime(pTimerPeqzoneReuse); + if (!c->GetPTimers().Expired(&database, pTimerPeqzoneReuse, false)) { + c->Message( + Chat::White, + fmt::format( + "You must wait {} before using this command again.", + ConvertSecondsToTime(time_left) + ).c_str() + ); + return; + } + } + + auto hp_ratio = RuleI(Zone, PEQZoneHPRatio); + if (c->GetHPRatio() < hp_ratio) { + c->Message( + Chat::White, + fmt::format( + "You cannot use this command with less than {}%% health.", + hp_ratio + ).c_str() + ); + return; + } + + if ( + c->IsInvisible(c) || + c->IsRooted() || + c->IsStunned() || + c->IsMezzed() || + c->AutoAttackEnabled() || + c->GetInvul() + ) { + c->Message(Chat::White, "You cannot use this command in your current state. Settle down and wait."); + return; + } + + auto zone_id = ( + sep->IsNumber(1) ? + static_cast(std::stoul(sep->arg[1])) : + static_cast(ZoneID(sep->arg[1])) + ); + auto zone_short_name = ZoneName(zone_id); + auto zone_long_name = ZoneLongName(zone_id); + if ( + !zone_id || + !zone_short_name + ) { + c->Message( + Chat::White, + fmt::format( + "No zones were found matching '{}'.", + sep->arg[1] + ).c_str() + ); + return; + } + + bool allows_peqzone = ( + content_db.GetPEQZone(zone_id, 0) ? + true : + false + ); + if (!allows_peqzone) { + c->Message( + Chat::White, + fmt::format( + "You cannot use this command to enter {} ({}).", + zone_long_name, + zone_short_name + ).c_str() + ); + return; + } + + if (zone_id == zone->GetZoneID()) { + c->Message( + Chat::White, + fmt::format( + "You are already in {} ({}).", + zone->GetLongName(), + zone->GetShortName() + ).c_str() + ); + return; + } + + if (RuleB(Zone, UsePEQZoneDebuffs)) { + c->SpellOnTarget(RuleI(Zone, PEQZoneDebuff1), c); + c->SpellOnTarget(RuleI(Zone, PEQZoneDebuff2), c); + } + + if (reuse_timer) { + c->GetPTimers().Start(pTimerPeqzoneReuse, reuse_timer); + } + + c->MoveZone(zone_short_name); +} diff --git a/zone/gm_commands/permaclass.cpp b/zone/gm_commands/permaclass.cpp new file mode 100755 index 000000000..3aa72c97a --- /dev/null +++ b/zone/gm_commands/permaclass.cpp @@ -0,0 +1,40 @@ +#include "../client.h" + +void command_permaclass(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments || !sep->IsNumber(1)) { + c->Message(Chat::White, "Usage: #permaclass [Class ID]"); + return; + } + + Client *target = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); + } + + auto class_id = std::stoi(sep->arg[1]); + + LogInfo("Class changed by {} for {} to {} ({})", + c->GetCleanName(), + target->GetCleanName(), + GetClassIDName(class_id), + class_id + ); + + target->SetBaseClass(class_id); + target->Save(); + target->Kick("Class was changed."); + + if (c != target) { + c->Message( + Chat::White, + fmt::format( + "Class changed for {} to {} ({}).", + target->GetCleanName(), + GetClassIDName(class_id), + class_id + ).c_str() + ); + } +} diff --git a/zone/gm_commands/permagender.cpp b/zone/gm_commands/permagender.cpp new file mode 100755 index 000000000..bdfd33013 --- /dev/null +++ b/zone/gm_commands/permagender.cpp @@ -0,0 +1,52 @@ +#include "../client.h" + +void command_permagender(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments || !sep->IsNumber(1)) { + c->Message(Chat::White, "Usage: #permagender [Gender ID]"); + c->Message(Chat::White, "Genders: 0 = Male, 1 = Female, 2 = Neuter"); + return; + } + + Client *target = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); + } + + auto gender_id = std::stoi(sep->arg[1]); + if (gender_id < 0 || gender_id > 2) { + c->Message(Chat::White, "Usage: #permagender [Gender ID]"); + c->Message(Chat::White, "Genders: 0 = Male, 1 = Female, 2 = Neuter"); + return; + } + + LogInfo("Gender changed by {} for {} to {} ({})", + c->GetCleanName(), + target->GetCleanName(), + GetGenderName(gender_id), + gender_id + ); + + target->SetBaseGender(gender_id); + target->Save(); + target->SendIllusionPacket(target->GetRace(), gender_id); + + c->Message( + Chat::White, + fmt::format( + "Gender changed for {} to {} ({}).", + ( + c == target ? + "yourself" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ), + GetGenderName(gender_id), + gender_id + ).c_str() + ); +} diff --git a/zone/gm_commands/permarace.cpp b/zone/gm_commands/permarace.cpp new file mode 100755 index 000000000..bee8e6dc2 --- /dev/null +++ b/zone/gm_commands/permarace.cpp @@ -0,0 +1,52 @@ +#include "../client.h" + +void command_permarace(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments || !sep->IsNumber(1)) { + c->Message(Chat::White, "Usage: #permarace [Race ID]"); + c->Message( + Chat::White, + "NOTE: Not all models are global. If a model is not global, it will appear as a human on character select and in zones without the model." + ); + return; + } + + Client *target = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); + } + + auto race_id = std::stoi(sep->arg[1]); + auto gender_id = Mob::GetDefaultGender(race_id, target->GetBaseGender()); + + LogInfo("Race changed by {} for {} to {} ({})", + c->GetCleanName(), + target->GetCleanName(), + GetRaceIDName(race_id), + race_id + ); + + target->SetBaseRace(race_id); + target->SetBaseGender(gender_id); + target->Save(); + target->SendIllusionPacket(race_id, gender_id); + + c->Message( + Chat::White, + fmt::format( + "Race changed for {} to {} ({}).", + ( + c == target ? + "yourself" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ), + GetRaceIDName(race_id), + race_id + ).c_str() + ); +} diff --git a/zone/gm_commands/petitems.cpp b/zone/gm_commands/petitems.cpp new file mode 100644 index 000000000..833b9e276 --- /dev/null +++ b/zone/gm_commands/petitems.cpp @@ -0,0 +1,25 @@ +#include "../client.h" + +void command_petitems(Client *c, const Seperator *sep) +{ + if (!c->GetPet()) { + c->Message(Chat::White, "You must have a pet to use this command."); + return; + } + + auto pet = c->GetPet()->CastToNPC(); + auto loot_list = pet->GetLootList(); + if (!loot_list.empty()) { + pet->QueryLoot(c, true); + c->Message( + Chat::White, + fmt::format( + "Your pet has {} item{}.", + loot_list.size(), + loot_list.size() != 1 ? "s" : "" + ).c_str() + ); + } else { + c->Message(Chat::White, "Your pet has no items."); + } +} diff --git a/zone/gm_commands/petitioninfo.cpp b/zone/gm_commands/petitioninfo.cpp new file mode 100755 index 000000000..1700e4100 --- /dev/null +++ b/zone/gm_commands/petitioninfo.cpp @@ -0,0 +1,39 @@ +#include "../client.h" + +void command_petitioninfo(Client *c, const Seperator *sep) +{ + if (sep->arg[1][0] == 0) { + c->Message(Chat::White, "Usage: #petitioninfo (petition number) Type #listpetition for a list"); + return; + } + + std::string query = "SELECT petid, charname, accountname, zone, charclass, charrace, charlevel FROM petitions ORDER BY petid"; + auto results = database.QueryDatabase(query); + if (!results.Success()) { + return; + } + + LogInfo("Petition information request from [{}], petition number:", c->GetName(), atoi(sep->argplus[1])); + + if (results.RowCount() == 0) { + c->Message(Chat::Red, "There was an error in your request: ID not found! Please check the Id and try again."); + return; + } + + for (auto row = results.begin(); row != results.end(); ++row) + if (strcasecmp(row[0], sep->argplus[1]) == 0) { + c->Message( + Chat::Red, + " ID : %s Character Name: %s Account Name: %s Zone: %s Character Class: %s Character Race: %s Character Level: %s", + row[0], + row[1], + row[2], + row[3], + row[4], + row[5], + row[6] + ); + } + +} + diff --git a/zone/gm_commands/petname.cpp b/zone/gm_commands/petname.cpp new file mode 100755 index 000000000..47eea80c3 --- /dev/null +++ b/zone/gm_commands/petname.cpp @@ -0,0 +1,22 @@ +#include "../client.h" + +void command_petname(Client *c, const Seperator *sep) +{ + Mob *target; + target = c->GetTarget(); + + if (!target) { + c->Message(Chat::White, "Usage: #petname newname (requires a target)"); + } + else if (target->IsPet() && (target->GetOwnerID() == c->GetID()) && strlen(sep->arg[1]) > 0) { + char *oldname = strdup(target->GetName()); + target->TempName(sep->arg[1]); + c->Message(Chat::White, "Renamed %s to %s", oldname, sep->arg[1]); + free(oldname); + } + else { + target->TempName(); + c->Message(Chat::White, "Restored the original name"); + } +} + diff --git a/zone/gm_commands/pf.cpp b/zone/gm_commands/pf.cpp new file mode 100755 index 000000000..5867e346b --- /dev/null +++ b/zone/gm_commands/pf.cpp @@ -0,0 +1,21 @@ +#include "../client.h" + +void command_pf(Client *c, const Seperator *sep) +{ + if (c->GetTarget()) { + Mob *who = c->GetTarget(); + c->Message(Chat::White, "POS: (%.2f, %.2f, %.2f)", who->GetX(), who->GetY(), who->GetZ()); + c->Message( + Chat::White, + "WP: %s (%d/%d)", + to_string(who->GetCurrentWayPoint()).c_str(), + who->IsNPC() ? who->CastToNPC()->GetMaxWp() : -1 + ); + c->Message(Chat::White, "pause=%d RAspeed=%d", who->GetCWPP(), who->GetRunAnimSpeed()); + //who->DumpMovement(c); + } + else { + c->Message(Chat::White, "ERROR: target required"); + } +} + diff --git a/zone/gm_commands/picklock.cpp b/zone/gm_commands/picklock.cpp new file mode 100755 index 000000000..c4b538eff --- /dev/null +++ b/zone/gm_commands/picklock.cpp @@ -0,0 +1,24 @@ +#include "../client.h" + +void command_picklock(Client *c, const Seperator *sep) +{ + Mob *target = c->GetTarget(); + if (!target) { + c->Message(Chat::Red, "You must have a target."); + return; + } + + if (target->IsNPC()) { + if (c->HasSkill(EQ::skills::SkillPickLock)) { + if (DistanceSquaredNoZ(c->GetPosition(), target->GetPosition()) > RuleI(Adventure, LDoNTrapDistanceUse)) { + c->Message(Chat::Red, "%s is too far away.", target->GetCleanName()); + return; + } + c->HandleLDoNPickLock(target->CastToNPC(), c->GetSkill(EQ::skills::SkillPickLock), LDoNTypeMechanical); + } + else { + c->Message(Chat::Red, "You do not have the pick locks skill."); + } + } +} + diff --git a/zone/gm_commands/profanity.cpp b/zone/gm_commands/profanity.cpp new file mode 100755 index 000000000..6774c43c6 --- /dev/null +++ b/zone/gm_commands/profanity.cpp @@ -0,0 +1,74 @@ +#include "../client.h" +#include "../worldserver.h" + +extern WorldServer worldserver; + +#include "../../common/profanity_manager.h" + +void command_profanity(Client *c, const Seperator *sep) +{ + std::string arg1(sep->arg[1]); + + while (true) { + if (arg1.compare("list") == 0) { + // do nothing + } + else if (arg1.compare("clear") == 0) { + EQ::ProfanityManager::DeleteProfanityList(&database); + auto pack = new ServerPacket(ServerOP_RefreshCensorship); + worldserver.SendPacket(pack); + safe_delete(pack); + } + else if (arg1.compare("add") == 0) { + if (!EQ::ProfanityManager::AddProfanity(&database, sep->arg[2])) { + c->Message(Chat::Red, "Could not add '%s' to the profanity list.", sep->arg[2]); + } + auto pack = new ServerPacket(ServerOP_RefreshCensorship); + worldserver.SendPacket(pack); + safe_delete(pack); + } + else if (arg1.compare("del") == 0) { + if (!EQ::ProfanityManager::RemoveProfanity(&database, sep->arg[2])) { + c->Message(Chat::Red, "Could not delete '%s' from the profanity list.", sep->arg[2]); + } + auto pack = new ServerPacket(ServerOP_RefreshCensorship); + worldserver.SendPacket(pack); + safe_delete(pack); + } + else if (arg1.compare("reload") == 0) { + if (!EQ::ProfanityManager::UpdateProfanityList(&database)) { + c->Message(Chat::Red, "Could not reload the profanity list."); + } + auto pack = new ServerPacket(ServerOP_RefreshCensorship); + worldserver.SendPacket(pack); + safe_delete(pack); + } + else { + break; + } + + std::string popup; + const auto &list = EQ::ProfanityManager::GetProfanityList(); + for (const auto &iter : list) { + popup.append(iter); + popup.append("
"); + } + if (list.empty()) { + popup.append("** Censorship Inactive **
"); + } + else { + popup.append("** End of List **
"); + } + + c->SendPopupToClient("Profanity List", popup.c_str()); + + return; + } + + c->Message(Chat::White, "Usage: #profanity [list] - shows profanity list"); + c->Message(Chat::White, "Usage: #profanity [clear] - deletes all entries"); + c->Message(Chat::White, "Usage: #profanity [add] [] - adds entry"); + c->Message(Chat::White, "Usage: #profanity [del] [] - deletes entry"); + c->Message(Chat::White, "Usage: #profanity [reload] - reloads profanity list"); +} + diff --git a/zone/gm_commands/profilereset.cpp b/zone/gm_commands/profilereset.cpp new file mode 100755 index 000000000..0f98dae23 --- /dev/null +++ b/zone/gm_commands/profilereset.cpp @@ -0,0 +1,8 @@ +#include "../client.h" + +void command_profilereset(Client *c, const Seperator *sep) +{ + ResetZoneProfile(); +} +#endif + diff --git a/zone/gm_commands/proximity.cpp b/zone/gm_commands/proximity.cpp new file mode 100755 index 000000000..3db0a714e --- /dev/null +++ b/zone/gm_commands/proximity.cpp @@ -0,0 +1,74 @@ +#include "../client.h" + +void command_proximity(Client *c, const Seperator *sep) +{ + if (!c->GetTarget() || (c->GetTarget() && !c->GetTarget()->IsNPC())) { + c->Message(Chat::White, "You must target an NPC"); + return; + } + + for (auto &iter : entity_list.GetNPCList()) { + auto npc = iter.second; + std::string name = npc->GetName(); + + if (name.find("Proximity") != std::string::npos) { + npc->Depop(); + } + } + + NPC *npc = c->GetTarget()->CastToNPC(); + + std::vector points; + + FindPerson_Point p{}; + + if (npc->IsProximitySet()) { + glm::vec4 position; + position.w = npc->GetHeading(); + position.x = npc->GetProximityMinX(); + position.y = npc->GetProximityMinY(); + position.z = npc->GetZ(); + + position.x = npc->GetProximityMinX(); + position.y = npc->GetProximityMinY(); + NPC::SpawnNodeNPC("Proximity", "", position); + + position.x = npc->GetProximityMinX(); + position.y = npc->GetProximityMaxY(); + NPC::SpawnNodeNPC("Proximity", "", position); + + position.x = npc->GetProximityMaxX(); + position.y = npc->GetProximityMinY(); + NPC::SpawnNodeNPC("Proximity", "", position); + + position.x = npc->GetProximityMaxX(); + position.y = npc->GetProximityMaxY(); + NPC::SpawnNodeNPC("Proximity", "", position); + + p.x = npc->GetProximityMinX(); + p.y = npc->GetProximityMinY(); + p.z = npc->GetZ(); + points.push_back(p); + + p.x = npc->GetProximityMinX(); + p.y = npc->GetProximityMaxY(); + points.push_back(p); + + p.x = npc->GetProximityMaxX(); + p.y = npc->GetProximityMaxY(); + points.push_back(p); + + p.x = npc->GetProximityMaxX(); + p.y = npc->GetProximityMinY(); + points.push_back(p); + + p.x = npc->GetProximityMinX(); + p.y = npc->GetProximityMinY(); + points.push_back(p); + } + + if (c->ClientVersion() >= EQ::versions::ClientVersion::RoF) { + c->SendPathPacket(points); + } +} + diff --git a/zone/gm_commands/push.cpp b/zone/gm_commands/push.cpp new file mode 100755 index 000000000..6ca548640 --- /dev/null +++ b/zone/gm_commands/push.cpp @@ -0,0 +1,35 @@ +#include "../client.h" +#include "../fastmath.h" + +extern FastMath g_Math; + +void command_push(Client *c, const Seperator *sep) +{ + Mob *t = c; + if (c->GetTarget() != nullptr) { + t = c->GetTarget(); + } + + if (!sep->arg[1] || !sep->IsNumber(1)) { + c->Message(Chat::White, "ERROR: Must provide at least a push back."); + return; + } + + float back = atof(sep->arg[1]); + float up = 0.0f; + + if (sep->arg[2] && sep->IsNumber(2)) { + up = atof(sep->arg[2]); + } + + if (t->IsNPC()) { + t->IncDeltaX(back * g_Math.FastSin(c->GetHeading())); + t->IncDeltaY(back * g_Math.FastCos(c->GetHeading())); + t->IncDeltaZ(up); + t->SetForcedMovement(6); + } + else if (t->IsClient()) { + // TODO: send packet to push + } +} + diff --git a/zone/gm_commands/pvp.cpp b/zone/gm_commands/pvp.cpp new file mode 100755 index 000000000..5ba3d092a --- /dev/null +++ b/zone/gm_commands/pvp.cpp @@ -0,0 +1,28 @@ +#include "../client.h" + +void command_pvp(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments) { + c->Message(Chat::White, "Usage: #pvp [On|Off]"); + return; + } + + bool pvp_state = atobool(sep->arg[1]); + Client* target = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); + } + + target->SetPVP(pvp_state); + if (c != target) { + c->Message( + Chat::White, + fmt::format( + "{} now follows the ways of {}.", + target->GetCleanName(), + pvp_state ? "Discord" : "Order" + ).c_str() + ); + } +} diff --git a/zone/gm_commands/qglobal.cpp b/zone/gm_commands/qglobal.cpp new file mode 100755 index 000000000..7625a2604 --- /dev/null +++ b/zone/gm_commands/qglobal.cpp @@ -0,0 +1,62 @@ +#include "../client.h" + +void command_qglobal(Client *c, const Seperator *sep) +{ + //In-game switch for qglobal column + if (sep->arg[1][0] == 0) { + c->Message(Chat::White, "Syntax: #qglobal [on/off/view]. Requires NPC target."); + return; + } + + Mob *target = c->GetTarget(); + + if (!target || !target->IsNPC()) { + c->Message(Chat::Red, "NPC Target Required!"); + return; + } + + if (!strcasecmp(sep->arg[1], "on")) { + std::string query = StringFormat( + "UPDATE npc_types SET qglobal = 1 WHERE id = '%i'", + target->GetNPCTypeID()); + auto results = content_db.QueryDatabase(query); + if (!results.Success()) { + c->Message(Chat::Yellow, "Could not update database."); + return; + } + + c->Message(Chat::Yellow, "Success! Changes take effect on zone reboot."); + return; + } + + if (!strcasecmp(sep->arg[1], "off")) { + std::string query = StringFormat( + "UPDATE npc_types SET qglobal = 0 WHERE id = '%i'", + target->GetNPCTypeID()); + auto results = content_db.QueryDatabase(query); + if (!results.Success()) { + c->Message(Chat::Yellow, "Could not update database."); + return; + } + + c->Message(Chat::Yellow, "Success! Changes take effect on zone reboot."); + return; + } + + if (!strcasecmp(sep->arg[1], "view")) { + const NPCType *type = content_db.LoadNPCTypesData(target->GetNPCTypeID()); + if (!type) { + c->Message(Chat::Yellow, "Invalid NPC type."); + } + else if (type->qglobal) { + c->Message(Chat::Yellow, "This NPC has quest globals active."); + } + else { + c->Message(Chat::Yellow, "This NPC has quest globals disabled."); + } + return; + } + + c->Message(Chat::Yellow, "Invalid action specified."); +} + diff --git a/zone/gm_commands/questerrors.cpp b/zone/gm_commands/questerrors.cpp new file mode 100755 index 000000000..cbc35e599 --- /dev/null +++ b/zone/gm_commands/questerrors.cpp @@ -0,0 +1,23 @@ +#include "../client.h" +#include "../quest_parser_collection.h" + +void command_questerrors(Client *c, const Seperator *sep) +{ + std::list err; + parse->GetErrors(err); + c->Message(Chat::White, "Current Quest Errors:"); + + auto iter = err.begin(); + int i = 0; + while (iter != err.end()) { + if (i >= 30) { + c->Message(Chat::White, "Maximum of 30 Errors shown..."); + break; + } + + c->Message(Chat::White, iter->c_str()); + ++i; + ++iter; + } +} + diff --git a/zone/gm_commands/race.cpp b/zone/gm_commands/race.cpp new file mode 100755 index 000000000..2729965b1 --- /dev/null +++ b/zone/gm_commands/race.cpp @@ -0,0 +1,29 @@ +#include "../client.h" + +void command_race(Client *c, const Seperator *sep) +{ + Mob *target = c->CastToMob(); + + if (sep->IsNumber(1)) { + auto race = atoi(sep->arg[1]); + if ((race >= 0 && race <= RuleI(NPC, MaxRaceID)) || (race >= 2253 && race <= 2259)) { + if ((c->GetTarget()) && c->Admin() >= commandRaceOthers) { + target = c->GetTarget(); + } + target->SendIllusionPacket(race); + } + else { + c->Message( + Chat::White, + fmt::format( + "Usage: #race [0-{}, 2253-2259] (0 for back to normal)", + RuleI(NPC, MaxRaceID)).c_str()); + } + } + else { + c->Message( + Chat::White, + fmt::format("Usage: #race [0-{}, 2253-2259] (0 for back to normal)", RuleI(NPC, MaxRaceID)).c_str()); + } +} + diff --git a/zone/gm_commands/raidloot.cpp b/zone/gm_commands/raidloot.cpp new file mode 100755 index 000000000..9dfb1c21c --- /dev/null +++ b/zone/gm_commands/raidloot.cpp @@ -0,0 +1,70 @@ +#include "../client.h" +#include "../groups.h" +#include "../raids.h" +#include "../raids.h" + +void command_raidloot(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments) { + c->Message(Chat::White, "Usage: #raidloot [All|GroupLeader|RaidLeader|Selected]"); + return; + } + + auto client_raid = c->GetRaid(); + if (!client_raid) { + c->Message(Chat::White, "You must be in a Raid to use this command."); + return; + } + + if (!client_raid->IsLeader(c)) { + c->Message(Chat::White, "You must be the Raid Leader to use this command."); + return; + } + + std::string raid_loot_type = str_tolower(sep->arg[1]); + bool is_all = raid_loot_type.find("all") != std::string::npos; + bool is_group_leader = raid_loot_type.find("groupleader") != std::string::npos; + bool is_raid_leader = raid_loot_type.find("raidleader") != std::string::npos; + bool is_selected = raid_loot_type.find("selected") != std::string::npos; + if ( + !is_all && + !is_group_leader && + !is_raid_leader && + !is_selected + ) { + c->Message(Chat::White, "Usage: #raidloot [All|GroupLeader|RaidLeader|Selected]"); + return; + } + + std::map loot_types = { + {RaidLootTypes::All, "All"}, + {RaidLootTypes::GroupLeader, "GroupLeader"}, + {RaidLootTypes::RaidLeader, "RaidLeader"}, + {RaidLootTypes::Selected, "Selected"} + }; + + uint32 loot_type; + if (is_all) { + loot_type = RaidLootTypes::All; + } + else if (is_group_leader) { + loot_type = RaidLootTypes::GroupLeader; + } + else if (is_raid_leader) { + loot_type = RaidLootTypes::RaidLeader; + } + else if (is_selected) { + loot_type = RaidLootTypes::Selected; + } + + c->Message( + Chat::White, + fmt::format( + "Loot type changed to {} ({}).", + loot_types[loot_type], + loot_type + ).c_str() + ); +} + diff --git a/zone/gm_commands/randomfeatures.cpp b/zone/gm_commands/randomfeatures.cpp new file mode 100755 index 000000000..9b2c85c10 --- /dev/null +++ b/zone/gm_commands/randomfeatures.cpp @@ -0,0 +1,18 @@ +#include "../client.h" + +void command_randomfeatures(Client *c, const Seperator *sep) +{ + Mob *target = c->GetTarget(); + if (!target) { + c->Message(Chat::White, "Error: This command requires a target"); + } + else { + if (target->RandomizeFeatures()) { + c->Message(Chat::White, "Features Randomized"); + } + else { + c->Message(Chat::White, "This command requires a Playable Race as the target"); + } + } +} + diff --git a/zone/gm_commands/refreshgroup.cpp b/zone/gm_commands/refreshgroup.cpp new file mode 100755 index 000000000..243997141 --- /dev/null +++ b/zone/gm_commands/refreshgroup.cpp @@ -0,0 +1,19 @@ +#include "../client.h" +#include "../groups.h" + +void command_refreshgroup(Client *c, const Seperator *sep) +{ + if (!c) { + return; + } + + Group *g = c->GetGroup(); + + if (!g) { + return; + } + + database.RefreshGroupFromDB(c); + //g->SendUpdate(7, c); +} + diff --git a/zone/gm_commands/reloadaa.cpp b/zone/gm_commands/reloadaa.cpp new file mode 100755 index 000000000..5df40b105 --- /dev/null +++ b/zone/gm_commands/reloadaa.cpp @@ -0,0 +1,17 @@ +#include "../client.h" +#include "../../common/file_util.h" + +void command_reloadaa(Client *c, const Seperator *sep) +{ + c->Message(Chat::White, "Reloading Alternate Advancement Data..."); + zone->LoadAlternateAdvancement(); + c->Message(Chat::White, "Alternate Advancement Data Reloaded"); + entity_list.SendAlternateAdvancementStats(); +} + +inline bool file_exists(const std::string &name) +{ + std::ifstream f(name.c_str()); + return f.good(); +} + diff --git a/zone/gm_commands/reloadallrules.cpp b/zone/gm_commands/reloadallrules.cpp new file mode 100755 index 000000000..d5fb8b757 --- /dev/null +++ b/zone/gm_commands/reloadallrules.cpp @@ -0,0 +1,16 @@ +#include "../client.h" +#include "../worldserver.h" + +extern WorldServer worldserver; + +void command_reloadallrules(Client *c, const Seperator *sep) +{ + if (c) { + auto pack = new ServerPacket(ServerOP_ReloadRules, 0); + worldserver.SendPacket(pack); + c->Message(Chat::Red, "Successfully sent the packet to world to reload rules globally. (including world)"); + safe_delete(pack); + + } +} + diff --git a/zone/gm_commands/reloadcontentflags.cpp b/zone/gm_commands/reloadcontentflags.cpp new file mode 100755 index 000000000..2098fa329 --- /dev/null +++ b/zone/gm_commands/reloadcontentflags.cpp @@ -0,0 +1,15 @@ +#include "../client.h" +#include "../worldserver.h" + +extern WorldServer worldserver; + +void command_reloadcontentflags(Client *c, const Seperator *sep) +{ + if (c) { + auto pack = new ServerPacket(ServerOP_ReloadContentFlags, 0); + worldserver.SendPacket(pack); + c->Message(Chat::Red, "Successfully sent the packet to world to reload content flags globally."); + safe_delete(pack); + } +} + diff --git a/zone/gm_commands/reloademote.cpp b/zone/gm_commands/reloademote.cpp new file mode 100755 index 000000000..3003776e4 --- /dev/null +++ b/zone/gm_commands/reloademote.cpp @@ -0,0 +1,9 @@ +#include "../client.h" + +void command_reloademote(Client *c, const Seperator *sep) +{ + zone->NPCEmoteList.Clear(); + zone->LoadNPCEmotes(&zone->NPCEmoteList); + c->Message(Chat::White, "NPC emotes reloaded."); +} + diff --git a/zone/gm_commands/reloadlevelmods.cpp b/zone/gm_commands/reloadlevelmods.cpp new file mode 100755 index 000000000..29ce28c11 --- /dev/null +++ b/zone/gm_commands/reloadlevelmods.cpp @@ -0,0 +1,15 @@ +#include "../client.h" + +void command_reloadlevelmods(Client *c, const Seperator *sep) +{ + if (sep->arg[1][0] == 0) { + if (RuleB(Zone, LevelBasedEXPMods)) { + zone->LoadLevelEXPMods(); + c->Message(Chat::Yellow, "Level based EXP Mods have been reloaded zonewide"); + } + else { + c->Message(Chat::Yellow, "Level based EXP Mods are disabled in rules!"); + } + } +} + diff --git a/zone/gm_commands/reloadmerchants.cpp b/zone/gm_commands/reloadmerchants.cpp new file mode 100755 index 000000000..560551682 --- /dev/null +++ b/zone/gm_commands/reloadmerchants.cpp @@ -0,0 +1,8 @@ +#include "../client.h" + +void command_reloadmerchants(Client *c, const Seperator *sep) +{ + entity_list.ReloadMerchants(); + c->Message(Chat::Yellow, "Reloading merchants."); +} + diff --git a/zone/gm_commands/reloadperlexportsettings.cpp b/zone/gm_commands/reloadperlexportsettings.cpp new file mode 100755 index 000000000..1c3137728 --- /dev/null +++ b/zone/gm_commands/reloadperlexportsettings.cpp @@ -0,0 +1,16 @@ +#include "../client.h" +#include "../worldserver.h" + +extern WorldServer worldserver; + +void command_reloadperlexportsettings(Client *c, const Seperator *sep) +{ + if (c) { + auto pack = new ServerPacket(ServerOP_ReloadPerlExportSettings, 0); + worldserver.SendPacket(pack); + c->Message(Chat::Red, "Successfully sent the packet to world to reload Perl Export settings"); + safe_delete(pack); + + } +} + diff --git a/zone/gm_commands/reloadqst.cpp b/zone/gm_commands/reloadqst.cpp new file mode 100755 index 000000000..48909377a --- /dev/null +++ b/zone/gm_commands/reloadqst.cpp @@ -0,0 +1,23 @@ +#include "../client.h" +#include "../quest_parser_collection.h" + +void command_reloadqst(Client *c, const Seperator *sep) +{ + bool stop_timers = false; + + if (sep->IsNumber(1)) { + stop_timers = std::stoi(sep->arg[1]) != 0 ? true : false; + } + + std::string stop_timers_message = stop_timers ? " and stopping timers" : ""; + c->Message( + Chat::White, + fmt::format( + "Clearing quest memory cache{}.", + stop_timers_message + ).c_str() + ); + entity_list.ClearAreas(); + parse->ReloadQuests(stop_timers); +} + diff --git a/zone/gm_commands/reloadstatic.cpp b/zone/gm_commands/reloadstatic.cpp new file mode 100755 index 000000000..39666f724 --- /dev/null +++ b/zone/gm_commands/reloadstatic.cpp @@ -0,0 +1,8 @@ +#include "../client.h" + +void command_reloadstatic(Client *c, const Seperator *sep) +{ + c->Message(Chat::White, "Reloading zone static data..."); + zone->ReloadStaticData(); +} + diff --git a/zone/gm_commands/reloadtitles.cpp b/zone/gm_commands/reloadtitles.cpp new file mode 100755 index 000000000..f9143b966 --- /dev/null +++ b/zone/gm_commands/reloadtitles.cpp @@ -0,0 +1,14 @@ +#include "../client.h" +#include "../worldserver.h" + +extern WorldServer worldserver; + +void command_reloadtitles(Client *c, const Seperator *sep) +{ + auto pack = new ServerPacket(ServerOP_ReloadTitles, 0); + worldserver.SendPacket(pack); + safe_delete(pack); + c->Message(Chat::Yellow, "Player Titles Reloaded."); + +} + diff --git a/zone/gm_commands/reloadtraps.cpp b/zone/gm_commands/reloadtraps.cpp new file mode 100755 index 000000000..1057ec771 --- /dev/null +++ b/zone/gm_commands/reloadtraps.cpp @@ -0,0 +1,8 @@ +#include "../client.h" + +void command_reloadtraps(Client *c, const Seperator *sep) +{ + entity_list.UpdateAllTraps(true, true); + c->Message(Chat::Default, "Traps reloaded for %s.", zone->GetShortName()); +} + diff --git a/zone/gm_commands/reloadworld.cpp b/zone/gm_commands/reloadworld.cpp new file mode 100755 index 000000000..f87574d79 --- /dev/null +++ b/zone/gm_commands/reloadworld.cpp @@ -0,0 +1,22 @@ +#include "../client.h" +#include "../worldserver.h" + +extern WorldServer worldserver; + +void command_reloadworld(Client *c, const Seperator *sep) +{ + int world_repop = atoi(sep->arg[1]); + if (world_repop == 0) { + c->Message(Chat::White, "Reloading quest cache worldwide."); + } + else { + c->Message(Chat::White, "Reloading quest cache and repopping zones worldwide."); + } + + auto pack = new ServerPacket(ServerOP_ReloadWorld, sizeof(ReloadWorld_Struct)); + ReloadWorld_Struct *RW = (ReloadWorld_Struct *) pack->pBuffer; + RW->Option = world_repop; + worldserver.SendPacket(pack); + safe_delete(pack); +} + diff --git a/zone/gm_commands/reloadworldrules.cpp b/zone/gm_commands/reloadworldrules.cpp new file mode 100755 index 000000000..8e1b12ff3 --- /dev/null +++ b/zone/gm_commands/reloadworldrules.cpp @@ -0,0 +1,15 @@ +#include "../client.h" +#include "../worldserver.h" + +extern WorldServer worldserver; + +void command_reloadworldrules(Client *c, const Seperator *sep) +{ + if (c) { + auto pack = new ServerPacket(ServerOP_ReloadRulesWorld, 0); + worldserver.SendPacket(pack); + c->Message(Chat::Red, "Successfully sent the packet to world to reload rules. (only world)"); + safe_delete(pack); + } +} + diff --git a/zone/gm_commands/reloadzps.cpp b/zone/gm_commands/reloadzps.cpp new file mode 100755 index 000000000..353499d88 --- /dev/null +++ b/zone/gm_commands/reloadzps.cpp @@ -0,0 +1,8 @@ +#include "../client.h" + +void command_reloadzps(Client *c, const Seperator *sep) +{ + content_db.LoadStaticZonePoints(&zone->zone_point_list, zone->GetShortName(), zone->GetInstanceVersion()); + c->Message(Chat::White, "Reloading server zone_points."); +} + diff --git a/zone/gm_commands/removeitem.cpp b/zone/gm_commands/removeitem.cpp new file mode 100644 index 000000000..6285e65d4 --- /dev/null +++ b/zone/gm_commands/removeitem.cpp @@ -0,0 +1,85 @@ +#include "../client.h" + +void command_removeitem(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments || !sep->IsNumber(1)) { + c->Message(Chat::White, "Usage: #removeitem [Item ID] [Amount]"); + return; + } + + Client *target = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); + } + + auto target_string = ( + c == target ? + "yourself" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ); + + auto item_id = std::stoi(sep->arg[1]); + if (!database.GetItem(item_id)) { + c->Message( + Chat::White, + fmt::format( + "Item ID {} could not be found.", + item_id + ).c_str() + ); + return; + } + + auto item_link = database.CreateItemLink(item_id); + auto amount = sep->IsNumber(2) ? std::stoul(sep->arg[2]) : 1; + auto item_count = target->CountItem(item_id); + if (item_count) { + if (item_count >= amount) { + target->RemoveItem(item_id, amount); + + c->Message( + Chat::White, + fmt::format( + "Removed {} {} ({}) from {}.", + amount, + item_link, + item_id, + target_string + ).c_str() + ); + } else { + target->RemoveItem(item_id, item_count); + + c->Message( + Chat::White, + fmt::format( + "Removed {} {} ({}) from {} because {} did not have {} {} ({}).", + item_count, + item_link, + item_id, + target_string, + c == target ? "you" : "they", + amount, + item_link, + item_id + ).c_str() + ); + } + } else { + c->Message( + Chat::White, + fmt::format( + "Could not find any {} ({}) to delete from {}.", + database.CreateItemLink(item_id), + item_id, + target_string + ).c_str() + ); + } +} + diff --git a/zone/gm_commands/repop.cpp b/zone/gm_commands/repop.cpp new file mode 100755 index 000000000..114ef6fa5 --- /dev/null +++ b/zone/gm_commands/repop.cpp @@ -0,0 +1,40 @@ +#include "../client.h" + +void command_repop(Client *c, const Seperator *sep) +{ + int timearg = 1; + int delay = 0; + + if (sep->arg[1] && strcasecmp(sep->arg[1], "force") == 0) { + timearg++; + + LinkedListIterator iterator(zone->spawn2_list); + iterator.Reset(); + while (iterator.MoreElements()) { + std::string query = StringFormat( + "DELETE FROM respawn_times WHERE id = %lu AND instance_id = %lu", + (unsigned long) iterator.GetData()->GetID(), + (unsigned long) zone->GetInstanceID() + ); + auto results = database.QueryDatabase(query); + iterator.Advance(); + } + c->Message(Chat::White, "Zone depop: Force resetting spawn timers."); + } + + if (!sep->IsNumber(timearg)) { + c->Message(Chat::White, "Zone depopped - repopping now."); + + zone->Repop(); + + /* Force a spawn2 timer trigger so we don't delay actually spawning the NPC's */ + zone->spawn2_timer.Trigger(); + return; + } + + c->Message(Chat::White, "Zone depoped. Repop in %i seconds", atoi(sep->arg[timearg])); + zone->Repop(atoi(sep->arg[timearg]) * 1000); + + zone->spawn2_timer.Trigger(); +} + diff --git a/zone/gm_commands/resetaa.cpp b/zone/gm_commands/resetaa.cpp new file mode 100755 index 000000000..d83ba6beb --- /dev/null +++ b/zone/gm_commands/resetaa.cpp @@ -0,0 +1,13 @@ +#include "../client.h" + +void command_resetaa(Client *c, const Seperator *sep) +{ + if (c->GetTarget() && c->GetTarget()->IsClient()) { + c->GetTarget()->CastToClient()->ResetAA(); + c->Message(Chat::Red, "Successfully reset %s's AAs", c->GetTarget()->GetName()); + } + else { + c->Message(Chat::White, "Usage: Target a client and use #resetaa to reset the AA data in their Profile."); + } +} + diff --git a/zone/gm_commands/resetaa_timer.cpp b/zone/gm_commands/resetaa_timer.cpp new file mode 100755 index 000000000..452ed4266 --- /dev/null +++ b/zone/gm_commands/resetaa_timer.cpp @@ -0,0 +1,26 @@ +#include "../client.h" + +void command_resetaa_timer(Client *c, const Seperator *sep) +{ + Client *target = nullptr; + if (!c->GetTarget() || !c->GetTarget()->IsClient()) { + target = c; + } + else { + target = c->GetTarget()->CastToClient(); + } + + if (sep->IsNumber(1)) { + int timer_id = atoi(sep->arg[1]); + c->Message(Chat::White, "Reset of timer %i for %s", timer_id, c->GetName()); + c->ResetAlternateAdvancementTimer(timer_id); + } + else if (!strcasecmp(sep->arg[1], "all")) { + c->Message(Chat::White, "Reset all timers for %s", c->GetName()); + c->ResetAlternateAdvancementTimers(); + } + else { + c->Message(Chat::White, "usage: #resetaa_timer [all | timer_id]"); + } +} + diff --git a/zone/gm_commands/resetdisc_timer.cpp b/zone/gm_commands/resetdisc_timer.cpp new file mode 100755 index 000000000..0c3cfe267 --- /dev/null +++ b/zone/gm_commands/resetdisc_timer.cpp @@ -0,0 +1,23 @@ +#include "../client.h" + +void command_resetdisc_timer(Client *c, const Seperator *sep) +{ + Client *target = c->GetTarget()->CastToClient(); + if (!c->GetTarget() || !c->GetTarget()->IsClient()) { + target = c; + } + + if (sep->IsNumber(1)) { + int timer_id = atoi(sep->arg[1]); + c->Message(Chat::White, "Reset of disc timer %i for %s", timer_id, c->GetName()); + c->ResetDisciplineTimer(timer_id); + } + else if (!strcasecmp(sep->arg[1], "all")) { + c->Message(Chat::White, "Reset all disc timers for %s", c->GetName()); + c->ResetAllDisciplineTimers(); + } + else { + c->Message(Chat::White, "usage: #resetdisc_timer [all | timer_id]"); + } +} + diff --git a/zone/gm_commands/revoke.cpp b/zone/gm_commands/revoke.cpp new file mode 100755 index 000000000..34ee10ee3 --- /dev/null +++ b/zone/gm_commands/revoke.cpp @@ -0,0 +1,48 @@ +#include "../client.h" +#include "../worldserver.h" + +extern WorldServer worldserver; + +void command_revoke(Client *c, const Seperator *sep) +{ + if (sep->arg[1][0] == 0 || sep->arg[2][0] == 0) { + c->Message(Chat::White, "Usage: #revoke [charname] [1/0]"); + return; + } + + uint32 characterID = database.GetAccountIDByChar(sep->arg[1]); + if (characterID == 0) { + c->Message(Chat::Red, "Character does not exist."); + return; + } + + int flag = sep->arg[2][0] == '1' ? true : false; + std::string query = StringFormat("UPDATE account SET revoked = %d WHERE id = %i", flag, characterID); + auto results = database.QueryDatabase(query); + + c->Message( + Chat::Red, + "%s account number %i with the character %s.", + flag ? "Revoking" : "Unrevoking", + characterID, + sep->arg[1] + ); + + Client *revokee = entity_list.GetClientByAccID(characterID); + if (revokee) { + c->Message(Chat::White, "Found %s in this zone.", revokee->GetName()); + revokee->SetRevoked(flag); + return; + } + + c->Message(Chat::Red, "#revoke: Couldn't find %s in this zone, passing request to worldserver.", sep->arg[1]); + + auto outapp = new ServerPacket(ServerOP_Revoke, sizeof(RevokeStruct)); + RevokeStruct *revoke = (RevokeStruct *) outapp->pBuffer; + strn0cpy(revoke->adminname, c->GetName(), 64); + strn0cpy(revoke->name, sep->arg[1], 64); + revoke->toggle = flag; + worldserver.SendPacket(outapp); + safe_delete(outapp); +} + diff --git a/zone/gm_commands/roambox.cpp b/zone/gm_commands/roambox.cpp new file mode 100755 index 000000000..3c31f2312 --- /dev/null +++ b/zone/gm_commands/roambox.cpp @@ -0,0 +1,93 @@ +#include "../client.h" +#include "../groups.h" + +void command_roambox(Client *c, const Seperator *sep) +{ + std::string arg1 = sep->arg[1]; + + Mob *target = c->GetTarget(); + if (!target || !target->IsNPC()) { + c->Message(Chat::Red, "You need a valid NPC target for this command"); + return; + } + + NPC *npc = dynamic_cast(target); + int spawn_group_id = npc->GetSpawnGroupId(); + if (spawn_group_id <= 0) { + c->Message(Chat::Red, "NPC needs a valid SpawnGroup!"); + return; + } + + if (arg1 == "set") { + int box_size = (sep->arg[2] ? atoi(sep->arg[2]) : 0); + int delay = (sep->arg[3] ? atoi(sep->arg[3]) : 15000); + if (box_size > 0) { + std::string query = fmt::format( + SQL( + UPDATE spawngroup SET + dist = {}, + min_x = {}, + max_x = {}, + min_y = {}, + max_y = {}, + delay = {} + WHERE id = {} + ), + (box_size / 2), + npc->GetX() - (box_size / 2), + npc->GetX() + (box_size / 2), + npc->GetY() - (box_size / 2), + npc->GetY() + (box_size / 2), + delay, + spawn_group_id + ); + + database.QueryDatabase(query); + + c->Message( + Chat::Yellow, + "NPC (%s) Roam Box set to box size of [%i] SpawnGroupId [%i] delay [%i]", + npc->GetCleanName(), + box_size, + spawn_group_id, + delay + ); + + return; + } + + c->Message(Chat::Red, "Box size must be set!"); + } + + if (arg1 == "remove") { + std::string query = fmt::format( + SQL( + UPDATE spawngroup SET + dist = 0, + min_x = 0, + max_x = 0, + min_y = 0, + max_y = 0, + delay = 0 + WHERE id = {} + ), + spawn_group_id + ); + + database.QueryDatabase(query); + + c->Message( + Chat::Yellow, + "NPC (%s) Roam Box has been removed from SpawnGroupID [%i]", + npc->GetCleanName(), + spawn_group_id + ); + + return; + } + + c->Message(Chat::Yellow, "> Command Usage"); + c->Message(Chat::Yellow, "#roambox set box_size [delay = 0]"); + c->Message(Chat::Yellow, "#roambox remove"); +} + diff --git a/zone/gm_commands/rules.cpp b/zone/gm_commands/rules.cpp new file mode 100755 index 000000000..65ef8bc37 --- /dev/null +++ b/zone/gm_commands/rules.cpp @@ -0,0 +1,232 @@ +#include "../client.h" + +void command_rules(Client *c, const Seperator *sep) +{ + //super-command for managing rules settings + if (sep->arg[1][0] == '\0' || !strcasecmp(sep->arg[1], "help")) { + c->Message(Chat::White, "Syntax: #rules [subcommand]."); + c->Message(Chat::White, "-- Rule Set Manipulation --"); + c->Message(Chat::White, "...listsets - List avaliable rule sets"); + c->Message(Chat::White, "...current - gives the name of the ruleset currently running in this zone"); + c->Message(Chat::White, "...reload - Reload the selected ruleset in this zone"); + c->Message(Chat::White, "...switch (ruleset name) - Change the selected ruleset and load it"); + c->Message( + Chat::White, + "...load (ruleset name) - Load a ruleset in just this zone without changing the selected set" + ); +//too lazy to write this right now: +// c->Message(Chat::White, "...wload (ruleset name) - Load a ruleset in all zones without changing the selected set"); + c->Message(Chat::White, "...store [ruleset name] - Store the running ruleset as the specified name"); + c->Message(Chat::White, "---------------------"); + c->Message(Chat::White, "-- Running Rule Manipulation --"); + c->Message(Chat::White, "...reset - Reset all rules to their default values"); + c->Message(Chat::White, "...get [rule] - Get the specified rule's local value"); + c->Message(Chat::White, "...set (rule) (value) - Set the specified rule to the specified value locally only"); + c->Message( + Chat::White, + "...setdb (rule) (value) - Set the specified rule to the specified value locally and in the DB" + ); + c->Message( + Chat::White, + "...list [catname] - List all rules in the specified category (or all categiries if omitted)" + ); + c->Message(Chat::White, "...values [catname] - List the value of all rules in the specified category"); + return; + } + + if (!strcasecmp(sep->arg[1], "current")) { + c->Message( + Chat::White, "Currently running ruleset '%s' (%d)", RuleManager::Instance()->GetActiveRuleset(), + RuleManager::Instance()->GetActiveRulesetID()); + } + else if (!strcasecmp(sep->arg[1], "listsets")) { + std::map sets; + if (!RuleManager::Instance()->ListRulesets(&database, sets)) { + c->Message(Chat::Red, "Failed to list rule sets!"); + return; + } + + c->Message(Chat::White, "Avaliable rule sets:"); + std::map::iterator cur, end; + cur = sets.begin(); + end = sets.end(); + for (; cur != end; ++cur) { + c->Message(Chat::White, "(%d) %s", cur->first, cur->second.c_str()); + } + } + else if (!strcasecmp(sep->arg[1], "reload")) { + RuleManager::Instance()->LoadRules(&database, RuleManager::Instance()->GetActiveRuleset(), true); + c->Message( + Chat::White, "The active ruleset (%s (%d)) has been reloaded", RuleManager::Instance()->GetActiveRuleset(), + RuleManager::Instance()->GetActiveRulesetID()); + } + else if (!strcasecmp(sep->arg[1], "switch")) { + //make sure this is a valid rule set.. + int rsid = RuleManager::Instance()->GetRulesetID(&database, sep->arg[2]); + if (rsid < 0) { + c->Message(Chat::Red, "Unknown rule set '%s'", sep->arg[2]); + return; + } + if (!database.SetVariable("RuleSet", sep->arg[2])) { + c->Message(Chat::Red, "Failed to update variables table to change selected rule set"); + return; + } + + //TODO: we likely want to reload this ruleset everywhere... + RuleManager::Instance()->LoadRules(&database, sep->arg[2], true); + + c->Message( + Chat::White, + "The selected ruleset has been changed to (%s (%d)) and reloaded locally", + sep->arg[2], + rsid + ); + } + else if (!strcasecmp(sep->arg[1], "load")) { + //make sure this is a valid rule set.. + int rsid = RuleManager::Instance()->GetRulesetID(&database, sep->arg[2]); + if (rsid < 0) { + c->Message(Chat::Red, "Unknown rule set '%s'", sep->arg[2]); + return; + } + RuleManager::Instance()->LoadRules(&database, sep->arg[2], true); + c->Message(Chat::White, "Loaded ruleset '%s' (%d) locally", sep->arg[2], rsid); + } + else if (!strcasecmp(sep->arg[1], "store")) { + if (sep->argnum == 1) { + //store current rule set. + RuleManager::Instance()->SaveRules(&database); + c->Message(Chat::White, "Rules saved"); + } + else if (sep->argnum == 2) { + RuleManager::Instance()->SaveRules(&database, sep->arg[2]); + int prersid = RuleManager::Instance()->GetActiveRulesetID(); + int rsid = RuleManager::Instance()->GetRulesetID(&database, sep->arg[2]); + if (rsid < 0) { + c->Message(Chat::Red, "Unable to query ruleset ID after store, it most likely failed."); + } + else { + c->Message(Chat::White, "Stored rules as ruleset '%s' (%d)", sep->arg[2], rsid); + if (prersid != rsid) { + c->Message(Chat::White, "Rule set %s (%d) is now active in this zone", sep->arg[2], rsid); + } + } + } + else { + c->Message(Chat::Red, "Invalid argument count, see help."); + return; + } + } + else if (!strcasecmp(sep->arg[1], "reset")) { + RuleManager::Instance()->ResetRules(true); + c->Message(Chat::White, "The running ruleset has been set to defaults"); + + } + else if (!strcasecmp(sep->arg[1], "get")) { + if (sep->argnum != 2) { + c->Message(Chat::Red, "Invalid argument count, see help."); + return; + } + std::string value; + if (!RuleManager::Instance()->GetRule(sep->arg[2], value)) { + c->Message(Chat::Red, "Unable to find rule %s", sep->arg[2]); + } + else { + c->Message(Chat::White, "%s - %s", sep->arg[2], value.c_str()); + } + + } + else if (!strcasecmp(sep->arg[1], "set")) { + if (sep->argnum != 3) { + c->Message(Chat::Red, "Invalid argument count, see help."); + return; + } + if (!RuleManager::Instance()->SetRule(sep->arg[2], sep->arg[3], nullptr, false, true)) { + c->Message(Chat::Red, "Failed to modify rule"); + } + else { + c->Message(Chat::White, "Rule modified locally."); + } + } + else if (!strcasecmp(sep->arg[1], "setdb")) { + if (sep->argnum != 3) { + c->Message(Chat::Red, "Invalid argument count, see help."); + return; + } + if (!RuleManager::Instance()->SetRule(sep->arg[2], sep->arg[3], &database, true, true)) { + c->Message(Chat::Red, "Failed to modify rule"); + } + else { + c->Message(Chat::White, "Rule modified locally and in the database."); + } + } + else if (!strcasecmp(sep->arg[1], "list")) { + if (sep->argnum == 1) { + std::vector rule_list; + if (!RuleManager::Instance()->ListCategories(rule_list)) { + c->Message(Chat::Red, "Failed to list categories!"); + return; + } + c->Message(Chat::White, "Rule Categories:"); + std::vector::iterator cur, end; + cur = rule_list.begin(); + end = rule_list.end(); + for (; cur != end; ++cur) { + c->Message(Chat::White, " %s", *cur); + } + } + else if (sep->argnum == 2) { + const char *catfilt = nullptr; + if (std::string("all") != sep->arg[2]) { + catfilt = sep->arg[2]; + } + std::vector rule_list; + if (!RuleManager::Instance()->ListRules(catfilt, rule_list)) { + c->Message(Chat::Red, "Failed to list rules!"); + return; + } + c->Message(Chat::White, "Rules in category %s:", sep->arg[2]); + std::vector::iterator cur, end; + cur = rule_list.begin(); + end = rule_list.end(); + for (; cur != end; ++cur) { + c->Message(Chat::White, " %s", *cur); + } + } + else { + c->Message(Chat::Red, "Invalid argument count, see help."); + } + } + else if (!strcasecmp(sep->arg[1], "values")) { + if (sep->argnum != 2) { + c->Message(Chat::Red, "Invalid argument count, see help."); + return; + } + else { + const char *catfilt = nullptr; + if (std::string("all") != sep->arg[2]) { + catfilt = sep->arg[2]; + } + std::vector rule_list; + if (!RuleManager::Instance()->ListRules(catfilt, rule_list)) { + c->Message(Chat::Red, "Failed to list rules!"); + return; + } + c->Message(Chat::White, "Rules & values in category %s:", sep->arg[2]); + std::vector::iterator cur, end; + cur = rule_list.begin(); + end = rule_list.end(); + for (std::string tmp_value; cur != end; ++cur) { + if (RuleManager::Instance()->GetRule(*cur, tmp_value)) { + c->Message(Chat::White, " %s - %s", *cur, tmp_value.c_str()); + } + } + } + + } + else { + c->Message(Chat::Yellow, "Invalid action specified. use '#rules help' for help"); + } +} + + diff --git a/zone/gm_commands/save.cpp b/zone/gm_commands/save.cpp new file mode 100755 index 000000000..e22b2cbfd --- /dev/null +++ b/zone/gm_commands/save.cpp @@ -0,0 +1,33 @@ +#include "../client.h" +#include "../corpse.h" + +void command_save(Client *c, const Seperator *sep) +{ + if (c->GetTarget() == 0) { + c->Message(Chat::White, "Error: no target"); + } + else if (c->GetTarget()->IsClient()) { + if (c->GetTarget()->CastToClient()->Save(2)) { + c->Message(Chat::White, "%s successfully saved.", c->GetTarget()->GetName()); + } + else { + c->Message(Chat::White, "Manual save for %s failed.", c->GetTarget()->GetName()); + } + } + else if (c->GetTarget()->IsPlayerCorpse()) { + if (c->GetTarget()->CastToMob()->Save()) { + c->Message( + Chat::White, + "%s successfully saved. (dbid=%u)", + c->GetTarget()->GetName(), + c->GetTarget()->CastToCorpse()->GetCorpseDBID()); + } + else { + c->Message(Chat::White, "Manual save for %s failed.", c->GetTarget()->GetName()); + } + } + else { + c->Message(Chat::White, "Error: target not a Client/PlayerCorpse"); + } +} + diff --git a/zone/gm_commands/scale.cpp b/zone/gm_commands/scale.cpp new file mode 100755 index 000000000..d260a25e4 --- /dev/null +++ b/zone/gm_commands/scale.cpp @@ -0,0 +1,136 @@ +#include "../client.h" +#include "../npc_scale_manager.h" + +void command_scale(Client *c, const Seperator *sep) +{ + if (sep->argnum == 0) { + c->Message(Chat::Yellow, "# Usage # "); + c->Message(Chat::Yellow, "#scale [static/dynamic] (With targeted NPC)"); + c->Message(Chat::Yellow, "#scale [npc_name_search] [static/dynamic] (To make zone-wide changes)"); + c->Message(Chat::Yellow, "#scale all [static/dynamic]"); + return; + } + + /** + * Targeted changes + */ + if (c->GetTarget() && c->GetTarget()->IsNPC() && sep->argnum < 2) { + NPC *npc = c->GetTarget()->CastToNPC(); + + bool apply_status = false; + if (strcasecmp(sep->arg[1], "dynamic") == 0) { + c->Message(Chat::Yellow, "Applying global base scaling to npc dynamically (All stats set to zeroes)..."); + apply_status = npc_scale_manager->ApplyGlobalBaseScalingToNPCDynamically(npc); + } + else if (strcasecmp(sep->arg[1], "static") == 0) { + c->Message(Chat::Yellow, "Applying global base scaling to npc statically (Copying base stats onto NPC)..."); + apply_status = npc_scale_manager->ApplyGlobalBaseScalingToNPCStatically(npc); + } + else { + return; + } + + if (apply_status) { + c->Message(Chat::Yellow, "Applied to NPC '%s' successfully!", npc->GetName()); + } + else { + c->Message( + Chat::Yellow, "Failed to load scaling data from the database " + "for this npc / type, see 'NPCScaling' log for more info" + ); + } + } + else if (c->GetTarget() && sep->argnum < 2) { + c->Message(Chat::Yellow, "Target must be an npc!"); + } + + /** + * Zonewide + */ + if (sep->argnum > 1) { + + std::string scale_type; + if (strcasecmp(sep->arg[2], "dynamic") == 0) { + scale_type = "dynamic"; + } + else if (strcasecmp(sep->arg[2], "static") == 0) { + scale_type = "static"; + } + + if (scale_type.length() <= 0) { + c->Message( + Chat::Yellow, + "You must first set if you intend on using static versus dynamic for these changes" + ); + c->Message(Chat::Yellow, "#scale [npc_name_search] [static/dynamic]"); + c->Message(Chat::Yellow, "#scale all [static/dynamic]"); + return; + } + + std::string search_string = sep->arg[1]; + + auto &entity_list_search = entity_list.GetNPCList(); + + int found_count = 0; + for (auto &itr : entity_list_search) { + NPC *entity = itr.second; + + std::string entity_name = entity->GetName(); + + /** + * Filter by name + */ + if (search_string.length() > 0 && entity_name.find(search_string) == std::string::npos && + strcasecmp(sep->arg[1], "all") != 0) { + continue; + } + + std::string status = "(Searching)"; + + if (strcasecmp(sep->arg[3], "apply") == 0) { + status = "(Applying)"; + + if (strcasecmp(sep->arg[2], "dynamic") == 0) { + npc_scale_manager->ApplyGlobalBaseScalingToNPCDynamically(entity); + } + if (strcasecmp(sep->arg[2], "static") == 0) { + npc_scale_manager->ApplyGlobalBaseScalingToNPCStatically(entity); + } + } + + c->Message( + 15, + "| ID %5d | %s | x %.0f | y %0.f | z %.0f | DBID %u %s", + entity->GetID(), + entity->GetName(), + entity->GetX(), + entity->GetY(), + entity->GetZ(), + entity->GetNPCTypeID(), + status.c_str() + ); + + found_count++; + } + + if (strcasecmp(sep->arg[3], "apply") == 0) { + c->Message(Chat::Yellow, "%s scaling applied against (%i) NPC's", sep->arg[2], found_count); + } + else { + + std::string saylink = StringFormat( + "#scale %s %s apply", + sep->arg[1], + sep->arg[2] + ); + + c->Message(Chat::Yellow, "Found (%i) NPC's that match this search...", found_count); + c->Message( + Chat::Yellow, "To apply these changes, click <%s> or type %s", + EQ::SayLinkEngine::GenerateQuestSaylink(saylink, false, "Apply").c_str(), + saylink.c_str() + ); + } + } +} + diff --git a/zone/gm_commands/scribespell.cpp b/zone/gm_commands/scribespell.cpp new file mode 100755 index 000000000..5acfd3a8d --- /dev/null +++ b/zone/gm_commands/scribespell.cpp @@ -0,0 +1,66 @@ +#include "../client.h" + +void command_scribespell(Client *c, const Seperator *sep) +{ + uint16 spell_id = 0; + uint16 book_slot = -1; + Client *t = c; + + if (c->GetTarget() && c->GetTarget()->IsClient() && c->GetGM()) { + t = c->GetTarget()->CastToClient(); + } + + if (!sep->arg[1][0]) { + c->Message(Chat::White, "FORMAT: #scribespell "); + return; + } + + spell_id = atoi(sep->arg[1]); + + if (IsValidSpell(spell_id)) { + t->Message(Chat::White, "Scribing spell: %s (%i) to spellbook.", spells[spell_id].name, spell_id); + + if (t != c) { + c->Message(Chat::White, "Scribing spell: %s (%i) for %s.", spells[spell_id].name, spell_id, t->GetName()); + } + + LogInfo("Scribe spell: [{}] ([{}]) request for [{}] from [{}]", + spells[spell_id].name, + spell_id, + t->GetName(), + c->GetName()); + + if (spells[spell_id].classes[WARRIOR] != 0 && spells[spell_id].skill != 52 && + spells[spell_id].classes[t->GetPP().class_ - 1] > 0 && !IsDiscipline(spell_id)) { + book_slot = t->GetNextAvailableSpellBookSlot(); + + if (book_slot >= 0 && t->FindSpellBookSlotBySpellID(spell_id) < 0) { + t->ScribeSpell(spell_id, book_slot); + } + else { + t->Message( + Chat::Red, + "Unable to scribe spell: %s (%i) to your spellbook.", + spells[spell_id].name, + spell_id + ); + + if (t != c) { + c->Message( + Chat::Red, + "Unable to scribe spell: %s (%i) for %s.", + spells[spell_id].name, + spell_id, + t->GetName()); + } + } + } + else { + c->Message(Chat::Red, "Your target can not scribe this spell."); + } + } + else { + c->Message(Chat::Red, "Spell ID: %i is an unknown spell and cannot be scribed.", spell_id); + } +} + diff --git a/zone/gm_commands/scribespells.cpp b/zone/gm_commands/scribespells.cpp new file mode 100755 index 000000000..2d084d9df --- /dev/null +++ b/zone/gm_commands/scribespells.cpp @@ -0,0 +1,67 @@ +#include "../client.h" + +void command_scribespells(Client *c, const Seperator *sep) +{ + Client *target = c; + if (c->GetTarget() && c->GetTarget()->IsClient() && c->GetGM()) { + target = c->GetTarget()->CastToClient(); + } + + if (sep->argnum < 1 || !sep->IsNumber(1)) { + c->Message(Chat::White, "FORMAT: #scribespells "); + return; + } + + uint8 rule_max_level = (uint8) RuleI(Character, MaxLevel); + uint8 max_level = (uint8) std::stoi(sep->arg[1]); + uint8 min_level = ( + sep->IsNumber(2) ? + (uint8) std::stoi(sep->arg[2]) : + 1 + ); // Default to Level 1 if there isn't a 2nd argument + + if (!c->GetGM()) { // Default to Character:MaxLevel if we're not a GM and Level is higher than the max level + if (max_level > rule_max_level) { + max_level = rule_max_level; + } + + if (min_level > rule_max_level) { + min_level = rule_max_level; + } + } + + if (max_level < 1 || min_level < 1) { + c->Message(Chat::White, "ERROR: Level must be greater than or equal to 1."); + return; + } + + if (min_level > max_level) { + c->Message(Chat::White, "ERROR: Minimum Level must be less than or equal to Maximum Level."); + return; + } + + uint16 scribed_spells = target->ScribeSpells(min_level, max_level); + if (c != target) { + std::string spell_message = ( + scribed_spells > 0 ? + ( + scribed_spells == 1 ? + "A new spell" : + fmt::format( + "{} New spells", + scribed_spells + ) + ) : + "No new spells" + ); + c->Message( + Chat::White, + fmt::format( + "{} scribed for {}.", + spell_message, + target->GetCleanName() + ).c_str() + ); + } +} + diff --git a/zone/gm_commands/sendzonespawns.cpp b/zone/gm_commands/sendzonespawns.cpp new file mode 100755 index 000000000..caca8f6d0 --- /dev/null +++ b/zone/gm_commands/sendzonespawns.cpp @@ -0,0 +1,7 @@ +#include "../client.h" + +void command_sendzonespawns(Client *c, const Seperator *sep) +{ + entity_list.SendZoneSpawns(c); +} + diff --git a/zone/gm_commands/sensetrap.cpp b/zone/gm_commands/sensetrap.cpp new file mode 100755 index 000000000..792d7867d --- /dev/null +++ b/zone/gm_commands/sensetrap.cpp @@ -0,0 +1,24 @@ +#include "../client.h" + +void command_sensetrap(Client *c, const Seperator *sep) +{ + Mob *target = c->GetTarget(); + if (!target) { + c->Message(Chat::Red, "You must have a target."); + return; + } + + if (target->IsNPC()) { + if (c->HasSkill(EQ::skills::SkillSenseTraps)) { + if (DistanceSquaredNoZ(c->GetPosition(), target->GetPosition()) > RuleI(Adventure, LDoNTrapDistanceUse)) { + c->Message(Chat::Red, "%s is too far away.", target->GetCleanName()); + return; + } + c->HandleLDoNSenseTraps(target->CastToNPC(), c->GetSkill(EQ::skills::SkillSenseTraps), LDoNTypeMechanical); + } + else { + c->Message(Chat::Red, "You do not have the sense traps skill."); + } + } +} + diff --git a/zone/gm_commands/serverinfo.cpp b/zone/gm_commands/serverinfo.cpp new file mode 100755 index 000000000..e51da7266 --- /dev/null +++ b/zone/gm_commands/serverinfo.cpp @@ -0,0 +1,33 @@ +#include "../client.h" +#include "../../common/serverinfo.h" + +void command_serverinfo(Client *c, const Seperator *sep) +{ + auto os = EQ::GetOS(); + auto cpus = EQ::GetCPUs(); + auto pid = EQ::GetPID(); + auto rss = EQ::GetRSS(); + auto uptime = EQ::GetUptime(); + + c->Message(Chat::White, "Operating System Information"); + c->Message(Chat::White, "=================================================="); + c->Message(Chat::White, "System: %s", os.sysname.c_str()); + c->Message(Chat::White, "Release: %s", os.release.c_str()); + c->Message(Chat::White, "Version: %s", os.version.c_str()); + c->Message(Chat::White, "Machine: %s", os.machine.c_str()); + c->Message(Chat::White, "Uptime: %.2f seconds", uptime); + c->Message(Chat::White, "=================================================="); + c->Message(Chat::White, "CPU Information"); + c->Message(Chat::White, "=================================================="); + for (size_t i = 0; i < cpus.size(); ++i) { + auto &cp = cpus[i]; + c->Message(Chat::White, "CPU #%i: %s, Speed: %.2fGhz", i, cp.model.c_str(), cp.speed); + } + c->Message(Chat::White, "=================================================="); + c->Message(Chat::White, "Process Information"); + c->Message(Chat::White, "=================================================="); + c->Message(Chat::White, "PID: %u", pid); + c->Message(Chat::White, "RSS: %.2f MB", rss / 1048576.0); + c->Message(Chat::White, "=================================================="); +} + diff --git a/zone/gm_commands/serverrules.cpp b/zone/gm_commands/serverrules.cpp new file mode 100755 index 000000000..69bf799a7 --- /dev/null +++ b/zone/gm_commands/serverrules.cpp @@ -0,0 +1,7 @@ +#include "../client.h" + +void command_serverrules(Client *c, const Seperator *sep) +{ + c->SendRules(c); +} + diff --git a/zone/gm_commands/set_adventure_points.cpp b/zone/gm_commands/set_adventure_points.cpp new file mode 100755 index 000000000..fff6eec9a --- /dev/null +++ b/zone/gm_commands/set_adventure_points.cpp @@ -0,0 +1,84 @@ +#include "../client.h" +#include "../../common/data_verification.h" + +void command_set_adventure_points(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + + if ( + !arguments || + !sep->IsNumber(1) || + !sep->IsNumber(2) + ) { + c->Message(Chat::White, "Usage: #setadventurepoints [Theme] [Points]"); + c->Message(Chat::White, "Valid themes are as follows."); + auto theme_map = EQ::constants::GetLDoNThemeMap(); + for (const auto& theme : theme_map) { + c->Message( + Chat::White, + fmt::format( + "Theme {} | {}", + theme.first, + theme.second + ).c_str() + ); + } + c->Message(Chat::White, "Note: Theme 0 splits the points evenly across all Themes."); + return; + } + + auto target = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); + } + + auto theme_id = std::stoul(sep->arg[1]); + if (!EQ::ValueWithin(theme_id, LDoNThemes::Unused, LDoNThemes::TAK)) { + c->Message(Chat::White, "Valid themes are as follows."); + auto theme_map = EQ::constants::GetLDoNThemeMap(); + for (const auto& theme : theme_map) { + c->Message( + Chat::White, + fmt::format( + "Theme {} | {}", + theme.first, + theme.second + ).c_str() + ); + } + c->Message(Chat::White, "Note: Theme 0 splits the points evenly across all Themes."); + return; + } + + auto points = std::stoi(sep->arg[2]); + + c->Message( + Chat::White, + fmt::format( + "{} for {}.", + ( + theme_id == LDoNThemes::Unused ? + fmt::format( + "Splitting {} Points Evenly", + points + ) : + fmt::format( + "Adding {} {} Points", + points, + EQ::constants::GetLDoNThemeName(theme_id) + ) + ), + ( + c == target ? + "Yourself" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ) + ).c_str() + ); + + target->UpdateLDoNPoints(theme_id, points); +} diff --git a/zone/gm_commands/setaapts.cpp b/zone/gm_commands/setaapts.cpp new file mode 100755 index 000000000..cb5e9f3f0 --- /dev/null +++ b/zone/gm_commands/setaapts.cpp @@ -0,0 +1,71 @@ +#include "../client.h" +#include "../groups.h" +#include "../raids.h" +#include "../raids.h" + +void command_setaapts(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (arguments <= 1 || !sep->IsNumber(2)) { + c->Message(Chat::White, "Usage: #setaapts [AA|Group|Raid] [AA Amount]"); + return; + } + + Client *target = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); + } + + std::string aa_type = str_tolower(sep->arg[1]); + std::string group_raid_string; + uint32 aa_points = static_cast(std::min(std::stoull(sep->arg[2]), (unsigned long long) 2000000000)); + bool is_aa = aa_type.find("aa") != std::string::npos; + bool is_group = aa_type.find("group") != std::string::npos; + bool is_raid = aa_type.find("raid") != std::string::npos; + if (!is_aa && !is_group && !is_raid) { + c->Message(Chat::White, "Usage: #setaapts [AA|Group|Raid] [AA Amount]"); + return; + } + + if (is_aa) { + target->GetPP().aapoints = aa_points; + target->GetPP().expAA = 0; + target->SendAlternateAdvancementStats(); + } + else if (is_group || is_raid) { + if (is_group) { + group_raid_string = "Group "; + target->GetPP().group_leadership_points = aa_points; + target->GetPP().group_leadership_exp = 0; + } + else if (is_raid) { + group_raid_string = "Raid "; + target->GetPP().raid_leadership_points = aa_points; + target->GetPP().raid_leadership_exp = 0; + } + target->SendLeadershipEXPUpdate(); + } + + std::string aa_message = fmt::format( + "{} now {} {} {}AA Point{}.", + ( + c == target ? + "You" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ), + c == target ? "have" : "has", + aa_points, + group_raid_string, + aa_points != 1 ? "s" : "" + + ); + c->Message( + Chat::White, + aa_message.c_str() + ); +} + diff --git a/zone/gm_commands/setaaxp.cpp b/zone/gm_commands/setaaxp.cpp new file mode 100755 index 000000000..081a7d1d6 --- /dev/null +++ b/zone/gm_commands/setaaxp.cpp @@ -0,0 +1,75 @@ +#include "../client.h" +#include "../groups.h" +#include "../raids.h" +#include "../raids.h" + +void command_setaaxp(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (arguments <= 1 || !sep->IsNumber(2)) { + c->Message(Chat::White, "Usage: #setaaxp [AA|Group|Raid] [AA Experience]"); + return; + } + + Client *target = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); + } + + std::string aa_type = str_tolower(sep->arg[1]); + std::string group_raid_string; + uint32 aa_experience = static_cast(std::min( + std::stoull(sep->arg[2]), + (unsigned long long) 2000000000 + )); + bool is_aa = aa_type.find("aa") != std::string::npos; + bool is_group = aa_type.find("group") != std::string::npos; + bool is_raid = aa_type.find("raid") != std::string::npos; + if (!is_aa && !is_group && !is_raid) { + c->Message(Chat::White, "Usage: #setaaxp [AA|Group|Raid] [AA Experience]"); + return; + } + + if (is_aa) { + target->SetEXP( + target->GetEXP(), + aa_experience, + false + ); + } + else if (is_group) { + group_raid_string = "Group "; + target->SetLeadershipEXP( + aa_experience, + target->GetRaidEXP() + ); + } + else if (is_raid) { + group_raid_string = "Raid "; + target->SetLeadershipEXP( + target->GetGroupEXP(), + aa_experience + ); + } + + std::string aa_exp_message = fmt::format( + "{} now {} {} {}AA Experience.", + ( + c == target ? + "You" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ), + c == target ? "have" : "has", + aa_experience, + group_raid_string + ); + c->Message( + Chat::White, + aa_exp_message.c_str() + ); +} + diff --git a/zone/gm_commands/setaltcurrency.cpp b/zone/gm_commands/setaltcurrency.cpp new file mode 100644 index 000000000..339542d6e --- /dev/null +++ b/zone/gm_commands/setaltcurrency.cpp @@ -0,0 +1,59 @@ +#include "../client.h" + +void command_setaltcurrency(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if ( + arguments < 2 || + !sep->IsNumber(1) || + !sep->IsNumber(2) + ) { + c->Message(Chat::White, "Command Syntax: #setaltcurrency [Currency ID] [Amount]"); + return; + } + + auto target = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); + } + + auto currency_id = std::stoul(sep->arg[1]); + auto amount = static_cast(std::min(std::stoll(sep->arg[2]), (long long) 2000000000)); + uint32 currency_item_id = zone->GetCurrencyItemID(currency_id); + if (!currency_item_id) { + c->Message( + Chat::White, + fmt::format( + "Currency ID {} could not be found.", + currency_id + ).c_str() + ); + return; + } + + target->SetAlternateCurrencyValue(currency_id, amount); + + c->Message( + Chat::White, + fmt::format( + "{} now {} {} {}.", + ( + c == target ? + "You" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ), + c == target ? "have" : "has", + ( + amount ? + std::to_string(amount) : + "no" + ), + database.CreateItemLink(currency_item_id) + ).c_str() + ); +} + diff --git a/zone/gm_commands/setanim.cpp b/zone/gm_commands/setanim.cpp new file mode 100755 index 000000000..63b925166 --- /dev/null +++ b/zone/gm_commands/setanim.cpp @@ -0,0 +1,58 @@ +#include "../client.h" + +void command_setanim(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments || !sep->IsNumber(1)) { + c->Message(Chat::White, "Usage: #setanim [Animation ID (IDs are 0 to 4)]"); + return; + } + + Mob* target = c; + if (c->GetTarget()) { + target = c->GetTarget(); + } + + + int animation_id = std::stoi(sep->arg[1]); + if ( + animation_id < 0 || + animation_id > eaLooting + ) { + c->Message(Chat::White, "Usage: #setanim [Animation ID (IDs are 0 to 4)]"); + return; + } + + target->SetAppearance(static_cast(animation_id), false); + std::string animation_name; + if (animation_id == eaStanding) { + animation_name = "Standing"; + } else if (animation_id == eaSitting) { + animation_name = "Sitting"; + } else if (animation_id == eaCrouching) { + animation_name = "Crouching"; + } else if (animation_id == eaDead) { + animation_name = "Dead"; + } else if (animation_id == eaLooting) { + animation_name = "Looting"; + } + + c->Message( + Chat::White, + fmt::format( + "Set animation to {} ({}) for {}.", + animation_name, + animation_id, + ( + c == target ? + "yourself" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ).c_str() + ) + ).c_str() + ); +} + diff --git a/zone/gm_commands/setcrystals.cpp b/zone/gm_commands/setcrystals.cpp new file mode 100755 index 000000000..4f7f1f14f --- /dev/null +++ b/zone/gm_commands/setcrystals.cpp @@ -0,0 +1,60 @@ +#include "../client.h" + +void command_setcrystals(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (arguments <= 1 || !sep->IsNumber(2)) { + c->Message(Chat::White, "Usage: #setcrystals [Ebon|Radiant] [Crystal Amount]"); + return; + } + + Client *target = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); + } + + std::string crystal_type = str_tolower(sep->arg[1]); + uint32 crystal_amount = static_cast(std::min( + std::stoull(sep->arg[2]), + (unsigned long long) 2000000000 + )); + bool is_ebon = crystal_type.find("ebon") != std::string::npos; + bool is_radiant = crystal_type.find("radiant") != std::string::npos; + if (!is_ebon && !is_radiant) { + c->Message(Chat::White, "Usage: #setcrystals [Ebon|Radiant] [Crystal Amount]"); + return; + } + + uint32 crystal_item_id = ( + is_ebon ? + RuleI(Zone, EbonCrystalItemID) : + RuleI(Zone, RadiantCrystalItemID) + ); + + auto crystal_link = database.CreateItemLink(crystal_item_id); + if (is_radiant) { + target->SetRadiantCrystals(crystal_amount); + } else { + target->SetEbonCrystals(crystal_amount); + } + + c->Message( + Chat::White, + fmt::format( + "{} now {} {} {}.", + ( + c == target ? + "You" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ), + c == target ? "have" : "has", + crystal_amount, + crystal_link + ).c_str() + ); +} + diff --git a/zone/gm_commands/setendurance.cpp b/zone/gm_commands/setendurance.cpp new file mode 100644 index 000000000..a6c42f8ea --- /dev/null +++ b/zone/gm_commands/setendurance.cpp @@ -0,0 +1,62 @@ +#include "../client.h" + +void command_setendurance(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments || !sep->IsNumber(1)) { + c->Message(Chat::White, "Usage: #setendurance [Endurance]"); + return; + } + + auto endurance = static_cast(std::min(std::stoll(sep->arg[1]), (long long) 2000000000)); + bool set_to_max = false; + Mob* target = c; + if (c->GetTarget()) { + target = c->GetTarget(); + } + + if (target->IsClient()) { + if (endurance >= target->CastToClient()->GetMaxEndurance()) { + endurance = target->CastToClient()->GetMaxEndurance(); + set_to_max = true; + } + + target->CastToClient()->SetEndurance(endurance); + } else { + if (endurance >= target->GetMaxEndurance()) { + endurance = target->GetMaxEndurance(); + set_to_max = true; + } + + target->SetEndurance(endurance); + } + + c->Message( + Chat::White, + fmt::format( + "Set {} to {} Endurance{}.", + ( + c == target ? + "yourself" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ), + ( + set_to_max ? + "full" : + std::to_string(endurance) + ), + ( + set_to_max ? + fmt::format( + " ({})", + endurance + ) : + "" + ) + ).c_str() + ); +} \ No newline at end of file diff --git a/zone/gm_commands/setfaction.cpp b/zone/gm_commands/setfaction.cpp new file mode 100755 index 000000000..17b3c288f --- /dev/null +++ b/zone/gm_commands/setfaction.cpp @@ -0,0 +1,51 @@ +#include "../client.h" + +void command_setfaction(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments || !sep->IsNumber(1)) { + c->Message(Chat::White, "Usage: #setfaction [Faction ID]"); + return; + } + + if ( + !c->GetTarget() || + ( + c->GetTarget() && + c->GetTarget()->IsClient() + ) + ) { + c->Message(Chat::White, "You must target an NPC to use this command."); + return; + } + + NPC* target = c->GetTarget()->CastToNPC(); + auto npc_id = target->GetNPCTypeID(); + auto faction_id = std::stoi(sep->arg[1]); + auto faction_name = content_db.GetFactionName(faction_id); + if (!faction_name.empty()) { + c->Message( + Chat::White, + fmt::format( + "Faction Changed | Name: {} ({}) Faction: {} ({}).", + target->GetCleanName(), + npc_id, + content_db.GetFactionName(faction_id), + faction_id + ).c_str() + ); + + std::string query = fmt::format( + "UPDATE npc_types SET npc_faction_id = {} WHERE id = {}", + faction_id, + npc_id + ); + content_db.QueryDatabase(query); + } else { + c->Message( + Chat::White, + "Invalid Faction ID, please specify a valid Faction ID." + ); + } +} + diff --git a/zone/gm_commands/setgraveyard.cpp b/zone/gm_commands/setgraveyard.cpp new file mode 100755 index 000000000..e1b043e4b --- /dev/null +++ b/zone/gm_commands/setgraveyard.cpp @@ -0,0 +1,44 @@ +#include "../client.h" + +void command_setgraveyard(Client *c, const Seperator *sep) +{ + uint32 zoneid = 0; + uint32 graveyard_id = 0; + Client *t = c; + + if (c->GetTarget() && c->GetTarget()->IsClient() && c->GetGM()) { + t = c->GetTarget()->CastToClient(); + } + + if (!sep->arg[1][0]) { + c->Message(Chat::White, "Usage: #setgraveyard [zonename]"); + return; + } + + zoneid = ZoneID(sep->arg[1]); + + if (zoneid > 0) { + graveyard_id = content_db.CreateGraveyardRecord(zoneid, t->GetPosition()); + + if (graveyard_id > 0) { + c->Message(Chat::White, "Successfuly added a new record for this graveyard!"); + if (content_db.AddGraveyardIDToZone(zoneid, graveyard_id) > 0) { + c->Message(Chat::White, "Successfuly added this new graveyard for the zone %s.", sep->arg[1]); + // TODO: Set graveyard data to the running zone process. + c->Message(Chat::White, "Done!"); + } + else { + c->Message(Chat::White, "Unable to add this new graveyard to the zone %s.", sep->arg[1]); + } + } + else { + c->Message(Chat::White, "Unable to create a new graveyard record in the database."); + } + } + else { + c->Message(Chat::White, "Unable to retrieve a ZoneID for the zone: %s", sep->arg[1]); + } + + return; +} + diff --git a/zone/gm_commands/sethp.cpp b/zone/gm_commands/sethp.cpp new file mode 100644 index 000000000..c9c73c96f --- /dev/null +++ b/zone/gm_commands/sethp.cpp @@ -0,0 +1,55 @@ +#include "../client.h" + +void command_sethp(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments || !sep->IsNumber(1)) { + c->Message(Chat::White, "Usage: #sethp [Health]"); + return; + } + + auto health = static_cast(std::min(std::stoll(sep->arg[1]), (long long) 2000000000)); + bool set_to_max = false; + Mob* target = c; + if (c->GetTarget()) { + target = c->GetTarget(); + } + + if (health >= target->GetMaxHP()) { + health = target->GetMaxHP(); + set_to_max = true; + } + + target->SetHP(health); + target->SendHPUpdate(); + + c->Message( + Chat::White, + fmt::format( + "Set {} to {} Health{}.", + ( + c == target ? + "yourself" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ), + ( + set_to_max ? + "full" : + std::to_string(health) + ), + ( + set_to_max ? + fmt::format( + " ({})", + health + ) : + "" + ) + ).c_str() + ); +} + diff --git a/zone/gm_commands/setlanguage.cpp b/zone/gm_commands/setlanguage.cpp new file mode 100755 index 000000000..50353f150 --- /dev/null +++ b/zone/gm_commands/setlanguage.cpp @@ -0,0 +1,61 @@ +#include "../client.h" +#include "../../common/languages.h" + +void command_setlanguage(Client *c, const Seperator *sep) +{ + Client *target = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); + } + + auto language_id = sep->IsNumber(1) ? std::stoi(sep->arg[1]) : -1; + auto language_value = sep->IsNumber(2) ? std::stoi(sep->arg[2]) : -1; + if (!strcasecmp(sep->arg[1], "list")) { + for (int language = LANG_COMMON_TONGUE; language <= LANG_UNKNOWN; language++) { + c->Message( + Chat::White, + fmt::format( + "Language {}: {}", + language, + EQ::constants::GetLanguageName(language) + ).c_str() + ); + } + } + else if ( + language_id < LANG_COMMON_TONGUE || + language_id > LANG_UNKNOWN || + language_value < 0 || + language_value > 100 + ) { + c->Message(Chat::White, "Usage: #setlanguage [Language ID] [Language Value]"); + c->Message(Chat::White, "Usage: #setlanguage [List]"); + c->Message(Chat::White, "Language ID = 0 to 27", LANG_UNKNOWN); + c->Message(Chat::White, "Language Value = 0 to 100", HIGHEST_CAN_SET_SKILL); + } + else { + LogInfo( + "Set language request from [{}], Target: [{}] Language ID: [{}] Language Value: [{}]", + c->GetCleanName(), + target->GetCleanName(), + language_id, + language_value + ); + + target->SetLanguageSkill(language_id, language_value); + + if (c != target) { + c->Message( + Chat::White, + fmt::format( + "Set {} ({}) to {} for {}.", + EQ::constants::GetLanguageName(language_id), + language_id, + language_value, + target->GetCleanName() + ).c_str() + ); + } + } +} + diff --git a/zone/gm_commands/setlsinfo.cpp b/zone/gm_commands/setlsinfo.cpp new file mode 100755 index 000000000..64f38da01 --- /dev/null +++ b/zone/gm_commands/setlsinfo.cpp @@ -0,0 +1,24 @@ +#include "../client.h" +#include "../worldserver.h" + +extern WorldServer worldserver; + +void command_setlsinfo(Client *c, const Seperator *sep) +{ + if (sep->argnum != 2) { + c->Message(Chat::White, "Format: #setlsinfo email password"); + } + else { + auto pack = new ServerPacket( + ServerOP_LSAccountUpdate, + sizeof(ServerLSAccountUpdate_Struct)); + ServerLSAccountUpdate_Struct *s = (ServerLSAccountUpdate_Struct *) pack->pBuffer; + s->useraccountid = c->LSAccountID(); + strn0cpy(s->useraccount, c->AccountName(), 30); + strn0cpy(s->user_email, sep->arg[1], 100); + strn0cpy(s->userpassword, sep->arg[2], 50); + worldserver.SendPacket(pack); + c->Message(Chat::White, "Login Server update packet sent."); + } +} + diff --git a/zone/gm_commands/setmana.cpp b/zone/gm_commands/setmana.cpp new file mode 100644 index 000000000..505e85afe --- /dev/null +++ b/zone/gm_commands/setmana.cpp @@ -0,0 +1,63 @@ +#include "../client.h" + +void command_setmana(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments || !sep->IsNumber(1)) { + c->Message(Chat::White, "Usage: #setmana [Mana]"); + return; + } + + auto mana = static_cast(std::min(std::stoll(sep->arg[1]), (long long) 2000000000)); + bool set_to_max = false; + Mob* target = c; + if (c->GetTarget()) { + target = c->GetTarget(); + } + + if (target->IsClient()) { + if (mana >= target->CastToClient()->CalcMaxMana()) { + mana = target->CastToClient()->CalcMaxMana(); + set_to_max = true; + } + + target->CastToClient()->SetMana(mana); + } else { + if (mana >= target->CalcMaxMana()) { + mana = target->CalcMaxMana(); + set_to_max = true; + } + + target->SetMana(mana); + } + + c->Message( + Chat::White, + fmt::format( + "Set {} to {} Mana{}.", + ( + c == target ? + "yourself" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ), + ( + set_to_max ? + "full" : + std::to_string(mana) + ), + ( + set_to_max ? + fmt::format( + " ({})", + mana + ) : + "" + ) + ).c_str() + ); +} + diff --git a/zone/gm_commands/setpass.cpp b/zone/gm_commands/setpass.cpp new file mode 100755 index 000000000..4bf84d300 --- /dev/null +++ b/zone/gm_commands/setpass.cpp @@ -0,0 +1,29 @@ +#include "../client.h" + +void command_setpass(Client *c, const Seperator *sep) +{ + if (sep->argnum != 2) { + c->Message(Chat::White, "Format: #setpass accountname password"); + } + else { + std::string user; + std::string loginserver; + ParseAccountString(sep->arg[1], user, loginserver); + + int16 tmpstatus = 0; + uint32 tmpid = database.GetAccountIDByName(user.c_str(), loginserver.c_str(), &tmpstatus); + if (!tmpid) { + c->Message(Chat::White, "Error: Account not found"); + } + else if (tmpstatus > c->Admin()) { + c->Message(Chat::White, "Cannot change password: Account's status is higher than yours"); + } + else if (database.SetLocalPassword(tmpid, sep->arg[2])) { + c->Message(Chat::White, "Password changed."); + } + else { + c->Message(Chat::White, "Error changing password."); + } + } +} + diff --git a/zone/gm_commands/setpvppoints.cpp b/zone/gm_commands/setpvppoints.cpp new file mode 100755 index 000000000..d124cb6eb --- /dev/null +++ b/zone/gm_commands/setpvppoints.cpp @@ -0,0 +1,40 @@ +#include "../client.h" + +void command_setpvppoints(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments || !sep->IsNumber(1)) { + c->Message(Chat::White, "Command Syntax: #setpvppoints [Amount]"); + return; + } + + Client *target = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); + } + + uint32 pvp_points = static_cast(std::min(std::stoull(sep->arg[1]), (unsigned long long) 2000000000)); + target->SetPVPPoints(pvp_points); + target->Save(); + target->SendPVPStats(); + std::string pvp_message = fmt::format( + "{} now {} {} PVP Point{}.", + ( + c == target ? + "You" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ), + c == target ? "have" : "has", + pvp_points, + pvp_points != 1 ? "s" : "" + ); + c->Message( + Chat::White, + pvp_message.c_str() + ); +} + diff --git a/zone/gm_commands/setskill.cpp b/zone/gm_commands/setskill.cpp new file mode 100755 index 000000000..625d54e0e --- /dev/null +++ b/zone/gm_commands/setskill.cpp @@ -0,0 +1,55 @@ +#include "../client.h" + +void command_setskill(Client *c, const Seperator *sep) +{ + Client *target = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); + } + + auto skill_id = sep->IsNumber(1) ? std::stoi(sep->arg[1]) : -1; + auto skill_value = sep->IsNumber(2) ? std::stoi(sep->arg[2]) : -1; + if ( + skill_id < 0 || + skill_id > EQ::skills::HIGHEST_SKILL || + skill_value < 0 || + skill_value > HIGHEST_CAN_SET_SKILL + ) { + c->Message(Chat::White, "Usage: #setskill [Skill ID] [Skill Value]"); + c->Message(Chat::White, fmt::format("Skill ID = 0 to {}", EQ::skills::HIGHEST_SKILL).c_str()); + c->Message(Chat::White, fmt::format("Skill Value = 0 to {}", HIGHEST_CAN_SET_SKILL).c_str()); + } + else { + LogInfo( + "Set skill request from [{}], Target: [{}] Skill ID: [{}] Skill Value: [{}]", + c->GetCleanName(), + target->GetCleanName(), + skill_id, + skill_value + ); + + if ( + skill_id >= EQ::skills::Skill1HBlunt && + skill_id <= EQ::skills::HIGHEST_SKILL + ) { + target->SetSkill( + (EQ::skills::SkillType) skill_id, + skill_value + ); + + if (c != target) { + c->Message( + Chat::White, + fmt::format( + "Set {} ({}) to {} for {}.", + EQ::skills::GetSkillName((EQ::skills::SkillType) skill_id), + skill_id, + skill_value, + target->GetCleanName() + ).c_str() + ); + } + } + } +} + diff --git a/zone/gm_commands/setskillall.cpp b/zone/gm_commands/setskillall.cpp new file mode 100755 index 000000000..dd8b9992a --- /dev/null +++ b/zone/gm_commands/setskillall.cpp @@ -0,0 +1,30 @@ +#include "../client.h" + +void command_setskillall(Client *c, const Seperator *sep) +{ + if (c->GetTarget() == 0) { + c->Message(Chat::White, "Error: #setallskill: No target."); + } + else if (!c->GetTarget()->IsClient()) { + c->Message(Chat::White, "Error: #setskill: Target must be a client."); + } + else if (!sep->IsNumber(1) || atoi(sep->arg[1]) < 0 || atoi(sep->arg[1]) > HIGHEST_CAN_SET_SKILL) { + c->Message(Chat::White, "Usage: #setskillall value "); + c->Message(Chat::White, " value = 0 to %d", HIGHEST_CAN_SET_SKILL); + } + else { + if (c->Admin() >= commandSetSkillsOther || c->GetTarget() == c || c->GetTarget() == 0) { + LogInfo("Set ALL skill request from [{}], target:[{}]", c->GetName(), c->GetTarget()->GetName()); + uint16 level = atoi(sep->arg[1]); + for (EQ::skills::SkillType skill_num = EQ::skills::Skill1HBlunt; + skill_num <= EQ::skills::HIGHEST_SKILL; + skill_num = (EQ::skills::SkillType) (skill_num + 1)) { + c->GetTarget()->CastToClient()->SetSkill(skill_num, level); + } + } + else { + c->Message(Chat::White, "Error: Your status is not high enough to set anothers skills"); + } + } +} + diff --git a/zone/gm_commands/setstartzone.cpp b/zone/gm_commands/setstartzone.cpp new file mode 100755 index 000000000..cf946041d --- /dev/null +++ b/zone/gm_commands/setstartzone.cpp @@ -0,0 +1,60 @@ +#include "../client.h" + +void command_setstartzone(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments) { + c->Message(Chat::White, "Usage: #setstartzone [Zone ID|Zone Short Name]"); + c->Message( + Chat::White, + "Optional Usage: Use '#setstartzone Reset' or '#setstartzone 0' to clear a starting zone. Player can select a starting zone using /setstartcity" + ); + return; + } + + auto target = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); + } + + auto zone_id = ( + sep->IsNumber(1) ? + std::stoul(sep->arg[1]) : + ZoneID(sep->arg[1]) + ); + + target->SetStartZone(zone_id); + + bool is_reset = ( + !strcasecmp(sep->arg[1], "reset") || + zone_id == 0 + ); + + c->Message( + Chat::White, + fmt::format( + "Start Zone {} for {} |{}", + is_reset ? "Reset" : "Changed", + ( + c == target ? + "Yourself" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ), + ( + zone_id ? + fmt::format( + " {} ({}) ID: {}", + ZoneLongName(zone_id), + ZoneName(zone_id), + zone_id + ) : + "" + ) + ).c_str() + ); +} + diff --git a/zone/gm_commands/setstat.cpp b/zone/gm_commands/setstat.cpp new file mode 100755 index 000000000..56ff81a05 --- /dev/null +++ b/zone/gm_commands/setstat.cpp @@ -0,0 +1,14 @@ +#include "../client.h" + +void command_setstat(Client *c, const Seperator *sep) +{ + if (sep->arg[1][0] && sep->arg[2][0] && c->GetTarget() != 0 && c->GetTarget()->IsClient()) { + c->GetTarget()->CastToClient()->SetStats(atoi(sep->arg[1]), atoi(sep->arg[2])); + } + else { + c->Message(Chat::White, "This command is used to permanently increase or decrease a players stats."); + c->Message(Chat::White, "Usage: #setstat {type} {value the stat should be}"); + c->Message(Chat::White, "Types: Str: 0, Sta: 1, Agi: 2, Dex: 3, Int: 4, Wis: 5, Cha: 6"); + } +} + diff --git a/zone/gm_commands/setxp.cpp b/zone/gm_commands/setxp.cpp new file mode 100755 index 000000000..c82bb87a3 --- /dev/null +++ b/zone/gm_commands/setxp.cpp @@ -0,0 +1,23 @@ +#include "../client.h" + +void command_setxp(Client *c, const Seperator *sep) +{ + Client *t = c; + + if (c->GetTarget() && c->GetTarget()->IsClient()) { + t = c->GetTarget()->CastToClient(); + } + + if (sep->IsNumber(1)) { + if (atoi(sep->arg[1]) > 9999999) { + c->Message(Chat::White, "Error: Value too high."); + } + else { + t->AddEXP(atoi(sep->arg[1])); + } + } + else { + c->Message(Chat::White, "Usage: #setxp number"); + } +} + diff --git a/zone/gm_commands/showbonusstats.cpp b/zone/gm_commands/showbonusstats.cpp new file mode 100755 index 000000000..2680b36f1 --- /dev/null +++ b/zone/gm_commands/showbonusstats.cpp @@ -0,0 +1,49 @@ +#include "../client.h" + +void command_showbonusstats(Client *c, const Seperator *sep) +{ + if (c->GetTarget() == 0) { + c->Message(Chat::White, "ERROR: No target!"); + } + else if (!c->GetTarget()->IsMob() && !c->GetTarget()->IsClient()) { + c->Message(Chat::White, "ERROR: Target is not a Mob or Player!"); + } + else { + bool bAll = false; + if (sep->arg[1][0] == '\0' || strcasecmp(sep->arg[1], "all") == 0) { + bAll = true; + } + if (bAll || (strcasecmp(sep->arg[1], "item") == 0)) { + c->Message(Chat::White, "Target Item Bonuses:"); + c->Message( + Chat::White, + " Accuracy: %i%% Divine Save: %i%%", + c->GetTarget()->GetItemBonuses().Accuracy, + c->GetTarget()->GetItemBonuses().DivineSaveChance + ); + c->Message( + Chat::White, + " Flurry: %i%% HitChance: %i%%", + c->GetTarget()->GetItemBonuses().FlurryChance, + c->GetTarget()->GetItemBonuses().HitChance / 15 + ); + } + if (bAll || (strcasecmp(sep->arg[1], "spell") == 0)) { + c->Message(Chat::White, " Target Spell Bonuses:"); + c->Message( + Chat::White, + " Accuracy: %i%% Divine Save: %i%%", + c->GetTarget()->GetSpellBonuses().Accuracy, + c->GetTarget()->GetSpellBonuses().DivineSaveChance + ); + c->Message( + Chat::White, + " Flurry: %i%% HitChance: %i%% ", + c->GetTarget()->GetSpellBonuses().FlurryChance, + c->GetTarget()->GetSpellBonuses().HitChance / 15 + ); + } + c->Message(Chat::White, " Effective Casting Level: %i", c->GetTarget()->GetCasterLevel(0)); + } +} + diff --git a/zone/gm_commands/showbuffs.cpp b/zone/gm_commands/showbuffs.cpp new file mode 100755 index 000000000..bd8e558d6 --- /dev/null +++ b/zone/gm_commands/showbuffs.cpp @@ -0,0 +1,12 @@ +#include "../client.h" + +void command_showbuffs(Client *c, const Seperator *sep) +{ + if (c->GetTarget() == 0) { + c->CastToMob()->ShowBuffs(c); + } + else { + c->GetTarget()->CastToMob()->ShowBuffs(c); + } +} + diff --git a/zone/gm_commands/shownpcgloballoot.cpp b/zone/gm_commands/shownpcgloballoot.cpp new file mode 100755 index 000000000..546631c5c --- /dev/null +++ b/zone/gm_commands/shownpcgloballoot.cpp @@ -0,0 +1,16 @@ +#include "../client.h" + +void command_shownpcgloballoot(Client *c, const Seperator *sep) +{ + auto tar = c->GetTarget(); + + if (!tar || !tar->IsNPC()) { + c->Message(Chat::White, "You must target an NPC to use this command."); + return; + } + + auto npc = tar->CastToNPC(); + c->Message(Chat::White, "GlobalLoot for %s (%d)", npc->GetName(), npc->GetNPCTypeID()); + zone->ShowNPCGlobalLoot(c, npc); +} + diff --git a/zone/gm_commands/shownumhits.cpp b/zone/gm_commands/shownumhits.cpp new file mode 100755 index 000000000..b8b06867b --- /dev/null +++ b/zone/gm_commands/shownumhits.cpp @@ -0,0 +1,8 @@ +#include "../client.h" + +void command_shownumhits(Client *c, const Seperator *sep) +{ + c->ShowNumHits(); + return; +} + diff --git a/zone/gm_commands/showskills.cpp b/zone/gm_commands/showskills.cpp new file mode 100755 index 000000000..e970dd7aa --- /dev/null +++ b/zone/gm_commands/showskills.cpp @@ -0,0 +1,44 @@ +#include "../client.h" + +void command_showskills(Client *c, const Seperator *sep) +{ + Client *target = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); + } + + bool show_all = false; + + if (!strcasecmp("all", sep->arg[1])) { + show_all = true; + } + + c->Message( + Chat::White, + fmt::format( + "Skills | Name: {}", + target->GetCleanName() + ).c_str() + ); + + for ( + EQ::skills::SkillType skill_type = EQ::skills::Skill1HBlunt; + skill_type <= EQ::skills::HIGHEST_SKILL; + skill_type = (EQ::skills::SkillType) (skill_type + 1) + ) { + if (show_all || (target->CanHaveSkill(skill_type) && target->MaxSkill(skill_type))) { + c->Message( + Chat::White, + fmt::format( + "{} ({}) | Current: {} Max: {} Raw: {}", + EQ::skills::GetSkillName(skill_type), + skill_type, + target->GetSkill(skill_type), + target->MaxSkill(skill_type), + target->GetRawSkill(skill_type) + ).c_str() + ); + } + } +} + diff --git a/zone/gm_commands/showspellslist.cpp b/zone/gm_commands/showspellslist.cpp new file mode 100755 index 000000000..2b50864e6 --- /dev/null +++ b/zone/gm_commands/showspellslist.cpp @@ -0,0 +1,13 @@ +#include "../client.h" + +void command_showspellslist(Client *c, const Seperator *sep) +{ + Mob *target = c->GetTarget(); + if (!target || !target->IsNPC()) { + c->Message(Chat::White, "You must target an NPC to use this command."); + return; + } + target->CastToNPC()->AISpellsList(c); + return; +} + diff --git a/zone/gm_commands/showstats.cpp b/zone/gm_commands/showstats.cpp new file mode 100755 index 000000000..cae9609c2 --- /dev/null +++ b/zone/gm_commands/showstats.cpp @@ -0,0 +1,12 @@ +#include "../client.h" + +void command_showstats(Client *c, const Seperator *sep) +{ + if (c->GetTarget() != 0) { + c->GetTarget()->ShowStats(c); + } + else { + c->ShowStats(c); + } +} + diff --git a/zone/gm_commands/showzonegloballoot.cpp b/zone/gm_commands/showzonegloballoot.cpp new file mode 100755 index 000000000..ecf774f88 --- /dev/null +++ b/zone/gm_commands/showzonegloballoot.cpp @@ -0,0 +1,13 @@ +#include "../client.h" + +void command_showzonegloballoot(Client *c, const Seperator *sep) +{ + c->Message( + Chat::White, + "GlobalLoot for %s (%d:%d)", + zone->GetShortName(), + zone->GetZoneID(), + zone->GetInstanceVersion()); + zone->ShowZoneGlobalLoot(c); +} + diff --git a/zone/gm_commands/showzonepoints.cpp b/zone/gm_commands/showzonepoints.cpp new file mode 100755 index 000000000..d1e8c2428 --- /dev/null +++ b/zone/gm_commands/showzonepoints.cpp @@ -0,0 +1,157 @@ +#include "../client.h" + +void command_showzonepoints(Client *c, const Seperator *sep) +{ + auto &mob_list = entity_list.GetMobList(); + for (auto itr : mob_list) { + Mob *mob = itr.second; + if (mob->IsNPC() && mob->GetRace() == 2254) { + mob->Depop(); + } + } + + int found_zone_points = 0; + + c->Message(Chat::White, "Listing zone points..."); + c->SendChatLineBreak(); + + for (auto &virtual_zone_point : zone->virtual_zone_point_list) { + std::string zone_long_name = ZoneLongName(virtual_zone_point.target_zone_id); + + c->Message( + Chat::White, + fmt::format( + "Virtual Zone Point x [{}] y [{}] z [{}] h [{}] width [{}] height [{}] | To [{}] ({}) x [{}] y [{}] z [{}] h [{}]", + virtual_zone_point.x, + virtual_zone_point.y, + virtual_zone_point.z, + virtual_zone_point.heading, + virtual_zone_point.width, + virtual_zone_point.height, + zone_long_name.c_str(), + virtual_zone_point.target_zone_id, + virtual_zone_point.target_x, + virtual_zone_point.target_y, + virtual_zone_point.target_z, + virtual_zone_point.target_heading + ).c_str() + ); + + std::string node_name = fmt::format("ZonePoint To [{}]", zone_long_name); + + float half_width = ((float) virtual_zone_point.width / 2); + + NPC::SpawnZonePointNodeNPC( + node_name, glm::vec4( + (float) virtual_zone_point.x + half_width, + (float) virtual_zone_point.y + half_width, + virtual_zone_point.z, + virtual_zone_point.heading + )); + + NPC::SpawnZonePointNodeNPC( + node_name, glm::vec4( + (float) virtual_zone_point.x + half_width, + (float) virtual_zone_point.y - half_width, + virtual_zone_point.z, + virtual_zone_point.heading + )); + + NPC::SpawnZonePointNodeNPC( + node_name, glm::vec4( + (float) virtual_zone_point.x - half_width, + (float) virtual_zone_point.y - half_width, + virtual_zone_point.z, + virtual_zone_point.heading + )); + + NPC::SpawnZonePointNodeNPC( + node_name, glm::vec4( + (float) virtual_zone_point.x - half_width, + (float) virtual_zone_point.y + half_width, + virtual_zone_point.z, + virtual_zone_point.heading + )); + + NPC::SpawnZonePointNodeNPC( + node_name, glm::vec4( + (float) virtual_zone_point.x + half_width, + (float) virtual_zone_point.y + half_width, + (float) virtual_zone_point.z + (float) virtual_zone_point.height, + virtual_zone_point.heading + )); + + NPC::SpawnZonePointNodeNPC( + node_name, glm::vec4( + (float) virtual_zone_point.x + half_width, + (float) virtual_zone_point.y - half_width, + (float) virtual_zone_point.z + (float) virtual_zone_point.height, + virtual_zone_point.heading + )); + + NPC::SpawnZonePointNodeNPC( + node_name, glm::vec4( + (float) virtual_zone_point.x - half_width, + (float) virtual_zone_point.y - half_width, + (float) virtual_zone_point.z + (float) virtual_zone_point.height, + virtual_zone_point.heading + )); + + NPC::SpawnZonePointNodeNPC( + node_name, glm::vec4( + (float) virtual_zone_point.x - half_width, + (float) virtual_zone_point.y + half_width, + (float) virtual_zone_point.z + (float) virtual_zone_point.height, + virtual_zone_point.heading + )); + + found_zone_points++; + } + + LinkedListIterator iterator(zone->zone_point_list); + iterator.Reset(); + while (iterator.MoreElements()) { + ZonePoint *zone_point = iterator.GetData(); + std::string zone_long_name = ZoneLongName(zone_point->target_zone_id); + std::string node_name = fmt::format("ZonePoint To [{}]", zone_long_name); + + NPC::SpawnZonePointNodeNPC( + node_name, glm::vec4( + zone_point->x, + zone_point->y, + zone_point->z, + zone_point->heading + ) + ); + + c->Message( + Chat::White, + fmt::format( + "Client Side Zone Point x [{}] y [{}] z [{}] h [{}] number [{}] | To [{}] ({}) x [{}] y [{}] z [{}] h [{}]", + zone_point->x, + zone_point->y, + zone_point->z, + zone_point->heading, + zone_point->number, + zone_long_name.c_str(), + zone_point->target_zone_id, + zone_point->target_x, + zone_point->target_y, + zone_point->target_z, + zone_point->target_heading + ).c_str() + ); + + iterator.Advance(); + + found_zone_points++; + } + + if (found_zone_points == 0) { + c->Message(Chat::White, "There were no zone points found..."); + } + + c->SendChatLineBreak(); + +} + diff --git a/zone/gm_commands/shutdown.cpp b/zone/gm_commands/shutdown.cpp new file mode 100755 index 000000000..2af0eaf67 --- /dev/null +++ b/zone/gm_commands/shutdown.cpp @@ -0,0 +1,8 @@ +#include "../client.h" +#include "../../world/main.h" + +void command_shutdown(Client *c, const Seperator *sep) +{ + CatchSignal(2); +} + diff --git a/zone/gm_commands/size.cpp b/zone/gm_commands/size.cpp new file mode 100755 index 000000000..3ff412549 --- /dev/null +++ b/zone/gm_commands/size.cpp @@ -0,0 +1,46 @@ +#include "../client.h" + +void command_size(Client *c, const Seperator *sep) +{ + Mob *target = c->GetTarget(); + if (!sep->IsNumber(1)) { + c->Message(Chat::White, "Usage: #size [0 - 255] (Decimal increments are allowed)"); + } + else { + float newsize = atof(sep->arg[1]); + if (newsize > 255) { + c->Message(Chat::White, "Error: #size: Size can not be greater than 255."); + } + else if (newsize < 0) { + c->Message(Chat::White, "Error: #size: Size can not be less than 0."); + } + else if (!target) { + c->Message(Chat::White, "Error: this command requires a target"); + } + else { + uint16 Race = target->GetModel(); + uint8 Gender = target->GetGender(); + uint8 Texture = 0xFF; + uint8 HelmTexture = 0xFF; + uint8 HairColor = target->GetHairColor(); + uint8 BeardColor = target->GetBeardColor(); + uint8 EyeColor1 = target->GetEyeColor1(); + uint8 EyeColor2 = target->GetEyeColor2(); + uint8 HairStyle = target->GetHairStyle(); + uint8 LuclinFace = target->GetLuclinFace(); + uint8 Beard = target->GetBeard(); + uint32 DrakkinHeritage = target->GetDrakkinHeritage(); + uint32 DrakkinTattoo = target->GetDrakkinTattoo(); + uint32 DrakkinDetails = target->GetDrakkinDetails(); + + target->SendIllusionPacket( + Race, Gender, Texture, HelmTexture, HairColor, BeardColor, + EyeColor1, EyeColor2, HairStyle, LuclinFace, Beard, 0xFF, + DrakkinHeritage, DrakkinTattoo, DrakkinDetails, newsize + ); + + c->Message(Chat::White, "Size = %f", atof(sep->arg[1])); + } + } +} + diff --git a/zone/gm_commands/spawn.cpp b/zone/gm_commands/spawn.cpp new file mode 100755 index 000000000..2dd42ccd6 --- /dev/null +++ b/zone/gm_commands/spawn.cpp @@ -0,0 +1,29 @@ +#include "../client.h" + +void command_spawn(Client *c, const Seperator *sep) +{ + if (sep->arg[1][0] != 0) { + Client *client = entity_list.GetClientByName(sep->arg[1]); + if (client) { + c->Message(Chat::White, "You cannot spawn a mob with the same name as a character!"); + return; + } + } + + NPC *npc = NPC::SpawnNPC(sep->argplus[1], c->GetPosition(), c); + if (!npc) { + c->Message( + Chat::White, + "Format: #spawn name race level material hp gender class priweapon secweapon merchantid bodytype - spawns a npc those parameters." + ); + c->Message( + Chat::White, + "Name Format: NPCFirstname_NPCLastname - All numbers in a name are stripped and \"_\" characters become a space." + ); + c->Message( + Chat::White, + "Note: Using \"-\" for gender will autoselect the gender for the race. Using \"-\" for HP will use the calculated maximum HP." + ); + } +} + diff --git a/zone/gm_commands/spawnfix.cpp b/zone/gm_commands/spawnfix.cpp new file mode 100755 index 000000000..830ae870a --- /dev/null +++ b/zone/gm_commands/spawnfix.cpp @@ -0,0 +1,39 @@ +#include "../client.h" + +void command_spawnfix(Client *c, const Seperator *sep) +{ + Mob *target_mob = c->GetTarget(); + if (!target_mob || !target_mob->IsNPC()) { + c->Message(Chat::White, "Error: #spawnfix: Need an NPC target."); + return; + } + + Spawn2 *s2 = target_mob->CastToNPC()->respawn2; + + if (!s2) { + c->Message( + Chat::White, + "#spawnfix FAILED -- cannot determine which spawn entry in the database this mob came from." + ); + return; + } + + std::string query = StringFormat( + "UPDATE spawn2 SET x = '%f', y = '%f', z = '%f', heading = '%f' WHERE id = '%i'", + c->GetX(), + c->GetY(), + target_mob->GetFixedZ(c->GetPosition()), + c->GetHeading(), + s2->GetID() + ); + auto results = content_db.QueryDatabase(query); + if (!results.Success()) { + c->Message(Chat::Red, "Update failed! MySQL gave the following error:"); + c->Message(Chat::Red, results.ErrorMessage().c_str()); + return; + } + + c->Message(Chat::White, "Updating coordinates successful."); + target_mob->Depop(false); +} + diff --git a/zone/gm_commands/spawnstatus.cpp b/zone/gm_commands/spawnstatus.cpp new file mode 100755 index 000000000..9c9307bd8 --- /dev/null +++ b/zone/gm_commands/spawnstatus.cpp @@ -0,0 +1,28 @@ +#include "../client.h" + +void command_spawnstatus(Client *c, const Seperator *sep) +{ + if ((sep->arg[1][0] == 'e') | (sep->arg[1][0] == 'E')) { + // show only enabled spawns + zone->ShowEnabledSpawnStatus(c); + } + else if ((sep->arg[1][0] == 'd') | (sep->arg[1][0] == 'D')) { + // show only disabled spawns + zone->ShowDisabledSpawnStatus(c); + } + else if ((sep->arg[1][0] == 'a') | (sep->arg[1][0] == 'A')) { + // show all spawn staus with no filters + zone->SpawnStatus(c); + } + else if (sep->IsNumber(1)) { + // show spawn status by spawn2 id + zone->ShowSpawnStatusByID(c, atoi(sep->arg[1])); + } + else if (strcmp(sep->arg[1], "help") == 0) { + c->Message(Chat::White, "Usage: #spawnstatus <[a]ll | [d]isabled | [e]nabled | {Spawn2 ID}>"); + } + else { + zone->SpawnStatus(c); + } +} + diff --git a/zone/gm_commands/spellinfo.cpp b/zone/gm_commands/spellinfo.cpp new file mode 100755 index 000000000..a40a3be66 --- /dev/null +++ b/zone/gm_commands/spellinfo.cpp @@ -0,0 +1,154 @@ +#include "../client.h" + +void command_spellinfo(Client *c, const Seperator *sep) +{ + if (sep->arg[1][0] == 0) { + c->Message(Chat::White, "Usage: #spellinfo [spell_id]"); + } + else { + short int spell_id = atoi(sep->arg[1]); + const struct SPDat_Spell_Struct *s = &spells[spell_id]; + c->Message(Chat::White, "Spell info for spell #%d:", spell_id); + c->Message(Chat::White, " name: %s", s->name); + c->Message(Chat::White, " player_1: %s", s->player_1); + c->Message(Chat::White, " teleport_zone: %s", s->teleport_zone); + c->Message(Chat::White, " you_cast: %s", s->you_cast); + c->Message(Chat::White, " other_casts: %s", s->other_casts); + c->Message(Chat::White, " cast_on_you: %s", s->cast_on_you); + c->Message(Chat::White, " spell_fades: %s", s->spell_fades); + c->Message(Chat::White, " range: %f", s->range); + c->Message(Chat::White, " aoe_range: %f", s->aoe_range); + c->Message(Chat::White, " push_back: %f", s->push_back); + c->Message(Chat::White, " push_up: %f", s->push_up); + c->Message(Chat::White, " cast_time: %d", s->cast_time); + c->Message(Chat::White, " recovery_time: %d", s->recovery_time); + c->Message(Chat::White, " recast_time: %d", s->recast_time); + c->Message(Chat::White, " buff_duration_formula: %d", s->buff_duration_formula); + c->Message(Chat::White, " buff_duration: %d", s->buff_duration); + c->Message(Chat::White, " AEDuration: %d", s->aoe_duration); + c->Message(Chat::White, " mana: %d", s->mana); + c->Message( + Chat::White, + " base[12]: %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d", + s->base_value[0], + s->base_value[1], + s->base_value[2], + s->base_value[3], + s->base_value[4], + s->base_value[5], + s->base_value[6], + s->base_value[7], + s->base_value[8], + s->base_value[9], + s->base_value[10], + s->base_value[11] + ); + c->Message( + Chat::White, + " base22[12]: %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d", + s->limit_value[0], + s->limit_value[1], + s->limit_value[2], + s->limit_value[3], + s->limit_value[4], + s->limit_value[5], + s->limit_value[6], + s->limit_value[7], + s->limit_value[8], + s->limit_value[9], + s->limit_value[10], + s->limit_value[11] + ); + c->Message( + Chat::White, + " max[12]: %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d", + s->max_value[0], + s->max_value[1], + s->max_value[2], + s->max_value[3], + s->max_value[4], + s->max_value[5], + s->max_value[6], + s->max_value[7], + s->max_value[8], + s->max_value[9], + s->max_value[10], + s->max_value[11] + ); + c->Message( + Chat::White, + " components[4]: %d, %d, %d, %d", + s->component[0], + s->component[1], + s->component[2], + s->component[3] + ); + c->Message( + Chat::White, + " component_counts[4]: %d, %d, %d, %d", + s->component_count[0], + s->component_count[1], + s->component_count[2], + s->component_count[3] + ); + c->Message( + Chat::White, + " NoexpendReagent[4]: %d, %d, %d, %d", + s->no_expend_reagent[0], + s->no_expend_reagent[1], + s->no_expend_reagent[2], + s->no_expend_reagent[3] + ); + c->Message( + Chat::White, + " formula[12]: 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x", + s->formula[0], + s->formula[1], + s->formula[2], + s->formula[3], + s->formula[4], + s->formula[5], + s->formula[6], + s->formula[7], + s->formula[8], + s->formula[9], + s->formula[10], + s->formula[11] + ); + c->Message(Chat::White, " goodEffect: %d", s->good_effect); + c->Message(Chat::White, " Activated: %d", s->activated); + c->Message(Chat::White, " resisttype: %d", s->resist_type); + c->Message( + Chat::White, + " effectid[12]: 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x", + s->effect_id[0], + s->effect_id[1], + s->effect_id[2], + s->effect_id[3], + s->effect_id[4], + s->effect_id[5], + s->effect_id[6], + s->effect_id[7], + s->effect_id[8], + s->effect_id[9], + s->effect_id[10], + s->effect_id[11] + ); + c->Message(Chat::White, " targettype: %d", s->target_type); + c->Message(Chat::White, " basediff: %d", s->base_difficulty); + c->Message(Chat::White, " skill: %d", s->skill); + c->Message(Chat::White, " zonetype: %d", s->zone_type); + c->Message(Chat::White, " EnvironmentType: %d", s->environment_type); + c->Message(Chat::White, " TimeOfDay: %d", s->time_of_day); + c->Message( + Chat::White, " classes[15]: %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d", + s->classes[0], s->classes[1], s->classes[2], s->classes[3], s->classes[4], + s->classes[5], s->classes[6], s->classes[7], s->classes[8], s->classes[9], + s->classes[10], s->classes[11], s->classes[12], s->classes[13], s->classes[14] + ); + c->Message(Chat::White, " CastingAnim: %d", s->casting_animation); + c->Message(Chat::White, " SpellAffectIndex: %d", s->spell_affect_index); + c->Message(Chat::White, " RecourseLink: %d", s->recourse_link); + } +} + diff --git a/zone/gm_commands/stun.cpp b/zone/gm_commands/stun.cpp new file mode 100755 index 000000000..53bfad8cc --- /dev/null +++ b/zone/gm_commands/stun.cpp @@ -0,0 +1,63 @@ +#include "../client.h" + +void command_stun(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments || !sep->IsNumber(1)) { + c->Message(Chat::White, "Usage: #stun [Duration]"); + return; + } + + Mob *target = c; + int duration = static_cast(std::min(std::stoll(sep->arg[1]), (long long) 2000000000)); + + if (duration < 0) { + duration = 0; + } + + if (c->GetTarget()) { + target = c->GetTarget(); + if (target->IsClient()) { + target->CastToClient()->Stun(duration); + } else if (target->IsNPC()) { + target->CastToNPC()->Stun(duration); + } + } else { + c->Stun(duration); + } + + std::string stun_message = ( + duration ? + fmt::format( + "You stunned {} for {}.", + ( + c == target ? + "yourself" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ), + ConvertMillisecondsToTime(duration) + ) : + fmt::format( + "You unstunned {}.", + ( + c == target ? + "yourself" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ) + ) + ); + c->Message( + Chat::White, + stun_message.c_str() + ); +} + + diff --git a/zone/gm_commands/summon.cpp b/zone/gm_commands/summon.cpp new file mode 100755 index 000000000..586217112 --- /dev/null +++ b/zone/gm_commands/summon.cpp @@ -0,0 +1,97 @@ +#include "../client.h" +#include "../worldserver.h" + +extern WorldServer worldserver; + +#include "../corpse.h" + +void command_summon(Client *c, const Seperator *sep) +{ + Mob *t; + + if (sep->arg[1][0] != 0) // arg specified + { + Client *client = entity_list.GetClientByName(sep->arg[1]); + if (client != 0) { // found player in zone + t = client->CastToMob(); + } + else { + if (!worldserver.Connected()) { + c->Message(Chat::White, "Error: World server disconnected."); + } + else { // player is in another zone + //Taking this command out until we test the factor of 8 in ServerOP_ZonePlayer + //c->Message(Chat::White, "Summoning player from another zone not yet implemented."); + //return; + + auto pack = new ServerPacket(ServerOP_ZonePlayer, sizeof(ServerZonePlayer_Struct)); + ServerZonePlayer_Struct *szp = (ServerZonePlayer_Struct *) pack->pBuffer; + strcpy(szp->adminname, c->GetName()); + szp->adminrank = c->Admin(); + szp->ignorerestrictions = 2; + strcpy(szp->name, sep->arg[1]); + strcpy(szp->zone, zone->GetShortName()); + szp->x_pos = c->GetX(); // May need to add a factor of 8 in here.. + szp->y_pos = c->GetY(); + szp->z_pos = c->GetZ(); + szp->instance_id = zone->GetInstanceID(); + worldserver.SendPacket(pack); + safe_delete(pack); + } + return; + } + } + else if (c->GetTarget()) { // have target + t = c->GetTarget(); + } + else { + c->Message(Chat::White, "Usage: #summon [charname] Either target or charname is required"); + return; + } + + if (!t) { + return; + } + + if (t->IsNPC()) { // npc target + c->Message( + Chat::White, + "Summoning NPC %s to %1.1f, %1.1f, %1.1f", + t->GetName(), + c->GetX(), + c->GetY(), + c->GetZ()); + t->CastToNPC()->GMMove(c->GetX(), c->GetY(), c->GetZ(), c->GetHeading()); + t->CastToNPC()->SaveGuardSpot(glm::vec4(0.0f)); + } + else if (t->IsCorpse()) { // corpse target + c->Message( + Chat::White, + "Summoning corpse %s to %1.1f, %1.1f, %1.1f", + t->GetName(), + c->GetX(), + c->GetY(), + c->GetZ()); + t->CastToCorpse()->GMMove(c->GetX(), c->GetY(), c->GetZ(), c->GetHeading()); + } + else if (t->IsClient()) { + c->Message( + Chat::White, + "Summoning player %s to %1.1f, %1.1f, %1.1f", + t->GetName(), + c->GetX(), + c->GetY(), + c->GetZ()); + t->CastToClient()->MovePC( + zone->GetZoneID(), + zone->GetInstanceID(), + c->GetX(), + c->GetY(), + c->GetZ(), + c->GetHeading(), + 2, + GMSummon + ); + } +} + diff --git a/zone/gm_commands/summonburiedplayercorpse.cpp b/zone/gm_commands/summonburiedplayercorpse.cpp new file mode 100755 index 000000000..5534ecadc --- /dev/null +++ b/zone/gm_commands/summonburiedplayercorpse.cpp @@ -0,0 +1,28 @@ +#include "../client.h" +#include "../corpse.h" + +void command_summonburiedplayercorpse(Client *c, const Seperator *sep) +{ + Client *t = c; + + if (c->GetTarget() && c->GetTarget()->IsClient() && c->GetGM()) { + t = c->GetTarget()->CastToClient(); + } + else { + c->Message(Chat::White, "You must first select a target!"); + return; + } + + Corpse *PlayerCorpse = database.SummonBuriedCharacterCorpses( + t->CharacterID(), + t->GetZoneID(), + zone->GetInstanceID(), + t->GetPosition()); + + if (!PlayerCorpse) { + c->Message(Chat::White, "Your target doesn't have any buried corpses."); + } + + return; +} + diff --git a/zone/gm_commands/summonitem.cpp b/zone/gm_commands/summonitem.cpp new file mode 100755 index 000000000..e3053bd71 --- /dev/null +++ b/zone/gm_commands/summonitem.cpp @@ -0,0 +1,92 @@ +#include "../client.h" + +void command_summonitem(Client *c, const Seperator *sep) +{ + uint32 item_id = 0; + int16 charges = -1; + uint32 augment_one = 0; + uint32 augment_two = 0; + uint32 augment_three = 0; + uint32 augment_four = 0; + uint32 augment_five = 0; + uint32 augment_six = 0; + int arguments = sep->argnum; + std::string cmd_msg = sep->msg; + size_t link_open = cmd_msg.find('\x12'); + size_t link_close = cmd_msg.find_last_of('\x12'); + if (link_open != link_close && (cmd_msg.length() - link_open) > EQ::constants::SAY_LINK_BODY_SIZE) { + EQ::SayLinkBody_Struct link_body; + EQ::saylink::DegenerateLinkBody(link_body, cmd_msg.substr(link_open + 1, EQ::constants::SAY_LINK_BODY_SIZE)); + item_id = link_body.item_id; + augment_one = link_body.augment_1; + augment_two = link_body.augment_2; + augment_three = link_body.augment_3; + augment_four = link_body.augment_4; + augment_five = link_body.augment_5; + augment_six = link_body.augment_6; + } + else if (!sep->IsNumber(1)) { + c->Message( + Chat::White, + "Usage: #summonitem [item id | link] [charges] [augment_one_id] [augment_two_id] [augment_three_id] [augment_four_id] [augment_five_id] [augment_six_id] (Charges are optional.)" + ); + return; + } + else { + item_id = atoi(sep->arg[1]); + } + + if (!item_id) { + c->Message(Chat::White, "Enter a valid item ID."); + return; + } + + uint8 item_status = 0; + uint8 current_status = c->Admin(); + const EQ::ItemData *item = database.GetItem(item_id); + if (item) { + item_status = item->MinStatus; + } + + if (item_status > current_status) { + c->Message( + Chat::White, + fmt::format( + "Insufficient status to summon this item, current status is {}, required status is {}.", + current_status, + item_status + ).c_str() + ); + } + + if (arguments >= 2 && sep->IsNumber(2)) { + charges = atoi(sep->arg[2]); + } + + if (arguments >= 3 && sep->IsNumber(3)) { + augment_one = atoi(sep->arg[3]); + } + + if (arguments >= 4 && sep->IsNumber(4)) { + augment_two = atoi(sep->arg[4]); + } + + if (arguments >= 5 && sep->IsNumber(5)) { + augment_three = atoi(sep->arg[5]); + } + + if (arguments >= 6 && sep->IsNumber(6)) { + augment_four = atoi(sep->arg[6]); + } + + if (arguments >= 7 && sep->IsNumber(7)) { + augment_five = atoi(sep->arg[7]); + } + + if (arguments == 8 && sep->IsNumber(8)) { + augment_six = atoi(sep->arg[8]); + } + + c->SummonItem(item_id, charges, augment_one, augment_two, augment_three, augment_four, augment_five, augment_six); +} + diff --git a/zone/gm_commands/suspend.cpp b/zone/gm_commands/suspend.cpp new file mode 100755 index 000000000..49721507e --- /dev/null +++ b/zone/gm_commands/suspend.cpp @@ -0,0 +1,101 @@ +#include "../client.h" +#include "../worldserver.h" + +extern WorldServer worldserver; + +void command_suspend(Client *c, const Seperator *sep) +{ + if ((sep->arg[1][0] == 0) || (sep->arg[2][0] == 0)) { + c->Message( + Chat::White, + "Usage: #suspend (Specify 0 days to lift the suspension immediately) " + ); + return; + } + + int duration = atoi(sep->arg[2]); + + if (duration < 0) { + duration = 0; + } + + std::string message; + + if (duration > 0) { + int i = 3; + while (1) { + if (sep->arg[i][0] == 0) { + break; + } + + if (message.length() > 0) { + message.push_back(' '); + } + + message += sep->arg[i]; + ++i; + } + + if (message.length() == 0) { + c->Message( + Chat::White, + "Usage: #suspend (Specify 0 days to lift the suspension immediately) " + ); + return; + } + } + + auto escName = new char[strlen(sep->arg[1]) * 2 + 1]; + database.DoEscapeString(escName, sep->arg[1], strlen(sep->arg[1])); + int accountID = database.GetAccountIDByChar(escName); + safe_delete_array(escName); + + if (accountID <= 0) { + c->Message(Chat::Red, "Character does not exist."); + return; + } + + std::string query = StringFormat( + "UPDATE `account` SET `suspendeduntil` = DATE_ADD(NOW(), INTERVAL %i DAY), " + "suspend_reason = '%s' WHERE `id` = %i", + duration, EscapeString(message).c_str(), accountID + ); + auto results = database.QueryDatabase(query); + + if (duration) { + c->Message( + Chat::Red, + "Account number %i with the character %s has been temporarily suspended for %i day(s).", + accountID, + sep->arg[1], + duration + ); + } + else { + c->Message( + Chat::Red, + "Account number %i with the character %s is no longer suspended.", + accountID, + sep->arg[1] + ); + } + + Client *bannedClient = entity_list.GetClientByName(sep->arg[1]); + + if (bannedClient) { + bannedClient->WorldKick(); + return; + } + + auto pack = new ServerPacket(ServerOP_KickPlayer, sizeof(ServerKickPlayer_Struct)); + ServerKickPlayer_Struct *sks = (ServerKickPlayer_Struct *) pack->pBuffer; + + strn0cpy(sks->adminname, c->GetName(), sizeof(sks->adminname)); + strn0cpy(sks->name, sep->arg[1], sizeof(sks->name)); + sks->adminrank = c->Admin(); + + worldserver.SendPacket(pack); + + safe_delete(pack); +} + diff --git a/zone/gm_commands/task.cpp b/zone/gm_commands/task.cpp new file mode 100755 index 000000000..a616eb06d --- /dev/null +++ b/zone/gm_commands/task.cpp @@ -0,0 +1,198 @@ +#include "../client.h" +#include "../worldserver.h" + +extern WorldServer worldserver; + +#include "../../common/shared_tasks.h" + +void command_task(Client *c, const Seperator *sep) +{ + //super-command for managing tasks + if (sep->arg[1][0] == '\0' || !strcasecmp(sep->arg[1], "help")) { + c->Message(Chat::White, "Syntax: #task [subcommand]"); + c->Message(Chat::White, "------------------------------------------------"); + c->Message(Chat::White, "# Task System Commands"); + c->Message(Chat::White, "------------------------------------------------"); + c->Message( + Chat::White, + fmt::format( + "--- [{}] List active tasks for a client", + EQ::SayLinkEngine::GenerateQuestSaylink("#task show", false, "show") + ).c_str() + ); + c->Message(Chat::White, "--- update [count] | Updates task"); + c->Message(Chat::White, "--- assign | Assigns task to client"); + c->Message( + Chat::White, + fmt::format( + "--- [{}] Reload all Task information from the database", + EQ::SayLinkEngine::GenerateQuestSaylink("#task reloadall", false, "reloadall") + ).c_str() + ); + c->Message( + Chat::White, + fmt::format( + "--- [{}] Reload Task and Activity information for a single task", + EQ::SayLinkEngine::GenerateQuestSaylink("#task reload task", false, "reload task") + ).c_str() + ); + c->Message( + Chat::White, + fmt::format( + "--- [{}] Reload goal/reward list information", + EQ::SayLinkEngine::GenerateQuestSaylink("#task reload lists", false, "reload lists") + ).c_str() + ); + c->Message( + Chat::White, + fmt::format( + "--- [{}] Reload proximity information", + EQ::SayLinkEngine::GenerateQuestSaylink("#task reload prox", false, "reload prox") + ).c_str() + ); + c->Message( + Chat::White, + fmt::format( + "--- [{}] Reload task set information", + EQ::SayLinkEngine::GenerateQuestSaylink("#task reload sets", false, "reload sets") + ).c_str() + ); + c->Message( + Chat::White, + fmt::format( + "--- [{}] Purges targeted characters task timers", + EQ::SayLinkEngine::GenerateQuestSaylink("#task purgetimers", false, "purgetimers") + ).c_str() + ); + + c->Message(Chat::White, "------------------------------------------------"); + c->Message(Chat::White, "# Shared Task System Commands"); + c->Message(Chat::White, "------------------------------------------------"); + c->Message( + Chat::White, + fmt::format( + "--- [{}] Purges all active Shared Tasks in memory and database ", + EQ::SayLinkEngine::GenerateQuestSaylink("#task sharedpurge", false, "sharedpurge") + ).c_str() + ); + + return; + } + + Client *client_target = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + client_target = c->GetTarget()->CastToClient(); + } + + if (!strcasecmp(sep->arg[1], "show")) { + c->ShowClientTasks(client_target); + return; + } + + if (!strcasecmp(sep->arg[1], "purgetimers")) { + c->Message(15, fmt::format("{}'s task timers have been purged", client_target->GetCleanName()).c_str()); + if (client_target != c) { + client_target->Message(15, "[GM] Your task timers have been purged by a GM"); + } + client_target->PurgeTaskTimers(); + return; + } + + if (!strcasecmp(sep->arg[1], "update")) { + if (sep->argnum >= 3) { + int task_id = atoi(sep->arg[2]); + int activity_id = atoi(sep->arg[3]); + int count = 1; + + if (sep->argnum >= 4) { + count = atoi(sep->arg[4]); + if (count <= 0) { + count = 1; + } + } + c->Message( + Chat::Yellow, + "Updating Task [%i] Activity [%i] Count [%i] for client [%s]", + task_id, + activity_id, + count, + client_target->GetCleanName() + ); + client_target->UpdateTaskActivity(task_id, activity_id, count); + c->ShowClientTasks(client_target); + } + return; + } + + if (!strcasecmp(sep->arg[1], "sharedpurge")) { + if (!strcasecmp(sep->arg[2], "confirm")) { + LogTasksDetail("Sending purge request"); + auto pack = new ServerPacket(ServerOP_SharedTaskPurgeAllCommand, 0); + worldserver.SendPacket(pack); + safe_delete(pack); + + return; + } + + c->Message( + Chat::White, + fmt::format( + "[WARNING] This will purge all active Shared Tasks [{}]?", + EQ::SayLinkEngine::GenerateQuestSaylink("#task sharedpurge confirm", false, "confirm") + ).c_str() + ); + + return; + } + + if (!strcasecmp(sep->arg[1], "assign")) { + int task_id = atoi(sep->arg[2]); + if ((task_id > 0) && (task_id < MAXTASKS)) { + client_target->AssignTask(task_id, 0, false); + c->Message(Chat::Yellow, "Assigned task [%i] to [%s]", task_id, client_target->GetCleanName()); + } + return; + } + + if (!strcasecmp(sep->arg[1], "reloadall")) { + c->Message(Chat::Yellow, "Sending reloadtasks to world"); + worldserver.SendReloadTasks(RELOADTASKS); + c->Message(Chat::Yellow, "Back again"); + return; + } + + if (!strcasecmp(sep->arg[1], "reload")) { + if (sep->arg[2][0] != '\0') { + if (!strcasecmp(sep->arg[2], "lists")) { + c->Message(Chat::Yellow, "Sending reload lists to world"); + worldserver.SendReloadTasks(RELOADTASKGOALLISTS); + c->Message(Chat::Yellow, "Reloaded"); + return; + } + if (!strcasecmp(sep->arg[2], "prox")) { + c->Message(Chat::Yellow, "Sending reload proximities to world"); + worldserver.SendReloadTasks(RELOADTASKPROXIMITIES); + c->Message(Chat::Yellow, "Reloaded"); + return; + } + if (!strcasecmp(sep->arg[2], "sets")) { + c->Message(Chat::Yellow, "Sending reload task sets to world"); + worldserver.SendReloadTasks(RELOADTASKSETS); + c->Message(Chat::Yellow, "Reloaded"); + return; + } + if (!strcasecmp(sep->arg[2], "task") && (sep->arg[3][0] != '\0')) { + int task_id = atoi(sep->arg[3]); + if ((task_id > 0) && (task_id < MAXTASKS)) { + c->Message(Chat::Yellow, "Sending reload task %i to world", task_id); + worldserver.SendReloadTasks(RELOADTASKS, task_id); + c->Message(Chat::Yellow, "Reloaded"); + return; + } + } + } + + } + c->Message(Chat::White, "Unable to interpret command. Type #task help"); + +} diff --git a/zone/gm_commands/tattoo.cpp b/zone/gm_commands/tattoo.cpp new file mode 100755 index 000000000..8d1433bbe --- /dev/null +++ b/zone/gm_commands/tattoo.cpp @@ -0,0 +1,37 @@ +#include "../client.h" + +void command_tattoo(Client *c, const Seperator *sep) +{ + Mob *target = c->GetTarget(); + if (!sep->IsNumber(1)) { + c->Message(Chat::White, "Usage: #tattoo [number of Drakkin tattoo]"); + } + else if (!target) { + c->Message(Chat::White, "Error: this command requires a target"); + } + else { + uint16 Race = target->GetRace(); + uint8 Gender = target->GetGender(); + uint8 Texture = 0xFF; + uint8 HelmTexture = 0xFF; + uint8 HairColor = target->GetHairColor(); + uint8 BeardColor = target->GetBeardColor(); + uint8 EyeColor1 = target->GetEyeColor1(); + uint8 EyeColor2 = target->GetEyeColor2(); + uint8 HairStyle = target->GetHairStyle(); + uint8 LuclinFace = target->GetLuclinFace(); + uint8 Beard = target->GetBeard(); + uint32 DrakkinHeritage = target->GetDrakkinHeritage(); + uint32 DrakkinTattoo = atoi(sep->arg[1]); + uint32 DrakkinDetails = target->GetDrakkinDetails(); + + target->SendIllusionPacket( + Race, Gender, Texture, HelmTexture, HairColor, BeardColor, + EyeColor1, EyeColor2, HairStyle, LuclinFace, Beard, 0xFF, + DrakkinHeritage, DrakkinTattoo, DrakkinDetails + ); + + c->Message(Chat::White, "Tattoo = %i", atoi(sep->arg[1])); + } +} + diff --git a/zone/gm_commands/tempname.cpp b/zone/gm_commands/tempname.cpp new file mode 100755 index 000000000..849549472 --- /dev/null +++ b/zone/gm_commands/tempname.cpp @@ -0,0 +1,22 @@ +#include "../client.h" + +void command_tempname(Client *c, const Seperator *sep) +{ + Mob *target; + target = c->GetTarget(); + + if (!target) { + c->Message(Chat::White, "Usage: #tempname newname (requires a target)"); + } + else if (strlen(sep->arg[1]) > 0) { + char *oldname = strdup(target->GetName()); + target->TempName(sep->arg[1]); + c->Message(Chat::White, "Renamed %s to %s", oldname, sep->arg[1]); + free(oldname); + } + else { + target->TempName(); + c->Message(Chat::White, "Restored the original name"); + } +} + diff --git a/zone/gm_commands/texture.cpp b/zone/gm_commands/texture.cpp new file mode 100755 index 000000000..09f00df4b --- /dev/null +++ b/zone/gm_commands/texture.cpp @@ -0,0 +1,65 @@ +#include "../client.h" + +void command_texture(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments || !sep->IsNumber(1)) { + c->Message(Chat::White, "Usage: #texture [Texture] [Helmet Texture]"); + return; + } + + auto texture = static_cast(std::min(std::stoul(sep->arg[1]), (unsigned long) 65535)); + auto helmet_texture = static_cast( + sep->IsNumber(2) ? + std::min(std::stoul(sep->arg[2]), (unsigned long) 255) : + 0 + ); + + Mob* target = c; + if (c->GetTarget() && c->Admin() >= commandTextureOthers) { + target = c->GetTarget(); + } + + if (Mob::IsPlayerRace(target->GetModel())) { // Player Races Wear Armor, so Wearchange is sent instead + for ( + int texture_slot = EQ::textures::textureBegin; + texture_slot <= EQ::textures::LastTintableTexture; + texture_slot++ + ) { + target->SendTextureWC(texture_slot, texture); + } + } else { // Non-Player Races only need Illusion Packets to be sent for texture + target->SendIllusionPacket( + target->GetModel(), + target->GetGender(), + texture, + helmet_texture + ); + } + + c->Message( + Chat::White, + fmt::format( + "Texture Changed for {} | Texture: {}{}", + ( + c == target ? + "Yourself" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ), + texture, + ( + Mob::IsPlayerRace(target->GetModel()) ? + "" : + fmt::format( + " Helmet Texture: {}", + helmet_texture + ) + ) + ).c_str() + ); +} + diff --git a/zone/gm_commands/time.cpp b/zone/gm_commands/time.cpp new file mode 100755 index 000000000..fd241d75f --- /dev/null +++ b/zone/gm_commands/time.cpp @@ -0,0 +1,32 @@ +#include "../client.h" + +void command_time(Client *c, const Seperator *sep) +{ + char timeMessage[255]; + int minutes = 0; + if (sep->IsNumber(1)) { + if (sep->IsNumber(2)) { + minutes = atoi(sep->arg[2]); + } + c->Message(Chat::Red, "Setting world time to %s:%i (Timezone: 0)...", sep->arg[1], minutes); + zone->SetTime(atoi(sep->arg[1]) + 1, minutes); + LogInfo("{} :: Setting world time to {}:{} (Timezone: 0)...", c->GetCleanName(), sep->arg[1], minutes); + } + else { + c->Message(Chat::Red, "To set the Time: #time HH [MM]"); + TimeOfDay_Struct eqTime; + zone->zone_time.GetCurrentEQTimeOfDay(time(0), &eqTime); + sprintf( + timeMessage, "%02d:%s%d %s (Timezone: %ih %im)", + ((eqTime.hour - 1) % 12) == 0 ? 12 : ((eqTime.hour - 1) % 12), + (eqTime.minute < 10) ? "0" : "", + eqTime.minute, + (eqTime.hour >= 13) ? "pm" : "am", + zone->zone_time.getEQTimeZoneHr(), + zone->zone_time.getEQTimeZoneMin() + ); + c->Message(Chat::Red, "It is now %s.", timeMessage); + LogInfo("Current Time is: {}", timeMessage); + } +} + diff --git a/zone/gm_commands/timers.cpp b/zone/gm_commands/timers.cpp new file mode 100755 index 000000000..4e36661e4 --- /dev/null +++ b/zone/gm_commands/timers.cpp @@ -0,0 +1,46 @@ +#include "../client.h" + +void command_timers(Client *c, const Seperator *sep) +{ + auto target = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); + } + + std::vector> timers; + target->GetPTimers().ToVector(timers); + + std::string popup_title = fmt::format( + "Recast Timers for {}", + c == target ? + "Yourself" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ); + + std::string popup_text = ""; + + popup_text += ""; + + for (const auto& timer : timers) { + auto remaining_time = timer.second->GetRemainingTime(); + if (remaining_time) { + popup_text += fmt::format( + "", + timer.first, + ConvertSecondsToTime(remaining_time) + ); + } + } + + popup_text += "
Timer IDRemaining
{}{}
"; + + c->SendPopupToClient( + popup_title.c_str(), + popup_text.c_str() + ); +} + diff --git a/zone/gm_commands/timezone.cpp b/zone/gm_commands/timezone.cpp new file mode 100755 index 000000000..d4e628cc0 --- /dev/null +++ b/zone/gm_commands/timezone.cpp @@ -0,0 +1,32 @@ +#include "../client.h" + +void command_timezone(Client *c, const Seperator *sep) +{ + if (sep->arg[1][0] == 0 && !sep->IsNumber(1)) { + c->Message(Chat::Red, "Usage: #timezone HH [MM]"); + c->Message( + Chat::Red, + "Current timezone is: %ih %im", + zone->zone_time.getEQTimeZoneHr(), + zone->zone_time.getEQTimeZoneMin()); + } + else { + uint8 hours = atoi(sep->arg[1]); + uint8 minutes = atoi(sep->arg[2]); + if (!sep->IsNumber(2)) { + minutes = 0; + } + c->Message(Chat::Red, "Setting timezone to %i h %i m", hours, minutes); + uint32 ntz = (hours * 60) + minutes; + zone->zone_time.setEQTimeZone(ntz); + content_db.SetZoneTZ(zone->GetZoneID(), zone->GetInstanceVersion(), ntz); + + // Update all clients with new TZ. + auto outapp = new EQApplicationPacket(OP_TimeOfDay, sizeof(TimeOfDay_Struct)); + TimeOfDay_Struct *tod = (TimeOfDay_Struct *) outapp->pBuffer; + zone->zone_time.GetCurrentEQTimeOfDay(time(0), tod); + entity_list.QueueClients(c, outapp); + safe_delete(outapp); + } +} + diff --git a/zone/gm_commands/title.cpp b/zone/gm_commands/title.cpp new file mode 100755 index 000000000..48b935b3c --- /dev/null +++ b/zone/gm_commands/title.cpp @@ -0,0 +1,68 @@ +#include "../client.h" +#include "../titles.h" + +void command_title(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments) { + c->Message( + Chat::White, + "Usage: #title [Remove|Title] [Save (0 = False, 1 = True)]" + ); + return; + } + + bool is_remove = !strcasecmp(sep->arg[1], "remove"); + std::string title = is_remove ? "" : sep->arg[1]; + bool save_title = sep->IsNumber(2) ? atobool(sep->arg[2]) : false; + + auto target = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); + } + + if (title.size() > 31) { + c->Message(Chat::White, "Title must be 31 characters or less."); + return; + } + + if (!title.empty()) { + find_replace(title, "_", " "); + } + + if (!save_title || is_remove) { + target->SetAATitle(title.c_str()); + } else if (save_title) { + title_manager.CreateNewPlayerTitle(target, title.c_str()); + } + + target->Save(); + + c->Message( + Chat::White, + fmt::format( + "Title has been {}{} for {}{}", + is_remove ? "removed" : "changed", + !is_remove && save_title ? " and saved" : "", + ( + c == target ? + "yourself" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ), + ( + is_remove ? + "." : + fmt::format( + " to '{}'.", + title + ) + ) + ).c_str() + ); +} + + diff --git a/zone/gm_commands/titlesuffix.cpp b/zone/gm_commands/titlesuffix.cpp new file mode 100755 index 000000000..7dbad5e2b --- /dev/null +++ b/zone/gm_commands/titlesuffix.cpp @@ -0,0 +1,66 @@ +#include "../client.h" +#include "../titles.h" + +void command_titlesuffix(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments) { + c->Message( + Chat::White, + "Usage: #titlesuffix [Remove|Title] [Save (0 = False, 1 = True)]" + ); + return; + } + + bool is_remove = !strcasecmp(sep->arg[1], "remove"); + std::string suffix = is_remove ? "" : sep->arg[1]; + bool save_suffix = sep->IsNumber(2) ? atobool(sep->arg[2]) : false; + + auto target = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); + } + + if (suffix.size() > 31) { + c->Message(Chat::White, "Title suffix must be 31 characters or less."); + return; + } + + if (!suffix.empty()) { + find_replace(suffix, "_", " "); + } + + if (!save_suffix || is_remove) { + target->SetTitleSuffix(suffix.c_str()); + } else if (save_suffix) { + title_manager.CreateNewPlayerSuffix(target, suffix.c_str()); + } + + target->Save(); + + c->Message( + Chat::White, + fmt::format( + "Title suffix has been {}{} for {}{}", + is_remove ? "removed" : "changed", + !is_remove && save_suffix ? " and saved" : "", + ( + c == target ? + "yourself" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ), + ( + is_remove ? + "." : + fmt::format( + " to '{}'.", + suffix + ) + ) + ).c_str() + ); +} diff --git a/zone/gm_commands/traindisc.cpp b/zone/gm_commands/traindisc.cpp new file mode 100755 index 000000000..7e5fff255 --- /dev/null +++ b/zone/gm_commands/traindisc.cpp @@ -0,0 +1,68 @@ +#include "../client.h" + +void command_traindisc(Client *c, const Seperator *sep) +{ + Client *target = c; + if (c->GetTarget() && c->GetTarget()->IsClient() && c->GetGM()) { + target = c->GetTarget()->CastToClient(); + } + + if (sep->argnum < 1 || !sep->IsNumber(1)) { + c->Message(Chat::White, "FORMAT: #traindisc "); + return; + } + + uint8 rule_max_level = (uint8) RuleI(Character, MaxLevel); + uint8 max_level = (uint8) std::stoi(sep->arg[1]); + uint8 min_level = ( + sep->IsNumber(2) ? + (uint8) + std::stoi(sep->arg[2]) : + 1 + ); // Default to Level 1 if there isn't a 2nd argument + + if (!c->GetGM()) { // Default to Character:MaxLevel if we're not a GM and Level is higher than the max level + if (max_level > rule_max_level) { + max_level = rule_max_level; + } + + if (min_level > rule_max_level) { + min_level = rule_max_level; + } + } + + if (max_level < 1 || min_level < 1) { + c->Message(Chat::White, "ERROR: Level must be greater than or equal to 1."); + return; + } + + if (min_level > max_level) { + c->Message(Chat::White, "ERROR: Minimum Level must be less than or equal to Maximum Level."); + return; + } + + uint16 learned_disciplines = target->LearnDisciplines(min_level, max_level); + if (c != target) { + std::string discipline_message = ( + learned_disciplines > 0 ? + ( + learned_disciplines == 1 ? + "A new discipline" : + fmt::format( + "{} New disciplines", + learned_disciplines + ) + ) : + "No new disciplines" + ); + c->Message( + Chat::White, + fmt::format( + "{} learned for {}.", + discipline_message, + target->GetCleanName() + ).c_str() + ); + } +} + diff --git a/zone/gm_commands/trapinfo.cpp b/zone/gm_commands/trapinfo.cpp new file mode 100755 index 000000000..6d2d8fcf1 --- /dev/null +++ b/zone/gm_commands/trapinfo.cpp @@ -0,0 +1,7 @@ +#include "../client.h" + +void command_trapinfo(Client *c, const Seperator *sep) +{ + entity_list.GetTrapInfo(c); +} + diff --git a/zone/gm_commands/tune.cpp b/zone/gm_commands/tune.cpp new file mode 100755 index 000000000..e34f9aa49 --- /dev/null +++ b/zone/gm_commands/tune.cpp @@ -0,0 +1,524 @@ +#include "../client.h" + +void command_tune(Client *c, const Seperator *sep) +{ + //Work in progress - Kayen + + if (sep->arg[1][0] == '\0' || !strcasecmp(sep->arg[1], "help")) { + c->Message(Chat::White, "Syntax: #tune [subcommand]."); + c->Message(Chat::White, "-- Tune System Commands --"); + c->Message( + Chat::White, + "-- Usage: Returns recommended combat statistical values based on a desired outcome through simulated combat." + ); + c->Message( + Chat::White, + "-- This commmand can answer the following difficult questions whening tunings NPCs and Players." + ); + c->Message( + Chat::White, + "-- Question: What is the average damage mitigation my AC provides against a specific targets attacks?" + ); + c->Message( + Chat::White, + "-- Question: What is amount of AC would I need to add to acheive a specific average damage mitigation agianst specific targets attacks?" + ); + c->Message( + Chat::White, + "-- Question: What is amount of AC would I need to add to my target to acheive a specific average damage mitigation from my attacks?" + ); + c->Message(Chat::White, "-- Question: What is my targets average AC damage mitigation based on my ATK stat?"); + c->Message( + Chat::White, + "-- Question: What is amount of ATK would I need to add to myself to acheive a specific average damage mitigation on my target?" + ); + c->Message( + Chat::White, + "-- Question: What is amount of ATK would I need to add to my target to acheive a specific average AC damage mitigation on myself?" + ); + c->Message(Chat::White, "-- Question: What is my hit chance against a target?"); + c->Message( + Chat::White, + "-- Question: What is the amount of avoidance I need to add to my target to achieve a specific hit chance?" + ); + c->Message( + Chat::White, + "-- Question: What is the amount of accuracy I need to add to my target to achieve a specific chance of hitting me?" + ); + c->Message(Chat::White, "-- Question: ... and many more..."); + c->Message(Chat::White, " "); + c->Message(Chat::White, "...#tune stats [A/D]"); + c->Message( + Chat::White, + "...#tune FindATK [A/D] [pct mitigation] [interval] [loop_max] [AC override] [Info Level]" + ); + c->Message( + Chat::White, + "...#tune FindAC [A/D] [pct mitigation] [interval] [loop_max] [ATK override] [Info Level] " + ); + c->Message( + Chat::White, + "...#tune FindAccuracy [A/D] [hit chance] [interval] [loop_max] [Avoidance override] [Info Level]" + ); + c->Message( + Chat::White, + "...#tune FindAvoidance [A/D] [hit chance] [interval] [loop_max] [Accuracy override] [Info Level] " + ); + c->Message(Chat::White, " "); + c->Message(Chat::White, "-- DETAILS AND EXAMPLES ON USAGE"); + c->Message(Chat::White, " "); + c->Message( + Chat::White, + "...Returns combat statistics, including AC mitigation pct, hit chance, and avoid melee chance for attacker and defender." + ); + c->Message(Chat::White, "...#tune stats [A/D]"); + c->Message(Chat::White, "..."); + c->Message( + Chat::White, + "...Returns recommended ATK adjustment (+/-) on ATTACKER that will result in a specific average AC mitigation pct on DEFENDER. " + ); + c->Message( + Chat::White, + "...#tune FindATK [A/D] [pct mitigation] [interval][loop_max][AC override][Info Level]" + ); + c->Message( + Chat::White, + "...Example: Find the amount of ATK stat I need to add to the targeted NPC so that it hits me for 50 pct damage on average." + ); + c->Message(Chat::White, "...Example: #tune FindATK D 50"); + c->Message(Chat::White, "..."); + c->Message( + Chat::White, + "...Returns recommended AC adjustment(+/-) on DEFENDER for a specific average AC mitigation pct from ATTACKER. " + ); + c->Message( + Chat::White, + "...#tune FindAC [A/D] [pct mitigation] [interval][loop_max][ATK override][Info Level] " + ); + c->Message( + Chat::White, + "...Example: Find the amount of AC stat I need to add to the targeted NPC so that I hit it for 70 pct damage on average." + ); + c->Message(Chat::White, "...Example: #tune FindAC D 70"); + c->Message(Chat::White, "..."); + c->Message( + Chat::White, + "...Returns recommended Accuracy adjustment (+/-) on ATTACKER that will result in a specific hit chance pct on DEFENDER. " + ); + c->Message( + Chat::White, + "...#tune FindAccuracy [A/D] [hit chance] [interval][loop_max][Avoidance override][Info Level]" + ); + c->Message( + Chat::White, + "...Example: Find the amount of Accuracy stat I need to add to the targeted NPC so that it has a 60 pct hit chance against me." + ); + c->Message(Chat::White, "...Example: #tune FindAccuracy D 60"); + c->Message(Chat::White, "..."); + c->Message( + Chat::White, + "...Returns recommended Avoidance adjustment (+/-) on DEFENDER for in a specific hit chance pct from ATTACKER. " + ); + c->Message( + Chat::White, + "...#tune FindAvoidance [A/D] [hit chance] [interval][loop_max][Accuracy override][Info Level] " + ); + c->Message( + Chat::White, + "...Example: Find the amount of Avoidance stat I need to add to the targeted NPC so that I have a 30 pct hit chance against it." + ); + c->Message(Chat::White, "...Example: #tune FindAvoidance D 30"); + c->Message(Chat::White, "... "); + c->Message(Chat::White, "...Usage: [A/D] You must input either A or D."); + c->Message(Chat::White, "...Category [A] : YOU are the ATTACKER. YOUR TARGET is the DEFENDER."); + c->Message(Chat::White, "...Category [D] : YOU are the DEFENDER. YOUR TARGET is the ATTACKER."); + c->Message(Chat::White, "...If TARGET is in combat, DEFENDER is the TARGETs TARGET."); + + c->Message(Chat::White, " "); + + c->Message( + Chat::White, + "-- Warning: The calculations done in this process are intense and can potentially cause zone crashes depending on parameters set, use with caution!" + ); + c->Message(Chat::White, "-- Below are OPTIONAL parameters."); + c->Message( + Chat::White, + "-- Note: [interval] Determines how much the stat being checked increases/decreases till it finds the best result. Lower is more accurate. Default=10" + ); + c->Message( + Chat::White, + "-- Note: [loop_max] Determines how many iterations are done to increases/decreases the stat till it finds the best result. Higher is more accurate. Default=1000" + ); + c->Message( + Chat::White, + "-- Note: [Stat Override] Will override that stat on mob being checked with the specified value. Default=0" + ); + c->Message( + Chat::White, + "-- Example: If as the attacker you want to find the ATK value you would need to have agianst a target with 1000 AC to achieve an average AC mitigation of 50 pct." + ); + c->Message(Chat::White, "-- Example: #tune FindATK A 50 0 0 1000"); + c->Message(Chat::White, "-- Note: [Info Level] How much parsing detail is displayed[0 - 1]. Default: [0] "); + c->Message(Chat::White, " "); + + return; + } + /* + Category A: YOU are the attacker and your target is the defender + Category D: YOU are the defender and your target is the attacker + */ + + Mob *attacker = c; + Mob *defender = c->GetTarget(); + + if (!defender) { + c->Message(Chat::White, "[#Tune] - Error no target selected. [#Tune help]"); + return; + } + + //Use if checkings on engaged targets. + Mob *ttarget = attacker->GetTarget(); + if (ttarget) { + defender = ttarget; + } + + if (!strcasecmp(sep->arg[1], "stats")) { + + if (!strcasecmp(sep->arg[2], "A")) { + c->TuneGetStats(defender, attacker); + } + else if (!strcasecmp(sep->arg[2], "D")) { + c->TuneGetStats(attacker, defender); + } + else { + c->TuneGetStats(defender, attacker); + } + return; + } + + if (!strcasecmp(sep->arg[1], "FindATK")) { + float pct_mitigation = atof(sep->arg[3]); + int interval = atoi(sep->arg[4]); + int max_loop = atoi(sep->arg[5]); + int ac_override = atoi(sep->arg[6]); + int info_level = atoi(sep->arg[7]); + + if (!pct_mitigation) { + c->Message(Chat::White, "[#Tune] - Error must enter the desired percent mitigation on defender."); + c->Message( + Chat::White, + "...Returns recommended ATK adjustment (+/-) on ATTACKER that will result in a specific average AC mitigation pct on DEFENDER. " + ); + c->Message( + Chat::White, + "...#tune FindATK [A/D] [pct mitigation] [interval][loop_max][AC override][Info Level]" + ); + c->Message( + Chat::White, + "...Example: Find the amount of ATK stat I need to add to the targeted NPC so that it hits me for 50 pct damage on average." + ); + c->Message(Chat::White, "...Example: #tune FindATK D 50"); + return; + } + + if (!interval) { + interval = 10; + } + if (!max_loop) { + max_loop = 1000; + } + if (!ac_override) { + ac_override = 0; + } + if (!info_level) { + info_level = 0; + } + + if (!strcasecmp(sep->arg[2], "A")) { + c->TuneGetATKByPctMitigation( + defender, + attacker, + pct_mitigation, + interval, + max_loop, + ac_override, + info_level + ); + } + else if (!strcasecmp(sep->arg[2], "D")) { + c->TuneGetATKByPctMitigation( + attacker, + defender, + pct_mitigation, + interval, + max_loop, + ac_override, + info_level + ); + } + else { + c->Message(Chat::White, "#Tune - Error no category selcted. [#Tune help]"); + c->Message( + Chat::White, + "Usage #tune FindATK [A/B] [pct mitigation] [interval][loop_max][AC Overwride][Info Level] " + ); + c->Message(Chat::White, "...Usage: [A/D] You must input either A or D."); + c->Message(Chat::White, "...Category [A] : YOU are the ATTACKER. YOUR TARGET is the DEFENDER."); + c->Message(Chat::White, "...Category [D] : YOU are the DEFENDER. YOUR TARGET is the ATTACKER."); + c->Message(Chat::White, "...If TARGET is in combat, DEFENDER is the TARGETs TARGET."); + c->Message(Chat::White, "... "); + c->Message( + Chat::White, + "...Example: Find the amount of ATK stat I need to add to the targeted NPC so that it hits me for 50 pct damage on average." + ); + c->Message(Chat::White, "...Example: #tune FindATK D 50"); + } + return; + } + + if (!strcasecmp(sep->arg[1], "FindAC")) { + float pct_mitigation = atof(sep->arg[3]); + int interval = atoi(sep->arg[4]); + int max_loop = atoi(sep->arg[5]); + int atk_override = atoi(sep->arg[6]); + int info_level = atoi(sep->arg[7]); + + if (!pct_mitigation) { + c->Message(Chat::White, "#Tune - Error must enter the desired percent mitigation on defender."); + c->Message( + Chat::White, + "...Returns recommended AC adjustment(+/-) on DEFENDER for a specific average AC mitigation pct from ATTACKER. " + ); + c->Message( + Chat::White, + "...#tune FindAC [A/D] [pct mitigation] [interval][loop_max][ATK override][Info Level] " + ); + c->Message( + Chat::White, + "...Example: Find the amount of AC stat I need to add to the targeted NPC so that I hit it for 70 pct damage on average." + ); + c->Message(Chat::White, "...Example: #tune FindAC D 70"); + return; + } + + if (!interval) { + interval = 10; + } + if (!max_loop) { + max_loop = 1000; + } + if (!atk_override) { + atk_override = 0; + } + if (!info_level) { + info_level = 0; + } + + if (!strcasecmp(sep->arg[2], "A")) { + c->TuneGetACByPctMitigation( + defender, + attacker, + pct_mitigation, + interval, + max_loop, + atk_override, + info_level + ); + } + else if (!strcasecmp(sep->arg[2], "D")) { + c->TuneGetACByPctMitigation( + attacker, + defender, + pct_mitigation, + interval, + max_loop, + atk_override, + info_level + ); + } + else { + c->Message(Chat::White, "#Tune - Error no category selcted. [#Tune help]"); + c->Message( + Chat::White, + "Usage #tune FindATK [A/B] [pct mitigation] [interval][loop_max][AC Overwride][Info Level] " + ); + c->Message(Chat::White, "...Usage: [A/D] You must input either A or D."); + c->Message(Chat::White, "...Category [A] : YOU are the ATTACKER. YOUR TARGET is the DEFENDER."); + c->Message(Chat::White, "...Category [D] : YOU are the DEFENDER. YOUR TARGET is the ATTACKER."); + c->Message(Chat::White, "...If TARGET is in combat, DEFENDER is the TARGETs TARGET."); + c->Message(Chat::White, "... "); + c->Message( + Chat::White, + "...Example: Find the amount of AC stat I need to add to the targeted NPC so that I hit it for 70 pct damage on average." + ); + c->Message(Chat::White, "...Example: #tune FindAC D 70"); + } + + return; + } + + if (!strcasecmp(sep->arg[1], "FindAccuracy")) { + float hit_chance = atof(sep->arg[3]); + int interval = atoi(sep->arg[4]); + int max_loop = atoi(sep->arg[5]); + int avoid_override = atoi(sep->arg[6]); + int info_level = atoi(sep->arg[7]); + + if (!hit_chance) { + c->Message(Chat::White, "#Tune - Error must enter the desired hit chance on defender."); + c->Message( + Chat::White, + "...Returns recommended Accuracy adjustment (+/-) on ATTACKER that will result in a specific hit chance pct on DEFENDER. " + ); + c->Message( + Chat::White, + "...#tune FindAccuracy [A/D] [hit chance] [interval][loop_max][Avoidance override][Info Level]" + ); + c->Message( + Chat::White, + "...Example: Find the amount of Accuracy stat I need to add to the targeted NPC so that it has a 60 pct hit chance against me." + ); + c->Message(Chat::White, "...Example: #tune FindAccuracy D 60"); + return; + } + + if (!interval) { + interval = 10; + } + if (!max_loop) { + max_loop = 1000; + } + if (!avoid_override) { + avoid_override = 0; + } + if (!info_level) { + info_level = 0; + } + + if (!strcasecmp(sep->arg[2], "A")) { + c->TuneGetAccuracyByHitChance( + defender, + attacker, + hit_chance, + interval, + max_loop, + avoid_override, + info_level + ); + } + else if (!strcasecmp(sep->arg[2], "D")) { + c->TuneGetAccuracyByHitChance( + attacker, + defender, + hit_chance, + interval, + max_loop, + avoid_override, + info_level + ); + } + else { + c->Message(Chat::White, "#Tune - Error no category selcted. [#Tune help]"); + c->Message( + Chat::White, + "...#tune FindAccuracy [A/D] [hit chance] [interval][loop_max][Avoidance override][Info Level]" + ); + c->Message(Chat::White, "...Usage: [A/D] You must input either A or D."); + c->Message(Chat::White, "...Category [A] : YOU are the ATTACKER. YOUR TARGET is the DEFENDER."); + c->Message(Chat::White, "...Category [D] : YOU are the DEFENDER. YOUR TARGET is the ATTACKER."); + c->Message(Chat::White, "...If TARGET is in combat, DEFENDER is the TARGETs TARGET."); + c->Message(Chat::White, "... "); + c->Message( + Chat::White, + "...Example: Find the amount of Accuracy stat I need to add to the targeted NPC so that it has a 60 pct hit chance against me." + ); + c->Message(Chat::White, "...Example: #tune FindAccuracy D 60"); + } + + return; + } + + if (!strcasecmp(sep->arg[1], "FindAvoidance")) { + float hit_chance = atof(sep->arg[3]); + int interval = atoi(sep->arg[4]); + int max_loop = atoi(sep->arg[5]); + int acc_override = atoi(sep->arg[6]); + int info_level = atoi(sep->arg[7]); + + if (!hit_chance) { + c->Message(Chat::White, "#Tune - Error must enter the desired hit chance on defender."); + c->Message( + Chat::White, + "...Returns recommended Avoidance adjustment (+/-) on DEFENDER for in a specific hit chance pct from ATTACKER. " + ); + c->Message( + Chat::White, + "...#tune FindAvoidance [A/D] [hit chance] [interval][loop_max][Accuracy override][Info Level] " + ); + c->Message( + Chat::White, + "...Example: Find the amount of Avoidance stat I need to add to the targeted NPC so that I have a 30 pct hit chance against it." + ); + c->Message(Chat::White, "...Example: #tune FindAvoidance D 30"); + return; + } + if (!interval) { + interval = 10; + } + if (!max_loop) { + max_loop = 1000; + } + if (!acc_override) { + acc_override = 0; + } + if (!info_level) { + info_level = 0; + } + + if (!strcasecmp(sep->arg[2], "A")) { + c->TuneGetAvoidanceByHitChance( + defender, + attacker, + hit_chance, + interval, + max_loop, + acc_override, + info_level + ); + } + else if (!strcasecmp(sep->arg[2], "D")) { + c->TuneGetAvoidanceByHitChance( + attacker, + defender, + hit_chance, + interval, + max_loop, + acc_override, + info_level + ); + } + else { + c->Message(Chat::White, "#Tune - Error no category selcted. [#Tune help]"); + c->Message( + Chat::White, + "...#tune FindAvoidance [A/D] [hit chance] [interval][loop_max][Accuracy override][Info Level] " + ); + c->Message(Chat::White, "...Usage: [A/D] You must input either A or D."); + c->Message(Chat::White, "...Category [A] : YOU are the ATTACKER. YOUR TARGET is the DEFENDER."); + c->Message(Chat::White, "...Category [D] : YOU are the DEFENDER. YOUR TARGET is the ATTACKER."); + c->Message(Chat::White, "...If TARGET is in combat, DEFENDER is the TARGETs TARGET."); + c->Message(Chat::White, "... "); + c->Message( + Chat::White, + "...Example: Find the amount of Avoidance stat I need to add to the targeted NPC so that I have a 30 pct hit chance against it." + ); + c->Message(Chat::White, "...Example: #tune FindAvoidance D 30"); + } + + return; + } + + c->Message(Chat::White, "#Tune - Error no command [#Tune help]"); + return; +} + diff --git a/zone/gm_commands/ucs.cpp b/zone/gm_commands/ucs.cpp new file mode 100755 index 000000000..6d7d65f6a --- /dev/null +++ b/zone/gm_commands/ucs.cpp @@ -0,0 +1,89 @@ +#include "../client.h" + +void command_ucs(Client *c, const Seperator *sep) +{ + if (!c) { + return; + } + + LogInfo("Character [{}] attempting ucs reconnect while ucs server is [{}] available", + c->GetName(), (zone->IsUCSServerAvailable() ? "" : "un")); + + if (zone->IsUCSServerAvailable()) { + EQApplicationPacket *outapp = nullptr; + std::string buffer; + + std::string MailKey = database.GetMailKey(c->CharacterID(), true); + EQ::versions::UCSVersion ConnectionType = EQ::versions::ucsUnknown; + + // chat server packet + switch (c->ClientVersion()) { + case EQ::versions::ClientVersion::Titanium: + ConnectionType = EQ::versions::ucsTitaniumChat; + break; + case EQ::versions::ClientVersion::SoF: + ConnectionType = EQ::versions::ucsSoFCombined; + break; + case EQ::versions::ClientVersion::SoD: + ConnectionType = EQ::versions::ucsSoDCombined; + break; + case EQ::versions::ClientVersion::UF: + ConnectionType = EQ::versions::ucsUFCombined; + break; + case EQ::versions::ClientVersion::RoF: + ConnectionType = EQ::versions::ucsRoFCombined; + break; + case EQ::versions::ClientVersion::RoF2: + ConnectionType = EQ::versions::ucsRoF2Combined; + break; + default: + ConnectionType = EQ::versions::ucsUnknown; + break; + } + + buffer = StringFormat( + "%s,%i,%s.%s,%c%s", + Config->ChatHost.c_str(), + Config->ChatPort, + Config->ShortName.c_str(), + c->GetName(), + ConnectionType, + MailKey.c_str() + ); + + outapp = new EQApplicationPacket(OP_SetChatServer, (buffer.length() + 1)); + memcpy(outapp->pBuffer, buffer.c_str(), buffer.length()); + outapp->pBuffer[buffer.length()] = '\0'; + + c->QueuePacket(outapp); + safe_delete(outapp); + + // mail server packet + switch (c->ClientVersion()) { + case EQ::versions::ClientVersion::Titanium: + ConnectionType = EQ::versions::ucsTitaniumMail; + break; + default: + // retain value from previous switch + break; + } + + buffer = StringFormat( + "%s,%i,%s.%s,%c%s", + Config->MailHost.c_str(), + Config->MailPort, + Config->ShortName.c_str(), + c->GetName(), + ConnectionType, + MailKey.c_str() + ); + + outapp = new EQApplicationPacket(OP_SetChatServer2, (buffer.length() + 1)); + memcpy(outapp->pBuffer, buffer.c_str(), buffer.length()); + outapp->pBuffer[buffer.length()] = '\0'; + + c->QueuePacket(outapp); + safe_delete(outapp); + } +} + diff --git a/zone/gm_commands/undye.cpp b/zone/gm_commands/undye.cpp new file mode 100755 index 000000000..e5430a02b --- /dev/null +++ b/zone/gm_commands/undye.cpp @@ -0,0 +1,24 @@ +#include "../client.h" + +void command_undye(Client *c, const Seperator *sep) +{ + auto target = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); + } + + target->Undye(); + c->Message( + Chat::White, + fmt::format( + "Undyed armor for {}.", + c == target ? + "yourself" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ).c_str() + ); +} diff --git a/zone/gm_commands/undyeme.cpp b/zone/gm_commands/undyeme.cpp new file mode 100755 index 000000000..8bd6924b1 --- /dev/null +++ b/zone/gm_commands/undyeme.cpp @@ -0,0 +1,7 @@ +#include "../client.h" + +void command_undyeme(Client *c, const Seperator *sep) +{ + c->Undye(); + c->Message(Chat::White, "Undyed armor for yourself."); +} diff --git a/zone/gm_commands/unfreeze.cpp b/zone/gm_commands/unfreeze.cpp new file mode 100755 index 000000000..9066af500 --- /dev/null +++ b/zone/gm_commands/unfreeze.cpp @@ -0,0 +1,12 @@ +#include "../client.h" + +void command_unfreeze(Client *c, const Seperator *sep) +{ + if (c->GetTarget() != 0) { + c->GetTarget()->SendAppearancePacket(AT_Anim, ANIM_STAND); + } + else { + c->Message(Chat::White, "ERROR: Unfreeze requires a target."); + } +} + diff --git a/zone/gm_commands/unlock.cpp b/zone/gm_commands/unlock.cpp new file mode 100755 index 000000000..2d35220e6 --- /dev/null +++ b/zone/gm_commands/unlock.cpp @@ -0,0 +1,15 @@ +#include "../client.h" +#include "../worldserver.h" + +extern WorldServer worldserver; + +void command_unlock(Client *c, const Seperator *sep) +{ + auto outpack = new ServerPacket(ServerOP_Lock, sizeof(ServerLock_Struct)); + ServerLock_Struct *lss = (ServerLock_Struct *) outpack->pBuffer; + strcpy(lss->myname, c->GetName()); + lss->mode = 0; + worldserver.SendPacket(outpack); + safe_delete(outpack); +} + diff --git a/zone/gm_commands/unmemspell.cpp b/zone/gm_commands/unmemspell.cpp new file mode 100644 index 000000000..67ffc5664 --- /dev/null +++ b/zone/gm_commands/unmemspell.cpp @@ -0,0 +1,68 @@ +#include "../client.h" + +void command_unmemspell(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if ( + !arguments || + !sep->IsNumber(1) + ) { + c->Message(Chat::White, "Usage: #unmemspell [Spell ID]"); + return; + } + + auto target = c; + if (c->GetTarget() && c->GetTarget()->IsClient() && c->GetGM()) { + target = c->GetTarget()->CastToClient(); + } + + auto spell_id = static_cast(std::stoul(sep->arg[1])); + if (!IsValidSpell(spell_id)) { + c->Message( + Chat::White, + fmt::format( + "Spell ID {} could not be found.", + spell_id + ).c_str() + ); + return; + } + + auto spell_gem = target->FindMemmedSpellBySpellID(spell_id); + if (spell_gem == -1) { + c->Message( + Chat::White, + fmt::format( + "{} not have {} ({}) memorized.", + ( + c == target ? + "You do" : + fmt::format( + "{} ({}) does", + target->GetCleanName(), + target->GetID() + ) + ), + GetSpellName(spell_id), + spell_id + ).c_str() + ); + return; + } + + target->UnmemSpellBySpellID(spell_id); + + if (c != target) { + c->Message( + Chat::White, + fmt::format( + "{} ({}) unmemorized for {} ({}) from spell gem {}.", + GetSpellName(spell_id), + spell_id, + target->GetCleanName(), + target->GetID(), + spell_gem + ).c_str() + ); + } +} diff --git a/zone/gm_commands/unmemspells.cpp b/zone/gm_commands/unmemspells.cpp new file mode 100644 index 000000000..f5f6f0987 --- /dev/null +++ b/zone/gm_commands/unmemspells.cpp @@ -0,0 +1,43 @@ +#include "../client.h" + +void command_unmemspells(Client *c, const Seperator *sep) +{ + auto target = c; + if (c->GetTarget() && c->GetTarget()->IsClient() && c->GetGM()) { + target = c->GetTarget()->CastToClient(); + } + + auto memmed_count = target->MemmedCount(); + if (!memmed_count) { + c->Message( + Chat::White, + fmt::format( + "{} no spells to unmemorize.", + ( + c == target ? + "You have" : + fmt::format( + "{} ({}) has", + target->GetCleanName(), + target->GetID() + ) + ) + ).c_str() + ); + return; + } + + target->UnmemSpellAll(); + + if (c != target) { + c->Message( + Chat::White, + fmt::format( + "{} ({}) has had {} spells unmemorized.", + target->GetCleanName(), + target->GetID(), + memmed_count + ).c_str() + ); + } +} diff --git a/zone/gm_commands/unscribespell.cpp b/zone/gm_commands/unscribespell.cpp new file mode 100755 index 000000000..2c66bea8f --- /dev/null +++ b/zone/gm_commands/unscribespell.cpp @@ -0,0 +1,62 @@ +#include "../client.h" + +void command_unscribespell(Client *c, const Seperator *sep) +{ + uint16 spell_id = 0; + uint16 book_slot = -1; + Client *t = c; + + if (c->GetTarget() && c->GetTarget()->IsClient() && c->GetGM()) { + t = c->GetTarget()->CastToClient(); + } + + if (!sep->arg[1][0]) { + c->Message(Chat::White, "FORMAT: #unscribespell "); + return; + } + + spell_id = atoi(sep->arg[1]); + + if (IsValidSpell(spell_id)) { + book_slot = t->FindSpellBookSlotBySpellID(spell_id); + + if (book_slot >= 0) { + t->UnscribeSpell(book_slot); + + t->Message(Chat::White, "Unscribing spell: %s (%i) from spellbook.", spells[spell_id].name, spell_id); + + if (t != c) { + c->Message( + Chat::White, + "Unscribing spell: %s (%i) for %s.", + spells[spell_id].name, + spell_id, + t->GetName()); + } + + LogInfo("Unscribe spell: [{}] ([{}]) request for [{}] from [{}]", + spells[spell_id].name, + spell_id, + t->GetName(), + c->GetName()); + } + else { + t->Message( + Chat::Red, + "Unable to unscribe spell: %s (%i) from your spellbook. This spell is not scribed.", + spells[spell_id].name, + spell_id + ); + + if (t != c) { + c->Message( + Chat::Red, + "Unable to unscribe spell: %s (%i) for %s due to spell not scribed.", + spells[spell_id].name, + spell_id, + t->GetName()); + } + } + } +} + diff --git a/zone/gm_commands/unscribespells.cpp b/zone/gm_commands/unscribespells.cpp new file mode 100755 index 000000000..7b4b93211 --- /dev/null +++ b/zone/gm_commands/unscribespells.cpp @@ -0,0 +1,13 @@ +#include "../client.h" + +void command_unscribespells(Client *c, const Seperator *sep) +{ + Client *t = c; + + if (c->GetTarget() && c->GetTarget()->IsClient() && c->GetGM()) { + t = c->GetTarget()->CastToClient(); + } + + t->UnscribeSpellAll(); +} + diff --git a/zone/gm_commands/untraindisc.cpp b/zone/gm_commands/untraindisc.cpp new file mode 100755 index 000000000..c5d6ca1f2 --- /dev/null +++ b/zone/gm_commands/untraindisc.cpp @@ -0,0 +1,17 @@ +#include "../client.h" + +void command_untraindisc(Client *c, const Seperator *sep) +{ + Client *t = c; + if (c->GetTarget() && c->GetTarget()->IsClient() && c->GetGM()) { + t = c->GetTarget()->CastToClient(); + } + + for (int i = 0; i < MAX_PP_DISCIPLINES; i++) { + if (t->GetPP().disciplines.values[i] == atoi(sep->arg[1])) { + t->UntrainDisc(i, 1); + return; + } + } +} + diff --git a/zone/gm_commands/untraindiscs.cpp b/zone/gm_commands/untraindiscs.cpp new file mode 100755 index 000000000..8058c6e76 --- /dev/null +++ b/zone/gm_commands/untraindiscs.cpp @@ -0,0 +1,13 @@ +#include "../client.h" + +void command_untraindiscs(Client *c, const Seperator *sep) +{ + Client *t = c; + if (c->GetTarget() && c->GetTarget()->IsClient() && c->GetGM()) { + t = c->GetTarget()->CastToClient(); + } + + t->UntrainDiscAll(); + t->Message(Chat::Yellow, "All disciplines removed."); +} + diff --git a/zone/gm_commands/uptime.cpp b/zone/gm_commands/uptime.cpp new file mode 100755 index 000000000..3c1c7bfbc --- /dev/null +++ b/zone/gm_commands/uptime.cpp @@ -0,0 +1,22 @@ +#include "../client.h" +#include "../worldserver.h" + +extern WorldServer worldserver; + +void command_uptime(Client *c, const Seperator *sep) +{ + if (!worldserver.Connected()) { + c->Message(Chat::White, "Error: World server disconnected"); + } + else { + auto pack = new ServerPacket(ServerOP_Uptime, sizeof(ServerUptime_Struct)); + ServerUptime_Struct *sus = (ServerUptime_Struct *) pack->pBuffer; + strcpy(sus->adminname, c->GetName()); + if (sep->IsNumber(1) && atoi(sep->arg[1]) > 0) { + sus->zoneserverid = atoi(sep->arg[1]); + } + worldserver.SendPacket(pack); + safe_delete(pack); + } +} + diff --git a/zone/gm_commands/version.cpp b/zone/gm_commands/version.cpp new file mode 100755 index 000000000..cc5d0a531 --- /dev/null +++ b/zone/gm_commands/version.cpp @@ -0,0 +1,18 @@ +#include "../client.h" + +void command_version(Client *c, const Seperator *sep) +{ + std::string popup_text = ""; + + popup_text += fmt::format("", CURRENT_VERSION); + popup_text += fmt::format("", COMPILE_DATE, COMPILE_TIME); + popup_text += fmt::format("", LAST_MODIFIED); + + popup_text += "
Version{}
Compiled{} {}
Last Modified{}
"; + + c->SendPopupToClient( + "Server Version Information", + popup_text.c_str() + ); +} + diff --git a/zone/gm_commands/viewcurrencies.cpp b/zone/gm_commands/viewcurrencies.cpp new file mode 100644 index 000000000..c66393c6f --- /dev/null +++ b/zone/gm_commands/viewcurrencies.cpp @@ -0,0 +1,137 @@ +#include "../client.h" + +void command_viewcurrencies(Client *c, const Seperator *sep) +{ + Client *target = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); + } + + auto target_string = ( + c == target ? + "Yourself" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ); + + auto platinum = ( + target->GetMoney(3, 0) + + target->GetMoney(3, 1) + + target->GetMoney(3, 2) + + target->GetMoney(3, 3) + ); + + auto gold = ( + target->GetMoney(2, 0) + + target->GetMoney(2, 1) + + target->GetMoney(2, 2) + ); + + auto silver = ( + target->GetMoney(1, 0) + + target->GetMoney(1, 1) + + target->GetMoney(1, 2) + ); + + auto copper = ( + target->GetMoney(0, 0) + + target->GetMoney(0, 1) + + target->GetMoney(0, 2) + ); + + if ( + platinum || + gold || + silver || + copper + ) { + c->Message( + Chat::White, + fmt::format( + "Money for {} | {}", + target_string, + ConvertMoneyToString( + platinum, + gold, + silver, + copper + ) + ).c_str() + ); + } + + auto ebon_crystals = target->GetEbonCrystals(); + if (ebon_crystals) { + c->Message( + Chat::White, + fmt::format( + "{} for {} | {}", + database.CreateItemLink(RuleI(Zone, EbonCrystalItemID)), + target_string, + ebon_crystals + ).c_str() + ); + } + + auto radiant_crystals = target->GetRadiantCrystals(); + if (radiant_crystals) { + c->Message( + Chat::White, + fmt::format( + "{} for {} | {}", + database.CreateItemLink(RuleI(Zone, RadiantCrystalItemID)), + target_string, + radiant_crystals + ).c_str() + ); + } + + for (const auto& alternate_currency : zone->AlternateCurrencies) { + auto currency_value = target->GetAlternateCurrencyValue(alternate_currency.id); + if (currency_value) { + c->Message( + Chat::White, + fmt::format( + "{} for {} | {}", + database.CreateItemLink(alternate_currency.item_id), + target_string, + currency_value + ).c_str() + ); + } + } + + for ( + uint32 ldon_currency_id = LDoNThemes::GUK; + ldon_currency_id <= LDoNThemes::TAK; + ldon_currency_id++ + ) { + auto ldon_currency_value = target->GetLDoNPointsTheme(ldon_currency_id); + if (ldon_currency_value) { + c->Message( + Chat::White, + fmt::format( + "{} for {} | {}", + EQ::constants::GetLDoNThemeName(ldon_currency_id), + target_string, + ldon_currency_value + ).c_str() + ); + } + } + + auto pvp_points = target->GetPVPPoints(); + if (pvp_points) { + c->Message( + Chat::White, + fmt::format( + "PVP Points for {} | {}", + target_string, + pvp_points + ).c_str() + ); + } +} diff --git a/zone/gm_commands/viewnpctype.cpp b/zone/gm_commands/viewnpctype.cpp new file mode 100755 index 000000000..70447c145 --- /dev/null +++ b/zone/gm_commands/viewnpctype.cpp @@ -0,0 +1,31 @@ +#include "../client.h" + +void command_viewnpctype(Client *c, const Seperator *sep) +{ + if (sep->IsNumber(1)) { + uint32 npc_id = std::stoul(sep->arg[1]); + const NPCType *npc_type_data = content_db.LoadNPCTypesData(npc_id); + if (npc_type_data) { + auto npc = new NPC( + npc_type_data, + nullptr, + c->GetPosition(), + GravityBehavior::Water + ); + npc->ShowStats(c); + } + else { + c->Message( + Chat::White, + fmt::format( + "NPC ID {} was not found.", + npc_id + ).c_str() + ); + } + } + else { + c->Message(Chat::White, "Usage: #viewnpctype [NPC ID]"); + } +} + diff --git a/zone/gm_commands/viewpetition.cpp b/zone/gm_commands/viewpetition.cpp new file mode 100755 index 000000000..a59821a81 --- /dev/null +++ b/zone/gm_commands/viewpetition.cpp @@ -0,0 +1,31 @@ +#include "../client.h" + +void command_viewpetition(Client *c, const Seperator *sep) +{ + if (sep->arg[1][0] == 0) { + c->Message(Chat::White, "Usage: #viewpetition (petition number) Type #listpetition for a list"); + return; + } + + c->Message(Chat::Red, " ID : Character Name , Petition Text"); + + std::string query = "SELECT petid, charname, petitiontext FROM petitions ORDER BY petid"; + auto results = database.QueryDatabase(query); + if (!results.Success()) { + return; + } + + LogInfo("View petition request from [{}], petition number: [{}]", c->GetName(), atoi(sep->argplus[1])); + + if (results.RowCount() == 0) { + c->Message(Chat::Red, "There was an error in your request: ID not found! Please check the Id and try again."); + return; + } + + for (auto row = results.begin(); row != results.end(); ++row) + if (strcasecmp(row[0], sep->argplus[1]) == 0) { + c->Message(Chat::Yellow, " %s: %s , %s ", row[0], row[1], row[2]); + } + +} + diff --git a/zone/gm_commands/viewzoneloot.cpp b/zone/gm_commands/viewzoneloot.cpp new file mode 100755 index 000000000..89518750a --- /dev/null +++ b/zone/gm_commands/viewzoneloot.cpp @@ -0,0 +1,117 @@ +#include "../client.h" + +void command_viewzoneloot(Client *c, const Seperator *sep) +{ + std::map zone_loot_list; + auto npc_list = entity_list.GetNPCList(); + uint32 loot_amount = 0, loot_id = 1, search_item_id = 0; + if (sep->argnum == 1 && sep->IsNumber(1)) { + search_item_id = atoi(sep->arg[1]); + } + else if (sep->argnum == 1 && !sep->IsNumber(1)) { + c->Message( + Chat::Yellow, + "Usage: #viewzoneloot [item id]" + ); + return; + } + + for (auto npc_entity : npc_list) { + auto current_npc_item_list = npc_entity.second->GetItemList(); + zone_loot_list.insert({npc_entity.second->GetID(), current_npc_item_list}); + } + + for (auto loot_item : zone_loot_list) { + uint32 current_entity_id = loot_item.first; + auto current_item_list = loot_item.second; + auto current_npc = entity_list.GetNPCByID(current_entity_id); + std::string npc_link; + if (current_npc) { + std::string npc_name = current_npc->GetCleanName(); + uint32 instance_id = zone->GetInstanceID(); + uint32 zone_id = zone->GetZoneID(); + std::string command_link = EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format( + "#{} {} {} {} {}", + (instance_id != 0 ? "zoneinstance" : "zone"), + (instance_id != 0 ? instance_id : zone_id), + current_npc->GetX(), + current_npc->GetY(), + current_npc->GetZ() + ), + false, + "Goto" + ); + npc_link = fmt::format( + " NPC: {} (ID {}) [{}]", + npc_name, + current_entity_id, + command_link + ); + } + + for (auto current_item : current_item_list) { + if (search_item_id == 0 || current_item->item_id == search_item_id) { + EQ::SayLinkEngine linker; + linker.SetLinkType(EQ::saylink::SayLinkLootItem); + linker.SetLootData(current_item); + c->Message( + Chat::White, + fmt::format( + "{}. {} ({}){}", + loot_id, + linker.GenerateLink(), + current_item->item_id, + npc_link + ).c_str() + ); + loot_id++; + loot_amount++; + } + } + } + + + if (search_item_id != 0) { + std::string drop_string = ( + loot_amount > 0 ? + fmt::format( + "dropping in {} {}", + loot_amount, + (loot_amount > 1 ? "places" : "place") + ) : + "not dropping" + ); + + c->Message( + Chat::White, + fmt::format( + "{} ({}) is {}.", + database.CreateItemLink(search_item_id), + search_item_id, + drop_string + ).c_str() + ); + } + else { + std::string drop_string = ( + loot_amount > 0 ? + fmt::format( + "{} {} dropping", + (loot_amount > 1 ? "items" : "item"), + (loot_amount > 1 ? "are" : "is") + ) : + "items are dropping" + ); + + c->Message( + Chat::White, + fmt::format( + "{} {}.", + loot_amount, + drop_string + ).c_str() + ); + } +} + diff --git a/zone/gm_commands/wc.cpp b/zone/gm_commands/wc.cpp new file mode 100755 index 000000000..0ec247aca --- /dev/null +++ b/zone/gm_commands/wc.cpp @@ -0,0 +1,44 @@ +#include "../client.h" + +void command_wc(Client *c, const Seperator *sep) +{ + if (sep->argnum < 2) { + c->Message( + 0, + "Usage: #wc [wear slot] [material] [ [hero_forge_model] [elite_material] [unknown06] [unknown18] ]" + ); + } + else if (c->GetTarget() == nullptr) { + c->Message(Chat::Red, "You must have a target to do a wear change."); + } + else { + uint32 hero_forge_model = 0; + uint32 wearslot = atoi(sep->arg[1]); + + // Hero Forge + if (sep->argnum > 2) { + hero_forge_model = atoi(sep->arg[3]); + + if (hero_forge_model != 0 && hero_forge_model < 1000) { + // Shorthand Hero Forge ID. Otherwise use the value the user entered. + hero_forge_model = (hero_forge_model * 100) + wearslot; + } + } + /* + // Leaving here to add color option to the #wc command eventually + uint32 Color; + if (c->GetTarget()->IsClient()) + Color = c->GetTarget()->GetEquipmentColor(atoi(sep->arg[1])); + else + Color = c->GetTarget()->GetArmorTint(atoi(sep->arg[1])); + */ + c->GetTarget()->SendTextureWC( + wearslot, + atoi(sep->arg[2]), + hero_forge_model, + atoi(sep->arg[4]), + atoi(sep->arg[5]), + atoi(sep->arg[6])); + } +} + diff --git a/zone/gm_commands/weather.cpp b/zone/gm_commands/weather.cpp new file mode 100755 index 000000000..2590c51e3 --- /dev/null +++ b/zone/gm_commands/weather.cpp @@ -0,0 +1,70 @@ +#include "../client.h" + +void command_weather(Client *c, const Seperator *sep) +{ + if (!(sep->arg[1][0] == '0' || sep->arg[1][0] == '1' || sep->arg[1][0] == '2' || sep->arg[1][0] == '3')) { + c->Message(Chat::White, "Usage: #weather <0/1/2/3> - Off/Rain/Snow/Manual."); + } + else if (zone->zone_weather == 0) { + if (sep->arg[1][0] == + '3') { // Put in modifications here because it had a very good chance at screwing up the client's weather system if rain was sent during snow -T7 + if (sep->arg[2][0] != 0 && sep->arg[3][0] != 0) { + c->Message(Chat::White, "Sending weather packet... TYPE=%s, INTENSITY=%s", sep->arg[2], sep->arg[3]); + zone->zone_weather = atoi(sep->arg[2]); + auto outapp = new EQApplicationPacket(OP_Weather, 8); + outapp->pBuffer[0] = atoi(sep->arg[2]); + outapp->pBuffer[4] = atoi(sep->arg[3]); // This number changes in the packets, intensity? + entity_list.QueueClients(c, outapp); + safe_delete(outapp); + } + else { + c->Message(Chat::White, "Manual Usage: #weather 3 "); + } + } + else if (sep->arg[1][0] == '2') { + entity_list.Message(0, 0, "Snowflakes begin to fall from the sky."); + zone->zone_weather = 2; + auto outapp = new EQApplicationPacket(OP_Weather, 8); + outapp->pBuffer[0] = 0x01; + outapp->pBuffer[4] = 0x02; // This number changes in the packets, intensity? + entity_list.QueueClients(c, outapp); + safe_delete(outapp); + } + else if (sep->arg[1][0] == '1') { + entity_list.Message(0, 0, "Raindrops begin to fall from the sky."); + zone->zone_weather = 1; + auto outapp = new EQApplicationPacket(OP_Weather, 8); + outapp->pBuffer[4] = 0x01; // This is how it's done in Fear, and you can see a decent distance with it at this value + entity_list.QueueClients(c, outapp); + safe_delete(outapp); + } + } + else { + if (zone->zone_weather == 1) { // Doing this because if you have rain/snow on, you can only turn one off. + entity_list.Message(0, 0, "The sky clears as the rain ceases to fall."); + zone->zone_weather = 0; + auto outapp = new EQApplicationPacket(OP_Weather, 8); + // To shutoff weather you send an empty 8 byte packet (You get this everytime you zone even if the sky is clear) + entity_list.QueueClients(c, outapp); + safe_delete(outapp); + } + else if (zone->zone_weather == 2) { + entity_list.Message(0, 0, "The sky clears as the snow stops falling."); + zone->zone_weather = 0; + auto outapp = new EQApplicationPacket(OP_Weather, 8); + // To shutoff weather you send an empty 8 byte packet (You get this everytime you zone even if the sky is clear) + outapp->pBuffer[0] = 0x01; // Snow has it's own shutoff packet + entity_list.QueueClients(c, outapp); + safe_delete(outapp); + } + else { + entity_list.Message(0, 0, "The sky clears."); + zone->zone_weather = 0; + auto outapp = new EQApplicationPacket(OP_Weather, 8); + // To shutoff weather you send an empty 8 byte packet (You get this everytime you zone even if the sky is clear) + entity_list.QueueClients(c, outapp); + safe_delete(outapp); + } + } +} + diff --git a/zone/gm_commands/who.cpp b/zone/gm_commands/who.cpp new file mode 100755 index 000000000..43077573e --- /dev/null +++ b/zone/gm_commands/who.cpp @@ -0,0 +1,241 @@ +#include "../client.h" + +void command_who(Client *c, const Seperator *sep) +{ + std::string query = SQL( + SELECT + character_data.account_id, + character_data.name, + character_data.zone_id, + character_data.zone_instance, + COALESCE( + ( + SELECT guilds.name FROM guilds WHERE id = ( + ( + SELECT guild_id FROM guild_members WHERE char_id = character_data.id + ) + ) + ), + "" + ) AS guild_name, + character_data.level, + character_data.race, + character_data.class, + COALESCE( + ( + SELECT account.status FROM account WHERE account.id = character_data.account_id LIMIT 1 + ), + 0 + ) AS account_status, + COALESCE( + ( + SELECT account.name FROM account WHERE account.id = character_data.account_id LIMIT 1 + ), + 0 + ) AS account_name, + COALESCE( + ( + SELECT account_ip.ip FROM account_ip WHERE account_ip.accid = character_data.account_id ORDER BY account_ip.lastused DESC LIMIT 1 + ), + "" + ) AS account_ip + FROM + character_data + WHERE + last_login > (UNIX_TIMESTAMP() - 600) + ORDER BY + character_data.name; + ); + + auto results = database.QueryDatabase(query); + if (!results.Success() || !results.RowCount()) { + return; + } + + std::string search_string; + + if (sep->arg[1]) { + search_string = str_tolower(sep->arg[1]); + } + + int found_count = 0; + + c->Message(Chat::Who, "Players in EverQuest:"); + c->Message(Chat::Who, "------------------------------"); + + for (auto row : results) { + auto account_id = std::stoul(row[0]); + std::string player_name = row[1]; + auto zone_id = std::stoul(row[2]); + std::string zone_short_name = ZoneName(zone_id); + std::string zone_long_name = ZoneLongName(zone_id); + auto zone_instance = std::stoul(row[3]); + std::string guild_name = row[4]; + auto player_level = std::stoul(row[5]); + auto player_race = std::stoul(row[6]); + auto player_class = std::stoul(row[7]); + auto account_status = std::stoul(row[8]); + std::string account_name = row[9]; + std::string account_ip = row[10]; + std::string base_class_name = GetClassIDName(static_cast(player_class)); + std::string displayed_race_name = GetRaceIDName(static_cast(player_race)); + + if (search_string.length()) { + bool found_search_term = ( + str_tolower(player_name).find(search_string) != std::string::npos || + str_tolower(zone_short_name).find(search_string) != std::string::npos || + str_tolower(displayed_race_name).find(search_string) != std::string::npos || + str_tolower(base_class_name).find(search_string) != std::string::npos || + str_tolower(guild_name).find(search_string) != std::string::npos || + str_tolower(account_name).find(search_string) != std::string::npos || + str_tolower(account_ip).find(search_string) != std::string::npos + ); + + if (!found_search_term) { + continue; + } + } + + std::string displayed_guild_name; + if (guild_name.length()) { + displayed_guild_name = EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format( + "#who \"{}\"", + guild_name + ), + false, + fmt::format( + "<{}>", + guild_name + ) + ); + } + + auto goto_saylink = EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format( + "#goto {}", + player_name + ), + false, + "Goto" + ); + + auto summon_saylink = EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format( + "#summon {}", + player_name + ), + false, + "Summon" + ); + + std::string display_class_name = GetClassIDName( + static_cast(player_class), + static_cast(player_level) + ); + + auto class_saylink = EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format( + "#who {}", + base_class_name + ), + false, + display_class_name + ); + + auto race_saylink = EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format( + "#who %s", + displayed_race_name + ), + false, + displayed_race_name + ); + + auto zone_saylink = EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format( + "#who {}", + zone_short_name + ), + false, + zone_long_name + ); + + auto account_saylink = EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format( + "#who {}", + account_name + ), + false, + account_name + ); + + auto account_ip_saylink = EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format( + "#who {}", + account_ip + ), + false, + account_ip + ); + + auto status_level = ( + account_status ? + fmt::format( + "* {} * ", + EQ::constants::GetAccountStatusName(account_status) + ) : + "" + ); + + auto version_string = ( + zone_instance ? + fmt::format( + " ({})", + zone_instance + ) : + "" + ); + + c->Message( + Chat::Who, + fmt::format( + "{}[{} {} ({})] {} ({}) ({}) ({}) {} ZONE: {}{} ({} | {})", + status_level, + player_level, + class_saylink, + base_class_name, + player_name, + race_saylink, + account_saylink, + account_ip_saylink, + displayed_guild_name, + zone_saylink, + version_string, + goto_saylink, + summon_saylink + ).c_str() + ); + + found_count++; + } + + std::string count_string = found_count == 1 ? "is" : "are"; + + std::string message = ( + found_count ? + fmt::format( + "There {} {} player{} in EverQuest.", + count_string, + found_count, + found_count > 1 ? "s" : "" + ) : + "There are no players in EverQuest that match those filters." + ); + + c->Message( + Chat::Who, + message.c_str() + ); +} + diff --git a/zone/gm_commands/worldshutdown.cpp b/zone/gm_commands/worldshutdown.cpp new file mode 100755 index 000000000..4f27c4ad9 --- /dev/null +++ b/zone/gm_commands/worldshutdown.cpp @@ -0,0 +1,79 @@ +#include "../client.h" +#include "../worldserver.h" + +extern WorldServer worldserver; + +void command_worldshutdown(Client *c, const Seperator *sep) +{ + // GM command to shutdown world server and all zone servers + uint32 time = 0; + uint32 interval = 0; + if (worldserver.Connected()) { + if ( + sep->IsNumber(1) && + sep->IsNumber(2) && + (time = std::stoi(sep->arg[1]) > 0) && + (interval = std::stoi(sep->arg[2]) > 0) + ) { + int time_minutes = (time / 60); + quest_manager.WorldWideMessage( + Chat::Yellow, + fmt::format( + "[SYSTEM] World will be shutting down in {} minutes.", + time_minutes + ).c_str() + ); + c->Message( + Chat::White, + fmt::format( + "World will be shutting down in {} minutes, notifying every {} seconds", + time_minutes, + interval + ).c_str() + ); + auto pack = new ServerPacket(ServerOP_ShutdownAll, sizeof(WorldShutDown_Struct)); + WorldShutDown_Struct *wsd = (WorldShutDown_Struct *) pack->pBuffer; + wsd->time = (time * 1000); + wsd->interval = (interval * 1000); + worldserver.SendPacket(pack); + safe_delete(pack); + } + else if (!strcasecmp(sep->arg[1], "now")) { + quest_manager.WorldWideMessage( + Chat::Yellow, + "[SYSTEM] World is shutting down now." + ); + c->Message(Chat::White, "World is shutting down now."); + auto pack = new ServerPacket; + pack->opcode = ServerOP_ShutdownAll; + pack->size = 0; + worldserver.SendPacket(pack); + safe_delete(pack); + } + else if (!strcasecmp(sep->arg[1], "disable")) { + c->Message(Chat::White, "World shutdown has been aborted."); + auto pack = new ServerPacket(ServerOP_ShutdownAll, sizeof(WorldShutDown_Struct)); + WorldShutDown_Struct *wsd = (WorldShutDown_Struct *) pack->pBuffer; + wsd->time = 0; + wsd->interval = 0; + worldserver.SendPacket(pack); + safe_delete(pack); + } + else { + c->Message(Chat::White, "#worldshutdown - Shuts down the server and all zones."); + c->Message(Chat::White, "Usage: #worldshutdown now - Shuts down the server and all zones immediately."); + c->Message( + Chat::White, + "Usage: #worldshutdown disable - Stops the server from a previously scheduled shut down." + ); + c->Message( + Chat::White, + "Usage: #worldshutdown [timer] [interval] - Shuts down the server and all zones after [timer] seconds and notifies players every [interval] seconds." + ); + } + } + else { + c->Message(Chat::White, "Error: World server is disconnected."); + } +} + diff --git a/zone/gm_commands/worldwide.cpp b/zone/gm_commands/worldwide.cpp new file mode 100755 index 000000000..f940a4cbf --- /dev/null +++ b/zone/gm_commands/worldwide.cpp @@ -0,0 +1,148 @@ +#include "../client.h" + +void command_worldwide(Client *c, const Seperator *sep) +{ + std::string sub_command; + if (sep->arg[1]) { + sub_command = sep->arg[1]; + } + + if (sub_command == "cast") { + if (sep->arg[2] && Seperator::IsNumber(sep->arg[2])) { + uint8 update_type = WWSpellUpdateType_Cast; + auto spell_id = std::stoul(sep->arg[2]); + bool disable_message = false; + if (sep->arg[3] && Seperator::IsNumber(sep->arg[3])) { + disable_message = std::stoi(sep->arg[3]) ? true : false; + } + + c->Message( + Chat::White, + fmt::format( + "World Wide Cast Spell | Spell: {} ({})", + GetSpellName(spell_id), + spell_id + ).c_str() + ); + + quest_manager.WorldWideSpell(update_type, spell_id); + if (!disable_message) { + quest_manager.WorldWideMessage( + Chat::Yellow, + fmt::format( + "[SYSTEM] A GM has cast [{}] world-wide!", + GetSpellName(spell_id) + ).c_str() + ); + } + } + else { + c->Message(Chat::White, "Usage: #worldwide cast [Spell ID] [Disable Message]"); + } + } + else if (sub_command == "remove") { + if (sep->arg[2] && Seperator::IsNumber(sep->arg[2])) { + uint8 update_type = WWSpellUpdateType_Remove; + auto spell_id = std::stoul(sep->arg[2]); + + c->Message( + Chat::White, + fmt::format( + "World Wide Remove Spell | Spell: {} ({})", + GetSpellName(spell_id), + spell_id + ).c_str() + ); + + quest_manager.WorldWideSpell(update_type, spell_id); + } + else { + c->Message(Chat::White, "Usage: #worldwide remove [Spell ID]"); + } + } + else if (sub_command == "message") { + if (sep->arg[2]) { + std::string message = sep->argplus[2]; + quest_manager.WorldWideMessage( + Chat::White, + fmt::format( + "{}", + message + ).c_str() + ); + } + else { + c->Message(Chat::White, "Usage: #worldwide message [Message]"); + } + } + else if (sub_command == "move") { + if (sep->arg[2]) { + uint8 update_type = WWMoveUpdateType_MoveZone; + uint32 zone_id = 0; + std::string zone_short_name; + if (Seperator::IsNumber(sep->arg[2])) { + zone_id = std::stoul(sep->arg[2]); + } + + if (zone_id) { + zone_short_name = ZoneName(zone_id); + } + else { + zone_short_name = sep->arg[2]; + } + + c->Message( + Chat::White, + fmt::format( + "World Wide Zone | Zone: {} ({}) ID: {}", + ZoneLongName( + ZoneID(zone_short_name) + ), + zone_short_name, + ZoneID(zone_short_name) + ).c_str() + ); + + quest_manager.WorldWideMove(update_type, zone_short_name.c_str()); + } + else { + c->Message( + Chat::White, + "Usage: #worldwide move [Zone ID] or #worldwide move [Zone Short Name]" + ); + } + } + else if (sub_command == "moveinstance") { + if (Seperator::IsNumber(sep->arg[2])) { + uint8 update_type = WWMoveUpdateType_MoveZoneInstance; + const char *zone_short_name = ""; + uint16 instance_id = std::stoi(sep->arg[2]); + + c->Message( + Chat::White, + fmt::format( + "World Wide Zone Instance | Instance ID: {}", + instance_id + ).c_str() + ); + + quest_manager.WorldWideMove(update_type, zone_short_name, instance_id); + } + else { + c->Message(Chat::White, "Usage: #worldwide moveinstance [Instance ID]"); + } + } + + if (!sep->arg[1]) { + c->Message(Chat::White, "This command is used to perform world-wide tasks."); + c->Message(Chat::White, "Usage: #worldwide cast [Spell ID] [Disable Message]"); + c->Message(Chat::White, "Usage: #worldwide remove [Spell ID]"); + c->Message(Chat::White, "Usage: #worldwide message [Message]"); + c->Message( + Chat::White, + "Usage: #worldwide move [Zone ID] or #worldwide move [Zone Short Name]" + ); + c->Message(Chat::White, "Usage: #worldwide moveinstance [Instance ID]"); + } +} + diff --git a/zone/gm_commands/wp.cpp b/zone/gm_commands/wp.cpp new file mode 100755 index 000000000..4f0b9330e --- /dev/null +++ b/zone/gm_commands/wp.cpp @@ -0,0 +1,51 @@ +#include "../client.h" + +void command_wp(Client *c, const Seperator *sep) +{ + auto command_type = sep->arg[1]; + auto grid_id = atoi(sep->arg[2]); + if (grid_id != 0) { + auto pause = atoi(sep->arg[3]); + auto waypoint = atoi(sep->arg[4]); + auto zone_id = zone->GetZoneID(); + if (strcasecmp("add", command_type) == 0) { + if (waypoint == 0) { // Default to highest if it's left blank, or we enter 0 + waypoint = (content_db.GetHighestWaypoint(zone_id, grid_id) + 1); + } + + if (strcasecmp("-h", sep->arg[5]) == 0) { + content_db.AddWP(c, grid_id, waypoint, c->GetPosition(), pause, zone_id); + } + else { + auto position = c->GetPosition(); + position.w = -1; + content_db.AddWP(c, grid_id, waypoint, position, pause, zone_id); + } + c->Message( + Chat::White, + fmt::format( + "Waypoint {} added to grid {} with a pause of {} {}.", + waypoint, + grid_id, + pause, + (pause == 1 ? "second" : "seconds") + ).c_str() + ); + } + else if (strcasecmp("delete", command_type) == 0) { + content_db.DeleteWaypoint(c, grid_id, waypoint, zone_id); + c->Message( + Chat::White, + fmt::format( + "Waypoint {} deleted from grid {}.", + waypoint, + grid_id + ).c_str() + ); + } + } + else { + c->Message(Chat::White, "Usage: #wp [add|delete] [grid_id] [pause] [waypoint_id] [-h]"); + } +} + diff --git a/zone/gm_commands/wpadd.cpp b/zone/gm_commands/wpadd.cpp new file mode 100755 index 000000000..df74c03e4 --- /dev/null +++ b/zone/gm_commands/wpadd.cpp @@ -0,0 +1,52 @@ +#include "../client.h" + +void command_wpadd(Client *c, const Seperator *sep) +{ + int type1 = 0, type2 = 0, pause = 0; // Defaults for a new grid + Mob *target = c->GetTarget(); + if (target && target->IsNPC()) { + Spawn2 *s2info = target->CastToNPC()->respawn2; + if (s2info == nullptr) { + c->Message( + Chat::White, + "#wpadd Failed, you must target a valid spawn." + ); + return; + } + + if (sep->arg[1][0]) { + if (atoi(sep->arg[1]) >= 0) { + pause = atoi(sep->arg[1]); + } + else { + c->Message(Chat::White, "Usage: #wpadd [pause] [-h]"); + return; + } + } + auto position = c->GetPosition(); + if (strcmp("-h", sep->arg[2]) != 0) { + position.w = -1; + } + + auto zone_id = zone->GetZoneID(); + uint32 tmp_grid = content_db.AddWPForSpawn(c, s2info->GetID(), position, pause, type1, type2, zone_id); + if (tmp_grid) { + target->CastToNPC()->SetGrid(tmp_grid); + } + + auto grid_id = target->CastToNPC()->GetGrid(); + target->CastToNPC()->AssignWaypoints(grid_id); + c->Message( + Chat::White, + fmt::format( + "Waypoint added to grid {} in zone ID {}. Use #wpinfo to see waypoints for this NPC (may need to #repop first).", + grid_id, + zone_id + ).c_str() + ); + } + else { + c->Message(Chat::White, "You must target an NPC to use this."); + } +} + diff --git a/zone/gm_commands/wpinfo.cpp b/zone/gm_commands/wpinfo.cpp new file mode 100755 index 000000000..55b775581 --- /dev/null +++ b/zone/gm_commands/wpinfo.cpp @@ -0,0 +1,26 @@ +#include "../client.h" + +void command_wpinfo(Client *c, const Seperator *sep) +{ + if (!c->GetTarget() || !c->GetTarget()->IsNPC()) { + c->Message(Chat::White, "You must target an NPC to use this command."); + return; + } + + auto target = c->GetTarget()->CastToNPC(); + + if (!target->GetGrid()) { + c->Message( + Chat::White, + fmt::format( + "{} ({}) is not a part of any grid.", + target->GetCleanName(), + target->GetID() + ).c_str() + ); + return; + } + + target->DisplayWaypointInfo(c); +} + diff --git a/zone/gm_commands/xtargets.cpp b/zone/gm_commands/xtargets.cpp new file mode 100755 index 000000000..bd300e819 --- /dev/null +++ b/zone/gm_commands/xtargets.cpp @@ -0,0 +1,28 @@ +#include "../client.h" + +void command_xtargets(Client *c, const Seperator *sep) +{ + Client *t; + + if (c->GetTarget() && c->GetTarget()->IsClient()) { + t = c->GetTarget()->CastToClient(); + } + else { + t = c; + } + + if (sep->arg[1][0]) { + uint8 NewMax = atoi(sep->arg[1]); + + if ((NewMax < 5) || (NewMax > XTARGET_HARDCAP)) { + c->Message(Chat::Red, "Number of XTargets must be between 5 and %i", XTARGET_HARDCAP); + return; + } + t->SetMaxXTargets(NewMax); + c->Message(Chat::White, "Max number of XTargets set to %i", NewMax); + } + else { + t->ShowXTargets(c); + } +} + diff --git a/zone/gm_commands/zclip.cpp b/zone/gm_commands/zclip.cpp new file mode 100755 index 000000000..f8252885f --- /dev/null +++ b/zone/gm_commands/zclip.cpp @@ -0,0 +1,108 @@ +#include "../client.h" + +void command_zclip(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if ( + !arguments || + !sep->IsNumber(1) || + !sep->IsNumber(2) + ) { + c->Message(Chat::White, "Usage: #zclip [Minimum Clip] [Maximum Clip] [Fog Minimum Clip] [Fog Maximum Clip] [Permanent (0 = False, 1 = True)]"); + } + + auto minimum_clip = std::stof(sep->arg[1]); + auto maximum_clip = std::stof(sep->arg[2]); + auto minimum_fog_clip = sep->arg[3] ? std::stof(sep->arg[3]) : 0; + auto maximum_fog_clip = sep->arg[4] ? std::stof(sep->arg[4]) : 0; + auto permanent = sep->arg[5] ? atobool(sep->arg[5]) : false; + if (minimum_clip <= 0 || maximum_clip <= 0) { + c->Message(Chat::White, "Minimum Clip and Maximum Clip must be greater than 0."); + return; + } else if (minimum_clip > maximum_clip) { + c->Message(Chat::White, "Minimum Clip must be less than or equal to Maximum Clip!"); + return; + } else { + zone->newzone_data.minclip = minimum_clip; + zone->newzone_data.maxclip = maximum_clip; + + if (minimum_fog_clip) { + for (int fog_index = 0; fog_index < 4; fog_index++) { + zone->newzone_data.fog_minclip[fog_index] = minimum_fog_clip; + } + } + + if (maximum_fog_clip) { + for (int fog_index = 0; fog_index < 4; fog_index++) { + zone->newzone_data.fog_maxclip[fog_index] = maximum_fog_clip; + } + } + + if (permanent) { + auto query = fmt::format( + "UPDATE zone SET minclip = {:.2f}, maxclip = {:.2f} WHERE zoneidnumber = {} AND version = {}", + minimum_clip, + maximum_clip, + zone->GetZoneID(), + zone->GetInstanceVersion() + ); + database.QueryDatabase(query); + + if (minimum_fog_clip) { + query = fmt::format( + "UPDATE zone SET fog_minclip = {:.2f} WHERE zoneidnumber = {} AND version = {}", + minimum_fog_clip, + zone->GetZoneID(), + zone->GetInstanceVersion() + ); + database.QueryDatabase(query); + } + + if (maximum_fog_clip) { + query = fmt::format( + "UPDATE zone SET fog_maxclip = {:.2f} WHERE zoneidnumber = {} AND version = {}", + maximum_fog_clip, + zone->GetZoneID(), + zone->GetInstanceVersion() + ); + database.QueryDatabase(query); + } + } + + auto outapp = new EQApplicationPacket(OP_NewZone, sizeof(NewZone_Struct)); + memcpy(outapp->pBuffer, &zone->newzone_data, outapp->size); + entity_list.QueueClients(c, outapp); + safe_delete(outapp); + + c->Message( + Chat::White, + fmt::format( + "Clipping Changed | Zone: {} ({}) Permanent: {}", + zone->GetLongName(), + zone->GetZoneID(), + permanent ? "Yes" : "No" + ).c_str() + ); + + c->Message( + Chat::White, + fmt::format( + "Clipping Changed | Minimum Clip: {:.2f} Maximum Clip: {:.2f}", + minimum_clip, + maximum_clip + ).c_str() + ); + + if (minimum_fog_clip || maximum_fog_clip) { + c->Message( + Chat::White, + fmt::format( + "Clipping Changed | Fog Minimum Clip: {:.2f} Fog Maximum Clip: {:.2f}", + minimum_fog_clip, + maximum_fog_clip + ).c_str() + ); + } + } +} + diff --git a/zone/gm_commands/zcolor.cpp b/zone/gm_commands/zcolor.cpp new file mode 100755 index 000000000..bfe8cbd44 --- /dev/null +++ b/zone/gm_commands/zcolor.cpp @@ -0,0 +1,68 @@ +#include "../client.h" + +void command_zcolor(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if ( + !arguments || + !sep->IsNumber(1) || + !sep->IsNumber(2) || + !sep->IsNumber(3) + ) { + c->Message(Chat::White, "Usage: #zcolor [Red] [Green] [Blue] [Permanent (0 = False, 1 = True)]"); + return; + } + + auto red = std::stoul(sep->arg[1]); + auto green = std::stoul(sep->arg[2]); + auto blue = std::stoul(sep->arg[3]); + auto permanent = sep->arg[4] ? atobool(sep->arg[4]) : false; + if ( + red < 0 || + red > 255 || + green < 0 || + green > 255 || + blue < 0 || + blue > 255 + ) { + c->Message(Chat::White, "Colors cannot be less than 0 or greater than 255."); + return; + } + + if (permanent) { + auto query = fmt::format( + "UPDATE zone SET fog_red = {}, fog_green = {}, fog_blue = {} " + "WHERE zoneidnumber = {} AND version = {}", + red, + green, + blue, + zone->GetZoneID(), + zone->GetInstanceVersion() + ); + database.QueryDatabase(query); + } + + for (int fog_index = 0; fog_index < 4; fog_index++) { + zone->newzone_data.fog_red[fog_index] = static_cast(red); + zone->newzone_data.fog_green[fog_index] = static_cast(green); + zone->newzone_data.fog_blue[fog_index] = static_cast(blue); + } + + auto outapp = new EQApplicationPacket(OP_NewZone, sizeof(NewZone_Struct)); + memcpy(outapp->pBuffer, &zone->newzone_data, outapp->size); + entity_list.QueueClients(c, outapp); + safe_delete(outapp); + + c->Message( + Chat::White, + fmt::format( + "Fog Color Changed | Zone: {} ({}) Red: {} Green: {} Blue: {} Permanent: {}", + zone->GetLongName(), + zone->GetZoneID(), + red, + green, + blue, + permanent ? "Yes" : "No" + ).c_str() + ); +} diff --git a/zone/gm_commands/zheader.cpp b/zone/gm_commands/zheader.cpp new file mode 100755 index 000000000..f04672e63 --- /dev/null +++ b/zone/gm_commands/zheader.cpp @@ -0,0 +1,61 @@ +#include "../client.h" + +void command_zheader(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments) { + c->Message(Chat::White, "Usage: #zheader [Zone ID|Zone Short Name] [Version]"); + return; + } + + auto zone_id = ( + sep->IsNumber(1) ? + std::stoul(sep->arg[1]) : + ZoneID(sep->arg[1]) + ); + if (!zone_id) { + c->Message( + Chat::White, + fmt::format( + "Zone ID {} could not be found.", + zone_id + ).c_str() + ); + return; + } + + auto zone_short_name = ZoneName(zone_id); + auto zone_long_name = ZoneLongName(zone_id); + auto version = ( + sep->IsNumber(2) ? + std::stoul(sep->arg[2]) : + 0 + ); + + auto outapp = new EQApplicationPacket(OP_NewZone, sizeof(NewZone_Struct)); + memcpy(outapp->pBuffer, &zone->newzone_data, outapp->size); + entity_list.QueueClients(c, outapp); + safe_delete(outapp); + + c->Message( + Chat::White, + fmt::format( + "Zone Header Load {} | Zone: {} ({}){}", + ( + zone->LoadZoneCFG(zone_short_name, version) ? + "Suceeded" : + "Failed" + ), + zone_long_name, + zone_short_name, + ( + version ? + fmt::format( + " Version: {}", + version + ) : + "" + ) + ).c_str() + ); +} diff --git a/zone/gm_commands/zonebootup.cpp b/zone/gm_commands/zonebootup.cpp new file mode 100755 index 000000000..7c54e24fb --- /dev/null +++ b/zone/gm_commands/zonebootup.cpp @@ -0,0 +1,25 @@ +#include "../client.h" +#include "../worldserver.h" + +extern WorldServer worldserver; + +void command_zonebootup(Client *c, const Seperator *sep) +{ + if (!worldserver.Connected()) { + c->Message(Chat::White, "Error: World server disconnected"); + } + else if (sep->arg[2][0] == 0) { + c->Message(Chat::White, "Usage: #zonebootup ZoneServerID# zoneshortname"); + } + else { + auto pack = new ServerPacket(ServerOP_ZoneBootup, sizeof(ServerZoneStateChange_struct)); + ServerZoneStateChange_struct *s = (ServerZoneStateChange_struct *) pack->pBuffer; + s->ZoneServerID = atoi(sep->arg[1]); + strcpy(s->adminname, c->GetName()); + s->zoneid = ZoneID(sep->arg[2]); + s->makestatic = (bool) (strcasecmp(sep->arg[3], "static") == 0); + worldserver.SendPacket(pack); + safe_delete(pack); + } +} + diff --git a/zone/gm_commands/zonelock.cpp b/zone/gm_commands/zonelock.cpp new file mode 100755 index 000000000..f89f1e9ad --- /dev/null +++ b/zone/gm_commands/zonelock.cpp @@ -0,0 +1,76 @@ +#include "../client.h" +#include "../worldserver.h" + +extern WorldServer worldserver; + +void command_zonelock(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments) { + c->Message(Chat::White, "Usage: #zonelock list - Lists Locked Zones"); + if (c->Admin() >= commandLockZones) { + c->Message( + Chat::White, + "Usage: #zonelock lock [Zone ID] or #zonelock lock [Zone Short Name] - Locks a Zone by ID or Short Name" + ); + c->Message( + Chat::White, + "Usage: #zonelock unlock [Zone ID] or #zonelock unlock [Zone Short Name] - Unlocks a Zone by ID or Short Name" + ); + } + return; + } + + std::string lock_type = str_tolower(sep->arg[1]); + bool is_list = lock_type.find("list") != std::string::npos; + bool is_lock = lock_type.find("lock") != std::string::npos; + bool is_unlock = lock_type.find("unlock") != std::string::npos; + if (!is_list && !is_lock && !is_unlock) { + c->Message(Chat::White, "Usage: #zonelock list - Lists Locked Zones"); + if (c->Admin() >= commandLockZones) { + c->Message( + Chat::White, + "Usage: #zonelock lock [Zone ID] or #zonelock lock [Zone Short Name] - Locks a Zone by ID or Short Name" + ); + c->Message( + Chat::White, + "Usage: #zonelock unlock [Zone ID] or #zonelock unlock [Zone Short Name] - Unlocks a Zone by ID or Short Name" + ); + } + return; + } + + auto pack = new ServerPacket(ServerOP_LockZone, sizeof(ServerLockZone_Struct)); + ServerLockZone_Struct *lock_zone = (ServerLockZone_Struct *) pack->pBuffer; + strn0cpy(lock_zone->adminname, c->GetName(), sizeof(lock_zone->adminname)); + + if (is_list) { + lock_zone->op = ServerLockType::List; + worldserver.SendPacket(pack); + } + else if (!is_list && c->Admin() >= commandLockZones) { + auto zone_id = ( + sep->IsNumber(2) ? + static_cast(std::stoul(sep->arg[2])) : + static_cast(ZoneID(sep->arg[2])) + ); + std::string zone_short_name = str_tolower(ZoneName(zone_id, true)); + bool is_unknown_zone = zone_short_name.find("unknown") != std::string::npos; + if (zone_id && !is_unknown_zone) { + lock_zone->op = is_lock ? ServerLockType::Lock : ServerLockType::Unlock; + lock_zone->zoneID = zone_id; + worldserver.SendPacket(pack); + } + else { + c->Message( + Chat::White, + fmt::format( + "Usage: #zonelock {} [Zone ID] or #zonelock {} [Zone Short Name]", + is_lock ? "lock" : "unlock" + ).c_str() + ); + } + } + safe_delete(pack); +} + diff --git a/zone/gm_commands/zoneshutdown.cpp b/zone/gm_commands/zoneshutdown.cpp new file mode 100755 index 000000000..14550b9db --- /dev/null +++ b/zone/gm_commands/zoneshutdown.cpp @@ -0,0 +1,30 @@ +#include "../client.h" +#include "../worldserver.h" + +extern WorldServer worldserver; + +void command_zoneshutdown(Client *c, const Seperator *sep) +{ + if (!worldserver.Connected()) { + c->Message(Chat::White, "Error: World server disconnected"); + } + else if (sep->arg[1][0] == 0) { + c->Message(Chat::White, "Usage: #zoneshutdown zoneshortname"); + } + else { + auto pack = new ServerPacket( + ServerOP_ZoneShutdown, + sizeof(ServerZoneStateChange_struct)); + ServerZoneStateChange_struct *s = (ServerZoneStateChange_struct *) pack->pBuffer; + strcpy(s->adminname, c->GetName()); + if (sep->arg[1][0] >= '0' && sep->arg[1][0] <= '9') { + s->ZoneServerID = atoi(sep->arg[1]); + } + else { + s->zoneid = ZoneID(sep->arg[1]); + } + worldserver.SendPacket(pack); + safe_delete(pack); + } +} + diff --git a/zone/gm_commands/zonestatus.cpp b/zone/gm_commands/zonestatus.cpp new file mode 100755 index 000000000..51c5f763e --- /dev/null +++ b/zone/gm_commands/zonestatus.cpp @@ -0,0 +1,19 @@ +#include "../client.h" +#include "../worldserver.h" + +extern WorldServer worldserver; + +void command_zonestatus(Client *c, const Seperator *sep) +{ + if (!worldserver.Connected()) { + c->Message(Chat::White, "Error: World server disconnected"); + } + else { + auto pack = new ServerPacket(ServerOP_ZoneStatus, strlen(c->GetName()) + 2); + memset(pack->pBuffer, (uint8) c->Admin(), 1); + strcpy((char *) &pack->pBuffer[1], c->GetName()); + worldserver.SendPacket(pack); + delete pack; + } +} + diff --git a/zone/gm_commands/zopp.cpp b/zone/gm_commands/zopp.cpp new file mode 100755 index 000000000..08f10395b --- /dev/null +++ b/zone/gm_commands/zopp.cpp @@ -0,0 +1,64 @@ +#include "../client.h" + +void command_zopp(Client *c, const Seperator *sep) +{ // - Owner only command..non-targetable to eliminate malicious or mischievious activities. + if (!c) { + return; + } + else if (sep->argnum < 3 || sep->argnum > 4) { + c->Message(Chat::White, "Usage: #zopp [trade/summon] [slot id] [item id] [*charges]"); + } + else if (!strcasecmp(sep->arg[1], "trade") == 0 && !strcasecmp(sep->arg[1], "t") == 0 && + !strcasecmp(sep->arg[1], "summon") == 0 && !strcasecmp(sep->arg[1], "s") == 0) { + c->Message(Chat::White, "Usage: #zopp [trade/summon] [slot id] [item id] [*charges]"); + } + else if (!sep->IsNumber(2) || !sep->IsNumber(3) || (sep->argnum == 4 && !sep->IsNumber(4))) { + c->Message(Chat::White, "Usage: #zopp [trade/summon] [slot id] [item id] [*charges]"); + } + else { + ItemPacketType packettype; + + if (strcasecmp(sep->arg[1], "trade") == 0 || strcasecmp(sep->arg[1], "t") == 0) { + packettype = ItemPacketTrade; + } + else { + packettype = ItemPacketLimbo; + } + + int16 slotid = atoi(sep->arg[2]); + uint32 itemid = atoi(sep->arg[3]); + int16 charges = sep->argnum == 4 ? atoi(sep->arg[4]) : 1; // defaults to 1 charge if not specified + + const EQ::ItemData *FakeItem = database.GetItem(itemid); + + if (!FakeItem) { + c->Message(Chat::Red, "Error: Item [%u] is not a valid item id.", itemid); + return; + } + + int16 item_status = 0; + const EQ::ItemData *item = database.GetItem(itemid); + if (item) { + item_status = static_cast(item->MinStatus); + } + if (item_status > c->Admin()) { + c->Message(Chat::Red, "Error: Insufficient status to use this command."); + return; + } + + if (charges < 0 || charges > FakeItem->StackSize) { + c->Message(Chat::Red, "Warning: The specified charge count does not meet expected criteria!"); + c->Message(Chat::White, "Processing request..results may cause unpredictable behavior."); + } + + EQ::ItemInstance *FakeItemInst = database.CreateItem(FakeItem, charges); + c->SendItemPacket(slotid, FakeItemInst, packettype); + c->Message( + Chat::White, "Sending zephyr op packet to client - [%s] %s (%u) with %i %s to slot %i.", + packettype == ItemPacketTrade ? "Trade" : "Summon", FakeItem->Name, itemid, charges, + std::abs(charges == 1) ? "charge" : "charges", slotid + ); + safe_delete(FakeItemInst); + } +} + diff --git a/zone/gm_commands/zsafecoords.cpp b/zone/gm_commands/zsafecoords.cpp new file mode 100755 index 000000000..bce0ea743 --- /dev/null +++ b/zone/gm_commands/zsafecoords.cpp @@ -0,0 +1,66 @@ +#include "../client.h" + +void command_zsafecoords(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if ( + !arguments || + !sep->IsNumber(1) || + !sep->IsNumber(2) || + !sep->IsNumber(3) + ) { + c->Message(Chat::White, "Usage: #zsafecoords [X] [Y] [Z] [Heading] [Permanent (0 = False, 1 = True)]"); + c->Message(Chat::White, "Not sending Heading defaults to current Heading and the change is temporary."); + return; + } + + auto x = std::stof(sep->arg[1]); + auto y = std::stof(sep->arg[2]); + auto z = std::stof(sep->arg[3]); + auto heading = sep->arg[3] ? std::stof(sep->arg[3]) : c->GetHeading(); + auto permanent = sep->arg[4] ? atobool(sep->arg[4]) : false; + if (permanent) { + auto query = fmt::format( + "UPDATE zone SET safe_x = {:.2f}, safe_y = {:.2f}, safe_z = {:.2f}, safe_heading = {:.2f} WHERE zoneidnumber = {} AND version = {}", + x, + y, + z, + heading, + zone->GetZoneID(), + zone->GetInstanceVersion() + ); + database.QueryDatabase(query); + } + + zone->newzone_data.safe_x = x; + zone->newzone_data.safe_y = y; + zone->newzone_data.safe_z = z; + + auto outapp = new EQApplicationPacket(OP_NewZone, sizeof(NewZone_Struct)); + memcpy(outapp->pBuffer, &zone->newzone_data, outapp->size); + entity_list.QueueClients(c, outapp); + safe_delete(outapp); + + c->Message( + Chat::White, + fmt::format( + "Safe Coordinates Changed | Zone: {} ({}){} XYZ: {:.2f}, {:.2f}, {:.2f} Heading: {:.2f} Permanent: {} ", + zone->GetLongName(), + zone->GetZoneID(), + ( + zone->GetInstanceVersion() ? + fmt::format( + " Version: {}", + zone->GetInstanceVersion() + ) : + "" + ), + x, + y, + z, + heading, + permanent ? "Yes" : "No" + ).c_str() + ); +} + diff --git a/zone/gm_commands/zsave.cpp b/zone/gm_commands/zsave.cpp new file mode 100755 index 000000000..acfa4536d --- /dev/null +++ b/zone/gm_commands/zsave.cpp @@ -0,0 +1,16 @@ +#include "../client.h" + +void command_zsave(Client *c, const Seperator *sep) +{ + c->Message( + Chat::White, + fmt::format( + "Zone header {}.", + ( + zone->SaveZoneCFG() ? + "saved successfully" : + "failed to save" + ) + ).c_str() + ); +} diff --git a/zone/gm_commands/zsky.cpp b/zone/gm_commands/zsky.cpp new file mode 100755 index 000000000..d43b2f152 --- /dev/null +++ b/zone/gm_commands/zsky.cpp @@ -0,0 +1,44 @@ +#include "../client.h" + +void command_zsky(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments || !sep->IsNumber(1)) { + c->Message(Chat::White, "Usage: #zsky [Sky Type] [Permanent (0 = False, 1 = True)]"); + return; + } + + auto sky_type = std::stoul(sep->arg[1]); + auto permanent = sep->arg[2] ? atobool(sep->arg[2]) : false; + if (sky_type < 0 || sky_type > 255) { + c->Message(Chat::White, "Sky Type cannot be less than 0 or greater than 255!"); + return; + } + + if (permanent) { + auto query = fmt::format( + "UPDATE zone SET sky = {} WHERE zoneidnumber = {} AND version = {}", + sky_type, + zone->GetZoneID(), + zone->GetInstanceVersion() + ); + database.QueryDatabase(query); + } + + zone->newzone_data.sky = static_cast(sky_type); + auto outapp = new EQApplicationPacket(OP_NewZone, sizeof(NewZone_Struct)); + memcpy(outapp->pBuffer, &zone->newzone_data, outapp->size); + entity_list.QueueClients(c, outapp); + safe_delete(outapp); + + c->Message( + Chat::White, + fmt::format( + "Sky Changed | Zone: {} ({}) Sky Type: {} Permanent: {}", + zone->GetLongName(), + zone->GetZoneID(), + sky_type, + permanent ? "Yes" : "No" + ).c_str() + ); +} diff --git a/zone/gm_commands/zstats.cpp b/zone/gm_commands/zstats.cpp new file mode 100755 index 000000000..9c4330ae5 --- /dev/null +++ b/zone/gm_commands/zstats.cpp @@ -0,0 +1,208 @@ +#include "../client.h" + +void command_zstats(Client *c, const Seperator *sep) +{ + // Zone + c->Message( + Chat::White, + fmt::format( + "Zone | ID: {} Instance ID: {} Name: {} ({})", + zone->GetZoneID(), + zone->GetInstanceID(), + zone->GetLongName(), + zone->GetShortName() + ).c_str() + ); + + // Type + c->Message( + Chat::White, + fmt::format( + "Type: {}", + zone->newzone_data.ztype + ).c_str() + ); + + // Fog Data + for (int fog_index = 0; fog_index < 4; fog_index++) { + int fog_number = (fog_index + 1); + c->Message( + Chat::White, + fmt::format( + "Fog {} Colors | Red: {} Blue: {} Green: {} ", + fog_number, + zone->newzone_data.fog_red[fog_index], + zone->newzone_data.fog_green[fog_index], + zone->newzone_data.fog_blue[fog_index] + ).c_str() + ); + + c->Message( + Chat::White, + fmt::format( + "Fog {} Clipping Distance | Min: {} Max: {}", + fog_number, + zone->newzone_data.fog_minclip[fog_index], + zone->newzone_data.fog_maxclip[fog_index] + ).c_str() + ); + } + + // Fog Density + c->Message( + Chat::White, + fmt::format( + "Fog Density: {}", + zone->newzone_data.fog_density + ).c_str() + ); + + + // Gravity + c->Message( + Chat::White, + fmt::format( + "Gravity: {}", + zone->newzone_data.gravity + ).c_str() + ); + + // Time Type + c->Message( + Chat::White, + fmt::format( + "Time Type: {}", + zone->newzone_data.time_type + ).c_str() + ); + + // Experience Multiplier + c->Message( + Chat::White, + fmt::format( + "Experience Multiplier: {}", + zone->newzone_data.zone_exp_multiplier + ).c_str() + ); + + // Safe Coordinates + c->Message( + Chat::White, + fmt::format( + "Safe Coordinates: {}, {}, {}", + zone->newzone_data.safe_x, + zone->newzone_data.safe_y, + zone->newzone_data.safe_z + ).c_str() + ); + + // Max Z + c->Message( + Chat::White, + fmt::format( + "Max Z: {}", + zone->newzone_data.max_z + ).c_str() + ); + + // Underworld Z + c->Message( + Chat::White, + fmt::format( + "Underworld Z: {}", + zone->newzone_data.underworld + ).c_str() + ); + + // Clipping Distance + c->Message( + Chat::White, + fmt::format( + "Clipping Distance | Min: {} Max: {}", + zone->newzone_data.minclip, + zone->newzone_data.maxclip + ).c_str() + ); + + // Weather Data + for (int weather_index = 0; weather_index < 4; weather_index++) { + int weather_number = (weather_index + 1); + c->Message( + Chat::White, + fmt::format( + "Rain {} | Chance: {} Duration: {} ", + weather_number, + zone->newzone_data.rain_chance[weather_index], + zone->newzone_data.rain_duration[weather_index] + ).c_str() + ); + + c->Message( + Chat::White, + fmt::format( + "Snow {} | Chance: {} Duration: {}", + weather_number, + zone->newzone_data.snow_chance[weather_index], + zone->newzone_data.snow_duration[weather_index] + ).c_str() + ); + } + + // Sky Type + c->Message( + Chat::White, + fmt::format( + "Sky Type: {}", + zone->newzone_data.sky + ).c_str() + ); + + // Suspend Buffs + c->Message( + Chat::White, + fmt::format( + "Suspend Buffs: {}", + zone->newzone_data.SuspendBuffs + ).c_str() + ); + + // Regeneration Data + c->Message( + Chat::White, + fmt::format( + "Regen | Health: {} Mana: {} Endurance: {}", + zone->newzone_data.FastRegenHP, + zone->newzone_data.FastRegenMana, + zone->newzone_data.FastRegenEndurance + ).c_str() + ); + + // NPC Max Aggro Distance + c->Message( + Chat::White, + fmt::format( + "NPC Max Aggro Distance: {}", + zone->newzone_data.NPCAggroMaxDist + ).c_str() + ); + + // Underworld Teleport Index + c->Message( + Chat::White, + fmt::format( + "Underworld Teleport Index: {}", + zone->newzone_data.underworld_teleport_index + ).c_str() + ); + + // Lava Damage + c->Message( + Chat::White, + fmt::format( + "Lava Damage | Min: {} Max: {}", + zone->newzone_data.MinLavaDamage, + zone->newzone_data.LavaDamage + ).c_str() + ); +} + diff --git a/zone/gm_commands/zunderworld.cpp b/zone/gm_commands/zunderworld.cpp new file mode 100755 index 000000000..88499dc2e --- /dev/null +++ b/zone/gm_commands/zunderworld.cpp @@ -0,0 +1,39 @@ +#include "../client.h" + +void command_zunderworld(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments || !sep->IsNumber(1)) { + c->Message(Chat::White, "Usage: #zunderworld [Z] [Permanent (0 = False, 1 = True)]"); + return; + } + + auto z = std::stof(sep->arg[1]); + auto permanent = sep->arg[2] ? atobool(sep->arg[2]) : false; + if (permanent) { + auto query = fmt::format( + "UPDATE zone SET underworld = {:.2f} WHERE zoneidnumber = {} AND version = {}", + z, + zone->GetZoneID(), + zone->GetInstanceVersion() + ); + database.QueryDatabase(query); + } + + zone->newzone_data.underworld = z; + auto outapp = new EQApplicationPacket(OP_NewZone, sizeof(NewZone_Struct)); + memcpy(outapp->pBuffer, &zone->newzone_data, outapp->size); + entity_list.QueueClients(c, outapp); + safe_delete(outapp); + + c->Message( + Chat::White, + fmt::format( + "Underworld Z Changed | Zone: {} ({}) Z: {:.2f} Permanent: {}", + zone->GetLongName(), + zone->GetZoneID(), + z, + permanent ? "Yes" : "No" + ).c_str() + ); +} diff --git a/zone/groups.cpp b/zone/groups.cpp index 5d44dc5df..f2867f9d1 100644 --- a/zone/groups.cpp +++ b/zone/groups.cpp @@ -838,7 +838,7 @@ void Group::CastGroupSpell(Mob* caster, uint16 spell_id) { if(members[z] == caster) { caster->SpellOnTarget(spell_id, caster); #ifdef GROUP_BUFF_PETS - if(spells[spell_id].targettype != ST_GroupNoPets && caster->GetPet() && caster->HasPetAffinity() && !caster->GetPet()->IsCharmed()) + if(spells[spell_id].target_type != ST_GroupNoPets && caster->GetPet() && caster->HasPetAffinity() && !caster->GetPet()->IsCharmed()) caster->SpellOnTarget(spell_id, caster->GetPet()); #endif } @@ -849,7 +849,7 @@ void Group::CastGroupSpell(Mob* caster, uint16 spell_id) { members[z]->CalcSpellPowerDistanceMod(spell_id, distance); caster->SpellOnTarget(spell_id, members[z]); #ifdef GROUP_BUFF_PETS - if(spells[spell_id].targettype != ST_GroupNoPets && members[z]->GetPet() && members[z]->HasPetAffinity() && !members[z]->GetPet()->IsCharmed()) + if(spells[spell_id].target_type != ST_GroupNoPets && members[z]->GetPet() && members[z]->HasPetAffinity() && !members[z]->GetPet()->IsCharmed()) caster->SpellOnTarget(spell_id, members[z]->GetPet()); #endif } else @@ -861,42 +861,6 @@ void Group::CastGroupSpell(Mob* caster, uint16 spell_id) { disbandcheck = true; } -// does the caster + group -void Group::GroupBardPulse(Mob* caster, uint16 spell_id) { - uint32 z; - float range, distance; - - if(!caster) - return; - - castspell = true; - range = caster->GetAOERange(spell_id); - - float range2 = range*range; - - for(z=0; z < MAX_GROUP_MEMBERS; z++) { - if(members[z] == caster) { - caster->BardPulse(spell_id, caster); -#ifdef GROUP_BUFF_PETS - if(caster->GetPet() && caster->HasPetAffinity() && !caster->GetPet()->IsCharmed()) - caster->BardPulse(spell_id, caster->GetPet()); -#endif - } - else if(members[z] != nullptr) - { - distance = DistanceSquared(caster->GetPosition(), members[z]->GetPosition()); - if(distance <= range2) { - members[z]->BardPulse(spell_id, caster); -#ifdef GROUP_BUFF_PETS - if(members[z]->GetPet() && members[z]->HasPetAffinity() && !members[z]->GetPet()->IsCharmed()) - members[z]->GetPet()->BardPulse(spell_id, caster); -#endif - } else - LogSpells("Group bard pulse: [{}] is out of range [{}] at distance [{}] from [{}]", members[z]->GetName(), range, distance, caster->GetName()); - } - } -} - bool Group::IsGroupMember(Mob* client) { bool Result = false; diff --git a/zone/groups.h b/zone/groups.h index 57dfdbfb6..1a90b13ae 100644 --- a/zone/groups.h +++ b/zone/groups.h @@ -71,7 +71,6 @@ public: bool IsGroup() { return true; } void SendGroupJoinOOZ(Mob* NewMember); void CastGroupSpell(Mob* caster,uint16 spellid); - void GroupBardPulse(Mob* caster,uint16 spellid); void SplitExp(uint32 exp, Mob* other); void GroupMessage(Mob* sender,uint8 language,uint8 lang_skill,const char* message); void GroupMessageString(Mob* sender, uint32 type, uint32 string_id, const char* message,const char* message2=0,const char* message3=0,const char* message4=0,const char* message5=0,const char* message6=0,const char* message7=0,const char* message8=0,const char* message9=0, uint32 distance = 0); diff --git a/zone/guild_mgr.cpp b/zone/guild_mgr.cpp index c63effea5..81e221b6b 100644 --- a/zone/guild_mgr.cpp +++ b/zone/guild_mgr.cpp @@ -190,53 +190,123 @@ uint8 *ZoneGuildManager::MakeGuildMembers(uint32 guild_id, const char *prefix_na } void ZoneGuildManager::ListGuilds(Client *c) const { - c->Message(Chat::White, "Listing guilds on the server:"); - char leadername[64]; - std::map::const_iterator cur, end; - cur = m_guilds.begin(); - end = m_guilds.end(); - int r = 0; - for(; cur != end; ++cur) { - leadername[0] = '\0'; - database.GetCharName(cur->second->leader_char_id, leadername); - if (leadername[0] == '\0') - c->Message(Chat::White, " Guild #%i <%s>", cur->first, cur->second->name.c_str()); - else - c->Message(Chat::White, " Guild #%i <%s> Leader: %s", cur->first, cur->second->name.c_str(), leadername); - r++; + if (m_guilds.size()) { + c->Message( + Chat::White, + fmt::format( + "Listing {} Guild{}.", + m_guilds.size(), + m_guilds.size() != 1 ? "s" : "" + ).c_str() + ); + + for (const auto& guild : m_guilds) { + auto leader_name = database.GetCharNameByID(guild.second->leader_char_id); + c->Message( + Chat::White, + fmt::format( + "Guild {} | {}Name: {}", + guild.first, + ( + !leader_name.empty() ? + fmt::format( + "Leader: {} ({}) ", + leader_name, + guild.second->leader_char_id + ) : + "" + ), + guild.second->name + ).c_str() + ); + } + } else { + c->Message(Chat::White, "There are no Guilds to list."); } - c->Message(Chat::White, "%i guilds listed.", r); } void ZoneGuildManager::DescribeGuild(Client *c, uint32 guild_id) const { std::map::const_iterator res; res = m_guilds.find(guild_id); - if(res == m_guilds.end()) { - c->Message(Chat::White, "Guild %d not found.", guild_id); + if (res == m_guilds.end()) { + c->Message( + Chat::White, + fmt::format( + "Guild ID {} could not be found.", + guild_id + ).c_str() + ); return; } const GuildInfo *info = res->second; - c->Message(Chat::White, "Guild info DB# %i <%s>", guild_id, info->name.c_str()); + auto leader_name = database.GetCharNameByID(info->leader_char_id); + std::string popup_text = ""; + popup_text += fmt::format( + "", + info->name, + guild_id + ); + popup_text += fmt::format( + "", + leader_name, + info->leader_char_id + ); + popup_text += "

"; + popup_text += ""; + popup_text += ""; + popup_text += ""; + popup_text += ""; + popup_text += ""; + popup_text += ""; + popup_text += ""; + popup_text += ""; + popup_text += ""; + popup_text += ""; + popup_text += ""; - char leadername[64]; - database.GetCharName(info->leader_char_id, leadername); - c->Message(Chat::White, "Guild Leader: %s", leadername); - - char permbuffer[256]; - uint8 i; - for (i = 0; i <= GUILD_MAX_RANK; i++) { - char *permptr = permbuffer; - uint8 r; - for(r = 0; r < _MaxGuildAction; r++) - permptr += sprintf(permptr, " %s: %c", GuildActionNames[r], info->ranks[i].permissions[r]?'Y':'N'); - - c->Message(Chat::White, "Rank %i: %s", i, info->ranks[i].name.c_str()); - c->Message(Chat::White, "Permissions: %s", permbuffer); + for (uint8 guild_rank = 0; guild_rank <= GUILD_MAX_RANK; guild_rank++) { + auto can_hear_guild_chat = info->ranks[guild_rank].permissions[GUILD_HEAR] ? "" : ""; + auto can_speak_guild_chat = info->ranks[guild_rank].permissions[GUILD_SPEAK] ? "" : ""; + auto can_invite = info->ranks[guild_rank].permissions[GUILD_INVITE] ? "" : ""; + auto can_remove = info->ranks[guild_rank].permissions[GUILD_REMOVE] ? "" : ""; + auto can_promote = info->ranks[guild_rank].permissions[GUILD_PROMOTE] ? "" : ""; + auto can_demote = info->ranks[guild_rank].permissions[GUILD_DEMOTE] ? "" : ""; + auto can_set_motd = info->ranks[guild_rank].permissions[GUILD_MOTD] ? "" : ""; + auto can_war_peace = info->ranks[guild_rank].permissions[GUILD_WARPEACE] ? "" : ""; + popup_text += fmt::format( + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "", + !info->ranks[guild_rank].name.empty() ? info->ranks[guild_rank].name : "Nameless", + guild_rank, + can_demote, + can_hear_guild_chat, + can_invite, + can_promote, + can_remove, + can_set_motd, + can_speak_guild_chat, + can_war_peace + ); } + popup_text += "
Name{}Guild ID{}
Leader{}Character ID{}
RankDemoteHear Guild ChatInvitePromoteRemoveSet MOTDSpeak Guild ChatWar/Peace
{} ({}){}{}{}{}{}{}{}{}
"; + + c->SendPopupToClient( + "Guild Information", + popup_text.c_str() + ); } //in theory, we could get a pile of unused entries in this array, but only if @@ -1513,7 +1583,17 @@ void GuildApproval::GuildApproved() database.InsertPetitionToDB(pet); petition_list.UpdateGMQueue(); petition_list.UpdateZoneListQueue(); - worldserver.SendEmoteMessage(0, 0, 80, 15, "%s has made a petition. #%i", owner->CastToClient()->GetName(), pet->GetID()); + worldserver.SendEmoteMessage( + 0, + 0, + AccountStatus::QuestTroupe, + Chat::Yellow, + fmt::format( + "{} has made a petition. ID: {}", + owner->CastToClient()->GetName(), + pet->GetID() + ).c_str() + ); auto pack = new ServerPacket; pack->opcode = ServerOP_RefreshGuild; pack->size = tmp; diff --git a/zone/hate_list.cpp b/zone/hate_list.cpp index b4b6725bb..75923c99e 100644 --- a/zone/hate_list.cpp +++ b/zone/hate_list.cpp @@ -266,7 +266,7 @@ void HateList::DoFactionHits(int32 npc_faction_level_id) { } } -int HateList::GetSummonedPetCountOnHateList(Mob *hater) { +int HateList::GetSummonedPetCountOnHateList() { //Function to get number of 'Summoned' pets on a targets hate list to allow calculations for certian spell effects. //Unclear from description that pets are required to be 'summoned body type'. Will not require at this time. @@ -645,15 +645,53 @@ bool HateList::IsHateListEmpty() { void HateList::PrintHateListToClient(Client *c) { - auto iterator = list.begin(); - while (iterator != list.end()) - { - struct_HateList *e = (*iterator); - c->Message(Chat::White, "- name: %s, damage: %d, hate: %d", - (e->entity_on_hatelist && e->entity_on_hatelist->GetName()) ? e->entity_on_hatelist->GetName() : "(null)", - e->hatelist_damage, e->stored_hate_amount); + if (list.size()) { + c->Message( + Chat::White, + fmt::format( + "Displaying hate list for {} ({}).", + hate_owner->GetCleanName(), + hate_owner->GetID() + ).c_str() + ); - ++iterator; + auto entity_number = 1; + for (const auto& hate_entity : list) { + if (hate_entity->entity_on_hatelist) { + c->Message( + Chat::White, + fmt::format( + "Hate Entity {} | Name: {} ({}) Damage: {} Hate: {}", + entity_number, + hate_entity->entity_on_hatelist->GetName(), + hate_entity->entity_on_hatelist->GetID(), + hate_entity->hatelist_damage, + hate_entity->stored_hate_amount + ).c_str() + ); + } else { + c->Message( + Chat::White, + fmt::format( + "Hate Entity {} | Damage: {} Hate: {}", + entity_number, + hate_entity->hatelist_damage, + hate_entity->stored_hate_amount + ).c_str() + ); + } + + entity_number++; + } + } else { + c->Message( + Chat::White, + fmt::format( + "{} ({}) has nothing on its hatelist.", + hate_owner->GetCleanName(), + hate_owner->GetID() + ).c_str() + ); } } @@ -797,3 +835,152 @@ std::list HateList::GetHateListByDistance(int distance) } return hate_list; } + +#ifdef BOTS +Bot* HateList::GetRandomBotOnHateList(bool skip_mezzed) +{ + int count = list.size(); + if (count <= 0) { //If we don't have any entries it'll crash getting a random 0, -1 position. + return nullptr; + } + + if (count == 1) { //No need to do all that extra work if we only have one hate entry + if (*list.begin() && (*list.begin())->entity_on_hatelist->IsBot() && (!skip_mezzed || !(*list.begin())->entity_on_hatelist->IsMezzed())) { + return (*list.begin())->entity_on_hatelist->CastToBot(); + } + return nullptr; + } + + if (skip_mezzed) { + for (auto iter : list) { + if (iter->entity_on_hatelist->IsMezzed()) { + --count; + } + } + + if (count <= 0) { + return nullptr; + } + } + + int random = zone->random.Int(0, count - 1); + int counter = 0; + + for (auto iter : list) { + if (!iter->entity_on_hatelist->IsBot()) { + continue; + } + + if (skip_mezzed && iter->entity_on_hatelist->IsMezzed()) { + continue; + } + + if (counter < random) { + ++counter; + continue; + } + + return iter->entity_on_hatelist->CastToBot(); + } + + return nullptr; +} +#endif + +Client* HateList::GetRandomClientOnHateList(bool skip_mezzed) +{ + int count = list.size(); + if (count <= 0) { //If we don't have any entries it'll crash getting a random 0, -1 position. + return nullptr; + } + + if (count == 1) { //No need to do all that extra work if we only have one hate entry + if (*list.begin() && (*list.begin())->entity_on_hatelist->IsClient() && (!skip_mezzed || !(*list.begin())->entity_on_hatelist->IsMezzed())) { + return (*list.begin())->entity_on_hatelist->CastToClient(); + } + return nullptr; + } + + if (skip_mezzed) { + for (auto iter : list) { + if (iter->entity_on_hatelist->IsMezzed()) { + --count; + } + } + + if (count <= 0) { + return nullptr; + } + } + + int random = zone->random.Int(0, count - 1); + int counter = 0; + + for (auto iter : list) { + if (!iter->entity_on_hatelist->IsClient()) { + continue; + } + + if (skip_mezzed && iter->entity_on_hatelist->IsMezzed()) { + continue; + } + + if (counter < random) { + ++counter; + continue; + } + + return iter->entity_on_hatelist->CastToClient(); + } + + return nullptr; +} + +NPC* HateList::GetRandomNPCOnHateList(bool skip_mezzed) +{ + int count = list.size(); + if (count <= 0) { //If we don't have any entries it'll crash getting a random 0, -1 position. + return nullptr; + } + + if (count == 1) { //No need to do all that extra work if we only have one hate entry + if (*list.begin() && (*list.begin())->entity_on_hatelist->IsNPC() && (!skip_mezzed || !(*list.begin())->entity_on_hatelist->IsMezzed())) { + return (*list.begin())->entity_on_hatelist->CastToNPC(); + } + return nullptr; + } + + if (skip_mezzed) { + for (auto iter : list) { + if (iter->entity_on_hatelist->IsMezzed()) { + --count; + } + } + + if (count <= 0) { + return nullptr; + } + } + + int random = zone->random.Int(0, count - 1); + int counter = 0; + + for (auto iter : list) { + if (!iter->entity_on_hatelist->IsNPC()) { + continue; + } + + if (skip_mezzed && iter->entity_on_hatelist->IsMezzed()) { + continue; + } + + if (counter < random) { + ++counter; + continue; + } + + return iter->entity_on_hatelist->CastToNPC(); + } + + return nullptr; +} diff --git a/zone/hate_list.h b/zone/hate_list.h index 71c1f31cf..0dbfae841 100644 --- a/zone/hate_list.h +++ b/zone/hate_list.h @@ -49,12 +49,18 @@ public: Mob *GetEscapingEntOnHateList(); // returns first eligble entity Mob *GetEscapingEntOnHateList(Mob *center, float range = 0.0f, bool first = false); +#ifdef BOTS + Bot* GetRandomBotOnHateList(bool skip_mezzed = false); +#endif + Client* GetRandomClientOnHateList(bool skip_mezzed = false); + NPC* GetRandomNPCOnHateList(bool skip_mezzed = false); + bool IsEntOnHateList(Mob *mob); bool IsHateListEmpty(); bool RemoveEntFromHateList(Mob *ent); int AreaRampage(Mob *caster, Mob *target, int count, ExtraAttackOptions *opts); - int GetSummonedPetCountOnHateList(Mob *hater); + int GetSummonedPetCountOnHateList(); int GetHateRatio(Mob *top, Mob *other); int32 GetEntHateAmount(Mob *ent, bool in_damage = false); diff --git a/zone/inventory.cpp b/zone/inventory.cpp index b3d3b6d2b..41e67e7b9 100644 --- a/zone/inventory.cpp +++ b/zone/inventory.cpp @@ -186,25 +186,57 @@ bool Client::SummonItem(uint32 item_id, int16 charges, uint32 aug1, uint32 aug2, // make sure the item exists if(item == nullptr) { - Message(Chat::Red, "Item %u does not exist.", item_id); - LogInventory("Player [{}] on account [{}] attempted to create an item with an invalid id.\n(Item: [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug6: [{}])\n", - GetName(), account_name, item_id, aug1, aug2, aug3, aug4, aug5, aug6); - + Message( + Chat::Red, + fmt::format( + "Item {} does not exist.", + item_id + ).c_str() + ); + LogInventory( + "Player [{}] on account [{}] attempted to create an item with an invalid id.\n" + "Item [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug6: [{}])\n", + GetName(), + account_name, + item_id, + aug1, + aug2, + aug3, + aug4, + aug5, + aug6 + ); return false; } // check that there is not a lore conflict between base item and existing inventory else if(CheckLoreConflict(item)) { // DuplicateLoreMessage(item_id); - Message(Chat::Red, "You already have a lore %s (%i) in your inventory.", item->Name, item_id); - + Message( + Chat::Red, + fmt::format( + "You already have a lore {} ({}) in your inventory.", + database.CreateItemLink(item_id), + item_id + ).c_str() + ); return false; } // check to make sure we are augmenting an augmentable item else if (((!item->IsClassCommon()) || (item->AugType > 0)) && (aug1 | aug2 | aug3 | aug4 | aug5 | aug6)) { Message(Chat::Red, "You can not augment an augment or a non-common class item."); - LogInventory("Player [{}] on account [{}] attempted to augment an augment or a non-common class item.\n(Item: [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug5: [{}])\n", - GetName(), account_name, item->ID, aug1, aug2, aug3, aug4, aug5, aug6); - + LogInventory( + "Player [{}] on account [{}] attempted to augment an augment or a non-common class item.\n" + "Item [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug5: [{}])\n", + GetName(), + account_name, + item->ID, + aug1, + aug2, + aug3, + aug4, + aug5, + aug6 + ); return false; } @@ -216,7 +248,7 @@ bool Client::SummonItem(uint32 item_id, int16 charges, uint32 aug1, uint32 aug2, /* else if(item->MinStatus && ((this->Admin() < item->MinStatus) || (this->Admin() < RuleI(GM, MinStatusToSummonItem)))) { Message(Chat::Red, "You are not a GM or do not have the status to summon this item."); - LogInventory("Player [{}] on account [{}] attempted to create a GM-only item with a status of [{}].\n(Item: [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug6: [{}], MinStatus: [{}])\n", + LogInventory("Player [{}] on account [{}] attempted to create a GM-only item with a status of [{}].\n"Item [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug6: [{}], MinStatus: [{}])\n", GetName(), account_name, this->Admin(), item->ID, aug1, aug2, aug3, aug4, aug5, aug6, item->MinStatus); return false; @@ -224,23 +256,39 @@ bool Client::SummonItem(uint32 item_id, int16 charges, uint32 aug1, uint32 aug2, */ uint32 augments[EQ::invaug::SOCKET_COUNT] = { aug1, aug2, aug3, aug4, aug5, aug6 }; - - uint32 classes = item->Classes; - uint32 races = item->Races; - uint32 slots = item->Slots; - - bool enforcewear = RuleB(Inventory, EnforceAugmentWear); - bool enforcerestr = RuleB(Inventory, EnforceAugmentRestriction); - bool enforceusable = RuleB(Inventory, EnforceAugmentUsability); - + uint32 classes = item->Classes; + uint32 races = item->Races; + uint32 slots = item->Slots; + bool enforce_wearable = RuleB(Inventory, EnforceAugmentWear); + bool enforce_restrictions = RuleB(Inventory, EnforceAugmentRestriction); + bool enforce_usable = RuleB(Inventory, EnforceAugmentUsability); for (int iter = EQ::invaug::SOCKET_BEGIN; iter <= EQ::invaug::SOCKET_END; ++iter) { + int augment_slot = iter + 1; const EQ::ItemData* augtest = database.GetItem(augments[iter]); - if(augtest == nullptr) { if(augments[iter]) { - Message(Chat::Red, "Augment %u (Aug%i) does not exist.", augments[iter], iter + 1); - LogInventory("Player [{}] on account [{}] attempted to create an augment (Aug[{}]) with an invalid id.\n(Item: [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug6: [{}])\n", - GetName(), account_name, (iter + 1), item->ID, aug1, aug2, aug3, aug4, aug5, aug6); + Message( + Chat::Red, + fmt::format( + "Augment {} in Augment Slot {} does not exist.", + augments[iter], + augment_slot + ).c_str() + ); + LogInventory( + "Player [{}] on account [{}] attempted to create an augment (Aug[{}]) with an invalid id.\n" + "Item [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug6: [{}])\n", + GetName(), + account_name, + augment_slot, + item->ID, + aug1, + aug2, + aug3, + aug4, + aug5, + aug6 + ); return false; } @@ -249,15 +297,42 @@ bool Client::SummonItem(uint32 item_id, int16 charges, uint32 aug1, uint32 aug2, // check that there is not a lore conflict between augment and existing inventory if(CheckLoreConflict(augtest)) { // DuplicateLoreMessage(augtest->ID); - Message(Chat::Red, "You already have a lore %s (%u) in your inventory.", augtest->Name, augtest->ID); + Message( + Chat::Red, + fmt::format( + "You already have a lore {} ({}) in your inventory.", + database.CreateItemLink(augtest->ID), + augtest->ID + ).c_str() + ); return false; } // check that augment is an actual augment else if(augtest->AugType == 0) { - Message(Chat::Red, "%s (%u) (Aug%i) is not an actual augment.", augtest->Name, augtest->ID, iter + 1); - LogInventory("Player [{}] on account [{}] attempted to use a non-augment item (Aug[{}]) as an augment.\n(Item: [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug6: [{}])\n", - GetName(), account_name, item->ID, (iter + 1), aug1, aug2, aug3, aug4, aug5, aug6); + Message( + Chat::Red, + fmt::format( + "{} ({}) in Augment Slot {} is not an actual augment.", + database.CreateItemLink(augtest->ID), + augtest->ID, + augment_slot + ).c_str() + ); + LogInventory( + "Player [{}] on account [{}] attempted to use a non-augment item (Augment Slot [{}]) as an augment.\n" + "Item [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug6: [{}])\n", + GetName(), + account_name, + item->ID, + augment_slot, + aug1, + aug2, + aug3, + aug4, + aug5, + aug6 + ); return false; } @@ -269,232 +344,354 @@ bool Client::SummonItem(uint32 item_id, int16 charges, uint32 aug1, uint32 aug2, else if(augtest->MinStatus && ((this->Admin() < augtest->MinStatus) || (this->Admin() < RuleI(GM, MinStatusToSummonItem)))) { Message(Chat::Red, "You are not a GM or do not have the status to summon this augment."); LogInventory("Player [{}] on account [{}] attempted to create a GM-only augment (Aug[{}]) with a status of [{}].\n(Item: [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], MinStatus: [{}])\n", - GetName(), account_name, (iter + 1), this->Admin(), item->ID, aug1, aug2, aug3, aug4, aug5, aug6, item->MinStatus); + GetName(), account_name, augment_slot, this->Admin(), item->ID, aug1, aug2, aug3, aug4, aug5, aug6, item->MinStatus); return false; } */ // check for augment type allowance - if(enforcewear) { + if(enforce_wearable) { if ((item->AugSlotType[iter] == EQ::item::AugTypeNone) || !(((uint32)1 << (item->AugSlotType[iter] - 1)) & augtest->AugType)) { - Message(Chat::Red, "Augment %u (Aug%i) is not acceptable wear on Item %u.", augments[iter], iter + 1, item->ID); - LogInventory("Player [{}] on account [{}] attempted to augment an item with an unacceptable augment type (Aug[{}]).\n(Item: [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug6: [{}])\n", - GetName(), account_name, (iter + 1), item->ID, aug1, aug2, aug3, aug4, aug5, aug6); + Message( + Chat::Red, + fmt::format( + "Augment {} ({}) in Augment Slot {} is not capable of being socketed in to {} ({}).", + database.CreateItemLink(augments[iter]), + augments[iter], + augment_slot, + database.CreateItemLink(item->ID), + item->ID + ).c_str() + ); + LogInventory( + "Player [{}] on account [{}] attempted to augment an item with an unacceptable augment type (Aug[{}]).\n" + "Item [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug6: [{}])\n", + GetName(), + account_name, + augment_slot, + item->ID, + aug1, + aug2, + aug3, + aug4, + aug5, + aug6 + ); return false; } if(item->AugSlotVisible[iter] == 0) { - Message(Chat::Red, "Item %u has not evolved enough to accept Augment %u (Aug%i).", item->ID, augments[iter], iter + 1); - LogInventory("Player [{}] on account [{}] attempted to augment an unevolved item with augment type (Aug[{}]).\n(Item: [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug6: [{}])\n", - GetName(), account_name, (iter + 1), item->ID, aug1, aug2, aug3, aug4, aug5, aug6); + Message( + Chat::Red, + fmt::format( + "{} ({}) has not evolved enough to accept {} ({}) in Augment Slot {}.", + database.CreateItemLink(item->ID), + item->ID, + database.CreateItemLink(augments[iter]), + augments[iter], + augment_slot + ).c_str() + ); + LogInventory( + "Player [{}] on account [{}] attempted to augment an unevolved item with augment type (Aug[{}]).\n" + "Item [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug6: [{}])\n", + GetName(), + account_name, + augment_slot, + item->ID, + aug1, + aug2, + aug3, + aug4, + aug5, + aug6 + ); return false; } } // check for augment to item restriction - if(enforcerestr) { - bool restrictfail = false; - uint8 it = item->ItemType; - - switch(augtest->AugRestrict) { - case EQ::item::AugRestrictionAny: - break; - case EQ::item::AugRestrictionArmor: - switch(it) { - case EQ::item::ItemTypeArmor: + if(enforce_restrictions) { + bool is_restricted = false; + uint8 item_type = item->ItemType; + switch (augtest->AugRestrict) { + case EQ::item::AugRestrictionAny: break; + case EQ::item::AugRestrictionArmor: + switch (item_type) { + case EQ::item::ItemTypeArmor: + break; + default: + is_restricted = true; + break; + } + break; + case EQ::item::AugRestrictionWeapons: + switch (item_type) { + case EQ::item::ItemType1HSlash: + case EQ::item::ItemType1HBlunt: + case EQ::item::ItemType1HPiercing: + case EQ::item::ItemTypeMartial: + case EQ::item::ItemType2HSlash: + case EQ::item::ItemType2HBlunt: + case EQ::item::ItemType2HPiercing: + case EQ::item::ItemTypeBow: + break; + default: + is_restricted = true; + break; + } + break; + case EQ::item::AugRestriction1HWeapons: + switch (item_type) { + case EQ::item::ItemType1HSlash: + case EQ::item::ItemType1HBlunt: + case EQ::item::ItemType1HPiercing: + case EQ::item::ItemTypeMartial: + break; + default: + is_restricted = true; + break; + } + break; + case EQ::item::AugRestriction2HWeapons: + switch (item_type) { + case EQ::item::ItemType2HSlash: + case EQ::item::ItemType2HBlunt: + case EQ::item::ItemType2HPiercing: + case EQ::item::ItemTypeBow: + break; + default: + is_restricted = true; + break; + } + break; + case EQ::item::AugRestriction1HSlash: + switch (item_type) { + case EQ::item::ItemType1HSlash: + break; + default: + is_restricted = true; + break; + } + break; + case EQ::item::AugRestriction1HBlunt: + switch (item_type) { + case EQ::item::ItemType1HBlunt: + break; + default: + is_restricted = true; + break; + } + break; + case EQ::item::AugRestrictionPiercing: + switch (item_type) { + case EQ::item::ItemType1HPiercing: + break; + default: + is_restricted = true; + break; + } + break; + case EQ::item::AugRestrictionHandToHand: + switch (item_type) { + case EQ::item::ItemTypeMartial: + break; + default: + is_restricted = true; + break; + } + break; + case EQ::item::AugRestriction2HSlash: + switch (item_type) { + case EQ::item::ItemType2HSlash: + break; + default: + is_restricted = true; + break; + } + break; + case EQ::item::AugRestriction2HBlunt: + switch (item_type) { + case EQ::item::ItemType2HBlunt: + break; + default: + is_restricted = true; + break; + } + break; + case EQ::item::AugRestriction2HPierce: + switch (item_type) { + case EQ::item::ItemType2HPiercing: + break; + default: + is_restricted = true; + break; + } + break; + case EQ::item::AugRestrictionBows: + switch (item_type) { + case EQ::item::ItemTypeBow: + break; + default: + is_restricted = true; + break; + } + break; + case EQ::item::AugRestrictionShields: + switch (item_type) { + case EQ::item::ItemTypeShield: + break; + default: + is_restricted = true; + break; + } + break; + case EQ::item::AugRestriction1HSlash1HBluntOrHandToHand: + switch (item_type) { + case EQ::item::ItemType1HSlash: + case EQ::item::ItemType1HBlunt: + case EQ::item::ItemTypeMartial: + break; + default: + is_restricted = true; + break; + } + break; + case EQ::item::AugRestriction1HBluntOrHandToHand: + switch (item_type) { + case EQ::item::ItemType1HBlunt: + case EQ::item::ItemTypeMartial: + break; + default: + is_restricted = true; + break; + } + break; + // These 3 are in-work + case EQ::item::AugRestrictionUnknown1: + case EQ::item::AugRestrictionUnknown2: + case EQ::item::AugRestrictionUnknown3: default: - restrictfail = true; + is_restricted = true; break; - } - break; - case EQ::item::AugRestrictionWeapons: - switch(it) { - case EQ::item::ItemType1HSlash: - case EQ::item::ItemType1HBlunt: - case EQ::item::ItemType1HPiercing: - case EQ::item::ItemTypeMartial: - case EQ::item::ItemType2HSlash: - case EQ::item::ItemType2HBlunt: - case EQ::item::ItemType2HPiercing: - case EQ::item::ItemTypeBow: - break; - default: - restrictfail = true; - break; - } - break; - case EQ::item::AugRestriction1HWeapons: - switch(it) { - case EQ::item::ItemType1HSlash: - case EQ::item::ItemType1HBlunt: - case EQ::item::ItemType1HPiercing: - case EQ::item::ItemTypeMartial: - break; - default: - restrictfail = true; - break; - } - break; - case EQ::item::AugRestriction2HWeapons: - switch(it) { - case EQ::item::ItemType2HSlash: - case EQ::item::ItemType2HBlunt: - case EQ::item::ItemType2HPiercing: - case EQ::item::ItemTypeBow: - break; - default: - restrictfail = true; - break; - } - break; - case EQ::item::AugRestriction1HSlash: - switch(it) { - case EQ::item::ItemType1HSlash: - break; - default: - restrictfail = true; - break; - } - break; - case EQ::item::AugRestriction1HBlunt: - switch(it) { - case EQ::item::ItemType1HBlunt: - break; - default: - restrictfail = true; - break; - } - break; - case EQ::item::AugRestrictionPiercing: - switch(it) { - case EQ::item::ItemType1HPiercing: - break; - default: - restrictfail = true; - break; - } - break; - case EQ::item::AugRestrictionHandToHand: - switch(it) { - case EQ::item::ItemTypeMartial: - break; - default: - restrictfail = true; - break; - } - break; - case EQ::item::AugRestriction2HSlash: - switch(it) { - case EQ::item::ItemType2HSlash: - break; - default: - restrictfail = true; - break; - } - break; - case EQ::item::AugRestriction2HBlunt: - switch(it) { - case EQ::item::ItemType2HBlunt: - break; - default: - restrictfail = true; - break; - } - break; - case EQ::item::AugRestriction2HPierce: - switch(it) { - case EQ::item::ItemType2HPiercing: - break; - default: - restrictfail = true; - break; - } - break; - case EQ::item::AugRestrictionBows: - switch(it) { - case EQ::item::ItemTypeBow: - break; - default: - restrictfail = true; - break; - } - break; - case EQ::item::AugRestrictionShields: - switch(it) { - case EQ::item::ItemTypeShield: - break; - default: - restrictfail = true; - break; - } - break; - case EQ::item::AugRestriction1HSlash1HBluntOrHandToHand: - switch(it) { - case EQ::item::ItemType1HSlash: - case EQ::item::ItemType1HBlunt: - case EQ::item::ItemTypeMartial: - break; - default: - restrictfail = true; - break; - } - break; - case EQ::item::AugRestriction1HBluntOrHandToHand: - switch(it) { - case EQ::item::ItemType1HBlunt: - case EQ::item::ItemTypeMartial: - break; - default: - restrictfail = true; - break; - } - break; - // These 3 are in-work - case EQ::item::AugRestrictionUnknown1: - case EQ::item::AugRestrictionUnknown2: - case EQ::item::AugRestrictionUnknown3: - default: - restrictfail = true; - break; } - if(restrictfail) { - Message(Chat::Red, "Augment %u (Aug%i) is restricted from wear on Item %u.", augments[iter], (iter + 1), item->ID); - LogInventory("Player [{}] on account [{}] attempted to augment an item with a restricted augment (Aug[{}]).\n(Item: [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug6: [{}])\n", - GetName(), account_name, (iter + 1), item->ID, aug1, aug2, aug3, aug4, aug5, aug6); + if(is_restricted) { + Message( + Chat::Red, + fmt::format( + "{} ({}) in Augment Slot {} is restricted from being augmented in to {} ({}).", + database.CreateItemLink(augments[iter]), + augments[iter], + augment_slot, + database.CreateItemLink(item->ID), + item->ID + ).c_str() + ); + LogInventory( + "Player [{}] on account [{}] attempted to augment an item with a restricted augment (Aug[{}]).\n" + "Item [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug6: [{}])\n", + GetName(), + account_name, + augment_slot, + item->ID, + aug1, + aug2, + aug3, + aug4, + aug5, + aug6 + ); return false; } } - if(enforceusable) { + if(enforce_usable) { // check for class usability if(item->Classes && !(classes &= augtest->Classes)) { - Message(Chat::Red, "Augment %u (Aug%i) will result in an item not usable by any class.", augments[iter], (iter + 1)); - LogInventory("Player [{}] on account [{}] attempted to create an item unusable by any class.\n(Item: [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug6: [{}])\n", - GetName(), account_name, item->ID, aug1, aug2, aug3, aug4, aug5, aug6); + Message( + Chat::Red, + fmt::format( + "{} ({}) in Augment Slot {} will result in an item unusable by any class.", + database.CreateItemLink(augments[iter]), + augments[iter], + augment_slot + ).c_str() + ); + LogInventory( + "Player [{}] on account [{}] attempted to create an item unusable by any class.\n" + "Item [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug6: [{}])\n", + GetName(), + account_name, + item->ID, + aug1, + aug2, + aug3, + aug4, + aug5, + aug6 + ); return false; } // check for race usability if(item->Races && !(races &= augtest->Races)) { - Message(Chat::Red, "Augment %u (Aug%i) will result in an item not usable by any race.", augments[iter], (iter + 1)); - LogInventory("Player [{}] on account [{}] attempted to create an item unusable by any race.\n(Item: [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug6: [{}])\n", - GetName(), account_name, item->ID, aug1, aug2, aug3, aug4, aug5, aug6); + Message( + Chat::Red, + fmt::format( + "{} ({}) in Augment Slot {} will result in an item unusable by any race.", + database.CreateItemLink(augments[iter]), + augments[iter], + augment_slot + ).c_str() + ); + LogInventory( + "Player [{}] on account [{}] attempted to create an item unusable by any race.\n" + "Item [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug6: [{}])\n", + GetName(), + account_name, + item->ID, + aug1, + aug2, + aug3, + aug4, + aug5, + aug6 + ); return false; } // check for slot usability if(item->Slots && !(slots &= augtest->Slots)) { - Message(Chat::Red, "Augment %u (Aug%i) will result in an item not usable in any slot.", augments[iter], (iter + 1)); - LogInventory("Player [{}] on account [{}] attempted to create an item unusable in any slot.\n(Item: [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug6: [{}])\n", - GetName(), account_name, item->ID, aug1, aug2, aug3, aug4, aug5, aug6); + Message( + Chat::Red, + fmt::format( + "{} ({}) in Augment Slot {} will result in an item unusable in any slot.", + database.CreateItemLink(augments[iter]), + augments[iter], + augment_slot + ).c_str() + ); + LogInventory( + "Player [{}] on account [{}] attempted to create an item unusable in any slot.\n" + "Item [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug6: [{}])\n", + GetName(), + account_name, + item->ID, + aug1, + aug2, + aug3, + aug4, + aug5, + aug6 + ); return false; } @@ -506,12 +703,11 @@ bool Client::SummonItem(uint32 item_id, int16 charges, uint32 aug1, uint32 aug2, // if the item is stackable and the charge amount is -1 or 0 then set to 1 charge. // removed && item->MaxCharges == 0 if -1 or 0 was passed max charges is irrelevant - if(charges <= 0 && item->Stackable) + if(charges <= 0 && item->Stackable) { charges = 1; - - // if the charges is -1, then no charge value was passed in set to max charges - else if(charges == -1) + } else if(charges == -1) { // if the charges is -1, then no charge value was passed in set to max charges charges = item->MaxCharges; + } // in any other situation just use charges as passed @@ -520,35 +716,67 @@ bool Client::SummonItem(uint32 item_id, int16 charges, uint32 aug1, uint32 aug2, if(inst == nullptr) { Message(Chat::Red, "An unknown server error has occurred and your item was not created."); // this goes to logfile since this is a major error - LogError("Player [{}] on account [{}] encountered an unknown item creation error.\n(Item: [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug6: [{}])\n", - GetName(), account_name, item->ID, aug1, aug2, aug3, aug4, aug5, aug6); + LogError( + "Player [{}] on account [{}] encountered an unknown item creation error.\n" + "Item [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug6: [{}])\n", + GetName(), + account_name, + item->ID, + aug1, + aug2, + aug3, + aug4, + aug5, + aug6 + ); return false; } // add any validated augments for (int iter = EQ::invaug::SOCKET_BEGIN; iter <= EQ::invaug::SOCKET_END; ++iter) { - if(augments[iter]) + if(augments[iter]) { inst->PutAugment(&database, iter, augments[iter]); + } } // attune item - if(attuned && inst->GetItem()->Attuneable) + if(attuned && inst->GetItem()->Attuneable) { inst->SetAttuned(true); + } inst->SetOrnamentIcon(ornament_icon); inst->SetOrnamentationIDFile(ornament_idfile); inst->SetOrnamentHeroModel(ornament_hero_model); // check to see if item is usable in requested slot - if (enforceusable && (to_slot >= EQ::invslot::EQUIPMENT_BEGIN && to_slot <= EQ::invslot::EQUIPMENT_END)) { + if (enforce_usable && (to_slot >= EQ::invslot::EQUIPMENT_BEGIN && to_slot <= EQ::invslot::EQUIPMENT_END)) { uint32 slottest = to_slot; - if(!(slots & ((uint32)1 << slottest))) { - Message(0, "This item is not equipable at slot %u - moving to cursor.", to_slot); - LogInventory("Player [{}] on account [{}] attempted to equip an item unusable in slot [{}] - moved to cursor.\n(Item: [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug6: [{}])\n", - GetName(), account_name, to_slot, item->ID, aug1, aug2, aug3, aug4, aug5, aug6); - + Message( + Chat::White, + fmt::format( + "{} ({}) cannot be equipped in {} ({}), moving to cursor.", + database.CreateItemLink(item->ID), + item->ID, + EQ::invslot::GetInvPossessionsSlotName(to_slot), + to_slot + ).c_str() + ); + LogInventory( + "Player [{}] on account [{}] attempted to equip an item unusable in slot [{}] - moved to cursor.\n" + "Item [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug6: [{}])\n", + GetName(), + account_name, + to_slot, + item->ID, + aug1, + aug2, + aug3, + aug4, + aug5, + aug6 + ); to_slot = EQ::invslot::slotCursor; } } @@ -557,8 +785,7 @@ bool Client::SummonItem(uint32 item_id, int16 charges, uint32 aug1, uint32 aug2, if (to_slot == EQ::invslot::slotCursor) { PushItemOnCursor(*inst); SendItemPacket(EQ::invslot::slotCursor, inst, ItemPacketLimbo); - } - else { + } else { PutItemInInventory(to_slot, *inst, true); } @@ -566,8 +793,9 @@ bool Client::SummonItem(uint32 item_id, int16 charges, uint32 aug1, uint32 aug2, // discover item and any augments if((RuleB(Character, EnableDiscoveredItems)) && !GetGM()) { - if(!IsDiscovered(item_id)) + if(!IsDiscovered(item_id)) { DiscoverItem(item_id); + } /* // Augments should have been discovered prior to being placed on an item. for (int iter = AUG_BEGIN; iter < EQ::constants::ITEM_COMMON_SIZE; ++iter) { @@ -865,7 +1093,7 @@ void Client::SendCursorBuffer() } // Remove item from inventory -void Client::DeleteItemInInventory(int16 slot_id, int8 quantity, bool client_update, bool update_db) { +void Client::DeleteItemInInventory(int16 slot_id, int16 quantity, bool client_update, bool update_db) { #if (EQDEBUG >= 5) LogDebug("DeleteItemInInventory([{}], [{}], [{}])", slot_id, quantity, (client_update) ? "true":"false"); #endif @@ -1673,7 +1901,10 @@ bool Client::SwapItem(MoveItem_Struct* move_in) { if (dstbag) dstbagid = dstbag->GetItem()->ID; } - if (srcitemid==17899 || srcbagid==17899 || dstitemid==17899 || dstbagid==17899){ + if ((srcbagid && srcbag->GetItem()->BagType == EQ::item::BagTypeTradersSatchel) || + (dstbagid && dstbag->GetItem()->BagType == EQ::item::BagTypeTradersSatchel) || + (srcitemid && src_inst->GetItem()->BagType == EQ::item::BagTypeTradersSatchel) || + (dstitemid && dst_inst->GetItem()->BagType == EQ::item::BagTypeTradersSatchel)) { this->Trader_EndTrader(); this->Message(Chat::Red,"You cannot move your Trader Satchels, or items inside them, while Trading."); } @@ -1993,9 +2224,13 @@ bool Client::SwapItem(MoveItem_Struct* move_in) { } int matslot = SlotConvert2(dst_slot_id); - if (dst_slot_id <= EQ::invslot::EQUIPMENT_END && matslot != EQ::textures::armorHead) { // think this is to allow the client to update with /showhelm + if (dst_slot_id <= EQ::invslot::EQUIPMENT_END) {// on Titanium and ROF2 /showhelm works even if sending helm slot SendWearChange(matslot); } + // This is part of a bug fix to ensure heroforge graphics display to other clients in zone. + if (queue_wearchange_slot >= 0) { + heroforge_wearchange_timer.Start(100); + } // Step 7: Save change to the database if (src_slot_id == EQ::invslot::slotCursor) { @@ -2023,6 +2258,7 @@ bool Client::SwapItem(MoveItem_Struct* move_in) { // Step 8: Re-calc stats CalcBonuses(); + ApplyWeaponsStance(); return true; } @@ -2271,67 +2507,14 @@ void Client::DyeArmorBySlot(uint8 slot, uint8 red, uint8 green, uint8 blue, uint SendWearChange(slot); } -#if 0 -bool Client::DecreaseByItemType(uint32 type, uint8 amt) { - const ItemData* TempItem = 0; - EQ::ItemInstance* ins; - int x; - for(x=EQ::legacy::POSSESSIONS_BEGIN; x <= EQ::legacy::POSSESSIONS_END; x++) - { - TempItem = 0; - ins = GetInv().GetItem(x); - if (ins) - TempItem = ins->GetItem(); - if (TempItem && TempItem->ItemType == type) - { - if (ins->GetCharges() < amt) - { - amt -= ins->GetCharges(); - DeleteItemInInventory(x,amt,true); - } - else - { - DeleteItemInInventory(x,amt,true); - amt = 0; - } - if (amt < 1) - return true; - } - } - for(x=EQ::legacy::GENERAL_BAGS_BEGIN; x <= EQ::legacy::GENERAL_BAGS_END; x++) - { - TempItem = 0; - ins = GetInv().GetItem(x); - if (ins) - TempItem = ins->GetItem(); - if (TempItem && TempItem->ItemType == type) - { - if (ins->GetCharges() < amt) - { - amt -= ins->GetCharges(); - DeleteItemInInventory(x,amt,true); - } - else - { - DeleteItemInInventory(x,amt,true); - amt = 0; - } - if (amt < 1) - return true; - } - } - return false; -} -#endif - -bool Client::DecreaseByID(uint32 type, uint8 amt) { +bool Client::DecreaseByID(uint32 type, int16 quantity) { const EQ::ItemData* TempItem = nullptr; EQ::ItemInstance* ins = nullptr; int x; int num = 0; for (x = EQ::invslot::POSSESSIONS_BEGIN; x <= EQ::invslot::POSSESSIONS_END; ++x) { - if (num >= amt) + if (num >= quantity) break; if (((uint64)1 << x) & GetInv().GetLookup()->PossessionsBitmask == 0) continue; @@ -2345,7 +2528,7 @@ bool Client::DecreaseByID(uint32 type, uint8 amt) { } for (x = EQ::invbag::GENERAL_BAGS_BEGIN; x <= EQ::invbag::GENERAL_BAGS_END; ++x) { - if (num >= amt) + if (num >= quantity) break; if ((((uint64)1 << (EQ::invslot::GENERAL_BEGIN + ((x - EQ::invbag::GENERAL_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT))) & GetInv().GetLookup()->PossessionsBitmask) == 0) continue; @@ -2359,7 +2542,7 @@ bool Client::DecreaseByID(uint32 type, uint8 amt) { } for (x = EQ::invbag::CURSOR_BAG_BEGIN; x <= EQ::invbag::CURSOR_BAG_END; ++x) { - if (num >= amt) + if (num >= quantity) break; TempItem = nullptr; @@ -2370,12 +2553,12 @@ bool Client::DecreaseByID(uint32 type, uint8 amt) { num += ins->GetCharges(); } - if (num < amt) + if (num < quantity) return false; for (x = EQ::invslot::POSSESSIONS_BEGIN; x <= EQ::invslot::POSSESSIONS_END; ++x) { - if (amt < 1) + if (quantity < 1) break; if (((uint64)1 << x) & GetInv().GetLookup()->PossessionsBitmask == 0) continue; @@ -2387,18 +2570,18 @@ bool Client::DecreaseByID(uint32 type, uint8 amt) { if (TempItem && TempItem->ID != type) continue; - if (ins->GetCharges() < amt) { - amt -= ins->GetCharges(); - DeleteItemInInventory(x, amt, true); + if (ins->GetCharges() < quantity) { + quantity -= ins->GetCharges(); + DeleteItemInInventory(x, quantity, true); } else { - DeleteItemInInventory(x, amt, true); - amt = 0; + DeleteItemInInventory(x, quantity, true); + quantity = 0; } } for (x = EQ::invbag::GENERAL_BAGS_BEGIN; x <= EQ::invbag::GENERAL_BAGS_END; ++x) { - if (amt < 1) + if (quantity < 1) break; if ((((uint64)1 << (EQ::invslot::GENERAL_BEGIN + ((x - EQ::invbag::GENERAL_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT))) & GetInv().GetLookup()->PossessionsBitmask) == 0) continue; @@ -2410,18 +2593,18 @@ bool Client::DecreaseByID(uint32 type, uint8 amt) { if (TempItem && TempItem->ID != type) continue; - if (ins->GetCharges() < amt) { - amt -= ins->GetCharges(); - DeleteItemInInventory(x, amt, true); + if (ins->GetCharges() < quantity) { + quantity -= ins->GetCharges(); + DeleteItemInInventory(x, quantity, true); } else { - DeleteItemInInventory(x, amt, true); - amt = 0; + DeleteItemInInventory(x, quantity, true); + quantity = 0; } } for (x = EQ::invbag::CURSOR_BAG_BEGIN; x <= EQ::invbag::CURSOR_BAG_END; ++x) { - if (amt < 1) + if (quantity < 1) break; TempItem = nullptr; @@ -2431,13 +2614,13 @@ bool Client::DecreaseByID(uint32 type, uint8 amt) { if (TempItem && TempItem->ID != type) continue; - if (ins->GetCharges() < amt) { - amt -= ins->GetCharges(); - DeleteItemInInventory(x, amt, true); + if (ins->GetCharges() < quantity) { + quantity -= ins->GetCharges(); + DeleteItemInInventory(x, quantity, true); } else { - DeleteItemInInventory(x, amt, true); - amt = 0; + DeleteItemInInventory(x, quantity, true); + quantity = 0; } } @@ -2518,7 +2701,7 @@ void Client::DisenchantSummonedBags(bool client_update) { for (auto slot_id = EQ::invslot::GENERAL_BEGIN; slot_id <= EQ::invslot::GENERAL_END; ++slot_id) { if (((uint64)1 << slot_id) & GetInv().GetLookup()->PossessionsBitmask == 0) - continue; // not useable this session - will be disenchanted once player logs in on client that doesn't exclude affected slots + continue; // not usable this session - will be disenchanted once player logs in on client that doesn't exclude affected slots auto inst = m_inv[slot_id]; if (!inst) { continue; } @@ -2674,7 +2857,7 @@ void Client::RemoveNoRent(bool client_update) auto inst = m_inv[slot_id]; if(inst && !inst->GetItem()->NoRent) { LogInventory("NoRent Timer Lapse: Deleting [{}] from slot [{}]", inst->GetItem()->Name, slot_id); - DeleteItemInInventory(slot_id, 0, false); // Can't delete from client Bank slots + DeleteItemInInventory(slot_id); // Can't delete from client Bank slots } } @@ -2686,7 +2869,7 @@ void Client::RemoveNoRent(bool client_update) auto inst = m_inv[slot_id]; if(inst && !inst->GetItem()->NoRent) { LogInventory("NoRent Timer Lapse: Deleting [{}] from slot [{}]", inst->GetItem()->Name, slot_id); - DeleteItemInInventory(slot_id, 0, false); // Can't delete from client Bank Container slots + DeleteItemInInventory(slot_id); // Can't delete from client Bank Container slots } } @@ -2694,7 +2877,7 @@ void Client::RemoveNoRent(bool client_update) auto inst = m_inv[slot_id]; if(inst && !inst->GetItem()->NoRent) { LogInventory("NoRent Timer Lapse: Deleting [{}] from slot [{}]", inst->GetItem()->Name, slot_id); - DeleteItemInInventory(slot_id, 0, false); // Can't delete from client Shared Bank slots + DeleteItemInInventory(slot_id); // Can't delete from client Shared Bank slots } } @@ -2702,7 +2885,7 @@ void Client::RemoveNoRent(bool client_update) auto inst = m_inv[slot_id]; if(inst && !inst->GetItem()->NoRent) { LogInventory("NoRent Timer Lapse: Deleting [{}] from slot [{}]", inst->GetItem()->Name, slot_id); - DeleteItemInInventory(slot_id, 0, false); // Can't delete from client Shared Bank Container slots + DeleteItemInInventory(slot_id); // Can't delete from client Shared Bank Container slots } } @@ -3649,3 +3832,341 @@ std::string EQ::InventoryProfile::GetCustomItemData(int16 slot_id, std::string i } return ""; } + +int EQ::InventoryProfile::GetItemStatValue(uint32 item_id, const char* identifier) { + const EQ::ItemInstance* inst = database.CreateItem(item_id); + if (!inst) + return 0; + + const EQ::ItemData* item = inst->GetItem(); + if (!item) + return 0; + + if (!identifier) + return 0; + + int32 stat = 0; + + std::string id = identifier; + for(uint32 i = 0; i < id.length(); ++i) { + id[i] = tolower(id[i]); + } + if (id == "itemclass") + stat = int32(item->ItemClass); + if (id == "id") + stat = int32(item->ID); + if (id == "idfile") + stat = atoi(&item->IDFile[2]); + if (id == "weight") + stat = int32(item->Weight); + if (id == "norent") + stat = int32(item->NoRent); + if (id == "nodrop") + stat = int32(item->NoDrop); + if (id == "size") + stat = int32(item->Size); + if (id == "slots") + stat = int32(item->Slots); + if (id == "price") + stat = int32(item->Price); + if (id == "icon") + stat = int32(item->Icon); + if (id == "loregroup") + stat = int32(item->LoreGroup); + if (id == "loreflag") + stat = int32(item->LoreFlag); + if (id == "pendingloreflag") + stat = int32(item->PendingLoreFlag); + if (id == "artifactflag") + stat = int32(item->ArtifactFlag); + if (id == "summonedflag") + stat = int32(item->SummonedFlag); + if (id == "fvnodrop") + stat = int32(item->FVNoDrop); + if (id == "favor") + stat = int32(item->Favor); + if (id == "guildfavor") + stat = int32(item->GuildFavor); + if (id == "pointtype") + stat = int32(item->PointType); + if (id == "bagtype") + stat = int32(item->BagType); + if (id == "bagslots") + stat = int32(item->BagSlots); + if (id == "bagsize") + stat = int32(item->BagSize); + if (id == "bagwr") + stat = int32(item->BagWR); + if (id == "benefitflag") + stat = int32(item->BenefitFlag); + if (id == "tradeskills") + stat = int32(item->Tradeskills); + if (id == "cr") + stat = int32(item->CR); + if (id == "dr") + stat = int32(item->DR); + if (id == "pr") + stat = int32(item->PR); + if (id == "mr") + stat = int32(item->MR); + if (id == "fr") + stat = int32(item->FR); + if (id == "astr") + stat = int32(item->AStr); + if (id == "asta") + stat = int32(item->ASta); + if (id == "aagi") + stat = int32(item->AAgi); + if (id == "adex") + stat = int32(item->ADex); + if (id == "acha") + stat = int32(item->ACha); + if (id == "aint") + stat = int32(item->AInt); + if (id == "awis") + stat = int32(item->AWis); + if (id == "hp") + stat = int32(item->HP); + if (id == "mana") + stat = int32(item->Mana); + if (id == "ac") + stat = int32(item->AC); + if (id == "deity") + stat = int32(item->Deity); + if (id == "skillmodvalue") + stat = int32(item->SkillModValue); + if (id == "skillmodtype") + stat = int32(item->SkillModType); + if (id == "banedmgrace") + stat = int32(item->BaneDmgRace); + if (id == "banedmgamt") + stat = int32(item->BaneDmgAmt); + if (id == "banedmgbody") + stat = int32(item->BaneDmgBody); + if (id == "magic") + stat = int32(item->Magic); + if (id == "casttime_") + stat = int32(item->CastTime_); + if (id == "reqlevel") + stat = int32(item->ReqLevel); + if (id == "bardtype") + stat = int32(item->BardType); + if (id == "bardvalue") + stat = int32(item->BardValue); + if (id == "light") + stat = int32(item->Light); + if (id == "delay") + stat = int32(item->Delay); + if (id == "reclevel") + stat = int32(item->RecLevel); + if (id == "recskill") + stat = int32(item->RecSkill); + if (id == "elemdmgtype") + stat = int32(item->ElemDmgType); + if (id == "elemdmgamt") + stat = int32(item->ElemDmgAmt); + if (id == "range") + stat = int32(item->Range); + if (id == "damage") + stat = int32(item->Damage); + if (id == "color") + stat = int32(item->Color); + if (id == "classes") + stat = int32(item->Classes); + if (id == "races") + stat = int32(item->Races); + if (id == "maxcharges") + stat = int32(item->MaxCharges); + if (id == "itemtype") + stat = int32(item->ItemType); + if (id == "material") + stat = int32(item->Material); + if (id == "casttime") + stat = int32(item->CastTime); + if (id == "elitematerial") + stat = int32(item->EliteMaterial); + if (id == "herosforgemodel") + stat = int32(item->HerosForgeModel); + if (id == "procrate") + stat = int32(item->ProcRate); + if (id == "combateffects") + stat = int32(item->CombatEffects); + if (id == "shielding") + stat = int32(item->Shielding); + if (id == "stunresist") + stat = int32(item->StunResist); + if (id == "strikethrough") + stat = int32(item->StrikeThrough); + if (id == "extradmgskill") + stat = int32(item->ExtraDmgSkill); + if (id == "extradmgamt") + stat = int32(item->ExtraDmgAmt); + if (id == "spellshield") + stat = int32(item->SpellShield); + if (id == "avoidance") + stat = int32(item->Avoidance); + if (id == "accuracy") + stat = int32(item->Accuracy); + if (id == "charmfileid") + stat = int32(item->CharmFileID); + if (id == "factionmod1") + stat = int32(item->FactionMod1); + if (id == "factionmod2") + stat = int32(item->FactionMod2); + if (id == "factionmod3") + stat = int32(item->FactionMod3); + if (id == "factionmod4") + stat = int32(item->FactionMod4); + if (id == "factionamt1") + stat = int32(item->FactionAmt1); + if (id == "factionamt2") + stat = int32(item->FactionAmt2); + if (id == "factionamt3") + stat = int32(item->FactionAmt3); + if (id == "factionamt4") + stat = int32(item->FactionAmt4); + if (id == "augtype") + stat = int32(item->AugType); + if (id == "ldontheme") + stat = int32(item->LDoNTheme); + if (id == "ldonprice") + stat = int32(item->LDoNPrice); + if (id == "ldonsold") + stat = int32(item->LDoNSold); + if (id == "banedmgraceamt") + stat = int32(item->BaneDmgRaceAmt); + if (id == "augrestrict") + stat = int32(item->AugRestrict); + if (id == "endur") + stat = int32(item->Endur); + if (id == "dotshielding") + stat = int32(item->DotShielding); + if (id == "attack") + stat = int32(item->Attack); + if (id == "regen") + stat = int32(item->Regen); + if (id == "manaregen") + stat = int32(item->ManaRegen); + if (id == "enduranceregen") + stat = int32(item->EnduranceRegen); + if (id == "haste") + stat = int32(item->Haste); + if (id == "damageshield") + stat = int32(item->DamageShield); + if (id == "recastdelay") + stat = int32(item->RecastDelay); + if (id == "recasttype") + stat = int32(item->RecastType); + if (id == "augdistiller") + stat = int32(item->AugDistiller); + if (id == "attuneable") + stat = int32(item->Attuneable); + if (id == "nopet") + stat = int32(item->NoPet); + if (id == "potionbelt") + stat = int32(item->PotionBelt); + if (id == "stackable") + stat = int32(item->Stackable); + if (id == "notransfer") + stat = int32(item->NoTransfer); + if (id == "questitemflag") + stat = int32(item->QuestItemFlag); + if (id == "stacksize") + stat = int32(item->StackSize); + if (id == "potionbeltslots") + stat = int32(item->PotionBeltSlots); + if (id == "book") + stat = int32(item->Book); + if (id == "booktype") + stat = int32(item->BookType); + if (id == "svcorruption") + stat = int32(item->SVCorruption); + if (id == "purity") + stat = int32(item->Purity); + if (id == "backstabdmg") + stat = int32(item->BackstabDmg); + if (id == "dsmitigation") + stat = int32(item->DSMitigation); + if (id == "heroicstr") + stat = int32(item->HeroicStr); + if (id == "heroicint") + stat = int32(item->HeroicInt); + if (id == "heroicwis") + stat = int32(item->HeroicWis); + if (id == "heroicagi") + stat = int32(item->HeroicAgi); + if (id == "heroicdex") + stat = int32(item->HeroicDex); + if (id == "heroicsta") + stat = int32(item->HeroicSta); + if (id == "heroiccha") + stat = int32(item->HeroicCha); + if (id == "heroicmr") + stat = int32(item->HeroicMR); + if (id == "heroicfr") + stat = int32(item->HeroicFR); + if (id == "heroiccr") + stat = int32(item->HeroicCR); + if (id == "heroicdr") + stat = int32(item->HeroicDR); + if (id == "heroicpr") + stat = int32(item->HeroicPR); + if (id == "heroicsvcorrup") + stat = int32(item->HeroicSVCorrup); + if (id == "healamt") + stat = int32(item->HealAmt); + if (id == "spelldmg") + stat = int32(item->SpellDmg); + if (id == "ldonsellbackrate") + stat = int32(item->LDoNSellBackRate); + if (id == "scriptfileid") + stat = int32(item->ScriptFileID); + if (id == "expendablearrow") + stat = int32(item->ExpendableArrow); + if (id == "clairvoyance") + stat = int32(item->Clairvoyance); + // Begin Effects + if (id == "clickeffect") + stat = int32(item->Click.Effect); + if (id == "clicktype") + stat = int32(item->Click.Type); + if (id == "clicklevel") + stat = int32(item->Click.Level); + if (id == "clicklevel2") + stat = int32(item->Click.Level2); + if (id == "proceffect") + stat = int32(item->Proc.Effect); + if (id == "proctype") + stat = int32(item->Proc.Type); + if (id == "proclevel") + stat = int32(item->Proc.Level); + if (id == "proclevel2") + stat = int32(item->Proc.Level2); + if (id == "worneffect") + stat = int32(item->Worn.Effect); + if (id == "worntype") + stat = int32(item->Worn.Type); + if (id == "wornlevel") + stat = int32(item->Worn.Level); + if (id == "wornlevel2") + stat = int32(item->Worn.Level2); + if (id == "focuseffect") + stat = int32(item->Focus.Effect); + if (id == "focustype") + stat = int32(item->Focus.Type); + if (id == "focuslevel") + stat = int32(item->Focus.Level); + if (id == "focuslevel2") + stat = int32(item->Focus.Level2); + if (id == "scrolleffect") + stat = int32(item->Scroll.Effect); + if (id == "scrolltype") + stat = int32(item->Scroll.Type); + if (id == "scrolllevel") + stat = int32(item->Scroll.Level); + if (id == "scrolllevel2") + stat = int32(item->Scroll.Level2); + + safe_delete(inst); + return stat; +} diff --git a/zone/loottables.cpp b/zone/loottables.cpp index b4e0f00f0..5c3259502 100644 --- a/zone/loottables.cpp +++ b/zone/loottables.cpp @@ -128,12 +128,13 @@ void ZoneDatabase::AddLootDropToNPC(NPC *npc, uint32 lootdrop_id, ItemList *item return; } + // if this lootdrop is droplimit=0 and mindrop 0, scan list once and return if (droplimit == 0 && mindrop == 0) { for (uint32 i = 0; i < loot_drop->NumEntries; ++i) { int charges = loot_drop->Entries[i].multiplier; for (int j = 0; j < charges; ++j) { if (zone->random.Real(0.0, 100.0) <= loot_drop->Entries[i].chance && - npc->MeetsLootDropLevelRequirements(loot_drop->Entries[i])) { + npc->MeetsLootDropLevelRequirements(loot_drop->Entries[i], true)) { const EQ::ItemData *database_item = GetItem(loot_drop->Entries[i].item_id); npc->AddLootDrop( database_item, @@ -155,29 +156,34 @@ void ZoneDatabase::AddLootDropToNPC(NPC *npc, uint32 lootdrop_id, ItemList *item } float roll_t = 0.0f; - float roll_t_min = 0.0f; bool active_item_list = false; for (uint32 i = 0; i < loot_drop->NumEntries; ++i) { const EQ::ItemData *db_item = GetItem(loot_drop->Entries[i].item_id); - if (db_item) { + if (db_item && npc->MeetsLootDropLevelRequirements(loot_drop->Entries[i])) { roll_t += loot_drop->Entries[i].chance; active_item_list = true; } } - roll_t_min = roll_t; - roll_t = EQ::ClampLower(roll_t, 100.0f); - if (!active_item_list) { return; } + // This will pick one item per iteration until mindrop. + // Don't let the compare against chance fool you. + // The roll isn't 0-100, its 0-total and it picks the item, we're just + // looping to find the lucky item, descremening otherwise. This is ok, + // items with chance 60 are 6 times more likely than items chance 10. for (int i = 0; i < mindrop; ++i) { - float roll = (float) zone->random.Real(0.0, roll_t_min); + float roll = (float) zone->random.Real(0.0, roll_t); for (uint32 j = 0; j < loot_drop->NumEntries; ++j) { const EQ::ItemData *db_item = GetItem(loot_drop->Entries[j].item_id); if (db_item) { - if (roll < loot_drop->Entries[j].chance && npc->MeetsLootDropLevelRequirements(loot_drop->Entries[j])) { + // if it doesn't meet the requirements do nothing + if (!npc->MeetsLootDropLevelRequirements(loot_drop->Entries[j])) + continue; + + if (roll < loot_drop->Entries[j].chance) { npc->AddLootDrop( db_item, item_list, @@ -208,41 +214,63 @@ void ZoneDatabase::AddLootDropToNPC(NPC *npc, uint32 lootdrop_id, ItemList *item } } - for (int i = mindrop; i < droplimit; ++i) { - float roll = (float) zone->random.Real(0.0, roll_t); - for (uint32 j = 0; j < loot_drop->NumEntries; ++j) { - const EQ::ItemData *db_item = GetItem(loot_drop->Entries[j].item_id); - if (db_item) { - if (roll < loot_drop->Entries[j].chance && npc->MeetsLootDropLevelRequirements(loot_drop->Entries[j])) { - npc->AddLootDrop( - db_item, - item_list, - loot_drop->Entries[j] - ); + // Now that mindrop has been established see if we get any more based + // on item odds. Pick a random entry to start at so no one entry is favored. + // Look at all items until we hit droplimit or look at all items. - int charges = (int) loot_drop->Entries[i].multiplier; - charges = EQ::ClampLower(charges, 1); + if (droplimit <= mindrop) { + return; + } - for (int k = 1; k < charges; ++k) { - float c_roll = (float) zone->random.Real(0.0, 100.0); - if (c_roll <= loot_drop->Entries[i].chance) { - npc->AddLootDrop( - db_item, - item_list, - loot_drop->Entries[i] - ); - } + int start_index = zone->random.Int(0,loot_drop->NumEntries-1); + int j = start_index; + int dropped = mindrop; + + do { + + LogLootDetail("DropLimit Starting at [{}] out of [{}]", j, + loot_drop->NumEntries); + + const EQ::ItemData *db_item = GetItem(loot_drop->Entries[j].item_id); + if (db_item && + npc->MeetsLootDropLevelRequirements(loot_drop->Entries[j])) { + + float iroll = (float) zone->random.Real(0.0, 100.0); + + LogLootDetail("Rolled [{}] Needed [{}]", iroll, loot_drop->Entries[j].chance); + // If this item succeeds the chance roll + if (iroll < loot_drop->Entries[j].chance) { + + ++dropped; + LogLootDetail("Dropping item [{}]", loot_drop->Entries[j].item_id); + npc->AddLootDrop( + db_item, + item_list, + loot_drop->Entries[j] + ); + + int charges = (int) loot_drop->Entries[j].multiplier; + charges = EQ::ClampLower(charges, 1); + + for (int k = 1; k < charges; ++k) { + float c_roll = (float) zone->random.Real(0.0, 100.0); + if (c_roll <= loot_drop->Entries[j].chance) { + npc->AddLootDrop( + db_item, + item_list, + loot_drop->Entries[j] + ); } - - j = loot_drop->NumEntries; - break; - } - else { - roll -= loot_drop->Entries[j].chance; } } } - } // We either ran out of items or reached our limit. + + // Wrap the loop back to catch the start of loop + if (++j >= loot_drop->NumEntries) { + j = 0; + } + + } while (dropped < droplimit && j != start_index); npc->UpdateEquipmentLight(); // no wearchange associated with this function..so, this should not be needed @@ -250,27 +278,31 @@ void ZoneDatabase::AddLootDropToNPC(NPC *npc, uint32 lootdrop_id, ItemList *item // npc->SendAppearancePacket(AT_Light, npc->GetActiveLightValue()); } -bool NPC::MeetsLootDropLevelRequirements(LootDropEntries_Struct loot_drop) +bool NPC::MeetsLootDropLevelRequirements(LootDropEntries_Struct loot_drop, bool verbose) { if (loot_drop.npc_min_level > 0 && GetLevel() < loot_drop.npc_min_level) { - LogLootDetail( - "NPC [{}] does not meet loot_drop level requirements (min_level) level [{}] current [{}] for item [{}]", - GetCleanName(), - loot_drop.npc_min_level, - GetLevel(), - database.CreateItemLink(loot_drop.item_id) - ); + if (verbose) { + LogLootDetail( + "NPC [{}] does not meet loot_drop level requirements (min_level) level [{}] current [{}] for item [{}]", + GetCleanName(), + loot_drop.npc_min_level, + GetLevel(), + database.CreateItemLink(loot_drop.item_id) + ); + } return false; } if (loot_drop.npc_max_level > 0 && GetLevel() > loot_drop.npc_max_level) { - LogLootDetail( - "NPC [{}] does not meet loot_drop level requirements (max_level) level [{}] current [{}] for item [{}]", - GetCleanName(), - loot_drop.npc_max_level, - GetLevel(), - database.CreateItemLink(loot_drop.item_id) - ); + if (verbose) { + LogLootDetail( + "NPC [{}] does not meet loot_drop level requirements (max_level) level [{}] current [{}] for item [{}]", + GetCleanName(), + loot_drop.npc_max_level, + GetLevel(), + database.CreateItemLink(loot_drop.item_id) + ); + } return false; } diff --git a/zone/lua_bot.cpp b/zone/lua_bot.cpp new file mode 100644 index 000000000..69ddc1187 --- /dev/null +++ b/zone/lua_bot.cpp @@ -0,0 +1,15 @@ +#ifdef BOTS +#ifdef LUA_EQEMU + +#include "lua.hpp" +#include + +#include "bot.h" +#include "lua_bot.h" + +luabind::scope lua_register_bot() { + return luabind::class_("Bot").def(luabind::constructor<>()); +} + +#endif +#endif \ No newline at end of file diff --git a/zone/lua_bot.h b/zone/lua_bot.h new file mode 100644 index 000000000..fffb844ea --- /dev/null +++ b/zone/lua_bot.h @@ -0,0 +1,32 @@ +#ifdef BOTS +#ifndef EQEMU_LUA_BOT_H +#define EQEMU_LUA_BOT_H +#ifdef LUA_EQEMU + +#include "lua_mob.h" + +class Bot; +class Lua_Bot; + +namespace luabind { + struct scope; +} + +luabind::scope lua_register_bot(); + +class Lua_Bot : public Lua_Mob +{ + typedef Bot NativeType; +public: + Lua_Bot() { SetLuaPtrData(nullptr); } + Lua_Bot(Bot *d) { SetLuaPtrData(reinterpret_cast(d)); } + virtual ~Lua_Bot() { } + + operator Bot*() { + return reinterpret_cast(GetLuaPtrData()); + } +}; + +#endif +#endif +#endif \ No newline at end of file diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index c963feec0..8da35c6fa 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -15,6 +15,7 @@ #include "lua_group.h" #include "lua_raid.h" #include "lua_packet.h" +#include "dialogue_window.h" #include "../common/expedition_lockout_timer.h" struct InventoryWhere { }; @@ -94,6 +95,11 @@ void Lua_Client::Duck() { self->Duck(); } +void Lua_Client::Sit() { + Lua_Safe_Call_Void(); + self->Sit(); +} + void Lua_Client::DyeArmorBySlot(uint8 slot, uint8 red, uint8 green, uint8 blue) { Lua_Safe_Call_Void(); self->DyeArmorBySlot(slot, red, green, blue); @@ -169,11 +175,6 @@ int Lua_Client::GetLanguageSkill(int skill_id) { return self->GetLanguageSkill(skill_id); } -const char *Lua_Client::GetLastName() { - Lua_Safe_Call_String(); - return self->GetLastName(); -} - int Lua_Client::GetLDoNPointsTheme(int theme) { Lua_Safe_Call_Int(); return self->GetLDoNPointsTheme(theme); @@ -239,9 +240,9 @@ uint32 Lua_Client::GetTotalSecondsPlayed() { return self->GetTotalSecondsPlayed(); } -void Lua_Client::UpdateLDoNPoints(int points, uint32 theme) { +void Lua_Client::UpdateLDoNPoints(uint32 theme_id, int points) { Lua_Safe_Call_Void(); - self->UpdateLDoNPoints(points, theme); + self->UpdateLDoNPoints(theme_id, points); } void Lua_Client::SetDeity(int v) { @@ -304,6 +305,11 @@ void Lua_Client::SetBindPoint(int to_zone, int to_instance, float new_x, float n self->SetBindPoint(0, to_zone, to_instance, glm::vec3(new_x, new_y, new_z)); } +void Lua_Client::SetBindPoint(int to_zone, int to_instance, float new_x, float new_y, float new_z, float new_heading) { + Lua_Safe_Call_Void(); + self->SetBindPoint2(0, to_zone, to_instance, glm::vec4(new_x, new_y, new_z, new_heading)); +} + float Lua_Client::GetBindX() { Lua_Safe_Call_Real(); return self->GetBindX(); @@ -644,6 +650,16 @@ uint16 Lua_Client::FindMemmedSpellBySlot(int slot) { return self->FindMemmedSpellBySlot(slot); } +int Lua_Client::FindMemmedSpellBySpellID(uint16 spell_id) { + Lua_Safe_Call_Int(); + return self->FindMemmedSpellBySpellID(spell_id); +} + +int Lua_Client::FindEmptyMemSlot() { + Lua_Safe_Call_Int(); + return self->FindEmptyMemSlot(); +} + int Lua_Client::MemmedCount() { Lua_Safe_Call_Int(); return self->MemmedCount(); @@ -1819,6 +1835,11 @@ int Lua_Client::GetClientMaxLevel() { return self->GetClientMaxLevel(); } +void Lua_Client::DialogueWindow(std::string markdown) { + Lua_Safe_Call_Void(); + DialogueWindow::Render(self, std::move(markdown)); +} + DynamicZoneLocation GetDynamicZoneLocationFromTable(const luabind::object& lua_table) { DynamicZoneLocation zone_location; @@ -1859,6 +1880,7 @@ Lua_Expedition Lua_Client::CreateExpedition(luabind::object expedition_table) { // luabind will catch thrown cast_failed exceptions for invalid/missing args luabind::object instance_info = expedition_table["instance"]; + luabind::object expedition_info = expedition_table["expedition"]; luabind::object zone = instance_info["zone"]; uint32_t zone_id = 0; @@ -1875,6 +1897,9 @@ Lua_Expedition Lua_Client::CreateExpedition(luabind::object expedition_table) { uint32_t zone_duration = luabind::object_cast(instance_info["duration"]); DynamicZone dz{ zone_id, zone_version, zone_duration, DynamicZoneType::Expedition }; + dz.SetName(luabind::object_cast(expedition_info["name"])); + dz.SetMinPlayers(luabind::object_cast(expedition_info["min_players"])); + dz.SetMaxPlayers(luabind::object_cast(expedition_info["max_players"])); // the dz_info table supports optional hash entries for 'compass', 'safereturn', and 'zonein' data if (luabind::type(expedition_table["compass"]) == LUA_TTABLE) @@ -1895,21 +1920,13 @@ Lua_Expedition Lua_Client::CreateExpedition(luabind::object expedition_table) { dz.SetZoneInLocation(zonein_loc); } - luabind::object expedition_info = expedition_table["expedition"]; - - std::string expedition_name = luabind::object_cast(expedition_info["name"]); - uint32_t min_players = luabind::object_cast(expedition_info["min_players"]); - uint32_t max_players = luabind::object_cast(expedition_info["max_players"]); - bool disable_messages = false; - + bool disable_messages = false; if (luabind::type(expedition_info["disable_messages"]) == LUA_TBOOLEAN) { disable_messages = luabind::object_cast(expedition_info["disable_messages"]); } - ExpeditionRequest request{ expedition_name, min_players, max_players, disable_messages }; - - return self->CreateExpedition(dz, request); + return self->CreateExpedition(dz, disable_messages); } Lua_Expedition Lua_Client::CreateExpedition(std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, uint32 min_players, uint32 max_players) { @@ -2048,6 +2065,55 @@ void Lua_Client::MovePCDynamicZone(std::string zone_name, int zone_version, bool return self->MovePCDynamicZone(zone_name, zone_version, msg_if_invalid); } +void Lua_Client::CreateTaskDynamicZone(int task_id, luabind::object dz_table) { + Lua_Safe_Call_Void(); + + if (luabind::type(dz_table) != LUA_TTABLE) + { + return; + } + + // luabind will catch thrown cast_failed exceptions for invalid/missing args + luabind::object instance_info = dz_table["instance"]; + luabind::object zone = instance_info["zone"]; + + uint32_t zone_id = 0; + if (luabind::type(zone) == LUA_TSTRING) + { + zone_id = ZoneID(luabind::object_cast(zone)); + } + else if (luabind::type(zone) == LUA_TNUMBER) + { + zone_id = luabind::object_cast(zone); + } + + uint32_t zone_version = luabind::object_cast(instance_info["version"]); + + // tasks override dz duration so duration is ignored here + DynamicZone dz{ zone_id, zone_version, 0, DynamicZoneType::None }; + + // the dz_info table supports optional hash entries for 'compass', 'safereturn', and 'zonein' data + if (luabind::type(dz_table["compass"]) == LUA_TTABLE) + { + auto compass_loc = GetDynamicZoneLocationFromTable(dz_table["compass"]); + dz.SetCompass(compass_loc); + } + + if (luabind::type(dz_table["safereturn"]) == LUA_TTABLE) + { + auto safereturn_loc = GetDynamicZoneLocationFromTable(dz_table["safereturn"]); + dz.SetSafeReturn(safereturn_loc); + } + + if (luabind::type(dz_table["zonein"]) == LUA_TTABLE) + { + auto zonein_loc = GetDynamicZoneLocationFromTable(dz_table["zonein"]); + dz.SetZoneInLocation(zonein_loc); + } + + self->CreateTaskDynamicZone(task_id, dz); +} + void Lua_Client::Fling(float value, float target_x, float target_y, float target_z) { Lua_Safe_Call_Void(); self->Fling(value, target_x, target_y, target_z); @@ -2063,355 +2129,605 @@ void Lua_Client::Fling(float value, float target_x, float target_y, float target self->Fling(value, target_x, target_y, target_z, ignore_los, clipping); } +double Lua_Client::GetAAEXPModifier(uint32 zone_id) { + Lua_Safe_Call_Real(); + return self->GetAAEXPModifier(zone_id); +} + +double Lua_Client::GetEXPModifier(uint32 zone_id) { + Lua_Safe_Call_Real(); + return self->GetEXPModifier(zone_id); +} + +void Lua_Client::SetAAEXPModifier(uint32 zone_id, double aa_modifier) { + Lua_Safe_Call_Void(); + self->SetAAEXPModifier(zone_id, aa_modifier); +} + +void Lua_Client::SetEXPModifier(uint32 zone_id, double exp_modifier) { + Lua_Safe_Call_Void(); + self->SetEXPModifier(zone_id, exp_modifier); +} + +void Lua_Client::AddLDoNLoss(uint32 theme_id) { + Lua_Safe_Call_Void(); + self->UpdateLDoNWinLoss(theme_id); +} + +void Lua_Client::AddLDoNWin(uint32 theme_id) { + Lua_Safe_Call_Void(); + self->UpdateLDoNWinLoss(theme_id, true); +} + +void Lua_Client::SetHideMe(bool hide_me_state) { + Lua_Safe_Call_Void(); + self->SetHideMe(hide_me_state); +} + +void Lua_Client::Popup(const char* title, const char* text) { + Lua_Safe_Call_Void(); + self->SendFullPopup(title, text); +} + +void Lua_Client::Popup(const char* title, const char* text, uint32 popup_id) { + Lua_Safe_Call_Void(); + self->SendFullPopup(title, text, popup_id); +} + +void Lua_Client::Popup(const char* title, const char* text, uint32 popup_id, uint32 negative_id) { + Lua_Safe_Call_Void(); + self->SendFullPopup(title, text, popup_id, negative_id); +} + +void Lua_Client::Popup(const char* title, const char* text, uint32 popup_id, uint32 negative_id, uint32 button_type) { + Lua_Safe_Call_Void(); + self->SendFullPopup(title, text, popup_id, negative_id, button_type); +} + +void Lua_Client::Popup(const char* title, const char* text, uint32 popup_id, uint32 negative_id, uint32 button_type, uint32 duration) { + Lua_Safe_Call_Void(); + self->SendFullPopup(title, text, popup_id, negative_id, button_type, duration); +} + +void Lua_Client::Popup(const char* title, const char* text, uint32 popup_id, uint32 negative_id, uint32 button_type, uint32 duration, const char* button_name_one, const char* button_name_two) { + Lua_Safe_Call_Void(); + self->SendFullPopup(title, text, popup_id, negative_id, button_type, duration, button_name_one, button_name_two); +} + +void Lua_Client::Popup(const char* title, const char* text, uint32 popup_id, uint32 negative_id, uint32 button_type, uint32 duration, const char* button_name_one, const char* button_name_two, uint32 sound_controls) { + Lua_Safe_Call_Void(); + self->SendFullPopup(title, text, popup_id, negative_id, button_type, duration, button_name_one, button_name_two, sound_controls); +} + +void Lua_Client::ResetAllDisciplineTimers() { + Lua_Safe_Call_Void(); + self->ResetAllDisciplineTimers(); +} + +void Lua_Client::SendToInstance(std::string instance_type, std::string zone_short_name, uint32 instance_version, float x, float y, float z, float heading, std::string instance_identifier, uint32 duration) { + Lua_Safe_Call_Void(); + self->SendToInstance(instance_type, zone_short_name, instance_version, x, y, z, heading, instance_identifier, duration); +} + +int Lua_Client::CountItem(uint32 item_id) { + Lua_Safe_Call_Int(); + return self->CountItem(item_id); +} + +void Lua_Client::RemoveItem(uint32 item_id) { + Lua_Safe_Call_Void(); + self->RemoveItem(item_id); +} + +void Lua_Client::RemoveItem(uint32 item_id, uint32 quantity) { + Lua_Safe_Call_Void(); + self->RemoveItem(item_id, quantity); +} + +void Lua_Client::SetGMStatus(uint32 newStatus) { + Lua_Safe_Call_Void(); + self->SetGMStatus(newStatus); +} + +void Lua_Client::UntrainDiscBySpellID(uint16 spell_id) { + Lua_Safe_Call_Void(); + self->UntrainDiscBySpellID(spell_id); +} + +void Lua_Client::UntrainDiscBySpellID(uint16 spell_id, bool update_client) { + Lua_Safe_Call_Void(); + self->UntrainDiscBySpellID(spell_id, update_client); +} + +int Lua_Client::GetIPExemption() { + Lua_Safe_Call_Int(); + return self->GetIPExemption(); +} + +std::string Lua_Client::GetIPString() { + Lua_Safe_Call_String(); + return self->GetIPString(); +} + +void Lua_Client::SetIPExemption(int exemption_amount) { + Lua_Safe_Call_Void(); + self->SetIPExemption(exemption_amount); +} + +void Lua_Client::ReadBookByName(std::string book_name, uint8 book_type) { + Lua_Safe_Call_Void(); + self->ReadBookByName(book_name, book_type); +} + +void Lua_Client::SummonBaggedItems(uint32 bag_item_id, luabind::adl::object bag_items_table) { + Lua_Safe_Call_Void(); + if (luabind::type(bag_items_table) != LUA_TTABLE) { + return; + } + + std::vector bagged_items; + + luabind::raw_iterator end; // raw_iterator uses lua_rawget + for (luabind::raw_iterator it(bag_items_table); it != end; ++it) + { + // verify array element is a table for item details + if (luabind::type(*it) == LUA_TTABLE) + { + // no need to try/catch, quest lua parser already catches exceptions + ServerLootItem_Struct item{}; + item.item_id = luabind::object_cast((*it)["item_id"]); + item.charges = luabind::object_cast((*it)["charges"]); + item.attuned = luabind::type((*it)["attuned"]) != LUA_TNIL ? luabind::object_cast((*it)["attuned"]) : 0; + item.aug_1 = luabind::type((*it)["augment_one"]) != LUA_TNIL ? luabind::object_cast((*it)["augment_one"]) : 0; + item.aug_2 = luabind::type((*it)["augment_two"]) != LUA_TNIL ? luabind::object_cast((*it)["augment_two"]) : 0; + item.aug_3 = luabind::type((*it)["augment_three"]) != LUA_TNIL ? luabind::object_cast((*it)["augment_three"]) : 0; + item.aug_4 = luabind::type((*it)["augment_four"]) != LUA_TNIL ? luabind::object_cast((*it)["augment_four"]) : 0; + item.aug_5 = luabind::type((*it)["augment_five"]) != LUA_TNIL ? luabind::object_cast((*it)["augment_five"]) : 0; + item.aug_6 = luabind::type((*it)["augment_six"]) != LUA_TNIL ? luabind::object_cast((*it)["augment_six"]) : 0; + bagged_items.emplace_back(item); + } + } + + self->SummonBaggedItems(bag_item_id, bagged_items); +} + +void Lua_Client::RemoveLDoNLoss(uint32 theme_id) { + Lua_Safe_Call_Void(); + self->UpdateLDoNWinLoss(theme_id, false, true); +} + +void Lua_Client::RemoveLDoNWin(uint32 theme_id) { + Lua_Safe_Call_Void(); + self->UpdateLDoNWinLoss(theme_id, true, true); +} + +uint16 Lua_Client::ScribeSpells(uint8 min_level, uint8 max_level) { + Lua_Safe_Call_Int(); + return self->ScribeSpells(min_level, max_level); +} + +uint16 Lua_Client::LearnDisciplines(uint8 min_level, uint8 max_level) { + Lua_Safe_Call_Int(); + return self->LearnDisciplines(min_level, max_level); +} + +int Lua_Client::GetNextAvailableDisciplineSlot() { + Lua_Safe_Call_Int(); + return self->GetNextAvailableDisciplineSlot(); +} + +int Lua_Client::GetNextAvailableDisciplineSlot(int starting_slot) { + Lua_Safe_Call_Int(); + return self->GetNextAvailableDisciplineSlot(starting_slot); +} + +void Lua_Client::ResetCastbarCooldownBySlot(int slot) { + Lua_Safe_Call_Void(); + self->ResetCastbarCooldownBySlot(slot); +} + +void Lua_Client::ResetAllCastbarCooldowns() { + Lua_Safe_Call_Void(); + self->ResetAllCastbarCooldowns(); +} + +void Lua_Client::ResetCastbarCooldownBySpellID(uint32 spell_id) { + Lua_Safe_Call_Void(); + self->ResetCastbarCooldownBySpellID(spell_id); +} + luabind::scope lua_register_client() { return luabind::class_("Client") - .def(luabind::constructor<>()) - .def("SendSound", (void(Lua_Client::*)(void))&Lua_Client::SendSound) - .def("Save", (void(Lua_Client::*)(void))&Lua_Client::Save) - .def("Save", (void(Lua_Client::*)(int))&Lua_Client::Save) - .def("SaveBackup", (void(Lua_Client::*)(void))&Lua_Client::SaveBackup) - .def("Connected", (bool(Lua_Client::*)(void))&Lua_Client::Connected) - .def("InZone", (bool(Lua_Client::*)(void))&Lua_Client::InZone) - .def("Kick", (void(Lua_Client::*)(void))&Lua_Client::Kick) - .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("GetAFK", (int(Lua_Client::*)(void))&Lua_Client::GetAFK) - .def("SetAFK", (void(Lua_Client::*)(uint8))&Lua_Client::SetAFK) - .def("GetAnon", (int(Lua_Client::*)(void))&Lua_Client::GetAnon) - .def("SetAnon", (void(Lua_Client::*)(uint8))&Lua_Client::SetAnon) - .def("Duck", (void(Lua_Client::*)(void))&Lua_Client::Duck) - .def("DyeArmorBySlot", (void(Lua_Client::*)(uint8,uint8,uint8,uint8))&Lua_Client::DyeArmorBySlot) - .def("DyeArmorBySlot", (void(Lua_Client::*)(uint8,uint8,uint8,uint8,uint8))&Lua_Client::DyeArmorBySlot) - .def("Stand", (void(Lua_Client::*)(void))&Lua_Client::Stand) - .def("SetGM", (void(Lua_Client::*)(bool))&Lua_Client::SetGM) - .def("SetPVP", (void(Lua_Client::*)(bool))&Lua_Client::SetPVP) - .def("GetPVP", (bool(Lua_Client::*)(void))&Lua_Client::GetPVP) - .def("GetGM", (bool(Lua_Client::*)(void))&Lua_Client::GetGM) - .def("SetBaseClass", (void(Lua_Client::*)(int))&Lua_Client::SetBaseClass) - .def("SetBaseRace", (void(Lua_Client::*)(int))&Lua_Client::SetBaseRace) - .def("SetBaseGender", (void(Lua_Client::*)(int))&Lua_Client::SetBaseGender) - .def("GetClassBitmask", (int(Lua_Client::*)(void))&Lua_Client::GetClassBitmask) - .def("GetRaceBitmask", (int(Lua_Client::*)(void))&Lua_Client::GetRaceBitmask) - .def("GetBaseFace", (int(Lua_Client::*)(void))&Lua_Client::GetBaseFace) - .def("GetLanguageSkill", (int(Lua_Client::*)(int))&Lua_Client::GetLanguageSkill) - .def("GetLastName", (const char *(Lua_Client::*)(void))&Lua_Client::GetLastName) - .def("GetLDoNPointsTheme", (int(Lua_Client::*)(int))&Lua_Client::GetLDoNPointsTheme) - .def("GetBaseSTR", (int(Lua_Client::*)(void))&Lua_Client::GetBaseSTR) - .def("GetBaseSTA", (int(Lua_Client::*)(void))&Lua_Client::GetBaseSTA) - .def("GetBaseCHA", (int(Lua_Client::*)(void))&Lua_Client::GetBaseCHA) - .def("GetBaseDEX", (int(Lua_Client::*)(void))&Lua_Client::GetBaseDEX) - .def("GetBaseINT", (int(Lua_Client::*)(void))&Lua_Client::GetBaseINT) - .def("GetBaseAGI", (int(Lua_Client::*)(void))&Lua_Client::GetBaseAGI) - .def("GetBaseWIS", (int(Lua_Client::*)(void))&Lua_Client::GetBaseWIS) - .def("GetWeight", (int(Lua_Client::*)(void))&Lua_Client::GetWeight) - .def("GetEXP", (uint32(Lua_Client::*)(void))&Lua_Client::GetEXP) - .def("GetAAExp", (uint32(Lua_Client::*)(void))&Lua_Client::GetAAExp) - .def("GetAAPercent", (uint32(Lua_Client::*)(void))&Lua_Client::GetAAPercent) - .def("GetTotalSecondsPlayed", (uint32(Lua_Client::*)(void))&Lua_Client::GetTotalSecondsPlayed) - .def("UpdateLDoNPoints", (void(Lua_Client::*)(int,uint32))&Lua_Client::UpdateLDoNPoints) - .def("SetDeity", (void(Lua_Client::*)(int))&Lua_Client::SetDeity) - .def("AddEXP", (void(Lua_Client::*)(uint32))&Lua_Client::AddEXP) - .def("AddEXP", (void(Lua_Client::*)(uint32,int))&Lua_Client::AddEXP) - .def("AddEXP", (void(Lua_Client::*)(uint32,int,bool))&Lua_Client::AddEXP) - .def("SetEXP", (void(Lua_Client::*)(uint32,uint32))&Lua_Client::SetEXP) - .def("SetEXP", (void(Lua_Client::*)(uint32,uint32,bool))&Lua_Client::SetEXP) - .def("SetBindPoint", (void(Lua_Client::*)(void))&Lua_Client::SetBindPoint) - .def("SetBindPoint", (void(Lua_Client::*)(int))&Lua_Client::SetBindPoint) - .def("SetBindPoint", (void(Lua_Client::*)(int,int))&Lua_Client::SetBindPoint) - .def("SetBindPoint", (void(Lua_Client::*)(int,int,float))&Lua_Client::SetBindPoint) - .def("SetBindPoint", (void(Lua_Client::*)(int,int,float,float))&Lua_Client::SetBindPoint) - .def("SetBindPoint", (void(Lua_Client::*)(int,int,float,float, float))&Lua_Client::SetBindPoint) - .def("GetBindX", (float(Lua_Client::*)(void))&Lua_Client::GetBindX) - .def("GetBindX", (float(Lua_Client::*)(int))&Lua_Client::GetBindX) - .def("GetBindY", (float(Lua_Client::*)(void))&Lua_Client::GetBindY) - .def("GetBindY", (float(Lua_Client::*)(int))&Lua_Client::GetBindY) - .def("GetBindZ", (float(Lua_Client::*)(void))&Lua_Client::GetBindZ) - .def("GetBindZ", (float(Lua_Client::*)(int))&Lua_Client::GetBindZ) - .def("GetBindHeading", (float(Lua_Client::*)(void))&Lua_Client::GetBindHeading) - .def("GetBindHeading", (float(Lua_Client::*)(int))&Lua_Client::GetBindHeading) - .def("GetBindZoneID", (uint32(Lua_Client::*)(void))&Lua_Client::GetBindZoneID) - .def("GetBindZoneID", (uint32(Lua_Client::*)(int))&Lua_Client::GetBindZoneID) - .def("GetTargetRingX", (float(Lua_Client::*)(void))&Lua_Client::GetTargetRingX) - .def("GetTargetRingY", (float(Lua_Client::*)(void))&Lua_Client::GetTargetRingY) - .def("GetTargetRingZ", (float(Lua_Client::*)(void))&Lua_Client::GetTargetRingZ) - .def("SetPrimaryWeaponOrnamentation", (void(Lua_Client::*)(uint32))&Lua_Client::SetPrimaryWeaponOrnamentation) - .def("SetSecondaryWeaponOrnamentation", (void(Lua_Client::*)(uint32))&Lua_Client::SetSecondaryWeaponOrnamentation) - .def("MovePC", (void(Lua_Client::*)(int,float,float,float,float))&Lua_Client::MovePC) - .def("MovePCInstance", (void(Lua_Client::*)(int,int,float,float,float,float))&Lua_Client::MovePCInstance) - .def("MoveZone", (void(Lua_Client::*)(const char*))&Lua_Client::MoveZone) - .def("MoveZoneGroup", (void(Lua_Client::*)(const char*))&Lua_Client::MoveZoneGroup) - .def("MoveZoneRaid", (void(Lua_Client::*)(const char*))&Lua_Client::MoveZoneRaid) - .def("MoveZoneInstance", (void(Lua_Client::*)(uint16))&Lua_Client::MoveZoneInstance) - .def("MoveZoneInstanceGroup", (void(Lua_Client::*)(uint16))&Lua_Client::MoveZoneInstanceGroup) - .def("MoveZoneInstanceRaid", (void(Lua_Client::*)(uint16))&Lua_Client::MoveZoneInstanceRaid) - .def("ChangeLastName", (void(Lua_Client::*)(const char *in))&Lua_Client::ChangeLastName) - .def("GetFactionLevel", (int(Lua_Client::*)(uint32,uint32,uint32,uint32,uint32,uint32,Lua_NPC))&Lua_Client::GetFactionLevel) - .def("SetFactionLevel", (void(Lua_Client::*)(uint32,uint32,int,int,int))&Lua_Client::SetFactionLevel) - .def("SetFactionLevel2", (void(Lua_Client::*)(uint32,int,int,int,int,int,int))&Lua_Client::SetFactionLevel2) - .def("GetRawItemAC", (int(Lua_Client::*)(void))&Lua_Client::GetRawItemAC) - .def("AccountID", (uint32(Lua_Client::*)(void))&Lua_Client::AccountID) - .def("AccountName", (const char *(Lua_Client::*)(void))&Lua_Client::AccountName) - .def("GetAccountAge", (int(Lua_Client::*)(void))&Lua_Client::GetAccountAge) - .def("Admin", (int(Lua_Client::*)(void))&Lua_Client::Admin) - .def("CharacterID", (uint32(Lua_Client::*)(void))&Lua_Client::CharacterID) - .def("GuildRank", (int(Lua_Client::*)(void))&Lua_Client::GuildRank) - .def("GuildID", (uint32(Lua_Client::*)(void))&Lua_Client::GuildID) - .def("GetFace", (int(Lua_Client::*)(void))&Lua_Client::GetFace) - .def("TakeMoneyFromPP", (bool(Lua_Client::*)(uint64))&Lua_Client::TakeMoneyFromPP) - .def("TakeMoneyFromPP", (bool(Lua_Client::*)(uint64,bool))&Lua_Client::TakeMoneyFromPP) - .def("AddMoneyToPP", (void(Lua_Client::*)(uint32,uint32,uint32,uint32,bool))&Lua_Client::AddMoneyToPP) - .def("TGB", (bool(Lua_Client::*)(void))&Lua_Client::TGB) - .def("GetSkillPoints", (int(Lua_Client::*)(void))&Lua_Client::GetSkillPoints) - .def("SetSkillPoints", (void(Lua_Client::*)(int))&Lua_Client::SetSkillPoints) - .def("IncreaseSkill", (void(Lua_Client::*)(int))&Lua_Client::IncreaseSkill) - .def("IncreaseSkill", (void(Lua_Client::*)(int,int))&Lua_Client::IncreaseSkill) - .def("IncreaseLanguageSkill", (void(Lua_Client::*)(int))&Lua_Client::IncreaseLanguageSkill) - .def("IncreaseLanguageSkill", (void(Lua_Client::*)(int,int))&Lua_Client::IncreaseLanguageSkill) - .def("GetRawSkill", (int(Lua_Client::*)(int))&Lua_Client::GetRawSkill) - .def("HasSkill", (bool(Lua_Client::*)(int))&Lua_Client::HasSkill) - .def("CanHaveSkill", (bool(Lua_Client::*)(int))&Lua_Client::CanHaveSkill) - .def("SetSkill", (void(Lua_Client::*)(int,int))&Lua_Client::SetSkill) - .def("AddSkill", (void(Lua_Client::*)(int,int))&Lua_Client::AddSkill) - .def("CheckSpecializeIncrease", (void(Lua_Client::*)(int))&Lua_Client::CheckSpecializeIncrease) - .def("CheckIncreaseSkill", (void(Lua_Client::*)(int,Lua_Mob))&Lua_Client::CheckIncreaseSkill) - .def("CheckIncreaseSkill", (void(Lua_Client::*)(int,Lua_Mob,int))&Lua_Client::CheckIncreaseSkill) - .def("SetLanguageSkill", (void(Lua_Client::*)(int,int))&Lua_Client::SetLanguageSkill) - .def("MaxSkill", (int(Lua_Client::*)(int))&Lua_Client::MaxSkill) - .def("IsMedding", (bool(Lua_Client::*)(void))&Lua_Client::IsMedding) - .def("GetDuelTarget", (int(Lua_Client::*)(void))&Lua_Client::GetDuelTarget) - .def("IsDueling", (bool(Lua_Client::*)(void))&Lua_Client::IsDueling) - .def("SetDuelTarget", (void(Lua_Client::*)(int))&Lua_Client::SetDuelTarget) - .def("SetDueling", (void(Lua_Client::*)(bool))&Lua_Client::SetDueling) - .def("ResetAA", (void(Lua_Client::*)(void))&Lua_Client::ResetAA) - .def("MemSpell", (void(Lua_Client::*)(int,int))&Lua_Client::MemSpell) - .def("MemSpell", (void(Lua_Client::*)(int,int,bool))&Lua_Client::MemSpell) - .def("UnmemSpell", (void(Lua_Client::*)(int))&Lua_Client::UnmemSpell) - .def("UnmemSpell", (void(Lua_Client::*)(int,bool))&Lua_Client::UnmemSpell) - .def("UnmemSpellBySpellID", (void(Lua_Client::*)(int32))&Lua_Client::UnmemSpellBySpellID) - .def("UnmemSpellAll", (void(Lua_Client::*)(void))&Lua_Client::UnmemSpellAll) - .def("UnmemSpellAll", (void(Lua_Client::*)(bool))&Lua_Client::UnmemSpellAll) - .def("FindMemmedSpellBySlot", (uint16(Lua_Client::*)(int))&Lua_Client::FindMemmedSpellBySlot) - .def("MemmedCount", (int(Lua_Client::*)(void))&Lua_Client::MemmedCount) - .def("GetLearnableDisciplines", (luabind::object(Lua_Client::*)(lua_State* L))&Lua_Client::GetLearnableDisciplines) - .def("GetLearnableDisciplines", (luabind::object(Lua_Client::*)(lua_State* L,uint8))&Lua_Client::GetLearnableDisciplines) - .def("GetLearnableDisciplines", (luabind::object(Lua_Client::*)(lua_State* L,uint8,uint8))&Lua_Client::GetLearnableDisciplines) - .def("GetLearnedDisciplines", (luabind::object(Lua_Client::*)(lua_State* L))&Lua_Client::GetLearnedDisciplines) - .def("GetMemmedSpells", (luabind::object(Lua_Client::*)(lua_State* L))&Lua_Client::GetMemmedSpells) - .def("GetScribedSpells", (luabind::object(Lua_Client::*)(lua_State* L))&Lua_Client::GetScribedSpells) - .def("GetScribeableSpells", (luabind::object(Lua_Client::*)(lua_State* L))&Lua_Client::GetScribeableSpells) - .def("GetScribeableSpells", (luabind::object(Lua_Client::*)(lua_State* L,uint8))&Lua_Client::GetScribeableSpells) - .def("GetScribeableSpells", (luabind::object(Lua_Client::*)(lua_State* L,uint8,uint8))&Lua_Client::GetScribeableSpells) - .def("ScribeSpell", (void(Lua_Client::*)(int,int))&Lua_Client::ScribeSpell) - .def("ScribeSpell", (void(Lua_Client::*)(int,int,bool))&Lua_Client::ScribeSpell) - .def("UnscribeSpell", (void(Lua_Client::*)(int))&Lua_Client::UnscribeSpell) - .def("UnscribeSpell", (void(Lua_Client::*)(int,bool))&Lua_Client::UnscribeSpell) - .def("UnscribeSpellAll", (void(Lua_Client::*)(void))&Lua_Client::UnscribeSpellAll) - .def("UnscribeSpellAll", (void(Lua_Client::*)(bool))&Lua_Client::UnscribeSpellAll) - .def("TrainDisc", (void(Lua_Client::*)(int))&Lua_Client::TrainDisc) - .def("TrainDiscBySpellID", (void(Lua_Client::*)(int32))&Lua_Client::TrainDiscBySpellID) - .def("GetDiscSlotBySpellID", (int(Lua_Client::*)(int32))&Lua_Client::GetDiscSlotBySpellID) - .def("UntrainDisc", (void(Lua_Client::*)(int))&Lua_Client::UntrainDisc) - .def("UntrainDisc", (void(Lua_Client::*)(int,bool))&Lua_Client::UntrainDisc) - .def("UntrainDiscAll", (void(Lua_Client::*)(void))&Lua_Client::UntrainDiscAll) - .def("UntrainDiscAll", (void(Lua_Client::*)(bool))&Lua_Client::UntrainDiscAll) - .def("IsStanding", (bool(Lua_Client::*)(void))&Lua_Client::IsStanding) - .def("IsSitting", (bool(Lua_Client::*)(void))&Lua_Client::IsSitting) - .def("IsCrouching", (bool(Lua_Client::*)(void))&Lua_Client::IsCrouching) - .def("SetFeigned", (void(Lua_Client::*)(bool))&Lua_Client::SetFeigned) - .def("GetFeigned", (bool(Lua_Client::*)(void))&Lua_Client::GetFeigned) - .def("AutoSplitEnabled", (bool(Lua_Client::*)(void))&Lua_Client::AutoSplitEnabled) - .def("SetHorseId", (void(Lua_Client::*)(int))&Lua_Client::SetHorseId) - .def("GetHorseId", (int(Lua_Client::*)(void))&Lua_Client::GetHorseId) - .def("NukeItem", (void(Lua_Client::*)(uint32))&Lua_Client::NukeItem) - .def("NukeItem", (void(Lua_Client::*)(uint32,int))&Lua_Client::NukeItem) - .def("SetTint", (void(Lua_Client::*)(int,uint32))&Lua_Client::SetTint) - .def("SetMaterial", (void(Lua_Client::*)(int,uint32))&Lua_Client::SetMaterial) - .def("Undye", (void(Lua_Client::*)(void))&Lua_Client::Undye) - .def("GetItemIDAt", (int(Lua_Client::*)(int))&Lua_Client::GetItemIDAt) - .def("GetAugmentIDAt", (int(Lua_Client::*)(int,int))&Lua_Client::GetAugmentIDAt) - .def("DeleteItemInInventory", (void(Lua_Client::*)(int,int))&Lua_Client::DeleteItemInInventory) - .def("DeleteItemInInventory", (void(Lua_Client::*)(int,int,bool))&Lua_Client::DeleteItemInInventory) - .def("SummonItem", (void(Lua_Client::*)(uint32))&Lua_Client::SummonItem) - .def("SummonItem", (void(Lua_Client::*)(uint32,int))&Lua_Client::SummonItem) - .def("SummonItem", (void(Lua_Client::*)(uint32,int,uint32))&Lua_Client::SummonItem) - .def("SummonItem", (void(Lua_Client::*)(uint32,int,uint32,uint32))&Lua_Client::SummonItem) - .def("SummonItem", (void(Lua_Client::*)(uint32,int,uint32,uint32,uint32))&Lua_Client::SummonItem) - .def("SummonItem", (void(Lua_Client::*)(uint32,int,uint32,uint32,uint32,uint32))&Lua_Client::SummonItem) - .def("SummonItem", (void(Lua_Client::*)(uint32,int,uint32,uint32,uint32,uint32,uint32))&Lua_Client::SummonItem) - .def("SummonItem", (void(Lua_Client::*)(uint32,int,uint32,uint32,uint32,uint32,uint32,bool))&Lua_Client::SummonItem) - .def("SummonItem", (void(Lua_Client::*)(uint32,int,uint32,uint32,uint32,uint32,uint32,bool,int))&Lua_Client::SummonItem) - .def("SetStats", (void(Lua_Client::*)(int,int))&Lua_Client::SetStats) - .def("IncStats", (void(Lua_Client::*)(int,int))&Lua_Client::IncStats) - .def("DropItem", (void(Lua_Client::*)(int))&Lua_Client::DropItem) - .def("BreakInvis", (void(Lua_Client::*)(void))&Lua_Client::BreakInvis) - .def("LeaveGroup", (void(Lua_Client::*)(void))&Lua_Client::LeaveGroup) - .def("IsGrouped", (bool(Lua_Client::*)(void))&Lua_Client::IsGrouped) - .def("IsRaidGrouped", (bool(Lua_Client::*)(void))&Lua_Client::IsRaidGrouped) - .def("Hungry", (bool(Lua_Client::*)(void))&Lua_Client::Hungry) - .def("Thirsty", (bool(Lua_Client::*)(void))&Lua_Client::Thirsty) - .def("GetInstrumentMod", (int(Lua_Client::*)(int))&Lua_Client::GetInstrumentMod) - .def("DecreaseByID", (bool(Lua_Client::*)(uint32,int))&Lua_Client::DecreaseByID) - .def("Escape", (void(Lua_Client::*)(void))&Lua_Client::Escape) - .def("GoFish", (void(Lua_Client::*)(void))&Lua_Client::GoFish) - .def("ForageItem", (void(Lua_Client::*)(void))&Lua_Client::ForageItem) - .def("ForageItem", (void(Lua_Client::*)(bool))&Lua_Client::ForageItem) - .def("CalcPriceMod", (float(Lua_Client::*)(Lua_Mob,bool))&Lua_Client::CalcPriceMod) - .def("ResetTrade", (void(Lua_Client::*)(void))&Lua_Client::ResetTrade) - .def("GetDisciplineTimer", (uint32(Lua_Client::*)(uint32))&Lua_Client::GetDisciplineTimer) - .def("ResetDisciplineTimer", (void(Lua_Client::*)(uint32))&Lua_Client::ResetDisciplineTimer) - .def("UseDiscipline", (bool(Lua_Client::*)(int,int))&Lua_Client::UseDiscipline) - .def("HasDisciplineLearned", (bool(Lua_Client::*)(uint16))&Lua_Client::HasDisciplineLearned) - .def("GetCharacterFactionLevel", (int(Lua_Client::*)(int))&Lua_Client::GetCharacterFactionLevel) - .def("SetZoneFlag", (void(Lua_Client::*)(int))&Lua_Client::SetZoneFlag) - .def("ClearZoneFlag", (void(Lua_Client::*)(int))&Lua_Client::ClearZoneFlag) - .def("HasZoneFlag", (bool(Lua_Client::*)(int))&Lua_Client::HasZoneFlag) - .def("SendZoneFlagInfo", (void(Lua_Client::*)(Lua_Client))&Lua_Client::SendZoneFlagInfo) - .def("SetAATitle", (void(Lua_Client::*)(const char *))&Lua_Client::SetAATitle) - .def("GetClientVersion", (int(Lua_Client::*)(void))&Lua_Client::GetClientVersion) - .def("GetClientVersionBit", (uint32(Lua_Client::*)(void))&Lua_Client::GetClientVersionBit) - .def("SetTitleSuffix", (void(Lua_Client::*)(const char *))&Lua_Client::SetTitleSuffix) - .def("SetAAPoints", (void(Lua_Client::*)(int))&Lua_Client::SetAAPoints) - .def("GetAAPoints", (int(Lua_Client::*)(void))&Lua_Client::GetAAPoints) - .def("GetSpentAA", (int(Lua_Client::*)(void))&Lua_Client::GetSpentAA) - .def("AddAAPoints", (void(Lua_Client::*)(int))&Lua_Client::AddAAPoints) - .def("RefundAA", (void(Lua_Client::*)(void))&Lua_Client::RefundAA) - .def("GetModCharacterFactionLevel", (int(Lua_Client::*)(int))&Lua_Client::GetModCharacterFactionLevel) - .def("GetLDoNWins", (int(Lua_Client::*)(void))&Lua_Client::GetLDoNWins) - .def("GetLDoNLosses", (int(Lua_Client::*)(void))&Lua_Client::GetLDoNLosses) - .def("GetLDoNWinsTheme", (int(Lua_Client::*)(int))&Lua_Client::GetLDoNWinsTheme) - .def("GetLDoNLossesTheme", (int(Lua_Client::*)(int))&Lua_Client::GetLDoNLossesTheme) - .def("GetStartZone", (int(Lua_Client::*)(void))&Lua_Client::GetStartZone) - .def("SetStartZone", (void(Lua_Client::*)(int))&Lua_Client::SetStartZone) - .def("SetStartZone", (void(Lua_Client::*)(int,float))&Lua_Client::SetStartZone) - .def("SetStartZone", (void(Lua_Client::*)(int,float,float))&Lua_Client::SetStartZone) - .def("SetStartZone", (void(Lua_Client::*)(int,float,float,float))&Lua_Client::SetStartZone) - .def("KeyRingAdd", (void(Lua_Client::*)(uint32))&Lua_Client::KeyRingAdd) - .def("KeyRingCheck", (bool(Lua_Client::*)(uint32))&Lua_Client::KeyRingCheck) - .def("AddPVPPoints", (void(Lua_Client::*)(uint32))&Lua_Client::AddPVPPoints) - .def("AddCrystals", (void(Lua_Client::*)(uint32,uint32))&Lua_Client::AddCrystals) - .def("SetEbonCrystals", (void(Lua_Client::*)(uint32))&Lua_Client::SetEbonCrystals) - .def("SetRadiantCrystals", (void(Lua_Client::*)(uint32))&Lua_Client::SetRadiantCrystals) - .def("GetPVPPoints", (uint32(Lua_Client::*)(void))&Lua_Client::GetPVPPoints) - .def("GetRadiantCrystals", (uint32(Lua_Client::*)(void))&Lua_Client::GetRadiantCrystals) - .def("GetEbonCrystals", (uint32(Lua_Client::*)(void))&Lua_Client::GetEbonCrystals) - .def("QuestReadBook", (void(Lua_Client::*)(const char *,int))&Lua_Client::QuestReadBook) - .def("UpdateGroupAAs", (void(Lua_Client::*)(int,uint32))&Lua_Client::UpdateGroupAAs) - .def("GetGroupPoints", (uint32(Lua_Client::*)(void))&Lua_Client::GetGroupPoints) - .def("GetRaidPoints", (uint32(Lua_Client::*)(void))&Lua_Client::GetRaidPoints) - .def("LearnRecipe", (void(Lua_Client::*)(uint32))&Lua_Client::LearnRecipe) - .def("GetEndurance", (int(Lua_Client::*)(void))&Lua_Client::GetEndurance) - .def("GetMaxEndurance", (int(Lua_Client::*)(void))&Lua_Client::GetMaxEndurance) - .def("GetEndurancePercent", (int(Lua_Client::*)(void))&Lua_Client::GetEndurancePercent) - .def("SetEndurance", (void(Lua_Client::*)(int))&Lua_Client::SetEndurance) - .def("SendOPTranslocateConfirm", (void(Lua_Client::*)(Lua_Mob,int))&Lua_Client::SendOPTranslocateConfirm) - .def("GetIP", (uint32(Lua_Client::*)(void))&Lua_Client::GetIP) - .def("AddLevelBasedExp", (void(Lua_Client::*)(int))&Lua_Client::AddLevelBasedExp) - .def("AddLevelBasedExp", (void(Lua_Client::*)(int,int))&Lua_Client::AddLevelBasedExp) - .def("AddLevelBasedExp", (void(Lua_Client::*)(int,int,bool))&Lua_Client::AddLevelBasedExp) - .def("IncrementAA", (void(Lua_Client::*)(int))&Lua_Client::IncrementAA) - .def("GrantAlternateAdvancementAbility", (bool(Lua_Client::*)(int, int))&Lua_Client::GrantAlternateAdvancementAbility) - .def("GrantAlternateAdvancementAbility", (bool(Lua_Client::*)(int, int, bool))&Lua_Client::GrantAlternateAdvancementAbility) - .def("MarkSingleCompassLoc", (void(Lua_Client::*)(float,float,float))&Lua_Client::MarkSingleCompassLoc) - .def("MarkSingleCompassLoc", (void(Lua_Client::*)(float,float,float,int))&Lua_Client::MarkSingleCompassLoc) - .def("ClearCompassMark",(void(Lua_Client::*)(void))&Lua_Client::ClearCompassMark) - .def("GetNextAvailableSpellBookSlot", (int(Lua_Client::*)(void))&Lua_Client::GetNextAvailableSpellBookSlot) - .def("GetNextAvailableSpellBookSlot", (int(Lua_Client::*)(int))&Lua_Client::GetNextAvailableSpellBookSlot) - .def("GetSpellIDByBookSlot", (uint32(Lua_Client::*)(int))& Lua_Client::GetSpellIDByBookSlot) - .def("FindSpellBookSlotBySpellID", (int(Lua_Client::*)(int))&Lua_Client::FindSpellBookSlotBySpellID) - .def("UpdateTaskActivity", (void(Lua_Client::*)(int,int,int))&Lua_Client::UpdateTaskActivity) - .def("AssignTask", (void(Lua_Client::*)(int,int))&Lua_Client::AssignTask) - .def("AssignTask", (void(Lua_Client::*)(int,int,bool))&Lua_Client::AssignTask) - .def("FailTask", (void(Lua_Client::*)(int))&Lua_Client::FailTask) - .def("IsTaskCompleted", (bool(Lua_Client::*)(int))&Lua_Client::IsTaskCompleted) - .def("IsTaskActive", (bool(Lua_Client::*)(int))&Lua_Client::IsTaskActive) - .def("IsTaskActivityActive", (bool(Lua_Client::*)(int,int))&Lua_Client::IsTaskActivityActive) - .def("GetCorpseCount", (int(Lua_Client::*)(void))&Lua_Client::GetCorpseCount) - .def("GetCorpseID", (int(Lua_Client::*)(int))&Lua_Client::GetCorpseID) - .def("GetCorpseItemAt", (int(Lua_Client::*)(int,int))&Lua_Client::GetCorpseItemAt) - .def("AssignToInstance", (void(Lua_Client::*)(int))&Lua_Client::AssignToInstance) - .def("Freeze", (void(Lua_Client::*)(void))&Lua_Client::Freeze) - .def("UnFreeze", (void(Lua_Client::*)(void))&Lua_Client::UnFreeze) - .def("GetAggroCount", (int(Lua_Client::*)(void))&Lua_Client::GetAggroCount) - .def("GetCarriedMoney", (uint64(Lua_Client::*)(void))&Lua_Client::GetCarriedMoney) - .def("GetAllMoney", (uint64(Lua_Client::*)(void))&Lua_Client::GetAllMoney) - .def("GetMoney", (uint32(Lua_Client::*)(uint8, uint8))&Lua_Client::GetMoney) - .def("OpenLFGuildWindow", (void(Lua_Client::*)(void))&Lua_Client::OpenLFGuildWindow) - .def("NotifyNewTitlesAvailable", (void(Lua_Client::*)(void))&Lua_Client::NotifyNewTitlesAvailable) - .def("Signal", (void(Lua_Client::*)(uint32))&Lua_Client::Signal) - .def("AddAlternateCurrencyValue", (void(Lua_Client::*)(uint32,int))&Lua_Client::AddAlternateCurrencyValue) - .def("SetAlternateCurrencyValue", (void(Lua_Client::*)(uint32,int))&Lua_Client::SetAlternateCurrencyValue) - .def("GetAlternateCurrencyValue", (int(Lua_Client::*)(uint32))&Lua_Client::GetAlternateCurrencyValue) - .def("SendWebLink", (void(Lua_Client::*)(const char *))&Lua_Client::SendWebLink) - .def("HasSpellScribed", (bool(Lua_Client::*)(int))&Lua_Client::HasSpellScribed) - .def("SetAccountFlag", (void(Lua_Client::*)(std::string,std::string))&Lua_Client::SetAccountFlag) - .def("GetAccountFlag", (std::string(Lua_Client::*)(std::string))&Lua_Client::GetAccountFlag) - .def("GetGroup", (Lua_Group(Lua_Client::*)(void))&Lua_Client::GetGroup) - .def("GetRaid", (Lua_Raid(Lua_Client::*)(void))&Lua_Client::GetRaid) - .def("PutItemInInventory", (bool(Lua_Client::*)(int,Lua_ItemInst))&Lua_Client::PutItemInInventory) - .def("PushItemOnCursor", (bool(Lua_Client::*)(Lua_ItemInst))&Lua_Client::PushItemOnCursor) - .def("GetInventory", (Lua_Inventory(Lua_Client::*)(void))&Lua_Client::GetInventory) - .def("SendItemScale", (void(Lua_Client::*)(Lua_ItemInst))&Lua_Client::SendItemScale) - .def("QueuePacket", (void(Lua_Client::*)(Lua_Packet))&Lua_Client::QueuePacket) - .def("QueuePacket", (void(Lua_Client::*)(Lua_Packet,bool))&Lua_Client::QueuePacket) - .def("QueuePacket", (void(Lua_Client::*)(Lua_Packet,bool,int))&Lua_Client::QueuePacket) - .def("QueuePacket", (void(Lua_Client::*)(Lua_Packet,bool,int,int))&Lua_Client::QueuePacket) - .def("GetHunger", (int(Lua_Client::*)(void))&Lua_Client::GetHunger) - .def("GetThirst", (int(Lua_Client::*)(void))&Lua_Client::GetThirst) - .def("SetHunger", (void(Lua_Client::*)(int))&Lua_Client::SetHunger) - .def("SetThirst", (void(Lua_Client::*)(int))&Lua_Client::SetThirst) - .def("SetConsumption", (void(Lua_Client::*)(int, int))&Lua_Client::SetConsumption) - .def("SendMarqueeMessage", (void(Lua_Client::*)(uint32, uint32, uint32, uint32, uint32, std::string))&Lua_Client::SendMarqueeMessage) - .def("SendColoredText", (void(Lua_Client::*)(uint32, std::string))&Lua_Client::SendColoredText) - .def("PlayMP3", (void(Lua_Client::*)(std::string))&Lua_Client::PlayMP3) - .def("QuestReward", (void(Lua_Client::*)(Lua_Mob))&Lua_Client::QuestReward) - .def("QuestReward", (void(Lua_Client::*)(Lua_Mob, uint32))&Lua_Client::QuestReward) - .def("QuestReward", (void(Lua_Client::*)(Lua_Mob, uint32, uint32))&Lua_Client::QuestReward) - .def("QuestReward", (void(Lua_Client::*)(Lua_Mob, uint32, uint32, uint32))&Lua_Client::QuestReward) - .def("QuestReward", (void(Lua_Client::*)(Lua_Mob, uint32, uint32, uint32, uint32))&Lua_Client::QuestReward) - .def("QuestReward", (void(Lua_Client::*)(Lua_Mob, uint32, uint32, uint32, uint32, uint32))&Lua_Client::QuestReward) - .def("QuestReward", (void(Lua_Client::*)(Lua_Mob, uint32, uint32, uint32, uint32, uint32, uint32))&Lua_Client::QuestReward) - .def("QuestReward", (void(Lua_Client::*)(Lua_Mob, uint32, uint32, uint32, uint32, uint32, uint32, bool))&Lua_Client::QuestReward) - .def("QuestReward", (void(Lua_Client::*)(Lua_Mob, luabind::adl::object))&Lua_Client::QuestReward) - .def("IsDead", &Lua_Client::IsDead) - .def("CalcCurrentWeight", &Lua_Client::CalcCurrentWeight) - .def("CalcATK", &Lua_Client::CalcATK) - .def("FilteredMessage", &Lua_Client::FilteredMessage) - .def("EnableAreaHPRegen", &Lua_Client::EnableAreaHPRegen) - .def("DisableAreaHPRegen", &Lua_Client::DisableAreaHPRegen) - .def("EnableAreaManaRegen", &Lua_Client::EnableAreaManaRegen) - .def("DisableAreaManaRegen", &Lua_Client::DisableAreaManaRegen) - .def("EnableAreaEndRegen", &Lua_Client::EnableAreaEndRegen) - .def("DisableAreaEndRegen", &Lua_Client::DisableAreaEndRegen) - .def("EnableAreaRegens", &Lua_Client::EnableAreaRegens) - .def("DisableAreaRegens", &Lua_Client::DisableAreaRegens) - .def("SetClientMaxLevel", (void(Lua_Client::*)(int))&Lua_Client::SetClientMaxLevel) - .def("GetClientMaxLevel", (int(Lua_Client::*)(void))&Lua_Client::GetClientMaxLevel) - .def("CreateExpedition", (Lua_Expedition(Lua_Client::*)(luabind::object))&Lua_Client::CreateExpedition) - .def("CreateExpedition", (Lua_Expedition(Lua_Client::*)(std::string, uint32, uint32, std::string, uint32, uint32))&Lua_Client::CreateExpedition) - .def("CreateExpedition", (Lua_Expedition(Lua_Client::*)(std::string, uint32, uint32, std::string, uint32, uint32, bool))&Lua_Client::CreateExpedition) - .def("GetExpedition", (Lua_Expedition(Lua_Client::*)(void))&Lua_Client::GetExpedition) - .def("GetExpeditionLockouts", (luabind::object(Lua_Client::*)(lua_State* L))&Lua_Client::GetExpeditionLockouts) - .def("GetExpeditionLockouts", (luabind::object(Lua_Client::*)(lua_State* L, std::string))&Lua_Client::GetExpeditionLockouts) - .def("GetLockoutExpeditionUUID", (std::string(Lua_Client::*)(std::string, std::string))&Lua_Client::GetLockoutExpeditionUUID) - .def("AddExpeditionLockout", (void(Lua_Client::*)(std::string, std::string, uint32))&Lua_Client::AddExpeditionLockout) - .def("AddExpeditionLockout", (void(Lua_Client::*)(std::string, std::string, uint32, std::string))&Lua_Client::AddExpeditionLockout) - .def("AddExpeditionLockoutDuration", (void(Lua_Client::*)(std::string, std::string, int))&Lua_Client::AddExpeditionLockoutDuration) - .def("AddExpeditionLockoutDuration", (void(Lua_Client::*)(std::string, std::string, int, std::string))&Lua_Client::AddExpeditionLockoutDuration) - .def("RemoveAllExpeditionLockouts", (void(Lua_Client::*)(void))&Lua_Client::RemoveAllExpeditionLockouts) - .def("RemoveAllExpeditionLockouts", (void(Lua_Client::*)(std::string))&Lua_Client::RemoveAllExpeditionLockouts) - .def("RemoveExpeditionLockout", (void(Lua_Client::*)(std::string, std::string))&Lua_Client::RemoveExpeditionLockout) - .def("HasExpeditionLockout", (bool(Lua_Client::*)(std::string, std::string))&Lua_Client::HasExpeditionLockout) - .def("MovePCDynamicZone", (void(Lua_Client::*)(uint32))&Lua_Client::MovePCDynamicZone) - .def("MovePCDynamicZone", (void(Lua_Client::*)(uint32, int))&Lua_Client::MovePCDynamicZone) - .def("MovePCDynamicZone", (void(Lua_Client::*)(uint32, int, bool))&Lua_Client::MovePCDynamicZone) - .def("MovePCDynamicZone", (void(Lua_Client::*)(std::string))&Lua_Client::MovePCDynamicZone) - .def("MovePCDynamicZone", (void(Lua_Client::*)(std::string, int))&Lua_Client::MovePCDynamicZone) - .def("MovePCDynamicZone", (void(Lua_Client::*)(std::string, int, bool))&Lua_Client::MovePCDynamicZone) - .def("Fling", (void(Lua_Client::*)(float,float,float,float))&Lua_Client::Fling) - .def("Fling", (void(Lua_Client::*)(float,float,float,float,bool))&Lua_Client::Fling) - .def("Fling", (void(Lua_Client::*)(float,float,float,float,bool,bool))&Lua_Client::Fling); + .def(luabind::constructor<>()) + .def("AccountID", (uint32(Lua_Client::*)(void))&Lua_Client::AccountID) + .def("AccountName", (const char *(Lua_Client::*)(void))&Lua_Client::AccountName) + .def("AddAAPoints", (void(Lua_Client::*)(int))&Lua_Client::AddAAPoints) + .def("AddAlternateCurrencyValue", (void(Lua_Client::*)(uint32,int))&Lua_Client::AddAlternateCurrencyValue) + .def("AddCrystals", (void(Lua_Client::*)(uint32,uint32))&Lua_Client::AddCrystals) + .def("AddEXP", (void(Lua_Client::*)(uint32))&Lua_Client::AddEXP) + .def("AddEXP", (void(Lua_Client::*)(uint32,int))&Lua_Client::AddEXP) + .def("AddEXP", (void(Lua_Client::*)(uint32,int,bool))&Lua_Client::AddEXP) + .def("AddExpeditionLockout", (void(Lua_Client::*)(std::string, std::string, uint32))&Lua_Client::AddExpeditionLockout) + .def("AddExpeditionLockout", (void(Lua_Client::*)(std::string, std::string, uint32, std::string))&Lua_Client::AddExpeditionLockout) + .def("AddExpeditionLockoutDuration", (void(Lua_Client::*)(std::string, std::string, int))&Lua_Client::AddExpeditionLockoutDuration) + .def("AddExpeditionLockoutDuration", (void(Lua_Client::*)(std::string, std::string, int, std::string))&Lua_Client::AddExpeditionLockoutDuration) + .def("AddLDoNLoss", (void(Lua_Client::*)(uint32))&Lua_Client::AddLDoNLoss) + .def("AddLDoNWin", (void(Lua_Client::*)(uint32))&Lua_Client::AddLDoNWin) + .def("AddLevelBasedExp", (void(Lua_Client::*)(int))&Lua_Client::AddLevelBasedExp) + .def("AddLevelBasedExp", (void(Lua_Client::*)(int,int))&Lua_Client::AddLevelBasedExp) + .def("AddLevelBasedExp", (void(Lua_Client::*)(int,int,bool))&Lua_Client::AddLevelBasedExp) + .def("AddMoneyToPP", (void(Lua_Client::*)(uint32,uint32,uint32,uint32,bool))&Lua_Client::AddMoneyToPP) + .def("AddPVPPoints", (void(Lua_Client::*)(uint32))&Lua_Client::AddPVPPoints) + .def("AddSkill", (void(Lua_Client::*)(int,int))&Lua_Client::AddSkill) + .def("Admin", (int(Lua_Client::*)(void))&Lua_Client::Admin) + .def("AssignTask", (void(Lua_Client::*)(int,int))&Lua_Client::AssignTask) + .def("AssignTask", (void(Lua_Client::*)(int,int,bool))&Lua_Client::AssignTask) + .def("AssignToInstance", (void(Lua_Client::*)(int))&Lua_Client::AssignToInstance) + .def("AutoSplitEnabled", (bool(Lua_Client::*)(void))&Lua_Client::AutoSplitEnabled) + .def("BreakInvis", (void(Lua_Client::*)(void))&Lua_Client::BreakInvis) + .def("CalcATK", &Lua_Client::CalcATK) + .def("CalcCurrentWeight", &Lua_Client::CalcCurrentWeight) + .def("CalcPriceMod", (float(Lua_Client::*)(Lua_Mob,bool))&Lua_Client::CalcPriceMod) + .def("CanHaveSkill", (bool(Lua_Client::*)(int))&Lua_Client::CanHaveSkill) + .def("ChangeLastName", (void(Lua_Client::*)(const char *in))&Lua_Client::ChangeLastName) + .def("CharacterID", (uint32(Lua_Client::*)(void))&Lua_Client::CharacterID) + .def("CheckIncreaseSkill", (void(Lua_Client::*)(int,Lua_Mob))&Lua_Client::CheckIncreaseSkill) + .def("CheckIncreaseSkill", (void(Lua_Client::*)(int,Lua_Mob,int))&Lua_Client::CheckIncreaseSkill) + .def("CheckSpecializeIncrease", (void(Lua_Client::*)(int))&Lua_Client::CheckSpecializeIncrease) + .def("ClearCompassMark",(void(Lua_Client::*)(void))&Lua_Client::ClearCompassMark) + .def("ClearZoneFlag", (void(Lua_Client::*)(int))&Lua_Client::ClearZoneFlag) + .def("Connected", (bool(Lua_Client::*)(void))&Lua_Client::Connected) + .def("CountItem", (int(Lua_Client::*)(uint32))&Lua_Client::CountItem) + .def("CreateExpedition", (Lua_Expedition(Lua_Client::*)(luabind::object))&Lua_Client::CreateExpedition) + .def("CreateExpedition", (Lua_Expedition(Lua_Client::*)(std::string, uint32, uint32, std::string, uint32, uint32))&Lua_Client::CreateExpedition) + .def("CreateExpedition", (Lua_Expedition(Lua_Client::*)(std::string, uint32, uint32, std::string, uint32, uint32, bool))&Lua_Client::CreateExpedition) + .def("CreateTaskDynamicZone", &Lua_Client::CreateTaskDynamicZone) + .def("DecreaseByID", (bool(Lua_Client::*)(uint32,int))&Lua_Client::DecreaseByID) + .def("DeleteItemInInventory", (void(Lua_Client::*)(int,int))&Lua_Client::DeleteItemInInventory) + .def("DeleteItemInInventory", (void(Lua_Client::*)(int,int,bool))&Lua_Client::DeleteItemInInventory) + .def("DiaWind", (void(Lua_Client::*)(std::string))&Lua_Client::DialogueWindow) + .def("DialogueWindow", (void(Lua_Client::*)(std::string))&Lua_Client::DialogueWindow) + .def("DisableAreaEndRegen", &Lua_Client::DisableAreaEndRegen) + .def("DisableAreaHPRegen", &Lua_Client::DisableAreaHPRegen) + .def("DisableAreaManaRegen", &Lua_Client::DisableAreaManaRegen) + .def("DisableAreaRegens", &Lua_Client::DisableAreaRegens) + .def("Disconnect", (void(Lua_Client::*)(void))&Lua_Client::Disconnect) + .def("DropItem", (void(Lua_Client::*)(int))&Lua_Client::DropItem) + .def("Duck", (void(Lua_Client::*)(void))&Lua_Client::Duck) + .def("DyeArmorBySlot", (void(Lua_Client::*)(uint8,uint8,uint8,uint8))&Lua_Client::DyeArmorBySlot) + .def("DyeArmorBySlot", (void(Lua_Client::*)(uint8,uint8,uint8,uint8,uint8))&Lua_Client::DyeArmorBySlot) + .def("EnableAreaEndRegen", &Lua_Client::EnableAreaEndRegen) + .def("EnableAreaHPRegen", &Lua_Client::EnableAreaHPRegen) + .def("EnableAreaManaRegen", &Lua_Client::EnableAreaManaRegen) + .def("EnableAreaRegens", &Lua_Client::EnableAreaRegens) + .def("Escape", (void(Lua_Client::*)(void))&Lua_Client::Escape) + .def("FailTask", (void(Lua_Client::*)(int))&Lua_Client::FailTask) + .def("FilteredMessage", &Lua_Client::FilteredMessage) + .def("FindEmptyMemSlot", (int(Lua_Client::*)(void))&Lua_Client::FindEmptyMemSlot) + .def("FindMemmedSpellBySlot", (uint16(Lua_Client::*)(int))&Lua_Client::FindMemmedSpellBySlot) + .def("FindMemmedSpellBySpellID", (int(Lua_Client::*)(uint16))&Lua_Client::FindMemmedSpellBySpellID) + .def("FindSpellBookSlotBySpellID", (int(Lua_Client::*)(int))&Lua_Client::FindSpellBookSlotBySpellID) + .def("Fling", (void(Lua_Client::*)(float,float,float,float))&Lua_Client::Fling) + .def("Fling", (void(Lua_Client::*)(float,float,float,float,bool))&Lua_Client::Fling) + .def("Fling", (void(Lua_Client::*)(float,float,float,float,bool,bool))&Lua_Client::Fling) + .def("ForageItem", (void(Lua_Client::*)(bool))&Lua_Client::ForageItem) + .def("ForageItem", (void(Lua_Client::*)(void))&Lua_Client::ForageItem) + .def("Freeze", (void(Lua_Client::*)(void))&Lua_Client::Freeze) + .def("GetAAEXPModifier", (double(Lua_Client::*)(uint32))&Lua_Client::GetAAEXPModifier) + .def("GetAAExp", (uint32(Lua_Client::*)(void))&Lua_Client::GetAAExp) + .def("GetAAPercent", (uint32(Lua_Client::*)(void))&Lua_Client::GetAAPercent) + .def("GetAAPoints", (int(Lua_Client::*)(void))&Lua_Client::GetAAPoints) + .def("GetAFK", (int(Lua_Client::*)(void))&Lua_Client::GetAFK) + .def("GetAccountAge", (int(Lua_Client::*)(void))&Lua_Client::GetAccountAge) + .def("GetAccountFlag", (std::string(Lua_Client::*)(std::string))&Lua_Client::GetAccountFlag) + .def("GetAggroCount", (int(Lua_Client::*)(void))&Lua_Client::GetAggroCount) + .def("GetAllMoney", (uint64(Lua_Client::*)(void))&Lua_Client::GetAllMoney) + .def("GetAlternateCurrencyValue", (int(Lua_Client::*)(uint32))&Lua_Client::GetAlternateCurrencyValue) + .def("GetAnon", (int(Lua_Client::*)(void))&Lua_Client::GetAnon) + .def("GetAugmentIDAt", (int(Lua_Client::*)(int,int))&Lua_Client::GetAugmentIDAt) + .def("GetBaseAGI", (int(Lua_Client::*)(void))&Lua_Client::GetBaseAGI) + .def("GetBaseCHA", (int(Lua_Client::*)(void))&Lua_Client::GetBaseCHA) + .def("GetBaseDEX", (int(Lua_Client::*)(void))&Lua_Client::GetBaseDEX) + .def("GetBaseFace", (int(Lua_Client::*)(void))&Lua_Client::GetBaseFace) + .def("GetBaseINT", (int(Lua_Client::*)(void))&Lua_Client::GetBaseINT) + .def("GetBaseSTA", (int(Lua_Client::*)(void))&Lua_Client::GetBaseSTA) + .def("GetBaseSTR", (int(Lua_Client::*)(void))&Lua_Client::GetBaseSTR) + .def("GetBaseWIS", (int(Lua_Client::*)(void))&Lua_Client::GetBaseWIS) + .def("GetBindHeading", (float(Lua_Client::*)(int))&Lua_Client::GetBindHeading) + .def("GetBindHeading", (float(Lua_Client::*)(void))&Lua_Client::GetBindHeading) + .def("GetBindX", (float(Lua_Client::*)(int))&Lua_Client::GetBindX) + .def("GetBindX", (float(Lua_Client::*)(void))&Lua_Client::GetBindX) + .def("GetBindY", (float(Lua_Client::*)(int))&Lua_Client::GetBindY) + .def("GetBindY", (float(Lua_Client::*)(void))&Lua_Client::GetBindY) + .def("GetBindZ", (float(Lua_Client::*)(int))&Lua_Client::GetBindZ) + .def("GetBindZ", (float(Lua_Client::*)(void))&Lua_Client::GetBindZ) + .def("GetBindZoneID", (uint32(Lua_Client::*)(int))&Lua_Client::GetBindZoneID) + .def("GetBindZoneID", (uint32(Lua_Client::*)(void))&Lua_Client::GetBindZoneID) + .def("GetCarriedMoney", (uint64(Lua_Client::*)(void))&Lua_Client::GetCarriedMoney) + .def("GetCharacterFactionLevel", (int(Lua_Client::*)(int))&Lua_Client::GetCharacterFactionLevel) + .def("GetClassBitmask", (int(Lua_Client::*)(void))&Lua_Client::GetClassBitmask) + .def("GetClientMaxLevel", (int(Lua_Client::*)(void))&Lua_Client::GetClientMaxLevel) + .def("GetClientVersion", (int(Lua_Client::*)(void))&Lua_Client::GetClientVersion) + .def("GetClientVersionBit", (uint32(Lua_Client::*)(void))&Lua_Client::GetClientVersionBit) + .def("GetCorpseCount", (int(Lua_Client::*)(void))&Lua_Client::GetCorpseCount) + .def("GetCorpseID", (int(Lua_Client::*)(int))&Lua_Client::GetCorpseID) + .def("GetCorpseItemAt", (int(Lua_Client::*)(int,int))&Lua_Client::GetCorpseItemAt) + .def("GetDiscSlotBySpellID", (int(Lua_Client::*)(int32))&Lua_Client::GetDiscSlotBySpellID) + .def("GetDisciplineTimer", (uint32(Lua_Client::*)(uint32))&Lua_Client::GetDisciplineTimer) + .def("GetDuelTarget", (int(Lua_Client::*)(void))&Lua_Client::GetDuelTarget) + .def("GetEXP", (uint32(Lua_Client::*)(void))&Lua_Client::GetEXP) + .def("GetEXPModifier", (double(Lua_Client::*)(uint32))&Lua_Client::GetEXPModifier) + .def("GetEbonCrystals", (uint32(Lua_Client::*)(void))&Lua_Client::GetEbonCrystals) + .def("GetEndurance", (int(Lua_Client::*)(void))&Lua_Client::GetEndurance) + .def("GetEndurancePercent", (int(Lua_Client::*)(void))&Lua_Client::GetEndurancePercent) + .def("GetExpedition", (Lua_Expedition(Lua_Client::*)(void))&Lua_Client::GetExpedition) + .def("GetExpeditionLockouts", (luabind::object(Lua_Client::*)(lua_State* L))&Lua_Client::GetExpeditionLockouts) + .def("GetExpeditionLockouts", (luabind::object(Lua_Client::*)(lua_State* L, std::string))&Lua_Client::GetExpeditionLockouts) + .def("GetFace", (int(Lua_Client::*)(void))&Lua_Client::GetFace) + .def("GetFactionLevel", (int(Lua_Client::*)(uint32,uint32,uint32,uint32,uint32,uint32,Lua_NPC))&Lua_Client::GetFactionLevel) + .def("GetFeigned", (bool(Lua_Client::*)(void))&Lua_Client::GetFeigned) + .def("GetGM", (bool(Lua_Client::*)(void))&Lua_Client::GetGM) + .def("GetGroup", (Lua_Group(Lua_Client::*)(void))&Lua_Client::GetGroup) + .def("GetGroupPoints", (uint32(Lua_Client::*)(void))&Lua_Client::GetGroupPoints) + .def("GetHorseId", (int(Lua_Client::*)(void))&Lua_Client::GetHorseId) + .def("GetHunger", (int(Lua_Client::*)(void))&Lua_Client::GetHunger) + .def("GetIP", (uint32(Lua_Client::*)(void))&Lua_Client::GetIP) + .def("GetIPExemption", (int(Lua_Client::*)(void))&Lua_Client::GetIPExemption) + .def("GetIPString", (std::string(Lua_Client::*)(void))&Lua_Client::GetIPString) + .def("GetInstrumentMod", (int(Lua_Client::*)(int))&Lua_Client::GetInstrumentMod) + .def("GetInventory", (Lua_Inventory(Lua_Client::*)(void))&Lua_Client::GetInventory) + .def("GetItemIDAt", (int(Lua_Client::*)(int))&Lua_Client::GetItemIDAt) + .def("GetLDoNLosses", (int(Lua_Client::*)(void))&Lua_Client::GetLDoNLosses) + .def("GetLDoNLossesTheme", (int(Lua_Client::*)(int))&Lua_Client::GetLDoNLossesTheme) + .def("GetLDoNPointsTheme", (int(Lua_Client::*)(int))&Lua_Client::GetLDoNPointsTheme) + .def("GetLDoNWins", (int(Lua_Client::*)(void))&Lua_Client::GetLDoNWins) + .def("GetLDoNWinsTheme", (int(Lua_Client::*)(int))&Lua_Client::GetLDoNWinsTheme) + .def("GetLanguageSkill", (int(Lua_Client::*)(int))&Lua_Client::GetLanguageSkill) + .def("GetLearnableDisciplines", (luabind::object(Lua_Client::*)(lua_State* L))&Lua_Client::GetLearnableDisciplines) + .def("GetLearnableDisciplines", (luabind::object(Lua_Client::*)(lua_State* L,uint8))&Lua_Client::GetLearnableDisciplines) + .def("GetLearnableDisciplines", (luabind::object(Lua_Client::*)(lua_State* L,uint8,uint8))&Lua_Client::GetLearnableDisciplines) + .def("GetLearnedDisciplines", (luabind::object(Lua_Client::*)(lua_State* L))&Lua_Client::GetLearnedDisciplines) + .def("GetLockoutExpeditionUUID", (std::string(Lua_Client::*)(std::string, std::string))&Lua_Client::GetLockoutExpeditionUUID) + .def("GetMaxEndurance", (int(Lua_Client::*)(void))&Lua_Client::GetMaxEndurance) + .def("GetMemmedSpells", (luabind::object(Lua_Client::*)(lua_State* L))&Lua_Client::GetMemmedSpells) + .def("GetModCharacterFactionLevel", (int(Lua_Client::*)(int))&Lua_Client::GetModCharacterFactionLevel) + .def("GetMoney", (uint32(Lua_Client::*)(uint8, uint8))&Lua_Client::GetMoney) + .def("GetNextAvailableDisciplineSlot", (int(Lua_Client::*)(int))&Lua_Client::GetNextAvailableDisciplineSlot) + .def("GetNextAvailableDisciplineSlot", (int(Lua_Client::*)(void))&Lua_Client::GetNextAvailableDisciplineSlot) + .def("GetNextAvailableSpellBookSlot", (int(Lua_Client::*)(int))&Lua_Client::GetNextAvailableSpellBookSlot) + .def("GetNextAvailableSpellBookSlot", (int(Lua_Client::*)(void))&Lua_Client::GetNextAvailableSpellBookSlot) + .def("GetPVP", (bool(Lua_Client::*)(void))&Lua_Client::GetPVP) + .def("GetPVPPoints", (uint32(Lua_Client::*)(void))&Lua_Client::GetPVPPoints) + .def("GetRaceBitmask", (int(Lua_Client::*)(void))&Lua_Client::GetRaceBitmask) + .def("GetRadiantCrystals", (uint32(Lua_Client::*)(void))&Lua_Client::GetRadiantCrystals) + .def("GetRaid", (Lua_Raid(Lua_Client::*)(void))&Lua_Client::GetRaid) + .def("GetRaidPoints", (uint32(Lua_Client::*)(void))&Lua_Client::GetRaidPoints) + .def("GetRawItemAC", (int(Lua_Client::*)(void))&Lua_Client::GetRawItemAC) + .def("GetRawSkill", (int(Lua_Client::*)(int))&Lua_Client::GetRawSkill) + .def("GetScribeableSpells", (luabind::object(Lua_Client::*)(lua_State* L))&Lua_Client::GetScribeableSpells) + .def("GetScribeableSpells", (luabind::object(Lua_Client::*)(lua_State* L,uint8))&Lua_Client::GetScribeableSpells) + .def("GetScribeableSpells", (luabind::object(Lua_Client::*)(lua_State* L,uint8,uint8))&Lua_Client::GetScribeableSpells) + .def("GetScribedSpells", (luabind::object(Lua_Client::*)(lua_State* L))&Lua_Client::GetScribedSpells) + .def("GetSkillPoints", (int(Lua_Client::*)(void))&Lua_Client::GetSkillPoints) + .def("GetSpellIDByBookSlot", (uint32(Lua_Client::*)(int))&Lua_Client::GetSpellIDByBookSlot) + .def("GetSpentAA", (int(Lua_Client::*)(void))&Lua_Client::GetSpentAA) + .def("GetStartZone", (int(Lua_Client::*)(void))&Lua_Client::GetStartZone) + .def("GetTargetRingX", (float(Lua_Client::*)(void))&Lua_Client::GetTargetRingX) + .def("GetTargetRingY", (float(Lua_Client::*)(void))&Lua_Client::GetTargetRingY) + .def("GetTargetRingZ", (float(Lua_Client::*)(void))&Lua_Client::GetTargetRingZ) + .def("GetThirst", (int(Lua_Client::*)(void))&Lua_Client::GetThirst) + .def("GetTotalSecondsPlayed", (uint32(Lua_Client::*)(void))&Lua_Client::GetTotalSecondsPlayed) + .def("GetWeight", (int(Lua_Client::*)(void))&Lua_Client::GetWeight) + .def("GoFish", (void(Lua_Client::*)(void))&Lua_Client::GoFish) + .def("GrantAlternateAdvancementAbility", (bool(Lua_Client::*)(int, int))&Lua_Client::GrantAlternateAdvancementAbility) + .def("GrantAlternateAdvancementAbility", (bool(Lua_Client::*)(int, int, bool))&Lua_Client::GrantAlternateAdvancementAbility) + .def("GuildID", (uint32(Lua_Client::*)(void))&Lua_Client::GuildID) + .def("GuildRank", (int(Lua_Client::*)(void))&Lua_Client::GuildRank) + .def("HasDisciplineLearned", (bool(Lua_Client::*)(uint16))&Lua_Client::HasDisciplineLearned) + .def("HasExpeditionLockout", (bool(Lua_Client::*)(std::string, std::string))&Lua_Client::HasExpeditionLockout) + .def("HasSkill", (bool(Lua_Client::*)(int))&Lua_Client::HasSkill) + .def("HasSpellScribed", (bool(Lua_Client::*)(int))&Lua_Client::HasSpellScribed) + .def("HasZoneFlag", (bool(Lua_Client::*)(int))&Lua_Client::HasZoneFlag) + .def("Hungry", (bool(Lua_Client::*)(void))&Lua_Client::Hungry) + .def("InZone", (bool(Lua_Client::*)(void))&Lua_Client::InZone) + .def("IncStats", (void(Lua_Client::*)(int,int))&Lua_Client::IncStats) + .def("IncreaseLanguageSkill", (void(Lua_Client::*)(int))&Lua_Client::IncreaseLanguageSkill) + .def("IncreaseLanguageSkill", (void(Lua_Client::*)(int,int))&Lua_Client::IncreaseLanguageSkill) + .def("IncreaseSkill", (void(Lua_Client::*)(int))&Lua_Client::IncreaseSkill) + .def("IncreaseSkill", (void(Lua_Client::*)(int,int))&Lua_Client::IncreaseSkill) + .def("IncrementAA", (void(Lua_Client::*)(int))&Lua_Client::IncrementAA) + .def("IsCrouching", (bool(Lua_Client::*)(void))&Lua_Client::IsCrouching) + .def("IsDead", &Lua_Client::IsDead) + .def("IsDueling", (bool(Lua_Client::*)(void))&Lua_Client::IsDueling) + .def("IsGrouped", (bool(Lua_Client::*)(void))&Lua_Client::IsGrouped) + .def("IsLD", (bool(Lua_Client::*)(void))&Lua_Client::IsLD) + .def("IsMedding", (bool(Lua_Client::*)(void))&Lua_Client::IsMedding) + .def("IsRaidGrouped", (bool(Lua_Client::*)(void))&Lua_Client::IsRaidGrouped) + .def("IsSitting", (bool(Lua_Client::*)(void))&Lua_Client::IsSitting) + .def("IsStanding", (bool(Lua_Client::*)(void))&Lua_Client::IsStanding) + .def("IsTaskActive", (bool(Lua_Client::*)(int))&Lua_Client::IsTaskActive) + .def("IsTaskActivityActive", (bool(Lua_Client::*)(int,int))&Lua_Client::IsTaskActivityActive) + .def("IsTaskCompleted", (bool(Lua_Client::*)(int))&Lua_Client::IsTaskCompleted) + .def("KeyRingAdd", (void(Lua_Client::*)(uint32))&Lua_Client::KeyRingAdd) + .def("KeyRingCheck", (bool(Lua_Client::*)(uint32))&Lua_Client::KeyRingCheck) + .def("Kick", (void(Lua_Client::*)(void))&Lua_Client::Kick) + .def("LearnDisciplines", (uint16(Lua_Client::*)(uint8,uint8))&Lua_Client::LearnDisciplines) + .def("LearnRecipe", (void(Lua_Client::*)(uint32))&Lua_Client::LearnRecipe) + .def("LeaveGroup", (void(Lua_Client::*)(void))&Lua_Client::LeaveGroup) + .def("MarkSingleCompassLoc", (void(Lua_Client::*)(float,float,float))&Lua_Client::MarkSingleCompassLoc) + .def("MarkSingleCompassLoc", (void(Lua_Client::*)(float,float,float,int))&Lua_Client::MarkSingleCompassLoc) + .def("MaxSkill", (int(Lua_Client::*)(int))&Lua_Client::MaxSkill) + .def("MemSpell", (void(Lua_Client::*)(int,int))&Lua_Client::MemSpell) + .def("MemSpell", (void(Lua_Client::*)(int,int,bool))&Lua_Client::MemSpell) + .def("MemmedCount", (int(Lua_Client::*)(void))&Lua_Client::MemmedCount) + .def("MovePC", (void(Lua_Client::*)(int,float,float,float,float))&Lua_Client::MovePC) + .def("MovePCDynamicZone", (void(Lua_Client::*)(std::string))&Lua_Client::MovePCDynamicZone) + .def("MovePCDynamicZone", (void(Lua_Client::*)(std::string, int))&Lua_Client::MovePCDynamicZone) + .def("MovePCDynamicZone", (void(Lua_Client::*)(std::string, int, bool))&Lua_Client::MovePCDynamicZone) + .def("MovePCDynamicZone", (void(Lua_Client::*)(uint32))&Lua_Client::MovePCDynamicZone) + .def("MovePCDynamicZone", (void(Lua_Client::*)(uint32, int))&Lua_Client::MovePCDynamicZone) + .def("MovePCDynamicZone", (void(Lua_Client::*)(uint32, int, bool))&Lua_Client::MovePCDynamicZone) + .def("MovePCInstance", (void(Lua_Client::*)(int,int,float,float,float,float))&Lua_Client::MovePCInstance) + .def("MoveZone", (void(Lua_Client::*)(const char*))&Lua_Client::MoveZone) + .def("MoveZoneGroup", (void(Lua_Client::*)(const char*))&Lua_Client::MoveZoneGroup) + .def("MoveZoneInstance", (void(Lua_Client::*)(uint16))&Lua_Client::MoveZoneInstance) + .def("MoveZoneInstanceGroup", (void(Lua_Client::*)(uint16))&Lua_Client::MoveZoneInstanceGroup) + .def("MoveZoneInstanceRaid", (void(Lua_Client::*)(uint16))&Lua_Client::MoveZoneInstanceRaid) + .def("MoveZoneRaid", (void(Lua_Client::*)(const char*))&Lua_Client::MoveZoneRaid) + .def("NotifyNewTitlesAvailable", (void(Lua_Client::*)(void))&Lua_Client::NotifyNewTitlesAvailable) + .def("NukeItem", (void(Lua_Client::*)(uint32))&Lua_Client::NukeItem) + .def("NukeItem", (void(Lua_Client::*)(uint32,int))&Lua_Client::NukeItem) + .def("OpenLFGuildWindow", (void(Lua_Client::*)(void))&Lua_Client::OpenLFGuildWindow) + .def("PlayMP3", (void(Lua_Client::*)(std::string))&Lua_Client::PlayMP3) + .def("Popup", (void(Lua_Client::*)(const char*,const char*))&Lua_Client::Popup) + .def("Popup", (void(Lua_Client::*)(const char*,const char*,uint32))&Lua_Client::Popup) + .def("Popup", (void(Lua_Client::*)(const char*,const char*,uint32,uint32))&Lua_Client::Popup) + .def("Popup", (void(Lua_Client::*)(const char*,const char*,uint32,uint32,uint32))&Lua_Client::Popup) + .def("Popup", (void(Lua_Client::*)(const char*,const char*,uint32,uint32,uint32,uint32))&Lua_Client::Popup) + .def("Popup", (void(Lua_Client::*)(const char*,const char*,uint32,uint32,uint32,uint32,const char*,const char*))&Lua_Client::Popup) + .def("Popup", (void(Lua_Client::*)(const char*,const char*,uint32,uint32,uint32,uint32,const char*,const char*,uint32))&Lua_Client::Popup) + .def("PushItemOnCursor", (bool(Lua_Client::*)(Lua_ItemInst))&Lua_Client::PushItemOnCursor) + .def("PutItemInInventory", (bool(Lua_Client::*)(int,Lua_ItemInst))&Lua_Client::PutItemInInventory) + .def("QuestReadBook", (void(Lua_Client::*)(const char *,int))&Lua_Client::QuestReadBook) + .def("QuestReward", (void(Lua_Client::*)(Lua_Mob))&Lua_Client::QuestReward) + .def("QuestReward", (void(Lua_Client::*)(Lua_Mob, luabind::adl::object))&Lua_Client::QuestReward) + .def("QuestReward", (void(Lua_Client::*)(Lua_Mob, uint32))&Lua_Client::QuestReward) + .def("QuestReward", (void(Lua_Client::*)(Lua_Mob, uint32, uint32))&Lua_Client::QuestReward) + .def("QuestReward", (void(Lua_Client::*)(Lua_Mob, uint32, uint32, uint32))&Lua_Client::QuestReward) + .def("QuestReward", (void(Lua_Client::*)(Lua_Mob, uint32, uint32, uint32, uint32))&Lua_Client::QuestReward) + .def("QuestReward", (void(Lua_Client::*)(Lua_Mob, uint32, uint32, uint32, uint32, uint32))&Lua_Client::QuestReward) + .def("QuestReward", (void(Lua_Client::*)(Lua_Mob, uint32, uint32, uint32, uint32, uint32, uint32))&Lua_Client::QuestReward) + .def("QuestReward", (void(Lua_Client::*)(Lua_Mob, uint32, uint32, uint32, uint32, uint32, uint32, bool))&Lua_Client::QuestReward) + .def("QueuePacket", (void(Lua_Client::*)(Lua_Packet))&Lua_Client::QueuePacket) + .def("QueuePacket", (void(Lua_Client::*)(Lua_Packet,bool))&Lua_Client::QueuePacket) + .def("QueuePacket", (void(Lua_Client::*)(Lua_Packet,bool,int))&Lua_Client::QueuePacket) + .def("QueuePacket", (void(Lua_Client::*)(Lua_Packet,bool,int,int))&Lua_Client::QueuePacket) + .def("ReadBookByName", (void(Lua_Client::*)(std::string,uint8))&Lua_Client::ReadBookByName) + .def("RefundAA", (void(Lua_Client::*)(void))&Lua_Client::RefundAA) + .def("RemoveAllExpeditionLockouts", (void(Lua_Client::*)(std::string))&Lua_Client::RemoveAllExpeditionLockouts) + .def("RemoveAllExpeditionLockouts", (void(Lua_Client::*)(void))&Lua_Client::RemoveAllExpeditionLockouts) + .def("RemoveExpeditionLockout", (void(Lua_Client::*)(std::string, std::string))&Lua_Client::RemoveExpeditionLockout) + .def("RemoveItem", (void(Lua_Client::*)(uint32))&Lua_Client::RemoveItem) + .def("RemoveItem", (void(Lua_Client::*)(uint32,uint32))&Lua_Client::RemoveItem) + .def("RemoveLDoNLoss", (void(Lua_Client::*)(uint32))&Lua_Client::RemoveLDoNLoss) + .def("RemoveLDoNWin", (void(Lua_Client::*)(uint32))&Lua_Client::RemoveLDoNWin) + .def("ResetAA", (void(Lua_Client::*)(void))&Lua_Client::ResetAA) + .def("ResetAllDisciplineTimers", (void(Lua_Client::*)(void))&Lua_Client::ResetAllDisciplineTimers) + .def("ResetAllCastbarCooldowns", (void(Lua_Client::*)(void))&Lua_Client::ResetAllCastbarCooldowns) + .def("ResetCastbarCooldownBySlot", (void(Lua_Client::*)(int))&Lua_Client::ResetCastbarCooldownBySlot) + .def("ResetCastbarCooldownBySpellID", (void(Lua_Client::*)(uint32))&Lua_Client::ResetCastbarCooldownBySpellID) + .def("ResetDisciplineTimer", (void(Lua_Client::*)(uint32))&Lua_Client::ResetDisciplineTimer) + .def("ResetTrade", (void(Lua_Client::*)(void))&Lua_Client::ResetTrade) + .def("Save", (void(Lua_Client::*)(int))&Lua_Client::Save) + .def("Save", (void(Lua_Client::*)(void))&Lua_Client::Save) + .def("SaveBackup", (void(Lua_Client::*)(void))&Lua_Client::SaveBackup) + .def("ScribeSpell", (void(Lua_Client::*)(int,int))&Lua_Client::ScribeSpell) + .def("ScribeSpell", (void(Lua_Client::*)(int,int,bool))&Lua_Client::ScribeSpell) + .def("ScribeSpells", (uint16(Lua_Client::*)(uint8,uint8))&Lua_Client::ScribeSpells) + .def("SendColoredText", (void(Lua_Client::*)(uint32, std::string))&Lua_Client::SendColoredText) + .def("SendItemScale", (void(Lua_Client::*)(Lua_ItemInst))&Lua_Client::SendItemScale) + .def("SendMarqueeMessage", (void(Lua_Client::*)(uint32, uint32, uint32, uint32, uint32, std::string))&Lua_Client::SendMarqueeMessage) + .def("SendOPTranslocateConfirm", (void(Lua_Client::*)(Lua_Mob,int))&Lua_Client::SendOPTranslocateConfirm) + .def("SendSound", (void(Lua_Client::*)(void))&Lua_Client::SendSound) + .def("SendToGuildHall", (void(Lua_Client::*)(void))&Lua_Client::SendToGuildHall) + .def("SendToInstance", (void(Lua_Client::*)(std::string,std::string,uint32,float,float,float,float,std::string,uint32))&Lua_Client::SendToInstance) + .def("SendWebLink", (void(Lua_Client::*)(const char *))&Lua_Client::SendWebLink) + .def("SendZoneFlagInfo", (void(Lua_Client::*)(Lua_Client))&Lua_Client::SendZoneFlagInfo) + .def("SetAAEXPModifier", (void(Lua_Client::*)(uint32,double))&Lua_Client::SetAAEXPModifier) + .def("SetAAPoints", (void(Lua_Client::*)(int))&Lua_Client::SetAAPoints) + .def("SetAATitle", (void(Lua_Client::*)(const char *))&Lua_Client::SetAATitle) + .def("SetAFK", (void(Lua_Client::*)(uint8))&Lua_Client::SetAFK) + .def("SetAccountFlag", (void(Lua_Client::*)(std::string,std::string))&Lua_Client::SetAccountFlag) + .def("SetAccountFlag", (void(Lua_Client::*)(std::string,std::string))&Lua_Client::SetAccountFlag) + .def("SetAlternateCurrencyValue", (void(Lua_Client::*)(uint32,int))&Lua_Client::SetAlternateCurrencyValue) + .def("SetAnon", (void(Lua_Client::*)(uint8))&Lua_Client::SetAnon) + .def("SetBaseClass", (void(Lua_Client::*)(int))&Lua_Client::SetBaseClass) + .def("SetBaseGender", (void(Lua_Client::*)(int))&Lua_Client::SetBaseGender) + .def("SetBaseRace", (void(Lua_Client::*)(int))&Lua_Client::SetBaseRace) + .def("SetBindPoint", (void(Lua_Client::*)(int))&Lua_Client::SetBindPoint) + .def("SetBindPoint", (void(Lua_Client::*)(int,int))&Lua_Client::SetBindPoint) + .def("SetBindPoint", (void(Lua_Client::*)(int,int,float))&Lua_Client::SetBindPoint) + .def("SetBindPoint", (void(Lua_Client::*)(int,int,float,float))&Lua_Client::SetBindPoint) + .def("SetBindPoint", (void(Lua_Client::*)(int,int,float,float,float))&Lua_Client::SetBindPoint) + .def("SetBindPoint", (void(Lua_Client::*)(int,int,float,float,float,float))&Lua_Client::SetBindPoint) + .def("SetBindPoint", (void(Lua_Client::*)(void))&Lua_Client::SetBindPoint) + .def("SetClientMaxLevel", (void(Lua_Client::*)(int))&Lua_Client::SetClientMaxLevel) + .def("SetConsumption", (void(Lua_Client::*)(int, int))&Lua_Client::SetConsumption) + .def("SetDeity", (void(Lua_Client::*)(int))&Lua_Client::SetDeity) + .def("SetDuelTarget", (void(Lua_Client::*)(int))&Lua_Client::SetDuelTarget) + .def("SetDueling", (void(Lua_Client::*)(bool))&Lua_Client::SetDueling) + .def("SetEXP", (void(Lua_Client::*)(uint32,uint32))&Lua_Client::SetEXP) + .def("SetEXP", (void(Lua_Client::*)(uint32,uint32,bool))&Lua_Client::SetEXP) + .def("SetEXPModifier", (void(Lua_Client::*)(uint32,double))&Lua_Client::SetEXPModifier) + .def("SetEbonCrystals", (void(Lua_Client::*)(uint32))&Lua_Client::SetEbonCrystals) + .def("SetEndurance", (void(Lua_Client::*)(int))&Lua_Client::SetEndurance) + .def("SetFactionLevel", (void(Lua_Client::*)(uint32,uint32,int,int,int))&Lua_Client::SetFactionLevel) + .def("SetFactionLevel2", (void(Lua_Client::*)(uint32,int,int,int,int,int,int))&Lua_Client::SetFactionLevel2) + .def("SetFeigned", (void(Lua_Client::*)(bool))&Lua_Client::SetFeigned) + .def("SetGM", (void(Lua_Client::*)(bool))&Lua_Client::SetGM) + .def("SetGMStatus", (void(Lua_Client::*)(int32))&Lua_Client::SetGMStatus) + .def("SetHideMe", (void(Lua_Client::*)(bool))&Lua_Client::SetHideMe) + .def("SetHorseId", (void(Lua_Client::*)(int))&Lua_Client::SetHorseId) + .def("SetHunger", (void(Lua_Client::*)(int))&Lua_Client::SetHunger) + .def("SetIPExemption", (void(Lua_Client::*)(int))&Lua_Client::SetIPExemption) + .def("SetLanguageSkill", (void(Lua_Client::*)(int,int))&Lua_Client::SetLanguageSkill) + .def("SetMaterial", (void(Lua_Client::*)(int,uint32))&Lua_Client::SetMaterial) + .def("SetPVP", (void(Lua_Client::*)(bool))&Lua_Client::SetPVP) + .def("SetPrimaryWeaponOrnamentation", (void(Lua_Client::*)(uint32))&Lua_Client::SetPrimaryWeaponOrnamentation) + .def("SetRadiantCrystals", (void(Lua_Client::*)(uint32))&Lua_Client::SetRadiantCrystals) + .def("SetSecondaryWeaponOrnamentation", (void(Lua_Client::*)(uint32))&Lua_Client::SetSecondaryWeaponOrnamentation) + .def("SetSkill", (void(Lua_Client::*)(int,int))&Lua_Client::SetSkill) + .def("SetSkillPoints", (void(Lua_Client::*)(int))&Lua_Client::SetSkillPoints) + .def("SetStartZone", (void(Lua_Client::*)(int))&Lua_Client::SetStartZone) + .def("SetStartZone", (void(Lua_Client::*)(int,float))&Lua_Client::SetStartZone) + .def("SetStartZone", (void(Lua_Client::*)(int,float,float))&Lua_Client::SetStartZone) + .def("SetStartZone", (void(Lua_Client::*)(int,float,float,float))&Lua_Client::SetStartZone) + .def("SetStats", (void(Lua_Client::*)(int,int))&Lua_Client::SetStats) + .def("SetThirst", (void(Lua_Client::*)(int))&Lua_Client::SetThirst) + .def("SetTint", (void(Lua_Client::*)(int,uint32))&Lua_Client::SetTint) + .def("SetTitleSuffix", (void(Lua_Client::*)(const char *))&Lua_Client::SetTitleSuffix) + .def("SetZoneFlag", (void(Lua_Client::*)(int))&Lua_Client::SetZoneFlag) + .def("Signal", (void(Lua_Client::*)(uint32))&Lua_Client::Signal) + .def("Sit", (void(Lua_Client::*)(void))&Lua_Client::Sit) + .def("Stand", (void(Lua_Client::*)(void))&Lua_Client::Stand) + .def("SummonBaggedItems", (void(Lua_Client::*)(uint32,luabind::adl::object))&Lua_Client::SummonBaggedItems) + .def("SummonItem", (void(Lua_Client::*)(uint32))&Lua_Client::SummonItem) + .def("SummonItem", (void(Lua_Client::*)(uint32,int))&Lua_Client::SummonItem) + .def("SummonItem", (void(Lua_Client::*)(uint32,int,uint32))&Lua_Client::SummonItem) + .def("SummonItem", (void(Lua_Client::*)(uint32,int,uint32,uint32))&Lua_Client::SummonItem) + .def("SummonItem", (void(Lua_Client::*)(uint32,int,uint32,uint32,uint32))&Lua_Client::SummonItem) + .def("SummonItem", (void(Lua_Client::*)(uint32,int,uint32,uint32,uint32,uint32))&Lua_Client::SummonItem) + .def("SummonItem", (void(Lua_Client::*)(uint32,int,uint32,uint32,uint32,uint32,uint32))&Lua_Client::SummonItem) + .def("SummonItem", (void(Lua_Client::*)(uint32,int,uint32,uint32,uint32,uint32,uint32,bool))&Lua_Client::SummonItem) + .def("SummonItem", (void(Lua_Client::*)(uint32,int,uint32,uint32,uint32,uint32,uint32,bool,int))&Lua_Client::SummonItem) + .def("TGB", (bool(Lua_Client::*)(void))&Lua_Client::TGB) + .def("TakeMoneyFromPP", (bool(Lua_Client::*)(uint64))&Lua_Client::TakeMoneyFromPP) + .def("TakeMoneyFromPP", (bool(Lua_Client::*)(uint64,bool))&Lua_Client::TakeMoneyFromPP) + .def("Thirsty", (bool(Lua_Client::*)(void))&Lua_Client::Thirsty) + .def("TrainDisc", (void(Lua_Client::*)(int))&Lua_Client::TrainDisc) + .def("TrainDiscBySpellID", (void(Lua_Client::*)(int32))&Lua_Client::TrainDiscBySpellID) + .def("UnFreeze", (void(Lua_Client::*)(void))&Lua_Client::UnFreeze) + .def("Undye", (void(Lua_Client::*)(void))&Lua_Client::Undye) + .def("UnmemSpell", (void(Lua_Client::*)(int))&Lua_Client::UnmemSpell) + .def("UnmemSpell", (void(Lua_Client::*)(int,bool))&Lua_Client::UnmemSpell) + .def("UnmemSpellAll", (void(Lua_Client::*)(bool))&Lua_Client::UnmemSpellAll) + .def("UnmemSpellAll", (void(Lua_Client::*)(void))&Lua_Client::UnmemSpellAll) + .def("UnmemSpellBySpellID", (void(Lua_Client::*)(int32))&Lua_Client::UnmemSpellBySpellID) + .def("UnscribeSpell", (void(Lua_Client::*)(int))&Lua_Client::UnscribeSpell) + .def("UnscribeSpell", (void(Lua_Client::*)(int,bool))&Lua_Client::UnscribeSpell) + .def("UnscribeSpellAll", (void(Lua_Client::*)(bool))&Lua_Client::UnscribeSpellAll) + .def("UnscribeSpellAll", (void(Lua_Client::*)(void))&Lua_Client::UnscribeSpellAll) + .def("UntrainDisc", (void(Lua_Client::*)(int))&Lua_Client::UntrainDisc) + .def("UntrainDisc", (void(Lua_Client::*)(int,bool))&Lua_Client::UntrainDisc) + .def("UntrainDiscAll", (void(Lua_Client::*)(bool))&Lua_Client::UntrainDiscAll) + .def("UntrainDiscAll", (void(Lua_Client::*)(void))&Lua_Client::UntrainDiscAll) + .def("UntrainDiscBySpellID", (void(Lua_Client::*)(uint16))&Lua_Client::UntrainDiscBySpellID) + .def("UntrainDiscBySpellID", (void(Lua_Client::*)(uint16,bool))&Lua_Client::UntrainDiscBySpellID) + .def("UpdateGroupAAs", (void(Lua_Client::*)(int,uint32))&Lua_Client::UpdateGroupAAs) + .def("UpdateLDoNPoints", (void(Lua_Client::*)(uint32,int))&Lua_Client::UpdateLDoNPoints) + .def("UpdateTaskActivity", (void(Lua_Client::*)(int,int,int))&Lua_Client::UpdateTaskActivity) + .def("UseDiscipline", (bool(Lua_Client::*)(int,int))&Lua_Client::UseDiscipline) + .def("WorldKick", (void(Lua_Client::*)(void))&Lua_Client::WorldKick); } luabind::scope lua_register_inventory_where() { diff --git a/zone/lua_client.h b/zone/lua_client.h index 55b98087a..ee6881160 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -31,6 +31,7 @@ public: } void SendSound(); + void Sit(); void Save(); void Save(int commit_now); void SaveBackup(); @@ -41,6 +42,7 @@ public: bool IsLD(); void WorldKick(); void SendToGuildHall(); + void SendToInstance(std::string instance_type, std::string zone_short_name, uint32 instance_version, float x, float y, float z, float heading, std::string instance_identifier, uint32 duration); int GetAnon(); void SetAnon(uint8 anon_flag); int GetAFK(); @@ -55,12 +57,11 @@ public: bool GetGM(); void SetBaseClass(int v); void SetBaseRace(int v); - void SetBaseGender(int v); + void SetBaseGender(int v); int GetClassBitmask(); int GetRaceBitmask(); int GetBaseFace(); int GetLanguageSkill(int skill_id); - const char *GetLastName(); int GetLDoNPointsTheme(int theme); int GetBaseSTR(); int GetBaseSTA(); @@ -71,10 +72,18 @@ public: int GetBaseWIS(); int GetWeight(); uint32 GetEXP(); + double GetEXPModifier(uint32 zone_id); + double GetAAEXPModifier(uint32 zone_id); + void SetAAEXPModifier(uint32 zone_id, double aa_modifier); + void SetEXPModifier(uint32 zone_id, double exp_modifier); uint32 GetAAExp(); uint32 GetAAPercent(); uint32 GetTotalSecondsPlayed(); - void UpdateLDoNPoints(int points, uint32 theme); + void AddLDoNLoss(uint32 theme_id); + void AddLDoNWin(uint32 theme_id); + void RemoveLDoNLoss(uint32 theme_id); + void RemoveLDoNWin(uint32 theme_id); + void UpdateLDoNPoints(uint32 theme_id, int points); void SetDeity(int v); void AddEXP(uint32 add_exp); void AddEXP(uint32 add_exp, int conlevel); @@ -87,6 +96,7 @@ public: void SetBindPoint(int to_zone, int to_instance, float new_x); void SetBindPoint(int to_zone, int to_instance, float new_x, float new_y); void SetBindPoint(int to_zone, int to_instance, float new_x, float new_y, float new_z); + void SetBindPoint(int to_zone, int to_instance, float new_x, float new_y, float new_z, float new_heading); float GetBindX(); float GetBindX(int index); float GetBindY(); @@ -153,7 +163,9 @@ public: void UnmemSpellBySpellID(int32 spell_id); void UnmemSpellAll(); void UnmemSpellAll(bool update_client); + int FindEmptyMemSlot(); uint16 FindMemmedSpellBySlot(int slot); + int FindMemmedSpellBySpellID(uint16 spell_id); int MemmedCount(); luabind::object GetLearnableDisciplines(lua_State* L); luabind::object GetLearnableDisciplines(lua_State* L, uint8 min_level); @@ -166,15 +178,19 @@ public: luabind::object GetScribeableSpells(lua_State* L, uint8 min_level, uint8 max_level); void ScribeSpell(int spell_id, int slot); void ScribeSpell(int spell_id, int slot, bool update_client); + uint16 ScribeSpells(uint8 min_level, uint8 max_level); void UnscribeSpell(int slot); void UnscribeSpell(int slot, bool update_client); void UnscribeSpellAll(); void UnscribeSpellAll(bool update_client); void TrainDisc(int itemid); + uint16 LearnDisciplines(uint8 min_level, uint8 max_level); void TrainDiscBySpellID(int32 spell_id); int GetDiscSlotBySpellID(int32 spell_id); void UntrainDisc(int slot); void UntrainDisc(int slot, bool update_client); + void UntrainDiscBySpellID(uint16 spell_id); + void UntrainDiscBySpellID(uint16 spell_id, bool update_client); void UntrainDiscAll(); void UntrainDiscAll(bool update_client); bool IsStanding(); @@ -205,6 +221,7 @@ public: bool attuned); void SummonItem(uint32 item_id, int charges, uint32 aug1, uint32 aug2, uint32 aug3, uint32 aug4, uint32 aug5, bool attuned, int to_slot); + void SummonBaggedItems(uint32 bag_item_id, luabind::adl::object bag_items_table); void SetStats(int type, int value); void IncStats(int type, int value); void DropItem(int slot_id); @@ -224,6 +241,10 @@ public: void ResetTrade(); uint32 GetDisciplineTimer(uint32 timer_id); void ResetDisciplineTimer(uint32 timer_id); + void ResetCastbarCooldownBySlot(int slot); + void ResetAllCastbarCooldowns(); + void ResetCastbarCooldownBySpellID(uint32 spell_id); + void ResetAllDisciplineTimers(); bool UseDiscipline(int spell_id, int target_id); bool HasDisciplineLearned(uint16 spell_id); int GetCharacterFactionLevel(int faction_id); @@ -250,6 +271,7 @@ public: void SetStartZone(int zone_id, float x); void SetStartZone(int zone_id, float x, float y); void SetStartZone(int zone_id, float x, float y, float z); + void SetStartZone(int zone_id, float x, float y, float z, float heading); void KeyRingAdd(uint32 item); bool KeyRingCheck(uint32 item); void AddPVPPoints(uint32 points); @@ -260,6 +282,7 @@ public: uint32 GetRadiantCrystals(); uint32 GetEbonCrystals(); void QuestReadBook(const char *text, int type); + void ReadBookByName(std::string book_name, uint8 book_type); void UpdateGroupAAs(int points, uint32 type); uint32 GetGroupPoints(); uint32 GetRaidPoints(); @@ -270,6 +293,9 @@ public: void SetEndurance(int endur); void SendOPTranslocateConfirm(Lua_Mob caster, int spell_id); uint32 GetIP(); + std::string GetIPString(); + int GetIPExemption(); + void SetIPExemption(int exemption_amount); void AddLevelBasedExp(int exp_pct); void AddLevelBasedExp(int exp_pct, int max_level); void AddLevelBasedExp(int exp_pct, int max_level, bool ignore_mods); @@ -281,6 +307,8 @@ public: void ClearCompassMark(); int GetNextAvailableSpellBookSlot(); int GetNextAvailableSpellBookSlot(int start); + int GetNextAvailableDisciplineSlot(); + int GetNextAvailableDisciplineSlot(int starting_slot); uint32 GetSpellIDByBookSlot(int book_slot); int FindSpellBookSlotBySpellID(int spell_id); void UpdateTaskActivity(int task, int activity, int count); @@ -350,6 +378,18 @@ public: void DisableAreaEndRegen(); void EnableAreaRegens(int value); void DisableAreaRegens(); + void SetHideMe(bool hide_me_state); + void Popup(const char* title, const char* text); + void Popup(const char* title, const char* text, uint32 popup_id); + void Popup(const char* title, const char* text, uint32 popup_id, uint32 negative_id); + void Popup(const char* title, const char* text, uint32 popup_id, uint32 negative_id, uint32 button_type); + void Popup(const char* title, const char* text, uint32 popup_id, uint32 negative_id, uint32 button_type, uint32 duration); + void Popup(const char* title, const char* text, uint32 popup_id, uint32 negative_id, uint32 button_type, uint32 duration, const char* button_name_one, const char* button_name_two); + void Popup(const char* title, const char* text, uint32 popup_id, uint32 negative_id, uint32 button_type, uint32 duration, const char* button_name_one, const char* button_name_two, uint32 sound_controls); + int CountItem(uint32 item_id); + void RemoveItem(uint32 item_id); + void RemoveItem(uint32 item_id, uint32 quantity); + void SetGMStatus(uint32 newStatus); void SetPrimaryWeaponOrnamentation(uint32 model_id); void SetSecondaryWeaponOrnamentation(uint32 model_id); @@ -357,6 +397,8 @@ public: void SetClientMaxLevel(int value); int GetClientMaxLevel(); + void DialogueWindow(std::string markdown); + Lua_Expedition CreateExpedition(luabind::object expedition_info); Lua_Expedition CreateExpedition(std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, uint32 min_players, uint32 max_players); Lua_Expedition CreateExpedition(std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, uint32 min_players, uint32 max_players, bool disable_messages); @@ -378,6 +420,7 @@ public: void MovePCDynamicZone(std::string zone_name); void MovePCDynamicZone(std::string zone_name, int zone_version); void MovePCDynamicZone(std::string zone_name, int zone_version, bool msg_if_invalid); + void CreateTaskDynamicZone(int task_id, luabind::object dz_table); void Fling(float value, float target_x, float target_y, float target_z); void Fling(float value, float target_x, float target_y, float target_z, bool ignore_los); void Fling(float value, float target_x, float target_y, float target_z, bool ignore_los, bool clipping); diff --git a/zone/lua_corpse.cpp b/zone/lua_corpse.cpp index 789555a54..d1f5039c7 100644 --- a/zone/lua_corpse.cpp +++ b/zone/lua_corpse.cpp @@ -2,11 +2,16 @@ #include "lua.hpp" #include +#include #include "corpse.h" #include "lua_corpse.h" #include "lua_client.h" +struct Lua_Corpse_Loot_List { + std::vector entries; +}; + uint32 Lua_Corpse::GetCharID() { Lua_Safe_Call_Int(); return self->GetCharID(); @@ -152,40 +157,95 @@ void Lua_Corpse::AddLooter(Lua_Mob who) { self->AddLooter(who); } +bool Lua_Corpse::HasItem(uint32 item_id) { + Lua_Safe_Call_Bool(); + return self->HasItem(item_id); +} + +uint16 Lua_Corpse::CountItem(uint32 item_id) { + Lua_Safe_Call_Int(); + return self->CountItem(item_id); +} + +uint32 Lua_Corpse::GetItemIDBySlot(uint16 loot_slot) { + Lua_Safe_Call_Int(); + return self->GetItemIDBySlot(loot_slot); +} + +uint16 Lua_Corpse::GetFirstSlotByItemID(uint32 item_id) { + Lua_Safe_Call_Int(); + return self->GetFirstSlotByItemID(item_id); +} + +void Lua_Corpse::RemoveItemByID(uint32 item_id) { + Lua_Safe_Call_Void(); + self->RemoveItemByID(item_id); +} + +void Lua_Corpse::RemoveItemByID(uint32 item_id, int quantity) { + Lua_Safe_Call_Void(); + self->RemoveItemByID(item_id, quantity); +} + +Lua_Corpse_Loot_List Lua_Corpse::GetLootList(lua_State* L) { + Lua_Safe_Call_Class(Lua_Corpse_Loot_List); + Lua_Corpse_Loot_List ret; + auto loot_list = self->GetLootList(); + + for (auto item_id : loot_list) { + ret.entries.push_back(item_id); + } + + return ret; +} + luabind::scope lua_register_corpse() { return luabind::class_("Corpse") - .def(luabind::constructor<>()) - .property("null", &Lua_Corpse::Null) - .property("valid", &Lua_Corpse::Valid) - .def("GetCharID", (uint32(Lua_Corpse::*)(void))&Lua_Corpse::GetCharID) - .def("GetDecayTime", (uint32(Lua_Corpse::*)(void))&Lua_Corpse::GetDecayTime) - .def("Lock", (void(Lua_Corpse::*)(void))&Lua_Corpse::Lock) - .def("UnLock", (void(Lua_Corpse::*)(void))&Lua_Corpse::UnLock) - .def("IsLocked", (bool(Lua_Corpse::*)(void))&Lua_Corpse::IsLocked) - .def("ResetLooter", (void(Lua_Corpse::*)(void))&Lua_Corpse::ResetLooter) - .def("GetDBID", (uint32(Lua_Corpse::*)(void))&Lua_Corpse::GetDBID) - .def("IsRezzed", (bool(Lua_Corpse::*)(void))&Lua_Corpse::IsRezzed) - .def("GetOwnerName", (const char *(Lua_Corpse::*)(void))&Lua_Corpse::GetOwnerName) - .def("Save", (bool(Lua_Corpse::*)(void))&Lua_Corpse::Save) - .def("Delete", (void(Lua_Corpse::*)(void))&Lua_Corpse::Delete) - .def("Bury", (void(Lua_Corpse::*)(void))&Lua_Corpse::Bury) - .def("Depop", (void(Lua_Corpse::*)(void))&Lua_Corpse::Depop) - .def("CountItems", (uint32(Lua_Corpse::*)(void))&Lua_Corpse::CountItems) - .def("AddItem", (void(Lua_Corpse::*)(uint32, uint16, int16, uint32, uint32, uint32, uint32, uint32))&Lua_Corpse::AddItem) - .def("GetWornItem", (uint32(Lua_Corpse::*)(int16))&Lua_Corpse::GetWornItem) - .def("RemoveItem", (void(Lua_Corpse::*)(uint16))&Lua_Corpse::RemoveItem) - .def("SetCash", (void(Lua_Corpse::*)(uint32, uint32, uint32, uint32))&Lua_Corpse::SetCash) - .def("RemoveCash", (void(Lua_Corpse::*)(void))&Lua_Corpse::RemoveCash) - .def("IsEmpty", (bool(Lua_Corpse::*)(void))&Lua_Corpse::IsEmpty) - .def("SetDecayTimer", (void(Lua_Corpse::*)(uint32))&Lua_Corpse::SetDecayTimer) - .def("CanMobLoot", (bool(Lua_Corpse::*)(int))&Lua_Corpse::CanMobLoot) - .def("AllowMobLoot", (void(Lua_Corpse::*)(Lua_Mob, uint8))&Lua_Corpse::AllowMobLoot) - .def("Summon", (bool(Lua_Corpse::*)(Lua_Client, bool, bool))&Lua_Corpse::Summon) - .def("GetCopper", (uint32(Lua_Corpse::*)(void))&Lua_Corpse::GetCopper) - .def("GetSilver", (uint32(Lua_Corpse::*)(void))&Lua_Corpse::GetSilver) - .def("GetGold", (uint32(Lua_Corpse::*)(void))&Lua_Corpse::GetGold) - .def("GetPlatinum", (uint32(Lua_Corpse::*)(void))&Lua_Corpse::GetPlatinum) - .def("AddLooter", (void(Lua_Corpse::*)(Lua_Mob))&Lua_Corpse::AddLooter); + .def(luabind::constructor<>()) + .property("null", &Lua_Corpse::Null) + .property("valid", &Lua_Corpse::Valid) + .def("AddItem", (void(Lua_Corpse::*)(uint32, uint16, int16, uint32, uint32, uint32, uint32, uint32))&Lua_Corpse::AddItem) + .def("AddLooter", (void(Lua_Corpse::*)(Lua_Mob))&Lua_Corpse::AddLooter) + .def("AllowMobLoot", (void(Lua_Corpse::*)(Lua_Mob, uint8))&Lua_Corpse::AllowMobLoot) + .def("Bury", (void(Lua_Corpse::*)(void))&Lua_Corpse::Bury) + .def("CanMobLoot", (bool(Lua_Corpse::*)(int))&Lua_Corpse::CanMobLoot) + .def("CountItem", (uint16(Lua_Corpse::*)(uint32))&Lua_Corpse::CountItem) + .def("CountItems", (uint32(Lua_Corpse::*)(void))&Lua_Corpse::CountItems) + .def("Delete", (void(Lua_Corpse::*)(void))&Lua_Corpse::Delete) + .def("Depop", (void(Lua_Corpse::*)(void))&Lua_Corpse::Depop) + .def("GetCharID", (uint32(Lua_Corpse::*)(void))&Lua_Corpse::GetCharID) + .def("GetCopper", (uint32(Lua_Corpse::*)(void))&Lua_Corpse::GetCopper) + .def("GetDBID", (uint32(Lua_Corpse::*)(void))&Lua_Corpse::GetDBID) + .def("GetDecayTime", (uint32(Lua_Corpse::*)(void))&Lua_Corpse::GetDecayTime) + .def("GetFirstSlotByItemID", (uint16(Lua_Corpse::*)(uint32))&Lua_Corpse::GetFirstSlotByItemID) + .def("GetGold", (uint32(Lua_Corpse::*)(void))&Lua_Corpse::GetGold) + .def("GetItemIDBySlot", (uint32(Lua_Corpse::*)(uint16))&Lua_Corpse::GetItemIDBySlot) + .def("GetLootList", (Lua_Corpse_Loot_List(Lua_Corpse::*)(lua_State* L))&Lua_Corpse::GetLootList) + .def("GetOwnerName", (const char *(Lua_Corpse::*)(void))&Lua_Corpse::GetOwnerName) + .def("GetPlatinum", (uint32(Lua_Corpse::*)(void))&Lua_Corpse::GetPlatinum) + .def("GetSilver", (uint32(Lua_Corpse::*)(void))&Lua_Corpse::GetSilver) + .def("GetWornItem", (uint32(Lua_Corpse::*)(int16))&Lua_Corpse::GetWornItem) + .def("HasItem", (bool(Lua_Corpse::*)(uint32))&Lua_Corpse::HasItem) + .def("IsEmpty", (bool(Lua_Corpse::*)(void))&Lua_Corpse::IsEmpty) + .def("IsLocked", (bool(Lua_Corpse::*)(void))&Lua_Corpse::IsLocked) + .def("IsRezzed", (bool(Lua_Corpse::*)(void))&Lua_Corpse::IsRezzed) + .def("Lock", (void(Lua_Corpse::*)(void))&Lua_Corpse::Lock) + .def("RemoveCash", (void(Lua_Corpse::*)(void))&Lua_Corpse::RemoveCash) + .def("RemoveItem", (void(Lua_Corpse::*)(uint16))&Lua_Corpse::RemoveItem) + .def("RemoveItemByID", (void(Lua_Corpse::*)(uint32))&Lua_Corpse::RemoveItemByID) + .def("RemoveItemByID", (void(Lua_Corpse::*)(uint32,int))&Lua_Corpse::RemoveItemByID) + .def("ResetLooter", (void(Lua_Corpse::*)(void))&Lua_Corpse::ResetLooter) + .def("Save", (bool(Lua_Corpse::*)(void))&Lua_Corpse::Save) + .def("SetCash", (void(Lua_Corpse::*)(uint32, uint32, uint32, uint32))&Lua_Corpse::SetCash) + .def("SetDecayTimer", (void(Lua_Corpse::*)(uint32))&Lua_Corpse::SetDecayTimer) + .def("Summon", (bool(Lua_Corpse::*)(Lua_Client, bool, bool))&Lua_Corpse::Summon) + .def("UnLock", (void(Lua_Corpse::*)(void))&Lua_Corpse::UnLock); + +} + +luabind::scope lua_register_corpse_loot_list() { + return luabind::class_("CorpseLootList") + .def_readwrite("entries", &Lua_Corpse_Loot_List::entries, luabind::return_stl_iterator); } #endif diff --git a/zone/lua_corpse.h b/zone/lua_corpse.h index dfb3b4b6d..0df694b51 100644 --- a/zone/lua_corpse.h +++ b/zone/lua_corpse.h @@ -6,12 +6,14 @@ class Corpse; class Lua_Client; +struct Lua_Corpse_Loot_List; namespace luabind { struct scope; } luabind::scope lua_register_corpse(); +luabind::scope lua_register_corpse_loot_list(); class Lua_Corpse : public Lua_Mob { @@ -42,6 +44,8 @@ public: void AddItem(uint32 itemnum, uint16 charges, int16 slot, uint32 aug1, uint32 aug2, uint32 aug3, uint32 aug4, uint32 aug5); uint32 GetWornItem(int16 equipSlot); void RemoveItem(uint16 lootslot); + void RemoveItemByID(uint32 item_id); + void RemoveItemByID(uint32 item_id, int quantity); void SetCash(uint32 copper, uint32 silver, uint32 gold, uint32 platinum); void RemoveCash(); bool IsEmpty(); @@ -54,6 +58,11 @@ public: uint32 GetGold(); uint32 GetPlatinum(); void AddLooter(Lua_Mob who); + bool HasItem(uint32 item_id); + uint16 CountItem(uint32 item_id); + uint32 GetItemIDBySlot(uint16 loot_slot); + uint16 GetFirstSlotByItemID(uint32 item_id); + Lua_Corpse_Loot_List GetLootList(lua_State* L); }; #endif diff --git a/zone/lua_door.cpp b/zone/lua_door.cpp index 1b39d3e54..bb10e4035 100644 --- a/zone/lua_door.cpp +++ b/zone/lua_door.cpp @@ -178,41 +178,41 @@ void Lua_Door::ForceClose(Lua_Mob sender, bool alt_mode) { luabind::scope lua_register_door() { return luabind::class_("Door") - .def(luabind::constructor<>()) - .property("null", &Lua_Door::Null) - .property("valid", &Lua_Door::Valid) - .def("SetDoorName", (void(Lua_Door::*)(const char*))&Lua_Door::SetDoorName) - .def("GetDoorName", (const char*(Lua_Door::*)(void))&Lua_Door::GetDoorName) - .def("GetX", (float(Lua_Door::*)(void))&Lua_Door::GetX) - .def("GetY", (float(Lua_Door::*)(void))&Lua_Door::GetY) - .def("GetZ", (float(Lua_Door::*)(void))&Lua_Door::GetZ) - .def("GetHeading", (float(Lua_Door::*)(void))&Lua_Door::GetHeading) - .def("SetX", (void(Lua_Door::*)(float))&Lua_Door::SetX) - .def("SetY", (void(Lua_Door::*)(float))&Lua_Door::SetY) - .def("SetZ", (void(Lua_Door::*)(float))&Lua_Door::SetZ) - .def("SetHeading", (void(Lua_Door::*)(float))&Lua_Door::SetHeading) - .def("SetLocation", (void(Lua_Door::*)(float,float,float))&Lua_Door::SetLocation) - .def("GetDoorDBID", (uint32(Lua_Door::*)(void))&Lua_Door::GetDoorDBID) - .def("GetDoorID", (uint32(Lua_Door::*)(void))&Lua_Door::GetDoorID) - .def("SetSize", (void(Lua_Door::*)(uint32))&Lua_Door::SetSize) - .def("GetSize", (uint32(Lua_Door::*)(void))&Lua_Door::GetSize) - .def("SetIncline", (void(Lua_Door::*)(uint32))&Lua_Door::SetIncline) - .def("GetIncline", (uint32(Lua_Door::*)(void))&Lua_Door::GetIncline) - .def("SetOpenType", (void(Lua_Door::*)(uint32))&Lua_Door::SetOpenType) - .def("GetOpenType", (uint32(Lua_Door::*)(void))&Lua_Door::GetOpenType) - .def("SetDisableTimer", (void(Lua_Door::*)(bool))&Lua_Door::SetDisableTimer) - .def("GetDisableTimer", (bool(Lua_Door::*)(void))&Lua_Door::GetDisableTimer) - .def("SetLockPick", (void(Lua_Door::*)(uint32))&Lua_Door::SetLockPick) - .def("GetLockPick", (uint32(Lua_Door::*)(void))&Lua_Door::GetLockPick) - .def("SetKeyItem", (void(Lua_Door::*)(uint32))&Lua_Door::SetKeyItem) - .def("GetKeyItem", (uint32(Lua_Door::*)(void))&Lua_Door::GetKeyItem) - .def("SetNoKeyring", (void(Lua_Door::*)(int))&Lua_Door::SetNoKeyring) - .def("GetNoKeyring", (int(Lua_Door::*)(void))&Lua_Door::GetNoKeyring) - .def("CreateDatabaseEntry", (void(Lua_Door::*)(void))&Lua_Door::CreateDatabaseEntry) - .def("ForceOpen", (void(Lua_Door::*)(Lua_Mob))&Lua_Door::ForceOpen) - .def("ForceOpen", (void(Lua_Door::*)(Lua_Mob,bool))&Lua_Door::ForceOpen) - .def("ForceClose", (void(Lua_Door::*)(Lua_Mob))&Lua_Door::ForceClose) - .def("ForceClose", (void(Lua_Door::*)(Lua_Mob,bool))&Lua_Door::ForceClose); + .def(luabind::constructor<>()) + .property("null", &Lua_Door::Null) + .property("valid", &Lua_Door::Valid) + .def("CreateDatabaseEntry", (void(Lua_Door::*)(void))&Lua_Door::CreateDatabaseEntry) + .def("ForceClose", (void(Lua_Door::*)(Lua_Mob))&Lua_Door::ForceClose) + .def("ForceClose", (void(Lua_Door::*)(Lua_Mob,bool))&Lua_Door::ForceClose) + .def("ForceOpen", (void(Lua_Door::*)(Lua_Mob))&Lua_Door::ForceOpen) + .def("ForceOpen", (void(Lua_Door::*)(Lua_Mob,bool))&Lua_Door::ForceOpen) + .def("GetDisableTimer", (bool(Lua_Door::*)(void))&Lua_Door::GetDisableTimer) + .def("GetDoorDBID", (uint32(Lua_Door::*)(void))&Lua_Door::GetDoorDBID) + .def("GetDoorID", (uint32(Lua_Door::*)(void))&Lua_Door::GetDoorID) + .def("GetDoorName", (const char*(Lua_Door::*)(void))&Lua_Door::GetDoorName) + .def("GetHeading", (float(Lua_Door::*)(void))&Lua_Door::GetHeading) + .def("GetIncline", (uint32(Lua_Door::*)(void))&Lua_Door::GetIncline) + .def("GetKeyItem", (uint32(Lua_Door::*)(void))&Lua_Door::GetKeyItem) + .def("GetLockPick", (uint32(Lua_Door::*)(void))&Lua_Door::GetLockPick) + .def("GetNoKeyring", (int(Lua_Door::*)(void))&Lua_Door::GetNoKeyring) + .def("GetOpenType", (uint32(Lua_Door::*)(void))&Lua_Door::GetOpenType) + .def("GetSize", (uint32(Lua_Door::*)(void))&Lua_Door::GetSize) + .def("GetX", (float(Lua_Door::*)(void))&Lua_Door::GetX) + .def("GetY", (float(Lua_Door::*)(void))&Lua_Door::GetY) + .def("GetZ", (float(Lua_Door::*)(void))&Lua_Door::GetZ) + .def("SetDisableTimer", (void(Lua_Door::*)(bool))&Lua_Door::SetDisableTimer) + .def("SetDoorName", (void(Lua_Door::*)(const char*))&Lua_Door::SetDoorName) + .def("SetHeading", (void(Lua_Door::*)(float))&Lua_Door::SetHeading) + .def("SetIncline", (void(Lua_Door::*)(uint32))&Lua_Door::SetIncline) + .def("SetKeyItem", (void(Lua_Door::*)(uint32))&Lua_Door::SetKeyItem) + .def("SetLocation", (void(Lua_Door::*)(float,float,float))&Lua_Door::SetLocation) + .def("SetLockPick", (void(Lua_Door::*)(uint32))&Lua_Door::SetLockPick) + .def("SetNoKeyring", (void(Lua_Door::*)(int))&Lua_Door::SetNoKeyring) + .def("SetOpenType", (void(Lua_Door::*)(uint32))&Lua_Door::SetOpenType) + .def("SetSize", (void(Lua_Door::*)(uint32))&Lua_Door::SetSize) + .def("SetX", (void(Lua_Door::*)(float))&Lua_Door::SetX) + .def("SetY", (void(Lua_Door::*)(float))&Lua_Door::SetY) + .def("SetZ", (void(Lua_Door::*)(float))&Lua_Door::SetZ); } #endif diff --git a/zone/lua_entity.cpp b/zone/lua_entity.cpp index 725e78f5e..045a7e6a7 100644 --- a/zone/lua_entity.cpp +++ b/zone/lua_entity.cpp @@ -12,6 +12,10 @@ #include "lua_object.h" #include "lua_door.h" +#ifdef BOTS +#include "lua_bot.h" +#endif + bool Lua_Entity::IsClient() { Lua_Safe_Call_Bool(); return self->IsClient(); @@ -118,31 +122,42 @@ Lua_Door Lua_Entity::CastToDoor() { return Lua_Door(m); } +#ifdef BOTS +Lua_Bot Lua_Entity::CastToBot() { + void *d = GetLuaPtrData(); + Bot *b = reinterpret_cast(d); + return Lua_Bot(b); +} +#endif + luabind::scope lua_register_entity() { return luabind::class_("Entity") - .def(luabind::constructor<>()) - .property("null", &Lua_Entity::Null) - .property("valid", &Lua_Entity::Valid) - .def("IsClient", &Lua_Entity::IsClient) - .def("IsNPC", &Lua_Entity::IsNPC) - .def("IsMob", &Lua_Entity::IsMob) - .def("IsMerc", &Lua_Entity::IsMerc) - .def("IsCorpse", &Lua_Entity::IsCorpse) - .def("IsPlayerCorpse", &Lua_Entity::IsPlayerCorpse) - .def("IsNPCCorpse", &Lua_Entity::IsNPCCorpse) - .def("IsObject", &Lua_Entity::IsObject) - .def("IsDoor", &Lua_Entity::IsDoor) - .def("IsTrap", &Lua_Entity::IsTrap) - .def("IsBeacon", &Lua_Entity::IsBeacon) - .def("IsEncounter", &Lua_Entity::IsEncounter) - .def("IsBot", &Lua_Entity::IsBot) - .def("GetID", &Lua_Entity::GetID) - .def("CastToClient", &Lua_Entity::CastToClient) - .def("CastToNPC", &Lua_Entity::CastToNPC) - .def("CastToMob", &Lua_Entity::CastToMob) - .def("CastToCorpse", &Lua_Entity::CastToCorpse) - .def("CastToObject", &Lua_Entity::CastToObject) - .def("CastToDoor", &Lua_Entity::CastToDoor); + .def(luabind::constructor<>()) + .property("null", &Lua_Entity::Null) + .property("valid", &Lua_Entity::Valid) +#ifdef BOTS + .def("CastToBot", &Lua_Entity::CastToBot) +#endif + .def("CastToClient", &Lua_Entity::CastToClient) + .def("CastToCorpse", &Lua_Entity::CastToCorpse) + .def("CastToDoor", &Lua_Entity::CastToDoor) + .def("CastToMob", &Lua_Entity::CastToMob) + .def("CastToNPC", &Lua_Entity::CastToNPC) + .def("CastToObject", &Lua_Entity::CastToObject) + .def("GetID", &Lua_Entity::GetID) + .def("IsBeacon", &Lua_Entity::IsBeacon) + .def("IsBot", &Lua_Entity::IsBot) + .def("IsClient", &Lua_Entity::IsClient) + .def("IsCorpse", &Lua_Entity::IsCorpse) + .def("IsDoor", &Lua_Entity::IsDoor) + .def("IsEncounter", &Lua_Entity::IsEncounter) + .def("IsMerc", &Lua_Entity::IsMerc) + .def("IsMob", &Lua_Entity::IsMob) + .def("IsNPC", &Lua_Entity::IsNPC) + .def("IsNPCCorpse", &Lua_Entity::IsNPCCorpse) + .def("IsObject", &Lua_Entity::IsObject) + .def("IsPlayerCorpse", &Lua_Entity::IsPlayerCorpse) + .def("IsTrap", &Lua_Entity::IsTrap); } #endif diff --git a/zone/lua_entity.h b/zone/lua_entity.h index b45208581..4954d13ff 100644 --- a/zone/lua_entity.h +++ b/zone/lua_entity.h @@ -6,6 +6,9 @@ class Entity; class Lua_Client; +#ifdef BOTS +class Lua_Bot; +#endif class Lua_NPC; class Lua_Mob; struct Lua_HateList; @@ -54,6 +57,9 @@ public: Lua_Corpse CastToCorpse(); Lua_Object CastToObject(); Lua_Door CastToDoor(); +#ifdef BOTS + Lua_Bot CastToBot(); +#endif }; #endif diff --git a/zone/lua_entity_list.cpp b/zone/lua_entity_list.cpp index e4f2b207d..9ee08ecbf 100644 --- a/zone/lua_entity_list.cpp +++ b/zone/lua_entity_list.cpp @@ -17,6 +17,10 @@ #include "lua_raid.h" #include "lua_spawn.h" +#ifdef BOTS +#include "lua_bot.h" +#endif + struct Lua_Mob_List { std::vector entries; }; @@ -29,6 +33,12 @@ struct Lua_Client_List { std::vector entries; }; +#ifdef BOTS +struct Lua_Bot_List { + std::vector entries; +}; +#endif + struct Lua_Corpse_List { std::vector entries; }; @@ -207,7 +217,12 @@ void Lua_EntityList::Message(uint32 guild_dbid, uint32 type, const char *message void Lua_EntityList::MessageStatus(uint32 guild_dbid, int min_status, uint32 type, const char *message) { Lua_Safe_Call_Void(); - self->MessageStatus(guild_dbid, min_status, type, message); + self->MessageStatus( + guild_dbid, + min_status, + type, + message + ); } void Lua_EntityList::MessageClose(Lua_Mob sender, bool skip_sender, float dist, uint32 type, const char *message) { @@ -262,12 +277,12 @@ void Lua_EntityList::SignalMobsByNPCID(uint32 npc_id, int signal) { self->SignalMobsByNPCID(npc_id, signal); } -int Lua_EntityList::DeleteNPCCorpses() { +uint32 Lua_EntityList::DeleteNPCCorpses() { Lua_Safe_Call_Int(); return self->DeleteNPCCorpses(); } -int Lua_EntityList::DeletePlayerCorpses() { +uint32 Lua_EntityList::DeletePlayerCorpses() { Lua_Safe_Call_Int(); return self->DeletePlayerCorpses(); } @@ -345,6 +360,29 @@ Lua_Client_List Lua_EntityList::GetClientList() { return ret; } +#ifdef BOTS +Lua_Bot Lua_EntityList::GetBotByID(uint32 bot_id) { + Lua_Safe_Call_Class(Lua_Bot); + return Lua_Bot(self->GetBotByBotID(bot_id)); +} + +Lua_Bot Lua_EntityList::GetBotByName(std::string bot_name) { + Lua_Safe_Call_Class(Lua_Bot); + return Lua_Bot(self->GetBotByBotName(bot_name)); +} + +Lua_Bot_List Lua_EntityList::GetBotList() { + Lua_Safe_Call_Class(Lua_Bot_List); + Lua_Bot_List ret; + auto &bot_list = self->GetBotList(); + for (auto bot_iterator : bot_list) { + ret.entries.push_back(Lua_Bot(bot_iterator)); + } + + return ret; +} +#endif + Lua_Client_List Lua_EntityList::GetShuffledClientList() { Lua_Safe_Call_Class(Lua_Client_List); Lua_Client_List ret; @@ -444,107 +482,119 @@ void Lua_EntityList::ChannelMessage(Lua_Mob from, int channel_num, int language, luabind::scope lua_register_entity_list() { return luabind::class_("EntityList") - .def(luabind::constructor<>()) - .property("null", &Lua_EntityList::Null) - .property("valid", &Lua_EntityList::Valid) - .def("GetMobID", (Lua_Mob(Lua_EntityList::*)(int))&Lua_EntityList::GetMobID) - .def("GetMob", (Lua_Mob(Lua_EntityList::*)(const char*))&Lua_EntityList::GetMob) - .def("GetMob", (Lua_Mob(Lua_EntityList::*)(int))&Lua_EntityList::GetMob) - .def("GetMobByNpcTypeID", (Lua_Mob(Lua_EntityList::*)(int))&Lua_EntityList::GetMobByNpcTypeID) - .def("IsMobSpawnedByNpcTypeID", (bool(Lua_EntityList::*)(int))&Lua_EntityList::IsMobSpawnedByNpcTypeID) - .def("GetNPCByID", (Lua_NPC(Lua_EntityList::*)(int))&Lua_EntityList::GetNPCByID) - .def("GetNPCByNPCTypeID", (Lua_NPC(Lua_EntityList::*)(int))&Lua_EntityList::GetNPCByNPCTypeID) - .def("GetNPCBySpawnID", (Lua_NPC(Lua_EntityList::*)(int))&Lua_EntityList::GetNPCBySpawnID) - .def("GetClientByName", (Lua_Client(Lua_EntityList::*)(const char*))&Lua_EntityList::GetClientByName) - .def("GetClientByAccID", (Lua_Client(Lua_EntityList::*)(uint32))&Lua_EntityList::GetClientByAccID) - .def("GetClientByID", (Lua_Client(Lua_EntityList::*)(int))&Lua_EntityList::GetClientByID) - .def("GetClientByCharID", (Lua_Client(Lua_EntityList::*)(uint32))&Lua_EntityList::GetClientByCharID) - .def("GetClientByWID", (Lua_Client(Lua_EntityList::*)(uint32))&Lua_EntityList::GetClientByWID) - .def("GetObjectByID", (Lua_Object(Lua_EntityList::*)(int))&Lua_EntityList::GetObjectByID) - .def("GetObjectByDBID", (Lua_Object(Lua_EntityList::*)(uint32))&Lua_EntityList::GetObjectByDBID) - .def("GetDoorsByID", (Lua_Door(Lua_EntityList::*)(int))&Lua_EntityList::GetDoorsByID) - .def("GetDoorsByDBID", (Lua_Door(Lua_EntityList::*)(uint32))&Lua_EntityList::GetDoorsByDBID) - .def("GetDoorsByDoorID", (Lua_Door(Lua_EntityList::*)(uint32))&Lua_EntityList::GetDoorsByDoorID) - .def("FindDoor", (Lua_Door(Lua_EntityList::*)(uint32))&Lua_EntityList::FindDoor) - .def("GetGroupByMob", (Lua_Group(Lua_EntityList::*)(Lua_Mob))&Lua_EntityList::GetGroupByMob) - .def("GetGroupByClient", (Lua_Group(Lua_EntityList::*)(Lua_Client))&Lua_EntityList::GetGroupByClient) - .def("GetGroupByID", (Lua_Group(Lua_EntityList::*)(int))&Lua_EntityList::GetGroupByID) - .def("GetGroupByLeaderName", (Lua_Group(Lua_EntityList::*)(const char*))&Lua_EntityList::GetGroupByLeaderName) - .def("GetRaidByID", (Lua_Raid(Lua_EntityList::*)(int))&Lua_EntityList::GetRaidByID) - .def("GetRaidByClient", (Lua_Raid(Lua_EntityList::*)(Lua_Client))&Lua_EntityList::GetRaidByClient) - .def("GetCorpseByOwner", (Lua_Corpse(Lua_EntityList::*)(Lua_Client))&Lua_EntityList::GetCorpseByOwner) - .def("GetCorpseByID", (Lua_Corpse(Lua_EntityList::*)(int))&Lua_EntityList::GetCorpseByID) - .def("GetCorpseByName", (Lua_Corpse(Lua_EntityList::*)(const char*))&Lua_EntityList::GetCorpseByName) - .def("GetSpawnByID", (Lua_Spawn(Lua_EntityList::*)(uint32))&Lua_EntityList::GetSpawnByID) - .def("ClearClientPetitionQueue", (void(Lua_EntityList::*)(void))&Lua_EntityList::ClearClientPetitionQueue) - .def("CanAddHateForMob", (bool(Lua_EntityList::*)(Lua_Mob))&Lua_EntityList::CanAddHateForMob) - .def("Message", (void(Lua_EntityList::*)(uint32, uint32, const char*))&Lua_EntityList::Message) - .def("MessageStatus", (void(Lua_EntityList::*)(uint32, uint32, uint32, const char*))&Lua_EntityList::MessageStatus) - .def("MessageClose", (void(Lua_EntityList::*)(Lua_Mob, bool, float, uint32, const char*))&Lua_EntityList::MessageClose) - .def("FilteredMessageClose", &Lua_EntityList::FilteredMessageClose) - .def("RemoveFromTargets", (void(Lua_EntityList::*)(Lua_Mob))&Lua_EntityList::RemoveFromTargets) - .def("RemoveFromTargets", (void(Lua_EntityList::*)(Lua_Mob, bool))&Lua_EntityList::RemoveFromTargets) - .def("ReplaceWithTarget", (void(Lua_EntityList::*)(Lua_Mob, Lua_Mob))&Lua_EntityList::ReplaceWithTarget) - .def("OpenDoorsNear", (void(Lua_EntityList::*)(Lua_Mob))&Lua_EntityList::OpenDoorsNear) - .def("MakeNameUnique", (std::string(Lua_EntityList::*)(const char*))&Lua_EntityList::MakeNameUnique) - .def("RemoveNumbers", (std::string(Lua_EntityList::*)(const char*))&Lua_EntityList::RemoveNumbers) - .def("SignalMobsByNPCID", (void(Lua_EntityList::*)(uint32, int))&Lua_EntityList::SignalMobsByNPCID) - .def("DeleteNPCCorpses", (int(Lua_EntityList::*)(void))&Lua_EntityList::DeleteNPCCorpses) - .def("DeletePlayerCorpses", (int(Lua_EntityList::*)(void))&Lua_EntityList::DeletePlayerCorpses) - .def("HalveAggro", (void(Lua_EntityList::*)(Lua_Mob))&Lua_EntityList::HalveAggro) - .def("DoubleAggro", (void(Lua_EntityList::*)(Lua_Mob))&Lua_EntityList::DoubleAggro) - .def("ClearFeignAggro", (void(Lua_EntityList::*)(Lua_Mob))&Lua_EntityList::ClearFeignAggro) - .def("Fighting", (bool(Lua_EntityList::*)(Lua_Mob))&Lua_EntityList::Fighting) - .def("RemoveFromHateLists", (void(Lua_EntityList::*)(Lua_Mob))&Lua_EntityList::RemoveFromHateLists) - .def("RemoveFromHateLists", (void(Lua_EntityList::*)(Lua_Mob, bool))&Lua_EntityList::RemoveFromHateLists) - .def("MessageGroup", (void(Lua_EntityList::*)(Lua_Mob, bool, uint32, const char*))&Lua_EntityList::MessageGroup) - .def("GetRandomClient", (Lua_Client(Lua_EntityList::*)(float, float, float, float))&Lua_EntityList::GetRandomClient) - .def("GetRandomClient", (Lua_Client(Lua_EntityList::*)(float, float, float, float, Lua_Client))&Lua_EntityList::GetRandomClient) - .def("GetMobList", (Lua_Mob_List(Lua_EntityList::*)(void))&Lua_EntityList::GetMobList) - .def("GetClientList", (Lua_Client_List(Lua_EntityList::*)(void))&Lua_EntityList::GetClientList) - .def("GetShuffledClientList", (Lua_Client_List(Lua_EntityList::*)(void))&Lua_EntityList::GetShuffledClientList) - .def("GetNPCList", (Lua_NPC_List(Lua_EntityList::*)(void))&Lua_EntityList::GetNPCList) - .def("GetCorpseList", (Lua_Corpse_List(Lua_EntityList::*)(void))&Lua_EntityList::GetCorpseList) - .def("GetObjectList", (Lua_Object_List(Lua_EntityList::*)(void))&Lua_EntityList::GetObjectList) - .def("GetDoorsList", (Lua_Doors_List(Lua_EntityList::*)(void))&Lua_EntityList::GetDoorsList) - .def("GetSpawnList", (Lua_Spawn_List(Lua_EntityList::*)(void))&Lua_EntityList::GetSpawnList) - .def("SignalAllClients", (void(Lua_EntityList::*)(int))&Lua_EntityList::SignalAllClients) - .def("ChannelMessage", (void(Lua_EntityList::*)(Lua_Mob, int, int, const char*))&Lua_EntityList::ChannelMessage); + .def(luabind::constructor<>()) + .property("null", &Lua_EntityList::Null) + .property("valid", &Lua_EntityList::Valid) + .def("CanAddHateForMob", (bool(Lua_EntityList::*)(Lua_Mob))&Lua_EntityList::CanAddHateForMob) + .def("ChannelMessage", (void(Lua_EntityList::*)(Lua_Mob, int, int, const char*))&Lua_EntityList::ChannelMessage) + .def("ClearClientPetitionQueue", (void(Lua_EntityList::*)(void))&Lua_EntityList::ClearClientPetitionQueue) + .def("ClearFeignAggro", (void(Lua_EntityList::*)(Lua_Mob))&Lua_EntityList::ClearFeignAggro) + .def("DeleteNPCCorpses", (uint32(Lua_EntityList::*)(void))&Lua_EntityList::DeleteNPCCorpses) + .def("DeletePlayerCorpses", (uint32(Lua_EntityList::*)(void))&Lua_EntityList::DeletePlayerCorpses) + .def("DoubleAggro", (void(Lua_EntityList::*)(Lua_Mob))&Lua_EntityList::DoubleAggro) + .def("Fighting", (bool(Lua_EntityList::*)(Lua_Mob))&Lua_EntityList::Fighting) + .def("FilteredMessageClose", &Lua_EntityList::FilteredMessageClose) + .def("FindDoor", (Lua_Door(Lua_EntityList::*)(uint32))&Lua_EntityList::FindDoor) +#ifdef BOTS + .def("GetBotByID", (Lua_Bot(Lua_EntityList::*)(uint32))&Lua_EntityList::GetBotByID) + .def("GetBotByName", (Lua_Bot(Lua_EntityList::*)(std::string))&Lua_EntityList::GetBotByName) + .def("GetBotList", (Lua_Bot_List(Lua_EntityList::*)(void))&Lua_EntityList::GetBotList) +#endif + .def("GetClientByAccID", (Lua_Client(Lua_EntityList::*)(uint32))&Lua_EntityList::GetClientByAccID) + .def("GetClientByCharID", (Lua_Client(Lua_EntityList::*)(uint32))&Lua_EntityList::GetClientByCharID) + .def("GetClientByID", (Lua_Client(Lua_EntityList::*)(int))&Lua_EntityList::GetClientByID) + .def("GetClientByName", (Lua_Client(Lua_EntityList::*)(const char*))&Lua_EntityList::GetClientByName) + .def("GetClientByWID", (Lua_Client(Lua_EntityList::*)(uint32))&Lua_EntityList::GetClientByWID) + .def("GetClientList", (Lua_Client_List(Lua_EntityList::*)(void))&Lua_EntityList::GetClientList) + .def("GetCorpseByID", (Lua_Corpse(Lua_EntityList::*)(int))&Lua_EntityList::GetCorpseByID) + .def("GetCorpseByName", (Lua_Corpse(Lua_EntityList::*)(const char*))&Lua_EntityList::GetCorpseByName) + .def("GetCorpseByOwner", (Lua_Corpse(Lua_EntityList::*)(Lua_Client))&Lua_EntityList::GetCorpseByOwner) + .def("GetCorpseList", (Lua_Corpse_List(Lua_EntityList::*)(void))&Lua_EntityList::GetCorpseList) + .def("GetDoorsByDBID", (Lua_Door(Lua_EntityList::*)(uint32))&Lua_EntityList::GetDoorsByDBID) + .def("GetDoorsByDoorID", (Lua_Door(Lua_EntityList::*)(uint32))&Lua_EntityList::GetDoorsByDoorID) + .def("GetDoorsByID", (Lua_Door(Lua_EntityList::*)(int))&Lua_EntityList::GetDoorsByID) + .def("GetDoorsList", (Lua_Doors_List(Lua_EntityList::*)(void))&Lua_EntityList::GetDoorsList) + .def("GetGroupByClient", (Lua_Group(Lua_EntityList::*)(Lua_Client))&Lua_EntityList::GetGroupByClient) + .def("GetGroupByID", (Lua_Group(Lua_EntityList::*)(int))&Lua_EntityList::GetGroupByID) + .def("GetGroupByLeaderName", (Lua_Group(Lua_EntityList::*)(const char*))&Lua_EntityList::GetGroupByLeaderName) + .def("GetGroupByMob", (Lua_Group(Lua_EntityList::*)(Lua_Mob))&Lua_EntityList::GetGroupByMob) + .def("GetMob", (Lua_Mob(Lua_EntityList::*)(const char*))&Lua_EntityList::GetMob) + .def("GetMob", (Lua_Mob(Lua_EntityList::*)(int))&Lua_EntityList::GetMob) + .def("GetMobByNpcTypeID", (Lua_Mob(Lua_EntityList::*)(int))&Lua_EntityList::GetMobByNpcTypeID) + .def("GetMobID", (Lua_Mob(Lua_EntityList::*)(int))&Lua_EntityList::GetMobID) + .def("GetMobList", (Lua_Mob_List(Lua_EntityList::*)(void))&Lua_EntityList::GetMobList) + .def("GetNPCByID", (Lua_NPC(Lua_EntityList::*)(int))&Lua_EntityList::GetNPCByID) + .def("GetNPCByNPCTypeID", (Lua_NPC(Lua_EntityList::*)(int))&Lua_EntityList::GetNPCByNPCTypeID) + .def("GetNPCBySpawnID", (Lua_NPC(Lua_EntityList::*)(int))&Lua_EntityList::GetNPCBySpawnID) + .def("GetNPCList", (Lua_NPC_List(Lua_EntityList::*)(void))&Lua_EntityList::GetNPCList) + .def("GetObjectByDBID", (Lua_Object(Lua_EntityList::*)(uint32))&Lua_EntityList::GetObjectByDBID) + .def("GetObjectByID", (Lua_Object(Lua_EntityList::*)(int))&Lua_EntityList::GetObjectByID) + .def("GetObjectList", (Lua_Object_List(Lua_EntityList::*)(void))&Lua_EntityList::GetObjectList) + .def("GetRaidByClient", (Lua_Raid(Lua_EntityList::*)(Lua_Client))&Lua_EntityList::GetRaidByClient) + .def("GetRaidByID", (Lua_Raid(Lua_EntityList::*)(int))&Lua_EntityList::GetRaidByID) + .def("GetRandomClient", (Lua_Client(Lua_EntityList::*)(float, float, float, float))&Lua_EntityList::GetRandomClient) + .def("GetRandomClient", (Lua_Client(Lua_EntityList::*)(float, float, float, float, Lua_Client))&Lua_EntityList::GetRandomClient) + .def("GetShuffledClientList", (Lua_Client_List(Lua_EntityList::*)(void))&Lua_EntityList::GetShuffledClientList) + .def("GetSpawnByID", (Lua_Spawn(Lua_EntityList::*)(uint32))&Lua_EntityList::GetSpawnByID) + .def("GetSpawnList", (Lua_Spawn_List(Lua_EntityList::*)(void))&Lua_EntityList::GetSpawnList) + .def("HalveAggro", (void(Lua_EntityList::*)(Lua_Mob))&Lua_EntityList::HalveAggro) + .def("IsMobSpawnedByNpcTypeID", (bool(Lua_EntityList::*)(int))&Lua_EntityList::IsMobSpawnedByNpcTypeID) + .def("MakeNameUnique", (std::string(Lua_EntityList::*)(const char*))&Lua_EntityList::MakeNameUnique) + .def("Message", (void(Lua_EntityList::*)(uint32, uint32, const char*))&Lua_EntityList::Message) + .def("MessageClose", (void(Lua_EntityList::*)(Lua_Mob, bool, float, uint32, const char*))&Lua_EntityList::MessageClose) + .def("MessageGroup", (void(Lua_EntityList::*)(Lua_Mob, bool, uint32, const char*))&Lua_EntityList::MessageGroup) + .def("MessageStatus", (void(Lua_EntityList::*)(uint32, uint32, uint32, const char*))&Lua_EntityList::MessageStatus) + .def("OpenDoorsNear", (void(Lua_EntityList::*)(Lua_Mob))&Lua_EntityList::OpenDoorsNear) + .def("RemoveFromHateLists", (void(Lua_EntityList::*)(Lua_Mob))&Lua_EntityList::RemoveFromHateLists) + .def("RemoveFromHateLists", (void(Lua_EntityList::*)(Lua_Mob, bool))&Lua_EntityList::RemoveFromHateLists) + .def("RemoveFromTargets", (void(Lua_EntityList::*)(Lua_Mob))&Lua_EntityList::RemoveFromTargets) + .def("RemoveFromTargets", (void(Lua_EntityList::*)(Lua_Mob, bool))&Lua_EntityList::RemoveFromTargets) + .def("RemoveNumbers", (std::string(Lua_EntityList::*)(const char*))&Lua_EntityList::RemoveNumbers) + .def("ReplaceWithTarget", (void(Lua_EntityList::*)(Lua_Mob, Lua_Mob))&Lua_EntityList::ReplaceWithTarget) + .def("SignalAllClients", (void(Lua_EntityList::*)(int))&Lua_EntityList::SignalAllClients) + .def("SignalMobsByNPCID", (void(Lua_EntityList::*)(uint32, int))&Lua_EntityList::SignalMobsByNPCID); } luabind::scope lua_register_mob_list() { return luabind::class_("MobList") - .def_readwrite("entries", &Lua_Mob_List::entries, luabind::return_stl_iterator); + .def_readwrite("entries", &Lua_Mob_List::entries, luabind::return_stl_iterator); } luabind::scope lua_register_client_list() { return luabind::class_("ClientList") - .def_readwrite("entries", &Lua_Client_List::entries, luabind::return_stl_iterator); + .def_readwrite("entries", &Lua_Client_List::entries, luabind::return_stl_iterator); } +#ifdef BOTS +luabind::scope lua_register_bot_list() { + return luabind::class_("BotList") + .def_readwrite("entries", &Lua_Bot_List::entries, luabind::return_stl_iterator); +} +#endif + luabind::scope lua_register_npc_list() { return luabind::class_("NPCList") - .def_readwrite("entries", &Lua_NPC_List::entries, luabind::return_stl_iterator); + .def_readwrite("entries", &Lua_NPC_List::entries, luabind::return_stl_iterator); } luabind::scope lua_register_corpse_list() { return luabind::class_("CorpseList") - .def_readwrite("entries", &Lua_Corpse_List::entries, luabind::return_stl_iterator); + .def_readwrite("entries", &Lua_Corpse_List::entries, luabind::return_stl_iterator); } luabind::scope lua_register_object_list() { return luabind::class_("ObjectList") - .def_readwrite("entries", &Lua_Object_List::entries, luabind::return_stl_iterator); + .def_readwrite("entries", &Lua_Object_List::entries, luabind::return_stl_iterator); } luabind::scope lua_register_door_list() { return luabind::class_("DoorList") - .def_readwrite("entries", &Lua_Doors_List::entries, luabind::return_stl_iterator); + .def_readwrite("entries", &Lua_Doors_List::entries, luabind::return_stl_iterator); } luabind::scope lua_register_spawn_list() { return luabind::class_("SpawnList") - .def_readwrite("entries", &Lua_Spawn_List::entries, luabind::return_stl_iterator); + .def_readwrite("entries", &Lua_Spawn_List::entries, luabind::return_stl_iterator); } #endif diff --git a/zone/lua_entity_list.h b/zone/lua_entity_list.h index 04030e58e..ba67971dd 100644 --- a/zone/lua_entity_list.h +++ b/zone/lua_entity_list.h @@ -7,6 +7,9 @@ class EntityList; class Lua_Mob; class Lua_Client; +#ifdef BOTS +class Lua_Bot; +#endif class Lua_NPC; class Lua_Door; class Lua_Corpse; @@ -16,6 +19,9 @@ class Lua_Raid; class Lua_Spawn; struct Lua_Mob_List; struct Lua_Client_List; +#ifdef BOTS +struct Lua_Bot_List; +#endif struct Lua_NPC_List; struct Lua_Corpse_List; struct Lua_Object_List; @@ -35,6 +41,10 @@ luabind::scope lua_register_object_list(); luabind::scope lua_register_door_list(); luabind::scope lua_register_spawn_list(); +#ifdef BOTS +luabind::scope lua_register_bot_list(); +#endif + class Lua_EntityList : public Lua_Ptr { typedef EntityList NativeType; @@ -89,8 +99,8 @@ public: std::string MakeNameUnique(const char *name); std::string RemoveNumbers(const char *name); void SignalMobsByNPCID(uint32 npc_id, int signal); - int DeleteNPCCorpses(); - int DeletePlayerCorpses(); + uint32 DeleteNPCCorpses(); + uint32 DeletePlayerCorpses(); void HalveAggro(Lua_Mob who); void DoubleAggro(Lua_Mob who); void ClearFeignAggro(Lua_Mob who); @@ -110,6 +120,11 @@ public: Lua_Spawn_List GetSpawnList(); void SignalAllClients(int signal); void ChannelMessage(Lua_Mob from, int channel_num, int language, const char *message); +#ifdef BOTS + Lua_Bot GetBotByID(uint32 bot_id); + Lua_Bot GetBotByName(std::string bot_name); + Lua_Bot_List GetBotList(); +#endif }; #endif diff --git a/zone/lua_expedition.cpp b/zone/lua_expedition.cpp index a7ac929c0..0ed63782b 100644 --- a/zone/lua_expedition.cpp +++ b/zone/lua_expedition.cpp @@ -1,23 +1,3 @@ -/** - * 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 - * - */ - #ifdef LUA_EQEMU #include "lua_expedition.h" @@ -69,7 +49,7 @@ uint32_t Lua_Expedition::GetID() { int Lua_Expedition::GetInstanceID() { Lua_Safe_Call_Int(); - return self->GetDynamicZone().GetInstanceID(); + return self->GetDynamicZone()->GetInstanceID(); } std::string Lua_Expedition::GetLeaderName() { @@ -104,7 +84,7 @@ std::string Lua_Expedition::GetLootEventBySpawnID(uint32_t spawn_id) { uint32_t Lua_Expedition::GetMemberCount() { Lua_Safe_Call_Int(); - return self->GetMemberCount(); + return self->GetDynamicZone()->GetMemberCount(); } luabind::object Lua_Expedition::GetMembers(lua_State* L) { @@ -113,9 +93,9 @@ luabind::object Lua_Expedition::GetMembers(lua_State* L) { if (d_) { auto self = reinterpret_cast(d_); - for (const auto& member : self->GetMembers()) + for (const auto& member : self->GetDynamicZone()->GetMembers()) { - lua_table[member.name] = member.char_id; + lua_table[member.name] = member.id; } } return lua_table; @@ -128,27 +108,27 @@ std::string Lua_Expedition::GetName() { int Lua_Expedition::GetSecondsRemaining() { Lua_Safe_Call_Int(); - return self->GetDynamicZone().GetSecondsRemaining(); + return self->GetDynamicZone()->GetSecondsRemaining(); } std::string Lua_Expedition::GetUUID() { Lua_Safe_Call_String(); - return self->GetUUID(); + return self->GetDynamicZone()->GetUUID(); } int Lua_Expedition::GetZoneID() { Lua_Safe_Call_Int(); - return self->GetDynamicZone().GetZoneID(); + return self->GetDynamicZone()->GetZoneID(); } std::string Lua_Expedition::GetZoneName() { Lua_Safe_Call_String(); - return ZoneName(self->GetDynamicZone().GetZoneID()); + return ZoneName(self->GetDynamicZone()->GetZoneID()); } int Lua_Expedition::GetZoneVersion() { Lua_Safe_Call_Int(); - return self->GetDynamicZone().GetZoneVersion(); + return self->GetDynamicZone()->GetZoneVersion(); } bool Lua_Expedition::HasLockout(std::string event_name) { @@ -168,7 +148,7 @@ bool Lua_Expedition::IsLocked() { void Lua_Expedition::RemoveCompass() { Lua_Safe_Call_Void(); - self->SetDzCompass(0, 0, 0, 0, true); + self->GetDynamicZone()->SetCompass(0, 0, 0, 0, true); } void Lua_Expedition::RemoveLockout(std::string event_name) { @@ -178,12 +158,12 @@ void Lua_Expedition::RemoveLockout(std::string event_name) { void Lua_Expedition::SetCompass(uint32_t zone_id, float x, float y, float z) { Lua_Safe_Call_Void(); - self->SetDzCompass(zone_id, x, y, z, true); + self->GetDynamicZone()->SetCompass(zone_id, x, y, z, true); } void Lua_Expedition::SetCompass(std::string zone_name, float x, float y, float z) { Lua_Safe_Call_Void(); - self->SetDzCompass(zone_name, x, y, z, true); + self->GetDynamicZone()->SetCompass(ZoneID(zone_name), x, y, z, true); } void Lua_Expedition::SetLocked(bool lock_expedition) { @@ -218,23 +198,23 @@ void Lua_Expedition::SetReplayLockoutOnMemberJoin(bool enable) { void Lua_Expedition::SetSafeReturn(uint32_t zone_id, float x, float y, float z, float heading) { Lua_Safe_Call_Void(); - self->SetDzSafeReturn(zone_id, x, y, z, heading, true); + self->GetDynamicZone()->SetSafeReturn(zone_id, x, y, z, heading, true); } void Lua_Expedition::SetSafeReturn(std::string zone_name, float x, float y, float z, float heading) { Lua_Safe_Call_Void(); - self->SetDzSafeReturn(zone_name, x, y, z, heading, true); + self->GetDynamicZone()->SetSafeReturn(ZoneID(zone_name), x, y, z, heading, true); } void Lua_Expedition::SetSecondsRemaining(uint32_t seconds_remaining) { Lua_Safe_Call_Void(); - self->SetDzSecondsRemaining(seconds_remaining); + self->GetDynamicZone()->SetSecondsRemaining(seconds_remaining); } void Lua_Expedition::SetZoneInLocation(float x, float y, float z, float heading) { Lua_Safe_Call_Void(); - self->SetDzZoneInLocation(x, y, z, heading, true); + self->GetDynamicZone()->SetZoneInLocation(x, y, z, heading, true); } void Lua_Expedition::UpdateLockoutDuration(std::string event_name, uint32_t duration) { @@ -249,59 +229,59 @@ void Lua_Expedition::UpdateLockoutDuration(std::string event_name, uint32_t dura luabind::scope lua_register_expedition() { return luabind::class_("Expedition") - .def(luabind::constructor<>()) - .property("null", &Lua_Expedition::Null) - .property("valid", &Lua_Expedition::Valid) - .def("AddLockout", (void(Lua_Expedition::*)(std::string, uint32_t))&Lua_Expedition::AddLockout) - .def("AddLockoutDuration", (void(Lua_Expedition::*)(std::string, int))&Lua_Expedition::AddLockoutDuration) - .def("AddLockoutDuration", (void(Lua_Expedition::*)(std::string, int, bool))&Lua_Expedition::AddLockoutDuration) - .def("AddReplayLockout", (void(Lua_Expedition::*)(uint32_t))&Lua_Expedition::AddReplayLockout) - .def("AddReplayLockoutDuration", (void(Lua_Expedition::*)(int))&Lua_Expedition::AddReplayLockoutDuration) - .def("AddReplayLockoutDuration", (void(Lua_Expedition::*)(int, bool))&Lua_Expedition::AddReplayLockoutDuration) - .def("GetDynamicZoneID", &Lua_Expedition::GetDynamicZoneID) - .def("GetID", (uint32_t(Lua_Expedition::*)(void))&Lua_Expedition::GetID) - .def("GetInstanceID", (int(Lua_Expedition::*)(void))&Lua_Expedition::GetInstanceID) - .def("GetLeaderName", (std::string(Lua_Expedition::*)(void))&Lua_Expedition::GetLeaderName) - .def("GetLockouts", &Lua_Expedition::GetLockouts) - .def("GetLootEventByNPCTypeID", (std::string(Lua_Expedition::*)(uint32_t))&Lua_Expedition::GetLootEventByNPCTypeID) - .def("GetLootEventBySpawnID", (std::string(Lua_Expedition::*)(uint32_t))&Lua_Expedition::GetLootEventBySpawnID) - .def("GetMemberCount", (uint32_t(Lua_Expedition::*)(void))&Lua_Expedition::GetMemberCount) - .def("GetMembers", &Lua_Expedition::GetMembers) - .def("GetName", (std::string(Lua_Expedition::*)(void))&Lua_Expedition::GetName) - .def("GetSecondsRemaining", (int(Lua_Expedition::*)(void))&Lua_Expedition::GetSecondsRemaining) - .def("GetUUID", (std::string(Lua_Expedition::*)(void))&Lua_Expedition::GetUUID) - .def("GetZoneID", (int(Lua_Expedition::*)(void))&Lua_Expedition::GetZoneID) - .def("GetZoneName", &Lua_Expedition::GetZoneName) - .def("GetZoneVersion", &Lua_Expedition::GetZoneVersion) - .def("HasLockout", (bool(Lua_Expedition::*)(std::string))&Lua_Expedition::HasLockout) - .def("HasReplayLockout", (bool(Lua_Expedition::*)(void))&Lua_Expedition::HasReplayLockout) - .def("IsLocked", &Lua_Expedition::IsLocked) - .def("RemoveCompass", (void(Lua_Expedition::*)(void))&Lua_Expedition::RemoveCompass) - .def("RemoveLockout", (void(Lua_Expedition::*)(std::string))&Lua_Expedition::RemoveLockout) - .def("SetCompass", (void(Lua_Expedition::*)(uint32_t, float, float, float))&Lua_Expedition::SetCompass) - .def("SetCompass", (void(Lua_Expedition::*)(std::string, float, float, float))&Lua_Expedition::SetCompass) - .def("SetLocked", (void(Lua_Expedition::*)(bool))&Lua_Expedition::SetLocked) - .def("SetLocked", (void(Lua_Expedition::*)(bool, int))&Lua_Expedition::SetLocked) - .def("SetLocked", (void(Lua_Expedition::*)(bool, int, uint32_t))&Lua_Expedition::SetLocked) - .def("SetLootEventByNPCTypeID", (void(Lua_Expedition::*)(uint32_t, std::string))&Lua_Expedition::SetLootEventByNPCTypeID) - .def("SetLootEventBySpawnID", (void(Lua_Expedition::*)(uint32_t, std::string))&Lua_Expedition::SetLootEventBySpawnID) - .def("SetReplayLockoutOnMemberJoin", (void(Lua_Expedition::*)(bool))&Lua_Expedition::SetReplayLockoutOnMemberJoin) - .def("SetSafeReturn", (void(Lua_Expedition::*)(uint32_t, float, float, float, float))&Lua_Expedition::SetSafeReturn) - .def("SetSafeReturn", (void(Lua_Expedition::*)(std::string, float, float, float, float))&Lua_Expedition::SetSafeReturn) - .def("SetSecondsRemaining", &Lua_Expedition::SetSecondsRemaining) - .def("SetZoneInLocation", (void(Lua_Expedition::*)(float, float, float, float))&Lua_Expedition::SetZoneInLocation) - .def("UpdateLockoutDuration", (void(Lua_Expedition::*)(std::string, uint32_t))&Lua_Expedition::UpdateLockoutDuration) - .def("UpdateLockoutDuration", (void(Lua_Expedition::*)(std::string, uint32_t, bool))&Lua_Expedition::UpdateLockoutDuration); + .def(luabind::constructor<>()) + .property("null", &Lua_Expedition::Null) + .property("valid", &Lua_Expedition::Valid) + .def("AddLockout", (void(Lua_Expedition::*)(std::string, uint32_t))&Lua_Expedition::AddLockout) + .def("AddLockoutDuration", (void(Lua_Expedition::*)(std::string, int))&Lua_Expedition::AddLockoutDuration) + .def("AddLockoutDuration", (void(Lua_Expedition::*)(std::string, int, bool))&Lua_Expedition::AddLockoutDuration) + .def("AddReplayLockout", (void(Lua_Expedition::*)(uint32_t))&Lua_Expedition::AddReplayLockout) + .def("AddReplayLockoutDuration", (void(Lua_Expedition::*)(int))&Lua_Expedition::AddReplayLockoutDuration) + .def("AddReplayLockoutDuration", (void(Lua_Expedition::*)(int, bool))&Lua_Expedition::AddReplayLockoutDuration) + .def("GetDynamicZoneID", &Lua_Expedition::GetDynamicZoneID) + .def("GetID", (uint32_t(Lua_Expedition::*)(void))&Lua_Expedition::GetID) + .def("GetInstanceID", (int(Lua_Expedition::*)(void))&Lua_Expedition::GetInstanceID) + .def("GetLeaderName", (std::string(Lua_Expedition::*)(void))&Lua_Expedition::GetLeaderName) + .def("GetLockouts", &Lua_Expedition::GetLockouts) + .def("GetLootEventByNPCTypeID", (std::string(Lua_Expedition::*)(uint32_t))&Lua_Expedition::GetLootEventByNPCTypeID) + .def("GetLootEventBySpawnID", (std::string(Lua_Expedition::*)(uint32_t))&Lua_Expedition::GetLootEventBySpawnID) + .def("GetMemberCount", (uint32_t(Lua_Expedition::*)(void))&Lua_Expedition::GetMemberCount) + .def("GetMembers", &Lua_Expedition::GetMembers) + .def("GetName", (std::string(Lua_Expedition::*)(void))&Lua_Expedition::GetName) + .def("GetSecondsRemaining", (int(Lua_Expedition::*)(void))&Lua_Expedition::GetSecondsRemaining) + .def("GetUUID", (std::string(Lua_Expedition::*)(void))&Lua_Expedition::GetUUID) + .def("GetZoneID", (int(Lua_Expedition::*)(void))&Lua_Expedition::GetZoneID) + .def("GetZoneName", &Lua_Expedition::GetZoneName) + .def("GetZoneVersion", &Lua_Expedition::GetZoneVersion) + .def("HasLockout", (bool(Lua_Expedition::*)(std::string))&Lua_Expedition::HasLockout) + .def("HasReplayLockout", (bool(Lua_Expedition::*)(void))&Lua_Expedition::HasReplayLockout) + .def("IsLocked", &Lua_Expedition::IsLocked) + .def("RemoveCompass", (void(Lua_Expedition::*)(void))&Lua_Expedition::RemoveCompass) + .def("RemoveLockout", (void(Lua_Expedition::*)(std::string))&Lua_Expedition::RemoveLockout) + .def("SetCompass", (void(Lua_Expedition::*)(uint32_t, float, float, float))&Lua_Expedition::SetCompass) + .def("SetCompass", (void(Lua_Expedition::*)(std::string, float, float, float))&Lua_Expedition::SetCompass) + .def("SetLocked", (void(Lua_Expedition::*)(bool))&Lua_Expedition::SetLocked) + .def("SetLocked", (void(Lua_Expedition::*)(bool, int))&Lua_Expedition::SetLocked) + .def("SetLocked", (void(Lua_Expedition::*)(bool, int, uint32_t))&Lua_Expedition::SetLocked) + .def("SetLootEventByNPCTypeID", (void(Lua_Expedition::*)(uint32_t, std::string))&Lua_Expedition::SetLootEventByNPCTypeID) + .def("SetLootEventBySpawnID", (void(Lua_Expedition::*)(uint32_t, std::string))&Lua_Expedition::SetLootEventBySpawnID) + .def("SetReplayLockoutOnMemberJoin", (void(Lua_Expedition::*)(bool))&Lua_Expedition::SetReplayLockoutOnMemberJoin) + .def("SetSafeReturn", (void(Lua_Expedition::*)(uint32_t, float, float, float, float))&Lua_Expedition::SetSafeReturn) + .def("SetSafeReturn", (void(Lua_Expedition::*)(std::string, float, float, float, float))&Lua_Expedition::SetSafeReturn) + .def("SetSecondsRemaining", &Lua_Expedition::SetSecondsRemaining) + .def("SetZoneInLocation", (void(Lua_Expedition::*)(float, float, float, float))&Lua_Expedition::SetZoneInLocation) + .def("UpdateLockoutDuration", (void(Lua_Expedition::*)(std::string, uint32_t))&Lua_Expedition::UpdateLockoutDuration) + .def("UpdateLockoutDuration", (void(Lua_Expedition::*)(std::string, uint32_t, bool))&Lua_Expedition::UpdateLockoutDuration); } luabind::scope lua_register_expedition_lock_messages() { return luabind::class_("ExpeditionLockMessage") - .enum_("constants") - [ - luabind::value("None", static_cast(ExpeditionLockMessage::None)), - luabind::value("Close", static_cast(ExpeditionLockMessage::Close)), - luabind::value("Begin", static_cast(ExpeditionLockMessage::Begin)) - ]; + .enum_("constants") + [ + luabind::value("None", static_cast(ExpeditionLockMessage::None)), + luabind::value("Close", static_cast(ExpeditionLockMessage::Close)), + luabind::value("Begin", static_cast(ExpeditionLockMessage::Begin)) + ]; } #endif // LUA_EQEMU diff --git a/zone/lua_general.cpp b/zone/lua_general.cpp index a4fd77449..db4394b0f 100644 --- a/zone/lua_general.cpp +++ b/zone/lua_general.cpp @@ -19,6 +19,7 @@ #include "lua_npc.h" #include "lua_entity_list.h" #include "lua_expedition.h" +#include "lua_spell.h" #include "quest_parser_collection.h" #include "questmgr.h" #include "qglobals.h" @@ -901,7 +902,7 @@ bool lua_delete_data(std::string bucket_key) { return DataBucket::DeleteData(bucket_key); } -const char *lua_get_char_name_by_id(uint32 char_id) { +std::string lua_get_char_name_by_id(uint32 char_id) { return database.GetCharNameByID(char_id); } @@ -917,11 +918,11 @@ 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) { +uint32 lua_get_currency_id(uint32 item_id) { return quest_manager.getcurrencyid(item_id); } -int lua_get_currency_item_id(int currency_id) { +uint32 lua_get_currency_item_id(uint32 currency_id) { return quest_manager.getcurrencyitemid(currency_id); } @@ -937,7 +938,7 @@ 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) { +std::string lua_get_npc_name_by_id(uint32 npc_id) { return quest_manager.getnpcnamebyid(npc_id); } @@ -1045,482 +1046,6 @@ void lua_send_mail(const char *to, const char *from, const char *subject, const quest_manager.SendMail(to, from, subject, message); } -void lua_cross_zone_assign_task_by_char_id(int character_id, uint32 task_id) { - quest_manager.CrossZoneAssignTaskByCharID(character_id, task_id); -} - -void lua_cross_zone_assign_task_by_char_id(int character_id, uint32 task_id, bool enforce_level_requirement) { - quest_manager.CrossZoneAssignTaskByCharID(character_id, task_id, enforce_level_requirement); -} - -void lua_cross_zone_assign_task_by_group_id(int group_id, uint32 task_id) { - quest_manager.CrossZoneAssignTaskByGroupID(group_id, task_id); -} - -void lua_cross_zone_assign_task_by_group_id(int group_id, uint32 task_id, bool enforce_level_requirement) { - quest_manager.CrossZoneAssignTaskByGroupID(group_id, task_id, enforce_level_requirement); -} - -void lua_cross_zone_assign_task_by_raid_id(int raid_id, uint32 task_id) { - quest_manager.CrossZoneAssignTaskByRaidID(raid_id, task_id); -} - -void lua_cross_zone_assign_task_by_raid_id(int raid_id, uint32 task_id, bool enforce_level_requirement) { - quest_manager.CrossZoneAssignTaskByRaidID(raid_id, task_id, enforce_level_requirement); -} - -void lua_cross_zone_assign_task_by_guild_id(int guild_id, uint32 task_id) { - quest_manager.CrossZoneAssignTaskByGuildID(guild_id, task_id); -} - -void lua_cross_zone_assign_task_by_guild_id(int guild_id, uint32 task_id, bool enforce_level_requirement) { - quest_manager.CrossZoneAssignTaskByGuildID(guild_id, task_id, enforce_level_requirement); -} - -void lua_cross_zone_cast_spell_by_char_id(int character_id, uint32 spell_id) { - quest_manager.CrossZoneCastSpellByCharID(character_id, spell_id); -} - -void lua_cross_zone_cast_spell_by_group_id(int group_id, uint32 spell_id) { - quest_manager.CrossZoneCastSpellByGroupID(group_id, spell_id); -} - -void lua_cross_zone_cast_spell_by_raid_id(int raid_id, uint32 spell_id) { - quest_manager.CrossZoneCastSpellByRaidID(raid_id, spell_id); -} - -void lua_cross_zone_cast_spell_by_guild_id(int guild_id, uint32 spell_id) { - quest_manager.CrossZoneCastSpellByGuildID(guild_id, spell_id); -} - -void lua_cross_zone_disable_task_by_char_id(int character_id, uint32 task_id) { - quest_manager.CrossZoneDisableTaskByCharID(character_id, task_id); -} - -void lua_cross_zone_disable_task_by_group_id(int group_id, uint32 task_id) { - quest_manager.CrossZoneDisableTaskByGroupID(group_id, task_id); -} - -void lua_cross_zone_disable_task_by_raid_id(int raid_id, uint32 task_id) { - quest_manager.CrossZoneDisableTaskByRaidID(raid_id, task_id); -} - -void lua_cross_zone_disable_task_by_guild_id(int guild_id, uint32 task_id) { - quest_manager.CrossZoneDisableTaskByGuildID(guild_id, task_id); -} - -void lua_cross_zone_enable_task_by_char_id(int character_id, uint32 task_id) { - quest_manager.CrossZoneEnableTaskByCharID(character_id, task_id); -} - -void lua_cross_zone_enable_task_by_group_id(int group_id, uint32 task_id) { - quest_manager.CrossZoneEnableTaskByGroupID(group_id, task_id); -} - -void lua_cross_zone_enable_task_by_raid_id(int raid_id, uint32 task_id) { - quest_manager.CrossZoneEnableTaskByRaidID(raid_id, task_id); -} - -void lua_cross_zone_enable_task_by_guild_id(int guild_id, uint32 task_id) { - quest_manager.CrossZoneEnableTaskByGuildID(guild_id, task_id); -} - -void lua_cross_zone_fail_task_by_char_id(int character_id, uint32 task_id) { - quest_manager.CrossZoneFailTaskByCharID(character_id, task_id); -} - -void lua_cross_zone_fail_task_by_group_id(int group_id, uint32 task_id) { - quest_manager.CrossZoneFailTaskByGroupID(group_id, task_id); -} - -void lua_cross_zone_fail_task_by_raid_id(int raid_id, uint32 task_id) { - quest_manager.CrossZoneFailTaskByRaidID(raid_id, task_id); -} - -void lua_cross_zone_fail_task_by_guild_id(int guild_id, uint32 task_id) { - quest_manager.CrossZoneFailTaskByGuildID(guild_id, task_id); -} - -void lua_cross_zone_marquee_by_char_id(int character_id, uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char *message) { - quest_manager.CrossZoneMarqueeByCharID(character_id, type, priority, fade_in, fade_out, duration, message); -} - -void lua_cross_zone_marquee_by_group_id(int group_id, uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char *message) { - quest_manager.CrossZoneMarqueeByGroupID(group_id, type, priority, fade_in, fade_out, duration, message); -} - -void lua_cross_zone_marquee_by_raid_id(int raid_id, uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char *message) { - quest_manager.CrossZoneMarqueeByRaidID(raid_id, type, priority, fade_in, fade_out, duration, message); -} - -void lua_cross_zone_marquee_by_guild_id(int guild_id, uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char *message) { - quest_manager.CrossZoneMarqueeByGuildID(guild_id, type, priority, fade_in, fade_out, duration, message); -} - -void lua_cross_zone_message_player_by_name(uint32 type, const char *character_name, const char *message) { - quest_manager.CrossZoneMessagePlayerByName(type, character_name, message); -} - -void lua_cross_zone_message_player_by_group_id(uint32 type, int group_id, const char *message) { - quest_manager.CrossZoneMessagePlayerByGroupID(type, group_id, message); -} - -void lua_cross_zone_message_player_by_raid_id(uint32 type, int raid_id, const char *message) { - quest_manager.CrossZoneMessagePlayerByRaidID(type, raid_id, message); -} - -void lua_cross_zone_message_player_by_guild_id(uint32 type, int guild_id, const char *message) { - quest_manager.CrossZoneMessagePlayerByGuildID(type, guild_id, message); -} - -void lua_cross_zone_move_player_by_char_id(int character_id, const char *zone_short_name) { - quest_manager.CrossZoneMovePlayerByCharID(character_id, zone_short_name); -} - -void lua_cross_zone_move_player_by_group_id(int group_id, const char *zone_short_name) { - quest_manager.CrossZoneMovePlayerByGroupID(group_id, zone_short_name); -} - -void lua_cross_zone_move_player_by_raid_id(int raid_id, const char *zone_short_name) { - quest_manager.CrossZoneMovePlayerByRaidID(raid_id, zone_short_name); -} - -void lua_cross_zone_move_player_by_guild_id(int guild_id, const char *zone_short_name) { - quest_manager.CrossZoneMovePlayerByGuildID(guild_id, zone_short_name); -} - -void lua_cross_zone_move_instance_by_char_id(int character_id, uint16 instance_id) { - quest_manager.CrossZoneMoveInstanceByCharID(character_id, instance_id); -} - -void lua_cross_zone_move_instance_by_group_id(int group_id, uint16 instance_id) { - quest_manager.CrossZoneMoveInstanceByGroupID(group_id, instance_id); -} - -void lua_cross_zone_move_instance_by_raid_id(int raid_id, uint16 instance_id) { - quest_manager.CrossZoneMoveInstanceByRaidID(raid_id, instance_id); -} - -void lua_cross_zone_move_instance_by_guild_id(int guild_id, uint16 instance_id) { - quest_manager.CrossZoneMoveInstanceByGuildID(guild_id, instance_id); -} - -void lua_cross_zone_remove_spell_by_char_id(int character_id, uint32 spell_id) { - quest_manager.CrossZoneRemoveSpellByCharID(character_id, spell_id); -} - -void lua_cross_zone_remove_spell_by_group_id(int group_id, uint32 spell_id) { - quest_manager.CrossZoneRemoveSpellByGroupID(group_id, spell_id); -} - -void lua_cross_zone_remove_spell_by_raid_id(int raid_id, uint32 spell_id) { - quest_manager.CrossZoneRemoveSpellByRaidID(raid_id, spell_id); -} - -void lua_cross_zone_remove_spell_by_guild_id(int guild_id, uint32 spell_id) { - quest_manager.CrossZoneRemoveSpellByGuildID(guild_id, spell_id); -} - -void lua_cross_zone_remove_task_by_char_id(int character_id, uint32 task_id) { - quest_manager.CrossZoneRemoveTaskByCharID(character_id, task_id); -} - -void lua_cross_zone_remove_task_by_group_id(int group_id, uint32 task_id) { - quest_manager.CrossZoneRemoveTaskByGroupID(group_id, task_id); -} - -void lua_cross_zone_remove_task_by_raid_id(int raid_id, uint32 task_id) { - quest_manager.CrossZoneRemoveTaskByRaidID(raid_id, task_id); -} - -void lua_cross_zone_remove_task_by_guild_id(int guild_id, uint32 task_id) { - quest_manager.CrossZoneRemoveTaskByGuildID(guild_id, task_id); -} - -void lua_cross_zone_reset_activity_by_char_id(int character_id, uint32 task_id, int activity_id) { - quest_manager.CrossZoneResetActivityByCharID(character_id, task_id, activity_id); -} - -void lua_cross_zone_reset_activity_by_group_id(int group_id, uint32 task_id, int activity_id) { - quest_manager.CrossZoneResetActivityByGroupID(group_id, task_id, activity_id); -} - -void lua_cross_zone_reset_activity_by_raid_id(int raid_id, uint32 task_id, int activity_id) { - quest_manager.CrossZoneResetActivityByRaidID(raid_id, task_id, activity_id); -} - -void lua_cross_zone_reset_activity_by_guild_id(int guild_id, uint32 task_id, int activity_id) { - quest_manager.CrossZoneResetActivityByGuildID(guild_id, task_id, activity_id); -} - -void lua_cross_zone_set_entity_variable_by_client_name(const char *character_name, const char *variable_name, const char *variable_value) { - quest_manager.CrossZoneSetEntityVariableByClientName(character_name, variable_name, variable_value); -} - -void lua_cross_zone_set_entity_variable_by_group_id(int group_id, const char *variable_name, const char *variable_value) { - quest_manager.CrossZoneSetEntityVariableByGroupID(group_id, variable_name, variable_value); -} - -void lua_cross_zone_set_entity_variable_by_raid_id(int raid_id, const char *variable_name, const char *variable_value) { - quest_manager.CrossZoneSetEntityVariableByRaidID(raid_id, variable_name, variable_value); -} - -void lua_cross_zone_set_entity_variable_by_guild_id(int guild_id, const char *variable_name, const char *variable_value) { - quest_manager.CrossZoneSetEntityVariableByGuildID(guild_id, variable_name, variable_value); -} - -void lua_cross_zone_signal_client_by_char_id(uint32 character_id, int signal) { - quest_manager.CrossZoneSignalPlayerByCharID(character_id, signal); -} - -void lua_cross_zone_signal_client_by_group_id(uint32 group_id, int signal) { - quest_manager.CrossZoneSignalPlayerByGroupID(group_id, signal); -} - -void lua_cross_zone_signal_client_by_raid_id(uint32 raid_id, int signal) { - quest_manager.CrossZoneSignalPlayerByRaidID(raid_id, signal); -} - -void lua_cross_zone_signal_client_by_guild_id(uint32 guild_id, int signal) { - quest_manager.CrossZoneSignalPlayerByGuildID(guild_id, signal); -} - -void lua_cross_zone_signal_client_by_name(const char *character_name, int signal) { - quest_manager.CrossZoneSignalPlayerByName(character_name, signal); -} - -void lua_cross_zone_signal_npc_by_npctype_id(uint32 npctype_id, int signal) { - quest_manager.CrossZoneSignalNPCByNPCTypeID(npctype_id, signal); -} - -void lua_cross_zone_update_activity_by_char_id(int character_id, uint32 task_id, int activity_id) { - quest_manager.CrossZoneUpdateActivityByCharID(character_id, task_id, activity_id); -} - -void lua_cross_zone_update_activity_by_char_id(int character_id, uint32 task_id, int activity_id, int activity_count) { - quest_manager.CrossZoneUpdateActivityByCharID(character_id, task_id, activity_id, activity_count); -} - -void lua_cross_zone_update_activity_by_group_id(int group_id, uint32 task_id, int activity_id) { - quest_manager.CrossZoneUpdateActivityByGroupID(group_id, task_id, activity_id); -} - -void lua_cross_zone_update_activity_by_group_id(int group_id, uint32 task_id, int activity_id, int activity_count) { - quest_manager.CrossZoneUpdateActivityByGroupID(group_id, task_id, activity_id, activity_count); -} - -void lua_cross_zone_update_activity_by_raid_id(int raid_id, uint32 task_id, int activity_id) { - quest_manager.CrossZoneUpdateActivityByRaidID(raid_id, task_id, activity_id); -} - -void lua_cross_zone_update_activity_by_raid_id(int raid_id, uint32 task_id, int activity_id, int activity_count) { - quest_manager.CrossZoneUpdateActivityByRaidID(raid_id, task_id, activity_id, activity_count); -} - -void lua_cross_zone_update_activity_by_guild_id(int guild_id, uint32 task_id, int activity_id) { - quest_manager.CrossZoneUpdateActivityByGuildID(guild_id, task_id, activity_id); -} - -void lua_cross_zone_update_activity_by_guild_id(int guild_id, uint32 task_id, int activity_id, int activity_count) { - quest_manager.CrossZoneUpdateActivityByGuildID(guild_id, task_id, activity_id, activity_count); -} - -void lua_world_wide_assign_task(uint32 task_id) { - quest_manager.WorldWideAssignTask(task_id); -} - -void lua_world_wide_assign_task(uint32 task_id, bool enforce_level_requirement) { - quest_manager.WorldWideAssignTask(task_id, enforce_level_requirement); -} - -void lua_world_wide_assign_task(uint32 task_id, bool enforce_level_requirement, uint8 min_status) { - quest_manager.WorldWideAssignTask(task_id, enforce_level_requirement, min_status); -} - -void lua_world_wide_assign_task(uint32 task_id, bool enforce_level_requirement, uint8 min_status, uint8 max_status) { - quest_manager.WorldWideAssignTask(task_id, enforce_level_requirement, min_status, max_status); -} - -void lua_world_wide_cast_spell(uint32 spell_id) { - quest_manager.WorldWideCastSpell(spell_id); -} - -void lua_world_wide_cast_spell(uint32 spell_id, uint8 min_status) { - quest_manager.WorldWideCastSpell(spell_id, min_status); -} - -void lua_world_wide_cast_spell(uint32 spell_id, uint8 min_status, uint8 max_status) { - quest_manager.WorldWideCastSpell(spell_id, min_status, max_status); -} - -void lua_world_wide_disable_task(uint32 task_id) { - quest_manager.WorldWideDisableTask(task_id); -} - -void lua_world_wide_disable_task(uint32 task_id, uint8 min_status) { - quest_manager.WorldWideDisableTask(task_id, min_status); -} - -void lua_world_wide_disable_task(uint32 task_id, uint8 min_status, uint8 max_status) { - quest_manager.WorldWideDisableTask(task_id, min_status, max_status); -} - -void lua_world_wide_enable_task(uint32 task_id) { - quest_manager.WorldWideEnableTask(task_id); -} - -void lua_world_wide_enable_task(uint32 task_id, uint8 min_status) { - quest_manager.WorldWideEnableTask(task_id, min_status); -} - -void lua_world_wide_enable_task(uint32 task_id, uint8 min_status, uint8 max_status) { - quest_manager.WorldWideEnableTask(task_id, min_status, max_status); -} - -void lua_world_wide_fail_task(uint32 task_id) { - quest_manager.WorldWideFailTask(task_id); -} - -void lua_world_wide_fail_task(uint32 task_id, uint8 min_status) { - quest_manager.WorldWideFailTask(task_id, min_status); -} - -void lua_world_wide_fail_task(uint32 task_id, uint8 min_status, uint8 max_status) { - quest_manager.WorldWideFailTask(task_id, min_status, max_status); -} - -void lua_world_wide_marquee(uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char *message) { - quest_manager.WorldWideMarquee(type, priority, fade_in, fade_out, duration, message); -} - -void lua_world_wide_marquee(uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char *message, uint8 min_status) { - quest_manager.WorldWideMarquee(type, priority, fade_in, fade_out, duration, message, min_status); -} - -void lua_world_wide_marquee(uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char *message, uint8 min_status, uint8 max_status) { - quest_manager.WorldWideMarquee(type, priority, fade_in, fade_out, duration, message, min_status, max_status); -} - -void lua_world_wide_message(uint32 type, const char *message) { - quest_manager.WorldWideMessage(type, message); -} - -void lua_world_wide_message(uint32 type, const char *message, uint8 min_status) { - quest_manager.WorldWideMessage(type, message, min_status); -} - -void lua_world_wide_message(uint32 type, const char *message, uint8 min_status, uint8 max_status) { - quest_manager.WorldWideMessage(type, message, min_status, max_status); -} - -void lua_world_wide_move(const char *zone_short_name) { - quest_manager.WorldWideMove(zone_short_name); -} - -void lua_world_wide_move(const char *zone_short_name, uint8 min_status) { - quest_manager.WorldWideMove(zone_short_name, min_status); -} - -void lua_world_wide_move(const char *zone_short_name, uint8 min_status, uint8 max_status) { - quest_manager.WorldWideMove(zone_short_name, min_status, max_status); -} - -void lua_world_wide_move_instance(uint16 instance_id) { - quest_manager.WorldWideMoveInstance(instance_id); -} - -void lua_world_wide_move_instance(uint16 instance_id, uint8 min_status) { - quest_manager.WorldWideMoveInstance(instance_id, min_status); -} - -void lua_world_wide_move_instance(uint16 instance_id, uint8 min_status, uint8 max_status) { - quest_manager.WorldWideMoveInstance(instance_id, min_status, max_status); -} - -void lua_world_wide_remove_spell(uint32 spell_id) { - quest_manager.WorldWideRemoveSpell(spell_id); -} - -void lua_world_wide_remove_spell(uint32 spell_id, uint8 min_status) { - quest_manager.WorldWideRemoveSpell(spell_id, min_status); -} - -void lua_world_wide_remove_spell(uint32 spell_id, uint8 min_status, uint8 max_status) { - quest_manager.WorldWideRemoveSpell(spell_id, min_status, max_status); -} - -void lua_world_wide_remove_task(uint32 task_id) { - quest_manager.WorldWideRemoveTask(task_id); -} - -void lua_world_wide_remove_task(uint32 task_id, uint8 min_status) { - quest_manager.WorldWideRemoveTask(task_id, min_status); -} - -void lua_world_wide_remove_task(uint32 task_id, uint8 min_status, uint8 max_status) { - quest_manager.WorldWideRemoveTask(task_id, min_status, max_status); -} - -void lua_world_wide_reset_activity(uint32 task_id, int activity_id) { - quest_manager.WorldWideResetActivity(task_id, activity_id); -} - -void lua_world_wide_reset_activity(uint32 task_id, int activity_id, uint8 min_status) { - quest_manager.WorldWideResetActivity(task_id, activity_id, min_status); -} - -void lua_world_wide_reset_activity(uint32 task_id, int activity_id, uint8 min_status, uint8 max_status) { - quest_manager.WorldWideResetActivity(task_id, activity_id, min_status, max_status); -} - -void lua_world_wide_set_entity_variable_client(const char *variable_name, const char *variable_value) { - quest_manager.WorldWideSetEntityVariableClient(variable_name, variable_value); -} - -void lua_world_wide_set_entity_variable_client(const char *variable_name, const char *variable_value, uint8 min_status) { - quest_manager.WorldWideSetEntityVariableClient(variable_name, variable_value, min_status); -} - -void lua_world_wide_set_entity_variable_client(const char *variable_name, const char *variable_value, uint8 min_status, uint8 max_status) { - quest_manager.WorldWideSetEntityVariableClient(variable_name, variable_value, min_status, max_status); -} - -void lua_world_wide_set_entity_variable_npc(const char *variable_name, const char *variable_value) { - quest_manager.WorldWideSetEntityVariableNPC(variable_name, variable_value); -} - -void lua_world_wide_signal_client(uint32 signal) { - quest_manager.WorldWideSignalClient(signal); -} - -void lua_world_wide_signal_client(uint32 signal, uint8 min_status) { - quest_manager.WorldWideSignalClient(signal, min_status); -} - -void lua_world_wide_signal_client(uint32 signal, uint8 min_status, uint8 max_status) { - quest_manager.WorldWideSignalClient(signal, min_status, max_status); -} - -void lua_world_wide_signal_npc(uint32 signal) { - quest_manager.WorldWideSignalNPC(signal); -} - -void lua_world_wide_update_activity(uint32 task_id, int activity_id) { - quest_manager.WorldWideUpdateActivity(task_id, activity_id); -} - -void lua_world_wide_update_activity(uint32 task_id, int activity_id, int activity_count) { - quest_manager.WorldWideUpdateActivity(task_id, activity_id, activity_count); -} - -void lua_world_wide_update_activity(uint32 task_id, int activity_id, int activity_count, uint8 min_status) { - quest_manager.WorldWideUpdateActivity(task_id, activity_id, activity_count, min_status); -} - -void lua_world_wide_update_activity(uint32 task_id, int activity_id, int activity_count, uint8 min_status, uint8 max_status) { - quest_manager.WorldWideUpdateActivity(task_id, activity_id, activity_count, min_status, max_status); -} - luabind::adl::object lua_get_qglobals(lua_State *L, Lua_NPC npc, Lua_Client client) { luabind::adl::object ret = luabind::newtable(L); @@ -1608,6 +1133,10 @@ int lua_get_zone_id() { return zone->GetZoneID(); } +int lua_get_zone_id_by_name(const char* zone_name) { + return ZoneID(zone_name); +} + const char *lua_get_zone_long_name() { if(!zone) return ""; @@ -1615,6 +1144,16 @@ const char *lua_get_zone_long_name() { return zone->GetLongName(); } +const char *lua_get_zone_long_name_by_name(const char* zone_name) { + return ZoneLongName( + ZoneID(zone_name) + ); +} + +const char *lua_get_zone_long_name_by_id(uint32 zone_id) { + return ZoneLongName(zone_id); +} + const char *lua_get_zone_short_name() { if(!zone) return ""; @@ -1622,6 +1161,10 @@ const char *lua_get_zone_short_name() { return zone->GetShortName(); } +const char *lua_get_zone_short_name_by_id(uint32 zone_id) { + return ZoneName(zone_id); +} + int lua_get_zone_instance_id() { if(!zone) return 0; @@ -1714,6 +1257,7 @@ void lua_add_spawn_point(luabind::adl::object table) { uint32 variance; uint32 timeleft = 0; uint32 grid = 0; + bool path_when_zone_idle = false; int condition_id = 0; int condition_min_value = 0; bool enabled = true; @@ -1823,6 +1367,14 @@ void lua_add_spawn_point(luabind::adl::object table) { } } + cur = table["path_when_zone_idle"]; + if(luabind::type(cur) != LUA_TNIL) { + try { + path_when_zone_idle = luabind::object_cast(cur); + } catch(luabind::cast_failed &) { + } + } + cur = table["condition_id"]; if(luabind::type(cur) != LUA_TNIL) { try { @@ -1857,8 +1409,10 @@ void lua_add_spawn_point(luabind::adl::object table) { lua_remove_spawn_point(spawn2_id); - auto t = new Spawn2(spawn2_id, spawngroup_id, x, y, z, heading, respawn, variance, timeleft, grid, - condition_id, condition_min_value, enabled, static_cast(animation)); + auto t = new Spawn2(spawn2_id, spawngroup_id, x, y, z, heading, respawn, + variance, timeleft, grid, path_when_zone_idle, condition_id, + condition_min_value, enabled, static_cast(animation)); + zone->spawn2_list.Insert(t); } } @@ -1899,6 +1453,10 @@ Lua_ItemInst lua_get_quest_item() { return quest_manager.GetQuestItem(); } +Lua_Spell lua_get_quest_spell() { + return quest_manager.GetQuestSpell(); +} + std::string lua_get_encounter() { return quest_manager.GetEncounter(); } @@ -2185,7 +1743,7 @@ bool lua_is_content_flag_enabled(std::string content_flag){ } void lua_set_content_flag(std::string flag_name, bool enabled){ - ZoneStore::SetContentFlag(flag_name, enabled); + content_service.SetContentFlag(flag_name, enabled); } Lua_Expedition lua_get_expedition() { @@ -2304,6 +1862,1519 @@ std::string lua_seconds_to_time(int duration) { return quest_manager.secondstotime(duration); } +std::string lua_get_hex_color_code(std::string color_name) { + return quest_manager.gethexcolorcode(color_name); +} + +double lua_get_aa_exp_modifier_by_char_id(uint32 character_id, uint32 zone_id) { + return database.GetAAEXPModifier(character_id, zone_id); +} + +double lua_get_exp_modifier_by_char_id(uint32 character_id, uint32 zone_id) { + return database.GetEXPModifier(character_id, zone_id); +} + +void lua_set_aa_exp_modifier_by_char_id(uint32 character_id, uint32 zone_id, double aa_modifier) { + database.SetAAEXPModifier(character_id, zone_id, aa_modifier); +} + +void lua_set_exp_modifier_by_char_id(uint32 character_id, uint32 zone_id, double exp_modifier) { + database.SetEXPModifier(character_id, zone_id, exp_modifier); +} + +void lua_add_ldon_loss(uint32 theme_id) { + quest_manager.addldonloss(theme_id); +} + +void lua_add_ldon_points(uint32 theme_id, int points) { + quest_manager.addldonpoints(theme_id, points); +} + +void lua_add_ldon_win(uint32 theme_id) { + quest_manager.addldonwin(theme_id); +} + +void lua_remove_ldon_loss(uint32 theme_id) { + quest_manager.removeldonloss(theme_id); +} + +void lua_remove_ldon_win(uint32 theme_id) { + quest_manager.addldonwin(theme_id); +} + +std::string lua_get_clean_npc_name_by_id(uint32 npc_id) { + return quest_manager.getcleannpcnamebyid(npc_id); +} + +std::string lua_get_gender_name(uint32 gender_id) { + return quest_manager.getgendername(gender_id); +} + +std::string lua_get_deity_name(uint32 deity_id) { + return quest_manager.getdeityname(deity_id); +} + +std::string lua_get_inventory_slot_name(int16 slot_id) { + return quest_manager.getinventoryslotname(slot_id); +} + +void lua_rename(std::string name) { + quest_manager.rename(name); +} + +std::string lua_get_data_remaining(std::string bucket_name) { + return DataBucket::GetDataRemaining(bucket_name); +} + +int lua_get_item_stat(uint32 item_id, std::string stat_identifier) { + return quest_manager.getitemstat(item_id, stat_identifier); +} + +int lua_get_spell_stat(uint32 spell_id, std::string stat_identifier) { + return quest_manager.getspellstat(spell_id, stat_identifier); +} + +int lua_get_spell_stat(uint32 spell_id, std::string stat_identifier, uint8 slot) { + return quest_manager.getspellstat(spell_id, stat_identifier, slot); +} + +void lua_cross_zone_add_ldon_loss_by_char_id(int character_id, uint32 theme_id) { + uint8 update_type = CZUpdateType_Character; + uint8 update_subtype = CZLDoNUpdateSubtype_AddLoss; + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, character_id, theme_id); +} + +void lua_cross_zone_add_ldon_loss_by_group_id(int group_id, uint32 theme_id) { + uint8 update_type = CZUpdateType_Group; + uint8 update_subtype = CZLDoNUpdateSubtype_AddLoss; + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, group_id, theme_id); +} + +void lua_cross_zone_add_ldon_loss_by_raid_id(int raid_id, uint32 theme_id) { + uint8 update_type = CZUpdateType_Raid; + uint8 update_subtype = CZLDoNUpdateSubtype_AddLoss; + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, raid_id, theme_id); +} + +void lua_cross_zone_add_ldon_loss_by_guild_id(int guild_id, uint32 theme_id) { + uint8 update_type = CZUpdateType_Guild; + uint8 update_subtype = CZLDoNUpdateSubtype_AddLoss; + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, guild_id, theme_id); +} + +void lua_cross_zone_add_ldon_loss_by_expedition_id(uint32 expedition_id, uint32 theme_id) { + uint8 update_type = CZUpdateType_Expedition; + uint8 update_subtype = CZLDoNUpdateSubtype_AddLoss; + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, expedition_id, theme_id); +} + +void lua_cross_zone_add_ldon_loss_by_client_name(const char* client_name, uint32 theme_id) { + uint8 update_type = CZUpdateType_ClientName; + uint8 update_subtype = CZLDoNUpdateSubtype_AddLoss; + int update_identifier = 0; + int points = 1; + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, update_identifier, theme_id, points, client_name); +} + +void lua_cross_zone_add_ldon_points_by_char_id(int character_id, uint32 theme_id, int points) { + uint8 update_type = CZUpdateType_Character; + uint8 update_subtype = CZLDoNUpdateSubtype_AddPoints; + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, character_id, theme_id, points); +} + +void lua_cross_zone_add_ldon_points_by_group_id(int group_id, uint32 theme_id, int points) { + uint8 update_type = CZUpdateType_Group; + uint8 update_subtype = CZLDoNUpdateSubtype_AddPoints; + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, group_id, theme_id, points); +} + +void lua_cross_zone_add_ldon_points_by_raid_id(int raid_id, uint32 theme_id, int points) { + uint8 update_type = CZUpdateType_Raid; + uint8 update_subtype = CZLDoNUpdateSubtype_AddPoints; + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, raid_id, theme_id, points); +} + +void lua_cross_zone_add_ldon_points_by_guild_id(int guild_id, uint32 theme_id, int points) { + uint8 update_type = CZUpdateType_Guild; + uint8 update_subtype = CZLDoNUpdateSubtype_AddPoints; + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, guild_id, theme_id, points); +} + +void lua_cross_zone_add_ldon_points_by_expedition_id(uint32 expedition_id, uint32 theme_id, int points) { + uint8 update_type = CZUpdateType_Expedition; + uint8 update_subtype = CZLDoNUpdateSubtype_AddPoints; + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, expedition_id, theme_id, points); +} + +void lua_cross_zone_add_ldon_points_by_client_name(const char* client_name, uint32 theme_id, int points) { + uint8 update_type = CZUpdateType_ClientName; + uint8 update_subtype = CZLDoNUpdateSubtype_AddPoints; + int update_identifier = 0; + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, update_identifier, theme_id, points, client_name); +} + +void lua_cross_zone_add_ldon_win_by_char_id(int character_id, uint32 theme_id) { + uint8 update_type = CZUpdateType_Character; + uint8 update_subtype = CZLDoNUpdateSubtype_AddWin; + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, character_id, theme_id); +} + +void lua_cross_zone_add_ldon_win_by_group_id(int group_id, uint32 theme_id) { + uint8 update_type = CZUpdateType_Group; + uint8 update_subtype = CZLDoNUpdateSubtype_AddWin; + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, group_id, theme_id); +} + +void lua_cross_zone_add_ldon_win_by_raid_id(int raid_id, uint32 theme_id) { + uint8 update_type = CZUpdateType_Raid; + uint8 update_subtype = CZLDoNUpdateSubtype_AddWin; + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, raid_id, theme_id); +} + +void lua_cross_zone_add_ldon_win_by_guild_id(int guild_id, uint32 theme_id) { + uint8 update_type = CZUpdateType_Guild; + uint8 update_subtype = CZLDoNUpdateSubtype_AddWin; + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, guild_id, theme_id); +} + +void lua_cross_zone_add_ldon_win_by_expedition_id(uint32 expedition_id, uint32 theme_id) { + uint8 update_type = CZUpdateType_Expedition; + uint8 update_subtype = CZLDoNUpdateSubtype_AddWin; + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, expedition_id, theme_id); +} + +void lua_cross_zone_add_ldon_win_by_client_name(const char* client_name, uint32 theme_id) { + uint8 update_type = CZUpdateType_ClientName; + uint8 update_subtype = CZLDoNUpdateSubtype_AddWin; + int update_identifier = 0; + int points = 1; + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, update_identifier, theme_id, points, client_name); +} + +void lua_cross_zone_assign_task_by_char_id(int character_id, uint32 task_id) { + uint8 update_type = CZUpdateType_Character; + uint8 update_subtype = CZTaskUpdateSubtype_AssignTask; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, character_id, task_id, task_subidentifier, update_count, enforce_level_requirement); +} + +void lua_cross_zone_assign_task_by_char_id(int character_id, uint32 task_id, bool enforce_level_requirement) { + uint8 update_type = CZUpdateType_Character; + uint8 update_subtype = CZTaskUpdateSubtype_AssignTask; + int task_subidentifier = -1; + int update_count = 1; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, character_id, task_id, task_subidentifier, update_count, enforce_level_requirement); +} + +void lua_cross_zone_assign_task_by_group_id(int group_id, uint32 task_id) { + uint8 update_type = CZUpdateType_Group; + uint8 update_subtype = CZTaskUpdateSubtype_AssignTask; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, group_id, task_id, task_subidentifier, update_count, enforce_level_requirement); +} + +void lua_cross_zone_assign_task_by_group_id(int group_id, uint32 task_id, bool enforce_level_requirement) { + uint8 update_type = CZUpdateType_Group; + uint8 update_subtype = CZTaskUpdateSubtype_AssignTask; + int task_subidentifier = -1; + int update_count = 1; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, group_id, task_id, task_subidentifier, update_count, enforce_level_requirement); +} + +void lua_cross_zone_assign_task_by_raid_id(int raid_id, uint32 task_id) { + uint8 update_type = CZUpdateType_Raid; + uint8 update_subtype = CZTaskUpdateSubtype_AssignTask; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, raid_id, task_id, task_subidentifier, update_count, enforce_level_requirement); +} + +void lua_cross_zone_assign_task_by_raid_id(int raid_id, uint32 task_id, bool enforce_level_requirement) { + uint8 update_type = CZUpdateType_Group; + uint8 update_subtype = CZTaskUpdateSubtype_AssignTask; + int task_subidentifier = -1; + int update_count = 1; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, raid_id, task_id, task_subidentifier, update_count, enforce_level_requirement); +} + +void lua_cross_zone_assign_task_by_guild_id(int guild_id, uint32 task_id) { + uint8 update_type = CZUpdateType_Guild; + uint8 update_subtype = CZTaskUpdateSubtype_AssignTask; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, guild_id, task_id, task_subidentifier, update_count, enforce_level_requirement); +} + +void lua_cross_zone_assign_task_by_guild_id(int guild_id, uint32 task_id, bool enforce_level_requirement) { + uint8 update_type = CZUpdateType_Guild; + uint8 update_subtype = CZTaskUpdateSubtype_AssignTask; + int task_subidentifier = -1; + int update_count = 1; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, guild_id, task_id, task_subidentifier, update_count, enforce_level_requirement); +} + +void lua_cross_zone_assign_task_by_expedition_id(uint32 expedition_id, uint32 task_id) { + uint8 update_type = CZUpdateType_Expedition; + uint8 update_subtype = CZTaskUpdateSubtype_AssignTask; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, expedition_id, task_id, task_subidentifier, update_count, enforce_level_requirement); +} + +void lua_cross_zone_assign_task_by_expedition_id(uint32 expedition_id, uint32 task_id, bool enforce_level_requirement) { + uint8 update_type = CZUpdateType_Expedition; + uint8 update_subtype = CZTaskUpdateSubtype_AssignTask; + int task_subidentifier = -1; + int update_count = 1; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, expedition_id, task_id, task_subidentifier, update_count, enforce_level_requirement); +} + +void lua_cross_zone_assign_task_by_client_name(const char* client_name, uint32 task_id) { + uint8 update_type = CZUpdateType_ClientName; + uint8 update_subtype = CZTaskUpdateSubtype_AssignTask; + int update_identifier = 0; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, update_identifier, task_id, task_subidentifier, update_count, enforce_level_requirement, client_name); +} + +void lua_cross_zone_assign_task_by_client_name(const char* client_name, uint32 task_id, bool enforce_level_requirement) { + uint8 update_type = CZUpdateType_ClientName; + uint8 update_subtype = CZTaskUpdateSubtype_AssignTask; + int update_identifier = 0; + int task_subidentifier = -1; + int update_count = 1; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, update_identifier, task_id, task_subidentifier, update_count, enforce_level_requirement, client_name); +} + +void lua_cross_zone_cast_spell_by_char_id(int character_id, uint32 spell_id) { + uint8 update_type = CZUpdateType_Character; + uint8 update_subtype = CZSpellUpdateSubtype_Cast; + quest_manager.CrossZoneSpell(update_type, update_subtype, character_id, spell_id); +} + +void lua_cross_zone_cast_spell_by_group_id(int group_id, uint32 spell_id) { + uint8 update_type = CZUpdateType_Group; + uint8 update_subtype = CZSpellUpdateSubtype_Cast; + quest_manager.CrossZoneSpell(update_type, update_subtype, group_id, spell_id); +} + +void lua_cross_zone_cast_spell_by_raid_id(int raid_id, uint32 spell_id) { + uint8 update_type = CZUpdateType_Raid; + uint8 update_subtype = CZSpellUpdateSubtype_Cast; + quest_manager.CrossZoneSpell(update_type, update_subtype, raid_id, spell_id); +} + +void lua_cross_zone_cast_spell_by_guild_id(int guild_id, uint32 spell_id) { + uint8 update_type = CZUpdateType_Guild; + uint8 update_subtype = CZSpellUpdateSubtype_Cast; + quest_manager.CrossZoneSpell(update_type, update_subtype, guild_id, spell_id); +} + +void lua_cross_zone_cast_spell_by_expedition_id(uint32 expedition_id, uint32 spell_id) { + uint8 update_type = CZUpdateType_Expedition; + uint8 update_subtype = CZSpellUpdateSubtype_Cast; + quest_manager.CrossZoneSpell(update_type, update_subtype, expedition_id, spell_id); +} + +void lua_cross_zone_cast_spell_by_client_name(const char* client_name, uint32 spell_id) { + uint8 update_type = CZUpdateType_ClientName; + uint8 update_subtype = CZSpellUpdateSubtype_Cast; + int update_identifier = 0; + quest_manager.CrossZoneSpell(update_type, update_subtype, update_identifier, spell_id, client_name); +} + +void lua_cross_zone_dialogue_window_by_char_id(int character_id, const char* message) { + uint8 update_type = CZUpdateType_Character; + quest_manager.CrossZoneDialogueWindow(update_type, character_id, message); +} + +void lua_cross_zone_dialogue_window_by_group_id(int group_id, const char* message) { + uint8 update_type = CZUpdateType_Group; + quest_manager.CrossZoneDialogueWindow(update_type, group_id, message); +} + +void lua_cross_zone_dialogue_window_by_raid_id(int raid_id, const char* message) { + uint8 update_type = CZUpdateType_Raid; + quest_manager.CrossZoneDialogueWindow(update_type, raid_id, message); +} + +void lua_cross_zone_dialogue_window_by_guild_id(int guild_id, const char* message) { + uint8 update_type = CZUpdateType_Guild; + quest_manager.CrossZoneDialogueWindow(update_type, guild_id, message); +} + +void lua_cross_zone_dialogue_window_by_expedition_id(uint32 expedition_id, const char* message) { + uint8 update_type = CZUpdateType_Expedition; + quest_manager.CrossZoneDialogueWindow(update_type, expedition_id, message); +} + +void lua_cross_zone_dialogue_window_by_client_name(const char* client_name, const char* message) { + uint8 update_type = CZUpdateType_ClientName; + int update_identifier = 0; + quest_manager.CrossZoneDialogueWindow(update_type, update_identifier, message, client_name); +} + +void lua_cross_zone_disable_task_by_char_id(int character_id, uint32 task_id) { + uint8 update_type = CZUpdateType_Character; + uint8 update_subtype = CZTaskUpdateSubtype_DisableTask; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, character_id, task_id, task_subidentifier, update_count, enforce_level_requirement); +} + +void lua_cross_zone_disable_task_by_group_id(int group_id, uint32 task_id) { + uint8 update_type = CZUpdateType_Group; + uint8 update_subtype = CZTaskUpdateSubtype_DisableTask; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, group_id, task_id, task_subidentifier, update_count, enforce_level_requirement); +} + +void lua_cross_zone_disable_task_by_raid_id(int raid_id, uint32 task_id) { + uint8 update_type = CZUpdateType_Raid; + uint8 update_subtype = CZTaskUpdateSubtype_DisableTask; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, raid_id, task_id, task_subidentifier, update_count, enforce_level_requirement); +} + +void lua_cross_zone_disable_task_by_guild_id(int guild_id, uint32 task_id) { + uint8 update_type = CZUpdateType_Guild; + uint8 update_subtype = CZTaskUpdateSubtype_DisableTask; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, guild_id, task_id, task_subidentifier, update_count, enforce_level_requirement); +} + +void lua_cross_zone_disable_task_by_expedition_id(uint32 expedition_id, uint32 task_id) { + uint8 update_type = CZUpdateType_Expedition; + uint8 update_subtype = CZTaskUpdateSubtype_DisableTask; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, expedition_id, task_id, task_subidentifier, update_count, enforce_level_requirement); +} + +void lua_cross_zone_disable_task_by_client_name(const char* client_name, uint32 task_id) { + uint8 update_type = CZUpdateType_ClientName; + uint8 update_subtype = CZTaskUpdateSubtype_DisableTask; + int update_identifier = 0; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, update_identifier, task_id, task_subidentifier, update_count, enforce_level_requirement, client_name); +} + +void lua_cross_zone_enable_task_by_char_id(int character_id, uint32 task_id) { + uint8 update_type = CZUpdateType_Character; + uint8 update_subtype = CZTaskUpdateSubtype_EnableTask; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, character_id, task_id, task_subidentifier, update_count, enforce_level_requirement); +} + +void lua_cross_zone_enable_task_by_group_id(int group_id, uint32 task_id) { + uint8 update_type = CZUpdateType_Group; + uint8 update_subtype = CZTaskUpdateSubtype_EnableTask; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, group_id, task_id, task_subidentifier, update_count, enforce_level_requirement); +} + +void lua_cross_zone_enable_task_by_raid_id(int raid_id, uint32 task_id) { + uint8 update_type = CZUpdateType_Raid; + uint8 update_subtype = CZTaskUpdateSubtype_EnableTask; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, raid_id, task_id, task_subidentifier, update_count, enforce_level_requirement); +} + +void lua_cross_zone_enable_task_by_guild_id(int guild_id, uint32 task_id) { + uint8 update_type = CZUpdateType_Guild; + uint8 update_subtype = CZTaskUpdateSubtype_EnableTask; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, guild_id, task_id, task_subidentifier, update_count, enforce_level_requirement); +} + +void lua_cross_zone_enable_task_by_expedition_id(uint32 expedition_id, uint32 task_id) { + uint8 update_type = CZUpdateType_Expedition; + uint8 update_subtype = CZTaskUpdateSubtype_EnableTask; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, expedition_id, task_id, task_subidentifier, update_count, enforce_level_requirement); +} + +void lua_cross_zone_enable_task_by_client_name(const char* client_name, uint32 task_id) { + uint8 update_type = CZUpdateType_ClientName; + uint8 update_subtype = CZTaskUpdateSubtype_EnableTask; + int update_identifier = 0; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, update_identifier, task_id, task_subidentifier, update_count, enforce_level_requirement, client_name); +} + +void lua_cross_zone_fail_task_by_char_id(int character_id, uint32 task_id) { + uint8 update_type = CZUpdateType_Character; + uint8 update_subtype = CZTaskUpdateSubtype_FailTask; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, character_id, task_id, task_subidentifier, update_count, enforce_level_requirement); +} + +void lua_cross_zone_fail_task_by_group_id(int group_id, uint32 task_id) { + uint8 update_type = CZUpdateType_Group; + uint8 update_subtype = CZTaskUpdateSubtype_FailTask; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, group_id, task_id, task_subidentifier, update_count, enforce_level_requirement); +} + +void lua_cross_zone_fail_task_by_raid_id(int raid_id, uint32 task_id) { + uint8 update_type = CZUpdateType_Raid; + uint8 update_subtype = CZTaskUpdateSubtype_FailTask; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, raid_id, task_id, task_subidentifier, update_count, enforce_level_requirement); +} + +void lua_cross_zone_fail_task_by_guild_id(int guild_id, uint32 task_id) { + uint8 update_type = CZUpdateType_Guild; + uint8 update_subtype = CZTaskUpdateSubtype_FailTask; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, guild_id, task_id, task_subidentifier, update_count, enforce_level_requirement); +} + +void lua_cross_zone_fail_task_by_expedition_id(uint32 expedition_id, uint32 task_id) { + uint8 update_type = CZUpdateType_Expedition; + uint8 update_subtype = CZTaskUpdateSubtype_FailTask; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, expedition_id, task_id, task_subidentifier, update_count, enforce_level_requirement); +} + +void lua_cross_zone_fail_task_by_client_name(const char* client_name, uint32 task_id) { + uint8 update_type = CZUpdateType_ClientName; + uint8 update_subtype = CZTaskUpdateSubtype_FailTask; + int update_identifier = 0; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, update_identifier, task_id, task_subidentifier, update_count, enforce_level_requirement, client_name); +} + +void lua_cross_zone_marquee_by_char_id(int character_id, uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char* message) { + uint8 update_type = CZUpdateType_Character; + quest_manager.CrossZoneMarquee(update_type, character_id, type, priority, fade_in, fade_out, duration, message); +} + +void lua_cross_zone_marquee_by_group_id(int group_id, uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char* message) { + uint8 update_type = CZUpdateType_Group; + quest_manager.CrossZoneMarquee(update_type, group_id, type, priority, fade_in, fade_out, duration, message); +} + +void lua_cross_zone_marquee_by_raid_id(int raid_id, uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char* message) { + uint8 update_type = CZUpdateType_Raid; + quest_manager.CrossZoneMarquee(update_type, raid_id, type, priority, fade_in, fade_out, duration, message); +} + +void lua_cross_zone_marquee_by_guild_id(int guild_id, uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char* message) { + uint8 update_type = CZUpdateType_Guild; + quest_manager.CrossZoneMarquee(update_type, guild_id, type, priority, fade_in, fade_out, duration, message); +} + +void lua_cross_zone_marquee_by_expedition_id(uint32 expedition_id, uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char* message) { + uint8 update_type = CZUpdateType_Expedition; + quest_manager.CrossZoneMarquee(update_type, expedition_id, type, priority, fade_in, fade_out, duration, message); +} + +void lua_cross_zone_marquee_by_client_name(const char* client_name, uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char* message) { + uint8 update_type = CZUpdateType_ClientName; + int update_identifier = 0; + quest_manager.CrossZoneMarquee(update_type, update_identifier, type, priority, fade_in, fade_out, duration, message, client_name); +} + +void lua_cross_zone_message_player_by_char_id(uint32 type, int character_id, const char* message) { + uint8 update_type = CZUpdateType_Character; + quest_manager.CrossZoneMessage(update_type, character_id, type, message); +} + +void lua_cross_zone_message_player_by_group_id(uint32 type, int group_id, const char* message) { + uint8 update_type = CZUpdateType_Group; + quest_manager.CrossZoneMessage(update_type, group_id, type, message); +} + +void lua_cross_zone_message_player_by_raid_id(uint32 type, int raid_id, const char* message) { + uint8 update_type = CZUpdateType_Raid; + quest_manager.CrossZoneMessage(update_type, raid_id, type, message); +} + +void lua_cross_zone_message_player_by_guild_id(uint32 type, int guild_id, const char* message) { + uint8 update_type = CZUpdateType_Guild; + quest_manager.CrossZoneMessage(update_type, guild_id, type, message); +} + +void lua_cross_zone_message_player_by_expedition_id(uint32 type, int expedition_id, const char* message) { + uint8 update_type = CZUpdateType_Expedition; + quest_manager.CrossZoneMessage(update_type, expedition_id, type, message); +} + +void lua_cross_zone_message_player_by_name(uint32 type, const char* client_name, const char* message) { + uint8 update_type = CZUpdateType_ClientName; + int update_identifier = 0; + quest_manager.CrossZoneMessage(update_type, update_identifier, type, message, client_name); +} + +void lua_cross_zone_move_player_by_char_id(int character_id, const char* zone_short_name) { + uint8 update_type = CZUpdateType_Character; + uint8 update_subtype = CZMoveUpdateSubtype_MoveZone; + uint16 instance_id = 0; + quest_manager.CrossZoneMove(update_type, update_subtype, character_id, zone_short_name, instance_id); +} + +void lua_cross_zone_move_player_by_group_id(int group_id, const char* zone_short_name) { + uint8 update_type = CZUpdateType_Group; + uint8 update_subtype = CZMoveUpdateSubtype_MoveZone; + uint16 instance_id = 0; + quest_manager.CrossZoneMove(update_type, update_subtype, group_id, zone_short_name, instance_id); +} + +void lua_cross_zone_move_player_by_raid_id(int raid_id, const char* zone_short_name) { + uint8 update_type = CZUpdateType_Raid; + uint8 update_subtype = CZMoveUpdateSubtype_MoveZone; + uint16 instance_id = 0; + quest_manager.CrossZoneMove(update_type, update_subtype, raid_id, zone_short_name, instance_id); +} + +void lua_cross_zone_move_player_by_guild_id(int guild_id, const char* zone_short_name) { + uint8 update_type = CZUpdateType_Guild; + uint8 update_subtype = CZMoveUpdateSubtype_MoveZone; + uint16 instance_id = 0; + quest_manager.CrossZoneMove(update_type, update_subtype, guild_id, zone_short_name, instance_id); +} + +void lua_cross_zone_move_player_by_expedition_id(int expedition_id, const char* zone_short_name) { + uint8 update_type = CZUpdateType_Expedition; + uint8 update_subtype = CZMoveUpdateSubtype_MoveZone; + uint16 instance_id = 0; + quest_manager.CrossZoneMove(update_type, update_subtype, expedition_id, zone_short_name, instance_id); +} + +void lua_cross_zone_move_player_by_client_name(const char* client_name, const char* zone_short_name) { + uint8 update_type = CZUpdateType_Character; + uint8 update_subtype = CZMoveUpdateSubtype_MoveZone; + int update_identifier = 0; + uint16 instance_id = 0; + quest_manager.CrossZoneMove(update_type, update_subtype, update_identifier, zone_short_name, instance_id, client_name); +} + +void lua_cross_zone_move_instance_by_char_id(int character_id, uint16 instance_id) { + uint8 update_type = CZUpdateType_Character; + uint8 update_subtype = CZMoveUpdateSubtype_MoveZoneInstance; + const char* zone_short_name = ""; + quest_manager.CrossZoneMove(update_type, update_subtype, character_id, zone_short_name, instance_id); +} + +void lua_cross_zone_move_instance_by_group_id(int group_id, uint16 instance_id) { + uint8 update_type = CZUpdateType_Group; + uint8 update_subtype = CZMoveUpdateSubtype_MoveZoneInstance; + const char* zone_short_name = ""; + quest_manager.CrossZoneMove(update_type, update_subtype, group_id, zone_short_name, instance_id); +} + +void lua_cross_zone_move_instance_by_raid_id(int raid_id, uint16 instance_id) { + uint8 update_type = CZUpdateType_Raid; + uint8 update_subtype = CZMoveUpdateSubtype_MoveZoneInstance; + const char* zone_short_name = ""; + quest_manager.CrossZoneMove(update_type, update_subtype, raid_id, zone_short_name, instance_id); +} + +void lua_cross_zone_move_instance_by_guild_id(int guild_id, uint16 instance_id) { + uint8 update_type = CZUpdateType_Guild; + uint8 update_subtype = CZMoveUpdateSubtype_MoveZoneInstance; + const char* zone_short_name = ""; + quest_manager.CrossZoneMove(update_type, update_subtype, guild_id, zone_short_name, instance_id); +} + +void lua_cross_zone_move_instance_by_expedition_id(uint32 expedition_id, uint16 instance_id) { + uint8 update_type = CZUpdateType_Expedition; + uint8 update_subtype = CZMoveUpdateSubtype_MoveZoneInstance; + const char* zone_short_name = ""; + quest_manager.CrossZoneMove(update_type, update_subtype, expedition_id, zone_short_name, instance_id); +} + +void lua_cross_zone_move_instance_by_client_name(const char* client_name, uint16 instance_id) { + uint8 update_type = CZUpdateType_ClientName; + uint8 update_subtype = CZMoveUpdateSubtype_MoveZoneInstance; + int update_identifier = 0; + const char* zone_short_name = ""; + quest_manager.CrossZoneMove(update_type, update_subtype, update_identifier, zone_short_name, instance_id, client_name); +} + +void lua_cross_zone_remove_ldon_loss_by_char_id(int character_id, uint32 theme_id) { + uint8 update_type = CZUpdateType_Character; + uint8 update_subtype = CZLDoNUpdateSubtype_RemoveLoss; + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, character_id, theme_id); +} + +void lua_cross_zone_remove_ldon_loss_by_group_id(int group_id, uint32 theme_id) { + uint8 update_type = CZUpdateType_Group; + uint8 update_subtype = CZLDoNUpdateSubtype_RemoveLoss; + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, group_id, theme_id); +} + +void lua_cross_zone_remove_ldon_loss_by_raid_id(int raid_id, uint32 theme_id) { + uint8 update_type = CZUpdateType_Raid; + uint8 update_subtype = CZLDoNUpdateSubtype_RemoveLoss; + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, raid_id, theme_id); +} + +void lua_cross_zone_remove_ldon_loss_by_guild_id(int guild_id, uint32 theme_id) { + uint8 update_type = CZUpdateType_Guild; + uint8 update_subtype = CZLDoNUpdateSubtype_RemoveLoss; + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, guild_id, theme_id); +} + +void lua_cross_zone_remove_ldon_loss_by_expedition_id(uint32 expedition_id, uint32 theme_id) { + uint8 update_type = CZUpdateType_Expedition; + uint8 update_subtype = CZLDoNUpdateSubtype_RemoveLoss; + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, expedition_id, theme_id); +} + +void lua_cross_zone_remove_ldon_loss_by_client_name(const char* client_name, uint32 theme_id) { + uint8 update_type = CZUpdateType_ClientName; + uint8 update_subtype = CZLDoNUpdateSubtype_RemoveLoss; + int update_identifier = 0; + int points = 1; + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, update_identifier, theme_id, points, client_name); +} + +void lua_cross_zone_remove_ldon_win_by_char_id(int character_id, uint32 theme_id) { + uint8 update_type = CZUpdateType_Character; + uint8 update_subtype = CZLDoNUpdateSubtype_RemoveWin; + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, character_id, theme_id); +} + +void lua_cross_zone_remove_ldon_win_by_group_id(int group_id, uint32 theme_id) { + uint8 update_type = CZUpdateType_Group; + uint8 update_subtype = CZLDoNUpdateSubtype_RemoveWin; + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, group_id, theme_id); +} + +void lua_cross_zone_remove_ldon_win_by_raid_id(int raid_id, uint32 theme_id) { + uint8 update_type = CZUpdateType_Raid; + uint8 update_subtype = CZLDoNUpdateSubtype_RemoveWin; + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, raid_id, theme_id); +} + +void lua_cross_zone_remove_ldon_win_by_guild_id(int guild_id, uint32 theme_id) { + uint8 update_type = CZUpdateType_Guild; + uint8 update_subtype = CZLDoNUpdateSubtype_RemoveWin; + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, guild_id, theme_id); +} + +void lua_cross_zone_remove_ldon_win_by_expedition_id(uint32 expedition_id, uint32 theme_id) { + uint8 update_type = CZUpdateType_Expedition; + uint8 update_subtype = CZLDoNUpdateSubtype_RemoveWin; + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, expedition_id, theme_id); +} + +void lua_cross_zone_remove_ldon_win_by_client_name(const char* client_name, uint32 theme_id) { + uint8 update_type = CZUpdateType_ClientName; + uint8 update_subtype = CZLDoNUpdateSubtype_RemoveWin; + int update_identifier = 0; + int points = 1; + quest_manager.CrossZoneLDoNUpdate(update_type, update_subtype, update_identifier, theme_id, points, client_name); +} + +void lua_cross_zone_remove_spell_by_char_id(int character_id, uint32 spell_id) { + uint8 update_type = CZUpdateType_Character; + uint8 update_subtype = CZSpellUpdateSubtype_Remove; + quest_manager.CrossZoneSpell(update_type, update_subtype, character_id, spell_id); +} + +void lua_cross_zone_remove_spell_by_group_id(int group_id, uint32 spell_id) { + uint8 update_type = CZUpdateType_Group; + uint8 update_subtype = CZSpellUpdateSubtype_Remove; + quest_manager.CrossZoneSpell(update_type, update_subtype, group_id, spell_id); +} + +void lua_cross_zone_remove_spell_by_raid_id(int raid_id, uint32 spell_id) { + uint8 update_type = CZUpdateType_Raid; + uint8 update_subtype = CZSpellUpdateSubtype_Remove; + quest_manager.CrossZoneSpell(update_type, update_subtype, raid_id, spell_id); +} + +void lua_cross_zone_remove_spell_by_guild_id(int guild_id, uint32 spell_id) { + uint8 update_type = CZUpdateType_Guild; + uint8 update_subtype = CZSpellUpdateSubtype_Remove; + quest_manager.CrossZoneSpell(update_type, update_subtype, guild_id, spell_id); +} + +void lua_cross_zone_remove_spell_by_expedition_id(uint32 expedition_id, uint32 spell_id) { + uint8 update_type = CZUpdateType_Expedition; + uint8 update_subtype = CZSpellUpdateSubtype_Remove; + quest_manager.CrossZoneSpell(update_type, update_subtype, expedition_id, spell_id); +} + +void lua_cross_zone_remove_spell_by_client_name(const char* client_name, uint32 spell_id) { + uint8 update_type = CZUpdateType_ClientName; + uint8 update_subtype = CZSpellUpdateSubtype_Remove; + int update_identifier = 0; + quest_manager.CrossZoneSpell(update_type, update_subtype, update_identifier, spell_id, client_name); +} + +void lua_cross_zone_remove_task_by_char_id(int character_id, uint32 task_id) { + uint8 update_type = CZUpdateType_Character; + uint8 update_subtype = CZTaskUpdateSubtype_RemoveTask; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, character_id, task_id, task_subidentifier, update_count, enforce_level_requirement); +} + +void lua_cross_zone_remove_task_by_group_id(int group_id, uint32 task_id) { + uint8 update_type = CZUpdateType_Group; + uint8 update_subtype = CZTaskUpdateSubtype_RemoveTask; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, group_id, task_id, task_subidentifier, update_count, enforce_level_requirement); +} + +void lua_cross_zone_remove_task_by_raid_id(int raid_id, uint32 task_id) { + uint8 update_type = CZUpdateType_Raid; + uint8 update_subtype = CZTaskUpdateSubtype_RemoveTask; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, raid_id, task_id, task_subidentifier, update_count, enforce_level_requirement); +} + +void lua_cross_zone_remove_task_by_guild_id(int guild_id, uint32 task_id) { + uint8 update_type = CZUpdateType_Guild; + uint8 update_subtype = CZTaskUpdateSubtype_RemoveTask; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, guild_id, task_id, task_subidentifier, update_count, enforce_level_requirement); +} + +void lua_cross_zone_remove_task_by_expedition_id(uint32 expedition_id, uint32 task_id) { + uint8 update_type = CZUpdateType_Expedition; + uint8 update_subtype = CZTaskUpdateSubtype_RemoveTask; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, expedition_id, task_id, task_subidentifier, update_count, enforce_level_requirement); +} + +void lua_cross_zone_remove_task_by_client_name(const char* client_name, uint32 task_id) { + uint8 update_type = CZUpdateType_ClientName; + uint8 update_subtype = CZTaskUpdateSubtype_RemoveTask; + int update_identifier = 0; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, update_identifier, task_id, task_subidentifier, update_count, enforce_level_requirement, client_name); +} + +void lua_cross_zone_reset_activity_by_char_id(int character_id, uint32 task_id, int activity_id) { + uint8 update_type = CZUpdateType_Character; + uint8 update_subtype = CZTaskUpdateSubtype_ActivityReset; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, character_id, task_id, activity_id, update_count, enforce_level_requirement); +} + +void lua_cross_zone_reset_activity_by_group_id(int group_id, uint32 task_id, int activity_id) { + uint8 update_type = CZUpdateType_Group; + uint8 update_subtype = CZTaskUpdateSubtype_ActivityReset; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, group_id, task_id, activity_id, update_count, enforce_level_requirement); +} + +void lua_cross_zone_reset_activity_by_raid_id(int raid_id, uint32 task_id, int activity_id) { + uint8 update_type = CZUpdateType_Raid; + uint8 update_subtype = CZTaskUpdateSubtype_ActivityReset; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, raid_id, task_id, activity_id, update_count, enforce_level_requirement); +} + +void lua_cross_zone_reset_activity_by_guild_id(int guild_id, uint32 task_id, int activity_id) { + uint8 update_type = CZUpdateType_Guild; + uint8 update_subtype = CZTaskUpdateSubtype_ActivityReset; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, guild_id, task_id, activity_id, update_count, enforce_level_requirement); +} + +void lua_cross_zone_reset_activity_by_expedition_id(uint32 expedition_id, uint32 task_id, int activity_id) { + uint8 update_type = CZUpdateType_Expedition; + uint8 update_subtype = CZTaskUpdateSubtype_ActivityReset; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, expedition_id, task_id, activity_id, update_count, enforce_level_requirement); +} + +void lua_cross_zone_reset_activity_by_client_name(const char* client_name, uint32 task_id, int activity_id) { + uint8 update_type = CZUpdateType_Guild; + uint8 update_subtype = CZTaskUpdateSubtype_ActivityReset; + int update_identifier = 0; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, update_identifier, task_id, activity_id, update_count, enforce_level_requirement, client_name); +} + +void lua_cross_zone_set_entity_variable_by_char_id(int character_id, const char* variable_name, const char* variable_value) { + uint8 update_type = CZUpdateType_Character; + quest_manager.CrossZoneSetEntityVariable(update_type, character_id, variable_name, variable_value); +} + +void lua_cross_zone_set_entity_variable_by_group_id(int group_id, const char* variable_name, const char* variable_value) { + uint8 update_type = CZUpdateType_Group; + quest_manager.CrossZoneSetEntityVariable(update_type, group_id, variable_name, variable_value); +} + +void lua_cross_zone_set_entity_variable_by_raid_id(int raid_id, const char* variable_name, const char* variable_value) { + uint8 update_type = CZUpdateType_Raid; + quest_manager.CrossZoneSetEntityVariable(update_type, raid_id, variable_name, variable_value); +} + +void lua_cross_zone_set_entity_variable_by_guild_id(int guild_id, const char* variable_name, const char* variable_value) { + uint8 update_type = CZUpdateType_Guild; + quest_manager.CrossZoneSetEntityVariable(update_type, guild_id, variable_name, variable_value); +} + +void lua_cross_zone_set_entity_variable_by_expedition_id(uint32 expedition_id, const char* variable_name, const char* variable_value) { + uint8 update_type = CZUpdateType_Expedition; + quest_manager.CrossZoneSetEntityVariable(update_type, expedition_id, variable_name, variable_value); +} + +void lua_cross_zone_set_entity_variable_by_client_name(const char* character_name, const char* variable_name, const char* variable_value) { + uint8 update_type = CZUpdateType_ClientName; + int update_identifier = 0; + quest_manager.CrossZoneSetEntityVariable(update_type, update_identifier, variable_name, variable_value, character_name); +} + +void lua_cross_zone_signal_client_by_char_id(uint32 character_id, int signal) { + uint8 update_type = CZUpdateType_Character; + quest_manager.CrossZoneSignal(update_type, character_id, signal); +} + +void lua_cross_zone_signal_client_by_group_id(uint32 group_id, int signal) { + uint8 update_type = CZUpdateType_Group; + quest_manager.CrossZoneSignal(update_type, group_id, signal); +} + +void lua_cross_zone_signal_client_by_raid_id(uint32 raid_id, int signal) { + uint8 update_type = CZUpdateType_Raid; + quest_manager.CrossZoneSignal(update_type, raid_id, signal); +} + +void lua_cross_zone_signal_client_by_guild_id(uint32 guild_id, int signal) { + uint8 update_type = CZUpdateType_Guild; + quest_manager.CrossZoneSignal(update_type, guild_id, signal); +} + +void lua_cross_zone_signal_client_by_expedition_id(uint32 expedition_id, int signal) { + uint8 update_type = CZUpdateType_Expedition; + quest_manager.CrossZoneSignal(update_type, expedition_id, signal); +} + +void lua_cross_zone_signal_client_by_name(const char* client_name, int signal) { + uint8 update_type = CZUpdateType_ClientName; + int update_identifier = 0; + quest_manager.CrossZoneSignal(update_type, update_identifier, signal, client_name); +} + +void lua_cross_zone_signal_npc_by_npctype_id(uint32 npctype_id, int signal) { + uint8 update_type = CZUpdateType_NPC; + quest_manager.CrossZoneSignal(update_type, npctype_id, signal); +} + +void lua_cross_zone_update_activity_by_char_id(int character_id, uint32 task_id, int activity_id) { + uint8 update_type = CZUpdateType_Character; + uint8 update_subtype = CZTaskUpdateSubtype_ActivityUpdate; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, character_id, task_id, activity_id, update_count, enforce_level_requirement); +} + +void lua_cross_zone_update_activity_by_char_id(int character_id, uint32 task_id, int activity_id, int activity_count) { + uint8 update_type = CZUpdateType_Character; + uint8 update_subtype = CZTaskUpdateSubtype_ActivityUpdate; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, character_id, task_id, activity_id, activity_count, enforce_level_requirement); +} + +void lua_cross_zone_update_activity_by_group_id(int group_id, uint32 task_id, int activity_id) { + uint8 update_type = CZUpdateType_Group; + uint8 update_subtype = CZTaskUpdateSubtype_ActivityUpdate; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, group_id, task_id, activity_id, update_count, enforce_level_requirement); +} + +void lua_cross_zone_update_activity_by_group_id(int group_id, uint32 task_id, int activity_id, int activity_count) { + uint8 update_type = CZUpdateType_Group; + uint8 update_subtype = CZTaskUpdateSubtype_ActivityUpdate; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, group_id, task_id, activity_id, activity_count, enforce_level_requirement); +} + +void lua_cross_zone_update_activity_by_raid_id(int raid_id, uint32 task_id, int activity_id) { + uint8 update_type = CZUpdateType_Raid; + uint8 update_subtype = CZTaskUpdateSubtype_ActivityUpdate; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, raid_id, task_id, activity_id, update_count, enforce_level_requirement); +} + +void lua_cross_zone_update_activity_by_raid_id(int raid_id, uint32 task_id, int activity_id, int activity_count) { + uint8 update_type = CZUpdateType_Raid; + uint8 update_subtype = CZTaskUpdateSubtype_ActivityUpdate; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, raid_id, task_id, activity_id, activity_count, enforce_level_requirement); +} + +void lua_cross_zone_update_activity_by_guild_id(int guild_id, uint32 task_id, int activity_id) { + uint8 update_type = CZUpdateType_Guild; + uint8 update_subtype = CZTaskUpdateSubtype_ActivityUpdate; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, guild_id, task_id, activity_id, update_count, enforce_level_requirement); +} + +void lua_cross_zone_update_activity_by_guild_id(int guild_id, uint32 task_id, int activity_id, int activity_count) { + uint8 update_type = CZUpdateType_Guild; + uint8 update_subtype = CZTaskUpdateSubtype_ActivityUpdate; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, guild_id, task_id, activity_id, activity_count, enforce_level_requirement); +} + +void lua_cross_zone_update_activity_by_expedition_id(uint32 expedition_id, uint32 task_id, int activity_id) { + uint8 update_type = CZUpdateType_Expedition; + uint8 update_subtype = CZTaskUpdateSubtype_ActivityUpdate; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, expedition_id, task_id, activity_id, update_count, enforce_level_requirement); +} + +void lua_cross_zone_update_activity_by_expedition_id(uint32 expedition_id, uint32 task_id, int activity_id, int activity_count) { + uint8 update_type = CZUpdateType_Expedition; + uint8 update_subtype = CZTaskUpdateSubtype_ActivityUpdate; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, expedition_id, task_id, activity_id, activity_count, enforce_level_requirement); +} + +void lua_cross_zone_update_activity_by_client_name(const char* client_name, uint32 task_id, int activity_id) { + uint8 update_type = CZUpdateType_ClientName; + uint8 update_subtype = CZTaskUpdateSubtype_ActivityUpdate; + int update_identifier = 0; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, update_identifier, task_id, activity_id, update_count, enforce_level_requirement, client_name); +} + +void lua_cross_zone_update_activity_by_client_name(const char* client_name, uint32 task_id, int activity_id, int activity_count) { + uint8 update_type = CZUpdateType_ClientName; + uint8 update_subtype = CZTaskUpdateSubtype_ActivityUpdate; + int update_identifier = 0; + bool enforce_level_requirement = false; + quest_manager.CrossZoneTaskUpdate(update_type, update_subtype, update_identifier, task_id, activity_id, activity_count, enforce_level_requirement, client_name); +} + +void lua_world_wide_add_ldon_loss(uint32 theme_id) { + uint8 update_type = WWLDoNUpdateType_AddLoss; + quest_manager.WorldWideLDoNUpdate(update_type, theme_id); +} + +void lua_world_wide_add_ldon_loss(uint32 theme_id, uint8 min_status) { + uint8 update_type = WWLDoNUpdateType_AddLoss; + int points = 1; + quest_manager.WorldWideLDoNUpdate(update_type, theme_id, points, min_status); +} + +void lua_world_wide_add_ldon_loss(uint32 theme_id, uint8 min_status, uint8 max_status) { + uint8 update_type = WWLDoNUpdateType_AddLoss; + int points = 1; + quest_manager.WorldWideLDoNUpdate(update_type, theme_id, points, min_status, max_status); +} + +void lua_world_wide_add_ldon_points(uint32 theme_id, int points) { + uint8 update_type = WWLDoNUpdateType_AddPoints; + quest_manager.WorldWideLDoNUpdate(update_type, theme_id, points); +} + +void lua_world_wide_add_ldon_points(uint32 theme_id, int points, uint8 min_status) { + uint8 update_type = WWLDoNUpdateType_AddPoints; + quest_manager.WorldWideLDoNUpdate(update_type, theme_id, points, min_status); +} + +void lua_world_wide_add_ldon_points(uint32 theme_id, int points, uint8 min_status, uint8 max_status) { + uint8 update_type = WWLDoNUpdateType_AddPoints; + quest_manager.WorldWideLDoNUpdate(update_type, theme_id, points, min_status, max_status); +} + +void lua_world_wide_add_ldon_win(uint32 theme_id) { + uint8 update_type = WWLDoNUpdateType_AddWin; + quest_manager.WorldWideLDoNUpdate(update_type, theme_id); +} + +void lua_world_wide_add_ldon_win(uint32 theme_id, uint8 min_status) { + uint8 update_type = WWLDoNUpdateType_AddWin; + int points = 1; + quest_manager.WorldWideLDoNUpdate(update_type, theme_id, points, min_status); +} + +void lua_world_wide_add_ldon_win(uint32 theme_id, uint8 min_status, uint8 max_status) { + uint8 update_type = WWLDoNUpdateType_AddWin; + int points = 1; + quest_manager.WorldWideLDoNUpdate(update_type, theme_id, points, min_status, max_status); +} + +void lua_world_wide_assign_task(uint32 task_id) { + uint8 update_type = WWTaskUpdateType_AssignTask; + quest_manager.WorldWideTaskUpdate(update_type, task_id); +} + +void lua_world_wide_assign_task(uint32 task_id, bool enforce_level_requirement) { + uint8 update_type = WWTaskUpdateType_AssignTask; + int task_subidentifier = -1; + int update_count = 1; + quest_manager.WorldWideTaskUpdate(update_type, task_id, task_subidentifier, update_count, enforce_level_requirement); +} + +void lua_world_wide_assign_task(uint32 task_id, bool enforce_level_requirement, uint8 min_status) { + uint8 update_type = WWTaskUpdateType_AssignTask; + int task_subidentifier = -1; + int update_count = 1; + quest_manager.WorldWideTaskUpdate(update_type, task_id, task_subidentifier, update_count, enforce_level_requirement, min_status); +} + +void lua_world_wide_assign_task(uint32 task_id, bool enforce_level_requirement, uint8 min_status, uint8 max_status) { + uint8 update_type = WWTaskUpdateType_AssignTask; + int task_subidentifier = -1; + int update_count = 1; + quest_manager.WorldWideTaskUpdate(update_type, task_id, task_subidentifier, update_count, enforce_level_requirement, min_status, max_status); +} + +void lua_world_wide_cast_spell(uint32 spell_id) { + uint8 update_type = WWSpellUpdateType_Cast; + quest_manager.WorldWideSpell(update_type, spell_id); +} + +void lua_world_wide_cast_spell(uint32 spell_id, uint8 min_status) { + uint8 update_type = WWSpellUpdateType_Cast; + quest_manager.WorldWideSpell(update_type, spell_id, min_status); +} + +void lua_world_wide_cast_spell(uint32 spell_id, uint8 min_status, uint8 max_status) { + uint8 update_type = WWSpellUpdateType_Cast; + quest_manager.WorldWideSpell(update_type, spell_id, min_status, max_status); +} + +void lua_world_wide_dialogue_window(const char* message) { + quest_manager.WorldWideDialogueWindow(message); +} + +void lua_world_wide_dialogue_window(const char* message, uint8 min_status) { + quest_manager.WorldWideDialogueWindow(message, min_status); +} + +void lua_world_wide_dialogue_window(const char* message, uint8 min_status, uint8 max_status) { + quest_manager.WorldWideDialogueWindow(message, min_status, max_status); +} + +void lua_world_wide_disable_task(uint32 task_id) { + uint8 update_type = WWTaskUpdateType_DisableTask; + quest_manager.WorldWideTaskUpdate(update_type, task_id); +} + +void lua_world_wide_disable_task(uint32 task_id, uint8 min_status) { + uint8 update_type = WWTaskUpdateType_DisableTask; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.WorldWideTaskUpdate(update_type, task_id, task_subidentifier, update_count, enforce_level_requirement, min_status); +} + +void lua_world_wide_disable_task(uint32 task_id, uint8 min_status, uint8 max_status) { + uint8 update_type = WWTaskUpdateType_DisableTask; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.WorldWideTaskUpdate(update_type, task_id, task_subidentifier, update_count, enforce_level_requirement, min_status, max_status); +} + +void lua_world_wide_enable_task(uint32 task_id) { + uint8 update_type = WWTaskUpdateType_EnableTask; + quest_manager.WorldWideTaskUpdate(update_type, task_id); +} + +void lua_world_wide_enable_task(uint32 task_id, uint8 min_status) { + uint8 update_type = WWTaskUpdateType_EnableTask; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.WorldWideTaskUpdate(update_type, task_id, task_subidentifier, update_count, enforce_level_requirement, min_status); +} + +void lua_world_wide_enable_task(uint32 task_id, uint8 min_status, uint8 max_status) { + uint8 update_type = WWTaskUpdateType_EnableTask; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.WorldWideTaskUpdate(update_type, task_id, task_subidentifier, update_count, enforce_level_requirement, min_status, max_status); +} + +void lua_world_wide_fail_task(uint32 task_id) { + uint8 update_type = WWTaskUpdateType_FailTask; + quest_manager.WorldWideTaskUpdate(update_type, task_id); +} + +void lua_world_wide_fail_task(uint32 task_id, uint8 min_status) { + uint8 update_type = WWTaskUpdateType_FailTask; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.WorldWideTaskUpdate(update_type, task_id, task_subidentifier, update_count, enforce_level_requirement, min_status); +} + +void lua_world_wide_fail_task(uint32 task_id, uint8 min_status, uint8 max_status) { + uint8 update_type = WWTaskUpdateType_FailTask; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.WorldWideTaskUpdate(update_type, task_id, task_subidentifier, update_count, enforce_level_requirement, min_status, max_status); +} + +void lua_world_wide_marquee(uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char* message) { + quest_manager.WorldWideMarquee(type, priority, fade_in, fade_out, duration, message); +} + +void lua_world_wide_marquee(uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char* message, uint8 min_status) { + quest_manager.WorldWideMarquee(type, priority, fade_in, fade_out, duration, message, min_status); +} + +void lua_world_wide_marquee(uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char* message, uint8 min_status, uint8 max_status) { + quest_manager.WorldWideMarquee(type, priority, fade_in, fade_out, duration, message, min_status, max_status); +} + +void lua_world_wide_message(uint32 type, const char* message) { + quest_manager.WorldWideMessage(type, message); +} + +void lua_world_wide_message(uint32 type, const char* message, uint8 min_status) { + quest_manager.WorldWideMessage(type, message, min_status); +} + +void lua_world_wide_message(uint32 type, const char* message, uint8 min_status, uint8 max_status) { + quest_manager.WorldWideMessage(type, message, min_status, max_status); +} + +void lua_world_wide_move(const char* zone_short_name) { + uint8 update_type = WWMoveUpdateType_MoveZone; + quest_manager.WorldWideMove(update_type, zone_short_name); +} + +void lua_world_wide_move(const char* zone_short_name, uint8 min_status) { + uint8 update_type = WWMoveUpdateType_MoveZone; + uint16 instance_id = 0; + quest_manager.WorldWideMove(update_type, zone_short_name, instance_id, min_status); +} + +void lua_world_wide_move(const char* zone_short_name, uint8 min_status, uint8 max_status) { + uint8 update_type = WWMoveUpdateType_MoveZone; + uint16 instance_id = 0; + quest_manager.WorldWideMove(update_type, zone_short_name, instance_id, min_status, max_status); +} + +void lua_world_wide_move_instance(uint16 instance_id) { + uint8 update_type = WWMoveUpdateType_MoveZoneInstance; + const char* zone_short_name = ""; + quest_manager.WorldWideMove(update_type, zone_short_name, instance_id); +} + +void lua_world_wide_move_instance(uint16 instance_id, uint8 min_status) { + uint8 update_type = WWMoveUpdateType_MoveZoneInstance; + const char* zone_short_name = ""; + quest_manager.WorldWideMove(update_type, zone_short_name, instance_id, min_status); +} + +void lua_world_wide_move_instance(uint16 instance_id, uint8 min_status, uint8 max_status) { + uint8 update_type = WWMoveUpdateType_MoveZoneInstance; + const char* zone_short_name = ""; + quest_manager.WorldWideMove(update_type, zone_short_name, instance_id, min_status, max_status); +} + +void lua_world_wide_remove_ldon_loss(uint32 theme_id) { + uint8 update_type = WWLDoNUpdateType_RemoveLoss; + quest_manager.WorldWideLDoNUpdate(update_type, theme_id); +} + +void lua_world_wide_remove_ldon_loss(uint32 theme_id, uint8 min_status) { + uint8 update_type = WWLDoNUpdateType_RemoveLoss; + int points = 1; + quest_manager.WorldWideLDoNUpdate(update_type, theme_id, points, min_status); +} + +void lua_world_wide_remove_ldon_loss(uint32 theme_id, uint8 min_status, uint8 max_status) { + uint8 update_type = WWLDoNUpdateType_RemoveLoss; + int points = 1; + quest_manager.WorldWideLDoNUpdate(update_type, theme_id, points, min_status, max_status); +} + +void lua_world_wide_remove_ldon_win(uint32 theme_id) { + uint8 update_type = WWLDoNUpdateType_RemoveWin; + quest_manager.WorldWideLDoNUpdate(update_type, theme_id); +} + +void lua_world_wide_remove_ldon_win(uint32 theme_id, uint8 min_status) { + uint8 update_type = WWLDoNUpdateType_RemoveWin; + int points = 1; + quest_manager.WorldWideLDoNUpdate(update_type, theme_id, points, min_status); +} + +void lua_world_wide_remove_ldon_win(uint32 theme_id, uint8 min_status, uint8 max_status) { + uint8 update_type = WWLDoNUpdateType_RemoveWin; + int points = 1; + quest_manager.WorldWideLDoNUpdate(update_type, theme_id, points, min_status, max_status); +} + +void lua_world_wide_remove_spell(uint32 spell_id) { + uint8 update_type = WWSpellUpdateType_Remove; + quest_manager.WorldWideSpell(update_type, spell_id); +} + +void lua_world_wide_remove_spell(uint32 spell_id, uint8 min_status) { + uint8 update_type = WWSpellUpdateType_Remove; + quest_manager.WorldWideSpell(update_type, spell_id, min_status); +} + +void lua_world_wide_remove_spell(uint32 spell_id, uint8 min_status, uint8 max_status) { + uint8 update_type = WWSpellUpdateType_Remove; + quest_manager.WorldWideSpell(update_type, spell_id, min_status, max_status); +} + +void lua_world_wide_remove_task(uint32 task_id) { + uint8 update_type = WWTaskUpdateType_RemoveTask; + quest_manager.WorldWideTaskUpdate(update_type, task_id); +} + +void lua_world_wide_remove_task(uint32 task_id, uint8 min_status) { + uint8 update_type = WWTaskUpdateType_RemoveTask; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.WorldWideTaskUpdate(update_type, task_id, task_subidentifier, update_count, enforce_level_requirement, min_status); +} + +void lua_world_wide_remove_task(uint32 task_id, uint8 min_status, uint8 max_status) { + uint8 update_type = WWTaskUpdateType_RemoveTask; + int task_subidentifier = -1; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.WorldWideTaskUpdate(update_type, task_id, task_subidentifier, update_count, enforce_level_requirement, min_status, max_status); +} + +void lua_world_wide_reset_activity(uint32 task_id, int activity_id) { + uint8 update_type = WWTaskUpdateType_ActivityReset; + quest_manager.WorldWideTaskUpdate(update_type, task_id, activity_id); +} + +void lua_world_wide_reset_activity(uint32 task_id, int activity_id, uint8 min_status) { + uint8 update_type = WWTaskUpdateType_ActivityReset; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.WorldWideTaskUpdate(update_type, task_id, activity_id, update_count, enforce_level_requirement, min_status); +} + +void lua_world_wide_reset_activity(uint32 task_id, int activity_id, uint8 min_status, uint8 max_status) { + uint8 update_type = WWTaskUpdateType_ActivityReset; + int update_count = 1; + bool enforce_level_requirement = false; + quest_manager.WorldWideTaskUpdate(update_type, task_id, activity_id, update_count, enforce_level_requirement, min_status, max_status); +} + +void lua_world_wide_set_entity_variable_client(const char* variable_name, const char* variable_value) { + uint8 update_type = WWSetEntityVariableUpdateType_Character; + quest_manager.WorldWideSetEntityVariable(update_type, variable_name, variable_value); +} + +void lua_world_wide_set_entity_variable_client(const char* variable_name, const char* variable_value, uint8 min_status) { + uint8 update_type = WWSetEntityVariableUpdateType_Character; + quest_manager.WorldWideSetEntityVariable(update_type, variable_name, variable_value, min_status); +} + +void lua_world_wide_set_entity_variable_client(const char* variable_name, const char* variable_value, uint8 min_status, uint8 max_status) { + uint8 update_type = WWSetEntityVariableUpdateType_Character; + quest_manager.WorldWideSetEntityVariable(update_type, variable_name, variable_value, min_status, max_status); +} + +void lua_world_wide_set_entity_variable_npc(const char* variable_name, const char* variable_value) { + uint8 update_type = WWSetEntityVariableUpdateType_NPC; + quest_manager.WorldWideSetEntityVariable(update_type, variable_name, variable_value); +} + +void lua_world_wide_signal_client(uint32 signal) { + uint8 update_type = WWSignalUpdateType_Character; + quest_manager.WorldWideSignal(update_type, signal); +} + +void lua_world_wide_signal_client(uint32 signal, uint8 min_status) { + uint8 update_type = WWSignalUpdateType_Character; + quest_manager.WorldWideSignal(update_type, signal, min_status); +} + +void lua_world_wide_signal_client(uint32 signal, uint8 min_status, uint8 max_status) { + uint8 update_type = WWSignalUpdateType_Character; + quest_manager.WorldWideSignal(update_type, signal, min_status, max_status); +} + +void lua_world_wide_signal_npc(uint32 signal) { + uint8 update_type = WWSignalUpdateType_NPC; + quest_manager.WorldWideSignal(update_type, signal); +} + +void lua_world_wide_update_activity(uint32 task_id, int activity_id) { + uint8 update_type = WWTaskUpdateType_ActivityUpdate; + quest_manager.WorldWideTaskUpdate(update_type, task_id, activity_id); +} + +void lua_world_wide_update_activity(uint32 task_id, int activity_id, int activity_count) { + uint8 update_type = WWTaskUpdateType_ActivityUpdate; + quest_manager.WorldWideTaskUpdate(update_type, task_id, activity_id, activity_count); +} + +void lua_world_wide_update_activity(uint32 task_id, int activity_id, int activity_count, uint8 min_status) { + uint8 update_type = WWTaskUpdateType_ActivityUpdate; + bool enforce_level_requirement = false; + quest_manager.WorldWideTaskUpdate(update_type, task_id, activity_id, activity_count, enforce_level_requirement, min_status); +} + +void lua_world_wide_update_activity(uint32 task_id, int activity_id, int activity_count, uint8 min_status, uint8 max_status) { + uint8 update_type = WWTaskUpdateType_ActivityUpdate; + bool enforce_level_requirement = false; + quest_manager.WorldWideTaskUpdate(update_type, task_id, activity_id, activity_count, enforce_level_requirement, min_status, max_status); +} + +bool lua_is_npc_spawned(luabind::adl::object table) { + if(luabind::type(table) != LUA_TTABLE) { + return false; + } + + std::vector npc_ids; + int index = 1; + while (luabind::type(table[index]) != LUA_TNIL) { + auto current_id = table[index]; + uint32 npc_id = 0; + if(luabind::type(current_id) != LUA_TNIL) { + try { + npc_id = luabind::object_cast(current_id); + } catch(luabind::cast_failed &) { + } + } else { + break; + } + + npc_ids.push_back(npc_id); + ++index; + } + + if (npc_ids.empty()) { + return false; + } + + return entity_list.IsNPCSpawned(npc_ids); +} + +uint32 lua_count_spawned_npcs(luabind::adl::object table) { + if(luabind::type(table) != LUA_TTABLE) { + return 0; + } + + std::vector npc_ids; + int index = 1; + while (luabind::type(table[index]) != LUA_TNIL) { + auto current_id = table[index]; + uint32 npc_id = 0; + if(luabind::type(current_id) != LUA_TNIL) { + try { + npc_id = luabind::object_cast(current_id); + } catch(luabind::cast_failed &) { + } + } else { + break; + } + + npc_ids.push_back(npc_id); + ++index; + } + + if (npc_ids.empty()) { + return 0; + } + + return entity_list.CountSpawnedNPCs(npc_ids); +} + +Lua_Spell lua_get_spell(uint32 spell_id) { + return Lua_Spell(spell_id); +} + +std::string lua_get_ldon_theme_name(uint32 theme_id) { + return quest_manager.getldonthemename(theme_id); +} + +std::string lua_get_faction_name(int faction_id) { + return quest_manager.getfactionname(faction_id); +} + +std::string lua_get_language_name(int language_id) { + return quest_manager.getlanguagename(language_id); +} + +std::string lua_get_body_type_name(uint32 bodytype_id) { + return quest_manager.getbodytypename(bodytype_id); +} + +std::string lua_get_consider_level_name(uint8 consider_level) { + return quest_manager.getconsiderlevelname(consider_level); +} + +std::string lua_get_environmental_damage_name(uint8 damage_type) { + return quest_manager.getenvironmentaldamagename(damage_type); +} + #define LuaCreateNPCParse(name, c_type, default_value) do { \ cur = table[#name]; \ if(luabind::type(cur) != LUA_TNIL) { \ @@ -2645,6 +3716,7 @@ luabind::scope lua_register_general() { 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_clean_npc_name_by_id", &lua_get_clean_npc_name_by_id), 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), @@ -2678,6 +3750,102 @@ luabind::scope lua_register_general() { luabind::def("wear_change", &lua_wear_change), luabind::def("voice_tell", &lua_voice_tell), luabind::def("send_mail", &lua_send_mail), + luabind::def("get_qglobals", (luabind::adl::object(*)(lua_State*,Lua_NPC,Lua_Client))&lua_get_qglobals), + luabind::def("get_qglobals", (luabind::adl::object(*)(lua_State*,Lua_Client))&lua_get_qglobals), + luabind::def("get_qglobals", (luabind::adl::object(*)(lua_State*,Lua_NPC))&lua_get_qglobals), + luabind::def("get_qglobals", (luabind::adl::object(*)(lua_State*))&lua_get_qglobals), + luabind::def("get_entity_list", &lua_get_entity_list), + luabind::def("zone", &lua_zone), + luabind::def("zone_group", &lua_zone_group), + luabind::def("zone_raid", &lua_zone_raid), + luabind::def("get_zone_id", &lua_get_zone_id), + luabind::def("get_zone_id_by_name", &lua_get_zone_id_by_name), + luabind::def("get_zone_long_name", &lua_get_zone_long_name), + luabind::def("get_zone_long_name_by_name", &lua_get_zone_long_name_by_name), + luabind::def("get_zone_long_name_by_id", &lua_get_zone_long_name_by_id), + luabind::def("get_zone_short_name", &lua_get_zone_short_name), + luabind::def("get_zone_short_name_by_id", &lua_get_zone_short_name_by_id), + luabind::def("get_zone_instance_id", &lua_get_zone_instance_id), + luabind::def("get_zone_instance_version", &lua_get_zone_instance_version), + luabind::def("get_zone_weather", &lua_get_zone_weather), + luabind::def("get_zone_time", &lua_get_zone_time), + luabind::def("add_area", &lua_add_area), + luabind::def("remove_area", &lua_remove_area), + luabind::def("clear_areas", &lua_clear_areas), + luabind::def("add_spawn_point", &lua_add_spawn_point), + luabind::def("remove_spawn_point", &lua_remove_spawn_point), + luabind::def("attack", &lua_attack), + luabind::def("attack_npc", &lua_attack_npc), + luabind::def("attack_npc_type", &lua_attack_npc_type), + luabind::def("follow", (void(*)(int))&lua_follow), + luabind::def("follow", (void(*)(int,int))&lua_follow), + luabind::def("stop_follow", &lua_stop_follow), + luabind::def("get_initiator", &lua_get_initiator), + luabind::def("get_owner", &lua_get_owner), + luabind::def("get_quest_item", &lua_get_quest_item), + luabind::def("get_quest_spell", &lua_get_quest_spell), + luabind::def("get_encounter", &lua_get_encounter), + luabind::def("map_opcodes", &lua_map_opcodes), + luabind::def("clear_opcode", &lua_clear_opcode), + luabind::def("enable_recipe", &lua_enable_recipe), + luabind::def("disable_recipe", &lua_disable_recipe), + luabind::def("clear_npctype_cache", &lua_clear_npctype_cache), + luabind::def("reloadzonestaticdata", &lua_reloadzonestaticdata), + luabind::def("update_zone_header", &lua_update_zone_header), + luabind::def("clock", &lua_clock), + luabind::def("create_npc", &lua_create_npc), + luabind::def("log", (void(*)(int, std::string))&lua_log), + luabind::def("debug", (void(*)(std::string))&lua_debug), + luabind::def("debug", (void(*)(std::string, int))&lua_debug), + luabind::def("log_combat", (void(*)(std::string))&lua_log_combat), + luabind::def("seconds_to_time", &lua_seconds_to_time), + luabind::def("get_hex_color_code", &lua_get_hex_color_code), + luabind::def("get_aa_exp_modifier_by_char_id", &lua_get_aa_exp_modifier_by_char_id), + luabind::def("get_exp_modifier_by_char_id", &lua_get_exp_modifier_by_char_id), + luabind::def("set_aa_exp_modifier_by_char_id", &lua_set_aa_exp_modifier_by_char_id), + luabind::def("set_exp_modifier_by_char_id", &lua_set_exp_modifier_by_char_id), + luabind::def("add_ldon_loss", &lua_add_ldon_loss), + luabind::def("add_ldon_points", &lua_add_ldon_points), + luabind::def("add_ldon_win", &lua_add_ldon_win), + luabind::def("get_gender_name", &lua_get_gender_name), + luabind::def("get_deity_name", &lua_get_deity_name), + luabind::def("get_inventory_slot_name", &lua_get_inventory_slot_name), + luabind::def("rename", &lua_rename), + luabind::def("get_data_remaining", &lua_get_data_remaining), + luabind::def("get_item_stat", &lua_get_item_stat), + luabind::def("get_spell_stat", (int(*)(uint32,std::string))&lua_get_spell_stat), + luabind::def("get_spell_stat", (int(*)(uint32,std::string,uint8))&lua_get_spell_stat), + luabind::def("is_npc_spawned", &lua_is_npc_spawned), + luabind::def("count_spawned_npcs", &lua_count_spawned_npcs), + luabind::def("get_spell", &lua_get_spell), + luabind::def("get_ldon_theme_name", &lua_get_ldon_theme_name), + luabind::def("get_faction_name", &lua_get_faction_name), + luabind::def("get_language_name", &lua_get_language_name), + luabind::def("get_body_type_name", &lua_get_body_type_name), + luabind::def("get_consider_level_name", &lua_get_consider_level_name), + luabind::def("get_environmental_damage_name", &lua_get_environmental_damage_name), + + /* + Cross Zone + */ + luabind::def("cross_zone_add_ldon_loss_by_char_id", &lua_cross_zone_add_ldon_loss_by_char_id), + luabind::def("cross_zone_add_ldon_loss_by_group_id", &lua_cross_zone_add_ldon_loss_by_group_id), + luabind::def("cross_zone_add_ldon_loss_by_raid_id", &lua_cross_zone_add_ldon_loss_by_raid_id), + luabind::def("cross_zone_add_ldon_loss_by_guild_id", &lua_cross_zone_add_ldon_loss_by_guild_id), + luabind::def("cross_zone_add_ldon_loss_by_expedition_id", &lua_cross_zone_add_ldon_loss_by_expedition_id), + luabind::def("cross_zone_add_ldon_loss_by_client_name", &lua_cross_zone_add_ldon_loss_by_client_name), + luabind::def("cross_zone_add_ldon_points_by_char_id", &lua_cross_zone_add_ldon_points_by_char_id), + luabind::def("cross_zone_add_ldon_points_by_group_id", &lua_cross_zone_add_ldon_points_by_group_id), + luabind::def("cross_zone_add_ldon_points_by_raid_id", &lua_cross_zone_add_ldon_points_by_raid_id), + luabind::def("cross_zone_add_ldon_points_by_guild_id", &lua_cross_zone_add_ldon_points_by_guild_id), + luabind::def("cross_zone_add_ldon_points_by_expedition_id", &lua_cross_zone_add_ldon_points_by_expedition_id), + luabind::def("cross_zone_add_ldon_points_by_client_name", &lua_cross_zone_add_ldon_points_by_client_name), + luabind::def("cross_zone_add_ldon_win_by_char_id", &lua_cross_zone_add_ldon_win_by_char_id), + luabind::def("cross_zone_add_ldon_win_by_group_id", &lua_cross_zone_add_ldon_win_by_group_id), + luabind::def("cross_zone_add_ldon_win_by_raid_id", &lua_cross_zone_add_ldon_win_by_raid_id), + luabind::def("cross_zone_add_ldon_win_by_guild_id", &lua_cross_zone_add_ldon_win_by_guild_id), + luabind::def("cross_zone_add_ldon_win_by_expedition_id", &lua_cross_zone_add_ldon_win_by_expedition_id), + luabind::def("cross_zone_add_ldon_win_by_client_name", &lua_cross_zone_add_ldon_win_by_client_name), luabind::def("cross_zone_assign_task_by_char_id", (void(*)(int,uint32))&lua_cross_zone_assign_task_by_char_id), luabind::def("cross_zone_assign_task_by_char_id", (void(*)(int,uint32,bool))&lua_cross_zone_assign_task_by_char_id), luabind::def("cross_zone_assign_task_by_group_id", (void(*)(int,uint32))&lua_cross_zone_assign_task_by_group_id), @@ -2686,58 +3854,105 @@ luabind::scope lua_register_general() { luabind::def("cross_zone_assign_task_by_raid_id", (void(*)(int,uint32,bool))&lua_cross_zone_assign_task_by_raid_id), luabind::def("cross_zone_assign_task_by_guild_id", (void(*)(int,uint32))&lua_cross_zone_assign_task_by_guild_id), luabind::def("cross_zone_assign_task_by_guild_id", (void(*)(int,uint32,bool))&lua_cross_zone_assign_task_by_guild_id), + luabind::def("cross_zone_assign_task_by_expedition_id", (void(*)(uint32,uint32))&lua_cross_zone_assign_task_by_expedition_id), + luabind::def("cross_zone_assign_task_by_expedition_id", (void(*)(uint32,uint32,bool))&lua_cross_zone_assign_task_by_expedition_id), + luabind::def("cross_zone_assign_task_by_client_name", (void(*)(const char*,uint32))&lua_cross_zone_assign_task_by_client_name), + luabind::def("cross_zone_assign_task_by_client_name", (void(*)(const char*,uint32,bool))&lua_cross_zone_assign_task_by_client_name), luabind::def("cross_zone_cast_spell_by_char_id", &lua_cross_zone_cast_spell_by_char_id), luabind::def("cross_zone_cast_spell_by_group_id", &lua_cross_zone_cast_spell_by_group_id), luabind::def("cross_zone_cast_spell_by_raid_id", &lua_cross_zone_cast_spell_by_raid_id), luabind::def("cross_zone_cast_spell_by_guild_id", &lua_cross_zone_cast_spell_by_guild_id), + luabind::def("cross_zone_cast_spell_by_expedition_id", &lua_cross_zone_cast_spell_by_expedition_id), + luabind::def("cross_zone_cast_spell_by_client_name", &lua_cross_zone_cast_spell_by_client_name), + luabind::def("cross_zone_dialogue_window_by_char_id", &lua_cross_zone_dialogue_window_by_char_id), + luabind::def("cross_zone_dialogue_window_by_group_id", &lua_cross_zone_dialogue_window_by_group_id), + luabind::def("cross_zone_dialogue_window_by_raid_id", &lua_cross_zone_dialogue_window_by_raid_id), + luabind::def("cross_zone_dialogue_window_by_guild_id", &lua_cross_zone_dialogue_window_by_guild_id), + luabind::def("cross_zone_dialogue_window_by_expedition_id", &lua_cross_zone_dialogue_window_by_expedition_id), + luabind::def("cross_zone_dialogue_window_by_client_name", &lua_cross_zone_dialogue_window_by_client_name), luabind::def("cross_zone_disable_task_by_char_id", &lua_cross_zone_disable_task_by_char_id), luabind::def("cross_zone_disable_task_by_group_id", &lua_cross_zone_disable_task_by_group_id), luabind::def("cross_zone_disable_task_by_raid_id", &lua_cross_zone_disable_task_by_raid_id), luabind::def("cross_zone_disable_task_by_guild_id", &lua_cross_zone_disable_task_by_guild_id), + luabind::def("cross_zone_disable_task_by_expedition_id", &lua_cross_zone_disable_task_by_expedition_id), + luabind::def("cross_zone_disable_task_by_client_name", &lua_cross_zone_disable_task_by_client_name), luabind::def("cross_zone_enable_task_by_char_id", &lua_cross_zone_enable_task_by_char_id), luabind::def("cross_zone_enable_task_by_group_id", &lua_cross_zone_enable_task_by_group_id), luabind::def("cross_zone_enable_task_by_raid_id", &lua_cross_zone_enable_task_by_raid_id), luabind::def("cross_zone_enable_task_by_guild_id", &lua_cross_zone_enable_task_by_guild_id), + luabind::def("cross_zone_enable_task_by_expedition_id", &lua_cross_zone_enable_task_by_expedition_id), + luabind::def("cross_zone_enable_task_by_client_name", &lua_cross_zone_enable_task_by_client_name), luabind::def("cross_zone_fail_task_by_char_id", &lua_cross_zone_fail_task_by_char_id), luabind::def("cross_zone_fail_task_by_group_id", &lua_cross_zone_fail_task_by_group_id), luabind::def("cross_zone_fail_task_by_raid_id", &lua_cross_zone_fail_task_by_raid_id), luabind::def("cross_zone_fail_task_by_guild_id", &lua_cross_zone_fail_task_by_guild_id), + luabind::def("cross_zone_fail_task_by_expedition_id", &lua_cross_zone_fail_task_by_expedition_id), + luabind::def("cross_zone_fail_task_by_client_name", &lua_cross_zone_fail_task_by_client_name), luabind::def("cross_zone_marquee_by_char_id", &lua_cross_zone_marquee_by_char_id), luabind::def("cross_zone_marquee_by_group_id", &lua_cross_zone_marquee_by_group_id), luabind::def("cross_zone_marquee_by_raid_id", &lua_cross_zone_marquee_by_raid_id), luabind::def("cross_zone_marquee_by_guild_id", &lua_cross_zone_marquee_by_guild_id), - luabind::def("cross_zone_message_player_by_name", &lua_cross_zone_message_player_by_name), + luabind::def("cross_zone_marquee_by_expedition_id", &lua_cross_zone_marquee_by_expedition_id), + luabind::def("cross_zone_marquee_by_client_name", &lua_cross_zone_marquee_by_client_name), + luabind::def("cross_zone_message_player_by_char_id", &lua_cross_zone_message_player_by_char_id), luabind::def("cross_zone_message_player_by_group_id", &lua_cross_zone_message_player_by_group_id), luabind::def("cross_zone_message_player_by_raid_id", &lua_cross_zone_message_player_by_raid_id), luabind::def("cross_zone_message_player_by_guild_id", &lua_cross_zone_message_player_by_guild_id), + luabind::def("cross_zone_message_player_by_expedition_id", &lua_cross_zone_message_player_by_expedition_id), + luabind::def("cross_zone_message_player_by_name", &lua_cross_zone_message_player_by_name), luabind::def("cross_zone_move_player_by_char_id", &lua_cross_zone_move_player_by_char_id), luabind::def("cross_zone_move_player_by_group_id", &lua_cross_zone_move_player_by_group_id), luabind::def("cross_zone_move_player_by_raid_id", &lua_cross_zone_move_player_by_raid_id), luabind::def("cross_zone_move_player_by_guild_id", &lua_cross_zone_move_player_by_guild_id), + luabind::def("cross_zone_move_player_by_expedition_id", &lua_cross_zone_move_player_by_expedition_id), + luabind::def("cross_zone_move_player_by_client_name", &lua_cross_zone_move_player_by_client_name), luabind::def("cross_zone_move_instance_by_char_id", &lua_cross_zone_move_instance_by_char_id), luabind::def("cross_zone_move_instance_by_group_id", &lua_cross_zone_move_instance_by_group_id), luabind::def("cross_zone_move_instance_by_raid_id", &lua_cross_zone_move_instance_by_raid_id), luabind::def("cross_zone_move_instance_by_guild_id", &lua_cross_zone_move_instance_by_guild_id), + luabind::def("cross_zone_move_instance_by_expedition_id", &lua_cross_zone_move_instance_by_expedition_id), + luabind::def("cross_zone_move_instance_by_client_name", &lua_cross_zone_move_instance_by_client_name), + luabind::def("cross_zone_remove_ldon_loss_by_char_id", &lua_cross_zone_remove_ldon_loss_by_char_id), + luabind::def("cross_zone_remove_ldon_loss_by_group_id", &lua_cross_zone_remove_ldon_loss_by_group_id), + luabind::def("cross_zone_remove_ldon_loss_by_raid_id", &lua_cross_zone_remove_ldon_loss_by_raid_id), + luabind::def("cross_zone_remove_ldon_loss_by_guild_id", &lua_cross_zone_remove_ldon_loss_by_guild_id), + luabind::def("cross_zone_remove_ldon_loss_by_expedition_id", &lua_cross_zone_remove_ldon_loss_by_expedition_id), + luabind::def("cross_zone_remove_ldon_loss_by_client_name", &lua_cross_zone_remove_ldon_loss_by_client_name), + luabind::def("cross_zone_remove_ldon_win_by_char_id", &lua_cross_zone_remove_ldon_win_by_char_id), + luabind::def("cross_zone_remove_ldon_win_by_group_id", &lua_cross_zone_remove_ldon_win_by_group_id), + luabind::def("cross_zone_remove_ldon_win_by_raid_id", &lua_cross_zone_remove_ldon_win_by_raid_id), + luabind::def("cross_zone_remove_ldon_win_by_guild_id", &lua_cross_zone_remove_ldon_win_by_guild_id), + luabind::def("cross_zone_remove_ldon_win_by_expedition_id", &lua_cross_zone_remove_ldon_win_by_expedition_id), + luabind::def("cross_zone_remove_ldon_win_by_client_name", &lua_cross_zone_remove_ldon_win_by_client_name), luabind::def("cross_zone_remove_spell_by_char_id", &lua_cross_zone_remove_spell_by_char_id), luabind::def("cross_zone_remove_spell_by_group_id", &lua_cross_zone_remove_spell_by_group_id), luabind::def("cross_zone_remove_spell_by_raid_id", &lua_cross_zone_remove_spell_by_raid_id), luabind::def("cross_zone_remove_spell_by_guild_id", &lua_cross_zone_remove_spell_by_guild_id), + luabind::def("cross_zone_remove_spell_by_expedition_id", &lua_cross_zone_remove_spell_by_expedition_id), + luabind::def("cross_zone_remove_spell_by_client_name", &lua_cross_zone_remove_spell_by_client_name), luabind::def("cross_zone_remove_task_by_char_id", &lua_cross_zone_remove_task_by_char_id), luabind::def("cross_zone_remove_task_by_group_id", &lua_cross_zone_remove_task_by_group_id), luabind::def("cross_zone_remove_task_by_raid_id", &lua_cross_zone_remove_task_by_raid_id), luabind::def("cross_zone_remove_task_by_guild_id", &lua_cross_zone_remove_task_by_guild_id), + luabind::def("cross_zone_remove_task_by_expedition_id", &lua_cross_zone_remove_task_by_expedition_id), + luabind::def("cross_zone_remove_task_by_client_name", &lua_cross_zone_remove_task_by_client_name), luabind::def("cross_zone_reset_activity_by_char_id", &lua_cross_zone_reset_activity_by_char_id), luabind::def("cross_zone_reset_activity_by_group_id", &lua_cross_zone_reset_activity_by_group_id), luabind::def("cross_zone_reset_activity_by_raid_id", &lua_cross_zone_reset_activity_by_raid_id), luabind::def("cross_zone_reset_activity_by_guild_id", &lua_cross_zone_reset_activity_by_guild_id), + luabind::def("cross_zone_reset_activity_by_expedition_id", &lua_cross_zone_reset_activity_by_expedition_id), + luabind::def("cross_zone_reset_activity_by_client_name", &lua_cross_zone_reset_activity_by_client_name), luabind::def("cross_zone_set_entity_variable_by_client_name", &lua_cross_zone_set_entity_variable_by_client_name), luabind::def("cross_zone_set_entity_variable_by_group_id", &lua_cross_zone_set_entity_variable_by_group_id), luabind::def("cross_zone_set_entity_variable_by_raid_id", &lua_cross_zone_set_entity_variable_by_raid_id), luabind::def("cross_zone_set_entity_variable_by_guild_id", &lua_cross_zone_set_entity_variable_by_guild_id), + luabind::def("cross_zone_set_entity_variable_by_expedition_id", &lua_cross_zone_set_entity_variable_by_expedition_id), + luabind::def("cross_zone_set_entity_variable_by_client_name", &lua_cross_zone_set_entity_variable_by_client_name), luabind::def("cross_zone_signal_client_by_char_id", &lua_cross_zone_signal_client_by_char_id), luabind::def("cross_zone_signal_client_by_group_id", &lua_cross_zone_signal_client_by_group_id), luabind::def("cross_zone_signal_client_by_raid_id", &lua_cross_zone_signal_client_by_raid_id), luabind::def("cross_zone_signal_client_by_guild_id", &lua_cross_zone_signal_client_by_guild_id), + luabind::def("cross_zone_signal_client_by_expedition_id", &lua_cross_zone_signal_client_by_expedition_id), luabind::def("cross_zone_signal_client_by_name", &lua_cross_zone_signal_client_by_name), luabind::def("cross_zone_signal_npc_by_npctype_id", &lua_cross_zone_signal_npc_by_npctype_id), luabind::def("cross_zone_update_activity_by_char_id", (void(*)(int,uint32,int))&lua_cross_zone_update_activity_by_char_id), @@ -2748,6 +3963,23 @@ luabind::scope lua_register_general() { luabind::def("cross_zone_update_activity_by_raid_id", (void(*)(int,uint32,int,int))&lua_cross_zone_update_activity_by_raid_id), luabind::def("cross_zone_update_activity_by_guild_id", (void(*)(int,uint32,int))&lua_cross_zone_update_activity_by_guild_id), luabind::def("cross_zone_update_activity_by_guild_id", (void(*)(int,uint32,int,int))&lua_cross_zone_update_activity_by_guild_id), + luabind::def("cross_zone_update_activity_by_expedition_id", (void(*)(uint32,uint32,int))&lua_cross_zone_update_activity_by_expedition_id), + luabind::def("cross_zone_update_activity_by_expedition_id", (void(*)(uint32,uint32,int,int))&lua_cross_zone_update_activity_by_expedition_id), + luabind::def("cross_zone_update_activity_by_client_name", (void(*)(const char*,uint32,int))&lua_cross_zone_update_activity_by_client_name), + luabind::def("cross_zone_update_activity_by_client_name", (void(*)(const char*,uint32,int,int))&lua_cross_zone_update_activity_by_client_name), + + /* + World Wide + */ + luabind::def("world_wide_add_ldon_loss", (void(*)(uint32))&lua_world_wide_add_ldon_loss), + luabind::def("world_wide_add_ldon_loss", (void(*)(uint32,uint8))&lua_world_wide_add_ldon_loss), + luabind::def("world_wide_add_ldon_loss", (void(*)(uint32,uint8,uint8))&lua_world_wide_add_ldon_loss), + luabind::def("world_wide_add_ldon_points", (void(*)(uint32,int))&lua_world_wide_add_ldon_points), + luabind::def("world_wide_add_ldon_points", (void(*)(uint32,int,uint8))&lua_world_wide_add_ldon_points), + luabind::def("world_wide_add_ldon_points", (void(*)(uint32,int,uint8,uint8))&lua_world_wide_add_ldon_points), + luabind::def("world_wide_add_ldon_loss", (void(*)(uint32))&lua_world_wide_add_ldon_win), + luabind::def("world_wide_add_ldon_loss", (void(*)(uint32,uint8))&lua_world_wide_add_ldon_win), + luabind::def("world_wide_add_ldon_loss", (void(*)(uint32,uint8,uint8))&lua_world_wide_add_ldon_win), luabind::def("world_wide_assign_task", (void(*)(uint32))&lua_world_wide_assign_task), luabind::def("world_wide_assign_task", (void(*)(uint32,bool))&lua_world_wide_assign_task), luabind::def("world_wide_assign_task", (void(*)(uint32,bool,uint8))&lua_world_wide_assign_task), @@ -2755,6 +3987,9 @@ luabind::scope lua_register_general() { luabind::def("world_wide_cast_spell", (void(*)(uint32))&lua_world_wide_cast_spell), luabind::def("world_wide_cast_spell", (void(*)(uint32,uint8))&lua_world_wide_cast_spell), luabind::def("world_wide_cast_spell", (void(*)(uint32,uint8,uint8))&lua_world_wide_cast_spell), + luabind::def("world_wide_dialogue_window", (void(*)(const char*))&lua_world_wide_dialogue_window), + luabind::def("world_wide_dialogue_window", (void(*)(const char*,uint8))&lua_world_wide_dialogue_window), + luabind::def("world_wide_dialogue_window", (void(*)(const char*,uint8,uint8))&lua_world_wide_dialogue_window), luabind::def("world_wide_disable_task", (void(*)(uint32))&lua_world_wide_disable_task), luabind::def("world_wide_disable_task", (void(*)(uint32,uint8))&lua_world_wide_disable_task), luabind::def("world_wide_disable_task", (void(*)(uint32,uint8,uint8))&lua_world_wide_disable_task), @@ -2797,50 +4032,6 @@ luabind::scope lua_register_general() { luabind::def("world_wide_update_activity", (void(*)(uint32,int,int))&lua_world_wide_update_activity), luabind::def("world_wide_update_activity", (void(*)(uint32,int,int,uint8))&lua_world_wide_update_activity), luabind::def("world_wide_update_activity", (void(*)(uint32,int,int,uint8,uint8))&lua_world_wide_update_activity), - luabind::def("get_qglobals", (luabind::adl::object(*)(lua_State*,Lua_NPC,Lua_Client))&lua_get_qglobals), - luabind::def("get_qglobals", (luabind::adl::object(*)(lua_State*,Lua_Client))&lua_get_qglobals), - luabind::def("get_qglobals", (luabind::adl::object(*)(lua_State*,Lua_NPC))&lua_get_qglobals), - luabind::def("get_qglobals", (luabind::adl::object(*)(lua_State*))&lua_get_qglobals), - luabind::def("get_entity_list", &lua_get_entity_list), - luabind::def("zone", &lua_zone), - luabind::def("zone_group", &lua_zone_group), - luabind::def("zone_raid", &lua_zone_raid), - luabind::def("get_zone_id", &lua_get_zone_id), - luabind::def("get_zone_long_name", &lua_get_zone_long_name), - luabind::def("get_zone_short_name", &lua_get_zone_short_name), - luabind::def("get_zone_instance_id", &lua_get_zone_instance_id), - luabind::def("get_zone_instance_version", &lua_get_zone_instance_version), - luabind::def("get_zone_weather", &lua_get_zone_weather), - luabind::def("get_zone_time", &lua_get_zone_time), - luabind::def("add_area", &lua_add_area), - luabind::def("remove_area", &lua_remove_area), - luabind::def("clear_areas", &lua_clear_areas), - luabind::def("add_spawn_point", &lua_add_spawn_point), - luabind::def("remove_spawn_point", &lua_remove_spawn_point), - luabind::def("attack", &lua_attack), - luabind::def("attack_npc", &lua_attack_npc), - luabind::def("attack_npc_type", &lua_attack_npc_type), - luabind::def("follow", (void(*)(int))&lua_follow), - luabind::def("follow", (void(*)(int,int))&lua_follow), - luabind::def("stop_follow", &lua_stop_follow), - luabind::def("get_initiator", &lua_get_initiator), - luabind::def("get_owner", &lua_get_owner), - luabind::def("get_quest_item", &lua_get_quest_item), - luabind::def("get_encounter", &lua_get_encounter), - luabind::def("map_opcodes", &lua_map_opcodes), - luabind::def("clear_opcode", &lua_clear_opcode), - luabind::def("enable_recipe", &lua_enable_recipe), - luabind::def("disable_recipe", &lua_disable_recipe), - luabind::def("clear_npctype_cache", &lua_clear_npctype_cache), - luabind::def("reloadzonestaticdata", &lua_reloadzonestaticdata), - luabind::def("update_zone_header", &lua_update_zone_header), - luabind::def("clock", &lua_clock), - luabind::def("create_npc", &lua_create_npc), - luabind::def("log", (void(*)(int, std::string))&lua_log), - luabind::def("debug", (void(*)(std::string))&lua_debug), - luabind::def("debug", (void(*)(std::string, int))&lua_debug), - luabind::def("log_combat", (void(*)(std::string))&lua_log_combat), - luabind::def("seconds_to_time", &lua_seconds_to_time), /** * Expansions @@ -2976,7 +4167,7 @@ luabind::scope lua_register_events() { luabind::value("target_change", static_cast(EVENT_TARGET_CHANGE)), luabind::value("hate_list", static_cast(EVENT_HATE_LIST)), luabind::value("spell_effect", static_cast(EVENT_SPELL_EFFECT_CLIENT)), - luabind::value("spell_buff_tic", static_cast(EVENT_SPELL_BUFF_TIC_CLIENT)), + luabind::value("spell_buff_tic", static_cast(EVENT_SPELL_EFFECT_BUFF_TIC_CLIENT)), luabind::value("spell_fade", static_cast(EVENT_SPELL_FADE)), luabind::value("spell_effect_translocate_complete", static_cast(EVENT_SPELL_EFFECT_TRANSLOCATE_COMPLETE)), luabind::value("combine_success ", static_cast(EVENT_COMBINE_SUCCESS )), @@ -3016,7 +4207,12 @@ luabind::scope lua_register_events() { luabind::value("tick", static_cast(EVENT_TICK)), luabind::value("spawn_zone", static_cast(EVENT_SPAWN_ZONE)), luabind::value("death_zone", static_cast(EVENT_DEATH_ZONE)), - luabind::value("use_skill", static_cast(EVENT_USE_SKILL)) + luabind::value("use_skill", static_cast(EVENT_USE_SKILL)), + luabind::value("warp", static_cast(EVENT_WARP)), + luabind::value("test_buff", static_cast(EVENT_TEST_BUFF)), + luabind::value("consider", static_cast(EVENT_CONSIDER)), + luabind::value("consider_corpse", static_cast(EVENT_CONSIDER_CORPSE)), + luabind::value("loot_zone", static_cast(EVENT_LOOT_ZONE)) ]; } @@ -3027,11 +4223,11 @@ luabind::scope lua_register_faction() { luabind::value("Ally", static_cast(FACTION_ALLY)), luabind::value("Warmly", static_cast(FACTION_WARMLY)), luabind::value("Kindly", static_cast(FACTION_KINDLY)), - luabind::value("Amiable", static_cast(FACTION_AMIABLE)), - luabind::value("Indifferent", static_cast(FACTION_INDIFFERENT)), - luabind::value("Apprehensive", static_cast(FACTION_APPREHENSIVE)), - luabind::value("Dubious", static_cast(FACTION_DUBIOUS)), - luabind::value("Threatenly", static_cast(FACTION_THREATENLY)), + luabind::value("Amiable", static_cast(FACTION_AMIABLY)), + luabind::value("Indifferent", static_cast(FACTION_INDIFFERENTLY)), + luabind::value("Apprehensive", static_cast(FACTION_APPREHENSIVELY)), + luabind::value("Dubious", static_cast(FACTION_DUBIOUSLY)), + luabind::value("Threatenly", static_cast(FACTION_THREATENINGLY)), luabind::value("Scowls", static_cast(FACTION_SCOWLS)) ]; } diff --git a/zone/lua_group.cpp b/zone/lua_group.cpp index 2cac41418..668067b59 100644 --- a/zone/lua_group.cpp +++ b/zone/lua_group.cpp @@ -121,29 +121,29 @@ bool Lua_Group::DoesAnyMemberHaveExpeditionLockout(std::string expedition_name, luabind::scope lua_register_group() { return luabind::class_("Group") - .def(luabind::constructor<>()) - .property("null", &Lua_Group::Null) - .property("valid", &Lua_Group::Valid) - .def("DisbandGroup", (void(Lua_Group::*)(void))&Lua_Group::DisbandGroup) - .def("IsGroupMember", (bool(Lua_Group::*)(Lua_Mob))&Lua_Group::IsGroupMember) - .def("CastGroupSpell", (void(Lua_Group::*)(Lua_Mob,int))&Lua_Group::CastGroupSpell) - .def("SplitExp", (void(Lua_Group::*)(uint32,Lua_Mob))&Lua_Group::SplitExp) - .def("GroupMessage", (void(Lua_Group::*)(Lua_Mob,int,const char* message))&Lua_Group::GroupMessage) - .def("GetTotalGroupDamage", (uint32(Lua_Group::*)(Lua_Mob))&Lua_Group::GetTotalGroupDamage) - .def("SplitMoney", (void(Lua_Group::*)(uint32,uint32,uint32,uint32))&Lua_Group::SplitMoney) - .def("SplitMoney", (void(Lua_Group::*)(uint32,uint32,uint32,uint32,Lua_Client))&Lua_Group::SplitMoney) - .def("SetLeader", (void(Lua_Group::*)(Lua_Mob))&Lua_Group::SetLeader) - .def("GetLeader", (Lua_Mob(Lua_Group::*)(void))&Lua_Group::GetLeader) - .def("GetLeaderName", (const char*(Lua_Group::*)(void))&Lua_Group::GetLeaderName) - .def("IsLeader", (bool(Lua_Group::*)(Lua_Mob))&Lua_Group::IsLeader) - .def("GroupCount", (int(Lua_Group::*)(void))&Lua_Group::GroupCount) - .def("GetHighestLevel", (int(Lua_Group::*)(void))&Lua_Group::GetHighestLevel) - .def("GetLowestLevel", (int(Lua_Group::*)(void))&Lua_Group::GetLowestLevel) - .def("TeleportGroup", (void(Lua_Group::*)(Lua_Mob,uint32,uint32,float,float,float,float))&Lua_Group::TeleportGroup) - .def("GetID", (int(Lua_Group::*)(void))&Lua_Group::GetID) - .def("GetMember", (Lua_Mob(Lua_Group::*)(int))&Lua_Group::GetMember) - .def("DoesAnyMemberHaveExpeditionLockout", (bool(Lua_Group::*)(std::string, std::string))&Lua_Group::DoesAnyMemberHaveExpeditionLockout) - .def("DoesAnyMemberHaveExpeditionLockout", (bool(Lua_Group::*)(std::string, std::string, int))&Lua_Group::DoesAnyMemberHaveExpeditionLockout); + .def(luabind::constructor<>()) + .property("null", &Lua_Group::Null) + .property("valid", &Lua_Group::Valid) + .def("CastGroupSpell", (void(Lua_Group::*)(Lua_Mob,int))&Lua_Group::CastGroupSpell) + .def("DisbandGroup", (void(Lua_Group::*)(void))&Lua_Group::DisbandGroup) + .def("DoesAnyMemberHaveExpeditionLockout", (bool(Lua_Group::*)(std::string, std::string))&Lua_Group::DoesAnyMemberHaveExpeditionLockout) + .def("DoesAnyMemberHaveExpeditionLockout", (bool(Lua_Group::*)(std::string, std::string, int))&Lua_Group::DoesAnyMemberHaveExpeditionLockout) + .def("GetHighestLevel", (int(Lua_Group::*)(void))&Lua_Group::GetHighestLevel) + .def("GetID", (int(Lua_Group::*)(void))&Lua_Group::GetID) + .def("GetLeader", (Lua_Mob(Lua_Group::*)(void))&Lua_Group::GetLeader) + .def("GetLeaderName", (const char*(Lua_Group::*)(void))&Lua_Group::GetLeaderName) + .def("GetLowestLevel", (int(Lua_Group::*)(void))&Lua_Group::GetLowestLevel) + .def("GetMember", (Lua_Mob(Lua_Group::*)(int))&Lua_Group::GetMember) + .def("GetTotalGroupDamage", (uint32(Lua_Group::*)(Lua_Mob))&Lua_Group::GetTotalGroupDamage) + .def("GroupCount", (int(Lua_Group::*)(void))&Lua_Group::GroupCount) + .def("GroupMessage", (void(Lua_Group::*)(Lua_Mob,int,const char* message))&Lua_Group::GroupMessage) + .def("IsGroupMember", (bool(Lua_Group::*)(Lua_Mob))&Lua_Group::IsGroupMember) + .def("IsLeader", (bool(Lua_Group::*)(Lua_Mob))&Lua_Group::IsLeader) + .def("SetLeader", (void(Lua_Group::*)(Lua_Mob))&Lua_Group::SetLeader) + .def("SplitExp", (void(Lua_Group::*)(uint32,Lua_Mob))&Lua_Group::SplitExp) + .def("SplitMoney", (void(Lua_Group::*)(uint32,uint32,uint32,uint32))&Lua_Group::SplitMoney) + .def("SplitMoney", (void(Lua_Group::*)(uint32,uint32,uint32,uint32,Lua_Client))&Lua_Group::SplitMoney) + .def("TeleportGroup", (void(Lua_Group::*)(Lua_Mob,uint32,uint32,float,float,float,float))&Lua_Group::TeleportGroup); } #endif diff --git a/zone/lua_hate_entry.cpp b/zone/lua_hate_entry.cpp index 14eed4a7f..7d209bd43 100644 --- a/zone/lua_hate_entry.cpp +++ b/zone/lua_hate_entry.cpp @@ -58,17 +58,17 @@ void Lua_HateEntry::SetFrenzy(bool value) { luabind::scope lua_register_hate_entry() { return luabind::class_("HateEntry") - .property("null", &Lua_HateEntry::Null) - .property("valid", &Lua_HateEntry::Valid) - .property("ent", &Lua_HateEntry::GetEnt, &Lua_HateEntry::SetEnt) - .property("damage", &Lua_HateEntry::GetDamage, &Lua_HateEntry::SetDamage) - .property("hate", &Lua_HateEntry::GetHate, &Lua_HateEntry::SetHate) - .property("frenzy", &Lua_HateEntry::GetFrenzy, &Lua_HateEntry::SetFrenzy); + .property("null", &Lua_HateEntry::Null) + .property("valid", &Lua_HateEntry::Valid) + .property("damage", &Lua_HateEntry::GetDamage, &Lua_HateEntry::SetDamage) + .property("ent", &Lua_HateEntry::GetEnt, &Lua_HateEntry::SetEnt) + .property("frenzy", &Lua_HateEntry::GetFrenzy, &Lua_HateEntry::SetFrenzy) + .property("hate", &Lua_HateEntry::GetHate, &Lua_HateEntry::SetHate); } luabind::scope lua_register_hate_list() { return luabind::class_("HateList") - .def_readwrite("entries", &Lua_HateList::entries, luabind::return_stl_iterator); + .def_readwrite("entries", &Lua_HateList::entries, luabind::return_stl_iterator); } #endif diff --git a/zone/lua_hate_list.cpp b/zone/lua_hate_list.cpp index e8c6a52df..ca1fd6eaf 100644 --- a/zone/lua_hate_list.cpp +++ b/zone/lua_hate_list.cpp @@ -51,19 +51,18 @@ void Lua_HateEntry::SetFrenzy(bool value) { } luabind::scope lua_register_hate_entry() { - return luabind::class_("HateEntry") - .property("null", &Lua_HateEntry::Null) - .property("valid", &Lua_HateEntry::Valid) - .property("ent", &Lua_HateEntry::GetEnt, &Lua_HateEntry::SetEnt) - .property("damage", &Lua_HateEntry::GetDamage, &Lua_HateEntry::SetDamage) - .property("hate", &Lua_HateEntry::GetHate, &Lua_HateEntry::SetHate) - .property("frenzy", &Lua_HateEntry::GetFrenzy, &Lua_HateEntry::SetFrenzy); + .property("null", &Lua_HateEntry::Null) + .property("valid", &Lua_HateEntry::Valid) + .property("damage", &Lua_HateEntry::GetDamage, &Lua_HateEntry::SetDamage) + .property("ent", &Lua_HateEntry::GetEnt, &Lua_HateEntry::SetEnt) + .property("frenzy", &Lua_HateEntry::GetFrenzy, &Lua_HateEntry::SetFrenzy) + .property("hate", &Lua_HateEntry::GetHate, &Lua_HateEntry::SetHate); } luabind::scope lua_register_hate_list() { return luabind::class_("HateList") - .def_readwrite("entries", &Lua_HateList::entries, luabind::return_stl_iterator); + .def_readwrite("entries", &Lua_HateList::entries, luabind::return_stl_iterator); } #endif diff --git a/zone/lua_inventory.cpp b/zone/lua_inventory.cpp index 1eba6e6f6..9c5b9c599 100644 --- a/zone/lua_inventory.cpp +++ b/zone/lua_inventory.cpp @@ -164,38 +164,62 @@ int Lua_Inventory::GetSlotByItemInst(Lua_ItemInst inst) { return self->GetSlotByItemInst(inst); } +int Lua_Inventory::CountAugmentEquippedByID(uint32 item_id) { + Lua_Safe_Call_Int(); + return self->CountAugmentEquippedByID(item_id); +} + +bool Lua_Inventory::HasAugmentEquippedByID(uint32 item_id) { + Lua_Safe_Call_Bool(); + return self->HasAugmentEquippedByID(item_id); +} + +int Lua_Inventory::CountItemEquippedByID(uint32 item_id) { + Lua_Safe_Call_Int(); + return self->CountItemEquippedByID(item_id); +} + +bool Lua_Inventory::HasItemEquippedByID(uint32 item_id) { + Lua_Safe_Call_Bool(); + return self->HasItemEquippedByID(item_id); +} + luabind::scope lua_register_inventory() { return luabind::class_("Inventory") - .def(luabind::constructor<>()) - .def("GetItem", (Lua_ItemInst(Lua_Inventory::*)(int))&Lua_Inventory::GetItem) - .def("GetItem", (Lua_ItemInst(Lua_Inventory::*)(int,int))&Lua_Inventory::GetItem) - .def("PutItem", (int(Lua_Inventory::*)(int,Lua_ItemInst))&Lua_Inventory::PutItem) - .def("PushCursor", (int(Lua_Inventory::*)(Lua_ItemInst))&Lua_Inventory::PushCursor) - .def("SwapItem", (bool(Lua_Inventory::*)(int,int))&Lua_Inventory::SwapItem) - .def("DeleteItem", (bool(Lua_Inventory::*)(int))&Lua_Inventory::DeleteItem) - .def("DeleteItem", (bool(Lua_Inventory::*)(int,int))&Lua_Inventory::DeleteItem) - .def("CheckNoDrop", (bool(Lua_Inventory::*)(int))&Lua_Inventory::CheckNoDrop) - .def("PopItem", (Lua_ItemInst(Lua_Inventory::*)(int))&Lua_Inventory::PopItem) - .def("HasItem", (int(Lua_Inventory::*)(int))&Lua_Inventory::HasItem) - .def("HasItem", (int(Lua_Inventory::*)(int,int))&Lua_Inventory::HasItem) - .def("HasItem", (int(Lua_Inventory::*)(int,int,int))&Lua_Inventory::HasItem) - .def("HasSpaceForItem", (bool(Lua_Inventory::*)(Lua_Item,int))&Lua_Inventory::HasSpaceForItem) - .def("HasItemByUse", (int(Lua_Inventory::*)(int))&Lua_Inventory::HasItemByUse) - .def("HasItemByUse", (int(Lua_Inventory::*)(int,uint8))&Lua_Inventory::HasItemByUse) - .def("HasItemByUse", (int(Lua_Inventory::*)(int,uint8,uint8))&Lua_Inventory::HasItemByUse) - .def("HasItemByLoreGroup", (int(Lua_Inventory::*)(uint32))&Lua_Inventory::HasItemByLoreGroup) - .def("HasItemByLoreGroup", (int(Lua_Inventory::*)(uint32,int))&Lua_Inventory::HasItemByLoreGroup) - .def("FindFreeSlot", (int(Lua_Inventory::*)(bool,bool))&Lua_Inventory::FindFreeSlot) - .def("FindFreeSlot", (int(Lua_Inventory::*)(bool,bool,int))&Lua_Inventory::FindFreeSlot) - .def("FindFreeSlot", (int(Lua_Inventory::*)(bool,bool,int,bool))&Lua_Inventory::FindFreeSlot) - .def("CalcSlotId", (int(Lua_Inventory::*)(int))&Lua_Inventory::CalcSlotId) - .def("CalcSlotId", (int(Lua_Inventory::*)(int,int))&Lua_Inventory::CalcSlotId) - .def("CalcBagIdx", (int(Lua_Inventory::*)(int))&Lua_Inventory::CalcBagIdx) - .def("CalcSlotFromMaterial", (int(Lua_Inventory::*)(int))&Lua_Inventory::CalcSlotFromMaterial) - .def("CalcMaterialFromSlot", (int(Lua_Inventory::*)(int))&Lua_Inventory::CalcMaterialFromSlot) - .def("CanItemFitInContainer", (bool(Lua_Inventory::*)(Lua_Item,Lua_Item))&Lua_Inventory::CanItemFitInContainer) - .def("SupportsContainers", (bool(Lua_Inventory::*)(int))&Lua_Inventory::SupportsContainers) - .def("GetSlotByItemInst", (int(Lua_Inventory::*)(Lua_ItemInst))&Lua_Inventory::GetSlotByItemInst); + .def(luabind::constructor<>()) + .def("CalcBagIdx", (int(Lua_Inventory::*)(int))&Lua_Inventory::CalcBagIdx) + .def("CalcMaterialFromSlot", (int(Lua_Inventory::*)(int))&Lua_Inventory::CalcMaterialFromSlot) + .def("CalcSlotFromMaterial", (int(Lua_Inventory::*)(int))&Lua_Inventory::CalcSlotFromMaterial) + .def("CalcSlotId", (int(Lua_Inventory::*)(int))&Lua_Inventory::CalcSlotId) + .def("CalcSlotId", (int(Lua_Inventory::*)(int,int))&Lua_Inventory::CalcSlotId) + .def("CanItemFitInContainer", (bool(Lua_Inventory::*)(Lua_Item,Lua_Item))&Lua_Inventory::CanItemFitInContainer) + .def("CheckNoDrop", (bool(Lua_Inventory::*)(int))&Lua_Inventory::CheckNoDrop) + .def("CountAugmentEquippedByID", (int(Lua_Inventory::*)(uint32))&Lua_Inventory::CountAugmentEquippedByID) + .def("CountItemEquippedByID", (int(Lua_Inventory::*)(uint32))&Lua_Inventory::CountItemEquippedByID) + .def("DeleteItem", (bool(Lua_Inventory::*)(int))&Lua_Inventory::DeleteItem) + .def("DeleteItem", (bool(Lua_Inventory::*)(int,int))&Lua_Inventory::DeleteItem) + .def("FindFreeSlot", (int(Lua_Inventory::*)(bool,bool))&Lua_Inventory::FindFreeSlot) + .def("FindFreeSlot", (int(Lua_Inventory::*)(bool,bool,int))&Lua_Inventory::FindFreeSlot) + .def("FindFreeSlot", (int(Lua_Inventory::*)(bool,bool,int,bool))&Lua_Inventory::FindFreeSlot) + .def("GetItem", (Lua_ItemInst(Lua_Inventory::*)(int))&Lua_Inventory::GetItem) + .def("GetItem", (Lua_ItemInst(Lua_Inventory::*)(int,int))&Lua_Inventory::GetItem) + .def("GetSlotByItemInst", (int(Lua_Inventory::*)(Lua_ItemInst))&Lua_Inventory::GetSlotByItemInst) + .def("HasAugmentEquippedByID", (bool(Lua_Inventory::*)(uint32))&Lua_Inventory::HasAugmentEquippedByID) + .def("HasItem", (int(Lua_Inventory::*)(int))&Lua_Inventory::HasItem) + .def("HasItem", (int(Lua_Inventory::*)(int,int))&Lua_Inventory::HasItem) + .def("HasItem", (int(Lua_Inventory::*)(int,int,int))&Lua_Inventory::HasItem) + .def("HasItemByLoreGroup", (int(Lua_Inventory::*)(uint32))&Lua_Inventory::HasItemByLoreGroup) + .def("HasItemByLoreGroup", (int(Lua_Inventory::*)(uint32,int))&Lua_Inventory::HasItemByLoreGroup) + .def("HasItemByUse", (int(Lua_Inventory::*)(int))&Lua_Inventory::HasItemByUse) + .def("HasItemByUse", (int(Lua_Inventory::*)(int,uint8))&Lua_Inventory::HasItemByUse) + .def("HasItemByUse", (int(Lua_Inventory::*)(int,uint8,uint8))&Lua_Inventory::HasItemByUse) + .def("HasItemEquippedByID", (bool(Lua_Inventory::*)(uint32))&Lua_Inventory::HasItemEquippedByID) + .def("HasSpaceForItem", (bool(Lua_Inventory::*)(Lua_Item,int))&Lua_Inventory::HasSpaceForItem) + .def("PopItem", (Lua_ItemInst(Lua_Inventory::*)(int))&Lua_Inventory::PopItem) + .def("PushCursor", (int(Lua_Inventory::*)(Lua_ItemInst))&Lua_Inventory::PushCursor) + .def("PutItem", (int(Lua_Inventory::*)(int,Lua_ItemInst))&Lua_Inventory::PutItem) + .def("SupportsContainers", (bool(Lua_Inventory::*)(int))&Lua_Inventory::SupportsContainers) + .def("SwapItem", (bool(Lua_Inventory::*)(int,int))&Lua_Inventory::SwapItem); } #endif diff --git a/zone/lua_inventory.h b/zone/lua_inventory.h index b64e9c48c..17966b4f4 100644 --- a/zone/lua_inventory.h +++ b/zone/lua_inventory.h @@ -43,7 +43,11 @@ public: bool DeleteItem(int slot_id); bool DeleteItem(int slot_id, int quantity); bool CheckNoDrop(int slot_id); + int CountAugmentEquippedByID(uint32 item_id); + int CountItemEquippedByID(uint32 item_id); Lua_ItemInst PopItem(int slot_id); + bool HasAugmentEquippedByID(uint32 item_id); + bool HasItemEquippedByID(uint32 item_id); int HasItem(int item_id); int HasItem(int item_id, int quantity); int HasItem(int item_id, int quantity, int where); diff --git a/zone/lua_item.cpp b/zone/lua_item.cpp index 5ee5f5d69..f693407d9 100644 --- a/zone/lua_item.cpp +++ b/zone/lua_item.cpp @@ -903,185 +903,185 @@ const char *Lua_Item::GetScrollName() { luabind::scope lua_register_item() { return luabind::class_("Item") - .def(luabind::constructor<>()) - .def(luabind::constructor()) - .def("null", &Lua_Item::Null) - .def("valid", &Lua_Item::Valid) - .def("MinStatus", &Lua_Item::GetMinStatus) - .def("ItemClass", &Lua_Item::GetItemClass) - .def("Name", &Lua_Item::GetName) - .def("Lore", &Lua_Item::GetLore) - .def("IDFile", &Lua_Item::GetIDFile) - .def("ID", &Lua_Item::GetID) - .def("Weight", &Lua_Item::GetWeight) - .def("NoRent", &Lua_Item::GetNoRent) - .def("NoDrop", &Lua_Item::GetNoDrop) - .def("Size", &Lua_Item::GetSize) - .def("Slots", &Lua_Item::GetSlots) - .def("Price", &Lua_Item::GetPrice) - .def("Icon", &Lua_Item::GetIcon) - .def("LoreGroup", &Lua_Item::GetLoreGroup) - .def("LoreFlag", &Lua_Item::GetLoreFlag) - .def("PendingLoreFlag", &Lua_Item::GetPendingLoreFlag) - .def("ArtifactFlag", &Lua_Item::GetArtifactFlag) - .def("SummonedFlag", &Lua_Item::GetSummonedFlag) - .def("FVNoDrop", &Lua_Item::GetFVNoDrop) - .def("Favor", &Lua_Item::GetFavor) - .def("GuildFavor", &Lua_Item::GetGuildFavor) - .def("PointType", &Lua_Item::GetPointType) - .def("BagType", &Lua_Item::GetBagType) - .def("BagSlots", &Lua_Item::GetBagSlots) - .def("BagSize", &Lua_Item::GetBagSize) - .def("BagWR", &Lua_Item::GetBagWR) - .def("BenefitFlag", &Lua_Item::GetBenefitFlag) - .def("Tradeskills", &Lua_Item::GetTradeskills) - .def("CR", &Lua_Item::GetCR) - .def("DR", &Lua_Item::GetDR) - .def("PR", &Lua_Item::GetPR) - .def("MR", &Lua_Item::GetMR) - .def("FR", &Lua_Item::GetFR) - .def("AStr", &Lua_Item::GetAStr) - .def("ASta", &Lua_Item::GetASta) - .def("AAgi", &Lua_Item::GetAAgi) - .def("ADex", &Lua_Item::GetADex) - .def("ACha", &Lua_Item::GetACha) - .def("AInt", &Lua_Item::GetAInt) - .def("AWis", &Lua_Item::GetAWis) - .def("HP", &Lua_Item::GetHP) - .def("Mana", &Lua_Item::GetMana) - .def("AC", &Lua_Item::GetAC) - .def("Deity", &Lua_Item::GetDeity) - .def("SkillModValue", &Lua_Item::GetSkillModValue) - .def("SkillModType", &Lua_Item::GetSkillModType) - .def("BaneDmgRace", &Lua_Item::GetBaneDmgRace) - .def("BaneDmgAmt", &Lua_Item::GetBaneDmgAmt) - .def("BaneDmgBody", &Lua_Item::GetBaneDmgBody) - .def("Magic", &Lua_Item::GetMagic) - .def("CastTime_", &Lua_Item::GetCastTime_) - .def("ReqLevel", &Lua_Item::GetReqLevel) - .def("BardType", &Lua_Item::GetBardType) - .def("BardValue", &Lua_Item::GetBardValue) - .def("Light", &Lua_Item::GetLight) - .def("Delay", &Lua_Item::GetDelay) - .def("RecLevel", &Lua_Item::GetRecLevel) - .def("RecSkill", &Lua_Item::GetRecSkill) - .def("ElemDmgType", &Lua_Item::GetElemDmgType) - .def("ElemDmgAmt", &Lua_Item::GetElemDmgAmt) - .def("Range", &Lua_Item::GetRange) - .def("Damage", &Lua_Item::GetDamage) - .def("Color", &Lua_Item::GetColor) - .def("Classes", &Lua_Item::GetClasses) - .def("Races", &Lua_Item::GetRaces) - .def("MaxCharges", &Lua_Item::GetMaxCharges) - .def("ItemType", &Lua_Item::GetItemType) - .def("Material", &Lua_Item::GetMaterial) - .def("SellRate", &Lua_Item::GetSellRate) - .def("Fulfilment", &Lua_Item::GetFulfilment) - .def("CastTime", &Lua_Item::GetCastTime) - .def("EliteMaterial", &Lua_Item::GetEliteMaterial) - .def("ProcRate", &Lua_Item::GetProcRate) - .def("CombatEffects", &Lua_Item::GetCombatEffects) - .def("Shielding", &Lua_Item::GetShielding) - .def("StunResist", &Lua_Item::GetStunResist) - .def("StrikeThrough", &Lua_Item::GetStrikeThrough) - .def("ExtraDmgSkill", &Lua_Item::GetExtraDmgSkill) - .def("ExtraDmgAmt", &Lua_Item::GetExtraDmgAmt) - .def("SpellShield", &Lua_Item::GetSpellShield) - .def("Avoidance", &Lua_Item::GetAvoidance) - .def("Accuracy", &Lua_Item::GetAccuracy) - .def("CharmFileID", &Lua_Item::GetCharmFileID) - .def("FactionMod1", &Lua_Item::GetFactionMod1) - .def("FactionMod2", &Lua_Item::GetFactionMod2) - .def("FactionMod3", &Lua_Item::GetFactionMod3) - .def("FactionMod4", &Lua_Item::GetFactionMod4) - .def("FactionAmt1", &Lua_Item::GetFactionAmt1) - .def("FactionAmt2", &Lua_Item::GetFactionAmt2) - .def("FactionAmt3", &Lua_Item::GetFactionAmt3) - .def("FactionAmt4", &Lua_Item::GetFactionAmt4) - .def("CharmFile", &Lua_Item::GetCharmFile) - .def("AugType", &Lua_Item::GetAugType) - .def("AugSlotType", &Lua_Item::GetAugSlotType) - .def("AugSlotVisible", &Lua_Item::GetAugSlotVisible) - .def("AugSlotUnk2", &Lua_Item::GetAugSlotUnk2) - .def("LDoNTheme", &Lua_Item::GetLDoNTheme) - .def("LDoNPrice", &Lua_Item::GetLDoNPrice) - .def("LDoNSold", &Lua_Item::GetLDoNSold) - .def("BaneDmgRaceAmt", &Lua_Item::GetBaneDmgRaceAmt) - .def("AugRestrict", &Lua_Item::GetAugRestrict) - .def("Endur", &Lua_Item::GetEndur) - .def("DotShielding", &Lua_Item::GetDotShielding) - .def("Attack", &Lua_Item::GetAttack) - .def("Regen", &Lua_Item::GetRegen) - .def("ManaRegen", &Lua_Item::GetManaRegen) - .def("EnduranceRegen", &Lua_Item::GetEnduranceRegen) - .def("Haste", &Lua_Item::GetHaste) - .def("DamageShield", &Lua_Item::GetDamageShield) - .def("RecastDelay", &Lua_Item::GetRecastDelay) - .def("RecastType", &Lua_Item::GetRecastType) - .def("AugDistiller", &Lua_Item::GetAugDistiller) - .def("Attuneable", &Lua_Item::GetAttuneable) - .def("NoPet", &Lua_Item::GetNoPet) - .def("PotionBelt", &Lua_Item::GetPotionBelt) - .def("Stackable", &Lua_Item::GetStackable) - .def("NoTransfer", &Lua_Item::GetNoTransfer) - .def("QuestItemFlag", &Lua_Item::GetQuestItemFlag) - .def("StackSize", &Lua_Item::GetStackSize) - .def("PotionBeltSlots", &Lua_Item::GetPotionBeltSlots) - .def("Click_Effect", &Lua_Item::GetClick_Effect) - .def("Click_Type", &Lua_Item::GetClick_Type) - .def("Click_Level", &Lua_Item::GetClick_Level) - .def("Click_Level2", &Lua_Item::GetClick_Level2) - .def("Proc_Effect", &Lua_Item::GetProc_Effect) - .def("Proc_Type", &Lua_Item::GetProc_Type) - .def("Proc_Level", &Lua_Item::GetProc_Level) - .def("Proc_Level2", &Lua_Item::GetProc_Level2) - .def("Worn_Effect", &Lua_Item::GetWorn_Effect) - .def("Worn_Type", &Lua_Item::GetWorn_Type) - .def("Worn_Level", &Lua_Item::GetWorn_Level) - .def("Worn_Level2", &Lua_Item::GetWorn_Level2) - .def("Focus_Effect", &Lua_Item::GetFocus_Effect) - .def("Focus_Type", &Lua_Item::GetFocus_Type) - .def("Focus_Level", &Lua_Item::GetFocus_Level) - .def("Focus_Level2", &Lua_Item::GetFocus_Level2) - .def("Scroll_Effect", &Lua_Item::GetScroll_Effect) - .def("Scroll_Type", &Lua_Item::GetScroll_Type) - .def("Scroll_Level", &Lua_Item::GetScroll_Level) - .def("Scroll_Level2", &Lua_Item::GetScroll_Level2) - .def("Bard_Effect", &Lua_Item::GetBard_Effect) - .def("Bard_Type", &Lua_Item::GetBard_Type) - .def("Bard_Level", &Lua_Item::GetBard_Level) - .def("Bard_Level2", &Lua_Item::GetBard_Level2) - .def("Book", &Lua_Item::GetBook) - .def("BookType", &Lua_Item::GetBookType) - .def("Filename", &Lua_Item::GetFilename) - .def("SVCorruption", &Lua_Item::GetSVCorruption) - .def("Purity", &Lua_Item::GetPurity) - .def("BackstabDmg", &Lua_Item::GetBackstabDmg) - .def("DSMitigation", &Lua_Item::GetDSMitigation) - .def("HeroicStr", &Lua_Item::GetHeroicStr) - .def("HeroicInt", &Lua_Item::GetHeroicInt) - .def("HeroicWis", &Lua_Item::GetHeroicWis) - .def("HeroicAgi", &Lua_Item::GetHeroicAgi) - .def("HeroicDex", &Lua_Item::GetHeroicDex) - .def("HeroicSta", &Lua_Item::GetHeroicSta) - .def("HeroicCha", &Lua_Item::GetHeroicCha) - .def("HeroicMR", &Lua_Item::GetHeroicMR) - .def("HeroicFR", &Lua_Item::GetHeroicFR) - .def("HeroicCR", &Lua_Item::GetHeroicCR) - .def("HeroicDR", &Lua_Item::GetHeroicDR) - .def("HeroicPR", &Lua_Item::GetHeroicPR) - .def("HeroicSVCorrup", &Lua_Item::GetHeroicSVCorrup) - .def("HealAmt", &Lua_Item::GetHealAmt) - .def("SpellDmg", &Lua_Item::GetSpellDmg) - .def("LDoNSellBackRate", &Lua_Item::GetLDoNSellBackRate) - .def("ScriptFileID", &Lua_Item::GetScriptFileID) - .def("ExpendableArrow", &Lua_Item::GetExpendableArrow) - .def("Clairvoyance", &Lua_Item::GetClairvoyance) - .def("ClickName", &Lua_Item::GetClickName) - .def("ProcName", &Lua_Item::GetProcName) - .def("WornName", &Lua_Item::GetWornName) - .def("FocusName", &Lua_Item::GetFocusName) - .def("ScrollName", &Lua_Item::GetScrollName); + .def(luabind::constructor<>()) + .def(luabind::constructor()) + .def("null", &Lua_Item::Null) + .def("valid", &Lua_Item::Valid) + .def("AAgi", &Lua_Item::GetAAgi) + .def("AC", &Lua_Item::GetAC) + .def("ACha", &Lua_Item::GetACha) + .def("ADex", &Lua_Item::GetADex) + .def("AInt", &Lua_Item::GetAInt) + .def("ASta", &Lua_Item::GetASta) + .def("AStr", &Lua_Item::GetAStr) + .def("AWis", &Lua_Item::GetAWis) + .def("Accuracy", &Lua_Item::GetAccuracy) + .def("ArtifactFlag", &Lua_Item::GetArtifactFlag) + .def("Attack", &Lua_Item::GetAttack) + .def("Attuneable", &Lua_Item::GetAttuneable) + .def("AugDistiller", &Lua_Item::GetAugDistiller) + .def("AugRestrict", &Lua_Item::GetAugRestrict) + .def("AugSlotType", &Lua_Item::GetAugSlotType) + .def("AugSlotUnk2", &Lua_Item::GetAugSlotUnk2) + .def("AugSlotVisible", &Lua_Item::GetAugSlotVisible) + .def("AugType", &Lua_Item::GetAugType) + .def("Avoidance", &Lua_Item::GetAvoidance) + .def("BackstabDmg", &Lua_Item::GetBackstabDmg) + .def("BagSize", &Lua_Item::GetBagSize) + .def("BagSlots", &Lua_Item::GetBagSlots) + .def("BagType", &Lua_Item::GetBagType) + .def("BagWR", &Lua_Item::GetBagWR) + .def("BaneDmgAmt", &Lua_Item::GetBaneDmgAmt) + .def("BaneDmgBody", &Lua_Item::GetBaneDmgBody) + .def("BaneDmgRace", &Lua_Item::GetBaneDmgRace) + .def("BaneDmgRaceAmt", &Lua_Item::GetBaneDmgRaceAmt) + .def("BardType", &Lua_Item::GetBardType) + .def("BardValue", &Lua_Item::GetBardValue) + .def("Bard_Effect", &Lua_Item::GetBard_Effect) + .def("Bard_Level", &Lua_Item::GetBard_Level) + .def("Bard_Level2", &Lua_Item::GetBard_Level2) + .def("Bard_Type", &Lua_Item::GetBard_Type) + .def("BenefitFlag", &Lua_Item::GetBenefitFlag) + .def("Book", &Lua_Item::GetBook) + .def("BookType", &Lua_Item::GetBookType) + .def("CR", &Lua_Item::GetCR) + .def("CastTime", &Lua_Item::GetCastTime) + .def("CastTime_", &Lua_Item::GetCastTime_) + .def("CharmFile", &Lua_Item::GetCharmFile) + .def("CharmFileID", &Lua_Item::GetCharmFileID) + .def("Clairvoyance", &Lua_Item::GetClairvoyance) + .def("Classes", &Lua_Item::GetClasses) + .def("ClickName", &Lua_Item::GetClickName) + .def("Click_Effect", &Lua_Item::GetClick_Effect) + .def("Click_Level", &Lua_Item::GetClick_Level) + .def("Click_Level2", &Lua_Item::GetClick_Level2) + .def("Click_Type", &Lua_Item::GetClick_Type) + .def("Color", &Lua_Item::GetColor) + .def("CombatEffects", &Lua_Item::GetCombatEffects) + .def("DR", &Lua_Item::GetDR) + .def("DSMitigation", &Lua_Item::GetDSMitigation) + .def("Damage", &Lua_Item::GetDamage) + .def("DamageShield", &Lua_Item::GetDamageShield) + .def("Deity", &Lua_Item::GetDeity) + .def("Delay", &Lua_Item::GetDelay) + .def("DotShielding", &Lua_Item::GetDotShielding) + .def("ElemDmgAmt", &Lua_Item::GetElemDmgAmt) + .def("ElemDmgType", &Lua_Item::GetElemDmgType) + .def("EliteMaterial", &Lua_Item::GetEliteMaterial) + .def("Endur", &Lua_Item::GetEndur) + .def("EnduranceRegen", &Lua_Item::GetEnduranceRegen) + .def("ExpendableArrow", &Lua_Item::GetExpendableArrow) + .def("ExtraDmgAmt", &Lua_Item::GetExtraDmgAmt) + .def("ExtraDmgSkill", &Lua_Item::GetExtraDmgSkill) + .def("FR", &Lua_Item::GetFR) + .def("FVNoDrop", &Lua_Item::GetFVNoDrop) + .def("FactionAmt1", &Lua_Item::GetFactionAmt1) + .def("FactionAmt2", &Lua_Item::GetFactionAmt2) + .def("FactionAmt3", &Lua_Item::GetFactionAmt3) + .def("FactionAmt4", &Lua_Item::GetFactionAmt4) + .def("FactionMod1", &Lua_Item::GetFactionMod1) + .def("FactionMod2", &Lua_Item::GetFactionMod2) + .def("FactionMod3", &Lua_Item::GetFactionMod3) + .def("FactionMod4", &Lua_Item::GetFactionMod4) + .def("Favor", &Lua_Item::GetFavor) + .def("Filename", &Lua_Item::GetFilename) + .def("FocusName", &Lua_Item::GetFocusName) + .def("Focus_Effect", &Lua_Item::GetFocus_Effect) + .def("Focus_Level", &Lua_Item::GetFocus_Level) + .def("Focus_Level2", &Lua_Item::GetFocus_Level2) + .def("Focus_Type", &Lua_Item::GetFocus_Type) + .def("Fulfilment", &Lua_Item::GetFulfilment) + .def("GuildFavor", &Lua_Item::GetGuildFavor) + .def("HP", &Lua_Item::GetHP) + .def("Haste", &Lua_Item::GetHaste) + .def("HealAmt", &Lua_Item::GetHealAmt) + .def("HeroicAgi", &Lua_Item::GetHeroicAgi) + .def("HeroicCR", &Lua_Item::GetHeroicCR) + .def("HeroicCha", &Lua_Item::GetHeroicCha) + .def("HeroicDR", &Lua_Item::GetHeroicDR) + .def("HeroicDex", &Lua_Item::GetHeroicDex) + .def("HeroicFR", &Lua_Item::GetHeroicFR) + .def("HeroicInt", &Lua_Item::GetHeroicInt) + .def("HeroicMR", &Lua_Item::GetHeroicMR) + .def("HeroicPR", &Lua_Item::GetHeroicPR) + .def("HeroicSVCorrup", &Lua_Item::GetHeroicSVCorrup) + .def("HeroicSta", &Lua_Item::GetHeroicSta) + .def("HeroicStr", &Lua_Item::GetHeroicStr) + .def("HeroicWis", &Lua_Item::GetHeroicWis) + .def("ID", &Lua_Item::GetID) + .def("IDFile", &Lua_Item::GetIDFile) + .def("Icon", &Lua_Item::GetIcon) + .def("ItemClass", &Lua_Item::GetItemClass) + .def("ItemType", &Lua_Item::GetItemType) + .def("LDoNPrice", &Lua_Item::GetLDoNPrice) + .def("LDoNSellBackRate", &Lua_Item::GetLDoNSellBackRate) + .def("LDoNSold", &Lua_Item::GetLDoNSold) + .def("LDoNTheme", &Lua_Item::GetLDoNTheme) + .def("Light", &Lua_Item::GetLight) + .def("Lore", &Lua_Item::GetLore) + .def("LoreFlag", &Lua_Item::GetLoreFlag) + .def("LoreGroup", &Lua_Item::GetLoreGroup) + .def("MR", &Lua_Item::GetMR) + .def("Magic", &Lua_Item::GetMagic) + .def("Mana", &Lua_Item::GetMana) + .def("ManaRegen", &Lua_Item::GetManaRegen) + .def("Material", &Lua_Item::GetMaterial) + .def("MaxCharges", &Lua_Item::GetMaxCharges) + .def("MinStatus", &Lua_Item::GetMinStatus) + .def("Name", &Lua_Item::GetName) + .def("NoDrop", &Lua_Item::GetNoDrop) + .def("NoPet", &Lua_Item::GetNoPet) + .def("NoRent", &Lua_Item::GetNoRent) + .def("NoTransfer", &Lua_Item::GetNoTransfer) + .def("PR", &Lua_Item::GetPR) + .def("PendingLoreFlag", &Lua_Item::GetPendingLoreFlag) + .def("PointType", &Lua_Item::GetPointType) + .def("PotionBelt", &Lua_Item::GetPotionBelt) + .def("PotionBeltSlots", &Lua_Item::GetPotionBeltSlots) + .def("Price", &Lua_Item::GetPrice) + .def("ProcName", &Lua_Item::GetProcName) + .def("ProcRate", &Lua_Item::GetProcRate) + .def("Proc_Effect", &Lua_Item::GetProc_Effect) + .def("Proc_Level", &Lua_Item::GetProc_Level) + .def("Proc_Level2", &Lua_Item::GetProc_Level2) + .def("Proc_Type", &Lua_Item::GetProc_Type) + .def("Purity", &Lua_Item::GetPurity) + .def("QuestItemFlag", &Lua_Item::GetQuestItemFlag) + .def("Races", &Lua_Item::GetRaces) + .def("Range", &Lua_Item::GetRange) + .def("RecLevel", &Lua_Item::GetRecLevel) + .def("RecSkill", &Lua_Item::GetRecSkill) + .def("RecastDelay", &Lua_Item::GetRecastDelay) + .def("RecastType", &Lua_Item::GetRecastType) + .def("Regen", &Lua_Item::GetRegen) + .def("ReqLevel", &Lua_Item::GetReqLevel) + .def("SVCorruption", &Lua_Item::GetSVCorruption) + .def("ScriptFileID", &Lua_Item::GetScriptFileID) + .def("ScrollName", &Lua_Item::GetScrollName) + .def("Scroll_Effect", &Lua_Item::GetScroll_Effect) + .def("Scroll_Level", &Lua_Item::GetScroll_Level) + .def("Scroll_Level2", &Lua_Item::GetScroll_Level2) + .def("Scroll_Type", &Lua_Item::GetScroll_Type) + .def("SellRate", &Lua_Item::GetSellRate) + .def("Shielding", &Lua_Item::GetShielding) + .def("Size", &Lua_Item::GetSize) + .def("SkillModType", &Lua_Item::GetSkillModType) + .def("SkillModValue", &Lua_Item::GetSkillModValue) + .def("Slots", &Lua_Item::GetSlots) + .def("SpellDmg", &Lua_Item::GetSpellDmg) + .def("SpellShield", &Lua_Item::GetSpellShield) + .def("StackSize", &Lua_Item::GetStackSize) + .def("Stackable", &Lua_Item::GetStackable) + .def("StrikeThrough", &Lua_Item::GetStrikeThrough) + .def("StunResist", &Lua_Item::GetStunResist) + .def("SummonedFlag", &Lua_Item::GetSummonedFlag) + .def("Tradeskills", &Lua_Item::GetTradeskills) + .def("Weight", &Lua_Item::GetWeight) + .def("WornName", &Lua_Item::GetWornName) + .def("Worn_Effect", &Lua_Item::GetWorn_Effect) + .def("Worn_Level", &Lua_Item::GetWorn_Level) + .def("Worn_Level2", &Lua_Item::GetWorn_Level2) + .def("Worn_Type", &Lua_Item::GetWorn_Type); } #endif diff --git a/zone/lua_iteminst.cpp b/zone/lua_iteminst.cpp index ba3c8a2c6..149c087b6 100644 --- a/zone/lua_iteminst.cpp +++ b/zone/lua_iteminst.cpp @@ -264,58 +264,70 @@ void Lua_ItemInst::ClearTimers() { self->ClearTimers(); } +bool Lua_ItemInst::ContainsAugmentByID(uint32 item_id) { + Lua_Safe_Call_Bool(); + return self->ContainsAugmentByID(item_id); +} + +int Lua_ItemInst::CountAugmentByID(uint32 item_id) { + Lua_Safe_Call_Int(); + return self->CountAugmentByID(item_id); +} + luabind::scope lua_register_iteminst() { return luabind::class_("ItemInst") - .def(luabind::constructor<>()) - .def(luabind::constructor()) - .def(luabind::constructor()) - .property("null", &Lua_ItemInst::Null) - .property("valid", &Lua_ItemInst::Valid) - .def("IsType", (bool(Lua_ItemInst::*)(int))&Lua_ItemInst::IsType) - .def("IsStackable", (bool(Lua_ItemInst::*)(void))&Lua_ItemInst::IsStackable) - .def("IsEquipable", (bool(Lua_ItemInst::*)(int,int))&Lua_ItemInst::IsEquipable) - .def("IsEquipable", (bool(Lua_ItemInst::*)(int))&Lua_ItemInst::IsEquipable) - .def("IsAugmentable", (bool(Lua_ItemInst::*)(void))&Lua_ItemInst::IsAugmentable) - .def("GetAugmentType", (int(Lua_ItemInst::*)(void))&Lua_ItemInst::GetAugmentType) - .def("IsExpendable", (bool(Lua_ItemInst::*)(void))&Lua_ItemInst::IsExpendable) - .def("GetItem", (Lua_ItemInst(Lua_ItemInst::*)(int))&Lua_ItemInst::GetItem) - .def("GetUnscaledItem", (Lua_ItemInst(Lua_ItemInst::*)(int))&Lua_ItemInst::GetUnscaledItem) - .def("GetItemID", (uint32(Lua_ItemInst::*)(int))&Lua_ItemInst::GetItemID) - .def("GetTotalItemCount", (int(Lua_ItemInst::*)(void))&Lua_ItemInst::GetTotalItemCount) - .def("GetAugment", (Lua_ItemInst(Lua_ItemInst::*)(int))&Lua_ItemInst::GetAugment) - .def("GetAugmentItemID", (uint32(Lua_ItemInst::*)(int))&Lua_ItemInst::GetAugmentItemID) - .def("IsAugmented", (bool(Lua_ItemInst::*)(void))&Lua_ItemInst::IsAugmented) - .def("IsWeapon", (bool(Lua_ItemInst::*)(void))&Lua_ItemInst::IsWeapon) - .def("IsAmmo", (bool(Lua_ItemInst::*)(void))&Lua_ItemInst::IsAmmo) - .def("GetID", (uint32(Lua_ItemInst::*)(void))&Lua_ItemInst::GetID) - .def("GetItemScriptID", (uint32(Lua_ItemInst::*)(void))&Lua_ItemInst::GetItemScriptID) - .def("GetItem", (Lua_Item(Lua_ItemInst::*)(void))&Lua_ItemInst::GetItem) - .def("GetCharges", (int(Lua_ItemInst::*)(void))&Lua_ItemInst::GetCharges) - .def("SetCharges", (void(Lua_ItemInst::*)(int))&Lua_ItemInst::SetCharges) - .def("GetPrice", (uint32(Lua_ItemInst::*)(void))&Lua_ItemInst::GetPrice) - .def("SetPrice", (void(Lua_ItemInst::*)(uint32))&Lua_ItemInst::SetPrice) - .def("SetColor", (void(Lua_ItemInst::*)(uint32))&Lua_ItemInst::SetColor) - .def("GetColor", (uint32(Lua_ItemInst::*)(void))&Lua_ItemInst::GetColor) - .def("IsInstNoDrop", (bool(Lua_ItemInst::*)(void))&Lua_ItemInst::IsInstNoDrop) - .def("SetInstNoDrop", (void(Lua_ItemInst::*)(bool))&Lua_ItemInst::SetInstNoDrop) - .def("GetCustomDataString", (std::string(Lua_ItemInst::*)(void))&Lua_ItemInst::GetCustomDataString) - .def("SetCustomData", (void(Lua_ItemInst::*)(std::string,std::string))&Lua_ItemInst::SetCustomData) - .def("SetCustomData", (void(Lua_ItemInst::*)(std::string,int))&Lua_ItemInst::SetCustomData) - .def("SetCustomData", (void(Lua_ItemInst::*)(std::string,float))&Lua_ItemInst::SetCustomData) - .def("SetCustomData", (void(Lua_ItemInst::*)(std::string,bool))&Lua_ItemInst::SetCustomData) - .def("GetCustomData", (std::string(Lua_ItemInst::*)(std::string))&Lua_ItemInst::GetCustomData) - .def("DeleteCustomData", (void(Lua_ItemInst::*)(std::string))&Lua_ItemInst::DeleteCustomData) - .def("SetScaling", (void(Lua_ItemInst::*)(bool))&Lua_ItemInst::SetScaling) - .def("SetScale", (void(Lua_ItemInst::*)(double))&Lua_ItemInst::SetScale) - .def("GetExp", (uint32(Lua_ItemInst::*)(void))&Lua_ItemInst::GetExp) - .def("SetExp", (void(Lua_ItemInst::*)(uint32))&Lua_ItemInst::SetExp) - .def("AddExp", (void(Lua_ItemInst::*)(uint32))&Lua_ItemInst::AddExp) - .def("GetMaxEvolveLvl", (int(Lua_ItemInst::*)(void))&Lua_ItemInst::GetMaxEvolveLvl) - .def("GetKillsNeeded", (uint32(Lua_ItemInst::*)(int))&Lua_ItemInst::GetKillsNeeded) - .def("Clone", (Lua_ItemInst(Lua_ItemInst::*)(void))&Lua_ItemInst::Clone) - .def("SetTimer", (void(Lua_ItemInst::*)(std::string,uint32))&Lua_ItemInst::SetTimer) - .def("StopTimer", (void(Lua_ItemInst::*)(std::string))&Lua_ItemInst::StopTimer) - .def("ClearTimers", (void(Lua_ItemInst::*)(void))&Lua_ItemInst::ClearTimers); + .def(luabind::constructor<>()) + .def(luabind::constructor()) + .def(luabind::constructor()) + .property("null", &Lua_ItemInst::Null) + .property("valid", &Lua_ItemInst::Valid) + .def("AddExp", (void(Lua_ItemInst::*)(uint32))&Lua_ItemInst::AddExp) + .def("ClearTimers", (void(Lua_ItemInst::*)(void))&Lua_ItemInst::ClearTimers) + .def("Clone", (Lua_ItemInst(Lua_ItemInst::*)(void))&Lua_ItemInst::Clone) + .def("ContainsAugmentByID", (bool(Lua_ItemInst::*)(uint32))&Lua_ItemInst::ContainsAugmentByID) + .def("CountAugmentByID", (int(Lua_ItemInst::*)(uint32))&Lua_ItemInst::CountAugmentByID) + .def("DeleteCustomData", (void(Lua_ItemInst::*)(std::string))&Lua_ItemInst::DeleteCustomData) + .def("GetAugment", (Lua_ItemInst(Lua_ItemInst::*)(int))&Lua_ItemInst::GetAugment) + .def("GetAugmentItemID", (uint32(Lua_ItemInst::*)(int))&Lua_ItemInst::GetAugmentItemID) + .def("GetAugmentType", (int(Lua_ItemInst::*)(void))&Lua_ItemInst::GetAugmentType) + .def("GetCharges", (int(Lua_ItemInst::*)(void))&Lua_ItemInst::GetCharges) + .def("GetColor", (uint32(Lua_ItemInst::*)(void))&Lua_ItemInst::GetColor) + .def("GetCustomData", (std::string(Lua_ItemInst::*)(std::string))&Lua_ItemInst::GetCustomData) + .def("GetCustomDataString", (std::string(Lua_ItemInst::*)(void))&Lua_ItemInst::GetCustomDataString) + .def("GetExp", (uint32(Lua_ItemInst::*)(void))&Lua_ItemInst::GetExp) + .def("GetID", (uint32(Lua_ItemInst::*)(void))&Lua_ItemInst::GetID) + .def("GetItem", (Lua_Item(Lua_ItemInst::*)(void))&Lua_ItemInst::GetItem) + .def("GetItem", (Lua_ItemInst(Lua_ItemInst::*)(int))&Lua_ItemInst::GetItem) + .def("GetItemID", (uint32(Lua_ItemInst::*)(int))&Lua_ItemInst::GetItemID) + .def("GetItemScriptID", (uint32(Lua_ItemInst::*)(void))&Lua_ItemInst::GetItemScriptID) + .def("GetKillsNeeded", (uint32(Lua_ItemInst::*)(int))&Lua_ItemInst::GetKillsNeeded) + .def("GetMaxEvolveLvl", (int(Lua_ItemInst::*)(void))&Lua_ItemInst::GetMaxEvolveLvl) + .def("GetPrice", (uint32(Lua_ItemInst::*)(void))&Lua_ItemInst::GetPrice) + .def("GetTotalItemCount", (int(Lua_ItemInst::*)(void))&Lua_ItemInst::GetTotalItemCount) + .def("GetUnscaledItem", (Lua_ItemInst(Lua_ItemInst::*)(int))&Lua_ItemInst::GetUnscaledItem) + .def("IsAmmo", (bool(Lua_ItemInst::*)(void))&Lua_ItemInst::IsAmmo) + .def("IsAugmentable", (bool(Lua_ItemInst::*)(void))&Lua_ItemInst::IsAugmentable) + .def("IsAugmented", (bool(Lua_ItemInst::*)(void))&Lua_ItemInst::IsAugmented) + .def("IsEquipable", (bool(Lua_ItemInst::*)(int))&Lua_ItemInst::IsEquipable) + .def("IsEquipable", (bool(Lua_ItemInst::*)(int,int))&Lua_ItemInst::IsEquipable) + .def("IsExpendable", (bool(Lua_ItemInst::*)(void))&Lua_ItemInst::IsExpendable) + .def("IsInstNoDrop", (bool(Lua_ItemInst::*)(void))&Lua_ItemInst::IsInstNoDrop) + .def("IsStackable", (bool(Lua_ItemInst::*)(void))&Lua_ItemInst::IsStackable) + .def("IsType", (bool(Lua_ItemInst::*)(int))&Lua_ItemInst::IsType) + .def("IsWeapon", (bool(Lua_ItemInst::*)(void))&Lua_ItemInst::IsWeapon) + .def("SetCharges", (void(Lua_ItemInst::*)(int))&Lua_ItemInst::SetCharges) + .def("SetColor", (void(Lua_ItemInst::*)(uint32))&Lua_ItemInst::SetColor) + .def("SetCustomData", (void(Lua_ItemInst::*)(std::string,bool))&Lua_ItemInst::SetCustomData) + .def("SetCustomData", (void(Lua_ItemInst::*)(std::string,float))&Lua_ItemInst::SetCustomData) + .def("SetCustomData", (void(Lua_ItemInst::*)(std::string,int))&Lua_ItemInst::SetCustomData) + .def("SetCustomData", (void(Lua_ItemInst::*)(std::string,std::string))&Lua_ItemInst::SetCustomData) + .def("SetExp", (void(Lua_ItemInst::*)(uint32))&Lua_ItemInst::SetExp) + .def("SetInstNoDrop", (void(Lua_ItemInst::*)(bool))&Lua_ItemInst::SetInstNoDrop) + .def("SetPrice", (void(Lua_ItemInst::*)(uint32))&Lua_ItemInst::SetPrice) + .def("SetScale", (void(Lua_ItemInst::*)(double))&Lua_ItemInst::SetScale) + .def("SetScaling", (void(Lua_ItemInst::*)(bool))&Lua_ItemInst::SetScaling) + .def("SetTimer", (void(Lua_ItemInst::*)(std::string,uint32))&Lua_ItemInst::SetTimer) + .def("StopTimer", (void(Lua_ItemInst::*)(std::string))&Lua_ItemInst::StopTimer); } #endif diff --git a/zone/lua_iteminst.h b/zone/lua_iteminst.h index 7614bea5d..c0a4d50c4 100644 --- a/zone/lua_iteminst.h +++ b/zone/lua_iteminst.h @@ -80,6 +80,8 @@ public: void SetTimer(std::string name, uint32 time); void StopTimer(std::string name); void ClearTimers(); + bool ContainsAugmentByID(uint32 item_id); + int CountAugmentByID(uint32 item_id); private: bool cloned_; diff --git a/zone/lua_mob.cpp b/zone/lua_mob.cpp index e702b8bf1..e19f93906 100644 --- a/zone/lua_mob.cpp +++ b/zone/lua_mob.cpp @@ -5,12 +5,17 @@ #include "client.h" #include "npc.h" +#ifdef BOTS +#include "lua_bot.h" +#endif #include "lua_item.h" #include "lua_iteminst.h" #include "lua_mob.h" +#include "lua_npc.h" #include "lua_hate_list.h" #include "lua_client.h" #include "lua_stat_bonuses.h" +#include "dialogue_window.h" struct SpecialAbilities { }; @@ -755,7 +760,19 @@ double Lua_Mob::GetSize() { void Lua_Mob::Message(int type, const char *message) { Lua_Safe_Call_Void(); - self->Message(type, message); + + // auto inject saylinks + if (RuleB(Chat, QuestDialogueUsesDialogueWindow) && self->IsClient()) { + std::string window_markdown = message; + DialogueWindow::Render(self->CastToClient(), window_markdown); + } + else if (RuleB(Chat, AutoInjectSaylinksToClientMessage)) { + std::string new_message = EQ::SayLinkEngine::InjectSaylinksIfNotExist(message); + self->Message(type, new_message.c_str()); + } + else { + self->Message(type, message); + } } void Lua_Mob::MessageString(int type, int string_id, uint32 distance) { @@ -967,6 +984,23 @@ Lua_HateList Lua_Mob::GetHateList() { return ret; } +Lua_HateList Lua_Mob::GetShuffledHateList() { + Lua_Safe_Call_Class(Lua_HateList); + Lua_HateList ret; + + auto h_list = self->GetHateList(); + auto iter = h_list.begin(); + while(iter != h_list.end()) { + Lua_HateEntry e(*iter); + ret.entries.push_back(e); + ++iter; + } + + zone->random.Shuffle(ret.entries.begin(), ret.entries.end()); + + return ret; +} + Lua_Mob Lua_Mob::GetHateTop() { Lua_Safe_Call_Class(Lua_Mob); return Lua_Mob(self->GetHateTop()); @@ -1857,9 +1891,9 @@ void Lua_Mob::WearChange(int material_slot, int texture, uint32 color) { self->WearChange(material_slot, texture, color); } -void Lua_Mob::DoKnockback(Lua_Mob caster, uint32 pushback, uint32 pushup) { +void Lua_Mob::DoKnockback(Lua_Mob caster, uint32 push_back, uint32 push_up) { Lua_Safe_Call_Void(); - self->DoKnockback(caster, pushback, pushup); + self->DoKnockback(caster, push_back, push_up); } void Lua_Mob::AddNimbusEffect(int effect_id) { @@ -1907,10 +1941,10 @@ int Lua_Mob::GetSkillDmgTaken(int skill) { return self->GetSkillDmgTaken(static_cast(skill)); } -int Lua_Mob::GetFcDamageAmtIncoming(Lua_Mob caster, uint32 spell_id, bool use_skill, uint16 skill) +int Lua_Mob::GetFcDamageAmtIncoming(Lua_Mob caster, int32 spell_id) { Lua_Safe_Call_Int(); - return self->GetFcDamageAmtIncoming(caster, spell_id, use_skill, skill); + return self->GetFcDamageAmtIncoming(caster, spell_id); } int Lua_Mob::GetSkillDmgAmt(uint16 skill) @@ -2164,6 +2198,16 @@ bool Lua_Mob::HasPet() { return self->HasPet(); } +void Lua_Mob::RemovePet() { + Lua_Safe_Call_Void(); + return self->SetPet(nullptr); +} + +void Lua_Mob::SetPet(Lua_Mob new_pet) { + Lua_Safe_Call_Void(); + return self->SetPet(new_pet); +} + bool Lua_Mob::IsSilenced() { Lua_Safe_Call_Bool(); return self->IsSilenced(); @@ -2342,412 +2386,462 @@ Lua_HateList Lua_Mob::GetHateListByDistance(int distance) { return ret; } +const char *Lua_Mob::GetLastName() { + Lua_Safe_Call_String(); + return self->GetLastName(); +} + +bool Lua_Mob::CanClassEquipItem(uint32 item_id) { + Lua_Safe_Call_Bool(); + return self->CanClassEquipItem(item_id); +} + +bool Lua_Mob::CanRaceEquipItem(uint32 item_id) { + Lua_Safe_Call_Bool(); + return self->CanRaceEquipItem(item_id); +} + +void Lua_Mob::RemoveAllNimbusEffects() { + Lua_Safe_Call_Void(); + self->RemoveAllNimbusEffects(); +} + +#ifdef BOTS +Lua_Bot Lua_Mob::GetHateRandomBot() { + Lua_Safe_Call_Class(Lua_Bot); + return Lua_Bot(self->GetHateRandomBot()); +} +#endif + +Lua_Client Lua_Mob::GetHateRandomClient() { + Lua_Safe_Call_Class(Lua_Client); + return Lua_Client(self->GetHateRandomClient()); +} + +Lua_NPC Lua_Mob::GetHateRandomNPC() { + Lua_Safe_Call_Class(Lua_NPC); + return Lua_NPC(self->GetHateRandomNPC()); +} + luabind::scope lua_register_mob() { return luabind::class_("Mob") - .def(luabind::constructor<>()) - .def("GetName", &Lua_Mob::GetName) - .def("Depop", (void(Lua_Mob::*)(void))&Lua_Mob::Depop) - .def("Depop", (void(Lua_Mob::*)(bool))&Lua_Mob::Depop) - .def("BehindMob", (bool(Lua_Mob::*)(void))&Lua_Mob::BehindMob) - .def("BehindMob", (bool(Lua_Mob::*)(Lua_Mob))&Lua_Mob::BehindMob) - .def("BehindMob", (bool(Lua_Mob::*)(Lua_Mob,float))&Lua_Mob::BehindMob) - .def("BehindMob", (bool(Lua_Mob::*)(Lua_Mob,float,float))&Lua_Mob::BehindMob) - .def("SetLevel", (void(Lua_Mob::*)(int))&Lua_Mob::SetLevel) - .def("SetLevel", (void(Lua_Mob::*)(int,bool))&Lua_Mob::SetLevel) - .def("IsMoving", &Lua_Mob::IsMoving) - .def("GotoBind", &Lua_Mob::GotoBind) - .def("Attack", (bool(Lua_Mob::*)(Lua_Mob))&Lua_Mob::Attack) - .def("Attack", (bool(Lua_Mob::*)(Lua_Mob,int))&Lua_Mob::Attack) - .def("Attack", (bool(Lua_Mob::*)(Lua_Mob,int,bool))&Lua_Mob::Attack) - .def("Attack", (bool(Lua_Mob::*)(Lua_Mob,int,bool,bool))&Lua_Mob::Attack) - .def("Attack", (bool(Lua_Mob::*)(Lua_Mob,int,bool,bool,bool))&Lua_Mob::Attack) - .def("Attack", (bool(Lua_Mob::*)(Lua_Mob,int,bool,bool,bool,luabind::adl::object))&Lua_Mob::Attack) - .def("Damage", (void(Lua_Mob::*)(Lua_Mob,int,int,int))&Lua_Mob::Damage) - .def("Damage", (void(Lua_Mob::*)(Lua_Mob,int,int,int,bool))&Lua_Mob::Damage) - .def("Damage", (void(Lua_Mob::*)(Lua_Mob,int,int,int,bool,int))&Lua_Mob::Damage) - .def("Damage", (void(Lua_Mob::*)(Lua_Mob,int,int,int,bool,int,bool))&Lua_Mob::Damage) - .def("RangedAttack", &Lua_Mob::RangedAttack) - .def("ThrowingAttack", &Lua_Mob::ThrowingAttack) - .def("Heal", &Lua_Mob::Heal) - .def("HealDamage", (void(Lua_Mob::*)(uint32))&Lua_Mob::HealDamage) - .def("HealDamage", (void(Lua_Mob::*)(uint32,Lua_Mob))&Lua_Mob::HealDamage) - .def("GetLevelCon", (uint32(Lua_Mob::*)(int))&Lua_Mob::GetLevelCon) - .def("GetLevelCon", (uint32(Lua_Mob::*)(int,int))&Lua_Mob::GetLevelCon) - .def("SetHP", &Lua_Mob::SetHP) - .def("DoAnim", (void(Lua_Mob::*)(int))&Lua_Mob::DoAnim) - .def("DoAnim", (void(Lua_Mob::*)(int,int))&Lua_Mob::DoAnim) - .def("DoAnim", (void(Lua_Mob::*)(int,int,bool))&Lua_Mob::DoAnim) - .def("DoAnim", (void(Lua_Mob::*)(int,int,bool,int))&Lua_Mob::DoAnim) - .def("ChangeSize", (void(Lua_Mob::*)(double))&Lua_Mob::ChangeSize) - .def("ChangeSize", (void(Lua_Mob::*)(double,bool))&Lua_Mob::ChangeSize) - .def("RandomizeFeatures", (void(Lua_Mob::*)(bool,bool))&Lua_Mob::RandomizeFeatures) - .def("GMMove", (void(Lua_Mob::*)(double,double,double))&Lua_Mob::GMMove) - .def("GMMove", (void(Lua_Mob::*)(double,double,double,double))&Lua_Mob::GMMove) - .def("GMMove", (void(Lua_Mob::*)(double,double,double,double,bool))&Lua_Mob::GMMove) - .def("TryMoveAlong", (void(Lua_Mob::*)(float,float))&Lua_Mob::TryMoveAlong) - .def("TryMoveAlong", (void(Lua_Mob::*)(float,float,bool))&Lua_Mob::TryMoveAlong) - .def("HasProcs", &Lua_Mob::HasProcs) - .def("IsInvisible", (bool(Lua_Mob::*)(void))&Lua_Mob::IsInvisible) - .def("IsInvisible", (bool(Lua_Mob::*)(Lua_Mob))&Lua_Mob::IsInvisible) - .def("SetInvisible", &Lua_Mob::SetInvisible) - .def("FindBuff", &Lua_Mob::FindBuff) - .def("FindBuffBySlot", (uint16(Lua_Mob::*)(int))&Lua_Mob::FindBuffBySlot) - .def("BuffCount", &Lua_Mob::BuffCount) - .def("FindType", (bool(Lua_Mob::*)(int))&Lua_Mob::FindType) - .def("FindType", (bool(Lua_Mob::*)(int,bool))&Lua_Mob::FindType) - .def("FindType", (bool(Lua_Mob::*)(int,bool,int))&Lua_Mob::FindType) - .def("GetBuffSlotFromType", &Lua_Mob::GetBuffSlotFromType) - .def("GetBaseRace", &Lua_Mob::GetBaseRace) - .def("GetBaseGender", &Lua_Mob::GetBaseGender) - .def("GetDeity", &Lua_Mob::GetDeity) - .def("GetRace", &Lua_Mob::GetRace) - .def("GetRaceName", &Lua_Mob::GetRaceName) - .def("GetGender", &Lua_Mob::GetGender) - .def("GetTexture", &Lua_Mob::GetTexture) - .def("GetHelmTexture", &Lua_Mob::GetHelmTexture) - .def("GetHairColor", &Lua_Mob::GetHairColor) - .def("GetBeardColor", &Lua_Mob::GetBeardColor) - .def("GetEyeColor1", &Lua_Mob::GetEyeColor1) - .def("GetEyeColor2", &Lua_Mob::GetEyeColor2) - .def("GetHairStyle", &Lua_Mob::GetHairStyle) - .def("GetLuclinFace", &Lua_Mob::GetLuclinFace) - .def("GetBeard", &Lua_Mob::GetBeard) - .def("GetDrakkinHeritage", &Lua_Mob::GetDrakkinHeritage) - .def("GetDrakkinTattoo", &Lua_Mob::GetDrakkinTattoo) - .def("GetDrakkinDetails", &Lua_Mob::GetDrakkinDetails) - .def("GetClass", &Lua_Mob::GetClass) - .def("GetClassName", &Lua_Mob::GetClassName) - .def("GetLevel", &Lua_Mob::GetLevel) - .def("GetCleanName", &Lua_Mob::GetCleanName) - .def("GetTarget", &Lua_Mob::GetTarget) - .def("SetTarget", &Lua_Mob::SetTarget) - .def("GetHPRatio", &Lua_Mob::GetHPRatio) - .def("IsWarriorClass", &Lua_Mob::IsWarriorClass) - .def("GetHP", &Lua_Mob::GetHP) - .def("GetMaxHP", &Lua_Mob::GetMaxHP) - .def("GetItemStat", (int(Lua_Mob::*)(uint32,const char*))&Lua_Mob::GetItemStat) - .def("GetItemHPBonuses", &Lua_Mob::GetItemHPBonuses) - .def("GetSpellHPBonuses", &Lua_Mob::GetSpellHPBonuses) - .def("GetWalkspeed", &Lua_Mob::GetWalkspeed) - .def("GetRunspeed", &Lua_Mob::GetRunspeed) - .def("GetCasterLevel", &Lua_Mob::GetCasterLevel) - .def("GetMaxMana", &Lua_Mob::GetMaxMana) - .def("GetMana", &Lua_Mob::GetMana) - .def("SetMana", &Lua_Mob::SetMana) - .def("GetManaRatio", &Lua_Mob::GetManaRatio) - .def("GetAC", &Lua_Mob::GetAC) - .def("GetDisplayAC", &Lua_Mob::GetDisplayAC) - .def("GetATK", &Lua_Mob::GetATK) - .def("GetSTR", &Lua_Mob::GetSTR) - .def("GetSTA", &Lua_Mob::GetSTA) - .def("GetDEX", &Lua_Mob::GetDEX) - .def("GetAGI", &Lua_Mob::GetAGI) - .def("GetINT", &Lua_Mob::GetINT) - .def("GetWIS", &Lua_Mob::GetWIS) - .def("GetCHA", &Lua_Mob::GetCHA) - .def("GetMR", &Lua_Mob::GetMR) - .def("GetFR", &Lua_Mob::GetFR) - .def("GetDR", &Lua_Mob::GetDR) - .def("GetPR", &Lua_Mob::GetPR) - .def("GetCR", &Lua_Mob::GetCR) - .def("GetCorruption", &Lua_Mob::GetCorruption) - .def("GetPhR", &Lua_Mob::GetPhR) - .def("GetMaxSTR", &Lua_Mob::GetMaxSTR) - .def("GetMaxSTA", &Lua_Mob::GetMaxSTA) - .def("GetMaxDEX", &Lua_Mob::GetMaxDEX) - .def("GetMaxAGI", &Lua_Mob::GetMaxAGI) - .def("GetMaxINT", &Lua_Mob::GetMaxINT) - .def("GetMaxWIS", &Lua_Mob::GetMaxWIS) - .def("GetMaxCHA", &Lua_Mob::GetMaxCHA) - .def("ResistSpell", (double(Lua_Mob::*)(int,int,Lua_Mob))&Lua_Mob::ResistSpell) - .def("ResistSpell", (double(Lua_Mob::*)(int,int,Lua_Mob,bool))&Lua_Mob::ResistSpell) - .def("ResistSpell", (double(Lua_Mob::*)(int,int,Lua_Mob,bool,int))&Lua_Mob::ResistSpell) - .def("ResistSpell", (double(Lua_Mob::*)(int,int,Lua_Mob,bool,int,bool))&Lua_Mob::ResistSpell) - .def("GetSpecializeSkillValue", &Lua_Mob::GetSpecializeSkillValue) - .def("GetNPCTypeID", &Lua_Mob::GetNPCTypeID) - .def("IsTargeted", &Lua_Mob::IsTargeted) - .def("GetX", &Lua_Mob::GetX) - .def("GetY", &Lua_Mob::GetY) - .def("GetZ", &Lua_Mob::GetZ) - .def("GetHeading", &Lua_Mob::GetHeading) - .def("GetWaypointX", &Lua_Mob::GetWaypointX) - .def("GetWaypointY", &Lua_Mob::GetWaypointY) - .def("GetWaypointZ", &Lua_Mob::GetWaypointZ) - .def("GetWaypointH", &Lua_Mob::GetWaypointH) - .def("GetWaypointPause", &Lua_Mob::GetWaypointPause) - .def("GetWaypointID", &Lua_Mob::GetWaypointID) - .def("SetCurrentWP", &Lua_Mob::SetCurrentWP) - .def("GetSize", &Lua_Mob::GetSize) - .def("Message", &Lua_Mob::Message) - .def("MessageString", &Lua_Mob::MessageString) - .def("Message_StringID", &Lua_Mob::MessageString) - .def("Say", (void(Lua_Mob::*)(const char*))& Lua_Mob::Say) - .def("Say", (void(Lua_Mob::*)(const char*, int))& Lua_Mob::Say) - .def("QuestSay", (void(Lua_Mob::*)(Lua_Client,const char *))&Lua_Mob::QuestSay) - .def("QuestSay", (void(Lua_Mob::*)(Lua_Client,const char *,luabind::adl::object))&Lua_Mob::QuestSay) - .def("Shout", (void(Lua_Mob::*)(const char*))& Lua_Mob::Shout) - .def("Shout", (void(Lua_Mob::*)(const char*, int))& Lua_Mob::Shout) - .def("Emote", &Lua_Mob::Emote) - .def("InterruptSpell", (void(Lua_Mob::*)(void))&Lua_Mob::InterruptSpell) - .def("InterruptSpell", (void(Lua_Mob::*)(int))&Lua_Mob::InterruptSpell) - .def("CastSpell", (bool(Lua_Mob::*)(int,int))&Lua_Mob::CastSpell) - .def("CastSpell", (bool(Lua_Mob::*)(int,int,int))&Lua_Mob::CastSpell) - .def("CastSpell", (bool(Lua_Mob::*)(int,int,int,int))&Lua_Mob::CastSpell) - .def("CastSpell", (bool(Lua_Mob::*)(int,int,int,int,int))&Lua_Mob::CastSpell) - .def("CastSpell", (bool(Lua_Mob::*)(int,int,int,int,int,int))&Lua_Mob::CastSpell) - .def("CastSpell", (bool(Lua_Mob::*)(int,int,int,int,int,int,int,int))&Lua_Mob::CastSpell) - .def("CastSpell", (bool(Lua_Mob::*)(int,int,int,int,int,int,int,int,int))&Lua_Mob::CastSpell) - .def("SpellFinished", (bool(Lua_Mob::*)(int,Lua_Mob))&Lua_Mob::SpellFinished) - .def("SpellFinished", (bool(Lua_Mob::*)(int,Lua_Mob,int))&Lua_Mob::SpellFinished) - .def("SpellFinished", (bool(Lua_Mob::*)(int,Lua_Mob,int,int))&Lua_Mob::SpellFinished) - .def("SpellFinished", (bool(Lua_Mob::*)(int,Lua_Mob,int,int,uint32))&Lua_Mob::SpellFinished) - .def("SpellFinished", (bool(Lua_Mob::*)(int,Lua_Mob,int,int,uint32,int))&Lua_Mob::SpellFinished) - .def("SpellFinished", (bool(Lua_Mob::*)(int,Lua_Mob,int,int,uint32,int,bool))&Lua_Mob::SpellFinished) - .def("SendBeginCast", &Lua_Mob::SendBeginCast) - .def("SpellEffect", &Lua_Mob::SpellEffect) - .def("GetPet", &Lua_Mob::GetPet) - .def("GetOwner", &Lua_Mob::GetOwner) - .def("GetHateList", &Lua_Mob::GetHateList) - .def("GetHateListByDistance", (Lua_HateList(Lua_Mob::*)(void))&Lua_Mob::GetHateListByDistance) - .def("GetHateListByDistance", (Lua_HateList(Lua_Mob::*)(int))&Lua_Mob::GetHateListByDistance) - .def("GetHateTop", (Lua_Mob(Lua_Mob::*)(void))&Lua_Mob::GetHateTop) - .def("GetHateDamageTop", (Lua_Mob(Lua_Mob::*)(Lua_Mob))&Lua_Mob::GetHateDamageTop) - .def("GetHateRandom", (Lua_Mob(Lua_Mob::*)(void))&Lua_Mob::GetHateRandom) - .def("GetHateClosest", &Lua_Mob::GetHateClosest) - .def("AddToHateList", (void(Lua_Mob::*)(Lua_Mob))&Lua_Mob::AddToHateList) - .def("AddToHateList", (void(Lua_Mob::*)(Lua_Mob,int))&Lua_Mob::AddToHateList) - .def("AddToHateList", (void(Lua_Mob::*)(Lua_Mob,int,int))&Lua_Mob::AddToHateList) - .def("AddToHateList", (void(Lua_Mob::*)(Lua_Mob,int,int,bool))&Lua_Mob::AddToHateList) - .def("AddToHateList", (void(Lua_Mob::*)(Lua_Mob,int,int,bool,bool))&Lua_Mob::AddToHateList) - .def("AddToHateList", (void(Lua_Mob::*)(Lua_Mob,int,int,bool,bool,bool))&Lua_Mob::AddToHateList) - .def("SetHate", (void(Lua_Mob::*)(Lua_Mob))&Lua_Mob::SetHate) - .def("SetHate", (void(Lua_Mob::*)(Lua_Mob,int))&Lua_Mob::SetHate) - .def("SetHate", (void(Lua_Mob::*)(Lua_Mob,int,int))&Lua_Mob::SetHate) - .def("HalveAggro", &Lua_Mob::HalveAggro) - .def("DoubleAggro", &Lua_Mob::DoubleAggro) - .def("GetHateAmount", (uint32(Lua_Mob::*)(Lua_Mob))&Lua_Mob::GetHateAmount) - .def("GetHateAmount", (uint32(Lua_Mob::*)(Lua_Mob,bool))&Lua_Mob::GetHateAmount) - .def("GetDamageAmount", (uint32(Lua_Mob::*)(Lua_Mob))&Lua_Mob::GetDamageAmount) - .def("WipeHateList", (void(Lua_Mob::*)(void))&Lua_Mob::WipeHateList) - .def("CheckAggro", (bool(Lua_Mob::*)(Lua_Mob))&Lua_Mob::CheckAggro) - .def("Stun", (void(Lua_Mob::*)(int))&Lua_Mob::Stun) - .def("UnStun", (void(Lua_Mob::*)(void))&Lua_Mob::UnStun) - .def("IsStunned", (bool(Lua_Mob::*)(void))&Lua_Mob::IsStunned) - .def("Spin", (void(Lua_Mob::*)(void))&Lua_Mob::Spin) - .def("Kill", (void(Lua_Mob::*)(void))&Lua_Mob::Kill) - .def("CanThisClassDoubleAttack", (bool(Lua_Mob::*)(void))&Lua_Mob::CanThisClassDoubleAttack) - .def("CanThisClassDualWield", (bool(Lua_Mob::*)(void))&Lua_Mob::CanThisClassDualWield) - .def("CanThisClassRiposte", (bool(Lua_Mob::*)(void))&Lua_Mob::CanThisClassRiposte) - .def("CanThisClassDodge", (bool(Lua_Mob::*)(void))&Lua_Mob::CanThisClassDodge) - .def("CanThisClassParry", (bool(Lua_Mob::*)(void))&Lua_Mob::CanThisClassParry) - .def("CanThisClassBlock", (bool(Lua_Mob::*)(void))&Lua_Mob::CanThisClassBlock) - .def("SetInvul", (void(Lua_Mob::*)(bool))&Lua_Mob::SetInvul) - .def("GetInvul", (bool(Lua_Mob::*)(void))&Lua_Mob::GetInvul) - .def("SetExtraHaste", (void(Lua_Mob::*)(int))&Lua_Mob::SetExtraHaste) - .def("GetHaste", (int(Lua_Mob::*)(void))&Lua_Mob::GetHaste) - .def("GetHandToHandDamage", (int(Lua_Mob::*)(void))&Lua_Mob::GetHandToHandDamage) - .def("GetHandToHandDelay", (int(Lua_Mob::*)(void))&Lua_Mob::GetHandToHandDelay) - .def("Mesmerize", (void(Lua_Mob::*)(void))&Lua_Mob::Mesmerize) - .def("IsMezzed", (bool(Lua_Mob::*)(void))&Lua_Mob::IsMezzed) - .def("IsEnraged", (bool(Lua_Mob::*)(void))&Lua_Mob::IsEnraged) - .def("GetReverseFactionCon", (int(Lua_Mob::*)(Lua_Mob))&Lua_Mob::GetReverseFactionCon) - .def("IsAIControlled", (bool(Lua_Mob::*)(void))&Lua_Mob::IsAIControlled) - .def("GetAggroRange", (float(Lua_Mob::*)(void))&Lua_Mob::GetAggroRange) - .def("GetAssistRange", (float(Lua_Mob::*)(void))&Lua_Mob::GetAssistRange) - .def("SetPetOrder", (void(Lua_Mob::*)(int))&Lua_Mob::SetPetOrder) - .def("GetPetOrder", (int(Lua_Mob::*)(void))&Lua_Mob::GetPetOrder) - .def("IsRoamer", (bool(Lua_Mob::*)(void))&Lua_Mob::IsRoamer) - .def("IsRooted", (bool(Lua_Mob::*)(void))&Lua_Mob::IsRooted) - .def("IsEngaged", (bool(Lua_Mob::*)(void))&Lua_Mob::IsEngaged) - .def("FaceTarget", (void(Lua_Mob::*)(Lua_Mob))&Lua_Mob::FaceTarget) - .def("SetHeading", (void(Lua_Mob::*)(double))&Lua_Mob::SetHeading) - .def("CalculateHeadingToTarget", (double(Lua_Mob::*)(double,double))&Lua_Mob::CalculateHeadingToTarget) - .def("RunTo", (void(Lua_Mob::*)(double, double, double))&Lua_Mob::RunTo) - .def("WalkTo", (void(Lua_Mob::*)(double, double, double))&Lua_Mob::WalkTo) - .def("NavigateTo", (void(Lua_Mob::*)(double,double,double))&Lua_Mob::NavigateTo) - .def("StopNavigation", (void(Lua_Mob::*)(void))&Lua_Mob::StopNavigation) - .def("CalculateDistance", (float(Lua_Mob::*)(double,double,double))&Lua_Mob::CalculateDistance) - .def("SendTo", (void(Lua_Mob::*)(double,double,double))&Lua_Mob::SendTo) - .def("SendToFixZ", (void(Lua_Mob::*)(double,double,double))&Lua_Mob::SendToFixZ) - .def("NPCSpecialAttacks", (void(Lua_Mob::*)(const char*,int))&Lua_Mob::NPCSpecialAttacks) - .def("NPCSpecialAttacks", (void(Lua_Mob::*)(const char*,int,bool))&Lua_Mob::NPCSpecialAttacks) - .def("NPCSpecialAttacks", (void(Lua_Mob::*)(const char*,int,bool,bool))&Lua_Mob::NPCSpecialAttacks) - .def("GetResist", (int(Lua_Mob::*)(int))&Lua_Mob::GetResist) - .def("Charmed", (bool(Lua_Mob::*)(void))&Lua_Mob::Charmed) - .def("CheckAggroAmount", (int(Lua_Mob::*)(int))&Lua_Mob::CheckAggroAmount) - .def("CheckAggroAmount", (int(Lua_Mob::*)(int,bool))&Lua_Mob::CheckAggroAmount) - .def("CheckHealAggroAmount", (int(Lua_Mob::*)(int))&Lua_Mob::CheckHealAggroAmount) - .def("CheckHealAggroAmount", (int(Lua_Mob::*)(int,uint32))&Lua_Mob::CheckHealAggroAmount) - .def("GetAA", (int(Lua_Mob::*)(int))&Lua_Mob::GetAA) - .def("GetAAByAAID", (int(Lua_Mob::*)(int))&Lua_Mob::GetAAByAAID) - .def("SetAA", (bool(Lua_Mob::*)(int,int))&Lua_Mob::SetAA) - .def("SetAA", (bool(Lua_Mob::*)(int,int,int))&Lua_Mob::SetAA) - .def("DivineAura", (bool(Lua_Mob::*)(void))&Lua_Mob::DivineAura) - .def("SetOOCRegen", (void(Lua_Mob::*)(int))&Lua_Mob::SetOOCRegen) - .def("GetEntityVariable", (const char*(Lua_Mob::*)(const char*))&Lua_Mob::GetEntityVariable) - .def("SetEntityVariable", (void(Lua_Mob::*)(const char*,const char*))&Lua_Mob::SetEntityVariable) - .def("EntityVariableExists", (bool(Lua_Mob::*)(const char*))&Lua_Mob::EntityVariableExists) - .def("Signal", (void(Lua_Mob::*)(uint32))&Lua_Mob::Signal) - .def("CombatRange", (bool(Lua_Mob::*)(Lua_Mob))&Lua_Mob::CombatRange) - .def("DoSpecialAttackDamage", (void(Lua_Mob::*)(Lua_Mob,int,int))&Lua_Mob::DoSpecialAttackDamage) - .def("DoSpecialAttackDamage", (void(Lua_Mob::*)(Lua_Mob,int,int,int))&Lua_Mob::DoSpecialAttackDamage) - .def("DoSpecialAttackDamage", (void(Lua_Mob::*)(Lua_Mob,int,int,int,int))&Lua_Mob::DoSpecialAttackDamage) - .def("DoSpecialAttackDamage", (void(Lua_Mob::*)(Lua_Mob,int,int,int,int,int))&Lua_Mob::DoSpecialAttackDamage) - .def("DoThrowingAttackDmg", (void(Lua_Mob::*)(Lua_Mob))&Lua_Mob::DoThrowingAttackDmg) - .def("DoThrowingAttackDmg", (void(Lua_Mob::*)(Lua_Mob,Lua_ItemInst))&Lua_Mob::DoThrowingAttackDmg) - .def("DoThrowingAttackDmg", (void(Lua_Mob::*)(Lua_Mob,Lua_ItemInst,Lua_Item))&Lua_Mob::DoThrowingAttackDmg) - .def("DoThrowingAttackDmg", (void(Lua_Mob::*)(Lua_Mob,Lua_ItemInst,Lua_Item,int))&Lua_Mob::DoThrowingAttackDmg) - .def("DoThrowingAttackDmg", (void(Lua_Mob::*)(Lua_Mob,Lua_ItemInst,Lua_Item,int,int))&Lua_Mob::DoThrowingAttackDmg) - .def("DoThrowingAttackDmg", (void(Lua_Mob::*)(Lua_Mob,Lua_ItemInst,Lua_Item,int,int,int))&Lua_Mob::DoThrowingAttackDmg) - .def("DoMeleeSkillAttackDmg", (void(Lua_Mob::*)(Lua_Mob,int,int))&Lua_Mob::DoMeleeSkillAttackDmg) - .def("DoMeleeSkillAttackDmg", (void(Lua_Mob::*)(Lua_Mob,int,int,int))&Lua_Mob::DoMeleeSkillAttackDmg) - .def("DoMeleeSkillAttackDmg", (void(Lua_Mob::*)(Lua_Mob,int,int,int,int))&Lua_Mob::DoMeleeSkillAttackDmg) - .def("DoMeleeSkillAttackDmg", (void(Lua_Mob::*)(Lua_Mob,int,int,int,int,bool))&Lua_Mob::DoMeleeSkillAttackDmg) - .def("DoArcheryAttackDmg", (void(Lua_Mob::*)(Lua_Mob))&Lua_Mob::DoArcheryAttackDmg) - .def("DoArcheryAttackDmg", (void(Lua_Mob::*)(Lua_Mob,Lua_ItemInst))&Lua_Mob::DoArcheryAttackDmg) - .def("DoArcheryAttackDmg", (void(Lua_Mob::*)(Lua_Mob,Lua_ItemInst,Lua_ItemInst))&Lua_Mob::DoArcheryAttackDmg) - .def("DoArcheryAttackDmg", (void(Lua_Mob::*)(Lua_Mob,Lua_ItemInst,Lua_ItemInst,int))&Lua_Mob::DoArcheryAttackDmg) - .def("DoArcheryAttackDmg", (void(Lua_Mob::*)(Lua_Mob,Lua_ItemInst,Lua_ItemInst,int,int))&Lua_Mob::DoArcheryAttackDmg) - .def("DoArcheryAttackDmg", (void(Lua_Mob::*)(Lua_Mob,Lua_ItemInst,Lua_ItemInst,int,int,int))&Lua_Mob::DoArcheryAttackDmg) - .def("CheckLoS", (bool(Lua_Mob::*)(Lua_Mob))&Lua_Mob::CheckLoS) - .def("CheckLoSToLoc", (bool(Lua_Mob::*)(double,double,double))&Lua_Mob::CheckLoSToLoc) - .def("CheckLoSToLoc", (bool(Lua_Mob::*)(double,double,double,double))&Lua_Mob::CheckLoSToLoc) - .def("FindGroundZ", (double(Lua_Mob::*)(double,double))&Lua_Mob::FindGroundZ) - .def("FindGroundZ", (double(Lua_Mob::*)(double,double,double))&Lua_Mob::FindGroundZ) - .def("ProjectileAnimation", (void(Lua_Mob::*)(Lua_Mob,int))&Lua_Mob::ProjectileAnimation) - .def("ProjectileAnimation", (void(Lua_Mob::*)(Lua_Mob,int,bool))&Lua_Mob::ProjectileAnimation) - .def("ProjectileAnimation", (void(Lua_Mob::*)(Lua_Mob,int,bool,double))&Lua_Mob::ProjectileAnimation) - .def("ProjectileAnimation", (void(Lua_Mob::*)(Lua_Mob,int,bool,double,double))&Lua_Mob::ProjectileAnimation) - .def("ProjectileAnimation", (void(Lua_Mob::*)(Lua_Mob,int,bool,double,double,double))&Lua_Mob::ProjectileAnimation) - .def("ProjectileAnimation", (void(Lua_Mob::*)(Lua_Mob,int,bool,double,double,double,double))&Lua_Mob::ProjectileAnimation) - .def("HasNPCSpecialAtk", (bool(Lua_Mob::*)(const char*))&Lua_Mob::HasNPCSpecialAtk) - .def("SendAppearanceEffect", (void(Lua_Mob::*)(uint32,uint32,uint32,uint32,uint32))&Lua_Mob::SendAppearanceEffect) - .def("SendAppearanceEffect", (void(Lua_Mob::*)(uint32,uint32,uint32,uint32,uint32,Lua_Client))&Lua_Mob::SendAppearanceEffect) - .def("SetFlyMode", (void(Lua_Mob::*)(int))&Lua_Mob::SetFlyMode) - .def("SetTexture", (void(Lua_Mob::*)(int))&Lua_Mob::SetTexture) - .def("SetRace", (void(Lua_Mob::*)(int))&Lua_Mob::SetRace) - .def("SetGender", (void(Lua_Mob::*)(int))&Lua_Mob::SetGender) - .def("SendIllusionPacket", (void(Lua_Mob::*)(luabind::adl::object))&Lua_Mob::SendIllusionPacket) - .def("ChangeRace", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeRace) - .def("ChangeGender", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeGender) - .def("ChangeTexture", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeTexture) - .def("ChangeHelmTexture", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeHelmTexture) - .def("ChangeHairColor", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeHairColor) - .def("ChangeBeardColor", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeBeardColor) - .def("ChangeEyeColor1", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeEyeColor1) - .def("ChangeEyeColor2", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeEyeColor2) - .def("ChangeHairStyle", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeHairStyle) - .def("ChangeLuclinFace", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeLuclinFace) - .def("ChangeBeard", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeBeard) - .def("ChangeDrakkinHeritage", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeDrakkinHeritage) - .def("ChangeDrakkinTattoo", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeDrakkinTattoo) - .def("ChangeDrakkinDetails", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeDrakkinDetails) - .def("CameraEffect", (void(Lua_Mob::*)(uint32,uint32))&Lua_Mob::CameraEffect) - .def("CameraEffect", (void(Lua_Mob::*)(uint32,uint32,Lua_Client))&Lua_Mob::CameraEffect) - .def("CameraEffect", (void(Lua_Mob::*)(uint32,uint32,Lua_Client,bool))&Lua_Mob::CameraEffect) - .def("SendSpellEffect", (void(Lua_Mob::*)(uint32,uint32,uint32,bool,uint32))&Lua_Mob::SendSpellEffect) - .def("SendSpellEffect", (void(Lua_Mob::*)(uint32,uint32,uint32,bool,uint32,bool))&Lua_Mob::SendSpellEffect) - .def("SendSpellEffect", (void(Lua_Mob::*)(uint32,uint32,uint32,bool,uint32,bool,Lua_Client))&Lua_Mob::SendSpellEffect) - .def("TempName", (void(Lua_Mob::*)(void))&Lua_Mob::TempName) - .def("TempName", (void(Lua_Mob::*)(const char*))&Lua_Mob::TempName) - .def("GetGlobal", (std::string(Lua_Mob::*)(const char*))&Lua_Mob::GetGlobal) - .def("SetGlobal", (void(Lua_Mob::*)(const char*,const char*,int,const char*))&Lua_Mob::SetGlobal) - .def("SetGlobal", (void(Lua_Mob::*)(const char*,const char*,int,const char*,Lua_Mob))&Lua_Mob::SetGlobal) - .def("TarGlobal", (void(Lua_Mob::*)(const char*,const char*,const char*,int,int,int))&Lua_Mob::TarGlobal) - .def("DelGlobal", (void(Lua_Mob::*)(const char*))&Lua_Mob::DelGlobal) - .def("SetSlotTint", (void(Lua_Mob::*)(int,int,int,int))&Lua_Mob::SetSlotTint) - .def("WearChange", (void(Lua_Mob::*)(int,int,uint32))&Lua_Mob::WearChange) - .def("DoKnockback", (void(Lua_Mob::*)(Lua_Mob,uint32,uint32))&Lua_Mob::DoKnockback) - .def("AddNimbusEffect", (void(Lua_Mob::*)(int))&Lua_Mob::AddNimbusEffect) - .def("RemoveNimbusEffect", (void(Lua_Mob::*)(int))&Lua_Mob::RemoveNimbusEffect) - .def("IsFeared", (bool(Lua_Mob::*)(void))&Lua_Mob::IsFeared) - .def("IsBlind", (bool(Lua_Mob::*)(void))&Lua_Mob::IsBlind) - .def("IsRunning", (bool(Lua_Mob::*)(void))&Lua_Mob::IsRunning) - .def("SetRunning", (void(Lua_Mob::*)(bool))&Lua_Mob::SetRunning) - .def("SetBodyType", (void(Lua_Mob::*)(int,bool))&Lua_Mob::SetBodyType) - .def("SetTargetable", (void(Lua_Mob::*)(bool))&Lua_Mob::SetTargetable) - .def("ModSkillDmgTaken", (void(Lua_Mob::*)(int,int))&Lua_Mob::ModSkillDmgTaken) - .def("GetModSkillDmgTaken", (int(Lua_Mob::*)(int))&Lua_Mob::GetModSkillDmgTaken) - .def("GetSkillDmgTaken", (int(Lua_Mob::*)(int))&Lua_Mob::GetSkillDmgTaken) - .def("GetFcDamageAmtIncoming", &Lua_Mob::GetFcDamageAmtIncoming) - .def("GetSkillDmgAmt", (int(Lua_Mob::*)(int))&Lua_Mob::GetSkillDmgAmt) - .def("SetAllowBeneficial", (void(Lua_Mob::*)(bool))&Lua_Mob::SetAllowBeneficial) - .def("GetAllowBeneficial", (bool(Lua_Mob::*)(void))&Lua_Mob::GetAllowBeneficial) - .def("IsBeneficialAllowed", (bool(Lua_Mob::*)(Lua_Mob))&Lua_Mob::IsBeneficialAllowed) - .def("ModVulnerability", (void(Lua_Mob::*)(int,int))&Lua_Mob::ModVulnerability) - .def("GetModVulnerability", (int(Lua_Mob::*)(int))&Lua_Mob::GetModVulnerability) - .def("SetDisableMelee", (void(Lua_Mob::*)(bool))&Lua_Mob::SetDisableMelee) - .def("IsMeleeDisabled", (bool(Lua_Mob::*)(void))&Lua_Mob::IsMeleeDisabled) - .def("SetFlurryChance", (void(Lua_Mob::*)(int))&Lua_Mob::SetFlurryChance) - .def("GetFlurryChance", (int(Lua_Mob::*)(void))&Lua_Mob::GetFlurryChance) - .def("GetSkill", (int(Lua_Mob::*)(int))&Lua_Mob::GetSkill) - .def("GetSpecialAbility", (int(Lua_Mob::*)(int))&Lua_Mob::GetSpecialAbility) - .def("GetSpecialAbilityParam", (int(Lua_Mob::*)(int,int))&Lua_Mob::GetSpecialAbilityParam) - .def("SetSpecialAbility", (void(Lua_Mob::*)(int,int))&Lua_Mob::SetSpecialAbility) - .def("SetSpecialAbilityParam", (void(Lua_Mob::*)(int,int,int))&Lua_Mob::SetSpecialAbilityParam) - .def("ClearSpecialAbilities", (void(Lua_Mob::*)(void))&Lua_Mob::ClearSpecialAbilities) - .def("ProcessSpecialAbilities", (void(Lua_Mob::*)(std::string))&Lua_Mob::ProcessSpecialAbilities) - .def("GetAppearance", (uint32(Lua_Mob::*)(void))&Lua_Mob::GetAppearance) - .def("SetAppearance", (void(Lua_Mob::*)(int))&Lua_Mob::SetAppearance) - .def("SetAppearance", (void(Lua_Mob::*)(int,bool))&Lua_Mob::SetAppearance) - .def("SetDestructibleObject", (void(Lua_Mob::*)(bool))&Lua_Mob::SetDestructibleObject) - .def("IsImmuneToSpell", (bool(Lua_Mob::*)(int,Lua_Mob))&Lua_Mob::IsImmuneToSpell) - .def("BuffFadeBySpellID", (void(Lua_Mob::*)(int))&Lua_Mob::BuffFadeBySpellID) - .def("BuffFadeByEffect", (void(Lua_Mob::*)(int))&Lua_Mob::BuffFadeByEffect) - .def("BuffFadeByEffect", (void(Lua_Mob::*)(int,int))&Lua_Mob::BuffFadeByEffect) - .def("BuffFadeAll", (void(Lua_Mob::*)(void))&Lua_Mob::BuffFadeAll) - .def("BuffFadeBySlot", (void(Lua_Mob::*)(int))&Lua_Mob::BuffFadeBySlot) - .def("BuffFadeBySlot", (void(Lua_Mob::*)(int,bool))&Lua_Mob::BuffFadeBySlot) - .def("CanBuffStack", (int(Lua_Mob::*)(int,int))&Lua_Mob::CanBuffStack) - .def("CanBuffStack", (int(Lua_Mob::*)(int,int,bool))&Lua_Mob::CanBuffStack) - .def("SetPseudoRoot", (void(Lua_Mob::*)(bool))&Lua_Mob::SetPseudoRoot) - .def("SeeInvisible", (uint8(Lua_Mob::*)(void))&Lua_Mob::SeeInvisible) - .def("SeeInvisibleUndead", (bool(Lua_Mob::*)(void))&Lua_Mob::SeeInvisibleUndead) - .def("SeeHide", (bool(Lua_Mob::*)(void))&Lua_Mob::SeeHide) - .def("SeeImprovedHide", (bool(Lua_Mob::*)(bool))&Lua_Mob::SeeImprovedHide) - .def("GetNimbusEffect1", (uint8(Lua_Mob::*)(void))&Lua_Mob::GetNimbusEffect1) - .def("GetNimbusEffect2", (uint8(Lua_Mob::*)(void))&Lua_Mob::GetNimbusEffect2) - .def("GetNimbusEffect3", (uint8(Lua_Mob::*)(void))&Lua_Mob::GetNimbusEffect3) - .def("IsTargetable", (bool(Lua_Mob::*)(void))&Lua_Mob::IsTargetable) - .def("HasShieldEquiped", (bool(Lua_Mob::*)(void))&Lua_Mob::HasShieldEquiped) - .def("HasTwoHandBluntEquiped", (bool(Lua_Mob::*)(void))&Lua_Mob::HasTwoHandBluntEquiped) - .def("HasTwoHanderEquipped", (bool(Lua_Mob::*)(void))&Lua_Mob::HasTwoHanderEquipped) - .def("GetHerosForgeModel", (int32(Lua_Mob::*)(uint8))&Lua_Mob::GetHerosForgeModel) - .def("IsEliteMaterialItem", (uint32(Lua_Mob::*)(uint8))&Lua_Mob::IsEliteMaterialItem) - .def("GetBaseSize", (double(Lua_Mob::*)(void))&Lua_Mob::GetBaseSize) - .def("HasOwner", (bool(Lua_Mob::*)(void))&Lua_Mob::HasOwner) - .def("IsPet", (bool(Lua_Mob::*)(void))&Lua_Mob::IsPet) - .def("HasPet", (bool(Lua_Mob::*)(void))&Lua_Mob::HasPet) - .def("IsSilenced", (bool(Lua_Mob::*)(void))&Lua_Mob::IsSilenced) - .def("IsAmnesiad", (bool(Lua_Mob::*)(void))&Lua_Mob::IsAmnesiad) - .def("GetMeleeMitigation", (int32(Lua_Mob::*)(void))&Lua_Mob::GetMeleeMitigation) - .def("GetWeaponDamageBonus", &Lua_Mob::GetWeaponDamageBonus) - .def("GetItemBonuses", &Lua_Mob::GetItemBonuses) - .def("GetSpellBonuses", &Lua_Mob::GetSpellBonuses) - .def("GetAABonuses", &Lua_Mob::GetAABonuses) - .def("GetMeleeDamageMod_SE", &Lua_Mob::GetMeleeDamageMod_SE) - .def("GetMeleeMinDamageMod_SE", &Lua_Mob::GetMeleeMinDamageMod_SE) - .def("IsAttackAllowed", &Lua_Mob::IsAttackAllowed) - .def("IsCasting", &Lua_Mob::IsCasting) - .def("AttackAnimation", &Lua_Mob::AttackAnimation) - .def("GetWeaponDamage", &Lua_Mob::GetWeaponDamage) - .def("IsBerserk", &Lua_Mob::IsBerserk) - .def("TryFinishingBlow", &Lua_Mob::TryFinishingBlow) - .def("GetBodyType", &Lua_Mob::GetBodyType) - .def("GetOrigBodyType", &Lua_Mob::GetOrigBodyType) - .def("CheckNumHitsRemaining", &Lua_Mob::CheckNumHitsRemaining) - .def("DeleteBucket", (void(Lua_Mob::*)(std::string))&Lua_Mob::DeleteBucket) - .def("GetBucket", (std::string(Lua_Mob::*)(std::string))&Lua_Mob::GetBucket) - .def("GetBucketExpires", (std::string(Lua_Mob::*)(std::string))&Lua_Mob::GetBucketExpires) - .def("GetBucketKey", (std::string(Lua_Mob::*)(void))&Lua_Mob::GetBucketKey) - .def("GetBucketRemaining", (std::string(Lua_Mob::*)(std::string))&Lua_Mob::GetBucketRemaining) - .def("SetBucket", (void(Lua_Mob::*)(std::string,std::string))&Lua_Mob::SetBucket) - .def("SetBucket", (void(Lua_Mob::*)(std::string,std::string,std::string))&Lua_Mob::SetBucket) - .def("IsHorse", &Lua_Mob::IsHorse); + .def(luabind::constructor<>()) + .def("AddNimbusEffect", (void(Lua_Mob::*)(int))&Lua_Mob::AddNimbusEffect) + .def("AddToHateList", (void(Lua_Mob::*)(Lua_Mob))&Lua_Mob::AddToHateList) + .def("AddToHateList", (void(Lua_Mob::*)(Lua_Mob,int))&Lua_Mob::AddToHateList) + .def("AddToHateList", (void(Lua_Mob::*)(Lua_Mob,int,int))&Lua_Mob::AddToHateList) + .def("AddToHateList", (void(Lua_Mob::*)(Lua_Mob,int,int,bool))&Lua_Mob::AddToHateList) + .def("AddToHateList", (void(Lua_Mob::*)(Lua_Mob,int,int,bool,bool))&Lua_Mob::AddToHateList) + .def("AddToHateList", (void(Lua_Mob::*)(Lua_Mob,int,int,bool,bool,bool))&Lua_Mob::AddToHateList) + .def("Attack", (bool(Lua_Mob::*)(Lua_Mob))&Lua_Mob::Attack) + .def("Attack", (bool(Lua_Mob::*)(Lua_Mob,int))&Lua_Mob::Attack) + .def("Attack", (bool(Lua_Mob::*)(Lua_Mob,int,bool))&Lua_Mob::Attack) + .def("Attack", (bool(Lua_Mob::*)(Lua_Mob,int,bool,bool))&Lua_Mob::Attack) + .def("Attack", (bool(Lua_Mob::*)(Lua_Mob,int,bool,bool,bool))&Lua_Mob::Attack) + .def("Attack", (bool(Lua_Mob::*)(Lua_Mob,int,bool,bool,bool,luabind::adl::object))&Lua_Mob::Attack) + .def("AttackAnimation", &Lua_Mob::AttackAnimation) + .def("BehindMob", (bool(Lua_Mob::*)(Lua_Mob))&Lua_Mob::BehindMob) + .def("BehindMob", (bool(Lua_Mob::*)(Lua_Mob,float))&Lua_Mob::BehindMob) + .def("BehindMob", (bool(Lua_Mob::*)(Lua_Mob,float,float))&Lua_Mob::BehindMob) + .def("BehindMob", (bool(Lua_Mob::*)(void))&Lua_Mob::BehindMob) + .def("BuffCount", &Lua_Mob::BuffCount) + .def("BuffFadeAll", (void(Lua_Mob::*)(void))&Lua_Mob::BuffFadeAll) + .def("BuffFadeByEffect", (void(Lua_Mob::*)(int))&Lua_Mob::BuffFadeByEffect) + .def("BuffFadeByEffect", (void(Lua_Mob::*)(int,int))&Lua_Mob::BuffFadeByEffect) + .def("BuffFadeBySlot", (void(Lua_Mob::*)(int))&Lua_Mob::BuffFadeBySlot) + .def("BuffFadeBySlot", (void(Lua_Mob::*)(int,bool))&Lua_Mob::BuffFadeBySlot) + .def("BuffFadeBySpellID", (void(Lua_Mob::*)(int))&Lua_Mob::BuffFadeBySpellID) + .def("CalculateDistance", (float(Lua_Mob::*)(double,double,double))&Lua_Mob::CalculateDistance) + .def("CalculateHeadingToTarget", (double(Lua_Mob::*)(double,double))&Lua_Mob::CalculateHeadingToTarget) + .def("CameraEffect", (void(Lua_Mob::*)(uint32,uint32))&Lua_Mob::CameraEffect) + .def("CameraEffect", (void(Lua_Mob::*)(uint32,uint32,Lua_Client))&Lua_Mob::CameraEffect) + .def("CameraEffect", (void(Lua_Mob::*)(uint32,uint32,Lua_Client,bool))&Lua_Mob::CameraEffect) + .def("CanBuffStack", (int(Lua_Mob::*)(int,int))&Lua_Mob::CanBuffStack) + .def("CanBuffStack", (int(Lua_Mob::*)(int,int,bool))&Lua_Mob::CanBuffStack) + .def("CanClassEquipItem", &Lua_Mob::CanClassEquipItem) + .def("CanRaceEquipItem", &Lua_Mob::CanRaceEquipItem) + .def("CanThisClassBlock", (bool(Lua_Mob::*)(void))&Lua_Mob::CanThisClassBlock) + .def("CanThisClassDodge", (bool(Lua_Mob::*)(void))&Lua_Mob::CanThisClassDodge) + .def("CanThisClassDoubleAttack", (bool(Lua_Mob::*)(void))&Lua_Mob::CanThisClassDoubleAttack) + .def("CanThisClassDualWield", (bool(Lua_Mob::*)(void))&Lua_Mob::CanThisClassDualWield) + .def("CanThisClassParry", (bool(Lua_Mob::*)(void))&Lua_Mob::CanThisClassParry) + .def("CanThisClassRiposte", (bool(Lua_Mob::*)(void))&Lua_Mob::CanThisClassRiposte) + .def("CastSpell", (bool(Lua_Mob::*)(int,int))&Lua_Mob::CastSpell) + .def("CastSpell", (bool(Lua_Mob::*)(int,int,int))&Lua_Mob::CastSpell) + .def("CastSpell", (bool(Lua_Mob::*)(int,int,int,int))&Lua_Mob::CastSpell) + .def("CastSpell", (bool(Lua_Mob::*)(int,int,int,int,int))&Lua_Mob::CastSpell) + .def("CastSpell", (bool(Lua_Mob::*)(int,int,int,int,int,int))&Lua_Mob::CastSpell) + .def("CastSpell", (bool(Lua_Mob::*)(int,int,int,int,int,int,int,int))&Lua_Mob::CastSpell) + .def("CastSpell", (bool(Lua_Mob::*)(int,int,int,int,int,int,int,int,int))&Lua_Mob::CastSpell) + .def("ChangeBeard", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeBeard) + .def("ChangeBeardColor", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeBeardColor) + .def("ChangeDrakkinDetails", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeDrakkinDetails) + .def("ChangeDrakkinHeritage", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeDrakkinHeritage) + .def("ChangeDrakkinTattoo", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeDrakkinTattoo) + .def("ChangeEyeColor1", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeEyeColor1) + .def("ChangeEyeColor2", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeEyeColor2) + .def("ChangeGender", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeGender) + .def("ChangeHairColor", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeHairColor) + .def("ChangeHairStyle", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeHairStyle) + .def("ChangeHelmTexture", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeHelmTexture) + .def("ChangeLuclinFace", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeLuclinFace) + .def("ChangeRace", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeRace) + .def("ChangeSize", (void(Lua_Mob::*)(double))&Lua_Mob::ChangeSize) + .def("ChangeSize", (void(Lua_Mob::*)(double,bool))&Lua_Mob::ChangeSize) + .def("ChangeTexture", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeTexture) + .def("Charmed", (bool(Lua_Mob::*)(void))&Lua_Mob::Charmed) + .def("CheckAggro", (bool(Lua_Mob::*)(Lua_Mob))&Lua_Mob::CheckAggro) + .def("CheckAggroAmount", (int(Lua_Mob::*)(int))&Lua_Mob::CheckAggroAmount) + .def("CheckAggroAmount", (int(Lua_Mob::*)(int,bool))&Lua_Mob::CheckAggroAmount) + .def("CheckHealAggroAmount", (int(Lua_Mob::*)(int))&Lua_Mob::CheckHealAggroAmount) + .def("CheckHealAggroAmount", (int(Lua_Mob::*)(int,uint32))&Lua_Mob::CheckHealAggroAmount) + .def("CheckLoS", (bool(Lua_Mob::*)(Lua_Mob))&Lua_Mob::CheckLoS) + .def("CheckLoSToLoc", (bool(Lua_Mob::*)(double,double,double))&Lua_Mob::CheckLoSToLoc) + .def("CheckLoSToLoc", (bool(Lua_Mob::*)(double,double,double,double))&Lua_Mob::CheckLoSToLoc) + .def("CheckNumHitsRemaining", &Lua_Mob::CheckNumHitsRemaining) + .def("ClearSpecialAbilities", (void(Lua_Mob::*)(void))&Lua_Mob::ClearSpecialAbilities) + .def("CombatRange", (bool(Lua_Mob::*)(Lua_Mob))&Lua_Mob::CombatRange) + .def("Damage", (void(Lua_Mob::*)(Lua_Mob,int,int,int))&Lua_Mob::Damage) + .def("Damage", (void(Lua_Mob::*)(Lua_Mob,int,int,int,bool))&Lua_Mob::Damage) + .def("Damage", (void(Lua_Mob::*)(Lua_Mob,int,int,int,bool,int))&Lua_Mob::Damage) + .def("Damage", (void(Lua_Mob::*)(Lua_Mob,int,int,int,bool,int,bool))&Lua_Mob::Damage) + .def("DelGlobal", (void(Lua_Mob::*)(const char*))&Lua_Mob::DelGlobal) + .def("DeleteBucket", (void(Lua_Mob::*)(std::string))&Lua_Mob::DeleteBucket) + .def("Depop", (void(Lua_Mob::*)(bool))&Lua_Mob::Depop) + .def("Depop", (void(Lua_Mob::*)(void))&Lua_Mob::Depop) + .def("DivineAura", (bool(Lua_Mob::*)(void))&Lua_Mob::DivineAura) + .def("DoAnim", (void(Lua_Mob::*)(int))&Lua_Mob::DoAnim) + .def("DoAnim", (void(Lua_Mob::*)(int,int))&Lua_Mob::DoAnim) + .def("DoAnim", (void(Lua_Mob::*)(int,int,bool))&Lua_Mob::DoAnim) + .def("DoAnim", (void(Lua_Mob::*)(int,int,bool,int))&Lua_Mob::DoAnim) + .def("DoArcheryAttackDmg", (void(Lua_Mob::*)(Lua_Mob))&Lua_Mob::DoArcheryAttackDmg) + .def("DoArcheryAttackDmg", (void(Lua_Mob::*)(Lua_Mob,Lua_ItemInst))&Lua_Mob::DoArcheryAttackDmg) + .def("DoArcheryAttackDmg", (void(Lua_Mob::*)(Lua_Mob,Lua_ItemInst,Lua_ItemInst))&Lua_Mob::DoArcheryAttackDmg) + .def("DoArcheryAttackDmg", (void(Lua_Mob::*)(Lua_Mob,Lua_ItemInst,Lua_ItemInst,int))&Lua_Mob::DoArcheryAttackDmg) + .def("DoArcheryAttackDmg", (void(Lua_Mob::*)(Lua_Mob,Lua_ItemInst,Lua_ItemInst,int,int))&Lua_Mob::DoArcheryAttackDmg) + .def("DoArcheryAttackDmg", (void(Lua_Mob::*)(Lua_Mob,Lua_ItemInst,Lua_ItemInst,int,int,int))&Lua_Mob::DoArcheryAttackDmg) + .def("DoKnockback", (void(Lua_Mob::*)(Lua_Mob,uint32,uint32))&Lua_Mob::DoKnockback) + .def("DoMeleeSkillAttackDmg", (void(Lua_Mob::*)(Lua_Mob,int,int))&Lua_Mob::DoMeleeSkillAttackDmg) + .def("DoMeleeSkillAttackDmg", (void(Lua_Mob::*)(Lua_Mob,int,int,int))&Lua_Mob::DoMeleeSkillAttackDmg) + .def("DoMeleeSkillAttackDmg", (void(Lua_Mob::*)(Lua_Mob,int,int,int,int))&Lua_Mob::DoMeleeSkillAttackDmg) + .def("DoMeleeSkillAttackDmg", (void(Lua_Mob::*)(Lua_Mob,int,int,int,int,bool))&Lua_Mob::DoMeleeSkillAttackDmg) + .def("DoSpecialAttackDamage", (void(Lua_Mob::*)(Lua_Mob,int,int))&Lua_Mob::DoSpecialAttackDamage) + .def("DoSpecialAttackDamage", (void(Lua_Mob::*)(Lua_Mob,int,int,int))&Lua_Mob::DoSpecialAttackDamage) + .def("DoSpecialAttackDamage", (void(Lua_Mob::*)(Lua_Mob,int,int,int,int))&Lua_Mob::DoSpecialAttackDamage) + .def("DoSpecialAttackDamage", (void(Lua_Mob::*)(Lua_Mob,int,int,int,int,int))&Lua_Mob::DoSpecialAttackDamage) + .def("DoThrowingAttackDmg", (void(Lua_Mob::*)(Lua_Mob))&Lua_Mob::DoThrowingAttackDmg) + .def("DoThrowingAttackDmg", (void(Lua_Mob::*)(Lua_Mob,Lua_ItemInst))&Lua_Mob::DoThrowingAttackDmg) + .def("DoThrowingAttackDmg", (void(Lua_Mob::*)(Lua_Mob,Lua_ItemInst,Lua_Item))&Lua_Mob::DoThrowingAttackDmg) + .def("DoThrowingAttackDmg", (void(Lua_Mob::*)(Lua_Mob,Lua_ItemInst,Lua_Item,int))&Lua_Mob::DoThrowingAttackDmg) + .def("DoThrowingAttackDmg", (void(Lua_Mob::*)(Lua_Mob,Lua_ItemInst,Lua_Item,int,int))&Lua_Mob::DoThrowingAttackDmg) + .def("DoThrowingAttackDmg", (void(Lua_Mob::*)(Lua_Mob,Lua_ItemInst,Lua_Item,int,int,int))&Lua_Mob::DoThrowingAttackDmg) + .def("DoubleAggro", &Lua_Mob::DoubleAggro) + .def("Emote", &Lua_Mob::Emote) + .def("EntityVariableExists", (bool(Lua_Mob::*)(const char*))&Lua_Mob::EntityVariableExists) + .def("FaceTarget", (void(Lua_Mob::*)(Lua_Mob))&Lua_Mob::FaceTarget) + .def("FindBuff", &Lua_Mob::FindBuff) + .def("FindBuffBySlot", (uint16(Lua_Mob::*)(int))&Lua_Mob::FindBuffBySlot) + .def("FindGroundZ", (double(Lua_Mob::*)(double,double))&Lua_Mob::FindGroundZ) + .def("FindGroundZ", (double(Lua_Mob::*)(double,double,double))&Lua_Mob::FindGroundZ) + .def("FindType", (bool(Lua_Mob::*)(int))&Lua_Mob::FindType) + .def("FindType", (bool(Lua_Mob::*)(int,bool))&Lua_Mob::FindType) + .def("FindType", (bool(Lua_Mob::*)(int,bool,int))&Lua_Mob::FindType) + .def("GMMove", (void(Lua_Mob::*)(double,double,double))&Lua_Mob::GMMove) + .def("GMMove", (void(Lua_Mob::*)(double,double,double,double))&Lua_Mob::GMMove) + .def("GMMove", (void(Lua_Mob::*)(double,double,double,double,bool))&Lua_Mob::GMMove) + .def("GetAA", (int(Lua_Mob::*)(int))&Lua_Mob::GetAA) + .def("GetAABonuses", &Lua_Mob::GetAABonuses) + .def("GetAAByAAID", (int(Lua_Mob::*)(int))&Lua_Mob::GetAAByAAID) + .def("GetAC", &Lua_Mob::GetAC) + .def("GetAGI", &Lua_Mob::GetAGI) + .def("GetATK", &Lua_Mob::GetATK) + .def("GetAggroRange", (float(Lua_Mob::*)(void))&Lua_Mob::GetAggroRange) + .def("GetAllowBeneficial", (bool(Lua_Mob::*)(void))&Lua_Mob::GetAllowBeneficial) + .def("GetAppearance", (uint32(Lua_Mob::*)(void))&Lua_Mob::GetAppearance) + .def("GetAssistRange", (float(Lua_Mob::*)(void))&Lua_Mob::GetAssistRange) + .def("GetBaseGender", &Lua_Mob::GetBaseGender) + .def("GetBaseRace", &Lua_Mob::GetBaseRace) + .def("GetBaseSize", (double(Lua_Mob::*)(void))&Lua_Mob::GetBaseSize) + .def("GetBeard", &Lua_Mob::GetBeard) + .def("GetBeardColor", &Lua_Mob::GetBeardColor) + .def("GetBodyType", &Lua_Mob::GetBodyType) + .def("GetBucket", (std::string(Lua_Mob::*)(std::string))&Lua_Mob::GetBucket) + .def("GetBucketExpires", (std::string(Lua_Mob::*)(std::string))&Lua_Mob::GetBucketExpires) + .def("GetBucketKey", (std::string(Lua_Mob::*)(void))&Lua_Mob::GetBucketKey) + .def("GetBucketRemaining", (std::string(Lua_Mob::*)(std::string))&Lua_Mob::GetBucketRemaining) + .def("GetBuffSlotFromType", &Lua_Mob::GetBuffSlotFromType) + .def("GetCHA", &Lua_Mob::GetCHA) + .def("GetCR", &Lua_Mob::GetCR) + .def("GetCasterLevel", &Lua_Mob::GetCasterLevel) + .def("GetClass", &Lua_Mob::GetClass) + .def("GetClassName", &Lua_Mob::GetClassName) + .def("GetCleanName", &Lua_Mob::GetCleanName) + .def("GetCorruption", &Lua_Mob::GetCorruption) + .def("GetDEX", &Lua_Mob::GetDEX) + .def("GetDR", &Lua_Mob::GetDR) + .def("GetDamageAmount", (uint32(Lua_Mob::*)(Lua_Mob))&Lua_Mob::GetDamageAmount) + .def("GetDeity", &Lua_Mob::GetDeity) + .def("GetDisplayAC", &Lua_Mob::GetDisplayAC) + .def("GetDrakkinDetails", &Lua_Mob::GetDrakkinDetails) + .def("GetDrakkinHeritage", &Lua_Mob::GetDrakkinHeritage) + .def("GetDrakkinTattoo", &Lua_Mob::GetDrakkinTattoo) + .def("GetEntityVariable", (const char*(Lua_Mob::*)(const char*))&Lua_Mob::GetEntityVariable) + .def("GetEyeColor1", &Lua_Mob::GetEyeColor1) + .def("GetEyeColor2", &Lua_Mob::GetEyeColor2) + .def("GetFR", &Lua_Mob::GetFR) + .def("GetFcDamageAmtIncoming", &Lua_Mob::GetFcDamageAmtIncoming) + .def("GetFlurryChance", (int(Lua_Mob::*)(void))&Lua_Mob::GetFlurryChance) + .def("GetGender", &Lua_Mob::GetGender) + .def("GetGlobal", (std::string(Lua_Mob::*)(const char*))&Lua_Mob::GetGlobal) + .def("GetHP", &Lua_Mob::GetHP) + .def("GetHPRatio", &Lua_Mob::GetHPRatio) + .def("GetHairColor", &Lua_Mob::GetHairColor) + .def("GetHairStyle", &Lua_Mob::GetHairStyle) + .def("GetHandToHandDamage", (int(Lua_Mob::*)(void))&Lua_Mob::GetHandToHandDamage) + .def("GetHandToHandDelay", (int(Lua_Mob::*)(void))&Lua_Mob::GetHandToHandDelay) + .def("GetHaste", (int(Lua_Mob::*)(void))&Lua_Mob::GetHaste) + .def("GetHateAmount", (uint32(Lua_Mob::*)(Lua_Mob))&Lua_Mob::GetHateAmount) + .def("GetHateAmount", (uint32(Lua_Mob::*)(Lua_Mob,bool))&Lua_Mob::GetHateAmount) + .def("GetHateClosest", &Lua_Mob::GetHateClosest) + .def("GetHateDamageTop", (Lua_Mob(Lua_Mob::*)(Lua_Mob))&Lua_Mob::GetHateDamageTop) + .def("GetHateList", &Lua_Mob::GetHateList) + .def("GetHateListByDistance", (Lua_HateList(Lua_Mob::*)(int))&Lua_Mob::GetHateListByDistance) + .def("GetHateListByDistance", (Lua_HateList(Lua_Mob::*)(void))&Lua_Mob::GetHateListByDistance) + .def("GetHateRandom", (Lua_Mob(Lua_Mob::*)(void))&Lua_Mob::GetHateRandom) +#ifdef BOTS + .def("GetHateRandomBot", (Lua_Bot(Lua_Mob::*)(void))&Lua_Mob::GetHateRandomBot) +#endif + .def("GetHateRandomClient", (Lua_Client(Lua_Mob::*)(void))&Lua_Mob::GetHateRandomClient) + .def("GetHateRandomNPC", (Lua_NPC(Lua_Mob::*)(void))&Lua_Mob::GetHateRandomNPC) + .def("GetHateTop", (Lua_Mob(Lua_Mob::*)(void))&Lua_Mob::GetHateTop) + .def("GetHeading", &Lua_Mob::GetHeading) + .def("GetHelmTexture", &Lua_Mob::GetHelmTexture) + .def("GetHerosForgeModel", (int32(Lua_Mob::*)(uint8))&Lua_Mob::GetHerosForgeModel) + .def("GetINT", &Lua_Mob::GetINT) + .def("GetInvul", (bool(Lua_Mob::*)(void))&Lua_Mob::GetInvul) + .def("GetItemBonuses", &Lua_Mob::GetItemBonuses) + .def("GetItemHPBonuses", &Lua_Mob::GetItemHPBonuses) + .def("GetItemStat", (int(Lua_Mob::*)(uint32,const char*))&Lua_Mob::GetItemStat) + .def("GetLastName", &Lua_Mob::GetLastName) + .def("GetLevel", &Lua_Mob::GetLevel) + .def("GetLevelCon", (uint32(Lua_Mob::*)(int))&Lua_Mob::GetLevelCon) + .def("GetLevelCon", (uint32(Lua_Mob::*)(int,int))&Lua_Mob::GetLevelCon) + .def("GetLuclinFace", &Lua_Mob::GetLuclinFace) + .def("GetMR", &Lua_Mob::GetMR) + .def("GetMana", &Lua_Mob::GetMana) + .def("GetManaRatio", &Lua_Mob::GetManaRatio) + .def("GetMaxAGI", &Lua_Mob::GetMaxAGI) + .def("GetMaxCHA", &Lua_Mob::GetMaxCHA) + .def("GetMaxDEX", &Lua_Mob::GetMaxDEX) + .def("GetMaxHP", &Lua_Mob::GetMaxHP) + .def("GetMaxINT", &Lua_Mob::GetMaxINT) + .def("GetMaxMana", &Lua_Mob::GetMaxMana) + .def("GetMaxSTA", &Lua_Mob::GetMaxSTA) + .def("GetMaxSTR", &Lua_Mob::GetMaxSTR) + .def("GetMaxWIS", &Lua_Mob::GetMaxWIS) + .def("GetMeleeDamageMod_SE", &Lua_Mob::GetMeleeDamageMod_SE) + .def("GetMeleeMinDamageMod_SE", &Lua_Mob::GetMeleeMinDamageMod_SE) + .def("GetMeleeMitigation", (int32(Lua_Mob::*)(void))&Lua_Mob::GetMeleeMitigation) + .def("GetModSkillDmgTaken", (int(Lua_Mob::*)(int))&Lua_Mob::GetModSkillDmgTaken) + .def("GetModVulnerability", (int(Lua_Mob::*)(int))&Lua_Mob::GetModVulnerability) + .def("GetNPCTypeID", &Lua_Mob::GetNPCTypeID) + .def("GetName", &Lua_Mob::GetName) + .def("GetNimbusEffect1", (uint8(Lua_Mob::*)(void))&Lua_Mob::GetNimbusEffect1) + .def("GetNimbusEffect2", (uint8(Lua_Mob::*)(void))&Lua_Mob::GetNimbusEffect2) + .def("GetNimbusEffect3", (uint8(Lua_Mob::*)(void))&Lua_Mob::GetNimbusEffect3) + .def("GetOrigBodyType", &Lua_Mob::GetOrigBodyType) + .def("GetOwner", &Lua_Mob::GetOwner) + .def("GetPR", &Lua_Mob::GetPR) + .def("GetPet", &Lua_Mob::GetPet) + .def("GetPetOrder", (int(Lua_Mob::*)(void))&Lua_Mob::GetPetOrder) + .def("GetPhR", &Lua_Mob::GetPhR) + .def("GetRace", &Lua_Mob::GetRace) + .def("GetRaceName", &Lua_Mob::GetRaceName) + .def("GetResist", (int(Lua_Mob::*)(int))&Lua_Mob::GetResist) + .def("GetReverseFactionCon", (int(Lua_Mob::*)(Lua_Mob))&Lua_Mob::GetReverseFactionCon) + .def("GetRunspeed", &Lua_Mob::GetRunspeed) + .def("GetSTA", &Lua_Mob::GetSTA) + .def("GetSTR", &Lua_Mob::GetSTR) + .def("GetShuffledHateList", &Lua_Mob::GetShuffledHateList) + .def("GetSize", &Lua_Mob::GetSize) + .def("GetSkill", (int(Lua_Mob::*)(int))&Lua_Mob::GetSkill) + .def("GetSkillDmgAmt", (int(Lua_Mob::*)(int))&Lua_Mob::GetSkillDmgAmt) + .def("GetSkillDmgTaken", (int(Lua_Mob::*)(int))&Lua_Mob::GetSkillDmgTaken) + .def("GetSpecialAbility", (int(Lua_Mob::*)(int))&Lua_Mob::GetSpecialAbility) + .def("GetSpecialAbilityParam", (int(Lua_Mob::*)(int,int))&Lua_Mob::GetSpecialAbilityParam) + .def("GetSpecializeSkillValue", &Lua_Mob::GetSpecializeSkillValue) + .def("GetSpellBonuses", &Lua_Mob::GetSpellBonuses) + .def("GetSpellHPBonuses", &Lua_Mob::GetSpellHPBonuses) + .def("GetTarget", &Lua_Mob::GetTarget) + .def("GetTexture", &Lua_Mob::GetTexture) + .def("GetWIS", &Lua_Mob::GetWIS) + .def("GetWalkspeed", &Lua_Mob::GetWalkspeed) + .def("GetWaypointH", &Lua_Mob::GetWaypointH) + .def("GetWaypointID", &Lua_Mob::GetWaypointID) + .def("GetWaypointPause", &Lua_Mob::GetWaypointPause) + .def("GetWaypointX", &Lua_Mob::GetWaypointX) + .def("GetWaypointY", &Lua_Mob::GetWaypointY) + .def("GetWaypointZ", &Lua_Mob::GetWaypointZ) + .def("GetWeaponDamage", &Lua_Mob::GetWeaponDamage) + .def("GetWeaponDamageBonus", &Lua_Mob::GetWeaponDamageBonus) + .def("GetX", &Lua_Mob::GetX) + .def("GetY", &Lua_Mob::GetY) + .def("GetZ", &Lua_Mob::GetZ) + .def("GotoBind", &Lua_Mob::GotoBind) + .def("HalveAggro", &Lua_Mob::HalveAggro) + .def("HasNPCSpecialAtk", (bool(Lua_Mob::*)(const char*))&Lua_Mob::HasNPCSpecialAtk) + .def("HasOwner", (bool(Lua_Mob::*)(void))&Lua_Mob::HasOwner) + .def("HasPet", (bool(Lua_Mob::*)(void))&Lua_Mob::HasPet) + .def("HasProcs", &Lua_Mob::HasProcs) + .def("HasShieldEquiped", (bool(Lua_Mob::*)(void))&Lua_Mob::HasShieldEquiped) + .def("HasTwoHandBluntEquiped", (bool(Lua_Mob::*)(void))&Lua_Mob::HasTwoHandBluntEquiped) + .def("HasTwoHanderEquipped", (bool(Lua_Mob::*)(void))&Lua_Mob::HasTwoHanderEquipped) + .def("Heal", &Lua_Mob::Heal) + .def("HealDamage", (void(Lua_Mob::*)(uint32))&Lua_Mob::HealDamage) + .def("HealDamage", (void(Lua_Mob::*)(uint32,Lua_Mob))&Lua_Mob::HealDamage) + .def("InterruptSpell", (void(Lua_Mob::*)(int))&Lua_Mob::InterruptSpell) + .def("InterruptSpell", (void(Lua_Mob::*)(void))&Lua_Mob::InterruptSpell) + .def("IsAIControlled", (bool(Lua_Mob::*)(void))&Lua_Mob::IsAIControlled) + .def("IsAmnesiad", (bool(Lua_Mob::*)(void))&Lua_Mob::IsAmnesiad) + .def("IsAttackAllowed", &Lua_Mob::IsAttackAllowed) + .def("IsBeneficialAllowed", (bool(Lua_Mob::*)(Lua_Mob))&Lua_Mob::IsBeneficialAllowed) + .def("IsBerserk", &Lua_Mob::IsBerserk) + .def("IsBlind", (bool(Lua_Mob::*)(void))&Lua_Mob::IsBlind) + .def("IsCasting", &Lua_Mob::IsCasting) + .def("IsEliteMaterialItem", (uint32(Lua_Mob::*)(uint8))&Lua_Mob::IsEliteMaterialItem) + .def("IsEngaged", (bool(Lua_Mob::*)(void))&Lua_Mob::IsEngaged) + .def("IsEnraged", (bool(Lua_Mob::*)(void))&Lua_Mob::IsEnraged) + .def("IsFeared", (bool(Lua_Mob::*)(void))&Lua_Mob::IsFeared) + .def("IsHorse", &Lua_Mob::IsHorse) + .def("IsImmuneToSpell", (bool(Lua_Mob::*)(int,Lua_Mob))&Lua_Mob::IsImmuneToSpell) + .def("IsInvisible", (bool(Lua_Mob::*)(Lua_Mob))&Lua_Mob::IsInvisible) + .def("IsInvisible", (bool(Lua_Mob::*)(void))&Lua_Mob::IsInvisible) + .def("IsMeleeDisabled", (bool(Lua_Mob::*)(void))&Lua_Mob::IsMeleeDisabled) + .def("IsMezzed", (bool(Lua_Mob::*)(void))&Lua_Mob::IsMezzed) + .def("IsMoving", &Lua_Mob::IsMoving) + .def("IsPet", (bool(Lua_Mob::*)(void))&Lua_Mob::IsPet) + .def("IsRoamer", (bool(Lua_Mob::*)(void))&Lua_Mob::IsRoamer) + .def("IsRooted", (bool(Lua_Mob::*)(void))&Lua_Mob::IsRooted) + .def("IsRunning", (bool(Lua_Mob::*)(void))&Lua_Mob::IsRunning) + .def("IsSilenced", (bool(Lua_Mob::*)(void))&Lua_Mob::IsSilenced) + .def("IsStunned", (bool(Lua_Mob::*)(void))&Lua_Mob::IsStunned) + .def("IsTargetable", (bool(Lua_Mob::*)(void))&Lua_Mob::IsTargetable) + .def("IsTargeted", &Lua_Mob::IsTargeted) + .def("IsWarriorClass", &Lua_Mob::IsWarriorClass) + .def("Kill", (void(Lua_Mob::*)(void))&Lua_Mob::Kill) + .def("Mesmerize", (void(Lua_Mob::*)(void))&Lua_Mob::Mesmerize) + .def("Message", &Lua_Mob::Message) + .def("MessageString", &Lua_Mob::MessageString) + .def("Message_StringID", &Lua_Mob::MessageString) + .def("ModSkillDmgTaken", (void(Lua_Mob::*)(int,int))&Lua_Mob::ModSkillDmgTaken) + .def("ModVulnerability", (void(Lua_Mob::*)(int,int))&Lua_Mob::ModVulnerability) + .def("NPCSpecialAttacks", (void(Lua_Mob::*)(const char*,int))&Lua_Mob::NPCSpecialAttacks) + .def("NPCSpecialAttacks", (void(Lua_Mob::*)(const char*,int,bool))&Lua_Mob::NPCSpecialAttacks) + .def("NPCSpecialAttacks", (void(Lua_Mob::*)(const char*,int,bool,bool))&Lua_Mob::NPCSpecialAttacks) + .def("NavigateTo", (void(Lua_Mob::*)(double,double,double))&Lua_Mob::NavigateTo) + .def("ProcessSpecialAbilities", (void(Lua_Mob::*)(std::string))&Lua_Mob::ProcessSpecialAbilities) + .def("ProjectileAnimation", (void(Lua_Mob::*)(Lua_Mob,int))&Lua_Mob::ProjectileAnimation) + .def("ProjectileAnimation", (void(Lua_Mob::*)(Lua_Mob,int,bool))&Lua_Mob::ProjectileAnimation) + .def("ProjectileAnimation", (void(Lua_Mob::*)(Lua_Mob,int,bool,double))&Lua_Mob::ProjectileAnimation) + .def("ProjectileAnimation", (void(Lua_Mob::*)(Lua_Mob,int,bool,double,double))&Lua_Mob::ProjectileAnimation) + .def("ProjectileAnimation", (void(Lua_Mob::*)(Lua_Mob,int,bool,double,double,double))&Lua_Mob::ProjectileAnimation) + .def("ProjectileAnimation", (void(Lua_Mob::*)(Lua_Mob,int,bool,double,double,double,double))&Lua_Mob::ProjectileAnimation) + .def("QuestSay", (void(Lua_Mob::*)(Lua_Client,const char *))&Lua_Mob::QuestSay) + .def("QuestSay", (void(Lua_Mob::*)(Lua_Client,const char *,luabind::adl::object))&Lua_Mob::QuestSay) + .def("RandomizeFeatures", (void(Lua_Mob::*)(bool,bool))&Lua_Mob::RandomizeFeatures) + .def("RangedAttack", &Lua_Mob::RangedAttack) + .def("RemoveAllNimbusEffects", &Lua_Mob::RemoveAllNimbusEffects) + .def("RemoveNimbusEffect", (void(Lua_Mob::*)(int))&Lua_Mob::RemoveNimbusEffect) + .def("RemovePet", &Lua_Mob::RemovePet) + .def("ResistSpell", (double(Lua_Mob::*)(int,int,Lua_Mob))&Lua_Mob::ResistSpell) + .def("ResistSpell", (double(Lua_Mob::*)(int,int,Lua_Mob,bool))&Lua_Mob::ResistSpell) + .def("ResistSpell", (double(Lua_Mob::*)(int,int,Lua_Mob,bool,int))&Lua_Mob::ResistSpell) + .def("ResistSpell", (double(Lua_Mob::*)(int,int,Lua_Mob,bool,int,bool))&Lua_Mob::ResistSpell) + .def("RunTo", (void(Lua_Mob::*)(double, double, double))&Lua_Mob::RunTo) + .def("Say", (void(Lua_Mob::*)(const char*))& Lua_Mob::Say) + .def("Say", (void(Lua_Mob::*)(const char*, int))& Lua_Mob::Say) + .def("SeeHide", (bool(Lua_Mob::*)(void))&Lua_Mob::SeeHide) + .def("SeeImprovedHide", (bool(Lua_Mob::*)(bool))&Lua_Mob::SeeImprovedHide) + .def("SeeInvisible", (uint8(Lua_Mob::*)(void))&Lua_Mob::SeeInvisible) + .def("SeeInvisibleUndead", (bool(Lua_Mob::*)(void))&Lua_Mob::SeeInvisibleUndead) + .def("SendAppearanceEffect", (void(Lua_Mob::*)(uint32,uint32,uint32,uint32,uint32))&Lua_Mob::SendAppearanceEffect) + .def("SendAppearanceEffect", (void(Lua_Mob::*)(uint32,uint32,uint32,uint32,uint32,Lua_Client))&Lua_Mob::SendAppearanceEffect) + .def("SendBeginCast", &Lua_Mob::SendBeginCast) + .def("SendIllusionPacket", (void(Lua_Mob::*)(luabind::adl::object))&Lua_Mob::SendIllusionPacket) + .def("SendSpellEffect", (void(Lua_Mob::*)(uint32,uint32,uint32,bool,uint32))&Lua_Mob::SendSpellEffect) + .def("SendSpellEffect", (void(Lua_Mob::*)(uint32,uint32,uint32,bool,uint32,bool))&Lua_Mob::SendSpellEffect) + .def("SendSpellEffect", (void(Lua_Mob::*)(uint32,uint32,uint32,bool,uint32,bool,Lua_Client))&Lua_Mob::SendSpellEffect) + .def("SendTo", (void(Lua_Mob::*)(double,double,double))&Lua_Mob::SendTo) + .def("SendToFixZ", (void(Lua_Mob::*)(double,double,double))&Lua_Mob::SendToFixZ) + .def("SetAA", (bool(Lua_Mob::*)(int,int))&Lua_Mob::SetAA) + .def("SetAA", (bool(Lua_Mob::*)(int,int,int))&Lua_Mob::SetAA) + .def("SetAllowBeneficial", (void(Lua_Mob::*)(bool))&Lua_Mob::SetAllowBeneficial) + .def("SetAppearance", (void(Lua_Mob::*)(int))&Lua_Mob::SetAppearance) + .def("SetAppearance", (void(Lua_Mob::*)(int,bool))&Lua_Mob::SetAppearance) + .def("SetBodyType", (void(Lua_Mob::*)(int,bool))&Lua_Mob::SetBodyType) + .def("SetBucket", (void(Lua_Mob::*)(std::string,std::string))&Lua_Mob::SetBucket) + .def("SetBucket", (void(Lua_Mob::*)(std::string,std::string,std::string))&Lua_Mob::SetBucket) + .def("SetCurrentWP", &Lua_Mob::SetCurrentWP) + .def("SetDestructibleObject", (void(Lua_Mob::*)(bool))&Lua_Mob::SetDestructibleObject) + .def("SetDisableMelee", (void(Lua_Mob::*)(bool))&Lua_Mob::SetDisableMelee) + .def("SetEntityVariable", (void(Lua_Mob::*)(const char*,const char*))&Lua_Mob::SetEntityVariable) + .def("SetExtraHaste", (void(Lua_Mob::*)(int))&Lua_Mob::SetExtraHaste) + .def("SetFlurryChance", (void(Lua_Mob::*)(int))&Lua_Mob::SetFlurryChance) + .def("SetFlyMode", (void(Lua_Mob::*)(int))&Lua_Mob::SetFlyMode) + .def("SetGender", (void(Lua_Mob::*)(int))&Lua_Mob::SetGender) + .def("SetGlobal", (void(Lua_Mob::*)(const char*,const char*,int,const char*))&Lua_Mob::SetGlobal) + .def("SetGlobal", (void(Lua_Mob::*)(const char*,const char*,int,const char*,Lua_Mob))&Lua_Mob::SetGlobal) + .def("SetHP", &Lua_Mob::SetHP) + .def("SetHate", (void(Lua_Mob::*)(Lua_Mob))&Lua_Mob::SetHate) + .def("SetHate", (void(Lua_Mob::*)(Lua_Mob,int))&Lua_Mob::SetHate) + .def("SetHate", (void(Lua_Mob::*)(Lua_Mob,int,int))&Lua_Mob::SetHate) + .def("SetHeading", (void(Lua_Mob::*)(double))&Lua_Mob::SetHeading) + .def("SetInvisible", &Lua_Mob::SetInvisible) + .def("SetInvul", (void(Lua_Mob::*)(bool))&Lua_Mob::SetInvul) + .def("SetLevel", (void(Lua_Mob::*)(int))&Lua_Mob::SetLevel) + .def("SetLevel", (void(Lua_Mob::*)(int,bool))&Lua_Mob::SetLevel) + .def("SetMana", &Lua_Mob::SetMana) + .def("SetOOCRegen", (void(Lua_Mob::*)(int))&Lua_Mob::SetOOCRegen) + .def("SetPet", &Lua_Mob::SetPet) + .def("SetPetOrder", (void(Lua_Mob::*)(int))&Lua_Mob::SetPetOrder) + .def("SetPseudoRoot", (void(Lua_Mob::*)(bool))&Lua_Mob::SetPseudoRoot) + .def("SetRace", (void(Lua_Mob::*)(int))&Lua_Mob::SetRace) + .def("SetRunning", (void(Lua_Mob::*)(bool))&Lua_Mob::SetRunning) + .def("SetSlotTint", (void(Lua_Mob::*)(int,int,int,int))&Lua_Mob::SetSlotTint) + .def("SetSpecialAbility", (void(Lua_Mob::*)(int,int))&Lua_Mob::SetSpecialAbility) + .def("SetSpecialAbilityParam", (void(Lua_Mob::*)(int,int,int))&Lua_Mob::SetSpecialAbilityParam) + .def("SetTarget", &Lua_Mob::SetTarget) + .def("SetTargetable", (void(Lua_Mob::*)(bool))&Lua_Mob::SetTargetable) + .def("SetTexture", (void(Lua_Mob::*)(int))&Lua_Mob::SetTexture) + .def("Shout", (void(Lua_Mob::*)(const char*))& Lua_Mob::Shout) + .def("Shout", (void(Lua_Mob::*)(const char*, int))& Lua_Mob::Shout) + .def("Signal", (void(Lua_Mob::*)(uint32))&Lua_Mob::Signal) + .def("SpellEffect", &Lua_Mob::SpellEffect) + .def("SpellFinished", (bool(Lua_Mob::*)(int,Lua_Mob))&Lua_Mob::SpellFinished) + .def("SpellFinished", (bool(Lua_Mob::*)(int,Lua_Mob,int))&Lua_Mob::SpellFinished) + .def("SpellFinished", (bool(Lua_Mob::*)(int,Lua_Mob,int,int))&Lua_Mob::SpellFinished) + .def("SpellFinished", (bool(Lua_Mob::*)(int,Lua_Mob,int,int,uint32))&Lua_Mob::SpellFinished) + .def("SpellFinished", (bool(Lua_Mob::*)(int,Lua_Mob,int,int,uint32,int))&Lua_Mob::SpellFinished) + .def("SpellFinished", (bool(Lua_Mob::*)(int,Lua_Mob,int,int,uint32,int,bool))&Lua_Mob::SpellFinished) + .def("Spin", (void(Lua_Mob::*)(void))&Lua_Mob::Spin) + .def("StopNavigation", (void(Lua_Mob::*)(void))&Lua_Mob::StopNavigation) + .def("Stun", (void(Lua_Mob::*)(int))&Lua_Mob::Stun) + .def("TarGlobal", (void(Lua_Mob::*)(const char*,const char*,const char*,int,int,int))&Lua_Mob::TarGlobal) + .def("TempName", (void(Lua_Mob::*)(const char*))&Lua_Mob::TempName) + .def("TempName", (void(Lua_Mob::*)(void))&Lua_Mob::TempName) + .def("ThrowingAttack", &Lua_Mob::ThrowingAttack) + .def("TryFinishingBlow", &Lua_Mob::TryFinishingBlow) + .def("TryMoveAlong", (void(Lua_Mob::*)(float,float))&Lua_Mob::TryMoveAlong) + .def("TryMoveAlong", (void(Lua_Mob::*)(float,float,bool))&Lua_Mob::TryMoveAlong) + .def("UnStun", (void(Lua_Mob::*)(void))&Lua_Mob::UnStun) + .def("WalkTo", (void(Lua_Mob::*)(double, double, double))&Lua_Mob::WalkTo) + .def("WearChange", (void(Lua_Mob::*)(int,int,uint32))&Lua_Mob::WearChange) + .def("WipeHateList", (void(Lua_Mob::*)(void))&Lua_Mob::WipeHateList); } luabind::scope lua_register_special_abilities() { return luabind::class_("SpecialAbility") + .enum_("constants") [ luabind::value("summon", static_cast(SPECATK_SUMMON)), @@ -2798,7 +2892,8 @@ luabind::scope lua_register_special_abilities() { luabind::value("immune_damage_client", static_cast(IMMUNE_DAMAGE_CLIENT)), luabind::value("immune_damage_npc", static_cast(IMMUNE_DAMAGE_NPC)), luabind::value("immune_aggro_client", static_cast(IMMUNE_AGGRO_CLIENT)), - luabind::value("immune_aggro_npc", static_cast(IMMUNE_AGGRO_NPC)) + luabind::value("immune_aggro_npc", static_cast(IMMUNE_AGGRO_NPC)), + luabind::value("modify_avoid_damage", static_cast(MODIFY_AVOID_DAMAGE)) ]; } diff --git a/zone/lua_mob.h b/zone/lua_mob.h index 4b090765c..f6b678d36 100644 --- a/zone/lua_mob.h +++ b/zone/lua_mob.h @@ -9,6 +9,11 @@ struct Lua_HateList; class Lua_Item; class Lua_ItemInst; class Lua_StatBonuses; +#ifdef BOTS +class Lua_Bot; +#endif +class Lua_NPC; +class Lua_Client; namespace luabind { struct scope; @@ -110,6 +115,7 @@ public: int GetClass(); int GetLevel(); const char *GetCleanName(); + const char *GetLastName(); Lua_Mob GetTarget(); void SetTarget(Lua_Mob t); double GetHPRatio(); @@ -198,11 +204,17 @@ public: Lua_Mob GetPet(); Lua_Mob GetOwner(); Lua_HateList GetHateList(); + Lua_HateList GetShuffledHateList(); Lua_HateList GetHateListByDistance(); Lua_HateList GetHateListByDistance(int distance); Lua_Mob GetHateTop(); Lua_Mob GetHateDamageTop(Lua_Mob other); Lua_Mob GetHateRandom(); +#ifdef BOTS + Lua_Bot GetHateRandomBot(); +#endif + Lua_Client GetHateRandomClient(); + Lua_NPC GetHateRandomNPC(); Lua_Mob GetHateClosest(); void AddToHateList(Lua_Mob other); void AddToHateList(Lua_Mob other, int hate); @@ -350,9 +362,10 @@ public: void DelGlobal(const char *varname); void SetSlotTint(int material_slot, int red_tint, int green_tint, int blue_tint); void WearChange(int material_slot, int texture, uint32 color); - void DoKnockback(Lua_Mob caster, uint32 pushback, uint32 pushup); + void DoKnockback(Lua_Mob caster, uint32 push_back, uint32 push_up); void AddNimbusEffect(int effect_id); void RemoveNimbusEffect(int effect_id); + void RemoveAllNimbusEffects(); bool IsRunning(); void SetRunning(bool running); void SetBodyType(int new_body, bool overwrite_orig); @@ -360,7 +373,7 @@ public: void ModSkillDmgTaken(int skill, int value); int GetModSkillDmgTaken(int skill); int GetSkillDmgTaken(int skill); - int GetFcDamageAmtIncoming(Lua_Mob caster, uint32 spell_id, bool use_skill, uint16 skill); + int GetFcDamageAmtIncoming(Lua_Mob caster, int32 spell_id); int GetSkillDmgAmt(uint16 skill); void SetAllowBeneficial(bool value); bool GetAllowBeneficial(); @@ -409,6 +422,8 @@ public: bool HasOwner(); bool IsPet(); bool HasPet(); + void RemovePet(); + void SetPet(Lua_Mob new_pet); bool IsSilenced(); bool IsAmnesiad(); int32 GetMeleeMitigation(); @@ -428,7 +443,6 @@ public: int GetBodyType(); int GetOrigBodyType(); void CheckNumHitsRemaining(int type, int32 buff_slot, uint16 spell_id); - void DeleteBucket(std::string bucket_name); std::string GetBucket(std::string bucket_name); std::string GetBucketExpires(std::string bucket_name); @@ -437,6 +451,8 @@ public: void SetBucket(std::string bucket_name, std::string bucket_value); void SetBucket(std::string bucket_name, std::string bucket_value, std::string expiration); bool IsHorse(); + bool CanClassEquipItem(uint32 item_id); + bool CanRaceEquipItem(uint32 item_id); }; #endif diff --git a/zone/lua_npc.cpp b/zone/lua_npc.cpp index 32d268fb5..d82f936e0 100644 --- a/zone/lua_npc.cpp +++ b/zone/lua_npc.cpp @@ -2,11 +2,16 @@ #include "lua.hpp" #include +#include #include "npc.h" #include "lua_npc.h" #include "lua_client.h" +struct Lua_NPC_Loot_List { + std::vector entries; +}; + void Lua_NPC::Signal(int id) { Lua_Safe_Call_Void(); self->SignalNPC(id); @@ -262,9 +267,9 @@ int Lua_NPC::GetMaxWp() { return self->GetMaxWp(); } -void Lua_NPC::DisplayWaypointInfo(Lua_Client to) { +void Lua_NPC::DisplayWaypointInfo(Lua_Client client) { Lua_Safe_Call_Void(); - self->DisplayWaypointInfo(to); + self->DisplayWaypointInfo(client); } void Lua_NPC::CalculateNewWaypoint() { @@ -564,120 +569,222 @@ void Lua_NPC::ScaleNPC(uint8 npc_level) self->ScaleNPC(npc_level); } +bool Lua_NPC::IsRaidTarget() +{ + Lua_Safe_Call_Bool(); + return self->IsRaidTarget(); +} + +void Lua_NPC::ChangeLastName(const char *lastname) +{ + Lua_Safe_Call_Void(); + self->ChangeLastName(lastname); +} + +void Lua_NPC::ClearLastName() +{ + Lua_Safe_Call_Void(); + self->ClearLastName(); +} + +bool Lua_NPC::HasItem(uint32 item_id) +{ + Lua_Safe_Call_Bool(); + return self->HasItem(item_id); +} + +uint16 Lua_NPC::CountItem(uint32 item_id) +{ + Lua_Safe_Call_Int(); + return self->CountItem(item_id); +} + +uint32 Lua_NPC::GetItemIDBySlot(uint16 loot_slot) +{ + Lua_Safe_Call_Int(); + return self->GetItemIDBySlot(loot_slot); +} + +uint16 Lua_NPC::GetFirstSlotByItemID(uint32 item_id) +{ + Lua_Safe_Call_Int(); + return self->GetFirstSlotByItemID(item_id); +} + +float Lua_NPC::GetHealScale() +{ + Lua_Safe_Call_Real(); + return self->GetHealScale(); +} + +float Lua_NPC::GetSpellScale() +{ + Lua_Safe_Call_Real(); + return self->GetSpellScale(); +} + +Lua_NPC_Loot_List Lua_NPC::GetLootList(lua_State* L) { + Lua_Safe_Call_Class(Lua_NPC_Loot_List); + Lua_NPC_Loot_List ret; + auto loot_list = self->GetLootList(); + + for (auto item_id : loot_list) { + ret.entries.push_back(item_id); + } + + return ret; +} + +void Lua_NPC::AddAISpellEffect(int spell_effect_id, int base_value, int limit_value, int max_value) +{ + Lua_Safe_Call_Void(); + self->AddSpellEffectToNPCList(spell_effect_id, base_value, limit_value, max_value, true); +} + +void Lua_NPC::RemoveAISpellEffect(int spell_effect_id) +{ + Lua_Safe_Call_Void(); + self->RemoveSpellEffectFromNPCList(spell_effect_id, true); +} + +bool Lua_NPC::HasAISpellEffect(int spell_effect_id) +{ + Lua_Safe_Call_Bool(); + return self->HasAISpellEffect(spell_effect_id); +} + luabind::scope lua_register_npc() { - return luabind::class_("NPC") - .def(luabind::constructor<>()) - .def("Signal", (void(Lua_NPC::*)(int))&Lua_NPC::Signal) - .def("CheckNPCFactionAlly", (int(Lua_NPC::*)(int))&Lua_NPC::CheckNPCFactionAlly) - .def("AddItem", (void(Lua_NPC::*)(int,int))&Lua_NPC::AddItem) - .def("AddItem", (void(Lua_NPC::*)(int,int,bool))&Lua_NPC::AddItem) - .def("AddItem", (void(Lua_NPC::*)(int,int,bool,int))&Lua_NPC::AddItem) - .def("AddItem", (void(Lua_NPC::*)(int,int,bool,int,int))&Lua_NPC::AddItem) - .def("AddItem", (void(Lua_NPC::*)(int,int,bool,int,int,int))&Lua_NPC::AddItem) - .def("AddItem", (void(Lua_NPC::*)(int,int,bool,int,int,int,int))&Lua_NPC::AddItem) - .def("AddItem", (void(Lua_NPC::*)(int,int,bool,int,int,int,int,int))&Lua_NPC::AddItem) - .def("AddItem", (void(Lua_NPC::*)(int,int,bool,int,int,int,int,int,int))&Lua_NPC::AddItem) - .def("AddLootTable", (void(Lua_NPC::*)(void))&Lua_NPC::AddLootTable) - .def("AddLootTable", (void(Lua_NPC::*)(int))&Lua_NPC::AddLootTable) - .def("RemoveItem", (void(Lua_NPC::*)(int))&Lua_NPC::RemoveItem) - .def("RemoveItem", (void(Lua_NPC::*)(int,int))&Lua_NPC::RemoveItem) - .def("RemoveItem", (void(Lua_NPC::*)(int,int,int))&Lua_NPC::RemoveItem) - .def("ClearItemList", (void(Lua_NPC::*)(void))&Lua_NPC::ClearItemList) - .def("AddCash", (void(Lua_NPC::*)(int,int,int,int))&Lua_NPC::AddCash) - .def("RemoveCash", (void(Lua_NPC::*)(void))&Lua_NPC::RemoveCash) - .def("CountLoot", (int(Lua_NPC::*)(void))&Lua_NPC::CountLoot) - .def("GetLoottableID", (int(Lua_NPC::*)(void))&Lua_NPC::GetLoottableID) - .def("GetCopper", (uint32(Lua_NPC::*)(void))&Lua_NPC::GetCopper) - .def("GetSilver", (uint32(Lua_NPC::*)(void))&Lua_NPC::GetSilver) - .def("GetGold", (uint32(Lua_NPC::*)(void))&Lua_NPC::GetGold) - .def("GetPlatinum", (uint32(Lua_NPC::*)(void))&Lua_NPC::GetPlatinum) - .def("SetCopper", (void(Lua_NPC::*)(uint32))&Lua_NPC::SetCopper) - .def("SetSilver", (void(Lua_NPC::*)(uint32))&Lua_NPC::SetSilver) - .def("SetGold", (void(Lua_NPC::*)(uint32))&Lua_NPC::SetGold) - .def("SetPlatinum", (void(Lua_NPC::*)(uint32))&Lua_NPC::SetPlatinum) - .def("SetGrid", (void(Lua_NPC::*)(int))&Lua_NPC::SetGrid) - .def("SetSaveWaypoint", (void(Lua_NPC::*)(int))&Lua_NPC::SetSaveWaypoint) - .def("SetSp2", (void(Lua_NPC::*)(int))&Lua_NPC::SetSp2) - .def("GetWaypointMax", (int(Lua_NPC::*)(void))&Lua_NPC::GetWaypointMax) - .def("GetGrid", (int(Lua_NPC::*)(void))&Lua_NPC::GetGrid) - .def("GetSp2", (uint32(Lua_NPC::*)(void))&Lua_NPC::GetSp2) - .def("GetNPCFactionID", (int(Lua_NPC::*)(void))&Lua_NPC::GetNPCFactionID) - .def("GetPrimaryFaction", (int(Lua_NPC::*)(void))&Lua_NPC::GetPrimaryFaction) - .def("GetNPCHate", (int(Lua_NPC::*)(Lua_Mob))&Lua_NPC::GetNPCHate) - .def("IsOnHatelist", (bool(Lua_NPC::*)(Lua_Mob))&Lua_NPC::IsOnHatelist) - .def("SetNPCFactionID", (void(Lua_NPC::*)(int))&Lua_NPC::SetNPCFactionID) - .def("GetMaxDMG", (uint32(Lua_NPC::*)(void))&Lua_NPC::GetMaxDMG) - .def("GetMinDMG", (uint32(Lua_NPC::*)(void))&Lua_NPC::GetMinDMG) - .def("IsAnimal", (bool(Lua_NPC::*)(void))&Lua_NPC::IsAnimal) - .def("GetPetSpellID", (int(Lua_NPC::*)(void))&Lua_NPC::GetPetSpellID) - .def("SetPetSpellID", (void(Lua_NPC::*)(int))&Lua_NPC::SetPetSpellID) - .def("GetMaxDamage", (uint32(Lua_NPC::*)(int))&Lua_NPC::GetMaxDamage) - .def("SetTaunting", (void(Lua_NPC::*)(bool))&Lua_NPC::SetTaunting) - .def("IsTaunting", (bool(Lua_NPC::*)(void))&Lua_NPC::IsTaunting) - .def("PickPocket", (void(Lua_NPC::*)(Lua_Client))&Lua_NPC::PickPocket) - .def("StartSwarmTimer", (void(Lua_NPC::*)(uint32))&Lua_NPC::StartSwarmTimer) - .def("DoClassAttacks", (void(Lua_NPC::*)(Lua_Mob))&Lua_NPC::DoClassAttacks) - .def("GetMaxWp", (int(Lua_NPC::*)(void))&Lua_NPC::GetMaxWp) - .def("DisplayWaypointInfo", (void(Lua_NPC::*)(Lua_Client))&Lua_NPC::DisplayWaypointInfo) - .def("CalculateNewWaypoint", (void(Lua_NPC::*)(void))&Lua_NPC::CalculateNewWaypoint) - .def("AssignWaypoints", (void(Lua_NPC::*)(int))&Lua_NPC::AssignWaypoints) - .def("SetWaypointPause", (void(Lua_NPC::*)(void))&Lua_NPC::SetWaypointPause) - .def("UpdateWaypoint", (void(Lua_NPC::*)(int))&Lua_NPC::UpdateWaypoint) - .def("StopWandering", (void(Lua_NPC::*)(void))&Lua_NPC::StopWandering) - .def("ResumeWandering", (void(Lua_NPC::*)(void))&Lua_NPC::ResumeWandering) - .def("PauseWandering", (void(Lua_NPC::*)(int))&Lua_NPC::PauseWandering) - .def("MoveTo", (void(Lua_NPC::*)(float,float,float,float,bool))&Lua_NPC::MoveTo) - .def("NextGuardPosition", (void(Lua_NPC::*)(void))&Lua_NPC::NextGuardPosition) - .def("SaveGuardSpot", (void(Lua_NPC::*)(float,float,float,float))&Lua_NPC::SaveGuardSpot) - .def("IsGuarding", (bool(Lua_NPC::*)(void))&Lua_NPC::IsGuarding) - .def("AI_SetRoambox", (void(Lua_NPC::*)(float,float,float,float,float))&Lua_NPC::AI_SetRoambox) - .def("AI_SetRoambox", (void(Lua_NPC::*)(float,float,float,float,float,uint32,uint32))&Lua_NPC::AI_SetRoambox) - .def("SetFollowID", (void(Lua_NPC::*)(int))&Lua_NPC::SetFollowID) - .def("SetFollowDistance", (void(Lua_NPC::*)(int))&Lua_NPC::SetFollowDistance) - .def("SetFollowCanRun", (void(Lua_NPC::*)(bool))&Lua_NPC::SetFollowCanRun) - .def("GetFollowID", (int(Lua_NPC::*)(void))&Lua_NPC::GetFollowID) - .def("GetFollowDistance", (int(Lua_NPC::*)(void))&Lua_NPC::GetFollowDistance) - .def("GetFollowCanRun", (bool(Lua_NPC::*)(void))&Lua_NPC::GetFollowCanRun) - .def("GetNPCSpellsID", (int(Lua_NPC::*)(void))&Lua_NPC::GetNPCSpellsID) - .def("GetNPCSpellsID", (int(Lua_NPC::*)(void))&Lua_NPC::GetNPCSpellsID) - .def("GetSpawnPointID", (int(Lua_NPC::*)(void))&Lua_NPC::GetSpawnPointID) - .def("GetSpawnPointX", (float(Lua_NPC::*)(void))&Lua_NPC::GetSpawnPointX) - .def("GetSpawnPointY", (float(Lua_NPC::*)(void))&Lua_NPC::GetSpawnPointY) - .def("GetSpawnPointZ", (float(Lua_NPC::*)(void))&Lua_NPC::GetSpawnPointZ) - .def("GetSpawnPointH", (float(Lua_NPC::*)(void))&Lua_NPC::GetSpawnPointH) - .def("GetGuardPointX", (float(Lua_NPC::*)(void))&Lua_NPC::GetGuardPointX) - .def("GetGuardPointY", (float(Lua_NPC::*)(void))&Lua_NPC::GetGuardPointY) - .def("GetGuardPointZ", (float(Lua_NPC::*)(void))&Lua_NPC::GetGuardPointZ) - .def("SetPrimSkill", (void(Lua_NPC::*)(int))&Lua_NPC::SetPrimSkill) - .def("SetSecSkill", (void(Lua_NPC::*)(int))&Lua_NPC::SetSecSkill) - .def("SetSimpleRoamBox", (void(Lua_NPC::*)(float))&Lua_NPC::SetSimpleRoamBox) - .def("SetSimpleRoamBox", (void(Lua_NPC::*)(float, float))&Lua_NPC::SetSimpleRoamBox) - .def("SetSimpleRoamBox", (void(Lua_NPC::*)(float, float, int))&Lua_NPC::SetSimpleRoamBox) - .def("GetPrimSkill", (int(Lua_NPC::*)(void))&Lua_NPC::GetPrimSkill) - .def("GetSecSkill", (int(Lua_NPC::*)(void))&Lua_NPC::GetSecSkill) - .def("GetSwarmOwner", (int(Lua_NPC::*)(void))&Lua_NPC::GetSwarmOwner) - .def("GetSwarmTarget", (int(Lua_NPC::*)(void))&Lua_NPC::GetSwarmTarget) - .def("SetSwarmTarget", (void(Lua_NPC::*)(int))&Lua_NPC::SetSwarmTarget) - .def("ModifyNPCStat", (void(Lua_NPC::*)(const char*,const char*))&Lua_NPC::ModifyNPCStat) - .def("AddAISpell", (void(Lua_NPC::*)(int,int,int,int,int,int))&Lua_NPC::AddAISpell) - .def("AddAISpell", (void(Lua_NPC::*)(int,int,int,int,int,int,int,int))&Lua_NPC::AddAISpell) - .def("RemoveAISpell", (void(Lua_NPC::*)(int))&Lua_NPC::RemoveAISpell) - .def("SetSpellFocusDMG", (void(Lua_NPC::*)(int))&Lua_NPC::SetSpellFocusDMG) - .def("SetSpellFocusHeal", (void(Lua_NPC::*)(int))&Lua_NPC::SetSpellFocusHeal) - .def("GetSpellFocusDMG", (void(Lua_NPC::*)(int))&Lua_NPC::GetSpellFocusDMG) - .def("GetSpellFocusHeal", (void(Lua_NPC::*)(int))&Lua_NPC::GetSpellFocusHeal) - .def("GetSlowMitigation", (int(Lua_NPC::*)(void))&Lua_NPC::GetSlowMitigation) - .def("GetAttackSpeed", (float(Lua_NPC::*)(void))&Lua_NPC::GetAttackSpeed) - .def("GetAttackDelay", (int(Lua_NPC::*)(void))&Lua_NPC::GetAttackDelay) - .def("GetAccuracyRating", (int(Lua_NPC::*)(void))&Lua_NPC::GetAccuracyRating) - .def("GetSpawnKillCount", (int(Lua_NPC::*)(void))&Lua_NPC::GetSpawnKillCount) - .def("GetScore", (int(Lua_NPC::*)(void))&Lua_NPC::GetScore) - .def("MerchantOpenShop", (void(Lua_NPC::*)(void))&Lua_NPC::MerchantOpenShop) - .def("MerchantCloseShop", (void(Lua_NPC::*)(void))&Lua_NPC::MerchantCloseShop) - .def("GetRawAC", (int(Lua_NPC::*)(void))&Lua_NPC::GetRawAC) - .def("GetAvoidanceRating", &Lua_NPC::GetAvoidanceRating) - .def("RecalculateSkills", (void(Lua_NPC::*)(void))&Lua_NPC::RecalculateSkills) - .def("ScaleNPC", (void(Lua_NPC::*)(uint8))&Lua_NPC::ScaleNPC); + return luabind::class_("NPC") + .def(luabind::constructor<>()) + .def("AI_SetRoambox", (void(Lua_NPC::*)(float,float,float,float,float))&Lua_NPC::AI_SetRoambox) + .def("AI_SetRoambox", (void(Lua_NPC::*)(float,float,float,float,float,uint32,uint32))&Lua_NPC::AI_SetRoambox) + .def("AddAISpell", (void(Lua_NPC::*)(int,int,int,int,int,int))&Lua_NPC::AddAISpell) + .def("AddAISpell", (void(Lua_NPC::*)(int,int,int,int,int,int,int,int))&Lua_NPC::AddAISpell) + .def("AddAISpellEffect", (void(Lua_NPC::*)(int,int,int,int))&Lua_NPC::AddAISpellEffect) + .def("AddCash", (void(Lua_NPC::*)(int,int,int,int))&Lua_NPC::AddCash) + .def("AddItem", (void(Lua_NPC::*)(int,int))&Lua_NPC::AddItem) + .def("AddItem", (void(Lua_NPC::*)(int,int,bool))&Lua_NPC::AddItem) + .def("AddItem", (void(Lua_NPC::*)(int,int,bool,int))&Lua_NPC::AddItem) + .def("AddItem", (void(Lua_NPC::*)(int,int,bool,int,int))&Lua_NPC::AddItem) + .def("AddItem", (void(Lua_NPC::*)(int,int,bool,int,int,int))&Lua_NPC::AddItem) + .def("AddItem", (void(Lua_NPC::*)(int,int,bool,int,int,int,int))&Lua_NPC::AddItem) + .def("AddItem", (void(Lua_NPC::*)(int,int,bool,int,int,int,int,int))&Lua_NPC::AddItem) + .def("AddItem", (void(Lua_NPC::*)(int,int,bool,int,int,int,int,int,int))&Lua_NPC::AddItem) + .def("AddLootTable", (void(Lua_NPC::*)(int))&Lua_NPC::AddLootTable) + .def("AddLootTable", (void(Lua_NPC::*)(void))&Lua_NPC::AddLootTable) + .def("AssignWaypoints", (void(Lua_NPC::*)(int))&Lua_NPC::AssignWaypoints) + .def("CalculateNewWaypoint", (void(Lua_NPC::*)(void))&Lua_NPC::CalculateNewWaypoint) + .def("ChangeLastName", (void(Lua_NPC::*)(const char*))&Lua_NPC::ChangeLastName) + .def("CheckNPCFactionAlly", (int(Lua_NPC::*)(int))&Lua_NPC::CheckNPCFactionAlly) + .def("ClearItemList", (void(Lua_NPC::*)(void))&Lua_NPC::ClearItemList) + .def("ClearLastName", (void(Lua_NPC::*)(void))&Lua_NPC::ClearLastName) + .def("CountItem", (uint16(Lua_NPC::*)(uint32))&Lua_NPC::CountItem) + .def("CountLoot", (int(Lua_NPC::*)(void))&Lua_NPC::CountLoot) + .def("DisplayWaypointInfo", (void(Lua_NPC::*)(Lua_Client))&Lua_NPC::DisplayWaypointInfo) + .def("DoClassAttacks", (void(Lua_NPC::*)(Lua_Mob))&Lua_NPC::DoClassAttacks) + .def("GetAccuracyRating", (int(Lua_NPC::*)(void))&Lua_NPC::GetAccuracyRating) + .def("GetAttackDelay", (int(Lua_NPC::*)(void))&Lua_NPC::GetAttackDelay) + .def("GetAttackSpeed", (float(Lua_NPC::*)(void))&Lua_NPC::GetAttackSpeed) + .def("GetAvoidanceRating", &Lua_NPC::GetAvoidanceRating) + .def("GetCopper", (uint32(Lua_NPC::*)(void))&Lua_NPC::GetCopper) + .def("GetFirstSlotByItemID", (uint16(Lua_NPC::*)(uint32))&Lua_NPC::GetFirstSlotByItemID) + .def("GetFollowCanRun", (bool(Lua_NPC::*)(void))&Lua_NPC::GetFollowCanRun) + .def("GetFollowDistance", (int(Lua_NPC::*)(void))&Lua_NPC::GetFollowDistance) + .def("GetFollowID", (int(Lua_NPC::*)(void))&Lua_NPC::GetFollowID) + .def("GetGold", (uint32(Lua_NPC::*)(void))&Lua_NPC::GetGold) + .def("GetGrid", (int(Lua_NPC::*)(void))&Lua_NPC::GetGrid) + .def("GetGuardPointX", (float(Lua_NPC::*)(void))&Lua_NPC::GetGuardPointX) + .def("GetGuardPointY", (float(Lua_NPC::*)(void))&Lua_NPC::GetGuardPointY) + .def("GetGuardPointZ", (float(Lua_NPC::*)(void))&Lua_NPC::GetGuardPointZ) + .def("GetHealScale", (float(Lua_NPC::*)(void))&Lua_NPC::GetHealScale) + .def("GetItemIDBySlot", (uint32(Lua_NPC::*)(uint16))&Lua_NPC::GetItemIDBySlot) + .def("GetLootList", (Lua_NPC_Loot_List(Lua_NPC::*)(lua_State* L))&Lua_NPC::GetLootList) + .def("GetLoottableID", (int(Lua_NPC::*)(void))&Lua_NPC::GetLoottableID) + .def("GetMaxDMG", (uint32(Lua_NPC::*)(void))&Lua_NPC::GetMaxDMG) + .def("GetMaxDamage", (uint32(Lua_NPC::*)(int))&Lua_NPC::GetMaxDamage) + .def("GetMaxWp", (int(Lua_NPC::*)(void))&Lua_NPC::GetMaxWp) + .def("GetMinDMG", (uint32(Lua_NPC::*)(void))&Lua_NPC::GetMinDMG) + .def("GetNPCFactionID", (int(Lua_NPC::*)(void))&Lua_NPC::GetNPCFactionID) + .def("GetNPCHate", (int(Lua_NPC::*)(Lua_Mob))&Lua_NPC::GetNPCHate) + .def("GetNPCSpellsID", (int(Lua_NPC::*)(void))&Lua_NPC::GetNPCSpellsID) + .def("GetNPCSpellsID", (int(Lua_NPC::*)(void))&Lua_NPC::GetNPCSpellsID) + .def("GetPetSpellID", (int(Lua_NPC::*)(void))&Lua_NPC::GetPetSpellID) + .def("GetPlatinum", (uint32(Lua_NPC::*)(void))&Lua_NPC::GetPlatinum) + .def("GetPrimSkill", (int(Lua_NPC::*)(void))&Lua_NPC::GetPrimSkill) + .def("GetPrimaryFaction", (int(Lua_NPC::*)(void))&Lua_NPC::GetPrimaryFaction) + .def("GetRawAC", (int(Lua_NPC::*)(void))&Lua_NPC::GetRawAC) + .def("GetScore", (int(Lua_NPC::*)(void))&Lua_NPC::GetScore) + .def("GetSecSkill", (int(Lua_NPC::*)(void))&Lua_NPC::GetSecSkill) + .def("GetSilver", (uint32(Lua_NPC::*)(void))&Lua_NPC::GetSilver) + .def("GetSlowMitigation", (int(Lua_NPC::*)(void))&Lua_NPC::GetSlowMitigation) + .def("GetSp2", (uint32(Lua_NPC::*)(void))&Lua_NPC::GetSp2) + .def("GetSpawnKillCount", (int(Lua_NPC::*)(void))&Lua_NPC::GetSpawnKillCount) + .def("GetSpawnPointH", (float(Lua_NPC::*)(void))&Lua_NPC::GetSpawnPointH) + .def("GetSpawnPointID", (int(Lua_NPC::*)(void))&Lua_NPC::GetSpawnPointID) + .def("GetSpawnPointX", (float(Lua_NPC::*)(void))&Lua_NPC::GetSpawnPointX) + .def("GetSpawnPointY", (float(Lua_NPC::*)(void))&Lua_NPC::GetSpawnPointY) + .def("GetSpawnPointZ", (float(Lua_NPC::*)(void))&Lua_NPC::GetSpawnPointZ) + .def("GetSpellFocusDMG", (void(Lua_NPC::*)(int))&Lua_NPC::GetSpellFocusDMG) + .def("GetSpellFocusHeal", (void(Lua_NPC::*)(int))&Lua_NPC::GetSpellFocusHeal) + .def("GetSpellScale", (float(Lua_NPC::*)(void))&Lua_NPC::GetSpellScale) + .def("GetSwarmOwner", (int(Lua_NPC::*)(void))&Lua_NPC::GetSwarmOwner) + .def("GetSwarmTarget", (int(Lua_NPC::*)(void))&Lua_NPC::GetSwarmTarget) + .def("GetWaypointMax", (int(Lua_NPC::*)(void))&Lua_NPC::GetWaypointMax) + .def("HasAISpellEffect", (bool(Lua_NPC::*)(int))&Lua_NPC::HasAISpellEffect) + .def("HasItem", (bool(Lua_NPC::*)(uint32))&Lua_NPC::HasItem) + .def("IsAnimal", (bool(Lua_NPC::*)(void))&Lua_NPC::IsAnimal) + .def("IsGuarding", (bool(Lua_NPC::*)(void))&Lua_NPC::IsGuarding) + .def("IsOnHatelist", (bool(Lua_NPC::*)(Lua_Mob))&Lua_NPC::IsOnHatelist) + .def("IsRaidTarget", (bool(Lua_NPC::*)(void))&Lua_NPC::IsRaidTarget) + .def("IsTaunting", (bool(Lua_NPC::*)(void))&Lua_NPC::IsTaunting) + .def("MerchantCloseShop", (void(Lua_NPC::*)(void))&Lua_NPC::MerchantCloseShop) + .def("MerchantOpenShop", (void(Lua_NPC::*)(void))&Lua_NPC::MerchantOpenShop) + .def("ModifyNPCStat", (void(Lua_NPC::*)(const char*,const char*))&Lua_NPC::ModifyNPCStat) + .def("MoveTo", (void(Lua_NPC::*)(float,float,float,float,bool))&Lua_NPC::MoveTo) + .def("NextGuardPosition", (void(Lua_NPC::*)(void))&Lua_NPC::NextGuardPosition) + .def("PauseWandering", (void(Lua_NPC::*)(int))&Lua_NPC::PauseWandering) + .def("PickPocket", (void(Lua_NPC::*)(Lua_Client))&Lua_NPC::PickPocket) + .def("RecalculateSkills", (void(Lua_NPC::*)(void))&Lua_NPC::RecalculateSkills) + .def("RemoveAISpell", (void(Lua_NPC::*)(int))&Lua_NPC::RemoveAISpell) + .def("RemoveAISpellEffect", (void(Lua_NPC::*)(int))&Lua_NPC::RemoveAISpellEffect) + .def("RemoveCash", (void(Lua_NPC::*)(void))&Lua_NPC::RemoveCash) + .def("RemoveItem", (void(Lua_NPC::*)(int))&Lua_NPC::RemoveItem) + .def("RemoveItem", (void(Lua_NPC::*)(int,int))&Lua_NPC::RemoveItem) + .def("RemoveItem", (void(Lua_NPC::*)(int,int,int))&Lua_NPC::RemoveItem) + .def("ResumeWandering", (void(Lua_NPC::*)(void))&Lua_NPC::ResumeWandering) + .def("SaveGuardSpot", (void(Lua_NPC::*)(float,float,float,float))&Lua_NPC::SaveGuardSpot) + .def("ScaleNPC", (void(Lua_NPC::*)(uint8))&Lua_NPC::ScaleNPC) + .def("SetCopper", (void(Lua_NPC::*)(uint32))&Lua_NPC::SetCopper) + .def("SetFollowCanRun", (void(Lua_NPC::*)(bool))&Lua_NPC::SetFollowCanRun) + .def("SetFollowDistance", (void(Lua_NPC::*)(int))&Lua_NPC::SetFollowDistance) + .def("SetFollowID", (void(Lua_NPC::*)(int))&Lua_NPC::SetFollowID) + .def("SetGold", (void(Lua_NPC::*)(uint32))&Lua_NPC::SetGold) + .def("SetGrid", (void(Lua_NPC::*)(int))&Lua_NPC::SetGrid) + .def("SetNPCFactionID", (void(Lua_NPC::*)(int))&Lua_NPC::SetNPCFactionID) + .def("SetPetSpellID", (void(Lua_NPC::*)(int))&Lua_NPC::SetPetSpellID) + .def("SetPlatinum", (void(Lua_NPC::*)(uint32))&Lua_NPC::SetPlatinum) + .def("SetPrimSkill", (void(Lua_NPC::*)(int))&Lua_NPC::SetPrimSkill) + .def("SetSaveWaypoint", (void(Lua_NPC::*)(int))&Lua_NPC::SetSaveWaypoint) + .def("SetSecSkill", (void(Lua_NPC::*)(int))&Lua_NPC::SetSecSkill) + .def("SetSilver", (void(Lua_NPC::*)(uint32))&Lua_NPC::SetSilver) + .def("SetSimpleRoamBox", (void(Lua_NPC::*)(float))&Lua_NPC::SetSimpleRoamBox) + .def("SetSimpleRoamBox", (void(Lua_NPC::*)(float, float))&Lua_NPC::SetSimpleRoamBox) + .def("SetSimpleRoamBox", (void(Lua_NPC::*)(float, float, int))&Lua_NPC::SetSimpleRoamBox) + .def("SetSp2", (void(Lua_NPC::*)(int))&Lua_NPC::SetSp2) + .def("SetSpellFocusDMG", (void(Lua_NPC::*)(int))&Lua_NPC::SetSpellFocusDMG) + .def("SetSpellFocusHeal", (void(Lua_NPC::*)(int))&Lua_NPC::SetSpellFocusHeal) + .def("SetSwarmTarget", (void(Lua_NPC::*)(int))&Lua_NPC::SetSwarmTarget) + .def("SetTaunting", (void(Lua_NPC::*)(bool))&Lua_NPC::SetTaunting) + .def("SetWaypointPause", (void(Lua_NPC::*)(void))&Lua_NPC::SetWaypointPause) + .def("Signal", (void(Lua_NPC::*)(int))&Lua_NPC::Signal) + .def("StartSwarmTimer", (void(Lua_NPC::*)(uint32))&Lua_NPC::StartSwarmTimer) + .def("StopWandering", (void(Lua_NPC::*)(void))&Lua_NPC::StopWandering) + .def("UpdateWaypoint", (void(Lua_NPC::*)(int))&Lua_NPC::UpdateWaypoint); +} + +luabind::scope lua_register_npc_loot_list() { + return luabind::class_("NPCLootList") + .def_readwrite("entries", &Lua_NPC_Loot_List::entries, luabind::return_stl_iterator); } #endif diff --git a/zone/lua_npc.h b/zone/lua_npc.h index 042a114ef..58cae136c 100644 --- a/zone/lua_npc.h +++ b/zone/lua_npc.h @@ -8,12 +8,14 @@ class NPC; class Lua_Mob; class Lua_NPC; class Lua_Client; +struct Lua_NPC_Loot_List; namespace luabind { struct scope; } luabind::scope lua_register_npc(); +luabind::scope lua_register_npc_loot_list(); class Lua_NPC : public Lua_Mob { @@ -78,7 +80,7 @@ public: void StartSwarmTimer(uint32 duration); void DoClassAttacks(Lua_Mob target); int GetMaxWp(); - void DisplayWaypointInfo(Lua_Client to); + void DisplayWaypointInfo(Lua_Client client); void CalculateNewWaypoint(); void AssignWaypoints(int grid); void SetWaypointPause(); @@ -137,6 +139,19 @@ public: void SetSimpleRoamBox(float box_size, float move_distance, int move_delay); void RecalculateSkills(); void ScaleNPC(uint8 npc_level); + bool IsRaidTarget(); + void ChangeLastName(const char *lastname); + void ClearLastName(); + bool HasItem(uint32 item_id); + uint16 CountItem(uint32 item_id); + uint32 GetItemIDBySlot(uint16 slot_id); + uint16 GetFirstSlotByItemID(uint32 item_id); + float GetHealScale(); + float GetSpellScale(); + Lua_NPC_Loot_List GetLootList(lua_State* L); + void AddAISpellEffect(int spell_effect_id, int base_value, int limit_value, int max_value); + void RemoveAISpellEffect(int spell_effect_id); + bool HasAISpellEffect(int spell_effect_id); }; #endif diff --git a/zone/lua_object.cpp b/zone/lua_object.cpp index eef878c66..09599ecac 100644 --- a/zone/lua_object.cpp +++ b/zone/lua_object.cpp @@ -180,43 +180,43 @@ bool Lua_Object::EntityVariableExists(const char *name) { luabind::scope lua_register_object() { return luabind::class_("Object") - .def(luabind::constructor<>()) - .property("null", &Lua_Object::Null) - .property("valid", &Lua_Object::Valid) - .def("Depop", (void(Lua_Object::*)(void))&Lua_Object::Depop) - .def("Repop", (void(Lua_Object::*)(void))&Lua_Object::Repop) - .def("SetModelName", (void(Lua_Object::*)(const char*))&Lua_Object::SetModelName) - .def("GetModelName", (const char*(Lua_Object::*)(void))&Lua_Object::GetModelName) - .def("GetX", (float(Lua_Object::*)(void))&Lua_Object::GetX) - .def("GetY", (float(Lua_Object::*)(void))&Lua_Object::GetY) - .def("GetZ", (float(Lua_Object::*)(void))&Lua_Object::GetZ) - .def("GetHeading", (float(Lua_Object::*)(void))&Lua_Object::GetHeading) - .def("SetX", (void(Lua_Object::*)(float))&Lua_Object::SetX) - .def("SetY", (void(Lua_Object::*)(float))&Lua_Object::SetY) - .def("SetZ", (void(Lua_Object::*)(float))&Lua_Object::SetZ) - .def("SetHeading", (void(Lua_Object::*)(float))&Lua_Object::SetHeading) - .def("SetLocation", (void(Lua_Object::*)(float,float,float))&Lua_Object::SetLocation) - .def("SetItemID", (void(Lua_Object::*)(uint32))&Lua_Object::SetItemID) - .def("GetItemID", (uint32(Lua_Object::*)(void))&Lua_Object::GetItemID) - .def("SetIcon", (void(Lua_Object::*)(uint32))&Lua_Object::SetIcon) - .def("GetIcon", (uint32(Lua_Object::*)(void))&Lua_Object::GetIcon) - .def("SetType", (void(Lua_Object::*)(uint32))&Lua_Object::SetType) - .def("GetType", (uint32(Lua_Object::*)(void))&Lua_Object::GetType) - .def("GetDBID", (uint32(Lua_Object::*)(void))&Lua_Object::GetDBID) - .def("ClearUser", (void(Lua_Object::*)(void))&Lua_Object::ClearUser) - .def("SetID", (void(Lua_Object::*)(int))&Lua_Object::SetID) - .def("GetID", (int(Lua_Object::*)(void))&Lua_Object::GetID) - .def("Save", (bool(Lua_Object::*)(void))&Lua_Object::Save) - .def("VarSave", (uint32(Lua_Object::*)(void))&Lua_Object::VarSave) - .def("DeleteItem", (void(Lua_Object::*)(int))&Lua_Object::DeleteItem) - .def("StartDecay", (void(Lua_Object::*)(void))&Lua_Object::StartDecay) - .def("Delete", (void(Lua_Object::*)(void))&Lua_Object::Delete) - .def("Delete", (void(Lua_Object::*)(bool))&Lua_Object::Delete) - .def("IsGroundSpawn", (bool(Lua_Object::*)(void))&Lua_Object::IsGroundSpawn) - .def("Close", (void(Lua_Object::*)(void))&Lua_Object::Close) - .def("GetEntityVariable", (const char*(Lua_Object::*)(const char*))&Lua_Object::GetEntityVariable) - .def("SetEntityVariable", (void(Lua_Object::*)(const char*,const char*))&Lua_Object::SetEntityVariable) - .def("EntityVariableExists", (bool(Lua_Object::*)(const char*))&Lua_Object::EntityVariableExists); + .def(luabind::constructor<>()) + .property("null", &Lua_Object::Null) + .property("valid", &Lua_Object::Valid) + .def("ClearUser", (void(Lua_Object::*)(void))&Lua_Object::ClearUser) + .def("Close", (void(Lua_Object::*)(void))&Lua_Object::Close) + .def("Delete", (void(Lua_Object::*)(bool))&Lua_Object::Delete) + .def("Delete", (void(Lua_Object::*)(void))&Lua_Object::Delete) + .def("DeleteItem", (void(Lua_Object::*)(int))&Lua_Object::DeleteItem) + .def("Depop", (void(Lua_Object::*)(void))&Lua_Object::Depop) + .def("EntityVariableExists", (bool(Lua_Object::*)(const char*))&Lua_Object::EntityVariableExists) + .def("GetDBID", (uint32(Lua_Object::*)(void))&Lua_Object::GetDBID) + .def("GetEntityVariable", (const char*(Lua_Object::*)(const char*))&Lua_Object::GetEntityVariable) + .def("GetHeading", (float(Lua_Object::*)(void))&Lua_Object::GetHeading) + .def("GetID", (int(Lua_Object::*)(void))&Lua_Object::GetID) + .def("GetIcon", (uint32(Lua_Object::*)(void))&Lua_Object::GetIcon) + .def("GetItemID", (uint32(Lua_Object::*)(void))&Lua_Object::GetItemID) + .def("GetModelName", (const char*(Lua_Object::*)(void))&Lua_Object::GetModelName) + .def("GetType", (uint32(Lua_Object::*)(void))&Lua_Object::GetType) + .def("GetX", (float(Lua_Object::*)(void))&Lua_Object::GetX) + .def("GetY", (float(Lua_Object::*)(void))&Lua_Object::GetY) + .def("GetZ", (float(Lua_Object::*)(void))&Lua_Object::GetZ) + .def("IsGroundSpawn", (bool(Lua_Object::*)(void))&Lua_Object::IsGroundSpawn) + .def("Repop", (void(Lua_Object::*)(void))&Lua_Object::Repop) + .def("Save", (bool(Lua_Object::*)(void))&Lua_Object::Save) + .def("SetEntityVariable", (void(Lua_Object::*)(const char*,const char*))&Lua_Object::SetEntityVariable) + .def("SetHeading", (void(Lua_Object::*)(float))&Lua_Object::SetHeading) + .def("SetID", (void(Lua_Object::*)(int))&Lua_Object::SetID) + .def("SetIcon", (void(Lua_Object::*)(uint32))&Lua_Object::SetIcon) + .def("SetItemID", (void(Lua_Object::*)(uint32))&Lua_Object::SetItemID) + .def("SetLocation", (void(Lua_Object::*)(float,float,float))&Lua_Object::SetLocation) + .def("SetModelName", (void(Lua_Object::*)(const char*))&Lua_Object::SetModelName) + .def("SetType", (void(Lua_Object::*)(uint32))&Lua_Object::SetType) + .def("SetX", (void(Lua_Object::*)(float))&Lua_Object::SetX) + .def("SetY", (void(Lua_Object::*)(float))&Lua_Object::SetY) + .def("SetZ", (void(Lua_Object::*)(float))&Lua_Object::SetZ) + .def("StartDecay", (void(Lua_Object::*)(void))&Lua_Object::StartDecay) + .def("VarSave", (uint32(Lua_Object::*)(void))&Lua_Object::VarSave); } #endif diff --git a/zone/lua_packet.cpp b/zone/lua_packet.cpp index 4936259e6..b07a7011f 100644 --- a/zone/lua_packet.cpp +++ b/zone/lua_packet.cpp @@ -89,6 +89,21 @@ void Lua_Packet::SetRawOpcode(int op) { self->SetOpcode(static_cast(op)); } +int Lua_Packet::GetWritePosition() { + Lua_Safe_Call_Int(); + return self->GetWritePosition(); +} + +void Lua_Packet::SetWritePosition(int offset) { + Lua_Safe_Call_Void(); + self->SetWritePosition(offset); +} + +void Lua_Packet::WriteInt8(int value) { + Lua_Safe_Call_Void(); + self->WriteUInt8(static_cast(value)); +} + void Lua_Packet::WriteInt8(int offset, int value) { Lua_Safe_Call_Void(); @@ -97,6 +112,11 @@ void Lua_Packet::WriteInt8(int offset, int value) { } } +void Lua_Packet::WriteInt16(int value) { + Lua_Safe_Call_Void(); + self->WriteUInt16(static_cast(value)); +} + void Lua_Packet::WriteInt16(int offset, int value) { Lua_Safe_Call_Void(); @@ -105,6 +125,11 @@ void Lua_Packet::WriteInt16(int offset, int value) { } } +void Lua_Packet::WriteInt32(int value) { + Lua_Safe_Call_Void(); + self->WriteUInt32(static_cast(value)); +} + void Lua_Packet::WriteInt32(int offset, int value) { Lua_Safe_Call_Void(); @@ -113,6 +138,11 @@ void Lua_Packet::WriteInt32(int offset, int value) { } } +void Lua_Packet::WriteInt64(int64 value) { + Lua_Safe_Call_Void(); + self->WriteUInt64(static_cast(value)); +} + void Lua_Packet::WriteInt64(int offset, int64 value) { Lua_Safe_Call_Void(); @@ -121,6 +151,11 @@ void Lua_Packet::WriteInt64(int offset, int64 value) { } } +void Lua_Packet::WriteFloat(float value) { + Lua_Safe_Call_Void(); + self->WriteFloat(value); +} + void Lua_Packet::WriteFloat(int offset, float value) { Lua_Safe_Call_Void(); @@ -129,6 +164,11 @@ void Lua_Packet::WriteFloat(int offset, float value) { } } +void Lua_Packet::WriteDouble(double value) { + Lua_Safe_Call_Void(); + self->WriteDouble(value); +} + void Lua_Packet::WriteDouble(int offset, double value) { Lua_Safe_Call_Void(); @@ -137,6 +177,11 @@ void Lua_Packet::WriteDouble(int offset, double value) { } } +void Lua_Packet::WriteString(std::string value) { + Lua_Safe_Call_Void(); + self->WriteString(value.c_str()); +} + void Lua_Packet::WriteString(int offset, std::string value) { Lua_Safe_Call_Void(); @@ -146,6 +191,11 @@ void Lua_Packet::WriteString(int offset, std::string value) { } } +void Lua_Packet::WriteFixedLengthString(std::string value) { + Lua_Safe_Call_Void(); + self->WriteLengthString(static_cast(value.size()), value.c_str()); +} + void Lua_Packet::WriteFixedLengthString(int offset, std::string value, int string_length) { Lua_Safe_Call_Void(); @@ -273,585 +323,595 @@ std::string Lua_Packet::ReadFixedLengthString(int offset, int string_length) { luabind::scope lua_register_packet() { return luabind::class_("Packet") - .def(luabind::constructor<>()) - .def(luabind::constructor()) - .def(luabind::constructor()) - .property("null", &Lua_Packet::Null) - .property("valid", &Lua_Packet::Valid) - .def("GetSize", &Lua_Packet::GetSize) - .def("GetOpcode", &Lua_Packet::GetOpcode) - .def("SetOpcode", &Lua_Packet::SetOpcode) - .def("GetRawOpcode", &Lua_Packet::GetRawOpcode) - .def("SetRawOpcode", &Lua_Packet::SetRawOpcode) - .def("WriteInt8", &Lua_Packet::WriteInt8) - .def("WriteInt16", &Lua_Packet::WriteInt16) - .def("WriteInt32", &Lua_Packet::WriteInt32) - .def("WriteInt64", &Lua_Packet::WriteInt64) - .def("WriteFloat", &Lua_Packet::WriteFloat) - .def("WriteDouble", &Lua_Packet::WriteDouble) - .def("WriteString", &Lua_Packet::WriteString) - .def("WriteFixedLengthString", &Lua_Packet::WriteFixedLengthString) - .def("ReadInt8", &Lua_Packet::ReadInt8) - .def("ReadInt16", &Lua_Packet::ReadInt16) - .def("ReadInt32", &Lua_Packet::ReadInt32) - .def("ReadInt64", &Lua_Packet::ReadInt64) - .def("ReadFloat", &Lua_Packet::ReadFloat) - .def("ReadDouble", &Lua_Packet::ReadDouble) - .def("ReadString", &Lua_Packet::ReadString) - .def("ReadFixedLengthString", &Lua_Packet::ReadFixedLengthString); + .def(luabind::constructor<>()) + .def(luabind::constructor()) + .def(luabind::constructor()) + .property("null", &Lua_Packet::Null) + .property("valid", &Lua_Packet::Valid) + .def("GetOpcode", &Lua_Packet::GetOpcode) + .def("GetRawOpcode", &Lua_Packet::GetRawOpcode) + .def("GetSize", &Lua_Packet::GetSize) + .def("GetWritePosition", &Lua_Packet::GetWritePosition) + .def("ReadDouble", &Lua_Packet::ReadDouble) + .def("ReadFixedLengthString", &Lua_Packet::ReadFixedLengthString) + .def("ReadFloat", &Lua_Packet::ReadFloat) + .def("ReadInt16", &Lua_Packet::ReadInt16) + .def("ReadInt32", &Lua_Packet::ReadInt32) + .def("ReadInt64", &Lua_Packet::ReadInt64) + .def("ReadInt8", &Lua_Packet::ReadInt8) + .def("ReadString", &Lua_Packet::ReadString) + .def("SetOpcode", &Lua_Packet::SetOpcode) + .def("SetRawOpcode", &Lua_Packet::SetRawOpcode) + .def("SetWritePosition", &Lua_Packet::SetWritePosition) + .def("WriteDouble", (void(Lua_Packet::*)(double))&Lua_Packet::WriteDouble) + .def("WriteDouble", (void(Lua_Packet::*)(int, double))&Lua_Packet::WriteDouble) + .def("WriteFixedLengthString", (void(Lua_Packet::*)(int, std::string, int))&Lua_Packet::WriteFixedLengthString) + .def("WriteFixedLengthString", (void(Lua_Packet::*)(std::string))&Lua_Packet::WriteFixedLengthString) + .def("WriteFloat", (void(Lua_Packet::*)(float))&Lua_Packet::WriteFloat) + .def("WriteFloat", (void(Lua_Packet::*)(int, float))&Lua_Packet::WriteFloat) + .def("WriteInt16", (void(Lua_Packet::*)(int))&Lua_Packet::WriteInt16) + .def("WriteInt16", (void(Lua_Packet::*)(int, int))&Lua_Packet::WriteInt16) + .def("WriteInt32", (void(Lua_Packet::*)(int))&Lua_Packet::WriteInt32) + .def("WriteInt32", (void(Lua_Packet::*)(int, int))&Lua_Packet::WriteInt32) + .def("WriteInt64", (void(Lua_Packet::*)(int, int64))&Lua_Packet::WriteInt64) + .def("WriteInt64", (void(Lua_Packet::*)(int64))&Lua_Packet::WriteInt64) + .def("WriteInt8", (void(Lua_Packet::*)(int))&Lua_Packet::WriteInt8) + .def("WriteInt8", (void(Lua_Packet::*)(int, int))&Lua_Packet::WriteInt8) + .def("WriteString", (void(Lua_Packet::*)(int, std::string))&Lua_Packet::WriteString) + .def("WriteString", (void(Lua_Packet::*)(std::string))&Lua_Packet::WriteString); } //TODO: Reorder these to match emu_oplist.h again luabind::scope lua_register_packet_opcodes() { return luabind::class_("Opcode") - .enum_("constants") - [ - luabind::value("ExploreUnknown", static_cast(OP_ExploreUnknown)), - luabind::value("Heartbeat", static_cast(OP_Heartbeat)), - luabind::value("ReloadUI", static_cast(OP_ReloadUI)), - luabind::value("IncreaseStats", static_cast(OP_IncreaseStats)), - luabind::value("ApproveZone", static_cast(OP_ApproveZone)), - luabind::value("Dye", static_cast(OP_Dye)), - luabind::value("Stamina", static_cast(OP_Stamina)), - luabind::value("ControlBoat", static_cast(OP_ControlBoat)), - luabind::value("MobUpdate", static_cast(OP_MobUpdate)), - luabind::value("ClientUpdate", static_cast(OP_ClientUpdate)), - luabind::value("ChannelMessage", static_cast(OP_ChannelMessage)), - luabind::value("SimpleMessage", static_cast(OP_SimpleMessage)), - luabind::value("FormattedMessage", static_cast(OP_FormattedMessage)), - luabind::value("TGB", static_cast(OP_TGB)), - luabind::value("Bind_Wound", static_cast(OP_Bind_Wound)), - luabind::value("Charm", static_cast(OP_Charm)), - luabind::value("Begging", static_cast(OP_Begging)), - luabind::value("MoveCoin", static_cast(OP_MoveCoin)), - luabind::value("SpawnDoor", static_cast(OP_SpawnDoor)), - luabind::value("Sneak", static_cast(OP_Sneak)), - luabind::value("ExpUpdate", static_cast(OP_ExpUpdate)), - luabind::value("DumpName", static_cast(OP_DumpName)), - luabind::value("RespondAA", static_cast(OP_RespondAA)), - luabind::value("UpdateAA", static_cast(OP_UpdateAA)), - luabind::value("SendAAStats", static_cast(OP_SendAAStats)), - luabind::value("SendAATable", static_cast(OP_SendAATable)), - luabind::value("AAAction", static_cast(OP_AAAction)), - luabind::value("BoardBoat", static_cast(OP_BoardBoat)), - luabind::value("LeaveBoat", static_cast(OP_LeaveBoat)), - luabind::value("AdventureInfoRequest", static_cast(OP_AdventureInfoRequest)), - luabind::value("AdventureInfo", static_cast(OP_AdventureInfo)), - luabind::value("AdventureRequest", static_cast(OP_AdventureRequest)), - luabind::value("AdventureDetails", static_cast(OP_AdventureDetails)), - luabind::value("LDoNButton", static_cast(OP_LDoNButton)), - luabind::value("AdventureData", static_cast(OP_AdventureData)), - luabind::value("AdventureFinish", static_cast(OP_AdventureFinish)), - luabind::value("LeaveAdventure", static_cast(OP_LeaveAdventure)), - luabind::value("AdventureUpdate", static_cast(OP_AdventureUpdate)), - luabind::value("SendExpZonein", static_cast(OP_SendExpZonein)), - luabind::value("RaidUpdate", static_cast(OP_RaidUpdate)), - luabind::value("GuildLeader", static_cast(OP_GuildLeader)), - luabind::value("GuildPeace", static_cast(OP_GuildPeace)), - luabind::value("GuildRemove", static_cast(OP_GuildRemove)), - luabind::value("GuildMemberList", static_cast(OP_GuildMemberList)), - luabind::value("GuildMemberUpdate", static_cast(OP_GuildMemberUpdate)), - luabind::value("GuildMemberLevelUpdate", static_cast(OP_GuildMemberLevelUpdate)), - luabind::value("GuildInvite", static_cast(OP_GuildInvite)), - luabind::value("GuildMOTD", static_cast(OP_GuildMOTD)), - luabind::value("SetGuildMOTD", static_cast(OP_SetGuildMOTD)), - luabind::value("GuildPublicNote", static_cast(OP_GuildPublicNote)), - luabind::value("GetGuildsList", static_cast(OP_GetGuildsList)), - luabind::value("GuildDemote", static_cast(OP_GuildDemote)), - luabind::value("GuildInviteAccept", static_cast(OP_GuildInviteAccept)), - luabind::value("GuildWar", static_cast(OP_GuildWar)), - luabind::value("GuildDelete", static_cast(OP_GuildDelete)), - luabind::value("GuildManageRemove", static_cast(OP_GuildManageRemove)), - luabind::value("GuildManageAdd", static_cast(OP_GuildManageAdd)), - luabind::value("GuildManageStatus", static_cast(OP_GuildManageStatus)), - luabind::value("GuildManageBanker", static_cast(OP_GuildManageBanker)), - luabind::value("GetGuildMOTD", static_cast(OP_GetGuildMOTD)), - luabind::value("Trader", static_cast(OP_Trader)), - luabind::value("Bazaar", static_cast(OP_Bazaar)), - luabind::value("BecomeTrader", static_cast(OP_BecomeTrader)), - luabind::value("TraderItemUpdate", static_cast(OP_TraderItemUpdate)), - luabind::value("TraderShop", static_cast(OP_TraderShop)), - luabind::value("TraderBuy", static_cast(OP_TraderBuy)), - luabind::value("PetCommands", static_cast(OP_PetCommands)), - luabind::value("TradeSkillCombine", static_cast(OP_TradeSkillCombine)), - luabind::value("AugmentItem", static_cast(OP_AugmentItem)), - luabind::value("ItemName", static_cast(OP_ItemName)), - luabind::value("ShopItem", static_cast(OP_ShopItem)), - luabind::value("ShopPlayerBuy", static_cast(OP_ShopPlayerBuy)), - luabind::value("ShopPlayerSell", static_cast(OP_ShopPlayerSell)), - luabind::value("ShopDelItem", static_cast(OP_ShopDelItem)), - luabind::value("ShopRequest", static_cast(OP_ShopRequest)), - luabind::value("ShopEnd", static_cast(OP_ShopEnd)), - luabind::value("LFGCommand", static_cast(OP_LFGCommand)), - luabind::value("LFGAppearance", static_cast(OP_LFGAppearance)), - luabind::value("GroupUpdate", static_cast(OP_GroupUpdate)), - luabind::value("GroupInvite", static_cast(OP_GroupInvite)), - luabind::value("GroupDisband", static_cast(OP_GroupDisband)), - luabind::value("GroupInvite2", static_cast(OP_GroupInvite2)), - luabind::value("GroupFollow", static_cast(OP_GroupFollow)), - luabind::value("GroupFollow2", static_cast(OP_GroupFollow2)), - luabind::value("GroupCancelInvite", static_cast(OP_GroupCancelInvite)), - luabind::value("CustomTitles", static_cast(OP_CustomTitles)), - luabind::value("Split", static_cast(OP_Split)), - luabind::value("Jump", static_cast(OP_Jump)), - luabind::value("ConsiderCorpse", static_cast(OP_ConsiderCorpse)), - luabind::value("SkillUpdate", static_cast(OP_SkillUpdate)), - luabind::value("GMEndTrainingResponse", static_cast(OP_GMEndTrainingResponse)), - luabind::value("GMEndTraining", static_cast(OP_GMEndTraining)), - luabind::value("GMTrainSkill", static_cast(OP_GMTrainSkill)), - luabind::value("GMTraining", static_cast(OP_GMTraining)), - luabind::value("DeleteItem", static_cast(OP_DeleteItem)), - luabind::value("CombatAbility", static_cast(OP_CombatAbility)), - luabind::value("TrackUnknown", static_cast(OP_TrackUnknown)), - luabind::value("TrackTarget", static_cast(OP_TrackTarget)), - luabind::value("Track", static_cast(OP_Track)), - luabind::value("ItemLinkClick", static_cast(OP_ItemLinkClick)), - luabind::value("ItemLinkResponse", static_cast(OP_ItemLinkResponse)), - luabind::value("ItemLinkText", static_cast(OP_ItemLinkText)), - luabind::value("RezzAnswer", static_cast(OP_RezzAnswer)), - luabind::value("RezzComplete", static_cast(OP_RezzComplete)), - luabind::value("SendZonepoints", static_cast(OP_SendZonepoints)), - luabind::value("SetRunMode", static_cast(OP_SetRunMode)), - luabind::value("InspectRequest", static_cast(OP_InspectRequest)), - luabind::value("InspectAnswer", static_cast(OP_InspectAnswer)), - luabind::value("SenseTraps", static_cast(OP_SenseTraps)), - luabind::value("DisarmTraps", static_cast(OP_DisarmTraps)), - luabind::value("Assist", static_cast(OP_Assist)), - luabind::value("AssistGroup", static_cast(OP_AssistGroup)), - luabind::value("PickPocket", static_cast(OP_PickPocket)), - luabind::value("LootRequest", static_cast(OP_LootRequest)), - luabind::value("EndLootRequest", static_cast(OP_EndLootRequest)), - luabind::value("MoneyOnCorpse", static_cast(OP_MoneyOnCorpse)), - luabind::value("LootComplete", static_cast(OP_LootComplete)), - luabind::value("LootItem", static_cast(OP_LootItem)), - luabind::value("MoveItem", static_cast(OP_MoveItem)), - luabind::value("WhoAllRequest", static_cast(OP_WhoAllRequest)), - luabind::value("WhoAllResponse", static_cast(OP_WhoAllResponse)), - luabind::value("Consume", static_cast(OP_Consume)), - luabind::value("AutoAttack", static_cast(OP_AutoAttack)), - luabind::value("AutoAttack2", static_cast(OP_AutoAttack2)), - luabind::value("TargetMouse", static_cast(OP_TargetMouse)), - luabind::value("TargetCommand", static_cast(OP_TargetCommand)), - luabind::value("TargetReject", static_cast(OP_TargetReject)), - luabind::value("TargetHoTT", static_cast(OP_TargetHoTT)), - luabind::value("Hide", static_cast(OP_Hide)), - luabind::value("Forage", static_cast(OP_Forage)), - luabind::value("Fishing", static_cast(OP_Fishing)), - luabind::value("Bug", static_cast(OP_Bug)), - luabind::value("Emote", static_cast(OP_Emote)), - luabind::value("Consider", static_cast(OP_Consider)), - luabind::value("FaceChange", static_cast(OP_FaceChange)), - luabind::value("RandomReq", static_cast(OP_RandomReq)), - luabind::value("RandomReply", static_cast(OP_RandomReply)), - luabind::value("Camp", static_cast(OP_Camp)), - luabind::value("YellForHelp", static_cast(OP_YellForHelp)), - luabind::value("SafePoint", static_cast(OP_SafePoint)), - luabind::value("Buff", static_cast(OP_Buff)), - luabind::value("ColoredText", static_cast(OP_ColoredText)), - luabind::value("SpecialMesg", static_cast(OP_SpecialMesg)), - luabind::value("Consent", static_cast(OP_Consent)), - luabind::value("ConsentResponse", static_cast(OP_ConsentResponse)), - luabind::value("Stun", static_cast(OP_Stun)), - luabind::value("BeginCast", static_cast(OP_BeginCast)), - luabind::value("CastSpell", static_cast(OP_CastSpell)), - luabind::value("InterruptCast", static_cast(OP_InterruptCast)), - luabind::value("Death", static_cast(OP_Death)), - luabind::value("FeignDeath", static_cast(OP_FeignDeath)), - luabind::value("Illusion", static_cast(OP_Illusion)), - luabind::value("LevelUpdate", static_cast(OP_LevelUpdate)), - luabind::value("LevelAppearance", static_cast(OP_LevelAppearance)), - luabind::value("MemorizeSpell", static_cast(OP_MemorizeSpell)), - luabind::value("HPUpdate", static_cast(OP_HPUpdate)), - luabind::value("Mend", static_cast(OP_Mend)), - luabind::value("Taunt", static_cast(OP_Taunt)), - luabind::value("GMDelCorpse", static_cast(OP_GMDelCorpse)), - luabind::value("GMFind", static_cast(OP_GMFind)), - luabind::value("GMServers", static_cast(OP_GMServers)), - luabind::value("GMGoto", static_cast(OP_GMGoto)), - luabind::value("GMSummon", static_cast(OP_GMSummon)), - luabind::value("GMKill", static_cast(OP_GMKill)), - luabind::value("GMLastName", static_cast(OP_GMLastName)), - luabind::value("GMToggle", static_cast(OP_GMToggle)), - luabind::value("GMEmoteZone", static_cast(OP_GMEmoteZone)), - luabind::value("GMBecomeNPC", static_cast(OP_GMBecomeNPC)), - luabind::value("GMHideMe", static_cast(OP_GMHideMe)), - luabind::value("GMZoneRequest", static_cast(OP_GMZoneRequest)), - luabind::value("GMZoneRequest2", static_cast(OP_GMZoneRequest2)), - luabind::value("Petition", static_cast(OP_Petition)), - luabind::value("PetitionRefresh", static_cast(OP_PetitionRefresh)), - luabind::value("PDeletePetition", static_cast(OP_PDeletePetition)), - luabind::value("PetitionBug", static_cast(OP_PetitionBug)), - luabind::value("PetitionUpdate", static_cast(OP_PetitionUpdate)), - luabind::value("PetitionCheckout", static_cast(OP_PetitionCheckout)), - luabind::value("PetitionCheckout2", static_cast(OP_PetitionCheckout2)), - luabind::value("PetitionDelete", static_cast(OP_PetitionDelete)), - luabind::value("PetitionResolve", static_cast(OP_PetitionResolve)), - luabind::value("PetitionCheckIn", static_cast(OP_PetitionCheckIn)), - luabind::value("PetitionUnCheckout", static_cast(OP_PetitionUnCheckout)), - luabind::value("PetitionQue", static_cast(OP_PetitionQue)), - luabind::value("SetServerFilter", static_cast(OP_SetServerFilter)), - luabind::value("NewSpawn", static_cast(OP_NewSpawn)), - luabind::value("Animation", static_cast(OP_Animation)), - luabind::value("ZoneChange", static_cast(OP_ZoneChange)), - luabind::value("DeleteSpawn", static_cast(OP_DeleteSpawn)), - luabind::value("EnvDamage", static_cast(OP_EnvDamage)), - luabind::value("Action", static_cast(OP_Action)), - luabind::value("Damage", static_cast(OP_Damage)), - luabind::value("ManaChange", static_cast(OP_ManaChange)), - luabind::value("ClientError", static_cast(OP_ClientError)), - luabind::value("Save", static_cast(OP_Save)), - luabind::value("LocInfo", static_cast(OP_LocInfo)), - luabind::value("Surname", static_cast(OP_Surname)), - luabind::value("ClearSurname", static_cast(OP_ClearSurname)), - luabind::value("SwapSpell", static_cast(OP_SwapSpell)), - luabind::value("DeleteSpell", static_cast(OP_DeleteSpell)), - luabind::value("CloseContainer", static_cast(OP_CloseContainer)), - luabind::value("ClickObjectAction", static_cast(OP_ClickObjectAction)), - luabind::value("GroundSpawn", static_cast(OP_GroundSpawn)), - luabind::value("ClearObject", static_cast(OP_ClearObject)), - luabind::value("ZoneUnavail", static_cast(OP_ZoneUnavail)), - luabind::value("ItemPacket", static_cast(OP_ItemPacket)), - luabind::value("TradeRequest", static_cast(OP_TradeRequest)), - luabind::value("TradeRequestAck", static_cast(OP_TradeRequestAck)), - luabind::value("TradeAcceptClick", static_cast(OP_TradeAcceptClick)), - luabind::value("TradeMoneyUpdate", static_cast(OP_TradeMoneyUpdate)), - luabind::value("TradeCoins", static_cast(OP_TradeCoins)), - luabind::value("CancelTrade", static_cast(OP_CancelTrade)), - luabind::value("FinishTrade", static_cast(OP_FinishTrade)), - luabind::value("SaveOnZoneReq", static_cast(OP_SaveOnZoneReq)), - luabind::value("Logout", static_cast(OP_Logout)), - luabind::value("LogoutReply", static_cast(OP_LogoutReply)), - luabind::value("PreLogoutReply", static_cast(OP_PreLogoutReply)), - luabind::value("DuelResponse2", static_cast(OP_DuelResponse2)), - luabind::value("InstillDoubt", static_cast(OP_InstillDoubt)), - luabind::value("SafeFallSuccess", static_cast(OP_SafeFallSuccess)), - luabind::value("DisciplineUpdate", static_cast(OP_DisciplineUpdate)), - luabind::value("SendGuildTributes", static_cast(OP_SendGuildTributes)), - luabind::value("SendTributes", static_cast(OP_SendTributes)), - luabind::value("TributeUpdate", static_cast(OP_TributeUpdate)), - luabind::value("TributeItem", static_cast(OP_TributeItem)), - luabind::value("TributePointUpdate", static_cast(OP_TributePointUpdate)), - luabind::value("TributeInfo", static_cast(OP_TributeInfo)), - luabind::value("GuildTributeInfo", static_cast(OP_GuildTributeInfo)), - luabind::value("OpenGuildTributeMaster", static_cast(OP_OpenGuildTributeMaster)), - luabind::value("OpenTributeMaster", static_cast(OP_OpenTributeMaster)), - luabind::value("TributeTimer", static_cast(OP_TributeTimer)), - luabind::value("SelectTribute", static_cast(OP_SelectTribute)), - luabind::value("TributeNPC", static_cast(OP_TributeNPC)), - luabind::value("TributeMoney", static_cast(OP_TributeMoney)), - luabind::value("TributeToggle", static_cast(OP_TributeToggle)), - luabind::value("CloseTributeMaster", static_cast(OP_CloseTributeMaster)), - luabind::value("RecipesFavorite", static_cast(OP_RecipesFavorite)), - luabind::value("RecipesSearch", static_cast(OP_RecipesSearch)), - luabind::value("RecipeReply", static_cast(OP_RecipeReply)), - luabind::value("RecipeDetails", static_cast(OP_RecipeDetails)), - luabind::value("RecipeAutoCombine", static_cast(OP_RecipeAutoCombine)), - luabind::value("Shielding", static_cast(OP_Shielding)), - luabind::value("FindPersonRequest", static_cast(OP_FindPersonRequest)), - luabind::value("FindPersonReply", static_cast(OP_FindPersonReply)), - luabind::value("ZoneEntry", static_cast(OP_ZoneEntry)), - luabind::value("PlayerProfile", static_cast(OP_PlayerProfile)), - luabind::value("CharInventory", static_cast(OP_CharInventory)), - luabind::value("ZoneSpawns", static_cast(OP_ZoneSpawns)), - luabind::value("Weather", static_cast(OP_Weather)), - luabind::value("ReqNewZone", static_cast(OP_ReqNewZone)), - luabind::value("NewZone", static_cast(OP_NewZone)), - luabind::value("ReqClientSpawn", static_cast(OP_ReqClientSpawn)), - luabind::value("SpawnAppearance", static_cast(OP_SpawnAppearance)), - luabind::value("ClientReady", static_cast(OP_ClientReady)), - luabind::value("ZoneComplete", static_cast(OP_ZoneComplete)), - luabind::value("ApproveWorld", static_cast(OP_ApproveWorld)), - luabind::value("LogServer", static_cast(OP_LogServer)), - luabind::value("MOTD", static_cast(OP_MOTD)), - luabind::value("SendLoginInfo", static_cast(OP_SendLoginInfo)), - luabind::value("DeleteCharacter", static_cast(OP_DeleteCharacter)), - luabind::value("SendCharInfo", static_cast(OP_SendCharInfo)), - luabind::value("ExpansionInfo", static_cast(OP_ExpansionInfo)), - luabind::value("CharacterCreate", static_cast(OP_CharacterCreate)), - luabind::value("CharacterCreateRequest", static_cast(OP_CharacterCreateRequest)), - luabind::value("RandomNameGenerator", static_cast(OP_RandomNameGenerator)), - luabind::value("GuildsList", static_cast(OP_GuildsList)), - luabind::value("ApproveName", static_cast(OP_ApproveName)), - luabind::value("EnterWorld", static_cast(OP_EnterWorld)), - luabind::value("PostEnterWorld ", static_cast(OP_PostEnterWorld )), - luabind::value("SendSystemStats", static_cast(OP_SendSystemStats)), - luabind::value("World_Client_CRC1", static_cast(OP_World_Client_CRC1)), - luabind::value("World_Client_CRC2", static_cast(OP_World_Client_CRC2)), - luabind::value("SetChatServer", static_cast(OP_SetChatServer)), - luabind::value("SetChatServer2", static_cast(OP_SetChatServer2)), - luabind::value("ZoneServerInfo", static_cast(OP_ZoneServerInfo)), - luabind::value("WorldClientReady", static_cast(OP_WorldClientReady)), - luabind::value("WorldUnknown001", static_cast(OP_WorldUnknown001)), - luabind::value("AckPacket", static_cast(OP_AckPacket)), - luabind::value("WearChange", static_cast(OP_WearChange)), - luabind::value("CrashDump", static_cast(OP_CrashDump)), - luabind::value("LoginComplete", static_cast(OP_LoginComplete)), - luabind::value("GMNameChange", static_cast(OP_GMNameChange)), - luabind::value("ReadBook", static_cast(OP_ReadBook)), - luabind::value("GMKick", static_cast(OP_GMKick)), - luabind::value("RezzRequest", static_cast(OP_RezzRequest)), - luabind::value("MultiLineMsg", static_cast(OP_MultiLineMsg)), - luabind::value("TimeOfDay", static_cast(OP_TimeOfDay)), - luabind::value("CompletedTasks", static_cast(OP_CompletedTasks)), - luabind::value("MoneyUpdate", static_cast(OP_MoneyUpdate)), - luabind::value("ClickObject", static_cast(OP_ClickObject)), - luabind::value("MoveDoor", static_cast(OP_MoveDoor)), - luabind::value("TraderDelItem", static_cast(OP_TraderDelItem)), - luabind::value("AdventureMerchantPurchase", static_cast(OP_AdventureMerchantPurchase)), - luabind::value("TestBuff", static_cast(OP_TestBuff)), - luabind::value("DuelResponse", static_cast(OP_DuelResponse)), - luabind::value("RequestDuel", static_cast(OP_RequestDuel)), - luabind::value("BazaarInspect", static_cast(OP_BazaarInspect)), - luabind::value("ClickDoor", static_cast(OP_ClickDoor)), - luabind::value("GroupAcknowledge", static_cast(OP_GroupAcknowledge)), - luabind::value("GroupDelete", static_cast(OP_GroupDelete)), - luabind::value("AdventureMerchantResponse", static_cast(OP_AdventureMerchantResponse)), - luabind::value("ShopEndConfirm", static_cast(OP_ShopEndConfirm)), - luabind::value("AdventureMerchantRequest", static_cast(OP_AdventureMerchantRequest)), - luabind::value("Sound", static_cast(OP_Sound)), - luabind::value("0x0193", static_cast(OP_0x0193)), - luabind::value("0x0347", static_cast(OP_0x0347)), - luabind::value("WorldComplete", static_cast(OP_WorldComplete)), - luabind::value("MobRename", static_cast(OP_MobRename)), - luabind::value("TaskDescription", static_cast(OP_TaskDescription)), - luabind::value("TaskActivity", static_cast(OP_TaskActivity)), - luabind::value("TaskMemberList", static_cast(OP_TaskMemberList)), - luabind::value("AnnoyingZoneUnknown", static_cast(OP_AnnoyingZoneUnknown)), - luabind::value("Some3ByteHPUpdate", static_cast(OP_Some3ByteHPUpdate)), - luabind::value("FloatListThing", static_cast(OP_FloatListThing)), - luabind::value("AAExpUpdate", static_cast(OP_AAExpUpdate)), - luabind::value("ForceFindPerson", static_cast(OP_ForceFindPerson)), - luabind::value("PlayMP3", static_cast(OP_PlayMP3)), - luabind::value("RequestClientZoneChange", static_cast(OP_RequestClientZoneChange)), - luabind::value("SomeItemPacketMaybe", static_cast(OP_SomeItemPacketMaybe)), - luabind::value("QueryResponseThing", static_cast(OP_QueryResponseThing)), - luabind::value("Some6ByteHPUpdate", static_cast(OP_Some6ByteHPUpdate)), - luabind::value("BankerChange", static_cast(OP_BankerChange)), - luabind::value("BecomeCorpse", static_cast(OP_BecomeCorpse)), - luabind::value("Action2", static_cast(OP_Action2)), - luabind::value("BazaarSearch", static_cast(OP_BazaarSearch)), - luabind::value("SetTitle", static_cast(OP_SetTitle)), - luabind::value("SetTitleReply", static_cast(OP_SetTitleReply)), - luabind::value("ConfirmDelete", static_cast(OP_ConfirmDelete)), - luabind::value("ConsentDeny", static_cast(OP_ConsentDeny)), - luabind::value("CrystalCountUpdate", static_cast(OP_CrystalCountUpdate)), - luabind::value("DeletePetition", static_cast(OP_DeletePetition)), - luabind::value("DenyResponse", static_cast(OP_DenyResponse)), - luabind::value("Disarm", static_cast(OP_Disarm)), - luabind::value("Feedback", static_cast(OP_Feedback)), - luabind::value("FriendsWho", static_cast(OP_FriendsWho)), - luabind::value("GMApproval", static_cast(OP_GMApproval)), - luabind::value("GMSearchCorpse", static_cast(OP_GMSearchCorpse)), - luabind::value("GuildBank", static_cast(OP_GuildBank)), - luabind::value("InitialHPUpdate", static_cast(OP_InitialHPUpdate)), - luabind::value("InitialMobHealth", static_cast(OP_InitialMobHealth)), - luabind::value("LFGGetMatchesRequest", static_cast(OP_LFGGetMatchesRequest)), - luabind::value("LFGGetMatchesResponse", static_cast(OP_LFGGetMatchesResponse)), - luabind::value("LFGResponse", static_cast(OP_LFGResponse)), - luabind::value("LFPCommand", static_cast(OP_LFPCommand)), - luabind::value("LFPGetMatchesRequest", static_cast(OP_LFPGetMatchesRequest)), - luabind::value("LFPGetMatchesResponse", static_cast(OP_LFPGetMatchesResponse)), - luabind::value("LeadershipExpToggle", static_cast(OP_LeadershipExpToggle)), - luabind::value("LeadershipExpUpdate", static_cast(OP_LeadershipExpUpdate)), - luabind::value("LoadSpellSet", static_cast(OP_LoadSpellSet)), - luabind::value("LockoutTimerInfo", static_cast(OP_LockoutTimerInfo)), - luabind::value("MendHPUpdate", static_cast(OP_MendHPUpdate)), - luabind::value("MobHealth", static_cast(OP_MobHealth)), - luabind::value("MoveLogDisregard", static_cast(OP_MoveLogDisregard)), - luabind::value("MoveLogRequest", static_cast(OP_MoveLogRequest)), - luabind::value("PetitionSearch", static_cast(OP_PetitionSearch)), - luabind::value("PetitionSearchResults", static_cast(OP_PetitionSearchResults)), - luabind::value("PetitionSearchText", static_cast(OP_PetitionSearchText)), - luabind::value("RaidInvite", static_cast(OP_RaidInvite)), - luabind::value("ReclaimCrystals", static_cast(OP_ReclaimCrystals)), - luabind::value("Report", static_cast(OP_Report)), - luabind::value("SenseHeading", static_cast(OP_SenseHeading)), - luabind::value("LDoNOpen", static_cast(OP_LDoNOpen)), - luabind::value("LDoNSenseTraps", static_cast(OP_LDoNSenseTraps)), - luabind::value("LDoNPickLock", static_cast(OP_LDoNPickLock)), - luabind::value("LDoNDisarmTraps", static_cast(OP_LDoNDisarmTraps)), - luabind::value("LDoNInspect", static_cast(OP_LDoNInspect)), - luabind::value("DynamicWall", static_cast(OP_DynamicWall)), - luabind::value("RequestTitles", static_cast(OP_RequestTitles)), - luabind::value("PurchaseLeadershipAA", static_cast(OP_PurchaseLeadershipAA)), - luabind::value("UpdateLeadershipAA", static_cast(OP_UpdateLeadershipAA)), - luabind::value("AdventurePointsUpdate", static_cast(OP_AdventurePointsUpdate)), - luabind::value("ZoneInUnknown", static_cast(OP_ZoneInUnknown)), - luabind::value("ZoneServerReady ", static_cast(OP_ZoneServerReady )), - luabind::value("ZoneGuildList", static_cast(OP_ZoneGuildList)), - luabind::value("SendTitleList", static_cast(OP_SendTitleList)), - luabind::value("NewTitlesAvailable", static_cast(OP_NewTitlesAvailable)), - luabind::value("Bandolier", static_cast(OP_Bandolier)), - luabind::value("OpenDiscordMerchant", static_cast(OP_OpenDiscordMerchant)), - luabind::value("DiscordMerchantInventory", static_cast(OP_DiscordMerchantInventory)), - luabind::value("GiveMoney", static_cast(OP_GiveMoney)), - luabind::value("OnLevelMessage", static_cast(OP_OnLevelMessage)), - luabind::value("RequestKnowledgeBase", static_cast(OP_RequestKnowledgeBase)), - luabind::value("KnowledgeBase", static_cast(OP_KnowledgeBase)), - luabind::value("VetRewardsAvaliable", static_cast(OP_VetRewardsAvaliable)), - luabind::value("VetClaimRequest", static_cast(OP_VetClaimRequest)), - luabind::value("VetClaimReply", static_cast(OP_VetClaimReply)), - luabind::value("WeaponEquip1", static_cast(OP_WeaponEquip1)), - luabind::value("PlayerStateAdd", static_cast(OP_PlayerStateAdd)), - luabind::value("PlayerStateRemove", static_cast(OP_PlayerStateRemove)), - luabind::value("WorldLogout", static_cast(OP_WorldLogout)), - luabind::value("SessionReady", static_cast(OP_SessionReady)), - luabind::value("Login", static_cast(OP_Login)), - luabind::value("ServerListRequest", static_cast(OP_ServerListRequest)), - luabind::value("PlayEverquestRequest", static_cast(OP_PlayEverquestRequest)), - luabind::value("ChatMessage", static_cast(OP_ChatMessage)), - luabind::value("LoginAccepted", static_cast(OP_LoginAccepted)), - luabind::value("ServerListResponse", static_cast(OP_ServerListResponse)), - luabind::value("Poll", static_cast(OP_Poll)), - luabind::value("PlayEverquestResponse", static_cast(OP_PlayEverquestResponse)), - luabind::value("EnterChat", static_cast(OP_EnterChat)), - luabind::value("PollResponse", static_cast(OP_PollResponse)), - luabind::value("Command", static_cast(OP_Command)), - luabind::value("ZonePlayerToBind", static_cast(OP_ZonePlayerToBind)), - luabind::value("AutoFire", static_cast(OP_AutoFire)), - luabind::value("Rewind", static_cast(OP_Rewind)), - luabind::value("OpenNewTasksWindow", static_cast(OP_OpenNewTasksWindow)), - luabind::value("TaskActivityComplete", static_cast(OP_TaskActivityComplete)), - luabind::value("AcceptNewTask", static_cast(OP_AcceptNewTask)), - luabind::value("CancelTask", static_cast(OP_CancelTask)), - luabind::value("TaskHistoryRequest", static_cast(OP_TaskHistoryRequest)), - luabind::value("TaskHistoryReply", static_cast(OP_TaskHistoryReply)), - luabind::value("PetBuffWindow", static_cast(OP_PetBuffWindow)), - luabind::value("RaidJoin", static_cast(OP_RaidJoin)), - luabind::value("Translocate", static_cast(OP_Translocate)), - luabind::value("Sacrifice", static_cast(OP_Sacrifice)), - luabind::value("KeyRing", static_cast(OP_KeyRing)), - luabind::value("PopupResponse", static_cast(OP_PopupResponse)), - luabind::value("DeleteCharge", static_cast(OP_DeleteCharge)), - luabind::value("PotionBelt", static_cast(OP_PotionBelt)), - luabind::value("Barter", static_cast(OP_Barter)), - luabind::value("VoiceMacroIn", static_cast(OP_VoiceMacroIn)), - luabind::value("VoiceMacroOut", static_cast(OP_VoiceMacroOut)), - luabind::value("WorldObjectsSent", static_cast(OP_WorldObjectsSent)), - luabind::value("BlockedBuffs", static_cast(OP_BlockedBuffs)), - luabind::value("RemoveBlockedBuffs", static_cast(OP_RemoveBlockedBuffs)), - luabind::value("ClearBlockedBuffs", static_cast(OP_ClearBlockedBuffs)), - luabind::value("GroupUpdateLeaderAA", static_cast(OP_GroupUpdateLeaderAA)), - luabind::value("MarkNPC", static_cast(OP_MarkNPC)), - luabind::value("ClearNPCMarks", static_cast(OP_ClearNPCMarks)), - luabind::value("DoGroupLeadershipAbility", static_cast(OP_DoGroupLeadershipAbility)), - luabind::value("DelegateAbility", static_cast(OP_DelegateAbility)), - luabind::value("SetGroupTarget", static_cast(OP_SetGroupTarget)), - luabind::value("ApplyPoison", static_cast(OP_ApplyPoison)), - luabind::value("FinishWindow", static_cast(OP_FinishWindow)), - luabind::value("FinishWindow2", static_cast(OP_FinishWindow2)), - luabind::value("ItemVerifyRequest", static_cast(OP_ItemVerifyRequest)), - luabind::value("ItemVerifyReply", static_cast(OP_ItemVerifyReply)), - luabind::value("GMTrainSkillConfirm", static_cast(OP_GMTrainSkillConfirm)), - luabind::value("RestState", static_cast(OP_RestState)), - luabind::value("AugmentInfo", static_cast(OP_AugmentInfo)), - luabind::value("PVPStats", static_cast(OP_PVPStats)), - luabind::value("PVPLeaderBoardRequest", static_cast(OP_PVPLeaderBoardRequest)), - luabind::value("PVPLeaderBoardReply", static_cast(OP_PVPLeaderBoardReply)), - luabind::value("PVPLeaderBoardDetailsRequest", static_cast(OP_PVPLeaderBoardDetailsRequest)), - luabind::value("PVPLeaderBoardDetailsReply", static_cast(OP_PVPLeaderBoardDetailsReply)), - luabind::value("DisciplineTimer", static_cast(OP_DisciplineTimer)), - luabind::value("RespawnWindow", static_cast(OP_RespawnWindow)), - luabind::value("AdventureMerchantSell", static_cast(OP_AdventureMerchantSell)), - luabind::value("AdventureStatsRequest", static_cast(OP_AdventureStatsRequest)), - luabind::value("AdventureStatsReply", static_cast(OP_AdventureStatsReply)), - luabind::value("AdventureLeaderboardRequest", static_cast(OP_AdventureLeaderboardRequest)), - luabind::value("AdventureLeaderboardReply", static_cast(OP_AdventureLeaderboardReply)), - luabind::value("SetStartCity", static_cast(OP_SetStartCity)), - luabind::value("LoginUnknown1", static_cast(OP_LoginUnknown1)), - luabind::value("LoginUnknown2", static_cast(OP_LoginUnknown2)), - luabind::value("ItemViewUnknown", static_cast(OP_ItemViewUnknown)), - luabind::value("GetGuildMOTDReply", static_cast(OP_GetGuildMOTDReply)), - luabind::value("SetGuildRank", static_cast(OP_SetGuildRank)), - luabind::value("SpawnPositionUpdate", static_cast(OP_SpawnPositionUpdate)), - luabind::value("ManaUpdate", static_cast(OP_ManaUpdate)), - luabind::value("EnduranceUpdate", static_cast(OP_EnduranceUpdate)), - luabind::value("MobManaUpdate", static_cast(OP_MobManaUpdate)), - luabind::value("MobEnduranceUpdate", static_cast(OP_MobEnduranceUpdate)), - luabind::value("GroupUpdateB", static_cast(OP_GroupUpdateB)), - luabind::value("GroupDisbandYou", static_cast(OP_GroupDisbandYou)), - luabind::value("GroupDisbandOther", static_cast(OP_GroupDisbandOther)), - luabind::value("GroupLeaderChange", static_cast(OP_GroupLeaderChange)), - luabind::value("GroupLeadershipAAUpdate", static_cast(OP_GroupLeadershipAAUpdate)), - luabind::value("GroupRoles", static_cast(OP_GroupRoles)), - luabind::value("SendFindableNPCs", static_cast(OP_SendFindableNPCs)), - luabind::value("HideCorpse", static_cast(OP_HideCorpse)), - luabind::value("TargetBuffs", static_cast(OP_TargetBuffs)), - luabind::value("TradeBusy", static_cast(OP_TradeBusy)), - luabind::value("GuildUpdateURLAndChannel", static_cast(OP_GuildUpdateURLAndChannel)), - luabind::value("CameraEffect", static_cast(OP_CameraEffect)), - luabind::value("SpellEffect", static_cast(OP_SpellEffect)), - luabind::value("DzQuit", static_cast(OP_DzQuit)), - luabind::value("DzListTimers", static_cast(OP_DzListTimers)), - luabind::value("DzPlayerList", static_cast(OP_DzPlayerList)), - luabind::value("DzAddPlayer", static_cast(OP_DzAddPlayer)), - luabind::value("DzRemovePlayer", static_cast(OP_DzRemovePlayer)), - luabind::value("DzSwapPlayer", static_cast(OP_DzSwapPlayer)), - luabind::value("DzMakeLeader", static_cast(OP_DzMakeLeader)), - luabind::value("DzExpeditionInvite", static_cast(OP_DzExpeditionInvite)), - luabind::value("DzExpeditionInviteResponse", static_cast(OP_DzExpeditionInviteResponse)), - luabind::value("DzExpeditionInfo", static_cast(OP_DzExpeditionInfo)), - luabind::value("DzMemberListName", static_cast(OP_DzMemberListName)), - luabind::value("DzMemberListStatus", static_cast(OP_DzMemberListStatus)), - luabind::value("DzSetLeaderName", static_cast(OP_DzSetLeaderName)), - luabind::value("DzExpeditionEndsWarning", static_cast(OP_DzExpeditionEndsWarning)), - luabind::value("DzExpeditionLockoutTimers", static_cast(OP_DzExpeditionLockoutTimers)), - luabind::value("DzMemberList", static_cast(OP_DzMemberList)), - luabind::value("DzCompass", static_cast(OP_DzCompass)), - luabind::value("DzChooseZone", static_cast(OP_DzChooseZone)), - luabind::value("DzChooseZoneReply", static_cast(OP_DzChooseZoneReply)), - luabind::value("BuffCreate", static_cast(OP_BuffCreate)), - luabind::value("GuildStatus", static_cast(OP_GuildStatus)), - luabind::value("BuffRemoveRequest", static_cast(OP_BuffRemoveRequest)), - luabind::value("CorpseDrag", static_cast(OP_CorpseDrag)), - luabind::value("CorpseDrop", static_cast(OP_CorpseDrop)), - luabind::value("ChangeSize", static_cast(OP_ChangeSize)), - luabind::value("GroupMakeLeader", static_cast(OP_GroupMakeLeader)), - luabind::value("RemoveAllDoors", static_cast(OP_RemoveAllDoors)), - luabind::value("RemoveNimbusEffect", static_cast(OP_RemoveNimbusEffect)), - luabind::value("GuildCreate", static_cast(OP_GuildCreate)), - luabind::value("AltCurrency", static_cast(OP_AltCurrency)), - luabind::value("FellowshipUpdate", static_cast(OP_FellowshipUpdate)), - luabind::value("AltCurrencyMerchantRequest", static_cast(OP_AltCurrencyMerchantRequest)), - luabind::value("AltCurrencyMerchantReply", static_cast(OP_AltCurrencyMerchantReply)), - luabind::value("AltCurrencyPurchase", static_cast(OP_AltCurrencyPurchase)), - luabind::value("AltCurrencySellSelection", static_cast(OP_AltCurrencySellSelection)), - luabind::value("AltCurrencyReclaim", static_cast(OP_AltCurrencyReclaim)), - luabind::value("AltCurrencySell", static_cast(OP_AltCurrencySell)), - luabind::value("Untargetable", static_cast(OP_Untargetable)), - luabind::value("CrystalReclaim", static_cast(OP_CrystalReclaim)), - luabind::value("CrystalCreate", static_cast(OP_CrystalCreate)), - luabind::value("SendMaxCharacters", static_cast(OP_SendMaxCharacters)), - luabind::value("SendMembership", static_cast(OP_SendMembership)), - luabind::value("SendMembershipDetails", static_cast(OP_SendMembershipDetails)), - luabind::value("LFGuild", static_cast(OP_LFGuild)), - luabind::value("XTargetRequest", static_cast(OP_XTargetRequest)), - luabind::value("XTargetResponse", static_cast(OP_XTargetResponse)), - luabind::value("XTargetAutoAddHaters", static_cast(OP_XTargetAutoAddHaters)), - luabind::value("Weblink", static_cast(OP_Weblink)), - luabind::value("InspectMessageUpdate", static_cast(OP_InspectMessageUpdate)), - luabind::value("ItemPreview", static_cast(OP_ItemPreview)), - luabind::value("MercenaryDataRequest", static_cast(OP_MercenaryDataRequest)), - luabind::value("MercenaryDataResponse", static_cast(OP_MercenaryDataResponse)), - luabind::value("MercenaryHire", static_cast(OP_MercenaryHire)), - luabind::value("MercenaryUnknown1", static_cast(OP_MercenaryUnknown1)), - luabind::value("MercenaryTimer", static_cast(OP_MercenaryTimer)), - luabind::value("MercenaryAssign", static_cast(OP_MercenaryAssign)), - luabind::value("MercenaryDataUpdate", static_cast(OP_MercenaryDataUpdate)), - luabind::value("MercenaryCommand", static_cast(OP_MercenaryCommand)), - luabind::value("MercenarySuspendRequest", static_cast(OP_MercenarySuspendRequest)), - luabind::value("MercenarySuspendResponse", static_cast(OP_MercenarySuspendResponse)), - luabind::value("MercenaryUnsuspendResponse", static_cast(OP_MercenaryUnsuspendResponse)), - luabind::value("MercenaryDataUpdateRequest", static_cast(OP_MercenaryDataUpdateRequest)), - luabind::value("MercenaryDismiss", static_cast(OP_MercenaryDismiss)), - luabind::value("MercenaryTimerRequest", static_cast(OP_MercenaryTimerRequest)), - luabind::value("OpenInventory", static_cast(OP_OpenInventory)), - luabind::value("OpenContainer", static_cast(OP_OpenContainer)), - luabind::value("Marquee", static_cast(OP_Marquee)), - luabind::value("ClientTimeStamp", static_cast(OP_ClientTimeStamp)), - luabind::value("GuildPromote", static_cast(OP_GuildPromote)), - luabind::value("Fling", static_cast(OP_Fling)) - ]; + .enum_("constants") + [ + luabind::value("ExploreUnknown", static_cast(OP_ExploreUnknown)), + luabind::value("Heartbeat", static_cast(OP_Heartbeat)), + luabind::value("ReloadUI", static_cast(OP_ReloadUI)), + luabind::value("IncreaseStats", static_cast(OP_IncreaseStats)), + luabind::value("ApproveZone", static_cast(OP_ApproveZone)), + luabind::value("Dye", static_cast(OP_Dye)), + luabind::value("Stamina", static_cast(OP_Stamina)), + luabind::value("ControlBoat", static_cast(OP_ControlBoat)), + luabind::value("MobUpdate", static_cast(OP_MobUpdate)), + luabind::value("ClientUpdate", static_cast(OP_ClientUpdate)), + luabind::value("ChannelMessage", static_cast(OP_ChannelMessage)), + luabind::value("SimpleMessage", static_cast(OP_SimpleMessage)), + luabind::value("FormattedMessage", static_cast(OP_FormattedMessage)), + luabind::value("TGB", static_cast(OP_TGB)), + luabind::value("Bind_Wound", static_cast(OP_Bind_Wound)), + luabind::value("Charm", static_cast(OP_Charm)), + luabind::value("Begging", static_cast(OP_Begging)), + luabind::value("MoveCoin", static_cast(OP_MoveCoin)), + luabind::value("SpawnDoor", static_cast(OP_SpawnDoor)), + luabind::value("Sneak", static_cast(OP_Sneak)), + luabind::value("ExpUpdate", static_cast(OP_ExpUpdate)), + luabind::value("DumpName", static_cast(OP_DumpName)), + luabind::value("RespondAA", static_cast(OP_RespondAA)), + luabind::value("UpdateAA", static_cast(OP_UpdateAA)), + luabind::value("SendAAStats", static_cast(OP_SendAAStats)), + luabind::value("SendAATable", static_cast(OP_SendAATable)), + luabind::value("AAAction", static_cast(OP_AAAction)), + luabind::value("BoardBoat", static_cast(OP_BoardBoat)), + luabind::value("LeaveBoat", static_cast(OP_LeaveBoat)), + luabind::value("AdventureInfoRequest", static_cast(OP_AdventureInfoRequest)), + luabind::value("AdventureInfo", static_cast(OP_AdventureInfo)), + luabind::value("AdventureRequest", static_cast(OP_AdventureRequest)), + luabind::value("AdventureDetails", static_cast(OP_AdventureDetails)), + luabind::value("LDoNButton", static_cast(OP_LDoNButton)), + luabind::value("AdventureData", static_cast(OP_AdventureData)), + luabind::value("AdventureFinish", static_cast(OP_AdventureFinish)), + luabind::value("LeaveAdventure", static_cast(OP_LeaveAdventure)), + luabind::value("AdventureUpdate", static_cast(OP_AdventureUpdate)), + luabind::value("SendExpZonein", static_cast(OP_SendExpZonein)), + luabind::value("RaidUpdate", static_cast(OP_RaidUpdate)), + luabind::value("GuildLeader", static_cast(OP_GuildLeader)), + luabind::value("GuildPeace", static_cast(OP_GuildPeace)), + luabind::value("GuildRemove", static_cast(OP_GuildRemove)), + luabind::value("GuildMemberList", static_cast(OP_GuildMemberList)), + luabind::value("GuildMemberUpdate", static_cast(OP_GuildMemberUpdate)), + luabind::value("GuildMemberLevelUpdate", static_cast(OP_GuildMemberLevelUpdate)), + luabind::value("GuildInvite", static_cast(OP_GuildInvite)), + luabind::value("GuildMOTD", static_cast(OP_GuildMOTD)), + luabind::value("SetGuildMOTD", static_cast(OP_SetGuildMOTD)), + luabind::value("GuildPublicNote", static_cast(OP_GuildPublicNote)), + luabind::value("GetGuildsList", static_cast(OP_GetGuildsList)), + luabind::value("GuildDemote", static_cast(OP_GuildDemote)), + luabind::value("GuildInviteAccept", static_cast(OP_GuildInviteAccept)), + luabind::value("GuildWar", static_cast(OP_GuildWar)), + luabind::value("GuildDelete", static_cast(OP_GuildDelete)), + luabind::value("GuildManageRemove", static_cast(OP_GuildManageRemove)), + luabind::value("GuildManageAdd", static_cast(OP_GuildManageAdd)), + luabind::value("GuildManageStatus", static_cast(OP_GuildManageStatus)), + luabind::value("GuildManageBanker", static_cast(OP_GuildManageBanker)), + luabind::value("GetGuildMOTD", static_cast(OP_GetGuildMOTD)), + luabind::value("Trader", static_cast(OP_Trader)), + luabind::value("Bazaar", static_cast(OP_Bazaar)), + luabind::value("BecomeTrader", static_cast(OP_BecomeTrader)), + luabind::value("TraderItemUpdate", static_cast(OP_TraderItemUpdate)), + luabind::value("TraderShop", static_cast(OP_TraderShop)), + luabind::value("TraderBuy", static_cast(OP_TraderBuy)), + luabind::value("PetCommands", static_cast(OP_PetCommands)), + luabind::value("TradeSkillCombine", static_cast(OP_TradeSkillCombine)), + luabind::value("AugmentItem", static_cast(OP_AugmentItem)), + luabind::value("ItemName", static_cast(OP_ItemName)), + luabind::value("ShopItem", static_cast(OP_ShopItem)), + luabind::value("ShopPlayerBuy", static_cast(OP_ShopPlayerBuy)), + luabind::value("ShopPlayerSell", static_cast(OP_ShopPlayerSell)), + luabind::value("ShopDelItem", static_cast(OP_ShopDelItem)), + luabind::value("ShopRequest", static_cast(OP_ShopRequest)), + luabind::value("ShopEnd", static_cast(OP_ShopEnd)), + luabind::value("LFGCommand", static_cast(OP_LFGCommand)), + luabind::value("LFGAppearance", static_cast(OP_LFGAppearance)), + luabind::value("GroupUpdate", static_cast(OP_GroupUpdate)), + luabind::value("GroupInvite", static_cast(OP_GroupInvite)), + luabind::value("GroupDisband", static_cast(OP_GroupDisband)), + luabind::value("GroupInvite2", static_cast(OP_GroupInvite2)), + luabind::value("GroupFollow", static_cast(OP_GroupFollow)), + luabind::value("GroupFollow2", static_cast(OP_GroupFollow2)), + luabind::value("GroupCancelInvite", static_cast(OP_GroupCancelInvite)), + luabind::value("CustomTitles", static_cast(OP_CustomTitles)), + luabind::value("Split", static_cast(OP_Split)), + luabind::value("Jump", static_cast(OP_Jump)), + luabind::value("ConsiderCorpse", static_cast(OP_ConsiderCorpse)), + luabind::value("SkillUpdate", static_cast(OP_SkillUpdate)), + luabind::value("GMEndTrainingResponse", static_cast(OP_GMEndTrainingResponse)), + luabind::value("GMEndTraining", static_cast(OP_GMEndTraining)), + luabind::value("GMTrainSkill", static_cast(OP_GMTrainSkill)), + luabind::value("GMTraining", static_cast(OP_GMTraining)), + luabind::value("DeleteItem", static_cast(OP_DeleteItem)), + luabind::value("CombatAbility", static_cast(OP_CombatAbility)), + luabind::value("TrackUnknown", static_cast(OP_TrackUnknown)), + luabind::value("TrackTarget", static_cast(OP_TrackTarget)), + luabind::value("Track", static_cast(OP_Track)), + luabind::value("ItemLinkClick", static_cast(OP_ItemLinkClick)), + luabind::value("ItemLinkResponse", static_cast(OP_ItemLinkResponse)), + luabind::value("ItemLinkText", static_cast(OP_ItemLinkText)), + luabind::value("RezzAnswer", static_cast(OP_RezzAnswer)), + luabind::value("RezzComplete", static_cast(OP_RezzComplete)), + luabind::value("SendZonepoints", static_cast(OP_SendZonepoints)), + luabind::value("SetRunMode", static_cast(OP_SetRunMode)), + luabind::value("InspectRequest", static_cast(OP_InspectRequest)), + luabind::value("InspectAnswer", static_cast(OP_InspectAnswer)), + luabind::value("SenseTraps", static_cast(OP_SenseTraps)), + luabind::value("DisarmTraps", static_cast(OP_DisarmTraps)), + luabind::value("Assist", static_cast(OP_Assist)), + luabind::value("AssistGroup", static_cast(OP_AssistGroup)), + luabind::value("PickPocket", static_cast(OP_PickPocket)), + luabind::value("LootRequest", static_cast(OP_LootRequest)), + luabind::value("EndLootRequest", static_cast(OP_EndLootRequest)), + luabind::value("MoneyOnCorpse", static_cast(OP_MoneyOnCorpse)), + luabind::value("LootComplete", static_cast(OP_LootComplete)), + luabind::value("LootItem", static_cast(OP_LootItem)), + luabind::value("MoveItem", static_cast(OP_MoveItem)), + luabind::value("WhoAllRequest", static_cast(OP_WhoAllRequest)), + luabind::value("WhoAllResponse", static_cast(OP_WhoAllResponse)), + luabind::value("Consume", static_cast(OP_Consume)), + luabind::value("AutoAttack", static_cast(OP_AutoAttack)), + luabind::value("AutoAttack2", static_cast(OP_AutoAttack2)), + luabind::value("TargetMouse", static_cast(OP_TargetMouse)), + luabind::value("TargetCommand", static_cast(OP_TargetCommand)), + luabind::value("TargetReject", static_cast(OP_TargetReject)), + luabind::value("TargetHoTT", static_cast(OP_TargetHoTT)), + luabind::value("Hide", static_cast(OP_Hide)), + luabind::value("Forage", static_cast(OP_Forage)), + luabind::value("Fishing", static_cast(OP_Fishing)), + luabind::value("Bug", static_cast(OP_Bug)), + luabind::value("Emote", static_cast(OP_Emote)), + luabind::value("Consider", static_cast(OP_Consider)), + luabind::value("FaceChange", static_cast(OP_FaceChange)), + luabind::value("RandomReq", static_cast(OP_RandomReq)), + luabind::value("RandomReply", static_cast(OP_RandomReply)), + luabind::value("Camp", static_cast(OP_Camp)), + luabind::value("YellForHelp", static_cast(OP_YellForHelp)), + luabind::value("SafePoint", static_cast(OP_SafePoint)), + luabind::value("Buff", static_cast(OP_Buff)), + luabind::value("ColoredText", static_cast(OP_ColoredText)), + luabind::value("SpecialMesg", static_cast(OP_SpecialMesg)), + luabind::value("Consent", static_cast(OP_Consent)), + luabind::value("ConsentResponse", static_cast(OP_ConsentResponse)), + luabind::value("Stun", static_cast(OP_Stun)), + luabind::value("BeginCast", static_cast(OP_BeginCast)), + luabind::value("CastSpell", static_cast(OP_CastSpell)), + luabind::value("InterruptCast", static_cast(OP_InterruptCast)), + luabind::value("Death", static_cast(OP_Death)), + luabind::value("FeignDeath", static_cast(OP_FeignDeath)), + luabind::value("Illusion", static_cast(OP_Illusion)), + luabind::value("LevelUpdate", static_cast(OP_LevelUpdate)), + luabind::value("LevelAppearance", static_cast(OP_LevelAppearance)), + luabind::value("MemorizeSpell", static_cast(OP_MemorizeSpell)), + luabind::value("HPUpdate", static_cast(OP_HPUpdate)), + luabind::value("Mend", static_cast(OP_Mend)), + luabind::value("Taunt", static_cast(OP_Taunt)), + luabind::value("GMDelCorpse", static_cast(OP_GMDelCorpse)), + luabind::value("GMFind", static_cast(OP_GMFind)), + luabind::value("GMServers", static_cast(OP_GMServers)), + luabind::value("GMGoto", static_cast(OP_GMGoto)), + luabind::value("GMSummon", static_cast(OP_GMSummon)), + luabind::value("GMKill", static_cast(OP_GMKill)), + luabind::value("GMLastName", static_cast(OP_GMLastName)), + luabind::value("GMToggle", static_cast(OP_GMToggle)), + luabind::value("GMEmoteZone", static_cast(OP_GMEmoteZone)), + luabind::value("GMBecomeNPC", static_cast(OP_GMBecomeNPC)), + luabind::value("GMHideMe", static_cast(OP_GMHideMe)), + luabind::value("GMZoneRequest", static_cast(OP_GMZoneRequest)), + luabind::value("GMZoneRequest2", static_cast(OP_GMZoneRequest2)), + luabind::value("Petition", static_cast(OP_Petition)), + luabind::value("PetitionRefresh", static_cast(OP_PetitionRefresh)), + luabind::value("PDeletePetition", static_cast(OP_PDeletePetition)), + luabind::value("PetitionBug", static_cast(OP_PetitionBug)), + luabind::value("PetitionUpdate", static_cast(OP_PetitionUpdate)), + luabind::value("PetitionCheckout", static_cast(OP_PetitionCheckout)), + luabind::value("PetitionCheckout2", static_cast(OP_PetitionCheckout2)), + luabind::value("PetitionDelete", static_cast(OP_PetitionDelete)), + luabind::value("PetitionResolve", static_cast(OP_PetitionResolve)), + luabind::value("PetitionCheckIn", static_cast(OP_PetitionCheckIn)), + luabind::value("PetitionUnCheckout", static_cast(OP_PetitionUnCheckout)), + luabind::value("PetitionQue", static_cast(OP_PetitionQue)), + luabind::value("SetServerFilter", static_cast(OP_SetServerFilter)), + luabind::value("NewSpawn", static_cast(OP_NewSpawn)), + luabind::value("Animation", static_cast(OP_Animation)), + luabind::value("ZoneChange", static_cast(OP_ZoneChange)), + luabind::value("DeleteSpawn", static_cast(OP_DeleteSpawn)), + luabind::value("EnvDamage", static_cast(OP_EnvDamage)), + luabind::value("Action", static_cast(OP_Action)), + luabind::value("Damage", static_cast(OP_Damage)), + luabind::value("ManaChange", static_cast(OP_ManaChange)), + luabind::value("ClientError", static_cast(OP_ClientError)), + luabind::value("Save", static_cast(OP_Save)), + luabind::value("LocInfo", static_cast(OP_LocInfo)), + luabind::value("Surname", static_cast(OP_Surname)), + luabind::value("ClearSurname", static_cast(OP_ClearSurname)), + luabind::value("SwapSpell", static_cast(OP_SwapSpell)), + luabind::value("DeleteSpell", static_cast(OP_DeleteSpell)), + luabind::value("CloseContainer", static_cast(OP_CloseContainer)), + luabind::value("ClickObjectAction", static_cast(OP_ClickObjectAction)), + luabind::value("GroundSpawn", static_cast(OP_GroundSpawn)), + luabind::value("ClearObject", static_cast(OP_ClearObject)), + luabind::value("ZoneUnavail", static_cast(OP_ZoneUnavail)), + luabind::value("ItemPacket", static_cast(OP_ItemPacket)), + luabind::value("TradeRequest", static_cast(OP_TradeRequest)), + luabind::value("TradeRequestAck", static_cast(OP_TradeRequestAck)), + luabind::value("TradeAcceptClick", static_cast(OP_TradeAcceptClick)), + luabind::value("TradeMoneyUpdate", static_cast(OP_TradeMoneyUpdate)), + luabind::value("TradeCoins", static_cast(OP_TradeCoins)), + luabind::value("CancelTrade", static_cast(OP_CancelTrade)), + luabind::value("FinishTrade", static_cast(OP_FinishTrade)), + luabind::value("SaveOnZoneReq", static_cast(OP_SaveOnZoneReq)), + luabind::value("Logout", static_cast(OP_Logout)), + luabind::value("LogoutReply", static_cast(OP_LogoutReply)), + luabind::value("PreLogoutReply", static_cast(OP_PreLogoutReply)), + luabind::value("DuelAccept", static_cast(OP_DuelAccept)), + luabind::value("InstillDoubt", static_cast(OP_InstillDoubt)), + luabind::value("SafeFallSuccess", static_cast(OP_SafeFallSuccess)), + luabind::value("DisciplineUpdate", static_cast(OP_DisciplineUpdate)), + luabind::value("SendGuildTributes", static_cast(OP_SendGuildTributes)), + luabind::value("SendTributes", static_cast(OP_SendTributes)), + luabind::value("TributeUpdate", static_cast(OP_TributeUpdate)), + luabind::value("TributeItem", static_cast(OP_TributeItem)), + luabind::value("TributePointUpdate", static_cast(OP_TributePointUpdate)), + luabind::value("TributeInfo", static_cast(OP_TributeInfo)), + luabind::value("GuildTributeInfo", static_cast(OP_GuildTributeInfo)), + luabind::value("OpenGuildTributeMaster", static_cast(OP_OpenGuildTributeMaster)), + luabind::value("OpenTributeMaster", static_cast(OP_OpenTributeMaster)), + luabind::value("TributeTimer", static_cast(OP_TributeTimer)), + luabind::value("SelectTribute", static_cast(OP_SelectTribute)), + luabind::value("TributeNPC", static_cast(OP_TributeNPC)), + luabind::value("TributeMoney", static_cast(OP_TributeMoney)), + luabind::value("TributeToggle", static_cast(OP_TributeToggle)), + luabind::value("CloseTributeMaster", static_cast(OP_CloseTributeMaster)), + luabind::value("RecipesFavorite", static_cast(OP_RecipesFavorite)), + luabind::value("RecipesSearch", static_cast(OP_RecipesSearch)), + luabind::value("RecipeReply", static_cast(OP_RecipeReply)), + luabind::value("RecipeDetails", static_cast(OP_RecipeDetails)), + luabind::value("RecipeAutoCombine", static_cast(OP_RecipeAutoCombine)), + luabind::value("Shielding", static_cast(OP_Shielding)), + luabind::value("FindPersonRequest", static_cast(OP_FindPersonRequest)), + luabind::value("FindPersonReply", static_cast(OP_FindPersonReply)), + luabind::value("ZoneEntry", static_cast(OP_ZoneEntry)), + luabind::value("PlayerProfile", static_cast(OP_PlayerProfile)), + luabind::value("CharInventory", static_cast(OP_CharInventory)), + luabind::value("ZoneSpawns", static_cast(OP_ZoneSpawns)), + luabind::value("Weather", static_cast(OP_Weather)), + luabind::value("ReqNewZone", static_cast(OP_ReqNewZone)), + luabind::value("NewZone", static_cast(OP_NewZone)), + luabind::value("ReqClientSpawn", static_cast(OP_ReqClientSpawn)), + luabind::value("SpawnAppearance", static_cast(OP_SpawnAppearance)), + luabind::value("ClientReady", static_cast(OP_ClientReady)), + luabind::value("ZoneComplete", static_cast(OP_ZoneComplete)), + luabind::value("ApproveWorld", static_cast(OP_ApproveWorld)), + luabind::value("LogServer", static_cast(OP_LogServer)), + luabind::value("MOTD", static_cast(OP_MOTD)), + luabind::value("SendLoginInfo", static_cast(OP_SendLoginInfo)), + luabind::value("DeleteCharacter", static_cast(OP_DeleteCharacter)), + luabind::value("SendCharInfo", static_cast(OP_SendCharInfo)), + luabind::value("ExpansionInfo", static_cast(OP_ExpansionInfo)), + luabind::value("CharacterCreate", static_cast(OP_CharacterCreate)), + luabind::value("CharacterCreateRequest", static_cast(OP_CharacterCreateRequest)), + luabind::value("RandomNameGenerator", static_cast(OP_RandomNameGenerator)), + luabind::value("GuildsList", static_cast(OP_GuildsList)), + luabind::value("ApproveName", static_cast(OP_ApproveName)), + luabind::value("EnterWorld", static_cast(OP_EnterWorld)), + luabind::value("PostEnterWorld ", static_cast(OP_PostEnterWorld )), + luabind::value("SendSystemStats", static_cast(OP_SendSystemStats)), + luabind::value("World_Client_CRC1", static_cast(OP_World_Client_CRC1)), + luabind::value("World_Client_CRC2", static_cast(OP_World_Client_CRC2)), + luabind::value("SetChatServer", static_cast(OP_SetChatServer)), + luabind::value("SetChatServer2", static_cast(OP_SetChatServer2)), + luabind::value("ZoneServerInfo", static_cast(OP_ZoneServerInfo)), + luabind::value("WorldClientReady", static_cast(OP_WorldClientReady)), + luabind::value("WorldUnknown001", static_cast(OP_WorldUnknown001)), + luabind::value("AckPacket", static_cast(OP_AckPacket)), + luabind::value("WearChange", static_cast(OP_WearChange)), + luabind::value("CrashDump", static_cast(OP_CrashDump)), + luabind::value("LoginComplete", static_cast(OP_LoginComplete)), + luabind::value("GMNameChange", static_cast(OP_GMNameChange)), + luabind::value("ReadBook", static_cast(OP_ReadBook)), + luabind::value("GMKick", static_cast(OP_GMKick)), + luabind::value("RezzRequest", static_cast(OP_RezzRequest)), + luabind::value("MultiLineMsg", static_cast(OP_MultiLineMsg)), + luabind::value("TimeOfDay", static_cast(OP_TimeOfDay)), + luabind::value("CompletedTasks", static_cast(OP_CompletedTasks)), + luabind::value("MoneyUpdate", static_cast(OP_MoneyUpdate)), + luabind::value("ClickObject", static_cast(OP_ClickObject)), + luabind::value("MoveDoor", static_cast(OP_MoveDoor)), + luabind::value("TraderDelItem", static_cast(OP_TraderDelItem)), + luabind::value("AdventureMerchantPurchase", static_cast(OP_AdventureMerchantPurchase)), + luabind::value("TestBuff", static_cast(OP_TestBuff)), + luabind::value("DuelDecline", static_cast(OP_DuelDecline)), + luabind::value("RequestDuel", static_cast(OP_RequestDuel)), + luabind::value("BazaarInspect", static_cast(OP_BazaarInspect)), + luabind::value("ClickDoor", static_cast(OP_ClickDoor)), + luabind::value("GroupAcknowledge", static_cast(OP_GroupAcknowledge)), + luabind::value("GroupDelete", static_cast(OP_GroupDelete)), + luabind::value("AdventureMerchantResponse", static_cast(OP_AdventureMerchantResponse)), + luabind::value("ShopEndConfirm", static_cast(OP_ShopEndConfirm)), + luabind::value("AdventureMerchantRequest", static_cast(OP_AdventureMerchantRequest)), + luabind::value("Sound", static_cast(OP_Sound)), + luabind::value("0x0193", static_cast(OP_0x0193)), + luabind::value("0x0347", static_cast(OP_0x0347)), + luabind::value("WorldComplete", static_cast(OP_WorldComplete)), + luabind::value("MobRename", static_cast(OP_MobRename)), + luabind::value("TaskDescription", static_cast(OP_TaskDescription)), + luabind::value("TaskActivity", static_cast(OP_TaskActivity)), + luabind::value("SharedTaskPlayerList", static_cast(OP_SharedTaskPlayerList)), + luabind::value("AnnoyingZoneUnknown", static_cast(OP_AnnoyingZoneUnknown)), + luabind::value("Some3ByteHPUpdate", static_cast(OP_Some3ByteHPUpdate)), + luabind::value("FloatListThing", static_cast(OP_FloatListThing)), + luabind::value("AAExpUpdate", static_cast(OP_AAExpUpdate)), + luabind::value("ForceFindPerson", static_cast(OP_ForceFindPerson)), + luabind::value("PlayMP3", static_cast(OP_PlayMP3)), + luabind::value("RequestClientZoneChange", static_cast(OP_RequestClientZoneChange)), + luabind::value("SomeItemPacketMaybe", static_cast(OP_SomeItemPacketMaybe)), + luabind::value("QueryResponseThing", static_cast(OP_QueryResponseThing)), + luabind::value("Some6ByteHPUpdate", static_cast(OP_Some6ByteHPUpdate)), + luabind::value("BankerChange", static_cast(OP_BankerChange)), + luabind::value("BecomeCorpse", static_cast(OP_BecomeCorpse)), + luabind::value("Action2", static_cast(OP_Action2)), + luabind::value("BazaarSearch", static_cast(OP_BazaarSearch)), + luabind::value("SetTitle", static_cast(OP_SetTitle)), + luabind::value("SetTitleReply", static_cast(OP_SetTitleReply)), + luabind::value("ConfirmDelete", static_cast(OP_ConfirmDelete)), + luabind::value("ConsentDeny", static_cast(OP_ConsentDeny)), + luabind::value("CrystalCountUpdate", static_cast(OP_CrystalCountUpdate)), + luabind::value("DeletePetition", static_cast(OP_DeletePetition)), + luabind::value("DenyResponse", static_cast(OP_DenyResponse)), + luabind::value("Disarm", static_cast(OP_Disarm)), + luabind::value("Feedback", static_cast(OP_Feedback)), + luabind::value("FriendsWho", static_cast(OP_FriendsWho)), + luabind::value("GMApproval", static_cast(OP_GMApproval)), + luabind::value("GMSearchCorpse", static_cast(OP_GMSearchCorpse)), + luabind::value("GuildBank", static_cast(OP_GuildBank)), + luabind::value("InitialHPUpdate", static_cast(OP_InitialHPUpdate)), + luabind::value("InitialMobHealth", static_cast(OP_InitialMobHealth)), + luabind::value("LFGGetMatchesRequest", static_cast(OP_LFGGetMatchesRequest)), + luabind::value("LFGGetMatchesResponse", static_cast(OP_LFGGetMatchesResponse)), + luabind::value("LFGResponse", static_cast(OP_LFGResponse)), + luabind::value("LFPCommand", static_cast(OP_LFPCommand)), + luabind::value("LFPGetMatchesRequest", static_cast(OP_LFPGetMatchesRequest)), + luabind::value("LFPGetMatchesResponse", static_cast(OP_LFPGetMatchesResponse)), + luabind::value("LeadershipExpToggle", static_cast(OP_LeadershipExpToggle)), + luabind::value("LeadershipExpUpdate", static_cast(OP_LeadershipExpUpdate)), + luabind::value("LoadSpellSet", static_cast(OP_LoadSpellSet)), + luabind::value("LockoutTimerInfo", static_cast(OP_LockoutTimerInfo)), + luabind::value("MendHPUpdate", static_cast(OP_MendHPUpdate)), + luabind::value("MobHealth", static_cast(OP_MobHealth)), + luabind::value("MoveLogDisregard", static_cast(OP_MoveLogDisregard)), + luabind::value("MoveLogRequest", static_cast(OP_MoveLogRequest)), + luabind::value("PetitionSearch", static_cast(OP_PetitionSearch)), + luabind::value("PetitionSearchResults", static_cast(OP_PetitionSearchResults)), + luabind::value("PetitionSearchText", static_cast(OP_PetitionSearchText)), + luabind::value("RaidInvite", static_cast(OP_RaidInvite)), + luabind::value("ReclaimCrystals", static_cast(OP_ReclaimCrystals)), + luabind::value("Report", static_cast(OP_Report)), + luabind::value("SenseHeading", static_cast(OP_SenseHeading)), + luabind::value("LDoNOpen", static_cast(OP_LDoNOpen)), + luabind::value("LDoNSenseTraps", static_cast(OP_LDoNSenseTraps)), + luabind::value("LDoNPickLock", static_cast(OP_LDoNPickLock)), + luabind::value("LDoNDisarmTraps", static_cast(OP_LDoNDisarmTraps)), + luabind::value("LDoNInspect", static_cast(OP_LDoNInspect)), + luabind::value("DynamicWall", static_cast(OP_DynamicWall)), + luabind::value("RequestTitles", static_cast(OP_RequestTitles)), + luabind::value("PurchaseLeadershipAA", static_cast(OP_PurchaseLeadershipAA)), + luabind::value("UpdateLeadershipAA", static_cast(OP_UpdateLeadershipAA)), + luabind::value("AdventurePointsUpdate", static_cast(OP_AdventurePointsUpdate)), + luabind::value("ZoneInUnknown", static_cast(OP_ZoneInUnknown)), + luabind::value("ZoneServerReady ", static_cast(OP_ZoneServerReady )), + luabind::value("ZoneGuildList", static_cast(OP_ZoneGuildList)), + luabind::value("SendTitleList", static_cast(OP_SendTitleList)), + luabind::value("NewTitlesAvailable", static_cast(OP_NewTitlesAvailable)), + luabind::value("Bandolier", static_cast(OP_Bandolier)), + luabind::value("OpenDiscordMerchant", static_cast(OP_OpenDiscordMerchant)), + luabind::value("DiscordMerchantInventory", static_cast(OP_DiscordMerchantInventory)), + luabind::value("GiveMoney", static_cast(OP_GiveMoney)), + luabind::value("OnLevelMessage", static_cast(OP_OnLevelMessage)), + luabind::value("RequestKnowledgeBase", static_cast(OP_RequestKnowledgeBase)), + luabind::value("KnowledgeBase", static_cast(OP_KnowledgeBase)), + luabind::value("VetRewardsAvaliable", static_cast(OP_VetRewardsAvaliable)), + luabind::value("VetClaimRequest", static_cast(OP_VetClaimRequest)), + luabind::value("VetClaimReply", static_cast(OP_VetClaimReply)), + luabind::value("WeaponEquip1", static_cast(OP_WeaponEquip1)), + luabind::value("PlayerStateAdd", static_cast(OP_PlayerStateAdd)), + luabind::value("PlayerStateRemove", static_cast(OP_PlayerStateRemove)), + luabind::value("WorldLogout", static_cast(OP_WorldLogout)), + luabind::value("SessionReady", static_cast(OP_SessionReady)), + luabind::value("Login", static_cast(OP_Login)), + luabind::value("ServerListRequest", static_cast(OP_ServerListRequest)), + luabind::value("PlayEverquestRequest", static_cast(OP_PlayEverquestRequest)), + luabind::value("ChatMessage", static_cast(OP_ChatMessage)), + luabind::value("LoginAccepted", static_cast(OP_LoginAccepted)), + luabind::value("ServerListResponse", static_cast(OP_ServerListResponse)), + luabind::value("Poll", static_cast(OP_Poll)), + luabind::value("PlayEverquestResponse", static_cast(OP_PlayEverquestResponse)), + luabind::value("EnterChat", static_cast(OP_EnterChat)), + luabind::value("PollResponse", static_cast(OP_PollResponse)), + luabind::value("Command", static_cast(OP_Command)), + luabind::value("ZonePlayerToBind", static_cast(OP_ZonePlayerToBind)), + luabind::value("AutoFire", static_cast(OP_AutoFire)), + luabind::value("Rewind", static_cast(OP_Rewind)), + luabind::value("TaskSelectWindow", static_cast(OP_TaskSelectWindow)), + luabind::value("TaskActivityComplete", static_cast(OP_TaskActivityComplete)), + luabind::value("AcceptNewTask", static_cast(OP_AcceptNewTask)), + luabind::value("CancelTask", static_cast(OP_CancelTask)), + luabind::value("TaskHistoryRequest", static_cast(OP_TaskHistoryRequest)), + luabind::value("TaskHistoryReply", static_cast(OP_TaskHistoryReply)), + luabind::value("PetBuffWindow", static_cast(OP_PetBuffWindow)), + luabind::value("RaidJoin", static_cast(OP_RaidJoin)), + luabind::value("Translocate", static_cast(OP_Translocate)), + luabind::value("Sacrifice", static_cast(OP_Sacrifice)), + luabind::value("KeyRing", static_cast(OP_KeyRing)), + luabind::value("PopupResponse", static_cast(OP_PopupResponse)), + luabind::value("DeleteCharge", static_cast(OP_DeleteCharge)), + luabind::value("PotionBelt", static_cast(OP_PotionBelt)), + luabind::value("Barter", static_cast(OP_Barter)), + luabind::value("VoiceMacroIn", static_cast(OP_VoiceMacroIn)), + luabind::value("VoiceMacroOut", static_cast(OP_VoiceMacroOut)), + luabind::value("WorldObjectsSent", static_cast(OP_WorldObjectsSent)), + luabind::value("BlockedBuffs", static_cast(OP_BlockedBuffs)), + luabind::value("RemoveBlockedBuffs", static_cast(OP_RemoveBlockedBuffs)), + luabind::value("ClearBlockedBuffs", static_cast(OP_ClearBlockedBuffs)), + luabind::value("GroupUpdateLeaderAA", static_cast(OP_GroupUpdateLeaderAA)), + luabind::value("MarkNPC", static_cast(OP_MarkNPC)), + luabind::value("ClearNPCMarks", static_cast(OP_ClearNPCMarks)), + luabind::value("DoGroupLeadershipAbility", static_cast(OP_DoGroupLeadershipAbility)), + luabind::value("DelegateAbility", static_cast(OP_DelegateAbility)), + luabind::value("SetGroupTarget", static_cast(OP_SetGroupTarget)), + luabind::value("ApplyPoison", static_cast(OP_ApplyPoison)), + luabind::value("FinishWindow", static_cast(OP_FinishWindow)), + luabind::value("FinishWindow2", static_cast(OP_FinishWindow2)), + luabind::value("ItemVerifyRequest", static_cast(OP_ItemVerifyRequest)), + luabind::value("ItemVerifyReply", static_cast(OP_ItemVerifyReply)), + luabind::value("GMTrainSkillConfirm", static_cast(OP_GMTrainSkillConfirm)), + luabind::value("RestState", static_cast(OP_RestState)), + luabind::value("AugmentInfo", static_cast(OP_AugmentInfo)), + luabind::value("PVPStats", static_cast(OP_PVPStats)), + luabind::value("PVPLeaderBoardRequest", static_cast(OP_PVPLeaderBoardRequest)), + luabind::value("PVPLeaderBoardReply", static_cast(OP_PVPLeaderBoardReply)), + luabind::value("PVPLeaderBoardDetailsRequest", static_cast(OP_PVPLeaderBoardDetailsRequest)), + luabind::value("PVPLeaderBoardDetailsReply", static_cast(OP_PVPLeaderBoardDetailsReply)), + luabind::value("DisciplineTimer", static_cast(OP_DisciplineTimer)), + luabind::value("RespawnWindow", static_cast(OP_RespawnWindow)), + luabind::value("AdventureMerchantSell", static_cast(OP_AdventureMerchantSell)), + luabind::value("AdventureStatsRequest", static_cast(OP_AdventureStatsRequest)), + luabind::value("AdventureStatsReply", static_cast(OP_AdventureStatsReply)), + luabind::value("AdventureLeaderboardRequest", static_cast(OP_AdventureLeaderboardRequest)), + luabind::value("AdventureLeaderboardReply", static_cast(OP_AdventureLeaderboardReply)), + luabind::value("SetStartCity", static_cast(OP_SetStartCity)), + luabind::value("LoginUnknown1", static_cast(OP_LoginUnknown1)), + luabind::value("LoginUnknown2", static_cast(OP_LoginUnknown2)), + luabind::value("ItemViewUnknown", static_cast(OP_ItemViewUnknown)), + luabind::value("GetGuildMOTDReply", static_cast(OP_GetGuildMOTDReply)), + luabind::value("SetGuildRank", static_cast(OP_SetGuildRank)), + luabind::value("SpawnPositionUpdate", static_cast(OP_SpawnPositionUpdate)), + luabind::value("ManaUpdate", static_cast(OP_ManaUpdate)), + luabind::value("EnduranceUpdate", static_cast(OP_EnduranceUpdate)), + luabind::value("MobManaUpdate", static_cast(OP_MobManaUpdate)), + luabind::value("MobEnduranceUpdate", static_cast(OP_MobEnduranceUpdate)), + luabind::value("GroupUpdateB", static_cast(OP_GroupUpdateB)), + luabind::value("GroupDisbandYou", static_cast(OP_GroupDisbandYou)), + luabind::value("GroupDisbandOther", static_cast(OP_GroupDisbandOther)), + luabind::value("GroupLeaderChange", static_cast(OP_GroupLeaderChange)), + luabind::value("GroupLeadershipAAUpdate", static_cast(OP_GroupLeadershipAAUpdate)), + luabind::value("GroupRoles", static_cast(OP_GroupRoles)), + luabind::value("SendFindableNPCs", static_cast(OP_SendFindableNPCs)), + luabind::value("HideCorpse", static_cast(OP_HideCorpse)), + luabind::value("TargetBuffs", static_cast(OP_TargetBuffs)), + luabind::value("TradeBusy", static_cast(OP_TradeBusy)), + luabind::value("GuildUpdateURLAndChannel", static_cast(OP_GuildUpdateURLAndChannel)), + luabind::value("CameraEffect", static_cast(OP_CameraEffect)), + luabind::value("SpellEffect", static_cast(OP_SpellEffect)), + luabind::value("DzQuit", static_cast(OP_DzQuit)), + luabind::value("DzListTimers", static_cast(OP_DzListTimers)), + luabind::value("DzPlayerList", static_cast(OP_DzPlayerList)), + luabind::value("DzAddPlayer", static_cast(OP_DzAddPlayer)), + luabind::value("DzRemovePlayer", static_cast(OP_DzRemovePlayer)), + luabind::value("DzSwapPlayer", static_cast(OP_DzSwapPlayer)), + luabind::value("DzMakeLeader", static_cast(OP_DzMakeLeader)), + luabind::value("DzExpeditionInvite", static_cast(OP_DzExpeditionInvite)), + luabind::value("DzExpeditionInviteResponse", static_cast(OP_DzExpeditionInviteResponse)), + luabind::value("DzExpeditionInfo", static_cast(OP_DzExpeditionInfo)), + luabind::value("DzMemberListName", static_cast(OP_DzMemberListName)), + luabind::value("DzMemberListStatus", static_cast(OP_DzMemberListStatus)), + luabind::value("DzSetLeaderName", static_cast(OP_DzSetLeaderName)), + luabind::value("DzExpeditionEndsWarning", static_cast(OP_DzExpeditionEndsWarning)), + luabind::value("DzExpeditionLockoutTimers", static_cast(OP_DzExpeditionLockoutTimers)), + luabind::value("DzMemberList", static_cast(OP_DzMemberList)), + luabind::value("DzCompass", static_cast(OP_DzCompass)), + luabind::value("DzChooseZone", static_cast(OP_DzChooseZone)), + luabind::value("DzChooseZoneReply", static_cast(OP_DzChooseZoneReply)), + luabind::value("BuffCreate", static_cast(OP_BuffCreate)), + luabind::value("GuildStatus", static_cast(OP_GuildStatus)), + luabind::value("BuffRemoveRequest", static_cast(OP_BuffRemoveRequest)), + luabind::value("CorpseDrag", static_cast(OP_CorpseDrag)), + luabind::value("CorpseDrop", static_cast(OP_CorpseDrop)), + luabind::value("ChangeSize", static_cast(OP_ChangeSize)), + luabind::value("GroupMakeLeader", static_cast(OP_GroupMakeLeader)), + luabind::value("RemoveAllDoors", static_cast(OP_RemoveAllDoors)), + luabind::value("RemoveNimbusEffect", static_cast(OP_RemoveNimbusEffect)), + luabind::value("GuildCreate", static_cast(OP_GuildCreate)), + luabind::value("AltCurrency", static_cast(OP_AltCurrency)), + luabind::value("FellowshipUpdate", static_cast(OP_FellowshipUpdate)), + luabind::value("AltCurrencyMerchantRequest", static_cast(OP_AltCurrencyMerchantRequest)), + luabind::value("AltCurrencyMerchantReply", static_cast(OP_AltCurrencyMerchantReply)), + luabind::value("AltCurrencyPurchase", static_cast(OP_AltCurrencyPurchase)), + luabind::value("AltCurrencySellSelection", static_cast(OP_AltCurrencySellSelection)), + luabind::value("AltCurrencyReclaim", static_cast(OP_AltCurrencyReclaim)), + luabind::value("AltCurrencySell", static_cast(OP_AltCurrencySell)), + luabind::value("Untargetable", static_cast(OP_Untargetable)), + luabind::value("CrystalReclaim", static_cast(OP_CrystalReclaim)), + luabind::value("CrystalCreate", static_cast(OP_CrystalCreate)), + luabind::value("SendMaxCharacters", static_cast(OP_SendMaxCharacters)), + luabind::value("SendMembership", static_cast(OP_SendMembership)), + luabind::value("SendMembershipDetails", static_cast(OP_SendMembershipDetails)), + luabind::value("LFGuild", static_cast(OP_LFGuild)), + luabind::value("XTargetRequest", static_cast(OP_XTargetRequest)), + luabind::value("XTargetResponse", static_cast(OP_XTargetResponse)), + luabind::value("XTargetAutoAddHaters", static_cast(OP_XTargetAutoAddHaters)), + luabind::value("Weblink", static_cast(OP_Weblink)), + luabind::value("InspectMessageUpdate", static_cast(OP_InspectMessageUpdate)), + luabind::value("ItemPreview", static_cast(OP_ItemPreview)), + luabind::value("MercenaryDataRequest", static_cast(OP_MercenaryDataRequest)), + luabind::value("MercenaryDataResponse", static_cast(OP_MercenaryDataResponse)), + luabind::value("MercenaryHire", static_cast(OP_MercenaryHire)), + luabind::value("MercenaryUnknown1", static_cast(OP_MercenaryUnknown1)), + luabind::value("MercenaryTimer", static_cast(OP_MercenaryTimer)), + luabind::value("MercenaryAssign", static_cast(OP_MercenaryAssign)), + luabind::value("MercenaryDataUpdate", static_cast(OP_MercenaryDataUpdate)), + luabind::value("MercenaryCommand", static_cast(OP_MercenaryCommand)), + luabind::value("MercenarySuspendRequest", static_cast(OP_MercenarySuspendRequest)), + luabind::value("MercenarySuspendResponse", static_cast(OP_MercenarySuspendResponse)), + luabind::value("MercenaryUnsuspendResponse", static_cast(OP_MercenaryUnsuspendResponse)), + luabind::value("MercenaryDataUpdateRequest", static_cast(OP_MercenaryDataUpdateRequest)), + luabind::value("MercenaryDismiss", static_cast(OP_MercenaryDismiss)), + luabind::value("MercenaryTimerRequest", static_cast(OP_MercenaryTimerRequest)), + luabind::value("OpenInventory", static_cast(OP_OpenInventory)), + luabind::value("OpenContainer", static_cast(OP_OpenContainer)), + luabind::value("Marquee", static_cast(OP_Marquee)), + luabind::value("ClientTimeStamp", static_cast(OP_ClientTimeStamp)), + luabind::value("GuildPromote", static_cast(OP_GuildPromote)), + luabind::value("Fling", static_cast(OP_Fling)) + ]; } #endif diff --git a/zone/lua_packet.h b/zone/lua_packet.h index a3c5ac553..9558b00f7 100644 --- a/zone/lua_packet.h +++ b/zone/lua_packet.h @@ -31,13 +31,23 @@ public: void SetOpcode(int op); int GetRawOpcode(); void SetRawOpcode(int op); + int GetWritePosition(); + void SetWritePosition(int offset); + void WriteInt8(int value); void WriteInt8(int offset, int value); + void WriteInt16(int value); void WriteInt16(int offset, int value); + void WriteInt32(int value); void WriteInt32(int offset, int value); + void WriteInt64(int64 value); void WriteInt64(int offset, int64 value); + void WriteFloat(float value); void WriteFloat(int offset, float value); + void WriteDouble(double value); void WriteDouble(int offset, double value); + void WriteString(std::string value); void WriteString(int offset, std::string value); + void WriteFixedLengthString(std::string value); void WriteFixedLengthString(int offset, std::string value, int string_length); int ReadInt8(int offset); int ReadInt16(int offset); diff --git a/zone/lua_parser.cpp b/zone/lua_parser.cpp index 59b10d4bf..2b1f89574 100644 --- a/zone/lua_parser.cpp +++ b/zone/lua_parser.cpp @@ -40,6 +40,10 @@ #include "lua_encounter.h" #include "lua_stat_bonuses.h" +#ifdef BOTS +#include "lua_bot.h" +#endif + const char *LuaEvents[_LargestEventID] = { "event_say", "event_trade", @@ -126,7 +130,13 @@ const char *LuaEvents[_LargestEventID] = { "event_death_zone", "event_use_skill", "event_combine_validate", - "event_bot_command" + "event_bot_command", + "event_warp", + "event_test_buff", + "event_combine", + "event_consider", + "event_consider_corpse", + "event_loot_zone" }; extern Zone *zone; @@ -176,6 +186,7 @@ LuaParser::LuaParser() { NPCArgumentDispatch[EVENT_FEIGN_DEATH] = handle_npc_single_client; NPCArgumentDispatch[EVENT_ENTER_AREA] = handle_npc_area; NPCArgumentDispatch[EVENT_LEAVE_AREA] = handle_npc_area; + NPCArgumentDispatch[EVENT_LOOT_ZONE] = handle_npc_loot_zone; PlayerArgumentDispatch[EVENT_SAY] = handle_player_say; PlayerArgumentDispatch[EVENT_ENVIRONMENTAL_DAMAGE] = handle_player_environmental_damage; @@ -192,6 +203,7 @@ LuaParser::LuaParser() { PlayerArgumentDispatch[EVENT_PLAYER_PICKUP] = handle_player_pick_up; PlayerArgumentDispatch[EVENT_CAST] = handle_player_cast; PlayerArgumentDispatch[EVENT_CAST_BEGIN] = handle_player_cast; + PlayerArgumentDispatch[EVENT_CAST_ON] = handle_player_cast; PlayerArgumentDispatch[EVENT_TASK_FAIL] = handle_player_task_fail; PlayerArgumentDispatch[EVENT_ZONE] = handle_player_zone; PlayerArgumentDispatch[EVENT_DUEL_WIN] = handle_player_duel_win; @@ -209,8 +221,13 @@ LuaParser::LuaParser() { PlayerArgumentDispatch[EVENT_RESPAWN] = handle_player_respawn; PlayerArgumentDispatch[EVENT_UNHANDLED_OPCODE] = handle_player_packet; PlayerArgumentDispatch[EVENT_USE_SKILL] = handle_player_use_skill; + PlayerArgumentDispatch[EVENT_TEST_BUFF] = handle_test_buff; PlayerArgumentDispatch[EVENT_COMBINE_VALIDATE] = handle_player_combine_validate; PlayerArgumentDispatch[EVENT_BOT_COMMAND] = handle_player_bot_command; + PlayerArgumentDispatch[EVENT_WARP] = handle_player_warp; + PlayerArgumentDispatch[EVENT_COMBINE] = handle_player_quest_combine; + PlayerArgumentDispatch[EVENT_CONSIDER] = handle_player_consider; + PlayerArgumentDispatch[EVENT_CONSIDER_CORPSE] = handle_player_consider_corpse; ItemArgumentDispatch[EVENT_ITEM_CLICK] = handle_item_click; ItemArgumentDispatch[EVENT_ITEM_CLICK_CAST] = handle_item_click; @@ -224,9 +241,9 @@ LuaParser::LuaParser() { ItemArgumentDispatch[EVENT_AUGMENT_INSERT] = handle_item_augment_insert; ItemArgumentDispatch[EVENT_AUGMENT_REMOVE] = handle_item_augment_remove; - SpellArgumentDispatch[EVENT_SPELL_EFFECT_CLIENT] = handle_spell_effect; - SpellArgumentDispatch[EVENT_SPELL_BUFF_TIC_CLIENT] = handle_spell_tic; - SpellArgumentDispatch[EVENT_SPELL_FADE] = handle_spell_fade; + SpellArgumentDispatch[EVENT_SPELL_EFFECT_CLIENT] = handle_spell_event; + SpellArgumentDispatch[EVENT_SPELL_EFFECT_BUFF_TIC_CLIENT] = handle_spell_event; + SpellArgumentDispatch[EVENT_SPELL_FADE] = handle_spell_event; SpellArgumentDispatch[EVENT_SPELL_EFFECT_TRANSLOCATE_COMPLETE] = handle_translocate_finish; EncounterArgumentDispatch[EVENT_TIMER] = handle_encounter_timer; @@ -310,7 +327,7 @@ int LuaParser::_EventNPC(std::string package_name, QuestEventID evt, NPC* npc, M arg_function(this, L, npc, init, data, extra_data, extra_pointers); Client *c = (init && init->IsClient()) ? init->CastToClient() : nullptr; - quest_manager.StartQuest(npc, c, nullptr); + quest_manager.StartQuest(npc, c); if(lua_pcall(L, 1, 1, 0)) { std::string error = lua_tostring(L, -1); AddError(error); @@ -403,7 +420,7 @@ int LuaParser::_EventPlayer(std::string package_name, QuestEventID evt, Client * auto arg_function = PlayerArgumentDispatch[evt]; arg_function(this, L, client, data, extra_data, extra_pointers); - quest_manager.StartQuest(client, client, nullptr); + quest_manager.StartQuest(client, client); if(lua_pcall(L, 1, 1, 0)) { std::string error = lua_tostring(L, -1); AddError(error); @@ -518,7 +535,7 @@ int LuaParser::_EventItem(std::string package_name, QuestEventID evt, Client *cl return 0; } -int LuaParser::EventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, uint32 extra_data, +int LuaParser::EventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, std::string data, uint32 extra_data, std::vector *extra_pointers) { evt = ConvertLuaEvent(evt); if(evt >= _LargestEventID) { @@ -531,10 +548,10 @@ int LuaParser::EventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spe return 0; } - return _EventSpell(package_name, evt, npc, client, spell_id, extra_data, extra_pointers); + return _EventSpell(package_name, evt, npc, client, spell_id, data, extra_data, extra_pointers); } -int LuaParser::_EventSpell(std::string package_name, QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, uint32 extra_data, +int LuaParser::_EventSpell(std::string package_name, QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, std::string data, uint32 extra_data, std::vector *extra_pointers, luabind::adl::object *l_func) { const char *sub_name = LuaEvents[evt]; @@ -565,9 +582,9 @@ int LuaParser::_EventSpell(std::string package_name, QuestEventID evt, NPC* npc, lua_setfield(L, -2, "self"); auto arg_function = SpellArgumentDispatch[evt]; - arg_function(this, L, npc, client, spell_id, extra_data, extra_pointers); + arg_function(this, L, npc, client, spell_id, data, extra_data, extra_pointers); - quest_manager.StartQuest(npc, client, nullptr); + quest_manager.StartQuest(npc, client, nullptr, const_cast(&spells[spell_id])); if(lua_pcall(L, 1, 1, 0)) { std::string error = lua_tostring(L, -1); AddError(error); @@ -633,7 +650,7 @@ int LuaParser::_EventEncounter(std::string package_name, QuestEventID evt, std:: auto arg_function = EncounterArgumentDispatch[evt]; arg_function(this, L, enc, data, extra_data, extra_pointers); - quest_manager.StartQuest(enc, nullptr, nullptr, encounter_name); + quest_manager.StartQuest(enc, nullptr, nullptr, nullptr, encounter_name); if(lua_pcall(L, 1, 1, 0)) { std::string error = lua_tostring(L, -1); AddError(error); @@ -1090,6 +1107,9 @@ void LuaParser::MapFunctions(lua_State *L) { lua_register_special_abilities(), lua_register_npc(), lua_register_client(), +#ifdef BOTS + lua_register_bot(), +#endif lua_register_inventory(), lua_register_inventory_where(), lua_register_iteminst(), @@ -1101,11 +1121,16 @@ void LuaParser::MapFunctions(lua_State *L) { lua_register_entity_list(), lua_register_mob_list(), lua_register_client_list(), +#ifdef BOTS + lua_register_bot_list(), +#endif lua_register_npc_list(), lua_register_corpse_list(), lua_register_object_list(), lua_register_door_list(), lua_register_spawn_list(), + lua_register_corpse_loot_list(), + lua_register_npc_loot_list(), lua_register_group(), lua_register_raid(), lua_register_corpse(), @@ -1251,7 +1276,7 @@ int LuaParser::DispatchEventItem(QuestEventID evt, Client *client, EQ::ItemInsta return ret; } -int LuaParser::DispatchEventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, uint32 extra_data, +int LuaParser::DispatchEventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, std::string data, uint32 extra_data, std::vector *extra_pointers) { evt = ConvertLuaEvent(evt); if(evt >= _LargestEventID) { @@ -1267,7 +1292,7 @@ int LuaParser::DispatchEventSpell(QuestEventID evt, NPC* npc, Client *client, ui while(riter != iter->second.end()) { if(riter->event_id == evt) { std::string package_name = "encounter_" + riter->encounter_name; - int i = _EventSpell(package_name, evt, npc, client, spell_id, extra_data, extra_pointers, &riter->lua_reference); + int i = _EventSpell(package_name, evt, npc, client, spell_id, data, extra_data, extra_pointers, &riter->lua_reference); if(i != 0) { ret = i; } @@ -1285,7 +1310,7 @@ int LuaParser::DispatchEventSpell(QuestEventID evt, NPC* npc, Client *client, ui while(riter != iter->second.end()) { if(riter->event_id == evt) { std::string package_name = "encounter_" + riter->encounter_name; - int i = _EventSpell(package_name, evt, npc, client, spell_id, extra_data, extra_pointers, &riter->lua_reference); + int i = _EventSpell(package_name, evt, npc, client, spell_id, data, extra_data, extra_pointers, &riter->lua_reference); if(i != 0) ret = i; } @@ -1304,9 +1329,9 @@ QuestEventID LuaParser::ConvertLuaEvent(QuestEventID evt) { case EVENT_SPELL_EFFECT_NPC: return EVENT_SPELL_EFFECT_CLIENT; break; - case EVENT_SPELL_BUFF_TIC_CLIENT: - case EVENT_SPELL_BUFF_TIC_NPC: - return EVENT_SPELL_BUFF_TIC_CLIENT; + case EVENT_SPELL_EFFECT_BUFF_TIC_CLIENT: + case EVENT_SPELL_EFFECT_BUFF_TIC_NPC: + return EVENT_SPELL_EFFECT_BUFF_TIC_CLIENT; break; case EVENT_AGGRO: case EVENT_ATTACK: diff --git a/zone/lua_parser.h b/zone/lua_parser.h index d058daf13..831f22061 100644 --- a/zone/lua_parser.h +++ b/zone/lua_parser.h @@ -46,7 +46,7 @@ public: std::vector *extra_pointers); virtual int EventItem(QuestEventID evt, Client *client, EQ::ItemInstance *item, Mob *mob, std::string data, uint32 extra_data, std::vector *extra_pointers); - virtual int EventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, uint32 extra_data, + virtual int EventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, std::string data, uint32 extra_data, std::vector *extra_pointers); virtual int EventEncounter(QuestEventID evt, std::string encounter_name, std::string data, uint32 extra_data, std::vector *extra_pointers); @@ -80,7 +80,7 @@ public: std::vector *extra_pointers); virtual int DispatchEventItem(QuestEventID evt, Client *client, EQ::ItemInstance *item, Mob *mob, std::string data, uint32 extra_data, std::vector *extra_pointers); - virtual int DispatchEventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, uint32 extra_data, + virtual int DispatchEventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, std::string data, uint32 extra_data, std::vector *extra_pointers); static LuaParser* Instance() { @@ -112,7 +112,7 @@ private: std::vector *extra_pointers, luabind::adl::object *l_func = nullptr); int _EventItem(std::string package_name, QuestEventID evt, Client *client, EQ::ItemInstance *item, Mob *mob, std::string data, uint32 extra_data, std::vector *extra_pointers, luabind::adl::object *l_func = nullptr); - int _EventSpell(std::string package_name, QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, uint32 extra_data, + int _EventSpell(std::string package_name, QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, std::string data, uint32 extra_data, std::vector *extra_pointers, luabind::adl::object *l_func = nullptr); int _EventEncounter(std::string package_name, QuestEventID evt, std::string encounter_name, std::string data, uint32 extra_data, std::vector *extra_pointers); diff --git a/zone/lua_parser_events.cpp b/zone/lua_parser_events.cpp index 9a1a2c67f..9adc0981e 100644 --- a/zone/lua_parser_events.cpp +++ b/zone/lua_parser_events.cpp @@ -237,6 +237,24 @@ void handle_npc_null(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, s std::vector *extra_pointers) { } +void handle_npc_loot_zone(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, std::string data, uint32 extra_data, + std::vector *extra_pointers) { + Lua_Client l_client(reinterpret_cast(init)); + luabind::adl::object l_client_o = luabind::adl::object(L, l_client); + l_client_o.push(L); + lua_setfield(L, -2, "other"); + + Lua_ItemInst l_item(EQ::any_cast(extra_pointers->at(0))); + luabind::adl::object l_item_o = luabind::adl::object(L, l_item); + l_item_o.push(L); + lua_setfield(L, -2, "item"); + + Lua_Corpse l_corpse(EQ::any_cast(extra_pointers->at(1))); + luabind::adl::object l_corpse_o = luabind::adl::object(L, l_corpse); + l_corpse_o.push(L); + lua_setfield(L, -2, "corpse"); +} + //Player void handle_player_say(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, std::vector *extra_pointers) { @@ -514,6 +532,9 @@ void handle_player_use_skill(QuestInterface *parse, lua_State* L, Client* client lua_setfield(L, -2, "skill_level"); } +void handle_test_buff(QuestInterface* parse, lua_State* L, Client* client, std::string data, uint32 extra_data, std::vector* extra_pointers) { +} + void handle_player_combine_validate(QuestInterface* parse, lua_State* L, Client* client, std::string data, uint32 extra_data, std::vector* extra_pointers) { Seperator sep(data.c_str()); @@ -558,6 +579,33 @@ void handle_player_bot_command(QuestInterface* parse, lua_State* L, Client* clie lua_setfield(L, -2, "args"); } +void handle_player_warp(QuestInterface* parse, lua_State* L, Client* client, std::string data, uint32 extra_data, std::vector* extra_pointers) { + Seperator sep(data.c_str()); + lua_pushnumber(L, std::stof(sep.arg[0])); + lua_setfield(L, -2, "from_x"); + + lua_pushnumber(L, std::stof(sep.arg[1])); + lua_setfield(L, -2, "from_y"); + + lua_pushnumber(L, std::stof(sep.arg[2])); + lua_setfield(L, -2, "from_z"); +} + +void handle_player_quest_combine(QuestInterface* parse, lua_State* L, Client* client, std::string data, uint32 extra_data, std::vector* extra_pointers) { + lua_pushinteger(L, std::stoi(data)); + lua_setfield(L, -2, "container_slot"); + } + +void handle_player_consider(QuestInterface* parse, lua_State* L, Client* client, std::string data, uint32 extra_data, std::vector* extra_pointers) { + lua_pushinteger(L, std::stoi(data)); + lua_setfield(L, -2, "entity_id"); +} + +void handle_player_consider_corpse(QuestInterface* parse, lua_State* L, Client* client, std::string data, uint32 extra_data, std::vector* extra_pointers) { + lua_pushinteger(L, std::stoi(data)); + lua_setfield(L, -2, "corpse_entity_id"); +} + //Item void handle_item_click(QuestInterface *parse, lua_State* L, Client* client, EQ::ItemInstance* item, Mob *mob, std::string data, uint32 extra_data, std::vector *extra_pointers) { @@ -654,8 +702,7 @@ void handle_item_null(QuestInterface *parse, lua_State* L, Client* client, EQ::I } //Spell -void handle_spell_effect(QuestInterface *parse, lua_State* L, NPC* npc, Client* client, uint32 spell_id, uint32 extra_data, - std::vector *extra_pointers) { +void handle_spell_event(QuestInterface *parse, lua_State* L, NPC* npc, Client* client, uint32 spell_id, std::string data, uint32 extra_data, std::vector *extra_pointers) { if(npc) { Lua_Mob l_npc(npc); luabind::adl::object l_npc_o = luabind::adl::object(L, l_npc); @@ -672,71 +719,30 @@ void handle_spell_effect(QuestInterface *parse, lua_State* L, NPC* npc, Client* lua_setfield(L, -2, "target"); - lua_pushinteger(L, *EQ::any_cast(extra_pointers->at(0))); - lua_setfield(L, -2, "buff_slot"); + lua_pushinteger(L, spell_id); + lua_setfield(L, -2, "spell_id"); - lua_pushinteger(L, extra_data); + Seperator sep(data.c_str()); + + lua_pushinteger(L, std::stoi(sep.arg[0])); lua_setfield(L, -2, "caster_id"); -} -void handle_spell_tic(QuestInterface *parse, lua_State* L, NPC* npc, Client* client, uint32 spell_id, uint32 extra_data, - std::vector *extra_pointers) { - if(npc) { - Lua_Mob l_npc(npc); - luabind::adl::object l_npc_o = luabind::adl::object(L, l_npc); - l_npc_o.push(L); - } else if(client) { - Lua_Mob l_client(client); - luabind::adl::object l_client_o = luabind::adl::object(L, l_client); - l_client_o.push(L); - } else { - Lua_Mob l_mob(nullptr); - luabind::adl::object l_mob_o = luabind::adl::object(L, l_mob); - l_mob_o.push(L); - } - - lua_setfield(L, -2, "target"); - - lua_pushinteger(L, *EQ::any_cast(extra_pointers->at(0))); + lua_pushinteger(L, std::stoi(sep.arg[1])); lua_setfield(L, -2, "tics_remaining"); - lua_pushinteger(L, *EQ::any_cast(extra_pointers->at(1))); + lua_pushinteger(L, std::stoi(sep.arg[2])); lua_setfield(L, -2, "caster_level"); - lua_pushinteger(L, *EQ::any_cast(extra_pointers->at(2))); + lua_pushinteger(L, std::stoi(sep.arg[3])); lua_setfield(L, -2, "buff_slot"); - - lua_pushinteger(L, extra_data); - lua_setfield(L, -2, "caster_id"); + + Lua_Spell l_spell(spell_id); + luabind::adl::object l_spell_o = luabind::adl::object(L, l_spell); + l_spell_o.push(L); + lua_setfield(L, -2, "spell"); } -void handle_spell_fade(QuestInterface *parse, lua_State* L, NPC* npc, Client* client, uint32 spell_id, uint32 extra_data, - std::vector *extra_pointers) { - if(npc) { - Lua_Mob l_npc(npc); - luabind::adl::object l_npc_o = luabind::adl::object(L, l_npc); - l_npc_o.push(L); - } else if(client) { - Lua_Mob l_client(client); - luabind::adl::object l_client_o = luabind::adl::object(L, l_client); - l_client_o.push(L); - } else { - Lua_Mob l_mob(nullptr); - luabind::adl::object l_mob_o = luabind::adl::object(L, l_mob); - l_mob_o.push(L); - } - - lua_setfield(L, -2, "target"); - - lua_pushinteger(L, extra_data); - lua_setfield(L, -2, "buff_slot"); - - lua_pushinteger(L, *EQ::any_cast(extra_pointers->at(0))); - lua_setfield(L, -2, "caster_id"); -} - -void handle_translocate_finish(QuestInterface *parse, lua_State* L, NPC* npc, Client* client, uint32 spell_id, uint32 extra_data, - std::vector *extra_pointers) { +void handle_translocate_finish(QuestInterface *parse, lua_State* L, NPC* npc, Client* client, uint32 spell_id, std::string data, uint32 extra_data, std::vector *extra_pointers) { if(npc) { Lua_Mob l_npc(npc); luabind::adl::object l_npc_o = luabind::adl::object(L, l_npc); @@ -754,9 +760,7 @@ void handle_translocate_finish(QuestInterface *parse, lua_State* L, NPC* npc, Cl lua_setfield(L, -2, "target"); } -void handle_spell_null(QuestInterface *parse, lua_State* L, NPC* npc, Client* client, uint32 spell_id, uint32 extra_data, - std::vector *extra_pointers) { -} +void handle_spell_null(QuestInterface *parse, lua_State* L, NPC* npc, Client* client, uint32 spell_id, std::string data, uint32 extra_data, std::vector *extra_pointers) { } void handle_encounter_timer(QuestInterface *parse, lua_State* L, Encounter* encounter, std::string data, uint32 extra_data, std::vector *extra_pointers) { diff --git a/zone/lua_parser_events.h b/zone/lua_parser_events.h index 0f826a421..5eced1243 100644 --- a/zone/lua_parser_events.h +++ b/zone/lua_parser_events.h @@ -5,7 +5,7 @@ typedef void(*NPCArgumentHandler)(QuestInterface*, lua_State*, NPC*, Mob*, std::string, uint32, std::vector*); typedef void(*PlayerArgumentHandler)(QuestInterface*, lua_State*, Client*, std::string, uint32, std::vector*); typedef void(*ItemArgumentHandler)(QuestInterface*, lua_State*, Client*, EQ::ItemInstance*, Mob*, std::string, uint32, std::vector*); -typedef void(*SpellArgumentHandler)(QuestInterface*, lua_State*, NPC*, Client*, uint32, uint32, std::vector*); +typedef void(*SpellArgumentHandler)(QuestInterface*, lua_State*, NPC*, Client*, uint32, std::string, uint32, std::vector*); typedef void(*EncounterArgumentHandler)(QuestInterface*, lua_State*, Encounter* encounter, std::string, uint32, std::vector*); //NPC @@ -41,6 +41,8 @@ void handle_npc_area(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, s std::vector *extra_pointers); void handle_npc_null(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, std::string data, uint32 extra_data, std::vector *extra_pointers); +void handle_npc_loot_zone(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, std::string data, uint32 extra_data, + std::vector *extra_pointers); //Player void handle_player_say(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, @@ -97,10 +99,20 @@ void handle_player_null(QuestInterface *parse, lua_State* L, Client* client, std std::vector *extra_pointers); void handle_player_use_skill(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, std::vector *extra_pointers); +void handle_test_buff(QuestInterface* parse, lua_State* L, Client* client, std::string data, uint32 extra_data, + std::vector* extra_pointers); void handle_player_combine_validate(QuestInterface* parse, lua_State* L, Client* client, std::string data, uint32 extra_data, std::vector* extra_pointers); void handle_player_bot_command(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, std::vector *extra_pointers); +void handle_player_warp(QuestInterface* parse, lua_State* L, Client* client, std::string data, uint32 extra_data, + std::vector* extra_pointers); +void handle_player_quest_combine(QuestInterface* parse, lua_State* L, Client* client, std::string data, uint32 extra_data, + std::vector* extra_pointers); +void handle_player_consider(QuestInterface* parse, lua_State* L, Client* client, std::string data, uint32 extra_data, + std::vector* extra_pointers); +void handle_player_consider_corpse(QuestInterface* parse, lua_State* L, Client* client, std::string data, uint32 extra_data, + std::vector* extra_pointers); //Item void handle_item_click(QuestInterface *parse, lua_State* L, Client* client, EQ::ItemInstance* item, Mob *mob, std::string data, uint32 extra_data, @@ -123,15 +135,11 @@ void handle_item_null(QuestInterface *parse, lua_State* L, Client* client, EQ::I std::vector *extra_pointers); //Spell -void handle_spell_effect(QuestInterface *parse, lua_State* L, NPC* npc, Client* client, uint32 spell_id, uint32 extra_data, +void handle_spell_event(QuestInterface *parse, lua_State* L, NPC* npc, Client* client, uint32 spell_id, std::string data, uint32 extra_data, std::vector *extra_pointers); -void handle_spell_tic(QuestInterface *parse, lua_State* L, NPC* npc, Client* client, uint32 spell_id, uint32 extra_data, +void handle_translocate_finish(QuestInterface *parse, lua_State* L, NPC* npc, Client* client, uint32 spell_id, std::string data, uint32 extra_data, std::vector *extra_pointers); -void handle_spell_fade(QuestInterface *parse, lua_State* L, NPC* npc, Client* client, uint32 spell_id, uint32 extra_data, - std::vector *extra_pointers); -void handle_translocate_finish(QuestInterface *parse, lua_State* L, NPC* npc, Client* client, uint32 spell_id, uint32 extra_data, - std::vector *extra_pointers); -void handle_spell_null(QuestInterface *parse, lua_State* L, NPC* npc, Client* client, uint32 spell_id, uint32 extra_data, +void handle_spell_null(QuestInterface *parse, lua_State* L, NPC* npc, Client* client, uint32 spell_id, std::string data, uint32 extra_data, std::vector *extra_pointers); diff --git a/zone/lua_raid.cpp b/zone/lua_raid.cpp index 78f6bb1b6..4bc99a7cd 100644 --- a/zone/lua_raid.cpp +++ b/zone/lua_raid.cpp @@ -145,32 +145,32 @@ bool Lua_Raid::DoesAnyMemberHaveExpeditionLockout(std::string expedition_name, s luabind::scope lua_register_raid() { return luabind::class_("Raid") - .def(luabind::constructor<>()) - .property("null", &Lua_Raid::Null) - .property("valid", &Lua_Raid::Valid) - .def("IsRaidMember", (bool(Lua_Raid::*)(const char*))&Lua_Raid::IsRaidMember) - .def("CastGroupSpell", (void(Lua_Raid::*)(Lua_Mob,int,uint32))&Lua_Raid::CastGroupSpell) - .def("GroupCount", (int(Lua_Raid::*)(uint32))&Lua_Raid::GroupCount) - .def("RaidCount", (int(Lua_Raid::*)(void))&Lua_Raid::RaidCount) - .def("GetGroup", (int(Lua_Raid::*)(const char*))&Lua_Raid::GetGroup) - .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,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) - .def("GetHighestLevel", (int(Lua_Raid::*)(void))&Lua_Raid::GetHighestLevel) - .def("GetLowestLevel", (int(Lua_Raid::*)(void))&Lua_Raid::GetLowestLevel) - .def("GetClientByIndex", (Lua_Client(Lua_Raid::*)(int))&Lua_Raid::GetClientByIndex) - .def("TeleportGroup", (int(Lua_Raid::*)(Lua_Mob,uint32,uint32,float,float,float,float,uint32))&Lua_Raid::TeleportGroup) - .def("TeleportRaid", (int(Lua_Raid::*)(Lua_Mob,uint32,uint32,float,float,float,float))&Lua_Raid::TeleportRaid) - .def("GetID", (int(Lua_Raid::*)(void))&Lua_Raid::GetID) - .def("GetMember", (Lua_Client(Lua_Raid::*)(int))&Lua_Raid::GetMember) - .def("GetGroupNumber", (int(Lua_Raid::*)(int))&Lua_Raid::GetGroupNumber) - .def("DoesAnyMemberHaveExpeditionLockout", (bool(Lua_Raid::*)(std::string, std::string))&Lua_Raid::DoesAnyMemberHaveExpeditionLockout) - .def("DoesAnyMemberHaveExpeditionLockout", (bool(Lua_Raid::*)(std::string, std::string, int))&Lua_Raid::DoesAnyMemberHaveExpeditionLockout); + .def(luabind::constructor<>()) + .property("null", &Lua_Raid::Null) + .property("valid", &Lua_Raid::Valid) + .def("BalanceHP", (void(Lua_Raid::*)(int,uint32))&Lua_Raid::BalanceHP) + .def("CastGroupSpell", (void(Lua_Raid::*)(Lua_Mob,int,uint32))&Lua_Raid::CastGroupSpell) + .def("DoesAnyMemberHaveExpeditionLockout", (bool(Lua_Raid::*)(std::string, std::string))&Lua_Raid::DoesAnyMemberHaveExpeditionLockout) + .def("DoesAnyMemberHaveExpeditionLockout", (bool(Lua_Raid::*)(std::string, std::string, int))&Lua_Raid::DoesAnyMemberHaveExpeditionLockout) + .def("GetClientByIndex", (Lua_Client(Lua_Raid::*)(int))&Lua_Raid::GetClientByIndex) + .def("GetGroup", (int(Lua_Raid::*)(Lua_Client))&Lua_Raid::GetGroup) + .def("GetGroup", (int(Lua_Raid::*)(const char*))&Lua_Raid::GetGroup) + .def("GetGroupNumber", (int(Lua_Raid::*)(int))&Lua_Raid::GetGroupNumber) + .def("GetHighestLevel", (int(Lua_Raid::*)(void))&Lua_Raid::GetHighestLevel) + .def("GetID", (int(Lua_Raid::*)(void))&Lua_Raid::GetID) + .def("GetLowestLevel", (int(Lua_Raid::*)(void))&Lua_Raid::GetLowestLevel) + .def("GetMember", (Lua_Client(Lua_Raid::*)(int))&Lua_Raid::GetMember) + .def("GetTotalRaidDamage", (uint32(Lua_Raid::*)(Lua_Mob))&Lua_Raid::GetTotalRaidDamage) + .def("GroupCount", (int(Lua_Raid::*)(uint32))&Lua_Raid::GroupCount) + .def("IsGroupLeader", (bool(Lua_Raid::*)(const char*))&Lua_Raid::IsGroupLeader) + .def("IsLeader", (bool(Lua_Raid::*)(const char*))&Lua_Raid::IsLeader) + .def("IsRaidMember", (bool(Lua_Raid::*)(const char*))&Lua_Raid::IsRaidMember) + .def("RaidCount", (int(Lua_Raid::*)(void))&Lua_Raid::RaidCount) + .def("SplitExp", (void(Lua_Raid::*)(uint32,Lua_Mob))&Lua_Raid::SplitExp) + .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("TeleportGroup", (int(Lua_Raid::*)(Lua_Mob,uint32,uint32,float,float,float,float,uint32))&Lua_Raid::TeleportGroup) + .def("TeleportRaid", (int(Lua_Raid::*)(Lua_Mob,uint32,uint32,float,float,float,float))&Lua_Raid::TeleportRaid); } #endif diff --git a/zone/lua_spawn.cpp b/zone/lua_spawn.cpp index be39bcf74..117b4a6b4 100644 --- a/zone/lua_spawn.cpp +++ b/zone/lua_spawn.cpp @@ -140,35 +140,35 @@ uint32 Lua_Spawn::GetKillCount() { luabind::scope lua_register_spawn() { return luabind::class_("Spawn") - .def(luabind::constructor<>()) - .property("null", &Lua_Spawn::Null) - .property("valid", &Lua_Spawn::Valid) - .def("LoadGrid", (void(Lua_Spawn::*)(void))&Lua_Spawn::LoadGrid) - .def("Enable", (void(Lua_Spawn::*)(void))&Lua_Spawn::Enable) - .def("Disable", (void(Lua_Spawn::*)(void))&Lua_Spawn::Disable) - .def("Enabled", (bool(Lua_Spawn::*)(void))&Lua_Spawn::Enabled) - .def("Reset", (void(Lua_Spawn::*)(void))&Lua_Spawn::Reset) - .def("Depop", (void(Lua_Spawn::*)(void))&Lua_Spawn::Depop) - .def("Repop", (void(Lua_Spawn::*)(void))&Lua_Spawn::Repop) - .def("Repop", (void(Lua_Spawn::*)(uint32))&Lua_Spawn::Repop) - .def("ForceDespawn", (void(Lua_Spawn::*)(void))&Lua_Spawn::ForceDespawn) - .def("GetID", (uint32(Lua_Spawn::*)(void))&Lua_Spawn::GetID) - .def("GetX", (float(Lua_Spawn::*)(void))&Lua_Spawn::GetX) - .def("GetY", (float(Lua_Spawn::*)(void))&Lua_Spawn::GetY) - .def("GetZ", (float(Lua_Spawn::*)(void))&Lua_Spawn::GetZ) - .def("GetHeading", (float(Lua_Spawn::*)(void))&Lua_Spawn::GetHeading) - .def("SetRespawnTimer", (void(Lua_Spawn::*)(uint32))&Lua_Spawn::SetRespawnTimer) - .def("SetVariance", (void(Lua_Spawn::*)(uint32))&Lua_Spawn::SetVariance) - .def("GetVariance", (uint32(Lua_Spawn::*)(void))&Lua_Spawn::GetVariance) - .def("RespawnTimer", (uint32(Lua_Spawn::*)(void))&Lua_Spawn::RespawnTimer) - .def("SpawnGroupID", (uint32(Lua_Spawn::*)(void))&Lua_Spawn::SpawnGroupID) - .def("CurrentNPCID", (uint32(Lua_Spawn::*)(void))&Lua_Spawn::CurrentNPCID) - .def("SetCurrentNPCID", (void(Lua_Spawn::*)(uint32))&Lua_Spawn::SetCurrentNPCID) - .def("GetSpawnCondition", (uint32(Lua_Spawn::*)(void))&Lua_Spawn::GetSpawnCondition) - .def("NPCPointerValid", (bool(Lua_Spawn::*)(void))&Lua_Spawn::NPCPointerValid) - .def("SetNPCPointer", (void(Lua_Spawn::*)(Lua_NPC))&Lua_Spawn::SetNPCPointer) - .def("SetTimer", (void(Lua_Spawn::*)(uint32))&Lua_Spawn::SetTimer) - .def("GetKillCount", (uint32(Lua_Spawn::*)(void))&Lua_Spawn::GetKillCount); + .def(luabind::constructor<>()) + .property("null", &Lua_Spawn::Null) + .property("valid", &Lua_Spawn::Valid) + .def("CurrentNPCID", (uint32(Lua_Spawn::*)(void))&Lua_Spawn::CurrentNPCID) + .def("Depop", (void(Lua_Spawn::*)(void))&Lua_Spawn::Depop) + .def("Disable", (void(Lua_Spawn::*)(void))&Lua_Spawn::Disable) + .def("Enable", (void(Lua_Spawn::*)(void))&Lua_Spawn::Enable) + .def("Enabled", (bool(Lua_Spawn::*)(void))&Lua_Spawn::Enabled) + .def("ForceDespawn", (void(Lua_Spawn::*)(void))&Lua_Spawn::ForceDespawn) + .def("GetHeading", (float(Lua_Spawn::*)(void))&Lua_Spawn::GetHeading) + .def("GetID", (uint32(Lua_Spawn::*)(void))&Lua_Spawn::GetID) + .def("GetKillCount", (uint32(Lua_Spawn::*)(void))&Lua_Spawn::GetKillCount) + .def("GetSpawnCondition", (uint32(Lua_Spawn::*)(void))&Lua_Spawn::GetSpawnCondition) + .def("GetVariance", (uint32(Lua_Spawn::*)(void))&Lua_Spawn::GetVariance) + .def("GetX", (float(Lua_Spawn::*)(void))&Lua_Spawn::GetX) + .def("GetY", (float(Lua_Spawn::*)(void))&Lua_Spawn::GetY) + .def("GetZ", (float(Lua_Spawn::*)(void))&Lua_Spawn::GetZ) + .def("LoadGrid", (void(Lua_Spawn::*)(void))&Lua_Spawn::LoadGrid) + .def("NPCPointerValid", (bool(Lua_Spawn::*)(void))&Lua_Spawn::NPCPointerValid) + .def("Repop", (void(Lua_Spawn::*)(uint32))&Lua_Spawn::Repop) + .def("Repop", (void(Lua_Spawn::*)(void))&Lua_Spawn::Repop) + .def("Reset", (void(Lua_Spawn::*)(void))&Lua_Spawn::Reset) + .def("RespawnTimer", (uint32(Lua_Spawn::*)(void))&Lua_Spawn::RespawnTimer) + .def("SetCurrentNPCID", (void(Lua_Spawn::*)(uint32))&Lua_Spawn::SetCurrentNPCID) + .def("SetNPCPointer", (void(Lua_Spawn::*)(Lua_NPC))&Lua_Spawn::SetNPCPointer) + .def("SetRespawnTimer", (void(Lua_Spawn::*)(uint32))&Lua_Spawn::SetRespawnTimer) + .def("SetTimer", (void(Lua_Spawn::*)(uint32))&Lua_Spawn::SetTimer) + .def("SetVariance", (void(Lua_Spawn::*)(uint32))&Lua_Spawn::SetVariance) + .def("SpawnGroupID", (uint32(Lua_Spawn::*)(void))&Lua_Spawn::SpawnGroupID); } #endif diff --git a/zone/lua_spell.cpp b/zone/lua_spell.cpp index 18353efaa..c2a5e622d 100644 --- a/zone/lua_spell.cpp +++ b/zone/lua_spell.cpp @@ -66,17 +66,17 @@ float Lua_Spell::GetRange() { float Lua_Spell::GetAoeRange() { Lua_Safe_Call_Real(); - return self->aoerange; + return self->aoe_range; } float Lua_Spell::GetPushBack() { Lua_Safe_Call_Real(); - return self->pushback; + return self->push_back; } float Lua_Spell::GetPushUp() { Lua_Safe_Call_Real(); - return self->pushup; + return self->push_up; } uint32 Lua_Spell::GetCastTime() { @@ -96,17 +96,17 @@ uint32 Lua_Spell::GetRecastTime() { uint32 Lua_Spell::GetBuffdurationFormula() { Lua_Safe_Call_Int(); - return self->buffdurationformula; + return self->buff_duration_formula; } uint32 Lua_Spell::GetBuffDuration() { Lua_Safe_Call_Int(); - return self->buffduration; + return self->buff_duration; } uint32 Lua_Spell::GetAEDuration() { Lua_Safe_Call_Int(); - return self->AEDuration; + return self->aoe_duration; } int Lua_Spell::GetMana() { @@ -121,7 +121,7 @@ int Lua_Spell::GetBase(int i) { return 0; } - return self->base[i]; + return self->base_value[i]; } int Lua_Spell::GetBase2(int i) { @@ -131,7 +131,7 @@ int Lua_Spell::GetBase2(int i) { return 0; } - return self->base2[i]; + return self->limit_value[i]; } int Lua_Spell::GetMax(int i) { @@ -141,7 +141,7 @@ int Lua_Spell::GetMax(int i) { return 0; } - return self->max[i]; + return self->max_value[i]; } int Lua_Spell::GetComponents(int i) { @@ -151,7 +151,7 @@ int Lua_Spell::GetComponents(int i) { return 0; } - return self->components[i]; + return self->component[i]; } int Lua_Spell::GetComponentCounts(int i) { @@ -161,7 +161,7 @@ int Lua_Spell::GetComponentCounts(int i) { return 0; } - return self->component_counts[i]; + return self->component_count[i]; } int Lua_Spell::GetNoexpendReagent(int i) { @@ -171,7 +171,7 @@ int Lua_Spell::GetNoexpendReagent(int i) { return 0; } - return self->NoexpendReagent[i]; + return self->no_expend_reagent[i]; } int Lua_Spell::GetFormula(int i) { @@ -186,17 +186,17 @@ int Lua_Spell::GetFormula(int i) { int Lua_Spell::GetGoodEffect() { Lua_Safe_Call_Int(); - return self->goodEffect; + return self->good_effect; } int Lua_Spell::GetActivated() { Lua_Safe_Call_Int(); - return self->Activated; + return self->activated; } int Lua_Spell::GetResistType() { Lua_Safe_Call_Int(); - return self->resisttype; + return self->resist_type; } int Lua_Spell::GetEffectID(int i) { @@ -206,17 +206,17 @@ int Lua_Spell::GetEffectID(int i) { return 0; } - return self->effectid[i]; + return self->effect_id[i]; } int Lua_Spell::GetTargetType() { Lua_Safe_Call_Int(); - return self->targettype; + return self->target_type; } int Lua_Spell::GetBaseDiff() { Lua_Safe_Call_Int(); - return self->basediff; + return self->base_difficulty; } int Lua_Spell::GetSkill() { @@ -226,17 +226,17 @@ int Lua_Spell::GetSkill() { int Lua_Spell::GetZoneType() { Lua_Safe_Call_Int(); - return self->zonetype; + return self->zone_type; } int Lua_Spell::GetEnvironmentType() { Lua_Safe_Call_Int(); - return self->EnvironmentType; + return self->environment_type; } int Lua_Spell::GetTimeOfDay() { Lua_Safe_Call_Int(); - return self->TimeOfDay; + return self->time_of_day; } int Lua_Spell::GetClasses(int i) { @@ -251,12 +251,12 @@ int Lua_Spell::GetClasses(int i) { int Lua_Spell::GetCastingAnim() { Lua_Safe_Call_Int(); - return self->CastingAnim; + return self->casting_animation; } int Lua_Spell::GetSpellAffectIndex() { Lua_Safe_Call_Int(); - return self->SpellAffectIndex; + return self->spell_affect_index; } int Lua_Spell::GetDisallowSit() { @@ -281,12 +281,12 @@ int Lua_Spell::GetUninterruptable() { int Lua_Spell::GetResistDiff() { Lua_Safe_Call_Int(); - return self->ResistDiff; + return self->resist_difficulty; } int Lua_Spell::GetRecourseLink() { Lua_Safe_Call_Int(); - return self->RecourseLink; + return self->recourse_link; } int Lua_Spell::GetShortBuffBox() { @@ -296,57 +296,57 @@ int Lua_Spell::GetShortBuffBox() { int Lua_Spell::GetDescNum() { Lua_Safe_Call_Int(); - return self->descnum; + return self->description_id; } int Lua_Spell::GetEffectDescNum() { Lua_Safe_Call_Int(); - return self->effectdescnum; + return self->effect_description_id; } int Lua_Spell::GetBonusHate() { Lua_Safe_Call_Int(); - return self->bonushate; + return self->bonus_hate; } int Lua_Spell::GetEndurCost() { Lua_Safe_Call_Int(); - return self->EndurCost; + return self->endurance_cost; } int Lua_Spell::GetEndurTimerIndex() { Lua_Safe_Call_Int(); - return self->EndurUpkeep; + return self->endurance_upkeep; } int Lua_Spell::GetHateAdded() { Lua_Safe_Call_Int(); - return self->HateAdded; + return self->hate_added; } int Lua_Spell::GetEndurUpkeep() { Lua_Safe_Call_Int(); - return self->EndurUpkeep; + return self->endurance_upkeep; } int Lua_Spell::GetNumHits() { Lua_Safe_Call_Int(); - return self->numhits; + return self->hit_number; } int Lua_Spell::GetPVPResistBase() { Lua_Safe_Call_Int(); - return self->pvpresistbase; + return self->pvp_resist_base; } int Lua_Spell::GetPVPResistCalc() { Lua_Safe_Call_Int(); - return self->pvpresistcalc; + return self->pvp_resist_per_level; } int Lua_Spell::GetPVPResistCap() { Lua_Safe_Call_Int(); - return self->pvpresistcap; + return self->pvp_resist_cap; } int Lua_Spell::GetSpellCategory() { @@ -354,6 +354,16 @@ int Lua_Spell::GetSpellCategory() { return self->spell_category; } +int Lua_Spell::GetPVPDuration() { + Lua_Safe_Call_Int(); + return self->pvp_duration; +} + +int Lua_Spell::GetPVPDurationCap() { + Lua_Safe_Call_Int(); + return self->pvp_duration_cap; +} + int Lua_Spell::GetCanMGB() { Lua_Safe_Call_Int(); return self->can_mgb; @@ -366,12 +376,12 @@ int Lua_Spell::GetDispelFlag() { int Lua_Spell::GetMinResist() { Lua_Safe_Call_Int(); - return self->MinResist; + return self->min_resist; } int Lua_Spell::GetMaxResist() { Lua_Safe_Call_Int(); - return self->MaxResist; + return self->max_resist; } int Lua_Spell::GetViralTargets() { @@ -386,7 +396,7 @@ int Lua_Spell::GetViralTimer() { int Lua_Spell::GetNimbusEffect() { Lua_Safe_Call_Int(); - return self->NimbusEffect; + return self->nimbus_effect; } float Lua_Spell::GetDirectionalStart() { @@ -401,7 +411,7 @@ float Lua_Spell::GetDirectionalEnd() { int Lua_Spell::GetSpellGroup() { Lua_Safe_Call_Int(); - return self->spellgroup; + return self->spell_group; } int Lua_Spell::GetPowerfulFlag() { @@ -411,27 +421,27 @@ int Lua_Spell::GetPowerfulFlag() { int Lua_Spell::GetCastRestriction() { Lua_Safe_Call_Int(); - return self->CastRestriction; + return self->cast_restriction; } bool Lua_Spell::GetAllowRest() { Lua_Safe_Call_Bool(); - return self->AllowRest; + return self->allow_rest; } bool Lua_Spell::GetInCombat() { Lua_Safe_Call_Bool(); - return self->InCombat; + return self->can_cast_in_combat; } bool Lua_Spell::GetOutOfCombat() { Lua_Safe_Call_Bool(); - return self->OutofCombat; + return self->can_cast_out_of_combat; } int Lua_Spell::GetAEMaxTargets() { Lua_Safe_Call_Int(); - return self->aemaxtargets; + return self->aoe_max_targets; } int Lua_Spell::GetMaxTargets() { @@ -441,27 +451,27 @@ int Lua_Spell::GetMaxTargets() { bool Lua_Spell::GetPersistDeath() { Lua_Safe_Call_Bool(); - return self->persistdeath; + return self->persist_death; } float Lua_Spell::GetMinDist() { Lua_Safe_Call_Real(); - return self->min_dist; + return self->min_distance; } float Lua_Spell::GetMinDistMod() { Lua_Safe_Call_Real(); - return self->min_dist_mod; + return self->min_distance_mod; } float Lua_Spell::GetMaxDist() { Lua_Safe_Call_Real(); - return self->max_dist; + return self->max_distance; } float Lua_Spell::GetMaxDistMod() { Lua_Safe_Call_Real(); - return self->max_dist_mod; + return self->max_distance_mod; } float Lua_Spell::GetMinRange() { @@ -471,97 +481,105 @@ float Lua_Spell::GetMinRange() { int Lua_Spell::GetDamageShieldType() { Lua_Safe_Call_Int(); - return self->DamageShieldType; + return self->damage_shield_type; +} + +int Lua_Spell::GetRank() { + Lua_Safe_Call_Int(); + return self->rank; } luabind::scope lua_register_spell() { return luabind::class_("Spell") - .def(luabind::constructor<>()) - .def(luabind::constructor()) - .property("null", &Lua_Spell::Null) - .property("valid", &Lua_Spell::Valid) - .def("ID", &Lua_Spell::GetID) - .def("Name", &Lua_Spell::GetName) - .def("Player1", &Lua_Spell::GetPlayer1) - .def("TeleportZone", &Lua_Spell::GetTeleportZone) - .def("YouCast", &Lua_Spell::GetYouCast) - .def("OtherCasts", &Lua_Spell::GetOtherCasts) - .def("CastOnYou", &Lua_Spell::GetCastOnYou) - .def("CastOnOther", &Lua_Spell::GetCastOnOther) - .def("SpellFades", &Lua_Spell::GetSpellFades) - .def("Range", &Lua_Spell::GetRange) - .def("AoeRange", &Lua_Spell::GetAoeRange) - .def("PushBack", &Lua_Spell::GetPushBack) - .def("PushUp", &Lua_Spell::GetPushUp) - .def("CastTime", &Lua_Spell::GetCastTime) - .def("RecoveryTime", &Lua_Spell::GetRecoveryTime) - .def("RecastTime", &Lua_Spell::GetRecastTime) - .def("BuffdurationFormula", &Lua_Spell::GetBuffdurationFormula) - .def("BuffDuration", &Lua_Spell::GetBuffDuration) - .def("AEDuration", &Lua_Spell::GetAEDuration) - .def("Mana", &Lua_Spell::GetMana) - .def("Base", &Lua_Spell::GetBase) - .def("Base2", &Lua_Spell::GetBase2) - .def("Max", &Lua_Spell::GetMax) - .def("Components", &Lua_Spell::GetComponents) - .def("ComponentCounts", &Lua_Spell::GetComponentCounts) - .def("NoexpendReagent", &Lua_Spell::GetNoexpendReagent) - .def("Formula", &Lua_Spell::GetFormula) - .def("GoodEffect", &Lua_Spell::GetGoodEffect) - .def("Activated", &Lua_Spell::GetActivated) - .def("ResistType", &Lua_Spell::GetResistType) - .def("EffectID", &Lua_Spell::GetEffectID) - .def("TargetType", &Lua_Spell::GetTargetType) - .def("BaseDiff", &Lua_Spell::GetBaseDiff) - .def("Skill", &Lua_Spell::GetSkill) - .def("ZoneType", &Lua_Spell::GetZoneType) - .def("EnvironmentType", &Lua_Spell::GetEnvironmentType) - .def("TimeOfDay", &Lua_Spell::GetTimeOfDay) - .def("Classes", &Lua_Spell::GetClasses) - .def("CastingAnim", &Lua_Spell::GetCastingAnim) - .def("SpellAffectIndex", &Lua_Spell::GetSpellAffectIndex) - .def("DisallowSit", &Lua_Spell::GetDisallowSit) - .def("Deities", &Lua_Spell::GetDeities) - .def("Uninterruptable", &Lua_Spell::GetUninterruptable) - .def("ResistDiff", &Lua_Spell::GetResistDiff) - .def("RecourseLink", &Lua_Spell::GetRecourseLink) - .def("ShortBuffBox", &Lua_Spell::GetShortBuffBox) - .def("DescNum", &Lua_Spell::GetDescNum) - .def("EffectDescNum", &Lua_Spell::GetEffectDescNum) - .def("BonusHate", &Lua_Spell::GetBonusHate) - .def("EndurCost", &Lua_Spell::GetEndurCost) - .def("EndurTimerIndex", &Lua_Spell::GetEndurTimerIndex) - .def("HateAdded", &Lua_Spell::GetHateAdded) - .def("EndurUpkeep", &Lua_Spell::GetEndurUpkeep) - .def("NumHits", &Lua_Spell::GetNumHits) - .def("PVPResistBase", &Lua_Spell::GetPVPResistBase) - .def("PVPResistCalc", &Lua_Spell::GetPVPResistCalc) - .def("PVPResistCap", &Lua_Spell::GetPVPResistCap) - .def("SpellCategory", &Lua_Spell::GetSpellCategory) - .def("CanMGB", &Lua_Spell::GetCanMGB) - .def("DispelFlag", &Lua_Spell::GetDispelFlag) - .def("MinResist", &Lua_Spell::GetMinResist) - .def("MaxResist", &Lua_Spell::GetMaxResist) - .def("ViralTargets", &Lua_Spell::GetViralTargets) - .def("ViralTimer", &Lua_Spell::GetViralTimer) - .def("NimbusEffect", &Lua_Spell::GetNimbusEffect) - .def("DirectionalStart", &Lua_Spell::GetDirectionalStart) - .def("DirectionalEnd", &Lua_Spell::GetDirectionalEnd) - .def("SpellGroup", &Lua_Spell::GetSpellGroup) - .def("PowerfulFlag", &Lua_Spell::GetPowerfulFlag) - .def("CastRestriction", &Lua_Spell::GetCastRestriction) - .def("AllowRest", &Lua_Spell::GetAllowRest) - .def("InCombat", &Lua_Spell::GetInCombat) - .def("OutOfCombat", &Lua_Spell::GetOutOfCombat) - .def("AEMaxTargets", &Lua_Spell::GetAEMaxTargets) - .def("MaxTargets", &Lua_Spell::GetMaxTargets) - .def("PersistDeath", &Lua_Spell::GetPersistDeath) - .def("MinDist", &Lua_Spell::GetMinDist) - .def("MinDistMod", &Lua_Spell::GetMinDistMod) - .def("MaxDist", &Lua_Spell::GetMaxDist) - .def("MaxDistMod", &Lua_Spell::GetMaxDistMod) - .def("MinRange", &Lua_Spell::GetMinRange) - .def("DamageShieldType", &Lua_Spell::GetDamageShieldType); + .def(luabind::constructor<>()) + .def(luabind::constructor()) + .property("null", &Lua_Spell::Null) + .property("valid", &Lua_Spell::Valid) + .def("AEDuration", &Lua_Spell::GetAEDuration) + .def("AEMaxTargets", &Lua_Spell::GetAEMaxTargets) + .def("Activated", &Lua_Spell::GetActivated) + .def("AllowRest", &Lua_Spell::GetAllowRest) + .def("AoeRange", &Lua_Spell::GetAoeRange) + .def("Base", &Lua_Spell::GetBase) + .def("Base2", &Lua_Spell::GetBase2) + .def("BaseDiff", &Lua_Spell::GetBaseDiff) + .def("BonusHate", &Lua_Spell::GetBonusHate) + .def("BuffDuration", &Lua_Spell::GetBuffDuration) + .def("BuffdurationFormula", &Lua_Spell::GetBuffdurationFormula) + .def("CanMGB", &Lua_Spell::GetCanMGB) + .def("CastOnOther", &Lua_Spell::GetCastOnOther) + .def("CastOnYou", &Lua_Spell::GetCastOnYou) + .def("CastRestriction", &Lua_Spell::GetCastRestriction) + .def("CastTime", &Lua_Spell::GetCastTime) + .def("CastingAnim", &Lua_Spell::GetCastingAnim) + .def("Classes", &Lua_Spell::GetClasses) + .def("ComponentCounts", &Lua_Spell::GetComponentCounts) + .def("Components", &Lua_Spell::GetComponents) + .def("DamageShieldType", &Lua_Spell::GetDamageShieldType) + .def("Deities", &Lua_Spell::GetDeities) + .def("DescNum", &Lua_Spell::GetDescNum) + .def("DirectionalEnd", &Lua_Spell::GetDirectionalEnd) + .def("DirectionalStart", &Lua_Spell::GetDirectionalStart) + .def("DisallowSit", &Lua_Spell::GetDisallowSit) + .def("DispelFlag", &Lua_Spell::GetDispelFlag) + .def("EffectDescNum", &Lua_Spell::GetEffectDescNum) + .def("EffectID", &Lua_Spell::GetEffectID) + .def("EndurCost", &Lua_Spell::GetEndurCost) + .def("EndurTimerIndex", &Lua_Spell::GetEndurTimerIndex) + .def("EndurUpkeep", &Lua_Spell::GetEndurUpkeep) + .def("EnvironmentType", &Lua_Spell::GetEnvironmentType) + .def("Formula", &Lua_Spell::GetFormula) + .def("GoodEffect", &Lua_Spell::GetGoodEffect) + .def("HateAdded", &Lua_Spell::GetHateAdded) + .def("ID", &Lua_Spell::GetID) + .def("InCombat", &Lua_Spell::GetInCombat) + .def("Mana", &Lua_Spell::GetMana) + .def("Max", &Lua_Spell::GetMax) + .def("MaxDist", &Lua_Spell::GetMaxDist) + .def("MaxDistMod", &Lua_Spell::GetMaxDistMod) + .def("MaxResist", &Lua_Spell::GetMaxResist) + .def("MaxTargets", &Lua_Spell::GetMaxTargets) + .def("MinDist", &Lua_Spell::GetMinDist) + .def("MinDistMod", &Lua_Spell::GetMinDistMod) + .def("MinRange", &Lua_Spell::GetMinRange) + .def("MinResist", &Lua_Spell::GetMinResist) + .def("Name", &Lua_Spell::GetName) + .def("NimbusEffect", &Lua_Spell::GetNimbusEffect) + .def("NoexpendReagent", &Lua_Spell::GetNoexpendReagent) + .def("NumHits", &Lua_Spell::GetNumHits) + .def("OtherCasts", &Lua_Spell::GetOtherCasts) + .def("OutOfCombat", &Lua_Spell::GetOutOfCombat) + .def("PVPDuration", &Lua_Spell::GetPVPDuration) + .def("PVPDurationCap", &Lua_Spell::GetPVPDurationCap) + .def("PVPResistBase", &Lua_Spell::GetPVPResistBase) + .def("PVPResistCalc", &Lua_Spell::GetPVPResistCalc) + .def("PVPResistCap", &Lua_Spell::GetPVPResistCap) + .def("PersistDeath", &Lua_Spell::GetPersistDeath) + .def("Player1", &Lua_Spell::GetPlayer1) + .def("PowerfulFlag", &Lua_Spell::GetPowerfulFlag) + .def("PushBack", &Lua_Spell::GetPushBack) + .def("PushUp", &Lua_Spell::GetPushUp) + .def("Range", &Lua_Spell::GetRange) + .def("Rank", &Lua_Spell::GetRank) + .def("RecastTime", &Lua_Spell::GetRecastTime) + .def("RecourseLink", &Lua_Spell::GetRecourseLink) + .def("RecoveryTime", &Lua_Spell::GetRecoveryTime) + .def("ResistDiff", &Lua_Spell::GetResistDiff) + .def("ResistType", &Lua_Spell::GetResistType) + .def("ShortBuffBox", &Lua_Spell::GetShortBuffBox) + .def("Skill", &Lua_Spell::GetSkill) + .def("SpellAffectIndex", &Lua_Spell::GetSpellAffectIndex) + .def("SpellCategory", &Lua_Spell::GetSpellCategory) + .def("SpellFades", &Lua_Spell::GetSpellFades) + .def("SpellGroup", &Lua_Spell::GetSpellGroup) + .def("TargetType", &Lua_Spell::GetTargetType) + .def("TeleportZone", &Lua_Spell::GetTeleportZone) + .def("TimeOfDay", &Lua_Spell::GetTimeOfDay) + .def("Uninterruptable", &Lua_Spell::GetUninterruptable) + .def("ViralTargets", &Lua_Spell::GetViralTargets) + .def("ViralTimer", &Lua_Spell::GetViralTimer) + .def("YouCast", &Lua_Spell::GetYouCast) + .def("ZoneType", &Lua_Spell::GetZoneType); } #endif diff --git a/zone/lua_spell.h b/zone/lua_spell.h index bfa5a19ed..b9af8a564 100644 --- a/zone/lua_spell.h +++ b/zone/lua_spell.h @@ -83,6 +83,8 @@ public: int GetPVPResistCalc(); int GetPVPResistCap(); int GetSpellCategory(); + int GetPVPDuration(); + int GetPVPDurationCap(); int GetCanMGB(); int GetDispelFlag(); int GetMinResist(); @@ -107,6 +109,7 @@ public: float GetMaxDistMod(); float GetMinRange(); int GetDamageShieldType(); + int GetRank(); }; #endif diff --git a/zone/lua_stat_bonuses.cpp b/zone/lua_stat_bonuses.cpp index d311cf123..9da4a64b4 100644 --- a/zone/lua_stat_bonuses.cpp +++ b/zone/lua_stat_bonuses.cpp @@ -342,7 +342,7 @@ int Lua_StatBonuses::Getadjusted_casting_skill() const { int Lua_StatBonuses::Getreflect_chance() const { Lua_Safe_Call_Int(); - return self->reflect_chance; + return self->reflect[SBIndex::REFLECT_CHANCE]; } uint32 Lua_StatBonuses::GetsingingMod() const { @@ -572,7 +572,7 @@ int32 Lua_StatBonuses::GetProcChanceSPA() const { int32 Lua_StatBonuses::GetExtraAttackChance() const { Lua_Safe_Call_Int(); - return self->ExtraAttackChance; + return self->ExtraAttackChance[0]; } int32 Lua_StatBonuses::GetDoTShielding() const { @@ -677,7 +677,7 @@ int Lua_StatBonuses::GetXPRateMod() const { bool Lua_StatBonuses::GetBlockNextSpell() const { Lua_Safe_Call_Bool(); - return self->BlockNextSpell; + //return self->BlockNextSpell; bonus no longer used due to effect being a focus } bool Lua_StatBonuses::GetImmuneToFlee() const { @@ -987,7 +987,7 @@ int32 Lua_StatBonuses::GetShieldEquipDmgMod() const { bool Lua_StatBonuses::GetTriggerOnValueAmount() const { Lua_Safe_Call_Bool(); - return self->TriggerOnValueAmount; + return self->TriggerOnCastRequirement; } int8 Lua_StatBonuses::GetStunBashChance() const { @@ -1162,7 +1162,7 @@ uint32 Lua_StatBonuses::GetMitigateDotRune(int idx) const { uint32 Lua_StatBonuses::GetManaAbsorbPercentDamage(int idx) const { Lua_Safe_Call_Int(); - return self->ManaAbsorbPercentDamage[idx]; + return self->ManaAbsorbPercentDamage; } int32 Lua_StatBonuses::GetImprovedTaunt(int idx) const { @@ -1287,263 +1287,263 @@ int32 Lua_StatBonuses::GetReduceTradeskillFail(int idx) const { luabind::scope lua_register_stat_bonuses() { return luabind::class_("StatBonuses") - .def(luabind::constructor<>()) - .def("AC", &Lua_StatBonuses::GetAC) - .def("HP", &Lua_StatBonuses::GetHP) - .def("HPRegen", &Lua_StatBonuses::GetHPRegen) - .def("MaxHP", &Lua_StatBonuses::GetMaxHP) - .def("ManaRegen", &Lua_StatBonuses::GetManaRegen) - .def("EnduranceRegen", &Lua_StatBonuses::GetEnduranceRegen) - .def("Mana", &Lua_StatBonuses::GetMana) - .def("Endurance", &Lua_StatBonuses::GetEndurance) - .def("ATK", &Lua_StatBonuses::GetATK) - .def("STR", &Lua_StatBonuses::GetSTR) - .def("STRCapMod", &Lua_StatBonuses::GetSTRCapMod) - .def("HeroicSTR", &Lua_StatBonuses::GetHeroicSTR) - .def("STA", &Lua_StatBonuses::GetSTA) - .def("STACapMod", &Lua_StatBonuses::GetSTACapMod) - .def("HeroicSTA", &Lua_StatBonuses::GetHeroicSTA) - .def("DEX", &Lua_StatBonuses::GetDEX) - .def("DEXCapMod", &Lua_StatBonuses::GetDEXCapMod) - .def("HeroicDEX", &Lua_StatBonuses::GetHeroicDEX) - .def("AGI", &Lua_StatBonuses::GetAGI) - .def("AGICapMod", &Lua_StatBonuses::GetAGICapMod) - .def("HeroicAGI", &Lua_StatBonuses::GetHeroicAGI) - .def("INT", &Lua_StatBonuses::GetINT) - .def("INTCapMod", &Lua_StatBonuses::GetINTCapMod) - .def("HeroicINT", &Lua_StatBonuses::GetHeroicINT) - .def("WIS", &Lua_StatBonuses::GetWIS) - .def("WISCapMod", &Lua_StatBonuses::GetWISCapMod) - .def("HeroicWIS", &Lua_StatBonuses::GetHeroicWIS) - .def("CHA", &Lua_StatBonuses::GetCHA) - .def("CHACapMod", &Lua_StatBonuses::GetCHACapMod) - .def("HeroicCHA", &Lua_StatBonuses::GetHeroicCHA) - .def("MR", &Lua_StatBonuses::GetMR) - .def("MRCapMod", &Lua_StatBonuses::GetMRCapMod) - .def("HeroicMR", &Lua_StatBonuses::GetHeroicMR) - .def("FR", &Lua_StatBonuses::GetFR) - .def("FRCapMod", &Lua_StatBonuses::GetFRCapMod) - .def("HeroicFR", &Lua_StatBonuses::GetHeroicFR) - .def("CR", &Lua_StatBonuses::GetCR) - .def("CRCapMod", &Lua_StatBonuses::GetCRCapMod) - .def("HeroicCR", &Lua_StatBonuses::GetHeroicCR) - .def("PR", &Lua_StatBonuses::GetPR) - .def("PRCapMod", &Lua_StatBonuses::GetPRCapMod) - .def("HeroicPR", &Lua_StatBonuses::GetHeroicPR) - .def("DR", &Lua_StatBonuses::GetDR) - .def("DRCapMod", &Lua_StatBonuses::GetDRCapMod) - .def("HeroicDR", &Lua_StatBonuses::GetHeroicDR) - .def("Corrup", &Lua_StatBonuses::GetCorrup) - .def("CorrupCapMod", &Lua_StatBonuses::GetCorrupCapMod) - .def("HeroicCorrup", &Lua_StatBonuses::GetHeroicCorrup) - .def("DamageShieldSpellID", &Lua_StatBonuses::GetDamageShieldSpellID) - .def("DamageShield", &Lua_StatBonuses::GetDamageShield) - .def("DamageShieldType", &Lua_StatBonuses::GetDamageShieldType) - .def("SpellDamageShield", &Lua_StatBonuses::GetSpellDamageShield) - .def("SpellShield", &Lua_StatBonuses::GetSpellShield) - .def("ReverseDamageShield", &Lua_StatBonuses::GetReverseDamageShield) - .def("ReverseDamageShieldSpellID", &Lua_StatBonuses::GetReverseDamageShieldSpellID) - .def("ReverseDamageShieldType", &Lua_StatBonuses::GetReverseDamageShieldType) - .def("movementspeed", &Lua_StatBonuses::Getmovementspeed) - .def("haste", &Lua_StatBonuses::Gethaste) - .def("hastetype2", &Lua_StatBonuses::Gethastetype2) - .def("hastetype3", &Lua_StatBonuses::Gethastetype3) - .def("inhibitmelee", &Lua_StatBonuses::Getinhibitmelee) - .def("AggroRange", &Lua_StatBonuses::GetAggroRange) - .def("AssistRange", &Lua_StatBonuses::GetAssistRange) - .def("skillmod", &Lua_StatBonuses::Getskillmod) - .def("skillmodmax", &Lua_StatBonuses::Getskillmodmax) - .def("effective_casting_level", &Lua_StatBonuses::Geteffective_casting_level) - .def("adjusted_casting_skill", &Lua_StatBonuses::Getadjusted_casting_skill) - .def("reflect_chance", &Lua_StatBonuses::Getreflect_chance) - .def("singingMod", &Lua_StatBonuses::GetsingingMod) - .def("Amplification", &Lua_StatBonuses::GetAmplification) - .def("brassMod", &Lua_StatBonuses::GetbrassMod) - .def("percussionMod", &Lua_StatBonuses::GetpercussionMod) - .def("windMod", &Lua_StatBonuses::GetwindMod) - .def("stringedMod", &Lua_StatBonuses::GetstringedMod) - .def("songModCap", &Lua_StatBonuses::GetsongModCap) - .def("hatemod", &Lua_StatBonuses::Gethatemod) - .def("EnduranceReduction", &Lua_StatBonuses::GetEnduranceReduction) - .def("StrikeThrough", &Lua_StatBonuses::GetStrikeThrough) - .def("MeleeMitigation", &Lua_StatBonuses::GetMeleeMitigation) - .def("MeleeMitigationEffect", &Lua_StatBonuses::GetMeleeMitigationEffect) - .def("CriticalHitChance", &Lua_StatBonuses::GetCriticalHitChance) - .def("CriticalSpellChance", &Lua_StatBonuses::GetCriticalSpellChance) - .def("SpellCritDmgIncrease", &Lua_StatBonuses::GetSpellCritDmgIncrease) - .def("SpellCritDmgIncNoStack", &Lua_StatBonuses::GetSpellCritDmgIncNoStack) - .def("DotCritDmgIncrease", &Lua_StatBonuses::GetDotCritDmgIncrease) - .def("CriticalHealChance", &Lua_StatBonuses::GetCriticalHealChance) - .def("CriticalHealOverTime", &Lua_StatBonuses::GetCriticalHealOverTime) - .def("CriticalDoTChance", &Lua_StatBonuses::GetCriticalDoTChance) - .def("CrippBlowChance", &Lua_StatBonuses::GetCrippBlowChance) - .def("AvoidMeleeChance", &Lua_StatBonuses::GetAvoidMeleeChance) - .def("AvoidMeleeChanceEffect", &Lua_StatBonuses::GetAvoidMeleeChanceEffect) - .def("RiposteChance", &Lua_StatBonuses::GetRiposteChance) - .def("DodgeChance", &Lua_StatBonuses::GetDodgeChance) - .def("ParryChance", &Lua_StatBonuses::GetParryChance) - .def("DualWieldChance", &Lua_StatBonuses::GetDualWieldChance) - .def("DoubleAttackChance", &Lua_StatBonuses::GetDoubleAttackChance) - .def("TripleAttackChance", &Lua_StatBonuses::GetTripleAttackChance) - .def("DoubleRangedAttack", &Lua_StatBonuses::GetDoubleRangedAttack) - .def("ResistSpellChance", &Lua_StatBonuses::GetResistSpellChance) - .def("ResistFearChance", &Lua_StatBonuses::GetResistFearChance) - .def("Fearless", &Lua_StatBonuses::GetFearless) - .def("IsFeared", &Lua_StatBonuses::GetIsFeared) - .def("IsBlind", &Lua_StatBonuses::GetIsBlind) - .def("StunResist", &Lua_StatBonuses::GetStunResist) - .def("MeleeSkillCheck", &Lua_StatBonuses::GetMeleeSkillCheck) - .def("MeleeSkillCheckSkill", &Lua_StatBonuses::GetMeleeSkillCheckSkill) - .def("HitChance", &Lua_StatBonuses::GetHitChance) - .def("HitChanceEffect", &Lua_StatBonuses::GetHitChanceEffect) - .def("DamageModifier", &Lua_StatBonuses::GetDamageModifier) - .def("DamageModifier2", &Lua_StatBonuses::GetDamageModifier2) - .def("MinDamageModifier", &Lua_StatBonuses::GetMinDamageModifier) - .def("ProcChance", &Lua_StatBonuses::GetProcChance) - .def("ProcChanceSPA", &Lua_StatBonuses::GetProcChanceSPA) - .def("ExtraAttackChance", &Lua_StatBonuses::GetExtraAttackChance) - .def("DoTShielding", &Lua_StatBonuses::GetDoTShielding) - .def("FlurryChance", &Lua_StatBonuses::GetFlurryChance) - .def("HundredHands", &Lua_StatBonuses::GetHundredHands) - .def("MeleeLifetap", &Lua_StatBonuses::GetMeleeLifetap) - .def("Vampirism", &Lua_StatBonuses::GetVampirism) - .def("HealRate", &Lua_StatBonuses::GetHealRate) - .def("MaxHPChange", &Lua_StatBonuses::GetMaxHPChange) - .def("HealAmt", &Lua_StatBonuses::GetHealAmt) - .def("SpellDmg", &Lua_StatBonuses::GetSpellDmg) - .def("Clairvoyance", &Lua_StatBonuses::GetClairvoyance) - .def("DSMitigation", &Lua_StatBonuses::GetDSMitigation) - .def("DSMitigationOffHand", &Lua_StatBonuses::GetDSMitigationOffHand) - .def("TwoHandBluntBlock", &Lua_StatBonuses::GetTwoHandBluntBlock) - .def("ItemManaRegenCap", &Lua_StatBonuses::GetItemManaRegenCap) - .def("GravityEffect", &Lua_StatBonuses::GetGravityEffect) - .def("AntiGate", &Lua_StatBonuses::GetAntiGate) - .def("MagicWeapon", &Lua_StatBonuses::GetMagicWeapon) - .def("IncreaseBlockChance", &Lua_StatBonuses::GetIncreaseBlockChance) - .def("PersistantCasting", &Lua_StatBonuses::GetPersistantCasting) - .def("XPRateMod", &Lua_StatBonuses::GetXPRateMod) - .def("BlockNextSpell", &Lua_StatBonuses::GetBlockNextSpell) - .def("ImmuneToFlee", &Lua_StatBonuses::GetImmuneToFlee) - .def("VoiceGraft", &Lua_StatBonuses::GetVoiceGraft) - .def("SpellProcChance", &Lua_StatBonuses::GetSpellProcChance) - .def("CharmBreakChance", &Lua_StatBonuses::GetCharmBreakChance) - .def("SongRange", &Lua_StatBonuses::GetSongRange) - .def("HPToManaConvert", &Lua_StatBonuses::GetHPToManaConvert) - .def("NegateEffects", &Lua_StatBonuses::GetNegateEffects) - .def("TriggerMeleeThreshold", &Lua_StatBonuses::GetTriggerMeleeThreshold) - .def("TriggerSpellThreshold", &Lua_StatBonuses::GetTriggerSpellThreshold) - .def("ShieldBlock", &Lua_StatBonuses::GetShieldBlock) - .def("BlockBehind", &Lua_StatBonuses::GetBlockBehind) - .def("CriticalRegenDecay", &Lua_StatBonuses::GetCriticalRegenDecay) - .def("CriticalHealDecay", &Lua_StatBonuses::GetCriticalHealDecay) - .def("CriticalDotDecay", &Lua_StatBonuses::GetCriticalDotDecay) - .def("DivineAura", &Lua_StatBonuses::GetDivineAura) - .def("DistanceRemoval", &Lua_StatBonuses::GetDistanceRemoval) - .def("FrenziedDevastation", &Lua_StatBonuses::GetFrenziedDevastation) - .def("NegateIfCombat", &Lua_StatBonuses::GetNegateIfCombat) - .def("Screech", &Lua_StatBonuses::GetScreech) - .def("AlterNPCLevel", &Lua_StatBonuses::GetAlterNPCLevel) - .def("BerserkSPA", &Lua_StatBonuses::GetBerserkSPA) - .def("Metabolism", &Lua_StatBonuses::GetMetabolism) - .def("Sanctuary", &Lua_StatBonuses::GetSanctuary) - .def("FactionModPct", &Lua_StatBonuses::GetFactionModPct) - .def("PC_Pet_Flurry", &Lua_StatBonuses::GetPC_Pet_Flurry) - .def("Packrat", &Lua_StatBonuses::GetPackrat) - .def("BuffSlotIncrease", &Lua_StatBonuses::GetBuffSlotIncrease) - .def("DelayDeath", &Lua_StatBonuses::GetDelayDeath) - .def("BaseMovementSpeed", &Lua_StatBonuses::GetBaseMovementSpeed) - .def("IncreaseRunSpeedCap", &Lua_StatBonuses::GetIncreaseRunSpeedCap) - .def("DoubleSpecialAttack", &Lua_StatBonuses::GetDoubleSpecialAttack) - .def("FrontalStunResist", &Lua_StatBonuses::GetFrontalStunResist) - .def("BindWound", &Lua_StatBonuses::GetBindWound) - .def("MaxBindWound", &Lua_StatBonuses::GetMaxBindWound) - .def("ChannelChanceSpells", &Lua_StatBonuses::GetChannelChanceSpells) - .def("ChannelChanceItems", &Lua_StatBonuses::GetChannelChanceItems) - .def("SeeInvis", &Lua_StatBonuses::GetSeeInvis) - .def("TripleBackstab", &Lua_StatBonuses::GetTripleBackstab) - .def("FrontalBackstabMinDmg", &Lua_StatBonuses::GetFrontalBackstabMinDmg) - .def("FrontalBackstabChance", &Lua_StatBonuses::GetFrontalBackstabChance) - .def("ConsumeProjectile", &Lua_StatBonuses::GetConsumeProjectile) - .def("ForageAdditionalItems", &Lua_StatBonuses::GetForageAdditionalItems) - .def("SalvageChance", &Lua_StatBonuses::GetSalvageChance) - .def("ArcheryDamageModifier", &Lua_StatBonuses::GetArcheryDamageModifier) - .def("SecondaryDmgInc", &Lua_StatBonuses::GetSecondaryDmgInc) - .def("GiveDoubleAttack", &Lua_StatBonuses::GetGiveDoubleAttack) - .def("PetCriticalHit", &Lua_StatBonuses::GetPetCriticalHit) - .def("PetAvoidance", &Lua_StatBonuses::GetPetAvoidance) - .def("CombatStability", &Lua_StatBonuses::GetCombatStability) - .def("DoubleRiposte", &Lua_StatBonuses::GetDoubleRiposte) - .def("Ambidexterity", &Lua_StatBonuses::GetAmbidexterity) - .def("PetMaxHP", &Lua_StatBonuses::GetPetMaxHP) - .def("PetFlurry", &Lua_StatBonuses::GetPetFlurry) - .def("MasteryofPast", &Lua_StatBonuses::GetMasteryofPast) - .def("GivePetGroupTarget", &Lua_StatBonuses::GetGivePetGroupTarget) - .def("RootBreakChance", &Lua_StatBonuses::GetRootBreakChance) - .def("UnfailingDivinity", &Lua_StatBonuses::GetUnfailingDivinity) - .def("ItemHPRegenCap", &Lua_StatBonuses::GetItemHPRegenCap) - .def("OffhandRiposteFail", &Lua_StatBonuses::GetOffhandRiposteFail) - .def("ItemATKCap", &Lua_StatBonuses::GetItemATKCap) - .def("ShieldEquipDmgMod", &Lua_StatBonuses::GetShieldEquipDmgMod) - .def("TriggerOnValueAmount", &Lua_StatBonuses::GetTriggerOnValueAmount) - .def("StunBashChance", &Lua_StatBonuses::GetStunBashChance) - .def("IncreaseChanceMemwipe", &Lua_StatBonuses::GetIncreaseChanceMemwipe) - .def("CriticalMend", &Lua_StatBonuses::GetCriticalMend) - .def("ImprovedReclaimEnergy", &Lua_StatBonuses::GetImprovedReclaimEnergy) - .def("PetMeleeMitigation", &Lua_StatBonuses::GetPetMeleeMitigation) - .def("IllusionPersistence", &Lua_StatBonuses::GetIllusionPersistence) - .def("extra_xtargets", &Lua_StatBonuses::Getextra_xtargets) - .def("ShroudofStealth", &Lua_StatBonuses::GetShroudofStealth) - .def("ReduceFallDamage", &Lua_StatBonuses::GetReduceFallDamage) - .def("TradeSkillMastery", &Lua_StatBonuses::GetTradeSkillMastery) - .def("NoBreakAESneak", &Lua_StatBonuses::GetNoBreakAESneak) - .def("FeignedCastOnChance", &Lua_StatBonuses::GetFeignedCastOnChance) - .def("DivineSaveChance", &Lua_StatBonuses::GetDivineSaveChance) - .def("DeathSave", &Lua_StatBonuses::GetDeathSave) - .def("Accuracy", &Lua_StatBonuses::GetAccuracy) - .def("SkillDmgTaken", &Lua_StatBonuses::GetSkillDmgTaken) - .def("SpellTriggers", &Lua_StatBonuses::GetSpellTriggers) - .def("SpellOnKill", &Lua_StatBonuses::GetSpellOnKill) - .def("SpellOnDeath", &Lua_StatBonuses::GetSpellOnDeath) - .def("CritDmgMod", &Lua_StatBonuses::GetCritDmgMod) - .def("SkillReuseTime", &Lua_StatBonuses::GetSkillReuseTime) - .def("SkillDamageAmount", &Lua_StatBonuses::GetSkillDamageAmount) - .def("HPPercCap", &Lua_StatBonuses::GetHPPercCap) - .def("ManaPercCap", &Lua_StatBonuses::GetManaPercCap) - .def("EndPercCap", &Lua_StatBonuses::GetEndPercCap) - .def("FocusEffects", &Lua_StatBonuses::GetFocusEffects) - .def("FocusEffectsWorn", &Lua_StatBonuses::GetFocusEffectsWorn) - .def("SkillDamageAmount2", &Lua_StatBonuses::GetSkillDamageAmount2) - .def("NegateAttacks", &Lua_StatBonuses::GetNegateAttacks) - .def("MitigateMeleeRune", &Lua_StatBonuses::GetMitigateMeleeRune) - .def("MeleeThresholdGuard", &Lua_StatBonuses::GetMeleeThresholdGuard) - .def("SpellThresholdGuard", &Lua_StatBonuses::GetSpellThresholdGuard) - .def("MitigateSpellRune", &Lua_StatBonuses::GetMitigateSpellRune) - .def("MitigateDotRune", &Lua_StatBonuses::GetMitigateDotRune) - .def("ManaAbsorbPercentDamage", &Lua_StatBonuses::GetManaAbsorbPercentDamage) - .def("ImprovedTaunt", &Lua_StatBonuses::GetImprovedTaunt) - .def("Root", &Lua_StatBonuses::GetRoot) - .def("AbsorbMagicAtt", &Lua_StatBonuses::GetAbsorbMagicAtt) - .def("MeleeRune", &Lua_StatBonuses::GetMeleeRune) - .def("AStacker", &Lua_StatBonuses::GetAStacker) - .def("BStacker", &Lua_StatBonuses::GetBStacker) - .def("CStacker", &Lua_StatBonuses::GetCStacker) - .def("DStacker", &Lua_StatBonuses::GetDStacker) - .def("LimitToSkill", &Lua_StatBonuses::GetLimitToSkill) - .def("SkillProc", &Lua_StatBonuses::GetSkillProc) - .def("SkillProcSuccess", &Lua_StatBonuses::GetSkillProcSuccess) - .def("PC_Pet_Rampage", &Lua_StatBonuses::GetPC_Pet_Rampage) - .def("SkillAttackProc", &Lua_StatBonuses::GetSkillAttackProc) - .def("SlayUndead", &Lua_StatBonuses::GetSlayUndead) - .def("GiveDoubleRiposte", &Lua_StatBonuses::GetGiveDoubleRiposte) - .def("RaiseSkillCap", &Lua_StatBonuses::GetRaiseSkillCap) - .def("SEResist", &Lua_StatBonuses::GetSEResist) - .def("FinishingBlow", &Lua_StatBonuses::GetFinishingBlow) - .def("FinishingBlowLvl", &Lua_StatBonuses::GetFinishingBlowLvl) - .def("HeadShot", &Lua_StatBonuses::GetHeadShot) - .def("HSLevel", &Lua_StatBonuses::GetHSLevel) - .def("Assassinate", &Lua_StatBonuses::GetAssassinate) - .def("AssassinateLevel", &Lua_StatBonuses::GetAssassinateLevel) - .def("ReduceTradeskillFail", &Lua_StatBonuses::GetReduceTradeskillFail); + .def(luabind::constructor<>()) + .def("AbsorbMagicAtt", &Lua_StatBonuses::GetAbsorbMagicAtt) + .def("AC", &Lua_StatBonuses::GetAC) + .def("Accuracy", &Lua_StatBonuses::GetAccuracy) + .def("adjusted_casting_skill", &Lua_StatBonuses::Getadjusted_casting_skill) + .def("AggroRange", &Lua_StatBonuses::GetAggroRange) + .def("AGI", &Lua_StatBonuses::GetAGI) + .def("AGICapMod", &Lua_StatBonuses::GetAGICapMod) + .def("AlterNPCLevel", &Lua_StatBonuses::GetAlterNPCLevel) + .def("Ambidexterity", &Lua_StatBonuses::GetAmbidexterity) + .def("Amplification", &Lua_StatBonuses::GetAmplification) + .def("AntiGate", &Lua_StatBonuses::GetAntiGate) + .def("ArcheryDamageModifier", &Lua_StatBonuses::GetArcheryDamageModifier) + .def("Assassinate", &Lua_StatBonuses::GetAssassinate) + .def("AssassinateLevel", &Lua_StatBonuses::GetAssassinateLevel) + .def("AssistRange", &Lua_StatBonuses::GetAssistRange) + .def("AStacker", &Lua_StatBonuses::GetAStacker) + .def("ATK", &Lua_StatBonuses::GetATK) + .def("AvoidMeleeChance", &Lua_StatBonuses::GetAvoidMeleeChance) + .def("AvoidMeleeChanceEffect", &Lua_StatBonuses::GetAvoidMeleeChanceEffect) + .def("BaseMovementSpeed", &Lua_StatBonuses::GetBaseMovementSpeed) + .def("BerserkSPA", &Lua_StatBonuses::GetBerserkSPA) + .def("BindWound", &Lua_StatBonuses::GetBindWound) + .def("BlockBehind", &Lua_StatBonuses::GetBlockBehind) + .def("BlockNextSpell", &Lua_StatBonuses::GetBlockNextSpell) + .def("brassMod", &Lua_StatBonuses::GetbrassMod) + .def("BStacker", &Lua_StatBonuses::GetBStacker) + .def("BuffSlotIncrease", &Lua_StatBonuses::GetBuffSlotIncrease) + .def("CHA", &Lua_StatBonuses::GetCHA) + .def("CHACapMod", &Lua_StatBonuses::GetCHACapMod) + .def("ChannelChanceItems", &Lua_StatBonuses::GetChannelChanceItems) + .def("ChannelChanceSpells", &Lua_StatBonuses::GetChannelChanceSpells) + .def("CharmBreakChance", &Lua_StatBonuses::GetCharmBreakChance) + .def("Clairvoyance", &Lua_StatBonuses::GetClairvoyance) + .def("CombatStability", &Lua_StatBonuses::GetCombatStability) + .def("ConsumeProjectile", &Lua_StatBonuses::GetConsumeProjectile) + .def("Corrup", &Lua_StatBonuses::GetCorrup) + .def("CorrupCapMod", &Lua_StatBonuses::GetCorrupCapMod) + .def("CR", &Lua_StatBonuses::GetCR) + .def("CRCapMod", &Lua_StatBonuses::GetCRCapMod) + .def("CrippBlowChance", &Lua_StatBonuses::GetCrippBlowChance) + .def("CritDmgMod", &Lua_StatBonuses::GetCritDmgMod) + .def("CriticalDoTChance", &Lua_StatBonuses::GetCriticalDoTChance) + .def("CriticalDotDecay", &Lua_StatBonuses::GetCriticalDotDecay) + .def("CriticalHealChance", &Lua_StatBonuses::GetCriticalHealChance) + .def("CriticalHealDecay", &Lua_StatBonuses::GetCriticalHealDecay) + .def("CriticalHealOverTime", &Lua_StatBonuses::GetCriticalHealOverTime) + .def("CriticalHitChance", &Lua_StatBonuses::GetCriticalHitChance) + .def("CriticalMend", &Lua_StatBonuses::GetCriticalMend) + .def("CriticalRegenDecay", &Lua_StatBonuses::GetCriticalRegenDecay) + .def("CriticalSpellChance", &Lua_StatBonuses::GetCriticalSpellChance) + .def("CStacker", &Lua_StatBonuses::GetCStacker) + .def("DamageModifier", &Lua_StatBonuses::GetDamageModifier) + .def("DamageModifier2", &Lua_StatBonuses::GetDamageModifier2) + .def("DamageShield", &Lua_StatBonuses::GetDamageShield) + .def("DamageShieldSpellID", &Lua_StatBonuses::GetDamageShieldSpellID) + .def("DamageShieldType", &Lua_StatBonuses::GetDamageShieldType) + .def("DeathSave", &Lua_StatBonuses::GetDeathSave) + .def("DelayDeath", &Lua_StatBonuses::GetDelayDeath) + .def("DEX", &Lua_StatBonuses::GetDEX) + .def("DEXCapMod", &Lua_StatBonuses::GetDEXCapMod) + .def("DistanceRemoval", &Lua_StatBonuses::GetDistanceRemoval) + .def("DivineAura", &Lua_StatBonuses::GetDivineAura) + .def("DivineSaveChance", &Lua_StatBonuses::GetDivineSaveChance) + .def("DodgeChance", &Lua_StatBonuses::GetDodgeChance) + .def("DotCritDmgIncrease", &Lua_StatBonuses::GetDotCritDmgIncrease) + .def("DoTShielding", &Lua_StatBonuses::GetDoTShielding) + .def("DoubleAttackChance", &Lua_StatBonuses::GetDoubleAttackChance) + .def("DoubleRangedAttack", &Lua_StatBonuses::GetDoubleRangedAttack) + .def("DoubleRiposte", &Lua_StatBonuses::GetDoubleRiposte) + .def("DoubleSpecialAttack", &Lua_StatBonuses::GetDoubleSpecialAttack) + .def("DR", &Lua_StatBonuses::GetDR) + .def("DRCapMod", &Lua_StatBonuses::GetDRCapMod) + .def("DSMitigation", &Lua_StatBonuses::GetDSMitigation) + .def("DSMitigationOffHand", &Lua_StatBonuses::GetDSMitigationOffHand) + .def("DStacker", &Lua_StatBonuses::GetDStacker) + .def("DualWieldChance", &Lua_StatBonuses::GetDualWieldChance) + .def("effective_casting_level", &Lua_StatBonuses::Geteffective_casting_level) + .def("EndPercCap", &Lua_StatBonuses::GetEndPercCap) + .def("Endurance", &Lua_StatBonuses::GetEndurance) + .def("EnduranceReduction", &Lua_StatBonuses::GetEnduranceReduction) + .def("EnduranceRegen", &Lua_StatBonuses::GetEnduranceRegen) + .def("extra_xtargets", &Lua_StatBonuses::Getextra_xtargets) + .def("ExtraAttackChance", &Lua_StatBonuses::GetExtraAttackChance) + .def("FactionModPct", &Lua_StatBonuses::GetFactionModPct) + .def("Fearless", &Lua_StatBonuses::GetFearless) + .def("FeignedCastOnChance", &Lua_StatBonuses::GetFeignedCastOnChance) + .def("FinishingBlow", &Lua_StatBonuses::GetFinishingBlow) + .def("FinishingBlowLvl", &Lua_StatBonuses::GetFinishingBlowLvl) + .def("FlurryChance", &Lua_StatBonuses::GetFlurryChance) + .def("FocusEffects", &Lua_StatBonuses::GetFocusEffects) + .def("FocusEffectsWorn", &Lua_StatBonuses::GetFocusEffectsWorn) + .def("ForageAdditionalItems", &Lua_StatBonuses::GetForageAdditionalItems) + .def("FR", &Lua_StatBonuses::GetFR) + .def("FRCapMod", &Lua_StatBonuses::GetFRCapMod) + .def("FrenziedDevastation", &Lua_StatBonuses::GetFrenziedDevastation) + .def("FrontalBackstabChance", &Lua_StatBonuses::GetFrontalBackstabChance) + .def("FrontalBackstabMinDmg", &Lua_StatBonuses::GetFrontalBackstabMinDmg) + .def("FrontalStunResist", &Lua_StatBonuses::GetFrontalStunResist) + .def("GiveDoubleAttack", &Lua_StatBonuses::GetGiveDoubleAttack) + .def("GiveDoubleRiposte", &Lua_StatBonuses::GetGiveDoubleRiposte) + .def("GivePetGroupTarget", &Lua_StatBonuses::GetGivePetGroupTarget) + .def("GravityEffect", &Lua_StatBonuses::GetGravityEffect) + .def("haste", &Lua_StatBonuses::Gethaste) + .def("hastetype2", &Lua_StatBonuses::Gethastetype2) + .def("hastetype3", &Lua_StatBonuses::Gethastetype3) + .def("hatemod", &Lua_StatBonuses::Gethatemod) + .def("HeadShot", &Lua_StatBonuses::GetHeadShot) + .def("HealAmt", &Lua_StatBonuses::GetHealAmt) + .def("HealRate", &Lua_StatBonuses::GetHealRate) + .def("HeroicAGI", &Lua_StatBonuses::GetHeroicAGI) + .def("HeroicCHA", &Lua_StatBonuses::GetHeroicCHA) + .def("HeroicCorrup", &Lua_StatBonuses::GetHeroicCorrup) + .def("HeroicCR", &Lua_StatBonuses::GetHeroicCR) + .def("HeroicDEX", &Lua_StatBonuses::GetHeroicDEX) + .def("HeroicDR", &Lua_StatBonuses::GetHeroicDR) + .def("HeroicFR", &Lua_StatBonuses::GetHeroicFR) + .def("HeroicINT", &Lua_StatBonuses::GetHeroicINT) + .def("HeroicMR", &Lua_StatBonuses::GetHeroicMR) + .def("HeroicPR", &Lua_StatBonuses::GetHeroicPR) + .def("HeroicSTA", &Lua_StatBonuses::GetHeroicSTA) + .def("HeroicSTR", &Lua_StatBonuses::GetHeroicSTR) + .def("HeroicWIS", &Lua_StatBonuses::GetHeroicWIS) + .def("HitChance", &Lua_StatBonuses::GetHitChance) + .def("HitChanceEffect", &Lua_StatBonuses::GetHitChanceEffect) + .def("HP", &Lua_StatBonuses::GetHP) + .def("HPPercCap", &Lua_StatBonuses::GetHPPercCap) + .def("HPRegen", &Lua_StatBonuses::GetHPRegen) + .def("HPToManaConvert", &Lua_StatBonuses::GetHPToManaConvert) + .def("HSLevel", &Lua_StatBonuses::GetHSLevel) + .def("HundredHands", &Lua_StatBonuses::GetHundredHands) + .def("IllusionPersistence", &Lua_StatBonuses::GetIllusionPersistence) + .def("ImmuneToFlee", &Lua_StatBonuses::GetImmuneToFlee) + .def("ImprovedReclaimEnergy", &Lua_StatBonuses::GetImprovedReclaimEnergy) + .def("ImprovedTaunt", &Lua_StatBonuses::GetImprovedTaunt) + .def("IncreaseBlockChance", &Lua_StatBonuses::GetIncreaseBlockChance) + .def("IncreaseChanceMemwipe", &Lua_StatBonuses::GetIncreaseChanceMemwipe) + .def("IncreaseRunSpeedCap", &Lua_StatBonuses::GetIncreaseRunSpeedCap) + .def("inhibitmelee", &Lua_StatBonuses::Getinhibitmelee) + .def("INT", &Lua_StatBonuses::GetINT) + .def("INTCapMod", &Lua_StatBonuses::GetINTCapMod) + .def("IsBlind", &Lua_StatBonuses::GetIsBlind) + .def("IsFeared", &Lua_StatBonuses::GetIsFeared) + .def("ItemATKCap", &Lua_StatBonuses::GetItemATKCap) + .def("ItemHPRegenCap", &Lua_StatBonuses::GetItemHPRegenCap) + .def("ItemManaRegenCap", &Lua_StatBonuses::GetItemManaRegenCap) + .def("LimitToSkill", &Lua_StatBonuses::GetLimitToSkill) + .def("MagicWeapon", &Lua_StatBonuses::GetMagicWeapon) + .def("Mana", &Lua_StatBonuses::GetMana) + .def("ManaAbsorbPercentDamage", &Lua_StatBonuses::GetManaAbsorbPercentDamage) + .def("ManaPercCap", &Lua_StatBonuses::GetManaPercCap) + .def("ManaRegen", &Lua_StatBonuses::GetManaRegen) + .def("MasteryofPast", &Lua_StatBonuses::GetMasteryofPast) + .def("MaxBindWound", &Lua_StatBonuses::GetMaxBindWound) + .def("MaxHP", &Lua_StatBonuses::GetMaxHP) + .def("MaxHPChange", &Lua_StatBonuses::GetMaxHPChange) + .def("MeleeLifetap", &Lua_StatBonuses::GetMeleeLifetap) + .def("MeleeMitigation", &Lua_StatBonuses::GetMeleeMitigation) + .def("MeleeMitigationEffect", &Lua_StatBonuses::GetMeleeMitigationEffect) + .def("MeleeRune", &Lua_StatBonuses::GetMeleeRune) + .def("MeleeSkillCheck", &Lua_StatBonuses::GetMeleeSkillCheck) + .def("MeleeSkillCheckSkill", &Lua_StatBonuses::GetMeleeSkillCheckSkill) + .def("MeleeThresholdGuard", &Lua_StatBonuses::GetMeleeThresholdGuard) + .def("Metabolism", &Lua_StatBonuses::GetMetabolism) + .def("MinDamageModifier", &Lua_StatBonuses::GetMinDamageModifier) + .def("MitigateDotRune", &Lua_StatBonuses::GetMitigateDotRune) + .def("MitigateMeleeRune", &Lua_StatBonuses::GetMitigateMeleeRune) + .def("MitigateSpellRune", &Lua_StatBonuses::GetMitigateSpellRune) + .def("movementspeed", &Lua_StatBonuses::Getmovementspeed) + .def("MR", &Lua_StatBonuses::GetMR) + .def("MRCapMod", &Lua_StatBonuses::GetMRCapMod) + .def("NegateAttacks", &Lua_StatBonuses::GetNegateAttacks) + .def("NegateEffects", &Lua_StatBonuses::GetNegateEffects) + .def("NegateIfCombat", &Lua_StatBonuses::GetNegateIfCombat) + .def("NoBreakAESneak", &Lua_StatBonuses::GetNoBreakAESneak) + .def("OffhandRiposteFail", &Lua_StatBonuses::GetOffhandRiposteFail) + .def("Packrat", &Lua_StatBonuses::GetPackrat) + .def("ParryChance", &Lua_StatBonuses::GetParryChance) + .def("PC_Pet_Flurry", &Lua_StatBonuses::GetPC_Pet_Flurry) + .def("PC_Pet_Rampage", &Lua_StatBonuses::GetPC_Pet_Rampage) + .def("percussionMod", &Lua_StatBonuses::GetpercussionMod) + .def("PersistantCasting", &Lua_StatBonuses::GetPersistantCasting) + .def("PetAvoidance", &Lua_StatBonuses::GetPetAvoidance) + .def("PetCriticalHit", &Lua_StatBonuses::GetPetCriticalHit) + .def("PetFlurry", &Lua_StatBonuses::GetPetFlurry) + .def("PetMaxHP", &Lua_StatBonuses::GetPetMaxHP) + .def("PetMeleeMitigation", &Lua_StatBonuses::GetPetMeleeMitigation) + .def("PR", &Lua_StatBonuses::GetPR) + .def("PRCapMod", &Lua_StatBonuses::GetPRCapMod) + .def("ProcChance", &Lua_StatBonuses::GetProcChance) + .def("ProcChanceSPA", &Lua_StatBonuses::GetProcChanceSPA) + .def("RaiseSkillCap", &Lua_StatBonuses::GetRaiseSkillCap) + .def("ReduceFallDamage", &Lua_StatBonuses::GetReduceFallDamage) + .def("ReduceTradeskillFail", &Lua_StatBonuses::GetReduceTradeskillFail) + .def("reflect_chance", &Lua_StatBonuses::Getreflect_chance) + .def("ResistFearChance", &Lua_StatBonuses::GetResistFearChance) + .def("ResistSpellChance", &Lua_StatBonuses::GetResistSpellChance) + .def("ReverseDamageShield", &Lua_StatBonuses::GetReverseDamageShield) + .def("ReverseDamageShieldSpellID", &Lua_StatBonuses::GetReverseDamageShieldSpellID) + .def("ReverseDamageShieldType", &Lua_StatBonuses::GetReverseDamageShieldType) + .def("RiposteChance", &Lua_StatBonuses::GetRiposteChance) + .def("Root", &Lua_StatBonuses::GetRoot) + .def("RootBreakChance", &Lua_StatBonuses::GetRootBreakChance) + .def("SalvageChance", &Lua_StatBonuses::GetSalvageChance) + .def("Sanctuary", &Lua_StatBonuses::GetSanctuary) + .def("Screech", &Lua_StatBonuses::GetScreech) + .def("SecondaryDmgInc", &Lua_StatBonuses::GetSecondaryDmgInc) + .def("SeeInvis", &Lua_StatBonuses::GetSeeInvis) + .def("SEResist", &Lua_StatBonuses::GetSEResist) + .def("ShieldBlock", &Lua_StatBonuses::GetShieldBlock) + .def("ShieldEquipDmgMod", &Lua_StatBonuses::GetShieldEquipDmgMod) + .def("ShroudofStealth", &Lua_StatBonuses::GetShroudofStealth) + .def("singingMod", &Lua_StatBonuses::GetsingingMod) + .def("SkillAttackProc", &Lua_StatBonuses::GetSkillAttackProc) + .def("SkillDamageAmount", &Lua_StatBonuses::GetSkillDamageAmount) + .def("SkillDamageAmount2", &Lua_StatBonuses::GetSkillDamageAmount2) + .def("SkillDmgTaken", &Lua_StatBonuses::GetSkillDmgTaken) + .def("skillmod", &Lua_StatBonuses::Getskillmod) + .def("skillmodmax", &Lua_StatBonuses::Getskillmodmax) + .def("SkillProc", &Lua_StatBonuses::GetSkillProc) + .def("SkillProcSuccess", &Lua_StatBonuses::GetSkillProcSuccess) + .def("SkillReuseTime", &Lua_StatBonuses::GetSkillReuseTime) + .def("SlayUndead", &Lua_StatBonuses::GetSlayUndead) + .def("songModCap", &Lua_StatBonuses::GetsongModCap) + .def("SongRange", &Lua_StatBonuses::GetSongRange) + .def("SpellCritDmgIncNoStack", &Lua_StatBonuses::GetSpellCritDmgIncNoStack) + .def("SpellCritDmgIncrease", &Lua_StatBonuses::GetSpellCritDmgIncrease) + .def("SpellDamageShield", &Lua_StatBonuses::GetSpellDamageShield) + .def("SpellDmg", &Lua_StatBonuses::GetSpellDmg) + .def("SpellOnDeath", &Lua_StatBonuses::GetSpellOnDeath) + .def("SpellOnKill", &Lua_StatBonuses::GetSpellOnKill) + .def("SpellProcChance", &Lua_StatBonuses::GetSpellProcChance) + .def("SpellShield", &Lua_StatBonuses::GetSpellShield) + .def("SpellThresholdGuard", &Lua_StatBonuses::GetSpellThresholdGuard) + .def("SpellTriggers", &Lua_StatBonuses::GetSpellTriggers) + .def("STA", &Lua_StatBonuses::GetSTA) + .def("STACapMod", &Lua_StatBonuses::GetSTACapMod) + .def("STR", &Lua_StatBonuses::GetSTR) + .def("STRCapMod", &Lua_StatBonuses::GetSTRCapMod) + .def("StrikeThrough", &Lua_StatBonuses::GetStrikeThrough) + .def("stringedMod", &Lua_StatBonuses::GetstringedMod) + .def("StunBashChance", &Lua_StatBonuses::GetStunBashChance) + .def("StunResist", &Lua_StatBonuses::GetStunResist) + .def("TradeSkillMastery", &Lua_StatBonuses::GetTradeSkillMastery) + .def("TriggerMeleeThreshold", &Lua_StatBonuses::GetTriggerMeleeThreshold) + .def("TriggerOnValueAmount", &Lua_StatBonuses::GetTriggerOnValueAmount) + .def("TriggerSpellThreshold", &Lua_StatBonuses::GetTriggerSpellThreshold) + .def("TripleAttackChance", &Lua_StatBonuses::GetTripleAttackChance) + .def("TripleBackstab", &Lua_StatBonuses::GetTripleBackstab) + .def("TwoHandBluntBlock", &Lua_StatBonuses::GetTwoHandBluntBlock) + .def("UnfailingDivinity", &Lua_StatBonuses::GetUnfailingDivinity) + .def("Vampirism", &Lua_StatBonuses::GetVampirism) + .def("VoiceGraft", &Lua_StatBonuses::GetVoiceGraft) + .def("windMod", &Lua_StatBonuses::GetwindMod) + .def("WIS", &Lua_StatBonuses::GetWIS) + .def("WISCapMod", &Lua_StatBonuses::GetWISCapMod) + .def("XPRateMod", &Lua_StatBonuses::GetXPRateMod); } #endif diff --git a/zone/main.cpp b/zone/main.cpp index b0d1a9f4d..fa6d47259 100644 --- a/zone/main.cpp +++ b/zone/main.cpp @@ -78,6 +78,7 @@ #include #include "../common/unix.h" #include "zone_store.h" +#include "zone_event_scheduler.h" #endif @@ -101,8 +102,9 @@ QueryServ *QServ = 0; TaskManager *task_manager = 0; NpcScaleManager *npc_scale_manager; QuestParserCollection *parse = 0; -EQEmuLogSys LogSys; -WorldContentService content_service; +EQEmuLogSys LogSys; +ZoneEventScheduler event_scheduler; +WorldContentService content_service; const SPDat_Spell_Struct* spells; int32 SPDAT_RECORDS = -1; const ZoneConfig *Config; @@ -253,9 +255,10 @@ int main(int argc, char** argv) { } /* Register Log System and Settings */ - LogSys.SetGMSayHandler(&Zone::GMSayHookCallBackProcess); - database.LoadLogSettings(LogSys.log_settings); - LogSys.StartFileLogs(); + LogSys.SetDatabase(&database) + ->LoadLogDatabaseSettings() + ->SetGMSayHandler(&Zone::GMSayHookCallBackProcess) + ->StartFileLogs(); /* Guilds */ guild_mgr.SetDatabase(&database); @@ -384,9 +387,13 @@ int main(int argc, char** argv) { LogInfo("Initialized dynamic dictionary entries"); } - content_service.SetExpansionContext(); + content_service.SetDatabase(&database) + ->SetExpansionContext() + ->ReloadContentFlags(); - ZoneStore::LoadContentFlags(); + event_scheduler.SetDatabase(&database)->LoadScheduledEvents(); + + EQ::SayLinkEngine::LoadCachedSaylinks(); #ifdef BOTS LogInfo("Loading bot commands"); @@ -431,6 +438,7 @@ int main(int argc, char** argv) { parse->ReloadQuests(); worldserver.Connect(); + worldserver.SetScheduler(&event_scheduler); Timer InterserverTimer(INTERSERVER_TIMER); // does MySQL pings and auto-reconnect #ifdef EQPROFILE @@ -542,11 +550,11 @@ int main(int argc, char** argv) { entity_list.CorpseProcess(); entity_list.TrapProcess(); entity_list.RaidProcess(); - entity_list.Process(); entity_list.MobProcess(); entity_list.BeaconProcess(); entity_list.EncounterProcess(); + event_scheduler.Process(zone, &content_service); if (zone) { if (!zone->Process()) { diff --git a/zone/merc.cpp b/zone/merc.cpp index 1da2cd1ae..306c1aeec 100644 --- a/zone/merc.cpp +++ b/zone/merc.cpp @@ -862,10 +862,10 @@ int32 Merc::CalcMaxHP() { if (current_hp > max_hp) current_hp = max_hp; - int hp_perc_cap = spellbonuses.HPPercCap[0]; + int hp_perc_cap = spellbonuses.HPPercCap[SBIndex::RESOURCE_PERCENT_CAP]; if(hp_perc_cap) { int curHP_cap = (max_hp * hp_perc_cap) / 100; - if (current_hp > curHP_cap || (spellbonuses.HPPercCap[1] && current_hp > spellbonuses.HPPercCap[1])) + if (current_hp > curHP_cap || (spellbonuses.HPPercCap[SBIndex::RESOURCE_AMOUNT_CAP] && current_hp > spellbonuses.HPPercCap[SBIndex::RESOURCE_AMOUNT_CAP])) current_hp = curHP_cap; } @@ -904,10 +904,10 @@ int32 Merc::CalcMaxMana() current_mana = max_mana; } - int mana_perc_cap = spellbonuses.ManaPercCap[0]; + int mana_perc_cap = spellbonuses.ManaPercCap[SBIndex::RESOURCE_PERCENT_CAP]; if(mana_perc_cap) { int curMana_cap = (max_mana * mana_perc_cap) / 100; - if (current_mana > curMana_cap || (spellbonuses.ManaPercCap[1] && current_mana > spellbonuses.ManaPercCap[1])) + if (current_mana > curMana_cap || (spellbonuses.ManaPercCap[SBIndex::RESOURCE_AMOUNT_CAP] && current_mana > spellbonuses.ManaPercCap[SBIndex::RESOURCE_AMOUNT_CAP])) current_mana = curMana_cap; } @@ -999,10 +999,10 @@ void Merc::CalcMaxEndurance() cur_end = max_end; } - int end_perc_cap = spellbonuses.EndPercCap[0]; + int end_perc_cap = spellbonuses.EndPercCap[SBIndex::RESOURCE_PERCENT_CAP]; if(end_perc_cap) { int curEnd_cap = (max_end * end_perc_cap) / 100; - if (cur_end > curEnd_cap || (spellbonuses.EndPercCap[1] && cur_end > spellbonuses.EndPercCap[1])) + if (cur_end > curEnd_cap || (spellbonuses.EndPercCap[SBIndex::RESOURCE_AMOUNT_CAP] && cur_end > spellbonuses.EndPercCap[SBIndex::RESOURCE_AMOUNT_CAP])) cur_end = curEnd_cap; } } @@ -1117,7 +1117,7 @@ void Merc::DoEnduranceUpkeep() { uint32 buff_count = GetMaxTotalSlots(); for (buffs_i = 0; buffs_i < buff_count; buffs_i++) { if (buffs[buffs_i].spellid != SPELL_UNKNOWN) { - int upkeep = spells[buffs[buffs_i].spellid].EndurUpkeep; + int upkeep = spells[buffs[buffs_i].spellid].endurance_upkeep; if(upkeep > 0) { has_effect = true; if(cost_redux > 0) { @@ -1624,7 +1624,7 @@ void Merc::AI_Process() { } } - int16 ExtraAttackChanceBonus = spellbonuses.ExtraAttackChance + itembonuses.ExtraAttackChance + aabonuses.ExtraAttackChance; + int16 ExtraAttackChanceBonus = spellbonuses.ExtraAttackChance[SBIndex::EXTRA_ATTACK_CHANCE] + itembonuses.ExtraAttackChance[SBIndex::EXTRA_ATTACK_CHANCE] + aabonuses.ExtraAttackChance[SBIndex::EXTRA_ATTACK_CHANCE]; if (GetTarget() && ExtraAttackChanceBonus) { if(zone->random.Roll(ExtraAttackChanceBonus)) @@ -1982,11 +1982,11 @@ bool Merc::AIDoSpellCast(uint16 spellid, Mob* tar, int32 mana_cost, uint32* oDon } else dist2 = DistanceSquared(m_Position, tar->GetPosition()); - if (((((spells[spellid].targettype==ST_GroupTeleport && mercSpell.type==SpellType_Heal) - || spells[spellid].targettype==ST_AECaster - || spells[spellid].targettype==ST_Group - || spells[spellid].targettype==ST_AEBard) - && dist2 <= spells[spellid].aoerange*spells[spellid].aoerange) + if (((((spells[spellid].target_type==ST_GroupTeleport && mercSpell.type==SpellType_Heal) + || spells[spellid].target_type==ST_AECaster + || spells[spellid].target_type==ST_Group + || spells[spellid].target_type==ST_AEBard) + && dist2 <= spells[spellid].aoe_range*spells[spellid].aoe_range) || dist2 <= GetActSpellRange(spellid, spells[spellid].range)*GetActSpellRange(spellid, spells[spellid].range)) && (mana_cost <= GetMana() || GetMana() == GetMaxMana())) { SetRunAnimSpeed(0); @@ -2007,8 +2007,8 @@ bool Merc::AIDoSpellCast(uint16 spellid, Mob* tar, int32 mana_cost, uint32* oDon else { //handle spell recast and recast timers SetSpellTimeCanCast(mercSpell.spellid, spells[spellid].recast_time); - if(spells[spellid].EndurTimerIndex > 0) { - SetSpellRecastTimer(spells[spellid].EndurTimerIndex, spellid, spells[spellid].recast_time); + if(spells[spellid].timer_id > 0) { + SetSpellRecastTimer(spells[spellid].timer_id, spellid, spells[spellid].recast_time); } } @@ -2177,13 +2177,13 @@ bool Merc::AICastSpell(int8 iChance, uint32 iSpellTypes) { itr != buffSpellList.end(); ++itr) { MercSpell selectedMercSpell = *itr; - if(!((spells[selectedMercSpell.spellid].targettype == ST_Target || spells[selectedMercSpell.spellid].targettype == ST_Pet || - spells[selectedMercSpell.spellid].targettype == ST_Group || spells[selectedMercSpell.spellid].targettype == ST_GroupTeleport || - spells[selectedMercSpell.spellid].targettype == ST_Self))) { + if(!((spells[selectedMercSpell.spellid].target_type == ST_Target || spells[selectedMercSpell.spellid].target_type == ST_Pet || + spells[selectedMercSpell.spellid].target_type == ST_Group || spells[selectedMercSpell.spellid].target_type == ST_GroupTeleport || + spells[selectedMercSpell.spellid].target_type == ST_Self))) { continue; } - if(spells[selectedMercSpell.spellid].targettype == ST_Self) { + if(spells[selectedMercSpell.spellid].target_type == ST_Self) { if( !this->IsImmuneToSpell(selectedMercSpell.spellid, this) && (this->CanBuffStack(selectedMercSpell.spellid, mercLevel, true) >= 0)) { @@ -2237,8 +2237,8 @@ bool Merc::AICastSpell(int8 iChance, uint32 iSpellTypes) { //don't cast group spells on pets if(IsGroupSpell(selectedMercSpell.spellid) - || spells[selectedMercSpell.spellid].targettype == ST_Group - || spells[selectedMercSpell.spellid].targettype == ST_GroupTeleport ) { + || spells[selectedMercSpell.spellid].target_type == ST_Group + || spells[selectedMercSpell.spellid].target_type == ST_GroupTeleport ) { continue; } @@ -2337,11 +2337,11 @@ bool Merc::AICastSpell(int8 iChance, uint32 iSpellTypes) { itr != buffSpellList.end(); ++itr) { MercSpell selectedMercSpell = *itr; - if(!(spells[selectedMercSpell.spellid].targettype == ST_Self)) { + if(!(spells[selectedMercSpell.spellid].target_type == ST_Self)) { continue; } - if (spells[selectedMercSpell.spellid].skill == EQ::skills::SkillBackstab && spells[selectedMercSpell.spellid].targettype == ST_Self) { + if (spells[selectedMercSpell.spellid].skill == EQ::skills::SkillBackstab && spells[selectedMercSpell.spellid].target_type == ST_Self) { if(!hidden) { continue; } @@ -2535,7 +2535,7 @@ bool Merc::CheckAENuke(Merc* caster, Mob* tar, uint16 spell_id, uint8 &numTarget for (auto itr = npc_list.begin(); itr != npc_list.end(); ++itr) { NPC* npc = *itr; - if(DistanceSquaredNoZ(npc->GetPosition(), tar->GetPosition()) <= spells[spell_id].aoerange * spells[spell_id].aoerange) { + if(DistanceSquaredNoZ(npc->GetPosition(), tar->GetPosition()) <= spells[spell_id].aoe_range * spells[spell_id].aoe_range) { if(!npc->IsMezzed()) { numTargets++; } @@ -2552,11 +2552,11 @@ bool Merc::CheckAENuke(Merc* caster, Mob* tar, uint16 spell_id, uint8 &numTarget return false; } -int16 Merc::GetFocusEffect(focusType type, uint16 spell_id) { +int32 Merc::GetFocusEffect(focusType type, uint16 spell_id) { - int16 realTotal = 0; - int16 realTotal2 = 0; - int16 realTotal3 = 0; + int32 realTotal = 0; + int32 realTotal2 = 0; + int32 realTotal3 = 0; bool rand_effectiveness = false; //Improved Healing, Damage & Mana Reduction are handled differently in that some are random percentages @@ -2572,10 +2572,10 @@ int16 Merc::GetFocusEffect(focusType type, uint16 spell_id) { const EQ::ItemData* TempItem = nullptr; const EQ::ItemData* UsedItem = nullptr; - uint16 UsedFocusID = 0; - int16 Total = 0; - int16 focus_max = 0; - int16 focus_max_real = 0; + int32 UsedFocusID = 0; + int32 Total = 0; + int32 focus_max = 0; + int32 focus_max_real = 0; //item focus for (int x = EQ::invslot::EQUIPMENT_BEGIN; x <= EQ::invslot::EQUIPMENT_END; ++x) @@ -2623,14 +2623,14 @@ int16 Merc::GetFocusEffect(focusType type, uint16 spell_id) { if (spellbonuses.FocusEffects[type]){ //Spell Focus - int16 Total2 = 0; - int16 focus_max2 = 0; - int16 focus_max_real2 = 0; + int32 Total2 = 0; + int32 focus_max2 = 0; + int32 focus_max_real2 = 0; int buff_tracker = -1; int buff_slot = 0; - uint16 focusspellid = 0; - uint16 focusspell_tracker = 0; + int32 focusspellid = 0; + int32 focusspell_tracker = 0; uint32 buff_max = GetMaxTotalSlots(); for (buff_slot = 0; buff_slot < buff_max; buff_slot++) { focusspellid = buffs[buff_slot].spellid; @@ -2667,7 +2667,7 @@ int16 Merc::GetFocusEffect(focusType type, uint16 spell_id) { realTotal2 = CalcFocusEffect(type, focusspell_tracker, spell_id); // For effects like gift of mana that only fire once, save the spellid into an array that consists of all available buff slots. - if(buff_tracker >= 0 && buffs[buff_tracker].numhits > 0) { + if(buff_tracker >= 0 && buffs[buff_tracker].hit_number > 0) { m_spellHitsLeft[buff_tracker] = focusspell_tracker; } } @@ -2756,19 +2756,6 @@ int32 Merc::GetActSpellCost(uint16 spell_id, int32 cost) return cost; } -int32 Merc::GetActSpellCasttime(uint16 spell_id, int32 casttime) -{ - int32 cast_reducer = 0; - cast_reducer += GetFocusEffect(focusSpellHaste, spell_id); - - if (cast_reducer > RuleI(Spells, MaxCastTimeReduction)) - cast_reducer = RuleI(Spells, MaxCastTimeReduction); - - casttime = (casttime*(100 - cast_reducer)/100); - - return casttime; -} - int8 Merc::GetChanceToCastBySpellType(uint32 spellType) { int mercStance = (int)GetStance(); int8 mercClass = GetClass(); @@ -3060,7 +3047,7 @@ std::list Merc::GetMercSpellsForSpellEffectAndTargetType(Merc* caster } if(IsEffectInSpell(mercSpellList[i].spellid, spellEffect) && caster->CheckStance(mercSpellList[i].stance)) { - if(spells[mercSpellList[i].spellid].targettype == targetType) { + if(spells[mercSpellList[i].spellid].target_type == targetType) { MercSpell MercSpell; MercSpell.spellid = mercSpellList[i].spellid; MercSpell.stance = mercSpellList[i].stance; @@ -3408,9 +3395,9 @@ MercSpell Merc::GetBestMercSpellForAETaunt(Merc* caster) { for (auto mercSpellListItr = mercSpellList.begin(); mercSpellListItr != mercSpellList.end(); ++mercSpellListItr) { // Assuming all the spells have been loaded into this list by level and in descending order - if((spells[mercSpellListItr->spellid].targettype == ST_AECaster - || spells[mercSpellListItr->spellid].targettype == ST_AETarget - || spells[mercSpellListItr->spellid].targettype == ST_UndeadAE) + if((spells[mercSpellListItr->spellid].target_type == ST_AECaster + || spells[mercSpellListItr->spellid].target_type == ST_AETarget + || spells[mercSpellListItr->spellid].target_type == ST_UndeadAE) && CheckSpellRecastTimers(caster, mercSpellListItr->spellid)) { result.spellid = mercSpellListItr->spellid; result.stance = mercSpellListItr->stance; @@ -3443,7 +3430,7 @@ MercSpell Merc::GetBestMercSpellForTaunt(Merc* caster) { for (auto mercSpellListItr = mercSpellList.begin(); mercSpellListItr != mercSpellList.end(); ++mercSpellListItr) { // Assuming all the spells have been loaded into this list by level and in descending order - if((spells[mercSpellListItr->spellid].targettype == ST_Target) + if((spells[mercSpellListItr->spellid].target_type == ST_Target) && CheckSpellRecastTimers(caster, mercSpellListItr->spellid)) { result.spellid = mercSpellListItr->spellid; result.stance = mercSpellListItr->stance; @@ -3913,22 +3900,22 @@ MercSpell Merc::GetBestMercSpellForNukeByTargetResists(Merc* caster, Mob* target // Assuming all the spells have been loaded into this list by level and in descending order if(IsPureNukeSpell(mercSpellListItr->spellid) && !IsAENukeSpell(mercSpellListItr->spellid) && CheckSpellRecastTimers(caster, mercSpellListItr->spellid)) { - if(selectLureNuke && (spells[mercSpellListItr->spellid].ResistDiff < lureResisValue)) { + if(selectLureNuke && (spells[mercSpellListItr->spellid].resist_difficulty < lureResisValue)) { spellSelected = true; } else { if(((target->GetMR() < target->GetCR()) || (target->GetMR() < target->GetFR())) && (GetSpellResistType(mercSpellListItr->spellid) == RESIST_MAGIC) - && (spells[mercSpellListItr->spellid].ResistDiff > lureResisValue)) + && (spells[mercSpellListItr->spellid].resist_difficulty > lureResisValue)) { spellSelected = true; } else if(((target->GetCR() < target->GetMR()) || (target->GetCR() < target->GetFR())) && (GetSpellResistType(mercSpellListItr->spellid) == RESIST_COLD) - && (spells[mercSpellListItr->spellid].ResistDiff > lureResisValue)) + && (spells[mercSpellListItr->spellid].resist_difficulty > lureResisValue)) { spellSelected = true; } else if(((target->GetFR() < target->GetCR()) || (target->GetFR() < target->GetMR())) && (GetSpellResistType(mercSpellListItr->spellid) == RESIST_FIRE) - && (spells[mercSpellListItr->spellid].ResistDiff > lureResisValue)) + && (spells[mercSpellListItr->spellid].resist_difficulty > lureResisValue)) { spellSelected = true; } @@ -4016,9 +4003,9 @@ bool Merc::UseDiscipline(int32 spell_id, int32 target) { if(spell.recast_time > 0) { - if(CheckDisciplineRecastTimers(this, spell_id, spells[spell_id].EndurTimerIndex)) { - if(spells[spell_id].EndurTimerIndex > 0) { - SetDisciplineRecastTimer(spells[spell_id].EndurTimerIndex, spell_id, spell.recast_time); + if(CheckDisciplineRecastTimers(this, spell_id, spells[spell_id].timer_id)) { + if(spells[spell_id].timer_id > 0) { + SetDisciplineRecastTimer(spells[spell_id].timer_id, spell_id, spell.recast_time); } SetSpellTimeCanCast(spell_id, spells[spell_id].recast_time); @@ -4028,8 +4015,8 @@ bool Merc::UseDiscipline(int32 spell_id, int32 target) { } } - if(GetEndurance() > spell.EndurCost) { - SetEndurance(GetEndurance() - spell.EndurCost); + if(GetEndurance() > spell.endurance_cost) { + SetEndurance(GetEndurance() - spell.endurance_cost); } else { //too fatigued to use this skill right now. return(false); @@ -4068,7 +4055,7 @@ bool Merc::CheckSpellRecastTimers(Merc *caster, uint16 spell_id) { if(caster) { MercSpell mercSpell = GetMercSpellBySpellID(caster, spell_id); if(mercSpell.spellid > 0 && mercSpell.time_cancast < Timer::GetCurrentTime()) { //checks spell recast - if(GetSpellRecastTimer(caster, spells[spell_id].EndurTimerIndex) < Timer::GetCurrentTime()) { //checks for spells on the same timer + if(GetSpellRecastTimer(caster, spells[spell_id].timer_id) < Timer::GetCurrentTime()) { //checks for spells on the same timer return true; //can cast spell } } diff --git a/zone/merc.h b/zone/merc.h index efd27f703..44ea7dda9 100644 --- a/zone/merc.h +++ b/zone/merc.h @@ -83,7 +83,6 @@ public: Corpse* GetGroupMemberCorpse(); // Merc Spell Casting Methods - virtual int32 GetActSpellCasttime(uint16 spell_id, int32 casttime); virtual int32 GetActSpellCost(uint16 spell_id, int32 cost); int8 GetChanceToCastBySpellType(uint32 spellType); void SetSpellRecastTimer(uint16 timer_id, uint16 spellid, uint32 recast_delay); @@ -273,7 +272,7 @@ protected: void AddItemBonuses(const EQ::ItemData *item, StatBonuses* newbon); int CalcRecommendedLevelBonus(uint8 level, uint8 reclevel, int basestat); - int16 GetFocusEffect(focusType type, uint16 spell_id); + int32 GetFocusEffect(focusType type, uint16 spell_id); std::vector merc_spells; std::map timers; diff --git a/zone/mob.cpp b/zone/mob.cpp index efaf619ef..306421057 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -26,6 +26,7 @@ #include "worldserver.h" #include "mob_movement_manager.h" #include "water_map.h" +#include "dialogue_window.h" #include #include @@ -108,6 +109,7 @@ Mob::Mob( stunned_timer(0), spun_timer(0), bardsong_timer(6000), + forget_timer(0), gravity_timer(1000), viral_timer(0), m_FearWalkTarget(-999999.0f, -999999.0f, -999999.0f), @@ -131,6 +133,7 @@ Mob::Mob( AI_Init(); SetMoving(false); moved = false; + turning = false; m_RewindLocation = glm::vec3(); m_RelativePosition = glm::vec4(); @@ -188,7 +191,6 @@ Mob::Mob( last_hp_percent = 0; last_hp = 0; - last_max_hp = 0; current_speed = base_runspeed; @@ -232,6 +234,7 @@ Mob::Mob( has_shieldequiped = false; has_twohandbluntequiped = false; has_twohanderequipped = false; + has_duelweaponsequiped = false; can_facestab = false; has_numhits = false; has_MGB = false; @@ -258,7 +261,6 @@ Mob::Mob( MR = CR = FR = DR = PR = Corrup = PhR = 0; ExtraHaste = 0; bEnraged = false; - shield_target = nullptr; current_mana = 0; max_mana = 0; hp_regen = in_hp_regen; @@ -281,24 +283,30 @@ Mob::Mob( InitializeBuffSlots(); + feigned = false; + // clear the proc arrays for (int j = 0; j < MAX_PROCS; j++) { - PermaProcs[j].spellID = SPELL_UNKNOWN; - PermaProcs[j].chance = 0; - PermaProcs[j].base_spellID = SPELL_UNKNOWN; - PermaProcs[j].level_override = -1; - SpellProcs[j].spellID = SPELL_UNKNOWN; - SpellProcs[j].chance = 0; - SpellProcs[j].base_spellID = SPELL_UNKNOWN; - SpellProcs[j].level_override = -1; - DefensiveProcs[j].spellID = SPELL_UNKNOWN; - DefensiveProcs[j].chance = 0; - DefensiveProcs[j].base_spellID = SPELL_UNKNOWN; - DefensiveProcs[j].level_override = -1; - RangedProcs[j].spellID = SPELL_UNKNOWN; - RangedProcs[j].chance = 0; - RangedProcs[j].base_spellID = SPELL_UNKNOWN; - RangedProcs[j].level_override = -1; + PermaProcs[j].spellID = SPELL_UNKNOWN; + PermaProcs[j].chance = 0; + PermaProcs[j].base_spellID = SPELL_UNKNOWN; + PermaProcs[j].level_override = -1; + PermaProcs[j].proc_reuse_time = 0; + SpellProcs[j].spellID = SPELL_UNKNOWN; + SpellProcs[j].chance = 0; + SpellProcs[j].base_spellID = SPELL_UNKNOWN; + SpellProcs[j].proc_reuse_time = 0; + SpellProcs[j].level_override = -1; + DefensiveProcs[j].spellID = SPELL_UNKNOWN; + DefensiveProcs[j].chance = 0; + DefensiveProcs[j].base_spellID = SPELL_UNKNOWN; + DefensiveProcs[j].level_override = -1; + DefensiveProcs[j].proc_reuse_time = 0; + RangedProcs[j].spellID = SPELL_UNKNOWN; + RangedProcs[j].chance = 0; + RangedProcs[j].base_spellID = SPELL_UNKNOWN; + RangedProcs[j].level_override = -1; + RangedProcs[j].proc_reuse_time = 0; } for (int i = EQ::textures::textureBegin; i < EQ::textures::materialCount; i++) { @@ -328,6 +336,7 @@ Mob::Mob( casting_spell_timer_duration = 0; casting_spell_inventory_slot = 0; casting_spell_aa_id = 0; + casting_spell_recast_adjust = 0; target = 0; ActiveProjectileATK = false; @@ -346,6 +355,21 @@ Mob::Mob( ProjectileAtk[i].ammo_slot = 0; ProjectileAtk[i].skill = 0; ProjectileAtk[i].speed_mod = 0.0f; + ProjectileAtk[i].disable_procs = false; + } + + for (int i = 0; i < MAX_FOCUS_PROC_LIMIT_TIMERS; i++) { + focusproclimit_spellid[i] = 0; + focusproclimit_timer[i].Disable(); + } + + for (int i = 0; i < MAX_PROC_LIMIT_TIMERS; i++) { + spell_proclimit_spellid[i] = 0; + spell_proclimit_timer[i].Disable(); + ranged_proclimit_spellid[i] = 0; + ranged_proclimit_timer[i].Disable(); + def_proclimit_spellid[i] = 0; + def_proclimit_timer[i].Disable(); } memset(&itembonuses, 0, sizeof(StatBonuses)); @@ -365,6 +389,7 @@ Mob::Mob( pet_regroup = false; _IsTempPet = false; pet_owner_client = false; + pet_owner_npc = false; pet_targetlock_id = 0; attacked_count = 0; @@ -373,11 +398,13 @@ Mob::Mob( silenced = false; amnesiad = false; inWater = false; - int m; - for (m = 0; m < MAX_SHIELDERS; m++) { - shielder[m].shielder_id = 0; - shielder[m].shielder_bonus = 0; - } + + shield_timer.Disable(); + m_shield_target_id = 0; + m_shielder_id = 0; + m_shield_target_mitigation = 0; + m_shielder_mitigation = 0; + m_shielder_max_distance = 0; destructibleobject = false; wandertype = 0; @@ -401,10 +428,14 @@ Mob::Mob( roamer = false; rooted = false; charmed = false; - has_virus = false; - for (int i = 0; i < MAX_SPELL_TRIGGER * 2; i++) { - viral_spells[i] = 0; - } + + weaponstance.enabled = false; + weaponstance.spellbonus_enabled = false; //Set when bonus is applied + weaponstance.itembonus_enabled = false; //Set when bonus is applied + weaponstance.aabonus_enabled = false; //Controlled by function TogglePassiveAA + weaponstance.spellbonus_buff_spell_id = 0; + weaponstance.itembonus_buff_spell_id = 0; + weaponstance.aabonus_buff_spell_id = 0; pStandingPetOrder = SPO_Follow; pseudo_rooted = false; @@ -453,6 +484,11 @@ Mob::Mob( Vulnerability_Mod[i] = 0; } + for (int i = 0; i < MAX_APPEARANCE_EFFECTS; i++) { + appearance_effects_id[i] = 0; + appearance_effects_slot[i] = 0; + } + emoteid = 0; endur_upkeep = false; degenerating_effects = false; @@ -460,6 +496,11 @@ Mob::Mob( AssistAggro = false; npc_assist_cap = 0; + use_double_melee_round_dmg_bonus = false; + dw_same_delay = 0; + + queue_wearchange_slot = -1; + #ifdef BOTS m_manual_follow = false; #endif @@ -545,13 +586,20 @@ uint32 Mob::GetAppearanceValue(EmuAppearance iAppearance) { void Mob::SetInvisible(uint8 state) { - invisible = state; - SendAppearancePacket(AT_Invis, invisible); - // Invis and hide breaks charms + if (state != Invisibility::Special) { + invisible = state; + SendAppearancePacket(AT_Invis, invisible); + } - auto formerpet = GetPet(); - if (formerpet && formerpet->GetPetType() == petCharmed && (invisible || hidden || improved_hidden)) - formerpet->BuffFadeByEffect(SE_Charm); + // Invis and hide breaks charms + auto pet = GetPet(); + if (pet && pet->GetPetType() == petCharmed && (invisible || hidden || improved_hidden || invisible_animals || invisible_undead)) { + if (RuleB(Pets, LivelikeBreakCharmOnInvis) || IsInvisible(pet)) { + pet->BuffFadeByEffect(SE_Charm); + } + + LogRules("Pets:LivelikeBreakCharmOnInvis for [{}] | Invis [{}] - Hidden [{}] - Shroud of Stealth [{}] - IVA [{}] - IVU [{}]", GetCleanName(), invisible, hidden, improved_hidden, invisible_animals, invisible_undead); + } } //check to see if `this` is invisible to `other` @@ -1316,11 +1364,9 @@ void Mob::CreateHPPacket(EQApplicationPacket* app) { if (ds->hp < GetNextHPEvent()) { - char buf[10]; - snprintf(buf, 9, "%i", GetNextHPEvent()); - buf[9] = '\0'; + std::string buf = fmt::format("{}", GetNextHPEvent()); SetNextHPEvent(-1); - parse->EventNPC(EVENT_HP, CastToNPC(), nullptr, buf, 0); + parse->EventNPC(EVENT_HP, CastToNPC(), nullptr, buf.c_str(), 0); } } @@ -1328,91 +1374,67 @@ void Mob::CreateHPPacket(EQApplicationPacket* app) { if (ds->hp > GetNextIncHPEvent()) { - char buf[10]; - snprintf(buf, 9, "%i", GetNextIncHPEvent()); - buf[9] = '\0'; + std::string buf = fmt::format("{}", GetNextIncHPEvent()); SetNextIncHPEvent(-1); - parse->EventNPC(EVENT_HP, CastToNPC(), nullptr, buf, 1); + parse->EventNPC(EVENT_HP, CastToNPC(), nullptr, buf.c_str(), 1); } } } -void Mob::SendHPUpdate(bool skip_self /*= false*/, bool force_update_all /*= false*/) { +void Mob::SendHPUpdate(bool force_update_all) +{ - /** - * If our HP is different from last HP update call - let's update selves - */ + // If our HP is different from last HP update call - let's update selves if (IsClient()) { - - // delay to allow the client to catch up on buff states - if (max_hp != last_max_hp) { - - last_max_hp = max_hp; - CastToClient()->hp_self_update_throttle_timer.Trigger(); - - return; - } - if (current_hp != last_hp || force_update_all) { - /** - * This is to prevent excessive packet sending under trains/fast combat - */ - if (this->CastToClient()->hp_self_update_throttle_timer.Check() || force_update_all) { - Log(Logs::General, Logs::HPUpdate, - "Mob::SendHPUpdate :: Update HP of self (%s) HP: %i/%i last: %i/%i skip_self: %s", - this->GetCleanName(), - current_hp, - max_hp, - last_hp, - last_max_hp, - (skip_self ? "true" : "false") - ); + LogHPUpdate( + "[SendHPUpdate] Update HP of self [{}] current_hp [{}] max_hp [{}] last_hp [{}]", + GetCleanName(), + current_hp, + max_hp, + last_hp + ); - if (!skip_self || this->CastToClient()->ClientVersion() >= EQ::versions::ClientVersion::SoD) { - auto client_packet = new EQApplicationPacket(OP_HPUpdate, sizeof(SpawnHPUpdate_Struct)); - auto *hp_packet_client = (SpawnHPUpdate_Struct *) client_packet->pBuffer; + auto client_packet = new EQApplicationPacket(OP_HPUpdate, sizeof(SpawnHPUpdate_Struct)); + auto *hp_packet_client = (SpawnHPUpdate_Struct *) client_packet->pBuffer; - hp_packet_client->cur_hp = static_cast(CastToClient()->GetHP() - itembonuses.HP); - hp_packet_client->spawn_id = GetID(); - hp_packet_client->max_hp = CastToClient()->GetMaxHP() - itembonuses.HP; + hp_packet_client->cur_hp = static_cast(CastToClient()->GetHP() - itembonuses.HP); + hp_packet_client->spawn_id = GetID(); + hp_packet_client->max_hp = CastToClient()->GetMaxHP() - itembonuses.HP; - CastToClient()->QueuePacket(client_packet); + CastToClient()->QueuePacket(client_packet); - safe_delete(client_packet); + safe_delete(client_packet); - ResetHPUpdateTimer(); - } + ResetHPUpdateTimer(); - /** - * Used to check if HP has changed to update self next round - */ - last_hp = current_hp; - } + // Used to check if HP has changed to update self next round + last_hp = current_hp; } } auto current_hp_percent = GetIntHPRatio(); - Log(Logs::General, - Logs::HPUpdate, - "Mob::SendHPUpdate :: SendHPUpdate %s HP is %i last %i", - this->GetCleanName(), + LogHPUpdateDetail( + "[SendHPUpdate] Client [{}] HP is [{}] last [{}]", + GetCleanName(), current_hp_percent, - last_hp_percent); + last_hp_percent + ); if (current_hp_percent == last_hp_percent && !force_update_all) { - Log(Logs::General, Logs::HPUpdate, "Mob::SendHPUpdate :: Same HP - skipping update"); + LogHPUpdateDetail("[SendHPUpdate] Same HP for mob [{}] skipping update", GetCleanName()); ResetHPUpdateTimer(); return; } else { if (IsClient() && RuleB(Character, MarqueeHPUpdates)) { - this->CastToClient()->SendHPUpdateMarquee(); + CastToClient()->SendHPUpdateMarquee(); } - Log(Logs::General, Logs::HPUpdate, "Mob::SendHPUpdate :: HP Changed - Send update"); + LogHPUpdate("[SendHPUpdate] HP Changed for mob [{}] send update", GetCleanName()); last_hp_percent = current_hp_percent; } @@ -1422,24 +1444,16 @@ void Mob::SendHPUpdate(bool skip_self /*= false*/, bool force_update_all /*= fal CreateHPPacket(&hp_packet); - /** - * Update those who have us targeted - */ + // update those who have us targeted entity_list.QueueClientsByTarget(this, &hp_packet, false, 0, false, true, EQ::versions::maskAllClients); - /** - * Update those who have us on x-target - */ + // Update those who have us on x-target entity_list.QueueClientsByXTarget(this, &hp_packet, false); - /** - * Update groups using Group LAA health name tag counter - */ + // Update groups using Group LAA health name tag counter entity_list.QueueToGroupsForNPCHealthAA(this, &hp_packet); - /** - * Group - */ + // Group if (IsGrouped()) { group = entity_list.GetGroupByMob(this); if (group) { @@ -1447,9 +1461,7 @@ void Mob::SendHPUpdate(bool skip_self /*= false*/, bool force_update_all /*= fal } } - /** - * Raid - */ + // Raid if (IsClient()) { Raid *raid = entity_list.GetRaidByClient(CastToClient()); if (raid) { @@ -1457,9 +1469,7 @@ void Mob::SendHPUpdate(bool skip_self /*= false*/, bool force_update_all /*= fal } } - /** - * Pet - */ + // Pet if (GetOwner() && GetOwner()->IsClient()) { GetOwner()->CastToClient()->QueuePacket(&hp_packet, false); group = entity_list.GetGroupByClient(GetOwner()->CastToClient()); @@ -1612,42 +1622,667 @@ void Mob::ShowStats(Client* client) { if (IsClient()) { CastToClient()->SendStatsWindow(client, RuleB(Character, UseNewStatsWindow)); - } - else if (IsCorpse()) { + } else if (IsCorpse()) { if (IsPlayerCorpse()) { - client->Message(Chat::White, " CharID: %i PlayerCorpse: %i", CastToCorpse()->GetCharID(), CastToCorpse()->GetCorpseDBID()); + client->Message( + Chat::White, + fmt::format( + "Player Corpse | Character ID: {} ID: {}", + CastToCorpse()->GetCharID(), + CastToCorpse()->GetCorpseDBID() + ).c_str() + ); + } else { + client->Message( + Chat::White, + fmt::format( + "NPC Corpse | ID: {}", + GetID() + ).c_str() + ); } - else { - client->Message(Chat::White, " NPCCorpse", GetID()); - } - } - else { - client->Message(Chat::White, " Level: %i AC: %i Class: %i Size: %1.1f Haste: %i", GetLevel(), ACSum(), GetClass(), GetSize(), GetHaste()); - client->Message(Chat::White, " HP: %i Max HP: %i",GetHP(), GetMaxHP()); - client->Message(Chat::White, " Mana: %i Max Mana: %i", GetMana(), GetMaxMana()); - client->Message(Chat::White, " Total ATK: %i Worn/Spell ATK (Cap %i): %i", GetATK(), RuleI(Character, ItemATKCap), GetATKBonus()); - client->Message(Chat::White, " STR: %i STA: %i DEX: %i AGI: %i INT: %i WIS: %i CHA: %i", GetSTR(), GetSTA(), GetDEX(), GetAGI(), GetINT(), GetWIS(), GetCHA()); - client->Message(Chat::White, " MR: %i PR: %i FR: %i CR: %i DR: %i Corruption: %i PhR: %i", GetMR(), GetPR(), GetFR(), GetCR(), GetDR(), GetCorrup(), GetPhR()); - client->Message(Chat::White, " Race: %i BaseRace: %i Texture: %i HelmTexture: %i Gender: %i BaseGender: %i", GetRace(), GetBaseRace(), GetTexture(), GetHelmTexture(), GetGender(), GetBaseGender()); - if (client->Admin() >= 100) - client->Message(Chat::White, " EntityID: %i PetID: %i OwnerID: %i AIControlled: %i Targetted: %i", GetID(), GetPetID(), GetOwnerID(), IsAIControlled(), targeted); + } else { + NPC* target = CastToNPC(); + std::string target_name = target->GetCleanName(); + std::string target_last_name = target->GetLastName(); + bool has_charmed_stats = ( + target->GetCharmedAccuracy() != 0 || + target->GetCharmedArmorClass() != 0 || + target->GetCharmedAttack() != 0 || + target->GetCharmedAttackDelay() != 0 || + target->GetCharmedAvoidance() != 0 || + target->GetCharmedMaxDamage() != 0 || + target->GetCharmedMinDamage() != 0 + ); - if (IsNPC()) { - NPC *n = CastToNPC(); - uint32 spawngroupid = 0; - if(n->respawn2 != 0) - spawngroupid = n->respawn2->SpawnGroupID(); - client->Message(Chat::White, " NPCID: %u SpawnGroupID: %u Grid: %i LootTable: %u FactionID: %i SpellsID: %u ", GetNPCTypeID(),spawngroupid, n->GetGrid(), n->GetLoottableID(), n->GetNPCFactionID(), n->GetNPCSpellsID()); - client->Message(Chat::White, " Accuracy: %i MerchantID: %i EmoteID: %i Runspeed: %.3f Walkspeed: %.3f", n->GetAccuracyRating(), n->MerchantType, n->GetEmoteID(), static_cast(0.025f * n->GetRunspeed()), static_cast(0.025f * n->GetWalkspeed())); - n->QueryLoot(client); - } - if (IsAIControlled()) { - client->Message(Chat::White, " AggroRange: %1.0f AssistRange: %1.0f", GetAggroRange(), GetAssistRange()); + // Spawn Data + if ( + target->GetGrid() || + target->GetSpawnGroupId() || + target->GetSpawnPointID() + ) { + client->Message( + Chat::White, + fmt::format( + "Spawn | Group: {} Point: {} Grid: {}", + target->GetSpawnGroupId(), + target->GetSpawnPointID(), + target->GetGrid() + ).c_str() + ); } - client->Message(Chat::White, " compute_tohit: %i TotalToHit: %i", compute_tohit(EQ::skills::SkillHandtoHand), GetTotalToHit(EQ::skills::SkillHandtoHand, 0)); - client->Message(Chat::White, " compute_defense: %i TotalDefense: %i", compute_defense(), GetTotalDefense()); - client->Message(Chat::White, " offense: %i mitigation ac: %i", offense(EQ::skills::SkillHandtoHand), GetMitigationAC()); + client->Message( + Chat::White, + fmt::format( + "Spawn | Raid: {} Rare: {}", + target->IsRaidTarget() ? "Yes" : "No", + target->IsRareSpawn() ? "Yes" : "No", + target->GetSkipGlobalLoot() ? "Yes" : "No" + ).c_str() + ); + + client->Message( + Chat::White, + fmt::format( + "Spawn | Skip Global Loot: {} Ignore Despawn: {}", + target->GetSkipGlobalLoot() ? "Yes" : "No", + target->GetIgnoreDespawn() ? "Yes" : "No" + ).c_str() + ); + + client->Message( + Chat::White, + fmt::format( + "Spawn | Findable: {} Trackable: {} Underwater: {}", + target->IsFindable() ? "Yes" : "No", + target->IsTrackable() ? "Yes" : "No", + target->IsUnderwaterOnly() ? "Yes" : "No" + ).c_str() + ); + + client->Message( + Chat::White, + fmt::format( + "Spawn | Stuck Behavior: {} Fly Mode: {}", + target->GetStuckBehavior(), + static_cast(target->GetFlyMode()) + ).c_str() + ); + + client->Message( + Chat::White, + fmt::format( + "Spawn | Aggro NPCs: {} Always Aggro: {}", + target->GetNPCAggro() ? "Yes" : "No", + target->GetAlwaysAggro() ? "Yes" : "No" + ).c_str() + ); + + // NPC + client->Message( + Chat::White, + fmt::format( + "NPC | ID: {} Entity ID: {} Name: {}{} Level: {}", + target->GetNPCTypeID(), + target->GetID(), + target_name, + ( + !target_last_name.empty() ? + fmt::format(" ({})", target_last_name) : + "" + ), + target->GetLevel() + ).c_str() + ); + + // Race / Class / Gender + client->Message( + Chat::White, + fmt::format( + "Race: {} ({}) Class: {} ({}) Gender: {} ({})", + GetRaceIDName(target->GetRace()), + target->GetRace(), + GetClassIDName(target->GetClass()), + target->GetClass(), + GetGenderName(target->GetGender()), + target->GetGender() + ).c_str() + ); + + // Faction + if (target->GetNPCFactionID()) { + auto faction_id = target->GetNPCFactionID(); + auto faction_name = content_db.GetFactionName(faction_id); + client->Message( + Chat::White, + fmt::format( + "Faction: {} ({})", + faction_name, + faction_id + ).c_str() + ); + } + + // Adventure Template + if (target->GetAdventureTemplate()) { + client->Message( + Chat::White, + fmt::format( + "Adventure Template: {}", + target->GetAdventureTemplate() + ).c_str() + ); + } + + // Body + auto bodytype_name = EQ::constants::GetBodyTypeName(target->GetBodyType()); + client->Message( + Chat::White, + fmt::format( + "Body | Size: {:.2f} Type: {}", + target->GetSize(), + ( + bodytype_name.empty() ? + fmt::format( + "{}", + target->GetBodyType() + ) : + fmt::format( + "{} ({})", + bodytype_name, + target->GetBodyType() + ) + ) + ).c_str() + ); + + // Face + client->Message( + Chat::White, + fmt::format( + "Features | Face: {} Eye One: {} Eye Two: {}", + target->GetLuclinFace(), + target->GetEyeColor1(), + target->GetEyeColor2() + ).c_str() + ); + + // Hair + client->Message( + Chat::White, + fmt::format( + "Features | Hair: {} Hair Color: {}", + target->GetHairStyle(), + target->GetHairColor() + ).c_str() + ); + + // Beard + client->Message( + Chat::White, + fmt::format( + "Features | Beard: {} Beard Color: {}", + target->GetBeard(), + target->GetBeardColor() + ).c_str() + ); + + // Drakkin Features + if (target->GetRace() == RACE_DRAKKIN_522) { + client->Message( + Chat::White, + fmt::format( + "Drakkin Features | Heritage: {} Tattoo: {} Details: {}", + target->GetDrakkinHeritage(), + target->GetDrakkinTattoo(), + target->GetDrakkinDetails() + ).c_str() + ); + } + + // Textures + client->Message( + Chat::White, + fmt::format( + "Textures | Armor: {} Helmet: {}", + target->GetTexture(), + target->GetHelmTexture() + ).c_str() + ); + + if ( + target->GetArmTexture() || + target->GetBracerTexture() || + target->GetHandTexture() + ) { + client->Message( + Chat::White, + fmt::format( + "Textures | Arms: {} Bracers: {} Hands: {}", + target->GetArmTexture(), + target->GetBracerTexture(), + target->GetHandTexture() + ).c_str() + ); + } + + if ( + target->GetFeetTexture() || + target->GetLegTexture() + ) { + client->Message( + Chat::White, + fmt::format( + "Textures | Legs: {} Feet: {}", + target->GetLegTexture(), + target->GetFeetTexture() + ).c_str() + ); + } + + // Hero's Forge + if (target->GetHeroForgeModel()) { + client->Message( + Chat::White, + fmt::format( + "Hero's Forge: {}", + target->GetHeroForgeModel() + ).c_str() + ); + } + + // Owner Data + if (target->GetOwner()) { + auto owner_name = target->GetOwner()->GetCleanName(); + auto owner_type = ( + target->GetOwner()->IsNPC() ? + "NPC" : + ( + target->GetOwner()->IsClient() ? + "Client" : + "Other" + ) + ); + auto owner_id = target->GetOwnerID(); + client->Message( + Chat::White, + fmt::format( + "Owner | Name: {} ({}) Type: {}", + owner_name, + owner_id, + owner_type + ).c_str() + ); + } + + // Pet Data + if (target->GetPet()) { + auto pet_name = target->GetPet()->GetCleanName(); + auto pet_id = target->GetPetID(); + client->Message( + Chat::White, + fmt::format( + "Pet | Name: {} ({})", + pet_name, + pet_id + ).c_str() + ); + } + + // Merchant Data + if (target->MerchantType) { + client->Message( + Chat::White, + fmt::format( + "Merchant | ID: {} Currency Type: {}", + target->MerchantType, + target->GetAltCurrencyType() + ).c_str() + ); + } + + // Spell Data + if (target->AI_HasSpells() || target->AI_HasSpellsEffects()) { + client->Message( + Chat::White, + fmt::format( + "Spells | ID: {} Effects ID: {}", + target->GetNPCSpellsID(), + target->GetNPCSpellsEffectsID() + ).c_str() + ); + } + + // Health + client->Message( + Chat::White, + fmt::format( + "Health: {}/{} ({:.2f}%) Regen: {}", + target->GetHP(), + target->GetMaxHP(), + target->GetHPRatio(), + target->GetHPRegen() + ).c_str() + ); + + // Mana + if (target->GetMaxMana() > 0) { + client->Message( + Chat::White, + fmt::format( + "Mana: {}/{} ({:.2f}%) Regen: {}", + target->GetMana(), + target->GetMaxMana(), + target->GetManaRatio(), + target->GetManaRegen() + ).c_str() + ); + } + + // Damage + client->Message( + Chat::White, + fmt::format( + "Damage | Min: {} Max: {}", + target->GetMinDMG(), + target->GetMaxDMG() + ).c_str() + ); + + // Attack Count / Delay + client->Message( + Chat::White, + fmt::format( + "Attack | Count: {} Delay: {}", + target->GetNumberOfAttacks(), + target->GetAttackDelay() + ).c_str() + ); + + // Weapon Textures + client->Message( + Chat::White, + fmt::format( + "Weapon Textures | Primary: {} Secondary: {} Ammo: {}", + target->GetEquipmentMaterial(EQ::textures::weaponPrimary), + target->GetEquipmentMaterial(EQ::textures::weaponSecondary), + target->GetAmmoIDfile() + ).c_str() + ); + + // Weapon Types + client->Message( + Chat::White, + fmt::format( + "Weapon Types | Primary: {} ({}) Secondary: {} ({})", + EQ::skills::GetSkillName(static_cast(target->GetPrimSkill())), + target->GetPrimSkill(), + EQ::skills::GetSkillName(static_cast(target->GetSecSkill())), + target->GetSecSkill() + ).c_str() + ); + + client->Message( + Chat::White, + fmt::format( + "Weapon Types | Ranged: {} ({})", + EQ::skills::GetSkillName(static_cast(target->GetRangedSkill())), + target->GetRangedSkill() + ).c_str() + ); + + // Combat Stats + client->Message( + Chat::White, + fmt::format( + "Combat Stats | Accuracy: {} Armor Class: {} Attack: {}", + target->GetAccuracyRating(), + target->GetAC(), + target->GetATK() + ).c_str() + ); + + client->Message( + Chat::White, + fmt::format( + "Combat Stats | Avoidance: {} Slow Mitigation: {}", + target->GetAvoidanceRating(), + target->GetSlowMitigation() + ).c_str() + ); + + client->Message( + Chat::White, + fmt::format( + "Combat Stats | To Hit: {} Total To Hit: {}", + compute_tohit(EQ::skills::SkillHandtoHand), + GetTotalToHit(EQ::skills::SkillHandtoHand, 0) + ).c_str() + ); + + client->Message( + Chat::White, + fmt::format( + "Combat Stats | Defense: {} Total Defense: {}", + compute_defense(), + GetTotalDefense() + ).c_str() + ); + + client->Message( + Chat::White, + fmt::format( + "Combat Stats | Offense: {} Mitigation Armor Class: {}", + offense(EQ::skills::SkillHandtoHand), + GetMitigationAC() + ).c_str() + ); + + // Stats + client->Message( + Chat::White, + fmt::format( + "Stats | Agility: {} Charisma: {} Dexterity: {} Intelligence: {}", + target->GetAGI(), + target->GetCHA(), + target->GetDEX(), + target->GetINT() + ).c_str() + ); + + client->Message( + Chat::White, + fmt::format( + "Stats | Stamina: {} Strength: {} Wisdom: {}", + target->GetSTA(), + target->GetSTR(), + target->GetWIS() + ).c_str() + ); + + // Charmed Stats + if (has_charmed_stats) { + client->Message( + Chat::White, + fmt::format( + "Charmed Stats | Attack: {} Attack Delay: {}", + target->GetCharmedAttack(), + target->GetCharmedAttackDelay() + ).c_str() + ); + + client->Message( + Chat::White, + fmt::format( + "Charmed Stats | Accuracy: {} Avoidance: {}", + target->GetCharmedAccuracy(), + target->GetCharmedAvoidance() + ).c_str() + ); + + client->Message( + Chat::White, + fmt::format( + "Charmed Stats | Min Damage: {} Max Damage: {}", + target->GetCharmedMinDamage(), + target->GetCharmedMaxDamage() + ).c_str() + ); + } + + // Resists + client->Message( + Chat::White, + fmt::format( + "Resists | Cold: {} Disease: {} Fire: {} Magic: {}", + target->GetCR(), + target->GetDR(), + target->GetFR(), + target->GetMR() + ).c_str() + ); + + client->Message( + Chat::White, + fmt::format( + "Resists | Poison: {} Corruption: {} Physical: {}", + target->GetPR(), + target->GetCorrup(), + target->GetPhR() + ).c_str() + ); + + // Scaling + client->Message( + Chat::White, + fmt::format( + "Scaling | Heal: {} Spell: {}", + target->GetHealScale(), + target->GetSpellScale() + ).c_str() + ); + + // See Invisible / Invisible vs. Undead / Hide / Improved Hide + client->Message( + Chat::White, + fmt::format( + "Can See | Invisible: {} Invisible vs. Undead: {}", + target->SeeInvisible() ? "Yes" : "No", + target->SeeInvisibleUndead() ? "Yes" : "No" + ).c_str() + ); + + client->Message( + Chat::White, + fmt::format( + "Can See | Hide: {} Improved Hide: {}", + target->SeeHide() ? "Yes" : "No", + target->SeeImprovedHide() ? "Yes" : "No" + ).c_str() + ); + + // Aggro / Assist Radius + client->Message( + Chat::White, + fmt::format( + "Radius | Aggro: {} Assist: {}", + target->GetAggroRange(), + target->GetAssistRange() + ).c_str() + ); + + // Emote + if (target->GetEmoteID()) { + client->Message( + Chat::White, + fmt::format( + "Emote: {}", + target->GetEmoteID() + ).c_str() + ); + } + + // Run/Walk Speed + client->Message( + Chat::White, + fmt::format( + "Speed | Run: {} Walk: {}", + target->GetRunspeed(), + target->GetWalkspeed() + ).c_str() + ); + + // Position + client->Message( + Chat::White, + fmt::format( + "Position | {}, {}, {}, {}", + target->GetX(), + target->GetY(), + target->GetZ(), + target->GetHeading() + ).c_str() + ); + + // Experience Modifier + client->Message( + Chat::White, + fmt::format( + "Experience Modifier: {}", + target->GetKillExpMod() + ).c_str() + ); + + // Quest Globals + client->Message( + Chat::White, + fmt::format( + "Quest Globals: {}", + target->qglobal ? "Enabled" : "Disabled" + ).c_str() + ); + + // Proximity + if (target->IsProximitySet()) { + client->Message( + Chat::White, + fmt::format( + "Proximity | Say: {}", + target->proximity->say ? "Enabled" : "Disabled" + ).c_str() + ); + + client->Message( + Chat::White, + fmt::format( + "Proximity X | Min: {} Max: {} Range: {}", + target->GetProximityMinX(), + target->GetProximityMaxX(), + (target->GetProximityMaxX() - target->GetProximityMinX()) + ).c_str() + ); + + client->Message( + Chat::White, + fmt::format( + "Proximity Y | Min: {} Max: {} Range: {}", + target->GetProximityMinY(), + target->GetProximityMaxY(), + (target->GetProximityMaxY() - target->GetProximityMinY()) + ).c_str() + ); + + client->Message( + Chat::White, + fmt::format( + "Proximity Z | Min: {} Max: {} Range: {}", + target->GetProximityMinZ(), + target->GetProximityMaxZ(), + (target->GetProximityMaxZ() - target->GetProximityMinZ()) + ).c_str() + ); + } } } @@ -1691,7 +2326,7 @@ void Mob::ShowBuffs(Client* client) { uint32 buff_count = GetMaxTotalSlots(); for (i=0; i < buff_count; i++) { if (buffs[i].spellid != SPELL_UNKNOWN) { - if (spells[buffs[i].spellid].buffdurationformula == DF_Permanent) + if (spells[buffs[i].spellid].buff_duration_formula == DF_Permanent) client->Message(Chat::White, " %i: %s: Permanent", i, spells[buffs[i].spellid].name); else client->Message(Chat::White, " %i: %s: %i tics left", i, spells[buffs[i].spellid].name, buffs[i].ticsremaining); @@ -1725,7 +2360,7 @@ void Mob::ShowBuffList(Client* client) { uint32 buff_count = GetMaxTotalSlots(); for (i = 0; i < buff_count; i++) { if (buffs[i].spellid != SPELL_UNKNOWN) { - if (spells[buffs[i].spellid].buffdurationformula == DF_Permanent) + if (spells[buffs[i].spellid].buff_duration_formula == DF_Permanent) client->Message(Chat::White, " %i: %s: Permanent", i, spells[buffs[i].spellid].name); else client->Message(Chat::White, " %i: %s: %i tics left", i, spells[buffs[i].spellid].name, buffs[i].ticsremaining); @@ -1761,7 +2396,8 @@ void Mob::SendIllusionPacket( uint32 in_drakkin_heritage, uint32 in_drakkin_tattoo, uint32 in_drakkin_details, - float in_size + float in_size, + bool send_appearance_effects ) { uint8 new_texture = in_texture; @@ -1780,21 +2416,21 @@ void Mob::SendIllusionPacket( race = in_race; if (race == 0) { - race = (use_model) ? use_model : GetBaseRace(); + race = use_model ? use_model : GetBaseRace(); } if (in_gender != 0xFF) { gender = in_gender; } else { - gender = (in_race) ? GetDefaultGender(race, gender) : GetBaseGender(); + gender = in_race ? GetDefaultGender(race, gender) : GetBaseGender(); } - if (in_texture == 0xFF && !IsPlayerRace(in_race)) { + if (in_texture == 0xFF && !IsPlayerRace(race)) { new_texture = GetTexture(); } - if (in_helmtexture == 0xFF && !IsPlayerRace(in_race)) { + if (in_helmtexture == 0xFF && !IsPlayerRace(race)) { new_helmtexture = GetHelmTexture(); } @@ -1827,38 +2463,10 @@ void Mob::SendIllusionPacket( new_drakkin_heritage = drakkin_heritage = CastToClient()->GetBaseHeritage(); new_drakkin_tattoo = drakkin_tattoo = CastToClient()->GetBaseTattoo(); new_drakkin_details = drakkin_details = CastToClient()->GetBaseDetails(); - switch (race) { - case OGRE: - size = 9; - break; - case TROLL: - size = 8; - break; - case VAHSHIR: - case BARBARIAN: - size = 7; - break; - case HALF_ELF: - case WOOD_ELF: - case DARK_ELF: - case FROGLOK: - size = 5; - break; - case DWARF: - size = 4; - break; - case HALFLING: - case GNOME: - size = 3; - break; - default: - size = 6; - break; - } } // update internal values for mob - size = (in_size <= 0.0f) ? GetSize() : in_size; + size = (in_size <= 0.0f) ? GetRaceGenderDefaultHeight(race, gender) : in_size; texture = new_texture; helmtexture = new_helmtexture; haircolor = new_haircolor; @@ -1898,6 +2506,10 @@ void Mob::SendIllusionPacket( /* Refresh armor and tints after send illusion packet */ SendArmorAppearance(); + if (send_appearance_effects) { + SendSavedAppearenceEffects(nullptr); + } + LogSpells( "Illusion: Race [{}] Gender [{}] Texture [{}] HelmTexture [{}] HairColor [{}] BeardColor [{}] EyeColor1 [{}] EyeColor2 [{}] HairStyle [{}] Face [{}] DrakkinHeritage [{}] DrakkinTattoo [{}] DrakkinDetails [{}] Size [{}]", race, @@ -2138,26 +2750,70 @@ uint16 Mob::GetFactionRace() { } uint8 Mob::GetDefaultGender(uint16 in_race, uint8 in_gender) { - if (Mob::IsPlayerRace(in_race) || in_race == 15 || in_race == 50 || in_race == 57 || in_race == 70 || in_race == 98 || in_race == 118 || in_race == 562) { - if (in_gender >= 2) { - // Male default for PC Races + if ( + Mob::IsPlayerRace(in_race) || + in_race == RACE_BROWNIE_15 || + in_race == RACE_KERRAN_23 || + in_race == RACE_LION_50 || + in_race == RACE_DRACNID_57 || + in_race == RACE_ZOMBIE_70 || + in_race == RACE_QEYNOS_CITIZEN_71 || + in_race == RACE_RIVERVALE_CITIZEN_81 || + in_race == RACE_HALAS_CITIZEN_90 || + in_race == RACE_GROBB_CITIZEN_92 || + in_race == RACE_OGGOK_CITIZEN_93 || + in_race == RACE_KALADIM_CITIZEN_94 || + in_race == RACE_ELF_VAMPIRE_98 || + in_race == RACE_FELGUARD_106 || + in_race == RACE_FAYGUARD_112 || + in_race == RACE_ERUDITE_GHOST_118 || + in_race == RACE_IKSAR_CITIZEN_139 || + in_race == RACE_TROLL_CREW_MEMBER_331 || + in_race == RACE_PIRATE_DECKHAND_332 || + in_race == RACE_GNOME_PIRATE_338 || + in_race == RACE_DARK_ELF_PIRATE_339 || + in_race == RACE_OGRE_PIRATE_340 || + in_race == RACE_HUMAN_PIRATE_341 || + in_race == RACE_ERUDITE_PIRATE_342 || + in_race == RACE_UNDEAD_PIRATE_344 || + in_race == RACE_KNIGHT_OF_HATE_351 || + in_race == RACE_WARLOCK_OF_HATE_352 || + in_race == RACE_UNDEAD_VAMPIRE_359 || + in_race == RACE_VAMPIRE_360 || + in_race == RACE_ZOMBIE_471 || + in_race == RACE_VAMPIRE_497 || + in_race == RACE_KERRAN_562 || + in_race == RACE_BROWNIE_568 || + in_race == RACE_HUMAN_566 || + in_race == RACE_ELVEN_GHOST_587 || + in_race == RACE_HUMAN_GHOST_588 || + in_race == RACE_COLDAIN_645 + ) { + if (in_gender >= 2) { // Male default for PC Races return 0; - } - else + } else { return in_gender; - } - else if (in_race == 44 || in_race == 52 || in_race == 55 || in_race == 65 || in_race == 67 || in_race == 88 || in_race == 117 || in_race == 127 || - in_race == 77 || in_race == 78 || in_race == 81 || in_race == 90 || in_race == 92 || in_race == 93 || in_race == 94 || in_race == 106 || in_race == 112 || in_race == 471) { - // Male only races + } + } else if ( + in_race == RACE_FREEPORT_GUARD_44 || + in_race == RACE_MIMIC_52 || + in_race == RACE_HUMAN_BEGGAR_55 || + in_race == RACE_VAMPIRE_65 || + in_race == RACE_HIGHPASS_CITIZEN_67 || + in_race == RACE_NERIAK_CITIZEN_77 || + in_race == RACE_ERUDITE_CITIZEN_78 || + in_race == RACE_CLOCKWORK_GNOME_88 || + in_race == RACE_DWARF_GHOST_117 || + in_race == RACE_SPECTRAL_IKSAR_147 || + in_race == RACE_INVISIBLE_MAN_127 || + in_race == RACE_VAMPYRE_208 || + in_race == RACE_BROKEN_SKULL_PIRATE_333 || + in_race == RACE_ERUDITE_678 + ) { // Male only races return 0; - - } - else if (in_race == 25 || in_race == 56) { - // Female only races + } else if (in_race == RACE_FAIRY_25 || in_race == RACE_PIXIE_56) { // Female only races return 1; - } - else { - // Neutral default for NPC Races + } else { // Neutral default for NPC Races return 2; } } @@ -2214,8 +2870,60 @@ void Mob::SendStunAppearance() safe_delete(outapp); } -void Mob::SendAppearanceEffect(uint32 parm1, uint32 parm2, uint32 parm3, uint32 parm4, uint32 parm5, Client *specific_target){ +void Mob::SendAppearanceEffect(uint32 parm1, uint32 parm2, uint32 parm3, uint32 parm4, uint32 parm5, Client *specific_target, + uint32 value1slot, uint32 value1ground, uint32 value2slot, uint32 value2ground, uint32 value3slot, uint32 value3ground, + uint32 value4slot, uint32 value4ground, uint32 value5slot, uint32 value5ground){ auto outapp = new EQApplicationPacket(OP_LevelAppearance, sizeof(LevelAppearance_Struct)); + + /* Location of the effect from value#slot, this is removed upon mob death/despawn. + 0 = pelvis1 + 1 = pelvis2 + 2 = helm + 3 = Offhand + 4 = Mainhand + 5 = left foot + 6 = right foot + 9 = Face + + value#ground = 1, will place the effect on ground, this is permanenant + */ + + //higher values can crash client + if (value1slot > 9) { + value1slot = 1; + } + if (value2slot > 9) { + value2slot = 1; + } + if (value2slot > 9) { + value2slot = 1; + } + if (value3slot > 9) { + value3slot = 1; + } + if (value4slot > 9) { + value4slot = 1; + } + if (value5slot > 9) { + value5slot = 1; + } + + if (!value1ground && parm1) { + SetAppearenceEffects(value1slot, parm1); + } + if (!value2ground && parm2) { + SetAppearenceEffects(value2slot, parm2); + } + if (!value3ground && parm3) { + SetAppearenceEffects(value3slot, parm3); + } + if (!value4ground && parm4) { + SetAppearenceEffects(value4slot, parm4); + } + if (!value5ground && parm5) { + SetAppearenceEffects(value5slot, parm5); + } + LevelAppearance_Struct* la = (LevelAppearance_Struct*)outapp->pBuffer; la->spawn_id = GetID(); la->parm1 = parm1; @@ -2225,16 +2933,16 @@ void Mob::SendAppearanceEffect(uint32 parm1, uint32 parm2, uint32 parm3, uint32 la->parm5 = parm5; // Note that setting the b values to 0 will disable the related effect from the corresponding parameter. // Setting the a value appears to have no affect at all.s - la->value1a = 1; - la->value1b = 1; - la->value2a = 1; - la->value2b = 1; - la->value3a = 1; - la->value3b = 1; - la->value4a = 1; - la->value4b = 1; - la->value5a = 1; - la->value5b = 1; + la->value1a = value1slot; + la->value1b = value1ground; + la->value2a = value2slot; + la->value2b = value2ground; + la->value3a = value3slot; + la->value3b = value3ground; + la->value4a = value4slot; + la->value4b = value4ground; + la->value5a = value5slot; + la->value5b = value5ground; if(specific_target == nullptr) { entity_list.QueueClients(this,outapp); } @@ -2244,6 +2952,62 @@ void Mob::SendAppearanceEffect(uint32 parm1, uint32 parm2, uint32 parm3, uint32 safe_delete(outapp); } +void Mob::SetAppearenceEffects(int32 slot, int32 value) +{ + for (int i = 0; i < MAX_APPEARANCE_EFFECTS; i++) { + if (!appearance_effects_id[i]) { + appearance_effects_id[i] = value; + appearance_effects_slot[i] = slot; + return; + } + } +} + +void Mob::GetAppearenceEffects() +{ + //used with GM command + if (!appearance_effects_id[0]) { + Message(Chat::Red, "No Appearance Effects exist on this mob"); + return; + } + + for (int i = 0; i < MAX_APPEARANCE_EFFECTS; i++) { + Message(Chat::Red, "ID: %i :: App Effect ID %i :: Slot %i", i, appearance_effects_id[i], appearance_effects_slot[i]); + } +} + +void Mob::ClearAppearenceEffects() +{ + for (int i = 0; i < MAX_APPEARANCE_EFFECTS; i++) { + appearance_effects_id[i] = 0; + appearance_effects_slot[i] = 0; + } +} + +void Mob::SendSavedAppearenceEffects(Client *receiver = nullptr) +{ + if (!appearance_effects_id[0]) { + return; + } + + if (appearance_effects_id[0]) { + SendAppearanceEffect(appearance_effects_id[0], appearance_effects_id[1], appearance_effects_id[2], appearance_effects_id[3], appearance_effects_id[4], receiver, + appearance_effects_slot[0], 0, appearance_effects_slot[1], 0, appearance_effects_slot[2], 0, appearance_effects_slot[3], 0, appearance_effects_slot[4], 0); + } + if (appearance_effects_id[5]) { + SendAppearanceEffect(appearance_effects_id[5], appearance_effects_id[6], appearance_effects_id[7], appearance_effects_id[8], appearance_effects_id[9], receiver, + appearance_effects_slot[5], 0, appearance_effects_slot[6], 0, appearance_effects_slot[7], 0, appearance_effects_slot[8], 0, appearance_effects_slot[9], 0); + } + if (appearance_effects_id[10]) { + SendAppearanceEffect(appearance_effects_id[10], appearance_effects_id[11], appearance_effects_id[12], appearance_effects_id[13], appearance_effects_id[14], receiver, + appearance_effects_slot[10], 0, appearance_effects_slot[11], 0, appearance_effects_slot[12], 0, appearance_effects_slot[13], 0, appearance_effects_slot[14], 0); + } + if (appearance_effects_id[15]) { + SendAppearanceEffect(appearance_effects_id[15], appearance_effects_id[16], appearance_effects_id[17], appearance_effects_id[18], appearance_effects_id[19], receiver, + appearance_effects_slot[15], 0, appearance_effects_slot[16], 0, appearance_effects_slot[17], 0, appearance_effects_slot[18], 0, appearance_effects_slot[19], 0); + } +} + void Mob::SendTargetable(bool on, Client *specific_target) { auto outapp = new EQApplicationPacket(OP_Untargetable, sizeof(Untargetable_Struct)); Untargetable_Struct *ut = (Untargetable_Struct*)outapp->pBuffer; @@ -2286,13 +3050,21 @@ void Mob::CameraEffect(uint32 duration, uint32 intensity, Client *c, bool global safe_delete(outapp); } -void Mob::SendSpellEffect(uint32 effectid, uint32 duration, uint32 finish_delay, bool zone_wide, uint32 unk020, bool perm_effect, Client *c) { +void Mob::SendSpellEffect(uint32 effect_id, uint32 duration, uint32 finish_delay, bool zone_wide, uint32 unk020, bool perm_effect, Client *c, uint32 caster_id, uint32 target_id) { + + if (!caster_id) { + caster_id = GetID(); + } + + if (!target_id) { + target_id = GetID(); + } auto outapp = new EQApplicationPacket(OP_SpellEffect, sizeof(SpellEffect_Struct)); SpellEffect_Struct* se = (SpellEffect_Struct*) outapp->pBuffer; - se->EffectID = effectid; // ID of the Particle Effect - se->EntityID = GetID(); - se->EntityID2 = GetID(); // EntityID again + se->EffectID = effect_id; // ID of the Particle Effect + se->EntityID = caster_id; //casting graphic animation + se->EntityID2 = target_id; // //target graphic animation se->Duration = duration; // In Milliseconds se->FinishDelay = finish_delay; // Seen 0 se->Unknown020 = unk020; // Seen 3000 @@ -2310,8 +3082,8 @@ void Mob::SendSpellEffect(uint32 effectid, uint32 duration, uint32 finish_delay, safe_delete(outapp); if (perm_effect) { - if(!IsNimbusEffectActive(effectid)) { - SetNimbusEffect(effectid); + if(!IsNimbusEffectActive(effect_id)) { + SetNimbusEffect(effect_id); } } @@ -2322,6 +3094,7 @@ void Mob::TempName(const char *newname) char temp_name[64]; char old_name[64]; strn0cpy(old_name, GetName(), 64); + clean_name[0] = 0; if(newname) strn0cpy(temp_name, newname, 64); @@ -2330,7 +3103,6 @@ void Mob::TempName(const char *newname) if(!newname) { strn0cpy(temp_name, GetOrigName(), 64); SetName(temp_name); - //CleanMobName(GetName(), temp_name); strn0cpy(temp_name, GetCleanName(), 64); } @@ -2375,12 +3147,15 @@ const int32& Mob::SetMana(int32 amount) void Mob::SetAppearance(EmuAppearance app, bool iIgnoreSelf) { - if (_appearance == app) + if (_appearance == app) { return; + } + _appearance = app; SendAppearancePacket(AT_Anim, GetAppearanceValue(app), true, iIgnoreSelf); - if (this->IsClient() && this->IsAIControlled()) + if (IsClient() && IsAIControlled()) { SendAppearancePacket(AT_Anim, ANIM_FREEZE, false, false); + } } bool Mob::UpdateActiveLight() @@ -2399,6 +3174,16 @@ bool Mob::UpdateActiveLight() return (m_Light.Level[EQ::lightsource::LightActive] != old_light_level); } +void Mob::SendWearChangeAndLighting(int8 last_texture) { + + for (int i = EQ::textures::textureBegin; i <= last_texture; i++) { + SendWearChange(i); + } + UpdateActiveLight(); + SendAppearancePacket(AT_Light, GetActiveLightType()); + +} + void Mob::ChangeSize(float in_size = 0, bool bNoRestriction) { // Size Code if (!bNoRestriction) @@ -2809,10 +3594,20 @@ bool Mob::HateSummon() { if(summon_level == 1) { entity_list.MessageClose(this, true, 500, Chat::Say, "%s says 'You will not evade me, %s!' ", GetCleanName(), target->GetCleanName() ); + float summoner_zoff = this->GetZOffset(); + float summoned_zoff = target->GetZOffset(); + auto new_pos = m_Position; + new_pos.z -= (summoner_zoff - summoned_zoff); + float angle = new_pos.w - target->GetHeading(); + new_pos.w = target->GetHeading(); + + // probably should be like half melee range, but we can't get melee range nicely because reasons :) + new_pos = target->TryMoveAlong(new_pos, 5.0f, angle); + if (target->IsClient()) - target->CastToClient()->MovePC(zone->GetZoneID(), zone->GetInstanceID(), m_Position.x, m_Position.y, m_Position.z, target->GetHeading(), 0, SummonPC); + target->CastToClient()->MovePC(zone->GetZoneID(), zone->GetInstanceID(), new_pos.x, new_pos.y, new_pos.z, new_pos.w, 0, SummonPC); else - target->GMMove(m_Position.x, m_Position.y, m_Position.z, target->GetHeading()); + target->GMMove(new_pos.x, new_pos.y, new_pos.z, new_pos.w); return true; } else if(summon_level == 2) { @@ -2938,10 +3733,38 @@ void Mob::Say(const char *format, ...) talker = this; } - entity_list.MessageCloseString( - talker, false, 200, 10, - GENERIC_SAY, GetCleanName(), buf - ); + int16 distance = 200; + + if (RuleB(Chat, QuestDialogueUsesDialogueWindow)) { + for (auto &e : entity_list.GetCloseMobList(talker, (distance * distance))) { + Mob *mob = e.second; + + if (!mob->IsClient()) { + continue; + } + + Client *client = mob->CastToClient(); + if (client->GetTarget() && client->GetTarget()->IsMob() && client->GetTarget()->CastToMob() == talker) { + std::string window_markdown = buf; + DialogueWindow::Render(client, window_markdown); + } + } + + return; + } + else if (RuleB(Chat, AutoInjectSaylinksToSay)) { + std::string new_message = EQ::SayLinkEngine::InjectSaylinksIfNotExist(buf); + entity_list.MessageCloseString( + talker, false, distance, Chat::NPCQuestSay, + GENERIC_SAY, GetCleanName(), new_message.c_str() + ); + } + else { + entity_list.MessageCloseString( + talker, false, distance, Chat::NPCQuestSay, + GENERIC_SAY, GetCleanName(), buf + ); + } } // @@ -3111,31 +3934,34 @@ uint32 Mob::GetLevelHP(uint8 tlevel) return multiplier; } -int32 Mob::GetActSpellCasttime(uint16 spell_id, int32 casttime) { +int32 Mob::GetActSpellCasttime(uint16 spell_id, int32 casttime) +{ + int32 cast_reducer = GetFocusEffect(focusSpellHaste, spell_id); + int32 cast_reducer_amt = GetFocusEffect(focusFcCastTimeAmt, spell_id); + int32 cast_reducer_no_limit = GetFocusEffect(focusFcCastTimeMod2, spell_id); - int32 cast_reducer = 0; - cast_reducer += GetFocusEffect(focusSpellHaste, spell_id); - - if (level >= 60 && casttime > 1000) - { - casttime = casttime / 2; - if (casttime < 1000) - casttime = 1000; - } else if (level >= 50 && casttime > 1000) { - int32 cast_deduction = (casttime*(level - 49))/5; - if (cast_deduction > casttime/2) - casttime /= 2; - else - casttime -= cast_deduction; + if (level > 50 && casttime >= 3000 && !spells[spell_id].good_effect && + (GetClass() == RANGER || GetClass() == SHADOWKNIGHT || GetClass() == PALADIN || GetClass() == BEASTLORD)) { + int level_mod = std::min(15, GetLevel() - 50); + cast_reducer += level_mod * 3; } - casttime = (casttime*(100 - cast_reducer)/100); - return casttime; + cast_reducer = std::min(cast_reducer, 50); //Max cast time with focusSpellHaste and level reducer is 50% of cast time. + cast_reducer += cast_reducer_no_limit; + casttime = casttime * (100 - cast_reducer) / 100; + casttime -= cast_reducer_amt; + + return std::max(casttime, 0); + } void Mob::ExecWeaponProc(const EQ::ItemInstance *inst, uint16 spell_id, Mob *on, int level_override) { // Changed proc targets to look up based on the spells goodEffect flag. // This should work for the majority of weapons. + if (!on) { + return; + } + if(spell_id == SPELL_UNKNOWN || on->GetSpecialAbility(NO_HARM_FROM_CLIENT)) { //This is so 65535 doesn't get passed to the client message and to logs because it is not relavant information for debugging. return; @@ -3158,6 +3984,16 @@ void Mob::ExecWeaponProc(const EQ::ItemInstance *inst, uint16 spell_id, Mob *on, return; } + if (IsSilenced() && !IsDiscipline(spell_id)) { + MessageString(Chat::Red, SILENCED_STRING); + return; + } + + if (IsAmnesiad() && IsDiscipline(spell_id)) { + MessageString(Chat::Red, MELEE_SILENCE); + return; + } + if(inst && IsClient()) { //const cast is dirty but it would require redoing a ton of interfaces at this point //It should be safe as we don't have any truly const EQ::ItemInstance floating around anywhere. @@ -3171,21 +4007,25 @@ void Mob::ExecWeaponProc(const EQ::ItemInstance *inst, uint16 spell_id, Mob *on, bool twinproc = false; int32 twinproc_chance = 0; - if(IsClient()) + if (IsClient()) { twinproc_chance = CastToClient()->GetFocusEffect(focusTwincast, spell_id); + } - if(twinproc_chance && zone->random.Roll(twinproc_chance)) + if (twinproc_chance && zone->random.Roll(twinproc_chance)) { twinproc = true; + } if (IsBeneficialSpell(spell_id) && (!IsNPC() || (IsNPC() && CastToNPC()->GetInnateProcSpellID() != spell_id))) { // NPC innate procs don't take this path ever - SpellFinished(spell_id, this, EQ::spells::CastingSlot::Item, 0, -1, spells[spell_id].ResistDiff, true, level_override); - if(twinproc) - SpellOnTarget(spell_id, this, false, false, 0, true, level_override); + SpellFinished(spell_id, this, EQ::spells::CastingSlot::Item, 0, -1, spells[spell_id].resist_difficulty, true, level_override); + if (twinproc) { + SpellFinished(spell_id, this, EQ::spells::CastingSlot::Item, 0, -1, spells[spell_id].resist_difficulty, true, level_override); + } } else if(!(on->IsClient() && on->CastToClient()->dead)) { //dont proc on dead clients - SpellFinished(spell_id, on, EQ::spells::CastingSlot::Item, 0, -1, spells[spell_id].ResistDiff, true, level_override); - if(twinproc) - SpellOnTarget(spell_id, on, false, false, 0, true, level_override); + SpellFinished(spell_id, on, EQ::spells::CastingSlot::Item, 0, -1, spells[spell_id].resist_difficulty, true, level_override); + if (twinproc && (!(on->IsClient() && on->CastToClient()->dead))) { + SpellFinished(spell_id, on, EQ::spells::CastingSlot::Item, 0, -1, spells[spell_id].resist_difficulty, true, level_override); + } } return; } @@ -3240,11 +4080,16 @@ int Mob::GetHaste() h = cap; // 51+ 25 (despite there being higher spells...), 1-50 10 - if (level > 50) // 51+ - h += spellbonuses.hastetype3 > 25 ? 25 : spellbonuses.hastetype3; - else // 1-50 + if (level > 50) { // 51+ + cap = RuleI(Character, Hastev3Cap); + if (spellbonuses.hastetype3 > cap) { + h += cap; + } else { + h += spellbonuses.hastetype3; + } + } else { // 1-50 h += spellbonuses.hastetype3 > 10 ? 10 : spellbonuses.hastetype3; - + } h += ExtraHaste; //GM granted haste. return 100 + h; @@ -3265,8 +4110,8 @@ void Mob::SetTarget(Mob *mob) else if (IsClient()) { parse->EventPlayer(EVENT_TARGET_CHANGE, CastToClient(), "", 0); - if (this->CastToClient()->admin > 200) { - this->DisplayInfo(mob); + if (CastToClient()->admin > AccountStatus::GMMgmt) { + DisplayInfo(mob); } #ifdef BOTS @@ -3278,8 +4123,8 @@ void Mob::SetTarget(Mob *mob) GetOwner()->CastToClient()->UpdateXTargetType(MyPetTarget, mob); } - if (this->IsClient() && this->GetTarget() && this->CastToClient()->hp_other_update_throttle_timer.Check()) { - this->GetTarget()->SendHPUpdate(false, true); + if (IsClient() && GetTarget()) { + GetTarget()->SendHPUpdate(true); } } @@ -3348,10 +4193,10 @@ int Mob::CountDispellableBuffs() if(buffs[x].counters) continue; - if(spells[buffs[x].spellid].goodEffect == 0) + if(spells[buffs[x].spellid].good_effect == 0) continue; - if(buffs[x].spellid != SPELL_UNKNOWN && spells[buffs[x].spellid].buffdurationformula != DF_Permanent) + if(buffs[x].spellid != SPELL_UNKNOWN && spells[buffs[x].spellid].buff_duration_formula != DF_Permanent) val++; } return val; @@ -3370,9 +4215,9 @@ int Mob::GetSnaredAmount() for(int j = 0; j < EFFECT_COUNT; j++) { - if (spells[buffs[i].spellid].effectid[j] == SE_MovementSpeed) + if (spells[buffs[i].spellid].effect_id[j] == SE_MovementSpeed) { - int val = CalcSpellEffectValue_formula(spells[buffs[i].spellid].formula[j], spells[buffs[i].spellid].base[j], spells[buffs[i].spellid].max[j], buffs[i].casterlevel, buffs[i].spellid); + int val = CalcSpellEffectValue_formula(spells[buffs[i].spellid].formula[j], spells[buffs[i].spellid].base_value[j], spells[buffs[i].spellid].max_value[j], buffs[i].casterlevel, buffs[i].spellid); //int effect = CalcSpellEffectValue(buffs[i].spellid, spells[buffs[i].spellid].effectid[j], buffs[i].casterlevel); if (val < 0 && std::abs(val) > worst_snare) worst_snare = std::abs(val); @@ -3385,15 +4230,17 @@ int Mob::GetSnaredAmount() void Mob::TriggerDefensiveProcs(Mob *on, uint16 hand, bool FromSkillProc, int damage) { - if (!on) + if (!on) { return; + } - if (!FromSkillProc) + if (!FromSkillProc) { on->TryDefensiveProc(this, hand); + } //Defensive Skill Procs if (damage < 0 && damage >= -4) { - uint16 skillinuse = 0; + EQ::skills::SkillType skillinuse = EQ::skills::SkillBlock; switch (damage) { case (-1): skillinuse = EQ::skills::SkillBlock; @@ -3412,11 +4259,15 @@ void Mob::TriggerDefensiveProcs(Mob *on, uint16 hand, bool FromSkillProc, int da break; } - if (on->HasSkillProcs()) - on->TrySkillProc(this, skillinuse, 0, false, hand, true); + TryCastOnSkillUse(on, skillinuse); - if (on->HasSkillProcSuccess()) + if (on->HasSkillProcs()) { + on->TrySkillProc(this, skillinuse, 0, false, hand, true); + } + + if (on && on->HasSkillProcSuccess()) { on->TrySkillProc(this, skillinuse, 0, true, hand, true); + } } } @@ -3489,180 +4340,91 @@ void Mob::SetNimbusEffect(uint32 nimbus_effect) } } -void Mob::TryTriggerOnCast(uint32 spell_id, bool aa_trigger) -{ - if(!IsValidSpell(spell_id)) - return; - - if (aabonuses.SpellTriggers[0] || spellbonuses.SpellTriggers[0] || itembonuses.SpellTriggers[0]){ - - for(int i = 0; i < MAX_SPELL_TRIGGER; i++){ - - if(aabonuses.SpellTriggers[i] && IsClient()) - TriggerOnCast(aabonuses.SpellTriggers[i], spell_id,1); - - if(spellbonuses.SpellTriggers[i]) - TriggerOnCast(spellbonuses.SpellTriggers[i], spell_id,0); - - if(itembonuses.SpellTriggers[i]) - TriggerOnCast(spellbonuses.SpellTriggers[i], spell_id,0); - } - } -} - -void Mob::TriggerOnCast(uint32 focus_spell, uint32 spell_id, bool aa_trigger) -{ - if (!IsValidSpell(focus_spell) || !IsValidSpell(spell_id)) - return; - - uint32 trigger_spell_id = 0; - - if (aa_trigger && IsClient()) { - // focus_spell = aaid - auto rank = zone->GetAlternateAdvancementRank(focus_spell); - if (rank) - trigger_spell_id = CastToClient()->CalcAAFocus(focusTriggerOnCast, *rank, spell_id); - - if (IsValidSpell(trigger_spell_id) && GetTarget()) - SpellFinished(trigger_spell_id, GetTarget(), EQ::spells::CastingSlot::Item, 0, -1, - spells[trigger_spell_id].ResistDiff); - } - - else { - trigger_spell_id = CalcFocusEffect(focusTriggerOnCast, focus_spell, spell_id); - - if (IsValidSpell(trigger_spell_id) && GetTarget()) { - SpellFinished(trigger_spell_id, GetTarget(), EQ::spells::CastingSlot::Item, 0, -1, - spells[trigger_spell_id].ResistDiff); - CheckNumHitsRemaining(NumHit::MatchingSpells, -1, focus_spell); - } - } -} - bool Mob::TrySpellTrigger(Mob *target, uint32 spell_id, int effect) { - if(!target || !IsValidSpell(spell_id)) + if (!target || !IsValidSpell(spell_id)) return false; - int spell_trig = 0; - // Count all the percentage chances to trigger for all effects - for(int i = 0; i < EFFECT_COUNT; i++) - { - if (spells[spell_id].effectid[i] == SE_SpellTrigger) - spell_trig += spells[spell_id].base[i]; - } - // If all the % add to 100, then only one of the effects can fire but one has to fire. - if (spell_trig == 100) - { - int trig_chance = 100; - for(int i = 0; i < EFFECT_COUNT; i++) - { - if (spells[spell_id].effectid[i] == SE_SpellTrigger) - { - if(zone->random.Int(0, trig_chance) <= spells[spell_id].base[i]) - { - // If we trigger an effect then its over. - if (IsValidSpell(spells[spell_id].base2[i])){ - SpellFinished(spells[spell_id].base2[i], target, EQ::spells::CastingSlot::Item, 0, -1, spells[spells[spell_id].base2[i]].ResistDiff); - return true; - } - } - else - { - // Increase the chance to fire for the next effect, if all effects fail, the final effect will fire. - trig_chance -= spells[spell_id].base[i]; - } - } + /*The effects SE_SpellTrigger (SPA 340) and SE_Chance_Best_in_Spell_Grp (SPA 469) work as follows, you typically will have 2-3 different spells each with their own + chance to be triggered with all chances equaling up to 100 pct, with only 1 spell out of the group being ultimately cast. + (ie Effect1 trigger spellA with 30% chance, Effect2 triggers spellB with 20% chance, Effect3 triggers spellC with 50% chance). + The following function ensures a stastically accurate chance for each spell to be cast based on their chance values. These effects are also used in spells where there + is only 1 effect using the trigger effect. In those situations we simply roll a chance for that spell to be cast once. + Note: Both SPA 340 and 469 can be in same spell and both cummulative add up to 100 pct chances. SPA469 only difference being the spell cast will + be "best in spell group", instead of a defined spell_id.*/ - } - } - // if the chances don't add to 100, then each effect gets a chance to fire, chance for no trigger as well. - else + int chance_array[EFFECT_COUNT] = {}; + int total_chance = 0; + int effect_slot = effect; + bool CastSpell = false; + + for (int i = 0; i < EFFECT_COUNT; i++) { - if(zone->random.Int(0, 100) <= spells[spell_id].base[effect]) - { - if (IsValidSpell(spells[spell_id].base2[effect])){ - SpellFinished(spells[spell_id].base2[effect], target, EQ::spells::CastingSlot::Item, 0, -1, spells[spells[spell_id].base2[effect]].ResistDiff); - return true; //Only trigger once of these per spell effect. + if (spells[spell_id].effect_id[i] == SE_SpellTrigger || spells[spell_id].effect_id[i] == SE_Chance_Best_in_Spell_Grp) + total_chance += spells[spell_id].base_value[i]; + } + + if (total_chance == 100) + { + int current_chance = 0; + int cummulative_chance = 0; + + for (int i = 0; i < EFFECT_COUNT; i++){ + //Find spells with SPA 340 and add the cummulative percent chances to the roll array + if ((spells[spell_id].effect_id[i] == SE_SpellTrigger) || (spells[spell_id].effect_id[i] == SE_Chance_Best_in_Spell_Grp)){ + + cummulative_chance = current_chance + spells[spell_id].base_value[i]; + chance_array[i] = cummulative_chance; + current_chance = cummulative_chance; + } + } + int random_roll = zone->random.Int(1, 100); + //Determine which spell out of the group of the spells (each with own percent chance out of 100) will be cast based on a single roll. + for (int i = 0; i < EFFECT_COUNT; i++){ + if (chance_array[i] != 0 && random_roll <= chance_array[i]) { + effect_slot = i; + CastSpell = true; + break; } } } + + //If the chances don't add to 100, then each effect gets a chance to fire, chance for no trigger as well. + else if (zone->random.Roll(spells[spell_id].base_value[effect])) { + CastSpell = true; //In this case effect_slot is what was passed into function. + } + + if (CastSpell) { + if (spells[spell_id].effect_id[effect_slot] == SE_SpellTrigger && IsValidSpell(spells[spell_id].limit_value[effect_slot])) { + SpellFinished(spells[spell_id].limit_value[effect_slot], target, EQ::spells::CastingSlot::Item, 0, -1, spells[spells[spell_id].limit_value[effect_slot]].resist_difficulty); + return true; + } + else if (IsClient() & spells[spell_id].effect_id[effect_slot] == SE_Chance_Best_in_Spell_Grp) { + uint32 best_spell_id = CastToClient()->GetHighestScribedSpellinSpellGroup(spells[spell_id].limit_value[effect_slot]); + if (IsValidSpell(best_spell_id)) { + SpellFinished(best_spell_id, target, EQ::spells::CastingSlot::Item, 0, -1, spells[best_spell_id].resist_difficulty); + } + return true;//Do nothing if you don't have the any spell in spell group scribed. + } + } + return false; } -void Mob::TryTriggerOnValueAmount(bool IsHP, bool IsMana, bool IsEndur, bool IsPet) +void Mob::TryTriggerOnCastRequirement() { - /* - At present time there is no obvious difference between ReqTarget and ReqCaster - ReqTarget is typically used in spells cast on a target where the trigger occurs on that target. - ReqCaster is typically self only spells where the triggers on self. - Regardless both trigger on the owner of the buff. - */ - - /* - Base2 Range: 1004 = Below < 80% HP - Base2 Range: 500-520 = Below (base2 - 500)*5 HP - Base2 Range: 521 = Below (?) Mana UKNOWN - Will assume its 20% unless proven otherwise - Base2 Range: 522 = Below (40%) Endurance - Base2 Range: 523 = Below (40%) Mana - Base2 Range: 220-? = Number of pets on hatelist to trigger (base2 - 220) (Set at 30 pets max for now) - 38311 = < 10% mana; - */ - - if (!spellbonuses.TriggerOnValueAmount) - return; - - if (spellbonuses.TriggerOnValueAmount){ - + if (spellbonuses.TriggerOnCastRequirement) { int buff_count = GetMaxTotalSlots(); - - for(int e = 0; e < buff_count; e++){ - - uint32 spell_id = buffs[e].spellid; - - if (IsValidSpell(spell_id)){ - - for(int i = 0; i < EFFECT_COUNT; i++){ - - if ((spells[spell_id].effectid[i] == SE_TriggerOnReqTarget) || (spells[spell_id].effectid[i] == SE_TriggerOnReqCaster)) { - - int base2 = spells[spell_id].base2[i]; - bool use_spell = false; - - if (IsHP){ - if ((base2 >= 500 && base2 <= 520) && GetHPRatio() < (base2 - 500)*5) - use_spell = true; - - else if (base2 == 1004 && GetHPRatio() < 80) - use_spell = true; - } - - else if (IsMana){ - if ( (base2 = 521 && GetManaRatio() < 20) || (base2 = 523 && GetManaRatio() < 40)) - use_spell = true; - - else if (base2 == 38311 && GetManaRatio() < 10) - use_spell = true; - } - - else if (IsEndur){ - if (base2 == 522 && GetEndurancePercent() < 40){ - use_spell = true; - } - } - - else if (IsPet){ - int count = hate_list.GetSummonedPetCountOnHateList(this); - if ((base2 >= 220 && base2 <= 250) && count >= (base2 - 220)){ - use_spell = true; - } - } - - if (use_spell){ - SpellFinished(spells[spell_id].base[i], this, EQ::spells::CastingSlot::Item, 0, -1, spells[spell_id].ResistDiff); - - if(!TryFadeEffect(e)) + for (int e = 0; e < buff_count; e++) { + int spell_id = buffs[e].spellid; + if (IsValidSpell(spell_id)) { + for (int i = 0; i < EFFECT_COUNT; i++) { + if ((spells[spell_id].effect_id[i] == SE_TriggerOnReqTarget) || (spells[spell_id].effect_id[i] == SE_TriggerOnReqCaster)) { + if (PassCastRestriction(spells[spell_id].limit_value[i])) { + SpellFinished(spells[spell_id].base_value[i], this, EQ::spells::CastingSlot::Item, 0, -1, spells[spell_id].resist_difficulty); + if (!TryFadeEffect(e)) { BuffFadeBySlot(e); + } } } } @@ -3671,7 +4433,6 @@ void Mob::TryTriggerOnValueAmount(bool IsHP, bool IsMana, bool IsEndur, bool IsP } } - //Twincast Focus effects should stack across different types (Spell, AA - when implemented ect) void Mob::TryTwincast(Mob *caster, Mob *target, uint32 spell_id) { @@ -3687,7 +4448,7 @@ void Mob::TryTwincast(Mob *caster, Mob *target, uint32 spell_id) if(zone->random.Roll(focus)) { Message(Chat::Spells,"You twincast %s!", spells[spell_id].name); - SpellFinished(spell_id, target, EQ::spells::CastingSlot::Item, 0, -1, spells[spell_id].ResistDiff); + SpellFinished(spell_id, target, EQ::spells::CastingSlot::Item, 0, -1, spells[spell_id].resist_difficulty); } } } @@ -3705,7 +4466,7 @@ void Mob::TryTwincast(Mob *caster, Mob *target, uint32 spell_id) { if(zone->random.Roll(focus)) { - SpellFinished(spell_id, target, EQ::spells::CastingSlot::Item, 0, -1, spells[spell_id].ResistDiff); + SpellFinished(spell_id, target, EQ::spells::CastingSlot::Item, 0, -1, spells[spell_id].resist_difficulty); } } } @@ -3713,67 +4474,94 @@ void Mob::TryTwincast(Mob *caster, Mob *target, uint32 spell_id) } } -int32 Mob::GetVulnerability(Mob* caster, uint32 spell_id, uint32 ticsremaining) +//Used for effects that should occur after the completion of the spell +void Mob::ApplyHealthTransferDamage(Mob *caster, Mob *target, uint16 spell_id) { + if (!IsValidSpell(spell_id)) + return; + + /* + Apply damage from Lifeburn type effects on caster at end of spell cast. + This allows for the AE spells to function without repeatedly killing caster + Damage or heal portion can be found as regular single use spell effect + */ + if (IsEffectInSpell(spell_id, SE_Health_Transfer)){ + for (int i = 0; i < EFFECT_COUNT; i++) { + + if (spells[spell_id].effect_id[i] == SE_Health_Transfer) { + int new_hp = GetMaxHP(); + new_hp -= GetMaxHP() * spells[spell_id].base_value[i] / 1000; + + if (new_hp > 0) { + SetHP(new_hp); + } + else { + Kill(); + } + } + } + } +} + +int32 Mob::GetVulnerability(Mob *caster, uint32 spell_id, uint32 ticsremaining) +{ + /* + Modifies incoming spell damage by percent, to increase or decrease damage, can be limited to specific resists. + Can be applied through quest function, spell focus or npc_spells_effects table. This function is run on the target of the spell. + */ + if (!IsValidSpell(spell_id)) return 0; if (!caster) return 0; - int32 value = 0; + int32 total_mod = 0; + int32 innate_mod = 0; + int32 fc_spell_vulnerability_mod = 0; + int32 fc_spell_damage_pct_incomingPC_mod = 0; - //Apply innate vulnerabilities - if (Vulnerability_Mod[GetSpellResistType(spell_id)] != 0) - value = Vulnerability_Mod[GetSpellResistType(spell_id)]; - - - else if (Vulnerability_Mod[HIGHEST_RESIST+1] != 0) - value = Vulnerability_Mod[HIGHEST_RESIST+1]; - - //Apply spell derived vulnerabilities - if (spellbonuses.FocusEffects[focusSpellVulnerability]){ - - int32 tmp_focus = 0; - int tmp_buffslot = -1; - - int buff_count = GetMaxTotalSlots(); - for(int i = 0; i < buff_count; i++) { - - if((IsValidSpell(buffs[i].spellid) && IsEffectInSpell(buffs[i].spellid, SE_FcSpellVulnerability))){ - - int32 focus = caster->CalcFocusEffect(focusSpellVulnerability, buffs[i].spellid, spell_id, true); - - if (!focus) - continue; - - if (tmp_focus && focus > tmp_focus){ - tmp_focus = focus; - tmp_buffslot = i; - } - - else if (!tmp_focus){ - tmp_focus = focus; - tmp_buffslot = i; - } - - } - } - - tmp_focus = caster->CalcFocusEffect(focusSpellVulnerability, buffs[tmp_buffslot].spellid, spell_id); - - if (tmp_focus < -99) - tmp_focus = -99; - - value += tmp_focus; - - if (tmp_buffslot >= 0) - CheckNumHitsRemaining(NumHit::MatchingSpells, tmp_buffslot); + //Apply innate vulnerabilities from quest functions and tables + if (Vulnerability_Mod[GetSpellResistType(spell_id)] != 0) { + innate_mod = Vulnerability_Mod[GetSpellResistType(spell_id)]; } - return value; + else if (Vulnerability_Mod[HIGHEST_RESIST + 1] != 0) { + innate_mod = Vulnerability_Mod[HIGHEST_RESIST + 1]; + } + + fc_spell_vulnerability_mod = GetFocusEffect(focusSpellVulnerability, spell_id, caster); + fc_spell_damage_pct_incomingPC_mod = GetFocusEffect(focusFcSpellDamagePctIncomingPC, spell_id, caster); + + total_mod = fc_spell_vulnerability_mod + fc_spell_damage_pct_incomingPC_mod; + + //Don't let focus derived mods reduce past 99% mitigation. Quest related can, and for custom functionality if negative will give a healing affect instead of damage. + if (total_mod < -99) { + total_mod = -99; + } + + total_mod += innate_mod; + return total_mod; } -int16 Mob::GetSkillDmgTaken(const EQ::skills::SkillType skill_used, ExtraAttackOptions *opts) +bool Mob::IsTargetedFocusEffect(int focus_type) { + + switch (focus_type) { + case focusSpellVulnerability: + case focusFcSpellDamagePctIncomingPC: + case focusFcDamageAmtIncoming: + case focusFcSpellDamageAmtIncomingPC: + case focusFcCastSpellOnLand: + case focusFcHealAmtIncoming: + case focusFcHealPctCritIncoming: + case focusFcHealPctIncoming: + return true; + default: + return false; + + } +} + +int32 Mob::GetSkillDmgTaken(const EQ::skills::SkillType skill_used, ExtraAttackOptions *opts) { int skilldmg_mod = 0; @@ -3792,17 +4580,108 @@ int16 Mob::GetSkillDmgTaken(const EQ::skills::SkillType skill_used, ExtraAttackO return skilldmg_mod; } -int16 Mob::GetHealRate(uint16 spell_id, Mob* caster) { +int32 Mob::GetPositionalDmgTaken(Mob *attacker) +{ + if (!attacker) + return 0; - int16 heal_rate = 0; + int front_arc = 0; + int back_arc = 0; + int total_mod = 0; - heal_rate += itembonuses.HealRate + spellbonuses.HealRate + aabonuses.HealRate; - heal_rate += GetFocusIncoming(focusFcHealPctIncoming, SE_FcHealPctIncoming, caster, spell_id); + back_arc += itembonuses.Damage_Taken_Position_Mod[SBIndex::POSITION_BACK] + aabonuses.Damage_Taken_Position_Mod[SBIndex::POSITION_BACK] + spellbonuses.Damage_Taken_Position_Mod[SBIndex::POSITION_BACK]; + front_arc += itembonuses.Damage_Taken_Position_Mod[SBIndex::POSITION_FRONT] + aabonuses.Damage_Taken_Position_Mod[SBIndex::POSITION_FRONT] + spellbonuses.Damage_Taken_Position_Mod[SBIndex::POSITION_FRONT]; - if(heal_rate < -99) - heal_rate = -99; + if (back_arc || front_arc) { //Do they have this bonus? + if (attacker->BehindMob(this, attacker->GetX(), attacker->GetY()))//Check if attacker is striking from behind + total_mod = back_arc; //If so, apply the back arc modifier only + else + total_mod = front_arc;//If not, apply the front arc modifer only + } - return heal_rate; + total_mod = round(static_cast(total_mod) * 0.1); + + if (total_mod < -100) + total_mod = -100; + + return total_mod; +} + +int32 Mob::GetPositionalDmgTakenAmt(Mob *attacker) +{ + if (!attacker) + return 0; + + int front_arc = 0; + int back_arc = 0; + int total_amt = 0; + + back_arc += itembonuses.Damage_Taken_Position_Amt[SBIndex::POSITION_BACK] + aabonuses.Damage_Taken_Position_Amt[SBIndex::POSITION_BACK] + spellbonuses.Damage_Taken_Position_Amt[SBIndex::POSITION_BACK]; + front_arc += itembonuses.Damage_Taken_Position_Amt[SBIndex::POSITION_FRONT] + aabonuses.Damage_Taken_Position_Amt[SBIndex::POSITION_FRONT] + spellbonuses.Damage_Taken_Position_Amt[SBIndex::POSITION_FRONT]; + + if (back_arc || front_arc) { + if (attacker->BehindMob(this, attacker->GetX(), attacker->GetY())) + total_amt = back_arc; + else + total_amt = front_arc; + } + + return total_amt; +} + +void Mob::SetBottomRampageList() +{ + auto &mob_list = entity_list.GetCloseMobList(this); + + for (auto &e : mob_list) { + auto mob = e.second; + if (!mob) { + continue; + } + + if (!mob->GetSpecialAbility(SPECATK_RAMPAGE)) { + continue; + } + + if (mob->IsNPC() && mob->CheckAggro(this)) { + for (int i = 0; i < mob->RampageArray.size(); i++) { + // Find this mob in the rampage list + if (this->GetID() == mob->RampageArray[i]) { + //Move to bottom of Rampage List + auto it = mob->RampageArray.begin() + i; + std::rotate(it, it + 1, mob->RampageArray.end()); + } + } + } + } +} + +void Mob::SetTopRampageList() +{ + auto &mob_list = entity_list.GetCloseMobList(this); + + for (auto &e : mob_list) { + auto mob = e.second; + if (!mob) { + continue; + } + + if (!mob->GetSpecialAbility(SPECATK_RAMPAGE)) { + continue; + } + + if (mob->IsNPC() && mob->CheckAggro(this)) { + for (int i = 0; i < mob->RampageArray.size(); i++) { + // Find this mob in the rampage list + if (this->GetID() == mob->RampageArray[i]) { + //Move to Top of Rampage List + auto it = mob->RampageArray.begin() + i; + std::rotate(it, it + 1, mob->RampageArray.end()); + std::rotate(mob->RampageArray.rbegin(), mob->RampageArray.rbegin() + 1, mob->RampageArray.rend()); + } + } + } + } } bool Mob::TryFadeEffect(int slot) @@ -3815,13 +4694,13 @@ bool Mob::TryFadeEffect(int slot) for(int i = 0; i < EFFECT_COUNT; i++) { - if (!spells[buffs[slot].spellid].effectid[i]) + if (!spells[buffs[slot].spellid].effect_id[i]) continue; - if (spells[buffs[slot].spellid].effectid[i] == SE_CastOnFadeEffectAlways || - spells[buffs[slot].spellid].effectid[i] == SE_CastOnRuneFadeEffect) + if (spells[buffs[slot].spellid].effect_id[i] == SE_CastOnFadeEffectAlways || + spells[buffs[slot].spellid].effect_id[i] == SE_CastOnRuneFadeEffect) { - uint16 spell_id = spells[buffs[slot].spellid].base[i]; + uint16 spell_id = spells[buffs[slot].spellid].base_value[i]; BuffFadeBySlot(slot); if(spell_id) @@ -3833,10 +4712,10 @@ bool Mob::TryFadeEffect(int slot) if(IsValidSpell(spell_id)) { if (IsBeneficialSpell(spell_id)) { - SpellFinished(spell_id, this, EQ::spells::CastingSlot::Item, 0, -1, spells[spell_id].ResistDiff); + SpellFinished(spell_id, this, EQ::spells::CastingSlot::Item, 0, -1, spells[spell_id].resist_difficulty); } else if(!(IsClient() && CastToClient()->dead)) { - SpellFinished(spell_id, this, EQ::spells::CastingSlot::Item, 0, -1, spells[spell_id].ResistDiff); + SpellFinished(spell_id, this, EQ::spells::CastingSlot::Item, 0, -1, spells[spell_id].resist_difficulty); } return true; } @@ -3870,7 +4749,7 @@ void Mob::TrySympatheticProc(Mob *target, uint32 spell_id) SpellFinished(focus_trigger, target); else - SpellFinished(focus_trigger, this, EQ::spells::CastingSlot::Item, 0, -1, spells[focus_trigger].ResistDiff); + SpellFinished(focus_trigger, this, EQ::spells::CastingSlot::Item, 0, -1, spells[focus_trigger].resist_difficulty); } // For detrimental spells, if the triggered spell is beneficial, then it will land on the caster // if the triggered spell is also detrimental, then it will land on the target @@ -3880,7 +4759,7 @@ void Mob::TrySympatheticProc(Mob *target, uint32 spell_id) SpellFinished(focus_trigger, this); else - SpellFinished(focus_trigger, target, EQ::spells::CastingSlot::Item, 0, -1, spells[focus_trigger].ResistDiff); + SpellFinished(focus_trigger, target, EQ::spells::CastingSlot::Item, 0, -1, spells[focus_trigger].resist_difficulty); } CheckNumHitsRemaining(NumHit::MatchingSpells, -1, focus_spell); @@ -3888,343 +4767,7 @@ void Mob::TrySympatheticProc(Mob *target, uint32 spell_id) int32 Mob::GetItemStat(uint32 itemid, const char *identifier) { - const EQ::ItemInstance* inst = database.CreateItem(itemid); - if (!inst) - return 0; - - const EQ::ItemData* item = inst->GetItem(); - if (!item) - return 0; - - if (!identifier) - return 0; - - int32 stat = 0; - - std::string id = identifier; - for(uint32 i = 0; i < id.length(); ++i) - { - id[i] = tolower(id[i]); - } - - if (id == "itemclass") - stat = int32(item->ItemClass); - if (id == "id") - stat = int32(item->ID); - if (id == "idfile") - stat = atoi(&item->IDFile[2]); - if (id == "weight") - stat = int32(item->Weight); - if (id == "norent") - stat = int32(item->NoRent); - if (id == "nodrop") - stat = int32(item->NoDrop); - if (id == "size") - stat = int32(item->Size); - if (id == "slots") - stat = int32(item->Slots); - if (id == "price") - stat = int32(item->Price); - if (id == "icon") - stat = int32(item->Icon); - if (id == "loregroup") - stat = int32(item->LoreGroup); - if (id == "loreflag") - stat = int32(item->LoreFlag); - if (id == "pendingloreflag") - stat = int32(item->PendingLoreFlag); - if (id == "artifactflag") - stat = int32(item->ArtifactFlag); - if (id == "summonedflag") - stat = int32(item->SummonedFlag); - if (id == "fvnodrop") - stat = int32(item->FVNoDrop); - if (id == "favor") - stat = int32(item->Favor); - if (id == "guildfavor") - stat = int32(item->GuildFavor); - if (id == "pointtype") - stat = int32(item->PointType); - if (id == "bagtype") - stat = int32(item->BagType); - if (id == "bagslots") - stat = int32(item->BagSlots); - if (id == "bagsize") - stat = int32(item->BagSize); - if (id == "bagwr") - stat = int32(item->BagWR); - if (id == "benefitflag") - stat = int32(item->BenefitFlag); - if (id == "tradeskills") - stat = int32(item->Tradeskills); - if (id == "cr") - stat = int32(item->CR); - if (id == "dr") - stat = int32(item->DR); - if (id == "pr") - stat = int32(item->PR); - if (id == "mr") - stat = int32(item->MR); - if (id == "fr") - stat = int32(item->FR); - if (id == "astr") - stat = int32(item->AStr); - if (id == "asta") - stat = int32(item->ASta); - if (id == "aagi") - stat = int32(item->AAgi); - if (id == "adex") - stat = int32(item->ADex); - if (id == "acha") - stat = int32(item->ACha); - if (id == "aint") - stat = int32(item->AInt); - if (id == "awis") - stat = int32(item->AWis); - if (id == "hp") - stat = int32(item->HP); - if (id == "mana") - stat = int32(item->Mana); - if (id == "ac") - stat = int32(item->AC); - if (id == "deity") - stat = int32(item->Deity); - if (id == "skillmodvalue") - stat = int32(item->SkillModValue); - if (id == "skillmodtype") - stat = int32(item->SkillModType); - if (id == "banedmgrace") - stat = int32(item->BaneDmgRace); - if (id == "banedmgamt") - stat = int32(item->BaneDmgAmt); - if (id == "banedmgbody") - stat = int32(item->BaneDmgBody); - if (id == "magic") - stat = int32(item->Magic); - if (id == "casttime_") - stat = int32(item->CastTime_); - if (id == "reqlevel") - stat = int32(item->ReqLevel); - if (id == "bardtype") - stat = int32(item->BardType); - if (id == "bardvalue") - stat = int32(item->BardValue); - if (id == "light") - stat = int32(item->Light); - if (id == "delay") - stat = int32(item->Delay); - if (id == "reclevel") - stat = int32(item->RecLevel); - if (id == "recskill") - stat = int32(item->RecSkill); - if (id == "elemdmgtype") - stat = int32(item->ElemDmgType); - if (id == "elemdmgamt") - stat = int32(item->ElemDmgAmt); - if (id == "range") - stat = int32(item->Range); - if (id == "damage") - stat = int32(item->Damage); - if (id == "color") - stat = int32(item->Color); - if (id == "classes") - stat = int32(item->Classes); - if (id == "races") - stat = int32(item->Races); - if (id == "maxcharges") - stat = int32(item->MaxCharges); - if (id == "itemtype") - stat = int32(item->ItemType); - if (id == "material") - stat = int32(item->Material); - if (id == "casttime") - stat = int32(item->CastTime); - if (id == "elitematerial") - stat = int32(item->EliteMaterial); - if (id == "herosforgemodel") - stat = int32(item->HerosForgeModel); - if (id == "procrate") - stat = int32(item->ProcRate); - if (id == "combateffects") - stat = int32(item->CombatEffects); - if (id == "shielding") - stat = int32(item->Shielding); - if (id == "stunresist") - stat = int32(item->StunResist); - if (id == "strikethrough") - stat = int32(item->StrikeThrough); - if (id == "extradmgskill") - stat = int32(item->ExtraDmgSkill); - if (id == "extradmgamt") - stat = int32(item->ExtraDmgAmt); - if (id == "spellshield") - stat = int32(item->SpellShield); - if (id == "avoidance") - stat = int32(item->Avoidance); - if (id == "accuracy") - stat = int32(item->Accuracy); - if (id == "charmfileid") - stat = int32(item->CharmFileID); - if (id == "factionmod1") - stat = int32(item->FactionMod1); - if (id == "factionmod2") - stat = int32(item->FactionMod2); - if (id == "factionmod3") - stat = int32(item->FactionMod3); - if (id == "factionmod4") - stat = int32(item->FactionMod4); - if (id == "factionamt1") - stat = int32(item->FactionAmt1); - if (id == "factionamt2") - stat = int32(item->FactionAmt2); - if (id == "factionamt3") - stat = int32(item->FactionAmt3); - if (id == "factionamt4") - stat = int32(item->FactionAmt4); - if (id == "augtype") - stat = int32(item->AugType); - if (id == "ldontheme") - stat = int32(item->LDoNTheme); - if (id == "ldonprice") - stat = int32(item->LDoNPrice); - if (id == "ldonsold") - stat = int32(item->LDoNSold); - if (id == "banedmgraceamt") - stat = int32(item->BaneDmgRaceAmt); - if (id == "augrestrict") - stat = int32(item->AugRestrict); - if (id == "endur") - stat = int32(item->Endur); - if (id == "dotshielding") - stat = int32(item->DotShielding); - if (id == "attack") - stat = int32(item->Attack); - if (id == "regen") - stat = int32(item->Regen); - if (id == "manaregen") - stat = int32(item->ManaRegen); - if (id == "enduranceregen") - stat = int32(item->EnduranceRegen); - if (id == "haste") - stat = int32(item->Haste); - if (id == "damageshield") - stat = int32(item->DamageShield); - if (id == "recastdelay") - stat = int32(item->RecastDelay); - if (id == "recasttype") - stat = int32(item->RecastType); - if (id == "augdistiller") - stat = int32(item->AugDistiller); - if (id == "attuneable") - stat = int32(item->Attuneable); - if (id == "nopet") - stat = int32(item->NoPet); - if (id == "potionbelt") - stat = int32(item->PotionBelt); - if (id == "stackable") - stat = int32(item->Stackable); - if (id == "notransfer") - stat = int32(item->NoTransfer); - if (id == "questitemflag") - stat = int32(item->QuestItemFlag); - if (id == "stacksize") - stat = int32(item->StackSize); - if (id == "potionbeltslots") - stat = int32(item->PotionBeltSlots); - if (id == "book") - stat = int32(item->Book); - if (id == "booktype") - stat = int32(item->BookType); - if (id == "svcorruption") - stat = int32(item->SVCorruption); - if (id == "purity") - stat = int32(item->Purity); - if (id == "backstabdmg") - stat = int32(item->BackstabDmg); - if (id == "dsmitigation") - stat = int32(item->DSMitigation); - if (id == "heroicstr") - stat = int32(item->HeroicStr); - if (id == "heroicint") - stat = int32(item->HeroicInt); - if (id == "heroicwis") - stat = int32(item->HeroicWis); - if (id == "heroicagi") - stat = int32(item->HeroicAgi); - if (id == "heroicdex") - stat = int32(item->HeroicDex); - if (id == "heroicsta") - stat = int32(item->HeroicSta); - if (id == "heroiccha") - stat = int32(item->HeroicCha); - if (id == "heroicmr") - stat = int32(item->HeroicMR); - if (id == "heroicfr") - stat = int32(item->HeroicFR); - if (id == "heroiccr") - stat = int32(item->HeroicCR); - if (id == "heroicdr") - stat = int32(item->HeroicDR); - if (id == "heroicpr") - stat = int32(item->HeroicPR); - if (id == "heroicsvcorrup") - stat = int32(item->HeroicSVCorrup); - if (id == "healamt") - stat = int32(item->HealAmt); - if (id == "spelldmg") - stat = int32(item->SpellDmg); - if (id == "ldonsellbackrate") - stat = int32(item->LDoNSellBackRate); - if (id == "scriptfileid") - stat = int32(item->ScriptFileID); - if (id == "expendablearrow") - stat = int32(item->ExpendableArrow); - if (id == "clairvoyance") - stat = int32(item->Clairvoyance); - // Begin Effects - if (id == "clickeffect") - stat = int32(item->Click.Effect); - if (id == "clicktype") - stat = int32(item->Click.Type); - if (id == "clicklevel") - stat = int32(item->Click.Level); - if (id == "clicklevel2") - stat = int32(item->Click.Level2); - if (id == "proceffect") - stat = int32(item->Proc.Effect); - if (id == "proctype") - stat = int32(item->Proc.Type); - if (id == "proclevel") - stat = int32(item->Proc.Level); - if (id == "proclevel2") - stat = int32(item->Proc.Level2); - if (id == "worneffect") - stat = int32(item->Worn.Effect); - if (id == "worntype") - stat = int32(item->Worn.Type); - if (id == "wornlevel") - stat = int32(item->Worn.Level); - if (id == "wornlevel2") - stat = int32(item->Worn.Level2); - if (id == "focuseffect") - stat = int32(item->Focus.Effect); - if (id == "focustype") - stat = int32(item->Focus.Type); - if (id == "focuslevel") - stat = int32(item->Focus.Level); - if (id == "focuslevel2") - stat = int32(item->Focus.Level2); - if (id == "scrolleffect") - stat = int32(item->Scroll.Effect); - if (id == "scrolltype") - stat = int32(item->Scroll.Type); - if (id == "scrolllevel") - stat = int32(item->Scroll.Level); - if (id == "scrolllevel2") - stat = int32(item->Scroll.Level2); - - safe_delete(inst); - return stat; + return EQ::InventoryProfile::GetItemStatValue(itemid, identifier); } std::string Mob::GetGlobal(const char *varname) { @@ -4481,10 +5024,11 @@ int Mob::QGVarDuration(const char *fmt) return duration; } -void Mob::DoKnockback(Mob *caster, uint32 pushback, uint32 pushup) +void Mob::DoKnockback(Mob *caster, uint32 push_back, uint32 push_up) { if(IsClient()) { + CastToClient()->cheat_manager.SetExemptStatus(KnockBack, true); auto outapp_push = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct)); PlayerPositionUpdateServer_Struct* spu = (PlayerPositionUpdateServer_Struct*)outapp_push->pBuffer; @@ -4495,8 +5039,8 @@ void Mob::DoKnockback(Mob *caster, uint32 pushback, uint32 pushup) look_heading -= 360; //x and y are crossed mkay - double new_x = pushback * sin(double(look_heading * 3.141592 / 180.0)); - double new_y = pushback * cos(double(look_heading * 3.141592 / 180.0)); + double new_x = push_back * sin(double(look_heading * 3.141592 / 180.0)); + double new_y = push_back * cos(double(look_heading * 3.141592 / 180.0)); spu->spawn_id = GetID(); spu->x_pos = FloatToEQ19(GetX()); @@ -4504,7 +5048,7 @@ void Mob::DoKnockback(Mob *caster, uint32 pushback, uint32 pushup) spu->z_pos = FloatToEQ19(GetZ()); spu->delta_x = FloatToEQ13(static_cast(new_x)); spu->delta_y = FloatToEQ13(static_cast(new_y)); - spu->delta_z = FloatToEQ13(static_cast(pushup)); + spu->delta_z = FloatToEQ13(static_cast(push_up)); spu->heading = FloatToEQ12(GetHeading()); // for ref: these were not passed on to other 5 clients while on Titanium standard (change to RoF2 standard: 11/16/2019) //eq->padding0002 = 0; @@ -4525,12 +5069,12 @@ void Mob::TrySpellOnKill(uint8 level, uint16 spell_id) { if(IsEffectInSpell(spell_id, SE_ProcOnSpellKillShot)) { for (int i = 0; i < EFFECT_COUNT; i++) { - if (spells[spell_id].effectid[i] == SE_ProcOnSpellKillShot) + if (spells[spell_id].effect_id[i] == SE_ProcOnSpellKillShot) { - if (IsValidSpell(spells[spell_id].base2[i]) && spells[spell_id].max[i] <= level) + if (IsValidSpell(spells[spell_id].limit_value[i]) && spells[spell_id].max_value[i] <= level) { - if(zone->random.Roll(spells[spell_id].base[i])) - SpellFinished(spells[spell_id].base2[i], this, EQ::spells::CastingSlot::Item, 0, -1, spells[spells[spell_id].base2[i]].ResistDiff); + if(zone->random.Roll(spells[spell_id].base_value[i])) + SpellFinished(spells[spell_id].limit_value[i], this, EQ::spells::CastingSlot::Item, 0, -1, spells[spells[spell_id].limit_value[i]].resist_difficulty); } } } @@ -4545,17 +5089,17 @@ void Mob::TrySpellOnKill(uint8 level, uint16 spell_id) if(aabonuses.SpellOnKill[i] && IsValidSpell(aabonuses.SpellOnKill[i]) && (level >= aabonuses.SpellOnKill[i + 2])) { if(zone->random.Roll(static_cast(aabonuses.SpellOnKill[i + 1]))) - SpellFinished(aabonuses.SpellOnKill[i], this, EQ::spells::CastingSlot::Item, 0, -1, spells[aabonuses.SpellOnKill[i]].ResistDiff); + SpellFinished(aabonuses.SpellOnKill[i], this, EQ::spells::CastingSlot::Item, 0, -1, spells[aabonuses.SpellOnKill[i]].resist_difficulty); } if(itembonuses.SpellOnKill[i] && IsValidSpell(itembonuses.SpellOnKill[i]) && (level >= itembonuses.SpellOnKill[i + 2])){ if(zone->random.Roll(static_cast(itembonuses.SpellOnKill[i + 1]))) - SpellFinished(itembonuses.SpellOnKill[i], this, EQ::spells::CastingSlot::Item, 0, -1, spells[aabonuses.SpellOnKill[i]].ResistDiff); + SpellFinished(itembonuses.SpellOnKill[i], this, EQ::spells::CastingSlot::Item, 0, -1, spells[aabonuses.SpellOnKill[i]].resist_difficulty); } if(spellbonuses.SpellOnKill[i] && IsValidSpell(spellbonuses.SpellOnKill[i]) && (level >= spellbonuses.SpellOnKill[i + 2])) { if(zone->random.Roll(static_cast(spellbonuses.SpellOnKill[i + 1]))) - SpellFinished(spellbonuses.SpellOnKill[i], this, EQ::spells::CastingSlot::Item, 0, -1, spells[aabonuses.SpellOnKill[i]].ResistDiff); + SpellFinished(spellbonuses.SpellOnKill[i], this, EQ::spells::CastingSlot::Item, 0, -1, spells[aabonuses.SpellOnKill[i]].resist_difficulty); } } @@ -4572,38 +5116,44 @@ bool Mob::TrySpellOnDeath() for(int i = 0; i < MAX_SPELL_TRIGGER*2; i+=2) { if(IsClient() && aabonuses.SpellOnDeath[i] && IsValidSpell(aabonuses.SpellOnDeath[i])) { if(zone->random.Roll(static_cast(aabonuses.SpellOnDeath[i + 1]))) { - SpellFinished(aabonuses.SpellOnDeath[i], this, EQ::spells::CastingSlot::Item, 0, -1, spells[aabonuses.SpellOnDeath[i]].ResistDiff); + SpellFinished(aabonuses.SpellOnDeath[i], this, EQ::spells::CastingSlot::Item, 0, -1, spells[aabonuses.SpellOnDeath[i]].resist_difficulty); } } if(itembonuses.SpellOnDeath[i] && IsValidSpell(itembonuses.SpellOnDeath[i])) { if(zone->random.Roll(static_cast(itembonuses.SpellOnDeath[i + 1]))) { - SpellFinished(itembonuses.SpellOnDeath[i], this, EQ::spells::CastingSlot::Item, 0, -1, spells[itembonuses.SpellOnDeath[i]].ResistDiff); + SpellFinished(itembonuses.SpellOnDeath[i], this, EQ::spells::CastingSlot::Item, 0, -1, spells[itembonuses.SpellOnDeath[i]].resist_difficulty); } } if(spellbonuses.SpellOnDeath[i] && IsValidSpell(spellbonuses.SpellOnDeath[i])) { if(zone->random.Roll(static_cast(spellbonuses.SpellOnDeath[i + 1]))) { - SpellFinished(spellbonuses.SpellOnDeath[i], this, EQ::spells::CastingSlot::Item, 0, -1, spells[spellbonuses.SpellOnDeath[i]].ResistDiff); + SpellFinished(spellbonuses.SpellOnDeath[i], this, EQ::spells::CastingSlot::Item, 0, -1, spells[spellbonuses.SpellOnDeath[i]].resist_difficulty); } } } - BuffFadeAll(); + BuffFadeNonPersistDeath(); return false; //You should not be able to use this effect and survive (ALWAYS return false), //attempting to place a heal in these effects will still result //in death because the heal will not register before the script kills you. } -int16 Mob::GetCritDmgMod(uint16 skill) +int16 Mob::GetCritDmgMod(uint16 skill, Mob* owner) { int critDmg_mod = 0; - // All skill dmg mod + Skill specific + // All skill dmg mod + Skill specific [SPA 330 and 496] critDmg_mod += itembonuses.CritDmgMod[EQ::skills::HIGHEST_SKILL + 1] + spellbonuses.CritDmgMod[EQ::skills::HIGHEST_SKILL + 1] + aabonuses.CritDmgMod[EQ::skills::HIGHEST_SKILL + 1] + itembonuses.CritDmgMod[skill] + spellbonuses.CritDmgMod[skill] + aabonuses.CritDmgMod[skill]; + critDmg_mod += itembonuses.CritDmgModNoStack[EQ::skills::HIGHEST_SKILL + 1] + spellbonuses.CritDmgModNoStack[EQ::skills::HIGHEST_SKILL + 1] + aabonuses.CritDmgModNoStack[EQ::skills::HIGHEST_SKILL + 1] + + itembonuses.CritDmgModNoStack[skill] + spellbonuses.CritDmgModNoStack[skill] + aabonuses.CritDmgModNoStack[skill]; + + if (owner) //Checked in TryPetCriticalHit + critDmg_mod += owner->aabonuses.Pet_Crit_Melee_Damage_Pct_Owner + owner->itembonuses.Pet_Crit_Melee_Damage_Pct_Owner + owner->spellbonuses.Pet_Crit_Melee_Damage_Pct_Owner; + return critDmg_mod; } @@ -4663,6 +5213,13 @@ int16 Mob::GetMeleeDamageMod_SE(uint16 skill) dmg_mod += itembonuses.DamageModifier2[EQ::skills::HIGHEST_SKILL + 1] + spellbonuses.DamageModifier2[EQ::skills::HIGHEST_SKILL + 1] + aabonuses.DamageModifier2[EQ::skills::HIGHEST_SKILL + 1] + itembonuses.DamageModifier2[skill] + spellbonuses.DamageModifier2[skill] + aabonuses.DamageModifier2[skill]; + dmg_mod += itembonuses.DamageModifier3[EQ::skills::HIGHEST_SKILL + 1] + spellbonuses.DamageModifier3[EQ::skills::HIGHEST_SKILL + 1] + aabonuses.DamageModifier3[EQ::skills::HIGHEST_SKILL + 1] + + itembonuses.DamageModifier3[skill] + spellbonuses.DamageModifier3[skill] + aabonuses.DamageModifier3[skill]; + + if (GetUseDoubleMeleeRoundDmgBonus()) { + dmg_mod += itembonuses.DoubleMeleeRound[SBIndex::DOUBLE_MELEE_ROUND_DMG_BONUS] + spellbonuses.DoubleMeleeRound[SBIndex::DOUBLE_MELEE_ROUND_DMG_BONUS] + aabonuses.DoubleMeleeRound[SBIndex::DOUBLE_MELEE_ROUND_DMG_BONUS]; + } + if(dmg_mod < -100) dmg_mod = -100; @@ -4694,6 +5251,35 @@ int16 Mob::GetCrippBlowChance() return crip_chance; } + +int16 Mob::GetMeleeDmgPositionMod(Mob* defender) +{ + if (!defender) + return 0; + + int front_arc = 0; + int back_arc = 0; + int total_mod = 0; + + back_arc += itembonuses.Melee_Damage_Position_Mod[SBIndex::POSITION_BACK] + aabonuses.Melee_Damage_Position_Mod[SBIndex::POSITION_BACK] + spellbonuses.Melee_Damage_Position_Mod[SBIndex::POSITION_BACK]; + front_arc += itembonuses.Melee_Damage_Position_Mod[SBIndex::POSITION_FRONT] + aabonuses.Melee_Damage_Position_Mod[SBIndex::POSITION_FRONT] + spellbonuses.Melee_Damage_Position_Mod[SBIndex::POSITION_FRONT]; + + if (back_arc || front_arc) { //Do they have this bonus? + if (BehindMob(defender, GetX(), GetY()))//Check if attacker is striking from behind + total_mod = back_arc; //If so, apply the back arc modifier only + else + total_mod = front_arc;//If not, apply the front arc modifer only + } + + total_mod = round(static_cast(total_mod) * 0.1); + + if (total_mod < -100) + total_mod = -100; + + return total_mod; + +} + int16 Mob::GetSkillReuseTime(uint16 skill) { int skill_reduction = this->itembonuses.SkillReuseTime[skill] + this->spellbonuses.SkillReuseTime[skill] + this->aabonuses.SkillReuseTime[skill]; @@ -4715,34 +5301,61 @@ int16 Mob::GetSkillDmgAmt(uint16 skill) return skill_dmg; } +int16 Mob::GetPositionalDmgAmt(Mob* defender) +{ + if (!defender) + return 0; + + //SPA 504 + int front_arc_dmg_amt = 0; + int back_arc_dmg_amt = 0; + + int total_amt = 0; + + back_arc_dmg_amt += itembonuses.Melee_Damage_Position_Amt[SBIndex::POSITION_BACK] + aabonuses.Melee_Damage_Position_Amt[SBIndex::POSITION_BACK] + spellbonuses.Melee_Damage_Position_Amt[SBIndex::POSITION_BACK]; + front_arc_dmg_amt += itembonuses.Melee_Damage_Position_Amt[SBIndex::POSITION_FRONT] + aabonuses.Melee_Damage_Position_Amt[SBIndex::POSITION_FRONT] + spellbonuses.Melee_Damage_Position_Amt[SBIndex::POSITION_FRONT]; + + if (back_arc_dmg_amt || front_arc_dmg_amt) { + if (BehindMob(defender, GetX(), GetY())) + total_amt = back_arc_dmg_amt; + else + total_amt = front_arc_dmg_amt; + } + + return total_amt; +} + void Mob::MeleeLifeTap(int32 damage) { int32 lifetap_amt = 0; - lifetap_amt = spellbonuses.MeleeLifetap + itembonuses.MeleeLifetap + aabonuses.MeleeLifetap - + spellbonuses.Vampirism + itembonuses.Vampirism + aabonuses.Vampirism; + int32 melee_lifetap_mod = spellbonuses.MeleeLifetap + itembonuses.MeleeLifetap + aabonuses.MeleeLifetap + + spellbonuses.Vampirism + itembonuses.Vampirism + aabonuses.Vampirism; - if(lifetap_amt && damage > 0){ + if(melee_lifetap_mod && damage > 0){ - lifetap_amt = damage * lifetap_amt / 100; - LogCombat("Melee lifetap healing for [{}] damage", damage); + lifetap_amt = damage * (static_cast(melee_lifetap_mod) / 100.0f); + LogCombat("Melee lifetap healing [{}] points of damage with modifier of [{}] ", lifetap_amt, melee_lifetap_mod); - if (lifetap_amt > 0) + if (lifetap_amt >= 0) { HealDamage(lifetap_amt); //Heal self for modified damage amount. - else + } + else { Damage(this, -lifetap_amt, 0, EQ::skills::SkillEvocation, false); //Dmg self for modified damage amount. + } } } -bool Mob::TryReflectSpell(uint32 spell_id) -{ - if (!spells[spell_id].reflectable) - return false; +bool Mob::TryDoubleMeleeRoundEffect() { - int chance = itembonuses.reflect_chance + spellbonuses.reflect_chance + aabonuses.reflect_chance; + auto chance = aabonuses.DoubleMeleeRound[SBIndex::DOUBLE_MELEE_ROUND_CHANCE] + itembonuses.DoubleMeleeRound[SBIndex::DOUBLE_MELEE_ROUND_CHANCE] + + spellbonuses.DoubleMeleeRound[SBIndex::DOUBLE_MELEE_ROUND_CHANCE]; - if(chance && zone->random.Roll(chance)) + if (chance && zone->random.Roll(chance)) { + SetUseDoubleMeleeRoundDmgBonus(true); return true; + } + SetUseDoubleMeleeRoundDmgBonus(false); return false; } @@ -4764,7 +5377,7 @@ void Mob::DoGravityEffect() { for (int i = 0; i < EFFECT_COUNT; i++) { - if(spells[buffs[slot].spellid].effectid[i] == SE_GravityEffect) { + if(spells[buffs[slot].spellid].effect_id[i] == SE_GravityEffect) { int casterId = buffs[slot].casterid; if(casterId) @@ -4776,7 +5389,7 @@ void Mob::DoGravityEffect() caster_x = caster->GetX(); caster_y = caster->GetY(); - value = static_cast(spells[buffs[slot].spellid].base[i]); + value = static_cast(spells[buffs[slot].spellid].base_value[i]); if(value == 0) continue; @@ -4836,59 +5449,53 @@ void Mob::DoGravityEffect() } } -void Mob::SpreadVirus(uint16 spell_id, uint16 casterID) +void Mob::AddNimbusEffect(int effect_id) { - int num_targs = spells[spell_id].viral_targets; - - Mob* caster = entity_list.GetMob(casterID); - Mob* target = nullptr; - // Only spread in zones without perm buffs - if(!zone->BuffTimersSuspended()) { - for(int i = 0; i < num_targs; i++) { - target = entity_list.GetTargetForVirus(this, spells[spell_id].viral_range); - if(target) { - // Only spreads to the uninfected - if(!target->FindBuff(spell_id)) { - if(caster) - caster->SpellOnTarget(spell_id, target); - - } - } - } - } -} - -void Mob::AddNimbusEffect(int effectid) -{ - SetNimbusEffect(effectid); + SetNimbusEffect(effect_id); auto outapp = new EQApplicationPacket(OP_AddNimbusEffect, sizeof(RemoveNimbusEffect_Struct)); auto ane = (RemoveNimbusEffect_Struct *)outapp->pBuffer; ane->spawnid = GetID(); - ane->nimbus_effect = effectid; + ane->nimbus_effect = effect_id; entity_list.QueueClients(this, outapp); safe_delete(outapp); } -void Mob::RemoveNimbusEffect(int effectid) +void Mob::RemoveNimbusEffect(int effect_id) { - if (effectid == nimbus_effect1) + if (effect_id == nimbus_effect1) nimbus_effect1 = 0; - else if (effectid == nimbus_effect2) + else if (effect_id == nimbus_effect2) nimbus_effect2 = 0; - else if (effectid == nimbus_effect3) + else if (effect_id == nimbus_effect3) nimbus_effect3 = 0; auto outapp = new EQApplicationPacket(OP_RemoveNimbusEffect, sizeof(RemoveNimbusEffect_Struct)); RemoveNimbusEffect_Struct* rne = (RemoveNimbusEffect_Struct*)outapp->pBuffer; rne->spawnid = GetID(); - rne->nimbus_effect = effectid; + rne->nimbus_effect = effect_id; entity_list.QueueClients(this, outapp); safe_delete(outapp); } +void Mob::RemoveAllNimbusEffects() +{ + uint32 nimbus_effects[3] = { nimbus_effect1, nimbus_effect2, nimbus_effect3 }; + for (auto ¤t_nimbus : nimbus_effects) { + auto remove_packet = new EQApplicationPacket(OP_RemoveNimbusEffect, sizeof(RemoveNimbusEffect_Struct)); + auto *remove_effect = (RemoveNimbusEffect_Struct*)remove_packet->pBuffer; + remove_effect->spawnid = GetID(); + remove_effect->nimbus_effect = current_nimbus; + entity_list.QueueClients(this, remove_packet); + safe_delete(remove_packet); + } + nimbus_effect1 = 0; + nimbus_effect2 = 0; + nimbus_effect3 = 0; +} + bool Mob::IsBoat() const { return ( @@ -4977,11 +5584,11 @@ void Mob::CastOnCurer(uint32 spell_id) { for(int i = 0; i < EFFECT_COUNT; i++) { - if (spells[spell_id].effectid[i] == SE_CastOnCurer) + if (spells[spell_id].effect_id[i] == SE_CastOnCurer) { - if(IsValidSpell(spells[spell_id].base[i])) + if(IsValidSpell(spells[spell_id].base_value[i])) { - SpellFinished(spells[spell_id].base[i], this); + SpellFinished(spells[spell_id].base_value[i], this); } } } @@ -4991,11 +5598,11 @@ void Mob::CastOnCure(uint32 spell_id) { for(int i = 0; i < EFFECT_COUNT; i++) { - if (spells[spell_id].effectid[i] == SE_CastOnCure) + if (spells[spell_id].effect_id[i] == SE_CastOnCure) { - if(IsValidSpell(spells[spell_id].base[i])) + if(IsValidSpell(spells[spell_id].base_value[i])) { - SpellFinished(spells[spell_id].base[i], this); + SpellFinished(spells[spell_id].base_value[i], this); } } } @@ -5008,11 +5615,11 @@ void Mob::CastOnNumHitFade(uint32 spell_id) for(int i = 0; i < EFFECT_COUNT; i++) { - if (spells[spell_id].effectid[i] == SE_CastonNumHitFade) + if (spells[spell_id].effect_id[i] == SE_CastonNumHitFade) { - if(IsValidSpell(spells[spell_id].base[i])) + if(IsValidSpell(spells[spell_id].base_value[i])) { - SpellFinished(spells[spell_id].base[i], this); + SpellFinished(spells[spell_id].base_value[i], this); } } } @@ -5036,7 +5643,7 @@ void Mob::SlowMitigation(Mob* caster) } } -uint16 Mob::GetSkillByItemType(int ItemType) +EQ::skills::SkillType Mob::GetSkillByItemType(int ItemType) { switch (ItemType) { case EQ::item::ItemType1HSlash: @@ -5092,22 +5699,6 @@ uint8 Mob::GetItemTypeBySkill(EQ::skills::SkillType skill) } } - -bool Mob::PassLimitToSkill(uint16 spell_id, uint16 skill) { - - if (!IsValidSpell(spell_id)) - return false; - - for (int i = 0; i < EFFECT_COUNT; i++) { - if (spells[spell_id].effectid[i] == SE_LimitToSkill){ - if (spells[spell_id].base[i] == skill){ - return true; - } - } - } - return false; -} - uint16 Mob::GetWeaponSpeedbyHand(uint16 hand) { uint16 weapon_speed = 0; @@ -5143,11 +5734,11 @@ int8 Mob::GetDecayEffectValue(uint16 spell_id, uint16 spelleffect) { for (int slot = 0; slot < buff_count; slot++){ if (IsValidSpell(buffs[slot].spellid)){ for (int i = 0; i < EFFECT_COUNT; i++){ - if(spells[buffs[slot].spellid].effectid[i] == spelleffect) { + if(spells[buffs[slot].spellid].effect_id[i] == spelleffect) { - int critchance = spells[buffs[slot].spellid].base[i]; - int decay = spells[buffs[slot].spellid].base2[i]; - int lvldiff = spell_level - spells[buffs[slot].spellid].max[i]; + int critchance = spells[buffs[slot].spellid].base_value[i]; + int decay = spells[buffs[slot].spellid].limit_value[i]; + int lvldiff = spell_level - spells[buffs[slot].spellid].max_value[i]; if(lvldiff > 0 && decay > 0) { @@ -5234,7 +5825,7 @@ void Mob::ClearItemFactionBonuses() { FACTION_VALUE Mob::GetSpecialFactionCon(Mob* iOther) { if (!iOther) - return FACTION_INDIFFERENT; + return FACTION_INDIFFERENTLY; iOther = iOther->GetOwnerOrSelf(); Mob* self = this->GetOwnerOrSelf(); @@ -5245,9 +5836,9 @@ FACTION_VALUE Mob::GetSpecialFactionCon(Mob* iOther) { int iOtherPrimaryFaction = iOther->GetPrimaryFaction(); if (selfPrimaryFaction >= 0 && selfAIcontrolled) - return FACTION_INDIFFERENT; + return FACTION_INDIFFERENTLY; if (iOther->GetPrimaryFaction() >= 0) - return FACTION_INDIFFERENT; + return FACTION_INDIFFERENTLY; /* special values: -2 = indiff to player, ally to AI on special values, indiff to AI -3 = dub to player, ally to AI on special values, indiff to AI @@ -5267,27 +5858,27 @@ FACTION_VALUE Mob::GetSpecialFactionCon(Mob* iOther) { if (selfAIcontrolled && iOtherAIControlled) return FACTION_ALLY; else - return FACTION_INDIFFERENT; + return FACTION_INDIFFERENTLY; case -3: // -3 = dub to player, ally to AI on special values, indiff to AI if (selfAIcontrolled && iOtherAIControlled) return FACTION_ALLY; else - return FACTION_DUBIOUS; + return FACTION_DUBIOUSLY; case -4: // -4 = atk to player, ally to AI on special values, indiff to AI if (selfAIcontrolled && iOtherAIControlled) return FACTION_ALLY; else return FACTION_SCOWLS; case -5: // -5 = indiff to player, indiff to AI - return FACTION_INDIFFERENT; + return FACTION_INDIFFERENTLY; case -6: // -6 = dub to player, indiff to AI if (selfAIcontrolled && iOtherAIControlled) - return FACTION_INDIFFERENT; + return FACTION_INDIFFERENTLY; else - return FACTION_DUBIOUS; + return FACTION_DUBIOUSLY; case -7: // -7 = atk to player, indiff to AI if (selfAIcontrolled && iOtherAIControlled) - return FACTION_INDIFFERENT; + return FACTION_INDIFFERENTLY; else return FACTION_SCOWLS; case -8: // -8 = indiff to players, ally to AI on same value, indiff to AI @@ -5295,25 +5886,25 @@ FACTION_VALUE Mob::GetSpecialFactionCon(Mob* iOther) { if (selfPrimaryFaction == iOtherPrimaryFaction) return FACTION_ALLY; else - return FACTION_INDIFFERENT; + return FACTION_INDIFFERENTLY; } else - return FACTION_INDIFFERENT; + return FACTION_INDIFFERENTLY; case -9: // -9 = dub to players, ally to AI on same value, indiff to AI if (selfAIcontrolled && iOtherAIControlled) { if (selfPrimaryFaction == iOtherPrimaryFaction) return FACTION_ALLY; else - return FACTION_INDIFFERENT; + return FACTION_INDIFFERENTLY; } else - return FACTION_DUBIOUS; + return FACTION_DUBIOUSLY; case -10: // -10 = atk to players, ally to AI on same value, indiff to AI if (selfAIcontrolled && iOtherAIControlled) { if (selfPrimaryFaction == iOtherPrimaryFaction) return FACTION_ALLY; else - return FACTION_INDIFFERENT; + return FACTION_INDIFFERENTLY; } else return FACTION_SCOWLS; @@ -5325,7 +5916,7 @@ FACTION_VALUE Mob::GetSpecialFactionCon(Mob* iOther) { return FACTION_SCOWLS; } else - return FACTION_INDIFFERENT; + return FACTION_INDIFFERENTLY; case -12: // -12 = dub to players, ally to AI on same value, atk to AI if (selfAIcontrolled && iOtherAIControlled) { if (selfPrimaryFaction == iOtherPrimaryFaction) @@ -5336,7 +5927,7 @@ FACTION_VALUE Mob::GetSpecialFactionCon(Mob* iOther) { } else - return FACTION_DUBIOUS; + return FACTION_DUBIOUSLY; case -13: // -13 = atk to players, ally to AI on same value, atk to AI if (selfAIcontrolled && iOtherAIControlled) { if (selfPrimaryFaction == iOtherPrimaryFaction) @@ -5347,11 +5938,11 @@ FACTION_VALUE Mob::GetSpecialFactionCon(Mob* iOther) { else return FACTION_SCOWLS; default: - return FACTION_INDIFFERENT; + return FACTION_INDIFFERENTLY; } } -bool Mob::HasSpellEffect(int effectid) +bool Mob::HasSpellEffect(int effect_id) { int i; @@ -5360,7 +5951,7 @@ bool Mob::HasSpellEffect(int effectid) { if(buffs[i].spellid == SPELL_UNKNOWN) { continue; } - if(IsEffectInSpell(buffs[i].spellid, effectid)) + if(IsEffectInSpell(buffs[i].spellid, effect_id)) { return(1); } @@ -5536,118 +6127,7 @@ bool Mob::GetSeeInvisible(uint8 see_invis) int32 Mob::GetSpellStat(uint32 spell_id, const char *identifier, uint8 slot) { - if (!IsValidSpell(spell_id)) - return 0; - - if (!identifier) - return 0; - - int32 stat = 0; - - if (slot > 0) - slot = slot - 1; - - std::string id = identifier; - for(uint32 i = 0; i < id.length(); ++i) - { - id[i] = tolower(id[i]); - } - - if (slot < 16){ - if (id == "classes") {return spells[spell_id].classes[slot]; } - else if (id == "dieties") {return spells[spell_id].deities[slot];} - } - - if (slot < 12){ - if (id == "base") {return spells[spell_id].base[slot];} - else if (id == "base2") {return spells[spell_id].base2[slot];} - else if (id == "max") {return spells[spell_id].max[slot];} - else if (id == "formula") {return spells[spell_id].formula[slot];} - else if (id == "effectid") {return spells[spell_id].effectid[slot];} - } - - if (slot < 4){ - if (id == "components") { return spells[spell_id].components[slot];} - else if (id == "component_counts") { return spells[spell_id].component_counts[slot];} - else if (id == "NoexpendReagent") {return spells[spell_id].NoexpendReagent[slot];} - } - - if (id == "range") {return static_cast(spells[spell_id].range); } - else if (id == "aoerange") {return static_cast(spells[spell_id].aoerange);} - else if (id == "pushback") {return static_cast(spells[spell_id].pushback);} - else if (id == "pushup") {return static_cast(spells[spell_id].pushup);} - else if (id == "cast_time") {return spells[spell_id].cast_time;} - else if (id == "recovery_time") {return spells[spell_id].recovery_time;} - else if (id == "recast_time") {return spells[spell_id].recast_time;} - else if (id == "buffdurationformula") {return spells[spell_id].buffdurationformula;} - else if (id == "buffduration") {return spells[spell_id].buffduration;} - else if (id == "AEDuration") {return spells[spell_id].AEDuration;} - else if (id == "mana") {return spells[spell_id].mana;} - //else if (id == "LightType") {stat = spells[spell_id].LightType;} - Not implemented - else if (id == "goodEffect") {return spells[spell_id].goodEffect;} - else if (id == "Activated") {return spells[spell_id].Activated;} - else if (id == "resisttype") {return spells[spell_id].resisttype;} - else if (id == "targettype") {return spells[spell_id].targettype;} - else if (id == "basedeiff") {return spells[spell_id].basediff;} - else if (id == "skill") {return spells[spell_id].skill;} - else if (id == "zonetype") {return spells[spell_id].zonetype;} - else if (id == "EnvironmentType") {return spells[spell_id].EnvironmentType;} - else if (id == "TimeOfDay") {return spells[spell_id].TimeOfDay;} - else if (id == "CastingAnim") {return spells[spell_id].CastingAnim;} - else if (id == "SpellAffectIndex") {return spells[spell_id].SpellAffectIndex; } - else if (id == "disallow_sit") {return spells[spell_id].disallow_sit; } - //else if (id == "spellanim") {stat = spells[spell_id].spellanim; } - Not implemented - else if (id == "uninterruptable") {return spells[spell_id].uninterruptable; } - else if (id == "ResistDiff") {return spells[spell_id].ResistDiff; } - else if (id == "dot_stacking_exemp") {return spells[spell_id].dot_stacking_exempt; } - else if (id == "RecourseLink") {return spells[spell_id].RecourseLink; } - else if (id == "no_partial_resist") {return spells[spell_id].no_partial_resist; } - else if (id == "short_buff_box") {return spells[spell_id].short_buff_box; } - else if (id == "descnum") {return spells[spell_id].descnum; } - else if (id == "effectdescnum") {return spells[spell_id].effectdescnum; } - else if (id == "npc_no_los") {return spells[spell_id].npc_no_los; } - else if (id == "reflectable") {return spells[spell_id].reflectable; } - else if (id == "bonushate") {return spells[spell_id].bonushate; } - else if (id == "EndurCost") {return spells[spell_id].EndurCost; } - else if (id == "EndurTimerIndex") {return spells[spell_id].EndurTimerIndex; } - else if (id == "IsDisciplineBuf") {return spells[spell_id].IsDisciplineBuff; } - else if (id == "HateAdded") {return spells[spell_id].HateAdded; } - else if (id == "EndurUpkeep") {return spells[spell_id].EndurUpkeep; } - else if (id == "numhitstype") {return spells[spell_id].numhitstype; } - else if (id == "numhits") {return spells[spell_id].numhits; } - else if (id == "pvpresistbase") {return spells[spell_id].pvpresistbase; } - else if (id == "pvpresistcalc") {return spells[spell_id].pvpresistcalc; } - else if (id == "pvpresistcap") {return spells[spell_id].pvpresistcap; } - else if (id == "spell_category") {return spells[spell_id].spell_category; } - else if (id == "can_mgb") {return spells[spell_id].can_mgb; } - else if (id == "dispel_flag") {return spells[spell_id].dispel_flag; } - else if (id == "MinResist") {return spells[spell_id].MinResist; } - else if (id == "MaxResist") {return spells[spell_id].MaxResist; } - else if (id == "viral_targets") {return spells[spell_id].viral_targets; } - else if (id == "viral_timer") {return spells[spell_id].viral_timer; } - else if (id == "NimbusEffect") {return spells[spell_id].NimbusEffect; } - else if (id == "directional_start") {return static_cast(spells[spell_id].directional_start); } - else if (id == "directional_end") {return static_cast(spells[spell_id].directional_end); } - else if (id == "not_focusable") {return spells[spell_id].not_focusable; } - else if (id == "suspendable") {return spells[spell_id].suspendable; } - else if (id == "viral_range") {return spells[spell_id].viral_range; } - else if (id == "spellgroup") {return spells[spell_id].spellgroup; } - else if (id == "rank") {return spells[spell_id].rank; } - else if (id == "no_resist") {return spells[spell_id].no_resist; } - else if (id == "CastRestriction") {return spells[spell_id].CastRestriction; } - else if (id == "AllowRest") {return spells[spell_id].AllowRest; } - else if (id == "InCombat") {return spells[spell_id].InCombat; } - else if (id == "OutofCombat") {return spells[spell_id].OutofCombat; } - else if (id == "aemaxtargets") {return spells[spell_id].aemaxtargets; } - else if (id == "no_heal_damage_item_mod") {return spells[spell_id].no_heal_damage_item_mod; } - else if (id == "persistdeath") {return spells[spell_id].persistdeath; } - else if (id == "min_dist") {return static_cast(spells[spell_id].min_dist); } - else if (id == "min_dist_mod") {return static_cast(spells[spell_id].min_dist_mod); } - else if (id == "max_dist") {return static_cast(spells[spell_id].max_dist); } - else if (id == "min_range") {return static_cast(spells[spell_id].min_range); } - else if (id == "DamageShieldType") {return spells[spell_id].DamageShieldType; } - - return stat; + return GetSpellStatValue(spell_id, identifier, slot); } bool Mob::CanClassEquipItem(uint32 item_id) @@ -5655,22 +6135,53 @@ bool Mob::CanClassEquipItem(uint32 item_id) const EQ::ItemData* itm = nullptr; itm = database.GetItem(item_id); - if (!itm) + if (!itm) { return false; + } - if(itm->Classes == 65535 ) + auto item_classes = itm->Classes; + if(item_classes == PLAYER_CLASS_ALL_MASK) { return true; + } - if (GetClass() > 16) + auto class_id = GetClass(); + if (class_id > BERSERKER) { return false; + } - int bitmask = 1; - bitmask = bitmask << (GetClass() - 1); - - if(!(itm->Classes & bitmask)) + int class_bitmask = GetPlayerClassBit(class_id); + if(!(item_classes & class_bitmask)) { return false; - else + } else { return true; + } +} + +bool Mob::CanRaceEquipItem(uint32 item_id) +{ + const EQ::ItemData* itm = nullptr; + itm = database.GetItem(item_id); + + if (!itm) { + return false; + } + + auto item_races = itm->Races; + if(item_races == PLAYER_RACE_ALL_MASK) { + return true; + } + + auto race_id = GetBaseRace(); + if (!IsPlayerRace(race_id)) { + return false; + } + + int race_bitmask = GetPlayerRaceBit(race_id); + if(!(item_races & race_bitmask)) { + return false; + } else { + return true; + } } void Mob::SendAddPlayerState(PlayerState new_state) @@ -5898,6 +6409,140 @@ float Mob::GetDefaultRaceSize() const { return GetRaceGenderDefaultHeight(race, gender); } +bool Mob::ShieldAbility(uint32 target_id, int shielder_max_distance, int shield_duration, int shield_target_mitigation, int shielder_mitigation, bool use_aa, bool can_shield_npc) +{ + Mob* shield_target = entity_list.GetMob(target_id); + if (!shield_target) { + return false; + } + + if (!can_shield_npc && shield_target->IsNPC()) { + if (IsClient()) { + MessageString(Chat::White, SHIELD_TARGET_NPC); + } + return false; + } + + if (shield_target->GetID() == GetID()) { //Client will give message "You can not shield yourself" + return false; + } + + //Edge case situations. If 'Shield Target' still has Shielder set but Shielder is not in zone. Catch and fix here. + if (shield_target->GetShielderID() && !entity_list.GetMob(shield_target->GetShielderID())) { + shield_target->SetShielderID(0); + } + + if (GetShielderID() && !entity_list.GetMob(GetShielderID())) { + SetShielderID(0); + } + + //You have a shielder, or your 'Shield Target' already has a 'Shielder' + if (GetShielderID() || shield_target->GetShielderID()) { + if (IsClient()) { + MessageString(Chat::White, ALREADY_SHIELDED); + } + return false; + } + + //You are being shielded or already have a 'Shield Target' + if (GetShieldTargetID() || shield_target->GetShieldTargetID()) { + if (IsClient()) { + MessageString(Chat::White, ALREADY_SHIELDING); + } + return false; + } + + //AA to increase SPA 230 extended shielding (default live is 15 distance units) + if (use_aa) { + shielder_max_distance += aabonuses.ExtendedShielding + itembonuses.ExtendedShielding + spellbonuses.ExtendedShielding; + shielder_max_distance = std::max(shielder_max_distance, 0); + } + + if (shield_target->CalculateDistance(GetX(), GetY(), GetZ()) > static_cast(shielder_max_distance)) { + return false; //Live does not give a message when out of range. + } + + entity_list.MessageCloseString(this, false, 100, 0, START_SHIELDING, GetCleanName(), shield_target->GetCleanName()); + + SetShieldTargetID(shield_target->GetID()); + SetShielderMitigation(shield_target_mitigation); + SetShielderMaxDistance(shielder_max_distance); + + shield_target->SetShielderID(GetID()); + shield_target->SetShieldTargetMitigation(shield_target_mitigation); + + //Calculate AA for adding time SPA 255 extend shield duration (Baseline ability is 12 seconds) + if (use_aa) { + shield_duration += (aabonuses.ShieldDuration + itembonuses.ShieldDuration + spellbonuses.ShieldDuration) * 1000; + shield_duration = std::max(shield_duration, 1); //Incase of negative modifiers lets just make min duration 1 ms. + } + + shield_timer.Start(static_cast(shield_duration)); + return true; +} + +void Mob::ShieldAbilityFinish() +{ + Mob* shield_target = entity_list.GetMob(GetShieldTargetID()); + + if (shield_target) { + entity_list.MessageCloseString(this, false, 100, 0, END_SHIELDING, GetCleanName(), shield_target->GetCleanName()); + shield_target->SetShielderID(0); + shield_target->SetShieldTargetMitigation(0); + } + SetShieldTargetID(0); + SetShielderMitigation(0); + SetShielderMaxDistance(0); + shield_timer.Disable(); +} + +void Mob::ShieldAbilityClearVariables() +{ + //If 'shield target' dies + if (GetShielderID()){ + Mob* shielder = entity_list.GetMob(GetShielderID()); + if (shielder) { + shielder->SetShieldTargetID(0); + shielder->SetShielderMitigation(0); + shielder->SetShielderMaxDistance(0); + shielder->shield_timer.Disable(); + } + SetShielderID(0); + SetShieldTargetMitigation(0); + } + + //If 'shielder' dies + if (GetShieldTargetID()) { + Mob* shield_target = entity_list.GetMob(GetShieldTargetID()); + if (shield_target) { + shield_target->SetShielderID(0); + shield_target->SetShieldTargetMitigation(0); + } + SetShieldTargetID(0); + SetShielderMitigation(0); + SetShielderMaxDistance(0); + shield_timer.Disable(); + } +} + +void Mob::SetFeigned(bool in_feigned) { + + if (in_feigned) { + if (IsClient()) { + if (RuleB(Character, FeignKillsPet)){ + SetPet(0); + } + CastToClient()->SetHorseId(0); + } + entity_list.ClearFeignAggro(this); + forget_timer.Start(FeignMemoryDuration); + } + else { + forget_timer.Disable(); + } + feigned = in_feigned; +} + #ifdef BOTS bool Mob::JoinHealRotationTargetPool(std::shared_ptr* heal_rotation) { diff --git a/zone/mob.h b/zone/mob.h index cad133442..ca38653fa 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -71,7 +71,7 @@ class Mob : public Entity { public: enum CLIENT_CONN_STATUS { CLIENT_CONNECTING, CLIENT_CONNECTED, CLIENT_LINKDEAD, CLIENT_KICKED, DISCONNECTED, CLIENT_ERROR, CLIENT_CONNECTINGALL }; - enum eStandingPetOrder { SPO_Follow, SPO_Sit, SPO_Guard }; + enum eStandingPetOrder { SPO_Follow, SPO_Sit, SPO_Guard, SPO_FeignDeath }; struct SpecialAbility { SpecialAbility() { @@ -195,7 +195,7 @@ public: // 13 = Primary (default), 14 = secondary virtual bool Attack(Mob* other, int Hand = EQ::invslot::slotPrimary, bool FromRiposte = false, bool IsStrikethrough = false, bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr) = 0; - void DoAttack(Mob *other, DamageHitInfo &hit, ExtraAttackOptions *opts = nullptr); + void DoAttack(Mob *other, DamageHitInfo &hit, ExtraAttackOptions *opts = nullptr, bool FromRiposte = false); int MonkSpecialAttack(Mob* other, uint8 skill_used); virtual void TryBackstab(Mob *other,int ReuseTime = 10); bool AvoidDamage(Mob *attacker, DamageHitInfo &hit); @@ -277,27 +277,24 @@ public: void ChangeSize(float in_size, bool bNoRestriction = false); void DoAnim(const int animnum, int type=0, bool ackreq = true, eqFilterType filter = FilterNone); void ProjectileAnimation(Mob* to, int item_id, bool IsArrow = false, float speed = 0, float angle = 0, float tilt = 0, float arc = 0, const char *IDFile = nullptr, EQ::skills::SkillType skillInUse = EQ::skills::SkillArchery); - void SendAppearanceEffect(uint32 parm1, uint32 parm2, uint32 parm3, uint32 parm4, uint32 parm5, Client *specific_target=nullptr); + void SendAppearanceEffect(uint32 parm1, uint32 parm2, uint32 parm3, uint32 parm4, uint32 parm5, Client *specific_target=nullptr, uint32 value1slot = 1, uint32 value1ground = 1, uint32 value2slot = 1, uint32 value2ground = 1, + uint32 value3slot = 1, uint32 value3ground = 1, uint32 value4slot = 1, uint32 value4ground = 1, uint32 value5slot = 1, uint32 value5ground = 1); void SendLevelAppearance(); void SendStunAppearance(); void SendTargetable(bool on, Client *specific_target = nullptr); void SetInvisible(uint8 state); void SetMobTextureProfile(uint8 material_slot, uint16 texture, uint32 color = 0, uint32 hero_forge_model = 0); - //Song - bool UseBardSpellLogic(uint16 spell_id = 0xffff, int slot = -1); - bool ApplyNextBardPulse(uint16 spell_id, Mob *spell_target, EQ::spells::CastingSlot slot); - void BardPulse(uint16 spell_id, Mob *caster); - //Spell - void SendSpellEffect(uint32 effectid, uint32 duration, uint32 finish_delay, bool zone_wide, - uint32 unk020, bool perm_effect = false, Client *c = nullptr); + void SendSpellEffect(uint32 effect_id, uint32 duration, uint32 finish_delay, bool zone_wide, + uint32 unk020, bool perm_effect = false, Client *c = nullptr, uint32 caster_id = 0, uint32 target_id = 0); bool IsBeneficialAllowed(Mob *target); virtual int GetCasterLevel(uint16 spell_id); void ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* newbon, uint16 casterID = 0, uint8 WornType = 0, int32 ticsremaining = 0, int buffslot = -1, int instrument_mod = 10, bool IsAISpellEffect = false, uint16 effect_id = 0, int32 se_base = 0, int32 se_limit = 0, int32 se_max = 0); - void NegateSpellsBonuses(uint16 spell_id); + void NegateSpellEffectBonuses(uint16 spell_id); + bool NegateSpellEffect(uint16 spell_id, int effect_id); virtual float GetActSpellRange(uint16 spell_id, float range, bool IsBard = false); virtual int32 GetActSpellDamage(uint16 spell_id, int32 value, Mob* target = nullptr); virtual int32 GetActDoTDamage(uint16 spell_id, int32 value, Mob* target); @@ -305,6 +302,7 @@ public: virtual int32 GetActSpellCost(uint16 spell_id, int32 cost){ return cost;} virtual int32 GetActSpellDuration(uint16 spell_id, int32 duration); virtual int32 GetActSpellCasttime(uint16 spell_id, int32 casttime); + virtual int32 GetActReflectedSpellDamage(int32 spell_id, int32 value, int effectiveness); float ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use_resist_override = false, int resist_override = 0, bool CharismaCheck = false, bool CharmTick = false, bool IsRoot = false, int level_override = -1); @@ -328,13 +326,16 @@ public: void CastedSpellFinished(uint16 spell_id, uint32 target_id, EQ::spells::CastingSlot slot, uint16 mana_used, uint32 inventory_slot = 0xFFFFFFFF, int16 resist_adjust = 0); bool SpellFinished(uint16 spell_id, Mob *target, EQ::spells::CastingSlot slot = EQ::spells::CastingSlot::Item, uint16 mana_used = 0, - uint32 inventory_slot = 0xFFFFFFFF, int16 resist_adjust = 0, bool isproc = false, int level_override = -1); + uint32 inventory_slot = 0xFFFFFFFF, int16 resist_adjust = 0, bool isproc = false, int level_override = -1, uint32 timer = 0xFFFFFFFF, uint32 timer_duration = 0, bool from_casted_spell = false, uint32 aa_id = 0); void SendBeginCast(uint16 spell_id, uint32 casttime); - virtual bool SpellOnTarget(uint16 spell_id, Mob* spelltar, bool reflect = false, - bool use_resist_adjust = false, int16 resist_adjust = 0, bool isproc = false, int level_override = -1); - virtual bool SpellEffect(Mob* caster, uint16 spell_id, float partial = 100, int level_override = -1); + virtual bool SpellOnTarget(uint16 spell_id, Mob* spelltar, int reflect_effectiveness = 0, + bool use_resist_adjust = false, int16 resist_adjust = 0, bool isproc = false, int level_override = -1, int32 duration_override = 0); + virtual bool SpellEffect(Mob* caster, uint16 spell_id, float partial = 100, int level_override = -1, int reflect_effectiveness = 0, int32 duration_override = 0); virtual bool DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_center, CastAction_type &CastAction, EQ::spells::CastingSlot slot, bool isproc = false); + bool DoCastingChecksOnCaster(int32 spell_id); + bool DoCastingChecksZoneRestrictions(bool check_on_casting, int32 spell_id); + bool DoCastingChecksOnTarget(bool check_on_casting, int32 spell_id, Mob* spell_target); virtual bool CheckFizzle(uint16 spell_id); virtual bool CheckSpellLevelRestriction(uint16 spell_id); virtual bool IsImmuneToSpell(uint16 spell_id, Mob *caster); @@ -342,25 +343,40 @@ public: void InterruptSpell(uint16 spellid = SPELL_UNKNOWN); void InterruptSpell(uint16, uint16, uint16 spellid = SPELL_UNKNOWN); void StopCasting(); + void StopCastSpell(int32 spell_id, bool send_spellbar_enable); inline bool IsCasting() const { return((casting_spell_id != 0)); } uint16 CastingSpellID() const { return casting_spell_id; } - bool DoCastingChecks(); bool TryDispel(uint8 caster_level, uint8 buff_level, int level_modifier); bool TrySpellProjectile(Mob* spell_target, uint16 spell_id, float speed = 1.5f); void ResourceTap(int32 damage, uint16 spell_id); void TryTriggerThreshHold(int32 damage, int effect_id, Mob* attacker); - bool CheckSpellCategory(uint16 spell_id, int category_id, int effect_id); void CalcDestFromHeading(float heading, float distance, float MaxZDiff, float StartX, float StartY, float &dX, float &dY, float &dZ); void BeamDirectional(uint16 spell_id, int16 resist_adjust); void ConeDirectional(uint16 spell_id, int16 resist_adjust); + void ApplyHealthTransferDamage(Mob *caster, Mob *target, uint16 spell_id); + void ApplySpellEffectIllusion(int32 spell_id, Mob* caster, int buffslot, int base, int limit, int max); + void ApplyIllusionToCorpse(int32 spell_id, Corpse* new_corpse); + void SendIllusionWearChange(Client* c); + int16 GetItemSlotToConsumeCharge(int32 spell_id, uint32 inventory_slot); + bool CheckItemRaceClassDietyRestrictionsOnCast(uint32 inventory_slot); + + //Bard + bool ApplyBardPulse(int32 spell_id, Mob *spell_target, EQ::spells::CastingSlot slot); + bool IsActiveBardSong(int32 spell_id); + bool HasActiveSong() const { return(bardsong != 0); } + void ZeroBardPulseVars(); + void DoBardCastingFromItemClick(bool is_casting_bard_song, uint32 cast_time, int32 spell_id, uint16 target_id, EQ::spells::CastingSlot slot, uint32 item_slot, + uint32 recast_type , uint32 recast_delay); + bool UseBardSpellLogic(uint16 spell_id = 0xffff, int slot = -1); //Buff void BuffProcess(); virtual void DoBuffTic(const Buffs_Struct &buff, int slot, Mob* caster = nullptr); void BuffFadeBySpellID(uint16 spell_id); void BuffFadeBySpellIDAndCaster(uint16 spell_id, uint16 caster_id); - void BuffFadeByEffect(int effectid, int skipslot = -1); + void BuffFadeByEffect(int effect_id, int slot_to_skip = -1); void BuffFadeAll(); + void BuffFadeBeneficial(); void BuffFadeNonPersistDeath(); void BuffFadeDetrimental(); void BuffFadeBySlot(int slot, bool iRecalcBonuses = true); @@ -391,7 +407,7 @@ public: void DoGravityEffect(); void DamageShield(Mob* other, bool spell_ds = false); int32 RuneAbsorb(int32 damage, uint16 type); - bool FindBuff(uint16 spellid); + bool FindBuff(uint16 spell_id); uint16 FindBuffBySlot(int slot); uint32 BuffCount(); bool FindType(uint16 type, bool bOffensive = false, uint16 threshold = 100); @@ -405,20 +421,25 @@ public: inline void SetMGB(bool val) { has_MGB = val; } bool HasProjectIllusion() const { return has_ProjectIllusion ; } inline void SetProjectIllusion(bool val) { has_ProjectIllusion = val; } - void SpreadVirus(uint16 spell_id, uint16 casterID); bool IsNimbusEffectActive(uint32 nimbus_effect); void SetNimbusEffect(uint32 nimbus_effect); inline virtual uint32 GetNimbusEffect1() const { return nimbus_effect1; } inline virtual uint32 GetNimbusEffect2() const { return nimbus_effect2; } inline virtual uint32 GetNimbusEffect3() const { return nimbus_effect3; } - void AddNimbusEffect(int effectid); - void RemoveNimbusEffect(int effectid); + void AddNimbusEffect(int effect_id); + void RemoveNimbusEffect(int effect_id); + void RemoveAllNimbusEffects(); inline const glm::vec3& GetTargetRingLocation() const { return m_TargetRing; } inline float GetTargetRingX() const { return m_TargetRing.x; } inline float GetTargetRingY() const { return m_TargetRing.y; } inline float GetTargetRingZ() const { return m_TargetRing.z; } inline bool HasEndurUpkeep() const { return endur_upkeep; } inline void SetEndurUpkeep(bool val) { endur_upkeep = val; } + bool HasBuffWithSpellGroup(int spell_group); + void SetAppearenceEffects(int32 slot, int32 value); + void GetAppearenceEffects(); + void ClearAppearenceEffects(); + void SendSavedAppearenceEffects(Client *receiver); //Basic Stats/Inventory virtual void SetLevel(uint8 in_level, bool command = false) { level = in_level; } @@ -431,6 +452,8 @@ public: inline void SetTwoHandBluntEquiped(bool val) { has_twohandbluntequiped = val; } bool HasTwoHanderEquipped() { return has_twohanderequipped; } void SetTwoHanderEquipped(bool val) { has_twohanderequipped = val; } + bool HasDualWeaponsEquiped() const { return has_duelweaponsequiped; } + inline void SetDuelWeaponsEquiped(bool val) { has_duelweaponsequiped = val; } bool CanFacestab() { return can_facestab; } void SetFacestab(bool val) { can_facestab = val; } virtual uint16 GetSkill(EQ::skills::SkillType skill_num) const { return 0; } @@ -440,6 +463,7 @@ public: virtual uint32 GetEquipmentColor(uint8 material_slot) const; virtual uint32 IsEliteMaterialItem(uint8 material_slot) const; bool CanClassEquipItem(uint32 item_id); + bool CanRaceEquipItem(uint32 item_id); bool AffectedBySpellExcludingSlot(int slot, int effect); virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, EQ::skills::SkillType attack_skill) = 0; virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQ::skills::SkillType attack_skill, @@ -618,6 +642,7 @@ public: void Teleport(const glm::vec3 &pos); void Teleport(const glm::vec4 &pos); void TryMoveAlong(float distance, float angle, bool send = true); + glm::vec4 TryMoveAlong(const glm::vec4 &start, float distance, float angle); void ProcessForcedMovement(); inline void IncDeltaX(float in) { m_Delta.x += in; } inline void IncDeltaY(float in) { m_Delta.y += in; } @@ -641,6 +666,11 @@ public: Mob* GetSecondaryHate(Mob *skip) { return hate_list.GetEntWithMostHateOnList(this, skip); } Mob* GetHateDamageTop(Mob* other) { return hate_list.GetDamageTopOnHateList(other);} Mob* GetHateRandom() { return hate_list.GetRandomEntOnHateList();} + Client* GetHateRandomClient() { return hate_list.GetRandomClientOnHateList(); } + NPC* GetHateRandomNPC() { return hate_list.GetRandomNPCOnHateList(); } +#ifdef BOTS + Bot* GetHateRandomBot() { return hate_list.GetRandomBotOnHateList(); } +#endif Mob* GetHateMost() { return hate_list.GetEntWithMostHateOnList();} Mob* GetHateClosest() { return hate_list.GetClosestEntOnHateList(this); } bool IsEngaged() { return(!hate_list.IsHateListEmpty()); } @@ -651,10 +681,10 @@ public: bool HateSummon(); void FaceTarget(Mob* mob_to_face = 0); void WipeHateList(); - void AddFeignMemory(Client* attacker); - void RemoveFromFeignMemory(Client* attacker); + void AddFeignMemory(Mob* attacker); + void RemoveFromFeignMemory(Mob* attacker); void ClearFeignMemory(); - bool IsOnFeignMemory(Client *attacker) const; + bool IsOnFeignMemory(Mob *attacker) const; void PrintHateListToClient(Client *who) { hate_list.PrintHateListToClient(who); } std::list& GetHateList() { return hate_list.GetHateList(); } std::list GetHateListByDistance(int distance = 0) { return hate_list.GetHateListByDistance(distance); } @@ -675,14 +705,14 @@ public: static void CreateSpawnPacket(EQApplicationPacket* app, NewSpawn_Struct* ns); virtual void FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho); void CreateHPPacket(EQApplicationPacket* app); - void SendHPUpdate(bool skip_self = false, bool force_update_all = false); + void SendHPUpdate(bool force_update_all = false); virtual void ResetHPUpdateTimer() {}; // does nothing //Util static uint32 RandomTimer(int min, int max); static uint8 GetDefaultGender(uint16 in_race, uint8 in_gender = 0xFF); static bool IsPlayerRace(uint16 in_race); - uint16 GetSkillByItemType(int ItemType); + EQ::skills::SkillType GetSkillByItemType(int ItemType); uint8 GetItemTypeBySkill(EQ::skills::SkillType skill); virtual void MakePet(uint16 spell_id, const char* pettype, const char *petname = nullptr); virtual void MakePoweredPet(uint16 spell_id, const char* pettype, int16 petpower, const char *petname = nullptr, float in_size = 0.0f); @@ -714,15 +744,15 @@ public: //Procs void TriggerDefensiveProcs(Mob *on, uint16 hand = EQ::invslot::slotPrimary, bool FromSkillProc = false, int damage = 0); - bool AddRangedProc(uint16 spell_id, uint16 iChance = 3, uint16 base_spell_id = SPELL_UNKNOWN); + bool AddRangedProc(uint16 spell_id, uint16 iChance = 3, uint16 base_spell_id = SPELL_UNKNOWN, uint32 proc_reuse_time = 0); bool RemoveRangedProc(uint16 spell_id, bool bAll = false); bool HasRangedProcs() const; - bool AddDefensiveProc(uint16 spell_id, uint16 iChance = 3, uint16 base_spell_id = SPELL_UNKNOWN); + bool AddDefensiveProc(uint16 spell_id, uint16 iChance = 3, uint16 base_spell_id = SPELL_UNKNOWN, uint32 proc_reuse_time = 0); bool RemoveDefensiveProc(uint16 spell_id, bool bAll = false); bool HasDefensiveProcs() const; bool HasSkillProcs() const; bool HasSkillProcSuccess() const; - bool AddProcToWeapon(uint16 spell_id, bool bPerma = false, uint16 iChance = 3, uint16 base_spell_id = SPELL_UNKNOWN, int level_override = -1); + bool AddProcToWeapon(uint16 spell_id, bool bPerma = false, uint16 iChance = 3, uint16 base_spell_id = SPELL_UNKNOWN, int level_override = -1, uint32 proc_reuse_time = 0); bool RemoveProcFromWeapon(uint16 spell_id, bool bAll = false); bool HasProcs() const; bool IsCombatProc(uint16 spell_id); @@ -767,13 +797,13 @@ public: void QuestJournalledSay(Client *QuestInitiator, const char *str, Journal::Options &opts); int32 GetItemStat(uint32 itemid, const char *identifier); - int16 CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, bool best_focus=false); + int32 CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, bool best_focus=false, uint16 casterid = 0, Mob *caster = nullptr); uint8 IsFocusEffect(uint16 spellid, int effect_index, bool AA=false,uint32 aa_effect=0); void SendIllusionPacket(uint16 in_race, uint8 in_gender = 0xFF, uint8 in_texture = 0xFF, uint8 in_helmtexture = 0xFF, uint8 in_haircolor = 0xFF, uint8 in_beardcolor = 0xFF, uint8 in_eyecolor1 = 0xFF, uint8 in_eyecolor2 = 0xFF, uint8 in_hairstyle = 0xFF, uint8 in_luclinface = 0xFF, uint8 in_beard = 0xFF, uint8 in_aa_title = 0xFF, uint32 in_drakkin_heritage = 0xFFFFFFFF, uint32 in_drakkin_tattoo = 0xFFFFFFFF, - uint32 in_drakkin_details = 0xFFFFFFFF, float in_size = -1.0f); + uint32 in_drakkin_details = 0xFFFFFFFF, float in_size = -1.0f, bool send_appearance_effects = true); bool RandomizeFeatures(bool send_illusion = true, bool set_variables = true); virtual void Stun(int duration); virtual void UnStun(); @@ -781,27 +811,28 @@ public: inline void Amnesia(bool newval) { amnesiad = newval; } void TemporaryPets(uint16 spell_id, Mob *target, const char *name_override = nullptr, uint32 duration_override = 0, bool followme=true, bool sticktarg=false, uint16 *controlled_pet_id = nullptr); void TypesTemporaryPets(uint32 typesid, Mob *target, const char *name_override = nullptr, uint32 duration_override = 0, bool followme=true, bool sticktarg=false); - void WakeTheDead(uint16 spell_id, Mob *target, uint32 duration); + void WakeTheDead(uint16 spell_id, Corpse *corpse_to_use, Mob *target, uint32 duration); void Spin(); void Kill(); bool PassCharismaCheck(Mob* caster, uint16 spell_id); bool TryDeathSave(); bool TryDivineSave(); void DoBuffWearOffEffect(uint32 index); - void TryTriggerOnCast(uint32 spell_id, bool aa_trigger); - void TriggerOnCast(uint32 focus_spell, uint32 spell_id, bool aa_trigger); + void TryTriggerOnCastFocusEffect(focusType type, uint16 spell_id); + bool TryTriggerOnCastProc(uint16 focusspellid, uint16 spell_id, uint16 proc_spellid); bool TrySpellTrigger(Mob *target, uint32 spell_id, int effect); - void TryTriggerOnValueAmount(bool IsHP = false, bool IsMana = false, bool IsEndur = false, bool IsPet = false); + void TryTriggerOnCastRequirement(); void TryTwincast(Mob *caster, Mob *target, uint32 spell_id); void TrySympatheticProc(Mob *target, uint32 spell_id); bool TryFadeEffect(int slot); uint16 GetSpellEffectResistChance(uint16 spell_id); - int16 GetHealRate(uint16 spell_id, Mob* caster = nullptr); - int32 GetVulnerability(Mob* caster, uint32 spell_id, uint32 ticsremaining); - int32 GetFcDamageAmtIncoming(Mob *caster, uint32 spell_id, bool use_skill = false, uint16 skill=0); - int32 GetFocusIncoming(focusType type, int effect, Mob *caster, uint32 spell_id); - int16 GetSkillDmgTaken(const EQ::skills::SkillType skill_used, ExtraAttackOptions *opts = nullptr); - void DoKnockback(Mob *caster, uint32 pushback, uint32 pushup); + int32 GetVulnerability(Mob *caster, uint32 spell_id, uint32 ticsremaining); + int32 GetFcDamageAmtIncoming(Mob *caster, int32 spell_id); + int32 GetFocusIncoming(focusType type, int effect, Mob *caster, uint32 spell_id); //**** This can be removed when bot healing focus code is updated **** + int32 GetSkillDmgTaken(const EQ::skills::SkillType skill_used, ExtraAttackOptions *opts = nullptr); + int32 GetPositionalDmgTaken(Mob *attacker); + int32 GetPositionalDmgTakenAmt(Mob *attacker); + void DoKnockback(Mob *caster, uint32 push_back, uint32 push_up); int16 CalcResistChanceBonus(); int16 CalcFearResistChance(); void TrySpellOnKill(uint8 level, uint16 spell_id); @@ -810,21 +841,22 @@ public: void CastOnCure(uint32 spell_id); void CastOnNumHitFade(uint32 spell_id); void SlowMitigation(Mob* caster); - int16 GetCritDmgMod(uint16 skill); + int16 GetCritDmgMod(uint16 skill, Mob* owner = nullptr); int16 GetMeleeDamageMod_SE(uint16 skill); int16 GetMeleeMinDamageMod_SE(uint16 skill); int16 GetCrippBlowChance(); + int16 GetMeleeDmgPositionMod(Mob* defender); int16 GetSkillReuseTime(uint16 skill); int GetCriticalChanceBonus(uint16 skill); int16 GetSkillDmgAmt(uint16 skill); - bool TryReflectSpell(uint32 spell_id); - bool CanBlockSpell() const { return(spellbonuses.BlockNextSpell); } + int16 GetPositionalDmgAmt(Mob* defender); + inline bool CanBlockSpell() const { return(spellbonuses.FocusEffects[focusBlockNextSpell]); } bool DoHPToManaCovert(uint16 mana_cost = 0); - int32 ApplySpellEffectiveness(int16 spell_id, int32 value, bool IsBard = false, uint16 caster_id=0); int8 GetDecayEffectValue(uint16 spell_id, uint16 spelleffect); int32 GetExtraSpellAmt(uint16 spell_id, int32 extra_spell_amt, int32 base_spell_dmg); void MeleeLifeTap(int32 damage); - bool PassCastRestriction(bool UseCastRestriction = true, int16 value = 0, bool IsDamage = true); + bool PassCastRestriction(int value); + void SendCastRestrictionMessage(int requirement_id, bool is_target_requirement = true, bool is_discipline = false); bool ImprovedTaunt(); bool TryRootFadeByDamage(int buffslot, Mob* attacker); float GetSlowMitigation() const { return slow_mitigation; } @@ -832,6 +864,32 @@ public: inline int16 GetSpellPowerDistanceMod() const { return SpellPowerDistanceMod; }; inline void SetSpellPowerDistanceMod(int16 value) { SpellPowerDistanceMod = value; }; int32 GetSpellStat(uint32 spell_id, const char *identifier, uint8 slot = 0); + bool HarmonySpellLevelCheck(int32 spell_id, Mob* target = nullptr); + bool PassCharmTargetRestriction(Mob *target); + bool CanFocusUseRandomEffectivenessByType(focusType type); + int GetFocusRandomEffectivenessValue(int focus_base, int focus_base2, bool best_focus = 0); + int GetHealRate() const { return itembonuses.HealRate + spellbonuses.HealRate + aabonuses.HealRate; } + int GetMemoryBlurChance(int base_chance); + inline bool HasBaseEffectFocus() const { return (spellbonuses.FocusEffects[focusFcBaseEffects] || aabonuses.FocusEffects[focusFcBaseEffects] || itembonuses.FocusEffects[focusFcBaseEffects]); } + int32 GetDualWieldingSameDelayWeapons() const { return dw_same_delay; } + inline void SetDualWieldingSameDelayWeapons(int32 val) { dw_same_delay = val; } + bool IsTargetedFocusEffect(int focus_type); + bool HasPersistDeathIllusion(int32 spell_id); + + + bool TryDoubleMeleeRoundEffect(); + bool GetUseDoubleMeleeRoundDmgBonus() const { return use_double_melee_round_dmg_bonus; } + inline void SetUseDoubleMeleeRoundDmgBonus(bool val) { use_double_melee_round_dmg_bonus = val; } + + void CastSpellOnLand(Mob* caster, int32 spell_id); + + bool IsFocusProcLimitTimerActive(int32 focus_spell_id); + void SetFocusProcLimitTimer(int32 focus_spell_id, uint32 focus_reuse_time); + bool IsProcLimitTimerActive(int32 base_spell_id, uint32 proc_reuse_time, int proc_type); + void SetProcLimitTimer(int32 base_spell_id, uint32 proc_reuse_time, int proc_type); + + void VirusEffectProcess(); + void SpreadVirusEffect(int32 spell_id, uint32 caster_id, int32 buff_tics_remaining); void ModSkillDmgTaken(EQ::skills::SkillType skill_num, int value); int16 GetModSkillDmgTaken(const EQ::skills::SkillType skill_num); @@ -867,6 +925,7 @@ public: virtual void UpdateEquipmentLight() { m_Light.Type[EQ::lightsource::LightEquipment] = 0; m_Light.Level[EQ::lightsource::LightEquipment] = 0; } inline void SetSpellLightType(uint8 light_type) { m_Light.Type[EQ::lightsource::LightSpell] = (light_type & 0x0F); m_Light.Level[EQ::lightsource::LightSpell] = EQ::lightsource::TypeToLevel(m_Light.Type[EQ::lightsource::LightSpell]); } + void SendWearChangeAndLighting(int8 last_texture); inline uint8 GetActiveLightType() { return m_Light.Type[EQ::lightsource::LightActive]; } bool UpdateActiveLight(); // returns true if change, false if no change @@ -901,9 +960,14 @@ public: bool HasPetAffinity() { if (aabonuses.GivePetGroupTarget || itembonuses.GivePetGroupTarget || spellbonuses.GivePetGroupTarget) return true; return false; } inline bool IsPetOwnerClient() const { return pet_owner_client; } inline void SetPetOwnerClient(bool value) { pet_owner_client = value; } + inline bool IsPetOwnerNPC() const { return pet_owner_npc; } + inline void SetPetOwnerNPC(bool value) { pet_owner_npc = value; } inline bool IsTempPet() const { return _IsTempPet; } inline void SetTempPet(bool value) { _IsTempPet = value; } inline bool IsHorse() { return is_horse; } + int GetPetAvoidanceBonusFromOwner(); + int GetPetACBonusFromOwner(); + int GetPetATKBonusFromOwner(); inline const bodyType GetBodyType() const { return bodytype; } inline const bodyType GetOrigBodyType() const { return orig_bodytype; } @@ -948,10 +1012,10 @@ public: int32 ReduceAllDamage(int32 damage); void DoSpecialAttackDamage(Mob *who, EQ::skills::SkillType skill, int base_damage, int min_damage = 0, int32 hate_override = -1, int ReuseTime = 10); - virtual void DoThrowingAttackDmg(Mob* other, const EQ::ItemInstance* RangeWeapon = nullptr, const EQ::ItemData* AmmoItem = nullptr, uint16 weapon_damage = 0, int16 chance_mod = 0, int16 focus = 0, int ReuseTime = 0, uint32 range_id = 0, int AmmoSlot = 0, float speed = 4.0f); + virtual void DoThrowingAttackDmg(Mob* other, const EQ::ItemInstance* RangeWeapon = nullptr, const EQ::ItemData* AmmoItem = nullptr, uint16 weapon_damage = 0, int16 chance_mod = 0, int16 focus = 0, int ReuseTime = 0, uint32 range_id = 0, int AmmoSlot = 0, float speed = 4.0f, bool DisableProcs = false); void DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, EQ::skills::SkillType skillinuse, int16 chance_mod = 0, int16 focus = 0, bool CanRiposte = false, int ReuseTime = 0); - virtual void DoArcheryAttackDmg(Mob* other, const EQ::ItemInstance* RangeWeapon = nullptr, const EQ::ItemInstance* Ammo = nullptr, uint16 weapon_damage = 0, int16 chance_mod = 0, int16 focus = 0, int ReuseTime = 0, uint32 range_id = 0, uint32 ammo_id = 0, const EQ::ItemData *AmmoItem = nullptr, int AmmoSlot = 0, float speed = 4.0f); - bool TryProjectileAttack(Mob* other, const EQ::ItemData *item, EQ::skills::SkillType skillInUse, uint16 weapon_dmg, const EQ::ItemInstance* RangeWeapon, const EQ::ItemInstance* Ammo, int AmmoSlot, float speed); + virtual void DoArcheryAttackDmg(Mob* other, const EQ::ItemInstance* RangeWeapon = nullptr, const EQ::ItemInstance* Ammo = nullptr, uint16 weapon_damage = 0, int16 chance_mod = 0, int16 focus = 0, int ReuseTime = 0, uint32 range_id = 0, uint32 ammo_id = 0, const EQ::ItemData *AmmoItem = nullptr, int AmmoSlot = 0, float speed = 4.0f, bool DisableProcs = false); + bool TryProjectileAttack(Mob* other, const EQ::ItemData *item, EQ::skills::SkillType skillInUse, uint16 weapon_dmg, const EQ::ItemInstance* RangeWeapon, const EQ::ItemInstance* Ammo, int AmmoSlot, float speed, bool DisableProcs = false); void ProjectileAttack(); inline bool HasProjectileAttack() const { return ActiveProjectileATK; } inline void SetProjectileAttack(bool value) { ActiveProjectileATK = value; } @@ -961,6 +1025,8 @@ public: bool Rampage(ExtraAttackOptions *opts); bool AddRampage(Mob*); void ClearRampage(); + void SetBottomRampageList(); + void SetTopRampageList(); void AreaRampage(ExtraAttackOptions *opts); inline bool IsSpecialAttack(eSpecialAttacks in) { return m_specialattacks == in; } @@ -1005,7 +1071,7 @@ public: inline const bool IsRoamer() const { return roamer; } inline const int GetWanderType() const { return wandertype; } inline const bool IsRooted() const { return rooted || permarooted; } - inline const bool HasVirus() const { return has_virus; } + inline const bool IsPermaRooted() const { return permarooted; } int GetSnaredAmount(); inline const bool IsPseudoRooted() const { return pseudo_rooted; } inline void SetPseudoRoot(bool prState) { pseudo_rooted = prState; } @@ -1070,18 +1136,16 @@ public: void InstillDoubt(Mob *who); int16 GetResist(uint8 type) const; - Mob* GetShieldTarget() const { return shield_target; } - void SetShieldTarget(Mob* mob) { shield_target = mob; } - bool HasActiveSong() const { return(bardsong != 0); } bool Charmed() const { return typeofpet == petCharmed; } static uint32 GetLevelHP(uint8 tlevel); uint32 GetZoneID() const; //for perl virtual int32 CheckAggroAmount(uint16 spell_id, Mob *target, bool isproc = false); virtual int32 CheckHealAggroAmount(uint16 spell_id, Mob *target, uint32 heal_possible = 0); - uint32 GetInstrumentMod(uint16 spell_id) const; + //uint32 GetInstrumentMod(uint16 spell_id) const; + uint32 GetInstrumentMod(uint16 spell_id); int CalcSpellEffectValue(uint16 spell_id, int effect_id, int caster_level = 1, uint32 instrument_mod = 10, Mob *caster = nullptr, int ticsremaining = 0,uint16 casterid=0); - int CalcSpellEffectValue_formula(int formula, int base, int max, int caster_level, uint16 spell_id, int ticsremaining = 0); + int CalcSpellEffectValue_formula(int formula, int base_value, int max_value, int caster_level, uint16 spell_id, int ticsremaining = 0); virtual int CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2, int caster_level2, Mob* caster1 = nullptr, Mob* caster2 = nullptr, int buffslot = -1); uint32 GetCastedSpellInvSlot() const { return casting_spell_inventory_slot; } @@ -1109,14 +1173,33 @@ public: bool IsMoved() { return moved; } void SetMoved(bool moveflag) { moved = moveflag; } - Shielders_Struct shielder[MAX_SHIELDERS]; Trade* trade; + bool ShieldAbility(uint32 target_id, int shielder_max_distance = 15, int shield_duration = 12000, int shield_target_mitigation = 50, int shielder_mitigation = 75, bool use_aa = false, bool can_shield_npc = true); + void DoShieldDamageOnShielder(Mob *shield_target, int hit_damage_done, EQ::skills::SkillType skillInUse); + void ShieldAbilityFinish(); + void ShieldAbilityClearVariables(); + inline uint32 GetShielderID() const { return m_shielder_id; } + inline void SetShielderID(uint32 val) { m_shielder_id = val; } + inline uint32 GetShieldTargetID() const { return m_shield_target_id; } + inline void SetShieldTargetID(uint32 val) { m_shield_target_id = val; } + inline int GetShieldTargetMitigation() const { return m_shield_target_mitigation; } + inline void SetShieldTargetMitigation(int val) { m_shield_target_mitigation = val; } + inline int GetShielderMitigation() const { return m_shielder_mitigation; } + inline void SetShielderMitigation(int val) { m_shielder_mitigation = val; } + inline int GetMaxShielderDistance() const { return m_shielder_max_distance; } + inline void SetShielderMaxDistance(int val) { m_shielder_max_distance = val; } + + WeaponStance_Struct weaponstance; + bool IsWeaponStanceEnabled() const { return weaponstance.enabled; } + inline void SetWeaponStanceEnabled(bool val) { weaponstance.enabled = val; } + + inline glm::vec4 GetCurrentWayPoint() const { return m_CurrentWayPoint; } inline float GetCWPP() const { return(static_cast(cur_wp_pause)); } inline int GetCWP() const { return(cur_wp); } void SetCurrentWP(int waypoint) { cur_wp = waypoint; } - virtual FACTION_VALUE GetReverseFactionCon(Mob* iOther) { return FACTION_INDIFFERENT; } + virtual FACTION_VALUE GetReverseFactionCon(Mob* iOther) { return FACTION_INDIFFERENTLY; } virtual const bool IsUnderwaterOnly() const { return false; } inline bool IsTrackable() const { return(trackable); } @@ -1139,7 +1222,7 @@ public: inline void SetEmoteID(uint16 emote) { emoteid = emote; } inline uint16 GetEmoteID() { return emoteid; } - bool HasSpellEffect(int effectid); + bool HasSpellEffect(int effect_id); int mod_effect_value(int effect_value, uint16 spell_id, int effect_type, Mob* caster, uint16 caster_id); float mod_hit_chance(float chancetohit, EQ::skills::SkillType skillinuse, Mob* attacker); float mod_riposte_chance(float ripostchance, Mob* attacker); @@ -1166,14 +1249,38 @@ public: bool mod_will_aggro(Mob *attacker, Mob *on); //Command #Tune functions - int32 Tune_MeleeMitigation(Mob* GM, Mob *attacker, int32 damage, int32 minhit, ExtraAttackOptions *opts = nullptr, int Msg =0, int ac_override=0, int atk_override=0, int add_ac=0, int add_atk = 0); - virtual int32 Tune_GetMeleeMitDmg(Mob* GM, Mob *attacker, int32 damage, int32 minhit, float mit_rating, float atk_rating); - uint32 Tune_GetMeanDamage(Mob* GM, Mob *attacker, int32 damage, int32 minhit, ExtraAttackOptions *opts = nullptr, int Msg = 0,int ac_override=0, int atk_override=0, int add_ac=0, int add_atk = 0); - void Tune_FindATKByPctMitigation(Mob* defender, Mob *attacker, float pct_mitigation, int interval = 50, int max_loop = 100, int ac_override=0,int Msg =0); - void Tune_FindACByPctMitigation(Mob* defender, Mob *attacker, float pct_mitigation, int interval = 50, int max_loop = 100, int atk_override=0,int Msg =0); - float Tune_CheckHitChance(Mob* defender, Mob* attacker, EQ::skills::SkillType skillinuse, int Hand, int16 chance_mod, int Msg = 1, int acc_override = 0, int avoid_override = 0, int add_acc = 0, int add_avoid = 0); - void Tune_FindAccuaryByHitChance(Mob* defender, Mob *attacker, float hit_chance, int interval, int max_loop, int avoid_override, int Msg = 0); - void Tune_FindAvoidanceByHitChance(Mob* defender, Mob *attacker, float hit_chance, int interval, int max_loop, int acc_override, int Msg = 0); + void TuneGetStats(Mob* defender, Mob *attacker); + void TuneGetACByPctMitigation(Mob* defender, Mob *attacker, float pct_mitigation, int interval = 10, int max_loop = 1000, int atk_override = 0, int Msg = 0); + void TuneGetATKByPctMitigation(Mob* defender, Mob *attacker, float pct_mitigation, int interval = 10, int max_loop = 1000, int ac_override = 0, int Msg = 0); + void TuneGetAvoidanceByHitChance(Mob* defender, Mob *attacker, float hit_chance, int interval, int max_loop, int accuracy_override, int Msg); + void TuneGetAccuracyByHitChance(Mob* defender, Mob *attacker, float hit_chance, int interval, int max_loop, int avoidance_override, int Msg); + /*support functions*/ + int TuneClientGetMeanDamage(Mob* other, int ac_override = 0, int atk_override = 0, int add_ac = 0, int add_atk = 0); + int TuneClientGetMaxDamage(Mob* other); + int TuneClientGetMinDamage(Mob* other, int max_hit); + float TuneGetACMitigationPct(Mob* defender, Mob *attacker); + int TuneGetOffense(Mob* defender, Mob *attacker, int atk_override = 0); + int TuneGetAccuracy(Mob* defender, Mob *attacker, int accuracy_override = 0, int add_accuracy = 0); + int TuneGetAvoidance(Mob* defender, Mob *attacker, int avoidance_override = 0, int add_avoidance = 0); + float TuneGetHitChance(Mob* defender, Mob *attacker, int avoidance_override = 0, int accuracy_override = 0, int add_avoidance = 0, int add_accuracy = 0); + float TuneGetAvoidMeleeChance(Mob* defender, Mob *attacker, int type); + int TuneCalcEvasionBonus(int final_avoidance, int base_avoidance); + /*modified combat code - These SYNC to attack.cpp, relevant changes to these functions in attack.cpp should be changed to the below as well*/ + int TuneNPCAttack(Mob* other, bool no_avoid = true, bool no_hit_chance = true, int hit_chance_bonus = 10000, int ac_override = 0, int atk_override = 0, int add_ac = 0, int add_atk = 0, + bool get_offense = false, bool get_accuracy = false, int avoidance_override = 0, int accuracy_override = 0, int add_avoidance = 0, int add_accuracy = 0); + int TuneClientAttack(Mob* other, bool no_avoid = true, bool no_hit_chance = true, int hit_chance_bonus = 10000, int ac_override = 0, int atk_override = 0, int add_ac = 0, int add_atk = 0, + bool get_offense = false, bool get_accuracy = false, int avoidance_override = 0, int accuracy_override = 0, int add_avoidance = 0, int add_accuracy = 0); + void TuneDoAttack(Mob *other, DamageHitInfo &hit, ExtraAttackOptions *opts = nullptr, bool no_avoid = true, bool no_hit_chance = true, int ac_override = 0, int add_ac = 0, + int avoidance_override = 0, int accuracy_override = 0, int add_avoidance = 0, int add_accuracy = 0); + void TuneMeleeMitigation(Mob *attacker, DamageHitInfo &hit, int ac_override, int add_ac); + int Tuneoffense(EQ::skills::SkillType skill, int atk_override = 0, int add_atk = 0); + int TuneACSum(bool skip_caps=false, int ac_override = 0, int add_ac = 0); + int TuneGetTotalToHit(EQ::skills::SkillType skill, int chance_mod, int accuracy_override = 0, int add_accurracy = 0); // compute_tohit + spell bonuses + int Tunecompute_tohit(EQ::skills::SkillType skillinuse, int accuracy_override = 0, int add_accuracy = 0); + int TuneGetTotalDefense(int avoidance_override = 0, int add_avoidance = 0); + int Tunecompute_defense(int avoidance_override = 0, int add_avoidance = 0); + bool TuneCheckHitChance(Mob* other, DamageHitInfo &hit, int avoidance_override = 0, int add_avoidance = 0); + EQ::skills::SkillType TuneAttackAnimation(int Hand, const EQ::ItemInstance* weapon, EQ::skills::SkillType skillinuse = EQ::skills::Skill1HBlunt); //aa new uint32 GetAA(uint32 rank_id, uint32 *charges = nullptr) const; @@ -1201,6 +1308,10 @@ public: bool CanOpenDoors() const; void SetCanOpenDoors(bool can_open); + void SetFeigned(bool in_feigned); + /// this cures timing issues cuz dead animation isn't done but server side feigning is? + inline bool GetFeigned() const { return(feigned); } + void DeleteBucket(std::string bucket_name); std::string GetBucket(std::string bucket_name); std::string GetBucketExpires(std::string bucket_name); @@ -1352,13 +1463,14 @@ protected: bool spawned; void CalcSpellBonuses(StatBonuses* newbon); virtual void CalcBonuses(); - void TrySkillProc(Mob *on, uint16 skill, uint16 ReuseTime, bool Success = false, uint16 hand = 0, bool IsDefensive = false); // hand = SlotCharm? - bool PassLimitToSkill(uint16 spell_id, uint16 skill); + void TrySkillProc(Mob *on, EQ::skills::SkillType skill, uint16 ReuseTime, bool Success = false, uint16 hand = 0, bool IsDefensive = false); // hand if 0 means its a skill ability for proc rate checks, otherwise hand is passed. + bool PassLimitToSkill(EQ::skills::SkillType skill, int32 spell_id, int proc_type, int aa_id=0); bool PassLimitClass(uint32 Classes_, uint16 Class_); + void TryCastOnSkillUse(Mob *on, EQ::skills::SkillType skill); void TryDefensiveProc(Mob *on, uint16 hand = EQ::invslot::slotPrimary); void TryWeaponProc(const EQ::ItemInstance* inst, const EQ::ItemData* weapon, Mob *on, uint16 hand = EQ::invslot::slotPrimary); void TrySpellProc(const EQ::ItemInstance* inst, const EQ::ItemData* weapon, Mob *on, uint16 hand = EQ::invslot::slotPrimary); - void TryWeaponProc(const EQ::ItemInstance* weapon, Mob *on, uint16 hand = EQ::invslot::slotPrimary); + void TryCombatProcs(const EQ::ItemInstance* weapon, Mob *on, uint16 hand = EQ::invslot::slotPrimary, const EQ::ItemData* weapon_data = nullptr); void ExecWeaponProc(const EQ::ItemInstance* weapon, uint16 spell_id, Mob *on, int level_override = -1); virtual float GetProcChances(float ProcBonus, uint16 hand = EQ::invslot::slotPrimary); virtual float GetDefensiveProcChances(float &ProcBonus, float &ProcChance, uint16 hand = EQ::invslot::slotPrimary, Mob *on = nullptr); @@ -1368,7 +1480,7 @@ protected: virtual #endif int GetBaseSkillDamage(EQ::skills::SkillType skill, Mob *target = nullptr); - virtual int16 GetFocusEffect(focusType type, uint16 spell_id) { return 0; } + virtual int32 GetFocusEffect(focusType type, uint16 spell_id, Mob *caster = nullptr) { return 0; } void CalculateNewFearpoint(); float FindGroundZ(float new_x, float new_y, float z_offset=0.0); float FindDestGroundZ(glm::vec3 dest, float z_offset=0.0); @@ -1409,6 +1521,29 @@ protected: int16 slow_mitigation; // Allows for a slow mitigation (100 = 100%, 50% = 50%) Timer tic_timer; Timer mana_timer; + int32 dw_same_delay; + + Timer focusproclimit_timer[MAX_FOCUS_PROC_LIMIT_TIMERS]; //SPA 511 + int32 focusproclimit_spellid[MAX_FOCUS_PROC_LIMIT_TIMERS]; //SPA 511 + + Timer spell_proclimit_timer[MAX_PROC_LIMIT_TIMERS]; //SPA 512 + int32 spell_proclimit_spellid[MAX_PROC_LIMIT_TIMERS]; //SPA 512 + Timer ranged_proclimit_timer[MAX_PROC_LIMIT_TIMERS]; //SPA 512 + int32 ranged_proclimit_spellid[MAX_PROC_LIMIT_TIMERS]; //SPA 512 + Timer def_proclimit_timer[MAX_PROC_LIMIT_TIMERS]; //SPA 512 + int32 def_proclimit_spellid[MAX_PROC_LIMIT_TIMERS]; //SPA 512 + + int32 appearance_effects_id[MAX_APPEARANCE_EFFECTS]; + int32 appearance_effects_slot[MAX_APPEARANCE_EFFECTS]; + + int queue_wearchange_slot; + + Timer shield_timer; + uint32 m_shield_target_id; + uint32 m_shielder_id; + int m_shield_target_mitigation; + int m_shielder_mitigation; + int m_shielder_max_distance; //spell casting vars Timer spellend_timer; @@ -1425,6 +1560,7 @@ protected: uint32 casting_spell_type; int16 casting_spell_resist_adjust; uint32 casting_spell_aa_id; + uint32 casting_spell_recast_adjust; bool casting_spell_checks; uint16 bardsong; EQ::spells::CastingSlot bardsong_slot; @@ -1456,8 +1592,6 @@ protected: uint8 aa_title; - Mob* shield_target; - int ExtraHaste; // for the #haste command bool mezzed; bool stunned; @@ -1466,12 +1600,12 @@ protected: bool silenced; bool amnesiad; bool inWater; // Set to true or false by Water Detection code if enabled by rules - bool has_virus; // whether this mob has a viral spell on them - uint16 viral_spells[MAX_SPELL_TRIGGER*2]; // Stores the spell ids of the viruses on target and caster ids bool offhand; bool has_shieldequiped; bool has_twohandbluntequiped; bool has_twohanderequipped; + bool has_duelweaponsequiped; + bool use_double_melee_round_dmg_bonus; bool can_facestab; bool has_numhits; bool has_MGB; @@ -1482,10 +1616,12 @@ protected: bool endur_upkeep; bool degenerating_effects; // true if we have a buff that needs to be recalced every tick bool spawned_in_water; + public: bool GetWasSpawnedInWater() const; void SetSpawnedInWater(bool spawned_in_water); + bool turning; protected: @@ -1498,7 +1634,6 @@ protected: Timer bardsong_timer; Timer gravity_timer; Timer viral_timer; - uint8 viral_timer_counter; // MobAI stuff eStandingPetOrder pStandingPetOrder; @@ -1576,6 +1711,7 @@ protected: bool _IsTempPet; int16 count_TempPet; bool pet_owner_client; //Flags regular and pets as belonging to a client + bool pet_owner_npc; //Flags regular and pets as belonging to a npc uint32 pet_targetlock_id; glm::vec3 m_TargetRing; @@ -1594,20 +1730,22 @@ protected: std::unordered_map> aa_ranks; Timer aa_timers[aaTimerMax]; - + bool is_horse; AuraMgr aura_mgr; AuraMgr trap_mgr; + bool feigned; + Timer forget_timer; // our 2 min everybody forgets you timer + bool m_can_open_doors; MobMovementManager *mMovementManager; private: - void _StopSong(); //this is not what you think it is Mob* target; - + #ifdef BOTS std::shared_ptr m_target_of_heal_rotation; diff --git a/zone/mob_ai.cpp b/zone/mob_ai.cpp index 306c6dd64..906b3b5c9 100644 --- a/zone/mob_ai.cpp +++ b/zone/mob_ai.cpp @@ -115,11 +115,11 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes, bool bInnates // should probably match that eventually. This should be good enough for now I guess .... if ( ( - (spells[AIspells[i].spellid].targettype == ST_HateList || spells[AIspells[i].spellid].targettype == ST_AETargetHateList) || + (spells[AIspells[i].spellid].target_type == ST_HateList || spells[AIspells[i].spellid].target_type == ST_AETargetHateList) || ( // note: I think this check is actually wrong and we should be checking range instead in all cases, BUT if range is 0, range check is skipped? Works for now - (spells[AIspells[i].spellid].targettype==ST_AECaster || spells[AIspells[i].spellid].targettype==ST_AEBard || spells[AIspells[i].spellid].targettype==ST_AEClientV1) - && dist2 <= spells[AIspells[i].spellid].aoerange*spells[AIspells[i].spellid].aoerange + (spells[AIspells[i].spellid].target_type==ST_AECaster || spells[AIspells[i].spellid].target_type==ST_AEBard || spells[AIspells[i].spellid].target_type==ST_AEClientV1) + && dist2 <= spells[AIspells[i].spellid].aoe_range*spells[AIspells[i].spellid].aoe_range ) || dist2 <= spells[AIspells[i].spellid].range*spells[AIspells[i].spellid].range ) @@ -135,7 +135,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes, bool bInnates switch (AIspells[i].type) { case SpellType_Heal: { if ( - (spells[AIspells[i].spellid].targettype == ST_Target || tar == this) + (spells[AIspells[i].spellid].target_type == ST_Target || tar == this) && tar->DontHealMeBefore() < Timer::GetCurrentTime() && !(tar->IsPet() && tar->GetOwner()->IsClient()) //no buffing PC's pets ) { @@ -174,7 +174,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes, bool bInnates } case SpellType_Buff: { if ( - (spells[AIspells[i].spellid].targettype == ST_Target || tar == this) + (spells[AIspells[i].spellid].target_type == ST_Target || tar == this) && tar->DontBuffMeBefore() < Timer::GetCurrentTime() && !tar->IsImmuneToSpell(AIspells[i].spellid, this) && tar->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0 @@ -838,20 +838,21 @@ void Client::AI_Process() else { if(AI_feign_remember_timer->Check()) { - std::set::iterator RememberedCharID; - RememberedCharID = feign_memory_list.begin(); - while (RememberedCharID != feign_memory_list.end()) { - Client* remember_client = entity_list.GetClientByCharID(*RememberedCharID); - if (remember_client == nullptr) { + std::set::iterator remembered_feigned_mobid; + remembered_feigned_mobid = feign_memory_list.begin(); + while (remembered_feigned_mobid != feign_memory_list.end()) { + + Mob* remembered_mob = entity_list.GetMob(*remembered_feigned_mobid); + if (remembered_mob == nullptr || remembered_mob->IsCorpse()) { //they are gone now... - RememberedCharID = feign_memory_list.erase(RememberedCharID); - } else if (!remember_client->GetFeigned()) { - AddToHateList(remember_client->CastToMob(),1); - RememberedCharID = feign_memory_list.erase(RememberedCharID); + remembered_feigned_mobid = feign_memory_list.erase(remembered_feigned_mobid); + } else if (!remembered_mob->GetFeigned()) { + AddToHateList(remembered_mob,1); + remembered_feigned_mobid = feign_memory_list.erase(remembered_feigned_mobid); break; } else { //they are still feigned, carry on... - ++RememberedCharID; + ++remembered_feigned_mobid; } } } @@ -1000,6 +1001,14 @@ void Mob::AI_Process() { continue; } + if (door->GetTriggerDoorID() > 0) { + continue; + } + + if (door->GetDoorParam() > 0) { + continue; + } + float distance = DistanceSquared(m_Position, door->GetPosition()); float distance_scan_door_open = 20; @@ -1125,6 +1134,10 @@ void Mob::AI_Process() { ProjectileAttack(); + if (shield_timer.Check()) { + ShieldAbilityFinish(); + } + auto npcSpawnPoint = CastToNPC()->GetSpawnPoint(); if (GetSpecialAbility(TETHER)) { float tether_range = static_cast(GetSpecialAbilityParam(TETHER, 0)); @@ -1218,17 +1231,16 @@ void Mob::AI_Process() { } } + + //SE_PC_Pet_Rampage SPA 464 on pet, chance modifier if ((IsPet() || IsTempPet()) && IsPetOwnerClient()) { - if (spellbonuses.PC_Pet_Rampage[0] || itembonuses.PC_Pet_Rampage[0] || - aabonuses.PC_Pet_Rampage[0]) { - int chance = spellbonuses.PC_Pet_Rampage[0] + itembonuses.PC_Pet_Rampage[0] + - aabonuses.PC_Pet_Rampage[0]; - if (zone->random.Roll(chance)) { - Rampage(nullptr); - } + int chance = spellbonuses.PC_Pet_Rampage[SBIndex::PET_RAMPAGE_CHANCE] + itembonuses.PC_Pet_Rampage[SBIndex::PET_RAMPAGE_CHANCE] + aabonuses.PC_Pet_Rampage[SBIndex::PET_RAMPAGE_CHANCE]; + if (chance && zone->random.Roll(chance)) { + Rampage(nullptr); } } + if (GetSpecialAbility(SPECATK_RAMPAGE) && !specialed) { int rampage_chance = GetSpecialAbilityParam(SPECATK_RAMPAGE, 0); rampage_chance = rampage_chance > 0 ? rampage_chance : 20; @@ -1263,6 +1275,14 @@ void Mob::AI_Process() { } } + //SE_PC_Pet_Rampage SPA 465 on pet, chance modifier + if ((IsPet() || IsTempPet()) && IsPetOwnerClient()) { + int chance = spellbonuses.PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_CHANCE] + itembonuses.PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_CHANCE] + aabonuses.PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_CHANCE]; + if (chance && zone->random.Roll(chance)) { + Rampage(nullptr); + } + } + if (GetSpecialAbility(SPECATK_AREA_RAMPAGE) && !specialed) { int rampage_chance = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 0); rampage_chance = rampage_chance > 0 ? rampage_chance : 20; @@ -1329,7 +1349,7 @@ void Mob::AI_Process() { } // mob/npc waits until call for help complete, others can move else if (AI_movement_timer->Check() && target && - (GetOwnerID() || IsBot() || + (GetOwnerID() || IsBot() || IsTempPet() || CastToNPC()->GetCombatEvent())) { if (!IsRooted()) { LogAI("Pursuing [{}] while engaged", target->GetName()); @@ -1354,22 +1374,22 @@ void Mob::AI_Process() { // 6/14/06 // Improved Feign Death Memory // check to see if any of our previous feigned targets have gotten up. - std::set::iterator RememberedCharID; - RememberedCharID = feign_memory_list.begin(); - while (RememberedCharID != feign_memory_list.end()) { - Client *remember_client = entity_list.GetClientByCharID(*RememberedCharID); - if (remember_client == nullptr) { + std::set::iterator remembered_feigned_mobid; + remembered_feigned_mobid = feign_memory_list.begin(); + while (remembered_feigned_mobid != feign_memory_list.end()) { + Mob *remembered_mob = entity_list.GetMob(*remembered_feigned_mobid); + if (remembered_mob == nullptr || remembered_mob->IsCorpse()) { //they are gone now... - RememberedCharID = feign_memory_list.erase(RememberedCharID); + remembered_feigned_mobid = feign_memory_list.erase(remembered_feigned_mobid); } - else if (!remember_client->GetFeigned()) { - AddToHateList(remember_client->CastToMob(), 1); - RememberedCharID = feign_memory_list.erase(RememberedCharID); + else if (!remembered_mob->GetFeigned()) { + AddToHateList(remembered_mob, 1); + remembered_feigned_mobid = feign_memory_list.erase(remembered_feigned_mobid); break; } else { //they are still feigned, carry on... - ++RememberedCharID; + ++remembered_feigned_mobid; } } } @@ -1466,6 +1486,10 @@ void Mob::AI_Process() { } break; } + case SPO_FeignDeath: { + SetAppearance(eaDead, false); + break; + } } if (IsPetRegroup()) { return; @@ -1538,6 +1562,11 @@ void Mob::AI_Process() { } } + if (forget_timer.Check()) { + forget_timer.Disable(); + entity_list.ClearZoneFeignAggro(this); + } + //Do Ranged attack here if (doranged) { RangedAttack(target); @@ -1752,9 +1781,8 @@ void NPC::AI_DoMovement() { } //kick off event_waypoint arrive - char temp[16]; - sprintf(temp, "%d", cur_wp); - parse->EventNPC(EVENT_WAYPOINT_ARRIVE, CastToNPC(), nullptr, temp, 0); + std::string buf = fmt::format("{}", cur_wp); + parse->EventNPC(EVENT_WAYPOINT_ARRIVE, CastToNPC(), nullptr, buf.c_str(), 0); // No need to move as we are there. Next loop will // take care of normal grids, even at pause 0. // We do need to call and setup a wp if we're cur_wp=-2 @@ -1871,9 +1899,8 @@ void NPC::AI_SetupNextWaypoint() { if (!DistractedFromGrid) { //kick off event_waypoint depart - char temp[16]; - sprintf(temp, "%d", cur_wp); - parse->EventNPC(EVENT_WAYPOINT_DEPART, CastToNPC(), nullptr, temp, 0); + std::string buf = fmt::format("{}", cur_wp); + parse->EventNPC(EVENT_WAYPOINT_DEPART, CastToNPC(), nullptr, buf.c_str(), 0); //setup our next waypoint, if we are still on our normal grid //remember that the quest event above could have done anything it wanted with our grid @@ -2456,10 +2483,8 @@ void NPC::CheckSignal() { if (!signal_q.empty()) { int signal_id = signal_q.front(); signal_q.pop_front(); - char buf[32]; - snprintf(buf, 31, "%d", signal_id); - buf[31] = '\0'; - parse->EventNPC(EVENT_SIGNAL, this, nullptr, buf, 0); + std::string buf = fmt::format("{}", signal_id); + parse->EventNPC(EVENT_SIGNAL, this, nullptr, buf.c_str(), 0); } } @@ -2490,7 +2515,7 @@ create table npc_spells_entries ( */ bool IsSpellInList(DBnpcspells_Struct* spell_list, uint16 iSpellID); -bool IsSpellEffectInList(DBnpcspellseffects_Struct* spelleffect_list, uint16 iSpellEffectID, int32 base, int32 limit, int32 max); +bool IsSpellEffectInList(DBnpcspellseffects_Struct* spelleffect_list, uint16 iSpellEffectID, int32 base_value, int32 limit, int32 max_value); bool NPC::AI_AddNPCSpells(uint32 iDBSpellsID) { // ok, this function should load the list, and the parent list then shove them into the struct and sort @@ -2701,12 +2726,12 @@ bool NPC::AI_AddNPCSpellsEffects(uint32 iDBSpellsEffectsID) { if (parentlist) { for (i=0; inumentries; i++) { if (GetLevel() >= parentlist->entries[i].minlevel && GetLevel() <= parentlist->entries[i].maxlevel && parentlist->entries[i].spelleffectid > 0) { - if (!IsSpellEffectInList(spell_effects_list, parentlist->entries[i].spelleffectid, parentlist->entries[i].base, - parentlist->entries[i].limit, parentlist->entries[i].max)) + if (!IsSpellEffectInList(spell_effects_list, parentlist->entries[i].spelleffectid, parentlist->entries[i].base_value, + parentlist->entries[i].limit, parentlist->entries[i].max_value)) { AddSpellEffectToNPCList(parentlist->entries[i].spelleffectid, - parentlist->entries[i].base, parentlist->entries[i].limit, - parentlist->entries[i].max); + parentlist->entries[i].base_value, parentlist->entries[i].limit, + parentlist->entries[i].max_value); } } } @@ -2715,8 +2740,8 @@ bool NPC::AI_AddNPCSpellsEffects(uint32 iDBSpellsEffectsID) { for (i=0; inumentries; i++) { if (GetLevel() >= spell_effects_list->entries[i].minlevel && GetLevel() <= spell_effects_list->entries[i].maxlevel && spell_effects_list->entries[i].spelleffectid > 0) { AddSpellEffectToNPCList(spell_effects_list->entries[i].spelleffectid, - spell_effects_list->entries[i].base, spell_effects_list->entries[i].limit, - spell_effects_list->entries[i].max); + spell_effects_list->entries[i].base_value, spell_effects_list->entries[i].limit, + spell_effects_list->entries[i].max_value); } } @@ -2730,15 +2755,14 @@ void NPC::ApplyAISpellEffects(StatBonuses* newbon) for (int i = 0; i < AIspellsEffects.size(); i++) ApplySpellsBonuses(0, 0, newbon, 0, 0, 0, -1, 10, true, AIspellsEffects[i].spelleffectid, - AIspellsEffects[i].base, AIspellsEffects[i].limit, AIspellsEffects[i].max); + AIspellsEffects[i].base_value, AIspellsEffects[i].limit, AIspellsEffects[i].max_value); return; } // adds a spell to the list, taking into account priority and resorting list as needed. -void NPC::AddSpellEffectToNPCList(uint16 iSpellEffectID, int32 base, int32 limit, int32 max) +void NPC::AddSpellEffectToNPCList(uint16 iSpellEffectID, int32 base_value, int32 limit, int32 max_value, bool apply_bonus) { - if(!iSpellEffectID) return; @@ -2746,16 +2770,50 @@ void NPC::AddSpellEffectToNPCList(uint16 iSpellEffectID, int32 base, int32 limit AISpellsEffects_Struct t; t.spelleffectid = iSpellEffectID; - t.base = base; + t.base_value = base_value; t.limit = limit; - t.max = max; + t.max_value = max_value; AIspellsEffects.push_back(t); + + //we recalculate if applied from quest script. + if (apply_bonus) { + CalcBonuses(); + } } -bool IsSpellEffectInList(DBnpcspellseffects_Struct* spelleffect_list, uint16 iSpellEffectID, int32 base, int32 limit, int32 max) { +void NPC::RemoveSpellEffectFromNPCList(uint16 iSpellEffectID, bool apply_bonus) +{ + auto iter = AIspellsEffects.begin(); + while (iter != AIspellsEffects.end()) + { + if ((*iter).spelleffectid == iSpellEffectID) + { + iter = AIspellsEffects.erase(iter); + continue; + } + ++iter; + } + + if (apply_bonus) { + CalcBonuses(); + } +} + +bool NPC::HasAISpellEffect(uint16 spell_effect_id) +{ + for (const auto& spell_effect : AIspellsEffects) { + if (spell_effect.spelleffectid == spell_effect_id) { + return true; + } + } + + return false; +} + +bool IsSpellEffectInList(DBnpcspellseffects_Struct* spelleffect_list, uint16 iSpellEffectID, int32 base_value, int32 limit, int32 max_value) { for (uint32 i=0; i < spelleffect_list->numentries; i++) { - if (spelleffect_list->entries[i].spelleffectid == iSpellEffectID && spelleffect_list->entries[i].base == base - && spelleffect_list->entries[i].limit == limit && spelleffect_list->entries[i].max == max) + if (spelleffect_list->entries[i].spelleffectid == iSpellEffectID && spelleffect_list->entries[i].base_value == base_value + && spelleffect_list->entries[i].limit == limit && spelleffect_list->entries[i].max_value == max_value) return true; } return false; @@ -2811,12 +2869,90 @@ void NPC::RemoveSpellFromNPCList(uint16 spell_id) void NPC::AISpellsList(Client *c) { - if (!c) + if (!c) { return; + } - for (auto it = AIspells.begin(); it != AIspells.end(); ++it) - c->Message(Chat::White, "%s (%d): Type %d, Priority %d, Recast Delay %d, Resist Adjust %d, Min HP %d, Max HP %d", - spells[it->spellid].name, it->spellid, it->type, it->priority, it->recast_delay, it->resist_adjust, it->min_hp, it->max_hp); + if (AIspells.size() > 0) { + c->Message( + Chat::White, + fmt::format( + "{} has {} AI spells.", + GetCleanName(), + AIspells.size() + ).c_str() + ); + + int spell_slot = 1; + for (const auto& ai_spell : AIspells) { + c->Message( + Chat::White, + fmt::format( + "Spell {} | Name: {} ({}) Type: {} Mana Cost: {}", + spell_slot, + GetSpellName(ai_spell.spellid), + ai_spell.spellid, + ai_spell.type, + ai_spell.manacost + ).c_str() + ); + + c->Message( + Chat::White, + fmt::format( + "Spell {} | Priority: {} Recast Delay: {}", + spell_slot, + ai_spell.priority, + ai_spell.recast_delay + ).c_str() + ); + + if (ai_spell.time_cancast) { + c->Message( + Chat::White, + fmt::format( + "Spell {} | Time Can Cast : {}", + spell_slot, + ai_spell.time_cancast + ).c_str() + ); + } + + if (ai_spell.resist_adjust) { + c->Message( + Chat::White, + fmt::format( + "Spell {} | Resist Adjust : {}", + spell_slot, + ai_spell.resist_adjust + ).c_str() + ); + } + + if (ai_spell.min_hp || ai_spell.max_hp) { + c->Message( + Chat::White, + fmt::format( + "Spell {} | Min HP: {} Max HP: {}", + spell_slot, + ai_spell.min_hp, + ai_spell.max_hp + ).c_str() + ); + } + + spell_slot++; + } + } + else { + c->Message( + Chat::White, + fmt::format( + "{} has no AI spells.", + GetCleanName() + ).c_str() + ); + } return; } @@ -2915,7 +3051,7 @@ DBnpcspells_Struct *ZoneDatabase::GetNPCSpells(uint32 iDBSpellsID) if (row[9]) entry.resist_adjust = atoi(row[9]); else if (IsValidSpell(spell_id)) - entry.resist_adjust = spells[spell_id].ResistDiff; + entry.resist_adjust = spells[spell_id].resist_difficulty; spell_set.entries.push_back(entry); } @@ -3008,9 +3144,9 @@ DBnpcspellseffects_Struct *ZoneDatabase::GetNPCSpellsEffects(uint32 iDBSpellsEff npc_spellseffects_cache[iDBSpellsEffectsID]->entries[entryIndex].spelleffectid = spell_effect_id; npc_spellseffects_cache[iDBSpellsEffectsID]->entries[entryIndex].minlevel = atoi(row[1]); npc_spellseffects_cache[iDBSpellsEffectsID]->entries[entryIndex].maxlevel = atoi(row[2]); - npc_spellseffects_cache[iDBSpellsEffectsID]->entries[entryIndex].base = atoi(row[3]); + npc_spellseffects_cache[iDBSpellsEffectsID]->entries[entryIndex].base_value = atoi(row[3]); npc_spellseffects_cache[iDBSpellsEffectsID]->entries[entryIndex].limit = atoi(row[4]); - npc_spellseffects_cache[iDBSpellsEffectsID]->entries[entryIndex].max = atoi(row[5]); + npc_spellseffects_cache[iDBSpellsEffectsID]->entries[entryIndex].max_value = atoi(row[5]); } return npc_spellseffects_cache[iDBSpellsEffectsID]; diff --git a/zone/mob_appearance.cpp b/zone/mob_appearance.cpp index c02d27e11..112ad81c1 100644 --- a/zone/mob_appearance.cpp +++ b/zone/mob_appearance.cpp @@ -378,9 +378,7 @@ void Mob::SendArmorAppearance(Client *one_client) * The other packets work for primary/secondary. */ - Log(Logs::Detail, Logs::MobAppearance, "Mob::SendArmorAppearance [%s]", - this->GetCleanName() - ); + LogMobAppearance("[SendArmorAppearance] [{}]", GetCleanName()); if (IsPlayerRace(race)) { if (!IsClient()) { @@ -435,6 +433,9 @@ void Mob::SendWearChange(uint8 material_slot, Client *one_client) wear_change->wear_slot_id = material_slot; + // Part of a bug fix to ensure heroforge models send to other clients in zone. + queue_wearchange_slot = wear_change->hero_forge_model ? material_slot : -1; + if (!one_client) { entity_list.QueueClients(this, packet); } diff --git a/zone/mob_info.cpp b/zone/mob_info.cpp index 554d5d571..5b7011f33 100644 --- a/zone/mob_info.cpp +++ b/zone/mob_info.cpp @@ -557,7 +557,7 @@ inline std::string WriteDisplayInfoSection( * "total_to_hit" = "Total To Hit" */ if (attribute_name.find('_') != std::string::npos) { - std::vector split_string = split(attribute_name, '_'); + auto split_string = SplitString(attribute_name, '_'); std::string new_attribute_name; for (std::string &string_value : split_string) { new_attribute_name += ucfirst(string_value) + " "; diff --git a/zone/mob_movement_manager.cpp b/zone/mob_movement_manager.cpp index ecef56c3a..c323a13c2 100644 --- a/zone/mob_movement_manager.cpp +++ b/zone/mob_movement_manager.cpp @@ -63,6 +63,7 @@ public: if (!m_started) { m_started = true; + mob->turning = true; mob->SetMoving(true); if (dist > 15.0f && rotate_to_speed > 0.0 && rotate_to_speed <= 25.0) { //send basic rotation @@ -84,6 +85,7 @@ public: mob->SetHeading(to); mob->SetMoving(false); mob_movement_manager->SendCommandToClients(mob, 0.0, 0.0, 0.0, 0.0, 0, ClientRangeCloseMedium); + mob->turning = false; return true; } @@ -1370,7 +1372,9 @@ void MobMovementManager::UpdatePathBoat(Mob *who, float x, float y, float z, Mob { auto eiter = _impl->Entries.find(who); auto &ent = (*eiter); + float to = who->CalculateHeadingToTarget(x, y); + PushRotateTo(ent.second, who, to, mode); PushSwimTo(ent.second, x, y, z, mode); PushStopMoving(ent.second); } diff --git a/zone/mod_functions.cpp b/zone/mod_functions.cpp index b0b335a4e..5091da606 100644 --- a/zone/mod_functions.cpp +++ b/zone/mod_functions.cpp @@ -61,7 +61,7 @@ bool Client::mod_client_message(char* message, uint8 chan_num) { return(true); } bool Client::mod_can_increase_skill(EQ::skills::SkillType skillid, Mob* against_who) { return(false); } //chance of general skill increase, rolled against 0-99 where higher chance is better. -int16 Client::mod_increase_skill_chance(int16 chance, Mob* against_who) { return(chance); } +double Client::mod_increase_skill_chance(double chance, Mob* against_who) { return(chance); } //Max percent of health you can bind wound starting with default value for class, item, and AA bonuses int Client::mod_bindwound_percent(int max_percent, Mob* bindmob) { return(max_percent); } @@ -151,7 +151,7 @@ int32 Mob::mod_monk_special_damage(int32 ndamage, EQ::skills::SkillType skill_ty //ndamage - Backstab damage as calculated by default formulas int32 Mob::mod_backstab_damage(int32 ndamage) { return(ndamage); } -//Chance for 50+ archery bonus damage if Combat:UseArcheryBonusRoll is true. Base is Combat:ArcheryBonusChance +//Chance for 50+ archery bonus damage if Combat:UseArcheryBonusRoll is true. int Mob::mod_archery_bonus_chance(int bonuschance, const EQ::ItemInstance* RangeWeapon) { return(bonuschance); } //Archery bonus damage diff --git a/zone/mod_functions_base.cpp b/zone/mod_functions_base.cpp index 477050534..5323ca32a 100644 --- a/zone/mod_functions_base.cpp +++ b/zone/mod_functions_base.cpp @@ -152,7 +152,7 @@ int32 Mob::mod_monk_special_damage(int32 ndamage, SkillType skill_type) { return //ndamage - Backstab damage as calculated by default formulas int32 Mob::mod_backstab_damage(int32 ndamage) { return(ndamage); } -//Chance for 50+ archery bonus damage if Combat:UseArcheryBonusRoll is true. Base is Combat:ArcheryBonusChance +//Chance for 50+ archery bonus damage if Combat:UseArcheryBonusRoll is true. int Mob::mod_archery_bonus_chance(int bonuschance, const ItemInst* RangeWeapon) { return(bonuschance); } //Archery bonus damage diff --git a/zone/npc.cpp b/zone/npc.cpp index 7f7c61a58..5226efaa3 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -285,6 +285,19 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi npc_aggro = npc_type_data->npc_aggro; + AISpellVar.fail_recast = static_cast(RuleI(Spells, AI_SpellCastFinishedFailRecast)); + AISpellVar.engaged_no_sp_recast_min = static_cast(RuleI(Spells, AI_EngagedNoSpellMinRecast)); + AISpellVar.engaged_no_sp_recast_max = static_cast(RuleI(Spells, AI_EngagedNoSpellMaxRecast)); + AISpellVar.engaged_beneficial_self_chance = static_cast (RuleI(Spells, AI_EngagedBeneficialSelfChance)); + AISpellVar.engaged_beneficial_other_chance = static_cast (RuleI(Spells, AI_EngagedBeneficialOtherChance)); + AISpellVar.engaged_detrimental_chance = static_cast (RuleI(Spells, AI_EngagedDetrimentalChance)); + AISpellVar.pursue_no_sp_recast_min = static_cast(RuleI(Spells, AI_PursueNoSpellMinRecast)); + AISpellVar.pursue_no_sp_recast_max = static_cast(RuleI(Spells, AI_PursueNoSpellMaxRecast)); + AISpellVar.pursue_detrimental_chance = static_cast (RuleI(Spells, AI_PursueDetrimentalChance)); + AISpellVar.idle_no_sp_recast_min = static_cast(RuleI(Spells, AI_IdleNoSpellMinRecast)); + AISpellVar.idle_no_sp_recast_max = static_cast(RuleI(Spells, AI_IdleNoSpellMaxRecast)); + AISpellVar.idle_beneficial_chance = static_cast (RuleI(Spells, AI_IdleBeneficialChance)); + AI_Init(); AI_Start(); @@ -397,19 +410,6 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi SetMana(GetMaxMana()); - AISpellVar.fail_recast = static_cast(RuleI(Spells, AI_SpellCastFinishedFailRecast)); - AISpellVar.engaged_no_sp_recast_min = static_cast(RuleI(Spells, AI_EngagedNoSpellMinRecast)); - AISpellVar.engaged_no_sp_recast_max = static_cast(RuleI(Spells, AI_EngagedNoSpellMaxRecast)); - AISpellVar.engaged_beneficial_self_chance = static_cast (RuleI(Spells, AI_EngagedBeneficialSelfChance)); - AISpellVar.engaged_beneficial_other_chance = static_cast (RuleI(Spells, AI_EngagedBeneficialOtherChance)); - AISpellVar.engaged_detrimental_chance = static_cast (RuleI(Spells, AI_EngagedDetrimentalChance)); - AISpellVar.pursue_no_sp_recast_min = static_cast(RuleI(Spells, AI_PursueNoSpellMinRecast)); - AISpellVar.pursue_no_sp_recast_max = static_cast(RuleI(Spells, AI_PursueNoSpellMaxRecast)); - AISpellVar.pursue_detrimental_chance = static_cast (RuleI(Spells, AI_PursueDetrimentalChance)); - AISpellVar.idle_no_sp_recast_min = static_cast(RuleI(Spells, AI_IdleNoSpellMinRecast)); - AISpellVar.idle_no_sp_recast_max = static_cast(RuleI(Spells, AI_IdleNoSpellMaxRecast)); - AISpellVar.idle_beneficial_chance = static_cast (RuleI(Spells, AI_IdleBeneficialChance)); - if (GetBodyType() == BT_Animal && !RuleB(NPC, AnimalsOpenDoors)) { m_can_open_doors = false; } @@ -509,12 +509,8 @@ void NPC::SetTarget(Mob* mob) { if(mob == GetTarget()) //dont bother if they are allready our target return; - //This is not the default behavior for swarm pets, must be specified from quest functions or rules value. - if(GetSwarmInfo() && GetSwarmInfo()->target && GetTarget() && (GetTarget()->GetHP() > 0)) { - Mob *targ = entity_list.GetMob(GetSwarmInfo()->target); - if(targ != mob){ - return; - } + if (GetPetTargetLockID()) { + TryDepopTargetLockedPets(mob); } if (mob) { @@ -641,37 +637,151 @@ void NPC::ClearItemList() { SendAppearancePacket(AT_Light, GetActiveLightType()); } -void NPC::QueryLoot(Client* to) +void NPC::QueryLoot(Client* to, bool is_pet_query) { - to->Message(Chat::White, "| # Current Loot (%s) LootTableID: %i", GetName(), GetLoottableID()); - - int item_count = 0; - for (auto cur = itemlist.begin(); cur != itemlist.end(); ++cur, ++item_count) { - if (!(*cur)) { - LogError("NPC::QueryLoot() - ItemList error, null item"); - continue; - } - if (!(*cur)->item_id || !database.GetItem((*cur)->item_id)) { - LogError("NPC::QueryLoot() - Database error, invalid item"); - continue; + if (!itemlist.empty()) { + if (!is_pet_query) { + to->Message( + Chat::White, + fmt::format( + "Loot | {} ({}) ID: {} Loottable ID: {}", + GetName(), + GetID(), + GetNPCTypeID(), + GetLoottableID() + ).c_str() + ); } - EQ::SayLinkEngine linker; - linker.SetLinkType(EQ::saylink::SayLinkLootItem); - linker.SetLootData(*cur); + int item_count = 0; + for (auto current_item : itemlist) { + int item_number = (item_count + 1); + if (!current_item) { + LogError("NPC::QueryLoot() - ItemList error, null item."); + continue; + } - to->Message( - 0, - "| -- Item %i: %s ID: %u min_level: %u max_level: %u", - item_count, - linker.GenerateLink().c_str(), - (*cur)->item_id, - (*cur)->trivial_min_level, - (*cur)->trivial_max_level - ); + if (!current_item->item_id || !database.GetItem(current_item->item_id)) { + LogError("NPC::QueryLoot() - Database error, invalid item."); + continue; + } + + EQ::SayLinkEngine linker; + linker.SetLinkType(EQ::saylink::SayLinkLootItem); + linker.SetLootData(current_item); + + to->Message( + Chat::White, + fmt::format( + "Item {} | {} ({}){}", + item_number, + linker.GenerateLink().c_str(), + current_item->item_id, + ( + current_item->charges > 1 ? + fmt::format( + " Amount: {}", + current_item->charges + ) : + "" + ) + ).c_str() + ); + item_count++; + } } - to->Message(Chat::White, "| %i Platinum %i Gold %i Silver %i Copper", platinum, gold, silver, copper); + if (!is_pet_query) { + bool has_money = ( + platinum > 0 || + gold > 0 || + silver > 0 || + copper > 0 + ); + if (has_money) { + to->Message( + Chat::White, + fmt::format( + "Money | {}", + ConvertMoneyToString( + platinum, + gold, + silver, + copper + ) + ).c_str() + ); + } + } +} + +bool NPC::HasItem(uint32 item_id) { + if (!database.GetItem(item_id)) { + return false; + } + + for (auto current_item = itemlist.begin(); current_item != itemlist.end(); ++current_item) { + ServerLootItem_Struct* loot_item = *current_item; + if (!loot_item) { + LogError("NPC::HasItem() - ItemList error, null item"); + continue; + } + + if (!loot_item->item_id || !database.GetItem(loot_item->item_id)) { + LogError("NPC::HasItem() - Database error, invalid item"); + continue; + } + + if (loot_item->item_id == item_id) { + return true; + } + } + return false; +} + +uint16 NPC::CountItem(uint32 item_id) { + uint16 item_count = 0; + if (!database.GetItem(item_id)) { + return item_count; + } + + for (auto current_item = itemlist.begin(); current_item != itemlist.end(); ++current_item) { + ServerLootItem_Struct* loot_item = *current_item; + if (!loot_item) { + LogError("NPC::CountItem() - ItemList error, null item"); + continue; + } + + if (!loot_item->item_id || !database.GetItem(loot_item->item_id)) { + LogError("NPC::CountItem() - Database error, invalid item"); + continue; + } + + if (loot_item->item_id == item_id) { + item_count += loot_item->charges > 0 ? loot_item->charges : 1; + } + } + return item_count; +} + +uint32 NPC::GetItemIDBySlot(uint16 loot_slot) { + for (auto current_item = itemlist.begin(); current_item != itemlist.end(); ++current_item) { + ServerLootItem_Struct* loot_item = *current_item; + if (loot_item->lootslot == loot_slot) { + return loot_item->item_id; + } + } + return 0; +} + +uint16 NPC::GetFirstSlotByItemID(uint32 item_id) { + for (auto current_item = itemlist.begin(); current_item != itemlist.end(); ++current_item) { + ServerLootItem_Struct* loot_item = *current_item; + if (loot_item->item_id == item_id) { + return loot_item->lootslot; + } + } + return 0; } void NPC::AddCash(uint16 in_copper, uint16 in_silver, uint16 in_gold, uint16 in_platinum) { @@ -733,6 +843,10 @@ bool NPC::Process() SpellProcess(); + if (swarm_timer.Check()) { + DepopSwarmPets(); + } + if (mob_close_scan_timer.Check()) { entity_list.ScanCloseMobs(close_mobs, this, IsMoving()); } @@ -859,19 +973,8 @@ bool NPC::Process() SendHPUpdate(); } - if(HasVirus()) { - if(viral_timer.Check()) { - viral_timer_counter++; - for(int i = 0; i < MAX_SPELL_TRIGGER*2; i+=2) { - if(viral_spells[i] && spells[viral_spells[i]].viral_timer > 0) { - if(viral_timer_counter % spells[viral_spells[i]].viral_timer == 0) { - SpreadVirus(viral_spells[i], viral_spells[i+1]); - } - } - } - } - if(viral_timer_counter > 999) - viral_timer_counter = 0; + if (viral_timer.Check()) { + VirusEffectProcess(); } if(spellbonuses.GravityEffect == 1) { @@ -916,6 +1019,10 @@ bool NPC::Process() if (assist_timer.Check() && IsEngaged() && !Charmed() && !HasAssistAggro() && NPCAssistCap() < RuleI(Combat, NPCAssistCap)) { + // Some cases like flash of light used for aggro haven't set target + if (!GetTarget()) { + SetTarget(hate_list.GetEntWithMostHateOnList(this)); + } AIYellForHelp(this, GetTarget()); if (NPCAssistCap() > 0 && !assist_cap_timer.Enabled()) assist_cap_timer.Start(RuleI(Combat, NPCAssistCapTimer)); @@ -990,7 +1097,7 @@ void NPC::Depop(bool StartSpawnTimer) { bool NPC::DatabaseCastAccepted(int spell_id) { for (int i=0; i < EFFECT_COUNT; i++) { - switch(spells[spell_id].effectid[i]) { + switch(spells[spell_id].effect_id[i]) { case SE_Stamina: { if(IsEngaged() && GetHPRatio() < 100) return true; @@ -1000,7 +1107,7 @@ bool NPC::DatabaseCastAccepted(int spell_id) { } case SE_CurrentHPOnce: case SE_CurrentHP: { - if(this->GetHPRatio() < 100 && spells[spell_id].buffduration == 0) + if(this->GetHPRatio() < 100 && spells[spell_id].buff_duration == 0) return true; else return false; @@ -1033,7 +1140,7 @@ bool NPC::DatabaseCastAccepted(int spell_id) { break; } default: - if(spells[spell_id].goodEffect == 1 && !(spells[spell_id].buffduration == 0 && this->GetHPRatio() == 100) && !IsEngaged()) + if(spells[spell_id].good_effect == 1 && !(spells[spell_id].buff_duration == 0 && this->GetHPRatio() == 100) && !IsEngaged()) return true; return false; } @@ -1051,7 +1158,7 @@ bool NPC::SpawnZoneController() memset(npc_type, 0, sizeof(NPCType)); strncpy(npc_type->name, "zone_controller", 60); - npc_type->current_hp = 2000000000; + npc_type->current_hp = 2000000000; npc_type->max_hp = 2000000000; npc_type->hp_regen = 100000000; npc_type->race = 240; @@ -1095,8 +1202,7 @@ bool NPC::SpawnZoneController() return true; } -void NPC::SpawnGridNodeNPC(const glm::vec4 &position, int32 grid_number, int32 zoffset) { - +void NPC::SpawnGridNodeNPC(const glm::vec4 &position, int32 grid_id, int32 grid_number, int32 zoffset) { auto npc_type = new NPCType; memset(npc_type, 0, sizeof(NPCType)); @@ -1108,35 +1214,34 @@ void NPC::SpawnGridNodeNPC(const glm::vec4 &position, int32 grid_number, int32 z strcat(npc_type->name, "(Stacked)"); } - npc_type->current_hp = 4000000; - npc_type->max_hp = 4000000; - npc_type->race = 2254; - npc_type->gender = 2; - npc_type->class_ = 9; - npc_type->deity = 1; - npc_type->level = 200; - npc_type->npc_id = 0; - npc_type->loottable_id = 0; - npc_type->texture = 1; - npc_type->light = 1; - npc_type->size = 1; - npc_type->runspeed = 0; - npc_type->merchanttype = 1; - npc_type->bodytype = 1; - npc_type->show_name = true; - npc_type->findable = true; + npc_type->current_hp = 4000000; + npc_type->max_hp = 4000000; + npc_type->race = 2254; + npc_type->gender = 2; + npc_type->class_ = 9; + npc_type->deity = 1; + npc_type->level = 200; + npc_type->npc_id = 0; + npc_type->loottable_id = 0; + npc_type->texture = 1; + npc_type->light = 1; + npc_type->size = 1; + npc_type->runspeed = 0; + npc_type->merchanttype = 1; + npc_type->bodytype = 1; + npc_type->show_name = true; + npc_type->findable = true; + strn0cpy(npc_type->special_abilities, "24,1^35,1", 512); auto node_position = glm::vec4(position.x, position.y, position.z, position.w); - auto npc = new NPC(npc_type, nullptr, node_position, GravityBehavior::Flying); - - npc->name[strlen(npc->name)-3] = (char) NULL; - + auto npc = new NPC(npc_type, nullptr, node_position, GravityBehavior::Flying); + npc->name[strlen(npc->name) - 3] = (char) NULL; npc->GiveNPCTypeData(npc_type); - + npc->SetEntityVariable("grid_id", itoa(grid_id)); entity_list.AddNPC(npc); } -void NPC::SpawnZonePointNodeNPC(std::string name, const glm::vec4 &position) +NPC * NPC::SpawnZonePointNodeNPC(std::string name, const glm::vec4 &position) { auto npc_type = new NPCType; memset(npc_type, 0, sizeof(NPCType)); @@ -1164,6 +1269,8 @@ void NPC::SpawnZonePointNodeNPC(std::string name, const glm::vec4 &position) npc_type->show_name = true; npc_type->findable = true; + strcpy(npc_type->special_abilities, "12,1^13,1^14,1^15,1^16,1^17,1^19,1^22,1^24,1^25,1^28,1^31,1^35,1^39,1^42,1"); + auto node_position = glm::vec4(position.x, position.y, position.z, position.w); auto npc = new NPC(npc_type, nullptr, node_position, GravityBehavior::Flying); @@ -1172,14 +1279,15 @@ void NPC::SpawnZonePointNodeNPC(std::string name, const glm::vec4 &position) npc->GiveNPCTypeData(npc_type); entity_list.AddNPC(npc); + + return npc; } NPC * NPC::SpawnNodeNPC(std::string name, std::string last_name, const glm::vec4 &position) { auto npc_type = new NPCType; memset(npc_type, 0, sizeof(NPCType)); - sprintf(npc_type->name, "%s", name.c_str()); - sprintf(npc_type->lastname, "%s", last_name.c_str()); + strncpy(npc_type->name, name.c_str(), 60); npc_type->current_hp = 4000000; npc_type->max_hp = 4000000; @@ -1201,9 +1309,13 @@ NPC * NPC::SpawnNodeNPC(std::string name, std::string last_name, const glm::vec4 npc_type->findable = true; npc_type->runspeed = 1.25; + strcpy(npc_type->special_abilities, "12,1^13,1^14,1^15,1^16,1^17,1^19,1^22,1^24,1^25,1^28,1^31,1^35,1^39,1^42,1"); + auto node_position = glm::vec4(position.x, position.y, position.z, position.w); auto npc = new NPC(npc_type, nullptr, node_position, GravityBehavior::Flying); + npc->name[strlen(npc->name) - 3] = (char) NULL; + npc->GiveNPCTypeData(npc_type); entity_list.AddNPC(npc, true, true); @@ -1620,25 +1732,25 @@ uint32 ZoneDatabase::AddNPCTypes(const char *zone, uint32 zone_version, Client * uint32 ZoneDatabase::NPCSpawnDB(uint8 command, const char* zone, uint32 zone_version, Client *c, NPC* spawn, uint32 extra) { switch (command) { - case 0: { // Create a new NPC and add all spawn related data + case NPCSpawnTypes::CreateNewSpawn: { // Create a new NPC and add all spawn related data return CreateNewNPCCommand(zone, zone_version, c, spawn, extra); } - case 1:{ // Add new spawn group and spawn point for an existing NPC Type ID + case NPCSpawnTypes::AddNewSpawngroup: { // Add new spawn group and spawn point for an existing NPC Type ID return AddNewNPCSpawnGroupCommand(zone, zone_version, c, spawn, extra); } - case 2: { // Update npc_type appearance and other data on targeted spawn + case NPCSpawnTypes::UpdateAppearance: { // Update npc_type appearance and other data on targeted spawn return UpdateNPCTypeAppearance(c, spawn); } - case 3: { // delete spawn from spawning, but leave in npc_types table + case NPCSpawnTypes::RemoveSpawn: { // delete spawn from spawning, but leave in npc_types table return DeleteSpawnLeaveInNPCTypeTable(zone, c, spawn); } - case 4: { //delete spawn from DB (including npc_type) + case NPCSpawnTypes::DeleteSpawn: { //delete spawn from DB (including npc_type) return DeleteSpawnRemoveFromNPCTypeTable(zone, zone_version, c, spawn); } - case 5: { // add a spawn from spawngroup + case NPCSpawnTypes::AddSpawnFromSpawngroup: { // add a spawn from spawngroup return AddSpawnFromSpawnGroup(zone, zone_version, c, spawn, extra); } - case 6: { // add npc_type + case NPCSpawnTypes::CreateNewNPC: { // add npc_type return AddNPCTypes(zone, zone_version, c, spawn, extra); } } @@ -2261,6 +2373,10 @@ void NPC::PetOnSpawn(NewSpawn_Struct* ns) strn0cpy(ns->spawn.lastName, tmp_lastname.c_str(), sizeof(ns->spawn.lastName)); } } + + if (swarmOwner->IsNPC()) { + SetPetOwnerNPC(true); + } } else if(GetOwnerID()) { @@ -2276,6 +2392,13 @@ void NPC::PetOnSpawn(NewSpawn_Struct* ns) if (tmp_lastname.size() < sizeof(ns->spawn.lastName)) strn0cpy(ns->spawn.lastName, tmp_lastname.c_str(), sizeof(ns->spawn.lastName)); } + else + { + if (entity_list.GetNPCByID(GetOwnerID())) + { + SetPetOwnerNPC(true); + } + } } } else @@ -2797,7 +2920,7 @@ FACTION_VALUE NPC::GetReverseFactionCon(Mob* iOther) { return GetSpecialFactionCon(iOther); if (primaryFaction == 0) - return FACTION_INDIFFERENT; + return FACTION_INDIFFERENTLY; //if we are a pet, use our owner's faction stuff Mob *own = GetOwner(); @@ -2807,7 +2930,7 @@ FACTION_VALUE NPC::GetReverseFactionCon(Mob* iOther) { //make sure iOther is an npc //also, if we dont have a faction, then they arnt gunna think anything of us either if(!iOther->IsNPC() || GetPrimaryFaction() == 0) - return(FACTION_INDIFFERENT); + return(FACTION_INDIFFERENTLY); //if we get here, iOther is an NPC too @@ -2831,7 +2954,7 @@ FACTION_VALUE NPC::CheckNPCFactionAlly(int32 other_faction) { else if (fac->npc_value < 0) return FACTION_SCOWLS; else - return FACTION_INDIFFERENT; + return FACTION_INDIFFERENTLY; } } @@ -2843,7 +2966,7 @@ FACTION_VALUE NPC::CheckNPCFactionAlly(int32 other_faction) { if (GetPrimaryFaction() == other_faction) return FACTION_ALLY; else - return FACTION_INDIFFERENT; + return FACTION_INDIFFERENTLY; } bool NPC::IsFactionListAlly(uint32 other_faction) { @@ -2959,37 +3082,13 @@ void NPC::ClearLastName() void NPC::DepopSwarmPets() { - if (GetSwarmInfo()) { if (GetSwarmInfo()->duration->Check(false)){ Mob* owner = entity_list.GetMobID(GetSwarmInfo()->owner_id); - if (owner) + if (owner) { owner->SetTempPetCount(owner->GetTempPetCount() - 1); - - Depop(); - return; - } - - //This is only used for optional quest or rule derived behavior now if you force a temp pet on a specific target. - if (GetSwarmInfo()->target) { - Mob *targMob = entity_list.GetMob(GetSwarmInfo()->target); - if(!targMob || (targMob && targMob->IsCorpse())){ - Mob* owner = entity_list.GetMobID(GetSwarmInfo()->owner_id); - if (owner) - owner->SetTempPetCount(owner->GetTempPetCount() - 1); - - Depop(); - return; } - } - } - - if (IsPet() && GetPetType() == petTargetLock && GetPetTargetLockID()){ - - Mob *targMob = entity_list.GetMob(GetPetTargetLockID()); - - if(!targMob || (targMob && targMob->IsCorpse())){ - Kill(); + Depop(); return; } } @@ -3217,6 +3316,11 @@ bool NPC::AICheckCloseBeneficialSpells( */ void NPC::AIYellForHelp(Mob *sender, Mob *attacker) { + LogAIYellForHelp("Mob[{}] Target[{}]", + (sender == nullptr ? "NULL MOB" : GetCleanName()), + (attacker == nullptr ? "NULL TARGET" : attacker->GetCleanName()) + ); + if (!sender || !attacker) { return; } @@ -3225,11 +3329,14 @@ void NPC::AIYellForHelp(Mob *sender, Mob *attacker) * If we dont have a faction set, we're gonna be indiff to everybody */ if (sender->GetPrimaryFaction() == 0) { + LogAIYellForHelp("No Primary Faction"); return; } - if (sender->HasAssistAggro()) + if (sender->HasAssistAggro()) { + LogAIYellForHelp("I have assist aggro"); return; + } LogAIYellForHelp( "NPC [{}] ID [{}] is starting to scan", @@ -3293,18 +3400,17 @@ void NPC::AIYellForHelp(Mob *sender, Mob *attacker) * if they are in range, make sure we are not green... * then jump in if they are our friend */ - if (mob->GetLevel() >= 50 || attacker->GetLevelCon(mob->GetLevel()) != CON_GRAY) { - bool use_primary_faction = false; + if (mob->GetLevel() >= 50 || mob->AlwaysAggro() || attacker->GetLevelCon(mob->GetLevel()) != CON_GRAY) { if (mob->GetPrimaryFaction() == sender->CastToNPC()->GetPrimaryFaction()) { const NPCFactionList *cf = content_db.GetNPCFactionEntry(mob->CastToNPC()->GetNPCFactionID()); if (cf) { - if (cf->assistprimaryfaction != 0) { - use_primary_faction = true; + if (cf->assistprimaryfaction == 0) { + continue; //Same faction and ignore primary assist } } } - if (use_primary_faction || sender->GetReverseFactionCon(mob) <= FACTION_AMIABLE) { + if (sender->GetReverseFactionCon(mob) <= FACTION_AMIABLY) { //attacking someone on same faction, or a friend //Father Nitwit: make sure we can see them. if (mob->CheckLosFN(sender)) { @@ -3357,3 +3463,53 @@ void NPC::ScaleNPC(uint8 npc_level) { npc_scale_manager->ResetNPCScaling(this); npc_scale_manager->ScaleNPC(this); } + +bool NPC::IsGuard() +{ + switch (GetRace()) { + case RT_GUARD: + if (GetTexture() == 1 || GetTexture() == 2) + return true; + break; + case RT_IKSAR_2: + if (GetTexture() == 1) + return true; + break; + case RT_GUARD_2: + case RT_GUARD_3: + case RT_GUARD_4: + case RT_HUMAN_3: + case RT_HALFLING_2: + case RT_ERUDITE_2: + case RT_BARBARIAN_2: + case RT_DARK_ELF_2: + case RT_TROLL_2: + case OGGOK_CITIZEN: + case RT_DWARF_2: + return true; + default: + break; + } + if (GetPrimaryFaction() == DB_FACTION_GEM_CHOPPERS || GetPrimaryFaction() == DB_FACTION_HERETICS || GetPrimaryFaction() == DB_FACTION_KING_AKANON) { //these 3 factions of guards use player races instead of their own races so we must define them by faction. + return true; + } + return false; +} + +std::vector NPC::GetLootList() { + std::vector npc_items; + for (auto current_item = itemlist.begin(); current_item != itemlist.end(); ++current_item) { + ServerLootItem_Struct* loot_item = *current_item; + if (!loot_item) { + LogError("NPC::GetLootList() - ItemList error, null item"); + continue; + } + + if (std::find(npc_items.begin(), npc_items.end(), loot_item->item_id) != npc_items.end()) { + continue; + } + + npc_items.push_back(loot_item->item_id); + } + return npc_items; +} diff --git a/zone/npc.h b/zone/npc.h index de64e6b59..aa9089e4f 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -70,9 +70,9 @@ struct AISpells_Struct { struct AISpellsEffects_Struct { uint16 spelleffectid; - int32 base; + int32 base_value; int32 limit; - int32 max; + int32 max_value; }; struct AISpellsVar_Struct { @@ -114,8 +114,8 @@ public: virtual ~NPC(); static NPC *SpawnNodeNPC(std::string name, std::string last_name, const glm::vec4 &position); - static void SpawnGridNodeNPC(const glm::vec4 &position, int32 grid_number, int32 zoffset); - static void SpawnZonePointNodeNPC(std::string name, const glm::vec4 &position); + static void SpawnGridNodeNPC(const glm::vec4 &position, int32 grid_id, int32 grid_number, int32 zoffset); + static NPC * SpawnZonePointNodeNPC(std::string name, const glm::vec4 &position); //abstract virtual function implementations requird by base abstract class virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, EQ::skills::SkillType attack_skill); @@ -175,11 +175,12 @@ public: bool DatabaseCastAccepted(int spell_id); bool IsFactionListAlly(uint32 other_faction); + bool IsGuard(); FACTION_VALUE CheckNPCFactionAlly(int32 other_faction); virtual FACTION_VALUE GetReverseFactionCon(Mob* iOther); - void GoToBind(uint8 bindnum = 0) { GMMove(m_SpawnPoint.x, m_SpawnPoint.y, m_SpawnPoint.z, m_SpawnPoint.w); } - void Gate(uint8 bindnum = 0); + void GoToBind(uint8 bind_number = 0) { GMMove(m_SpawnPoint.x, m_SpawnPoint.y, m_SpawnPoint.z, m_SpawnPoint.w); } + void Gate(uint8 bind_number = 0); void GetPetState(SpellBuff_Struct *buffs, uint32 *items, char *name); void SetPetState(SpellBuff_Struct *buffs, uint32 *items); @@ -197,11 +198,17 @@ public: void RemoveItem(uint32 item_id, uint16 quantity = 0, uint16 slot = 0); void CheckTrivialMinMaxLevelDrop(Mob *killer); void ClearItemList(); + inline const ItemList &GetItemList() { return itemlist; } ServerLootItem_Struct* GetItem(int slot_id); void AddCash(uint16 in_copper, uint16 in_silver, uint16 in_gold, uint16 in_platinum); void AddCash(); void RemoveCash(); - void QueryLoot(Client* to); + void QueryLoot(Client* to, bool is_pet_query = false); + bool HasItem(uint32 item_id); + uint16 CountItem(uint32 item_id); + uint32 GetItemIDBySlot(uint16 loot_slot); + uint16 GetFirstSlotByItemID(uint32 item_id); + std::vector GetLootList(); uint32 CountLoot(); inline uint32 GetLoottableID() const { return loottable_id; } virtual void UpdateEquipmentLight(); @@ -253,6 +260,7 @@ public: uint32 GetSwarmTarget(); void SetSwarmTarget(int target_id = 0); void DepopSwarmPets(); + void TryDepopTargetLockedPets(Mob* current_target); void PetOnSpawn(NewSpawn_Struct* ns); void SignalNPC(int _signal_id); @@ -275,6 +283,8 @@ public: content_db.GetFactionIdsForNPC(npc_faction_id, &faction_list, &primary_faction); } + int32 GetFocusEffect(focusType type, uint16 spell_id, Mob* caster = nullptr); + glm::vec4 m_SpawnPoint; uint32 GetMaxDMG() const {return max_dmg;} @@ -293,6 +303,7 @@ public: void PickPocket(Client* thief); void Disarm(Client* client, int chance); void StartSwarmTimer(uint32 duration) { swarm_timer.Start(duration); } + void DisableSwarmTimer() { swarm_timer.Disable(); } void AddLootDrop( const EQ::ItemData *item2, @@ -307,7 +318,7 @@ public: uint32 aug6 = 0 ); - bool MeetsLootDropLevelRequirements(LootDropEntries_Struct loot_drop); + bool MeetsLootDropLevelRequirements(LootDropEntries_Struct loot_drop, bool verbose=false); virtual void DoClassAttacks(Mob *target); void CheckSignal(); @@ -319,7 +330,7 @@ public: //waypoint crap int GetMaxWp() const { return max_wp; } - void DisplayWaypointInfo(Client *to); + void DisplayWaypointInfo(Client *client); void CalculateNewWaypoint(); void AssignWaypoints(int32 grid_id, int start_wp = 0); void SetWaypointPause(); @@ -438,8 +449,10 @@ public: uint32 GetAdventureTemplate() const { return adventure_template_id; } void AddSpellToNPCList(int16 iPriority, uint16 iSpellID, uint32 iType, int16 iManaCost, int32 iRecastDelay, int16 iResistAdjust, int8 min_hp, int8 max_hp); - void AddSpellEffectToNPCList(uint16 iSpellEffectID, int32 base, int32 limit, int32 max); + void AddSpellEffectToNPCList(uint16 iSpellEffectID, int32 base_value, int32 limit, int32 max_value, bool apply_bonus = false); void RemoveSpellFromNPCList(uint16 spell_id); + void RemoveSpellEffectFromNPCList(uint16 iSpellEffectID, bool apply_bonus = false); + bool HasAISpellEffect(uint16 spell_effect_id); Timer *GetRefaceTimer() const { return reface_timer; } const uint32 GetAltCurrencyType() const { return NPCTypedata->alt_currency_type; } @@ -490,6 +503,25 @@ public: uint32 GetRoamboxDelay() const; uint32 GetRoamboxMinDelay() const; + inline uint8 GetArmTexture() { return armtexture; } + inline uint8 GetBracerTexture() { return bracertexture; } + inline uint8 GetHandTexture() { return handtexture; } + inline uint8 GetFeetTexture() { return feettexture; } + inline uint8 GetLegTexture() { return legtexture; } + + inline int GetCharmedAccuracy() { return charm_accuracy_rating; } + inline int GetCharmedArmorClass() { return charm_ac; } + inline int GetCharmedAttack() { return charm_atk; } + inline int GetCharmedAttackDelay() { return charm_attack_delay; } + inline int GetCharmedAvoidance() { return charm_avoidance_rating; } + inline int GetCharmedMaxDamage() { return charm_max_dmg; } + inline int GetCharmedMinDamage() { return charm_min_dmg; } + + inline bool GetAlwaysAggro() { return always_aggro; } + inline bool GetNPCAggro() { return npc_aggro; } + inline bool GetIgnoreDespawn() { return ignore_despawn; } + inline bool GetSkipGlobalLoot() { return skip_global_loot; } + std::unique_ptr AIautocastspell_timer; virtual int GetStuckBehavior() const { return NPCTypedata_ours ? NPCTypedata_ours->stuck_behavior : NPCTypedata->stuck_behavior; } @@ -542,7 +574,7 @@ protected: virtual bool AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes, bool bInnates = false); virtual bool AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore = 0); AISpellsVar_Struct AISpellVar; - int16 GetFocusEffect(focusType type, uint16 spell_id); + int32 GetFocusEffect(focusType type, uint16 spell_id); uint16 innate_proc_spell_id; uint32 npc_spells_effects_id; diff --git a/zone/npc_scale_manager.cpp b/zone/npc_scale_manager.cpp index 158ac299b..086abf75d 100644 --- a/zone/npc_scale_manager.cpp +++ b/zone/npc_scale_manager.cpp @@ -20,9 +20,11 @@ #include "npc_scale_manager.h" #include "../common/string_util.h" +#include "../common/repositories/npc_scale_global_base_repository.h" +#include "../common/repositories/npc_types_repository.h" /** - * @param npc + * @param npc */ void NpcScaleManager::ScaleNPC(NPC *npc) { @@ -164,9 +166,10 @@ void NpcScaleManager::ScaleNPC(NPC *npc) } } -void NpcScaleManager::ResetNPCScaling(NPC *npc) { +void NpcScaleManager::ResetNPCScaling(NPC *npc) +{ for (const auto &scaling_stat : scaling_stats) { - std::string stat_name = fmt::format("modify_stat_{}", scaling_stat); + std::string stat_name = fmt::format("modify_stat_{}", scaling_stat); std::string reset_value = "0"; if (npc->EntityVariableExists(stat_name.c_str())) { npc->ModifyNPCStat(scaling_stat.c_str(), reset_value.c_str()); @@ -176,72 +179,39 @@ void NpcScaleManager::ResetNPCScaling(NPC *npc) { bool NpcScaleManager::LoadScaleData() { - auto results = content_db.QueryDatabase( - "SELECT " - "type," - "level," - "ac," - "hp," - "accuracy," - "slow_mitigation," - "attack," - "strength," - "stamina," - "dexterity," - "agility," - "intelligence," - "wisdom," - "charisma," - "magic_resist," - "cold_resist," - "fire_resist," - "poison_resist," - "disease_resist," - "corruption_resist," - "physical_resist," - "min_dmg," - "max_dmg," - "hp_regen_rate," - "attack_delay," - "spell_scale," - "heal_scale," - "special_abilities" - " FROM `npc_scale_global_base`" - ); - - for (auto row = results.begin(); row != results.end(); ++row) { + for (auto &s: NpcScaleGlobalBaseRepository::All(content_db)) { global_npc_scale scale_data; - scale_data.type = atoi(row[0]); - scale_data.level = atoi(row[1]); - scale_data.ac = atoi(row[2]); - scale_data.hp = atoi(row[3]); - scale_data.accuracy = atoi(row[4]); - scale_data.slow_mitigation = atoi(row[5]); - scale_data.attack = atoi(row[6]); - scale_data.strength = atoi(row[7]); - scale_data.stamina = atoi(row[8]); - scale_data.dexterity = atoi(row[9]); - scale_data.agility = atoi(row[10]); - scale_data.intelligence = atoi(row[11]); - scale_data.wisdom = atoi(row[12]); - scale_data.charisma = atoi(row[13]); - scale_data.magic_resist = atoi(row[14]); - scale_data.cold_resist = atoi(row[15]); - scale_data.fire_resist = atoi(row[16]); - scale_data.poison_resist = atoi(row[17]); - scale_data.disease_resist = atoi(row[18]); - scale_data.corruption_resist = atoi(row[19]); - scale_data.physical_resist = atoi(row[20]); - scale_data.min_dmg = atoi(row[21]); - scale_data.max_dmg = atoi(row[22]); - scale_data.hp_regen_rate = atoi(row[23]); - scale_data.attack_delay = atoi(row[24]); - scale_data.spell_scale = atoi(row[25]); - scale_data.heal_scale = atoi(row[26]); + scale_data.type = s.type; + scale_data.level = s.level; + scale_data.ac = s.ac; + scale_data.hp = s.hp; + scale_data.accuracy = s.accuracy; + scale_data.slow_mitigation = s.slow_mitigation; + scale_data.attack = s.attack; + scale_data.strength = s.strength; + scale_data.stamina = s.stamina; + scale_data.dexterity = s.dexterity; + scale_data.agility = s.agility; + scale_data.intelligence = s.intelligence; + scale_data.wisdom = s.wisdom; + scale_data.charisma = s.charisma; + scale_data.magic_resist = s.magic_resist; + scale_data.cold_resist = s.cold_resist; + scale_data.fire_resist = s.fire_resist; + scale_data.poison_resist = s.poison_resist; + scale_data.disease_resist = s.disease_resist; + scale_data.corruption_resist = s.corruption_resist; + scale_data.physical_resist = s.physical_resist; + scale_data.min_dmg = s.min_dmg; + scale_data.max_dmg = s.max_dmg; + scale_data.hp_regen_rate = s.hp_regen_rate; + scale_data.attack_delay = s.attack_delay; + scale_data.spell_scale = s.spell_scale; + scale_data.heal_scale = s.heal_scale; - if (row[25]) { - scale_data.special_abilities = row[27]; + if (!s.special_abilities.empty()) { + scale_data.special_abilities = s.special_abilities; } npc_global_base_scaling_data.insert( @@ -489,9 +459,9 @@ bool NpcScaleManager::ApplyGlobalBaseScalingToNPCStatically(NPC *&npc) int8 npc_type = GetNPCScalingType(npc); int npc_level = npc->GetLevel(); - global_npc_scale scale_data = GetGlobalScaleDataForTypeLevel(npc_type, npc_level); + global_npc_scale g = GetGlobalScaleDataForTypeLevel(npc_type, npc_level); - if (!scale_data.level) { + if (!g.level) { LogNPCScaling( "NpcScaleManager::ApplyGlobalBaseScalingToNPCStatically NPC: [{}] - scaling data not found for type: [{}] level: [{}]", npc->GetCleanName(), @@ -502,67 +472,39 @@ bool NpcScaleManager::ApplyGlobalBaseScalingToNPCStatically(NPC *&npc) return false; } - std::string query = StringFormat( - "UPDATE `npc_types` SET " - "AC = %i, " - "hp = %i, " - "Accuracy = %i, " - "slow_mitigation = %i, " - "ATK = %i, " - "STR = %i, " - "STA = %i, " - "DEX = %i, " - "AGI = %i, " - "_INT = %i, " - "WIS = %i, " - "CHA = %i, " - "MR = %i, " - "CR = %i, " - "FR = %i, " - "PR = %i, " - "DR = %i, " - "Corrup = %i, " - "PhR = %i, " - "mindmg = %i, " - "maxdmg = %i, " - "hp_regen_rate = %i, " - "attack_delay = %i, " - "spellscale = %i, " - "healscale = %i, " - "special_abilities = '%s' " - "WHERE `id` = %i", - scale_data.ac, - scale_data.hp, - scale_data.accuracy, - scale_data.slow_mitigation, - scale_data.attack, - scale_data.strength, - scale_data.stamina, - scale_data.dexterity, - scale_data.agility, - scale_data.intelligence, - scale_data.wisdom, - scale_data.charisma, - scale_data.magic_resist, - scale_data.cold_resist, - scale_data.fire_resist, - scale_data.poison_resist, - scale_data.disease_resist, - scale_data.corruption_resist, - scale_data.physical_resist, - scale_data.min_dmg, - scale_data.max_dmg, - scale_data.hp_regen_rate, - scale_data.attack_delay, - scale_data.spell_scale, - scale_data.heal_scale, - EscapeString(scale_data.special_abilities).c_str(), - npc->GetNPCTypeID() - ); + auto n = NpcTypesRepository::FindOne(content_db, (int) npc->GetNPCTypeID()); + if (n.id > 0) { + n.AC = g.ac; + n.hp = g.hp; + n.Accuracy = g.accuracy; + n.slow_mitigation = g.slow_mitigation; + n.ATK = g.attack; + n.STR = g.strength; + n.STA = g.stamina; + n.DEX = g.dexterity; + n.AGI = g.agility; + n._INT = g.intelligence; + n.WIS = g.wisdom; + n.CHA = g.charisma; + n.MR = g.magic_resist; + n.CR = g.cold_resist; + n.FR = g.fire_resist; + n.PR = g.poison_resist; + n.DR = g.disease_resist; + n.Corrup = g.corruption_resist; + n.PhR = g.physical_resist; + n.mindmg = g.min_dmg; + n.maxdmg = g.max_dmg; + n.hp_regen_rate = g.hp_regen_rate; + n.attack_delay = g.attack_delay; + n.spellscale = (float) g.spell_scale; + n.healscale = (float) g.heal_scale; + n.special_abilities = g.special_abilities; - auto results = content_db.QueryDatabase(query); + return NpcTypesRepository::UpdateOne(content_db, n); + } - return results.Success(); + return false; } /** @@ -575,9 +517,9 @@ bool NpcScaleManager::ApplyGlobalBaseScalingToNPCDynamically(NPC *&npc) int8 npc_type = GetNPCScalingType(npc); int npc_level = npc->GetLevel(); - global_npc_scale scale_data = GetGlobalScaleDataForTypeLevel(npc_type, npc_level); + global_npc_scale d = GetGlobalScaleDataForTypeLevel(npc_type, npc_level); - if (!scale_data.level) { + if (!d.level) { LogNPCScaling( "NpcScaleManager::ApplyGlobalBaseScalingToNPCDynamically NPC: [{}] - scaling data not found for type: [{}] level: [{}]", npc->GetCleanName(), @@ -588,39 +530,37 @@ bool NpcScaleManager::ApplyGlobalBaseScalingToNPCDynamically(NPC *&npc) return false; } - std::string query = StringFormat( - "UPDATE `npc_types` SET " - "AC = 0, " - "hp = 0, " - "Accuracy = 0, " - "slow_mitigation = 0, " - "ATK = 0, " - "STR = 0, " - "STA = 0, " - "DEX = 0, " - "AGI = 0, " - "_INT = 0, " - "WIS = 0, " - "CHA = 0, " - "MR = 0, " - "CR = 0, " - "FR = 0, " - "PR = 0, " - "DR = 0, " - "Corrup = 0, " - "PhR = 0, " - "mindmg = 0, " - "maxdmg = 0, " - "hp_regen_rate = 0, " - "attack_delay = 0, " - "spellscale = 0, " - "healscale = 0, " - "special_abilities = '' " - "WHERE `id` = %i", - npc->GetNPCTypeID() - ); + auto n = NpcTypesRepository::FindOne(content_db, (int) npc->GetNPCTypeID()); + if (n.id > 0) { + n.AC = 0; + n.hp = 0; + n.Accuracy = 0; + n.slow_mitigation = 0; + n.ATK = 0; + n.STR = 0; + n.STA = 0; + n.DEX = 0; + n.AGI = 0; + n._INT = 0; + n.WIS = 0; + n.CHA = 0; + n.MR = 0; + n.CR = 0; + n.FR = 0; + n.PR = 0; + n.DR = 0; + n.Corrup = 0; + n.PhR = 0; + n.mindmg = 0; + n.maxdmg = 0; + n.hp_regen_rate = 0; + n.attack_delay = 0; + n.spellscale = 0; + n.healscale = 0; + n.special_abilities = ""; - auto results = content_db.QueryDatabase(query); + return NpcTypesRepository::UpdateOne(content_db, n); + } - return results.Success(); + return false; } diff --git a/zone/object.cpp b/zone/object.cpp index 88038bbe8..30222c404 100644 --- a/zone/object.cpp +++ b/zone/object.cpp @@ -509,12 +509,10 @@ bool Object::HandleClick(Client* sender, const ClickObject_Struct* click_object) m_inst->SetRecastTimestamp( database.GetItemRecastTimestamp(sender->CharacterID(), item->RecastType)); - char buf[10]; - snprintf(buf, 9, "%u", item->ID); - buf[9] = '\0'; + std::string export_string = fmt::format("{}", item->ID); std::vector args; args.push_back(m_inst); - if(parse->EventPlayer(EVENT_PLAYER_PICKUP, sender, buf, this->GetID(), &args)) + if(parse->EventPlayer(EVENT_PLAYER_PICKUP, sender, export_string, this->GetID(), &args)) { auto outapp = new EQApplicationPacket(OP_ClickObject, sizeof(ClickObject_Struct)); memcpy(outapp->pBuffer, click_object, sizeof(ClickObject_Struct)); diff --git a/zone/pathing.cpp b/zone/pathing.cpp index 1513cd5d6..87f8977f7 100644 --- a/zone/pathing.cpp +++ b/zone/pathing.cpp @@ -49,7 +49,7 @@ void Client::SendPathPacket(const std::vector &points) { .Then([this](const EQ::Any &result) { auto points = EQ::any_cast>(result); if (points.size() < 2) { - if (Admin() > 10) { + if (Admin() > AccountStatus::Steward) { Message(Chat::System, "Too few points"); } @@ -59,7 +59,7 @@ void Client::SendPathPacket(const std::vector &points) { } if (points.size() > 36) { - if (Admin() > 10) { + if (Admin() > AccountStatus::Steward) { Message(Chat::System, "Too many points %u", points.size()); } @@ -68,7 +68,7 @@ void Client::SendPathPacket(const std::vector &points) { return; } - if (Admin() > 10) { + if (Admin() > AccountStatus::Steward) { Message(Chat::System, "Total points %u", points.size()); } diff --git a/zone/perl_bot.cpp b/zone/perl_bot.cpp new file mode 100644 index 000000000..cb40bf013 --- /dev/null +++ b/zone/perl_bot.cpp @@ -0,0 +1,70 @@ +#ifdef BOTS +#include "../common/features.h" +#ifdef EMBPERL_XS_CLASSES +#include "../common/global_define.h" +#include "embperl.h" + +#ifdef seed +#undef seed +#endif + +#include "bot.h" + +#ifdef THIS +#undef THIS +#endif + +#define VALIDATE_THIS_IS_BOT \ + do { \ + if (sv_derived_from(ST(0), "Bot")) { \ + IV tmp = SvIV((SV*)SvRV(ST(0))); \ + THIS = INT2PTR(Bot*, tmp); \ + } else { \ + Perl_croak(aTHX_ "THIS is not of type Bot"); \ + } \ + if (THIS == nullptr) { \ + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); \ + } \ + } while (0); + +XS(XS_Bot_GetOwner); +XS(XS_Bot_GetOwner) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Bot::GetOwner(THIS)"); // @categories Script Utility, Bot + { + Bot* THIS; + Mob* bot_owner; + VALIDATE_THIS_IS_BOT; + bot_owner = THIS->GetBotOwner(); + ST(0) = sv_newmortal(); + sv_setref_pv(ST(0), "Mob", (void*)bot_owner); + } + XSRETURN(1); +} + +#ifdef __cplusplus +extern "C" +#endif + +XS(boot_Bot); +XS(boot_Bot) +{ + dXSARGS; + char file[256]; + strncpy(file, __FILE__, 256); + file[255] = 0; + + if (items != 1) + fprintf(stderr, "boot_Bot does not take any arguments."); + + char buf[128]; + + XS_VERSION_BOOTCHECK; + newXSproto(strcpy(buf, "GetOwner"), XS_Bot_GetOwner, file, "$"); + XSRETURN_YES; +} + +#endif //EMBPERL_XS_CLASSES +#endif //BOTS \ No newline at end of file diff --git a/zone/perl_client.cpp b/zone/perl_client.cpp index 04e93beae..544b7c50b 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -1,30 +1,3 @@ -/* -* This file was generated automatically by xsubpp version 1.9508 from the -* contents of tmp. Do not edit this file, edit tmp instead. -* -* ANY CHANGES MADE HERE WILL BE LOST! -* -*/ - - -/* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2004 EQEMu Development Team (http://eqemulator.net) - - 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 "../common/features.h" #ifdef EMBPERL_XS_CLASSES @@ -39,6 +12,7 @@ #include "client.h" #include "expedition.h" #include "titles.h" +#include "dialogue_window.h" #ifdef THIS /* this macro seems to leak out on some systems */ #undef THIS @@ -446,24 +420,6 @@ XS(XS_Client_GetLanguageSkill) { XSRETURN(1); } -XS(XS_Client_GetLastName); /* prototype to pass -Wmissing-prototypes */ -XS(XS_Client_GetLastName) { - dXSARGS; - if (items != 1) - Perl_croak(aTHX_ "Usage: Client::GetLastName(THIS)"); // @categories Account and Character - { - Client *THIS; - Const_char *RETVAL; - dXSTARG; - VALIDATE_THIS_IS_CLIENT; - RETVAL = THIS->GetLastName(); - sv_setpv(TARG, RETVAL); - XSprePUSH; - PUSHTARG; - } - XSRETURN(1); -} - XS(XS_Client_GetLDoNPointsTheme); /* prototype to pass -Wmissing-prototypes */ XS(XS_Client_GetLDoNPointsTheme) { dXSARGS; @@ -690,15 +646,15 @@ XS(XS_Client_UpdateLDoNPoints); /* prototype to pass -Wmissing-prototypes */ XS(XS_Client_UpdateLDoNPoints) { dXSARGS; if (items != 3) - Perl_croak(aTHX_ "Usage: Client::UpdateLDoNPoints(THIS, int32 points, uint32 theme)"); // @categories Currency and Points + Perl_croak(aTHX_ "Usage: Client::UpdateLDoNPoints(THIS, uint32 theme_id, int points)"); // @categories Currency and Points { Client *THIS; - bool RETVAL; - int32 points = (int32) SvIV(ST(1)); - uint32 theme = (uint32) SvUV(ST(2)); + bool RETVAL; + uint32 theme_id = (uint32) SvUV(ST(1)); + int points = (int) SvIV(ST(2)); VALIDATE_THIS_IS_CLIENT; - RETVAL = THIS->UpdateLDoNPoints(points, theme); - ST(0) = boolSV(RETVAL); + RETVAL = THIS->UpdateLDoNPoints(theme_id, points); + ST(0) = boolSV(RETVAL); sv_2mortal(ST(0)); } XSRETURN(1); @@ -771,47 +727,28 @@ XS(XS_Client_SetEXP) { XS(XS_Client_SetBindPoint); /* prototype to pass -Wmissing-prototypes */ XS(XS_Client_SetBindPoint) { dXSARGS; - if (items < 1 || items > 6) - Perl_croak(aTHX_ "Usage: Client::SetBindPoint(THIS, int to_zone = -1, int to_instance = 0, float new_x = 0.0f, float new_y = 0.0f, float new_z = 0.0f)"); // @categories Account and Character, Stats and Attributes + if (items < 1 || items > 7) + Perl_croak(aTHX_ "Usage: Client::SetBindPoint(THIS, [int to_zone = -1, int to_instance = 0, float new_x = 0.0f, float new_y = 0.0f, float new_z = 0.0f, float new_heading = 0.0f])"); // @categories Account and Character, Stats and Attributes { Client *THIS; - int to_zone; - int to_instance; - float new_x; - float new_y; - float new_z; + int to_zone = -1; + int to_instance = 0; + float new_x = 0.0f, new_y = 0.0f, new_z = 0.0f, new_heading = 0.0f; VALIDATE_THIS_IS_CLIENT; - if (items < 2) - to_zone = -1; - else { + if (items > 1) to_zone = (int) SvIV(ST(1)); - } - - if (items < 3) - to_instance = 0; - else { + if (items > 2) to_instance = (int) SvIV(ST(2)); - } - - if (items < 4) - new_x = 0.0f; - else { + if (items > 3) new_x = (float) SvNV(ST(3)); - } - - if (items < 5) - new_y = 0.0f; - else { + if (items > 4) new_y = (float) SvNV(ST(4)); - } - - if (items < 6) - new_z = 0.0f; - else { + if (items > 5) new_z = (float) SvNV(ST(5)); - } + if (items > 6) + new_heading = (float) SvNV(ST(6)); - THIS->SetBindPoint(0, to_zone, to_instance, glm::vec3(new_x, new_y, new_z)); + THIS->SetBindPoint2(0, to_zone, to_instance, glm::vec4(new_x, new_y, new_z, new_heading)); } XSRETURN_EMPTY; } @@ -1816,7 +1753,7 @@ XS(XS_Client_GetDuelTarget) { Perl_croak(aTHX_ "Usage: Client::GetDuelTarget(THIS)"); // @categories Account and Character, Script Utility { Client *THIS; - uint16 RETVAL; + uint32 RETVAL; dXSTARG; VALIDATE_THIS_IS_CLIENT; RETVAL = THIS->GetDuelTarget(); @@ -1849,7 +1786,7 @@ XS(XS_Client_SetDuelTarget) { Perl_croak(aTHX_ "Usage: Client::SetDuelTarget(THIS, set_id)"); // @categories Account and Character { Client *THIS; - uint16 set_id = (uint16) SvUV(ST(1)); + uint32 set_id = (uint32) SvUV(ST(1)); VALIDATE_THIS_IS_CLIENT; THIS->SetDuelTarget(set_id); } @@ -1960,6 +1897,23 @@ XS(XS_Client_UnmemSpellAll) { XSRETURN_EMPTY; } +XS(XS_Client_FindEmptyMemSlot); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Client_FindEmptyMemSlot) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Client::FindEmptyMemSlot(THIS)"); // @categories Account and Character, Spells and Disciplines + { + Client *THIS; + int RETVAL; + dXSTARG; + VALIDATE_THIS_IS_CLIENT; + RETVAL = THIS->FindEmptyMemSlot(); + XSprePUSH; + PUSHi((IV) RETVAL); + } + XSRETURN(1); +} + XS(XS_Client_FindMemmedSpellBySlot); /* prototype to pass -Wmissing-prototypes */ XS(XS_Client_FindMemmedSpellBySlot) { dXSARGS; @@ -1978,6 +1932,24 @@ XS(XS_Client_FindMemmedSpellBySlot) { XSRETURN(1); } +XS(XS_Client_FindMemmedSpellBySpellID); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Client_FindMemmedSpellBySpellID) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Client::FindMemmedSpellBySpellID(THIS, uint16 spell_id)"); // @categories Account and Character, Spells and Disciplines + { + Client *THIS; + int RETVAL; + dXSTARG; + uint16 spell_id = (uint16) SvUV(ST(1)); + VALIDATE_THIS_IS_CLIENT; + RETVAL = THIS->FindMemmedSpellBySpellID(spell_id); + XSprePUSH; + PUSHi((IV) RETVAL); + } + XSRETURN(1); +} + XS(XS_Client_MemmedCount); /* prototype to pass -Wmissing-prototypes */ XS(XS_Client_MemmedCount) { dXSARGS; @@ -2148,6 +2120,19 @@ XS(XS_Client_IsStanding) XSRETURN(1); } +XS(XS_Client_Sit); +XS(XS_Client_Sit) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Client::Sit(THIS)"); + { + Client *THIS; + VALIDATE_THIS_IS_CLIENT; + THIS->Sit(); + } + XSRETURN_EMPTY; +} + XS(XS_Client_IsSitting); /* prototype to pass -Wmissing-prototypes */ XS(XS_Client_IsSitting) { dXSARGS; @@ -2429,17 +2414,17 @@ XS(XS_Client_DeleteItemInInventory); /* prototype to pass -Wmissing-prototypes * XS(XS_Client_DeleteItemInInventory) { dXSARGS; if (items < 2 || items > 4) - Perl_croak(aTHX_ "Usage: Client::DeleteItemInInventory(THIS, int16 slot_id, [int8 quantity = 0], [bool client_update = false])"); // @categories Inventory and Items + Perl_croak(aTHX_ "Usage: Client::DeleteItemInInventory(THIS, int16 slot_id, [int16 quantity = 0], [bool client_update = false])"); // @categories Inventory and Items { Client *THIS; int16 slot_id = (int16) SvIV(ST(1)); - int8 quantity; + int16 quantity; bool client_update; VALIDATE_THIS_IS_CLIENT; if (items < 3) quantity = 0; else { - quantity = (int8) SvIV(ST(2)); + quantity = (int16) SvIV(ST(2)); } if (items < 4) @@ -2688,14 +2673,14 @@ XS(XS_Client_DecreaseByID); /* prototype to pass -Wmissing-prototypes */ XS(XS_Client_DecreaseByID) { dXSARGS; if (items != 3) - Perl_croak(aTHX_ "Usage: Client::DecreaseByID(THIS, uint32 type, unit8 amount)"); // @categories Script Utility + Perl_croak(aTHX_ "Usage: Client::DecreaseByID(THIS, uint32 type, int16 quantity)"); // @categories Script Utility { Client *THIS; bool RETVAL; uint32 type = (uint32) SvUV(ST(1)); - uint8 amt = (uint8) SvUV(ST(2)); + int16 quantity = (int16) SvIV(ST(2)); VALIDATE_THIS_IS_CLIENT; - RETVAL = THIS->DecreaseByID(type, amt); + RETVAL = THIS->DecreaseByID(type, quantity); ST(0) = boolSV(RETVAL); sv_2mortal(ST(0)); } @@ -3276,23 +3261,22 @@ XS(XS_Client_GetStartZone) { XS(XS_Client_SetStartZone); XS(XS_Client_SetStartZone) { dXSARGS; - if (items != 2 && items != 5) - Perl_croak(aTHX_ - "Usage: Client::SetStartZone(THIS, uint32 zone_id, [float x = 0], [float y = 0], [float z = 0])"); + if (items != 2 && items != 5 && items != 6) + Perl_croak(aTHX_ "Usage: Client::SetStartZone(THIS, uint32 zone_id, [float x = 0, float y = 0, float z = 0, [float heading = 0]])"); { Client *THIS; uint32 zoneid = (uint32) SvUV(ST(1)); - float x = 0; - float y = 0; - float z = 0; + float x = 0.0f, y = 0.0f, z = 0.0f, heading = 0.0f; VALIDATE_THIS_IS_CLIENT; if (items == 5) { - x = SvNV(ST(2)); - y = SvNV(ST(3)); - z = SvNV(ST(4)); + x = (float) SvNV(ST(2)); + y = (float) SvNV(ST(3)); + z = (float) SvNV(ST(4)); } + if (items == 6) + heading = (float) SvNV(ST(5)); - THIS->SetStartZone(zoneid, x, y, z); + THIS->SetStartZone(zoneid, x, y, z, heading); } XSRETURN_EMPTY; } @@ -3451,6 +3435,21 @@ XS(XS_Client_ReadBook) { XSRETURN_EMPTY; } +XS(XS_Client_SetGMStatus); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Client_SetGMStatus) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Client::SetGMStatus(THIS, int newStatus)"); // @categories Script Utility + { + Client *THIS; + int newStatus = (int)SvIV(ST(1)); + VALIDATE_THIS_IS_CLIENT; + THIS->SetGMStatus(newStatus); + THIS->UpdateAdmin(true); + } + XSRETURN_EMPTY; +} + XS(XS_Client_UpdateGroupAAs); /* prototype to pass -Wmissing-prototypes */ XS(XS_Client_UpdateGroupAAs) { dXSARGS; @@ -4025,7 +4024,7 @@ XS(XS_Client_Freeze); XS(XS_Client_Freeze) { dXSARGS; if (items != 1) - Perl_croak(aTHX_ "Usage: Client:Freeze(THIS)"); + Perl_croak(aTHX_ "Usage: Client::Freeze(THIS)"); { Client *THIS; VALIDATE_THIS_IS_CLIENT; @@ -4038,7 +4037,7 @@ XS(XS_Client_UnFreeze); XS(XS_Client_UnFreeze) { dXSARGS; if (items != 1) - Perl_croak(aTHX_ "Usage: Client:UnFreeze(THIS)"); + Perl_croak(aTHX_ "Usage: Client::UnFreeze(THIS)"); { Client *THIS; VALIDATE_THIS_IS_CLIENT; @@ -4438,7 +4437,7 @@ XS(XS_Client_PlayMP3); /* prototype to pass -Wmissing-prototypes */ XS(XS_Client_PlayMP3) { dXSARGS; if (items < 1 || items > 2) - Perl_croak(aTHX_ "Usage: Client::PlayMP3(THIS, string file_name)"); // @categories Script Utility + Perl_croak(aTHX_ "Usage: Client::PlayMP3(THIS, string file)"); // @categories Script Utility { Client *THIS; char *fname = nullptr; @@ -4768,26 +4767,165 @@ XS(XS_Client_GetClientMaxLevel) { XSRETURN(1); } +DynamicZoneLocation GetDynamicZoneLocationFromHash(HV* hash) +{ + // dynamic zone helper method (caller must validate hash) + SV** zone_ptr = hv_fetchs(hash, "zone", false); + SV** x_ptr = hv_fetchs(hash, "x", false); + SV** y_ptr = hv_fetchs(hash, "y", false); + SV** z_ptr = hv_fetchs(hash, "z", false); + SV** h_ptr = hv_fetchs(hash, "h", false); + + uint32_t zone_id = 0; + if (zone_ptr && SvIOK(*zone_ptr)) + { + zone_id = static_cast(SvIV(*zone_ptr)); + } + else if (zone_ptr && SvPOK(*zone_ptr)) + { + zone_id = ZoneID(SvPV_nolen(*zone_ptr)); + } + + // SvNIOK checks for number, integer or double + float x = (x_ptr && SvNIOK(*x_ptr)) ? static_cast(SvNV(*x_ptr)) : 0.0f; + float y = (y_ptr && SvNIOK(*y_ptr)) ? static_cast(SvNV(*y_ptr)) : 0.0f; + float z = (z_ptr && SvNIOK(*z_ptr)) ? static_cast(SvNV(*z_ptr)) : 0.0f; + float h = (h_ptr && SvNIOK(*h_ptr)) ? static_cast(SvNV(*h_ptr)) : 0.0f; + + return { zone_id, x, y, z, h }; +} + +Expedition* CreateExpeditionFromHash(Client* client, SV* hash_ref) +{ + if (!hash_ref || !SvROK(hash_ref)) // verify valid reference type + { + Perl_croak(aTHX_ "Client::CreateExpedition argument is not a reference type"); + } + + HV* hash = (HV*)SvRV(hash_ref); // dereference and verify type is hash + if (SvTYPE(hash) != SVt_PVHV) + { + Perl_croak(aTHX_ "Client::CreateExpedition reference argument is not to a hash type"); + } + + SV** expedition_info_ptr = hv_fetchs(hash, "expedition", false); + if (!expedition_info_ptr) + { + Perl_croak(aTHX_ "Client::CreateExpedition required 'expedition' key missing from hash"); + } + + if (!SvROK(*expedition_info_ptr) || SvTYPE(SvRV(*expedition_info_ptr)) != SVt_PVHV) + { + Perl_croak(aTHX_ "Client::CreateExpedition 'expedition' entry must have a hash table value"); + } + + SV** instance_info_ptr = hv_fetchs(hash, "instance", false); + if (!instance_info_ptr) + { + Perl_croak(aTHX_ "Client::CreateExpedition required 'instance' key missing from hash"); + } + + if (!SvROK(*instance_info_ptr) || SvTYPE(SvRV(*instance_info_ptr)) != SVt_PVHV) + { + Perl_croak(aTHX_ "Client::CreateExpedition 'instance' entry must have a hash table value"); + } + + // dereference the nested hash tables and validate required keys + HV* expedition_hash = (HV*)SvRV(*expedition_info_ptr); + SV** name_ptr = hv_fetchs(expedition_hash, "name", false); + SV** min_players_ptr = hv_fetchs(expedition_hash, "min_players", false); + SV** max_players_ptr = hv_fetchs(expedition_hash, "max_players", false); + SV** disable_msg_ptr = hv_fetchs(expedition_hash, "disable_messages", false); + if (!name_ptr || !min_players_ptr || !max_players_ptr) + { + Perl_croak(aTHX_ "Client::CreateExpedition 'expedition' hash table missing required keys (name, min_players, max_players)"); + } + + HV* instance_hash = (HV*)SvRV(*instance_info_ptr); + SV** instance_zone_ptr = hv_fetchs(instance_hash, "zone", false); + SV** version_ptr = hv_fetchs(instance_hash, "version", false); + SV** duration_ptr = hv_fetchs(instance_hash, "duration", false); + if (!instance_zone_ptr || !version_ptr || !duration_ptr) + { + Perl_croak(aTHX_ "Client::CreateExpedition 'instance' hash table missing required keys (zone, version, duration)"); + } + + uint32_t zone_id = 0; + if (SvIOK(*instance_zone_ptr)) + { + zone_id = static_cast(SvIV(*instance_zone_ptr)); + } + else if (SvPOK(*instance_zone_ptr)) + { + zone_id = ZoneID(SvPV_nolen(*instance_zone_ptr)); + } + else + { + Perl_croak(aTHX_ "Client::CreateExpedition zone value in 'instance' table must be int or string"); + } + + uint32_t zone_version = SvIOK(*version_ptr) ? static_cast(SvIV(*version_ptr)) : 0; + uint32_t zone_duration = SvIOK(*duration_ptr) ? static_cast(SvIV(*duration_ptr)) : 0; + + DynamicZone dz{ zone_id, zone_version, zone_duration, DynamicZoneType::Expedition }; + dz.SetName(SvPOK(*name_ptr) ? SvPV_nolen(*name_ptr) : ""); + dz.SetMinPlayers(SvIOK(*min_players_ptr) ? static_cast(SvIV(*min_players_ptr)) : 0); + dz.SetMaxPlayers(SvIOK(*max_players_ptr) ? static_cast(SvIV(*max_players_ptr)) : 0); + + SV** compass_ptr = hv_fetchs(hash, "compass", false); + if (compass_ptr && SvROK(*compass_ptr) && SvTYPE(SvRV(*compass_ptr)) == SVt_PVHV) + { + auto compass_loc = GetDynamicZoneLocationFromHash((HV*)SvRV(*compass_ptr)); + dz.SetCompass(compass_loc); + } + + SV** safereturn_ptr = hv_fetchs(hash, "safereturn", false); + if (safereturn_ptr && SvROK(*safereturn_ptr) && SvTYPE(SvRV(*safereturn_ptr)) == SVt_PVHV) + { + auto safereturn_loc = GetDynamicZoneLocationFromHash((HV*)SvRV(*safereturn_ptr)); + dz.SetSafeReturn(safereturn_loc); + } + + SV** zonein_ptr = hv_fetchs(hash, "zonein", false); + if (zonein_ptr && SvROK(*zonein_ptr) && SvTYPE(SvRV(*zonein_ptr)) == SVt_PVHV) + { + auto zonein_loc = GetDynamicZoneLocationFromHash((HV*)SvRV(*zonein_ptr)); + dz.SetZoneInLocation(zonein_loc); + } + + bool disable_messages = (disable_msg_ptr && SvIOK(*disable_msg_ptr)) ? SvTRUE(*disable_msg_ptr) : false; + + return client->CreateExpedition(dz, disable_messages); +} + XS(XS_Client_CreateExpedition); XS(XS_Client_CreateExpedition) { dXSARGS; - if (items != 7 && items != 8) { - Perl_croak(aTHX_ "Usage: Client::CreateExpedition(THIS, string zone_name, uint32 zone_version, uint32 duration, string expedition_name, uint32 min_players, uint32 max_players, [bool disable_messages = false])"); + if (items != 2 && items != 7 && items != 8) { + Perl_croak(aTHX_ "Usage: Client::CreateExpedition(THIS, HASHREF expedition_info | string zone_name, uint32 zone_version, uint32 duration, string expedition_name, uint32 min_players, uint32 max_players, [bool disable_messages = false])"); } Client* THIS = nullptr; VALIDATE_THIS_IS_CLIENT; - std::string zone_name(SvPV_nolen(ST(1))); - uint32 zone_version = (uint32)SvUV(ST(2)); - uint32 duration = (uint32)SvUV(ST(3)); - std::string expedition_name(SvPV_nolen(ST(4))); - uint32 min_players = (uint32)SvUV(ST(5)); - uint32 max_players = (uint32)SvUV(ST(6)); - bool disable_messages = (items > 7) ? (bool)SvTRUE(ST(7)) : false; + Expedition* RETVAL = nullptr; + if (items == 2) + { + RETVAL = CreateExpeditionFromHash(THIS, ST(1)); + } + else + { + std::string zone_name(SvPV_nolen(ST(1))); + uint32 zone_version = (uint32)SvUV(ST(2)); + uint32 duration = (uint32)SvUV(ST(3)); + std::string expedition_name(SvPV_nolen(ST(4))); + uint32 min_players = (uint32)SvUV(ST(5)); + uint32 max_players = (uint32)SvUV(ST(6)); + bool disable_messages = (items > 7) ? (bool)SvTRUE(ST(7)) : false; - Expedition* RETVAL = THIS->CreateExpedition(zone_name, zone_version, duration, - expedition_name, min_players, max_players, disable_messages); + RETVAL = THIS->CreateExpedition(zone_name, zone_version, duration, + expedition_name, min_players, max_players, disable_messages); + } ST(0) = sv_newmortal(); sv_setref_pv(ST(0), "Expedition", (void*)RETVAL); @@ -4795,6 +4933,94 @@ XS(XS_Client_CreateExpedition) { XSRETURN(1); } +XS(XS_Client_CreateTaskDynamicZone); +XS(XS_Client_CreateTaskDynamicZone) { + dXSARGS; + if (items != 3) { + Perl_croak(aTHX_ "Usage: Client::CreateTaskDynamicZone(THIS, int task_id, HASHREF dz_info)"); + } + + Client* THIS = nullptr; + VALIDATE_THIS_IS_CLIENT; + + SV* hash_ref = ST(2); + if (!hash_ref || !SvROK(hash_ref)) + { + Perl_croak(aTHX_ "Client::CreateTaskDynamicZone argument is not a reference type"); + } + + HV* hash = (HV*)SvRV(hash_ref); // dereference into hash + if (SvTYPE(hash) != SVt_PVHV) + { + Perl_croak(aTHX_ "Client::CreateTaskDynamicZone reference argument is not to a hash type"); + } + + SV** instance_info_ptr = hv_fetchs(hash, "instance", false); + if (!instance_info_ptr) + { + Perl_croak(aTHX_ "Client::CreateTaskDynamicZone required 'instance' key missing from hash"); + } + + if (!SvROK(*instance_info_ptr) || SvTYPE(SvRV(*instance_info_ptr)) != SVt_PVHV) + { + Perl_croak(aTHX_ "Client::CreateTaskDynamicZone 'instance' entry must have a hash table value"); + } + + HV* instance_hash = (HV*)SvRV(*instance_info_ptr); + SV** instance_zone_ptr = hv_fetchs(instance_hash, "zone", false); + SV** version_ptr = hv_fetchs(instance_hash, "version", false); + SV** duration_ptr = hv_fetchs(instance_hash, "duration", false); + if (!instance_zone_ptr || !version_ptr) + { + Perl_croak(aTHX_ "Client::CreateTaskDynamicZone 'instance' hash table missing required keys (zone, version, duration)"); + } + + uint32_t zone_id = 0; + SV* instance_zone = *instance_zone_ptr; + if (SvIOK(instance_zone)) + { + zone_id = static_cast(SvIV(instance_zone)); + } + else if (SvPOK(instance_zone)) + { + zone_id = ZoneID(SvPV_nolen(instance_zone)); + } + else + { + Perl_croak(aTHX_ "Client::CreateTaskDynamicZone zone value in 'instance' table must be int or string"); + } + + uint32_t zone_version = SvIOK(*version_ptr) ? static_cast(SvIV(*version_ptr)) : 0; + + // tasks override dz duration so duration is ignored here + DynamicZone dz{ zone_id, zone_version, 0, DynamicZoneType::None }; + + SV** compass_ptr = hv_fetchs(hash, "compass", false); + if (compass_ptr && SvROK(*compass_ptr) && SvTYPE(SvRV(*compass_ptr)) == SVt_PVHV) + { + auto compass_loc = GetDynamicZoneLocationFromHash((HV*)SvRV(*compass_ptr)); + dz.SetCompass(compass_loc); + } + + SV** safereturn_ptr = hv_fetchs(hash, "safereturn", false); + if (safereturn_ptr && SvROK(*safereturn_ptr) && SvTYPE(SvRV(*safereturn_ptr)) == SVt_PVHV) + { + auto safereturn_loc = GetDynamicZoneLocationFromHash((HV*)SvRV(*safereturn_ptr)); + dz.SetSafeReturn(safereturn_loc); + } + + SV** zonein_ptr = hv_fetchs(hash, "zonein", false); + if (zonein_ptr && SvROK(*zonein_ptr) && SvTYPE(SvRV(*zonein_ptr)) == SVt_PVHV) + { + auto zonein_loc = GetDynamicZoneLocationFromHash((HV*)SvRV(*zonein_ptr)); + dz.SetZoneInLocation(zonein_loc); + } + + uint32_t task_id = static_cast(SvUV(ST(1))); + + THIS->CreateTaskDynamicZone(task_id, dz); +} + XS(XS_Client_GetExpedition); XS(XS_Client_GetExpedition) { dXSARGS; @@ -5062,7 +5288,7 @@ XS(XS_Client_Fling) { VALIDATE_THIS_IS_CLIENT; if (items > 5) ignore_los = (bool) SvTRUE(ST(5)); - + if (items > 6) clipping = (bool) SvTRUE(ST(6)); @@ -5134,7 +5360,7 @@ XS(XS_Client_GetLearnableDisciplines) { min_level = (uint8)SvUV(ST(1)); if (items > 2) max_level = (uint8)SvUV(ST(2)); - + Client* THIS; VALIDATE_THIS_IS_CLIENT; auto learnable_disciplines = THIS->GetLearnableDisciplines(min_level, max_level); @@ -5156,7 +5382,7 @@ XS(XS_Client_GetLearnedDisciplines) { dXSARGS; if (items != 1) Perl_croak(aTHX_ "Usage: Client::GetLearnedDisciplines(THIS)"); - + Client* THIS; VALIDATE_THIS_IS_CLIENT; auto learned_disciplines = THIS->GetLearnedDisciplines(); @@ -5178,7 +5404,7 @@ XS(XS_Client_GetMemmedSpells) { dXSARGS; if (items != 1) Perl_croak(aTHX_ "Usage: Client::GetMemmedSpells(THIS)"); - + Client* THIS; VALIDATE_THIS_IS_CLIENT; auto memmed_spells = THIS->GetMemmedSpells(); @@ -5200,7 +5426,7 @@ XS(XS_Client_GetScribeableSpells) { dXSARGS; if (items < 1 || items > 3) Perl_croak(aTHX_ "Usage: Client::GetScribeableSpells(THIS, [uint8 min_level, uint8 max_level])"); - + uint8 min_level = 1; uint8 max_level = 0; if (items > 1) @@ -5229,7 +5455,7 @@ XS(XS_Client_GetScribedSpells) { dXSARGS; if (items != 1) Perl_croak(aTHX_ "Usage: Client::GetScribedSpells(THIS)"); - + Client* THIS; VALIDATE_THIS_IS_CLIENT; auto scribed_spells = THIS->GetScribedSpells(); @@ -5262,6 +5488,494 @@ XS(XS_Client_GetInventory) { XSRETURN(1); } +XS(XS_Client_GetAAEXPModifier); +XS(XS_Client_GetAAEXPModifier) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Client::GetAAEXPModifier(THIS, uint32 zone_id)"); + { + Client* THIS; + double aa_modifier = 1.0f; + uint32 zone_id = (uint32)SvUV(ST(1)); + dXSTARG; + VALIDATE_THIS_IS_CLIENT; + aa_modifier = THIS->GetAAEXPModifier(zone_id); + XSprePUSH; + PUSHn((double) aa_modifier); + } + XSRETURN(1); +} + +XS(XS_Client_GetEXPModifier); +XS(XS_Client_GetEXPModifier) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Client::GetEXPModifier(THIS, uint32 zone_id)"); + { + Client* THIS; + double exp_modifier = 1.0f; + uint32 zone_id = (uint32)SvUV(ST(1)); + dXSTARG; + VALIDATE_THIS_IS_CLIENT; + exp_modifier = THIS->GetEXPModifier(zone_id); + XSprePUSH; + PUSHn((double) exp_modifier); + } + XSRETURN(1); +} + +XS(XS_Client_SetAAEXPModifier); +XS(XS_Client_SetAAEXPModifier) { + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: Client::SetAAEXPModifier(THIS, uint32 zone_id, float aa_modifier)"); + { + Client* THIS; + uint32 zone_id = (uint32)SvUV(ST(1)); + double aa_modifier = (double) SvNV(ST(2)); + VALIDATE_THIS_IS_CLIENT; + THIS->SetAAEXPModifier(zone_id, aa_modifier); + } + XSRETURN_EMPTY; +} + +XS(XS_Client_SetEXPModifier); +XS(XS_Client_SetEXPModifier) { + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: Client::SetEXPModifier(THIS, uint32 zone_id, float exp_modifier)"); + { + Client* THIS; + uint32 zone_id = (uint32)SvUV(ST(1)); + double exp_modifier = (double) SvNV(ST(2)); + VALIDATE_THIS_IS_CLIENT; + THIS->SetEXPModifier(zone_id, exp_modifier); + } + XSRETURN_EMPTY; +} + +XS(XS_Client_AddLDoNLoss); +XS(XS_Client_AddLDoNLoss) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Client::AddLDoNLoss(THIS, uint32 theme_id)"); + { + Client* THIS; + uint32 theme_id = (uint32) SvUV(ST(1)); + VALIDATE_THIS_IS_CLIENT; + THIS->UpdateLDoNWinLoss(theme_id); + } + XSRETURN_EMPTY; +} + +XS(XS_Client_AddLDoNWin); +XS(XS_Client_AddLDoNWin) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Client::AddLDoNWin(THIS, uint32 theme_id)"); + { + Client* THIS; + uint32 theme_id = (uint32) SvUV(ST(1)); + VALIDATE_THIS_IS_CLIENT; + THIS->UpdateLDoNWinLoss(theme_id, true); + } + XSRETURN_EMPTY; +} + +XS(XS_Client_SetHideMe); +XS(XS_Client_SetHideMe) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Client::SetHideMe(THIS, bool hide_me_state)"); + { + Client* THIS; + bool hide_me_state = (bool) SvTRUE(ST(1)); + VALIDATE_THIS_IS_CLIENT; + THIS->SetHideMe(hide_me_state); + } + XSRETURN_EMPTY; +} + +XS(XS_Client_ResetAllDisciplineTimers); +XS(XS_Client_ResetAllDisciplineTimers) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Client::ResetAllDisciplineTimers(THIS)"); // @categories Spells and Disciplines + { + Client *THIS; + VALIDATE_THIS_IS_CLIENT; + THIS->ResetAllDisciplineTimers(); + } + XSRETURN_EMPTY; +} + +XS(XS_Client_SendToInstance); +XS(XS_Client_SendToInstance) { + dXSARGS; + if (items != 10) + Perl_croak(aTHX_ "Usage: Client::SendToInstance(THIS, string instance_type, string zone_short_name, uint32 instance_version, float x, float y, float z, float heading, string instance_identifier, uint32 duration)"); + { + Client* THIS; + std::string instance_type = (std::string) SvPV_nolen(ST(1)); + std::string zone_short_name = (std::string) SvPV_nolen(ST(2)); + uint32 instance_version = (uint32) SvUV(ST(3)); + float x = (float) SvNV(ST(4)); + float y = (float) SvNV(ST(5)); + float z = (float) SvNV(ST(6)); + float heading = (float) SvNV(ST(7)); + std::string instance_identifier = (std::string) SvPV_nolen(ST(8)); + uint32 duration = (uint32) SvUV(ST(9)); + VALIDATE_THIS_IS_CLIENT; + THIS->SendToInstance(instance_type, zone_short_name, instance_version, x, y, z, heading, instance_identifier, duration); + } + XSRETURN_EMPTY; +} + +XS(XS_Client_CountItem); +XS(XS_Client_CountItem) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Client::CountItem(THIS, uint32 item_id)"); + { + Client* THIS; + int item_count = 0; + uint32 item_id = (uint32) SvUV(ST(1)); + dXSTARG; + VALIDATE_THIS_IS_CLIENT; + item_count = THIS->CountItem(item_id); + XSprePUSH; + PUSHu((UV) item_count); + } + XSRETURN(1); +} + +XS(XS_Client_RemoveItem); +XS(XS_Client_RemoveItem) { + dXSARGS; + if (items != 2 && items != 3) + Perl_croak(aTHX_ "Usage: Client::RemoveItem(THIS, uint32 item_id, [uint32 quantity = 1])"); // @categories Spells and Disciplines + { + Client *THIS; + uint32 item_id = (uint32) SvUV(ST(1)); + uint32 quantity = 1; + VALIDATE_THIS_IS_CLIENT; + if (items == 3) { + quantity = (uint32) SvUV(ST(2)); + } + + THIS->RemoveItem(item_id, quantity); + } + XSRETURN_EMPTY; +} + +XS(XS_Client_DialogueWindow); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Client_DialogueWindow) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Client::DialogueWindow(THIS, string window_markdown)"); // @categories Script Utility + { + Client *THIS; + dXSTARG; + VALIDATE_THIS_IS_CLIENT; + + std::string window_markdown(SvPV_nolen(ST(1))); + DialogueWindow::Render(THIS, window_markdown); + } + XSRETURN_EMPTY; +} + +XS(XS_Client_DiaWind); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Client_DiaWind) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Client::DiaWind(THIS, string window_markdown)"); // @categories Script Utility + { + Client *THIS; + dXSTARG; + VALIDATE_THIS_IS_CLIENT; + + std::string window_markdown(SvPV_nolen(ST(1))); + DialogueWindow::Render(THIS, window_markdown); + + } + XSRETURN_EMPTY; +} + +XS(XS_Client_GetIPExemption); +XS(XS_Client_GetIPExemption) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Client::GetIPExemption(THIS)"); // @categories Account and Character + { + Client* THIS; + int exemption_amount = 0; + dXSTARG; + VALIDATE_THIS_IS_CLIENT; + exemption_amount = THIS->GetIPExemption(); + XSprePUSH; + PUSHi((IV) exemption_amount); + } + XSRETURN(1); +} + +XS(XS_Client_GetIPString); +XS(XS_Client_GetIPString) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Client::GetIPString(THIS)"); // @categories Account and Character + { + Client *THIS; + dXSTARG; + VALIDATE_THIS_IS_CLIENT; + std::string ip_string = THIS->GetIPString(); + sv_setpv(TARG, ip_string.c_str()); + XSprePUSH; + PUSHTARG; + } + XSRETURN(1); +} + +XS(XS_Client_SetIPExemption); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Client_SetIPExemption) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Client::SetIPExemption(THIS, int exemption_amount)"); // @categories Account and Character + { + Client *THIS; + int exemption_amount = (int) SvIV(ST(1)); + dXSTARG; + VALIDATE_THIS_IS_CLIENT; + THIS->SetIPExemption(exemption_amount); + } + XSRETURN_EMPTY; +} + +XS(XS_Client_ReadBookByName); +XS(XS_Client_ReadBookByName) { + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: Client::ReadBookByName(THIS, string book_name, uint8 book_type)"); // @categories Script Utility + { + Client *THIS; + std::string book_name(SvPV_nolen(ST(1))); + uint8 book_type = (uint8) SvUV(ST(2)); + VALIDATE_THIS_IS_CLIENT; + THIS->ReadBookByName(book_name, book_type); + } + XSRETURN_EMPTY; +} + +XS(XS_Client_UntrainDiscBySpellID); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Client_UntrainDiscBySpellID) { + dXSARGS; + if (items < 2 || items > 3) + Perl_croak(aTHX_ "Usage: Client::UntrainDiscBySpellID(THIS, uint16 spell_id, [bool update_client = true])"); // @categories Spells and Disciplines + { + Client *THIS; + uint16 spell_id = (uint16) SvUV(ST(1)); + bool update_client = true; + VALIDATE_THIS_IS_CLIENT; + if (items == 3) { + update_client = (bool) SvTRUE(ST(2)); + } + + THIS->UntrainDiscBySpellID(spell_id, update_client); + } + XSRETURN_EMPTY; +} + +XS(XS_Client_SummonBaggedItems); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Client_SummonBaggedItems) { + dXSARGS; + if (items != 3) { + Perl_croak(aTHX_ "Usage: Client::SummonBaggedItems(THIS, uint32 bag_item_id, ARRAYREF bag_items_array)"); // @categories Inventory and Items, Script Utility + } + + Client* THIS; + VALIDATE_THIS_IS_CLIENT; + + uint32 bag_item_id = (uint32) SvUV(ST(1)); + + // verify we're receiving a reference to an array type + SV* bag_items_avref = ST(2); + if (!bag_items_avref || !SvROK(bag_items_avref) || SvTYPE(SvRV(bag_items_avref)) != SVt_PVAV) { + Perl_croak(aTHX_ "Client::SummonBaggedItems second argument is not a reference to an array"); + } + + // dereference into the array + AV* bag_items_av = (AV*)SvRV(bag_items_avref); + + std::vector bagged_items; + + auto count = av_len(bag_items_av) + 1; + for (int i = 0; i < count; ++i) { + SV** element = av_fetch(bag_items_av, i, 0); + + // verify array element is a hash reference containing item details + if (element && SvROK(*element) && SvTYPE(SvRV(*element)) == SVt_PVHV) { + HV* hash = (HV*)SvRV(*element); // dereference + + SV** item_id_ptr = hv_fetchs(hash, "item_id", false); + SV** item_charges_ptr = hv_fetchs(hash, "charges", false); + SV** attuned_ptr = hv_fetchs(hash, "attuned", false); + SV** augment_one_ptr = hv_fetchs(hash, "augment_one", false); + SV** augment_two_ptr = hv_fetchs(hash, "augment_two", false); + SV** augment_three_ptr = hv_fetchs(hash, "augment_three", false); + SV** augment_four_ptr = hv_fetchs(hash, "augment_four", false); + SV** augment_five_ptr = hv_fetchs(hash, "augment_five", false); + SV** augment_six_ptr = hv_fetchs(hash, "augment_six", false); + if (item_id_ptr && item_charges_ptr) { + ServerLootItem_Struct item{}; + item.item_id = static_cast(SvUV(*item_id_ptr)); + item.charges = static_cast(SvIV(*item_charges_ptr)); + item.attuned = attuned_ptr ? static_cast(SvUV(*attuned_ptr)) : 0; + item.aug_1 = augment_one_ptr ? static_cast(SvUV(*augment_one_ptr)) : 0; + item.aug_2 = augment_two_ptr ? static_cast(SvUV(*augment_two_ptr)) : 0; + item.aug_3 = augment_three_ptr ? static_cast(SvUV(*augment_three_ptr)) : 0; + item.aug_4 = augment_four_ptr ? static_cast(SvUV(*augment_four_ptr)) : 0; + item.aug_5 = augment_five_ptr ? static_cast(SvUV(*augment_five_ptr)) : 0; + item.aug_6 = augment_six_ptr ? static_cast(SvUV(*augment_six_ptr)) : 0; + bagged_items.emplace_back(item); + } + } + } + + THIS->SummonBaggedItems(bag_item_id, bagged_items); + + XSRETURN_EMPTY; +} + +XS(XS_Client_RemoveLDoNLoss); +XS(XS_Client_RemoveLDoNLoss) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Client::RemoveLDoNLoss(THIS, uint32 theme_id)"); + { + Client* THIS; + uint32 theme_id = (uint32) SvUV(ST(1)); + VALIDATE_THIS_IS_CLIENT; + THIS->UpdateLDoNWinLoss(theme_id, false, true); + } + XSRETURN_EMPTY; +} + +XS(XS_Client_RemoveLDoNWin); +XS(XS_Client_RemoveLDoNWin) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Client::RemoveLDoNWin(THIS, uint32 theme_id)"); + { + Client* THIS; + uint32 theme_id = (uint32) SvUV(ST(1)); + VALIDATE_THIS_IS_CLIENT; + THIS->UpdateLDoNWinLoss(theme_id, true, true); + } + XSRETURN_EMPTY; +} + +XS(XS_Client_GetFreeDisciplineSlot); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Client_GetFreeDisciplineSlot) { + dXSARGS; + if (items != 1 || items != 2) + Perl_croak(aTHX_ "Usage: Client::GetFreeDisciplineSlot(THIS, [int starting_slot = 0])"); // @categories Spells and Disciplines + { + Client *THIS; + int free_discipline_slot; + int starting_slot = 0; + dXSTARG; + VALIDATE_THIS_IS_CLIENT; + if (items == 2) { + starting_slot = SvIV(ST(1)); + } + + free_discipline_slot = THIS->GetNextAvailableDisciplineSlot(starting_slot); + XSprePUSH; + PUSHi((IV) free_discipline_slot); + } + XSRETURN(1); +} + +XS(XS_Client_ScribeSpells); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Client_ScribeSpells) { + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: Client::ScribeSpells(THIS, uint8 min_level, uint8 max_level)"); // @categories Spells and Disciplines + { + Client *THIS; + uint8 min_level = (uint8) SvUV(ST(1)); + uint8 max_level = (uint8) SvUV(ST(2)); + uint16 scribed_spells = 0; + dXSTARG; + VALIDATE_THIS_IS_CLIENT; + + scribed_spells = THIS->ScribeSpells(min_level, max_level); + XSprePUSH; + PUSHu((UV) scribed_spells); + } + XSRETURN(1); +} + +XS(XS_Client_LearnDisciplines); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Client_LearnDisciplines) { + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: Client::LearnDisciplines(THIS, uint8 min_level, uint8 max_level)"); // @categories Spells and Disciplines + { + Client *THIS; + uint8 min_level = (uint8) SvUV(ST(1)); + uint8 max_level = (uint8) SvUV(ST(2)); + uint16 learned_disciplines = 0; + dXSTARG; + VALIDATE_THIS_IS_CLIENT; + + learned_disciplines = THIS->LearnDisciplines(min_level, max_level); + XSprePUSH; + PUSHu((UV) learned_disciplines); + } + XSRETURN(1); +} + +XS(XS_Client_ResetCastbarCooldownBySlot); +XS(XS_Client_ResetCastbarCooldownBySlot) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Client::ResetCastbarCooldownBySlot(THIS, int slot)"); + { + Client* THIS; + int slot = (int) SvIV(ST(1)); + VALIDATE_THIS_IS_CLIENT; + THIS->ResetCastbarCooldownBySlot(slot); + } + XSRETURN_EMPTY; +} + +XS(XS_Client_ResetAllCastbarCooldowns); +XS(XS_Client_ResetAllCastbarCooldowns) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Client::ResetAllCastbarCooldowns(THIS)"); + { + Client* THIS; + VALIDATE_THIS_IS_CLIENT; + THIS->ResetAllCastbarCooldowns(); + } + XSRETURN_EMPTY; +} + +XS(XS_Client_ResetCastbarCooldownBySpellID); +XS(XS_Client_ResetCastbarCooldownBySpellID) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Client::ResetCastbarCooldownBySpellID(THIS, uint32 spell_id)"); + { + Client* THIS; + uint32 spell_id = (uint32) SvUV(ST(1)); + VALIDATE_THIS_IS_CLIENT; + THIS->ResetCastbarCooldownBySpellID(spell_id); + } + XSRETURN_EMPTY; +} + #ifdef __cplusplus extern "C" #endif @@ -5278,10 +5992,7 @@ XS(boot_Client) { //add the strcpy stuff to get rid of const warnings.... - - XS_VERSION_BOOTCHECK; - newXSproto(strcpy(buf, "AccountID"), XS_Client_AccountID, file, "$"); newXSproto(strcpy(buf, "AccountName"), XS_Client_AccountName, file, "$"); newXSproto(strcpy(buf, "AddAAPoints"), XS_Client_AddAAPoints, file, "$$"); @@ -5290,6 +6001,8 @@ XS(boot_Client) { newXSproto(strcpy(buf, "AddEXP"), XS_Client_AddEXP, file, "$$;$$"); newXSproto(strcpy(buf, "AddExpeditionLockout"), XS_Client_AddExpeditionLockout, file, "$$$$;$"); newXSproto(strcpy(buf, "AddExpeditionLockoutDuration"), XS_Client_AddExpeditionLockoutDuration, file, "$$$$;$"); + newXSproto(strcpy(buf, "AddLDoNLoss"), XS_Client_AddLDoNLoss, file, "$$"); + newXSproto(strcpy(buf, "AddLDoNWin"), XS_Client_AddLDoNWin, file, "$$"); newXSproto(strcpy(buf, "AddLevelBasedExp"), XS_Client_AddLevelBasedExp, file, "$$;$$"); newXSproto(strcpy(buf, "AddMoneyToPP"), XS_Client_AddMoneyToPP, file, "$$$$$$"); newXSproto(strcpy(buf, "AddPVPPoints"), XS_Client_AddPVPPoints, file, "$$"); @@ -5308,10 +6021,14 @@ XS(boot_Client) { newXSproto(strcpy(buf, "CheckSpecializeIncrease"), XS_Client_CheckSpecializeIncrease, file, "$$"); newXSproto(strcpy(buf, "ClearCompassMark"), XS_Client_ClearCompassMark, file, "$"); newXSproto(strcpy(buf, "ClearZoneFlag"), XS_Client_ClearZoneFlag, file, "$$"); - newXSproto(strcpy(buf, "CreateExpedition"), XS_Client_CreateExpedition, file, "$$$$$$$;$"); newXSproto(strcpy(buf, "Connected"), XS_Client_Connected, file, "$"); + newXSproto(strcpy(buf, "CountItem"), XS_Client_CountItem, file, "$$"); + newXSproto(strcpy(buf, "CreateExpedition"), XS_Client_CreateExpedition, file, "$$$$$$$;$"); + newXSproto(strcpy(buf, "CreateTaskDynamicZone"), XS_Client_CreateTaskDynamicZone, file, "$$"); newXSproto(strcpy(buf, "DecreaseByID"), XS_Client_DecreaseByID, file, "$$$"); newXSproto(strcpy(buf, "DeleteItemInInventory"), XS_Client_DeleteItemInInventory, file, "$$;$$"); + newXSproto(strcpy(buf, "DiaWind"), XS_Client_DiaWind, file, "$$"); + newXSproto(strcpy(buf, "DialogueWindow"), XS_Client_DialogueWindow, file, "$$"); newXSproto(strcpy(buf, "Disconnect"), XS_Client_Disconnect, file, "$"); newXSproto(strcpy(buf, "DropItem"), XS_Client_DropItem, file, "$$"); newXSproto(strcpy(buf, "Duck"), XS_Client_Duck, file, "$"); @@ -5319,16 +6036,21 @@ XS(boot_Client) { newXSproto(strcpy(buf, "Escape"), XS_Client_Escape, file, "$"); newXSproto(strcpy(buf, "ExpeditionMessage"), XS_Client_ExpeditionMessage, file, "$$$"); newXSproto(strcpy(buf, "FailTask"), XS_Client_FailTask, file, "$$"); + newXSproto(strcpy(buf, "FindEmptyMemSlot"), XS_Client_FindEmptyMemSlot, file, "$"); + newXSproto(strcpy(buf, "FindMemmedSpellBySlot"), XS_Client_FindMemmedSpellBySlot, file, "$$"); + newXSproto(strcpy(buf, "FindMemmedSpellBySpellID"), XS_Client_FindMemmedSpellBySpellID, file, "$$"); newXSproto(strcpy(buf, "Fling"), XS_Client_Fling, file, "$$$$$;$$"); newXSproto(strcpy(buf, "ForageItem"), XS_Client_ForageItem, file, "$"); newXSproto(strcpy(buf, "Freeze"), XS_Client_Freeze, file, "$"); + newXSproto(strcpy(buf, "GMKill"), XS_Client_GMKill, file, "$"); + newXSproto(strcpy(buf, "GetAAEXPModifier"), XS_Client_GetAAEXPModifier, file, "$$"); newXSproto(strcpy(buf, "GetAAExp"), XS_Client_GetAAExp, file, "$"); newXSproto(strcpy(buf, "GetAALevel"), XS_Client_GetAALevel, file, "$$"); newXSproto(strcpy(buf, "GetAAPercent"), XS_Client_GetAAPercent, file, "$"); newXSproto(strcpy(buf, "GetAAPoints"), XS_Client_GetAAPoints, file, "$$"); + newXSproto(strcpy(buf, "GetAFK"), XS_Client_GetAFK, file, "$"); newXSproto(strcpy(buf, "GetAccountAge"), XS_Client_GetAccountAge, file, "$"); newXSproto(strcpy(buf, "GetAccountFlag"), XS_Client_GetAccountFlag, file, "$$"); - newXSproto(strcpy(buf, "GetAFK"), XS_Client_GetAFK, file, "$"); newXSproto(strcpy(buf, "GetAggroCount"), XS_Client_GetAggroCount, file, "$"); newXSproto(strcpy(buf, "GetAllMoney"), XS_Client_GetAllMoney, file, "$"); newXSproto(strcpy(buf, "GetAlternateCurrencyValue"), XS_Client_GetAlternateCurrencyValue, file, "$$"); @@ -5359,38 +6081,41 @@ XS(boot_Client) { newXSproto(strcpy(buf, "GetCorpseID"), XS_Client_GetCorpseID, file, "$$"); newXSproto(strcpy(buf, "GetCorpseItemAt"), XS_Client_GetCorpseItemAt, file, "$$$"); newXSproto(strcpy(buf, "GetCustomItemData"), XS_Client_GetCustomItemData, file, "$$$"); - newXSproto(strcpy(buf, "GetDisciplineTimer"), XS_Client_GetDisciplineTimer, file, "$$"); newXSproto(strcpy(buf, "GetDiscSlotBySpellID"), XS_Client_GetDiscSlotBySpellID, file, "$$"); + newXSproto(strcpy(buf, "GetDisciplineTimer"), XS_Client_GetDisciplineTimer, file, "$$"); newXSproto(strcpy(buf, "GetDuelTarget"), XS_Client_GetDuelTarget, file, "$"); + newXSproto(strcpy(buf, "GetEXP"), XS_Client_GetEXP, file, "$"); + newXSproto(strcpy(buf, "GetEXPModifier"), XS_Client_GetEXPModifier, file, "$$"); newXSproto(strcpy(buf, "GetEbonCrystals"), XS_Client_GetEbonCrystals, file, "$"); newXSproto(strcpy(buf, "GetEndurance"), XS_Client_GetEndurance, file, "$"); newXSproto(strcpy(buf, "GetEnduranceRatio"), XS_Client_GetEnduranceRatio, file, "$"); - newXSproto(strcpy(buf, "GetEXP"), XS_Client_GetEXP, file, "$"); newXSproto(strcpy(buf, "GetExpedition"), XS_Client_GetExpedition, file, "$"); newXSproto(strcpy(buf, "GetExpeditionLockouts"), XS_Client_GetExpeditionLockouts, file, "$;$"); newXSproto(strcpy(buf, "GetFace"), XS_Client_GetFace, file, "$"); newXSproto(strcpy(buf, "GetFactionLevel"), XS_Client_GetFactionLevel, file, "$$$$$$$$"); newXSproto(strcpy(buf, "GetFeigned"), XS_Client_GetFeigned, file, "$"); + newXSproto(strcpy(buf, "GetFreeDisciplineSlot"), XS_Client_GetFreeDisciplineSlot, file, "$;$"); newXSproto(strcpy(buf, "GetFreeSpellBookSlot"), XS_Client_GetFreeSpellBookSlot, file, "$;$"); newXSproto(strcpy(buf, "GetGM"), XS_Client_GetGM, file, "$"); newXSproto(strcpy(buf, "GetGroup"), XS_Client_GetGroup, file, "$"); newXSproto(strcpy(buf, "GetGroupPoints"), XS_Client_GetGroupPoints, file, "$"); newXSproto(strcpy(buf, "GetHorseId"), XS_Client_GetHorseId, file, "$"); newXSproto(strcpy(buf, "GetHunger"), XS_Client_GetHunger, file, "$$"); + newXSproto(strcpy(buf, "GetIP"), XS_Client_GetIP, file, "$"); + newXSproto(strcpy(buf, "GetIPExemption"), XS_Client_GetIPExemption, file, "$"); + newXSproto(strcpy(buf, "GetIPString"), XS_Client_GetIPString, file, "$"); newXSproto(strcpy(buf, "GetInstanceID"), XS_Client_GetInstanceID, file, "$$"); newXSproto(strcpy(buf, "GetInstrumentMod"), XS_Client_GetInstrumentMod, file, "$$"); newXSproto(strcpy(buf, "GetInventory"), XS_Client_GetInventory, file, "$"); - newXSproto(strcpy(buf, "GetIP"), XS_Client_GetIP, file, "$"); newXSproto(strcpy(buf, "GetItemAt"), XS_Client_GetItemAt, file, "$$"); newXSproto(strcpy(buf, "GetItemIDAt"), XS_Client_GetItemIDAt, file, "$$"); newXSproto(strcpy(buf, "GetItemInInventory"), XS_Client_GetItemInInventory, file, "$$"); - newXSproto(strcpy(buf, "GetLanguageSkill"), XS_Client_GetLanguageSkill, file, "$$"); - newXSproto(strcpy(buf, "GetLastName"), XS_Client_GetLastName, file, "$"); newXSproto(strcpy(buf, "GetLDoNLosses"), XS_Client_GetLDoNLosses, file, "$"); newXSproto(strcpy(buf, "GetLDoNLossesTheme"), XS_Client_GetLDoNLossesTheme, file, "$$"); newXSproto(strcpy(buf, "GetLDoNPointsTheme"), XS_Client_GetLDoNPointsTheme, file, "$"); newXSproto(strcpy(buf, "GetLDoNWins"), XS_Client_GetLDoNWins, file, "$"); newXSproto(strcpy(buf, "GetLDoNWinsTheme"), XS_Client_GetLDoNWinsTheme, file, "$$"); + newXSproto(strcpy(buf, "GetLanguageSkill"), XS_Client_GetLanguageSkill, file, "$$"); newXSproto(strcpy(buf, "GetLearnableDisciplines"), XS_Client_GetLearnableDisciplines, file, "$;$$"); newXSproto(strcpy(buf, "GetLearnedDisciplines"), XS_Client_GetLearnedDisciplines, file, "$"); newXSproto(strcpy(buf, "GetLockoutExpeditionUUID"), XS_Client_GetLockoutExpeditionUUID, file, "$$$"); @@ -5420,7 +6145,6 @@ XS(boot_Client) { newXSproto(strcpy(buf, "GetThirst"), XS_Client_GetThirst, file, "$$"); newXSproto(strcpy(buf, "GetTotalSecondsPlayed"), XS_Client_GetTotalSecondsPlayed, file, "$"); newXSproto(strcpy(buf, "GetWeight"), XS_Client_GetWeight, file, "$"); - newXSproto(strcpy(buf, "GMKill"), XS_Client_GMKill, file, "$"); newXSproto(strcpy(buf, "GoFish"), XS_Client_GoFish, file, "$"); newXSproto(strcpy(buf, "GrantAlternateAdvancementAbility"), XS_Client_GrantAlternateAdvancementAbility, file, "$$$;$"); newXSproto(strcpy(buf, "GuildID"), XS_Client_GuildID, file, "$"); @@ -5431,59 +6155,70 @@ XS(boot_Client) { newXSproto(strcpy(buf, "HasSpellScribed"), XS_Client_HasSkill, file, "$$"); newXSproto(strcpy(buf, "HasZoneFlag"), XS_Client_HasZoneFlag, file, "$$"); newXSproto(strcpy(buf, "Hungry"), XS_Client_Hungry, file, "$"); + newXSproto(strcpy(buf, "InZone"), XS_Client_InZone, file, "$"); + newXSproto(strcpy(buf, "IncStats"), XS_Client_IncStats, file, "$$$"); newXSproto(strcpy(buf, "IncreaseLanguageSkill"), XS_Client_IncreaseLanguageSkill, file, "$$;$"); newXSproto(strcpy(buf, "IncreaseSkill"), XS_Client_IncreaseSkill, file, "$$;$"); newXSproto(strcpy(buf, "IncrementAA"), XS_Client_IncrementAA, file, "$$"); - newXSproto(strcpy(buf, "IncStats"), XS_Client_IncStats, file, "$$$"); - newXSproto(strcpy(buf, "InZone"), XS_Client_InZone, file, "$"); newXSproto(strcpy(buf, "IsBecomeNPC"), XS_Client_IsBecomeNPC, file, "$"); + newXSproto(strcpy(buf, "IsCrouching"), XS_Client_IsCrouching, file, "$"); newXSproto(strcpy(buf, "IsDueling"), XS_Client_IsDueling, file, "$"); newXSproto(strcpy(buf, "IsGrouped"), XS_Client_IsGrouped, file, "$"); newXSproto(strcpy(buf, "IsLD"), XS_Client_IsLD, file, "$"); newXSproto(strcpy(buf, "IsMedding"), XS_Client_IsMedding, file, "$"); newXSproto(strcpy(buf, "IsRaidGrouped"), XS_Client_IsRaidGrouped, file, "$"); - newXSproto(strcpy(buf, "IsStanding"), XS_Client_IsStanding, file, "$"); newXSproto(strcpy(buf, "IsSitting"), XS_Client_IsSitting, file, "$"); - newXSproto(strcpy(buf, "IsCrouching"), XS_Client_IsCrouching, file, "$"); + newXSproto(strcpy(buf, "IsStanding"), XS_Client_IsStanding, file, "$"); newXSproto(strcpy(buf, "IsTaskActive"), XS_Client_IsTaskActive, file, "$$"); newXSproto(strcpy(buf, "IsTaskActivityActive"), XS_Client_IsTaskActivityActive, file, "$$$"); newXSproto(strcpy(buf, "IsTaskCompleted"), XS_Client_IsTaskCompleted, file, "$$"); newXSproto(strcpy(buf, "KeyRingAdd"), XS_Client_KeyRingAdd, file, "$$"); newXSproto(strcpy(buf, "KeyRingCheck"), XS_Client_KeyRingCheck, file, "$$"); newXSproto(strcpy(buf, "Kick"), XS_Client_Kick, file, "$"); + newXSproto(strcpy(buf, "LearnDisciplines"), XS_Client_LearnDisciplines, file, "$$$"); newXSproto(strcpy(buf, "LearnRecipe"), XS_Client_LearnRecipe, file, "$$"); newXSproto(strcpy(buf, "LeaveGroup"), XS_Client_LeaveGroup, file, "$"); newXSproto(strcpy(buf, "LoadZoneFlags"), XS_Client_LoadZoneFlags, file, "$"); newXSproto(strcpy(buf, "MarkCompassLoc"), XS_Client_MarkCompassLoc, file, "$$$$"); newXSproto(strcpy(buf, "MaxSkill"), XS_Client_MaxSkill, file, "$$;$$"); newXSproto(strcpy(buf, "MemSpell"), XS_Client_MemSpell, file, "$$$;$"); + newXSproto(strcpy(buf, "MemmedCount"), XS_Client_MemmedCount, file, "$"); newXSproto(strcpy(buf, "MovePC"), XS_Client_MovePC, file, "$$$$$$"); newXSproto(strcpy(buf, "MovePCDynamicZone"), XS_Client_MovePCDynamicZone, file, "$$;$$"); newXSproto(strcpy(buf, "MovePCInstance"), XS_Client_MovePCInstance, file, "$$$$$$$"); newXSproto(strcpy(buf, "MoveZone"), XS_Client_MoveZone, file, "$$"); newXSproto(strcpy(buf, "MoveZoneGroup"), XS_Client_MoveZoneGroup, file, "$$"); - newXSproto(strcpy(buf, "MoveZoneRaid"), XS_Client_MoveZoneRaid, file, "$$"); newXSproto(strcpy(buf, "MoveZoneInstance"), XS_Client_MoveZoneInstance, file, "$$"); newXSproto(strcpy(buf, "MoveZoneInstanceGroup"), XS_Client_MoveZoneInstanceGroup, file, "$$"); newXSproto(strcpy(buf, "MoveZoneInstanceRaid"), XS_Client_MoveZoneInstanceRaid, file, "$$"); + newXSproto(strcpy(buf, "MoveZoneRaid"), XS_Client_MoveZoneRaid, file, "$$"); newXSproto(strcpy(buf, "NPCSpawn"), XS_Client_NPCSpawn, file, "$$$;$"); + newXSproto(strcpy(buf, "NotifyNewTitlesAvailable"), XS_Client_NotifyNewTitlesAvailable, file, "$"); newXSproto(strcpy(buf, "NukeItem"), XS_Client_NukeItem, file, "$$;$"); newXSproto(strcpy(buf, "OpenLFGuildWindow"), XS_Client_OpenLFGuildWindow, file, "$"); - newXSproto(strcpy(buf, "NotifyNewTitlesAvailable"), XS_Client_NotifyNewTitlesAvailable, file, "$"); newXSproto(strcpy(buf, "PlayMP3"), XS_Client_PlayMP3, file, "$;$"); newXSproto(strcpy(buf, "Popup2"), XS_Client_Popup2, file, "$$$;$$$$$$$"); newXSproto(strcpy(buf, "QuestReward"), XS_Client_QuestReward, file, "$$;$$$$$$$"); newXSproto(strcpy(buf, "ReadBook"), XS_Client_ReadBook, file, "$$$"); + newXSproto(strcpy(buf, "ReadBookByName"), XS_Client_ReadBookByName, file, "$$$"); newXSproto(strcpy(buf, "RefundAA"), XS_Client_RefundAA, file, "$$"); newXSproto(strcpy(buf, "RemoveAllExpeditionLockouts"), XS_Client_RemoveAllExpeditionLockouts, file, "$;$"); newXSproto(strcpy(buf, "RemoveExpeditionLockout"), XS_Client_RemoveExpeditionLockout, file, "$$$"); + newXSproto(strcpy(buf, "RemoveItem"), XS_Client_RemoveItem, file, "$$;$"); + newXSproto(strcpy(buf, "RemoveLDoNLoss"), XS_Client_RemoveLDoNLoss, file, "$$"); + newXSproto(strcpy(buf, "RemoveLDoNWin"), XS_Client_RemoveLDoNWin, file, "$$"); newXSproto(strcpy(buf, "RemoveNoRent"), XS_Client_RemoveNoRent, file, "$"); newXSproto(strcpy(buf, "ResetAA"), XS_Client_ResetAA, file, "$"); + newXSproto(strcpy(buf, "ResetAllDisciplineTimers"), XS_Client_ResetAllDisciplineTimers, file, "$"); + newXSproto(strcpy(buf, "ResetAllCastbarCooldowns"), XS_Client_ResetAllCastbarCooldowns, file, "$"); + newXSproto(strcpy(buf, "ResetCastbarCooldownBySlot"), XS_Client_ResetCastbarCooldownBySlot, file, "$$"); + newXSproto(strcpy(buf, "ResetCastbarCooldownBySpellID"), XS_Client_ResetCastbarCooldownBySpellID, file, "$$"); newXSproto(strcpy(buf, "ResetDisciplineTimer"), XS_Client_ResetDisciplineTimer, file, "$$"); newXSproto(strcpy(buf, "ResetTrade"), XS_Client_ResetTrade, file, "$"); newXSproto(strcpy(buf, "Save"), XS_Client_Save, file, "$$"); newXSproto(strcpy(buf, "SaveBackup"), XS_Client_SaveBackup, file, "$"); newXSproto(strcpy(buf, "ScribeSpell"), XS_Client_ScribeSpell, file, "$$$;$"); + newXSproto(strcpy(buf, "ScribeSpells"), XS_Client_ScribeSpells, file, "$$$"); newXSproto(strcpy(buf, "SendColoredText"), XS_Client_SendColoredText, file, "$$$"); newXSproto(strcpy(buf, "SendMarqueeMessage"), XS_Client_SendMarqueeMessage, file, "$$$$$$$"); newXSproto(strcpy(buf, "SendOPTranslocateConfirm"), XS_Client_SendOPTranslocateConfirm, file, "$$$"); @@ -5491,68 +6226,75 @@ XS(boot_Client) { 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, "SendToInstance"), XS_Client_SendToInstance, file, "$$$$$$$$$$"); newXSproto(strcpy(buf, "SendWebLink"), XS_Client_SendWebLink, file, "$:$"); newXSproto(strcpy(buf, "SendZoneFlagInfo"), XS_Client_SendZoneFlagInfo, file, "$$"); + newXSproto(strcpy(buf, "SetAAEXPModifier"), XS_Client_SetAAEXPModifier, file, "$$$"); newXSproto(strcpy(buf, "SetAAPoints"), XS_Client_SetAAPoints, file, "$$"); newXSproto(strcpy(buf, "SetAATitle"), XS_Client_SetAATitle, file, "$$;$"); - newXSproto(strcpy(buf, "SetAccountFlag"), XS_Client_SetAccountFlag, file, "$$"); newXSproto(strcpy(buf, "SetAFK"), XS_Client_SetAFK, file, "$$"); - newXSproto(strcpy(buf, "SetAnon"), XS_Client_SetAnon, file, "$$"); + newXSproto(strcpy(buf, "SetAccountFlag"), XS_Client_SetAccountFlag, file, "$$"); newXSproto(strcpy(buf, "SetAlternateCurrencyValue"), XS_Client_SetAlternateCurrencyValue, file, "$$$"); + newXSproto(strcpy(buf, "SetAnon"), XS_Client_SetAnon, file, "$$"); newXSproto(strcpy(buf, "SetBaseClass"), XS_Client_SetBaseClass, file, "$$"); newXSproto(strcpy(buf, "SetBaseGender"), XS_Client_SetBaseGender, file, "$$"); newXSproto(strcpy(buf, "SetBaseRace"), XS_Client_SetBaseRace, file, "$$"); newXSproto(strcpy(buf, "SetBecomeNPC"), XS_Client_SetBecomeNPC, file, "$$"); newXSproto(strcpy(buf, "SetBecomeNPCLevel"), XS_Client_SetBecomeNPCLevel, file, "$$"); - newXSproto(strcpy(buf, "SetBindPoint"), XS_Client_SetBindPoint, file, "$;$$$$$"); - newXSproto(strcpy(buf, "SetConsumption"), XS_Client_SetConsumption, file, "$$$"); + newXSproto(strcpy(buf, "SetBindPoint"), XS_Client_SetBindPoint, file, "$;$$$$$$"); newXSproto(strcpy(buf, "SetClientMaxLevel"), XS_Client_SetClientMaxLevel, file, "$$"); + newXSproto(strcpy(buf, "SetConsumption"), XS_Client_SetConsumption, file, "$$$"); newXSproto(strcpy(buf, "SetCustomItemData"), XS_Client_SetCustomItemData, file, "$$$$"); newXSproto(strcpy(buf, "SetDeity"), XS_Client_SetDeity, file, "$$"); - newXSproto(strcpy(buf, "SetDueling"), XS_Client_SetDueling, file, "$$"); newXSproto(strcpy(buf, "SetDuelTarget"), XS_Client_SetDuelTarget, file, "$$"); + newXSproto(strcpy(buf, "SetDueling"), XS_Client_SetDueling, file, "$$"); + newXSproto(strcpy(buf, "SetEXP"), XS_Client_SetEXP, file, "$$$;$"); + newXSproto(strcpy(buf, "SetEXPModifier"), XS_Client_SetEXPModifier, file, "$$$"); newXSproto(strcpy(buf, "SetEbonCrystals"), XS_Client_SetEbonCrystals, file, "$$"); newXSproto(strcpy(buf, "SetEndurance"), XS_Client_SetEndurance, file, "$$"); - newXSproto(strcpy(buf, "SetEXP"), XS_Client_SetEXP, file, "$$$;$"); newXSproto(strcpy(buf, "SetFactionLevel"), XS_Client_SetFactionLevel, file, "$$$$$$"); newXSproto(strcpy(buf, "SetFactionLevel2"), XS_Client_SetFactionLevel2, file, "$$$$$$$"); newXSproto(strcpy(buf, "SetFeigned"), XS_Client_SetFeigned, file, "$$"); newXSproto(strcpy(buf, "SetGM"), XS_Client_SetGM, file, "$$"); + newXSproto(strcpy(buf, "SetGMStatus"), XS_Client_SetGMStatus, file, "$$"); + newXSproto(strcpy(buf, "SetHideMe"), XS_Client_SetHideMe, file, "$$"); newXSproto(strcpy(buf, "SetHorseId"), XS_Client_SetHorseId, file, "$$"); newXSproto(strcpy(buf, "SetHunger"), XS_Client_SetHunger, file, "$$"); + newXSproto(strcpy(buf, "SetIPExemption"), XS_Client_SetIPExemption, file, "$$"); newXSproto(strcpy(buf, "SetLanguageSkill"), XS_Client_SetLanguageSkill, file, "$$$"); newXSproto(strcpy(buf, "SetMaterial"), XS_Client_SetMaterial, file, "$$$"); - newXSproto(strcpy(buf, "SetPrimaryWeaponOrnamentation"), XS_Client_SetPrimaryWeaponOrnamentation, file, "$$"); newXSproto(strcpy(buf, "SetPVP"), XS_Client_SetPVP, file, "$$"); + newXSproto(strcpy(buf, "SetPrimaryWeaponOrnamentation"), XS_Client_SetPrimaryWeaponOrnamentation, file, "$$"); newXSproto(strcpy(buf, "SetRadiantCrystals"), XS_Client_SetRadiantCrystals, file, "$$"); newXSproto(strcpy(buf, "SetSecondaryWeaponOrnamentation"), XS_Client_SetSecondaryWeaponOrnamentation, file, "$$"); newXSproto(strcpy(buf, "SetSkill"), XS_Client_SetSkill, file, "$$$"); newXSproto(strcpy(buf, "SetSkillPoints"), XS_Client_SetSkillPoints, file, "$$"); - newXSproto(strcpy(buf, "SetStartZone"), XS_Client_SetStartZone, file, "$$"); + newXSproto(strcpy(buf, "SetStartZone"), XS_Client_SetStartZone, file, "$$;$$$$"); newXSproto(strcpy(buf, "SetStats"), XS_Client_SetStats, file, "$$$"); newXSproto(strcpy(buf, "SetThirst"), XS_Client_SetThirst, file, "$$"); newXSproto(strcpy(buf, "SetTint"), XS_Client_SetTint, file, "$$$"); newXSproto(strcpy(buf, "SetTitleSuffix"), XS_Client_SetTitleSuffix, file, "$$;$"); newXSproto(strcpy(buf, "SetZoneFlag"), XS_Client_SetZoneFlag, file, "$$"); newXSproto(strcpy(buf, "SilentMessage"), XS_Client_SilentMessage, file, "$$"); + newXSproto(strcpy(buf, "Sit"), XS_Client_Sit, file, "$"); newXSproto(strcpy(buf, "SlotConvert2"), XS_Client_SlotConvert2, file, "$$"); newXSproto(strcpy(buf, "Stand"), XS_Client_Stand, file, "$"); + newXSproto(strcpy(buf, "SummonBaggedItems"), XS_Client_SummonBaggedItems, file, "$$$"); newXSproto(strcpy(buf, "SummonItem"), XS_Client_SummonItem, file, "$$;$$$$$$$$"); - newXSproto(strcpy(buf, "TakeMoneyFromPP"), XS_Client_TakeMoneyFromPP, file, "$$;$"); newXSproto(strcpy(buf, "TGB"), XS_Client_TGB, file, "$"); + newXSproto(strcpy(buf, "TakeMoneyFromPP"), XS_Client_TakeMoneyFromPP, file, "$$;$"); newXSproto(strcpy(buf, "Thirsty"), XS_Client_Thirsty, file, "$"); newXSproto(strcpy(buf, "TrainDiscBySpellID"), XS_Client_TrainDiscBySpellID, file, "$$"); - newXSproto(strcpy(buf, "Undye"), XS_Client_Undye, file, "$"); newXSproto(strcpy(buf, "UnFreeze"), XS_Client_UnFreeze, file, "$"); + newXSproto(strcpy(buf, "Undye"), XS_Client_Undye, file, "$"); newXSproto(strcpy(buf, "UnmemSpell"), XS_Client_UnmemSpell, file, "$$;$"); newXSproto(strcpy(buf, "UnmemSpellAll"), XS_Client_UnmemSpellAll, file, "$;$"); newXSproto(strcpy(buf, "UnmemSpellBySpellID"), XS_Client_UnmemSpellBySpellID, file, "$$"); - newXSproto(strcpy(buf, "FindMemmedSpellBySlot"), XS_Client_FindMemmedSpellBySlot, file, "$$"); - newXSproto(strcpy(buf, "MemmedCount"), XS_Client_MemmedCount, file, "$"); newXSproto(strcpy(buf, "UnscribeSpell"), XS_Client_UnscribeSpell, file, "$$;$"); newXSproto(strcpy(buf, "UnscribeSpellAll"), XS_Client_UnscribeSpellAll, file, "$;$"); newXSproto(strcpy(buf, "UntrainDisc"), XS_Client_UntrainDisc, file, "$$;$"); newXSproto(strcpy(buf, "UntrainDiscAll"), XS_Client_UntrainDiscAll, file, "$;$"); + newXSproto(strcpy(buf, "UntrainDiscBySpellID"), XS_Client_UntrainDiscBySpellID, file, "$$;$"); newXSproto(strcpy(buf, "UpdateAdmin"), XS_Client_UpdateAdmin, file, "$;$"); newXSproto(strcpy(buf, "UpdateGroupAAs"), XS_Client_UpdateGroupAAs, file, "$$$"); newXSproto(strcpy(buf, "UpdateLDoNPoints"), XS_Client_UpdateLDoNPoints, file, "$$$"); diff --git a/zone/perl_doors.cpp b/zone/perl_doors.cpp index b922b5091..b92774edf 100644 --- a/zone/perl_doors.cpp +++ b/zone/perl_doors.cpp @@ -1,30 +1,3 @@ -/* -* This file was generated automatically by xsubpp version 1.9508 from the -* contents of tmp. Do not edit this file, edit tmp instead. -* -* ANY CHANGES MADE HERE WILL BE LOST! -* -*/ - - -/* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2004 EQEMu Development Team (http://eqemulator.net) - - 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 "../common/features.h" #ifdef EMBPERL_XS_CLASSES @@ -502,33 +475,33 @@ XS(boot_Doors) { //add the strcpy stuff to get rid of const warnings.... XS_VERSION_BOOTCHECK; + newXSproto(strcpy(buf, "CreateDatabaseEntry"), XS_Doors_CreateDatabaseEntry, file, "$"); + newXSproto(strcpy(buf, "GetDoorDBID"), XS_Doors_GetDoorDBID, file, "$"); + newXSproto(strcpy(buf, "GetDoorID"), XS_Doors_GetDoorID, file, "$"); + newXSproto(strcpy(buf, "GetHeading"), XS_Doors_GetHeading, file, "$"); newXSproto(strcpy(buf, "GetID"), XS_Doors_GetID, file, "$"); - newXSproto(strcpy(buf, "SetModelName"), XS_Doors_SetModelName, file, "$$"); + newXSproto(strcpy(buf, "GetIncline"), XS_Doors_GetIncline, file, "$"); + newXSproto(strcpy(buf, "GetKeyItem"), XS_Doors_GetKeyItem, file, "$"); + newXSproto(strcpy(buf, "GetLockPick"), XS_Doors_GetLockpick, file, "$"); newXSproto(strcpy(buf, "GetModelName"), XS_Doors_GetModelName, file, "$"); + newXSproto(strcpy(buf, "GetNoKeyring"), XS_Doors_GetNoKeyring, file, "$"); + newXSproto(strcpy(buf, "GetOpenType"), XS_Doors_GetOpenType, file, "$"); + newXSproto(strcpy(buf, "GetSize"), XS_Doors_GetSize, file, "$"); newXSproto(strcpy(buf, "GetX"), XS_Doors_GetX, file, "$"); newXSproto(strcpy(buf, "GetY"), XS_Doors_GetY, file, "$"); newXSproto(strcpy(buf, "GetZ"), XS_Doors_GetZ, file, "$"); - newXSproto(strcpy(buf, "GetHeading"), XS_Doors_GetHeading, file, "$"); + newXSproto(strcpy(buf, "SetHeading"), XS_Doors_SetHeading, file, "$$"); + newXSproto(strcpy(buf, "SetIncline"), XS_Doors_SetIncline, file, "$$"); + newXSproto(strcpy(buf, "SetKeyItem"), XS_Doors_SetKeyItem, file, "$$"); + newXSproto(strcpy(buf, "SetLocation"), XS_Doors_SetLocation, file, "$$$$"); + newXSproto(strcpy(buf, "SetLockPick"), XS_Doors_SetLockpick, file, "$$"); + newXSproto(strcpy(buf, "SetModelName"), XS_Doors_SetModelName, file, "$$"); + newXSproto(strcpy(buf, "SetNoKeyring"), XS_Doors_SetNoKeyring, file, "$$"); + newXSproto(strcpy(buf, "SetOpenType"), XS_Doors_SetOpenType, file, "$$"); + newXSproto(strcpy(buf, "SetSize"), XS_Doors_SetSize, file, "$$"); newXSproto(strcpy(buf, "SetX"), XS_Doors_SetX, file, "$$"); newXSproto(strcpy(buf, "SetY"), XS_Doors_SetY, file, "$$"); newXSproto(strcpy(buf, "SetZ"), XS_Doors_SetZ, file, "$$"); - newXSproto(strcpy(buf, "SetHeading"), XS_Doors_SetHeading, file, "$$"); - newXSproto(strcpy(buf, "SetLocation"), XS_Doors_SetLocation, file, "$$$$"); - newXSproto(strcpy(buf, "GetDoorDBID"), XS_Doors_GetDoorDBID, file, "$"); - newXSproto(strcpy(buf, "GetDoorID"), XS_Doors_GetDoorID, file, "$"); - newXSproto(strcpy(buf, "SetSize"), XS_Doors_SetSize, file, "$$"); - newXSproto(strcpy(buf, "GetSize"), XS_Doors_GetSize, file, "$"); - newXSproto(strcpy(buf, "SetIncline"), XS_Doors_SetIncline, file, "$$"); - newXSproto(strcpy(buf, "GetIncline"), XS_Doors_GetIncline, file, "$"); - newXSproto(strcpy(buf, "SetOpenType"), XS_Doors_SetOpenType, file, "$$"); - newXSproto(strcpy(buf, "GetOpenType"), XS_Doors_GetOpenType, file, "$"); - newXSproto(strcpy(buf, "SetLockPick"), XS_Doors_SetLockpick, file, "$$"); - newXSproto(strcpy(buf, "GetLockPick"), XS_Doors_GetLockpick, file, "$"); - newXSproto(strcpy(buf, "SetKeyItem"), XS_Doors_SetKeyItem, file, "$$"); - newXSproto(strcpy(buf, "GetKeyItem"), XS_Doors_GetKeyItem, file, "$"); - newXSproto(strcpy(buf, "SetNoKeyring"), XS_Doors_SetNoKeyring, file, "$$"); - newXSproto(strcpy(buf, "GetNoKeyring"), XS_Doors_GetNoKeyring, file, "$"); - newXSproto(strcpy(buf, "CreateDatabaseEntry"), XS_Doors_CreateDatabaseEntry, file, "$"); XSRETURN_YES; } #endif //EMBPERL_XS_CLASSES diff --git a/zone/perl_entity.cpp b/zone/perl_entity.cpp index c2b3d6a32..7dddf791b 100644 --- a/zone/perl_entity.cpp +++ b/zone/perl_entity.cpp @@ -1,30 +1,3 @@ -/* -* This file was generated automatically by xsubpp version 1.9508 from the -* contents of tmp. Do not edit this file, edit tmp instead. -* -* ANY CHANGES MADE HERE WILL BE LOST! -* -*/ - - -/* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2004 EQEMu Development Team (http://eqemulator.net) - - 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 "../common/features.h" #ifdef EMBPERL_XS_CLASSES @@ -128,7 +101,7 @@ XS(XS_EntityList_IsMobSpawnedByNpcTypeID); /* prototype pass -Wmissing-prototype XS(XS_EntityList_IsMobSpawnedByNpcTypeID) { dXSARGS; if (items != 2) - Perl_croak(aTHX_ "Usage: EntityList::ValidMobByNpcTypeID(THIS, get_id)"); // @categories Script Utility + Perl_croak(aTHX_ "Usage: EntityList::IsMobSpawnedByNpcTypeID(THIS, get_id)"); // @categories Script Utility { EntityList *THIS; bool RETVAL; @@ -883,7 +856,12 @@ XS(XS_EntityList_MessageStatus) { uint32 type = (uint32) SvUV(ST(3)); char *message = (char *) SvPV_nolen(ST(4)); VALIDATE_THIS_IS_ENTITY; - THIS->MessageStatus(to_guilddbid, to_minstatus, type, message); + THIS->MessageStatus( + to_guilddbid, + to_minstatus, + type, + message + ); } XSRETURN_EMPTY; } @@ -1062,12 +1040,12 @@ XS(XS_EntityList_DeleteNPCCorpses) { Perl_croak(aTHX_ "Usage: EntityList::DeleteNPCCorpses(THIS)"); // @categories Corpse { EntityList *THIS; - int32 RETVAL; + uint32 RETVAL; dXSTARG; VALIDATE_THIS_IS_ENTITY; RETVAL = THIS->DeleteNPCCorpses(); XSprePUSH; - PUSHi((IV) RETVAL); + PUSHu((UV) RETVAL); } XSRETURN(1); } @@ -1079,12 +1057,12 @@ XS(XS_EntityList_DeletePlayerCorpses) { Perl_croak(aTHX_ "Usage: EntityList::DeletePlayerCorpses(THIS)"); // @categories Account and Character, Corpse { EntityList *THIS; - int32 RETVAL; + uint32 RETVAL; dXSTARG; VALIDATE_THIS_IS_ENTITY; RETVAL = THIS->DeletePlayerCorpses(); XSprePUSH; - PUSHi((IV) RETVAL); + PUSHu((UV) RETVAL); } XSRETURN(1); } @@ -1310,6 +1288,64 @@ XS(XS_EntityList_GetClientList) { XSRETURN(num_clients); } +#ifdef BOTS +XS(XS_EntityList_GetBotByID); +XS(XS_EntityList_GetBotByID) { + dXSARGS; + int bot_count = 0; + if (items != 2) + Perl_croak(aTHX_ "Usage: EntityList::GetBotByID(THIS, uint32 bot_id)"); // @categories Script Utility, Bot + { + EntityList* THIS; + Bot* RETVAL; + uint32 bot_id = (uint32) SvUV(ST(1)); + VALIDATE_THIS_IS_ENTITY; + RETVAL = THIS->GetBotByBotID(bot_id); + ST(0) = sv_newmortal(); + sv_setref_pv(ST(0), "Bot", (void *) RETVAL); + } + XSRETURN(1); +} + +XS(XS_EntityList_GetBotByName); +XS(XS_EntityList_GetBotByName) { + dXSARGS; + int bot_count = 0; + if (items != 2) + Perl_croak(aTHX_ "Usage: EntityList::GetBotByName(THIS, string bot_name)"); // @categories Script Utility, Bot + { + EntityList* THIS; + Bot* RETVAL; + std::string bot_name = (std::string) SvPV_nolen(ST(1)); + VALIDATE_THIS_IS_ENTITY; + RETVAL = THIS->GetBotByBotName(bot_name); + ST(0) = sv_newmortal(); + sv_setref_pv(ST(0), "Bot", (void *) RETVAL); + } + XSRETURN(1); +} + +XS(XS_EntityList_GetBotList); +XS(XS_EntityList_GetBotList) { + dXSARGS; + int bot_count = 0; + if (items != 1) + Perl_croak(aTHX_ "Usage: EntityList::GetBotList(THIS)"); // @categories Script Utility, Bot + { + EntityList *THIS; + VALIDATE_THIS_IS_ENTITY; + auto current_bot_list = THIS->GetBotList(); + for (auto bot_iterator : current_bot_list) { + ST(0) = sv_newmortal(); + sv_setref_pv(ST(0), "Bot", (void *)bot_iterator); + XPUSHs(ST(0)); + bot_count++; + } + } + XSRETURN(bot_count); +} +#endif + XS(XS_EntityList_GetNPCList); /* prototype to pass -Wmissing-prototypes */ XS(XS_EntityList_GetNPCList) { dXSARGS; @@ -1440,83 +1476,85 @@ XS(boot_EntityList) { //add the strcpy stuff to get rid of const warnings.... - - XS_VERSION_BOOTCHECK; - - newXSproto(strcpy(buf, "GetMobID"), XS_EntityList_GetMobID, file, "$$"); - newXSproto(strcpy(buf, "GetMob"), XS_EntityList_GetMob, file, "$$"); - newXSproto(strcpy(buf, "GetMobByID"), XS_EntityList_GetMobByID, file, "$$"); - newXSproto(strcpy(buf, "GetMobByNpcTypeID"), XS_EntityList_GetMobByNpcTypeID, file, "$$"); - newXSproto(strcpy(buf, "IsMobSpawnedByNpcTypeID"), XS_EntityList_IsMobSpawnedByNpcTypeID, file, "$$"); - newXSproto(strcpy(buf, "GetNPCByID"), XS_EntityList_GetNPCByID, file, "$$"); - newXSproto(strcpy(buf, "GetNPCByNPCTypeID"), XS_EntityList_GetNPCByNPCTypeID, file, "$$"); - newXSproto(strcpy(buf, "GetNPCBySpawnID"), XS_EntityList_GetNPCBySpawnID, file, "$$"); - newXSproto(strcpy(buf, "GetClientByName"), XS_EntityList_GetClientByName, file, "$$"); + newXSproto(strcpy(buf, "CanAddHateForMob"), XS_EntityList_CanAddHateForMob, file, "$$"); + newXSproto(strcpy(buf, "Clear"), XS_EntityList_Clear, file, "$"); + newXSproto(strcpy(buf, "ClearClientPetitionQueue"), XS_EntityList_ClearClientPetitionQueue, file, "$"); + newXSproto(strcpy(buf, "ClearFeignAggro"), XS_EntityList_ClearFeignAggro, file, "$$"); + newXSproto(strcpy(buf, "DeleteNPCCorpses"), XS_EntityList_DeleteNPCCorpses, file, "$"); + newXSproto(strcpy(buf, "DeletePlayerCorpses"), XS_EntityList_DeletePlayerCorpses, file, "$"); + newXSproto(strcpy(buf, "DoubleAggro"), XS_EntityList_DoubleAggro, file, "$$"); + newXSproto(strcpy(buf, "Fighting"), XS_EntityList_Fighting, file, "$$"); + newXSproto(strcpy(buf, "FindDoor"), XS_EntityList_FindDoor, file, "$$"); +#ifdef BOTS + newXSproto(strcpy(buf, "GetBotByID"), XS_EntityList_GetBotByID, file, "$$"); + newXSproto(strcpy(buf, "GetBotByName"), XS_EntityList_GetBotByName, file, "$$"); + newXSproto(strcpy(buf, "GetBotList"), XS_EntityList_GetBotList, file, "$"); +#endif newXSproto(strcpy(buf, "GetClientByAccID"), XS_EntityList_GetClientByAccID, file, "$$"); - newXSproto(strcpy(buf, "GetClientByID"), XS_EntityList_GetClientByID, file, "$$"); newXSproto(strcpy(buf, "GetClientByCharID"), XS_EntityList_GetClientByCharID, file, "$$"); + newXSproto(strcpy(buf, "GetClientByID"), XS_EntityList_GetClientByID, file, "$$"); + newXSproto(strcpy(buf, "GetClientByName"), XS_EntityList_GetClientByName, file, "$$"); newXSproto(strcpy(buf, "GetClientByWID"), XS_EntityList_GetClientByWID, file, "$$"); - newXSproto(strcpy(buf, "GetObjectByID"), XS_EntityList_GetObjectByID, file, "$"); - newXSproto(strcpy(buf, "GetObjectByDBID"), XS_EntityList_GetObjectByDBID, file, "$"); - newXSproto(strcpy(buf, "GetDoorsByID"), XS_EntityList_GetDoorsByID, file, "$$"); + newXSproto(strcpy(buf, "GetClientList"), XS_EntityList_GetClientList, file, "$"); + newXSproto(strcpy(buf, "GetCorpseByID"), XS_EntityList_GetCorpseByID, file, "$$"); + newXSproto(strcpy(buf, "GetCorpseByName"), XS_EntityList_GetCorpseByName, file, "$$"); + newXSproto(strcpy(buf, "GetCorpseByOwner"), XS_EntityList_GetCorpseByOwner, file, "$$"); + newXSproto(strcpy(buf, "GetCorpseList"), XS_EntityList_GetCorpseList, file, "$"); newXSproto(strcpy(buf, "GetDoorsByDBID"), XS_EntityList_GetDoorsByDBID, file, "$$"); newXSproto(strcpy(buf, "GetDoorsByDoorID"), XS_EntityList_GetDoorsByDoorID, file, "$$"); - newXSproto(strcpy(buf, "FindDoor"), XS_EntityList_FindDoor, file, "$$"); - newXSproto(strcpy(buf, "GetGroupByMob"), XS_EntityList_GetGroupByMob, file, "$$"); + newXSproto(strcpy(buf, "GetDoorsByID"), XS_EntityList_GetDoorsByID, file, "$$"); + newXSproto(strcpy(buf, "GetDoorsList"), XS_EntityList_GetDoorsList, file, "$"); newXSproto(strcpy(buf, "GetGroupByClient"), XS_EntityList_GetGroupByClient, file, "$$"); newXSproto(strcpy(buf, "GetGroupByID"), XS_EntityList_GetGroupByID, file, "$$"); newXSproto(strcpy(buf, "GetGroupByLeaderName"), XS_EntityList_GetGroupByLeaderName, file, "$$"); - newXSproto(strcpy(buf, "GetRaidByID"), XS_EntityList_GetRaidByID, file, "$$"); + newXSproto(strcpy(buf, "GetGroupByMob"), XS_EntityList_GetGroupByMob, file, "$$"); + newXSproto(strcpy(buf, "GetMob"), XS_EntityList_GetMob, file, "$$"); + newXSproto(strcpy(buf, "GetMobByID"), XS_EntityList_GetMobByID, file, "$$"); + newXSproto(strcpy(buf, "GetMobByNpcTypeID"), XS_EntityList_GetMobByNpcTypeID, file, "$$"); + newXSproto(strcpy(buf, "GetMobID"), XS_EntityList_GetMobID, file, "$$"); + newXSproto(strcpy(buf, "GetMobList"), XS_EntityList_GetMobList, file, "$"); + newXSproto(strcpy(buf, "GetNPCByID"), XS_EntityList_GetNPCByID, file, "$$"); + newXSproto(strcpy(buf, "GetNPCByNPCTypeID"), XS_EntityList_GetNPCByNPCTypeID, file, "$$"); + newXSproto(strcpy(buf, "GetNPCBySpawnID"), XS_EntityList_GetNPCBySpawnID, file, "$$"); + newXSproto(strcpy(buf, "GetNPCList"), XS_EntityList_GetNPCList, file, "$"); + newXSproto(strcpy(buf, "GetObjectByDBID"), XS_EntityList_GetObjectByDBID, file, "$"); + newXSproto(strcpy(buf, "GetObjectByID"), XS_EntityList_GetObjectByID, file, "$"); + newXSproto(strcpy(buf, "GetObjectList"), XS_EntityList_GetObjectList, file, "$"); newXSproto(strcpy(buf, "GetRaidByClient"), XS_EntityList_GetRaidByClient, file, "$$"); - newXSproto(strcpy(buf, "GetCorpseByOwner"), XS_EntityList_GetCorpseByOwner, file, "$$"); - newXSproto(strcpy(buf, "GetCorpseByID"), XS_EntityList_GetCorpseByID, file, "$$"); - newXSproto(strcpy(buf, "GetCorpseByName"), XS_EntityList_GetCorpseByName, file, "$$"); - newXSproto(strcpy(buf, "ClearClientPetitionQueue"), XS_EntityList_ClearClientPetitionQueue, file, "$"); - newXSproto(strcpy(buf, "CanAddHateForMob"), XS_EntityList_CanAddHateForMob, file, "$$"); - newXSproto(strcpy(buf, "Clear"), XS_EntityList_Clear, file, "$"); - newXSproto(strcpy(buf, "RemoveMob"), XS_EntityList_RemoveMob, file, "$$"); - newXSproto(strcpy(buf, "RemoveClient"), XS_EntityList_RemoveClient, file, "$$"); - newXSproto(strcpy(buf, "RemoveNPC"), XS_EntityList_RemoveNPC, file, "$$"); - newXSproto(strcpy(buf, "RemoveGroup"), XS_EntityList_RemoveGroup, file, "$$"); - newXSproto(strcpy(buf, "RemoveCorpse"), XS_EntityList_RemoveCorpse, file, "$$"); - newXSproto(strcpy(buf, "RemoveDoor"), XS_EntityList_RemoveDoor, file, "$$"); - newXSproto(strcpy(buf, "RemoveTrap"), XS_EntityList_RemoveTrap, file, "$$"); - newXSproto(strcpy(buf, "RemoveObject"), XS_EntityList_RemoveObject, file, "$$"); - newXSproto(strcpy(buf, "RemoveAllMobs"), XS_EntityList_RemoveAllMobs, file, "$"); + newXSproto(strcpy(buf, "GetRaidByID"), XS_EntityList_GetRaidByID, file, "$$"); + newXSproto(strcpy(buf, "GetRandomClient"), XS_EntityList_GetRandomClient, file, "$$$$$;$"); + newXSproto(strcpy(buf, "HalveAggro"), XS_EntityList_HalveAggro, file, "$$"); + newXSproto(strcpy(buf, "IsMobSpawnedByNpcTypeID"), XS_EntityList_IsMobSpawnedByNpcTypeID, file, "$$"); + newXSproto(strcpy(buf, "MakeNameUnique"), XS_EntityList_MakeNameUnique, file, "$$"); + newXSproto(strcpy(buf, "Message"), XS_EntityList_Message, file, "$$$$;@"); + newXSproto(strcpy(buf, "MessageClose"), XS_EntityList_MessageClose, file, "$$$$$$;@"); + newXSproto(strcpy(buf, "MessageGroup"), XS_EntityList_MessageGroup, file, "$$$$$;@"); + newXSproto(strcpy(buf, "MessageStatus"), XS_EntityList_MessageStatus, file, "$$$$$;@"); + newXSproto(strcpy(buf, "OpenDoorsNear"), XS_EntityList_OpenDoorsNear, file, "$$"); newXSproto(strcpy(buf, "RemoveAllClients"), XS_EntityList_RemoveAllClients, file, "$"); - newXSproto(strcpy(buf, "RemoveAllNPCs"), XS_EntityList_RemoveAllNPCs, file, "$"); - newXSproto(strcpy(buf, "RemoveAllGroups"), XS_EntityList_RemoveAllGroups, file, "$"); newXSproto(strcpy(buf, "RemoveAllCorpses"), XS_EntityList_RemoveAllCorpses, file, "$"); newXSproto(strcpy(buf, "RemoveAllDoors"), XS_EntityList_RemoveAllDoors, file, "$"); - newXSproto(strcpy(buf, "RemoveAllTraps"), XS_EntityList_RemoveAllTraps, file, "$"); + newXSproto(strcpy(buf, "RemoveAllGroups"), XS_EntityList_RemoveAllGroups, file, "$"); + newXSproto(strcpy(buf, "RemoveAllMobs"), XS_EntityList_RemoveAllMobs, file, "$"); + newXSproto(strcpy(buf, "RemoveAllNPCs"), XS_EntityList_RemoveAllNPCs, file, "$"); newXSproto(strcpy(buf, "RemoveAllObjects"), XS_EntityList_RemoveAllObjects, file, "$"); - newXSproto(strcpy(buf, "Message"), XS_EntityList_Message, file, "$$$$;@"); - newXSproto(strcpy(buf, "MessageStatus"), XS_EntityList_MessageStatus, file, "$$$$$;@"); - newXSproto(strcpy(buf, "MessageClose"), XS_EntityList_MessageClose, file, "$$$$$$;@"); - newXSproto(strcpy(buf, "RemoveFromTargets"), XS_EntityList_RemoveFromTargets, file, "$$"); - newXSproto(strcpy(buf, "ReplaceWithTarget"), XS_EntityList_ReplaceWithTarget, file, "$$$"); - newXSproto(strcpy(buf, "OpenDoorsNear"), XS_EntityList_OpenDoorsNear, file, "$$"); - newXSproto(strcpy(buf, "MakeNameUnique"), XS_EntityList_MakeNameUnique, file, "$$"); - newXSproto(strcpy(buf, "RemoveNumbers"), XS_EntityList_RemoveNumbers, file, "$$"); - newXSproto(strcpy(buf, "SignalMobsByNPCID"), XS_EntityList_SignalMobsByNPCID, file, "$$$"); + newXSproto(strcpy(buf, "RemoveAllTraps"), XS_EntityList_RemoveAllTraps, file, "$"); + newXSproto(strcpy(buf, "RemoveClient"), XS_EntityList_RemoveClient, file, "$$"); + newXSproto(strcpy(buf, "RemoveCorpse"), XS_EntityList_RemoveCorpse, file, "$$"); + newXSproto(strcpy(buf, "RemoveDoor"), XS_EntityList_RemoveDoor, file, "$$"); newXSproto(strcpy(buf, "RemoveEntity"), XS_EntityList_RemoveEntity, file, "$$"); - newXSproto(strcpy(buf, "DeleteNPCCorpses"), XS_EntityList_DeleteNPCCorpses, file, "$"); - newXSproto(strcpy(buf, "DeletePlayerCorpses"), XS_EntityList_DeletePlayerCorpses, file, "$"); - newXSproto(strcpy(buf, "HalveAggro"), XS_EntityList_HalveAggro, file, "$$"); - newXSproto(strcpy(buf, "DoubleAggro"), XS_EntityList_DoubleAggro, file, "$$"); - newXSproto(strcpy(buf, "ClearFeignAggro"), XS_EntityList_ClearFeignAggro, file, "$$"); - newXSproto(strcpy(buf, "Fighting"), XS_EntityList_Fighting, file, "$$"); newXSproto(strcpy(buf, "RemoveFromHateLists"), XS_EntityList_RemoveFromHateLists, file, "$$;$"); - newXSproto(strcpy(buf, "MessageGroup"), XS_EntityList_MessageGroup, file, "$$$$$;@"); - newXSproto(strcpy(buf, "GetRandomClient"), XS_EntityList_GetRandomClient, file, "$$$$$;$"); - newXSproto(strcpy(buf, "GetMobList"), XS_EntityList_GetMobList, file, "$"); - newXSproto(strcpy(buf, "GetClientList"), XS_EntityList_GetClientList, file, "$"); - newXSproto(strcpy(buf, "GetNPCList"), XS_EntityList_GetNPCList, file, "$"); - newXSproto(strcpy(buf, "GetCorpseList"), XS_EntityList_GetCorpseList, file, "$"); - newXSproto(strcpy(buf, "GetObjectList"), XS_EntityList_GetObjectList, file, "$"); - newXSproto(strcpy(buf, "GetDoorsList"), XS_EntityList_GetDoorsList, file, "$"); + newXSproto(strcpy(buf, "RemoveFromTargets"), XS_EntityList_RemoveFromTargets, file, "$$"); + newXSproto(strcpy(buf, "RemoveGroup"), XS_EntityList_RemoveGroup, file, "$$"); + newXSproto(strcpy(buf, "RemoveMob"), XS_EntityList_RemoveMob, file, "$$"); + newXSproto(strcpy(buf, "RemoveNPC"), XS_EntityList_RemoveNPC, file, "$$"); + newXSproto(strcpy(buf, "RemoveNumbers"), XS_EntityList_RemoveNumbers, file, "$$"); + newXSproto(strcpy(buf, "RemoveObject"), XS_EntityList_RemoveObject, file, "$$"); + newXSproto(strcpy(buf, "RemoveTrap"), XS_EntityList_RemoveTrap, file, "$$"); + newXSproto(strcpy(buf, "ReplaceWithTarget"), XS_EntityList_ReplaceWithTarget, file, "$$$"); newXSproto(strcpy(buf, "SignalAllClients"), XS_EntityList_SignalAllClients, file, "$$"); + newXSproto(strcpy(buf, "SignalMobsByNPCID"), XS_EntityList_SignalMobsByNPCID, file, "$$$"); XSRETURN_YES; } diff --git a/zone/perl_expedition.cpp b/zone/perl_expedition.cpp index e7b477636..69cf7ac95 100644 --- a/zone/perl_expedition.cpp +++ b/zone/perl_expedition.cpp @@ -1,23 +1,3 @@ -/** - * 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 "../common/features.h" #ifdef EMBPERL_XS_CLASSES @@ -142,7 +122,7 @@ XS(XS_Expedition_GetDynamicZoneID) { Expedition* THIS = nullptr; VALIDATE_THIS_IS_EXPEDITION; - XSRETURN_UV(THIS->GetDynamicZoneID()); + XSRETURN_UV(THIS->GetDynamicZone()->GetID()); } XS(XS_Expedition_GetID); @@ -168,7 +148,7 @@ XS(XS_Expedition_GetInstanceID) { Expedition* THIS = nullptr; VALIDATE_THIS_IS_EXPEDITION; - XSRETURN_UV(THIS->GetInstanceID()); + XSRETURN_UV(THIS->GetDynamicZone()->GetInstanceID()); } XS(XS_Expedition_GetLeaderName); @@ -247,7 +227,7 @@ XS(XS_Expedition_GetMemberCount) { Expedition* THIS = nullptr; VALIDATE_THIS_IS_EXPEDITION; - XSRETURN_UV(THIS->GetMemberCount()); + XSRETURN_UV(THIS->GetDynamicZone()->GetMemberCount()); } XS(XS_Expedition_GetMembers); @@ -262,11 +242,10 @@ XS(XS_Expedition_GetMembers) { HV* hash = newHV(); - auto members = THIS->GetMembers(); - for (const auto& member : members) + for (const auto& member : THIS->GetDynamicZone()->GetMembers()) { hv_store(hash, member.name.c_str(), static_cast(member.name.size()), - newSVuv(member.char_id), 0); + newSVuv(member.id), 0); } ST(0) = sv_2mortal(newRV_noinc((SV*)hash)); @@ -296,7 +275,7 @@ XS(XS_Expedition_GetSecondsRemaining) { Expedition* THIS = nullptr; VALIDATE_THIS_IS_EXPEDITION; - XSRETURN_UV(THIS->GetDynamicZone().GetSecondsRemaining()); + XSRETURN_UV(THIS->GetDynamicZone()->GetSecondsRemaining()); } XS(XS_Expedition_GetUUID); @@ -309,7 +288,7 @@ XS(XS_Expedition_GetUUID) { Expedition* THIS = nullptr; VALIDATE_THIS_IS_EXPEDITION; - XSRETURN_PV(THIS->GetUUID().c_str()); + XSRETURN_PV(THIS->GetDynamicZone()->GetUUID().c_str()); } XS(XS_Expedition_GetZoneID); @@ -322,7 +301,7 @@ XS(XS_Expedition_GetZoneID) { Expedition* THIS = nullptr; VALIDATE_THIS_IS_EXPEDITION; - XSRETURN_UV(THIS->GetDynamicZone().GetZoneID()); + XSRETURN_UV(THIS->GetDynamicZone()->GetZoneID()); } XS(XS_Expedition_GetZoneName); @@ -335,7 +314,7 @@ XS(XS_Expedition_GetZoneName) { Expedition* THIS = nullptr; VALIDATE_THIS_IS_EXPEDITION; - XSRETURN_PV(ZoneName(THIS->GetDynamicZone().GetZoneID())); + XSRETURN_PV(ZoneName(THIS->GetDynamicZone()->GetZoneID())); } XS(XS_Expedition_GetZoneVersion); @@ -348,7 +327,7 @@ XS(XS_Expedition_GetZoneVersion) { Expedition* THIS = nullptr; VALIDATE_THIS_IS_EXPEDITION; - XSRETURN_UV(THIS->GetDynamicZone().GetZoneVersion()); + XSRETURN_UV(THIS->GetDynamicZone()->GetZoneVersion()); } XS(XS_Expedition_HasLockout); @@ -407,7 +386,7 @@ XS(XS_Expedition_RemoveCompass) { Expedition* THIS = nullptr; VALIDATE_THIS_IS_EXPEDITION; - THIS->SetDzCompass(0, 0, 0, 0, true); + THIS->GetDynamicZone()->SetCompass(0, 0, 0, 0, true); XSRETURN_EMPTY; } @@ -446,12 +425,12 @@ XS(XS_Expedition_SetCompass) { if (SvTYPE(ST(1)) == SVt_PV) { std::string zone_name(SvPV_nolen(ST(1))); - THIS->SetDzCompass(zone_name, x, y, z, true); + THIS->GetDynamicZone()->SetCompass(ZoneID(zone_name), x, y, z, true); } else if (SvTYPE(ST(1)) == SVt_IV) { uint32_t zone_id = static_cast(SvUV(ST(1))); - THIS->SetDzCompass(zone_id, x, y, z, true); + THIS->GetDynamicZone()->SetCompass(zone_id, x, y, z, true); } else { @@ -556,12 +535,12 @@ XS(XS_Expedition_SetSafeReturn) { if (SvTYPE(ST(1)) == SVt_PV) { std::string zone_name(SvPV_nolen(ST(1))); - THIS->SetDzSafeReturn(zone_name, x, y, z, heading, true); + THIS->GetDynamicZone()->SetSafeReturn(ZoneID(zone_name), x, y, z, heading, true); } else if (SvTYPE(ST(1)) == SVt_IV) { uint32_t zone_id = static_cast(SvUV(ST(1))); - THIS->SetDzSafeReturn(zone_id, x, y, z, heading, true); + THIS->GetDynamicZone()->SetSafeReturn(zone_id, x, y, z, heading, true); } else { @@ -582,7 +561,7 @@ XS(XS_Expedition_SetSecondsRemaining) { VALIDATE_THIS_IS_EXPEDITION; uint32_t seconds_remaining = static_cast(SvUV(ST(1))); - THIS->SetDzSecondsRemaining(seconds_remaining); + THIS->GetDynamicZone()->SetSecondsRemaining(seconds_remaining); XSRETURN_EMPTY; } @@ -602,7 +581,7 @@ XS(XS_Expedition_SetZoneInLocation) { float z = static_cast(SvNV(ST(3))); float heading = static_cast(SvNV(ST(4))); - THIS->SetDzZoneInLocation(x, y, z, heading, true); + THIS->GetDynamicZone()->SetZoneInLocation(x, y, z, heading, true); XSRETURN_EMPTY; } diff --git a/zone/perl_groups.cpp b/zone/perl_groups.cpp index 70c321a21..d40f9578b 100644 --- a/zone/perl_groups.cpp +++ b/zone/perl_groups.cpp @@ -1,30 +1,3 @@ -/* -* This file was generated automatically by xsubpp version 1.9508 from the -* contents of tmp. Do not edit this file, edit tmp instead. -* -* ANY CHANGES MADE HERE WILL BE LOST! -* -*/ - - -/* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2004 EQEMu Development Team (http://eqemulator.net) - - 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 "../common/features.h" #ifdef EMBPERL_XS_CLASSES @@ -479,26 +452,25 @@ XS(boot_Group) { //add the strcpy stuff to get rid of const warnings.... XS_VERSION_BOOTCHECK; - - newXSproto(strcpy(buf, "DisbandGroup"), XS_Group_DisbandGroup, file, "$"); - newXSproto(strcpy(buf, "IsGroupMember"), XS_Group_IsGroupMember, file, "$$"); newXSproto(strcpy(buf, "CastGroupSpell"), XS_Group_CastGroupSpell, file, "$$$"); - newXSproto(strcpy(buf, "SplitExp"), XS_Group_SplitExp, file, "$$$"); - newXSproto(strcpy(buf, "GroupMessage"), XS_Group_GroupMessage, file, "$$$"); - newXSproto(strcpy(buf, "GetTotalGroupDamage"), XS_Group_GetTotalGroupDamage, file, "$$"); - newXSproto(strcpy(buf, "SplitMoney"), XS_Group_SplitMoney, file, "$$$$$"); - newXSproto(strcpy(buf, "SetLeader"), XS_Group_SetLeader, file, "$$"); + newXSproto(strcpy(buf, "DisbandGroup"), XS_Group_DisbandGroup, file, "$"); + newXSproto(strcpy(buf, "DoesAnyMemberHaveExpeditionLockout"), XS_Group_DoesAnyMemberHaveExpeditionLockout, file, "$$$;$"); + newXSproto(strcpy(buf, "GetHighestLevel"), XS_Group_GetHighestLevel, file, "$"); + newXSproto(strcpy(buf, "GetID"), XS_Group_GetID, file, "$"); newXSproto(strcpy(buf, "GetLeader"), XS_Group_GetLeader, file, "$"); newXSproto(strcpy(buf, "GetLeaderName"), XS_Group_GetLeaderName, file, "$"); - newXSproto(strcpy(buf, "SendHPPacketsTo"), XS_Group_SendHPPacketsTo, file, "$$"); - newXSproto(strcpy(buf, "SendHPPacketsFrom"), XS_Group_SendHPPacketsFrom, file, "$$"); - newXSproto(strcpy(buf, "IsLeader"), XS_Group_IsLeader, file, "$$"); - newXSproto(strcpy(buf, "GroupCount"), XS_Group_GroupCount, file, "$"); - newXSproto(strcpy(buf, "GetHighestLevel"), XS_Group_GetHighestLevel, file, "$"); - newXSproto(strcpy(buf, "TeleportGroup"), XS_Group_TeleportGroup, file, "$$$$$$$"); - newXSproto(strcpy(buf, "GetID"), XS_Group_GetID, file, "$"); newXSproto(strcpy(buf, "GetMember"), XS_Group_GetMember, file, "$$"); - newXSproto(strcpy(buf, "DoesAnyMemberHaveExpeditionLockout"), XS_Group_DoesAnyMemberHaveExpeditionLockout, file, "$$$;$"); + newXSproto(strcpy(buf, "GetTotalGroupDamage"), XS_Group_GetTotalGroupDamage, file, "$$"); + newXSproto(strcpy(buf, "GroupCount"), XS_Group_GroupCount, file, "$"); + newXSproto(strcpy(buf, "GroupMessage"), XS_Group_GroupMessage, file, "$$$"); + newXSproto(strcpy(buf, "IsGroupMember"), XS_Group_IsGroupMember, file, "$$"); + newXSproto(strcpy(buf, "IsLeader"), XS_Group_IsLeader, file, "$$"); + newXSproto(strcpy(buf, "SendHPPacketsFrom"), XS_Group_SendHPPacketsFrom, file, "$$"); + newXSproto(strcpy(buf, "SendHPPacketsTo"), XS_Group_SendHPPacketsTo, file, "$$"); + newXSproto(strcpy(buf, "SetLeader"), XS_Group_SetLeader, file, "$$"); + newXSproto(strcpy(buf, "SplitExp"), XS_Group_SplitExp, file, "$$$"); + newXSproto(strcpy(buf, "SplitMoney"), XS_Group_SplitMoney, file, "$$$$$"); + newXSproto(strcpy(buf, "TeleportGroup"), XS_Group_TeleportGroup, file, "$$$$$$$"); XSRETURN_YES; } diff --git a/zone/perl_hateentry.cpp b/zone/perl_hateentry.cpp index cd98648fa..65ecd41ac 100644 --- a/zone/perl_hateentry.cpp +++ b/zone/perl_hateentry.cpp @@ -1,21 +1,3 @@ -/* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2009 EQEMu Development Team (http://eqemulator.net) - - 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 "../common/features.h" #include "client.h" @@ -52,7 +34,7 @@ XS(XS_HateEntry_GetEnt); /* prototype to pass -Wmissing-prototypes */ XS(XS_HateEntry_GetEnt) { dXSARGS; if (items != 1) - Perl_croak(aTHX_ "Usage: HateEntry::GetData(THIS)"); // @categories Script Utility, Hate and Aggro + Perl_croak(aTHX_ "Usage: HateEntry::GetEnt(THIS)"); // @categories Script Utility, Hate and Aggro { struct_HateList *THIS; Mob *RETVAL; @@ -116,11 +98,9 @@ XS(boot_HateEntry) { //add the strcpy stuff to get rid of const warnings.... XS_VERSION_BOOTCHECK; - - newXSproto(strcpy(buf, "GetEnt"), XS_HateEntry_GetEnt, file, "$"); newXSproto(strcpy(buf, "GetDamage"), XS_HateEntry_GetDamage, file, "$"); + newXSproto(strcpy(buf, "GetEnt"), XS_HateEntry_GetEnt, file, "$"); newXSproto(strcpy(buf, "GetHate"), XS_HateEntry_GetHate, file, "$"); - XSRETURN_YES; } diff --git a/zone/perl_inventory.cpp b/zone/perl_inventory.cpp index 56452873c..ad3cfc08a 100644 --- a/zone/perl_inventory.cpp +++ b/zone/perl_inventory.cpp @@ -1,30 +1,3 @@ -/* -* This file was generated automatically by xsubpp version 1.9508 from the -* contents of tmp. Do not edit this file, edit tmp instead. -* -* ANY CHANGES MADE HERE WILL BE LOST! -* -*/ - - -/* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2004 EQEMu Development Team (http://eqemulator.net) - - 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 "../common/features.h" #include "client.h" @@ -441,6 +414,76 @@ XS(XS_Inventory_PutItem) { XSRETURN(1); } +XS(XS_Inventory_HasAugmentEquippedByID); +XS(XS_Inventory_HasAugmentEquippedByID) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Inventory::HasAugmentEquippedByID(THIS, uint32 item_id)"); + { + EQ::InventoryProfile* THIS; + bool has_equipped = false; + uint32 item_id = (uint32) SvUV(ST(1)); + VALIDATE_THIS_IS_INVENTORY; + has_equipped = THIS->HasAugmentEquippedByID(item_id); + ST(0) = boolSV(has_equipped); + sv_2mortal(ST(0)); + } + XSRETURN(1); +} + +XS(XS_Inventory_CountAugmentEquippedByID); +XS(XS_Inventory_CountAugmentEquippedByID) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Inventory::CountAugmentEquippedByID(THIS, uint32 item_id)"); + { + EQ::InventoryProfile* THIS; + int quantity = 0; + uint32 item_id = (uint32) SvUV(ST(1)); + dXSTARG; + VALIDATE_THIS_IS_INVENTORY; + quantity = THIS->CountAugmentEquippedByID(item_id); + XSprePUSH; + PUSHi((IV)quantity); + } + XSRETURN(1); +} + +XS(XS_Inventory_HasItemEquippedByID); +XS(XS_Inventory_HasItemEquippedByID) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Inventory::HasItemEquippedByID(THIS, uint32 item_id)"); + { + EQ::InventoryProfile* THIS; + bool has_equipped = false; + uint32 item_id = (uint32) SvUV(ST(1)); + VALIDATE_THIS_IS_INVENTORY; + has_equipped = THIS->HasItemEquippedByID(item_id); + ST(0) = boolSV(has_equipped); + sv_2mortal(ST(0)); + } + XSRETURN(1); +} + +XS(XS_Inventory_CountItemEquippedByID); +XS(XS_Inventory_CountItemEquippedByID) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Inventory::CountItemEquippedByID(THIS, uint32 item_id)"); + { + EQ::InventoryProfile* THIS; + int quantity = 0; + uint32 item_id = (uint32) SvUV(ST(1)); + dXSTARG; + VALIDATE_THIS_IS_INVENTORY; + quantity = THIS->CountItemEquippedByID(item_id); + XSprePUSH; + PUSHi((IV)quantity); + } + XSRETURN(1); +} + #ifdef __cplusplus extern "C" #endif @@ -457,6 +500,8 @@ XS(boot_Inventory) { char buf[128]; XS_VERSION_BOOTCHECK; newXSproto(strcpy(buf, "CanItemFitInContainer"), XS_Inventory_CanItemFitInContainer, file, "$$$"); + newXSproto(strcpy(buf, "CountAugmentEquippedByID"), XS_Inventory_CountAugmentEquippedByID, file, "$$"); + newXSproto(strcpy(buf, "CountItemEquippedByID"), XS_Inventory_CountItemEquippedByID, file, "$$"); newXSproto(strcpy(buf, "CheckNoDrop"), XS_Inventory_CheckNoDrop, file, "$$"); newXSproto(strcpy(buf, "DeleteItem"), XS_Inventory_DeleteItem, file, "$$;$"); newXSproto(strcpy(buf, "FindFreeSlot"), XS_Inventory_FindFreeSlot, file, "$$$;$$"); @@ -466,9 +511,11 @@ XS(boot_Inventory) { newXSproto(strcpy(buf, "GetSlotByItemInst"), XS_Inventory_GetSlotByItemInst, file, "$$"); newXSproto(strcpy(buf, "GetSlotFromMaterial"), XS_Inventory_GetSlotFromMaterial, file, "$$"); newXSproto(strcpy(buf, "GetSlotID"), XS_Inventory_GetSlotID, file, "$$;$"); + newXSproto(strcpy(buf, "HasAugmentEquippedByID"), XS_Inventory_HasAugmentEquippedByID, file, "$$"); newXSproto(strcpy(buf, "HasItem"), XS_Inventory_HasItem, file, "$$;$$"); newXSproto(strcpy(buf, "HasItemByLoreGroup"), XS_Inventory_HasItemByLoreGroup, file, "$$;$"); newXSproto(strcpy(buf, "HasItemByUse"), XS_Inventory_HasItemByUse, file, "$$;$$"); + newXSproto(strcpy(buf, "HasItemEquippedByID"), XS_Inventory_HasItemEquippedByID, file, "$$"); newXSproto(strcpy(buf, "HasSpaceForItem"), XS_Inventory_HasSpaceForItem, file, "$$$"); newXSproto(strcpy(buf, "PopItem"), XS_Inventory_PopItem, file, "$$"); newXSproto(strcpy(buf, "PushCursor"), XS_Inventory_PushCursor, file, "$$"); diff --git a/zone/perl_mob.cpp b/zone/perl_mob.cpp index f5dc1067a..efa71d2dc 100644 --- a/zone/perl_mob.cpp +++ b/zone/perl_mob.cpp @@ -1,30 +1,3 @@ -/* -* This file was generated automatically by xsubpp version 1.9508 from the -* contents of tmp. Do not edit this file, edit tmp instead. -* -* ANY CHANGES MADE HERE WILL BE LOST! -* -*/ - - -/* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2004 EQEMu Development Team (http://eqemulator.net) - - 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 "../common/features.h" #ifdef EMBPERL_XS_CLASSES @@ -41,6 +14,11 @@ typedef const char Const_char; #include "mob.h" #include "client.h" #include "../common/spdat.h" +#include "dialogue_window.h" + +#ifdef BOTS +#include "bot.h" +#endif #ifdef THIS /* this macro seems to leak out on some systems */ #undef THIS @@ -985,7 +963,7 @@ XS(XS_Mob_BuffCount) { VALIDATE_THIS_IS_MOB; RETVAL = THIS->BuffCount(); XSprePUSH; - PUSHu((UV) RETVAL); + PUSHu((UV) RETVAL); } XSRETURN(1); } @@ -2616,7 +2594,18 @@ XS(XS_Mob_Message) { uint32 type = (uint32) SvUV(ST(1)); char *message = (char *) SvPV_nolen(ST(2)); VALIDATE_THIS_IS_MOB; - THIS->Message(type, message); + + if (RuleB(Chat, QuestDialogueUsesDialogueWindow) && THIS->IsClient()) { + std::string window_markdown = message; + DialogueWindow::Render(THIS->CastToClient(), window_markdown); + } + else if (RuleB(Chat, AutoInjectSaylinksToClientMessage)) { + std::string new_message = EQ::SayLinkEngine::InjectSaylinksIfNotExist(message); + THIS->Message(type, new_message.c_str()); + } + else { + THIS->Message(type, message); + } } XSRETURN_EMPTY; } @@ -2784,7 +2773,7 @@ XS(XS_Mob_SpellFinished) { if (items > 4) { resist_diff = (int16) SvUV(ST(4)); } else { - resist_diff = spells[spell_id].ResistDiff; + resist_diff = spells[spell_id].resist_difficulty; } THIS->SpellFinished(spell_id, spell_target, EQ::spells::CastingSlot::Item, mana_cost, -1, resist_diff); @@ -2839,7 +2828,7 @@ XS(XS_Mob_BuffFadeByEffect) { Perl_croak(aTHX_ "Usage: Mob::BuffFadeByEffect(THIS, int effect_id, int skip_slot = -1)"); // @categories Script Utility, Spells and Disciplines { Mob *THIS; - int effectid = (int) SvIV(ST(1)); + int effect_id = (int) SvIV(ST(1)); int skipslot; VALIDATE_THIS_IS_MOB; if (items < 3) @@ -2848,7 +2837,7 @@ XS(XS_Mob_BuffFadeByEffect) { skipslot = (int) SvIV(ST(2)); } - THIS->BuffFadeByEffect(effectid, skipslot); + THIS->BuffFadeByEffect(effect_id, skipslot); } XSRETURN_EMPTY; } @@ -4175,44 +4164,6 @@ XS(XS_Mob_GetResist) { XSRETURN(1); } -XS(XS_Mob_GetShieldTarget); /* prototype to pass -Wmissing-prototypes */ -XS(XS_Mob_GetShieldTarget) { - dXSARGS; - if (items != 1) - Perl_croak(aTHX_ "Usage: Mob::GetShieldTarget(THIS)"); // @categories Script Utility - { - Mob *THIS; - Mob *RETVAL; - VALIDATE_THIS_IS_MOB; - RETVAL = THIS->GetShieldTarget(); - ST(0) = sv_newmortal(); - sv_setref_pv(ST(0), "Mob", (void *) RETVAL); - } - XSRETURN(1); -} - -XS(XS_Mob_SetShieldTarget); /* prototype to pass -Wmissing-prototypes */ -XS(XS_Mob_SetShieldTarget) { - dXSARGS; - if (items != 2) - Perl_croak(aTHX_ "Usage: Mob::SetShieldTarget(THIS, mob)"); // @categories Script Utility - { - Mob *THIS; - Mob *mob; - VALIDATE_THIS_IS_MOB; - if (sv_derived_from(ST(1), "Mob")) { - IV tmp = SvIV((SV *) SvRV(ST(1))); - mob = INT2PTR(Mob *, tmp); - } else - Perl_croak(aTHX_ "mob is not of type Mob"); - if (mob == nullptr) - Perl_croak(aTHX_ "mob is nullptr, avoiding crash."); - - THIS->SetShieldTarget(mob); - } - XSRETURN_EMPTY; -} - XS(XS_Mob_Charmed); /* prototype to pass -Wmissing-prototypes */ XS(XS_Mob_Charmed) { dXSARGS; @@ -4750,8 +4701,8 @@ XS(XS_Mob_HasNPCSpecialAtk) { XS(XS_Mob_SendAppearanceEffect); /* prototype to pass -Wmissing-prototypes */ XS(XS_Mob_SendAppearanceEffect) { dXSARGS; - if (items < 2 || items > 7) - Perl_croak(aTHX_ "Usage: Mob::SendAppearanceEffect(THIS, int32 param_1, [int32 param_2 = 0], [int32 param_3 = 0], [int32 param_4 = 0], [int32 param_5 = 0], [Client* single_client_to_send_to = null])"); // @categories Script Utility + if (items < 2 || items > 17) + Perl_croak(aTHX_ "Usage: Mob::SendAppearanceEffect(THIS, int32 effect1, [int32 effect2 = 0], [int32 effect3 = 0], [int32 effect4 = 0], [int32 effect5 = 0], [Client* single_client_to_send_to = null]), [uint32 slot1 = 1], [uint32 ground1 = 1], [uint32 slot2 = 1], [uint32 ground2 = 1], [uint32 slot3 = 1], [uint32 ground2 = 1], [uint32 slot4 = 1], [uint32 ground4 = 1], [uint32 slot5 = 1], [uint32 ground5 = 1]"); // @categories Script Utility { Mob *THIS; int32 parm1 = (int32) SvIV(ST(1)); @@ -4759,7 +4710,18 @@ XS(XS_Mob_SendAppearanceEffect) { int32 parm3 = 0; int32 parm4 = 0; int32 parm5 = 0; + uint32 value1slot = 1; + uint32 value1ground = 1; + uint32 value2slot = 1; + uint32 value2ground = 1; + uint32 value3slot = 1; + uint32 value3ground = 1; + uint32 value4slot = 1; + uint32 value4ground = 1; + uint32 value5slot = 1; + uint32 value5ground = 1; Client *client = nullptr; + bool nullclient = false; VALIDATE_THIS_IS_MOB; if (items > 2) { parm2 = (int32) SvIV(ST(2)); } if (items > 3) { parm3 = (int32) SvIV(ST(3)); } @@ -4767,15 +4729,131 @@ XS(XS_Mob_SendAppearanceEffect) { if (items > 5) { parm5 = (int32) SvIV(ST(5)); } if (items > 6) { if (sv_derived_from(ST(6), "Client")) { - IV tmp = SvIV((SV *) SvRV(ST(6))); + IV tmp = SvIV((SV *)SvRV(ST(6))); client = INT2PTR(Client *, tmp); - } else + } + else { + nullclient = true; + } + if (client == nullptr) { + nullclient = true; + } + } + if (items > 7) { value1slot = (uint32)SvIV(ST(7)); } + if (items > 8) { value1ground = (uint32)SvIV(ST(8)); } + if (items > 9) { value2slot = (uint32)SvIV(ST(9)); } + if (items > 10) { value2ground = (uint32)SvIV(ST(10)); } + if (items > 11) { value3slot = (uint32)SvIV(ST(11)); } + if (items > 12) { value3ground = (uint32)SvIV(ST(12)); } + if (items > 13) { value4slot = (uint32)SvIV(ST(13)); } + if (items > 14) { value4ground = (uint32)SvIV(ST(14)); } + if (items > 15) { value5slot = (uint32)SvIV(ST(15)); } + if (items > 16) { value5ground = (uint32)SvIV(ST(16)); } + + if (nullclient) { + THIS->SendAppearanceEffect(parm1, parm2, parm3, parm4, parm5, 0, value1slot, value1ground, value2slot, value2ground, value3slot, value3ground, + value4slot, value4ground, value5slot, value5ground); + } + else { + THIS->SendAppearanceEffect(parm1, parm2, parm3, parm4, parm5, client, value1slot, value1ground, value2slot, value2ground, value3slot, value3ground, + value4slot, value4ground, value5slot, value5ground); + } + } + XSRETURN_EMPTY; +} + +XS(XS_Mob_SendAppearanceEffectActor); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Mob_SendAppearanceEffectActor) { + dXSARGS; + if (items < 3 || items > 12) + Perl_croak(aTHX_ "Usage: Mob::SendAppearanceEffectActor(THIS, int32 effect1, uint32 slot1 = 0, [int32 effect2 = 0], [uint32 slot2 = 0], [int32 effect3 = 0], [uint32 slot3 = 0], [int32 effect4 = 0], [uint32 slot4 = 0], [int32 effect5 = 0], [uint32 slot5 = 0], [Client* single_client_to_send_to = null])"); // @categories Script Utility + { + Mob *THIS; + int32 parm1 = (int32)SvIV(ST(1)); + uint32 value1slot = (uint32)SvIV(ST(2)); + int32 parm2 = 0; + uint32 value2slot = 0; + int32 parm3 = 0; + uint32 value3slot = 0; + int32 parm4 = 0; + uint32 value4slot = 0; + int32 parm5 = 0; + uint32 value5slot = 0; + Client *client = nullptr; + VALIDATE_THIS_IS_MOB; + if (items > 3) { parm2 = (int32)SvIV(ST(3)); } + if (items > 4) { value2slot = (uint32)SvIV(ST(4)); } + if (items > 5) { parm3 = (int32)SvIV(ST(5)); } + if (items > 6) { value3slot = (uint32)SvIV(ST(6)); } + if (items > 7) { parm4 = (int32)SvIV(ST(7)); } + if (items > 8) { value4slot = (uint32)SvIV(ST(8)); } + if (items > 9) { parm5 = (int32)SvIV(ST(9)); } + if (items > 10) { value5slot = (uint32)SvIV(ST(10)); } + if (items > 11) { + if (sv_derived_from(ST(11), "Client")) { + IV tmp = SvIV((SV *)SvRV(ST(11))); + client = INT2PTR(Client *, tmp); + } + else Perl_croak(aTHX_ "client is not of type Client"); if (client == nullptr) Perl_croak(aTHX_ "client is nullptr, avoiding crash."); } - THIS->SendAppearanceEffect(parm1, parm2, parm3, parm4, parm5, client); + THIS->SendAppearanceEffect(parm1, parm2, parm3, parm4, parm5, client, value1slot, 0, value2slot, 0, value3slot, 0, + value4slot, 0, value5slot, 0); + } + XSRETURN_EMPTY; +} + +XS(XS_Mob_SendAppearanceEffectGround); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Mob_SendAppearanceEffectGround) { + dXSARGS; + if (items < 3 || items > 8) + Perl_croak(aTHX_ "Usage: Mob::SendAppearanceEffectGround(THIS, int32 effect1, [int32 effect2 = 0], [int32 effect3 = 0], [int32 effect4 = 0], [int32 effect5 = 0], [Client* single_client_to_send_to = null])"); // @categories Script Utility + { + Mob *THIS; + int32 parm1 = (int32)SvIV(ST(1)); + int32 parm2 = 0; + int32 parm3 = 0; + int32 parm4 = 0; + int32 parm5 = 0; + Client *client = nullptr; + VALIDATE_THIS_IS_MOB; + if (items > 3) { parm2 = (int32)SvIV(ST(2)); } + if (items > 4) { parm3 = (int32)SvIV(ST(3)); } + if (items > 5) { parm4 = (int32)SvIV(ST(4)); } + if (items > 6) { parm5 = (int32)SvIV(ST(5)); } + if (items > 7) { + if (sv_derived_from(ST(6), "Client")) { + IV tmp = SvIV((SV *)SvRV(ST(11))); + client = INT2PTR(Client *, tmp); + } + else + Perl_croak(aTHX_ "client is not of type Client"); + if (client == nullptr) + Perl_croak(aTHX_ "client is nullptr, avoiding crash."); + } + + THIS->SendAppearanceEffect(parm1, parm2, parm3, parm4, parm5, client, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1); + } + XSRETURN_EMPTY; +} + +XS(XS_Mob_RemoveAllAppearanceEffects); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Mob_RemoveAllAppearanceEffects) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Mob::RemoveAllAppearanceEffects(THIS)"); // @categories Script Utility + { + Mob *THIS; + VALIDATE_THIS_IS_MOB; + THIS->SendIllusionPacket(THIS->GetRace(), THIS->GetGender(), THIS->GetTexture(), THIS->GetHelmTexture(), + THIS->GetHairColor(), THIS->GetBeardColor(), THIS->GetEyeColor1(), THIS->GetEyeColor2(), + THIS->GetHairStyle(), THIS->GetLuclinFace(), THIS->GetBeard(), 0xFF, + THIS->GetDrakkinHeritage(), THIS->GetDrakkinTattoo(), THIS->GetDrakkinDetails(), THIS->GetSize(), false); + THIS->ClearAppearenceEffects(); } XSRETURN_EMPTY; } @@ -4913,8 +4991,8 @@ XS(XS_Mob_CameraEffect) { XS(XS_Mob_SpellEffect); /* prototype to pass -Wmissing-prototypes */ XS(XS_Mob_SpellEffect) { dXSARGS; - if (items < 2 || items > 8) - Perl_croak(aTHX_ "Usage: Mob::SpellEffect(THIS, uint32 effect, [uint32 duration = 5000], [uint32 finish_delay = 0], [bool zone_wide = false], [uint32 unk20 = 3000], [bool perm_effect = false], [Client* single_client])"); // @categories Spells and Disciplines + if (items < 2 || items > 10) + Perl_croak(aTHX_ "Usage: Mob::SpellEffect(THIS, uint32 effect, [uint32 duration = 5000], [uint32 finish_delay = 0], [bool zone_wide = false], [uint32 unk20 = 3000], [bool perm_effect = false], [Client* single_client]), [caster_id = 0], [target_id = 0]"); // @categories Spells and Disciplines { Mob *THIS; uint32 effect = (uint32) SvUV(ST(1)); @@ -4924,6 +5002,9 @@ XS(XS_Mob_SpellEffect) { uint32 unk20 = 3000; bool perm_effect = false; Client *client = nullptr; + uint32 caster_id = 0; + uint32 target_id = 0; + bool nullclient = false; VALIDATE_THIS_IS_MOB; if (items > 2) { duration = (uint32) SvUV(ST(2)); } if (items > 3) { finish_delay = (uint32) SvUV(ST(3)); } @@ -4932,16 +5013,26 @@ XS(XS_Mob_SpellEffect) { if (items > 6) { perm_effect = (bool) SvTRUE(ST(6)); } if (items > 7) { if (sv_derived_from(ST(7), "Client")) { - IV tmp = SvIV((SV *) SvRV(ST(7))); + IV tmp = SvIV((SV *)SvRV(ST(7))); client = INT2PTR(Client *, tmp); - } else - Perl_croak(aTHX_ "client is not of type Client"); - if (client == nullptr) - Perl_croak(aTHX_ "client is nullptr, avoiding crash."); + } + else { + nullclient = true; + } + if (client == nullptr) { + nullclient = true; + } + } + if (items > 8) { caster_id = (uint32)SvUV(ST(8)); } + if (items > 9) { target_id = (uint32)SvUV(ST(9)); } + + if (nullclient) { + THIS->SendSpellEffect(effect, duration, finish_delay, zone_wide, unk20, perm_effect, 0, caster_id, target_id); + } + else { + THIS->SendSpellEffect(effect, duration, finish_delay, zone_wide, unk20, perm_effect, client, caster_id, target_id); } - - THIS->SendSpellEffect(effect, duration, finish_delay, zone_wide, unk20, perm_effect, client); } XSRETURN_EMPTY; } @@ -5115,8 +5206,8 @@ XS(XS_Mob_DoKnockback) { { Mob *THIS; Mob *caster; - uint32 pushback = (uint16) SvUV(ST(2)); - uint32 pushup = (uint16) SvUV(ST(2)); + uint32 push_back = (uint16) SvUV(ST(2)); + uint32 push_up = (uint16) SvUV(ST(2)); VALIDATE_THIS_IS_MOB; if (sv_derived_from(ST(1), "Mob")) { IV tmp = SvIV((SV *) SvRV(ST(1))); @@ -5126,7 +5217,7 @@ XS(XS_Mob_DoKnockback) { if (caster == nullptr) Perl_croak(aTHX_ "caster is nullptr, avoiding crash."); - THIS->DoKnockback(caster, pushback, pushup); + THIS->DoKnockback(caster, push_back, push_up); } XSRETURN_EMPTY; } @@ -5138,9 +5229,9 @@ XS(XS_Mob_RemoveNimbusEffect) { Perl_croak(aTHX_ "Usage: Mob::RemoveNimbusEffect(THIS, int32 effect_id)"); // @categories Script Utility { Mob *THIS; - int32 effectid = (int32) SvIV(ST(1)); + int32 effect_id = (int32) SvIV(ST(1)); VALIDATE_THIS_IS_MOB; - THIS->RemoveNimbusEffect(effectid); + THIS->RemoveNimbusEffect(effect_id); } XSRETURN_EMPTY; } @@ -5648,7 +5739,7 @@ XS(XS_Mob_CanClassEquipItem) { uint32 item_id = (uint32) SvUV(ST(1)); VALIDATE_THIS_IS_MOB; RETVAL = THIS->CanClassEquipItem(item_id); - ST(0) = boolSV(RETVAL); + ST(0) = boolSV(RETVAL); sv_2mortal(ST(0)); } XSRETURN(1); @@ -5967,6 +6058,43 @@ XS(XS_Mob_HasPet) { XSRETURN(1); } +XS(XS_Mob_RemovePet); +XS(XS_Mob_RemovePet) { + dXSARGS; + if (items != 1) { + Perl_croak(aTHX_ "Usage: Mob::RemovePet(THIS)"); // @categories Pet + } + + Mob* THIS; + VALIDATE_THIS_IS_MOB; + + THIS->SetPet(nullptr); + + XSRETURN_EMPTY; +} + +XS(XS_Mob_SetPet); +XS(XS_Mob_SetPet) { + dXSARGS; + if (items != 2) { + Perl_croak(aTHX_ "Usage: Mob::SetPet(THIS, Mob* new_pet)"); // @categories Pet + } + + Mob* THIS; + VALIDATE_THIS_IS_MOB; + + Mob* new_pet = nullptr; // passing null or invalid new_pet removes pet + if (sv_derived_from(ST(1), "Mob")) + { + IV tmp = SvIV((SV*)SvRV(ST(1))); + new_pet = INT2PTR(Mob*, tmp); + } + + THIS->SetPet(new_pet); + + XSRETURN_EMPTY; +} + XS(XS_Mob_IsSilenced); XS(XS_Mob_IsSilenced) { dXSARGS; @@ -6050,7 +6178,7 @@ XS(XS_Mob_GetClassName) { XSprePUSH; PUSHTARG; } - XSRETURN(1); + XSRETURN(1); } XS(XS_Mob_GetRaceName); @@ -6073,9 +6201,9 @@ XS(XS_Mob_GetRaceName) { XS(XS_Mob_DeleteBucket); XS(XS_Mob_DeleteBucket) { - dXSARGS; + dXSARGS; if (items != 2) - Perl_croak(aTHX_ "Usage: Mob::DeleteBucket(THIS, std::string bucket_name)"); // @categories Script Utility + Perl_croak(aTHX_ "Usage: Mob::DeleteBucket(THIS, string bucket_name)"); // @categories Script Utility { Mob* THIS; std::string bucket_name = (std::string) SvPV_nolen(ST(1)); @@ -6087,9 +6215,9 @@ XS(XS_Mob_DeleteBucket) { XS(XS_Mob_GetBucket); XS(XS_Mob_GetBucket) { - dXSARGS; + dXSARGS; if (items != 2) - Perl_croak(aTHX_ "Usage: Mob::GetBucket(THIS, std::string bucket_name)"); // @categories Script Utility + Perl_croak(aTHX_ "Usage: Mob::GetBucket(THIS, string bucket_name)"); // @categories Script Utility { Mob* THIS; dXSTARG; @@ -6106,9 +6234,9 @@ XS(XS_Mob_GetBucket) { XS(XS_Mob_GetBucketExpires); XS(XS_Mob_GetBucketExpires) { - dXSARGS; + dXSARGS; if (items != 2) - Perl_croak(aTHX_ "Usage: Mob::GetBucketExpires(THIS, std::string bucket_name)"); // @categories Script Utility + Perl_croak(aTHX_ "Usage: Mob::GetBucketExpires(THIS, string bucket_name)"); // @categories Script Utility { Mob* THIS; dXSTARG; @@ -6125,7 +6253,7 @@ XS(XS_Mob_GetBucketExpires) { XS(XS_Mob_GetBucketKey); XS(XS_Mob_GetBucketKey) { - dXSARGS; + dXSARGS; if (items != 1) Perl_croak(aTHX_ "Usage: Mob::GetBucketKey(THIS)"); // @categories Script Utility { @@ -6143,9 +6271,9 @@ XS(XS_Mob_GetBucketKey) { XS(XS_Mob_GetBucketRemaining); XS(XS_Mob_GetBucketRemaining) { - dXSARGS; + dXSARGS; if (items != 2) - Perl_croak(aTHX_ "Usage: Mob::GetBucketRemaining(THIS, std::string bucket_name)"); // @categories Script Utility + Perl_croak(aTHX_ "Usage: Mob::GetBucketRemaining(THIS, string bucket_name)"); // @categories Script Utility { Mob* THIS; dXSTARG; @@ -6162,9 +6290,9 @@ XS(XS_Mob_GetBucketRemaining) { XS(XS_Mob_SetBucket); XS(XS_Mob_SetBucket) { - dXSARGS; + dXSARGS; if (items < 3 || items > 4) - Perl_croak(aTHX_ "Usage: Mob::SetBucket(THIS, std::string bucket_name, std::string bucket_value, [std::string expiration])"); // @categories Script Utility + Perl_croak(aTHX_ "Usage: Mob::SetBucket(THIS, string bucket_name, string bucket_value, [string expiration])"); // @categories Script Utility { Mob* THIS; std::string key = (std::string) SvPV_nolen(ST(1)); @@ -6180,7 +6308,7 @@ XS(XS_Mob_SetBucket) { } XS(XS_Mob_IsHorse); -XS(XS_Mob_IsHorse) { +XS(XS_Mob_IsHorse) { dXSARGS; if (items != 1) Perl_croak(aTHX_ "Usage: Mob::IsHorse(THIS)"); // @categories Script Utility @@ -6235,6 +6363,180 @@ XS(XS_Mob_GetHateClosest) { XSRETURN(1); } +XS(XS_Mob_GetLastName); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Mob_GetLastName) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Mob::GetLastName(THIS)"); // @categories Script Utility + { + Mob *THIS; + Const_char *last_name; + dXSTARG; + VALIDATE_THIS_IS_MOB; + last_name = THIS->GetLastName(); + sv_setpv(TARG, last_name); + XSprePUSH; + PUSHTARG; + } + XSRETURN(1); +} + +XS(XS_Mob_CanRaceEquipItem); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Mob_CanRaceEquipItem) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Mob::CanRaceEquipItem(THIS, uint32 item_id)"); // @categories Inventory and Items, Script Utility + { + Mob *THIS; + bool RETVAL; + uint32 item_id = (uint32) SvUV(ST(1)); + VALIDATE_THIS_IS_MOB; + RETVAL = THIS->CanRaceEquipItem(item_id); + ST(0) = boolSV(RETVAL); + sv_2mortal(ST(0)); + } + XSRETURN(1); +} + +XS(XS_Mob_RemoveAllNimbusEffects); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Mob_RemoveAllNimbusEffects) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Mob::RemoveAllNimbusEffects(THIS)"); // @categories Script Utility + { + Mob *THIS; + VALIDATE_THIS_IS_MOB; + THIS->RemoveAllNimbusEffects(); + } + XSRETURN_EMPTY; +} + +XS(XS_Mob_AddNimbusEffect); +XS(XS_Mob_AddNimbusEffect) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Mob::AddNimbusEffect(THIS, int effect_id)"); // @categories Script Utility + { + Mob* THIS; + int effect_id = (int) SvIV(ST(1)); + VALIDATE_THIS_IS_MOB; + THIS->AddNimbusEffect(effect_id); + } + XSRETURN_EMPTY; +} + +XS(XS_Mob_ShieldAbility); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Mob_ShieldAbility) { + dXSARGS; + if (items < 2 || items > 6) + Perl_croak(aTHX_ "Usage: Mob::ShieldAbility(THIS, uint32 target_id, [int32 shielder__max_distance = 15], [int32 shield_duration = 12000], [int32 shield_target_mitigation= 50], [int32 shielder_mitigation = 50], [bool use_aa = false], bool [can_shield_npc = true]"); // @categories Spells and Disciplines + { + Mob *THIS; + uint32 target_id = (uint32)SvUV(ST(1)); + int32 shielder_max_distance = (int32)SvUV(ST(2)); + int32 shield_duration = (int32)SvUV(ST(3)); + int32 shield_target_mitigation = (int32)SvUV(ST(4)); + int32 shielder_mitigation = (int32)SvUV(ST(5)); + bool use_aa = (bool)SvTRUE(ST(6)); + bool can_shield_npc = (bool)SvTRUE(ST(7)); + + VALIDATE_THIS_IS_MOB; + if (items < 3) { + shielder_max_distance = 15; + } + + if (items < 4) { + shield_duration = 12000; + } + + if (items < 5) { + shield_target_mitigation = 50; + } + + if (items < 6) { + shielder_mitigation = 50; + } + + if (items < 7) { + use_aa = false; + } + + if (items < 8) { + can_shield_npc = true; + } + THIS->ShieldAbility(target_id, shielder_max_distance, shield_duration, shield_duration, shield_duration, use_aa, can_shield_npc); + + } + XSRETURN_EMPTY; +} + +XS(XS_Mob_GetHateRandomClient); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Mob_GetHateRandomClient) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Mob::GetHateRandomClient(THIS)"); // @categories Hate and Aggro + { + Mob* THIS; + Client* RETVAL; + VALIDATE_THIS_IS_MOB; + RETVAL = THIS->GetHateRandomClient(); + ST(0) = sv_newmortal(); + sv_setref_pv(ST(0), "Client", (void *) RETVAL); + } + XSRETURN(1); +} + +XS(XS_Mob_GetHateRandomNPC); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Mob_GetHateRandomNPC) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Mob::GetHateRandomNPC(THIS)"); // @categories Hate and Aggro + { + Mob* THIS; + NPC* RETVAL; + VALIDATE_THIS_IS_MOB; + RETVAL = THIS->GetHateRandomNPC(); + ST(0) = sv_newmortal(); + sv_setref_pv(ST(0), "NPC", (void *) RETVAL); + } + XSRETURN(1); +} + +#ifdef BOTS +XS(XS_Mob_CastToBot); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Mob_CastToBot) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Mob::CastToBot(THIS)"); + { + Mob* THIS; + Bot* RETVAL; + VALIDATE_THIS_IS_MOB; + RETVAL = THIS->CastToBot(); + ST(0) = sv_newmortal(); + sv_setref_pv(ST(0), "Bot", (void*)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Mob_GetHateRandomBot); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Mob_GetHateRandomBot) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Mob::GetHateRandomBot(THIS)"); // @categories Hate and Aggro + { + Mob* THIS; + Bot* RETVAL; + VALIDATE_THIS_IS_MOB; + RETVAL = THIS->GetHateRandomBot(); + ST(0) = sv_newmortal(); + sv_setref_pv(ST(0), "Bot", (void *) RETVAL); + } + XSRETURN(1); +} +#endif + #ifdef __cplusplus extern "C" #endif @@ -6252,337 +6554,352 @@ XS(boot_Mob) { //add the strcpy stuff to get rid of const warnings.... XS_VERSION_BOOTCHECK; - - newXSproto(strcpy(buf, "IsClient"), XS_Mob_IsClient, file, "$"); - newXSproto(strcpy(buf, "IsNPC"), XS_Mob_IsNPC, file, "$"); - newXSproto(strcpy(buf, "IsBot"), XS_Mob_IsBot, file, "$"); - newXSproto(strcpy(buf, "IsMob"), XS_Mob_IsMob, file, "$"); - newXSproto(strcpy(buf, "IsCorpse"), XS_Mob_IsCorpse, file, "$"); - newXSproto(strcpy(buf, "IsPlayerCorpse"), XS_Mob_IsPlayerCorpse, file, "$"); - newXSproto(strcpy(buf, "IsNPCCorpse"), XS_Mob_IsNPCCorpse, file, "$"); - newXSproto(strcpy(buf, "IsObject"), XS_Mob_IsObject, file, "$"); - newXSproto(strcpy(buf, "IsDoor"), XS_Mob_IsDoor, file, "$"); - newXSproto(strcpy(buf, "IsTrap"), XS_Mob_IsTrap, file, "$"); - newXSproto(strcpy(buf, "IsBeacon"), XS_Mob_IsBeacon, file, "$"); - newXSproto(strcpy(buf, "IsHorse"), XS_Mob_IsHorse, file, "$"); - newXSproto(strcpy(buf, "CastToClient"), XS_Mob_CastToClient, file, "$"); - newXSproto(strcpy(buf, "CastToNPC"), XS_Mob_CastToNPC, file, "$"); - newXSproto(strcpy(buf, "CastToMob"), XS_Mob_CastToMob, file, "$"); - newXSproto(strcpy(buf, "CastToCorpse"), XS_Mob_CastToCorpse, file, "$"); - newXSproto(strcpy(buf, "GetID"), XS_Mob_GetID, file, "$"); - newXSproto(strcpy(buf, "GetName"), XS_Mob_GetName, file, "$"); - newXSproto(strcpy(buf, "Depop"), XS_Mob_Depop, file, "$;$"); - newXSproto(strcpy(buf, "RogueAssassinate"), XS_Mob_RogueAssassinate, file, "$$"); - newXSproto(strcpy(buf, "BehindMob"), XS_Mob_BehindMob, file, "$;$$$"); - newXSproto(strcpy(buf, "SetLevel"), XS_Mob_SetLevel, file, "$$;$"); - newXSproto(strcpy(buf, "GetSkill"), XS_Mob_GetSkill, file, "$$"); - newXSproto(strcpy(buf, "SendWearChange"), XS_Mob_SendWearChange, file, "$$"); - newXSproto(strcpy(buf, "GetEquipment"), XS_Mob_GetEquipment, file, "$$"); - newXSproto(strcpy(buf, "GetEquipmentMaterial"), XS_Mob_GetEquipmentMaterial, file, "$$"); - newXSproto(strcpy(buf, "GetEquipmentColor"), XS_Mob_GetEquipmentColor, file, "$$"); - newXSproto(strcpy(buf, "GetArmorTint"), XS_Mob_GetArmorTint, file, "$$"); - newXSproto(strcpy(buf, "IsMoving"), XS_Mob_IsMoving, file, "$"); - newXSproto(strcpy(buf, "GoToBind"), XS_Mob_GoToBind, file, "$"); - newXSproto(strcpy(buf, "Gate"), XS_Mob_Gate, file, "$"); + newXSproto(strcpy(buf, "AddFeignMemory"), XS_Mob_AddFeignMemory, file, "$$"); + newXSproto(strcpy(buf, "AddNimbusEffect"), XS_Mob_AddNimbusEffect, file, "$$"); + newXSproto(strcpy(buf, "AddToHateList"), XS_Mob_AddToHateList, file, "$$;$$$$$"); newXSproto(strcpy(buf, "Attack"), XS_Mob_Attack, file, "$$;$$"); - newXSproto(strcpy(buf, "Damage"), XS_Mob_Damage, file, "$$$$$;$$$"); - newXSproto(strcpy(buf, "RangedAttack"), XS_Mob_RangedAttack, file, "$$"); - newXSproto(strcpy(buf, "ThrowingAttack"), XS_Mob_ThrowingAttack, file, "$$"); - newXSproto(strcpy(buf, "Heal"), XS_Mob_Heal, file, "$"); - newXSproto(strcpy(buf, "HealDamage"), XS_Mob_HealDamage, file, "$$;$"); - newXSproto(strcpy(buf, "SetMaxHP"), XS_Mob_SetMaxHP, file, "$"); - newXSproto(strcpy(buf, "GetLevelCon"), XS_Mob_GetLevelCon, file, "$$"); - newXSproto(strcpy(buf, "SetHP"), XS_Mob_SetHP, file, "$$"); - newXSproto(strcpy(buf, "DoAnim"), XS_Mob_DoAnim, file, "$$;$"); - newXSproto(strcpy(buf, "ChangeSize"), XS_Mob_ChangeSize, file, "$$;$"); - newXSproto(strcpy(buf, "RandomizeFeatures"), XS_Mob_RandomizeFeatures, file, "$$;$"); - newXSproto(strcpy(buf, "GMMove"), XS_Mob_GMMove, file, "$$$$;$"); - newXSproto(strcpy(buf, "HasProcs"), XS_Mob_HasProcs, file, "$"); - newXSproto(strcpy(buf, "IsInvisible"), XS_Mob_IsInvisible, file, "$;$"); - newXSproto(strcpy(buf, "SetInvisible"), XS_Mob_SetInvisible, file, "$$"); - newXSproto(strcpy(buf, "FindBuff"), XS_Mob_FindBuff, file, "$$"); - newXSproto(strcpy(buf, "FindBuffBySlot"), XS_Mob_FindBuffBySlot, file, "$$"); + newXSproto(strcpy(buf, "BehindMob"), XS_Mob_BehindMob, file, "$;$$$"); newXSproto(strcpy(buf, "BuffCount"), XS_Mob_BuffCount, file, "$"); - newXSproto(strcpy(buf, "FindType"), XS_Mob_FindType, file, "$$;$$"); - newXSproto(strcpy(buf, "GetBuffSlotFromType"), XS_Mob_GetBuffSlotFromType, file, "$$"); - newXSproto(strcpy(buf, "MakePet"), XS_Mob_MakePet, file, "$$$;$"); - newXSproto(strcpy(buf, "GetBaseRace"), XS_Mob_GetBaseRace, file, "$"); - newXSproto(strcpy(buf, "GetBaseGender"), XS_Mob_GetBaseGender, file, "$"); - newXSproto(strcpy(buf, "GetDeity"), XS_Mob_GetDeity, file, "$"); - newXSproto(strcpy(buf, "GetRace"), XS_Mob_GetRace, file, "$"); - newXSproto(strcpy(buf, "GetRaceName"), XS_Mob_GetRaceName, file, "$"); - newXSproto(strcpy(buf, "GetGender"), XS_Mob_GetGender, file, "$"); - newXSproto(strcpy(buf, "GetTexture"), XS_Mob_GetTexture, file, "$"); - newXSproto(strcpy(buf, "GetHelmTexture"), XS_Mob_GetHelmTexture, file, "$"); - newXSproto(strcpy(buf, "GetHairColor"), XS_Mob_GetHairColor, file, "$"); - newXSproto(strcpy(buf, "GetBeardColor"), XS_Mob_GetBeardColor, file, "$"); - newXSproto(strcpy(buf, "GetEyeColor1"), XS_Mob_GetEyeColor1, file, "$"); - newXSproto(strcpy(buf, "GetEyeColor2"), XS_Mob_GetEyeColor2, file, "$"); - newXSproto(strcpy(buf, "GetHairStyle"), XS_Mob_GetHairStyle, file, "$"); - newXSproto(strcpy(buf, "GetLuclinFace"), XS_Mob_GetLuclinFace, file, "$"); - newXSproto(strcpy(buf, "GetBeard"), XS_Mob_GetBeard, file, "$"); - newXSproto(strcpy(buf, "GetDrakkinHeritage"), XS_Mob_GetDrakkinHeritage, file, "$"); - newXSproto(strcpy(buf, "GetDrakkinTattoo"), XS_Mob_GetDrakkinTattoo, file, "$"); - newXSproto(strcpy(buf, "GetDrakkinDetails"), XS_Mob_GetDrakkinDetails, file, "$"); - newXSproto(strcpy(buf, "GetClass"), XS_Mob_GetClass, file, "$"); - newXSproto(strcpy(buf, "GetClassName"), XS_Mob_GetClassName, file, "$"); - newXSproto(strcpy(buf, "GetLevel"), XS_Mob_GetLevel, file, "$"); - newXSproto(strcpy(buf, "GetCleanName"), XS_Mob_GetCleanName, file, "$"); - newXSproto(strcpy(buf, "GetTarget"), XS_Mob_GetTarget, file, "$"); - newXSproto(strcpy(buf, "SetTarget"), XS_Mob_SetTarget, file, "$$"); - newXSproto(strcpy(buf, "GetHPRatio"), XS_Mob_GetHPRatio, file, "$"); - newXSproto(strcpy(buf, "IsWarriorClass"), XS_Mob_IsWarriorClass, file, "$"); - newXSproto(strcpy(buf, "GetHP"), XS_Mob_GetHP, file, "$"); - newXSproto(strcpy(buf, "GetMaxHP"), XS_Mob_GetMaxHP, file, "$"); - newXSproto(strcpy(buf, "GetItemHPBonuses"), XS_Mob_GetItemHPBonuses, file, "$"); - newXSproto(strcpy(buf, "GetSpellHPBonuses"), XS_Mob_GetSpellHPBonuses, file, "$"); - newXSproto(strcpy(buf, "GetSpellIDFromSlot"), XS_Mob_GetSpellIDFromSlot, file, "$$"); - newXSproto(strcpy(buf, "GetWalkspeed"), XS_Mob_GetWalkspeed, file, "$"); - newXSproto(strcpy(buf, "GetRunspeed"), XS_Mob_GetRunspeed, file, "$"); - newXSproto(strcpy(buf, "GetCasterLevel"), XS_Mob_GetCasterLevel, file, "$$"); - newXSproto(strcpy(buf, "GetMaxMana"), XS_Mob_GetMaxMana, file, "$"); - newXSproto(strcpy(buf, "GetMana"), XS_Mob_GetMana, file, "$"); - newXSproto(strcpy(buf, "SetMana"), XS_Mob_SetMana, file, "$$"); - newXSproto(strcpy(buf, "GetManaRatio"), XS_Mob_GetManaRatio, file, "$"); - newXSproto(strcpy(buf, "GetAC"), XS_Mob_GetAC, file, "$"); - newXSproto(strcpy(buf, "GetDisplayAC"), XS_Mob_GetDisplayAC, file, "$"); - newXSproto(strcpy(buf, "GetATK"), XS_Mob_GetATK, file, "$"); - newXSproto(strcpy(buf, "GetSTR"), XS_Mob_GetSTR, file, "$"); - newXSproto(strcpy(buf, "GetSTA"), XS_Mob_GetSTA, file, "$"); - newXSproto(strcpy(buf, "GetDEX"), XS_Mob_GetDEX, file, "$"); - newXSproto(strcpy(buf, "GetAGI"), XS_Mob_GetAGI, file, "$"); - newXSproto(strcpy(buf, "GetINT"), XS_Mob_GetINT, file, "$"); - newXSproto(strcpy(buf, "GetWIS"), XS_Mob_GetWIS, file, "$"); - newXSproto(strcpy(buf, "GetCHA"), XS_Mob_GetCHA, file, "$"); - newXSproto(strcpy(buf, "GetMR"), XS_Mob_GetMR, file, "$"); - newXSproto(strcpy(buf, "GetFR"), XS_Mob_GetFR, file, "$"); - newXSproto(strcpy(buf, "GetDR"), XS_Mob_GetDR, file, "$"); - newXSproto(strcpy(buf, "GetPR"), XS_Mob_GetPR, file, "$"); - newXSproto(strcpy(buf, "GetCR"), XS_Mob_GetCR, file, "$"); - newXSproto(strcpy(buf, "GetCorruption"), XS_Mob_GetCorruption, file, "$"); - newXSproto(strcpy(buf, "GetPhR"), XS_Mob_GetPhR, file, "$"); - newXSproto(strcpy(buf, "GetMaxSTR"), XS_Mob_GetMaxSTR, file, "$"); - newXSproto(strcpy(buf, "GetMaxSTA"), XS_Mob_GetMaxSTA, file, "$"); - newXSproto(strcpy(buf, "GetMaxDEX"), XS_Mob_GetMaxDEX, file, "$"); - newXSproto(strcpy(buf, "GetMaxAGI"), XS_Mob_GetMaxAGI, file, "$"); - newXSproto(strcpy(buf, "GetMaxINT"), XS_Mob_GetMaxINT, file, "$"); - newXSproto(strcpy(buf, "GetMaxWIS"), XS_Mob_GetMaxWIS, file, "$"); - newXSproto(strcpy(buf, "GetMaxCHA"), XS_Mob_GetMaxCHA, file, "$"); - newXSproto(strcpy(buf, "GetActSpellRange"), XS_Mob_GetActSpellRange, file, "$$$"); - newXSproto(strcpy(buf, "GetActSpellDamage"), XS_Mob_GetActSpellDamage, file, "$$$"); - newXSproto(strcpy(buf, "GetActSpellHealing"), XS_Mob_GetActSpellHealing, file, "$$$"); - newXSproto(strcpy(buf, "GetActSpellCost"), XS_Mob_GetActSpellCost, file, "$$$"); - newXSproto(strcpy(buf, "GetActSpellDuration"), XS_Mob_GetActSpellDuration, file, "$$$"); - newXSproto(strcpy(buf, "GetActSpellCasttime"), XS_Mob_GetActSpellCasttime, file, "$$$"); - newXSproto(strcpy(buf, "ResistSpell"), XS_Mob_ResistSpell, file, "$$$$"); - newXSproto(strcpy(buf, "GetSpecializeSkillValue"), XS_Mob_GetSpecializeSkillValue, file, "$$"); - newXSproto(strcpy(buf, "GetNPCTypeID"), XS_Mob_GetNPCTypeID, file, "$"); - newXSproto(strcpy(buf, "IsTargeted"), XS_Mob_IsTargeted, file, "$"); - newXSproto(strcpy(buf, "GetX"), XS_Mob_GetX, file, "$"); - newXSproto(strcpy(buf, "GetY"), XS_Mob_GetY, file, "$"); - newXSproto(strcpy(buf, "GetZ"), XS_Mob_GetZ, file, "$"); - newXSproto(strcpy(buf, "GetHeading"), XS_Mob_GetHeading, file, "$"); - newXSproto(strcpy(buf, "GetWaypointX"), XS_Mob_GetWaypointX, file, "$"); - newXSproto(strcpy(buf, "GetWaypointY"), XS_Mob_GetWaypointY, file, "$"); - newXSproto(strcpy(buf, "GetWaypointZ"), XS_Mob_GetWaypointZ, file, "$"); - newXSproto(strcpy(buf, "GetWaypointH"), XS_Mob_GetWaypointH, file, "$"); - newXSproto(strcpy(buf, "GetWaypointPause"), XS_Mob_GetWaypointPause, file, "$"); - newXSproto(strcpy(buf, "GetWaypointID"), XS_Mob_GetWaypointID, file, "$"); - newXSproto(strcpy(buf, "SetCurrentWP"), XS_Mob_SetCurrentWP, file, "$$"); - newXSproto(strcpy(buf, "GetSize"), XS_Mob_GetSize, file, "$"); - newXSproto(strcpy(buf, "SetFollowID"), XS_Mob_SetFollowID, file, "$$"); - newXSproto(strcpy(buf, "GetFollowID"), XS_Mob_GetFollowID, file, "$"); - newXSproto(strcpy(buf, "Message"), XS_Mob_Message, file, "$$$;@"); - newXSproto(strcpy(buf, "Message_StringID"), XS_Mob_Message_StringID, file, "$$$;$"); - newXSproto(strcpy(buf, "Say"), XS_Mob_Say, file, "$$;@"); - newXSproto(strcpy(buf, "Shout"), XS_Mob_Shout, file, "$$;@"); - newXSproto(strcpy(buf, "Emote"), XS_Mob_Emote, file, "$$;@"); - newXSproto(strcpy(buf, "InterruptSpell"), XS_Mob_InterruptSpell, file, "$;$"); - newXSproto(strcpy(buf, "CastSpell"), XS_Mob_CastSpell, file, "$$$;$$$"); - newXSproto(strcpy(buf, "SpellFinished"), XS_Mob_SpellFinished, file, "$$;$$"); - newXSproto(strcpy(buf, "IsImmuneToSpell"), XS_Mob_IsImmuneToSpell, file, "$$$"); - newXSproto(strcpy(buf, "BuffFadeBySpellID"), XS_Mob_BuffFadeBySpellID, file, "$$"); - newXSproto(strcpy(buf, "BuffFadeByEffect"), XS_Mob_BuffFadeByEffect, file, "$$;$"); newXSproto(strcpy(buf, "BuffFadeAll"), XS_Mob_BuffFadeAll, file, "$"); + newXSproto(strcpy(buf, "BuffFadeByEffect"), XS_Mob_BuffFadeByEffect, file, "$$;$"); newXSproto(strcpy(buf, "BuffFadeBySlot"), XS_Mob_BuffFadeBySlot, file, "$$;$"); + newXSproto(strcpy(buf, "BuffFadeBySpellID"), XS_Mob_BuffFadeBySpellID, file, "$$"); + newXSproto(strcpy(buf, "CalculateDistance"), XS_Mob_CalculateDistance, file, "$$$$"); + newXSproto(strcpy(buf, "CalculateHeadingToTarget"), XS_Mob_CalculateHeadingToTarget, file, "$$$"); + newXSproto(strcpy(buf, "CameraEffect"), XS_Mob_CameraEffect, file, "$$;$$$"); newXSproto(strcpy(buf, "CanBuffStack"), XS_Mob_CanBuffStack, file, "$$$;$"); - newXSproto(strcpy(buf, "IsCasting"), XS_Mob_IsCasting, file, "$"); - newXSproto(strcpy(buf, "CastingSpellID"), XS_Mob_CastingSpellID, file, "$"); - newXSproto(strcpy(buf, "SetAppearance"), XS_Mob_SetAppearance, file, "$$;$"); - newXSproto(strcpy(buf, "GetAppearance"), XS_Mob_GetAppearance, file, "$"); - newXSproto(strcpy(buf, "GetRunAnimSpeed"), XS_Mob_GetRunAnimSpeed, file, "$"); - newXSproto(strcpy(buf, "SetRunAnimSpeed"), XS_Mob_SetRunAnimSpeed, file, "$$"); - newXSproto(strcpy(buf, "SetPetID"), XS_Mob_SetPetID, file, "$$"); - newXSproto(strcpy(buf, "GetPetID"), XS_Mob_GetPetID, file, "$"); - newXSproto(strcpy(buf, "SetOwnerID"), XS_Mob_SetOwnerID, file, "$$"); - newXSproto(strcpy(buf, "GetOwnerID"), XS_Mob_GetOwnerID, file, "$"); - newXSproto(strcpy(buf, "GetPetType"), XS_Mob_GetPetType, file, "$"); - newXSproto(strcpy(buf, "GetBodyType"), XS_Mob_GetBodyType, file, "$"); - newXSproto(strcpy(buf, "Stun"), XS_Mob_Stun, file, "$$"); - newXSproto(strcpy(buf, "Spin"), XS_Mob_Spin, file, "$"); - newXSproto(strcpy(buf, "Kill"), XS_Mob_Kill, file, "$"); - newXSproto(strcpy(buf, "SetInvul"), XS_Mob_SetInvul, file, "$$"); - newXSproto(strcpy(buf, "GetInvul"), XS_Mob_GetInvul, file, "$"); - newXSproto(strcpy(buf, "SetExtraHaste"), XS_Mob_SetExtraHaste, file, "$$"); - newXSproto(strcpy(buf, "GetHaste"), XS_Mob_GetHaste, file, "$"); - newXSproto(strcpy(buf, "GetHandToHandDamage"), XS_Mob_GetHandToHandDamage, file, "$"); + newXSproto(strcpy(buf, "CanClassEquipItem"), XS_Mob_CanClassEquipItem, file, "$$"); + newXSproto(strcpy(buf, "CanRaceEquipItem"), XS_Mob_CanRaceEquipItem, file, "$$"); + newXSproto(strcpy(buf, "CanThisClassDodge"), XS_Mob_CanThisClassDodge, file, "$"); newXSproto(strcpy(buf, "CanThisClassDoubleAttack"), XS_Mob_CanThisClassDoubleAttack, file, "$"); newXSproto(strcpy(buf, "CanThisClassDualWield"), XS_Mob_CanThisClassDualWield, file, "$"); - newXSproto(strcpy(buf, "CanThisClassRiposte"), XS_Mob_CanThisClassRiposte, file, "$"); - newXSproto(strcpy(buf, "CanThisClassDodge"), XS_Mob_CanThisClassDodge, file, "$"); newXSproto(strcpy(buf, "CanThisClassParry"), XS_Mob_CanThisClassParry, file, "$"); - newXSproto(strcpy(buf, "GetHandToHandDelay"), XS_Mob_GetHandToHandDelay, file, "$"); - newXSproto(strcpy(buf, "GetClassLevelFactor"), XS_Mob_GetClassLevelFactor, file, "$"); - newXSproto(strcpy(buf, "Mesmerize"), XS_Mob_Mesmerize, file, "$"); - newXSproto(strcpy(buf, "IsMezzed"), XS_Mob_IsMezzed, file, "$"); - newXSproto(strcpy(buf, "IsStunned"), XS_Mob_IsStunned, file, "$"); - newXSproto(strcpy(buf, "StartEnrage"), XS_Mob_StartEnrage, file, "$"); - newXSproto(strcpy(buf, "IsEnraged"), XS_Mob_IsEnraged, file, "$"); - newXSproto(strcpy(buf, "GetReverseFactionCon"), XS_Mob_GetReverseFactionCon, file, "$$"); - newXSproto(strcpy(buf, "IsAIControlled"), XS_Mob_IsAIControlled, file, "$"); - newXSproto(strcpy(buf, "GetAggroRange"), XS_Mob_GetAggroRange, file, "$"); - newXSproto(strcpy(buf, "GetAssistRange"), XS_Mob_GetAssistRange, file, "$"); - newXSproto(strcpy(buf, "SetPetOrder"), XS_Mob_SetPetOrder, file, "$$"); - newXSproto(strcpy(buf, "GetPetOrder"), XS_Mob_GetPetOrder, file, "$"); - newXSproto(strcpy(buf, "IsRoamer"), XS_Mob_IsRoamer, file, "$"); - newXSproto(strcpy(buf, "IsRooted"), XS_Mob_IsRooted, file, "$"); - newXSproto(strcpy(buf, "AddToHateList"), XS_Mob_AddToHateList, file, "$$;$$$$$"); - newXSproto(strcpy(buf, "SetHate"), XS_Mob_SetHate, file, "$$;$$"); - newXSproto(strcpy(buf, "HalveAggro"), XS_Mob_HalveAggro, file, "$$"); - newXSproto(strcpy(buf, "DoubleAggro"), XS_Mob_DoubleAggro, file, "$$"); - newXSproto(strcpy(buf, "GetHateAmount"), XS_Mob_GetHateAmount, file, "$$;$"); - newXSproto(strcpy(buf, "GetDamageAmount"), XS_Mob_GetDamageAmount, file, "$$"); - newXSproto(strcpy(buf, "GetHateTop"), XS_Mob_GetHateTop, file, "$"); - newXSproto(strcpy(buf, "GetHateDamageTop"), XS_Mob_GetHateDamageTop, file, "$$"); - newXSproto(strcpy(buf, "GetHateRandom"), XS_Mob_GetHateRandom, file, "$"); - newXSproto(strcpy(buf, "IsEngaged"), XS_Mob_IsEngaged, file, "$"); - newXSproto(strcpy(buf, "HateSummon"), XS_Mob_HateSummon, file, "$"); - newXSproto(strcpy(buf, "FaceTarget"), XS_Mob_FaceTarget, file, "$;$$"); - newXSproto(strcpy(buf, "SetHeading"), XS_Mob_SetHeading, file, "$$"); - newXSproto(strcpy(buf, "WipeHateList"), XS_Mob_WipeHateList, file, "$"); - newXSproto(strcpy(buf, "CheckAggro"), XS_Mob_CheckAggro, file, "$$"); - newXSproto(strcpy(buf, "CalculateHeadingToTarget"), XS_Mob_CalculateHeadingToTarget, file, "$$$"); - newXSproto(strcpy(buf, "RunTo"), XS_Mob_RunTo, file, "$$$$"); - newXSproto(strcpy(buf, "WalkTo"), XS_Mob_WalkTo, file, "$$$$"); - newXSproto(strcpy(buf, "NavigateTo"), XS_Mob_NavigateTo, file, "$$$$"); - newXSproto(strcpy(buf, "StopNavigation"), XS_Mob_StopNavigation, file, "$"); - newXSproto(strcpy(buf, "CalculateDistance"), XS_Mob_CalculateDistance, file, "$$$$"); - newXSproto(strcpy(buf, "SendTo"), XS_Mob_SendTo, file, "$$$$"); - newXSproto(strcpy(buf, "SendToFixZ"), XS_Mob_SendToFixZ, file, "$$$$"); - newXSproto(strcpy(buf, "NPCSpecialAttacks"), XS_Mob_NPCSpecialAttacks, file, "$$$;$$"); - newXSproto(strcpy(buf, "DontHealMeBefore"), XS_Mob_DontHealMeBefore, file, "$"); - newXSproto(strcpy(buf, "DontBuffMeBefore"), XS_Mob_DontBuffMeBefore, file, "$"); - newXSproto(strcpy(buf, "DontDotMeBefore"), XS_Mob_DontDotMeBefore, file, "$"); - newXSproto(strcpy(buf, "DontRootMeBefore"), XS_Mob_DontRootMeBefore, file, "$"); - newXSproto(strcpy(buf, "DontSnareMeBefore"), XS_Mob_DontSnareMeBefore, file, "$"); - newXSproto(strcpy(buf, "GetResist"), XS_Mob_GetResist, file, "$$"); - newXSproto(strcpy(buf, "GetShieldTarget"), XS_Mob_GetShieldTarget, file, "$"); - newXSproto(strcpy(buf, "SetShieldTarget"), XS_Mob_SetShieldTarget, file, "$$"); + newXSproto(strcpy(buf, "CanThisClassRiposte"), XS_Mob_CanThisClassRiposte, file, "$"); + newXSproto(strcpy(buf, "CastSpell"), XS_Mob_CastSpell, file, "$$$;$$$"); +#ifdef BOTS + newXSproto(strcpy(buf, "CastToBot"), XS_Mob_CastToBot, file, "$"); +#endif + newXSproto(strcpy(buf, "CastToClient"), XS_Mob_CastToClient, file, "$"); + newXSproto(strcpy(buf, "CastToCorpse"), XS_Mob_CastToCorpse, file, "$"); + newXSproto(strcpy(buf, "CastToMob"), XS_Mob_CastToMob, file, "$"); + newXSproto(strcpy(buf, "CastToNPC"), XS_Mob_CastToNPC, file, "$"); + newXSproto(strcpy(buf, "CastingSpellID"), XS_Mob_CastingSpellID, file, "$"); + newXSproto(strcpy(buf, "ChangeSize"), XS_Mob_ChangeSize, file, "$$;$"); newXSproto(strcpy(buf, "Charmed"), XS_Mob_Charmed, file, "$"); - newXSproto(strcpy(buf, "GetLevelHP"), XS_Mob_GetLevelHP, file, "$$"); - newXSproto(strcpy(buf, "GetZoneID"), XS_Mob_GetZoneID, file, "$"); + newXSproto(strcpy(buf, "CheckAggro"), XS_Mob_CheckAggro, file, "$$"); newXSproto(strcpy(buf, "CheckAggroAmount"), XS_Mob_CheckAggroAmount, file, "$$"); newXSproto(strcpy(buf, "CheckHealAggroAmount"), XS_Mob_CheckHealAggroAmount, file, "$$"); - newXSproto(strcpy(buf, "GetAA"), XS_Mob_GetAA, file, "$$"); - newXSproto(strcpy(buf, "GetAAByAAID"), XS_Mob_GetAAByAAID, file, "$$"); - newXSproto(strcpy(buf, "SetAA"), XS_Mob_SetAA, file, "$$$;$"); - newXSproto(strcpy(buf, "DivineAura"), XS_Mob_DivineAura, file, "$"); - newXSproto(strcpy(buf, "AddFeignMemory"), XS_Mob_AddFeignMemory, file, "$$"); - newXSproto(strcpy(buf, "RemoveFromFeignMemory"), XS_Mob_RemoveFromFeignMemory, file, "$$"); - newXSproto(strcpy(buf, "ClearFeignMemory"), XS_Mob_ClearFeignMemory, file, "$"); - newXSproto(strcpy(buf, "SetOOCRegen"), XS_Mob_SetOOCRegen, file, "$$"); - newXSproto(strcpy(buf, "GetEntityVariable"), XS_Mob_GetEntityVariable, file, "$$"); - newXSproto(strcpy(buf, "SetEntityVariable"), XS_Mob_SetEntityVariable, file, "$$$"); - newXSproto(strcpy(buf, "EntityVariableExists"), XS_Mob_EntityVariableExists, file, "$$"); - newXSproto(strcpy(buf, "GetHateList"), XS_Mob_GetHateList, file, "$"); - newXSproto(strcpy(buf, "SignalClient"), XS_Mob_SignalClient, file, "$$$"); - newXSproto(strcpy(buf, "CombatRange"), XS_Mob_CombatRange, file, "$$"); - newXSproto(strcpy(buf, "DoSpecialAttackDamage"), XS_Mob_DoSpecialAttackDamage, file, "$$$$;$$"); newXSproto(strcpy(buf, "CheckLoS"), XS_Mob_CheckLoS, file, "$$"); newXSproto(strcpy(buf, "CheckLoSToLoc"), XS_Mob_CheckLoSToLoc, file, "$$$$;$"); - newXSproto(strcpy(buf, "FindGroundZ"), XS_Mob_FindGroundZ, file, "$$$;$"); - newXSproto(strcpy(buf, "ProjectileAnim"), XS_Mob_ProjectileAnim, file, "$$$;$$$$$$"); - newXSproto(strcpy(buf, "HasNPCSpecialAtk"), XS_Mob_HasNPCSpecialAtk, file, "$$"); - newXSproto(strcpy(buf, "SendAppearanceEffect"), XS_Mob_SendAppearanceEffect, file, "$$;$$$$"); - newXSproto(strcpy(buf, "SetFlyMode"), XS_Mob_SetFlyMode, file, "$$"); - newXSproto(strcpy(buf, "SetTexture"), XS_Mob_SetTexture, file, "$$"); - newXSproto(strcpy(buf, "SetRace"), XS_Mob_SetRace, file, "$$"); - newXSproto(strcpy(buf, "SetGender"), XS_Mob_SetGender, file, "$$"); - newXSproto(strcpy(buf, "SendIllusion"), XS_Mob_SendIllusion, file, "$$;$$$$$$$$$$$$"); - newXSproto(strcpy(buf, "MakeTempPet"), XS_Mob_MakeTempPet, file, "$$;$$$$"); - newXSproto(strcpy(buf, "TypesTempPet"), XS_Mob_TypesTempPet, file, "$$;$$$$$"); - newXSproto(strcpy(buf, "CameraEffect"), XS_Mob_CameraEffect, file, "$$;$$$"); - newXSproto(strcpy(buf, "SpellEffect"), XS_Mob_SpellEffect, file, "$$;$$$$$$"); - newXSproto(strcpy(buf, "TempName"), XS_Mob_TempName, file, "$:$"); - newXSproto(strcpy(buf, "GetItemStat"), XS_Mob_GetItemStat, file, "$$$"); - newXSproto(strcpy(buf, "GetGlobal"), XS_Mob_GetGlobal, file, "$$"); - newXSproto(strcpy(buf, "SetGlobal"), XS_Mob_SetGlobal, file, "$$$$$;$"); - newXSproto(strcpy(buf, "TarGlobal"), XS_Mob_TarGlobal, file, "$$$$$$$"); - newXSproto(strcpy(buf, "DelGlobal"), XS_Mob_DelGlobal, file, "$$"); - newXSproto(strcpy(buf, "SetSlotTint"), XS_Mob_SetSlotTint, file, "$$$$$"); - newXSproto(strcpy(buf, "WearChange"), XS_Mob_WearChange, file, "$$$;$$"); - newXSproto(strcpy(buf, "DoKnockback"), XS_Mob_DoKnockback, file, "$$$$"); - newXSproto(strcpy(buf, "RemoveNimbusEffect"), XS_Mob_RemoveNimbusEffect, file, "$$"); - newXSproto(strcpy(buf, "IsRunning"), XS_Mob_IsRunning, file, "$"); - newXSproto(strcpy(buf, "SetRunning"), XS_Mob_SetRunning, file, "$$"); - newXSproto(strcpy(buf, "SetBodyType"), XS_Mob_SetBodyType, file, "$$;$"); - newXSproto(strcpy(buf, "SetDeltas"), XS_Mob_SetDeltas, file, "$$$$$"); - newXSproto(strcpy(buf, "SetLD"), XS_Mob_SetLD, file, "$$"); - newXSproto(strcpy(buf, "SetTargetable"), XS_Mob_SetTargetable, file, "$$"); - newXSproto(strcpy(buf, "ModSkillDmgTaken"), XS_Mob_ModSkillDmgTaken, file, "$$$"); - newXSproto(strcpy(buf, "GetModSkillDmgTaken"), XS_Mob_GetModSkillDmgTaken, file, "$$"); - newXSproto(strcpy(buf, "GetSkillDmgTaken"), XS_Mob_GetSkillDmgTaken, file, "$$"); - newXSproto(strcpy(buf, "SetAllowBeneficial"), XS_Mob_SetAllowBeneficial, file, "$$"); - newXSproto(strcpy(buf, "GetAllowBeneficial"), XS_Mob_GetAllowBeneficial, file, "$$"); - newXSproto(strcpy(buf, "IsBeneficialAllowed"), XS_Mob_IsBeneficialAllowed, file, "$$"); - newXSproto(strcpy(buf, "ModVulnerability"), XS_Mob_ModVulnerability, file, "$$$"); - newXSproto(strcpy(buf, "GetModVulnerability"), XS_Mob_GetModVulnerability, file, "$$"); - newXSproto(strcpy(buf, "DoMeleeSkillAttackDmg"), XS_Mob_DoMeleeSkillAttackDmg, file, "$$$$$$$"); - newXSproto(strcpy(buf, "DoArcheryAttackDmg"), XS_Mob_DoArcheryAttackDmg, file, "$$$$$$$"); - newXSproto(strcpy(buf, "DoThrowingAttackDmg"), XS_Mob_DoThrowingAttackDmg, file, "$$$$$$$"); - newXSproto(strcpy(buf, "SetDisableMelee"), XS_Mob_SetDisableMelee, file, "$$"); - newXSproto(strcpy(buf, "IsMeleeDisabled"), XS_Mob_IsMeleeDisabled, file, "$"); - newXSproto(strcpy(buf, "SetFlurryChance"), XS_Mob_SetFlurryChance, file, "$$"); - newXSproto(strcpy(buf, "GetFlurryChance"), XS_Mob_GetFlurryChance, file, "$"); - newXSproto(strcpy(buf, "GetSpellStat"), XS_Mob_GetSpellStat, file, "$$$$"); - newXSproto(strcpy(buf, "GetSpecialAbility"), XS_Mob_GetSpecialAbility, file, "$$"); - newXSproto(strcpy(buf, "GetSpecialAbilityParam"), XS_Mob_GetSpecialAbilityParam, file, "$$$"); - newXSproto(strcpy(buf, "SetSpecialAbility"), XS_Mob_SetSpecialAbility, file, "$$$"); - newXSproto(strcpy(buf, "SetSpecialAbilityParam"), XS_Mob_SetSpecialAbilityParam, file, "$$$$"); + newXSproto(strcpy(buf, "ClearFeignMemory"), XS_Mob_ClearFeignMemory, file, "$"); newXSproto(strcpy(buf, "ClearSpecialAbilities"), XS_Mob_ClearSpecialAbilities, file, "$"); - newXSproto(strcpy(buf, "ProcessSpecialAbilities"), XS_Mob_ProcessSpecialAbilities, file, "$$"); - newXSproto(strcpy(buf, "CanClassEquipItem"), XS_Mob_CanClassEquipItem, file, "$$"); - newXSproto(strcpy(buf, "IsFeared"), XS_Mob_IsFeared, file, "$"); - newXSproto(strcpy(buf, "IsBlind"), XS_Mob_IsBlind, file, "$"); - newXSproto(strcpy(buf, "SeeInvisible"), XS_Mob_SeeInvisible, file, "$"); - newXSproto(strcpy(buf, "SeeInvisibleUndead"), XS_Mob_SeeInvisibleUndead, file, "$"); - newXSproto(strcpy(buf, "SeeHide"), XS_Mob_SeeHide, file, "$"); - newXSproto(strcpy(buf, "SeeImprovedHide"), XS_Mob_SeeImprovedHide, file, "$"); - newXSproto(strcpy(buf, "GetNimbusEffect1"), XS_Mob_GetNimbusEffect1, file, "$"); - newXSproto(strcpy(buf, "GetNimbusEffect2"), XS_Mob_GetNimbusEffect2, file, "$"); - newXSproto(strcpy(buf, "GetNimbusEffect3"), XS_Mob_GetNimbusEffect3, file, "$"); - newXSproto(strcpy(buf, "IsTargetable"), XS_Mob_IsTargetable, file, "$"); - newXSproto(strcpy(buf, "HasShieldEquiped"), XS_Mob_HasShieldEquiped, file, "$"); - newXSproto(strcpy(buf, "HasTwoHandBluntEquiped"), XS_Mob_HasTwoHandBluntEquiped, file, "$"); - newXSproto(strcpy(buf, "HasTwoHanderEquipped"), XS_Mob_HasTwoHanderEquipped, file, "$"); - newXSproto(strcpy(buf, "GetHerosForgeModel"), XS_Mob_GetHerosForgeModel, file, "$$"); - newXSproto(strcpy(buf, "IsEliteMaterialItem"), XS_Mob_IsEliteMaterialItem, file, "$$"); - newXSproto(strcpy(buf, "GetBaseSize"), XS_Mob_GetBaseSize, file, "$"); - newXSproto(strcpy(buf, "HasOwner"), XS_Mob_HasOwner, file, "$"); - newXSproto(strcpy(buf, "IsPet"), XS_Mob_IsPet, file, "$"); - newXSproto(strcpy(buf, "HasPet"), XS_Mob_HasPet, file, "$"); - newXSproto(strcpy(buf, "IsSilenced"), XS_Mob_IsSilenced, file, "$"); - newXSproto(strcpy(buf, "IsAmnesiad"), XS_Mob_IsAmnesiad, file, "$"); - newXSproto(strcpy(buf, "GetMeleeMitigation"), XS_Mob_GetMeleeMitigation, file, "$"); - newXSproto(strcpy(buf, "TryMoveAlong"), XS_Mob_TryMoveAlong, file, "$$$;$"); + newXSproto(strcpy(buf, "CombatRange"), XS_Mob_CombatRange, file, "$$"); + newXSproto(strcpy(buf, "Damage"), XS_Mob_Damage, file, "$$$$$;$$$"); + newXSproto(strcpy(buf, "DelGlobal"), XS_Mob_DelGlobal, file, "$$"); newXSproto(strcpy(buf, "DeleteBucket"), XS_Mob_DeleteBucket, file, "$$"); + newXSproto(strcpy(buf, "Depop"), XS_Mob_Depop, file, "$;$"); + newXSproto(strcpy(buf, "DivineAura"), XS_Mob_DivineAura, file, "$"); + newXSproto(strcpy(buf, "DoAnim"), XS_Mob_DoAnim, file, "$$;$"); + newXSproto(strcpy(buf, "DoArcheryAttackDmg"), XS_Mob_DoArcheryAttackDmg, file, "$$$$$$$"); + newXSproto(strcpy(buf, "DoKnockback"), XS_Mob_DoKnockback, file, "$$$$"); + newXSproto(strcpy(buf, "DoMeleeSkillAttackDmg"), XS_Mob_DoMeleeSkillAttackDmg, file, "$$$$$$$"); + newXSproto(strcpy(buf, "DoSpecialAttackDamage"), XS_Mob_DoSpecialAttackDamage, file, "$$$$;$$"); + newXSproto(strcpy(buf, "DoThrowingAttackDmg"), XS_Mob_DoThrowingAttackDmg, file, "$$$$$$$"); + newXSproto(strcpy(buf, "DontBuffMeBefore"), XS_Mob_DontBuffMeBefore, file, "$"); + newXSproto(strcpy(buf, "DontDotMeBefore"), XS_Mob_DontDotMeBefore, file, "$"); + newXSproto(strcpy(buf, "DontHealMeBefore"), XS_Mob_DontHealMeBefore, file, "$"); + newXSproto(strcpy(buf, "DontRootMeBefore"), XS_Mob_DontRootMeBefore, file, "$"); + newXSproto(strcpy(buf, "DontSnareMeBefore"), XS_Mob_DontSnareMeBefore, file, "$"); + newXSproto(strcpy(buf, "DoubleAggro"), XS_Mob_DoubleAggro, file, "$$"); + newXSproto(strcpy(buf, "Emote"), XS_Mob_Emote, file, "$$;@"); + newXSproto(strcpy(buf, "EntityVariableExists"), XS_Mob_EntityVariableExists, file, "$$"); + newXSproto(strcpy(buf, "FaceTarget"), XS_Mob_FaceTarget, file, "$;$$"); + newXSproto(strcpy(buf, "FindBuff"), XS_Mob_FindBuff, file, "$$"); + newXSproto(strcpy(buf, "FindBuffBySlot"), XS_Mob_FindBuffBySlot, file, "$$"); + newXSproto(strcpy(buf, "FindGroundZ"), XS_Mob_FindGroundZ, file, "$$$;$"); + newXSproto(strcpy(buf, "FindType"), XS_Mob_FindType, file, "$$;$$"); + newXSproto(strcpy(buf, "GMMove"), XS_Mob_GMMove, file, "$$$$;$"); + newXSproto(strcpy(buf, "Gate"), XS_Mob_Gate, file, "$"); + newXSproto(strcpy(buf, "GetAA"), XS_Mob_GetAA, file, "$$"); + newXSproto(strcpy(buf, "GetAAByAAID"), XS_Mob_GetAAByAAID, file, "$$"); + newXSproto(strcpy(buf, "GetAC"), XS_Mob_GetAC, file, "$"); + newXSproto(strcpy(buf, "GetAGI"), XS_Mob_GetAGI, file, "$"); + newXSproto(strcpy(buf, "GetATK"), XS_Mob_GetATK, file, "$"); + newXSproto(strcpy(buf, "GetActSpellCasttime"), XS_Mob_GetActSpellCasttime, file, "$$$"); + newXSproto(strcpy(buf, "GetActSpellCost"), XS_Mob_GetActSpellCost, file, "$$$"); + newXSproto(strcpy(buf, "GetActSpellDamage"), XS_Mob_GetActSpellDamage, file, "$$$"); + newXSproto(strcpy(buf, "GetActSpellDuration"), XS_Mob_GetActSpellDuration, file, "$$$"); + newXSproto(strcpy(buf, "GetActSpellHealing"), XS_Mob_GetActSpellHealing, file, "$$$"); + newXSproto(strcpy(buf, "GetActSpellRange"), XS_Mob_GetActSpellRange, file, "$$$"); + newXSproto(strcpy(buf, "GetAggroRange"), XS_Mob_GetAggroRange, file, "$"); + newXSproto(strcpy(buf, "GetAllowBeneficial"), XS_Mob_GetAllowBeneficial, file, "$$"); + newXSproto(strcpy(buf, "GetAppearance"), XS_Mob_GetAppearance, file, "$"); + newXSproto(strcpy(buf, "GetArmorTint"), XS_Mob_GetArmorTint, file, "$$"); + newXSproto(strcpy(buf, "GetAssistRange"), XS_Mob_GetAssistRange, file, "$"); + newXSproto(strcpy(buf, "GetBaseGender"), XS_Mob_GetBaseGender, file, "$"); + newXSproto(strcpy(buf, "GetBaseRace"), XS_Mob_GetBaseRace, file, "$"); + newXSproto(strcpy(buf, "GetBaseSize"), XS_Mob_GetBaseSize, file, "$"); + newXSproto(strcpy(buf, "GetBeard"), XS_Mob_GetBeard, file, "$"); + newXSproto(strcpy(buf, "GetBeardColor"), XS_Mob_GetBeardColor, file, "$"); + newXSproto(strcpy(buf, "GetBodyType"), XS_Mob_GetBodyType, file, "$"); newXSproto(strcpy(buf, "GetBucket"), XS_Mob_GetBucket, file, "$$"); newXSproto(strcpy(buf, "GetBucketExpires"), XS_Mob_GetBucketExpires, file, "$$"); newXSproto(strcpy(buf, "GetBucketKey"), XS_Mob_GetBucketKey, file, "$"); newXSproto(strcpy(buf, "GetBucketRemaining"), XS_Mob_GetBucketRemaining, file, "$$"); - newXSproto(strcpy(buf, "SetBucket"), XS_Mob_SetBucket, file, "$$$;$"); + newXSproto(strcpy(buf, "GetBuffSlotFromType"), XS_Mob_GetBuffSlotFromType, file, "$$"); + newXSproto(strcpy(buf, "GetCHA"), XS_Mob_GetCHA, file, "$"); + newXSproto(strcpy(buf, "GetCR"), XS_Mob_GetCR, file, "$"); + newXSproto(strcpy(buf, "GetCasterLevel"), XS_Mob_GetCasterLevel, file, "$$"); + newXSproto(strcpy(buf, "GetClass"), XS_Mob_GetClass, file, "$"); + newXSproto(strcpy(buf, "GetClassLevelFactor"), XS_Mob_GetClassLevelFactor, file, "$"); + newXSproto(strcpy(buf, "GetClassName"), XS_Mob_GetClassName, file, "$"); + newXSproto(strcpy(buf, "GetCleanName"), XS_Mob_GetCleanName, file, "$"); + newXSproto(strcpy(buf, "GetCorruption"), XS_Mob_GetCorruption, file, "$"); + newXSproto(strcpy(buf, "GetDEX"), XS_Mob_GetDEX, file, "$"); + newXSproto(strcpy(buf, "GetDR"), XS_Mob_GetDR, file, "$"); + newXSproto(strcpy(buf, "GetDamageAmount"), XS_Mob_GetDamageAmount, file, "$$"); + newXSproto(strcpy(buf, "GetDeity"), XS_Mob_GetDeity, file, "$"); + newXSproto(strcpy(buf, "GetDisplayAC"), XS_Mob_GetDisplayAC, file, "$"); + newXSproto(strcpy(buf, "GetDrakkinDetails"), XS_Mob_GetDrakkinDetails, file, "$"); + newXSproto(strcpy(buf, "GetDrakkinHeritage"), XS_Mob_GetDrakkinHeritage, file, "$"); + newXSproto(strcpy(buf, "GetDrakkinTattoo"), XS_Mob_GetDrakkinTattoo, file, "$"); + newXSproto(strcpy(buf, "GetEntityVariable"), XS_Mob_GetEntityVariable, file, "$$"); + newXSproto(strcpy(buf, "GetEquipment"), XS_Mob_GetEquipment, file, "$$"); + newXSproto(strcpy(buf, "GetEquipmentColor"), XS_Mob_GetEquipmentColor, file, "$$"); + newXSproto(strcpy(buf, "GetEquipmentMaterial"), XS_Mob_GetEquipmentMaterial, file, "$$"); + newXSproto(strcpy(buf, "GetEyeColor1"), XS_Mob_GetEyeColor1, file, "$"); + newXSproto(strcpy(buf, "GetEyeColor2"), XS_Mob_GetEyeColor2, file, "$"); + newXSproto(strcpy(buf, "GetFR"), XS_Mob_GetFR, file, "$"); + newXSproto(strcpy(buf, "GetFlurryChance"), XS_Mob_GetFlurryChance, file, "$"); + newXSproto(strcpy(buf, "GetFollowID"), XS_Mob_GetFollowID, file, "$"); + newXSproto(strcpy(buf, "GetGender"), XS_Mob_GetGender, file, "$"); + newXSproto(strcpy(buf, "GetGlobal"), XS_Mob_GetGlobal, file, "$$"); + newXSproto(strcpy(buf, "GetHP"), XS_Mob_GetHP, file, "$"); + newXSproto(strcpy(buf, "GetHPRatio"), XS_Mob_GetHPRatio, file, "$"); + newXSproto(strcpy(buf, "GetHairColor"), XS_Mob_GetHairColor, file, "$"); + newXSproto(strcpy(buf, "GetHairStyle"), XS_Mob_GetHairStyle, file, "$"); + newXSproto(strcpy(buf, "GetHandToHandDamage"), XS_Mob_GetHandToHandDamage, file, "$"); + newXSproto(strcpy(buf, "GetHandToHandDelay"), XS_Mob_GetHandToHandDelay, file, "$"); + newXSproto(strcpy(buf, "GetHaste"), XS_Mob_GetHaste, file, "$"); + newXSproto(strcpy(buf, "GetHateAmount"), XS_Mob_GetHateAmount, file, "$$;$"); newXSproto(strcpy(buf, "GetHateClosest"), XS_Mob_GetHateClosest, file, "$"); + newXSproto(strcpy(buf, "GetHateDamageTop"), XS_Mob_GetHateDamageTop, file, "$$"); + newXSproto(strcpy(buf, "GetHateList"), XS_Mob_GetHateList, file, "$"); newXSproto(strcpy(buf, "GetHateListByDistance"), XS_Mob_GetHateListByDistance, file, "$;$"); + newXSproto(strcpy(buf, "GetHateRandom"), XS_Mob_GetHateRandom, file, "$"); +#ifdef BOTS + newXSproto(strcpy(buf, "GetHateRandomBot"), XS_Mob_GetHateRandomBot, file, "$"); +#endif + newXSproto(strcpy(buf, "GetHateRandomClient"), XS_Mob_GetHateRandomClient, file, "$"); + newXSproto(strcpy(buf, "GetHateRandomNPC"), XS_Mob_GetHateRandomNPC, file, "$"); + newXSproto(strcpy(buf, "GetHateTop"), XS_Mob_GetHateTop, file, "$"); + newXSproto(strcpy(buf, "GetHeading"), XS_Mob_GetHeading, file, "$"); + newXSproto(strcpy(buf, "GetHelmTexture"), XS_Mob_GetHelmTexture, file, "$"); + newXSproto(strcpy(buf, "GetHerosForgeModel"), XS_Mob_GetHerosForgeModel, file, "$$"); + newXSproto(strcpy(buf, "GetID"), XS_Mob_GetID, file, "$"); + newXSproto(strcpy(buf, "GetINT"), XS_Mob_GetINT, file, "$"); + newXSproto(strcpy(buf, "GetInvul"), XS_Mob_GetInvul, file, "$"); + newXSproto(strcpy(buf, "GetItemHPBonuses"), XS_Mob_GetItemHPBonuses, file, "$"); + newXSproto(strcpy(buf, "GetItemStat"), XS_Mob_GetItemStat, file, "$$$"); + newXSproto(strcpy(buf, "GetLastName"), XS_Mob_GetLastName, file, "$"); + newXSproto(strcpy(buf, "GetLevel"), XS_Mob_GetLevel, file, "$"); + newXSproto(strcpy(buf, "GetLevelCon"), XS_Mob_GetLevelCon, file, "$$"); + newXSproto(strcpy(buf, "GetLevelHP"), XS_Mob_GetLevelHP, file, "$$"); + newXSproto(strcpy(buf, "GetLuclinFace"), XS_Mob_GetLuclinFace, file, "$"); + newXSproto(strcpy(buf, "GetMR"), XS_Mob_GetMR, file, "$"); + newXSproto(strcpy(buf, "GetMana"), XS_Mob_GetMana, file, "$"); + newXSproto(strcpy(buf, "GetManaRatio"), XS_Mob_GetManaRatio, file, "$"); + newXSproto(strcpy(buf, "GetMaxAGI"), XS_Mob_GetMaxAGI, file, "$"); + newXSproto(strcpy(buf, "GetMaxCHA"), XS_Mob_GetMaxCHA, file, "$"); + newXSproto(strcpy(buf, "GetMaxDEX"), XS_Mob_GetMaxDEX, file, "$"); + newXSproto(strcpy(buf, "GetMaxHP"), XS_Mob_GetMaxHP, file, "$"); + newXSproto(strcpy(buf, "GetMaxINT"), XS_Mob_GetMaxINT, file, "$"); + newXSproto(strcpy(buf, "GetMaxMana"), XS_Mob_GetMaxMana, file, "$"); + newXSproto(strcpy(buf, "GetMaxSTA"), XS_Mob_GetMaxSTA, file, "$"); + newXSproto(strcpy(buf, "GetMaxSTR"), XS_Mob_GetMaxSTR, file, "$"); + newXSproto(strcpy(buf, "GetMaxWIS"), XS_Mob_GetMaxWIS, file, "$"); + newXSproto(strcpy(buf, "GetMeleeMitigation"), XS_Mob_GetMeleeMitigation, file, "$"); + newXSproto(strcpy(buf, "GetModSkillDmgTaken"), XS_Mob_GetModSkillDmgTaken, file, "$$"); + newXSproto(strcpy(buf, "GetModVulnerability"), XS_Mob_GetModVulnerability, file, "$$"); + newXSproto(strcpy(buf, "GetNPCTypeID"), XS_Mob_GetNPCTypeID, file, "$"); + newXSproto(strcpy(buf, "GetName"), XS_Mob_GetName, file, "$"); + newXSproto(strcpy(buf, "GetNimbusEffect1"), XS_Mob_GetNimbusEffect1, file, "$"); + newXSproto(strcpy(buf, "GetNimbusEffect2"), XS_Mob_GetNimbusEffect2, file, "$"); + newXSproto(strcpy(buf, "GetNimbusEffect3"), XS_Mob_GetNimbusEffect3, file, "$"); + newXSproto(strcpy(buf, "GetOwnerID"), XS_Mob_GetOwnerID, file, "$"); + newXSproto(strcpy(buf, "GetPR"), XS_Mob_GetPR, file, "$"); + newXSproto(strcpy(buf, "GetPetID"), XS_Mob_GetPetID, file, "$"); + newXSproto(strcpy(buf, "GetPetOrder"), XS_Mob_GetPetOrder, file, "$"); + newXSproto(strcpy(buf, "GetPetType"), XS_Mob_GetPetType, file, "$"); + newXSproto(strcpy(buf, "GetPhR"), XS_Mob_GetPhR, file, "$"); + newXSproto(strcpy(buf, "GetRace"), XS_Mob_GetRace, file, "$"); + newXSproto(strcpy(buf, "GetRaceName"), XS_Mob_GetRaceName, file, "$"); + newXSproto(strcpy(buf, "GetResist"), XS_Mob_GetResist, file, "$$"); + newXSproto(strcpy(buf, "GetReverseFactionCon"), XS_Mob_GetReverseFactionCon, file, "$$"); + newXSproto(strcpy(buf, "GetRunAnimSpeed"), XS_Mob_GetRunAnimSpeed, file, "$"); + newXSproto(strcpy(buf, "GetRunspeed"), XS_Mob_GetRunspeed, file, "$"); + newXSproto(strcpy(buf, "GetSTA"), XS_Mob_GetSTA, file, "$"); + newXSproto(strcpy(buf, "GetSTR"), XS_Mob_GetSTR, file, "$"); + newXSproto(strcpy(buf, "GetSize"), XS_Mob_GetSize, file, "$"); + newXSproto(strcpy(buf, "GetSkill"), XS_Mob_GetSkill, file, "$$"); + newXSproto(strcpy(buf, "GetSkillDmgTaken"), XS_Mob_GetSkillDmgTaken, file, "$$"); + newXSproto(strcpy(buf, "GetSpecialAbility"), XS_Mob_GetSpecialAbility, file, "$$"); + newXSproto(strcpy(buf, "GetSpecialAbilityParam"), XS_Mob_GetSpecialAbilityParam, file, "$$$"); + newXSproto(strcpy(buf, "GetSpecializeSkillValue"), XS_Mob_GetSpecializeSkillValue, file, "$$"); + newXSproto(strcpy(buf, "GetSpellHPBonuses"), XS_Mob_GetSpellHPBonuses, file, "$"); + newXSproto(strcpy(buf, "GetSpellIDFromSlot"), XS_Mob_GetSpellIDFromSlot, file, "$$"); + newXSproto(strcpy(buf, "GetSpellStat"), XS_Mob_GetSpellStat, file, "$$$$"); + newXSproto(strcpy(buf, "GetTarget"), XS_Mob_GetTarget, file, "$"); + newXSproto(strcpy(buf, "GetTexture"), XS_Mob_GetTexture, file, "$"); + newXSproto(strcpy(buf, "GetWIS"), XS_Mob_GetWIS, file, "$"); + newXSproto(strcpy(buf, "GetWalkspeed"), XS_Mob_GetWalkspeed, file, "$"); + newXSproto(strcpy(buf, "GetWaypointH"), XS_Mob_GetWaypointH, file, "$"); + newXSproto(strcpy(buf, "GetWaypointID"), XS_Mob_GetWaypointID, file, "$"); + newXSproto(strcpy(buf, "GetWaypointPause"), XS_Mob_GetWaypointPause, file, "$"); + newXSproto(strcpy(buf, "GetWaypointX"), XS_Mob_GetWaypointX, file, "$"); + newXSproto(strcpy(buf, "GetWaypointY"), XS_Mob_GetWaypointY, file, "$"); + newXSproto(strcpy(buf, "GetWaypointZ"), XS_Mob_GetWaypointZ, file, "$"); + newXSproto(strcpy(buf, "GetX"), XS_Mob_GetX, file, "$"); + newXSproto(strcpy(buf, "GetY"), XS_Mob_GetY, file, "$"); + newXSproto(strcpy(buf, "GetZ"), XS_Mob_GetZ, file, "$"); + newXSproto(strcpy(buf, "GetZoneID"), XS_Mob_GetZoneID, file, "$"); + newXSproto(strcpy(buf, "GoToBind"), XS_Mob_GoToBind, file, "$"); + newXSproto(strcpy(buf, "HalveAggro"), XS_Mob_HalveAggro, file, "$$"); + newXSproto(strcpy(buf, "HasNPCSpecialAtk"), XS_Mob_HasNPCSpecialAtk, file, "$$"); + newXSproto(strcpy(buf, "HasOwner"), XS_Mob_HasOwner, file, "$"); + newXSproto(strcpy(buf, "HasPet"), XS_Mob_HasPet, file, "$"); + newXSproto(strcpy(buf, "HasProcs"), XS_Mob_HasProcs, file, "$"); + newXSproto(strcpy(buf, "HasShieldEquiped"), XS_Mob_HasShieldEquiped, file, "$"); + newXSproto(strcpy(buf, "HasTwoHandBluntEquiped"), XS_Mob_HasTwoHandBluntEquiped, file, "$"); + newXSproto(strcpy(buf, "HasTwoHanderEquipped"), XS_Mob_HasTwoHanderEquipped, file, "$"); + newXSproto(strcpy(buf, "HateSummon"), XS_Mob_HateSummon, file, "$"); + newXSproto(strcpy(buf, "Heal"), XS_Mob_Heal, file, "$"); + newXSproto(strcpy(buf, "HealDamage"), XS_Mob_HealDamage, file, "$$;$"); + newXSproto(strcpy(buf, "InterruptSpell"), XS_Mob_InterruptSpell, file, "$;$"); + newXSproto(strcpy(buf, "IsAIControlled"), XS_Mob_IsAIControlled, file, "$"); + newXSproto(strcpy(buf, "IsAmnesiad"), XS_Mob_IsAmnesiad, file, "$"); + newXSproto(strcpy(buf, "IsBeacon"), XS_Mob_IsBeacon, file, "$"); + newXSproto(strcpy(buf, "IsBeneficialAllowed"), XS_Mob_IsBeneficialAllowed, file, "$$"); + newXSproto(strcpy(buf, "IsBlind"), XS_Mob_IsBlind, file, "$"); + newXSproto(strcpy(buf, "IsBot"), XS_Mob_IsBot, file, "$"); + newXSproto(strcpy(buf, "IsCasting"), XS_Mob_IsCasting, file, "$"); + newXSproto(strcpy(buf, "IsClient"), XS_Mob_IsClient, file, "$"); + newXSproto(strcpy(buf, "IsCorpse"), XS_Mob_IsCorpse, file, "$"); + newXSproto(strcpy(buf, "IsDoor"), XS_Mob_IsDoor, file, "$"); + newXSproto(strcpy(buf, "IsEliteMaterialItem"), XS_Mob_IsEliteMaterialItem, file, "$$"); + newXSproto(strcpy(buf, "IsEngaged"), XS_Mob_IsEngaged, file, "$"); + newXSproto(strcpy(buf, "IsEnraged"), XS_Mob_IsEnraged, file, "$"); + newXSproto(strcpy(buf, "IsFeared"), XS_Mob_IsFeared, file, "$"); + newXSproto(strcpy(buf, "IsHorse"), XS_Mob_IsHorse, file, "$"); + newXSproto(strcpy(buf, "IsImmuneToSpell"), XS_Mob_IsImmuneToSpell, file, "$$$"); + newXSproto(strcpy(buf, "IsInvisible"), XS_Mob_IsInvisible, file, "$;$"); + newXSproto(strcpy(buf, "IsMeleeDisabled"), XS_Mob_IsMeleeDisabled, file, "$"); + newXSproto(strcpy(buf, "IsMezzed"), XS_Mob_IsMezzed, file, "$"); + newXSproto(strcpy(buf, "IsMob"), XS_Mob_IsMob, file, "$"); + newXSproto(strcpy(buf, "IsMoving"), XS_Mob_IsMoving, file, "$"); + newXSproto(strcpy(buf, "IsNPC"), XS_Mob_IsNPC, file, "$"); + newXSproto(strcpy(buf, "IsNPCCorpse"), XS_Mob_IsNPCCorpse, file, "$"); + newXSproto(strcpy(buf, "IsObject"), XS_Mob_IsObject, file, "$"); + newXSproto(strcpy(buf, "IsPet"), XS_Mob_IsPet, file, "$"); + newXSproto(strcpy(buf, "IsPlayerCorpse"), XS_Mob_IsPlayerCorpse, file, "$"); + newXSproto(strcpy(buf, "IsRoamer"), XS_Mob_IsRoamer, file, "$"); + newXSproto(strcpy(buf, "IsRooted"), XS_Mob_IsRooted, file, "$"); + newXSproto(strcpy(buf, "IsRunning"), XS_Mob_IsRunning, file, "$"); + newXSproto(strcpy(buf, "IsSilenced"), XS_Mob_IsSilenced, file, "$"); + newXSproto(strcpy(buf, "IsStunned"), XS_Mob_IsStunned, file, "$"); + newXSproto(strcpy(buf, "IsTargetable"), XS_Mob_IsTargetable, file, "$"); + newXSproto(strcpy(buf, "IsTargeted"), XS_Mob_IsTargeted, file, "$"); + newXSproto(strcpy(buf, "IsTrap"), XS_Mob_IsTrap, file, "$"); + newXSproto(strcpy(buf, "IsWarriorClass"), XS_Mob_IsWarriorClass, file, "$"); + newXSproto(strcpy(buf, "Kill"), XS_Mob_Kill, file, "$"); + newXSproto(strcpy(buf, "MakePet"), XS_Mob_MakePet, file, "$$$;$"); + newXSproto(strcpy(buf, "MakeTempPet"), XS_Mob_MakeTempPet, file, "$$;$$$$"); + newXSproto(strcpy(buf, "Mesmerize"), XS_Mob_Mesmerize, file, "$"); + newXSproto(strcpy(buf, "Message"), XS_Mob_Message, file, "$$$;@"); + newXSproto(strcpy(buf, "Message_StringID"), XS_Mob_Message_StringID, file, "$$$;$"); + newXSproto(strcpy(buf, "ModSkillDmgTaken"), XS_Mob_ModSkillDmgTaken, file, "$$$"); + newXSproto(strcpy(buf, "ModVulnerability"), XS_Mob_ModVulnerability, file, "$$$"); + newXSproto(strcpy(buf, "NPCSpecialAttacks"), XS_Mob_NPCSpecialAttacks, file, "$$$;$$"); + newXSproto(strcpy(buf, "NavigateTo"), XS_Mob_NavigateTo, file, "$$$$"); + newXSproto(strcpy(buf, "ProcessSpecialAbilities"), XS_Mob_ProcessSpecialAbilities, file, "$$"); + newXSproto(strcpy(buf, "ProjectileAnim"), XS_Mob_ProjectileAnim, file, "$$$;$$$$$$"); + newXSproto(strcpy(buf, "RandomizeFeatures"), XS_Mob_RandomizeFeatures, file, "$$;$"); + newXSproto(strcpy(buf, "RangedAttack"), XS_Mob_RangedAttack, file, "$$"); + newXSproto(strcpy(buf, "RemoveAllAppearanceEffects"), XS_Mob_RemoveAllAppearanceEffects, file, "$"); + newXSproto(strcpy(buf, "RemoveAllNimbusEffects"), XS_Mob_RemoveAllNimbusEffects, file, "$"); + newXSproto(strcpy(buf, "RemoveFromFeignMemory"), XS_Mob_RemoveFromFeignMemory, file, "$$"); + newXSproto(strcpy(buf, "RemoveNimbusEffect"), XS_Mob_RemoveNimbusEffect, file, "$$"); + newXSproto(strcpy(buf, "RemovePet"), XS_Mob_RemovePet, file, "$"); + newXSproto(strcpy(buf, "ResistSpell"), XS_Mob_ResistSpell, file, "$$$$"); + newXSproto(strcpy(buf, "RogueAssassinate"), XS_Mob_RogueAssassinate, file, "$$"); + newXSproto(strcpy(buf, "RunTo"), XS_Mob_RunTo, file, "$$$$"); + newXSproto(strcpy(buf, "Say"), XS_Mob_Say, file, "$$;@"); + newXSproto(strcpy(buf, "SeeHide"), XS_Mob_SeeHide, file, "$"); + newXSproto(strcpy(buf, "SeeImprovedHide"), XS_Mob_SeeImprovedHide, file, "$"); + newXSproto(strcpy(buf, "SeeInvisible"), XS_Mob_SeeInvisible, file, "$"); + newXSproto(strcpy(buf, "SeeInvisibleUndead"), XS_Mob_SeeInvisibleUndead, file, "$"); + newXSproto(strcpy(buf, "SendAppearanceEffect"), XS_Mob_SendAppearanceEffect, file, "$$;$$$$$$$$$$$$$$"); + newXSproto(strcpy(buf, "SendAppearanceEffectActor"), XS_Mob_SendAppearanceEffectActor, file, "$$$;$$$$$$$$$"); + newXSproto(strcpy(buf, "SendAppearanceEffectGround"), XS_Mob_SendAppearanceEffectGround, file, "$$$;$$$$$$$$$"); + newXSproto(strcpy(buf, "SendIllusion"), XS_Mob_SendIllusion, file, "$$;$$$$$$$$$$$$"); + newXSproto(strcpy(buf, "SendTo"), XS_Mob_SendTo, file, "$$$$"); + newXSproto(strcpy(buf, "SendToFixZ"), XS_Mob_SendToFixZ, file, "$$$$"); + newXSproto(strcpy(buf, "SendWearChange"), XS_Mob_SendWearChange, file, "$$"); + newXSproto(strcpy(buf, "SetAA"), XS_Mob_SetAA, file, "$$$;$"); + newXSproto(strcpy(buf, "SetAllowBeneficial"), XS_Mob_SetAllowBeneficial, file, "$$"); + newXSproto(strcpy(buf, "SetAppearance"), XS_Mob_SetAppearance, file, "$$;$"); + newXSproto(strcpy(buf, "SetBodyType"), XS_Mob_SetBodyType, file, "$$;$"); + newXSproto(strcpy(buf, "SetBucket"), XS_Mob_SetBucket, file, "$$$;$"); + newXSproto(strcpy(buf, "SetCurrentWP"), XS_Mob_SetCurrentWP, file, "$$"); + newXSproto(strcpy(buf, "SetDeltas"), XS_Mob_SetDeltas, file, "$$$$$"); + newXSproto(strcpy(buf, "SetDisableMelee"), XS_Mob_SetDisableMelee, file, "$$"); + newXSproto(strcpy(buf, "SetEntityVariable"), XS_Mob_SetEntityVariable, file, "$$$"); + newXSproto(strcpy(buf, "SetExtraHaste"), XS_Mob_SetExtraHaste, file, "$$"); + newXSproto(strcpy(buf, "SetFlurryChance"), XS_Mob_SetFlurryChance, file, "$$"); + newXSproto(strcpy(buf, "SetFlyMode"), XS_Mob_SetFlyMode, file, "$$"); + newXSproto(strcpy(buf, "SetFollowID"), XS_Mob_SetFollowID, file, "$$"); + newXSproto(strcpy(buf, "SetGender"), XS_Mob_SetGender, file, "$$"); + newXSproto(strcpy(buf, "SetGlobal"), XS_Mob_SetGlobal, file, "$$$$$;$"); + newXSproto(strcpy(buf, "SetHP"), XS_Mob_SetHP, file, "$$"); + newXSproto(strcpy(buf, "SetHate"), XS_Mob_SetHate, file, "$$;$$"); + newXSproto(strcpy(buf, "SetHeading"), XS_Mob_SetHeading, file, "$$"); + newXSproto(strcpy(buf, "SetInvisible"), XS_Mob_SetInvisible, file, "$$"); + newXSproto(strcpy(buf, "SetInvul"), XS_Mob_SetInvul, file, "$$"); + newXSproto(strcpy(buf, "SetLD"), XS_Mob_SetLD, file, "$$"); + newXSproto(strcpy(buf, "SetLevel"), XS_Mob_SetLevel, file, "$$;$"); + newXSproto(strcpy(buf, "SetMana"), XS_Mob_SetMana, file, "$$"); + newXSproto(strcpy(buf, "SetMaxHP"), XS_Mob_SetMaxHP, file, "$"); + newXSproto(strcpy(buf, "SetOOCRegen"), XS_Mob_SetOOCRegen, file, "$$"); + newXSproto(strcpy(buf, "SetOwnerID"), XS_Mob_SetOwnerID, file, "$$"); + newXSproto(strcpy(buf, "SetPet"), XS_Mob_SetPet, file, "$$"); + newXSproto(strcpy(buf, "SetPetID"), XS_Mob_SetPetID, file, "$$"); + newXSproto(strcpy(buf, "SetPetOrder"), XS_Mob_SetPetOrder, file, "$$"); + newXSproto(strcpy(buf, "SetRace"), XS_Mob_SetRace, file, "$$"); + newXSproto(strcpy(buf, "SetRunAnimSpeed"), XS_Mob_SetRunAnimSpeed, file, "$$"); + newXSproto(strcpy(buf, "SetRunning"), XS_Mob_SetRunning, file, "$$"); + newXSproto(strcpy(buf, "SetSlotTint"), XS_Mob_SetSlotTint, file, "$$$$$"); + newXSproto(strcpy(buf, "SetSpecialAbility"), XS_Mob_SetSpecialAbility, file, "$$$"); + newXSproto(strcpy(buf, "SetSpecialAbilityParam"), XS_Mob_SetSpecialAbilityParam, file, "$$$$"); + newXSproto(strcpy(buf, "SetTarget"), XS_Mob_SetTarget, file, "$$"); + newXSproto(strcpy(buf, "SetTargetable"), XS_Mob_SetTargetable, file, "$$"); + newXSproto(strcpy(buf, "SetTexture"), XS_Mob_SetTexture, file, "$$"); + newXSproto(strcpy(buf, "ShieldAbility"), XS_Mob_ShieldAbility, file, "$$$$$$$$"); + newXSproto(strcpy(buf, "Shout"), XS_Mob_Shout, file, "$$;@"); + newXSproto(strcpy(buf, "SignalClient"), XS_Mob_SignalClient, file, "$$$"); + newXSproto(strcpy(buf, "SpellEffect"), XS_Mob_SpellEffect, file, "$$;$$$$$$$$"); + newXSproto(strcpy(buf, "SpellFinished"), XS_Mob_SpellFinished, file, "$$;$$"); + newXSproto(strcpy(buf, "Spin"), XS_Mob_Spin, file, "$"); + newXSproto(strcpy(buf, "StartEnrage"), XS_Mob_StartEnrage, file, "$"); + newXSproto(strcpy(buf, "StopNavigation"), XS_Mob_StopNavigation, file, "$"); + newXSproto(strcpy(buf, "Stun"), XS_Mob_Stun, file, "$$"); + newXSproto(strcpy(buf, "TarGlobal"), XS_Mob_TarGlobal, file, "$$$$$$$"); + newXSproto(strcpy(buf, "TempName"), XS_Mob_TempName, file, "$:$"); + newXSproto(strcpy(buf, "ThrowingAttack"), XS_Mob_ThrowingAttack, file, "$$"); + newXSproto(strcpy(buf, "TryMoveAlong"), XS_Mob_TryMoveAlong, file, "$$$;$"); + newXSproto(strcpy(buf, "TypesTempPet"), XS_Mob_TypesTempPet, file, "$$;$$$$$"); + newXSproto(strcpy(buf, "WalkTo"), XS_Mob_WalkTo, file, "$$$$"); + newXSproto(strcpy(buf, "WearChange"), XS_Mob_WearChange, file, "$$$;$$"); + newXSproto(strcpy(buf, "WipeHateList"), XS_Mob_WipeHateList, file, "$"); XSRETURN_YES; } diff --git a/zone/perl_npc.cpp b/zone/perl_npc.cpp index 4dd9b77b4..1b6794d72 100644 --- a/zone/perl_npc.cpp +++ b/zone/perl_npc.cpp @@ -1,30 +1,3 @@ -/* -* This file was generated automatically by xsubpp version 1.9508 from the -* contents of tmp. Do not edit this file, edit tmp instead. -* -* ANY CHANGES MADE HERE WILL BE LOST! -* -*/ - - -/* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2004 EQEMu Development Team (http://eqemulator.net) - - 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 "../common/features.h" #ifdef EMBPERL_XS_CLASSES @@ -802,20 +775,23 @@ XS(XS_NPC_DisplayWaypointInfo); /* prototype to pass -Wmissing-prototypes */ XS(XS_NPC_DisplayWaypointInfo) { dXSARGS; if (items != 2) - Perl_croak(aTHX_ "Usage: NPC::DisplayWaypointInfo(THIS, Client* target)"); // @categories Script Utility + Perl_croak(aTHX_ "Usage: NPC::DisplayWaypointInfo(THIS, Client* client)"); // @categories Script Utility { - NPC *THIS; - Client *to; + NPC *THIS; + Client *client; VALIDATE_THIS_IS_NPC; if (sv_derived_from(ST(1), "Client")) { IV tmp = SvIV((SV *) SvRV(ST(1))); - to = INT2PTR(Client *, tmp); - } else - Perl_croak(aTHX_ "to is not of type Client"); - if (to == nullptr) - Perl_croak(aTHX_ "to is nullptr, avoiding crash."); + client = INT2PTR(Client *, tmp); + } else { + Perl_croak(aTHX_ "client is not of type Client"); + } - THIS->DisplayWaypointInfo(to); + if (!client) { + Perl_croak(aTHX_ "client is nullptr, avoiding crash."); + } + + THIS->DisplayWaypointInfo(client); } XSRETURN_EMPTY; } @@ -1459,7 +1435,7 @@ XS(XS_NPC_GetAvoidanceRating); /* prototype to pass -Wmissing-prototypes */ XS(XS_NPC_GetAvoidanceRating) { dXSARGS; if (items != 1) - Perl_croak(aTHX_ "Usage: NPC::GetAvoidanceyRating(THIS)"); // @categories Stats and Attributes + Perl_croak(aTHX_ "Usage: NPC::GetAvoidanceRating(THIS)"); // @categories Stats and Attributes { NPC *THIS; int32 RETVAL; @@ -1725,6 +1701,200 @@ XS(XS_NPC_ScaleNPC) { XSRETURN_EMPTY; } +XS(XS_NPC_IsRaidTarget); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_IsRaidTarget) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::IsRaidTarget(THIS)"); // @categories Stats and Attributes + { + NPC *THIS; + bool is_raid_target; + VALIDATE_THIS_IS_NPC; + is_raid_target = THIS->IsRaidTarget(); + ST(0) = boolSV(is_raid_target); + sv_2mortal(ST(0)); + } + XSRETURN(1); +} + +XS(XS_NPC_HasItem); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_HasItem) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: NPC::HasItem(THIS, uint32 item_id)"); // @categories Script Utility + { + NPC *THIS; + bool has_item = false; + uint32 item_id = (uint32) SvUV(ST(1)); + VALIDATE_THIS_IS_NPC; + has_item = THIS->HasItem(item_id); + ST(0) = boolSV(has_item); + sv_2mortal(ST(0)); + } + XSRETURN(1); +} + +XS(XS_NPC_CountItem); +XS(XS_NPC_CountItem) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: NPC::CountItem(THIS, uint32 item_id)"); // @categories Script Utility + { + NPC *THIS; + uint16 item_count = 0; + uint32 item_id = (uint32) SvUV(ST(1)); + dXSTARG; + VALIDATE_THIS_IS_NPC; + item_count = THIS->CountItem(item_id); + XSprePUSH; + PUSHu((UV) item_count); + } + XSRETURN(1); +} + +XS(XS_NPC_GetItemIDBySlot); +XS(XS_NPC_GetItemIDBySlot) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: NPC::GetItemIDBySlot(THIS, uint16 loot_slot)"); // @categories Script Utility + { + NPC *THIS; + uint32 item_id = 0; + uint16 loot_slot = (uint16) SvUV(ST(1)); + dXSTARG; + VALIDATE_THIS_IS_NPC; + item_id = THIS->GetItemIDBySlot(loot_slot); + XSprePUSH; + PUSHu((UV) item_id); + } + XSRETURN(1); +} + +XS(XS_NPC_GetFirstSlotByItemID); +XS(XS_NPC_GetFirstSlotByItemID) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: NPC::GetFirstSlotByItemID(THIS, uint32 item_id)"); // @categories Script Utility + { + NPC *THIS; + uint16 loot_slot = 0; + uint32 item_id = (uint32) SvUV(ST(1)); + dXSTARG; + VALIDATE_THIS_IS_NPC; + loot_slot = THIS->GetFirstSlotByItemID(item_id); + XSprePUSH; + PUSHu((UV) loot_slot); + } + XSRETURN(1); +} + +XS(XS_NPC_GetHealScale); +XS(XS_NPC_GetHealScale) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetHealScale(THIS)"); // @categories Stats and Attributes + { + NPC *THIS; + float healscale; + dXSTARG; + VALIDATE_THIS_IS_NPC; + healscale = THIS->GetHealScale(); + XSprePUSH; + PUSHn((double) healscale); + } + XSRETURN(1); +} + +XS(XS_NPC_GetSpellScale); +XS(XS_NPC_GetSpellScale) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetSpellScale(THIS)"); // @categories Stats and Attributes + { + NPC *THIS; + float spellscale; + dXSTARG; + VALIDATE_THIS_IS_NPC; + spellscale = THIS->GetSpellScale(); + XSprePUSH; + PUSHn((double) spellscale); + } + XSRETURN(1); +} + +XS(XS_NPC_GetLootList); +XS(XS_NPC_GetLootList) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetLootList(THIS)"); // @categories Script Utility + { + NPC *THIS; + VALIDATE_THIS_IS_NPC; + auto npc_items = THIS->GetLootList(); + auto item_count = npc_items.size(); + if (item_count > 0) { + EXTEND(sp, item_count); + for (int index = 0; index < item_count; ++index) { + ST(index) = sv_2mortal(newSVuv(npc_items[index])); + } + XSRETURN(item_count); + } + SV* return_value = &PL_sv_undef; + ST(0) = return_value; + XSRETURN(1); + } +} + +XS(XS_NPC_AddAISpellEffect); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_AddAISpellEffect) { + dXSARGS; + if (items != 5) + Perl_croak(aTHX_ "Usage: NPC::AddAISpellEffect(THIS, int spell_effect_id, int base_value, int limit_value, int max_value)"); // @categories Spells and Disciplines + { + NPC *THIS; + + int spell_effect_id = (int) SvIV(ST(1)); + int base_value = (int) SvIV(ST(2)); + int limit_value = (int) SvIV(ST(3)); + int max_value = (int) SvIV(ST(4)); + + VALIDATE_THIS_IS_NPC; + THIS->AddSpellEffectToNPCList(spell_effect_id, base_value, limit_value, max_value, true); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_RemoveAISpellEffect); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_RemoveAISpellEffect) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: NPC::RemoveAISpellEffect(THIS, int spell_effect_id)"); // @categories Spells and Disciplines + { + NPC *THIS; + int spell_effect_id = (int) SvIV(ST(1)); + VALIDATE_THIS_IS_NPC; + THIS->RemoveSpellEffectFromNPCList(spell_effect_id, true); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_HasAISpellEffect); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_HasAISpellEffect) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: NPC::HasAISpellEffect(THIS, int spell_effect_id)"); // @categories Spells and Disciplines + { + NPC *THIS; + bool has_spell_effect = false; + int spell_effect_id = (int) SvIV(ST(1)); + VALIDATE_THIS_IS_NPC; + has_spell_effect = THIS->HasAISpellEffect(spell_effect_id); + ST(0) = boolSV(has_spell_effect); + sv_2mortal(ST(0)); + } + XSRETURN(1); +} + #ifdef __cplusplus extern "C" #endif @@ -1742,106 +1912,116 @@ XS(boot_NPC) { //add the strcpy stuff to get rid of const warnings.... XS_VERSION_BOOTCHECK; - - newXSproto(strcpy(buf, "SignalNPC"), XS_NPC_SignalNPC, file, "$$"); - newXSproto(strcpy(buf, "CheckNPCFactionAlly"), XS_NPC_CheckNPCFactionAlly, file, "$$"); + newXSproto(strcpy(buf, "AI_SetRoambox"), XS_NPC_AI_SetRoambox, file, "$$$$$$;$$"); + newXSproto(strcpy(buf, "AddAISpell"), XS_NPC_AddSpellToNPCList, file, "$$$$$$$"); + newXSproto(strcpy(buf, "AddAISpellEffect"), XS_NPC_AddAISpellEffect, file, "$$$$$"); + newXSproto(strcpy(buf, "AddCash"), XS_NPC_AddCash, file, "$$$$$"); + newXSproto(strcpy(buf, "AddDefensiveProc"), XS_NPC_AddDefensiveProc, file, "$$$"); newXSproto(strcpy(buf, "AddItem"), XS_NPC_AddItem, file, "$$;$$$$$$$$"); newXSproto(strcpy(buf, "AddLootTable"), XS_NPC_AddLootTable, file, "$"); - newXSproto(strcpy(buf, "RemoveItem"), XS_NPC_RemoveItem, file, "$$;$$"); - newXSproto(strcpy(buf, "ClearItemList"), XS_NPC_ClearItemList, file, "$"); - newXSproto(strcpy(buf, "AddCash"), XS_NPC_AddCash, file, "$$$$$"); - newXSproto(strcpy(buf, "RemoveCash"), XS_NPC_RemoveCash, file, "$"); - newXSproto(strcpy(buf, "CountLoot"), XS_NPC_CountLoot, file, "$"); - newXSproto(strcpy(buf, "GetLoottableID"), XS_NPC_GetLoottableID, file, "$"); - newXSproto(strcpy(buf, "GetCopper"), XS_NPC_GetCopper, file, "$"); - newXSproto(strcpy(buf, "GetSilver"), XS_NPC_GetSilver, file, "$"); - newXSproto(strcpy(buf, "GetGold"), XS_NPC_GetGold, file, "$"); - newXSproto(strcpy(buf, "GetPlatinum"), XS_NPC_GetPlatinum, file, "$"); - newXSproto(strcpy(buf, "SetCopper"), XS_NPC_SetCopper, file, "$$"); - newXSproto(strcpy(buf, "SetSilver"), XS_NPC_SetSilver, file, "$$"); - newXSproto(strcpy(buf, "SetGold"), XS_NPC_SetGold, file, "$$"); - newXSproto(strcpy(buf, "SetPlatinum"), XS_NPC_SetPlatinum, file, "$$"); - newXSproto(strcpy(buf, "SetGrid"), XS_NPC_SetGrid, file, "$$"); - newXSproto(strcpy(buf, "SetSaveWaypoint"), XS_NPC_SetSaveWaypoint, file, "$$"); - newXSproto(strcpy(buf, "SetSp2"), XS_NPC_SetSp2, file, "$$"); - newXSproto(strcpy(buf, "GetWaypointMax"), XS_NPC_GetWaypointMax, file, "$"); - newXSproto(strcpy(buf, "GetGrid"), XS_NPC_GetGrid, file, "$"); - newXSproto(strcpy(buf, "GetSp2"), XS_NPC_GetSp2, file, "$"); - newXSproto(strcpy(buf, "GetNPCFactionID"), XS_NPC_GetNPCFactionID, file, "$"); - newXSproto(strcpy(buf, "GetPrimaryFaction"), XS_NPC_GetPrimaryFaction, file, "$"); - newXSproto(strcpy(buf, "GetNPCHate"), XS_NPC_GetNPCHate, file, "$$"); - newXSproto(strcpy(buf, "IsOnHatelist"), XS_NPC_IsOnHatelist, file, "$$"); - newXSproto(strcpy(buf, "RemoveFromHateList"), XS_NPC_RemoveFromHateList, file, "$$"); - newXSproto(strcpy(buf, "SetNPCFactionID"), XS_NPC_SetNPCFactionID, file, "$$"); - newXSproto(strcpy(buf, "GetMaxDMG"), XS_NPC_GetMaxDMG, file, "$"); - newXSproto(strcpy(buf, "GetMinDMG"), XS_NPC_GetMinDMG, file, "$"); - newXSproto(strcpy(buf, "IsAnimal"), XS_NPC_IsAnimal, file, "$"); - newXSproto(strcpy(buf, "GetPetSpellID"), XS_NPC_GetPetSpellID, file, "$"); - newXSproto(strcpy(buf, "SetPetSpellID"), XS_NPC_SetPetSpellID, file, "$$"); - newXSproto(strcpy(buf, "GetMaxDamage"), XS_NPC_GetMaxDamage, file, "$$"); - newXSproto(strcpy(buf, "SetTaunting"), XS_NPC_SetTaunting, file, "$$"); - newXSproto(strcpy(buf, "IsTaunting"), XS_NPC_IsTaunting, file, "$"); - newXSproto(strcpy(buf, "PickPocket"), XS_NPC_PickPocket, file, "$$"); - newXSproto(strcpy(buf, "StartSwarmTimer"), XS_NPC_StartSwarmTimer, file, "$$"); - newXSproto(strcpy(buf, "DoClassAttacks"), XS_NPC_DoClassAttacks, file, "$$"); - newXSproto(strcpy(buf, "GetMaxWp"), XS_NPC_GetMaxWp, file, "$"); - newXSproto(strcpy(buf, "DisplayWaypointInfo"), XS_NPC_DisplayWaypointInfo, file, "$$"); - newXSproto(strcpy(buf, "CalculateNewWaypoint"), XS_NPC_CalculateNewWaypoint, file, "$"); + newXSproto(strcpy(buf, "AddMeleeProc"), XS_NPC_AddMeleeProc, file, "$$$"); + newXSproto(strcpy(buf, "AddRangedProc"), XS_NPC_AddRangedProc, file, "$$$"); newXSproto(strcpy(buf, "AssignWaypoints"), XS_NPC_AssignWaypoints, file, "$$"); - newXSproto(strcpy(buf, "SetWaypointPause"), XS_NPC_SetWaypointPause, file, "$"); - newXSproto(strcpy(buf, "UpdateWaypoint"), XS_NPC_UpdateWaypoint, file, "$$"); - newXSproto(strcpy(buf, "StopWandering"), XS_NPC_StopWandering, file, "$"); - newXSproto(strcpy(buf, "ResumeWandering"), XS_NPC_ResumeWandering, file, "$"); - newXSproto(strcpy(buf, "PauseWandering"), XS_NPC_PauseWandering, file, "$$"); - newXSproto(strcpy(buf, "MoveTo"), XS_NPC_MoveTo, file, "$$$$"); - newXSproto(strcpy(buf, "NextGuardPosition"), XS_NPC_NextGuardPosition, file, "$"); - newXSproto(strcpy(buf, "SaveGuardSpot"), XS_NPC_SaveGuardSpot, file, "$$$$$"); - newXSproto(strcpy(buf, "IsGuarding"), XS_NPC_IsGuarding, file, "$"); - newXSproto(strcpy(buf, "AI_SetRoambox"), XS_NPC_AI_SetRoambox, file, "$$$$$$;$$"); + newXSproto(strcpy(buf, "CalculateNewWaypoint"), XS_NPC_CalculateNewWaypoint, file, "$"); + newXSproto(strcpy(buf, "ChangeLastName"), XS_NPC_ChangeLastName, file, "$:$"); + newXSproto(strcpy(buf, "CheckNPCFactionAlly"), XS_NPC_CheckNPCFactionAlly, file, "$$"); + newXSproto(strcpy(buf, "ClearItemList"), XS_NPC_ClearItemList, file, "$"); + newXSproto(strcpy(buf, "ClearLastName"), XS_NPC_ClearLastName, file, "$"); + newXSproto(strcpy(buf, "CountItem"), XS_NPC_CountItem, file, "$$"); + newXSproto(strcpy(buf, "CountLoot"), XS_NPC_CountLoot, file, "$"); + newXSproto(strcpy(buf, "DisplayWaypointInfo"), XS_NPC_DisplayWaypointInfo, file, "$$"); + newXSproto(strcpy(buf, "DoClassAttacks"), XS_NPC_DoClassAttacks, file, "$$"); + newXSproto(strcpy(buf, "GetAccuracyRating"), XS_NPC_GetAccuracyRating, file, "$"); + newXSproto(strcpy(buf, "GetAttackDelay"), XS_NPC_GetAttackDelay, file, "$"); + newXSproto(strcpy(buf, "GetAttackSpeed"), XS_NPC_GetAttackSpeed, file, "$"); + newXSproto(strcpy(buf, "GetAvoidanceRating"), XS_NPC_GetAvoidanceRating, file, "$"); + newXSproto(strcpy(buf, "GetCombatState"), XS_NPC_GetCombatState, file, "$"); + newXSproto(strcpy(buf, "GetCopper"), XS_NPC_GetCopper, file, "$"); + newXSproto(strcpy(buf, "GetFirstSlotByItemID"), XS_NPC_GetFirstSlotByItemID, file, "$$"); + newXSproto(strcpy(buf, "GetGold"), XS_NPC_GetGold, file, "$"); + newXSproto(strcpy(buf, "GetGrid"), XS_NPC_GetGrid, file, "$"); + newXSproto(strcpy(buf, "GetGuardPointX"), XS_NPC_GetGuardPointX, file, "$"); + newXSproto(strcpy(buf, "GetGuardPointY"), XS_NPC_GetGuardPointY, file, "$"); + newXSproto(strcpy(buf, "GetGuardPointZ"), XS_NPC_GetGuardPointZ, file, "$"); + newXSproto(strcpy(buf, "GetHealScale"), XS_NPC_GetHealScale, file, "$"); + newXSproto(strcpy(buf, "GetItemIDBySlot"), XS_NPC_GetItemIDBySlot, file, "$$"); + newXSproto(strcpy(buf, "GetLootList"), XS_NPC_GetLootList, file, "$"); + newXSproto(strcpy(buf, "GetLoottableID"), XS_NPC_GetLoottableID, file, "$"); + newXSproto(strcpy(buf, "GetMaxDMG"), XS_NPC_GetMaxDMG, file, "$"); + newXSproto(strcpy(buf, "GetMaxDamage"), XS_NPC_GetMaxDamage, file, "$$"); + newXSproto(strcpy(buf, "GetMaxWp"), XS_NPC_GetMaxWp, file, "$"); + newXSproto(strcpy(buf, "GetMinDMG"), XS_NPC_GetMinDMG, file, "$"); + newXSproto(strcpy(buf, "GetNPCFactionID"), XS_NPC_GetNPCFactionID, file, "$"); + newXSproto(strcpy(buf, "GetNPCHate"), XS_NPC_GetNPCHate, file, "$$"); newXSproto(strcpy(buf, "GetNPCSpellsID"), XS_NPC_GetNPCSpellsID, file, "$"); + newXSproto(strcpy(buf, "GetPetSpellID"), XS_NPC_GetPetSpellID, file, "$"); + newXSproto(strcpy(buf, "GetPlatinum"), XS_NPC_GetPlatinum, file, "$"); + newXSproto(strcpy(buf, "GetPrimSkill"), XS_NPC_GetPrimSkill, file, "$"); + newXSproto(strcpy(buf, "GetPrimaryFaction"), XS_NPC_GetPrimaryFaction, file, "$"); + newXSproto(strcpy(buf, "GetScore"), XS_NPC_GetScore, file, "$"); + newXSproto(strcpy(buf, "GetSecSkill"), XS_NPC_GetSecSkill, file, "$"); + newXSproto(strcpy(buf, "GetSilver"), XS_NPC_GetSilver, file, "$"); + newXSproto(strcpy(buf, "GetSlowMitigation"), XS_NPC_GetSlowMitigation, file, "$"); + newXSproto(strcpy(buf, "GetSp2"), XS_NPC_GetSp2, file, "$"); + newXSproto(strcpy(buf, "GetSpawnKillCount"), XS_NPC_GetSpawnKillCount, file, "$"); + newXSproto(strcpy(buf, "GetSpawnPointH"), XS_NPC_GetSpawnPointH, file, "$"); newXSproto(strcpy(buf, "GetSpawnPointID"), XS_NPC_GetSpawnPointID, file, "$"); newXSproto(strcpy(buf, "GetSpawnPointX"), XS_NPC_GetSpawnPointX, file, "$"); newXSproto(strcpy(buf, "GetSpawnPointY"), XS_NPC_GetSpawnPointY, file, "$"); newXSproto(strcpy(buf, "GetSpawnPointZ"), XS_NPC_GetSpawnPointZ, file, "$"); - newXSproto(strcpy(buf, "GetSpawnPointH"), XS_NPC_GetSpawnPointH, file, "$"); - newXSproto(strcpy(buf, "GetGuardPointX"), XS_NPC_GetGuardPointX, file, "$"); - newXSproto(strcpy(buf, "GetGuardPointY"), XS_NPC_GetGuardPointY, file, "$"); - newXSproto(strcpy(buf, "GetGuardPointZ"), XS_NPC_GetGuardPointZ, file, "$"); - newXSproto(strcpy(buf, "SetPrimSkill"), XS_NPC_SetPrimSkill, file, "$$"); - newXSproto(strcpy(buf, "SetSecSkill"), XS_NPC_SetSecSkill, file, "$$"); - newXSproto(strcpy(buf, "GetPrimSkill"), XS_NPC_GetPrimSkill, file, "$"); - newXSproto(strcpy(buf, "GetSecSkill"), XS_NPC_GetSecSkill, file, "$"); - newXSproto(strcpy(buf, "GetSwarmOwner"), XS_NPC_GetSwarmOwner, file, "$"); - newXSproto(strcpy(buf, "GetSwarmTarget"), XS_NPC_GetSwarmTarget, file, "$"); - newXSproto(strcpy(buf, "SetSwarmTarget"), XS_NPC_SetSwarmTarget, file, "$$"); - newXSproto(strcpy(buf, "ModifyNPCStat"), XS_NPC_ModifyNPCStat, file, "$$$"); - newXSproto(strcpy(buf, "AddAISpell"), XS_NPC_AddSpellToNPCList, file, "$$$$$$$"); - newXSproto(strcpy(buf, "RemoveAISpell"), XS_NPC_RemoveSpellFromNPCList, file, "$$"); - newXSproto(strcpy(buf, "SetSpellFocusDMG"), XS_NPC_SetSpellFocusDMG, file, "$$"); - newXSproto(strcpy(buf, "SetSpellFocusHeal"), XS_NPC_SetSpellFocusHeal, file, "$$"); newXSproto(strcpy(buf, "GetSpellFocusDMG"), XS_NPC_GetSpellFocusDMG, file, "$"); newXSproto(strcpy(buf, "GetSpellFocusHeal"), XS_NPC_GetSpellFocusHeal, file, "$"); - newXSproto(strcpy(buf, "GetSlowMitigation"), XS_NPC_GetSlowMitigation, file, "$"); - newXSproto(strcpy(buf, "GetAttackSpeed"), XS_NPC_GetAttackSpeed, file, "$"); - newXSproto(strcpy(buf, "GetAttackDelay"), XS_NPC_GetAttackDelay, file, "$"); - newXSproto(strcpy(buf, "GetAccuracyRating"), XS_NPC_GetAccuracyRating, file, "$"); - newXSproto(strcpy(buf, "GetAvoidanceRating"), XS_NPC_GetAvoidanceRating, file, "$"); - newXSproto(strcpy(buf, "GetSpawnKillCount"), XS_NPC_GetSpawnKillCount, file, "$"); - newXSproto(strcpy(buf, "GetScore"), XS_NPC_GetScore, file, "$"); - newXSproto(strcpy(buf, "MerchantOpenShop"), XS_NPC_MerchantOpenShop, file, "$"); + newXSproto(strcpy(buf, "GetSpellScale"), XS_NPC_GetSpellScale, file, "$"); + newXSproto(strcpy(buf, "GetSwarmOwner"), XS_NPC_GetSwarmOwner, file, "$"); + newXSproto(strcpy(buf, "GetSwarmTarget"), XS_NPC_GetSwarmTarget, file, "$"); + newXSproto(strcpy(buf, "GetWaypointMax"), XS_NPC_GetWaypointMax, file, "$"); + newXSproto(strcpy(buf, "HasAISpellEffect"), XS_NPC_HasAISpellEffect, file, "$$"); + newXSproto(strcpy(buf, "HasItem"), XS_NPC_HasItem, file, "$$"); + newXSproto(strcpy(buf, "IsAnimal"), XS_NPC_IsAnimal, file, "$"); + newXSproto(strcpy(buf, "IsGuarding"), XS_NPC_IsGuarding, file, "$"); + newXSproto(strcpy(buf, "IsOnHatelist"), XS_NPC_IsOnHatelist, file, "$$"); + newXSproto(strcpy(buf, "IsRaidTarget"), XS_NPC_IsRaidTarget, file, "$"); + newXSproto(strcpy(buf, "IsTaunting"), XS_NPC_IsTaunting, file, "$"); newXSproto(strcpy(buf, "MerchantCloseShop"), XS_NPC_MerchantCloseShop, file, "$"); - newXSproto(strcpy(buf, "AddMeleeProc"), XS_NPC_AddMeleeProc, file, "$$$"); - newXSproto(strcpy(buf, "AddRangedProc"), XS_NPC_AddRangedProc, file, "$$$"); - newXSproto(strcpy(buf, "AddDefensiveProc"), XS_NPC_AddDefensiveProc, file, "$$$"); + newXSproto(strcpy(buf, "MerchantOpenShop"), XS_NPC_MerchantOpenShop, file, "$"); + newXSproto(strcpy(buf, "ModifyNPCStat"), XS_NPC_ModifyNPCStat, file, "$$$"); + newXSproto(strcpy(buf, "MoveTo"), XS_NPC_MoveTo, file, "$$$$"); + newXSproto(strcpy(buf, "NextGuardPosition"), XS_NPC_NextGuardPosition, file, "$"); + newXSproto(strcpy(buf, "PauseWandering"), XS_NPC_PauseWandering, file, "$$"); + newXSproto(strcpy(buf, "PickPocket"), XS_NPC_PickPocket, file, "$$"); + newXSproto(strcpy(buf, "RecalculateSkills"), XS_NPC_RecalculateSkills, file, "$"); + newXSproto(strcpy(buf, "RemoveAISpell"), XS_NPC_RemoveSpellFromNPCList, file, "$$"); + newXSproto(strcpy(buf, "RemoveAISpellEffect"), XS_NPC_RemoveAISpellEffect, file, "$$"); + newXSproto(strcpy(buf, "RemoveCash"), XS_NPC_RemoveCash, file, "$"); + newXSproto(strcpy(buf, "RemoveDefensiveProc"), XS_NPC_RemoveDefensiveProc, file, "$$"); + newXSproto(strcpy(buf, "RemoveFromHateList"), XS_NPC_RemoveFromHateList, file, "$$"); + newXSproto(strcpy(buf, "RemoveItem"), XS_NPC_RemoveItem, file, "$$;$$"); newXSproto(strcpy(buf, "RemoveMeleeProc"), XS_NPC_RemoveMeleeProc, file, "$$"); newXSproto(strcpy(buf, "RemoveRangedProc"), XS_NPC_RemoveRangedProc, file, "$$"); - newXSproto(strcpy(buf, "RemoveDefensiveProc"), XS_NPC_RemoveDefensiveProc, file, "$$"); - newXSproto(strcpy(buf, "ChangeLastName"), XS_NPC_ChangeLastName, file, "$:$"); - newXSproto(strcpy(buf, "ClearLastName"), XS_NPC_ClearLastName, file, "$"); - newXSproto(strcpy(buf, "GetCombatState"), XS_NPC_GetCombatState, file, "$"); - newXSproto(strcpy(buf, "SetSimpleRoamBox"), XS_NPC_SetSimpleRoamBox, file, "$$;$$"); - newXSproto(strcpy(buf, "RecalculateSkills"), XS_NPC_RecalculateSkills, file, "$"); + newXSproto(strcpy(buf, "ResumeWandering"), XS_NPC_ResumeWandering, file, "$"); + newXSproto(strcpy(buf, "SaveGuardSpot"), XS_NPC_SaveGuardSpot, file, "$$$$$"); newXSproto(strcpy(buf, "ScaleNPC"), XS_NPC_ScaleNPC, file, "$$"); + newXSproto(strcpy(buf, "SetCopper"), XS_NPC_SetCopper, file, "$$"); + newXSproto(strcpy(buf, "SetGold"), XS_NPC_SetGold, file, "$$"); + newXSproto(strcpy(buf, "SetGrid"), XS_NPC_SetGrid, file, "$$"); + newXSproto(strcpy(buf, "SetNPCFactionID"), XS_NPC_SetNPCFactionID, file, "$$"); + newXSproto(strcpy(buf, "SetPetSpellID"), XS_NPC_SetPetSpellID, file, "$$"); + newXSproto(strcpy(buf, "SetPlatinum"), XS_NPC_SetPlatinum, file, "$$"); + newXSproto(strcpy(buf, "SetPrimSkill"), XS_NPC_SetPrimSkill, file, "$$"); + newXSproto(strcpy(buf, "SetSaveWaypoint"), XS_NPC_SetSaveWaypoint, file, "$$"); + newXSproto(strcpy(buf, "SetSecSkill"), XS_NPC_SetSecSkill, file, "$$"); + newXSproto(strcpy(buf, "SetSilver"), XS_NPC_SetSilver, file, "$$"); + newXSproto(strcpy(buf, "SetSimpleRoamBox"), XS_NPC_SetSimpleRoamBox, file, "$$;$$"); + newXSproto(strcpy(buf, "SetSp2"), XS_NPC_SetSp2, file, "$$"); + newXSproto(strcpy(buf, "SetSpellFocusDMG"), XS_NPC_SetSpellFocusDMG, file, "$$"); + newXSproto(strcpy(buf, "SetSpellFocusHeal"), XS_NPC_SetSpellFocusHeal, file, "$$"); + newXSproto(strcpy(buf, "SetSwarmTarget"), XS_NPC_SetSwarmTarget, file, "$$"); + newXSproto(strcpy(buf, "SetTaunting"), XS_NPC_SetTaunting, file, "$$"); + newXSproto(strcpy(buf, "SetWaypointPause"), XS_NPC_SetWaypointPause, file, "$"); + newXSproto(strcpy(buf, "SignalNPC"), XS_NPC_SignalNPC, file, "$$"); + newXSproto(strcpy(buf, "StartSwarmTimer"), XS_NPC_StartSwarmTimer, file, "$$"); + newXSproto(strcpy(buf, "StopWandering"), XS_NPC_StopWandering, file, "$"); + newXSproto(strcpy(buf, "UpdateWaypoint"), XS_NPC_UpdateWaypoint, file, "$$"); XSRETURN_YES; } diff --git a/zone/perl_object.cpp b/zone/perl_object.cpp index c1e5d76db..fac167bc7 100644 --- a/zone/perl_object.cpp +++ b/zone/perl_object.cpp @@ -1,30 +1,3 @@ -/* -* This file was generated automatically by xsubpp version 1.9508 from the -* contents of tmp. Do not edit this file, edit tmp instead. -* -* ANY CHANGES MADE HERE WILL BE LOST! -* -*/ - - -/* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2004 EQEMu Development Team (http://eqemulator.net) - - 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 "../common/features.h" #ifdef EMBPERL_XS_CLASSES @@ -740,47 +713,47 @@ XS(boot_Object) { //add the strcpy stuff to get rid of const warnings.... XS_VERSION_BOOTCHECK; + newXSproto(strcpy(buf, "ClearUser"), XS_Object_ClearUser, file, "$"); + newXSproto(strcpy(buf, "Close"), XS_Object_Close, file, "$"); + newXSproto(strcpy(buf, "Delete"), XS_Object_Delete, file, "$$"); + newXSproto(strcpy(buf, "DeleteItem"), XS_Object_DeleteItem, file, "$$"); newXSproto(strcpy(buf, "Depop"), XS_Object_Depop, file, "$"); - newXSproto(strcpy(buf, "Repop"), XS_Object_Repop, file, "$"); - newXSproto(strcpy(buf, "SetModelName"), XS_Object_SetModelName, file, "$$"); + newXSproto(strcpy(buf, "EntityVariableExists"), XS_Object_EntityVariableExists, file, "$$"); + newXSproto(strcpy(buf, "GetDBID"), XS_Object_GetDBID, file, "$"); + newXSproto(strcpy(buf, "GetEntityVariable"), XS_Object_GetEntityVariable, file, "$$"); + newXSproto(strcpy(buf, "GetHeading"), XS_Object_GetHeading, file, "$"); + newXSproto(strcpy(buf, "GetID"), XS_Object_GetID, file, "$"); + newXSproto(strcpy(buf, "GetIcon"), XS_Object_GetIcon, file, "$"); + newXSproto(strcpy(buf, "GetItemID"), XS_Object_GetItemID, file, "$"); newXSproto(strcpy(buf, "GetModelName"), XS_Object_GetModelName, file, "$"); + newXSproto(strcpy(buf, "GetSize"), XS_Object_GetSize, file, "$"); + newXSproto(strcpy(buf, "GetSolidType"), XS_Object_GetSolidType, file, "$"); + newXSproto(strcpy(buf, "GetTiltX"), XS_Object_GetTiltX, file, "$$"); + newXSproto(strcpy(buf, "GetTiltY"), XS_Object_GetTiltY, file, "$"); + newXSproto(strcpy(buf, "GetType"), XS_Object_GetType, file, "$"); newXSproto(strcpy(buf, "GetX"), XS_Object_GetX, file, "$"); newXSproto(strcpy(buf, "GetY"), XS_Object_GetY, file, "$"); newXSproto(strcpy(buf, "GetZ"), XS_Object_GetZ, file, "$"); - newXSproto(strcpy(buf, "GetHeading"), XS_Object_GetHeading, file, "$"); + newXSproto(strcpy(buf, "IsGroundSpawn"), XS_Object_IsGroundSpawn, file, "$"); + newXSproto(strcpy(buf, "Repop"), XS_Object_Repop, file, "$"); + newXSproto(strcpy(buf, "Save"), XS_Object_Save, file, "$"); + newXSproto(strcpy(buf, "SetEntityVariable"), XS_Object_SetEntityVariable, file, "$$$"); + newXSproto(strcpy(buf, "SetHeading"), XS_Object_SetHeading, file, "$$"); + newXSproto(strcpy(buf, "SetID"), XS_Object_SetID, file, "$$"); + newXSproto(strcpy(buf, "SetIcon"), XS_Object_SetIcon, file, "$$"); + newXSproto(strcpy(buf, "SetItemID"), XS_Object_SetItemID, file, "$$"); + newXSproto(strcpy(buf, "SetLocation"), XS_Object_SetLocation, file, "$$$$"); + newXSproto(strcpy(buf, "SetModelName"), XS_Object_SetModelName, file, "$$"); + newXSproto(strcpy(buf, "SetSize"), XS_Object_SetSize, file, "$$"); + newXSproto(strcpy(buf, "SetSolidType"), XS_Object_SetSolidType, file, "$$"); + newXSproto(strcpy(buf, "SetTiltX"), XS_Object_SetTiltX, file, "$$"); + newXSproto(strcpy(buf, "SetTiltY"), XS_Object_SetTiltY, file, "$"); + newXSproto(strcpy(buf, "SetType"), XS_Object_SetType, file, "$$"); newXSproto(strcpy(buf, "SetX"), XS_Object_SetX, file, "$$"); newXSproto(strcpy(buf, "SetY"), XS_Object_SetY, file, "$$"); newXSproto(strcpy(buf, "SetZ"), XS_Object_SetZ, file, "$$"); - newXSproto(strcpy(buf, "SetHeading"), XS_Object_SetHeading, file, "$$"); - newXSproto(strcpy(buf, "SetLocation"), XS_Object_SetLocation, file, "$$$$"); - newXSproto(strcpy(buf, "SetItemID"), XS_Object_SetItemID, file, "$$"); - newXSproto(strcpy(buf, "GetItemID"), XS_Object_GetItemID, file, "$"); - newXSproto(strcpy(buf, "SetIcon"), XS_Object_SetIcon, file, "$$"); - newXSproto(strcpy(buf, "GetIcon"), XS_Object_GetIcon, file, "$"); - newXSproto(strcpy(buf, "SetType"), XS_Object_SetType, file, "$$"); - newXSproto(strcpy(buf, "GetType"), XS_Object_GetType, file, "$"); - newXSproto(strcpy(buf, "GetDBID"), XS_Object_GetDBID, file, "$"); - newXSproto(strcpy(buf, "ClearUser"), XS_Object_ClearUser, file, "$"); - newXSproto(strcpy(buf, "SetID"), XS_Object_SetID, file, "$$"); - newXSproto(strcpy(buf, "GetID"), XS_Object_GetID, file, "$"); - newXSproto(strcpy(buf, "Save"), XS_Object_Save, file, "$"); - newXSproto(strcpy(buf, "VarSave"), XS_Object_VarSave, file, "$"); - newXSproto(strcpy(buf, "DeleteItem"), XS_Object_DeleteItem, file, "$$"); newXSproto(strcpy(buf, "StartDecay"), XS_Object_StartDecay, file, "$$"); - newXSproto(strcpy(buf, "Delete"), XS_Object_Delete, file, "$$"); - newXSproto(strcpy(buf, "IsGroundSpawn"), XS_Object_IsGroundSpawn, file, "$"); - newXSproto(strcpy(buf, "Close"), XS_Object_Close, file, "$"); - newXSproto(strcpy(buf, "GetEntityVariable"), XS_Object_GetEntityVariable, file, "$$"); - newXSproto(strcpy(buf, "SetEntityVariable"), XS_Object_SetEntityVariable, file, "$$$"); - newXSproto(strcpy(buf, "EntityVariableExists"), XS_Object_EntityVariableExists, file, "$$"); - newXSproto(strcpy(buf, "SetSolidType"), XS_Object_SetSolidType, file, "$$"); - newXSproto(strcpy(buf, "GetSolidType"), XS_Object_GetSolidType, file, "$"); - newXSproto(strcpy(buf, "SetSize"), XS_Object_SetSize, file, "$$"); - newXSproto(strcpy(buf, "GetSize"), XS_Object_GetSize, file, "$"); - newXSproto(strcpy(buf, "SetTiltX"), XS_Object_SetTiltX, file, "$$"); - newXSproto(strcpy(buf, "SetTiltY"), XS_Object_SetTiltY, file, "$"); - newXSproto(strcpy(buf, "GetTiltX"), XS_Object_GetTiltX, file, "$$"); - newXSproto(strcpy(buf, "GetTiltY"), XS_Object_GetTiltY, file, "$"); + newXSproto(strcpy(buf, "VarSave"), XS_Object_VarSave, file, "$"); XSRETURN_YES; } #endif //EMBPERL_XS_CLASSES diff --git a/zone/perl_perlpacket.cpp b/zone/perl_perlpacket.cpp index 36fb97440..e4965d2a8 100644 --- a/zone/perl_perlpacket.cpp +++ b/zone/perl_perlpacket.cpp @@ -1,30 +1,3 @@ -/* -* This file was generated automatically by xsubpp version 1.9508 from the -* contents of tmp. Do not edit this file, edit tmp instead. -* -* ANY CHANGES MADE HERE WILL BE LOST! -* -*/ - - -/* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2004 EQEMu Development Team (http://eqemulator.net) - - 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 "../common/features.h" #ifdef EMBPERL_XS_CLASSES #include "../common/global_define.h" @@ -420,29 +393,26 @@ XS(boot_PerlPacket) //add the strcpy stuff to get rid of const warnings.... - - XS_VERSION_BOOTCHECK ; - - newXSproto(strcpy(buf, "new"), XS_PerlPacket_new, file, "$;$$"); - newXSproto(strcpy(buf, "DESTROY"), XS_PerlPacket_DESTROY, file, "$"); - newXSproto(strcpy(buf, "SetOpcode"), XS_PerlPacket_SetOpcode, file, "$$"); - newXSproto(strcpy(buf, "Resize"), XS_PerlPacket_Resize, file, "$$"); - newXSproto(strcpy(buf, "SendTo"), XS_PerlPacket_SendTo, file, "$$"); - newXSproto(strcpy(buf, "SendToAll"), XS_PerlPacket_SendToAll, file, "$"); - newXSproto(strcpy(buf, "Zero"), XS_PerlPacket_Zero, file, "$"); - newXSproto(strcpy(buf, "FromArray"), XS_PerlPacket_FromArray, file, "$$$"); - newXSproto(strcpy(buf, "SetByte"), XS_PerlPacket_SetByte, file, "$$$"); - newXSproto(strcpy(buf, "SetShort"), XS_PerlPacket_SetShort, file, "$$$"); - newXSproto(strcpy(buf, "SetLong"), XS_PerlPacket_SetLong, file, "$$$"); - newXSproto(strcpy(buf, "SetFloat"), XS_PerlPacket_SetFloat, file, "$$$"); - newXSproto(strcpy(buf, "SetString"), XS_PerlPacket_SetString, file, "$$$"); - newXSproto(strcpy(buf, "SetEQ1319"), XS_PerlPacket_SetEQ1319, file, "$$$$"); - newXSproto(strcpy(buf, "SetEQ1913"), XS_PerlPacket_SetEQ1913, file, "$$$$"); - newXSproto(strcpy(buf, "GetByte"), XS_PerlPacket_GetByte, file, "$$"); - newXSproto(strcpy(buf, "GetShort"), XS_PerlPacket_GetShort, file, "$$"); - newXSproto(strcpy(buf, "GetLong"), XS_PerlPacket_GetLong, file, "$$"); - newXSproto(strcpy(buf, "GetFloat"), XS_PerlPacket_GetFloat, file, "$$"); + newXSproto(strcpy(buf, "DESTROY"), XS_PerlPacket_DESTROY, file, "$"); + newXSproto(strcpy(buf, "FromArray"), XS_PerlPacket_FromArray, file, "$$$"); + newXSproto(strcpy(buf, "GetByte"), XS_PerlPacket_GetByte, file, "$$"); + newXSproto(strcpy(buf, "GetFloat"), XS_PerlPacket_GetFloat, file, "$$"); + newXSproto(strcpy(buf, "GetLong"), XS_PerlPacket_GetLong, file, "$$"); + newXSproto(strcpy(buf, "GetShort"), XS_PerlPacket_GetShort, file, "$$"); + newXSproto(strcpy(buf, "Resize"), XS_PerlPacket_Resize, file, "$$"); + newXSproto(strcpy(buf, "SendTo"), XS_PerlPacket_SendTo, file, "$$"); + newXSproto(strcpy(buf, "SendToAll"), XS_PerlPacket_SendToAll, file, "$"); + newXSproto(strcpy(buf, "SetByte"), XS_PerlPacket_SetByte, file, "$$$"); + newXSproto(strcpy(buf, "SetEQ1319"), XS_PerlPacket_SetEQ1319, file, "$$$$"); + newXSproto(strcpy(buf, "SetEQ1913"), XS_PerlPacket_SetEQ1913, file, "$$$$"); + newXSproto(strcpy(buf, "SetFloat"), XS_PerlPacket_SetFloat, file, "$$$"); + newXSproto(strcpy(buf, "SetLong"), XS_PerlPacket_SetLong, file, "$$$"); + newXSproto(strcpy(buf, "SetOpcode"), XS_PerlPacket_SetOpcode, file, "$$"); + newXSproto(strcpy(buf, "SetShort"), XS_PerlPacket_SetShort, file, "$$$"); + newXSproto(strcpy(buf, "SetString"), XS_PerlPacket_SetString, file, "$$$"); + newXSproto(strcpy(buf, "Zero"), XS_PerlPacket_Zero, file, "$"); + newXSproto(strcpy(buf, "new"), XS_PerlPacket_new, file, "$;$$"); XSRETURN_YES; } diff --git a/zone/perl_player_corpse.cpp b/zone/perl_player_corpse.cpp index 8979d2102..3d9e5b85a 100644 --- a/zone/perl_player_corpse.cpp +++ b/zone/perl_player_corpse.cpp @@ -1,30 +1,3 @@ -/* -* This file was generated automatically by xsubpp version 1.9508 from the -* contents of tmp. Do not edit this file, edit tmp instead. -* -* ANY CHANGES MADE HERE WILL BE LOST! -* -*/ - - -/* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2004 EQEMu Development Team (http://eqemulator.net) - - 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 "../common/features.h" #ifdef EMBPERL_XS_CLASSES @@ -528,6 +501,118 @@ XS(XS_Corpse_IsRezzed) { XSRETURN(1); } +XS(XS_Corpse_HasItem); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Corpse_HasItem) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Corpse::HasItem(THIS, uint32 item_id)"); // @categories Script Utility + { + Corpse *THIS; + bool has_item = false; + uint32 item_id = (uint32) SvUV(ST(1)); + VALIDATE_THIS_IS_CORPSE; + has_item = THIS->HasItem(item_id); + ST(0) = boolSV(has_item); + sv_2mortal(ST(0)); + } + XSRETURN(1); +} + +XS(XS_Corpse_CountItem); +XS(XS_Corpse_CountItem) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Corpse::CountItem(THIS, uint32 item_id)"); // @categories Script Utility + { + Corpse *THIS; + uint16 item_count = 0; + uint32 item_id = (uint32) SvUV(ST(1)); + dXSTARG; + VALIDATE_THIS_IS_CORPSE; + item_count = THIS->CountItem(item_id); + XSprePUSH; + PUSHu((UV) item_count); + } + XSRETURN(1); +} + +XS(XS_Corpse_GetItemIDBySlot); +XS(XS_Corpse_GetItemIDBySlot) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Corpse::GetItemIDBySlot(THIS, uint16 loot_slot)"); // @categories Script Utility + { + Corpse *THIS; + uint32 item_id = 0; + uint16 loot_slot = (uint16) SvUV(ST(1)); + dXSTARG; + VALIDATE_THIS_IS_CORPSE; + item_id = THIS->GetItemIDBySlot(loot_slot); + XSprePUSH; + PUSHu((UV) item_id); + } + XSRETURN(1); +} + +XS(XS_Corpse_GetFirstSlotByItemID); +XS(XS_Corpse_GetFirstSlotByItemID) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Corpse::GetFirstSlotByItemID(THIS, uint32 item_id)"); // @categories Script Utility + { + Corpse *THIS; + uint16 loot_slot = 0; + uint32 item_id = (uint32) SvUV(ST(1)); + dXSTARG; + VALIDATE_THIS_IS_CORPSE; + loot_slot = THIS->GetFirstSlotByItemID(item_id); + XSprePUSH; + PUSHu((UV) loot_slot); + } + XSRETURN(1); +} + +XS(XS_Corpse_RemoveItemByID); +XS(XS_Corpse_RemoveItemByID) { + dXSARGS; + if (items != 2 && items != 3) + Perl_croak(aTHX_ "Usage: Corpse::RemoveItemByID(THIS, uint32 item_id, [int quantity = 1])"); // @categories Script Utility + { + Corpse *THIS; + uint32 item_id = (uint32) SvUV(ST(1)); + int quantity = 1; + VALIDATE_THIS_IS_CORPSE; + if (items == 3) + quantity = (int) SvIV(ST(2)); + + THIS->RemoveItemByID(item_id, quantity); + } + XSRETURN_EMPTY; +} + +XS(XS_Corpse_GetLootList); +XS(XS_Corpse_GetLootList) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Corpse::GetLootList(THIS)"); // @categories Script Utility + { + Corpse *THIS; + VALIDATE_THIS_IS_CORPSE; + auto corpse_items = THIS->GetLootList(); + auto item_count = corpse_items.size(); + if (item_count > 0) { + EXTEND(sp, item_count); + for (int index = 0; index < item_count; ++index) { + ST(index) = sv_2mortal(newSVuv(corpse_items[index])); + } + XSRETURN(item_count); + } + SV* return_value = &PL_sv_undef; + ST(0) = return_value; + XSRETURN(1); + } +} + #ifdef __cplusplus extern "C" #endif @@ -545,35 +630,40 @@ XS(boot_Corpse) { //add the strcpy stuff to get rid of const warnings.... XS_VERSION_BOOTCHECK; - - newXSproto(strcpy(buf, "GetCharID"), XS_Corpse_GetCharID, file, "$"); - newXSproto(strcpy(buf, "GetDecayTime"), XS_Corpse_GetDecayTime, file, "$"); - newXSproto(strcpy(buf, "Lock"), XS_Corpse_Lock, file, "$"); - newXSproto(strcpy(buf, "UnLock"), XS_Corpse_UnLock, file, "$"); - newXSproto(strcpy(buf, "IsLocked"), XS_Corpse_IsLocked, file, "$"); - newXSproto(strcpy(buf, "ResetLooter"), XS_Corpse_ResetLooter, file, "$"); - newXSproto(strcpy(buf, "GetDBID"), XS_Corpse_GetDBID, file, "$"); - newXSproto(strcpy(buf, "GetOwnerName"), XS_Corpse_GetOwnerName, file, "$"); - newXSproto(strcpy(buf, "SetDecayTimer"), XS_Corpse_SetDecayTimer, file, "$$"); - newXSproto(strcpy(buf, "IsEmpty"), XS_Corpse_IsEmpty, file, "$"); newXSproto(strcpy(buf, "AddItem"), XS_Corpse_AddItem, file, "$$$;$"); - newXSproto(strcpy(buf, "GetWornItem"), XS_Corpse_GetWornItem, file, "$$"); - newXSproto(strcpy(buf, "RemoveItem"), XS_Corpse_RemoveItem, file, "$$"); - newXSproto(strcpy(buf, "SetCash"), XS_Corpse_SetCash, file, "$$$$$"); - newXSproto(strcpy(buf, "RemoveCash"), XS_Corpse_RemoveCash, file, "$"); - newXSproto(strcpy(buf, "CountItems"), XS_Corpse_CountItems, file, "$"); - newXSproto(strcpy(buf, "Delete"), XS_Corpse_Delete, file, "$"); - newXSproto(strcpy(buf, "GetCopper"), XS_Corpse_GetCopper, file, "$"); - newXSproto(strcpy(buf, "GetSilver"), XS_Corpse_GetSilver, file, "$"); - newXSproto(strcpy(buf, "GetGold"), XS_Corpse_GetGold, file, "$"); - newXSproto(strcpy(buf, "GetPlatinum"), XS_Corpse_GetPlatinum, file, "$"); - newXSproto(strcpy(buf, "Summon"), XS_Corpse_Summon, file, "$$$"); + newXSproto(strcpy(buf, "AddLooter"), XS_Corpse_AddLooter, file, "$$"); + newXSproto(strcpy(buf, "AllowMobLoot"), XS_Corpse_AllowMobLoot, file, "$$$"); + newXSproto(strcpy(buf, "CanMobLoot"), XS_Corpse_CanMobLoot, file, "$$"); newXSproto(strcpy(buf, "CastRezz"), XS_Corpse_CastRezz, file, "$$$"); newXSproto(strcpy(buf, "CompleteRezz"), XS_Corpse_CompleteRezz, file, "$"); - newXSproto(strcpy(buf, "CanMobLoot"), XS_Corpse_CanMobLoot, file, "$$"); - newXSproto(strcpy(buf, "AllowMobLoot"), XS_Corpse_AllowMobLoot, file, "$$$"); - newXSproto(strcpy(buf, "AddLooter"), XS_Corpse_AddLooter, file, "$$"); + newXSproto(strcpy(buf, "CountItem"), XS_Corpse_CountItem, file, "$$"); + newXSproto(strcpy(buf, "CountItems"), XS_Corpse_CountItems, file, "$"); + newXSproto(strcpy(buf, "Delete"), XS_Corpse_Delete, file, "$"); + newXSproto(strcpy(buf, "GetCharID"), XS_Corpse_GetCharID, file, "$"); + newXSproto(strcpy(buf, "GetCopper"), XS_Corpse_GetCopper, file, "$"); + newXSproto(strcpy(buf, "GetDBID"), XS_Corpse_GetDBID, file, "$"); + newXSproto(strcpy(buf, "GetDecayTime"), XS_Corpse_GetDecayTime, file, "$"); + newXSproto(strcpy(buf, "GetFirstSlotByItemID"), XS_Corpse_GetFirstSlotByItemID, file, "$$"); + newXSproto(strcpy(buf, "GetGold"), XS_Corpse_GetGold, file, "$"); + newXSproto(strcpy(buf, "GetItemIDBySlot"), XS_Corpse_GetItemIDBySlot, file, "$$"); + newXSproto(strcpy(buf, "GetLootList"), XS_Corpse_GetLootList, file, "$"); + newXSproto(strcpy(buf, "GetOwnerName"), XS_Corpse_GetOwnerName, file, "$"); + newXSproto(strcpy(buf, "GetPlatinum"), XS_Corpse_GetPlatinum, file, "$"); + newXSproto(strcpy(buf, "GetSilver"), XS_Corpse_GetSilver, file, "$"); + newXSproto(strcpy(buf, "GetWornItem"), XS_Corpse_GetWornItem, file, "$$"); + newXSproto(strcpy(buf, "HasItem"), XS_Corpse_HasItem, file, "$$"); + newXSproto(strcpy(buf, "IsEmpty"), XS_Corpse_IsEmpty, file, "$"); + newXSproto(strcpy(buf, "IsLocked"), XS_Corpse_IsLocked, file, "$"); newXSproto(strcpy(buf, "IsRezzed"), XS_Corpse_IsRezzed, file, "$"); + newXSproto(strcpy(buf, "Lock"), XS_Corpse_Lock, file, "$"); + newXSproto(strcpy(buf, "RemoveCash"), XS_Corpse_RemoveCash, file, "$"); + newXSproto(strcpy(buf, "RemoveItem"), XS_Corpse_RemoveItem, file, "$$"); + newXSproto(strcpy(buf, "RemoveItemByID"), XS_Corpse_RemoveItemByID, file, "$$;$"); + newXSproto(strcpy(buf, "ResetLooter"), XS_Corpse_ResetLooter, file, "$"); + newXSproto(strcpy(buf, "SetCash"), XS_Corpse_SetCash, file, "$$$$$"); + newXSproto(strcpy(buf, "SetDecayTimer"), XS_Corpse_SetDecayTimer, file, "$$"); + newXSproto(strcpy(buf, "Summon"), XS_Corpse_Summon, file, "$$$"); + newXSproto(strcpy(buf, "UnLock"), XS_Corpse_UnLock, file, "$"); XSRETURN_YES; } diff --git a/zone/perl_questitem.cpp b/zone/perl_questitem.cpp index e1029b33a..709201a71 100644 --- a/zone/perl_questitem.cpp +++ b/zone/perl_questitem.cpp @@ -1,21 +1,3 @@ -/* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2004 EQEMu Development Team (http://eqemulator.net) - - 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 "../common/features.h" #include "client.h" @@ -186,6 +168,41 @@ XS(XS_QuestItem_GetID) { XSRETURN(1); } +XS(XS_QuestItem_ContainsAugmentByID); /* prototype to pass -Wmissing-prototypes */ +XS(XS_QuestItem_ContainsAugmentByID) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: QuestItem::ContainsAugmentByID(THIS, uint32 item_id)"); // @categories Inventory and Items + { + EQ::ItemInstance *THIS; + uint32 item_id = (uint32) SvUV(ST(1)); + bool contains_augment = false; + VALIDATE_THIS_IS_ITEM; + contains_augment = THIS->ContainsAugmentByID(item_id); + ST(0) = boolSV(contains_augment); + sv_2mortal(ST(0)); + } + XSRETURN(1); +} + +XS(XS_QuestItem_CountAugmentByID); /* prototype to pass -Wmissing-prototypes */ +XS(XS_QuestItem_CountAugmentByID) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: QuestItem::CountAugmentByID(THIS, uint32 item_id)"); // @categories Inventory and Items + { + EQ::ItemInstance *THIS; + int quantity = 0; + uint32 item_id = (uint32) SvUV(ST(1)); + dXSTARG; + VALIDATE_THIS_IS_ITEM; + quantity = THIS->CountAugmentByID(item_id); + XSprePUSH; + PUSHi((IV) quantity); + } + XSRETURN(1); +} + #ifdef __cplusplus extern "C" #endif @@ -204,16 +221,16 @@ XS(boot_QuestItem) { //add the strcpy stuff to get rid of const warnings.... XS_VERSION_BOOTCHECK; - - newXSproto(strcpy(buf, "GetName"), XS_QuestItem_GetName, file, "$"); - newXSproto(strcpy(buf, "SetScale"), XS_QuestItem_SetScale, file, "$"); - newXSproto(strcpy(buf, "ItemSay"), XS_QuestItem_ItemSay, file, "$"); - newXSproto(strcpy(buf, "IsType"), XS_QuestItem_IsType, file, "$$"); - newXSproto(strcpy(buf, "IsAttuned"), XS_QuestItem_IsAttuned, file, "$"); - newXSproto(strcpy(buf, "GetCharges"), XS_QuestItem_GetCharges, file, "$"); + newXSproto(strcpy(buf, "ContainsAugmentByID"), XS_QuestItem_ContainsAugmentByID, file, "$$"); + newXSproto(strcpy(buf, "CountAugmentByID"), XS_QuestItem_CountAugmentByID, file, "$$"); newXSproto(strcpy(buf, "GetAugment"), XS_QuestItem_GetAugment, file, "$$"); + newXSproto(strcpy(buf, "GetCharges"), XS_QuestItem_GetCharges, file, "$"); newXSproto(strcpy(buf, "GetID"), XS_QuestItem_GetID, file, "$"); - + newXSproto(strcpy(buf, "GetName"), XS_QuestItem_GetName, file, "$"); + newXSproto(strcpy(buf, "IsAttuned"), XS_QuestItem_IsAttuned, file, "$"); + newXSproto(strcpy(buf, "IsType"), XS_QuestItem_IsType, file, "$$"); + newXSproto(strcpy(buf, "ItemSay"), XS_QuestItem_ItemSay, file, "$"); + newXSproto(strcpy(buf, "SetScale"), XS_QuestItem_SetScale, file, "$"); XSRETURN_YES; } diff --git a/zone/perl_raids.cpp b/zone/perl_raids.cpp index 452741be6..07834de9f 100644 --- a/zone/perl_raids.cpp +++ b/zone/perl_raids.cpp @@ -1,30 +1,3 @@ -/* -* This file was generated automatically by ExtUtils::ParseXS version 2.18 from the -* contents of raids.h.xs. Do not edit this file, edit raids.h.xs instead. -* -* ANY CHANGES MADE HERE WILL BE LOST! -* -*/ - - -/* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2004 EQEMu Development Team (http://eqemulator.net) - - 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 "../common/features.h" #ifdef EMBPERL_XS_CLASSES @@ -304,7 +277,7 @@ XS(XS_Raid_GetClientByIndex); /* prototype to pass -Wmissing-prototypes */ XS(XS_Raid_GetClientByIndex) { dXSARGS; if (items != 2) - Perl_croak(aTHX_ "Usage: Raid::GetClientByIndex(THIS, uint16 raid_indez)"); // @categories Raid + Perl_croak(aTHX_ "Usage: Raid::GetClientByIndex(THIS, uint16 raid_index)"); // @categories Raid { Raid *THIS; Client *RETVAL; @@ -448,26 +421,25 @@ XS(boot_Raid) { //add the strcpy stuff to get rid of const warnings.... XS_VERSION_BOOTCHECK; - - newXSproto(strcpy(buf, "IsRaidMember"), XS_Raid_IsRaidMember, file, "$$"); - newXSproto(strcpy(buf, "CastGroupSpell"), XS_Raid_CastGroupSpell, file, "$$$$"); - newXSproto(strcpy(buf, "GroupCount"), XS_Raid_GroupCount, file, "$$"); - newXSproto(strcpy(buf, "RaidCount"), XS_Raid_RaidCount, file, "$"); - 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, "BalanceHP"), XS_Raid_BalanceHP, file, "$$$"); - newXSproto(strcpy(buf, "IsLeader"), XS_Raid_IsLeader, file, "$$"); - newXSproto(strcpy(buf, "IsGroupLeader"), XS_Raid_IsGroupLeader, file, "$$"); - newXSproto(strcpy(buf, "GetHighestLevel"), XS_Raid_GetHighestLevel, file, "$"); - newXSproto(strcpy(buf, "GetLowestLevel"), XS_Raid_GetLowestLevel, file, "$"); + newXSproto(strcpy(buf, "CastGroupSpell"), XS_Raid_CastGroupSpell, file, "$$$$"); + newXSproto(strcpy(buf, "DoesAnyMemberHaveExpeditionLockout"), XS_Raid_DoesAnyMemberHaveExpeditionLockout, file, "$$$;$"); newXSproto(strcpy(buf, "GetClientByIndex"), XS_Raid_GetClientByIndex, file, "$$"); + newXSproto(strcpy(buf, "GetGroup"), XS_Raid_GetGroup, file, "$$"); + newXSproto(strcpy(buf, "GetHighestLevel"), XS_Raid_GetHighestLevel, file, "$"); + newXSproto(strcpy(buf, "GetID"), XS_Raid_GetID, file, "$"); + newXSproto(strcpy(buf, "GetLowestLevel"), XS_Raid_GetLowestLevel, file, "$"); + newXSproto(strcpy(buf, "GetMember"), XS_Raid_GetMember, file, "$$"); + newXSproto(strcpy(buf, "GetTotalRaidDamage"), XS_Raid_GetTotalRaidDamage, file, "$$"); + newXSproto(strcpy(buf, "GroupCount"), XS_Raid_GroupCount, file, "$$"); + newXSproto(strcpy(buf, "IsGroupLeader"), XS_Raid_IsGroupLeader, file, "$$"); + newXSproto(strcpy(buf, "IsLeader"), XS_Raid_IsLeader, file, "$$"); + newXSproto(strcpy(buf, "IsRaidMember"), XS_Raid_IsRaidMember, file, "$$"); + newXSproto(strcpy(buf, "RaidCount"), XS_Raid_RaidCount, file, "$"); + newXSproto(strcpy(buf, "SplitExp"), XS_Raid_SplitExp, file, "$$$"); + newXSproto(strcpy(buf, "SplitMoney"), XS_Raid_SplitMoney, file, "$$$$$$"); newXSproto(strcpy(buf, "TeleportGroup"), XS_Raid_TeleportGroup, file, "$$$$$$$$"); newXSproto(strcpy(buf, "TeleportRaid"), XS_Raid_TeleportRaid, file, "$$$$$$$"); - newXSproto(strcpy(buf, "GetID"), XS_Raid_GetID, file, "$"); - newXSproto(strcpy(buf, "GetMember"), XS_Raid_GetMember, file, "$$"); - newXSproto(strcpy(buf, "DoesAnyMemberHaveExpeditionLockout"), XS_Raid_DoesAnyMemberHaveExpeditionLockout, file, "$$$;$"); XSRETURN_YES; } diff --git a/zone/perl_spell.cpp b/zone/perl_spell.cpp new file mode 100644 index 000000000..85aa27d05 --- /dev/null +++ b/zone/perl_spell.cpp @@ -0,0 +1,2138 @@ +#include "../common/features.h" + +#ifdef EMBPERL_XS_CLASSES + +#include "../common/global_define.h" +#include "embperl.h" + +#ifdef seed +#undef seed +#endif + +#include "../common/spdat.h" + +#ifdef THIS /* this macro seems to leak out on some systems */ +#undef THIS +#endif + +#define VALIDATE_THIS_IS_SPELL \ + do { \ + if (sv_derived_from(ST(0), "Spell")) { \ + IV tmp = SvIV((SV*)SvRV(ST(0))); \ + THIS = INT2PTR(SPDat_Spell_Struct*, tmp); \ + } else { \ + Perl_croak(aTHX_ "THIS is not of type SPDat_Spell_Struct"); \ + } \ + if (THIS == nullptr) { \ + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); \ + } \ + } while (0); + +XS(XS_Spell_GetActivated); +XS(XS_Spell_GetActivated) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetActivated(THIS)"); + { + SPDat_Spell_Struct* THIS; + int activated; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + activated = THIS->activated; + XSprePUSH; + PUSHi((IV) activated); + } + XSRETURN(1); +} + +XS(XS_Spell_GetAllowRest); +XS(XS_Spell_GetAllowRest) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetAllowRest(THIS)"); + { + SPDat_Spell_Struct* THIS; + bool allow_rest; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + allow_rest = THIS->allow_rest; + ST(0) = boolSV(allow_rest); + sv_2mortal(ST(0)); + } + XSRETURN(1); +} + +XS(XS_Spell_GetAOEDuration); +XS(XS_Spell_GetAOEDuration) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetAOEDuration(THIS)"); + { + SPDat_Spell_Struct* THIS; + uint32 aoe_duration; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + aoe_duration = THIS->aoe_duration; + XSprePUSH; + PUSHu((UV) aoe_duration); + } + XSRETURN(1); +} + +XS(XS_Spell_GetAOEMaxTargets); +XS(XS_Spell_GetAOEMaxTargets) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetAOEMaxTargets(THIS)"); + { + SPDat_Spell_Struct* THIS; + int aoe_max_targets; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + aoe_max_targets = THIS->aoe_max_targets; + XSprePUSH; + PUSHi((IV) aoe_max_targets); + } + XSRETURN(1); +} + +XS(XS_Spell_GetAOERange); +XS(XS_Spell_GetAOERange) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetAOERange(THIS)"); + { + SPDat_Spell_Struct* THIS; + float aoe_range; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + aoe_range = THIS->aoe_range; + XSprePUSH; + PUSHn((double) aoe_range); + } + XSRETURN(1); +} + +XS(XS_Spell_GetBaseDifficulty); +XS(XS_Spell_GetBaseDifficulty) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetBaseDifficulty(THIS)"); + { + SPDat_Spell_Struct* THIS; + int base_difficulty; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + base_difficulty = THIS->base_difficulty; + XSprePUSH; + PUSHi((IV) base_difficulty); + } + XSRETURN(1); +} + +XS(XS_Spell_GetBaseValue); +XS(XS_Spell_GetBaseValue) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Spell::GetBaseValue(THIS, uint8 slot)"); + { + SPDat_Spell_Struct* THIS; + int base_value; + uint8 slot = (uint8) SvUV(ST(1)); + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + base_value = THIS->base_value[slot]; + XSprePUSH; + PUSHi((IV) base_value); + } + XSRETURN(1); +} + +XS(XS_Spell_GetBonusHate); +XS(XS_Spell_GetBonusHate) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetBonusHate(THIS)"); + { + SPDat_Spell_Struct* THIS; + int bonus_hate; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + bonus_hate = THIS->bonus_hate; + XSprePUSH; + PUSHi((IV) bonus_hate); + } + XSRETURN(1); +} + +XS(XS_Spell_GetBuffDuration); +XS(XS_Spell_GetBuffDuration) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetBuffDuration(THIS)"); + { + SPDat_Spell_Struct* THIS; + uint32 buff_duration; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + buff_duration = THIS->buff_duration; + XSprePUSH; + PUSHu((UV) buff_duration); + } + XSRETURN(1); +} + +XS(XS_Spell_GetBuffDurationFormula); +XS(XS_Spell_GetBuffDurationFormula) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetBuffDurationFormula(THIS)"); + { + SPDat_Spell_Struct* THIS; + uint32 buff_duration_formula; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + buff_duration_formula = THIS->buff_duration_formula; + XSprePUSH; + PUSHu((UV) buff_duration_formula); + } + XSRETURN(1); +} + +XS(XS_Spell_GetCanCastInCombat); +XS(XS_Spell_GetCanCastInCombat) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetCanCastInCombat(THIS)"); + { + SPDat_Spell_Struct* THIS; + bool can_cast_in_combat; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + can_cast_in_combat = THIS->can_cast_in_combat; + ST(0) = boolSV(can_cast_in_combat); + sv_2mortal(ST(0)); + } + XSRETURN(1); +} + +XS(XS_Spell_GetCanCastOutOfCombat); +XS(XS_Spell_GetCanCastOutOfCombat) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetCanCastOutOfCombat(THIS)"); + { + SPDat_Spell_Struct* THIS; + bool can_cast_out_of_combat; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + can_cast_out_of_combat = THIS->can_cast_out_of_combat; + ST(0) = boolSV(can_cast_out_of_combat); + sv_2mortal(ST(0)); + } + XSRETURN(1); +} + +XS(XS_Spell_GetCanMGB); +XS(XS_Spell_GetCanMGB) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetCanMGB(THIS)"); + { + SPDat_Spell_Struct* THIS; + bool can_mgb; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + can_mgb = THIS->can_mgb; + ST(0) = boolSV(can_mgb); + sv_2mortal(ST(0)); + } + XSRETURN(1); +} + +XS(XS_Spell_GetCastNotStanding); +XS(XS_Spell_GetCastNotStanding) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetCastNotStanding(THIS)"); + { + SPDat_Spell_Struct* THIS; + bool cast_not_standing; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + cast_not_standing = THIS->cast_not_standing; + ST(0) = boolSV(cast_not_standing); + sv_2mortal(ST(0)); + } + XSRETURN(1); +} + +XS(XS_Spell_GetCastOnOther); +XS(XS_Spell_GetCastOnOther) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetCastOnOther(THIS)"); + { + SPDat_Spell_Struct* THIS; + std::string cast_on_other; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + cast_on_other = THIS->cast_on_other; + sv_setpv(TARG, cast_on_other.c_str()); + XSprePUSH; + PUSHTARG; + } + XSRETURN(1); +} + +XS(XS_Spell_GetCastOnYou); +XS(XS_Spell_GetCastOnYou) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetCastOnYou(THIS)"); + { + SPDat_Spell_Struct* THIS; + std::string cast_on_you; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + cast_on_you = THIS->cast_on_you; + sv_setpv(TARG, cast_on_you.c_str()); + XSprePUSH; + PUSHTARG; + } + XSRETURN(1); +} + +XS(XS_Spell_GetCastRestriction); +XS(XS_Spell_GetCastRestriction) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetCastRestriction(THIS)"); + { + SPDat_Spell_Struct* THIS; + int cast_restriction; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + cast_restriction = THIS->cast_restriction; + XSprePUSH; + PUSHi((IV) cast_restriction); + } + XSRETURN(1); +} + +XS(XS_Spell_GetCastTime); +XS(XS_Spell_GetCastTime) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetCastTime(THIS)"); + { + SPDat_Spell_Struct* THIS; + uint32 cast_time; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + cast_time = THIS->cast_time; + XSprePUSH; + PUSHu((UV) cast_time); + } + XSRETURN(1); +} + +XS(XS_Spell_GetCasterRequirementID); +XS(XS_Spell_GetCasterRequirementID) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetCasterRequirementID(THIS)"); + { + SPDat_Spell_Struct* THIS; + int caster_requirement_id; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + caster_requirement_id = THIS->caster_requirement_id; + XSprePUSH; + PUSHi((IV) caster_requirement_id); + } + XSRETURN(1); +} + +XS(XS_Spell_GetCastingAnimation); +XS(XS_Spell_GetCastingAnimation) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetCastingAnimation(THIS)"); + { + SPDat_Spell_Struct* THIS; + uint8 casting_animation; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + casting_animation = THIS->casting_animation; + XSprePUSH; + PUSHu((UV) casting_animation); + } + XSRETURN(1); +} + +XS(XS_Spell_GetClasses); +XS(XS_Spell_GetClasses) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Spell::GetClasses(THIS, uint8 slot)"); + { + SPDat_Spell_Struct* THIS; + uint8 classes; + uint8 slot = (uint8) SvUV(ST(1)); + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + classes = THIS->classes[slot]; + XSprePUSH; + PUSHu((UV) classes); + } + XSRETURN(1); +} + +XS(XS_Spell_GetComponent); +XS(XS_Spell_GetComponent) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Spell::GetComponent(THIS, uint8 slot)"); + { + SPDat_Spell_Struct* THIS; + int component; + uint8 slot = (uint8) SvUV(ST(1)); + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + component = THIS->component[slot]; + XSprePUSH; + PUSHi((IV) component); + } + XSRETURN(1); +} + +XS(XS_Spell_GetComponentCount); +XS(XS_Spell_GetComponentCount) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Spell::GetComponentCount(THIS, uint8 slot)"); + { + SPDat_Spell_Struct* THIS; + int component_count; + uint8 slot = (uint8) SvUV(ST(1)); + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + component_count = THIS->component_count[slot]; + XSprePUSH; + PUSHi((IV) component_count); + } + XSRETURN(1); +} + +XS(XS_Spell_GetDeities); +XS(XS_Spell_GetDeities) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Spell::GetDeities(THIS, uint8 slot)"); + { + SPDat_Spell_Struct* THIS; + int8 deities; + uint8 slot = (uint8) SvUV(ST(1)); + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + deities = THIS->deities[slot]; + XSprePUSH; + PUSHi((IV) deities); + } + XSRETURN(1); +} + +XS(XS_Spell_GetDeityAgnostic); +XS(XS_Spell_GetDeityAgnostic) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetDeityAgnostic(THIS)"); + { + SPDat_Spell_Struct* THIS; + int8 deity_agnostic; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + deity_agnostic = THIS->deity_agnostic; + XSprePUSH; + PUSHi((IV) deity_agnostic); + } + XSRETURN(1); +} + +XS(XS_Spell_GetDescriptionID); +XS(XS_Spell_GetDescriptionID) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetDescriptionID(THIS)"); + { + SPDat_Spell_Struct* THIS; + int description_id; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + description_id = THIS->description_id; + XSprePUSH; + PUSHi((IV) description_id); + } + XSRETURN(1); +} + +XS(XS_Spell_GetDirectionalEnd); +XS(XS_Spell_GetDirectionalEnd) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetDirectionalEnd(THIS)"); + { + SPDat_Spell_Struct* THIS; + float directional_end; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + directional_end = THIS->directional_end; + XSprePUSH; + PUSHn((double) directional_end); + } + XSRETURN(1); +} + +XS(XS_Spell_GetDirectionalStart); +XS(XS_Spell_GetDirectionalStart) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetDirectionalStart(THIS)"); + { + SPDat_Spell_Struct* THIS; + float directional_start; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + directional_start = THIS->directional_start; + XSprePUSH; + PUSHn((double) directional_start); + } + XSRETURN(1); +} + +XS(XS_Spell_GetDisallowSit); +XS(XS_Spell_GetDisallowSit) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetDisallowSit(THIS)"); + { + SPDat_Spell_Struct* THIS; + int8 disallow_sit; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + disallow_sit = THIS->disallow_sit; + XSprePUSH; + PUSHi((IV) disallow_sit); + } + XSRETURN(1); +} + +XS(XS_Spell_GetDispelFlag); +XS(XS_Spell_GetDispelFlag) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetDispelFlag(THIS)"); + { + SPDat_Spell_Struct* THIS; + int dispel_flag; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + dispel_flag = THIS->dispel_flag; + XSprePUSH; + PUSHi((IV) dispel_flag); + } + XSRETURN(1); +} + +XS(XS_Spell_GetEffectDescriptionID); +XS(XS_Spell_GetEffectDescriptionID) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetEffectDescriptionID(THIS)"); + { + SPDat_Spell_Struct* THIS; + int effect_description_id; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + effect_description_id = THIS->effect_description_id; + XSprePUSH; + PUSHi((IV) effect_description_id); + } + XSRETURN(1); +} + +XS(XS_Spell_GetEffectID); +XS(XS_Spell_GetEffectID) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Spell::GetEffectID(THIS, uint8 slot)"); + { + SPDat_Spell_Struct* THIS; + int effect_id; + uint8 slot = (uint8) SvUV(ST(1)); + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + effect_id = THIS->effect_id[slot]; + XSprePUSH; + PUSHi((IV) effect_id); + } + XSRETURN(1); +} + +XS(XS_Spell_GetEnduranceCost); +XS(XS_Spell_GetEnduranceCost) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetEnduranceCost(THIS)"); + { + SPDat_Spell_Struct* THIS; + int endurance_cost; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + endurance_cost = THIS->endurance_cost; + XSprePUSH; + PUSHi((IV) endurance_cost); + } + XSRETURN(1); +} + +XS(XS_Spell_GetEnduranceUpkeep); +XS(XS_Spell_GetEnduranceUpkeep) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetEnduranceUpkeep(THIS)"); + { + SPDat_Spell_Struct* THIS; + int endurance_upkeep; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + endurance_upkeep = THIS->endurance_upkeep; + XSprePUSH; + PUSHi((IV) endurance_upkeep); + } + XSRETURN(1); +} + +XS(XS_Spell_GetEnvironmentType); +XS(XS_Spell_GetEnvironmentType) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetEnvironmentType(THIS)"); + { + SPDat_Spell_Struct* THIS; + int8 environment_type; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + environment_type = THIS->environment_type; + XSprePUSH; + PUSHi((IV) environment_type); + } + XSRETURN(1); +} + +XS(XS_Spell_GetFeedbackable); +XS(XS_Spell_GetFeedbackable) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetFeedbackable(THIS)"); + { + SPDat_Spell_Struct* THIS; + bool feedbackable; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + feedbackable = THIS->feedbackable; + ST(0) = boolSV(feedbackable); + sv_2mortal(ST(0)); + } + XSRETURN(1); +} + +XS(XS_Spell_GetFormula); +XS(XS_Spell_GetFormula) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Spell::GetFormula(THIS, uint8 slot)"); + { + SPDat_Spell_Struct* THIS; + uint16 formula; + uint8 slot = (uint8) SvUV(ST(1)); + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + formula = THIS->formula[slot]; + XSprePUSH; + PUSHu((UV) formula); + } + XSRETURN(1); +} + +XS(XS_Spell_GetGoodEffect); +XS(XS_Spell_GetGoodEffect) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetGoodEffect(THIS)"); + { + SPDat_Spell_Struct* THIS; + int8 good_effect; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + good_effect = THIS->good_effect; + XSprePUSH; + PUSHi((IV) good_effect); + } + XSRETURN(1); +} + +XS(XS_Spell_GetHateAdded); +XS(XS_Spell_GetHateAdded) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetHateAdded(THIS)"); + { + SPDat_Spell_Struct* THIS; + int hate_added; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + hate_added = THIS->hate_added; + XSprePUSH; + PUSHi((IV) hate_added); + } + XSRETURN(1); +} + +XS(XS_Spell_GetHitNumber); +XS(XS_Spell_GetHitNumber) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetHitNumber(THIS)"); + { + SPDat_Spell_Struct* THIS; + int hit_number; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + hit_number = THIS->hit_number; + XSprePUSH; + PUSHi((IV) hit_number); + } + XSRETURN(1); +} + +XS(XS_Spell_GetHitNumberType); +XS(XS_Spell_GetHitNumberType) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetHitNumberType(THIS)"); + { + SPDat_Spell_Struct* THIS; + int hit_number_type; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + hit_number_type = THIS->hit_number_type; + XSprePUSH; + PUSHi((IV) hit_number_type); + } + XSRETURN(1); +} + +XS(XS_Spell_GetID); +XS(XS_Spell_GetID) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetID(THIS)"); + { + SPDat_Spell_Struct* THIS; + int id; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + id = THIS->id; + XSprePUSH; + PUSHi((IV) id); + } + XSRETURN(1); +} + +XS(XS_Spell_GetIsDiscipline); +XS(XS_Spell_GetIsDiscipline) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetIsDiscipline(THIS)"); + { + SPDat_Spell_Struct* THIS; + bool is_discipline; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + is_discipline = THIS->is_discipline; + ST(0) = boolSV(is_discipline); + sv_2mortal(ST(0)); + } + XSRETURN(1); +} + +XS(XS_Spell_GetLDoNTrap); +XS(XS_Spell_GetLDoNTrap) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetLDoNTrap(THIS)"); + { + SPDat_Spell_Struct* THIS; + bool ldon_trap; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + ldon_trap = THIS->ldon_trap; + ST(0) = boolSV(ldon_trap); + sv_2mortal(ST(0)); + } + XSRETURN(1); +} + +XS(XS_Spell_GetLimitValue); +XS(XS_Spell_GetLimitValue) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Spell::GetLimitValue(THIS, uint8 slot)"); + { + SPDat_Spell_Struct* THIS; + int limit_value; + uint8 slot = (uint8) SvUV(ST(1)); + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + limit_value = THIS->limit_value[slot]; + XSprePUSH; + PUSHi((IV) limit_value); + } + XSRETURN(1); +} + +XS(XS_Spell_GetMana); +XS(XS_Spell_GetMana) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetMana(THIS)"); + { + SPDat_Spell_Struct* THIS; + uint16 mana; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + mana = THIS->mana; + XSprePUSH; + PUSHu((UV) mana); + } + XSRETURN(1); +} + +XS(XS_Spell_GetMaxDistance); +XS(XS_Spell_GetMaxDistance) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetMaxDistance(THIS)"); + { + SPDat_Spell_Struct* THIS; + float max_distance; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + max_distance = THIS->max_distance; + XSprePUSH; + PUSHn((double) max_distance); + } + XSRETURN(1); +} + +XS(XS_Spell_GetMaxDistanceMod); +XS(XS_Spell_GetMaxDistanceMod) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetMaxDistanceMod(THIS)"); + { + SPDat_Spell_Struct* THIS; + float max_distance_mod; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + max_distance_mod = THIS->max_distance_mod; + XSprePUSH; + PUSHn((double) max_distance_mod); + } + XSRETURN(1); +} + +XS(XS_Spell_GetMaxResist); +XS(XS_Spell_GetMaxResist) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetMaxResist(THIS)"); + { + SPDat_Spell_Struct* THIS; + int max_resist; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + max_resist = THIS->max_resist; + XSprePUSH; + PUSHi((IV) max_resist); + } + XSRETURN(1); +} + +XS(XS_Spell_GetMaxValue); +XS(XS_Spell_GetMaxValue) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Spell::GetMaxValue(THIS, uint8 slot)"); + { + SPDat_Spell_Struct* THIS; + int max_value; + uint8 slot = (uint8) SvUV(ST(1)); + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + max_value = THIS->max_value[slot]; + XSprePUSH; + PUSHi((IV) max_value); + } + XSRETURN(1); +} + +XS(XS_Spell_GetMinDistance); +XS(XS_Spell_GetMinDistance) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetMinDistance(THIS)"); + { + SPDat_Spell_Struct* THIS; + float min_distance; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + min_distance = THIS->min_distance; + XSprePUSH; + PUSHn((double) min_distance); + } + XSRETURN(1); +} + +XS(XS_Spell_GetMinDistanceMod); +XS(XS_Spell_GetMinDistanceMod) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetMinDistanceMod(THIS)"); + { + SPDat_Spell_Struct* THIS; + float min_distance_mod; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + min_distance_mod = THIS->min_distance_mod; + XSprePUSH; + PUSHn((double) min_distance_mod); + } + XSRETURN(1); +} + +XS(XS_Spell_GetMinRange); +XS(XS_Spell_GetMinRange) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetMinRange(THIS)"); + { + SPDat_Spell_Struct* THIS; + float min_range; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + min_range = THIS->min_range; + XSprePUSH; + PUSHn((double) min_range); + } + XSRETURN(1); +} + +XS(XS_Spell_GetMinResist); +XS(XS_Spell_GetMinResist) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetMinResist(THIS)"); + { + SPDat_Spell_Struct* THIS; + int min_resist; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + min_resist = THIS->min_resist; + XSprePUSH; + PUSHi((IV) min_resist); + } + XSRETURN(1); +} + +XS(XS_Spell_GetName); +XS(XS_Spell_GetName) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetName(THIS)"); + { + SPDat_Spell_Struct* THIS; + std::string name; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + name = THIS->name; + sv_setpv(TARG, name.c_str()); + XSprePUSH; + PUSHTARG; + } + XSRETURN(1); +} + +XS(XS_Spell_GetNewIcon); +XS(XS_Spell_GetNewIcon) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetNewIcon(THIS)"); + { + SPDat_Spell_Struct* THIS; + int16 new_icon; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + new_icon = THIS->new_icon; + XSprePUSH; + PUSHi((IV) new_icon); + } + XSRETURN(1); +} + +XS(XS_Spell_GetNimbusEffect); +XS(XS_Spell_GetNimbusEffect) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetNimbusEffect(THIS)"); + { + SPDat_Spell_Struct* THIS; + int nimbus_effect; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + nimbus_effect = THIS->nimbus_effect; + XSprePUSH; + PUSHi((IV) nimbus_effect); + } + XSRETURN(1); +} + +XS(XS_Spell_GetNoBlock); +XS(XS_Spell_GetNoBlock) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetNoBlock(THIS)"); + { + SPDat_Spell_Struct* THIS; + bool no_block; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + no_block = THIS->no_block; + ST(0) = boolSV(no_block); + sv_2mortal(ST(0)); + } + XSRETURN(1); +} + +XS(XS_Spell_GetNoDetrimentalSpellAggro); +XS(XS_Spell_GetNoDetrimentalSpellAggro) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetNoDetrimentalSpellAggro(THIS)"); + { + SPDat_Spell_Struct* THIS; + bool no_detrimental_spell_aggro; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + no_detrimental_spell_aggro = THIS->no_detrimental_spell_aggro; + ST(0) = boolSV(no_detrimental_spell_aggro); + sv_2mortal(ST(0)); + } + XSRETURN(1); +} + +XS(XS_Spell_GetNoExpendReagent); +XS(XS_Spell_GetNoExpendReagent) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Spell::GetNoExpendReagent(THIS, uint8 slot)"); + { + SPDat_Spell_Struct* THIS; + int no_expend_reagent; + uint8 slot = (uint8) SvUV(ST(1)); + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + no_expend_reagent = THIS->no_expend_reagent[slot]; + XSprePUSH; + PUSHi((IV) no_expend_reagent); + } + XSRETURN(1); +} + +XS(XS_Spell_GetNoHealDamageItemMod); +XS(XS_Spell_GetNoHealDamageItemMod) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetNoHealDamageItemMod(THIS)"); + { + SPDat_Spell_Struct* THIS; + int no_heal_damage_item_mod; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + no_heal_damage_item_mod = THIS->no_heal_damage_item_mod; + XSprePUSH; + PUSHi((IV) no_heal_damage_item_mod); + } + XSRETURN(1); +} + +XS(XS_Spell_GetNoPartialResist); +XS(XS_Spell_GetNoPartialResist) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetNoPartialResist(THIS)"); + { + SPDat_Spell_Struct* THIS; + bool no_partial_resist; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + no_partial_resist = THIS->no_partial_resist; + ST(0) = boolSV(no_partial_resist); + sv_2mortal(ST(0)); + } + XSRETURN(1); +} + +XS(XS_Spell_GetNoRemove); +XS(XS_Spell_GetNoRemove) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetNoRemove(THIS)"); + { + SPDat_Spell_Struct* THIS; + bool no_remove; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + no_remove = THIS->no_remove; + ST(0) = boolSV(no_remove); + sv_2mortal(ST(0)); + } + XSRETURN(1); +} + +XS(XS_Spell_GetNoResist); +XS(XS_Spell_GetNoResist) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetNoResist(THIS)"); + { + SPDat_Spell_Struct* THIS; + int no_resist; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + no_resist = THIS->no_resist; + XSprePUSH; + PUSHi((IV) no_resist); + } + XSRETURN(1); +} + +XS(XS_Spell_GetNotFocusable); +XS(XS_Spell_GetNotFocusable) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetNotFocusable(THIS)"); + { + SPDat_Spell_Struct* THIS; + bool not_focusable; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + not_focusable = THIS->not_focusable; + ST(0) = boolSV(not_focusable); + sv_2mortal(ST(0)); + } + XSRETURN(1); +} + +XS(XS_Spell_GetNPCNoLOS); +XS(XS_Spell_GetNPCNoLOS) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetNPCNoLOS(THIS)"); + { + SPDat_Spell_Struct* THIS; + bool npc_no_los; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + npc_no_los = THIS->npc_no_los; + ST(0) = boolSV(npc_no_los); + sv_2mortal(ST(0)); + } + XSRETURN(1); +} + +XS(XS_Spell_GetOtherCasts); +XS(XS_Spell_GetOtherCasts) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetOtherCasts(THIS)"); + { + SPDat_Spell_Struct* THIS; + std::string other_casts; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + other_casts = THIS->other_casts; + sv_setpv(TARG, other_casts.c_str()); + XSprePUSH; + PUSHTARG; + } + XSRETURN(1); +} + +XS(XS_Spell_GetOverrideCritChance); +XS(XS_Spell_GetOverrideCritChance) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetOverrideCritChance(THIS)"); + { + SPDat_Spell_Struct* THIS; + int override_crit_chance; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + override_crit_chance = THIS->override_crit_chance; + XSprePUSH; + PUSHi((IV) override_crit_chance); + } + XSRETURN(1); +} + +XS(XS_Spell_GetPCNPCOnlyFlag); +XS(XS_Spell_GetPCNPCOnlyFlag) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetPCNPCOnlyFlag(THIS)"); + { + SPDat_Spell_Struct* THIS; + int pcnpc_only_flag; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + pcnpc_only_flag = THIS->pcnpc_only_flag; + XSprePUSH; + PUSHi((IV) pcnpc_only_flag); + } + XSRETURN(1); +} + +XS(XS_Spell_GetPersistDeath); +XS(XS_Spell_GetPersistDeath) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetPersistDeath(THIS)"); + { + SPDat_Spell_Struct* THIS; + bool persist_death; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + persist_death = THIS->persist_death; + ST(0) = boolSV(persist_death); + sv_2mortal(ST(0)); + } + XSRETURN(1); +} + +XS(XS_Spell_GetPlayer_1); +XS(XS_Spell_GetPlayer_1) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetPlayer_1(THIS)"); + { + SPDat_Spell_Struct* THIS; + std::string player_1; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + player_1 = THIS->player_1; + sv_setpv(TARG, player_1.c_str()); + XSprePUSH; + PUSHTARG; + } + XSRETURN(1); +} + +XS(XS_Spell_GetPushBack); +XS(XS_Spell_GetPushBack) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetPushBack(THIS)"); + { + SPDat_Spell_Struct* THIS; + float push_back; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + push_back = THIS->push_back; + XSprePUSH; + PUSHn((double) push_back); + } + XSRETURN(1); +} + +XS(XS_Spell_GetPushUp); +XS(XS_Spell_GetPushUp) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetPushUp(THIS)"); + { + SPDat_Spell_Struct* THIS; + float push_up; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + push_up = THIS->push_up; + XSprePUSH; + PUSHn((double) push_up); + } + XSRETURN(1); +} + +XS(XS_Spell_GetPVPDuration); +XS(XS_Spell_GetPVPDuration) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetPVPDuration(THIS)"); + { + SPDat_Spell_Struct* THIS; + int pvp_duration; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + pvp_duration = THIS->pvp_duration; + XSprePUSH; + PUSHi((IV) pvp_duration); + } + XSRETURN(1); +} + +XS(XS_Spell_GetPVPDurationCap); +XS(XS_Spell_GetPVPDurationCap) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetPVPDurationCap(THIS)"); + { + SPDat_Spell_Struct* THIS; + int pvp_duration_cap; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + pvp_duration_cap = THIS->pvp_duration_cap; + XSprePUSH; + PUSHi((IV) pvp_duration_cap); + } + XSRETURN(1); +} + +XS(XS_Spell_GetPVPResistBase); +XS(XS_Spell_GetPVPResistBase) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetPVPResistBase(THIS)"); + { + SPDat_Spell_Struct* THIS; + int pvp_resist_base; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + pvp_resist_base = THIS->pvp_resist_base; + XSprePUSH; + PUSHi((IV) pvp_resist_base); + } + XSRETURN(1); +} + +XS(XS_Spell_GetPVPResistCap); +XS(XS_Spell_GetPVPResistCap) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetPVPResistCap(THIS)"); + { + SPDat_Spell_Struct* THIS; + int pvp_resist_cap; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + pvp_resist_cap = THIS->pvp_resist_cap; + XSprePUSH; + PUSHi((IV) pvp_resist_cap); + } + XSRETURN(1); +} + +XS(XS_Spell_GetPVPResistPerLevel); +XS(XS_Spell_GetPVPResistPerLevel) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetPVPResistPerLevel(THIS)"); + { + SPDat_Spell_Struct* THIS; + int pvp_resist_per_level; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + pvp_resist_per_level = THIS->pvp_resist_per_level; + XSprePUSH; + PUSHi((IV) pvp_resist_per_level); + } + XSRETURN(1); +} + +XS(XS_Spell_GetRange); +XS(XS_Spell_GetRange) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetRange(THIS)"); + { + SPDat_Spell_Struct* THIS; + float range; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + range = THIS->range; + XSprePUSH; + PUSHn((double) range); + } + XSRETURN(1); +} + +XS(XS_Spell_GetRank); +XS(XS_Spell_GetRank) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetRank(THIS)"); + { + SPDat_Spell_Struct* THIS; + int rank; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + rank = THIS->rank; + XSprePUSH; + PUSHi((IV) rank); + } + XSRETURN(1); +} + +XS(XS_Spell_GetRecastTime); +XS(XS_Spell_GetRecastTime) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetRecastTime(THIS)"); + { + SPDat_Spell_Struct* THIS; + uint32 recast_time; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + recast_time = THIS->recast_time; + XSprePUSH; + PUSHu((UV) recast_time); + } + XSRETURN(1); +} + +XS(XS_Spell_GetRecourseLink); +XS(XS_Spell_GetRecourseLink) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetRecourseLink(THIS)"); + { + SPDat_Spell_Struct* THIS; + uint16 recourse_link; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + recourse_link = THIS->recourse_link; + XSprePUSH; + PUSHu((UV) recourse_link); + } + XSRETURN(1); +} + +XS(XS_Spell_GetRecoveryTime); +XS(XS_Spell_GetRecoveryTime) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetRecoveryTime(THIS)"); + { + SPDat_Spell_Struct* THIS; + uint32 recovery_time; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + recovery_time = THIS->recovery_time; + XSprePUSH; + PUSHu((UV) recovery_time); + } + XSRETURN(1); +} + +XS(XS_Spell_GetReflectable); +XS(XS_Spell_GetReflectable) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetReflectable(THIS)"); + { + SPDat_Spell_Struct* THIS; + bool reflectable; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + reflectable = THIS->reflectable; + ST(0) = boolSV(reflectable); + sv_2mortal(ST(0)); + } + XSRETURN(1); +} + +XS(XS_Spell_GetResistDifficulty); +XS(XS_Spell_GetResistDifficulty) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetResistDifficulty(THIS)"); + { + SPDat_Spell_Struct* THIS; + int16 resist_difficulty; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + resist_difficulty = THIS->resist_difficulty; + XSprePUSH; + PUSHi((IV) resist_difficulty); + } + XSRETURN(1); +} + +XS(XS_Spell_GetResistType); +XS(XS_Spell_GetResistType) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetResistType(THIS)"); + { + SPDat_Spell_Struct* THIS; + int resist_type; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + resist_type = THIS->resist_type; + XSprePUSH; + PUSHi((IV) resist_type); + } + XSRETURN(1); +} + +XS(XS_Spell_GetShortBuffBox); +XS(XS_Spell_GetShortBuffBox) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetShortBuffBox(THIS)"); + { + SPDat_Spell_Struct* THIS; + int8 short_buff_box; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + short_buff_box = THIS->short_buff_box; + XSprePUSH; + PUSHi((IV) short_buff_box); + } + XSRETURN(1); +} + +XS(XS_Spell_GetSkill); +XS(XS_Spell_GetSkill) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetSkill(THIS)"); + { + SPDat_Spell_Struct* THIS; + EQ::skills::SkillType skill; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + skill = THIS->skill; + XSprePUSH; + PUSHi((IV) skill); + } + XSRETURN(1); +} + +XS(XS_Spell_GetSneak); +XS(XS_Spell_GetSneak) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetSneak(THIS)"); + { + SPDat_Spell_Struct* THIS; + bool sneak; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + sneak = THIS->sneak; + ST(0) = boolSV(sneak); + sv_2mortal(ST(0)); + } + XSRETURN(1); +} + +XS(XS_Spell_GetSongCap); +XS(XS_Spell_GetSongCap) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetSongCap(THIS)"); + { + SPDat_Spell_Struct* THIS; + int song_cap; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + song_cap = THIS->song_cap; + XSprePUSH; + PUSHi((IV) song_cap); + } + XSRETURN(1); +} + +XS(XS_Spell_GetSpellAffectIndex); +XS(XS_Spell_GetSpellAffectIndex) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetSpellAffectIndex(THIS)"); + { + SPDat_Spell_Struct* THIS; + uint16 spell_affect_index; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + spell_affect_index = THIS->spell_affect_index; + XSprePUSH; + PUSHu((UV) spell_affect_index); + } + XSRETURN(1); +} + +XS(XS_Spell_GetSpellCategory); +XS(XS_Spell_GetSpellCategory) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetSpellCategory(THIS)"); + { + SPDat_Spell_Struct* THIS; + int spell_category; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + spell_category = THIS->spell_category; + XSprePUSH; + PUSHi((IV) spell_category); + } + XSRETURN(1); +} + +XS(XS_Spell_GetSpellClass); +XS(XS_Spell_GetSpellClass) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetSpellClass(THIS)"); + { + SPDat_Spell_Struct* THIS; + int spell_class; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + spell_class = THIS->spell_class; + XSprePUSH; + PUSHi((IV) spell_class); + } + XSRETURN(1); +} + +XS(XS_Spell_GetSpellFades); +XS(XS_Spell_GetSpellFades) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetSpellFades(THIS)"); + { + SPDat_Spell_Struct* THIS; + std::string spell_fades; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + spell_fades = THIS->spell_fades; + sv_setpv(TARG, spell_fades.c_str()); + XSprePUSH; + PUSHTARG; + } + XSRETURN(1); +} + +XS(XS_Spell_GetSpellGroup); +XS(XS_Spell_GetSpellGroup) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetSpellGroup(THIS)"); + { + SPDat_Spell_Struct* THIS; + int spell_group; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + spell_group = THIS->spell_group; + XSprePUSH; + PUSHi((IV) spell_group); + } + XSRETURN(1); +} + +XS(XS_Spell_GetSpellSubclass); +XS(XS_Spell_GetSpellSubclass) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetSpellSubclass(THIS)"); + { + SPDat_Spell_Struct* THIS; + int spell_subclass; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + spell_subclass = THIS->spell_subclass; + XSprePUSH; + PUSHi((IV) spell_subclass); + } + XSRETURN(1); +} + +XS(XS_Spell_GetSuspendable); +XS(XS_Spell_GetSuspendable) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetSuspendable(THIS)"); + { + SPDat_Spell_Struct* THIS; + bool suspendable; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + suspendable = THIS->suspendable; + ST(0) = boolSV(suspendable); + sv_2mortal(ST(0)); + } + XSRETURN(1); +} + +XS(XS_Spell_GetTargetType); +XS(XS_Spell_GetTargetType) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetTargetType(THIS)"); + { + SPDat_Spell_Struct* THIS; + SpellTargetType target_type; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + target_type = THIS->target_type; + XSprePUSH; + PUSHi((IV) target_type); + } + XSRETURN(1); +} + +XS(XS_Spell_GetTeleportZone); +XS(XS_Spell_GetTeleportZone) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetTeleportZone(THIS)"); + { + SPDat_Spell_Struct* THIS; + std::string teleport_zone; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + teleport_zone = THIS->teleport_zone; + sv_setpv(TARG, teleport_zone.c_str()); + XSprePUSH; + PUSHTARG; + } + XSRETURN(1); +} + +XS(XS_Spell_GetTimeOfDay); +XS(XS_Spell_GetTimeOfDay) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetTimeOfDay(THIS)"); + { + SPDat_Spell_Struct* THIS; + int8 time_of_day; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + time_of_day = THIS->time_of_day; + XSprePUSH; + PUSHi((IV) time_of_day); + } + XSRETURN(1); +} + +XS(XS_Spell_GetTimerID); +XS(XS_Spell_GetTimerID) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetTimerID(THIS)"); + { + SPDat_Spell_Struct* THIS; + int8 timer_id; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + timer_id = THIS->timer_id; + XSprePUSH; + PUSHi((IV) timer_id); + } + XSRETURN(1); +} + +XS(XS_Spell_GetTypeDescriptionID); +XS(XS_Spell_GetTypeDescriptionID) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetTypeDescriptionID(THIS)"); + { + SPDat_Spell_Struct* THIS; + int type_description_id; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + type_description_id = THIS->type_description_id; + XSprePUSH; + PUSHi((IV) type_description_id); + } + XSRETURN(1); +} + +XS(XS_Spell_GetUninterruptable); +XS(XS_Spell_GetUninterruptable) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetUninterruptable(THIS)"); + { + SPDat_Spell_Struct* THIS; + bool uninterruptable; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + uninterruptable = THIS->uninterruptable; + ST(0) = boolSV(uninterruptable); + sv_2mortal(ST(0)); + } + XSRETURN(1); +} + +XS(XS_Spell_GetUnstackableDOT); +XS(XS_Spell_GetUnstackableDOT) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetUnstackableDOT(THIS)"); + { + SPDat_Spell_Struct* THIS; + bool unstackable_dot; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + unstackable_dot = THIS->unstackable_dot; + ST(0) = boolSV(unstackable_dot); + sv_2mortal(ST(0)); + } + XSRETURN(1); +} + +XS(XS_Spell_GetViralRange); +XS(XS_Spell_GetViralRange) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetViralRange(THIS)"); + { + SPDat_Spell_Struct* THIS; + int viral_range; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + viral_range = THIS->viral_range; + XSprePUSH; + PUSHi((IV) viral_range); + } + XSRETURN(1); +} + +XS(XS_Spell_GetViralTargets); +XS(XS_Spell_GetViralTargets) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetViralTargets(THIS)"); + { + SPDat_Spell_Struct* THIS; + uint8 viral_targets; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + viral_targets = THIS->viral_targets; + XSprePUSH; + PUSHu((UV) viral_targets); + } + XSRETURN(1); +} + +XS(XS_Spell_GetViralTimer); +XS(XS_Spell_GetViralTimer) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetViralTimer(THIS)"); + { + SPDat_Spell_Struct* THIS; + uint8 viral_timer; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + viral_timer = THIS->viral_timer; + XSprePUSH; + PUSHu((UV) viral_timer); + } + XSRETURN(1); +} + +XS(XS_Spell_GetYouCast); +XS(XS_Spell_GetYouCast) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetYouCast(THIS)"); + { + SPDat_Spell_Struct* THIS; + std::string you_cast; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + you_cast = THIS->you_cast; + sv_setpv(TARG, you_cast.c_str()); + XSprePUSH; + PUSHTARG; + } + XSRETURN(1); +} + +XS(XS_Spell_GetZoneType); +XS(XS_Spell_GetZoneType) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Spell::GetZoneType(THIS)"); + { + SPDat_Spell_Struct* THIS; + int8 zone_type; + dXSTARG; + VALIDATE_THIS_IS_SPELL; + + zone_type = THIS->zone_type; + XSprePUSH; + PUSHi((IV) zone_type); + } + XSRETURN(1); +} + +#ifdef __cplusplus +extern "C" +#endif + +XS(boot_Spell); +XS(boot_Spell) { + dXSARGS; + char file[256]; + strncpy(file, __FILE__, 256); + file[255] = 0; + if (items != 1) + fprintf(stderr, "boot_Spell does not take any arguments."); + + char buf[128]; + XS_VERSION_BOOTCHECK; + newXSproto(strcpy(buf, "GetActivated"), XS_Spell_GetActivated, file, "$"); + newXSproto(strcpy(buf, "GetAllowRest"), XS_Spell_GetAllowRest, file, "$"); + newXSproto(strcpy(buf, "GetAOEDuration"), XS_Spell_GetAOEDuration, file, "$"); + newXSproto(strcpy(buf, "GetAOEMaxTargets"), XS_Spell_GetAOEMaxTargets, file, "$"); + newXSproto(strcpy(buf, "GetAOERange"), XS_Spell_GetAOERange, file, "$"); + newXSproto(strcpy(buf, "GetBaseDifficulty"), XS_Spell_GetBaseDifficulty, file, "$"); + newXSproto(strcpy(buf, "GetBaseValue"), XS_Spell_GetBaseValue, file, "$$"); + newXSproto(strcpy(buf, "GetBonusHate"), XS_Spell_GetBonusHate, file, "$"); + newXSproto(strcpy(buf, "GetBuffDuration"), XS_Spell_GetBuffDuration, file, "$"); + newXSproto(strcpy(buf, "GetBuffDurationFormula"), XS_Spell_GetBuffDurationFormula, file, "$"); + newXSproto(strcpy(buf, "GetCanCastInCombat"), XS_Spell_GetCanCastInCombat, file, "$"); + newXSproto(strcpy(buf, "GetCanCastOutOfCombat"), XS_Spell_GetCanCastOutOfCombat, file, "$"); + newXSproto(strcpy(buf, "GetCanMGB"), XS_Spell_GetCanMGB, file, "$"); + newXSproto(strcpy(buf, "GetCastNotStanding"), XS_Spell_GetCastNotStanding, file, "$"); + newXSproto(strcpy(buf, "GetCastOnOther"), XS_Spell_GetCastOnOther, file, "$"); + newXSproto(strcpy(buf, "GetCastOnYou"), XS_Spell_GetCastOnYou, file, "$"); + newXSproto(strcpy(buf, "GetCastRestriction"), XS_Spell_GetCastRestriction, file, "$"); + newXSproto(strcpy(buf, "GetCastTime"), XS_Spell_GetCastTime, file, "$"); + newXSproto(strcpy(buf, "GetCasterRequirementID"), XS_Spell_GetCasterRequirementID, file, "$"); + newXSproto(strcpy(buf, "GetCastingAnimation"), XS_Spell_GetCastingAnimation, file, "$"); + newXSproto(strcpy(buf, "GetClasses"), XS_Spell_GetClasses, file, "$$"); + newXSproto(strcpy(buf, "GetComponent"), XS_Spell_GetComponent, file, "$$"); + newXSproto(strcpy(buf, "GetComponentCount"), XS_Spell_GetComponentCount, file, "$$"); + newXSproto(strcpy(buf, "GetDeities"), XS_Spell_GetDeities, file, "$$"); + newXSproto(strcpy(buf, "GetDeityAgnostic"), XS_Spell_GetDeityAgnostic, file, "$"); + newXSproto(strcpy(buf, "GetDescriptionID"), XS_Spell_GetDescriptionID, file, "$"); + newXSproto(strcpy(buf, "GetDirectionalEnd"), XS_Spell_GetDirectionalEnd, file, "$"); + newXSproto(strcpy(buf, "GetDirectionalStart"), XS_Spell_GetDirectionalStart, file, "$"); + newXSproto(strcpy(buf, "GetDisallowSit"), XS_Spell_GetDisallowSit, file, "$"); + newXSproto(strcpy(buf, "GetDispelFlag"), XS_Spell_GetDispelFlag, file, "$"); + newXSproto(strcpy(buf, "GetEffectDescriptionID"), XS_Spell_GetEffectDescriptionID, file, "$"); + newXSproto(strcpy(buf, "GetEffectID"), XS_Spell_GetEffectID, file, "$$"); + newXSproto(strcpy(buf, "GetEnduranceCost"), XS_Spell_GetEnduranceCost, file, "$"); + newXSproto(strcpy(buf, "GetEnduranceUpkeep"), XS_Spell_GetEnduranceUpkeep, file, "$"); + newXSproto(strcpy(buf, "GetEnvironmentType"), XS_Spell_GetEnvironmentType, file, "$"); + newXSproto(strcpy(buf, "GetFeedbackable"), XS_Spell_GetFeedbackable, file, "$"); + newXSproto(strcpy(buf, "GetFormula"), XS_Spell_GetFormula, file, "$$"); + newXSproto(strcpy(buf, "GetGoodEffect"), XS_Spell_GetGoodEffect, file, "$"); + newXSproto(strcpy(buf, "GetHateAdded"), XS_Spell_GetHateAdded, file, "$"); + newXSproto(strcpy(buf, "GetHitNumber"), XS_Spell_GetHitNumber, file, "$"); + newXSproto(strcpy(buf, "GetHitNumberType"), XS_Spell_GetHitNumberType, file, "$"); + newXSproto(strcpy(buf, "GetID"), XS_Spell_GetID, file, "$"); + newXSproto(strcpy(buf, "GetIsDiscipline"), XS_Spell_GetIsDiscipline, file, "$"); + newXSproto(strcpy(buf, "GetLDoNTrap"), XS_Spell_GetLDoNTrap, file, "$"); + newXSproto(strcpy(buf, "GetLimitValue"), XS_Spell_GetLimitValue, file, "$$"); + newXSproto(strcpy(buf, "GetMana"), XS_Spell_GetMana, file, "$"); + newXSproto(strcpy(buf, "GetMaxDistance"), XS_Spell_GetMaxDistance, file, "$"); + newXSproto(strcpy(buf, "GetMaxDistanceMod"), XS_Spell_GetMaxDistanceMod, file, "$"); + newXSproto(strcpy(buf, "GetMaxResist"), XS_Spell_GetMaxResist, file, "$"); + newXSproto(strcpy(buf, "GetMaxValue"), XS_Spell_GetMaxValue, file, "$$"); + newXSproto(strcpy(buf, "GetMinDistance"), XS_Spell_GetMinDistance, file, "$"); + newXSproto(strcpy(buf, "GetMinDistanceMod"), XS_Spell_GetMinDistanceMod, file, "$"); + newXSproto(strcpy(buf, "GetMinRange"), XS_Spell_GetMinRange, file, "$"); + newXSproto(strcpy(buf, "GetMinResist"), XS_Spell_GetMinResist, file, "$"); + newXSproto(strcpy(buf, "GetName"), XS_Spell_GetName, file, "$"); + newXSproto(strcpy(buf, "GetNewIcon"), XS_Spell_GetNewIcon, file, "$"); + newXSproto(strcpy(buf, "GetNimbusEffect"), XS_Spell_GetNimbusEffect, file, "$"); + newXSproto(strcpy(buf, "GetNoBlock"), XS_Spell_GetNoBlock, file, "$"); + newXSproto(strcpy(buf, "GetNoDetrimentalSpellAggro"), XS_Spell_GetNoDetrimentalSpellAggro, file, "$"); + newXSproto(strcpy(buf, "GetNoExpendReagent"), XS_Spell_GetNoExpendReagent, file, "$$"); + newXSproto(strcpy(buf, "GetNoHealDamageItemMod"), XS_Spell_GetNoHealDamageItemMod, file, "$"); + newXSproto(strcpy(buf, "GetNoPartialResist"), XS_Spell_GetNoPartialResist, file, "$"); + newXSproto(strcpy(buf, "GetNoRemove"), XS_Spell_GetNoRemove, file, "$"); + newXSproto(strcpy(buf, "GetNoResist"), XS_Spell_GetNoResist, file, "$"); + newXSproto(strcpy(buf, "GetNotFocusable"), XS_Spell_GetNotFocusable, file, "$"); + newXSproto(strcpy(buf, "GetNPCNoLOS"), XS_Spell_GetNPCNoLOS, file, "$"); + newXSproto(strcpy(buf, "GetOtherCasts"), XS_Spell_GetOtherCasts, file, "$"); + newXSproto(strcpy(buf, "GetOverrideCritChance"), XS_Spell_GetOverrideCritChance, file, "$"); + newXSproto(strcpy(buf, "GetPCNPCOnlyFlag"), XS_Spell_GetPCNPCOnlyFlag, file, "$"); + newXSproto(strcpy(buf, "GetPersistDeath"), XS_Spell_GetPersistDeath, file, "$"); + newXSproto(strcpy(buf, "GetPlayer_1"), XS_Spell_GetPlayer_1, file, "$"); + newXSproto(strcpy(buf, "GetPushBack"), XS_Spell_GetPushBack, file, "$"); + newXSproto(strcpy(buf, "GetPushUp"), XS_Spell_GetPushUp, file, "$"); + newXSproto(strcpy(buf, "GetPVPDuration"), XS_Spell_GetPVPDuration, file, "$"); + newXSproto(strcpy(buf, "GetPVPDurationCap"), XS_Spell_GetPVPDurationCap, file, "$"); + newXSproto(strcpy(buf, "GetPVPResistBase"), XS_Spell_GetPVPResistBase, file, "$"); + newXSproto(strcpy(buf, "GetPVPResistCap"), XS_Spell_GetPVPResistCap, file, "$"); + newXSproto(strcpy(buf, "GetPVPResistPerLevel"), XS_Spell_GetPVPResistPerLevel, file, "$"); + newXSproto(strcpy(buf, "GetRange"), XS_Spell_GetRange, file, "$"); + newXSproto(strcpy(buf, "GetRank"), XS_Spell_GetRank, file, "$"); + newXSproto(strcpy(buf, "GetRecastTime"), XS_Spell_GetRecastTime, file, "$"); + newXSproto(strcpy(buf, "GetRecourseLink"), XS_Spell_GetRecourseLink, file, "$"); + newXSproto(strcpy(buf, "GetRecoveryTime"), XS_Spell_GetRecoveryTime, file, "$"); + newXSproto(strcpy(buf, "GetReflectable"), XS_Spell_GetReflectable, file, "$"); + newXSproto(strcpy(buf, "GetResistDifficulty"), XS_Spell_GetResistDifficulty, file, "$"); + newXSproto(strcpy(buf, "GetResistType"), XS_Spell_GetResistType, file, "$"); + newXSproto(strcpy(buf, "GetShortBuffBox"), XS_Spell_GetShortBuffBox, file, "$"); + newXSproto(strcpy(buf, "GetSkill"), XS_Spell_GetSkill, file, "$"); + newXSproto(strcpy(buf, "GetSneak"), XS_Spell_GetSneak, file, "$"); + newXSproto(strcpy(buf, "GetSongCap"), XS_Spell_GetSongCap, file, "$"); + newXSproto(strcpy(buf, "GetSpellAffectIndex"), XS_Spell_GetSpellAffectIndex, file, "$"); + newXSproto(strcpy(buf, "GetSpellCategory"), XS_Spell_GetSpellCategory, file, "$"); + newXSproto(strcpy(buf, "GetSpellClass"), XS_Spell_GetSpellClass, file, "$"); + newXSproto(strcpy(buf, "GetSpellFades"), XS_Spell_GetSpellFades, file, "$"); + newXSproto(strcpy(buf, "GetSpellGroup"), XS_Spell_GetSpellGroup, file, "$"); + newXSproto(strcpy(buf, "GetSpellSubclass"), XS_Spell_GetSpellSubclass, file, "$"); + newXSproto(strcpy(buf, "GetSuspendable"), XS_Spell_GetSuspendable, file, "$"); + newXSproto(strcpy(buf, "GetTargetType"), XS_Spell_GetTargetType, file, "$"); + newXSproto(strcpy(buf, "GetTeleportZone"), XS_Spell_GetTeleportZone, file, "$"); + newXSproto(strcpy(buf, "GetTimeOfDay"), XS_Spell_GetTimeOfDay, file, "$"); + newXSproto(strcpy(buf, "GetTimerID"), XS_Spell_GetTimerID, file, "$"); + newXSproto(strcpy(buf, "GetTypeDescriptionID"), XS_Spell_GetTypeDescriptionID, file, "$"); + newXSproto(strcpy(buf, "GetUninterruptable"), XS_Spell_GetUninterruptable, file, "$"); + newXSproto(strcpy(buf, "GetUnstackableDOT"), XS_Spell_GetUnstackableDOT, file, "$"); + newXSproto(strcpy(buf, "GetViralRange"), XS_Spell_GetViralRange, file, "$"); + newXSproto(strcpy(buf, "GetViralTargets"), XS_Spell_GetViralTargets, file, "$"); + newXSproto(strcpy(buf, "GetViralTimer"), XS_Spell_GetViralTimer, file, "$"); + newXSproto(strcpy(buf, "GetYouCast"), XS_Spell_GetYouCast, file, "$"); + newXSproto(strcpy(buf, "GetZoneType"), XS_Spell_GetZoneType, file, "$"); + XSRETURN_YES; +} + +#endif //EMBPERL_XS_CLASSES diff --git a/zone/perlparser.h b/zone/perlparser.h index 60020306f..686018543 100644 --- a/zone/perlparser.h +++ b/zone/perlparser.h @@ -32,7 +32,7 @@ public: PerlXSParser(); // ~PerlXSParser(); - virtual void SendCommands(const char * pkgprefix, const char *event, uint32 npcid, Mob* other, Mob* mob, ItemInst* iteminst); + virtual void SendCommands(const char * pkgprefix, const char *event, uint32 object_id, Mob* other, Mob* mob, ItemInst* iteminst, const SPDat_Spell_Struct *spell); protected: void map_funs(); diff --git a/zone/pets.cpp b/zone/pets.cpp index e88f0f246..1b3a7e3ec 100644 --- a/zone/pets.cpp +++ b/zone/pets.cpp @@ -273,6 +273,8 @@ void Mob::MakePoweredPet(uint16 spell_id, const char* pettype, int16 petpower, if (petname != nullptr) { // Name was provided, use it. strn0cpy(npc_type->name, petname, 64); + EntityList::RemoveNumbers(npc_type->name); + entity_list.MakeNameUnique(npc_type->name); } else if (record.petnaming == 0) { strcpy(npc_type->name, this->GetCleanName()); npc_type->name[25] = '\0'; @@ -299,39 +301,16 @@ void Mob::MakePoweredPet(uint16 spell_id, const char* pettype, int16 petpower, strcat(npc_type->name, "`s_pet"); } - //handle beastlord pet appearance - if(record.petnaming == 2) - { - switch(GetBaseRace()) - { - case VAHSHIR: - npc_type->race = TIGER; - npc_type->size *= 0.8f; - break; - case TROLL: - npc_type->race = ALLIGATOR; - npc_type->size *= 2.5f; - break; - case OGRE: - npc_type->race = BEAR; - npc_type->texture = 3; - npc_type->gender = 2; - break; - case BARBARIAN: - npc_type->race = WOLF; - npc_type->texture = 2; - break; - case IKSAR: - npc_type->race = WOLF; - npc_type->texture = 0; - npc_type->gender = 1; - npc_type->size *= 2.0f; - npc_type->luclinface = 0; - break; - default: - npc_type->race = WOLF; - npc_type->texture = 0; - } + // Beastlord Pets + if(record.petnaming == 2) { + uint16 race_id = GetBaseRace(); + auto beastlord_pet_data = content_db.GetBeastlordPetData(race_id); + npc_type->race = beastlord_pet_data.race_id; + npc_type->texture = beastlord_pet_data.texture; + npc_type->helmtexture = beastlord_pet_data.helm_texture; + npc_type->gender = beastlord_pet_data.gender; + npc_type->size *= beastlord_pet_data.size_modifier; + npc_type->luclinface = beastlord_pet_data.face; } // handle monster summoning pet appearance @@ -408,20 +387,64 @@ void Mob::MakePoweredPet(uint16 spell_id, const char* pettype, int16 petpower, SetPetID(npc->GetID()); // We need to handle PetType 5 (petHatelist), add the current target to the hatelist of the pet - if (record.petcontrol == petTargetLock) { - Mob* target = GetTarget(); + Mob* m_target = GetTarget(); - if (target){ - npc->AddToHateList(target, 1); - npc->SetPetTargetLockID(target->GetID()); + bool activiate_pet = false; + if (m_target && m_target->GetID() != GetID()) { + + if (spells[spell_id].target_type == ST_Self) { + float distance = CalculateDistance(m_target->GetX(), m_target->GetY(), m_target->GetZ()); + if (distance <= 200) { //Live distance on targetlock pets that self cast. No message is given if not in range. + activiate_pet = true; + } + } + else { + activiate_pet = true; + } + } + + if (activiate_pet){ + npc->AddToHateList(m_target, 1); + npc->SetPetTargetLockID(m_target->GetID()); npc->SetSpecialAbility(IMMUNE_AGGRO, 1); } - else - npc->Kill(); //On live casts spell 892 Unsummon (Kayen - Too limiting to use that for emu since pet can have more than 20k HP) + else { + npc->CastSpell(SPELL_UNSUMMON_SELF, npc->GetID()); //Live like behavior, damages self for 20K + if (!npc->HasDied()) { + npc->Kill(); //Ensure pet dies if over 20k HP. + } + } } } + +void NPC::TryDepopTargetLockedPets(Mob* current_target) { + + if (!current_target || (current_target && (current_target->GetID() != GetPetTargetLockID()) || current_target->IsCorpse())) { + + //Use when swarmpets are set to auto lock from quest or rule + if (GetSwarmInfo() && GetSwarmInfo()->target) { + Mob* owner = entity_list.GetMobID(GetSwarmInfo()->owner_id); + if (owner) { + owner->SetTempPetCount(owner->GetTempPetCount() - 1); + } + Depop(); + return; + } + //Use when pets are given petype 5 + if (IsPet() && GetPetType() == petTargetLock && GetPetTargetLockID()) { + CastSpell(SPELL_UNSUMMON_SELF, GetID()); //Live like behavior, damages self for 20K + if (!HasDied()) { + Kill(); //Ensure pet dies if over 20k HP. + } + return; + } + } +} + + + /* This is why the pets ghost - pets were being spawned too far away from its npc owner and some into walls or objects (+10), this sometimes creates the "ghost" effect. I changed to +2 (as close as I could get while it still looked good). I also noticed this can happen if an NPC is spawned on the same spot of another or in a related bad spot.*/ @@ -583,7 +606,7 @@ void NPC::SetPetState(SpellBuff_Struct *pet_buffs, uint32 *items) { buffs[i].casterlevel = pet_buffs[i].level; buffs[i].casterid = 0; buffs[i].counters = pet_buffs[i].counters; - buffs[i].numhits = spells[pet_buffs[i].spellid].numhits; + buffs[i].hit_number = spells[pet_buffs[i].spellid].hit_number; buffs[i].instrument_mod = pet_buffs[i].bard_modifier; } else { @@ -598,15 +621,18 @@ void NPC::SetPetState(SpellBuff_Struct *pet_buffs, uint32 *items) { for (int j1=0; j1 < GetPetMaxTotalSlots(); j1++) { if (buffs[j1].spellid <= (uint32)SPDAT_RECORDS) { for (int x1=0; x1 < EFFECT_COUNT; x1++) { - switch (spells[buffs[j1].spellid].effectid[x1]) { + switch (spells[buffs[j1].spellid].effect_id[x1]) { + case SE_AddMeleeProc: case SE_WeaponProc: // We need to reapply buff based procs // We need to do this here so suspended pets also regain their procs. - if (spells[buffs[j1].spellid].base2[x1] == 0) { - AddProcToWeapon(GetProcID(buffs[j1].spellid,x1), false, 100, buffs[j1].spellid); - } else { - AddProcToWeapon(GetProcID(buffs[j1].spellid,x1), false, 100+spells[buffs[j1].spellid].base2[x1], buffs[j1].spellid); - } + AddProcToWeapon(GetProcID(buffs[j1].spellid,x1), false, 100+spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid, buffs[j1].casterlevel, GetProcLimitTimer(buffs[j1].spellid, ProcType::MELEE_PROC)); + break; + case SE_DefensiveProc: + AddDefensiveProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid, GetProcLimitTimer(buffs[j1].spellid, ProcType::DEFENSIVE_PROC)); + break; + case SE_RangedProc: + AddRangedProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid, GetProcLimitTimer(buffs[j1].spellid, ProcType::RANGED_PROC)); break; case SE_Charm: case SE_Rune: @@ -716,3 +742,29 @@ bool Pet::CheckSpellLevelRestriction(uint16 spell_id) return owner->CheckSpellLevelRestriction(spell_id); return true; } + +BeastlordPetData::PetStruct ZoneDatabase::GetBeastlordPetData(uint16 race_id) { + BeastlordPetData::PetStruct beastlord_pet_data; + std::string query = fmt::format( + SQL( + SELECT + `pet_race`, `texture`, `helm_texture`, `gender`, `size_modifier`, `face` + FROM `pets_beastlord_data` + WHERE `player_race` = {} + ), + race_id + ); + auto results = QueryDatabase(query); + if (!results.Success() || results.RowCount() != 1) { + return beastlord_pet_data; + } + + auto row = results.begin(); + beastlord_pet_data.race_id = atoi(row[0]); + beastlord_pet_data.texture = atoi(row[1]); + beastlord_pet_data.helm_texture = atoi(row[2]); + beastlord_pet_data.gender = atoi(row[3]); + beastlord_pet_data.size_modifier = atof(row[4]); + beastlord_pet_data.face = atoi(row[5]); + return beastlord_pet_data; +} diff --git a/zone/quest_interface.h b/zone/quest_interface.h index d0f121cb6..794251e36 100644 --- a/zone/quest_interface.h +++ b/zone/quest_interface.h @@ -43,7 +43,7 @@ public: std::vector *extra_pointers) { return 0; } virtual int EventItem(QuestEventID evt, Client *client, EQ::ItemInstance *item, Mob *mob, std::string data, uint32 extra_data, std::vector *extra_pointers) { return 0; } - virtual int EventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, uint32 extra_data, + virtual int EventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, std::string data, uint32 extra_data, std::vector *extra_pointers) { return 0; } virtual int EventEncounter(QuestEventID evt, std::string encounter_name, std::string data, uint32 extra_data, std::vector *extra_pointers) { return 0; } @@ -70,7 +70,7 @@ public: std::vector *extra_pointers) { return 0; } virtual int DispatchEventItem(QuestEventID evt, Client *client, EQ::ItemInstance *item, Mob *mob, std::string data, uint32 extra_data, std::vector *extra_pointers) { return 0; } - virtual int DispatchEventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, uint32 extra_data, + virtual int DispatchEventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, std::string data, uint32 extra_data, std::vector *extra_pointers) { return 0; } virtual void AddVar(std::string name, std::string val) { } diff --git a/zone/quest_parser_collection.cpp b/zone/quest_parser_collection.cpp index 625425839..8c5185ad6 100644 --- a/zone/quest_parser_collection.cpp +++ b/zone/quest_parser_collection.cpp @@ -410,21 +410,21 @@ int QuestParserCollection::EventItem(QuestEventID evt, Client *client, EQ::ItemI return 0; } -int QuestParserCollection::EventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, uint32 extra_data, +int QuestParserCollection::EventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, std::string data, uint32 extra_data, std::vector *extra_pointers) { auto iter = _spell_quest_status.find(spell_id); if(iter != _spell_quest_status.end()) { //loaded or failed to load if(iter->second != QuestFailedToLoad) { auto qiter = _interfaces.find(iter->second); - int ret = DispatchEventSpell(evt, npc, client, spell_id, extra_data, extra_pointers); - int i = qiter->second->EventSpell(evt, npc, client, spell_id, extra_data, extra_pointers); + int ret = DispatchEventSpell(evt, npc, client, spell_id, data, extra_data, extra_pointers); + int i = qiter->second->EventSpell(evt, npc, client, spell_id, data, extra_data, extra_pointers); if(i != 0) { ret = i; } return ret; } - return DispatchEventSpell(evt, npc, client, spell_id, extra_data, extra_pointers); + return DispatchEventSpell(evt, npc, client, spell_id, data, extra_data, extra_pointers); } else if (_spell_quest_status[spell_id] != QuestFailedToLoad) { std::string filename; @@ -432,8 +432,8 @@ int QuestParserCollection::EventSpell(QuestEventID evt, NPC* npc, Client *client if (qi) { _spell_quest_status[spell_id] = qi->GetIdentifier(); qi->LoadSpellScript(filename, spell_id); - int ret = DispatchEventSpell(evt, npc, client, spell_id, extra_data, extra_pointers); - int i = qi->EventSpell(evt, npc, client, spell_id, extra_data, extra_pointers); + int ret = DispatchEventSpell(evt, npc, client, spell_id, data, extra_data, extra_pointers); + int i = qi->EventSpell(evt, npc, client, spell_id, data, extra_data, extra_pointers); if (i != 0) { ret = i; } @@ -441,7 +441,7 @@ int QuestParserCollection::EventSpell(QuestEventID evt, NPC* npc, Client *client } else { _spell_quest_status[spell_id] = QuestFailedToLoad; - return DispatchEventSpell(evt, npc, client, spell_id, extra_data, extra_pointers); + return DispatchEventSpell(evt, npc, client, spell_id, data, extra_data, extra_pointers); } } return 0; @@ -1042,12 +1042,12 @@ int QuestParserCollection::DispatchEventItem(QuestEventID evt, Client *client, E return ret; } -int QuestParserCollection::DispatchEventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, uint32 extra_data, +int QuestParserCollection::DispatchEventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, std::string data, uint32 extra_data, std::vector *extra_pointers) { int ret = 0; auto iter = _load_precedence.begin(); while(iter != _load_precedence.end()) { - int i = (*iter)->DispatchEventSpell(evt, npc, client, spell_id, extra_data, extra_pointers); + int i = (*iter)->DispatchEventSpell(evt, npc, client, spell_id, data, extra_data, extra_pointers); if(i != 0) { ret = i; } diff --git a/zone/quest_parser_collection.h b/zone/quest_parser_collection.h index 082b5b6f4..c8ebc2427 100644 --- a/zone/quest_parser_collection.h +++ b/zone/quest_parser_collection.h @@ -78,7 +78,7 @@ public: std::vector *extra_pointers = nullptr); int EventItem(QuestEventID evt, Client *client, EQ::ItemInstance *item, Mob *mob, std::string data, uint32 extra_data, std::vector *extra_pointers = nullptr); - int EventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, uint32 extra_data, + int EventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, std::string data, uint32 extra_data, std::vector *extra_pointers = nullptr); int EventEncounter(QuestEventID evt, std::string encounter_name, std::string data, uint32 extra_data, std::vector *extra_pointers = nullptr); @@ -131,7 +131,7 @@ private: std::vector *extra_pointers); int DispatchEventItem(QuestEventID evt, Client *client, EQ::ItemInstance *item, Mob *mob, std::string data, uint32 extra_data, std::vector *extra_pointers); - int DispatchEventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, uint32 extra_data, + int DispatchEventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, std::string data, uint32 extra_data, std::vector *extra_pointers); std::map _interfaces; diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index 3ffc505dd..02ffb60df 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -36,6 +36,7 @@ #include "zone.h" #include "zonedb.h" #include "zone_store.h" +#include "dialogue_window.h" #include #include @@ -56,6 +57,7 @@ QuestManager quest_manager; Mob *owner = nullptr; \ Client *initiator = nullptr; \ EQ::ItemInstance* questitem = nullptr; \ + const SPDat_Spell_Struct* questspell = nullptr; \ bool depop_npc = false; \ std::string encounter; \ do { \ @@ -64,6 +66,7 @@ QuestManager quest_manager; owner = e.owner; \ initiator = e.initiator; \ questitem = e.questitem; \ + questspell = e.questspell; \ depop_npc = e.depop_npc; \ encounter = e.encounter; \ } \ @@ -117,11 +120,12 @@ void QuestManager::Process() { } } -void QuestManager::StartQuest(Mob *_owner, Client *_initiator, EQ::ItemInstance* _questitem, std::string encounter) { +void QuestManager::StartQuest(Mob *_owner, Client *_initiator, EQ::ItemInstance* _questitem, const SPDat_Spell_Struct* _questspell, std::string encounter) { running_quest run; run.owner = _owner; run.initiator = _initiator; run.questitem = _questitem; + run.questspell = _questspell; run.depop_npc = false; run.encounter = encounter; quests_running_.push(run); @@ -371,14 +375,14 @@ void QuestManager::castspell(int spell_id, int target_id) { if (owner) { Mob *tgt = entity_list.GetMob(target_id); if(tgt != nullptr) - owner->SpellFinished(spell_id, tgt, EQ::spells::CastingSlot::Item, 0, -1, spells[spell_id].ResistDiff); + owner->SpellFinished(spell_id, tgt, EQ::spells::CastingSlot::Item, 0, -1, spells[spell_id].resist_difficulty); } } void QuestManager::selfcast(int spell_id) { QuestManagerCurrentQuestVars(); if (initiator) - initiator->SpellFinished(spell_id, initiator, EQ::spells::CastingSlot::Item, 0, -1, spells[spell_id].ResistDiff); + initiator->SpellFinished(spell_id, initiator, EQ::spells::CastingSlot::Item, 0, -1, spells[spell_id].resist_difficulty); } void QuestManager::addloot(int item_id, int charges, bool equipitem, int aug1, int aug2, int aug3, int aug4, int aug5, int aug6) { @@ -761,18 +765,39 @@ void QuestManager::shout2(const char *str) { if (!owner) { LogQuests("QuestManager::shout2 called with nullptr owner. Probably syntax error in quest file"); return; - } - else { - worldserver.SendEmoteMessage(0,0,0,13, "%s shouts, '%s'", owner->GetCleanName(), str); + } else { + worldserver.SendEmoteMessage( + 0, + 0, + AccountStatus::Player, + Chat::Red, + fmt::format( + "{} shouts, '{}'", + owner->GetCleanName(), + str + ).c_str() + ); } } void QuestManager::gmsay(const char *str, uint32 color, bool send_to_world, uint32 to_guilddbid, uint32 to_minstatus) { QuestManagerCurrentQuestVars(); - if(send_to_world) - worldserver.SendEmoteMessage(0, to_guilddbid, to_minstatus, color, "%s", str); - else - entity_list.MessageStatus(to_guilddbid, to_minstatus, color, "%s", str); + if(send_to_world) { + worldserver.SendEmoteMessage( + 0, + to_guilddbid, + to_minstatus, + color, + str + ); + } else { + entity_list.MessageStatus( + to_guilddbid, + to_minstatus, + color, + str + ); + } } void QuestManager::depop(int npc_type) { @@ -897,15 +922,15 @@ void QuestManager::sfollow() { owner->SetFollowID(0); } -void QuestManager::changedeity(int diety_id) { +void QuestManager::changedeity(int deity_id) { QuestManagerCurrentQuestVars(); //Changes the deity. if(initiator) { if(initiator->IsClient()) { - initiator->SetDeity(diety_id); - initiator->Message(Chat::Yellow,"Your Deity has been changed/set to: %i", diety_id); + initiator->SetDeity(deity_id); + initiator->Message(Chat::Yellow,"Your Deity has been changed/set to: %i", deity_id); initiator->Save(1); initiator->Kick("Deity change by QuestManager"); } @@ -1008,15 +1033,27 @@ std::string QuestManager::getspellname(uint32 spell_id) { } std::string QuestManager::getskillname(int skill_id) { - if (skill_id >= 0 && skill_id < EQ::skills::SkillCount) { - std::map Skills = EQ::skills::GetSkillTypeMap(); - for (auto skills_iter : Skills) { - if (skill_id == skills_iter.first) { - return skills_iter.second; - } - } - } - return std::string(); + return EQ::skills::GetSkillName(static_cast(skill_id)); +} + +std::string QuestManager::getldonthemename(uint32 theme_id) { + return EQ::constants::GetLDoNThemeName(theme_id); +} + +std::string QuestManager::getfactionname(int faction_id) { + return content_db.GetFactionName(faction_id); +} + +std::string QuestManager::getlanguagename(int language_id) { + return EQ::constants::GetLanguageName(language_id); +} + +std::string QuestManager::getbodytypename(uint32 bodytype_id) { + return EQ::constants::GetBodyTypeName(static_cast(bodytype_id)); +} + +std::string QuestManager::getconsiderlevelname(uint8 consider_level) { + return EQ::constants::GetConsiderLevelName(consider_level); } void QuestManager::safemove() { @@ -1044,6 +1081,31 @@ void QuestManager::snow(int weather) { safe_delete(outapp); } +void QuestManager::rename(std::string name) { + QuestManagerCurrentQuestVars(); + if (initiator && initiator->IsClient()) { + std::string current_name = initiator->GetName(); + if (initiator->ChangeFirstName(name.c_str(), current_name.c_str())) { + initiator->Message( + Chat::White, + fmt::format( + "Successfully renamed to {}, kicking to character select.", + name + ).c_str() + ); + initiator->Kick("Name was changed."); + } else { + initiator->Message( + Chat::Red, + fmt::format( + "Failed to rename {} to {}.", + current_name, name + ).c_str() + ); + } + } +} + void QuestManager::surname(const char *name) { QuestManagerCurrentQuestVars(); //Changes the last name. @@ -1087,65 +1149,12 @@ void QuestManager::permagender(int gender_id) { uint16 QuestManager::scribespells(uint8 max_level, uint8 min_level) { QuestManagerCurrentQuestVars(); - int book_slot = initiator->GetNextAvailableSpellBookSlot(); - std::vector spell_ids = initiator->GetScribeableSpells(min_level, max_level); - int spell_count = spell_ids.size(); - int spells_learned = 0; - if (spell_count > 0) { - for (auto spell_id : spell_ids) { - if (book_slot == -1) { - initiator->Message( - Chat::Red, - "Unable to scribe spell %s (%i) to Spell Book: Spell Book is Full.", spells[spell_id].name, spell_id - ); - break; - } - - if (initiator->HasSpellScribed(spell_id)) - continue; - - initiator->ScribeSpell(spell_id, book_slot); - book_slot = initiator->GetNextAvailableSpellBookSlot(book_slot); - spells_learned++; - } - } - - if (spells_learned > 0) { - std::string spell_message = (spells_learned == 1 ? "a new spell" : fmt::format("{} new spells", spells_learned)); - initiator->Message(Chat::White, fmt::format("You have learned {}!", spell_message).c_str()); - } - return spells_learned; + return initiator->ScribeSpells(min_level, max_level); } uint16 QuestManager::traindiscs(uint8 max_level, uint8 min_level) { QuestManagerCurrentQuestVars(); - int character_id = initiator->CharacterID(); - std::vector spell_ids = initiator->GetLearnableDisciplines(min_level, max_level); - int discipline_count = spell_ids.size(); - int disciplines_learned = 0; - if (discipline_count > 0) { - for (auto spell_id : spell_ids) { - if (initiator->HasDisciplineLearned(spell_id)) - continue; - - for (uint32 index = 0; index < MAX_PP_DISCIPLINES; index++) { - if (initiator->GetPP().disciplines.values[index] == 0) { - initiator->GetPP().disciplines.values[index] = spell_id; - database.SaveCharacterDisc(character_id, index, spell_id); - disciplines_learned++; - break; - } - } - } - } - - if (disciplines_learned > 0) { - std::string discipline_message = (disciplines_learned == 1 ? "a new discipline" : fmt::format("{} new disciplines", disciplines_learned)); - initiator->SendDisciplineUpdate(); - initiator->Message(Chat::White, fmt::format("You have learned {}!", discipline_message).c_str()); - } - - return disciplines_learned; + return initiator->LearnDisciplines(min_level, max_level); } void QuestManager::unscribespells() { @@ -1348,7 +1357,8 @@ void QuestManager::save() { void QuestManager::faction(int faction_id, int faction_value, int temp) { QuestManagerCurrentQuestVars(); - if (initiator && initiator->IsClient()) { + running_quest run = quests_running_.top(); + if(run.owner->IsCharmed() == false && initiator && initiator->IsClient()) { if(faction_id != 0 && faction_value != 0) { initiator->SetFactionLevel2( initiator->CharacterID(), @@ -1381,30 +1391,66 @@ void QuestManager::setguild(uint32 new_guild_id, uint8 new_rank) { void QuestManager::CreateGuild(const char *guild_name, const char *leader) { QuestManagerCurrentQuestVars(); - uint32 cid = database.GetCharacterID(leader); - char hString[250]; - if (cid == 0) { - worldserver.SendEmoteMessage(0, 0, 80, 15, "%s", "Guild Creation: Guild leader not found."); - return; - } - - uint32 tmp = guild_mgr.FindGuildByLeader(cid); - if (tmp != GUILD_NONE) { - sprintf(hString, "Guild Creation: Error: %s already is the leader of DB# %u '%s'.", leader, tmp, guild_mgr.GetGuildName(tmp)); - worldserver.SendEmoteMessage(0, 0, 80, 15, "%s", hString); - } - else { - uint32 gid = guild_mgr.CreateGuild(guild_name, cid); - if (gid == GUILD_NONE) - worldserver.SendEmoteMessage(0, 0, 80, 15, "%s", "Guild Creation: Guild creation failed"); - else { - sprintf(hString, "Guild Creation: Guild created: Leader: %u, number %u: %s", cid, gid, leader); - worldserver.SendEmoteMessage(0, 0, 80, 15, "%s", hString); - if(!guild_mgr.SetGuild(cid, gid, GUILD_LEADER)) - worldserver.SendEmoteMessage(0, 0, 80, 15, "%s", "Unable to set guild leader's guild in the database. Your going to have to run #guild set"); - } + uint32 character_id = database.GetCharacterID(leader); + if (character_id == 0) { + worldserver.SendEmoteMessage( + 0, + 0, + AccountStatus::QuestTroupe, + Chat::Yellow, + "Guild Error | Guild leader not found." + ); + return; + } + uint32 tmp = guild_mgr.FindGuildByLeader(character_id); + if (tmp != GUILD_NONE) { + worldserver.SendEmoteMessage( + 0, + 0, + AccountStatus::QuestTroupe, + Chat::Yellow, + fmt::format( + "Guild Error | {} already is the leader of {} ({}).", + leader, + guild_mgr.GetGuildName(tmp), + tmp + ).c_str() + ); + } else { + uint32 gid = guild_mgr.CreateGuild(guild_name, character_id); + if (gid == GUILD_NONE) { + worldserver.SendEmoteMessage( + 0, + 0, + AccountStatus::QuestTroupe, + Chat::Yellow, + "Guild Error | Guild creation failed." + ); + } else { + worldserver.SendEmoteMessage( + 0, + 0, + AccountStatus::QuestTroupe, + Chat::Yellow, + fmt::format( + "Guild Created | Leader: {} ({}) ID: {}", + leader, + character_id, + gid + ).c_str() + ); + if (!guild_mgr.SetGuild(character_id, gid, GUILD_LEADER)) { + worldserver.SendEmoteMessage( + 0, + 0, + AccountStatus::QuestTroupe, + Chat::Yellow, + "Unable to set guild leader's guild in the database. Use #guild set." + ); } + } + } } void QuestManager::settime(uint8 new_hour, uint8 new_min, bool update_world /*= true*/) @@ -1673,10 +1719,17 @@ void QuestManager::ding() { } -void QuestManager::rebind(int zoneid, const glm::vec3& location) { +void QuestManager::rebind(int zone_id, const glm::vec3& location) { QuestManagerCurrentQuestVars(); if(initiator && initiator->IsClient()) { - initiator->SetBindPoint(0, zoneid, 0, location); + initiator->SetBindPoint(0, zone_id, 0, location); + } +} + +void QuestManager::rebind(int zone_id, const glm::vec4& location) { + QuestManagerCurrentQuestVars(); + if(initiator && initiator->IsClient()) { + initiator->SetBindPoint2(0, zone_id, 0, location); } } @@ -1720,22 +1773,38 @@ void QuestManager::resume() { owner->CastToNPC()->ResumeWandering(); } -void QuestManager::addldonpoints(int32 points, uint32 theme) { +void QuestManager::addldonpoints(uint32 theme_id, int points) { QuestManagerCurrentQuestVars(); if(initiator) - initiator->UpdateLDoNPoints(points, theme); + initiator->UpdateLDoNPoints(theme_id, points); } -void QuestManager::addldonwin(int32 wins, uint32 theme) { +void QuestManager::addldonloss(uint32 theme_id) { QuestManagerCurrentQuestVars(); - if(initiator) - initiator->UpdateLDoNWins(theme, wins); + if(initiator) { + initiator->UpdateLDoNWinLoss(theme_id); + } } -void QuestManager::addldonloss(int32 losses, uint32 theme) { +void QuestManager::addldonwin(uint32 theme_id) { QuestManagerCurrentQuestVars(); - if(initiator) - initiator->UpdateLDoNLosses(theme, losses); + if(initiator) { + initiator->UpdateLDoNWinLoss(theme_id, true); + } +} + +void QuestManager::removeldonloss(uint32 theme_id) { + QuestManagerCurrentQuestVars(); + if(initiator) { + initiator->UpdateLDoNWinLoss(theme_id, false, true); + } +} + +void QuestManager::removeldonwin(uint32 theme_id) { + QuestManagerCurrentQuestVars(); + if(initiator) { + initiator->UpdateLDoNWinLoss(theme_id, true, true); + } } void QuestManager::setnexthpevent(int at) { @@ -2467,14 +2536,19 @@ void QuestManager::ze(int type, const char *str) { } void QuestManager::we(int type, const char *str) { - worldserver.SendEmoteMessage(0, 0, type, str); + worldserver.SendEmoteMessage( + 0, + 0, + type, + str + ); } void QuestManager::message(int color, const char *message) { QuestManagerCurrentQuestVars(); if (!initiator) return; - + initiator->Message(color, message); } @@ -2613,65 +2687,12 @@ int QuestManager::collectitems(uint32 item_id, bool remove) int QuestManager::countitem(uint32 item_id) { QuestManagerCurrentQuestVars(); - int quantity = 0; - EQ::ItemInstance *item = nullptr; - static const int16 slots[][2] = { - { EQ::invslot::POSSESSIONS_BEGIN, EQ::invslot::POSSESSIONS_END }, - { EQ::invbag::GENERAL_BAGS_BEGIN, EQ::invbag::GENERAL_BAGS_END }, - { EQ::invbag::CURSOR_BAG_BEGIN, EQ::invbag::CURSOR_BAG_END}, - { EQ::invslot::BANK_BEGIN, EQ::invslot::BANK_END }, - { EQ::invbag::BANK_BAGS_BEGIN, EQ::invbag::BANK_BAGS_END }, - { EQ::invslot::SHARED_BANK_BEGIN, EQ::invslot::SHARED_BANK_END }, - { EQ::invbag::SHARED_BANK_BAGS_BEGIN, EQ::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; + return initiator->CountItem(item_id); } void QuestManager::removeitem(uint32 item_id, uint32 quantity) { QuestManagerCurrentQuestVars(); - EQ::ItemInstance *item = nullptr; - static const int16 slots[][2] = { - { EQ::invslot::POSSESSIONS_BEGIN, EQ::invslot::POSSESSIONS_END }, - { EQ::invbag::GENERAL_BAGS_BEGIN, EQ::invbag::GENERAL_BAGS_END }, - { EQ::invbag::CURSOR_BAG_BEGIN, EQ::invbag::CURSOR_BAG_END}, - { EQ::invslot::BANK_BEGIN, EQ::invslot::BANK_END }, - { EQ::invbag::BANK_BAGS_BEGIN, EQ::invbag::BANK_BAGS_END }, - { EQ::invslot::SHARED_BANK_BEGIN, EQ::invslot::SHARED_BANK_END }, - { EQ::invbag::SHARED_BANK_BAGS_BEGIN, EQ::invbag::SHARED_BANK_BAGS_END }, - }; - int removed_count = 0; - 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) { - if (removed_count == quantity) - break; - - item = initiator->GetInv().GetItem(slot_id); - if (item && item->GetID() == item_id) { - int stack_size = item->IsStackable() ? item->GetCharges() : 1; - if ((removed_count + stack_size) <= quantity) { - removed_count += stack_size; - initiator->DeleteItemInInventory(slot_id, stack_size, true); - } else { - int amount_left = (quantity - removed_count); - if (amount_left > 0 && stack_size >= amount_left) { - removed_count += amount_left; - initiator->DeleteItemInInventory(slot_id, amount_left, true); - } - } - } - } - } + initiator->RemoveItem(item_id, quantity); } void QuestManager::UpdateSpawnTimer(uint32 id, uint32 newTime) @@ -2774,11 +2795,20 @@ std::string QuestManager::getitemname(uint32 item_id) { return item_name; } -const char *QuestManager::getnpcnamebyid(uint32 npc_id) { +std::string QuestManager::getnpcnamebyid(uint32 npc_id) { + std::string res; if (npc_id > 0) { - return database.GetNPCNameByID(npc_id); + res = database.GetNPCNameByID(npc_id); } - return ""; + return res; +} + +std::string QuestManager::getcleannpcnamebyid(uint32 npc_id) { + std::string res; + if (npc_id > 0) { + res = database.GetCleanNPCNameByID(npc_id); + } + return res; } uint16 QuestManager::CreateInstance(const char *zone, int16 version, uint32 duration) @@ -2984,11 +3014,12 @@ std::string QuestManager::saylink(char *saylink_text, bool silent, const char *l return EQ::SayLinkEngine::GenerateQuestSaylink(saylink_text, silent, link_name); } -const char* QuestManager::getcharnamebyid(uint32 char_id) { +std::string QuestManager::getcharnamebyid(uint32 char_id) { + std::string res; if (char_id > 0) { - return database.GetCharNameByID(char_id); + res = database.GetCharNameByID(char_id); } - return ""; + return res; } uint32 QuestManager::getcharidbyname(const char* name) { @@ -2999,28 +3030,12 @@ 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; +uint32 QuestManager::getcurrencyid(uint32 item_id) { + return zone->GetCurrencyID(item_id); } -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; +uint32 QuestManager::getcurrencyitemid(uint32 currency_id) { + return zone->GetCurrencyItemID(currency_id); } const char* QuestManager::getguildnamebyid(int guild_id) { @@ -3095,19 +3110,19 @@ uint8 QuestManager::FactionValue() case FACTION_SCOWLS: newfac = 1; break; - case FACTION_THREATENLY: + case FACTION_THREATENINGLY: newfac = 2; break; - case FACTION_DUBIOUS: + case FACTION_DUBIOUSLY: newfac = 3; break; - case FACTION_APPREHENSIVE: + case FACTION_APPREHENSIVELY: newfac = 4; break; - case FACTION_INDIFFERENT: + case FACTION_INDIFFERENTLY: newfac = 5; break; - case FACTION_AMIABLE: + case FACTION_AMIABLY: newfac = 6; break; case FACTION_KINDLY: @@ -3219,875 +3234,15 @@ int32 QuestManager::GetZoneID(const char *zone) { std::string QuestManager::GetZoneLongName(std::string zone_short_name) { - return zone_store.GetZoneLongName( - zone_store.GetZoneID(zone_short_name) - ); + return ZoneLongName(ZoneID(zone_short_name)); } -void QuestManager::CrossZoneAssignTaskByCharID(int character_id, uint32 task_id, bool enforce_level_requirement) { - QuestManagerCurrentQuestVars(); - if (initiator && owner) { - auto pack = new ServerPacket(ServerOP_CZTaskAssignPlayer, sizeof(CZTaskAssignPlayer_Struct)); - CZTaskAssignPlayer_Struct* CZTA = (CZTaskAssignPlayer_Struct*) pack->pBuffer; - CZTA->npc_entity_id = owner->GetID(); - CZTA->character_id = character_id; - CZTA->task_id = task_id; - CZTA->enforce_level_requirement = enforce_level_requirement; - worldserver.SendPacket(pack); - safe_delete(pack); - } +std::string QuestManager::GetZoneLongNameByID(uint32 zone_id) { + return ZoneLongName(zone_id); } -void QuestManager::CrossZoneAssignTaskByGroupID(int group_id, uint32 task_id, bool enforce_level_requirement) { - QuestManagerCurrentQuestVars(); - if (initiator && owner) { - auto pack = new ServerPacket(ServerOP_CZTaskAssignGroup, sizeof(CZTaskAssignGroup_Struct)); - CZTaskAssignGroup_Struct* CZTA = (CZTaskAssignGroup_Struct*) pack->pBuffer; - CZTA->npc_entity_id = owner->GetID(); - CZTA->group_id = group_id; - CZTA->task_id = task_id; - CZTA->enforce_level_requirement = enforce_level_requirement; - worldserver.SendPacket(pack); - safe_delete(pack); - } -} - -void QuestManager::CrossZoneAssignTaskByRaidID(int raid_id, uint32 task_id, bool enforce_level_requirement) { - QuestManagerCurrentQuestVars(); - if (initiator && owner) { - auto pack = new ServerPacket(ServerOP_CZTaskAssignRaid, sizeof(CZTaskAssignRaid_Struct)); - CZTaskAssignRaid_Struct* CZTA = (CZTaskAssignRaid_Struct*) pack->pBuffer; - CZTA->npc_entity_id = owner->GetID(); - CZTA->raid_id = raid_id; - CZTA->task_id = task_id; - CZTA->enforce_level_requirement = enforce_level_requirement; - worldserver.SendPacket(pack); - safe_delete(pack); - } -} - -void QuestManager::CrossZoneAssignTaskByGuildID(int guild_id, uint32 task_id, bool enforce_level_requirement) { - QuestManagerCurrentQuestVars(); - if (initiator && owner) { - auto pack = new ServerPacket(ServerOP_CZTaskAssignGuild, sizeof(CZTaskAssignGuild_Struct)); - CZTaskAssignGuild_Struct* CZTA = (CZTaskAssignGuild_Struct*) pack->pBuffer; - CZTA->npc_entity_id = owner->GetID(); - CZTA->guild_id = guild_id; - CZTA->task_id = task_id; - CZTA->enforce_level_requirement = enforce_level_requirement; - worldserver.SendPacket(pack); - safe_delete(pack); - } -} - -void QuestManager::CrossZoneCastSpellByCharID(int character_id, uint32 spell_id) { - auto pack = new ServerPacket(ServerOP_CZCastSpellPlayer, sizeof(CZCastSpellPlayer_Struct)); - CZCastSpellPlayer_Struct* CZCS = (CZCastSpellPlayer_Struct*) pack->pBuffer; - CZCS->character_id = character_id; - CZCS->spell_id = spell_id; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneCastSpellByGroupID(int group_id, uint32 spell_id) { - auto pack = new ServerPacket(ServerOP_CZCastSpellGroup, sizeof(CZCastSpellGroup_Struct)); - CZCastSpellGroup_Struct* CZCS = (CZCastSpellGroup_Struct*) pack->pBuffer; - CZCS->group_id = group_id; - CZCS->spell_id = spell_id; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneCastSpellByRaidID(int raid_id, uint32 spell_id) { - auto pack = new ServerPacket(ServerOP_CZCastSpellRaid, sizeof(CZCastSpellRaid_Struct)); - CZCastSpellRaid_Struct* CZCS = (CZCastSpellRaid_Struct*) pack->pBuffer; - CZCS->raid_id = raid_id; - CZCS->spell_id = spell_id; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneCastSpellByGuildID(int guild_id, uint32 spell_id) { - auto pack = new ServerPacket(ServerOP_CZCastSpellGuild, sizeof(CZCastSpellGuild_Struct)); - CZCastSpellGuild_Struct* CZCS = (CZCastSpellGuild_Struct*) pack->pBuffer; - CZCS->guild_id = guild_id; - CZCS->spell_id = spell_id; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneDisableTaskByCharID(int character_id, uint32 task_id) { - auto pack = new ServerPacket(ServerOP_CZTaskDisablePlayer, sizeof(CZTaskDisablePlayer_Struct)); - CZTaskDisablePlayer_Struct* CZTD = (CZTaskDisablePlayer_Struct*) pack->pBuffer; - CZTD->character_id = character_id; - CZTD->task_id = task_id; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneDisableTaskByGroupID(int group_id, uint32 task_id) { - auto pack = new ServerPacket(ServerOP_CZTaskDisableGroup, sizeof(CZTaskDisableGroup_Struct)); - CZTaskDisableGroup_Struct* CZTD = (CZTaskDisableGroup_Struct*) pack->pBuffer; - CZTD->group_id = group_id; - CZTD->task_id = task_id; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneDisableTaskByRaidID(int raid_id, uint32 task_id) { - auto pack = new ServerPacket(ServerOP_CZTaskDisableRaid, sizeof(CZTaskDisableRaid_Struct)); - CZTaskDisableRaid_Struct* CZTD = (CZTaskDisableRaid_Struct*) pack->pBuffer; - CZTD->raid_id = raid_id; - CZTD->task_id = task_id; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneDisableTaskByGuildID(int guild_id, uint32 task_id) { - auto pack = new ServerPacket(ServerOP_CZTaskDisableGuild, sizeof(CZTaskDisableGuild_Struct)); - CZTaskDisableGuild_Struct* CZTD = (CZTaskDisableGuild_Struct*) pack->pBuffer; - CZTD->guild_id = guild_id; - CZTD->task_id = task_id; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneEnableTaskByCharID(int character_id, uint32 task_id) { - auto pack = new ServerPacket(ServerOP_CZTaskEnablePlayer, sizeof(CZTaskEnablePlayer_Struct)); - CZTaskEnablePlayer_Struct* CZTE = (CZTaskEnablePlayer_Struct*) pack->pBuffer; - CZTE->character_id = character_id; - CZTE->task_id = task_id; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneEnableTaskByGroupID(int group_id, uint32 task_id) { - auto pack = new ServerPacket(ServerOP_CZTaskEnableGroup, sizeof(CZTaskEnableGroup_Struct)); - CZTaskEnableGroup_Struct* CZTE = (CZTaskEnableGroup_Struct*) pack->pBuffer; - CZTE->group_id = group_id; - CZTE->task_id = task_id; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneEnableTaskByRaidID(int raid_id, uint32 task_id) { - auto pack = new ServerPacket(ServerOP_CZTaskEnableRaid, sizeof(CZTaskEnableRaid_Struct)); - CZTaskEnableRaid_Struct* CZTE = (CZTaskEnableRaid_Struct*) pack->pBuffer; - CZTE->raid_id = raid_id; - CZTE->task_id = task_id; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneEnableTaskByGuildID(int guild_id, uint32 task_id) { - auto pack = new ServerPacket(ServerOP_CZTaskEnableGuild, sizeof(CZTaskEnableGuild_Struct)); - CZTaskEnableGuild_Struct* CZTE = (CZTaskEnableGuild_Struct*) pack->pBuffer; - CZTE->guild_id = guild_id; - CZTE->task_id = task_id; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneFailTaskByCharID(int character_id, uint32 task_id) { - auto pack = new ServerPacket(ServerOP_CZTaskFailPlayer, sizeof(CZTaskFailPlayer_Struct)); - CZTaskFailPlayer_Struct* CZTF = (CZTaskFailPlayer_Struct*) pack->pBuffer; - CZTF->character_id = character_id; - CZTF->task_id = task_id; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneFailTaskByGroupID(int group_id, uint32 task_id) { - auto pack = new ServerPacket(ServerOP_CZTaskFailGroup, sizeof(CZTaskFailGroup_Struct)); - CZTaskFailGroup_Struct* CZTF = (CZTaskFailGroup_Struct*) pack->pBuffer; - CZTF->group_id = group_id; - CZTF->task_id = task_id; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneFailTaskByRaidID(int raid_id, uint32 task_id) { - auto pack = new ServerPacket(ServerOP_CZTaskFailRaid, sizeof(CZTaskFailRaid_Struct)); - CZTaskFailRaid_Struct* CZTF = (CZTaskFailRaid_Struct*) pack->pBuffer; - CZTF->raid_id = raid_id; - CZTF->task_id = task_id; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneFailTaskByGuildID(int guild_id, uint32 task_id) { - auto pack = new ServerPacket(ServerOP_CZTaskFailGuild, sizeof(CZTaskFailGuild_Struct)); - CZTaskFailGuild_Struct* CZTF = (CZTaskFailGuild_Struct*) pack->pBuffer; - CZTF->guild_id = guild_id; - CZTF->task_id = task_id; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneMarqueeByCharID(int character_id, uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char *message) { - uint32 message_len = strlen(message) + 1; - auto pack = new ServerPacket(ServerOP_CZMarqueePlayer, sizeof(CZMarqueePlayer_Struct) + message_len); - CZMarqueePlayer_Struct* CZMS = (CZMarqueePlayer_Struct*) pack->pBuffer; - CZMS->character_id = character_id; - CZMS->type = type; - CZMS->priority = priority; - CZMS->fade_in = fade_in; - CZMS->fade_out = fade_out; - CZMS->duration = duration; - strn0cpy(CZMS->message, message, 512); - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneMarqueeByGroupID(int group_id, uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char *message) { - uint32 message_len = strlen(message) + 1; - auto pack = new ServerPacket(ServerOP_CZMarqueeGroup, sizeof(CZMarqueeGroup_Struct) + message_len); - CZMarqueeGroup_Struct* CZMS = (CZMarqueeGroup_Struct*) pack->pBuffer; - CZMS->group_id = group_id; - CZMS->type = type; - CZMS->priority = priority; - CZMS->fade_in = fade_in; - CZMS->fade_out = fade_out; - CZMS->duration = duration; - strn0cpy(CZMS->message, message, 512); - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneMarqueeByRaidID(int raid_id, uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char *message) { - uint32 message_len = strlen(message) + 1; - auto pack = new ServerPacket(ServerOP_CZMarqueeRaid, sizeof(CZMarqueeRaid_Struct) + message_len); - CZMarqueeRaid_Struct* CZMS = (CZMarqueeRaid_Struct*) pack->pBuffer; - CZMS->raid_id = raid_id; - CZMS->type = type; - CZMS->priority = priority; - CZMS->fade_in = fade_in; - CZMS->fade_out = fade_out; - CZMS->duration = duration; - strn0cpy(CZMS->message, message, 512); - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneMarqueeByGuildID(int guild_id, uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char *message) { - uint32 message_len = strlen(message) + 1; - auto pack = new ServerPacket(ServerOP_CZMarqueeGuild, sizeof(CZMarqueeGuild_Struct) + message_len); - CZMarqueeGuild_Struct* CZMS = (CZMarqueeGuild_Struct*) pack->pBuffer; - CZMS->guild_id = guild_id; - CZMS->type = type; - CZMS->priority = priority; - CZMS->fade_in = fade_in; - CZMS->fade_out = fade_out; - CZMS->duration = duration; - strn0cpy(CZMS->message, message, 512); - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneMessagePlayerByName(uint32 type, const char *character_name, const char *message) { - uint32 message_len = strlen(character_name) + 1; - uint32 message_len2 = strlen(message) + 1; - auto pack = new ServerPacket(ServerOP_CZMessagePlayer, sizeof(CZMessagePlayer_Struct) + message_len + message_len2); - CZMessagePlayer_Struct* CZSC = (CZMessagePlayer_Struct*) pack->pBuffer; - CZSC->type = type; - strn0cpy(CZSC->character_name, character_name, 64); - strn0cpy(CZSC->message, message, 512); - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneMessagePlayerByGroupID(uint32 type, int group_id, const char *message) { - uint32 message_len = strlen(message) + 1; - auto pack = new ServerPacket(ServerOP_CZMessageGroup, sizeof(CZMessageGroup_Struct) + message_len); - CZMessageGroup_Struct* CZGM = (CZMessageGroup_Struct*) pack->pBuffer; - CZGM->type = type; - CZGM->group_id = group_id; - strn0cpy(CZGM->message, message, 512); - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneMessagePlayerByRaidID(uint32 type, int raid_id, const char *message) { - uint32 message_len = strlen(message) + 1; - auto pack = new ServerPacket(ServerOP_CZMessageRaid, sizeof(CZMessageRaid_Struct) + message_len); - CZMessageRaid_Struct* CZRM = (CZMessageRaid_Struct*) pack->pBuffer; - CZRM->type = type; - CZRM->raid_id = raid_id; - strn0cpy(CZRM->message, message, 512); - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneMessagePlayerByGuildID(uint32 type, int guild_id, const char *message) { - uint32 message_len = strlen(message) + 1; - auto pack = new ServerPacket(ServerOP_CZMessageGuild, sizeof(CZMessageGuild_Struct) + message_len); - CZMessageGuild_Struct* CZGM = (CZMessageGuild_Struct*) pack->pBuffer; - CZGM->type = type; - CZGM->guild_id = guild_id; - strn0cpy(CZGM->message, message, 512); - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneMovePlayerByCharID(int character_id, const char *zone_short_name) { - uint32 message_len = strlen(zone_short_name) + 1; - auto pack = new ServerPacket(ServerOP_CZMovePlayer, sizeof(CZMovePlayer_Struct) + message_len); - CZMovePlayer_Struct* CZGM = (CZMovePlayer_Struct*) pack->pBuffer; - CZGM->character_id = character_id; - strn0cpy(CZGM->zone_short_name, zone_short_name, 32); - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneMovePlayerByGroupID(int group_id, const char *zone_short_name) { - uint32 message_len = strlen(zone_short_name) + 1; - auto pack = new ServerPacket(ServerOP_CZMoveGroup, sizeof(CZMoveGroup_Struct) + message_len); - CZMoveGroup_Struct* CZGM = (CZMoveGroup_Struct*) pack->pBuffer; - CZGM->group_id = group_id; - strn0cpy(CZGM->zone_short_name, zone_short_name, 32); - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneMovePlayerByRaidID(int raid_id, const char *zone_short_name) { - uint32 message_len = strlen(zone_short_name) + 1; - auto pack = new ServerPacket(ServerOP_CZMoveRaid, sizeof(CZMoveRaid_Struct) + message_len); - CZMoveRaid_Struct* CZRM = (CZMoveRaid_Struct*) pack->pBuffer; - CZRM->raid_id = raid_id; - strn0cpy(CZRM->zone_short_name, zone_short_name, 32); - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneMovePlayerByGuildID(int guild_id, const char *zone_short_name) { - uint32 message_len = strlen(zone_short_name) + 1; - auto pack = new ServerPacket(ServerOP_CZMoveGuild, sizeof(CZMoveGuild_Struct) + message_len); - CZMoveGuild_Struct* CZGM = (CZMoveGuild_Struct*) pack->pBuffer; - CZGM->guild_id = guild_id; - strn0cpy(CZGM->zone_short_name, zone_short_name, 32); - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneMoveInstanceByCharID(int character_id, uint16 instance_id) { - auto pack = new ServerPacket(ServerOP_CZMoveInstancePlayer, sizeof(CZMoveInstancePlayer_Struct)); - CZMoveInstancePlayer_Struct* CZMS = (CZMoveInstancePlayer_Struct*) pack->pBuffer; - CZMS->character_id = character_id; - CZMS->instance_id = instance_id; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneMoveInstanceByGroupID(int group_id, uint16 instance_id) { - auto pack = new ServerPacket(ServerOP_CZMoveInstanceGroup, sizeof(CZMoveInstanceGroup_Struct)); - CZMoveInstanceGroup_Struct* CZMS = (CZMoveInstanceGroup_Struct*) pack->pBuffer; - CZMS->group_id = group_id; - CZMS->instance_id = instance_id; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneMoveInstanceByRaidID(int raid_id, uint16 instance_id) { - auto pack = new ServerPacket(ServerOP_CZMoveInstanceRaid, sizeof(CZMoveInstanceRaid_Struct)); - CZMoveInstanceRaid_Struct* CZMS = (CZMoveInstanceRaid_Struct*) pack->pBuffer; - CZMS->raid_id = raid_id; - CZMS->instance_id = instance_id; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneMoveInstanceByGuildID(int guild_id, uint16 instance_id) { - auto pack = new ServerPacket(ServerOP_CZMoveInstanceGuild, sizeof(CZMoveInstanceGuild_Struct)); - CZMoveInstanceGuild_Struct* CZMS = (CZMoveInstanceGuild_Struct*) pack->pBuffer; - CZMS->guild_id = guild_id; - CZMS->instance_id = instance_id; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneRemoveSpellByCharID(int character_id, uint32 spell_id) { - auto pack = new ServerPacket(ServerOP_CZRemoveSpellPlayer, sizeof(CZRemoveSpellPlayer_Struct)); - CZRemoveSpellPlayer_Struct* CZCS = (CZRemoveSpellPlayer_Struct*) pack->pBuffer; - CZCS->character_id = character_id; - CZCS->spell_id = spell_id; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneRemoveSpellByGroupID(int group_id, uint32 spell_id) { - auto pack = new ServerPacket(ServerOP_CZRemoveSpellGroup, sizeof(CZRemoveSpellGroup_Struct)); - CZRemoveSpellGroup_Struct* CZCS = (CZRemoveSpellGroup_Struct*) pack->pBuffer; - CZCS->group_id = group_id; - CZCS->spell_id = spell_id; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneRemoveSpellByRaidID(int raid_id, uint32 spell_id) { - auto pack = new ServerPacket(ServerOP_CZRemoveSpellRaid, sizeof(CZRemoveSpellRaid_Struct)); - CZRemoveSpellRaid_Struct* CZCS = (CZRemoveSpellRaid_Struct*) pack->pBuffer; - CZCS->raid_id = raid_id; - CZCS->spell_id = spell_id; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneRemoveSpellByGuildID(int guild_id, uint32 spell_id) { - auto pack = new ServerPacket(ServerOP_CZRemoveSpellGuild, sizeof(CZRemoveSpellGuild_Struct)); - CZRemoveSpellGuild_Struct* CZCS = (CZRemoveSpellGuild_Struct*) pack->pBuffer; - CZCS->guild_id = guild_id; - CZCS->spell_id = spell_id; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneRemoveTaskByCharID(int character_id, uint32 task_id) { - auto pack = new ServerPacket(ServerOP_CZTaskRemovePlayer, sizeof(CZTaskRemovePlayer_Struct)); - CZTaskRemovePlayer_Struct* CZCS = (CZTaskRemovePlayer_Struct*) pack->pBuffer; - CZCS->character_id = character_id; - CZCS->task_id = task_id; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneRemoveTaskByGroupID(int group_id, uint32 task_id) { - auto pack = new ServerPacket(ServerOP_CZTaskRemoveGroup, sizeof(CZTaskRemoveGroup_Struct)); - CZTaskRemoveGroup_Struct* CZCS = (CZTaskRemoveGroup_Struct*) pack->pBuffer; - CZCS->group_id = group_id; - CZCS->task_id = task_id; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneRemoveTaskByRaidID(int raid_id, uint32 task_id) { - auto pack = new ServerPacket(ServerOP_CZTaskRemoveRaid, sizeof(CZTaskRemoveRaid_Struct)); - CZTaskRemoveRaid_Struct* CZCS = (CZTaskRemoveRaid_Struct*) pack->pBuffer; - CZCS->raid_id = raid_id; - CZCS->task_id = task_id; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneRemoveTaskByGuildID(int guild_id, uint32 task_id) { - auto pack = new ServerPacket(ServerOP_CZTaskRemoveGuild, sizeof(CZTaskRemoveGuild_Struct)); - CZTaskRemoveGuild_Struct* CZCS = (CZTaskRemoveGuild_Struct*) pack->pBuffer; - CZCS->guild_id = guild_id; - CZCS->task_id = task_id; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneResetActivityByCharID(int character_id, uint32 task_id, int activity_id) { - QuestManagerCurrentQuestVars(); - if (initiator && owner) { - auto pack = new ServerPacket(ServerOP_CZTaskActivityResetPlayer, sizeof(CZResetActivityPlayer_Struct)); - CZResetActivityPlayer_Struct* CZCA = (CZResetActivityPlayer_Struct*) pack->pBuffer; - CZCA->character_id = character_id; - CZCA->task_id = task_id; - CZCA->activity_id = activity_id; - worldserver.SendPacket(pack); - safe_delete(pack); - } -} - -void QuestManager::CrossZoneResetActivityByGroupID(int group_id, uint32 task_id, int activity_id) { - QuestManagerCurrentQuestVars(); - if (initiator && owner) { - auto pack = new ServerPacket(ServerOP_CZTaskActivityResetGroup, sizeof(CZResetActivityGroup_Struct)); - CZResetActivityGroup_Struct* CZCA = (CZResetActivityGroup_Struct*) pack->pBuffer; - CZCA->group_id = group_id; - CZCA->task_id = task_id; - CZCA->activity_id = activity_id; - worldserver.SendPacket(pack); - safe_delete(pack); - } -} - -void QuestManager::CrossZoneResetActivityByRaidID(int raid_id, uint32 task_id, int activity_id) { - QuestManagerCurrentQuestVars(); - if (initiator && owner) { - auto pack = new ServerPacket(ServerOP_CZTaskActivityResetRaid, sizeof(CZResetActivityRaid_Struct)); - CZResetActivityRaid_Struct* CZCA = (CZResetActivityRaid_Struct*) pack->pBuffer; - CZCA->raid_id = raid_id; - CZCA->task_id = task_id; - CZCA->activity_id = activity_id; - worldserver.SendPacket(pack); - safe_delete(pack); - } -} - -void QuestManager::CrossZoneResetActivityByGuildID(int guild_id, uint32 task_id, int activity_id) { - QuestManagerCurrentQuestVars(); - if (initiator && owner) { - auto pack = new ServerPacket(ServerOP_CZTaskActivityResetGuild, sizeof(CZResetActivityGuild_Struct)); - CZResetActivityGuild_Struct* CZCA = (CZResetActivityGuild_Struct*) pack->pBuffer; - CZCA->guild_id = guild_id; - CZCA->task_id = task_id; - CZCA->activity_id = activity_id; - worldserver.SendPacket(pack); - safe_delete(pack); - } -} - -void QuestManager::CrossZoneSignalNPCByNPCTypeID(uint32 npctype_id, uint32 signal) { - auto pack = new ServerPacket(ServerOP_CZSignalNPC, sizeof(CZNPCSignal_Struct)); - CZNPCSignal_Struct* CZSN = (CZNPCSignal_Struct*) pack->pBuffer; - CZSN->npctype_id = npctype_id; - CZSN->signal = signal; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneSignalPlayerByCharID(int character_id, uint32 signal) { - auto pack = new ServerPacket(ServerOP_CZSignalClient, sizeof(CZClientSignal_Struct)); - CZClientSignal_Struct* CZSC = (CZClientSignal_Struct*) pack->pBuffer; - CZSC->character_id = character_id; - CZSC->signal = signal; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneSetEntityVariableByClientName(const char *character_name, const char *variable_name, const char *variable_value) { - uint32 message_len = strlen(variable_name) + 1; - uint32 message_len2 = strlen(variable_value) + 1; - uint32 message_len3 = strlen(character_name) + 1; - auto pack = new ServerPacket(ServerOP_CZSetEntityVariableByClientName, sizeof(CZSetEntVarByClientName_Struct) + message_len + message_len2 + message_len3); - CZSetEntVarByClientName_Struct* CZ = (CZSetEntVarByClientName_Struct*) pack->pBuffer; - strn0cpy(CZ->character_name, character_name, 64); - strn0cpy(CZ->variable_name, variable_name, 256); - strn0cpy(CZ->variable_value, variable_value, 256); - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneSetEntityVariableByGroupID(int group_id, const char *variable_name, const char *variable_value) { - uint32 message_len = strlen(variable_name) + 1; - uint32 message_len2 = strlen(variable_value) + 1; - auto pack = new ServerPacket(ServerOP_CZSetEntityVariableByGroupID, sizeof(CZSetEntVarByGroupID_Struct) + message_len + message_len2); - CZSetEntVarByGroupID_Struct* CZ = (CZSetEntVarByGroupID_Struct*) pack->pBuffer; - CZ->group_id = group_id; - strn0cpy(CZ->variable_name, variable_name, 256); - strn0cpy(CZ->variable_value, variable_value, 256); - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneSetEntityVariableByRaidID(int raid_id, const char *variable_name, const char *variable_value) { - uint32 message_len = strlen(variable_name) + 1; - uint32 message_len2 = strlen(variable_value) + 1; - auto pack = new ServerPacket(ServerOP_CZSetEntityVariableByRaidID, sizeof(CZSetEntVarByRaidID_Struct) + message_len + message_len2); - CZSetEntVarByRaidID_Struct* CZ = (CZSetEntVarByRaidID_Struct*) pack->pBuffer; - CZ->raid_id = raid_id; - strn0cpy(CZ->variable_name, variable_name, 256); - strn0cpy(CZ->variable_value, variable_value, 256); - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneSetEntityVariableByGuildID(int guild_id, const char *variable_name, const char *variable_value) { - uint32 message_len = strlen(variable_name) + 1; - uint32 message_len2 = strlen(variable_value) + 1; - auto pack = new ServerPacket(ServerOP_CZSetEntityVariableByGuildID, sizeof(CZSetEntVarByGuildID_Struct) + message_len + message_len2); - CZSetEntVarByGuildID_Struct* CZ = (CZSetEntVarByGuildID_Struct*) pack->pBuffer; - CZ->guild_id = guild_id; - strn0cpy(CZ->variable_name, variable_name, 256); - strn0cpy(CZ->variable_value, variable_value, 256); - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneSetEntityVariableByNPCTypeID(uint32 npctype_id, const char *variable_name, const char *variable_value) { - uint32 message_len = strlen(variable_name) + 1; - uint32 message_len2 = strlen(variable_value) + 1; - auto pack = new ServerPacket(ServerOP_CZSetEntityVariableByNPCTypeID, sizeof(CZSetEntVarByNPCTypeID_Struct) + message_len + message_len2); - CZSetEntVarByNPCTypeID_Struct* CZSNBYNID = (CZSetEntVarByNPCTypeID_Struct*) pack->pBuffer; - CZSNBYNID->npctype_id = npctype_id; - strn0cpy(CZSNBYNID->variable_name, variable_name, 256); - strn0cpy(CZSNBYNID->variable_value, variable_value, 256); - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneSignalPlayerByGroupID(int group_id, uint32 signal) { - auto pack = new ServerPacket(ServerOP_CZSignalGroup, sizeof(CZGroupSignal_Struct)); - CZGroupSignal_Struct* CZGS = (CZGroupSignal_Struct*) pack->pBuffer; - CZGS->group_id = group_id; - CZGS->signal = signal; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneSignalPlayerByRaidID(int raid_id, uint32 signal) { - auto pack = new ServerPacket(ServerOP_CZSignalRaid, sizeof(CZRaidSignal_Struct)); - CZRaidSignal_Struct* CZRS = (CZRaidSignal_Struct*) pack->pBuffer; - CZRS->raid_id = raid_id; - CZRS->signal = signal; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneSignalPlayerByGuildID(int guild_id, uint32 signal) { - auto pack = new ServerPacket(ServerOP_CZSignalGuild, sizeof(CZGuildSignal_Struct)); - CZGuildSignal_Struct* CZGS = (CZGuildSignal_Struct*) pack->pBuffer; - CZGS->guild_id = guild_id; - CZGS->signal = signal; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneSignalPlayerByName(const char *character_name, uint32 signal) { - uint32 message_len = strlen(character_name) + 1; - auto pack = new ServerPacket(ServerOP_CZSignalClientByName, sizeof(CZClientSignalByName_Struct) + message_len); - CZClientSignalByName_Struct* CZSC = (CZClientSignalByName_Struct*) pack->pBuffer; - strn0cpy(CZSC->character_name, character_name, 64); - CZSC->signal = signal; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::CrossZoneUpdateActivityByCharID(int character_id, uint32 task_id, int activity_id, int activity_count) { - QuestManagerCurrentQuestVars(); - if (initiator && owner) { - auto pack = new ServerPacket(ServerOP_CZTaskActivityUpdatePlayer, sizeof(CZTaskActivityUpdatePlayer_Struct)); - CZTaskActivityUpdatePlayer_Struct* CZCA = (CZTaskActivityUpdatePlayer_Struct*) pack->pBuffer; - CZCA->character_id = character_id; - CZCA->task_id = task_id; - CZCA->activity_id = activity_id; - CZCA->activity_count = activity_count; - worldserver.SendPacket(pack); - safe_delete(pack); - } -} - -void QuestManager::CrossZoneUpdateActivityByGroupID(int group_id, uint32 task_id, int activity_id, int activity_count) { - QuestManagerCurrentQuestVars(); - if (initiator && owner) { - auto pack = new ServerPacket(ServerOP_CZTaskActivityUpdateGroup, sizeof(CZTaskActivityUpdateGroup_Struct)); - CZTaskActivityUpdateGroup_Struct* CZCA = (CZTaskActivityUpdateGroup_Struct*) pack->pBuffer; - CZCA->group_id = group_id; - CZCA->task_id = task_id; - CZCA->activity_id = activity_id; - CZCA->activity_count = activity_count; - worldserver.SendPacket(pack); - safe_delete(pack); - } -} - -void QuestManager::CrossZoneUpdateActivityByRaidID(int raid_id, uint32 task_id, int activity_id, int activity_count) { - QuestManagerCurrentQuestVars(); - if (initiator && owner) { - auto pack = new ServerPacket(ServerOP_CZTaskActivityUpdateRaid, sizeof(CZTaskActivityUpdateRaid_Struct)); - CZTaskActivityUpdateRaid_Struct* CZCA = (CZTaskActivityUpdateRaid_Struct*) pack->pBuffer; - CZCA->raid_id = raid_id; - CZCA->task_id = task_id; - CZCA->activity_id = activity_id; - CZCA->activity_count = activity_count; - worldserver.SendPacket(pack); - safe_delete(pack); - } -} - -void QuestManager::CrossZoneUpdateActivityByGuildID(int guild_id, uint32 task_id, int activity_id, int activity_count) { - QuestManagerCurrentQuestVars(); - if (initiator && owner) { - auto pack = new ServerPacket(ServerOP_CZTaskActivityUpdateGuild, sizeof(CZTaskActivityUpdateGuild_Struct)); - CZTaskActivityUpdateGuild_Struct* CZCA = (CZTaskActivityUpdateGuild_Struct*) pack->pBuffer; - CZCA->guild_id = guild_id; - CZCA->task_id = task_id; - CZCA->activity_id = activity_id; - CZCA->activity_count = activity_count; - worldserver.SendPacket(pack); - safe_delete(pack); - } -} - -void QuestManager::WorldWideAssignTask(uint32 task_id, bool enforce_level_requirement, uint8 min_status, uint8 max_status) { - QuestManagerCurrentQuestVars(); - if (initiator && owner) { - auto pack = new ServerPacket(ServerOP_WWAssignTask, sizeof(WWAssignTask_Struct)); - WWAssignTask_Struct* WWTA = (WWAssignTask_Struct*) pack->pBuffer; - WWTA->npc_entity_id = owner->GetID(); - WWTA->task_id = task_id; - WWTA->enforce_level_requirement = enforce_level_requirement; - WWTA->min_status = min_status; - WWTA->max_status = max_status; - worldserver.SendPacket(pack); - safe_delete(pack); - } -} - -void QuestManager::WorldWideCastSpell(uint32 spell_id, uint8 min_status, uint8 max_status) { - auto pack = new ServerPacket(ServerOP_WWCastSpell, sizeof(WWCastSpell_Struct)); - WWCastSpell_Struct* WWCS = (WWCastSpell_Struct*) pack->pBuffer; - WWCS->spell_id = spell_id; - WWCS->min_status = min_status; - WWCS->max_status = max_status; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::WorldWideDisableTask(uint32 task_id, uint8 min_status, uint8 max_status) { - auto pack = new ServerPacket(ServerOP_WWDisableTask, sizeof(WWDisableTask_Struct)); - WWDisableTask_Struct* WWDT = (WWDisableTask_Struct*) pack->pBuffer; - WWDT->task_id = task_id; - WWDT->min_status = min_status; - WWDT->max_status = max_status; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::WorldWideEnableTask(uint32 task_id, uint8 min_status, uint8 max_status) { - auto pack = new ServerPacket(ServerOP_WWEnableTask, sizeof(WWEnableTask_Struct)); - WWEnableTask_Struct* WWET = (WWEnableTask_Struct*) pack->pBuffer; - WWET->task_id = task_id; - WWET->min_status = min_status; - WWET->max_status = max_status; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::WorldWideFailTask(uint32 task_id, uint8 min_status, uint8 max_status) { - auto pack = new ServerPacket(ServerOP_WWFailTask, sizeof(WWFailTask_Struct)); - WWFailTask_Struct* WWFT = (WWFailTask_Struct*) pack->pBuffer; - WWFT->task_id = task_id; - WWFT->min_status = min_status; - WWFT->max_status = max_status; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::WorldWideMarquee(uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char *message, uint8 min_status, uint8 max_status) { - uint32 message_len = strlen(message) + 1; - auto pack = new ServerPacket(ServerOP_WWMarquee, sizeof(WWMarquee_Struct) + message_len); - WWMarquee_Struct* WWMS = (WWMarquee_Struct*) pack->pBuffer; - WWMS->type = type; - WWMS->priority = priority; - WWMS->fade_in = fade_in; - WWMS->fade_out = fade_out; - WWMS->duration = duration; - strn0cpy(WWMS->message, message, 512); - WWMS->min_status = min_status; - WWMS->max_status = max_status; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::WorldWideMessage(uint32 type, const char *message, uint8 min_status, uint8 max_status) { - uint32 message_len = strlen(message) + 1; - auto pack = new ServerPacket(ServerOP_WWMessage, sizeof(WWMessage_Struct) + message_len); - WWMessage_Struct* WWMS = (WWMessage_Struct*) pack->pBuffer; - WWMS->type = type; - strn0cpy(WWMS->message, message, 512); - WWMS->min_status = min_status; - WWMS->max_status = max_status; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::WorldWideMove(const char *zone_short_name, uint8 min_status, uint8 max_status) { - uint32 message_len = strlen(zone_short_name) + 1; - auto pack = new ServerPacket(ServerOP_WWMove, sizeof(WWMove_Struct) + message_len); - WWMove_Struct* WWMS = (WWMove_Struct*) pack->pBuffer;; - strn0cpy(WWMS->zone_short_name, zone_short_name, 32); - WWMS->min_status = min_status; - WWMS->max_status = max_status; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::WorldWideMoveInstance(uint16 instance_id, uint8 min_status, uint8 max_status) { - auto pack = new ServerPacket(ServerOP_WWMoveInstance, sizeof(WWMoveInstance_Struct)); - WWMoveInstance_Struct* WWMS = (WWMoveInstance_Struct*) pack->pBuffer; - WWMS->instance_id = instance_id; - WWMS->min_status = min_status; - WWMS->max_status = max_status; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::WorldWideRemoveSpell(uint32 spell_id, uint8 min_status, uint8 max_status) { - auto pack = new ServerPacket(ServerOP_WWRemoveSpell, sizeof(WWRemoveSpell_Struct)); - WWRemoveSpell_Struct* WWRS = (WWRemoveSpell_Struct*) pack->pBuffer; - WWRS->spell_id = spell_id; - WWRS->min_status = min_status; - WWRS->max_status = max_status; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::WorldWideRemoveTask(uint32 task_id, uint8 min_status, uint8 max_status) { - auto pack = new ServerPacket(ServerOP_WWRemoveTask, sizeof(WWRemoveTask_Struct)); - WWRemoveTask_Struct* WWRT = (WWRemoveTask_Struct*) pack->pBuffer; - WWRT->task_id = task_id; - WWRT->min_status = min_status; - WWRT->max_status = max_status; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::WorldWideResetActivity(uint32 task_id, int activity_id, uint8 min_status, uint8 max_status) { - auto pack = new ServerPacket(ServerOP_WWResetActivity, sizeof(WWResetActivity_Struct)); - WWResetActivity_Struct* WWRA = (WWResetActivity_Struct*) pack->pBuffer; - WWRA->task_id = task_id; - WWRA->activity_id = activity_id; - WWRA->min_status = min_status; - WWRA->max_status = max_status; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::WorldWideSetEntityVariableClient(const char *variable_name, const char *variable_value, uint8 min_status, uint8 max_status) { - uint32 message_len = strlen(variable_name) + 1; - uint32 message_len2 = strlen(variable_value) + 1; - auto pack = new ServerPacket(ServerOP_WWSetEntityVariableClient, sizeof(WWSetEntVarClient_Struct) + message_len + message_len2); - WWSetEntVarClient_Struct* WWSC = (WWSetEntVarClient_Struct*) pack->pBuffer; - strn0cpy(WWSC->variable_name, variable_name, 256); - strn0cpy(WWSC->variable_value, variable_value, 256); - WWSC->min_status = min_status; - WWSC->max_status = max_status; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::WorldWideSetEntityVariableNPC(const char *variable_name, const char *variable_value) { - uint32 message_len = strlen(variable_name) + 1; - uint32 message_len2 = strlen(variable_value) + 1; - auto pack = new ServerPacket(ServerOP_WWSetEntityVariableNPC, sizeof(WWSetEntVarNPC_Struct) + message_len + message_len2); - WWSetEntVarNPC_Struct* WWSN = (WWSetEntVarNPC_Struct*) pack->pBuffer; - strn0cpy(WWSN->variable_name, variable_name, 256); - strn0cpy(WWSN->variable_value, variable_value, 256); - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::WorldWideSignalClient(uint32 signal, uint8 min_status, uint8 max_status) { - auto pack = new ServerPacket(ServerOP_WWSignalClient, sizeof(WWSignalClient_Struct)); - WWSignalClient_Struct* WWSC = (WWSignalClient_Struct*) pack->pBuffer; - WWSC->signal = signal; - WWSC->min_status = min_status; - WWSC->max_status = max_status; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::WorldWideSignalNPC(uint32 signal) { - auto pack = new ServerPacket(ServerOP_WWSignalNPC, sizeof(WWSignalNPC_Struct)); - WWSignalNPC_Struct* WWSN = (WWSignalNPC_Struct*) pack->pBuffer; - WWSN->signal = signal; - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void QuestManager::WorldWideUpdateActivity(uint32 task_id, int activity_id, int activity_count, uint8 min_status, uint8 max_status) { - auto pack = new ServerPacket(ServerOP_WWUpdateActivity, sizeof(WWUpdateActivity_Struct)); - WWUpdateActivity_Struct* WWUA = (WWUpdateActivity_Struct*) pack->pBuffer; - WWUA->task_id = task_id; - WWUA->activity_id = activity_id; - WWUA->activity_count = activity_count; - WWUA->min_status = min_status; - WWUA->max_status = max_status; - worldserver.SendPacket(pack); - safe_delete(pack); +std::string QuestManager::GetZoneShortName(uint32 zone_id) { + return ZoneName(zone_id); } bool QuestManager::EnableRecipe(uint32 recipe_id) @@ -4164,6 +3319,15 @@ EQ::ItemInstance *QuestManager::GetQuestItem() const { return nullptr; } +const SPDat_Spell_Struct *QuestManager::GetQuestSpell() { + if(!quests_running_.empty()) { + running_quest e = quests_running_.top(); + return e.questspell; + } + + return nullptr; +} + std::string QuestManager::GetEncounter() const { if(!quests_running_.empty()) { running_quest e = quests_running_.top(); @@ -4196,12 +3360,11 @@ void QuestManager::UpdateZoneHeader(std::string type, std::string value) { for (int i = 0; i < 4; i++) { zone->newzone_data.fog_maxclip[i] = atof(value.c_str()); } - } - else if (strcasecmp(type.c_str(), "gravity") == 0) + } else if (strcasecmp(type.c_str(), "gravity") == 0) { zone->newzone_data.gravity = atof(value.c_str()); - else if (strcasecmp(type.c_str(), "time_type") == 0) + } else if (strcasecmp(type.c_str(), "time_type") == 0) { zone->newzone_data.time_type = atoi(value.c_str()); - else if (strcasecmp(type.c_str(), "rain_chance") == 0) { + } else if (strcasecmp(type.c_str(), "rain_chance") == 0) { for (int i = 0; i < 4; i++) { zone->newzone_data.rain_chance[i] = atoi(value.c_str()); } @@ -4217,27 +3380,31 @@ void QuestManager::UpdateZoneHeader(std::string type, std::string value) { for (int i = 0; i < 4; i++) { zone->newzone_data.snow_duration[i] = atoi(value.c_str()); } - } - else if (strcasecmp(type.c_str(), "sky") == 0) + } else if (strcasecmp(type.c_str(), "sky") == 0) { zone->newzone_data.sky = atoi(value.c_str()); - else if (strcasecmp(type.c_str(), "safe_x") == 0) + } else if (strcasecmp(type.c_str(), "safe_x") == 0) { zone->newzone_data.safe_x = atof(value.c_str()); - else if (strcasecmp(type.c_str(), "safe_y") == 0) + } else if (strcasecmp(type.c_str(), "safe_y") == 0) { zone->newzone_data.safe_y = atof(value.c_str()); - else if (strcasecmp(type.c_str(), "safe_z") == 0) + } else if (strcasecmp(type.c_str(), "safe_z") == 0) { zone->newzone_data.safe_z = atof(value.c_str()); - else if (strcasecmp(type.c_str(), "max_z") == 0) + } else if (strcasecmp(type.c_str(), "max_z") == 0) { zone->newzone_data.max_z = atof(value.c_str()); - else if (strcasecmp(type.c_str(), "underworld") == 0) + } else if (strcasecmp(type.c_str(), "underworld") == 0) { zone->newzone_data.underworld = atof(value.c_str()); - else if (strcasecmp(type.c_str(), "minclip") == 0) + } else if (strcasecmp(type.c_str(), "minclip") == 0) { zone->newzone_data.minclip = atof(value.c_str()); - else if (strcasecmp(type.c_str(), "maxclip") == 0) + } else if (strcasecmp(type.c_str(), "maxclip") == 0) { zone->newzone_data.maxclip = atof(value.c_str()); - else if (strcasecmp(type.c_str(), "fog_density") == 0) + } else if (strcasecmp(type.c_str(), "fog_density") == 0) { zone->newzone_data.fog_density = atof(value.c_str()); - else if (strcasecmp(type.c_str(), "suspendbuffs") == 0) + } else if (strcasecmp(type.c_str(), "suspendbuffs") == 0) { zone->newzone_data.SuspendBuffs = atoi(value.c_str()); + } else if (strcasecmp(type.c_str(), "lavadamage") == 0) { + zone->newzone_data.LavaDamage = atoi(value.c_str()); + } else if (strcasecmp(type.c_str(), "minlavadamage") == 0) { + zone->newzone_data.MinLavaDamage = atoi(value.c_str()); + } auto outapp = new EQApplicationPacket(OP_NewZone, sizeof(NewZone_Struct)); memcpy(outapp->pBuffer, &zone->newzone_data, outapp->size); @@ -4253,30 +3420,290 @@ EQ::ItemInstance *QuestManager::CreateItem(uint32 item_id, int16 charges, uint32 } std::string QuestManager::secondstotime(int duration) { - int timer_length = duration; - int hours = int(timer_length / 3600); - timer_length %= 3600; - int minutes = int(timer_length / 60); - timer_length %= 60; - int seconds = timer_length; - std::string time_string = "Unknown"; - std::string hour_string = (hours == 1 ? "Hour" : "Hours"); - std::string minute_string = (minutes == 1 ? "Minute" : "Minutes"); - std::string second_string = (seconds == 1 ? "Second" : "Seconds"); - if (hours > 0 && minutes > 0 && seconds > 0) { - time_string = fmt::format("{} {}, {} {}, and {} {}", hours, hour_string, minutes, minute_string, seconds, second_string); - } else if (hours > 0 && minutes > 0 && seconds == 0) { - time_string = fmt::format("{} {} and {} {}", hours, hour_string, minutes, minute_string); - } else if (hours > 0 && minutes == 0 && seconds > 0) { - time_string = fmt::format("{} {} and {} {}", hours, hour_string, seconds, second_string); - } else if (hours > 0 && minutes == 0 && seconds == 0) { - time_string = fmt::format("{} {}", hours, hour_string); - } else if (hours == 0 && minutes > 0 && seconds > 0) { - time_string = fmt::format("{} {} and {} {}", minutes, minute_string, seconds, second_string); - } else if (hours == 0 && minutes > 0 && seconds == 0) { - time_string = fmt::format("{} {}", minutes, minute_string); - } else if (hours == 0 && minutes == 0 && seconds > 0) { - time_string = fmt::format("{} {}", seconds, second_string); + return ConvertSecondsToTime(duration); +} + +std::string QuestManager::gethexcolorcode(std::string color_name) { + + for (auto color : html_colors) { + if (!strcasecmp(color.first.c_str(), color_name.c_str())) { + return color.second; + } } - return time_string; -} \ No newline at end of file + + return std::string(); +} + +double QuestManager::GetAAEXPModifierByCharID(uint32 character_id, uint32 zone_id) const { + return database.GetAAEXPModifier(character_id, zone_id); +} + +double QuestManager::GetEXPModifierByCharID(uint32 character_id, uint32 zone_id) const { + return database.GetEXPModifier(character_id, zone_id); +} + +void QuestManager::SetAAEXPModifierByCharID(uint32 character_id, uint32 zone_id, double aa_modifier) { + database.SetAAEXPModifier(character_id, zone_id, aa_modifier); +} + +void QuestManager::SetEXPModifierByCharID(uint32 character_id, uint32 zone_id, double exp_modifier) { + database.SetEXPModifier(character_id, zone_id, exp_modifier); +} + +std::string QuestManager::getgendername(uint32 gender_id) { + std::string gender_name = GetGenderName(gender_id); + return gender_name; +} + +std::string QuestManager::getdeityname(uint32 deity_id) { + return EQ::deity::DeityName(static_cast(deity_id)); +} + +std::string QuestManager::getinventoryslotname(int16 slot_id) { + return EQ::invslot::GetInvPossessionsSlotName(slot_id); +} + +int QuestManager::getitemstat(uint32 item_id, std::string stat_identifier) { + QuestManagerCurrentQuestVars(); + return EQ::InventoryProfile::GetItemStatValue(item_id, stat_identifier.c_str()); +} + +int QuestManager::getspellstat(uint32 spell_id, std::string stat_identifier, uint8 slot) { + QuestManagerCurrentQuestVars(); + return GetSpellStatValue(spell_id, stat_identifier.c_str(), slot); +} + +void QuestManager::CrossZoneDialogueWindow(uint8 update_type, int update_identifier, const char* message, const char* client_name) { + auto pack = new ServerPacket(ServerOP_CZDialogueWindow, sizeof(CZDialogueWindow_Struct)); + CZDialogueWindow_Struct* CZDW = (CZDialogueWindow_Struct*)pack->pBuffer; + CZDW->update_type = update_type; + CZDW->update_identifier = update_identifier; + strn0cpy(CZDW->message, message, 4096); + strn0cpy(CZDW->client_name, client_name, 64); + worldserver.SendPacket(pack); + safe_delete(pack); +} + +void QuestManager::CrossZoneLDoNUpdate(uint8 update_type, uint8 update_subtype, int update_identifier, uint32 theme_id, int points, const char* client_name) { + auto pack = new ServerPacket(ServerOP_CZLDoNUpdate, sizeof(CZLDoNUpdate_Struct)); + CZLDoNUpdate_Struct* CZLU = (CZLDoNUpdate_Struct*)pack->pBuffer; + CZLU->update_type = update_type; + CZLU->update_subtype = update_subtype; + CZLU->update_identifier = update_identifier; + CZLU->theme_id = theme_id; + CZLU->points = points; + strn0cpy(CZLU->client_name, client_name, 64); + worldserver.SendPacket(pack); + safe_delete(pack); +} + +void QuestManager::CrossZoneMarquee(uint8 update_type, int update_identifier, uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char* message, const char* client_name) { + auto pack = new ServerPacket(ServerOP_CZMarquee, sizeof(CZMarquee_Struct)); + CZMarquee_Struct* CZM = (CZMarquee_Struct*)pack->pBuffer; + CZM->update_type = update_type; + CZM->update_identifier = update_identifier; + CZM->type = type; + CZM->priority = priority; + CZM->fade_in = fade_in; + CZM->fade_out = fade_out; + CZM->duration = duration; + strn0cpy(CZM->message, message, 512); + strn0cpy(CZM->client_name, client_name, 64); + worldserver.SendPacket(pack); + safe_delete(pack); +} + +void QuestManager::CrossZoneMessage(uint8 update_type, int update_identifier, uint32 type, const char* message, const char* client_name) { + auto pack = new ServerPacket(ServerOP_CZMessage, sizeof(CZMarquee_Struct)); + CZMessage_Struct* CZM = (CZMessage_Struct*)pack->pBuffer; + CZM->update_type = update_type; + CZM->update_identifier = update_identifier; + CZM->type = type; + strn0cpy(CZM->message, message, 512); + strn0cpy(CZM->client_name, client_name, 64); + worldserver.SendPacket(pack); + safe_delete(pack); +} + +void QuestManager::CrossZoneMove(uint8 update_type, uint8 update_subtype, int update_identifier, const char* zone_short_name, uint16 instance_id, const char* client_name) { + auto pack = new ServerPacket(ServerOP_CZMove, sizeof(CZMove_Struct)); + CZMove_Struct* CZM = (CZMove_Struct*)pack->pBuffer; + CZM->update_type = update_type; + CZM->update_subtype = update_subtype; + CZM->update_identifier = update_identifier; + strn0cpy(CZM->zone_short_name, zone_short_name, 32); + CZM->instance_id = instance_id; + strn0cpy(CZM->client_name, client_name, 64); + worldserver.SendPacket(pack); + safe_delete(pack); +} + +void QuestManager::CrossZoneSetEntityVariable(uint8 update_type, int update_identifier, const char* variable_name, const char* variable_value, const char* client_name) { + auto pack = new ServerPacket(ServerOP_CZSetEntityVariable, sizeof(CZSetEntityVariable_Struct)); + CZSetEntityVariable_Struct* CZM = (CZSetEntityVariable_Struct*)pack->pBuffer; + CZM->update_type = update_type; + CZM->update_identifier = update_identifier; + strn0cpy(CZM->variable_name, variable_name, 256); + strn0cpy(CZM->variable_value, variable_value, 256); + strn0cpy(CZM->client_name, client_name, 64); + worldserver.SendPacket(pack); + safe_delete(pack); +} + +void QuestManager::CrossZoneSignal(uint8 update_type, int update_identifier, uint32 signal, const char* client_name) { + auto pack = new ServerPacket(ServerOP_CZSignal, sizeof(CZSignal_Struct)); + CZSignal_Struct* CZS = (CZSignal_Struct*)pack->pBuffer; + CZS->update_type = update_type; + CZS->update_identifier = update_identifier; + CZS->signal = signal; + strn0cpy(CZS->client_name, client_name, 64); + worldserver.SendPacket(pack); + safe_delete(pack); +} + +void QuestManager::CrossZoneSpell(uint8 update_type, uint8 update_subtype, int update_identifier, uint32 spell_id, const char* client_name) { + auto pack = new ServerPacket(ServerOP_CZSpell, sizeof(CZSpell_Struct)); + CZSpell_Struct* CZS = (CZSpell_Struct*)pack->pBuffer; + CZS->update_type = update_type; + CZS->update_subtype = update_subtype; + CZS->update_identifier = update_identifier; + CZS->spell_id = spell_id; + strn0cpy(CZS->client_name, client_name, 64); + worldserver.SendPacket(pack); + safe_delete(pack); +} + +void QuestManager::CrossZoneTaskUpdate(uint8 update_type, uint8 update_subtype, int update_identifier, uint32 task_identifier, int task_subidentifier, int update_count, bool enforce_level_requirement, const char* client_name) { + auto pack = new ServerPacket(ServerOP_CZTaskUpdate, sizeof(CZTaskUpdate_Struct)); + CZTaskUpdate_Struct* CZTU = (CZTaskUpdate_Struct*)pack->pBuffer; + CZTU->update_type = update_type; + CZTU->update_subtype = update_subtype; + CZTU->update_identifier = update_identifier; + CZTU->task_identifier = task_identifier; + CZTU->task_subidentifier = task_subidentifier; + CZTU->update_count = update_count; + CZTU->enforce_level_requirement = enforce_level_requirement; + strn0cpy(CZTU->client_name, client_name, 64); + worldserver.SendPacket(pack); + safe_delete(pack); +} + +void QuestManager::WorldWideDialogueWindow(const char* message, uint8 min_status, uint8 max_status) { + auto pack = new ServerPacket(ServerOP_WWDialogueWindow, sizeof(WWDialogueWindow_Struct)); + WWDialogueWindow_Struct* WWDW = (WWDialogueWindow_Struct*)pack->pBuffer; + strn0cpy(WWDW->message, message, 4096); + WWDW->min_status = min_status; + WWDW->max_status = max_status; + worldserver.SendPacket(pack); + safe_delete(pack); +} + +void QuestManager::WorldWideLDoNUpdate(uint8 update_type, uint32 theme_id, int points, uint8 min_status, uint8 max_status) { + auto pack = new ServerPacket(ServerOP_WWLDoNUpdate, sizeof(WWLDoNUpdate_Struct)); + WWLDoNUpdate_Struct* WWLU = (WWLDoNUpdate_Struct*)pack->pBuffer; + WWLU->update_type = update_type; + WWLU->theme_id = theme_id; + WWLU->points = points; + WWLU->min_status = min_status; + WWLU->max_status = max_status; + worldserver.SendPacket(pack); + safe_delete(pack); +} + +void QuestManager::WorldWideMarquee(uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char* message, uint8 min_status, uint8 max_status) { + auto pack = new ServerPacket(ServerOP_WWMarquee, sizeof(WWMarquee_Struct)); + WWMarquee_Struct* WWM = (WWMarquee_Struct*)pack->pBuffer; + WWM->type = type; + WWM->priority = priority; + WWM->fade_in = fade_in; + WWM->fade_out = fade_out; + WWM->duration = duration; + strn0cpy(WWM->message, message, 512); + WWM->min_status = min_status; + WWM->max_status = max_status; + worldserver.SendPacket(pack); + safe_delete(pack); +} + +void QuestManager::WorldWideMessage(uint32 type, const char* message, uint8 min_status, uint8 max_status) { + auto pack = new ServerPacket(ServerOP_WWMessage, sizeof(WWMarquee_Struct)); + WWMessage_Struct* WWM = (WWMessage_Struct*)pack->pBuffer; + WWM->type = type; + strn0cpy(WWM->message, message, 512); + WWM->min_status = min_status; + WWM->max_status = max_status; + worldserver.SendPacket(pack); + safe_delete(pack); +} + +void QuestManager::WorldWideMove(uint8 update_type, const char* zone_short_name, uint16 instance_id, uint8 min_status, uint8 max_status) { + auto pack = new ServerPacket(ServerOP_WWMove, sizeof(WWMove_Struct)); + WWMove_Struct* WWM = (WWMove_Struct*)pack->pBuffer; + WWM->update_type = update_type; + strn0cpy(WWM->zone_short_name, zone_short_name, 32); + WWM->instance_id = instance_id; + WWM->min_status = min_status; + WWM->max_status = max_status; + worldserver.SendPacket(pack); + safe_delete(pack); +} + +void QuestManager::WorldWideSetEntityVariable(uint8 update_type, const char* variable_name, const char* variable_value, uint8 min_status, uint8 max_status) { + auto pack = new ServerPacket(ServerOP_WWSetEntityVariable, sizeof(WWSetEntityVariable_Struct)); + WWSetEntityVariable_Struct* WWSEV = (WWSetEntityVariable_Struct*)pack->pBuffer; + WWSEV->update_type = update_type; + strn0cpy(WWSEV->variable_name, variable_name, 256); + strn0cpy(WWSEV->variable_value, variable_value, 256); + WWSEV->min_status = min_status; + WWSEV->max_status = max_status; + worldserver.SendPacket(pack); + safe_delete(pack); +} + +void QuestManager::WorldWideSignal(uint8 update_type, uint32 signal, uint8 min_status, uint8 max_status) { + auto pack = new ServerPacket(ServerOP_WWSignal, sizeof(WWSignal_Struct)); + WWSignal_Struct* WWS = (WWSignal_Struct*)pack->pBuffer; + WWS->update_type = update_type; + WWS->signal = signal; + WWS->min_status = min_status; + WWS->max_status = max_status; + worldserver.SendPacket(pack); + safe_delete(pack); +} + +void QuestManager::WorldWideSpell(uint8 update_type, uint32 spell_id, uint8 min_status, uint8 max_status) { + auto pack = new ServerPacket(ServerOP_WWSpell, sizeof(WWSpell_Struct)); + WWSpell_Struct* WWS = (WWSpell_Struct*)pack->pBuffer; + WWS->update_type = update_type; + WWS->spell_id = spell_id; + WWS->min_status = min_status; + WWS->max_status = max_status; + worldserver.SendPacket(pack); + safe_delete(pack); +} + +void QuestManager::WorldWideTaskUpdate(uint8 update_type, uint32 task_identifier, int task_subidentifier, int update_count, bool enforce_level_requirement, uint8 min_status, uint8 max_status) { + auto pack = new ServerPacket(ServerOP_WWTaskUpdate, sizeof(WWTaskUpdate_Struct)); + WWTaskUpdate_Struct* WWTU = (WWTaskUpdate_Struct*)pack->pBuffer; + WWTU->update_type = update_type; + WWTU->task_identifier = task_identifier; + WWTU->task_subidentifier = task_subidentifier; + WWTU->update_count = update_count; + WWTU->enforce_level_requirement = enforce_level_requirement; + WWTU->min_status = min_status; + WWTU->max_status = max_status; + worldserver.SendPacket(pack); + safe_delete(pack); +} + +const SPDat_Spell_Struct* QuestManager::getspell(uint32 spell_id) { + if (spells[spell_id].id) { + return &spells[spell_id]; + } + return nullptr; +} + +std::string QuestManager::getenvironmentaldamagename(uint8 damage_type) { + std::string environmental_damage_name = EQ::constants::GetEnvironmentalDamageName(damage_type); + return environmental_damage_name; +} diff --git a/zone/questmgr.h b/zone/questmgr.h index b5d2adfe2..f859a012d 100644 --- a/zone/questmgr.h +++ b/zone/questmgr.h @@ -38,6 +38,7 @@ class QuestManager { Mob *owner; Client *initiator; EQ::ItemInstance* questitem; + const SPDat_Spell_Struct* questspell; bool depop_npc; std::string encounter; }; @@ -51,7 +52,7 @@ public: QuestManager(); virtual ~QuestManager(); - void StartQuest(Mob *_owner, Client *_initiator = nullptr, EQ::ItemInstance* _questitem = nullptr, std::string encounter = ""); + void StartQuest(Mob *_owner, Client *_initiator = nullptr, EQ::ItemInstance* _questitem = nullptr, const SPDat_Spell_Struct* _questspell = nullptr, std::string encounter = ""); void EndQuest(); bool QuestsRunning() { return !quests_running_.empty(); } @@ -106,7 +107,7 @@ public: void settarget(const char *type, int target_id); void follow(int entity_id, int distance); void sfollow(); - void changedeity(int diety_id); + void changedeity(int deity_id); void exp(int amt); void level(int newlevel); void traindisc(int discipline_tome_item_id); @@ -114,9 +115,15 @@ public: std::string getracename(uint16 race_id); std::string getspellname(uint32 spell_id); std::string getskillname(int skill_id); + std::string getldonthemename(uint32 theme_id); + std::string getfactionname(int faction_id); + std::string getlanguagename(int language_id); + std::string getbodytypename(uint32 bodytype_id); + std::string getconsiderlevelname(uint8 consider_level); void safemove(); void rain(int weather); void snow(int weather); + void rename(std::string name); void surname(const char *name); void permaclass(int class_id); void permarace(int race_id); @@ -151,15 +158,18 @@ public: void targlobal(const char *varname, const char *value, const char *duration, int npcid, int charid, int zoneid); void delglobal(const char *varname); void ding(); - void rebind(int zoneid, const glm::vec3& location); + void rebind(int zone_id, const glm::vec3& location); + void rebind(int zone_id, const glm::vec4& location); void start(int wp); void stop(); void pause(int duration); void moveto(const glm::vec4& position, bool saveguardspot); void resume(); - void addldonpoints(int32 points, uint32 theme); - void addldonwin(int32 wins, uint32 theme); - void addldonloss(int32 losses, uint32 theme); + void addldonpoints(uint32 theme_id, int points); + void addldonloss(uint32 theme_id); + void addldonwin(uint32 theme_id); + void removeldonloss(uint32 theme_id); + void removeldonwin(uint32 theme_id); void setnexthpevent(int at); void setnextinchpevent(int at); void respawn(int npc_type, int grid); @@ -264,15 +274,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); + std::string 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); + uint32 getcurrencyid(uint32 item_id); + uint32 getcurrencyitemid(uint32 currency_id); const char* getguildnamebyid(int guild_id); int getguildidbycharid(uint32 char_id); int getgroupidbycharid(uint32 char_id); - const char* getnpcnamebyid(uint32 npc_id); + std::string getnpcnamebyid(uint32 npc_id); + std::string getcleannpcnamebyid(uint32 npc_id); int getraididbycharid(uint32 char_id); void SetRunning(bool val); bool IsRunning(); @@ -285,97 +296,50 @@ public: uint16 CreateDoor( const char* model, float x, float y, float z, float heading, uint8 opentype, uint16 size); int32 GetZoneID(const char *zone); static std::string GetZoneLongName(std::string zone_short_name); - void CrossZoneAssignTaskByCharID(int character_id, uint32 task_id, bool enforce_level_requirement = false); - void CrossZoneAssignTaskByGroupID(int group_id, uint32 task_id, bool enforce_level_requirement = false); - void CrossZoneAssignTaskByRaidID(int raid_id, uint32 task_id, bool enforce_level_requirement = false); - void CrossZoneAssignTaskByGuildID(int guild_id, uint32 task_id, bool enforce_level_requirement = false); - void CrossZoneCastSpellByCharID(int character_id, uint32 spell_id); - void CrossZoneCastSpellByGroupID(int group_id, uint32 spell_id); - void CrossZoneCastSpellByRaidID(int raid_id, uint32 spell_id); - void CrossZoneCastSpellByGuildID(int guild_id, uint32 spell_id); - void CrossZoneDisableTaskByCharID(int character_id, uint32 task_id); - void CrossZoneDisableTaskByGroupID(int group_id, uint32 task_id); - void CrossZoneDisableTaskByRaidID(int raid_id, uint32 task_id); - void CrossZoneDisableTaskByGuildID(int guild_id, uint32 task_id); - void CrossZoneEnableTaskByCharID(int character_id, uint32 task_id); - void CrossZoneEnableTaskByGroupID(int group_id, uint32 task_id); - void CrossZoneEnableTaskByRaidID(int raid_id, uint32 task_id); - void CrossZoneEnableTaskByGuildID(int guild_id, uint32 task_id); - void CrossZoneFailTaskByCharID(int character_id, uint32 task_id); - void CrossZoneFailTaskByGroupID(int group_id, uint32 task_id); - void CrossZoneFailTaskByRaidID(int raid_id, uint32 task_id); - void CrossZoneFailTaskByGuildID(int guild_id, uint32 task_id); - void CrossZoneMarqueeByCharID(int character_id, uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char *message); - void CrossZoneMarqueeByGroupID(int group_id, uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char *message); - void CrossZoneMarqueeByRaidID(int raid_id, uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char *message); - void CrossZoneMarqueeByGuildID(int guild_id, uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char *message); - void CrossZoneMessagePlayerByName(uint32 type, const char *character_name, const char *message); - void CrossZoneMessagePlayerByGroupID(uint32 type, int group_id, const char *message); - void CrossZoneMessagePlayerByRaidID(uint32 type, int raid_id, const char *message); - void CrossZoneMessagePlayerByGuildID(uint32 type, int guild_id, const char *message); - void CrossZoneMovePlayerByCharID(int character_id, const char *zone_short_name); - void CrossZoneMovePlayerByGroupID(int group_id, const char *zone_short_name); - void CrossZoneMovePlayerByRaidID(int raid_id, const char *zone_short_name); - void CrossZoneMovePlayerByGuildID(int guild_id, const char *zone_short_name); - void CrossZoneMoveInstanceByCharID(int character_id, uint16 instance_id); - void CrossZoneMoveInstanceByGroupID(int group_id, uint16 instance_id); - void CrossZoneMoveInstanceByRaidID(int raid_id, uint16 instance_id); - void CrossZoneMoveInstanceByGuildID(int guild_id, uint16 instance_id); - void CrossZoneRemoveSpellByCharID(int character_id, uint32 spell_id); - void CrossZoneRemoveSpellByGroupID(int group_id, uint32 spell_id); - void CrossZoneRemoveSpellByRaidID(int raid_id, uint32 spell_id); - void CrossZoneRemoveSpellByGuildID(int guild_id, uint32 spell_id); - void CrossZoneRemoveTaskByCharID(int character_id, uint32 task_id); - void CrossZoneRemoveTaskByGroupID(int group_id, uint32 task_id); - void CrossZoneRemoveTaskByRaidID(int raid_id, uint32 task_id); - void CrossZoneRemoveTaskByGuildID(int guild_id, uint32 task_id); - void CrossZoneResetActivityByCharID(int character_id, uint32 task_id, int activity_id); - void CrossZoneResetActivityByGroupID(int group_id, uint32 task_id, int activity_id); - void CrossZoneResetActivityByRaidID(int raid_id, uint32 task_id, int activity_id); - void CrossZoneResetActivityByGuildID(int guild_id, uint32 task_id, int activity_id); - void CrossZoneSetEntityVariableByNPCTypeID(uint32 npctype_id, const char *variable_name, const char *variable_value); - void CrossZoneSetEntityVariableByClientName(const char *character_name, const char *variable_name, const char *variable_value); - void CrossZoneSetEntityVariableByGroupID(int group_id, const char *variable_name, const char *variable_value); - void CrossZoneSetEntityVariableByRaidID(int raid_id, const char *variable_name, const char *variable_value); - void CrossZoneSetEntityVariableByGuildID(int guild_id, const char *variable_name, const char *variable_value); - void CrossZoneSignalPlayerByCharID(int charid, uint32 signal); - void CrossZoneSignalPlayerByGroupID(int group_id, uint32 signal); - void CrossZoneSignalPlayerByRaidID(int raid_id, uint32 signal); - void CrossZoneSignalPlayerByGuildID(int guild_id, uint32 signal); - void CrossZoneSignalNPCByNPCTypeID(uint32 npctype_id, uint32 signal); - void CrossZoneSignalPlayerByName(const char *character_name, uint32 signal); - void CrossZoneUpdateActivityByCharID(int character_id, uint32 task_id, int activity_id, int activity_count = 1); - void CrossZoneUpdateActivityByGroupID(int group_id, uint32 task_id, int activity_id, int activity_count = 1); - void CrossZoneUpdateActivityByRaidID(int raid_id, uint32 task_id, int activity_id, int activity_count = 1); - void CrossZoneUpdateActivityByGuildID(int guild_id, uint32 task_id, int activity_id, int activity_count = 1); - void WorldWideAssignTask(uint32 task_id, bool enforce_level_requirement = false, uint8 min_status = 0, uint8 max_status = 0); - void WorldWideCastSpell(uint32 spell_id, uint8 min_status = 0, uint8 max_status = 0); - void WorldWideDisableTask(uint32 task_id, uint8 min_status = 0, uint8 max_status = 0); - void WorldWideEnableTask(uint32 task_id, uint8 min_status = 0, uint8 max_status = 0); - void WorldWideFailTask(uint32 task_id, uint8 min_status = 0, uint8 max_status = 0); - void WorldWideMarquee(uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char *message, uint8 min_status = 0, uint8 max_status = 0); - void WorldWideMessage(uint32 type, const char *message, uint8 min_status = 0, uint8 max_status = 0); - void WorldWideMove(const char *zone_short_name, uint8 min_status = 0, uint8 max_status = 0); - void WorldWideMoveInstance(uint16 instance_id, uint8 min_status = 0, uint8 max_status = 0); - void WorldWideRemoveSpell(uint32 spell_id, uint8 min_status = 0, uint8 max_status = 0); - void WorldWideRemoveTask(uint32 task_id, uint8 min_status = 0, uint8 max_status = 0); - void WorldWideResetActivity(uint32 task_id, int activity_id, uint8 min_status = 0, uint8 max_status = 0); - void WorldWideSetEntityVariableClient(const char *variable_name, const char *variable_value, uint8 min_status = 0, uint8 max_status = 0); - void WorldWideSetEntityVariableNPC(const char *variable_name, const char *variable_value); - void WorldWideSignalClient(uint32 signal, uint8 min_status = 0, uint8 max_status = 0); - void WorldWideSignalNPC(uint32 signal); - void WorldWideUpdateActivity(uint32 task_id, int activity_id, int activity_count = 1, uint8 min_status = 0, uint8 max_status = 0); + static std::string GetZoneLongNameByID(uint32 zone_id); + static std::string GetZoneShortName(uint32 zone_id); + void CrossZoneDialogueWindow(uint8 update_type, int update_identifier, const char* message, const char* client_name = ""); + void CrossZoneLDoNUpdate(uint8 update_type, uint8 update_subtype, int update_identifier, uint32 theme_id, int points = 1, const char* client_name = ""); + void CrossZoneMarquee(uint8 update_type, int update_identifier, uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char* message, const char* client_name = ""); + void CrossZoneMessage(uint8 update_type, int update_identifier, uint32 type, const char* message, const char* client_name = ""); + void CrossZoneMove(uint8 update_type, uint8 update_subtype, int update_identifier, const char* zone_short_name, uint16 instance_id, const char* client_name = ""); + void CrossZoneSetEntityVariable(uint8 update_type, int update_identifier, const char* variable_name, const char* variable_value, const char* client_name = ""); + void CrossZoneSignal(uint8 update_type, int update_identifier, uint32 signal, const char* client_name = ""); + void CrossZoneSpell(uint8 update_type, uint8 update_subtype, int update_identifier, uint32 spell_id, const char* client_name = ""); + void CrossZoneTaskUpdate(uint8 update_type, uint8 update_subtype, int update_identifier, uint32 task_identifier, int task_subidentifier = -1, int update_count = 1, bool enforce_level_requirement = false, const char* client_name = ""); + void WorldWideDialogueWindow(const char* message, uint8 min_status = AccountStatus::Player, uint8 max_status = AccountStatus::Player); + void WorldWideLDoNUpdate(uint8 update_type, uint32 theme_id, int points = 1, uint8 min_status = AccountStatus::Player, uint8 max_status = AccountStatus::Player); + void WorldWideMarquee(uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, const char* message, uint8 min_status = AccountStatus::Player, uint8 max_status = AccountStatus::Player); + void WorldWideMessage(uint32 type, const char* message, uint8 min_status = AccountStatus::Player, uint8 max_status = AccountStatus::Player); + void WorldWideMove(uint8 update_type, const char* zone_short_name, uint16 instance_id = 0, uint8 min_status = AccountStatus::Player, uint8 max_status = AccountStatus::Player); + void WorldWideSetEntityVariable(uint8 update_type, const char* variable_name, const char* variable_value, uint8 min_status = AccountStatus::Player, uint8 max_status = AccountStatus::Player); + void WorldWideSignal(uint8 update_type, uint32 signal, uint8 min_status = AccountStatus::Player, uint8 max_status = AccountStatus::Player); + void WorldWideSpell(uint8 update_type, uint32 spell_id, uint8 min_status = AccountStatus::Player, uint8 max_status = AccountStatus::Player); + void WorldWideTaskUpdate(uint8 update_type, uint32 task_identifier, int task_subidentifier = -1, int update_count = 1, bool enforce_level_requirement = false, uint8 min_status = AccountStatus::Player, uint8 max_status = AccountStatus::Player); bool EnableRecipe(uint32 recipe_id); bool DisableRecipe(uint32 recipe_id); void ClearNPCTypeCache(int npctype_id); void ReloadZoneStaticData(); std::string secondstotime(int duration); + std::string gethexcolorcode(std::string color_name); + double GetAAEXPModifierByCharID(uint32 character_id, uint32 zone_id) const; + double GetEXPModifierByCharID(uint32 character_id, uint32 zone_id) const; + void SetAAEXPModifierByCharID(uint32 character_id, uint32 zone_id, double aa_modifier); + void SetEXPModifierByCharID(uint32 character_id, uint32 zone_id, double exp_modifier); + std::string getgendername(uint32 gender_id); + std::string getdeityname(uint32 deity_id); + std::string getinventoryslotname(int16 slot_id); + int getitemstat(uint32 item_id, std::string stat_identifier); + int getspellstat(uint32 spell_id, std::string stat_identifier, uint8 slot = 0); + const SPDat_Spell_Struct *getspell(uint32 spell_id); + std::string getenvironmentaldamagename(uint8 damage_type); Client *GetInitiator() const; NPC *GetNPC() const; Mob *GetOwner() const; EQ::InventoryProfile* GetInventory() const; EQ::ItemInstance *GetQuestItem() const; + const SPDat_Spell_Struct *GetQuestSpell(); std::string GetEncounter() const; inline bool ProximitySayInUse() { return HaveProximitySays; } diff --git a/zone/raids.cpp b/zone/raids.cpp index b96eedf43..56d5b31c8 100644 --- a/zone/raids.cpp +++ b/zone/raids.cpp @@ -635,7 +635,7 @@ void Raid::CastGroupSpell(Mob* caster, uint16 spellid, uint32 gid) if(members[x].member == caster) { caster->SpellOnTarget(spellid, caster); #ifdef GROUP_BUFF_PETS - if(spells[spellid].targettype != ST_GroupNoPets && caster->GetPet() && caster->HasPetAffinity() && !caster->GetPet()->IsCharmed()) + if(spells[spellid].target_type != ST_GroupNoPets && caster->GetPet() && caster->HasPetAffinity() && !caster->GetPet()->IsCharmed()) caster->SpellOnTarget(spellid, caster->GetPet()); #endif } @@ -646,7 +646,7 @@ void Raid::CastGroupSpell(Mob* caster, uint16 spellid, uint32 gid) if(distance <= range2){ caster->SpellOnTarget(spellid, members[x].member); #ifdef GROUP_BUFF_PETS - if(spells[spellid].targettype != ST_GroupNoPets && members[x].member->GetPet() && members[x].member->HasPetAffinity() && !members[x].member->GetPet()->IsCharmed()) + if(spells[spellid].target_type != ST_GroupNoPets && members[x].member->GetPet() && members[x].member->HasPetAffinity() && !members[x].member->GetPet()->IsCharmed()) caster->SpellOnTarget(spellid, members[x].member->GetPet()); #endif } @@ -927,42 +927,6 @@ void Raid::SplitMoney(uint32 gid, uint32 copper, uint32 silver, uint32 gold, uin } } -void Raid::GroupBardPulse(Mob* caster, uint16 spellid, uint32 gid){ - uint32 z; - float range, distance; - - if(!caster) - return; - - range = caster->GetAOERange(spellid); - - float range2 = range*range; - - for(z=0; z < MAX_RAID_MEMBERS; z++) { - if(members[z].member == caster) { - caster->BardPulse(spellid, caster); -#ifdef GROUP_BUFF_PETS - if(caster->GetPet() && caster->HasPetAffinity() && !caster->GetPet()->IsCharmed()) - caster->BardPulse(spellid, caster->GetPet()); -#endif - } - else if(members[z].member != nullptr) - { - if(members[z].GroupNumber == gid){ - distance = DistanceSquared(caster->GetPosition(), members[z].member->GetPosition()); - if(distance <= range2) { - members[z].member->BardPulse(spellid, caster); -#ifdef GROUP_BUFF_PETS - if(members[z].member->GetPet() && members[z].member->HasPetAffinity() && !members[z].member->GetPet()->IsCharmed()) - members[z].member->GetPet()->BardPulse(spellid, caster); -#endif - } else - LogSpells("Group bard pulse: [{}] is out of range [{}] at distance [{}] from [{}]", members[z].member->GetName(), range, distance, caster->GetName()); - } - } - } -} - void Raid::TeleportGroup(Mob* sender, uint32 zoneID, uint16 instance_id, float x, float y, float z, float heading, uint32 gid) { for(int i = 0; i < MAX_RAID_MEMBERS; i++) diff --git a/zone/raids.h b/zone/raids.h index 2daff6d4e..cfaea07e7 100644 --- a/zone/raids.h +++ b/zone/raids.h @@ -177,7 +177,6 @@ public: 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 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); void TeleportRaid(Mob* sender, uint32 zoneID, uint16 instance_id, float x, float y, float z, float heading); diff --git a/zone/shared_task_zone_messaging.cpp b/zone/shared_task_zone_messaging.cpp new file mode 100644 index 000000000..89e262847 --- /dev/null +++ b/zone/shared_task_zone_messaging.cpp @@ -0,0 +1,176 @@ +#include "shared_task_zone_messaging.h" +#include "../common/shared_tasks.h" +#include "../common/servertalk.h" +#include "client.h" +#include "../common/repositories/character_data_repository.h" +#include "../common/repositories/shared_task_members_repository.h" + +#include +#include +#include +#include +#include + +void SharedTaskZoneMessaging::HandleWorldMessage(ServerPacket *pack) +{ + switch (pack->opcode) { + case ServerOP_SharedTaskAcceptNewTask: { + auto p = reinterpret_cast(pack->pBuffer); + auto c = entity_list.GetClientByCharID(p->requested_character_id); + if (c) { + LogTasks("[ServerOP_SharedTaskAcceptNewTask] We're back in zone and I found [{}]", c->GetCleanName()); + + c->m_requesting_shared_task = true; + c->GetTaskState() + ->AcceptNewTask( + c, + (int) p->requested_task_id, + (int) p->requested_npc_type_id, + p->accept_time + ); + c->LoadClientTaskState(); + c->m_requesting_shared_task = false; + } + + break; + } + case ServerOP_SharedTaskUpdate: { + auto p = reinterpret_cast(pack->pBuffer); + auto c = entity_list.GetClientByCharID(p->source_character_id); + if (c) { + LogTasks("[ServerOP_SharedTaskUpdate] We're back in zone and I found [{}]", c->GetCleanName()); + + c->m_shared_task_update = true; + c->GetTaskState()->SharedTaskIncrementDoneCount( + c, + (int) p->task_id, + (int) p->activity_id, + (int) p->done_count, + p->ignore_quest_update + ); + c->m_shared_task_update = false; + } + + break; + } + case ServerOP_SharedTaskAttemptRemove: { + auto p = reinterpret_cast(pack->pBuffer); + auto c = entity_list.GetClientByCharID(p->requested_character_id); + if (c) { + LogTasks("[ServerOP_SharedTaskAttemptRemove] We're back in zone and I found [{}]", c->GetCleanName()); + + c->m_requested_shared_task_removal = true; + c->GetTaskState()->CancelTask( + c, + TASKSLOTSHAREDTASK, + static_cast((int) TASK_TYPE_SHARED), + p->remove_from_db + ); + c->m_requested_shared_task_removal = false; + + c->SetSharedTaskId(0); + } + + break; + } + case ServerOP_SharedTaskMemberlist: { + auto p = reinterpret_cast(pack->pBuffer); + + LogTasks( + "[ServerOP_SharedTaskMemberlist] We're back in zone and I'm searching for [{}]", + p->destination_character_id + ); + + // find character and route packet + auto c = entity_list.GetClientByCharID(p->destination_character_id); + if (c) { + LogTasks("[ServerOP_SharedTaskMemberlist] We're back in zone and I found [{}]", c->GetCleanName()); + + std::vector members; + + // deserialize members from world + EQ::Util::MemoryStreamReader ss(p->cereal_serialized_members, p->cereal_size); + cereal::BinaryInputArchive archive(ss); + archive(members); + + SerializeBuffer buf(sizeof(SharedTaskMemberList_Struct) + 15 * members.size()); + buf.WriteInt32(0); // unknown ids + buf.WriteInt32(0); + buf.WriteInt32((int32) members.size()); + + for (auto &m : members) { + buf.WriteString(m.character_name); + buf.WriteInt32(0); // monster mission + buf.WriteInt8(m.is_leader ? 1 : 0); + } + + auto outapp = std::make_unique(OP_SharedTaskMemberList, buf); + c->QueuePacket(outapp.get()); + } + + break; + } + case ServerOP_SharedTaskMemberChange: { + auto p = reinterpret_cast(pack->pBuffer); + + LogTasksDetail("[ServerOP_SharedTaskMemberChange] Searching for [{}]", p->destination_character_id); + + auto c = entity_list.GetClientByCharID(p->destination_character_id); + if (c) { + LogTasksDetail("[ServerOP_SharedTaskMemberChange] Found [{}]", c->GetCleanName()); + + SerializeBuffer buf; + buf.WriteInt32(0); // unique character id of receiver, leave 0 for emu + buf.WriteInt32(0); // unknown, seen 50, 4, 0 + buf.WriteInt8(p->removed ? 0 : 1); // 0: removed 1: added + buf.WriteString(p->player_name); + + // live sends more after the name but it might just be garbage from + // a re-used buffer (possibly a former name[64] buffer?) + + auto outapp = std::make_unique(OP_SharedTaskMemberChange, buf); + c->QueuePacket(outapp.get()); + } + + break; + } + case ServerOP_SharedTaskInvitePlayer: { + auto p = reinterpret_cast(pack->pBuffer); + auto c = entity_list.GetClientByCharID(p->requested_character_id); + if (c) { + LogTasks("[ServerOP_SharedTaskInvitePlayer] We're back in zone and I found [{}]", c->GetCleanName()); + + // init packet + auto outapp = new EQApplicationPacket(OP_SharedTaskInvite, sizeof(SharedTaskInvite_Struct)); + auto *i = (SharedTaskInvite_Struct *) outapp->pBuffer; + + // fill + i->unknown00 = 0; + i->invite_id = (int) p->invite_shared_task_id; + strn0cpy(i->inviter_name, p->inviter_name, 64); + strn0cpy(i->task_name, p->task_name, 64); + + // sends + c->QueuePacket(outapp); + safe_delete(outapp); + } + + break; + } + case ServerOP_SharedTaskPurgeAllCommand: { + LogTasksDetail("[ServerOP_SharedTaskPurgeAllCommand] Syncing clients"); + + for (auto &client: entity_list.GetClientList()) { + Client *c = client.second; + task_manager->SyncClientSharedTaskState(c, c->GetTaskState()); + c->RemoveClientTaskState(); + c->LoadClientTaskState(); + } + + break; + } + default: + break; + } + +} diff --git a/zone/shared_task_zone_messaging.h b/zone/shared_task_zone_messaging.h new file mode 100644 index 000000000..492e3ac92 --- /dev/null +++ b/zone/shared_task_zone_messaging.h @@ -0,0 +1,12 @@ +#ifndef EQEMU_SHARED_TASK_ZONE_MESSAGING_H +#define EQEMU_SHARED_TASK_ZONE_MESSAGING_H + +class ServerPacket; + +class SharedTaskZoneMessaging { +public: + static void HandleWorldMessage(ServerPacket *pack); +}; + + +#endif //EQEMU_SHARED_TASK_ZONE_MESSAGING_H diff --git a/zone/spawn2.cpp b/zone/spawn2.cpp index 37b8cc943..f15ff12c3 100644 --- a/zone/spawn2.cpp +++ b/zone/spawn2.cpp @@ -70,7 +70,8 @@ CREATE TABLE spawn_events ( Spawn2::Spawn2(uint32 in_spawn2_id, uint32 spawngroup_id, float in_x, float in_y, float in_z, float in_heading, uint32 respawn, uint32 variance, uint32 timeleft, uint32 grid, - uint16 in_cond_id, int16 in_min_value, bool in_enabled, EmuAppearance anim) + bool in_path_when_zone_idle, uint16 in_cond_id, int16 in_min_value, + bool in_enabled, EmuAppearance anim) : timer(100000), killcount(0) { spawn2_id = in_spawn2_id; @@ -82,6 +83,7 @@ Spawn2::Spawn2(uint32 in_spawn2_id, uint32 spawngroup_id, respawn_ = respawn; variance_ = variance; grid_ = grid; + path_when_zone_idle = in_path_when_zone_idle; condition_id = in_cond_id; condition_min_value = in_min_value; npcthis = nullptr; @@ -474,6 +476,7 @@ bool ZoneDatabase::PopulateZoneSpawnListClose(uint32 zoneid, LinkedList "respawntime, " "variance, " "pathgrid, " + "path_when_zone_idle, " "_condition, " "cond_value, " "enabled, " @@ -494,7 +497,7 @@ bool ZoneDatabase::PopulateZoneSpawnListClose(uint32 zoneid, LinkedList uint32 spawn_time_left = 0; Spawn2* new_spawn = 0; - bool perl_enabled = atoi(row[11]) == 1 ? true : false; + bool perl_enabled = atoi(row[12]) == 1 ? true : false; if (spawn_times.count(atoi(row[0])) != 0) spawn_time_left = spawn_times[atoi(row[0])]; @@ -508,21 +511,22 @@ bool ZoneDatabase::PopulateZoneSpawnListClose(uint32 zoneid, LinkedList if (mob_distance > repop_distance) continue; - new_spawn = new Spawn2( // - atoi(row[0]), // uint32 in_spawn2_id - atoi(row[1]), // uint32 spawngroup_id - atof(row[2]), // float in_x - atof(row[3]), // float in_y - atof(row[4]), // float in_z - atof(row[5]), // float in_heading - atoi(row[6]), // uint32 respawn - atoi(row[7]), // uint32 variance - spawn_time_left, // uint32 timeleft - atoi(row[8]), // uint32 grid - atoi(row[9]), // uint16 in_cond_id - atoi(row[10]), // int16 in_min_value - perl_enabled, // bool in_enabled - (EmuAppearance)atoi(row[12]) // EmuAppearance anim + new_spawn = new Spawn2( + atoi(row[0]), // uint32 in_spawn2_id + atoi(row[1]), // uint32 spawngroup_id + atof(row[2]), // float in_x + atof(row[3]), // float in_y + atof(row[4]), // float in_z + atof(row[5]), // float in_heading + atoi(row[6]), // uint32 respawn + atoi(row[7]), // uint32 variance + spawn_time_left, // uint32 timeleft + atoi(row[8]), // uint32 grid + (bool)atoi(row[9]), // bool path_when_zone_idle + atoi(row[10]), // uint16 in_cond_id + atoi(row[11]), // int16 in_min_value + perl_enabled, // bool in_enabled + (EmuAppearance)atoi(row[13]) // EmuAppearance anim ); spawn2_list.Insert(new_spawn); @@ -578,6 +582,7 @@ bool ZoneDatabase::PopulateZoneSpawnList(uint32 zoneid, LinkedList &spa "respawntime, " "variance, " "pathgrid, " + "path_when_zone_idle, " "_condition, " "cond_value, " "enabled, " @@ -598,26 +603,27 @@ bool ZoneDatabase::PopulateZoneSpawnList(uint32 zoneid, LinkedList &spa uint32 spawn_time_left = 0; Spawn2* new_spawn = 0; - bool perl_enabled = atoi(row[11]) == 1 ? true : false; + bool perl_enabled = atoi(row[12]) == 1 ? true : false; if (spawn_times.count(atoi(row[0])) != 0) spawn_time_left = spawn_times[atoi(row[0])]; - new_spawn = new Spawn2( // - atoi(row[0]), // uint32 in_spawn2_id - atoi(row[1]), // uint32 spawngroup_id - atof(row[2]), // float in_x - atof(row[3]), // float in_y - atof(row[4]), // float in_z - atof(row[5]), // float in_heading - atoi(row[6]), // uint32 respawn - atoi(row[7]), // uint32 variance - spawn_time_left, // uint32 timeleft - atoi(row[8]), // uint32 grid - atoi(row[9]), // uint16 in_cond_id - atoi(row[10]), // int16 in_min_value - perl_enabled, // bool in_enabled - (EmuAppearance)atoi(row[12]) // EmuAppearance anim + new_spawn = new Spawn2( + atoi(row[0]), // uint32 in_spawn2_id + atoi(row[1]), // uint32 spawngroup_id + atof(row[2]), // float in_x + atof(row[3]), // float in_y + atof(row[4]), // float in_z + atof(row[5]), // float in_heading + atoi(row[6]), // uint32 respawn + atoi(row[7]), // uint32 variance + spawn_time_left, // uint32 timeleft + atoi(row[8]), // uint32 grid + (bool)atoi(row[9]), // bool path_when_zone_idle + atoi(row[10]), // uint16 in_cond_id + atoi(row[11]), // int16 in_min_value + perl_enabled, // bool in_enabled + (EmuAppearance)atoi(row[13]) // EmuAppearance anim ); spawn2_list.Insert(new_spawn); @@ -632,9 +638,10 @@ bool ZoneDatabase::PopulateZoneSpawnList(uint32 zoneid, LinkedList &spa Spawn2* ZoneDatabase::LoadSpawn2(LinkedList &spawn2_list, uint32 spawn2id, uint32 timeleft) { std::string query = StringFormat("SELECT id, spawngroupID, x, y, z, heading, " - "respawntime, variance, pathgrid, _condition, " - "cond_value, enabled, animation FROM spawn2 " - "WHERE id = %i", spawn2id); + "respawntime, variance, pathgrid, " + "path_when_zone_idle, _condition, " + "cond_value, enabled, animation FROM spawn2 " + "WHERE id = %i", spawn2id); auto results = QueryDatabase(query); if (!results.Success()) { return nullptr; @@ -646,11 +653,12 @@ Spawn2* ZoneDatabase::LoadSpawn2(LinkedList &spawn2_list, uint32 spawn2 auto row = results.begin(); - bool perl_enabled = atoi(row[11]) == 1 ? true : false; + bool perl_enabled = atoi(row[12]) == 1 ? true : false; - auto newSpawn = new Spawn2(atoi(row[0]), atoi(row[1]), atof(row[2]), atof(row[3]), atof(row[4]), atof(row[5]), - atoi(row[6]), atoi(row[7]), timeleft, atoi(row[8]), atoi(row[9]), atoi(row[10]), - perl_enabled, (EmuAppearance)atoi(row[12])); + auto newSpawn = new Spawn2(atoi(row[0]), atoi(row[1]), atof(row[2]), + atof(row[3]), atof(row[4]), atof(row[5]), atoi(row[6]), atoi(row[7]), + timeleft, atoi(row[8]), (bool) atoi(row[9]), atoi(row[10]), + atoi(row[11]), perl_enabled, (EmuAppearance)atoi(row[13])); spawn2_list.Insert(newSpawn); @@ -824,8 +832,11 @@ void SpawnConditionManager::Process() { if(EQTime::IsTimeBefore(&tod, &cevent.next)) { //this event has been triggered. //execute the event - if(!cevent.strict || (cevent.strict && cevent.next.hour == tod.hour && cevent.next.day == tod.day && cevent.next.month == tod.month && cevent.next.year == tod.year)) + uint8 min = cevent.next.minute + RuleI(Zone, SpawnEventMin); + if(!cevent.strict || (cevent.strict && tod.minute < min && cevent.next.hour == tod.hour && cevent.next.day == tod.day && cevent.next.month == tod.month && cevent.next.year == tod.year)) ExecEvent(cevent, true); + else + LogSpawns("Event {}: Is strict, ExecEvent is skipped.", cevent.id); //add the period of the event to the trigger time EQTime::AddMinutes(cevent.period, &cevent.next); @@ -850,6 +861,7 @@ void SpawnConditionManager::ExecEvent(SpawnEvent &event, bool send_update) { std::map::iterator condi; condi = spawn_conditions.find(event.condition_id); if(condi == spawn_conditions.end()) { + //If we're here, strict has already been checked. Check again in case hour has changed. LogSpawns("Event [{}]: Unable to find condition [{}] to execute on", event.id, event.condition_id); return; //unable to find the spawn condition to operate on } diff --git a/zone/spawn2.h b/zone/spawn2.h index bf6530876..3f8b2e8ee 100644 --- a/zone/spawn2.h +++ b/zone/spawn2.h @@ -32,7 +32,7 @@ public: Spawn2(uint32 spawn2_id, uint32 spawngroup_id, float x, float y, float z, float heading, uint32 respawn, uint32 variance, - uint32 timeleft = 0, uint32 grid = 0, + uint32 timeleft = 0, uint32 grid = 0, bool in_path_when_zone_idle=false, uint16 cond_id = SC_AlwaysEnabled, int16 min_value = 0, bool in_enabled = true, EmuAppearance anim = eaStanding); ~Spawn2(); @@ -54,6 +54,7 @@ public: float GetY() { return y; } float GetZ() { return z; } float GetHeading() { return heading; } + bool PathWhenZoneIdle() { return path_when_zone_idle; } void SetRespawnTimer(uint32 newrespawntime) { respawn_ = newrespawntime; }; void SetVariance(uint32 newvariance) { variance_ = newvariance; } const uint32 GetVariance() const { return variance_; } @@ -86,6 +87,7 @@ private: float heading; uint32 variance_; uint32 grid_; + bool path_when_zone_idle; uint16 condition_id; int16 condition_min_value; bool enabled; diff --git a/zone/spawngroup.cpp b/zone/spawngroup.cpp index c337bfac0..84ae2a8f9 100644 --- a/zone/spawngroup.cpp +++ b/zone/spawngroup.cpp @@ -244,8 +244,11 @@ bool ZoneDatabase::LoadSpawnGroups(const char *zone_name, uint16 version, SpawnG AND spawnentry.spawngroupID = spawn2.spawngroupID AND - zone = '{}'), - zone_name + zone = '{}' + {} + ), + zone_name, + ContentFilterCriteria::apply("spawnentry") ); results = QueryDatabase(query); diff --git a/zone/special_attacks.cpp b/zone/special_attacks.cpp index 717bc4fbf..4d9114dde 100644 --- a/zone/special_attacks.cpp +++ b/zone/special_attacks.cpp @@ -156,6 +156,9 @@ void Mob::DoSpecialAttackDamage(Mob *who, EQ::skills::SkillType skill, int32 bas if (my_hit.base_damage == 0) my_hit.base_damage = GetBaseSkillDamage(my_hit.skill); + if (base_damage == DMG_INVULNERABLE) + my_hit.damage_done = DMG_INVULNERABLE; + if (who->GetInvul() || who->GetSpecialAbility(IMMUNE_MELEE)) my_hit.damage_done = DMG_INVULNERABLE; @@ -193,14 +196,6 @@ void Mob::DoSpecialAttackDamage(Mob *who, EQ::skills::SkillType skill, int32 bas DoAttack(who, my_hit); who->AddToHateList(this, hate, 0); - if (my_hit.damage_done > 0 && aabonuses.SkillAttackProc[0] && aabonuses.SkillAttackProc[1] == skill && - IsValidSpell(aabonuses.SkillAttackProc[2])) { - float chance = aabonuses.SkillAttackProc[0] / 1000.0f; - if (zone->random.Roll(chance)) - SpellFinished(aabonuses.SkillAttackProc[2], who, EQ::spells::CastingSlot::Item, 0, -1, - spells[aabonuses.SkillAttackProc[2]].ResistDiff); - } - who->Damage(this, my_hit.damage_done, SPELL_UNKNOWN, skill, false); // Make sure 'this' has not killed the target and 'this' is not dead (Damage shield ect). @@ -209,11 +204,14 @@ void Mob::DoSpecialAttackDamage(Mob *who, EQ::skills::SkillType skill, int32 bas if (HasDied()) return; - if (HasSkillProcs()) - TrySkillProc(who, skill, ReuseTime * 1000); + TryCastOnSkillUse(who, skill); - if (my_hit.damage_done > 0 && HasSkillProcSuccess()) + if (HasSkillProcs()) { + TrySkillProc(who, skill, ReuseTime * 1000); + } + if (my_hit.damage_done > 0 && HasSkillProcSuccess()) { TrySkillProc(who, skill, ReuseTime * 1000, true); + } } // We should probably refactor this to take the struct not the packet @@ -221,8 +219,8 @@ void Client::OPCombatAbility(const CombatAbility_Struct *ca_atk) { if (!GetTarget()) return; - // make sure were actually able to use such an attack. - if (spellend_timer.Enabled() || IsFeared() || IsStunned() || IsMezzed() || DivineAura() || dead) + // make sure were actually able to use such an attack. (Bards can throw while casting. ~Kayen confirmed on live 1/22) + if ((spellend_timer.Enabled() && GetClass() != BARD)|| IsFeared() || IsStunned() || IsMezzed() || DivineAura() || dead) return; pTimerType timer = pTimerCombatAbility; @@ -231,7 +229,6 @@ void Client::OPCombatAbility(const CombatAbility_Struct *ca_atk) if (ClientVersion() >= EQ::versions::ClientVersion::RoF2 && ca_atk->m_skill == EQ::skills::SkillTigerClaw) timer = pTimerCombatAbility2; - bool CanBypassSkillCheck = false; if (ca_atk->m_skill == EQ::skills::SkillBash) { // SLAM - Bash without a shield equipped @@ -328,7 +325,7 @@ void Client::OPCombatAbility(const CombatAbility_Struct *ca_atk) CheckIncreaseSkill(EQ::skills::SkillFrenzy, GetTarget(), 10); int AtkRounds = 1; int32 max_dmg = GetBaseSkillDamage(EQ::skills::SkillFrenzy, GetTarget()); - DoAnim(anim2HSlashing, 0, false); + DoAnim(anim1HWeapon, 0, false); max_dmg = mod_frenzy_damage(max_dmg); @@ -400,10 +397,16 @@ void Client::OPCombatAbility(const CombatAbility_Struct *ca_atk) } wuchance /= 4; } - // They didn't add a string ID for this. - std::string msg = StringFormat("The spirit of Master Wu fills you! You gain %d additional attack(s).", extra); - // live uses 400 here -- not sure if it's the best for all clients though - SendColoredText(400, msg); + if (extra) { + SendColoredText( + 400, + fmt::format( + "The spirit of Master Wu fills you! You gain {} additional attack{}.", + extra, + extra != 1 ? "s" : "" + ) + ); + } auto classic = RuleB(Combat, ClassicMasterWu); while (extra) { MonkSpecialAttack(GetTarget(), (classic ? MonkSPA[zone->random.Int(0, 4)] : ca_atk->m_skill)); @@ -585,6 +588,10 @@ void Mob::TryBackstab(Mob *other, int ReuseTime) { if(IsClient()) CastToClient()->CheckIncreaseSkill(EQ::skills::SkillBackstab, other, 10); m_specialattacks = eSpecialAttacks::None; + + int double_bs_front = aabonuses.Double_Backstab_Front + itembonuses.Double_Backstab_Front + spellbonuses.Double_Backstab_Front; + if (double_bs_front && other->GetHP() > 0 && zone->random.Roll(double_bs_front)) + RogueBackstab(other, false, ReuseTime); } else { //We do a single regular attack if we attack from the front without chaotic stab Attack(other, EQ::invslot::slotPrimary); @@ -632,6 +639,8 @@ void Client::RangedAttack(Mob* other, bool CanDoubleAttack) { //conditions to use an attack checked before we are called if (!other) return; + else if (other == this) + return; //make sure the attack and ranged timers are up //if the ranged timer is disabled, then they have no ranged weapon and shouldent be attacking anyhow if(!CanDoubleAttack && ((attack_timer.Enabled() && !attack_timer.Check(false)) || (ranged_timer.Enabled() && !ranged_timer.Check()))) { @@ -749,11 +758,22 @@ void Client::RangedAttack(Mob* other, bool CanDoubleAttack) { //EndlessQuiver AA base1 = 100% Chance to avoid consumption arrow. int ChanceAvoidConsume = aabonuses.ConsumeProjectile + itembonuses.ConsumeProjectile + spellbonuses.ConsumeProjectile; - if (RangeItem->ExpendableArrow || !ChanceAvoidConsume || (ChanceAvoidConsume < 100 && zone->random.Int(0,99) > ChanceAvoidConsume)){ + // Consume Ammo, unless Ammo Consumption is disabled or player has Endless Quiver + bool consumes_ammo = RuleB(Combat, ArcheryConsumesAmmo); + if ( + consumes_ammo && + ( + RangeItem->ExpendableArrow || + !ChanceAvoidConsume || + (ChanceAvoidConsume < 100 && zone->random.Int(0,99) > ChanceAvoidConsume) + ) + ) { DeleteItemInInventory(ammo_slot, 1, true); - LogCombat("Consumed one arrow from slot [{}]", ammo_slot); + LogCombat("Consumed Archery Ammo from slot {}.", ammo_slot); + } else if (!consumes_ammo) { + LogCombat("Archery Ammo Consumption is disabled."); } else { - LogCombat("Endless Quiver prevented ammo consumption"); + LogCombat("Endless Quiver prevented Ammo Consumption."); } CheckIncreaseSkill(EQ::skills::SkillArchery, GetTarget(), -15); @@ -762,7 +782,7 @@ void Client::RangedAttack(Mob* other, bool CanDoubleAttack) { void Mob::DoArcheryAttackDmg(Mob *other, const EQ::ItemInstance *RangeWeapon, const EQ::ItemInstance *Ammo, uint16 weapon_damage, int16 chance_mod, int16 focus, int ReuseTime, uint32 range_id, - uint32 ammo_id, const EQ::ItemData *AmmoItem, int AmmoSlot, float speed) + uint32 ammo_id, const EQ::ItemData *AmmoItem, int AmmoSlot, float speed, bool DisableProcs) { if ((other == nullptr || ((IsClient() && CastToClient()->dead) || (other->IsClient() && other->CastToClient()->dead)) || @@ -772,7 +792,7 @@ void Mob::DoArcheryAttackDmg(Mob *other, const EQ::ItemInstance *RangeWeapon, co const EQ::ItemInstance *_RangeWeapon = nullptr; const EQ::ItemInstance *_Ammo = nullptr; - const EQ::ItemData *ammo_lost = nullptr; + const EQ::ItemData *last_ammo_used = nullptr; /* If LaunchProjectile is false this function will do archery damage on target, @@ -796,7 +816,7 @@ void Mob::DoArcheryAttackDmg(Mob *other, const EQ::ItemInstance *RangeWeapon, co if (!RangeWeapon && !Ammo && range_id && ammo_id) { if (IsClient()) { _RangeWeapon = CastToClient()->m_inv[EQ::invslot::slotRange]; - if (_RangeWeapon && _RangeWeapon->GetItem() && + if (_RangeWeapon && _RangeWeapon->GetItem() && _RangeWeapon->GetItem()->ID == range_id) RangeWeapon = _RangeWeapon; @@ -804,7 +824,7 @@ void Mob::DoArcheryAttackDmg(Mob *other, const EQ::ItemInstance *RangeWeapon, co if (_Ammo && _Ammo->GetItem() && _Ammo->GetItem()->ID == ammo_id) Ammo = _Ammo; else - ammo_lost = database.GetItem(ammo_id); + last_ammo_used = database.GetItem(ammo_id); } } } @@ -827,13 +847,13 @@ void Mob::DoArcheryAttackDmg(Mob *other, const EQ::ItemInstance *RangeWeapon, co if (LaunchProjectile) { // 1: Shoot the Projectile once we calculate weapon damage. TryProjectileAttack(other, AmmoItem, EQ::skills::SkillArchery, (WDmg + ADmg), RangeWeapon, - Ammo, AmmoSlot, speed); + Ammo, AmmoSlot, speed, DisableProcs); return; } - // unsure when this should happen - if (focus) // From FcBaseEffects + if (focus) { WDmg += WDmg * focus / 100; + } if (WDmg > 0 || ADmg > 0) { if (WDmg < 0) @@ -874,40 +894,50 @@ void Mob::DoArcheryAttackDmg(Mob *other, const EQ::ItemInstance *RangeWeapon, co other->Damage(this, TotalDmg, SPELL_UNKNOWN, EQ::skills::SkillArchery); - // Skill Proc Success - if (TotalDmg > 0 && HasSkillProcSuccess() && other && !other->HasDied()) { - if (ReuseTime) - TrySkillProc(other, EQ::skills::SkillArchery, ReuseTime); - else - TrySkillProc(other, EQ::skills::SkillArchery, 0, true, EQ::invslot::slotRange); + + if (!DisableProcs) { + // Weapon Proc + if (RangeWeapon && other && !other->HasDied()) { + TryCombatProcs(RangeWeapon, other, EQ::invslot::slotRange); + } + + // Ammo Proc, do not try spell procs if from ammo. + if (last_ammo_used) { + TryWeaponProc(nullptr, last_ammo_used, other, EQ::invslot::slotRange); + } + else if (Ammo && other && !other->HasDied()) { + TryWeaponProc(Ammo, Ammo->GetItem(), other, EQ::invslot::slotRange); + } } - // end of old fuck - if (LaunchProjectile) - return; // Shouldn't reach this point durring initial launch phase, but just in case. + TryCastOnSkillUse(other, EQ::skills::SkillArchery); - // Weapon Proc - if (RangeWeapon && other && !other->HasDied()) - TryWeaponProc(RangeWeapon, other, EQ::invslot::slotRange); + if (!DisableProcs) { + // Skill Proc Attempt + if (HasSkillProcs() && other && !other->HasDied()) { + if (ReuseTime) { + TrySkillProc(other, EQ::skills::SkillArchery, ReuseTime); + } + else { + TrySkillProc(other, EQ::skills::SkillArchery, 0, false, EQ::invslot::slotRange); + } + } - // Ammo Proc - if (ammo_lost) - TryWeaponProc(nullptr, ammo_lost, other, EQ::invslot::slotRange); - else if (Ammo && other && !other->HasDied()) - TryWeaponProc(Ammo, other, EQ::invslot::slotRange); - - // Skill Proc - if (HasSkillProcs() && other && !other->HasDied()) { - if (ReuseTime) - TrySkillProc(other, EQ::skills::SkillArchery, ReuseTime); - else - TrySkillProc(other, EQ::skills::SkillArchery, 0, false, EQ::invslot::slotRange); + // Skill Proc Success ... can proc off hits OR misses + if (HasSkillProcSuccess() && other && !other->HasDied()) { + if (ReuseTime) { + TrySkillProc(other, EQ::skills::SkillArchery, ReuseTime, true); + } + else { + TrySkillProc(other, EQ::skills::SkillArchery, 0, true, EQ::invslot::slotRange); + } + } } } bool Mob::TryProjectileAttack(Mob *other, const EQ::ItemData *item, EQ::skills::SkillType skillInUse, uint16 weapon_dmg, const EQ::ItemInstance *RangeWeapon, - const EQ::ItemInstance *Ammo, int AmmoSlot, float speed) + const EQ::ItemInstance *Ammo, int AmmoSlot, float speed, bool DisableProcs) { if (!other) return false; @@ -925,11 +955,31 @@ bool Mob::TryProjectileAttack(Mob *other, const EQ::ItemData *item, EQ::skills:: if (slot < 0) return false; - float speed_mod = speed; - + float distance_mod = 0.0f; float distance = other->CalculateDistance(GetX(), GetY(), GetZ()); - float hit = - 1200.0f + (10 * distance / speed_mod); // Calcuation: 60 = Animation Lag, 1.8 = Speed modifier for speed of (4) + + /* + New Distance Mod constant (7/25/21 update), modifier is needed to adjust slower speeds to have correct impact times at short distances. + We use archery 4.0 speed as a baseline for the forumla. At speed 1.5 at 50 pct distance mod is needed, where as speed 4.0 there is no modifer. + Therefore, we derive out our modifer as follows. distance_mod = (speed - 4) * ((50 - 0)/(1.5-4)). The ratio there is -20.0f. distance_mod = (speed - 4) * -20.0f + For distances >125 we use different modifier, this was all meticulously tested by eye to get the best possible outcome for projectile impact times. Not perfect though. + */ + + if (distance <= 125.0f) { + if (speed != 4.0f) { //Standard functions will always be 4.0f for archery. + distance_mod = (speed - 4.0f) * -20.0f; + distance += distance * distance_mod / 100.0f; + } + } + else if (distance > 125.0f && distance <= 200.0f) + distance = 3.14f * (distance / 2.0f); //Get distance of arc to better reflect projectile path length + + else if (distance > 200.0f) { + distance = distance * 1.30f; //Add 30% to base distance if over 200 range to tighten up hit timing. + distance = 3.14f * (distance / 2.0f); //Get distance of arc to better reflect projectile path length + } + + float hit = 1200.0f + (10 * distance / speed); ProjectileAtk[slot].increment = 1; ProjectileAtk[slot].hit_increment = static_cast(hit); // This projected hit time if target does NOT MOVE @@ -945,16 +995,15 @@ bool Mob::TryProjectileAttack(Mob *other, const EQ::ItemData *item, EQ::skills:: if (Ammo && Ammo->GetItem()) ProjectileAtk[slot].ammo_id = Ammo->GetItem()->ID; - ProjectileAtk[slot].ammo_slot = 0; + ProjectileAtk[slot].ammo_slot = AmmoSlot; ProjectileAtk[slot].skill = skillInUse; - ProjectileAtk[slot].speed_mod = speed_mod; + ProjectileAtk[slot].speed_mod = speed; + ProjectileAtk[slot].disable_procs = DisableProcs; SetProjectileAttack(true); if (item) SendItemAnimation(other, item, skillInUse, speed); - // else if (IsNPC()) - // ProjectileAnimation(other, 0,false,speed,0,0,0,CastToNPC()->GetAmmoIDfile(),skillInUse); return true; } @@ -974,30 +1023,47 @@ void Mob::ProjectileAttack() disable = false; Mob *target = entity_list.GetMobID(ProjectileAtk[i].target_id); - if (target && target->IsMoving()) { // Only recalculate hit increment if target moving - // Due to frequency that we need to check increment the targets position variables may not be - // updated even if moving. Do a simple check before calculating distance. + if (target && target->IsMoving()) { + /* + Only recalculate hit increment if target is moving. + Due to frequency that we need to check increment the targets position variables may not be + updated even if moving. Do a simple check before calculating distance. + */ if (ProjectileAtk[i].tlast_x != target->GetX() || ProjectileAtk[i].tlast_y != target->GetY()) { + ProjectileAtk[i].tlast_x = target->GetX(); ProjectileAtk[i].tlast_y = target->GetY(); - float distance = target->CalculateDistance( - ProjectileAtk[i].origin_x, ProjectileAtk[i].origin_y, ProjectileAtk[i].origin_z); - float hit = 1200.0f + (10 * distance / ProjectileAtk[i].speed_mod); // Calcuation: 60 = - // Animation Lag, 1.8 = - // Speed modifier for speed - // of (4) + + //Recalculate from the original location the projectile was fired in relation to the current targets location. + float distance = target->CalculateDistance(ProjectileAtk[i].origin_x, ProjectileAtk[i].origin_y, ProjectileAtk[i].origin_z); + float distance_mod = 0.0f; + + if (distance <= 125.0f) { + distance_mod = (ProjectileAtk[i].speed_mod - 4.0f) * -20.0f; + distance += distance * distance_mod / 100.0f; + } + else if (distance > 125.0f && distance <= 200.0f) + distance = 3.14f * (distance / 2.0f); //Get distance of arc to better reflect projectile path length + + else if (distance > 200.0f) { + distance = distance * 1.30f; //Add 30% to base distance if over 200 range to tighten up hit timing. + distance = 3.14f * (distance / 2.0f); //Get distance of arc to better reflect projectile path length + } + + float hit = 1200.0f + (10 * distance / ProjectileAtk[i].speed_mod); + ProjectileAtk[i].hit_increment = static_cast(hit); } } - // We hit I guess? + // Check if we hit. if (ProjectileAtk[i].hit_increment <= ProjectileAtk[i].increment) { if (target) { if (IsNPC()) { if (ProjectileAtk[i].skill == EQ::skills::SkillConjuration) { if (IsValidSpell(ProjectileAtk[i].wpn_dmg)) SpellOnTarget(ProjectileAtk[i].wpn_dmg, target, false, true, - spells[ProjectileAtk[i].wpn_dmg].ResistDiff, + spells[ProjectileAtk[i].wpn_dmg].resist_difficulty, true); } else { CastToNPC()->DoRangedAttackDmg( @@ -1009,15 +1075,15 @@ void Mob::ProjectileAttack() DoArcheryAttackDmg(target, nullptr, nullptr, ProjectileAtk[i].wpn_dmg, 0, 0, 0, ProjectileAtk[i].ranged_id, ProjectileAtk[i].ammo_id, nullptr, - ProjectileAtk[i].ammo_slot); + ProjectileAtk[i].ammo_slot, 4.0f, ProjectileAtk[i].disable_procs); else if (ProjectileAtk[i].skill == EQ::skills::SkillThrowing) DoThrowingAttackDmg(target, nullptr, nullptr, ProjectileAtk[i].wpn_dmg, 0, 0, 0, ProjectileAtk[i].ranged_id, - ProjectileAtk[i].ammo_slot); + ProjectileAtk[i].ammo_slot, 4.0f, ProjectileAtk[i].disable_procs); else if (ProjectileAtk[i].skill == EQ::skills::SkillConjuration && IsValidSpell(ProjectileAtk[i].wpn_dmg)) SpellOnTarget(ProjectileAtk[i].wpn_dmg, target, false, true, - spells[ProjectileAtk[i].wpn_dmg].ResistDiff, true); + spells[ProjectileAtk[i].wpn_dmg].resist_difficulty, true); } } @@ -1207,15 +1273,20 @@ void NPC::DoRangedAttackDmg(Mob* other, bool Launch, int16 damage_mod, int16 cha other->Damage(this, TotalDmg, SPELL_UNKNOWN, skillInUse); - if (TotalDmg > 0 && HasSkillProcSuccess() && !other->HasDied()) - TrySkillProc(other, skillInUse, 0, true, EQ::invslot::slotRange); - //try proc on hits and misses - if(other && !other->HasDied()) + if (other && !other->HasDied()) { TrySpellProc(nullptr, (const EQ::ItemData*)nullptr, other, EQ::invslot::slotRange); + } - if (HasSkillProcs() && other && !other->HasDied()) + TryCastOnSkillUse(other, skillInUse); + + if (HasSkillProcs() && other && !other->HasDied()) { TrySkillProc(other, skillInUse, 0, false, EQ::invslot::slotRange); + } + + if (HasSkillProcSuccess() && other && !other->HasDied()) { + TrySkillProc(other, skillInUse, 0, true, EQ::invslot::slotRange); + } } void Client::ThrowingAttack(Mob* other, bool CanDoubleAttack) { //old was 51 @@ -1233,7 +1304,7 @@ void Client::ThrowingAttack(Mob* other, bool CanDoubleAttack) { //old was 51 int ammo_slot = EQ::invslot::slotRange; const EQ::ItemInstance* RangeWeapon = m_inv[EQ::invslot::slotRange]; - + if (!RangeWeapon || !RangeWeapon->IsClassCommon()) { LogCombat("Ranged attack canceled. Missing or invalid ranged weapon ([{}]) in slot [{}]", GetItemIDAt(EQ::invslot::slotRange), EQ::invslot::slotRange); Message(0, "Error: Rangeweapon: GetItem(%i)==0, you have nothing to throw!", GetItemIDAt(EQ::invslot::slotRange)); @@ -1284,7 +1355,7 @@ void Client::ThrowingAttack(Mob* other, bool CanDoubleAttack) { //old was 51 } if(!IsAttackAllowed(other) || - IsCasting() || + (IsCasting() && GetClass() != BARD) || IsSitting() || (DivineAura() && !GetGM()) || IsStunned() || @@ -1294,25 +1365,31 @@ void Client::ThrowingAttack(Mob* other, bool CanDoubleAttack) { //old was 51 return; } - DoThrowingAttackDmg(other, RangeWeapon, item); + DoThrowingAttackDmg(other, RangeWeapon, item, 0, 0, 0, 0, 0,ammo_slot); + + // Consume Ammo, unless Ammo Consumption is disabled + if (RuleB(Combat, ThrowingConsumesAmmo)) { + DeleteItemInInventory(ammo_slot, 1, true); + LogCombat("Consumed Throwing Ammo from slot {}.", ammo_slot); + } else { + LogCombat("Throwing Ammo Consumption is disabled."); + } - //consume ammo - DeleteItemInInventory(ammo_slot, 1, true); CommonBreakInvisibleFromCombat(); } void Mob::DoThrowingAttackDmg(Mob *other, const EQ::ItemInstance *RangeWeapon, const EQ::ItemData *AmmoItem, - uint16 weapon_damage, int16 chance_mod, int16 focus, int ReuseTime, uint32 range_id, - int AmmoSlot, float speed) + uint16 weapon_damage, int16 chance_mod, int16 focus, int ReuseTime, uint32 range_id, + int AmmoSlot, float speed, bool DisableProcs) { if ((other == nullptr || - ((IsClient() && CastToClient()->dead) || (other->IsClient() && other->CastToClient()->dead)) || - HasDied() || (!IsAttackAllowed(other)) || (other->GetInvul() || other->GetSpecialAbility(IMMUNE_MELEE)))) { + ((IsClient() && CastToClient()->dead) || (other->IsClient() && other->CastToClient()->dead)) || + HasDied() || (!IsAttackAllowed(other)) || (other->GetInvul() || other->GetSpecialAbility(IMMUNE_MELEE)))) { return; } - const EQ::ItemInstance *_RangeWeapon = nullptr; - const EQ::ItemData *ammo_lost = nullptr; + const EQ::ItemInstance *m_RangeWeapon = nullptr;//throwing weapon + const EQ::ItemData *last_ammo_used = nullptr; /* If LaunchProjectile is false this function will do archery damage on target, @@ -1324,19 +1401,23 @@ void Mob::DoThrowingAttackDmg(Mob *other, const EQ::ItemInstance *RangeWeapon, c if (RuleB(Combat, ProjectileDmgOnImpact)) { if (AmmoItem) { LaunchProjectile = true; - } else { + } + else { if (!RangeWeapon && range_id) { if (IsClient()) { - _RangeWeapon = CastToClient()->m_inv[AmmoSlot]; - if (_RangeWeapon && _RangeWeapon->GetItem() && - _RangeWeapon->GetItem()->ID != range_id) - RangeWeapon = _RangeWeapon; - else - ammo_lost = database.GetItem(range_id); + m_RangeWeapon = CastToClient()->m_inv[AmmoSlot]; + + if (m_RangeWeapon && m_RangeWeapon->GetItem() && m_RangeWeapon->GetItem()->ID == range_id) { + RangeWeapon = m_RangeWeapon; + } + else { + last_ammo_used = database.GetItem(range_id); + } } } } - } else if (AmmoItem) { + } + else if (AmmoItem) { SendItemAnimation(other, AmmoItem, EQ::skills::SkillThrowing); } @@ -1345,22 +1426,26 @@ void Mob::DoThrowingAttackDmg(Mob *other, const EQ::ItemInstance *RangeWeapon, c int WDmg = 0; if (!weapon_damage) { - if (IsClient() && RangeWeapon) + if (IsClient() && RangeWeapon) { WDmg = GetWeaponDamage(other, RangeWeapon); - else if (AmmoItem) + } + else if (AmmoItem) { WDmg = GetWeaponDamage(other, AmmoItem); + } if (LaunchProjectile) { TryProjectileAttack(other, AmmoItem, EQ::skills::SkillThrowing, WDmg, RangeWeapon, - nullptr, AmmoSlot, speed); + nullptr, AmmoSlot, speed); return; } - } else { + } + else { WDmg = weapon_damage; } - if (focus) // From FcBaseEffects + if (focus) { // no longer used, keep for quests WDmg += WDmg * focus / 100; + } int TotalDmg = 0; @@ -1379,7 +1464,8 @@ void Mob::DoThrowingAttackDmg(Mob *other, const EQ::ItemInstance *RangeWeapon, c TotalDmg = my_hit.damage_done; LogCombat("Item DMG [{}]. Hit for damage [{}]", WDmg, TotalDmg); - } else { + } + else { TotalDmg = DMG_INVULNERABLE; } @@ -1388,29 +1474,32 @@ void Mob::DoThrowingAttackDmg(Mob *other, const EQ::ItemInstance *RangeWeapon, c other->Damage(this, TotalDmg, SPELL_UNKNOWN, EQ::skills::SkillThrowing); - if (TotalDmg > 0 && HasSkillProcSuccess() && other && !other->HasDied()) { - if (ReuseTime) - TrySkillProc(other, EQ::skills::SkillThrowing, ReuseTime); - else - TrySkillProc(other, EQ::skills::SkillThrowing, 0, true, EQ::invslot::slotRange); + if (!DisableProcs && other && !other->HasDied()) { + TryCombatProcs(RangeWeapon, other, EQ::invslot::slotRange, last_ammo_used); } - // end old shit - if (LaunchProjectile) - return; + TryCastOnSkillUse(other, EQ::skills::SkillThrowing); - // Throwing item Proc - if (ammo_lost) - TryWeaponProc(nullptr, ammo_lost, other, EQ::invslot::slotRange); - else if (RangeWeapon && other && !other->HasDied()) - TryWeaponProc(RangeWeapon, other, EQ::invslot::slotRange); + if (!DisableProcs) { + if (HasSkillProcs() && other && !other->HasDied()) { + if (ReuseTime) { + TrySkillProc(other, EQ::skills::SkillThrowing, ReuseTime); + } + else { + TrySkillProc(other, EQ::skills::SkillThrowing, 0, false, EQ::invslot::slotRange); + } + } - if (HasSkillProcs() && other && !other->HasDied()) { - if (ReuseTime) - TrySkillProc(other, EQ::skills::SkillThrowing, ReuseTime); - else - TrySkillProc(other, EQ::skills::SkillThrowing, 0, false, EQ::invslot::slotRange); + if (HasSkillProcSuccess() && other && !other->HasDied()) { + if (ReuseTime) { + TrySkillProc(other, EQ::skills::SkillThrowing, ReuseTime, true); + } + else { + TrySkillProc(other, EQ::skills::SkillThrowing, 0, true, EQ::invslot::slotRange); + } + } } + if (IsClient()) { CastToClient()->CheckIncreaseSkill(EQ::skills::SkillThrowing, GetTarget()); } @@ -1492,7 +1581,7 @@ void Mob::ProjectileAnimation(Mob* to, int item_id, bool IsArrow, float speed, f speed = 4.0; } if(!angle) { - angle = CalculateHeadingToTarget(to->GetX(), to->GetY()) * 2; + angle = CalculateHeadingToTarget(to->GetX(), to->GetY()); } if(!tilt) { tilt = 125; @@ -1539,6 +1628,8 @@ void NPC::DoClassAttacks(Mob *target) { bool ca_time = classattack_timer.Check(false); bool ka_time = knightattack_timer.Check(false); + const EQ::ItemData* boots = database.GetItem(equipment[EQ::invslot::slotFeet]); + //only check attack allowed if we are going to do something if((taunt_time || ca_time || ka_time) && !IsAttackAllowed(target)) return; @@ -1610,8 +1701,9 @@ void NPC::DoClassAttacks(Mob *target) { DoAnim(animKick, 0, false); int32 dmg = GetBaseSkillDamage(EQ::skills::SkillKick); - if (GetWeaponDamage(target, (const EQ::ItemData*)nullptr) <= 0) + if (GetWeaponDamage(target, boots) <= 0) { dmg = DMG_INVULNERABLE; + } reuse = (KickReuseTime + 3) * 1000; DoSpecialAttackDamage(target, EQ::skills::SkillKick, dmg, GetMinDamage(), -1, reuse); @@ -1660,7 +1752,7 @@ void NPC::DoClassAttacks(Mob *target) { DoAnim(animKick, 0, false); int32 dmg = GetBaseSkillDamage(EQ::skills::SkillKick); - if (GetWeaponDamage(target, (const EQ::ItemData*)nullptr) <= 0) + if (GetWeaponDamage(target, boots) <= 0) dmg = DMG_INVULNERABLE; reuse = (KickReuseTime + 3) * 1000; @@ -1791,7 +1883,7 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) if (skill_to_use == EQ::skills::SkillFrenzy) { CheckIncreaseSkill(EQ::skills::SkillFrenzy, GetTarget(), 10); int AtkRounds = 1; - DoAnim(anim2HSlashing, 0, false); + DoAnim(anim1HWeapon, 0, false); ReuseTime = (FrenzyReuseTime - 1) / HasteMod; @@ -1897,7 +1989,7 @@ void Mob::Taunt(NPC *who, bool always_succeed, int chance_bonus, bool FromSpell, Mob *hate_top = who->GetHateMost(); int level_difference = GetLevel() - who->GetLevel(); - bool Success = false; + bool success = false; // Support for how taunt worked pre 2000 on LIVE - Can not taunt NPC over your level. if ((RuleB(Combat, TauntOverLevel) == false) && (level_difference < 0) || @@ -1910,7 +2002,7 @@ void Mob::Taunt(NPC *who, bool always_succeed, int chance_bonus, bool FromSpell, if ((hate_top && hate_top->GetHPRatio() >= 20) || hate_top == nullptr || chance_bonus) { // SE_Taunt this is flat chance if (chance_bonus) { - Success = zone->random.Roll(chance_bonus); + success = zone->random.Roll(chance_bonus); } else { float tauntchance = 50.0f; @@ -1944,14 +2036,14 @@ void Mob::Taunt(NPC *who, bool always_succeed, int chance_bonus, bool FromSpell, tauntchance /= 100.0f; - Success = tauntchance > zone->random.Real(0, 1); + success = tauntchance > zone->random.Real(0, 1); } - if (Success) { + if (success) { if (hate_top && hate_top != this) { int newhate = (who->GetNPCHate(hate_top) - who->GetNPCHate(this)) + 1 + bonus_hate; who->CastToNPC()->AddToHateList(this, newhate); - Success = true; + success = true; } else { who->CastToNPC()->AddToHateList(this, 12); } @@ -1965,11 +2057,15 @@ void Mob::Taunt(NPC *who, bool always_succeed, int chance_bonus, bool FromSpell, MessageString(Chat::SpellFailure, FAILED_TAUNT); } - if (HasSkillProcs()) - TrySkillProc(who, EQ::skills::SkillTaunt, TauntReuseTime * 1000); + TryCastOnSkillUse(who, EQ::skills::SkillTaunt); - if (Success && HasSkillProcSuccess()) + if (HasSkillProcs()) { + TrySkillProc(who, EQ::skills::SkillTaunt, TauntReuseTime * 1000); + } + + if (success && HasSkillProcSuccess()) { TrySkillProc(who, EQ::skills::SkillTaunt, TauntReuseTime * 1000, true); + } } void Mob::InstillDoubt(Mob *who) { @@ -1993,6 +2089,7 @@ void Mob::InstillDoubt(Mob *who) { //I think this formula needs work int value = 0; + bool success = false; //user's bonus value += GetSkill(EQ::skills::SkillIntimidation) + GetCHA() / 4; @@ -2004,7 +2101,8 @@ void Mob::InstillDoubt(Mob *who) { //temporary hack... //cast fear on them... should prolly be a different spell //and should be un-resistable. - SpellOnTarget(229, who, false, true, -2000); + SpellOnTarget(229, who, 0, true, -2000); + success = true; //is there a success message? } else { MessageString(Chat::LightBlue,NOT_SCARING); @@ -2015,6 +2113,16 @@ void Mob::InstillDoubt(Mob *who) { entity_list.AEAttack(target, 50); }*/ } + + TryCastOnSkillUse(who, EQ::skills::SkillIntimidation); + + if (HasSkillProcs()) { + TrySkillProc(who, EQ::skills::SkillIntimidation, InstillDoubtReuseTime * 1000); + } + + if (success && HasSkillProcSuccess()) { + TrySkillProc(who, EQ::skills::SkillIntimidation, InstillDoubtReuseTime * 1000, true); + } } int Mob::TryHeadShot(Mob *defender, EQ::skills::SkillType skillInUse) @@ -2022,9 +2130,9 @@ int Mob::TryHeadShot(Mob *defender, EQ::skills::SkillType skillInUse) // Only works on YOUR target. if (defender && defender->GetBodyType() == BT_Humanoid && !defender->IsClient() && skillInUse == EQ::skills::SkillArchery && GetTarget() == defender) { - uint32 HeadShot_Dmg = aabonuses.HeadShot[1] + spellbonuses.HeadShot[1] + itembonuses.HeadShot[1]; + uint32 HeadShot_Dmg = aabonuses.HeadShot[SBIndex::FINISHING_EFFECT_DMG] + spellbonuses.HeadShot[SBIndex::FINISHING_EFFECT_DMG] + itembonuses.HeadShot[SBIndex::FINISHING_EFFECT_DMG]; uint8 HeadShot_Level = 0; // Get Highest Headshot Level - HeadShot_Level = std::max({aabonuses.HSLevel[0], spellbonuses.HSLevel[0], itembonuses.HSLevel[0]}); + HeadShot_Level = std::max({aabonuses.HSLevel[SBIndex::FINISHING_EFFECT_LEVEL_MAX], spellbonuses.HSLevel[SBIndex::FINISHING_EFFECT_LEVEL_MAX], itembonuses.HSLevel[SBIndex::FINISHING_EFFECT_LEVEL_MAX]}); if (HeadShot_Dmg && HeadShot_Level && (defender->GetLevel() <= HeadShot_Level)) { int chance = GetDEX(); @@ -2032,10 +2140,10 @@ int Mob::TryHeadShot(Mob *defender, EQ::skills::SkillType skillInUse) if (IsClient()) chance += CastToClient()->GetHeroicDEX() / 25; chance *= 10; - int norm = aabonuses.HSLevel[1]; + int norm = aabonuses.HSLevel[SBIndex::FINISHING_EFFECT_LEVEL_CHANCE_BONUS]; if (norm > 0) chance = chance * norm / 100; - chance += aabonuses.HeadShot[0] + spellbonuses.HeadShot[0] + itembonuses.HeadShot[0]; + chance += aabonuses.HeadShot[SBIndex::FINISHING_EFFECT_PROC_CHANCE] + spellbonuses.HeadShot[SBIndex::FINISHING_EFFECT_PROC_CHANCE] + itembonuses.HeadShot[SBIndex::FINISHING_EFFECT_PROC_CHANCE]; if (zone->random.Int(1, 1000) <= chance) { entity_list.MessageCloseString( this, false, 200, Chat::MeleeCrit, FATAL_BOW_SHOT, @@ -2058,7 +2166,7 @@ int Mob::TryAssassinate(Mob *defender, EQ::skills::SkillType skillInUse) if (IsClient()) chance += CastToClient()->GetHeroicDEX(); chance *= 10; - int norm = aabonuses.AssassinateLevel[1]; + int norm = aabonuses.AssassinateLevel[SBIndex::FINISHING_EFFECT_LEVEL_CHANCE_BONUS]; if (norm > 0) chance = chance * norm / 100; } else if (skillInUse == EQ::skills::SkillThrowing) { @@ -2068,14 +2176,14 @@ int Mob::TryAssassinate(Mob *defender, EQ::skills::SkillType skillInUse) chance += 5; } - chance += aabonuses.Assassinate[0] + spellbonuses.Assassinate[0] + itembonuses.Assassinate[0]; + chance += aabonuses.Assassinate[SBIndex::FINISHING_EFFECT_PROC_CHANCE] + spellbonuses.Assassinate[SBIndex::FINISHING_EFFECT_PROC_CHANCE] + itembonuses.Assassinate[SBIndex::FINISHING_EFFECT_PROC_CHANCE]; uint32 Assassinate_Dmg = - aabonuses.Assassinate[1] + spellbonuses.Assassinate[1] + itembonuses.Assassinate[1]; + aabonuses.Assassinate[SBIndex::FINISHING_EFFECT_DMG] + spellbonuses.Assassinate[SBIndex::FINISHING_EFFECT_DMG] + itembonuses.Assassinate[SBIndex::FINISHING_EFFECT_DMG]; uint8 Assassinate_Level = 0; // Get Highest Headshot Level Assassinate_Level = std::max( - {aabonuses.AssassinateLevel[0], spellbonuses.AssassinateLevel[0], itembonuses.AssassinateLevel[0]}); + {aabonuses.AssassinateLevel[SBIndex::FINISHING_EFFECT_LEVEL_MAX], spellbonuses.AssassinateLevel[SBIndex::FINISHING_EFFECT_LEVEL_MAX], itembonuses.AssassinateLevel[SBIndex::FINISHING_EFFECT_LEVEL_MAX]}); // revamped AAs require AA line I believe? if (!Assassinate_Level) @@ -2115,8 +2223,9 @@ void Mob::DoMeleeSkillAttackDmg(Mob *other, uint16 weapon_damage, EQ::skills::Sk hate = weapon_damage; if (weapon_damage > 0) { - if (focus) // From FcBaseEffects + if (focus) { weapon_damage += weapon_damage * focus / 100; + } if (skillinuse == EQ::skills::SkillBash) { if (IsClient()) { @@ -2152,31 +2261,17 @@ void Mob::DoMeleeSkillAttackDmg(Mob *other, uint16 weapon_damage, EQ::skills::Sk damage = DMG_INVULNERABLE; } - bool CanSkillProc = true; if (skillinuse == EQ::skills::SkillOffense) { // Hack to allow damage to display. skillinuse = EQ::skills::SkillTigerClaw; //'strike' your opponent - Arbitrary choice for message. - CanSkillProc = false; // Disable skill procs } other->AddToHateList(this, hate, 0); - if (damage > 0 && aabonuses.SkillAttackProc[0] && aabonuses.SkillAttackProc[1] == skillinuse && - IsValidSpell(aabonuses.SkillAttackProc[2])) { - float chance = aabonuses.SkillAttackProc[0] / 1000.0f; - if (zone->random.Roll(chance)) - SpellFinished(aabonuses.SkillAttackProc[2], other, EQ::spells::CastingSlot::Item, 0, -1, - spells[aabonuses.SkillAttackProc[2]].ResistDiff); - } - other->Damage(this, damage, SPELL_UNKNOWN, skillinuse); if (HasDied()) return; - if (CanSkillProc && HasSkillProcs()) - TrySkillProc(other, skillinuse, ReuseTime); - - if (CanSkillProc && (damage > 0) && HasSkillProcSuccess()) - TrySkillProc(other, skillinuse, ReuseTime, true); + TryCastOnSkillUse(other, skillinuse); } bool Mob::CanDoSpecialAttack(Mob *other) { diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index b6c2f9cf8..01520dc3e 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -46,7 +46,7 @@ extern WorldServer worldserver; // the spell can still fail here, if the buff can't stack // in this case false will be returned, true otherwise -bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_override) +bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_override, int reflect_effectiveness, int32 duration_override) { int caster_level, buffslot, effect, effect_value, i; EQ::ItemInstance *SummonedItem=nullptr; @@ -63,6 +63,11 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove if (spell.disallow_sit && IsBuffSpell(spell_id) && IsClient() && (CastToClient()->IsSitting() || CastToClient()->GetHorseId() != 0)) return false; + bool CanMemoryBlurFromMez = true; + if (IsMezzed()) { //Check for special memory blur behavior when on mez, this needs to be before buff override. + CanMemoryBlurFromMez = false; + } + bool c_override = false; if (caster && caster->IsClient() && GetCastedSpellInvSlot() > 0) { const EQ::ItemInstance *inst = caster->CastToClient()->GetInv().GetItem(GetCastedSpellInvSlot()); @@ -114,7 +119,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove } else { - buffslot = AddBuff(caster, spell_id); + buffslot = AddBuff(caster, spell_id, duration_override); } if(buffslot == -1) // stacking failure return false; @@ -135,18 +140,18 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove { buffs[buffslot].melee_rune = 0; buffs[buffslot].magic_rune = 0; - buffs[buffslot].numhits = 0; + buffs[buffslot].hit_number = 0; - if (spells[spell_id].numhits > 0) { + if (spells[spell_id].hit_number > 0) { - int numhit = spells[spell_id].numhits; + int numhit = spells[spell_id].hit_number; numhit += numhit * caster->GetFocusEffect(focusFcLimitUse, spell_id) / 100; numhit += caster->GetFocusEffect(focusIncreaseNumHits, spell_id); - buffs[buffslot].numhits = numhit; + buffs[buffslot].hit_number = numhit; } - if (spells[spell_id].EndurUpkeep > 0) + if (spells[spell_id].endurance_upkeep > 0) SetEndurUpkeep(true); if (IsClient() && CastToClient()->ClientVersionBit() & EQ::versions::maskUFAndLater) @@ -156,58 +161,50 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove } } - if(IsNPC()) - { - std::vector args; - args.push_back(&buffslot); - int i = parse->EventSpell(EVENT_SPELL_EFFECT_NPC, CastToNPC(), nullptr, spell_id, caster ? caster->GetID() : 0, &args); - if(i != 0){ + std::string buf = fmt::format( + "{} {} {} {}", + caster ? caster->GetID() : 0, + buffslot >= 0 ? buffs[buffslot].ticsremaining : 0, + caster ? caster->GetLevel() : 0, + buffslot + ); + + if (IsClient()) { + if (parse->EventSpell(EVENT_SPELL_EFFECT_CLIENT, nullptr, CastToClient(), spell_id, buf, 0) != 0) { CalcBonuses(); return true; } - } - else if(IsClient()) - { - std::vector args; - args.push_back(&buffslot); - int i = parse->EventSpell(EVENT_SPELL_EFFECT_CLIENT, nullptr, CastToClient(), spell_id, caster ? caster->GetID() : 0, &args); - if(i != 0){ + } else if (IsNPC()) { + if (parse->EventSpell(EVENT_SPELL_EFFECT_NPC, CastToNPC(), nullptr, spell_id, buf, 0) != 0) { CalcBonuses(); return true; } } - if(spells[spell_id].viral_targets > 0) { - if(!viral_timer.Enabled()) + if(IsVirusSpell(spell_id)) { + + if (!viral_timer.Enabled()) { viral_timer.Start(1000); - - has_virus = true; - for(int i = 0; i < MAX_SPELL_TRIGGER*2; i+=2) - { - if(!viral_spells[i]) - { - viral_spells[i] = spell_id; - viral_spells[i+1] = caster->GetID(); - break; - } } + buffs[buffslot].virus_spread_time = zone->random.Int(GetViralMinSpreadTime(spell_id), GetViralMaxSpreadTime(spell_id)); } if (!IsPowerDistModSpell(spell_id)) SetSpellPowerDistanceMod(0); - bool SE_SpellTrigger_HasCast = false; + bool spell_trigger_cast_complete = false; //Used with SE_Spell_Trigger and SE_Chance_Best_in_Spell_Grp, true when spell has been triggered. // if buff slot, use instrument mod there, otherwise calc it uint32 instrument_mod = buffslot > -1 ? buffs[buffslot].instrument_mod : caster ? caster->GetInstrumentMod(spell_id) : 10; + // iterate through the effects in the spell for (i = 0; i < EFFECT_COUNT; i++) { if(IsBlankSpellEffect(spell_id, i)) continue; - effect = spell.effectid[i]; + effect = spell.effect_id[i]; effect_value = CalcSpellEffectValue(spell_id, i, caster_level, instrument_mod, caster ? caster : this); if(spell_id == SPELL_LAY_ON_HANDS && caster && caster->GetAA(aaImprovedLayOnHands)) @@ -216,10 +213,19 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove if (GetSpellPowerDistanceMod()) effect_value = effect_value*GetSpellPowerDistanceMod()/100; + //Prevents effect from being applied + if (spellbonuses.NegateEffects) { + if (effect != SE_NegateSpellEffect && NegateSpellEffect(spell_id, effect)) { + if (caster) { + caster->Message(Chat::Red, "Part or all of this spell has lost its effectiveness."); //Placeholder msg, until live one is obtained. + } + continue; + } + } + #ifdef SPELL_EFFECT_SPAM effect_desc[0] = 0; #endif - switch(effect) { case SE_CurrentHP: // nukes, heals; also regen/dot if a buff @@ -231,31 +237,41 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove if (buffslot >= 0) break; + if (spells[spell_id].limit_value[i] && !PassCastRestriction(spells[spell_id].limit_value[i])) { + break; //no messages are given on live if this fails. + } + // for offensive spells check if we have a spell rune on int32 dmg = effect_value; if(dmg < 0) { - if (!PassCastRestriction(false, spells[spell_id].base2[i], true)) - break; // take partial damage into account dmg = (int32) (dmg * partial / 100); //handles AAs and what not... if(caster) { - dmg = caster->GetActSpellDamage(spell_id, dmg, this); + if (reflect_effectiveness) { + dmg = caster->GetActReflectedSpellDamage(spell_id, (int32)(spells[spell_id].base_value[i] * partial / 100), reflect_effectiveness); + } + else { + dmg = caster->GetActSpellDamage(spell_id, dmg, this); + } caster->ResourceTap(-dmg, spell_id); } - dmg = -dmg; - Damage(caster, dmg, spell_id, spell.skill, false, buffslot, false); + if (dmg <= 0) { + dmg = -dmg; + Damage(caster, dmg, spell_id, spell.skill, false, buffslot, false); + } + //handles custom situation where quest function mitigation put high enough to allow damage to heal. + else { + HealDamage(dmg, caster); + } } else if(dmg > 0) { //healing spell... - if (!PassCastRestriction(false, spells[spell_id].base2[i], false)) - break; - if(caster) dmg = caster->GetActSpellHealing(spell_id, dmg, this); @@ -275,11 +291,11 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove #endif int32 dmg = effect_value; - if (spell_id == 2751 && caster) //Manaburn + if (spell_id == SPELL_MANA_BURN && caster) //Manaburn { dmg = caster->GetMana()*-3; caster->SetMana(0); - } else if (spell_id == 2755 && caster) //Lifeburn + } else if (spell_id == SPELL_LIFE_BURN && caster) //Lifeburn { dmg = caster->GetHP(); // just your current HP caster->SetHP(dmg / 4); // 2003 patch notes say ~ 1/4 HP. Should this be 1/4 your current HP or do 3/4 max HP dmg? Can it kill you? @@ -289,32 +305,33 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove // hack fix for client health not reflecting server value last_hp = 0; + if (spells[spell_id].limit_value[i] && !PassCastRestriction(spells[spell_id].limit_value[i])) { + break; + } + //do any AAs apply to these spells? if(dmg < 0) { - if (!PassCastRestriction(false, spells[spell_id].base2[i], true)) - break; dmg = -dmg; Damage(caster, dmg, spell_id, spell.skill, false, buffslot, false); } else { - if (!PassCastRestriction(false, spells[spell_id].base2[i], false)) - break; HealDamage(dmg, caster); } break; } + case SE_PercentalHeal: { #ifdef SPELL_EFFECT_SPAM - snprintf(effect_desc, _EDLEN, "Percental Heal: %+i (%d%% max)", spell.max[i], effect_value); + snprintf(effect_desc, _EDLEN, "Percental Heal: %+i (%d%% max)", spell.max_value[i], effect_value); #endif - int32 val = GetMaxHP() * spell.base[i] / 100; + int32 val = GetMaxHP() * spell.base_value[i] / 100; //This effect can also do damage by percent. if (val < 0) { - if (spell.max[i] && -val > spell.max[i]) - val = -spell.max[i]; + if (spell.max_value[i] && -val > spell.max_value[i]) + val = -spell.max_value[i]; if (caster) val = caster->GetActSpellDamage(spell_id, val, this); @@ -323,8 +340,8 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove else { - if (spell.max[i] && val > spell.max[i]) - val = spell.max[i]; + if (spell.max_value[i] && val > spell.max_value[i]) + val = spell.max_value[i]; if(caster) val = caster->GetActSpellHealing(spell_id, val, this); @@ -343,31 +360,13 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Complete Heal"); #endif - //make sure they are not allready affected by this... - //I think that is the point of making this a buff. - //this is in the wrong spot, it should be in the immune - //section so the buff timer does not get refreshed! - - int i; - bool inuse = false; - int buff_count = GetMaxTotalSlots(); - for(i = 0; i < buff_count; i++) { - if(buffs[i].spellid == spell_id && i != buffslot) { - Message(0, "You must wait before you can be affected by this spell again."); - inuse = true; - break; - } - } - if(inuse) - break; - - int32 val = 0; - val = 7500 * effect_value; - if (caster) + int val = 7500 * effect_value; + if (caster) { val = caster->GetActSpellHealing(spell_id, val, this); - - if (val > 0) + } + if (val > 0) { HealDamage(val, caster); + } break; } @@ -387,7 +386,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove caster->SetMana(caster->GetMana() + std::abs(effect_value)); if (effect_value < 0) - TryTriggerOnValueAmount(false, true); + TryTriggerOnCastRequirement(); #ifdef SPELL_EFFECT_SPAM if (caster) caster->Message(Chat::White, "You have gained %+i mana!", effect_value); @@ -403,7 +402,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove SetMana(GetMana() + effect_value); if (effect_value < 0) - TryTriggerOnValueAmount(false, true); + TryTriggerOnCastRequirement(); } break; @@ -425,8 +424,8 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Translocate: %s %d %d %d heading %d", - spell.teleport_zone, spell.base[1], spell.base[0], - spell.base[2], spell.base[3] + spell.teleport_zone, spell.base_value[1], spell.base_value[0], + spell.base_value[2], spell.base_value[3] ); #endif if(IsClient()) @@ -445,10 +444,10 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove float x, y, z, heading; const char *target_zone = nullptr; - x = static_cast(spell.base[1]); - y = static_cast(spell.base[0]); - z = static_cast(spell.base[2]); - heading = static_cast(spell.base[3]); + x = static_cast(spell.base_value[1]); + y = static_cast(spell.base_value[0]); + z = static_cast(spell.base_value[2]); + heading = static_cast(spell.base_value[3]); if(!strcmp(spell.teleport_zone, "same")) { @@ -517,10 +516,10 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove float x, y, z, heading; const char *target_zone = nullptr; - x = static_cast(spell.base[1]); - y = static_cast(spell.base[0]); - z = static_cast(spell.base[2]); - heading = static_cast(spell.base[3]); + x = static_cast(spell.base_value[1]); + y = static_cast(spell.base_value[0]); + z = static_cast(spell.base_value[2]); + heading = static_cast(spell.base_value[3]); if(!strcmp(spell.teleport_zone, "same")) { @@ -544,7 +543,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove if (effect == SE_GateCastersBindpoint && caster && caster->IsClient()) { // Teleport Bind uses caster's bind point - int index = spells[spell_id].base[i] - 1; + int index = spells[spell_id].base_value[i] - 1; if (index < 0 || index > 4) index = 0; x = caster->CastToClient()->GetBindX(index); @@ -590,7 +589,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Invisibility"); #endif - SetInvisible(spell.base[i]); + SetInvisible(spell.base_value[i]); break; } @@ -600,6 +599,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove snprintf(effect_desc, _EDLEN, "Invisibility to Animals"); #endif invisible_animals = true; + SetInvisible(Invisibility::Special); break; } @@ -610,6 +610,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove snprintf(effect_desc, _EDLEN, "Invisibility to Undead"); #endif invisible_undead = true; + SetInvisible(Invisibility::Special); break; } case SE_SeeInvis: @@ -617,7 +618,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "See Invisible"); #endif - see_invis = spell.base[i]; + see_invis = spell.base_value[i]; break; } @@ -629,22 +630,21 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove if(IsClient()){ EQ::ItemInstance* transI = CastToClient()->GetInv().GetItem(EQ::invslot::slotCursor); if (transI && transI->IsClassCommon() && transI->IsStackable()){ - uint32 fcharges = transI->GetCharges(); - //Does it sound like meat... maybe should check if it looks like meat too... - if(strstr(transI->GetItem()->Name, "meat") || - strstr(transI->GetItem()->Name, "Meat") || - strstr(transI->GetItem()->Name, "flesh") || - strstr(transI->GetItem()->Name, "Flesh") || - strstr(transI->GetItem()->Name, "parts") || - strstr(transI->GetItem()->Name, "Parts")){ - CastToClient()->DeleteItemInInventory(EQ::invslot::slotCursor, fcharges, true); - CastToClient()->SummonItem(13073, fcharges); - } - else{ - Message(Chat::Red, "You can only transmute flesh to bone."); - } + int16 fcharges = transI->GetCharges(); + //Does it sound like meat... maybe should check if it looks like meat too... + if(strstr(transI->GetItem()->Name, "meat") || + strstr(transI->GetItem()->Name, "Meat") || + strstr(transI->GetItem()->Name, "flesh") || + strstr(transI->GetItem()->Name, "Flesh") || + strstr(transI->GetItem()->Name, "parts") || + strstr(transI->GetItem()->Name, "Parts")){ + CastToClient()->DeleteItemInInventory(EQ::invslot::slotCursor, fcharges, true); + CastToClient()->SummonItem(13073, fcharges); } - else{ + else{ + Message(Chat::Red, "You can only transmute flesh to bone."); + } + } else{ Message(Chat::Red, "You can only transmute flesh to bone."); } } @@ -657,7 +657,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove #endif //Added client messages to give some indication this effect is active. // Is there a message generated? Too disgusted by raids. - uint32 time = spell.base[i] * 10 * 1000; + uint32 time = spell.base_value[i] * 10 * 1000; if (caster && caster->IsClient()) { if (caster->IsGrouped()) { auto group = caster->GetGroup(); @@ -695,7 +695,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove #endif //Typically we check for immunities else where but since stun immunities are different and only //Block the stun part and not the whole spell, we do it here, also do the message here so we wont get the message on a resist - int max_level = spell.max[i]; + int max_level = spell.max_value[i]; //max_level of 0 means we assume a default of 55. if (max_level == 0) max_level = RuleI(Spells, BaseImmunityLevel); @@ -732,39 +732,21 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove case SE_Charm: { #ifdef SPELL_EFFECT_SPAM - snprintf(effect_desc, _EDLEN, "Charm: %+i (up to lvl %d)", effect_value, spell.max[i]); + snprintf(effect_desc, _EDLEN, "Charm: %+i (up to lvl %d)", effect_value, spell.max_value[i]); #endif - if (!caster) // can't be someone's pet unless we know who that someone is + if (!caster) { // can't be someone's pet unless we know who that someone is break; + } - if(IsNPC()) - { + if (IsNPC()) { CastToNPC()->SaveGuardSpotCharm(); } InterruptSpell(); entity_list.RemoveDebuffs(this); - entity_list.RemoveFromTargets(this); + entity_list.RemoveFromHateLists(this); WipeHateList(); - if (IsClient() && caster->IsClient()) { - caster->Message(Chat::White, "Unable to cast charm on a fellow player."); - BuffFadeByEffect(SE_Charm); - break; - } else if(IsCorpse()) { - caster->Message(Chat::White, "Unable to cast charm on a corpse."); - BuffFadeByEffect(SE_Charm); - break; - } else if(caster->GetPet() != nullptr && caster->IsClient()) { - caster->Message(Chat::White, "You cannot charm something when you already have a pet."); - BuffFadeByEffect(SE_Charm); - break; - } else if(GetOwner()) { - caster->Message(Chat::White, "You cannot charm someone else's pet!"); - BuffFadeByEffect(SE_Charm); - break; - } - Mob *my_pet = GetPet(); if(my_pet) { @@ -853,7 +835,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove MessageID = SENSE_ANIMAL; } - Mob *ClosestMob = entity_list.GetClosestMobByBodyType(this, bt); + Mob *ClosestMob = entity_list.GetClosestMobByBodyType(this, bt, true); if(ClosestMob) { @@ -940,7 +922,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove if(caster && caster->IsClient() && caster != this) caster->CastToClient()->QueuePacket(message_packet); - CastToClient()->SetBindPoint(spells[spell_id].base[i] - 1); + CastToClient()->SetBindPoint(spells[spell_id].base_value[i] - 1); Save(); safe_delete(action_packet); safe_delete(message_packet); @@ -991,7 +973,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove if(caster->IsClient() && caster != this) caster->CastToClient()->QueuePacket(message_packet); - CastToClient()->SetBindPoint(spells[spell_id].base[i] - 1); + CastToClient()->SetBindPoint(spells[spell_id].base_value[i] - 1); Save(); safe_delete(action_packet); safe_delete(message_packet); @@ -1029,7 +1011,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove if(caster->IsClient() && caster != this) caster->CastToClient()->QueuePacket(message_packet); - CastToClient()->SetBindPoint(spells[spell_id].base[i] - 1); + CastToClient()->SetBindPoint(spells[spell_id].base_value[i] - 1); Save(); safe_delete(action_packet); safe_delete(message_packet); @@ -1047,7 +1029,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove if(!spellbonuses.AntiGate){ if(zone->random.Roll(effect_value)) - Gate(spells[spell_id].base2[i] - 1); + Gate(spells[spell_id].limit_value[i] - 1); else if (caster) caster->MessageString(Chat::SpellFailure,GATE_FAIL); } @@ -1064,14 +1046,13 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove caster->MessageString(Chat::SpellFailure, SPELL_NO_EFFECT, spells[spell_id].name); break; } - int buff_count = GetMaxTotalSlots(); for(int slot = 0; slot < buff_count; slot++) { if( buffs[slot].spellid != SPELL_UNKNOWN && spells[buffs[slot].spellid].dispel_flag == 0 && !IsDiscipline(buffs[slot].spellid)) { - if (caster && TryDispel(caster->GetLevel(),buffs[slot].casterlevel, effect_value)){ + if (caster && TryDispel(caster->GetCasterLevel(spell_id), buffs[slot].casterlevel, effect_value)){ BuffFadeBySlot(slot); slot = buff_count; } @@ -1090,14 +1071,19 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove caster->MessageString(Chat::SpellFailure, SPELL_NO_EFFECT, spells[spell_id].name); break; } - + /* + TODO: Parsing shows there is no level modifier. However, a consistent -2% modifer was + found on spell with value 950 (95% spells would have 7% failure rates). + Further investigation is needed. ~ Kayen + */ + int chance = spells[spell_id].base_value[i]; int buff_count = GetMaxTotalSlots(); for(int slot = 0; slot < buff_count; slot++) { if (buffs[slot].spellid != SPELL_UNKNOWN && IsDetrimentalSpell(buffs[slot].spellid) && spells[buffs[slot].spellid].dispel_flag == 0) { - if (caster && TryDispel(caster->GetLevel(),buffs[slot].casterlevel, effect_value)){ + if (zone->random.Int(1, 1000) <= chance){ BuffFadeBySlot(slot); slot = buff_count; } @@ -1116,14 +1102,15 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove caster->MessageString(Chat::SpellFailure, SPELL_NO_EFFECT, spells[spell_id].name); break; } - + + int chance = spells[spell_id].base_value[i]; int buff_count = GetMaxTotalSlots(); for(int slot = 0; slot < buff_count; slot++) { if (buffs[slot].spellid != SPELL_UNKNOWN && IsBeneficialSpell(buffs[slot].spellid) && spells[buffs[slot].spellid].dispel_flag == 0) { - if (caster && TryDispel(caster->GetLevel(),buffs[slot].casterlevel, effect_value)){ + if (zone->random.Int(1, 1000) <= chance) { BuffFadeBySlot(slot); slot = buff_count; } @@ -1134,13 +1121,23 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove case SE_Purify: { - //Attempt to remove all Deterimental buffs. - int buff_count = GetMaxTotalSlots(); - for(int slot = 0; slot < buff_count; slot++) { - if (buffs[slot].spellid != SPELL_UNKNOWN && - IsDetrimentalSpell(buffs[slot].spellid) && spells[buffs[slot].spellid].dispel_flag == 0) - { - if (caster && TryDispel(caster->GetLevel(),buffs[slot].casterlevel, effect_value)){ + //Attempt to remove up to base amount of detrimental effects (excluding charm, fear, resurrection, and revival sickness). + int purify_count = spells[spell_id].base_value[i]; + if (purify_count > GetMaxTotalSlots()) { + purify_count = GetMaxTotalSlots(); + } + + for(int slot = 0; slot < purify_count; slot++) { + if (IsValidSpell(buffs[slot].spellid) && IsDetrimentalSpell(buffs[slot].spellid)){ + + if (!IsEffectInSpell(buffs[slot].spellid, SE_Charm) && + !IsEffectInSpell(buffs[slot].spellid, SE_Fear) && + buffs[slot].spellid != SPELL_RESURRECTION_SICKNESS && + buffs[slot].spellid != SPELL_RESURRECTION_SICKNESS2 && + buffs[slot].spellid != SPELL_RESURRECTION_SICKNESS3 && + buffs[slot].spellid != SPELL_RESURRECTION_SICKNESS4 && + buffs[slot].spellid != SPELL_REVIVAL_SICKNESS) + { BuffFadeBySlot(slot); } } @@ -1159,17 +1156,17 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove case SE_SummonItem: { - const EQ::ItemData *item = database.GetItem(spell.base[i]); + const EQ::ItemData *item = database.GetItem(spell.base_value[i]); #ifdef SPELL_EFFECT_SPAM const char *itemname = item ? item->Name : "*Unknown Item*"; - snprintf(effect_desc, _EDLEN, "Summon Item: %s (id %d)", itemname, spell.base[i]); + snprintf(effect_desc, _EDLEN, "Summon Item: %s (id %d)", itemname, spell.base_value[i]); #endif if (!item) { - Message(Chat::Red, "Unable to summon item %d. Item not found.", spell.base[i]); + Message(Chat::Red, "Unable to summon item %d. Item not found.", spell.base_value[i]); } else if (IsClient()) { Client *c = CastToClient(); if (c->CheckLoreConflict(item)) { - c->DuplicateLoreMessage(spell.base[i]); + c->DuplicateLoreMessage(spell.base_value[i]); } else { int charges; if (item->Stackable) @@ -1187,7 +1184,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove c->SendItemPacket(EQ::invslot::slotCursor, SummonedItem, ItemPacketLimbo); safe_delete(SummonedItem); } - SummonedItem = database.CreateItem(spell.base[i], charges); + SummonedItem = database.CreateItem(spell.base_value[i], charges); } } @@ -1195,10 +1192,10 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove } case SE_SummonItemIntoBag: { - const EQ::ItemData *item = database.GetItem(spell.base[i]); + const EQ::ItemData *item = database.GetItem(spell.base_value[i]); #ifdef SPELL_EFFECT_SPAM const char *itemname = item ? item->Name : "*Unknown Item*"; - snprintf(effect_desc, _EDLEN, "Summon Item In Bag: %s (id %d)", itemname, spell.base[i]); + snprintf(effect_desc, _EDLEN, "Summon Item In Bag: %s (id %d)", itemname, spell.base_value[i]); #endif uint8 slot; @@ -1210,7 +1207,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove caster->Message(Chat::Red, "SE_SummonItemIntoBag but no room in summoned bag!"); } else if (IsClient()) { if (CastToClient()->CheckLoreConflict(item)) { - CastToClient()->DuplicateLoreMessage(spell.base[i]); + CastToClient()->DuplicateLoreMessage(spell.base_value[i]); } else { int charges; @@ -1224,7 +1221,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove if (charges < 1) charges = 1; - EQ::ItemInstance *SubItem = database.CreateItem(spell.base[i], charges); + EQ::ItemInstance *SubItem = database.CreateItem(spell.base_value[i], charges); if (SubItem != nullptr) { SummonedItem->PutItem(slot, *SubItem); safe_delete(SubItem); @@ -1280,7 +1277,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove snprintf(effect_desc, _EDLEN, "Invulnerability"); #endif if(spell_id==4789) // Touch of the Divine - Divine Save - buffs[buffslot].ticsremaining = spells[spell_id].buffduration; // Prevent focus/aa buff extension + buffs[buffslot].ticsremaining = spells[spell_id].buff_duration; // Prevent focus/aa buff extension SetInvul(true); break; @@ -1304,11 +1301,21 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Blind: %+i", effect_value); #endif - // this should catch the cures - if (BeneficialSpell(spell_id) && spells[spell_id].buffduration == 0) - BuffFadeByEffect(SE_Blind); - else if (!IsClient()) + // 'cure blind' + if (BeneficialSpell(spell_id) && spells[spell_id].buff_duration == 0) { + int buff_count = GetMaxBuffSlots(); + for (int slot = 0; slot < buff_count; slot++) { + if (buffs[slot].spellid != SPELL_UNKNOWN && IsEffectInSpell(buffs[slot].spellid, SE_Blind)) { + if (caster && TryDispel(caster->GetCasterLevel(spell_id), buffs[slot].casterlevel, 1)) { + BuffFadeBySlot(slot); + slot = buff_count; + } + } + } + } + else if (!IsClient()) { CalculateNewFearpoint(); + } break; } @@ -1317,9 +1324,6 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Melee Absorb Rune: %+i", effect_value); #endif - if (caster) - effect_value = caster->ApplySpellEffectiveness(spell_id, effect_value); - buffs[buffslot].melee_rune = effect_value; break; } @@ -1337,31 +1341,31 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove case SE_MitigateMeleeDamage: { - buffs[buffslot].melee_rune = spells[spell_id].max[i]; + buffs[buffslot].melee_rune = spells[spell_id].max_value[i]; break; } case SE_MeleeThresholdGuard: { - buffs[buffslot].melee_rune = spells[spell_id].max[i]; + buffs[buffslot].melee_rune = spells[spell_id].max_value[i]; break; } case SE_SpellThresholdGuard: { - buffs[buffslot].magic_rune = spells[spell_id].max[i]; + buffs[buffslot].magic_rune = spells[spell_id].max_value[i]; break; } case SE_MitigateSpellDamage: { - buffs[buffslot].magic_rune = spells[spell_id].max[i]; + buffs[buffslot].magic_rune = spells[spell_id].max_value[i]; break; } case SE_MitigateDotDamage: { - buffs[buffslot].dot_rune = spells[spell_id].max[i]; + buffs[buffslot].dot_rune = spells[spell_id].max_value[i]; break; } @@ -1380,7 +1384,12 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove #endif //this sends the levitate packet to everybody else //who does not otherwise receive the buff packet. - SendAppearancePacket(AT_Levitate, 2, true, true); + if (spells[spell_id].limit_value[i] == 1) { + SendAppearancePacket(AT_Levitate, EQ::constants::GravityBehavior::LevitateWhileRunning, true, true); + } + else { + SendAppearancePacket(AT_Levitate, EQ::constants::GravityBehavior::Levitating, true, true); + } break; } @@ -1403,88 +1412,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Illusion: race %d", effect_value); #endif - // Gender Illusions - if(spell.base[i] == -1) { - // Specific Gender Illusions - if(spell_id == 1732 || spell_id == 1731) { - int specific_gender = -1; - // Male - if(spell_id == 1732) - specific_gender = 0; - // Female - else if (spell_id == 1731) - specific_gender = 1; - if(specific_gender > -1) { - if(caster && caster->GetTarget()) { - SendIllusionPacket - ( - caster->GetTarget()->GetBaseRace(), - specific_gender, - caster->GetTarget()->GetTexture() - ); - } - } - } - // Change Gender Illusions - else { - if(caster && caster->GetTarget()) { - int opposite_gender = 0; - if(caster->GetTarget()->GetGender() == 0) - opposite_gender = 1; - - SendIllusionPacket - ( - caster->GetTarget()->GetRace(), - opposite_gender, - caster->GetTarget()->GetTexture() - ); - } - } - } - // Racial Illusions - else { - SendIllusionPacket - ( - spell.base[i], - Mob::GetDefaultGender(spell.base[i], GetGender()), - spell.base2[i], - spell.max[i] - ); - if(spell.base[i] == OGRE){ - SendAppearancePacket(AT_Size, 9); - } - else if(spell.base[i] == TROLL){ - SendAppearancePacket(AT_Size, 8); - } - else if(spell.base[i] == VAHSHIR || spell.base[i] == BARBARIAN){ - SendAppearancePacket(AT_Size, 7); - } - else if(spell.base[i] == HALF_ELF || spell.base[i] == WOOD_ELF || spell.base[i] == DARK_ELF || spell.base[i] == FROGLOK){ - SendAppearancePacket(AT_Size, 5); - } - else if(spell.base[i] == DWARF){ - SendAppearancePacket(AT_Size, 4); - } - else if(spell.base[i] == HALFLING || spell.base[i] == GNOME){ - SendAppearancePacket(AT_Size, 3); - } - else if(spell.base[i] == WOLF) { - SendAppearancePacket(AT_Size, 2); - } - else{ - SendAppearancePacket(AT_Size, 6); - } - } - - for (int x = EQ::textures::textureBegin; x <= EQ::textures::LastTintableTexture; x++) - SendWearChange(x); - - if (caster == this && spell.id != 287 && spell.id != 601 && - (spellbonuses.IllusionPersistence || aabonuses.IllusionPersistence || - itembonuses.IllusionPersistence)) - buffs[buffslot].persistant_buff = 1; - else - buffs[buffslot].persistant_buff = 0; + ApplySpellEffectIllusion(spell_id, caster, buffslot, spells[spell_id].base_value[i], spells[spell_id].limit_value[i], spells[spell_id].max_value[i]); break; } @@ -1512,16 +1440,16 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Memory Blur: %d", effect_value); #endif - int wipechance = spells[spell_id].base[i]; - int bonus = 0; - - if (caster){ - bonus = caster->spellbonuses.IncreaseChanceMemwipe + - caster->itembonuses.IncreaseChanceMemwipe + - caster->aabonuses.IncreaseChanceMemwipe; + //Memory blur component of Mez spells is not checked again if Mez is recast on a target that is already mezed + if (!CanMemoryBlurFromMez && IsEffectInSpell(spell_id, SE_Mez)) { + break; } - wipechance += wipechance*bonus/100; + int wipechance = 0; + + if (caster) { + wipechance = caster->GetMemoryBlurChance(effect_value); + } if(zone->random.Roll(wipechance)) { @@ -1540,7 +1468,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove snprintf(effect_desc, _EDLEN, "Spin: %d", effect_value); #endif // the spinning is handled by the client - int max_level = spells[spell_id].max[i]; + int max_level = spells[spell_id].max_value[i]; if(max_level == 0) max_level = RuleI(Spells, BaseImmunityLevel); // Default max is 55 level limit @@ -1635,18 +1563,17 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Feign Death"); #endif - //todo, look up spell ID in DB - if(spell_id == 2488) //Dook- Lifeburn fix + if(spell_id == SPELL_LIFEBURN) //Dook- Lifeburn fix break; if(IsClient()) { CastToClient()->SetHorseId(0); // dismount if have horse - if (zone->random.Int(0, 99) > spells[spell_id].base[i]) { - CastToClient()->SetFeigned(false); + if (zone->random.Int(0, 99) > spells[spell_id].base_value[i]) { + SetFeigned(false); entity_list.MessageCloseString(this, false, 200, 10, STRING_FEIGNFAILED, GetName()); } else { - CastToClient()->SetFeigned(true); + SetFeigned(true); } } break; @@ -1709,16 +1636,23 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Model Size: %d%%", effect_value); #endif - // Only allow 2 size changes from Base Size - float modifyAmount = (static_cast(effect_value) / 100.0f); - float maxModAmount = GetBaseSize() * modifyAmount * modifyAmount; - if ((GetSize() <= GetBaseSize() && GetSize() > maxModAmount) || - (GetSize() >= GetBaseSize() && GetSize() < maxModAmount) || - (GetSize() <= GetBaseSize() && maxModAmount > 1.0f) || - (GetSize() >= GetBaseSize() && maxModAmount < 1.0f)) - { - ChangeSize(GetSize() * modifyAmount); + if (effect_value && effect_value != 100) { + // Only allow 2 size changes from Base Size + float modifyAmount = (static_cast(effect_value) / 100.0f); + float maxModAmount = GetBaseSize() * modifyAmount * modifyAmount; + if ((GetSize() <= GetBaseSize() && GetSize() > maxModAmount) || + (GetSize() >= GetBaseSize() && GetSize() < maxModAmount) || + (GetSize() <= GetBaseSize() && maxModAmount > 1.0f) || + (GetSize() >= GetBaseSize() && maxModAmount < 1.0f)) + { + ChangeSize(GetSize() * modifyAmount); + } } + //Only applies to SPA 89, max value also likely does something, but unknown. + else if (effect == SE_ModelSize && spells[spell_id].limit_value[i]) { + ChangeSize(spells[spell_id].limit_value[i]); + } + break; } @@ -1841,7 +1775,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove Group* group = client_target->GetGroup(); if (!group->IsGroupMember(caster)) { if (caster != this) { - caster->MessageString(Chat::Red, SUMMON_ONLY_GROUP_CORPSE); + caster->MessageString(Chat::Red, SUMMON_ONLY_GROUP_CORPSE); break; } } @@ -1862,7 +1796,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove } } } - + if (client_target) { if (database.CountCharacterCorpses(client_target->CharacterID()) == 0) { if (caster == this) { @@ -1892,11 +1826,27 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Weapon Proc: %s (id %d)", spells[effect_value].name, procid); #endif + AddProcToWeapon(procid, false, 100 + spells[spell_id].limit_value[i], spell_id, caster_level, GetProcLimitTimer(spell_id, ProcType::MELEE_PROC)); + break; + } - if(spells[spell_id].base2[i] == 0) - AddProcToWeapon(procid, false, 100, spell_id, caster_level); - else - AddProcToWeapon(procid, false, spells[spell_id].base2[i]+100, spell_id, caster_level); + case SE_RangedProc: + { + uint16 procid = GetProcID(spell_id, i); +#ifdef SPELL_EFFECT_SPAM + snprintf(effect_desc, _EDLEN, "Ranged Proc: %+i", effect_value); +#endif + AddRangedProc(procid, 100 + spells[spell_id].limit_value[i], spell_id, GetProcLimitTimer(spell_id, ProcType::RANGED_PROC)); + break; + } + + case SE_DefensiveProc: + { + uint16 procid = GetProcID(spell_id, i); +#ifdef SPELL_EFFECT_SPAM + snprintf(effect_desc, _EDLEN, "Defensive Proc: %s (id %d)", spells[effect_value].name, procid); +#endif + AddDefensiveProc(procid, 100 + spells[spell_id].limit_value[i], spell_id, GetProcLimitTimer(spell_id, ProcType::DEFENSIVE_PROC)); break; } @@ -1906,15 +1856,15 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove snprintf(effect_desc, _EDLEN, "Melee Negate Attack Rune: %+i", effect_value); #endif if(buffslot >= 0) - buffs[buffslot].numhits = effect_value; + buffs[buffslot].hit_number = effect_value; break; } case SE_AppraiseLDonChest: { if(IsNPC()) { - int check = spell.max[0]; - int target = spell.targettype; + int check = spell.max_value[0]; + int target = spell.target_type; if(target == ST_LDoNChest_Cursed) { if(caster && caster->IsClient()) @@ -1937,8 +1887,8 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove { if(IsNPC()) { - int check = spell.max[0]; - int target = spell.targettype; + int check = spell.max_value[0]; + int target = spell.target_type; if(target == ST_LDoNChest_Cursed) { if(caster && caster->IsClient()) @@ -1961,8 +1911,8 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove { if(IsNPC()) { - int check = spell.max[0]; - int target = spell.targettype; + int check = spell.max_value[0]; + int target = spell.target_type; if(target == ST_LDoNChest_Cursed) { if(caster && caster->IsClient()) @@ -2219,9 +2169,12 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove snprintf(effect_desc, _EDLEN, "Call Pet"); #endif // this is cast on self, not on the pet - if(GetPet() && GetPet()->IsNPC()) - { - GetPet()->CastToNPC()->GMMove(GetX(), GetY(), GetZ(), GetHeading()); + Mob *casters_pet = GetPet(); + if(casters_pet && casters_pet->IsNPC()){ + casters_pet->CastToNPC()->GMMove(GetX(), GetY(), GetZ(), GetHeading()); + if (!casters_pet->GetTarget()) { + casters_pet->StopNavigation(); + } } break; } @@ -2255,30 +2208,47 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Fading Memories"); #endif - if(zone->random.Roll(spells[spell_id].base[i])) { + int max_level = 0; - if(caster && caster->IsClient()) - caster->CastToClient()->Escape(); - else - { - entity_list.RemoveFromTargets(caster); - SetInvisible(1); + if (RuleB(Spells, UseFadingMemoriesMaxLevel)) { + //handle ROF2 era where limit value determines max level + if (spells[spell_id].limit_value[i]) { + max_level = spells[spell_id].limit_value[i]; + } + //handle modern client era where max value determines max level or range above client. + else if (spells[spell_id].max_value[i]) { + if (spells[spell_id].max_value[i] >= 1000) { + max_level = 1000 - spells[spell_id].max_value[i]; + } + else { + max_level = GetLevel() + spells[spell_id].max_value[i]; + } } } - break; - } - case SE_RangedProc: - { -#ifdef SPELL_EFFECT_SPAM - snprintf(effect_desc, _EDLEN, "Ranged Proc: %+i", effect_value); -#endif - uint16 procid = GetProcID(spell_id, i); - - if(spells[spell_id].base2[i] == 0) - AddRangedProc(procid, 100, spell_id); - else - AddRangedProc(procid, spells[spell_id].base2[i]+100, spell_id); + if(zone->random.Roll(spells[spell_id].base_value[i])) { + if (IsClient()) { + int pre_aggro_count = CastToClient()->GetAggroCount(); + entity_list.RemoveFromTargetsFadingMemories(this, true, max_level); + SetInvisible(Invisibility::Invisible); + int post_aggro_count = CastToClient()->GetAggroCount(); + if (RuleB(Spells, UseFadingMemoriesMaxLevel)) { + if (pre_aggro_count == post_aggro_count) { + Message(Chat::SpellFailure, "You failed to escape from all your opponents."); + break; + } + else if (post_aggro_count) { + Message(Chat::SpellFailure, "You failed to escape from combat but you evade some of your opponents."); + break; + } + } + MessageString(Chat::Skills, ESCAPE); + } + else{ + entity_list.RemoveFromTargets(caster); + SetInvisible(Invisibility::Invisible); + } + } break; } @@ -2287,9 +2257,16 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Rampage"); #endif - if(caster) - entity_list.AEAttack(caster, 30, EQ::invslot::slotPrimary, 0, true); // on live wars dont get a duration ramp, its a one shot deal - + //defulat live range is 40, with 1 attack per round, no hit count limit + float rampage_range = 40; + if (spells[spell_id].aoe_range) { + rampage_range = spells[spell_id].aoe_range; //added for expanded functionality + } + int attack_count = spells[spell_id].base_value[i]; //added for expanded functionality + int hit_count = spells[spell_id].limit_value[i]; //added for expanded functionality + if (caster) { + entity_list.AEAttack(caster, rampage_range, EQ::invslot::slotPrimary, hit_count, true, attack_count); // on live wars dont get a duration ramp, its a one shot deal + } break; } @@ -2305,17 +2282,17 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove } case SE_AETaunt: - { + { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "AE Taunt"); #endif if(caster && caster->IsClient()){ //Live AE Taunt range is hardcoded at 40 (Spells for AE taunt all use zero range) Target type should be self only. float range = 40; - if (spells[spell_id].max[i])//custom support if you want to alter range of AE Taunt. - range = spells[spell_id].max[i]; + if (spells[spell_id].max_value[i])//custom support if you want to alter range of AE Taunt. + range = spells[spell_id].max_value[i]; - entity_list.AETaunt(caster->CastToClient(), range, spells[spell_id].base[i]); + entity_list.AETaunt(caster->CastToClient(), range, spells[spell_id].base_value[i]); } break; } @@ -2331,22 +2308,20 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove ???? = spells[spell_id].max[i] - MOST of the effects have this value. *Max is lower value then Weapon base, possibly min hit vs Weapon Damage range ie. MakeRandInt(max,base) */ - int16 focus = 0; int ReuseTime = spells[spell_id].recast_time + spells[spell_id].recovery_time; - if (!caster) + if (!caster) { break; - - focus = caster->GetFocusEffect(focusFcBaseEffects, spell_id); + } switch(spells[spell_id].skill) { case EQ::skills::SkillThrowing: - caster->DoThrowingAttackDmg(this, nullptr, nullptr, spells[spell_id].base[i],spells[spell_id].base2[i], focus, ReuseTime); + caster->DoThrowingAttackDmg(this, nullptr, nullptr, spells[spell_id].base_value[i],spells[spell_id].limit_value[i], 0, ReuseTime, 0, 0, 4.0f, true); break; case EQ::skills::SkillArchery: - caster->DoArcheryAttackDmg(this, nullptr, nullptr, spells[spell_id].base[i],spells[spell_id].base2[i],focus, ReuseTime); + caster->DoArcheryAttackDmg(this, nullptr, nullptr, spells[spell_id].base_value[i],spells[spell_id].limit_value[i], 0, ReuseTime, 0, 0, nullptr, 0, 4.0f, true); break; default: - caster->DoMeleeSkillAttackDmg(this, spells[spell_id].base[i], spells[spell_id].skill, spells[spell_id].base2[i], focus, false, ReuseTime); + caster->DoMeleeSkillAttackDmg(this, spells[spell_id].base_value[i], spells[spell_id].skill, spells[spell_id].limit_value[i], 0, false, ReuseTime); break; } break; @@ -2359,11 +2334,36 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove #endif //meh dupe issue with npc casting this if(caster && caster->IsClient()){ - int dur = spells[spell_id].max[i]; - if (!dur) + int dur = spells[spell_id].max_value[i]; + if (!dur) { dur = 60; + } - caster->WakeTheDead(spell_id, caster->GetTarget(), dur); + Mob* m_target = caster->GetTarget(); + if (m_target) { + entity_list.TryWakeTheDead(caster, m_target, spell_id, 250, dur, 1); + } + } + break; + } + + case SE_ArmyOfTheDead: + { + if (caster && caster->IsClient()) { + int dur = spells[spell_id].max_value[i]; + if (!dur) { + dur = 60; + } + + int amount = spells[spell_id].base_value[i]; + if (!amount) { + amount = 1; + } + + Mob* m_target = caster->GetTarget(); + if (m_target) { + entity_list.TryWakeTheDead(caster, m_target, spell_id, 250, dur, amount); + } } break; } @@ -2373,28 +2373,13 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove if(caster && caster->IsClient()) { char pet_name[64]; snprintf(pet_name, sizeof(pet_name), "%s`s doppelganger", caster->GetCleanName()); - int pet_count = spells[spell_id].base[i]; - int pet_duration = spells[spell_id].max[i]; + int pet_count = spells[spell_id].base_value[i]; + int pet_duration = spells[spell_id].max_value[i]; caster->CastToClient()->Doppelganger(spell_id, this, pet_name, pet_count, pet_duration); } break; } - case SE_DefensiveProc: - { - uint16 procid = GetProcID(spell_id, i); -#ifdef SPELL_EFFECT_SPAM - snprintf(effect_desc, _EDLEN, "Defensive Proc: %s (id %d)", spells[effect_value].name, procid); -#endif - if(spells[spell_id].base2[i] == 0) - AddDefensiveProc(procid, 100,spell_id); - else - AddDefensiveProc(procid, spells[spell_id].base2[i]+100,spell_id); - break; - - break; - } - case SE_BardAEDot: { #ifdef SPELL_EFFECT_SPAM @@ -2439,8 +2424,9 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove #endif if(IsClient()) { CastToClient()->SetEndurance(CastToClient()->GetEndurance() + effect_value); - if (effect_value < 0) - TryTriggerOnValueAmount(false, false, true); + if (effect_value < 0) { + TryTriggerOnCastRequirement(); + } } break; } @@ -2451,10 +2437,11 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove snprintf(effect_desc, _EDLEN, "Current Endurance Once: %+i", effect_value); #endif - if(IsClient()) { + if (IsClient()) { CastToClient()->SetEndurance(CastToClient()->GetEndurance() + effect_value); - if (effect_value < 0) - TryTriggerOnValueAmount(false, false, true); + if (effect_value < 0) { + TryTriggerOnCastRequirement(); + } } break; } @@ -2473,7 +2460,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove gid = r->GetGroup(caster->GetName()); if(gid < 11) { - r->BalanceHP(spell.base[i], gid, spell.range, caster, spell.base2[i]); + r->BalanceHP(spell.base_value[i], gid, spell.range, caster, spell.limit_value[i]); break; } } @@ -2483,7 +2470,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove if(!g) break; - g->BalanceHP(spell.base[i], spell.range, caster, spell.base2[i]); + g->BalanceHP(spell.base_value[i], spell.range, caster, spell.limit_value[i]); break; } @@ -2501,7 +2488,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove gid = r->GetGroup(caster->GetName()); if(gid < 11) { - r->BalanceMana(spell.base[i], gid, spell.range, caster, spell.base2[i]); + r->BalanceMana(spell.base_value[i], gid, spell.range, caster, spell.limit_value[i]); break; } } @@ -2511,7 +2498,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove if(!g) break; - g->BalanceMana(spell.base[i], spell.range, caster, spell.base2[i]); + g->BalanceMana(spell.base_value[i], spell.range, caster, spell.limit_value[i]); break; } @@ -2530,11 +2517,10 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_SuspendMinion: case SE_SuspendPet: { if(IsClient()) - CastToClient()->SuspendMinion(); + CastToClient()->SuspendMinion(spell.base_value[i]); break; } @@ -2547,6 +2533,38 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove if (CalcFocusEffect(focusFcTimerRefresh, spell_id, CastToClient()->m_pp.mem_spells[i])){ CastToClient()->m_pp.spellSlotRefresh[i] = 1; CastToClient()->GetPTimers().Clear(&database, (pTimerSpellStart + CastToClient()->m_pp.mem_spells[i])); + if (!CastToClient()->IsLinkedSpellReuseTimerReady(spells[CastToClient()->m_pp.mem_spells[i]].timer_id)) { + CastToClient()->GetPTimers().Clear(&database, (pTimerLinkedSpellReuseStart + spells[CastToClient()->m_pp.mem_spells[i]].timer_id)); + } + } + } + } + SetMana(GetMana()); + } + break; + } + + case SE_FcTimerLockout: { + if (IsClient()) { + for (unsigned int mem_spell : CastToClient()->m_pp.mem_spells) { + if (IsValidSpell(mem_spell)) { + int32 new_recast_timer = CalcFocusEffect( + focusFcTimerLockout, + spell_id, + mem_spell + ); + if (new_recast_timer) { + bool apply_recast_timer = true; + if (IsCasting() && casting_spell_id == mem_spell) { + apply_recast_timer = false; + } + if (apply_recast_timer) { + new_recast_timer = new_recast_timer / 1000; + CastToClient()->GetPTimers().Start( + pTimerSpellStart + mem_spell, + static_cast(new_recast_timer) + ); + } } } } @@ -2562,8 +2580,8 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove if(!caster->IsClient()) break; - int32 max_mana = spell.base[i]; - int ratio = spell.base2[i]; + int32 max_mana = spell.base_value[i]; + int ratio = spell.limit_value[i]; uint32 heal_amt = 0; if (caster->GetMana() <= max_mana){ @@ -2602,16 +2620,16 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove case SE_ManaDrainWithDmg: { int mana_damage = 0; - int32 mana_to_use = GetMana() - spell.base[i]; + int32 mana_to_use = GetMana() - spell.base_value[i]; if(mana_to_use > -1) { - SetMana(GetMana() - spell.base[i]); - TryTriggerOnValueAmount(false, true); + SetMana(GetMana() - spell.base_value[i]); + TryTriggerOnCastRequirement(); // we take full dmg(-10 to make the damage the right sign) - mana_damage = spell.base[i] / -10 * spell.base2[i]; + mana_damage = spell.base_value[i] / -10 * spell.limit_value[i]; Damage(caster, mana_damage, spell_id, spell.skill, false, i, true); } else { - mana_damage = GetMana() / -10 * spell.base2[i]; + mana_damage = GetMana() / -10 * spell.limit_value[i]; SetMana(0); Damage(caster, mana_damage, spell_id, spell.skill, false, i, true); } @@ -2622,16 +2640,16 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove { if(IsClient()) { int end_damage = 0; - int32 end_to_use = CastToClient()->GetEndurance() - spell.base[i]; + int32 end_to_use = CastToClient()->GetEndurance() - spell.base_value[i]; if(end_to_use > -1) { - CastToClient()->SetEndurance(CastToClient()->GetEndurance() - spell.base[i]); - TryTriggerOnValueAmount(false, false, true); + CastToClient()->SetEndurance(CastToClient()->GetEndurance() - spell.base_value[i]); + TryTriggerOnCastRequirement(); // we take full dmg(-10 to make the damage the right sign) - end_damage = spell.base[i] / -10 * spell.base2[i]; + end_damage = spell.base_value[i] / -10 * spell.limit_value[i]; Damage(caster, end_damage, spell_id, spell.skill, false, i, true); } else { - end_damage = CastToClient()->GetEndurance() / -10 * spell.base2[i]; + end_damage = CastToClient()->GetEndurance() / -10 * spell.limit_value[i]; CastToClient()->SetEndurance(0); Damage(caster, end_damage, spell_id, spell.skill, false, i, true); } @@ -2641,7 +2659,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove case SE_SetBodyType: { - SetBodyType((bodyType)spell.base[i], false); + SetBodyType((bodyType)spell.base_value[i], false); break; } @@ -2662,7 +2680,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove { float value, x_vector, y_vector, hypot; - value = (float)spell.base[i]; // distance away from target + value = (float)spell.base_value[i]; // distance away from target x_vector = target_x - my_x; y_vector = target_y - my_y; @@ -2694,8 +2712,8 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove case SE_ManaBurn: { - int32 max_mana = spell.base[i]; - int ratio = spell.base2[i]; + int32 max_mana = spell.base_value[i]; + int ratio = spell.limit_value[i]; int32 dmg = 0; if (caster){ @@ -2707,7 +2725,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove else { dmg = ratio*max_mana/10; caster->SetMana(caster->GetMana() - max_mana); - TryTriggerOnValueAmount(false, true); + TryTriggerOnCastRequirement(); } if(IsDetrimentalSpell(spell_id)) { @@ -2724,23 +2742,23 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove case SE_Taunt: { if (caster && IsNPC()){ - caster->Taunt(this->CastToNPC(), false, spell.base[i], true, spell.base2[i]); + caster->Taunt(this->CastToNPC(), false, spell.base_value[i], true, spell.limit_value[i]); } break; } case SE_AttackSpeed: - if (spell.base[i] < 100) + if (spell.base_value[i] < 100) SlowMitigation(caster); break; case SE_AttackSpeed2: - if (spell.base[i] < 100) + if (spell.base_value[i] < 100) SlowMitigation(caster); break; case SE_AttackSpeed3: - if (spell.base[i] < 0) + if (spell.base_value[i] < 0) SlowMitigation(caster); break; @@ -2751,7 +2769,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove case SE_AddHatePct: { if (IsNPC()){ - int32 new_hate = CastToNPC()->GetHateAmount(caster) * (100 + spell.base[i]) / 100; + int32 new_hate = CastToNPC()->GetHateAmount(caster) * (100 + spell.base_value[i]) / 100; if (new_hate <= 0) new_hate = 1; @@ -2788,7 +2806,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove if (buffslot >= 0) break; - if(!spells[spell_id].uninterruptable && IsCasting() && zone->random.Roll(spells[spell_id].base[i])) + if(!spells[spell_id].uninterruptable && IsCasting() && zone->random.Roll(spells[spell_id].base_value[i])) InterruptSpell(); break; @@ -2809,18 +2827,165 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove case SE_ApplyEffect: { - if (caster && IsValidSpell(spells[spell_id].base2[i])){ - if(zone->random.Roll(spells[spell_id].base[i])) - caster->SpellFinished(spells[spell_id].base2[i], this, EQ::spells::CastingSlot::Item, 0, -1, spells[spells[spell_id].base2[i]].ResistDiff); + if (caster && IsValidSpell(spells[spell_id].limit_value[i])){ + if(zone->random.Roll(spells[spell_id].base_value[i])) + caster->SpellFinished(spells[spell_id].limit_value[i], this, EQ::spells::CastingSlot::Item, 0, -1, spells[spells[spell_id].limit_value[i]].resist_difficulty); } break; } case SE_SpellTrigger: { - if (!SE_SpellTrigger_HasCast) { + if (!spell_trigger_cast_complete) { if (caster && caster->TrySpellTrigger(this, spell_id, i)) - SE_SpellTrigger_HasCast = true; + spell_trigger_cast_complete = true; + } + break; + } + + + case SE_Instant_Mana_Pct: { + effect_value = spells[spell_id].base_value[i]; + int32 amt = abs(GetMaxMana() * effect_value / 10000); + if (spells[spell_id].max_value[i] && amt > spells[spell_id].max_value[i]) + amt = spells[spell_id].max_value[i]; + + if (effect_value < 0) { + SetMana(GetMana() - amt); + } + else { + SetMana(GetMana() + amt); + } + break; + } + + case SE_Instant_Endurance_Pct: { + effect_value = spells[spell_id].base_value[i]; + if (IsClient()) { + int32 amt = abs(CastToClient()->GetMaxEndurance() * effect_value / 10000); + if (spells[spell_id].max_value[i] && amt > spells[spell_id].max_value[i]) + amt = spells[spell_id].max_value[i]; + + if (effect_value < 0) { + CastToClient()->SetEndurance(CastToClient()->GetEndurance() - amt); + } + else { + CastToClient()->SetEndurance(CastToClient()->GetEndurance() + amt); + } + } + break; + } + /* + Calc for base1 is found in ApplyHealthTransferDamage() due to needing to account for AOE functionality + since effect can potentially kill caster. + */ + case SE_Health_Transfer: { + effect_value = spells[spell_id].limit_value[i]; + int32 amt = abs(caster->GetMaxHP() * effect_value / 1000); + + if (effect_value < 0) { + Damage(caster, amt, spell_id, spell.skill, false, buffslot, false); + } + else { + HealDamage(amt, caster); + } + break; + } + + case SE_Chance_Best_in_Spell_Grp: { + if (!spell_trigger_cast_complete) { + if (caster && caster->TrySpellTrigger(this, spell_id, i)) + spell_trigger_cast_complete = true; + } + break; + } + + case SE_Trigger_Best_in_Spell_Grp: { + + if (caster && !caster->IsClient()) + break; + + if (zone->random.Roll(spells[spell_id].base_value[i])) { + uint32 best_spell_id = caster->CastToClient()->GetHighestScribedSpellinSpellGroup(spells[spell_id].limit_value[i]); + + if (caster && IsValidSpell(best_spell_id)) + caster->SpellFinished(best_spell_id, this, EQ::spells::CastingSlot::Item, 0, -1, spells[best_spell_id].resist_difficulty); + } + break; + } + + case SE_Trigger_Spell_Non_Item: { + //Only trigger if not from item + if (caster && caster->IsClient() && GetCastedSpellInvSlot() > 0) + break; + + if (zone->random.Roll(spells[spell_id].base_value[i]) && IsValidSpell(spells[spell_id].limit_value[i])) + caster->SpellFinished(spells[spell_id].limit_value[i], this, EQ::spells::CastingSlot::Item, 0, -1, spells[spells[spell_id].limit_value[i]].resist_difficulty); + + break; + } + + case SE_Hatelist_To_Tail_Index: { + if (caster && zone->random.Roll(spells[spell_id].base_value[i])) + caster->SetBottomRampageList(); + break; + } + + case SE_Hatelist_To_Top_Index: { + if (caster && zone->random.Roll(spells[spell_id].base_value[i])) + caster->SetTopRampageList(); + break; + } + + case SE_Fearstun: { + //Normal 'stun' restrictions do not apply. base1=duration, base2=PC duration, max =lv restrict + if (!caster) + break; + + if (IsNPC() && GetSpecialAbility(UNSTUNABLE)) { + caster->MessageString(Chat::SpellFailure, IMMUNE_STUN); + break; + } + + if (IsNPC() && GetSpecialAbility(UNFEARABLE)) { + caster->MessageString(Chat::SpellFailure, IMMUNE_FEAR); + break; + } + int max_level = 0; + if (spells[spell_id].max_value[i] >= 1000) { + max_level = spells[spell_id].max_value[i] - 1000; + } + else { + max_level = caster->GetLevel() + spells[spell_id].max_value[i]; + } + + if (spells[spell_id].max_value[i] == 0 || GetLevel() <= max_level) { + if (IsClient() && spells[spell_id].limit_value[i]) + Stun(spells[spell_id].limit_value[i]); + else + Stun(spells[spell_id].base_value[i]); + } + else + caster->MessageString(Chat::SpellFailure, FEAR_TOO_HIGH); + break; + } + + case SE_PetShield: { + if (IsPet()) { + Mob* petowner = GetOwner(); + if (petowner) { + int shield_duration = spells[spell_id].base_value[i] * 12 * 1000; + int shield_target_mitigation = spells[spell_id].limit_value[i] ? spells[spell_id].limit_value[i] : 50; + int shielder_mitigation = spells[spell_id].max_value[i] ? spells[spell_id].limit_value[i] : 50; + ShieldAbility(petowner->GetID(), 25, shield_duration, shield_target_mitigation, shielder_mitigation); + break; + } + } + } + + case SE_Weapon_Stance: { + if (IsClient()) { + CastToClient()->ApplyWeaponsStance(); } break; } @@ -2846,6 +3011,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove case SE_StunResist: case SE_MinDamageModifier: case SE_DamageModifier: + case SE_DamageModifier2: case SE_HitChance: case SE_MeleeSkillCheck: case SE_HundredHands: @@ -2939,6 +3105,8 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove case SE_HealRate: case SE_SkillDamageTaken: case SE_FcSpellVulnerability: + case SE_Fc_Spell_Damage_Pct_IncomingPC: + case SE_Fc_Spell_Damage_Amt_IncomingPC: case SE_FcTwincast: case SE_DelayDeath: case SE_CastOnFadeEffect: @@ -3035,7 +3203,6 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove case SE_LimitManaMax: case SE_DoubleRangedAttack: case SE_ShieldEquipDmgMod: - case SE_GroupShielding: case SE_TriggerOnReqTarget: case SE_LimitRace: case SE_FcLimitUse: @@ -3064,9 +3231,52 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove case SE_LimitSpellClass: case SE_Sanctuary: case SE_PetMeleeMitigation: - case SE_SkillProc: + case SE_SkillProcAttempt: case SE_SkillProcSuccess: case SE_SpellResistReduction: + case SE_Duration_HP_Pct: + case SE_Duration_Mana_Pct: + case SE_Duration_Endurance_Pct: + case SE_Endurance_Absorb_Pct_Damage: + case SE_AC_Mitigation_Max_Percent: + case SE_AC_Avoidance_Max_Percent: + case SE_Attack_Accuracy_Max_Percent: + case SE_Critical_Melee_Damage_Mod_Max: + case SE_Melee_Damage_Position_Mod: + case SE_Damage_Taken_Position_Mod: + case SE_Melee_Damage_Position_Amt: + case SE_Damage_Taken_Position_Amt: + case SE_DS_Mitigation_Amount: + case SE_DS_Mitigation_Percentage: + case SE_Double_Backstab_Front: + case SE_Pet_Crit_Melee_Damage_Pct_Owner: + case SE_Pet_Add_Atk: + case SE_TwinCastBlocker: + case SE_Fc_Cast_Spell_On_Land: + case SE_Ff_CasterClass: + case SE_Ff_Same_Caster: + case SE_Fc_ResistIncoming: + case SE_Fc_Amplify_Amt: + case SE_Fc_Amplify_Mod: + case SE_Fc_CastTimeAmt: + case SE_Fc_CastTimeMod2: + case SE_Ff_DurationMax: + case SE_Ff_Endurance_Max: + case SE_Ff_Endurance_Min: + case SE_Ff_ReuseTimeMin: + case SE_Ff_ReuseTimeMax: + case SE_Ff_Value_Min: + case SE_Ff_Value_Max: + case SE_AddExtraAttackPct_1h_Primary: + case SE_AddExtraAttackPct_1h_Secondary: + case SE_Double_Melee_Round: + case SE_Skill_Base_Damage_Mod: + case SE_Worn_Endurance_Regen_Cap: + case SE_Buy_AA_Rank: + case SE_Ff_FocusTimerMin: + case SE_Proc_Timer_Modifier: + case SE_FFItemClass: + case SE_SpellEffectResistChance: { break; } @@ -3098,48 +3308,77 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove } int Mob::CalcSpellEffectValue(uint16 spell_id, int effect_id, int caster_level, uint32 instrument_mod, Mob *caster, - int ticsremaining, uint16 caster_id) + int ticsremaining, uint16 caster_id) { - int formula, base, max, effect_value; - if (!IsValidSpell(spell_id) || effect_id < 0 || effect_id >= EFFECT_COUNT) return 0; - formula = spells[spell_id].formula[effect_id]; - base = spells[spell_id].base[effect_id]; - max = spells[spell_id].max[effect_id]; + int formula = spells[spell_id].formula[effect_id]; + int base_value = spells[spell_id].base_value[effect_id]; + int max_value = spells[spell_id].max_value[effect_id]; + int effect_value = 0; + int oval = 0; if (IsBlankSpellEffect(spell_id, effect_id)) return 0; - effect_value = CalcSpellEffectValue_formula(formula, base, max, caster_level, spell_id, ticsremaining); + effect_value = CalcSpellEffectValue_formula(formula, base_value, max_value, caster_level, spell_id, ticsremaining); // this doesn't actually need to be a song to get mods, just the right skill - if (EQ::skills::IsBardInstrumentSkill(spells[spell_id].skill) && - spells[spell_id].effectid[effect_id] != SE_AttackSpeed && - spells[spell_id].effectid[effect_id] != SE_AttackSpeed2 && - spells[spell_id].effectid[effect_id] != SE_AttackSpeed3 && - spells[spell_id].effectid[effect_id] != SE_Lull && - spells[spell_id].effectid[effect_id] != SE_ChangeFrenzyRad && - spells[spell_id].effectid[effect_id] != SE_Harmony && - spells[spell_id].effectid[effect_id] != SE_CurrentMana && - spells[spell_id].effectid[effect_id] != SE_ManaRegen_v2 && - spells[spell_id].effectid[effect_id] != SE_AddFaction) { + if (EQ::skills::IsBardInstrumentSkill(spells[spell_id].skill) + && IsInstrumentModAppliedToSpellEffect(spell_id, spells[spell_id].effect_id[effect_id])) { + oval = effect_value; + effect_value = effect_value * static_cast(instrument_mod) / 10; + LogSpells("Effect value [{}] altered with bard modifier of [{}] to yeild [{}]", + oval, instrument_mod, effect_value); + } + /* + SPA 413 SE_FcBaseEffects, modifies base value of a spell effect after formula calcultion, but before other focuses. + This is applied to non-Bards in Mob::GetInstrumentMod + Like bard modifiers, this is sent in the action_struct using action->instrument_mod (which is a base effect modifier) - int oval = effect_value; - int mod = ApplySpellEffectiveness(spell_id, instrument_mod, true, caster_id); - effect_value = effect_value * mod / 10; - LogSpells("Effect value [{}] altered with bard modifier of [{}] to yeild [{}]", + Issue: value sent with action->instrument_mod needs to be 10 or higher. Therefore lowest possible percent chance would be 11 (calculated to 10%) + there are modern spells that use less than 10% but we send as a uint where lowest value has to be 10, where it should be a float for current clients. + Though not ideal, at the moment for spells that are instant effects, the action packet doesn't matter and we will calculate the actual percent here correctly. + Logic here is, caster_id is only sent from ApplySpellBonuses. Thus if it is a buff a long as the base effects is set to over 10% and at +10% intervals + it will focus the base value correctly. + + */ + + /* + Calculate base effects modifier for casters who are not bards. + */ + + //This is checked from Mob::SpellEffects and applied to instant spells and runes. + if (caster && caster->GetClass() != BARD && caster->HasBaseEffectFocus()) { + + oval = effect_value; + int mod = caster->GetFocusEffect(focusFcBaseEffects, spell_id); + effect_value += effect_value * mod / 100; + + LogSpells("Instant Effect value [{}] altered with base effects modifier of [{}] to yeild [{}]", oval, mod, effect_value); } + //This is checked from Mob::ApplySpellBonuses, applied to buffs that receive bonuses. See above, must be in 10% intervals to work. + else if (caster_id && instrument_mod > 10) { - effect_value = mod_effect_value(effect_value, spell_id, spells[spell_id].effectid[effect_id], caster, caster_id); + Mob* buff_caster = entity_list.GetMob(caster_id);//If targeted bard song needed to confirm caster is not bard. + if (buff_caster && buff_caster->GetClass() != BARD) { + oval = effect_value; + effect_value = effect_value * static_cast(instrument_mod) / 10; + + LogSpells("Bonus Effect value [{}] altered with base effects modifier of [{}] to yeild [{}]", + oval, instrument_mod, effect_value); + } + } + + effect_value = mod_effect_value(effect_value, spell_id, spells[spell_id].effect_id[effect_id], caster, caster_id); return effect_value; } // generic formula calculations -int Mob::CalcSpellEffectValue_formula(int formula, int base, int max, int caster_level, uint16 spell_id, int ticsremaining) +int Mob::CalcSpellEffectValue_formula(int formula, int base_value, int max_value, int caster_level, uint16 spell_id, int ticsremaining) { /* i need those formulas checked!!!! @@ -3168,7 +3407,7 @@ i need those formulas checked!!!! 0x77 = min + level / 8 */ - int result = 0, updownsign = 1, ubase = base; + int result = 0, updownsign = 1, ubase = base_value; if(ubase < 0) ubase = 0 - ubase; @@ -3181,7 +3420,7 @@ Strangely, damage spells have a negative base and positive max, but snare has both of them negative, yet their range should work the same: (meaning they both start at a negative value and the value gets lower) */ - if (max < base && max != 0) + if (max_value < base_value && max_value != 0) { // values are calculated down updownsign = -1; @@ -3193,7 +3432,7 @@ snare has both of them negative, yet their range should work the same: } LogSpells("CSEV: spell [{}], formula [{}], base [{}], max [{}], lvl [{}]. Up/Down [{}]", - spell_id, formula, base, max, caster_level, updownsign); + spell_id, formula, base_value, max_value, caster_level, updownsign); switch(formula) { @@ -3216,7 +3455,7 @@ snare has both of them negative, yet their range should work the same: case 107: { - int ticdif = CalcBuffDuration_formula(caster_level, spells[spell_id].buffdurationformula, spells[spell_id].buffduration) - std::max((ticsremaining - 1), 0); + int ticdif = CalcBuffDuration_formula(caster_level, spells[spell_id].buff_duration_formula, spells[spell_id].buff_duration) - std::max((ticsremaining - 1), 0); if (ticdif < 0) ticdif = 0; result = updownsign * (ubase - ticdif); @@ -3225,7 +3464,7 @@ snare has both of them negative, yet their range should work the same: } case 108: { - int ticdif = CalcBuffDuration_formula(caster_level, spells[spell_id].buffdurationformula, spells[spell_id].buffduration) - std::max((ticsremaining - 1), 0); + int ticdif = CalcBuffDuration_formula(caster_level, spells[spell_id].buff_duration_formula, spells[spell_id].buff_duration) - std::max((ticsremaining - 1), 0); if (ticdif < 0) ticdif = 0; result = updownsign * (ubase - (2 * ticdif)); @@ -3275,10 +3514,11 @@ snare has both of them negative, yet their range should work the same: break; case 119: // confirmed 2/6/04 - result = ubase + (caster_level / 8); break; + result = ubase + (caster_level / 8); + break; case 120: { - int ticdif = CalcBuffDuration_formula(caster_level, spells[spell_id].buffdurationformula, spells[spell_id].buffduration) - std::max((ticsremaining - 1), 0); + int ticdif = CalcBuffDuration_formula(caster_level, spells[spell_id].buff_duration_formula, spells[spell_id].buff_duration) - std::max((ticsremaining - 1), 0); if (ticdif < 0) ticdif = 0; result = updownsign * (ubase - (5 * ticdif)); @@ -3289,7 +3529,7 @@ snare has both of them negative, yet their range should work the same: result = ubase + (caster_level / 3); break; case 122: { - int ticdif = CalcBuffDuration_formula(caster_level, spells[spell_id].buffdurationformula, spells[spell_id].buffduration) - std::max((ticsremaining - 1), 0); + int ticdif = CalcBuffDuration_formula(caster_level, spells[spell_id].buff_duration_formula, spells[spell_id].buff_duration) - std::max((ticsremaining - 1), 0); if(ticdif < 0) ticdif = 0; @@ -3298,7 +3538,7 @@ snare has both of them negative, yet their range should work the same: break; } case 123: // added 2/6/04 - result = zone->random.Int(ubase, std::abs(max)); + result = zone->random.Int(ubase, std::abs(max_value)); break; case 124: // check sign @@ -3391,7 +3631,7 @@ snare has both of them negative, yet their range should work the same: //these are used in stacking effects... formula unknown case 201: case 203: - result = max; + result = max_value; break; default: { @@ -3401,7 +3641,7 @@ snare has both of them negative, yet their range should work the same: { // These work like splurt, accept instead of being hard coded to 12, it is formula - 1000. // Formula 1999 seems to have a slightly different effect, so is not included here - int ticdif = CalcBuffDuration_formula(caster_level, spells[spell_id].buffdurationformula, spells[spell_id].buffduration) - std::max((ticsremaining - 1), 0); + int ticdif = CalcBuffDuration_formula(caster_level, spells[spell_id].buff_duration_formula, spells[spell_id].buff_duration) - std::max((ticsremaining - 1), 0); if(ticdif < 0) ticdif = 0; @@ -3421,25 +3661,25 @@ snare has both of them negative, yet their range should work the same: int oresult = result; // now check result against the allowed maximum - if (max != 0) + if (max_value != 0) { if (updownsign == 1) { - if (result > max) - result = max; + if (result > max_value) + result = max_value; } else { - if (result < max) - result = max; + if (result < max_value) + result = max_value; } } // if base is less than zero, then the result need to be negative too - if (base < 0 && result > 0) + if (base_value < 0 && result > 0) result *= -1; - LogSpells("Result: [{}] (orig [{}]), cap [{}] [{}]", result, oresult, max, (base < 0 && result > 0)?"Inverted due to negative base":""); + LogSpells("Result: [{}] (orig [{}]), cap [{}] [{}]", result, oresult, max_value, (base_value < 0 && result > 0)?"Inverted due to negative base":""); return result; } @@ -3459,8 +3699,8 @@ void Mob::BuffProcess() continue; // DF_Permanent uses -1 DF_Aura uses -4 but we need to check negatives for some spells for some reason? - if (spells[buffs[buffs_i].spellid].buffdurationformula != DF_Permanent && - spells[buffs[buffs_i].spellid].buffdurationformula != DF_Aura) { + if (spells[buffs[buffs_i].spellid].buff_duration_formula != DF_Permanent && + spells[buffs[buffs_i].spellid].buff_duration_formula != DF_Aura) { if(!zone->BuffTimersSuspended() || !IsSuspendableSpell(buffs[buffs_i].spellid)) { --buffs[buffs_i].ticsremaining; @@ -3486,7 +3726,7 @@ void Mob::BuffProcess() { CastToClient()->SendBuffDurationPacket(buffs[buffs_i], buffs_i); // Hack to get UF to play nicer, RoF seems fine without it - if (CastToClient()->ClientVersion() == EQ::versions::ClientVersion::UF && buffs[buffs_i].numhits > 0) + if (CastToClient()->ClientVersion() == EQ::versions::ClientVersion::UF && buffs[buffs_i].hit_number > 0) CastToClient()->SendBuffNumHitPacket(buffs[buffs_i], buffs_i); buffs[buffs_i].UpdateClient = false; } @@ -3504,24 +3744,20 @@ void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster) const SPDat_Spell_Struct &spell = spells[buff.spellid]; - if (IsNPC()) { - std::vector args; - args.push_back(&buff.ticsremaining); - args.push_back(&buff.casterlevel); - args.push_back(&slot); - int i = parse->EventSpell(EVENT_SPELL_BUFF_TIC_NPC, CastToNPC(), nullptr, buff.spellid, - caster ? caster->GetID() : 0, &args); - if (i != 0) { + std::string buf = fmt::format( + "{} {} {} {}", + caster ? caster->GetID() : 0, + buffs[slot].ticsremaining, + caster ? caster->GetLevel() : 0, + slot + ); + + if (IsClient()) { + if (parse->EventSpell(EVENT_SPELL_EFFECT_BUFF_TIC_CLIENT, nullptr, CastToClient(), buff.spellid, buf, 0) != 0) { return; } - } else { - std::vector args; - args.push_back(&buff.ticsremaining); - args.push_back(&buff.casterlevel); - args.push_back(&slot); - int i = parse->EventSpell(EVENT_SPELL_BUFF_TIC_CLIENT, nullptr, CastToClient(), buff.spellid, - caster ? caster->GetID() : 0, &args); - if (i != 0) { + } else if (IsNPC()) { + if (parse->EventSpell(EVENT_SPELL_EFFECT_BUFF_TIC_NPC, CastToNPC(), nullptr, buff.spellid, buf, 0) != 0) { return; } } @@ -3530,14 +3766,16 @@ void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster) if (IsBlankSpellEffect(buff.spellid, i)) continue; - effect = spell.effectid[i]; + effect = spell.effect_id[i]; // I copied the calculation into each case which needed it instead of // doing it every time up here, since most buff effects dont need it switch (effect) { - case SE_CurrentHP: { - if (!PassCastRestriction(false, spells[buff.spellid].base2[i], true)) + case SE_CurrentHP: { + if (spells[buff.spellid].limit_value[i] && !PassCastRestriction(spells[buff.spellid].limit_value[i])) { break; + } + effect_value = CalcSpellEffectValue(buff.spellid, i, buff.casterlevel, buff.instrument_mod, caster, buff.ticsremaining); // Handle client cast DOTs here. @@ -3626,19 +3864,15 @@ void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster) } case SE_WipeHateList: { - if (IsMezSpell(buff.spellid)) + if (IsMezSpell(buff.spellid)) { break; - - int wipechance = spells[buff.spellid].base[i]; - int bonus = 0; - - if (caster) { - bonus = caster->spellbonuses.IncreaseChanceMemwipe + - caster->itembonuses.IncreaseChanceMemwipe + - caster->aabonuses.IncreaseChanceMemwipe; } - wipechance += wipechance * bonus / 100; + int wipechance = 0; + + if (caster) { + wipechance = caster->GetMemoryBlurChance(effect_value); + } if (zone->random.Roll(wipechance)) { if (IsAIControlled()) { @@ -3665,7 +3899,7 @@ void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster) if (zone->random.Roll(RuleI(Spells, RootBreakCheckChance))) { float resist_check = - ResistSpell(spells[buff.spellid].resisttype, buff.spellid, caster, 0, 0, 0, 0, true); + ResistSpell(spells[buff.spellid].resist_type, buff.spellid, caster, 0, 0, 0, 0, true); if (resist_check == 100) break; @@ -3678,7 +3912,7 @@ void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster) case SE_Fear: { if (zone->random.Roll(RuleI(Spells, FearBreakCheckChance))) { - float resist_check = ResistSpell(spells[buff.spellid].resisttype, buff.spellid, caster,0,0,true); + float resist_check = ResistSpell(spells[buff.spellid].resist_type, buff.spellid, caster,0,0,true); if (resist_check == 100) break; @@ -3722,7 +3956,7 @@ void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster) case SE_InterruptCasting: { if (IsCasting()) { const auto &spell = spells[casting_spell_id]; - if (!spell.cast_not_standing && zone->random.Roll(spells[buff.spellid].base[i])) { + if (!IgnoreCastingRestriction(spell.id) && zone->random.Roll(spells[buff.spellid].base_value[i])) { InterruptSpell(); } } @@ -3734,7 +3968,7 @@ void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster) case SE_CastOnFadeEffectNPC: case SE_CastOnFadeEffectAlways: { if (buff.ticsremaining == 0) { - SpellFinished(spells[buff.spellid].base[i], this, EQ::spells::CastingSlot::Item, 0, -1, spells[spells[buff.spellid].base[i]].ResistDiff); + SpellFinished(spells[buff.spellid].base_value[i], this, EQ::spells::CastingSlot::Item, 0, -1, spells[spells[buff.spellid].base_value[i]].resist_difficulty); } break; } @@ -3753,7 +3987,7 @@ void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster) ((int(GetY()) - buff.caston_y) * (int(GetY()) - buff.caston_y)) + ((int(GetZ()) - buff.caston_z) * (int(GetZ()) - buff.caston_z)); - if (distance > (spells[buff.spellid].base[i] * spells[buff.spellid].base[i])) { + if (distance > (spells[buff.spellid].base_value[i] * spells[buff.spellid].base_value[i])) { if (!TryFadeEffect(slot)) BuffFadeBySlot(slot, true); @@ -3764,7 +3998,7 @@ void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster) case SE_AddHateOverTimePct: { if (IsNPC()) { - uint32 new_hate = CastToNPC()->GetHateAmount(caster) * (100 + spell.base[i]) / 100; + uint32 new_hate = CastToNPC()->GetHateAmount(caster) * (100 + spell.base_value[i]) / 100; if (new_hate <= 0) new_hate = 1; @@ -3773,6 +4007,55 @@ void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster) break; } + case SE_Duration_HP_Pct: { + effect_value = spells[buff.spellid].base_value[i]; + int32 amt = abs(GetMaxHP() * effect_value / 100); + if (spells[buff.spellid].max_value[i] && amt > spells[buff.spellid].max_value[i]) + amt = spells[buff.spellid].max_value[i]; + + if (effect_value < 0) { + Damage(this, amt, 0, EQ::skills::SkillEvocation, false); + } + else { + HealDamage(amt); + } + break; + } + + case SE_Duration_Mana_Pct: { + effect_value = spells[buff.spellid].base_value[i]; + int32 amt = abs(GetMaxMana() * effect_value / 100); + if (spells[buff.spellid].max_value[i] && amt > spells[buff.spellid].max_value[i]) + amt = spells[buff.spellid].max_value[i]; + + if (effect_value < 0) { + + SetMana(GetMana() - amt); + } + else { + SetMana(GetMana() + amt); + } + break; + } + + case SE_Duration_Endurance_Pct: { + effect_value = spells[buff.spellid].base_value[i]; + + if (IsClient()) { + int32 amt = abs(CastToClient()->GetMaxEndurance() * effect_value / 100); + if (spells[buff.spellid].max_value[i] && amt > spells[buff.spellid].max_value[i]) + amt = spells[buff.spellid].max_value[i]; + + if (effect_value < 0) { + CastToClient()->SetEndurance(CastToClient()->GetEndurance() - amt); + } + else { + CastToClient()->SetEndurance(CastToClient()->GetEndurance() + amt); + } + } + break; + } + default: { // do we need to do anyting here? } @@ -3804,41 +4087,30 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses) LogSpells("Fading buff [{}] from slot [{}]", buffs[slot].spellid, slot); - if(spells[buffs[slot].spellid].viral_targets > 0) { - bool last_virus = true; - for(int i = 0; i < MAX_SPELL_TRIGGER*2; i+=2) - { - if(viral_spells[i] && viral_spells[i] != buffs[slot].spellid) - { - // If we have a virus that doesn't match this one then don't stop the viral timer - last_virus = false; - } + std::string buf = fmt::format( + "{} {} {} {}", + buffs[slot].casterid, + buffs[slot].ticsremaining, + buffs[slot].casterlevel, + slot + ); + + if (IsClient()) { + if (parse->EventSpell(EVENT_SPELL_FADE, nullptr, CastToClient(), buffs[slot].spellid, buf, 0) != 0) { + return; } - // This is the last virus on us so lets stop timer - if(last_virus) { - viral_timer.Disable(); - has_virus = false; + } else if (IsNPC()) { + if (parse->EventSpell(EVENT_SPELL_FADE, CastToNPC(), nullptr, buffs[slot].spellid, buf, 0) != 0) { + return; } } - if(IsClient()) { - std::vector args; - args.push_back(&buffs[slot].casterid); - - parse->EventSpell(EVENT_SPELL_FADE, nullptr, CastToClient(), buffs[slot].spellid, slot, &args); - } else if(IsNPC()) { - std::vector args; - args.push_back(&buffs[slot].casterid); - - parse->EventSpell(EVENT_SPELL_FADE, CastToNPC(), nullptr, buffs[slot].spellid, slot, &args); - } - for (int i=0; i < EFFECT_COUNT; i++) { if(IsBlankSpellEffect(buffs[slot].spellid, i)) continue; - switch (spells[buffs[slot].spellid].effectid[i]) + switch (spells[buffs[slot].spellid].effect_id[i]) { case SE_AddMeleeProc: case SE_WeaponProc: @@ -3878,30 +4150,32 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses) case SE_Illusion: { SendIllusionPacket(0, GetBaseGender()); - if(GetRace() == OGRE){ + if (GetRace() == OGRE) { SendAppearancePacket(AT_Size, 9); } - else if(GetRace() == TROLL){ + else if (GetRace() == TROLL) { SendAppearancePacket(AT_Size, 8); } - else if(GetRace() == VAHSHIR || GetRace() == FROGLOK || GetRace() == BARBARIAN){ + else if (GetRace() == VAHSHIR || GetRace() == FROGLOK || GetRace() == BARBARIAN) { SendAppearancePacket(AT_Size, 7); } - else if(GetRace() == HALF_ELF || GetRace() == WOOD_ELF || GetRace() == DARK_ELF){ + else if (GetRace() == HALF_ELF || GetRace() == WOOD_ELF || GetRace() == DARK_ELF) { SendAppearancePacket(AT_Size, 5); } - else if(GetRace() == DWARF){ + else if (GetRace() == DWARF) { SendAppearancePacket(AT_Size, 4); } - else if(GetRace() == HALFLING || GetRace() == GNOME){ + else if (GetRace() == HALFLING || GetRace() == GNOME) { SendAppearancePacket(AT_Size, 3); } - else{ + else { SendAppearancePacket(AT_Size, 6); } - for (int x = EQ::textures::textureBegin; x <= EQ::textures::LastTintableTexture; x++){ + + for (int x = EQ::textures::textureBegin; x <= EQ::textures::LastTintableTexture; x++) { SendWearChange(x); } + break; } @@ -3915,7 +4189,7 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses) case SE_Invisibility2: case SE_Invisibility: { - SetInvisible(0); + SetInvisible(Invisibility::Visible); break; } @@ -3995,7 +4269,7 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses) } SendAppearancePacket(AT_Pet, 0, true, true); - Mob* tempmob = GetOwner(); + Mob* owner = GetOwner(); SetOwnerID(0); SetPetType(petNone); SetHeld(false); @@ -4004,25 +4278,82 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses) SetFocused(false); SetPetStop(false); SetPetRegroup(false); - if(tempmob) + if(owner) { - tempmob->SetPet(0); + owner->SetPet(0); } if (IsAIControlled()) { + //Remove damage over time effects on charmed pet and those applied by charmed pet. + if (RuleB(Spells, PreventFactionWarOnCharmBreak)) { + for (auto mob : hate_list.GetHateList()) { + auto tar = mob->entity_on_hatelist; + if (tar) { + if (tar->IsCasting()) { + tar->InterruptSpell(tar->CastingSpellID()); + } + uint32 buff_count = tar->GetMaxTotalSlots(); + for (unsigned int j = 0; j < buff_count; j++) { + if (IsValidSpell(tar->GetBuffs()[j].spellid)) { + auto spell = spells[tar->GetBuffs()[j].spellid]; + if (spell.good_effect == 0 && IsEffectInSpell(spell.id, SE_CurrentHP) && tar->GetBuffs()[j].casterid == GetID()) { + tar->BuffFadeBySpellID(spell.id); + } + } + } + } + } + if (IsCasting()) { + InterruptSpell(CastingSpellID()); + } + uint32 buff_count = GetMaxTotalSlots(); + for (unsigned int j = 0; j < buff_count; j++) { + if (IsValidSpell(GetBuffs()[j].spellid )) { + auto spell = spells[this->GetBuffs()[j].spellid]; + if (spell.good_effect == 0 && IsEffectInSpell(spell.id, SE_CurrentHP)) { + BuffFadeBySpellID(spell.id); + } + } + } + } + // clear the hate list of the mobs - entity_list.ReplaceWithTarget(this, tempmob); + entity_list.ReplaceWithTarget(this, owner); WipeHateList(); - if(tempmob) - AddToHateList(tempmob, 1, 0); + if (owner) { + AddToHateList(owner, 1, 0); + } + //If owner dead, briefly setting Immmune Aggro while hatelists wipe for both pet and targets is needed to ensure no reaggroing. + else if (IsNPC()){ + bool immune_aggro = GetSpecialAbility(IMMUNE_AGGRO); //check if already immune aggro + SetSpecialAbility(IMMUNE_AGGRO, 1); + WipeHateList(); + if (IsCasting()) { + InterruptSpell(CastingSpellID()); + } + entity_list.RemoveFromHateLists(this); + //If NPC targeting charmed pet are in process of casting on it after it is removed from hatelist, stop the cast to prevent reaggroing. + Mob *current_npc = nullptr; + for (auto &it : entity_list.GetNPCList()) { + current_npc = it.second; + + if (current_npc && current_npc->IsCasting() && current_npc->GetTarget() == this) { + current_npc->InterruptSpell(current_npc->CastingSpellID()); + } + } + + if (!immune_aggro) { + SetSpecialAbility(IMMUNE_AGGRO, 0); + } + } SendAppearancePacket(AT_Anim, ANIM_STAND); } - if(tempmob && tempmob->IsClient()) + if(owner && owner->IsClient()) { auto app = new EQApplicationPacket(OP_Charm, sizeof(Charm_Struct)); Charm_Struct *ps = (Charm_Struct*)app->pBuffer; - ps->owner_id = tempmob->GetID(); - ps->pet_id = this->GetID(); + ps->owner_id = owner->GetID(); + ps->pet_id = GetID(); ps->command = 0; entity_list.QueueClients(this, app); safe_delete(app); @@ -4111,11 +4442,30 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses) case SE_EyeOfZomm: { if (IsClient()) - { - CastToClient()->SetControlledMobId(0); + { + NPC* tmp_eye_of_zomm = entity_list.GetNPCByID(CastToClient()->GetControlledMobId()); + //On live there is about a 6 second delay before it despawns once new one spawns. + if (tmp_eye_of_zomm) { + tmp_eye_of_zomm->GetSwarmInfo()->duration->Disable(); + tmp_eye_of_zomm->GetSwarmInfo()->duration->Start(6000); + tmp_eye_of_zomm->DisableSwarmTimer(); + tmp_eye_of_zomm->StartSwarmTimer(6000); } + CastToClient()->SetControlledMobId(0); + } + } + + case SE_Weapon_Stance: + { + /* + If we click off the spell buff (or fades naturally) giving us + Weapon Stance effects it should remove all associated buff. + */ + if (weaponstance.spellbonus_buff_spell_id) { + BuffFadeBySpellID(weaponstance.spellbonus_buff_spell_id); + } + weaponstance.spellbonus_enabled = false; } - } } @@ -4139,7 +4489,7 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses) for(uint32 d = 0; d < buff_max; d++) { - if(IsValidSpell(buffs[d].spellid) && (buffs[d].numhits > 0)) { + if(IsValidSpell(buffs[d].spellid) && (buffs[d].hit_number > 0)) { Numhits(true); found_numhits = true; } @@ -4149,8 +4499,8 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses) Numhits(false); } - if (spells[buffs[slot].spellid].NimbusEffect > 0) - RemoveNimbusEffect(spells[buffs[slot].spellid].NimbusEffect); + if (spells[buffs[slot].spellid].nimbus_effect > 0) + RemoveNimbusEffect(spells[buffs[slot].spellid].nimbus_effect); buffs[slot].spellid = SPELL_UNKNOWN; if(IsPet() && GetOwner() && GetOwner()->IsClient()) { @@ -4191,20 +4541,33 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses) CalcBonuses(); } -int16 Client::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) +int32 Client::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) { const SPDat_Spell_Struct &spell = spells[spell_id]; - int16 value = 0; - int lvlModifier = 100; - int spell_level = 0; - int lvldiff = 0; - uint32 effect = 0; - int32 base1 = 0; - int32 base2 = 0; - uint32 slot = 0; + bool not_focusable = spells[spell_id].not_focusable; - bool LimitFailure = false; + int32 value = 0; + int lvlModifier = 100; + int spell_level = 0; + int lvldiff = 0; + uint32 effect = 0; + int32 base_value = 0; + int32 limit_value = 0; + uint32 slot = 0; + + int index_id = -1; + uint32 focus_reuse_time = 0; + + bool is_from_item_click = false; + bool try_apply_to_item_click = false; + bool has_item_limit_check = false; + + if (casting_spell_inventory_slot && casting_spell_inventory_slot != -1) { + is_from_item_click = true; + } + + bool LimitFailure = false; bool LimitInclude[MaxLimitInclude] = {false}; /* Certain limits require only one of several Include conditions to be true. Ie. Add damage to fire OR ice spells. @@ -4216,15 +4579,17 @@ int16 Client::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) 10/11 SE_LimitCastingSkill: 12/13 SE_LimitSpellClass: 14/15 SE_LimitSpellSubClass: + 16/17 SE_FFItemCLass: Remember: Update MaxLimitInclude in spdat.h if adding new limits that require Includes */ + int FocusCount = 0; for (const auto &e : rank.effects) { effect = e.effect_id; - base1 = e.base1; - base2 = e.base2; - slot = e.slot; + base_value = e.base_value; + limit_value = e.limit_value; + slot = e.slot; /* AA Foci's can contain multiple focus effects within the same AA. @@ -4233,22 +4598,23 @@ int16 Client::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) when the next valid focus effect is found. */ - if (IsFocusEffect(0, 0, true, effect) || (effect == SE_TriggerOnCast)) { + if (IsFocusEffect(0, 0, true, effect)) { FocusCount++; // If limit found on prior check next, else end loop. if (FocusCount > 1) { - for (int e = 0; e < MaxLimitInclude; e += 2) { - if (LimitInclude[e] && !LimitInclude[e + 1]) + for (int i = 0; i < MaxLimitInclude; i += 2) { + if (LimitInclude[i] && !LimitInclude[i + 1]) { LimitFailure = true; + } } if (LimitFailure) { - value = 0; + value = 0; LimitFailure = false; - for (int e = 0; e < MaxLimitInclude; e++) { - LimitInclude[e] = false; // Reset array + for (bool & l : LimitInclude) { + l = false; // Reset array } } @@ -4259,425 +4625,714 @@ int16 Client::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) } switch (effect) { - case SE_Blank: - break; + case SE_Blank: + break; - // Handle Focus Limits - - case SE_LimitResist: - if (base1 < 0) { - if (spell.resisttype == -base1) // Exclude - LimitFailure = true; - } else { - LimitInclude[0] = true; - if (spell.resisttype == base1) // Include - LimitInclude[1] = true; - } - break; - - case SE_LimitInstant: - if (base1 == 1 && spell.buffduration) // Fail if not instant - LimitFailure = true; - if (base1 == 0 && (spell.buffduration == 0)) // Fail if instant - LimitFailure = true; - - break; - - case SE_LimitMaxLevel: - spell_level = spell.classes[(GetClass() % 17) - 1]; - lvldiff = spell_level - base1; - // every level over cap reduces the effect by base2 percent unless from a clicky when - // ItemCastsUseFocus is true - if (lvldiff > 0 && (spell_level <= RuleI(Character, MaxLevel) || - RuleB(Character, ItemCastsUseFocus) == false)) { - if (base2 > 0) { - lvlModifier -= base2 * lvldiff; - if (lvlModifier < 1) + // Handle Focus Limits + case SE_LimitResist: + if (base_value < 0) { + if (spell.resist_type == -base_value) { // Exclude LimitFailure = true; - } else - LimitFailure = true; - } - break; - - case SE_LimitMinLevel: - if ((spell.classes[(GetClass() % 17) - 1]) < base1) - LimitFailure = true; - break; - - case SE_LimitCastTimeMin: - if (static_cast(spell.cast_time) < base1) - LimitFailure = true; - break; - - case SE_LimitCastTimeMax: - if (static_cast(spell.cast_time) > base1) - LimitFailure = true; - break; - - case SE_LimitSpell: - if (base1 < 0) { // Exclude - if (spell_id == -base1) - LimitFailure = true; - } else { - LimitInclude[2] = true; - if (spell_id == base1) // Include - LimitInclude[3] = true; - } - break; - - case SE_LimitMinDur: - if (base1 > CalcBuffDuration_formula(GetLevel(), spell.buffdurationformula, spell.buffduration)) - LimitFailure = true; - - break; - - case SE_LimitEffect: - if (base1 < 0) { - if (IsEffectInSpell(spell_id, -base1)) // Exclude - LimitFailure = true; - } else { - LimitInclude[4] = true; - // they use 33 here for all classes ... unsure if the type check is really needed - if (base1 == SE_SummonPet && type == focusReagentCost) { - if (IsSummonPetSpell(spell_id) || IsSummonSkeletonSpell(spell_id)) - LimitInclude[5] = true; - } else { - if (IsEffectInSpell(spell_id, base1)) // Include - LimitInclude[5] = true; - } - } - break; - - case SE_LimitSpellType: - switch (base1) { - case 0: - if (!IsDetrimentalSpell(spell_id)) - LimitFailure = true; - break; - case 1: - if (!IsBeneficialSpell(spell_id)) - LimitFailure = true; - break; - } - break; - - case SE_LimitManaMin: - if (spell.mana < base1) - LimitFailure = true; - break; - - case SE_LimitManaMax: - if (spell.mana > base1) - LimitFailure = true; - break; - - case SE_LimitTarget: - if (base1 < 0) { - if (-base1 == spell.targettype) // Exclude - LimitFailure = true; - } else { - LimitInclude[6] = true; - if (base1 == spell.targettype) // Include - LimitInclude[7] = true; - } - break; - - case SE_LimitCombatSkills: - if (base1 == 0 && (IsCombatSkill(spell_id) || IsCombatProc(spell_id))) // Exclude Discs / Procs - LimitFailure = true; - else if (base1 == 1 && (!IsCombatSkill(spell_id) || !IsCombatProc(spell_id))) // Exclude Spells - LimitFailure = true; - - break; - - case SE_LimitSpellGroup: - if (base1 < 0) { - if (-base1 == spell.spellgroup) // Exclude - LimitFailure = true; - } else { - LimitInclude[8] = true; - if (base1 == spell.spellgroup) // Include - LimitInclude[9] = true; - } - break; - - case SE_LimitCastingSkill: - if (base1 < 0) { - if (-base1 == spell.skill) - LimitFailure = true; - } else { - LimitInclude[10] = true; - if (base1 == spell.skill) - LimitInclude[11] = true; - } - break; - - case SE_LimitSpellClass: - if (base1 < 0) { // Exclude - if (CheckSpellCategory(spell_id, base1, SE_LimitSpellClass)) - return (0); - } else { - LimitInclude[12] = true; - if (CheckSpellCategory(spell_id, base1, SE_LimitSpellClass)) // Include - LimitInclude[13] = true; - } - break; - - case SE_LimitSpellSubclass: - if (base1 < 0) { // Exclude - if (CheckSpellCategory(spell_id, base1, SE_LimitSpellSubclass)) - return (0); - } else { - LimitInclude[14] = true; - if (CheckSpellCategory(spell_id, base1, SE_LimitSpellSubclass)) // Include - LimitInclude[15] = true; - } - break; - - case SE_LimitClass: - // Do not use this limit more then once per spell. If multiple class, treat value like items - // would. - if (!PassLimitClass(base1, GetClass())) - LimitFailure = true; - break; - - case SE_LimitRace: - if (base1 != GetRace()) - LimitFailure = true; - break; - - case SE_LimitUseMin: - if (base1 > spell.numhits) - LimitFailure = true; - break; - - case SE_LimitUseType: - if (base1 != spell.numhitstype) - LimitFailure = true; - break; - - // Handle Focus Effects - case SE_ImprovedDamage: - if (type == focusImprovedDamage && base1 > value) - value = base1; - break; - - case SE_ImprovedDamage2: - if (type == focusImprovedDamage2 && base1 > value) - value = base1; - break; - - case SE_ImprovedHeal: - if (type == focusImprovedHeal && base1 > value) - value = base1; - break; - - case SE_ReduceManaCost: - if (type == focusManaCost) - value = base1; - break; - - case SE_IncreaseSpellHaste: - if (type == focusSpellHaste && base1 > value) - value = base1; - break; - - case SE_IncreaseSpellDuration: - if (type == focusSpellDuration && base1 > value) - value = base1; - break; - - case SE_SpellDurationIncByTic: - if (type == focusSpellDurByTic && base1 > value) - value = base1; - break; - - case SE_SwarmPetDuration: - if (type == focusSwarmPetDuration && base1 > value) - value = base1; - break; - - case SE_IncreaseRange: - if (type == focusRange && base1 > value) - value = base1; - break; - - case SE_ReduceReagentCost: - if (type == focusReagentCost && base1 > value) - value = base1; - break; - - case SE_PetPowerIncrease: - if (type == focusPetPower && base1 > value) - value = base1; - break; - - case SE_SpellResistReduction: - if (type == focusResistRate && base1 > value) - value = base1; - break; - - case SE_SpellHateMod: - if (type == focusSpellHateMod) { - if (value != 0) { - if (value > 0) { - if (base1 > value) - value = base1; - } else { - if (base1 < value) - value = base1; } - } else - value = base1; - } - break; + } + else { + LimitInclude[IncludeExistsSELimitResist] = true; + if (spell.resist_type == base_value) { // Include + LimitInclude[IncludeFoundSELimitResist] = true; + } + } + break; - case SE_ReduceReuseTimer: - if (type == focusReduceRecastTime) - value = base1 / 1000; - break; + case SE_LimitInstant: + if (base_value == 1 && spell.buff_duration) { // Fail if not instant + LimitFailure = true; + } + if (base_value == 0 && (spell.buff_duration == 0)) { // Fail if instant + LimitFailure = true; + } - case SE_TriggerOnCast: - if (type == focusTriggerOnCast) { - if (zone->random.Roll(base1)) { - value = base2; - } else { - value = 0; + break; + + case SE_LimitMaxLevel: + spell_level = spell.classes[(GetClass() % 17) - 1]; + lvldiff = spell_level - base_value; + // every level over cap reduces the effect by base2 percent unless from a clicky when + // ItemCastsUseFocus is true + if (lvldiff > 0 && (spell_level <= RuleI(Character, MaxLevel) || RuleB(Character, ItemCastsUseFocus) == false)) { + if (limit_value > 0) { + lvlModifier -= limit_value * lvldiff; + if (lvlModifier < 1) { + LimitFailure = true; + } + } + else { + LimitFailure = true; + } + } + break; + + case SE_LimitMinLevel: + if ((spell.classes[(GetClass() % 17) - 1]) < base_value) { LimitFailure = true; } break; - } - case SE_FcSpellVulnerability: - if (type == focusSpellVulnerability) - value = base1; - break; + case SE_LimitCastTimeMin: + if (static_cast(spell.cast_time) < base_value) { + LimitFailure = true; + } + break; - case SE_BlockNextSpellFocus: - if (type == focusBlockNextSpell) { - if (zone->random.Roll(base1)) - value = 1; - } - break; + case SE_LimitCastTimeMax: + if (static_cast(spell.cast_time) > base_value) { + LimitFailure = true; + } + break; - case SE_FcTwincast: - if (type == focusTwincast) - value = base1; - break; + case SE_LimitSpell: + if (base_value < 0) { // Exclude + if (spell_id == -base_value) { + LimitFailure = true; + } + } + else { + LimitInclude[IncludeExistsSELimitSpell] = true; + if (spell_id == base_value) { // Include + LimitInclude[IncludeFoundSELimitSpell] = true; + } + } + break; - // Note if using these as AA, make sure this is first focus used. - case SE_SympatheticProc: - if (type == focusSympatheticProc) - value = base2; - break; + case SE_LimitMinDur: + if (base_value > CalcBuffDuration_formula(GetLevel(), spell.buff_duration_formula, spell.buff_duration)) { + LimitFailure = true; + } + break; - case SE_FcDamageAmt: - if (type == focusFcDamageAmt) - value = base1; - break; + case SE_LimitEffect: + if (base_value < 0) { + if (IsEffectInSpell(spell_id, -base_value)) { // Exclude + LimitFailure = true; + } + } + else { + LimitInclude[IncludeExistsSELimitEffect] = true; + // they use 33 here for all classes ... unsure if the type check is really needed + if (base_value == SE_SummonPet && type == focusReagentCost) { + if (IsSummonPetSpell(spell_id) || IsSummonSkeletonSpell(spell_id)) { + LimitInclude[IncludeFoundSELimitEffect] = true; + } + } + else { + if (IsEffectInSpell(spell_id, base_value)) { // Include + LimitInclude[IncludeFoundSELimitEffect] = true; + } + } + } + break; - case SE_FcDamageAmt2: - if (type == focusFcDamageAmt2) - value = base1; - break; + case SE_LimitSpellType: + switch (base_value) { + case 0: + if (!IsDetrimentalSpell(spell_id)) { + LimitFailure = true; + } + break; + case 1: + if (!IsBeneficialSpell(spell_id)) { + LimitFailure = true; + } + break; + } + break; - case SE_FcDamageAmtCrit: - if (type == focusFcDamageAmtCrit) - value = base1; - break; + case SE_LimitManaMin: + if (spell.mana < base_value) { + LimitFailure = true; + } + break; - case SE_FcDamageAmtIncoming: - if (type == focusFcDamageAmtIncoming) - value = base1; - break; + case SE_LimitManaMax: + if (spell.mana > base_value) { + LimitFailure = true; + } + break; - case SE_FcHealAmtIncoming: - if (type == focusFcHealAmtIncoming) - value = base1; - break; + case SE_LimitTarget: + if (base_value < 0) { + if (-base_value == spell.target_type) { // Exclude + LimitFailure = true; + } + } + else { + LimitInclude[IncludeExistsSELimitTarget] = true; + if (base_value == spell.target_type) { // Include + LimitInclude[IncludeFoundSELimitTarget] = true; + } + } + break; - case SE_FcHealPctCritIncoming: - if (type == focusFcHealPctCritIncoming) - value = base1; - break; + case SE_LimitCombatSkills: + if (base_value == 0 && (IsCombatSkill(spell_id) || IsCombatProc(spell_id))) { // Exclude Discs / Procs + LimitFailure = true; + } + else if (base_value == 1 && (!IsCombatSkill(spell_id) || !IsCombatProc(spell_id))) { // Exclude Spells + LimitFailure = true; + } - case SE_FcHealAmtCrit: - if (type == focusFcHealAmtCrit) - value = base1; - break; + break; - case SE_FcHealAmt: - if (type == focusFcHealAmt) - value = base1; - break; + case SE_LimitSpellGroup: + if (base_value < 0) { + if (-base_value == spell.spell_group) { // Exclude + LimitFailure = true; + } + } + else { + LimitInclude[IncludeExistsSELimitSpellGroup] = true; + if (base_value == spell.spell_group) { // Include + LimitInclude[IncludeFoundSELimitSpellGroup] = true; + } + } + break; - case SE_FcHealPctIncoming: - if (type == focusFcHealPctIncoming) - value = base1; - break; + case SE_LimitCastingSkill: + if (base_value < 0) { + if (-base_value == spell.skill) { + LimitFailure = true; + } + } + else { + LimitInclude[IncludeExistsSELimitCastingSkill] = true; + if (base_value == spell.skill) { + LimitInclude[IncludeFoundSELimitCastingSkill] = true; + } + } + break; - case SE_FcBaseEffects: - if (type == focusFcBaseEffects) - value = base1; - break; + case SE_LimitSpellClass: + if (base_value < 0) { // Exclude + if (-base_value == spell.spell_class) { + LimitFailure = true; + } + } + else { + LimitInclude[IncludeExistsSELimitSpellClass] = true; + if (base_value == spell.spell_class) { // Include + LimitInclude[IncludeFoundSELimitSpellClass] = true; + } + } + break; - case SE_FcDamagePctCrit: - if (type == focusFcDamagePctCrit) - value = base1; - break; + case SE_LimitSpellSubclass: + if (base_value < 0) { // Exclude + if (-base_value == spell.spell_subclass) { + LimitFailure = true; + } + } + else { + LimitInclude[IncludeExistsSELimitSpellSubclass] = true; + if (base_value == spell.spell_subclass) { // Include + LimitInclude[IncludeFoundSELimitSpellSubclass] = true; + } + } + break; - case SE_FcIncreaseNumHits: - if (type == focusIncreaseNumHits) - value = base1; - break; + case SE_LimitClass: + // Do not use this limit more then once per spell. If multiple class, treat value like items + // would. + if (!PassLimitClass(base_value, GetClass())) { + LimitFailure = true; + } + break; - case SE_FcLimitUse: - if (type == focusFcLimitUse) - value = base1; - break; + case SE_LimitRace: + if (base_value != GetRace()) { + LimitFailure = true; + } + break; - case SE_FcMute: - if (type == focusFcMute) - value = base1; - break; + case SE_LimitUseMin: + if (base_value > spell.hit_number) { + LimitFailure = true; + } + break; - case SE_FcStunTimeMod: - if (type == focusFcStunTimeMod) - value = base1; - break; + case SE_LimitUseType: + if (base_value != spell.hit_number_type) { + LimitFailure = true; + } + break; + + case SE_Ff_DurationMax: + if (base_value > spell.buff_duration) { + LimitFailure = true; + } + break; + + case SE_Ff_Endurance_Min: + if (spell.endurance_cost < base_value) { + LimitFailure = true; + } + break; + + case SE_Ff_Endurance_Max: + if (spell.endurance_cost > base_value) { + LimitFailure = true; + } + break; + + case SE_Ff_ReuseTimeMin: + if (spell.recast_time < base_value) { + LimitFailure = true; + } + break; + + case SE_Ff_ReuseTimeMax: + if (spell.recast_time > base_value) { + LimitFailure = true; + } + break; + + case SE_Ff_Value_Min: + index_id = GetSpellEffectIndex(spell_id, limit_value); + if (index_id >= 0 && spell.base_value[index_id] < base_value) { + LimitFailure = true; + } + break; + + case SE_Ff_Value_Max: + index_id = GetSpellEffectIndex(spell_id, limit_value); + if (index_id >= 0 && spell.base_value[index_id] > base_value) { + LimitFailure = true; + } + break; + + case SE_Ff_Override_NotFocusable: + if (base_value == 1) { + not_focusable = false; + } + break; + + case SE_Ff_FocusTimerMin: + if (IsFocusProcLimitTimerActive(-rank.id)) { + LimitFailure = true; + } + else { + focus_reuse_time = limit_value; + } + break; + + case SE_FFItemClass: + has_item_limit_check = true; + if (casting_spell_inventory_slot && casting_spell_inventory_slot != -1) { + if (IsClient() && casting_spell_slot == EQ::spells::CastingSlot::Item && casting_spell_inventory_slot != 0xFFFFFFFF) { + auto item = CastToClient()->GetInv().GetItem(casting_spell_inventory_slot); + if (item && item->GetItem()) { + //If ItemType set to < -1, then we will exclude either all Subtypes (-1000), or specific items by ItemType, SubType or Slot. See above for rules. + if (base_value < -1) { //Excludes + bool exclude_this_item = true; + int tmp_itemtype = (item->GetItem()->ItemType + 100) * -1; + //ItemType (if set to -1000, ignore and exclude any ItemType) + if (base_value < -1 && base_value != -1000) { + if (base_value != tmp_itemtype) { + exclude_this_item = false; + } + } + //SubType (if set to -1, ignore and exclude all SubTypes) + if (limit_value >= 0) { + if (limit_value != item->GetItem()->SubType) { + exclude_this_item = false; + } + } + if (exclude_this_item) { + LimitFailure = true; + } + } + else {//Includes + LimitInclude[IncludeExistsSEFFItemClass] = true; + bool include_this_item = true; + //ItemType (if set to -1, ignore and include any ItemType) + if (base_value >= 0) { + if (base_value != item->GetItem()->ItemType) { + include_this_item = false; + } + } + //SubType (if set to -1, ignore and include any SubType) + if (limit_value >= 0) { + if (limit_value != item->GetItem()->SubType) { + include_this_item = false; + } + } + if (include_this_item) { + LimitInclude[IncludeFoundSEFFItemClass] = true; + } + } + } + } + } + //If this is checking that focus can only be cast from an item, then if its not cast from item fail. + else if (base_value >= -1) { + LimitFailure = true; + } + //If we are checking to exclude items from a focus then do not fail unless the above check fails. + break; + + /* These are not applicable to AA's because there is never a 'caster' of the 'buff' with the focus effect. + case SE_Ff_Same_Caster: + case SE_Ff_CasterClass: + */ + + // Handle Focus Effects + case SE_ImprovedDamage: + if (type == focusImprovedDamage && base_value > value) { + value = base_value; + } + break; + + case SE_ImprovedDamage2: + if (type == focusImprovedDamage2 && base_value > value) { + value = base_value; + } + break; + + case SE_Fc_Amplify_Mod: + if (type == focusFcAmplifyMod && base_value > value) { + value = base_value; + } + break; + + case SE_ImprovedHeal: + if (type == focusImprovedHeal && base_value > value) { + value = base_value; + } + break; + + case SE_ReduceManaCost: + if (type == focusManaCost) { + value = base_value; + } + break; + + case SE_IncreaseSpellHaste: + if (type == focusSpellHaste && base_value > value) { + value = base_value; + try_apply_to_item_click = is_from_item_click ? true : false; + } + break; + + case SE_Fc_CastTimeMod2: + if (type == focusFcCastTimeMod2 && base_value > value) { + value = base_value; + try_apply_to_item_click = is_from_item_click ? true : false; + } + break; + + case SE_Fc_CastTimeAmt: + if (type == focusFcCastTimeAmt && base_value > value) { + value = base_value; + try_apply_to_item_click = is_from_item_click ? true : false; + } + break; + + case SE_IncreaseSpellDuration: + if (type == focusSpellDuration && base_value > value) { + value = base_value; + } + break; + + case SE_SpellDurationIncByTic: + if (type == focusSpellDurByTic && base_value > value) { + value = base_value; + } + break; + + case SE_SwarmPetDuration: + if (type == focusSwarmPetDuration && base_value > value) { + value = base_value; + } + break; + + case SE_IncreaseRange: + if (type == focusRange && base_value > value) { + value = base_value; + } + break; + + case SE_ReduceReagentCost: + if (type == focusReagentCost && base_value > value) { + value = base_value; + } + break; + + case SE_PetPowerIncrease: + if (type == focusPetPower && base_value > value) { + value = base_value; + } + break; + + case SE_SpellResistReduction: + if (type == focusResistRate && base_value > value) { + value = base_value; + } + break; + + case SE_Fc_ResistIncoming: + if (type == focusFcResistIncoming && base_value > value) { + value = base_value; + } + break; + + case SE_SpellHateMod: + if (type == focusSpellHateMod) { + if (value != 0) { + if (value > 0) { + if (base_value > value) { + value = base_value; + } + } + else { + if (base_value < value) { + value = base_value; + } + } + } + else { + value = base_value; + } + } + break; + + case SE_ReduceReuseTimer: + if (type == focusReduceRecastTime) { + value = base_value / 1000; + try_apply_to_item_click = is_from_item_click ? true : false; + } + break; + + case SE_TriggerOnCast: + if (type == focusTriggerOnCast) { + if (zone->random.Roll(base_value)) { + value = limit_value; + } + else { + value = 0; + LimitFailure = true; + } + break; + } + + case SE_FcSpellVulnerability: + if (type == focusSpellVulnerability) { + value = base_value; + } + break; + + case SE_Fc_Spell_Damage_Pct_IncomingPC: + if (type == focusFcSpellDamagePctIncomingPC) { + value = base_value; + } + break; + + case SE_BlockNextSpellFocus: + if (type == focusBlockNextSpell) { + if (zone->random.Roll(base_value)) { + value = 1; + } + } + break; + + case SE_FcTwincast: + if (type == focusTwincast && !IsEffectInSpell(spell_id, SE_TwinCastBlocker)) { + value = base_value; + } + break; + + // Note if using these as AA, make sure this is first focus used. + case SE_SympatheticProc: + if (type == focusSympatheticProc) { + value = limit_value; + } + break; + + case SE_FcDamageAmt: + if (type == focusFcDamageAmt) { + value = base_value; + } + break; + + case SE_FcDamageAmt2: + if (type == focusFcDamageAmt2) { + value = base_value; + } + break; + + case SE_Fc_Amplify_Amt: + if (type == focusFcAmplifyAmt) { + value = base_value; + } + break; + + case SE_FcDamageAmtCrit: + if (type == focusFcDamageAmtCrit) { + value = base_value; + } + break; + + case SE_FcDamageAmtIncoming: + if (type == focusFcDamageAmtIncoming) { + value = base_value; + } + break; + + case SE_Fc_Spell_Damage_Amt_IncomingPC: + if (type == focusFcSpellDamageAmtIncomingPC) { + value = base_value; + } + break; + + case SE_FcHealAmtIncoming: + if (type == focusFcHealAmtIncoming) { + value = base_value; + } + break; + + case SE_FcHealPctCritIncoming: + if (type == focusFcHealPctCritIncoming) { + value = base_value; + } + break; + + case SE_FcHealAmtCrit: + if (type == focusFcHealAmtCrit) { + value = base_value; + } + break; + + case SE_FcHealAmt: + if (type == focusFcHealAmt) { + value = base_value; + } + break; + + case SE_FcHealPctIncoming: + if (type == focusFcHealPctIncoming) { + value = base_value; + } + break; + + case SE_FcBaseEffects: + if (type == focusFcBaseEffects) { + value = base_value; + } + break; + + case SE_FcDamagePctCrit: + if (type == focusFcDamagePctCrit) { + value = base_value; + } + break; + + case SE_FcIncreaseNumHits: + if (type == focusIncreaseNumHits) { + value = base_value; + } + break; + + case SE_FcLimitUse: + if (type == focusFcLimitUse) { + value = base_value; + } + break; + + case SE_FcMute: + if (type == focusFcMute) { + value = base_value; + } + break; + + case SE_FcStunTimeMod: + if (type == focusFcStunTimeMod) { + value = base_value; + } + break; + + case SE_Fc_Cast_Spell_On_Land: + if (type == focusFcCastSpellOnLand) { + if (zone->random.Roll(base_value)) { + value = limit_value; + } + break; + } } } for (int e = 0; e < MaxLimitInclude; e += 2) { - if (LimitInclude[e] && !LimitInclude[e + 1]) + if (LimitInclude[e] && !LimitInclude[e + 1]) { return 0; + } } - if (LimitFailure) + if (try_apply_to_item_click && !has_item_limit_check) { return 0; + } + + if (LimitFailure) { + return 0; + } + + if (not_focusable) { + return 0; + } + + if (focus_reuse_time) { + SetFocusProcLimitTimer(-rank.id, focus_reuse_time); + } return (value * lvlModifier / 100); } //given an item/spell's focus ID and the spell being cast, determine the focus ammount, if any //assumes that spell_id is not a bard spell and that both ids are valid spell ids -int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, bool best_focus) +int32 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, bool best_focus, uint16 casterid, Mob *caster) { - if (!IsValidSpell(focus_id) || !IsValidSpell(spell_id)) + /* + 'this' is always the caster of the spell_id, most foci check for effects on the caster, however some check for effects on the target. + 'casterid' is the casterid of the caster of spell_id, used when spell_id is cast on a target with a focus effect that is checked by incoming spell. + */ + if (!IsValidSpell(focus_id) || !IsValidSpell(spell_id)) { return 0; + } + + // No further checks if spell_id no_focusable, unless spell focus_id contains an override limiter. + if (spells[spell_id].not_focusable && !IsEffectInSpell(focus_id, SE_Ff_Override_NotFocusable)) { + return 0; + } const SPDat_Spell_Struct &focus_spell = spells[focus_id]; - const SPDat_Spell_Struct &spell = spells[spell_id]; + const SPDat_Spell_Struct &spell = spells[spell_id]; - int16 value = 0; - int lvlModifier = 100; - int spell_level = 0; - int lvldiff = 0; + int32 value = 0; + int lvlModifier = 100; + int spell_level = 0; + int lvldiff = 0; uint32 Caston_spell_id = 0; + int index_id = -1; + uint32 focus_reuse_time = 0; //If this is set and all limits pass, start timer at end of script. + + bool is_from_item_click = false; + bool try_apply_to_item_click = false; + bool has_item_limit_check = false; + + if (casting_spell_inventory_slot && casting_spell_inventory_slot != -1) { + is_from_item_click = true; + } + bool LimitInclude[MaxLimitInclude] = {false}; - /* Certain limits require only one of several Include conditions to be true. Ie. Add damage to fire OR ice - spells. + /* Certain limits require only one of several Include conditions to be true. Determined by limits being negative or positive + Ie. Add damage to fire OR ice spells. If positive we 'Include', by checking each limit of same type to look for match until found. Opposed to + just 'Excluding', where if set to negative, if we find that match then focus fails, ie Add damage to all spells BUT Fire. 0/1 SE_LimitResist 2/3 SE_LimitSpell 4/5 SE_LimitEffect @@ -4686,506 +5341,869 @@ int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo 10/11 SE_LimitCastingSkill: 12/13 SE_LimitSpellClass: 14/15 SE_LimitSpellSubClass: + 16/17 SE_FFItemCLass: Remember: Update MaxLimitInclude in spdat.h if adding new limits that require Includes */ for (int i = 0; i < EFFECT_COUNT; i++) { - switch (focus_spell.effectid[i]) { - - case SE_Blank: - break; - - case SE_LimitResist: - if (focus_spell.base[i] < 0) { - if (spell.resisttype == -focus_spell.base[i]) // Exclude - return 0; - } else { - LimitInclude[0] = true; - if (spell.resisttype == focus_spell.base[i]) // Include - LimitInclude[1] = true; - } - break; - - case SE_LimitInstant: - if (focus_spell.base[i] == 1 && spell.buffduration) // Fail if not instant - return 0; - if (focus_spell.base[i] == 0 && (spell.buffduration == 0)) // Fail if instant - return 0; - - break; - - case SE_LimitMaxLevel: - if (IsNPC()) + switch (focus_spell.effect_id[i]) { + + case SE_Blank: break; - spell_level = spell.classes[(GetClass() % 17) - 1]; - lvldiff = spell_level - focus_spell.base[i]; - // every level over cap reduces the effect by focus_spell.base2[i] percent unless from a clicky - // when ItemCastsUseFocus is true - if (lvldiff > 0 && (spell_level <= RuleI(Character, MaxLevel) || - RuleB(Character, ItemCastsUseFocus) == false)) { - if (focus_spell.base2[i] > 0) { - lvlModifier -= focus_spell.base2[i] * lvldiff; - if (lvlModifier < 1) + + case SE_LimitResist: + if (focus_spell.base_value[i] < 0) { + if (spell.resist_type == -focus_spell.base_value[i]) { // Exclude return 0; - } else - return 0; - } - break; - - case SE_LimitMinLevel: - if (IsNPC()) - break; - if (spell.classes[(GetClass() % 17) - 1] < focus_spell.base[i]) - return (0); - break; - - case SE_LimitCastTimeMin: - if (spells[spell_id].cast_time < (uint16)focus_spell.base[i]) - return (0); - break; - - case SE_LimitCastTimeMax: - if (spells[spell_id].cast_time > (uint16)focus_spell.base[i]) - return (0); - break; - - case SE_LimitSpell: - if (focus_spell.base[i] < 0) { // Exclude - if (spell_id == -focus_spell.base[i]) - return (0); - } else { - LimitInclude[2] = true; - if (spell_id == focus_spell.base[i]) // Include - LimitInclude[3] = true; - } - break; - - case SE_LimitMinDur: - if (focus_spell.base[i] > - CalcBuffDuration_formula(GetLevel(), spell.buffdurationformula, spell.buffduration)) - return (0); - break; - - case SE_LimitEffect: - if (focus_spell.base[i] < 0) { - if (IsEffectInSpell(spell_id, -focus_spell.base[i])) // Exclude - return 0; - } else { - LimitInclude[4] = true; - if (IsEffectInSpell(spell_id, focus_spell.base[i])) // Include - LimitInclude[5] = true; - } - break; - - case SE_LimitSpellType: - switch (focus_spell.base[i]) { - case 0: - if (!IsDetrimentalSpell(spell_id)) - return 0; - break; - case 1: - if (!IsBeneficialSpell(spell_id)) - return 0; - break; - default: - LogInfo("CalcFocusEffect: unknown limit spelltype [{}]", - focus_spell.base[i]); - } - break; - - case SE_LimitManaMin: - if (spell.mana < focus_spell.base[i]) - return 0; - break; - - case SE_LimitManaMax: - if (spell.mana > focus_spell.base[i]) - return 0; - break; - - case SE_LimitTarget: - if (focus_spell.base[i] < 0) { - if (-focus_spell.base[i] == spell.targettype) // Exclude - return 0; - } else { - LimitInclude[6] = true; - if (focus_spell.base[i] == spell.targettype) // Include - LimitInclude[7] = true; - } - break; - - case SE_LimitCombatSkills: - if (focus_spell.base[i] == 0 && - (IsCombatSkill(spell_id) || IsCombatProc(spell_id))) // Exclude Discs / Procs - return 0; - else if (focus_spell.base[i] == 1 && - (!IsCombatSkill(spell_id) || !IsCombatProc(spell_id))) // Exclude Spells - return 0; - - break; - - case SE_LimitSpellGroup: - if (focus_spell.base[i] < 0) { - if (-focus_spell.base[i] == spell.spellgroup) // Exclude - return 0; - } else { - LimitInclude[8] = true; - if (focus_spell.base[i] == spell.spellgroup) // Include - LimitInclude[9] = true; - } - break; - - case SE_LimitCastingSkill: - if (focus_spell.base[i] < 0) { - if (-focus_spell.base[i] == spell.skill) - return 0; - } else { - LimitInclude[10] = true; - if (focus_spell.base[i] == spell.skill) - LimitInclude[11] = true; - } - break; - - case SE_LimitClass: - // Do not use this limit more then once per spell. If multiple class, treat value like items - // would. - if (!PassLimitClass(focus_spell.base[i], GetClass())) - return 0; - break; - - case SE_LimitRace: - if (focus_spell.base[i] != GetRace()) - return 0; - break; - - case SE_LimitUseMin: - if (focus_spell.base[i] > spell.numhits) - return 0; - break; - - case SE_LimitUseType: - if (focus_spell.base[i] != spell.numhitstype) - return 0; - break; - - case SE_CastonFocusEffect: - if (focus_spell.base[i] > 0) - Caston_spell_id = focus_spell.base[i]; - break; - - case SE_LimitSpellClass: - if (focus_spell.base[i] < 0) { // Exclude - if (CheckSpellCategory(spell_id, focus_spell.base[i], SE_LimitSpellClass)) - return (0); - } else { - LimitInclude[12] = true; - if (CheckSpellCategory(spell_id, focus_spell.base[i], SE_LimitSpellClass)) // Include - LimitInclude[13] = true; - } - break; - - case SE_LimitSpellSubclass: - if (focus_spell.base[i] < 0) { // Exclude - if (CheckSpellCategory(spell_id, focus_spell.base[i], SE_LimitSpellSubclass)) - return (0); - } else { - LimitInclude[14] = true; - if (CheckSpellCategory(spell_id, focus_spell.base[i], SE_LimitSpellSubclass)) // Include - LimitInclude[15] = true; - } - break; - - // handle effects - case SE_ImprovedDamage: - if (type == focusImprovedDamage) { - // This is used to determine which focus should be used for the random calculation - if (best_focus) { - // If the spell contains a value in the base2 field then that is the max value - if (focus_spell.base2[i] != 0) { - value = focus_spell.base2[i]; - } - // If the spell does not contain a base2 value, then its a straight non random - // value - else { - value = focus_spell.base[i]; } } - // Actual focus calculation starts here - else if (focus_spell.base2[i] == 0 || focus_spell.base[i] == focus_spell.base2[i]) { - value = focus_spell.base[i]; - } else { - value = zone->random.Int(focus_spell.base[i], focus_spell.base2[i]); + else { + LimitInclude[IncludeExistsSELimitResist] = true; + if (spell.resist_type == focus_spell.base_value[i]) { // Include + LimitInclude[IncludeFoundSELimitResist] = true; + } } - } - break; + break; - case SE_ImprovedDamage2: - if (type == focusImprovedDamage2) { - if (best_focus) { - if (focus_spell.base2[i] != 0) { - value = focus_spell.base2[i]; + case SE_LimitInstant: + if (focus_spell.base_value[i] == 1 && spell.buff_duration) { // Fail if not instant + return 0; + } + if (focus_spell.base_value[i] == 0 && (spell.buff_duration == 0)) { // Fail if instant + return 0; + } + + break; + + case SE_LimitMaxLevel: + if (IsNPC()) { + break; + } + spell_level = spell.classes[(GetClass() % 17) - 1]; + lvldiff = spell_level - focus_spell.base_value[i]; + // every level over cap reduces the effect by focus_spell.base2[i] percent unless from a clicky + // when ItemCastsUseFocus is true + if (lvldiff > 0 && (spell_level <= RuleI(Character, MaxLevel) || + RuleB(Character, ItemCastsUseFocus) == false)) { + if (focus_spell.limit_value[i] > 0) { + lvlModifier -= focus_spell.limit_value[i] * lvldiff; + if (lvlModifier < 1) { + return 0; + } } else { - value = focus_spell.base[i]; + return 0; } } - else if (focus_spell.base2[i] == 0 || focus_spell.base[i] == focus_spell.base2[i]) { - value = focus_spell.base[i]; - } else { - value = zone->random.Int(focus_spell.base[i], focus_spell.base2[i]); - } - } - break; + break; - case SE_ImprovedHeal: - if (type == focusImprovedHeal) { - if (best_focus) { - if (focus_spell.base2[i] != 0) { - value = focus_spell.base2[i]; - } else { - value = focus_spell.base[i]; + case SE_LimitMinLevel: + if (IsNPC()) { + break; + } + if (spell.classes[(GetClass() % 17) - 1] < focus_spell.base_value[i]) { + return (0); + } + break; + + case SE_LimitCastTimeMin: + if (spells[spell_id].cast_time < (uint16) focus_spell.base_value[i]) { + return (0); + } + break; + + case SE_LimitCastTimeMax: + if (spells[spell_id].cast_time > (uint16) focus_spell.base_value[i]) { + return (0); + } + break; + + case SE_LimitSpell: + if (focus_spell.base_value[i] < 0) { // Exclude + if (spell_id == -focus_spell.base_value[i]) { + return (0); } - } else if (focus_spell.base2[i] == 0 || focus_spell.base[i] == focus_spell.base2[i]) { - value = focus_spell.base[i]; - } else { - value = zone->random.Int(focus_spell.base[i], focus_spell.base2[i]); } - } - break; - - case SE_ReduceManaCost: - if (type == focusManaCost) { - if (best_focus) { - if (focus_spell.base2[i] != 0) { - value = focus_spell.base2[i]; - } else { - value = focus_spell.base[i]; + else { + LimitInclude[IncludeExistsSELimitSpell] = true; + if (spell_id == focus_spell.base_value[i]) { // Include + LimitInclude[IncludeFoundSELimitSpell] = true; } - } else if (focus_spell.base2[i] == 0 || focus_spell.base[i] == focus_spell.base2[i]) { - value = focus_spell.base[i]; - } else { - value = zone->random.Int(focus_spell.base[i], focus_spell.base2[i]); } - } - break; + break; - case SE_IncreaseSpellHaste: - if (type == focusSpellHaste && focus_spell.base[i] > value) - value = focus_spell.base[i]; - break; + case SE_LimitMinDur: + if (focus_spell.base_value[i] > + CalcBuffDuration_formula(GetLevel(), spell.buff_duration_formula, spell.buff_duration)) { + return (0); + } + break; - case SE_IncreaseSpellDuration: - if (type == focusSpellDuration && focus_spell.base[i] > value) - value = focus_spell.base[i]; - break; - - case SE_SpellDurationIncByTic: - if (type == focusSpellDurByTic && focus_spell.base[i] > value) - value = focus_spell.base[i]; - break; - - case SE_SwarmPetDuration: - if (type == focusSwarmPetDuration && focus_spell.base[i] > value) - value = focus_spell.base[i]; - break; - - case SE_IncreaseRange: - if (type == focusRange && focus_spell.base[i] > value) - value = focus_spell.base[i]; - break; - - case SE_ReduceReagentCost: - if (type == focusReagentCost && focus_spell.base[i] > value) - value = focus_spell.base[i]; - break; - - case SE_PetPowerIncrease: - if (type == focusPetPower && focus_spell.base[i] > value) - value = focus_spell.base[i]; - break; - - case SE_SpellResistReduction: - if (type == focusResistRate) { - if (best_focus) { - if (focus_spell.base2[i] != 0) { - value = focus_spell.base2[i]; - } else { - value = focus_spell.base[i]; + case SE_LimitEffect: + if (focus_spell.base_value[i] < 0) { + if (IsEffectInSpell(spell_id, -focus_spell.base_value[i])) { // Exclude + return 0; } - } else if (focus_spell.base2[i] == 0 || focus_spell.base[i] == focus_spell.base2[i]) { - value = focus_spell.base[i]; - } else { - value = zone->random.Int(focus_spell.base[i], focus_spell.base2[i]); } - } - break; - - case SE_SpellHateMod: - if (type == focusSpellHateMod) { - if (value != 0) { - if (value > 0) { - if (focus_spell.base[i] > value) - value = focus_spell.base[i]; - } else { - if (focus_spell.base[i] < value) - value = focus_spell.base[i]; + else { + LimitInclude[IncludeExistsSELimitEffect] = true; + if (IsEffectInSpell(spell_id, focus_spell.base_value[i])) { // Include + LimitInclude[IncludeFoundSELimitEffect] = true; } - } else - value = focus_spell.base[i]; - } - break; - - case SE_ReduceReuseTimer: - if (type == focusReduceRecastTime) - value = focus_spell.base[i] / 1000; - break; - - case SE_TriggerOnCast: - if (type == focusTriggerOnCast) { - if (zone->random.Roll(focus_spell.base[i])) - value = focus_spell.base2[i]; - else - value = 0; - } - break; - - case SE_BlockNextSpellFocus: - if (type == focusBlockNextSpell) { - if (zone->random.Roll(focus_spell.base[i])) - value = 1; - } - break; - - case SE_SympatheticProc: - if (type == focusSympatheticProc) { - value = focus_id; - } - break; - - case SE_FcSpellVulnerability: - if (type == focusSpellVulnerability) { - if (best_focus) { - if (focus_spell.base2[i] != 0) - value = focus_spell.base2[i]; - else - value = focus_spell.base[i]; - } else if (focus_spell.base2[i] == 0 || focus_spell.base[i] == focus_spell.base2[i]) { - value = focus_spell.base[i]; - } else { - value = zone->random.Int(focus_spell.base[i], focus_spell.base2[i]); } + break; + + case SE_LimitSpellType: + switch (focus_spell.base_value[i]) { + case 0: + if (!IsDetrimentalSpell(spell_id)) { + return 0; + } + break; + case 1: + if (!IsBeneficialSpell(spell_id)) { + return 0; + } + break; + default: + LogInfo("CalcFocusEffect: unknown limit spelltype [{}]", focus_spell.base_value[i]); + break; + } + break; + + case SE_LimitManaMin: + if (spell.mana < focus_spell.base_value[i]) { + return 0; + } + break; + + case SE_LimitManaMax: + if (spell.mana > focus_spell.base_value[i]) { + return 0; + } + break; + + case SE_LimitTarget: + if (focus_spell.base_value[i] < 0) { + if (-focus_spell.base_value[i] == spell.target_type) { // Exclude + return 0; + } + } + else { + LimitInclude[IncludeExistsSELimitTarget] = true; + if (focus_spell.base_value[i] == spell.target_type) { // Include + LimitInclude[IncludeFoundSELimitTarget] = true; + } + } + break; + + case SE_LimitCombatSkills: + if (focus_spell.base_value[i] == 0 && + (IsCombatSkill(spell_id) || IsCombatProc(spell_id))) { // Exclude Discs / Procs + return 0; + } + else if (focus_spell.base_value[i] == 1 && (!IsCombatSkill(spell_id) || !IsCombatProc(spell_id))) { // Exclude Spells + return 0; + } + + break; + + case SE_LimitSpellGroup: + if (focus_spell.base_value[i] < 0) { + if (-focus_spell.base_value[i] == spell.spell_group) { // Exclude + return 0; + } + } + else { + LimitInclude[IncludeExistsSELimitSpellGroup] = true; + if (focus_spell.base_value[i] == spell.spell_group) { // Include + LimitInclude[IncludeFoundSELimitSpellGroup] = true; + } + } + break; + + case SE_LimitCastingSkill: + if (focus_spell.base_value[i] < 0) { + if (-focus_spell.base_value[i] == spell.skill) { + return 0; + } + } + else { + LimitInclude[IncludeExistsSELimitCastingSkill] = true; + if (focus_spell.base_value[i] == spell.skill) { + LimitInclude[IncludeFoundSELimitCastingSkill] = true; + } + } + break; + + case SE_LimitClass: + // Do not use this limit more then once per spell. If multiple class, treat value like items + // would. + if (!PassLimitClass(focus_spell.base_value[i], GetClass())) { + return 0; + } + break; + + case SE_LimitRace: + if (focus_spell.base_value[i] != GetRace()) { + return 0; + } + break; + + case SE_LimitUseMin: + if (focus_spell.base_value[i] > spell.hit_number) { + return 0; + } + break; + + case SE_LimitUseType: + if (focus_spell.base_value[i] != spell.hit_number_type) { + return 0; + } + break; + + case SE_CastonFocusEffect: + if (focus_spell.base_value[i] > 0) { + Caston_spell_id = focus_spell.base_value[i]; + } + break; + + case SE_LimitSpellClass: + if (focus_spell.base_value[i] < 0) { // Exclude + if (-focus_spell.base_value[i] == spell.spell_class) { + return 0; + } + } + else { + LimitInclude[IncludeExistsSELimitSpellClass] = true; + if (focus_spell.base_value[i] == spell.spell_class) { // Include + LimitInclude[IncludeFoundSELimitSpellClass] = true; + } + } + break; + + case SE_LimitSpellSubclass: + if (focus_spell.base_value[i] < 0) { // Exclude + if (-focus_spell.base_value[i] == spell.spell_subclass) { + return 0; + } + } + else { + LimitInclude[IncludeExistsSELimitSpellSubclass] = true; + if (focus_spell.base_value[i] == spell.spell_subclass) { // Include + LimitInclude[IncludeFoundSELimitSpellSubclass] = true; + } + } + break; + + case SE_Ff_Same_Caster://hmm do i need to pass casterid from buff slot here + if (focus_spell.base_value[i] == 0) { + if (caster && casterid == caster->GetID()) { + return 0; + }//Mob casting is same as target, fail if you are casting on yourself. + } + else if (focus_spell.base_value[i] == 1) { + if (caster && casterid != caster->GetID()) { + return 0; + }//Mob casting is not same as target, fail if you are not casting on yourself. + } + break; + + case SE_Ff_CasterClass: { + + // Do not use this limit more then once per spell. If multiple class, treat value like items would. + if (caster && !PassLimitClass(focus_spell.base_value[i], caster->GetClass())) { + return 0; + } + break; } - break; - case SE_FcTwincast: - if (type == focusTwincast) - value = focus_spell.base[i]; - break; + case SE_Ff_DurationMax: + if (focus_spell.base_value[i] > spell.buff_duration) { + return 0; + } + break; - case SE_FcDamageAmt: - if (type == focusFcDamageAmt) - value = focus_spell.base[i]; - break; + case SE_Ff_Endurance_Min: + if (spell.endurance_cost < focus_spell.base_value[i]) { + return 0; + } + break; - case SE_FcDamageAmt2: - if (type == focusFcDamageAmt2) - value = focus_spell.base[i]; - break; + case SE_Ff_Endurance_Max: + if (spell.endurance_cost > focus_spell.base_value[i]) { + return 0; + } + break; - case SE_FcDamageAmtCrit: - if (type == focusFcDamageAmtCrit) - value = focus_spell.base[i]; - break; + case SE_Ff_ReuseTimeMin: + if (spell.recast_time < focus_spell.base_value[i]) { + return 0; + } + break; - case SE_FcDamageAmtIncoming: - if (type == focusFcDamageAmtIncoming) - value = focus_spell.base[i]; - break; + case SE_Ff_ReuseTimeMax: + if (spell.recast_time > focus_spell.base_value[i]) { + return 0; + } + break; - case SE_FcHealAmtIncoming: - if (type == focusFcHealAmtIncoming) - value = focus_spell.base[i]; - break; + case SE_Ff_Value_Min: + index_id = GetSpellEffectIndex(spell_id, focus_spell.limit_value[i]); + if (index_id >= 0 && spell.base_value[index_id] < focus_spell.base_value[i]) { + return 0; + } + break; - case SE_FcDamagePctCrit: - if (type == focusFcDamagePctCrit) - value = focus_spell.base[i]; - break; + case SE_Ff_Value_Max: + index_id = GetSpellEffectIndex(spell_id, focus_spell.limit_value[i]); + if (index_id >= 0 && spell.base_value[index_id] > focus_spell.base_value[i]) { + return 0; + } + break; - case SE_FcHealPctCritIncoming: - if (type == focusFcHealPctCritIncoming) - value = focus_spell.base[i]; - break; + case SE_Ff_FocusTimerMin: + if (IsFocusProcLimitTimerActive(focus_spell.id)) { + return 0; + } + else { + focus_reuse_time = focus_spell.limit_value[i]; + } + break; - case SE_FcHealAmtCrit: - if (type == focusFcHealAmtCrit) - value = focus_spell.base[i]; - break; + case SE_FFItemClass: - case SE_FcHealAmt: - if (type == focusFcHealAmt) - value = focus_spell.base[i]; - break; + /* + Limits focuses to check if cast from item clicks. Can be used to INCLUDE or EXCLUDE items by ItemType and/or SubType and/or Slots + Not used on live, going on information we have plus implemented as broadly as possible to allow all possible options. + base = item table field 'ItemType' Limit = item table field 'SubType' Max = item table field 'Slots' (this is slot bitmask) - case SE_FcHealPctIncoming: - if (type == focusFcHealPctIncoming) - value = focus_spell.base[i]; - break; + When including: Setting base, limit, max respectively to -1 will cause it to ignore that check, letting any type or slot ect be used. - case SE_FcBaseEffects: - if (type == focusFcBaseEffects) - value = focus_spell.base[i]; - break; + Special rules for excluding. base value needs to be negative < -1, if excluding all ItemTypes set to -1000. + For SubType and Slots set using same rules above as for includes. Ie. -1 for all, positive for specifics + To exclude a specific ItemType we have to do some math. The exclude value will be the negative value of (ItemType + 100). + If ItemType = 10, then SET ItemType= -110 to exclude. If its ItemType 0, then SET ItemType= -100 to exclude ect. Not ideal but it works. - case SE_FcIncreaseNumHits: - if (type == focusIncreaseNumHits) - value = focus_spell.base[i]; - break; + Usage example: [INCLUDE] Only focus spell if from click cast and is a 'defense armor' item type=10 [base= 10, limit= -1, max= -1] + Usage example: [INCLUDE] Only focus spell if from click cast and is from helmet slot' slots= 4 [base= -1, limit= -1, max= 4] + Usage example: [EXCLUDE] Do not focus spell if it is from an item click. [base= -1000, limit= -1, max= -1] + Usage example: [EXCLUDE] Do not focus spell if it is from an item click from a helmet slot. [base= -1000, limit= -1, max= 4] + Usage example: [EXCLUDE] Do not focus spell if it is from an item click and is a 'defense armor' item type=10. [base= -110, limit= -1, max= -1] - case SE_FcLimitUse: - if (type == focusFcLimitUse) - value = focus_spell.base[i]; - break; + Note: You can apply multiple includes or excludes to a single focus spell, using multiple SPA 415 limits in the spell. Ie. Check for clicks from ItemType 10 or 11. - case SE_FcMute: - if (type == focusFcMute) - value = focus_spell.base[i]; - break; + */ + has_item_limit_check = true; + if (casting_spell_inventory_slot && casting_spell_inventory_slot != -1) { + if (IsClient() && casting_spell_slot == EQ::spells::CastingSlot::Item && casting_spell_inventory_slot != 0xFFFFFFFF) { + auto item = CastToClient()->GetInv().GetItem(casting_spell_inventory_slot); + if (item && item->GetItem()) { + //If ItemType set to < -1, then we will exclude either all Subtypes (-1000), or specific items by ItemType, SubType or Slot. See above for rules. + if (focus_spell.base_value[i] < -1) { //Excludes + bool exclude_this_item = true; + int tmp_itemtype = (item->GetItem()->ItemType + 100) * -1; + //ItemType (if set to -1000, ignore and exclude any ItemType) + if (focus_spell.base_value[i] < -1 && focus_spell.base_value[i] != -1000) { + if (focus_spell.base_value[i] != tmp_itemtype) { + exclude_this_item = false; + } + } + //SubType (if set to -1, ignore and exclude all SubTypes) + if (focus_spell.limit_value[i] >= 0) { + if (focus_spell.limit_value[i] != item->GetItem()->SubType) { + exclude_this_item = false; + } + } + //item slot bitmask (if set to -1, ignore and exclude all SubTypes) + if (focus_spell.max_value[i] >= 0) { + if (focus_spell.max_value[i] != item->GetItem()->Slots) { + exclude_this_item = false; + } + } + if (exclude_this_item) { + return 0; + } + } + else {//Includes + LimitInclude[IncludeExistsSEFFItemClass] = true; + bool include_this_item = true; + //ItemType (if set to -1, ignore and include any ItemType) + if (focus_spell.base_value[i] >= 0) { + if (focus_spell.base_value[i] != item->GetItem()->ItemType) { + include_this_item = false; + } + } + //SubType (if set to -1, ignore and include any SubType) + if (focus_spell.limit_value[i] >= 0) { + if (focus_spell.limit_value[i] != item->GetItem()->SubType) { + include_this_item = false; + } + } + //item slot bitmask (if set to -1, ignore and include any slot) + if (focus_spell.max_value[i] >= 0) { + if (focus_spell.max_value[i] != item->GetItem()->Slots) { + include_this_item = false; + } + } - case SE_FcStunTimeMod: - if (type == focusFcStunTimeMod) - value = focus_spell.base[i]; - break; + if (include_this_item) { + LimitInclude[IncludeFoundSEFFItemClass] = true; + } + } + } + } + } + //If this is checking that focus can only be cast from an item, then if its not cast from item fail. + else if (focus_spell.base_value[i] >= -1) { + return 0; + } + //If we are checking to exclude items from a focus then do not fail unless the above check fails. + break; - case SE_FcTimerRefresh: - if (type == focusFcTimerRefresh) - value = focus_spell.base[i]; - break; + // handle effects + case SE_ImprovedDamage: + if (type == focusImprovedDamage) { + value = GetFocusRandomEffectivenessValue(focus_spell.base_value[i], focus_spell.limit_value[i], best_focus); + } + break; + + case SE_ImprovedDamage2: + if (type == focusImprovedDamage2) { + value = GetFocusRandomEffectivenessValue(focus_spell.base_value[i], focus_spell.limit_value[i], best_focus); + } + break; + + case SE_Fc_Amplify_Mod: + if (type == focusFcAmplifyMod && focus_spell.base_value[i] > value) { + value = focus_spell.base_value[i]; + } + break; + + case SE_ImprovedHeal: + if (type == focusImprovedHeal) { + value = GetFocusRandomEffectivenessValue(focus_spell.base_value[i], focus_spell.limit_value[i], best_focus); + } + break; + + case SE_ReduceManaCost: + if (type == focusManaCost) { + value = GetFocusRandomEffectivenessValue(focus_spell.base_value[i], focus_spell.limit_value[i], best_focus); + } + break; + + case SE_IncreaseSpellHaste: + if (type == focusSpellHaste && focus_spell.base_value[i] > value) { + value = focus_spell.base_value[i]; + try_apply_to_item_click = is_from_item_click ? true : false; + } + break; + + case SE_Fc_CastTimeMod2: + if (type == focusFcCastTimeMod2 && focus_spell.base_value[i] > value) { + value = focus_spell.base_value[i]; + try_apply_to_item_click = is_from_item_click ? true : false; + } + break; + + case SE_Fc_CastTimeAmt: + if (type == focusFcCastTimeAmt && focus_spell.base_value[i] > value) { + value = focus_spell.base_value[i]; + try_apply_to_item_click = is_from_item_click ? true : false; + } + break; + + case SE_IncreaseSpellDuration: + if (type == focusSpellDuration && focus_spell.base_value[i] > value) { + value = focus_spell.base_value[i]; + } + break; + + case SE_SpellDurationIncByTic: + if (type == focusSpellDurByTic && focus_spell.base_value[i] > value) { + value = focus_spell.base_value[i]; + } + break; + + case SE_SwarmPetDuration: + if (type == focusSwarmPetDuration && focus_spell.base_value[i] > value) { + value = focus_spell.base_value[i]; + } + break; + + case SE_IncreaseRange: + if (type == focusRange && focus_spell.base_value[i] > value) { + value = focus_spell.base_value[i]; + } + break; + + case SE_ReduceReagentCost: + if (type == focusReagentCost) { + value = GetFocusRandomEffectivenessValue(focus_spell.base_value[i], focus_spell.limit_value[i], best_focus); + } + break; + + case SE_PetPowerIncrease: + if (type == focusPetPower && focus_spell.base_value[i] > value) { + value = focus_spell.base_value[i]; + } + break; + + case SE_SpellResistReduction: + if (type == focusResistRate) { + value = GetFocusRandomEffectivenessValue(focus_spell.base_value[i], focus_spell.limit_value[i], best_focus); + } + break; + + case SE_Fc_ResistIncoming: + if (type == focusFcResistIncoming && focus_spell.base_value[i] > value) { + value = focus_spell.base_value[i]; + } + break; + + case SE_SpellHateMod: + if (type == focusSpellHateMod) { + value = GetFocusRandomEffectivenessValue(focus_spell.base_value[i], focus_spell.limit_value[i], best_focus); + } + break; + + case SE_ReduceReuseTimer: + if (type == focusReduceRecastTime) { + value = focus_spell.base_value[i] / 1000; + try_apply_to_item_click = is_from_item_click ? true : false; + } + break; + + case SE_TriggerOnCast: + if (type == focusTriggerOnCast) { + if (zone->random.Roll(focus_spell.base_value[i])) { + value = focus_spell.limit_value[i]; + } + else { + value = 0; + } + } + break; + + case SE_BlockNextSpellFocus: + if (type == focusBlockNextSpell) { + if (zone->random.Roll(focus_spell.base_value[i])) { + value = 1; + } + } + break; + + case SE_SympatheticProc: + if (type == focusSympatheticProc) { + value = focus_id; + } + break; + + case SE_FcSpellVulnerability: + if (type == focusSpellVulnerability) { + value = GetFocusRandomEffectivenessValue(focus_spell.base_value[i], focus_spell.limit_value[i], best_focus); + } + break; + + case SE_Fc_Spell_Damage_Pct_IncomingPC: + if (type == focusFcSpellDamagePctIncomingPC) { + value = GetFocusRandomEffectivenessValue(focus_spell.base_value[i], focus_spell.limit_value[i], best_focus); + } + break; + + case SE_FcTwincast: + if (type == focusTwincast && !IsEffectInSpell(spell_id, SE_TwinCastBlocker)) { + value = focus_spell.base_value[i]; + } + break; + + case SE_FcDamageAmt: + if (type == focusFcDamageAmt) { + value = focus_spell.base_value[i]; + } + break; + + case SE_FcDamageAmt2: + if (type == focusFcDamageAmt2) { + value = focus_spell.base_value[i]; + } + break; + + case SE_Fc_Amplify_Amt: + if (type == focusFcAmplifyAmt) { + value = focus_spell.base_value[i]; + } + break; + + case SE_FcDamageAmtCrit: + if (type == focusFcDamageAmtCrit) { + value = focus_spell.base_value[i]; + } + break; + + case SE_FcDamageAmtIncoming: + if (type == focusFcDamageAmtIncoming) { + value = focus_spell.base_value[i]; + } + break; + + case SE_Fc_Spell_Damage_Amt_IncomingPC: + if (type == focusFcSpellDamageAmtIncomingPC) { + value = focus_spell.base_value[i]; + } + break; + + case SE_FcHealAmtIncoming: + if (type == focusFcHealAmtIncoming) { + value = focus_spell.base_value[i]; + } + break; + + case SE_FcDamagePctCrit: + if (type == focusFcDamagePctCrit) { + value = focus_spell.base_value[i]; + } + break; + + case SE_FcHealPctCritIncoming: + if (type == focusFcHealPctCritIncoming) { + value = GetFocusRandomEffectivenessValue(focus_spell.base_value[i], focus_spell.limit_value[i], best_focus); + } + break; + + case SE_FcHealAmtCrit: + if (type == focusFcHealAmtCrit) { + value = focus_spell.base_value[i]; + } + break; + + case SE_FcHealAmt: + if (type == focusFcHealAmt) { + value = focus_spell.base_value[i]; + } + break; + + case SE_FcHealPctIncoming: + if (type == focusFcHealPctIncoming) { + value = GetFocusRandomEffectivenessValue(focus_spell.base_value[i], focus_spell.limit_value[i], best_focus); + } + break; + + case SE_FcBaseEffects: + if (type == focusFcBaseEffects) { + value = focus_spell.base_value[i]; + } + break; + + case SE_FcIncreaseNumHits: + if (type == focusIncreaseNumHits) { + value = focus_spell.base_value[i]; + } + break; + + case SE_FcLimitUse: + if (type == focusFcLimitUse) { + value = focus_spell.base_value[i]; + } + break; + + case SE_FcMute: + if (type == focusFcMute) { + value = focus_spell.base_value[i]; + } + break; + + case SE_FcStunTimeMod: + if (type == focusFcStunTimeMod) { + value = focus_spell.base_value[i]; + } + break; + + case SE_FcTimerRefresh: + if (type == focusFcTimerRefresh) { + value = focus_spell.base_value[i]; + } + break; + + case SE_FcTimerLockout: + if (type == focusFcTimerLockout) { + value = focus_spell.base_value[i]; + } + break; + + case SE_Fc_Cast_Spell_On_Land: + if (type == focusFcCastSpellOnLand) { + if (zone->random.Roll(focus_spell.base_value[i])) { + value = focus_spell.limit_value[i]; + } + break; + } #if EQDEBUG >= 6 - // this spits up a lot of garbage when calculating spell focuses - // since they have all kinds of extra effects on them. - default: - LogInfo("CalcFocusEffect: unknown effectid [{}]", - focus_spell.effectid[i]); + // this spits up a lot of garbage when calculating spell focuses + // since they have all kinds of extra effects on them. + default: + LogInfo("CalcFocusEffect: unknown effectid [{}]", + focus_spell.effect_id[i]); #endif } } for (int e = 0; e < MaxLimitInclude; e += 2) { - if (LimitInclude[e] && !LimitInclude[e + 1]) + if (LimitInclude[e] && !LimitInclude[e + 1]) { return 0; + } + } + + /* + For item click cast/recast focus modifiers. Only use if SPA 415 exists. + This is an item click but does not have SPA 415 limiter. Fail here. + */ + + if (try_apply_to_item_click && !has_item_limit_check) { + return 0; } if (Caston_spell_id) { - if (IsValidSpell(Caston_spell_id) && (Caston_spell_id != spell_id)) - SpellFinished(Caston_spell_id, this, EQ::spells::CastingSlot::Item, 0, -1, spells[Caston_spell_id].ResistDiff); + if (IsValidSpell(Caston_spell_id) && (Caston_spell_id != spell_id)) { + SpellFinished( + Caston_spell_id, + this, + EQ::spells::CastingSlot::Item, + 0, + -1, + spells[Caston_spell_id].resist_difficulty + ); + } + } + + if (focus_reuse_time) { + SetFocusProcLimitTimer(focus_spell.id, focus_reuse_time); } return (value * lvlModifier / 100); } +void Mob::TryTriggerOnCastFocusEffect(focusType type, uint16 spell_id) +{ + if (IsBardSong(spell_id)) { + return; + } + + if (!IsValidSpell(spell_id)) { + return; + } + + int32 focus_spell_id = 0; + int32 proc_spellid = 0; + + // item focus + if (IsClient() && itembonuses.FocusEffects[type]) { + const EQ::ItemData *temp_item = nullptr; + + for (int x = EQ::invslot::EQUIPMENT_BEGIN; x <= EQ::invslot::EQUIPMENT_END; x++) { + temp_item = nullptr; + EQ::ItemInstance *ins = CastToClient()->GetInv().GetItem(x); + if (!ins) { + continue; + } + temp_item = ins->GetItem(); + if (temp_item && temp_item->Focus.Effect > 0 && IsValidSpell(temp_item->Focus.Effect)) { + focus_spell_id = temp_item->Focus.Effect; + if (!IsEffectInSpell(focus_spell_id, SE_TriggerOnCast)) { + continue; + } + + proc_spellid = CalcFocusEffect(type, focus_spell_id, spell_id); + if (proc_spellid) { + TryTriggerOnCastProc(focus_spell_id, spell_id, proc_spellid); + } + } + + for (int y = EQ::invaug::SOCKET_BEGIN; y <= EQ::invaug::SOCKET_END; ++y) { + EQ::ItemInstance *aug = ins->GetAugment(y); + if (aug) { + const EQ::ItemData *temp_item_aug = aug->GetItem(); + if (temp_item_aug && temp_item_aug->Focus.Effect > 0 && IsValidSpell(temp_item_aug->Focus.Effect)) { + focus_spell_id = temp_item_aug->Focus.Effect; + + if (!IsEffectInSpell(focus_spell_id, SE_TriggerOnCast)) { + continue; + } + + proc_spellid = CalcFocusEffect(type, focus_spell_id, spell_id); + if (proc_spellid) { + TryTriggerOnCastProc(focus_spell_id, spell_id, proc_spellid); + } + } + } + } + } + } + + // Spell Focus + if (spellbonuses.FocusEffects[type]) { + int buff_slot = 0; + for (buff_slot = 0; buff_slot < GetMaxTotalSlots(); buff_slot++) { + focus_spell_id = buffs[buff_slot].spellid; + if (!IsValidSpell(focus_spell_id)) { + continue; + } + + if (!IsEffectInSpell(focus_spell_id, SE_TriggerOnCast)) { + continue; + } + + proc_spellid = CalcFocusEffect(type, focus_spell_id, spell_id); + if (proc_spellid) { + TryTriggerOnCastProc(focus_spell_id, spell_id, proc_spellid); + CheckNumHitsRemaining(NumHit::MatchingSpells, buff_slot); + } + } + } + + // Only use one of this focus per AA effect. + if (IsClient() && aabonuses.FocusEffects[type]) { + for (const auto &aa : aa_ranks) { + auto ability_rank = zone->GetAlternateAdvancementAbilityAndRank(aa.first, aa.second.first); + auto ability = ability_rank.first; + auto rank = ability_rank.second; + + if (!ability) { + continue; + } + + if (rank->effects.empty()) { + continue; + } + + proc_spellid = CastToClient()->CalcAAFocus(type, *rank, spell_id); + if (proc_spellid) { + TryTriggerOnCastProc(0, spell_id, proc_spellid); + } + } + } +} + +bool Mob::TryTriggerOnCastProc(uint16 focusspellid, uint16 spell_id, uint16 proc_spellid) +{ + // We confirm spell_id and focuspellid are valid before passing into this. + if (IsValidSpell(proc_spellid) && spell_id != focusspellid && spell_id != proc_spellid) { + Mob* proc_target = GetTarget(); + if (proc_target) { + SpellFinished(proc_spellid, proc_target, EQ::spells::CastingSlot::Item, 0, -1, spells[proc_spellid].resist_difficulty); + return true; + } + // Edge cases where proc spell does not require a target such as PBAE, allows proc to still occur even if target potentially dead. Live spells exist with PBAE procs. + else if (!SpellRequiresTarget(proc_spellid)) { + SpellFinished(proc_spellid, this, EQ::spells::CastingSlot::Item, 0, -1, spells[proc_spellid].resist_difficulty); + return true; + } + } + return false; +} + uint16 Client::GetSympatheticFocusEffect(focusType type, uint16 spell_id) { if (IsBardSong(spell_id)) @@ -5215,7 +6233,7 @@ uint16 Client::GetSympatheticFocusEffect(focusType type, uint16 spell_id) { proc_spellid = CalcFocusEffect(type, TempItem->Focus.Effect, spell_id); if (IsValidSpell(proc_spellid)){ - ProcChance = GetSympatheticProcChances(spell_id, spells[TempItem->Focus.Effect].base[0], TempItem->ProcRate); + ProcChance = GetSympatheticProcChances(spell_id, spells[TempItem->Focus.Effect].base_value[0], TempItem->ProcRate); if(zone->random.Roll(ProcChance)) SympatheticProcList.push_back(proc_spellid); } @@ -5234,7 +6252,7 @@ uint16 Client::GetSympatheticFocusEffect(focusType type, uint16 spell_id) { if (TempItemAug && TempItemAug->Focus.Effect > 0 && IsValidSpell(TempItemAug->Focus.Effect)) { proc_spellid = CalcFocusEffect(type, TempItemAug->Focus.Effect, spell_id); if (IsValidSpell(proc_spellid)){ - ProcChance = GetSympatheticProcChances(spell_id, spells[TempItemAug->Focus.Effect].base[0], TempItemAug->ProcRate); + ProcChance = GetSympatheticProcChances(spell_id, spells[TempItemAug->Focus.Effect].base_value[0], TempItemAug->ProcRate); if(zone->random.Roll(ProcChance)) SympatheticProcList.push_back(proc_spellid); } @@ -5289,7 +6307,7 @@ uint16 Client::GetSympatheticFocusEffect(focusType type, uint16 spell_id) { proc_spellid = CalcAAFocus(type, *rank, spell_id); if (IsValidSpell(proc_spellid)) { - ProcChance = GetSympatheticProcChances(spell_id, rank->effects[0].base1); + ProcChance = GetSympatheticProcChances(spell_id, rank->effects[0].base_value); if (zone->random.Roll(ProcChance)) SympatheticProcList.push_back(proc_spellid); } @@ -5307,23 +6325,23 @@ uint16 Client::GetSympatheticFocusEffect(focusType type, uint16 spell_id) { return 0; } -int16 Client::GetFocusEffect(focusType type, uint16 spell_id) +int32 Client::GetFocusEffect(focusType type, uint16 spell_id, Mob *caster) { - if (IsBardSong(spell_id) && type != focusFcBaseEffects && type != focusSpellDuration) + if (IsBardSong(spell_id) && type != focusFcBaseEffects && type != focusSpellDuration && type != focusReduceRecastTime) { return 0; + } - if (spells[spell_id].not_focusable) - return 0; + int32 realTotal = 0; + int32 realTotal2 = 0; + int32 realTotal3 = 0; - int16 realTotal = 0; - int16 realTotal2 = 0; - int16 realTotal3 = 0; bool rand_effectiveness = false; //Improved Healing, Damage & Mana Reduction are handled differently in that some are random percentages //In these cases we need to find the most powerful effect, so that each piece of gear wont get its own chance - if(RuleB(Spells, LiveLikeFocusEffects) && (type == focusManaCost || type == focusImprovedHeal || type == focusImprovedDamage || type == focusImprovedDamage2 || type == focusResistRate)) + if (RuleB(Spells, LiveLikeFocusEffects) && CanFocusUseRandomEffectivenessByType(type)) { rand_effectiveness = true; + } //Check if item focus effect exists for the client. if (itembonuses.FocusEffects[type]){ @@ -5331,9 +6349,9 @@ int16 Client::GetFocusEffect(focusType type, uint16 spell_id) const EQ::ItemData* TempItem = nullptr; const EQ::ItemData* UsedItem = nullptr; uint16 UsedFocusID = 0; - int16 Total = 0; - int16 focus_max = 0; - int16 focus_max_real = 0; + int32 Total = 0; + int32 focus_max = 0; + int32 focus_max_real = 0; //item focus for (int x = EQ::invslot::EQUIPMENT_BEGIN; x <= EQ::invslot::EQUIPMENT_END; x++) @@ -5494,14 +6512,14 @@ int16 Client::GetFocusEffect(focusType type, uint16 spell_id) if (spellbonuses.FocusEffects[type]){ //Spell Focus - int16 Total2 = 0; - int16 focus_max2 = 0; - int16 focus_max_real2 = 0; + int32 Total2 = 0; + int32 focus_max2 = 0; + int32 focus_max_real2 = 0; int buff_tracker = -1; int buff_slot = 0; - uint16 focusspellid = 0; - uint16 focusspell_tracker = 0; + int32 focusspellid = 0; + int32 focusspell_tracker = 0; int buff_max = GetMaxTotalSlots(); for (buff_slot = 0; buff_slot < buff_max; buff_slot++) { focusspellid = buffs[buff_slot].spellid; @@ -5509,7 +6527,7 @@ int16 Client::GetFocusEffect(focusType type, uint16 spell_id) continue; if(rand_effectiveness) { - focus_max2 = CalcFocusEffect(type, focusspellid, spell_id, true); + focus_max2 = CalcFocusEffect(type, focusspellid, spell_id, true, buffs[buff_slot].casterid, caster); if (focus_max2 > 0 && focus_max_real2 >= 0 && focus_max2 > focus_max_real2) { focus_max_real2 = focus_max2; buff_tracker = buff_slot; @@ -5521,7 +6539,7 @@ int16 Client::GetFocusEffect(focusType type, uint16 spell_id) } } else { - Total2 = CalcFocusEffect(type, focusspellid, spell_id); + Total2 = CalcFocusEffect(type, focusspellid, spell_id, false, buffs[buff_slot].casterid, caster); if (Total2 > 0 && realTotal2 >= 0 && Total2 > realTotal2) { realTotal2 = Total2; buff_tracker = buff_slot; @@ -5534,11 +6552,16 @@ int16 Client::GetFocusEffect(focusType type, uint16 spell_id) } } + uint16 original_caster_id = 0; + if (buff_tracker >= 0 && buffs[buff_tracker].casterid > 0) { + original_caster_id = buffs[buff_tracker].casterid; + } + if(focusspell_tracker && rand_effectiveness && focus_max_real2 != 0) - realTotal2 = CalcFocusEffect(type, focusspell_tracker, spell_id); + realTotal2 = CalcFocusEffect(type, focusspell_tracker, spell_id, false, original_caster_id, caster); // For effects like gift of mana that only fire once, save the spellid into an array that consists of all available buff slots. - if(buff_tracker >= 0 && buffs[buff_tracker].numhits > 0) { + if(buff_tracker >= 0 && buffs[buff_tracker].hit_number > 0) { m_spellHitsLeft[buff_tracker] = focusspell_tracker; } } @@ -5547,7 +6570,7 @@ int16 Client::GetFocusEffect(focusType type, uint16 spell_id) // AA Focus if (aabonuses.FocusEffects[type]){ - int16 Total3 = 0; + int32 Total3 = 0; for (const auto &aa : aa_ranks) { auto ability_rank = zone->GetAlternateAdvancementAbilityAndRank(aa.first, aa.second.first); @@ -5577,35 +6600,34 @@ int16 Client::GetFocusEffect(focusType type, uint16 spell_id) //by reagent conservation for obvious reasons. //Non-Live like feature to allow for an additive focus bonus to be applied from foci that are placed in worn slot. (No limit checks) - int16 worneffect_bonus = 0; + int32 worneffect_bonus = 0; if (RuleB(Spells, UseAdditiveFocusFromWornSlot)) worneffect_bonus = itembonuses.FocusEffectsWorn[type]; return realTotal + realTotal2 + realTotal3 + worneffect_bonus; } -int16 NPC::GetFocusEffect(focusType type, uint16 spell_id) { +int32 NPC::GetFocusEffect(focusType type, uint16 spell_id, Mob* caster) { - if (spells[spell_id].not_focusable) - return 0; + int32 realTotal = 0; + int32 realTotal2 = 0; - int16 realTotal = 0; - int16 realTotal2 = 0; bool rand_effectiveness = false; //Improved Healing, Damage & Mana Reduction are handled differently in that some are random percentages //In these cases we need to find the most powerful effect, so that each piece of gear wont get its own chance - if(RuleB(Spells, LiveLikeFocusEffects) && (type == focusManaCost || type == focusImprovedHeal || type == focusImprovedDamage || type == focusImprovedDamage2)) + if (RuleB(Spells, LiveLikeFocusEffects) && CanFocusUseRandomEffectivenessByType(type)) { rand_effectiveness = true; + } if (RuleB(Spells, NPC_UseFocusFromItems) && itembonuses.FocusEffects[type]){ const EQ::ItemData* TempItem = nullptr; const EQ::ItemData* UsedItem = nullptr; uint16 UsedFocusID = 0; - int16 Total = 0; - int16 focus_max = 0; - int16 focus_max_real = 0; + int32 Total = 0; + int32 focus_max = 0; + int32 focus_max_real = 0; //item focus for (int i = EQ::invslot::EQUIPMENT_BEGIN; i <= EQ::invslot::EQUIPMENT_END; i++){ @@ -5648,17 +6670,17 @@ int16 NPC::GetFocusEffect(focusType type, uint16 spell_id) { realTotal = CalcFocusEffect(type, UsedFocusID, spell_id); } - if (RuleB(Spells, NPC_UseFocusFromSpells) && spellbonuses.FocusEffects[type]){ + if ((RuleB(Spells, NPC_UseFocusFromSpells) || IsTargetedFocusEffect(type)) && spellbonuses.FocusEffects[type]){ //Spell Focus - int16 Total2 = 0; - int16 focus_max2 = 0; - int16 focus_max_real2 = 0; + int32 Total2 = 0; + int32 focus_max2 = 0; + int32 focus_max_real2 = 0; int buff_tracker = -1; int buff_slot = 0; - uint16 focusspellid = 0; - uint16 focusspell_tracker = 0; + int32 focusspellid = 0; + int32 focusspell_tracker = 0; int buff_max = GetMaxTotalSlots(); for (buff_slot = 0; buff_slot < buff_max; buff_slot++) { focusspellid = buffs[buff_slot].spellid; @@ -5666,7 +6688,7 @@ int16 NPC::GetFocusEffect(focusType type, uint16 spell_id) { continue; if(rand_effectiveness) { - focus_max2 = CalcFocusEffect(type, focusspellid, spell_id, true); + focus_max2 = CalcFocusEffect(type, focusspellid, spell_id, true, buffs[buff_slot].casterid, caster); if (focus_max2 > 0 && focus_max_real2 >= 0 && focus_max2 > focus_max_real2) { focus_max_real2 = focus_max2; buff_tracker = buff_slot; @@ -5678,7 +6700,7 @@ int16 NPC::GetFocusEffect(focusType type, uint16 spell_id) { } } else { - Total2 = CalcFocusEffect(type, focusspellid, spell_id); + Total2 = CalcFocusEffect(type, focusspellid, spell_id, false, buffs[buff_slot].casterid, caster); if (Total2 > 0 && realTotal2 >= 0 && Total2 > realTotal2) { realTotal2 = Total2; buff_tracker = buff_slot; @@ -5691,11 +6713,17 @@ int16 NPC::GetFocusEffect(focusType type, uint16 spell_id) { } } - if(focusspell_tracker && rand_effectiveness && focus_max_real2 != 0) - realTotal2 = CalcFocusEffect(type, focusspell_tracker, spell_id); + uint16 original_caster_id = 0; + if (buff_tracker >= 0 && buffs[buff_tracker].casterid > 0) { + original_caster_id = buffs[buff_tracker].casterid; + } + + if (focusspell_tracker && rand_effectiveness && focus_max_real2 != 0) { + realTotal2 = CalcFocusEffect(type, focusspell_tracker, spell_id, false, original_caster_id, caster); + } // For effects like gift of mana that only fire once, save the spellid into an array that consists of all available buff slots. - if(buff_tracker >= 0 && buffs[buff_tracker].numhits > 0) { + if(buff_tracker >= 0 && buffs[buff_tracker].hit_number > 0) { m_spellHitsLeft[buff_tracker] = focusspell_tracker; } } @@ -5735,16 +6763,16 @@ void Mob::CheckNumHitsRemaining(NumHit type, int32 buff_slot, uint16 spell_id) //Spell specific procs [Type 7,10,11] if (IsValidSpell(spell_id)) { for (int d = 0; d < buff_max; d++) { - if (buffs[d].spellid == spell_id && buffs[d].numhits > 0 && - spells[buffs[d].spellid].numhitstype == static_cast(type)) { + if (buffs[d].spellid == spell_id && buffs[d].hit_number > 0 && + spells[buffs[d].spellid].hit_number_type == static_cast(type)) { #ifdef BOTS buff_name = spells[buffs[d].spellid].name; - buff_counter = (buffs[d].numhits - 1); + buff_counter = (buffs[d].hit_number - 1); buff_update = true; #endif - if (--buffs[d].numhits == 0) { + if (--buffs[d].hit_number == 0) { CastOnNumHitFade(buffs[d].spellid); if (!TryFadeEffect(d)) BuffFadeBySlot(d, true); @@ -5755,11 +6783,11 @@ void Mob::CheckNumHitsRemaining(NumHit type, int32 buff_slot, uint16 spell_id) } } else if (type == NumHit::MatchingSpells) { if (buff_slot >= 0) { - if (--buffs[buff_slot].numhits == 0) { + if (--buffs[buff_slot].hit_number == 0) { #ifdef BOTS buff_name = spells[buffs[buff_slot].spellid].name; - buff_counter = (buffs[buff_slot].numhits - 1); + buff_counter = (buffs[buff_slot].hit_number - 1); buff_update = true; #endif @@ -5778,11 +6806,11 @@ void Mob::CheckNumHitsRemaining(NumHit type, int32 buff_slot, uint16 spell_id) #ifdef BOTS buff_name = spells[buffs[d].spellid].name; - buff_counter = (buffs[d].numhits - 1); + buff_counter = (buffs[d].hit_number - 1); buff_update = true; #endif - if (--buffs[d].numhits == 0) { + if (--buffs[d].hit_number == 0) { CastOnNumHitFade(buffs[d].spellid); m_spellHitsLeft[d] = 0; if (!TryFadeEffect(d)) @@ -5795,16 +6823,16 @@ void Mob::CheckNumHitsRemaining(NumHit type, int32 buff_slot, uint16 spell_id) } } else { for (int d = 0; d < buff_max; d++) { - if (IsValidSpell(buffs[d].spellid) && buffs[d].numhits > 0 && - spells[buffs[d].spellid].numhitstype == static_cast(type)) { + if (IsValidSpell(buffs[d].spellid) && buffs[d].hit_number > 0 && + spells[buffs[d].spellid].hit_number_type == static_cast(type)) { #ifdef BOTS buff_name = spells[buffs[d].spellid].name; - buff_counter = (buffs[d].numhits - 1); + buff_counter = (buffs[d].hit_number - 1); buff_update = true; #endif - if (--buffs[d].numhits == 0) { + if (--buffs[d].hit_number == 0) { CastOnNumHitFade(buffs[d].spellid); if (!TryFadeEffect(d)) BuffFadeBySlot(d, true); @@ -5842,7 +6870,7 @@ void Mob::CheckNumHitsRemaining(NumHit type, int32 buff_slot, uint16 spell_id) uint16 Mob::GetProcID(uint16 spell_id, uint8 effect_index) { if (!RuleB(Spells, SHDProcIDOffByOne)) // UF+ spell files - return spells[spell_id].base[effect_index]; + return spells[spell_id].base_value[effect_index]; // We should actually just be checking if the mob is SHD, but to not force // custom servers to create new spells, we will still do this @@ -5859,9 +6887,9 @@ uint16 Mob::GetProcID(uint16 spell_id, uint8 effect_index) } if (sk && !other) - return spells[spell_id].base[effect_index] + 1; + return spells[spell_id].base_value[effect_index] + 1; else - return spells[spell_id].base[effect_index]; + return spells[spell_id].base_value[effect_index]; } bool Mob::TryDivineSave() @@ -5874,16 +6902,16 @@ bool Mob::TryDivineSave() -If desired, additional spells can be triggered from the AA/item/spell effect, generally a heal. */ - int32 SuccessChance = aabonuses.DivineSaveChance[0] + itembonuses.DivineSaveChance[0] + spellbonuses.DivineSaveChance[0]; + int32 SuccessChance = aabonuses.DivineSaveChance[SBIndex::DIVINE_SAVE_CHANCE] + itembonuses.DivineSaveChance[SBIndex::DIVINE_SAVE_CHANCE] + spellbonuses.DivineSaveChance[SBIndex::DIVINE_SAVE_CHANCE]; if (SuccessChance && zone->random.Roll(SuccessChance)) { SetHP(1); int32 EffectsToTry[] = { - aabonuses.DivineSaveChance[1], - itembonuses.DivineSaveChance[1], - spellbonuses.DivineSaveChance[1] + aabonuses.DivineSaveChance[SBIndex::DIVINE_SAVE_SPELL_TRIGGER_ID], + itembonuses.DivineSaveChance[SBIndex::DIVINE_SAVE_SPELL_TRIGGER_ID], + spellbonuses.DivineSaveChance[SBIndex::DIVINE_SAVE_SPELL_TRIGGER_ID] }; //Fade the divine save effect here after saving the old effects off. //That way, if desired, the effect could apply SE_DivineSave again. @@ -5896,7 +6924,7 @@ bool Mob::TryDivineSave() } } - SpellOnTarget(4789, this); //Touch of the Divine=4789, an Invulnerability/HoT/Purify effect + SpellOnTarget(SPELL_TOUCH_OF_THE_DIVINE, this); //Touch of the Divine=4789, an Invulnerability/HoT/Purify effect SendHPUpdate(); return true; } @@ -5918,10 +6946,10 @@ bool Mob::TryDeathSave() { -In later expansions this SE_DeathSave was given a level limit and a heal value in its effect data. */ - if (spellbonuses.DeathSave[0]){ + if (spellbonuses.DeathSave[SBIndex::DEATH_SAVE_TYPE]){ int SuccessChance = 0; - int buffSlot = spellbonuses.DeathSave[1]; + int buffSlot = spellbonuses.DeathSave[SBIndex::DEATH_SAVE_BUFFSLOT]; int32 UD_HealMod = 0; int HealAmt = 300; //Death Pact max Heal @@ -5936,12 +6964,12 @@ bool Mob::TryDeathSave() { if(zone->random.Roll(SuccessChance)) { - if(spellbonuses.DeathSave[0] == 2) + if(spellbonuses.DeathSave[SBIndex::DEATH_SAVE_TYPE] == 2) HealAmt = RuleI(Spells, DivineInterventionHeal); //8000HP is how much LIVE Divine Intervention max heals //Check if bonus Heal amount can be applied ([3] Bonus Heal [2] Level limit) - if (spellbonuses.DeathSave[3] && (GetLevel() >= spellbonuses.DeathSave[2])) - HealAmt += spellbonuses.DeathSave[3]; + if (spellbonuses.DeathSave[SBIndex::DEATH_SAVE_HEAL_AMT] && (GetLevel() >= spellbonuses.DeathSave[SBIndex::DEATH_SAVE_MIN_LEVEL_FOR_HEAL])) + HealAmt += spellbonuses.DeathSave[SBIndex::DEATH_SAVE_HEAL_AMT]; if ((GetMaxHP() - GetHP()) < HealAmt) HealAmt = GetMaxHP() - GetHP(); @@ -5949,7 +6977,7 @@ bool Mob::TryDeathSave() { SetHP((GetHP()+HealAmt)); Message(263, "The gods have healed you for %i points of damage.", HealAmt); - if(spellbonuses.DeathSave[0] == 2) + if(spellbonuses.DeathSave[SBIndex::DEATH_SAVE_TYPE] == 2) entity_list.MessageCloseString( this, false, @@ -5973,12 +7001,12 @@ bool Mob::TryDeathSave() { if(zone->random.Roll(SuccessChance)) { - if(spellbonuses.DeathSave[0] == 2) + if(spellbonuses.DeathSave[SBIndex::DEATH_SAVE_TYPE] == 2) HealAmt = RuleI(Spells, DivineInterventionHeal); //Check if bonus Heal amount can be applied ([3] Bonus Heal [2] Level limit) - if (spellbonuses.DeathSave[3] && (GetLevel() >= spellbonuses.DeathSave[2])) - HealAmt += spellbonuses.DeathSave[3]; + if (spellbonuses.DeathSave[SBIndex::DEATH_SAVE_HEAL_AMT] && (GetLevel() >= spellbonuses.DeathSave[SBIndex::DEATH_SAVE_MIN_LEVEL_FOR_HEAL])) + HealAmt += spellbonuses.DeathSave[SBIndex::DEATH_SAVE_HEAL_AMT]; HealAmt = HealAmt*UD_HealMod/100; @@ -5988,7 +7016,7 @@ bool Mob::TryDeathSave() { SetHP((GetHP()+HealAmt)); Message(263, "The gods have healed you for %i points of damage.", HealAmt); - if(spellbonuses.DeathSave[0] == 2) + if(spellbonuses.DeathSave[SBIndex::DEATH_SAVE_TYPE] == 2) entity_list.MessageCloseString( this, false, @@ -6054,8 +7082,8 @@ float Mob::GetSympatheticProcChances(uint16 spell_id, int16 ProcRateMod, int32 I int16 Mob::GetSympatheticSpellProcRate(uint16 spell_id) { for (int i = 0; i < EFFECT_COUNT; i++){ - if (spells[spell_id].effectid[i] == SE_SympatheticProc) - return spells[spell_id].base[i]; + if (spells[spell_id].effect_id[i] == SE_SympatheticProc) + return spells[spell_id].base_value[i]; } return 0; @@ -6064,8 +7092,8 @@ int16 Mob::GetSympatheticSpellProcRate(uint16 spell_id) uint16 Mob::GetSympatheticSpellProcID(uint16 spell_id) { for (int i = 0; i < EFFECT_COUNT; i++){ - if (spells[spell_id].effectid[i] == SE_SympatheticProc) - return spells[spell_id].base2[i]; + if (spells[spell_id].effect_id[i] == SE_SympatheticProc) + return spells[spell_id].limit_value[i]; } return 0; @@ -6085,130 +7113,65 @@ bool Mob::DoHPToManaCovert(uint16 mana_cost) return false; } -int32 Mob::GetFcDamageAmtIncoming(Mob *caster, uint32 spell_id, bool use_skill, uint16 skill ) +int32 Mob::GetFcDamageAmtIncoming(Mob *caster, int32 spell_id) { - //Used to check focus derived from SE_FcDamageAmtIncoming which adds direct damage to Spells or Skill based attacks. + //THIS is target of spell cast int32 dmg = 0; - bool limit_exists = false; - bool skill_found = false; - - if (!caster) - return 0; - - if (spellbonuses.FocusEffects[focusFcDamageAmtIncoming]){ - int buff_count = GetMaxTotalSlots(); - for(int i = 0; i < buff_count; i++){ - - if( (IsValidSpell(buffs[i].spellid) && (IsEffectInSpell(buffs[i].spellid, SE_FcDamageAmtIncoming))) ){ - - if (use_skill){ - int32 temp_dmg = 0; - for (int e = 0; e < EFFECT_COUNT; e++) { - - if (spells[buffs[i].spellid].effectid[e] == SE_FcDamageAmtIncoming){ - temp_dmg += spells[buffs[i].spellid].base[e]; - continue; - } - - if (!skill_found){ - if ((spells[buffs[i].spellid].effectid[e] == SE_LimitToSkill) || - (spells[buffs[i].spellid].effectid[e] == SE_LimitCastingSkill)){ - limit_exists = true; - - if (spells[buffs[i].spellid].base[e] == skill) - skill_found = true; - } - } - } - if ((!limit_exists) || (limit_exists && skill_found)){ - dmg += temp_dmg; - CheckNumHitsRemaining(NumHit::MatchingSpells, i); - } - } - - else{ - int32 focus = caster->CalcFocusEffect(focusFcDamageAmtIncoming, buffs[i].spellid, spell_id); - if(focus){ - dmg += focus; - CheckNumHitsRemaining(NumHit::MatchingSpells, i); - } - } - } - } - } + dmg += GetFocusEffect(focusFcDamageAmtIncoming, spell_id, caster); //SPA 297 SE_FcDamageAmtIncoming + dmg += GetFocusEffect(focusFcSpellDamageAmtIncomingPC, spell_id, caster); //SPA 484 SE_Fc_Spell_Damage_Amt_IncomingPC return dmg; } int32 Mob::GetFocusIncoming(focusType type, int effect, Mob *caster, uint32 spell_id) { + //**** This can be removed when bot healing focus code is updated **** + /* This is a general function for calculating best focus effect values for focus effects that exist on targets but modify incoming spells. Should be used when checking for foci that can exist on clients or npcs ect. Example: When your target has a focus limited buff that increases amount of healing on them. */ - if (!caster) + if (!caster) { return 0; + } int value = 0; if (spellbonuses.FocusEffects[type]){ - int32 tmp_focus = 0; - int tmp_buffslot = -1; + int32 tmp_focus = 0; + int tmp_buffslot = -1; - int buff_count = GetMaxTotalSlots(); - for(int i = 0; i < buff_count; i++) { + int buff_count = GetMaxTotalSlots(); + for(int i = 0; i < buff_count; i++) { - if((IsValidSpell(buffs[i].spellid) && IsEffectInSpell(buffs[i].spellid, effect))){ + if((IsValidSpell(buffs[i].spellid) && IsEffectInSpell(buffs[i].spellid, effect))){ - int32 focus = caster->CalcFocusEffect(type, buffs[i].spellid, spell_id); + int32 focus = caster->CalcFocusEffect(type, buffs[i].spellid, spell_id); - if (!focus) - continue; + if (!focus) { + continue; + } - if (tmp_focus && focus > tmp_focus){ - tmp_focus = focus; - tmp_buffslot = i; - } + if (tmp_focus && focus > tmp_focus){ + tmp_focus = focus; + tmp_buffslot = i; + } - else if (!tmp_focus){ - tmp_focus = focus; - tmp_buffslot = i; - } + else if (!tmp_focus){ + tmp_focus = focus; + tmp_buffslot = i; } } - - value = tmp_focus; - - if (tmp_buffslot >= 0) - CheckNumHitsRemaining(NumHit::MatchingSpells, tmp_buffslot); } + value = tmp_focus; - return value; -} + if (tmp_buffslot >= 0) + CheckNumHitsRemaining(NumHit::MatchingSpells, tmp_buffslot); + } -int32 Mob::ApplySpellEffectiveness(int16 spell_id, int32 value, bool IsBard, uint16 caster_id) { - - // 9-17-12: This is likely causing crashes, disabled till can resolve. - if (IsBard) - return value; - - Mob* caster = this; - - if (caster_id && caster_id != GetID())//Make sure we are checking the casters focus - caster = entity_list.GetMob(caster_id); - - if (!caster) - return value; - - int16 focus = caster->GetFocusEffect(focusFcBaseEffects, spell_id); - - if (IsBard) - value += focus; - else - value += value*focus/100; return value; } @@ -6221,7 +7184,6 @@ bool Mob::PassLimitClass(uint32 Classes_, uint16 Class_) return false; Class_ += 1; - for (int CurrentClass = 1; CurrentClass <= PLAYER_CLASS_COUNT; ++CurrentClass){ if (Classes_ % 2 == 1){ if (CurrentClass == Class_) @@ -6249,18 +7211,18 @@ uint16 Mob::GetSpellEffectResistChance(uint16 spell_id) for(int d = 0; d < MAX_RESISTABLE_EFFECTS*2; d+=2) { - if (spells[spell_id].effectid[i] == aabonuses.SEResist[d]){ + if (spells[spell_id].effect_id[i] == aabonuses.SEResist[d]){ resist_chance += aabonuses.SEResist[d+1]; found = true; } - if (spells[spell_id].effectid[i] == itembonuses.SEResist[d]){ + if (spells[spell_id].effect_id[i] == itembonuses.SEResist[d]){ resist_chance += itembonuses.SEResist[d+1]; found = true; } - if (spells[spell_id].effectid[i] == spellbonuses.SEResist[d]){ + if (spells[spell_id].effect_id[i] == spellbonuses.SEResist[d]){ resist_chance += spellbonuses.SEResist[d+1]; found = true; } @@ -6274,15 +7236,6 @@ uint16 Mob::GetSpellEffectResistChance(uint16 spell_id) bool Mob::TryDispel(uint8 caster_level, uint8 buff_level, int level_modifier){ - /*Live 5-20-14 Patch Note: Updated all spells which use Remove Detrimental and - Cancel Beneficial spell effects to use a new method. The chances for those spells to - affect their targets have not changed unless otherwise noted.*/ - - /*This should provide a somewhat accurate conversion between pre 5/14 base values and post. - until more information is avialble - Kayen*/ - if (level_modifier >= 100) - level_modifier = level_modifier/100; - //Dispels - Check level of caster agianst buffs level (level of the caster who cast the buff) //Effect value of dispels are treated as a level modifier. //Values for scaling were obtain from live parses, best estimates. @@ -6309,25 +7262,24 @@ bool Mob::TryDispel(uint8 caster_level, uint8 buff_level, int level_modifier){ return false; } - bool Mob::ImprovedTaunt(){ - if (spellbonuses.ImprovedTaunt[0]){ + if (spellbonuses.ImprovedTaunt[SBIndex::IMPROVED_TAUNT_MAX_LVL]){ - if (GetLevel() > spellbonuses.ImprovedTaunt[0]) + if (GetLevel() > spellbonuses.ImprovedTaunt[SBIndex::IMPROVED_TAUNT_MAX_LVL]) return false; - if (spellbonuses.ImprovedTaunt[2] >= 0){ + if (spellbonuses.ImprovedTaunt[SBIndex::IMPROVED_TAUNT_BUFFSLOT] >= 0){ - target = entity_list.GetMob(buffs[spellbonuses.ImprovedTaunt[2]].casterid); + target = entity_list.GetMob(buffs[spellbonuses.ImprovedTaunt[SBIndex::IMPROVED_TAUNT_BUFFSLOT]].casterid); if (target){ SetTarget(target); return true; } else { - if(!TryFadeEffect(spellbonuses.ImprovedTaunt[2])) - BuffFadeBySlot(spellbonuses.ImprovedTaunt[2], true); //If caster killed removed effect. + if(!TryFadeEffect(spellbonuses.ImprovedTaunt[SBIndex::IMPROVED_TAUNT_BUFFSLOT])) + BuffFadeBySlot(spellbonuses.ImprovedTaunt[SBIndex::IMPROVED_TAUNT_BUFFSLOT], true); //If caster killed removed effect. } } } @@ -6336,336 +7288,2045 @@ bool Mob::ImprovedTaunt(){ } -bool Mob::PassCastRestriction(bool UseCastRestriction, int16 value, bool IsDamage) +bool Mob::PassCastRestriction(int value) { - /*If return TRUE spell met all restrictions and can continue (this = target). - This check is used when the spell_new field CastRestriction is defined OR spell effect '0'(DD/Heal) has a defined limit - Range 1 : UNKNOWN -- the spells with this seem to not have a restiction, true for now - Range 100 : *Animal OR Humanoid - Range 101 : *Dragon - Range 102 : *Animal OR Insect - Range 103 : NOT USED - Range 104 : *Animal - Range 105 : Plant - Range 106 : *Giant - Range 107 : NOT USED - Range 108 : NOT Animal or Humaniod - Range 109 : *Bixie - Range 111 : *Harpy - Range 112 : *Sporali - Range 113 : *Kobold - Range 114 : *Shade Giant - Range 115 : *Drakkin - Range 116 : NOT USED - Range 117 : *Animal OR Plant - Range 118 : *Summoned - Range 119 : *Firepet - Range 120 : Undead - Range 121 : *Living (NOT Undead) - Range 122 : *Fairy - Range 123 : Humanoid - Range 124 : *Undead HP < 10% - Range 125 : *Clockwork HP < 10% - Range 126 : *Wisp HP < 10% - Range 127 : UNKNOWN - Range 128 : pure melee -- guess - Range 129 : pure caster -- guess - Range 130 : hybrid -- guess - Range 150 : UNKNOWN - Range 190 : No Raid boss flag *not implemented - Range 191 : This spell will deal less damage to 'exceptionally strong targets' - Raid boss flag *not implemented - Range 201 : Damage if HP > 75% - Range 203 : Damage if HP < 20% - Range 216 : TARGET NOT IN COMBAT - Range 221 - 249 : Causing damage dependent on how many pets/swarmpets are attacking your target. - Range 250 : Damage if HP < 35% - Range 300 - 303 : UNKOWN *not implemented - Range 304 : Chain + Plate class (buffs) - Range 399 - 409 : Heal if HP within a specified range (400 = 0-25% 401 = 25 - 35% 402 = 35-45% ect) - Range 410 - 411 : UNKOWN -- examples are auras that cast on NPCs maybe in combat/out of combat? - Range 500 - 599 : Heal if HP less than a specified value - Range 600 - 699 : Limit to Body Type [base2 - 600 = Body] - Range 700 : NPC only -- from patch notes "Wizard - Arcane Fusion no longer deals damage to non-NPC targets. This should ensure that wizards who fail their Bucolic Gambit are slightly less likely to annihilate themselves." - Range 701 : NOT PET - Range 800 : UKNOWN -- Target's Target Test (16598) - Range 812 : UNKNOWN -- triggered by Thaumatize Owner - Range 814 : UNKNOWN -- Vegetentacles - Range 815 : UNKNOWN -- Pumpkin Pulp Splash - Range 816 : UNKNOWN -- Rotten Fruit Splash - Range 817 : UNKNOWN -- Tainted Bixie Pollen Splash - Range 818 - 819 : If Undead/If Not Undead - Range 820 - 822 : UKNOWN - Range 835 : Unknown *not implemented - Range 836 - 837 : Progression Server / Live Server *not fully implemented - Range 839 : Progression Server and GoD released -- broken until Oct 21 2015 on live *not fully implemented - Range 842 - 844 : Humaniod lv MAX ((842 - 800) * 2) - Range 845 - 847 : UNKNOWN - Range 860 - 871 : Humanoid lv MAX 860 = 90, 871 = 104 *not implemented - Range 10000 - 11000 : Limit to Race [base2 - 10000 = Race] (*Not on live: Too useful a function to not implement) - THIS IS A WORK IN PROGRESS + /* + Restriction ID corresponds to the type 39 value in dstr_us on live clients (2021). See enum SpellRestriction for full list. + Modern client will give a message corresponding the type 39 field in the dstr_us for many of these effects upon failure. + + Use with spell_news table field 'CastRestriction' which limits targets by restrictions below and 'caster_requirement' (field220) + which limits caster by restrictions below. + These restrictions also apply to direct damage,dot, heal spells using SPA 0 or SPA 79 by placing a restriction id in the LIMIT field. + + Note: (ID 221 - 249) For effect seen in mage spell 'Shock of Many' which increases damage based on number of pets on targets hatelist. The way it is implemented + works for how our ROF2 spell file handles the effect where each slot fires individually, while on live it only takes the highest + value. In the future the way check is done will need to be adjusted to check a defined range instead of just great than. */ - if (value <= 0) + if (value <= 0) { return true; + } - if (IsDamage || UseCastRestriction) { + switch(value) + { + case 1: + return true; + break; - switch(value) - { - case 1: + case IS_NOT_ON_HORSE: + if (IsClient() && !CastToClient()->GetHorseId()) return true; + break; - case 100: - if ((GetBodyType() == BT_Animal) || (GetBodyType() == BT_Humanoid)) - return true; - break; + case IS_ANIMAL_OR_HUMANOID: + if ((GetBodyType() == BT_Animal) || (GetBodyType() == BT_Humanoid)) + return true; + break; - case 101: - if (GetBodyType() == BT_Dragon || GetBodyType() == BT_VeliousDragon || GetBodyType() == BT_Dragon3) - return true; - break; + case IS_DRAGON: + if (GetBodyType() == BT_Dragon || GetBodyType() == BT_VeliousDragon || GetBodyType() == BT_Dragon3) + return true; + break; - case 102: - if ((GetBodyType() == BT_Animal) || (GetBodyType() == BT_Insect)) - return true; - break; + case IS_ANIMAL_OR_INSECT: + if ((GetBodyType() == BT_Animal) || (GetBodyType() == BT_Insect)) + return true; + break; - case 104: - if (GetBodyType() == BT_Animal) - return true; - break; + case IS_BODY_TYPE_MISC: + if ((GetBodyType() == BT_Humanoid) || (GetBodyType() == BT_Lycanthrope) || (GetBodyType() == BT_Giant) || + (GetBodyType() == BT_RaidGiant) || (GetBodyType() == BT_RaidColdain) || (GetBodyType() == BT_Animal)|| + (GetBodyType() == BT_Construct) || (GetBodyType() == BT_Dragon) || (GetBodyType() == BT_Insect)|| + (GetBodyType() == BT_VeliousDragon) || (GetBodyType() == BT_Muramite) || (GetBodyType() == BT_Magical)) + return true; + break; - case 105: - if (GetBodyType() == BT_Plant) - return true; - break; + case IS_BODY_TYPE_MISC2: + if ((GetBodyType() == BT_Humanoid) || (GetBodyType() == BT_Lycanthrope) || (GetBodyType() == BT_Giant) || + (GetBodyType() == BT_RaidGiant) || (GetBodyType() == BT_RaidColdain) || (GetBodyType() == BT_Animal) || + (GetBodyType() == BT_Insect)) + return true; + break; - case 106: - if (GetBodyType() == BT_Giant) - return true; - break; + case IS_PLANT: + if (GetBodyType() == BT_Plant) + return true; + break; - case 108: - if ((GetBodyType() != BT_Animal) || (GetBodyType() != BT_Humanoid)) - return true; - break; + case IS_GIANT: + if (GetBodyType() == BT_Giant) + return true; + break; - case 109: - if ((GetRace() == 520) ||(GetRace() == 79)) - return true; - break; + case IS_NOT_ANIMAL_OR_HUMANOID: + if ((GetBodyType() != BT_Animal) || (GetBodyType() != BT_Humanoid)) + return true; + break; - case 111: - if ((GetRace() == 527) ||(GetRace() == 11)) - return true; - break; + case IS_BIXIE: + case IS_BIXIE2: + if ((GetRace() == RT_BIXIE) ||(GetRace() == RT_BIXIE_2)) + return true; + break; - case 112: - if ((GetRace() == 456) ||(GetRace() == 28)) - return true; - break; + case IS_HARPY: + if ((GetRace() == RT_HARPY) ||(GetRace() == RT_HARPY_2)) + return true; + break; - case 113: - if ((GetRace() == 456) ||(GetRace() == 48)) - return true; - break; + case IS_GNOLL: + if ((GetRace() == RT_GNOLL) || (GetRace() == RT_GNOLL_2) || (GetRace() == RT_GNOLL_3)) + return true; + break; - case 114: - if (GetRace() == 526) - return true; - break; + case IS_SPORALI: + if ((GetRace() == RT_SPORALI) ||(GetRace() == RT_FUNGUSMAN)) + return true; + break; - case 115: - if (GetRace() == 522) - return true; - break; + case IS_KOBOLD: + if ((GetRace() == RT_KOBOLD) ||(GetRace() == RT_KOBOLD_2)) + return true; + break; - case 117: - if ((GetBodyType() == BT_Animal) || (GetBodyType() == BT_Plant)) - return true; - break; + case IS_FROSTCRYPT_SHADE: + if (GetRace() == RT_GIANT_SHADE) + return true; + break; - case 118: - if (GetBodyType() == BT_Summoned) - return true; - break; + case IS_DRAKKIN: + if (GetRace() == RT_DRAKKIN) + return true; + break; - case 119: - if (IsPet() && ((GetRace() == 212) || ((GetRace() == 75) && GetTexture() == 1))) - return true; - break; + case IS_UNDEAD_OR_VALDEHOLM_GIANT: + if (GetBodyType() == BT_Undead || GetRace() == RT_GIANT_12 || GetRace() == RT_GIANT_13) + return true; + break; - case 120: - if (GetBodyType() == BT_Undead) - return true; - break; + case IS_ANIMAL_OR_PLANT: + if ((GetBodyType() == BT_Animal) || (GetBodyType() == BT_Plant)) + return true; + break; - case 121: - if (GetBodyType() != BT_Undead) - return true; - break; + case IS_SUMMONED: + if (GetBodyType() == BT_Summoned) + return true; + break; - case 122: - if ((GetRace() == 473) || (GetRace() == 425)) - return true; - break; + case IS_CLASS_WIZARD: + case IS_WIZARD_USED_ON_MAGE_FIRE_PET: + if (GetClass() == WIZARD) + return true; + break; - case 123: - if (GetBodyType() == BT_Humanoid) - return true; - break; + case IS_UNDEAD: + if (GetBodyType() == BT_Undead) + return true; + break; - case 124: - if ((GetBodyType() == BT_Undead) && (GetHPRatio() < 10)) - return true; - break; + case IS_NOT_UNDEAD_OR_SUMMONED_OR_VAMPIRE: + if ((GetBodyType() != BT_Undead) && (GetBodyType() != BT_Summoned) && (GetBodyType() != BT_Vampire)) + return true; + break; - case 125: - if ((GetRace() == 457 || GetRace() == 88) && (GetHPRatio() < 10)) - return true; - break; + case IS_FAE_OR_PIXIE: + if ((GetRace() == RT_PIXIE) || (GetRace() == RT_FAY_DRAKE)) + return true; + break; - case 126: - if ((GetRace() == 581 || GetRace() == 69) && (GetHPRatio() < 10)) - return true; - break; + case IS_HUMANOID: + if (GetBodyType() == BT_Humanoid) + return true; + break; - case 201: - if (GetHPRatio() > 75) - return true; - break; + case IS_UNDEAD_AND_HP_LESS_THAN_10_PCT: + if ((GetBodyType() == BT_Undead) && (GetHPRatio() < 10)) + return true; + break; - case 204: - if (GetHPRatio() < 20) - return true; - break; + case IS_CLOCKWORK_AND_HP_LESS_THAN_45_PCT: + if ((GetRace() == RT_GNOMEWORK || GetRace() == RACE_CLOCKWORK_GNOME_88) && (GetHPRatio() < 45)) + return true; + break; - case 216: - if (!IsEngaged()) - return true; - break; + case IS_WISP_AND_HP_LESS_THAN_10_PCT: + if ((GetRace() == RT_WILL_O_WISP) && (GetHPRatio() < 10)) + return true; + break; - case 250: - if (GetHPRatio() < 35) - return true; - break; + case IS_CLASS_MELEE_THAT_CAN_BASH_OR_KICK_EXCEPT_BARD: + if ((GetClass() != BARD) && (GetClass() != ROGUE) && IsFighterClass(GetClass())) + return true; + break; - case 304: - if (IsClient() && - ((GetClass() == WARRIOR) || (GetClass() == BARD) || (GetClass() == SHADOWKNIGHT) || (GetClass() == PALADIN) || (GetClass() == CLERIC) - || (GetClass() == RANGER) || (GetClass() == SHAMAN) || (GetClass() == ROGUE) || (GetClass() == BERSERKER))) - return true; - break; + case IS_CLASS_PURE_MELEE: + if (GetClass() == ROGUE || GetClass() == WARRIOR || GetClass() == BERSERKER || GetClass() == MONK) + return true; + break; - case 700: - if (IsNPC()) - return true; - break; + case IS_CLASS_PURE_CASTER: + if (IsINTCasterClass(GetClass())) + return true; + break; - case 701: - if (!IsPet()) - return true; - break; + case IS_CLASS_HYBRID_CLASS: + if (IsHybridClass(GetClass())) + return true; + break; - case 818: - if (GetBodyType() == BT_Undead) - return true; - break; + case IS_CLASS_WARRIOR: + if (GetClass() == WARRIOR) + return true; + break; - case 819: - if (GetBodyType() != BT_Undead) - return true; - break; + case IS_CLASS_CLERIC: + if (GetClass() == CLERIC) + return true; + break; - case 836: - return true; // todo implement progression flag assume not progression for now + case IS_CLASS_PALADIN: + if (GetClass() == PALADIN) + return true; + break; - case 837: - return false; // todo implement progression flag assume not progression for now + case IS_CLASS_RANGER: + if (GetClass() == RANGER) + return true; + break; - case 839: - return true; // todo implement progression flag assume not progression for now, this one is a check if GoD is live + case IS_CLASS_SHADOWKNIGHT: + if (GetClass() == SHADOWKNIGHT) + return true; + break; - case 842: - if (GetBodyType() == BT_Humanoid && GetLevel() <= 84) - return true; - break; + case IS_CLASS_DRUID: + if (GetClass() == DRUID) + return true; + break; - case 843: - if (GetBodyType() == BT_Humanoid && GetLevel() <= 86) - return true; - break; + case IS_CLASS_MONK: + if (GetClass() == MONK) + return true; + break; - case 844: - if (GetBodyType() == BT_Humanoid && GetLevel() <= 88) - return true; - break; + case IS_CLASS_BARD2: + case IS_CLASS_BARD: + if (GetClass() == BARD) + return true; + break; + + case IS_CLASS_ROGUE: + if (GetClass() == ROGUE) + return true; + break; + + case IS_CLASS_SHAMAN: + if (GetClass() == SHAMAN) + return true; + break; + + case IS_CLASS_NECRO: + if (GetClass() == NECROMANCER) + return true; + break; + + case IS_CLASS_MAGE: + if (GetClass() == MAGICIAN) + return true; + break; + + case IS_CLASS_ENCHANTER: + if (GetClass() == ENCHANTER) + return true; + break; + + case IS_CLASS_BEASTLORD: + if (GetClass() == BEASTLORD) + return true; + break; + + case IS_CLASS_BERSERKER: + if (GetClass() == BERSERKER) + return true; + break; + + case IS_CLASS_CLR_SHM_DRU: + if (IsWISCasterClass(GetClass())) + return true; + break; + + case IS_CLASS_NOT_WAR_PAL_SK: + if ((GetClass() != WARRIOR) && (GetClass() != PALADIN) && (GetClass() != SHADOWKNIGHT)) + return true; + break; + + case IS_LEVEL_UNDER_100: + if (GetLevel() < 100) + return true; + break; + + case IS_NOT_RAID_BOSS: + if (!IsRaidTarget()) + return true; + break; + + case IS_RAID_BOSS: + if (IsRaidTarget()) + return true; + break; + + case FRENZIED_BURNOUT_ACTIVE: + if (HasBuffWithSpellGroup(SPELLGROUP_FRENZIED_BURNOUT)) + return true; + break; + + case FRENZIED_BURNOUT_NOT_ACTIVE: + if (!HasBuffWithSpellGroup(SPELLGROUP_FRENZIED_BURNOUT)) + return true; + break; + + case IS_HP_ABOVE_75_PCT: + if (GetHPRatio() > 75) + return true; + break; + + case IS_HP_LESS_THAN_20_PCT: + if (GetHPRatio() <= 20) + return true; + break; + + case IS_HP_LESS_THAN_50_PCT: + if (GetHPRatio() <= 50) + return true; + break; + + case IS_HP_LESS_THAN_75_PCT: + if (GetHPRatio() <= 75) + return true; + break; + + case IS_NOT_IN_COMBAT: + if (!IsEngaged()) + return true; + break; + + case IS_HP_LESS_THAN_35_PCT: + if (GetHPRatio() <= 35) + return true; + break; + + case HAS_BETWEEN_1_TO_2_PETS_ON_HATELIST: { + int count = hate_list.GetSummonedPetCountOnHateList(); + if (count >= 1 && count <= 2) { + return true; + } + break; } - //Limit to amount of pets - if (value >= 221 && value <= 249){ - int count = hate_list.GetSummonedPetCountOnHateList(this); + case HAS_BETWEEN_3_TO_5_PETS_ON_HATELIST: { + int count = hate_list.GetSummonedPetCountOnHateList(); + if (count >= 3 && count <= 5) { + return true; + } + break; + } - for (int base2_value = 221; base2_value <= 249; ++base2_value){ - if (value == base2_value){ - if (count >= (base2_value - 220)){ - return true; - } + case HAS_BETWEEN_6_TO_9_PETS_ON_HATELIST: { + int count = hate_list.GetSummonedPetCountOnHateList(); + if (count >= 6 && count <= 9) { + return true; + } + break; + } + + case HAS_BETWEEN_10_TO_14_PETS_ON_HATELIST: { + int count = hate_list.GetSummonedPetCountOnHateList(); + if (count >= 10 && count <= 14) { + return true; + } + break; + } + + case HAS_MORE_THAN_14_PETS_ON_HATELIST: { + int count = hate_list.GetSummonedPetCountOnHateList(); + if (count > 14) { + return true; + } + break; + } + + case IS_CLASS_CHAIN_OR_PLATE: + if (IsClient() && + ((GetClass() == WARRIOR) || (GetClass() == BARD) || (GetClass() == SHADOWKNIGHT) || (GetClass() == PALADIN) || (GetClass() == CLERIC) + || (GetClass() == RANGER) || (GetClass() == SHAMAN) || (GetClass() == ROGUE) || (GetClass() == BERSERKER))) + return true; + break; + + case IS_HP_BETWEEN_5_AND_9_PCT: + if (GetHPRatio() >= 5 && GetHPRatio() <= 9) + return true; + break; + + case IS_HP_BETWEEN_10_AND_14_PCT: + if (GetHPRatio() >= 10 && GetHPRatio() <= 14) + return true; + break; + + case IS_HP_BETWEEN_15_AND_19_PCT: + if (GetHPRatio() >= 15 && GetHPRatio() <= 19) + return true; + break; + + case IS_HP_BETWEEN_20_AND_24_PCT: + if (GetHPRatio() >= 20 && GetHPRatio() <= 24) + return true; + break; + + case IS_HP_BETWEEN_25_AND_29_PCT: + if (GetHPRatio() >= 25 && GetHPRatio() <= 29) + return true; + break; + + case IS_HP_BETWEEN_30_AND_34_PCT: + if (GetHPRatio() >= 30 && GetHPRatio() <= 34) + return true; + break; + + case IS_HP_BETWEEN_35_AND_39_PCT: + if (GetHPRatio() >= 35 && GetHPRatio() <= 39) + return true; + break; + + case IS_HP_BETWEEN_40_AND_44_PCT: + if (GetHPRatio() >= 40 && GetHPRatio() <= 44) + return true; + break; + + case IS_HP_BETWEEN_45_AND_49_PCT: + if (GetHPRatio() >= 45 && GetHPRatio() <= 49) + return true; + break; + + case IS_HP_BETWEEN_50_AND_54_PCT: + if (GetHPRatio() >= 50 && GetHPRatio() <= 54) + return true; + break; + + case IS_HP_BETWEEN_55_AND_59_PCT: + if (GetHPRatio() >= 55 && GetHPRatio() <= 59) + return true; + break; + + case IS_HP_BETWEEN_5_AND_15_PCT: + if (GetHPRatio() >= 5 && GetHPRatio() <= 15) + return true; + break; + + case IS_HP_BETWEEN_15_AND_25_PCT: + if (GetHPRatio() >= 15 && GetHPRatio() <= 25) + return true; + break; + + case IS_HP_BETWEEN_1_AND_25_PCT: + if (GetHPRatio() <= 25) + return true; + break; + + case IS_HP_BETWEEN_25_AND_35_PCT: + if (GetHPRatio() > 25 && GetHPRatio() <= 35) + return true; + break; + + case IS_HP_BETWEEN_35_AND_45_PCT: + if (GetHPRatio() > 35 && GetHPRatio() <= 45) + return true; + break; + + case IS_HP_BETWEEN_45_AND_55_PCT: + if (GetHPRatio() > 45 && GetHPRatio() <= 55) + return true; + break; + + case IS_HP_BETWEEN_55_AND_65_PCT: + if (GetHPRatio() > 55 && GetHPRatio() <= 65) + return true; + break; + + case IS_HP_BETWEEN_65_AND_75_PCT: + if (GetHPRatio() > 65 && GetHPRatio() <= 75) + return true; + break; + + case IS_HP_BETWEEN_75_AND_85_PCT: + if (GetHPRatio() > 75 && GetHPRatio() <= 85) + return true; + break; + + case IS_HP_BETWEEN_85_AND_95_PCT: + if (GetHPRatio() > 85 && GetHPRatio() <= 95) + return true; + break; + + case IS_HP_ABOVE_45_PCT: + if (GetHPRatio() > 45) + return true; + break; + + case IS_HP_ABOVE_55_PCT: + if (GetHPRatio() > 55) + return true; + break; + + case IS_MANA_ABOVE_10_PCT: + if (GetManaRatio() > 10) + return true; + break; + + case IS_ENDURANCE_BELOW_40_PCT: + if (IsClient() && CastToClient()->GetEndurancePercent() <= 40) + return true; + break; + + case IS_MANA_BELOW_40_PCT: + if (GetManaRatio() <= 40) + return true; + break; + + case IS_HP_ABOVE_20_PCT: + if (GetHPRatio() > 20) + return true; + break; + + case IS_NOT_UNDEAD_OR_SUMMONED: + if ((GetBodyType() != BT_Undead) && (GetBodyType() != BT_Summoned)) + return true; + break; + + case IS_NOT_PLANT: + if (GetBodyType() != BT_Plant) + return true; + break; + + case IS_NOT_CLIENT: + if (!IsClient()) + return true; + break; + + case IS_CLIENT: + if (IsClient()) + return true; + break; + + case IS_LEVEL_ABOVE_42_AND_IS_CLIENT: + if (IsClient() && GetLevel() > 42) + return true; + break; + + case IS_TREANT: + if (GetRace() == RT_TREANT || GetRace() == RT_TREANT_2 || GetRace() == RT_TREANT_3) + return true; + break; + + case IS_SCARECROW: + if (GetRace() == RT_SCARECROW || GetRace() == RT_SCARECROW_2) + return true; + break; + + case IS_VAMPIRE_OR_UNDEAD_OR_UNDEADPET: + if (GetBodyType() == BT_Vampire || GetBodyType() == BT_Undead || GetBodyType() == BT_SummonedUndead) + return true; + break; + + case IS_NOT_VAMPIRE_OR_UNDEAD: + if (GetBodyType() != BT_Vampire && GetBodyType() != BT_Undead && GetBodyType() != BT_SummonedUndead) + return true; + break; + + case IS_CLASS_KNIGHT_HYBRID_MELEE: + if (IsHybridClass(GetClass()) || IsNonSpellFighterClass(GetClass())) + return true; + break; + + case IS_CLASS_WARRIOR_CASTER_PRIEST: + if (IsCasterClass(GetClass()) || GetClass() == WARRIOR) + return true; + break; + + case IS_END_BELOW_21_PCT: + if (IsClient() && CastToClient()->GetEndurancePercent() <= 21) + return true; + break; + + case IS_END_BELOW_25_PCT: + if (IsClient() && CastToClient()->GetEndurancePercent() <= 25) + return true; + break; + + case IS_END_BELOW_29_PCT: + if (IsClient() && CastToClient()->GetEndurancePercent() <= 29) + return true; + break; + + case IS_REGULAR_SERVER: + return true; // todo implement progression flag assume not progression for now + break; + + case IS_PROGRESSION_SERVER: + return false; // todo implement progression flag assume not progression for now + break; + + case IS_GOD_EXPANSION_UNLOCKED: + return true; // todo implement progression flag assume not progression for now, this one is a check if GoD is live + break; + + case IS_HUMANOID_LEVEL_84_MAX: + if (GetBodyType() == BT_Humanoid && GetLevel() <= 84) + return true; + break; + + case IS_HUMANOID_LEVEL_86_MAX: + if (GetBodyType() == BT_Humanoid && GetLevel() <= 86) + return true; + break; + + case IS_HUMANOID_LEVEL_88_MAX: + if (GetBodyType() == BT_Humanoid && GetLevel() <= 88) + return true; + break; + + case IS_LEVEL_90_MAX: + if (GetLevel() <= 90) + return true; + break; + + case IS_LEVEL_92_MAX: + if (GetLevel() <= 92) + return true; + break; + + case IS_LEVEL_94_MAX: + if (GetLevel() <= 94) + return true; + break; + + case IS_LEVEL_95_MAX: + if (GetLevel() <= 95) + return true; + break; + + case IS_LEVEL_97_MAX: + if (GetLevel() <= 97) + return true; + break; + + case IS_LEVEL_99_MAX: + if (GetLevel() <= 99) + return true; + break; + + case IS_LEVEL_100_MAX: + if (GetLevel() <= 100) + return true; + break; + + case IS_LEVEL_102_MAX: + if (GetLevel() <= 102) + return true; + break; + + case IS_LEVEL_104_MAX: + if (GetLevel() <= 104) + return true; + break; + case IS_LEVEL_105_MAX: + if (GetLevel() <= 105) + return true; + break; + + case IS_LEVEL_107_MAX: + if (GetLevel() <= 107) + return true; + break; + + case IS_LEVEL_109_MAX: + if (GetLevel() <= 109) + return true; + break; + case IS_LEVEL_110_MAX: + if (GetLevel() <= 110) + return true; + break; + + case IS_LEVEL_112_MAX: + if (GetLevel() <= 112) + return true; + break; + + case IS_LEVEL_114_MAX: + if (GetLevel() <= 114) + return true; + break; + + case IS_BETWEEN_LEVEL_1_AND_75: + if (GetLevel() >= 1 && GetLevel() <= 75) + return true; + break; + + case IS_BETWEEN_LEVEL_76_AND_85: + if (GetLevel() >= 76 && GetLevel() <= 85) + return true; + break; + + case IS_BETWEEN_LEVEL_86_AND_95: + if (GetLevel() >= 86 && GetLevel() <= 95) + return true; + break; + + case IS_BETWEEN_LEVEL_96_AND_105: + if (GetLevel() >= 96 && GetLevel() <= 105) + return true; + break; + + case IS_HP_LESS_THAN_80_PCT: + if (GetHPRatio() < 80) + return true; + break; + + case IS_LEVEL_ABOVE_34: + if (GetLevel() < 34) + return true; + break; + + case HAS_NO_MANA_BURN_BUFF: { + bool has_effect = false; + for (int i = 0; i < GetMaxTotalSlots(); i++) { + if (IsValidSpell(buffs[i].spellid) && IsEffectInSpell(buffs[i].spellid, SE_ManaBurn)) { + has_effect = true; } } - } - - //Limit to Body Type - if (value >= 600 && value <= 699){ - if (GetBodyType() == (value - 600)) + if (!has_effect) { return true; - } - - //Limit to Race. *Not implemented on live - if (value >= 10000 && value <= 11000){ - if (GetRace() == (value - 10000)) - return true; - } - } //End Damage - - if (!IsDamage || UseCastRestriction) { - - //Heal only if HP within specified range. [Doesn't follow a set forumla for all values...] - if (value >= 400 && value <= 408){ - for (int base2_value = 400; base2_value <= 408; ++base2_value){ - if (value == base2_value){ - - if (value == 400 && GetHPRatio() <= 25) - return true; - - else if (value == base2_value){ - if (GetHPRatio() > 25+((base2_value - 401)*10) && GetHPRatio() <= 35+((base2_value - 401)*10)) - return true; - } - } } + break; } - else if (value >= 500 && value <= 549){ - for (int base2_value = 500; base2_value <= 520; ++base2_value){ - if (value == base2_value){ - if (GetHPRatio() < (base2_value - 500)*5) - return true; - } - } - } - - else if (value == 399) { - if (GetHPRatio() > 15 && GetHPRatio() <= 25) + case IS_CLIENT_AND_MALE_PLATE_USER: + if (IsClient() && GetGender() == MALE && IsPlateClass(GetClass())) return true; - } - } // End Heal + break; + case IS_CLEINT_AND_MALE_DRUID_ENCHANTER_MAGICIAN_NECROANCER_SHAMAN_OR_WIZARD: + if (IsClient() && GetGender() == MALE && (IsCasterClass(GetClass()) && GetClass() != CLERIC)) + return true; + break; + + case IS_CLIENT_AND_MALE_BEASTLORD_BERSERKER_MONK_RANGER_OR_ROGUE: + if (IsClient() && GetGender() == MALE && + (GetClass() == BEASTLORD || GetClass() == BERSERKER || GetClass() == MONK || GetClass() == RANGER || GetClass() == ROGUE)) + return true; + break; + + case IS_CLIENT_AND_FEMALE_PLATE_USER: + if (IsClient() && GetGender() == FEMALE && IsPlateClass(GetClass())) + return true; + break; + + case IS_CLIENT_AND_FEMALE_DRUID_ENCHANTER_MAGICIAN_NECROANCER_SHAMAN_OR_WIZARD: + if (IsClient() && GetGender() == FEMALE && (IsCasterClass(GetClass()) && GetClass() != CLERIC)) + return true; + break; + + case IS_CLIENT_AND_FEMALE_BEASTLORD_BERSERKER_MONK_RANGER_OR_ROGUE: + if (IsClient() && GetGender() == FEMALE && + (GetClass() == BEASTLORD || GetClass() == BERSERKER || GetClass() == MONK || GetClass() == RANGER || GetClass() == ROGUE)) + return true; + break; + + case IS_HP_ABOVE_50_PCT: + if (GetHPRatio() > 50) + return true; + break; + + case IS_HP_BELOW_50_PCT: + if (GetHPRatio() <= 50) + return true; + break; + + case IS_OFF_HAND_EQUIPED: + if (HasShieldEquiped() || CanThisClassDualWield()) + return true; + break; + + case IS_MANA_BELOW_20_PCT: + if (GetManaRatio() <= 20) + return true; + break; + + case IS_MANA_ABOVE_50_PCT: + if (GetManaRatio() >= 50) + return true; + break; + + case IS_SUMMONED_OR_UNDEAD: + if (GetBodyType() == BT_Summoned || GetBodyType() == BT_Undead) + return true; + break; + + + case IS_CLASS_CASTER_PRIEST: + if (IsCasterClass(GetClass())) + return true; + break; + + case IS_END_OR_MANA_ABOVE_20_PCT: { + if (IsNonSpellFighterClass(GetClass()) && CastToClient()->GetEndurancePercent() >= 20) { + return true; + } + else if (!IsNonSpellFighterClass(GetClass()) && GetManaRatio() >= 20) { + return true; + } + break; + } + + case IS_END_OR_MANA_BELOW_30_PCT: + case IS_END_OR_MANA_BELOW_30_PCT2: { + if (IsNonSpellFighterClass(GetClass()) && CastToClient()->GetEndurancePercent() <= 30) { + return true; + } + else if (!IsNonSpellFighterClass(GetClass()) && GetManaRatio() <= 30) { + return true; + } + break; + } + + case IS_NOT_CLASS_BARD: + if (GetClass() != BARD) + return true; + break; + + case HAS_NO_PACT_OF_FATE_RECOURSE_BUFF: + if (!FindBuff(SPELL_PACT_OF_HATE_RECOURSE)) + return true; + break; + + case HAS_NO_ROGUES_FURY_BUFF: + if (!HasBuffWithSpellGroup(SPELLGROUP_ROGUES_FURY)) + return true; + break; + + case HAS_NO_ILLUSIONS_OF_GRANDEUR_BUFF: + if (!HasBuffWithSpellGroup(SPELLGROUP_ILLUSION_OF_GRANDEUR)) + return true; + break; + + case HAS_NO_HARMONIOUS_PRECISION_BUFF: + if (!HasBuffWithSpellGroup(SPELLGROUP_HARMONIOUS_PRECISION)) + return true; + break; + + case HAS_NO_HARMONIOUS_EXPANSE_BUFF: + if (!HasBuffWithSpellGroup(SPELLGROUP_HARMONIOUS_EXPANSE)) + return true; + break; + + case HAS_NO_FURIOUS_RAMPAGE_BUFF: + if (!HasBuffWithSpellGroup(SPELLGROUP_FURIOUS_RAMPAGE)) + return true; + break; + + case HAS_NO_SHROUD_OF_PRAYER_BUFF: + if (!HasBuffWithSpellGroup(SPELLGROUP_SHROUD_OF_PRAYER)) + return true; + break; + + case HAS_INCENDIARY_OOZE_BUFF: + if (FindBuff(SPELL_INCENDIARY_OOZE_BUFF)) + return true; + break; + + //Not handled, just allow them to pass for now. + case UNKNOWN_3: + case HAS_CRYSTALLIZED_FLAME_BUFF: + case UNKNOWN_199: + case UNKNOWN_TOO_MUCH_HP_410: + case UNKNOWN_TOO_MUCH_HP_411: + case HAS_TBL_ESIANTI_ACCESS: + case HAS_ITEM_CLOCKWORK_SCRAPS: + case IN_TWO_HANDED_STANCE: + case IN_DUAL_WIELD_HANDED_STANCE: + case IN_SHIELD_STANCE: + case NOT_IN_TWO_HANDED_STANCE: + case NOT_IN_DUAL_WIELD_HANDED_STANCE: + case NOT_IN_SHIELD_STANCE: + case DISABLED_UNTIL_EXPANSION_ROK: + case DISABLED_UNTIL_EXPANSION_SOV: + case DISABLED_UNTIL_EXPANSION_SOL: + case DISABLED_UNTIL_EXPANSION_POP: + case DISABLED_UNTIL_EXPANSION_LOY: + case DISABLED_UNTIL_EXPANSION_LDON: + case DISABLED_UNTIL_EXPANSION_GOD: + case DISABLED_UNTIL_EXPANSION_OOW: + case DISABLED_UNTIL_EXPANSION_DON: + case DISABLED_UNTIL_EXPANSION_DOD: + case DISABLED_UNTIL_EXPANSION_POR: + case DISABLED_UNTIL_EXPANSION_TSS: + case DISABLED_UNTIL_EXPANSION_TBS: + case DISABLED_UNTIL_EXPANSION_SOF: + case DISABLED_UNTIL_EXPANSION_SOD: + case DISABLED_UNTIL_EXPANSION_UF: + case DISABLED_UNTIL_EXPANSION_HOT: + case DISABLED_UNTIL_EXPANSION_VOA: + case DISABLED_UNTIL_EXPANSION_ROF: + case DISABLED_UNTIL_EXPANSION_COF: + case DISABLED_UNTIL_EXPANSION_TDS: + case DISABLED_UNTIL_EXPANSION_TBM: + case DISABLED_UNTIL_EXPANSION_EOK: + case DISABLED_UNTIL_EXPANSION_ROS: + case DISABLED_UNTIL_EXPANSION_TBL: + case DISABLED_UNTIL_EXPANSION_TOV: + case DISABLED_UNTIL_EXPANSION_COV: + case HAS_TRAVELED_TO_STRATOS: + case HAS_TRAVELED_TO_AALISHAI: + case HAS_TRAVELED_TO_MEARATS: + case COMPLETED_ACHIEVEMENT_LEGENDARY_ANSWERER: + case NOT_COMPLETED_ACHIEVEMENT_LEGENDARY_ANSWERER: + case HAS_WEAPONSTANCE_DEFENSIVE_PROFICIENCY: + case HAS_WEAPONSTANCE_TWO_HAND_PROFICIENCY: + case HAS_WEAPONSTANCE_DUAL_WEILD_PROFICIENCY: + case UNKNOWN_812: + case UNKNOWN_814: + case UNKNOWN_822: + case UNKNOWN_840: + case UNKNOWN_841: + case UNKNOWN_99999: + return true; + break; + } + + if (value >= HAS_AT_LEAST_1_PET_ON_HATELIST && value <= HAS_AT_LEAST_20_PETS_ON_HATELIST) { + int count = hate_list.GetSummonedPetCountOnHateList(); + int minium_amount_of_pets_needed = (1 + value) - HAS_AT_LEAST_1_PET_ON_HATELIST; + + if (count >= minium_amount_of_pets_needed) { + return true; + } + } + + if (value >= IS_HP_BELOW_5_PCT && value <= IS_HP_BELOW_95_PCT) { + int hp_below_amt = 5 * ((1 + value) - IS_HP_BELOW_5_PCT); + if (GetHPRatio() <= hp_below_amt) { + return true; + } + } + + if (value >= IS_BODY_TYPE_UNDEFINED && value <= IS_BODY_TYPE_MURAMITE){ + if (GetBodyType() == (value - IS_BODY_TYPE_UNDEFINED)) + return true; + } + + //Limit to Race. *Not implemented on live, too much potential not to give an option here. + if (value >= IS_RACE_FIRST_CUSTOM && value <= IS_RACE_LAST_CUSTOM){ + if (GetRace() == (value - IS_RACE_FIRST_CUSTOM)) + return true; + } return false; } -bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id, float speed){ +void Mob::SendCastRestrictionMessage(int requirement_id, bool target_requirement, bool is_discipline) { + + if (!IsClient()) { + return; + } + /* + Most of these are the live messages that modern clients give. Current live dbstr_us type 39 + Having these messages display greatly enhances the useabllity of these fields. (CastRestriction=target, caster_requirement_id=caster) + If target_requirement is false then use the caster requirement message. + Added support for different messages for certain high yield restrictions based on if + target or caster requirements. + + */ + + const char *msg = ""; + + if (target_requirement) { + msg = "Your target does not meet the spell requirements. "; + } + else { + if (is_discipline) { + msg = "Your combat ability is interrupted. "; + } + else { + msg = "Your spell is interrupted. "; + } + } + + switch (requirement_id) + { + case IS_ANIMAL_OR_HUMANOID: + Message(Chat::Red, "%s This spell will only work on animals or humanoid creatures.", msg); + break; + case IS_DRAGON: + Message(Chat::Red, "%s This spell will only work on dragons.", msg); + break; + case IS_ANIMAL_OR_INSECT: + Message(Chat::Red, "%s This spell will only work on animals or insects.", msg); + break; + case IS_BODY_TYPE_MISC: + Message(Chat::Red, "%s This spell will only work on humanoids, lycanthropes, giants, Kael Drakkel giants, Coldain, animals, insects, constructs, dragons, Skyshrine dragons, Muramites, or creatures constructed from magic.", msg); + break; + case IS_BODY_TYPE_MISC2: + Message(Chat::Red, "%s This spell will only work on humanoids, lycanthropes, giants, Kael Drakkel giants, Coldain, animals, or insects.", msg); + break; + case IS_PLANT: + Message(Chat::Red, "%s This spell will only work on plants.", msg); + break; + case IS_GIANT: + Message(Chat::Red, "%s This spell will only work on animals.", msg); + break; + case IS_NOT_ANIMAL_OR_HUMANOID: + Message(Chat::Red, "%s This spell will only work on targets that are neither animals or humanoid.", msg); + break; + case IS_BIXIE: + Message(Chat::Red, "%s This spell will only work on bixies.", msg); + break; + case IS_HARPY: + Message(Chat::Red, "%s This spell will only work on harpies.", msg); + break; + case IS_GNOLL: + Message(Chat::Red, "%s This spell will only work on gnolls.", msg); + break; + case IS_SPORALI: + Message(Chat::Red, "%s This spell will only work on fungusoids.", msg); + break; + case IS_KOBOLD: + Message(Chat::Red, "%s This spell will only work on kobolds.", msg); + break; + case IS_FROSTCRYPT_SHADE: + Message(Chat::Red, "%s This spell will only work on undead creatures or the Shades of Frostcrypt.", msg); + break; + case IS_DRAKKIN: + Message(Chat::Red, "%s This spell will only work on Drakkin.", msg); + break; + case IS_UNDEAD_OR_VALDEHOLM_GIANT: + Message(Chat::Red, "%s This spell will only work on undead creatures or the inhabitants of Valdeholm.", msg); + break; + case IS_ANIMAL_OR_PLANT: + Message(Chat::Red, "%s This spell will only work on plants or animals.", msg); + break; + case IS_SUMMONED: + Message(Chat::Red, "%s This spell will only work on constructs, elementals, or summoned elemental minions.", msg); + break; + case IS_WIZARD_USED_ON_MAGE_FIRE_PET: + Message(Chat::Red, "%s This spell will only work on wizards.", msg); + break; + case IS_UNDEAD: + Message(Chat::Red, "%s This spell will only work on undead.", msg); + break; + case IS_NOT_UNDEAD_OR_SUMMONED_OR_VAMPIRE: + Message(Chat::Red, "%s This spell will only work on creatures that are not undead, constructs, elementals, or vampires.", msg); + break; + case IS_FAE_OR_PIXIE: + Message(Chat::Red, "%s This spell will only work on Fae or pixies.", msg); + break; + case IS_HUMANOID: + Message(Chat::Red, "%s This spell will only work on humanoids.", msg); + break; + case IS_UNDEAD_AND_HP_LESS_THAN_10_PCT: + Message(Chat::Red, "%s The Essence Extractor whirrs but does not light up.", msg); + break; + case IS_CLOCKWORK_AND_HP_LESS_THAN_45_PCT: + Message(Chat::Red, "%s This spell will only work on clockwork gnomes.", msg); + break; + case IS_WISP_AND_HP_LESS_THAN_10_PCT: + Message(Chat::Red, "%s This spell will only work on wisps at or below 10 pct of their maximum HP.", msg); + break; + case IS_CLASS_MELEE_THAT_CAN_BASH_OR_KICK_EXCEPT_BARD: + Message(Chat::Red, "%s This spell will only work on non-bard targets that can bash or kick.", msg); + break; + case IS_CLASS_PURE_MELEE: + if (target_requirement) { + Message(Chat::Red, "%s This spell will only affect melee classes (warriors, monks, rogues, and berserkers).", msg); + } + else { + Message(Chat::Red, "%s This ability can only be used by melee classes (warriors, monks, rogues, and berserkers).", msg); + } + break; + case IS_CLASS_PURE_CASTER: + if (target_requirement) { + Message(Chat::Red, "%s This spell will only affect pure caster classes (necromancers, wizards, magicians, and enchanters).", msg); + } + else { + Message(Chat::Red, "%s This ability can only be used by pure caster classes (necromancers, wizards, magicians, and enchanters).", msg); + } + break; + case IS_CLASS_HYBRID_CLASS: + if (target_requirement) { + Message(Chat::Red, "%s This spell will only affect hybrid classes (paladins, rangers, shadow knights, bards, and beastlords).", msg); + } + else { + Message(Chat::Red, "%s This ability can only be used by hybrid classes (paladins, rangers, shadow knights, bards, and beastlords).", msg); + } + break; + case IS_CLASS_WARRIOR: + if (target_requirement) { + Message(Chat::Red, "%s This spell will only affect Warriors.", msg); + } + else { + Message(Chat::Red, "%s This ability can only be used by Warriors.", msg); + } + break; + case IS_CLASS_CLERIC: + if (target_requirement) { + Message(Chat::Red, "%s This spell will only affect Clerics.", msg); + } + else { + Message(Chat::Red, "%s This ability can only be used by Clerics.", msg); + } + break; + case IS_CLASS_PALADIN: + if (target_requirement) { + Message(Chat::Red, "%s This spell will only affect Paladins.", msg); + } + else { + Message(Chat::Red, "%s This ability can only be used by Paladins.", msg); + } + break; + case IS_CLASS_RANGER: + if (target_requirement) { + Message(Chat::Red, "%s This spell will only affect Warriors.", msg); + } + else { + Message(Chat::Red, "%s This ability can only be used by Warriors.", msg); + } + break; + case IS_CLASS_SHADOWKNIGHT: + if (target_requirement) { + Message(Chat::Red, "%s This spell will only affect Shadow Knights.", msg); + } + else { + Message(Chat::Red, "%s This ability can only be used by Shadow Knights.", msg); + } + break; + case IS_CLASS_DRUID: + if (target_requirement) { + Message(Chat::Red, "%s This spell will only affect Druids.", msg); + } + else { + Message(Chat::Red, "%s This ability can only be used by Druids.", msg); + } + break; + case IS_CLASS_MONK: + if (target_requirement) { + Message(Chat::Red, "%s This spell will only affect Monks.", msg); + } + else { + Message(Chat::Red, "%s This ability can only be used by Monks.", msg); + } + break; + case IS_CLASS_BARD: + if (target_requirement) { + Message(Chat::Red, "%s This spell will only affect Bards.", msg); + } + else { + Message(Chat::Red, "%s This ability can only be used by Bards..", msg); + } + break; + case IS_CLASS_ROGUE: + if (target_requirement) { + Message(Chat::Red, "%s This spell will only affect Rogues.", msg); + } + else { + Message(Chat::Red, "%s This ability can only be used by Rogues.", msg); + } + break; + case IS_CLASS_SHAMAN: + if (target_requirement) { + Message(Chat::Red, "%s This spell will only affect Shamans.", msg); + } + else { + Message(Chat::Red, "%s This ability can only be used by Shamans.", msg); + } + break; + case IS_CLASS_NECRO: + if (target_requirement) { + Message(Chat::Red, "%s This spell will only affect Necromancers.", msg); + } + else { + Message(Chat::Red, "%s This ability can only be used by Necromancers.", msg); + } + break; + case IS_CLASS_WIZARD: + if (target_requirement) { + Message(Chat::Red, "%s This spell will only affect Wizards.", msg); + } + else { + Message(Chat::Red, "%s This ability can only be used by Wizards.", msg); + } + break; + case IS_CLASS_MAGE: + if (target_requirement) { + Message(Chat::Red, "%s This spell will only affect Magicians.", msg); + } + else { + Message(Chat::Red, "%s This ability can only be used by Magicians.", msg); + } + break; + case IS_CLASS_ENCHANTER: + if (target_requirement) { + Message(Chat::Red, "%s This spell will only affect Enchanters.", msg); + } + else { + Message(Chat::Red, "%s This ability can only be used by Enchanters.", msg); + } + break; + case IS_CLASS_BEASTLORD: + if (target_requirement) { + Message(Chat::Red, "%s This spell will only affect Beastlords.", msg); + } + else { + Message(Chat::Red, "%s This ability can only be used by Beastlords.", msg); + } + break; + case IS_CLASS_BERSERKER: + if (target_requirement) { + Message(Chat::Red, "%s This spell will only affect Berserkers.", msg); + } + else { + Message(Chat::Red, "%s This ability can only be used by Berserkers.", msg); + } + break; + case IS_CLASS_CLR_SHM_DRU: + if (target_requirement) { + Message(Chat::Red, "%s This spell will only affect priest classes (clerics, druids, and shaman).", msg); + } + else { + Message(Chat::Red, "%s This ability can only be used by priest classes (clerics, druids, and shaman).", msg); + } + break; + case IS_CLASS_NOT_WAR_PAL_SK: + if (target_requirement) { + Message(Chat::Red, "%s This spell will not affect Warriors, Paladins, or Shadow Knights.", msg); + } + else { + Message(Chat::Red, "%s This ability can only be used by Warriors, Paladins, or Shadow Knights.", msg); + } + break; + case IS_LEVEL_UNDER_100: + Message(Chat::Red, "%s This spell will not affect any target over level 100.", msg); + break; + case IS_NOT_RAID_BOSS: + Message(Chat::Red, "%s This spell will not affect raid bosses.", msg); + break; + case IS_RAID_BOSS: + Message(Chat::Red, "%s This spell will only affect raid bosses.", msg); + break; + case FRENZIED_BURNOUT_ACTIVE: + Message(Chat::Red, "%s This spell will only cast if you have Frenzied Burnout active.", msg); + break; + case FRENZIED_BURNOUT_NOT_ACTIVE: + Message(Chat::Red, "%s This spell will only cast if you do not have Frenzied Burnout active.", msg); + break; + case IS_HP_ABOVE_75_PCT: + Message(Chat::Red, "%s Your taret's HP must be at or above 75 pct of its maximum.", msg); + break; + case IS_HP_LESS_THAN_20_PCT: + Message(Chat::Red, "%s Your target's HP must be at 20 pct of its maximum or below.", msg); + break; + case IS_HP_LESS_THAN_50_PCT: + Message(Chat::Red, "%s Your target's HP must be at 50 pct of its maximum or below.", msg); + break; + case IS_HP_LESS_THAN_75_PCT: + Message(Chat::Red, "%s Your target's HP must be at 75 pct of its maximum or below.", msg); + break; + case IS_NOT_IN_COMBAT: + Message(Chat::Red, "%s This spell will only affect creatures that are not in combat.", msg); + break; + case HAS_AT_LEAST_1_PET_ON_HATELIST: + Message(Chat::Red, "%s Your target must have least 1 pet on their hate list.", msg); + break; + case HAS_AT_LEAST_2_PETS_ON_HATELIST: + Message(Chat::Red, "%s Your target must have least 2 pets on their hate list.", msg); + break; + case HAS_AT_LEAST_3_PETS_ON_HATELIST: + Message(Chat::Red, "%s Your target must have least 3 pets on their hate list.", msg); + break; + case HAS_AT_LEAST_4_PETS_ON_HATELIST: + Message(Chat::Red, "%s Your target must have least 4 pets on their hate list.", msg); + break; + case HAS_AT_LEAST_5_PETS_ON_HATELIST: + Message(Chat::Red, "%s Your target must have least 5 pets on their hate list.", msg); + break; + case HAS_AT_LEAST_6_PETS_ON_HATELIST: + Message(Chat::Red, "%s Your target must have least 6 pets on their hate list.", msg); + break; + case HAS_AT_LEAST_7_PETS_ON_HATELIST: + Message(Chat::Red, "%s Your target must have least 7 pets on their hate list.", msg); + break; + case HAS_AT_LEAST_8_PETS_ON_HATELIST: + Message(Chat::Red, "%s Your target must have least 8 pets on their hate list.", msg); + break; + case HAS_AT_LEAST_9_PETS_ON_HATELIST: + Message(Chat::Red, "%s Your target must have least 9 pets on their hate list.", msg); + break; + case HAS_AT_LEAST_10_PETS_ON_HATELIST: + Message(Chat::Red, "%s Your target must have least 10 pets on their hate list.", msg); + break; + case HAS_AT_LEAST_11_PETS_ON_HATELIST: + Message(Chat::Red, "%s Your target must have least 11 pets on their hate list.", msg); + break; + case HAS_AT_LEAST_12_PETS_ON_HATELIST: + Message(Chat::Red, "%s Your target must have least 12 pets on their hate list.", msg); + break; + case HAS_AT_LEAST_13_PETS_ON_HATELIST: + Message(Chat::Red, "%s Your target must have least 13 pets on their hate list.", msg); + break; + case HAS_AT_LEAST_14_PETS_ON_HATELIST: + Message(Chat::Red, "%s Your target must have least 14 pets on their hate list.", msg); + break; + case HAS_AT_LEAST_15_PETS_ON_HATELIST: + Message(Chat::Red, "%s Your target must have least 15 pets on their hate list.", msg); + break; + case HAS_AT_LEAST_16_PETS_ON_HATELIST: + Message(Chat::Red, "%s Your target must have least 16 pets on their hate list.", msg); + break; + case HAS_AT_LEAST_17_PETS_ON_HATELIST: + Message(Chat::Red, "%s Your target must have least 17 pets on their hate list.", msg); + break; + case HAS_AT_LEAST_18_PETS_ON_HATELIST: + Message(Chat::Red, "%s Your target must have least 18 pets on their hate list.", msg); + break; + case HAS_AT_LEAST_19_PETS_ON_HATELIST: + Message(Chat::Red, "%s Your target must have least 19 pets on their hate list.", msg); + break; + case HAS_AT_LEAST_20_PETS_ON_HATELIST: + Message(Chat::Red, "%s Your target must have least 20 pets on their hate list.", msg); + break; + case IS_HP_LESS_THAN_35_PCT: + Message(Chat::Red, "%s Your target's HP must be at 35 pct of its maximum or below.", msg); + break; + case HAS_BETWEEN_1_TO_2_PETS_ON_HATELIST: + Message(Chat::Red, "%s Your target must have between 1 and 2 pets on their hate list.", msg); + break; + case HAS_BETWEEN_3_TO_5_PETS_ON_HATELIST: + Message(Chat::Red, "%s Your target must have between 3 and 5 pets on their hate list.", msg); + break; + case HAS_BETWEEN_6_TO_9_PETS_ON_HATELIST: + Message(Chat::Red, "%s Your target must have between 6 and 9 pets on their hate list.", msg); + break; + case HAS_BETWEEN_10_TO_14_PETS_ON_HATELIST: + Message(Chat::Red, "%s Your target must have between 10 and 14 pets on their hate list.", msg); + break; + case HAS_MORE_THAN_14_PETS_ON_HATELIST: + Message(Chat::Red, "%s Your target must have between 15 or more pets on their hate list.", msg); + break; + case IS_CLASS_CHAIN_OR_PLATE: + Message(Chat::Red, "%s This spell will only affect plate or chain wearing classes.", msg); + break; + case IS_HP_BETWEEN_5_AND_9_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be between 5 pct and 9 pct of its maximum.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be between 5 pct and 9 pct of your maximum HP.", msg); + } + break; + case IS_HP_BETWEEN_10_AND_14_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be between 10 pct and 14 pct of its maximum.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be between 10 pct and 14 pct of your maximum HP.", msg); + } + break; + case IS_HP_BETWEEN_15_AND_19_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be between 15 pct and 19 pct of its maximum.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be between 15 pct and 19 pct of your maximum HP.", msg); + } + break; + case IS_HP_BETWEEN_20_AND_24_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be between 20 pct and 54 pct of its maximum.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be between 20 pct and 24 pct of your maximum HP.", msg); + } + break; + case IS_HP_BETWEEN_25_AND_29_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be between 25 pct and 29 pct of its maximum.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be between 25 pct and 29 pct of your maximum HP.", msg); + } + break; + case IS_HP_BETWEEN_30_AND_34_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be between 30 pct and 34 pct of its maximum.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be between 30 pct and 34 pct of your maximum HP.", msg); + } + break; + case IS_HP_BETWEEN_35_AND_39_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be between 35 pct and 39 pct of its maximum.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be between 35 pct and 39 pct of your maximum HP.", msg); + } + break; + case IS_HP_BETWEEN_40_AND_44_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be between 40 pct and 44 pct of its maximum.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be between 40 pct and 44 pct of your maximum HP.", msg); + } + break; + case IS_HP_BETWEEN_45_AND_49_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be between 45 pct and 49 pct of its maximum.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be between 45 pct and 49 pct of your maximum HP.", msg); + } + break; + case IS_HP_BETWEEN_50_AND_54_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be between 50 pct and 54 pct of its maximum.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be between 50 pct and 54 pct of your maximum HP.", msg); + } + break; + case IS_HP_BETWEEN_55_AND_59_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be between 55 pct and 59 pct of its maximum.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be between 55 pct and 59 pct of your maximum HP.", msg); + } + break; + case IS_HP_BETWEEN_5_AND_15_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be between 5 pct and 15 pct of its maximum.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be between 5 pct and 15 pct of your maximum HP.", msg); + } + break; + case IS_HP_BETWEEN_15_AND_25_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be between 15 pct and 25 pct of its maximum.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be between 15 pct and 25 pct of your maximum HP.", msg); + } + break; + case IS_HP_BETWEEN_1_AND_25_PCT: + Message(Chat::Red, "%s Your target's HP must be at 25 pct of its maximum or below.", msg); + break; + case IS_HP_BETWEEN_25_AND_35_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be between 25 pct and 35 pct of its maximum.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be between 25 pct and 35 pct of your maximum HP.", msg); + } + break; + case IS_HP_BETWEEN_35_AND_45_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be between 35 pct and 45 pct of its maximum.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be between 35 pct and 45 pct of your maximum HP.", msg); + } + break; + case IS_HP_BETWEEN_45_AND_55_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be between 45 pct and 55 pct of its maximum.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be between 45 pct and 55 pct of your maximum HP.", msg); + } + break; + case IS_HP_BETWEEN_55_AND_65_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be between 55 pct and 65 pct of its maximum.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be between 55 pct and 65 pct of your maximum HP.", msg); + } + break; + case IS_HP_BETWEEN_65_AND_75_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be between 65 pct and 75 pct of its maximum.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be between 65 pct and 75 pct of your maximum HP.", msg); + } + break; + case IS_HP_BETWEEN_75_AND_85_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be between 75 pct and 85 pct of its maximum.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be between 75 pct and 85 pct of your maximum HP.", msg); + } + break; + case IS_HP_BETWEEN_85_AND_95_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be between 85 pct and 95 pct of its maximum.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be between 85 pct and 95 pct of your maximum HP.", msg); + } + break; + case IS_HP_ABOVE_45_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be at least 45 pct of its maximum.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be at least 45 pct of your maximum HP.", msg); + } + break; + case IS_HP_ABOVE_55_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be at least 55 pct of its maximum.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be at least 55 pct of your maximum HP.", msg); + } + break; + case UNKNOWN_TOO_MUCH_HP_410: + Message(Chat::Red, "%s Your target has too much HP to be affected by this spell.", msg); + break; + case UNKNOWN_TOO_MUCH_HP_411: + Message(Chat::Red, "%s Your target has too much HP to be affected by this spell.", msg); + break; + case IS_HP_ABOVE_99_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be at or above 99 pct of its maximum.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be at or above 99 pct of your maximum HP.", msg); + } + break; + case IS_MANA_ABOVE_10_PCT: + Message(Chat::Red, "%s You must have at least 10 pct of your maximum mana available to cast this spell.", msg); + break; + case IS_HP_BELOW_5_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be at 5 pct of its maximum or below.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be at or below 5 pct of your maximum HP.", msg); + } + break; + case IS_HP_BELOW_10_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be at 10 pct of its maximum or below.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be at or below 10 pct of your maximum HP.", msg); + } + break; + case IS_HP_BELOW_15_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be at 15 pct of its maximum or below.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be at or below 15 pct of your maximum HP.", msg); + } + break; + case IS_HP_BELOW_20_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be at 20 pct of its maximum or below.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be at or below 20 pct of your maximum HP.", msg); + } + break; + case IS_HP_BELOW_25_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be at 25 pct of its maximum or below.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be at or below 25 pct of your maximum HP.", msg); + } + break; + case IS_HP_BELOW_30_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be at 30 pct of its maximum or below.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be at or below 30 pct of your maximum HP.", msg); + } + break; + case IS_HP_BELOW_35_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be at 35 pct of its maximum or below.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be at or below 35 pct of your maximum HP.", msg); + } + break; + case IS_HP_BELOW_40_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be at 40 pct of its maximum or below.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be at or below 40 pct of your maximum HP.", msg); + } + break; + case IS_HP_BELOW_45_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be at 45 pct of its maximum or below.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be at or below 45 pct of your maximum HP.", msg); + } + break; + case IS_HP_BELOW_50_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be at 50 pct of its maximum or below.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be at or below 50 pct of your maximum HP.", msg); + } + break; + case IS_HP_BELOW_55_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be at 55 pct of its maximum or below.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be at or below 55 pct of your maximum HP.", msg); + } + break; + case IS_HP_BELOW_60_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be at 60 pct of its maximum or below.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be at or below 60 pct of your maximum HP.", msg); + } + break; + case IS_HP_BELOW_65_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be at 65 pct of its maximum or below.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be at or below 65 pct of your maximum HP.", msg); + } + break; + case IS_HP_BELOW_70_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be at 70 pct of its maximum or below.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be at or below 70 pct of your maximum HP.", msg); + } + break; + case IS_HP_BELOW_75_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be at 75 pct of its maximum or below.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be at or below 75 pct of your maximum HP.", msg); + } + break; + case IS_HP_BELOW_80_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be at 80 pct of its maximum or below.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be at or below 80 pct of your maximum HP.", msg); + } + break; + case IS_HP_BELOW_85_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be at 85 pct of its maximum or below.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be at or below 85 pct of your maximum HP.", msg); + } + break; + case IS_HP_BELOW_90_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be at 90 pct of its maximum or below.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be at or below 90 pct of your maximum HP.", msg); + } + break; + case IS_HP_BELOW_95_PCT: + if (target_requirement) { + Message(Chat::Red, "%s Your target's HP must be at 95 pct of its maximum or below.", msg); + } + else { + Message(Chat::Red, "%s This ability requires you to be at or below 95 pct of your maximum HP.", msg); + } + break; + case IS_ENDURANCE_BELOW_40_PCT: + Message(Chat::Red, "%s This ability requires you to be at or below 40 pct of your maximum endurance.", msg); + break; + case IS_MANA_BELOW_40_PCT: + Message(Chat::Red, "%s This spells requires you to be at or below 40 pct of your maximum mana.", msg); + break; + case IS_HP_ABOVE_20_PCT: + Message(Chat::Red, "%s Your target's HP must be at least 21 pct of its maximum.", msg); + break; + case IS_BODY_TYPE_UNDEFINED: + Message(Chat::Red, "%s This spell will only work on creatures with an undefined body type.", msg); + break; + case IS_BODY_TYPE_HUMANOID: + Message(Chat::Red, "%s This spell will only work on humanoid creatures.", msg); + break; + case IS_BODY_TYPE_WEREWOLF: + Message(Chat::Red, "%s This spell will only work on lycanthrope creatures.", msg); + break; + case IS_BODY_TYPE_UNDEAD: + Message(Chat::Red, "%s This spell will only work on undead creatures.", msg); + break; + case IS_BODY_TYPE_GIANTS: + Message(Chat::Red, "%s This spell will only work on giants.", msg); + break; + case IS_BODY_TYPE_CONSTRUCTS: + Message(Chat::Red, "%s This spell will only work on constructs.", msg); + break; + case IS_BODY_TYPE_EXTRAPLANAR: + Message(Chat::Red, "%s This spell will only work on extraplanar creatures.", msg); + break; + case IS_BODY_TYPE_MAGICAL_CREATURE: + Message(Chat::Red, "%s This spell will only work on creatures constructed from magic.", msg); + break; + case IS_BODY_TYPE_UNDEADPET: + Message(Chat::Red, "%s This spell will only work on animated undead servants.", msg); + break; + case IS_BODY_TYPE_KAELGIANT: + Message(Chat::Red, "%s This spell will only work on the Giants of Kael Drakkal.", msg); + break; + case IS_BODY_TYPE_COLDAIN: + Message(Chat::Red, "%s This spell will only work on Coldain Dwarves.", msg); + break; + case IS_BODY_TYPE_VAMPIRE: + Message(Chat::Red, "%s This spell will only work on vampires.", msg); + break; + case IS_BODY_TYPE_ATEN_HA_RA: + Message(Chat::Red, "%s This spell will only work on Aten Ha Ra.", msg); + break; + case IS_BODY_TYPE_GREATER_AHKEVANS: + Message(Chat::Red, "%s This spell will only work on Greater Ahkevans.", msg); + break; + case IS_BODY_TYPE_KHATI_SHA: + Message(Chat::Red, "%s This spell will only work on Khati Sha.", msg); + break; + case IS_BODY_TYPE_LORD_INQUISITOR_SERU: + Message(Chat::Red, "%s This spell will only work on Lord Inquisitor Seru.", msg); + break; + case IS_BODY_TYPE_GRIEG_VENEFICUS: + Message(Chat::Red, "%s This spell will only work on Grieg Veneficus.", msg); + break; + case IS_BODY_TYPE_FROM_PLANE_OF_WAR: + Message(Chat::Red, "%s This spell will only work on creatures from the Plane of War.", msg); + break; + case IS_BODY_TYPE_LUGGALD: + Message(Chat::Red, "%s This spell will only work on Luggalds.", msg); + break; + case IS_BODY_TYPE_ANIMAL: + Message(Chat::Red, "%s This spell will only work on animals.", msg); + break; + case IS_BODY_TYPE_INSECT: + Message(Chat::Red, "%s This spell will only work on insects.", msg); + break; + case IS_BODY_TYPE_MONSTER: + Message(Chat::Red, "%s This spell will only work on monsters.", msg); + break; + case IS_BODY_TYPE_ELEMENTAL: + Message(Chat::Red, "%s This spell will only work on elemental creatures.", msg); + break; + case IS_BODY_TYPE_PLANT: + Message(Chat::Red, "%s This spell will only work on plants.", msg); + break; + case IS_BODY_TYPE_DRAGON2: + Message(Chat::Red, "%s This spell will only work on dragons.", msg); + break; + case IS_BODY_TYPE_SUMMONED_ELEMENTAL: + Message(Chat::Red, "%s This spell will only work on summoned elementals.", msg); + break; + case IS_BODY_TYPE_DRAGON_OF_TOV: + Message(Chat::Red, "%s This spell will only work on Dragons of Veeshan's Temple.", msg); + break; + case IS_BODY_TYPE_FAMILIAR: + Message(Chat::Red, "%s This spell will only work on familiars.", msg); + break; + case IS_BODY_TYPE_MURAMITE: + Message(Chat::Red, "%s This spell will only work on Muramites.", msg); + break; + case IS_NOT_UNDEAD_OR_SUMMONED: + Message(Chat::Red, "%s This spell will only targets that are not undead or summoned.", msg); + break; + case IS_NOT_PLANT: + Message(Chat::Red, "%s This spell will not affect plants.", msg); + break; + case IS_NOT_CLIENT: + Message(Chat::Red, "%s This spell will not work on adventurers.", msg); + break; + case IS_CLIENT: + Message(Chat::Red, "%s This spell will only work on adventurers.", msg); + break; + case IS_LEVEL_ABOVE_42_AND_IS_CLIENT: + Message(Chat::Red, "%s This spell will only work on level 43 or higher adventurers.", msg); + break; + case IS_TREANT: + Message(Chat::Red, "%s This spell will only work on treants.", msg); + break; + case IS_BIXIE2: + Message(Chat::Red, "%s This spell will only work on bixies.", msg); + break; + case IS_SCARECROW: + Message(Chat::Red, "%s This spell will only work on scarecrows.", msg); + break; + case IS_VAMPIRE_OR_UNDEAD_OR_UNDEADPET: + Message(Chat::Red, "%s This spell will only work on vampires, undead, or animated undead creatures.", msg); + break; + case IS_NOT_VAMPIRE_OR_UNDEAD: + Message(Chat::Red, "%s This spell will not work on vampires or undead creatures.", msg); + break; + case IS_CLASS_KNIGHT_HYBRID_MELEE: + Message(Chat::Red, "%s This spell will only work on knights, hybrids, or melee classes.", msg); + break; + case IS_CLASS_WARRIOR_CASTER_PRIEST: + Message(Chat::Red, "%s This spell will only work on warriors, casters, or priests.", msg); + break; + case IS_END_BELOW_21_PCT: + Message(Chat::Red, "%s This ability requires you to be at or below 21 pct of your maximum endurance.", msg); + break; + case IS_END_BELOW_25_PCT: + Message(Chat::Red, "%s This ability requires you to be at or below 25 pct of your maximum endurance.", msg); + break; + case IS_END_BELOW_29_PCT: + Message(Chat::Red, "%s This ability requires you to be at or below 29 pct of your maximum endurance.", msg); + break; + case IS_HUMANOID_LEVEL_84_MAX: + Message(Chat::Red, "%s This spell will only work on humanoids up to level 84.", msg); + break; + case IS_HUMANOID_LEVEL_86_MAX: + Message(Chat::Red, "%s This spell will only work on humanoids up to level 86.", msg); + break; + case IS_HUMANOID_LEVEL_88_MAX: + Message(Chat::Red, "%s This spell will only work on humanoids up to level 88.", msg); + break; + case HAS_CRYSTALLIZED_FLAME_BUFF: + Message(Chat::Red, "%s This spell will only work on targets afflicted by Crystallized Flame.", msg); + break; + case HAS_INCENDIARY_OOZE_BUFF: + Message(Chat::Red, "%s This spell will only work on targets afflicted by Incendiary Ooze.", msg); + break; + case IS_LEVEL_90_MAX: + Message(Chat::Red, "%s This spell will ony work targets level up to level 90.", msg); + break; + case IS_LEVEL_92_MAX: + Message(Chat::Red, "%s This spell will ony work targets level up to level 92.", msg); + break; + case IS_LEVEL_94_MAX: + Message(Chat::Red, "%s This spell will ony work targets level up to level 94.", msg); + break; + case IS_LEVEL_95_MAX: + Message(Chat::Red, "%s This spell will ony work targets level up to level 95.", msg); + break; + case IS_LEVEL_97_MAX: + Message(Chat::Red, "%s This spell will ony work targets level up to level 97.", msg); + break; + case IS_LEVEL_99_MAX: + Message(Chat::Red, "%s This spell will ony work targets level up to level 99.", msg); + break; + case IS_LEVEL_100_MAX: + Message(Chat::Red, "%s This spell will ony work targets level up to level 100.", msg); + break; + case IS_LEVEL_102_MAX: + Message(Chat::Red, "%s This spell will ony work targets level up to level 102.", msg); + break; + case IS_LEVEL_104_MAX: + Message(Chat::Red, "%s This spell will ony work targets level up to level 104.", msg); + break; + case IS_LEVEL_105_MAX: + Message(Chat::Red, "%s This spell will ony work targets level up to level 105.", msg); + break; + case IS_LEVEL_107_MAX: + Message(Chat::Red, "%s This spell will ony work targets level up to level 107.", msg); + break; + case IS_LEVEL_109_MAX: + Message(Chat::Red, "%s This spell will ony work targets level up to level 109.", msg); + break; + case IS_LEVEL_110_MAX: + Message(Chat::Red, "%s This spell will ony work targets level up to level 110.", msg); + break; + case IS_LEVEL_112_MAX: + Message(Chat::Red, "%s This spell will ony work targets level up to level 112.", msg); + break; + case IS_LEVEL_114_MAX: + Message(Chat::Red, "%s This spell will ony work targets level up to level 114.", msg); + break; + case HAS_TBL_ESIANTI_ACCESS: + Message(Chat::Red, "%s This spell will only transport adventurers who have gained access to Esianti: Palace of the Winds.", msg); + break; + case IS_BETWEEN_LEVEL_1_AND_75: + Message(Chat::Red, "%s This spell will only work on targets between level 1 and 75.", msg); + break; + case IS_BETWEEN_LEVEL_76_AND_85: + Message(Chat::Red, "%s This spell will only work on targets between level 76 and 85.", msg); + break; + case IS_BETWEEN_LEVEL_86_AND_95: + Message(Chat::Red, "%s This spell will only work on targets between level 86 and 95.", msg); + break; + case IS_BETWEEN_LEVEL_96_AND_105: + Message(Chat::Red, "%s This spell will only work on targets between level 96 and 105.", msg); + break; + case IS_HP_LESS_THAN_80_PCT: + Message(Chat::Red, "%s Your target's HP must be at 80 pct of its maximum or below.", msg); + break; + case IS_LEVEL_ABOVE_34: + Message(Chat::Red, "%s Your target must be level 35 or higher.", msg); + break; + case IN_TWO_HANDED_STANCE: + Message(Chat::Red, "%s You must be in your two-handed stance to use this ability.", msg); + break; + case IN_DUAL_WIELD_HANDED_STANCE: + Message(Chat::Red, "%s You must be in your dual-wielding stance to use this ability.", msg); + break; + case IN_SHIELD_STANCE: + Message(Chat::Red, "%s You must be in your shield stance to use this ability.", msg); + break; + case NOT_IN_TWO_HANDED_STANCE: + Message(Chat::Red, "%s You may not use this ability if you are in your two-handed stance.", msg); + break; + case NOT_IN_DUAL_WIELD_HANDED_STANCE: + Message(Chat::Red, "%s You may not use this ability if you are in your dual-wielding stance.", msg); + break; + case NOT_IN_SHIELD_STANCE: + Message(Chat::Red, "%s You may not use this ability if you are in your shield stance.", msg); + break; + case LEVEL_46_MAX: + Message(Chat::Red, "%s Your target must be level 46 or below.", msg); + break; + case HAS_NO_MANA_BURN_BUFF: + Message(Chat::Red, "%s This spell will not take hold until the effects of the previous Mana Burn have expired.", msg); + break; + case IS_CLIENT_AND_MALE_PLATE_USER: + Message(Chat::Red, "%s Your target wouldn't look right as that Jann.", msg); + break; + case IS_CLEINT_AND_MALE_DRUID_ENCHANTER_MAGICIAN_NECROANCER_SHAMAN_OR_WIZARD: + Message(Chat::Red, "%s Your target wouldn't look right as that Jann.", msg); + break; + case IS_CLIENT_AND_MALE_BEASTLORD_BERSERKER_MONK_RANGER_OR_ROGUE: + Message(Chat::Red, "%s Your target wouldn't look right as that Jann.", msg); + break; + case IS_CLIENT_AND_FEMALE_PLATE_USER: + Message(Chat::Red, "%s Your target wouldn't look right as that Jann.", msg); + break; + case IS_CLIENT_AND_FEMALE_DRUID_ENCHANTER_MAGICIAN_NECROANCER_SHAMAN_OR_WIZARD: + Message(Chat::Red, "%s Your target wouldn't look right as that Jann.", msg); + break; + case IS_CLIENT_AND_FEMALE_BEASTLORD_BERSERKER_MONK_RANGER_OR_ROGUE: + Message(Chat::Red, "%s Your target wouldn't look right as that Jann.", msg); + break; + case HAS_TRAVELED_TO_STRATOS: + Message(Chat::Red, "%s You must travel to Stratos at least once before wishing to go there.", msg); + break; + case HAS_TRAVELED_TO_AALISHAI: + Message(Chat::Red, "%s You must travel to Aalishai at least once before wishing to go there.", msg); + break; + case HAS_TRAVELED_TO_MEARATS: + Message(Chat::Red, "%s You must travel to Mearatas at least once before wishing to go there.", msg); + break; + case IS_HP_ABOVE_50_PCT: + Message(Chat::Red, "%s This target must be above 50 pct of its maximum hit points.", msg); + break; + case IS_HP_UNDER_50_PCT: + Message(Chat::Red, "%s This target must be at oe below 50 pct of its maximum hit points.", msg); + break; + case IS_OFF_HAND_EQUIPED: + Message(Chat::Red, "%s You must be wielding a weapon or shield in your offhand to use this ability.", msg); + break; + case HAS_NO_PACT_OF_FATE_RECOURSE_BUFF: + Message(Chat::Red, "%s This spell will not work while Pact of Fate Recourse is active.", msg); + break; + case HAS_NO_SHROUD_OF_PRAYER_BUFF: + Message(Chat::Red, "%s Your target cannot receive another Quiet Prayer this soon.", msg); + break; + case IS_MANA_BELOW_20_PCT: + Message(Chat::Red, "%s This ability requires you to be at or below 20 pct of your maximum mana.", msg); + break; + case IS_MANA_ABOVE_50_PCT: + Message(Chat::Red, "%s This ability requires you to be at or above 50 pct of your maximum mana.", msg); + break; + case COMPLETED_ACHIEVEMENT_LEGENDARY_ANSWERER: + Message(Chat::Red, "%s You have completed Legendary Answerer.", msg); + break; + case HAS_NO_ROGUES_FURY_BUFF: + Message(Chat::Red, "%s This spell will not affect anyone that currently has Rogue's Fury active.", msg); + break; + case NOT_COMPLETED_ACHIEVEMENT_LEGENDARY_ANSWERER: + Message(Chat::Red, "%s You must complete Legendary Answerer.", msg); + break; + case IS_SUMMONED_OR_UNDEAD: + Message(Chat::Red, "%s This spell can only be used on summoned or undead.", msg); + break; + case IS_CLASS_CASTER_PRIEST: + Message(Chat::Red, "%s This ability requires you to be a caster or priest.", msg); + break; + case IS_END_OR_MANA_ABOVE_20_PCT: + Message(Chat::Red, "%s You must have at least 20 pct of your maximum mana and endurance to use this ability.", msg); + break; + case IS_END_OR_MANA_BELOW_30_PCT: + Message(Chat::Red, "%s Your target already has 30 pct or more of their maximum mana or endurance.", msg); + break; + case IS_CLASS_BARD2: + if (target_requirement) { + Message(Chat::Red, "%s This spell will only affect Bards.", msg); + } + else { + Message(Chat::Red, "%s This ability can only be used by Bards.", msg); + } + break; + case IS_NOT_CLASS_BARD: + if (target_requirement) { + Message(Chat::Red, "%s This spell can not affect Bards.", msg); + } + else { + Message(Chat::Red, "%s This ability can not be used by Bards.", msg); + } + break; + case HAS_NO_FURIOUS_RAMPAGE_BUFF: + Message(Chat::Red, "%s This ability cannot be activated while Furious Rampage is active.", msg); + break; + case IS_END_OR_MANA_BELOW_30_PCT2: + Message(Chat::Red, "%s You can only perform this solo if you have less than 30 pct mana or endurance.", msg); + break; + case HAS_NO_HARMONIOUS_PRECISION_BUFF: + Message(Chat::Red, "%s This spell will not work if you have the Harmonious Precision line active.", msg); + break; + case HAS_NO_HARMONIOUS_EXPANSE_BUFF: + Message(Chat::Red, "%s This spell will not work if you have the Harmonious Expanse line active.", msg); + break; + default: + if (target_requirement) { + Message(Chat::Red, "Your target does not meet the spell requirements.", msg); + } + else { + Message(Chat::Red, "Your spell would not take hold on your target.", msg); + } + break; + } +} + +bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id, float speed) { /*For mage 'Bolt' line and other various spells. -This is mostly accurate for how the modern clients handle this effect. @@ -6685,12 +9346,12 @@ bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id, float speed){ if (!spell_target) return false; - uint8 anim = spells[spell_id].CastingAnim; + uint8 anim = spells[spell_id].casting_animation; int slot = -1; //Make sure there is an avialable bolt to be cast. for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { - if (ProjectileAtk[i].target_id == 0){ + if (ProjectileAtk[i].target_id == 0) { slot = i; break; } @@ -6699,11 +9360,35 @@ bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id, float speed){ if (slot < 0) return false; + float arc = 0.0f; + float distance_mod = 0.0f; + if (CheckLosFN(spell_target)) { - float speed_mod = speed; //Constant for adjusting speeds to match calculated impact time. float distance = spell_target->CalculateDistance(GetX(), GetY(), GetZ()); - float hit = 1200.0f + (10 * distance / speed_mod); + + /* + New Distance Mod constant (7/25/21 update), modifier is needed to adjust slower speeds to have correct impact times at short distances. + We use archery 4.0 speed as a baseline for the forumla. At speed 1.5 at 50 pct distance mod is needed, where as speed 4.0 there is no modifer. + Therefore, we derive out our modifer as follows. distance_mod = (speed - 4) * ((50 - 0)/(1.5-4)). The ratio there is -20.0f. distance_mod = (speed - 4) * -20.0f + For distances >125 we use different modifier, this was all meticulously tested by eye to get the best possible outcome for projectile impact times. Not perfect though. + */ + + if (distance <= 125.0f) { + distance_mod = (speed - 4.0f) * -20.0f; + distance += distance * distance_mod / 100.0f; + } + else if (distance > 125.0f && distance <= 200.0f) + distance = 3.14f * (distance / 2.0f); //Get distance of arc to better reflect projectile path length + + else if (distance > 200.0f) { + arc = 50.0f - ((distance - 200.0f) * 0.266f); //Arc angle gets drastically larger if >200 distance, lets lower it down gradually for better effect. + arc = std::max(arc, 20.0f); //No lower than 20 arc + distance = distance * 1.30f; //Add 30% to base distance if over 200 range to tighten up hit timing. + distance = 3.14f * (distance / 2.0f); //Get distance of arc to better reflect projectile path length + } + + float hit = 1200.0f + (10 * distance / speed); ProjectileAtk[slot].increment = 1; ProjectileAtk[slot].hit_increment = static_cast(hit); //This projected hit time if target does NOT MOVE @@ -6713,37 +9398,36 @@ bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id, float speed){ ProjectileAtk[slot].origin_y = GetY(); ProjectileAtk[slot].origin_z = GetZ(); ProjectileAtk[slot].skill = EQ::skills::SkillConjuration; - ProjectileAtk[slot].speed_mod = speed_mod; + ProjectileAtk[slot].speed_mod = speed; SetProjectileAttack(true); } //This will use the correct graphic as defined in the player_1 field of spells_new table. Found in UF+ spell files. if (RuleB(Spells, UseLiveSpellProjectileGFX)) { - ProjectileAnimation(spell_target,0, false, speed,0,0,0, spells[spell_id].player_1); + ProjectileAnimation(spell_target, 0, false, speed, 0.0f, 0.0f, arc, spells[spell_id].player_1); } - //This allows limited support for server using older spell files that do not contain data for bolt graphics. else { //Only use fire graphic for fire spells. - if (spells[spell_id].resisttype == RESIST_FIRE) { + if (spells[spell_id].resist_type == RESIST_FIRE) { - if (IsClient()){ + if (IsClient()) { if (CastToClient()->ClientVersionBit() <= 4) //Titanium needs alternate graphic. - ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_Titanium)), false, speed); + ProjectileAnimation(spell_target, (RuleI(Spells, FRProjectileItem_Titanium)), false, speed, 0.0f, 0.0f, arc); else - ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_SOF)), false, speed); - } + ProjectileAnimation(spell_target, (RuleI(Spells, FRProjectileItem_SOF)), false, speed, 0.0f, 0.0f, arc); + } else - ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_NPC)), false, speed); + ProjectileAnimation(spell_target, (RuleI(Spells, FRProjectileItem_NPC)), false, speed, 0.0f, 0.0f, arc); } //Default to an arrow if not using a mage bolt (Use up to date spell file and enable above rules for best results) else - ProjectileAnimation(spell_target,0, 1, speed); + ProjectileAnimation(spell_target, 0, 1, speed, 0.0f, 0.0f, arc); } - if (spells[spell_id].CastingAnim == 64) + if (spells[spell_id].casting_animation == 64) anim = 44; //Corrects for animation error. DoAnim(anim, 0, true, IsClient() ? FilterPCSpells : FilterNPCSpells); //Override the default projectile animation. @@ -6757,26 +9441,26 @@ void Mob::ResourceTap(int32 damage, uint16 spellid) return; for (int i = 0; i < EFFECT_COUNT; i++) { - if (spells[spellid].effectid[i] == SE_ResourceTap) { - damage = (damage * spells[spellid].base[i]) / 1000; - + if (spells[spellid].effect_id[i] == SE_ResourceTap) { + damage = (damage * spells[spellid].base_value[i]) / 1000; + if (damage) { - if (spells[spellid].max[i] && (damage > spells[spellid].max[i])) - damage = spells[spellid].max[i]; - - if (spells[spellid].base2[i] == 0) { // HP Tap + if (spells[spellid].max_value[i] && (damage > spells[spellid].max_value[i])) + damage = spells[spellid].max_value[i]; + + if (spells[spellid].limit_value[i] == 0) { // HP Tap if (damage > 0) HealDamage(damage); else Damage(this, -damage, 0, EQ::skills::SkillEvocation, false); } - - if (spells[spellid].base2[i] == 1) // Mana Tap + + if (spells[spellid].limit_value[i] == 1) // Mana Tap SetMana(GetMana() + damage); - - if (spells[spellid].base2[i] == 2 && IsClient()) // Endurance Tap + + if (spells[spellid].limit_value[i] == 2 && IsClient()) // Endurance Tap CastToClient()->SetEndurance(CastToClient()->GetEndurance() + damage); - + } } } @@ -6800,21 +9484,21 @@ void Mob::TryTriggerThreshHold(int32 damage, int effect_id, Mob* attacker){ for(int i = 0; i < EFFECT_COUNT; i++){ - if (spells[buffs[slot].spellid].effectid[i] == effect_id){ + if (spells[buffs[slot].spellid].effect_id[i] == effect_id){ - uint16 spell_id = spells[buffs[slot].spellid].base[i]; + uint16 spell_id = spells[buffs[slot].spellid].base_value[i]; - if (damage > spells[buffs[slot].spellid].base2[i]){ + if (damage > spells[buffs[slot].spellid].limit_value[i]){ BuffFadeBySlot(slot); if (IsValidSpell(spell_id)) { if (IsBeneficialSpell(spell_id)) - SpellFinished(spell_id, this, EQ::spells::CastingSlot::Item, 0, -1, spells[spell_id].ResistDiff); + SpellFinished(spell_id, this, EQ::spells::CastingSlot::Item, 0, -1, spells[spell_id].resist_difficulty); else if(attacker) - SpellFinished(spell_id, attacker, EQ::spells::CastingSlot::Item, 0, -1, spells[spell_id].ResistDiff); + SpellFinished(spell_id, attacker, EQ::spells::CastingSlot::Item, 0, -1, spells[spell_id].resist_difficulty); } } } @@ -6823,55 +9507,51 @@ void Mob::TryTriggerThreshHold(int32 damage, int effect_id, Mob* attacker){ } } -bool Mob::CheckSpellCategory(uint16 spell_id, int category_id, int effect_id){ +void Mob::CastSpellOnLand(Mob* caster, int32 spell_id) +{ + /* + This function checks for incoming spells on a mob, if they meet the criteria for focus SE_Fc_Cast_Spell_on_Land then + a new spell will be cast by THIS mob as specified by the focus effect. Note: Chance to cast the spell is determined in + the CalcFocusEffect function if not 100pct. + ApplyFocusProcLimiter() function checks for SE_Proc_Timer_Modifier which allows for limiting how often a spell from effect can be triggered + for example, if set to base=1 and base2= 1500, then for everyone 1 successful trigger, you will be unable to trigger again for 1.5 seconds. - if (!IsValidSpell(spell_id) || !category_id) - return false; - - int effectid = 0; - int category = 0; - - /*Category ID SE_LimitSpellClass [(+) Include (-) Exclude] - 1 = UNK - 2 = Cures - 3 = Offensive Spells - 4 = UNK - 5 = UNK - 6 = Lifetap + Live only has this focus in buffs/debuffs that can be placed on a target. TODO: Will consider adding support for it as AA and Item. */ + if (!caster) + return; - /*Category ID SE_LimitSpellSubClass [(+) Include (-) Exclude] - 5 = UNK - 8 = UNK - */ + int32 trigger_spell_id = 0; - if (effect_id == SE_LimitSpellClass) { + //Step 1: Check this focus effect exists on the mob. + if (spellbonuses.FocusEffects[focusFcCastSpellOnLand]) { - switch(category_id) - { - case 2: - if (IsCureSpell(spell_id)) - return true; - break; + int buff_count = GetMaxTotalSlots(); + for (int i = 0; i < buff_count; i++) { - case 3: - if (IsDetrimentalSpell(spell_id)) - return true; - break; + if ((IsValidSpell(buffs[i].spellid) && (buffs[i].spellid != spell_id) && IsEffectInSpell(buffs[i].spellid, SE_Fc_Cast_Spell_On_Land))) { - case 6: - if (spells[spell_id].targettype == ST_Tap || spells[spell_id].targettype == ST_TargetAETap) - return true; - break; + //Step 2: Check if we pass all focus limiters and focus chance roll + trigger_spell_id = CalcFocusEffect(focusFcCastSpellOnLand, buffs[i].spellid, spell_id, false, buffs[i].casterid, caster); + + if (IsValidSpell(trigger_spell_id) && (trigger_spell_id != spell_id)) { + + //Step 3: Cast spells + if (IsBeneficialSpell(trigger_spell_id)) { + SpellFinished(trigger_spell_id, this, EQ::spells::CastingSlot::Item, 0, -1, spells[trigger_spell_id].resist_difficulty); + } + else { + Mob* current_target = GetTarget(); + //For now don't let players cast detrimental effects on themselves if they are targeting themselves. Need to confirm behavior. + if (current_target && current_target->GetID() != GetID()) + SpellFinished(trigger_spell_id, current_target, EQ::spells::CastingSlot::Item, 0, -1, spells[trigger_spell_id].resist_difficulty); + } + } + if (i >= 0) + CheckNumHitsRemaining(NumHit::MatchingSpells, i); + } } } - - else if (effect_id == SE_LimitSpellSubclass) { - //Pending Implementation when category types are figured out. - return false; - } - - return false; } void Mob::CalcSpellPowerDistanceMod(uint16 spell_id, float range, Mob* caster) @@ -6885,12 +9565,12 @@ void Mob::CalcSpellPowerDistanceMod(uint16 spell_id, float range, Mob* caster) else distance = sqrt(range); - distance = EQ::Clamp(distance, spells[spell_id].min_dist, spells[spell_id].max_dist); + distance = EQ::Clamp(distance, spells[spell_id].min_distance, spells[spell_id].max_distance); - float dm_range = spells[spell_id].max_dist - spells[spell_id].min_dist; - float dm_mod_interval = spells[spell_id].max_dist_mod - spells[spell_id].min_dist_mod; - float dist_from_min = distance - spells[spell_id].min_dist; - float mod = spells[spell_id].min_dist_mod + (dist_from_min * (dm_mod_interval/dm_range)); + float dm_range = spells[spell_id].max_distance - spells[spell_id].min_distance; + float dm_mod_interval = spells[spell_id].max_distance_mod - spells[spell_id].min_distance_mod; + float dist_from_min = distance - spells[spell_id].min_distance; + float mod = spells[spell_id].min_distance_mod + (dist_from_min * (dm_mod_interval/dm_range)); mod *= 100.0f; SetSpellPowerDistanceMod(static_cast(mod)); @@ -6953,8 +9633,636 @@ void Client::BreakFeignDeathWhenCastOn(bool IsResisted) MessageString(Chat::SpellFailure,FD_CAST_ON_NO_BREAK); return; } - + SetFeigned(false); MessageString(Chat::SpellFailure,FD_CAST_ON); } } + +bool Mob::HarmonySpellLevelCheck(int32 spell_id, Mob *target) +{ + //'this' = caster of spell + if (!target) { + return false; + } + + for (int i = 0; i < EFFECT_COUNT; i++) { + // not important to check limit on SE_Lull as it doesnt have one and if the other components won't land, then SE_Lull wont either + if (spells[spell_id].effect_id[i] == SE_ChangeFrenzyRad || spells[spell_id].effect_id[i] == SE_Harmony) { + if ((spells[spell_id].max_value[i] != 0 && target->GetLevel() > spells[spell_id].max_value[i]) || target->GetSpecialAbility(IMMUNE_PACIFY)) { + return false; + } + } + } + return true; +} + +bool Mob::PassCharmTargetRestriction(Mob *target) { + + //Level restriction check should not go here. + if (!target) { + return false; + } + + if (target->IsClient() && IsClient()) { + MessageString(Chat::Red, CANNOT_AFFECT_PC); + LogSpells("Spell casting canceled: Can not cast charm on a client."); + return false; + } + else if (target->IsCorpse()) { + LogSpells("Spell casting canceled: Can not cast charm on a corpse."); + return false; + } + else if (GetPet() && IsClient()) { + MessageString(Chat::Red, ONLY_ONE_PET); + LogSpells("Spell casting canceled: Can not cast charm if you have a pet."); + return false; + } + else if (target->GetOwner()) { + MessageString(Chat::Red, CANNOT_CHARM); + LogSpells("Spell casting canceled: Can not cast charm on a pet."); + return false; + } + return true; +} + +bool Mob::PassLimitToSkill(EQ::skills::SkillType skill, int32 spell_id, int proc_type, int aa_id) +{ + /* + Check if SE_AddMeleProc or SE_RangedProc have a skill limiter. Passes automatically if no skill limiters present. + */ + int32 proc_type_spaid = 0; + if (proc_type == ProcType::MELEE_PROC) { + proc_type_spaid = SE_AddMeleeProc; + } + if (proc_type == ProcType::RANGED_PROC) { + proc_type_spaid = SE_RangedProc; + } + + bool match_proc_type = false; + bool has_limit_check = false; + + if (!aa_id && spellbonuses.LimitToSkill[EQ::skills::HIGHEST_SKILL + 3]) { + + if (spell_id == SPELL_UNKNOWN) { + return false; + } + + for (int i = 0; i < EFFECT_COUNT; i++) { + if (spells[spell_id].effect_id[i] == proc_type_spaid) { + match_proc_type = true; + } + if (match_proc_type && spells[spell_id].effect_id[i] == SE_LimitToSkill && spells[spell_id].base_value[i] <= EQ::skills::HIGHEST_SKILL) { + + has_limit_check = true; + if (spells[spell_id].base_value[i] == skill) { + return true; + } + } + } + } + else if (aabonuses.LimitToSkill[EQ::skills::HIGHEST_SKILL + 3]) { + + int rank_id = 1; + AA::Rank *rank = zone->GetAlternateAdvancementRank(aa_id); + + if (!rank) { + return true; + } + + AA::Ability *ability_in = rank->base_ability; + if (!ability_in) { + return true; + } + + for (auto &aa : aa_ranks) { + auto ability_rank = zone->GetAlternateAdvancementAbilityAndRank(aa.first, aa.second.first); + auto ability = ability_rank.first; + auto rank = ability_rank.second; + + if (!ability) { + continue; + } + + for (auto &effect : rank->effects) { + if (effect.effect_id == proc_type_spaid) { + match_proc_type = true; + } + + if (match_proc_type && effect.effect_id == SE_LimitToSkill && effect.base_value <= EQ::skills::HIGHEST_SKILL) { + has_limit_check = true; + if (effect.base_value == skill) { + return true; + } + } + } + } + } + + if (has_limit_check) { + return false; //Limit was found, but not matched, fail. + } + else { + return true; //No limit is present, automatically pass. + } +} + +bool Mob::CanFocusUseRandomEffectivenessByType(focusType type) +{ + switch (type) { + case focusImprovedDamage: + case focusImprovedDamage2: + case focusImprovedHeal: + case focusManaCost: + case focusResistRate: + case focusFcDamagePctCrit: + case focusReagentCost: + case focusSpellHateMod: + case focusSpellVulnerability: + case focusFcSpellDamagePctIncomingPC: + case focusFcHealPctIncoming: + case focusFcHealPctCritIncoming: + return true; + } + + return false; +} + +int Mob::GetFocusRandomEffectivenessValue(int focus_base, int focus_base2, bool best_focus) +{ + int value = 0; + // This is used to determine which focus should be used for the random calculation + if (best_focus) { + // If the spell does not contain a base2 value, then its a straight non random + value = focus_base; + // If the spell contains a value in the base2 field then that is the max value + if (focus_base2 != 0) { + value = focus_base2; + } + return value; + } + else if (focus_base2 == 0 || focus_base == focus_base2) { // Actual focus calculation starts here + return focus_base; + } + + return zone->random.Int(focus_base, focus_base2); +} + +bool Mob::NegateSpellEffect(uint16 spell_id, int effect_id) +{ + /* + This works for most effects, anything handled purely by the client will bypass this (ie Gate, Shadowstep) + Seen with resurrection effects, likely blocks the client from accepting a ressurection request. *Not implement at this time. + */ + + for (int i = 0; i < GetMaxTotalSlots(); i++) { + //Check for any buffs containing NegateEffect + if (IsValidSpell(buffs[i].spellid) && IsEffectInSpell(buffs[i].spellid, SE_NegateSpellEffect) && spell_id != buffs[i].spellid) { + //Match each of the negate effects with the current spell effect, if found, that effect will not be applied. + for (int j = 0; j < EFFECT_COUNT; j++) + { + if (spells[buffs[i].spellid].effect_id[j] == SE_NegateSpellEffect && + spells[buffs[i].spellid].limit_value[j] == effect_id && + (spells[buffs[i].spellid].base_value[j] == NEGATE_SPA_ALL_BONUSES || + spells[buffs[i].spellid].base_value[j] == NEGATE_SPA_SPELLBONUS || + spells[buffs[i].spellid].base_value[j] == NEGATE_SPA_SPELLBONUS_AND_ITEMBONUS || + spells[buffs[i].spellid].base_value[j] == NEGATE_SPA_SPELLBONUS_AND_AABONUS)) { + return true; + } + } + } + } + return false; +} + +int Mob::GetMemoryBlurChance(int base_chance) +{ + /* + Memory Blur mechanic for SPA 62 + Chance formula is effect chance + charisma modifer + caster level modifier + Effect chance is base value of spell + Charisma modifier is CHA/10 = %, with MAX of 15% (thus 150 cha gives you max bonus) + Caster level modifier. +100% if caster < level 17 which scales down to 25% at > 53. ** + (Yes the above gets worse as you level. Behavior was confirmed on live.) + Memory blur is applied to mez on initial cast using same formula. However, recasting on a target that + is already mezed will not give a chance to memory blur. The blur is not checked on buff ticks. + + SPA 242 SE_IncreaseChanceMemwipe modifies the final chance after all bonuses are applied. + This is also applied to memory blur from mez spells. + + this = caster + */ + int cha_mod = int(GetCHA() / 10); + cha_mod = std::min(cha_mod, 15); + + int lvl_mod = 0; + if (GetLevel() < 17) { + lvl_mod = 100; + } + else if (GetLevel() > 53) { + lvl_mod = 25; + } + else { + lvl_mod = 100 + ((GetLevel() - 16)*-2);//Derived from above range of values.** + } + + int chance = cha_mod + lvl_mod + base_chance; + + int chance_mod = spellbonuses.IncreaseChanceMemwipe + itembonuses.IncreaseChanceMemwipe + aabonuses.IncreaseChanceMemwipe; + + chance += chance * chance_mod / 100; + return chance; +} + +void Mob::VirusEffectProcess() +{ + /* + Virus Mechanics + To qualify as a virus effect buff, all of the following spell table need to be set. (At some point will correct names) + viral_targets = MIN_SPREAD_TIME + viral_timer = MAX_SPREAD_TIME + viral_range = SPREAD_RADIUS + Once a buff with a viral effect is applied, a 1000 ms timer will begin. + The time at which the virus will attempt to spread is determined by a random value between MIN_SPREAD_TIME and MAX_SPREAD_TIME + Each time the virus attempts to spread the next time interval will be chosen at random again. + If a spreader finds a target for viral buff, when the viral buff spreads the duration on the new target will be the time remaining on the spreaders buff. + Spreaders DOES NOT need LOS to spread. There is no max amount of targets the virus can spread to. + When the spreader no longer has any viral buffs the timer stops. + The current code supports spreading for both detrimental and beneficial spells. + */ + + // Only spread in zones without perm buffs + if (zone->BuffTimersSuspended()) { + viral_timer.Disable(); + return; + } + + bool stop_timer = true; + for (int buffs_i = 0; buffs_i < GetMaxTotalSlots(); ++buffs_i) + { + if (IsValidSpell(buffs[buffs_i].spellid) && IsVirusSpell(buffs[buffs_i].spellid)) + { + if (buffs[buffs_i].virus_spread_time > 0) { + buffs[buffs_i].virus_spread_time -= 1; + stop_timer = false; + } + + if (buffs[buffs_i].virus_spread_time <= 0) { + buffs[buffs_i].virus_spread_time = zone->random.Int(GetViralMinSpreadTime(buffs[buffs_i].spellid), GetViralMaxSpreadTime(buffs[buffs_i].spellid)); + SpreadVirusEffect(buffs[buffs_i].spellid, buffs[buffs_i].casterid, buffs[buffs_i].ticsremaining); + stop_timer = false; + } + } + } + + if (stop_timer) { + viral_timer.Disable(); + } +} + +void Mob::SpreadVirusEffect(int32 spell_id, uint32 caster_id, int32 buff_tics_remaining) +{ + Mob *caster = entity_list.GetMob(caster_id); + + if (!caster) { + return; + } + + std::vector targets_in_range = entity_list.GetTargetsForVirusEffect( + this, + caster, + GetViralSpreadRange(spell_id), + spells[spell_id].pcnpc_only_flag, + spell_id + ); + + for (auto &mob: targets_in_range) { + if (!mob) { + continue; + } + + if (!mob->FindBuff(spell_id)) { + if (caster) { + if (buff_tics_remaining) { + //When virus is spread, the buff on new target is applied with the amount of time remaining on the spreaders buff. + caster->SpellOnTarget(spell_id, mob, 0, false, 0, false, -1, buff_tics_remaining); + } + } + } + } +} + +bool Mob::IsFocusProcLimitTimerActive(int32 focus_spell_id) { + /* + Used with SPA 511 SE_Ff_FocusTimerMin to limit how often a focus effect can be applied. + Ie. Can only have a spell trigger once every 15 seconds, or to be more creative can only + have the fire spells received a very high special focused once every 30 seconds. + Note, this stores timers for both spell, item and AA related focuses For AA the focus_spell_id + is saved as the the negative value of the rank.id (to avoid conflicting with spell_ids) + */ + for (int i = 0; i < MAX_FOCUS_PROC_LIMIT_TIMERS; i++) { + if (focusproclimit_spellid[i] == focus_spell_id) { + if (focusproclimit_timer[i].Enabled()) { + if (focusproclimit_timer[i].GetRemainingTime() > 0) { + return true; + } + else { + focusproclimit_timer[i].Disable(); + focusproclimit_spellid[i] = 0; + } + } + } + } + return false; +} + +void Mob::SetFocusProcLimitTimer(int32 focus_spell_id, uint32 focus_reuse_time) { + + bool is_set = false; + + for (int i = 0; i < MAX_FOCUS_PROC_LIMIT_TIMERS; i++) { + if (!focusproclimit_spellid[i] && !is_set) { + focusproclimit_spellid[i] = focus_spell_id; + focusproclimit_timer[i].SetTimer(focus_reuse_time); + is_set = true; + } + //Remove old temporary focus if was from a buff you no longer have. + else if (focusproclimit_spellid[i] > 0 && !FindBuff(focus_spell_id)) { + focusproclimit_spellid[i] = 0; + focusproclimit_timer[i].Disable(); + } + } +} + +bool Mob::IsProcLimitTimerActive(int32 base_spell_id, uint32 proc_reuse_time, int proc_type) { + /* + Used with SPA 512 SE_Proc_Timer_Modifier to limit how often a proc can be cast. + If this effect exists it will prevent the next proc from firing until the timer + defined in SPA 512 is finished. Ie. 1 proc every 55 seconds. + Spell, Ranged, and Defensive procs all have their own timer array, therefore + you can stack multiple different types of effects in the same spell. Make sure + SPA 512 goes directly after each proc you want to have the timer. + */ + if (!proc_reuse_time) { + return false; + } + + for (int i = 0; i < MAX_PROC_LIMIT_TIMERS; i++) { + + if (proc_type == ProcType::MELEE_PROC) { + if (spell_proclimit_spellid[i] == base_spell_id) { + if (spell_proclimit_timer[i].Enabled()) { + if (spell_proclimit_timer[i].GetRemainingTime() > 0) { + return true; + } + else { + spell_proclimit_timer[i].Disable(); + spell_proclimit_spellid[i] = 0; + } + } + } + } + else if (proc_type == ProcType::RANGED_PROC) { + if (ranged_proclimit_spellid[i] == base_spell_id) { + if (ranged_proclimit_timer[i].Enabled()) { + if (ranged_proclimit_timer[i].GetRemainingTime() > 0) { + return true; + } + else { + ranged_proclimit_timer[i].Disable(); + ranged_proclimit_spellid[i] = 0; + } + } + } + } + else if (proc_type == ProcType::DEFENSIVE_PROC) { + if (def_proclimit_spellid[i] == base_spell_id) { + if (def_proclimit_timer[i].Enabled()) { + if (def_proclimit_timer[i].GetRemainingTime() > 0) { + return true; + } + else { + def_proclimit_timer[i].Disable(); + def_proclimit_spellid[i] = 0; + } + } + } + } + } + return false; +} + +void Mob::SetProcLimitTimer(int32 base_spell_id, uint32 proc_reuse_time, int proc_type) { + + if (!proc_reuse_time) { + return; + } + + bool is_set = false; + + for (int i = 0; i < MAX_PROC_LIMIT_TIMERS; i++) { + + if (proc_type == ProcType::MELEE_PROC) { + if (!spell_proclimit_spellid[i] && !is_set) { + spell_proclimit_spellid[i] = base_spell_id; + spell_proclimit_timer[i].SetTimer(proc_reuse_time); + is_set = true; + } + else if (spell_proclimit_spellid[i] > 0 && !FindBuff(base_spell_id)) { + spell_proclimit_spellid[i] = 0; + spell_proclimit_timer[i].Disable(); + } + } + + if (proc_type == ProcType::RANGED_PROC) { + if (!ranged_proclimit_spellid[i] && !is_set) { + ranged_proclimit_spellid[i] = base_spell_id; + ranged_proclimit_timer[i].SetTimer(proc_reuse_time); + is_set = true; + } + else if (ranged_proclimit_spellid[i] > 0 && !FindBuff(base_spell_id)) { + ranged_proclimit_spellid[i] = 0; + ranged_proclimit_timer[i].Disable(); + } + } + + if (proc_type == ProcType::DEFENSIVE_PROC) { + if (!def_proclimit_spellid[i] && !is_set) { + def_proclimit_spellid[i] = base_spell_id; + def_proclimit_timer[i].SetTimer(proc_reuse_time); + is_set = true; + } + else if (def_proclimit_spellid[i] > 0 && !FindBuff(base_spell_id)) { + def_proclimit_spellid[i] = 0; + def_proclimit_timer[i].Disable(); + } + } + } +} + +void Mob::SendIllusionWearChange(Client* c) { + + /* + We send this to client on Client::CompleteConnect() to properly update textures of + other mobs in zone with illusions on them. + */ + if (!c) { + return; + } + + if (!spellbonuses.Illusion && !itembonuses.Illusion && !aabonuses.Illusion) { + return; + } + + for (int x = EQ::textures::textureBegin; x <= EQ::textures::LastTintableTexture; x++) { + SendWearChange(x, c); + } +} + +void Mob::ApplyIllusionToCorpse(int32 spell_id, Corpse* new_corpse) { + + //Transfers most illusions over to the corpse upon death + if (!IsValidSpell(spell_id)) { + return; + } + + if (!new_corpse) { + return; + } + + for (int i = 0; i < EFFECT_COUNT; i++){ + if (spells[spell_id].effect_id[i] == SE_Illusion) { + new_corpse->ApplySpellEffectIllusion(spell_id, nullptr, -1, spells[spell_id].base_value[i], spells[spell_id].limit_value[i], spells[spell_id].max_value[i]); + return; + } + } +} + +void Mob::ApplySpellEffectIllusion(int32 spell_id, Mob *caster, int buffslot, int base, int limit, int max) +{ + // Gender Illusions + if (base == -1) { + // Specific Gender Illusions + if (spell_id == SPELL_ILLUSION_MALE || spell_id == SPELL_ILLUSION_FEMALE) { + int specific_gender = -1; + // Male + if (spell_id == SPELL_ILLUSION_MALE) + specific_gender = 0; + // Female + else if (spell_id == SPELL_ILLUSION_FEMALE) + specific_gender = 1; + if (specific_gender > -1) { + if (caster && caster->GetTarget()) { + SendIllusionPacket + ( + caster->GetTarget()->GetBaseRace(), + specific_gender, + caster->GetTarget()->GetTexture() + ); + } + } + } + // Change Gender Illusions + else { + if (caster && caster->GetTarget()) { + int opposite_gender = 0; + if (caster->GetTarget()->GetGender() == 0) + opposite_gender = 1; + + SendIllusionPacket + ( + caster->GetTarget()->GetRace(), + opposite_gender, + caster->GetTarget()->GetTexture() + ); + } + } + } + // Racial Illusions + else { + auto gender_id = ( + max > 0 && + ( + max != 3 || + limit == 0 + ) ? + (max - 1) : + Mob::GetDefaultGender(base, GetGender()) + ); + + auto race_size = GetRaceGenderDefaultHeight( + base, + gender_id + ); + + if (base != RACE_ELEMENTAL_75) { + if (max > 0) { + if (limit == 0) { + SendIllusionPacket( + base, + gender_id + ); + } + else { + if (max != 3) { + SendIllusionPacket( + base, + gender_id, + limit, + max + ); + } + else { + SendIllusionPacket( + base, + gender_id, + limit, + limit + ); + } + } + } + else { + SendIllusionPacket( + base, + gender_id, + limit, + max + ); + } + + } + else { + SendIllusionPacket( + base, + gender_id, + limit + ); + } + SendAppearancePacket(AT_Size, race_size); + } + + for (int x = EQ::textures::textureBegin; x <= EQ::textures::LastTintableTexture; x++) { + SendWearChange(x); + } + + if (buffslot != -1) { + if (caster == this && spell_id != SPELL_MINOR_ILLUSION && spell_id != SPELL_ILLUSION_TREE && + (spellbonuses.IllusionPersistence || aabonuses.IllusionPersistence || itembonuses.IllusionPersistence)) { + buffs[buffslot].persistant_buff = 1; + } + else { + buffs[buffslot].persistant_buff = 0; + } + } +} + +bool Mob::HasPersistDeathIllusion(int32 spell_id) { + + if (spellbonuses.IllusionPersistence > 1 || aabonuses.IllusionPersistence > 1 || itembonuses.IllusionPersistence > 1) { + if (spell_id != SPELL_MINOR_ILLUSION && spell_id != SPELL_ILLUSION_TREE && IsEffectInSpell(spell_id, SE_Illusion) && IsBeneficialSpell(spell_id)) { + return true; + } + } + return false; +} diff --git a/zone/spells.cpp b/zone/spells.cpp index f1000dfd4..276e49e8e 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -136,9 +136,6 @@ void Mob::SpellProcess() void NPC::SpellProcess() { Mob::SpellProcess(); - if (swarm_timer.Check()) { - DepopSwarmPets(); - } } /////////////////////////////////////////////////////////////////////////////// @@ -160,151 +157,75 @@ bool Mob::CastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot, uint32 aa_id) { LogSpells("CastSpell called for spell [{}] ([{}]) on entity [{}], slot [{}], time [{}], mana [{}], from item slot [{}]", - (IsValidSpell(spell_id))?spells[spell_id].name:"UNKNOWN SPELL", spell_id, target_id, static_cast(slot), cast_time, mana_cost, (item_slot==0xFFFFFFFF)?999:item_slot); + (IsValidSpell(spell_id)) ? spells[spell_id].name : "UNKNOWN SPELL", spell_id, target_id, static_cast(slot), cast_time, mana_cost, (item_slot == 0xFFFFFFFF) ? 999 : item_slot); - if(casting_spell_id == spell_id) + if (casting_spell_id == spell_id) { ZeroCastingVars(); + } - if - ( - !IsValidSpell(spell_id) || + //If spell fails checks here determine if we need to send packet to client to reset spell bar. + bool send_spellbar_enable = true; + if ((item_slot != -1 && cast_time == 0) || aa_id) { + send_spellbar_enable = false; + } + + if (!IsValidSpell(spell_id) || casting_spell_id || delaytimer || - spellend_timer.Enabled() || - IsStunned() || - IsFeared() || - IsMezzed() || - (IsSilenced() && !IsDiscipline(spell_id)) || - (IsAmnesiad() && IsDiscipline(spell_id)) - ) - { - LogSpells("Spell casting canceled: not able to cast now. Valid? [{}], casting [{}], waiting? [{}], spellend? [{}], stunned? [{}], feared? [{}], mezed? [{}], silenced? [{}], amnesiad? [{}]", - IsValidSpell(spell_id), casting_spell_id, delaytimer, spellend_timer.Enabled(), IsStunned(), IsFeared(), IsMezzed(), IsSilenced(), IsAmnesiad() ); - if(IsSilenced() && !IsDiscipline(spell_id)) - MessageString(Chat::Red, SILENCED_STRING); - if(IsAmnesiad() && IsDiscipline(spell_id)) - MessageString(Chat::Red, MELEE_SILENCE); - if(IsClient()) - CastToClient()->SendSpellBarEnable(spell_id); - if(casting_spell_id && IsNPC()) - CastToNPC()->AI_Event_SpellCastFinished(false, static_cast(casting_spell_slot)); - return(false); + spellend_timer.Enabled()) { + LogSpells("Spell casting canceled: not able to cast now. Valid? [{}], casting [{}], waiting? [{}], spellend? [{}]", + IsValidSpell(spell_id), casting_spell_id, delaytimer, spellend_timer.Enabled()); + StopCastSpell(spell_id, send_spellbar_enable); + return false; } + + if (!DoCastingChecksOnCaster(spell_id) || + !DoCastingChecksZoneRestrictions(true, spell_id) || + !DoCastingChecksOnTarget(true, spell_id, entity_list.GetMobID(target_id))) { + StopCastSpell(spell_id, send_spellbar_enable); + return false; + } + else { + casting_spell_checks = true; + } + //It appears that the Sanctuary effect is removed by a check on the client side (keep this however for redundancy) - if (spellbonuses.Sanctuary && (spells[spell_id].targettype != ST_Self && GetTarget() != this) || IsDetrimentalSpell(spell_id)) + if (spellbonuses.Sanctuary && (spells[spell_id].target_type != ST_Self && GetTarget() != this) || IsDetrimentalSpell(spell_id)) { BuffFadeByEffect(SE_Sanctuary); - - if(IsClient()){ - int chance = CastToClient()->GetFocusEffect(focusFcMute, spell_id);//Client only - - if (zone->random.Roll(chance)) { - MessageString(Chat::Red, SILENCED_STRING); - if(IsClient()) - CastToClient()->SendSpellBarEnable(spell_id); - return(false); - } } - if(IsDetrimentalSpell(spell_id) && !zone->CanDoCombat()){ - MessageString(Chat::Red, SPELL_WOULDNT_HOLD); - if(IsClient()) - CastToClient()->SendSpellBarEnable(spell_id); - if(casting_spell_id && IsNPC()) - CastToNPC()->AI_Event_SpellCastFinished(false, static_cast(casting_spell_slot)); - return(false); - } - - //cannot cast under divine aura - if(DivineAura()) { - LogSpells("Spell casting canceled: cannot cast while Divine Aura is in effect"); - InterruptSpell(173, 0x121, false); - return(false); - } - - if (spellbonuses.NegateIfCombat) + if (spellbonuses.NegateIfCombat) { BuffFadeByEffect(SE_NegateIfCombat); - - if(IsClient() && GetTarget() && IsHarmonySpell(spell_id)) - { - for(int i = 0; i < EFFECT_COUNT; i++) { - // not important to check limit on SE_Lull as it doesnt have one and if the other components won't land, then SE_Lull wont either - if (spells[spell_id].effectid[i] == SE_ChangeFrenzyRad || spells[spell_id].effectid[i] == SE_Harmony) { - if((spells[spell_id].max[i] != 0 && GetTarget()->GetLevel() > spells[spell_id].max[i]) || GetTarget()->GetSpecialAbility(IMMUNE_PACIFY)) { - InterruptSpell(CANNOT_AFFECT_NPC, 0x121, spell_id); - return(false); - } - } - } } - if (HasActiveSong() && IsBardSong(spell_id)) { + //Casting a spell from an item click will also stop bard pulse. + if (HasActiveSong() && (IsBardSong(spell_id) || slot == CastingSlot::Item)) { LogSpells("Casting a new song while singing a song. Killing old song [{}]", bardsong); //Note: this does NOT tell the client - _StopSong(); + ZeroBardPulseVars(); } //Added to prevent MQ2 exploitation of equipping normally-unequippable/clickable items with effects and clicking them for benefits. - if(item_slot && IsClient() && (slot == CastingSlot::Item || slot == CastingSlot::PotionBelt)) + if (item_slot && IsClient() && (slot == CastingSlot::Item || slot == CastingSlot::PotionBelt)) { - EQ::ItemInstance *itm = CastToClient()->GetInv().GetItem(item_slot); - int bitmask = 1; - bitmask = bitmask << (CastToClient()->GetClass() - 1); - if( itm && itm->GetItem()->Classes != 65535 ) { - if ((itm->GetItem()->Click.Type == EQ::item::ItemEffectEquipClick) && !(itm->GetItem()->Classes & bitmask)) { - if (CastToClient()->ClientVersion() < EQ::versions::ClientVersion::SoF) { - // They are casting a spell from an item that requires equipping but shouldn't let them equip it - LogError("HACKER: [{}] (account: [{}]) attempted to click an equip-only effect on item [{}] (id: [{}]) which they shouldn't be able to equip!", - CastToClient()->GetCleanName(), CastToClient()->AccountName(), itm->GetItem()->Name, itm->GetItem()->ID); - database.SetHackerFlag(CastToClient()->AccountName(), CastToClient()->GetCleanName(), "Clicking equip-only item with an invalid class"); - } - else { - MessageString(Chat::Red, MUST_EQUIP_ITEM); - } - return(false); - } - if ((itm->GetItem()->Click.Type == EQ::item::ItemEffectClick2) && !(itm->GetItem()->Classes & bitmask)) { - if (CastToClient()->ClientVersion() < EQ::versions::ClientVersion::SoF) { - // They are casting a spell from an item that they don't meet the race/class requirements to cast - LogError("HACKER: [{}] (account: [{}]) attempted to click a race/class restricted effect on item [{}] (id: [{}]) which they shouldn't be able to click!", - CastToClient()->GetCleanName(), CastToClient()->AccountName(), itm->GetItem()->Name, itm->GetItem()->ID); - database.SetHackerFlag(CastToClient()->AccountName(), CastToClient()->GetCleanName(), "Clicking race/class restricted item with an invalid class"); - } - else { - if (CastToClient()->ClientVersion() >= EQ::versions::ClientVersion::RoF) - { - // Line 181 in eqstr_us.txt was changed in RoF+ - Message(Chat::Yellow, "Your race, class, or deity cannot use this item."); - } - else - { - MessageString(Chat::Red, CANNOT_USE_ITEM); - } - } - return(false); - } - } - if (itm && (itm->GetItem()->Click.Type == EQ::item::ItemEffectEquipClick) && item_slot > EQ::invslot::EQUIPMENT_END){ - if (CastToClient()->ClientVersion() < EQ::versions::ClientVersion::SoF) { - // They are attempting to cast a must equip clicky without having it equipped - LogError("HACKER: [{}] (account: [{}]) attempted to click an equip-only effect on item [{}] (id: [{}]) without equiping it!", CastToClient()->GetCleanName(), CastToClient()->AccountName(), itm->GetItem()->Name, itm->GetItem()->ID); - database.SetHackerFlag(CastToClient()->AccountName(), CastToClient()->GetCleanName(), "Clicking equip-only item without equiping it"); - } - else { - MessageString(Chat::Red, MUST_EQUIP_ITEM); - } - return(false); + if (!CheckItemRaceClassDietyRestrictionsOnCast(item_slot)) { + StopCastSpell(spell_id, send_spellbar_enable); + return false; } } + std::string export_string = fmt::format("{}", spell_id); if(IsClient()) { - char temp[64]; - sprintf(temp, "%d", spell_id); - if (parse->EventPlayer(EVENT_CAST_BEGIN, CastToClient(), temp, 0) != 0) - return false; + if (parse->EventPlayer(EVENT_CAST_BEGIN, CastToClient(), export_string, 0) != 0) { + if (IsDiscipline(spell_id)) { + CastToClient()->SendDisciplineTimer(spells[spell_id].timer_id, 0); + } else { + CastToClient()->SendSpellBarEnable(spell_id); + } + return(false); + } } else if(IsNPC()) { - char temp[64]; - sprintf(temp, "%d", spell_id); - parse->EventNPC(EVENT_CAST_BEGIN, CastToNPC(), nullptr, temp, 0); + parse->EventNPC(EVENT_CAST_BEGIN, CastToNPC(), nullptr, export_string, 0); } //To prevent NPC ghosting when spells are cast from scripts @@ -318,7 +239,7 @@ bool Mob::CastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot, } else { - return(DoCastSpell(spell_id, target_id, slot, cast_time, mana_cost, oSpellWillFinish, item_slot, timer, timer_duration, spells[spell_id].ResistDiff, aa_id)); + return(DoCastSpell(spell_id, target_id, slot, cast_time, mana_cost, oSpellWillFinish, item_slot, timer, timer_duration, spells[spell_id].resist_difficulty, aa_id)); } } @@ -383,7 +304,7 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot, Chat::SpellFailure, (IsClient() ? FilterPCSpells : FilterNPCSpells), (fizzle_msg == MISS_NOTE ? MISSED_NOTE_OTHER : SPELL_FIZZLE_OTHER), - /* + /* MessageFormat: You miss a note, bringing your song to a close! (if missed note) MessageFormat: A missed note brings %1's song to a close! MessageFormat: %1's spell fizzles! @@ -391,7 +312,7 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot, GetName() ); - TryTriggerOnValueAmount(false, true); + TryTriggerOnCastRequirement(); return(false); } @@ -402,27 +323,34 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot, // and a target wasn't provided, then it's us; unless TGB is on and this // is a TGB compatible spell. if((IsGroupSpell(spell_id) || - spell.targettype == ST_AEClientV1 || - spell.targettype == ST_Self || - spell.targettype == ST_AECaster || - spell.targettype == ST_Ring || - spell.targettype == ST_Beam || - spell.targettype == ST_TargetOptional) && target_id == 0) + spell.target_type == ST_AEClientV1 || + spell.target_type == ST_Self || + spell.target_type == ST_AECaster || + spell.target_type == ST_Ring || + spell.target_type == ST_Beam) && target_id == 0) { - LogSpells("Spell [{}] auto-targeted the caster. Group? [{}], target type [{}]", spell_id, IsGroupSpell(spell_id), spell.targettype); + LogSpells("Spell [{}] auto-targeted the caster. Group? [{}], target type [{}]", spell_id, IsGroupSpell(spell_id), spell.target_type); target_id = GetID(); } - if(cast_time <= -1) { + if (cast_time <= -1) { // save the non-reduced cast time to use in the packet cast_time = orgcasttime = spell.cast_time; // if there's a cast time, check if they have a modifier for it - if(cast_time) { + if (cast_time) { cast_time = GetActSpellCasttime(spell_id, cast_time); } } - else + //must use SPA 415 with focus (SPA 127/500/501) to reduce item recast + else if (cast_time && IsClient() && slot == CastingSlot::Item && item_slot != 0xFFFFFFFF) { orgcasttime = cast_time; + if (cast_time) { + cast_time = GetActSpellCasttime(spell_id, cast_time); + } + } + else { + orgcasttime = cast_time; + } // we checked for spells not requiring targets above if(target_id == 0) { @@ -434,6 +362,7 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot, } else { InterruptSpell(0, 0, 0); //the 0 args should cause no messages } + ZeroCastingVars(); return(false); } @@ -441,39 +370,42 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot, casting_spell_targetid = target_id; // We don't get actual mana cost here, that's done when we consume the mana - if (mana_cost == -1) + if (mana_cost == -1) { mana_cost = spell.mana; + } // mana is checked for clients on the frontend. we need to recheck it for NPCs though // If you're at full mana, let it cast even if you dont have enough mana // we calculated this above, now enforce it - if(mana_cost > 0 && slot != CastingSlot::Item) - { + if(mana_cost > 0 && slot != CastingSlot::Item) { int my_curmana = GetMana(); int my_maxmana = GetMaxMana(); - if(my_curmana < mana_cost) // not enough mana - { + if(my_curmana < mana_cost) {// not enough mana //this is a special case for NPCs with no mana... - if(IsNPC() && my_curmana == my_maxmana) - { + if(IsNPC() && my_curmana == my_maxmana){ mana_cost = 0; - } else { + } + else { + //The client will prevent spell casting if insufficient mana, this is only for serverside enforcement. LogSpells("Spell Error not enough mana spell=[{}] mymana=[{}] cost=[{}]\n", spell_id, my_curmana, mana_cost); if(IsClient()) { //clients produce messages... npcs should not for this case MessageString(Chat::Red, INSUFFICIENT_MANA); InterruptSpell(); - } else { + } + else { InterruptSpell(0, 0, 0); //the 0 args should cause no messages } + ZeroCastingVars(); return(false); } } } - if(mana_cost > GetMana()) + if (mana_cost > GetMana()) { mana_cost = GetMana(); + } // we know our mana cost now casting_spell_mana = mana_cost; @@ -486,16 +418,13 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot, // now tell the people in the area -- we ALWAYS want to send this, even instant cast spells. // The only time this is skipped is for NPC innate procs and weapon procs. Procs from buffs // oddly still send this. Since those cases don't reach here, we don't need to check them - if (slot != CastingSlot::Discipline) + if (slot != CastingSlot::Discipline) { SendBeginCast(spell_id, orgcasttime); + } // cast time is 0, just finish it right now and be done with it if(cast_time == 0) { - if (!DoCastingChecks()) { - StopCasting(); - return false; - } - CastedSpellFinished(spell_id, target_id, slot, mana_cost, item_slot, resist_adjust); + CastedSpellFinished(spell_id, target_id, slot, mana_cost, item_slot, resist_adjust); // return(true); } @@ -522,11 +451,6 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot, MessageString(Chat::Spells, BEGINS_TO_GLOW, item->GetItem()->Name); } - if (!DoCastingChecks()) { - StopCasting(); - return false; - } - return(true); } @@ -553,60 +477,390 @@ void Mob::SendBeginCast(uint16 spell_id, uint32 casttime) safe_delete(outapp); } -/* - * Some failures should be caught before the spell finishes casting - * This is especially helpful to clients when they cast really long things - * If this passes it sets casting_spell_checks to true which is checked in - * SpellProcess(), if a situation ever arises where a spell is delayed by these - * it's probably doing something wrong. - */ +bool Mob::DoCastingChecksOnCaster(int32 spell_id) { -bool Mob::DoCastingChecks() -{ + /* + These are casting requirements on the CASTER that will cancel a spell before spell finishes casting or prevent spell from casting. + - caster_requirmement_id : checks specific requirements on caster (cast initiates) + - linked timer spells. (cast initiates) [cancel before begin cast message] + - must be out of combat spell field. (client blocks) + - must be in combat spell field. (client blocks) + + Always checked at the start of CastSpell. + Checked before special cases for bards casting from SpellFinished. + */ + + /* + Cannot cast if stunned or mezzed, unless spell has 'cast_not_standing' flag. + */ + if ((IsStunned() || IsMezzed()) && !IgnoreCastingRestriction(spell_id)) { + LogSpells("Spell casting canceled [{}] : can not cast spell when stunned.", spell_id); + return false; + } + /* + Can not cast if feared. + */ + if (IsFeared()) { + LogSpells("Spell casting canceled [{}] : can not cast spell when feared.", spell_id); + return false; + } + /* + Can not cast if spell + */ + if ((IsSilenced() && !IsDiscipline(spell_id))) { + MessageString(Chat::Red, SILENCED_STRING); + LogSpells("Spell casting canceled [{}] : can not cast spell when silenced.", spell_id); + return false; + } + /* + Can not cast if discipline. + */ + if (IsAmnesiad() && IsDiscipline(spell_id)) { + MessageString(Chat::Red, MELEE_SILENCE); + LogSpells("Spell casting canceled [{}] : can not use discipline with amnesia.", spell_id); + return false; + } + /* + Cannot cast under divine aura, unless spell has 'cast_not_standing' flag. + */ + if (DivineAura() && !IgnoreCastingRestriction(spell_id)) { + LogSpells("Spell casting canceled [{}] : cannot cast while Divine Aura is in effect.", spell_id); + InterruptSpell(173, 0x121, false); //not sure we need this. + return false; + } + /* + Linked Reused Timers that are not ready + */ + if (IsClient() && spells[spell_id].timer_id > 0 && casting_spell_slot < CastingSlot::MaxGems) { + if (!CastToClient()->IsLinkedSpellReuseTimerReady(spells[spell_id].timer_id)) { + LogSpells("Spell casting canceled [{}] : linked reuse timer not ready.", spell_id); + return false; + } + } + /* + Spells that use caster_requirement_id field which requires specific conditions on caster to be met before casting. + */ + if (spells[spell_id].caster_requirement_id && !PassCastRestriction(spells[spell_id].caster_requirement_id)) { + SendCastRestrictionMessage(spells[spell_id].caster_requirement_id, false, IsDiscipline(spell_id)); + LogSpells("Spell casting canceled [{}] : caster requirement id [{}] not met.", spell_id, spells[spell_id].caster_requirement_id); + return false; + } + /* + Spells that use field can_cast_in_comabt or can_cast_out of combat restricting + caster to meet one of those conditions. If beneficial spell check casters state. + If detrimental check the targets state (done elsewhere in this function). + */ + if (!spells[spell_id].can_cast_in_combat && spells[spell_id].can_cast_out_of_combat) { + if (IsBeneficialSpell(spell_id)) { + if ((IsNPC() && IsEngaged()) || (IsClient() && CastToClient()->GetAggroCount())) { + if (IsDiscipline(spell_id)) { + MessageString(Chat::Red, NO_ABILITY_IN_COMBAT); + } + else { + MessageString(Chat::Red, NO_CAST_IN_COMBAT); + } + LogSpells("Spell casting canceled [{}] : can not use spell while in combat.", spell_id); + return false; + } + } + } + else if (spells[spell_id].can_cast_in_combat && !spells[spell_id].can_cast_out_of_combat) { + if (IsBeneficialSpell(spell_id)) { + if ((IsNPC() && !IsEngaged()) || (IsClient() && !CastToClient()->GetAggroCount())) { + if (IsDiscipline(spell_id)) { + MessageString(Chat::Red, NO_ABILITY_OUT_OF_COMBAT); + } + else { + MessageString(Chat::Red, NO_CAST_OUT_OF_COMBAT); + } + LogSpells("Spell casting canceled [{}] : can not use spell while out of combat.", spell_id); + return false; + } + } + } + /* + Focus version of Silence will prevent spell casting + */ + if (IsClient() && !IsDiscipline(spell_id)) { + int chance = CastToClient()->GetFocusEffect(focusFcMute, spell_id);//client only + if (chance && zone->random.Roll(chance)) { + MessageString(Chat::Red, SILENCED_STRING); + LogSpells("Spell casting canceled: can not cast spell when silenced from SPA 357 FcMute."); + return(false); + } + } + + return true; +} + +bool Mob::DoCastingChecksZoneRestrictions(bool check_on_casting, int32 spell_id) { + + /* + These are casting requirements determined by ZONE limiters that will cancel a spell before spell finishes casting or prevent spell from casting. + - levitate zone restriction (client blocks) [cancel before begin cast message] + - can not cast outdoor [cancels after spell finishes channeling] + + If the spell is a casted spell, check on CastSpell and ignore on SpellFinished. + If the spell is a initiated from SpellFinished, then check at start of SpellFinished. + */ + + bool ignore_if_npc_or_gm = false; if (!IsClient() || (IsClient() && CastToClient()->GetGM())) { - casting_spell_checks = true; - return true; + ignore_if_npc_or_gm = true; } - uint16 spell_id = casting_spell_id; - Mob *spell_target = entity_list.GetMob(casting_spell_targetid); + /* + Zone ares that prevent blocked spells from being cast. + If on cast iniated then check any mob casting, if on spellfinished only check if is from client. + */ + if ((check_on_casting && !ignore_if_npc_or_gm) || (!check_on_casting && IsClient())) { + if (zone->IsSpellBlocked(spell_id, glm::vec3(GetPosition()))) { + if (IsClient()) { + if (!CastToClient()->GetGM()) { + const char *msg = zone->GetSpellBlockedMessage(spell_id, glm::vec3(GetPosition())); + if (msg) { + Message(Chat::Red, msg); + return false; + } + else { + Message(Chat::Red, "You can't cast this spell here."); + return false; + } + LogSpells("Spell casting canceled [{}] : can not cast in this zone location blocked spell.", spell_id); + } + else { + LogSpells("GM Cast Blocked Spell: [{}] (ID [{}])", GetSpellName(spell_id), spell_id); + } + } + return false; + } + } + /* + Zones where you can not use levitate spells. + */ + if (!ignore_if_npc_or_gm && !zone->CanLevitate() && IsEffectInSpell(spell_id, SE_Levitate)) { //check on spellfinished. + Message(Chat::Red, "You have entered an area where levitation effects do not function."); + LogSpells("Spell casting canceled [{}] : can not cast levitation in this zone.", spell_id); + return false; + } + /* + Zones where you can not use detrimental spells. + */ + if (IsDetrimentalSpell(spell_id) && !zone->CanDoCombat()) { + Message(Chat::Red, "You cannot cast detrimental spells here."); + return false; + } - if (RuleB(Spells, BuffLevelRestrictions)) { + if (check_on_casting) { + /* + Zones where you can not cast out door only spells. This is only checked when casting is completed. + */ + if (!ignore_if_npc_or_gm && spells[spell_id].zone_type == 1 && !zone->CanCastOutdoor()) { + if (IsClient() && !CastToClient()->GetGM()) { + MessageString(Chat::Red, CAST_OUTDOORS); + LogSpells("Spell casting canceled [{}] : can not cast outdoors.", spell_id); + return false; + } + } + /* + Zones where you can not gate. + */ + if (IsClient() && + (zone->GetZoneID() == Zones::TUTORIAL || zone->GetZoneID() == Zones::LOAD) && + CastToClient()->Admin() < AccountStatus::QuestTroupe) { + if (IsEffectInSpell(spell_id, SE_Gate) || + IsEffectInSpell(spell_id, SE_Translocate) || + IsEffectInSpell(spell_id, SE_Teleport)) { + Message(Chat::White, "The Gods brought you here, only they can send you away."); + return false; + } + } + } + + return true; +} + + +bool Mob::DoCastingChecksOnTarget(bool check_on_casting, int32 spell_id, Mob *spell_target) { + + /* + These are casting requirements or TARGETS that will cancel a spell before spell finishes casting or prevent spell from casting. + - cast_restriction : checks specific requirements on target (cast initiates) + - target level restriction on buffs (cast initiates) + - can not cast life tap on self (client blocks) [cancel before begin cast message] + - can not cast sacrifice on self (cast initiates) [cancel before begin cast message] + - charm restrictions (cast initiates) [cancel before begin cast message] + - pcnpc_only_flag - (client blocks] [cancel before being cast message] + + If the spell is a casted spell, check on CastSpell and ignore on SpellFinished. + If the spell is a initiated from SpellFinished, then check at start of SpellFinished. + Always check again on SpellOnTarget to account for AE checks. + */ + + bool ignore_on_casting = false; + bool ignore_if_npc_or_gm = false; + if (!IsClient() || (IsClient() && CastToClient()->GetGM())) { + ignore_if_npc_or_gm = true; + } + + if (check_on_casting){ + + if (!spell_target) { + if (IsGroupSpell(spell_id) || + spells[spell_id].target_type == ST_AEClientV1 || + spells[spell_id].target_type == ST_AECaster || + spells[spell_id].target_type == ST_Ring || + spells[spell_id].target_type == ST_Beam) { + return true; + } + else if (spells[spell_id].target_type == ST_Self) { + spell_target = this; + } + } + else { + if (IsGroupSpell(spell_id) && spell_target != this) { + ignore_on_casting = true; + } + } + } + + //If we still do not have a target end. + if (!spell_target){ + return false; + } + + /* + Spells that use caster_restriction field which requires specific conditions on target to be met before casting. + [Insufficient mana first] + */ + if (spells[spell_id].cast_restriction && !spell_target->PassCastRestriction(spells[spell_id].cast_restriction)) { + SendCastRestrictionMessage(spells[spell_id].cast_restriction, true, IsDiscipline(spell_id)); + LogSpells("Spell casting canceled [{}] : target requirement id [{}] not met.", spell_id, spells[spell_id].caster_requirement_id); + return false; + } + /* + Spells that use field can_cast_in_comabt or can_cast_out of combat restricting + caster to meet one of those conditions. If beneficial spell check casters state (done else where in this function) + if detrimental check the targets state. + */ + if (!spells[spell_id].can_cast_in_combat && spells[spell_id].can_cast_out_of_combat) { + if (IsDetrimentalSpell(spell_id)) { + if (((spell_target->IsNPC() && spell_target->IsEngaged()) || + (spell_target->IsClient() && spell_target->CastToClient()->GetAggroCount()))) { + MessageString(Chat::Red, SPELL_NO_EFFECT); // Unsure correct string + LogSpells("Spell casting canceled [{}] : can not use spell while your target is in combat.", spell_id); + return false; + } + } + } + else if (spells[spell_id].can_cast_in_combat && !spells[spell_id].can_cast_out_of_combat) { + if (IsDetrimentalSpell(spell_id)) { + if (((spell_target->IsNPC() && !spell_target->IsEngaged()) || + (spell_target->IsClient() && !spell_target->CastToClient()->GetAggroCount()))) { + MessageString(Chat::Red, SPELL_NO_EFFECT); // Unsure correct string + LogSpells("Spell casting canceled [{}] : can not use spell while your target is out of combat.", spell_id); + return false; + } + } + } + /* + Prevent buffs from being cast on targets who don't meet level restriction + */ + if (!ignore_if_npc_or_gm && RuleB(Spells, BuffLevelRestrictions)) { // casting_spell_targetid is guaranteed to be what we went, check for ST_Self for now should work though - if (spell_target && spells[spell_id].targettype != ST_Self && !spell_target->CheckSpellLevelRestriction(spell_id)) { + if (spells[spell_id].target_type != ST_Self && !spell_target->CheckSpellLevelRestriction(spell_id)) { LogSpells("Spell [{}] failed: recipient did not meet the level restrictions", spell_id); - if (!IsBardSong(spell_id)) + if (!IsBardSong(spell_id)) { MessageString(Chat::SpellFailure, SPELL_TOO_POWERFUL); + } return false; } } - - if (spells[spell_id].zonetype == 1 && !zone->CanCastOutdoor()) { - MessageString(Chat::Red, CAST_OUTDOORS); + /* + Prevents buff from being cast based on tareget ing PC OR NPC (1 = PCs, 2 = NPCs) + These target types skip pcnpc only check (according to dev quotes) + */ + if (!ignore_on_casting) { + if (spells[spell_id].pcnpc_only_flag && spells[spell_id].target_type != ST_AETargetHateList && spells[spell_id].target_type != ST_HateList) { + if (spells[spell_id].pcnpc_only_flag == 1 && !spell_target->IsClient() && !spell_target->IsMerc() && !spell_target->IsBot()) { + if (check_on_casting) { + Message(Chat::SpellFailure, "This spell only works on other PCs"); + } + return false; + } + else if (spells[spell_id].pcnpc_only_flag == 2 && (spell_target->IsClient() || spell_target->IsMerc() || spell_target->IsBot())) { + if (check_on_casting) { + Message(Chat::SpellFailure, "This spell only works on NPCs."); + } + return false; + } + } + } + /* + Cannot cast life tap on self + */ + if (this == spell_target && IsLifetapSpell(spell_id)) { + LogSpells("You cannot lifetap yourself"); + MessageString(Chat::SpellFailure, CANT_DRAIN_SELF); return false; } - - if (IsEffectInSpell(spell_id, SE_Levitate) && !zone->CanLevitate()) { - Message(Chat::Red, "You can't levitate in this zone."); + /* + Cannot cast sacrifice on self + */ + if (this == spell_target && IsSacrificeSpell(spell_id)) { + LogSpells("You cannot sacrifice yourself"); + MessageString(Chat::SpellFailure, CANNOT_SAC_SELF); return false; } + /* + Max level of target for harmony to take hold + */ + if (IsClient() && IsHarmonySpell(spell_id) && !HarmonySpellLevelCheck(spell_id, spell_target)) { + MessageString(Chat::SpellFailure, SPELL_NO_EFFECT); + LogSpells("Spell casting canceled [{}] : can not use harmony on this target.", spell_id); + return false; + } + /* + Various charm related target restrictions + */ + if (IsEffectInSpell(spell_id, SE_Charm) && !PassCharmTargetRestriction(spell_target)) { + LogSpells("Spell casting canceled [{}] : can not use charm on this target.", spell_id); + return false; + } + /* + Requires target to be in same group or same raid in order to apply invisible. + */ + if (check_on_casting && RuleB(Spells, InvisRequiresGroup) && IsInvisSpell(spell_id)) { + if (IsClient() && spell_target && spell_target->IsClient()) { + if (spell_target && spell_target->GetID() != GetID()) { + bool cast_failed = true; + if (spell_target->IsGrouped()) { + Group *target_group = spell_target->GetGroup(); + Group *my_group = GetGroup(); + if (target_group && + my_group && + (target_group->GetID() == my_group->GetID())) { + cast_failed = false; + } + } + else if (spell_target->IsRaidGrouped()) { + Raid *target_raid = spell_target->GetRaid(); + Raid *my_raid = GetRaid(); + if (target_raid && + my_raid && + (target_raid->GetGroup(spell_target->CastToClient()) == my_raid->GetGroup(this->CastToClient()))) { + cast_failed = false; + } + } - if(zone->IsSpellBlocked(spell_id, glm::vec3(GetPosition()))) { - const char *msg = zone->GetSpellBlockedMessage(spell_id, glm::vec3(GetPosition())); - if (msg) { - Message(Chat::Red, msg); - return false; - } else { - Message(Chat::Red, "You can't cast this spell here."); - return false; + if (cast_failed) { + MessageString(Chat::Red, TARGET_GROUP_MEMBER); + return false; + } + } } } - if (IsClient() && spells[spell_id].EndurTimerIndex > 0 && casting_spell_slot < CastingSlot::MaxGems) - if (!CastToClient()->IsLinkedSpellReuseTimerReady(spells[spell_id].EndurTimerIndex)) - return false; - - casting_spell_checks = true; return true; } @@ -786,7 +1040,7 @@ bool Client::CheckFizzle(uint16 spell_id) // > 0 --> skill is lower, higher chance of fizzle // < 0 --> skill is better, lower chance of fizzle // the max that diff can be is +- 235 - float diff = par_skill + static_cast(spells[spell_id].basediff) - act_skill; + float diff = par_skill + static_cast(spells[spell_id].base_difficulty) - act_skill; // if you have high int/wis you fizzle less, you fizzle more if you are stupid if(GetClass() == BARD) @@ -833,9 +1087,20 @@ void Mob::ZeroCastingVars() casting_spell_resist_adjust = 0; casting_spell_checks = false; casting_spell_aa_id = 0; + casting_spell_recast_adjust = 0; delaytimer = false; } + +//This will cause server to stop trying to pulse a bard song. Does not stop song clientside. +void Mob::ZeroBardPulseVars() +{ + bardsong = 0; + bardsong_target_id = 0; + bardsong_slot = CastingSlot::Gem1; + bardsong_timer.Disable(); +} + void Mob::InterruptSpell(uint16 spellid) { if (spellid == SPELL_UNKNOWN) @@ -875,8 +1140,9 @@ void Mob::InterruptSpell(uint16 message, uint16 color, uint16 spellid) if(!spellid) return; - if (bardsong || IsBardSong(casting_spell_id)) - _StopSong(); + if (bardsong || IsBardSong(casting_spell_id)) { + ZeroBardPulseVars(); + } if(bard_song_mode) { return; @@ -947,17 +1213,40 @@ void Mob::StopCasting() c->ResetAlternateAdvancementTimer(casting_spell_aa_id); } + int casting_slot = -1; + if (casting_spell_slot < CastingSlot::MaxGems) { + casting_slot = static_cast(casting_spell_slot); + } + auto outapp = new EQApplicationPacket(OP_ManaChange, sizeof(ManaChange_Struct)); auto mc = (ManaChange_Struct *)outapp->pBuffer; mc->new_mana = GetMana(); mc->stamina = GetEndurance(); mc->spell_id = casting_spell_id; mc->keepcasting = 0; + mc->slot = casting_slot; c->FastQueuePacket(&outapp); } ZeroCastingVars(); } +void Mob::StopCastSpell(int32 spell_id, bool send_spellbar_enable) +{ + /* + This is used when spells fail at CastSpell or when CastSpell is bypassed and spell is launched initially from SpellFinished. + send_spellbar_enabled is false when the following + -AA that fail at CastSpell because they never get timer set. + -Instant cast items that fail at CastSpell because they never get timer set. + */ + if (casting_spell_id && IsNPC()) { + CastToNPC()->AI_Event_SpellCastFinished(false, static_cast(casting_spell_slot)); + } + + if (send_spellbar_enable) { + SendSpellBarEnable(spell_id); + } +} + // this is called after the timer is up and the spell is finished // casting. everything goes through here, including items with zero cast time // only to be used from SpellProcess @@ -967,6 +1256,13 @@ void Mob::StopCasting() void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slot, uint16 mana_used, uint32 inventory_slot, int16 resist_adjust) { + if (!IsValidSpell(spell_id)) + { + LogSpells("Casting of [{}] canceled: invalid spell id", spell_id); + InterruptSpell(); + return; + } + bool IsFromItem = false; EQ::ItemInstance *item = nullptr; @@ -979,29 +1275,23 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo return; } } - + /* + Reinforcement only. Checks Item and Augment click recasts. + Titanium client will prevent item recast on its own. This is only used to enforce. Titanium items are cast from Handle_OP_CastSpell. + SOF+ client does not prevent item recast on its own. We enforce this in Handle_OP_ItemVerifyRequest where items are cast from. + */ if(IsClient() && (slot == CastingSlot::Item || slot == CastingSlot::PotionBelt)) { IsFromItem = true; - item = CastToClient()->GetInv().GetItem(inventory_slot); - if(item && item->GetItem()->RecastDelay > 0) - { - if(!CastToClient()->GetPTimers().Expired(&database, (pTimerItemStart + item->GetItem()->RecastType), false)) { - MessageString(Chat::Red, SPELL_RECAST); - LogSpells("Casting of [{}] canceled: item spell reuse timer not expired", spell_id); - StopCasting(); - return; - } + item = CastToClient()->GetInv().GetItem(inventory_slot); //checked for in reagents and charges. + if (CastToClient()->HasItemRecastTimer(spell_id, inventory_slot)) { + MessageString(Chat::Red, SPELL_RECAST); + LogSpells("Casting of [{}] canceled: item or augment spell reuse timer not expired", spell_id); + StopCasting(); + return; } } - if(!IsValidSpell(spell_id)) - { - LogSpells("Casting of [{}] canceled: invalid spell id", spell_id); - InterruptSpell(); - return; - } - // prevent rapid recast - this can happen if somehow the spell gems // become desynced and the player casts again. if(IsClient()) @@ -1031,25 +1321,27 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo // a spell bar slot if(GetClass() == BARD) // bard's can move when casting any spell... { - if (IsBardSong(spell_id)) { - if(spells[spell_id].buffduration == 0xFFFF) { - LogSpells("Bard song [{}] not applying bard logic because duration. dur=[{}], recast=[{}]", spells[spell_id].buffduration); - } else { - // So long recast bard songs need special bard logic, although the effects don't repulse like other songs - // This is basically a hack to get that effect - // You can hold down the long recast spells, but you only get the effects once - // TODO fuck bards. - if (spells[spell_id].recast_time == 0) { + if (IsBardSong(spell_id) && slot < CastingSlot::MaxGems) { + if (spells[spell_id].buff_duration == 0xFFFF) { + LogSpells("Bard song [{}] not applying bard logic because duration. dur=[{}], recast=[{}]", spells[spell_id].buff_duration); + } + else { + if (IsPulsingBardSong(spell_id)) { bardsong = spell_id; bardsong_slot = slot; - //NOTE: theres a lot more target types than this to think about... - if (spell_target == nullptr || (spells[spell_id].targettype != ST_Target && spells[spell_id].targettype != ST_AETarget)) - bardsong_target_id = GetID(); - else + + if (spell_target) { bardsong_target_id = spell_target->GetID(); + } + else if (spells[spell_id].target_type != ST_Target && spells[spell_id].target_type != ST_AETarget) { + bardsong_target_id = GetID(); //This is a failsafe, you should always have a spell_target unless that target died/zoned. + } + else { + InterruptSpell(); + } bardsong_timer.Start(6000); } - LogSpells("Bard song [{}] started: slot [{}], target id [{}]", bardsong, (int) bardsong_slot, bardsong_target_id); + LogSpells("Bard song [{}] started: slot [{}], target id [{}]", bardsong, (int)bardsong_slot, bardsong_target_id); bard_song_mode = true; } } @@ -1167,8 +1459,8 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo int component, component_count, inv_slot_id; bool missingreags = false; for(int t_count = 0; t_count < 4; t_count++) { - component = spells[spell_id].components[t_count]; - component_count = spells[spell_id].component_counts[t_count]; + component = spells[spell_id].component[t_count]; + component_count = spells[spell_id].component_count[t_count]; if (component == -1) continue; @@ -1176,14 +1468,14 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo // bard components are requirements for a certain instrument type, not a specific item if(bard_song_mode) { bool HasInstrument = true; - int InstComponent = spells[spell_id].NoexpendReagent[0]; + int InstComponent = spells[spell_id].no_expend_reagent[0]; switch (InstComponent) { case -1: continue; // no instrument required, go to next component // percussion songs (13000 = hand drum) - case 13000: + case INSTRUMENT_HAND_DRUM: if(itembonuses.percussionMod == 0) { // check for the appropriate instrument type HasInstrument = false; c->MessageString(Chat::Red, SONG_NEEDS_DRUM); // send an error message if missing @@ -1191,7 +1483,7 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo break; // wind songs (13001 = wooden flute) - case 13001: + case INSTRUMENT_WOODEN_FLUTE: if(itembonuses.windMod == 0) { HasInstrument = false; c->MessageString(Chat::Red, SONG_NEEDS_WIND); @@ -1199,7 +1491,7 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo break; // string songs (13011 = lute) - case 13011: + case INSTRUMENT_LUTE: if(itembonuses.stringedMod == 0) { HasInstrument = false; c->MessageString(Chat::Red, SONG_NEEDS_STRINGS); @@ -1207,7 +1499,7 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo break; // brass songs (13012 = horn) - case 13012: + case INSTRUMENT_HORN: if(itembonuses.brassMod == 0) { HasInstrument = false; c->MessageString(Chat::Red, SONG_NEEDS_BRASS); @@ -1233,7 +1525,7 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo else { if (!RuleB(Character, PetsUseReagents) && (IsEffectInSpell(spell_id, SE_SummonPet) || IsEffectInSpell(spell_id, SE_NecPet))) { //bypass reagent cost - } + } else if(c->GetInv().HasItem(component, component_count, invWhereWorn|invWherePersonal) == -1) // item not found { if (!missingreags) @@ -1271,11 +1563,11 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo { int noexpend; for(int t_count = 0; t_count < 4; t_count++) { - component = spells[spell_id].components[t_count]; - noexpend = spells[spell_id].NoexpendReagent[t_count]; + component = spells[spell_id].component[t_count]; + noexpend = spells[spell_id].no_expend_reagent[t_count]; if (component == -1 || noexpend == component) continue; - component_count = spells[spell_id].component_counts[t_count]; + component_count = spells[spell_id].component_count[t_count]; LogSpells("Spell [{}]: Consuming [{}] of spell component item id [{}]", spell_id, component_count, component); // Components found, Deleting // now we go looking for and deleting the items one by one @@ -1305,79 +1597,11 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo if(IsClient() && (slot == CastingSlot::Item || slot == CastingSlot::PotionBelt) && inventory_slot != 0xFFFFFFFF) // 10 is an item { - bool fromaug = false; - EQ::ItemData* augitem = nullptr; - uint32 recastdelay = 0; - uint32 recasttype = 0; - - while (true) { - if (item == nullptr) - break; - - for (int r = EQ::invaug::SOCKET_BEGIN; r <= EQ::invaug::SOCKET_END; r++) { - const EQ::ItemInstance* aug_i = item->GetAugment(r); - - if (!aug_i) - continue; - const EQ::ItemData* aug = aug_i->GetItem(); - if (!aug) - continue; - - if (aug->Click.Effect == spell_id) - { - recastdelay = aug_i->GetItem()->RecastDelay; - recasttype = aug_i->GetItem()->RecastType; - fromaug = true; - break; - } - } - - break; - } - - //Test the aug recast delay - if(IsClient() && fromaug && recastdelay > 0) - { - if(!CastToClient()->GetPTimers().Expired(&database, (pTimerItemStart + recasttype), false)) { - MessageString(Chat::Red, SPELL_RECAST); - LogSpells("Casting of [{}] canceled: item spell reuse timer not expired", spell_id); - StopCasting(); - return; - } - else - { - //Can we start the timer here? I don't see why not. - CastToClient()->GetPTimers().Start((pTimerItemStart + recasttype), recastdelay); - database.UpdateItemRecastTimestamps(CastToClient()->CharacterID(), recasttype, - CastToClient()->GetPTimers().Get(pTimerItemStart + recasttype)->GetReadyTimestamp()); - } - } - - if (item && item->IsClassCommon() && (item->GetItem()->Click.Effect == spell_id) && item->GetCharges() || fromaug) - { - //const ItemData* item = item->GetItem(); - int16 charges = item->GetItem()->MaxCharges; - - if(fromaug) { charges = -1; } //Don't destroy the parent item - - if(charges > -1) { // charged item, expend a charge - LogSpells("Spell [{}]: Consuming a charge from item [{}] ([{}]) which had [{}]/[{}] charges", spell_id, item->GetItem()->Name, item->GetItem()->ID, item->GetCharges(), item->GetItem()->MaxCharges); - DeleteChargeFromSlot = inventory_slot; - } else { - LogSpells("Spell [{}]: Cast from unlimited charge item [{}] ([{}]) ([{}] charges)", spell_id, item->GetItem()->Name, item->GetItem()->ID, item->GetItem()->MaxCharges); - } - } - else - { - LogSpells("Item used to cast spell [{}] was missing from inventory slot [{}] after casting!", spell_id, inventory_slot); - Message(Chat::Red, "Casting Error: Active casting item not found in inventory slot %i", inventory_slot); - InterruptSpell(); - return; - } + DeleteChargeFromSlot = GetItemSlotToConsumeCharge(spell_id, inventory_slot); } // we're done casting, now try to apply the spell - if( !SpellFinished(spell_id, spell_target, slot, mana_used, inventory_slot, resist_adjust) ) + if(!SpellFinished(spell_id, spell_target, slot, mana_used, inventory_slot, resist_adjust, false,-1, 0xFFFFFFFF, 0, true)) { LogSpells("Casting of [{}] canceled: SpellFinished returned false", spell_id); // most of the cases we return false have a message already or are logic errors that shouldn't happen @@ -1390,26 +1614,24 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo CheckNumHitsRemaining(NumHit::MatchingSpells); TrySympatheticProc(target, spell_id); } - + TryTwincast(this, target, spell_id); - TryTriggerOnCast(spell_id, 0); + TryTriggerOnCastFocusEffect(focusTriggerOnCast, spell_id); - if(DeleteChargeFromSlot >= 0) + if (DeleteChargeFromSlot >= 0) { CastToClient()->DeleteItemInInventory(DeleteChargeFromSlot, 1, true); + } // // at this point the spell has successfully been cast // + std::string export_string = fmt::format("{}", spell_id); if(IsClient()) { - char temp[64]; - sprintf(temp, "%d", spell_id); - parse->EventPlayer(EVENT_CAST, CastToClient(), temp, 0); + parse->EventPlayer(EVENT_CAST, CastToClient(), export_string, 0); } else if(IsNPC()) { - char temp[64]; - sprintf(temp, "%d", spell_id); - parse->EventNPC(EVENT_CAST, CastToNPC(), nullptr, temp, 0); + parse->EventNPC(EVENT_CAST, CastToNPC(), nullptr, export_string, 0); } if(bard_song_mode) @@ -1417,10 +1639,19 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo if(IsClient()) { Client *c = CastToClient(); - c->CheckSongSkillIncrease(spell_id); - if (spells[spell_id].EndurTimerIndex > 0 && slot < CastingSlot::MaxGems) - c->SetLinkedSpellReuseTimer(spells[spell_id].EndurTimerIndex, spells[spell_id].recast_time / 1000); - c->MemorizeSpell(static_cast(slot), spell_id, memSpellSpellbar); + if((IsFromItem && RuleB(Character, SkillUpFromItems)) || !IsFromItem) { + c->CheckSongSkillIncrease(spell_id); + } + if (spells[spell_id].timer_id > 0 && slot < CastingSlot::MaxGems) { + c->SetLinkedSpellReuseTimer(spells[spell_id].timer_id, (spells[spell_id].recast_time / 1000) - (casting_spell_recast_adjust / 1000)); + } + if (RuleB(Spells, EnableBardMelody)) { + c->MemorizeSpell(static_cast(slot), spell_id, memSpellSpellbar, casting_spell_recast_adjust); + } + + if (!IsFromItem) { + c->CheckSongSkillIncrease(spell_id); + } } LogSpells("Bard song [{}] should be started", spell_id); } @@ -1432,15 +1663,17 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo SendSpellBarEnable(spell_id); // this causes the delayed refresh of the spell bar gems - if (spells[spell_id].EndurTimerIndex > 0 && slot < CastingSlot::MaxGems) - c->SetLinkedSpellReuseTimer(spells[spell_id].EndurTimerIndex, spells[spell_id].recast_time / 1000); - c->MemorizeSpell(static_cast(slot), spell_id, memSpellSpellbar); + if (spells[spell_id].timer_id > 0 && slot < CastingSlot::MaxGems) { + c->SetLinkedSpellReuseTimer(spells[spell_id].timer_id, (spells[spell_id].recast_time / 1000) - (casting_spell_recast_adjust / 1000)); + } + + c->MemorizeSpell(static_cast(slot), spell_id, memSpellSpellbar, casting_spell_recast_adjust); // this tells the client that casting may happen again SetMana(GetMana()); // skills - if (EQ::skills::IsCastingSkill(spells[spell_id].skill)) { + if (EQ::skills::IsCastingSkill(spells[spell_id].skill) && ((IsFromItem && RuleB(Character, SkillUpFromItems)) || !IsFromItem)) { c->CheckIncreaseSkill(spells[spell_id].skill, nullptr); // increased chance of gaining channel skill if you regained concentration @@ -1497,7 +1730,7 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce // and that causes the spell to be executed differently bodyType target_bt = BT_Humanoid; - SpellTargetType targetType = spells[spell_id].targettype; + SpellTargetType targetType = spells[spell_id].target_type; bodyType mob_body = spell_target ? spell_target->GetBodyType() : BT_Humanoid; if(IsPlayerIllusionSpell(spell_id) @@ -1516,57 +1749,6 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce if (isproc && IsNPC() && CastToNPC()->GetInnateProcSpellID() == spell_id) targetType = ST_Target; - if (spell_target && !spell_target->PassCastRestriction(true, spells[spell_id].CastRestriction)){ - MessageString(Chat::Red,SPELL_NEED_TAR); - return false; - } - - //Must be out of combat. (If Beneficial checks casters combat state, Deterimental checks targets) - if (!spells[spell_id].InCombat && spells[spell_id].OutofCombat) { - if (IsDetrimentalSpell(spell_id)) { - if (spell_target && - ((spell_target->IsNPC() && spell_target->IsEngaged()) || - (spell_target->IsClient() && spell_target->CastToClient()->GetAggroCount()))) { - MessageString(Chat::Red, SPELL_NO_EFFECT); // Unsure correct string - return false; - } - } - - else if (IsBeneficialSpell(spell_id)) { - if ((IsNPC() && IsEngaged()) || (IsClient() && CastToClient()->GetAggroCount())) { - if (IsDiscipline(spell_id)) - MessageString(Chat::Red, NO_ABILITY_IN_COMBAT); - else - MessageString(Chat::Red, NO_CAST_IN_COMBAT); - - return false; - } - } - } - - // Must be in combat. (If Beneficial checks casters combat state, Deterimental checks targets) - else if (spells[spell_id].InCombat && !spells[spell_id].OutofCombat) { - if (IsDetrimentalSpell(spell_id)) { - if (spell_target && - ((spell_target->IsNPC() && !spell_target->IsEngaged()) || - (spell_target->IsClient() && !spell_target->CastToClient()->GetAggroCount()))) { - MessageString(Chat::Red, SPELL_NO_EFFECT); // Unsure correct string - return false; - } - } - - else if (IsBeneficialSpell(spell_id)) { - if ((IsNPC() && !IsEngaged()) || (IsClient() && !CastToClient()->GetAggroCount())) { - if (IsDiscipline(spell_id)) - MessageString(Chat::Red, NO_ABILITY_OUT_OF_COMBAT); - else - MessageString(Chat::Red, NO_CAST_OUT_OF_COMBAT); - - return false; - } - } - } - switch (targetType) { // single target spells @@ -1579,8 +1761,12 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce case ST_TargetOptional: { - if(!spell_target) - spell_target = this; + if (!spell_target) + { + LogSpells("Spell [{}] canceled: invalid target (normal)", spell_id); + MessageString(Chat::Red, SPELL_NEED_TAR); + return false; // can't cast these unless we have a target + } CastAction = SingleTarget; break; } @@ -1814,7 +2000,7 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce spell_target = this; } - if (spell_target && spell_target->IsPet() && spells[spell_id].targettype == ST_GroupNoPets){ + if (spell_target && spell_target->IsPet() && spells[spell_id].target_type == ST_GroupNoPets){ MessageString(Chat::Red,NO_CAST_ON_PET); return false; } @@ -2021,8 +2207,8 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce default: { - LogSpells("I dont know Target Type: [{}] Spell: ([{}]) [{}]", spells[spell_id].targettype, spell_id, spells[spell_id].name); - Message(0, "I dont know Target Type: %d Spell: (%d) %s", spells[spell_id].targettype, spell_id, spells[spell_id].name); + LogSpells("I dont know Target Type: [{}] Spell: ([{}]) [{}]", spells[spell_id].target_type, spell_id, spells[spell_id].name); + Message(0, "I dont know Target Type: %d Spell: (%d) %s", spells[spell_id].target_type, spell_id, spells[spell_id].name); CastAction = CastActUnknown; break; } @@ -2034,77 +2220,60 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce // we can't interrupt in this, or anything called from this! // if you need to abort the casting, return false bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, uint16 mana_used, - uint32 inventory_slot, int16 resist_adjust, bool isproc, int level_override) + uint32 inventory_slot, int16 resist_adjust, bool isproc, int level_override, + uint32 timer, uint32 timer_duration, bool from_casted_spell, uint32 aa_id) { - //EQApplicationPacket *outapp = nullptr; Mob *ae_center = nullptr; if(!IsValidSpell(spell_id)) return false; - if( spells[spell_id].zonetype == 1 && !zone->CanCastOutdoor()){ - if(IsClient()){ - if(!CastToClient()->GetGM()){ - MessageString(Chat::Red, CAST_OUTDOORS); - return false; - } - } - } - - if(IsEffectInSpell(spell_id, SE_Levitate) && !zone->CanLevitate()){ - if(IsClient()){ - if(!CastToClient()->GetGM()){ - Message(Chat::Red, "You can't levitate in this zone."); - return false; - } - } - } - - if(IsClient() && !CastToClient()->GetGM()){ - - if(zone->IsSpellBlocked(spell_id, glm::vec3(GetPosition()))){ - const char *msg = zone->GetSpellBlockedMessage(spell_id, glm::vec3(GetPosition())); - if(msg){ - Message(Chat::Red, msg); - return false; - } - else{ - Message(Chat::Red, "You can't cast this spell here."); - return false; - } + //Death Touch targets the pet owner instead of the pet when said pet is tanking. + if ((RuleB(Spells, CazicTouchTargetsPetOwner) && spell_target && spell_target->HasOwner()) && spell_id == SPELL_CAZIC_TOUCH || spell_id == SPELL_TOUCH_OF_VINITRAS) { + Mob* owner = spell_target->GetOwner(); + if (owner) { + spell_target = owner; } } - if (IsClient() && CastToClient()->GetGM()){ - if (zone->IsSpellBlocked(spell_id, glm::vec3(GetPosition()))){ - LogSpells("GM Cast Blocked Spell: [{}] (ID [{}])", GetSpellName(spell_id), spell_id); + //Guard Assist Code + if (RuleB(Character, PVPEnableGuardFactionAssist) && spell_target && IsDetrimentalSpell(spell_id) && spell_target != this) { + if (IsClient() && spell_target->IsClient()|| (HasOwner() && GetOwner()->IsClient() && spell_target->IsClient())) { + auto& mob_list = entity_list.GetCloseMobList(spell_target); + for (auto& e : mob_list) { + auto mob = e.second; + if (mob->IsNPC() && mob->CastToNPC()->IsGuard()) { + float distance = Distance(spell_target->GetPosition(), mob->GetPosition()); + if ((mob->CheckLosFN(spell_target) || mob->CheckLosFN(this)) && distance <= 70) { + auto petorowner = GetOwnerOrSelf(); + if (spell_target->GetReverseFactionCon(mob) <= petorowner->GetReverseFactionCon(mob)) { + mob->AddToHateList(this); + } + } + } + } } } - if - ( - this->IsClient() && - (zone->GetZoneID() == 183 || zone->GetZoneID() == 184) && // load - CastToClient()->Admin() < 80 - ) - { - if - ( - IsEffectInSpell(spell_id, SE_Gate) || - IsEffectInSpell(spell_id, SE_Translocate) || - IsEffectInSpell(spell_id, SE_Teleport) - ) - { - Message(0, "The Gods brought you here, only they can send you away."); + //If spell was casted then we already checked these so skip, otherwise check here if being called directly from spell finished. + if (!from_casted_spell){ + if (!DoCastingChecksZoneRestrictions(true, spell_id)) { + LogSpells("Spell [{}]: Zone restriction failure.", spell_id); + return false; + } + if (!DoCastingChecksOnTarget(true, spell_id, spell_target)) { + LogSpells("Spell [{}]: Casting checks on Target failure.", spell_id); return false; } } //determine the type of spell target we have CastAction_type CastAction; - if(!DetermineSpellTargets(spell_id, spell_target, ae_center, CastAction, slot, isproc)) + if (!DetermineSpellTargets(spell_id, spell_target, ae_center, CastAction, slot, isproc)) { + LogSpells("Spell [{}]: Determine spell targets failure.", spell_id); return(false); + } LogSpells("Spell [{}]: target type [{}], target [{}], AE center [{}]", spell_id, CastAction, spell_target?spell_target->GetName():"NONE", ae_center?ae_center->GetName():"NONE"); @@ -2115,7 +2284,7 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, ui if(IsAEDurationSpell(spell_id)) { // the spells are AE target, but we aim them on a beacon Mob *beacon_loc = spell_target ? spell_target : this; - auto beacon = new Beacon(beacon_loc, spells[spell_id].AEDuration); + auto beacon = new Beacon(beacon_loc, spells[spell_id].aoe_duration); entity_list.AddBeacon(beacon); LogSpells("Spell [{}]: AE duration beacon created, entity id [{}]", spell_id, beacon->GetName()); spell_target = nullptr; @@ -2124,7 +2293,7 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, ui } // check line of sight to target if it's a detrimental spell - if(!spells[spell_id].npc_no_los && spell_target && IsDetrimentalSpell(spell_id) && !CheckLosFN(spell_target) && !IsHarmonySpell(spell_id) && spells[spell_id].targettype != ST_TargetOptional) + if(!spells[spell_id].npc_no_los && spell_target && IsDetrimentalSpell(spell_id) && !CheckLosFN(spell_target) && !IsHarmonySpell(spell_id) && spells[spell_id].target_type != ST_TargetOptional) { LogSpells("Spell [{}]: cannot see target [{}]", spell_id, spell_target->GetName()); MessageString(Chat::Red,CANT_SEE_TARGET); @@ -2142,14 +2311,13 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, ui //range check our target, if we have one and it is not us float range = spells[spell_id].range + GetRangeDistTargetSizeMod(spell_target); if(IsClient() && CastToClient()->TGB() && IsTGBCompatibleSpell(spell_id) && IsGroupSpell(spell_id)) - range = spells[spell_id].aoerange; + range = spells[spell_id].aoe_range; range = GetActSpellRange(spell_id, range); - if(IsPlayerIllusionSpell(spell_id) - && IsClient() - && (HasProjectIllusion())){ + if(IsClient() && IsPlayerIllusionSpell(spell_id) && (HasProjectIllusion())){ range = 100; } + if(spell_target != nullptr && spell_target != this) { //casting a spell on somebody but ourself, make sure they are in range float dist2 = DistanceSquared(m_Position, spell_target->GetPosition()); @@ -2218,14 +2386,14 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, ui return(false); } if (isproc) { - SpellOnTarget(spell_id, spell_target, false, true, resist_adjust, true, level_override); + SpellOnTarget(spell_id, spell_target, 0, true, resist_adjust, true, level_override); } else { - if (spells[spell_id].targettype == ST_TargetOptional){ + if (spells[spell_id].target_type == ST_TargetOptional){ if (!TrySpellProjectile(spell_target, spell_id)) return false; } - else if(!SpellOnTarget(spell_id, spell_target, false, true, resist_adjust, false, level_override)) { + else if(!SpellOnTarget(spell_id, spell_target, 0, true, resist_adjust, false, level_override)) { if(IsBuffSpell(spell_id) && IsBeneficialSpell(spell_id)) { // Prevent mana usage/timers being set for beneficial buffs if(casting_spell_aa_id) @@ -2250,15 +2418,15 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, ui case AECaster: case AETarget: { -#ifdef BOTS - if(IsBot()) { - bool StopLogic = false; - if(!this->CastToBot()->DoFinishedSpellAETarget(spell_id, spell_target, slot, StopLogic)) - return false; - if(StopLogic) - break; - } -#endif //BOTS +//#ifdef BOTS +// if(IsBot()) { +// bool StopLogic = false; +// if(!this->CastToBot()->DoFinishedSpellAETarget(spell_id, spell_target, slot, StopLogic)) +// return false; +// if(StopLogic) +// break; +// } +//#endif //BOTS // we can't cast an AE spell without something to center it on assert(ae_center != nullptr); @@ -2273,10 +2441,10 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, ui // NPCs should never be affected by an AE they cast. PB AEs shouldn't affect caster either // I don't think any other cases that get here matter - bool affect_caster = !IsNPC() && spells[spell_id].targettype != ST_AECaster; + bool affect_caster = !IsNPC() && spells[spell_id].target_type != ST_AECaster; - if (spells[spell_id].targettype == ST_AETargetHateList) - hate_list.SpellCast(this, spell_id, spells[spell_id].aoerange, ae_center); + if (spells[spell_id].target_type == ST_AETargetHateList) + hate_list.SpellCast(this, spell_id, spells[spell_id].aoe_range, ae_center); else entity_list.AESpell(this, ae_center, spell_id, affect_caster, resist_adjust); } @@ -2345,7 +2513,7 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, ui SpellOnTarget(spell_id, this); #ifdef GROUP_BUFF_PETS //pet too - if (spells[spell_id].targettype != ST_GroupNoPets && GetPet() && HasPetAffinity() && !GetPet()->IsCharmed()) + if (spells[spell_id].target_type != ST_GroupNoPets && GetPet() && HasPetAffinity() && !GetPet()->IsCharmed()) SpellOnTarget(spell_id, GetPet()); #endif } @@ -2353,7 +2521,7 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, ui SpellOnTarget(spell_id, spell_target); #ifdef GROUP_BUFF_PETS //pet too - if (spells[spell_id].targettype != ST_GroupNoPets && spell_target->GetPet() && spell_target->HasPetAffinity() && !spell_target->GetPet()->IsCharmed()) + if (spells[spell_id].target_type != ST_GroupNoPets && spell_target->GetPet() && spell_target->HasPetAffinity() && !spell_target->GetPet()->IsCharmed()) SpellOnTarget(spell_id, spell_target->GetPet()); #endif } @@ -2365,7 +2533,7 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, ui { if(!IsClient()) { - hate_list.SpellCast(this, spell_id, spells[spell_id].range > spells[spell_id].aoerange ? spells[spell_id].range : spells[spell_id].aoerange); + hate_list.SpellCast(this, spell_id, spells[spell_id].range > spells[spell_id].aoe_range ? spells[spell_id].range : spells[spell_id].aoe_range); } break; } @@ -2411,39 +2579,58 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, ui LogSpells("Spell [{}]: consuming [{}] mana", spell_id, mana_used); if (!DoHPToManaCovert(mana_used)) { SetMana(GetMana() - mana_used); - TryTriggerOnValueAmount(false, true); + TryTriggerOnCastRequirement(); } } // one may want to check if this is a disc or not, but we actually don't, there are non disc stuff that have end cost // lets not consume end for custom items that have disc procs. // One might also want to filter out USE_ITEM_SPELL_SLOT, but DISCIPLINE_SPELL_SLOT are both #defined to the same thing ... - if (spells[spell_id].EndurCost && !isproc) { - auto end_cost = spells[spell_id].EndurCost; + if (spells[spell_id].endurance_cost && !isproc) { + auto end_cost = spells[spell_id].endurance_cost; if (mgb) end_cost *= 2; SetEndurance(GetEndurance() - EQ::ClampUpper(end_cost, GetEndurance())); - TryTriggerOnValueAmount(false, false, true); + TryTriggerOnCastRequirement(); } - if (mgb) + if (mgb) { SetMGB(false); - - //set our reuse timer on long ass reuse_time spells... + } + /* + Set Recast Timer on spells. + */ if(IsClient() && !isproc) { - if(casting_spell_aa_id) { - AA::Rank *rank = zone->GetAlternateAdvancementRank(casting_spell_aa_id); - - if(rank && rank->base_ability) { - ExpendAlternateAdvancementCharge(rank->base_ability->id); + if (slot == CastingSlot::AltAbility) { + if (!aa_id) { + aa_id = casting_spell_aa_id; + } + if (aa_id) { + AA::Rank *rank = zone->GetAlternateAdvancementRank(aa_id); + //handle expendable AA's + if (rank && rank->base_ability) { + ExpendAlternateAdvancementCharge(rank->base_ability->id); + } + //set AA recast timer + CastToClient()->SendAlternateAdvancementTimer(rank->spell_type, 0, 0); } } - else if(spell_id == casting_spell_id && casting_spell_timer != 0xFFFFFFFF) + //handle bard AA and Discipline recast timers when singing + if (GetClass() == BARD && spell_id != casting_spell_id && timer != 0xFFFFFFFF) { + CastToClient()->GetPTimers().Start(timer, timer_duration); + LogSpells("Spell [{}]: Setting BARD custom reuse timer [{}] to [{}]", spell_id, casting_spell_timer, casting_spell_timer_duration); + } + //handles AA and Discipline recast timers + else if (spell_id == casting_spell_id && casting_spell_timer != 0xFFFFFFFF) { - //aa new todo: aa expendable charges here CastToClient()->GetPTimers().Start(casting_spell_timer, casting_spell_timer_duration); LogSpells("Spell [{}]: Setting custom reuse timer [{}] to [{}]", spell_id, casting_spell_timer, casting_spell_timer_duration); } - else if(spells[spell_id].recast_time > 1000 && !spells[spell_id].IsDisciplineBuff) { + else if(spell_id == casting_spell_id && casting_spell_timer != 0xFFFFFFFF) + { + CastToClient()->GetPTimers().Start(casting_spell_timer, casting_spell_timer_duration); + LogSpells("Spell [{}]: Setting custom reuse timer [{}] to [{}]", spell_id, casting_spell_timer, casting_spell_timer_duration); + } + else if(spells[spell_id].recast_time > 1000 && !spells[spell_id].is_discipline) { int recast = spells[spell_id].recast_time/1000; if (spell_id == SPELL_LAY_ON_HANDS) //lay on hands { @@ -2453,263 +2640,93 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, ui { recast -= GetAA(aaTouchoftheWicked) * 420; } - int reduction = CastToClient()->GetFocusEffect(focusReduceRecastTime, spell_id);//Client only - if(reduction) + + int reduction = CastToClient()->GetFocusEffect(focusReduceRecastTime, spell_id); + + if (reduction) { recast -= reduction; - + casting_spell_recast_adjust = reduction * 1000; //used later to adjust on client with memorizespell_struct + if (recast < 0) { + casting_spell_recast_adjust = spells[spell_id].recast_time; + } + recast = std::max(recast, 0); + } + LogSpells("Spell [{}]: Setting long reuse timer to [{}] s (orig [{}])", spell_id, recast, spells[spell_id].recast_time); - CastToClient()->GetPTimers().Start(pTimerSpellStart + spell_id, recast); + + if (recast > 0) { + CastToClient()->GetPTimers().Start(pTimerSpellStart + spell_id, recast); + } } } - - if(IsClient() && (slot == CastingSlot::Item || slot == CastingSlot::PotionBelt)) - { - EQ::ItemInstance *itm = CastToClient()->GetInv().GetItem(inventory_slot); - if(itm && itm->GetItem()->RecastDelay > 0){ - auto recast_type = itm->GetItem()->RecastType; - CastToClient()->GetPTimers().Start((pTimerItemStart + recast_type), itm->GetItem()->RecastDelay); - database.UpdateItemRecastTimestamps( - CastToClient()->CharacterID(), recast_type, - CastToClient()->GetPTimers().Get(pTimerItemStart + recast_type)->GetReadyTimestamp()); - auto outapp = new EQApplicationPacket(OP_ItemRecastDelay, sizeof(ItemRecastDelay_Struct)); - ItemRecastDelay_Struct *ird = (ItemRecastDelay_Struct *)outapp->pBuffer; - ird->recast_delay = itm->GetItem()->RecastDelay; - ird->recast_type = recast_type; - CastToClient()->QueuePacket(outapp); - safe_delete(outapp); - } + /* + Set Recast Timer on item clicks, including augmenets. + */ + if(IsClient() && (slot == CastingSlot::Item || slot == CastingSlot::PotionBelt)){ + CastToClient()->SetItemRecastTimer(spell_id, inventory_slot); } - if(IsNPC()) + if (IsNPC()) { CastToNPC()->AI_Event_SpellCastFinished(true, static_cast(slot)); + } + + ApplyHealthTransferDamage(this, target, spell_id); + + //This needs to be here for bind sight to update correctly on client. + if (IsClient() && IsEffectInSpell(spell_id, SE_BindSight)) { + for (int i = 0; i < GetMaxTotalSlots(); i++) { + if (buffs[i].spellid == spell_id) { + CastToClient()->SendBuffNumHitPacket(buffs[i], i);//its hack, it works. + } + } + } + //Check if buffs has numhits, then resend packet so it displays the hit count. + if (IsClient() && spells[spell_id].hit_number) { + for (int i = 0; i < GetMaxTotalSlots(); i++) { + if (buffs[i].spellid == spell_id && buffs[i].hit_number > 0) { + CastToClient()->SendBuffNumHitPacket(buffs[i], i); + break; + } + } + } return true; } -/* - * handle bard song pulses... - * - * we make several assumptions that SpellFinished does not: - * - there are no AEDuration (beacon) bard songs - * - there are no recourse spells on bard songs - * - there is no long recast delay on bard songs - * - * return false to stop the song - */ -bool Mob::ApplyNextBardPulse(uint16 spell_id, Mob *spell_target, CastingSlot slot) { - if(slot == CastingSlot::Item) { - //bard songs should never come from items... - LogSpells("Bard Song Pulse [{}]: Supposidly cast from an item. Killing song", spell_id); - return(false); +bool Mob::ApplyBardPulse(int32 spell_id, Mob *spell_target, CastingSlot slot) { + + /* + Check any bard specific special behaviors we need before applying the next pulse. + Note: Silence does not stop an active bard pulse. + */ + if (!spell_target) { + return false; + } + /* + Bard song charm that have no mana will continue to try and pulse on target, but will only reapply when charm fades. + Live does not spam client with do not take hold messages. Checking here avoids that from happening. Only try to reapply if charm fades. + */ + if (spell_target->IsCharmed() && spells[spell_id].mana == 0 && spell_target->GetOwner() == this && IsEffectInSpell(spell_id, SE_Charm)) { + return true; + } + /* + If divine aura applied while pulsing, it is not interrupted but does not reapply until DA fades. + */ + if (DivineAura() && !IgnoreCastingRestriction(spell_id)) { + return true; + } + /* + Fear will stop pulsing. + */ + if (IsFeared()) { + return false; } - //determine the type of spell target we have - Mob *ae_center = nullptr; - CastAction_type CastAction; - if(!DetermineSpellTargets(spell_id, spell_target, ae_center, CastAction, slot)) { - LogSpells("Bard Song Pulse [{}]: was unable to determine target. Stopping", spell_id); - return(false); + if (!SpellFinished(spell_id, spell_target, slot, spells[spell_id].mana, 0xFFFFFFFF, spells[spell_id].resist_difficulty)) { + return false; } - if(ae_center != nullptr && ae_center->IsBeacon()) { - LogSpells("Bard Song Pulse [{}]: Unsupported Beacon NPC AE spell", spell_id); - return(false); - } - - //use mana, if this spell has a mana cost - int mana_used = spells[spell_id].mana; - if(mana_used > 0) { - if(mana_used > GetMana()) { - //ran out of mana... this calls StopSong() for us - LogSpells("Ran out of mana while singing song [{}]", spell_id); - return(false); - } - - LogSpells("Bard Song Pulse [{}]: consuming [{}] mana (have [{}])", spell_id, mana_used, GetMana()); - SetMana(GetMana() - mana_used); - } - - // check line of sight to target if it's a detrimental spell - if(spell_target && IsDetrimentalSpell(spell_id) && !CheckLosFN(spell_target)) - { - LogSpells("Bard Song Pulse [{}]: cannot see target [{}]", spell_target->GetName()); - MessageString(Chat::Red, CANT_SEE_TARGET); - return(false); - } - - //range check our target, if we have one and it is not us - float range = 0.00f; - - range = GetActSpellRange(spell_id, spells[spell_id].range, true); - if(spell_target != nullptr && spell_target != this) { - //casting a spell on somebody but ourself, make sure they are in range - float dist2 = DistanceSquared(m_Position, spell_target->GetPosition()); - float range2 = range * range; - if(dist2 > range2) { - //target is out of range. - LogSpells("Bard Song Pulse [{}]: Spell target is out of range (squared: [{}] > [{}])", spell_id, dist2, range2); - MessageString(Chat::Red, TARGET_OUT_OF_RANGE); - return(false); - } - } - - // - // Switch #2 - execute the spell - // - switch(CastAction) - { - default: - case CastActUnknown: - case SingleTarget: - { - if(spell_target == nullptr) { - LogSpells("Bard Song Pulse [{}]: Targeted spell, but we have no target", spell_id); - return(false); - } - LogSpells("Bard Song Pulse: Targeted. spell [{}], target [{}]", spell_id, spell_target->GetName()); - spell_target->BardPulse(spell_id, this); - break; - } - - case AECaster: - { - if(IsBeneficialSpell(spell_id)) - SpellOnTarget(spell_id, this); - - bool affect_caster = !IsNPC(); //NPC AE spells do not affect the NPC caster - entity_list.AEBardPulse(this, this, spell_id, affect_caster); - break; - } - case AETarget: - { - // we can't cast an AE spell without something to center it on - if(ae_center == nullptr) { - LogSpells("Bard Song Pulse [{}]: AE Targeted spell, but we have no target", spell_id); - return(false); - } - - // regular PB AE or targeted AE spell - spell_target is null if PB - if(spell_target) { // this must be an AETarget spell - // affect the target too - spell_target->BardPulse(spell_id, this); - LogSpells("Bard Song Pulse: spell [{}], AE target [{}]", spell_id, spell_target->GetName()); - } else { - LogSpells("Bard Song Pulse: spell [{}], AE with no target", spell_id); - } - bool affect_caster = !IsNPC(); //NPC AE spells do not affect the NPC caster - entity_list.AEBardPulse(this, ae_center, spell_id, affect_caster); - break; - } - - case GroupSpell: - { - if(spell_target->IsGrouped()) { - LogSpells("Bard Song Pulse: spell [{}], Group targeting group of [{}]", spell_id, spell_target->GetName()); - Group *target_group = entity_list.GetGroupByMob(spell_target); - if(target_group) - target_group->GroupBardPulse(this, spell_id); - } - else if(spell_target->IsRaidGrouped() && spell_target->IsClient()) { - LogSpells("Bard Song Pulse: spell [{}], Raid group targeting raid group of [{}]", spell_id, spell_target->GetName()); - Raid *r = entity_list.GetRaidByClient(spell_target->CastToClient()); - if(r){ - uint32 gid = r->GetGroup(spell_target->GetName()); - if(gid < 12){ - r->GroupBardPulse(this, spell_id, gid); - } - else{ - BardPulse(spell_id, this); -#ifdef GROUP_BUFF_PETS - if (GetPet() && HasPetAffinity() && !GetPet()->IsCharmed()) - GetPet()->BardPulse(spell_id, this); -#endif - } - } - } - else { - LogSpells("Bard Song Pulse: spell [{}], Group target without group. Affecting caster", spell_id); - BardPulse(spell_id, this); -#ifdef GROUP_BUFF_PETS - if (GetPet() && HasPetAffinity() && !GetPet()->IsCharmed()) - GetPet()->BardPulse(spell_id, this); -#endif - } - break; - } - } - - if(IsClient()) - CastToClient()->CheckSongSkillIncrease(spell_id); - - return(true); -} - -void Mob::BardPulse(uint16 spell_id, Mob *caster) { - int buffs_i; - int buff_count = GetMaxTotalSlots(); - for (buffs_i = 0; buffs_i < buff_count; buffs_i++) { - if(buffs[buffs_i].spellid != spell_id) - continue; - if(buffs[buffs_i].casterid != caster->GetID()) { - LogSpells("Bard Pulse for [{}]: found buff from caster [{}] and we are pulsing for [{}] are there two bards playing the same song???", spell_id, buffs[buffs_i].casterid, caster->GetID()); - return; - } - //extend the spell if it will expire before the next pulse - if(buffs[buffs_i].ticsremaining <= 3) { - buffs[buffs_i].ticsremaining += 3; - LogSpells("Bard Song Pulse [{}]: extending duration in slot [{}] to [{}] tics", spell_id, buffs_i, buffs[buffs_i].ticsremaining); - } - - //should we send this buff update to the client... seems like it would - //be a lot of traffic for no reason... -//this may be the wrong packet... - if(IsClient()) { - auto packet = new EQApplicationPacket(OP_Action, sizeof(Action_Struct)); - - Action_Struct* action = (Action_Struct*) packet->pBuffer; - action->source = caster->GetID(); - action->target = GetID(); - action->spell = spell_id; - action->force = spells[spell_id].pushback; - action->hit_heading = GetHeading(); - action->hit_pitch = spells[spell_id].pushup; - action->instrument_mod = caster->GetInstrumentMod(spell_id); - action->effect_flag = 0; - action->spell_level = action->level = buffs[buffs_i].casterlevel; - action->type = DamageTypeSpell; - entity_list.QueueCloseClients(this, packet, false, RuleI(Range, SongMessages), 0, true, IsClient() ? FilterPCSpells : FilterNPCSpells); - - action->effect_flag = 4; - - if(!IsEffectInSpell(spell_id, SE_BindAffinity)) - { - CastToClient()->QueuePacket(packet); - } - - auto message_packet = new EQApplicationPacket(OP_Damage, sizeof(CombatDamage_Struct)); - CombatDamage_Struct *cd = (CombatDamage_Struct *)message_packet->pBuffer; - cd->target = action->target; - cd->source = action->source; - cd->type = DamageTypeSpell; - cd->spellid = action->spell; - cd->force = action->force; - cd->hit_heading = action->hit_heading; - cd->hit_pitch = action->hit_pitch; - cd->damage = 0; - if(!IsEffectInSpell(spell_id, SE_BindAffinity)) - { - entity_list.QueueCloseClients(this, message_packet, false, RuleI(Range, SongMessages), 0, true, IsClient() ? FilterPCSpells : FilterNPCSpells); - } - safe_delete(message_packet); - safe_delete(packet); - - } - //we are done... - return; - } - LogSpells("Bard Song Pulse [{}]: Buff not found, reapplying spell", spell_id); - //this spell is not affecting this mob, apply it. - caster->SpellOnTarget(spell_id, this); + return true; } /////////////////////////////////////////////////////////////////////////////// @@ -2738,8 +2755,14 @@ int Mob::CalcBuffDuration(Mob *caster, Mob *target, uint16 spell_id, int32 caste if(!target) target = caster; - formula = spells[spell_id].buffdurationformula; - duration = spells[spell_id].buffduration; + // PVP duration + if (IsDetrimentalSpell(spell_id) && target->IsClient() && caster->IsClient()) { + formula = spells[spell_id].pvp_duration; + duration = spells[spell_id].pvp_duration_cap; + } else { + formula = spells[spell_id].buff_duration_formula; + duration = spells[spell_id].buff_duration; + } int castlevel = caster->GetCasterLevel(spell_id); if(caster_level_override > 0) @@ -2747,9 +2770,11 @@ int Mob::CalcBuffDuration(Mob *caster, Mob *target, uint16 spell_id, int32 caste int res = CalcBuffDuration_formula(castlevel, formula, duration); if (caster == target && (target->aabonuses.IllusionPersistence || target->spellbonuses.IllusionPersistence || - target->itembonuses.IllusionPersistence) && - spell_id != 287 && spell_id != 601 && IsEffectInSpell(spell_id, SE_Illusion)) + target->itembonuses.IllusionPersistence) && + spell_id != SPELL_MINOR_ILLUSION && spell_id != SPELL_ILLUSION_TREE && IsEffectInSpell(spell_id, SE_Illusion)) { res = 10000; // ~16h override + } + res = mod_buff_duration(res, caster, target, spell_id); @@ -2846,7 +2871,18 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2, LogSpells("Check Stacking on old [{}] ([{}]) @ lvl [{}] (by [{}]) vs. new [{}] ([{}]) @ lvl [{}] (by [{}])", sp1.name, spellid1, caster_level1, (caster1==nullptr)?"Nobody":caster1->GetName(), sp2.name, spellid2, caster_level2, (caster2==nullptr)?"Nobody":caster2->GetName()); + if (spellbonuses.CompleteHealBuffBlocker && IsEffectInSpell(spellid2, SE_CompleteHeal)) { + Message(0, "You must wait before you can be affected by this spell again."); + return -1; + } + if (spellid1 == spellid2 ) { + + if (spellid1 == SPELL_EYE_OF_ZOMM && spellid2 == SPELL_EYE_OF_ZOMM) {//only the original Eye of Zomm spell will not take hold if affect is already on you, other versions client fades the buff as soon as cast. + MessageString(Chat::Red, SPELL_NO_HOLD); + return -1; + } + if (!IsStackableDot(spellid1) && !IsEffectInSpell(spellid1, SE_ManaBurn)) { // mana burn spells we need to use the stacking command blocks live actually checks those first, we should probably rework to that too if (caster_level1 > caster_level2) { // cur buff higher level than new if (IsEffectInSpell(spellid1, SE_ImprovedTaunt)) { @@ -2885,7 +2921,7 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2, if (spellid1 != spellid2) { for (i = 0; i < EFFECT_COUNT; i++) { // we don't want this optimization for mana burns - if (sp1.effectid[i] != sp2.effectid[i] || sp1.effectid[i] == SE_ManaBurn) { + if (sp1.effect_id[i] != sp2.effect_id[i] || sp1.effect_id[i] == SE_ManaBurn) { effect_match = false; break; } @@ -2900,41 +2936,43 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2, if (!effect_match) { for(i = 0; i < EFFECT_COUNT; i++) { - effect1 = sp1.effectid[i]; - effect2 = sp2.effectid[i]; + effect1 = sp1.effect_id[i]; + effect2 = sp2.effect_id[i]; if (spellbonuses.Screech == 1) { - if (effect2 == SE_Screech && sp2.base[i] == -1) { + if (effect2 == SE_Screech && sp2.base_value[i] == -1) { MessageString(Chat::SpellFailure, SCREECH_BUFF_BLOCK, sp2.name); return -1; } } - /*Buff stacking prevention spell effects (446 - 449) works as follows... If B prevent A, if C prevent B, if D prevent C. - If checking same type ie A vs A, which ever effect base value is higher will take hold. - Special check is added to make sure the buffs stack properly when applied from fade on duration effect, since the buff - is not fully removed at the time of the trgger*/ - if (spellbonuses.AStacker[0]) { - if ((effect2 == SE_AStacker) && (sp2.effectid[i] <= spellbonuses.AStacker[1])) + /* + Buff stacking prevention spell effects (446 - 449) works as follows... If B prevent A, if C prevent B, if D prevent C. + If checking same type ie A vs A, which ever effect base value is higher will take hold. + Special check is added to make sure the buffs stack properly when applied from fade on duration effect, since the buff + is not fully removed at the time of the trigger + */ + if (spellbonuses.AStacker[SBIndex::BUFFSTACKER_EXISTS]) { + if ((effect2 == SE_AStacker) && (sp2.effect_id[i] <= spellbonuses.AStacker[SBIndex::BUFFSTACKER_VALUE])) return -1; } - if (spellbonuses.BStacker[0]) { - if ((effect2 == SE_BStacker) && (sp2.effectid[i] <= spellbonuses.BStacker[1])) + if (spellbonuses.BStacker[SBIndex::BUFFSTACKER_EXISTS]) { + if ((effect2 == SE_BStacker) && (sp2.effect_id[i] <= spellbonuses.BStacker[SBIndex::BUFFSTACKER_VALUE])) return -1; if ((effect2 == SE_AStacker) && (!IsCastonFadeDurationSpell(spellid1) && buffs[buffslot].ticsremaining != 1 && IsEffectInSpell(spellid1, SE_BStacker))) return -1; } - if (spellbonuses.CStacker[0]) { - if ((effect2 == SE_CStacker) && (sp2.effectid[i] <= spellbonuses.CStacker[1])) + if (spellbonuses.CStacker[SBIndex::BUFFSTACKER_EXISTS]) { + if ((effect2 == SE_CStacker) && (sp2.effect_id[i] <= spellbonuses.CStacker[SBIndex::BUFFSTACKER_VALUE])) return -1; if ((effect2 == SE_BStacker) && (!IsCastonFadeDurationSpell(spellid1) && buffs[buffslot].ticsremaining != 1 && IsEffectInSpell(spellid1, SE_CStacker))) return -1; } - if (spellbonuses.DStacker[0]) { - if ((effect2 == SE_DStacker) && (sp2.effectid[i] <= spellbonuses.DStacker[1])) + if (spellbonuses.DStacker[SBIndex::BUFFSTACKER_EXISTS]) { + if ((effect2 == SE_DStacker) && (sp2.effect_id[i] <= spellbonuses.DStacker[SBIndex::BUFFSTACKER_VALUE])) return -1; if ((effect2 == SE_CStacker) && (!IsCastonFadeDurationSpell(spellid1) && buffs[buffslot].ticsremaining != 1 && IsEffectInSpell(spellid1, SE_DStacker))) return -1; @@ -2942,10 +2980,10 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2, if(effect2 == SE_StackingCommand_Overwrite) { - overwrite_effect = sp2.base[i]; + overwrite_effect = sp2.base_value[i]; overwrite_slot = sp2.formula[i] - 201; //they use base 1 for slots, we use base 0 - overwrite_below_value = sp2.max[i]; - if(sp1.effectid[overwrite_slot] == overwrite_effect) + overwrite_below_value = sp2.max_value[i]; + if(sp1.effect_id[overwrite_slot] == overwrite_effect) { sp1_value = CalcSpellEffectValue(spellid1, overwrite_slot, caster_level1); @@ -2964,11 +3002,11 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2, } } else if (effect1 == SE_StackingCommand_Block) { - blocked_effect = sp1.base[i]; + blocked_effect = sp1.base_value[i]; blocked_slot = sp1.formula[i] - 201; - blocked_below_value = sp1.max[i]; + blocked_below_value = sp1.max_value[i]; - if (sp2.effectid[blocked_slot] == blocked_effect) + if (sp2.effect_id[blocked_slot] == blocked_effect) { sp2_value = CalcSpellEffectValue(spellid2, blocked_slot, caster_level2); @@ -3010,8 +3048,8 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2, if(IsBlankSpellEffect(spellid1, i) || IsBlankSpellEffect(spellid2, i)) continue; - effect1 = sp1.effectid[i]; - effect2 = sp2.effectid[i]; + effect1 = sp1.effect_id[i]; + effect2 = sp2.effect_id[i]; /* Quick check, are the effects the same, if so then @@ -3031,7 +3069,7 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2, // negative AC affects are skipped. Ex. Sun's Corona and Glacier Breath should stack // There may be more SPAs we need to add here .... // The client does just check base rather than calculating the affect change value. - if ((effect1 == SE_ArmorClass || effect1 == SE_ACv2) && sp2.base[i] < 0) + if ((effect1 == SE_ArmorClass || effect1 == SE_ACv2) && sp2.base_value[i] < 0) continue; /* @@ -3271,6 +3309,8 @@ int Mob::AddBuff(Mob *caster, uint16 spell_id, int duration, int32 level_overrid return -1; } } + //do not fade buff if from bard pulse, live does not give a fades message. + bool from_bard_song_pulse = caster ? caster->IsActiveBardSong(spell_id) : false; // at this point we know that this buff will stick, but we have // to remove some other buffs already worn if will_overwrite is true @@ -3280,7 +3320,9 @@ int Mob::AddBuff(Mob *caster, uint16 spell_id, int duration, int32 level_overrid end = overwrite_slots.end(); for (; cur != end; ++cur) { // strip spell - BuffFadeBySlot(*cur, false); + if (!from_bard_song_pulse) { + BuffFadeBySlot(*cur, false); + } // if we hadn't found a free slot before, or if this is earlier // we use it @@ -3290,7 +3332,7 @@ int Mob::AddBuff(Mob *caster, uint16 spell_id, int duration, int32 level_overrid } // now add buff at emptyslot - assert(buffs[emptyslot].spellid == SPELL_UNKNOWN); // sanity check + //assert(buffs[emptyslot].spellid == SPELL_UNKNOWN); // sanity check buffs[emptyslot].spellid = spell_id; buffs[emptyslot].casterlevel = caster_level; @@ -3301,7 +3343,7 @@ int Mob::AddBuff(Mob *caster, uint16 spell_id, int duration, int32 level_overrid buffs[emptyslot].casterid = caster ? caster->GetID() : 0; buffs[emptyslot].ticsremaining = duration; buffs[emptyslot].counters = CalculateCounters(spell_id); - buffs[emptyslot].numhits = spells[spell_id].numhits; + buffs[emptyslot].hit_number = spells[spell_id].hit_number; buffs[emptyslot].client = caster ? caster->IsClient() : 0; buffs[emptyslot].persistant_buff = 0; buffs[emptyslot].caston_x = 0; @@ -3310,12 +3352,13 @@ int Mob::AddBuff(Mob *caster, uint16 spell_id, int duration, int32 level_overrid buffs[emptyslot].dot_rune = 0; buffs[emptyslot].ExtraDIChance = 0; buffs[emptyslot].RootBreakChance = 0; + buffs[emptyslot].virus_spread_time = 0; buffs[emptyslot].instrument_mod = caster ? caster->GetInstrumentMod(spell_id) : 10; - if (level_override > 0) { + if (level_override > 0 || buffs[emptyslot].hit_number > 0) { buffs[emptyslot].UpdateClient = true; } else { - if (buffs[emptyslot].ticsremaining > (1 + CalcBuffDuration_formula(caster_level, spells[spell_id].buffdurationformula, spells[spell_id].buffduration))) + if (buffs[emptyslot].ticsremaining > (1 + CalcBuffDuration_formula(caster_level, spells[spell_id].buff_duration_formula, spells[spell_id].buff_duration))) buffs[emptyslot].UpdateClient = true; } @@ -3392,7 +3435,7 @@ int Mob::CanBuffStack(uint16 spellid, uint8 caster_level, bool iFailIfOverwrite) firstfree = i; } if(ret == -1) { - + LogAI("Buff [{}] would conflict with [{}] in slot [{}], reporting stack failure", spellid, curbuf.spellid, i); return -1; // stop the spell, can't stack it } @@ -3419,12 +3462,9 @@ int Mob::CanBuffStack(uint16 spellid, uint8 caster_level, bool iFailIfOverwrite) // and if you don't want effects just return false. interrupting here will // break stuff // -bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_resist_adjust, int16 resist_adjust, - bool isproc, int level_override) +bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, int reflect_effectiveness, bool use_resist_adjust, int16 resist_adjust, + bool isproc, int level_override, int32 duration_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) { @@ -3433,10 +3473,16 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r return false; } - if(spelltar->IsClient() && spelltar->CastToClient()->IsHoveringForRespawn()) + if (spelltar->IsClient() && spelltar->CastToClient()->IsHoveringForRespawn()) { + return false; + } + + if (!IsValidSpell(spell_id)) return false; - if(IsDetrimentalSpell(spell_id) && !IsAttackAllowed(spelltar, true) && !IsResurrectionEffects(spell_id)) { + bool is_damage_or_lifetap_spell = IsDamageSpell(spell_id) || IsLifetapSpell(spell_id); + + if(IsDetrimentalSpell(spell_id) && !IsAttackAllowed(spelltar, true) && !IsResurrectionEffects(spell_id) && !IsEffectInSpell(spell_id, SE_BindSight)) { if(!IsClient() || !CastToClient()->GetGM()) { MessageString(Chat::SpellFailure, SPELL_NO_HOLD); return false; @@ -3446,14 +3492,11 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r EQApplicationPacket *action_packet = nullptr, *message_packet = nullptr; float spell_effectiveness; - if(!IsValidSpell(spell_id)) - return false; - // these target types skip pcnpc only check (according to dev quotes) // other AE spells this is redundant, oh well // 1 = PCs, 2 = NPCs - if (spells[spell_id].pcnpc_only_flag && spells[spell_id].targettype != ST_AETargetHateList && - spells[spell_id].targettype != ST_HateList) { + if (spells[spell_id].pcnpc_only_flag && spells[spell_id].target_type != ST_AETargetHateList && + spells[spell_id].target_type != ST_HateList) { if (spells[spell_id].pcnpc_only_flag == 1 && !spelltar->IsClient() && !spelltar->IsMerc() && !spelltar->IsBot()) return false; else if (spells[spell_id].pcnpc_only_flag == 2 && (spelltar->IsClient() || spelltar->IsMerc() || spelltar->IsBot())) @@ -3495,11 +3538,7 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r } // select target - if // Bind Sight line of spells - ( - spell_id == 500 || // bind sight - spell_id == 407 // cast sight - ) + if (IsEffectInSpell(spell_id, SE_BindSight)) { action->target = GetID(); } @@ -3511,9 +3550,9 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r action->spell_level = action->level = caster_level; // caster level, for animation only action->type = 231; // 231 means a spell action->spell = spell_id; - action->force = spells[spell_id].pushback; + action->force = spells[spell_id].push_back; action->hit_heading = GetHeading(); - action->hit_pitch = spells[spell_id].pushup; + action->hit_pitch = spells[spell_id].push_up; action->instrument_mod = GetInstrumentMod(spell_id); action->effect_flag = 0; @@ -3527,27 +3566,26 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r spelltar, /* Sender */ action_packet, /* Packet */ true, /* Ignore Sender */ - RuleI(Range, SpellMessages), + RuleI(Range, SpellMessages), this, /* Skip this Mob */ true, /* Packet ACK */ (spelltar->IsClient() ? FilterPCSpells : FilterNPCSpells) /* EQ Filter Type: (8 or 9) */ ); /* Send the EVENT_CAST_ON event */ - if(spelltar->IsNPC()) - { - char temp1[100]; - sprintf(temp1, "%d", spell_id); - parse->EventNPC(EVENT_CAST_ON, spelltar->CastToNPC(), this, temp1, 0); - } - else if (spelltar->IsClient()) - { - char temp1[100]; - sprintf(temp1, "%d", spell_id); - parse->EventPlayer(EVENT_CAST_ON, spelltar->CastToClient(),temp1, 0); + std::string export_string = fmt::format("{}", spell_id); + if(spelltar->IsNPC()) { + parse->EventNPC(EVENT_CAST_ON, spelltar->CastToNPC(), this, export_string, 0); + } else if (spelltar->IsClient()) { + parse->EventPlayer(EVENT_CAST_ON, spelltar->CastToClient(), export_string, 0); } - mod_spell_cast(spell_id, spelltar, reflect, use_resist_adjust, resist_adjust, isproc); + mod_spell_cast(spell_id, spelltar, reflect_effectiveness, use_resist_adjust, resist_adjust, isproc); + + if (!DoCastingChecksOnTarget(false, spell_id, spelltar)) { + safe_delete(action_packet); + return false; + } // now check if the spell is allowed to land if (RuleB(Spells, EnableBlockedBuffs)) { @@ -3568,8 +3606,10 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r } } - // invuln mobs can't be affected by any spells, good or bad - if(spelltar->GetInvul() || spelltar->DivineAura()) { + // invuln mobs can't be affected by any spells, good or bad, except if caster is casting a spell with 'cast_not_standing' on self. + if ((spelltar->GetInvul() && !spelltar->DivineAura()) || + (spelltar != this && spelltar->DivineAura()) || + (spelltar == this && spelltar->DivineAura() && !IgnoreCastingRestriction(spell_id))) { LogSpells("Casting spell [{}] on [{}] aborted: they are invulnerable", spell_id, spelltar->GetName()); safe_delete(action_packet); return false; @@ -3599,33 +3639,36 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r // Prevent double invising, which made you uninvised // Not sure if all 3 should be stacking - if(IsEffectInSpell(spell_id, SE_Invisibility)) - { - if(spelltar->invisible) - { - spelltar->MessageString(Chat::SpellFailure, ALREADY_INVIS, GetCleanName()); - safe_delete(action_packet); - return false; - } - } - if(IsEffectInSpell(spell_id, SE_InvisVsUndead)) - { - if(spelltar->invisible_undead) + if (!RuleB(Spells, AllowDoubleInvis)) { + if (IsEffectInSpell(spell_id, SE_Invisibility)) { - spelltar->MessageString(Chat::SpellFailure, ALREADY_INVIS, GetCleanName()); - safe_delete(action_packet); - return false; + if (spelltar->invisible) + { + spelltar->MessageString(Chat::SpellFailure, ALREADY_INVIS, GetCleanName()); + safe_delete(action_packet); + return false; + } } - } - if(IsEffectInSpell(spell_id, SE_InvisVsAnimals)) - { - if(spelltar->invisible_animals) + if (IsEffectInSpell(spell_id, SE_InvisVsUndead)) { - spelltar->MessageString(Chat::SpellFailure, ALREADY_INVIS, GetCleanName()); - safe_delete(action_packet); - return false; + if (spelltar->invisible_undead) + { + spelltar->MessageString(Chat::SpellFailure, ALREADY_INVIS, GetCleanName()); + safe_delete(action_packet); + return false; + } + } + + if (IsEffectInSpell(spell_id, SE_InvisVsAnimals)) + { + if (spelltar->invisible_animals) + { + spelltar->MessageString(Chat::SpellFailure, ALREADY_INVIS, GetCleanName()); + safe_delete(action_packet); + return false; + } } } @@ -3698,7 +3741,7 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r ) ) { - if(spells[spell_id].targettype == ST_AEBard) { + if(spells[spell_id].target_type == ST_AEBard) { //if it was a beneficial AE bard song don't spam the window that it would not hold LogSpells("Beneficial ae bard song [{}] can't take hold [{}] -> [{}], IBA? [{}]", spell_id, GetName(), spelltar->GetName(), IsBeneficialAllowed(spelltar)); } else { @@ -3710,7 +3753,7 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r } } } - else if ( !IsAttackAllowed(spelltar, true) && !IsResurrectionEffects(spell_id)) // Detrimental spells - PVP check + else if ( !IsAttackAllowed(spelltar, true) && !IsResurrectionEffects(spell_id) && !IsEffectInSpell(spell_id, SE_BindSight)) // Detrimental spells - PVP check { LogSpells("Detrimental spell [{}] can't take hold [{}] -> [{}]", spell_id, GetName(), spelltar->GetName()); spelltar->MessageString(Chat::SpellFailure, YOU_ARE_PROTECTED, GetCleanName()); @@ -3732,7 +3775,7 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r } //check for AE_Undead - if(spells[spell_id].targettype == ST_UndeadAE){ + if(spells[spell_id].target_type == ST_UndeadAE){ if(spelltar->GetBodyType() != BT_SummonedUndead && spelltar->GetBodyType() != BT_Undead && spelltar->GetBodyType() != BT_Vampire) @@ -3741,6 +3784,13 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r return false; } } + //Need this to account for special AOE cases. + if (IsClient() && IsHarmonySpell(spell_id) && !HarmonySpellLevelCheck(spell_id, spelltar)) { + MessageString(Chat::SpellFailure, SPELL_NO_EFFECT); + safe_delete(action_packet); + return false; + } + // Block next spell effect should be used up first(since its blocking the next spell) if(CanBlockSpell()) { int buff_count = GetMaxTotalSlots(); @@ -3757,69 +3807,101 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r } } } - // Reflect - if(spelltar && spelltar->TryReflectSpell(spell_id) && !reflect && IsDetrimentalSpell(spell_id) && this != spelltar) { - int reflect_chance = 0; + /* + Reflect + base= % Chance to Reflect + Limit= Resist Modifier (+Value for decrease chance to resist) + Max= % of base spell damage (this is the base before any formula or focus is applied) + On live any type of detrimental spell can be reflected as long as the Reflectable spell field is set, this includes AOE. + The 'caster' of the reflected spell is owner of the reflect effect. Caster's focus effects are NOT applied to reflected spell. + + reflect_effectiveness is applied to damage spells, a value of 100 is no change to base damage. Other values change by percent. (50=50% of damage) + we this variable to both check if a spell being applied is from a reflection and for the damage modifier. + + There are a few spells in database that are not detrimental that have Reflectable field set, however from testing, they do not actually reflect. + */ + if(spells[spell_id].reflectable && !reflect_effectiveness && spelltar && this != spelltar && IsDetrimentalSpell(spell_id) && + (spelltar->spellbonuses.reflect[SBIndex::REFLECT_CHANCE] || spelltar->aabonuses.reflect[SBIndex::REFLECT_CHANCE] || spelltar->itembonuses.reflect[SBIndex::REFLECT_CHANCE])) { + bool can_spell_reflect = false; switch(RuleI(Spells, ReflectType)) { - case 0: + case REFLECT_DISABLED: break; - case 1: + case REFLECT_SINGLE_TARGET_SPELLS_ONLY: { - if(spells[spell_id].targettype == ST_Target) { + if(spells[spell_id].target_type == ST_Target) { for(int y = 0; y < 16; y++) { - if(spells[spell_id].classes[y] < 255) - reflect_chance = 1; + if (spells[spell_id].classes[y] < 255) { + can_spell_reflect = true; + } } } break; } - case 2: + case REFLECT_ALL_PLAYER_SPELLS: { for(int y = 0; y < 16; y++) { - if(spells[spell_id].classes[y] < 255) - reflect_chance = 1; + if (spells[spell_id].classes[y] < 255) { + can_spell_reflect = true; + } } break; } - case 3: + case RELFECT_ALL_SINGLE_TARGET_SPELLS: { - if(spells[spell_id].targettype == ST_Target) - reflect_chance = 1; - + if (spells[spell_id].target_type == ST_Target) { + can_spell_reflect = true; + } break; } - case 4: - reflect_chance = 1; + case REFLECT_ALL_SPELLS: //This is live like behavior + can_spell_reflect = true; default: break; } - if (reflect_chance) { + if (can_spell_reflect) { - if (RuleB(Spells, ReflectMessagesClose)) { - entity_list.MessageCloseString( - this, /* Sender */ - false, /* Skip Sender */ - RuleI(Range, SpellMessages), /* Range */ - Chat::Spells, /* Type */ - SPELL_REFLECT, /* String ID */ - GetCleanName(), /* Message 1 */ - spelltar->GetCleanName() /* Message 2 */ - ); + int reflect_resist_adjust = 0; + int reflect_effectiveness_mod = 0; //Need value of 100 to do baseline unmodified damage. + + if (spelltar->spellbonuses.reflect[SBIndex::REFLECT_CHANCE] && zone->random.Roll(spelltar->spellbonuses.reflect[SBIndex::REFLECT_CHANCE])) { + reflect_resist_adjust = spelltar->spellbonuses.reflect[SBIndex::REFLECT_RESISTANCE_MOD]; + reflect_effectiveness_mod = spelltar->spellbonuses.reflect[SBIndex::REFLECT_DMG_EFFECTIVENESS] ? spelltar->spellbonuses.reflect[SBIndex::REFLECT_DMG_EFFECTIVENESS] : 100; } - else { - MessageString(Chat::Spells, SPELL_REFLECT, GetCleanName(), spelltar->GetCleanName()); + else if (spelltar->aabonuses.reflect[SBIndex::REFLECT_CHANCE] && zone->random.Roll(spelltar->aabonuses.reflect[SBIndex::REFLECT_CHANCE])) { + reflect_effectiveness_mod = 100; + reflect_resist_adjust = spelltar->aabonuses.reflect[SBIndex::REFLECT_RESISTANCE_MOD]; + } + else if (spelltar->itembonuses.reflect[SBIndex::REFLECT_CHANCE] && zone->random.Roll(spelltar->itembonuses.reflect[SBIndex::REFLECT_CHANCE])) { + reflect_resist_adjust = spelltar->itembonuses.reflect[SBIndex::REFLECT_RESISTANCE_MOD]; + reflect_effectiveness_mod = spelltar->itembonuses.reflect[SBIndex::REFLECT_DMG_EFFECTIVENESS] ? spelltar->itembonuses.reflect[SBIndex::REFLECT_DMG_EFFECTIVENESS] : 100; } - CheckNumHitsRemaining(NumHit::ReflectSpell); - // caster actually appears to change - // ex. During OMM fight you click your reflect mask and you get the recourse from the reflected - // spell - spelltar->SpellOnTarget(spell_id, this, true, use_resist_adjust, resist_adjust); - safe_delete(action_packet); - return false; + if (reflect_effectiveness_mod) { + + if (RuleB(Spells, ReflectMessagesClose)) { + entity_list.MessageCloseString( + this, /* Sender */ + false, /* Skip Sender */ + RuleI(Range, SpellMessages), /* Range */ + Chat::Spells, /* Type */ + SPELL_REFLECT, /* String ID */ + GetCleanName(), /* Message 1 */ + spelltar->GetCleanName() /* Message 2 */ + ); + } + else { + MessageString(Chat::Spells, SPELL_REFLECT, GetCleanName(), spelltar->GetCleanName()); + } + + CheckNumHitsRemaining(NumHit::ReflectSpell); + + spelltar->SpellOnTarget(spell_id, this, reflect_effectiveness_mod, use_resist_adjust, (resist_adjust - reflect_resist_adjust)); + safe_delete(action_packet); + return false; + } } } @@ -3831,9 +3913,9 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r spelltar->BreakInvisibleSpells(); //Any detrimental spell cast on you will drop invisible (can be AOE, non damage ect). if (IsCharmSpell(spell_id) || IsMezSpell(spell_id) || IsFearSpell(spell_id)) - spell_effectiveness = spelltar->ResistSpell(spells[spell_id].resisttype, spell_id, this, use_resist_adjust, resist_adjust, true, false, false, level_override); + spell_effectiveness = spelltar->ResistSpell(spells[spell_id].resist_type, spell_id, this, use_resist_adjust, resist_adjust, true, false, false, level_override); else - spell_effectiveness = spelltar->ResistSpell(spells[spell_id].resisttype, spell_id, this, use_resist_adjust, resist_adjust, false, false, false, level_override); + spell_effectiveness = spelltar->ResistSpell(spells[spell_id].resist_type, spell_id, this, use_resist_adjust, resist_adjust, false, false, false, level_override); if(spell_effectiveness < 100) { @@ -3841,7 +3923,7 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r { LogSpells("Spell [{}] was completely resisted by [{}]", spell_id, spelltar->GetName()); - if (spells[spell_id].resisttype == RESIST_PHYSICAL){ + if (spells[spell_id].resist_type == RESIST_PHYSICAL){ MessageString(Chat::SpellFailure, PHYSICAL_RESIST_FAIL,spells[spell_id].name); spelltar->MessageString(Chat::SpellFailure, YOU_RESIST, spells[spell_id].name); } @@ -3883,7 +3965,7 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r spelltar->CastToClient()->BreakSneakWhenCastOn(this, true); spelltar->CastToClient()->BreakFeignDeathWhenCastOn(true); } - + spelltar->CheckNumHitsRemaining(NumHit::IncomingSpells); CheckNumHitsRemaining(NumHit::OutgoingSpells); @@ -3911,8 +3993,9 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r spell_effectiveness = 100; } - if(spelltar->spellbonuses.SpellDamageShield && IsDetrimentalSpell(spell_id)) + if (spells[spell_id].feedbackable && (spelltar->spellbonuses.SpellDamageShield || spelltar->itembonuses.SpellDamageShield || spelltar->aabonuses.SpellDamageShield)) { spelltar->DamageShield(this, true); + } if (spelltar->IsAIControlled() && IsDetrimentalSpell(spell_id) && !IsHarmonySpell(spell_id)) { int32 aggro_amount = CheckAggroAmount(spell_id, spelltar, isproc); @@ -3925,11 +4008,21 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r spelltar->SetHateAmountOnEnt(this, std::max(newhate, 1)); } } else if (IsBeneficialSpell(spell_id) && !IsSummonPCSpell(spell_id)) { - if (this != spelltar && spelltar->IsClient() && IsClient()) - CastToClient()->UpdateRestTimer(spelltar->CastToClient()->GetRestTimer()); + if (this != spelltar && IsClient()){ + if (spelltar->IsClient()) { + CastToClient()->UpdateRestTimer(spelltar->CastToClient()->GetRestTimer()); + } + else if (spelltar->IsPet()) { + Mob *owner = spelltar->GetOwner(); + if (owner && owner != this && owner->IsClient()) { + CastToClient()->UpdateRestTimer(owner->CastToClient()->GetRestTimer()); + } + } + } + entity_list.AddHealAggro( - spelltar, this, - CheckHealAggroAmount(spell_id, spelltar, (spelltar->GetMaxHP() - spelltar->GetHP()))); + spelltar, this, + CheckHealAggroAmount(spell_id, spelltar, (spelltar->GetMaxHP() - spelltar->GetHP()))); } // make sure spelltar is high enough level for the buff @@ -3943,7 +4036,7 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r } // cause the effects to the target - if(!spelltar->SpellEffect(this, spell_id, spell_effectiveness, level_override)) + if(!spelltar->SpellEffect(this, spell_id, spell_effectiveness, level_override, reflect_effectiveness, duration_override)) { // if SpellEffect returned false there's a problem applying the // spell. It's most likely a buff that can't stack. @@ -3954,8 +4047,12 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r return false; } - if (IsValidSpell(spells[spell_id].RecourseLink) && spells[spell_id].RecourseLink != spell_id) - SpellFinished(spells[spell_id].RecourseLink, this, CastingSlot::Item, 0, -1, spells[spells[spell_id].RecourseLink].ResistDiff); + //Check SE_Fc_Cast_Spell_On_Land SPA 481 on target, if hit by this spell and Conditions are Met then target will cast the specified spell. + if (spelltar) + spelltar->CastSpellOnLand(this, spell_id); + + if (IsValidSpell(spells[spell_id].recourse_link) && spells[spell_id].recourse_link != spell_id) + SpellFinished(spells[spell_id].recourse_link, this, CastingSlot::Item, 0, -1, spells[spells[spell_id].recourse_link].resist_difficulty); if (IsDetrimentalSpell(spell_id)) { @@ -3971,9 +4068,16 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r // the complete sequence is 2 actions and 1 damage message action->effect_flag = 0x04; // this is a success flag - if(spells[spell_id].pushback != 0.0f || spells[spell_id].pushup != 0.0f) + if(spells[spell_id].push_back != 0.0f || spells[spell_id].push_up != 0.0f) { - if (RuleB(Spells, NPCSpellPush) && !spelltar->IsRooted() && spelltar->ForcedMovement == 0) { + if (spelltar->IsClient()) + { + if (!IsBuffSpell(spell_id)) + { + spelltar->CastToClient()->cheat_manager.SetExemptStatus(KnockBack, true); + } + } + else if (RuleB(Spells, NPCSpellPush) && !spelltar->IsPermaRooted() && !spelltar->IsPseudoRooted() && spelltar->ForcedMovement == 0) { spelltar->m_Delta.x += action->force * g_Math.FastSin(action->hit_heading); spelltar->m_Delta.y += action->force * g_Math.FastCos(action->hit_heading); spelltar->m_Delta.z += action->hit_pitch; @@ -3981,6 +4085,11 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r } } + if (spelltar->IsClient() && IsEffectInSpell(spell_id, SE_ShadowStep)) + { + spelltar->CastToClient()->cheat_manager.SetExemptStatus(ShadowStep, true); + } + if(!IsEffectInSpell(spell_id, SE_BindAffinity)) { if(spelltar != this && spelltar->IsClient()) // send to target @@ -4008,7 +4117,7 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r spelltar, /* Sender */ message_packet, /* Packet */ false, /* Ignore Sender */ - RuleI(Range, SpellMessages), + RuleI(Range, SpellMessages), 0, /* Skip this mob */ true, /* Packet ACK */ (spelltar->IsClient() ? FilterPCSpells : FilterNPCSpells) /* Message Filter Type: (8 or 9) */ @@ -4056,9 +4165,9 @@ void Corpse::CastRezz(uint16 spellid, Mob* Caster) rezz->zone_id = zone->GetZoneID(); rezz->instance_id = zone->GetInstanceID(); rezz->spellid = spellid; - rezz->x = this->m_Position.x; - rezz->y = this->m_Position.y; - rezz->z = this->m_Position.z; + rezz->x = m_Position.x; + rezz->y = m_Position.y; + rezz->z = GetFixedZ(m_Position); rezz->unknown000 = 0x00000000; rezz->unknown020 = 0x00000000; rezz->unknown088 = 0x00000000; @@ -4067,179 +4176,250 @@ void Corpse::CastRezz(uint16 spellid, Mob* Caster) safe_delete(outapp); } -bool Mob::FindBuff(uint16 spellid) +bool Mob::FindBuff(uint16 spell_id) { - int i; - uint32 buff_count = GetMaxTotalSlots(); - for(i = 0; i < buff_count; i++) - if(buffs[i].spellid == spellid) + for (int buff_slot = 0; buff_slot < buff_count; buff_slot++) { + auto current_spell_id = buffs[buff_slot].spellid; + if ( + IsValidSpell(current_spell_id) && + current_spell_id == spell_id + ) { return true; + } + } return false; } uint16 Mob::FindBuffBySlot(int slot) { - if (buffs[slot].spellid != SPELL_UNKNOWN) - return buffs[slot].spellid; - + auto current_spell_id = buffs[slot].spellid; + if (IsValidSpell(current_spell_id)) { + return current_spell_id; + } + return 0; } uint32 Mob::BuffCount() { uint32 active_buff_count = 0; int buff_count = GetMaxTotalSlots(); - for (int i = 0; i < buff_count; i++) - if (buffs[i].spellid != SPELL_UNKNOWN) + for (int buff_slot = 0; buff_slot < buff_count; buff_slot++) { + if (IsValidSpell(buffs[buff_slot].spellid)) { active_buff_count++; - + } + } + return active_buff_count; } -// removes all buffs +bool Mob::HasBuffWithSpellGroup(int spell_group) +{ + for (int buff_slot = 0; buff_slot < GetMaxTotalSlots(); buff_slot++) { + auto current_spell_id = buffs[buff_slot].spellid; + if ( + IsValidSpell(current_spell_id) && + spells[current_spell_id].spell_group == spell_group + ) { + return true; + } + } + + return false; +} + void Mob::BuffFadeAll() { + bool recalc_bonus = false; int buff_count = GetMaxTotalSlots(); - for (int j = 0; j < buff_count; j++) { - if(buffs[j].spellid != SPELL_UNKNOWN) - BuffFadeBySlot(j, false); - } - //we tell BuffFadeBySlot not to recalc, so we can do it only once when were done - CalcBonuses(); -} - -void Mob::BuffFadeNonPersistDeath() -{ - int buff_count = GetMaxTotalSlots(); - for (int j = 0; j < buff_count; j++) { - if (buffs[j].spellid != SPELL_UNKNOWN && !IsPersistDeathSpell(buffs[j].spellid)) - BuffFadeBySlot(j, false); - } - //we tell BuffFadeBySlot not to recalc, so we can do it only once when were done - CalcBonuses(); -} - -void Mob::BuffFadeDetrimental() { - int buff_count = GetMaxTotalSlots(); - for (int j = 0; j < buff_count; j++) { - if(buffs[j].spellid != SPELL_UNKNOWN) { - if(IsDetrimentalSpell(buffs[j].spellid)) - BuffFadeBySlot(j, false); - } - } - //we tell BuffFadeBySlot not to recalc, so we can do it only once when were done - CalcBonuses(); -} - -void Mob::BuffFadeDetrimentalByCaster(Mob *caster) -{ - if(!caster) - return; - - int buff_count = GetMaxTotalSlots(); - for (int j = 0; j < buff_count; j++) { - if(buffs[j].spellid != SPELL_UNKNOWN) { - if(IsDetrimentalSpell(buffs[j].spellid)) - { - //this is a pretty terrible way to do this but - //there really isn't another way till I rewrite the basics - Mob * c = entity_list.GetMob(buffs[j].casterid); - if(c && c == caster) - BuffFadeBySlot(j, false); - } - } - } - //we tell BuffFadeBySlot not to recalc, so we can do it only once when were done - CalcBonuses(); -} - -void Mob::BuffFadeBySitModifier() -{ - bool r_bonus = false; - uint32 buff_count = GetMaxTotalSlots(); - for(uint32 j = 0; j < buff_count; ++j) - { - if(buffs[j].spellid != SPELL_UNKNOWN) - { - if(spells[buffs[j].spellid].disallow_sit) - { - BuffFadeBySlot(j, false); - r_bonus = true; - } + for (int buff_slot = 0; buff_slot < buff_count; buff_slot++) { + if (IsValidSpell(buffs[buff_slot].spellid)) { + BuffFadeBySlot(buff_slot, false); + recalc_bonus = true; } } - if(r_bonus) - { + if (recalc_bonus) { CalcBonuses(); } } -// removes the buff matching spell_id -void Mob::BuffFadeBySpellID(uint16 spell_id) +void Mob::BuffFadeNonPersistDeath() { + bool recalc_bonus = false; int buff_count = GetMaxTotalSlots(); - for (int j = 0; j < buff_count; j++) - { - if (buffs[j].spellid == spell_id) - BuffFadeBySlot(j, false); + for (int buff_slot = 0; buff_slot < buff_count; buff_slot++) { + auto current_spell_id = buffs[buff_slot].spellid; + if ( + IsValidSpell(current_spell_id) && + !IsPersistDeathSpell(current_spell_id) && + !HasPersistDeathIllusion(current_spell_id) + ) { + BuffFadeBySlot(buff_slot, false); + recalc_bonus = true; + } } - //we tell BuffFadeBySlot not to recalc, so we can do it only once when were done - CalcBonuses(); + if (recalc_bonus) { + CalcBonuses(); + } +} + +void Mob::BuffFadeBeneficial() { + bool recalc_bonus = false; + int buff_count = GetMaxTotalSlots(); + for (int buff_slot = 0; buff_slot < buff_count; buff_slot++) { + auto current_spell_id = buffs[buff_slot].spellid; + if ( + IsValidSpell(current_spell_id) && + IsBeneficialSpell(current_spell_id) + ) { + BuffFadeBySlot(buff_slot, false); + recalc_bonus = true; + } + } + + if (recalc_bonus) { + CalcBonuses(); + } +} + +void Mob::BuffFadeDetrimental() { + bool recalc_bonus = false; + int buff_count = GetMaxTotalSlots(); + for (int buff_slot = 0; buff_slot < buff_count; buff_slot++) { + auto current_spell_id = buffs[buff_slot].spellid; + if ( + IsValidSpell(current_spell_id) && + IsDetrimentalSpell(current_spell_id) + ) { + BuffFadeBySlot(buff_slot, false); + recalc_bonus = true; + } + } + + if (recalc_bonus) { + CalcBonuses(); + } +} + +void Mob::BuffFadeDetrimentalByCaster(Mob *caster) +{ + if(!caster) { + return; + } + + bool recalc_bonus = false; + int buff_count = GetMaxTotalSlots(); + for (int buff_slot = 0; buff_slot < buff_count; buff_slot++) { + auto current_spell_id = buffs[buff_slot].spellid; + if ( + IsValidSpell(current_spell_id) && + IsDetrimentalSpell(current_spell_id) && + caster->GetID() == buffs[buff_slot].casterid + ) { + BuffFadeBySlot(buff_slot, false); + recalc_bonus = true; + } + } + + if (recalc_bonus) { + CalcBonuses(); + } +} + +void Mob::BuffFadeBySitModifier() +{ + bool recalc_bonus = false; + int buff_count = GetMaxTotalSlots(); + for (int buff_slot = 0; buff_slot < buff_count; buff_slot++) { + auto current_spell_id = buffs[buff_slot].spellid; + if ( + IsValidSpell(current_spell_id) && + spells[current_spell_id].disallow_sit + ) { + BuffFadeBySlot(buff_slot, false); + recalc_bonus = true; + } + } + + if (recalc_bonus) { + CalcBonuses(); + } +} + +void Mob::BuffFadeBySpellID(uint16 spell_id) +{ + bool recalc_bonus = false; + int buff_count = GetMaxTotalSlots(); + for (int buff_slot = 0; buff_slot < buff_count; buff_slot++) { + if (buffs[buff_slot].spellid == spell_id) { + BuffFadeBySlot(buff_slot, false); + recalc_bonus = true; + } + } + + if (recalc_bonus) { + CalcBonuses(); + } } void Mob::BuffFadeBySpellIDAndCaster(uint16 spell_id, uint16 caster_id) { bool recalc_bonus = false; auto buff_count = GetMaxTotalSlots(); - for (int i = 0; i < buff_count; ++i) { - if (buffs[i].spellid == spell_id && buffs[i].casterid == caster_id) { - BuffFadeBySlot(i, false); + for (int buff_slot = 0; buff_slot < buff_count; buff_slot++) { + if ( + buffs[buff_slot].spellid == spell_id && + buffs[buff_slot].casterid == caster_id + ) { + BuffFadeBySlot(buff_slot, false); recalc_bonus = true; } } - if (recalc_bonus) + if (recalc_bonus) { CalcBonuses(); + } } -// removes buffs containing effectid, skipping skipslot -void Mob::BuffFadeByEffect(int effectid, int skipslot) +void Mob::BuffFadeByEffect(int effect_id, int slot_to_skip) { - int i; - + bool recalc_bonus = false; int buff_count = GetMaxTotalSlots(); - for(i = 0; i < buff_count; i++) - { - if(buffs[i].spellid == SPELL_UNKNOWN) - continue; - if(IsEffectInSpell(buffs[i].spellid, effectid) && i != skipslot) - BuffFadeBySlot(i, false); + for(int buff_slot = 0; buff_slot < buff_count; buff_slot++) { + auto current_spell_id = buffs[buff_slot].spellid; + if ( + IsValidSpell(current_spell_id) && + IsEffectInSpell(current_spell_id, effect_id) && + buff_slot != slot_to_skip + ) { + BuffFadeBySlot(buff_slot, false); + recalc_bonus = true; + } } - //we tell BuffFadeBySlot not to recalc, so we can do it only once when were done - CalcBonuses(); + if (recalc_bonus) { + CalcBonuses(); + } } bool Mob::IsAffectedByBuff(uint16 spell_id) { - int buff_count = GetMaxTotalSlots(); - for (int i = 0; i < buff_count; ++i) - if (buffs[i].spellid == spell_id) - return true; - - return false; + return FindBuff(spell_id); } bool Mob::IsAffectedByBuffByGlobalGroup(GlobalGroup group) { int buff_count = GetMaxTotalSlots(); - for (int i = 0; i < buff_count; ++i) { - if (buffs[i].spellid == SPELL_UNKNOWN) - continue; - if (spells[buffs[i].spellid].spell_category == static_cast(group)) + for (int buff_slot = 0; buff_slot < buff_count; buff_slot++) { + auto current_spell_id = buffs[buff_slot].spellid; + if ( + IsValidSpell(current_spell_id) && + spells[current_spell_id].spell_category == static_cast(group) + ) { return true; + } } return false; @@ -4284,10 +4464,10 @@ bool Mob::IsImmuneToSpell(uint16 spell_id, Mob *caster) effect_index = GetSpellEffectIndex(spell_id, SE_Mez); assert(effect_index >= 0); // NPCs get to ignore the max level - if((GetLevel() > spells[spell_id].max[effect_index]) && + if((GetLevel() > spells[spell_id].max_value[effect_index]) && (!caster->IsNPC() || (caster->IsNPC() && !RuleB(Spells, NPCIgnoreBaseImmunity)))) { - LogSpells("Our level ([{}]) is higher than the limit of this Mez spell ([{}])", GetLevel(), spells[spell_id].max[effect_index]); + LogSpells("Our level ([{}]) is higher than the limit of this Mez spell ([{}])", GetLevel(), spells[spell_id].max_value[effect_index]); caster->MessageString(Chat::SpellFailure, CANNOT_MEZ_WITH_SPELL); AddToHateList(caster, 1,0,true,false,false,spell_id); return true; @@ -4328,7 +4508,7 @@ bool Mob::IsImmuneToSpell(uint16 spell_id, Mob *caster) caster->MessageString(Chat::Red, IMMUNE_FEAR); // need to verify message type, not in MQ2Cast for easy look up return true; } - else if(GetLevel() > spells[spell_id].max[effect_index] && spells[spell_id].max[effect_index] != 0) + else if(GetLevel() > spells[spell_id].max_value[effect_index] && spells[spell_id].max_value[effect_index] != 0) { LogSpells("Level is [{}], cannot be feared by this spell", GetLevel()); caster->MessageString(Chat::Shout, FEAR_TOO_HIGH); @@ -4377,9 +4557,9 @@ bool Mob::IsImmuneToSpell(uint16 spell_id, Mob *caster) // check level limit of charm spell effect_index = GetSpellEffectIndex(spell_id, SE_Charm); assert(effect_index >= 0); - if(GetLevel() > spells[spell_id].max[effect_index] && spells[spell_id].max[effect_index] != 0) + if(GetLevel() > spells[spell_id].max_value[effect_index] && spells[spell_id].max_value[effect_index] != 0) { - LogSpells("Our level ([{}]) is higher than the limit of this Charm spell ([{}])", GetLevel(), spells[spell_id].max[effect_index]); + LogSpells("Our level ([{}]) is higher than the limit of this Charm spell ([{}])", GetLevel(), spells[spell_id].max_value[effect_index]); caster->MessageString(Chat::Red, CANNOT_CHARM_YET); // need to verify message type, not in MQ2Cast for easy look up AddToHateList(caster, 1,0,true,false,false,spell_id); return true; @@ -4498,14 +4678,30 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use } //Get resist modifier and adjust it based on focus 2 resist about eq to 1% resist chance - int resist_modifier = (use_resist_override) ? resist_override : spells[spell_id].ResistDiff; + int resist_modifier = 0; + if (use_resist_override) { + resist_modifier = resist_override; + } else { + // PVP, we don't have the normal per_level or cap stuff implemented ... so ahh do that + // and make sure the PVP versions are also handled. + if (IsClient() && caster->IsClient()) { + resist_modifier = spells[spell_id].pvp_resist_base; + } else { + resist_modifier = spells[spell_id].resist_difficulty; + } + } if(caster->GetSpecialAbility(CASTING_RESIST_DIFF)) resist_modifier += caster->GetSpecialAbilityParam(CASTING_RESIST_DIFF, 0); int focus_resist = caster->GetFocusEffect(focusResistRate, spell_id); + resist_modifier -= 2 * focus_resist; + int focus_incoming_resist = GetFocusEffect(focusFcResistIncoming, spell_id, caster); + + resist_modifier -= focus_incoming_resist; + //Check for fear resist bool IsFear = false; if(IsFearSpell(spell_id)) @@ -4548,7 +4744,7 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use // JULY 24, 2002 changes int level = GetLevel(); - if (IsPetOwnerClient() && caster->IsNPC() && !caster->IsPetOwnerClient()) { + if (RuleB(Spells,July242002PetResists) && IsPetOwnerClient() && caster->IsNPC() && !caster->IsPetOwnerClient()) { auto owner = GetOwner(); if (owner != nullptr) { target_resist = std::max(target_resist, owner->GetResist(resist_type)); @@ -4638,7 +4834,7 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use */ int16 charisma = caster->GetCHA(); - if (IsFear && (spells[spell_id].targettype != ST_Undead)){ + if (IsFear && (spells[spell_id].target_type != ST_Undead)){ if (charisma < 100) resist_modifier -= 20; @@ -4676,14 +4872,14 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use resist_chance = mod_spell_resist(resist_chance, level_mod, resist_modifier, target_resist, resist_type, spell_id, caster); //Do our min and max resist checks. - if(resist_chance > spells[spell_id].MaxResist && spells[spell_id].MaxResist != 0) + if(resist_chance > spells[spell_id].max_resist && spells[spell_id].max_resist != 0) { - resist_chance = spells[spell_id].MaxResist; + resist_chance = spells[spell_id].max_resist; } - if(resist_chance < spells[spell_id].MinResist && spells[spell_id].MinResist != 0) + if(resist_chance < spells[spell_id].min_resist && spells[spell_id].min_resist != 0) { - resist_chance = spells[spell_id].MinResist; + resist_chance = spells[spell_id].min_resist; } //Average charm duration agianst mobs with 0% chance to resist on LIVE is ~ 68 ticks. @@ -4835,7 +5031,7 @@ int16 Mob::CalcFearResistChance() */ float Mob::GetAOERange(uint16 spell_id) { - float range = spells[spell_id].aoerange; + float range = spells[spell_id].aoe_range; /** * For TGB @@ -4930,6 +5126,7 @@ void Mob::SendSpellBarEnable(uint16 spell_id) manachange->spell_id = spell_id; manachange->stamina = CastToClient()->GetEndurance(); manachange->keepcasting = 0; + manachange->slot = CastToClient()->FindMemmedSpellBySpellID(spell_id); outapp->priority = 6; CastToClient()->QueuePacket(outapp); safe_delete(outapp); @@ -4941,11 +5138,13 @@ void Mob::Stun(int duration) if(stunned && stunned_timer.GetRemainingTime() > uint32(duration)) return; - if(IsValidSpell(casting_spell_id) && !spells[casting_spell_id].uninterruptable) { + auto spell_id = bardsong ? bardsong : casting_spell_id; + + if(IsValidSpell(spell_id) && !spells[spell_id].uninterruptable) { int persistent_casting = spellbonuses.PersistantCasting + itembonuses.PersistantCasting + aabonuses.PersistantCasting; if(zone->random.Int(0,99) > persistent_casting) - InterruptSpell(); + InterruptSpell(spell_id); } if(duration > 0) @@ -5001,8 +5200,10 @@ void Mob::Mesmerize() { mezzed = true; - if (casting_spell_id) - InterruptSpell(); + auto spell_id = bardsong ? bardsong : casting_spell_id; + + if (spell_id) + InterruptSpell(spell_id); StopNavigation(); } @@ -5056,121 +5257,147 @@ void Client::MakeBuffFadePacket(uint16 spell_id, int slot_id, bool send_message) void Client::MemSpell(uint16 spell_id, int slot, bool update_client) { - if(slot >= EQ::spells::SPELL_GEM_COUNT || slot < 0) + if (slot >= EQ::spells::SPELL_GEM_COUNT || slot < 0) { return; + } - if(update_client) - { - if(m_pp.mem_spells[slot] != 0xFFFFFFFF) + if(update_client) { + if (IsValidSpell(m_pp.mem_spells[slot])) { UnmemSpell(slot, update_client); + } } m_pp.mem_spells[slot] = spell_id; LogSpells("Spell [{}] memorized into slot [{}]", spell_id, slot); - database.SaveCharacterMemorizedSpell(this->CharacterID(), m_pp.mem_spells[slot], slot); + database.SaveCharacterMemorizedSpell(CharacterID(), m_pp.mem_spells[slot], slot); - if(update_client) - { + if(update_client) { MemorizeSpell(slot, spell_id, memSpellMemorize); } } void Client::UnmemSpell(int slot, bool update_client) { - if(slot > EQ::spells::SPELL_GEM_COUNT || slot < 0) + if (slot >= EQ::spells::SPELL_GEM_COUNT || slot < 0) { return; + } LogSpells("Spell [{}] forgotten from slot [{}]", m_pp.mem_spells[slot], slot); m_pp.mem_spells[slot] = 0xFFFFFFFF; - database.DeleteCharacterMemorizedSpell(this->CharacterID(), m_pp.mem_spells[slot], slot); + database.DeleteCharacterMemorizedSpell(CharacterID(), m_pp.mem_spells[slot], slot); - if(update_client) - { + if(update_client) { MemorizeSpell(slot, m_pp.mem_spells[slot], memSpellForget); } } void Client::UnmemSpellBySpellID(int32 spell_id) { - for(int i = 0; i < EQ::spells::SPELL_GEM_COUNT; i++) { - if(m_pp.mem_spells[i] == spell_id) { - UnmemSpell(i, true); - break; - } + auto spell_gem = FindMemmedSpellBySpellID(spell_id); + if (spell_gem >= EQ::spells::SPELL_GEM_COUNT || spell_gem < 0) { + return; } + + UnmemSpell(spell_gem); } void Client::UnmemSpellAll(bool update_client) { - int i; - - for(i = 0; i < EQ::spells::SPELL_GEM_COUNT; i++) - if(m_pp.mem_spells[i] != 0xFFFFFFFF) - UnmemSpell(i, update_client); + for (int spell_gem = 0; spell_gem < EQ::spells::SPELL_GEM_COUNT; spell_gem++) { + if (IsValidSpell(m_pp.mem_spells[spell_gem])) { + UnmemSpell(spell_gem, update_client); + } + } } uint32 Client::GetSpellIDByBookSlot(int book_slot) { if (book_slot <= EQ::spells::SPELLBOOK_SIZE) { return GetSpellByBookSlot(book_slot); } - return -1; //default + return -1; +} + +int Client::FindEmptyMemSlot() { + for (int spell_gem = 0; spell_gem < EQ::spells::SPELL_GEM_COUNT; spell_gem++) { + if (!IsValidSpell(m_pp.mem_spells[spell_gem])) { + return spell_gem; + } + } + return -1; } uint16 Client::FindMemmedSpellBySlot(int slot) { - if (m_pp.mem_spells[slot] != 0xFFFFFFFF) + if (IsValidSpell(m_pp.mem_spells[slot])) { return m_pp.mem_spells[slot]; - + } return 0; } int Client::MemmedCount() { int memmed_count = 0; - for (int i = 0; i < EQ::spells::SPELL_GEM_COUNT; i++) - if (m_pp.mem_spells[i] != 0xFFFFFFFF) + for (int spell_gem = 0; spell_gem < EQ::spells::SPELL_GEM_COUNT; spell_gem++) { + if (IsValidSpell(m_pp.mem_spells[spell_gem])) { memmed_count++; - + } + } return memmed_count; } +int Client::FindMemmedSpellBySpellID(uint16 spell_id) { + for (int spell_gem = 0; spell_gem < EQ::spells::SPELL_GEM_COUNT; spell_gem++) { + if (IsValidSpell(m_pp.mem_spells[spell_gem]) && m_pp.mem_spells[spell_gem] == spell_id) { + return spell_gem; + } + } + return -1; +} -void Client::ScribeSpell(uint16 spell_id, int slot, bool update_client) + +void Client::ScribeSpell(uint16 spell_id, int slot, bool update_client, bool defer_save) { - if(slot >= EQ::spells::SPELLBOOK_SIZE || slot < 0) + if (slot >= EQ::spells::SPELLBOOK_SIZE || slot < 0) { return; + } - if(update_client) - { - if(m_pp.spell_book[slot] != 0xFFFFFFFF) - UnscribeSpell(slot, update_client); + if (update_client) { + if (m_pp.spell_book[slot] != 0xFFFFFFFF) { + UnscribeSpell(slot, update_client, defer_save); + } } m_pp.spell_book[slot] = spell_id; - database.SaveCharacterSpell(this->CharacterID(), spell_id, slot); + + // defer save if we're bulk saving elsewhere + if (!defer_save) { + database.SaveCharacterSpell(this->CharacterID(), spell_id, slot); + } LogSpells("Spell [{}] scribed into spell book slot [{}]", spell_id, slot); - if(update_client) - { + if (update_client) { MemorizeSpell(slot, spell_id, memSpellScribing); } } -void Client::UnscribeSpell(int slot, bool update_client) +void Client::UnscribeSpell(int slot, bool update_client, bool defer_save) { - if(slot >= EQ::spells::SPELLBOOK_SIZE || slot < 0) + if (slot >= EQ::spells::SPELLBOOK_SIZE || slot < 0) { return; + } LogSpells("Spell [{}] erased from spell book slot [{}]", m_pp.spell_book[slot], slot); m_pp.spell_book[slot] = 0xFFFFFFFF; - database.DeleteCharacterSpell(this->CharacterID(), m_pp.spell_book[slot], slot); - if(update_client && slot < EQ::spells::DynamicLookup(ClientVersion(), GetGM())->SpellbookSize) - { - auto outapp = new EQApplicationPacket(OP_DeleteSpell, sizeof(DeleteSpell_Struct)); - DeleteSpell_Struct* del = (DeleteSpell_Struct*)outapp->pBuffer; + if (!defer_save) { + database.DeleteCharacterSpell(this->CharacterID(), m_pp.spell_book[slot], slot); + } + + if (update_client && slot < EQ::spells::DynamicLookup(ClientVersion(), GetGM())->SpellbookSize) { + auto outapp = new EQApplicationPacket(OP_DeleteSpell, sizeof(DeleteSpell_Struct)); + DeleteSpell_Struct *del = (DeleteSpell_Struct *) outapp->pBuffer; del->spell_slot = slot; - del->success = 1; + del->success = 1; QueuePacket(outapp); safe_delete(outapp); } @@ -5178,36 +5405,53 @@ void Client::UnscribeSpell(int slot, bool update_client) void Client::UnscribeSpellAll(bool update_client) { - for(int i = 0; i < EQ::spells::SPELLBOOK_SIZE; i++) - { - if(m_pp.spell_book[i] != 0xFFFFFFFF) - UnscribeSpell(i, update_client); + for (int i = 0; i < EQ::spells::SPELLBOOK_SIZE; i++) { + if (m_pp.spell_book[i] != 0xFFFFFFFF) { + UnscribeSpell(i, update_client, true); + } } + + // bulk save at end (this will only delete) + SaveSpells(); } -void Client::UntrainDisc(int slot, bool update_client) +void Client::UntrainDisc(int slot, bool update_client, bool defer_save) { - if(slot >= MAX_PP_DISCIPLINES || slot < 0) + if (slot >= MAX_PP_DISCIPLINES || slot < 0) { return; + } LogSpells("Discipline [{}] untrained from slot [{}]", m_pp.disciplines.values[slot], slot); m_pp.disciplines.values[slot] = 0; - database.DeleteCharacterDisc(this->CharacterID(), slot); - if(update_client) - { + if (!defer_save) { + database.DeleteCharacterDisc(this->CharacterID(), slot); + } + + if (update_client) { SendDisciplineUpdate(); } } void Client::UntrainDiscAll(bool update_client) { - int i; + for (int i = 0; i < MAX_PP_DISCIPLINES; i++) { + if (m_pp.disciplines.values[i] != 0) { + UntrainDisc(i, update_client, true); + } + } - for(i = 0; i < MAX_PP_DISCIPLINES; i++) - { - if(m_pp.disciplines.values[i] != 0) - UntrainDisc(i, update_client); + // bulk delete / save + SaveDisciplines(); +} + +void Client::UntrainDiscBySpellID(uint16 spell_id, bool update_client) +{ + for (int slot = 0; slot < MAX_PP_DISCIPLINES; slot++) { + if (m_pp.disciplines.values[slot] == spell_id) { + UntrainDisc(slot, update_client); + return; + } } } @@ -5229,6 +5473,27 @@ int Client::FindSpellBookSlotBySpellID(uint16 spellid) { return -1; //default } +uint32 Client::GetHighestScribedSpellinSpellGroup(uint32 spell_group) +{ + //Typical live spells follow 1/5/10 rank value for actual ranks 1/2/3, but this can technically be set as anything. + + int highest_rank = 0; //highest ranked found in spellgroup + uint32 highest_spell_id = 0; //spell_id of the highest ranked spell you have scribed in that spell rank. + + for (int i = 0; i < EQ::spells::SPELLBOOK_SIZE; i++) { + + if (IsValidSpell(m_pp.spell_book[i])) { + if (spells[m_pp.spell_book[i]].spell_group == spell_group) { + if (highest_rank < spells[m_pp.spell_book[i]].rank) { + highest_rank = spells[m_pp.spell_book[i]].rank; + highest_spell_id = m_pp.spell_book[i]; + } + } + } + } + return highest_spell_id; +} + bool Client::SpellGlobalCheck(uint16 spell_id, uint32 char_id) { std::string spell_global_name; int spell_global_value; @@ -5308,7 +5573,7 @@ bool Client::SpellBucketCheck(uint16 spell_id, uint32 char_id) { if (results.RowCount() != 1) return true; - + auto row = results.begin(); spell_bucket_name = row[0]; spell_bucket_value = atoi(row[1]); @@ -5359,7 +5624,7 @@ int16 Mob::GetBuffSlotFromType(uint16 type) { for (int i = 0; i < buff_count; i++) { if (buffs[i].spellid != SPELL_UNKNOWN) { for (int j = 0; j < EFFECT_COUNT; j++) { - if (spells[buffs[i].spellid].effectid[j] == type ) + if (spells[buffs[i].spellid].effect_id[j] == type ) return i; } } @@ -5382,11 +5647,11 @@ bool Mob::FindType(uint16 type, bool bOffensive, uint16 threshold) { for (int j = 0; j < EFFECT_COUNT; j++) { // adjustments necessary for offensive npc casting behavior if (bOffensive) { - if (spells[buffs[i].spellid].effectid[j] == type) { + if (spells[buffs[i].spellid].effect_id[j] == type) { int16 value = - CalcSpellEffectValue_formula(spells[buffs[i].spellid].buffdurationformula, - spells[buffs[i].spellid].base[j], - spells[buffs[i].spellid].max[j], + CalcSpellEffectValue_formula(spells[buffs[i].spellid].buff_duration_formula, + spells[buffs[i].spellid].base_value[j], + spells[buffs[i].spellid].max_value[j], buffs[i].casterlevel, buffs[i].spellid); Log(Logs::General, Logs::Normal, "FindType: type = %d; value = %d; threshold = %d", @@ -5395,7 +5660,7 @@ bool Mob::FindType(uint16 type, bool bOffensive, uint16 threshold) { return true; } } else { - if (spells[buffs[i].spellid].effectid[j] == type ) + if (spells[buffs[i].spellid].effect_id[j] == type ) return true; } } @@ -5406,27 +5671,38 @@ bool Mob::FindType(uint16 type, bool bOffensive, uint16 threshold) { bool Mob::IsCombatProc(uint16 spell_id) { - if (RuleB(Spells, FocusCombatProcs)) + if (RuleB(Spells, FocusCombatProcs)) { return false; + } - if(spell_id == SPELL_UNKNOWN) + if (spell_id == SPELL_UNKNOWN) { return(false); + } + /* + Procs that originate from casted spells are still limited by SPA 311 (~Kayen confirmed on live 2/4/22) + */ + for (int i = 0; i < MAX_PROCS; i++) { + if (PermaProcs[i].spellID == spell_id || + SpellProcs[i].spellID == spell_id || + RangedProcs[i].spellID == spell_id || + DefensiveProcs[i].spellID == spell_id) { + return true; + } + } - if ((spells[spell_id].cast_time == 0) && (spells[spell_id].recast_time == 0) && (spells[spell_id].recovery_time == 0)) - { - - for (int i = 0; i < MAX_PROCS; i++){ - if (PermaProcs[i].spellID == spell_id || SpellProcs[i].spellID == spell_id - || RangedProcs[i].spellID == spell_id){ + if (IsClient()) { + for (int i = 0; i < MAX_AA_PROCS; i += 4) { + if (aabonuses.SpellProc[i + 1] == spell_id || + aabonuses.RangedProc[i + 1] == spell_id || + aabonuses.DefensiveProc[i + 1] == spell_id) { return true; } } } - return false; } -bool Mob::AddProcToWeapon(uint16 spell_id, bool bPerma, uint16 iChance, uint16 base_spell_id, int level_override) { +bool Mob::AddProcToWeapon(uint16 spell_id, bool bPerma, uint16 iChance, uint16 base_spell_id, int level_override, uint32 proc_reuse_time) { if(spell_id == SPELL_UNKNOWN) return(false); @@ -5438,8 +5714,8 @@ bool Mob::AddProcToWeapon(uint16 spell_id, bool bPerma, uint16 iChance, uint16 b PermaProcs[i].chance = iChance; PermaProcs[i].base_spellID = base_spell_id; PermaProcs[i].level_override = level_override; + PermaProcs[i].proc_reuse_time = proc_reuse_time; LogSpells("Added permanent proc spell [{}] with chance [{}] to slot [{}]", spell_id, iChance, i); - return true; } } @@ -5453,6 +5729,7 @@ bool Mob::AddProcToWeapon(uint16 spell_id, bool bPerma, uint16 iChance, uint16 b SpellProcs[i].spellID = spell_id; SpellProcs[i].chance = iChance; SpellProcs[i].level_override = level_override; + SpellProcs[i].proc_reuse_time = proc_reuse_time; Log(Logs::Detail, Logs::Spells, "Replaced poison-granted proc spell %d with chance %d to slot %d", spell_id, iChance, i); return true; } @@ -5469,6 +5746,7 @@ bool Mob::AddProcToWeapon(uint16 spell_id, bool bPerma, uint16 iChance, uint16 b SpellProcs[i].chance = iChance; SpellProcs[i].base_spellID = base_spell_id;; SpellProcs[i].level_override = level_override; + SpellProcs[i].proc_reuse_time = proc_reuse_time; LogSpells("Added [{}]-granted proc spell [{}] with chance [{}] to slot [{}]", (base_spell_id == POISON_PROC) ? "poison" : "spell", spell_id, iChance, i); return true; } @@ -5485,13 +5763,14 @@ bool Mob::RemoveProcFromWeapon(uint16 spell_id, bool bAll) { SpellProcs[i].chance = 0; SpellProcs[i].base_spellID = SPELL_UNKNOWN; SpellProcs[i].level_override = -1; + SpellProcs[i].proc_reuse_time = 0; LogSpells("Removed proc [{}] from slot [{}]", spell_id, i); } } return true; } -bool Mob::AddDefensiveProc(uint16 spell_id, uint16 iChance, uint16 base_spell_id) +bool Mob::AddDefensiveProc(uint16 spell_id, uint16 iChance, uint16 base_spell_id, uint32 proc_reuse_time) { if(spell_id == SPELL_UNKNOWN) return(false); @@ -5502,6 +5781,7 @@ bool Mob::AddDefensiveProc(uint16 spell_id, uint16 iChance, uint16 base_spell_id DefensiveProcs[i].spellID = spell_id; DefensiveProcs[i].chance = iChance; DefensiveProcs[i].base_spellID = base_spell_id; + DefensiveProcs[i].proc_reuse_time = proc_reuse_time; LogSpells("Added spell-granted defensive proc spell [{}] with chance [{}] to slot [{}]", spell_id, iChance, i); return true; } @@ -5517,13 +5797,14 @@ bool Mob::RemoveDefensiveProc(uint16 spell_id, bool bAll) DefensiveProcs[i].spellID = SPELL_UNKNOWN; DefensiveProcs[i].chance = 0; DefensiveProcs[i].base_spellID = SPELL_UNKNOWN; + DefensiveProcs[i].proc_reuse_time = 0; LogSpells("Removed defensive proc [{}] from slot [{}]", spell_id, i); } } return true; } -bool Mob::AddRangedProc(uint16 spell_id, uint16 iChance, uint16 base_spell_id) +bool Mob::AddRangedProc(uint16 spell_id, uint16 iChance, uint16 base_spell_id, uint32 proc_reuse_time) { if(spell_id == SPELL_UNKNOWN) return(false); @@ -5534,6 +5815,7 @@ bool Mob::AddRangedProc(uint16 spell_id, uint16 iChance, uint16 base_spell_id) RangedProcs[i].spellID = spell_id; RangedProcs[i].chance = iChance; RangedProcs[i].base_spellID = base_spell_id; + RangedProcs[i].proc_reuse_time = proc_reuse_time; LogSpells("Added spell-granted ranged proc spell [{}] with chance [{}] to slot [{}]", spell_id, iChance, i); return true; } @@ -5548,7 +5830,8 @@ bool Mob::RemoveRangedProc(uint16 spell_id, bool bAll) if (bAll || RangedProcs[i].spellID == spell_id) { RangedProcs[i].spellID = SPELL_UNKNOWN; RangedProcs[i].chance = 0; - RangedProcs[i].base_spellID = SPELL_UNKNOWN;; + RangedProcs[i].base_spellID = SPELL_UNKNOWN; + RangedProcs[i].proc_reuse_time = 0; LogSpells("Removed ranged proc [{}] from slot [{}]", spell_id, i); } } @@ -5584,17 +5867,6 @@ int Mob::GetCasterLevel(uint16 spell_id) { return std::max(1, level); } -//this method does NOT tell the client to stop singing the song. -//this is NOT the right way to stop a mob from singing, use InterruptSpell -//you should really know what your doing before you call this -void Mob::_StopSong() -{ - bardsong = 0; - bardsong_target_id = 0; - bardsong_slot = CastingSlot::Gem1; - bardsong_timer.Disable(); -} - //This member function sets the buff duration on the client //however it does not work if sent quickly after an action packets, which is what one might perfer to do //Thus I use this in the buff process to update the correct duration once after casting @@ -5623,7 +5895,7 @@ void Client::SendBuffDurationPacket(Buffs_Struct &buff, int slot) else if (buff.counters) sbf->buff.counters = buff.counters; sbf->buff.player_id = buff.casterid; - sbf->buff.num_hits = buff.numhits; + sbf->buff.num_hits = buff.hit_number; sbf->buff.y = buff.caston_y; sbf->buff.x = buff.caston_x; sbf->buff.z = buff.caston_z; @@ -5649,7 +5921,7 @@ void Client::SendBuffNumHitPacket(Buffs_Struct &buff, int slot) bi->entries[0].buff_slot = slot; bi->entries[0].spell_id = buff.spellid; bi->entries[0].tics_remaining = buff.ticsremaining; - bi->entries[0].num_hits = buff.numhits; + bi->entries[0].num_hits = buff.hit_number; strn0cpy(bi->entries[0].caster, buff.caster_name, 64); bi->name_lengths = strlen(bi->entries[0].caster); FastQueuePacket(&outapp); @@ -5746,7 +6018,7 @@ EQApplicationPacket *Mob::MakeBuffsPacket(bool for_target) buff->entries[index].buff_slot = i; buff->entries[index].spell_id = buffs[i].spellid; buff->entries[index].tics_remaining = buffs[i].ticsremaining; - buff->entries[index].num_hits = buffs[i].numhits; + buff->entries[index].num_hits = buffs[i].hit_number; strn0cpy(buff->entries[index].caster, buffs[i].caster_name, 64); buff->name_lengths += strlen(buff->entries[index].caster); ++index; @@ -5839,6 +6111,144 @@ void Client::SendSpellAnim(uint16 targetid, uint16 spell_id) entity_list.QueueCloseClients(this, &app, false, RuleI(Range, SpellParticles)); } +void Client::SendItemRecastTimer(int32 recast_type, uint32 recast_delay) +{ + if (recast_type == -1) { + return; + } + + if (!recast_delay) { + recast_delay = GetPTimers().GetRemainingTime(pTimerItemStart + recast_type); + } + + if (recast_delay) { + auto outapp = new EQApplicationPacket(OP_ItemRecastDelay, sizeof(ItemRecastDelay_Struct)); + ItemRecastDelay_Struct *ird = (ItemRecastDelay_Struct *)outapp->pBuffer; + ird->recast_delay = recast_delay; + ird->recast_type = static_cast(recast_type); + QueuePacket(outapp); + safe_delete(outapp); + } +} + +void Client::SetItemRecastTimer(int32 spell_id, uint32 inventory_slot) +{ + EQ::ItemInstance *item = CastToClient()->GetInv().GetItem(inventory_slot); + + int recast_delay = 0; + int recast_type = 0; + bool from_augment = false; + + if (!item) { + return; + } + + //Check primary item. + if (item->GetItem()->RecastDelay > 0) { + recast_type = item->GetItem()->RecastType; + recast_delay = item->GetItem()->RecastDelay; + } + //Check augmenent + else{ + for (int r = EQ::invaug::SOCKET_BEGIN; r <= EQ::invaug::SOCKET_END; r++) { + const EQ::ItemInstance* aug_i = item->GetAugment(r); + + if (!aug_i) { + continue; + } + const EQ::ItemData* aug = aug_i->GetItem(); + if (!aug) { + continue; + } + + if (aug->Click.Effect == spell_id) { + recast_delay = aug_i->GetItem()->RecastDelay; + recast_type = aug_i->GetItem()->RecastType; + from_augment = true; + break; + } + } + } + //must use SPA 415 with focus (SPA 310) to reduce item recast + int reduction = GetFocusEffect(focusReduceRecastTime, spell_id); + if (reduction) { + recast_delay -= reduction; + } + + recast_delay = std::max(recast_delay, 0); + + if (recast_delay > 0) { + + GetPTimers().Start((pTimerItemStart + recast_type), static_cast(recast_delay)); + if (recast_type != -1) { + database.UpdateItemRecastTimestamps( + CharacterID(), + recast_type, + GetPTimers().Get(pTimerItemStart + recast_type)->GetReadyTimestamp() + ); + } + + if (!from_augment) { + SendItemRecastTimer(recast_type, static_cast(recast_delay)); + } + } +} + +bool Client::HasItemRecastTimer(int32 spell_id, uint32 inventory_slot) +{ + EQ::ItemInstance *item = CastToClient()->GetInv().GetItem(inventory_slot); + + int recast_delay = 0; + int recast_type = 0; + bool from_augment = false; + + if (!item) { + return false; + } + + if (!item->GetItem()) { + return false; + } + + //Check primary item. + if (item->GetItem()->RecastDelay > 0) { + recast_type = item->GetItem()->RecastType; + recast_delay = item->GetItem()->RecastDelay; + } + //Check augmenent + else { + for (int r = EQ::invaug::SOCKET_BEGIN; r <= EQ::invaug::SOCKET_END; r++) { + const EQ::ItemInstance* aug_i = item->GetAugment(r); + + if (!aug_i) { + continue; + } + const EQ::ItemData* aug = aug_i->GetItem(); + if (!aug) { + continue; + } + + if (aug->Click.Effect == spell_id) { + if (aug_i->GetItem() && aug_i->GetItem()->RecastDelay > 0) { + recast_delay = aug_i->GetItem()->RecastDelay; + recast_type = aug_i->GetItem()->RecastType; + } + break; + } + } + } + //do not check if item has no recast delay. + if (!recast_delay) { + return false; + } + //if time is not expired, then it exists and therefore we have a recast on this item. + if (!CastToClient()->GetPTimers().Expired(&database, (pTimerItemStart + recast_type), false)) { + return true; + } + + return false; +} + void Mob::CalcDestFromHeading(float heading, float distance, float MaxZDiff, float StartX, float StartY, float &dX, float &dY, float &dZ) { if (!distance) { return; } @@ -5896,13 +6306,13 @@ void Mob::BeamDirectional(uint16 spell_id, int16 resist_adjust) auto fac = (*iter)->GetReverseFactionCon(this); if (beneficial_targets) { // only affect mobs we would assist. - if (!(fac <= FACTION_AMIABLE)) { + if (!(fac <= FACTION_AMIABLY)) { ++iter; continue; } } else { // affect mobs that are on our hate list, or which have bad faction with us - if (!(CheckAggro(*iter) || fac == FACTION_THREATENLY || fac == FACTION_SCOWLS)) { + if (!(CheckAggro(*iter) || fac == FACTION_THREATENINGLY || fac == FACTION_SCOWLS)) { ++iter; continue; } @@ -5912,15 +6322,15 @@ void Mob::BeamDirectional(uint16 spell_id, int16 resist_adjust) //# shortest distance from line to target point float d = std::abs((*iter)->GetY() - m * (*iter)->GetX() - b) / sqrt(m * m + 1); - if (d <= spells[spell_id].aoerange) { + if (d <= spells[spell_id].aoe_range) { if (CheckLosFN((*iter)) || spells[spell_id].npc_no_los) { (*iter)->CalcSpellPowerDistanceMod(spell_id, 0, this); - SpellOnTarget(spell_id, (*iter), false, true, resist_adjust); + SpellOnTarget(spell_id, (*iter), 0, true, resist_adjust); maxtarget_count++; } // not sure if we need this check, but probably do, need to check if it should be default limited or not - if (spells[spell_id].aemaxtargets && maxtarget_count >= spells[spell_id].aemaxtargets) + if (spells[spell_id].aoe_max_targets && maxtarget_count >= spells[spell_id].aoe_max_targets) return; } ++iter; @@ -5948,8 +6358,8 @@ void Mob::ConeDirectional(uint16 spell_id, int16 resist_adjust) std::list targets_in_range; - entity_list.GetTargetsForConeArea(this, spells[spell_id].min_range, spells[spell_id].aoerange, - spells[spell_id].aoerange / 2, spells[spell_id].pcnpc_only_flag, targets_in_range); + entity_list.GetTargetsForConeArea(this, spells[spell_id].min_range, spells[spell_id].aoe_range, + spells[spell_id].aoe_range / 2, spells[spell_id].pcnpc_only_flag, targets_in_range); auto iter = targets_in_range.begin(); while (iter != targets_in_range.end()) { @@ -5971,13 +6381,13 @@ void Mob::ConeDirectional(uint16 spell_id, int16 resist_adjust) auto fac = (*iter)->GetReverseFactionCon(this); if (beneficial_targets) { // only affect mobs we would assist. - if (!(fac <= FACTION_AMIABLE)) { + if (!(fac <= FACTION_AMIABLY)) { ++iter; continue; } } else { // affect mobs that are on our hate list, or which have bad faction with us - if (!(CheckAggro(*iter) || fac == FACTION_THREATENLY || fac == FACTION_SCOWLS)) { + if (!(CheckAggro(*iter) || fac == FACTION_THREATENINGLY || fac == FACTION_SCOWLS)) { ++iter; continue; } @@ -5989,7 +6399,7 @@ void Mob::ConeDirectional(uint16 spell_id, int16 resist_adjust) (heading_to_target >= 0.0f && heading_to_target <= angle_end)) { if (CheckLosFN((*iter)) || spells[spell_id].npc_no_los) { (*iter)->CalcSpellPowerDistanceMod(spell_id, 0, this); - SpellOnTarget(spell_id, (*iter), false, true, resist_adjust); + SpellOnTarget(spell_id, (*iter), 0, true, resist_adjust); maxtarget_count++; } } @@ -5997,14 +6407,14 @@ void Mob::ConeDirectional(uint16 spell_id, int16 resist_adjust) if (heading_to_target >= angle_start && heading_to_target <= angle_end) { if (CheckLosFN((*iter)) || spells[spell_id].npc_no_los) { (*iter)->CalcSpellPowerDistanceMod(spell_id, 0, this); - SpellOnTarget(spell_id, (*iter), false, true, resist_adjust); + SpellOnTarget(spell_id, (*iter), 0, true, resist_adjust); maxtarget_count++; } } } // my SHM breath could hit all 5 dummies I could summon in arena - if (spells[spell_id].aemaxtargets && maxtarget_count >= spells[spell_id].aemaxtargets) + if (spells[spell_id].aoe_max_targets && maxtarget_count >= spells[spell_id].aoe_max_targets) return; ++iter; @@ -6033,4 +6443,239 @@ bool Client::IsLinkedSpellReuseTimerReady(uint32 timer_id) return GetPTimers().Expired(&database, pTimerLinkedSpellReuseStart + timer_id, false); } +int Client::GetNextAvailableDisciplineSlot(int starting_slot) { + for (uint32 index = starting_slot; index < MAX_PP_DISCIPLINES; index++) { + if (!IsValidSpell(GetPP().disciplines.values[index])) { + return index; + } + } + return -1; // Return -1 if No Slots open +} + +void Client::ResetCastbarCooldownBySlot(int slot) { + if (slot < 0) { + for (unsigned int i = 0; i < EQ::spells::SPELL_GEM_COUNT; ++i) { + if(IsValidSpell(m_pp.mem_spells[i])) { + m_pp.spellSlotRefresh[i] = 1; + GetPTimers().Clear(&database, (pTimerSpellStart + m_pp.mem_spells[i])); + if (!IsLinkedSpellReuseTimerReady(spells[m_pp.mem_spells[i]].timer_id)) { + GetPTimers().Clear(&database, (pTimerLinkedSpellReuseStart + spells[m_pp.mem_spells[i]].timer_id)); + } + if (spells[m_pp.mem_spells[i]].timer_id > 0 && spells[m_pp.mem_spells[i]].timer_id < MAX_DISCIPLINE_TIMERS) { + SetLinkedSpellReuseTimer(spells[m_pp.mem_spells[i]].timer_id, 0); + } + SendSpellBarEnable(m_pp.mem_spells[i]); + } + } + } else if (slot < EQ::spells::SPELL_GEM_COUNT) { + if(IsValidSpell(m_pp.mem_spells[slot])) { + m_pp.spellSlotRefresh[slot] = 1; + GetPTimers().Clear(&database, (pTimerSpellStart + m_pp.mem_spells[slot])); + if (!IsLinkedSpellReuseTimerReady(spells[m_pp.mem_spells[slot]].timer_id)) { + GetPTimers().Clear(&database, (pTimerLinkedSpellReuseStart + spells[m_pp.mem_spells[slot]].timer_id)); + + } + if (spells[m_pp.mem_spells[slot]].timer_id > 0 && spells[m_pp.mem_spells[slot]].timer_id < MAX_DISCIPLINE_TIMERS) { + SetLinkedSpellReuseTimer(spells[m_pp.mem_spells[slot]].timer_id, 0); + } + SendSpellBarEnable(m_pp.mem_spells[slot]); + } + } +} + +void Client::ResetAllCastbarCooldowns() { + for (unsigned int i = 0; i < EQ::spells::SPELL_GEM_COUNT; ++i) { + if(IsValidSpell(m_pp.mem_spells[i])) { + m_pp.spellSlotRefresh[i] = 1; + GetPTimers().Clear(&database, (pTimerSpellStart + m_pp.mem_spells[i])); + if (!IsLinkedSpellReuseTimerReady(spells[m_pp.mem_spells[i]].timer_id)) { + GetPTimers().Clear(&database, (pTimerLinkedSpellReuseStart + spells[m_pp.mem_spells[i]].timer_id)); + } + if (spells[m_pp.mem_spells[i]].timer_id > 0 && spells[m_pp.mem_spells[i]].timer_id < MAX_DISCIPLINE_TIMERS) { + SetLinkedSpellReuseTimer(spells[m_pp.mem_spells[i]].timer_id, 0); + } + SendSpellBarEnable(m_pp.mem_spells[i]); + } + } +} + +void Client::ResetCastbarCooldownBySpellID(uint32 spell_id) { + for (unsigned int i = 0; i < EQ::spells::SPELL_GEM_COUNT; ++i) { + if(IsValidSpell(m_pp.mem_spells[i]) && m_pp.mem_spells[i] == spell_id) { + m_pp.spellSlotRefresh[i] = 1; + GetPTimers().Clear(&database, (pTimerSpellStart + m_pp.mem_spells[i])); + if (!IsLinkedSpellReuseTimerReady(spells[m_pp.mem_spells[i]].timer_id)) { + GetPTimers().Clear(&database, (pTimerLinkedSpellReuseStart + spells[m_pp.mem_spells[i]].timer_id)); + } + if (spells[m_pp.mem_spells[i]].timer_id > 0 && spells[m_pp.mem_spells[i]].timer_id < MAX_DISCIPLINE_TIMERS) { + SetLinkedSpellReuseTimer(spells[m_pp.mem_spells[i]].timer_id, 0); + } + SendSpellBarEnable(m_pp.mem_spells[i]); + break; + } + } +} + +bool Mob::IsActiveBardSong(int32 spell_id) { + + if (spell_id == bardsong) { + return true; + } + return false; +} + +void Mob::DoBardCastingFromItemClick(bool is_casting_bard_song, uint32 cast_time, int32 spell_id, uint16 target_id, EQ::spells::CastingSlot slot, uint32 item_slot, uint32 recast_type, uint32 recast_delay) +{ + /* + Known bug: When a bard uses an augment with a clicky that has a cast time, the cast won't display. This issue only affects bards. + */ + if (is_casting_bard_song) { + //For spells with cast times. Cancel song cast, stop pusling and start item cast. + if (cast_time != 0) { + EQApplicationPacket *outapp = nullptr; + outapp = new EQApplicationPacket(OP_InterruptCast, sizeof(InterruptCast_Struct)); + InterruptCast_Struct* ic = (InterruptCast_Struct*)outapp->pBuffer; + ic->messageid = SONG_ENDS; + ic->spawnid = GetID(); + outapp->priority = 5; + CastToClient()->QueuePacket(outapp); + safe_delete(outapp); + + SendSpellBarDisable(); + ZeroCastingVars(); + ZeroBardPulseVars(); + } + } + + if (cast_time != 0) { + CastSpell(spell_id, target_id, CastingSlot::Item, cast_time, 0, 0, item_slot); + } + //Instant cast items do not stop bard songs or interrupt casting. + else if (CheckItemRaceClassDietyRestrictionsOnCast(item_slot) && DoCastingChecksOnCaster(spell_id)) { + int16 DeleteChargeFromSlot = GetItemSlotToConsumeCharge(spell_id, item_slot); + if (SpellFinished(spell_id, entity_list.GetMob(target_id), CastingSlot::Item, 0, item_slot)) { + if (IsClient() && DeleteChargeFromSlot >= 0) { + CastToClient()->DeleteItemInInventory(DeleteChargeFromSlot, 1, true); + } + } + } +} + +int16 Mob::GetItemSlotToConsumeCharge(int32 spell_id, uint32 inventory_slot) +{ + int16 DeleteChargeFromSlot = -1; + + if (!IsClient() || inventory_slot == 0xFFFFFFFF) { + return DeleteChargeFromSlot; + } + + EQ::ItemInstance *item = nullptr; + item = CastToClient()->GetInv().GetItem(inventory_slot); + + bool fromaug = false; + EQ::ItemData* augitem = nullptr; + + while (true) { + if (item == nullptr) + break; + + for (int r = EQ::invaug::SOCKET_BEGIN; r <= EQ::invaug::SOCKET_END; r++) { + const EQ::ItemInstance* aug_i = item->GetAugment(r); + + if (!aug_i) { + continue; + } + const EQ::ItemData* aug = aug_i->GetItem(); + if (!aug) { + continue; + } + if (aug->Click.Effect == spell_id){ + fromaug = true; + break; + } + } + + break; + } + + if (item && item->IsClassCommon() && (item->GetItem()->Click.Effect == spell_id) && item->GetCharges() || fromaug){ + int16 charges = item->GetItem()->MaxCharges; + + if (fromaug) { charges = -1; } //Don't destroy the parent item + + if (charges > -1) { // charged item, expend a charge + LogSpells("Spell [{}]: Consuming a charge from item [{}] ([{}]) which had [{}]/[{}] charges", spell_id, item->GetItem()->Name, item->GetItem()->ID, item->GetCharges(), item->GetItem()->MaxCharges); + DeleteChargeFromSlot = inventory_slot; + } + else { + LogSpells("Spell [{}]: Cast from unlimited charge item [{}] ([{}]) ([{}] charges)", spell_id, item->GetItem()->Name, item->GetItem()->ID, item->GetItem()->MaxCharges); + } + } + else{ + LogSpells("Item used to cast spell [{}] was missing from inventory slot [{}] after casting!", spell_id, inventory_slot); + Message(Chat::Red, "Casting Error: Active casting item not found in inventory slot %i", inventory_slot); + InterruptSpell(); + return DeleteChargeFromSlot; + } + return DeleteChargeFromSlot; +} + +bool Mob::CheckItemRaceClassDietyRestrictionsOnCast(uint32 inventory_slot) { + + if (inventory_slot == 0xFFFFFFFF) { + return false; + } + + //Added to prevent MQ2 exploitation of equipping normally-unequippable/clickable items with effects and clicking them for benefits. + EQ::ItemInstance *itm = CastToClient()->GetInv().GetItem(inventory_slot); + int bitmask = 1; + bitmask = bitmask << (CastToClient()->GetClass() - 1); + if (itm && itm->GetItem()->Classes != 65535) { + if ((itm->GetItem()->Click.Type == EQ::item::ItemEffectEquipClick) && !(itm->GetItem()->Classes & bitmask)) { + if (CastToClient()->ClientVersion() < EQ::versions::ClientVersion::SoF) { + // They are casting a spell from an item that requires equipping but shouldn't let them equip it + LogError("HACKER: [{}] (account: [{}]) attempted to click an equip-only effect on item [{}] (id: [{}]) which they shouldn't be able to equip!", + CastToClient()->GetCleanName(), CastToClient()->AccountName(), itm->GetItem()->Name, itm->GetItem()->ID); + database.SetHackerFlag(CastToClient()->AccountName(), CastToClient()->GetCleanName(), "Clicking equip-only item with an invalid class"); + } + else { + MessageString(Chat::Red, MUST_EQUIP_ITEM); + } + return(false); + } + if ((itm->GetItem()->Click.Type == EQ::item::ItemEffectClick2) && !(itm->GetItem()->Classes & bitmask)) { + if (CastToClient()->ClientVersion() < EQ::versions::ClientVersion::SoF) { + // They are casting a spell from an item that they don't meet the race/class requirements to cast + LogError("HACKER: [{}] (account: [{}]) attempted to click a race/class restricted effect on item [{}] (id: [{}]) which they shouldn't be able to click!", + CastToClient()->GetCleanName(), CastToClient()->AccountName(), itm->GetItem()->Name, itm->GetItem()->ID); + database.SetHackerFlag(CastToClient()->AccountName(), CastToClient()->GetCleanName(), "Clicking race/class restricted item with an invalid class"); + } + else { + if (CastToClient()->ClientVersion() >= EQ::versions::ClientVersion::RoF) + { + // Line 181 in eqstr_us.txt was changed in RoF+ + Message(Chat::Yellow, "Your race, class, or deity cannot use this item."); + } + else + { + MessageString(Chat::Red, CANNOT_USE_ITEM); + } + } + return(false); + } + } + if (itm && (itm->GetItem()->Click.Type == EQ::item::ItemEffectEquipClick) && inventory_slot > EQ::invslot::EQUIPMENT_END) { + if (CastToClient()->ClientVersion() < EQ::versions::ClientVersion::SoF) { + // They are attempting to cast a must equip clicky without having it equipped + LogError("HACKER: [{}] (account: [{}]) attempted to click an equip-only effect on item [{}] (id: [{}]) without equiping it!", CastToClient()->GetCleanName(), CastToClient()->AccountName(), itm->GetItem()->Name, itm->GetItem()->ID); + database.SetHackerFlag(CastToClient()->AccountName(), CastToClient()->GetCleanName(), "Clicking equip-only item without equiping it"); + } + else { + MessageString(Chat::Red, MUST_EQUIP_ITEM); + } + return(false); + } + + return true; +} diff --git a/zone/string_ids.h b/zone/string_ids.h index 25c62d6b2..d5b6c52f3 100644 --- a/zone/string_ids.h +++ b/zone/string_ids.h @@ -173,6 +173,7 @@ #define GENERIC_STRINGID_SAY 554 //%1 says '%T2' #define CANNOT_WAKE 555 //%1 tells you, 'I am unable to wake %2, master.' #define SUMMONING_CORPSE_ZONE 596 //Summoning %1's corpse(s). +#define TASK_NOT_RIGHT_LEVEL 615 //You are not at the right level for this task. #define PET_HOLD_SET_ON 698 //The pet hold mode has been set to on. #define PET_HOLD_SET_OFF 699 //The pet hold mode has been set to off. #define PET_FOCUS_SET_ON 700 //The pet focus mode has been set to on. @@ -287,12 +288,16 @@ #define SUSPEND_MINION_SUSPEND 3268 //%1 tells you, 'By your command, master.' #define ONLY_SUMMONED_PETS 3269 //3269 This effect only works with summoned pets. #define SUSPEND_MINION_FIGHTING 3270 //Your pet must be at peace, first. +#define SHIELD_TARGET_NPC 3278 //You must first target a living Player Character. #define ALREADY_SHIELDED 3279 //Either you or your target is already being shielded. +#define ALREADY_SHIELDING 3280 //Either you or your target is already shielding another. #define START_SHIELDING 3281 //%1 begins to use %2 as a living shield! #define END_SHIELDING 3282 //%1 ceases protecting %2. #define TRADESKILL_MISSING_ITEM 3455 //You are missing a %1. #define TRADESKILL_MISSING_COMPONENTS 3456 //Sorry, but you don't have everything you need for this recipe in your general inventory. #define TRADESKILL_LEARN_RECIPE 3457 //You have learned the recipe %1! +#define TASK_UPDATED 3471 //Your task '%1' has been updated. +#define YOU_ASSIGNED_TASK 3472 //You have been assigned the task '%1'. #define EXPEDITION_YOU_BELONG 3500 //You cannot create this expedition since you already belong to another. #define EXPEDITION_YOU_PLAYED_HERE 3501 //You cannot create this expedition for another %1d:%2h:%3m since you have recently played here. #define REQUIRED_PLAYER_COUNT 3503 //You do not meet the player count requirement. You have %1 players. You must have at least %2 and no more than %3. @@ -367,7 +372,10 @@ #define FAILED_TAUNT 5811 //You have failed to taunt your target. #define PHYSICAL_RESIST_FAIL 5817 //Your target avoided your %1 ability. #define AA_NO_TARGET 5825 //You must first select a target for this ability! +#define YOU_RECEIVE 5941 //You receive %1. +#define NO_TASK_OFFERS 6009 //Sorry %3, I don't have anything for someone with your abilities. #define MAX_ACTIVE_TASKS 6010 //Sorry %3, you already have the maximum number of active tasks. +#define TASK_REQUEST_COOLDOWN_TIMER 6011 //Sorry, %3, but you can't request another task for %4 minutes and %5 seconds. #define FORAGE_MASTERY 6012 //Your forage mastery has enabled you to find something else! #define GUILD_BANK_CANNOT_DEPOSIT 6097 // Cannot deposit this item. Containers must be empty, and only one of each LORE and no NO TRADE or TEMPORARY items may be deposited. #define GUILD_BANK_FULL 6098 // There is no more room in the Guild Bank. @@ -416,6 +424,7 @@ #define GAIN_GROUP_LEADERSHIP_EXP 8788 // #define GAIN_RAID_LEADERSHIP_EXP 8789 // #define BUFF_MINUTES_REMAINING 8799 //%1 (%2 minutes remaining) +#define YOU_HAVE_BEEN_GIVEN 8994 //You have been given: %1 #define NO_MORE_TRAPS 9002 //You have already placed your maximum number of traps. #define FEAR_TOO_HIGH 9035 //Your target is too high of a level for your fear spell. #define SLOW_MOSTLY_SUCCESSFUL 9029 //Your spell was mostly successful. @@ -460,6 +469,7 @@ #define LEADER_OF_X_GUILD 12258 #define NOT_IN_A_GUILD 12259 #define TARGET_PLAYER_FOR_GUILD_STATUS 12260 +#define TARGET_ALREADY_IN_GROUP 12265 //% 1 is already in another group. #define GROUP_INVITEE_NOT_FOUND 12268 //You must target a player or use /invite to invite someone to your group. #define GROUP_INVITEE_SELF 12270 //12270 You cannot invite yourself. #define ALREADY_IN_PARTY 12272 //That person is already in your party. diff --git a/zone/task_client_state.cpp b/zone/task_client_state.cpp index 3ded9eb27..264ad2456 100644 --- a/zone/task_client_state.cpp +++ b/zone/task_client_state.cpp @@ -1,6 +1,7 @@ #include "../common/global_define.h" #include "../common/misc_functions.h" #include "../common/repositories/character_activities_repository.h" +#include "../common/repositories/character_task_timers_repository.h" #include "../common/repositories/character_tasks_repository.h" #include "../common/repositories/completed_tasks_repository.h" #include "../common/rulesys.h" @@ -9,8 +10,13 @@ #include "quest_parser_collection.h" #include "task_client_state.h" #include "zonedb.h" +#include "../common/shared_tasks.h" +#include "worldserver.h" +#include "dynamic_zone.h" +#include "string_ids.h" -extern QueryServ *QServ; +extern WorldServer worldserver; +extern QueryServ *QServ; ClientTaskState::ClientTaskState() { @@ -23,9 +29,13 @@ ClientTaskState::ClientTaskState() m_active_quests[i].task_id = TASKSLOTEMPTY; } + m_active_task = {}; m_active_task.slot = 0; m_active_task.task_id = TASKSLOTEMPTY; - // TODO: shared task + + m_active_shared_task = {}; + m_active_shared_task.slot = 0; + m_active_shared_task.task_id = TASKSLOTEMPTY; } ClientTaskState::~ClientTaskState() @@ -92,7 +102,7 @@ void ClientTaskState::SendTaskHistory(Client *client, int task_index) for (int i = 0; i < p_task_data->activity_count; i++) { if (m_completed_tasks[adjusted_task_index].activity_done[i]) { task_history_reply_data_1 = (TaskHistoryReplyData1_Struct *) reply; - task_history_reply_data_1->ActivityType = p_task_data->activity_information[i].activity_type; + task_history_reply_data_1->ActivityType = static_cast(p_task_data->activity_information[i].activity_type); reply = (char *) task_history_reply_data_1 + sizeof(TaskHistoryReplyData1_Struct); VARSTRUCT_ENCODE_STRING(reply, p_task_data->activity_information[i].target_name.c_str()); VARSTRUCT_ENCODE_STRING(reply, p_task_data->activity_information[i].item_list.c_str()); @@ -321,7 +331,7 @@ bool ClientTaskState::HasSlotForTask(TaskInformation *task) case TaskType::Task: return m_active_task.task_id == TASKSLOTEMPTY; case TaskType::Shared: - return false; // todo + return m_active_shared_task.task_id == TASKSLOTEMPTY; case TaskType::Quest: for (auto &active_quest : m_active_quests) { if (active_quest.task_id == TASKSLOTEMPTY) { @@ -359,20 +369,39 @@ bool ClientTaskState::UnlockActivities(int character_id, ClientTaskInformation & { bool all_activities_complete = true; + LogTasksDetail( + "[UnlockActivities] Fetching task info for character_id [{}] task [{}] slot [{}] current_step [{}] accepted_time [{}] updated [{}]", + character_id, + task_info.task_id, + task_info.slot, + task_info.current_step, + task_info.accepted_time, + task_info.updated + ); + TaskInformation *p_task_data = task_manager->m_task_data[task_info.task_id]; if (p_task_data == nullptr) { return true; } + for (int i = 0; i < p_task_data->activity_count; i++) { + if (task_info.activity[i].activity_id >= 0) { + LogTasksDetail( + "[UnlockActivities] character_id [{}] task [{}] activity_id [{}] done_count [{}] activity_state [{}] updated [{}] sequence [{}]", + character_id, + task_info.task_id, + task_info.activity[i].activity_id, + task_info.activity[i].done_count, + task_info.activity[i].activity_state, + task_info.activity[i].updated, + p_task_data->sequence_mode + ); + } + } + // On loading the client state, all activities that are not completed, are // marked as hidden. For Sequential (non-stepped) mode, we mark the first // activity_information as active if not complete. - LogTasks( - "character_id [{}] task_id [{}] sequence_mode [{}]", - character_id, - task_info.task_id, - p_task_data->sequence_mode - ); if (p_task_data->sequence_mode == ActivitiesSequential) { if (task_info.activity[0].activity_state != ActivityCompleted) { @@ -418,15 +447,18 @@ bool ClientTaskState::UnlockActivities(int character_id, ClientTaskInformation & } } - CompletedTaskInformation completed_task_information{}; - completed_task_information.task_id = task_info.task_id; - completed_task_information.completed_time = time(nullptr); + if (p_task_data->type != TaskType::Shared) { + CompletedTaskInformation completed_task_information{}; + completed_task_information.task_id = task_info.task_id; + completed_task_information.completed_time = time(nullptr); - for (int i = 0; i < p_task_data->activity_count; i++) { - completed_task_information.activity_done[i] = (task_info.activity[i].activity_state == ActivityCompleted); + for (int i = 0; i < p_task_data->activity_count; i++) { + completed_task_information.activity_done[i] = (task_info.activity[i].activity_state == + ActivityCompleted); + } + + m_completed_tasks.push_back(completed_task_information); } - - m_completed_tasks.push_back(completed_task_information); } LogTasks("Returning sequential task, AllActivitiesComplete is [{}]", all_activities_complete); @@ -505,24 +537,42 @@ bool ClientTaskState::UnlockActivities(int character_id, ClientTaskInformation & } } - CompletedTaskInformation completed_task_information{}; - completed_task_information.task_id = task_info.task_id; - completed_task_information.completed_time = time(nullptr); + if (p_task_data->type != TaskType::Shared) { + CompletedTaskInformation completed_task_information{}; + completed_task_information.task_id = task_info.task_id; + completed_task_information.completed_time = time(nullptr); - for (int activity_id = 0; activity_id < p_task_data->activity_count; activity_id++) { - completed_task_information.activity_done[activity_id] = - (task_info.activity[activity_id].activity_state == ActivityCompleted); + for (int activity_id = 0; activity_id < p_task_data->activity_count; activity_id++) { + completed_task_information.activity_done[activity_id] = + (task_info.activity[activity_id].activity_state == ActivityCompleted); + } + + m_completed_tasks.push_back(completed_task_information); } - - m_completed_tasks.push_back(completed_task_information); } return true; } // Mark all non-completed tasks in the current step as active for (int activity = 0; activity < p_task_data->activity_count; activity++) { + LogTasksDetail( + "[UnlockActivities] - Debug task [{}] activity [{}] step_number [{}] current_step [{}]", + task_info.task_id, + activity, + p_task_data->activity_information[activity].step_number, + (int) task_info.current_step + + ); + if ((p_task_data->activity_information[activity].step_number == (int) task_info.current_step) && (task_info.activity[activity].activity_state == ActivityHidden)) { + + LogTasksDetail( + "[UnlockActivities] -- Debug task [{}] activity [{}] (ActivityActive)", + task_info.task_id, + activity + ); + task_info.activity[activity].activity_state = ActivityActive; task_info.activity[activity].updated = true; } @@ -531,23 +581,17 @@ bool ClientTaskState::UnlockActivities(int character_id, ClientTaskInformation & return false; } -void ClientTaskState::UpdateTasksOnKill(Client *client, int npc_type_id) -{ - UpdateTasksByNPC(client, ActivityKill, npc_type_id); -} - bool ClientTaskState::UpdateTasksOnSpeakWith(Client *client, int npc_type_id) { - return UpdateTasksByNPC(client, ActivitySpeakWith, npc_type_id); + return UpdateTasksByNPC(client, TaskActivityType::SpeakWith, npc_type_id); } -bool ClientTaskState::UpdateTasksByNPC(Client *client, int activity_type, int npc_type_id) +bool ClientTaskState::UpdateTasksByNPC(Client *client, TaskActivityType activity_type, int npc_type_id) { int is_updating = false; - // If the client has no tasks, there is nothing further to check. - if (!task_manager || (m_active_task_count == 0 && m_active_task.task_id == TASKSLOTEMPTY)) { // could be better ... + if (!HasActiveTasks()) { return false; } @@ -583,7 +627,7 @@ bool ClientTaskState::UpdateTasksByNPC(Client *client, int activity_type, int np client->GetName(), current_task->task_id, activity_id, - activity_type, + static_cast(activity_type), npc_type_id ); continue; @@ -624,7 +668,7 @@ int ClientTaskState::ActiveSpeakTask(int npc_type_id) // This method is to be used from Perl quests only and returns the task_id of the first // active task found which has an active SpeakWith activity_information for this NPC. - if (!task_manager || (m_active_task_count == 0 && m_active_task.task_id == TASKSLOTEMPTY)) { // could be better ... + if (!HasActiveTasks()) { return 0; } @@ -648,7 +692,7 @@ int ClientTaskState::ActiveSpeakTask(int npc_type_id) if (client_activity->activity_state != ActivityActive) { continue; } - if (activity_info->activity_type != ActivitySpeakWith) { + if (activity_info->activity_type != TaskActivityType::SpeakWith) { continue; } // Is there a zone restriction on the activity_information ? @@ -670,7 +714,7 @@ int ClientTaskState::ActiveSpeakActivity(int npc_type_id, int task_id) // This method is to be used from Perl quests only and returns the activity_id of the first // active activity_information found in the specified task which is to SpeakWith this NPC. - if (!task_manager || (m_active_task_count == 0 && m_active_task.task_id == TASKSLOTEMPTY)) { // could be better ... + if (!HasActiveTasks()) { return -1; } if (task_id <= 0 || task_id >= MAXTASKS) { @@ -697,7 +741,7 @@ int ClientTaskState::ActiveSpeakActivity(int npc_type_id, int task_id) if (client_activity->activity_state != ActivityActive) { continue; } - if (activity_info->activity_type != ActivitySpeakWith) { + if (activity_info->activity_type != TaskActivityType::SpeakWith) { continue; } // Is there a zone restriction on the activity_information ? @@ -715,7 +759,7 @@ int ClientTaskState::ActiveSpeakActivity(int npc_type_id, int task_id) return 0; } -void ClientTaskState::UpdateTasksForItem(Client *client, ActivityType activity_type, int item_id, int count) +void ClientTaskState::UpdateTasksForItem(Client *client, TaskActivityType activity_type, int item_id, int count) { // This method updates the client's task activities of the specified type which relate @@ -727,11 +771,11 @@ void ClientTaskState::UpdateTasksForItem(Client *client, ActivityType activity_t LogTasks( "[UpdateTasksForItem] activity_type [{}] item_id [{}]", - activity_type, + static_cast(activity_type), item_id ); - if (!task_manager || (m_active_task_count == 0 && m_active_task.task_id == TASKSLOTEMPTY)) { // could be better ... + if (!HasActiveTasks()) { return; } @@ -758,7 +802,7 @@ void ClientTaskState::UpdateTasksForItem(Client *client, ActivityType activity_t continue; } // We are only interested in the ActivityType we were called with - if (activity_info->activity_type != (int) activity_type) { + if (activity_info->activity_type != activity_type) { continue; } // Is there a zone restriction on the activity_information ? @@ -766,7 +810,7 @@ void ClientTaskState::UpdateTasksForItem(Client *client, ActivityType activity_t LogTasks( "[UpdateTasksForItem] Error: Character [{}] activity_information type [{}] for Item [{}] failed zone check", client->GetName(), - activity_type, + static_cast(activity_type), item_id ); continue; @@ -800,7 +844,8 @@ void ClientTaskState::UpdateTasksForItem(Client *client, ActivityType activity_t void ClientTaskState::UpdateTasksOnExplore(Client *client, int explore_id) { LogTasks("[UpdateTasksOnExplore] explore_id [{}]", explore_id); - if (!task_manager || (m_active_task_count == 0 && m_active_task.task_id == TASKSLOTEMPTY)) { // could be better ... + + if (!HasActiveTasks()) { return; } @@ -827,7 +872,7 @@ void ClientTaskState::UpdateTasksOnExplore(Client *client, int explore_id) continue; } // We are only interested in explore activities - if (activity_info->activity_type != ActivityExplore) { + if (activity_info->activity_type != TaskActivityType::Explore) { continue; } if (!activity_info->CheckZone(zone->GetZoneID())) { @@ -890,7 +935,8 @@ bool ClientTaskState::UpdateTasksOnDeliver( bool is_updated = false; LogTasks("[UpdateTasksOnDeliver] [{}]", npc_type_id); - if (!task_manager || (m_active_task_count == 0 && m_active_task.task_id == TASKSLOTEMPTY)) { // could be better ... + + if (!HasActiveTasks()) { return false; } @@ -917,8 +963,8 @@ bool ClientTaskState::UpdateTasksOnDeliver( } // We are only interested in Deliver activities - if (activity_info->activity_type != ActivityDeliver && - activity_info->activity_type != ActivityGiveCash) { + if (activity_info->activity_type != TaskActivityType::Deliver && + activity_info->activity_type != TaskActivityType::GiveCash) { continue; } // Is there a zone restriction on the activity_information ? @@ -936,7 +982,7 @@ bool ClientTaskState::UpdateTasksOnDeliver( } // Is the activity_information related to these items ? // - if ((activity_info->activity_type == ActivityGiveCash) && cash) { + if ((activity_info->activity_type == TaskActivityType::GiveCash) && cash) { LogTasks("[UpdateTasksOnDeliver] Increment on GiveCash"); IncrementDoneCount(client, p_task_data, i, activity_id, cash); is_updated = true; @@ -985,7 +1031,8 @@ void ClientTaskState::UpdateTasksOnTouch(Client *client, int zone_id) // If the client has no tasks, there is nothing further to check. LogTasks("[UpdateTasksOnTouch] [{}] ", zone_id); - if (!task_manager || (m_active_task_count == 0 && m_active_task.task_id == TASKSLOTEMPTY)) { // could be better ... + + if (!HasActiveTasks()) { return; } @@ -1011,7 +1058,7 @@ void ClientTaskState::UpdateTasksOnTouch(Client *client, int zone_id) continue; } // We are only interested in touch activities - if (activity_info->activity_type != ActivityTouch) { + if (activity_info->activity_type != TaskActivityType::Touch) { continue; } if (activity_info->goal_method != METHODSINGLEID) { @@ -1048,13 +1095,58 @@ void ClientTaskState::IncrementDoneCount( bool ignore_quest_update ) { - Log(Logs::General, Logs::Tasks, "[UPDATE] IncrementDoneCount"); - auto info = GetClientTaskInfo(task_information->type, task_index); if (info == nullptr) { return; } + LogTasks( + "[IncrementDoneCount] client [{}] task_id [{}] activity_id [{}] count [{}]", + client->GetCleanName(), + info->task_id, + activity_id, + count + ); + + // shared task shim + // intercept and pass to world first before processing normally + if (!client->m_shared_task_update && task_information->type == TaskType::Shared) { + + // struct + auto pack = new ServerPacket(ServerOP_SharedTaskUpdate, sizeof(ServerSharedTaskActivityUpdate_Struct)); + auto *r = (ServerSharedTaskActivityUpdate_Struct *) pack->pBuffer; + + // fill + r->source_character_id = client->CharacterID(); + r->task_id = info->task_id; + r->activity_id = activity_id; + r->done_count = info->activity[activity_id].done_count + count; + r->ignore_quest_update = ignore_quest_update; + + LogTasksDetail( + "[IncrementDoneCount] shared_task sending client [{}] task_id [{}] activity_id [{}] count [{}] ignore_quest_update [{}]", + r->source_character_id, + r->task_id, + r->activity_id, + r->done_count, + (ignore_quest_update ? "true" : "false") + ); + + SyncSharedTaskZoneClientDoneCountState( + client, + task_information, + task_index, + activity_id, + r->done_count + ); + + // send + worldserver.SendPacket(pack); + safe_delete(pack); + + return; + } + info->activity[activity_id].done_count += count; if (info->activity[activity_id].done_count > task_information->activity_information[activity_id].goal_count) { @@ -1062,24 +1154,19 @@ void ClientTaskState::IncrementDoneCount( } if (!ignore_quest_update) { - char buf[24]; - snprintf( - buf, - 23, - "%d %d %d", + std::string export_string = fmt::format( + "{} {} {}", info->activity[activity_id].done_count, info->activity[activity_id].activity_id, info->task_id ); - buf[23] = '\0'; - parse->EventPlayer(EVENT_TASK_UPDATE, client, buf, 0); + parse->EventPlayer(EVENT_TASK_UPDATE, client, export_string, 0); } info->activity[activity_id].updated = true; // Have we reached the goal count for this activity_information ? if (info->activity[activity_id].done_count >= task_information->activity_information[activity_id].goal_count) { - Log( - Logs::General, Logs::Tasks, "[UPDATE] Done (%i) = Goal (%i) for activity_information %i", + LogTasks("[IncrementDoneCount] done_count [{}] goal_count [{}] activity_id [{}]", info->activity[activity_id].done_count, task_information->activity_information[activity_id].goal_count, activity_id @@ -1089,49 +1176,45 @@ void ClientTaskState::IncrementDoneCount( info->activity[activity_id].activity_state = ActivityCompleted; // Unlock subsequent activities for this task bool task_complete = UnlockActivities(client->CharacterID(), *info); - Log(Logs::General, Logs::Tasks, "[UPDATE] TaskCompleted is %i", task_complete); + LogTasks("[IncrementDoneCount] task_complete is [{}]", task_complete); // and by the 'Task Stage Completed' message client->SendTaskActivityComplete(info->task_id, activity_id, task_index, task_information->type); // Send the updated task/activity_information list to the client task_manager->SendSingleActiveTaskToClient(client, *info, task_complete, false); // Inform the client the task has been updated, both by a chat message - client->Message(Chat::White, "Your task '%s' has been updated.", task_information->title.c_str()); + client->MessageString(Chat::White, TASK_UPDATED, task_information->title.c_str()); - if (task_information->activity_information[activity_id].goal_method != METHODQUEST) { - if (!ignore_quest_update) { - char buf[24]; - snprintf(buf, 23, "%d %d", info->task_id, info->activity[activity_id].activity_id); - buf[23] = '\0'; - parse->EventPlayer(EVENT_TASK_STAGE_COMPLETE, client, buf, 0); - } - /* QS: PlayerLogTaskUpdates :: Update */ - if (RuleB(QueryServ, PlayerLogTaskUpdates)) { - std::string event_desc = StringFormat( - "Task Stage Complete :: taskid:%i activityid:%i donecount:%i in zoneid:%i instid:%i", - info->task_id, - info->activity[activity_id].activity_id, - info->activity[activity_id].done_count, - client->GetZoneID(), - client->GetInstanceID()); - QServ->PlayerLogEvent(Player_Log_Task_Updates, client->CharacterID(), event_desc); - } + if (!ignore_quest_update) { + std::string export_string = fmt::format( + "{} {}", + info->task_id, + info->activity[activity_id].activity_id + ); + parse->EventPlayer(EVENT_TASK_STAGE_COMPLETE, client, export_string, 0); + } + /* QS: PlayerLogTaskUpdates :: Update */ + if (RuleB(QueryServ, PlayerLogTaskUpdates)) { + std::string event_desc = StringFormat( + "Task Stage Complete :: taskid:%i activityid:%i donecount:%i in zoneid:%i instid:%i", + info->task_id, + info->activity[activity_id].activity_id, + info->activity[activity_id].done_count, + client->GetZoneID(), + client->GetInstanceID()); + QServ->PlayerLogEvent(Player_Log_Task_Updates, client->CharacterID(), event_desc); } // If this task is now complete, the Completed tasks will have been // updated in UnlockActivities. Send the completed task list to the // client. This is the same sequence the packets are sent on live. if (task_complete) { - char buf[24]; - snprintf( - buf, - 23, - "%d %d %d", + std::string export_string = fmt::format( + "{} {} {}", info->activity[activity_id].done_count, info->activity[activity_id].activity_id, info->task_id ); - buf[23] = '\0'; - parse->EventPlayer(EVENT_TASK_COMPLETE, client, buf, 0); + parse->EventPlayer(EVENT_TASK_COMPLETE, client, export_string, 0); /* QS: PlayerLogTaskUpdates :: Complete */ if (RuleB(QueryServ, PlayerLogTaskUpdates)) { @@ -1145,16 +1228,24 @@ void ClientTaskState::IncrementDoneCount( QServ->PlayerLogEvent(Player_Log_Task_Updates, client->CharacterID(), event_desc); } - task_manager->SendCompletedTasksToClient(client, this); client->SendTaskActivityComplete(info->task_id, 0, task_index, task_information->type, 0); task_manager->SaveClientState(client, this); - //c->SendTaskComplete(TaskIndex); - client->CancelTask(task_index, task_information->type); - //if(Task->reward_method != METHODQUEST) RewardTask(c, Task); + // If Experience and/or cash rewards are set, reward them from the task even if reward_method is METHODQUEST RewardTask(client, task_information); //RemoveTask(c, TaskIndex); + // add replay timer (world adds timers to shared task members) + AddReplayTimer(client, *info, *task_information); + + // shared tasks linger at the completion step and do not get removed from the task window unlike quests/task + if (task_information->type == TaskType::Shared) { + return; + } + + task_manager->SendCompletedTasksToClient(client, this); + + client->CancelTask(task_index, task_information->type); } } @@ -1164,8 +1255,7 @@ void ClientTaskState::IncrementDoneCount( client, info->task_id, activity_id, - task_index, - task_information->activity_information[activity_id].optional + task_index ); task_manager->SaveClientState(client, this); } @@ -1178,6 +1268,10 @@ void ClientTaskState::RewardTask(Client *client, TaskInformation *task_informati return; } + if (!task_information->completion_emote.empty()) { + client->Message(Chat::Yellow, task_information->completion_emote.c_str()); + } + const EQ::ItemData *item_data; std::vector reward_list; @@ -1187,7 +1281,7 @@ void ClientTaskState::RewardTask(Client *client, TaskInformation *task_informati client->SummonItem(task_information->reward_id); item_data = database.GetItem(task_information->reward_id); if (item_data) { - client->Message(Chat::Yellow, "You receive %s as a reward.", item_data->Name); + client->MessageString(Chat::Yellow, YOU_HAVE_BEEN_GIVEN, item_data->Name); } } break; @@ -1198,7 +1292,7 @@ void ClientTaskState::RewardTask(Client *client, TaskInformation *task_informati client->SummonItem(item_id); item_data = database.GetItem(item_id); if (item_data) { - client->Message(Chat::Yellow, "You receive %s as a reward.", item_data->Name); + client->MessageString(Chat::Yellow, YOU_HAVE_BEEN_GIVEN, item_data->Name); } } break; @@ -1209,13 +1303,6 @@ void ClientTaskState::RewardTask(Client *client, TaskInformation *task_informati } } - if (!task_information->completion_emote.empty()) { - client->SendColoredText( - Chat::Yellow, - task_information->completion_emote - ); - } // unsure if they use this packet or color, should work - // just use normal NPC faction ID stuff if (task_information->faction_reward) { client->SetFactionLevel( @@ -1294,6 +1381,11 @@ void ClientTaskState::RewardTask(Client *client, TaskInformation *task_informati } } + if (task_information->reward_radiant_crystals > 0 || task_information->reward_ebon_crystals > 0) + { + client->AddCrystals(task_information->reward_radiant_crystals, task_information->reward_ebon_crystals); + } + client->SendSound(); } @@ -1303,6 +1395,10 @@ bool ClientTaskState::IsTaskActive(int task_id) return true; } + if (m_active_shared_task.task_id == task_id) { + return true; + } + if (m_active_task_count == 0 || task_id == 0) { return false; } @@ -1325,10 +1421,20 @@ void ClientTaskState::FailTask(Client *client, int task_id) m_active_task_count ); + + // type: Task if (m_active_task.task_id == task_id) { - client->SendTaskFailed(task_id, 0, TaskType::Task); + client->SendTaskFailed(task_id, TASKSLOTTASK, TaskType::Task); // Remove the task from the client - client->CancelTask(0, TaskType::Task); + client->CancelTask(TASKSLOTTASK, TaskType::Task); + return; + } + + // type: Shared Task + if (m_active_shared_task.task_id == task_id) { + client->SendTaskFailed(task_id, TASKSLOTSHAREDTASK, TaskType::Shared); + // Remove the task from the client + client->CancelTask(TASKSLOTSHAREDTASK, TaskType::Shared); return; } @@ -1357,15 +1463,19 @@ bool ClientTaskState::IsTaskActivityActive(int task_id, int activity_id) if (activity_id < 0) { return false; } - if (m_active_task_count == 0 && m_active_task.task_id == TASKSLOTEMPTY) { + if (m_active_task_count == 0 && m_active_task.task_id == TASKSLOTEMPTY && + m_active_shared_task.task_id == TASKSLOTEMPTY) { return false; } int active_task_index = -1; auto task_type = TaskType::Task; - if (m_active_task.task_id == task_id) { - active_task_index = 0; + active_task_index = TASKSLOTTASK; + } + if (m_active_shared_task.task_id == task_id) { + task_type = TaskType::Shared; + active_task_index = TASKSLOTSHAREDTASK; } if (active_task_index == -1) { @@ -1426,7 +1536,8 @@ void ClientTaskState::UpdateTaskActivity( ); // Quick sanity check - if (activity_id < 0 || (m_active_task_count == 0 && m_active_task.task_id == TASKSLOTEMPTY)) { + if (activity_id < 0 || (m_active_task_count == 0 && m_active_task.task_id == TASKSLOTEMPTY && + m_active_shared_task.task_id == TASKSLOTEMPTY)) { return; } @@ -1434,7 +1545,11 @@ void ClientTaskState::UpdateTaskActivity( auto type = TaskType::Task; if (m_active_task.task_id == task_id) { - active_task_index = 0; + active_task_index = TASKSLOTTASK; + } + if (m_active_shared_task.task_id == task_id) { + type = TaskType::Shared; + active_task_index = TASKSLOTSHAREDTASK; } if (active_task_index == -1) { @@ -1494,14 +1609,19 @@ void ClientTaskState::ResetTaskActivity(Client *client, int task_id, int activit ); // Quick sanity check - if (activity_id < 0 || (m_active_task_count == 0 && m_active_task.task_id == TASKSLOTEMPTY)) { + if (activity_id < 0 || (m_active_task_count == 0 && m_active_task.task_id == TASKSLOTEMPTY && + m_active_shared_task.task_id == TASKSLOTEMPTY)) { return; } int active_task_index = -1; auto type = TaskType::Task; if (m_active_task.task_id == task_id) { - active_task_index = 0; + active_task_index = TASKSLOTTASK; + } + if (m_active_shared_task.task_id == task_id) { + type = TaskType::Shared; + active_task_index = TASKSLOTSHAREDTASK; } if (active_task_index == -1) { @@ -1556,32 +1676,67 @@ void ClientTaskState::ResetTaskActivity(Client *client, int task_id, int activit ); } +void ClientTaskState::ShowClientTaskInfoMessage(ClientTaskInformation *task, Client *c) +{ + auto task_data = task_manager->m_task_data[task->task_id]; + + c->Message(Chat::White, "------------------------------------------------"); + c->Message( + Chat::White, "# [%s] | task_id [%i] title [%s] slot (%i)", + Tasks::GetTaskTypeDescription(task_data->type).c_str(), + task->task_id, + task_data->title.c_str(), + task->slot + ); + c->Message(Chat::White, "------------------------------------------------"); + c->Message( + Chat::White, + " -- Description [%s]\n", + task_data->description.c_str() + ); + + for (int activity_id = 0; activity_id < task_manager->GetActivityCount(task->task_id); activity_id++) { + std::vector update_increments = {"1", "5", "50"}; + std::string update_saylinks; + + for (auto &increment: update_increments) { + auto task_update_saylink = EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format( + "#task update {} {} {}", + task->task_id, + task->activity[activity_id].activity_id, + increment + ), + false, + increment + ); + + update_saylinks += "[" + task_update_saylink + "] "; + } + + c->Message( + Chat::White, + " --- Update %s activity_id [%i] done_count [%i] state [%d] (%s)", + update_saylinks.c_str(), + task->activity[activity_id].activity_id, + task->activity[activity_id].done_count, + task->activity[activity_id].activity_state, + Tasks::GetActivityStateDescription(task->activity[activity_id].activity_state).c_str() + ); + } +} + void ClientTaskState::ShowClientTasks(Client *client) { client->Message(Chat::White, "------------------------------------------------"); client->Message(Chat::White, "# Task Information | Client [%s]", client->GetCleanName()); // client->Message(Chat::White, "------------------------------------------------"); if (m_active_task.task_id != TASKSLOTEMPTY) { - client->Message( - Chat::White, - "Task: %i %s", - m_active_task.task_id, - task_manager->m_task_data[m_active_task.task_id]->title.c_str() - ); - client->Message( - Chat::White, - " description: [%s]\n", - task_manager->m_task_data[m_active_task.task_id]->description.c_str() - ); - for (int activity_id = 0; activity_id < task_manager->GetActivityCount(m_active_task.task_id); activity_id++) { - client->Message( - Chat::White, - " activity_information: %2d, done_count: %2d, Status: %d (0=Hidden, 1=Active, 2=Complete)", - m_active_task.activity[activity_id].activity_id, - m_active_task.activity[activity_id].done_count, - m_active_task.activity[activity_id].activity_state - ); - } + ShowClientTaskInfoMessage(&m_active_task, client); + } + + if (m_active_shared_task.task_id != TASKSLOTEMPTY) { + ShowClientTaskInfoMessage(&m_active_shared_task, client); } for (auto &active_quest : m_active_quests) { @@ -1589,48 +1744,7 @@ void ClientTaskState::ShowClientTasks(Client *client) continue; } - client->Message(Chat::White, "------------------------------------------------"); - client->Message( - Chat::White, "# Quest | task_id [%i] title [%s]", - active_quest.task_id, - task_manager->m_task_data[active_quest.task_id]->title.c_str() - ); - client->Message(Chat::White, "------------------------------------------------"); - - client->Message( - Chat::White, - " -- Description [%s]\n", - task_manager->m_task_data[active_quest.task_id]->description.c_str() - ); - - for (int activity_id = 0; activity_id < task_manager->GetActivityCount(active_quest.task_id); activity_id++) { - std::vector update_increments = {"1", "5", "50"}; - std::string update_saylinks; - - for (auto &increment: update_increments) { - auto task_update_saylink = EQ::SayLinkEngine::GenerateQuestSaylink( - fmt::format( - "#task update {} {} {}", - active_quest.task_id, - active_quest.activity[activity_id].activity_id, - increment - ), - false, - increment - ); - - update_saylinks += "[" + task_update_saylink + "] "; - } - - client->Message( - Chat::White, - " --- activity_id [%i] done_count [%i] state [%d] (0=hidden 1=active 2=complete) | Update %s", - active_quest.activity[activity_id].activity_id, - active_quest.activity[activity_id].done_count, - active_quest.activity[activity_id].activity_state, - update_saylinks.c_str() - ); - } + ShowClientTaskInfoMessage(&active_quest, client); } client->Message(Chat::White, "------------------------------------------------"); @@ -1639,6 +1753,8 @@ void ClientTaskState::ShowClientTasks(Client *client) // TODO: Shared Task int ClientTaskState::TaskTimeLeft(int task_id) { + + // type "task" if (m_active_task.task_id == task_id) { int time_now = time(nullptr); @@ -1656,6 +1772,24 @@ int ClientTaskState::TaskTimeLeft(int task_id) return (time_left > 0 ? time_left : 0); } + // type "shared task" + if (m_active_shared_task.task_id == task_id) { + int time_now = time(nullptr); + + TaskInformation *p_task_data = task_manager->m_task_data[task_id]; + if (p_task_data == nullptr) { + return -1; + } + + if (!p_task_data->duration) { + return -1; + } + + int time_left = (m_active_shared_task.accepted_time + p_task_data->duration - time_now); + + return (time_left > 0 ? time_left : 0); + } + if (m_active_task_count == 0) { return -1; } @@ -1731,12 +1865,14 @@ bool ClientTaskState::TaskOutOfTime(TaskType task_type, int index) void ClientTaskState::TaskPeriodicChecks(Client *client) { + + // type "task" if (m_active_task.task_id != TASKSLOTEMPTY) { - if (TaskOutOfTime(TaskType::Task, 0)) { + if (TaskOutOfTime(TaskType::Task, TASKSLOTTASK)) { // Send Red Task Failed Message - client->SendTaskFailed(m_active_task.task_id, 0, TaskType::Task); + client->SendTaskFailed(m_active_task.task_id, TASKSLOTTASK, TaskType::Task); // Remove the task from the client - client->CancelTask(0, TaskType::Task); + client->CancelTask(TASKSLOTTASK, TaskType::Task); // It is a conscious decision to only fail one task per call to this method, // otherwise the player will not see all the failed messages where multiple // tasks fail at the same time. @@ -1744,7 +1880,19 @@ void ClientTaskState::TaskPeriodicChecks(Client *client) } } - // TODO: shared tasks -- although that will probably be manager in world checking and telling zones to fail us + // type "shared" + if (m_active_shared_task.task_id != TASKSLOTEMPTY) { + if (TaskOutOfTime(TaskType::Shared, TASKSLOTSHAREDTASK)) { + // Send Red Task Failed Message + client->SendTaskFailed(m_active_shared_task.task_id, TASKSLOTSHAREDTASK, TaskType::Shared); + // Remove the task from the client + client->CancelTask(TASKSLOTSHAREDTASK, TaskType::Shared); + // It is a conscious decision to only fail one task per call to this method, + // otherwise the player will not see all the failed messages where multiple + // tasks fail at the same time. + return; + } + } if (m_active_task_count == 0) { return; @@ -1783,12 +1931,15 @@ bool ClientTaskState::IsTaskActivityCompleted(TaskType task_type, int index, int { switch (task_type) { case TaskType::Task: - if (index != 0) { + if (index != TASKSLOTTASK) { return false; } return m_active_task.activity[activity_id].activity_state == ActivityCompleted; case TaskType::Shared: - return false; // TODO: shared tasks + if (index != TASKSLOTSHAREDTASK) { + return false; + } + return m_active_shared_task.activity[activity_id].activity_state == ActivityCompleted; case TaskType::Quest: if (index < MAXACTIVEQUESTS) { return m_active_quests[index].activity[activity_id].activity_state == ActivityCompleted; @@ -1802,33 +1953,58 @@ bool ClientTaskState::IsTaskActivityCompleted(TaskType task_type, int index, int // should we be defaulting to hidden? ActivityState ClientTaskState::GetTaskActivityState(TaskType task_type, int index, int activity_id) { + ActivityState return_state = ActivityHidden; switch (task_type) { case TaskType::Task: - if (index != 0) { - return ActivityHidden; + if (index != TASKSLOTTASK) { + return_state = ActivityHidden; + break; } - return m_active_task.activity[activity_id].activity_state; + return_state = m_active_task.activity[activity_id].activity_state; + break; case TaskType::Shared: - return ActivityHidden; // TODO: shared tasks + if (index != TASKSLOTSHAREDTASK) { + return_state = ActivityHidden; + break; + } + return_state = m_active_shared_task.activity[activity_id].activity_state; + break; case TaskType::Quest: if (index < MAXACTIVEQUESTS) { - return m_active_quests[index].activity[activity_id].activity_state; + return_state = m_active_quests[index].activity[activity_id].activity_state; + break; } + break; default: - return ActivityHidden; + return_state = ActivityHidden; } + + LogTasksDetail( + "-- [GetTaskActivityState] task_type [{}] ({}) index [{}] activity_id [{}] activity_state [{}] ({})", + Tasks::GetTaskTypeIdentifier(task_type), + Tasks::GetTaskTypeDescription(task_type), + index, + activity_id, + Tasks::GetActivityStateIdentifier(return_state), + Tasks::GetActivityStateDescription(return_state) + ); + + return return_state; } int ClientTaskState::GetTaskActivityDoneCount(TaskType task_type, int index, int activity_id) { switch (task_type) { case TaskType::Task: - if (index != 0) { + if (index != TASKSLOTTASK) { return 0; } return m_active_task.activity[activity_id].done_count; case TaskType::Shared: - return 0; // TODO: shared tasks + if (index != TASKSLOTSHAREDTASK) { + return 0; + } + return m_active_shared_task.activity[activity_id].done_count; case TaskType::Quest: if (index < MAXACTIVEQUESTS) { return m_active_quests[index].activity[activity_id].done_count; @@ -1840,11 +2016,16 @@ int ClientTaskState::GetTaskActivityDoneCount(TaskType task_type, int index, int int ClientTaskState::GetTaskActivityDoneCountFromTaskID(int task_id, int activity_id) { + + // type "task" if (m_active_task.task_id == task_id) { return m_active_task.activity[activity_id].done_count; } - // TODO: shared tasks + // type "shared" + if (m_active_shared_task.task_id == task_id) { + return m_active_shared_task.activity[activity_id].done_count; + } int active_task_index = -1; @@ -1874,7 +2055,8 @@ int ClientTaskState::GetTaskStartTime(TaskType task_type, int index) return m_active_task.accepted_time; case TaskType::Quest: return m_active_quests[index].accepted_time; - case TaskType::Shared: // TODO + case TaskType::Shared: + return m_active_shared_task.accepted_time; default: return -1; } @@ -1887,9 +2069,16 @@ void ClientTaskState::CancelAllTasks(Client *client) // It removes tasks from the in-game client state ready for them to be // resent to the client, in case an updated task fails to load - CancelTask(client, 0, TaskType::Task, false); + // task + // these cancels lock up the client for some reason + CancelTask(client, TASKSLOTTASK, TaskType::Task, false); m_active_task.task_id = TASKSLOTEMPTY; + // shared task + CancelTask(client, TASKSLOTSHAREDTASK, TaskType::Shared, false); + m_active_shared_task.task_id = TASKSLOTEMPTY; + + // "quests" for (int task_index = 0; task_index < MAXACTIVEQUESTS; task_index++) if (m_active_quests[task_index].task_id != TASKSLOTEMPTY) { CancelTask(client, task_index, TaskType::Quest, false); @@ -1899,24 +2088,60 @@ void ClientTaskState::CancelAllTasks(Client *client) // TODO: shared } -void ClientTaskState::CancelTask(Client *client, int sequence_number, TaskType task_type, bool remove_from_db) +void ClientTaskState::CancelTask(Client *c, int sequence_number, TaskType task_type, bool remove_from_db) { + LogTasks("CancelTask"); + + // shared task middleware + // intercept and pass to world first before processing normally + if (!c->m_requested_shared_task_removal && task_type == TaskType::Shared && m_active_shared_task.task_id != 0) { + + // struct + auto pack = new ServerPacket(ServerOP_SharedTaskAttemptRemove, sizeof(ServerSharedTaskAttemptRemove_Struct)); + auto *r = (ServerSharedTaskAttemptRemove_Struct *) pack->pBuffer; + + // fill + r->requested_character_id = c->CharacterID(); + r->requested_task_id = m_active_shared_task.task_id; + r->remove_from_db = remove_from_db; + + // send + worldserver.SendPacket(pack); + safe_delete(pack); + + return; + } + + // packet auto outapp = new EQApplicationPacket(OP_CancelTask, sizeof(CancelTask_Struct)); - CancelTask_Struct *cts = (CancelTask_Struct *) outapp->pBuffer; + // fill + auto *cts = (CancelTask_Struct *) outapp->pBuffer; cts->SequenceNumber = sequence_number; cts->type = static_cast(task_type); - Log(Logs::General, Logs::Tasks, "[UPDATE] CancelTask"); - - client->QueuePacket(outapp); + // send + c->QueuePacket(outapp); safe_delete(outapp); + // persistence if (remove_from_db) { - RemoveTask(client, sequence_number, task_type); + RemoveTask(c, sequence_number, task_type); } } +void ClientTaskState::KickPlayersSharedTask(Client* client) +{ + uint32_t pack_size = sizeof(ServerSharedTaskKickPlayers_Struct); + auto pack = std::make_unique(ServerOP_SharedTaskKickPlayers, pack_size); + auto buf = reinterpret_cast(pack->pBuffer); + + buf->source_character_id = client->CharacterID(); + buf->task_id = m_active_shared_task.task_id; + + worldserver.SendPacket(pack.get()); +} + void ClientTaskState::RemoveTask(Client *client, int sequence_number, TaskType task_type) { int character_id = client->CharacterID(); @@ -1925,7 +2150,7 @@ void ClientTaskState::RemoveTask(Client *client, int sequence_number, TaskType t int task_id = -1; switch (task_type) { case TaskType::Task: - if (sequence_number == 0) { + if (sequence_number == TASKSLOTTASK) { task_id = m_active_task.task_id; } break; @@ -1934,7 +2159,11 @@ void ClientTaskState::RemoveTask(Client *client, int sequence_number, TaskType t task_id = m_active_quests[sequence_number].task_id; } break; - case TaskType::Shared: // TODO: + case TaskType::Shared: + if (sequence_number == TASKSLOTSHAREDTASK) { + task_id = m_active_shared_task.task_id; + } + break; default: break; } @@ -1954,7 +2183,8 @@ void ClientTaskState::RemoveTask(Client *client, int sequence_number, TaskType t m_active_task.task_id = TASKSLOTEMPTY; break; case TaskType::Shared: - break; // TODO: shared tasks + m_active_shared_task.task_id = TASKSLOTEMPTY; + break; case TaskType::Quest: m_active_quests[sequence_number].task_id = TASKSLOTEMPTY; m_active_task_count--; @@ -1966,48 +2196,26 @@ void ClientTaskState::RemoveTask(Client *client, int sequence_number, TaskType t void ClientTaskState::RemoveTaskByTaskID(Client *client, uint32 task_id) { - auto task_type = task_manager->GetTaskType(task_id); - int character_id = client->CharacterID(); - - CharacterActivitiesRepository::DeleteWhere( - database, - fmt::format("charid = {} AND taskid = {}", character_id, task_id) - ); - - CharacterTasksRepository::DeleteWhere( - database, - fmt::format("charid = {} AND taskid = {} AND type = {}", character_id, task_id, (int) task_type) - ); - - switch (task_type) { + switch (task_manager->GetTaskType(task_id)) { case TaskType::Task: { if (m_active_task.task_id == task_id) { - auto outapp = new EQApplicationPacket(OP_CancelTask, sizeof(CancelTask_Struct)); - CancelTask_Struct *cts = (CancelTask_Struct *) outapp->pBuffer; - cts->SequenceNumber = 0; - cts->type = static_cast(task_type); LogTasks("[UPDATE] RemoveTaskByTaskID found Task [{}]", task_id); - client->QueuePacket(outapp); - safe_delete(outapp); - m_active_task.task_id = TASKSLOTEMPTY; + CancelTask(client, TASKSLOTTASK, TaskType::Task, true); } break; } case TaskType::Shared: { - break; // TODO: shared tasks + if (m_active_shared_task.task_id == task_id) { + LogTasks("[UPDATE] RemoveTaskByTaskID found Shared Task [{}]", task_id); + CancelTask(client, TASKSLOTSHAREDTASK, TaskType::Shared, true); + } + break; } case TaskType::Quest: { for (int active_quest = 0; active_quest < MAXACTIVEQUESTS; active_quest++) { - if (m_active_quests[active_quest].task_id == task_id) { - auto outapp = new EQApplicationPacket(OP_CancelTask, sizeof(CancelTask_Struct)); - CancelTask_Struct *cts = (CancelTask_Struct *) outapp->pBuffer; - cts->SequenceNumber = active_quest; - cts->type = static_cast(task_type); + if (m_active_quests[active_quest].task_id == task_id) { LogTasks("[UPDATE] RemoveTaskByTaskID found Quest [{}] at index [{}]", task_id, active_quest); - m_active_quests[active_quest].task_id = TASKSLOTEMPTY; - m_active_task_count--; - client->QueuePacket(outapp); - safe_delete(outapp); + CancelTask(client, active_quest, TaskType::Quest, true); } } } @@ -2017,7 +2225,13 @@ void ClientTaskState::RemoveTaskByTaskID(Client *client, uint32 task_id) } } -void ClientTaskState::AcceptNewTask(Client *client, int task_id, int npc_type_id, bool enforce_level_requirement) +void ClientTaskState::AcceptNewTask( + Client *client, + int task_id, + int npc_type_id, + time_t accept_time, + bool enforce_level_requirement +) { if (!task_manager || task_id < 0 || task_id >= MAXTASKS) { client->Message(Chat::Red, "Task system not functioning, or task_id %i out of range.", task_id); @@ -2025,12 +2239,37 @@ void ClientTaskState::AcceptNewTask(Client *client, int task_id, int npc_type_id } auto task = task_manager->m_task_data[task_id]; - if (task == nullptr) { client->Message(Chat::Red, "Invalid task_id %i", task_id); return; } + // shared task + // intercept and pass to world first before processing normally + if (!client->m_requesting_shared_task && task->type == TaskType::Shared) { + LogTasksDetail( + "[AcceptNewTask] Initiating shared_task request | task_id [{}] character_id [{}] name [{}]", + task_id, + client->CharacterID(), + client->GetCleanName() + ); + + // struct + auto pack = new ServerPacket(ServerOP_SharedTaskRequest, sizeof(ServerSharedTaskRequest_Struct)); + auto *r = (ServerSharedTaskRequest_Struct *) pack->pBuffer; + + // fill + r->requested_character_id = client->CharacterID(); + r->requested_task_id = task_id; + r->requested_npc_type_id = npc_type_id; + + // send + worldserver.SendPacket(pack); + safe_delete(pack); + + return; + } + bool max_tasks = false; switch (task->type) { @@ -2040,8 +2279,9 @@ void ClientTaskState::AcceptNewTask(Client *client, int task_id, int npc_type_id } break; case TaskType::Shared: // TODO: shared tasks - // if (something) - max_tasks = true; + if (m_active_shared_task.task_id != TASKSLOTEMPTY) { + max_tasks = true; + } break; case TaskType::Quest: if (m_active_task_count == MAXACTIVEQUESTS) { @@ -2053,11 +2293,7 @@ void ClientTaskState::AcceptNewTask(Client *client, int task_id, int npc_type_id } if (max_tasks) { - client->Message( - Chat::Red, - "You already have the maximum allowable number of active tasks (%i)", - MAXACTIVEQUESTS - ); + client->MessageString(Chat::Yellow, MAX_ACTIVE_TASKS, ".", ".", client->GetName()); return; } @@ -2065,14 +2301,15 @@ void ClientTaskState::AcceptNewTask(Client *client, int task_id, int npc_type_id if (task->type == TaskType::Quest) { for (auto &active_quest : m_active_quests) { if (active_quest.task_id == task_id) { - client->Message(Chat::Red, "You have already been assigned this task."); + // live doesn't have an eqstr for it but this seems to be the string used for this scenario + client->Message(Chat::Yellow, "You are already working on a task for this person, you must finish it before asking for another."); return; } } } if (enforce_level_requirement && !task_manager->ValidateLevel(task_id, client->GetLevel())) { - client->Message(Chat::Red, "You are outside the level range of this task."); + client->MessageString(Chat::Yellow, TASK_NOT_RIGHT_LEVEL); return; } @@ -2080,6 +2317,39 @@ void ClientTaskState::AcceptNewTask(Client *client, int task_id, int npc_type_id return; } + // solo task timer lockout validation + if (task->type != TaskType::Shared) + { + auto task_timers = CharacterTaskTimersRepository::GetWhere(database, fmt::format( + "character_id = {} AND task_id = {} AND expire_time > NOW() ORDER BY timer_type ASC LIMIT 1", + client->CharacterID(), task_id + )); + + if (!task_timers.empty()) + { + auto timer_type = static_cast(task_timers.front().timer_type); + auto seconds = task_timers.front().expire_time - std::time(nullptr); + auto days = fmt::format_int(seconds / 86400).str(); + auto hours = fmt::format_int((seconds / 3600) % 24).str(); + auto mins = fmt::format_int((seconds / 60) % 60).str(); + + // these solo task messages are in SharedTaskMessage for convenience + namespace EQStr = SharedTaskMessage; + if (timer_type == TaskTimerType::Replay) + { + int eqstr_id = EQStr::TASK_ASSIGN_WAIT_REPLAY_TIMER; + client->MessageString(Chat::Red, eqstr_id, days.c_str(), hours.c_str(), mins.c_str()); + } + else if (timer_type == TaskTimerType::Request) + { + int eqstr_id = EQStr::TASK_ASSIGN_WAIT_REQUEST_TIMER; + client->Message(Chat::Red, fmt::format(EQStr::GetEQStr(eqstr_id), days, hours, mins).c_str()); + } + + return; + } + } + // We do it this way, because when the Client cancels a task, it retains the sequence number of the remaining // tasks in it's window, until something causes the TaskDescription packets to be sent again. We could just // resend all the active task data to the client when it cancels a task, but that could be construed as a @@ -2090,9 +2360,11 @@ void ClientTaskState::AcceptNewTask(Client *client, int task_id, int npc_type_id case TaskType::Task: active_slot = &m_active_task; break; - case TaskType::Shared: // TODO: shared - active_slot = nullptr; + + case TaskType::Shared: + active_slot = &m_active_shared_task; break; + case TaskType::Quest: for (int task_index = 0; task_index < MAXACTIVEQUESTS; task_index++) { Log(Logs::General, Logs::Tasks, @@ -2104,22 +2376,19 @@ void ClientTaskState::AcceptNewTask(Client *client, int task_id, int npc_type_id } } break; + default: break; } // This shouldn't happen unless there is a bug in the handling of ActiveTaskCount somewhere if (active_slot == nullptr) { - client->Message( - Chat::Red, - "You already have the maximum allowable number of active tasks (%i)", - MAXACTIVEQUESTS - ); + client->MessageString(Chat::Yellow, MAX_ACTIVE_TASKS, ".", ".", client->GetName()); return; } active_slot->task_id = task_id; - active_slot->accepted_time = time(nullptr); + active_slot->accepted_time = static_cast(accept_time); active_slot->updated = true; active_slot->current_step = -1; @@ -2136,12 +2405,39 @@ void ClientTaskState::AcceptNewTask(Client *client, int task_id, int npc_type_id m_active_task_count++; } + // add request timer (shared task timers are added to members by world) + if (task->request_timer_seconds > 0) + { + auto expire_time = active_slot->accepted_time + task->request_timer_seconds; + + auto seconds = expire_time - std::time(nullptr); + if (seconds > 0) // not already expired + { + if (task->type != TaskType::Shared) + { + auto timer = CharacterTaskTimersRepository::NewEntity(); + timer.character_id = client->CharacterID(); + timer.task_id = task_id; + timer.timer_type = static_cast(TaskTimerType::Request); + timer.expire_time = expire_time; + + CharacterTaskTimersRepository::InsertOne(database, timer); + } + + client->Message(Chat::Yellow, fmt::format( + SharedTaskMessage::GetEQStr(SharedTaskMessage::RECEIVED_REQUEST_TIMER), + task->title, + fmt::format_int(seconds / 86400).c_str(), // days + fmt::format_int((seconds / 3600) % 24).c_str(), // hours + fmt::format_int((seconds / 60) % 60).c_str() // minutes + ).c_str()); + } + } + task_manager->SendSingleActiveTaskToClient(client, *active_slot, false, true); - client->Message( - Chat::White, - "You have been assigned the task '%s'.", - task_manager->m_task_data[task_id]->title.c_str() - ); + client->StartTaskRequestCooldownTimer(); + client->MessageString(Chat::White, YOU_ASSIGNED_TASK, task->title.c_str()); + task_manager->SaveClientState(client, this); std::string buf = std::to_string(task_id); @@ -2175,3 +2471,248 @@ void ClientTaskState::ProcessTaskProximities(Client *client, float x, float y, f UpdateTasksOnExplore(client, explore_id); } } + +void ClientTaskState::SharedTaskIncrementDoneCount( + Client *client, + int task_id, + int activity_id, + int done_count, + bool ignore_quest_update +) +{ + TaskInformation *t = task_manager->m_task_data[task_id]; + + auto info = GetClientTaskInfo(t->type, TASKSLOTSHAREDTASK); + if (info == nullptr) { + return; + } + + // absolute value update + info->activity[activity_id].done_count = done_count; + + LogTasksDetail( + "[SharedTaskIncrementDoneCount] Setting task_id [{}] to absolute done_count value of [{}] via increment [{}]", + task_id, + info->activity[activity_id].done_count, + done_count + ); + + IncrementDoneCount( + client, + t, + TASKSLOTSHAREDTASK, + activity_id, + 0, // no op + ignore_quest_update + ); +} + +const ClientTaskInformation &ClientTaskState::GetActiveSharedTask() const +{ + return m_active_shared_task; +} + +bool ClientTaskState::HasActiveSharedTask() +{ + return GetActiveSharedTask().task_id != 0; +} + +void ClientTaskState::CreateTaskDynamicZone(Client* client, int task_id, DynamicZone& dz_request) +{ + auto task = task_manager->m_task_data[task_id]; + if (!task) + { + return; + } + + // dz should be named the version-based zone name (used in choose zone window and dz window on live) + auto zone_info = zone_store.GetZone(dz_request.GetZoneID(), dz_request.GetZoneVersion()); + dz_request.SetName(zone_info.long_name.empty() ? task->title : zone_info.long_name); + dz_request.SetMinPlayers(task->min_players); + dz_request.SetMaxPlayers(task->max_players); + + // a task might create a dz from an objective so override dz duration to time remaining + // live probably creates the dz with the shared task and just adds members for objectives + std::chrono::seconds seconds(TaskTimeLeft(task_id)); + if (task->duration == 0 || seconds.count() < 0) + { + // todo: maybe add a rule for duration + // cap unlimited duration tasks so instance isn't held indefinitely + // expected behavior is to re-acquire any unlimited tasks that have an expired dz + seconds = std::chrono::duration_cast(std::chrono::hours(24)); + } + + dz_request.SetDuration(static_cast(seconds.count())); + + if (task->type == TaskType::Task || task->type == TaskType::Quest) + { + if (task->type == TaskType::Task) { + dz_request.SetType(DynamicZoneType::Task); + } else { + dz_request.SetType(DynamicZoneType::Quest); + } + + // todo: can enable non-shared task dz creation when dz ids are persisted for them (db also) + //DynamicZoneMember solo_member{ client->CharacterID(), client->GetCleanName() }; + //DynamicZone::CreateNew(dz_request, { solo_member }); + } + else if (task->type == TaskType::Shared) + { + dz_request.SetType(DynamicZoneType::Mission); + + // shared task missions are created in world + EQ::Net::DynamicPacket dyn_pack = dz_request.GetSerializedDzPacket(); + + auto pack_size = sizeof(ServerSharedTaskCreateDynamicZone_Struct) + dyn_pack.Length(); + auto pack = std::make_unique(ServerOP_SharedTaskCreateDynamicZone, static_cast(pack_size)); + auto buf = reinterpret_cast(pack->pBuffer); + buf->source_character_id = client->CharacterID(); + buf->task_id = task_id; + buf->cereal_size = static_cast(dyn_pack.Length()); + memcpy(buf->cereal_data, dyn_pack.Data(), dyn_pack.Length()); + + worldserver.SendPacket(pack.get()); + } + +} + +void ClientTaskState::ListTaskTimers(Client* client) +{ + LogTasksDetail("[ListTaskTimers] Client [{}]", client->GetCleanName()); + + // this isn't live-like but we need to throttle query (alternative is caching timers) + if (!client->m_list_task_timers_rate_limit.Check()) { + client->Message(Chat::White, "Sending messages too fast"); + return; + } + + auto character_task_timers = CharacterTaskTimersRepository::GetWhere( + database, fmt::format( + "character_id = {} AND expire_time > NOW()", + client->CharacterID() + ) + ); + + for (const auto& task_timer : character_task_timers) + { + auto task = task_manager->m_task_data[task_timer.task_id]; + if (task) + { + auto timer_type = static_cast(task_timer.timer_type); + auto seconds = task_timer.expire_time - std::time(nullptr); + auto days = fmt::format_int(seconds / 86400).str(); + auto hours = fmt::format_int((seconds / 3600) % 24).str(); + auto mins = fmt::format_int((seconds / 60) % 60).str(); + + if (timer_type == TaskTimerType::Replay) + { + client->MessageString(Chat::Yellow, SharedTaskMessage::REPLAY_TIMER_REMAINING, + task->title.c_str(), days.c_str(), hours.c_str(), mins.c_str()); + } + else + { + auto eqstr = SharedTaskMessage::GetEQStr(SharedTaskMessage::REQUEST_TIMER_REMAINING); + client->Message(Chat::Yellow, fmt::format(eqstr, task->title, days, hours, mins).c_str()); + } + } + } + + if (character_task_timers.empty()) { + client->MessageString(Chat::Yellow, SharedTaskMessage::YOU_NO_CURRENT_REPLAY_TIMERS); + } +} + +void ClientTaskState::AddReplayTimer(Client* client, ClientTaskInformation& client_task, TaskInformation& task) +{ + // world adds timers for shared tasks and handles messages + if (task.type != TaskType::Shared && task.replay_timer_seconds > 0) + { + // solo task replay timers are based on completion time + auto expire_time = std::time(nullptr) + task.replay_timer_seconds; + + auto timer = CharacterTaskTimersRepository::NewEntity(); + timer.character_id = client->CharacterID(); + timer.task_id = client_task.task_id; + timer.expire_time = expire_time; + timer.timer_type = static_cast(TaskTimerType::Replay); + + CharacterTaskTimersRepository::InsertOne(database, timer); + + client->Message(Chat::Yellow, fmt::format( + SharedTaskMessage::GetEQStr(SharedTaskMessage::RECEIVED_REPLAY_TIMER), + task.title, + fmt::format_int(task.replay_timer_seconds / 86400).c_str(), // days + fmt::format_int((task.replay_timer_seconds / 3600) % 24).c_str(), // hours + fmt::format_int((task.replay_timer_seconds / 60) % 60).c_str() // minutes + ).c_str()); + } +} + +// Sync zone client donecount state +// +// World is always authoritative, but we still need to keep zone state in sync with reality and in this case we need +// to update zone since world hasn't had a chance to let clients at the zone level know it's ok to progress to the next +// donecount +// +// If we send updates too fast and world has not had a chance to confirm and then inform clients to set their +// absolute state we will miss and discard updates because each update sets the same donecount +// +// Example: say we have a 100 kill task and we kill 10 mobs in an AOE, world gets 10 updates at once but they are +// all from the same donecount so world only confirms 1 was actually killed and the task updates only 1 which is not +// intended behavior. +// +// In this case we need to ensure that clients at the zone level increment internally even if they aren't +// necessarily confirmed by world yet because any of them could inform world of the next donecount so we need to sync +// zone-level before sending updates to world +void ClientTaskState::SyncSharedTaskZoneClientDoneCountState( + Client *p_client, + TaskInformation *p_information, + int task_index, + int activity_id, + uint32 done_count +) +{ + for (auto &e : entity_list.GetClientList()) { + auto c = e.second; + if (c->GetSharedTaskId() == p_client->GetSharedTaskId()) { + auto t = c->GetTaskState()->GetClientTaskInfo(p_information->type, task_index); + if (t == nullptr) { + continue; + } + + LogTasksDetail( + "[IncrementDoneCount] Setting internally client [{}] to donecount [{}]", + c->GetCleanName(), + done_count + ); + + t->activity[activity_id].done_count = (int) done_count; + } + } +} + +bool ClientTaskState::HasActiveTasks() +{ + if (!task_manager) { + return false; + } + + if (m_active_task.task_id != TASKSLOTEMPTY) { + return true; + } + + if (m_active_shared_task.task_id != TASKSLOTEMPTY) { + return true; + } + + bool has_active_quest = false; + for (auto &active_quest : m_active_quests) { + if (active_quest.task_id == TASKSLOTEMPTY) { + continue; + } + + return true; + } + + return false; +} diff --git a/zone/task_client_state.h b/zone/task_client_state.h index 6812e99c6..ab3ad1044 100644 --- a/zone/task_client_state.h +++ b/zone/task_client_state.h @@ -20,7 +20,7 @@ public: int GetTaskActivityDoneCount(TaskType task_type, int index, int activity_id); int GetTaskActivityDoneCountFromTaskID(int task_id, int activity_id); int GetTaskStartTime(TaskType task_type, int index); - void AcceptNewTask(Client *client, int task_id, int npc_type_id, bool enforce_level_requirement = false); + void AcceptNewTask(Client *client, int task_id, int npc_type_id, time_t accept_time, bool enforce_level_requirement = false); void FailTask(Client *client, int task_id); int TaskTimeLeft(int task_id); int IsTaskCompleted(int task_id); @@ -29,13 +29,12 @@ public: ActivityState GetTaskActivityState(TaskType task_type, int index, int activity_id); void UpdateTaskActivity(Client *client, int task_id, int activity_id, int count, bool ignore_quest_update = false); void ResetTaskActivity(Client *client, int task_id, int activity_id); - void CancelTask(Client *client, int sequence_number, TaskType task_type, bool remove_from_db = true); + void CancelTask(Client *c, int sequence_number, TaskType task_type, bool remove_from_db = true); void CancelAllTasks(Client *client); void RemoveTask(Client *client, int sequence_number, TaskType task_type); void RemoveTaskByTaskID(Client *client, uint32 task_id); - bool UpdateTasksByNPC(Client *client, int activity_type, int npc_type_id); - void UpdateTasksOnKill(Client *client, int npc_type_id); - void UpdateTasksForItem(Client *client, ActivityType activity_type, int item_id, int count = 1); + bool UpdateTasksByNPC(Client *client, TaskActivityType activity_type, int npc_type_id); + void UpdateTasksForItem(Client *client, TaskActivityType activity_type, int item_id, int count = 1); void UpdateTasksOnExplore(Client *client, int explore_id); bool UpdateTasksOnSpeakWith(Client *client, int npc_type_id); bool UpdateTasksOnDeliver(Client *client, std::list &items, int cash, int npc_type_id); @@ -54,13 +53,29 @@ public: int ActiveTasksInSet(int task_set_id); int CompletedTasksInSet(int task_set_id); bool HasSlotForTask(TaskInformation *task); + void CreateTaskDynamicZone(Client* client, int task_id, DynamicZone& dz); + void ListTaskTimers(Client* client); + void KickPlayersSharedTask(Client* client); inline bool HasFreeTaskSlot() { return m_active_task.task_id == TASKSLOTEMPTY; } friend class TaskManager; + // wrapper to call internal IncrementDoneCount + void SharedTaskIncrementDoneCount( + Client *client, + int task_id, + int activity_id, + int done_count, + bool ignore_quest_update = false + ); + + const ClientTaskInformation &GetActiveSharedTask() const; + bool HasActiveSharedTask(); + private: - bool UnlockActivities(int character_id, ClientTaskInformation &task_info); + void AddReplayTimer(Client *client, ClientTaskInformation& client_task, TaskInformation& task); + void IncrementDoneCount( Client *client, TaskInformation *task_information, @@ -70,16 +85,21 @@ private: bool ignore_quest_update = false ); + bool UnlockActivities(int character_id, ClientTaskInformation &task_info); + inline ClientTaskInformation *GetClientTaskInfo(TaskType task_type, int index) { ClientTaskInformation *info = nullptr; switch (task_type) { case TaskType::Task: - if (index == 0) { + if (index == TASKSLOTTASK) { info = &m_active_task; } break; case TaskType::Shared: + if (index == TASKSLOTSHAREDTASK) { + info = &m_active_shared_task; + } break; case TaskType::Quest: if (index < MAXACTIVEQUESTS) { @@ -95,9 +115,13 @@ private: union { // easier to loop over struct { ClientTaskInformation m_active_task; // only one + + // acts as a read-only "view" of data that is managed by world and the internal task + // system largely behaves like other tasks but shims logic to world where necessary + ClientTaskInformation m_active_shared_task; // only one ClientTaskInformation m_active_quests[MAXACTIVEQUESTS]; }; - ClientTaskInformation m_active_tasks[MAXACTIVEQUESTS + 1]; + ClientTaskInformation m_active_tasks[MAXACTIVEQUESTS + 2] = {}; }; // Shared tasks should be limited to 1 as well int m_active_task_count; @@ -105,6 +129,17 @@ private: std::vector m_completed_tasks; int m_last_completed_task_loaded; bool m_checked_touch_activities; + + static void ShowClientTaskInfoMessage(ClientTaskInformation *task, Client *c); + + void SyncSharedTaskZoneClientDoneCountState( + Client *p_client, + TaskInformation *p_information, + int task_index, + int activity_id, + uint32 done_count + ); + bool HasActiveTasks(); }; diff --git a/zone/task_manager.cpp b/zone/task_manager.cpp index b16d95a4e..9204f4ee6 100644 --- a/zone/task_manager.cpp +++ b/zone/task_manager.cpp @@ -1,6 +1,7 @@ #include "../common/global_define.h" #include "../common/misc_functions.h" #include "../common/repositories/character_activities_repository.h" +#include "../common/repositories/character_data_repository.h" #include "../common/repositories/character_tasks_repository.h" #include "../common/repositories/completed_tasks_repository.h" #include "../common/repositories/task_activities_repository.h" @@ -9,6 +10,12 @@ #include "client.h" #include "string_ids.h" #include "task_manager.h" +#include "../common/repositories/shared_task_activity_state_repository.h" +#include "../common/repositories/shared_task_members_repository.h" +#include "../common/shared_tasks.h" +#include "worldserver.h" + +extern WorldServer worldserver; TaskManager::TaskManager() { @@ -87,30 +94,38 @@ bool TaskManager::LoadTasks(int single_task) } // load task data - m_task_data[task_id] = new TaskInformation; - m_task_data[task_id]->type = static_cast(task.type); - m_task_data[task_id]->duration = task.duration; - m_task_data[task_id]->duration_code = static_cast(task.duration_code); - m_task_data[task_id]->title = task.title; - m_task_data[task_id]->description = task.description; - m_task_data[task_id]->reward = task.reward; - m_task_data[task_id]->reward_id = task.rewardid; - m_task_data[task_id]->cash_reward = task.cashreward; - m_task_data[task_id]->experience_reward = task.xpreward; - m_task_data[task_id]->reward_method = (TaskMethodType) task.rewardmethod; - m_task_data[task_id]->faction_reward = task.faction_reward; - m_task_data[task_id]->min_level = task.minlevel; - m_task_data[task_id]->max_level = task.maxlevel; - m_task_data[task_id]->repeatable = task.repeatable; - m_task_data[task_id]->completion_emote = task.completion_emote; - m_task_data[task_id]->activity_count = 0; - m_task_data[task_id]->sequence_mode = ActivitiesSequential; - m_task_data[task_id]->last_step = 0; + m_task_data[task_id] = new TaskInformation(); + m_task_data[task_id]->type = static_cast(task.type); + m_task_data[task_id]->duration = task.duration; + m_task_data[task_id]->duration_code = static_cast(task.duration_code); + m_task_data[task_id]->title = task.title; + m_task_data[task_id]->description = task.description; + m_task_data[task_id]->reward = task.reward; + m_task_data[task_id]->reward_id = task.rewardid; + m_task_data[task_id]->cash_reward = task.cashreward; + m_task_data[task_id]->experience_reward = task.xpreward; + m_task_data[task_id]->reward_method = (TaskMethodType) task.rewardmethod; + m_task_data[task_id]->reward_radiant_crystals = task.reward_radiant_crystals; + m_task_data[task_id]->reward_ebon_crystals = task.reward_ebon_crystals; + m_task_data[task_id]->faction_reward = task.faction_reward; + m_task_data[task_id]->min_level = task.minlevel; + m_task_data[task_id]->max_level = task.maxlevel; + m_task_data[task_id]->level_spread = task.level_spread; + m_task_data[task_id]->min_players = task.min_players; + m_task_data[task_id]->max_players = task.max_players; + m_task_data[task_id]->repeatable = task.repeatable; + m_task_data[task_id]->completion_emote = task.completion_emote; + m_task_data[task_id]->replay_timer_seconds = task.replay_timer_seconds; + m_task_data[task_id]->request_timer_seconds = task.request_timer_seconds; + m_task_data[task_id]->activity_count = 0; + m_task_data[task_id]->sequence_mode = ActivitiesSequential; + m_task_data[task_id]->last_step = 0; LogTasksDetail( - "[LoadTasks] (Task) task_id [{}] type [{}] duration [{}] duration_code [{}] title [{}] description [{}] " + "[LoadTasks] (Task) task_id [{}] type [{}] () duration [{}] duration_code [{}] title [{}] description [{}] " " reward [{}] rewardid [{}] cashreward [{}] xpreward [{}] rewardmethod [{}] faction_reward [{}] minlevel [{}] " - " maxlevel [{}] repeatable [{}] completion_emote [{}] ", + " maxlevel [{}] level_spread [{}] min_players [{}] max_players [{}] repeatable [{}] completion_emote [{}]", + " replay_timer_seconds [{}] request_timer_seconds [{}]", task.id, task.type, task.duration, @@ -125,8 +140,13 @@ bool TaskManager::LoadTasks(int single_task) task.faction_reward, task.minlevel, task.maxlevel, + task.level_spread, + task.min_players, + task.max_players, task.repeatable, - task.completion_emote + task.completion_emote, + task.replay_timer_seconds, + task.request_timer_seconds ); } @@ -203,7 +223,7 @@ bool TaskManager::LoadTasks(int single_task) } // set activity data - activity_data->activity_type = task_activity.activitytype; + activity_data->activity_type = static_cast(task_activity.activitytype); activity_data->target_name = task_activity.target_name; activity_data->item_list = task_activity.item_list; activity_data->skill_list = task_activity.skill_list; @@ -231,11 +251,11 @@ bool TaskManager::LoadTasks(int single_task) LogTasksDetail( "[LoadTasks] (Activity) task_id [{}] activity_id [{}] slot [{}] activity_type [{}] goal_id [{}] goal_method [{}] goal_count [{}] zones [{}]" - " target_name [{}] item_list [{}] skill_list [{}] spell_list [{}] description_override [{}]", + " target_name [{}] item_list [{}] skill_list [{}] spell_list [{}] description_override [{}] sequence [{}]", task_id, activity_id, m_task_data[task_id]->activity_count, - activity_data->activity_type, + static_cast(activity_data->activity_type), activity_data->goal_id, activity_data->goal_method, activity_data->goal_count, @@ -244,7 +264,8 @@ bool TaskManager::LoadTasks(int single_task) activity_data->item_list.c_str(), activity_data->skill_list.c_str(), activity_data->spell_list.c_str(), - activity_data->description_override.c_str() + activity_data->description_override.c_str(), + (m_task_data[task_id]->sequence_mode == ActivitiesStepped ? "stepped" : "sequential") ); m_task_data[task_id]->activity_count++; @@ -265,14 +286,15 @@ bool TaskManager::SaveClientState(Client *client, ClientTaskState *client_task_s return false; } - const char *ERR_MYSQLERROR = "[TASKS]Error in TaskManager::SaveClientState %s"; + const char *ERR_MYSQLERROR = "[TASKS]Error in TaskManager::SaveClientState {}"; int character_id = client->CharacterID(); LogTasks("[SaveClientState] character_id [{}]", character_id); if (client_task_state->m_active_task_count > 0 || - client_task_state->m_active_task.task_id != TASKSLOTEMPTY) { // TODO: tasks + client_task_state->m_active_task.task_id != TASKSLOTEMPTY || + client_task_state->m_active_shared_task.task_id != TASKSLOTEMPTY) { for (auto &active_task : client_task_state->m_active_tasks) { int task_id = active_task.task_id; if (task_id == TASKSLOTEMPTY) { @@ -361,15 +383,14 @@ bool TaskManager::SaveClientState(Client *client, ClientTaskState *client_task_s } active_task.updated = false; - for (int activity_index = 0; - activity_index < m_task_data[task_id]->activity_count; - ++activity_index) + for (int activity_index = 0; activity_index < m_task_data[task_id]->activity_count; ++activity_index) { active_task.activity[activity_index].updated = false; + } } } - if (!RuleB(TaskSystem, RecordCompletedTasks) || - (client_task_state->m_completed_tasks.size() <= (unsigned int) client_task_state->m_last_completed_task_loaded)) { + if (!RuleB(TaskSystem, RecordCompletedTasks) || (client_task_state->m_completed_tasks.size() <= + (unsigned int) client_task_state->m_last_completed_task_loaded)) { client_task_state->m_last_completed_task_loaded = client_task_state->m_completed_tasks.size(); return true; } @@ -387,6 +408,11 @@ bool TaskManager::SaveClientState(Client *client, ClientTaskState *client_task_s continue; } + // we don't record completed shared tasks in the task quest log + if (m_task_data[task_id]->type == TaskType::Shared) { + break; + } + // First we save a record with an activity_id of -1. // This indicates this task was completed at the given time. We infer that all // none optional activities were completed. @@ -542,14 +568,22 @@ void TaskManager::TaskSetSelector(Client *client, ClientTaskState *client_task_s return; } + // forward to shared task selector validation if set contains a shared task + for (const auto& task_id : m_task_sets[task_set_id]) + { + if (m_task_data[task_id] && m_task_data[task_id]->type == TaskType::Shared) { + SharedTaskSelector(client, mob, m_task_sets[task_set_id].size(), m_task_sets[task_set_id].data()); + return; + } + } + + if (client->HasTaskRequestCooldownTimer()) { + client->SendTaskRequestCooldownTimerMessage(); + return; + } + if (m_task_sets[task_set_id].empty()) { - // I think this is suppose to be yellow - mob->SayString( - client, - Chat::Yellow, - MAX_ACTIVE_TASKS, - client->GetName() - ); + client->MessageString(Chat::Yellow, NO_TASK_OFFERS, ".", ".", client->GetName()); return; } @@ -584,13 +618,7 @@ void TaskManager::TaskSetSelector(Client *client, ClientTaskState *client_task_s SendTaskSelector(client, mob, task_list_index, task_list); } else { - // TODO: check color, I think this might be only for (Shared) Tasks, w/e -- think should be yellow - mob->SayString( - client, - Chat::Yellow, - MAX_ACTIVE_TASKS, - client->GetName() - ); + client->MessageString(Chat::Yellow, NO_TASK_OFFERS, ".", ".", client->GetName()); } } @@ -614,6 +642,22 @@ void TaskManager::TaskQuestSetSelector( return; } + // live prevents mixing selection types (also uses diff opcodes for solo vs shared tasks) + // to keep shared task validation live-like (and simple), any shared task will + // forward this to shared task validation and non-shared tasks will be dropped + for (int i = 0; i < count; ++i) { + auto task = tasks[i]; + if (m_task_data[task] && m_task_data[task]->type == TaskType::Shared) { + SharedTaskSelector(client, mob, count, tasks); + return; + } + } + + if (client->HasTaskRequestCooldownTimer()) { + client->SendTaskRequestCooldownTimerMessage(); + return; + } + for (int i = 0; i < count; ++i) { auto task = tasks[i]; // verify level, we're not currently on it, repeatable status, if it's a (shared) task @@ -630,24 +674,91 @@ void TaskManager::TaskQuestSetSelector( SendTaskSelector(client, mob, task_list_index, task_list); } else { - // TODO: check color, I think this might be only for (Shared) Tasks, w/e -- think should be yellow - mob->SayString( - client, - Chat::Yellow, - MAX_ACTIVE_TASKS, - client->GetName() - ); + client->MessageString(Chat::Yellow, NO_TASK_OFFERS, ".", ".", client->GetName()); + } +} + +void TaskManager::SharedTaskSelector(Client *client, Mob *mob, int count, const int *tasks) +{ + LogTasks("[UPDATE] SharedTaskSelector called for array size [{}]", count); + + if (count <= 0 || client->HasTaskRequestCooldownTimer()) { + client->SendTaskRequestCooldownTimerMessage(); + return; + } + + // check if requester already has a shared task (no need to query group/raid members if so) + if (client->GetTaskState()->HasActiveSharedTask()) { + client->MessageString(Chat::Red, SharedTaskMessage::NO_REQUEST_BECAUSE_HAVE_ONE); + return; + } + + // get group/raid member character data from db (need to query for character ids) + auto request = SharedTask::GetRequestCharacters(database, client->CharacterID()); + + // check if any group/raid member already has a shared task (already checked solo character) + bool validation_failed = false; + if (request.group_type != SharedTaskRequestGroupType::Solo) { + auto shared_task_members = SharedTaskMembersRepository::GetWhere( + database, + fmt::format("character_id IN ({}) LIMIT 1", fmt::join(request.character_ids, ","))); + + if (!shared_task_members.empty()) { + validation_failed = true; + + auto it = std::find_if( + request.characters.begin(), request.characters.end(), + [&](const CharacterDataRepository::CharacterData &char_data) { + return char_data.id == shared_task_members.front().character_id; + } + ); + + if (it != request.characters.end()) { + if (request.group_type == SharedTaskRequestGroupType::Group) { + client->MessageString( + Chat::Red, + SharedTaskMessage::NO_REQUEST_BECAUSE_GROUP_HAS_ONE, + it->name.c_str()); + } + else { + client->MessageString( + Chat::Red, + SharedTaskMessage::NO_REQUEST_BECAUSE_RAID_HAS_ONE, + it->name.c_str()); + } + } + } + } + + if (!validation_failed) { + // run type and level filters on task selections + int task_list[MAXCHOOSERENTRIES] = {0}; + int task_list_index = 0; + + for (int i = 0; i < count && task_list_index < MAXCHOOSERENTRIES; ++i) { + // todo: are there non repeatable shared tasks? (would need to check all group/raid members) + auto task = tasks[i]; + if (m_task_data[task] && + m_task_data[task]->type == TaskType::Shared && + request.lowest_level >= m_task_data[task]->min_level && + (m_task_data[task]->max_level == 0 || request.highest_level <= m_task_data[task]->max_level)) { + task_list[task_list_index++] = task; + } + } + + // check if any tasks are left to offer after filtering + if (task_list_index > 0) { + SendSharedTaskSelector(client, mob, task_list_index, task_list); + } + else { + client->MessageString(Chat::Red, SharedTaskMessage::YOU_DO_NOT_MEET_REQ_AVAILABLE); + } } } // sends task selector to client void TaskManager::SendTaskSelector(Client *client, Mob *mob, int task_count, int *task_list) { - if (client->ClientVersion() >= EQ::versions::ClientVersion::RoF) { - SendTaskSelectorNew(client, mob, task_count, task_list); - return; - } - // Titanium OpCode: 0x5e7c LogTasks("TaskSelector for [{}] Tasks", task_count); int player_level = client->GetLevel(); @@ -658,98 +769,6 @@ void TaskManager::SendTaskSelector(Client *client, Mob *mob, int task_count, int } } - int valid_task_count = 0; - - for (int i = 0; i < task_count; i++) { - if (!ValidateLevel(task_list[i], player_level)) { - continue; - } - if (client->IsTaskActive(task_list[i])) { - continue; - } - if (!IsTaskRepeatable(task_list[i]) && client->IsTaskCompleted(task_list[i])) { - continue; - } - - valid_task_count++; - } - - if (valid_task_count == 0) { - return; - } - - SerializeBuffer buf(50 * valid_task_count); - - buf.WriteUInt32(valid_task_count); - buf.WriteUInt32(2); // task type, live doesn't let you send more than one type, but we do? - buf.WriteUInt32(mob->GetID()); - - for (int task_index = 0; task_index < task_count; task_index++) { - if (!ValidateLevel(task_list[task_index], player_level)) { - continue; - } - if (client->IsTaskActive(task_list[task_index])) { - continue; - } - if (!IsTaskRepeatable(task_list[task_index]) && client->IsTaskCompleted(task_list[task_index])) { - continue; - } - - buf.WriteUInt32(task_list[task_index]); // task_id - - // affects color, difficulty? - if (client->ClientVersion() != EQ::versions::ClientVersion::Titanium) { - buf.WriteFloat(1.0f); - } - buf.WriteUInt32(m_task_data[task_list[task_index]]->duration); - buf.WriteUInt32(static_cast(m_task_data[task_list[task_index]]->duration_code)); - - buf.WriteString(m_task_data[task_list[task_index]]->title); // max 64 with null - buf.WriteString(m_task_data[task_list[task_index]]->description); // max 4000 with null - - // Has reward set flag - if (client->ClientVersion() != EQ::versions::ClientVersion::Titanium) { - buf.WriteUInt8(0); - } - - buf.WriteUInt32(m_task_data[task_list[task_index]]->activity_count); - - for (int activity_index = 0; - activity_index < m_task_data[task_list[task_index]]->activity_count; - ++activity_index) { - buf.WriteUInt32(activity_index); // ActivityNumber - auto &activity = m_task_data[task_list[task_index]]->activity_information[activity_index]; - buf.WriteUInt32(activity.activity_type); - buf.WriteUInt32(0); // solo, group, raid? - buf.WriteString(activity.target_name); // max length 64, "target name" so like loot x foo from bar (this is bar) - buf.WriteString(activity.item_list); // max length 64 in these clients - buf.WriteUInt32(activity.goal_count); - buf.WriteInt32(activity.skill_id); - buf.WriteInt32(activity.spell_id); - buf.WriteInt32(activity.zone_ids.empty() ? 0 : activity.zone_ids.front()); - buf.WriteString(activity.description_override); - } - } - - auto outapp = new EQApplicationPacket(OP_OpenNewTasksWindow, buf); - - client->QueuePacket(outapp); - safe_delete(outapp); -} - -void TaskManager::SendTaskSelectorNew(Client *client, Mob *mob, int task_count, int *task_list) -{ - LogTasks("SendTaskSelectorNew for [{}] Tasks", task_count); - - int player_level = client->GetLevel(); - - // Check if any of the tasks exist - for (int i = 0; i < task_count; i++) { - if (m_task_data[task_list[i]] != nullptr) { - break; - } - } - int valid_tasks_count = 0; for (int task_index = 0; task_index < task_count; task_index++) { if (!ValidateLevel(task_list[task_index], player_level)) { @@ -788,47 +807,36 @@ void TaskManager::SendTaskSelectorNew(Client *client, Mob *mob, int task_count, continue; } - buf.WriteUInt32(task_list[i]); // task_id - buf.WriteFloat(1.0f); // affects color, difficulty? - buf.WriteUInt32(m_task_data[task_list[i]]->duration); - buf.WriteUInt32(static_cast(m_task_data[task_list[i]]->duration_code)); // 1 = Short, 2 = Medium, 3 = Long, anything else Unlimited - - buf.WriteString(m_task_data[task_list[i]]->title); // max 64 with null - buf.WriteString(m_task_data[task_list[i]]->description); // max 4000 with null - - buf.WriteUInt8(0); // Has reward set flag - buf.WriteUInt32(m_task_data[task_list[i]]->activity_count); // activity_count - - for (int j = 0; j < m_task_data[task_list[i]]->activity_count; ++j) { - buf.WriteUInt32(j); // ActivityNumber - auto &activity = m_task_data[task_list[i]]->activity_information[j]; - buf.WriteUInt32(activity.activity_type); // ActivityType - buf.WriteUInt32(0); // solo, group, raid? - buf.WriteString(activity.target_name); // max length 64, "target name" so like loot x foo from bar (this is bar) - - // this string is item names - buf.WriteLengthString(activity.item_list); - - buf.WriteUInt32(activity.goal_count); // GoalCount - - // this string is skill IDs? probably one of the "use on" tasks - buf.WriteLengthString(activity.skill_list); - - // this string is spell IDs? probably one of the "use on" tasks - buf.WriteLengthString(activity.spell_list); - - //buf.WriteString(itoa(Tasks[TaskList[i]]->activity_information[activity_id].ZoneID)); - buf.WriteString(activity.zones); // Zone number in ascii max length 64, can be multiple with separated by ; - buf.WriteString(activity.description_override); // max length 128 -- overrides the automatic descriptions - // this doesn't appear to be shown to the client at all and isn't the same as zones ... defaults to '0' though - buf.WriteString(activity.zones); // Zone number in ascii max length 64, probably can be separated by ; too, haven't found it used - } + buf.WriteUInt32(task_list[i]); // task_id + m_task_data[task_list[i]]->SerializeSelector(buf, client->ClientVersion()); } - auto outapp = new EQApplicationPacket(OP_OpenNewTasksWindow, buf); + auto outapp = std::make_unique(OP_TaskSelectWindow, buf); + client->QueuePacket(outapp.get()); +} - client->QueuePacket(outapp); - safe_delete(outapp); +void TaskManager::SendSharedTaskSelector(Client *client, Mob *mob, int task_count, int *task_list) +{ + LogTasks("SendSharedTaskSelector for [{}] Tasks", task_count); + + // request timer is only set when shared task selection shown (not for failed validations) + client->StartTaskRequestCooldownTimer(); + + SerializeBuffer buf; + + buf.WriteUInt32(task_count); // number of tasks + // shared task selection (live doesn't mix types) makes client send shared task specific opcode for accepts + buf.WriteUInt32(static_cast(TaskType::Shared)); + buf.WriteUInt32(mob->GetID()); // task giver entity id + + for (int i = 0; i < task_count; ++i) { + int task_id = task_list[i]; + buf.WriteUInt32(task_id); + m_task_data[task_id]->SerializeSelector(buf, client->ClientVersion()); + } + + auto outapp = std::make_unique(OP_SharedTaskSelectWindow, buf); + client->QueuePacket(outapp.get()); } int TaskManager::GetActivityCount(int task_id) @@ -869,7 +877,7 @@ void TaskManager::ExplainTask(Client *client, int task_id) sprintf(ptr, "Act: %3i: ", i); ptr = ptr + strlen(ptr); switch (m_task_data[task_id]->activity_information[i].activity_type) { - case ActivityDeliver: + case TaskActivityType::Deliver: sprintf(ptr, "Deliver"); break; } @@ -953,35 +961,15 @@ void TaskManager::SendTaskActivityShort(Client *client, int task_id, int activit { // This activity_information Packet is sent for activities that have not yet been unlocked and appear as ??? // in the client. - - TaskActivityShort_Struct *task_activity_short; - if (client->ClientVersionBit() & EQ::versions::maskRoFAndLater) { - auto outapp = new EQApplicationPacket(OP_TaskActivity, 25); - outapp->WriteUInt32(client_task_index); - outapp->WriteUInt32(static_cast(m_task_data[task_id]->type)); - outapp->WriteUInt32(task_id); - outapp->WriteUInt32(activity_id); - outapp->WriteUInt32(0); - outapp->WriteUInt32(0xffffffff); - outapp->WriteUInt8(0); - client->FastQueuePacket(&outapp); - - return; - } - - auto outapp = new EQApplicationPacket(OP_TaskActivity, sizeof(TaskActivityShort_Struct)); - - task_activity_short = (TaskActivityShort_Struct *) outapp->pBuffer; - task_activity_short->TaskSequenceNumber = client_task_index; - task_activity_short->unknown2 = static_cast(m_task_data[task_id]->type); - task_activity_short->TaskID = task_id; - task_activity_short->ActivityID = activity_id; - task_activity_short->unknown3 = 0x000000; - task_activity_short->ActivityType = 0xffffffff; - task_activity_short->unknown4 = 0x00000000; - - client->QueuePacket(outapp); - safe_delete(outapp); + auto outapp = std::make_unique(OP_TaskActivity, 25); + outapp->WriteUInt32(client_task_index); + outapp->WriteUInt32(static_cast(m_task_data[task_id]->type)); + outapp->WriteUInt32(task_id); + outapp->WriteUInt32(activity_id); + outapp->WriteUInt32(0); + outapp->WriteUInt32(0xffffffff); + outapp->WriteUInt8(m_task_data[task_id]->activity_information[activity_id].optional ? 1 : 0); + client->QueuePacket(outapp.get()); } void TaskManager::SendTaskActivityLong( @@ -989,85 +977,6 @@ void TaskManager::SendTaskActivityLong( int task_id, int activity_id, int client_task_index, - bool optional, - bool task_complete -) -{ - - if (client->ClientVersion() >= EQ::versions::ClientVersion::RoF) { - SendTaskActivityNew(client, task_id, activity_id, client_task_index, optional, task_complete); - return; - } - - SerializeBuffer buf(100); - - buf.WriteUInt32(client_task_index); - buf.WriteUInt32(static_cast(m_task_data[task_id]->type)); - buf.WriteUInt32(task_id); - buf.WriteUInt32(activity_id); - buf.WriteUInt32(0); // unknown3 - - // We send our 'internal' types as ActivityCastOn. text3 should be set to the activity_information description, so it makes - // no difference to the client. All activity_information updates will be done based on our interal activity_information types. - if ((m_task_data[task_id]->activity_information[activity_id].activity_type > 0) && - m_task_data[task_id]->activity_information[activity_id].activity_type < 100) { - buf.WriteUInt32(m_task_data[task_id]->activity_information[activity_id].activity_type); - } - else { - buf.WriteUInt32(ActivityCastOn); - } // w/e! - - buf.WriteUInt32(optional); - buf.WriteUInt32(0); // solo, group, raid - - buf.WriteString(m_task_data[task_id]->activity_information[activity_id].target_name); // target name string - buf.WriteString(m_task_data[task_id]->activity_information[activity_id].item_list); // item name list - - if (m_task_data[task_id]->activity_information[activity_id].activity_type != ActivityGiveCash) { - buf.WriteUInt32(m_task_data[task_id]->activity_information[activity_id].goal_count); - } - else { - // For our internal type GiveCash, where the goal count has the amount of cash that must be given, - // we don't want the donecount and goalcount fields cluttered up with potentially large numbers, so we just - // send a goalcount of 1, and a bit further down, a donecount of 1 if the activity_information is complete, 0 otherwise. - // The text3 field should decribe the exact activity_information goal, e.g. give 3500gp to Hasten Bootstrutter. - buf.WriteUInt32(1); - } - - buf.WriteUInt32(m_task_data[task_id]->activity_information[activity_id].skill_id); - buf.WriteUInt32(m_task_data[task_id]->activity_information[activity_id].spell_id); - buf.WriteUInt32( - m_task_data[task_id]->activity_information[activity_id].zone_ids.empty() ? 0 - : m_task_data[task_id]->activity_information[activity_id].zone_ids.front()); - buf.WriteUInt32(0); - - buf.WriteString(m_task_data[task_id]->activity_information[activity_id].description_override); - - if (m_task_data[task_id]->activity_information[activity_id].activity_type != ActivityGiveCash) { - buf.WriteUInt32(client->GetTaskActivityDoneCount(m_task_data[task_id]->type, client_task_index, activity_id)); - } - else { - // For internal activity_information types, done_count is either 1 if the activity_information is complete, 0 otherwise. - buf.WriteUInt32((client->GetTaskActivityDoneCount(m_task_data[task_id]->type, client_task_index, activity_id) >= - m_task_data[task_id]->activity_information[activity_id].goal_count)); - } - - buf.WriteUInt32(1); // unknown - - auto outapp = new EQApplicationPacket(OP_TaskActivity, buf); - - client->QueuePacket(outapp); - safe_delete(outapp); - -} - -// Used only by RoF+ Clients -void TaskManager::SendTaskActivityNew( - Client *client, - int task_id, - int activity_id, - int client_task_index, - bool optional, bool task_complete ) { @@ -1079,66 +988,89 @@ void TaskManager::SendTaskActivityNew( buf.WriteUInt32(activity_id); buf.WriteUInt32(0); // unknown3 - // We send our 'internal' types as ActivityCastOn. text3 should be set to the activity_information description, so it makes - // no difference to the client. All activity_information updates will be done based on our interal activity_information types. - if ((m_task_data[task_id]->activity_information[activity_id].activity_type > 0) && - m_task_data[task_id]->activity_information[activity_id].activity_type < 100) { - buf.WriteUInt32(m_task_data[task_id]->activity_information[activity_id].activity_type); - } - else { - buf.WriteUInt32(ActivityCastOn); - } // w/e! + const auto &activity = m_task_data[task_id]->activity_information[activity_id]; + int done_count = client->GetTaskActivityDoneCount(m_task_data[task_id]->type, client_task_index, activity_id); - buf.WriteUInt8(optional); - buf.WriteUInt32(0); // solo, group, raid + activity.SerializeObjective(buf, client->ClientVersion(), done_count); - // One of these unknown fields maybe related to the 'Use On' activity_information types - buf.WriteString(m_task_data[task_id]->activity_information[activity_id].target_name); // target name string + auto outapp = std::make_unique(OP_TaskActivity, buf); + client->QueuePacket(outapp.get()); +} - buf.WriteLengthString(m_task_data[task_id]->activity_information[activity_id].item_list); // item name list - - // Goal Count - if (m_task_data[task_id]->activity_information[activity_id].activity_type != ActivityGiveCash) { - buf.WriteUInt32(m_task_data[task_id]->activity_information[activity_id].goal_count); - } - else { - buf.WriteUInt32(1); - } // GoalCount - - // skill ID list ; separated - buf.WriteLengthString(m_task_data[task_id]->activity_information[activity_id].skill_list); - - // spelll ID list ; separated -- unsure wtf we're doing here - buf.WriteLengthString(m_task_data[task_id]->activity_information[activity_id].spell_list); - - buf.WriteString(m_task_data[task_id]->activity_information[activity_id].zones); - buf.WriteUInt32(0); // unknown7 - - buf.WriteString(m_task_data[task_id]->activity_information[activity_id].description_override); // description override - - if (m_task_data[task_id]->activity_information[activity_id].activity_type != ActivityGiveCash) { - buf.WriteUInt32( - client->GetTaskActivityDoneCount( - m_task_data[task_id]->type, - client_task_index, - activity_id - )); // done_count - } - else { - // For internal activity_information types, done_count is either 1 if the activity_information is complete, 0 otherwise. - buf.WriteUInt32((client->GetTaskActivityDoneCount(m_task_data[task_id]->type, client_task_index, activity_id) >= - m_task_data[task_id]->activity_information[activity_id].goal_count)); +void TaskManager::SendActiveTaskToClient( + ClientTaskInformation *task, + Client *client, + int task_index, + bool task_complete +) +{ + auto state = client->GetTaskState(); + if (!state) { + return; } - buf.WriteUInt8(1); // unknown9 + int start_time = task->accepted_time; + int task_id = task->task_id; + auto task_type = m_task_data[task_id]->type; + auto task_duration = m_task_data[task_id]->duration; - buf.WriteString(m_task_data[task_id]->activity_information[activity_id].zones); + SendActiveTaskDescription( + client, + task_id, + *task, + start_time, + task_duration, + false + ); - auto outapp = new EQApplicationPacket(OP_TaskActivity, buf); + LogTasks("[SendActiveTasksToClient] task_id [{}] activity_count [{}] task_index [{}]", + task_id, + GetActivityCount(task_id), + task_index); - client->QueuePacket(outapp); - safe_delete(outapp); + int sequence = 0; + int fixed_index = task_index; + for (int activity_id = 0; activity_id < GetActivityCount(task_id); activity_id++) { + if (client->GetTaskActivityState(task_type, fixed_index, activity_id) != ActivityHidden) { + LogTasks( + "[SendActiveTasksToClient] (Long Update) task_id [{}] activity_id [{}] fixed_index [{}] task_complete [{}]", + task_id, + activity_id, + fixed_index, + task_complete ? "true" : "false" + ); + if (activity_id == GetActivityCount(task_id) - 1) { + SendTaskActivityLong( + client, + task_id, + activity_id, + fixed_index, + task_complete + ); + } + else { + SendTaskActivityLong( + client, + task_id, + activity_id, + fixed_index, + 0 + ); + } + } + else { + LogTasks( + "[SendActiveTasksToClient] (Short Update) task_id [{}] activity_id [{}] fixed_index [{}]", + task_id, + activity_id, + fixed_index + ); + + SendTaskActivityShort(client, task_id, activity_id, fixed_index); + } + sequence++; + } } void TaskManager::SendActiveTasksToClient(Client *client, bool task_complete) @@ -1148,64 +1080,27 @@ void TaskManager::SendActiveTasksToClient(Client *client, bool task_complete) return; } - for (int task_index = 0; task_index < MAXACTIVEQUESTS + 1; task_index++) { - int task_id = state->m_active_tasks[task_index].task_id; - if ((task_id == 0) || (m_task_data[task_id] == 0)) { + // task + if (state->m_active_task.task_id != TASKSLOTEMPTY) { + SendActiveTaskToClient(&state->m_active_task, client, 0, task_complete); + } + + // shared task + if (state->m_active_shared_task.task_id != TASKSLOTEMPTY) { + SendActiveTaskToClient(&state->m_active_shared_task, client, 0, task_complete); + } + + // quests + for (int task_index = 0; task_index < MAXACTIVEQUESTS; task_index++) { + int task_id = state->m_active_quests[task_index].task_id; + if ((task_id == 0) || (m_task_data[task_id] == nullptr)) { continue; } - int start_time = state->m_active_tasks[task_index].accepted_time; - SendActiveTaskDescription( - client, task_id, state->m_active_tasks[task_index], start_time, m_task_data[task_id]->duration, - false - ); - LogTasks("[SendActiveTasksToClient] task_id [{}] activity_count [{}]", task_id, GetActivityCount(task_id)); + LogTasksDetail("--"); + LogTasksDetail("[SendActiveTasksToClient] Task [{}]", m_task_data[task_id]->title); - int sequence = 0; - int fixed_index = m_task_data[task_id]->type == TaskType::Task ? 0 : task_index - 1; // hmmm fuck - for (int activity_id = 0; activity_id < GetActivityCount(task_id); activity_id++) { - if (client->GetTaskActivityState(m_task_data[task_id]->type, fixed_index, activity_id) != ActivityHidden) { - LogTasks( - "[SendActiveTasksToClient] (Long Update) task_id [{}] activity_id [{}] fixed_index [{}] task_complete [{}]", - task_id, - activity_id, - fixed_index, - task_complete ? "true" : "false" - ); - - if (activity_id == GetActivityCount(task_id) - 1) { - SendTaskActivityLong( - client, - task_id, - activity_id, - fixed_index, - m_task_data[task_id]->activity_information[activity_id].optional, - task_complete - ); - } - else { - SendTaskActivityLong( - client, - task_id, - activity_id, - fixed_index, - m_task_data[task_id]->activity_information[activity_id].optional, - 0 - ); - } - } - else { - LogTasks( - "[SendActiveTasksToClient] (Short Update) task_id [{}] activity_id [{}] fixed_index [{}]", - task_id, - activity_id, - fixed_index - ); - - SendTaskActivityShort(client, task_id, activity_id, fixed_index); - } - sequence++; - } + SendActiveTaskToClient(&state->m_active_quests[task_index], client, task_index, task_complete); } } @@ -1241,16 +1136,10 @@ void TaskManager::SendSingleActiveTaskToClient( activity_id, task_complete); if (activity_id == GetActivityCount(task_id) - 1) { - SendTaskActivityLong( - client, task_id, activity_id, task_info.slot, - m_task_data[task_id]->activity_information[activity_id].optional, task_complete - ); + SendTaskActivityLong(client, task_id, activity_id, task_info.slot, task_complete); } else { - SendTaskActivityLong( - client, task_id, activity_id, task_info.slot, - m_task_data[task_id]->activity_information[activity_id].optional, 0 - ); + SendTaskActivityLong(client, task_id, activity_id, task_info.slot, 0); } } else { @@ -1320,7 +1209,9 @@ void TaskManager::SendActiveTaskDescription( task_description_header->TaskID = task_id; task_description_header->open_window = bring_up_task_journal; task_description_header->task_type = static_cast(m_task_data[task_id]->type); - task_description_header->reward_type = 0; // TODO: 4 says Radiant Crystals else Ebon Crystals when shared task + + constexpr uint32_t reward_radiant_type = 4; // Radiant Crystals, anything else is Ebon for shared tasks + task_description_header->reward_type = m_task_data[task_id]->reward_radiant_crystals > 0 ? reward_radiant_type : 0; Ptr = (char *) task_description_header + sizeof(TaskDescriptionHeader_Struct); @@ -1362,7 +1253,11 @@ void TaskManager::SendActiveTaskDescription( Ptr += m_task_data[task_id]->item_link.length() + 1; tdt = (TaskDescriptionTrailer_Struct *) Ptr; - tdt->Points = 0x00000000; // Points Count TODO: this does have a visible affect on the client ... + // shared tasks show radiant/ebon crystal reward, non-shared tasks show generic points + tdt->Points = m_task_data[task_id]->reward_ebon_crystals; + if (m_task_data[task_id]->reward_radiant_crystals > 0) { + tdt->Points = m_task_data[task_id]->reward_radiant_crystals; + } tdt->has_reward_selection = 0; // TODO: new rewards window client->QueuePacket(outapp); @@ -1375,21 +1270,30 @@ bool TaskManager::LoadClientState(Client *client, ClientTaskState *client_task_s return false; } + client->SetSharedTaskId(0); + int character_id = client->CharacterID(); client_task_state->m_active_task_count = 0; LogTasks("[LoadClientState] for character_id [{}]", character_id); + // in a case where a client somehow lost local state with what state exists in world - we need + // to perform an inverse sync where we inject the task + SyncClientSharedTaskStateToLocal(client); + auto character_tasks = CharacterTasksRepository::GetWhere( database, fmt::format("charid = {} ORDER BY acceptedtime", character_id) ); for (auto &character_task: character_tasks) { - int task_id = character_task.taskid; - int slot = character_task.slot; - auto type = static_cast(character_task.type); + int task_id = character_task.taskid; + int slot = character_task.slot; + + // this used to be loaded from character_tasks + // this should just load from the tasks table + auto type = task_manager->GetTaskType(character_task.taskid); if ((task_id < 0) || (task_id >= MAXTASKS)) { LogTasks( @@ -1400,6 +1304,8 @@ bool TaskManager::LoadClientState(Client *client, ClientTaskState *client_task_s } // client data bucket pointer + // this actually fetches the proper task type instances to be loaded with data + // whether it be quest / task / shared task auto task_info = client_task_state->GetClientTaskInfo(type, slot); if (task_info == nullptr) { LogTasks( @@ -1423,14 +1329,16 @@ bool TaskManager::LoadClientState(Client *client, ClientTaskState *client_task_s i.activity_id = -1; } + // this check keeps a lot of core task updating code from working properly (shared or otherwise) if (type == TaskType::Quest) { ++client_task_state->m_active_task_count; } LogTasks( - "[LoadClientState] character_id [{}] task_id [{}] accepted_time [{}]", + "[LoadClientState] character_id [{}] task_id [{}] slot [{}] accepted_time [{}]", character_id, task_id, + slot, character_task.acceptedtime ); } @@ -1465,12 +1373,18 @@ bool TaskManager::LoadClientState(Client *client, ClientTaskState *client_task_s continue; } + // type: task ClientTaskInformation *task_info = nullptr; if (client_task_state->m_active_task.task_id == task_id) { task_info = &client_task_state->m_active_task; } - // wasn't task + // type: shared task + if (client_task_state->m_active_shared_task.task_id == task_id) { + task_info = &client_task_state->m_active_shared_task; + } + + // type: quest if (task_info == nullptr) { for (auto &active_quest : client_task_state->m_active_quests) { if (active_quest.task_id == task_id) { @@ -1511,6 +1425,8 @@ bool TaskManager::LoadClientState(Client *client, ClientTaskState *client_task_s ); } + SyncClientSharedTaskState(client, client_task_state); + if (RuleB(TaskSystem, RecordCompletedTasks)) { CompletedTaskInformation completed_task_information{}; @@ -1639,18 +1555,355 @@ bool TaskManager::LoadClientState(Client *client, ClientTaskState *client_task_s } } + LogTasksDetail( + "[LoadClientState] m_active_task task_id is [{}] slot [{}]", + client_task_state->m_active_task.task_id, + client_task_state->m_active_task.slot + ); if (client_task_state->m_active_task.task_id != TASKSLOTEMPTY) { client_task_state->UnlockActivities(character_id, client_task_state->m_active_task); + + // purely debugging + LogTasksDetail( + "[LoadClientState] Fetching task info for character_id [{}] task [{}] slot [{}] current_step [{}] accepted_time [{}] updated [{}]", + character_id, + client_task_state->m_active_task.task_id, + client_task_state->m_active_task.slot, + client_task_state->m_active_task.current_step, + client_task_state->m_active_task.accepted_time, + client_task_state->m_active_task.updated + ); + + TaskInformation *p_task_data = task_manager->m_task_data[client_task_state->m_active_task.task_id]; + if (p_task_data != nullptr) { + for (int i = 0; i < p_task_data->activity_count; i++) { + if (client_task_state->m_active_task.activity[i].activity_id >= 0) { + LogTasksDetail( + "[LoadClientState] -- character_id [{}] task [{}] activity_id [{}] done_count [{}] activity_state [{}] updated [{}] sequence [{}]", + character_id, + client_task_state->m_active_task.task_id, + client_task_state->m_active_task.activity[i].activity_id, + client_task_state->m_active_task.activity[i].done_count, + client_task_state->m_active_task.activity[i].activity_state, + client_task_state->m_active_task.activity[i].updated, + p_task_data->sequence_mode + ); + } + } + } } - // TODO: shared + // shared task + LogTasksDetail( + "[LoadClientState] m_active_shared_task task_id is [{}] slot [{}]", + client_task_state->m_active_shared_task.task_id, + client_task_state->m_active_shared_task.slot + ); + if (client_task_state->m_active_shared_task.task_id != TASKSLOTEMPTY) { + client_task_state->UnlockActivities(character_id, client_task_state->m_active_shared_task); + } + + // quests (max 20 or 40 depending on client) for (auto &active_quest : client_task_state->m_active_quests) { if (active_quest.task_id != TASKSLOTEMPTY) { client_task_state->UnlockActivities(character_id, active_quest); } } - LogTasks("[LoadClientState] for Character ID [{}] DONE!", character_id); + LogTasksDetail("[LoadClientState] for Character ID [{}] DONE!", character_id); + LogTasksDetail("---", character_id); return true; } + +void TaskManager::SyncClientSharedTaskState(Client *c, ClientTaskState *cts) +{ + LogTasksDetail( + "[SyncClientSharedTaskState] Syncing client shared task state" + ); + + SyncClientSharedTaskWithPersistedState(c, cts); + SyncClientSharedTaskRemoveLocalIfNotExists(c, cts); +} + +void TaskManager::SyncClientSharedTaskWithPersistedState(Client *c, ClientTaskState *cts) +{ + auto character_tasks = CharacterTasksRepository::GetWhere( + database, + fmt::format("charid = {} ORDER BY acceptedtime", c->CharacterID()) + ); + + for (auto &character_task: character_tasks) { + if (character_task.type == TASK_TYPE_SHARED) { + auto st = SharedTaskMembersRepository::GetWhere( + database, + fmt::format( + "character_id = {}", + c->CharacterID() + ) + ); + + if (!st.empty()) { + int64 shared_task_id = st[0].shared_task_id; + auto activities = SharedTaskActivityStateRepository::GetWhere( + database, + fmt::format( + "shared_task_id = {}", + shared_task_id + ) + ); + + ClientTaskInformation *shared_task = nullptr; + shared_task = &cts->m_active_shared_task; + + // has active shared task + if (cts->HasActiveSharedTask()) { + + LogTasksDetail( + "[SyncClientSharedTaskWithPersistedState] Client [{}] has shared_task, sync with database", + c->GetCleanName() + ); + + bool fell_behind_state = false; + for (auto &a: activities) { + + LogTasksDetail( + "[LoadClientState] shared_task loop local [{}] shared [{}]", + shared_task->activity[a.activity_id].done_count, + a.done_count + ); + + // we're behind shared task state, update self + if (shared_task->activity[a.activity_id].done_count < a.done_count) { + + // update done count + shared_task->activity[a.activity_id].done_count = a.done_count; + + // activity state + shared_task->activity[a.activity_id].activity_state = + (a.completed_time > 0 ? ActivityCompleted : ActivityHidden); + + // set flag to persist later + fell_behind_state = true; + } + } + + // fell behind, force a save of client state + if (fell_behind_state) { + SaveClientState(c, cts); + } + + c->SetSharedTaskId(shared_task_id); + } + } + } + } +} + +void TaskManager::SyncClientSharedTaskRemoveLocalIfNotExists(Client *c, ClientTaskState *cts) +{ + // has active shared task + if (cts->HasActiveSharedTask()) { + auto members = SharedTaskMembersRepository::GetWhere( + database, + fmt::format( + "character_id = {}", + c->CharacterID() + ) + ); + + // if we don't actually have a membership anywhere, remove ourself locally + if (members.empty()) { + LogTasksDetail( + "[SyncClientSharedTaskRemoveLocalIfNotExists] Client [{}] Shared task [{}] doesn't exist in world, removing from local", + c->GetCleanName(), + cts->m_active_shared_task.task_id + ); + + std::string delete_where = fmt::format( + "charid = {} and taskid = {}", + c->CharacterID(), + cts->m_active_shared_task.task_id + ); + CharacterTasksRepository::DeleteWhere(database, delete_where); + CharacterActivitiesRepository::DeleteWhere(database, delete_where); + + c->MessageString(Chat::Yellow, SharedTaskMessage::YOU_ARE_NO_LONGER_A_MEMBER, + m_task_data[cts->m_active_shared_task.task_id]->title.c_str()); + + // remove as active task if doesn't exist + cts->m_active_shared_task = {}; + + // persist removal from local record + SaveClientState(c, cts); + } + } +} + +// in a case where a client somehow lost local state with what state exists in world - we need +// to perform an inverse sync where we inject the task +void TaskManager::SyncClientSharedTaskStateToLocal( + Client *c +) +{ + auto character_tasks = CharacterTasksRepository::GetWhere( + database, + fmt::format("charid = {} ORDER BY acceptedtime", c->CharacterID()) + ); + + bool has_character_shared_task = false; + + for (auto &character_task: character_tasks) { + if (character_task.type == TASK_TYPE_SHARED) { + has_character_shared_task = true; + } + } + + if (!has_character_shared_task) { + LogTasksDetail("[SyncClientSharedTaskStateToLocal] We don't have a shared character task locally"); + auto stm = SharedTaskMembersRepository::GetWhere( + database, + fmt::format( + "character_id = {}", + c->CharacterID() + ) + ); + + if (!stm.empty()) { + LogTasksDetail("[SyncClientSharedTaskStateToLocal] We have membership in database"); + auto s = SharedTasksRepository::FindOne( + database, + (int) stm.front().shared_task_id + ); + + if (s.id > 0) { + LogTasksDetail("[SyncClientSharedTaskStateToLocal] Creating entity"); + + // create task locally + auto ct = CharacterTasksRepository::NewEntity(); + ct.charid = (int) c->CharacterID(); + ct.acceptedtime = (int) s.accepted_time; + ct.taskid = (int) s.task_id; + ct.slot = 0; + ct.type = TASK_TYPE_SHARED; + character_tasks.emplace_back(ct); + CharacterTasksRepository::InsertOne(database, ct); + + // create activities locally + auto activities = SharedTaskActivityStateRepository::GetWhere( + database, + fmt::format( + "shared_task_id = {}", + (int) stm.front().shared_task_id + ) + ); + + std::vector character_activities = {}; + + for (auto &a: activities) { + auto ca = CharacterActivitiesRepository::NewEntity(); + ca.completed = a.completed_time > 0; + ca.charid = (int) c->CharacterID(); + ca.donecount = a.done_count; + ca.taskid = s.task_id; + ca.activityid = a.activity_id; + character_activities.emplace_back(ca); + } + CharacterActivitiesRepository::InsertMany(database, character_activities); + } + } + } +} + +void TaskManager::HandleUpdateTasksOnKill(Client *client, uint32 npc_type_id) +{ + for (auto &c: client->GetPartyMembers()) { + if (!c->ClientDataLoaded() || !c->HasTaskState()) { + continue; + } + + LogTasksDetail("[HandleUpdateTasksOnKill] Looping through client [{}]", c->GetCleanName()); + + // loop over the union of tasks and quests + for (auto &active_task : c->GetTaskState()->m_active_tasks) { + auto current_task = &active_task; + if (current_task->task_id == TASKSLOTEMPTY) { + continue; + } + + // Check if there are any active kill activities for this p_task_data + auto p_task_data = m_task_data[current_task->task_id]; + if (p_task_data == nullptr) { + return; + } + + for (int activity_id = 0; activity_id < p_task_data->activity_count; activity_id++) { + ClientActivityInformation *client_activity = ¤t_task->activity[activity_id]; + ActivityInformation *activity_info = &p_task_data->activity_information[activity_id]; + + // We are not interested in completed or hidden activities + if (client_activity->activity_state != ActivityActive) { + continue; + } + + // We are only interested in Kill activities + if (activity_info->activity_type != TaskActivityType::Kill) { + continue; + } + + // Is there a zone restriction on the activity_information ? + if (!activity_info->CheckZone(zone->GetZoneID())) { + LogTasks( + "[HandleUpdateTasksOnKill] character [{}] task_id [{}] activity_id [{}] activity_type [{}] for NPC [{}] failed zone check", + client->GetName(), + current_task->task_id, + activity_id, + static_cast(TaskActivityType::Kill), + npc_type_id + ); + continue; + } + // Is the activity_information to kill this type of NPC ? + switch (activity_info->goal_method) { + case METHODSINGLEID: + if (activity_info->goal_id != npc_type_id) { + LogTasksDetail("[HandleUpdateTasksOnKill] Matched single goal"); + continue; + } + break; + + case METHODLIST: + if (!m_goal_list_manager.IsInList( + activity_info->goal_id, + (int) npc_type_id + )) { + LogTasksDetail("[HandleUpdateTasksOnKill] Matched list goal"); + continue; + } + break; + + default: + // If METHODQUEST, don't updated the activity_information here + continue; + } + + LogTasksDetail("[HandleUpdateTasksOnKill] passed checks"); + + // handle actual update + // legacy eqemu task update logic loops through group on kill of npc to update a single task + if (p_task_data->type != TaskType::Shared) { + LogTasksDetail("[HandleUpdateTasksOnKill] Non-Shared Update"); + c->GetTaskState()->IncrementDoneCount(c, p_task_data, current_task->slot, activity_id); + continue; + } + + LogTasksDetail("[HandleUpdateTasksOnKill] Shared update"); + + // shared tasks only require one client to receive an update to propagate + if (c == client) { + c->GetTaskState()->IncrementDoneCount(c, p_task_data, current_task->slot, activity_id); + } + } + } + } +} diff --git a/zone/task_manager.h b/zone/task_manager.h index f1fe41bd9..16eb51fcd 100644 --- a/zone/task_manager.h +++ b/zone/task_manager.h @@ -6,6 +6,7 @@ #include "task_proximity_manager.h" #include "task_goal_list_manager.h" #include "../common/types.h" +#include "../common/repositories/character_tasks_repository.h" #include #include #include @@ -31,7 +32,6 @@ public: bool LoadClientState(Client *client, ClientTaskState *client_task_state); bool SaveClientState(Client *client, ClientTaskState *client_task_state); void SendTaskSelector(Client *client, Mob *mob, int task_count, int *task_list); - void SendTaskSelectorNew(Client *client, Mob *mob, int task_count, int *task_list); bool ValidateLevel(int task_id, int player_level); std::string GetTaskName(uint32 task_id); TaskType GetTaskType(uint32 task_id); @@ -44,6 +44,7 @@ public: int count, int *tasks ); + void SharedTaskSelector(Client* client, Mob* mob, int count, const int* tasks); void SendActiveTasksToClient(Client *client, bool task_complete = false); void SendSingleActiveTaskToClient( Client *client, @@ -57,15 +58,6 @@ public: int task_id, int activity_id, int client_task_index, - bool optional, - bool task_complete = false - ); - void SendTaskActivityNew( - Client *client, - int task_id, - int activity_id, - int client_task_index, - bool optional, bool task_complete = false ); void SendCompletedTasksToClient(Client *c, ClientTaskState *client_task_state); @@ -77,6 +69,10 @@ public: friend class ClientTaskState; + // shared tasks + void SyncClientSharedTaskState(Client *c, ClientTaskState *cts); + + void HandleUpdateTasksOnKill(Client *client, uint32 npc_type_id); private: TaskGoalListManager m_goal_list_manager; @@ -92,6 +88,13 @@ private: bool bring_up_task_journal = false ); + void SendActiveTaskToClient(ClientTaskInformation *task, Client *client, int task_index, bool task_complete); + + // shared tasks + void SyncClientSharedTaskWithPersistedState(Client *c, ClientTaskState *cts); + void SyncClientSharedTaskRemoveLocalIfNotExists(Client *c, ClientTaskState *cts); + void SendSharedTaskSelector(Client* client, Mob* mob, int task_count, int* task_list); + void SyncClientSharedTaskStateToLocal(Client *c); }; diff --git a/zone/tasks.cpp b/zone/tasks.cpp index 4f240bab5..a524ebd5f 100644 --- a/zone/tasks.cpp +++ b/zone/tasks.cpp @@ -5,8 +5,10 @@ #include "client.h" #include "queryserv.h" #include "quest_parser_collection.h" +#include "string_ids.h" #include "tasks.h" #include "zonedb.h" +#include "../common/repositories/character_task_timers_repository.h" extern QueryServ *QServ; @@ -17,7 +19,7 @@ void Client::LoadClientTaskState() safe_delete(task_state); } - task_state = new ClientTaskState; + task_state = new ClientTaskState(); if (!task_manager->LoadClientState(this, task_state)) { safe_delete(task_state); } @@ -95,10 +97,8 @@ void Client::SendTaskActivityComplete( void Client::SendTaskFailed(int task_id, int task_index, TaskType task_type) { // 0x54eb - char buf[24]; - snprintf(buf, 23, "%d", task_id); - buf[23] = '\0'; - parse->EventPlayer(EVENT_TASK_FAIL, this, buf, 0); + std::string export_string = fmt::format("{}", task_id); + parse->EventPlayer(EVENT_TASK_FAIL, this, export_string, 0); TaskActivityComplete_Struct *task_activity_complete; @@ -118,6 +118,42 @@ void Client::SendTaskFailed(int task_id, int task_index, TaskType task_type) safe_delete(outapp); } +bool Client::HasTaskRequestCooldownTimer() +{ + if (task_request_timer.Check(false)) + { + task_request_timer.Disable(); + } + return (!GetGM() && task_request_timer.Enabled()); +} +void Client::SendTaskRequestCooldownTimerMessage() +{ + if (HasTaskRequestCooldownTimer()) + { + uint32_t seconds = task_request_timer.GetRemainingTime() / 1000; + MessageString(Chat::Yellow, TASK_REQUEST_COOLDOWN_TIMER, + ".", ".", // args start at %3 for this eqstr + GetName(), + fmt::format_int(seconds / 60).c_str(), // minutes + fmt::format_int(seconds % 60).c_str() // seconds + ); + } +} +void Client::StartTaskRequestCooldownTimer() +{ + uint32_t milliseconds = RuleI(TaskSystem, RequestCooldownTimerSeconds) * 1000; + task_request_timer.Start(milliseconds); + + uint32_t size = sizeof(uint32_t); + auto outapp = std::make_unique(OP_TaskRequestTimer, size); + outapp->WriteUInt32(milliseconds); + QueuePacket(outapp.get()); +} + +void Client::PurgeTaskTimers() +{ + CharacterTaskTimersRepository::DeleteWhere(database, fmt::format("character_id = {}", CharacterID())); +} diff --git a/zone/tasks.h b/zone/tasks.h index d3fed2c09..56be4fa8d 100644 --- a/zone/tasks.h +++ b/zone/tasks.h @@ -2,24 +2,12 @@ #define TASKS_H #include "../common/types.h" +#include "../common/tasks.h" #include #include #include #include -#define MAXTASKS 10000 -#define MAXTASKSETS 1000 -#define MAXACTIVEQUESTS 19 // The Client has a hard cap of 19 active quests, 29 in SoD+ -#define MAXCHOOSERENTRIES 40 // The Max Chooser (Task Selector entries) is capped at 40 in the Titanium Client. -#define MAXACTIVITIESPERTASK 20 // The Client has a hard cap of 20 activities per task. -#define TASKSLOTEMPTY 0 // This is used to determine if a client's active task slot is empty. - -// Command Codes for worldserver ServerOP_ReloadTasks -#define RELOADTASKS 0 -#define RELOADTASKGOALLISTS 1 -#define RELOADTASKPROXIMITIES 2 -#define RELOADTASKSETS 3 - class Client; class Mob; @@ -27,123 +15,4 @@ namespace EQ { class ItemInstance; } -typedef enum { - METHODSINGLEID = 0, - METHODLIST = 1, - METHODQUEST = 2 -} TaskMethodType; - -struct ActivityInformation { - int step_number; - int activity_type; - std::string target_name; // name mob, location -- default empty - std::string item_list; // likely defaults to empty - std::string skill_list; // IDs ; separated -- default -1 - std::string spell_list; // IDs ; separated -- default 0 - std::string description_override; // overrides auto generated description -- default empty - int skill_id; // older clients, first id from above - int spell_id; // older clients, first id from above - int goal_id; - TaskMethodType goal_method; - int goal_count; - int deliver_to_npc; - std::vector zone_ids; - std::string zones; // IDs ; searated, ZoneID is the first in this list for older clients -- default empty string - bool optional; - - inline bool CheckZone(int zone_id) - { - if (zone_ids.empty()) { - return true; - } - return std::find(zone_ids.begin(), zone_ids.end(), zone_id) != zone_ids.end(); - } -}; - -typedef enum { - ActivitiesSequential = 0, - ActivitiesStepped = 1 -} SequenceType; - -enum class TaskType { - Task = 0, // can have at max 1 - Shared = 1, // can have at max 1 - Quest = 2, // can have at max 19 or 29 depending on client - E = 3 // can have at max 19 or 29 depending on client, not present in live anymore -}; - -enum class DurationCode { - None = 0, - Short = 1, - Medium = 2, - Long = 3 -}; - -struct TaskInformation { - TaskType type; - int duration; - DurationCode duration_code; // description for time investment for when duration == 0 - std::string title; // max length 64 - std::string description; // max length 4000, 2048 on Tit - std::string reward; - std::string item_link; // max length 128 older clients, item link gets own string - std::string completion_emote; // emote after completing task, yellow. Maybe should make more generic ... but yellow for now! - int reward_id; - int cash_reward; // Expressed in copper - int experience_reward; - int faction_reward; // just a npc_faction_id - TaskMethodType reward_method; - int activity_count; - SequenceType sequence_mode; - int last_step; - short min_level; - short max_level; - bool repeatable; - ActivityInformation activity_information[MAXACTIVITIESPERTASK]; -}; - -typedef enum { - ActivityHidden = 0, - ActivityActive = 1, - ActivityCompleted = 2 -} ActivityState; - -typedef enum { - ActivityDeliver = 1, - ActivityKill = 2, - ActivityLoot = 3, - ActivitySpeakWith = 4, - ActivityExplore = 5, - ActivityTradeSkill = 6, - ActivityFish = 7, - ActivityForage = 8, - ActivityCastOn = 9, - ActivitySkillOn = 10, - ActivityTouch = 11, - ActivityCollect = 13, - ActivityGiveCash = 100 -} ActivityType; - -struct ClientActivityInformation { - int activity_id; - int done_count; - ActivityState activity_state; - bool updated; // Flag so we know if we need to updated the database -}; - -struct ClientTaskInformation { - int slot; // intrusive, but makes things easier :P - int task_id; - int current_step; - int accepted_time; - bool updated; - ClientActivityInformation activity[MAXACTIVITIESPERTASK]; -}; - -struct CompletedTaskInformation { - int task_id; - int completed_time; - bool activity_done[MAXACTIVITIESPERTASK]; -}; - #endif diff --git a/zone/titles.cpp b/zone/titles.cpp index a0cd10a12..8bdabc678 100644 --- a/zone/titles.cpp +++ b/zone/titles.cpp @@ -19,6 +19,7 @@ #include "../common/eq_packet_structs.h" #include "../common/string_util.h" #include "../common/misc_functions.h" +#include "../common/repositories/titles_repository.h" #include "client.h" #include "entity.h" @@ -34,206 +35,191 @@ TitleManager::TitleManager() { bool TitleManager::LoadTitles() { - Titles.clear(); + titles.clear(); std::string query = "SELECT `id`, `skill_id`, `min_skill_value`, `max_skill_value`, " - "`min_aa_points`, `max_aa_points`, `class`, `gender`, `char_id`, " - "`status`, `item_id`, `prefix`, `suffix`, `title_set` FROM titles"; - auto results = database.QueryDatabase(query); - if (!results.Success()) { + "`min_aa_points`, `max_aa_points`, `class`, `gender`, `char_id`, " + "`status`, `item_id`, `prefix`, `suffix`, `title_set` FROM titles"; + auto results = database.QueryDatabase(query); + if (!results.Success() || !results.RowCount()) { return false; } - for (auto row = results.begin(); row != results.end(); ++row) { - TitleEntry Title; - Title.TitleID = atoi(row[0]); - Title.SkillID = (EQ::skills::SkillType) atoi(row[1]); - Title.MinSkillValue = atoi(row[2]); - Title.MaxSkillValue = atoi(row[3]); - Title.MinAAPoints = atoi(row[4]); - Title.MaxAAPoints = atoi(row[5]); - Title.Class = atoi(row[6]); - Title.Gender = atoi(row[7]); - Title.CharID = atoi(row[8]); - Title.Status = atoi(row[9]); - Title.ItemID = atoi(row[10]); - Title.Prefix = row[11]; - Title.Suffix = row[12]; - Title.TitleSet = atoi(row[13]); - Titles.push_back(Title); + for (auto row : results) { + TitleEntry title; + title.title_id = std::stoi(row[0]); + title.skill_id = (EQ::skills::SkillType) std::stoi(row[1]); + title.min_skill_value = std::stoi(row[2]); + title.max_skill_value = std::stoi(row[3]); + title.min_aa_points = std::stoi(row[4]); + title.max_aa_points = std::stoi(row[5]); + title.class_id = std::stoi(row[6]); + title.gender_id = std::stoi(row[7]); + title.character_id = std::stoi(row[8]); + title.status = std::stoi(row[9]); + title.item_id = std::stoi(row[10]); + title.prefix = row[11]; + title.suffix = row[12]; + title.titleset = std::stoi(row[13]); + titles.push_back(title); } return true; } -EQApplicationPacket *TitleManager::MakeTitlesPacket(Client *c) +EQApplicationPacket *TitleManager::MakeTitlesPacket(Client *client) { - std::vector::iterator Iterator; - - std::vector AvailableTitles; - - uint32 Length = 4; - - Iterator = Titles.begin(); - - while(Iterator != Titles.end()) - { - if(!IsClientEligibleForTitle(c, Iterator)) - { - ++Iterator; + std::vector available_titles; + uint32 length = 4; + for (const auto& title : titles) { + if (!IsClientEligibleForTitle(client, title)) { continue; } - AvailableTitles.push_back((*Iterator)); - - Length += Iterator->Prefix.length() + Iterator->Suffix.length() + 6; - - ++Iterator; - + available_titles.push_back(title); + length += title.prefix.length() + title.suffix.length() + 6; } - auto outapp = new EQApplicationPacket(OP_SendTitleList, Length); - - char *Buffer = (char *)outapp->pBuffer; - - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, AvailableTitles.size()); - - Iterator = AvailableTitles.begin(); - - while(Iterator != AvailableTitles.end()) - { - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, Iterator->TitleID); - - VARSTRUCT_ENCODE_STRING(Buffer, Iterator->Prefix.c_str()); - - VARSTRUCT_ENCODE_STRING(Buffer, Iterator->Suffix.c_str()); - - ++Iterator; + auto outapp = new EQApplicationPacket(OP_SendTitleList, length); + char *buffer = (char *)outapp->pBuffer; + VARSTRUCT_ENCODE_TYPE(uint32, buffer, available_titles.size()); + for (const auto& available_title : available_titles) { + VARSTRUCT_ENCODE_TYPE(uint32, buffer, available_title.title_id); + VARSTRUCT_ENCODE_STRING(buffer, available_title.prefix.c_str()); + VARSTRUCT_ENCODE_STRING(buffer, available_title.suffix.c_str()); } return(outapp); } -int TitleManager::NumberOfAvailableTitles(Client *c) +int TitleManager::NumberOfAvailableTitles(Client *client) { - int Count = 0; - - std::vector::iterator Iterator; - - Iterator = Titles.begin(); - - while(Iterator != Titles.end()) - { - if(IsClientEligibleForTitle(c, Iterator)) - ++Count; - - ++Iterator; - } - - return Count; -} - -std::string TitleManager::GetPrefix(int TitleID) -{ - std::vector::iterator Iterator; - - Iterator = Titles.begin(); - - while(Iterator != Titles.end()) - { - if((*Iterator).TitleID == TitleID) - return (*Iterator).Prefix; - - ++Iterator; - } - - return ""; -} - -std::string TitleManager::GetSuffix(int TitleID) -{ - std::vector::iterator Iterator; - - Iterator = Titles.begin(); - - while(Iterator != Titles.end()) - { - if((*Iterator).TitleID == TitleID) - return (*Iterator).Suffix; - - ++Iterator; - } - - return ""; -} - -bool TitleManager::IsClientEligibleForTitle(Client *c, std::vector::iterator Title) -{ - if((Title->CharID >= 0) && (c->CharacterID() != static_cast(Title->CharID))) - return false; - - if((Title->Status >= 0) && (c->Admin() < Title->Status)) - return false; - - if((Title->Gender >= 0) && (c->GetBaseGender() != Title->Gender)) - return false; - - if((Title->Class >= 0) && (c->GetBaseClass() != Title->Class)) - return false; - - if((Title->MinAAPoints >= 0) && (c->GetSpentAA() < static_cast(Title->MinAAPoints))) - return false; - - if((Title->MaxAAPoints >= 0) && (c->GetSpentAA() > static_cast(Title->MaxAAPoints))) - return false; - - if(Title->SkillID >= 0) - { - if ((Title->MinSkillValue >= 0) && (c->GetRawSkill(static_cast(Title->SkillID)) < static_cast(Title->MinSkillValue))) - return false; - - if ((Title->MaxSkillValue >= 0) && (c->GetRawSkill(static_cast(Title->SkillID)) > static_cast(Title->MaxSkillValue))) - return false; - + int count = 0; + for (const auto& title : titles) { + if (IsClientEligibleForTitle(client, title)) { + ++count; } + } - if ((Title->ItemID >= 1) && (c->GetInv().HasItem(Title->ItemID, 0, 0xFF) == INVALID_INDEX)) - return false; - - if((Title->TitleSet > 0) && (!c->CheckTitle(Title->TitleSet))) - return false; - - return true; + return count; } -bool TitleManager::IsNewAATitleAvailable(int AAPoints, int Class) +std::string TitleManager::GetPrefix(int title_id) { - std::vector::iterator Iterator; + if (!title_id) { + return ""; + } - Iterator = Titles.begin(); + for (const auto& title : titles) { + if (title.title_id == title_id) { + return title.prefix; + } + } - while(Iterator != Titles.end()) - { - if((((*Iterator).Class == -1) || ((*Iterator).Class == Class)) && ((*Iterator).MinAAPoints == AAPoints)) - return true; + return ""; +} - ++Iterator; +std::string TitleManager::GetSuffix(int title_id) +{ + if (!title_id) { + return ""; + } + + for (const auto& title : titles) { + if (title.title_id == title_id) { + return title.suffix; + } + } + + return ""; +} + +bool TitleManager::HasTitle(Client* client, uint32 title_id) +{ + if (!client || !title_id) { + return false; + } + + for (const auto& title : titles) { + if (title.title_id == title_id) { + return IsClientEligibleForTitle(client, title); + } } return false; } -bool TitleManager::IsNewTradeSkillTitleAvailable(int SkillID, int SkillValue) +bool TitleManager::IsClientEligibleForTitle(Client *client, TitleEntry title) { - std::vector::iterator Iterator; + if (!client) { + return false; + } - Iterator = Titles.begin(); + if (title.character_id >= 0 && client->CharacterID() != static_cast(title.character_id)) { + return false; + } - while(Iterator != Titles.end()) - { - if(((*Iterator).SkillID == SkillID) && ((*Iterator).MinSkillValue == SkillValue)) + if (title.status >= 0 && client->Admin() < title.status) { + return false; + } + + if (title.gender_id >= 0 && client->GetBaseGender() != title.gender_id) { + return false; + } + + if (title.class_id >= 0 && client->GetBaseClass() != title.class_id) { + return false; + } + + if (title.min_aa_points >= 0 && client->GetSpentAA() < title.min_aa_points) { + return false; + } + + if (title.max_aa_points >= 0 && client->GetSpentAA() > title.max_aa_points) { + return false; + } + + if (title.skill_id >= 0) { + auto skill_id = static_cast(title.skill_id); + if (title.min_skill_value >= 0 && client->GetRawSkill(skill_id) < static_cast(title.min_skill_value)) { + return false; + } + + if (title.max_skill_value >= 0 && client->GetRawSkill(skill_id) > static_cast(title.max_skill_value)) { + return false; + } + } + + if (title.item_id >= 1 && client->GetInv().HasItem(title.item_id) == INVALID_INDEX) { + return false; + } + + if (title.titleset > 0 && !client->CheckTitle(title.titleset)) { + return false; + } + + return true; +} + +bool TitleManager::IsNewAATitleAvailable(int aa_points, int class_id) +{ + for (const auto& title : titles) { + if ( + (title.class_id == -1 || title.class_id == class_id) && + title.min_aa_points == aa_points + ) { return true; + } + } - ++Iterator; + return false; +} + +bool TitleManager::IsNewTradeSkillTitleAvailable(int skill_id, int skill_value) +{ + for (const auto& title : titles) { + if (title.skill_id == skill_id && title.min_skill_value == skill_value) { + return true; + } } return false; @@ -241,142 +227,138 @@ bool TitleManager::IsNewTradeSkillTitleAvailable(int SkillID, int SkillValue) void TitleManager::CreateNewPlayerTitle(Client *client, const char *title) { - if(!client || !title) + if (!client || !title) { return; - - auto escTitle = new char[strlen(title) * 2 + 1]; + } client->SetAATitle(title); - database.DoEscapeString(escTitle, title, strlen(title)); - auto query = StringFormat("SELECT `id` FROM titles " - "WHERE `prefix` = '%s' AND char_id = %i", - escTitle, client->CharacterID()); - auto results = database.QueryDatabase(query); - if (results.Success() && results.RowCount() > 0){ - safe_delete_array(escTitle); - return; + auto query = fmt::format( + "SELECT `id` FROM titles WHERE `prefix` = '{}' AND char_id = {}", + EscapeString(title), + client->CharacterID() + ); + auto results = database.QueryDatabase(query); + if (results.Success() && results.RowCount()){ + return; } - query = StringFormat("INSERT INTO titles (`char_id`, `prefix`) VALUES(%i, '%s')", - client->CharacterID(), escTitle); - safe_delete_array(escTitle); - results = database.QueryDatabase(query); - if(!results.Success()) { - return; - } + query = fmt::format( + "INSERT INTO titles (`char_id`, `prefix`) VALUES ({}, '{}')", + client->CharacterID(), + EscapeString(title) + ); + results = database.QueryDatabase(query); + if (!results.Success()) { + return; + } - auto pack = new ServerPacket(ServerOP_ReloadTitles, 0); - worldserver.SendPacket(pack); - safe_delete(pack); + auto pack = new ServerPacket(ServerOP_ReloadTitles, 0); + worldserver.SendPacket(pack); + safe_delete(pack); } void TitleManager::CreateNewPlayerSuffix(Client *client, const char *suffix) { - if(!client || !suffix) + if (!client || !suffix) { return; - - client->SetTitleSuffix(suffix); - - auto escSuffix = new char[strlen(suffix) * 2 + 1]; - database.DoEscapeString(escSuffix, suffix, strlen(suffix)); - - std::string query = StringFormat("SELECT `id` FROM titles " - "WHERE `suffix` = '%s' AND char_id = %i", - escSuffix, client->CharacterID()); - auto results = database.QueryDatabase(query); - if (results.Success() && results.RowCount() > 0) { - safe_delete_array(escSuffix); - return; - } - - query = StringFormat("INSERT INTO titles (`char_id`, `suffix`) VALUES(%i, '%s')", - client->CharacterID(), escSuffix); - safe_delete_array(escSuffix); - results = database.QueryDatabase(query); - if(!results.Success()) { - return; - } - - auto pack = new ServerPacket(ServerOP_ReloadTitles, 0); - worldserver.SendPacket(pack); - safe_delete(pack); -} - -void Client::SetAATitle(const char *Title) -{ - strn0cpy(m_pp.title, Title, sizeof(m_pp.title)); - - auto outapp = new EQApplicationPacket(OP_SetTitleReply, sizeof(SetTitleReply_Struct)); - - SetTitleReply_Struct *strs = (SetTitleReply_Struct *)outapp->pBuffer; - - strn0cpy(strs->title, Title, sizeof(strs->title)); - - strs->entity_id = GetID(); - - entity_list.QueueClients(this, outapp, false); - - safe_delete(outapp); -} - -void Client::SetTitleSuffix(const char *Suffix) -{ - strn0cpy(m_pp.suffix, Suffix, sizeof(m_pp.suffix)); - - auto outapp = new EQApplicationPacket(OP_SetTitleReply, sizeof(SetTitleReply_Struct)); - - SetTitleReply_Struct *strs = (SetTitleReply_Struct *)outapp->pBuffer; - - strs->is_suffix = 1; - - strn0cpy(strs->title, Suffix, sizeof(strs->title)); - - strs->entity_id = GetID(); - - entity_list.QueueClients(this, outapp, false); - - safe_delete(outapp); -} - -void Client::EnableTitle(int titleSet) { - - if (CheckTitle(titleSet)) - return; - - std::string query = StringFormat("INSERT INTO player_titlesets " - "(char_id, title_set) VALUES (%i, %i)", - CharacterID(), titleSet); - auto results = database.QueryDatabase(query); - if(!results.Success()) - LogError("Error in EnableTitle query for titleset [{}] and charid [{}]", titleSet, CharacterID()); - -} - -bool Client::CheckTitle(int titleSet) { - - std::string query = StringFormat("SELECT `id` FROM player_titlesets " - "WHERE `title_set`=%i AND `char_id`=%i LIMIT 1", - titleSet, CharacterID()); - auto results = database.QueryDatabase(query); - if (!results.Success()) { - return false; } - if (results.RowCount() == 0) - return false; + client->SetTitleSuffix(suffix); + + std::string query = fmt::format( + "SELECT `id` FROM titles WHERE `suffix` = '{}' AND char_id = {}", + EscapeString(suffix), + client->CharacterID() + ); + auto results = database.QueryDatabase(query); + if (results.Success() && results.RowCount()) { + return; + } + + query = fmt::format( + "INSERT INTO titles (`char_id`, `suffix`) VALUES ({}, '{}')", + client->CharacterID(), + EscapeString(suffix) + ); + results = database.QueryDatabase(query); + if (!results.Success()) { + return; + } + + auto pack = new ServerPacket(ServerOP_ReloadTitles, 0); + worldserver.SendPacket(pack); + safe_delete(pack); +} + +void Client::SetAATitle(const char *title) +{ + strn0cpy(m_pp.title, title, sizeof(m_pp.title)); + auto outapp = new EQApplicationPacket(OP_SetTitleReply, sizeof(SetTitleReply_Struct)); + SetTitleReply_Struct *strs = (SetTitleReply_Struct *)outapp->pBuffer; + strn0cpy(strs->title, title, sizeof(strs->title)); + strs->entity_id = GetID(); + entity_list.QueueClients(this, outapp, false); + safe_delete(outapp); +} + +void Client::SetTitleSuffix(const char *suffix) +{ + strn0cpy(m_pp.suffix, suffix, sizeof(m_pp.suffix)); + auto outapp = new EQApplicationPacket(OP_SetTitleReply, sizeof(SetTitleReply_Struct)); + SetTitleReply_Struct *strs = (SetTitleReply_Struct *)outapp->pBuffer; + strs->is_suffix = 1; + strn0cpy(strs->title, suffix, sizeof(strs->title)); + strs->entity_id = GetID(); + entity_list.QueueClients(this, outapp, false); + safe_delete(outapp); +} + +void Client::EnableTitle(int title_set) +{ + if (CheckTitle(title_set)) { + return; + } + + std::string query = fmt::format( + "INSERT INTO player_titlesets (char_id, title_set) VALUES ({}, {})", + CharacterID(), + title_set + ); + auto results = database.QueryDatabase(query); + if (!results.Success()) { + LogError("Error in EnableTitle query for titleset [{}] and charid [{}]", title_set, CharacterID()); + } + +} + +bool Client::CheckTitle(int title_set) +{ + std::string query = fmt::format( + "SELECT `id` FROM player_titlesets WHERE `title_set` = {} AND `char_id` = {} LIMIT 1", + title_set, + CharacterID() + ); + auto results = database.QueryDatabase(query); + if (!results.Success() || !results.RowCount()) { + return false; + } return true; } -void Client::RemoveTitle(int titleSet) { - - if (!CheckTitle(titleSet)) +void Client::RemoveTitle(int title_set) +{ + if (!CheckTitle(title_set)) { return; + } - std::string query = StringFormat("DELETE FROM player_titlesets " - "WHERE `title_set` = %i AND `char_id` = %i", - titleSet, CharacterID()); - database.QueryDatabase(query); + TitlesRepository::DeleteWhere( + database, + fmt::format( + "`title_set` = {} AND `char_id` = {}", + title_set, + CharacterID() + ) + ); } - diff --git a/zone/titles.h b/zone/titles.h index a942a5688..640d4d29a 100644 --- a/zone/titles.h +++ b/zone/titles.h @@ -25,20 +25,20 @@ class EQApplicationPacket; struct TitleEntry { - int TitleID; - int SkillID; - int MinSkillValue; - int MaxSkillValue; - int MinAAPoints; - int MaxAAPoints; - int Class; - int Gender; - int CharID; - int Status; - int ItemID; - std::string Prefix; - std::string Suffix; - int TitleSet; + int title_id; + int skill_id; + int min_skill_value; + int max_skill_value; + int min_aa_points; + int max_aa_points; + int class_id; + int gender_id; + int character_id; + int status; + int item_id; + std::string prefix; + std::string suffix; + int titleset; }; class TitleManager @@ -48,18 +48,19 @@ public: bool LoadTitles(); - EQApplicationPacket *MakeTitlesPacket(Client *c); - std::string GetPrefix(int TitleID); - std::string GetSuffix(int TitleID); - int NumberOfAvailableTitles(Client *c); - bool IsClientEligibleForTitle(Client *c, std::vector::iterator Title); - bool IsNewAATitleAvailable(int AAPoints, int Class); - bool IsNewTradeSkillTitleAvailable(int SkillID, int SkillValue); - void CreateNewPlayerTitle(Client *c, const char *Title); - void CreateNewPlayerSuffix(Client *c, const char *Suffix); + EQApplicationPacket *MakeTitlesPacket(Client *client); + std::string GetPrefix(int title_id); + std::string GetSuffix(int title_id); + int NumberOfAvailableTitles(Client *client); + bool IsClientEligibleForTitle(Client *client, TitleEntry title); + bool IsNewAATitleAvailable(int aa_points, int class_id); + bool IsNewTradeSkillTitleAvailable(int skill_id, int skill_value); + void CreateNewPlayerTitle(Client *client, const char *title); + void CreateNewPlayerSuffix(Client *client, const char *suffix); + bool HasTitle(Client* client, uint32 title_id); protected: - std::vector Titles; + std::vector titles; }; extern TitleManager title_manager; diff --git a/zone/tradeskills.cpp b/zone/tradeskills.cpp index 52423b368..45fda2761 100644 --- a/zone/tradeskills.cpp +++ b/zone/tradeskills.cpp @@ -43,109 +43,93 @@ static const EQ::skills::SkillType TradeskillUnknown = EQ::skills::Skill1HBlunt; void Object::HandleAugmentation(Client* user, const AugmentItem_Struct* in_augment, Object *worldo) { - if (!user || !in_augment) - { + if (!user || !in_augment) { LogError("Client or AugmentItem_Struct not set in Object::HandleAugmentation"); return; } EQ::ItemInstance* container = nullptr; - if (worldo) - { + if (worldo) { container = worldo->m_inst; - } - else - { - // Check to see if they have an inventory container type 53 that is used for this. + } else { // Check to see if they have an inventory container type 53 that is used for this. EQ::InventoryProfile& user_inv = user->GetInv(); EQ::ItemInstance* inst = nullptr; inst = user_inv.GetItem(in_augment->container_slot); - if (inst) - { + if (inst) { const EQ::ItemData* item = inst->GetItem(); - if (item && inst->IsType(EQ::item::ItemClassBag) && item->BagType == 53) - { - // We have found an appropriate inventory augmentation sealer + if (item && inst->IsType(EQ::item::ItemClassBag) && item->BagType == EQ::item::BagTypeAugmentationSealer) { // We have found an appropriate inventory augmentation sealer container = inst; // Verify that no more than two items are in container to guarantee no inadvertant wipes. - uint8 itemsFound = 0; - for (uint8 i = EQ::invbag::SLOT_BEGIN; i < EQ::invtype::WORLD_SIZE; i++) - { + uint8 items_found = 0; + for (uint8 i = EQ::invbag::SLOT_BEGIN; i < EQ::invtype::WORLD_SIZE; i++) { const EQ::ItemInstance* inst = container->GetItem(i); - if (inst) - { - itemsFound++; + if (inst) { + items_found++; } } - if (itemsFound != 2) - { - user->Message(Chat::Red, "Error: Too many/few items in augmentation container."); + if (items_found < 2) { + user->Message(Chat::Red, "Error: Too few items in augmentation container."); + return; + } else if (items_found > 2) { + user->Message(Chat::Red, "Error: Too many items in augmentation container."); return; } } } } - if(!container) - { + if(!container) { LogError("Player tried to augment an item without a container set"); user->Message(Chat::Red, "Error: This item is not a container!"); return; } EQ::ItemInstance *tobe_auged = nullptr, *auged_with = nullptr; - int8 slot=-1; + int8 slot = -1; - // Verify 2 items in the augmentation device - if (container->GetItem(0) && container->GetItem(1)) - { + if (container->GetItem(0) && container->GetItem(1)) { // Verify 2 items in the augmentation device // Verify 1 item is augmentable and the other is not - if (container->GetItem(0)->IsAugmentable() && !container->GetItem(1)->IsAugmentable()) - { + if (container->GetItem(0)->IsAugmentable() && !container->GetItem(1)->IsAugmentable()) { tobe_auged = container->GetItem(0); auged_with = container->GetItem(1); - } - else if (!container->GetItem(0)->IsAugmentable() && container->GetItem(1)->IsAugmentable()) - { + } else if (!container->GetItem(0)->IsAugmentable() && container->GetItem(1)->IsAugmentable()) { tobe_auged = container->GetItem(1); auged_with = container->GetItem(0); - } - else - { + } else { // Either 2 augmentable items found or none found // This should never occur due to client restrictions, but prevent in case of a hack - user->Message(Chat::Red, "Error: Must be 1 augmentable item in the sealer"); + user->Message(Chat::Red, "Error: There must be 1 augmentable item in the sealer."); return; } - } - else - { - // This happens if the augment button is clicked more than once quickly while augmenting - if (!container->GetItem(0)) - { - user->Message(Chat::Red, "Error: No item in slot 0 of sealer"); + } else { // This happens if the augment button is clicked more than once quickly while augmenting + if (!container->GetItem(0)) { + user->Message(Chat::Red, "Error: No item in the first slot of sealer."); } - if (!container->GetItem(1)) - { - user->Message(Chat::Red, "Error: No item in slot 1 of sealer"); + + if (!container->GetItem(1)) { + user->Message(Chat::Red, "Error: No item in the second slot of sealer."); } return; } - bool deleteItems = false; + if (!RuleB(Inventory, AllowMultipleOfSameAugment) && tobe_auged->ContainsAugmentByID(auged_with->GetID())) { + user->Message(Chat::Red, "Error: Cannot put multiple of the same augment in an item."); + return; + } - EQ::ItemInstance *itemOneToPush = nullptr, *itemTwoToPush = nullptr; + bool delete_items = false; - // Adding augment - if (in_augment->augment_slot == -1) - { - if (((slot=tobe_auged->AvailableAugmentSlot(auged_with->GetAugmentType()))!=-1) && - (tobe_auged->AvailableWearSlot(auged_with->GetItem()->Slots))) - { + EQ::ItemInstance *item_one_to_push = nullptr, *item_two_to_push = nullptr; + + if (in_augment->augment_slot == -1) { // Adding augment + if ( + ((slot = tobe_auged->AvailableAugmentSlot(auged_with->GetAugmentType())) != -1) && + tobe_auged->AvailableWearSlot(auged_with->GetItem()->Slots) + ) { tobe_auged->PutAugment(slot, *auged_with); EQ::ItemInstance *aug = tobe_auged->GetAugment(slot); @@ -158,20 +142,15 @@ void Object::HandleAugmentation(Client* user, const AugmentItem_Struct* in_augme parse->EventItem(EVENT_AUGMENT_INSERT, user, aug, nullptr, "", slot, &args); } - itemOneToPush = tobe_auged->Clone(); - deleteItems = true; + item_one_to_push = tobe_auged->Clone(); + delete_items = true; + } else { + user->Message(Chat::Red, "Error: No available slot for augment."); } - else - { - user->Message(Chat::Red, "Error: No available slot for augment"); - } - } - else - { + } else { EQ::ItemInstance *old_aug = nullptr; - bool isSolvent = auged_with->GetItem()->ItemType == EQ::item::ItemTypeAugmentationSolvent; - if (!isSolvent && auged_with->GetItem()->ItemType != EQ::item::ItemTypeAugmentationDistiller) - { + bool is_solvent = auged_with->GetItem()->ItemType == EQ::item::ItemTypeAugmentationSolvent; + if (!is_solvent && auged_with->GetItem()->ItemType != EQ::item::ItemTypeAugmentationDistiller) { LogError("Player tried to remove an augment without a solvent or distiller"); user->Message(Chat::Red, "Error: Missing an augmentation solvent or distiller for removing this augment."); @@ -180,8 +159,7 @@ void Object::HandleAugmentation(Client* user, const AugmentItem_Struct* in_augme EQ::ItemInstance *aug = tobe_auged->GetAugment(in_augment->augment_slot); if (aug) { - if (!isSolvent && auged_with->GetItem()->ID != aug->GetItem()->AugDistiller) - { + if (!is_solvent && auged_with->GetItem()->ID != aug->GetItem()->AugDistiller) { LogError("Player tried to safely remove an augment with the wrong distiller (item [{}] vs expected [{}])", auged_with->GetItem()->ID, aug->GetItem()->AugDistiller); user->Message(Chat::Red, "Error: Wrong augmentation distiller for safely removing this augment."); return; @@ -191,29 +169,27 @@ void Object::HandleAugmentation(Client* user, const AugmentItem_Struct* in_augme parse->EventItem(EVENT_UNAUGMENT_ITEM, user, tobe_auged, nullptr, "", slot, &args); args.assign(1, tobe_auged); - args.push_back(&isSolvent); + args.push_back(&is_solvent); parse->EventItem(EVENT_AUGMENT_REMOVE, user, aug, nullptr, "", slot, &args); } - if (isSolvent) + if (is_solvent) { tobe_auged->DeleteAugment(in_augment->augment_slot); - else + } else { old_aug = tobe_auged->RemoveAugment(in_augment->augment_slot); + } - itemOneToPush = tobe_auged->Clone(); - if (old_aug) - itemTwoToPush = old_aug->Clone(); + item_one_to_push = tobe_auged->Clone(); + if (old_aug) { + item_two_to_push = old_aug->Clone(); + } - - - deleteItems = true; + delete_items = true; } - if (deleteItems) - { - if (worldo) - { + if (delete_items) { + if (worldo) { container->Clear(); auto outapp = new EQApplicationPacket(OP_ClearObject, sizeof(ClearObject_Struct)); ClearObject_Struct *cos = (ClearObject_Struct *)outapp->pBuffer; @@ -221,34 +197,26 @@ void Object::HandleAugmentation(Client* user, const AugmentItem_Struct* in_augme user->QueuePacket(outapp); safe_delete(outapp); database.DeleteWorldContainer(worldo->m_id, zone->GetZoneID()); - } - else - { - // Delete items in our inventory container... - for (uint8 i = EQ::invbag::SLOT_BEGIN; i < EQ::invtype::WORLD_SIZE; i++) - { + } else { // Delete items in our inventory container... + for (uint8 i = EQ::invbag::SLOT_BEGIN; i < EQ::invtype::WORLD_SIZE; i++) { const EQ::ItemInstance* inst = container->GetItem(i); - if (inst) - { + if (inst) { user->DeleteItemInInventory(EQ::InventoryProfile::CalcSlotId(in_augment->container_slot, i), 0, true); } } - // Explicitly mark container as cleared. - container->Clear(); + + container->Clear(); // Explicitly mark container as cleared. } } // Must push items after the items in inventory are deleted - necessary due to lore items... - if (itemOneToPush) - { - user->PushItemOnCursor(*itemOneToPush, true); + if (item_one_to_push) { + user->PushItemOnCursor(*item_one_to_push, true); } - if (itemTwoToPush) - { - user->PushItemOnCursor(*itemTwoToPush, true); + if (item_two_to_push) { + user->PushItemOnCursor(*item_two_to_push, true); } - } // Perform tradeskill combine @@ -375,6 +343,14 @@ void Object::HandleCombine(Client* user, const NewCombine_Struct* in_combine, Ob } DBTradeskillRecipe_Struct spec; + + if (parse->EventPlayer(EVENT_COMBINE, user, std::to_string(in_combine->container_slot), 0) == 1) { + auto outapp = new EQApplicationPacket(OP_TradeSkillCombine, 0); + user->QueuePacket(outapp); + safe_delete(outapp); + return; + } + if (!content_db.GetTradeRecipe(container, c_type, some_id, user->CharacterID(), &spec)) { LogTradeskillsDetail("[HandleCombine] Check 2"); @@ -496,11 +472,11 @@ void Object::HandleCombine(Client* user, const NewCombine_Struct* in_combine, Ob user->DeleteItemInInventory(in_combine->container_slot, 0, true); } } + if (success) { - parse->EventPlayer(EVENT_COMBINE_SUCCESS, user, spec.name.c_str(), spec.recipe_id); - } - else { - parse->EventPlayer(EVENT_COMBINE_FAILURE, user, spec.name.c_str(), spec.recipe_id); + parse->EventPlayer(EVENT_COMBINE_SUCCESS, user, spec.name, spec.recipe_id); + } else { + parse->EventPlayer(EVENT_COMBINE_FAILURE, user, spec.name, spec.recipe_id); } } @@ -664,10 +640,12 @@ void Object::HandleAutoCombine(Client* user, const RecipeAutoCombine_Struct* rac if(success && spec.replace_container) { // user->DeleteItemInInventory(in_combine->container_slot, 0, true); } - if (success) - parse->EventPlayer(EVENT_COMBINE_SUCCESS, user, spec.name.c_str(), spec.recipe_id); - else - parse->EventPlayer(EVENT_COMBINE_FAILURE, user, spec.name.c_str(), spec.recipe_id); + + if (success) { + parse->EventPlayer(EVENT_COMBINE_SUCCESS, user, spec.name, spec.recipe_id); + } else { + parse->EventPlayer(EVENT_COMBINE_FAILURE, user, spec.name, spec.recipe_id); + } } EQ::skills::SkillType Object::TypeToSkill(uint32 type) @@ -774,7 +752,7 @@ void Client::SendTradeskillSearchResults( for (auto row = results.begin(); row != results.end(); ++row) { if (row == nullptr || row[0] == nullptr || row[1] == nullptr || row[2] == nullptr || row[3] == nullptr || - row[5] == nullptr) { + row[4] == nullptr || row[5] == nullptr) { continue; } @@ -782,27 +760,36 @@ void Client::SendTradeskillSearchResults( const char *name = row[1]; uint32 trivial = (uint32) atoi(row[2]); uint32 comp_count = (uint32) atoi(row[3]); - uint32 tradeskill = (uint16) atoi(row[5]); + uint32 tradeskill = (uint16) atoi(row[4]); + uint32 must_learn = (uint16) atoi(row[5]); + // Skip the recipes that exceed the threshold in skill difference // Recipes that have either been made before or were // explicitly learned are excempt from that limit + + auto character_learned_recipe = CharacterRecipeListRepository::GetRecipe( + character_learned_recipe_list, + recipe_id + ); + if (RuleB(Skills, UseLimitTradeskillSearchSkillDiff) && ((int32) trivial - (int32) GetSkill((EQ::skills::SkillType) tradeskill)) > RuleI(Skills, MaxTradeskillSearchSkillDiff)) { LogTradeskills("Checking limit recipe_id [{}] name [{}]", recipe_id, name); - auto character_learned_recipe = CharacterRecipeListRepository::GetRecipe( - character_learned_recipe_list, - recipe_id - ); - if (character_learned_recipe.made_count == 0) { continue; } } + //Skip recipes that must be learned + if ((must_learn & 0xf) && !character_learned_recipe.recipe_id) { + continue; + } + + auto outapp = new EQApplicationPacket(OP_RecipeReply, sizeof(RecipeReply_Struct)); RecipeReply_Struct *reply = (RecipeReply_Struct *) outapp->pBuffer; @@ -976,6 +963,9 @@ bool Client::TradeskillExecute(DBTradeskillRecipe_Struct *spec) { case EQ::skills::SkillTinkering: skillup_modifier = RuleI(Character, TradeskillUpTinkering); break; + case EQ::skills::SkillTailoring: + skillup_modifier = RuleI(Character, TradeskillUpTailoring); + break; default: skillup_modifier = 2; break; @@ -1093,7 +1083,7 @@ bool Client::TradeskillExecute(DBTradeskillRecipe_Struct *spec) { } if (RuleB(TaskSystem, EnableTaskSystem)) { - UpdateTasksForItem(ActivityTradeSkill, itr->first, itr->second); + UpdateTasksForItem(TaskActivityType::TradeSkill, itr->first, itr->second); } ++itr; @@ -1138,9 +1128,11 @@ bool Client::TradeskillExecute(DBTradeskillRecipe_Struct *spec) { itr = spec->salvage.begin(); uint8 sc = 0; while(itr != spec->salvage.end()) { - for(sc = 0; sc < itr->second; sc++) - if(zone->random.Roll(SalvageChance)) + for (sc = 0; sc < itr->second; sc++) { + if (zone->random.Roll(SalvageChance)) { SummonItem(itr->first, 1); + } + } ++itr; } } @@ -1413,7 +1405,6 @@ bool ZoneDatabase::GetTradeRecipe( DBTradeskillRecipe_Struct *spec ) { - std::string container_where_filter; if (some_id == 0) { // world combiner so no item number @@ -1481,7 +1472,7 @@ bool ZoneDatabase::GetTradeRecipe( recipe_id ); - if (character_learned_recipe.made_count > 0) { + if (character_learned_recipe.recipe_id) { //If this exists we learned it LogTradeskills("[GetTradeRecipe] made_count [{}]", character_learned_recipe.made_count); spec->has_learnt = true; @@ -1536,10 +1527,10 @@ bool ZoneDatabase::GetTradeRecipe( "FROM tradeskill_recipe_entries " "WHERE salvagecount > 0 AND recipe_id = %u", recipe_id ); - + results = QueryDatabase(query); if (results.Success()) { - for (auto row = results.begin(); row != results.begin(); ++row) { + for (auto row = results.begin(); row != results.end(); ++row) { uint32 item = (uint32) atoi(row[0]); uint8 num = (uint8) atoi(row[1]); spec->salvage.push_back(std::pair(item, num)); diff --git a/zone/trading.cpp b/zone/trading.cpp index 1d75bd941..87ba86d68 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -205,41 +205,45 @@ void Trade::LogTrade() item_count++; } - if (((this->cp + this->sp + this->gp + this->pp)>0) || (item_count>0)) + if ((cp + sp + gp + pp) || item_count) { admin_level = trader->Admin(); - else - admin_level = 999; + } else { + admin_level = (AccountStatus::Max + 1); + } if (zone->tradevar == 7) { logtrade = true; - } - else if ((admin_level>=10) && (admin_level<20)) { - if ((zone->tradevar<8) && (zone->tradevar>5)) + } else if ( + admin_level >= AccountStatus::Steward && + admin_level < AccountStatus::ApprenticeGuide + ) { + if (zone->tradevar < 8 && zone->tradevar > 5) { logtrade = true; - } - else if (admin_level<=20) { - if ((zone->tradevar<8) && (zone->tradevar>4)) + } + } else if (admin_level <= AccountStatus::ApprenticeGuide) { + if (zone->tradevar < 8 && zone->tradevar > 4) { logtrade = true; - } - else if (admin_level<=80) { - if ((zone->tradevar<8) && (zone->tradevar>3)) + } + } else if (admin_level <= AccountStatus::QuestTroupe) { + if (zone->tradevar < 8 && zone->tradevar > 3) { logtrade = true; - } - else if (admin_level<=100){ - if ((zone->tradevar<9) && (zone->tradevar>2)) + } + } else if (admin_level <= AccountStatus::GMAdmin) { + if (zone->tradevar < 9 && zone->tradevar > 2) { logtrade = true; - } - else if (admin_level<=150){ - if (((zone->tradevar<8) && (zone->tradevar>1)) || (zone->tradevar==9)) + } + } else if (admin_level <= AccountStatus::GMLeadAdmin) { + if ((zone->tradevar < 8 && zone->tradevar > 1) || zone->tradevar == 9) { logtrade = true; - } - else if (admin_level<=255){ - if ((zone->tradevar<8) && (zone->tradevar>0)) + } + } else if (admin_level <= AccountStatus::Max){ + if (zone->tradevar < 8 && zone->tradevar > 0) { logtrade = true; + } } } - if (logtrade == true) { + if (logtrade) { char logtext[1000] = {0}; uint32 cash = 0; bool comma = false; @@ -1266,7 +1270,7 @@ uint32 Client::FindTraderItemSerialNumber(int32 ItemID) { uint16 SlotID = 0; for (int i = EQ::invslot::GENERAL_BEGIN; i <= EQ::invslot::GENERAL_END; i++){ item = this->GetInv().GetItem(i); - if (item && item->GetItem()->ID == 17899){ //Traders Satchel + if (item && item->GetItem()->BagType == EQ::item::BagTypeTradersSatchel){ for (int x = EQ::invbag::SLOT_BEGIN; x <= EQ::invbag::SLOT_END; x++) { // we already have the parent bag and a contents iterator..why not just iterate the bag!?? SlotID = EQ::InventoryProfile::CalcSlotId(i, x); @@ -1289,7 +1293,7 @@ EQ::ItemInstance* Client::FindTraderItemBySerialNumber(int32 SerialNumber){ uint16 SlotID = 0; for (int i = EQ::invslot::GENERAL_BEGIN; i <= EQ::invslot::GENERAL_END; i++){ item = this->GetInv().GetItem(i); - if(item && item->GetItem()->ID == 17899){ //Traders Satchel + if (item && item->GetItem()->BagType == EQ::item::BagTypeTradersSatchel){ for (int x = EQ::invbag::SLOT_BEGIN; x <= EQ::invbag::SLOT_END; x++) { // we already have the parent bag and a contents iterator..why not just iterate the bag!?? SlotID = EQ::InventoryProfile::CalcSlotId(i, x); @@ -1322,7 +1326,7 @@ GetItems_Struct* Client::GetTraderItems(){ if (ndx >= 80) break; item = this->GetInv().GetItem(i); - if(item && item->GetItem()->ID == 17899){ //Traders Satchel + if (item && item->GetItem()->BagType == EQ::item::BagTypeTradersSatchel){ for (int x = EQ::invbag::SLOT_BEGIN; x <= EQ::invbag::SLOT_END; x++) { if (ndx >= 80) break; @@ -1349,7 +1353,7 @@ uint16 Client::FindTraderItem(int32 SerialNumber, uint16 Quantity){ uint16 SlotID = 0; for (int i = EQ::invslot::GENERAL_BEGIN; i <= EQ::invslot::GENERAL_END; i++) { item = this->GetInv().GetItem(i); - if(item && item->GetItem()->ID == 17899){ //Traders Satchel + if (item && item->GetItem()->BagType == EQ::item::BagTypeTradersSatchel){ for (int x = EQ::invbag::SLOT_BEGIN; x <= EQ::invbag::SLOT_END; x++){ SlotID = EQ::InventoryProfile::CalcSlotId(i, x); @@ -1369,7 +1373,7 @@ uint16 Client::FindTraderItem(int32 SerialNumber, uint16 Quantity){ return 0; } -void Client::NukeTraderItem(uint16 Slot,int16 Charges,uint16 Quantity,Client* Customer,uint16 TraderSlot, int32 SerialNumber, int32 itemid) { +void Client::NukeTraderItem(uint16 Slot,int16 Charges,int16 Quantity,Client* Customer,uint16 TraderSlot, int32 SerialNumber, int32 itemid) { if(!Customer) return; @@ -1447,7 +1451,7 @@ void Client::TraderUpdate(uint16 SlotID,uint32 TraderID){ safe_delete(outapp); } -void Client::FindAndNukeTraderItem(int32 SerialNumber, uint16 Quantity, Client* Customer, uint16 TraderSlot){ +void Client::FindAndNukeTraderItem(int32 SerialNumber, int16 Quantity, Client* Customer, uint16 TraderSlot){ const EQ::ItemInstance* item= nullptr; bool Stackable = false; diff --git a/zone/trap.cpp b/zone/trap.cpp index 327f0ef80..f3fc3d22e 100644 --- a/zone/trap.cpp +++ b/zone/trap.cpp @@ -136,7 +136,7 @@ void Trap::Trigger(Mob* trigger) entity_list.MessageClose(trigger,false,100,13,"%s",message.c_str()); } if(hiddenTrigger){ - hiddenTrigger->SpellFinished(effectvalue, trigger, EQ::spells::CastingSlot::Item, 0, -1, spells[effectvalue].ResistDiff); + hiddenTrigger->SpellFinished(effectvalue, trigger, EQ::spells::CastingSlot::Item, 0, -1, spells[effectvalue].resist_difficulty); } break; case trapTypeAlarm: diff --git a/zone/tribute.cpp b/zone/tribute.cpp index 95640947e..ed8c69cf5 100644 --- a/zone/tribute.cpp +++ b/zone/tribute.cpp @@ -138,20 +138,20 @@ void Client::DoTributeUpdate() { uint32 tid = m_pp.tributes[r].tribute; if(tid == TRIBUTE_NONE) { if (m_inv[EQ::invslot::TRIBUTE_BEGIN + r]) - DeleteItemInInventory(EQ::invslot::TRIBUTE_BEGIN + r, 0, false); + DeleteItemInInventory(EQ::invslot::TRIBUTE_BEGIN + r); continue; } if(tribute_list.count(tid) != 1) { if (m_inv[EQ::invslot::TRIBUTE_BEGIN + r]) - DeleteItemInInventory(EQ::invslot::TRIBUTE_BEGIN + r, 0, false); + DeleteItemInInventory(EQ::invslot::TRIBUTE_BEGIN + r); continue; } //sanity check if(m_pp.tributes[r].tier >= MAX_TRIBUTE_TIERS) { if (m_inv[EQ::invslot::TRIBUTE_BEGIN + r]) - DeleteItemInInventory(EQ::invslot::TRIBUTE_BEGIN + r, 0, false); + DeleteItemInInventory(EQ::invslot::TRIBUTE_BEGIN + r); m_pp.tributes[r].tier = 0; continue; } @@ -165,7 +165,7 @@ void Client::DoTributeUpdate() { if(inst == nullptr) continue; - PutItemInInventory(EQ::invslot::TRIBUTE_BEGIN + r, *inst, false); + PutItemInInventory(EQ::invslot::TRIBUTE_BEGIN + r, *inst); SendItemPacket(EQ::invslot::TRIBUTE_BEGIN + r, inst, ItemPacketTributeItem); safe_delete(inst); } @@ -173,7 +173,7 @@ void Client::DoTributeUpdate() { //unequip tribute items... for (r = 0; r < EQ::invtype::TRIBUTE_SIZE; r++) { if (m_inv[EQ::invslot::TRIBUTE_BEGIN + r]) - DeleteItemInInventory(EQ::invslot::TRIBUTE_BEGIN + r, 0, false); + DeleteItemInInventory(EQ::invslot::TRIBUTE_BEGIN + r); } } CalcBonuses(); @@ -261,10 +261,10 @@ int32 Client::TributeItem(uint32 slot, uint32 quantity) { if(inst->IsStackable()) { if(inst->GetCharges() < (int32)quantity) //dont have enough.... return(0); - DeleteItemInInventory(slot, quantity, false); + DeleteItemInInventory(slot, quantity); } else { quantity = 1; - DeleteItemInInventory(slot, 0, false); + DeleteItemInInventory(slot); } pts *= quantity; diff --git a/zone/tune.cpp b/zone/tune.cpp index f22fe6a53..a5bf04fed 100644 --- a/zone/tune.cpp +++ b/zone/tune.cpp @@ -27,16 +27,23 @@ #include "../common/skills.h" #include "../common/spdat.h" #include "../common/string_util.h" +#include "../common/data_verification.h" +#include "../common/misc_functions.h" #include "queryserv.h" #include "quest_parser_collection.h" #include "string_ids.h" #include "water_map.h" #include "worldserver.h" #include "zone.h" +#include "lua_parser.h" +#include "fastmath.h" +#include "mob.h" +#include "npc.h" #include #include #include +#include #ifdef BOTS #include "bot.h" @@ -44,6 +51,7 @@ extern QueryServ* QServ; extern WorldServer worldserver; +extern FastMath g_Math; #ifdef _WINDOWS #define snprintf _snprintf @@ -54,1035 +62,1342 @@ extern WorldServer worldserver; extern EntityList entity_list; extern Zone* zone; -void Mob::Tune_FindATKByPctMitigation(Mob* defender,Mob *attacker, float pct_mitigation, int interval, int max_loop, int ac_override, int Msg) + +void Mob::TuneGetStats(Mob* defender, Mob *attacker) { - /*Find the amount of 'ATTACK' stat that has to be added/subtracted FROM ATTACKER to reach a specific average mitigation value on the TARGET. - Can use ac_override to find the value verse a hypothetical amount of worn AC */ - - int atk = 0; - uint32 total_damage = 0; - int32 damage = 0; - uint32 minhit = 0; - int mean_dmg = 0; - float tmp_pct_mitigated = 0.0f; - int end = 0; - - if (attacker->IsNPC()) - { - damage = static_cast(attacker->CastToNPC()->GetMaxDMG()); - minhit = attacker->CastToNPC()->GetMinDMG(); - } - else if (attacker->IsClient()) - { - damage = static_cast(attacker->CastToClient()->GetMeleeDamage(this)); - minhit = attacker->CastToClient()->GetMeleeDamage(this, true); - } - - if (damage == 0 || minhit == 0) - { - Message(0, "#Tune - Processing... Abort! Damage not found! [MaxDMG %i MinDMG %i]", damage,minhit); + if (!defender || !attacker) { + Message(0, "#Tune - Processing... Abort! Can not find attacker or defender"); return; } + int max_damage = 0; + int min_damage = 0; + int mean_dmg = 0; + float tmp_pct_mitigated = 0.0f; + float hit_chance = 0.0f; - mean_dmg = defender->Tune_GetMeanDamage(this, attacker, damage, minhit, nullptr, 0, ac_override, 0, 0,atk); - tmp_pct_mitigated = 100.0f - static_cast( mean_dmg * 100 /damage); + max_damage = attacker->TuneClientGetMaxDamage(defender); + min_damage = attacker->TuneClientGetMinDamage(defender, max_damage); - Message(0, "#Tune - Begin Parse [Interval %i Max Loop Iterations %i]", interval, max_loop); - Message(0, "#Tune - Processing... Find ATK for attacker Mitigation (%.0f) pct on defender [MaxDMG %i MinDMG %i Current Mitigation %.2f]", pct_mitigation, damage, minhit,tmp_pct_mitigated); - - if (tmp_pct_mitigated < pct_mitigation) - interval = interval * -1; - - for (int j=0; j < max_loop; j++) + if (!max_damage) { - mean_dmg = defender->Tune_GetMeanDamage(this, attacker, damage, minhit, nullptr, 0, ac_override,0, 0,atk); - tmp_pct_mitigated = 100.0f - ( static_cast(mean_dmg) * 100.0f /static_cast(damage)); + Message(0, "#Tune - Processing... Abort! Damage not found! [MaxDMG %i MinDMG %i]", max_damage, min_damage); + return; + } + mean_dmg = attacker->TuneClientGetMeanDamage(defender); + tmp_pct_mitigated = 100.0f - (static_cast(mean_dmg) * 100.0f / static_cast(max_damage)); - if (Msg >= 3) - Message(0, "#Tune - Processing... [%i] [ATK %i] Average Melee Hit %i | Pct Mitigated %.2f ",j,atk, mean_dmg, tmp_pct_mitigated); + hit_chance = TuneGetHitChance(defender, attacker); - if (interval > 0 && tmp_pct_mitigated <= pct_mitigation) - end = 1; - - else if (interval < 0 && tmp_pct_mitigated >= pct_mitigation) - end = 1; - - else if (interval < 0 && mean_dmg == minhit) - end = 2; + Message(0, "#STATS#############START######################"); + Message(0, "[#Tune] Defender Statistics vs Attacker"); + Message(0, "[#Tune] Defender Name: %s", defender->GetCleanName()); + Message(0, "[#Tune] AC Mitigation pct: %.0f pct ", round(tmp_pct_mitigated)); + Message(0, "[#Tune] Total AC: %i ", defender->TuneACSum()); + Message(0, "[#Tune] Mean Damage Taken: %i per hit", mean_dmg); + Message(0, "[#Tune] Chance to be missed: %.0f pct", (100.0f - round(hit_chance))); + Message(0, "[#Tune] Avoidance: %i ", TuneGetAvoidance(defender, attacker)); + Message(0, "[#Tune] Riposte Chance: %.0f pct ", round(TuneGetAvoidMeleeChance(defender, attacker, DMG_RIPOSTED))); + Message(0, "[#Tune] Block Chance: %.0f pct ", round(TuneGetAvoidMeleeChance(defender, attacker, DMG_BLOCKED))); + Message(0, "[#Tune] Parry Chance: %.0f pct ", round(TuneGetAvoidMeleeChance(defender, attacker, DMG_PARRIED))); + Message(0, "[#Tune] Dodge Chance: %.0f pct ", round(TuneGetAvoidMeleeChance(defender, attacker, DMG_DODGED))); - if (end >= 1){ + Message(0, "################################################"); + Message(0, "[#Tune] Attacker Statistics vs Defender"); + Message(0, "[#Tune] Attacker Name: %s", attacker->GetCleanName()); - defender->Tune_MeleeMitigation(this, attacker, damage, minhit, nullptr,Msg,ac_override, 0, 0, atk); - - if (end == 2) - Message(0, "#Tune - [WARNING] Mitigation can not be further decreased due to minium hit value (%i).",minhit); + if (max_damage > 0) { + Message(0, "[#Tune] Max Damage %i Min Damage %i", max_damage, min_damage); + Message(0, "[#Tune] Total Offense: %i ", TuneGetOffense(defender, attacker)); + Message(0, "[#Tune] Chance to hit: %.0f pct", round(hit_chance)); + Message(0, "[#Tune] Accuracy: %i ", TuneGetAccuracy( defender,attacker)); - if (attacker->IsNPC()){ - Message(0, "#Tune - Recommended NPC ATK ADJUSTMENT ( %i ) on ' %s ' average mitigation of (%.0f) pct verse ' %s '. ",atk, attacker->GetCleanName(), pct_mitigation, defender->GetCleanName()); - Message(0, "#SET: [NPC Attack STAT] = [%i]",atk + defender->CastToNPC()->ATK); - } - if (attacker->IsClient()){ - Message(0, "#Tune - Recommended CLIENT ATK ADJUSTMENT ( %i ) on ' %s ' average mitigation of (%.0f) pct verse ' %s '. ", atk, attacker->GetCleanName(), pct_mitigation, defender->GetCleanName()); - Message(0, "#Modify (+/-): [Client Attack STAT/SE_ATK(2)] [%i]",atk); - } - - return; - } - - atk = atk + interval; + } + else{ + Message(0, "[#Tune] Can not melee this target"); } - Message(0, "#Tune - Error: Unable to find desired result for (%.0f) pct - Increase interval (%i) AND/OR max loop value (%i) and run again.", pct_mitigation, interval, max_loop); - Message(0, "#Tune - Parse ended at ATK ADJUSTMENT ( %i ) average target mitigation of (%.0f) pct.",atk,tmp_pct_mitigated); + Message(0, "#STATS#############COMPLETE###################"); + return; } -void Mob::Tune_FindACByPctMitigation(Mob* defender, Mob *attacker, float pct_mitigation, int interval, int max_loop, int atk_override, int Msg) +void Mob::TuneGetACByPctMitigation(Mob* defender, Mob *attacker, float pct_mitigation, int interval, int max_loop, int atk_override, int Msg) { + Message(0, " "); + /* + Find the amount of AC stat that has to be added/subtracted from DEFENDER to reach a specific average mitigation value based on ATTACKER's offense statistics. + Can use atk_override to find the value verse a hypothetical amount of worn ATK + */ - /*Find the amount of AC stat that has to be added/subtracted from TARGET to reach a specific average mitigation value based on ATTACKER's statistics. - Can use ac_override to find the value verse a hypothetical amount of worn AC */ - - int add_ac = 0; - uint32 total_damage = 0; - int32 damage = 0; - uint32 minhit = 0; - int mean_dmg = 0; - float tmp_pct_mitigated = 0.0f; - int end = 0; - - - if (attacker->IsNPC()) - { - damage = static_cast(attacker->CastToNPC()->GetMaxDMG()); - minhit = attacker->CastToNPC()->GetMinDMG(); + if (pct_mitigation > 100 || pct_mitigation < 0) { + Message(0, "[#Tune] - Processing... Abort! Mitigation value out of range ( %.0f ) pct. Must be between 0-100.", pct_mitigation); + return; } - else if (attacker->IsClient()) - { - damage = static_cast(attacker->CastToClient()->GetMeleeDamage(this)); - minhit = attacker->CastToClient()->GetMeleeDamage(this, true); + if (!defender) { + Message(0, "[#Tune] - Processing... Abort! No Defender found."); + return; } - - if (damage == 0 || minhit == 0) - { - Message(0, "#Tune - Processing... Abort! Damage not found! [MaxDMG %i MinDMG %i]", damage,minhit); + if (!attacker) { + Message(0, "[#Tune] - Processing... Abort! No Attacker found."); + return; + } + if (defender->GetID() == attacker->GetID()) { + Message(0, "[#Tune] - Processing... Abort! Error Attacker can not be the Defender."); return; } - mean_dmg = defender->Tune_GetMeanDamage(this, attacker, damage, minhit, nullptr, 0, 0, atk_override); - tmp_pct_mitigated = 100.0f - static_cast( mean_dmg * 100 /damage); + int max_damage = 0; + int min_damage = 0; + int mean_dmg = 0; + float tmp_pct_mitigated = 0.0f; + float base_pct_mitigation = pct_mitigation; + int loop_add_ac = 0; + int end = 0; + int value = 0; - Message(0, "#Tune - Begin Parse [Interval %i Max Loop Iterations %i]", interval, max_loop); - Message(0, "#Tune - Processing... Find AC for defender Mitigation (%.0f) pct from attacker [MaxDMG %i MinDMG %i Current Mitigation %.2f]", pct_mitigation, damage, minhit,tmp_pct_mitigated); + max_damage = attacker->TuneClientGetMaxDamage(defender); + min_damage = attacker->TuneClientGetMinDamage(defender, max_damage); + + if (!max_damage) + { + Message(0, "#Tune - Processing... Abort! Damage not found! [MaxDMG %i MinDMG %i]", max_damage, min_damage); + return; + } + + //Obtain baseline mitigation for current stats + mean_dmg = attacker->TuneClientGetMeanDamage(defender,0,atk_override); + tmp_pct_mitigated = 100.0f - (static_cast(mean_dmg) * 100.0f / static_cast(max_damage)); + + Message(0, "###################START###################"); + Message(0, "[#Tune] DFENDER Name: %s", defender->GetCleanName()); + Message(0, "[#Tune] DEFENDER AC Mitigation pct: %.0f pct ", round(tmp_pct_mitigated)); + Message(0, "[#Tune] DEFENDER Total AC: %i ", defender->TuneACSum()); + Message(0, "[#Tune] ATTACKER Name: %s", attacker->GetCleanName()); + Message(0, "[#Tune] ATTACKER Max Damage %i Min Damage %i", max_damage, min_damage); + Message(0, "[#Tune] ATTACKER Total Offense: %i ", TuneGetOffense(defender, attacker, atk_override)); + Message(0, "##########################################"); + Message(0, "[#Tune] Begin Parse [Interval %i Max Loop Iterations %i]", interval, max_loop); if (tmp_pct_mitigated > pct_mitigation) - interval = interval * -1; - - for (int j=0; j < max_loop; j++) { - mean_dmg = defender->Tune_GetMeanDamage(this, attacker, damage, minhit, nullptr, 0, 0,atk_override, add_ac, 0); - tmp_pct_mitigated = 100.0f - ( static_cast(mean_dmg) * 100.0f /static_cast(damage)); + interval = interval * -1; + Message(0, "[#Tune] NOTE: Defenders 'AC' must be LOWERED due to defenders AC Mitigation ( %.0f pct ) being greater than the desired ( %.0f pct )", tmp_pct_mitigated, pct_mitigation); + } - if (Msg >= 3) - Message(0, "#Tune - Processing... [%i] [AC %i] Average Melee Hit %i | Pct Mitigated %.2f ",j,add_ac, mean_dmg, tmp_pct_mitigated); + Message(0, "[#Tune] Processing... Find AC for defender to have Mitigation of ( %.0f pct ) agianst this attacker.", pct_mitigation); + + + for (int j = 0; j < max_loop; j++) + { + mean_dmg = attacker->TuneClientGetMeanDamage(defender, 0, atk_override, loop_add_ac,0); + tmp_pct_mitigated = 100.0f - (static_cast(mean_dmg) * 100.0f / static_cast(max_damage)); + + if (Msg >= 1) + { + Message(0, "[#Tune] - Processing... [%i] [AC %i] Average Melee Hit %i | Pct Mitigated %.2f ", j, loop_add_ac, mean_dmg, tmp_pct_mitigated); + } if (interval > 0 && tmp_pct_mitigated >= pct_mitigation) + { end = 1; + } else if (interval < 0 && tmp_pct_mitigated <= pct_mitigation) + { end = 1; + } - else if (interval < 0 && mean_dmg == minhit) - end = 2; + else if (interval < 0 && mean_dmg == min_damage) + { + Message(0, "[#Tune][WARNING] Mitigation can not be further decreased due to minium hit value (%i). Minium mitigation ( %.0f ) pct", min_damage, tmp_pct_mitigated); + base_pct_mitigation = tmp_pct_mitigated; + end = 1; + } - if (end >= 1){ + if (end >= 1) { - defender->Tune_MeleeMitigation(this, attacker, damage, minhit, nullptr,Msg,0,atk_override, add_ac, 0); - - if (end == 2) - Message(0, "#Tune - [WARNING] Mitigation can not be further decreased due to minium hit value (%i).",minhit); + Message(0, "###################RESULTS###################"); - if (defender->IsNPC()){ - Message(Chat::LightGray, "#Tune - Recommended NPC AC ADJUSTMENT ( %i ) on ' %s ' for an average mitigation of (+ %.0f) pct from attacker ' %s '.",add_ac,defender->GetCleanName(), pct_mitigation, attacker->GetCleanName()); - Message(0, "#SET: [NPC Attack STAT] = [%i]",add_ac + defender->CastToNPC()->GetRawAC()); + if (atk_override) { + Message(0, "[#Tune] ATK STAT OVERRRIDE. This is the amount of AC adjustment needed if this attacker had ( %i ) raw ATK stat", atk_override); } - if (defender->IsClient()){ - Message(Chat::LightGray, "#Tune - Recommended CLIENT AC ADJUSTMENT ( %i ) on ' %s ' for an average mitigation of (+ %.0f) pct from attacker ' %s '.",add_ac,defender->GetCleanName(), pct_mitigation, attacker->GetCleanName()); - Message(0, "#Modify (+/-): [Client AC STAT/SE_AC(1)] [%i]",add_ac); + + if (defender->IsNPC()) + { + + Message(0, "[#Tune] Recommended NPC RAW AC ADJUSTMENT ( %i ) on ' %s ' to acheive an average mitigation of ( %.0f pct ) verse ' %s '", loop_add_ac, defender->GetCleanName(), base_pct_mitigation, attacker->GetCleanName()); + Message(0, "[#Tune] SET NPC 'AC' stat value = [ %i ]", loop_add_ac + defender->CastToNPC()->GetRawAC()); + Message(0, "###################COMPLETE###################"); + } + if (defender->IsClient()) + { + Message(0, "[#Tune] Recommended CLIENT AC ADJUSTMENT ( %i ) on ' %s ' to acheive an average mitigation of ( %.0f pct ) verse ' %s '", loop_add_ac, defender->GetCleanName(), base_pct_mitigation, attacker->GetCleanName()); + + if (loop_add_ac >= 0) { + Message(0, "[#Tune] MODIFY Client Item AC or Spell Effect AC by [+ %i ]", loop_add_ac); + } + else { + Message(0, "[#Tune] MODIFY Client Item AC or Spell Effect AC by [ %i ]", loop_add_ac); + } + + Message(0, "###################COMPLETE###################"); } return; } - - - add_ac = add_ac + interval; + loop_add_ac = loop_add_ac + interval; } - Message(0, "#Tune - Error: Unable to find desired result for (%.0f) pct - Increase interval (%i) AND/OR max loop value (%i) and run again.", pct_mitigation, interval, max_loop); - Message(0, "#Tune - Parse ended at AC ADJUSTMENT ( %i ) at average mitigation of (%.0f) / (%.0f) pct.",add_ac,tmp_pct_mitigated / pct_mitigation); + Message(0, "###################ABORT#######################"); + Message(0, "[#Tune] - Error: Unable to find desired result for ( %.0f pct ) - Increase interval ( %i ) AND/OR max loop value ( %i ) and run again.", pct_mitigation, interval, max_loop); + Message(0, "[#Tune] - Parse ended at an AC ADJUSTMENT of ( %i ) on ' %s ' to acheive an average mitigation of ( %.0f pct ) verse ' %s '", loop_add_ac, attacker->GetCleanName(), tmp_pct_mitigated, defender->GetCleanName()); + Message(0, "###################COMPLETE###################"); + return; } -uint32 Mob::Tune_GetMeanDamage(Mob* GM, Mob *attacker, int32 damage, int32 minhit, ExtraAttackOptions *opts, int Msg, - int ac_override, int atk_override, int add_ac, int add_atk) +void Mob::TuneGetATKByPctMitigation(Mob* defender, Mob *attacker, float pct_mitigation, int interval, int max_loop, int ac_override, int Msg) +{ + Message(0, " "); + /* + Find the amount of ATK stat that has to be added/subtracted from ATTACKER to reach a specific average mitigation value based on DEFENDERS's mitigation statistics. + Can use ac_override to find the value verse a hypothetical amount of worn AC + */ + if (pct_mitigation > 100 || pct_mitigation < 0) { + Message(0, "[#Tune] - Processing... Abort! Mitigation value out of range ( %.0f ) pct. Must be between 0-100.", pct_mitigation); + return; + } + if (!defender) { + Message(0, "[#Tune] - Processing... Abort! No Defender found."); + return; + } + if (!attacker) { + Message(0, "[#Tune] - Processing... Abort! No Attacker found."); + return; + } + if (defender->GetID() == attacker->GetID()) { + Message(0, "[#Tune] - Processing... Abort! Error Attacker can not be the Defender."); + return; + } + + int max_damage = 0; + int min_damage = 0; + int mean_dmg = 0; + float tmp_pct_mitigated = 0.0f; + float base_pct_mitigation = pct_mitigation; + int loop_add_atk = 0; + int end = 0; + int value = 0; + + + max_damage = attacker->TuneClientGetMaxDamage(defender); + min_damage = attacker->TuneClientGetMinDamage(defender, max_damage); + + if (!max_damage) + { + Message(0, "#Tune - Processing... Abort! Damage not found! [MaxDMG %i MinDMG %i]", max_damage, min_damage); + return; + } + + //Obtain baseline mitigation for current stats + mean_dmg = attacker->TuneClientGetMeanDamage(defender, ac_override); + tmp_pct_mitigated = 100.0f - (static_cast(mean_dmg) * 100.0f / static_cast(max_damage)); + + Message(0, "###################START###################"); + Message(0, "[#Tune] DFENDER Name: %s", defender->GetCleanName()); + Message(0, "[#Tune] DEFENDER AC Mitigation pct: %.0f pct ", round(tmp_pct_mitigated)); + Message(0, "[#Tune] DEFENDER Total AC: %i ", defender->TuneACSum(false, ac_override)); + Message(0, "[#Tune] ATTACKER Name: %s", attacker->GetCleanName()); + Message(0, "[#Tune] ATTACKER Max Damage %i Min Damage %i", max_damage, min_damage); + Message(0, "[#Tune] ATTACKER Total Offense: %i ", TuneGetOffense(defender, attacker)); + Message(0, "##########################################"); + Message(0, "[#Tune] Begin Parse [Interval %i Max Loop Iterations %i]", interval, max_loop); + + if (tmp_pct_mitigated < pct_mitigation) { + interval = interval * -1; + Message(0, "[#Tune] NOTE: Attackers 'ATK' must be LOWERED due to defenders AC Mitigation ( %.0f pct ) being less than the desired ( %.0f pct )", tmp_pct_mitigated, pct_mitigation); + } + + Message(0, "[#Tune] Processing... Find ATK on attacker for defender to have Mitigation of ( %.0f pct ) agianst this attacker.", pct_mitigation); + + for (int j = 0; j < max_loop; j++) + { + mean_dmg = attacker->TuneClientGetMeanDamage(defender, ac_override, 0, 0, loop_add_atk); + tmp_pct_mitigated = 100.0f - (static_cast(mean_dmg) * 100.0f / static_cast(max_damage)); + + if (Msg >= 3) + { + Message(0, "[#Tune] - Processing... [%i] [ATK %i] Average Melee Hit %i | Pct Mitigated %.2f ", j, loop_add_atk, mean_dmg, tmp_pct_mitigated); + } + + if (interval > 0 && tmp_pct_mitigated <= pct_mitigation) { + end = 1; + } + else if (interval < 0 && tmp_pct_mitigated >= pct_mitigation) { + end = 1; + } + + else if (interval < 0 && mean_dmg == min_damage) + { + Message(0, "[#Tune] [WARNING] Mitigation can not be further decreased due to minium hit value ( %i ). Minium mitigation ( %.0f pct )", min_damage, tmp_pct_mitigated); + base_pct_mitigation = tmp_pct_mitigated; + end = 1; + } + + if (end >= 1) { + + Message(0, "###################RESULTS###################"); + + if (ac_override) { + Message(0, "[#Tune] AC STAT OVERRRIDE. This is the amount of ATK adjustment needed if this defender had ( %i ) raw AC stat", ac_override); + } + + if (attacker->IsNPC()) { + Message(0, "[#Tune] Recommended NPC ATK ADJUSTMENT ( %i ) on ' %s ' so that their hits on average are mitgiated by ( %.0f pct ) verse ' %s '. ", loop_add_atk, attacker->GetCleanName(), base_pct_mitigation, defender->GetCleanName()); + Message(0, "[#Tune] SET NPC 'ATK' stat value = [ %i ]", loop_add_atk + defender->CastToNPC()->ATK); + Message(0, "###################COMPLETE###################"); + } + if (attacker->IsClient()) { + Message(0, "[#Tune] Recommended CLIENT ATK ADJUSTMENT ( %i ) on ' %s ' so that their hits on average are mitigated by ( %.0f pct ) verse ' %s '. ", loop_add_atk, attacker->GetCleanName(), base_pct_mitigation, defender->GetCleanName()); + + if (loop_add_atk >= 0) { + Message(0, "[#Tune] MODIFY Client Item ATK or Spell Effect ATK by [+ %i ]", loop_add_atk); + } + else { + Message(0, "[#Tune] MODIFY Client Item ATK or Spell Effect ATK by [ %i ]", loop_add_atk); + } + + Message(0, "###################COMPLETE###################"); + } + + return; + } + + loop_add_atk = loop_add_atk + interval; + } + + Message(0, "###################ABORT#######################"); + Message(0, "[#Tune] - Error: Unable to find desired result for ( %.0f pct ) - Increase interval ( %i ) AND/OR max loop value ( %i ) and run again.", pct_mitigation, interval, max_loop); + Message(0, "[#Tune] - Parse ended at an ATK ADJUSTMENT of ( %i ) on ' %s ' so that their hits on average are mitigated by ( %.0f pct ) verse ' %s '.", loop_add_atk, attacker->GetCleanName(), tmp_pct_mitigated, defender->GetCleanName()); + Message(0, "###################COMPLETE###################"); + return; +} + +void Mob::TuneGetAvoidanceByHitChance(Mob* defender, Mob *attacker, float hit_chance, int interval, int max_loop, int accuracy_override, int Msg) +{ + Message(0, " "); + if (hit_chance > 100 || hit_chance < 0) { + Message(0, "[#Tune] - Processing... Abort! Hit Chance value out of range ( %.0f ) pct. Must be between 0-100.", hit_chance); + return; + } + if (!defender) { + Message(0, "[#Tune] - Processing... Abort! No Defender found."); + return; + } + if (!attacker) { + Message(0, "[#Tune] - Processing... Abort! No Attacker found."); + return; + } + if (defender->GetID() == attacker->GetID()) { + Message(0, "[#Tune] - Processing... Abort! Error Attacker can not be the Defender."); + return; + } + + int loop_add_avoid = 0; + float tmp_hit_chance = 0.0f; + bool end = false; + int base_avoidance = TuneGetAvoidance(defender, attacker); + + tmp_hit_chance = TuneGetHitChance(defender, attacker, 0, accuracy_override); + + Message(0, "###################START###################"); + Message(0, "[#Tune] DEFENDER Name: %s", defender->GetCleanName()); + Message(0, "[#Tune] DEFENDER Chance to be missed: %.0f pct", (100.0f - round(tmp_hit_chance))); + Message(0, "[#Tune] DEFENDER Avoidance: %i ", TuneGetAvoidance(defender, attacker)); + Message(0, "[#Tune] ATTACKER Name: %s", attacker->GetCleanName()); + Message(0, "[#Tune] ATTACKER Chance to hit: %.0f pct", round(tmp_hit_chance)); + Message(0, "[#Tune] ATTACKER Accuracy: %i ", TuneGetAccuracy(defender, attacker, accuracy_override)); + Message(0, "##########################################"); + Message(0, "[#Tune] Begin Parse [Interval %i Max Loop Iterations %i]", interval, max_loop); + + if (tmp_hit_chance < hit_chance) { + interval = interval * -1; + Message(0, "[#Tune] NOTE: Defenders 'AVOIDANCE' must be LOWERED due to defenders ( %.0f pct ) chance to be hit being less than the desired ( %.0f pct )", tmp_hit_chance, hit_chance); + } + + Message(0, "[#Tune] - Processing... Find Avoidance needed on defender for a ( %.0f pct ) hit chance from attacker. Base attacker hit chance ( %.0f pct ). ", hit_chance, tmp_hit_chance); + + for (int j = 0; j < max_loop; j++) + { + tmp_hit_chance = TuneGetHitChance(defender, attacker,0, accuracy_override, loop_add_avoid,0); + + if (Msg >= 3) + { + Message(0, "[#Tune] - Processing... [%i] AVOIDANCE %i | Hit Chance %.2f ", j, loop_add_avoid, tmp_hit_chance); + } + + if (interval > 0 && tmp_hit_chance <= hit_chance) + { + end = true; + } + else if (interval < 0 && tmp_hit_chance >= hit_chance) + { + end = true; + } + + if (end) { + + Message(0, "###################RESULTS###################"); + + if (accuracy_override) { + Message(0, "[#Tune] ACCURACY STAT OVERRRIDE. This is the amount of AVOIDANCE adjustment needed if this attacker had ( %i ) raw ACCURACY stat", accuracy_override); + } + + if (defender->IsNPC()) { + Message(0, "[#Tune] Recommended NPC AVOIDANCE ADJUSTMENT of ( %i ) on ' %s ' will result in ' %s ' having a ( %.0f pct) hit chance.", loop_add_avoid, defender->GetCleanName(), attacker->GetCleanName(), hit_chance); + Message(0, "[#Tune] SET NPC 'AVOIDANCE' stat value = [ %i ]", loop_add_avoid + defender->CastToNPC()->GetAvoidanceRating()); + Message(0, "###################COMPLETE###################"); + } + else if (defender->IsClient()) { + Message(0, "[#Tune] Recommended CLIENT AVOIDANCE ADJUSTMENT of ( %i ) on ' %s ' will result in ' %s ' having a ( %.0f pct) hit chance.", loop_add_avoid, defender->GetCleanName(), attacker->GetCleanName(), hit_chance); + + int final_avoidance = TuneGetAvoidance(defender, attacker, 0, loop_add_avoid); + int evasion_bonus = TuneCalcEvasionBonus(final_avoidance, base_avoidance); + + if (loop_add_avoid >= 0) { + Message(0, "[#Tune] OPTION1: MODIFY Client Heroic AGI or Avoidance Mod2 stat by [+ %i ]", loop_add_avoid); + Message(0, "[#Tune] OPTION2: Give CLIENT an evasion bonus using SPA 172 Evasion SE_AvoidMeleeChance from (spells/items/aa) of [+ %i pct ]", evasion_bonus); + + } + else { + Message(0, "[#Tune] OPTION1: MODIFY Client Heroic AGI or Avoidance Mod2 stat by [ %i ]", loop_add_avoid); + Message(0, "[#Tune] OPTION2: Give CLIENT an evasion bonus using SPA 172 Evasion SE_AvoidMeleeChance from (spells/items/aa) of [ %i pct ]", evasion_bonus); + } + + Message(0, "###################COMPLETE###################"); + } + + return; + } + + loop_add_avoid = loop_add_avoid + interval; + } + + Message(0, "###################ABORT#######################"); + Message(0, "[#Tune] Error: Unable to find desired result for ( %.0f pct) - Increase interval (%i) AND/OR max loop value (%i) and run again.", hit_chance, interval, max_loop); + Message(0, "[#Tune] Parse ended at AVOIDANCE ADJUSTMENT ( %i ) on ' %s ' will result in ' %s ' having a ( %.0f pct) hit chance.", loop_add_avoid, defender->GetCleanName(), hit_chance, attacker->GetCleanName()); + Message(0, "###################COMPLETE###################"); +} + +void Mob::TuneGetAccuracyByHitChance(Mob* defender, Mob *attacker, float hit_chance, int interval, int max_loop, int avoidance_override, int Msg) +{ + Message(0, " "); + if (hit_chance > 100 || hit_chance < 0) { + Message(0, "[#Tune] - Processing... Abort! Hit Chance value out of range ( %.0f ) pct. Must be between 0-100.", hit_chance); + return; + } + if (!defender) { + Message(0, "[#Tune] - Processing... Abort! No Defender found."); + return; + } + if (!attacker) { + Message(0, "[#Tune] - Processing... Abort! No Attacker found."); + return; + } + if (defender->GetID() == attacker->GetID()) { + Message(0, "[#Tune] - Processing... Abort! Error Attacker can not be the Defender."); + return; + } + + int loop_add_accuracy = 0; + float tmp_hit_chance = 0.0f; + bool end = false; + + tmp_hit_chance = TuneGetHitChance(defender, attacker, avoidance_override); + + Message(0, "###################START###################"); + Message(0, "[#Tune] DEFENDER Name: %s", defender->GetCleanName()); + Message(0, "[#Tune] DEFENDER Chance to be missed: %.0f pct", (100.0f - round(tmp_hit_chance))); + Message(0, "[#Tune] DEFENDER Avoidance: %i ", TuneGetAvoidance(defender, attacker, avoidance_override)); + Message(0, "[#Tune] ATTACKER Name: %s", attacker->GetCleanName()); + Message(0, "[#Tune] ATTACKER Chance to hit: %.0f pct", round(tmp_hit_chance)); + Message(0, "[#Tune] ATTACKER Accuracy: %i ", TuneGetAccuracy(defender, attacker)); + Message(0, "##########################################"); + Message(0, "[#Tune] Begin Parse [Interval %i Max Loop Iterations %i]", interval, max_loop); + + + if (tmp_hit_chance > hit_chance) { + interval = interval * -1; + Message(0, "[#Tune] NOTE: Attackers 'ACCURACY' must be LOWERED due to attackers ( %.0f pct ) chance to hit being less than the desired ( %.0f pct )", tmp_hit_chance, hit_chance); + } + + Message(0, "[#Tune] - Processing... Find Accuracy needed on attacker for a ( %.0f pct ) hit chance on defender. Base attacker hit chance ( %.0f pct ). ", hit_chance, tmp_hit_chance); + + for (int j = 0; j < max_loop; j++) + { + + if (Msg >= 3) + { + Message(0, "[#Tune] - Processing... [%i] ACCURACY %i | Hit Chance %.2f ", j, loop_add_accuracy, tmp_hit_chance); + } + + if (interval > 0 && tmp_hit_chance >= hit_chance) + { + end = true; + } + + else if (interval < 0 && tmp_hit_chance <= hit_chance) + { + end = true; + } + + if (end) { + + Message(0, "###################RESULTS###################"); + + if (avoidance_override) { + Message(0, "[#Tune] AVOIDANCE STAT OVERRRIDE. This is the amount of ACCURACY adjustment needed if this defender had ( %i ) raw AVOIDANCE stat", avoidance_override); + } + + if (defender->IsNPC()) { + Message(0, "[#Tune] Recommended NPC ACCURACY ADJUSTMENT of ( %i ) on ' %s ' will result in ( %.0f pct ) chance to hit ' %s '.", loop_add_accuracy, defender->GetCleanName(), hit_chance, attacker->GetCleanName()); + Message(0, "[#Tune] SET NPC 'ACCURACY' stat value = [ %i ]", loop_add_accuracy + defender->CastToNPC()->GetAccuracyRating()); + Message(0, "###################COMPLETE###################"); + } + else if (defender->IsClient()) { + Message(0, "[#Tune] Recommended CLIENT AVOIDANCE ADJUSTMENT of ( %i ) on %s ' will result in ( %.0f pct ) chance to hit ' %s '.", loop_add_accuracy, defender->GetCleanName(), hit_chance, attacker->GetCleanName()); + + if (loop_add_accuracy >= 0) { + Message(0, "[#Tune] OPTION1: MODIFY Client Avoidance Mod2 stat or SPA 216 Melee Accuracy (spells/items/aa) [+ %i ]", loop_add_accuracy); + + } + else { + Message(0, "[#Tune] OPTION1: MODIFY Client Avoidance Mod2 stat or SPA 216 Melee Accuracy (spells/items/aa) [ %i ]", loop_add_accuracy); + } + + Message(0, "###################COMPLETE###################"); + } + + return; + } + + loop_add_accuracy = loop_add_accuracy + interval; + } + + Message(0, "###################ABORT#######################"); + Message(0, "[#Tune] Error: Unable to find desired result for ( %.0f pct) - Increase interval (%i) AND/OR max loop value (%i) and run again.", hit_chance, interval, max_loop); + Message(0, "[#Tune] Parse ended at ACCURACY ADJUSTMENT of ( %i ) on ' %s ' will result in ( %.0f pct ) chance to hit ' %s '.", loop_add_accuracy, defender->GetCleanName(), hit_chance, attacker->GetCleanName()); + Message(0, "###################COMPLETE###################"); +} + +/* + Tune support functions +*/ + +int Mob::TuneClientGetMeanDamage(Mob* other, int ac_override, int atk_override, int add_ac, int add_atk) { uint32 total_damage = 0; int loop_max = 1000; - for (int i=0; i < loop_max ; i++) + for (int i = 0; i < loop_max; i++) { - total_damage += Tune_MeleeMitigation(GM, attacker, damage, minhit, nullptr,0,ac_override, atk_override, add_ac, add_atk); - } - - return(total_damage/loop_max); -} - -int32 Mob::Tune_MeleeMitigation(Mob* GM, Mob *attacker, int32 damage, int32 minhit, ExtraAttackOptions *opts, int Msg, - int ac_override, int atk_override, int add_ac, int add_atk) -{ - if (damage <= 0) - return 0; - - Mob* defender = this; - float aa_mit = (aabonuses.CombatStability + itembonuses.CombatStability + - spellbonuses.CombatStability) / 100.0f; - - if (Msg){ - - GM->Message(Chat::White, "######### Melee Mitigation Report: Start [Detail Level %i]#########", Msg); - GM->Message(Chat::White, "#ATTACKER: %s", attacker->GetCleanName()); - GM->Message(Chat::White, "#DEFENDER: %s", defender->GetCleanName()); - } - - if (RuleB(Combat, UseIntervalAC)) { - float softcap = (GetSkill(EQ::skills::SkillDefense) + GetLevel()) * - RuleR(Combat, SoftcapFactor) * (1.0 + aa_mit); - float mitigation_rating = 0.0; - float attack_rating = 0.0; - int shield_ac = 0; - int armor = 0; - float weight = 0.0; - - if (Msg >= 2){ - GM->Message(Chat::White, " "); - GM->Message(Chat::White, "### Calculate Mitigation Rating ###"); - if (aabonuses.CombatStability) - GM->Message(Chat::White, "# %i #### DEFENDER SE_CombatStability(259) AA Bonus", aabonuses.CombatStability); - if (spellbonuses.CombatStability) - GM->Message(Chat::White, "# %i #### DEFENDER SE_CombatStability(259) Spell Bonus", spellbonuses.CombatStability); - if (itembonuses.CombatStability) - GM->Message(Chat::White, "# %i #### DEFENDER SE_CombatStability(259) Worn Bonus", itembonuses.CombatStability); - - GM->Message(Chat::White, "# %.2f #### DEFENDER Base Soft Cap", softcap); - } - - float monkweight = RuleI(Combat, MonkACBonusWeight); - monkweight = mod_monk_weight(monkweight, attacker); - if (IsClient()) { - armor = CastToClient()->GetRawACNoShield(shield_ac) + add_ac; - weight = (CastToClient()->CalcCurrentWeight() / 10.0); - - if (ac_override) - armor = ac_override; - - if (Msg >=2 ){ - GM->Message(Chat::White, "# %i #### DEFENDER AC Equiped/Worn Bonus", itembonuses.AC); - GM->Message(Chat::White, "# %i #### DEFENDER SE_ArmorClass(1) AA Bonus", aabonuses.AC); - GM->Message(Chat::White, "# %i #### DEFENDER SE_ArmorClass(1) Spell Bonus", spellbonuses.AC); - GM->Message(Chat::White, "# %i #### DEFENDER Shield AC", shield_ac); - GM->Message(Chat::White, "# %i #### DEFENDER Total Client Armor - NO shield", armor); - } - - } else if (IsNPC()) { - armor = CastToNPC()->GetRawAC() + add_ac; - - if (ac_override) - armor = ac_override; - - if (Msg >=2 ){ - GM->Message(Chat::White, "# %i #### DEFENDER AC Equiped/Worn Bonus", itembonuses.AC); - GM->Message(Chat::White, "# %i #### DEFENDER SE_ArmorClass(1) Spell Bonus", spellbonuses.AC); - GM->Message(Chat::White, "# %i #### DEFENDER NPC AC Stat", CastToNPC()->GetRawAC()); - } - - int PetACBonus = 0; - - if (!IsPet()){ - armor = (armor / RuleR(Combat, NPCACFactor)); - if (Msg >=2 ) - GM->Message(Chat::White, "# %i #### DEFENDER NPC Armor after RuleR(Combat, NPCACFactor) %.2f", armor, RuleR(Combat, NPCACFactor)); - } - - Mob *owner = nullptr; - if (IsPet()) - owner = GetOwner(); - else if ((CastToNPC()->GetSwarmOwner())) - owner = entity_list.GetMobID(CastToNPC()->GetSwarmOwner()); - - if (owner){ - PetACBonus = owner->aabonuses.PetMeleeMitigation + owner->itembonuses.PetMeleeMitigation + owner->spellbonuses.PetMeleeMitigation; - - if (Msg >=2 ){ - if (owner->aabonuses.PetMeleeMitigation) - GM->Message(Chat::White, "# %i #### DEFENDER Pet Owner SE_PetMeleeMitigation(379) AA Bonus", owner->aabonuses.PetMeleeMitigation); - if (owner->spellbonuses.PetMeleeMitigation) - GM->Message(Chat::White, "# %i #### DEFENDER Pet Owner SE_PetMeleeMitigation(379) Spell Bonus",owner->spellbonuses.PetMeleeMitigation); - if (owner->itembonuses.PetMeleeMitigation) - GM->Message(Chat::White, "# %i #### DEFENDER Pet Owner SE_PetMeleeMitigation(379) Worn Bonus", owner->itembonuses.PetMeleeMitigation); - } - } - - armor += spellbonuses.AC + itembonuses.AC + PetACBonus + 1; - - if (Msg >= 2) - GM->Message(Chat::White, "# %i #### DEFENDER NPC Total Base Armor",armor); + total_damage += TuneClientAttack(other, true, true, 10000, ac_override, atk_override, add_ac, add_atk); } - - if (opts) { - armor *= (1.0f - opts->armor_pen_percent); - armor -= opts->armor_pen_flat; + else { + total_damage += TuneNPCAttack(other, true, true, 10000, ac_override, atk_override, add_ac, add_atk); } - - if (RuleB(Combat, OldACSoftcapRules)) { - if (GetClass() == WIZARD || GetClass() == MAGICIAN || - GetClass() == NECROMANCER || GetClass() == ENCHANTER) - softcap = RuleI(Combat, ClothACSoftcap); - else if (GetClass() == MONK && weight <= monkweight) - softcap = RuleI(Combat, MonkACSoftcap); - else if(GetClass() == DRUID || GetClass() == BEASTLORD || GetClass() == MONK) - softcap = RuleI(Combat, LeatherACSoftcap); - else if(GetClass() == SHAMAN || GetClass() == ROGUE || - GetClass() == BERSERKER || GetClass() == RANGER) - softcap = RuleI(Combat, ChainACSoftcap); - else - softcap = RuleI(Combat, PlateACSoftcap); - } - softcap += shield_ac; - armor += shield_ac; - - if (RuleB(Combat, OldACSoftcapRules)) - softcap += (softcap * (aa_mit * RuleR(Combat, AAMitigationACFactor))); - if (armor > softcap) { - int softcap_armor = armor - softcap; - if (RuleB(Combat, OldACSoftcapRules)) { - if (GetClass() == WARRIOR) - softcap_armor = softcap_armor * RuleR(Combat, WarriorACSoftcapReturn); - else if (GetClass() == SHADOWKNIGHT || GetClass() == PALADIN || - (GetClass() == MONK && weight <= monkweight)) - softcap_armor = softcap_armor * RuleR(Combat, KnightACSoftcapReturn); - else if (GetClass() == CLERIC || GetClass() == BARD || - GetClass() == BERSERKER || GetClass() == ROGUE || - GetClass() == SHAMAN || GetClass() == MONK) - softcap_armor = softcap_armor * RuleR(Combat, LowPlateChainACSoftcapReturn); - else if (GetClass() == RANGER || GetClass() == BEASTLORD) - softcap_armor = softcap_armor * RuleR(Combat, LowChainLeatherACSoftcapReturn); - else if (GetClass() == WIZARD || GetClass() == MAGICIAN || - GetClass() == NECROMANCER || GetClass() == ENCHANTER || - GetClass() == DRUID) - softcap_armor = softcap_armor * RuleR(Combat, CasterACSoftcapReturn); - else - softcap_armor = softcap_armor * RuleR(Combat, MiscACSoftcapReturn); - } else { - if (GetClass() == WARRIOR) - softcap_armor *= RuleR(Combat, WarACSoftcapReturn); - else if (GetClass() == PALADIN || GetClass() == SHADOWKNIGHT) - softcap_armor *= RuleR(Combat, PalShdACSoftcapReturn); - else if (GetClass() == CLERIC || GetClass() == RANGER || - GetClass() == MONK || GetClass() == BARD) - softcap_armor *= RuleR(Combat, ClrRngMnkBrdACSoftcapReturn); - else if (GetClass() == DRUID || GetClass() == NECROMANCER || - GetClass() == WIZARD || GetClass() == ENCHANTER || - GetClass() == MAGICIAN) - softcap_armor *= RuleR(Combat, DruNecWizEncMagACSoftcapReturn); - else if (GetClass() == ROGUE || GetClass() == SHAMAN || - GetClass() == BEASTLORD || GetClass() == BERSERKER) - softcap_armor *= RuleR(Combat, RogShmBstBerACSoftcapReturn); - else - softcap_armor *= RuleR(Combat, MiscACSoftcapReturn); - } - - - armor = softcap + softcap_armor; - if (Msg >= 2) - GM->Message(Chat::White, "# %i #### DEFENDER Final Armor [Soft Cap %i Soft Cap Armor %i]",armor, softcap,softcap_armor); - } - int tmp_armor = armor; - if (GetClass() == WIZARD || GetClass() == MAGICIAN || - GetClass() == NECROMANCER || GetClass() == ENCHANTER){ - mitigation_rating = ((GetSkill(EQ::skills::SkillDefense) + itembonuses.HeroicAGI / 10) / 4.0) + armor + 1; - if (Msg >= 2) - GM->Message(Chat::White, "# + %.2f #### DEFENDER Armor Bonus [Defense Skill %i Heroic Agi %i]", mitigation_rating - tmp_armor, GetSkill(EQ::skills::SkillDefense), itembonuses.HeroicAGI); - } - else{ - mitigation_rating = ((GetSkill(EQ::skills::SkillDefense) + itembonuses.HeroicAGI / 10) / 3.0) + (armor * 1.333333) + 1; - if (Msg >= 2) - GM->Message(Chat::White, "# + %.2f #### DEFENDER Armor Bonus [Defense Skill %i Heroic Agi %i]", mitigation_rating - tmp_armor, GetSkill(EQ::skills::SkillDefense), itembonuses.HeroicAGI); - - } - mitigation_rating *= 0.847; - - if (Msg >= 1) - GM->Message(Chat::White, "# %.2f #### DEFENDER Final Mitigation Rating", mitigation_rating); - - - if (Msg >= 2){ - GM->Message(Chat::White, " "); - GM->Message(Chat::White, "### Mitigation Bonus Effects ###"); - if (itembonuses.MeleeMitigation) - GM->Message(Chat::White, "# %i #### DEFENDER Item Mod2 Shielding", itembonuses.MeleeMitigation); - if (aabonuses.MeleeMitigationEffect) - GM->Message(Chat::White, "# %i #### DEFENDER SE_MeleeMitigation(168) AA Bonus", aabonuses.MeleeMitigationEffect); - if (spellbonuses.MeleeMitigationEffect) - GM->Message(Chat::White, "# %i #### DEFENDER SE_MeleeMitigation(168) Spell Bonus", spellbonuses.MeleeMitigationEffect); - if (itembonuses.MeleeMitigationEffect) - GM->Message(Chat::White, "# %i #### DEFENDER SE_MeleeMitigation(168) Worn Bonus", itembonuses.MeleeMitigationEffect); - } - - mitigation_rating = mod_mitigation_rating(mitigation_rating, attacker); - - if (attacker->IsClient()){ - if (atk_override) - attack_rating = (atk_override + ((attacker->GetSTR() - 66) * 0.9) + (attacker->GetSkill(EQ::skills::SkillOffense)*1.345)); - else - attack_rating = ((attacker->CastToClient()->CalcATK() + add_atk) + ((attacker->GetSTR() - 66) * 0.9) + (attacker->GetSkill(EQ::skills::SkillOffense)*1.345)); - - } - else{ - if (atk_override) - attack_rating = (atk_override + (attacker->GetSkill(EQ::skills::SkillOffense)*1.345) + ((attacker->GetSTR() - 66) * 0.9)); - else - attack_rating = ((attacker->GetATK() + add_atk) + (attacker->GetSkill(EQ::skills::SkillOffense)*1.345) + ((attacker->GetSTR() - 66) * 0.9)); - } - - attack_rating = attacker->mod_attack_rating(attack_rating, this); - - if (Msg >= 2){ - GM->Message(Chat::White, " "); - GM->Message(Chat::White, "### Calculate Attack Rating ###"); - if (attacker->IsClient()){ - GM->Message(Chat::White, "# %i #### ATTACKER Worn/Equip ATK Bonus", attacker->itembonuses.ATK); - GM->Message(Chat::White, "# %i #### ATTACKER SE_ATK(2) AA Bonus", attacker->aabonuses.ATK); - GM->Message(Chat::White, "# %i #### ATTACKER SE_ATK(2) spell Bonus", attacker->spellbonuses.ATK); - GM->Message(Chat::White, "# %i #### ATTACKER Leadership Bonus", attacker->CastToClient()->GroupLeadershipAAOffenseEnhancement()); - GM->Message(Chat::White, "# %i #### ATTACKER Worn/Equip ATK Bonus", attacker->itembonuses.ATK); - GM->Message(Chat::White, "# %i #### ATTACKER Worn/Equip ATK Bonus", attacker->itembonuses.ATK); - GM->Message(Chat::White, "# %.2f #### ATTACKER Strength Stat ATK Bonus [Stat Amt: %i]", ((attacker->GetSTR()-66) * 0.9),attacker->GetSTR()); - GM->Message(Chat::White, "# %.2f #### ATTACKER Offensive Skill ATK Bonus [Stat Amt: %i]", (attacker->GetSkill(EQ::skills::SkillOffense)*1.345), attacker->GetSkill(EQ::skills::SkillOffense)); - } - - else{ - GM->Message(Chat::White, "# %i #### ATTACKER Worn/Equip ATK Bonus", attacker->itembonuses.ATK); - GM->Message(Chat::White, "# %i #### ATTACKER SE_ATK(2) spell Bonus", attacker->spellbonuses.ATK); - GM->Message(Chat::White, "# %i #### ATTACKER NPC ATK Stat", attacker->CastToNPC()->ATK); - GM->Message(Chat::White, "# %.2f #### ATTACKER Strength Stat ATK Bonus [Stat Amt: %i]", ((attacker->GetSTR()-66) * 0.9),attacker->GetSTR()); - GM->Message(Chat::White, "# %.2f #### ATTACKER Offensive Skill ATK Bonus [Stat Amt: %i]", (attacker->GetSkill(EQ::skills::SkillOffense)*1.345), attacker->GetSkill(EQ::skills::SkillOffense)); - } - } - - if (Msg >= 1){ - GM->Message(Chat::White, "# %.2f #### ATTACKER Final Attack Rating", attack_rating); - GM->Message(Chat::White, "######### Melee Mitigation Report: Complete #########", Msg); - } - - - //damage = GetMeleeMitDmg(attacker, damage, minhit, mitigation_rating, attack_rating); - } - - if (damage < 0) - damage = 0; - - return damage; -} - -// This is called when the Mob is the one being hit -int32 Mob::Tune_GetMeleeMitDmg(Mob* GM, Mob *attacker, int32 damage, int32 minhit, - float mit_rating, float atk_rating) -{ - float d = 10.0; - float mit_roll = zone->random.Real(0, mit_rating); - float atk_roll = zone->random.Real(0, atk_rating); - - if (atk_roll > mit_roll) { - float a_diff = atk_roll - mit_roll; - float thac0 = atk_rating * RuleR(Combat, ACthac0Factor); - float thac0cap = attacker->GetLevel() * 9 + 20; - if (thac0 > thac0cap) - thac0 = thac0cap; - - d -= 10.0 * (a_diff / thac0); - } else if (mit_roll > atk_roll) { - float m_diff = mit_roll - atk_roll; - float thac20 = mit_rating * RuleR(Combat, ACthac20Factor); - float thac20cap = GetLevel() * 9 + 20; - if (thac20 > thac20cap) - thac20 = thac20cap; - - d += 10.0 * (m_diff / thac20); } - if (d < 0.0) - d = 0.0; - else if (d > 20.0) - d = 20.0; - - float interval = (damage - minhit) / 20.0; - damage -= ((int)d * interval); - - damage -= (minhit * itembonuses.MeleeMitigation / 100); - damage -= (damage * (spellbonuses.MeleeMitigationEffect + itembonuses.MeleeMitigationEffect + aabonuses.MeleeMitigationEffect) / 100); - return damage; + return(total_damage / loop_max); } -// This is called when the Client is the one being hit -int32 Client::Tune_GetMeleeMitDmg(Mob* GM, Mob *attacker, int32 damage, int32 minhit, - float mit_rating, float atk_rating) +int Mob::TuneClientGetMaxDamage(Mob* other) { - if (!attacker->IsNPC() || RuleB(Combat, UseOldDamageIntervalRules)) - return 0; //Mob::GetMeleeMitDmg(attacker, damage, minhit, mit_rating, atk_rating); - int d = 10; - // floats for the rounding issues - float dmg_interval = (damage - minhit) / 19.0; - float dmg_bonus = minhit - dmg_interval; - float spellMeleeMit = (spellbonuses.MeleeMitigationEffect + itembonuses.MeleeMitigationEffect + aabonuses.MeleeMitigationEffect) / 100.0; - if (GetClass() == WARRIOR) - spellMeleeMit += 0.05; - dmg_bonus -= dmg_bonus * (itembonuses.MeleeMitigation / 100.0); - dmg_interval -= dmg_interval * spellMeleeMit; + uint32 max_hit = 0; + uint32 current_hit = 0; + int loop_max = 1000; - float mit_roll = zone->random.Real(0, mit_rating); - float atk_roll = zone->random.Real(0, atk_rating); + for (int i = 0; i < loop_max; i++) + { + if (IsClient()) { + current_hit = TuneClientAttack(other, true, true, 10000, 1, 10000); + } + else { + current_hit = TuneNPCAttack(other, true, true, 10000, 1, 10000); + } - if (atk_roll > mit_roll) { - float a_diff = atk_roll - mit_roll; - float thac0 = atk_rating * RuleR(Combat, ACthac0Factor); - float thac0cap = attacker->GetLevel() * 9 + 20; - if (thac0 > thac0cap) - thac0 = thac0cap; + if (current_hit > max_hit) { + max_hit = current_hit; + } + } + return(max_hit); +} - d += 10 * (a_diff / thac0); - } else if (mit_roll > atk_roll) { - float m_diff = mit_roll - atk_roll; - float thac20 = mit_rating * RuleR(Combat, ACthac20Factor); - float thac20cap = GetLevel() * 9 + 20; - if (thac20 > thac20cap) - thac20 = thac20cap; +int Mob::TuneClientGetMinDamage(Mob* other, int max_hit) +{ + uint32 min_hit = max_hit; + uint32 current_hit = 0; + int loop_max = 1000; - d -= 10 * (m_diff / thac20); + for (int i = 0; i < loop_max; i++) + { + if (IsClient()) { + current_hit = TuneClientAttack(other, true, true, 10000, 10000, 1); + } + else { + current_hit = TuneNPCAttack(other, true, true, 10000, 10000, 1); + } + + if (current_hit < min_hit) { + min_hit = current_hit; + } + } + return(min_hit); +} + +float Mob::TuneGetACMitigationPct(Mob* defender, Mob *attacker) { + + int max_damage = 0; + int min_damage = 0; + + max_damage = attacker->TuneClientGetMaxDamage(defender); + min_damage = attacker->TuneClientGetMinDamage(defender, max_damage); + + if (!max_damage) + { + Message(0, "[#Tune] Calculation Failure. Error: [Mob::TuneGetACMitigationPct] No max damage found"); + return max_damage; } - if (d < 1) - d = 1; - else if (d > 20) - d = 20; + int mean_dmg = attacker->TuneClientGetMeanDamage(defender); + float tmp_pct_mitigated = 100.0f - (static_cast(mean_dmg) * 100.0f / static_cast(max_damage)); - return static_cast((dmg_bonus + dmg_interval * d)); + return tmp_pct_mitigated; } - -int32 Client::GetMeleeDamage(Mob* other, bool GetMinDamage) +int Mob::TuneGetOffense(Mob* defender, Mob *attacker, int atk_override) { + int offense_rating = 0; + if (attacker->IsClient()) { + offense_rating = attacker->TuneClientAttack(defender, true, true, 0, 0, atk_override, 0, 0, true); + } + else { + offense_rating = attacker->TuneNPCAttack(defender, true, true, 0, 0, atk_override, 0, 0, true); + } + return offense_rating; +} + +int Mob::TuneGetAccuracy(Mob* defender, Mob *attacker, int accuracy_override, int add_accuracy) +{ + int accuracy = 0; + if (attacker->IsClient()) { + accuracy = attacker->TuneClientAttack(defender, true, true, 0, 0, 0, 0, 0, false, true,0,accuracy_override,0,add_accuracy); + } + else { + accuracy = attacker->TuneNPCAttack(defender, true, true, 0, 0, 0, 0, 0, false, true, 0, accuracy_override, 0, add_accuracy); + } + return accuracy; +} + +int Mob::TuneGetAvoidance(Mob* defender, Mob *attacker, int avoidance_override, int add_avoidance) +{ + return defender->TuneGetTotalDefense(avoidance_override, add_avoidance); +} + +float Mob::TuneGetHitChance(Mob* defender, Mob *attacker, int avoidance_override, int accuracy_override, int add_avoidance, int add_accuracy) +{ + uint32 hit_count = 0; + uint32 current_hit = 0; + + int loop_max = 2000; + + for (int i = 0; i < loop_max; i++) + { + if (attacker->IsClient()) { + current_hit = attacker->TuneClientAttack(defender, true, false, 0, 0, 0, 0, 0, false, false, avoidance_override, accuracy_override, add_avoidance, add_accuracy); + } + else { + current_hit = attacker->TuneNPCAttack(defender, true, false, 0, 0, 0, 0, 0, false, false, avoidance_override, accuracy_override, add_avoidance, add_accuracy); + } + + if (current_hit > 0) { + hit_count++; + } + } + float chance = (static_cast(hit_count) / 2000.0f) * 100.0f; + return chance; +} + +float Mob::TuneGetAvoidMeleeChance(Mob* defender, Mob *attacker, int type) +{ + uint32 current_hit = 0; + uint32 hit_count = 0; + + /* + -1 - block + -2 - parry + -3 - riposte + -4 - dodge + */ + int loop_max = 3000; + + for (int i = 0; i < loop_max; i++) + { + if (attacker->IsClient()) { + current_hit = attacker->TuneClientAttack(defender, false, true, 0); + } + else { + current_hit = attacker->TuneNPCAttack(defender, false, true, 0); + } + + if (current_hit == type) { + hit_count++; + } + } + float chance = (static_cast(hit_count) / 3000.0f) * 100.0f; + return chance; +} + +int Mob::TuneCalcEvasionBonus(int final_avoidance, int base_avoidance) { + + /* + float eb = static_cast(final_avoidance) / static_cast(base_avoidance); + Shout(" eb %.2f ", eb); + eb = eb * 100.f; + Shout(" eb %.2f ", eb); + eb = eb - 100.0f; + Shout(" eb %.2f ", eb); + return eb; + */ + + int loop_max = 3000; + int evasion_bonus = 10; + int current_avoidance = 0; + + int interval = 5; + + if (base_avoidance > final_avoidance) + { + interval = interval * -1; + } + + for (int i = 0; i < loop_max; i++) + { + current_avoidance = (base_avoidance * (100 + evasion_bonus)) / 100; + + if (interval > 0 && current_avoidance >= final_avoidance) + { + return evasion_bonus; + } + else if (interval < 0 && current_avoidance <= final_avoidance) + { + return evasion_bonus; + } + evasion_bonus = evasion_bonus + interval; + } + return 0; +} + +/* + Calculate from modified attack.cpp functions. +*/ + +int Mob::TuneNPCAttack(Mob* other, bool no_avoid, bool no_hit_chance, int hit_chance_bonus, int ac_override, int atk_override, int add_ac, int add_atk, bool get_offense, bool get_accuracy, + int avoidance_override, int accuracy_override, int add_avoidance, int add_accuracy) +{ + if (!IsNPC()) { + Message(Chat::Red, "#Tune Failure: A null NON NPC object was passed to TuneNPCAttack() for evaluation!"); + return false; + } + + if (!other) { + Message(Chat::Red, "#Tune Failure: A null Mob object was passed to TuneNPCAttack() for evaluation!"); + return false; + } + + //Check that we can attack before we calc heading and face our target + if (!IsAttackAllowed(other)) { + Message(Chat::Red, "#Tune Failure: This NPC can not attack this target!"); + return false; + } + + DamageHitInfo my_hit; + my_hit.skill = EQ::skills::SkillHandtoHand; + my_hit.hand = EQ::invslot::slotPrimary; + my_hit.damage_done = 1; + + my_hit.skill = static_cast(CastToNPC()->GetPrimSkill()); + OffHandAtk(false); + + uint8 otherlevel = other->GetLevel(); + uint8 mylevel = this->GetLevel(); + + otherlevel = otherlevel ? otherlevel : 1; + mylevel = mylevel ? mylevel : 1; + + my_hit.base_damage = CastToNPC()->GetBaseDamage(); + my_hit.min_damage = CastToNPC()->GetMinDamage(); + //int32 hate = my_hit.base_damage + my_hit.min_damage; + + my_hit.offense = Tuneoffense(my_hit.skill, atk_override, add_atk); + if (get_offense) { + return my_hit.offense; + } + my_hit.tohit = TuneGetTotalToHit(my_hit.skill, hit_chance_bonus, accuracy_override, add_accuracy); + if (get_accuracy) { + return my_hit.tohit; + } + + TuneDoAttack(other, my_hit, nullptr, no_avoid, no_hit_chance, ac_override, add_ac, avoidance_override, accuracy_override, add_avoidance, add_accuracy); + + LogCombat("Final damage against [{}]: [{}]", other->GetName(), my_hit.damage_done); + if (other->IsClient() && IsPet() && GetOwner()->IsClient()) { + //pets do half damage to clients in pvp + my_hit.damage_done /= 2; + if (my_hit.damage_done < 1) + my_hit.damage_done = 1; + } + + return my_hit.damage_done; +} + +int Mob::TuneClientAttack(Mob* other, bool no_avoid, bool no_hit_chance, int hit_chance_bonus, int ac_override, int atk_override, int add_ac, int add_atk, bool get_offense, bool get_accuracy, + int avoidance_override, int accuracy_override, int add_avoidance, int add_accuracy) +{ + if (!IsClient()) { + Message(Chat::Red, "#Tune Failure: A null NON CLIENT object was passed to TuneClientAttack() for evaluation!"); + return false; + } + + if (!other) { + Message(Chat::Red, "#Tune Failure: A null Mob object was passed to TuneClientAttack() for evaluation!"); + return false; + } + int Hand = EQ::invslot::slotPrimary; - if (!other) - return 0; - - EQ::ItemInstance* weapon; - weapon = GetInv().GetItem(EQ::invslot::slotPrimary); - - if(weapon != nullptr) { + EQ::ItemInstance* weapon = nullptr; + weapon = CastToClient()->GetInv().GetItem(EQ::invslot::slotPrimary); + OffHandAtk(false); + + if (weapon != nullptr) { if (!weapon->IsWeapon()) { - return(0); + Message(Chat::Red, "#Tune Failure: Attack cancelled, Item %s is not a weapon!", weapon->GetItem()->Name); + return(false); } - } + } - EQ::skills::SkillType skillinuse = AttackAnimation(Hand, weapon); + DamageHitInfo my_hit; + my_hit.skill = TuneAttackAnimation(Hand, weapon); - int damage = 0; + // Now figure out damage + my_hit.damage_done = 1; + my_hit.min_damage = 0; uint8 mylevel = GetLevel() ? GetLevel() : 1; uint32 hate = 0; - if (weapon) hate = weapon->GetItem()->Damage + weapon->GetItem()->ElemDmgAmt; - int weapon_damage = GetWeaponDamage(other, weapon, &hate); - if (hate == 0 && weapon_damage > 1) hate = weapon_damage; + if (weapon) + hate = (weapon->GetItem()->Damage + weapon->GetItem()->ElemDmgAmt); - if(weapon_damage > 0){ - if(IsBerserk() && GetClass() == BERSERKER){ - int bonus = 3 + GetLevel()/10; //unverified - weapon_damage = weapon_damage * (100+bonus) / 100; + my_hit.base_damage = GetWeaponDamage(other, weapon, &hate); + if (hate == 0 && my_hit.base_damage > 1) + hate = my_hit.base_damage; + + //if weapon damage > 0 then we know we can hit the target with this weapon + //otherwise we cannot and we set the damage to -5 later on + if (my_hit.base_damage > 0) { + // if we revamp this function be more general, we will have to make sure this isn't + // executed for anything BUT normal melee damage weapons from auto attack + if (Hand == EQ::invslot::slotPrimary || Hand == EQ::invslot::slotSecondary) + my_hit.base_damage = CastToClient()->DoDamageCaps(my_hit.base_damage); + auto shield_inc = spellbonuses.ShieldEquipDmgMod + itembonuses.ShieldEquipDmgMod + aabonuses.ShieldEquipDmgMod; + if (shield_inc > 0 && HasShieldEquiped() && Hand == EQ::invslot::slotPrimary) { + my_hit.base_damage = my_hit.base_damage * (100 + shield_inc) / 100; + hate = hate * (100 + shield_inc) / 100; } - int min_hit = 1; - int max_hit = 2;//(2*weapon_damage*GetDamageTable(skillinuse)) / 100; - - if(GetLevel() < 10 && max_hit > RuleI(Combat, HitCapPre10)) - max_hit = (RuleI(Combat, HitCapPre10)); - else if(GetLevel() < 20 && max_hit > RuleI(Combat, HitCapPre20)) - max_hit = (RuleI(Combat, HitCapPre20)); - - CheckIncreaseSkill(skillinuse, other, -15); - CheckIncreaseSkill(EQ::skills::SkillOffense, other, -15); - - -#ifndef EQEMU_NO_WEAPON_DAMAGE_BONUS + // *************************************************************** + // *** Calculate the damage bonus, if applicable, for this hit *** + // *************************************************************** int ucDamageBonus = 0; if (Hand == EQ::invslot::slotPrimary && GetLevel() >= 28 && IsWarriorClass()) { + // Damage bonuses apply only to hits from the main hand (Hand == MainPrimary) by characters level 28 and above + // who belong to a melee class. If we're here, then all of these conditions apply. + ucDamageBonus = GetWeaponDamageBonus(weapon ? weapon->GetItem() : (const EQ::ItemData*) nullptr); - min_hit += (int) ucDamageBonus; - max_hit += (int) ucDamageBonus; + my_hit.min_damage = ucDamageBonus; hate += ucDamageBonus; } -#endif - min_hit += min_hit * GetMeleeMinDamageMod_SE(skillinuse) / 100; - if(max_hit < min_hit) - max_hit = min_hit; + my_hit.offense = Tuneoffense(my_hit.skill, atk_override, add_atk); // we need this a few times + if (get_offense) { + return my_hit.offense; + } + my_hit.hand = Hand; - if (GetMinDamage) - return min_hit; + my_hit.tohit = TuneGetTotalToHit(my_hit.skill, hit_chance_bonus, accuracy_override, add_accuracy); + if (get_accuracy) { + return my_hit.tohit; + } + TuneDoAttack(other, my_hit, nullptr, no_avoid, no_hit_chance, ac_override, add_ac, avoidance_override, accuracy_override, add_avoidance, add_accuracy); + } + else { + my_hit.damage_done = DMG_INVULNERABLE; + } + + /////////////////////////////////////////////////////////// + ////// Send Attack Damage + /////////////////////////////////////////////////////////// + + //other->Damage(this, my_hit.damage_done, SPELL_UNKNOWN, my_hit.skill, true, -1, false, m_specialattacks); + + return my_hit.damage_done; +} + +void Mob::TuneDoAttack(Mob *other, DamageHitInfo &hit, ExtraAttackOptions *opts, bool no_avoid, bool no_hit_chance, int ac_override, int add_ac, + int avoidance_override, int accuracy_override, int add_avoidance, int add_accuracy) +{ + if (!other) + return; + LogCombat("[{}]::DoAttack vs [{}] base [{}] min [{}] offense [{}] tohit [{}] skill [{}]", GetName(), + other->GetName(), hit.base_damage, hit.min_damage, hit.offense, hit.tohit, hit.skill); + + // check to see if we hit.. + if (no_avoid || (!no_avoid && other->AvoidDamage(this, hit))) { + int strike_through = itembonuses.StrikeThrough + spellbonuses.StrikeThrough + aabonuses.StrikeThrough; + if (strike_through && zone->random.Roll(strike_through)) { + MessageString(Chat::StrikeThrough, + STRIKETHROUGH_STRING); // You strike through your opponents defenses! + hit.damage_done = 1; // set to one, we will check this to continue + } + // I'm pretty sure you can riposte a riposte + if (hit.damage_done == DMG_RIPOSTED) { + //DoRiposte(other); //Disabled for TUNE + //if (IsDead()) + return; + } + LogCombat("Avoided/strikethrough damage with code [{}]", hit.damage_done); + } + + if (hit.damage_done >= 0) { + if (no_hit_chance || (!no_hit_chance && other->TuneCheckHitChance(this, hit, avoidance_override, add_avoidance))) { + other->TuneMeleeMitigation(this, hit, ac_override, add_ac); + if (hit.damage_done > 0) { + ApplyDamageTable(hit); + CommonOutgoingHitSuccess(other, hit, opts); + } + LogCombat("Final damage after all reductions: [{}]", hit.damage_done); + } + else { + LogCombat("Attack missed. Damage set to 0"); + hit.damage_done = 0; + } + } +} + +void Mob::TuneMeleeMitigation(Mob *attacker, DamageHitInfo &hit, int ac_override, int add_ac) +{ + if (hit.damage_done < 0 || hit.base_damage == 0) + return; + + Mob* defender = this; + //auto mitigation = defender->GetMitigationAC(); + auto mitigation = defender->TuneACSum(false, ac_override, add_ac); + if (IsClient() && attacker->IsClient()) + mitigation = mitigation * 80 / 100; // 2004 PvP changes + + auto roll = RollD20(hit.offense, mitigation); + + // +0.5 for rounding, min to 1 dmg + hit.damage_done = std::max(static_cast(roll * static_cast(hit.base_damage) + 0.5), 1); +} + +int Mob::TuneACSum(bool skip_caps, int ac_override, int add_ac) +{ + int ac = 0; // this should be base AC whenever shrouds come around + ac += itembonuses.AC; // items + food + tribute + + if (IsClient()) { + ac = ac_override; + ac += add_ac; + } + + int shield_ac = 0; + if (HasShieldEquiped() && IsClient()) { + auto client = CastToClient(); + auto inst = client->GetInv().GetItem(EQ::invslot::slotSecondary); + if (inst) { + if (inst->GetItemRecommendedLevel(true) <= GetLevel()) + shield_ac = inst->GetItemArmorClass(true); + else + shield_ac = client->CalcRecommendedLevelBonus(GetLevel(), inst->GetItemRecommendedLevel(true), inst->GetItemArmorClass(true)); + } + shield_ac += client->GetHeroicSTR() / 10; + } + // EQ math + ac = (ac * 4) / 3; + // anti-twink + if (!skip_caps && IsClient() && GetLevel() < RuleI(Combat, LevelToStopACTwinkControl)) + ac = std::min(ac, 25 + 6 * GetLevel()); + ac = std::max(0, ac + GetClassRaceACBonus()); + if (IsNPC()) { + // This is the developer tweaked number + // for the VAST amount of NPCs in EQ this number didn't exceed 600 until recently (PoWar) + // According to the guild hall Combat Dummies, a level 50 classic EQ mob it should be ~115 + // For a 60 PoP mob ~120, 70 OoW ~120 + + + if (ac_override) { + ac += ac_override; + } + else { + ac += GetAC(); + } + ac += add_ac; + + ac += GetPetACBonusFromOwner(); + auto spell_aa_ac = aabonuses.AC + spellbonuses.AC; + ac += GetSkill(EQ::skills::SkillDefense) / 5; + if (EQ::ValueWithin(static_cast(GetClass()), NECROMANCER, ENCHANTER)) + ac += spell_aa_ac / 3; else - return max_hit; + ac += spell_aa_ac / 4; + } + else { // TODO: so we can't set NPC skills ... so the skill bonus ends up being HUGE so lets nerf them a bit + auto spell_aa_ac = aabonuses.AC + spellbonuses.AC; + if (EQ::ValueWithin(static_cast(GetClass()), NECROMANCER, ENCHANTER)) + ac += GetSkill(EQ::skills::SkillDefense) / 2 + spell_aa_ac / 3; + else + ac += GetSkill(EQ::skills::SkillDefense) / 3 + spell_aa_ac / 4; } - return 0; + if (GetAGI() > 70) + ac += GetAGI() / 20; + if (ac < 0) + ac = 0; + + if (!skip_caps && (IsClient())) { + auto softcap = GetACSoftcap(); + auto returns = GetSoftcapReturns(); + int total_aclimitmod = aabonuses.CombatStability + itembonuses.CombatStability + spellbonuses.CombatStability; + if (total_aclimitmod) + softcap = (softcap * (100 + total_aclimitmod)) / 100; + softcap += shield_ac; + if (ac > softcap) { + auto over_cap = ac - softcap; + ac = softcap + (over_cap * returns); + } + } + + return ac; } -void Mob::Tune_FindAccuaryByHitChance(Mob* defender, Mob *attacker, float hit_chance, int interval, int max_loop, int avoid_override, int Msg) +int Mob::Tuneoffense(EQ::skills::SkillType skill, int atk_override, int add_atk) { + int offense = GetSkill(skill); + int stat_bonus = GetSTR(); - int add_acc = 0; - float tmp_hit_chance = 0.0f; - bool end = false; + switch (skill) { + case EQ::skills::SkillArchery: + case EQ::skills::SkillThrowing: + stat_bonus = GetDEX(); + break; - EQ::skills::SkillType skillinuse = EQ::skills::SkillHandtoHand; - if (attacker->IsClient()) - {//Will check first equiped weapon for skill. Ie. remove wepaons to assess bow. - EQ::ItemInstance* weapon; - weapon = attacker->CastToClient()->GetInv().GetItem(EQ::invslot::slotPrimary); - - if(weapon && weapon->IsWeapon()){ - skillinuse = attacker->CastToClient()->AttackAnimation(EQ::invslot::slotPrimary, weapon); + // Mobs with no weapons default to H2H. + // Since H2H is capped at 100 for many many classes, + // lets not handicap mobs based on not spawning with a + // weapon. + // + // Maybe we tweak this if Disarm is actually implemented. + + case EQ::skills::SkillHandtoHand: + offense = GetBestMeleeSkill(); + break; + } + + if (stat_bonus >= 75) + offense += (2 * stat_bonus - 150) / 3; + + int32 tune_atk = GetATK(); + if (atk_override) { + tune_atk = atk_override; + } + + tune_atk += add_atk; + + offense += tune_atk + GetPetATKBonusFromOwner(); + return offense; +} + +EQ::skills::SkillType Mob::TuneAttackAnimation(int Hand, const EQ::ItemInstance* weapon, EQ::skills::SkillType skillinuse) +{ + // Determine animation + int type = 0; + if (weapon && weapon->IsClassCommon()) { + const EQ::ItemData* item = weapon->GetItem(); + + switch (item->ItemType) { + case EQ::item::ItemType1HSlash: // 1H Slashing + skillinuse = EQ::skills::Skill1HSlashing; + type = anim1HWeapon; + break; + case EQ::item::ItemType2HSlash: // 2H Slashing + skillinuse = EQ::skills::Skill2HSlashing; + type = anim2HSlashing; + break; + case EQ::item::ItemType1HPiercing: // Piercing + skillinuse = EQ::skills::Skill1HPiercing; + type = anim1HPiercing; + break; + case EQ::item::ItemType1HBlunt: // 1H Blunt + skillinuse = EQ::skills::Skill1HBlunt; + type = anim1HWeapon; + break; + case EQ::item::ItemType2HBlunt: // 2H Blunt + skillinuse = EQ::skills::Skill2HBlunt; + type = RuleB(Combat, Classic2HBAnimation) ? anim2HWeapon : anim2HSlashing; + break; + case EQ::item::ItemType2HPiercing: // 2H Piercing + if (IsClient() && CastToClient()->ClientVersion() < EQ::versions::ClientVersion::RoF2) + skillinuse = EQ::skills::Skill1HPiercing; + else + skillinuse = EQ::skills::Skill2HPiercing; + type = anim2HWeapon; + break; + case EQ::item::ItemTypeMartial: + skillinuse = EQ::skills::SkillHandtoHand; + type = animHand2Hand; + break; + default: + skillinuse = EQ::skills::SkillHandtoHand; + type = animHand2Hand; + break; + }// switch + } + else if (IsNPC()) { + switch (skillinuse) { + case EQ::skills::Skill1HSlashing: // 1H Slashing + type = anim1HWeapon; + break; + case EQ::skills::Skill2HSlashing: // 2H Slashing + type = anim2HSlashing; + break; + case EQ::skills::Skill1HPiercing: // Piercing + type = anim1HPiercing; + break; + case EQ::skills::Skill1HBlunt: // 1H Blunt + type = anim1HWeapon; + break; + case EQ::skills::Skill2HBlunt: // 2H Blunt + type = anim2HSlashing; //anim2HWeapon + break; + case EQ::skills::Skill2HPiercing: // 2H Piercing + type = anim2HWeapon; + break; + case EQ::skills::SkillHandtoHand: + type = animHand2Hand; + break; + default: + type = animHand2Hand; + break; + }// switch + } + else { + skillinuse = EQ::skills::SkillHandtoHand; + type = animHand2Hand; + } + + // If we're attacking with the secondary hand, play the dual wield anim + if (Hand == EQ::invslot::slotSecondary) // DW anim + type = animDualWield; + + return skillinuse; +} + +int Mob::Tunecompute_tohit(EQ::skills::SkillType skillinuse, int accuracy_override, int add_accuracy) +{ + int tohit = GetSkill(EQ::skills::SkillOffense) + 7; + tohit += GetSkill(skillinuse); + if (IsNPC()) { + if (accuracy_override) { + tohit += accuracy_override; } else { - weapon = attacker->CastToClient()->GetInv().GetItem(EQ::invslot::slotSecondary); - if (weapon && weapon->IsWeapon()) - skillinuse = attacker->CastToClient()->AttackAnimation(EQ::invslot::slotSecondary, weapon); - else { - weapon = attacker->CastToClient()->GetInv().GetItem(EQ::invslot::slotRange); - if (weapon && weapon->IsWeapon()) - skillinuse = attacker->CastToClient()->AttackAnimation(EQ::invslot::slotRange, weapon); - } + tohit += CastToNPC()->GetAccuracyRating(); + } + tohit += add_accuracy; + } + if (IsClient()) { + double reduction = CastToClient()->m_pp.intoxication / 2.0; + if (reduction > 20.0) { + reduction = std::min((110 - reduction) / 100.0, 1.0); + tohit = reduction * static_cast(tohit); + } + else if (IsBerserk()) { + tohit += (GetLevel() * 2) / 5; } } - - tmp_hit_chance = Tune_CheckHitChance(defender, attacker, skillinuse, EQ::invslot::slotPrimary, 0, 0, 0, avoid_override); - - - Message(0, "#Tune - Begin Parse [Interval %i Max Loop Iterations %i]", interval, max_loop); - Message(0, "#Tune - Processing... Find Accuracy for hit chance on attacker of (%.0f) pct on defender [Current Hit Chance %.2f]", hit_chance, tmp_hit_chance); - - - if (tmp_hit_chance > hit_chance) - interval = interval * -1; - - for (int j=0; j < max_loop; j++) - { - tmp_hit_chance = Tune_CheckHitChance(defender, attacker, skillinuse, EQ::invslot::slotPrimary, 0, false, 0, avoid_override, add_acc); - - if (Msg >= 3) - Message(Chat::Yellow, "#Tune - Processing... [%i] [ACCURACY %i] Hit Chance %.2f ",j,add_acc,tmp_hit_chance); - - if (interval > 0 && tmp_hit_chance >= hit_chance){ - end = true; - } - - else if (interval < 0 && tmp_hit_chance <= hit_chance){ - end = true; - } - - if (end){ - - Tune_CheckHitChance(defender, attacker, skillinuse, EQ::invslot::slotPrimary, 0, Msg, 0, avoid_override);//Display Stat Report - - Message(0, " "); - - if (attacker->IsNPC()){ - Message(0, "#Recommended NPC Accuracy Statistic adjustment of ( %i ) on ' %s ' for a hit chance of (+ %.0f) pct verse ' %s '. ",add_acc,defender->GetCleanName(), hit_chance, attacker->GetCleanName()); - Message(0, "#SET: [NPC Avoidance] = [%i]",add_acc + defender->CastToNPC()->GetAccuracyRating()); - } - else if (attacker->IsClient()){ - Message(0, "#Recommended Client Accuracy Bonus adjustment of ( %i ) on ' %s ' for a hit chance of (+ %.0f) pct verse ' %s '. ",add_acc,defender->GetCleanName(), hit_chance, attacker->GetCleanName()); - Message(0, "#Modify (+/-): [Item Mod2 Accuracy] [%i]",add_acc); - Message(0, "#Modify (+/-): [SE_Accuracy(216)] [%i]",add_acc); - Message(0, "#Modify (+/-): [SE_HitChance(184)] [%i]",add_acc / 15); - } - - return; - } - - - add_acc = add_acc + interval; - } - - Message(Chat::LightGray, "#Tune - Error: Unable to find desired result for (%.0f) pct - Increase interval (%i) AND/OR max loop value (%i) and run again.", hit_chance, interval, max_loop); - Message(Chat::LightGray, "#Tune - Parse ended at ACCURACY ADJUSTMENTT ( %i ) at hit chance of (%.0f) / (%.0f) pct.",add_acc,tmp_hit_chance / hit_chance); + return std::max(tohit, 1); } -void Mob::Tune_FindAvoidanceByHitChance(Mob* defender, Mob *attacker, float hit_chance, int interval, int max_loop, int acc_override, int Msg) +// return -1 in cases that always hit +int Mob::TuneGetTotalToHit(EQ::skills::SkillType skill, int chance_mod, int accuracy_override, int add_accuracy) { - int add_avoid = 0; - float tmp_hit_chance = 0.0f; - bool end = false; + if (chance_mod >= 10000) // override for stuff like SE_SkillAttack + return -1; - EQ::skills::SkillType skillinuse = EQ::skills::SkillHandtoHand; - if (attacker->IsClient()) - {//Will check first equiped weapon for skill. Ie. remove wepaons to assess bow. - EQ::ItemInstance* weapon; - weapon = attacker->CastToClient()->GetInv().GetItem(EQ::invslot::slotPrimary); - - if(weapon && weapon->IsWeapon()){ - skillinuse = attacker->CastToClient()->AttackAnimation(EQ::invslot::slotPrimary, weapon); + // calculate attacker's accuracy + auto accuracy = Tunecompute_tohit(skill, accuracy_override, add_accuracy) + 10; // add 10 in case the NPC's stats are fucked + if (chance_mod > 0) // multiplier + accuracy *= chance_mod; + + // Torven parsed an apparent constant of 1.2 somewhere in here * 6 / 5 looks eqmathy to me! + // new test clients have 121 / 100 + accuracy = (accuracy * 121) / 100; + + // unsure on the stacking order of these effects, rather hard to parse + // item mod2 accuracy isn't applied to range? Theory crafting and parses back it up I guess + // mod2 accuracy -- flat bonus + if (skill != EQ::skills::SkillArchery && skill != EQ::skills::SkillThrowing) + accuracy += itembonuses.HitChance; + + //518 Increase ATK accuracy by percentage, stackable + auto atkhit_bonus = itembonuses.Attack_Accuracy_Max_Percent + aabonuses.Attack_Accuracy_Max_Percent + spellbonuses.Attack_Accuracy_Max_Percent; + if (atkhit_bonus) + accuracy += round(static_cast(accuracy) * static_cast(atkhit_bonus) * 0.0001); + + // 216 Melee Accuracy Amt aka SE_Accuracy -- flat bonus + accuracy += itembonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1] + + aabonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1] + + spellbonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1] + + itembonuses.Accuracy[skill] + + aabonuses.Accuracy[skill] + + spellbonuses.Accuracy[skill]; + + if (IsClient()) { + if (accuracy_override) { + accuracy = accuracy_override; + } + accuracy += add_accuracy; + } + + // auto hit discs (and looks like there are some autohit AAs) + if (spellbonuses.HitChanceEffect[skill] >= 10000 || aabonuses.HitChanceEffect[skill] >= 10000) + return -1; + + if (spellbonuses.HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1] >= 10000) + return -1; + + // 184 Accuracy % aka SE_HitChance -- percentage increase + auto hit_bonus = itembonuses.HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1] + + aabonuses.HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1] + + spellbonuses.HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1] + + itembonuses.HitChanceEffect[skill] + + aabonuses.HitChanceEffect[skill] + + spellbonuses.HitChanceEffect[skill]; + + accuracy = (accuracy * (100 + hit_bonus)) / 100; + return accuracy; +} + +// return -1 in cases that always miss +int Mob::TuneGetTotalDefense(int avoidance_override, int add_avoidance) +{ + auto avoidance = Tunecompute_defense(avoidance_override, add_avoidance) + 10; // add 10 in case the NPC's stats are fucked + auto evasion_bonus = spellbonuses.AvoidMeleeChanceEffect; // we check this first since it has a special case + if (evasion_bonus >= 10000) + return -1; + + // 515 SE_AC_Avoidance_Max_Percent + auto ac_aviodance_bonus = itembonuses.AC_Avoidance_Max_Percent + aabonuses.AC_Avoidance_Max_Percent + spellbonuses.AC_Avoidance_Max_Percent; + if (ac_aviodance_bonus) + avoidance += round(static_cast(avoidance) * static_cast(ac_aviodance_bonus) * 0.0001); + + // 172 Evasion aka SE_AvoidMeleeChance + evasion_bonus += itembonuses.AvoidMeleeChanceEffect + aabonuses.AvoidMeleeChanceEffect; // item bonus here isn't mod2 avoidance + + // 215 Pet Avoidance % aka SE_PetAvoidance + evasion_bonus += GetPetAvoidanceBonusFromOwner(); + + // Evasion is a percentage bonus according to AA descriptions + if (evasion_bonus) + avoidance = (avoidance * (100 + evasion_bonus)) / 100; + + return avoidance; +} + +int Mob::Tunecompute_defense(int avoidance_override, int add_avoidance) +{ + int defense = GetSkill(EQ::skills::SkillDefense) * 400 / 225; + defense += (8000 * (GetAGI() - 40)) / 36000; + if (IsClient()) { + if (avoidance_override) { + defense = avoidance_override; } else { - weapon = attacker->CastToClient()->GetInv().GetItem(EQ::invslot::slotSecondary); - if (weapon && weapon->IsWeapon()) - skillinuse = attacker->CastToClient()->AttackAnimation(EQ::invslot::slotSecondary, weapon); - else { - weapon = attacker->CastToClient()->GetInv().GetItem(EQ::invslot::slotRange); - if (weapon && weapon->IsWeapon()) - skillinuse = attacker->CastToClient()->AttackAnimation(EQ::invslot::slotRange, weapon); - } + defense += CastToClient()->GetHeroicAGI() / 10; + } + defense += add_avoidance; //1 pt = 10 heroic agi + } + + //516 SE_AC_Mitigation_Max_Percent + auto ac_bonus = itembonuses.AC_Mitigation_Max_Percent + aabonuses.AC_Mitigation_Max_Percent + spellbonuses.AC_Mitigation_Max_Percent; + if (ac_bonus) + defense += round(static_cast(defense) * static_cast(ac_bonus) * 0.0001); + + defense += itembonuses.AvoidMeleeChance; // item mod2 + if (IsNPC()) { + if (avoidance_override) { + defense += avoidance_override; + } + else { + defense += CastToNPC()->GetAvoidanceRating(); + } + defense += add_avoidance; + } + + if (IsClient()) { + double reduction = CastToClient()->m_pp.intoxication / 2.0; + if (reduction > 20.0) { + reduction = std::min((110 - reduction) / 100.0, 1.0); + defense = reduction * static_cast(defense); } } - tmp_hit_chance = Tune_CheckHitChance(defender, attacker, skillinuse, EQ::invslot::slotPrimary, 0, 0, acc_override, 0); - - Message(0, "#Tune - Begin Parse [Interval %i Max Loop Iterations %i]", interval, max_loop); - Message(0, "#Tune - Processing... Find Avoidance for hit chance on defender of (%.0f) pct from attacker. [Current Hit Chance %.2f]", hit_chance, tmp_hit_chance); - - if (tmp_hit_chance < hit_chance) - interval = interval * -1; - - for (int j=0; j < max_loop; j++) - { - tmp_hit_chance = Tune_CheckHitChance(defender, attacker, skillinuse, EQ::invslot::slotPrimary, 0, 0, acc_override, 0, 0, add_avoid); - - if (Msg >= 3) - Message(0, "#Tune - Processing... [%i] [AVOIDANCE %i] Hit Chance %.2f ",j,add_avoid,tmp_hit_chance); - - if (interval > 0 && tmp_hit_chance <= hit_chance){ - end = true; - } - - else if (interval < 0 && tmp_hit_chance >= hit_chance){ - end = true; - } - - if (end){ - - Tune_CheckHitChance(defender, attacker, skillinuse, EQ::invslot::slotPrimary, 0, Msg, acc_override, 0);//Display Stat Report - - Message(0, " "); - - if (defender->IsNPC()){ - Message(0, "#Recommended NPC Avoidance Statistic adjustment of ( %i ) on ' %s ' for a hit chance of ( %.0f) pct from ' %s '. ",add_avoid,defender->GetCleanName(), hit_chance, attacker->GetCleanName()); - Message(0, "#SET: [NPC Avoidance] = [%i]",add_avoid + defender->CastToNPC()->GetAvoidanceRating()); - } - else if (defender->IsClient()){ - Message(0, "#Recommended Client Avoidance Bonus adjustment of ( %i ) on ' %s ' for a hit chance of ( %.0f) pct from ' %s '. ",add_avoid,defender->GetCleanName(), hit_chance, attacker->GetCleanName()); - Message(0, "#Modify (+/-): [Item Mod2 Avoidance] [%i]",add_avoid); - Message(0, "#Modify (+/-): [SE_AvoidMeleeChance(172)] [%i]",add_avoid / 10); - } - - return; - } - - add_avoid = add_avoid + interval; - } - - Message(0, "#Tune - Error: Unable to find desired result for (%.0f) pct - Increase interval (%i) AND/OR max loop value (%i) and run again.", hit_chance, interval, max_loop); - Message(0, "#Tune - Parse ended at AVOIDANCE ADJUSTMENT ( %i ) at hit chance of (%.0f) / (%.0f) pct.",add_avoid,tmp_hit_chance / hit_chance); + return std::max(1, defense); } - -float Mob::Tune_CheckHitChance(Mob* defender, Mob* attacker, EQ::skills::SkillType skillinuse, int Hand, int16 chance_mod, int Msg, int acc_override, int avoid_override, int add_acc, int add_avoid) +// called when a mob is attacked, does the checks to see if it's a hit +// and does other mitigation checks. 'this' is the mob being attacked. +bool Mob::TuneCheckHitChance(Mob* other, DamageHitInfo &hit, int avoidance_override, int add_avoidance) { - float chancetohit = RuleR(Combat, BaseHitChance); + Mob *attacker = other; + Mob *defender = this; - if(attacker->IsNPC() && !attacker->IsPet()) - chancetohit += RuleR(Combat, NPCBonusHitChance); - - if (Msg){ - - Message(0, "######### Hit Chance Report: Start [Detail Level %i]#########", Msg); - Message(0, "#ATTACKER: %s", attacker->GetCleanName()); - Message(0, "#DEFENDER: %s", defender->GetCleanName()); - if (Msg >= 2){ - Message(0, " "); - Message(0, "### Calculate Base Hit Chance ###"); - Message(0, "# + %.2f Total: %.2f #### RuleR(Combat, BaseHitChance)", RuleR(Combat, BaseHitChance), RuleR(Combat, BaseHitChance)); - if (attacker->IsNPC()) - Message(0, "# + %.2f Total: %.2f #### RuleR(Combat, NPCBonusHitChance)", RuleR(Combat, NPCBonusHitChance), chancetohit); - } - } - - float temp_chancetohit = chancetohit; - - bool pvpmode = false; - if(IsClient() && attacker->IsClient()) - pvpmode = true; - - if (chance_mod >= 10000) + if (defender->IsClient() && defender->CastToClient()->IsSitting()) return true; - float avoidanceBonus = 0; - float hitBonus = 0; + auto avoidance = defender->TuneGetTotalDefense(avoidance_override, add_avoidance); + if (avoidance == -1) // some sort of auto avoid disc + return false; - //////////////////////////////////////////////////////// - // To hit calcs go here - //////////////////////////////////////////////////////// + auto accuracy = hit.tohit; + if (accuracy == -1) + return true; - uint8 attacker_level = attacker->GetLevel() ? attacker->GetLevel() : 1; - uint8 defender_level = defender->GetLevel() ? defender->GetLevel() : 1; + // so now we roll! + // relevant dev quote: + // Then your chance to simply avoid the attack is checked (defender's avoidance roll beat the attacker's accuracy roll.) + int tohit_roll = zone->random.Roll0(accuracy); + int avoid_roll = zone->random.Roll0(avoidance); - //Calculate the level difference - - double level_difference = attacker_level - defender_level; - double range = defender->GetLevel(); - range = ((range / 4) + 3); - - if(level_difference < 0) - { - if(level_difference >= -range) - { - chancetohit += (level_difference / range) * RuleR(Combat,HitFalloffMinor); //5 - } - else if (level_difference >= -(range+3.0)) - { - chancetohit -= RuleR(Combat,HitFalloffMinor); - chancetohit += ((level_difference+range) / (3.0)) * RuleR(Combat,HitFalloffModerate); //7 - } - else - { - chancetohit -= (RuleR(Combat,HitFalloffMinor) + RuleR(Combat,HitFalloffModerate)); - chancetohit += ((level_difference+range+3.0)/12.0) * RuleR(Combat,HitFalloffMajor); //50 - } - } - else - { - chancetohit += (RuleR(Combat,HitBonusPerLevel) * level_difference); - } - - if (Msg >= 2) - Message(0, "# + %.2f Total: %.2f #### Level Modifers", chancetohit - temp_chancetohit, chancetohit); - - temp_chancetohit = chancetohit; - - chancetohit -= ((float)defender->GetAGI() * RuleR(Combat, AgiHitFactor)); - - if (Msg >= 2) - Message(0, "# - %.2f Total: %.2f #### DEFENDER Agility", ((float)defender->GetAGI() * RuleR(Combat, AgiHitFactor)), chancetohit); - - if(attacker->IsClient()) - { - chancetohit -= (RuleR(Combat,WeaponSkillFalloff) * (attacker->CastToClient()->MaxSkill(skillinuse) - attacker->GetSkill(skillinuse))); - if (Msg >= 2) - Message(0, "# - %.2f Total: %.2f ##### ATTACKER Wpn Skill Mod: ", (RuleR(Combat,WeaponSkillFalloff) * (attacker->CastToClient()->MaxSkill(skillinuse) - attacker->GetSkill(skillinuse))), chancetohit); - } - - if(defender->IsClient()) - { - chancetohit += (RuleR(Combat, WeaponSkillFalloff) * (defender->CastToClient()->MaxSkill(EQ::skills::SkillDefense) - defender->GetSkill(EQ::skills::SkillDefense))); - if (Msg >= 2) - Message(0, "# + %.2f Total: %.2f #### DEFENDER Defense Skill Mod", (RuleR(Combat, WeaponSkillFalloff) * (defender->CastToClient()->MaxSkill(EQ::skills::SkillDefense) - defender->GetSkill(EQ::skills::SkillDefense))), chancetohit); - } - - - //I dont think this is 100% correct, but at least it does something... - if(attacker->spellbonuses.MeleeSkillCheckSkill == skillinuse || attacker->spellbonuses.MeleeSkillCheckSkill == 255) { - chancetohit += attacker->spellbonuses.MeleeSkillCheck; - if (Msg >= 2) - Message(0, "# + %.2f Total: %.2f #### ATTACKER SE_MeleeSkillCheck(183) Spell Bonus", attacker->spellbonuses.MeleeSkillCheck , chancetohit); - } - if(attacker->itembonuses.MeleeSkillCheckSkill == skillinuse || attacker->itembonuses.MeleeSkillCheckSkill == 255) { - chancetohit += attacker->itembonuses.MeleeSkillCheck; - if (Msg >= 2) - Message(0, "# + %.2f Total: %.2f #### ATTACKER SE_MeleeSkillCheck(183) Worn Bonus", attacker->itembonuses.MeleeSkillCheck , chancetohit); - } - - if (Msg) - Message(0, "#FINAL Base Hit Chance: %.2f percent", chancetohit); - - if (Msg >= 2){ - Message(0, " "); - Message(0, "######### Calculate Avoidance Bonuses #########"); - } - - //Avoidance Bonuses on defender decreases baseline hit chance by percent. - avoidanceBonus = defender->spellbonuses.AvoidMeleeChanceEffect + - defender->itembonuses.AvoidMeleeChanceEffect + - defender->aabonuses.AvoidMeleeChanceEffect + - (defender->itembonuses.AvoidMeleeChance / 10.0f); //Item Mod 'Avoidence' - - if (Msg >= 2){ - if (defender->aabonuses.AvoidMeleeChanceEffect) - Message(0, "# %i #### DEFENDER SE_AvoidMeleeChance(172) AA Bonus", defender->aabonuses.AvoidMeleeChanceEffect); - if (defender->spellbonuses.AvoidMeleeChanceEffect) - Message(0, "# %i #### DEFENDER SE_AvoidMeleeChance(172) Spell Bonus", defender->spellbonuses.AvoidMeleeChanceEffect); - if (defender->itembonuses.AvoidMeleeChanceEffect) - Message(0, "# %i #### DEFENDER SE_AvoidMeleeChance(172) Worn Bonus", defender->itembonuses.AvoidMeleeChanceEffect); - if (defender->itembonuses.AvoidMeleeChance) - Message(0, "# %i #### DEFENDER Avoidance Item Mod2 Bonus[Amt: %i] ", defender->itembonuses.AvoidMeleeChance / 10.0f,defender->itembonuses.AvoidMeleeChance); - } - - - Mob *owner = nullptr; - if (defender->IsPet()) - owner = defender->GetOwner(); - else if ((defender->IsNPC() && defender->CastToNPC()->GetSwarmOwner())) - owner = entity_list.GetMobID(defender->CastToNPC()->GetSwarmOwner()); - - if (owner){ - avoidanceBonus += owner->aabonuses.PetAvoidance + owner->spellbonuses.PetAvoidance + owner->itembonuses.PetAvoidance; - - if (Msg >= 2){ - if (owner->aabonuses.PetAvoidance) - Message(0, "# %i #### DEFENDER SE_PetAvoidance(215) AA Bonus", owner->aabonuses.PetAvoidance); - if (owner->aabonuses.PetAvoidance) - Message(0, "# %i #### DEFENDER SE_PetAvoidance(215) Spell Bonus", owner->itembonuses.PetAvoidance); - if (owner->aabonuses.PetAvoidance) - Message(0, "# %i #### DEFENDER SE_PetAvoidance(215) Worn Bonus", owner->spellbonuses.PetAvoidance); - } - } - - if(defender->IsNPC()){ - avoidanceBonus += ((defender->CastToNPC()->GetAvoidanceRating() + add_avoid) / 10.0f); //Modifier from database - if (Msg >= 2) - Message(0, "# + %.2f #### DEFENDER NPC AVOIDANCE STAT [Stat Amt: %i] ", ((defender->CastToNPC()->GetAvoidanceRating() + add_avoid) / 10.0f),defender->CastToNPC()->GetAvoidanceRating()); - } - else if(defender->IsClient()){ - avoidanceBonus += (add_avoid / 10.0f); //Avoidance Item Mod - } - - //#tune override value - if (avoid_override){ - avoidanceBonus = (avoid_override / 10.0f); - if (Msg >= 2) - Message(0, "%.2f #### DEFENDER 'AVOIDANCE OVERRIDE'", avoidanceBonus); - } - - if (Msg) - Message(0, "#FINAL Avoidance Bonus': %.2f percent ", avoidanceBonus); - - if (Msg >= 2){ - Message(0, " "); - Message(0, "######### Calculate Accuracy Bonuses #########"); - } - - //Hit Chance Bonuses on attacker increases baseline hit chance by percent. - hitBonus += attacker->itembonuses.HitChanceEffect[skillinuse] + - attacker->spellbonuses.HitChanceEffect[skillinuse]+ - attacker->aabonuses.HitChanceEffect[skillinuse]+ - attacker->itembonuses.HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1] + - attacker->spellbonuses.HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1] + - attacker->aabonuses.HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1]; - - if (Msg >= 2){ - if (attacker->aabonuses.HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1]) - Message(0, "# %i #### ATTACKER SE_HitChance(184) AA Bonus [All Skills]", attacker->aabonuses.HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1]); - if (attacker->spellbonuses.HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1]) - Message(0, "# %i #### ATTACKER SE_HitChance(184) Spell Bonus [All Skills]", attacker->spellbonuses.HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1]); - if (attacker->itembonuses.HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1]) - Message(0, "# %i #### ATTACKER SE_HitChance(184) Worn Bonus [All Skills]", attacker->itembonuses.HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1]); - if (attacker->itembonuses.HitChanceEffect[skillinuse]) - Message(0, "# %i #### ATTACKER SE_HitChance(184) AA Bonus [Skill]", attacker->aabonuses.HitChanceEffect[skillinuse]); - if (attacker->spellbonuses.HitChanceEffect[skillinuse]) - Message(0, "# %i #### ATTACKER SE_HitChance(184) Spell Bonus [Skill]", attacker->spellbonuses.HitChanceEffect[skillinuse]); - if (attacker->itembonuses.HitChanceEffect[skillinuse]) - Message(0, "# %i #### ATTACKER SE_HitChance(184) Worn Bonus [Skill]", attacker->itembonuses.HitChanceEffect[skillinuse]); - } - - //Accuracy = Spell Effect , HitChance = 'Accuracy' from Item Effect - //Only AA derived accuracy can be skill limited. ie (Precision of the Pathfinder, Dead Aim) - hitBonus += (attacker->itembonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1] + - attacker->spellbonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1] + - attacker->aabonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1] + - attacker->aabonuses.Accuracy[skillinuse] + - attacker->itembonuses.HitChance) / 15.0f; //Item Mod 'Accuracy' - - if (Msg >= 2) { - if (attacker->aabonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1]) - Message(0, "# %.2f #### ATTACKER SE_Accuracy(216) AA Bonus [All Skills] [Stat Amt: %i]", static_cast(attacker->aabonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1]) / 15.0f, attacker->aabonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1]); - if (attacker->spellbonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1]) - Message(0, "# %.2f #### ATTACKER SE_Accuracy(216) Spell Bonus [All Skills] [Stat Amt: %i]", static_cast(attacker->spellbonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1]) / 15.0f, attacker->spellbonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1]); - if (attacker->itembonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1]) - Message(0, "# %.2f #### ATTACKER SE_Accuracy(216) Worn Bonus [All Skills] [Stat Amt: %i]", static_cast(attacker->itembonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1]) / 15.0f, attacker->itembonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1]); - if (attacker->aabonuses.Accuracy[skillinuse]) - Message(0, "# %.2f #### ATTACKER SE_Accuracy(216) AA Bonus [Skill] [Stat Amt: %i]", static_cast(attacker->aabonuses.Accuracy[skillinuse])/15.0f,attacker->aabonuses.Accuracy[skillinuse]); - if (attacker->itembonuses.HitChance) - Message(0, "# %.2f #### ATTACKER Accuracy Item Mod2 Bonus [Stat Amt: %i]", static_cast(attacker->itembonuses.HitChance)/15.0f,attacker->itembonuses.HitChance); - } - - hitBonus += chance_mod; //Modifier applied from casted/disc skill attacks. - - if(attacker->IsNPC()){ - if (acc_override){ - hitBonus = (acc_override / 10.0f); - if (Msg >= 2) - Message(0, "# %.2f #### ATTACKER 'ACCURACY OVERRIDE'", hitBonus); - } - else { - hitBonus += ((attacker->CastToNPC()->GetAccuracyRating() + add_acc) / 10.0f); //Modifier from database - if (Msg >= 2){ - Message(0, "# %.2f #### ATTACKER NPC ACCURACY STAT [Stat Amt: %i] ", ((attacker->CastToNPC()->GetAccuracyRating() + add_avoid) / 10.0f),attacker->CastToNPC()->GetAccuracyRating()); - } - } - } - else if(attacker->IsClient()){ - if (acc_override){ - hitBonus = (acc_override / 15.0f); - if (Msg >= 2) - Message(0, "# %.2f #### ATTACKER 'ACCURACY OVERRIDE': %.2f "); - } - else - hitBonus += (add_acc / 15.0f); //Modifier from database - } - - if (skillinuse == EQ::skills::SkillArchery){ - hitBonus -= hitBonus*RuleR(Combat, ArcheryHitPenalty); - if (Msg >= 2) - Message(0, "# %.2f pct #### RuleR(Combat, ArcheryHitPenalty) ", RuleR(Combat, ArcheryHitPenalty)); - } - - //Calculate final chance to hit - chancetohit += ((chancetohit * (hitBonus - avoidanceBonus)) / 100.0f); - - if (Msg){ - Message(0, "#FINAL Accuracy Bonus': %.2f percent", hitBonus); - - if (Msg >= 2) - Message(0, " "); - - Message(0, "#FINAL Hit Chance: %.2f percent [Max: %.2f Min: %.2f] ", chancetohit, RuleR(Combat,MaxChancetoHit), RuleR(Combat,MinChancetoHit) ); - Message(0, "######### Hit Chance Report: Completed #########"); - } - - chancetohit = mod_hit_chance(chancetohit, skillinuse, attacker); - - // Chance to hit; Max 95%, Min 5% DEFAULTS - if(chancetohit > 1000 || chancetohit < -1000) { - //if chance to hit is crazy high, that means a discipline is in use, and let it stay there - } - else if(chancetohit > RuleR(Combat,MaxChancetoHit)) { - chancetohit = RuleR(Combat,MaxChancetoHit); - } - else if(chancetohit < RuleR(Combat,MinChancetoHit)) { - chancetohit = RuleR(Combat,MinChancetoHit); - } - - return(chancetohit); + // tie breaker? Don't want to be biased any one way + if (tohit_roll == avoid_roll) + return zone->random.Roll(50); + return tohit_roll > avoid_roll; } diff --git a/zone/waypoints.cpp b/zone/waypoints.cpp index 8ed940194..f0c1fc1ca 100644 --- a/zone/waypoints.cpp +++ b/zone/waypoints.cpp @@ -76,26 +76,53 @@ void NPC::AI_SetRoambox( roambox_min_delay = min_delay; } -void NPC::DisplayWaypointInfo(Client *c) { +void NPC::DisplayWaypointInfo(Client *client) { + client->Message( + Chat::White, + fmt::format( + "Waypoint Info for {} ({}) | Grid: {} Waypoint: {} of {}", + GetCleanName(), + GetID(), + GetGrid(), + GetCurWp(), + GetMaxWp() + ).c_str() + ); - c->Message(Chat::White, "Mob is on grid %d, in spawn group %d, on waypoint %d/%d", - GetGrid(), - GetSpawnGroupId(), - GetCurWp(), - GetMaxWp()); + client->Message( + Chat::White, + fmt::format( + "Waypoint Info for {} ({}) | Spawn Group: {} Spawn Point: {}", + GetCleanName(), + GetID(), + GetSpawnGroupId(), + GetSpawnPointID() + ).c_str() + ); - - std::vector::iterator cur, end; - cur = Waypoints.begin(); - end = Waypoints.end(); - for (; cur != end; ++cur) { - c->Message(Chat::White, "Waypoint %d: (%.2f,%.2f,%.2f,%.2f) pause %d", - cur->index, - cur->x, - cur->y, - cur->z, - cur->heading, - cur->pause); + + for (const auto& current_waypoint : Waypoints) { + client->Message( + Chat::White, + fmt::format( + "Waypoint {}{} | XYZ: {:.2f}, {:.2f}, {:.2f} Heading: {:.2f}{}", + current_waypoint.index, + current_waypoint.centerpoint ? " (Center)" : "", + current_waypoint.x, + current_waypoint.y, + current_waypoint.z, + current_waypoint.heading, + ( + current_waypoint.pause ? + fmt::format( + "{} ({})", + ConvertSecondsToTime(current_waypoint.pause), + current_waypoint.pause + ) : + "" + ) + ).c_str() + ); } } @@ -138,11 +165,10 @@ void NPC::ResumeWandering() if (m_CurrentWayPoint.x == GetX() && m_CurrentWayPoint.y == GetY()) { // are we we at a waypoint? if so, trigger event and start to next - char temp[100]; - itoa(cur_wp, temp, 10); //do this before updating to next waypoint + std::string buf = fmt::format("{}", cur_wp); CalculateNewWaypoint(); SetAppearance(eaStanding, false); - parse->EventNPC(EVENT_WAYPOINT_DEPART, this, nullptr, temp, 0); + parse->EventNPC(EVENT_WAYPOINT_DEPART, this, nullptr, buf.c_str(), 0); } // if not currently at a waypoint, we continue on to the one we were headed to before the stop } else @@ -932,9 +958,31 @@ void Mob::TryMoveAlong(float distance, float angle, bool send) } new_pos.z = GetFixedZ(new_pos); + Teleport(new_pos); } +// like above, but takes a starting position and returns a new location instead of actually moving +glm::vec4 Mob::TryMoveAlong(const glm::vec4 &start, float distance, float angle) +{ + angle += start.w; + angle = FixHeading(angle); + + glm::vec3 tmp_pos; + glm::vec3 new_pos = start; + new_pos.x += distance * g_Math.FastSin(angle); + new_pos.y += distance * g_Math.FastCos(angle); + + if (zone->HasMap()) { + if (zone->zonemap->LineIntersectsZone(start, new_pos, 0.0f, &tmp_pos)) + new_pos = tmp_pos; + } + + new_pos.z = GetFixedZ(new_pos); + + return {new_pos.x, new_pos.y, new_pos.z, start.w}; +} + int ZoneDatabase::GetHighestGrid(uint32 zoneid) { std::string query = StringFormat("SELECT COALESCE(MAX(id), 0) FROM grid WHERE zoneid = %i", zoneid); @@ -1029,13 +1077,31 @@ void ZoneDatabase::ModifyGrid(Client *client, bool remove, uint32 id, uint8 type return; } - std::string query = StringFormat("DELETE FROM grid where id=%i", id); + std::string query = StringFormat("DELETE FROM grid where id=%i and zoneid=%i", id, zoneid); auto results = QueryDatabase(query); query = StringFormat("DELETE FROM grid_entries WHERE zoneid = %i AND gridid = %i", zoneid, id); results = QueryDatabase(query); } +bool ZoneDatabase::GridExistsInZone(uint32 zone_id, uint32 grid_id) { + bool grid_exists = false; + std::string query = fmt::format( + "SELECT * FROM `grid` WHERE `id` = {} AND `zoneid` = {}", + grid_id, + zone_id + ); + auto results = QueryDatabase(query); + if (!results.Success()) { + return grid_exists; + } + + if (results.RowCount() == 1) { + grid_exists = true; + } + return grid_exists; +} + /************************************** * AddWP - Adds a new waypoint to a specific grid for a specific zone. */ diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index a838a75cb..e478f62f4 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -54,7 +54,9 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "zone.h" #include "zone_config.h" #include "zone_reload.h" - +#include "../common/shared_tasks.h" +#include "shared_task_zone_messaging.h" +#include "dialogue_window.h" extern EntityList entity_list; extern Zone* zone; @@ -87,7 +89,7 @@ void WorldServer::Connect() m_connection->OnMessage(std::bind(&WorldServer::HandleMessage, this, std::placeholders::_1, std::placeholders::_2)); - m_keepalive = std::make_unique(2500, true, std::bind(&WorldServer::OnKeepAlive, this, std::placeholders::_1)); + m_keepalive = std::make_unique(1000, true, std::bind(&WorldServer::OnKeepAlive, this, std::placeholders::_1)); } bool WorldServer::SendPacket(ServerPacket *pack) @@ -159,13 +161,22 @@ void WorldServer::OnConnected() { safe_delete(pack); if (is_zone_loaded) { - this->SetZoneData(zone->GetZoneID(), zone->GetInstanceID()); + SetZoneData(zone->GetZoneID(), zone->GetInstanceID()); entity_list.UpdateWho(true); - this->SendEmoteMessage(0, 0, 15, "Zone connect: %s", zone->GetLongName()); + SendEmoteMessage( + 0, + 0, + Chat::Yellow, + fmt::format( + "Zone Connected | {} ({})", + zone->GetLongName(), + zone->GetZoneID() + ).c_str() + ); zone->GetTimeSync(); } else { - this->SetZoneData(0); + SetZoneData(0); } pack = new ServerPacket(ServerOP_LSZoneBoot, sizeof(ZoneBoot_Struct)); @@ -447,7 +458,12 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) ServerEmoteMessage_Struct* sem = (ServerEmoteMessage_Struct*)pack->pBuffer; if (sem->to[0] != 0) { if (strcasecmp(sem->to, zone->GetShortName()) == 0) - entity_list.MessageStatus(sem->guilddbid, sem->minstatus, sem->type, (char*)sem->message); + entity_list.MessageStatus( + sem->guilddbid, + sem->minstatus, + sem->type, + (char*)sem->message + ); else { Client* client = entity_list.GetClientByName(sem->to); if (client) { @@ -463,11 +479,22 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) } else { char* newmessage = 0; - if (strstr(sem->message, "^") == 0) - entity_list.MessageStatus(sem->guilddbid, sem->minstatus, sem->type, sem->message); - else { - for (newmessage = strtok((char*)sem->message, "^"); newmessage != nullptr; newmessage = strtok(nullptr, "^")) - entity_list.MessageStatus(sem->guilddbid, sem->minstatus, sem->type, newmessage); + if (strstr(sem->message, "^") == 0) { + entity_list.MessageStatus( + sem->guilddbid, + sem->minstatus, + sem->type, + sem->message + ); + } else { + for (newmessage = strtok((char*)sem->message, "^"); newmessage != nullptr; newmessage = strtok(nullptr, "^")) { + entity_list.MessageStatus( + sem->guilddbid, + sem->minstatus, + sem->type, + newmessage + ); + } } } break; @@ -500,7 +527,16 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) SetZoneData(0); } else { - SendEmoteMessage(0, 0, 15, "Zone shutdown: %s", zone->GetLongName()); + SendEmoteMessage( + 0, + 0, + Chat::Yellow, + fmt::format( + "Zone Shutdown | {} ({})", + zone->GetLongName(), + zone->GetZoneID() + ).c_str() + ); ServerZoneStateChange_struct* zst = (ServerZoneStateChange_struct *)pack->pBuffer; std::cout << "Zone shutdown by " << zst->adminname << std::endl; @@ -521,7 +557,16 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) zone->StartShutdownTimer(AUTHENTICATION_TIMEOUT * 1000); } else { - SendEmoteMessage(zst->adminname, 0, 0, "Zone bootup failed: Already running '%s'", zone->GetShortName()); + SendEmoteMessage( + zst->adminname, + 0, + Chat::White, + fmt::format( + "Zone Bootup Failed | {} ({}) Already running", + zone->GetLongName(), + zone->GetZoneID() + ).c_str() + ); } break; } @@ -588,7 +633,19 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) else if (client->GetAnon() == 1 && client->Admin() > szp->adminrank) break; else { - SendEmoteMessage(szp->adminname, 0, 0, "Summoning %s to %s %1.1f, %1.1f, %1.1f", szp->name, szp->zone, szp->x_pos, szp->y_pos, szp->z_pos); + SendEmoteMessage( + szp->adminname, + 0, + Chat::White, + fmt::format( + "Summoning {} to {} at {:.2f}, {:.2f}, {:.2f}", + szp->name, + szp->zone, + szp->x_pos, + szp->y_pos, + szp->z_pos + ).c_str() + ); } if (!szp->instance_id) { client->MovePC(ZoneID(szp->zone), szp->instance_id, szp->x_pos, szp->y_pos, szp->z_pos, client->GetHeading(), szp->ignorerestrictions, GMSummon); @@ -613,13 +670,33 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) if (client) { if (skp->adminrank >= client->Admin()) { client->WorldKick(); - if (is_zone_loaded) - SendEmoteMessage(skp->adminname, 0, 0, "Remote Kick: %s booted in zone %s.", skp->name, zone->GetShortName()); - else - SendEmoteMessage(skp->adminname, 0, 0, "Remote Kick: %s booted.", skp->name); + SendEmoteMessage( + skp->adminname, + 0, + Chat::White, + fmt::format( + "Remote Kick | {} booted{}", + skp->name, + is_zone_loaded ? + fmt::format( + "in {} ({})", + zone->GetLongName(), + zone->GetZoneID() + ) : + "" + ).c_str() + ); + } else if (client->GetAnon() != 1) { + SendEmoteMessage( + skp->adminname, + 0, + Chat::White, + fmt::format( + "Remote Kick | Your Status Level is not high enough to kick {}.", + skp->name + ).c_str() + ); } - else if (client->GetAnon() != 1) - SendEmoteMessage(skp->adminname, 0, 0, "Remote Kick: Your avatar level is not high enough to kick %s", skp->name); } break; } @@ -629,13 +706,33 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) if (client) { if (skp->admin >= client->Admin()) { client->GMKill(); - if (is_zone_loaded) - SendEmoteMessage(skp->gmname, 0, 0, "Remote Kill: %s killed in zone %s.", skp->target, zone->GetShortName()); - else - SendEmoteMessage(skp->gmname, 0, 0, "Remote Kill: %s killed.", skp->target); + SendEmoteMessage( + skp->gmname, + 0, + Chat::White, + fmt::format( + "Remote Kill | {} killed{}", + skp->target, + is_zone_loaded ? + fmt::format( + "in {} ({})", + zone->GetLongName(), + zone->GetZoneID() + ) : + "" + ).c_str() + ); + } else if (client->GetAnon() != 1) { + SendEmoteMessage( + skp->gmname, + 0, + Chat::White, + fmt::format( + "Remote Kill | Your Status Level is not high enough to kill {}", + skp->target + ).c_str() + ); } - else if (client->GetAnon() != 1) - SendEmoteMessage(skp->gmname, 0, 0, "Remote Kill: Your avatar level is not high enough to kill %s", skp->target); } break; } @@ -672,7 +769,20 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) ServerGMGoto_Struct* gmg = (ServerGMGoto_Struct*)pack->pBuffer; Client* client = entity_list.GetClientByName(gmg->gotoname); if (client) { - SendEmoteMessage(gmg->myname, 0, 13, "Summoning you to: %s @ %s, %1.1f, %1.1f, %1.1f", client->GetName(), zone->GetShortName(), client->GetX(), client->GetY(), client->GetZ()); + SendEmoteMessage( + gmg->myname, + 0, + Chat::Red, + fmt::format( + "Summoning you to {} ({}) in {} at {:.2f}, {:.2f}, {:.2f}", + client->GetCleanName(), + zone->GetLongName(), + zone->GetZoneID(), + client->GetX(), + client->GetY(), + client->GetZ() + ).c_str() + ); auto outpack = new ServerPacket(ServerOP_ZonePlayer, sizeof(ServerZonePlayer_Struct)); ServerZonePlayer_Struct* szp = (ServerZonePlayer_Struct*)outpack->pBuffer; strcpy(szp->adminname, gmg->myname); @@ -686,7 +796,15 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) safe_delete(outpack); } else { - SendEmoteMessage(gmg->myname, 0, 13, "Error: %s not found", gmg->gotoname); + SendEmoteMessage( + gmg->myname, + 0, + Chat::Red, + fmt::format( + "Error: {} not found.", + gmg->gotoname + ).c_str() + ); } break; } @@ -708,19 +826,17 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) } ServerUptime_Struct* sus = (ServerUptime_Struct*)pack->pBuffer; uint32 ms = Timer::GetCurrentTime(); - uint32 d = ms / 86400000; - ms -= d * 86400000; - uint32 h = ms / 3600000; - ms -= h * 3600000; - uint32 m = ms / 60000; - ms -= m * 60000; - uint32 s = ms / 1000; - if (d) - this->SendEmoteMessage(sus->adminname, 0, 0, "Zone #%i Uptime: %02id %02ih %02im %02is", sus->zoneserverid, d, h, m, s); - else if (h) - this->SendEmoteMessage(sus->adminname, 0, 0, "Zone #%i Uptime: %02ih %02im %02is", sus->zoneserverid, h, m, s); - else - this->SendEmoteMessage(sus->adminname, 0, 0, "Zone #%i Uptime: %02im %02is", sus->zoneserverid, m, s); + std::string time_string = ConvertMillisecondsToTime(ms); + SendEmoteMessage( + sus->adminname, + 0, + Chat::White, + fmt::format( + "Zoneserver {} | Uptime: {}", + sus->zoneserverid, + time_string + ).c_str() + ); } case ServerOP_Petition: { std::cout << "Got Server Requested Petition List Refresh" << std::endl; @@ -854,9 +970,19 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) case ServerOP_Revoke: { RevokeStruct* rev = (RevokeStruct*)pack->pBuffer; Client* client = entity_list.GetClientByName(rev->name); - if (client) - { - SendEmoteMessage(rev->adminname, 0, 0, "%s: %srevoking %s", zone->GetShortName(), rev->toggle ? "" : "un", client->GetName()); + if (client) { + SendEmoteMessage( + rev->adminname, + 0, + Chat::White, + fmt::format( + "Zone {} ({}) | {} {}.", + zone->GetLongName(), + zone->GetZoneID(), + rev->toggle ? "Revoking" : "Unrevoking", + client->GetCleanName() + ).c_str() + ); client->SetRevoked(rev->toggle); } break; @@ -1824,17 +1950,63 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) break; } case ServerOP_ReloadRules: { - worldserver.SendEmoteMessage( - 0, 0, 100, 15, - "Rules reloaded for Zone: '%s' Instance ID: %u", - (zone ? zone->GetLongName() : StringFormat("Null zone pointer [pid]:[%i]", getpid()).c_str()), - (zone ? zone->GetInstanceID() : 0xFFFFFFFFF) - ); + if (zone) { + worldserver.SendEmoteMessage( + 0, + 0, + AccountStatus::GMAdmin, + Chat::Yellow, + fmt::format( + "Rules reloaded for {}.", + fmt::format( + "{} ({})", + zone->GetLongName(), + zone->GetZoneID() + ), + ( + zone->GetInstanceID() ? + fmt::format( + "Instance ID: {}", + zone->GetInstanceID() + ) : + "" + ) + ).c_str() + ); + } RuleManager::Instance()->LoadRules(&database, RuleManager::Instance()->GetActiveRuleset(), true); break; } + case ServerOP_ReloadContentFlags: { + if (zone) { + worldserver.SendEmoteMessage( + 0, + 0, + AccountStatus::GMAdmin, + Chat::Yellow, + fmt::format( + "Content flags (and expansion) reloaded for {}.", + fmt::format( + "{} ({})", + zone->GetLongName(), + zone->GetZoneID() + ), + ( + zone->GetInstanceID() ? + fmt::format( + "Instance ID: {}", + zone->GetInstanceID() + ) : + "" + ) + ).c_str() + ); + } + content_service.SetExpansionContext()->ReloadContentFlags(); + break; + } case ServerOP_ReloadLogs: { - database.LoadLogSettings(LogSys.log_settings); + LogSys.LoadLogDatabaseSettings(); break; } case ServerOP_ReloadPerlExportSettings: { @@ -1886,921 +2058,1033 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) } break; } - case ServerOP_CZCastSpellPlayer: + case ServerOP_CZDialogueWindow: { - CZCastSpellPlayer_Struct* CZSC = (CZCastSpellPlayer_Struct*) pack->pBuffer; - Client* client = entity_list.GetClientByCharID(CZSC->character_id); - if (client) { - client->SpellFinished(CZSC->spell_id, client); + CZDialogueWindow_Struct* CZDW = (CZDialogueWindow_Struct*) pack->pBuffer; + uint8 update_type = CZDW->update_type; + int update_identifier = CZDW->update_identifier; + std::string message = CZDW->message; + const char* client_name = CZDW->client_name; + if (update_type == CZUpdateType_Character) { + auto client = entity_list.GetClientByCharID(update_identifier); + if (client) { + DialogueWindow::Render(client, message); + } + } else if (update_type == CZUpdateType_Group) { + auto client_group = entity_list.GetGroupByID(update_identifier); + if (client_group) { + for (int member_index = 0; member_index < MAX_GROUP_MEMBERS; member_index++) { + if (client_group->members[member_index] && client_group->members[member_index]->IsClient()) { + auto group_member = client_group->members[member_index]->CastToClient(); + DialogueWindow::Render(group_member, message); + } + } + } + } else if (update_type == CZUpdateType_Raid) { + auto client_raid = entity_list.GetRaidByID(update_identifier); + if (client_raid) { + for (int member_index = 0; member_index < MAX_RAID_MEMBERS; member_index++) { + if (client_raid->members[member_index].member && client_raid->members[member_index].member->IsClient()) { + auto raid_member = client_raid->members[member_index].member->CastToClient(); + DialogueWindow::Render(raid_member, message); + } + } + } + } else if (update_type == CZUpdateType_Guild) { + for (auto &client: entity_list.GetClientList()) { + if (client.second->GuildID() > 0 && client.second->GuildID() == update_identifier) { + DialogueWindow::Render(client.second, message); + } + } + } else if (update_type == CZUpdateType_Expedition) { + for (auto &client: entity_list.GetClientList()) { + if (client.second->GetExpedition() && client.second->GetExpedition()->GetID() == update_identifier) { + DialogueWindow::Render(client.second, message); + } + } + } else if (update_type == CZUpdateType_ClientName) { + auto client = entity_list.GetClientByName(client_name); + if (client) { + DialogueWindow::Render(client, message); + } } break; } - case ServerOP_CZCastSpellGroup: + case ServerOP_CZLDoNUpdate: { - CZCastSpellGroup_Struct* CZSC = (CZCastSpellGroup_Struct*) pack->pBuffer; - auto client_group = entity_list.GetGroupByID(CZSC->group_id); - if (client_group) { - for (int member_index = 0; member_index < MAX_GROUP_MEMBERS; member_index++) { - if (client_group->members[member_index] && client_group->members[member_index]->IsClient()) { - auto group_member = client_group->members[member_index]->CastToClient(); - group_member->SpellFinished(CZSC->spell_id, group_member); + CZLDoNUpdate_Struct* CZLU = (CZLDoNUpdate_Struct*) pack->pBuffer; + uint8 update_type = CZLU->update_type; + uint8 update_subtype = CZLU->update_subtype; + int update_identifier = CZLU->update_identifier; + uint32 theme_id = CZLU->theme_id; + int points = CZLU->points; + const char* client_name = CZLU->client_name; + if (update_type == CZUpdateType_Character) { + auto client = entity_list.GetClientByCharID(update_identifier); + if (client) { + switch (update_subtype) { + case CZLDoNUpdateSubtype_AddLoss: + client->UpdateLDoNWinLoss(theme_id, false); + break; + case CZLDoNUpdateSubtype_AddPoints: + client->UpdateLDoNPoints(theme_id, points); + break; + case CZLDoNUpdateSubtype_AddWin: + client->UpdateLDoNWinLoss(theme_id, true); + break; + case CZLDoNUpdateSubtype_RemoveLoss: + client->UpdateLDoNWinLoss(theme_id, false, true); + break; + case CZLDoNUpdateSubtype_RemoveWin: + client->UpdateLDoNWinLoss(theme_id, true, true); + break; + default: + break; + } + } + break; + } else if (update_type == CZUpdateType_Group) { + auto client_group = entity_list.GetGroupByID(update_identifier); + if (client_group) { + for (int member_index = 0; member_index < MAX_GROUP_MEMBERS; member_index++) { + if (client_group->members[member_index] && client_group->members[member_index]->IsClient()) { + auto client_group_member = client_group->members[member_index]->CastToClient(); + switch (update_subtype) { + case CZLDoNUpdateSubtype_AddLoss: + client_group_member->UpdateLDoNWinLoss(theme_id, false); + break; + case CZLDoNUpdateSubtype_AddPoints: + client_group_member->UpdateLDoNPoints(theme_id, points); + break; + case CZLDoNUpdateSubtype_AddWin: + client_group_member->UpdateLDoNWinLoss(theme_id, true); + break; + case CZLDoNUpdateSubtype_RemoveLoss: + client_group_member->UpdateLDoNWinLoss(theme_id, false, true); + break; + case CZLDoNUpdateSubtype_RemoveWin: + client_group_member->UpdateLDoNWinLoss(theme_id, true, true); + break; + default: + break; + } + } + } + } + } else if (update_type == CZUpdateType_Raid) { + auto client_raid = entity_list.GetRaidByID(update_identifier); + if (client_raid) { + for (int member_index = 0; member_index < MAX_RAID_MEMBERS; member_index++) { + auto client_raid_member = client_raid->members[member_index].member; + if (client_raid_member && client_raid_member->IsClient()) { + switch (update_subtype) { + case CZLDoNUpdateSubtype_AddLoss: + client_raid_member->UpdateLDoNWinLoss(theme_id, false); + break; + case CZLDoNUpdateSubtype_AddPoints: + client_raid_member->UpdateLDoNPoints(theme_id, points); + break; + case CZLDoNUpdateSubtype_AddWin: + client_raid_member->UpdateLDoNWinLoss(theme_id, true); + break; + case CZLDoNUpdateSubtype_RemoveLoss: + client_raid_member->UpdateLDoNWinLoss(theme_id, false, true); + break; + case CZLDoNUpdateSubtype_RemoveWin: + client_raid_member->UpdateLDoNWinLoss(theme_id, true, true); + break; + default: + break; + } + } + } + } + } else if (update_type == CZUpdateType_Guild) { + for (auto &client : entity_list.GetClientList()) { + if (client.second->GuildID() > 0 && client.second->GuildID() == update_identifier) { + switch (update_subtype) { + case CZLDoNUpdateSubtype_AddLoss: + client.second->UpdateLDoNWinLoss(theme_id, false); + break; + case CZLDoNUpdateSubtype_AddPoints: + client.second->UpdateLDoNPoints(theme_id, points); + break; + case CZLDoNUpdateSubtype_AddWin: + client.second->UpdateLDoNWinLoss(theme_id, true); + break; + case CZLDoNUpdateSubtype_RemoveLoss: + client.second->UpdateLDoNWinLoss(theme_id, false, true); + break; + case CZLDoNUpdateSubtype_RemoveWin: + client.second->UpdateLDoNWinLoss(theme_id, true, true); + break; + default: + break; + } + } + } + } else if (update_type == CZUpdateType_Expedition) { + for (auto &client : entity_list.GetClientList()) { + if (client.second->GetExpedition() && client.second->GetExpedition()->GetID() == update_identifier) { + switch (update_subtype) { + case CZLDoNUpdateSubtype_AddLoss: + client.second->UpdateLDoNWinLoss(theme_id, false); + break; + case CZLDoNUpdateSubtype_AddPoints: + client.second->UpdateLDoNPoints(theme_id, points); + break; + case CZLDoNUpdateSubtype_AddWin: + client.second->UpdateLDoNWinLoss(theme_id, true); + break; + case CZLDoNUpdateSubtype_RemoveLoss: + client.second->UpdateLDoNWinLoss(theme_id, false, true); + break; + case CZLDoNUpdateSubtype_RemoveWin: + client.second->UpdateLDoNWinLoss(theme_id, true, true); + break; + default: + break; + } + } + } + } else if (update_type == CZUpdateType_ClientName) { + auto client = entity_list.GetClientByName(client_name); + if (client) { + switch (update_subtype) { + case CZLDoNUpdateSubtype_AddLoss: + client->UpdateLDoNWinLoss(theme_id, false); + break; + case CZLDoNUpdateSubtype_AddPoints: + client->UpdateLDoNPoints(theme_id, points); + break; + case CZLDoNUpdateSubtype_AddWin: + client->UpdateLDoNWinLoss(theme_id, true); + break; + case CZLDoNUpdateSubtype_RemoveLoss: + client->UpdateLDoNWinLoss(theme_id, false, true); + break; + case CZLDoNUpdateSubtype_RemoveWin: + client->UpdateLDoNWinLoss(theme_id, true, true); + break; + default: + break; + } + } + break; + } + break; + } + case ServerOP_CZMarquee: + { + CZMarquee_Struct* CZM = (CZMarquee_Struct*) pack->pBuffer; + uint8 update_type = CZM->update_type; + int update_identifier = CZM->update_identifier; + uint32 type = CZM->type; + uint32 priority = CZM->priority; + uint32 fade_in = CZM->fade_in; + uint32 fade_out = CZM->fade_out; + uint32 duration = CZM->duration; + const char* message = CZM->message; + const char* client_name = CZM->client_name; + if (update_type == CZUpdateType_Character) { + auto client = entity_list.GetClientByCharID(update_identifier); + if (client) { + client->SendMarqueeMessage(type, priority, fade_in, fade_out, duration, message); + } + } else if (update_type == CZUpdateType_Group) { + auto client_group = entity_list.GetGroupByID(update_identifier); + if (client_group) { + for (int member_index = 0; member_index < MAX_GROUP_MEMBERS; member_index++) { + if (client_group->members[member_index] && client_group->members[member_index]->IsClient()) { + auto group_member = client_group->members[member_index]->CastToClient(); + group_member->SendMarqueeMessage(type, priority, fade_in, fade_out, duration, message); + } + } + } + } else if (update_type == CZUpdateType_Raid) { + auto client_raid = entity_list.GetRaidByID(update_identifier); + if (client_raid) { + for (int member_index = 0; member_index < MAX_RAID_MEMBERS; member_index++) { + if (client_raid->members[member_index].member && client_raid->members[member_index].member->IsClient()) { + auto raid_member = client_raid->members[member_index].member->CastToClient(); + raid_member->SendMarqueeMessage(type, priority, fade_in, fade_out, duration, message); + } + } + } + } else if (update_type == CZUpdateType_Guild) { + for (auto &client: entity_list.GetClientList()) { + if (client.second->GuildID() > 0 && client.second->GuildID() == update_identifier) { + client.second->SendMarqueeMessage(type, priority, fade_in, fade_out, duration, message); + } + } + } else if (update_type == CZUpdateType_Expedition) { + for (auto &client: entity_list.GetClientList()) { + if (client.second->GetExpedition() && client.second->GetExpedition()->GetID() == update_identifier) { + client.second->SendMarqueeMessage(type, priority, fade_in, fade_out, duration, message); + } + } + } else if (update_type == CZUpdateType_ClientName) { + auto client = entity_list.GetClientByName(client_name); + if (client) { + client->SendMarqueeMessage(type, priority, fade_in, fade_out, duration, message); + } + } + break; + } + case ServerOP_CZMessage: + { + CZMessage_Struct* CZM = (CZMessage_Struct*) pack->pBuffer; + uint8 update_type = CZM->update_type; + int update_identifier = CZM->update_identifier; + uint32 type = CZM->type; + const char* message = CZM->message; + const char* client_name = CZM->client_name; + if (update_type == CZUpdateType_Character) { + auto client = entity_list.GetClientByCharID(update_identifier); + if (client) { + client->Message(type, message); + } + } else if (update_type == CZUpdateType_Group) { + auto client_group = entity_list.GetGroupByID(update_identifier); + if (client_group) { + for (int member_index = 0; member_index < MAX_GROUP_MEMBERS; member_index++) { + if (client_group->members[member_index] && client_group->members[member_index]->IsClient()) { + auto group_member = client_group->members[member_index]->CastToClient(); + group_member->Message(type, message); + } + } + } + } else if (update_type == CZUpdateType_Raid) { + auto client_raid = entity_list.GetRaidByID(update_identifier); + if (client_raid) { + for (int member_index = 0; member_index < MAX_RAID_MEMBERS; member_index++) { + if (client_raid->members[member_index].member && client_raid->members[member_index].member->IsClient()) { + auto raid_member = client_raid->members[member_index].member->CastToClient(); + raid_member->Message(type, message); + } + } + } + } else if (update_type == CZUpdateType_Guild) { + for (auto &client: entity_list.GetClientList()) { + if (client.second->GuildID() > 0 && client.second->GuildID() == update_identifier) { + client.second->Message(type, message); + } + } + } else if (update_type == CZUpdateType_Expedition) { + for (auto &client: entity_list.GetClientList()) { + if (client.second->GetExpedition() && client.second->GetExpedition()->GetID() == update_identifier) { + client.second->Message(type, message); + } + } + } else if (update_type == CZUpdateType_ClientName) { + auto client = entity_list.GetClientByName(client_name); + if (client) { + client->Message(type, message); + } + } + break; + } + case ServerOP_CZMove: + { + CZMove_Struct* CZM = (CZMove_Struct*) pack->pBuffer; + uint8 update_type = CZM->update_type; + uint8 update_subtype = CZM->update_subtype; + int update_identifier = CZM->update_identifier; + const char* zone_short_name = CZM->zone_short_name; + uint16 instance_id = CZM->instance_id; + const char* client_name = CZM->client_name; + if (update_type == CZUpdateType_Character) { + auto client = entity_list.GetClientByCharID(update_identifier); + if (client) { + switch (update_subtype) { + case CZMoveUpdateSubtype_MoveZone: + client->MoveZone(zone_short_name); + break; + case CZMoveUpdateSubtype_MoveZoneInstance: + client->MoveZoneInstance(instance_id); + break; + } + } + } else if (update_type == CZUpdateType_Group) { + auto client_group = entity_list.GetGroupByID(update_identifier); + if (client_group) { + for (int member_index = 0; member_index < MAX_GROUP_MEMBERS; member_index++) { + if (client_group->members[member_index] && client_group->members[member_index]->IsClient()) { + auto group_member = client_group->members[member_index]->CastToClient(); + switch (update_subtype) { + case CZMoveUpdateSubtype_MoveZone: + group_member->MoveZone(zone_short_name); + break; + case CZMoveUpdateSubtype_MoveZoneInstance: + group_member->MoveZoneInstance(instance_id); + break; + } + } + } + } + } else if (update_type == CZUpdateType_Raid) { + auto client_raid = entity_list.GetRaidByID(update_identifier); + if (client_raid) { + for (int member_index = 0; member_index < MAX_RAID_MEMBERS; member_index++) { + if (client_raid->members[member_index].member && client_raid->members[member_index].member->IsClient()) { + auto raid_member = client_raid->members[member_index].member->CastToClient(); + switch (update_subtype) { + case CZMoveUpdateSubtype_MoveZone: + raid_member->MoveZone(zone_short_name); + break; + case CZMoveUpdateSubtype_MoveZoneInstance: + raid_member->MoveZoneInstance(instance_id); + break; + } + } + } + } + } else if (update_type == CZUpdateType_Guild) { + for (auto &client: entity_list.GetClientList()) { + if (client.second->GuildID() > 0 && client.second->GuildID() == update_identifier) { + switch (update_subtype) { + case CZMoveUpdateSubtype_MoveZone: + client.second->MoveZone(zone_short_name); + break; + case CZMoveUpdateSubtype_MoveZoneInstance: + client.second->MoveZoneInstance(instance_id); + break; + } + } + } + } else if (update_type == CZUpdateType_Expedition) { + for (auto &client: entity_list.GetClientList()) { + if (client.second->GetExpedition() && client.second->GetExpedition()->GetID() == update_identifier) { + switch (update_subtype) { + case CZMoveUpdateSubtype_MoveZone: + client.second->MoveZone(zone_short_name); + break; + case CZMoveUpdateSubtype_MoveZoneInstance: + client.second->MoveZoneInstance(instance_id); + break; + } + } + } + } else if (update_type == CZUpdateType_ClientName) { + auto client = entity_list.GetClientByName(client_name); + if (client) { + switch (update_subtype) { + case CZMoveUpdateSubtype_MoveZone: + client->MoveZone(zone_short_name); + break; + case CZMoveUpdateSubtype_MoveZoneInstance: + client->MoveZoneInstance(instance_id); + break; } } } break; } - case ServerOP_CZCastSpellRaid: + case ServerOP_CZSetEntityVariable: { - CZCastSpellRaid_Struct* CZSC = (CZCastSpellRaid_Struct*) pack->pBuffer; - auto client_raid = entity_list.GetRaidByID(CZSC->raid_id); - if (client_raid) { - for (int member_index = 0; member_index < MAX_RAID_MEMBERS; member_index++) { - if (client_raid->members[member_index].member && client_raid->members[member_index].member->IsClient()) { - auto raid_member = client_raid->members[member_index].member->CastToClient(); - raid_member->SpellFinished(CZSC->spell_id, raid_member); + CZSetEntityVariable_Struct* CZSEV = (CZSetEntityVariable_Struct*) pack->pBuffer; + uint8 update_type = CZSEV->update_type; + int update_identifier = CZSEV->update_identifier; + const char* variable_name = CZSEV->variable_name; + const char* variable_value = CZSEV->variable_value; + const char* client_name = CZSEV->client_name; + if (update_type == CZUpdateType_Character) { + auto client = entity_list.GetClientByCharID(update_identifier); + if (client) { + client->SetEntityVariable(variable_name, variable_value); + } + } else if (update_type == CZUpdateType_Group) { + auto client_group = entity_list.GetGroupByID(update_identifier); + if (client_group) { + for (int member_index = 0; member_index < MAX_GROUP_MEMBERS; member_index++) { + if (client_group->members[member_index] && client_group->members[member_index]->IsClient()) { + auto group_member = client_group->members[member_index]->CastToClient(); + group_member->SetEntityVariable(variable_name, variable_value); + } + } + } + } else if (update_type == CZUpdateType_Raid) { + auto client_raid = entity_list.GetRaidByID(update_identifier); + if (client_raid) { + for (int member_index = 0; member_index < MAX_RAID_MEMBERS; member_index++) { + if (client_raid->members[member_index].member && client_raid->members[member_index].member->IsClient()) { + auto raid_member = client_raid->members[member_index].member->CastToClient(); + raid_member->SetEntityVariable(variable_name, variable_value); + } + } + } + } else if (update_type == CZUpdateType_Guild) { + for (auto &client: entity_list.GetClientList()) { + if (client.second->GuildID() > 0 && client.second->GuildID() == update_identifier) { + client.second->SetEntityVariable(variable_name, variable_value); + } + } + } else if (update_type == CZUpdateType_Expedition) { + for (auto &client: entity_list.GetClientList()) { + if (client.second->GetExpedition() && client.second->GetExpedition()->GetID() == update_identifier) { + client.second->SetEntityVariable(variable_name, variable_value); + } + } + } else if (update_type == CZUpdateType_ClientName) { + auto client = entity_list.GetClientByName(client_name); + if (client) { + client->SetEntityVariable(variable_name, variable_value); + } + } else if (update_type == CZUpdateType_NPC) { + auto npc = entity_list.GetNPCByNPCTypeID(update_identifier); + if (npc) { + npc->SetEntityVariable(variable_name, variable_value); + } + } + break; + } + case ServerOP_CZSignal: + { + CZSignal_Struct* CZS = (CZSignal_Struct*) pack->pBuffer; + uint8 update_type = CZS->update_type; + int update_identifier = CZS->update_identifier; + uint32 signal = CZS->signal; + const char* client_name = CZS->client_name; + if (update_type == CZUpdateType_Character) { + auto client = entity_list.GetClientByCharID(update_identifier); + if (client) { + client->Signal(signal); + } + } else if (update_type == CZUpdateType_Group) { + auto client_group = entity_list.GetGroupByID(update_identifier); + if (client_group) { + for (int member_index = 0; member_index < MAX_GROUP_MEMBERS; member_index++) { + if (client_group->members[member_index] && client_group->members[member_index]->IsClient()) { + auto group_member = client_group->members[member_index]->CastToClient(); + group_member->Signal(signal); + } + } + } + } else if (update_type == CZUpdateType_Raid) { + auto client_raid = entity_list.GetRaidByID(update_identifier); + if (client_raid) { + for (int member_index = 0; member_index < MAX_RAID_MEMBERS; member_index++) { + if (client_raid->members[member_index].member && client_raid->members[member_index].member->IsClient()) { + auto raid_member = client_raid->members[member_index].member->CastToClient(); + raid_member->Signal(signal); + } + } + } + } else if (update_type == CZUpdateType_Guild) { + for (auto &client: entity_list.GetClientList()) { + if (client.second->GuildID() > 0 && client.second->GuildID() == update_identifier) { + client.second->Signal(signal); + } + } + } else if (update_type == CZUpdateType_Expedition) { + for (auto &client: entity_list.GetClientList()) { + if (client.second->GetExpedition() && client.second->GetExpedition()->GetID() == update_identifier) { + client.second->Signal(signal); + } + } + } else if (update_type == CZUpdateType_ClientName) { + auto client = entity_list.GetClientByName(client_name); + if (client) { + client->Signal(signal); + } + } else if (update_type = CZUpdateType_NPC) { + auto npc = entity_list.GetNPCByNPCTypeID(update_identifier); + if (npc) { + npc->SignalNPC(signal); + } + } + break; + } + case ServerOP_CZSpell: + { + CZSpell_Struct* CZS = (CZSpell_Struct*) pack->pBuffer; + uint8 update_type = CZS->update_type; + uint8 update_subtype = CZS->update_subtype; + int update_identifier = CZS->update_identifier; + uint32 spell_id = CZS->spell_id; + const char* client_name = CZS->client_name; + if (update_type == CZUpdateType_Character) { + auto client = entity_list.GetClientByCharID(update_identifier); + if (client) { + switch (update_subtype) { + case CZSpellUpdateSubtype_Cast: + client->SpellFinished(spell_id, client); + break; + case CZSpellUpdateSubtype_Remove: + client->BuffFadeBySpellID(spell_id); + break; + } + } + } else if (update_type == CZUpdateType_Group) { + auto client_group = entity_list.GetGroupByID(update_identifier); + if (client_group) { + for (int member_index = 0; member_index < MAX_GROUP_MEMBERS; member_index++) { + if (client_group->members[member_index] && client_group->members[member_index]->IsClient()) { + auto group_member = client_group->members[member_index]->CastToClient(); + switch (update_subtype) { + case CZSpellUpdateSubtype_Cast: + group_member->SpellFinished(spell_id, group_member); + break; + case CZSpellUpdateSubtype_Remove: + group_member->BuffFadeBySpellID(spell_id); + break; + } + } + } + } + } else if (update_type == CZUpdateType_Raid) { + auto client_raid = entity_list.GetRaidByID(update_identifier); + if (client_raid) { + for (int member_index = 0; member_index < MAX_RAID_MEMBERS; member_index++) { + if (client_raid->members[member_index].member && client_raid->members[member_index].member->IsClient()) { + auto raid_member = client_raid->members[member_index].member->CastToClient(); + switch (update_subtype) { + case CZSpellUpdateSubtype_Cast: + raid_member->SpellFinished(spell_id, raid_member); + break; + case CZSpellUpdateSubtype_Remove: + raid_member->BuffFadeBySpellID(spell_id); + break; + } + } + } + } + } else if (update_type == CZUpdateType_Guild) { + for (auto &client: entity_list.GetClientList()) { + if (client.second->GuildID() > 0 && client.second->GuildID() == update_identifier) { + switch (update_subtype) { + case CZSpellUpdateSubtype_Cast: + client.second->SpellFinished(spell_id, client.second); + break; + case CZSpellUpdateSubtype_Remove: + client.second->BuffFadeBySpellID(spell_id); + break; + } + } + } + } else if (update_type == CZUpdateType_Expedition) { + for (auto &client: entity_list.GetClientList()) { + if (client.second->GetExpedition() && client.second->GetExpedition()->GetID() == update_identifier) { + switch (update_subtype) { + case CZSpellUpdateSubtype_Cast: + client.second->SpellFinished(spell_id, client.second); + break; + case CZSpellUpdateSubtype_Remove: + client.second->BuffFadeBySpellID(spell_id); + break; + } + } + } + } else if (update_type == CZUpdateType_ClientName) { + auto client = entity_list.GetClientByName(client_name); + if (client) { + switch (update_subtype) { + case CZSpellUpdateSubtype_Cast: + client->SpellFinished(spell_id, client); + break; + case CZSpellUpdateSubtype_Remove: + client->BuffFadeBySpellID(spell_id); + break; } } } break; } - case ServerOP_CZCastSpellGuild: + case ServerOP_CZTaskUpdate: { - CZCastSpellGuild_Struct* CZSC = (CZCastSpellGuild_Struct*) pack->pBuffer; + CZTaskUpdate_Struct* CZTU = (CZTaskUpdate_Struct*) pack->pBuffer; + uint8 update_type = CZTU->update_type; + uint8 update_subtype = CZTU->update_subtype; + int update_identifier = CZTU->update_identifier; + uint32 task_identifier = CZTU->task_identifier; + int task_subidentifier = CZTU->task_subidentifier; + int update_count = CZTU->update_count; + bool enforce_level_requirement = CZTU->enforce_level_requirement; + const char* client_name = CZTU->client_name; + if (update_type == CZUpdateType_Character) { + auto client = entity_list.GetClientByCharID(update_identifier); + if (client) { + switch (update_subtype) { + case CZTaskUpdateSubtype_ActivityReset: + client->ResetTaskActivity(task_identifier, task_subidentifier); + break; + case CZTaskUpdateSubtype_ActivityUpdate: + client->UpdateTaskActivity(task_identifier, task_subidentifier, update_count); + break; + case CZTaskUpdateSubtype_AssignTask: + client->AssignTask(task_identifier, task_subidentifier, enforce_level_requirement); + break; + case CZTaskUpdateSubtype_DisableTask: + client->DisableTask(1, reinterpret_cast(task_identifier)); + break; + case CZTaskUpdateSubtype_EnableTask: + client->EnableTask(1, reinterpret_cast(task_identifier)); + break; + case CZTaskUpdateSubtype_FailTask: + client->FailTask(task_identifier); + break; + case CZTaskUpdateSubtype_RemoveTask: + client->RemoveTaskByTaskID(task_identifier); + break; + } + } + break; + } else if (update_type == CZUpdateType_Group) { + auto client_group = entity_list.GetGroupByID(update_identifier); + if (client_group) { + for (int member_index = 0; member_index < MAX_GROUP_MEMBERS; member_index++) { + if (client_group->members[member_index] && client_group->members[member_index]->IsClient()) { + auto group_member = client_group->members[member_index]->CastToClient(); + switch (update_subtype) { + case CZTaskUpdateSubtype_ActivityReset: + group_member->ResetTaskActivity(task_identifier, task_subidentifier); + break; + case CZTaskUpdateSubtype_ActivityUpdate: + group_member->UpdateTaskActivity(task_identifier, task_subidentifier, update_count); + break; + case CZTaskUpdateSubtype_AssignTask: + group_member->AssignTask(task_identifier, task_subidentifier, enforce_level_requirement); + break; + case CZTaskUpdateSubtype_DisableTask: + group_member->DisableTask(1, reinterpret_cast(task_identifier)); + break; + case CZTaskUpdateSubtype_EnableTask: + group_member->EnableTask(1, reinterpret_cast(task_identifier)); + break; + case CZTaskUpdateSubtype_FailTask: + group_member->FailTask(task_identifier); + break; + case CZTaskUpdateSubtype_RemoveTask: + group_member->RemoveTaskByTaskID(task_identifier); + break; + } + } + } + } + } else if (update_type == CZUpdateType_Raid) { + auto client_raid = entity_list.GetRaidByID(update_identifier); + if (client_raid) { + for (int member_index = 0; member_index < MAX_RAID_MEMBERS; member_index++) { + if (client_raid->members[member_index].member && client_raid->members[member_index].member->IsClient()) { + auto raid_member = client_raid->members[member_index].member->CastToClient(); + switch (update_subtype) { + case CZTaskUpdateSubtype_ActivityReset: + raid_member->ResetTaskActivity(task_identifier, task_subidentifier); + break; + case CZTaskUpdateSubtype_ActivityUpdate: + raid_member->UpdateTaskActivity(task_identifier, task_subidentifier, update_count); + break; + case CZTaskUpdateSubtype_AssignTask: + raid_member->AssignTask(task_identifier, task_subidentifier, enforce_level_requirement); + break; + case CZTaskUpdateSubtype_DisableTask: + raid_member->DisableTask(1, reinterpret_cast(task_identifier)); + break; + case CZTaskUpdateSubtype_EnableTask: + raid_member->EnableTask(1, reinterpret_cast(task_identifier)); + break; + case CZTaskUpdateSubtype_FailTask: + raid_member->FailTask(task_identifier); + break; + case CZTaskUpdateSubtype_RemoveTask: + raid_member->RemoveTaskByTaskID(task_identifier); + break; + } + } + } + } + } else if (update_type == CZUpdateType_Guild) { + for (auto &client: entity_list.GetClientList()) { + if (client.second->GuildID() > 0 && client.second->GuildID() == update_identifier) { + switch (update_subtype) { + case CZTaskUpdateSubtype_ActivityReset: + client.second->ResetTaskActivity(task_identifier, task_subidentifier); + break; + case CZTaskUpdateSubtype_ActivityUpdate: + client.second->UpdateTaskActivity(task_identifier, task_subidentifier, update_count); + break; + case CZTaskUpdateSubtype_AssignTask: + client.second->AssignTask(task_identifier, task_subidentifier, enforce_level_requirement); + break; + case CZTaskUpdateSubtype_DisableTask: + client.second->DisableTask(1, reinterpret_cast(task_identifier)); + break; + case CZTaskUpdateSubtype_EnableTask: + client.second->EnableTask(1, reinterpret_cast(task_identifier)); + break; + case CZTaskUpdateSubtype_FailTask: + client.second->FailTask(task_identifier); + break; + case CZTaskUpdateSubtype_RemoveTask: + client.second->RemoveTaskByTaskID(task_identifier); + break; + } + } + } + } else if (update_type == CZUpdateType_Expedition) { + for (auto &client: entity_list.GetClientList()) { + if (client.second->GetExpedition() && client.second->GetExpedition()->GetID() == update_identifier) { + switch (update_subtype) { + case CZTaskUpdateSubtype_ActivityReset: + client.second->ResetTaskActivity(task_identifier, task_subidentifier); + break; + case CZTaskUpdateSubtype_ActivityUpdate: + client.second->UpdateTaskActivity(task_identifier, task_subidentifier, update_count); + break; + case CZTaskUpdateSubtype_AssignTask: + client.second->AssignTask(task_identifier, task_subidentifier, enforce_level_requirement); + break; + case CZTaskUpdateSubtype_DisableTask: + client.second->DisableTask(1, reinterpret_cast(task_identifier)); + break; + case CZTaskUpdateSubtype_EnableTask: + client.second->EnableTask(1, reinterpret_cast(task_identifier)); + break; + case CZTaskUpdateSubtype_FailTask: + client.second->FailTask(task_identifier); + break; + case CZTaskUpdateSubtype_RemoveTask: + client.second->RemoveTaskByTaskID(task_identifier); + break; + } + } + } + } else if (update_type == CZUpdateType_ClientName) { + auto client = entity_list.GetClientByName(client_name); + if (client) { + switch (update_subtype) { + case CZTaskUpdateSubtype_ActivityReset: + client->ResetTaskActivity(task_identifier, task_subidentifier); + break; + case CZTaskUpdateSubtype_ActivityUpdate: + client->UpdateTaskActivity(task_identifier, task_subidentifier, update_count); + break; + case CZTaskUpdateSubtype_AssignTask: + client->AssignTask(task_identifier, task_subidentifier, enforce_level_requirement); + break; + case CZTaskUpdateSubtype_DisableTask: + client->DisableTask(1, reinterpret_cast(task_identifier)); + break; + case CZTaskUpdateSubtype_EnableTask: + client->EnableTask(1, reinterpret_cast(task_identifier)); + break; + case CZTaskUpdateSubtype_FailTask: + client->FailTask(task_identifier); + break; + case CZTaskUpdateSubtype_RemoveTask: + client->RemoveTaskByTaskID(task_identifier); + break; + } + } + } + break; + } + case ServerOP_WWDialogueWindow: + { + WWDialogueWindow_Struct* WWDW = (WWDialogueWindow_Struct*) pack->pBuffer; + std::string message = WWDW->message; + uint8 min_status = WWDW->min_status; + uint8 max_status = WWDW->max_status; for (auto &client : entity_list.GetClientList()) { - if (client.second->GuildID() > 0 && client.second->GuildID() == CZSC->guild_id) { - client.second->SpellFinished(CZSC->spell_id, client.second); + if (client.second->Admin() >= min_status && (client.second->Admin() <= max_status || max_status == AccountStatus::Player)) { + DialogueWindow::Render(client.second, message); } } break; } - case ServerOP_CZMarqueePlayer: + case ServerOP_WWLDoNUpdate: { - CZMarqueePlayer_Struct* CZMS = (CZMarqueePlayer_Struct*) pack->pBuffer; - auto client = entity_list.GetClientByCharID(CZMS->character_id); - std::string message = CZMS->message; - if (client) { - client->SendMarqueeMessage(CZMS->type, CZMS->priority, CZMS->fade_in, CZMS->fade_out, CZMS->duration, message); - } - break; - } - case ServerOP_CZMarqueeGroup: - { - CZMarqueeGroup_Struct* CZMS = (CZMarqueeGroup_Struct*) pack->pBuffer; - auto client_group = entity_list.GetGroupByID(CZMS->group_id); - std::string message = CZMS->message; - if (client_group) { - for (int member_index = 0; member_index < MAX_GROUP_MEMBERS; member_index++) { - if (client_group->members[member_index] && client_group->members[member_index]->IsClient()) { - auto group_member = client_group->members[member_index]->CastToClient(); - group_member->SendMarqueeMessage(CZMS->type, CZMS->priority, CZMS->fade_in, CZMS->fade_out, CZMS->duration, message); - } - } - } - break; - } - case ServerOP_CZMarqueeRaid: - { - CZMarqueeRaid_Struct* CZMS = (CZMarqueeRaid_Struct*) pack->pBuffer; - auto client_raid = entity_list.GetRaidByID(CZMS->raid_id); - std::string message = CZMS->message; - if (client_raid) { - for (int member_index = 0; member_index < MAX_RAID_MEMBERS; member_index++) { - if (client_raid->members[member_index].member && client_raid->members[member_index].member->IsClient()) { - auto raid_member = client_raid->members[member_index].member->CastToClient(); - raid_member->SendMarqueeMessage(CZMS->type, CZMS->priority, CZMS->fade_in, CZMS->fade_out, CZMS->duration, message); - } - } - } - break; - } - case ServerOP_CZMarqueeGuild: - { - CZMarqueeGuild_Struct* CZMS = (CZMarqueeGuild_Struct*) pack->pBuffer; - std::string message = CZMS->message; + WWLDoNUpdate_Struct* WWLU = (WWLDoNUpdate_Struct*) pack->pBuffer; + uint8 update_type = WWLU->update_type; + uint32 theme_id = WWLU->theme_id; + int points = WWLU->points; + uint8 min_status = WWLU->min_status; + uint8 max_status = WWLU->max_status; for (auto &client : entity_list.GetClientList()) { - if (client.second->GuildID() > 0 && client.second->GuildID() == CZMS->guild_id) { - client.second->SendMarqueeMessage(CZMS->type, CZMS->priority, CZMS->fade_in, CZMS->fade_out, CZMS->duration, message); - } - } - break; - } - case ServerOP_CZMessagePlayer: - { - CZMessagePlayer_Struct* CZCS = (CZMessagePlayer_Struct*) pack->pBuffer; - auto client = entity_list.GetClientByName(CZCS->character_name); - if (client) { - client->Message(CZCS->type, CZCS->message); - } - break; - } - case ServerOP_CZMessageGroup: - { - CZMessageGroup_Struct* CZGM = (CZMessageGroup_Struct*) pack->pBuffer; - auto client_group = entity_list.GetGroupByID(CZGM->group_id); - if (client_group) { - for (int member_index = 0; member_index < MAX_GROUP_MEMBERS; member_index++) { - if (client_group->members[member_index] && client_group->members[member_index]->IsClient()) { - auto group_member = client_group->members[member_index]->CastToClient(); - group_member->Message(CZGM->type, CZGM->message); - } - } - } - break; - } - case ServerOP_CZMessageRaid: - { - CZMessageRaid_Struct* CZRM = (CZMessageRaid_Struct*) pack->pBuffer; - auto client_raid = entity_list.GetRaidByID(CZRM->raid_id); - if (client_raid) { - for (int member_index = 0; member_index < MAX_RAID_MEMBERS; member_index++) { - if (client_raid->members[member_index].member && client_raid->members[member_index].member->IsClient()) { - auto raid_member = client_raid->members[member_index].member->CastToClient(); - raid_member->Message(CZRM->type, CZRM->message); - } - } - } - break; - } - case ServerOP_CZMessageGuild: - { - CZMessageGuild_Struct* CZGM = (CZMessageGuild_Struct*) pack->pBuffer; - for (auto &client : entity_list.GetClientList()) { - if (client.second->GuildID() > 0 && client.second->GuildID() == CZGM->guild_id) { - client.second->Message(CZGM->type, CZGM->message); - } - } - break; - } - case ServerOP_CZMovePlayer: - { - CZMovePlayer_Struct* CZMP = (CZMovePlayer_Struct*) pack->pBuffer; - auto client = entity_list.GetClientByCharID(CZMP->character_id); - if (client) { - client->MoveZone(CZMP->zone_short_name); - } - break; - } - case ServerOP_CZMoveGroup: - { - CZMoveGroup_Struct* CZMG = (CZMoveGroup_Struct*) pack->pBuffer; - auto client_group = entity_list.GetGroupByID(CZMG->group_id); - if (client_group) { - for (int member_index = 0; member_index < MAX_GROUP_MEMBERS; member_index++) { - if (client_group->members[member_index] && client_group->members[member_index]->IsClient()) { - auto group_member = client_group->members[member_index]->CastToClient(); - group_member->MoveZone(CZMG->zone_short_name); - } - } - } - break; - } - case ServerOP_CZMoveRaid: - { - CZMoveRaid_Struct* CZMR = (CZMoveRaid_Struct*) pack->pBuffer; - auto client_raid = entity_list.GetRaidByID(CZMR->raid_id); - if (client_raid) { - for (int member_index = 0; member_index < MAX_RAID_MEMBERS; member_index++) { - if (client_raid->members[member_index].member && client_raid->members[member_index].member->IsClient()) { - auto raid_member = client_raid->members[member_index].member->CastToClient(); - raid_member->MoveZone(CZMR->zone_short_name); - } - } - } - break; - } - case ServerOP_CZMoveGuild: - { - CZMoveGuild_Struct* CZMG = (CZMoveGuild_Struct*) pack->pBuffer; - for (auto &client : entity_list.GetClientList()) { - if (client.second->GuildID() > 0 && client.second->GuildID() == CZMG->guild_id) { - client.second->MoveZone(CZMG->zone_short_name); - } - } - break; - } - - case ServerOP_CZMoveInstancePlayer: - { - CZMoveInstancePlayer_Struct* CZMP = (CZMoveInstancePlayer_Struct*) pack->pBuffer; - auto client = entity_list.GetClientByCharID(CZMP->character_id); - if (client) { - client->MoveZoneInstance(CZMP->instance_id); - } - break; - } - case ServerOP_CZMoveInstanceGroup: - { - CZMoveInstanceGroup_Struct* CZMG = (CZMoveInstanceGroup_Struct*) pack->pBuffer; - auto client_group = entity_list.GetGroupByID(CZMG->group_id); - if (client_group) { - for (int member_index = 0; member_index < MAX_GROUP_MEMBERS; member_index++) { - if (client_group->members[member_index] && client_group->members[member_index]->IsClient()) { - auto group_member = client_group->members[member_index]->CastToClient(); - group_member->MoveZoneInstance(CZMG->instance_id); - } - } - } - break; - } - case ServerOP_CZMoveInstanceRaid: - { - CZMoveInstanceRaid_Struct* CZMR = (CZMoveInstanceRaid_Struct*) pack->pBuffer; - auto client_raid = entity_list.GetRaidByID(CZMR->raid_id); - if (client_raid) { - for (int member_index = 0; member_index < MAX_RAID_MEMBERS; member_index++) { - if (client_raid->members[member_index].member && client_raid->members[member_index].member->IsClient()) { - auto raid_member = client_raid->members[member_index].member->CastToClient(); - raid_member->MoveZoneInstance(CZMR->instance_id); - } - } - } - break; - } - case ServerOP_CZMoveInstanceGuild: - { - CZMoveInstanceGuild_Struct* CZMG = (CZMoveInstanceGuild_Struct*) pack->pBuffer; - for (auto &client : entity_list.GetClientList()) { - if (client.second->GuildID() > 0 && client.second->GuildID() == CZMG->guild_id) { - client.second->MoveZoneInstance(CZMG->instance_id); - } - } - break; - } - case ServerOP_CZRemoveSpellPlayer: - { - CZRemoveSpellPlayer_Struct* CZRS = (CZRemoveSpellPlayer_Struct*) pack->pBuffer; - auto client = entity_list.GetClientByCharID(CZRS->character_id); - if (client) { - client->BuffFadeBySpellID(CZRS->spell_id); - } - break; - } - case ServerOP_CZRemoveSpellGroup: - { - CZRemoveSpellGroup_Struct* CZRS = (CZRemoveSpellGroup_Struct*) pack->pBuffer; - auto client_group = entity_list.GetGroupByID(CZRS->group_id); - if (client_group) { - for (int member_index = 0; member_index < MAX_GROUP_MEMBERS; member_index++) { - if (client_group->members[member_index] && client_group->members[member_index]->IsClient()) { - auto group_member = client_group->members[member_index]->CastToClient(); - group_member->BuffFadeBySpellID(CZRS->spell_id); - } - } - } - break; - } - case ServerOP_CZRemoveSpellRaid: - { - CZRemoveSpellRaid_Struct* CZRS = (CZRemoveSpellRaid_Struct*) pack->pBuffer; - auto client_raid = entity_list.GetRaidByID(CZRS->raid_id); - if (client_raid) { - for (int member_index = 0; member_index < MAX_RAID_MEMBERS; member_index++) { - if (client_raid->members[member_index].member && client_raid->members[member_index].member->IsClient()) { - auto raid_member = client_raid->members[member_index].member->CastToClient(); - raid_member->BuffFadeBySpellID(CZRS->spell_id); - } - } - } - break; - } - case ServerOP_CZRemoveSpellGuild: - { - CZRemoveSpellGuild_Struct* CZRS = (CZRemoveSpellGuild_Struct*) pack->pBuffer; - for (auto &client : entity_list.GetClientList()) { - if (client.second->GuildID() > 0 && client.second->GuildID() == CZRS->guild_id) { - client.second->BuffFadeBySpellID(CZRS->spell_id); - } - } - break; - } - case ServerOP_CZSetEntityVariableByClientName: - { - CZSetEntVarByClientName_Struct* CZCS = (CZSetEntVarByClientName_Struct*) pack->pBuffer; - auto client = entity_list.GetClientByName(CZCS->character_name); - if (client) { - client->SetEntityVariable(CZCS->variable_name, CZCS->variable_value); - } - break; - } - case ServerOP_CZSetEntityVariableByGroupID: - { - CZSetEntVarByGroupID_Struct* CZCS = (CZSetEntVarByGroupID_Struct*) pack->pBuffer; - auto client_group = entity_list.GetGroupByID(CZCS->group_id); - if (client_group) { - for (int member_index = 0; member_index < MAX_GROUP_MEMBERS; member_index++) { - if (client_group->members[member_index] && client_group->members[member_index]->IsClient()) { - auto group_member = client_group->members[member_index]->CastToClient(); - group_member->SetEntityVariable(CZCS->variable_name, CZCS->variable_value); - } - } - } - break; - } - case ServerOP_CZSetEntityVariableByRaidID: - { - CZSetEntVarByRaidID_Struct* CZCS = (CZSetEntVarByRaidID_Struct*) pack->pBuffer; - auto client_raid = entity_list.GetRaidByID(CZCS->raid_id); - if (client_raid) { - for (int member_index = 0; member_index < MAX_RAID_MEMBERS; member_index++) { - if (client_raid->members[member_index].member && client_raid->members[member_index].member->IsClient()) { - auto raid_member = client_raid->members[member_index].member->CastToClient(); - raid_member->SetEntityVariable(CZCS->variable_name, CZCS->variable_value); - } - } - } - break; - } - case ServerOP_CZSetEntityVariableByGuildID: - { - CZSetEntVarByGuildID_Struct* CZCS = (CZSetEntVarByGuildID_Struct*) pack->pBuffer; - for (auto &client : entity_list.GetClientList()) { - if (client.second->GuildID() > 0 && client.second->GuildID() == CZCS->guild_id) { - client.second->SetEntityVariable(CZCS->variable_name, CZCS->variable_value); - } - } - break; - } - case ServerOP_CZSetEntityVariableByNPCTypeID: - { - CZSetEntVarByNPCTypeID_Struct* CZM = (CZSetEntVarByNPCTypeID_Struct*) pack->pBuffer; - auto npc = entity_list.GetNPCByNPCTypeID(CZM->npctype_id); - if (npc != 0) { - npc->SetEntityVariable(CZM->variable_name, CZM->variable_value); - } - break; - } - case ServerOP_CZSignalNPC: - { - CZNPCSignal_Struct* CZCN = (CZNPCSignal_Struct*) pack->pBuffer; - auto npc = entity_list.GetNPCByNPCTypeID(CZCN->npctype_id); - if (npc != 0) { - npc->SignalNPC(CZCN->signal); - } - break; - } - case ServerOP_CZSignalClient: - { - CZClientSignal_Struct* CZCS = (CZClientSignal_Struct*) pack->pBuffer; - auto client = entity_list.GetClientByCharID(CZCS->character_id); - if (client) { - client->Signal(CZCS->signal); - } - break; - } - case ServerOP_CZSignalGroup: - { - CZGroupSignal_Struct* CZGS = (CZGroupSignal_Struct*) pack->pBuffer; - auto client_group = entity_list.GetGroupByID(CZGS->group_id); - if (client_group) { - for (int member_index = 0; member_index < MAX_GROUP_MEMBERS; member_index++) { - if (client_group->members[member_index] && client_group->members[member_index]->IsClient()) { - auto group_member = client_group->members[member_index]->CastToClient(); - group_member->Signal(CZGS->signal); - } - } - } - break; - } - case ServerOP_CZSignalRaid: - { - CZRaidSignal_Struct* CZRS = (CZRaidSignal_Struct*) pack->pBuffer; - auto client_raid = entity_list.GetRaidByID(CZRS->raid_id); - if (client_raid) { - for (int member_index = 0; member_index < MAX_RAID_MEMBERS; member_index++) { - if (client_raid->members[member_index].member && client_raid->members[member_index].member->IsClient()) { - auto raid_member = client_raid->members[member_index].member->CastToClient(); - raid_member->Signal(CZRS->signal); - } - } - } - break; - } - case ServerOP_CZSignalGuild: - { - CZGuildSignal_Struct* CZGS = (CZGuildSignal_Struct*) pack->pBuffer; - for (auto &client : entity_list.GetClientList()) { - if (client.second->GuildID() > 0 && client.second->GuildID() == CZGS->guild_id) { - client.second->Signal(CZGS->signal); - } - } - break; - } - case ServerOP_CZSignalClientByName: - { - CZClientSignalByName_Struct* CZCS = (CZClientSignalByName_Struct*) pack->pBuffer; - auto client = entity_list.GetClientByName(CZCS->character_name); - if (client) { - client->Signal(CZCS->signal); - } - break; - } - case ServerOP_CZTaskAssignPlayer: - { - CZTaskAssignPlayer_Struct* CZTA = (CZTaskAssignPlayer_Struct*) pack->pBuffer; - auto client = entity_list.GetClientByCharID(CZTA->character_id); - if (client) { - client->AssignTask(CZTA->task_id, CZTA->npc_entity_id, CZTA->enforce_level_requirement); - } - break; - } - case ServerOP_CZTaskAssignGroup: - { - CZTaskAssignGroup_Struct* CZTA = (CZTaskAssignGroup_Struct*) pack->pBuffer; - auto client_group = entity_list.GetGroupByID(CZTA->group_id); - if (client_group) { - for (int member_index = 0; member_index < MAX_GROUP_MEMBERS; member_index++) { - if (client_group->members[member_index] && client_group->members[member_index]->IsClient()) { - auto group_member = client_group->members[member_index]->CastToClient(); - group_member->AssignTask(CZTA->task_id, CZTA->npc_entity_id, CZTA->enforce_level_requirement); - } - } - } - break; - } - case ServerOP_CZTaskAssignRaid: - { - CZTaskAssignRaid_Struct* CZTA = (CZTaskAssignRaid_Struct*) pack->pBuffer; - auto client_raid = entity_list.GetRaidByID(CZTA->raid_id); - if (client_raid) { - for (int member_index = 0; member_index < MAX_RAID_MEMBERS; member_index++) { - if (client_raid->members[member_index].member && client_raid->members[member_index].member->IsClient()) { - auto raid_member = client_raid->members[member_index].member->CastToClient(); - raid_member->AssignTask(CZTA->task_id, CZTA->npc_entity_id, CZTA->enforce_level_requirement); - } - } - } - break; - } - case ServerOP_CZTaskAssignGuild: - { - CZTaskAssignGuild_Struct* CZTA = (CZTaskAssignGuild_Struct*) pack->pBuffer; - for (auto &client : entity_list.GetClientList()) { - if (client.second->GuildID() > 0 && client.second->GuildID() == CZTA->guild_id) { - client.second->AssignTask(CZTA->task_id, CZTA->npc_entity_id, CZTA->enforce_level_requirement); - } - } - break; - } - case ServerOP_CZTaskActivityResetPlayer: - { - CZTaskActivityResetPlayer_Struct* CZRA = (CZTaskActivityResetPlayer_Struct*) pack->pBuffer; - auto client = entity_list.GetClientByCharID(CZRA->character_id); - if (client) { - client->ResetTaskActivity(CZRA->task_id, CZRA->activity_id); - } - break; - } - case ServerOP_CZTaskActivityResetGroup: - { - CZTaskActivityResetGroup_Struct* CZRA = (CZTaskActivityResetGroup_Struct*) pack->pBuffer; - auto client_group = entity_list.GetGroupByID(CZRA->group_id); - if (client_group) { - for (int member_index = 0; member_index < MAX_GROUP_MEMBERS; member_index++) { - if (client_group->members[member_index] && client_group->members[member_index]->IsClient()) { - auto group_member = client_group->members[member_index]->CastToClient(); - group_member->ResetTaskActivity(CZRA->task_id, CZRA->activity_id); - } - } - } - break; - } - case ServerOP_CZTaskActivityResetRaid: - { - CZTaskActivityResetRaid_Struct* CZRA = (CZTaskActivityResetRaid_Struct*) pack->pBuffer; - auto client_raid = entity_list.GetRaidByID(CZRA->raid_id); - if (client_raid) { - for (int member_index = 0; member_index < MAX_RAID_MEMBERS; member_index++) { - if (client_raid->members[member_index].member && client_raid->members[member_index].member->IsClient()) { - auto raid_member = client_raid->members[member_index].member->CastToClient(); - raid_member->ResetTaskActivity(CZRA->task_id, CZRA->activity_id); - } - } - } - break; - } - case ServerOP_CZTaskActivityResetGuild: - { - CZTaskActivityResetGuild_Struct* CZRA = (CZTaskActivityResetGuild_Struct*) pack->pBuffer; - for (auto &client : entity_list.GetClientList()) { - if (client.second->GuildID() > 0 && client.second->GuildID() == CZRA->guild_id) { - client.second->ResetTaskActivity(CZRA->task_id, CZRA->activity_id); - } - } - break; - } - case ServerOP_CZTaskActivityUpdatePlayer: - { - CZTaskActivityUpdatePlayer_Struct* CZUA = (CZTaskActivityUpdatePlayer_Struct*) pack->pBuffer; - auto client = entity_list.GetClientByCharID(CZUA->character_id); - if (client) { - client->UpdateTaskActivity(CZUA->task_id, CZUA->activity_id, CZUA->activity_count); - } - break; - } - case ServerOP_CZTaskActivityUpdateGroup: - { - CZTaskActivityUpdateGroup_Struct* CZUA = (CZTaskActivityUpdateGroup_Struct*) pack->pBuffer; - auto client_group = entity_list.GetGroupByID(CZUA->group_id); - if (client_group) { - for (int member_index = 0; member_index < MAX_GROUP_MEMBERS; member_index++) { - if (client_group->members[member_index] && client_group->members[member_index]->IsClient()) { - auto group_member = client_group->members[member_index]->CastToClient(); - group_member->UpdateTaskActivity(CZUA->task_id, CZUA->activity_id, CZUA->activity_count); - } - } - } - break; - } - case ServerOP_CZTaskActivityUpdateRaid: - { - CZTaskActivityUpdateRaid_Struct* CZUA = (CZTaskActivityUpdateRaid_Struct*) pack->pBuffer; - auto client_raid = entity_list.GetRaidByID(CZUA->raid_id); - if (client_raid) { - for (int member_index = 0; member_index < MAX_RAID_MEMBERS; member_index++) { - if (client_raid->members[member_index].member && client_raid->members[member_index].member->IsClient()) { - auto raid_member = client_raid->members[member_index].member->CastToClient(); - raid_member->UpdateTaskActivity(CZUA->task_id, CZUA->activity_id, CZUA->activity_count); - } - } - } - break; - } - case ServerOP_CZTaskActivityUpdateGuild: - { - CZTaskActivityUpdateGuild_Struct* CZUA = (CZTaskActivityUpdateGuild_Struct*) pack->pBuffer; - for (auto &client : entity_list.GetClientList()) { - if (client.second->GuildID() > 0 && client.second->GuildID() == CZUA->guild_id) { - client.second->UpdateTaskActivity(CZUA->task_id, CZUA->activity_id, CZUA->activity_count); - } - } - break; - } - case ServerOP_CZTaskDisablePlayer: - { - CZTaskDisablePlayer_Struct* CZUA = (CZTaskDisablePlayer_Struct*) pack->pBuffer; - auto client = entity_list.GetClientByCharID(CZUA->character_id); - if (client) { - client->DisableTask(1, reinterpret_cast(CZUA->task_id)); - } - break; - } - case ServerOP_CZTaskDisableGroup: - { - CZTaskDisableGroup_Struct* CZUA = (CZTaskDisableGroup_Struct*) pack->pBuffer; - auto client_group = entity_list.GetGroupByID(CZUA->group_id); - if (client_group) { - for (int member_index = 0; member_index < MAX_GROUP_MEMBERS; member_index++) { - if (client_group->members[member_index] && client_group->members[member_index]->IsClient()) { - auto group_member = client_group->members[member_index]->CastToClient(); - group_member->DisableTask(1, reinterpret_cast(CZUA->task_id)); - } - } - } - break; - } - case ServerOP_CZTaskDisableRaid: - { - CZTaskDisableRaid_Struct* CZUA = (CZTaskDisableRaid_Struct*) pack->pBuffer; - auto client_raid = entity_list.GetRaidByID(CZUA->raid_id); - if (client_raid) { - for (int member_index = 0; member_index < MAX_RAID_MEMBERS; member_index++) { - if (client_raid->members[member_index].member && client_raid->members[member_index].member->IsClient()) { - auto raid_member = client_raid->members[member_index].member->CastToClient(); - raid_member->DisableTask(1, reinterpret_cast(CZUA->task_id)); - } - } - } - break; - } - case ServerOP_CZTaskDisableGuild: - { - CZTaskDisableGuild_Struct* CZUA = (CZTaskDisableGuild_Struct*) pack->pBuffer; - for (auto &client : entity_list.GetClientList()) { - if (client.second->GuildID() > 0 && client.second->GuildID() == CZUA->guild_id) { - client.second->DisableTask(1, reinterpret_cast(CZUA->task_id)); - } - } - break; - } - case ServerOP_CZTaskEnablePlayer: - { - CZTaskEnablePlayer_Struct* CZUA = (CZTaskEnablePlayer_Struct*) pack->pBuffer; - auto client = entity_list.GetClientByCharID(CZUA->character_id); - if (client) { - client->EnableTask(1, reinterpret_cast(CZUA->task_id)); - } - break; - } - case ServerOP_CZTaskEnableGroup: - { - CZTaskEnableGroup_Struct* CZUA = (CZTaskEnableGroup_Struct*) pack->pBuffer; - auto client_group = entity_list.GetGroupByID(CZUA->group_id); - if (client_group) { - for (int member_index = 0; member_index < MAX_GROUP_MEMBERS; member_index++) { - if (client_group->members[member_index] && client_group->members[member_index]->IsClient()) { - auto group_member = client_group->members[member_index]->CastToClient(); - group_member->EnableTask(1, reinterpret_cast(CZUA->task_id)); - } - } - } - break; - } - case ServerOP_CZTaskEnableRaid: - { - CZTaskEnableRaid_Struct* CZUA = (CZTaskEnableRaid_Struct*) pack->pBuffer; - auto client_raid = entity_list.GetRaidByID(CZUA->raid_id); - if (client_raid) { - for (int member_index = 0; member_index < MAX_RAID_MEMBERS; member_index++) { - if (client_raid->members[member_index].member && client_raid->members[member_index].member->IsClient()) { - auto raid_member = client_raid->members[member_index].member->CastToClient(); - raid_member->EnableTask(1, reinterpret_cast(CZUA->task_id)); - } - } - } - break; - } - case ServerOP_CZTaskEnableGuild: - { - CZTaskEnableGuild_Struct* CZUA = (CZTaskEnableGuild_Struct*) pack->pBuffer; - for (auto &client : entity_list.GetClientList()) { - if (client.second->GuildID() > 0 && client.second->GuildID() == CZUA->guild_id) { - client.second->EnableTask(1, reinterpret_cast(CZUA->task_id)); - } - } - break; - } - case ServerOP_CZTaskFailPlayer: - { - CZTaskFailPlayer_Struct* CZUA = (CZTaskFailPlayer_Struct*) pack->pBuffer; - auto client = entity_list.GetClientByCharID(CZUA->character_id); - if (client) { - client->FailTask(CZUA->task_id); - } - break; - } - case ServerOP_CZTaskFailGroup: - { - CZTaskFailGroup_Struct* CZUA = (CZTaskFailGroup_Struct*) pack->pBuffer; - auto client_group = entity_list.GetGroupByID(CZUA->group_id); - if (client_group) { - for (int member_index = 0; member_index < MAX_GROUP_MEMBERS; member_index++) { - if (client_group->members[member_index] && client_group->members[member_index]->IsClient()) { - auto group_member = client_group->members[member_index]->CastToClient(); - group_member->FailTask(CZUA->task_id); - } - } - } - break; - } - case ServerOP_CZTaskFailRaid: - { - CZTaskFailRaid_Struct* CZUA = (CZTaskFailRaid_Struct*) pack->pBuffer; - auto client_raid = entity_list.GetRaidByID(CZUA->raid_id); - if (client_raid) { - for (int member_index = 0; member_index < MAX_RAID_MEMBERS; member_index++) { - if (client_raid->members[member_index].member && client_raid->members[member_index].member->IsClient()) { - auto raid_member = client_raid->members[member_index].member->CastToClient(); - raid_member->FailTask(CZUA->task_id); - } - } - } - break; - } - case ServerOP_CZTaskFailGuild: - { - CZTaskFailGuild_Struct* CZUA = (CZTaskFailGuild_Struct*) pack->pBuffer; - for (auto &client : entity_list.GetClientList()) { - if (client.second->GuildID() > 0 && client.second->GuildID() == CZUA->guild_id) { - client.second->FailTask(CZUA->task_id); - } - } - break; - } - case ServerOP_CZTaskRemovePlayer: - { - CZTaskRemovePlayer_Struct* CZTR = (CZTaskRemovePlayer_Struct*) pack->pBuffer; - auto client = entity_list.GetClientByCharID(CZTR->character_id); - if (client) { - client->RemoveTaskByTaskID(CZTR->task_id); - } - break; - } - case ServerOP_CZTaskRemoveGroup: - { - CZTaskRemoveGroup_Struct* CZTR = (CZTaskRemoveGroup_Struct*) pack->pBuffer; - auto client_group = entity_list.GetGroupByID(CZTR->group_id); - if (client_group) { - for (int member_index = 0; member_index < MAX_GROUP_MEMBERS; member_index++) { - if (client_group->members[member_index] && client_group->members[member_index]->IsClient()) { - auto group_member = client_group->members[member_index]->CastToClient(); - group_member->RemoveTaskByTaskID(CZTR->task_id); - } - } - } - break; - } - case ServerOP_CZTaskRemoveRaid: - { - CZTaskRemoveRaid_Struct* CZTR = (CZTaskRemoveRaid_Struct*) pack->pBuffer; - auto client_raid = entity_list.GetRaidByID(CZTR->raid_id); - if (client_raid) { - for (int member_index = 0; member_index < MAX_RAID_MEMBERS; member_index++) { - if (client_raid->members[member_index].member && client_raid->members[member_index].member->IsClient()) { - auto raid_member = client_raid->members[member_index].member->CastToClient(); - raid_member->RemoveTaskByTaskID(CZTR->task_id); - } - } - } - break; - } - case ServerOP_CZTaskRemoveGuild: - { - CZTaskRemoveGuild_Struct* CZTR = (CZTaskRemoveGuild_Struct*) pack->pBuffer; - for (auto &client : entity_list.GetClientList()) { - if (client.second->GuildID() > 0 && client.second->GuildID() == CZTR->guild_id) { - client.second->RemoveTaskByTaskID(CZTR->task_id); - } - } - break; - } - case ServerOP_WWAssignTask: - { - WWAssignTask_Struct* WWAT = (WWAssignTask_Struct*) pack->pBuffer; - for (auto &client : entity_list.GetClientList()) { - auto client_status = client.second->Admin(); - if (client_status >= WWAT->min_status && (client_status <= WWAT->max_status || WWAT->max_status == 0)) { - client.second->AssignTask(WWAT->task_id, WWAT->npc_entity_id, WWAT->enforce_level_requirement); - } - } - break; - } - case ServerOP_WWCastSpell: - { - WWCastSpell_Struct* WWCS = (WWCastSpell_Struct*) pack->pBuffer; - for (auto &client : entity_list.GetClientList()) { - auto client_status = client.second->Admin(); - if (client_status >= WWCS->min_status && (client_status <= WWCS->max_status || WWCS->max_status == 0)) { - client.second->SpellFinished(WWCS->spell_id, client.second); - } - } - break; - } - case ServerOP_WWDisableTask: - { - WWDisableTask_Struct* WWDT = (WWDisableTask_Struct*) pack->pBuffer; - for (auto &client : entity_list.GetClientList()) { - auto client_status = client.second->Admin(); - if (client_status >= WWDT->min_status && (client_status <= WWDT->max_status || WWDT->max_status == 0)) { - client.second->DisableTask(1, reinterpret_cast(WWDT->task_id)); - } - } - break; - } - case ServerOP_WWEnableTask: - { - WWEnableTask_Struct* WWET = (WWEnableTask_Struct*) pack->pBuffer; - for (auto &client : entity_list.GetClientList()) { - auto client_status = client.second->Admin(); - if (client_status >= WWET->min_status && (client_status <= WWET->max_status || WWET->max_status == 0)) { - client.second->EnableTask(1, reinterpret_cast(WWET->task_id)); - } - } - break; - } - case ServerOP_WWFailTask: - { - WWFailTask_Struct* WWFT = (WWFailTask_Struct*) pack->pBuffer; - for (auto &client : entity_list.GetClientList()) { - auto client_status = client.second->Admin(); - if (client_status >= WWFT->min_status && (client_status <= WWFT->max_status || WWFT->max_status == 0)) { - client.second->FailTask(WWFT->task_id); + switch (update_type) { + case WWLDoNUpdateType_AddLoss: + if (client.second->Admin() >= min_status && (client.second->Admin() <= max_status || max_status == AccountStatus::Player)) { + client.second->UpdateLDoNWinLoss(theme_id, false); + } + break; + case WWLDoNUpdateType_AddPoints: + if (client.second->Admin() >= min_status && (client.second->Admin() <= max_status || max_status == AccountStatus::Player)) { + client.second->UpdateLDoNPoints(theme_id, points); + } + break; + case WWLDoNUpdateType_AddWin: + if (client.second->Admin() >= min_status && (client.second->Admin() <= max_status || max_status == AccountStatus::Player)) { + client.second->UpdateLDoNWinLoss(theme_id, true); + } + break; + case WWLDoNUpdateType_RemoveLoss: + if (client.second->Admin() >= min_status && (client.second->Admin() <= max_status || max_status == AccountStatus::Player)) { + client.second->UpdateLDoNWinLoss(theme_id, false, true); + } + break; + case WWLDoNUpdateType_RemoveWin: + if (client.second->Admin() >= min_status && (client.second->Admin() <= max_status || max_status == AccountStatus::Player)) { + client.second->UpdateLDoNWinLoss(theme_id, true, true); + } + break; } } break; } case ServerOP_WWMarquee: { - WWMarquee_Struct* WWMS = (WWMarquee_Struct*) pack->pBuffer; - std::string message = WWMS->message; + WWMarquee_Struct* WWM = (WWMarquee_Struct*) pack->pBuffer; + uint32 type = WWM->type; + uint32 priority = WWM->priority; + uint32 fade_in = WWM->fade_in; + uint32 fade_out = WWM->fade_out; + uint32 duration = WWM->duration; + const char* message = WWM->message; + uint8 min_status = WWM->min_status; + uint8 max_status = WWM->max_status; for (auto &client : entity_list.GetClientList()) { - auto client_status = client.second->Admin(); - if (client_status >= WWMS->min_status && (client_status <= WWMS->max_status || WWMS->max_status == 0)) { - client.second->SendMarqueeMessage(WWMS->type, WWMS->priority, WWMS->fade_in, WWMS->fade_out, WWMS->duration, message); + if (client.second->Admin() >= min_status && (client.second->Admin() <= max_status || max_status == AccountStatus::Player)) { + client.second->SendMarqueeMessage(type, priority, fade_in, fade_out, duration, message); } } break; } case ServerOP_WWMessage: { - WWMessage_Struct* WWMS = (WWMessage_Struct*) pack->pBuffer; + WWMessage_Struct* WWM = (WWMessage_Struct*) pack->pBuffer; + uint32 type = WWM->type; + const char* message = WWM->message; + uint8 min_status = WWM->min_status; + uint8 max_status = WWM->max_status; for (auto &client : entity_list.GetClientList()) { - auto client_status = client.second->Admin(); - if (client_status >= WWMS->min_status && (client_status <= WWMS->max_status || WWMS->max_status == 0)) { - client.second->Message(WWMS->type, WWMS->message); + if (client.second->Admin() >= min_status && (client.second->Admin() <= max_status || max_status == AccountStatus::Player)) { + client.second->Message(type, message); } } break; } case ServerOP_WWMove: { - WWMove_Struct* WWMS = (WWMove_Struct*) pack->pBuffer; + WWMove_Struct* WWM = (WWMove_Struct*) pack->pBuffer; + uint8 update_type = WWM->update_type; + uint16 instance_id = WWM->instance_id; + const char* zone_short_name = WWM->zone_short_name; + uint8 min_status = WWM->min_status; + uint8 max_status = WWM->max_status; for (auto &client : entity_list.GetClientList()) { - auto client_status = client.second->Admin(); - if (client_status >= WWMS->min_status && (client_status <= WWMS->max_status || WWMS->max_status == 0)) { - client.second->MoveZone(WWMS->zone_short_name); + switch (update_type) { + case WWMoveUpdateType_MoveZone: + if (client.second->Admin() >= min_status && (client.second->Admin() <= max_status || max_status == AccountStatus::Player)) { + client.second->MoveZone(zone_short_name); + } + break; + case WWMoveUpdateType_MoveZoneInstance: + if (client.second->Admin() >= min_status && (client.second->Admin() <= max_status || max_status == AccountStatus::Player)) { + client.second->MoveZoneInstance(instance_id); + } + break; } } break; } - case ServerOP_WWMoveInstance: + case ServerOP_WWSetEntityVariable: { - WWMoveInstance_Struct* WWMS = (WWMoveInstance_Struct*) pack->pBuffer; - for (auto &client : entity_list.GetClientList()) { - auto client_status = client.second->Admin(); - if (client_status >= WWMS->min_status && (client_status <= WWMS->max_status || WWMS->max_status == 0)) { - client.second->MoveZoneInstance(WWMS->instance_id); + WWSetEntityVariable_Struct* WWSEV = (WWSetEntityVariable_Struct*) pack->pBuffer; + uint8 update_type = WWSEV->update_type; + const char* variable_name = WWSEV->variable_name; + const char* variable_value = WWSEV->variable_value; + uint8 min_status = WWSEV->min_status; + uint8 max_status = WWSEV->max_status; + if (update_type == WWSetEntityVariableUpdateType_Character) { + for (auto &client : entity_list.GetClientList()) { + if (client.second->Admin() >= min_status && (client.second->Admin() <= max_status || max_status == AccountStatus::Player)) { + client.second->SetEntityVariable(variable_name, variable_value); + } + } + } else if (update_type == WWSetEntityVariableUpdateType_NPC) { + for (auto &npc : entity_list.GetNPCList()) { + npc.second->SetEntityVariable(variable_name, variable_value); } } break; } - case ServerOP_WWRemoveSpell: + case ServerOP_WWSignal: { - WWRemoveSpell_Struct* WWRS = (WWRemoveSpell_Struct*) pack->pBuffer; - for (auto &client : entity_list.GetClientList()) { - auto client_status = client.second->Admin(); - if (client_status >= WWRS->min_status && (client_status <= WWRS->max_status || WWRS->max_status == 0)) { - client.second->BuffFadeBySpellID(WWRS->spell_id); + WWSignal_Struct* WWS = (WWSignal_Struct*) pack->pBuffer; + uint8 update_type = WWS->update_type; + uint32 signal = WWS->signal; + uint8 min_status = WWS->min_status; + uint8 max_status = WWS->max_status; + if (update_type == WWSignalUpdateType_Character) { + for (auto &client : entity_list.GetClientList()) { + if (client.second->Admin() >= min_status && (client.second->Admin() <= max_status || max_status == AccountStatus::Player)) { + client.second->Signal(signal); + } + } + } else if (update_type == WWSignalUpdateType_NPC) { + for (auto &npc : entity_list.GetNPCList()) { + npc.second->SignalNPC(signal); } } break; } - case ServerOP_WWRemoveTask: + case ServerOP_WWSpell: { - WWRemoveTask_Struct* WWRT = (WWRemoveTask_Struct*) pack->pBuffer; - for (auto &client : entity_list.GetClientList()) { - auto client_status = client.second->Admin(); - if (client_status >= WWRT->min_status && (client_status <= WWRT->max_status || WWRT->max_status == 0)) { - client.second->RemoveTaskByTaskID(WWRT->task_id); + WWSpell_Struct* WWS = (WWSpell_Struct*) pack->pBuffer; + uint8 update_type = WWS->update_type; + uint32 spell_id = WWS->spell_id; + uint8 min_status = WWS->min_status; + uint8 max_status = WWS->max_status; + if (update_type == WWSpellUpdateType_Cast) { + for (auto &client : entity_list.GetClientList()) { + if (client.second->Admin() >= min_status && (client.second->Admin() <= max_status || max_status == AccountStatus::Player)) { + client.second->SpellFinished(spell_id, client.second); + } + } + } else if (update_type == WWSpellUpdateType_Remove) { + for (auto &client : entity_list.GetClientList()) { + if (client.second->Admin() >= min_status && (client.second->Admin() <= max_status || max_status == AccountStatus::Player)) { + client.second->BuffFadeBySpellID(spell_id); + } } } break; } - case ServerOP_WWResetActivity: + case ServerOP_WWTaskUpdate: { - WWResetActivity_Struct* WWRA = (WWResetActivity_Struct*) pack->pBuffer; + WWTaskUpdate_Struct* WWTU = (WWTaskUpdate_Struct*) pack->pBuffer; + uint8 update_type = WWTU->update_type; + uint32 task_identifier = WWTU->task_identifier; + int task_subidentifier = WWTU->task_subidentifier; + int update_count = WWTU->update_count; + bool enforce_level_requirement = WWTU->enforce_level_requirement; + uint8 min_status = WWTU->min_status; + uint8 max_status = WWTU->max_status; for (auto &client : entity_list.GetClientList()) { - auto client_status = client.second->Admin(); - if (client_status >= WWRA->min_status && (client_status <= WWRA->max_status || WWRA->max_status == 0)) { - client.second->ResetTaskActivity(WWRA->task_id, WWRA->activity_id); - } - } - break; - } - case ServerOP_WWSetEntityVariableClient: - { - WWSetEntVarClient_Struct* WWSC = (WWSetEntVarClient_Struct*) pack->pBuffer; - for (auto &client : entity_list.GetClientList()) { - auto client_status = client.second->Admin(); - if (client_status >= WWSC->min_status && (client_status <= WWSC->max_status || WWSC->max_status == 0)) { - client.second->SetEntityVariable(WWSC->variable_name, WWSC->variable_value); - } - } - break; - } - case ServerOP_WWSetEntityVariableNPC: - { - WWSetEntVarNPC_Struct* WWSN = (WWSetEntVarNPC_Struct*) pack->pBuffer; - for (auto &npc : entity_list.GetNPCList()) { - npc.second->SetEntityVariable(WWSN->variable_name, WWSN->variable_value); - } - break; - } - case ServerOP_WWSignalClient: - { - WWSignalClient_Struct* WWSC = (WWSignalClient_Struct*) pack->pBuffer; - for (auto &client : entity_list.GetClientList()) { - auto client_status = client.second->Admin(); - if (client_status >= WWSC->min_status && (client_status <= WWSC->max_status || WWSC->max_status == 0)) { - client.second->Signal(WWSC->signal); - } - } - break; - } - case ServerOP_WWSignalNPC: - { - WWSignalNPC_Struct* WWSN = (WWSignalNPC_Struct*) pack->pBuffer; - for (auto &npc : entity_list.GetNPCList()) { - npc.second->SignalNPC(WWSN->signal); - } - break; - } - case ServerOP_WWUpdateActivity: - { - WWUpdateActivity_Struct* WWUA = (WWUpdateActivity_Struct*) pack->pBuffer; - for (auto &client : entity_list.GetClientList()) { - auto client_status = client.second->Admin(); - if (client_status >= WWUA->min_status && (client_status <= WWUA->max_status || WWUA->max_status == 0)) { - client.second->UpdateTaskActivity(WWUA->task_id, WWUA->activity_id, WWUA->activity_count); + if (client.second->Admin() >= min_status && (client.second->Admin() <= max_status || max_status == AccountStatus::Player)) { + switch (update_type) { + case WWTaskUpdateType_ActivityReset: + client.second->ResetTaskActivity(task_identifier, task_subidentifier); + break; + case WWTaskUpdateType_ActivityUpdate: + client.second->UpdateTaskActivity(task_identifier, task_subidentifier, update_count); + break; + case WWTaskUpdateType_AssignTask: + client.second->AssignTask(task_identifier, task_subidentifier, enforce_level_requirement); + break; + case WWTaskUpdateType_DisableTask: + client.second->DisableTask(1, reinterpret_cast(task_identifier)); + break; + case WWTaskUpdateType_EnableTask: + client.second->EnableTask(1, reinterpret_cast(task_identifier)); + break; + case WWTaskUpdateType_FailTask: + client.second->FailTask(task_identifier); + break; + case WWTaskUpdateType_RemoveTask: + client.second->RemoveTaskByTaskID(task_identifier); + break; + } } } break; @@ -2815,6 +3099,15 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) break; } + case ServerOP_UpdateSchedulerEvents: { + LogScheduler("Received signal from world to update"); + if (m_zone_scheduler) { + m_zone_scheduler->LoadScheduledEvents(); + } + + break; + } + case ServerOP_HotReloadQuests: { if (!zone) { @@ -2836,11 +3129,28 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) if (request_zone_short_name == local_zone_short_name || can_reload_global_script) { zone->SetQuestHotReloadQueued(true); } else if (request_zone_short_name == "all") { - std::string reload_quest_saylink = EQ::SayLinkEngine::GenerateQuestSaylink("#reloadquest", false, "Locally"); - std::string reload_world_saylink = EQ::SayLinkEngine::GenerateQuestSaylink("#reloadworld", false, "Globally"); - worldserver.SendEmoteMessage(0, 0, 20, 15, "A quest, plugin, or global script has changed reload quests [%s] [%s].", reload_quest_saylink.c_str(), reload_world_saylink.c_str()); + std::string reload_quest_saylink = EQ::SayLinkEngine::GenerateQuestSaylink( + "#reloadquest", + false, + "Locally" + ); + std::string reload_world_saylink = EQ::SayLinkEngine::GenerateQuestSaylink( + "#reloadworld", + false, + "Globally" + ); + worldserver.SendEmoteMessage( + 0, + 0, + AccountStatus::ApprenticeGuide, + Chat::Yellow, + fmt::format( + "A quest, plugin, or global script has changed. Reload: [{}] [{}]", + reload_quest_saylink, + reload_world_saylink + ).c_str() + ); } - break; } case ServerOP_ChangeSharedMem: @@ -2880,45 +3190,57 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) case ServerOP_CZClientMessageString: { auto buf = reinterpret_cast(pack->pBuffer); - Client* client = entity_list.GetClientByName(buf->character_name); + Client* client = entity_list.GetClientByName(buf->client_name); if (client) { client->MessageString(buf); } break; } case ServerOP_ExpeditionCreate: - case ServerOP_ExpeditionDeleted: - case ServerOP_ExpeditionLeaderChanged: case ServerOP_ExpeditionLockout: case ServerOP_ExpeditionLockoutDuration: case ServerOP_ExpeditionLockState: - case ServerOP_ExpeditionMemberChange: - case ServerOP_ExpeditionMemberSwap: - case ServerOP_ExpeditionMemberStatus: - case ServerOP_ExpeditionMembersRemoved: case ServerOP_ExpeditionReplayOnJoin: - case ServerOP_ExpeditionGetOnlineMembers: case ServerOP_ExpeditionDzAddPlayer: case ServerOP_ExpeditionDzMakeLeader: - case ServerOP_ExpeditionDzCompass: - case ServerOP_ExpeditionDzSafeReturn: - case ServerOP_ExpeditionDzZoneIn: - case ServerOP_ExpeditionDzDuration: case ServerOP_ExpeditionCharacterLockout: - case ServerOP_ExpeditionExpireWarning: { Expedition::HandleWorldMessage(pack); break; } - case ServerOP_DzCharacterChange: - case ServerOP_DzRemoveAllCharacters: + case ServerOP_DzCreated: + case ServerOP_DzDeleted: + case ServerOP_DzAddRemoveMember: + case ServerOP_DzSwapMembers: + case ServerOP_DzRemoveAllMembers: + case ServerOP_DzDurationUpdate: + case ServerOP_DzGetMemberStatuses: + case ServerOP_DzSetCompass: + case ServerOP_DzSetSafeReturn: + case ServerOP_DzSetZoneIn: + case ServerOP_DzUpdateMemberStatus: + case ServerOP_DzLeaderChanged: + case ServerOP_DzExpireWarning: { DynamicZone::HandleWorldMessage(pack); break; } + case ServerOP_SharedTaskAcceptNewTask: + case ServerOP_SharedTaskUpdate: + case ServerOP_SharedTaskAttemptRemove: + case ServerOP_SharedTaskMemberlist: + case ServerOP_SharedTaskMemberChange: + case ServerOP_SharedTaskInvitePlayer: + case ServerOP_SharedTaskPurgeAllCommand: + { + SharedTaskZoneMessaging::HandleWorldMessage(pack); + break; + } default: { - std::cout << " Unknown ZSopcode:" << (int)pack->opcode; - std::cout << " size:" << pack->size << std::endl; + LogInfo("[HandleMessage] Unknown ZS Opcode [{}] size [{}]", (int)pack->opcode, pack->size); + +// std::cout << " Unknown ZSopcode:" << (int)pack->opcode; +// std::cout << " size:" << pack->size << std::endl; break; } } @@ -2940,7 +3262,7 @@ bool WorldServer::SendChannelMessage(Client* from, const char* to, uint8 chan_nu if (from == 0) { strcpy(scm->from, "ZServer"); - scm->fromadmin = 0; + scm->fromadmin = AccountStatus::Player; } else { strcpy(scm->from, from->GetName()); @@ -2975,7 +3297,13 @@ bool WorldServer::SendEmoteMessage(const char* to, uint32 to_guilddbid, uint32 t vsnprintf(buffer, sizeof(buffer) - 1, message, argptr); va_end(argptr); - return SendEmoteMessage(to, to_guilddbid, 0, type, buffer); + return SendEmoteMessage( + to, + to_guilddbid, + AccountStatus::Player, + type, + buffer + ); } bool WorldServer::SendEmoteMessage(const char* to, uint32 to_guilddbid, int16 to_minstatus, uint32 type, const char* message, ...) { @@ -2987,7 +3315,12 @@ bool WorldServer::SendEmoteMessage(const char* to, uint32 to_guilddbid, int16 to va_end(argptr); if (!Connected() && to == 0) { - entity_list.MessageStatus(to_guilddbid, to_minstatus, type, buffer); + entity_list.MessageStatus( + to_guilddbid, + to_minstatus, + type, + buffer + ); return false; } @@ -3083,13 +3416,16 @@ void WorldServer::HandleReloadTasks(ServerPacket *pack) case RELOADTASKS: entity_list.SaveAllClientsTaskState(); + // TODO: Reload at the world level for shared tasks + if (rts->Parameter == 0) { Log(Logs::General, Logs::Tasks, "[GLOBALLOAD] Reload ALL tasks"); safe_delete(task_manager); task_manager = new TaskManager; task_manager->LoadTasks(); - if (zone) + if (zone) { task_manager->LoadProximities(zone->GetZoneID()); + } entity_list.ReloadAllClientsTaskState(); } else { @@ -3301,3 +3637,14 @@ void WorldServer::OnKeepAlive(EQ::Timer *t) ServerPacket pack(ServerOP_KeepAlive, 0); SendPacket(&pack); } + +ZoneEventScheduler *WorldServer::GetScheduler() const +{ + return m_zone_scheduler; +} + +void WorldServer::SetScheduler(ZoneEventScheduler *scheduler) +{ + WorldServer::m_zone_scheduler = scheduler; +} + diff --git a/zone/worldserver.h b/zone/worldserver.h index 1eee0b948..bcccb67b7 100644 --- a/zone/worldserver.h +++ b/zone/worldserver.h @@ -20,6 +20,7 @@ #include "../common/eq_packet_structs.h" #include "../common/net/servertalk_client_connection.h" +#include "zone_event_scheduler.h" class ServerPacket; class EQApplicationPacket; @@ -76,6 +77,11 @@ private: std::unique_ptr m_connection; std::unique_ptr m_keepalive; + + ZoneEventScheduler *m_zone_scheduler; +public: + ZoneEventScheduler *GetScheduler() const; + void SetScheduler(ZoneEventScheduler *scheduler); }; #endif diff --git a/zone/zone.cpp b/zone/zone.cpp index 5fd0d5706..2dffad339 100755 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -150,10 +150,18 @@ bool Zone::Bootup(uint32 iZoneID, uint32 iInstanceID, bool iStaticZone) { } LogInfo("---- Zone server [{}], listening on port:[{}] ----", zonename, ZoneConfig::get()->ZonePort); - LogInfo("Zone Bootup: [{}] ([{}]: [{}])", zonename, iZoneID, iInstanceID); + LogInfo("Zone Bootup: [{}] [{}] ([{}]: [{}])", + (iStaticZone) ? "Static" : "Dynamic", zonename, iZoneID, iInstanceID); parse->Init(); UpdateWindowTitle(nullptr); - zone->GetTimeSync(); + + // Dynamic zones need to Sync here. + // Static zones sync when they connect in worldserver.cpp. + // Static zones cannot sync here as request is ignored by worldserver. + if (!iStaticZone) + { + zone->GetTimeSync(); + } zone->RequestUCSServerStatus(); @@ -198,24 +206,27 @@ bool Zone::LoadZoneObjects() if (!shortname) continue; - Door d; - memset(&d, 0, sizeof(d)); + // todo: clean up duplicate code with command_object + auto d = DoorsRepository::NewEntity(); - strn0cpy(d.zone_name, shortname, sizeof(d.zone_name)); - d.db_id = 1000000000 + atoi(row[0]); // Out of range of normal use for doors.id - d.door_id = -1; // Client doesn't care if these are all the same door_id + d.zone = shortname; + d.id = 1000000000 + atoi(row[0]); // Out of range of normal use for doors.id + d.doorid = -1; // Client doesn't care if these are all the same door_id d.pos_x = atof(row[2]); // xpos d.pos_y = atof(row[3]); // ypos d.pos_z = atof(row[4]); // zpos d.heading = atof(row[5]); // heading - strn0cpy(d.door_name, row[8], sizeof(d.door_name)); // objectname - // Strip trailing "_ACTORDEF" if present. Client won't accept it for doors. - int len = strlen(d.door_name); - if ((len > 9) && (memcmp(&d.door_name[len - 9], "_ACTORDEF", 10) == 0)) - d.door_name[len - 9] = '\0'; + d.name = row[8]; // objectname - memcpy(d.dest_zone, "NONE", 5); + // Strip trailing "_ACTORDEF" if present. Client won't accept it for doors. + int pos = d.name.size() - strlen("_ACTORDEF"); + if (pos > 0 && d.name.compare(pos, std::string::npos, "_ACTORDEF") == 0) + { + d.name.erase(pos); + } + + d.dest_zone = "NONE"; if ((d.size = atoi(row[11])) == 0) // unknown08 = optional size percentage d.size = 100; @@ -233,7 +244,7 @@ bool Zone::LoadZoneObjects() d.incline = atoi(row[13]); // unknown20 = optional model incline value d.client_version_mask = 0xFFFFFFFF; // We should load the mask from the zone. - auto door = new Doors(&d); + auto door = new Doors(d); entity_list.AddDoor(door); } @@ -361,7 +372,8 @@ void Zone::DumpMerchantList(uint32 npcid) { int Zone::SaveTempItem(uint32 merchantid, uint32 npcid, uint32 item, int32 charges, bool sold) { - LogInventory("Transaction of [{}] [{}]", charges, item); + LogInventory("[{}] [{}] charges of [{}]", ((sold) ? "Sold" : "Bought"), + charges, item); //DumpMerchantList(npcid); // Iterate past main items. // If the item being transacted is in this list, return 0; @@ -419,12 +431,10 @@ int Zone::SaveTempItem(uint32 merchantid, uint32 npcid, uint32 item, int32 charg if (!ml.origslot) { ml.origslot = ml.slot; } - - if (charges > 0) { + if (ml.charges > 0) { database.SaveMerchantTemp(npcid, ml.origslot, item, ml.charges); tmp_merlist.push_back(ml); - } - else { + } else { database.DeleteMerchantTemp(npcid, ml.origslot); } } @@ -535,6 +545,10 @@ void Zone::LoadTempMerchantData() ) ); + if (!results.Success() || results.RowCount() == 0) { + return; + } + std::vector npc_ids; for (auto row = results.begin(); row != results.end(); ++row) { npc_ids.push_back(row[0]); @@ -909,29 +923,19 @@ void Zone::LoadZoneDoors(const char* zone, int16 version) { LogInfo("Loading doors for [{}] ", zone); - uint32 maxid; - int32 count = content_db.GetDoorsCount(&maxid, zone, version); - if(count < 1) { + auto door_entries = content_db.LoadDoors(zone, version); + if (door_entries.empty()) + { LogInfo("No doors loaded"); return; } - auto dlist = new Door[count]; - - if(!content_db.LoadDoors(count, dlist, zone, version)) { - LogError("Failed to load doors"); - delete[] dlist; - return; - } - - int r; - Door *d = dlist; - for(r = 0; r < count; r++, d++) { - auto newdoor = new Doors(d); + for (const auto& entry : door_entries) + { + auto newdoor = new Doors(entry); entity_list.AddDoor(newdoor); - Log(Logs::Detail, Logs::Doors, "Door Add to Entity List, index: %u db id: %u, door_id %u", r, dlist[r].db_id, dlist[r].door_id); + LogDoorsDetail("Door added to entity list, db id: [{}], door_id: [{}]", entry.id, entry.doorid); } - delete[] dlist; } Zone::Zone(uint32 in_zoneid, uint32 in_instanceid, const char* in_short_name) @@ -941,8 +945,7 @@ Zone::Zone(uint32 in_zoneid, uint32 in_instanceid, const char* in_short_name) spawn2_timer(1000), hot_reload_timer(1000), qglobal_purge_timer(30000), - hotzone_timer(120000), - m_SafePoint(0.0f,0.0f,0.0f), + m_SafePoint(0.0f,0.0f,0.0f,0.0f), m_Graveyard(0.0f,0.0f,0.0f,0.0f) { zoneid = in_zoneid; @@ -1186,6 +1189,9 @@ bool Zone::Init(bool iStaticZone) { petition_list.ClearPetitions(); petition_list.ReadDatabase(); + LogInfo("Loading dynamic zones"); + DynamicZone::CacheAllFromDatabase(); + LogInfo("Loading active Expeditions"); Expedition::CacheAllFromDatabase(); @@ -1249,14 +1255,13 @@ void Zone::ReloadStaticData() { ); } // if that fails, try the file name, then load defaults - content_service.SetExpansionContext(); + content_service.SetExpansionContext()->ReloadContentFlags(); - ZoneStore::LoadContentFlags(); LogInfo("Zone Static Data Reloaded"); } -bool Zone::LoadZoneCFG(const char* filename, uint16 instance_id) +bool Zone::LoadZoneCFG(const char* filename, uint16 instance_version) { memset(&newzone_data, 0, sizeof(NewZone_Struct)); @@ -1264,7 +1269,7 @@ bool Zone::LoadZoneCFG(const char* filename, uint16 instance_id) if (!content_db.GetZoneCFG( ZoneID(filename), - instance_id, + instance_version, &newzone_data, can_bind, can_combat, @@ -1279,7 +1284,7 @@ bool Zone::LoadZoneCFG(const char* filename, uint16 instance_id) &map_name )) { // If loading a non-zero instance failed, try loading the default - if (instance_id != 0) { + if (instance_version != 0) { safe_delete_array(map_name); if (!content_db.GetZoneCFG( ZoneID(filename), @@ -1313,7 +1318,7 @@ bool Zone::LoadZoneCFG(const char* filename, uint16 instance_id) GetShortName(), GetLongName(), GetInstanceVersion(), - instance_id + instance_version ); return true; @@ -1493,16 +1498,14 @@ bool Zone::Process() { { if(Instance_Timer->Check()) { - // if this is a dynamic zone instance notify system associated with it - auto expedition = Expedition::FindCachedExpeditionByZoneInstance(GetZoneID(), GetInstanceID()); - if (expedition) + auto dz = GetDynamicZone(); + if (dz) { - expedition->RemoveAllMembers(false); // entity list will teleport clients out immediately + dz->RemoveAllMembers(); // entity list will teleport clients out immediately } // instance shutting down, move corpses to graveyard or non-instanced zone at same coords entity_list.MovePlayerCorpsesToGraveyard(true); - entity_list.GateAllClientsToSafeReturn(); database.DeleteInstance(GetInstanceID()); Instance_Shutdown_Timer = new Timer(20000); //20 seconds @@ -1582,8 +1585,6 @@ bool Zone::Process() { } } - if(hotzone_timer.Check()) { UpdateHotzone(); } - mMovementManager->Process(); return true; @@ -1817,7 +1818,8 @@ void Zone::Repop(uint32 delay) void Zone::GetTimeSync() { - if (worldserver.Connected() && !zone_has_current_time) { + if (!zone_has_current_time) { + LogInfo("Requesting world time"); auto pack = new ServerPacket(ServerOP_GetWorldTime, 1); worldserver.SendPacket(pack); safe_delete(pack); @@ -1915,7 +1917,11 @@ ZonePoint* Zone::GetClosestZonePoint(const glm::vec3& location, uint32 to, Clien // this shouldn't open up any exploits since those situations are detected later on if ((zone->HasWaterMap() && !zone->watermap->InZoneLine(glm::vec3(client->GetPosition()))) || (!zone->HasWaterMap() && closest_dist > 400.0f && closest_dist < max_distance2)) { - //TODO cheat detection + if (client) { + if (!client->cheat_manager.GetExemptStatus(Port)) { + client->cheat_manager.CheatDetected(MQZoneUnknownDest, location); + } + } LogInfo("WARNING: Closest zone point for zone id [{}] is [{}], you might need to update your zone_points table if you dont arrive at the right spot", to, closest_dist); LogInfo(". [{}]", to_string(location).c_str()); } @@ -2414,10 +2420,9 @@ void Zone::LoadAlternateCurrencies() return; } - for (auto row = results.begin(); row != results.end(); ++row) - { - current_currency.id = atoi(row[0]); - current_currency.item_id = atoi(row[1]); + for (auto row : results) { + current_currency.id = std::stoul(row[0]); + current_currency.item_id = std::stoul(row[1]); AlternateCurrencies.push_back(current_currency); } @@ -2602,19 +2607,9 @@ uint32 Zone::GetSpawnKillCount(uint32 in_spawnid) { return 0; } -void Zone::UpdateHotzone() +void Zone::SetIsHotzone(bool is_hotzone) { - std::string query = StringFormat("SELECT hotzone FROM zone WHERE short_name = '%s'", GetShortName()); - auto results = content_db.QueryDatabase(query); - if (!results.Success()) - return; - - if (results.RowCount() == 0) - return; - - auto row = results.begin(); - - is_hotzone = atoi(row[0]) == 0 ? false: true; + Zone::is_hotzone = is_hotzone; } void Zone::RequestUCSServerStatus() { @@ -2736,13 +2731,44 @@ DynamicZone* Zone::GetDynamicZone() return nullptr; } - auto expedition = Expedition::FindCachedExpeditionByZoneInstance(GetZoneID(), GetInstanceID()); - if (expedition) + // todo: cache dynamic zone id on zone later for faster lookup + for (const auto& dz_iter : zone->dynamic_zone_cache) { - return &expedition->GetDynamicZone(); + if (dz_iter.second->IsSameDz(GetZoneID(), GetInstanceID())) + { + return dz_iter.second.get(); + } } - // todo: tasks, missions, and quests with an associated dz for this instance id - return nullptr; } + +uint32 Zone::GetCurrencyID(uint32 item_id) +{ + if (!item_id) { + return 0; + } + + for (const auto& alternate_currency : AlternateCurrencies) { + if (item_id == alternate_currency.item_id) { + return alternate_currency.id; + } + } + + return 0; +} + +uint32 Zone::GetCurrencyItemID(uint32 currency_id) +{ + if (!currency_id) { + return 0; + } + + for (const auto& alternate_currency : AlternateCurrencies) { + if (currency_id == alternate_currency.id) { + return alternate_currency.item_id; + } + } + + return 0; +} diff --git a/zone/zone.h b/zone/zone.h index e02bfd8c2..23262778d 100755 --- a/zone/zone.h +++ b/zone/zone.h @@ -134,7 +134,7 @@ public: bool IsUCSServerAvailable() { return m_ucss_available; } bool IsZone(uint32 zone_id, uint16 instance_id) const; bool LoadGroundSpawns(); - bool LoadZoneCFG(const char *filename, uint16 instance_id); + bool LoadZoneCFG(const char *filename, uint16 instance_version); bool LoadZoneObjects(); bool Process(); bool SaveZoneCFG(); @@ -167,7 +167,7 @@ public: inline const uint32 &graveyard_zoneid() { return pgraveyard_zoneid; } inline const uint32 GetInstanceID() const { return instanceid; } inline const uint32 GetZoneID() const { return zoneid; } - inline glm::vec3 GetSafePoint() { return m_SafePoint; } + inline glm::vec4 GetSafePoint() { return m_SafePoint; } inline glm::vec4 GetGraveyardPoint() { return m_Graveyard; } inline std::vector GetGlobalLootTables(NPC *mob) const { return m_global_loot.GetGlobalLootTables(mob); } inline Timer *GetInstanceTimer() { return Instance_Timer; } @@ -222,6 +222,7 @@ public: std::vector zone_grids; std::vector zone_grid_entries; + std::unordered_map> dynamic_zone_cache; std::unordered_map> expedition_cache; time_t weather_timer; @@ -240,6 +241,9 @@ public: uint32 GetSpawnKillCount(uint32 in_spawnid); uint32 GetTempMerchantQuantity(uint32 NPCID, uint32 Slot); + uint32 GetCurrencyID(uint32 item_id); + uint32 GetCurrencyItemID(uint32 currency_id); + void AddAggroMob() { aggroedmobs++; } void AddAuth(ServerZoneIncomingClient_Struct *szic); void ChangeWeather(); @@ -289,7 +293,6 @@ public: void SpawnConditionChanged(const SpawnCondition &c, int16 old_value); void SpawnStatus(Mob *client); void StartShutdownTimer(uint32 set_time = (RuleI(Zone, AutoShutdownDelay))); - void UpdateHotzone(); void UpdateQGlobal(uint32 qid, QGlobal newGlobal); void weatherSend(Client *client = nullptr); @@ -328,24 +331,29 @@ public: auto message_split = SplitString(message, '\n'); entity_list.MessageStatus( 0, - 80, + AccountStatus::QuestTroupe, LogSys.GetGMSayColorFromCategory(log_category), - "%s", message_split[0].c_str() ); for (size_t iter = 1; iter < message_split.size(); ++iter) { entity_list.MessageStatus( 0, - 80, + AccountStatus::QuestTroupe, LogSys.GetGMSayColorFromCategory(log_category), - "--- %s", - message_split[iter].c_str() + fmt::format( + "--- {}", + message_split[iter] + ).c_str() ); } - } - else { - entity_list.MessageStatus(0, 80, LogSys.GetGMSayColorFromCategory(log_category), "%s", message.c_str()); + } else { + entity_list.MessageStatus( + 0, + AccountStatus::QuestTroupe, + LogSys.GetGMSayColorFromCategory(log_category), + message.c_str() + ); } } @@ -356,6 +364,7 @@ public: */ void mod_init(); void mod_repop(); + void SetIsHotzone(bool is_hotzone); private: bool allow_mercs; @@ -376,7 +385,7 @@ private: char *map_name; char *short_name; char file_name[16]; - glm::vec3 m_SafePoint; + glm::vec4 m_SafePoint; glm::vec4 m_Graveyard; int default_ruleset; int zone_total_blocked_spells; @@ -401,7 +410,6 @@ private: Timer *Weather_Timer; Timer autoshutdown_timer; Timer clientauth_timer; - Timer hotzone_timer; Timer initgrids_timer; Timer qglobal_purge_timer; ZoneSpellsBlocked *blocked_spells; diff --git a/zone/zone_event_scheduler.cpp b/zone/zone_event_scheduler.cpp new file mode 100644 index 000000000..2294ad50e --- /dev/null +++ b/zone/zone_event_scheduler.cpp @@ -0,0 +1,165 @@ +#include "zone_event_scheduler.h" +#include "../common/rulesys.h" +#include + +void ZoneEventScheduler::Process(Zone *zone, WorldContentService *content_service) +{ + std::time_t time = std::time(nullptr); + std::tm *now = std::localtime(&time); + + // once a minute polling + if (m_last_polled_minute != now->tm_min) { + int month = (now->tm_mon + 1); + int year = (now->tm_year + 1900); + + LogSchedulerDetail( + "Polling year [{}] month [{}] day [{}] hour [{}] minute [{}]", + year, + month, + now->tm_mday, + now->tm_hour, + now->tm_min + ); + + // because stored active events could have a reference of time that has been changed since + // the time has been updated, we need to make sure we update internal fields so that + // the scheduler can properly end events if we set a new end date + SyncEventDataWithActiveEvents(); + + // active events + for (auto &e: m_active_events) { + LogSchedulerDetail("Looping active event [{}]", e.description); + + // if event becomes no longer active + if (!ValidateEventReadyToActivate(e)) { + LogSchedulerDetail("Looping active event validated [{}]", e.event_type); + if (e.event_type == ServerEvents::EVENT_TYPE_HOT_ZONE_ACTIVE) { + LogScheduler("Deactivating event [{}] disabling hotzone status", e.description); + if (search_deliminated_string(e.event_data, zone->GetShortName()) != std::string::npos) { + zone->SetIsHotzone(false); + } + RemoveActiveEvent(e); + } + + if (e.event_type == ServerEvents::EVENT_TYPE_RULE_CHANGE) { + LogScheduler("Deactivating event [{}] resetting rules to normal", e.description); + RuleManager::Instance()->LoadRules(m_database, RuleManager::Instance()->GetActiveRuleset(), true); + + // force active events clear and reapply all active events because we reset the entire state + // ideally if we could revert only the state of which was originally set we would only remove one active event + m_active_events.clear(); + } + + if (e.event_type == ServerEvents::EVENT_TYPE_CONTENT_FLAG_CHANGE) { + auto flag_name = e.event_data; + if (!flag_name.empty()) { + LogScheduler("Deactivating event [{}] resetting content flags", e.description); + content_service->ReloadContentFlags(); + } + + // force active events clear and reapply all active events because we reset the entire state + // ideally if we could revert only the state of which was originally set we would only remove one active event + m_active_events.clear(); + } + } + } + + // check for active + for (auto &e: m_events) { + + // discard uninteresting events as its less work to calculate time on events we don't care about + // different processes are interested in different events + if ( + e.event_type != ServerEvents::EVENT_TYPE_HOT_ZONE_ACTIVE && + e.event_type != ServerEvents::EVENT_TYPE_CONTENT_FLAG_CHANGE && + e.event_type != ServerEvents::EVENT_TYPE_RULE_CHANGE + ) { + continue; + } + + // the scheduler as of today manipulates events in memory and is preferred to be that way + // the scheduler changes temporary "state" in the server for a period of time for things such as + // hotzone activation, content flag activation, rule value activation + // when these events expire, the events become untoggled in memory + // there can be support for one-time events that are more suitable to run from worlds scheduler + // such as broadcasts, reloads + if (ValidateEventReadyToActivate(e) && !IsEventActive(e)) { + if (e.event_type == ServerEvents::EVENT_TYPE_HOT_ZONE_ACTIVE) { + if (search_deliminated_string(e.event_data, zone->GetShortName()) != std::string::npos) { + zone->SetIsHotzone(true); + LogScheduler("Activating Event [{}] Enabling zone as hotzone", e.description); + } + m_active_events.push_back(e); + } + + if (e.event_type == ServerEvents::EVENT_TYPE_RULE_CHANGE) { + auto params = SplitString(e.event_data, '='); + auto rule_key = params[0]; + auto rule_value = params[1]; + if (!rule_key.empty() && !rule_value.empty()) { + LogScheduler( + "Activating Event [{}] scheduled rule change, setting rule [{}] to [{}]", + e.description, + rule_key, + rule_value + ); + RuleManager::Instance()->SetRule(rule_key.c_str(), rule_value.c_str(), nullptr, false, true); + } + m_active_events.push_back(e); + } + + if (e.event_type == ServerEvents::EVENT_TYPE_CONTENT_FLAG_CHANGE) { + auto flag_name = e.event_data; + if (!flag_name.empty()) { + LogScheduler( + "Activating Event [{}] scheduled content flag change, setting flag [{}] to enabled", + e.description, + flag_name + ); + + // add new flag entity to stack + auto flags = content_service->GetContentFlags(); + auto f = ContentFlagsRepository::NewEntity(); + f.flag_name = flag_name; + f.enabled = 1; + flags.push_back(f); + + content_service->SetContentFlags(flags); + m_active_events.push_back(e); + } + } + } + } + + m_last_polled_minute = now->tm_min; + } +} + +// because stored active events could have a reference of time that has been changed since +// the time has been updated, we need to make sure we update internal fields so that +// the scheduler can properly end events if we set a new end date +void ZoneEventScheduler::SyncEventDataWithActiveEvents() +{ + for (auto &a: m_active_events) { + for (auto &e: m_events) { + if (e.id == a.id) { + a.description = e.description; + a.event_type = e.event_type; + a.event_data = e.event_data; + a.minute_start = e.minute_start; + a.hour_start = e.hour_start; + a.day_start = e.day_start; + a.month_start = e.month_start; + a.year_start = e.year_start; + a.minute_end = e.minute_end; + a.hour_end = e.hour_end; + a.day_end = e.day_end; + a.month_end = e.month_end; + a.year_end = e.year_end; + a.cron_expression = e.cron_expression; + a.created_at = e.created_at; + a.deleted_at = e.deleted_at; + } + } + } +} diff --git a/zone/zone_event_scheduler.h b/zone/zone_event_scheduler.h new file mode 100644 index 000000000..a2e6c67ad --- /dev/null +++ b/zone/zone_event_scheduler.h @@ -0,0 +1,14 @@ +#ifndef EQEMU_ZONE_EVENT_SCHEDULER_H +#define EQEMU_ZONE_EVENT_SCHEDULER_H + +#include "../common/server_event_scheduler.h" +#include "zone.h" +#include "../common/content/world_content_service.h" + +class ZoneEventScheduler : public ServerEventScheduler { +public: + void Process(Zone *zone, WorldContentService *content_service); + void SyncEventDataWithActiveEvents(); +}; + +#endif //EQEMU_ZONE_EVENT_SCHEDULER_H diff --git a/zone/zone_store.cpp b/zone/zone_store.cpp index 2a1b2da79..52eeace3c 100644 --- a/zone/zone_store.cpp +++ b/zone/zone_store.cpp @@ -80,6 +80,26 @@ const char *ZoneStore::GetZoneName(uint32 zone_id, bool error_unknown) return nullptr; } +/** + * @param zone_id + * @param error_unknown + * @return + */ +const char *ZoneStore::GetZoneLongName(uint32 zone_id, bool error_unknown) +{ + for (auto &z: zones) { + if (z.zoneidnumber == zone_id) { + return z.long_name.c_str(); + } + } + + if (error_unknown) { + return "UNKNOWN"; + } + + return nullptr; +} + /** * @param zone_id * @return @@ -140,54 +160,3 @@ ZoneRepository::Zone ZoneStore::GetZone(const char *in_zone_name) return ZoneRepository::Zone(); } - -/** - * @return - */ -void ZoneStore::LoadContentFlags() -{ - std::vector set_content_flags; - auto content_flags = ContentFlagsRepository::GetWhere(database, "enabled = 1"); - - set_content_flags.reserve(content_flags.size()); - for (auto &flags: content_flags) { - set_content_flags.push_back(flags.flag_name); - } - - LogInfo( - "Enabled content flags [{}]", - implode(", ", set_content_flags) - ); - - content_service.SetContentFlags(set_content_flags); -} - -/** - * Sets the value in the database and proceeds to load content flags into the server context again - * - * @param content_flag_name - * @param enabled - */ -void ZoneStore::SetContentFlag(const std::string &content_flag_name, bool enabled) -{ - auto content_flags = ContentFlagsRepository::GetWhere(database, - fmt::format("flag_name = '{}'", content_flag_name) - ); - - auto content_flag = ContentFlagsRepository::NewEntity(); - if (!content_flags.empty()) { - content_flag = content_flags.front(); - } - - content_flag.enabled = enabled ? 1 : 0; - content_flag.flag_name = content_flag_name; - - if (!content_flags.empty()) { - ContentFlagsRepository::UpdateOne(database, content_flag); - } - else { - ContentFlagsRepository::InsertOne(database, content_flag); - } - - LoadContentFlags(); -} diff --git a/zone/zone_store.h b/zone/zone_store.h index 8513c51fa..7238b0ade 100644 --- a/zone/zone_store.h +++ b/zone/zone_store.h @@ -41,9 +41,7 @@ public: std::string GetZoneName(uint32 zone_id); std::string GetZoneLongName(uint32 zone_id); const char *GetZoneName(uint32 zone_id, bool error_unknown = false); - - static void LoadContentFlags(); - static void SetContentFlag(const std::string& content_flag_name, bool enabled); + const char *GetZoneLongName(uint32 zone_id, bool error_unknown = false); }; extern ZoneStore zone_store; @@ -60,7 +58,13 @@ inline const char *ZoneName(uint32 zone_id, bool error_unknown = false) error_unknown ); } -inline const char *ZoneLongName(uint32 zone_id) { return zone_store.GetZoneLongName(zone_id).c_str(); } +inline const char *ZoneLongName(uint32 zone_id, bool error_unknown = false) +{ + return zone_store.GetZoneLongName( + zone_id, + error_unknown + ); +} inline ZoneRepository::Zone GetZone(uint32 zone_id, int version = 0) { return zone_store.GetZone(zone_id, version); }; inline ZoneRepository::Zone GetZone(const char *in_zone_name) { return zone_store.GetZone(in_zone_name); }; diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index 27f54862b..12bcb378d 100755 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -37,7 +37,6 @@ ZoneDatabase::ZoneDatabase(const char* host, const char* user, const char* passw } void ZoneDatabase::ZDBInitVars() { - memset(door_isopen_array, 0, sizeof(door_isopen_array)); npc_spellseffects_cache = 0; npc_spellseffects_loadtried = 0; max_faction = 0; @@ -62,20 +61,31 @@ ZoneDatabase::~ZoneDatabase() { } } -bool ZoneDatabase::SaveZoneCFG(uint32 zoneid, uint16 instance_id, NewZone_Struct* zd) { - - std::string query = StringFormat("UPDATE zone SET underworld = %f, minclip = %f, " - "maxclip = %f, fog_minclip = %f, fog_maxclip = %f, " - "fog_blue = %i, fog_red = %i, fog_green = %i, " - "sky = %i, ztype = %i, zone_exp_multiplier = %f, " - "safe_x = %f, safe_y = %f, safe_z = %f " - "WHERE zoneidnumber = %i AND version = %i", - zd->underworld, zd->minclip, - zd->maxclip, zd->fog_minclip[0], zd->fog_maxclip[0], - zd->fog_blue[0], zd->fog_red[0], zd->fog_green[0], - zd->sky, zd->ztype, zd->zone_exp_multiplier, - zd->safe_x, zd->safe_y, zd->safe_z, - zoneid, instance_id); +bool ZoneDatabase::SaveZoneCFG(uint32 zoneid, uint16 instance_version, NewZone_Struct* zd) { + std::string query = fmt::format( + "UPDATE zone SET underworld = {:.2f}, minclip = {:.2f}, " + "maxclip = {:.2f}, fog_minclip = {:.2f}, fog_maxclip = {:.2f}, " + "fog_blue = {}, fog_red = {}, fog_green = {}, " + "sky = {}, ztype = {}, zone_exp_multiplier = {:.2f}, " + "safe_x = {:.2f}, safe_y = {:.2f}, safe_z = {:.2f} " + "WHERE zoneidnumber = {} AND version = {}", + zd->underworld, + zd->minclip, + zd->maxclip, + zd->fog_minclip[0], + zd->fog_maxclip[0], + zd->fog_blue[0], + zd->fog_red[0], + zd->fog_green[0], + zd->sky, + zd->ztype, + zd->zone_exp_multiplier, + zd->safe_x, + zd->safe_y, + zd->safe_z, + zoneid, + instance_version + ); auto results = QueryDatabase(query); if (!results.Success()) { return false; @@ -86,7 +96,7 @@ bool ZoneDatabase::SaveZoneCFG(uint32 zoneid, uint16 instance_id, NewZone_Struct bool ZoneDatabase::GetZoneCFG( uint32 zoneid, - uint16 instance_id, + uint16 instance_version, NewZone_Struct *zone_data, bool &can_bind, bool &can_combat, @@ -103,7 +113,7 @@ bool ZoneDatabase::GetZoneCFG( *map_filename = new char[100]; zone_data->zone_id = zoneid; - std::string query = StringFormat( + std::string query = fmt::format( "SELECT " "ztype, " // 0 "fog_red, " // 1 @@ -167,11 +177,13 @@ bool ZoneDatabase::GetZoneCFG( "fast_regen_endurance, " // 59 "npc_max_aggro_dist, " // 60 "max_movement_update_range, " // 61 - "underworld_teleport_index " // 62 - "FROM zone WHERE zoneidnumber = %i AND version = %i %s", + "underworld_teleport_index, " // 62 + "lava_damage, " // 63 + "min_lava_damage " // 64 + "FROM zone WHERE zoneidnumber = {} AND version = {} {}", zoneid, - instance_id, - ContentFilterCriteria::apply().c_str() + instance_version, + ContentFilterCriteria::apply() ); auto results = QueryDatabase(query); if (!results.Success()) { @@ -220,6 +232,8 @@ bool ZoneDatabase::GetZoneCFG( zone_data->FastRegenEndurance = atoi(row[59]); zone_data->NPCAggroMaxDist = atoi(row[60]); zone_data->underworld_teleport_index = atoi(row[62]); + zone_data->LavaDamage = atoi(row[63]); + zone_data->MinLavaDamage = atoi(row[64]); int bindable = 0; bindable = atoi(row[31]); @@ -622,23 +636,6 @@ bool ZoneDatabase::SetSpecialAttkFlag(uint8 id, const char* flag) { return results.RowsAffected() != 0; } -bool ZoneDatabase::DoorIsOpen(uint8 door_id,const char* zone_name) -{ - if(door_isopen_array[door_id] == 0) { - SetDoorPlace(1,door_id,zone_name); - return false; - } - else { - SetDoorPlace(0,door_id,zone_name); - return true; - } -} - -void ZoneDatabase::SetDoorPlace(uint8 value,uint8 door_id,const char* zone_name) -{ - door_isopen_array[door_id] = value; -} - // Load child objects for a world container (i.e., forge, bag dropped to ground, etc) void ZoneDatabase::LoadWorldContainer(uint32 parentid, EQ::ItemInstance* container) { @@ -1263,7 +1260,7 @@ bool ZoneDatabase::LoadCharacterLanguages(uint32 character_id, PlayerProfile_Str } bool ZoneDatabase::LoadCharacterLeadershipAA(uint32 character_id, PlayerProfile_Struct* pp){ - std::string query = StringFormat("SELECT slot, rank FROM character_leadership_abilities WHERE `id` = %u", character_id); + std::string query = StringFormat("SELECT slot, `rank` FROM character_leadership_abilities WHERE `id` = %u", character_id); auto results = database.QueryDatabase(query); uint32 slot = 0; for (auto row = results.begin(); row != results.end(); ++row) { slot = atoi(row[0]); @@ -1470,7 +1467,7 @@ bool ZoneDatabase::LoadCharacterBindPoint(uint32 character_id, PlayerProfile_Str if (index < 0 || index > 4) continue; - pp->binds[index].zoneId = atoi(row[1]); + pp->binds[index].zone_id = atoi(row[1]); pp->binds[index].instance_id = atoi(row[2]); pp->binds[index].x = atoi(row[3]); pp->binds[index].y = atoi(row[4]); @@ -1493,10 +1490,10 @@ bool ZoneDatabase::SaveCharacterBindPoint(uint32 character_id, const BindStruct std::string query = StringFormat("REPLACE INTO `character_bind` (id, zone_id, instance_id, x, y, z, heading, slot) VALUES (%u, " "%u, %u, %f, %f, %f, %f, %i)", - character_id, bind.zoneId, bind.instance_id, bind.x, bind.y, bind.z, bind.heading, bind_num); + character_id, bind.zone_id, bind.instance_id, bind.x, bind.y, bind.z, bind.heading, bind_num); LogDebug("ZoneDatabase::SaveCharacterBindPoint for character ID: [{}] zone_id: [{}] instance_id: [{}] position: [{}] [{}] [{}] [{}] bind_num: [{}]", - character_id, bind.zoneId, bind.instance_id, bind.x, bind.y, bind.z, bind.heading, bind_num); + character_id, bind.zone_id, bind.instance_id, bind.x, bind.y, bind.z, bind.heading, bind_num); auto results = QueryDatabase(query); if (!results.RowsAffected()) @@ -1565,7 +1562,7 @@ bool ZoneDatabase::SaveCharacterLeadershipAA(uint32 character_id, PlayerProfile_ for (int i = 0; i < MAX_LEADERSHIP_AA_ARRAY; i++){ if (pp->leader_abilities.ranks[i] > 0){ if (first_entry != 1){ - query = StringFormat("REPLACE INTO `character_leadership_abilities` (id, slot, rank) VALUES (%i, %u, %u)", character_id, i, pp->leader_abilities.ranks[i]); + query = StringFormat("REPLACE INTO `character_leadership_abilities` (id, slot, `rank`) VALUES (%i, %u, %u)", character_id, i, pp->leader_abilities.ranks[i]); first_entry = 1; } query = query + StringFormat(", (%i, %u, %u)", character_id, i, pp->leader_abilities.ranks[i]); @@ -1969,7 +1966,7 @@ bool ZoneDatabase::DeleteCharacterLeadershipAAs(uint32 character_id){ } bool ZoneDatabase::DeleteCharacterAAs(uint32 character_id){ - std::string query = StringFormat("DELETE FROM `character_alternate_abilities` WHERE `id` = %u", character_id); + std::string query = StringFormat("DELETE FROM `character_alternate_abilities` WHERE `id` = %u AND `aa_id` NOT IN(SELECT a.first_rank_id FROM aa_ability a WHERE a.grant_only != 0)", character_id); QueryDatabase(query); return true; } @@ -3109,12 +3106,12 @@ void ZoneDatabase::SaveMercBuffs(Merc *merc) { "caston_x, Persistent, caston_y, caston_z, ExtraDIChance) " "VALUES (%u, %u, %u, %u, %u, %d, %u, %u, %u, %u, %u, %u, %u, %i, %u, %i, %i, %i);", merc->GetMercID(), buffs[buffCount].spellid, buffs[buffCount].casterlevel, - spells[buffs[buffCount].spellid].buffdurationformula, buffs[buffCount].ticsremaining, + spells[buffs[buffCount].spellid].buff_duration_formula, buffs[buffCount].ticsremaining, CalculatePoisonCounters(buffs[buffCount].spellid) > 0 ? buffs[buffCount].counters : 0, CalculateDiseaseCounters(buffs[buffCount].spellid) > 0 ? buffs[buffCount].counters : 0, CalculateCurseCounters(buffs[buffCount].spellid) > 0 ? buffs[buffCount].counters : 0, CalculateCorruptionCounters(buffs[buffCount].spellid) > 0 ? buffs[buffCount].counters : 0, - buffs[buffCount].numhits, buffs[buffCount].melee_rune, buffs[buffCount].magic_rune, + buffs[buffCount].hit_number, buffs[buffCount].melee_rune, buffs[buffCount].magic_rune, buffs[buffCount].dot_rune, buffs[buffCount].caston_x, IsPersistent, buffs[buffCount].caston_y, buffs[buffCount].caston_z, buffs[buffCount].ExtraDIChance); results = database.QueryDatabase(query); @@ -3163,7 +3160,7 @@ void ZoneDatabase::LoadMercBuffs(Merc *merc) { if(CalculateCorruptionCounters(buffs[buffCount].spellid) > 0) buffs[buffCount].counters = atoi(row[7]); - buffs[buffCount].numhits = atoi(row[8]); + buffs[buffCount].hit_number = atoi(row[8]); buffs[buffCount].melee_rune = atoi(row[9]); buffs[buffCount].magic_rune = atoi(row[10]); buffs[buffCount].dot_rune = atoi(row[11]); @@ -3505,28 +3502,140 @@ void ZoneDatabase::UpdateKarma(uint32 acct_id, uint32 amount) QueryDatabase(query); } -void ZoneDatabase::ListAllInstances(Client* client, uint32 charid) +void ZoneDatabase::ListAllInstances(Client* client, uint32 character_id) { - if(!client) + if (!client) { return; + } - std::string query = StringFormat("SELECT instance_list.id, zone, version " - "FROM instance_list JOIN instance_list_player " - "ON instance_list.id = instance_list_player.id " - "WHERE instance_list_player.charid = %lu", - (unsigned long)charid); - auto results = QueryDatabase(query); - if (!results.Success()) - return; + std::string query = fmt::format( + "SELECT instance_list.id, zone, version, start_time, duration, never_expires " + "FROM instance_list JOIN instance_list_player " + "ON instance_list.id = instance_list_player.id " + "WHERE instance_list_player.charid = {}", + character_id + ); + auto results = QueryDatabase(query); + if (!results.Success()) { + return; + } - char name[64]; - database.GetCharName(charid, name); - client->Message(Chat::White, "%s is part of the following instances:", name); + auto character_name = database.GetCharNameByID(character_id); + bool is_same_client = client->CharacterID() == character_id; + if (character_name.empty()) { + client->Message( + Chat::White, + fmt::format( + "Character ID '{}' does not exist.", + character_id + ).c_str() + ); + return; + } - for (auto row = results.begin(); row != results.end(); ++row) { - client->Message(Chat::White, "%s - id: %lu, version: %lu", ZoneName(atoi(row[1])), - (unsigned long)atoi(row[0]), (unsigned long)atoi(row[2])); - } + if (!results.RowCount()) { + client->Message( + Chat::White, + fmt::format( + "{} not in any Instances.", + ( + is_same_client ? + "You are" : + fmt::format( + "{} ({}) is", + character_name, + character_id + ) + ) + ).c_str() + ); + return; + } + + client->Message( + Chat::White, + fmt::format( + "{} in the following Instances.", + ( + is_same_client ? + "You are" : + fmt::format( + "{} ({}) is", + character_name, + character_id + ) + ) + ).c_str() + ); + + uint32 instance_count = 0; + for (auto row : results) { + auto instance_id = std::stoul(row[0]); + auto zone_id = std::stoul(row[1]); + auto version = std::stoul(row[2]); + auto start_time = std::stoul(row[3]); + auto duration = std::stoul(row[4]); + auto never_expires = std::stoi(row[5]) ? true : false; + std::string remaining_time_string = "Never"; + timeval time_value; + gettimeofday(&time_value, nullptr); + auto current_time = time_value.tv_sec; + auto remaining_time = ((start_time + duration) - current_time); + if (!never_expires) { + if (remaining_time > 0) { + remaining_time_string = ConvertSecondsToTime(remaining_time); + } else { + remaining_time_string = "Already Expired"; + } + } + + client->Message( + Chat::White, + fmt::format("Instance {} | Zone: {} ({}){}", + instance_id, + ZoneLongName(zone_id), + zone_id, + ( + version ? + fmt::format( + " Version: {}", + version + ) : + "" + ) + ).c_str() + ); + + client->Message( + Chat::White, + fmt::format( + "Instance {} | Expires: {}", + instance_id, + remaining_time_string, + remaining_time + ).c_str() + ); + + instance_count++; + } + + client->Message( + Chat::White, + fmt::format( + "{} in {} Instance{}.", + ( + is_same_client ? + "You are" : + fmt::format( + "{} ({}) is", + character_name, + character_id + ) + ), + instance_count, + instance_count != 1 ? "s" : "" + ).c_str() + ); } void ZoneDatabase::QGlobalPurge() @@ -3590,7 +3699,7 @@ void ZoneDatabase::SaveBuffs(Client *client) { "VALUES('%u', '%u', '%u', '%u', '%s', '%d', '%u', '%u', '%u', '%u', '%u', '%u', " "'%i', '%i', '%i', '%i', '%i')", client->CharacterID(), index, buffs[index].spellid, buffs[index].casterlevel, buffs[index].caster_name, buffs[index].ticsremaining, - buffs[index].counters, buffs[index].numhits, buffs[index].melee_rune, + buffs[index].counters, buffs[index].hit_number, buffs[index].melee_rune, buffs[index].magic_rune, buffs[index].persistant_buff, buffs[index].dot_rune, buffs[index].caston_x, buffs[index].caston_y, buffs[index].caston_z, buffs[index].ExtraDIChance, buffs[index].instrument_mod); @@ -3630,7 +3739,7 @@ void ZoneDatabase::LoadBuffs(Client *client) uint32 caster_level = atoi(row[2]); int32 ticsremaining = atoi(row[4]); uint32 counters = atoul(row[5]); - uint32 numhits = atoul(row[6]); + uint32 hit_number = atoul(row[6]); uint32 melee_rune = atoul(row[7]); uint32 magic_rune = atoul(row[8]); uint8 persistent = atoul(row[9]); @@ -3656,7 +3765,7 @@ void ZoneDatabase::LoadBuffs(Client *client) buffs[slot_id].ticsremaining = ticsremaining; buffs[slot_id].counters = counters; - buffs[slot_id].numhits = numhits; + buffs[slot_id].hit_number = hit_number; buffs[slot_id].melee_rune = melee_rune; buffs[slot_id].magic_rune = magic_rune; buffs[slot_id].persistant_buff = persistent ? true : false; @@ -3666,6 +3775,7 @@ void ZoneDatabase::LoadBuffs(Client *client) buffs[slot_id].caston_z = caston_z; buffs[slot_id].ExtraDIChance = ExtraDIChance; buffs[slot_id].RootBreakChance = 0; + buffs[slot_id].virus_spread_time = 0; buffs[slot_id].UpdateClient = false; buffs[slot_id].instrument_mod = instrument_mod; } @@ -3678,12 +3788,12 @@ void ZoneDatabase::LoadBuffs(Client *client) for (int effectIndex = 0; effectIndex < EFFECT_COUNT; ++effectIndex) { - if (spells[buffs[index].spellid].effectid[effectIndex] == SE_Charm) { + if (spells[buffs[index].spellid].effect_id[effectIndex] == SE_Charm) { buffs[index].spellid = SPELL_UNKNOWN; break; } - if (spells[buffs[index].spellid].effectid[effectIndex] == SE_Illusion) { + if (spells[buffs[index].spellid].effect_id[effectIndex] == SE_Illusion) { if (buffs[index].persistant_buff) break; @@ -3930,8 +4040,8 @@ bool ZoneDatabase::GetFactionData(FactionMods* fm, uint32 class_mod, uint32 race } fm->base = faction_array[faction_id]->base; - fm->min = faction_array[faction_id]->min; // The lowest your personal earned faction can go - before race/class/diety adjustments. - fm->max = faction_array[faction_id]->max; // The highest your personal earned faction can go - before race/class/diety adjustments. + fm->min = faction_array[faction_id]->min; // The lowest your personal earned faction can go - before race/class/deity adjustments. + fm->max = faction_array[faction_id]->max; // The highest your personal earned faction can go - before race/class/deity adjustments. if(class_mod > 0) { char str[32]; @@ -3994,6 +4104,22 @@ bool ZoneDatabase::GetFactionName(int32 faction_id, char* name, uint32 buflen) { } +std::string ZoneDatabase::GetFactionName(int32 faction_id) +{ + std::string faction_name; + if ( + faction_id <= 0 || + faction_id > static_cast(max_faction) || + !faction_array[faction_id] + ) { + return faction_name; + } + + faction_name = faction_array[faction_id]->name; + + return faction_name; +} + //o-------------------------------------------------------------- //| Name: GetNPCFactionList; Dec. 16, 2001 //o-------------------------------------------------------------- @@ -4131,7 +4257,7 @@ bool ZoneDatabase::LoadFactionData() LogInfo("Unable to load Faction Base data..."); } - // load race, class and diety modifiers + // load race, class and deity modifiers query = fmt::format("SELECT `faction_id`, `mod`, `mod_name` FROM `faction_list_mod` WHERE `faction_id` IN ({})", faction_id_criteria); auto modifier_results = QueryDatabase(query); @@ -4893,3 +5019,103 @@ uint32 ZoneDatabase::SaveSaylinkID(const char* saylink_text) return results.LastInsertedID(); } + +double ZoneDatabase::GetAAEXPModifier(uint32 character_id, uint32 zone_id) const { + std::string query = fmt::format( + SQL( + SELECT + `aa_modifier` + FROM + `character_exp_modifiers` + WHERE + `character_id` = {} + AND + (`zone_id` = {} OR `zone_id` = 0) + ORDER BY `zone_id` DESC + LIMIT 1 + ), + character_id, + zone_id + ); + auto results = database.QueryDatabase(query); + for (auto row = results.begin(); row != results.end(); ++row) { + return atof(row[0]); + } + return 1.0f; +} + +double ZoneDatabase::GetEXPModifier(uint32 character_id, uint32 zone_id) const { + std::string query = fmt::format( + SQL( + SELECT + `exp_modifier` + FROM + `character_exp_modifiers` + WHERE + `character_id` = {} + AND + (`zone_id` = {} OR `zone_id` = 0) + ORDER BY `zone_id` DESC + LIMIT 1 + ), + character_id, + zone_id + ); + auto results = database.QueryDatabase(query); + for (auto row = results.begin(); row != results.end(); ++row) { + return atof(row[0]); + } + return 1.0f; +} + +void ZoneDatabase::SetAAEXPModifier(uint32 character_id, uint32 zone_id, double aa_modifier) { + float exp_modifier = GetEXPModifier(character_id, zone_id); + std::string query = fmt::format( + SQL( + REPLACE INTO + `character_exp_modifiers` + VALUES + ({}, {}, {}, {}) + ), + character_id, + zone_id, + aa_modifier, + exp_modifier + ); + database.QueryDatabase(query); +} + +void ZoneDatabase::SetEXPModifier(uint32 character_id, uint32 zone_id, double exp_modifier) { + float aa_modifier = GetAAEXPModifier(character_id, zone_id); + std::string query = fmt::format( + SQL( + REPLACE INTO + `character_exp_modifiers` + VALUES + ({}, {}, {}, {}) + ), + character_id, + zone_id, + aa_modifier, + exp_modifier + ); + database.QueryDatabase(query); +} + +void ZoneDatabase::UpdateGMStatus(uint32 accID, int newStatus) +{ + if (accID) { + std::string query = fmt::format( + SQL( + UPDATE + `account` + SET `status` = {} + WHERE + `id` = {} + ), + newStatus, + accID + ); + database.QueryDatabase(query); + } +} diff --git a/zone/zonedb.h b/zone/zonedb.h index d2fae4232..e12a0ed19 100644 --- a/zone/zonedb.h +++ b/zone/zonedb.h @@ -10,11 +10,14 @@ #include "../common/eqemu_logsys.h" #include "aa_ability.h" #include "event_codes.h" +#include "../common/repositories/doors_repository.h" #ifdef BOTS #include "bot_database.h" #endif +#define WOLF 42 + class Client; class Corpse; class Merc; @@ -69,9 +72,9 @@ struct DBnpcspellseffects_entries_Struct { int16 spelleffectid; uint8 minlevel; uint8 maxlevel; - int32 base; + int32 base_value; int32 limit; - int32 max; + int32 max_value; }; #pragma pack() @@ -243,6 +246,38 @@ struct ClientMercEntry { uint32 npcid; }; +namespace BeastlordPetData { + struct PetStruct { + uint16 race_id = WOLF; + uint8 texture = 0; + uint8 helm_texture = 0; + uint8 gender = 2; + float size_modifier = 1.0f; + uint8 face = 0; + }; +} + +namespace NPCSpawnTypes { + enum : uint8 { + CreateNewSpawn, + AddNewSpawngroup, + UpdateAppearance, + RemoveSpawn, + DeleteSpawn, + AddSpawnFromSpawngroup, + CreateNewNPC + }; +} + +namespace RaidLootTypes { + enum : uint32 { + RaidLeader = 1, + GroupLeader, + Selected, + All + }; +} + class ZoneDatabase : public SharedDatabase { typedef std::list ItemList; public: @@ -276,6 +311,9 @@ public: void DeleteBuyLines(uint32 CharID); void UpdateBuyLine(uint32 CharID, uint32 BuySlot, uint32 Quantity); + + void UpdateGMStatus(uint32 accID, int newStatus); + /** ************************************************ * Character @@ -319,7 +357,7 @@ public: bool SaveCharacterAA(uint32 character_id, uint32 aa_id, uint32 current_level, uint32 charges); bool SaveCharacterBandolier(uint32 character_id, uint8 bandolier_id, uint8 bandolier_slot, uint32 item_id, uint32 icon, const char* bandolier_name); - bool SaveCharacterBindPoint(uint32 character_id, const BindStruct &bind, uint32 bind_num); + bool SaveCharacterBindPoint(uint32 character_id, const BindStruct &bind, uint32 bind_number); bool SaveCharacterCurrency(uint32 character_id, PlayerProfile_Struct* pp); bool SaveCharacterData(uint32 character_id, uint32 account_id, PlayerProfile_Struct* pp, ExtendedProfile_Struct* m_epp); bool SaveCharacterDisc(uint32 character_id, uint32 slot_id, uint32 disc_id); @@ -331,6 +369,11 @@ public: bool SaveCharacterSkill(uint32 character_id, uint32 skill_id, uint32 value); bool SaveCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool SaveCharacterTribute(uint32 character_id, PlayerProfile_Struct* pp); + + double GetAAEXPModifier(uint32 character_id, uint32 zone_id) const; + double GetEXPModifier(uint32 character_id, uint32 zone_id) const; + void SetAAEXPModifier(uint32 character_id, uint32 zone_id, double aa_modifier); + void SetEXPModifier(uint32 character_id, uint32 zone_id, double exp_modifier); /* Character Inventory */ bool NoRentExpired(const char* name); @@ -380,10 +423,12 @@ public: /* Faction */ bool GetNPCFactionList(uint32 npcfaction_id, int32* faction_id, int32* value, uint8* temp, int32* primary_faction = 0); bool GetFactionData(FactionMods* fd, uint32 class_mod, uint32 race_mod, uint32 deity_mod, int32 faction_id); //needed for factions Dec, 16 2001 - bool GetFactionName(int32 faction_id, char* name, uint32 buflen); // needed for factions Dec, 16 2001 + bool GetFactionName(int faction_id, char* name, uint32 buflen); // needed for factions Dec, 16 2001 + std::string GetFactionName(int faction_id); bool GetFactionIdsForNPC(uint32 nfl_id, std::list *faction_list, int32* primary_faction = 0); // improve faction handling bool SetCharacterFactionLevel(uint32 char_id, int32 faction_id, int32 value, uint8 temp, faction_map &val_list); // needed for factions Dec, 16 2001 bool LoadFactionData(); + inline uint32 GetMaxFaction() { return max_faction; } /* AAs New */ bool LoadAlternateAdvancementAbilities(std::unordered_map> &abilities, @@ -393,7 +438,7 @@ public: /* Zone related */ bool GetZoneCFG( uint32 zoneid, - uint16 instance_id, + uint16 instance_version, NewZone_Struct *data, bool &can_bind, bool &can_combat, @@ -406,7 +451,7 @@ public: uint8 &zone_type, int &ruleset, char **map_filename); - bool SaveZoneCFG(uint32 zoneid, uint16 instance_id, NewZone_Struct* zd); + bool SaveZoneCFG(uint32 zoneid, uint16 instance_version, NewZone_Struct* zd); bool LoadStaticZonePoints(LinkedList* zone_point_list,const char* zonename, uint32 version); bool UpdateZoneSafeCoords(const char* zonename, const glm::vec3& location); uint8 GetUseCFGSafeCoords(); @@ -430,6 +475,7 @@ public: void AddWP(Client *c, uint32 gridid, uint32 wpnum, const glm::vec4& position, uint32 pause, uint16 zoneid); uint32 AddWPForSpawn(Client *c, uint32 spawn2id, const glm::vec4& position, uint32 pause, int type1, int type2, uint16 zoneid); void ModifyGrid(Client *c, bool remove, uint32 id, uint8 type = 0, uint8 type2 = 0, uint16 zoneid = 0); + bool GridExistsInZone(uint32 zone_id, uint32 grid_id); void ModifyWP(Client *c, uint32 grid_id, uint32 wp_num, const glm::vec3& location, uint32 script = 0, uint16 zoneid = 0); uint8 GetGridType(uint32 grid, uint32 zoneid); uint8 GetGridType2(uint32 grid, uint16 zoneid); @@ -453,6 +499,7 @@ public: bool GetPetEntry(const char *pet_type, PetRecord *into); bool GetPoweredPetEntry(const char *pet_type, int16 petpower, PetRecord *into); bool GetBasePetItems(int32 equipmentset, uint32 *items); + BeastlordPetData::PetStruct GetBeastlordPetData(uint16 race_id); void AddLootTableToNPC(NPC* npc, uint32 loottable_id, ItemList* itemlist, uint32* copper, uint32* silver, uint32* gold, uint32* plat); void AddLootDropToNPC(NPC* npc, uint32 lootdrop_id, ItemList* item_list, uint8 droplimit, uint8 mindrop); uint32 GetMaxNPCSpellsID(); @@ -501,9 +548,7 @@ public: bool LoadTributes(); /* Doors */ - bool DoorIsOpen(uint8 door_id,const char* zone_name); - void SetDoorPlace(uint8 value,uint8 door_id,const char* zone_name); - bool LoadDoors(int32 door_count, Door *into, const char *zone_name, int16 version); + std::vector LoadDoors(const std::string& zone_name, int16 version); uint32 GetGuildEQID(uint32 guilddbid); void UpdateDoorGuildID(int doorid, int guild_id); int32 GetDoorsCount(uint32* oMaxID, const char *zone_name, int16 version); @@ -531,7 +576,7 @@ public: uint8 RaidGroupCount(uint32 raidid, uint32 groupid); /* Instancing */ - void ListAllInstances(Client* c, uint32 charid); + void ListAllInstances(Client* c, uint32 character_id); /* QGlobals */ void QGlobalPurge(); @@ -571,7 +616,6 @@ protected: std::unordered_set npc_spells_loadtried; DBnpcspellseffects_Struct** npc_spellseffects_cache; bool* npc_spellseffects_loadtried; - uint8 door_isopen_array[255]; }; extern ZoneDatabase database; diff --git a/zone/zonedump.h b/zone/zonedump.h index 09f11b65b..4bbdf47c8 100644 --- a/zone/zonedump.h +++ b/zone/zonedump.h @@ -201,37 +201,6 @@ struct PlayerCorpse_Struct { //std::list items; }; -struct Door { - uint32 db_id; - uint8 door_id; - char zone_name[32]; - char door_name[32]; - float pos_x; - float pos_y; - float pos_z; - float heading; - int incline; - uint8 opentype; - uint32 guild_id; - uint16 lock_pick; - uint32 keyitem; - uint8 nokeyring; - uint8 trigger_door; - uint8 trigger_type; - uint8 disable_timer; - uint32 door_param; - int invert_state; - uint16 size; - char dest_zone[16]; - uint32 dest_instance_id; - float dest_x; - float dest_y; - float dest_z; - float dest_heading; - uint8 is_ldon_door; - uint32 client_version_mask; -}; - #pragma pack() #endif diff --git a/zone/zoning.cpp b/zone/zoning.cpp index 675767754..688d34e2f 100644 --- a/zone/zoning.cpp +++ b/zone/zoning.cpp @@ -78,11 +78,11 @@ void Client::Handle_OP_ZoneChange(const EQApplicationPacket *app) { target_zone_id = zonesummon_id; break; case GateToBindPoint: - target_zone_id = m_pp.binds[0].zoneId; + target_zone_id = m_pp.binds[0].zone_id; target_instance_id = m_pp.binds[0].instance_id; break; case ZoneToBindPoint: - target_zone_id = m_pp.binds[0].zoneId; + target_zone_id = m_pp.binds[0].zone_id; target_instance_id = m_pp.binds[0].instance_id; break; case ZoneSolicited: //we told the client to zone somewhere, so we know where they are going. @@ -99,9 +99,14 @@ void Client::Handle_OP_ZoneChange(const EQApplicationPacket *app) { //unable to find a zone point... is there anything else //that can be a valid un-zolicited zone request? - //Todo cheat detection Message(Chat::Red, "Invalid unsolicited zone request."); LogError("Zoning [{}]: Invalid unsolicited zone request to zone id [{}]", GetName(), target_zone_id); + if (GetBindZoneID() == target_zone_id) { + cheat_manager.CheatDetected(MQGate, glm::vec3(zc->x, zc->y, zc->z)); + } + else { + cheat_manager.CheatDetected(MQZone, glm::vec3(zc->x, zc->y, zc->z)); + } SendZoneCancel(zc); return; } @@ -134,7 +139,12 @@ void Client::Handle_OP_ZoneChange(const EQApplicationPacket *app) { //then we assume this is invalid. if(!zone_point || zone_point->target_zone_id != target_zone_id) { LogError("Zoning [{}]: Invalid unsolicited zone request to zone id [{}]", GetName(), target_zone_id); - //todo cheat detection + if (GetBindZoneID() == target_zone_id) { + cheat_manager.CheatDetected(MQGate, glm::vec3(zc->x, zc->y, zc->z)); + } + else { + cheat_manager.CheatDetected(MQZone, glm::vec3(zc->x, zc->y, zc->z)); + } SendZoneCancel(zc); return; } @@ -170,11 +180,21 @@ void Client::Handle_OP_ZoneChange(const EQApplicationPacket *app) { } /* Load up the Safe Coordinates, restrictions and verify the zone name*/ - float safe_x, safe_y, safe_z; - int16 minstatus = 0; - uint8 minlevel = 0; + float safe_x, safe_y, safe_z, safe_heading; + int16 min_status = AccountStatus::Player; + uint8 min_level = 0; char flag_needed[128]; - if(!content_db.GetSafePoints(target_zone_name, database.GetInstanceVersion(target_instance_id), &safe_x, &safe_y, &safe_z, &minstatus, &minlevel, flag_needed)) { + if(!content_db.GetSafePoints( + target_zone_name, + database.GetInstanceVersion(target_instance_id), + &safe_x, + &safe_y, + &safe_z, + &safe_heading, + &min_status, + &min_level, + flag_needed + )) { //invalid zone... Message(Chat::Red, "Invalid target zone while getting safe points."); LogError("Zoning [{}]: Unable to get safe coordinates for zone [{}]", GetName(), target_zone_name); @@ -182,48 +202,59 @@ void Client::Handle_OP_ZoneChange(const EQApplicationPacket *app) { return; } - char buf[10]; - snprintf(buf, 9, "%d", target_zone_id); - buf[9] = '\0'; - parse->EventPlayer(EVENT_ZONE, this, buf, 0); + std::string export_string = fmt::format("{}", target_zone_id); + parse->EventPlayer(EVENT_ZONE, this, export_string, 0); //handle circumvention of zone restrictions //we need the value when creating the outgoing packet as well. - uint8 ignorerestrictions = zonesummon_ignorerestrictions; + uint8 ignore_restrictions = zonesummon_ignorerestrictions; zonesummon_ignorerestrictions = 0; - float dest_x=0, dest_y=0, dest_z=0, dest_h; - dest_h = GetHeading(); + float target_x = 0, target_y = 0, target_z = 0, target_heading = 0; switch(zone_mode) { case EvacToSafeCoords: case ZoneToSafeCoords: - LogDebug("Zoning [{}] to safe coords ([{}],[{}],[{}]) in [{}] ([{}])", GetName(), safe_x, safe_y, safe_z, target_zone_name, target_zone_id); - dest_x = safe_x; - dest_y = safe_y; - dest_z = safe_z; + LogDebug( + "Zoning [{}] to safe coords ([{}], [{}], [{}], [{}]) in [{}] ([{}])", + GetName(), + safe_x, + safe_y, + safe_z, + safe_heading, + target_zone_name, + target_zone_id + ); + target_x = safe_x; + target_y = safe_y; + target_z = safe_z; + target_heading = safe_heading; break; case GMSummon: - dest_x = m_ZoneSummonLocation.x; - dest_y = m_ZoneSummonLocation.y; - dest_z = m_ZoneSummonLocation.z; - ignorerestrictions = 1; + target_x = m_ZoneSummonLocation.x; + target_y = m_ZoneSummonLocation.y; + target_z = m_ZoneSummonLocation.z; + target_heading = m_ZoneSummonLocation.w; + ignore_restrictions = 1; break; case GateToBindPoint: - dest_x = m_pp.binds[0].x; - dest_y = m_pp.binds[0].y; - dest_z = m_pp.binds[0].z; + target_x = m_pp.binds[0].x; + target_y = m_pp.binds[0].y; + target_z = m_pp.binds[0].z; + target_heading = m_pp.binds[0].heading; break; case ZoneToBindPoint: - dest_x = m_pp.binds[0].x; - dest_y = m_pp.binds[0].y; - dest_z = m_pp.binds[0].z; - ignorerestrictions = 1; //can always get to our bind point? seems exploitable + target_x = m_pp.binds[0].x; + target_y = m_pp.binds[0].y; + target_z = m_pp.binds[0].z; + target_heading = m_pp.binds[0].heading; + ignore_restrictions = 1; //can always get to our bind point? seems exploitable break; case ZoneSolicited: //we told the client to zone somewhere, so we know where they are going. //recycle zonesummon variables - dest_x = m_ZoneSummonLocation.x; - dest_y = m_ZoneSummonLocation.y; - dest_z = m_ZoneSummonLocation.z; + target_x = m_ZoneSummonLocation.x; + target_y = m_ZoneSummonLocation.y; + target_z = m_ZoneSummonLocation.z; + target_heading = m_ZoneSummonLocation.w; break; case ZoneUnsolicited: //client came up with this on its own. //client requested a zoning... what are the cases when this could happen? @@ -234,21 +265,24 @@ void Client::Handle_OP_ZoneChange(const EQApplicationPacket *app) { //999999 is a placeholder for 'same as where they were from' if(zone_point->target_x == 999999) - dest_x = GetX(); + target_x = GetX(); else - dest_x = zone_point->target_x; + target_x = zone_point->target_x; + if(zone_point->target_y == 999999) - dest_y = GetY(); + target_y = GetY(); else - dest_y = zone_point->target_y; + target_y = zone_point->target_y; + if(zone_point->target_z == 999999) - dest_z=GetZ(); + target_z = GetZ(); else - dest_z = zone_point->target_z; + target_z = zone_point->target_z; + if(zone_point->target_heading == 999) - dest_h = GetHeading(); + target_heading = GetHeading(); else - dest_h = zone_point->target_heading; + target_heading = zone_point->target_heading; break; } @@ -256,7 +290,12 @@ void Client::Handle_OP_ZoneChange(const EQApplicationPacket *app) { //for now, there are no other cases... //could not find a valid reason for them to be zoning, stop it. - //todo cheat detection + if (GetBindZoneID() == target_zone_id) { + cheat_manager.CheatDetected(MQGate, glm::vec3(zc->x, zc->y, zc->z)); + } + else { + cheat_manager.CheatDetected(MQZone, glm::vec3(zc->x, zc->y, zc->z)); + } LogError("Zoning [{}]: Invalid unsolicited zone request to zone id [{}]. Not near a zone point", GetName(), target_zone_name); SendZoneCancel(zc); return; @@ -272,12 +311,12 @@ void Client::Handle_OP_ZoneChange(const EQApplicationPacket *app) { //not sure when we would use ZONE_ERROR_NOTREADY //enforce min status and level - if (!ignorerestrictions && (Admin() < minstatus || GetLevel() < minlevel)) + if (!ignore_restrictions && (Admin() < min_status || GetLevel() < min_level)) { myerror = ZONE_ERROR_NOEXPERIENCE; } - if(!ignorerestrictions && flag_needed[0] != '\0') { + if(!ignore_restrictions && flag_needed[0] != '\0') { //the flag needed string is not empty, meaning a flag is required. if(Admin() < minStatusToIgnoreZoneFlags && !HasZoneFlag(target_zone_id)) { @@ -343,7 +382,7 @@ void Client::Handle_OP_ZoneChange(const EQApplicationPacket *app) { if(myerror == 1) { //we have successfully zoned - DoZoneSuccess(zc, target_zone_id, target_instance_id, dest_x, dest_y, dest_z, dest_h, ignorerestrictions); + DoZoneSuccess(zc, target_zone_id, target_instance_id, target_x, target_y, target_z, target_heading, ignore_restrictions); } else { LogError("Zoning [{}]: Rules prevent this char from zoning into [{}]", GetName(), target_zone_name); SendZoneError(zc, myerror); @@ -353,6 +392,7 @@ void Client::Handle_OP_ZoneChange(const EQApplicationPacket *app) { void Client::SendZoneCancel(ZoneChange_Struct *zc) { //effectively zone them right back to where they were //unless we find a better way to stop the zoning process. + cheat_manager.SetExemptStatus(Port, true); EQApplicationPacket *outapp = nullptr; outapp = new EQApplicationPacket(OP_ZoneChange, sizeof(ZoneChange_Struct)); ZoneChange_Struct *zc2 = (ZoneChange_Struct*)outapp->pBuffer; @@ -371,7 +411,7 @@ void Client::SendZoneCancel(ZoneChange_Struct *zc) { void Client::SendZoneError(ZoneChange_Struct *zc, int8 err) { LogError("Zone [{}] is not available because target wasn't found or character insufficent level", zc->zoneID); - + cheat_manager.SetExemptStatus(Port, true); EQApplicationPacket *outapp = nullptr; outapp = new EQApplicationPacket(OP_ZoneChange, sizeof(ZoneChange_Struct)); ZoneChange_Struct *zc2 = (ZoneChange_Struct*)outapp->pBuffer; @@ -463,7 +503,7 @@ void Client::DoZoneSuccess(ZoneChange_Struct *zc, uint16 zone_id, uint32 instanc //reset to unsolicited. zone_mode = ZoneUnsolicited; - m_ZoneSummonLocation = glm::vec3(); + m_ZoneSummonLocation = glm::vec4(); zonesummon_id = 0; zonesummon_ignorerestrictions = 0; } @@ -641,35 +681,32 @@ void Client::ZonePC(uint32 zoneID, uint32 instance_id, float x, float y, float z pShortZoneName = ZoneName(zoneID); content_db.GetZoneLongName(pShortZoneName, &pZoneName); + cheat_manager.SetExemptStatus(Port, true); + if(!pZoneName) { Message(Chat::Red, "Invalid zone number specified"); safe_delete_array(pZoneName); return; } iZoneNameLength = strlen(pZoneName); - glm::vec3 safePoint; - + glm::vec4 zone_safe_point; switch(zm) { case EvacToSafeCoords: case ZoneToSafeCoords: - safePoint = zone->GetSafePoint(); - x = safePoint.x; - y = safePoint.y; - z = safePoint.z; - SetHeading(heading); + zone_safe_point = zone->GetSafePoint(); + x = zone_safe_point.x; + y = zone_safe_point.y; + z = zone_safe_point.z; + heading = zone_safe_point.w; break; case GMSummon: m_Position = glm::vec4(x, y, z, heading); - m_ZoneSummonLocation = glm::vec3(m_Position); - SetHeading(heading); - + m_ZoneSummonLocation = m_Position; zonesummon_id = zoneID; zonesummon_ignorerestrictions = 1; break; case ZoneSolicited: - m_ZoneSummonLocation = glm::vec3(x,y,z); - SetHeading(heading); - + m_ZoneSummonLocation = glm::vec4(x, y, z, heading); zonesummon_id = zoneID; zonesummon_ignorerestrictions = ignorerestrictions; break; @@ -684,23 +721,20 @@ void Client::ZonePC(uint32 zoneID, uint32 instance_id, float x, float y, float z y = m_Position.y = m_pp.binds[0].y; z = m_Position.z = m_pp.binds[0].z; heading = m_pp.binds[0].heading; - zonesummon_ignorerestrictions = 1; LogDebug("Player [{}] has died and will be zoned to bind point in zone: [{}] at LOC x=[{}], y=[{}], z=[{}], heading=[{}]", GetName(), pZoneName, m_pp.binds[0].x, m_pp.binds[0].y, m_pp.binds[0].z, m_pp.binds[0].heading); break; case SummonPC: - m_ZoneSummonLocation = glm::vec3(x, y, z); - m_Position = glm::vec4(m_ZoneSummonLocation, 0.0f); - SetHeading(heading); + m_ZoneSummonLocation = glm::vec4(x, y, z, heading); + m_Position = m_ZoneSummonLocation; break; case Rewind: LogDebug("[{}] has requested a /rewind from [{}], [{}], [{}], to [{}], [{}], [{}] in [{}]", GetName(), m_Position.x, m_Position.y, m_Position.z, m_RewindLocation.x, m_RewindLocation.y, m_RewindLocation.z, zone->GetShortName()); - m_ZoneSummonLocation = glm::vec3(x, y, z); - m_Position = glm::vec4(m_ZoneSummonLocation, 0.0f); - SetHeading(heading); + m_ZoneSummonLocation = glm::vec4(x, y, z, heading); + m_Position = m_ZoneSummonLocation; break; default: LogError("Client::ZonePC() received a reguest to perform an unsupported client zone operation"); @@ -847,7 +881,7 @@ void Client::ZonePC(uint32 zoneID, uint32 instance_id, float x, float y, float z { if(zm != EvacToSafeCoords && zm != ZoneToSafeCoords && zm != ZoneToBindPoint) { - m_ZoneSummonLocation = glm::vec3(); + m_ZoneSummonLocation = glm::vec4(); zonesummon_id = 0; zonesummon_ignorerestrictions = 0; zone_mode = ZoneUnsolicited; @@ -866,61 +900,100 @@ void Client::GoToSafeCoords(uint16 zone_id, uint16 instance_id) { } -void Mob::Gate(uint8 bindnum) { - GoToBind(bindnum); +void Mob::Gate(uint8 bind_number) { + GoToBind(bind_number); if (RuleB(NPC, NPCHealOnGate) && this->IsNPC() && this->GetHPRatio() <= RuleR(NPC, NPCHealOnGateAmount)) { auto HealAmount = (RuleR(NPC, NPCHealOnGateAmount) / 100); SetHP(int(this->GetMaxHP() * HealAmount)); } } -void Client::Gate(uint8 bindnum) { - Mob::Gate(bindnum); +void Client::Gate(uint8 bind_number) { + Mob::Gate(bind_number); } -void NPC::Gate(uint8 bindnum) { +void NPC::Gate(uint8 bind_number) { entity_list.MessageCloseString(this, true, RuleI(Range, SpellMessages), Chat::Spells, GATES, GetCleanName()); - Mob::Gate(bindnum); + Mob::Gate(bind_number); } -void Client::SetBindPoint(int bind_num, int to_zone, int to_instance, const glm::vec3 &location) +void Client::SetBindPoint(int bind_number, int to_zone, int to_instance, const glm::vec3 &location) { - if (bind_num < 0 || bind_num >= 4) - bind_num = 0; + if (bind_number < 0 || bind_number >= 4) + bind_number = 0; if (to_zone == -1) { - m_pp.binds[bind_num].zoneId = zone->GetZoneID(); - m_pp.binds[bind_num].instance_id = - (zone->GetInstanceID() != 0 && zone->IsInstancePersistent()) ? zone->GetInstanceID() : 0; - m_pp.binds[bind_num].x = m_Position.x; - m_pp.binds[bind_num].y = m_Position.y; - m_pp.binds[bind_num].z = m_Position.z; + m_pp.binds[bind_number].zone_id = zone->GetZoneID(); + m_pp.binds[bind_number].instance_id = (zone->GetInstanceID() != 0 && zone->IsInstancePersistent()) ? zone->GetInstanceID() : 0; + m_pp.binds[bind_number].x = m_Position.x; + m_pp.binds[bind_number].y = m_Position.y; + m_pp.binds[bind_number].z = m_Position.z; } else { - m_pp.binds[bind_num].zoneId = to_zone; - m_pp.binds[bind_num].instance_id = to_instance; - m_pp.binds[bind_num].x = location.x; - m_pp.binds[bind_num].y = location.y; - m_pp.binds[bind_num].z = location.z; + m_pp.binds[bind_number].zone_id = to_zone; + m_pp.binds[bind_number].instance_id = to_instance; + m_pp.binds[bind_number].x = location.x; + m_pp.binds[bind_number].y = location.y; + m_pp.binds[bind_number].z = location.z; } - database.SaveCharacterBindPoint(this->CharacterID(), m_pp.binds[bind_num], bind_num); + database.SaveCharacterBindPoint(this->CharacterID(), m_pp.binds[bind_number], bind_number); } -void Client::GoToBind(uint8 bindnum) { +void Client::SetBindPoint2(int bind_number, int to_zone, int to_instance, const glm::vec4 &location) +{ + if (bind_number < 0 || bind_number >= 4) + bind_number = 0; + + if (to_zone == -1) { + m_pp.binds[bind_number].zone_id = zone->GetZoneID(); + m_pp.binds[bind_number].instance_id = (zone->GetInstanceID() != 0 && zone->IsInstancePersistent()) ? zone->GetInstanceID() : 0; + m_pp.binds[bind_number].x = m_Position.x; + m_pp.binds[bind_number].y = m_Position.y; + m_pp.binds[bind_number].z = m_Position.z; + m_pp.binds[bind_number].heading = m_Position.w; + } else { + m_pp.binds[bind_number].zone_id = to_zone; + m_pp.binds[bind_number].instance_id = to_instance; + m_pp.binds[bind_number].x = location.x; + m_pp.binds[bind_number].y = location.y; + m_pp.binds[bind_number].z = location.z; + m_pp.binds[bind_number].heading = location.w; + } + database.SaveCharacterBindPoint(this->CharacterID(), m_pp.binds[bind_number], bind_number); +} + +void Client::GoToBind(uint8 bind_number) { // if the bind number is invalid, use the primary bind - if(bindnum > 4) - bindnum = 0; + if(bind_number > 4) + bind_number = 0; // move the client, which will zone them if needed. // ignore restrictions on the zone request..? - if(bindnum == 0) - MovePC(m_pp.binds[0].zoneId, m_pp.binds[0].instance_id, 0.0f, 0.0f, 0.0f, 0.0f, 1, GateToBindPoint); + if(bind_number == 0) + MovePC( + m_pp.binds[0].zone_id, + m_pp.binds[0].instance_id, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1, + GateToBindPoint + ); else - MovePC(m_pp.binds[bindnum].zoneId, m_pp.binds[bindnum].instance_id, m_pp.binds[bindnum].x, m_pp.binds[bindnum].y, m_pp.binds[bindnum].z, m_pp.binds[bindnum].heading, 1); + MovePC( + m_pp.binds[bind_number].zone_id, + m_pp.binds[bind_number].instance_id, + m_pp.binds[bind_number].x, + m_pp.binds[bind_number].y, + m_pp.binds[bind_number].z, + m_pp.binds[bind_number].heading, + 1 + ); } void Client::GoToDeath() { - MovePC(m_pp.binds[0].zoneId, m_pp.binds[0].instance_id, 0.0f, 0.0f, 0.0f, 0.0f, 1, ZoneToBindPoint); + MovePC(m_pp.binds[0].zone_id, m_pp.binds[0].instance_id, 0.0f, 0.0f, 0.0f, 0.0f, 1, ZoneToBindPoint); } void Client::SetZoneFlag(uint32 zone_id) { @@ -970,39 +1043,72 @@ bool Client::HasZoneFlag(uint32 zone_id) const { void Client::SendZoneFlagInfo(Client *to) const { if(zone_flags.empty()) { - to->Message(Chat::White, "%s has no zone flags.", GetName()); + to->Message( + Chat::White, + fmt::format( + "{} {} no Zone Flags.", + to == this ? "You" : GetName(), + to == this ? "have" : "has" + ).c_str() + ); return; } - std::set::const_iterator cur, end; - cur = zone_flags.begin(); - end = zone_flags.end(); - char empty[1] = { '\0' }; + to->Message( + Chat::White, + fmt::format( + "{} {} the following Flags:", + to == this ? "You" : GetName(), + to == this ? "have" : "has" + ).c_str() + ); - to->Message(Chat::White, "Flags for %s:", GetName()); - - for(; cur != end; ++cur) { - uint32 zoneid = *cur; - - const char *short_name = ZoneName(zoneid); - - char *long_name = nullptr; - content_db.GetZoneLongName(short_name, &long_name); - if(long_name == nullptr) - long_name = empty; - - float safe_x, safe_y, safe_z; - int16 minstatus = 0; - uint8 minlevel = 0; + int flag_count = 0; + for (const auto& zone_id : zone_flags) { + int flag_number = (flag_count + 1); + const char* zone_short_name = ZoneName(zone_id); + std::string zone_long_name = ZoneLongName(zone_id); + float safe_x, safe_y, safe_z, safe_heading; + int16 min_status = AccountStatus::Player; + uint8 min_level = 0; char flag_name[128]; - if(!content_db.GetSafePoints(short_name, 0, &safe_x, &safe_y, &safe_z, &minstatus, &minlevel, flag_name)) { - strcpy(flag_name, "(ERROR GETTING NAME)"); + if(!content_db.GetSafePoints( + zone_short_name, + 0, + &safe_x, + &safe_y, + &safe_z, + &safe_heading, + &min_status, + &min_level, + flag_name + )) { + strcpy(flag_name, "ERROR"); } - to->Message(Chat::White, "Has Flag %s for zone %s (%d,%s)", flag_name, long_name, zoneid, short_name); - if(long_name != empty) - delete[] long_name; + to->Message( + Chat::White, + fmt::format( + "Zone Flag {} | Zone ID: {} Zone Name: {} ({}) Flag Name: {}", + flag_number, + zone_id, + zone_long_name, + zone_short_name, + flag_name + ).c_str() + ); + flag_count++; } + + to->Message( + Chat::White, + fmt::format( + "{} {} {} Zone Flags.", + to == this ? "You" : GetName(), + to == this ? "have" : "has", + flag_count + ).c_str() + ); } bool Client::CanBeInZone() { @@ -1013,22 +1119,32 @@ bool Client::CanBeInZone() { if(Admin() >= RuleI(GM, MinStatusToZoneAnywhere)) return(true); - float safe_x, safe_y, safe_z; - int16 minstatus = 0; - uint8 minlevel = 0; + float safe_x, safe_y, safe_z, safe_heading; + int16 min_status = AccountStatus::Player; + uint8 min_level = 0; char flag_needed[128]; - if(!content_db.GetSafePoints(zone->GetShortName(), zone->GetInstanceVersion(), &safe_x, &safe_y, &safe_z, &minstatus, &minlevel, flag_needed)) { + if(!content_db.GetSafePoints( + zone->GetShortName(), + zone->GetInstanceVersion(), + &safe_x, + &safe_y, + &safe_z, + &safe_heading, + &min_status, + &min_level, + flag_needed + )) { //this should not happen... LogDebug("[CLIENT] Unable to query zone info for ourself [{}]", zone->GetShortName()); return(false); } - if(GetLevel() < minlevel) { - LogDebug("[CLIENT] Character does not meet min level requirement ([{}] < [{}])!", GetLevel(), minlevel); + if(GetLevel() < min_level) { + LogDebug("[CLIENT] Character does not meet min level requirement ([{}] < [{}])!", GetLevel(), min_level); return(false); } - if(Admin() < minstatus) { - LogDebug("[CLIENT] Character does not meet min status requirement ([{}] < [{}])!", Admin(), minstatus); + if(Admin() < min_status) { + LogDebug("[CLIENT] Character does not meet min status requirement ([{}] < [{}])!", Admin(), min_status); return(false); }