From 7decf74505aa965060ff11124bc460ebb498a36b Mon Sep 17 00:00:00 2001 From: Natedog2012 Date: Thu, 15 Jul 2021 19:00:50 -0500 Subject: [PATCH 01/13] Add Rank to lua Spell --- zone/lua_spell.cpp | 8 +++++++- zone/lua_spell.h | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/zone/lua_spell.cpp b/zone/lua_spell.cpp index 18353efaa..c7730efff 100644 --- a/zone/lua_spell.cpp +++ b/zone/lua_spell.cpp @@ -474,6 +474,11 @@ int Lua_Spell::GetDamageShieldType() { return self->DamageShieldType; } +int Lua_Spell::GetRank() { + Lua_Safe_Call_Int(); + return self->rank; +} + luabind::scope lua_register_spell() { return luabind::class_("Spell") .def(luabind::constructor<>()) @@ -561,7 +566,8 @@ luabind::scope lua_register_spell() { .def("MaxDist", &Lua_Spell::GetMaxDist) .def("MaxDistMod", &Lua_Spell::GetMaxDistMod) .def("MinRange", &Lua_Spell::GetMinRange) - .def("DamageShieldType", &Lua_Spell::GetDamageShieldType); + .def("DamageShieldType", &Lua_Spell::GetDamageShieldType) + .def("Rank", &Lua_Spell::GetRank); } #endif diff --git a/zone/lua_spell.h b/zone/lua_spell.h index bfa5a19ed..e2def8a6a 100644 --- a/zone/lua_spell.h +++ b/zone/lua_spell.h @@ -107,6 +107,7 @@ public: float GetMaxDistMod(); float GetMinRange(); int GetDamageShieldType(); + int GetRank(); }; #endif From ddb14187b009a242a2f5706a5fb78d04d793da2a Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Fri, 16 Jul 2021 21:50:46 -0400 Subject: [PATCH 02/13] Add some knobs to our RNG class Also included is an additive lagged fibonacci generator that should is very similar to EQ's. Also added BIASED_INT_DIST in case someone wants to use "bad" int distribution to more closely match EQ as well. An option to set a custom engine (just in case people would like to play with other std engines) is available. There is also support for GCC's SIMD accelerated extension to std random engines. All these options are hidden behind advanced options in CMake since they're rather advanced knobs. --- CMakeLists.txt | 41 ++++++ common/CMakeLists.txt | 1 + common/additive_lagged_fibonacci_engine.h | 147 ++++++++++++++++++++++ common/random.h | 46 +++++-- 4 files changed, 228 insertions(+), 7 deletions(-) create mode 100644 common/additive_lagged_fibonacci_engine.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 526d68099..55d193624 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 084ac9c4f..47edd22e8 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -451,6 +451,7 @@ SET(repositories ) SET(common_headers + additive_lagged_fibonacci_engine.h any.h base_packet.h base_data.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/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; }; } From fe7cb764b2c41707766ac127c5f5ea713cc31258 Mon Sep 17 00:00:00 2001 From: KimLS Date: Thu, 22 Jul 2021 19:03:05 -0700 Subject: [PATCH 03/13] Fix for compile issue when you either don't have openSSL or you're using a version not supported by httplib --- CMakeLists.txt | 3 +++ zone/command.cpp | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 526d68099..b7548f5ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -198,6 +198,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/zone/command.cpp b/zone/command.cpp index d786e505c..a02da9ab1 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -75,8 +75,6 @@ #include "mob_movement_manager.h" #include "npc_scale_manager.h" #include "../common/content/world_content_service.h" - -#define CPPHTTPLIB_OPENSSL_SUPPORT #include "../common/http/httplib.h" extern QueryServ* QServ; From b40140422704c1cd014680884eebd16322e0b35a Mon Sep 17 00:00:00 2001 From: splose Date: Mon, 26 Jul 2021 12:21:06 -0400 Subject: [PATCH 04/13] [Quest API] Add $client->SetGMStatus() (#1465) * add $client->SetGMStatus() * add UpdateAdmin after setting status --- zone/client.cpp | 8 +++++++- zone/client.h | 1 + zone/perl_client.cpp | 17 +++++++++++++++++ zone/zonedb.cpp | 18 ++++++++++++++++++ zone/zonedb.h | 3 +++ 5 files changed, 46 insertions(+), 1 deletion(-) diff --git a/zone/client.cpp b/zone/client.cpp index eb93f136f..c49f36eb7 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -10130,7 +10130,6 @@ void Client::SetAFK(uint8 afk_flag) { 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); @@ -10241,3 +10240,10 @@ void Client::RemoveItem(uint32 item_id, uint32 quantity) } } } + +void Client::SetAdminStatus(int newStatus) { + if (this->Admin() != newStatus) + database.UpdateGMStatus(this->AccountID(), newStatus); + + return; +} diff --git a/zone/client.h b/zone/client.h index 74e2eed54..f3af176c0 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1523,6 +1523,7 @@ public: void LoadAccountFlags(); void SetAccountFlag(std::string flag, std::string val); std::string GetAccountFlag(std::string flag); + void SetAdminStatus(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); diff --git a/zone/perl_client.cpp b/zone/perl_client.cpp index 36c0ee361..7613d624f 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -3413,6 +3413,22 @@ 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; + uint32 accID = THIS->AccountID(); + int newStatus = (int)SvIV(ST(1)); + VALIDATE_THIS_IS_CLIENT; + THIS->SetAdminStatus(newStatus); + THIS->UpdateAdmin(true); + } + XSRETURN_EMPTY; +} + XS(XS_Client_UpdateGroupAAs); /* prototype to pass -Wmissing-prototypes */ XS(XS_Client_UpdateGroupAAs) { dXSARGS; @@ -5706,6 +5722,7 @@ XS(boot_Client) { newXSproto(strcpy(buf, "UntrainDisc"), XS_Client_UntrainDisc, file, "$$;$"); newXSproto(strcpy(buf, "UntrainDiscAll"), XS_Client_UntrainDiscAll, file, "$;$"); newXSproto(strcpy(buf, "UpdateAdmin"), XS_Client_UpdateAdmin, file, "$;$"); + newXSproto(strcpy(buf, "SetGMStatus"), XS_Client_SetGMStatus, file, "$$"); newXSproto(strcpy(buf, "UpdateGroupAAs"), XS_Client_UpdateGroupAAs, file, "$$$"); newXSproto(strcpy(buf, "UpdateLDoNPoints"), XS_Client_UpdateLDoNPoints, file, "$$$"); newXSproto(strcpy(buf, "UpdateTaskActivity"), XS_Client_UpdateTaskActivity, file, "$$$$;$"); diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index 9c493a915..7e195ab14 100755 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -4977,3 +4977,21 @@ void ZoneDatabase::SetEXPModifier(uint32 character_id, uint32 zone_id, double ex ); 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 7f523e4e1..61eb97ae1 100644 --- a/zone/zonedb.h +++ b/zone/zonedb.h @@ -289,6 +289,9 @@ public: void DeleteBuyLines(uint32 CharID); void UpdateBuyLine(uint32 CharID, uint32 BuySlot, uint32 Quantity); + + void UpdateGMStatus(uint32 accID, int newStatus); + /** ************************************************ * Character From 792a3b144311d00ac6c06ef86317010ec95e402b Mon Sep 17 00:00:00 2001 From: E Spause Date: Mon, 26 Jul 2021 13:03:17 -0400 Subject: [PATCH 05/13] Add SetGMStatus to LUA, cleanup unused variable, cleanup naming of new function added to Client class, remove unneeded return on void function. (#1471) * Fix issue #1469 - remove unused variable in perl_client * Add SetGMStatus to LUA, clean up naming in client.cpp to be consistent with the perl/lua naming, remove unneeded return in void function * Delete PERL_CLIENT.ipch --- zone/client.cpp | 4 +--- zone/client.h | 2 +- zone/lua_client.cpp | 8 +++++++- zone/lua_client.h | 1 + zone/perl_client.cpp | 3 +-- 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index c49f36eb7..ea2ea6fdd 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -10241,9 +10241,7 @@ void Client::RemoveItem(uint32 item_id, uint32 quantity) } } -void Client::SetAdminStatus(int newStatus) { +void Client::SetGMStatus(int newStatus) { if (this->Admin() != newStatus) database.UpdateGMStatus(this->AccountID(), newStatus); - - return; } diff --git a/zone/client.h b/zone/client.h index f3af176c0..0e73370c3 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1523,7 +1523,7 @@ public: void LoadAccountFlags(); void SetAccountFlag(std::string flag, std::string val); std::string GetAccountFlag(std::string flag); - void SetAdminStatus(int newStatus); + 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); diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index 233f40622..29699aeff 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -2158,6 +2158,11 @@ void Lua_Client::RemoveItem(uint32 item_id, uint32 quantity) { return self->RemoveItem(item_id, quantity); } +void Lua_Client::SetGMStatus(uint32 newStatus) { + Lua_Safe_Call_Void(); + return self->SetGMStatus(newStatus); +} + luabind::scope lua_register_client() { return luabind::class_("Client") .def(luabind::constructor<>()) @@ -2525,7 +2530,8 @@ luabind::scope lua_register_client() { .def("SendToInstance", (void(Lua_Client::*)(std::string,std::string,uint32,float,float,float,float,std::string,uint32))&Lua_Client::SendToInstance) .def("CountItem", (int(Lua_Client::*)(uint32))&Lua_Client::CountItem) .def("RemoveItem", (void(Lua_Client::*)(uint32))&Lua_Client::RemoveItem) - .def("RemoveItem", (void(Lua_Client::*)(uint32,uint32))&Lua_Client::RemoveItem); + .def("RemoveItem", (void(Lua_Client::*)(uint32,uint32))&Lua_Client::RemoveItem) + .def("SetGMStatus", (void(Lua_Client::*)(int32))& Lua_Client::SetGMStatus); } luabind::scope lua_register_inventory_where() { diff --git a/zone/lua_client.h b/zone/lua_client.h index 51da4b9df..1d44640e7 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -370,6 +370,7 @@ public: 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); diff --git a/zone/perl_client.cpp b/zone/perl_client.cpp index 7613d624f..1fc989bf7 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -3420,10 +3420,9 @@ XS(XS_Client_SetGMStatus) { Perl_croak(aTHX_ "Usage: Client::SetGMStatus(THIS, int newStatus)"); // @categories Script Utility { Client *THIS; - uint32 accID = THIS->AccountID(); int newStatus = (int)SvIV(ST(1)); VALIDATE_THIS_IS_CLIENT; - THIS->SetAdminStatus(newStatus); + THIS->SetGMStatus(newStatus); THIS->UpdateAdmin(true); } XSRETURN_EMPTY; From 5d92d484a16158382cfdff83606ab7cc70ae0836 Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Mon, 26 Jul 2021 22:20:13 -0400 Subject: [PATCH 06/13] Projectiles Update (#1468) Fixed spell projectiles whose angle was not being calculated correctly. Significantly improved all projectile timings. At least up to range of 300 should see more accurate timings for damage/effects occurring upon projectile impact. Will be noticed most significantly for all spell projectiles and for longer range archery. --- zone/special_attacks.cpp | 71 ++++++++++++++++++++++++++++++---------- zone/spell_effects.cpp | 49 +++++++++++++++++++-------- 2 files changed, 89 insertions(+), 31 deletions(-) diff --git a/zone/special_attacks.cpp b/zone/special_attacks.cpp index be3408c81..6c49948d5 100644 --- a/zone/special_attacks.cpp +++ b/zone/special_attacks.cpp @@ -929,11 +929,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 @@ -951,14 +971,12 @@ bool Mob::TryProjectileAttack(Mob *other, const EQ::ItemData *item, EQ::skills:: ProjectileAtk[slot].ammo_slot = 0; ProjectileAtk[slot].skill = skillInUse; - ProjectileAtk[slot].speed_mod = speed_mod; + ProjectileAtk[slot].speed_mod = speed; 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; } @@ -978,23 +996,40 @@ 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()) { @@ -1496,7 +1531,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; diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index e5e447b5b..d20df5ace 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -6995,7 +6995,7 @@ bool Mob::PassCastRestriction(bool UseCastRestriction, int16 value, bool IsDama return false; } -bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id, float speed){ +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. @@ -7020,7 +7020,7 @@ bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id, float speed){ //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; } @@ -7029,11 +7029,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 @@ -7043,34 +7067,33 @@ 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 (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) From 7e85224202e313e0de876d8458ab32ab29daaa18 Mon Sep 17 00:00:00 2001 From: Gangsta <48196367+GangstaEQ@users.noreply.github.com> Date: Mon, 26 Jul 2021 23:03:17 -0700 Subject: [PATCH 07/13] [Merchants] Fix issue where an item purchased with 1 charges actually is bought with 0 charges Co-authored-by: ProducerZekServer --- zone/client_packet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 92b3080c7..41d66705c 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -12997,7 +12997,7 @@ void Client::Handle_OP_ShopPlayerBuy(const EQApplicationPacket *app) int16 charges = 0; if (item->Stackable || tmpmer_used) charges = mp->quantity; - else if ( item->MaxCharges > 1) + else if ( item->MaxCharges >= 1) charges = item->MaxCharges; EQ::ItemInstance* inst = database.CreateItem(item, charges); From fee8772bb6ecfa949f2f88458a433c8191f4073b Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Thu, 29 Jul 2021 18:55:06 -0400 Subject: [PATCH 08/13] Implemented SPA 482 SE_Skill_Base_Damage_Mod (#1474) * Implemented SPA 482 SE_Skill_Base_Damage_Mod Implemented SPA 482 SE_Skill_Base_Damage_Mod Modifies base melee damage by skill Base: pct Limit: skill(-1=ALL), max: none * Update spell_effects.cpp fix to remove unknown spa message --- common/spdat.h | 2 +- zone/bonuses.cpp | 36 ++++++++++++++++++++++++++++++++++++ zone/common.h | 1 + zone/mob.cpp | 3 +++ zone/spell_effects.cpp | 1 + 5 files changed, 42 insertions(+), 1 deletion(-) diff --git a/common/spdat.h b/common/spdat.h index 4dbbc7bbb..aaa2dcc6e 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -825,7 +825,7 @@ typedef enum { //#define SE_Ff_Value_Min 479 // //#define SE_Ff_Value_Max 480 // #define SE_Fc_Cast_Spell_On_Land 481 // Implemented - [FOCUS] Spells cast on target with this Focus Effect will have chance to cause a Spell to be cast if limits met. -//#define SE_Skill_Base_Damage_Mod 482 // +#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 - [FOCUS] modifies incoming spell damage by percent #define SE_Fc_Spell_Damage_Amt_IncomingPC 484 // Implemented - [FOCUS] modifies incoming spell damage by flat amount. Typically adds damage to incoming spells. #define SE_Ff_CasterClass 485 // Implemented - [FOCUS LIMIT] Caster of spell on target with a focus effect that is checked by incoming spells must be specified class. diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index 3f067aee9..437468dcb 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -1177,6 +1177,17 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } + case SE_Skill_Base_Damage_Mod: { + // Bad data or unsupported new skill + if (base2 > EQ::skills::HIGHEST_SKILL) + break; + if (base2 == ALL_SKILLS) + newbon->DamageModifier3[EQ::skills::HIGHEST_SKILL + 1] += base1; + else + newbon->DamageModifier3[base2] += base1; + break; + } + case SE_SlayUndead: { if (newbon->SlayUndead[1] < base1) newbon->SlayUndead[0] = base1; // Rate @@ -2323,6 +2334,19 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } + case SE_Skill_Base_Damage_Mod: + { + // Bad data or unsupported new skill + if (base2 > EQ::skills::HIGHEST_SKILL) + break; + int skill = base2 == ALL_SKILLS ? EQ::skills::HIGHEST_SKILL + 1 : base2; + 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 @@ -4242,6 +4266,18 @@ void Mob::NegateSpellsBonuses(uint16 spell_id) break; } + case SE_Skill_Base_Damage_Mod: + { + for (int e = 0; e < EQ::skills::HIGHEST_SKILL + 1; e++) + { + spellbonuses.DamageModifier3[e] = effect_value; + aabonuses.DamageModifier3[e] = effect_value; + itembonuses.DamageModifier3[e] = effect_value; + } + break; + } + + case SE_MinDamageModifier: { for (int e = 0; e < EQ::skills::HIGHEST_SKILL + 1; e++) diff --git a/zone/common.h b/zone/common.h index 1acb335de..16d762915 100644 --- a/zone/common.h +++ b/zone/common.h @@ -442,6 +442,7 @@ 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 diff --git a/zone/mob.cpp b/zone/mob.cpp index e26c5406c..9ae5be42f 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -4856,6 +4856,9 @@ 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(dmg_mod < -100) dmg_mod = -100; diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index d20df5ace..a5c61fb5d 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -3214,6 +3214,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove case SE_Fc_Cast_Spell_On_Land: case SE_Ff_CasterClass: case SE_Ff_Same_Caster: + case SE_Skill_Base_Damage_Mod: { break; } From a50663e0a4d5bec43731ad39da6647c67081b0dc Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Thu, 29 Jul 2021 18:56:02 -0400 Subject: [PATCH 09/13] Fix SE_TwinCastBlocker to block twinproc (#1476) Improvement to SE_TwinCastBlocker implementation. Will now ensure both spell casted and weapon proced twincasts can be effectively blocked. --- zone/mob.cpp | 3 --- zone/spell_effects.cpp | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/zone/mob.cpp b/zone/mob.cpp index 9ae5be42f..ce966726c 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -3702,9 +3702,6 @@ void Mob::TryTwincast(Mob *caster, Mob *target, uint32 spell_id) if(!IsValidSpell(spell_id)) return; - if (IsEffectInSpell(spell_id, SE_TwinCastBlocker)) - return; - if(IsClient()) { int32 focus = CastToClient()->GetFocusEffect(focusTwincast, spell_id); diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index a5c61fb5d..eb75c7892 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -4812,7 +4812,7 @@ int16 Client::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) break; case SE_FcTwincast: - if (type == focusTwincast) + if (type == focusTwincast && !IsEffectInSpell(spell_id, SE_TwinCastBlocker)) value = base1; break; @@ -5382,7 +5382,7 @@ int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo break; case SE_FcTwincast: - if (type == focusTwincast) + if (type == focusTwincast && !IsEffectInSpell(spell_id, SE_TwinCastBlocker)) value = focus_spell.base[i]; break; From 78b15a0214a0e9dcb21bb2a7c4a2cccadc044715 Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Thu, 29 Jul 2021 19:19:35 -0400 Subject: [PATCH 10/13] Implemented SPA 498 and 499 (extra attack chance effects) (#1475) * Implemented SPA 498 and 499 Implemented SE_AddExtraAttackPct_1h_Primary 498 , gives your double attacks a percent chance to perform an extra attack with 1-handed primary weapon, base: chance, limit: amt attacks max: none SE_AddExtraAttackPct_1h_Secondary 499 gives your double attacks a percent chance to perform an extra attack with 1-handed secondary weapon, base: chance, limit: amt attacks max: none Added limit functionality to similar effect SPA 266 SPA 266 will now be calculated to take highest percent value when applying bonus. (was additive, which does not seem correct based on AA data) * Update attack.cpp code update * Update bonuses.cpp code update * Update spdat.h added commas * Update spell_effects.cpp fix to remove unknown spa message Co-authored-by: Michael Cook (mackal) --- common/spdat.h | 6 +-- zone/attack.cpp | 39 ++++++++++++++--- zone/bonuses.cpp | 89 +++++++++++++++++++++++++++++++++++---- zone/common.h | 4 +- zone/lua_stat_bonuses.cpp | 2 +- zone/merc.cpp | 2 +- zone/spell_effects.cpp | 2 + 7 files changed, 124 insertions(+), 20 deletions(-) diff --git a/common/spdat.h b/common/spdat.h index aaa2dcc6e..c41c22347 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -609,7 +609,7 @@ typedef enum { #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. @@ -841,8 +841,8 @@ typedef enum { //#define SE_Ff_DurationMax 495 // #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 // -//#define SE_AddExtraAttackPct_1h_Secondary 499 // +#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 // //#define SE_Fc_CastTimeAmt 501 // #define SE_Fearstun 502 // implemented - Stun with a max level limit. Normal stun restrictions don't apply. diff --git a/zone/attack.cpp b/zone/attack.cpp index d09271c55..aa6c30bd4 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -5483,13 +5483,40 @@ void Client::DoAttackRounds(Mob *target, int hand, bool IsFromSpell) CheckIncreaseSkill(EQ::skills::SkillDoubleAttack, target, -10); 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[0] + spellbonuses.ExtraAttackChance[0] + + itembonuses.ExtraAttackChance[0]; + if (extraattackchance && zone->random.Roll(extraattackchance)) { + auto extraattackamt = std::max({ aabonuses.ExtraAttackChance[1], spellbonuses.ExtraAttackChance[1], itembonuses.ExtraAttackChance[1] }); + for (int i = 0; i < extraattackamt; i++) { + Attack(target, hand, false, false, IsFromSpell); + } + } + } + else { + auto extraattackchance_primary = aabonuses.ExtraAttackChancePrimary[0] + spellbonuses.ExtraAttackChancePrimary[0] + + itembonuses.ExtraAttackChancePrimary[0]; + if (extraattackchance_primary && zone->random.Roll(extraattackchance_primary)) { + auto extraattackamt_primary = std::max({ aabonuses.ExtraAttackChancePrimary[1], spellbonuses.ExtraAttackChancePrimary[1], itembonuses.ExtraAttackChancePrimary[1] }); + for (int i = 0; i < extraattackamt_primary; i++) { + Attack(target, hand, false, false, IsFromSpell); + } + } + } + } + + if (hand == EQ::invslot::slotSecondary) { + auto extraattackchance_secondary = aabonuses.ExtraAttackChanceSecondary[0] + spellbonuses.ExtraAttackChanceSecondary[0] + + itembonuses.ExtraAttackChanceSecondary[0]; + if (extraattackchance_secondary && zone->random.Roll(extraattackchance_secondary)) { + auto extraattackamt_secondary = std::max({ aabonuses.ExtraAttackChanceSecondary[1], spellbonuses.ExtraAttackChanceSecondary[1], itembonuses.ExtraAttackChanceSecondary[1] }); + for (int i = 0; i < extraattackamt_secondary; i++) { + Attack(target, hand, false, false, IsFromSpell); + } + } } // you can only triple from the main hand diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index 437468dcb..e52e61533 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -860,9 +860,6 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) case SE_MaxBindWound: newbon->MaxBindWound += base1; break; - case SE_ExtraAttackChance: - newbon->ExtraAttackChance += base1; - break; case SE_SeeInvis: newbon->SeeInvis = base1; break; @@ -984,7 +981,6 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) case SE_BlockBehind: newbon->BlockBehind += base1; break; - case SE_StrikeThrough: case SE_StrikeThrough2: newbon->StrikeThrough += base1; @@ -1572,6 +1568,34 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) newbon->Pet_Add_Atk += base1; break; + case SE_ExtraAttackChance: + { + if (newbon->ExtraAttackChance[0] < base1) { + newbon->ExtraAttackChance[0] = base1; + newbon->ExtraAttackChance[1] = base2 ? base2 : 1; + } + break; + } + + case SE_AddExtraAttackPct_1h_Primary: + { + if (newbon->ExtraAttackChancePrimary[0] < base1) { + newbon->ExtraAttackChancePrimary[0] = base1; + newbon->ExtraAttackChancePrimary[1] = base2 ? base2 : 1; + } + break; + } + + case SE_AddExtraAttackPct_1h_Secondary: + { + + if (newbon->ExtraAttackChanceSecondary[0] < base1) { + newbon->ExtraAttackChanceSecondary[0] = base1; + newbon->ExtraAttackChanceSecondary[1] = base2 ? base2 : 1; + } + break; + } + // to do case SE_PetDiscipline: break; @@ -2382,8 +2406,45 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne } case SE_ExtraAttackChance: - new_bonus->ExtraAttackChance += effect_value; + { + if (AdditiveWornBonus) { + new_bonus->ExtraAttackChance[0] += effect_value; + new_bonus->ExtraAttackChance[1] = base2 ? base2 : 1; + } + if (new_bonus->ExtraAttackChance[0] < effect_value) { + new_bonus->ExtraAttackChance[0] = effect_value; + new_bonus->ExtraAttackChance[1] = base2 ? base2 : 1; + } break; + } + + case SE_AddExtraAttackPct_1h_Primary: + { + if (AdditiveWornBonus) { + new_bonus->ExtraAttackChancePrimary[0] += effect_value; + new_bonus->ExtraAttackChancePrimary[1] = base2 ? base2 : 1; + } + + if (new_bonus->ExtraAttackChancePrimary[0] < effect_value) { + new_bonus->ExtraAttackChancePrimary[0] = effect_value; + new_bonus->ExtraAttackChancePrimary[1] = base2 ? base2 : 1; + } + break; + } + + case SE_AddExtraAttackPct_1h_Secondary: + { + if (AdditiveWornBonus) { + new_bonus->ExtraAttackChanceSecondary[0] += effect_value; + new_bonus->ExtraAttackChanceSecondary[1] = base2 ? base2 : 1; + } + + if (new_bonus->ExtraAttackChanceSecondary[0] < effect_value) { + new_bonus->ExtraAttackChanceSecondary[0] = effect_value; + new_bonus->ExtraAttackChanceSecondary[1] = base2 ? base2 : 1; + } + break; + } case SE_PercentXPIncrease: { @@ -4302,9 +4363,21 @@ void Mob::NegateSpellsBonuses(uint16 spell_id) break; case SE_ExtraAttackChance: - spellbonuses.ExtraAttackChance = effect_value; - aabonuses.ExtraAttackChance = effect_value; - itembonuses.ExtraAttackChance = effect_value; + spellbonuses.ExtraAttackChance[0] = effect_value; + aabonuses.ExtraAttackChance[0] = effect_value; + itembonuses.ExtraAttackChance[0] = effect_value; + break; + + case SE_AddExtraAttackPct_1h_Primary: + spellbonuses.ExtraAttackChancePrimary[0] = effect_value; + aabonuses.ExtraAttackChancePrimary[0] = effect_value; + itembonuses.ExtraAttackChancePrimary[0] = effect_value; + break; + + case SE_AddExtraAttackPct_1h_Secondary: + spellbonuses.ExtraAttackChanceSecondary[0] = effect_value; + aabonuses.ExtraAttackChanceSecondary[0] = effect_value; + itembonuses.ExtraAttackChanceSecondary[0] = effect_value; break; case SE_PercentXPIncrease: diff --git a/zone/common.h b/zone/common.h index 16d762915..e2aa19af5 100644 --- a/zone/common.h +++ b/zone/common.h @@ -446,7 +446,9 @@ struct StatBonuses { 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 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 diff --git a/zone/lua_stat_bonuses.cpp b/zone/lua_stat_bonuses.cpp index b6561199b..c007e09ca 100644 --- a/zone/lua_stat_bonuses.cpp +++ b/zone/lua_stat_bonuses.cpp @@ -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 { diff --git a/zone/merc.cpp b/zone/merc.cpp index 3b8631edd..62b02c8a0 100644 --- a/zone/merc.cpp +++ b/zone/merc.cpp @@ -1624,7 +1624,7 @@ void Merc::AI_Process() { } } - int16 ExtraAttackChanceBonus = spellbonuses.ExtraAttackChance + itembonuses.ExtraAttackChance + aabonuses.ExtraAttackChance; + int16 ExtraAttackChanceBonus = spellbonuses.ExtraAttackChance[0] + itembonuses.ExtraAttackChance[0] + aabonuses.ExtraAttackChance[0]; if (GetTarget() && ExtraAttackChanceBonus) { if(zone->random.Roll(ExtraAttackChanceBonus)) diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index eb75c7892..f39340876 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -3214,6 +3214,8 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove case SE_Fc_Cast_Spell_On_Land: case SE_Ff_CasterClass: case SE_Ff_Same_Caster: + case SE_AddExtraAttackPct_1h_Primary: + case SE_AddExtraAttackPct_1h_Secondary: case SE_Skill_Base_Damage_Mod: { break; From 187d6e9dc435c5113655bbabd3bd02782401a384 Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Thu, 29 Jul 2021 19:20:21 -0400 Subject: [PATCH 11/13] [Spells] Bug fix for AOE Harmony/Lull (#1472) * Bug fix for AOE Harmony/Lull type spells. Fixed bug with SPA 30 SE_SE_ChangeFrenzyRad and SPA 86 SE_Harmony allowing those spells to affect NPC's above their level restrictions when cast as a 'Targeted AE' spell (ie. Harmony, Wake of Tranquility) when the targeted NPC was bellow level restricted range, but the NPC's next to them were above it. As coded now, the adjacent NPC's if over level limit will still get the buff applied to them BUT will not get any benefits from the buff. This bug was originally reported by: Isaaru * Live like behavior Implemented the live like behavior, if this case occurs on live, the buff is not applied to the targets over the level limit and "Your target looks unaffected" message is given. * code optimization code optimization --- zone/bonuses.cpp | 6 +++++- zone/mob.h | 1 + zone/spell_effects.cpp | 18 ++++++++++++++++++ zone/spells.cpp | 20 +++++++++----------- 4 files changed, 33 insertions(+), 12 deletions(-) diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index e52e61533..6ae0075bd 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -1750,7 +1750,9 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne case SE_ChangeFrenzyRad: { - // redundant to have level check here + if (max != 0 && GetLevel() > max) + break; + if(new_bonus->AggroRange == -1 || effect_value < new_bonus->AggroRange) { new_bonus->AggroRange = static_cast(effect_value); @@ -1760,6 +1762,8 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne case SE_Harmony: { + if (max != 0 && GetLevel() > max) + 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 diff --git a/zone/mob.h b/zone/mob.h index 4e051d76f..ee3801833 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -837,6 +837,7 @@ 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); void CastSpellOnLand(Mob* caster, uint32 spell_id); void FocusProcLimitProcess(); diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index f39340876..18cd28117 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -7441,3 +7441,21 @@ void Client::BreakFeignDeathWhenCastOn(bool IsResisted) 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].effectid[i] == SE_ChangeFrenzyRad || spells[spell_id].effectid[i] == SE_Harmony) { + if ((spells[spell_id].max[i] != 0 && target->GetLevel() > spells[spell_id].max[i]) || target->GetSpecialAbility(IMMUNE_PACIFY)) { + return false; + } + } + } + return true; +} diff --git a/zone/spells.cpp b/zone/spells.cpp index c246c79a8..9a2d6f27c 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -224,17 +224,9 @@ bool Mob::CastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot, 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 (IsClient() && IsHarmonySpell(spell_id) && !HarmonySpellLevelCheck(spell_id, entity_list.GetMobID(target_id))) { + InterruptSpell(SPELL_NO_EFFECT, 0x121, spell_id); + return false; } if (HasActiveSong() && IsBardSong(spell_id)) { @@ -3789,6 +3781,12 @@ 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); + return false; + } + // Block next spell effect should be used up first(since its blocking the next spell) if(CanBlockSpell()) { int buff_count = GetMaxTotalSlots(); From c3f8b8073b3ddb24269c78860be6d6f812271713 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Fri, 30 Jul 2021 12:09:44 -0400 Subject: [PATCH 12/13] Fix bots with ExtraAttackChance changes (#1480) This should probably be updated to match everything in client, but this will at least fix compile --- zone/bot.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index 757d77b9a..0cb352a18 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -3272,7 +3272,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()) { From 2d296eb317e2aee2ff24206f1e1a5478cdb19023 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Fri, 30 Jul 2021 12:47:22 -0400 Subject: [PATCH 13/13] [CI] Enable Bots (Typo) in Drone Config (#1481) --- .drone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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))