diff --git a/.gitignore b/.gitignore index 52dbcb224..805c783a4 100644 --- a/.gitignore +++ b/.gitignore @@ -33,5 +33,7 @@ Build_32/ build_32/ Build_64/ build_64/ +x64/ +x86/ log/ logs/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 95f21acee..ce1a3bc9b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,12 +13,6 @@ #EQEMU_LOG_LEVEL_QUEST #EQEMU_LOG_LEVEL_COMMANDS #EQEMU_LOG_LEVEL_CRASH -#EQEMU_STREAM_SEND_RATE -#EQEMU_STREAM_DECAY_RATE -#EQEMU_STREAM_RETRANSMIT_TIMEOUT_MUL -#EQEMU_STREAM_RETRANSMIT_TIMEOUT_MAX -#EQEMU_STREAM_AVERAGE_DELTA_MAX -#EQEMU_STREAM_RETRANSMIT_ACKED_PACKETS #EQEMU_DEPOP_INVALIDATES_CACHE #EQEMU_ENABLE_BOTS #EQEMU_DISABLE_LOGSYS @@ -78,7 +72,7 @@ IF(MSVC) SET(MYSQL_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/dependencies/mysql_x86") SET(LUA_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/dependencies/luaj_x86") SET(SODIUM_INCLUDE_HINTS "${CMAKE_CURRENT_SOURCE_DIR}/dependencies/libsodium/include") - SET(OPENSSL_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/dependencies/openssl_x64") + SET(OPENSSL_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/dependencies/openssl_x86") IF(MSVC_VERSION GREATER 1800) SET(SODIUM_LIBRARY_HINTS "${CMAKE_CURRENT_SOURCE_DIR}/dependencies/libsodium/Win32/Release/v140/dynamic") ELSEIF(MSVC_VERSION EQUAL 1800) @@ -237,14 +231,6 @@ SET(EQEMU_LOG_LEVEL_CRASH 3 CACHE STRING "EQEmu logging level for [Crash]: MARK_AS_ADVANCED(EQEMU_LOG_LEVEL_STATUS EQEMU_LOG_LEVEL_NORMAL EQEMU_LOG_LEVEL_ERROR EQEMU_LOG_LEVEL_DEBUG EQEMU_LOG_LEVEL_QUEST EQEMU_LOG_LEVEL_COMMANDS EQEMU_LOG_LEVEL_CRASH) -SET(EQEMU_STREAM_SEND_RATE 1048576 CACHE STRING "Advanced: Base amount of data stream can send before throttle.") -SET(EQEMU_STREAM_DECAY_RATE 78642 CACHE STRING "Advanced: Base amount of data stream recovers per tic.") -SET(EQEMU_STREAM_RETRANSMIT_TIMEOUT_MUL 3.0 CACHE STRING "Advanced: Multiplier on retransmit timeout.") -SET(EQEMU_STREAM_RETRANSMIT_TIMEOUT_MAX 5000 CACHE STRING "Advanced: Max in ms for retransmit timeout timer.") -SET(EQEMU_STREAM_AVERAGE_DELTA_MAX 2500 CACHE STRING "Advanced: The maximum average delta in ms allowed.") -SET(EQEMU_STREAM_RETRANSMIT_ACKED_PACKETS TRUE CACHE BOOL "Advanced: Whether or not acked packets can be retransmitted") -MARK_AS_ADVANCED(EQEMU_STREAM_SEND_RATE EQEMU_STREAM_DECAY_RATE EQEMU_STREAM_RETRANSMIT_TIMEOUT_MUL EQEMU_STREAM_RETRANSMIT_TIMEOUT_MAX EQEMU_STREAM_AVERAGE_DELTA_MAX EQEMU_STREAM_RETRANSMIT_ACKED_PACKETS) - #NPC Types Cache Behavior OPTION(EQEMU_DEPOP_INVALIDATES_CACHE "#repop invalidates the npc_types cache (will cause a larger database hit on #repop but is more convienent)." ON) @@ -306,11 +292,6 @@ ADD_DEFINITIONS(-DEQDEBUG=${EQEMU_DEBUG_LEVEL}) ADD_DEFINITIONS(-DINVERSEXY) ADD_DEFINITIONS(-DFIELD_ITEMS) ADD_DEFINITIONS(-DMAP_DIR="${EQEMU_MAP_DIR}") -ADD_DEFINITIONS(-DRATEBASE=${EQEMU_STREAM_SEND_RATE}) -ADD_DEFINITIONS(-DDECAYBASE=${EQEMU_STREAM_DECAY_RATE}) -ADD_DEFINITIONS(-DRETRANSMIT_TIMEOUT_MULT=${EQEMU_STREAM_RETRANSMIT_TIMEOUT_MUL}) -ADD_DEFINITIONS(-DRETRANSMIT_TIMEOUT_MAX=${EQEMU_STREAM_RETRANSMIT_TIMEOUT_MAX}) -ADD_DEFINITIONS(-DAVERAGE_DELTA_MAX=${EQEMU_STREAM_AVERAGE_DELTA_MAX}) ADD_DEFINITIONS(-DLOG_LEVEL_STATUS=${EQEMU_LOG_LEVEL_STATUS}) ADD_DEFINITIONS(-DLOG_LEVEL_NORMAL=${EQEMU_LOG_LEVEL_NORMAL}) ADD_DEFINITIONS(-DLOG_LEVEL_ERROR=${EQEMU_LOG_LEVEL_ERROR}) @@ -320,12 +301,6 @@ ADD_DEFINITIONS(-DLOG_LEVEL_COMMANDS=${EQEMU_LOG_LEVEL_COMMANDS}) ADD_DEFINITIONS(-DLOG_LEVEL_CRASH=${EQEMU_LOG_LEVEL_CRASH}) ADD_DEFINITIONS(-DGLM_FORCE_RADIANS) -IF(EQEMU_STREAM_RETRANSMIT_ACKED_PACKETS) - ADD_DEFINITIONS(-DRETRANSMIT_ACKED_PACKETS=true) -ELSE(EQEMU_STREAM_RETRANSMIT_ACKED_PACKETS) - ADD_DEFINITIONS(-DRETRANSMIT_ACKED_PACKETS=false) -ENDIF(EQEMU_STREAM_RETRANSMIT_ACKED_PACKETS) - #Find everything we need FIND_PACKAGE(ZLIB REQUIRED) FIND_PACKAGE(MySQL REQUIRED) diff --git a/README.md b/README.md index 057937a0c..f99b17975 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ **EQEmulator is a custom completely from-scratch open source server implementation for EverQuest built mostly on C++** * MySQL/MariaDB is used as the database engine (over 200+ tables) * Perl and LUA are both supported scripting languages for NPC/Player/Quest oriented events - * Open source database (Project EQ) has content up to expansion GoD (included in server installs) + * Open source database (Project EQ) has content up to expansion OoW (included in server installs) * Game server environments and databases can be heavily customized to create all new experiences * Hundreds of Quests/events created and maintained by Project EQ @@ -20,14 +20,14 @@ * [Easy Install](http://wiki.eqemulator.org/p?Akkas_PEQ_Server_Installer&frm=Main#from-scratch-installation-instructions-windows) * [Advanced Setup](http://wiki.eqemulator.org/p?Complete_Windows-based_Server_Setup_Guide) -### > Debian/Ubuntu + + +### > Debian/Ubuntu/CentOS/Fedora +* 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 -### > CentOS/Fedora - -> curl -O https://raw.githubusercontent.com/EQEmu/Server/master/utils/scripts/linux_installer/install.sh install.sh && chmod 755 install.sh && ./install.sh - ## Supported Clients |Titanium Edition|Secrets of Faydwer|Seeds of Destruction|Underfoot|Rain of Fear| diff --git a/changelog.txt b/changelog.txt index 8edf925b0..9568f8392 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,25 +1,71 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- + +== 7/14/2017 == +Akkadius: HP Update tuning - HP Updates are now forced when a client is targeted +Akkadius: Client position updates should be smoother (granted the client has a good connection) + - Clients should also no longer randomly disappear + +== 7/11/2017 == +Akkadius: Raid/Group/XTarget HP/Mana/Endurance updates now only send when percentage changes +Akkadius: Raid/Group Mana/Endurance updates should now update real-time once again +Akkadius: Fixed an issue with clients looking like they are 'skipping' when they are moving in view of another client +Akkadius: Fixed an issue with NPC's who are ghosted in plain view of a client when they are not really there + +== 7/9/2017 == +Akkadius: Fix HP update issues, rework logic for more accurate HP updates +Akkadius: Massive reductions in unnecessary network traffic especially during high spam combat fights + - HP Updates now only send to others when HP percentage changes (0-100%) + - HP Updates were sending excessively even during idle zones when HP wasn't changing at all + - Attack animations now only send once per second versus up to a hundred times a second per Mob/Client + - 17,000 OP_ClientUpdate packets per second have been observed in combat scenarios, some of the major culprits have been + throttled without affecting what the client should see + - Before and After packet differences under similar load/tests (Packets per second) + - 7,000 - 8,000 OP_Animation pps After: 600-800 pps + - 13,0000 - 17,000 OP_MobHealth pps After: 1-10 pps + - 15,0000 - 20,000 OP_ClientUpdate pps After: 500-1,000 pps + - Packet reports from a 46 client test here: + https://gist.github.com/Akkadius/28b7ad2fdd82bdd15ea737c68f404346 + - Servers who use Marquee HP updates will also recieve far less packet spam as they will only be sent when HP changes + +== 7/1/2017 == +Akkadius: Resolve issues with NPC's hopping to the ceiling in small corridors +Akkadius: Improved grounding issues with NPC's during combat +Akkadius: Improved scenarios where NPC's need to be dragged out of the ground - they should correct themselves far more consistently + - Scenarios where an NPC is coming up from the bottom floor, or from the top floor, they will correct much better + - A video of these tests can be found here: https://www.youtube.com/watch?v=HtC7bVNM7ZQ&feature=youtu.be + +== 6/28/2017 == +Akkadius: Fixed issues with Z correctness when NPCs are pathing on normal grids +Akkadius: Fixed issues with Z correctness when NPCs are engaged with players following +Akkadius: NPC corpses should fall into the ground far less + +== 6/25/2017 == +Akkadius: New rules made by developers are now automatically created when world boots up, this keeps + from having to issue schema SQL updates every time rules are added. + - Whenever a rule isn't present in the database, it will be automatically created +Akkadius: Sped up saylink retrieval x1000 helpful for dialogues, plugins with many saylinks + == 4/16/2017 == KLS: Merge eqstream branch - -UDP client stack completely rewritten should both have better throughput and recover better (peq has had far fewer reports of desyncs). - -TCP Server to Server connection stack completely rewritten. - -Server connections reconnect much more reliably and quickly now. - -Now supports optional packet encryption via libsodium (https://download.libsodium.org/doc/). - -Protocol behind the tcp connections has changed (see breaking changes section). - -API significantly changed and should be easier to write new servers or handlers for. - -Telnet console connection has been separated out from the current port (see breaking changes section). - -Because of changes to the TCP stack, lsreconnect and echo have been disabled. - -The server tic rate has been changed to be approx 30 fps from 500+ fps. - -Changed how missiles and movement were calculated slightly to account for this (Missiles in particular are not perfect but close enough). + - UDP client stack completely rewritten should both have better throughput and recover better (peq has had far fewer reports of desyncs). + - TCP Server to Server connection stack completely rewritten. + - Server connections reconnect much more reliably and quickly now. + - Now supports optional packet encryption via libsodium (https://download.libsodium.org/doc/). + - Protocol behind the tcp connections has changed (see breaking changes section). + - API significantly changed and should be easier to write new servers or handlers for. + - Telnet console connection has been separated out from the current port (see breaking changes section). + - Because of changes to the TCP stack, lsreconnect and echo have been disabled. + - The server tic rate has been changed to be approx 30 fps from 500+ fps. + - Changed how missiles and movement were calculated slightly to account for this (Missiles in particular are not perfect but close enough). - -Breaking changes: - -Users who use the cmake install feature should be aware that the install directory is now %cmake_install_dir%/bin instead of just %cmake_install_dir%/ - -To support new features such as encryption the underlying protocol had to change... however some servers such as the public login server will be slow to change so we've included a compatibility layer for legacy login connections: - -You should add 1 to the login section of your configuration file when connecting to a server that is using the old protocol. - -The central eqemu login server uses the old protocol and probably will for the forseeable future so if your server is connecting to it be sure to add that tag to your configuration file in that section. - -Telnet no longer uses the same port as the Server to Server connection and because of this the tcp tag no longer has any effect on telnet connections. - -To enable telnet you need to add a telnet tag in the world section of configuration such as: + - Breaking changes: + - Users who use the cmake install feature should be aware that the install directory is now %cmake_install_dir%/bin instead of just %cmake_install_dir%/ + - To support new features such as encryption the underlying protocol had to change... however some servers such as the public login server will be slow to change so we've included a compatibility layer for legacy login connections: + - You should add 1 to the login section of your configuration file when connecting to a server that is using the old protocol. + - The central eqemu login server uses the old protocol and probably will for the forseeable future so if your server is connecting to it be sure to add that tag to your configuration file in that section. + - Telnet no longer uses the same port as the Server to Server connection and because of this the tcp tag no longer has any effect on telnet connections. + - To enable telnet you need to add a telnet tag in the world section of configuration such as: == 4/1/2017 == diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 06486d2db..d65efd183 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -103,6 +103,7 @@ SET(common_sources tinyxml/tinyxml.cpp tinyxml/tinyxmlerror.cpp tinyxml/tinyxmlparser.cpp + util/directory.cpp util/uuid.cpp ) @@ -257,6 +258,7 @@ SET(common_headers tinyxml/tinystr.h tinyxml/tinyxml.h util/memory_stream.h + util/directory.h util/uuid.h ) @@ -366,6 +368,8 @@ SOURCE_GROUP(TinyXML FILES SOURCE_GROUP(Util FILES util/memory_stream.h + util/directory.cpp + util/directory.h util/uuid.cpp util/uuid.h ) diff --git a/common/emu_oplist.h b/common/emu_oplist.h index cf6f4ca28..29a20cb14 100644 --- a/common/emu_oplist.h +++ b/common/emu_oplist.h @@ -355,6 +355,7 @@ N(OP_OpenTributeMaster), N(OP_PDeletePetition), N(OP_PetBuffWindow), N(OP_PetCommands), +N(OP_PetCommandState), N(OP_PetHoTT), N(OP_Petition), N(OP_PetitionBug), @@ -407,6 +408,7 @@ N(OP_ReloadUI), N(OP_RemoveAllDoors), N(OP_RemoveBlockedBuffs), N(OP_RemoveNimbusEffect), +N(OP_RemoveTrap), N(OP_Report), N(OP_ReqClientSpawn), N(OP_ReqNewZone), @@ -522,6 +524,7 @@ N(OP_TributeToggle), N(OP_TributeUpdate), N(OP_Untargetable), N(OP_UpdateAA), +N(OP_UpdateAura), N(OP_UpdateLeadershipAA), N(OP_VetClaimReply), N(OP_VetClaimRequest), diff --git a/common/eq_constants.h b/common/eq_constants.h index b4e887b0a..d20bfdf4a 100644 --- a/common/eq_constants.h +++ b/common/eq_constants.h @@ -27,7 +27,7 @@ //SpawnAppearance types: (compared two clients for server-originating types: SoF & RoF2) #define AT_Die 0 // this causes the client to keel over and zone to bind point (default action) #define AT_WhoLevel 1 // the level that shows up on /who -//#define AT_2 2 // unknown +#define AT_HPMax 2 // idk #define AT_Invis 3 // 0 = visible, 1 = invisible #define AT_PVP 4 // 0 = blue, 1 = pvp (red) #define AT_Light 5 // light type emitted by player (lightstone, shiny shield) @@ -36,33 +36,37 @@ #define AT_SpawnID 16 // server to client, sets player spawn id #define AT_HP 17 // Client->Server, my HP has changed (like regen tic) #define AT_Linkdead 18 // 0 = normal, 1 = linkdead -#define AT_Levitate 19 // 0=off, 1=flymode, 2=levitate +#define AT_Levitate 19 // 0=off, 1=flymode, 2=levitate max 5, see GravityBehavior enum #define AT_GM 20 // 0 = normal, 1 = GM - all odd numbers seem to make it GM #define AT_Anon 21 // 0 = normal, 1 = anon, 2 = roleplay #define AT_GuildID 22 #define AT_GuildRank 23 // 0=member, 1=officer, 2=leader #define AT_AFK 24 // 0 = normal, 1 = afk #define AT_Pet 25 // Param is EntityID of owner, or 0 for when charm breaks -//#define AT_27 27 // unknown +#define AT_Summoned 27 // Unsure #define AT_Split 28 // 0 = normal, 1 = autosplit on (not showing in SoF+) (client-to-server only) #define AT_Size 29 // spawn's size (present: SoF, absent: RoF2) -//#define AT_30 30 // unknown -#define AT_NPCName 31 // change PC's name's color to NPC color 0 = normal, 1 = npc name -//#define AT_32 32 // unknown -//#define AT_33 33 // unknown +#define AT_SetType 30 // 0 = PC, 1 = NPC, 2 <= = corpse +#define AT_NPCName 31 // change PC's name's color to NPC color 0 = normal, 1 = npc name, Trader on RoF2? +#define AT_AARank 32 // AA Rank Title ID thingy, does is this the title in /who? +#define AT_CancelSneakHide 33 // Turns off Hide and Sneak //#define AT_34 34 // unknown (present: SoF, absent: RoF2) -//#define AT_35 35 // unknown -//#define AT_36 36 // unknown -//#define AT_37 37 // unknown -//#define AT_38 38 // unknown -//#define AT_39 39 // unknown +#define AT_AreaHPRegen 35 // guild hall regen pool sets to value * 0.001 +#define AT_AreaManaRegen 36 // guild hall regen pool sets to value * 0.001 +#define AT_AreaEndRegen 37 // guild hall regen pool sets to value * 0.001 +#define AT_FreezeBuffs 38 // Freezes beneficial buff timers +#define AT_NpcTintIndex 39 // not 100% sure +#define AT_GroupConsent 40 // auto consent group +#define AT_RaidConsent 41 // auto consent raid +#define AT_GuildConsent 42 // auto consent guild #define AT_ShowHelm 43 // 0 = hide graphic, 1 = show graphic -#define AT_DamageState 44 // The damage state of a destructible object (0 through 4) -//#define AT_46 46 // unknown -//#define AT_48 48 // unknown -//#define AT_49 49 // unknown -//#define AT_52 52 // (absent: SoF, present: RoF2) (not a replacement for RoF absent 29 or 34) -//#define AT_53 53 // (absent: SoF, present: RoF2) (not a replacement for RoF absent 29 or 34) +#define AT_DamageState 44 // The damage state of a destructible object (0 through 10) plays soundids most only have 2 or 4 states though +#define AT_EQPlayers 45 // /eqplayersupdate +#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_GuildShow 52 // this is what MQ2 call sit, not sure +#define AT_Offline 53 // Offline mode //#define AT_Trader 300 // Bazaar Trader Mode (not present in SoF or RoF2) diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index 3730e1d14..79b8f7250 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -305,6 +305,7 @@ union uint8 DestructibleUnk8; uint32 DestructibleUnk9; bool targetable_with_hotkey; + bool show_name; }; @@ -1116,6 +1117,11 @@ struct PetCommand_Struct { /*004*/ uint32 target; }; +struct PetCommandState_Struct { +/*00*/ uint32 button_id; +/*04*/ uint32 state; +}; + /* ** Delete Spawn ** Length: 4 Bytes @@ -1690,6 +1696,7 @@ struct OnLevelMessage_Struct uint32 Duration; uint32 PopupID; uint32 NegativeID; + uint32 SoundControls; char ButtonName0[25]; char ButtonName1[25]; }; @@ -5326,6 +5333,24 @@ struct fling_struct { /* 28 */ }; +// used when action == 0 +struct AuraCreate_Struct { +/* 00 */ uint32 action; // 0 = add, 1 = delete, 2 = reset +/* 04 */ uint32 type; // unsure -- normal auras show 1 clicky (ex. Circle of Power) show 0 +/* 08 */ char aura_name[64]; +/* 72 */ uint32 entity_id; +/* 76 */ uint32 icon; +/* 80 */ +}; + +// used when action == 1 +struct AuraDestory_Struct { +/* 00 */ uint32 action; // 0 = add, 1 = delete, 2 = reset +/* 04 */ uint32 entity_id; +/* 08 */ +}; +// I think we can assume it's just action for 2, client doesn't seem to do anything with the rest of the data in that case + // Restore structure packing to default #pragma pack() diff --git a/common/eq_stream_proxy.cpp b/common/eq_stream_proxy.cpp index 7fb4c9432..6a3cc6ced 100644 --- a/common/eq_stream_proxy.cpp +++ b/common/eq_stream_proxy.cpp @@ -2,6 +2,8 @@ #include "global_define.h" #include "eq_stream_proxy.h" #include "struct_strategy.h" +#include "eqemu_logsys.h" +#include "opcodemgr.h" EQStreamProxy::EQStreamProxy(std::shared_ptr &stream, const StructStrategy *structs, OpcodeManager **opcodes) @@ -39,6 +41,11 @@ void EQStreamProxy::QueuePacket(const EQApplicationPacket *p, bool ack_req) { if(p == nullptr) return; + if (p->GetOpcode() != OP_SpecialMesg) { + Log(Logs::General, Logs::Server_Client_Packet, "[%s - 0x%04x] [Size: %u]", OpcodeManager::EmuToName(p->GetOpcode()), p->GetOpcode(), p->Size()); + Log(Logs::General, Logs::Server_Client_Packet_With_Dump, "[%s - 0x%04x] [Size: %u] %s", OpcodeManager::EmuToName(p->GetOpcode()), p->GetOpcode(), p->Size(), DumpPacketToString(p).c_str()); + } + EQApplicationPacket *newp = p->Copy(); FastQueuePacket(&newp, ack_req); } diff --git a/common/eqemu_logsys.h b/common/eqemu_logsys.h index ed821831a..56542c07d 100644 --- a/common/eqemu_logsys.h +++ b/common/eqemu_logsys.h @@ -86,6 +86,8 @@ enum LogCategory { Login_Server, Client_Login, Headless_Client, + HP_Update, + FixZ, MaxCategoryID /* Don't Remove this*/ }; @@ -135,7 +137,10 @@ static const char* LogCategoryName[LogCategory::MaxCategoryID] = { "Packet :: Server -> Client (Dump)", "Packet :: Client -> Server (Dump)", "Login Server", - "Client Login" + "Client Login", + "Headless Client", + "HP Update", + "FixZ" }; } diff --git a/common/item_data.h b/common/item_data.h index e5d1a24f2..ae7e3bbdb 100644 --- a/common/item_data.h +++ b/common/item_data.h @@ -332,7 +332,7 @@ namespace EQEmu }; struct ItemEffect_Struct { - int16 Effect; + int32 Effect; uint8 Type; uint8 Level; uint8 Level2; diff --git a/common/net/console_server.cpp b/common/net/console_server.cpp index d8f08b05d..ab420bd42 100644 --- a/common/net/console_server.cpp +++ b/common/net/console_server.cpp @@ -25,6 +25,23 @@ void EQ::Net::ConsoleServer::RegisterLogin(ConsoleServerLoginCallback fn) m_login = fn; } +EQ::Net::ConsoleServerConnection *EQ::Net::ConsoleServer::FindByAccountName(const std::string &acct_name) { + for (auto &iter : m_connections) { + if (iter.second->UserName().compare(acct_name) == 0) { + return iter.second.get(); + } + } + + return nullptr; +} + + +void EQ::Net::ConsoleServer::SendChannelMessage(const ServerChannelMessage_Struct* scm, std::function onTell) { + for (auto &iter : m_connections) { + iter.second->SendChannelMessage(scm, onTell); + } +} + void EQ::Net::ConsoleServer::ConnectionDisconnected(ConsoleServerConnection *c) { auto iter = m_connections.find(c->GetUUID()); diff --git a/common/net/console_server.h b/common/net/console_server.h index 5a0c48f8e..d28f43844 100644 --- a/common/net/console_server.h +++ b/common/net/console_server.h @@ -25,7 +25,8 @@ namespace EQ void RegisterCall(const std::string& command, int status_required, const std::string& help_definition, ConsoleServerCallback fn); void RegisterLogin(ConsoleServerLoginCallback fn); - + ConsoleServerConnection *FindByAccountName(const std::string &acct_name); + void SendChannelMessage(const ServerChannelMessage_Struct* scm, std::function onTell); private: void ConnectionDisconnected(ConsoleServerConnection *c); void ProcessCommand(ConsoleServerConnection *c, const std::string& cmd); diff --git a/common/net/console_server_connection.cpp b/common/net/console_server_connection.cpp index 8764f09b6..249817818 100644 --- a/common/net/console_server_connection.cpp +++ b/common/net/console_server_connection.cpp @@ -2,6 +2,8 @@ #include "../common/util/uuid.h" #include "../common/net/packet.h" #include "../common/eqemu_logsys.h" +#include "../common/servertalk.h" +#include "../common/rulesys.h" EQ::Net::ConsoleServerConnection::ConsoleServerConnection(ConsoleServer *parent, std::shared_ptr connection) { @@ -107,6 +109,53 @@ void EQ::Net::ConsoleServerConnection::QueueMessage(const std::string &msg) } } +bool EQ::Net::ConsoleServerConnection::SendChannelMessage(const ServerChannelMessage_Struct* scm, std::function onTell) { + if (!m_accept_messages) { + return false; + } + + switch (scm->chan_num) { + if (RuleB(Chat, ServerWideAuction)) { + case 4: { + QueueMessage(fmt::format("{0} auctions, '{1}'", scm->from, scm->message)); + break; + } + } + + if (RuleB(Chat, ServerWideOOC)) { + case 5: { + QueueMessage(fmt::format("{0} says ooc, '{1}'", scm->from, scm->message)); + break; + } + } + + case 6: { + QueueMessage(fmt::format("{0} BROADCASTS, '{1}'", scm->from, scm->message)); + break; + } + + case 7: { + QueueMessage(fmt::format("[{0}] tells you, '{1}'", scm->from, scm->message)); + if (onTell) { + onTell(); + } + + break; + } + + case 11: { + QueueMessage(fmt::format("{0} GMSAYS, '{1}'", scm->from, scm->message)); + break; + } + + default: { + return false; + } + } + + return true; +} + void EQ::Net::ConsoleServerConnection::OnRead(TCPConnection *c, const unsigned char *data, size_t sz) { for (size_t i = 0; i < sz; ++i) { diff --git a/common/net/console_server_connection.h b/common/net/console_server_connection.h index d826ba07b..a49700841 100644 --- a/common/net/console_server_connection.h +++ b/common/net/console_server_connection.h @@ -4,6 +4,8 @@ #include #include +struct ServerChannelMessage_Struct; + namespace EQ { namespace Net @@ -42,6 +44,7 @@ namespace EQ bool AcceptMessages() const { return m_accept_messages; } void SetAcceptMessages(bool v) { m_accept_messages = v; } void QueueMessage(const std::string &msg); + bool SendChannelMessage(const ServerChannelMessage_Struct* scm, std::function onTell); private: void OnRead(TCPConnection* c, const unsigned char* data, size_t sz); void OnDisconnect(TCPConnection* c); diff --git a/common/net/daybreak_connection.cpp b/common/net/daybreak_connection.cpp index 68fc24ad2..095fab525 100644 --- a/common/net/daybreak_connection.cpp +++ b/common/net/daybreak_connection.cpp @@ -135,26 +135,26 @@ void EQ::Net::DaybreakConnectionManager::Process() switch (status) { - case StatusConnecting: { - auto time_since_last_send = std::chrono::duration_cast(now - connection->m_last_send); - if ((size_t)time_since_last_send.count() > m_options.connect_delay_ms) { - connection->SendConnect(); - } - } - break; - case StatusConnected: { - if (m_options.keepalive_delay_ms != 0) { + case StatusConnecting: { auto time_since_last_send = std::chrono::duration_cast(now - connection->m_last_send); - if ((size_t)time_since_last_send.count() > m_options.keepalive_delay_ms) { - connection->SendKeepAlive(); + if ((size_t)time_since_last_send.count() > m_options.connect_delay_ms) { + connection->SendConnect(); + } + break; + } + case StatusConnected: { + if (m_options.keepalive_delay_ms != 0) { + auto time_since_last_send = std::chrono::duration_cast(now - connection->m_last_send); + if ((size_t)time_since_last_send.count() > m_options.keepalive_delay_ms) { + connection->SendKeepAlive(); + } } } - } - case StatusDisconnecting: - connection->Process(); - break; - default: - break; + case StatusDisconnecting: + connection->Process(); + break; + default: + break; } iter++; @@ -170,12 +170,12 @@ void EQ::Net::DaybreakConnectionManager::ProcessResend() switch (status) { - case StatusConnected: - case StatusDisconnecting: - connection->ProcessResend(); - break; - default: - break; + case StatusConnected: + case StatusDisconnecting: + connection->ProcessResend(); + break; + default: + break; } iter++; @@ -382,13 +382,8 @@ void EQ::Net::DaybreakConnection::ProcessPacket(Packet &p) return; } - if (p.GetInt8(0) != 0) { - LogF(Logs::Detail, Logs::Netcode, "Error parsing packet, did not start with a 0 frame, not a valid protocol packet."); - return; - } - auto opcode = p.GetInt8(1); - if (opcode == OP_KeepAlive || opcode == OP_OutboundPing) { + if (p.GetInt8(0) == 0 && (opcode == OP_KeepAlive || opcode == OP_OutboundPing)) { return; } @@ -406,14 +401,20 @@ void EQ::Net::DaybreakConnection::ProcessPacket(Packet &p) for (int i = 1; i >= 0; --i) { switch (m_encode_passes[i]) { - case EncodeCompression: - Decompress(temp, DaybreakHeader::size(), temp.Length() - DaybreakHeader::size()); - break; - case EncodeXOR: - Decode(temp, DaybreakHeader::size(), temp.Length() - DaybreakHeader::size()); - break; - default: - break; + case EncodeCompression: + if(temp.GetInt8(0) == 0) + Decompress(temp, DaybreakHeader::size(), temp.Length() - DaybreakHeader::size()); + else + Decompress(temp, 1, temp.Length() - 1); + break; + case EncodeXOR: + if (temp.GetInt8(0) == 0) + Decode(temp, DaybreakHeader::size(), temp.Length() - DaybreakHeader::size()); + else + Decode(temp, 1, temp.Length() - 1); + break; + default: + break; } } @@ -424,11 +425,14 @@ void EQ::Net::DaybreakConnection::ProcessPacket(Packet &p) for (int i = 1; i >= 0; --i) { switch (m_encode_passes[i]) { - case EncodeXOR: - Decode(temp, DaybreakHeader::size(), temp.Length() - DaybreakHeader::size()); - break; - default: - break; + case EncodeXOR: + if (temp.GetInt8(0) == 0) + Decode(temp, DaybreakHeader::size(), temp.Length() - DaybreakHeader::size()); + else + Decode(temp, 1, temp.Length() - 1); + break; + default: + break; } } @@ -490,274 +494,274 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p) } switch (p.GetInt8(1)) { - case OP_Combined: { - if (m_status == StatusDisconnecting) { - SendDisconnect(); - return; - } - - char *current = (char*)p.Data() + 2; - char *end = (char*)p.Data() + p.Length(); - while (current < end) { - uint8_t subpacket_length = *(uint8_t*)current; - current += 1; - - if (end < current + subpacket_length) { + case OP_Combined: { + if (m_status == StatusDisconnecting) { + SendDisconnect(); return; } - ProcessDecodedPacket(StaticPacket(current, subpacket_length)); - current += subpacket_length; - } - break; - } + char *current = (char*)p.Data() + 2; + char *end = (char*)p.Data() + p.Length(); + while (current < end) { + uint8_t subpacket_length = *(uint8_t*)current; + current += 1; - case OP_AppCombined: - { - if (m_status == StatusDisconnecting) { - SendDisconnect(); - return; - } - - uint8_t *current = (uint8_t*)p.Data() + 2; - uint8_t *end = (uint8_t*)p.Data() + p.Length(); - - while (current < end) { - uint32_t subpacket_length = 0; - if (*current == 0xFF) - { - if (end < current + 3) { - throw std::out_of_range("Error in OP_AppCombined, end < current + 3"); + if (end < current + subpacket_length) { + return; } - if (*(current + 1) == 0xFF && *(current + 2) == 0xFF) { - if (end < current + 7) { - throw std::out_of_range("Error in OP_AppCombined, end < current + 7"); + ProcessDecodedPacket(StaticPacket(current, subpacket_length)); + current += subpacket_length; + } + break; + } + + case OP_AppCombined: + { + if (m_status == StatusDisconnecting) { + SendDisconnect(); + return; + } + + uint8_t *current = (uint8_t*)p.Data() + 2; + uint8_t *end = (uint8_t*)p.Data() + p.Length(); + + while (current < end) { + uint32_t subpacket_length = 0; + if (*current == 0xFF) + { + if (end < current + 3) { + throw std::out_of_range("Error in OP_AppCombined, end < current + 3"); } - subpacket_length = (uint32_t)( - (*(current + 3) << 24) | - (*(current + 4) << 16) | - (*(current + 5) << 8) | - (*(current + 6)) - ); - current += 7; + if (*(current + 1) == 0xFF && *(current + 2) == 0xFF) { + if (end < current + 7) { + throw std::out_of_range("Error in OP_AppCombined, end < current + 7"); + } + + subpacket_length = (uint32_t)( + (*(current + 3) << 24) | + (*(current + 4) << 16) | + (*(current + 5) << 8) | + (*(current + 6)) + ); + current += 7; + } + else { + subpacket_length = (uint32_t)( + (*(current + 1) << 8) | + (*(current + 2)) + ); + current += 3; + } } else { - subpacket_length = (uint32_t)( - (*(current + 1) << 8) | - (*(current + 2)) - ); - current += 3; + subpacket_length = (uint32_t)((*(current + 0))); + current += 1; + } + + ProcessDecodedPacket(StaticPacket(current, subpacket_length)); + current += subpacket_length; + } + } + + case OP_SessionRequest: + { + if (m_status == StatusConnected) { + auto request = p.GetSerialize(0); + + if (NetworkToHost(request.connect_code) != m_connect_code) { + return; + } + + DaybreakConnectReply reply; + reply.zero = 0; + reply.opcode = OP_SessionResponse; + reply.connect_code = HostToNetwork(m_connect_code); + reply.encode_key = HostToNetwork(m_encode_key); + reply.crc_bytes = m_crc_bytes; + reply.max_packet_size = HostToNetwork(m_max_packet_size); + reply.encode_pass1 = m_encode_passes[0]; + reply.encode_pass2 = m_encode_passes[1]; + DynamicPacket p; + p.PutSerialize(0, reply); + InternalSend(p); + } + + break; + } + + case OP_SessionResponse: + { + if (m_status == StatusConnecting) { + auto reply = p.GetSerialize(0); + + if (m_connect_code == reply.connect_code) { + m_encode_key = reply.encode_key; + m_crc_bytes = reply.crc_bytes; + m_encode_passes[0] = (DaybreakEncodeType)reply.encode_pass1; + m_encode_passes[1] = (DaybreakEncodeType)reply.encode_pass2; + m_max_packet_size = reply.max_packet_size; + ChangeStatus(StatusConnected); } } - else { - subpacket_length = (uint32_t)((*(current + 0))); - current += 1; - } - - ProcessDecodedPacket(StaticPacket(current, subpacket_length)); - current += subpacket_length; + break; } - } - case OP_SessionRequest: - { - if (m_status == StatusConnected) { - auto request = p.GetSerialize(0); - - if (NetworkToHost(request.connect_code) != m_connect_code) { + case OP_Packet: + case OP_Packet2: + case OP_Packet3: + case OP_Packet4: + { + if (m_status == StatusDisconnecting) { + SendDisconnect(); return; } - DaybreakConnectReply reply; - reply.zero = 0; - reply.opcode = OP_SessionResponse; - reply.connect_code = HostToNetwork(m_connect_code); - reply.encode_key = HostToNetwork(m_encode_key); - reply.crc_bytes = m_crc_bytes; - reply.max_packet_size = HostToNetwork(m_max_packet_size); - reply.encode_pass1 = m_encode_passes[0]; - reply.encode_pass2 = m_encode_passes[1]; - DynamicPacket p; - p.PutSerialize(0, reply); - InternalSend(p); - } + auto header = p.GetSerialize(0); + auto sequence = NetworkToHost(header.sequence); + auto stream_id = header.opcode - OP_Packet; + auto stream = &m_streams[stream_id]; - break; - } - - case OP_SessionResponse: - { - if (m_status == StatusConnecting) { - auto reply = p.GetSerialize(0); - - if (m_connect_code == reply.connect_code) { - m_encode_key = reply.encode_key; - m_crc_bytes = reply.crc_bytes; - m_encode_passes[0] = (DaybreakEncodeType)reply.encode_pass1; - m_encode_passes[1] = (DaybreakEncodeType)reply.encode_pass2; - m_max_packet_size = reply.max_packet_size; - ChangeStatus(StatusConnected); + auto order = CompareSequence(stream->sequence_in, sequence); + if (order == SequenceFuture) { + SendOutOfOrderAck(stream_id, sequence); + AddToQueue(stream_id, sequence, p); } - } - break; - } - - case OP_Packet: - case OP_Packet2: - case OP_Packet3: - case OP_Packet4: - { - if (m_status == StatusDisconnecting) { - SendDisconnect(); - return; - } - - auto header = p.GetSerialize(0); - auto sequence = NetworkToHost(header.sequence); - auto stream_id = header.opcode - OP_Packet; - auto stream = &m_streams[stream_id]; - - auto order = CompareSequence(stream->sequence_in, sequence); - if (order == SequenceFuture) { - SendOutOfOrderAck(stream_id, sequence); - AddToQueue(stream_id, sequence, p); - } - else if (order == SequencePast) { - SendAck(stream_id, stream->sequence_in - 1); - } - else { - RemoveFromQueue(stream_id, sequence); - SendAck(stream_id, stream->sequence_in); - stream->sequence_in++; - StaticPacket next((char*)p.Data() + DaybreakReliableHeader::size(), p.Length() - DaybreakReliableHeader::size()); - ProcessDecodedPacket(next); - } - - break; - } - - case OP_Fragment: - case OP_Fragment2: - case OP_Fragment3: - case OP_Fragment4: - { - auto header = p.GetSerialize(0); - auto sequence = NetworkToHost(header.sequence); - auto stream_id = header.opcode - OP_Fragment; - auto stream = &m_streams[stream_id]; - - auto order = CompareSequence(stream->sequence_in, sequence); - - if (order == SequenceFuture) { - SendOutOfOrderAck(stream_id, sequence); - AddToQueue(stream_id, sequence, p); - } - else if (order == SequencePast) { - SendAck(stream_id, stream->sequence_in - 1); - } - else { - RemoveFromQueue(stream_id, sequence); - SendAck(stream_id, stream->sequence_in); - stream->sequence_in++; - - if (stream->fragment_total_bytes == 0) { - auto fragheader = p.GetSerialize(0); - stream->fragment_total_bytes = NetworkToHost(fragheader.total_size); - stream->fragment_current_bytes = 0; - stream->fragment_packet.Reserve(stream->fragment_total_bytes); - stream->fragment_packet.PutData( - stream->fragment_current_bytes, - (char*)p.Data() + DaybreakReliableFragmentHeader::size(), p.Length() - DaybreakReliableFragmentHeader::size()); - - stream->fragment_current_bytes += (uint32_t)(p.Length() - DaybreakReliableFragmentHeader::size()); + else if (order == SequencePast) { + SendAck(stream_id, stream->sequence_in - 1); } else { - stream->fragment_packet.PutData( - stream->fragment_current_bytes, - (char*)p.Data() + DaybreakReliableHeader::size(), p.Length() - DaybreakReliableHeader::size()); + RemoveFromQueue(stream_id, sequence); + SendAck(stream_id, stream->sequence_in); + stream->sequence_in++; + StaticPacket next((char*)p.Data() + DaybreakReliableHeader::size(), p.Length() - DaybreakReliableHeader::size()); + ProcessDecodedPacket(next); + } - stream->fragment_current_bytes += (uint32_t)(p.Length() - DaybreakReliableHeader::size()); + break; + } - if (stream->fragment_current_bytes >= stream->fragment_total_bytes) { - ProcessDecodedPacket(stream->fragment_packet); - stream->fragment_packet.Clear(); - stream->fragment_total_bytes = 0; + case OP_Fragment: + case OP_Fragment2: + case OP_Fragment3: + case OP_Fragment4: + { + auto header = p.GetSerialize(0); + auto sequence = NetworkToHost(header.sequence); + auto stream_id = header.opcode - OP_Fragment; + auto stream = &m_streams[stream_id]; + + auto order = CompareSequence(stream->sequence_in, sequence); + + if (order == SequenceFuture) { + SendOutOfOrderAck(stream_id, sequence); + AddToQueue(stream_id, sequence, p); + } + else if (order == SequencePast) { + SendAck(stream_id, stream->sequence_in - 1); + } + else { + RemoveFromQueue(stream_id, sequence); + SendAck(stream_id, stream->sequence_in); + stream->sequence_in++; + + if (stream->fragment_total_bytes == 0) { + auto fragheader = p.GetSerialize(0); + stream->fragment_total_bytes = NetworkToHost(fragheader.total_size); stream->fragment_current_bytes = 0; + stream->fragment_packet.Reserve(stream->fragment_total_bytes); + stream->fragment_packet.PutData( + stream->fragment_current_bytes, + (char*)p.Data() + DaybreakReliableFragmentHeader::size(), p.Length() - DaybreakReliableFragmentHeader::size()); + + stream->fragment_current_bytes += (uint32_t)(p.Length() - DaybreakReliableFragmentHeader::size()); + } + else { + stream->fragment_packet.PutData( + stream->fragment_current_bytes, + (char*)p.Data() + DaybreakReliableHeader::size(), p.Length() - DaybreakReliableHeader::size()); + + stream->fragment_current_bytes += (uint32_t)(p.Length() - DaybreakReliableHeader::size()); + + if (stream->fragment_current_bytes >= stream->fragment_total_bytes) { + ProcessDecodedPacket(stream->fragment_packet); + stream->fragment_packet.Clear(); + stream->fragment_total_bytes = 0; + stream->fragment_current_bytes = 0; + } } } + + break; } - break; - } - - case OP_Ack: - case OP_Ack2: - case OP_Ack3: - case OP_Ack4: - { - auto header = p.GetSerialize(0); - auto sequence = NetworkToHost(header.sequence); - auto stream_id = header.opcode - OP_Ack; - Ack(stream_id, sequence); - break; - } - - case OP_OutOfOrderAck: - case OP_OutOfOrderAck2: - case OP_OutOfOrderAck3: - case OP_OutOfOrderAck4: - { - auto header = p.GetSerialize(0); - auto sequence = NetworkToHost(header.sequence); - auto stream_id = header.opcode - OP_OutOfOrderAck; - OutOfOrderAck(stream_id, sequence); - break; - } - - case OP_SessionDisconnect: - { - if (m_status == StatusConnected || m_status == StatusDisconnecting) { - FlushBuffer(); - SendDisconnect(); + case OP_Ack: + case OP_Ack2: + case OP_Ack3: + case OP_Ack4: + { + auto header = p.GetSerialize(0); + auto sequence = NetworkToHost(header.sequence); + auto stream_id = header.opcode - OP_Ack; + Ack(stream_id, sequence); + break; } - ChangeStatus(StatusDisconnecting); - break; - } - - case OP_Padding: - { - auto self = m_self.lock(); - if (m_owner->m_on_packet_recv && self) { - m_owner->m_on_packet_recv(self, StaticPacket((char*)p.Data() + 1, p.Length() - 1)); + case OP_OutOfOrderAck: + case OP_OutOfOrderAck2: + case OP_OutOfOrderAck3: + case OP_OutOfOrderAck4: + { + auto header = p.GetSerialize(0); + auto sequence = NetworkToHost(header.sequence); + auto stream_id = header.opcode - OP_OutOfOrderAck; + OutOfOrderAck(stream_id, sequence); + break; } - break; - } - case OP_SessionStatRequest: - { - auto request = p.GetSerialize(0); - DaybreakSessionStatResponse response; - response.zero = 0; - response.opcode = OP_SessionStatResponse; - response.timestamp = request.timestamp; - response.our_timestamp = EQ::Net::HostToNetwork(std::chrono::duration_cast(std::chrono::high_resolution_clock::now().time_since_epoch()).count()); - response.client_sent = request.packets_sent; - response.client_recv = request.packets_recv; - response.server_sent = EQ::Net::HostToNetwork(m_stats.sent_packets); - response.server_recv = EQ::Net::HostToNetwork(m_stats.recv_packets); - DynamicPacket out; - out.PutSerialize(0, response); - InternalSend(out); - break; - } - case OP_SessionStatResponse: - break; - default: - LogF(Logs::Detail, Logs::Netcode, "Unhandled opcode {0:#x}", p.GetInt8(1)); - break; + case OP_SessionDisconnect: + { + if (m_status == StatusConnected || m_status == StatusDisconnecting) { + FlushBuffer(); + SendDisconnect(); + } + + ChangeStatus(StatusDisconnecting); + break; + } + + case OP_Padding: + { + auto self = m_self.lock(); + if (m_owner->m_on_packet_recv && self) { + m_owner->m_on_packet_recv(self, StaticPacket((char*)p.Data() + 1, p.Length() - 1)); + } + break; + } + case OP_SessionStatRequest: + { + auto request = p.GetSerialize(0); + + DaybreakSessionStatResponse response; + response.zero = 0; + response.opcode = OP_SessionStatResponse; + response.timestamp = request.timestamp; + response.our_timestamp = EQ::Net::HostToNetwork(std::chrono::duration_cast(std::chrono::high_resolution_clock::now().time_since_epoch()).count()); + response.client_sent = request.packets_sent; + response.client_recv = request.packets_recv; + response.server_sent = EQ::Net::HostToNetwork(m_stats.sent_packets); + response.server_recv = EQ::Net::HostToNetwork(m_stats.recv_packets); + DynamicPacket out; + out.PutSerialize(0, response); + InternalSend(out); + break; + } + case OP_SessionStatResponse: + break; + default: + LogF(Logs::Detail, Logs::Netcode, "Unhandled opcode {0:#x}", p.GetInt8(1)); + break; } } else { @@ -782,16 +786,16 @@ bool EQ::Net::DaybreakConnection::ValidateCRC(Packet &p) int calculated = 0; int actual = 0; switch (m_crc_bytes) { - case 2: - actual = NetworkToHost(*(int16_t*)&data[p.Length() - (size_t)m_crc_bytes]) & 0xffff; - calculated = Crc32(data, (int)(p.Length() - (size_t)m_crc_bytes), m_encode_key) & 0xffff; - break; - case 4: - actual = NetworkToHost(*(int32_t*)&data[p.Length() - (size_t)m_crc_bytes]); - calculated = Crc32(data, (int)(p.Length() - (size_t)m_crc_bytes), m_encode_key); - break; - default: - return false; + case 2: + actual = NetworkToHost(*(int16_t*)&data[p.Length() - (size_t)m_crc_bytes]) & 0xffff; + calculated = Crc32(data, (int)(p.Length() - (size_t)m_crc_bytes), m_encode_key) & 0xffff; + break; + case 4: + actual = NetworkToHost(*(int32_t*)&data[p.Length() - (size_t)m_crc_bytes]); + calculated = Crc32(data, (int)(p.Length() - (size_t)m_crc_bytes), m_encode_key); + break; + default: + return false; } if (actual == calculated) { @@ -809,14 +813,14 @@ void EQ::Net::DaybreakConnection::AppendCRC(Packet &p) int calculated = 0; switch (m_crc_bytes) { - case 2: - calculated = Crc32(p.Data(), (int)p.Length(), m_encode_key) & 0xffff; - p.PutInt16(p.Length(), EQ::Net::HostToNetwork((int16_t)calculated)); - break; - case 4: - calculated = Crc32(p.Data(), (int)p.Length(), m_encode_key); - p.PutInt32(p.Length(), EQ::Net::HostToNetwork(calculated)); - break; + case 2: + calculated = Crc32(p.Data(), (int)p.Length(), m_encode_key) & 0xffff; + p.PutInt16(p.Length(), EQ::Net::HostToNetwork((int16_t)calculated)); + break; + case 4: + calculated = Crc32(p.Data(), (int)p.Length(), m_encode_key); + p.PutInt32(p.Length(), EQ::Net::HostToNetwork(calculated)); + break; } } @@ -1055,7 +1059,7 @@ void EQ::Net::DaybreakConnection::Ack(int stream, uint16_t seq) while (iter != s->sent_packets.end()) { auto order = CompareSequence(seq, iter->first); - if (order != SequenceFuture) { + if (order != SequenceFuture) { uint64_t round_time = (uint64_t)std::chrono::duration_cast(now - iter->second.last_sent).count(); m_stats.max_ping = std::max(m_stats.max_ping, round_time); @@ -1187,27 +1191,24 @@ void EQ::Net::DaybreakConnection::InternalSend(Packet &p) if (PacketCanBeEncoded(p)) { DynamicPacket out; - - if (p.GetUInt8(0) != 0) { - out.PutUInt8(0, 0); - out.PutUInt8(1, OP_Combined); - out.PutUInt8(2, p.Length()); - out.PutPacket(3, p); - } - else { - out.PutPacket(0, p); - } + out.PutPacket(0, p); for (int i = 0; i < 2; ++i) { switch (m_encode_passes[i]) { - case EncodeCompression: - Compress(out, DaybreakHeader::size(), out.Length() - DaybreakHeader::size()); - break; - case EncodeXOR: - Encode(out, DaybreakHeader::size(), out.Length() - DaybreakHeader::size()); - break; - default: - break; + case EncodeCompression: + if(out.GetInt8(0) == 0) + Compress(out, DaybreakHeader::size(), out.Length() - DaybreakHeader::size()); + else + Compress(out, 1, out.Length() - 1); + break; + case EncodeXOR: + if (out.GetInt8(0) == 0) + Encode(out, DaybreakHeader::size(), out.Length() - DaybreakHeader::size()); + else + Encode(out, 1, out.Length() - 1); + break; + default: + break; } } diff --git a/common/net/daybreak_connection.h b/common/net/daybreak_connection.h index 409471589..016ef3258 100644 --- a/common/net/daybreak_connection.h +++ b/common/net/daybreak_connection.h @@ -218,7 +218,7 @@ namespace EQ encode_passes[1] = DaybreakEncodeType::EncodeNone; port = 0; hold_size = 448; - hold_length_ms = 10; + hold_length_ms = 50; simulated_in_packet_loss = 0; simulated_out_packet_loss = 0; tic_rate_hertz = 60.0; diff --git a/common/net/eqstream.cpp b/common/net/eqstream.cpp index 26fdf1d8e..ffa441939 100644 --- a/common/net/eqstream.cpp +++ b/common/net/eqstream.cpp @@ -81,7 +81,12 @@ void EQ::Net::EQStream::QueuePacket(const EQApplicationPacket *p, bool ack_req) break; } - m_connection->QueuePacket(out); + if (ack_req) { + m_connection->QueuePacket(out); + } + else { + m_connection->QueuePacket(out, 0, false); + } } } diff --git a/common/net/packet.cpp b/common/net/packet.cpp index 347774459..4c7dc74b6 100644 --- a/common/net/packet.cpp +++ b/common/net/packet.cpp @@ -139,6 +139,10 @@ void EQ::Net::Packet::PutCString(size_t offset, const char *str) void EQ::Net::Packet::PutPacket(size_t offset, const Packet &p) { + if (p.Length() == 0) { + return; + } + if (Length() < offset + p.Length()) { if (!Resize(offset + p.Length())) { throw std::out_of_range("Packet::PutPacket(), could not resize packet and would of written past the end."); @@ -150,6 +154,10 @@ void EQ::Net::Packet::PutPacket(size_t offset, const Packet &p) void EQ::Net::Packet::PutData(size_t offset, void *data, size_t length) { + if (length == 0) { + return; + } + if (Length() < offset + length) { if (!Resize(offset + length)) { throw std::out_of_range("Packet::PutData(), could not resize packet and would of written past the end."); diff --git a/common/patches/rof.cpp b/common/patches/rof.cpp index e342c5410..7ce6f900f 100644 --- a/common/patches/rof.cpp +++ b/common/patches/rof.cpp @@ -1849,6 +1849,7 @@ namespace RoF eq->Text_Count = 4096; memcpy(eq->Text, emu->Text, sizeof(eq->Text)); OUT(Buttons); + OUT(SoundControls); OUT(Duration); OUT(PopupID); OUT(NegativeID); @@ -3929,7 +3930,7 @@ namespace RoF if (strlen(emu->suffix)) PacketSize += strlen(emu->suffix) + 1; - bool ShowName = 1; + bool ShowName = emu->show_name; if (emu->bodytype >= 66) { emu->race = 127; diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index e38fde6f1..2e8d8458b 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -1927,6 +1927,7 @@ namespace RoF2 eq->Text_Count = 4096; memcpy(eq->Text, emu->Text, sizeof(eq->Text)); OUT(Buttons); + OUT(SoundControls); OUT(Duration); OUT(PopupID); OUT(NegativeID); @@ -4085,7 +4086,7 @@ namespace RoF2 PacketSize += strlen(emu->DestructibleString) + 1; } - bool ShowName = 1; + bool ShowName = emu->show_name; if (emu->bodytype >= 66) { emu->race = 127; @@ -4119,6 +4120,7 @@ namespace RoF2 VARSTRUCT_ENCODE_STRING(Buffer, emu->name); VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->spawnId); VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->level); + // actually melee range variable, this probably screws the shit out of melee ranges :D if (emu->DestructibleObject) { VARSTRUCT_ENCODE_TYPE(float, Buffer, 10); // was int and 0x41200000 @@ -4127,7 +4129,7 @@ namespace RoF2 { VARSTRUCT_ENCODE_TYPE(float, Buffer, SpawnSize - 0.7); // Eye Height? } - VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->NPC); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->NPC); // 0 PC, 1 NPC etc structs::Spawn_Struct_Bitfields *Bitfields = (structs::Spawn_Struct_Bitfields*)Buffer; @@ -4158,6 +4160,7 @@ namespace RoF2 Buffer += sizeof(structs::Spawn_Struct_Bitfields); + // actually part of bitfields uint8 OtherData = 0; if (emu->class_ == 62) //LDoN Chest @@ -4173,6 +4176,7 @@ namespace RoF2 OtherData = OtherData | 0xe1; // Live has 0xe1 for OtherData VARSTRUCT_ENCODE_TYPE(uint8, Buffer, OtherData); + // float EmitterScalingRadius if (emu->DestructibleObject) { @@ -4182,6 +4186,7 @@ namespace RoF2 { VARSTRUCT_ENCODE_TYPE(float, Buffer, -1); // unknown3 } + // int DefaultEmitterID VARSTRUCT_ENCODE_TYPE(float, Buffer, 0); // unknown4 if (emu->DestructibleObject || emu->class_ == 62) @@ -4191,8 +4196,9 @@ namespace RoF2 VARSTRUCT_ENCODE_STRING(Buffer, emu->DestructibleString); VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->DestructibleAppearance); - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->DestructibleUnk1); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->DestructibleUnk1); // ObjectAnimationID + // these 10 are SoundIDs VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->DestructibleID1); VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->DestructibleID2); VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->DestructibleID3); @@ -4204,8 +4210,8 @@ namespace RoF2 VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->DestructibleUnk5); VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->DestructibleUnk6); VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->DestructibleUnk7); - VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->DestructibleUnk8); - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->DestructibleUnk9); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->DestructibleUnk8); // bInteractiveObjectCollidable + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->DestructibleUnk9); // IteractiveObjectType } @@ -4213,6 +4219,7 @@ namespace RoF2 { // Setting this next field to zero will cause a crash. Looking at ShowEQ, if it is zero, the bodytype field is not // present. Will sort that out later. + // This is the CharacterPropertyHash, it can have multiple fields VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 1); // This is a properties count field VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->bodytype); } @@ -4232,10 +4239,10 @@ namespace RoF2 VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->drakkin_tattoo); VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->drakkin_details); - VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->equip_chest2); - VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // unknown9 - VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // unknown10 - VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->helm); // unknown11 + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->equip_chest2); // InNonPCRaceIllusion + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // material + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // variation + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->helm); // headtype VARSTRUCT_ENCODE_TYPE(float, Buffer, emu->size); VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->face); @@ -4243,6 +4250,7 @@ namespace RoF2 VARSTRUCT_ENCODE_TYPE(float, Buffer, emu->runspeed); VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->race); + // From MQ2: todo: create enum for this byte. Holding: Nothing=0 A RightHand Weapon=1 A Shield=2 Dual Wielding Two Weapons=3 A Spear=4 A LeftHand Weapon=5 A Two Handed Weapon=6 A bow=7 VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // ShowEQ calls this 'Holding' VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->deity); if (emu->NPC) @@ -4275,19 +4283,19 @@ namespace RoF2 VARSTRUCT_ENCODE_STRING(Buffer, emu->lastName); - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // aatitle ?? + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // aatitle VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->NPC ? 0 : 1); // unknown - Must be 1 for guild name to be shown abover players head. - VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // unknown + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // TempPet VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->petOwnerId); - VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // unknown13 + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // FindBits MQ2 name VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->PlayerState); - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // unknown15 - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // unknown16 - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // unknown17 - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0xffffffff); // unknown18 - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0xffffffff); // unknown19 + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // NpcTintIndex + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // PrimaryTintIndex + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // SecondaryTintIndex + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0xffffffff); // These do something with OP_WeaponEquip1 + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0xffffffff); // ^ if ((emu->NPC == 0) || (emu->race <= 12) || (emu->race == 128) || (emu->race == 130) || (emu->race == 330) || (emu->race == 522)) { @@ -4355,12 +4363,16 @@ namespace RoF2 VARSTRUCT_ENCODE_STRING(Buffer, emu->suffix); } + // skipping two ints + // unknown, maybe some sort of spawn ID + // SplineID -- no idea Buffer += 8; VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->IsMercenary); - VARSTRUCT_ENCODE_STRING(Buffer, "0000000000000000"); - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0xffffffff); - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0xffffffff); + VARSTRUCT_ENCODE_STRING(Buffer, "0000000000000000"); // RealEstateItemGuid + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0xffffffff); // RealEstateID + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0xffffffff); // RealEstateItemID // 29 zero bytes follow + // PhysicsEffects follow here ... unsure what they are but it's a count followed by a struct like {spellid, casterid, effectid, baseeffect} Buffer += 29; if (Buffer != (BufferStart + PacketSize)) { diff --git a/common/patches/rof2_structs.h b/common/patches/rof2_structs.h index 13befdad9..1b3c41794 100644 --- a/common/patches/rof2_structs.h +++ b/common/patches/rof2_structs.h @@ -328,38 +328,43 @@ showeq -> eqemu sed -e 's/_t//g' -e 's/seto_0xFF/set_to_0xFF/g' */ +// I think this is actually 5 bytes +// IDA's pseudocode reads this as 5 bytes pulled into 2 DWORDs struct Spawn_Struct_Bitfields { + // byte 1 /*00*/ unsigned gender:2; // Gender (0=male, 1=female, 2=monster) /*02*/ unsigned ispet:1; // Guessed based on observing live spawns /*03*/ unsigned afk:1; // 0=no, 1=afk /*04*/ unsigned anon:2; // 0=normal, 1=anon, 2=roleplay /*06*/ unsigned gm:1; -/*06*/ unsigned sneak:1; +/*07*/ unsigned sneak:1; + // byte 2 /*08*/ unsigned lfg:1; -/*09*/ unsigned unknown09:1; -/*10*/ unsigned invis:1; // May have invis & sneak the wrong way around ... not sure how to tell which is which -/*11*/ unsigned invis1:1; // GM Invis? Can only be seen with #gm on - same for the below -/*12*/ unsigned invis2:1; // This one also make the NPC/PC invis -/*13*/ unsigned invis3:1; // This one also make the NPC/PC invis -/*14*/ unsigned invis4:1; // This one also make the NPC/PC invis -/*15*/ unsigned invis6:1; // This one also make the NPC/PC invis -/*16*/ unsigned invis7:1; // This one also make the NPC/PC invis -/*17*/ unsigned invis8:1; // This one also make the NPC/PC invis -/*18*/ unsigned invis9:1; // This one also make the NPC/PC invis -/*19*/ unsigned invis10:1; // This one also make the NPC/PC invis -/*20*/ unsigned invis11:1; // This one also make the NPC/PC invis -/*21*/ unsigned invis12:1; // This one also make the NPC/PC invis +/*09*/ unsigned betabuffed:1; +/*10*/ unsigned invis:12; // there are 3000 different (non-GM) invis levels /*22*/ unsigned linkdead:1; // 1 Toggles LD on or off after name. Correct for RoF2 /*23*/ unsigned showhelm:1; + // byte 4 /*24*/ unsigned unknown24:1; // Prefixes name with ! /*25*/ unsigned trader:1; -/*26*/ unsigned unknown26:1; +/*26*/ unsigned animationonpop:1; /*27*/ unsigned targetable:1; /*28*/ unsigned targetable_with_hotkey:1; /*29*/ unsigned showname:1; -/*30*/ unsigned unknown30:1; -/*30*/ unsigned untargetable:1; // Untargetable with mouse +/*30*/ unsigned idleanimationsoff:1; // what we called statue? +/*31*/ unsigned untargetable:1; // bClickThrough +/* do these later +32 unsigned buyer:1; +33 unsigned offline:1; +34 unsigned interactiveobject:1; +35 unsigned flung:1; // hmm this vfunc appears to do stuff with leve and flung variables +36 unsigned title:1; +37 unsigned suffix:1; +38 unsigned padding1:1; +39 unsigned padding2:1; +40 unsinged padding3:1; +*/ /* // Unknown in RoF2 unsigned betabuffed:1; @@ -498,7 +503,7 @@ struct Spawn_Struct /*0000*/ //char title[0]; // only read if(hasTitleOrSuffix & 4) /*0000*/ //char suffix[0]; // only read if(hasTitleOrSuffix & 8) - char unknown20[8]; + char unknown20[8]; // 2 ints, first unknown, 2nd SplineID uint8 IsMercenary; // If NPC == 1 and this == 1, then the NPC name is Orange. /*0000*/ char unknown21[55]; }; @@ -2092,7 +2097,7 @@ struct OnLevelMessage_Struct { /*0000*/ uint32 ButtonName1_Count; /*0000*/ char ButtonName1[25]; /*0000*/ uint8 Buttons; -/*0000*/ uint8 Unknown4275; // Something to do with audio controls +/*0000*/ uint8 SoundControls; // Something to do with audio controls /*0000*/ uint32 Duration; /*0000*/ uint32 PopupID; // If none zero, a response packet with 00 00 00 00 is returned on clicking the left button /*0000*/ uint32 NegativeID; // If none zero, a response packet with 01 00 00 00 is returned on clicking the right button diff --git a/common/patches/rof_structs.h b/common/patches/rof_structs.h index 32c7c0d28..24439d7f0 100644 --- a/common/patches/rof_structs.h +++ b/common/patches/rof_structs.h @@ -339,18 +339,7 @@ struct Spawn_Struct_Bitfields /*06*/ unsigned sneak:1; /*08*/ unsigned lfg:1; /*09*/ unsigned unknown09:1; -/*10*/ unsigned invis:1; // May have invis & sneak the wrong way around ... not sure how to tell which is which -/*11*/ unsigned invis1:1; // GM Invis? Can only be seen with #gm on - same for the below -/*12*/ unsigned invis2:1; // This one also make the NPC/PC invis -/*13*/ unsigned invis3:1; // This one also make the NPC/PC invis -/*14*/ unsigned invis4:1; // This one also make the NPC/PC invis -/*15*/ unsigned invis6:1; // This one also make the NPC/PC invis -/*16*/ unsigned invis7:1; // This one also make the NPC/PC invis -/*17*/ unsigned invis8:1; // This one also make the NPC/PC invis -/*18*/ unsigned invis9:1; // This one also make the NPC/PC invis -/*19*/ unsigned invis10:1; // This one also make the NPC/PC invis -/*20*/ unsigned invis11:1; // This one also make the NPC/PC invis -/*21*/ unsigned invis12:1; // This one also make the NPC/PC invis +/*10*/ unsigned invis:12; // there are 3000 different (non-GM) invis levels /*22*/ unsigned linkdead:1; // 1 Toggles LD on or off after name. Correct for RoF /*23*/ unsigned showhelm:1; /*24*/ unsigned unknown24:1; // Prefixes name with ! @@ -2122,7 +2111,7 @@ struct OnLevelMessage_Struct { /*0000*/ uint32 ButtonName1_Count; /*0000*/ char ButtonName1[25]; /*0000*/ uint8 Buttons; -/*0000*/ uint8 Unknown4275; // Something to do with audio controls +/*0000*/ uint8 SoundControls; // Something to do with audio controls /*0000*/ uint32 Duration; /*0000*/ uint32 PopupID; // If none zero, a response packet with 00 00 00 00 is returned on clicking the left button /*0000*/ uint32 NegativeID; // If none zero, a response packet with 01 00 00 00 is returned on clicking the right button diff --git a/common/patches/sod.cpp b/common/patches/sod.cpp index 9d4db7737..276f939e5 100644 --- a/common/patches/sod.cpp +++ b/common/patches/sod.cpp @@ -1373,6 +1373,7 @@ namespace SoD memcpy(eq->Title, emu->Title, sizeof(eq->Title)); memcpy(eq->Text, emu->Text, sizeof(eq->Text)); OUT(Buttons); + OUT(SoundControls); OUT(Duration); OUT(PopupID); OUT(NegativeID); @@ -2559,7 +2560,7 @@ namespace SoD PacketSize += strlen(emu->DestructibleString) + 1; } - bool ShowName = 1; + bool ShowName = emu->show_name; if (emu->bodytype >= 66) { emu->race = 127; @@ -3291,73 +3292,60 @@ namespace SoD switch (eq->command) { - case 0x04: - emu->command = 0x00; // /pet health + case 1: // back off + emu->command = 28; break; - case 0x10: - emu->command = 0x01; // /pet leader + case 2: // get lost + emu->command = 29; break; - case 0x07: - emu->command = 0x02; // /pet attack or Pet Window + case 3: // as you were ??? + emu->command = 4; // fuck it follow break; - case 0x03: // Case Guessed - emu->command = 0x03; // /pet qattack - case 0x08: - emu->command = 0x04; // /pet follow or Pet Window + case 4: // report HP + emu->command = 0; break; - case 0x05: - emu->command = 0x05; // /pet guard or Pet Window + case 5: // guard here + emu->command = 5; break; - case 0x09: - emu->command = 0x07; // /pet sit or Pet Window + case 6: // guard me + emu->command = 4; // fuck it follow break; - case 0x0a: - emu->command = 0x08; // /pet stand or Pet Window + case 7: // attack + emu->command = 2; break; - case 0x06: - emu->command = 0x1e; // /pet guard me + case 8: // follow + emu->command = 4; break; - case 0x0f: // Case Made Up - emu->command = 0x09; // /pet stop + case 9: // sit down + emu->command = 7; break; - case 0x0b: - emu->command = 0x0d; // /pet taunt or Pet Window + case 10: // stand up + emu->command = 8; break; - case 0x0e: - emu->command = 0x0e; // /pet notaunt or Pet Window + case 11: // taunt toggle + emu->command = 12; break; - case 0x0c: - emu->command = 0x0f; // /pet hold + case 12: // hold toggle + emu->command = 15; break; - case 0x1b: - emu->command = 0x10; // /pet hold on + case 13: // taunt on + emu->command = 13; break; - case 0x1c: - emu->command = 0x11; // /pet hold off + case 14: // no taunt + emu->command = 14; break; - case 0x11: - emu->command = 0x12; // Slumber? + // 15 is target, doesn't send packet + case 16: // leader + emu->command = 1; break; - case 0x12: - emu->command = 0x15; // /pet no cast + case 17: // feign + emu->command = 27; break; - case 0x0d: // Case Made Up - emu->command = 0x16; // Pet Window No Cast + case 18: // no cast toggle + emu->command = 21; break; - case 0x13: - emu->command = 0x18; // /pet focus - break; - case 0x19: - emu->command = 0x19; // /pet focus on - break; - case 0x1a: - emu->command = 0x1a; // /pet focus off - break; - case 0x01: - emu->command = 0x1c; // /pet back off - break; - case 0x02: - emu->command = 0x1d; // /pet get lost + case 19: // focus toggle + emu->command = 24; break; default: emu->command = eq->command; diff --git a/common/patches/sod_structs.h b/common/patches/sod_structs.h index fdcb03658..af449f758 100644 --- a/common/patches/sod_structs.h +++ b/common/patches/sod_structs.h @@ -250,8 +250,7 @@ struct Spawn_Struct_Bitfields unsigned sneak:1; unsigned lfg:1; unsigned padding5:1; - unsigned invis:1; // 0 = visible, 1 = invis/sneaking - unsigned padding7:11; + unsigned invis:12; // there are 3000 different (non-GM) invis levels unsigned gm:1; unsigned anon:2; // 0=normal, 1=anon, 2=roleplay unsigned gender:2; // Gender (0=male, 1=female, 2=monster) @@ -1760,7 +1759,7 @@ struct OnLevelMessage_Struct { /*4224*/ char ButtonName0[25]; // If Buttons = 1, these two are the text for the left and right buttons respectively /*4249*/ char ButtonName1[25]; /*4274*/ uint8 Buttons; -/*4275*/ uint8 Unknown4275; // Something to do with audio controls +/*4275*/ uint8 SoundControls; // Something to do with audio controls /*4276*/ uint32 Duration; /*4280*/ uint32 PopupID; // If none zero, a response packet with 00 00 00 00 is returned on clicking the left button /*4284*/ uint32 NegativeID; // If none zero, a response packet with 01 00 00 00 is returned on clicking the right button diff --git a/common/patches/sof.cpp b/common/patches/sof.cpp index 456bc7339..77d65ce16 100644 --- a/common/patches/sof.cpp +++ b/common/patches/sof.cpp @@ -2097,7 +2097,7 @@ namespace SoF int k; for (r = 0; r < entrycount; r++, eq++, emu++) { - eq->showname = 1; //New Field - Toggles Name Display on or off - 0 = off, 1 = on + eq->showname = emu->show_name ? 1 : 0; //New Field - Toggles Name Display on or off - 0 = off, 1 = on eq->linkdead = 0; //New Field - Toggles LD on or off after name - 0 = off, 1 = on eq->statue = 0; //New Field - 1 freezes animation eq->showhelm = emu->showhelm; @@ -2136,10 +2136,10 @@ namespace SoF eq->findable = emu->findable; if (emu->bodytype >= 66) { - eq->bodytype = 11; //non-targetable - eq->showname = 0; //no visible name - eq->race = 127; //invisible man - eq->gender = 0; //invisible men are gender 0 + eq->bodytype = 11; //non-targetable + eq->showname = 0; //no visible name + eq->race = 127; //invisible man + eq->gender = 0; //invisible men are gender 0 } else { @@ -2675,73 +2675,60 @@ namespace SoF switch (eq->command) { - case 0x04: - emu->command = 0x00; // /pet health + case 1: // back off + emu->command = 28; break; - case 0x10: - emu->command = 0x01; // /pet leader + case 2: // get lost + emu->command = 29; break; - case 0x07: - emu->command = 0x02; // /pet attack or Pet Window + case 3: // as you were ??? + emu->command = 4; // fuck it follow break; - case 0x03: // Case Guessed - emu->command = 0x03; // /pet qattack - case 0x08: - emu->command = 0x04; // /pet follow or Pet Window + case 4: // report HP + emu->command = 0; break; - case 0x05: - emu->command = 0x05; // /pet guard or Pet Window + case 5: // guard here + emu->command = 5; break; - case 0x09: - emu->command = 0x07; // /pet sit or Pet Window + case 6: // guard me + emu->command = 4; // fuck it follow break; - case 0x0a: - emu->command = 0x08; // /pet stand or Pet Window + case 7: // attack + emu->command = 2; break; - case 0x06: - emu->command = 0x1e; // /pet guard me + case 8: // follow + emu->command = 4; break; - case 0x0f: // Case Made Up - emu->command = 0x09; // Stop? + case 9: // sit down + emu->command = 7; break; - case 0x0b: - emu->command = 0x0d; // /pet taunt or Pet Window + case 10: // stand up + emu->command = 8; break; - case 0x0e: - emu->command = 0x0e; // /pet notaunt or Pet Window + case 11: // taunt toggle + emu->command = 12; break; - case 0x0c: - emu->command = 0x0f; // /pet hold + case 12: // hold toggle + emu->command = 15; break; - case 0x1b: - emu->command = 0x10; // /pet hold on + case 13: // taunt on + emu->command = 13; break; - case 0x1c: - emu->command = 0x11; // /pet hold off + case 14: // no taunt + emu->command = 14; break; - case 0x11: - emu->command = 0x12; // Slumber? + // 15 is target, doesn't send packet + case 16: // leader + emu->command = 1; break; - case 0x12: - emu->command = 0x15; // /pet no cast + case 17: // feign + emu->command = 27; break; - case 0x0d: // Case Made Up - emu->command = 0x16; // Pet Window No Cast + case 18: // no cast toggle + emu->command = 21; break; - case 0x13: - emu->command = 0x18; // /pet focus - break; - case 0x19: - emu->command = 0x19; // /pet focus on - break; - case 0x1a: - emu->command = 0x1a; // /pet focus off - break; - case 0x01: - emu->command = 0x1c; // /pet back off - break; - case 0x02: - emu->command = 0x1d; // /pet get lost + case 19: // focus toggle + emu->command = 24; break; default: emu->command = eq->command; diff --git a/common/patches/titanium.cpp b/common/patches/titanium.cpp index 302befb6a..e9679dc06 100644 --- a/common/patches/titanium.cpp +++ b/common/patches/titanium.cpp @@ -2030,73 +2030,60 @@ namespace Titanium switch (eq->command) { - case 0x04: - emu->command = 0x00; // /pet health + case 1: // back off + emu->command = 28; break; - case 0x10: - emu->command = 0x01; // /pet leader + case 2: // get lost + emu->command = 29; break; - case 0x07: - emu->command = 0x02; // /pet attack or Pet Window + case 3: // as you were ??? + emu->command = 4; // fuck it follow break; - case 0x03: // Case Guessed - emu->command = 0x03; // /pet qattack - case 0x08: - emu->command = 0x04; // /pet follow or Pet Window + case 4: // report HP + emu->command = 0; break; - case 0x05: - emu->command = 0x05; // /pet guard or Pet Window + case 5: // guard here + emu->command = 5; break; - case 0x09: - emu->command = 0x07; // /pet sit or Pet Window + case 6: // guard me + emu->command = 4; // fuck it follow break; - case 0x0a: - emu->command = 0x08; // /pet stand or Pet Window + case 7: // attack + emu->command = 2; break; - case 0x06: - emu->command = 0x1e; // /pet guard me + case 8: // follow + emu->command = 4; break; - case 0x0f: // Case Made Up - emu->command = 0x09; // Stop? + case 9: // sit down + emu->command = 7; break; - case 0x0b: - emu->command = 0x0d; // /pet taunt or Pet Window + case 10: // stand up + emu->command = 8; break; - case 0x0e: - emu->command = 0x0e; // /pet notaunt or Pet Window + case 11: // taunt toggle + emu->command = 12; break; - case 0x0c: - emu->command = 0x0f; // /pet hold + case 12: // hold toggle + emu->command = 15; break; - case 0x1b: - emu->command = 0x10; // /pet hold on + case 13: // taunt on + emu->command = 13; break; - case 0x1c: - emu->command = 0x11; // /pet hold off + case 14: // no taunt + emu->command = 14; break; - case 0x11: - emu->command = 0x12; // Slumber? + // 15 is target, doesn't send packet + case 16: // leader + emu->command = 1; break; - case 0x12: - emu->command = 0x15; // /pet no cast + case 17: // feign + emu->command = 27; break; - case 0x0d: // Case Made Up - emu->command = 0x16; // Pet Window No Cast + case 18: // no cast toggle + emu->command = 21; break; - case 0x13: - emu->command = 0x18; // /pet focus - break; - case 0x19: - emu->command = 0x19; // /pet focus on - break; - case 0x1a: - emu->command = 0x1a; // /pet focus off - break; - case 0x01: - emu->command = 0x1c; // /pet back off - break; - case 0x02: - emu->command = 0x1d; // /pet get lost + case 19: // focus toggle + emu->command = 24; break; default: emu->command = eq->command; diff --git a/common/patches/uf.cpp b/common/patches/uf.cpp index e2093aef0..8b0c86cc5 100644 --- a/common/patches/uf.cpp +++ b/common/patches/uf.cpp @@ -1607,6 +1607,7 @@ namespace UF memcpy(eq->Title, emu->Title, sizeof(eq->Title)); memcpy(eq->Text, emu->Text, sizeof(eq->Text)); OUT(Buttons); + OUT(SoundControls); OUT(Duration); OUT(PopupID); OUT(NegativeID); @@ -2843,7 +2844,7 @@ namespace UF PacketSize += strlen(emu->DestructibleString) + 1; } - bool ShowName = 1; + bool ShowName = emu->show_name; if (emu->bodytype >= 66) { emu->race = 127; diff --git a/common/patches/uf_structs.h b/common/patches/uf_structs.h index 560a9378d..b187f9f82 100644 --- a/common/patches/uf_structs.h +++ b/common/patches/uf_structs.h @@ -250,8 +250,7 @@ struct Spawn_Struct_Bitfields unsigned sneak:1; unsigned lfg:1; unsigned padding5:1; - unsigned invis:1; // 0 = visible, 1 = invis/sneaking - unsigned padding7:11; + unsigned invis:12; // there are 3000 different (non-GM) invis levels unsigned gm:1; unsigned anon:2; // 0=normal, 1=anon, 2=roleplay unsigned gender:2; // Gender (0=male, 1=female, 2=monster) @@ -1801,7 +1800,7 @@ struct OnLevelMessage_Struct { /*4224*/ char ButtonName0[25]; // If Buttons = 1, these two are the text for the left and right buttons respectively /*4249*/ char ButtonName1[25]; /*4274*/ uint8 Buttons; -/*4275*/ uint8 Unknown4275; // Something to do with audio controls +/*4275*/ uint8 SoundControls; // Something to do with audio controls /*4276*/ uint32 Duration; /*4280*/ uint32 PopupID; // If none zero, a response packet with 00 00 00 00 is returned on clicking the left button /*4284*/ uint32 NegativeID; // If none zero, a response packet with 01 00 00 00 is returned on clicking the right button diff --git a/common/ruletypes.h b/common/ruletypes.h index e9599e09b..a51a224b0 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -134,7 +134,6 @@ RULE_INT(Character, TradeskillUpMakePoison, 2) // Make Poison skillup rate adjus RULE_INT(Character, TradeskillUpPottery, 4) // Pottery skillup rate adjust. Lower is faster. RULE_INT(Character, TradeskillUpResearch, 1) // Research skillup rate adjust. Lower is faster. RULE_INT(Character, TradeskillUpTinkering, 2) // Tinkering skillup rate adjust. Lower is faster. -RULE_BOOL(Character, SpamHPUpdates, false) // if your server has stupid amounts of HP that causes client display issues, turn this on! RULE_BOOL(Character, MarqueeHPUpdates, false) // Will show Health % in center of screen < 100% RULE_INT(Character, IksarCommonTongue, 95) // 95 By default (live-like?) RULE_INT(Character, OgreCommonTongue, 95) // 95 By default (live-like?) @@ -151,6 +150,7 @@ RULE_BOOL(Character, AllowMQTarget, false) // Disables putting players in the 'h RULE_BOOL(Character, UseOldBindWound, false) // Uses the original bind wound behavior RULE_BOOL(Character, GrantHoTTOnCreate, false) // Grant Health of Target's Target leadership AA on character creation RULE_BOOL(Character, UseOldConSystem, false) // Grant Health of Target's Target leadership AA on character creation +RULE_BOOL(Character, OPClientUpdateVisualDebug, false) // Shows a pulse and forward directional particle each time the client sends its position to server RULE_CATEGORY_END() RULE_CATEGORY(Mercs) @@ -235,7 +235,6 @@ RULE_BOOL(World, StartZoneSameAsBindOnCreation, true) //Should the start zone AL RULE_CATEGORY_END() RULE_CATEGORY(Zone) -RULE_INT(Zone, NPCPositonUpdateTicCount, 32) //ms between intervals of sending a position update to the entire zone. RULE_INT(Zone, ClientLinkdeadMS, 180000) //the time a client remains link dead on the server after a sudden disconnection RULE_INT(Zone, GraveyardTimeMS, 1200000) //ms time until a player corpse is moved to a zone's graveyard, if one is specified for the zone RULE_BOOL(Zone, EnableShadowrest, 1) // enables or disables the shadowrest zone feature for player corpses. Default is turned on. @@ -274,6 +273,8 @@ RULE_BOOL(Map, FixPathingZWhenLoading, true) //increases zone boot times a bit RULE_BOOL(Map, FixPathingZAtWaypoints, false) //alternative to `WhenLoading`, accomplishes the same thing but does it at each waypoint instead of once at boot time. RULE_BOOL(Map, FixPathingZWhenMoving, false) //very CPU intensive, but helps hopping with widely spaced waypoints. RULE_BOOL(Map, FixPathingZOnSendTo, false) //try to repair Z coords in the SendTo routine as well. +RULE_BOOL(Map, FixZWhenMoving, true) // Automatically fix NPC Z coordinates when moving/pathing/engaged (Far less CPU intensive than its predecessor) +RULE_BOOL(Map, MobZVisualDebug, false) // Displays spell effects determining whether or not NPC is hitting Best Z calcs (blue for hit, red for miss) RULE_REAL(Map, FixPathingZMaxDeltaMoving, 20) //at runtime while pathing: max change in Z to allow the BestZ code to apply. RULE_REAL(Map, FixPathingZMaxDeltaWaypoint, 20) //at runtime at each waypoint: max change in Z to allow the BestZ code to apply. RULE_REAL(Map, FixPathingZMaxDeltaSendTo, 20) //at runtime in SendTo: max change in Z to allow the BestZ code to apply. @@ -289,7 +290,7 @@ RULE_BOOL(Pathing, AggroReturnToGrid, true) // Enable pathing for aggroed roamin 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, ZDiffThreshold, 10) // If a mob las LOS to it's target, it will run to it if the Z difference is < this. +RULE_REAL(Pathing, ZDiffThresholdNew, 80) // If a mob las LOS to it's target, it will run to it if the Z difference is < this. RULE_INT(Pathing, LOSCheckFrequency, 1000) // A mob will check for LOS to it's target this often (milliseconds). RULE_INT(Pathing, RouteUpdateFrequencyShort, 1000) // How often a new route will be calculated if the target has moved. RULE_INT(Pathing, RouteUpdateFrequencyLong, 5000) // How often a new route will be calculated if the target has moved. @@ -348,6 +349,7 @@ RULE_INT(Spells, MaxTotalSlotsNPC, 60) // default to Tit's limit RULE_INT(Spells, MaxTotalSlotsPET, 30) // default to Tit's limit RULE_BOOL (Spells, EnableBlockedBuffs, true) RULE_INT(Spells, ReflectType, 3) //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) // 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 RULE_INT(Spells, BaseImmunityLevel, 55) // The level that targets start to be immune to stun, fear and mez spells with a max level of 0. @@ -397,6 +399,7 @@ RULE_BOOL(Spells, FlatItemExtraSpellAmt, false) // allow SpellDmg stat to affect RULE_BOOL(Spells, IgnoreSpellDmgLvlRestriction, false) // ignore the 5 level spread on applying SpellDmg RULE_BOOL(Spells, AllowItemTGB, false) // TGB doesn't work with items on live, custom servers want it though RULE_BOOL(Spells, NPCInnateProcOverride, true) // NPC innate procs override the target type to single target. +RULE_BOOL(Spells, OldRainTargets, false) // use old incorrectly implemented max targets for rains RULE_CATEGORY_END() RULE_CATEGORY(Combat) diff --git a/common/servertalk.h b/common/servertalk.h index fa3f10aad..f5965cd74 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -189,6 +189,7 @@ #define ServerOP_ReloadWorld 0x4009 #define ServerOP_ReloadLogs 0x4010 #define ServerOP_ReloadPerlExportSettings 0x4011 +#define ServerOP_CZSetEntityVariableByClientName 0x4012 /* Query Server OP Codes */ #define ServerOP_QSPlayerLogTrades 0x5010 #define ServerOP_QSPlayerLogHandins 0x5011 @@ -1263,6 +1264,12 @@ struct CZSetEntVarByNPCTypeID_Struct { char m_var[256]; }; +struct CZSetEntVarByClientName_Struct { + char CharName[64]; + char id[256]; + char m_var[256]; +}; + struct ReloadWorld_Struct{ uint32 Option; }; diff --git a/common/shareddb.cpp b/common/shareddb.cpp index 2efb06913..331826ef2 100644 --- a/common/shareddb.cpp +++ b/common/shareddb.cpp @@ -1674,6 +1674,7 @@ void SharedDatabase::LoadSpells(void *data, int max_spells) { 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; diff --git a/common/spdat.h b/common/spdat.h index 913ee5a4b..605e9a6cf 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -468,7 +468,7 @@ typedef enum { #define SE_Blank 254 // implemented #define SE_ShieldDuration 255 // not implemented as bonus - increases duration of /shield #define SE_ShroudofStealth 256 // implemented -#define SE_PetDiscipline 257 // not implemented as bonus - /pet hold +#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 @@ -478,7 +478,7 @@ typedef enum { #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_PetDiscipline2 267 // *not implemented - /pet focus, /pet no cast +#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) @@ -562,9 +562,9 @@ typedef enum { #define SE_LimitManaMin 348 // implemented #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 -//#define SE_IncreaseTrapCount 352 // *not implemented - looks to be some type of invulnerability? Test ITC (8755) -//#define SE_AdditionalAura 353 // *not implemented - allows use of more than 1 aura, aa effect +#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 +#define SE_IncreaseTrapCount 352 // *not implemented - looks to be some type of invulnerability? Test ITC (8755) +#define SE_AdditionalAura 353 // *not implemented - allows use of more than 1 aura, aa effect //#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 @@ -757,7 +757,7 @@ struct SPDat_Spell_Struct // -- DIETY_BERTOXXULOUS ... DIETY_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 +/* 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 diff --git a/common/util/directory.cpp b/common/util/directory.cpp new file mode 100644 index 000000000..9bc075bbe --- /dev/null +++ b/common/util/directory.cpp @@ -0,0 +1,49 @@ +#include "directory.h" + +#ifdef _WIN32 +#include "win_dirent.h" +#else +#include +#endif + +struct EQ::Directory::impl { + DIR *m_dir; +}; + +EQ::Directory::Directory(const std::string &path) +{ + m_impl = new impl; + m_impl->m_dir = opendir(path.c_str()); +} + +EQ::Directory::~Directory() +{ + if (m_impl->m_dir) { + closedir(m_impl->m_dir); + } + + delete m_impl; +} + +bool EQ::Directory::Exists() +{ + return m_impl->m_dir != nullptr; +} + +void EQ::Directory::GetFiles(std::vector& files) +{ + if (m_impl->m_dir) { + struct dirent *ent; + while ((ent = readdir(m_impl->m_dir)) != nullptr) { + switch (ent->d_type) { + case DT_REG: + files.push_back(ent->d_name); + break; + default: + break; + } + } + + rewinddir(m_impl->m_dir); + } +} diff --git a/common/util/directory.h b/common/util/directory.h new file mode 100644 index 000000000..d5f7441b2 --- /dev/null +++ b/common/util/directory.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +namespace EQ { + class Directory + { + public: + Directory(const std::string &path); + ~Directory(); + + bool Exists(); + void GetFiles(std::vector &files); + private: + struct impl; + impl *m_impl; + }; +} diff --git a/common/util/win_dirent.h b/common/util/win_dirent.h new file mode 100644 index 000000000..89f4df49f --- /dev/null +++ b/common/util/win_dirent.h @@ -0,0 +1,928 @@ +/* + * Dirent interface for Microsoft Visual Studio + * Version 1.21 + * + * Copyright (C) 2006-2012 Toni Ronkko + * This file is part of dirent. Dirent may be freely distributed + * under the MIT license. For all details and documentation, see + * https://github.com/tronkko/dirent + */ +#ifndef DIRENT_H +#define DIRENT_H + +/* + * Include windows.h without Windows Sockets 1.1 to prevent conflicts with + * Windows Sockets 2.0. + */ +#ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Indicates that d_type field is available in dirent structure */ +#define _DIRENT_HAVE_D_TYPE + +/* Indicates that d_namlen field is available in dirent structure */ +#define _DIRENT_HAVE_D_NAMLEN + +/* Entries missing from MSVC 6.0 */ +#if !defined(FILE_ATTRIBUTE_DEVICE) +# define FILE_ATTRIBUTE_DEVICE 0x40 +#endif + +/* File type and permission flags for stat(), general mask */ +#if !defined(S_IFMT) +# define S_IFMT _S_IFMT +#endif + +/* Directory bit */ +#if !defined(S_IFDIR) +# define S_IFDIR _S_IFDIR +#endif + +/* Character device bit */ +#if !defined(S_IFCHR) +# define S_IFCHR _S_IFCHR +#endif + +/* Pipe bit */ +#if !defined(S_IFFIFO) +# define S_IFFIFO _S_IFFIFO +#endif + +/* Regular file bit */ +#if !defined(S_IFREG) +# define S_IFREG _S_IFREG +#endif + +/* Read permission */ +#if !defined(S_IREAD) +# define S_IREAD _S_IREAD +#endif + +/* Write permission */ +#if !defined(S_IWRITE) +# define S_IWRITE _S_IWRITE +#endif + +/* Execute permission */ +#if !defined(S_IEXEC) +# define S_IEXEC _S_IEXEC +#endif + +/* Pipe */ +#if !defined(S_IFIFO) +# define S_IFIFO _S_IFIFO +#endif + +/* Block device */ +#if !defined(S_IFBLK) +# define S_IFBLK 0 +#endif + +/* Link */ +#if !defined(S_IFLNK) +# define S_IFLNK 0 +#endif + +/* Socket */ +#if !defined(S_IFSOCK) +# define S_IFSOCK 0 +#endif + +/* Read user permission */ +#if !defined(S_IRUSR) +# define S_IRUSR S_IREAD +#endif + +/* Write user permission */ +#if !defined(S_IWUSR) +# define S_IWUSR S_IWRITE +#endif + +/* Execute user permission */ +#if !defined(S_IXUSR) +# define S_IXUSR 0 +#endif + +/* Read group permission */ +#if !defined(S_IRGRP) +# define S_IRGRP 0 +#endif + +/* Write group permission */ +#if !defined(S_IWGRP) +# define S_IWGRP 0 +#endif + +/* Execute group permission */ +#if !defined(S_IXGRP) +# define S_IXGRP 0 +#endif + +/* Read others permission */ +#if !defined(S_IROTH) +# define S_IROTH 0 +#endif + +/* Write others permission */ +#if !defined(S_IWOTH) +# define S_IWOTH 0 +#endif + +/* Execute others permission */ +#if !defined(S_IXOTH) +# define S_IXOTH 0 +#endif + +/* Maximum length of file name */ +#if !defined(PATH_MAX) +# define PATH_MAX MAX_PATH +#endif +#if !defined(FILENAME_MAX) +# define FILENAME_MAX MAX_PATH +#endif +#if !defined(NAME_MAX) +# define NAME_MAX FILENAME_MAX +#endif + +/* File type flags for d_type */ +#define DT_UNKNOWN 0 +#define DT_REG S_IFREG +#define DT_DIR S_IFDIR +#define DT_FIFO S_IFIFO +#define DT_SOCK S_IFSOCK +#define DT_CHR S_IFCHR +#define DT_BLK S_IFBLK +#define DT_LNK S_IFLNK + +/* Macros for converting between st_mode and d_type */ +#define IFTODT(mode) ((mode) & S_IFMT) +#define DTTOIF(type) (type) + +/* + * File type macros. Note that block devices, sockets and links cannot be + * distinguished on Windows and the macros S_ISBLK, S_ISSOCK and S_ISLNK are + * only defined for compatibility. These macros should always return false + * on Windows. + */ +#if !defined(S_ISFIFO) +# define S_ISFIFO(mode) (((mode) & S_IFMT) == S_IFIFO) +#endif +#if !defined(S_ISDIR) +# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) +#endif +#if !defined(S_ISREG) +# define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) +#endif +#if !defined(S_ISLNK) +# define S_ISLNK(mode) (((mode) & S_IFMT) == S_IFLNK) +#endif +#if !defined(S_ISSOCK) +# define S_ISSOCK(mode) (((mode) & S_IFMT) == S_IFSOCK) +#endif +#if !defined(S_ISCHR) +# define S_ISCHR(mode) (((mode) & S_IFMT) == S_IFCHR) +#endif +#if !defined(S_ISBLK) +# define S_ISBLK(mode) (((mode) & S_IFMT) == S_IFBLK) +#endif + +/* Return the exact length of d_namlen without zero terminator */ +#define _D_EXACT_NAMLEN(p) ((p)->d_namlen) + +/* Return number of bytes needed to store d_namlen */ +#define _D_ALLOC_NAMLEN(p) (PATH_MAX) + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* Wide-character version */ +struct _wdirent { + /* Always zero */ + long d_ino; + + /* Structure size */ + unsigned short d_reclen; + + /* Length of name without \0 */ + size_t d_namlen; + + /* File type */ + int d_type; + + /* File name */ + wchar_t d_name[PATH_MAX]; +}; +typedef struct _wdirent _wdirent; + +struct _WDIR { + /* Current directory entry */ + struct _wdirent ent; + + /* Private file data */ + WIN32_FIND_DATAW data; + + /* True if data is valid */ + int cached; + + /* Win32 search handle */ + HANDLE handle; + + /* Initial directory name */ + wchar_t *patt; +}; +typedef struct _WDIR _WDIR; + +static _WDIR *_wopendir (const wchar_t *dirname); +static struct _wdirent *_wreaddir (_WDIR *dirp); +static int _wclosedir (_WDIR *dirp); +static void _wrewinddir (_WDIR* dirp); + + +/* For compatibility with Symbian */ +#define wdirent _wdirent +#define WDIR _WDIR +#define wopendir _wopendir +#define wreaddir _wreaddir +#define wclosedir _wclosedir +#define wrewinddir _wrewinddir + + +/* Multi-byte character versions */ +struct dirent { + /* Always zero */ + long d_ino; + + /* Structure size */ + unsigned short d_reclen; + + /* Length of name without \0 */ + size_t d_namlen; + + /* File type */ + int d_type; + + /* File name */ + char d_name[PATH_MAX]; +}; +typedef struct dirent dirent; + +struct DIR { + struct dirent ent; + struct _WDIR *wdirp; +}; +typedef struct DIR DIR; + +static DIR *opendir (const char *dirname); +static struct dirent *readdir (DIR *dirp); +static int closedir (DIR *dirp); +static void rewinddir (DIR* dirp); + + +/* Internal utility functions */ +static WIN32_FIND_DATAW *dirent_first (_WDIR *dirp); +static WIN32_FIND_DATAW *dirent_next (_WDIR *dirp); + +static int dirent_mbstowcs_s( + size_t *pReturnValue, + wchar_t *wcstr, + size_t sizeInWords, + const char *mbstr, + size_t count); + +static int dirent_wcstombs_s( + size_t *pReturnValue, + char *mbstr, + size_t sizeInBytes, + const wchar_t *wcstr, + size_t count); + +static void dirent_set_errno (int error); + +/* + * Open directory stream DIRNAME for read and return a pointer to the + * internal working area that is used to retrieve individual directory + * entries. + */ +static _WDIR* +_wopendir( + const wchar_t *dirname) +{ + _WDIR *dirp = NULL; + int error; + + /* Must have directory name */ + if (dirname == NULL || dirname[0] == '\0') { + dirent_set_errno (ENOENT); + return NULL; + } + + /* Allocate new _WDIR structure */ + dirp = (_WDIR*) malloc (sizeof (struct _WDIR)); + if (dirp != NULL) { + DWORD n; + + /* Reset _WDIR structure */ + dirp->handle = INVALID_HANDLE_VALUE; + dirp->patt = NULL; + dirp->cached = 0; + + /* Compute the length of full path plus zero terminator + * + * Note that on WinRT there's no way to convert relative paths + * into absolute paths, so just assume its an absolute path. + */ +# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) + n = wcslen(dirname); +# else + n = GetFullPathNameW (dirname, 0, NULL, NULL); +# endif + + /* Allocate room for absolute directory name and search pattern */ + dirp->patt = (wchar_t*) malloc (sizeof (wchar_t) * n + 16); + if (dirp->patt) { + + /* + * Convert relative directory name to an absolute one. This + * allows rewinddir() to function correctly even when current + * working directory is changed between opendir() and rewinddir(). + * + * Note that on WinRT there's no way to convert relative paths + * into absolute paths, so just assume its an absolute path. + */ +# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) + wcsncpy_s(dirp->patt, n+1, dirname, n); +# else + n = GetFullPathNameW (dirname, n, dirp->patt, NULL); +# endif + if (n > 0) { + wchar_t *p; + + /* Append search pattern \* to the directory name */ + p = dirp->patt + n; + if (dirp->patt < p) { + switch (p[-1]) { + case '\\': + case '/': + case ':': + /* Directory ends in path separator, e.g. c:\temp\ */ + /*NOP*/; + break; + + default: + /* Directory name doesn't end in path separator */ + *p++ = '\\'; + } + } + *p++ = '*'; + *p = '\0'; + + /* Open directory stream and retrieve the first entry */ + if (dirent_first (dirp)) { + /* Directory stream opened successfully */ + error = 0; + } else { + /* Cannot retrieve first entry */ + error = 1; + dirent_set_errno (ENOENT); + } + + } else { + /* Cannot retrieve full path name */ + dirent_set_errno (ENOENT); + error = 1; + } + + } else { + /* Cannot allocate memory for search pattern */ + error = 1; + } + + } else { + /* Cannot allocate _WDIR structure */ + error = 1; + } + + /* Clean up in case of error */ + if (error && dirp) { + _wclosedir (dirp); + dirp = NULL; + } + + return dirp; +} + +/* + * Read next directory entry. The directory entry is returned in dirent + * structure in the d_name field. Individual directory entries returned by + * this function include regular files, sub-directories, pseudo-directories + * "." and ".." as well as volume labels, hidden files and system files. + */ +static struct _wdirent* +_wreaddir( + _WDIR *dirp) +{ + WIN32_FIND_DATAW *datap; + struct _wdirent *entp; + + /* Read next directory entry */ + datap = dirent_next (dirp); + if (datap) { + size_t n; + DWORD attr; + + /* Pointer to directory entry to return */ + entp = &dirp->ent; + + /* + * Copy file name as wide-character string. If the file name is too + * long to fit in to the destination buffer, then truncate file name + * to PATH_MAX characters and zero-terminate the buffer. + */ + n = 0; + while (n + 1 < PATH_MAX && datap->cFileName[n] != 0) { + entp->d_name[n] = datap->cFileName[n]; + n++; + } + dirp->ent.d_name[n] = 0; + + /* Length of file name excluding zero terminator */ + entp->d_namlen = n; + + /* File type */ + attr = datap->dwFileAttributes; + if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) { + entp->d_type = DT_CHR; + } else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) { + entp->d_type = DT_DIR; + } else { + entp->d_type = DT_REG; + } + + /* Reset dummy fields */ + entp->d_ino = 0; + entp->d_reclen = sizeof (struct _wdirent); + + } else { + + /* Last directory entry read */ + entp = NULL; + + } + + return entp; +} + +/* + * Close directory stream opened by opendir() function. This invalidates the + * DIR structure as well as any directory entry read previously by + * _wreaddir(). + */ +static int +_wclosedir( + _WDIR *dirp) +{ + int ok; + if (dirp) { + + /* Release search handle */ + if (dirp->handle != INVALID_HANDLE_VALUE) { + FindClose (dirp->handle); + dirp->handle = INVALID_HANDLE_VALUE; + } + + /* Release search pattern */ + if (dirp->patt) { + free (dirp->patt); + dirp->patt = NULL; + } + + /* Release directory structure */ + free (dirp); + ok = /*success*/0; + + } else { + /* Invalid directory stream */ + dirent_set_errno (EBADF); + ok = /*failure*/-1; + } + return ok; +} + +/* + * Rewind directory stream such that _wreaddir() returns the very first + * file name again. + */ +static void +_wrewinddir( + _WDIR* dirp) +{ + if (dirp) { + /* Release existing search handle */ + if (dirp->handle != INVALID_HANDLE_VALUE) { + FindClose (dirp->handle); + } + + /* Open new search handle */ + dirent_first (dirp); + } +} + +/* Get first directory entry (internal) */ +static WIN32_FIND_DATAW* +dirent_first( + _WDIR *dirp) +{ + WIN32_FIND_DATAW *datap; + + /* Open directory and retrieve the first entry */ + dirp->handle = FindFirstFileExW( + dirp->patt, FindExInfoStandard, &dirp->data, + FindExSearchNameMatch, NULL, 0); + if (dirp->handle != INVALID_HANDLE_VALUE) { + + /* a directory entry is now waiting in memory */ + datap = &dirp->data; + dirp->cached = 1; + + } else { + + /* Failed to re-open directory: no directory entry in memory */ + dirp->cached = 0; + datap = NULL; + + } + return datap; +} + +/* Get next directory entry (internal) */ +static WIN32_FIND_DATAW* +dirent_next( + _WDIR *dirp) +{ + WIN32_FIND_DATAW *p; + + /* Get next directory entry */ + if (dirp->cached != 0) { + + /* A valid directory entry already in memory */ + p = &dirp->data; + dirp->cached = 0; + + } else if (dirp->handle != INVALID_HANDLE_VALUE) { + + /* Get the next directory entry from stream */ + if (FindNextFileW (dirp->handle, &dirp->data) != FALSE) { + /* Got a file */ + p = &dirp->data; + } else { + /* The very last entry has been processed or an error occured */ + FindClose (dirp->handle); + dirp->handle = INVALID_HANDLE_VALUE; + p = NULL; + } + + } else { + + /* End of directory stream reached */ + p = NULL; + + } + + return p; +} + +/* + * Open directory stream using plain old C-string. + */ +static DIR* +opendir( + const char *dirname) +{ + struct DIR *dirp; + int error; + + /* Must have directory name */ + if (dirname == NULL || dirname[0] == '\0') { + dirent_set_errno (ENOENT); + return NULL; + } + + /* Allocate memory for DIR structure */ + dirp = (DIR*) malloc (sizeof (struct DIR)); + if (dirp) { + wchar_t wname[PATH_MAX]; + size_t n; + + /* Convert directory name to wide-character string */ + error = dirent_mbstowcs_s (&n, wname, PATH_MAX, dirname, PATH_MAX); + if (!error) { + + /* Open directory stream using wide-character name */ + dirp->wdirp = _wopendir (wname); + if (dirp->wdirp) { + /* Directory stream opened */ + error = 0; + } else { + /* Failed to open directory stream */ + error = 1; + } + + } else { + /* + * Cannot convert file name to wide-character string. This + * occurs if the string contains invalid multi-byte sequences or + * the output buffer is too small to contain the resulting + * string. + */ + error = 1; + } + + } else { + /* Cannot allocate DIR structure */ + error = 1; + } + + /* Clean up in case of error */ + if (error && dirp) { + free (dirp); + dirp = NULL; + } + + return dirp; +} + +/* + * Read next directory entry. + * + * When working with text consoles, please note that file names returned by + * readdir() are represented in the default ANSI code page while any output to + * console is typically formatted on another code page. Thus, non-ASCII + * characters in file names will not usually display correctly on console. The + * problem can be fixed in two ways: (1) change the character set of console + * to 1252 using chcp utility and use Lucida Console font, or (2) use + * _cprintf function when writing to console. The _cprinf() will re-encode + * ANSI strings to the console code page so many non-ASCII characters will + * display correcly. + */ +static struct dirent* +readdir( + DIR *dirp) +{ + WIN32_FIND_DATAW *datap; + struct dirent *entp; + + /* Read next directory entry */ + datap = dirent_next (dirp->wdirp); + if (datap) { + size_t n; + int error; + + /* Attempt to convert file name to multi-byte string */ + error = dirent_wcstombs_s( + &n, dirp->ent.d_name, PATH_MAX, datap->cFileName, PATH_MAX); + + /* + * If the file name cannot be represented by a multi-byte string, + * then attempt to use old 8+3 file name. This allows traditional + * Unix-code to access some file names despite of unicode + * characters, although file names may seem unfamiliar to the user. + * + * Be ware that the code below cannot come up with a short file + * name unless the file system provides one. At least + * VirtualBox shared folders fail to do this. + */ + if (error && datap->cAlternateFileName[0] != '\0') { + error = dirent_wcstombs_s( + &n, dirp->ent.d_name, PATH_MAX, + datap->cAlternateFileName, PATH_MAX); + } + + if (!error) { + DWORD attr; + + /* Initialize directory entry for return */ + entp = &dirp->ent; + + /* Length of file name excluding zero terminator */ + entp->d_namlen = n - 1; + + /* File attributes */ + attr = datap->dwFileAttributes; + if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) { + entp->d_type = DT_CHR; + } else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) { + entp->d_type = DT_DIR; + } else { + entp->d_type = DT_REG; + } + + /* Reset dummy fields */ + entp->d_ino = 0; + entp->d_reclen = sizeof (struct dirent); + + } else { + /* + * Cannot convert file name to multi-byte string so construct + * an errornous directory entry and return that. Note that + * we cannot return NULL as that would stop the processing + * of directory entries completely. + */ + entp = &dirp->ent; + entp->d_name[0] = '?'; + entp->d_name[1] = '\0'; + entp->d_namlen = 1; + entp->d_type = DT_UNKNOWN; + entp->d_ino = 0; + entp->d_reclen = 0; + } + + } else { + /* No more directory entries */ + entp = NULL; + } + + return entp; +} + +/* + * Close directory stream. + */ +static int +closedir( + DIR *dirp) +{ + int ok; + if (dirp) { + + /* Close wide-character directory stream */ + ok = _wclosedir (dirp->wdirp); + dirp->wdirp = NULL; + + /* Release multi-byte character version */ + free (dirp); + + } else { + + /* Invalid directory stream */ + dirent_set_errno (EBADF); + ok = /*failure*/-1; + + } + return ok; +} + +/* + * Rewind directory stream to beginning. + */ +static void +rewinddir( + DIR* dirp) +{ + /* Rewind wide-character string directory stream */ + _wrewinddir (dirp->wdirp); +} + +/* Convert multi-byte string to wide character string */ +static int +dirent_mbstowcs_s( + size_t *pReturnValue, + wchar_t *wcstr, + size_t sizeInWords, + const char *mbstr, + size_t count) +{ + int error; + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + + /* Microsoft Visual Studio 2005 or later */ + error = mbstowcs_s (pReturnValue, wcstr, sizeInWords, mbstr, count); + +#else + + /* Older Visual Studio or non-Microsoft compiler */ + size_t n; + + /* Convert to wide-character string (or count characters) */ + n = mbstowcs (wcstr, mbstr, sizeInWords); + if (!wcstr || n < count) { + + /* Zero-terminate output buffer */ + if (wcstr && sizeInWords) { + if (n >= sizeInWords) { + n = sizeInWords - 1; + } + wcstr[n] = 0; + } + + /* Length of resuting multi-byte string WITH zero terminator */ + if (pReturnValue) { + *pReturnValue = n + 1; + } + + /* Success */ + error = 0; + + } else { + + /* Could not convert string */ + error = 1; + + } + +#endif + + return error; +} + +/* Convert wide-character string to multi-byte string */ +static int +dirent_wcstombs_s( + size_t *pReturnValue, + char *mbstr, + size_t sizeInBytes, /* max size of mbstr */ + const wchar_t *wcstr, + size_t count) +{ + int error; + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + + /* Microsoft Visual Studio 2005 or later */ + error = wcstombs_s (pReturnValue, mbstr, sizeInBytes, wcstr, count); + +#else + + /* Older Visual Studio or non-Microsoft compiler */ + size_t n; + + /* Convert to multi-byte string (or count the number of bytes needed) */ + n = wcstombs (mbstr, wcstr, sizeInBytes); + if (!mbstr || n < count) { + + /* Zero-terminate output buffer */ + if (mbstr && sizeInBytes) { + if (n >= sizeInBytes) { + n = sizeInBytes - 1; + } + mbstr[n] = '\0'; + } + + /* Length of resulting multi-bytes string WITH zero-terminator */ + if (pReturnValue) { + *pReturnValue = n + 1; + } + + /* Success */ + error = 0; + + } else { + + /* Cannot convert string */ + error = 1; + + } + +#endif + + return error; +} + +/* Set errno variable */ +static void +dirent_set_errno( + int error) +{ +#if defined(_MSC_VER) && _MSC_VER >= 1400 + + /* Microsoft Visual Studio 2005 and later */ + _set_errno (error); + +#else + + /* Non-Microsoft compiler or older Microsoft compiler */ + errno = error; + +#endif +} + + +#ifdef __cplusplus +} +#endif +#endif /*DIRENT_H*/ diff --git a/common/version.h b/common/version.h index 411493356..52af87872 100644 --- a/common/version.h +++ b/common/version.h @@ -30,7 +30,7 @@ Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt */ -#define CURRENT_BINARY_DATABASE_VERSION 9110 +#define CURRENT_BINARY_DATABASE_VERSION 9114 #ifdef BOTS #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9017 #else diff --git a/utils/mods/classic_wow_experience.lua b/utils/mods/classic_wow_experience.lua new file mode 100644 index 000000000..cde740ff6 --- /dev/null +++ b/utils/mods/classic_wow_experience.lua @@ -0,0 +1,159 @@ +--Mod file to demo changing the experience tables +--In this case I used some old wow tables (roughly it's not 100%) + +function GetRequiredAAExperience(e) + e.level = 51; + return GetEXPForLevel(e); +end + +function GetExperienceForKill(e) + local ML = e.other:GetLevel(); + local CL = e.self:GetLevel(); + + if(ML > CL) then + local lmod = (ML - CL) * 0.05; + if(lmod > 1.0) then + lmod = 1.0; + end + e.ReturnValue = BaseXP(ML) * (1 + lmod); + elseif(ML < CL) then + local lmod = (CL - ML) * 0.05; + if(lmod > 1.0) then + lmod = 1.0; + end + e.ReturnValue = BaseXP(ML) * (1 - lmod); + else + e.ReturnValue = BaseXP(ML); + end + + e.IgnoreDefault = true; + return e; +end + +function BaseXP(L) + local base = L * 5; + + if(L < 60) then + base = base + 45; + elseif(L < 70) then + base = base + 235; + elseif(L < 80) then + base = base + 580; + else + base = base + 1875; + end + + return base; +end + +function GetEXPForLevel(e) + local exp_table = { + 0, + 400, + 900, + 1400, + 2100, + 2800, + 3600, + 4500, + 5400, + 6500, + 7600, + 8700, + 9800, + 11000, + 12300, + 13600, + 15000, + 16400, + 17800, + 19300, + 20800, + 22400, + 24000, + 25500, + 27200, + 28900, + 30500, + 32200, + 33900, + 36300, + 38800, + 41600, + 44600, + 48000, + 51400, + 55000, + 58700, + 62400, + 66200, + 70200, + 74300, + 78500, + 82800, + 87100, + 91600, + 96300, + 101000, + 105800, + 110700, + 115700, + 120900, + 126100, + 131500, + 137000, + 142500, + 148200, + 154000, + 159900, + 165800, + 172000, + 290000, + 317000, + 349000, + 386000, + 428000, + 475000, + 527000, + 585000, + 648000, + 717000, + 1523800, + 1539000, + 1555700, + 1571800, + 1587900, + 1604200, + 1620700, + 1637400, + 1653900, + 1670800, + 1670800, + 1670800, + 2121500, + 2669000, + 3469000, + 4583000, + 13000000, + 15080000, + 22600000, + 27300000, + 32800000 + }; + + if(e.level < 1) then + e.ReturnValue = 0; + e.IgnoreDefault = true; + return e; + end + + if(e.level > 91) then + e.ReturnValue = exp_table[91]; + e.IgnoreDefault = true; + return e; + end + + e.ReturnValue = exp_table[e.level]; + e.IgnoreDefault = true; + return e; +end diff --git a/utils/mods/legacy_combat.lua b/utils/mods/legacy_combat.lua new file mode 100644 index 000000000..9ab70cf4d --- /dev/null +++ b/utils/mods/legacy_combat.lua @@ -0,0 +1,754 @@ +MonkACBonusWeight = RuleI.Get(Rule.MonkACBonusWeight); +NPCACFactor = RuleR.Get(Rule.NPCACFactor); +OldACSoftcapRules = RuleB.Get(Rule.OldACSoftcapRules); +ClothACSoftcap = RuleI.Get(Rule.ClothACSoftcap); +LeatherACSoftcap = RuleI.Get(Rule.LeatherACSoftcap); +MonkACSoftcap = RuleI.Get(Rule.MonkACSoftcap); +ChainACSoftcap = RuleI.Get(Rule.ChainACSoftcap); +PlateACSoftcap = RuleI.Get(Rule.PlateACSoftcap); + +AAMitigationACFactor = RuleR.Get(Rule.AAMitigationACFactor); +WarriorACSoftcapReturn = RuleR.Get(Rule.WarriorACSoftcapReturn); +KnightACSoftcapReturn = RuleR.Get(Rule.KnightACSoftcapReturn); +LowPlateChainACSoftcapReturn = RuleR.Get(Rule.LowPlateChainACSoftcapReturn); +LowChainLeatherACSoftcapReturn = RuleR.Get(Rule.LowChainLeatherACSoftcapReturn); +CasterACSoftcapReturn = RuleR.Get(Rule.CasterACSoftcapReturn); +MiscACSoftcapReturn = RuleR.Get(Rule.MiscACSoftcapReturn); +WarACSoftcapReturn = RuleR.Get(Rule.WarACSoftcapReturn); +ClrRngMnkBrdACSoftcapReturn = RuleR.Get(Rule.ClrRngMnkBrdACSoftcapReturn); +PalShdACSoftcapReturn = RuleR.Get(Rule.PalShdACSoftcapReturn); +DruNecWizEncMagACSoftcapReturn = RuleR.Get(Rule.DruNecWizEncMagACSoftcapReturn); +RogShmBstBerACSoftcapReturn = RuleR.Get(Rule.RogShmBstBerACSoftcapReturn); +SoftcapFactor = RuleR.Get(Rule.SoftcapFactor); +ACthac0Factor = RuleR.Get(Rule.ACthac0Factor); +ACthac20Factor = RuleR.Get(Rule.ACthac20Factor); + +MeleeBaseCritChance = 0.0; +ClientBaseCritChance = 0.0; +BerserkBaseCritChance = 6.0; +WarBerBaseCritChance = 3.0; +RogueCritThrowingChance = 25; +RogueDeadlyStrikeChance = 80; +RogueDeadlyStrikeMod = 2; + +BaseHitChance = RuleR.Get(Rule.BaseHitChance); +NPCBonusHitChance = RuleR.Get(Rule.NPCBonusHitChance); +HitFalloffMinor = RuleR.Get(Rule.HitFalloffMinor); +HitFalloffModerate = RuleR.Get(Rule.HitFalloffModerate); +HitFalloffMajor = RuleR.Get(Rule.HitFalloffMajor); +HitBonusPerLevel = RuleR.Get(Rule.HitBonusPerLevel); +AgiHitFactor = RuleR.Get(Rule.AgiHitFactor); +WeaponSkillFalloff = RuleR.Get(Rule.WeaponSkillFalloff); +ArcheryHitPenalty = RuleR.Get(Rule.ArcheryHitPenalty); +UseOldDamageIntervalRules = RuleB.Get(Rule.UseOldDamageIntervalRules); + +CriticalMessageRange = RuleI.Get(Rule.CriticalDamage); + +function MeleeMitigation(e) + e.IgnoreDefault = true; + + if e.hit.damage_done < 0 or e.hit.base_damage == 0 then + return e; + end + + e.hit.damage_done = 2 * e.hit.base_damage * GetDamageTable(e.other, e.hit.skill) / 100; + e.hit = DoMeleeMitigation(e.self, e.other, e.hit, e.opts); + return e; +end + +function CheckHitChance(e) + e.IgnoreDefault = true; + + local other = e.other; + local attacker = other; + local self = e.self; + local defender = self; + local chancetohit = BaseHitChance; + local chance_mod = 0; + + if(e.opts ~= nil) then + chance_mod = e.opts.hit_chance; + end + + if(attacker:IsNPC() and not attacker:IsPet()) then + chancetohit = chancetohit + NPCBonusHitChance; + end + + local pvpmode = false; + if(self:IsClient() and other:IsClient()) then + pvpmode = true; + end + + if (chance_mod >= 10000) then + e.ReturnValue = true; + return e; + end + + local avoidanceBonus = 0; + local hitBonus = 0; + + local attacker_level = attacker:GetLevel(); + if(attacker_level < 1) then + attacker_level = 1; + end + + local defender_level = defender:GetLevel(); + if(defender_level < 1) then + defender_level = 1; + end + + local level_difference = attacker_level - defender_level; + local range = defender_level; + range = ((range / 4) + 3); + + if(level_difference < 0) then + if(level_difference >= -range) then + chancetohit = chancetohit + ((level_difference / range) * HitFalloffMinor); + elseif (level_difference >= -(range+3.0)) then + chancetohit = chancetohit - HitFalloffMinor; + chancetohit = chancetohit + (((level_difference + range) / 3.0) * HitFalloffModerate); + else + chancetohit = chancetohit - (HitFalloffMinor + HitFalloffModerate); + chancetohit = chancetohit + (((level_difference + range + 3.0) / 12.0) * HitFalloffMajor); + end + else + chancetohit = chancetohit + (HitBonusPerLevel * level_difference); + end + + chancetohit = chancetohit - (defender:GetAGI() * AgiHitFactor); + + if(attacker:IsClient()) then + chancetohit = chancetohit - (WeaponSkillFalloff * (attacker:CastToClient():MaxSkill(e.hit.skill) - attacker:GetSkill(e.hit.skill))); + end + + if(defender:IsClient()) then + chancetohit = chancetohit + (WeaponSkillFalloff * (defender:CastToClient():MaxSkill(Skill.Defense) - defender:GetSkill(Skill.Defense))); + end + + local attacker_spellbonuses = attacker:GetSpellBonuses(); + local attacker_itembonuses = attacker:GetItemBonuses(); + local attacker_aabonuses = attacker:GetAABonuses(); + local defender_spellbonuses = defender:GetSpellBonuses(); + local defender_itembonuses = defender:GetItemBonuses(); + local defender_aabonuses = defender:GetAABonuses(); + + if(attacker_spellbonuses:MeleeSkillCheckSkill() == e.hit.skill or attacker_spellbonuses:MeleeSkillCheckSkill() == 255) then + chancetohit = chancetohit + attacker_spellbonuses:MeleeSkillCheck(); + end + + if(attacker_itembonuses:MeleeSkillCheckSkill() == e.hit.skill or attacker_itembonuses:MeleeSkillCheckSkill() == 255) then + chancetohit = chancetohit + attacker_itembonuses:MeleeSkillCheck(); + end + + avoidanceBonus = defender_spellbonuses:AvoidMeleeChanceEffect() + + defender_itembonuses:AvoidMeleeChanceEffect() + + defender_aabonuses:AvoidMeleeChanceEffect() + + (defender_itembonuses:AvoidMeleeChance() / 10.0); + + local owner = Mob(); + if (defender:IsPet()) then + owner = defender:GetOwner(); + elseif (defender:IsNPC() and defender:CastToNPC():GetSwarmOwner()) then + local entity_list = eq.get_entity_list(); + owner = entity_list:GetMobID(defender:CastToNPC():GetSwarmOwner()); + end + + if (owner.valid) then + avoidanceBonus = avoidanceBonus + owner:GetAABonuses():PetAvoidance() + owner:GetSpellBonuses():PetAvoidance() + owner:GetItemBonuses():PetAvoidance(); + end + + if(defender:IsNPC()) then + avoidanceBonus = avoidanceBonus + (defender:CastToNPC():GetAvoidanceRating() / 10.0); + end + + hitBonus = hitBonus + attacker_itembonuses:HitChanceEffect(e.hit.skill) + + attacker_spellbonuses:HitChanceEffect(e.hit.skill) + + attacker_aabonuses:HitChanceEffect(e.hit.skill) + + attacker_itembonuses:HitChanceEffect(Skill.HIGHEST_SKILL + 1) + + attacker_spellbonuses:HitChanceEffect(Skill.HIGHEST_SKILL + 1) + + attacker_aabonuses:HitChanceEffect(Skill.HIGHEST_SKILL + 1); + + hitBonus = hitBonus + (attacker_itembonuses:Accuracy(Skill.HIGHEST_SKILL + 1) + + attacker_spellbonuses:Accuracy(Skill.HIGHEST_SKILL + 1) + + attacker_aabonuses:Accuracy(Skill.HIGHEST_SKILL + 1) + + attacker_aabonuses:Accuracy(e.hit.skill) + + attacker_itembonuses:HitChance()) / 15.0; + + hitBonus = hitBonus + chance_mod; + + if(attacker:IsNPC()) then + hitBonus = hitBonus + (attacker:CastToNPC():GetAccuracyRating() / 10.0); + end + + if (e.hit.skill == Skill.Archery) then + hitBonus = hitBonus - (hitBonus * ArcheryHitPenalty); + end + + chancetohit = chancetohit + ((chancetohit * (hitBonus - avoidanceBonus)) / 100.0); + + if(chancetohit > 1000 or chancetohit < -1000) then + elseif(chancetohit > 95) then + chancetohit = 95; + elseif(chancetohit < 5) then + chancetohit = 5; + end + + local tohit_roll = Random.Real(0, 100); + if(tohit_roll <= chancetohit) then + e.ReturnValue = true; + else + e.ReturnValue = false; + end + + return e; +end + +function TryCriticalHit(e) + e.IgnoreDefault = true; + + local self = e.self; + local defender = e.other; + + if(e.hit.damage_done < 1 or defender.null) then + return e; + end + + if ((self:IsPet() and self:GetOwner():IsClient()) or (self:IsNPC() and self:CastToNPC():GetSwarmOwner() ~= 0)) then + e.hit = TryPetCriticalHit(self, defender, e.hit); + return e; + end + + if (self:IsPet() and self:GetOwner().valid and self:GetOwner():IsBot()) then + e.hit = TryPetCriticalHit(self, defender, e.hit); + return e; + end + + local critChance = 0.0; + local IsBerskerSPA = false; + local aabonuses = self:GetAABonuses(); + local itembonuses = self:GetItemBonuses(); + local spellbonuses = self:GetSpellBonuses(); + local entity_list = eq.get_entity_list(); + + if (defender:GetBodyType() == BT.Undead or defender:GetBodyType() == BT.SummonedUndead or defender:GetBodyType() == BT.Vampire) then + local SlayRateBonus = aabonuses:SlayUndead(0) + itembonuses:SlayUndead(0) + spellbonuses:SlayUndead(0); + if (SlayRateBonus > 0) then + local slayChance = SlayRateBonus / 10000.0; + if (Random.RollReal(slayChance)) then + local SlayDmgBonus = aabonuses:SlayUndead(1) + itembonuses:SlayUndead(1) + spellbonuses:SlayUndead(1); + e.hit.damage_done = (e.hit.damage_done * SlayDmgBonus * 2.25) / 100; + + if (self:GetGender() == 1) then + entity_list:FilteredMessageClose(self, false, CriticalMessageRange, MT.CritMelee, Filter.MeleeCrits, string.format('%s\'s holy blade cleanses her target! (%d)', self:GetCleanName(), e.hit.damage_done)); + else + entity_list:FilteredMessageClose(self, false, CriticalMessageRange, MT.CritMelee, Filter.MeleeCrits, string.format('%s\'s holy blade cleanses his target! (%d)', self:GetCleanName(), e.hit.damage_done)); + end + + return e; + end + end + end + + critChance = critChance + MeleeBaseCritChance; + + if (self:IsClient()) then + critChance = critChance + ClientBaseCritChance; + + if (spellbonuses:BerserkSPA() or itembonuses:BerserkSPA() or aabonuses:BerserkSPA()) then + IsBerskerSPA = true; + end + + if (((self:GetClass() == Class.WARRIOR or self:GetClass() == Class.BERSERKER) and self:GetLevel() >= 12) or IsBerskerSPA) then + if (self:IsBerserk() or IsBerskerSPA) then + critChance = critChance + BerserkBaseCritChance; + else + critChance = critChance + WarBerBaseCritChance; + end + end + end + + local deadlyChance = 0; + local deadlyMod = 0; + if (e.hit.skill == Skill.Archery and self:GetClass() == Class.RANGER and self:GetSkill(Skill.Archery) >= 65) then + critChance = critChance + 6; + end + + if (e.hit.skill == Skill.Throwing and self:GetClass() == Class.ROGUE and self:GetSkill(Skill.Throwing) >= 65) then + critChance = critChance + RogueCritThrowingChance; + deadlyChance = RogueDeadlyStrikeChance; + deadlyMod = RogueDeadlyStrikeMod; + end + + local CritChanceBonus = GetCriticalChanceBonus(self, e.hit.skill); + + if (CritChanceBonus > 0 or critChance > 0) then + if (self:GetDEX() <= 255) then + critChance = critChance + (self:GetDEX() / 125.0); + elseif (self:GetDEX() > 255) then + critChance = critChance + ((self:GetDEX() - 255) / 500.0) + 2.0; + end + critChance = critChance + (critChance * CritChanceBonus / 100.0); + end + + if(opts ~= nil) then + critChance = critChance * opts.crit_percent; + critChance = critChance + opts.crit_flat; + end + + if(critChance > 0) then + + critChance = critChance / 100; + + if(Random.RollReal(critChance)) then + local critMod = 200; + local crip_success = false; + local CripplingBlowChance = GetCrippBlowChance(self); + + if (CripplingBlowChance > 0 or (self:IsBerserk() or IsBerskerSPA)) then + if (not self:IsBerserk() and not IsBerskerSPA) then + critChance = critChance * (CripplingBlowChance / 100.0); + end + + if ((self:IsBerserk() or IsBerskerSPA) or Random.RollReal(critChance)) then + critMod = 400; + crip_success = true; + end + end + + critMod = critMod + GetCritDmgMod(self, e.hit.skill) * 2; + e.hit.damage_done = e.hit.damage_done * critMod / 100; + + local deadlySuccess = false; + if (deadlyChance > 0 and Random.RollReal(deadlyChance / 100.0)) then + if (self:BehindMob(defender, self:GetX(), self:GetY())) then + e.hit.damage_done = e.hit.damage_done * deadlyMod; + deadlySuccess = true; + end + end + + if (crip_success) then + entity_list:FilteredMessageClose(self, false, CriticalMessageRange, MT.CritMelee, Filter.MeleeCrits, string.format('%s lands a Crippling Blow! (%d)', self:GetCleanName(), e.hit.damage_done)); + if (defender:GetLevel() <= 55 and not defender:GetSpecialAbility(SpecialAbility.unstunable)) then + defender:Emote("staggers."); + defender:Stun(0); + end + elseif (deadlySuccess) then + entity_list:FilteredMessageClose(self, false, CriticalMessageRange, MT.CritMelee, Filter.MeleeCrits, string.format('%s scores a Deadly Strike! (%d)', self:GetCleanName(), e.hit.damage_done)); + else + entity_list:FilteredMessageClose(self, false, CriticalMessageRange, MT.CritMelee, Filter.MeleeCrits, string.format('%s scores a critical hit! (%d)', self:GetCleanName(), e.hit.damage_done)); + end + end + end + + return e; +end + +function TryPetCriticalHit(self, defender, hit) + if(hit.damage_done < 1) then + return hit; + end + + local owner = Mob(); + local critChance = MeleeBaseCritChance; + local critMod = 163; + + if (self:IsPet()) then + owner = self:GetOwner(); + elseif (self:IsNPC() and self:CastToNPC():GetSwarmOwner()) then + local entity_list = eq.get_entity_list(); + owner = entity_list:GetMobID(self:CastToNPC():GetSwarmOwner()); + else + return hit; + end + + if (owner.null) then + return hit; + end + + local CritPetChance = owner:GetAABonuses():PetCriticalHit() + owner:GetItemBonuses():PetCriticalHit() + owner:GetSpellBonuses():PetCriticalHit(); + local CritChanceBonus = GetCriticalChanceBonus(self, hit.skill); + + if (CritPetChance or critChance) then + critChance = critChance + CritPetChance; + critChance = critChance + (critChance * CritChanceBonus / 100.0); + end + + if(critChance > 0) then + critChance = critChance / 100; + + if(Random.RollReal(critChance)) then + local entity_list = eq.get_entity_list(); + critMod = critMod + GetCritDmgMod(self, hit.skill) * 2; + hit.damage_done = (hit.damage_done * critMod) / 100; + entity_list:FilteredMessageClose(this, false, CriticalMessageRange, + MT.CritMelee, Filter.MeleeCrits, string.format('%s scores a critical hit! (%d)', + self:GetCleanName(), e.hit.damage_done)); + end + end + + return hit; +end + +function GetCriticalChanceBonus(self, skill) + + local critical_chance = 0; + + local aabonuses = self:GetAABonuses(); + local itembonuses = self:GetItemBonuses(); + local spellbonuses = self:GetSpellBonuses(); + + critical_chance = critical_chance + itembonuses:CriticalHitChance(Skill.HIGHEST_SKILL + 1); + critical_chance = critical_chance + spellbonuses:CriticalHitChance(Skill.HIGHEST_SKILL + 1); + critical_chance = critical_chance + aabonuses:CriticalHitChance(Skill.HIGHEST_SKILL + 1); + critical_chance = critical_chance + itembonuses:CriticalHitChance(skill); + critical_chance = critical_chance + spellbonuses:CriticalHitChance(skill); + critical_chance = critical_chance + aabonuses:CriticalHitChance(skill); + + return critical_chance; +end + +function GetCritDmgMod(self, skill) + local critDmg_mod = 0; + + local aabonuses = self:GetAABonuses(); + local itembonuses = self:GetItemBonuses(); + local spellbonuses = self:GetSpellBonuses(); + + critDmg_mod = critDmg_mod + itembonuses:CritDmgMod(Skill.HIGHEST_SKILL + 1); + critDmg_mod = critDmg_mod + spellbonuses:CritDmgMod(Skill.HIGHEST_SKILL + 1); + critDmg_mod = critDmg_mod + aabonuses:CritDmgMod(Skill.HIGHEST_SKILL + 1); + critDmg_mod = critDmg_mod + itembonuses:CritDmgMod(skill); + critDmg_mod = critDmg_mod + spellbonuses:CritDmgMod(skill); + critDmg_mod = critDmg_mod + aabonuses:CritDmgMod(skill); + + return critDmg_mod; +end + +function GetCrippBlowChance(self) + local aabonuses = self:GetAABonuses(); + local itembonuses = self:GetItemBonuses(); + local spellbonuses = self:GetSpellBonuses(); + local crip_chance = itembonuses:CrippBlowChance() + spellbonuses:CrippBlowChance() + aabonuses:CrippBlowChance(); + + if(crip_chance < 0) then + crip_chance = 0; + end + + return crip_chance; +end + +function DoMeleeMitigation(defender, attacker, hit, opts) + if hit.damage_done <= 0 then + return hit; + end + + local aabonuses = defender:GetAABonuses(); + local itembonuses = defender:GetItemBonuses(); + local spellbonuses = defender:GetSpellBonuses(); + + local aa_mit = (aabonuses:CombatStability() + itembonuses:CombatStability() + spellbonuses:CombatStability()) / 100.0; + local softcap = (defender:GetSkill(15) + defender:GetLevel()) * SoftcapFactor * (1.0 + aa_mit); + local mitigation_rating = 0.0; + local attack_rating = 0.0; + local shield_ac = 0; + local armor = 0; + local weight = 0.0; + local monkweight = MonkACBonusWeight; + + if defender:IsClient() then + armor, shield_ac = GetRawACNoShield(defender); + weight = defender:CastToClient():CalcCurrentWeight() / 10; + elseif defender:IsNPC() then + armor = defender:CastToNPC():GetRawAC(); + local PetACBonus = 0; + + if not defender:IsPet() then + armor = armor / NPCACFactor; + end + + local owner = Mob(); + if defender:IsPet() then + owner = defender:GetOwner(); + elseif defender:CastToNPC():GetSwarmOwner() ~= 0 then + local entity_list = eq.get_entity_list(); + owner = entity_list:GetMobID(defender:CastToNPC():GetSwarmOwner()); + end + + if owner.valid then + PetACBonus = owner:GetAABonuses():PetMeleeMitigation() + owner:GetItemBonuses():PetMeleeMitigation() + owner:GetSpellBonuses():PetMeleeMitigation(); + end + + armor = armor + defender:GetSpellBonuses():AC() + defender:GetItemBonuses():AC() + PetACBonus + 1; + end + + if (opts ~= nil) then + armor = armor * (1.0 - opts.armor_pen_percent); + armor = armor - opts.armor_pen_flat; + end + + local defender_class = defender:GetClass(); + if OldACSoftcapRules then + if defender_class == Class.WIZARD or defender_class == Class.MAGICIAN or defender_class == Class.NECROMANCER or defender_class == Class.ENCHANTER then + softcap = ClothACSoftcap; + elseif defender_class == Class.MONK and weight <= monkweight then + softcap = MonkACSoftcap; + elseif defender_class == Class.DRUID or defender_class == Class.BEASTLORD or defender_class == Class.MONK then + softcap = LeatherACSoftcap; + elseif defender_class == Class.SHAMAN or defender_class == Class.ROGUE or defender_class == Class.BERSERKER or defender_class == Class.RANGER then + softcap = ChainACSoftcap; + else + softcap = PlateACSoftcap; + end + end + + softcap = softcap + shield_ac; + armor = armor + shield_ac; + + if OldACSoftcapRules then + softcap = softcap + (softcap * (aa_mit * AAMitigationACFactor)); + end + + if armor > softcap then + local softcap_armor = armor - softcap; + if OldACSoftcapRules then + if defender_class == Class.WARRIOR then + softcap_armor = softcap_armor * WarriorACSoftcapReturn; + elseif defender_class == Class.SHADOWKNIGHT or defender_class == Class.PALADIN or (defender_class == Class.MONK and weight <= monkweight) then + softcap_armor = softcap_armor * KnightACSoftcapReturn; + elseif defender_class == Class.CLERIC or defender_class == Class.BARD or defender_class == Class.BERSERKER or defender_class == Class.ROGUE or defender_class == Class.SHAMAN or defender_class == Class.MONK then + softcap_armor = softcap_armor * LowPlateChainACSoftcapReturn; + elseif defender_class == Class.RANGER or defender_class == Class.BEASTLORD then + softcap_armor = softcap_armor * LowChainLeatherACSoftcapReturn; + elseif defender_class == Class.WIZARD or defender_class == Class.MAGICIAN or defender_class == Class.NECROMANCER or defender_class == Class.ENCHANTER or defender_class == Class.DRUID then + softcap_armor = softcap_armor * CasterACSoftcapReturn; + else + softcap_armor = softcap_armor * MiscACSoftcapReturn; + end + else + if defender_class == Class.WARRIOR then + softcap_armor = softcap_armor * WarACSoftcapReturn; + elseif defender_class == Class.PALADIN or defender_class == Class.SHADOWKNIGHT then + softcap_armor = softcap_armor * PalShdACSoftcapReturn; + elseif defender_class == Class.CLERIC or defender_class == Class.RANGER or defender_class == Class.MONK or defender_class == Class.BARD then + softcap_armor = softcap_armor * ClrRngMnkBrdACSoftcapReturn; + elseif defender_class == Class.DRUID or defender_class == Class.NECROMANCER or defender_class == Class.WIZARD or defender_class == Class.ENCHANTER or defender_class == Class.MAGICIAN then + softcap_armor = softcap_armor * DruNecWizEncMagACSoftcapReturn; + elseif defender_class == Class.ROGUE or defender_class == Class.SHAMAN or defender_class == Class.BEASTLORD or defender_class == Class.BERSERKER then + softcap_armor = softcap_armor * RogShmBstBerACSoftcapReturn; + else + softcap_armor = softcap_armor * MiscACSoftcapReturn; + end + end + + armor = softcap + softcap_armor; + end + + local mitigation_rating; + if defender_class == Class.WIZARD or defender_class == Class.MAGICIAN or defender_class == Class.NECROMANCER or defender_class == Class.ENCHANTER then + mitigation_rating = ((defender:GetSkill(Skill.Defense) + defender:GetItemBonuses():HeroicAGI() / 10) / 4.0) + armor + 1; + else + mitigation_rating = ((defender:GetSkill(Skill.Defense) + defender:GetItemBonuses():HeroicAGI() / 10) / 3.0) + (armor * 1.333333) + 1; + end + + mitigation_rating = mitigation_rating * 0.847; + + local attack_rating; + if attacker:IsClient() then + attack_rating = (attacker:CastToClient():CalcATK() + ((attacker:GetSTR() - 66) * 0.9) + (attacker:GetSkill(Skill.Offense)*1.345)); + else + attack_rating = (attacker:GetATK() + (attacker:GetSkill(Skill.Offense)*1.345) + ((attacker:GetSTR() - 66) * 0.9)); + end + + hit.damage_done = GetMeleeMitDmg(defender, attacker, hit.damage_done, hit.min_damage, mitigation_rating, attack_rating); + + if hit.damage_done < 0 then + hit.damage_done = 0; + end + + return hit; +end + +function GetMeleeMitDmg(defender, attacker, damage, min_damage, mitigation_rating, attack_rating) + if defender:IsClient() then + return ClientGetMeleeMitDmg(defender, attacker, damage, min_damage, mitigation_rating, attack_rating); + else + return MobGetMeleeMitDmg(defender, attacker, damage, min_damage, mitigation_rating, attack_rating); + end +end + +function ClientGetMeleeMitDmg(defender, attacker, damage, min_damage, mitigation_rating, attack_rating) + if (not attacker:IsNPC() or UseOldDamageIntervalRules) then + return MobGetMeleeMitDmg(defender, attacker, damage, min_damage, mitigation_rating, attack_rating); + end + + local d = 10; + local dmg_interval = (damage - min_damage) / 19.0; + local dmg_bonus = min_damage - dmg_interval; + local spellMeleeMit = (defender:GetSpellBonuses():MeleeMitigationEffect() + defender:GetItemBonuses():MeleeMitigationEffect() + defender:GetAABonuses():MeleeMitigationEffect()) / 100.0; + if (defender:GetClass() == Class.WARRIOR) then + spellMeleeMit = spellMeleeMit - 0.05; + end + + dmg_bonus = dmg_bonus - (dmg_bonus * (defender:GetItemBonuses():MeleeMitigation() / 100.0)); + dmg_interval = dmg_interval + (dmg_interval * spellMeleeMit); + + local mit_roll = Random.Real(0, mitigation_rating); + local atk_roll = Random.Real(0, attack_rating); + + if (atk_roll > mit_roll) then + local a_diff = atk_roll - mit_roll; + local thac0 = attack_rating * ACthac0Factor; + local thac0cap = attacker:GetLevel() * 9 + 20; + if (thac0 > thac0cap) then + thac0 = thac0cap; + end + + d = d + (10 * (a_diff / thac0)); + elseif (mit_roll > atk_roll) then + local m_diff = mit_roll - atk_roll; + local thac20 = mitigation_rating * ACthac20Factor; + local thac20cap = defender:GetLevel() * 9 + 20; + if (thac20 > thac20cap) then + thac20 = thac20cap; + end + + d = d - (10 * (m_diff / thac20)); + end + + if (d < 1) then + d = 1; + elseif (d > 20) then + d = 20; + end + + return math.floor(dmg_bonus + dmg_interval * d); +end + +function MobGetMeleeMitDmg(defender, attacker, damage, min_damage, mitigation_rating, attack_rating) + local d = 10.0; + local mit_roll = Random.Real(0, mitigation_rating); + local atk_roll = Random.Real(0, attack_rating); + + if (atk_roll > mit_roll) then + local a_diff = atk_roll - mit_roll; + local thac0 = attack_rating * ACthac0Factor; + local thac0cap = attacker:GetLevel() * 9 + 20; + if (thac0 > thac0cap) then + thac0 = thac0cap; + end + + d = d - (10.0 * (a_diff / thac0)); + elseif (mit_roll > atk_roll) then + local m_diff = mit_roll - atk_roll; + local thac20 = mitigation_rating * ACthac20Factor; + local thac20cap = defender:GetLevel() * 9 + 20; + if (thac20 > thac20cap) then + thac20 = thac20cap; + end + + d = d + (10.0 * (m_diff / thac20)); + end + + if (d < 0.0) then + d = 0.0; + elseif (d > 20.0) then + d = 20.0; + end + + local interval = (damage - min_damage) / 20.0; + damage = damage - (math.floor(d) * interval); + damage = damage - (min_damage * defender:GetItemBonuses():MeleeMitigation() / 100); + damage = damage + (damage * (defender:GetSpellBonuses():MeleeMitigationEffect() + defender:GetItemBonuses():MeleeMitigationEffect() + defender:GetAABonuses():MeleeMitigationEffect()) / 100); + + return damage; +end + +function GetRawACNoShield(self) + self = self:CastToClient(); + + local ac = self:GetItemBonuses():AC() + self:GetSpellBonuses():AC() + self:GetAABonuses():AC(); + local shield_ac = 0; + local inst = self:GetInventory():GetItem(Slot.Secondary); + + if inst.valid then + if inst:GetItem():ItemType() == 8 then + shield_ac = inst:GetItem():AC(); + + for i = 1, 6 do + local augment = inst:GetAugment(i - 1); + if augment.valid then + shield_ac = shield_ac + augment:GetItem():AC(); + end + end + end + end + + ac = ac - shield_ac; + return ac, shield_ac; +end + +function GetDamageTable(attacker, skill) + if not attacker:IsClient() then + return 100; + end + + if attacker:GetLevel() <= 51 then + local ret_table = 0; + local str_over_75 = 0; + if attacker:GetSTR() > 75 then + str_over_75 = attacker:GetSTR() - 75; + end + + if str_over_75 > 255 then + ret_table = (attacker:GetSkill(skill) + 255) / 2; + else + ret_table = (attacker:GetSkill(skill) + str_over_75) / 2; + end + + if ret_table < 100 then + return 100; + end + + return ret_table; + elseif attacker:GetLevel() >= 90 then + if attacker:GetClass() == 7 then + return 379; + else + return 345; + end + else + local dmg_table = { 275, 275, 275, 275, 275, 280, 280, 280, 280, 285, 285, 285, 290, 290, 295, 295, 300, 300, 300, 305, 305, 305, 310, 310, 315, 315, 320, 320, 320, 325, 325, 325, 330, 330, 335, 335, 340, 340, 340 }; + + if attacker:GetClass() == 7 then + local monkDamageTableBonus = 20; + return (dmg_table[attacker:GetLevel() - 50] * (100 + monkDamageTableBonus) / 100); + else + return dmg_table[attacker:GetLevel() - 50]; + end + end + return 100; +end + +function ApplyDamageTable(e) + e.IgnoreDefault = true; + return e; +end + +function CommonOutgoingHitSuccess(e) + e = ApplyMeleeDamageBonus(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 = TryCriticalHit(e); + e.self:CheckNumHitsRemaining(5, -1, 65535); + e.IgnoreDefault = true; + return e; +end + +function ApplyMeleeDamageBonus(e) + local dmgbonusmod = e.self:GetMeleeDamageMod_SE(e.hit.skill); + if (opts) then + dmgbonusmod = dmgbonusmod + e.opts.melee_damage_bonus_flat; + end + + e.hit.damage_done = e.hit.damage_done + (e.hit.damage_done * dmgbonusmod / 100); + return e; +end diff --git a/utils/mods/load_order.txt b/utils/mods/load_order.txt new file mode 100644 index 000000000..979674adb --- /dev/null +++ b/utils/mods/load_order.txt @@ -0,0 +1 @@ +legacy_combat.lua diff --git a/utils/patches/patch_RoF.conf b/utils/patches/patch_RoF.conf index ad54b6341..339bfd800 100644 --- a/utils/patches/patch_RoF.conf +++ b/utils/patches/patch_RoF.conf @@ -196,6 +196,7 @@ OP_Consent=0x400e OP_ConsentDeny=0x34c1 OP_AutoFire=0x314e OP_PetCommands=0x0093 +OP_PetCommandState=0x74ed OP_PetHoTT=0x0df4 OP_DeleteSpell=0x305c OP_Surname=0x1a87 @@ -671,3 +672,7 @@ OP_Some3ByteHPUpdate=0x0000 # initial HP update for mobs OP_InitialHPUpdate=0x0000 OP_ItemRecastDelay=0x57ed + +#aura related +OP_UpdateAura=0x1fa9 +OP_RemoveTrap=0x6a4d diff --git a/utils/patches/patch_RoF2.conf b/utils/patches/patch_RoF2.conf index 9f588d18f..e63e5e9e9 100644 --- a/utils/patches/patch_RoF2.conf +++ b/utils/patches/patch_RoF2.conf @@ -195,6 +195,7 @@ OP_Consent=0x1fd1 OP_ConsentDeny=0x7a45 OP_AutoFire=0x241e OP_PetCommands=0x0159 +OP_PetCommandState=0x1dc8 OP_PetHoTT=0x794a OP_DeleteSpell=0x3358 OP_Surname=0x0423 @@ -674,3 +675,7 @@ OP_RAWOutOfSession=0x0000 # we need to document the differences between these packets to make identifying them easier OP_Some3ByteHPUpdate=0x0000 # initial HP update for mobs OP_InitialHPUpdate=0x0000 + +#aura related +OP_UpdateAura=0x1456 +OP_RemoveTrap=0x71da diff --git a/utils/patches/patch_SoD.conf b/utils/patches/patch_SoD.conf index 08e072d33..ed7a19715 100644 --- a/utils/patches/patch_SoD.conf +++ b/utils/patches/patch_SoD.conf @@ -669,3 +669,7 @@ OP_Some3ByteHPUpdate=0x0000 # initial HP update for mobs OP_InitialHPUpdate=0x0000 # OP_ItemRecastDelay=0x15c4 + +#aura related +OP_UpdateAura=0x169a +OP_RemoveTrap=0x4bb6 diff --git a/utils/patches/patch_SoF.conf b/utils/patches/patch_SoF.conf index d758daea9..7ed41ff4a 100644 --- a/utils/patches/patch_SoF.conf +++ b/utils/patches/patch_SoF.conf @@ -658,3 +658,7 @@ OP_ItemRecastDelay=0x0ada #OP_NpcMoveUpdate=0x0d11 #SEQ 10/07/08 --NEW FROM SEQ #OP_Zone_MissingName01=0x0000 # #new titles avaliable: # + +#aura related +OP_UpdateAura=0x62a9 +OP_RemoveTrap=0x7bd9 diff --git a/utils/patches/patch_UF.conf b/utils/patches/patch_UF.conf index f41b62740..af698d52a 100644 --- a/utils/patches/patch_UF.conf +++ b/utils/patches/patch_UF.conf @@ -198,6 +198,7 @@ OP_Consent=0x6bb9 # C OP_ConsentDeny=0x4cd1 # C OP_AutoFire=0x5db5 # C OP_PetCommands=0x7706 # C +OP_PetCommandState=0x1a79 OP_PetHoTT=0x2528 OP_DeleteSpell=0x0698 # C OP_Surname=0x44ae # C @@ -684,3 +685,7 @@ OP_ItemRecastDelay=0x82d7 # unhandled OP_ShieldGroup=0x23a1 + +#aura related +OP_UpdateAura=0x2480 +OP_RemoveTrap=0x0115 diff --git a/utils/scripts/eqemu_server.pl b/utils/scripts/eqemu_server.pl index e9ba02099..42d956edd 100644 --- a/utils/scripts/eqemu_server.pl +++ b/utils/scripts/eqemu_server.pl @@ -49,6 +49,7 @@ if(-e "eqemu_server_skip_update.txt"){ #::: Check for script self update do_self_update_check_routine() if !$skip_self_update_check; +get_windows_wget(); get_perl_version(); read_eqemu_config_xml(); get_mysql_path(); @@ -200,7 +201,7 @@ sub new_server { } closedir(DIR); - if($file_count > 1 && (!-e "install_variables.txt" && !-e "../install_variables.txt")){ + if($file_count > 4 && (!-e "install_variables.txt" && !-e "../install_variables.txt")){ print "[New Server] ERROR: You must run eqemu_server.pl in an empty directory\n"; <>; exit; @@ -280,6 +281,8 @@ sub new_server { show_install_summary_info(); + rmtree('updates_staged'); + return; } else { @@ -517,6 +520,13 @@ sub get_perl_version { no warnings; } +sub get_windows_wget { + if(!-e "wget.exe" && $OS eq "Windows"){ + eval "use LWP::Simple qw(getstore);"; + getstore("https://raw.githubusercontent.com/Akkadius/EQEmuInstall/master/wget.exe", "wget.exe"); + } +} + sub do_self_update_check_routine { #::: Check for internet connection before updating @@ -524,7 +534,7 @@ sub do_self_update_check_routine { print "[Update] Cannot check update without internet connection...\n"; return; } - + #::: Check for script changes :: eqemu_server.pl get_remote_file($eqemu_repository_request_url . "utils/scripts/eqemu_server.pl", "updates_staged/eqemu_server.pl", 0, 1, 1); @@ -997,68 +1007,14 @@ sub get_remote_file{ } } - if($OS eq "Windows"){ - #::: For non-text type requests... - if($content_type == 1){ - $break = 0; - while($break == 0) { - eval "use LWP::Simple qw(getstore);"; - # use LWP::Simple qw(getstore); - # print "request is " . $request_url . "\n"; - # print "destination file is supposed to be " . $destination_file . "\n"; - if(!getstore($request_url, $destination_file)){ - print "[Download] Error, no connection or failed request...\n\n"; - } - # sleep(1); - #::: Make sure the file exists before continuing... - if(-e $destination_file) { - $break = 1; - print "[Download] Saved: (" . $destination_file . ") from " . $request_url . "\n" if !$silent_download; - } else { $break = 0; } - usleep(500); - - if($no_retry){ - $break = 1; - } - } - } - else{ - $break = 0; - while($break == 0) { - require LWP::UserAgent; - my $ua = LWP::UserAgent->new; - $ua->timeout(10); - $ua->env_proxy; - my $response = $ua->get($request_url); - if ($response->is_success){ - open (FILE, '> ' . $destination_file . ''); - print FILE $response->decoded_content; - close (FILE); - } - else { - print "[Download] Error, no connection or failed request...\n\n"; - } - if(-e $destination_file) { - $break = 1; - print "[Download] Saved: (" . $destination_file . ") from " . $request_url . "\n" if !$silent_download; - } else { $break = 0; } - usleep(500); - - if($no_retry){ - $break = 1; - } - } - } - } - if($OS eq "Linux"){ - #::: wget -O db_update/db_update_manifest.txt https://raw.githubusercontent.com/EQEmu/Server/master/utils/sql/db_update_manifest.txt - $wget = `wget --no-check-certificate --quiet -O $destination_file $request_url`; - print "[Download] Saved: (" . $destination_file . ") from " . $request_url . "\n" if !$silent_download; - if($wget=~/unable to resolve/i){ - print "Error, no connection or failed request...\n\n"; - #die; - } + #::: wget -O db_update/db_update_manifest.txt https://raw.githubusercontent.com/EQEmu/Server/master/utils/sql/db_update_manifest.txt + $wget = `wget -N --no-check-certificate --quiet -O $destination_file $request_url`; + print "[Download] Saved: (" . $destination_file . ") from " . $request_url . "\n" if !$silent_download; + if($wget=~/unable to resolve/i){ + print "Error, no connection or failed request...\n\n"; + #die; } + } #::: Trim Whitespaces @@ -1498,7 +1454,7 @@ sub map_files_fetch_bulk{ get_remote_file("http://github.com/Akkadius/EQEmuMaps/archive/master.zip", "maps/maps.zip", 1); unzip('maps/maps.zip', 'maps/'); my @files; - my $start_dir = "maps/EQEmuMaps-master/maps"; + my $start_dir = "maps/EQEmuMaps-master/"; find( sub { push @files, $File::Find::name unless -d; }, $start_dir @@ -1551,12 +1507,12 @@ sub map_files_fetch{ } sub quest_files_fetch{ - if (!-e "updates_staged/Quests-Plugins-master/quests/") { + if (!-e "updates_staged/projecteqquests-master/") { print "[Update] Fetching Latest Quests --- \n"; - get_remote_file("https://github.com/EQEmu/Quests-Plugins/archive/master.zip", "updates_staged/Quests-Plugins-master.zip", 1); + get_remote_file("https://codeload.github.com/ProjectEQ/projecteqquests/zip/master", "updates_staged/projecteqquests-master.zip", 1); print "[Install] Fetched latest quests...\n"; mkdir('updates_staged'); - unzip('updates_staged/Quests-Plugins-master.zip', 'updates_staged/'); + unzip('updates_staged/projecteqquests-master.zip', 'updates_staged/'); } $fc = 0; @@ -1564,7 +1520,7 @@ sub quest_files_fetch{ use File::Compare; my @files; - my $start_dir = "updates_staged/Quests-Plugins-master/quests/"; + my $start_dir = "updates_staged/projecteqquests-master/"; find( sub { push @files, $File::Find::name unless -d; }, $start_dir @@ -1573,7 +1529,7 @@ sub quest_files_fetch{ if($file=~/\.pl|\.lua|\.ext/i){ $staged_file = $file; $destination_file = $file; - $destination_file =~s/updates_staged\/Quests-Plugins-master\///g; + $destination_file =~s/updates_staged\/projecteqquests-master\//quests\//g; if (!-e $destination_file) { copy_file($staged_file, $destination_file); @@ -1603,27 +1559,28 @@ sub quest_files_fetch{ } } - rmtree('updates_staged'); - if($fc == 0){ print "[Update] No Quest Updates found... \n\n"; } } sub lua_modules_fetch { - if (!-e "updates_staged/Quests-Plugins-master/quests/lua_modules/") { - print "[Update] Fetching Latest LUA Modules --- \n"; - get_remote_file("https://github.com/EQEmu/Quests-Plugins/archive/master.zip", "updates_staged/Quests-Plugins-master.zip", 1); - print "[Update] Fetched latest LUA Modules...\n"; - unzip('updates_staged/Quests-Plugins-master.zip', 'updates_staged/'); + if (!-e "updates_staged/projecteqquests-master/") { + print "[Update] Fetching Latest lua modules --- \n"; + get_remote_file("https://codeload.github.com/ProjectEQ/projecteqquests/zip/master", "updates_staged/projecteqquests-master.zip", 1); + print "[Install] Fetched latest lua modules...\n"; + mkdir('updates_staged'); + unzip('updates_staged/projecteqquests-master.zip', 'updates_staged/'); } $fc = 0; use File::Find; use File::Compare; + mkdir('lua_modules'); + my @files; - my $start_dir = "updates_staged/Quests-Plugins-master/quests/lua_modules/"; + my $start_dir = "updates_staged/projecteqquests-master/lua_modules/"; find( sub { push @files, $File::Find::name unless -d; }, $start_dir @@ -1632,7 +1589,7 @@ sub lua_modules_fetch { if($file=~/\.pl|\.lua|\.ext/i){ $staged_file = $file; $destination_file = $file; - $destination_file =~s/updates_staged\/Quests-Plugins-master\/quests\///g; + $destination_file =~s/updates_staged\/projecteqquests-master\/lua_modules\//lua_modules\//g; if (!-e $destination_file) { copy_file($staged_file, $destination_file); @@ -1667,19 +1624,22 @@ sub lua_modules_fetch { } sub plugins_fetch{ - if (!-e "updates_staged/Quests-Plugins-master/plugins/") { - print "[Update] Fetching Latest Plugins\n"; - get_remote_file("https://github.com/EQEmu/Quests-Plugins/archive/master.zip", "updates_staged/Quests-Plugins-master.zip", 1); - print "[Update] Fetched latest plugins\n"; - unzip('updates_staged/Quests-Plugins-master.zip', 'updates_staged/'); + if (!-e "updates_staged/projecteqquests-master/") { + print "[Update] Fetching Latest plugins --- \n"; + get_remote_file("https://codeload.github.com/ProjectEQ/projecteqquests/zip/master", "updates_staged/projecteqquests-master.zip", 1); + print "[Install] Fetched latest plugins...\n"; + mkdir('updates_staged'); + unzip('updates_staged/projecteqquests-master.zip', 'updates_staged/'); } $fc = 0; use File::Find; use File::Compare; + mkdir('plugins'); + my @files; - my $start_dir = "updates_staged/Quests-Plugins-master/plugins/"; + my $start_dir = "updates_staged/projecteqquests-master/plugins/"; find( sub { push @files, $File::Find::name unless -d; }, $start_dir @@ -1688,7 +1648,7 @@ sub plugins_fetch{ if($file=~/\.pl|\.lua|\.ext/i){ $staged_file = $file; $destination_file = $file; - $destination_file =~s/updates_staged\/Quests-Plugins-master\///g; + $destination_file =~s/updates_staged\/projecteqquests-master\///g; if (!-e $destination_file) { copy_file($staged_file, $destination_file); @@ -2220,3 +2180,4 @@ sub generate_random_password { return $randpassword; } + diff --git a/utils/scripts/linux_installer/install.sh b/utils/scripts/linux_installer/install.sh index 97cfa582e..55972e8c8 100644 --- a/utils/scripts/linux_installer/install.sh +++ b/utils/scripts/linux_installer/install.sh @@ -114,9 +114,24 @@ if [[ "$OS" == "Debian" ]]; then apt-get $apt_options install open-vm-tools apt-get $apt_options install unzip apt-get $apt_options install uuid-dev + apt-get $apt_options install wget apt-get $apt_options install zlib-bin apt-get $apt_options install zlibc - + apt-get $apt_options install libsodium-dev + apt-get $apt_options install libsodium18 + + # If libsodium18 isn't installed (Debian), let's download both that and the dev package and install them. + if dpkg-query -s "libsodium18" 1>/dev/null 2>&1; then + echo "Sodium library already installed." + else + wget http://ftp.us.debian.org/debian/pool/main/libs/libsodium/libsodium-dev_1.0.11-1~bpo8+1_amd64.deb -O /home/eqemu/libsodium-dev.deb + wget http://ftp.us.debian.org/debian/pool/main/libs/libsodium/libsodium18_1.0.11-1~bpo8+1_amd64.deb -O /home/eqemu/libsodium18.deb + dpkg -i /home/eqemu/libsodium*.deb + # Cleanup after ourselves + rm -f /home/eqemu/libsodium-dev.deb + rm -f /home/eqemu/libsodium18.deb + fi + #::: Install FTP for remote FTP access echo "proftpd-basic shared/proftpd/inetd_or_standalone select standalone" | debconf-set-selections apt-get -y -q install proftpd @@ -149,8 +164,35 @@ EOF elif [[ "$OS" == "fedora_core" ]]; then # Do Fedora stuff - dnf -y install open-vm-tools vim cmake boost-devel zlib-devel mariadb-server mariadb-devel mariadb-libs perl perl-DBD-MySQL perl-IO-stringy perl-devel lua-devel lua-sql-mysql dos2unix php-mysql proftpd wget compat-lua-libs compat-lua-devel compat-lua perl-Time-HiRes - dnf -y groupinstall "Development Tools" "Basic Web Server" "C Development Tools and Libraries" + dnf -y install open-vm-tools + dnf -y install vim + dnf -y install cmake + dnf -y install boost-devel + dnf -y install zlib-devel + dnf -y install mariadb-server + dnf -y install mariadb-devel + dnf -y install mariadb-libs + dnf -y install perl + dnf -y install perl-DBD-MySQL + dnf -y install perl-IO-stringy + dnf -y install perl-devel + dnf -y install lua-devel + dnf -y install lua-sql-mysql + dnf -y install dos2unix + dnf -y install php-mysql + dnf -y install php-mysqlnd + dnf -y install proftpd + dnf -y install wget + dnf -y install compat-lua-libs + dnf -y install compat-lua-devel + dnf -y install compat-lua + dnf -y install perl-Time-HiRes + dnf -y install libuuid-devel + dnf -y install libsodium + dnf -y install libsodium-devel + dnf -y groupinstall "Development Tools" + dnf -y groupinstall "Basic Web Server" + dnf -y groupinstall "C Development Tools and Libraries" fi if [[ "$OS" == "fedora_core" ]] || [[ "$OS" == "red_hat" ]]; then diff --git a/utils/sql/db_update_manifest.txt b/utils/sql/db_update_manifest.txt index b4dce6c86..faccf9e48 100644 --- a/utils/sql/db_update_manifest.txt +++ b/utils/sql/db_update_manifest.txt @@ -364,6 +364,10 @@ 9108|2017_04_07_ignore_despawn.sql|SHOW COLUMNS FROM `npc_types` LIKE 'ignore_despawn'|empty| 9109|2017_04_08_doors_disable_timer.sql|SHOW COLUMNS FROM `doors` LIKE 'disable_timer'|empty| 9110|2017_04_10_graveyard.sql|show index from graveyard WHERE key_name = 'zone_id_nonunique'|empty| +9111|2017_06_24_saylink_index.sql|SHOW INDEX FROM `saylink` WHERE `key_name` = 'phrase_index'|empty| +9112|2017_06_24_rule_values_expand.sql|SHOW COLUMNS FROM rule_values WHERE Field = 'rule_value' and Type = 'varchar(30)'|empty| +9113|2017_07_19_show_name.sql|SHOW COLUMNS FROM `npc_types` LIKE 'show_name'|empty| +9114|2017_07_22_aura.sql|SHOW TABLES LIKE 'auras'|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/required/2017_06_24_rule_values_expand.sql b/utils/sql/git/required/2017_06_24_rule_values_expand.sql new file mode 100644 index 000000000..2d4720007 --- /dev/null +++ b/utils/sql/git/required/2017_06_24_rule_values_expand.sql @@ -0,0 +1,2 @@ +ALTER TABLE `rule_values` +MODIFY COLUMN `rule_value` varchar(30) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL DEFAULT '' AFTER `rule_name`; diff --git a/utils/sql/git/required/2017_06_24_saylink_index.sql b/utils/sql/git/required/2017_06_24_saylink_index.sql new file mode 100644 index 000000000..3dfb5c9ef --- /dev/null +++ b/utils/sql/git/required/2017_06_24_saylink_index.sql @@ -0,0 +1,2 @@ +ALTER TABLE `saylink` +ADD INDEX `phrase_index` (`phrase`) USING BTREE ; \ No newline at end of file diff --git a/utils/sql/git/required/2017_07_19_show_name.sql b/utils/sql/git/required/2017_07_19_show_name.sql new file mode 100644 index 000000000..e5bda11cf --- /dev/null +++ b/utils/sql/git/required/2017_07_19_show_name.sql @@ -0,0 +1,3 @@ +ALTER TABLE `npc_types` ADD COLUMN `show_name` TINYINT(2) NOT NULL DEFAULT 1; +ALTER TABLE `npc_types` ADD COLUMN `untargetable` TINYINT(2) NOT NULL DEFAULT 0; +UPDATE `npc_types` SET `show_name` = 0, `untargetable` = 1 WHERE `bodytype` >= 66; diff --git a/utils/sql/git/required/2017_07_22_aura.sql b/utils/sql/git/required/2017_07_22_aura.sql new file mode 100644 index 000000000..5ab461eaa --- /dev/null +++ b/utils/sql/git/required/2017_07_22_aura.sql @@ -0,0 +1,127 @@ +CREATE TABLE `auras` ( + `type` INT(10) NOT NULL, + `npc_type` INT(10) NOT NULL, + `name` VARCHAR(64) NOT NULL, + `spell_id` INT(10) NOT NULL, + `distance` INT(10) NOT NULL DEFAULT 60, + `aura_type` INT(10) NOT NULL DEFAULT 1, + `spawn_type` INT(10) NOT NULL DEFAULT 0, + `movement` INT(10) NOT NULL DEFAULT 0, + `duration` INT(10) NOT NULL DEFAULT 5400, + `icon` INT(10) NOT NULL DEFAULT -1, + `cast_time` INT(10) NOT NULL DEFAULT 0, + PRIMARY KEY(`type`) +); + +CREATE TABLE `character_auras` ( + `id` INT(10) NOT NULL, + `slot` TINYINT(10) NOT NULL, + `spell_id` INT(10) NOT NULL, + PRIMARY KEY (`id`, `slot`) +); + +SELECT IFNULL((MAX(id) + 1), 2000000) INTO @suggestedid FROM npc_types where id LIKE '2000___'; +INSERT INTO npc_types SET id=@suggestedid, name="IOAuraOfTheMuse55", lastname="", level="55", race="127", class="62", bodytype="11", hp="4027.6216", mana="0.0000", gender="0", texture="0", helmtexture="0", herosforgemodel="0", size="2", hp_regen_rate="0", mana_regen_rate="0", loottable_id="0", npc_spells_id="0", mindmg="52.7514", maxdmg="166.8270", attack_count="-1", special_abilities="12,1^13,1^14,1^15,1^16,1^17,1^18,1^19,1^20,1^21,1^22,1^23,1^24,1^25,1^28,1^31,1^35,1^", aggroradius="70", assistradius="0", face="0", luclin_hairstyle="0", luclin_haircolor="0", luclin_eyecolor="0", luclin_eyecolor2="0", luclin_beardcolor="0", luclin_beard="0", drakkin_heritage="0", drakkin_tattoo="0", drakkin_details="0", armortint_red="0", armortint_green="0", armortint_blue="0", d_melee_texture1="0", d_melee_texture2="0", prim_melee_type="28", sec_melee_type="28", runspeed="1.25", MR="21.1027", CR="21.1027", DR="21.1027", FR="21.1027", PR="21.1027", Corrup="21.1027", PhR="48.3333", see_invis="0", see_invis_undead="0", qglobal="0", AC="257.3784", npc_aggro="0", spawn_limit="0", attack_delay="29.4486", findable="0", STR="205.0000", STA="205.0000", DEX="205.0000", AGI="205.0000", _INT="205.0000", WIS="205.0000", CHA="205.0000", see_hide="0", see_improved_hide="0", trackable="0", ATK="0", Accuracy="0", Avoidance="0", slow_mitigation="0", version="0", maxlevel="0", scalerate="100", private_corpse="0", unique_spawn_by_name="0", underwater="0", emoteid="0", spellscale="100", healscale="100", no_target_hotkey="0", raid_target="0", light="0", ignore_despawn="0", show_name="0"; +INSERT INTO auras SET type=8926, npc_type=@suggestedid, name="Aura_of_Insight", spell_id=8939, distance=60, aura_type=1, spawn_type=0, movement=0, duration=5400, icon=99, cast_time=-1; + +SELECT IFNULL((MAX(id) + 1), 2000000) INTO @suggestedid FROM npc_types where id LIKE '2000___'; +INSERT INTO npc_types SET id=@suggestedid, name="IOAuraOfTheMuse", lastname="", level="70", race="127", class="62", bodytype="11", hp="4027.6216", mana="0.0000", gender="0", texture="0", helmtexture="0", herosforgemodel="0", size="2", hp_regen_rate="0", mana_regen_rate="0", loottable_id="0", npc_spells_id="0", mindmg="52.7514", maxdmg="166.8270", attack_count="-1", special_abilities="12,1^13,1^14,1^15,1^16,1^17,1^18,1^19,1^20,1^21,1^22,1^23,1^24,1^25,1^28,1^31,1^35,1^", aggroradius="70", assistradius="0", face="0", luclin_hairstyle="0", luclin_haircolor="0", luclin_eyecolor="0", luclin_eyecolor2="0", luclin_beardcolor="0", luclin_beard="0", drakkin_heritage="0", drakkin_tattoo="0", drakkin_details="0", armortint_red="0", armortint_green="0", armortint_blue="0", d_melee_texture1="0", d_melee_texture2="0", prim_melee_type="28", sec_melee_type="28", runspeed="1.25", MR="21.1027", CR="21.1027", DR="21.1027", FR="21.1027", PR="21.1027", Corrup="21.1027", PhR="48.3333", see_invis="0", see_invis_undead="0", qglobal="0", AC="257.3784", npc_aggro="0", spawn_limit="0", attack_delay="29.4486", findable="0", STR="205.0000", STA="205.0000", DEX="205.0000", AGI="205.0000", _INT="205.0000", WIS="205.0000", CHA="205.0000", see_hide="0", see_improved_hide="0", trackable="0", ATK="0", Accuracy="0", Avoidance="0", slow_mitigation="0", version="0", maxlevel="0", scalerate="100", private_corpse="0", unique_spawn_by_name="0", underwater="0", emoteid="0", spellscale="100", healscale="100", no_target_hotkey="0", raid_target="0", light="0", ignore_despawn="0", show_name="0"; +INSERT INTO auras SET type=8488, npc_type=@suggestedid, name="Aura_of_the_Muse", spell_id=8489, distance=60, aura_type=1, spawn_type=0, movement=0, duration=5400, icon=-1, cast_time=-1; + +SELECT IFNULL((MAX(id) + 1), 2000000) INTO @suggestedid FROM npc_types where id LIKE '2000___'; +INSERT INTO npc_types SET id=@suggestedid, name="IOChampionsAura55", lastname="", level="55", race="127", class="62", bodytype="11", hp="4027.6216", mana="0.0000", gender="0", texture="0", helmtexture="0", herosforgemodel="0", size="2", hp_regen_rate="0", mana_regen_rate="0", loottable_id="0", npc_spells_id="0", mindmg="52.7514", maxdmg="166.8270", attack_count="-1", special_abilities="12,1^13,1^14,1^15,1^16,1^17,1^18,1^19,1^20,1^21,1^22,1^23,1^24,1^25,1^28,1^31,1^35,1^", aggroradius="70", assistradius="0", face="0", luclin_hairstyle="0", luclin_haircolor="0", luclin_eyecolor="0", luclin_eyecolor2="0", luclin_beardcolor="0", luclin_beard="0", drakkin_heritage="0", drakkin_tattoo="0", drakkin_details="0", armortint_red="0", armortint_green="0", armortint_blue="0", d_melee_texture1="0", d_melee_texture2="0", prim_melee_type="28", sec_melee_type="28", runspeed="1.25", MR="21.1027", CR="21.1027", DR="21.1027", FR="21.1027", PR="21.1027", Corrup="21.1027", PhR="48.3333", see_invis="0", see_invis_undead="0", qglobal="0", AC="257.3784", npc_aggro="0", spawn_limit="0", attack_delay="29.4486", findable="0", STR="205.0000", STA="205.0000", DEX="205.0000", AGI="205.0000", _INT="205.0000", WIS="205.0000", CHA="205.0000", see_hide="0", see_improved_hide="0", trackable="0", ATK="0", Accuracy="0", Avoidance="0", slow_mitigation="0", version="0", maxlevel="0", scalerate="100", private_corpse="0", unique_spawn_by_name="0", underwater="0", emoteid="0", spellscale="100", healscale="100", no_target_hotkey="0", raid_target="0", light="0", ignore_despawn="0", show_name="0"; +INSERT INTO auras SET type=8921, npc_type=@suggestedid, name="Myrmidon's_Aura", spell_id=8935, distance=60, aura_type=1, spawn_type=0, movement=0, duration=5400, icon=-1, cast_time=-1; + +SELECT IFNULL((MAX(id) + 1), 2000000) INTO @suggestedid FROM npc_types where id LIKE '2000___'; +INSERT INTO npc_types SET id=@suggestedid, name="IOChampionsAura", lastname="", level="70", race="127", class="62", bodytype="11", hp="4027.6216", mana="0.0000", gender="0", texture="0", helmtexture="0", herosforgemodel="0", size="2", hp_regen_rate="0", mana_regen_rate="0", loottable_id="0", npc_spells_id="0", mindmg="52.7514", maxdmg="166.8270", attack_count="-1", special_abilities="12,1^13,1^14,1^15,1^16,1^17,1^18,1^19,1^20,1^21,1^22,1^23,1^24,1^25,1^28,1^31,1^35,1^", aggroradius="70", assistradius="0", face="0", luclin_hairstyle="0", luclin_haircolor="0", luclin_eyecolor="0", luclin_eyecolor2="0", luclin_beardcolor="0", luclin_beard="0", drakkin_heritage="0", drakkin_tattoo="0", drakkin_details="0", armortint_red="0", armortint_green="0", armortint_blue="0", d_melee_texture1="0", d_melee_texture2="0", prim_melee_type="28", sec_melee_type="28", runspeed="1.25", MR="21.1027", CR="21.1027", DR="21.1027", FR="21.1027", PR="21.1027", Corrup="21.1027", PhR="48.3333", see_invis="0", see_invis_undead="0", qglobal="0", AC="257.3784", npc_aggro="0", spawn_limit="0", attack_delay="29.4486", findable="0", STR="205.0000", STA="205.0000", DEX="205.0000", AGI="205.0000", _INT="205.0000", WIS="205.0000", CHA="205.0000", see_hide="0", see_improved_hide="0", trackable="0", ATK="0", Accuracy="0", Avoidance="0", slow_mitigation="0", version="0", maxlevel="0", scalerate="100", private_corpse="0", unique_spawn_by_name="0", underwater="0", emoteid="0", spellscale="100", healscale="100", no_target_hotkey="0", raid_target="0", light="0", ignore_despawn="0", show_name="0"; +INSERT INTO auras SET type=8468, npc_type=@suggestedid, name="Champion's_Aura", spell_id=8469, distance=60, aura_type=1, spawn_type=0, movement=0, duration=5400, icon=-1, cast_time=-1; + +SELECT IFNULL((MAX(id) + 1), 2000000) INTO @suggestedid FROM npc_types where id LIKE '2000___'; +INSERT INTO npc_types SET id=@suggestedid, name="IOBlessedAura55", lastname="", level="55", race="127", class="62", bodytype="11", hp="4027.6216", mana="0.0000", gender="0", texture="0", helmtexture="0", herosforgemodel="0", size="2", hp_regen_rate="0", mana_regen_rate="0", loottable_id="0", npc_spells_id="0", mindmg="52.7514", maxdmg="166.8270", attack_count="-1", special_abilities="12,1^13,1^14,1^15,1^16,1^17,1^18,1^19,1^20,1^21,1^22,1^23,1^24,1^25,1^28,1^31,1^35,1^", aggroradius="70", assistradius="0", face="0", luclin_hairstyle="0", luclin_haircolor="0", luclin_eyecolor="0", luclin_eyecolor2="0", luclin_beardcolor="0", luclin_beard="0", drakkin_heritage="0", drakkin_tattoo="0", drakkin_details="0", armortint_red="0", armortint_green="0", armortint_blue="0", d_melee_texture1="0", d_melee_texture2="0", prim_melee_type="28", sec_melee_type="28", runspeed="1.25", MR="21.1027", CR="21.1027", DR="21.1027", FR="21.1027", PR="21.1027", Corrup="21.1027", PhR="48.3333", see_invis="0", see_invis_undead="0", qglobal="0", AC="257.3784", npc_aggro="0", spawn_limit="0", attack_delay="29.4486", findable="0", STR="205.0000", STA="205.0000", DEX="205.0000", AGI="205.0000", _INT="205.0000", WIS="205.0000", CHA="205.0000", see_hide="0", see_improved_hide="0", trackable="0", ATK="0", Accuracy="0", Avoidance="0", slow_mitigation="0", version="0", maxlevel="0", scalerate="100", private_corpse="0", unique_spawn_by_name="0", underwater="0", emoteid="0", spellscale="100", healscale="100", no_target_hotkey="0", raid_target="0", light="0", ignore_despawn="0", show_name="0"; +INSERT INTO auras SET type=8925, npc_type=@suggestedid, name="Holy_Aura", spell_id=8938, distance=60, aura_type=1, spawn_type=0, movement=0, duration=5400, icon=-1, cast_time=-1; + +SELECT IFNULL((MAX(id) + 1), 2000000) INTO @suggestedid FROM npc_types where id LIKE '2000___'; +INSERT INTO npc_types SET id=@suggestedid, name="IOBlessedAura", lastname="", level="70", race="127", class="62", bodytype="11", hp="4027.6216", mana="0.0000", gender="0", texture="0", helmtexture="0", herosforgemodel="0", size="2", hp_regen_rate="0", mana_regen_rate="0", loottable_id="0", npc_spells_id="0", mindmg="52.7514", maxdmg="166.8270", attack_count="-1", special_abilities="12,1^13,1^14,1^15,1^16,1^17,1^18,1^19,1^20,1^21,1^22,1^23,1^24,1^25,1^28,1^31,1^35,1^", aggroradius="70", assistradius="0", face="0", luclin_hairstyle="0", luclin_haircolor="0", luclin_eyecolor="0", luclin_eyecolor2="0", luclin_beardcolor="0", luclin_beard="0", drakkin_heritage="0", drakkin_tattoo="0", drakkin_details="0", armortint_red="0", armortint_green="0", armortint_blue="0", d_melee_texture1="0", d_melee_texture2="0", prim_melee_type="28", sec_melee_type="28", runspeed="1.25", MR="21.1027", CR="21.1027", DR="21.1027", FR="21.1027", PR="21.1027", Corrup="21.1027", PhR="48.3333", see_invis="0", see_invis_undead="0", qglobal="0", AC="257.3784", npc_aggro="0", spawn_limit="0", attack_delay="29.4486", findable="0", STR="205.0000", STA="205.0000", DEX="205.0000", AGI="205.0000", _INT="205.0000", WIS="205.0000", CHA="205.0000", see_hide="0", see_improved_hide="0", trackable="0", ATK="0", Accuracy="0", Avoidance="0", slow_mitigation="0", version="0", maxlevel="0", scalerate="100", private_corpse="0", unique_spawn_by_name="0", underwater="0", emoteid="0", spellscale="100", healscale="100", no_target_hotkey="0", raid_target="0", light="0", ignore_despawn="0", show_name="0"; +INSERT INTO auras SET type=8481, npc_type=@suggestedid, name="Blessed_Aura", spell_id=8482, distance=60, aura_type=1, spawn_type=0, movement=0, duration=5400, icon=-1, cast_time=-1; + +SELECT IFNULL((MAX(id) + 1), 2000000) INTO @suggestedid FROM npc_types where id LIKE '2000___'; +INSERT INTO npc_types SET id=@suggestedid, name="IOMastersAura55", lastname="", level="55", race="127", class="62", bodytype="11", hp="4027.6216", mana="0.0000", gender="0", texture="0", helmtexture="0", herosforgemodel="0", size="2", hp_regen_rate="0", mana_regen_rate="0", loottable_id="0", npc_spells_id="0", mindmg="52.7514", maxdmg="166.8270", attack_count="-1", special_abilities="12,1^13,1^14,1^15,1^16,1^17,1^18,1^19,1^20,1^21,1^22,1^23,1^24,1^25,1^28,1^31,1^35,1^", aggroradius="70", assistradius="0", face="0", luclin_hairstyle="0", luclin_haircolor="0", luclin_eyecolor="0", luclin_eyecolor2="0", luclin_beardcolor="0", luclin_beard="0", drakkin_heritage="0", drakkin_tattoo="0", drakkin_details="0", armortint_red="0", armortint_green="0", armortint_blue="0", d_melee_texture1="0", d_melee_texture2="0", prim_melee_type="28", sec_melee_type="28", runspeed="1.25", MR="21.1027", CR="21.1027", DR="21.1027", FR="21.1027", PR="21.1027", Corrup="21.1027", PhR="48.3333", see_invis="0", see_invis_undead="0", qglobal="0", AC="257.3784", npc_aggro="0", spawn_limit="0", attack_delay="29.4486", findable="0", STR="205.0000", STA="205.0000", DEX="205.0000", AGI="205.0000", _INT="205.0000", WIS="205.0000", CHA="205.0000", see_hide="0", see_improved_hide="0", trackable="0", ATK="0", Accuracy="0", Avoidance="0", slow_mitigation="0", version="0", maxlevel="0", scalerate="100", private_corpse="0", unique_spawn_by_name="0", underwater="0", emoteid="0", spellscale="100", healscale="100", no_target_hotkey="0", raid_target="0", light="0", ignore_despawn="0", show_name="0"; +INSERT INTO auras SET type=8923, npc_type=@suggestedid, name="Disciples_Aura", spell_id=8937, distance=60, aura_type=1, spawn_type=0, movement=0, duration=5400, icon=-1, cast_time=-1; + +SELECT IFNULL((MAX(id) + 1), 2000000) INTO @suggestedid FROM npc_types where id LIKE '2000___'; +INSERT INTO npc_types SET id=@suggestedid, name="IOMastersAura", lastname="", level="70", race="127", class="62", bodytype="11", hp="4027.6216", mana="0.0000", gender="0", texture="0", helmtexture="0", herosforgemodel="0", size="2", hp_regen_rate="0", mana_regen_rate="0", loottable_id="0", npc_spells_id="0", mindmg="52.7514", maxdmg="166.8270", attack_count="-1", special_abilities="12,1^13,1^14,1^15,1^16,1^17,1^18,1^19,1^20,1^21,1^22,1^23,1^24,1^25,1^28,1^31,1^35,1^", aggroradius="70", assistradius="0", face="0", luclin_hairstyle="0", luclin_haircolor="0", luclin_eyecolor="0", luclin_eyecolor2="0", luclin_beardcolor="0", luclin_beard="0", drakkin_heritage="0", drakkin_tattoo="0", drakkin_details="0", armortint_red="0", armortint_green="0", armortint_blue="0", d_melee_texture1="0", d_melee_texture2="0", prim_melee_type="28", sec_melee_type="28", runspeed="1.25", MR="21.1027", CR="21.1027", DR="21.1027", FR="21.1027", PR="21.1027", Corrup="21.1027", PhR="48.3333", see_invis="0", see_invis_undead="0", qglobal="0", AC="257.3784", npc_aggro="0", spawn_limit="0", attack_delay="29.4486", findable="0", STR="205.0000", STA="205.0000", DEX="205.0000", AGI="205.0000", _INT="205.0000", WIS="205.0000", CHA="205.0000", see_hide="0", see_improved_hide="0", trackable="0", ATK="0", Accuracy="0", Avoidance="0", slow_mitigation="0", version="0", maxlevel="0", scalerate="100", private_corpse="0", unique_spawn_by_name="0", underwater="0", emoteid="0", spellscale="100", healscale="100", no_target_hotkey="0", raid_target="0", light="0", ignore_despawn="0", show_name="0"; +INSERT INTO auras SET type=8474, npc_type=@suggestedid, name="Master's_Aura", spell_id=8475, distance=60, aura_type=1, spawn_type=0, movement=0, duration=5400, icon=-1, cast_time=-1; + +SELECT IFNULL((MAX(id) + 1), 2000000) INTO @suggestedid FROM npc_types where id LIKE '2000___'; +INSERT INTO npc_types SET id=@suggestedid, name="IOQuicksandTrap55", lastname="", level="55", race="127", class="62", bodytype="11", hp="4027.6216", mana="0.0000", gender="0", texture="0", helmtexture="0", herosforgemodel="0", size="2", hp_regen_rate="0", mana_regen_rate="0", loottable_id="0", npc_spells_id="0", mindmg="52.7514", maxdmg="166.8270", attack_count="-1", special_abilities="12,1^13,1^14,1^15,1^16,1^17,1^18,1^19,1^20,1^21,1^22,1^23,1^24,1^25,1^28,1^31,1^35,1^", aggroradius="70", assistradius="0", face="0", luclin_hairstyle="0", luclin_haircolor="0", luclin_eyecolor="0", luclin_eyecolor2="0", luclin_beardcolor="0", luclin_beard="0", drakkin_heritage="0", drakkin_tattoo="0", drakkin_details="0", armortint_red="0", armortint_green="0", armortint_blue="0", d_melee_texture1="0", d_melee_texture2="0", prim_melee_type="28", sec_melee_type="28", runspeed="1.25", MR="21.1027", CR="21.1027", DR="21.1027", FR="21.1027", PR="21.1027", Corrup="21.1027", PhR="48.3333", see_invis="0", see_invis_undead="0", qglobal="0", AC="257.3784", npc_aggro="0", spawn_limit="0", attack_delay="29.4486", findable="0", STR="205.0000", STA="205.0000", DEX="205.0000", AGI="205.0000", _INT="205.0000", WIS="205.0000", CHA="205.0000", see_hide="0", see_improved_hide="0", trackable="0", ATK="0", Accuracy="0", Avoidance="0", slow_mitigation="0", version="0", maxlevel="0", scalerate="100", private_corpse="0", unique_spawn_by_name="0", underwater="0", emoteid="0", spellscale="100", healscale="100", no_target_hotkey="0", raid_target="0", light="0", ignore_despawn="0", show_name="0"; +INSERT INTO auras SET type=8933, npc_type=@suggestedid, name="Earthen_Strength", spell_id=8948, distance=60, aura_type=2, spawn_type=0, movement=0, duration=5400, icon=-1, cast_time=-1; + +SELECT IFNULL((MAX(id) + 1), 2000000) INTO @suggestedid FROM npc_types where id LIKE '2000___'; +INSERT INTO npc_types SET id=@suggestedid, name="IOQuicksandTrap", lastname="", level="70", race="127", class="62", bodytype="11", hp="4027.6216", mana="0.0000", gender="0", texture="0", helmtexture="0", herosforgemodel="0", size="2", hp_regen_rate="0", mana_regen_rate="0", loottable_id="0", npc_spells_id="0", mindmg="52.7514", maxdmg="166.8270", attack_count="-1", special_abilities="12,1^13,1^14,1^15,1^16,1^17,1^18,1^19,1^20,1^21,1^22,1^23,1^24,1^25,1^28,1^31,1^35,1^", aggroradius="70", assistradius="0", face="0", luclin_hairstyle="0", luclin_haircolor="0", luclin_eyecolor="0", luclin_eyecolor2="0", luclin_beardcolor="0", luclin_beard="0", drakkin_heritage="0", drakkin_tattoo="0", drakkin_details="0", armortint_red="0", armortint_green="0", armortint_blue="0", d_melee_texture1="0", d_melee_texture2="0", prim_melee_type="28", sec_melee_type="28", runspeed="1.25", MR="21.1027", CR="21.1027", DR="21.1027", FR="21.1027", PR="21.1027", Corrup="21.1027", PhR="48.3333", see_invis="0", see_invis_undead="0", qglobal="0", AC="257.3784", npc_aggro="0", spawn_limit="0", attack_delay="29.4486", findable="0", STR="205.0000", STA="205.0000", DEX="205.0000", AGI="205.0000", _INT="205.0000", WIS="205.0000", CHA="205.0000", see_hide="0", see_improved_hide="0", trackable="0", ATK="0", Accuracy="0", Avoidance="0", slow_mitigation="0", version="0", maxlevel="0", scalerate="100", private_corpse="0", unique_spawn_by_name="0", underwater="0", emoteid="0", spellscale="100", healscale="100", no_target_hotkey="0", raid_target="0", light="0", ignore_despawn="0", show_name="0"; +INSERT INTO auras SET type=8518, npc_type=@suggestedid, name="Rathe's_Strength", spell_id=8519, distance=60, aura_type=2, spawn_type=0, movement=0, duration=5400, icon=-1, cast_time=-1; + +SELECT IFNULL((MAX(id) + 1), 2000000) INTO @suggestedid FROM npc_types where id LIKE '2000___'; +INSERT INTO npc_types SET id=@suggestedid, name="IOIllusionistsAura55", lastname="", level="55", race="127", class="62", bodytype="11", hp="4027.6216", mana="0.0000", gender="0", texture="0", helmtexture="0", herosforgemodel="0", size="2", hp_regen_rate="0", mana_regen_rate="0", loottable_id="0", npc_spells_id="0", mindmg="52.7514", maxdmg="166.8270", attack_count="-1", special_abilities="12,1^13,1^14,1^15,1^16,1^17,1^18,1^19,1^20,1^21,1^22,1^23,1^24,1^25,1^28,1^31,1^35,1^", aggroradius="70", assistradius="0", face="0", luclin_hairstyle="0", luclin_haircolor="0", luclin_eyecolor="0", luclin_eyecolor2="0", luclin_beardcolor="0", luclin_beard="0", drakkin_heritage="0", drakkin_tattoo="0", drakkin_details="0", armortint_red="0", armortint_green="0", armortint_blue="0", d_melee_texture1="0", d_melee_texture2="0", prim_melee_type="28", sec_melee_type="28", runspeed="1.25", MR="21.1027", CR="21.1027", DR="21.1027", FR="21.1027", PR="21.1027", Corrup="21.1027", PhR="48.3333", see_invis="0", see_invis_undead="0", qglobal="0", AC="257.3784", npc_aggro="0", spawn_limit="0", attack_delay="29.4486", findable="0", STR="205.0000", STA="205.0000", DEX="205.0000", AGI="205.0000", _INT="205.0000", WIS="205.0000", CHA="205.0000", see_hide="0", see_improved_hide="0", trackable="0", ATK="0", Accuracy="0", Avoidance="0", slow_mitigation="0", version="0", maxlevel="0", scalerate="100", private_corpse="0", unique_spawn_by_name="0", underwater="0", emoteid="0", spellscale="100", healscale="100", no_target_hotkey="0", raid_target="0", light="0", ignore_despawn="0", show_name="0"; +INSERT INTO auras SET type=8931, npc_type=@suggestedid, name="Beguiler's_Aura", spell_id=8946, distance=60, aura_type=1, spawn_type=0, movement=0, duration=5400, icon=-1, cast_time=-1; + +SELECT IFNULL((MAX(id) + 1), 2000000) INTO @suggestedid FROM npc_types where id LIKE '2000___'; +INSERT INTO npc_types SET id=@suggestedid, name="IOIllusionistsAura", lastname="", level="70", race="127", class="62", bodytype="11", hp="4027.6216", mana="0.0000", gender="0", texture="0", helmtexture="0", herosforgemodel="0", size="2", hp_regen_rate="0", mana_regen_rate="0", loottable_id="0", npc_spells_id="0", mindmg="52.7514", maxdmg="166.8270", attack_count="-1", special_abilities="12,1^13,1^14,1^15,1^16,1^17,1^18,1^19,1^20,1^21,1^22,1^23,1^24,1^25,1^28,1^31,1^35,1^", aggroradius="70", assistradius="0", face="0", luclin_hairstyle="0", luclin_haircolor="0", luclin_eyecolor="0", luclin_eyecolor2="0", luclin_beardcolor="0", luclin_beard="0", drakkin_heritage="0", drakkin_tattoo="0", drakkin_details="0", armortint_red="0", armortint_green="0", armortint_blue="0", d_melee_texture1="0", d_melee_texture2="0", prim_melee_type="28", sec_melee_type="28", runspeed="1.25", MR="21.1027", CR="21.1027", DR="21.1027", FR="21.1027", PR="21.1027", Corrup="21.1027", PhR="48.3333", see_invis="0", see_invis_undead="0", qglobal="0", AC="257.3784", npc_aggro="0", spawn_limit="0", attack_delay="29.4486", findable="0", STR="205.0000", STA="205.0000", DEX="205.0000", AGI="205.0000", _INT="205.0000", WIS="205.0000", CHA="205.0000", see_hide="0", see_improved_hide="0", trackable="0", ATK="0", Accuracy="0", Avoidance="0", slow_mitigation="0", version="0", maxlevel="0", scalerate="100", private_corpse="0", unique_spawn_by_name="0", underwater="0", emoteid="0", spellscale="100", healscale="100", no_target_hotkey="0", raid_target="0", light="0", ignore_despawn="0", show_name="0"; +INSERT INTO auras SET type=8509, npc_type=@suggestedid, name="Illusionist's_Aura", spell_id=8510, distance=60, aura_type=1, spawn_type=0, movement=0, duration=5400, icon=-1, cast_time=-1; + +SELECT IFNULL((MAX(id) + 1), 2000000) INTO @suggestedid FROM npc_types where id LIKE '2000___'; +INSERT INTO npc_types SET id=@suggestedid, name="IOLivingVineTrap55", lastname="", level="55", race="127", class="62", bodytype="11", hp="4027.6216", mana="0.0000", gender="0", texture="0", helmtexture="0", herosforgemodel="0", size="2", hp_regen_rate="0", mana_regen_rate="0", loottable_id="0", npc_spells_id="0", mindmg="52.7514", maxdmg="166.8270", attack_count="-1", special_abilities="12,1^13,1^14,1^15,1^16,1^17,1^18,1^19,1^20,1^21,1^22,1^23,1^24,1^25,1^28,1^31,1^35,1^", aggroradius="70", assistradius="0", face="0", luclin_hairstyle="0", luclin_haircolor="0", luclin_eyecolor="0", luclin_eyecolor2="0", luclin_beardcolor="0", luclin_beard="0", drakkin_heritage="0", drakkin_tattoo="0", drakkin_details="0", armortint_red="0", armortint_green="0", armortint_blue="0", d_melee_texture1="0", d_melee_texture2="0", prim_melee_type="28", sec_melee_type="28", runspeed="1.25", MR="21.1027", CR="21.1027", DR="21.1027", FR="21.1027", PR="21.1027", Corrup="21.1027", PhR="48.3333", see_invis="0", see_invis_undead="0", qglobal="0", AC="257.3784", npc_aggro="0", spawn_limit="0", attack_delay="29.4486", findable="0", STR="205.0000", STA="205.0000", DEX="205.0000", AGI="205.0000", _INT="205.0000", WIS="205.0000", CHA="205.0000", see_hide="0", see_improved_hide="0", trackable="0", ATK="0", Accuracy="0", Avoidance="0", slow_mitigation="0", version="0", maxlevel="0", scalerate="100", private_corpse="0", unique_spawn_by_name="0", underwater="0", emoteid="0", spellscale="100", healscale="100", no_target_hotkey="0", raid_target="0", light="0", ignore_despawn="0", show_name="0"; +INSERT INTO auras SET type=8929, npc_type=@suggestedid, name="Aura_of_the_Grove", spell_id=8943, distance=60, aura_type=1, spawn_type=0, movement=0, duration=5400, icon=1, cast_time=12; + +SELECT IFNULL((MAX(id) + 1), 2000000) INTO @suggestedid FROM npc_types where id LIKE '2000___'; +INSERT INTO npc_types SET id=@suggestedid, name="IOLivingVineTrap", lastname="", level="70", race="127", class="62", bodytype="11", hp="4027.6216", mana="0.0000", gender="0", texture="0", helmtexture="0", herosforgemodel="0", size="2", hp_regen_rate="0", mana_regen_rate="0", loottable_id="0", npc_spells_id="0", mindmg="52.7514", maxdmg="166.8270", attack_count="-1", special_abilities="12,1^13,1^14,1^15,1^16,1^17,1^18,1^19,1^20,1^21,1^22,1^23,1^24,1^25,1^28,1^31,1^35,1^", aggroradius="70", assistradius="0", face="0", luclin_hairstyle="0", luclin_haircolor="0", luclin_eyecolor="0", luclin_eyecolor2="0", luclin_beardcolor="0", luclin_beard="0", drakkin_heritage="0", drakkin_tattoo="0", drakkin_details="0", armortint_red="0", armortint_green="0", armortint_blue="0", d_melee_texture1="0", d_melee_texture2="0", prim_melee_type="28", sec_melee_type="28", runspeed="1.25", MR="21.1027", CR="21.1027", DR="21.1027", FR="21.1027", PR="21.1027", Corrup="21.1027", PhR="48.3333", see_invis="0", see_invis_undead="0", qglobal="0", AC="257.3784", npc_aggro="0", spawn_limit="0", attack_delay="29.4486", findable="0", STR="205.0000", STA="205.0000", DEX="205.0000", AGI="205.0000", _INT="205.0000", WIS="205.0000", CHA="205.0000", see_hide="0", see_improved_hide="0", trackable="0", ATK="0", Accuracy="0", Avoidance="0", slow_mitigation="0", version="0", maxlevel="0", scalerate="100", private_corpse="0", unique_spawn_by_name="0", underwater="0", emoteid="0", spellscale="100", healscale="100", no_target_hotkey="0", raid_target="0", light="0", ignore_despawn="0", show_name="0"; +INSERT INTO auras SET type=8499, npc_type=@suggestedid, name="Aura_of_Life", spell_id=8500, distance=60, aura_type=1, spawn_type=0, movement=0, duration=5400, icon=1, cast_time=12; + +SELECT IFNULL((MAX(id) + 1), 2000000) INTO @suggestedid FROM npc_types where id LIKE '2000___'; +INSERT INTO npc_types SET id=@suggestedid, name="IOAuraOfThePious55", lastname="", level="55", race="127", class="62", bodytype="11", hp="4027.6216", mana="0.0000", gender="0", texture="0", helmtexture="0", herosforgemodel="0", size="2", hp_regen_rate="0", mana_regen_rate="0", loottable_id="0", npc_spells_id="0", mindmg="52.7514", maxdmg="166.8270", attack_count="-1", special_abilities="12,1^13,1^14,1^15,1^16,1^17,1^18,1^19,1^20,1^21,1^22,1^23,1^24,1^25,1^28,1^31,1^35,1^", aggroradius="70", assistradius="0", face="0", luclin_hairstyle="0", luclin_haircolor="0", luclin_eyecolor="0", luclin_eyecolor2="0", luclin_beardcolor="0", luclin_beard="0", drakkin_heritage="0", drakkin_tattoo="0", drakkin_details="0", armortint_red="0", armortint_green="0", armortint_blue="0", d_melee_texture1="0", d_melee_texture2="0", prim_melee_type="28", sec_melee_type="28", runspeed="1.25", MR="21.1027", CR="21.1027", DR="21.1027", FR="21.1027", PR="21.1027", Corrup="21.1027", PhR="48.3333", see_invis="0", see_invis_undead="0", qglobal="0", AC="257.3784", npc_aggro="0", spawn_limit="0", attack_delay="29.4486", findable="0", STR="205.0000", STA="205.0000", DEX="205.0000", AGI="205.0000", _INT="205.0000", WIS="205.0000", CHA="205.0000", see_hide="0", see_improved_hide="0", trackable="0", ATK="0", Accuracy="0", Avoidance="0", slow_mitigation="0", version="0", maxlevel="0", scalerate="100", private_corpse="0", unique_spawn_by_name="0", underwater="0", emoteid="0", spellscale="100", healscale="100", no_target_hotkey="0", raid_target="0", light="0", ignore_despawn="0", show_name="0"; +INSERT INTO auras SET type=8928, npc_type=@suggestedid, name="Aura_of_the_Zealot", spell_id=8940, distance=60, aura_type=1, spawn_type=0, movement=0, duration=5400, icon=-1, cast_time=-1; + +SELECT IFNULL((MAX(id) + 1), 2000000) INTO @suggestedid FROM npc_types where id LIKE '2000___'; +INSERT INTO npc_types SET id=@suggestedid, name="IOAuraOfThePious", lastname="", level="70", race="127", class="62", bodytype="11", hp="4027.6216", mana="0.0000", gender="0", texture="0", helmtexture="0", herosforgemodel="0", size="2", hp_regen_rate="0", mana_regen_rate="0", loottable_id="0", npc_spells_id="0", mindmg="52.7514", maxdmg="166.8270", attack_count="-1", special_abilities="12,1^13,1^14,1^15,1^16,1^17,1^18,1^19,1^20,1^21,1^22,1^23,1^24,1^25,1^28,1^31,1^35,1^", aggroradius="70", assistradius="0", face="0", luclin_hairstyle="0", luclin_haircolor="0", luclin_eyecolor="0", luclin_eyecolor2="0", luclin_beardcolor="0", luclin_beard="0", drakkin_heritage="0", drakkin_tattoo="0", drakkin_details="0", armortint_red="0", armortint_green="0", armortint_blue="0", d_melee_texture1="0", d_melee_texture2="0", prim_melee_type="28", sec_melee_type="28", runspeed="1.25", MR="21.1027", CR="21.1027", DR="21.1027", FR="21.1027", PR="21.1027", Corrup="21.1027", PhR="48.3333", see_invis="0", see_invis_undead="0", qglobal="0", AC="257.3784", npc_aggro="0", spawn_limit="0", attack_delay="29.4486", findable="0", STR="205.0000", STA="205.0000", DEX="205.0000", AGI="205.0000", _INT="205.0000", WIS="205.0000", CHA="205.0000", see_hide="0", see_improved_hide="0", trackable="0", ATK="0", Accuracy="0", Avoidance="0", slow_mitigation="0", version="0", maxlevel="0", scalerate="100", private_corpse="0", unique_spawn_by_name="0", underwater="0", emoteid="0", spellscale="100", healscale="100", no_target_hotkey="0", raid_target="0", light="0", ignore_despawn="0", show_name="0"; +INSERT INTO auras SET type=8495, npc_type=@suggestedid, name="Aura_of_the_Pious", spell_id=8496, distance=60, aura_type=1, spawn_type=0, movement=0, duration=5400, icon=-1, cast_time=-1; + +SELECT IFNULL((MAX(id) + 1), 2000000) INTO @suggestedid FROM npc_types where id LIKE '2000___'; +INSERT INTO npc_types SET id=@suggestedid, name="IOBloodlustAura55", lastname="", level="55", race="127", class="62", bodytype="11", hp="4027.6216", mana="0.0000", gender="0", texture="0", helmtexture="0", herosforgemodel="0", size="2", hp_regen_rate="0", mana_regen_rate="0", loottable_id="0", npc_spells_id="0", mindmg="52.7514", maxdmg="166.8270", attack_count="-1", special_abilities="12,1^13,1^14,1^15,1^16,1^17,1^18,1^19,1^20,1^21,1^22,1^23,1^24,1^25,1^28,1^31,1^35,1^", aggroradius="70", assistradius="0", face="0", luclin_hairstyle="0", luclin_haircolor="0", luclin_eyecolor="0", luclin_eyecolor2="0", luclin_beardcolor="0", luclin_beard="0", drakkin_heritage="0", drakkin_tattoo="0", drakkin_details="0", armortint_red="0", armortint_green="0", armortint_blue="0", d_melee_texture1="0", d_melee_texture2="0", prim_melee_type="28", sec_melee_type="28", runspeed="1.25", MR="21.1027", CR="21.1027", DR="21.1027", FR="21.1027", PR="21.1027", Corrup="21.1027", PhR="48.3333", see_invis="0", see_invis_undead="0", qglobal="0", AC="257.3784", npc_aggro="0", spawn_limit="0", attack_delay="29.4486", findable="0", STR="205.0000", STA="205.0000", DEX="205.0000", AGI="205.0000", _INT="205.0000", WIS="205.0000", CHA="205.0000", see_hide="0", see_improved_hide="0", trackable="0", ATK="0", Accuracy="0", Avoidance="0", slow_mitigation="0", version="0", maxlevel="0", scalerate="100", private_corpse="0", unique_spawn_by_name="0", underwater="0", emoteid="0", spellscale="100", healscale="100", no_target_hotkey="0", raid_target="0", light="0", ignore_despawn="0", show_name="0"; +INSERT INTO auras SET type=8924, npc_type=@suggestedid, name="Aura_of_Rage", spell_id=8959, distance=60, aura_type=1, spawn_type=0, movement=0, duration=5400, icon=-1, cast_time=-1; + +SELECT IFNULL((MAX(id) + 1), 2000000) INTO @suggestedid FROM npc_types where id LIKE '2000___'; +INSERT INTO npc_types SET id=@suggestedid, name="IOBloodlustAura", lastname="", level="70", race="127", class="62", bodytype="11", hp="4027.6216", mana="0.0000", gender="0", texture="0", helmtexture="0", herosforgemodel="0", size="2", hp_regen_rate="0", mana_regen_rate="0", loottable_id="0", npc_spells_id="0", mindmg="52.7514", maxdmg="166.8270", attack_count="-1", special_abilities="12,1^13,1^14,1^15,1^16,1^17,1^18,1^19,1^20,1^21,1^22,1^23,1^24,1^25,1^28,1^31,1^35,1^", aggroradius="70", assistradius="0", face="0", luclin_hairstyle="0", luclin_haircolor="0", luclin_eyecolor="0", luclin_eyecolor2="0", luclin_beardcolor="0", luclin_beard="0", drakkin_heritage="0", drakkin_tattoo="0", drakkin_details="0", armortint_red="0", armortint_green="0", armortint_blue="0", d_melee_texture1="0", d_melee_texture2="0", prim_melee_type="28", sec_melee_type="28", runspeed="1.25", MR="21.1027", CR="21.1027", DR="21.1027", FR="21.1027", PR="21.1027", Corrup="21.1027", PhR="48.3333", see_invis="0", see_invis_undead="0", qglobal="0", AC="257.3784", npc_aggro="0", spawn_limit="0", attack_delay="29.4486", findable="0", STR="205.0000", STA="205.0000", DEX="205.0000", AGI="205.0000", _INT="205.0000", WIS="205.0000", CHA="205.0000", see_hide="0", see_improved_hide="0", trackable="0", ATK="0", Accuracy="0", Avoidance="0", slow_mitigation="0", version="0", maxlevel="0", scalerate="100", private_corpse="0", unique_spawn_by_name="0", underwater="0", emoteid="0", spellscale="100", healscale="100", no_target_hotkey="0", raid_target="0", light="0", ignore_despawn="0", show_name="0"; +INSERT INTO auras SET type=8477, npc_type=@suggestedid, name="Bloodlust_Aura", spell_id=8478, distance=60, aura_type=1, spawn_type=0, movement=0, duration=5400, icon=-1, cast_time=-1; + +SELECT IFNULL((MAX(id) + 1), 2000000) INTO @suggestedid FROM npc_types where id LIKE '2000___'; +INSERT INTO npc_types SET id=@suggestedid, name="IOIdolOfMalaTrap55", lastname="", level="55", race="514", class="62", bodytype="5", hp="4027.6216", mana="0.0000", gender="2", texture="0", helmtexture="0", herosforgemodel="0", size="2.5", hp_regen_rate="0", mana_regen_rate="0", loottable_id="0", npc_spells_id="0", mindmg="52.7514", maxdmg="166.8270", attack_count="-1", special_abilities="12,1^13,1^14,1^15,1^16,1^17,1^18,1^19,1^20,1^21,1^22,1^23,1^24,1^25,1^28,1^31,1^35,1^", aggroradius="70", assistradius="0", face="0", luclin_hairstyle="0", luclin_haircolor="0", luclin_eyecolor="0", luclin_eyecolor2="0", luclin_beardcolor="0", luclin_beard="0", drakkin_heritage="0", drakkin_tattoo="0", drakkin_details="0", armortint_red="0", armortint_green="0", armortint_blue="0", d_melee_texture1="0", d_melee_texture2="0", prim_melee_type="28", sec_melee_type="28", runspeed="1.25", MR="21.1027", CR="21.1027", DR="21.1027", FR="21.1027", PR="21.1027", Corrup="21.1027", PhR="48.3333", see_invis="0", see_invis_undead="0", qglobal="0", AC="257.3784", npc_aggro="0", spawn_limit="0", attack_delay="29.4486", findable="0", STR="205.0000", STA="205.0000", DEX="205.0000", AGI="205.0000", _INT="205.0000", WIS="205.0000", CHA="205.0000", see_hide="0", see_improved_hide="0", trackable="0", ATK="0", Accuracy="0", Avoidance="0", slow_mitigation="0", version="0", maxlevel="0", scalerate="100", private_corpse="0", unique_spawn_by_name="0", underwater="0", emoteid="0", spellscale="100", healscale="100", no_target_hotkey="0", raid_target="0", light="0", ignore_despawn="0", show_name="0"; +INSERT INTO auras SET type=8930, npc_type=@suggestedid, name="Soul_Idol", spell_id=8945, distance=60, aura_type=3, spawn_type=1, movement=1, duration=120, icon=-1, cast_time=12; + +SELECT IFNULL((MAX(id) + 1), 2000000) INTO @suggestedid FROM npc_types where id LIKE '2000___'; +INSERT INTO npc_types SET id=@suggestedid, name="IOIdolOfMalaTrap", lastname="", level="70", race="514", class="62", bodytype="5", hp="4027.6216", mana="0.0000", gender="2", texture="0", helmtexture="0", herosforgemodel="0", size="2.5", hp_regen_rate="0", mana_regen_rate="0", loottable_id="0", npc_spells_id="0", mindmg="52.7514", maxdmg="166.8270", attack_count="-1", special_abilities="12,1^13,1^14,1^15,1^16,1^17,1^18,1^19,1^20,1^21,1^22,1^23,1^24,1^25,1^28,1^31,1^35,1^", aggroradius="70", assistradius="0", face="0", luclin_hairstyle="0", luclin_haircolor="0", luclin_eyecolor="0", luclin_eyecolor2="0", luclin_beardcolor="0", luclin_beard="0", drakkin_heritage="0", drakkin_tattoo="0", drakkin_details="0", armortint_red="0", armortint_green="0", armortint_blue="0", d_melee_texture1="0", d_melee_texture2="0", prim_melee_type="28", sec_melee_type="28", runspeed="1.25", MR="21.1027", CR="21.1027", DR="21.1027", FR="21.1027", PR="21.1027", Corrup="21.1027", PhR="48.3333", see_invis="0", see_invis_undead="0", qglobal="0", AC="257.3784", npc_aggro="0", spawn_limit="0", attack_delay="29.4486", findable="0", STR="205.0000", STA="205.0000", DEX="205.0000", AGI="205.0000", _INT="205.0000", WIS="205.0000", CHA="205.0000", see_hide="0", see_improved_hide="0", trackable="0", ATK="0", Accuracy="0", Avoidance="0", slow_mitigation="0", version="0", maxlevel="0", scalerate="100", private_corpse="0", unique_spawn_by_name="0", underwater="0", emoteid="0", spellscale="100", healscale="100", no_target_hotkey="0", raid_target="0", light="0", ignore_despawn="0", show_name="0"; +INSERT INTO auras SET type=8504, npc_type=@suggestedid, name="Spirit_Idol", spell_id=8505, distance=60, aura_type=3, spawn_type=1, movement=1, duration=120, icon=-1, cast_time=12; + +SELECT IFNULL((MAX(id) + 1), 2000000) INTO @suggestedid FROM npc_types where id LIKE '2000___'; +INSERT INTO npc_types SET id=@suggestedid, name="IODeathRuneTrap55", lastname="", level="55", race="510", class="62", bodytype="5", hp="4027.6216", mana="0.0000", gender="2", texture="0", helmtexture="0", herosforgemodel="0", size="3", hp_regen_rate="0", mana_regen_rate="0", loottable_id="0", npc_spells_id="0", mindmg="52.7514", maxdmg="166.8270", attack_count="-1", special_abilities="12,1^13,1^14,1^15,1^16,1^17,1^18,1^19,1^20,1^21,1^22,1^23,1^24,1^25,1^28,1^31,1^35,1^", aggroradius="70", assistradius="0", face="0", luclin_hairstyle="0", luclin_haircolor="0", luclin_eyecolor="0", luclin_eyecolor2="0", luclin_beardcolor="0", luclin_beard="0", drakkin_heritage="0", drakkin_tattoo="0", drakkin_details="0", armortint_red="0", armortint_green="0", armortint_blue="0", d_melee_texture1="0", d_melee_texture2="0", prim_melee_type="28", sec_melee_type="28", runspeed="1.25", MR="21.1027", CR="21.1027", DR="21.1027", FR="21.1027", PR="21.1027", Corrup="21.1027", PhR="48.3333", see_invis="0", see_invis_undead="0", qglobal="0", AC="257.3784", npc_aggro="0", spawn_limit="0", attack_delay="29.4486", findable="0", STR="205.0000", STA="205.0000", DEX="205.0000", AGI="205.0000", _INT="205.0000", WIS="205.0000", CHA="205.0000", see_hide="0", see_improved_hide="0", trackable="0", ATK="0", Accuracy="0", Avoidance="0", slow_mitigation="0", version="0", maxlevel="0", scalerate="100", private_corpse="0", unique_spawn_by_name="0", underwater="0", emoteid="0", spellscale="100", healscale="100", no_target_hotkey="0", raid_target="0", light="0", ignore_despawn="0", show_name="0"; +INSERT INTO auras SET type=8934, npc_type=@suggestedid, name="a_dark_rune", spell_id=8949, distance=25, aura_type=4, spawn_type=1, movement=1, duration=120, icon=-1, cast_time=-1; + +SELECT IFNULL((MAX(id) + 1), 2000000) INTO @suggestedid FROM npc_types where id LIKE '2000___'; +INSERT INTO npc_types SET id=@suggestedid, name="IODeathRuneTrap", lastname="", level="70", race="510", class="62", bodytype="5", hp="4027.6216", mana="0.0000", gender="2", texture="0", helmtexture="0", herosforgemodel="0", size="3", hp_regen_rate="0", mana_regen_rate="0", loottable_id="0", npc_spells_id="0", mindmg="52.7514", maxdmg="166.8270", attack_count="-1", special_abilities="12,1^13,1^14,1^15,1^16,1^17,1^18,1^19,1^20,1^21,1^22,1^23,1^24,1^25,1^28,1^31,1^35,1^", aggroradius="70", assistradius="0", face="0", luclin_hairstyle="0", luclin_haircolor="0", luclin_eyecolor="0", luclin_eyecolor2="0", luclin_beardcolor="0", luclin_beard="0", drakkin_heritage="0", drakkin_tattoo="0", drakkin_details="0", armortint_red="0", armortint_green="0", armortint_blue="0", d_melee_texture1="0", d_melee_texture2="0", prim_melee_type="28", sec_melee_type="28", runspeed="1.25", MR="21.1027", CR="21.1027", DR="21.1027", FR="21.1027", PR="21.1027", Corrup="21.1027", PhR="48.3333", see_invis="0", see_invis_undead="0", qglobal="0", AC="257.3784", npc_aggro="0", spawn_limit="0", attack_delay="29.4486", findable="0", STR="205.0000", STA="205.0000", DEX="205.0000", AGI="205.0000", _INT="205.0000", WIS="205.0000", CHA="205.0000", see_hide="0", see_improved_hide="0", trackable="0", ATK="0", Accuracy="0", Avoidance="0", slow_mitigation="0", version="0", maxlevel="0", scalerate="100", private_corpse="0", unique_spawn_by_name="0", underwater="0", emoteid="0", spellscale="100", healscale="100", no_target_hotkey="0", raid_target="0", light="0", ignore_despawn="0", show_name="0"; +INSERT INTO auras SET type=8523, npc_type=@suggestedid, name="a_death_rune", spell_id=8524, distance=25, aura_type=4, spawn_type=1, movement=1, duration=120, icon=-1, cast_time=-1; + +SELECT IFNULL((MAX(id) + 1), 2000000) INTO @suggestedid FROM npc_types where id LIKE '2000___'; +INSERT INTO npc_types SET id=@suggestedid, name="IOFireRuneTrap55", lastname="", level="55", race="510", class="62", bodytype="5", hp="4027.6216", mana="0.0000", gender="2", texture="0", helmtexture="0", herosforgemodel="0", size="3", hp_regen_rate="0", mana_regen_rate="0", loottable_id="0", npc_spells_id="0", mindmg="52.7514", maxdmg="166.8270", attack_count="-1", special_abilities="12,1^13,1^14,1^15,1^16,1^17,1^18,1^19,1^20,1^21,1^22,1^23,1^24,1^25,1^28,1^31,1^35,1^", aggroradius="70", assistradius="0", face="0", luclin_hairstyle="0", luclin_haircolor="0", luclin_eyecolor="0", luclin_eyecolor2="0", luclin_beardcolor="0", luclin_beard="0", drakkin_heritage="0", drakkin_tattoo="0", drakkin_details="0", armortint_red="0", armortint_green="0", armortint_blue="0", d_melee_texture1="0", d_melee_texture2="0", prim_melee_type="28", sec_melee_type="28", runspeed="1.25", MR="21.1027", CR="21.1027", DR="21.1027", FR="21.1027", PR="21.1027", Corrup="21.1027", PhR="48.3333", see_invis="0", see_invis_undead="0", qglobal="0", AC="257.3784", npc_aggro="0", spawn_limit="0", attack_delay="29.4486", findable="0", STR="205.0000", STA="205.0000", DEX="205.0000", AGI="205.0000", _INT="205.0000", WIS="205.0000", CHA="205.0000", see_hide="0", see_improved_hide="0", trackable="0", ATK="0", Accuracy="0", Avoidance="0", slow_mitigation="0", version="0", maxlevel="0", scalerate="100", private_corpse="0", unique_spawn_by_name="0", underwater="0", emoteid="0", spellscale="100", healscale="100", no_target_hotkey="0", raid_target="0", light="0", ignore_despawn="0", show_name="0"; +INSERT INTO auras SET type=8932, npc_type=@suggestedid, name="a_fiery_rune", spell_id=8947, distance=25, aura_type=4, spawn_type=1, movement=1, duration=120, icon=-1, cast_time=-1; + +SELECT IFNULL((MAX(id) + 1), 2000000) INTO @suggestedid FROM npc_types where id LIKE '2000___'; +INSERT INTO npc_types SET id=@suggestedid, name="IOFireRuneTrap", lastname="", level="70", race="510", class="62", bodytype="5", hp="4027.6216", mana="0.0000", gender="2", texture="0", helmtexture="0", herosforgemodel="0", size="3", hp_regen_rate="0", mana_regen_rate="0", loottable_id="0", npc_spells_id="0", mindmg="52.7514", maxdmg="166.8270", attack_count="-1", special_abilities="12,1^13,1^14,1^15,1^16,1^17,1^18,1^19,1^20,1^21,1^22,1^23,1^24,1^25,1^28,1^31,1^35,1^", aggroradius="70", assistradius="0", face="0", luclin_hairstyle="0", luclin_haircolor="0", luclin_eyecolor="0", luclin_eyecolor2="0", luclin_beardcolor="0", luclin_beard="0", drakkin_heritage="0", drakkin_tattoo="0", drakkin_details="0", armortint_red="0", armortint_green="0", armortint_blue="0", d_melee_texture1="0", d_melee_texture2="0", prim_melee_type="28", sec_melee_type="28", runspeed="1.25", MR="21.1027", CR="21.1027", DR="21.1027", FR="21.1027", PR="21.1027", Corrup="21.1027", PhR="48.3333", see_invis="0", see_invis_undead="0", qglobal="0", AC="257.3784", npc_aggro="0", spawn_limit="0", attack_delay="29.4486", findable="0", STR="205.0000", STA="205.0000", DEX="205.0000", AGI="205.0000", _INT="205.0000", WIS="205.0000", CHA="205.0000", see_hide="0", see_improved_hide="0", trackable="0", ATK="0", Accuracy="0", Avoidance="0", slow_mitigation="0", version="0", maxlevel="0", scalerate="100", private_corpse="0", unique_spawn_by_name="0", underwater="0", emoteid="0", spellscale="100", healscale="100", no_target_hotkey="0", raid_target="0", light="0", ignore_despawn="0", show_name="0"; +INSERT INTO auras SET type=8513, npc_type=@suggestedid, name="a_fire_rune", spell_id=8514, distance=25, aura_type=4, spawn_type=1, movement=1, duration=120, icon=-1, cast_time=-1; + +SELECT IFNULL((MAX(id) + 1), 2000000) INTO @suggestedid FROM npc_types where id LIKE '2000___'; +INSERT INTO npc_types SET id=@suggestedid, name="IOPoisonSpikesTrap55", lastname="", level="55", race="513", class="62", bodytype="5", hp="4027.6216", mana="0.0000", gender="2", texture="0", helmtexture="0", herosforgemodel="0", size="3", hp_regen_rate="0", mana_regen_rate="0", loottable_id="0", npc_spells_id="0", mindmg="52.7514", maxdmg="166.8270", attack_count="-1", special_abilities="12,1^13,1^14,1^15,1^16,1^17,1^18,1^19,1^20,1^21,1^22,1^23,1^24,1^25,1^28,1^31,1^35,1^", aggroradius="70", assistradius="0", face="0", luclin_hairstyle="0", luclin_haircolor="0", luclin_eyecolor="0", luclin_eyecolor2="0", luclin_beardcolor="0", luclin_beard="0", drakkin_heritage="0", drakkin_tattoo="0", drakkin_details="0", armortint_red="0", armortint_green="0", armortint_blue="0", d_melee_texture1="0", d_melee_texture2="0", prim_melee_type="28", sec_melee_type="28", runspeed="1.25", MR="21.1027", CR="21.1027", DR="21.1027", FR="21.1027", PR="21.1027", Corrup="21.1027", PhR="48.3333", see_invis="0", see_invis_undead="0", qglobal="0", AC="257.3784", npc_aggro="0", spawn_limit="0", attack_delay="29.4486", findable="0", STR="205.0000", STA="205.0000", DEX="205.0000", AGI="205.0000", _INT="205.0000", WIS="205.0000", CHA="205.0000", see_hide="0", see_improved_hide="0", trackable="0", ATK="0", Accuracy="0", Avoidance="0", slow_mitigation="0", version="0", maxlevel="0", scalerate="100", private_corpse="0", unique_spawn_by_name="0", underwater="0", emoteid="0", spellscale="100", healscale="100", no_target_hotkey="0", raid_target="0", light="0", ignore_despawn="0", show_name="0"; +INSERT INTO auras SET type=8922, npc_type=@suggestedid, name="poison_spurs", spell_id=8936, distance=25, aura_type=4, spawn_type=1, movement=1, duration=120, icon=-1, cast_time=-1; + +SELECT IFNULL((MAX(id) + 1), 2000000) INTO @suggestedid FROM npc_types where id LIKE '2000___'; +INSERT INTO npc_types SET id=@suggestedid, name="IOPoisonSpikesTrap", lastname="", level="70", race="513", class="62", bodytype="5", hp="4027.6216", mana="0.0000", gender="2", texture="0", helmtexture="0", herosforgemodel="0", size="3", hp_regen_rate="0", mana_regen_rate="0", loottable_id="0", npc_spells_id="0", mindmg="52.7514", maxdmg="166.8270", attack_count="-1", special_abilities="12,1^13,1^14,1^15,1^16,1^17,1^18,1^19,1^20,1^21,1^22,1^23,1^24,1^25,1^28,1^31,1^35,1^", aggroradius="70", assistradius="0", face="0", luclin_hairstyle="0", luclin_haircolor="0", luclin_eyecolor="0", luclin_eyecolor2="0", luclin_beardcolor="0", luclin_beard="0", drakkin_heritage="0", drakkin_tattoo="0", drakkin_details="0", armortint_red="0", armortint_green="0", armortint_blue="0", d_melee_texture1="0", d_melee_texture2="0", prim_melee_type="28", sec_melee_type="28", runspeed="1.25", MR="21.1027", CR="21.1027", DR="21.1027", FR="21.1027", PR="21.1027", Corrup="21.1027", PhR="48.3333", see_invis="0", see_invis_undead="0", qglobal="0", AC="257.3784", npc_aggro="0", spawn_limit="0", attack_delay="29.4486", findable="0", STR="205.0000", STA="205.0000", DEX="205.0000", AGI="205.0000", _INT="205.0000", WIS="205.0000", CHA="205.0000", see_hide="0", see_improved_hide="0", trackable="0", ATK="0", Accuracy="0", Avoidance="0", slow_mitigation="0", version="0", maxlevel="0", scalerate="100", private_corpse="0", unique_spawn_by_name="0", underwater="0", emoteid="0", spellscale="100", healscale="100", no_target_hotkey="0", raid_target="0", light="0", ignore_despawn="0", show_name="0"; +INSERT INTO auras SET type=8471, npc_type=@suggestedid, name="Poison Spikes", spell_id=8472, distance=25, aura_type=4, spawn_type=1, movement=1, duration=120, icon=-1, cast_time=-1; +UPDATE npc_types SET special_abilities = TRIM(TRAILING '^' FROM special_abilities); + diff --git a/world/client.cpp b/world/client.cpp index 19c7747b5..aaf7d4c9c 100644 --- a/world/client.cpp +++ b/world/client.cpp @@ -449,17 +449,36 @@ bool Client::HandleSendLoginInfoPacket(const EQApplicationPacket *app) { return false; } - cle->SetOnline(); - if(minilogin){ + cle->SetOnline(); WorldConfig::DisableStats(); Log(Logs::General, Logs::World_Server, "MiniLogin Account #%d",cle->AccountID()); } - else { - if (!is_player_zoning) { - Log(Logs::General, Logs::World_Server, - "Account (%s) Logging in :: LSID: %d ", cle->AccountName(), cle->LSID()); + else if (!is_player_zoning) { + // Track who is in and who is out of the game + char *inout= (char *) ""; + + if (cle->GetOnline() == CLE_Status_Never){ + // Desktop -> Char Select + inout = (char *) "In"; } + else { + // Game -> Char Select + inout=(char *) "Out"; + } + + // Always at Char select at this point. + // Either from a fresh client launch or coming back from the game. + // Exiting the game entirely does not come through here. + // Could use a Logging Out Completely message somewhere. + cle->SetOnline(CLE_Status_CharSelect); + + Log(Logs::General, Logs::World_Server, + "Account (%s) Logging(%s) to character select :: LSID: %d ", + cle->AccountName(), inout, cle->LSID()); + } + else { + cle->SetOnline(); } const WorldConfig *Config=WorldConfig::get(); @@ -1021,6 +1040,7 @@ bool Client::HandlePacket(const EQApplicationPacket *app) { } case OP_WorldLogout: { + // I don't see this getting executed on logout eqs->Close(); cle->SetOnline(CLE_Status_Offline); //allows this player to log in again without an ip restriction. return false; @@ -1261,6 +1281,10 @@ void Client::Clearance(int8 response) } else { zs_addr = zs->GetIP().c_str(); + if (!zs_addr[0]) { + zs_addr = WorldConfig::get()->LocalAddress.c_str(); + } + if(strcmp(zs_addr, "127.0.0.1") == 0) { Log(Logs::Detail, Logs::World_Server, "Local zone address was %s, setting local address to: %s", zs_addr, WorldConfig::get()->LocalAddress.c_str()); diff --git a/world/cliententry.h b/world/cliententry.h index cb096950c..24a837ae9 100644 --- a/world/cliententry.h +++ b/world/cliententry.h @@ -50,6 +50,7 @@ public: inline const char* LSName() const { return plsname; } inline int16 WorldAdmin() const { return pworldadmin; } inline const char* GetLSKey() const { return plskey; } + inline const int8 GetOnline() const { return pOnline; } // Account stuff inline uint32 AccountID() const { return paccountid; } diff --git a/world/net.cpp b/world/net.cpp index df3749829..599f01c8f 100644 --- a/world/net.cpp +++ b/world/net.cpp @@ -332,6 +332,8 @@ int main(int argc, char** argv) { database.ClearMerchantTemp(); } + RuleManager::Instance()->SaveRules(&database); + Log(Logs::General, Logs::World_Server, "Loading EQ time of day.."); TimeOfDay_Struct eqTime; time_t realtime; @@ -392,12 +394,12 @@ int main(int argc, char** argv) { server_connection->Listen(server_opts); Log(Logs::General, Logs::World_Server, "Server (TCP) listener started."); - server_connection->OnConnectionIdentified("Zone", [](std::shared_ptr connection) { + server_connection->OnConnectionIdentified("Zone", [&console](std::shared_ptr connection) { LogF(Logs::General, Logs::World_Server, "New Zone Server connection from {2} at {0}:{1}", connection->Handle()->RemoteIP(), connection->Handle()->RemotePort(), connection->GetUUID()); numzones++; - zoneserver_list.Add(new ZoneServer(connection)); + zoneserver_list.Add(new ZoneServer(connection, console.get())); }); server_connection->OnConnectionRemoved("Zone", [](std::shared_ptr connection) { diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index a3fc7da7f..4de771701 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -46,7 +46,7 @@ extern UCSConnection UCSLink; extern QueryServConnection QSLink; void CatchSignal(int sig_num); -ZoneServer::ZoneServer(std::shared_ptr connection) +ZoneServer::ZoneServer(std::shared_ptr connection, EQ::Net::ConsoleServer *console) : tcpc(connection), zone_boot_timer(5000) { /* Set Process tracking variable defaults */ @@ -73,6 +73,8 @@ ZoneServer::ZoneServer(std::shared_ptr conn zone_boot_timer.Disable(); } })); + + this->console = console; } ZoneServer::~ZoneServer() { @@ -412,6 +414,27 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { break; } if (scm->chan_num == 7 || scm->chan_num == 14) { + if (scm->deliverto[0] == '*') { + + if (console) { + auto con = console->FindByAccountName(&scm->deliverto[1]); + if (((!con) || (!con->SendChannelMessage(scm, [&scm]() { + auto pack = new ServerPacket(ServerOP_ChannelMessage, + sizeof(ServerChannelMessage_Struct) + strlen(scm->message) + 1); + memcpy(pack->pBuffer, scm, pack->size); + ServerChannelMessage_Struct* scm2 = (ServerChannelMessage_Struct*)pack->pBuffer; + strcpy(scm2->deliverto, scm2->from); + scm2->noreply = true; + client_list.SendPacket(scm->from, pack); + safe_delete(pack); + }))) && (!scm->noreply)) + { + zoneserver_list.SendEmoteMessage(scm->from, 0, 0, 0, "%s is not online at this time.", scm->to); + } + } + break; + } + 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))) { @@ -462,6 +485,20 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { cle->Server()->SendPacket(pack); } else { + if (scm->chan_num == 5 || scm->chan_num == 6 || scm->chan_num == 11) { + if (console) { + console->SendChannelMessage(scm, [&scm]() { + auto pack = new ServerPacket(ServerOP_ChannelMessage, + sizeof(ServerChannelMessage_Struct) + strlen(scm->message) + 1); + memcpy(pack->pBuffer, scm, pack->size); + ServerChannelMessage_Struct* scm2 = (ServerChannelMessage_Struct*)pack->pBuffer; + strcpy(scm2->deliverto, scm2->from); + scm2->noreply = true; + client_list.SendPacket(scm->from, pack); + safe_delete(pack); + }); + } + } zoneserver_list.SendPacket(pack); } break; @@ -1248,6 +1285,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { case ServerOP_CZSignalNPC: case ServerOP_CZSetEntityVariableByNPCTypeID: case ServerOP_CZSignalClient: + case ServerOP_CZSetEntityVariableByClientName: case ServerOP_WWMarquee: case ServerOP_DepopAllPlayersCorpses: case ServerOP_DepopPlayerCorpse: diff --git a/world/zoneserver.h b/world/zoneserver.h index a6bbc8373..1f9e9516c 100644 --- a/world/zoneserver.h +++ b/world/zoneserver.h @@ -22,6 +22,7 @@ #include "../net/servertalk_server.h" #include "../event/timer.h" #include "../timer.h" +#include "console.h" #include #include @@ -31,7 +32,7 @@ class ServerPacket; class ZoneServer : public WorldTCPConnection { public: - ZoneServer(std::shared_ptr connection); + ZoneServer(std::shared_ptr connection, EQ::Net::ConsoleServer *console); ~ZoneServer(); virtual inline bool IsZoneServer() { return true; } @@ -97,6 +98,7 @@ private: uint32 zone_os_process_id; std::string launcher_name; //the launcher which started us std::string launched_name; //the name of the zone we launched. + EQ::Net::ConsoleServer *console; }; #endif diff --git a/zone/CMakeLists.txt b/zone/CMakeLists.txt index b267ae11b..0171c8eb9 100644 --- a/zone/CMakeLists.txt +++ b/zone/CMakeLists.txt @@ -5,6 +5,7 @@ SET(zone_sources aa_ability.cpp aggro.cpp aggromanager.cpp + aura.cpp attack.cpp beacon.cpp bonuses.cpp @@ -51,6 +52,7 @@ SET(zone_sources lua_item.cpp lua_iteminst.cpp lua_mob.cpp + lua_mod.cpp lua_npc.cpp lua_object.cpp lua_packet.cpp @@ -59,6 +61,7 @@ SET(zone_sources lua_raid.cpp lua_spawn.cpp lua_spell.cpp + lua_stat_bonuses.cpp embperl.cpp embxs.cpp entity.cpp @@ -134,6 +137,7 @@ SET(zone_headers aa.h aa_ability.h aggromanager.h + aura.h basic_functions.h beacon.h bot.h @@ -173,6 +177,7 @@ SET(zone_headers lua_item.h lua_iteminst.h lua_mob.h + lua_mod.h lua_npc.h lua_object.h lua_packet.h @@ -182,6 +187,7 @@ SET(zone_headers lua_raid.h lua_spawn.h lua_spell.h + lua_stat_bonuses.h map.h masterentity.h maxskill.h diff --git a/zone/aa.cpp b/zone/aa.cpp index 2fb99a6fb..31ad9c162 100644 --- a/zone/aa.cpp +++ b/zone/aa.cpp @@ -46,8 +46,16 @@ void Mob::TemporaryPets(uint16 spell_id, Mob *targ, const char *name_override, u if (targ != nullptr && targ->IsCorpse()) return; + // yep, even these need pet power! + int act_power = 0; + + if (IsClient()) { + act_power = CastToClient()->GetFocusEffect(focusPetPower, spell_id); + act_power = CastToClient()->mod_pet_power(act_power, spell_id); + } + PetRecord record; - if (!database.GetPetEntry(spells[spell_id].teleport_zone, &record)) + if (!database.GetPoweredPetEntry(spells[spell_id].teleport_zone, act_power, &record)) { Log(Logs::General, Logs::Error, "Unknown swarm pet spell id: %d, check pets table", spell_id); Message(13, "Unable to find data for pet %s", spells[spell_id].teleport_zone); @@ -908,7 +916,7 @@ void Client::SendAlternateAdvancementRank(int aa_id, int level) { void Client::SendAlternateAdvancementStats() { auto outapp = new EQApplicationPacket(OP_AAExpUpdate, sizeof(AltAdvStats_Struct)); AltAdvStats_Struct *aps = (AltAdvStats_Struct *)outapp->pBuffer; - aps->experience = (uint32)(((float)330.0f * (float)m_pp.expAA) / (float)max_AAXP); + aps->experience = (uint32)(((float)330.0f * (float)m_pp.expAA) / (float)GetRequiredAAExperience()); aps->unspent = m_pp.aapoints; aps->percentage = m_epp.perAA; QueuePacket(outapp); @@ -1194,6 +1202,11 @@ void Client::ActivateAlternateAdvancementAbility(int rank_id, int target_id) { Message_StringID(MT_SpellFailure, SNEAK_RESTRICT); return; } + // + // 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) + target_id = GetPetID(); + // 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), EQEmu::CastingSlot::AltAbility, spells[rank->spell].mana, -1, spells[rank->spell].ResistDiff, false)) { diff --git a/zone/attack.cpp b/zone/attack.cpp index 2b9d081bf..4b480f580 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -31,6 +31,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "water_map.h" #include "worldserver.h" #include "zone.h" +#include "lua_parser.h" #include #include @@ -52,7 +53,7 @@ extern WorldServer worldserver; extern EntityList entity_list; extern Zone* zone; -bool Mob::AttackAnimation(EQEmu::skills::SkillType &skillinuse, int Hand, const EQEmu::ItemInstance* weapon) +EQEmu::skills::SkillType Mob::AttackAnimation(int Hand, const EQEmu::ItemInstance* weapon, EQEmu::skills::SkillType skillinuse) { // Determine animation int type = 0; @@ -137,7 +138,8 @@ bool Mob::AttackAnimation(EQEmu::skills::SkillType &skillinuse, int Hand, const type = animDualWield; DoAnim(type, 0, false); - return true; + + return skillinuse; } int Mob::compute_tohit(EQEmu::skills::SkillType skillinuse) @@ -271,6 +273,16 @@ int Mob::GetTotalDefense() // and does other mitigation checks. 'this' is the mob being attacked. bool Mob::CheckHitChance(Mob* other, DamageHitInfo &hit) { +#ifdef LUA_EQEMU + bool lua_ret = false; + bool ignoreDefault = false; + lua_ret = LuaParser::Instance()->CheckHitChance(this, other, hit, ignoreDefault); + + if(ignoreDefault) { + return lua_ret; + } +#endif + Mob *attacker = other; Mob *defender = this; Log(Logs::Detail, Logs::Attack, "CheckHitChance(%s) attacked by %s", defender->GetName(), attacker->GetName()); @@ -301,6 +313,16 @@ bool Mob::CheckHitChance(Mob* other, DamageHitInfo &hit) bool Mob::AvoidDamage(Mob *other, DamageHitInfo &hit) { +#ifdef LUA_EQEMU + bool lua_ret = false; + bool ignoreDefault = false; + lua_ret = LuaParser::Instance()->AvoidDamage(this, other, hit, ignoreDefault); + + if (ignoreDefault) { + return lua_ret; + } +#endif + /* 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. * @@ -871,6 +893,15 @@ double Mob::RollD20(int offense, int mitigation) void Mob::MeleeMitigation(Mob *attacker, DamageHitInfo &hit, ExtraAttackOptions *opts) { +#ifdef LUA_EQEMU + bool ignoreDefault = false; + LuaParser::Instance()->MeleeMitigation(this, attacker, hit, opts, ignoreDefault); + + if (ignoreDefault) { + return; + } +#endif + if (hit.damage_done < 0 || hit.base_damage == 0) return; @@ -1237,6 +1268,7 @@ void Mob::DoAttack(Mob *other, DamageHitInfo &hit, ExtraAttackOptions *opts) return; Log(Logs::Detail, Logs::Combat, "%s::DoAttack vs %s base %d min %d offense %d tohit %d skill %d", 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)) { int strike_through = itembonuses.StrikeThrough + spellbonuses.StrikeThrough + aabonuses.StrikeThrough; @@ -1331,7 +1363,7 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b DamageHitInfo my_hit; // calculate attack_skill and skillinuse depending on hand and weapon // also send Packet to near clients - AttackAnimation(my_hit.skill, Hand, weapon); + my_hit.skill = AttackAnimation(Hand, weapon); Log(Logs::Detail, Logs::Combat, "Attacking with %s in slot %d using skill %d", weapon ? weapon->GetItem()->Name : "Fist", Hand, my_hit.skill); // Now figure out damage @@ -1892,7 +1924,7 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool //do attack animation regardless of whether or not we can hit below int16 charges = 0; EQEmu::ItemInstance weapon_inst(weapon, charges); - AttackAnimation(my_hit.skill, Hand, &weapon_inst); + my_hit.skill = AttackAnimation(Hand, &weapon_inst, my_hit.skill); //basically "if not immune" then do the attack if (weapon_damage > 0) { @@ -2190,7 +2222,7 @@ bool NPC::Death(Mob* killer_mob, int32 damage, uint16 spell, EQEmu::skills::Skil Group *kg = entity_list.GetGroupByClient(give_exp_client); Raid *kr = entity_list.GetRaidByClient(give_exp_client); - int32 finalxp = EXP_FORMULA; + int32 finalxp = give_exp_client->GetExperienceForKill(this); finalxp = give_exp_client->mod_client_xp(finalxp, this); if (kr) { @@ -2356,6 +2388,7 @@ bool NPC::Death(Mob* killer_mob, int32 damage, uint16 spell, EQEmu::skills::Skil entity_list.UnMarkNPC(GetID()); entity_list.RemoveNPC(GetID()); + this->SetID(0); if (killer != 0 && emoteid != 0) @@ -2477,7 +2510,7 @@ bool NPC::Death(Mob* killer_mob, int32 damage, uint16 spell, EQEmu::skills::Skil return true; } -void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, bool iYellForHelp /*= true*/, bool bFrenzy /*= false*/, bool iBuffTic /*= false*/, uint16 spell_id) +void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, bool iYellForHelp /*= true*/, bool bFrenzy /*= false*/, bool iBuffTic /*= false*/, uint16 spell_id, bool pet_command) { if (!other) return; @@ -2516,13 +2549,18 @@ void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, b } } - if (IsPet() && GetOwner() && GetOwner()->GetAA(aaPetDiscipline) && IsHeld() && !IsFocused()) { //ignore aggro if hold and !focus - return; - } + // Pet that is /pet hold on will not add to their hate list if they're not engaged + // Pet that is /pet hold on and /pet focus on will not add others to their hate list + // Pet that is /pet ghold on will never add to their hate list unless /pet attack or /pet qattack - if (IsPet() && GetOwner() && GetOwner()->GetAA(aaPetDiscipline) && IsHeld() && GetOwner()->GetAA(aaAdvancedPetDiscipline) >= 1 && IsFocused()) { - if (!targetmob) - return; + // we skip these checks if it's forced through a pet command + if (!pet_command) { + if (IsPet()) { + if ((IsGHeld() || (IsHeld() && IsFocused())) && !on_hatelist) // we want them to be able to climb the hate list + return; + if ((IsHeld() || IsPetStop() || IsPetRegroup()) && !wasengaged) // not 100% sure on stop/regroup kind of hard to test, but regroup is like "classic hold" + return; + } } if (other->IsNPC() && (other->IsPet() || other->CastToNPC()->GetSwarmOwner() > 0)) @@ -2627,7 +2665,7 @@ void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, b } } - if (mypet && (!(GetAA(aaPetDiscipline) && mypet->IsHeld()))) { // I have a pet, add other to it + if (mypet && !mypet->IsHeld() && !mypet->IsPetStop()) { // I have a pet, add other to it if (!mypet->IsFamiliar() && !mypet->GetSpecialAbility(IMMUNE_AGGRO)) mypet->hate_list.AddEntToHateList(other, 0, 0, bFrenzy); } @@ -2796,6 +2834,8 @@ uint8 Mob::GetWeaponDamageBonus(const EQEmu::ItemData *weapon, bool offhand) } return damage_bonus; } + + return 0; } int Mob::GetHandToHandDamage(void) @@ -3306,7 +3346,10 @@ void Mob::CommonDamage(Mob* attacker, int &damage, const uint16 spell_id, const } //end `if there is some damage being done and theres anattacker person involved` Mob *pet = GetPet(); - if (pet && !pet->IsFamiliar() && !pet->GetSpecialAbility(IMMUNE_AGGRO) && !pet->IsEngaged() && attacker && attacker != this && !attacker->IsCorpse()) + // pets that have GHold will never automatically add NPCs + // pets that have Hold and no Focus will add NPCs if they're engaged + // pets that have Hold and Focus will not add NPCs + if (pet && !pet->IsFamiliar() && !pet->GetSpecialAbility(IMMUNE_AGGRO) && !pet->IsEngaged() && attacker && attacker != this && !attacker->IsCorpse() && !pet->IsGHeld()) { if (!pet->IsHeld()) { Log(Logs::Detail, Logs::Aggro, "Sending pet %s into battle due to attack.", pet->GetName()); @@ -3355,8 +3398,6 @@ void Mob::CommonDamage(Mob* attacker, int &damage, const uint16 spell_id, const SetHP(GetHP() - damage); - if (IsClient() && RuleB(Character, MarqueeHPUpdates)) - this->CastToClient()->SendHPUpdateMarquee(); if (HasDied()) { bool IsSaved = false; @@ -3507,8 +3548,11 @@ void Mob::CommonDamage(Mob* attacker, int &damage, const uint16 spell_id, const if (zone->zonemap && zone->zonemap->CheckLoS(glm::vec3(m_Position), new_pos)) { // If we have LoS on the new loc it should be reachable. if (IsNPC()) { // Is this adequate? + Teleport(new_pos); - SendPosUpdate(); + if (position_update_melee_push_timer.Check()) { + SendPositionUpdate(); + } } } else { @@ -4056,7 +4100,7 @@ void Mob::TryPetCriticalHit(Mob *defender, DamageHitInfo &hit) if (critChance > 0) { if (zone->random.Roll(critChance)) { - critMod += GetCritDmgMob(hit.skill); + critMod += GetCritDmgMod(hit.skill); hit.damage_done += 5; hit.damage_done = (hit.damage_done * critMod) / 100; @@ -4077,6 +4121,15 @@ void Mob::TryPetCriticalHit(Mob *defender, DamageHitInfo &hit) void Mob::TryCriticalHit(Mob *defender, DamageHitInfo &hit, ExtraAttackOptions *opts) { +#ifdef LUA_EQEMU + bool ignoreDefault = false; + LuaParser::Instance()->TryCriticalHit(this, defender, hit, opts, ignoreDefault); + + if (ignoreDefault) { + return; + } +#endif + if (hit.damage_done < 1 || !defender) return; @@ -4185,7 +4238,11 @@ void Mob::TryCriticalHit(Mob *defender, DamageHitInfo &hit, ExtraAttackOptions * // step 2: calculate damage hit.damage_done = std::max(hit.damage_done, hit.base_damage) + 5; int og_damage = hit.damage_done; - int crit_mod = 170 + GetCritDmgMob(hit.skill); + int crit_mod = 170 + GetCritDmgMod(hit.skill); + if (crit_mod < 100) { + crit_mod = 100; + } + hit.damage_done = hit.damage_done * crit_mod / 100; Log(Logs::Detail, Logs::Combat, "Crit success roll %d dex chance %d og dmg %d crit_mod %d new dmg %d", roll, dex_bonus, @@ -4246,7 +4303,7 @@ void Mob::TryCriticalHit(Mob *defender, DamageHitInfo &hit, ExtraAttackOptions * // Crippling blows also have a chance to stun // Kayen: Crippling Blow would cause a chance to interrupt for npcs < 55, with a // staggers message. - if (defender->GetLevel() <= 55 && !defender->GetSpecialAbility(IMMUNE_STUN)) { + if (defender->GetLevel() <= 55 && !defender->GetSpecialAbility(UNSTUNABLE)) { defender->Emote("staggers."); defender->Stun(2000); } @@ -4527,6 +4584,15 @@ const DamageTable &Mob::GetDamageTable() const void Mob::ApplyDamageTable(DamageHitInfo &hit) { +#ifdef LUA_EQEMU + bool ignoreDefault = false; + LuaParser::Instance()->ApplyDamageTable(this, hit, ignoreDefault); + + if (ignoreDefault) { + return; + } +#endif + // someone may want to add this to custom servers, can remove this if that's the case if (!IsClient() #ifdef BOTS @@ -4862,6 +4928,15 @@ void Mob::CommonOutgoingHitSuccess(Mob* defender, DamageHitInfo &hit, ExtraAttac if (!defender) return; +#ifdef LUA_EQEMU + bool ignoreDefault = false; + LuaParser::Instance()->CommonOutgoingHitSuccess(this, defender, hit, opts, ignoreDefault); + + if (ignoreDefault) { + return; + } +#endif + // BER weren't parsing the halving if (hit.skill == EQEmu::skills::SkillArchery || (hit.skill == EQEmu::skills::SkillThrowing && GetClass() != BERSERKER)) @@ -5295,4 +5370,4 @@ void Mob::DoOffHandAttackRounds(Mob *target, ExtraAttackOptions *opts) } } } -} \ No newline at end of file +} diff --git a/zone/aura.cpp b/zone/aura.cpp new file mode 100644 index 000000000..8c54b75ee --- /dev/null +++ b/zone/aura.cpp @@ -0,0 +1,936 @@ +#include "../common/string_util.h" + +#include "aura.h" +#include "client.h" +#include "string_ids.h" +#include "raids.h" + +Aura::Aura(NPCType *type_data, Mob *owner, AuraRecord &record) + : NPC(type_data, 0, owner->GetPosition(), FlyMode3), spell_id(record.spell_id), distance(record.distance), + remove_timer(record.duration), movement_timer(100), process_timer(100), aura_id(-1) +{ + GiveNPCTypeData(type_data); // we will delete this later on + m_owner = owner->GetID(); + + if (record.cast_time) { + cast_timer.SetTimer(record.cast_time); + cast_timer.Disable(); // we don't want to be enabled yet + } + + if (record.aura_type < static_cast(AuraType::Max)) + type = static_cast(record.aura_type); + else + type = AuraType::OnAllGroupMembers; + + if (record.spawn_type < static_cast(AuraSpawns::Max)) + spawn_type = static_cast(record.spawn_type); + else + spawn_type = AuraSpawns::GroupMembers; + + if (record.movement < static_cast(AuraMovement::Max)) + movement_type = static_cast(record.movement); + else + movement_type = AuraMovement::Follow; + + switch (type) { + case AuraType::OnAllFriendlies: + process_func = &Aura::ProcessOnAllFriendlies; + break; + case AuraType::OnAllGroupMembers: + process_func = &Aura::ProcessOnAllGroupMembers; + break; + case AuraType::OnGroupMembersPets: + process_func = &Aura::ProcessOnGroupMembersPets; + break; + case AuraType::Totem: + process_func = &Aura::ProcessTotem; + break; + case AuraType::EnterTrap: + process_func = &Aura::ProcessEnterTrap; + break; + case AuraType::ExitTrap: + process_func = &Aura::ProcessExitTrap; + break; + default: + process_func = nullptr; + } +} + +Mob *Aura::GetOwner() +{ + return entity_list.GetMob(m_owner); +} + +// not 100% sure how this one should work and PVP affects ... +void Aura::ProcessOnAllFriendlies(Mob *owner) +{ + auto &mob_list = entity_list.GetMobList(); // read only reference so we can do it all inline + std::set delayed_remove; + bool is_buff = IsBuffSpell(spell_id); // non-buff spells don't cast on enter + + for (auto &e : mob_list) { + auto mob = e.second; + if (mob->IsClient() || mob->IsPetOwnerClient() || mob->IsMerc()) { + auto it = casted_on.find(mob->GetID()); + + if (it != casted_on.end()) { // we are already on the list, let's check for removal + if (DistanceSquared(GetPosition(), mob->GetPosition()) > distance) + delayed_remove.insert(mob->GetID()); + } else { // not on list, lets check if we're in range + if (DistanceSquared(GetPosition(), mob->GetPosition()) <= distance) { + casted_on.insert(mob->GetID()); + if (is_buff) + SpellFinished(spell_id, mob); + } + } + } + } + + for (auto &e : delayed_remove) { + auto mob = entity_list.GetMob(e); + if (mob != nullptr && is_buff) // some auras cast instant spells so no need to remove + mob->BuffFadeBySpellIDAndCaster(spell_id, GetID()); + casted_on.erase(e); + } + + // so if we have a cast timer and our set isn't empty and timer is disabled we need to enable it + if (cast_timer.GetDuration() > 0 && !cast_timer.Enabled() && !casted_on.empty()) + cast_timer.Start(); + + if (!cast_timer.Enabled() || !cast_timer.Check()) + return; + + for (auto &e : casted_on) { + auto mob = entity_list.GetMob(e); + if (mob != nullptr) + SpellFinished(spell_id, mob); + } +} + +void Aura::ProcessOnAllGroupMembers(Mob *owner) +{ + auto &mob_list = entity_list.GetMobList(); // read only reference so we can do it all inline + std::set delayed_remove; + bool is_buff = IsBuffSpell(spell_id); // non-buff spells don't cast on enter + + if (owner->IsRaidGrouped() && owner->IsClient()) { // currently raids are just client, but safety check + auto raid = owner->GetRaid(); + if (raid == nullptr) { // well shit + owner->RemoveAura(GetID(), false, true); + return; + } + auto group_id = raid->GetGroup(owner->CastToClient()); + + // some lambdas so the for loop is less horrible ... + auto verify_raid_client = [&raid, &group_id, this](Client *c) { + auto idx = raid->GetPlayerIndex(c); + if (c->GetID() == m_owner) { + return DistanceSquared(GetPosition(), c->GetPosition()) <= distance; + } else if (idx == 0xFFFFFFFF || raid->members[idx].GroupNumber != group_id || raid->members[idx].GroupNumber == 0xFFFFFFFF) { + return false; + } else if (DistanceSquared(GetPosition(), c->GetPosition()) > distance) { + return false; + } + return true; + }; + + auto verify_raid_client_pet = [&raid, &group_id, this](Mob *m) { + auto idx = raid->GetPlayerIndex(m->GetOwner()->CastToClient()); + if (m->GetOwner()->GetID() == m_owner) { + return DistanceSquared(GetPosition(), m->GetPosition()) <= distance; + } else if (idx == 0xFFFFFFFF || raid->members[idx].GroupNumber != group_id || raid->members[idx].GroupNumber == 0xFFFFFFFF) { + return false; + } else if (DistanceSquared(GetPosition(), m->GetPosition()) > distance) { + return false; + } + return true; + }; + + auto verify_raid_client_swarm = [&raid, &group_id, this](NPC *n) { + auto owner = entity_list.GetMob(n->GetSwarmOwner()); + if (owner == nullptr) + return false; + auto idx = raid->GetPlayerIndex(owner->CastToClient()); + if (owner->GetID() == m_owner) { + return DistanceSquared(GetPosition(), n->GetPosition()) <= distance; + } else if (idx == 0xFFFFFFFF || raid->members[idx].GroupNumber != group_id || raid->members[idx].GroupNumber == 0xFFFFFFFF) { + return false; + } else if (DistanceSquared(GetPosition(), n->GetPosition()) > distance) { + return false; + } + return true; + }; + + for (auto &e : mob_list) { + auto mob = e.second; + // step 1: check if we're already managing this NPC's buff + auto it = casted_on.find(mob->GetID()); + if (it != casted_on.end()) { + // verify still good! + if (mob->IsClient()) { + if (!verify_raid_client(mob->CastToClient())) + delayed_remove.insert(mob->GetID()); + } else if (mob->IsPet() && mob->IsPetOwnerClient() && mob->GetOwner()) { + if (!verify_raid_client_pet(mob)) + delayed_remove.insert(mob->GetID()); + } else if (mob->IsNPC() && mob->IsPetOwnerClient()) { + auto npc = mob->CastToNPC(); + if (!verify_raid_client_swarm(npc)) + delayed_remove.insert(mob->GetID()); + } + } else { // we're not on it! + if (mob->IsClient() && verify_raid_client(mob->CastToClient())) { + casted_on.insert(mob->GetID()); + if (is_buff) + SpellFinished(spell_id, mob); + } else if (mob->IsPet() && mob->IsPetOwnerClient() && mob->GetOwner() && verify_raid_client_pet(mob)) { + casted_on.insert(mob->GetID()); + if (is_buff) + SpellFinished(spell_id, mob); + } else if (mob->IsNPC() && mob->IsPetOwnerClient()) { + auto npc = mob->CastToNPC(); + if (verify_raid_client_swarm(npc)) { + casted_on.insert(mob->GetID()); + if (is_buff) + SpellFinished(spell_id, mob); + } + } + } + } + } else if (owner->IsGrouped()) { + auto group = owner->GetGroup(); + if (group == nullptr) { // uh oh + owner->RemoveAura(GetID(), false, true); + return; + } + + // lambdas to make for loop less ugly + auto verify_group_pet = [&group, this](Mob *m) { + auto owner = m->GetOwner(); + if (owner != nullptr && group->IsGroupMember(owner) && DistanceSquared(GetPosition(), m->GetPosition()) <= distance) + return true; + return false; + }; + + auto verify_group_swarm = [&group, this](NPC *n) { + auto owner = entity_list.GetMob(n->GetSwarmOwner()); + if (owner != nullptr && group->IsGroupMember(owner) && DistanceSquared(GetPosition(), n->GetPosition()) <= distance) + return true; + return false; + }; + + for (auto &e : mob_list) { + auto mob = e.second; + auto it = casted_on.find(mob->GetID()); + + if (it != casted_on.end()) { // make sure we're still valid + if (mob->IsPet()) { + if (!verify_group_pet(mob)) + delayed_remove.insert(mob->GetID()); + } else if (mob->IsNPC() && mob->CastToNPC()->GetSwarmInfo()) { + if (!verify_group_swarm(mob->CastToNPC())) + delayed_remove.insert(mob->GetID()); + } else if (!group->IsGroupMember(mob) || DistanceSquared(GetPosition(), mob->GetPosition()) > distance) { + delayed_remove.insert(mob->GetID()); + } + } else { // not on, check if we should be! + if (mob->IsPet() && verify_group_pet(mob)) { + casted_on.insert(mob->GetID()); + if (is_buff) + SpellFinished(spell_id, mob); + } else if (mob->IsNPC() && mob->CastToNPC()->GetSwarmInfo() && verify_group_swarm(mob->CastToNPC())) { + casted_on.insert(mob->GetID()); + if (is_buff) + SpellFinished(spell_id, mob); + } else if (group->IsGroupMember(mob) && DistanceSquared(GetPosition(), mob->GetPosition()) <= distance) { + casted_on.insert(mob->GetID()); + if (is_buff) + SpellFinished(spell_id, mob); + } + } + } + } else { + auto verify_solo = [&owner, this](Mob *m) { + if (m->IsPet() && m->GetOwnerID() == owner->GetID()) + return true; + else if (m->IsNPC() && m->CastToNPC()->GetSwarmOwner() == owner->GetID()) + return true; + else if (m->GetID() == owner->GetID()) + return true; + else + return false; + }; + for (auto &e : mob_list) { + auto mob = e.second; + auto it = casted_on.find(mob->GetID()); + bool good = verify_solo(mob); + + if (it != casted_on.end()) { // make sure still valid + if (!good || DistanceSquared(GetPosition(), mob->GetPosition()) > distance) { + delayed_remove.insert(mob->GetID()); + } + } else if (good && DistanceSquared(GetPosition(), mob->GetPosition()) <= distance) { + casted_on.insert(mob->GetID()); + if (is_buff) + SpellFinished(spell_id, mob); + } + } + } + + for (auto &e : delayed_remove) { + auto mob = entity_list.GetMob(e); + if (mob != nullptr && is_buff) // some auras cast instant spells so no need to remove + mob->BuffFadeBySpellIDAndCaster(spell_id, GetID()); + casted_on.erase(e); + } + + // so if we have a cast timer and our set isn't empty and timer is disabled we need to enable it + if (cast_timer.GetDuration() > 0 && !cast_timer.Enabled() && !casted_on.empty()) + cast_timer.Start(); + + if (!cast_timer.Enabled() || !cast_timer.Check()) + return; + + // some auras have to recast (DRU for example, non-buff too) + for (auto &e : casted_on) { + auto mob = entity_list.GetMob(e); + if (mob != nullptr) + SpellFinished(spell_id, mob); + } +} + +void Aura::ProcessOnGroupMembersPets(Mob *owner) +{ + auto &mob_list = entity_list.GetMobList(); // read only reference so we can do it all inline + std::set delayed_remove; + bool is_buff = IsBuffSpell(spell_id); // non-buff spells don't cast on enter + // This type can either live on the pet (level 55/70 MAG aura) or on the pet owner (level 85 MAG aura) + auto group_member = owner->GetOwnerOrSelf(); + + if (group_member->IsRaidGrouped() && group_member->IsClient()) { // currently raids are just client, but safety check + auto raid = group_member->GetRaid(); + if (raid == nullptr) { // well shit + owner->RemoveAura(GetID(), false, true); + return; + } + auto group_id = raid->GetGroup(group_member->CastToClient()); + + // some lambdas so the for loop is less horrible ... + auto verify_raid_client_pet = [&raid, &group_id, &group_member, this](Mob *m) { + auto idx = raid->GetPlayerIndex(m->GetOwner()->CastToClient()); + if (m->GetOwner()->GetID() == group_member->GetID()) { + return DistanceSquared(GetPosition(), m->GetPosition()) <= distance; + } else if (idx == 0xFFFFFFFF || raid->members[idx].GroupNumber != group_id || raid->members[idx].GroupNumber == 0xFFFFFFFF) { + return false; + } else if (DistanceSquared(GetPosition(), m->GetPosition()) > distance) { + return false; + } + return true; + }; + + auto verify_raid_client_swarm = [&raid, &group_id, &group_member, this](NPC *n) { + auto owner = entity_list.GetMob(n->GetSwarmOwner()); + if (owner == nullptr) + return false; + auto idx = raid->GetPlayerIndex(owner->CastToClient()); + if (owner->GetID() == group_member->GetID()) { + return DistanceSquared(GetPosition(), n->GetPosition()) <= distance; + } else if (idx == 0xFFFFFFFF || raid->members[idx].GroupNumber != group_id || raid->members[idx].GroupNumber == 0xFFFFFFFF) { + return false; + } else if (DistanceSquared(GetPosition(), n->GetPosition()) > distance) { + return false; + } + return true; + }; + + for (auto &e : mob_list) { + auto mob = e.second; + // step 1: check if we're already managing this NPC's buff + auto it = casted_on.find(mob->GetID()); + if (it != casted_on.end()) { + // verify still good! + if (mob->IsPet() && mob->IsPetOwnerClient() && mob->GetOwner()) { + if (!verify_raid_client_pet(mob)) + delayed_remove.insert(mob->GetID()); + } else if (mob->IsNPC() && mob->IsPetOwnerClient()) { + auto npc = mob->CastToNPC(); + if (!verify_raid_client_swarm(npc)) + delayed_remove.insert(mob->GetID()); + } + } else { // we're not on it! + if (mob->IsClient()) { + continue; // never hit client + } else if (mob->IsPet() && mob->IsPetOwnerClient() && mob->GetOwner() && verify_raid_client_pet(mob)) { + casted_on.insert(mob->GetID()); + if (is_buff) + SpellFinished(spell_id, mob); + } else if (mob->IsNPC() && mob->IsPetOwnerClient()) { + auto npc = mob->CastToNPC(); + if (verify_raid_client_swarm(npc)) { + casted_on.insert(mob->GetID()); + if (is_buff) + SpellFinished(spell_id, mob); + } + } + } + } + } else if (group_member->IsGrouped()) { + auto group = group_member->GetGroup(); + if (group == nullptr) { // uh oh + owner->RemoveAura(GetID(), false, true); + return; + } + + // lambdas to make for loop less ugly + auto verify_group_pet = [&group, this](Mob *m) { + auto owner = m->GetOwner(); + if (owner != nullptr && group->IsGroupMember(owner) && DistanceSquared(GetPosition(), m->GetPosition()) <= distance) + return true; + return false; + }; + + auto verify_group_swarm = [&group, this](NPC *n) { + auto owner = entity_list.GetMob(n->GetSwarmOwner()); + if (owner != nullptr && group->IsGroupMember(owner) && DistanceSquared(GetPosition(), n->GetPosition()) <= distance) + return true; + return false; + }; + + for (auto &e : mob_list) { + auto mob = e.second; + auto it = casted_on.find(mob->GetID()); + + if (it != casted_on.end()) { // make sure we're still valid + if (mob->IsPet()) { + if (!verify_group_pet(mob)) + delayed_remove.insert(mob->GetID()); + } else if (mob->IsNPC() && mob->CastToNPC()->GetSwarmInfo()) { + if (!verify_group_swarm(mob->CastToNPC())) + delayed_remove.insert(mob->GetID()); + } + } else { // not on, check if we should be! + if (mob->IsClient()) { + continue; + } else if (mob->IsPet() && verify_group_pet(mob)) { + casted_on.insert(mob->GetID()); + if (is_buff) + SpellFinished(spell_id, mob); + } else if (mob->IsNPC() && mob->CastToNPC()->GetSwarmInfo() && verify_group_swarm(mob->CastToNPC())) { + casted_on.insert(mob->GetID()); + if (is_buff) + SpellFinished(spell_id, mob); + } + } + } + } else { + auto verify_solo = [&group_member, this](Mob *m) { + if (m->IsPet() && m->GetOwnerID() == group_member->GetID()) + return true; + else if (m->IsNPC() && m->CastToNPC()->GetSwarmOwner() == group_member->GetID()) + return true; + else + return false; + }; + for (auto &e : mob_list) { + auto mob = e.second; + auto it = casted_on.find(mob->GetID()); + bool good = verify_solo(mob); + + if (it != casted_on.end()) { // make sure still valid + if (!good || DistanceSquared(GetPosition(), mob->GetPosition()) > distance) { + delayed_remove.insert(mob->GetID()); + } + } else if (good && DistanceSquared(GetPosition(), mob->GetPosition()) <= distance) { + casted_on.insert(mob->GetID()); + if (is_buff) + SpellFinished(spell_id, mob); + } + } + } + + for (auto &e : delayed_remove) { + auto mob = entity_list.GetMob(e); + if (mob != nullptr && is_buff) // some auras cast instant spells so no need to remove + mob->BuffFadeBySpellIDAndCaster(spell_id, GetID()); + casted_on.erase(e); + } + + // so if we have a cast timer and our set isn't empty and timer is disabled we need to enable it + if (cast_timer.GetDuration() > 0 && !cast_timer.Enabled() && !casted_on.empty()) + cast_timer.Start(); + + if (!cast_timer.Enabled() || !cast_timer.Check()) + return; + + // some auras have to recast (DRU for example, non-buff too) + for (auto &e : casted_on) { + auto mob = entity_list.GetMob(e); + if (mob != nullptr) + SpellFinished(spell_id, mob); + } +} + +void Aura::ProcessTotem(Mob *owner) +{ + auto &mob_list = entity_list.GetMobList(); // read only reference so we can do it all inline + std::set delayed_remove; + bool is_buff = IsBuffSpell(spell_id); // non-buff spells don't cast on enter + + for (auto &e : mob_list) { + auto mob = e.second; + if (mob == this) + continue; + if (mob == owner) + continue; + if (owner->IsAttackAllowed(mob)) { // might need more checks ... + bool in_range = DistanceSquared(GetPosition(), mob->GetPosition()) <= distance; + auto it = casted_on.find(mob->GetID()); + if (it != casted_on.end()) { + if (!in_range) + delayed_remove.insert(mob->GetID()); + } else if (in_range) { + casted_on.insert(mob->GetID()); + SpellFinished(spell_id, mob); + } + } + } + + for (auto &e : delayed_remove) { + auto mob = entity_list.GetMob(e); + if (mob != nullptr && is_buff) // some auras cast instant spells so no need to remove + mob->BuffFadeBySpellIDAndCaster(spell_id, GetID()); + casted_on.erase(e); + } + + // so if we have a cast timer and our set isn't empty and timer is disabled we need to enable it + if (cast_timer.GetDuration() > 0 && !cast_timer.Enabled() && !casted_on.empty()) + cast_timer.Start(); + + if (!cast_timer.Enabled() || !cast_timer.Check()) + return; + + for (auto &e : casted_on) { + auto mob = entity_list.GetMob(e); + if (mob != nullptr) + SpellFinished(spell_id, mob); + } +} + +void Aura::ProcessEnterTrap(Mob *owner) +{ + auto &mob_list = entity_list.GetMobList(); // read only reference so we can do it all inline + + for (auto &e : mob_list) { + auto mob = e.second; + if (mob == this) + continue; + // might need more checks ... + if (owner->IsAttackAllowed(mob) && DistanceSquared(GetPosition(), mob->GetPosition()) <= distance) { + SpellFinished(spell_id, mob); + owner->RemoveAura(GetID(), false); // if we're a buff (ex. NEC) we don't want to strip :P + break; + } + } +} + +void Aura::ProcessExitTrap(Mob *owner) +{ + auto &mob_list = entity_list.GetMobList(); // read only reference so we can do it all inline + + for (auto &e : mob_list) { + auto mob = e.second; + if (mob == this) + continue; + // might need more checks ... + if (owner->IsAttackAllowed(mob)) { + bool in_range = DistanceSquared(GetPosition(), mob->GetPosition()) <= distance; + auto it = casted_on.find(mob->GetID()); + if (it != casted_on.end()) { + if (!in_range) { + SpellFinished(spell_id, mob); + owner->RemoveAura(GetID(), false); // if we're a buff we don't want to strip :P + break; + } + } else if (in_range) { + casted_on.insert(mob->GetID()); + } + } + } +} + +// this is less than ideal, but other solutions are a bit all over the place +// and hard to reason about +void Aura::ProcessSpawns() +{ + const auto &clients = entity_list.GetClientList(); + for (auto &e : clients) { + auto c = e.second; + bool spawned = spawned_for.find(c->GetID()) != spawned_for.end(); + if (ShouldISpawnFor(c)) { + if (!spawned) { + EQApplicationPacket app; + CreateSpawnPacket(&app, this); + c->QueuePacket(&app); + SendArmorAppearance(c); + spawned_for.insert(c->GetID()); + } + } else if (spawned) { + EQApplicationPacket app; + CreateDespawnPacket(&app, false); + c->QueuePacket(&app); + spawned_for.erase(c->GetID()); + } + } + return; +} + +bool Aura::Process() +{ + // Aura::Depop clears buffs + if (p_depop) + return false; + + auto owner = entity_list.GetMob(m_owner); + if (owner == nullptr) { + Depop(); + return true; + } + + if (remove_timer.Check()) { + owner->RemoveAura(GetID(), false, true); + return true; + } + + if (movement_type == AuraMovement::Follow && GetPosition() != owner->GetPosition() && movement_timer.Check()) { + m_Position = owner->GetPosition(); + auto app = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct)); + auto spu = (PlayerPositionUpdateServer_Struct*)app->pBuffer; + MakeSpawnUpdate(spu); + auto it = spawned_for.begin(); + while (it != spawned_for.end()) { + auto client = entity_list.GetClientByID(*it); + if (client) { + client->QueuePacket(app); + ++it; + } else { + it = spawned_for.erase(it); + } + } + } + // TODO: waypoints? + + if (!process_timer.Check()) + return true; + + if (spawn_type != AuraSpawns::Noone) + ProcessSpawns(); // bit of a hack + + if (process_func) + process_func(*this, owner); + + // TODO: quest calls + return true; +} + +bool Aura::ShouldISpawnFor(Client *c) +{ + if (spawn_type == AuraSpawns::Noone) + return false; + + if (spawn_type == AuraSpawns::Everyone) + return true; + + // hey, it's our owner! + if (c->GetID() == m_owner) + return true; + + // so this one is a bit trickier + auto owner = GetOwner(); + if (owner == nullptr) + return false; // hmm + + owner = owner->GetOwnerOrSelf(); // pet auras we need the pet's owner + if (owner == nullptr) // shouldn't really be needed + return false; + + // gotta check again for pet aura case -.- + if (owner == c) + return true; + + if (owner->IsRaidGrouped() && owner->IsClient()) { + auto raid = owner->GetRaid(); + if (raid == nullptr) + return false; // hmm + auto group_id = raid->GetGroup(owner->CastToClient()); + if (group_id == 0xFFFFFFFF) // owner handled above, and they're in a raid and groupless + return false; + + auto idx = raid->GetPlayerIndex(c); + if (idx == 0xFFFFFFFF) // they're not in our raid! + return false; + + if (raid->members[idx].GroupNumber != group_id) // in our raid, but not our group + return false; + + return true; // we got here so we know that 1 they're in our raid and 2 they're in our group! + } else if (owner->IsGrouped()) { + auto group = owner->GetGroup(); + if (group == nullptr) + return false; // hmm + + // easy, in our group + return group->IsGroupMember(c); + } + + // our owner is not raided or grouped, and they're handled above so we don't spawn! + return false; +} + +void Aura::Depop(bool skip_strip) +{ + // NEC trap casts a dot, so we need some way to not strip :P + if (!skip_strip && IsBuffSpell(spell_id)) { + for (auto &e : casted_on) { + auto mob = entity_list.GetMob(e); + if (mob != nullptr) + mob->BuffFadeBySpellIDAndCaster(spell_id, GetID()); + } + } + casted_on.clear(); + p_depop = true; +} + +// This creates an aura from a casted spell +void Mob::MakeAura(uint16 spell_id) +{ + // TODO: verify room in AuraMgr + if (!IsValidSpell(spell_id)) + return; + + AuraRecord record; + if (!database.GetAuraEntry(spell_id, record)) { + Message(13, "Unable to find data for aura %s", spells[spell_id].name); + Log(Logs::General, Logs::Error, "Unable to find data for aura %d, check auras table.", spell_id); + return; + } + + if (!IsValidSpell(record.spell_id)) { + Message(13, "Casted spell (%d) is not valid for aura %s", record.spell_id, spells[spell_id].name); + Log(Logs::General, Logs::Error, "Casted spell (%d) is not valid for aura %d, check auras table.", + record.spell_id, spell_id); + return; + } + + if (record.aura_type > static_cast(AuraType::Max)) { + return; // TODO: log + } + + bool trap = false; + + switch (static_cast(record.aura_type)) { + case AuraType::ExitTrap: + case AuraType::EnterTrap: + case AuraType::Totem: + trap = true; + break; + default: + trap = false; + break; + } + + if (!CanSpawnAura(trap)) + return; + + const auto base = database.LoadNPCTypesData(record.npc_type); + if (base == nullptr) { + Message(13, "Unable to load NPC data for aura %s", spells[spell_id].teleport_zone); + Log(Logs::General, Logs::Error, + "Unable to load NPC data for aura %s (NPC ID %d), check auras and npc_types tables.", + spells[spell_id].teleport_zone, record.npc_type); + return; + } + + auto npc_type = new NPCType; + memcpy(npc_type, base, sizeof(NPCType)); + + strn0cpy(npc_type->name, record.name, 64); + + auto npc = new Aura(npc_type, this, record); + npc->SetAuraID(spell_id); + entity_list.AddNPC(npc, false); + + if (trap) + AddTrap(npc, record); + else + AddAura(npc, record); +} + +bool ZoneDatabase::GetAuraEntry(uint16 spell_id, AuraRecord &record) +{ + auto query = StringFormat("SELECT npc_type, name, spell_id, distance, aura_type, spawn_type, movement, " + "duration, icon, cast_time FROM auras WHERE type='%d'", + spell_id); + + auto results = QueryDatabase(query); + if (!results.Success()) + return false; + + if (results.RowCount() != 1) + return false; + + auto row = results.begin(); + + record.npc_type = atoi(row[0]); + strn0cpy(record.name, row[1], 64); + record.spell_id = atoi(row[2]); + record.distance = atoi(row[3]); + record.distance *= record.distance; // so we can avoid sqrt + record.aura_type = atoi(row[4]); + record.spawn_type = atoi(row[5]); + record.movement = atoi(row[6]); + record.duration = atoi(row[7]) * 1000; // DB is in seconds + record.icon = atoi(row[8]); + record.cast_time = atoi(row[9]) * 1000; // DB is in seconds + + return true; +} + +void Mob::AddAura(Aura *aura, AuraRecord &record) +{ + // this is called only when it's safe + assert(aura != nullptr); + strn0cpy(aura_mgr.auras[aura_mgr.count].name, aura->GetCleanName(), 64); + aura_mgr.auras[aura_mgr.count].spawn_id = aura->GetID(); + aura_mgr.auras[aura_mgr.count].aura = aura; + if (record.icon == -1) + aura_mgr.auras[aura_mgr.count].icon = spells[record.spell_id].new_icon; + else + aura_mgr.auras[aura_mgr.count].icon = record.icon; + if (IsClient()) { + auto outapp = new EQApplicationPacket(OP_UpdateAura, sizeof(AuraCreate_Struct)); + auto aura_create = (AuraCreate_Struct *)outapp->pBuffer; + aura_create->action = 0; + aura_create->type = 1; // this can be 0 sometimes too + strn0cpy(aura_create->aura_name, aura_mgr.auras[aura_mgr.count].name, 64); + aura_create->entity_id = aura_mgr.auras[aura_mgr.count].spawn_id; + aura_create->icon = aura_mgr.auras[aura_mgr.count].icon; + CastToClient()->FastQueuePacket(&outapp); + } + // we can increment this now + aura_mgr.count++; +} + +void Mob::AddTrap(Aura *aura, AuraRecord &record) +{ + // this is called only when it's safe + assert(aura != nullptr); + strn0cpy(trap_mgr.auras[trap_mgr.count].name, aura->GetCleanName(), 64); + trap_mgr.auras[trap_mgr.count].spawn_id = aura->GetID(); + trap_mgr.auras[trap_mgr.count].aura = aura; + if (record.icon == -1) + trap_mgr.auras[trap_mgr.count].icon = spells[record.spell_id].new_icon; + else + trap_mgr.auras[trap_mgr.count].icon = record.icon; + // doesn't send to client + trap_mgr.count++; +} + +bool Mob::CanSpawnAura(bool trap) +{ + if (trap && !HasFreeTrapSlots()) { + Message_StringID(MT_SpellFailure, NO_MORE_TRAPS); + return false; + } else if (!trap && !HasFreeAuraSlots()) { + Message_StringID(MT_SpellFailure, NO_MORE_AURAS); + return false; + } + + return true; +} + +void Mob::RemoveAllAuras() +{ + if (IsClient()) { + database.SaveAuras(CastToClient()); + EQApplicationPacket outapp(OP_UpdateAura, 4); + outapp.WriteUInt32(2); + CastToClient()->QueuePacket(&outapp); + } + + // this is sent on camp/zone, so it just despawns? + if (aura_mgr.count) { + for (auto &e : aura_mgr.auras) { + if (e.aura) + e.aura->Depop(); + } + } + + aura_mgr.count = 0; + + if (trap_mgr.count) { + for (auto &e : trap_mgr.auras) { + if (e.aura) + e.aura->Depop(); + } + } + + trap_mgr.count = 0; + + return; +} + +void Mob::RemoveAura(int spawn_id, bool skip_strip, bool expired) +{ + for (int i = 0; i < aura_mgr.count; ++i) { + auto &aura = aura_mgr.auras[i]; + if (aura.spawn_id == spawn_id) { + if (aura.aura) + aura.aura->Depop(skip_strip); + if (expired && IsClient()) { + CastToClient()->SendColoredText( + CC_Yellow, StringFormat("%s has expired.", aura.name)); // TODO: verify color + // need to update client UI too + auto app = new EQApplicationPacket(OP_UpdateAura, sizeof(AuraDestory_Struct)); + auto ads = (AuraDestory_Struct *)app->pBuffer; + ads->action = 1; // delete + ads->entity_id = spawn_id; + CastToClient()->QueuePacket(app); + safe_delete(app); + } + while (aura_mgr.count - 1 > i) { + i++; + aura.spawn_id = aura_mgr.auras[i].spawn_id; + aura.icon = aura_mgr.auras[i].icon; + aura.aura = aura_mgr.auras[i].aura; + aura_mgr.auras[i].aura = nullptr; + strn0cpy(aura.name, aura_mgr.auras[i].name, 64); + } + aura_mgr.count--; + return; + } + } + + for (int i = 0; i < trap_mgr.count; ++i) { + auto &aura = trap_mgr.auras[i]; + if (aura.spawn_id == spawn_id) { + if (aura.aura) + aura.aura->Depop(skip_strip); + if (expired && IsClient()) + CastToClient()->SendColoredText( + CC_Yellow, StringFormat("%s has expired.", aura.name)); // TODO: verify color + while (trap_mgr.count - 1 > i) { + i++; + aura.spawn_id = trap_mgr.auras[i].spawn_id; + aura.icon = trap_mgr.auras[i].icon; + aura.aura = trap_mgr.auras[i].aura; + trap_mgr.auras[i].aura = nullptr; + strn0cpy(aura.name, trap_mgr.auras[i].name, 64); + } + trap_mgr.count--; + return; + } + } + + return; +} + diff --git a/zone/aura.h b/zone/aura.h new file mode 100644 index 000000000..ff4f2d51c --- /dev/null +++ b/zone/aura.h @@ -0,0 +1,91 @@ +#ifndef AURA_H +#define AURA_H + +#include +#include + +#include "mob.h" +#include "npc.h" +#include "../common/types.h" +#include "../common/timer.h" + +class Group; +class Raid; +class Mob; +struct NPCType; + +enum class AuraType { + OnAllFriendlies, // AE PC/Pet basically (ex. Circle of Power) + OnAllGroupMembers, // Normal buffing aura (ex. Champion's Aura) + OnGroupMembersPets, // Hits just pets (ex. Rathe's Strength) + Totem, // Starts pulsing on a timer when an enemy enters (ex. Idol of Malos) + EnterTrap, // Casts once when an enemy enters (ex. Fire Rune) + ExitTrap, // Casts when they start to flee (ex. Poison Spikes Trap) + FullyScripted, // We just call script function not a predefined + Max +}; + +enum class AuraSpawns { + GroupMembers, // most auras use this + Everyone, // this is like traps and clickies who cast on everyone + Noone, // custom! + Max +}; + +enum class AuraMovement { + Follow, // follows caster + Stationary, + Pathing, // some sorted pathing TODO: implement + Max +}; + +class Aura : public NPC +{ + // NOTE: We may have to override more virtual functions if they're causing issues +public: + Aura(NPCType *type_data, Mob *owner, AuraRecord &record); + ~Aura() { }; + + bool IsAura() const { return true; } + bool Process(); + void Depop(bool skip_strip = false); + Mob *GetOwner(); + + void ProcessOnAllFriendlies(Mob *owner); + void ProcessOnAllGroupMembers(Mob *owner); + void ProcessOnGroupMembersPets(Mob *owner); + void ProcessTotem(Mob *owner); + void ProcessEnterTrap(Mob *owner); + void ProcessExitTrap(Mob *owner); + void ProcessSpawns(); + + // we only save auras that follow you, and player casted + inline bool AuraZones() { return movement_type == AuraMovement::Follow && aura_id > -1; } + inline int GetSpellID() { return spell_id; } + inline int GetAuraID() { return aura_id; } + inline void SetAuraID(int in) { aura_id = in; } + + bool ShouldISpawnFor(Client *c); + // so when we join a group, we need to spawn not already spawned auras + // This is only possible when spawn type is GroupMembers + inline bool JoinGroupSpawnCheck() { return spawn_type == AuraSpawns::GroupMembers; } +private: + int m_owner; + int aura_id; // spell ID of the aura spell -1 if aura isn't from a casted spell + int spell_id; // spell we cast + int distance; // distance we remove + Timer remove_timer; // when we depop + Timer process_timer; // rate limit process calls + Timer cast_timer; // some auras pulse + Timer movement_timer; // rate limit movement updates + AuraType type; + AuraSpawns spawn_type; + AuraMovement movement_type; + + std::function process_func; + std::set casted_on; // we keep track of the other entities we've casted on + std::set spawned_for; +}; + +#endif /* !AURA_H */ + diff --git a/zone/beacon.cpp b/zone/beacon.cpp index 27f86fce7..36075dca5 100644 --- a/zone/beacon.cpp +++ b/zone/beacon.cpp @@ -68,6 +68,7 @@ Beacon::Beacon(Mob *at_mob, int lifetime) resist_adjust = 0; spell_iterations = 0; caster_id = 0; + max_targets = 4; // default if(lifetime) remove_timer.Start(); @@ -93,10 +94,12 @@ bool Beacon::Process() ) { Mob *caster = entity_list.GetMob(caster_id); - if(caster && spell_iterations--) + if(caster && spell_iterations-- && max_targets) { - bool affect_caster = (!caster->IsNPC() && !caster->IsAIControlled()); //NPC AE spells do not affect the NPC caster - entity_list.AESpell(caster, this, spell_id, affect_caster, resist_adjust); + // 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; + entity_list.AESpell(caster, this, spell_id, affect_caster, resist_adjust, &max_targets); } else { @@ -126,6 +129,8 @@ void Beacon::AELocationSpell(Mob *caster, uint16 cast_spell_id, int16 resist_adj this->resist_adjust = resist_adjust; spell_iterations = spells[spell_id].AEDuration / 2500; spell_iterations = spell_iterations < 1 ? 1 : spell_iterations; // at least 1 + if (spells[spell_id].aemaxtargets) + max_targets = spells[spell_id].aemaxtargets; spell_timer.Start(2500); spell_timer.Trigger(); } diff --git a/zone/beacon.h b/zone/beacon.h index b79ed318c..c22189dfd 100644 --- a/zone/beacon.h +++ b/zone/beacon.h @@ -56,6 +56,7 @@ protected: int16 resist_adjust; int spell_iterations; Timer spell_timer; + int max_targets; uint16 caster_id; private: diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index e732d596b..4406508fe 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -48,6 +48,14 @@ void Mob::CalcBonuses() SetAttackTimer(); CalcAC(); + /* 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()); + if (get_walk_speed >= 0.9 && this->fix_z_timer.GetDuration() != 100) { + this->fix_z_timer.SetTimer(100); + } + rooted = FindType(SE_Root); } @@ -1084,9 +1092,9 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; // base1 = effect value, base2 = skill restrictions(-1 for all) if (base2 == ALL_SKILLS) - newbon->CritDmgMob[EQEmu::skills::HIGHEST_SKILL + 1] += base1; + newbon->CritDmgMod[EQEmu::skills::HIGHEST_SKILL + 1] += base1; else - newbon->CritDmgMob[base2] += base1; + newbon->CritDmgMod[base2] += base1; break; } @@ -1442,11 +1450,27 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) newbon->FeignedCastOnChance = base1; break; + case SE_AddPetCommand: + if (base1 && base2 < PET_MAXCOMMANDS) + newbon->PetCommands[base2] = true; + break; + + case SE_FeignedMinion: + if (newbon->FeignedMinionChance < base1) + newbon->FeignedMinionChance = base1; + break; + + case SE_AdditionalAura: + newbon->aura_slots += base1; + break; + + case SE_IncreaseTrapCount: + newbon->trap_slots += base1; + break; + // to do case SE_PetDiscipline: break; - case SE_PetDiscipline2: - break; case SE_PotionBeltSlots: break; case SE_BandolierSlots: @@ -1465,8 +1489,6 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; case SE_TrapCircumvention: break; - case SE_FeignedMinion: - break; // not handled here case SE_HastenedAASkill: @@ -2435,9 +2457,9 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne if (base2 > EQEmu::skills::HIGHEST_SKILL) break; if(base2 == ALL_SKILLS) - new_bonus->CritDmgMob[EQEmu::skills::HIGHEST_SKILL + 1] += effect_value; + new_bonus->CritDmgMod[EQEmu::skills::HIGHEST_SKILL + 1] += effect_value; else - new_bonus->CritDmgMob[base2] += effect_value; + new_bonus->CritDmgMod[base2] += effect_value; break; } @@ -3187,6 +3209,16 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne if (new_bonus->FeignedCastOnChance < effect_value) new_bonus->FeignedCastOnChance = effect_value; break; + + case SE_AdditionalAura: + if (new_bonus->aura_slots < effect_value) + new_bonus->aura_slots = effect_value; + break; + + case SE_IncreaseTrapCount: + if (new_bonus->trap_slots < effect_value) + new_bonus->trap_slots = effect_value; + break; //Special custom cases for loading effects on to NPC from 'npc_spels_effects' table if (IsAISpellEffect) { @@ -4197,9 +4229,9 @@ void Mob::NegateSpellsBonuses(uint16 spell_id) { for (int e = 0; e < EQEmu::skills::HIGHEST_SKILL + 1; e++) { - spellbonuses.CritDmgMob[e] = effect_value; - aabonuses.CritDmgMob[e] = effect_value; - itembonuses.CritDmgMob[e] = effect_value; + spellbonuses.CritDmgMod[e] = effect_value; + aabonuses.CritDmgMod[e] = effect_value; + itembonuses.CritDmgMod[e] = effect_value; } break; } diff --git a/zone/bot.cpp b/zone/bot.cpp index c66fdeb56..c78c41d62 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -22,6 +22,7 @@ #include "object.h" #include "doors.h" #include "quest_parser_collection.h" +#include "lua_parser.h" #include "../common/string_util.h" #include "../common/say_link.h" @@ -83,7 +84,7 @@ Bot::Bot(NPCType npcTypeData, Client* botOwner) : NPC(&npcTypeData, nullptr, glm GenerateBaseStats(); // Calculate HitPoints Last As It Uses Base Stats cur_hp = GenerateBaseHitPoints(); - cur_mana = GenerateBaseManaPoints(); + current_mana = GenerateBaseManaPoints(); cur_end = CalcBaseEndurance(); hp_regen = CalcHPRegen(); mana_regen = CalcManaRegen(); @@ -128,7 +129,7 @@ Bot::Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double to _baseRace = npcTypeData.race; _baseGender = npcTypeData.gender; cur_hp = npcTypeData.cur_hp; - cur_mana = npcTypeData.Mana; + current_mana = npcTypeData.Mana; RestRegenHP = 0; RestRegenMana = 0; RestRegenEndurance = 0; @@ -205,8 +206,8 @@ Bot::Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double to SpellOnTarget(756, this); // Rezz effects } - if(cur_mana > max_mana) - cur_mana = max_mana; + if(current_mana > max_mana) + current_mana = max_mana; cur_end = max_end; } @@ -2171,7 +2172,7 @@ void Bot::AI_Process() { } if(IsMoving()) - SendPosUpdate(); + SendPositionUpdate(); else SendPosition(); } @@ -2382,7 +2383,7 @@ void Bot::AI_Process() { // TODO: Test RuleB(Bots, UpdatePositionWithTimer) if(IsMoving()) - SendPosUpdate(); + SendPositionUpdate(); else SendPosition(); } @@ -2504,7 +2505,7 @@ void Bot::AI_Process() { } if(IsMoving()) - SendPosUpdate(); + SendPositionUpdate(); else SendPosition(); } @@ -2928,6 +2929,7 @@ void Bot::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) { ns->spawn.light = m_Light.Type[EQEmu::lightsource::LightActive]; ns->spawn.helm = helmtexture; //(GetShowHelm() ? helmtexture : 0); //0xFF; ns->spawn.equip_chest2 = texture; //0xFF; + ns->spawn.show_name = true; const EQEmu::ItemData* item = nullptr; const EQEmu::ItemInstance* inst = nullptr; uint32 spawnedbotid = 0; @@ -3852,11 +3854,11 @@ void Bot::Damage(Mob *from, int32 damage, uint16 spell_id, EQEmu::skills::SkillT } //void Bot::AddToHateList(Mob* other, uint32 hate = 0, int32 damage = 0, bool iYellForHelp = true, bool bFrenzy = false, bool iBuffTic = false) -void Bot::AddToHateList(Mob* other, uint32 hate, int32 damage, bool iYellForHelp, bool bFrenzy, bool iBuffTic) { - Mob::AddToHateList(other, hate, damage, iYellForHelp, bFrenzy, iBuffTic); +void Bot::AddToHateList(Mob* other, uint32 hate, int32 damage, bool iYellForHelp, bool bFrenzy, bool iBuffTic, bool pet_command) { + Mob::AddToHateList(other, hate, damage, iYellForHelp, bFrenzy, iBuffTic, pet_command); } -bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts) { +bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts) { if (!other) { SetTarget(nullptr); Log(Logs::General, Logs::Error, "A null Mob object was passed to Bot::Attack for evaluation!"); @@ -3919,7 +3921,7 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b // calculate attack_skill and skillinuse depending on hand and weapon // also send Packet to near clients DamageHitInfo my_hit; - AttackAnimation(my_hit.skill, Hand, weapon); + my_hit.skill = AttackAnimation(Hand, weapon); Log(Logs::Detail, Logs::Combat, "Attacking with %s in slot %d using skill %d", weapon?weapon->GetItem()->Name:"Fist", Hand, my_hit.skill); // Now figure out damage @@ -5556,8 +5558,8 @@ int32 Bot::CalcMaxMana() { } } - if(cur_mana > max_mana) - cur_mana = max_mana; + if(current_mana > max_mana) + current_mana = max_mana; else if(max_mana < 0) max_mana = 0; diff --git a/zone/bot.h b/zone/bot.h index 6c1dfc53f..1a6bcf7cd 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -325,7 +325,7 @@ public: bool DoFinishedSpellGroupTarget(uint16 spell_id, Mob* spellTarget, EQEmu::CastingSlot slot, bool &stopLogic); void SendBotArcheryWearChange(uint8 material_slot, uint32 material, uint32 color); void Camp(bool databaseSave = true); - virtual void AddToHateList(Mob* other, uint32 hate = 0, int32 damage = 0, bool iYellForHelp = true, bool bFrenzy = false, bool iBuffTic = false); + virtual void AddToHateList(Mob* other, uint32 hate = 0, int32 damage = 0, bool iYellForHelp = true, bool bFrenzy = false, bool iBuffTic = false, bool pet_command = false); virtual void SetTarget(Mob* mob); virtual void Zone(); std::vector GetBotSpells() { return AIspells; } diff --git a/zone/client.cpp b/zone/client.cpp index e2a94638e..4866013a3 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -119,7 +119,6 @@ Client::Client(EQStreamInterface* ieqs) 0, 0 ), - //these must be listed in the order they appear in client.h position_timer(250), hpupdate_timer(2000), camp_timer(29000), @@ -158,10 +157,15 @@ Client::Client(EQStreamInterface* ieqs) m_AutoAttackTargetLocation(0.0f, 0.0f, 0.0f), last_region_type(RegionTypeUnsupported), m_dirtyautohaters(false), - npc_close_scan_timer(6000) + npc_close_scan_timer(6000), + hp_self_update_throttle_timer(300), + hp_other_update_throttle_timer(500), + position_update_timer(10000) { - for(int cf=0; cf < _FilterCount; cf++) - ClientFilters[cf] = FilterShow; + + for (int client_filter = 0; client_filter < _FilterCount; client_filter++) + ClientFilters[client_filter] = FilterShow; + character_id = 0; conn_state = NoPacketsReceived; client_data_loaded = false; @@ -190,8 +194,10 @@ Client::Client(EQStreamInterface* ieqs) strcpy(account_name, ""); tellsoff = false; last_reported_mana = 0; - last_reported_endur = 0; - gmhideme = false; + last_reported_endurance = 0; + last_reported_endurance_percent = 0; + last_reported_mana_percent = 0; + gm_hide_me = false; AFK = false; LFG = false; LFGFromLevel = 0; @@ -255,7 +261,7 @@ Client::Client(EQStreamInterface* ieqs) memset(&m_epp, 0, sizeof(m_epp)); PendingTranslocate = false; PendingSacrifice = false; - BoatID = 0; + controlling_boat_id = 0; KarmaUpdateTimer = new Timer(RuleI(Chat, KarmaUpdateIntervalMS)); GlobalChatLimiterTimer = new Timer(RuleI(Chat, IntervalDurationMS)); @@ -268,7 +274,7 @@ Client::Client(EQStreamInterface* ieqs) RestRegenMana = 0; RestRegenEndurance = 0; XPRate = 100; - cur_end = 0; + current_endurance = 0; m_TimeSinceLastPositionCheck = 0; m_DistanceSinceLastPositionCheck = 0.0f; @@ -286,7 +292,7 @@ Client::Client(EQStreamInterface* ieqs) HideCorpseMode = HideCorpseNone; PendingGuildInvitation = false; - cur_end = 0; + current_endurance = 0; InitializeBuffSlots(); @@ -357,7 +363,7 @@ Client::~Client() { m_tradeskill_object = nullptr; } - close_npcs.clear(); + close_mobs.clear(); if(IsDueling() && GetDuelTarget() != 0) { Entity* entity = entity_list.GetID(GetDuelTarget()); @@ -598,8 +604,8 @@ bool Client::Save(uint8 iCommitNow) { m_pp.cur_hp = GetHP(); } - m_pp.mana = cur_mana; - m_pp.endurance = cur_end; + m_pp.mana = current_mana; + m_pp.endurance = current_endurance; /* Save Character Currency */ database.SaveCharacterCurrency(CharacterID(), &m_pp); @@ -697,12 +703,13 @@ bool Client::AddPacket(const EQApplicationPacket *pApp, bool bAckreq) { //drop the packet because it will never get sent. return(false); } - auto c = new CLIENTPACKET; + + auto c = std::unique_ptr(new CLIENTPACKET); c->ack_req = bAckreq; c->app = pApp->Copy(); - clientpackets.Append(c); + clientpackets.push_back(std::move(c)); return true; } @@ -714,26 +721,23 @@ bool Client::AddPacket(EQApplicationPacket** pApp, bool bAckreq) { //drop the packet because it will never get sent. return(false); } - auto c = new CLIENTPACKET; + auto c = std::unique_ptr(new CLIENTPACKET); c->ack_req = bAckreq; c->app = *pApp; *pApp = nullptr; - clientpackets.Append(c); + clientpackets.push_back(std::move(c)); return true; } bool Client::SendAllPackets() { - LinkedListIterator iterator(clientpackets); - CLIENTPACKET* cp = nullptr; - iterator.Reset(); - while(iterator.MoreElements()) { - cp = iterator.GetData(); + while (!clientpackets.empty()) { + cp = clientpackets.front().get(); if(eqs) eqs->FastQueuePacket((EQApplicationPacket **)&cp->app, cp->ack_req); - iterator.RemoveCurrent(); + clientpackets.pop_front(); Log(Logs::Moderate, Logs::Client_Server_Packet, "Transmitting a packet"); } return true; @@ -1257,6 +1261,37 @@ void Client::Message(uint32 type, const char* message, ...) { safe_delete_array(buffer); } +void Client::FilteredMessage(Mob *sender, uint32 type, eqFilterType filter, const char* message, ...) { + if (!FilteredMessageCheck(sender, filter)) + return; + + va_list argptr; + auto buffer = new char[4096]; + va_start(argptr, message); + vsnprintf(buffer, 4096, message, argptr); + va_end(argptr); + + size_t len = strlen(buffer); + + //client dosent like our packet all the time unless + //we make it really big, then it seems to not care that + //our header is malformed. + //len = 4096 - sizeof(SpecialMesg_Struct); + + uint32 len_packet = sizeof(SpecialMesg_Struct) + len; + auto app = new EQApplicationPacket(OP_SpecialMesg, len_packet); + SpecialMesg_Struct* sm = (SpecialMesg_Struct*)app->pBuffer; + sm->header[0] = 0x00; // Header used for #emote style messages.. + sm->header[1] = 0x00; // Play around with these to see other types + sm->header[2] = 0x00; + sm->msg_type = type; + memcpy(sm->message, buffer, len + 1); + + FastQueuePacket(&app); + + safe_delete_array(buffer); +} + void Client::QuestJournalledMessage(const char *npcname, const char* message) { // npcnames longer than 60 characters crash the client when they log back in @@ -1790,67 +1825,86 @@ const int32& Client::SetMana(int32 amount) { amount = 0; if (amount > GetMaxMana()) amount = GetMaxMana(); - if (amount != cur_mana) + if (amount != current_mana) update = true; - cur_mana = amount; + current_mana = amount; if (update) Mob::SetMana(amount); - SendManaUpdatePacket(); - return cur_mana; + CheckManaEndUpdate(); + return current_mana; } -void Client::SendManaUpdatePacket() { +void Client::CheckManaEndUpdate() { if (!Connected()) return; - if (ClientVersion() >= EQEmu::versions::ClientVersion::SoD) { - SendManaUpdate(); - SendEnduranceUpdate(); - } + if (last_reported_mana != current_mana || last_reported_endurance != current_endurance) { - if (last_reported_mana != cur_mana || last_reported_endur != cur_end) { + if (ClientVersion() >= EQEmu::versions::ClientVersion::SoD) { + SendManaUpdate(); + SendEnduranceUpdate(); + } auto outapp = new EQApplicationPacket(OP_ManaChange, sizeof(ManaChange_Struct)); - ManaChange_Struct* manachange = (ManaChange_Struct*)outapp->pBuffer; - manachange->new_mana = cur_mana; - manachange->stamina = cur_end; - manachange->spell_id = casting_spell_id; - manachange->keepcasting = 1; + ManaChange_Struct* mana_change = (ManaChange_Struct*)outapp->pBuffer; + mana_change->new_mana = current_mana; + mana_change->stamina = current_endurance; + mana_change->spell_id = casting_spell_id; + mana_change->keepcasting = 1; outapp->priority = 6; QueuePacket(outapp); safe_delete(outapp); - Group *g = GetGroup(); + /* Let others know when our mana percent has changed */ + if (this->GetManaPercent() != last_reported_mana_percent) { + Group *group = this->GetGroup(); + Raid *raid = this->GetRaid(); - if(g) - { - outapp = new EQApplicationPacket(OP_MobManaUpdate, sizeof(MobManaUpdate_Struct)); - auto outapp2 = - new EQApplicationPacket(OP_MobEnduranceUpdate, sizeof(MobEnduranceUpdate_Struct)); + if (raid) { + raid->SendManaPacketFrom(this); + } + else if (group) { + group->SendManaPacketFrom(this); + } - MobManaUpdate_Struct *mmus = (MobManaUpdate_Struct *)outapp->pBuffer; - MobEnduranceUpdate_Struct *meus = (MobEnduranceUpdate_Struct *)outapp2->pBuffer; + auto mana_packet = new EQApplicationPacket(OP_ManaUpdate, sizeof(ManaUpdate_Struct)); + ManaUpdate_Struct* mana_update = (ManaUpdate_Struct*)mana_packet->pBuffer; + mana_update->cur_mana = GetMana(); + mana_update->max_mana = GetMaxMana(); + mana_update->spawn_id = GetID(); + QueuePacket(mana_packet); + entity_list.QueueClientsByXTarget(this, mana_packet, false); + safe_delete(mana_packet); - mmus->spawn_id = meus->spawn_id = GetID(); - - mmus->mana = GetManaPercent(); - meus->endurance = GetEndurancePercent(); - - - for(int i = 0; i < MAX_GROUP_MEMBERS; ++i) - if (g->members[i] && g->members[i]->IsClient() && (g->members[i] != this) && (g->members[i]->CastToClient()->ClientVersion() >= EQEmu::versions::ClientVersion::SoD)) - { - g->members[i]->CastToClient()->QueuePacket(outapp); - g->members[i]->CastToClient()->QueuePacket(outapp2); - } - - safe_delete(outapp); - safe_delete(outapp2); + last_reported_mana_percent = this->GetManaPercent(); } + /* Let others know when our endurance percent has changed */ + if (this->GetEndurancePercent() != last_reported_endurance_percent) { + Group *group = this->GetGroup(); + Raid *raid = this->GetRaid(); - last_reported_mana = cur_mana; - last_reported_endur = cur_end; + if (raid) { + raid->SendEndurancePacketFrom(this); + } + else if (group) { + group->SendEndurancePacketFrom(this); + } + + auto endurance_packet = new EQApplicationPacket(OP_EnduranceUpdate, sizeof(EnduranceUpdate_Struct)); + EnduranceUpdate_Struct* endurance_update = (EnduranceUpdate_Struct*)endurance_packet->pBuffer; + endurance_update->cur_end = GetEndurance(); + endurance_update->max_end = GetMaxEndurance(); + endurance_update->spawn_id = GetID(); + QueuePacket(endurance_packet); + entity_list.QueueClientsByXTarget(this, endurance_packet, false); + safe_delete(endurance_packet); + + last_reported_endurance_percent = this->GetEndurancePercent(); + } + + last_reported_mana = current_mana; + last_reported_endurance = current_endurance; } } @@ -1858,12 +1912,11 @@ void Client::SendManaUpdatePacket() { void Client::SendManaUpdate() { auto mana_app = new EQApplicationPacket(OP_ManaUpdate, sizeof(ManaUpdate_Struct)); - ManaUpdate_Struct* mus = (ManaUpdate_Struct*)mana_app->pBuffer; - mus->cur_mana = GetMana(); - mus->max_mana = GetMaxMana(); - mus->spawn_id = GetID(); + ManaUpdate_Struct* mana_update = (ManaUpdate_Struct*)mana_app->pBuffer; + mana_update->cur_mana = GetMana(); + mana_update->max_mana = GetMaxMana(); + mana_update->spawn_id = GetID(); QueuePacket(mana_app); - entity_list.QueueClientsByXTarget(this, mana_app, false); safe_delete(mana_app); } @@ -1871,12 +1924,11 @@ void Client::SendManaUpdate() void Client::SendEnduranceUpdate() { auto end_app = new EQApplicationPacket(OP_EnduranceUpdate, sizeof(EnduranceUpdate_Struct)); - EnduranceUpdate_Struct* eus = (EnduranceUpdate_Struct*)end_app->pBuffer; - eus->cur_end = GetEndurance(); - eus->max_end = GetMaxEndurance(); - eus->spawn_id = GetID(); + EnduranceUpdate_Struct* endurance_update = (EnduranceUpdate_Struct*)end_app->pBuffer; + endurance_update->cur_end = GetEndurance(); + endurance_update->max_end = GetMaxEndurance(); + endurance_update->spawn_id = GetID(); QueuePacket(end_app); - entity_list.QueueClientsByXTarget(this, end_app, false); safe_delete(end_app); } @@ -1892,6 +1944,7 @@ void Client::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) ns->spawn.guildID = GuildID(); // ns->spawn.linkdead = IsLD() ? 1 : 0; // ns->spawn.pvp = GetPVP() ? 1 : 0; + ns->spawn.show_name = true; strcpy(ns->spawn.title, m_pp.title); @@ -1920,7 +1973,7 @@ void Client::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) } bool Client::GMHideMe(Client* client) { - if (gmhideme) { + if (gm_hide_me) { if (client == 0) return true; else if (admin > client->Admin()) @@ -2339,7 +2392,9 @@ bool Client::CheckIncreaseSkill(EQEmu::skills::SkillType skillid, Mob *against_w return false; 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); if(against_who) { if(against_who->GetSpecialAbility(IMMUNE_AGGRO) || against_who->IsClient() || @@ -3187,9 +3242,9 @@ void Client::SetHideMe(bool flag) { EQApplicationPacket app; - gmhideme = flag; + gm_hide_me = flag; - if(gmhideme) + if(gm_hide_me) { database.SetHideMe(AccountID(),true); CreateDespawnPacket(&app, false); @@ -3737,8 +3792,8 @@ void Client::SetEndurance(int32 newEnd) newEnd = GetMaxEndurance(); } - cur_end = newEnd; - SendManaUpdatePacket(); + current_endurance = newEnd; + CheckManaEndUpdate(); } void Client::SacrificeConfirm(Client *caster) @@ -3931,6 +3986,46 @@ void Client::SendPopupToClient(const char *Title, const char *Text, uint32 Popup safe_delete(outapp); } +void Client::SendFullPopup(const char *Title, const char *Text, uint32 PopupID, uint32 NegativeID, uint32 Buttons, uint32 Duration, const char *ButtonName0, const char *ButtonName1, uint32 SoundControls) { + auto outapp = new EQApplicationPacket(OP_OnLevelMessage, sizeof(OnLevelMessage_Struct)); + OnLevelMessage_Struct *olms = (OnLevelMessage_Struct *)outapp->pBuffer; + + if((strlen(Text) > (sizeof(olms->Text)-1)) || (strlen(Title) > (sizeof(olms->Title) - 1)) ) { + safe_delete(outapp); + return; + } + + if (ButtonName0 && ButtonName1 && ( (strlen(ButtonName0) > (sizeof(olms->ButtonName0) - 1)) || (strlen(ButtonName1) > (sizeof(olms->ButtonName1) - 1)) ) ) { + safe_delete(outapp); + return; + } + + strcpy(olms->Title, Title); + strcpy(olms->Text, Text); + + olms->Buttons = Buttons; + + if (ButtonName0 == NULL || ButtonName1 == NULL) { + sprintf(olms->ButtonName0, "%s", "Yes"); + sprintf(olms->ButtonName1, "%s", "No"); + } else { + strcpy(olms->ButtonName0, ButtonName0); + strcpy(olms->ButtonName1, ButtonName1); + } + + if(Duration > 0) + olms->Duration = Duration * 1000; + else + olms->Duration = 0xffffffff; + + olms->PopupID = PopupID; + olms->NegativeID = NegativeID; + olms->SoundControls = SoundControls; + + QueuePacket(outapp); + safe_delete(outapp); +} + void Client::SendWindow(uint32 PopupID, uint32 NegativeID, uint32 Buttons, const char *ButtonName0, const char *ButtonName1, uint32 Duration, int title_type, Client* target, const char *Title, const char *Text, ...) { va_list argptr; char buffer[4096]; @@ -4171,7 +4266,7 @@ bool Client::GroupFollow(Client* inviter) { RemoveAutoXTargets(); } - SetXTargetAutoMgr(GetXTargetAutoMgr()); + SetXTargetAutoMgr(raid->GetXTargetAutoMgr()); if (!GetXTargetAutoMgr()->empty()) SetDirtyAutoHaters(); @@ -4304,7 +4399,7 @@ bool Client::GroupFollow(Client* inviter) { } database.RefreshGroupFromDB(this); - group->SendHPPacketsTo(this); + group->SendHPManaEndPacketsTo(this); //send updates to clients out of zone... group->SendGroupJoinOOZ(this); return true; @@ -5715,6 +5810,20 @@ void Client::SuspendMinion() Message_StringID(clientMessageTell, SUSPEND_MINION_UNSUSPEND, CurrentPet->GetCleanName()); memset(&m_suspendedminion, 0, sizeof(struct PetInfo)); + // TODO: These pet command states need to be synced ... + // Will just fix them for now + if (m_ClientVersionBit & EQEmu::versions::bit_UFAndLater) { + SetPetCommandState(PET_BUTTON_SIT, 0); + SetPetCommandState(PET_BUTTON_STOP, 0); + SetPetCommandState(PET_BUTTON_REGROUP, 0); + SetPetCommandState(PET_BUTTON_FOLLOW, 1); + SetPetCommandState(PET_BUTTON_GUARD, 0); + SetPetCommandState(PET_BUTTON_TAUNT, 1); + SetPetCommandState(PET_BUTTON_HOLD, 0); + SetPetCommandState(PET_BUTTON_GHOLD, 0); + SetPetCommandState(PET_BUTTON_FOCUS, 0); + SetPetCommandState(PET_BUTTON_SPELLHOLD, 0); + } } else return; @@ -6266,7 +6375,7 @@ void Client::LocateCorpse() SetHeading(CalculateHeadingToTarget(ClosestCorpse->GetX(), ClosestCorpse->GetY())); SetTarget(ClosestCorpse); SendTargetCommand(ClosestCorpse->GetID()); - SendPosUpdate(2); + SendPositionUpdate(2); } else if(!GetTarget()) Message_StringID(clientMessageError, SENSE_CORPSE_NONE); @@ -8489,7 +8598,7 @@ void Client::Consume(const EQEmu::ItemData *item, uint8 type, int16 slot, bool a if (type == EQEmu::item::ItemTypeFood) { - int hchange = item->CastTime * cons_mod; + int hchange = item->CastTime_ * cons_mod; hchange = mod_food_value(item, hchange); if(hchange < 0) { return; } @@ -8506,7 +8615,7 @@ void Client::Consume(const EQEmu::ItemData *item, uint8 type, int16 slot, bool a } else { - int tchange = item->CastTime * cons_mod; + int tchange = item->CastTime_ * cons_mod; tchange = mod_drink_value(item, tchange); if(tchange < 0) { return; } @@ -8660,8 +8769,8 @@ void Client::SendHPUpdateMarquee(){ return; /* Health Update Marquee Display: Custom*/ - uint32 health_percentage = (uint32)(this->cur_hp * 100 / this->max_hp); - if (health_percentage == 100) + uint8 health_percentage = (uint8)(this->cur_hp * 100 / this->max_hp); + if (health_percentage >= 100) return; std::string health_update_notification = StringFormat("Health: %u%%", health_percentage); @@ -8937,3 +9046,12 @@ void Client::ProcessAggroMeter() } } +void Client::SetPetCommandState(int button, int state) +{ + auto app = new EQApplicationPacket(OP_PetCommandState, sizeof(PetCommandState_Struct)); + auto pcs = (PetCommandState_Struct *)app->pBuffer; + pcs->button_id = button; + pcs->state = state; + FastQueuePacket(&app); +} + diff --git a/zone/client.h b/zone/client.h index aeabbd63a..952c98333 100644 --- a/zone/client.h +++ b/zone/client.h @@ -70,6 +70,7 @@ namespace EQEmu #include #include #include +#include #define CLIENT_TIMEOUT 90000 @@ -221,7 +222,7 @@ public: Client(EQStreamInterface * ieqs); ~Client(); - std::unordered_map close_npcs; + std::unordered_map close_mobs; bool is_client_moving; //abstract virtual function implementations required by base abstract class @@ -298,6 +299,7 @@ public: const char* GetBuyerWelcomeMessage() { return BuyerWelcomeMessage.c_str(); } void FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho); + bool ShouldISpawnFor(Client *c) { return !GMHideMe(c) && !IsHoveringForRespawn(); } virtual bool Process(); void LogMerchant(Client* player, Mob* merchant, uint32 quantity, uint32 price, const EQEmu::ItemData* item, bool buying); void SendPacketQueue(bool Block = true); @@ -307,6 +309,7 @@ public: void ChannelMessageSend(const char* from, const char* to, uint8 chan_num, uint8 language, const char* message, ...); void ChannelMessageSend(const char* from, const char* to, uint8 chan_num, uint8 language, uint8 lang_skill, const char* message, ...); void Message(uint32 type, const char* message, ...); + void FilteredMessage(Mob *sender, uint32 type, eqFilterType filter, const char* message, ...); void QuestJournalledMessage(const char *npcname, const char* message); void VoiceMacroReceived(uint32 Type, char *Target, uint32 MacroNumber); void SendSound(); @@ -318,7 +321,7 @@ public: bool GetRevoked() const { return revoked; } void SetRevoked(bool rev) { revoked = rev; } inline uint32 GetIP() const { return ip; } - inline bool GetHideMe() const { return gmhideme; } + inline bool GetHideMe() const { return gm_hide_me; } void SetHideMe(bool hm); inline uint16 GetPort() const { return port; } bool IsDead() const { return(dead); } @@ -351,6 +354,8 @@ public: inline InspectMessage_Struct& GetInspectMessage() { return m_inspect_message; } inline const InspectMessage_Struct& GetInspectMessage() const { return m_inspect_message; } + void SetPetCommandState(int button, int state); + bool CheckAccess(int16 iDBLevel, int16 iDefaultLevel); void CheckQuests(const char* zonename, const char* message, uint32 npc_id, uint32 item_id, Mob* other); @@ -536,11 +541,11 @@ public: void CalcMaxEndurance(); //This calculates the maximum endurance we can have int32 CalcBaseEndurance(); //Calculates Base End int32 CalcEnduranceRegen(); //Calculates endurance regen used in DoEnduranceRegen() - int32 GetEndurance() const {return cur_end;} //This gets our current endurance + int32 GetEndurance() const {return current_endurance;} //This gets our current endurance int32 GetMaxEndurance() const {return max_end;} //This gets our endurance from the last CalcMaxEndurance() call int32 CalcEnduranceRegenCap(); int32 CalcHPRegenCap(); - inline uint8 GetEndurancePercent() { return (uint8)((float)cur_end / (float)max_end * 100.0f); } + inline uint8 GetEndurancePercent() { return (uint8)((float)current_endurance / (float)max_end * 100.0f); } void SetEndurance(int32 newEnd); //This sets the current endurance to the new value void DoEnduranceRegen(); //This Regenerates endurance void DoEnduranceUpkeep(); //does the endurance upkeep @@ -567,6 +572,7 @@ public: void AddCrystals(uint32 Radiant, uint32 Ebon); void SendCrystalCounts(); + uint32 GetExperienceForKill(Mob *against); void AddEXP(uint32 in_add_exp, uint8 conlevel = 0xFF, bool resexp = false); uint32 CalcEXP(uint8 conlevel = 0xFF); void SetEXP(uint32 set_exp, uint32 set_aaxp, bool resexp=false); @@ -656,7 +662,7 @@ public: void RefreshGuildInfo(); - void SendManaUpdatePacket(); + void CheckManaEndUpdate(); void SendManaUpdate(); void SendEnduranceUpdate(); uint8 GetFace() const { return m_pp.face; } @@ -792,12 +798,12 @@ public: void AddAAPoints(uint32 points) { m_pp.aapoints += points; SendAlternateAdvancementStats(); } int GetAAPoints() { return m_pp.aapoints; } int GetSpentAA() { return m_pp.aapoints_spent; } + uint32 GetRequiredAAExperience(); //old AA methods that we still use void ResetAA(); void RefundAA(); void SendClearAA(); - inline uint32 GetMaxAAXP(void) const { return max_AAXP; } 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); @@ -942,6 +948,7 @@ public: inline bool HasSpellScribed(int spellid) { return (FindSpellBookSlotBySpellID(spellid) != -1 ? true : false); } uint16 GetMaxSkillAfterSpecializationRules(EQEmu::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); void SendWindow(uint32 PopupID, uint32 NegativeID, uint32 Buttons, const char *ButtonName0, const char *ButtonName1, uint32 Duration, int title_type, Client* target, const char *Title, const char *Text, ...); bool PendingTranslocate; time_t TranslocateTime; @@ -1066,7 +1073,7 @@ public: void Signal(uint32 data); Mob *GetBindSightTarget() { return bind_sight_target; } void SetBindSightTarget(Mob *n) { bind_sight_target = n; } - const uint16 GetBoatID() const { return BoatID; } + const uint16 GetBoatID() const { return controlling_boat_id; } void SendRewards(); bool TryReward(uint32 claim_id); QGlobalCache *GetQGlobals() { return qGlobals; } @@ -1257,6 +1264,8 @@ public: void CheckRegionTypeChanges(); + int32 CalcATK(); + protected: friend class Mob; void CalcItemBonuses(StatBonuses* newbon); @@ -1316,7 +1325,6 @@ private: void HandleTraderPriceUpdate(const EQApplicationPacket *app); - int32 CalcATK(); int32 CalcItemATKCap(); int32 CalcHaste(); @@ -1369,7 +1377,7 @@ private: bool duelaccepted; std::list keyring; bool tellsoff; // GM /toggle - bool gmhideme; + bool gm_hide_me; bool LFG; bool LFP; uint8 LFGFromLevel; @@ -1389,7 +1397,7 @@ private: uint32 weight; bool berserk; bool dead; - uint16 BoatID; + uint16 controlling_boat_id; uint16 TrackingID; uint16 CustomerID; uint16 TraderID; @@ -1404,7 +1412,7 @@ private: int Haste; //precalced value int32 max_end; - int32 cur_end; + int32 current_endurance; PlayerProfile_Struct m_pp; ExtendedProfile_Struct m_epp; @@ -1424,7 +1432,7 @@ private: bool AddPacket(const EQApplicationPacket *, bool); bool AddPacket(EQApplicationPacket**, bool); bool SendAllPackets(); - LinkedList clientpackets; + std::deque> clientpackets; //Zoning related stuff void SendZoneCancel(ZoneChange_Struct *zc); @@ -1478,7 +1486,9 @@ private: Timer helm_toggle_timer; Timer aggro_meter_timer; Timer npc_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 */ glm::vec3 m_Proximity; void BulkSendInventoryItems(); @@ -1487,7 +1497,6 @@ private: uint32 tribute_master_id; - uint32 max_AAXP; bool npcflag; uint8 npclevel; bool feigned; @@ -1495,7 +1504,10 @@ private: bool tgb; bool instalog; int32 last_reported_mana; - int32 last_reported_endur; + int32 last_reported_endurance; + + int8 last_reported_mana_percent; + int8 last_reported_endurance_percent; unsigned int AggroCount; // How many mobs are aggro on us. diff --git a/zone/client_mods.cpp b/zone/client_mods.cpp index ad6a8e7a2..6db0437c5 100644 --- a/zone/client_mods.cpp +++ b/zone/client_mods.cpp @@ -1046,14 +1046,14 @@ int32 Client::CalcMaxMana() if (max_mana < 0) { max_mana = 0; } - if (cur_mana > max_mana) { - cur_mana = max_mana; + if (current_mana > max_mana) { + current_mana = max_mana; } int mana_perc_cap = spellbonuses.ManaPercCap[0]; if (mana_perc_cap) { int curMana_cap = (max_mana * mana_perc_cap) / 100; - if (cur_mana > curMana_cap || (spellbonuses.ManaPercCap[1] && cur_mana > spellbonuses.ManaPercCap[1])) { - cur_mana = curMana_cap; + if (current_mana > curMana_cap || (spellbonuses.ManaPercCap[1] && current_mana > spellbonuses.ManaPercCap[1])) { + current_mana = curMana_cap; } } Log(Logs::Detail, Logs::Spells, "Client::CalcMaxMana() called for %s - returning %d", GetName(), max_mana); @@ -2034,14 +2034,14 @@ void Client::CalcMaxEndurance() if (max_end < 0) { max_end = 0; } - if (cur_end > max_end) { - cur_end = max_end; + if (current_endurance > max_end) { + current_endurance = max_end; } int end_perc_cap = spellbonuses.EndPercCap[0]; 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])) { - cur_end = curEnd_cap; + if (current_endurance > curEnd_cap || (spellbonuses.EndPercCap[1] && current_endurance > spellbonuses.EndPercCap[1])) { + current_endurance = curEnd_cap; } } } diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 4a9c9c22a..95c9013b2 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -323,6 +323,7 @@ void MapOpcodes() ConnectedOpcodes[OP_RecipesSearch] = &Client::Handle_OP_RecipesSearch; ConnectedOpcodes[OP_ReloadUI] = &Client::Handle_OP_ReloadUI; ConnectedOpcodes[OP_RemoveBlockedBuffs] = &Client::Handle_OP_RemoveBlockedBuffs; + ConnectedOpcodes[OP_RemoveTrap] = &Client::Handle_OP_RemoveTrap; ConnectedOpcodes[OP_Report] = &Client::Handle_OP_Report; ConnectedOpcodes[OP_RequestDuel] = &Client::Handle_OP_RequestDuel; ConnectedOpcodes[OP_RequestTitles] = &Client::Handle_OP_RequestTitles; @@ -383,6 +384,7 @@ void MapOpcodes() ConnectedOpcodes[OP_TributeUpdate] = &Client::Handle_OP_TributeUpdate; ConnectedOpcodes[OP_VetClaimRequest] = &Client::Handle_OP_VetClaimRequest; ConnectedOpcodes[OP_VoiceMacroIn] = &Client::Handle_OP_VoiceMacroIn; + ConnectedOpcodes[OP_UpdateAura] = &Client::Handle_OP_UpdateAura;; ConnectedOpcodes[OP_WearChange] = &Client::Handle_OP_WearChange; ConnectedOpcodes[OP_WhoAllRequest] = &Client::Handle_OP_WhoAllRequest; ConnectedOpcodes[OP_WorldUnknown001] = &Client::Handle_OP_Ignore; @@ -583,8 +585,17 @@ void Client::CompleteConnect() if (raid->IsLocked()) raid->SendRaidLockTo(this); + + raid->SendHPManaEndPacketsTo(this); } } + else { + Group *group = nullptr; + group = this->GetGroup(); + if (group) + group->SendHPManaEndPacketsTo(this); + } + //bulk raid send in here eventually @@ -858,6 +869,23 @@ void Client::CompleteConnect() CastToClient()->FastQueuePacket(&outapp); } + // TODO: load these states + // We at least will set them to the correct state for now + if (m_ClientVersionBit & EQEmu::versions::bit_UFAndLater && GetPet()) { + SetPetCommandState(PET_BUTTON_SIT, 0); + SetPetCommandState(PET_BUTTON_STOP, 0); + SetPetCommandState(PET_BUTTON_REGROUP, 0); + SetPetCommandState(PET_BUTTON_FOLLOW, 1); + SetPetCommandState(PET_BUTTON_GUARD, 0); + SetPetCommandState(PET_BUTTON_TAUNT, 1); + SetPetCommandState(PET_BUTTON_HOLD, 0); + SetPetCommandState(PET_BUTTON_GHOLD, 0); + SetPetCommandState(PET_BUTTON_FOCUS, 0); + SetPetCommandState(PET_BUTTON_SPELLHOLD, 0); + } + + database.LoadAuras(this); // this ends up spawning them so probably safer to load this later (here) + entity_list.RefreshClientXTargets(this); worldserver.RequestTellQueue(GetName()); @@ -1263,7 +1291,7 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) lsaccountid = atoi(row[2]); gmspeed = atoi(row[3]); revoked = atoi(row[4]); - gmhideme = atoi(row[5]); + gm_hide_me = atoi(row[5]); account_creation = atoul(row[6]); } @@ -1331,7 +1359,7 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) if (level) { level = m_pp.level; } /* If GM, not trackable */ - if (gmhideme) { trackable = false; } + if (gm_hide_me) { trackable = false; } /* Set Con State for Reporting */ conn_state = PlayerProfileLoaded; @@ -1340,8 +1368,7 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) /* Set Total Seconds Played */ TotalSecondsPlayed = m_pp.timePlayedMin * 60; - /* Set Max AA XP */ - max_AAXP = RuleI(AA, ExpPerPoint); + /* If we can maintain intoxication across zones, check for it */ if (!RuleB(Character, MaintainIntoxicationAcrossZones)) m_pp.intoxication = 0; @@ -2061,7 +2088,6 @@ void Client::Handle_OP_AdventureMerchantRequest(const EQApplicationPacket *app) return; merchantid = tmp->CastToNPC()->MerchantType; - tmp->CastToNPC()->FaceTarget(this->CastToMob()); const EQEmu::ItemData *item = nullptr; std::list merlist = zone->merchanttable[merchantid]; @@ -2993,9 +3019,12 @@ void Client::Handle_OP_AugmentItem(const EQApplicationPacket *app) if (!solvent) { - Log(Logs::General, Logs::Error, "Player tried to safely remove an augment without a distiller."); - Message(13, "Error: Missing an augmentation distiller for safely removing this augment."); - return; + old_aug = tobe_auged->GetAugment(in_augment->augment_index); + if (!old_aug || old_aug->GetItem()->AugDistiller != 0) { + Log(Logs::General, Logs::Error, "Player tried to safely remove an augment without a distiller."); + Message(13, "Error: Missing an augmentation distiller for safely removing this augment."); + return; + } } else if (solvent->GetItem()->ItemType == EQEmu::item::ItemTypeAugmentationDistiller) { @@ -3159,7 +3188,8 @@ void Client::Handle_OP_AugmentItem(const EQApplicationPacket *app) if (itemOneToPush && itemTwoToPush) { // Consume the augment distiller - DeleteItemInInventory(solvent_slot, solvent->IsStackable() ? 1 : 0, true); + if (solvent) + DeleteItemInInventory(solvent_slot, solvent->IsStackable() ? 1 : 0, true); // Remove the augmented item DeleteItemInInventory(item_slot, 0, true); @@ -3855,7 +3885,7 @@ void Client::Handle_OP_BoardBoat(const EQApplicationPacket *app) Mob* boat = entity_list.GetMob(boatname); if (!boat || (boat->GetRace() != CONTROLLED_BOAT && boat->GetRace() != 502)) return; - BoatID = boat->GetID(); // set the client's BoatID to show that it's on this boat + controlling_boat_id = boat->GetID(); // set the client's BoatID to show that it's on this boat return; } @@ -4346,7 +4376,7 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) if (dead) return; - //currently accepting two sizes, one has an extra byte on the end + /* Invalid size check */ if (app->size != sizeof(PlayerPositionUpdateClient_Struct) && app->size != (sizeof(PlayerPositionUpdateClient_Struct) + 1) ) { @@ -4355,30 +4385,30 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) } PlayerPositionUpdateClient_Struct* ppu = (PlayerPositionUpdateClient_Struct*)app->pBuffer; + /* Boat handling */ if (ppu->spawn_id != GetID()) { - // check if the id is for a boat the player is controlling - if (ppu->spawn_id == BoatID) { - Mob* boat = entity_list.GetMob(BoatID); - if (boat == 0) { // if the boat ID is invalid, reset the id and abort - BoatID = 0; + /* If player is controlling boat */ + if (ppu->spawn_id == controlling_boat_id) { + Mob* boat = entity_list.GetMob(controlling_boat_id); + if (boat == 0) { + controlling_boat_id = 0; return; } - // set the boat's position deltas - auto boatDelta = glm::vec4(ppu->delta_x, ppu->delta_y, ppu->delta_z, ppu->delta_heading); - boat->SetDelta(boatDelta); - // send an update to everyone nearby except the client controlling the boat - auto outapp = - new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct)); + auto boat_delta = glm::vec4(ppu->delta_x, ppu->delta_y, ppu->delta_z, ppu->delta_heading); + boat->SetDelta(boat_delta); + + auto outapp = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct)); PlayerPositionUpdateServer_Struct* ppus = (PlayerPositionUpdateServer_Struct*)outapp->pBuffer; boat->MakeSpawnUpdate(ppus); entity_list.QueueCloseClients(boat, outapp, true, 300, this, false); safe_delete(outapp); - // update the boat's position on the server, without sending an update + + /* Update the boat's position on the server, without sending an update */ boat->GMMove(ppu->x_pos, ppu->y_pos, ppu->z_pos, EQ19toFloat(ppu->heading), false); return; } - else return; // if not a boat, do nothing + else return; } float dist = 0; @@ -4389,51 +4419,34 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) dist += tmp*tmp; dist = sqrt(dist); - //the purpose of this first block may not be readily apparent - //basically it's so people don't do a moderate warp every 2.5 seconds - //letting it even out and basically getting the job done without triggering - if (dist == 0) - { - if (m_DistanceSinceLastPositionCheck > 0.0) - { + /* Hack checks */ + if (dist == 0) { + if (m_DistanceSinceLastPositionCheck > 0.0) { uint32 cur_time = Timer::GetCurrentTime(); - if ((cur_time - m_TimeSinceLastPositionCheck) > 0) - { + if ((cur_time - m_TimeSinceLastPositionCheck) > 0) { float speed = (m_DistanceSinceLastPositionCheck * 100) / (float)(cur_time - m_TimeSinceLastPositionCheck); int runs = GetRunspeed(); - if (speed > (runs * RuleR(Zone, MQWarpDetectionDistanceFactor))) - { - if (!GetGMSpeed() && (runs >= GetBaseRunspeed() || (speed > (GetBaseRunspeed() * RuleR(Zone, MQWarpDetectionDistanceFactor))))) - { - if (IsShadowStepExempted()) - { - if (m_DistanceSinceLastPositionCheck > 800) - { + if (speed > (runs * RuleR(Zone, MQWarpDetectionDistanceFactor))) { + if (!GetGMSpeed() && (runs >= GetBaseRunspeed() || (speed > (GetBaseRunspeed() * RuleR(Zone, MQWarpDetectionDistanceFactor))))) { + if (IsShadowStepExempted()) { + if (m_DistanceSinceLastPositionCheck > 800) { CheatDetected(MQWarpShadowStep, ppu->x_pos, ppu->y_pos, ppu->z_pos); } } - else if (IsKnockBackExempted()) - { - //still potential to trigger this if you're knocked back off a - //HUGE fall that takes > 2.5 seconds - if (speed > 30.0f) - { + else if (IsKnockBackExempted()) { + if (speed > 30.0f) { CheatDetected(MQWarpKnockBack, ppu->x_pos, ppu->y_pos, ppu->z_pos); } } - else if (!IsPortExempted()) - { - if (!IsMQExemptedArea(zone->GetZoneID(), ppu->x_pos, ppu->y_pos, ppu->z_pos)) - { - if (speed > (runs * 2 * RuleR(Zone, MQWarpDetectionDistanceFactor))) - { + else if (!IsPortExempted()) { + if (!IsMQExemptedArea(zone->GetZoneID(), ppu->x_pos, ppu->y_pos, ppu->z_pos)) { + if (speed > (runs * 2 * RuleR(Zone, MQWarpDetectionDistanceFactor))) { m_TimeSinceLastPositionCheck = cur_time; m_DistanceSinceLastPositionCheck = 0.0f; CheatDetected(MQWarp, ppu->x_pos, ppu->y_pos, ppu->z_pos); //Death(this, 10000000, SPELL_UNKNOWN, _1H_BLUNT); } - else - { + else { CheatDetected(MQWarpLight, ppu->x_pos, ppu->y_pos, ppu->z_pos); } } @@ -4448,64 +4461,42 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) m_CheatDetectMoved = false; } } - else - { + else { m_TimeSinceLastPositionCheck = Timer::GetCurrentTime(); m_CheatDetectMoved = false; } } - else - { + else { m_DistanceSinceLastPositionCheck += dist; m_CheatDetectMoved = true; - if (m_TimeSinceLastPositionCheck == 0) - { + if (m_TimeSinceLastPositionCheck == 0) { m_TimeSinceLastPositionCheck = Timer::GetCurrentTime(); } - else - { + else { uint32 cur_time = Timer::GetCurrentTime(); - if ((cur_time - m_TimeSinceLastPositionCheck) > 2500) - { + if ((cur_time - m_TimeSinceLastPositionCheck) > 2500) { float speed = (m_DistanceSinceLastPositionCheck * 100) / (float)(cur_time - m_TimeSinceLastPositionCheck); int runs = GetRunspeed(); - if (speed > (runs * RuleR(Zone, MQWarpDetectionDistanceFactor))) - { - if (!GetGMSpeed() && (runs >= GetBaseRunspeed() || (speed > (GetBaseRunspeed() * RuleR(Zone, MQWarpDetectionDistanceFactor))))) - { - if (IsShadowStepExempted()) - { - if (m_DistanceSinceLastPositionCheck > 800) - { - //if(!IsMQExemptedArea(zone->GetZoneID(), ppu->x_pos, ppu->y_pos, ppu->z_pos)) - //{ + if (speed > (runs * RuleR(Zone, MQWarpDetectionDistanceFactor))) { + if (!GetGMSpeed() && (runs >= GetBaseRunspeed() || (speed > (GetBaseRunspeed() * RuleR(Zone, MQWarpDetectionDistanceFactor))))) { + if (IsShadowStepExempted()) { + if (m_DistanceSinceLastPositionCheck > 800) { CheatDetected(MQWarpShadowStep, ppu->x_pos, ppu->y_pos, ppu->z_pos); - //Death(this, 10000000, SPELL_UNKNOWN, _1H_BLUNT); - //} } } - else if (IsKnockBackExempted()) - { - //still potential to trigger this if you're knocked back off a - //HUGE fall that takes > 2.5 seconds - if (speed > 30.0f) - { + else if (IsKnockBackExempted()) { + if (speed > 30.0f) { CheatDetected(MQWarpKnockBack, ppu->x_pos, ppu->y_pos, ppu->z_pos); } } - else if (!IsPortExempted()) - { - if (!IsMQExemptedArea(zone->GetZoneID(), ppu->x_pos, ppu->y_pos, ppu->z_pos)) - { - if (speed > (runs * 2 * RuleR(Zone, MQWarpDetectionDistanceFactor))) - { + else if (!IsPortExempted()) { + if (!IsMQExemptedArea(zone->GetZoneID(), ppu->x_pos, ppu->y_pos, ppu->z_pos)) { + if (speed > (runs * 2 * RuleR(Zone, MQWarpDetectionDistanceFactor))) { m_TimeSinceLastPositionCheck = cur_time; m_DistanceSinceLastPositionCheck = 0.0f; CheatDetected(MQWarp, ppu->x_pos, ppu->y_pos, ppu->z_pos); - //Death(this, 10000000, SPELL_UNKNOWN, _1H_BLUNT); } - else - { + else { CheatDetected(MQWarpLight, ppu->x_pos, ppu->y_pos, ppu->z_pos); } } @@ -4524,7 +4515,7 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) DragCorpses(); } - //Check to see if PPU should trigger an update to the rewind position. + /* Check to see if PPU should trigger an update to the rewind position. */ float rewind_x_diff = 0; float rewind_y_diff = 0; @@ -4533,14 +4524,19 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) rewind_y_diff = ppu->y_pos - m_RewindLocation.y; rewind_y_diff *= rewind_y_diff; - //We only need to store updated values if the player has moved. - //If the player has moved more than units for x or y, then we'll store - //his pre-PPU x and y for /rewind, in case he gets stuck. + /* + We only need to store updated values if the player has moved. + If the player has moved more than units for x or y, then we'll store + his pre-PPU x and y for /rewind, in case he gets stuck. + */ + if ((rewind_x_diff > 750) || (rewind_y_diff > 750)) m_RewindLocation = glm::vec3(m_Position); - //If the PPU was a large jump, such as a cross zone gate or Call of Hero, - //just update rewind coords to the new ppu coords. This will prevent exploitation. + /* + If the PPU was a large jump, such as a cross zone gate or Call of Hero, + just update rewind coordinates to the new ppu coordinates. This will prevent exploitation. + */ if ((rewind_x_diff > 5000) || (rewind_y_diff > 5000)) m_RewindLocation = glm::vec3(ppu->x_pos, ppu->y_pos, ppu->z_pos); @@ -4553,7 +4549,7 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) m_Proximity = glm::vec3(ppu->x_pos, ppu->y_pos, ppu->z_pos); } - // Update internal state + /* Update internal state */ m_Delta = glm::vec4(ppu->delta_x, ppu->delta_y, ppu->delta_z, ppu->delta_heading); if (IsTracking() && ((m_Position.x != ppu->x_pos) || (m_Position.y != ppu->y_pos))) { @@ -4561,7 +4557,7 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) CheckIncreaseSkill(EQEmu::skills::SkillTracking, nullptr, -20); } - // Break Hide if moving without sneaking and set rewind timer if moved + /* Break Hide if moving without sneaking and set rewind timer if moved */ if (ppu->y_pos != m_Position.y || ppu->x_pos != m_Position.x) { if ((hidden || improved_hidden) && !sneaking) { hidden = false; @@ -4606,36 +4602,41 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) client_scan_npc_aggro_timer.Start(3000); } } + + float new_heading = EQ19toFloat(ppu->heading); + int32 new_animation = ppu->animation; - // Outgoing client packet - float tmpheading = EQ19toFloat(ppu->heading); - /* The clients send an update at best every 1.3 seconds - * We want to avoid reflecting these updates to other clients as much as possible - * The client also sends an update every 280 ms while turning, if we prevent - * sending these by checking if the location is the same too aggressively, clients end up spinning - * so keep a count of how many packets are the same within a tolerance and stop when we get there */ + /* Update internal server position from what the client has sent */ + m_Position.x = ppu->x_pos; + m_Position.y = ppu->y_pos; + m_Position.z = ppu->z_pos; + + /* Visual Debugging */ + if (RuleB(Character, OPClientUpdateVisualDebug)) { + Log(Logs::General, Logs::Debug, "ClientUpdate: ppu x: %f y: %f z: %f h: %u", ppu->x_pos, ppu->y_pos, ppu->z_pos, ppu->heading); + this->SendAppearanceEffect(78, 0, 0, 0, 0); + this->SendAppearanceEffect(41, 0, 0, 0, 0); + } - bool pos_same = FCMP(ppu->y_pos, m_Position.y) && FCMP(ppu->x_pos, m_Position.x) && FCMP(tmpheading, m_Position.w) && ppu->animation == animation; - if (!pos_same || (pos_same && position_update_same_count < 6)) - { - if (pos_same) - position_update_same_count++; - else - position_update_same_count = 0; + /* Only feed real time updates when client is moving */ + if (is_client_moving || new_heading != m_Position.w || new_animation != animation) { - m_Position.x = ppu->x_pos; - m_Position.y = ppu->y_pos; - m_Position.z = ppu->z_pos; - m_Position.w = tmpheading; animation = ppu->animation; + m_Position.w = EQ19toFloat(ppu->heading); + /* Broadcast update to other clients */ auto outapp = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct)); - PlayerPositionUpdateServer_Struct* ppu = (PlayerPositionUpdateServer_Struct*)outapp->pBuffer; - MakeSpawnUpdate(ppu); - if (gmhideme) + PlayerPositionUpdateServer_Struct* position_update = (PlayerPositionUpdateServer_Struct*)outapp->pBuffer; + + MakeSpawnUpdate(position_update); + + if (gm_hide_me) { entity_list.QueueClientsStatus(this, outapp, true, Admin(), 250); - else - entity_list.QueueCloseClients(this, outapp, true, 300, nullptr, false); + } + else { + entity_list.QueueCloseClients(this, outapp, true, 300, nullptr, true); + } + safe_delete(outapp); } @@ -4648,27 +4649,6 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) return; } -/* -void Client::Handle_OP_CloseContainer(const EQApplicationPacket *app) -{ -if (app->size != sizeof(CloseContainer_Struct)) { -LogFile->write(EQEMuLog::Error, "Invalid size on CloseContainer_Struct: Expected %i, Got %i", -sizeof(CloseContainer_Struct), app->size); -return; -} - -SetTradeskillObject(nullptr); - -ClickObjectAck_Struct* oos = (ClickObjectAck_Struct*)app->pBuffer; -Entity* entity = entity_list.GetEntityObject(oos->drop_id); -if (entity && entity->IsObject()) { -Object* object = entity->CastToObject(); -object->Close(); -} -return; -} -*/ - void Client::Handle_OP_CombatAbility(const EQApplicationPacket *app) { if (app->size != sizeof(CombatAbility_Struct)) { @@ -4802,6 +4782,7 @@ void Client::Handle_OP_Consider(const EQApplicationPacket *app) mod_consider(tmob, con); QueuePacket(outapp); + safe_delete(outapp); // only wanted to check raid target once // and need con to still be around so, do it here! if (tmob->IsRaidTarget()) { @@ -4839,7 +4820,15 @@ void Client::Handle_OP_Consider(const EQApplicationPacket *app) SendColoredText(color, std::string("This creature would take an army to defeat!")); } - safe_delete(outapp); + + // this could be done better, but this is only called when you con so w/e + // Shroud of Stealth has a special message + if (improved_hidden && (!tmob->see_improved_hide && (tmob->see_invis || tmob->see_hide))) + Message_StringID(10, SOS_KEEPS_HIDDEN); + // we are trying to hide but they can see us + else if ((invisible || invisible_undead || hidden || invisible_animals) && !IsInvisible(tmob)) + Message_StringID(10, SUSPECT_SEES_YOU); + return; } @@ -8571,13 +8560,17 @@ void Client::Handle_OP_ItemVerifyRequest(const EQApplicationPacket *app) (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; } + // 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)) + target_id = GetPetID(); + Log(Logs::General, Logs::None, "OP ItemVerifyRequest: spell=%i, target=%i, inv=%i", spell_id, target_id, slot_id); if (m_inv.SupportsClickCasting(slot_id) || ((item->ItemType == EQEmu::item::ItemTypePotion || item->PotionBelt) && m_inv.SupportsPotionBeltCasting(slot_id))) // sanity check @@ -8906,12 +8899,12 @@ void Client::Handle_OP_LeaveAdventure(const EQApplicationPacket *app) void Client::Handle_OP_LeaveBoat(const EQApplicationPacket *app) { - Mob* boat = entity_list.GetMob(this->BoatID); // find the mob corresponding to the boat id + Mob* boat = entity_list.GetMob(this->controlling_boat_id); // find the mob corresponding to the boat id if (boat) { if ((boat->GetTarget() == this) && boat->GetHateAmount(this) == 0) // if the client somehow left while still controlling the boat (and the boat isn't attacking them) boat->SetTarget(0); // fix it to stop later problems } - this->BoatID = 0; + this->controlling_boat_id = 0; return; } @@ -9939,7 +9932,7 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) Mob *Owner = mypet->GetOwner(); if (Owner) mypet->Say_StringID(PET_LEADERIS, Owner->GetCleanName()); - else + else if (mypet->IsNPC()) mypet->Say_StringID(I_FOLLOW_NOONE); } } @@ -9950,9 +9943,6 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) if (mypet->GetPetType() == petTargetLock && (pet->command != PET_HEALTHREPORT && pet->command != PET_GETLOST)) return; - if (mypet->GetPetType() == petAnimation && (pet->command != PET_HEALTHREPORT && pet->command != PET_GETLOST) && !GetAA(aaAnimationEmpathy)) - return; - // just let the command "/pet get lost" work for familiars if (mypet->GetPetType() == petFamiliar && pet->command != PET_GETLOST) return; @@ -9982,25 +9972,31 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) break; //prevent pet from attacking stuff while feared if (!mypet->IsAttackAllowed(target)) { - mypet->Say_StringID(NOT_LEGAL_TARGET); + mypet->SayTo_StringID(this, NOT_LEGAL_TARGET); break; } - if ((mypet->GetPetType() == petAnimation && GetAA(aaAnimationEmpathy) >= 2) || mypet->GetPetType() != petAnimation) { + if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { if (target != this && DistanceSquaredNoZ(mypet->GetPosition(), target->GetPosition()) <= (RuleR(Pets, AttackCommandRange)*RuleR(Pets, AttackCommandRange))) { - if (mypet->IsHeld()) { - if (!mypet->IsFocused()) { - mypet->SetHeld(false); //break the hold and guard if we explicitly tell the pet to attack. - if (mypet->GetPetOrder() != SPO_Guard) - mypet->SetPetOrder(SPO_Follow); - } - else { - mypet->SetTarget(target); - } + if (mypet->IsPetStop()) { + mypet->SetPetStop(false); + SetPetCommandState(PET_BUTTON_STOP, 0); + } + if (mypet->IsPetRegroup()) { + mypet->SetPetRegroup(false); + SetPetCommandState(PET_BUTTON_REGROUP, 0); } zone->AddAggroMob(); - mypet->AddToHateList(target, 1); + // classic acts like qattack + int hate = 1; + if (mypet->IsEngaged()) { + auto top = mypet->GetHateMost(); + if (top && top != target) + hate += mypet->GetHateAmount(top) - mypet->GetHateAmount(target) + 100; // should be enough to cause target change + } + mypet->AddToHateList(target, hate, 0, true, false, false, SPELL_UNKNOWN, true); Message_StringID(MT_PetResponse, PET_ATTACKING, mypet->GetCleanName(), target->GetCleanName()); + SetTarget(target); } } break; @@ -10017,14 +10013,22 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) } if (!mypet->IsAttackAllowed(GetTarget())) { - mypet->Say_StringID(NOT_LEGAL_TARGET); + mypet->SayTo_StringID(this, NOT_LEGAL_TARGET); break; } - if ((mypet->GetPetType() == petAnimation && GetAA(aaAnimationEmpathy) >= 2) || mypet->GetPetType() != petAnimation) { + if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { if (GetTarget() != this && DistanceSquaredNoZ(mypet->GetPosition(), GetTarget()->GetPosition()) <= (RuleR(Pets, AttackCommandRange)*RuleR(Pets, AttackCommandRange))) { + if (mypet->IsPetStop()) { + mypet->SetPetStop(false); + SetPetCommandState(PET_BUTTON_STOP, 0); + } + if (mypet->IsPetRegroup()) { + mypet->SetPetRegroup(false); + SetPetCommandState(PET_BUTTON_REGROUP, 0); + } zone->AddAggroMob(); - mypet->AddToHateList(GetTarget(), 1); + mypet->AddToHateList(GetTarget(), 1, 0, true, false, false, SPELL_UNKNOWN, true); Message_StringID(MT_PetResponse, PET_ATTACKING, mypet->GetCleanName(), GetTarget()->GetCleanName()); } } @@ -10033,17 +10037,22 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) case PET_BACKOFF: { if (mypet->IsFeared()) break; //keeps pet running while feared - if ((mypet->GetPetType() == petAnimation && GetAA(aaAnimationEmpathy) >= 3) || mypet->GetPetType() != petAnimation) { - mypet->Say_StringID(MT_PetResponse, PET_CALMING); + if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { + mypet->SayTo_StringID(this, MT_PetResponse, PET_CALMING); mypet->WipeHateList(); mypet->SetTarget(nullptr); + if (mypet->IsPetStop()) { + mypet->SetPetStop(false); + SetPetCommandState(PET_BUTTON_STOP, 0); + } } break; } case PET_HEALTHREPORT: { - Message_StringID(MT_PetResponse, PET_REPORT_HP, ConvertArrayF(mypet->GetHPRatio(), val1)); - mypet->ShowBuffList(this); - //Message(10,"%s tells you, 'I have %d percent of my hit points left.'",mypet->GetName(),(uint8)mypet->GetHPRatio()); + if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { + Message_StringID(MT_PetResponse, PET_REPORT_HP, ConvertArrayF(mypet->GetHPRatio(), val1)); + mypet->ShowBuffList(this); + } break; } case PET_GETLOST: { @@ -10060,7 +10069,7 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) SetPet(nullptr); } - mypet->Say_StringID(MT_PetResponse, PET_GETLOST_STRING); + mypet->SayTo_StringID(this, MT_PetResponse, PET_GETLOST_STRING); mypet->CastToNPC()->Depop(); //Oddly, the client (Titanium) will still allow "/pet get lost" command despite me adding the code below. If someone can figure that out, you can uncomment this code and use it. @@ -10076,12 +10085,17 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) case PET_GUARDHERE: { if (mypet->IsFeared()) break; //could be exploited like PET_BACKOFF - if ((mypet->GetPetType() == petAnimation && GetAA(aaAnimationEmpathy) >= 1) || mypet->GetPetType() != petAnimation) { + if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { if (mypet->IsNPC()) { - mypet->SetHeld(false); - mypet->Say_StringID(MT_PetResponse, PET_GUARDINGLIFE); + mypet->SayTo_StringID(this, MT_PetResponse, PET_GUARDINGLIFE); mypet->SetPetOrder(SPO_Guard); mypet->CastToNPC()->SaveGuardSpot(); + if (!mypet->GetTarget()) // want them to not twitch if they're chasing something down + mypet->SetCurrentSpeed(0); + if (mypet->IsPetStop()) { + mypet->SetPetStop(false); + SetPetCommandState(PET_BUTTON_STOP, 0); + } } } break; @@ -10089,16 +10103,19 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) case PET_FOLLOWME: { if (mypet->IsFeared()) break; //could be exploited like PET_BACKOFF - if ((mypet->GetPetType() == petAnimation && GetAA(aaAnimationEmpathy) >= 1) || mypet->GetPetType() != petAnimation) { - mypet->SetHeld(false); - mypet->Say_StringID(MT_PetResponse, PET_FOLLOWING); + if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { + mypet->SayTo_StringID(this, MT_PetResponse, PET_FOLLOWING); mypet->SetPetOrder(SPO_Follow); mypet->SendAppearancePacket(AT_Anim, ANIM_STAND); + if (mypet->IsPetStop()) { + mypet->SetPetStop(false); + SetPetCommandState(PET_BUTTON_STOP, 0); + } } break; } case PET_TAUNT: { - if ((mypet->GetPetType() == petAnimation && GetAA(aaAnimationEmpathy) >= 3) || mypet->GetPetType() != petAnimation) { + if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { if (mypet->CastToNPC()->IsTaunting()) { Message_StringID(MT_PetResponse, PET_NO_TAUNT); @@ -10113,14 +10130,14 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) break; } case PET_TAUNT_ON: { - if ((mypet->GetPetType() == petAnimation && GetAA(aaAnimationEmpathy) >= 3) || mypet->GetPetType() != petAnimation) { + if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { Message_StringID(MT_PetResponse, PET_DO_TAUNT); mypet->CastToNPC()->SetTaunting(true); } break; } case PET_TAUNT_OFF: { - if ((mypet->GetPetType() == petAnimation && GetAA(aaAnimationEmpathy) >= 3) || mypet->GetPetType() != petAnimation) { + if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { Message_StringID(MT_PetResponse, PET_NO_TAUNT); mypet->CastToNPC()->SetTaunting(false); } @@ -10129,27 +10146,30 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) case PET_GUARDME: { if (mypet->IsFeared()) break; //could be exploited like PET_BACKOFF - if ((mypet->GetPetType() == petAnimation && GetAA(aaAnimationEmpathy) >= 1) || mypet->GetPetType() != petAnimation) { - mypet->SetHeld(false); - mypet->Say_StringID(MT_PetResponse, PET_GUARDME_STRING); + if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { + mypet->SayTo_StringID(this, MT_PetResponse, PET_GUARDME_STRING); mypet->SetPetOrder(SPO_Follow); mypet->SendAppearancePacket(AT_Anim, ANIM_STAND); + if (mypet->IsPetStop()) { + mypet->SetPetStop(false); + SetPetCommandState(PET_BUTTON_STOP, 0); + } } break; } case PET_SIT: { if (mypet->IsFeared()) break; //could be exploited like PET_BACKOFF - if ((mypet->GetPetType() == petAnimation && GetAA(aaAnimationEmpathy) >= 3) || mypet->GetPetType() != petAnimation) { + if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { if (mypet->GetPetOrder() == SPO_Sit) { - mypet->Say_StringID(MT_PetResponse, PET_SIT_STRING); + mypet->SayTo_StringID(this, MT_PetResponse, PET_SIT_STRING); mypet->SetPetOrder(SPO_Follow); mypet->SendAppearancePacket(AT_Anim, ANIM_STAND); } else { - mypet->Say_StringID(MT_PetResponse, PET_SIT_STRING); + mypet->SayTo_StringID(this, MT_PetResponse, PET_SIT_STRING); mypet->SetPetOrder(SPO_Sit); mypet->SetRunAnimSpeed(0); if (!mypet->UseBardSpellLogic()) //maybe we can have a bard pet @@ -10162,8 +10182,8 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) case PET_STANDUP: { if (mypet->IsFeared()) break; //could be exploited like PET_BACKOFF - if ((mypet->GetPetType() == petAnimation && GetAA(aaAnimationEmpathy) >= 3) || mypet->GetPetType() != petAnimation) { - mypet->Say_StringID(MT_PetResponse, PET_SIT_STRING); + if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { + mypet->SayTo_StringID(this, MT_PetResponse, PET_SIT_STRING); mypet->SetPetOrder(SPO_Follow); mypet->SendAppearancePacket(AT_Anim, ANIM_STAND); } @@ -10172,8 +10192,8 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) case PET_SITDOWN: { if (mypet->IsFeared()) break; //could be exploited like PET_BACKOFF - if ((mypet->GetPetType() == petAnimation && GetAA(aaAnimationEmpathy) >= 3) || mypet->GetPetType() != petAnimation) { - mypet->Say_StringID(MT_PetResponse, PET_SIT_STRING); + if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { + mypet->SayTo_StringID(this, MT_PetResponse, PET_SIT_STRING); mypet->SetPetOrder(SPO_Sit); mypet->SetRunAnimSpeed(0); if (!mypet->UseBardSpellLogic()) //maybe we can have a bard pet @@ -10182,152 +10202,274 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) } break; } - case PET_SLUMBER: { - if (mypet->IsFeared()) break; //could be exploited like PET_BACKOFF - - if (mypet->GetPetType() != petAnimation) { - // Needs to have an IsSleeping() check added and this case should toggle on/off - mypet->Say_StringID(MT_PetResponse, PET_SIT_STRING); - mypet->SetPetOrder(SPO_Sit); - mypet->SetRunAnimSpeed(0); - if (!mypet->UseBardSpellLogic()) //maybe we can have a bard pet - mypet->InterruptSpell(); //No cast 4 u. //i guess the pet should start casting - mypet->SendAppearancePacket(AT_Anim, ANIM_DEATH); - } - break; - } - case PET_SLUMBER_ON: { - if (mypet->IsFeared()) break; //could be exploited like PET_BACKOFF - - if (mypet->GetPetType() != petAnimation) { - mypet->Say_StringID(MT_PetResponse, PET_SIT_STRING); - mypet->SetPetOrder(SPO_Sit); - mypet->SetRunAnimSpeed(0); - if (!mypet->UseBardSpellLogic()) //maybe we can have a bard pet - mypet->InterruptSpell(); //No cast 4 u. //i guess the pet should start casting - mypet->SendAppearancePacket(AT_Anim, ANIM_DEATH); - } - break; - } - case PET_SLUMBER_OFF: { - if (mypet->IsFeared()) break; //could be exploited like PET_BACKOFF - - if (mypet->GetPetType() != petAnimation) { - mypet->Say_StringID(MT_PetResponse, PET_SIT_STRING); - mypet->SetPetOrder(SPO_Follow); - mypet->SetRunAnimSpeed(mypet->GetBaseRunspeed()); - mypet->SendAppearancePacket(AT_Anim, ANIM_STAND); - } - break; - } case PET_HOLD: { - if (GetAA(aaPetDiscipline) && mypet->IsNPC()) { - if (mypet->IsFeared()) - break; //could be exploited like PET_BACKOFF - + if (aabonuses.PetCommands[PetCommand] && mypet->IsNPC()) { if (mypet->IsHeld()) { + if (m_ClientVersionBit & EQEmu::versions::bit_SoDAndLater) + Message_StringID(MT_PetResponse, PET_HOLD_SET_OFF); mypet->SetHeld(false); } else { - mypet->Say_StringID(MT_PetResponse, PET_ON_HOLD); - mypet->WipeHateList(); + if (m_ClientVersionBit & EQEmu::versions::bit_SoDAndLater) + Message_StringID(MT_PetResponse, PET_HOLD_SET_ON); + + if (m_ClientVersionBit & EQEmu::versions::bit_UFAndLater) + mypet->SayTo_StringID(this, MT_PetResponse, PET_NOW_HOLDING); + else + mypet->SayTo_StringID(this, MT_PetResponse, PET_ON_HOLD); + mypet->SetHeld(true); } + mypet->SetGHeld(false); + SetPetCommandState(PET_BUTTON_GHOLD, 0); } break; } case PET_HOLD_ON: { - if (GetAA(aaPetDiscipline) && mypet->IsNPC() && !mypet->IsHeld()) { - if (mypet->IsFeared()) - break; //could be exploited like PET_BACKOFF + if (aabonuses.PetCommands[PetCommand] && mypet->IsNPC() && !mypet->IsHeld()) { + if (m_ClientVersionBit & EQEmu::versions::bit_SoDAndLater) + Message_StringID(MT_PetResponse, PET_HOLD_SET_ON); - mypet->Say_StringID(MT_PetResponse, PET_ON_HOLD); - mypet->WipeHateList(); + if (m_ClientVersionBit & EQEmu::versions::bit_UFAndLater) + mypet->SayTo_StringID(this, MT_PetResponse, PET_NOW_HOLDING); + else + mypet->SayTo_StringID(this, MT_PetResponse, PET_ON_HOLD); mypet->SetHeld(true); + mypet->SetGHeld(false); + SetPetCommandState(PET_BUTTON_GHOLD, 0); } break; } case PET_HOLD_OFF: { - if (GetAA(aaPetDiscipline) && mypet->IsNPC() && mypet->IsHeld()) + if (aabonuses.PetCommands[PetCommand] && mypet->IsNPC() && mypet->IsHeld()) { + if (m_ClientVersionBit & EQEmu::versions::bit_SoDAndLater) + Message_StringID(MT_PetResponse, PET_HOLD_SET_OFF); mypet->SetHeld(false); + } + break; + } + case PET_GHOLD: { + if (aabonuses.PetCommands[PetCommand] && mypet->IsNPC()) { + if (mypet->IsGHeld()) + { + if (m_ClientVersionBit & EQEmu::versions::bit_UFAndLater) + Message_StringID(MT_PetResponse, PET_OFF_GHOLD); + mypet->SetGHeld(false); + } + else + { + if (m_ClientVersionBit & EQEmu::versions::bit_UFAndLater) { + Message_StringID(MT_PetResponse, PET_ON_GHOLD); + mypet->SayTo_StringID(this, MT_PetResponse, PET_GHOLD_ON_MSG); + } else { + mypet->SayTo_StringID(this, MT_PetResponse, PET_ON_HOLD); + } + mypet->SetGHeld(true); + } + mypet->SetHeld(false); + SetPetCommandState(PET_BUTTON_HOLD, 0); + } + break; + } + case PET_GHOLD_ON: { + if (aabonuses.PetCommands[PetCommand] && mypet->IsNPC()) { + if (m_ClientVersionBit & EQEmu::versions::bit_UFAndLater) { + Message_StringID(MT_PetResponse, PET_ON_GHOLD); + mypet->SayTo_StringID(this, MT_PetResponse, PET_GHOLD_ON_MSG); + } else { + mypet->SayTo_StringID(this, MT_PetResponse, PET_ON_HOLD); + } + mypet->SetGHeld(true); + mypet->SetHeld(false); + SetPetCommandState(PET_BUTTON_HOLD, 0); + } + break; + } + case PET_GHOLD_OFF: { + if (aabonuses.PetCommands[PetCommand] && mypet->IsNPC() && mypet->IsGHeld()) { + if (m_ClientVersionBit & EQEmu::versions::bit_UFAndLater) + Message_StringID(MT_PetResponse, PET_OFF_GHOLD); + mypet->SetGHeld(false); + } break; } case PET_SPELLHOLD: { - if (GetAA(aaAdvancedPetDiscipline) == 2 && mypet->IsNPC()) { + if (aabonuses.PetCommands[PetCommand] && mypet->IsNPC()) { if (mypet->IsFeared()) break; if (mypet->IsNoCast()) { Message_StringID(MT_PetResponse, PET_CASTING); - mypet->CastToNPC()->SetNoCast(false); + if (m_ClientVersionBit & EQEmu::versions::bit_SoDAndLater) + Message_StringID(MT_PetResponse, PET_SPELLHOLD_SET_OFF); + mypet->SetNoCast(false); } else { Message_StringID(MT_PetResponse, PET_NOT_CASTING); - mypet->CastToNPC()->SetNoCast(true); + if (m_ClientVersionBit & EQEmu::versions::bit_SoDAndLater) + Message_StringID(MT_PetResponse, PET_SPELLHOLD_SET_ON); + mypet->SetNoCast(true); } } break; } case PET_SPELLHOLD_ON: { - if (GetAA(aaAdvancedPetDiscipline) == 2 && mypet->IsNPC()) { + if (aabonuses.PetCommands[PetCommand] && mypet->IsNPC()) { if (mypet->IsFeared()) break; if (!mypet->IsNoCast()) { Message_StringID(MT_PetResponse, PET_NOT_CASTING); - mypet->CastToNPC()->SetNoCast(true); + if (m_ClientVersionBit & EQEmu::versions::bit_SoDAndLater) + Message_StringID(MT_PetResponse, PET_SPELLHOLD_SET_ON); + mypet->SetNoCast(true); } } break; } case PET_SPELLHOLD_OFF: { - if (GetAA(aaAdvancedPetDiscipline) == 2 && mypet->IsNPC()) { + if (aabonuses.PetCommands[PetCommand] && mypet->IsNPC()) { if (mypet->IsFeared()) break; if (mypet->IsNoCast()) { Message_StringID(MT_PetResponse, PET_CASTING); - mypet->CastToNPC()->SetNoCast(false); + if (m_ClientVersionBit & EQEmu::versions::bit_SoDAndLater) + Message_StringID(MT_PetResponse, PET_SPELLHOLD_SET_OFF); + mypet->SetNoCast(false); } } break; } case PET_FOCUS: { - if (GetAA(aaAdvancedPetDiscipline) >= 1 && mypet->IsNPC()) { + if (aabonuses.PetCommands[PetCommand] && mypet->IsNPC()) { if (mypet->IsFeared()) break; if (mypet->IsFocused()) { Message_StringID(MT_PetResponse, PET_NOT_FOCUSING); - mypet->CastToNPC()->SetFocused(false); + if (m_ClientVersionBit & EQEmu::versions::bit_SoDAndLater) + Message_StringID(MT_PetResponse, PET_FOCUS_SET_OFF); + mypet->SetFocused(false); } else { Message_StringID(MT_PetResponse, PET_NOW_FOCUSING); - mypet->CastToNPC()->SetFocused(true); + if (m_ClientVersionBit & EQEmu::versions::bit_SoDAndLater) + Message_StringID(MT_PetResponse, PET_FOCUS_SET_ON); + mypet->SetFocused(true); } } break; } case PET_FOCUS_ON: { - if (GetAA(aaAdvancedPetDiscipline) >= 1 && mypet->IsNPC()) { + if (aabonuses.PetCommands[PetCommand] && mypet->IsNPC()) { if (mypet->IsFeared()) break; if (!mypet->IsFocused()) { Message_StringID(MT_PetResponse, PET_NOW_FOCUSING); - mypet->CastToNPC()->SetFocused(true); + if (m_ClientVersionBit & EQEmu::versions::bit_SoDAndLater) + Message_StringID(MT_PetResponse, PET_FOCUS_SET_ON); + mypet->SetFocused(true); } } break; } case PET_FOCUS_OFF: { - if (GetAA(aaAdvancedPetDiscipline) >= 1 && mypet->IsNPC()) { + if (aabonuses.PetCommands[PetCommand] && mypet->IsNPC()) { if (mypet->IsFeared()) break; if (mypet->IsFocused()) { Message_StringID(MT_PetResponse, PET_NOT_FOCUSING); - mypet->CastToNPC()->SetFocused(false); + if (m_ClientVersionBit & EQEmu::versions::bit_SoDAndLater) + Message_StringID(MT_PetResponse, PET_FOCUS_SET_OFF); + mypet->SetFocused(false); } } break; } + case PET_STOP: { + if (mypet->IsFeared()) break; //could be exploited like PET_BACKOFF + + if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { + if (mypet->IsPetStop()) { + mypet->SetPetStop(false); + } else { + mypet->SetPetStop(true); + mypet->SetCurrentSpeed(0); + mypet->SetTarget(nullptr); + if (mypet->IsPetRegroup()) { + mypet->SetPetRegroup(false); + SetPetCommandState(PET_BUTTON_REGROUP, 0); + } + } + mypet->SayTo_StringID(this, MT_PetResponse, PET_GETLOST_STRING); + } + break; + } + case PET_STOP_ON: { + if (mypet->IsFeared()) break; //could be exploited like PET_BACKOFF + + if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { + mypet->SetPetStop(true); + mypet->SetCurrentSpeed(0); + mypet->SetTarget(nullptr); + mypet->SayTo_StringID(this, MT_PetResponse, PET_GETLOST_STRING); + if (mypet->IsPetRegroup()) { + mypet->SetPetRegroup(false); + SetPetCommandState(PET_BUTTON_REGROUP, 0); + } + } + break; + } + case PET_STOP_OFF: { + if (mypet->IsFeared()) break; //could be exploited like PET_BACKOFF + + if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { + mypet->SetPetStop(false); + mypet->SayTo_StringID(this, MT_PetResponse, PET_GETLOST_STRING); + } + break; + } + case PET_REGROUP: { + if (mypet->IsFeared()) break; //could be exploited like PET_BACKOFF + + if (aabonuses.PetCommands[PetCommand]) { + if (mypet->IsPetRegroup()) { + mypet->SetPetRegroup(false); + mypet->SayTo_StringID(this, MT_PetResponse, PET_OFF_REGROUPING); + } else { + mypet->SetPetRegroup(true); + mypet->SetTarget(nullptr); + mypet->SayTo_StringID(this, MT_PetResponse, PET_ON_REGROUPING); + if (mypet->IsPetStop()) { + mypet->SetPetStop(false); + SetPetCommandState(PET_BUTTON_STOP, 0); + } + } + } + break; + } + case PET_REGROUP_ON: { + if (mypet->IsFeared()) break; //could be exploited like PET_BACKOFF + + if (aabonuses.PetCommands[PetCommand]) { + mypet->SetPetRegroup(true); + mypet->SetTarget(nullptr); + mypet->SayTo_StringID(this, MT_PetResponse, PET_ON_REGROUPING); + if (mypet->IsPetStop()) { + mypet->SetPetStop(false); + SetPetCommandState(PET_BUTTON_STOP, 0); + } + } + break; + } + case PET_REGROUP_OFF: { + if (mypet->IsFeared()) break; //could be exploited like PET_BACKOFF + + if (aabonuses.PetCommands[PetCommand]) { + mypet->SetPetRegroup(false); + mypet->SayTo_StringID(this, MT_PetResponse, PET_OFF_REGROUPING); + } + break; + } default: printf("Client attempted to use a unknown pet command:\n"); break; @@ -11654,6 +11796,28 @@ void Client::Handle_OP_RemoveBlockedBuffs(const EQApplicationPacket *app) } } +void Client::Handle_OP_RemoveTrap(const EQApplicationPacket *app) +{ + if (app->size != 4) {// just an int + Log(Logs::General, Logs::None, "Size mismatch in OP_RemoveTrap expected 4 got %i", app->size); + DumpPacket(app); + return; + } + + auto id = app->ReadUInt32(0); + bool good = false; + for (int i = 0; i < trap_mgr.count; ++i) { + if (trap_mgr.auras[i].spawn_id == id) { + good = true; + break; + } + } + if (good) + RemoveAura(id); + else + Message_StringID(MT_SpellFailure, NOT_YOUR_TRAP); // pretty sure this was red +} + void Client::Handle_OP_Report(const EQApplicationPacket *app) { if (!CanUseReport) @@ -14172,6 +14336,24 @@ void Client::Handle_OP_VoiceMacroIn(const EQApplicationPacket *app) } +void Client::Handle_OP_UpdateAura(const EQApplicationPacket *app) +{ + if (app->size != sizeof(AuraDestory_Struct)) { + Log(Logs::General, Logs::None, "Size mismatch in OP_UpdateAura expected %i got %i", + sizeof(AuraDestory_Struct), app->size); + return; + } + + // client only sends this for removing + auto aura = (AuraDestory_Struct *)app->pBuffer; + if (aura->action != 1) + return; // could log I guess, but should only ever get this action + + RemoveAura(aura->entity_id); + QueuePacket(app); // if we don't resend this, the client gets confused + return; +} + void Client::Handle_OP_WearChange(const EQApplicationPacket *app) { if (app->size != sizeof(WearChange_Struct)) { diff --git a/zone/client_packet.h b/zone/client_packet.h index 76a26e04e..c63a57825 100644 --- a/zone/client_packet.h +++ b/zone/client_packet.h @@ -236,6 +236,7 @@ void Handle_OP_RecipesSearch(const EQApplicationPacket *app); void Handle_OP_ReloadUI(const EQApplicationPacket *app); void Handle_OP_RemoveBlockedBuffs(const EQApplicationPacket *app); + void Handle_OP_RemoveTrap(const EQApplicationPacket *app); void Handle_OP_Report(const EQApplicationPacket *app); void Handle_OP_RequestDuel(const EQApplicationPacket *app); void Handle_OP_RequestTitles(const EQApplicationPacket *app); @@ -288,6 +289,7 @@ void Handle_OP_TributeNPC(const EQApplicationPacket *app); void Handle_OP_TributeToggle(const EQApplicationPacket *app); void Handle_OP_TributeUpdate(const EQApplicationPacket *app); + void Handle_OP_UpdateAura(const EQApplicationPacket *app); void Handle_OP_VetClaimRequest(const EQApplicationPacket *app); void Handle_OP_VoiceMacroIn(const EQApplicationPacket *app); void Handle_OP_WearChange(const EQApplicationPacket *app); diff --git a/zone/client_process.cpp b/zone/client_process.cpp index 130c10c9d..3e08c1fba 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -63,64 +63,49 @@ extern EntityList entity_list; bool Client::Process() { bool ret = true; - if (Connected() || IsLD()) - { + if (Connected() || IsLD()) { // try to send all packets that weren't sent before - if (!IsLD() && zoneinpacket_timer.Check()) - { + if (!IsLD() && zoneinpacket_timer.Check()) { SendAllPackets(); } - if (adventure_request_timer) - { - if (adventure_request_timer->Check()) - { + if (adventure_request_timer) { + if (adventure_request_timer->Check()) { safe_delete(adventure_request_timer); } } - if (adventure_create_timer) - { - if (adventure_create_timer->Check()) - { + if (adventure_create_timer) { + if (adventure_create_timer->Check()) { safe_delete(adventure_create_timer); } } - if (adventure_leave_timer) - { - if (adventure_leave_timer->Check()) - { + if (adventure_leave_timer) { + if (adventure_leave_timer->Check()) { safe_delete(adventure_leave_timer); } } - if (adventure_door_timer) - { - if (adventure_door_timer->Check()) - { + if (adventure_door_timer) { + if (adventure_door_timer->Check()) { safe_delete(adventure_door_timer); } } - if (adventure_stats_timer) - { - if (adventure_stats_timer->Check()) - { + if (adventure_stats_timer) { + if (adventure_stats_timer->Check()) { safe_delete(adventure_stats_timer); } } - if (adventure_leaderboard_timer) - { - if (adventure_leaderboard_timer->Check()) - { + if (adventure_leaderboard_timer) { + if (adventure_leaderboard_timer->Check()) { safe_delete(adventure_leaderboard_timer); } } - if (dead) - { + if (dead) { SetHP(-100); if (RespawnFromHoverTimer.Check()) HandleRespawnFromHover(0); @@ -134,8 +119,13 @@ bool Client::Process() { 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()) { + SendPositionUpdate(); + } + if (mana_timer.Check()) - SendManaUpdatePacket(); + CheckManaEndUpdate(); if (dead && dead_timer.Check()) { database.MoveCharacterToZone(GetName(), database.GetZoneName(m_pp.binds[0].zoneId)); @@ -254,19 +244,22 @@ bool Client::Process() { /* Build a close range list of NPC's */ if (npc_close_scan_timer.Check()) { - close_npcs.clear(); + close_mobs.clear(); - auto &npc_list = entity_list.GetNPCList(); + auto &mob_list = entity_list.GetMobList(); + float scan_range = (RuleI(Range, ClientNPCScan) * RuleI(Range, ClientNPCScan)); + float client_update_range = (RuleI(Range, MobPositionUpdates) * RuleI(Range, MobPositionUpdates)); - float scan_range = RuleI(Range, ClientNPCScan); - for (auto itr = npc_list.begin(); itr != npc_list.end(); ++itr) { - NPC* npc = itr->second; - float distance = DistanceNoZ(m_Position, npc->GetPosition()); - if(distance <= scan_range) { - close_npcs.insert(std::pair(npc, distance)); - } - else if (npc->GetAggroRange() > scan_range) { - close_npcs.insert(std::pair(npc, distance)); + for (auto itr = mob_list.begin(); itr != mob_list.end(); ++itr) { + Mob* mob = itr->second; + float distance = DistanceSquared(m_Position, mob->GetPosition()); + if (mob->IsNPC()) { + if (distance <= scan_range) { + close_mobs.insert(std::pair(mob, distance)); + } + else if (mob->GetAggroRange() > scan_range) { + close_mobs.insert(std::pair(mob, distance)); + } } } } @@ -448,14 +441,14 @@ bool Client::Process() { { animation = 0; m_Delta = glm::vec4(0.0f, 0.0f, 0.0f, m_Delta.w); - SendPosUpdate(2); + SendPositionUpdate(2); } } // Send a position packet every 8 seconds - if not done, other clients // see this char disappear after 10-12 seconds of inactivity if (position_timer_counter >= 36) { // Approx. 4 ticks per second - entity_list.SendPositionUpdates(this, pLastUpdateWZ, 500, GetTarget(), true); + entity_list.SendPositionUpdates(this, pLastUpdateWZ, RuleI(Range, MobPositionUpdates), GetTarget(), true); pLastUpdate = Timer::GetCurrentTime(); pLastUpdateWZ = pLastUpdate; position_timer_counter = 0; @@ -619,11 +612,17 @@ bool Client::Process() { // only if client is not feigned if (zone->CanDoCombat() && ret && !GetFeigned() && client_scan_npc_aggro_timer.Check()) { int npc_scan_count = 0; - for (auto it = close_npcs.begin(); it != close_npcs.end(); ++it) { - NPC *npc = it->first; + for (auto it = close_mobs.begin(); it != close_mobs.end(); ++it) { + Mob *mob = it->first; - if (npc->CheckWillAggro(this) && !npc->CheckAggro(this)) { - npc->AddToHateList(this, 25); + if (!mob) + continue; + + if (mob->IsClient()) + continue; + + if (mob->CheckWillAggro(this) && !mob->CheckAggro(this)) { + mob->AddToHateList(this, 25); } npc_scan_count++; } @@ -724,6 +723,8 @@ void Client::OnDisconnect(bool hard_disconnect) { } } + RemoveAllAuras(); + Mob *Other = trade->With(); if(Other) { @@ -985,8 +986,6 @@ void Client::BulkSendMerchantInventory(int merchant_id, int npcid) { Message_StringID(10, GENERIC_STRINGID_SAY, merch->GetCleanName(), handy_id, this->GetName(), handyitem->Name); else Message_StringID(10, GENERIC_STRINGID_SAY, merch->GetCleanName(), handy_id, this->GetName()); - - merch->CastToNPC()->FaceTarget(this->CastToMob()); } // safe_delete_array(cpi); @@ -1816,7 +1815,7 @@ void Client::DoManaRegen() { return; SetMana(GetMana() + CalcManaRegen() + RestRegenMana); - SendManaUpdatePacket(); + CheckManaEndUpdate(); } diff --git a/zone/command.cpp b/zone/command.cpp index efc90e01b..cbcdf6bb3 100644 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -173,6 +173,7 @@ int command_init(void) command_add("checklos", "- Check for line of sight to your target", 50, command_checklos) || command_add("clearinvsnapshots", "[use rule] - Clear inventory snapshot history (true - elapsed entries, false - all entries)", 200, command_clearinvsnapshots) || 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) || @@ -2977,6 +2978,11 @@ void command_reloadqst(Client *c, const Seperator *sep) } +void command_corpsefix(Client *c, const Seperator *sep) +{ + entity_list.CorpseFix(c); +} + void command_reloadworld(Client *c, const Seperator *sep) { c->Message(0, "Reloading quest cache and repopping zones worldwide."); @@ -7191,7 +7197,7 @@ void command_ginfo(Client *c, const Seperator *sep) void command_hp(Client *c, const Seperator *sep) { c->SendHPUpdate(); - c->SendManaUpdatePacket(); + c->CheckManaEndUpdate(); } void command_aggro(Client *c, const Seperator *sep) @@ -8734,9 +8740,9 @@ void command_object(Client *c, const Seperator *sep) // 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 (posx BETWEEN %.1f AND %.1f) " - "AND (posy BETWEEN %.1f AND %.1f) " - "AND (posz BETWEEN %.1f AND %.1f)", + "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. diff --git a/zone/command.h b/zone/command.h index 987adf674..0a850fbca 100644 --- a/zone/command.h +++ b/zone/command.h @@ -72,6 +72,7 @@ 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_corpse(Client *c, const Seperator *sep); +void command_corpsefix(Client *c, const Seperator *sep); void command_crashtest(Client *c, const Seperator *sep); void command_cvs(Client *c, const Seperator *sep); void command_d1(Client *c, const Seperator *sep); diff --git a/zone/common.h b/zone/common.h index f1f965740..7b6382a20 100644 --- a/zone/common.h +++ b/zone/common.h @@ -56,6 +56,57 @@ //Maximum distance from a zone point if zone was specified #define ZONEPOINT_ZONE_RANGE 40000.0f +// Defines based on the RoF2 Client +#define PET_HEALTHREPORT 0 // 0x00 - /pet health or Pet Window +#define PET_LEADER 1 // 0x01 - /pet leader or Pet Window +#define PET_ATTACK 2 // 0x02 - /pet attack or Pet Window +#define PET_QATTACK 3 // 0x03 - /pet qattack or Pet Window +#define PET_FOLLOWME 4 // 0x04 - /pet follow or Pet Window +#define PET_GUARDHERE 5 // 0x05 - /pet guard or Pet Window +#define PET_SIT 6 // 0x06 - /pet sit or Pet Window +#define PET_SITDOWN 7 // 0x07 - /pet sit on +#define PET_STANDUP 8 // 0x08 - /pet sit off +#define PET_STOP 9 // 0x09 - /pet stop or Pet Window - Not implemented +#define PET_STOP_ON 10 // 0x0a - /pet stop on - Not implemented +#define PET_STOP_OFF 11 // 0x0b - /pet stop off - Not implemented +#define PET_TAUNT 12 // 0x0c - /pet taunt or Pet Window +#define PET_TAUNT_ON 13 // 0x0d - /pet taunt on +#define PET_TAUNT_OFF 14 // 0x0e - /pet taunt off +#define PET_HOLD 15 // 0x0f - /pet hold or Pet Window, won't add to hate list unless attacking +#define PET_HOLD_ON 16 // 0x10 - /pet hold on +#define PET_HOLD_OFF 17 // 0x11 - /pet hold off +#define PET_GHOLD 18 // 0x12 - /pet ghold, will never add to hate list unless told to +#define PET_GHOLD_ON 19 // 0x13 - /pet ghold on +#define PET_GHOLD_OFF 20 // 0x14 - /pet ghold off +#define PET_SPELLHOLD 21 // 0x15 - /pet no cast or /pet spellhold or Pet Window +#define PET_SPELLHOLD_ON 22 // 0x16 - /pet spellhold on +#define PET_SPELLHOLD_OFF 23 // 0x17 - /pet spellhold off +#define PET_FOCUS 24 // 0x18 - /pet focus or Pet Window +#define PET_FOCUS_ON 25 // 0x19 - /pet focus on +#define PET_FOCUS_OFF 26 // 0x1a - /pet focus off +#define PET_FEIGN 27 // 0x1b - /pet feign +#define PET_BACKOFF 28 // 0x1c - /pet back off +#define PET_GETLOST 29 // 0x1d - /pet get lost +#define PET_GUARDME 30 // 0x1e - Same as /pet follow, but different message in older clients - define not from client /pet target in modern clients but doesn't send packet +#define PET_REGROUP 31 // 0x1f - /pet regroup, acts like classic hold. Stops attack and moves back to guard/you but doesn't clear hate list +#define PET_REGROUP_ON 32 // 0x20 - /pet regroup on, turns on regroup +#define PET_REGROUP_OFF 33 // 0x21 - /pet regroup off, turns off regroup +#define PET_MAXCOMMANDS PET_REGROUP_OFF + 1 + +// can change the state of these buttons with a packet +#define PET_BUTTON_SIT 0 +#define PET_BUTTON_STOP 1 +#define PET_BUTTON_REGROUP 2 +#define PET_BUTTON_FOLLOW 3 +#define PET_BUTTON_GUARD 4 +#define PET_BUTTON_TAUNT 5 +#define PET_BUTTON_HOLD 6 +#define PET_BUTTON_GHOLD 7 +#define PET_BUTTON_FOCUS 8 +#define PET_BUTTON_SPELLHOLD 9 + +#define AURA_HARDCAP 2 + typedef enum { //focus types focusSpellHaste = 1, focusSpellDuration, @@ -156,6 +207,16 @@ typedef enum { //fear states enum { FlyMode0 = 0, FlyMode1 = 1, Flymode2 = 2, FlyMode3 = 3 }; +// This is actually FlyMode, from MQ2 +enum GravityBehavior { + Ground, + Flying, + Levitating, + Water, + Floating, // boat + LevitateWhileRunning +}; + struct TradeEntity; class Trade; enum TradeState { @@ -359,7 +420,7 @@ struct StatBonuses { uint32 SpellTriggers[MAX_SPELL_TRIGGER]; // Innate/Spell/Item Spells that trigger when you cast 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 CritDmgMob[EQEmu::skills::HIGHEST_SKILL + 2]; // All Skills + -1 + int32 CritDmgMod[EQEmu::skills::HIGHEST_SKILL + 2]; // All Skills + -1 int32 SkillReuseTime[EQEmu::skills::HIGHEST_SKILL + 1]; // Reduces skill timers int32 SkillDamageAmount[EQEmu::skills::HIGHEST_SKILL + 2]; // All Skills + -1 int32 TwoHandBluntBlock; // chance to block when wielding two hand blunt weapon @@ -485,6 +546,10 @@ struct StatBonuses { uint8 TradeSkillMastery; // Allow number of tradeskills to exceed 200 skill. int16 NoBreakAESneak; // Percent value int16 FeignedCastOnChance; // Percent Value + bool PetCommands[PET_MAXCOMMANDS]; // SPA 267 + int FeignedMinionChance; // SPA 281 base1 = chance, just like normal FD + int aura_slots; + int trap_slots; }; typedef struct diff --git a/zone/effects.cpp b/zone/effects.cpp index 2537751aa..adc7b16b2 100644 --- a/zone/effects.cpp +++ b/zone/effects.cpp @@ -356,67 +356,15 @@ int32 Client::GetActSpellCost(uint16 spell_id, int32 cost) cost -= mana_back; } - // This formula was derived from the following resource: - // http://www.eqsummoners.com/eq1/specialization-library.html - // WildcardX - float PercentManaReduction = 0; - float SpecializeSkill = GetSpecializeSkillValue(spell_id); - int SuccessChance = zone->random.Int(0, 100); - - float bonus = 1.0; - switch(GetAA(aaSpellCastingMastery)) - { - case 1: - bonus += 0.05; - break; - case 2: - bonus += 0.15; - break; - case 3: - bonus += 0.30; - break; - } - - bonus += 0.05f * GetAA(aaAdvancedSpellCastingMastery); - - if(SuccessChance <= (SpecializeSkill * 0.3 * bonus)) - { - PercentManaReduction = 1 + 0.05f * SpecializeSkill; - switch(GetAA(aaSpellCastingMastery)) - { - case 1: - PercentManaReduction += 2.5; - break; - case 2: - PercentManaReduction += 5.0; - break; - case 3: - PercentManaReduction += 10.0; - break; - } - - switch(GetAA(aaAdvancedSpellCastingMastery)) - { - case 1: - PercentManaReduction += 2.5; - break; - case 2: - PercentManaReduction += 5.0; - break; - case 3: - PercentManaReduction += 10.0; - break; - } - } + int spec = GetSpecializeSkillValue(spell_id); + int PercentManaReduction = 0; + if (spec) + PercentManaReduction = 1 + spec / 20; // there seems to be some non-obvious rounding here, let's truncate for now. int16 focus_redux = GetFocusEffect(focusManaCost, spell_id); + PercentManaReduction += focus_redux; - if(focus_redux > 0) - { - PercentManaReduction += zone->random.Real(1, (double)focus_redux); - } - - cost -= (cost * (PercentManaReduction / 100)); + cost -= cost * PercentManaReduction / 100; // Gift of Mana - reduces spell cost to 1 mana if(focus_redux >= 100) { @@ -746,7 +694,7 @@ void EntityList::AETaunt(Client* taunter, float range, int32 bonus_hate) // causes caster to hit every mob within dist range of center with // spell_id. // NPC spells will only affect other NPCs with compatible faction -void EntityList::AESpell(Mob *caster, Mob *center, uint16 spell_id, bool affect_caster, int16 resist_adjust) +void EntityList::AESpell(Mob *caster, Mob *center, uint16 spell_id, bool affect_caster, int16 resist_adjust, int *max_targets) { Mob *curmob = nullptr; @@ -755,12 +703,25 @@ void EntityList::AESpell(Mob *caster, Mob *center, uint16 spell_id, bool affect_ float min_range2 = spells[spell_id].min_range * spells[spell_id].min_range; float dist_targ = 0; + const auto &position = spells[spell_id].targettype == ST_Ring ? caster->GetTargetRingLocation() : static_cast(center->GetPosition()); + glm::vec2 min = { position.x - dist, position.y - dist }; + glm::vec2 max = { position.x + dist, position.y + dist }; + bool bad = IsDetrimentalSpell(spell_id); bool isnpc = caster->IsNPC(); - int MAX_TARGETS_ALLOWED = 4; - if (spells[spell_id].aemaxtargets) - MAX_TARGETS_ALLOWED = spells[spell_id].aemaxtargets; + if (RuleB(Spells, OldRainTargets)) + max_targets = nullptr; // ignore it! + + // if we have a passed in value, use it, otherwise default to data + // detrimental Target AEs have a default value of 4 for PCs and unlimited for NPCs + int max_targets_allowed = 0; // 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 (IsTargetableAESpell(spell_id) && bad && !isnpc) + max_targets_allowed = 4; int iCounter = 0; @@ -769,8 +730,6 @@ void EntityList::AESpell(Mob *caster, Mob *center, uint16 spell_id, bool affect_ // test to fix possible cause of random zone crashes..external methods accessing client properties before they're initialized if (curmob->IsClient() && !curmob->CastToClient()->ClientFinishedLoading()) continue; - if (curmob == center) //do not affect center - continue; if (curmob == caster && !affect_caster) //watch for caster too continue; if (spells[spell_id].targettype == ST_TargetAENoPlayersPets && curmob->IsPetOwnerClient()) @@ -784,13 +743,10 @@ void EntityList::AESpell(Mob *caster, Mob *center, uint16 spell_id, bool affect_ continue; if (spells[spell_id].pcnpc_only_flag == 2 && (curmob->IsClient() || curmob->IsMerc())) continue; + if (!IsWithinAxisAlignedBox(static_cast(curmob->GetPosition()), min, max)) + continue; - if (spells[spell_id].targettype == ST_Ring) { - dist_targ = DistanceSquared(static_cast(curmob->GetPosition()), caster->GetTargetRingLocation()); - } - else if (center) { - dist_targ = DistanceSquared(curmob->GetPosition(), center->GetPosition()); - } + dist_targ = DistanceSquared(curmob->GetPosition(), position); if (dist_targ > dist2) //make sure they are in range continue; @@ -813,7 +769,7 @@ void EntityList::AESpell(Mob *caster, Mob *center, uint16 spell_id, bool affect_ if (bad) { if (!caster->IsAttackAllowed(curmob, true)) continue; - if (center && !spells[spell_id].npc_no_los && !center->CheckLosFN(curmob)) + if (center && !spells[spell_id].npc_no_los && !center->CheckLosFN(curmob)) continue; if (!center && !spells[spell_id].npc_no_los && !caster->CheckLosFN(caster->GetTargetRingX(), caster->GetTargetRingY(), caster->GetTargetRingZ(), curmob->GetSize())) continue; @@ -828,22 +784,17 @@ void EntityList::AESpell(Mob *caster, Mob *center, uint16 spell_id, bool affect_ } curmob->CalcSpellPowerDistanceMod(spell_id, dist_targ); + caster->SpellOnTarget(spell_id, curmob, false, true, resist_adjust); - //if we get here... cast the spell. - if (IsTargetableAESpell(spell_id) && bad) { - if (iCounter < MAX_TARGETS_ALLOWED) { - caster->SpellOnTarget(spell_id, curmob, false, true, resist_adjust); - } - } else { - if (spells[spell_id].aemaxtargets && iCounter < spells[spell_id].aemaxtargets) - caster->SpellOnTarget(spell_id, curmob, false, true, resist_adjust); - if (!spells[spell_id].aemaxtargets) - caster->SpellOnTarget(spell_id, curmob, false, true, resist_adjust); - } - - if (!isnpc || spells[spell_id].aemaxtargets) //npcs are not target limited (unless casting a spell with a target limit)... + if (max_targets_allowed) { // if we have a limit, increment count iCounter++; + if (iCounter >= max_targets_allowed) // we done + break; + } } + + if (max_targets && max_targets_allowed) + *max_targets = *max_targets - iCounter; } void EntityList::MassGroupBuff(Mob *caster, Mob *center, uint16 spell_id, bool affect_caster) diff --git a/zone/embparser.cpp b/zone/embparser.cpp index f76c2ea6a..80f6ace11 100644 --- a/zone/embparser.cpp +++ b/zone/embparser.cpp @@ -117,6 +117,7 @@ const char *QuestEventSubroutines[_LargestEventID] = { "EVENT_TICK", "EVENT_SPAWN_ZONE", "EVENT_DEATH_ZONE", + "EVENT_USE_SKILL", }; PerlembParser::PerlembParser() : perl(nullptr) { @@ -1441,6 +1442,12 @@ void PerlembParser::ExportEventVariables(std::string &package_name, QuestEventID ExportVar(package_name.c_str(), "killed_npc_id", sep.arg[4]); break; } + case EVENT_USE_SKILL:{ + Seperator sep(data); + ExportVar(package_name.c_str(), "skill_id", sep.arg[0]); + ExportVar(package_name.c_str(), "skill_level", sep.arg[1]); + break; + } default: { break; diff --git a/zone/embparser_api.cpp b/zone/embparser_api.cpp index d74282211..b440fdd3b 100644 --- a/zone/embparser_api.cpp +++ b/zone/embparser_api.cpp @@ -3599,6 +3599,24 @@ XS(XS__crosszonesetentityvariablebynpctypeid) XSRETURN_EMPTY; } +XS(XS__crosszonesetentityvariablebyclientname); +XS(XS__crosszonesetentityvariablebyclientname) +{ + dXSARGS; + + if (items != 3) + Perl_croak(aTHX_ "Usage: crosszonesetentityvariablebyclientname(clientname, id, m_var)"); + + if (items == 3) { + const char *clientname = (const char *)SvPV_nolen(ST(0)); + const char *id = (const char *)SvPV_nolen(ST(1)); + const char *m_var = (const char *)SvPV_nolen(ST(2)); + quest_manager.CrossZoneSetEntityVariableByClientName(clientname, id, m_var); + } + + XSRETURN_EMPTY; +} + XS(XS__crosszonesignalnpcbynpctypeid); XS(XS__crosszonesignalnpcbynpctypeid) { @@ -3766,6 +3784,7 @@ EXTERN_C XS(boot_quest) newXS(strcpy(buf, "createguild"), XS__createguild, file); newXS(strcpy(buf, "crosszonemessageplayerbyname"), XS__crosszonemessageplayerbyname, file); newXS(strcpy(buf, "crosszonesetentityvariablebynpctypeid"), XS__crosszonesetentityvariablebynpctypeid, file); + newXS(strcpy(buf, "crosszonesetentityvariablebyclientname"), XS__crosszonesetentityvariablebyclientname, file); newXS(strcpy(buf, "crosszonesignalclientbycharid"), XS__crosszonesignalclientbycharid, file); newXS(strcpy(buf, "crosszonesignalclientbyname"), XS__crosszonesignalclientbyname, file); newXS(strcpy(buf, "crosszonesignalnpcbynpctypeid"), XS__crosszonesignalnpcbynpctypeid, file); diff --git a/zone/entity.cpp b/zone/entity.cpp index 37af35fdb..344e79a04 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -659,6 +659,8 @@ void EntityList::AddNPC(NPC *npc, bool SendSpawnPacket, bool dontqueue) QueueClients(npc, app); npc->SendArmorAppearance(); npc->SetAppearance(npc->GetGuardPointAnim(),false); + if (!npc->IsTargetable()) + npc->SendTargetable(false); safe_delete(app); } else { auto ns = new NewSpawn_Struct; @@ -799,6 +801,8 @@ void EntityList::CheckSpawnQueue() NPC *pnpc = it->second; pnpc->SendArmorAppearance(); pnpc->SetAppearance(pnpc->GetGuardPointAnim(), false); + if (!pnpc->IsTargetable()) + pnpc->SendTargetable(false); } safe_delete(outapp); iterator.RemoveCurrent(); @@ -1242,12 +1246,9 @@ void EntityList::SendZoneSpawns(Client *client) auto it = mob_list.begin(); while (it != mob_list.end()) { Mob *ent = it->second; - if (!(ent->InZone()) || (ent->IsClient())) { - if (ent->CastToClient()->GMHideMe(client) || - ent->CastToClient()->IsHoveringForRespawn()) { - ++it; - continue; - } + if (!ent->InZone() || !ent->ShouldISpawnFor(client)) { + ++it; + continue; } app = new EQApplicationPacket; @@ -1275,17 +1276,16 @@ void EntityList::SendZoneSpawnsBulk(Client *client) for (auto it = mob_list.begin(); it != mob_list.end(); ++it) { spawn = it->second; if (spawn && spawn->GetID() > 0 && spawn->Spawned()) { - if (spawn->IsClient() && (spawn->CastToClient()->GMHideMe(client) || - spawn->CastToClient()->IsHoveringForRespawn())) + if (!spawn->ShouldISpawnFor(client)) continue; #if 1 const glm::vec4& spos = spawn->GetPosition(); - + delaypkt = false; if (DistanceSquared(cpos, spos) > dmax || (spawn->IsClient() && (spawn->GetRace() == MINOR_ILL_OBJ || spawn->GetRace() == TREE))) delaypkt = true; - + if (delaypkt) { app = new EQApplicationPacket; spawn->CreateSpawnPacket(app); @@ -2135,6 +2135,25 @@ void EntityList::MessageClose(Mob* sender, bool skipsender, float dist, uint32 t } } +void EntityList::FilteredMessageClose(Mob *sender, bool skipsender, float dist, uint32 type, eqFilterType filter, const char *message, ...) +{ + va_list argptr; + char buffer[4096]; + + va_start(argptr, message); + vsnprintf(buffer, 4095, message, argptr); + va_end(argptr); + + float dist2 = dist * dist; + + auto it = client_list.begin(); + while (it != client_list.end()) { + if (DistanceSquared(it->second->GetPosition(), sender->GetPosition()) <= dist2 && (!skipsender || it->second != sender)) + it->second->FilteredMessage(sender, type, filter, buffer); + ++it; + } +} + void EntityList::RemoveAllMobs() { auto it = mob_list.begin(); @@ -2263,6 +2282,9 @@ bool EntityList::RemoveMob(uint16 delete_id) auto it = mob_list.find(delete_id); if (it != mob_list.end()) { + + RemoveMobFromClientCloseLists(it->second); + if (npc_list.count(delete_id)) entity_list.RemoveNPC(delete_id); else if (client_list.count(delete_id)) @@ -2285,6 +2307,8 @@ bool EntityList::RemoveMob(Mob *delete_mob) auto it = mob_list.begin(); while (it != mob_list.end()) { if (it->second == delete_mob) { + RemoveMobFromClientCloseLists(it->second); + safe_delete(it->second); if (!corpse_list.count(it->first)) free_ids.push(it->first); @@ -2304,10 +2328,9 @@ bool EntityList::RemoveNPC(uint16 delete_id) // make sure its proximity is removed RemoveProximity(delete_id); // remove from client close lists - RemoveNPCFromClientCloseLists(npc); + RemoveMobFromClientCloseLists(npc->CastToMob()); // remove from the list npc_list.erase(it); - // remove from limit list if needed if (npc_limit_list.count(delete_id)) @@ -2317,11 +2340,11 @@ bool EntityList::RemoveNPC(uint16 delete_id) return false; } -bool EntityList::RemoveNPCFromClientCloseLists(NPC *npc) +bool EntityList::RemoveMobFromClientCloseLists(Mob *mob) { auto it = client_list.begin(); while (it != client_list.end()) { - it->second->close_npcs.erase(npc); + it->second->close_mobs.erase(mob); ++it; } return false; @@ -2615,10 +2638,9 @@ void EntityList::RemoveDebuffs(Mob *caster) // Currently, a new packet is sent per entity. // @todo: Come back and use FLAG_COMBINED to pack // all updates into one packet. -void EntityList::SendPositionUpdates(Client *client, uint32 cLastUpdate, - float range, Entity *alwayssend, bool iSendEvenIfNotChanged) +void EntityList::SendPositionUpdates(Client *client, uint32 cLastUpdate, float update_range, Entity *always_send, bool iSendEvenIfNotChanged) { - range = range * range; + update_range = (update_range * update_range); EQApplicationPacket *outapp = 0; PlayerPositionUpdateServer_Struct *ppu = 0; @@ -2626,27 +2648,37 @@ void EntityList::SendPositionUpdates(Client *client, uint32 cLastUpdate, auto it = mob_list.begin(); while (it != mob_list.end()) { - if (outapp == 0) { - outapp = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct)); - ppu = (PlayerPositionUpdateServer_Struct*)outapp->pBuffer; - } + mob = it->second; - if (mob && !mob->IsCorpse() && (it->second != client) + + if ( + mob && !mob->IsCorpse() + && (it->second != client) && (mob->IsClient() || iSendEvenIfNotChanged || (mob->LastChange() >= cLastUpdate)) - && (!it->second->IsClient() || !it->second->CastToClient()->GMHideMe(client))) { + && (it->second->ShouldISpawnFor(client)) + ) { + if ( + update_range == 0 + || (it->second == always_send) + || mob->IsClient() + || (DistanceSquared(mob->GetPosition(), client->GetPosition()) <= update_range) + ) { + if (mob && mob->IsClient() && mob->GetID() > 0) { + client->QueuePacket(outapp, false, Client::CLIENT_CONNECTED); - //bool Grouped = client->HasGroup() && mob->IsClient() && (client->GetGroup() == mob->CastToClient()->GetGroup()); + if (outapp == 0) { + outapp = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct)); + ppu = (PlayerPositionUpdateServer_Struct*)outapp->pBuffer; + } - //if (range == 0 || (iterator.GetData() == alwayssend) || Grouped || (mob->DistNoRootNoZ(*client) <= range)) { - if (range == 0 || (it->second == alwayssend) || mob->IsClient() || (DistanceSquared(mob->GetPosition(), client->GetPosition()) <= range)) { - mob->MakeSpawnUpdate(ppu); - } - if(mob && mob->IsClient() && mob->GetID()>0) { - client->QueuePacket(outapp, false, Client::CLIENT_CONNECTED); + mob->MakeSpawnUpdate(ppu); + + safe_delete(outapp); + outapp = 0; + } } } - safe_delete(outapp); - outapp = 0; + ++it; } @@ -2826,6 +2858,22 @@ int32 EntityList::DeleteNPCCorpses() return x; } +void EntityList::CorpseFix(Client* c) +{ + + auto it = corpse_list.begin(); + while (it != corpse_list.end()) { + Corpse* corpse = it->second; + if (corpse->IsNPCCorpse()) { + if (DistanceNoZ(c->GetPosition(), corpse->GetPosition()) < 100) { + c->Message(15, "Attempting to fix %s", it->second->GetCleanName()); + corpse->GMMove(corpse->GetX(), corpse->GetY(), c->GetZ() + 2, 0); + } + } + ++it; + } +} + // returns the number of corpses deleted. A negative number indicates an error code. int32 EntityList::DeletePlayerCorpses() { diff --git a/zone/entity.h b/zone/entity.h index 2f2237283..29075d9fe 100644 --- a/zone/entity.h +++ b/zone/entity.h @@ -79,6 +79,8 @@ public: virtual bool IsTrap() const { return false; } virtual bool IsBeacon() const { return false; } virtual bool IsEncounter() const { return false; } + virtual bool IsBot() const { return false; } + virtual bool IsAura() const { return false; } virtual bool Process() { return false; } virtual bool Save() { return true; } @@ -113,7 +115,6 @@ public: bool CheckCoordLosNoZLeaps(float cur_x, float cur_y, float cur_z, float trg_x, float trg_y, float trg_z, float perwalk=1); #ifdef BOTS - virtual bool IsBot() const { return false; } Bot* CastToBot(); #endif @@ -279,7 +280,7 @@ public: bool RemoveTrap(uint16 delete_id); bool RemoveObject(uint16 delete_id); bool RemoveProximity(uint16 delete_npc_id); - bool RemoveNPCFromClientCloseLists(NPC *npc); + bool RemoveMobFromClientCloseLists(Mob *mob); void RemoveAllMobs(); void RemoveAllClients(); void RemoveAllNPCs(); @@ -315,6 +316,7 @@ public: void Message(uint32 to_guilddbid, uint32 type, const char* message, ...); void MessageStatus(uint32 to_guilddbid, int to_minstatus, uint32 type, const char* message, ...); void MessageClose(Mob* sender, bool skipsender, float dist, uint32 type, const char* message, ...); + void FilteredMessageClose(Mob* sender, bool skipsender, float dist, uint32 type, eqFilterType filter, const char* message, ...); void Message_StringID(Mob *sender, bool skipsender, uint32 type, uint32 string_id, const char* message1=0,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); void FilteredMessage_StringID(Mob *sender, bool skipsender, uint32 type, eqFilterType filter, uint32 string_id, const char* message1=0,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); void MessageClose_StringID(Mob *sender, bool skipsender, float dist, uint32 type, uint32 string_id, const char* message1=0,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); @@ -356,7 +358,7 @@ public: void AEAttack(Mob *attacker, float dist, int Hand = EQEmu::inventory::slotPrimary, int count = 0, bool IsFromSpell = false); void AETaunt(Client *caster, float range=0, int32 bonus_hate=0); - void AESpell(Mob *caster, Mob *center, uint16 spell_id, bool affect_caster = true, int16 resist_adjust = 0); + void AESpell(Mob *caster, Mob *center, uint16 spell_id, bool affect_caster = true, int16 resist_adjust = 0, 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); @@ -369,7 +371,7 @@ public: Mob* FindDefenseNPC(uint32 npcid); void OpenDoorsNear(NPC* opener); void UpdateWho(bool iSendFullUpdate = false); - void SendPositionUpdates(Client* client, uint32 cLastUpdate = 0, float range = 0, Entity* alwayssend = 0, bool iSendEvenIfNotChanged = false); + void SendPositionUpdates(Client* client, uint32 cLastUpdate = 0, float update_range = 0, Entity* always_send = 0, bool iSendEvenIfNotChanged = false); char* MakeNameUnique(char* name); static char* RemoveNumbers(char* name); void SignalMobsByNPCID(uint32 npc_type, int signal_id); @@ -385,6 +387,7 @@ public: void FindPathsToAllNPCs(); int32 DeleteNPCCorpses(); int32 DeletePlayerCorpses(); + void CorpseFix(Client* c); void WriteEntityIDs(); void HalveAggro(Mob* who); void DoubleAggro(Mob* who); diff --git a/zone/event_codes.h b/zone/event_codes.h index 1b0e18505..bb623f041 100644 --- a/zone/event_codes.h +++ b/zone/event_codes.h @@ -85,6 +85,7 @@ typedef enum { EVENT_TICK, EVENT_SPAWN_ZONE, EVENT_DEATH_ZONE, + EVENT_USE_SKILL, _LargestEventID } QuestEventID; diff --git a/zone/exp.cpp b/zone/exp.cpp index 044a102e0..7d9a02fa8 100644 --- a/zone/exp.cpp +++ b/zone/exp.cpp @@ -28,6 +28,7 @@ #include "queryserv.h" #include "quest_parser_collection.h" +#include "lua_parser.h" #include "string_ids.h" #ifdef BOTS @@ -153,6 +154,26 @@ uint32 Client::CalcEXP(uint8 conlevel) { return in_add_exp; } +uint32 Client::GetExperienceForKill(Mob *against) +{ +#ifdef LUA_EQEMU + uint32 lua_ret = 0; + bool ignoreDefault = false; + lua_ret = LuaParser::Instance()->GetExperienceForKill(this, against, ignoreDefault); + + if (ignoreDefault) { + return lua_ret; + } +#endif + + if (against && against->IsNPC()) { + uint32 level = (uint32)against->GetLevel(); + return EXP_FORMULA; + } + + return 0; +} + void Client::AddEXP(uint32 in_add_exp, uint8 conlevel, bool resexp) { this->EVENT_ITEM_ScriptStopReturn(); @@ -339,8 +360,8 @@ void Client::AddEXP(uint32 in_add_exp, uint8 conlevel, bool resexp) { void Client::SetEXP(uint32 set_exp, uint32 set_aaxp, bool isrezzexp) { Log(Logs::Detail, Logs::None, "Attempting to Set Exp for %s (XP: %u, AAXP: %u, Rez: %s)", this->GetCleanName(), set_exp, set_aaxp, isrezzexp ? "true" : "false"); - //max_AAXP = GetEXPForLevel(52) - GetEXPForLevel(51); //GetEXPForLevel() doesn't depend on class/race, just level, so it shouldn't change between Clients - max_AAXP = RuleI(AA, ExpPerPoint); //this may be redundant since we're doing this in Client::FinishConnState2() + + auto max_AAXP = GetRequiredAAExperience(); if (max_AAXP == 0 || GetEXPForLevel(GetLevel()) == 0xFFFFFFFF) { Message(13, "Error in Client::SetEXP. EXP not set."); return; // Must be invalid class/race @@ -377,19 +398,23 @@ void Client::SetEXP(uint32 set_exp, uint32 set_aaxp, bool isrezzexp) { } if (isrezzexp) { - if (RuleI(Character, ShowExpValues) > 0) Message(MT_Experience, "You regain %s experience from resurrection. %s", exp_amount_message.c_str(), exp_percent_message.c_str()); + if (RuleI(Character, ShowExpValues) > 0) + Message(MT_Experience, "You regain %s experience from resurrection. %s", exp_amount_message.c_str(), exp_percent_message.c_str()); else Message_StringID(MT_Experience, REZ_REGAIN); } else { if (membercount > 1) { - if (RuleI(Character, ShowExpValues) > 0) Message(MT_Experience, "You have gained %s party experience! %s", exp_amount_message.c_str(), exp_percent_message.c_str()); + if (RuleI(Character, ShowExpValues) > 0) + Message(MT_Experience, "You have gained %s party experience! %s", exp_amount_message.c_str(), exp_percent_message.c_str()); else Message_StringID(MT_Experience, GAIN_GROUPXP); } else if (IsRaidGrouped()) { - if (RuleI(Character, ShowExpValues) > 0) Message(MT_Experience, "You have gained %s raid experience! %s", exp_amount_message.c_str(), exp_percent_message.c_str()); + if (RuleI(Character, ShowExpValues) > 0) + Message(MT_Experience, "You have gained %s raid experience! %s", exp_amount_message.c_str(), exp_percent_message.c_str()); else Message_StringID(MT_Experience, GAIN_RAIDEXP); } else { - if (RuleI(Character, ShowExpValues) > 0) Message(MT_Experience, "You have gained %s experience! %s", exp_amount_message.c_str(), exp_percent_message.c_str()); + if (RuleI(Character, ShowExpValues) > 0) + Message(MT_Experience, "You have gained %s experience! %s", exp_amount_message.c_str(), exp_percent_message.c_str()); else Message_StringID(MT_Experience, GAIN_XP); } } @@ -460,14 +485,13 @@ void Client::SetEXP(uint32 set_exp, uint32 set_aaxp, bool isrezzexp) { //add in how many points we had m_pp.aapoints += last_unspentAA; - //set_aaxp = m_pp.expAA % max_AAXP; //figure out how many points were actually gained /*uint32 gained = m_pp.aapoints - last_unspentAA;*/ //unused //Message(15, "You have gained %d skill points!!", m_pp.aapoints - last_unspentAA); char val1[20]={0}; - Message_StringID(MT_Experience, GAIN_ABILITY_POINT,ConvertArray(m_pp.aapoints, val1),m_pp.aapoints == 1 ? "" : "(s)"); //You have gained an ability point! You now have %1 ability point%2. + Message_StringID(MT_Experience, GAIN_ABILITY_POINT, ConvertArray(m_pp.aapoints, val1),m_pp.aapoints == 1 ? "" : "(s)"); //You have gained an ability point! You now have %1 ability point%2. /* QS: PlayerLogAARate */ if (RuleB(QueryServ, PlayerLogAARate)){ @@ -571,8 +595,7 @@ void Client::SetEXP(uint32 set_exp, uint32 set_aaxp, bool isrezzexp) { char val1[20]={0}; char val2[20]={0}; char val3[20]={0}; - Message_StringID(MT_Experience, GM_GAINXP,ConvertArray(set_aaxp,val1),ConvertArray(set_exp,val2),ConvertArray(GetEXPForLevel(GetLevel()+1),val3)); //[GM] You have gained %1 AXP and %2 EXP (%3). - //Message(15, "[GM] You now have %d / %d EXP and %d / %d AA exp.", set_exp, GetEXPForLevel(GetLevel()+1), set_aaxp, max_AAXP); + Message_StringID(MT_Experience, GM_GAINXP, ConvertArray(set_aaxp,val1),ConvertArray(set_exp,val2),ConvertArray(GetEXPForLevel(GetLevel()+1),val3)); //[GM] You have gained %1 AXP and %2 EXP (%3). } } @@ -664,6 +687,15 @@ void Client::SetLevel(uint8 set_level, bool command) // Add: You can set the values you want now, client will be always sync :) - Merkur uint32 Client::GetEXPForLevel(uint16 check_level) { +#ifdef LUA_EQEMU + uint32 lua_ret = 0; + bool ignoreDefault = false; + lua_ret = LuaParser::Instance()->GetEXPForLevel(this, check_level, ignoreDefault); + + if (ignoreDefault) { + return lua_ret; + } +#endif uint16 check_levelm1 = check_level-1; float mod; @@ -933,3 +965,17 @@ uint32 Client::GetCharMaxLevelFromQGlobal() { return false; } + +uint32 Client::GetRequiredAAExperience() { +#ifdef LUA_EQEMU + uint32 lua_ret = 0; + bool ignoreDefault = false; + lua_ret = LuaParser::Instance()->GetRequiredAAExperience(this, ignoreDefault); + + if (ignoreDefault) { + return lua_ret; + } +#endif + + return RuleI(AA, ExpPerPoint); +} diff --git a/zone/fearpath.cpp b/zone/fearpath.cpp index ea5bb16d9..c2ce95fbe 100644 --- a/zone/fearpath.cpp +++ b/zone/fearpath.cpp @@ -154,7 +154,8 @@ void Mob::CalculateNewFearpoint() int loop = 0; float ranx, rany, ranz; - currently_fleeing = false; + + currently_fleeing = true; while (loop < 100) //Max 100 tries { int ran = 250 - (loop*2); @@ -167,13 +168,13 @@ void Mob::CalculateNewFearpoint() float fdist = ranz - GetZ(); if (fdist >= -12 && fdist <= 12 && CheckCoordLosNoZLeaps(GetX(),GetY(),GetZ(),ranx,rany,ranz)) { - currently_fleeing = true; break; } } - if (currently_fleeing) - m_FearWalkTarget = glm::vec3(ranx, rany, ranz); - else //Break fear - BuffFadeByEffect(SE_Fear); + + if (loop <= 100) + { + m_FearWalkTarget = glm::vec3(ranx, rany, ranz); + } } diff --git a/zone/forage.cpp b/zone/forage.cpp index 5d21d599f..be93d39ae 100644 --- a/zone/forage.cpp +++ b/zone/forage.cpp @@ -196,7 +196,7 @@ bool Client::CanFish() { float step_size = RuleR(Watermap, FishingLineStepSize); - for(float i = 0.0f; i < len; i += step_size) { + for(float i = 0.0f; i < LineLength; i += step_size) { glm::vec3 dest(rodPosition.x, rodPosition.y, m_Position.z - i); bool in_lava = zone->watermap->InLava(dest); diff --git a/zone/groups.cpp b/zone/groups.cpp index d2342b4d9..c6fa6a978 100644 --- a/zone/groups.cpp +++ b/zone/groups.cpp @@ -338,6 +338,13 @@ bool Group::AddMember(Mob* newmember, const char *NewMemberName, uint32 Characte database.SetGroupID(NewMemberName, GetID(), owner->CharacterID(), true); } } + + Group* group = newmember->CastToClient()->GetGroup(); + if (group) { + group->SendHPManaEndPacketsTo(newmember); + group->SendHPPacketsFrom(newmember); + } + } else { @@ -387,31 +394,30 @@ void Group::QueuePacket(const EQApplicationPacket *app, bool ack_req) // Sends the rest of the group's hps to member. this is useful when someone // first joins a group, but otherwise there shouldn't be a need to call it -void Group::SendHPPacketsTo(Mob *member) +void Group::SendHPManaEndPacketsTo(Mob *member) { - if(member && member->IsClient()) - { + if(member && member->IsClient()) { EQApplicationPacket hpapp; EQApplicationPacket outapp(OP_MobManaUpdate, sizeof(MobManaUpdate_Struct)); - for (uint32 i = 0; i < MAX_GROUP_MEMBERS; i++) - { - if(members[i] && members[i] != member) - { + for (uint32 i = 0; i < MAX_GROUP_MEMBERS; i++) { + if(members[i] && members[i] != member) { members[i]->CreateHPPacket(&hpapp); member->CastToClient()->QueuePacket(&hpapp, false); safe_delete_array(hpapp.pBuffer); hpapp.size = 0; - if (member->CastToClient()->ClientVersion() >= EQEmu::versions::ClientVersion::SoD) - { + + if (member->CastToClient()->ClientVersion() >= EQEmu::versions::ClientVersion::SoD) { outapp.SetOpcode(OP_MobManaUpdate); - MobManaUpdate_Struct *mmus = (MobManaUpdate_Struct *)outapp.pBuffer; - mmus->spawn_id = members[i]->GetID(); - mmus->mana = members[i]->GetManaPercent(); + + MobManaUpdate_Struct *mana_update = (MobManaUpdate_Struct *)outapp.pBuffer; + mana_update->spawn_id = members[i]->GetID(); + mana_update->mana = members[i]->GetManaPercent(); member->CastToClient()->QueuePacket(&outapp, false); - MobEnduranceUpdate_Struct *meus = (MobEnduranceUpdate_Struct *)outapp.pBuffer; + + MobEnduranceUpdate_Struct *endurance_update = (MobEnduranceUpdate_Struct *)outapp.pBuffer; outapp.SetOpcode(OP_MobEnduranceUpdate); - meus->endurance = members[i]->GetEndurancePercent(); + endurance_update->endurance = members[i]->GetEndurancePercent(); member->CastToClient()->QueuePacket(&outapp, false); } } @@ -430,19 +436,58 @@ void Group::SendHPPacketsFrom(Mob *member) uint32 i; for(i = 0; i < MAX_GROUP_MEMBERS; i++) { - if(members[i] && members[i] != member && members[i]->IsClient()) - { + if(members[i] && members[i] != member && members[i]->IsClient()) { members[i]->CastToClient()->QueuePacket(&hp_app); - if (members[i]->CastToClient()->ClientVersion() >= EQEmu::versions::ClientVersion::SoD) - { + if (members[i]->CastToClient()->ClientVersion() >= EQEmu::versions::ClientVersion::SoD) { outapp.SetOpcode(OP_MobManaUpdate); - MobManaUpdate_Struct *mmus = (MobManaUpdate_Struct *)outapp.pBuffer; - mmus->spawn_id = member->GetID(); - mmus->mana = member->GetManaPercent(); + MobManaUpdate_Struct *mana_update = (MobManaUpdate_Struct *)outapp.pBuffer; + mana_update->spawn_id = member->GetID(); + mana_update->mana = member->GetManaPercent(); members[i]->CastToClient()->QueuePacket(&outapp, false); - MobEnduranceUpdate_Struct *meus = (MobEnduranceUpdate_Struct *)outapp.pBuffer; + + MobEnduranceUpdate_Struct *endurance_update = (MobEnduranceUpdate_Struct *)outapp.pBuffer; outapp.SetOpcode(OP_MobEnduranceUpdate); - meus->endurance = member->GetEndurancePercent(); + endurance_update->endurance = member->GetEndurancePercent(); + members[i]->CastToClient()->QueuePacket(&outapp, false); + } + } + } +} + +void Group::SendManaPacketFrom(Mob *member) +{ + if (!member) + return; + EQApplicationPacket outapp(OP_MobManaUpdate, sizeof(MobManaUpdate_Struct)); + + uint32 i; + for (i = 0; i < MAX_GROUP_MEMBERS; i++) { + if (members[i] && members[i] != member && members[i]->IsClient()) { + if (members[i]->CastToClient()->ClientVersion() >= EQEmu::versions::ClientVersion::SoD) { + outapp.SetOpcode(OP_MobManaUpdate); + MobManaUpdate_Struct *mana_update = (MobManaUpdate_Struct *)outapp.pBuffer; + mana_update->spawn_id = member->GetID(); + mana_update->mana = member->GetManaPercent(); + members[i]->CastToClient()->QueuePacket(&outapp, false); + } + } + } +} + +void Group::SendEndurancePacketFrom(Mob* member) +{ + if (!member) + return; + + EQApplicationPacket outapp(OP_MobEnduranceUpdate, sizeof(MobManaUpdate_Struct)); + + uint32 i; + for (i = 0; i < MAX_GROUP_MEMBERS; i++) { + if (members[i] && members[i] != member && members[i]->IsClient()) { + if (members[i]->CastToClient()->ClientVersion() >= EQEmu::versions::ClientVersion::SoD) { + MobEnduranceUpdate_Struct *endurance_update = (MobEnduranceUpdate_Struct *)outapp.pBuffer; + endurance_update->spawn_id = member->GetID(); + endurance_update->endurance = member->GetEndurancePercent(); members[i]->CastToClient()->QueuePacket(&outapp, false); } } @@ -648,7 +693,7 @@ bool Group::DelMember(Mob* oldmember, bool ignoresender) } } - if (GetLeader() == nullptr) + if (!GetLeaderName()) { DisbandGroup(); return true; diff --git a/zone/groups.h b/zone/groups.h index 6de364d93..79fb0d8b6 100644 --- a/zone/groups.h +++ b/zone/groups.h @@ -80,8 +80,10 @@ public: inline void SetLeader(Mob* newleader){ leader=newleader; }; inline Mob* GetLeader() { return leader; }; const char* GetLeaderName() { return membername[0]; }; - void SendHPPacketsTo(Mob* newmember); - void SendHPPacketsFrom(Mob* newmember); + void SendHPManaEndPacketsTo(Mob* newmember); + void SendHPPacketsFrom(Mob* member); + void SendManaPacketFrom(Mob* member); + void SendEndurancePacketFrom(Mob* member); bool UpdatePlayer(Mob* update); void MemberZoned(Mob* removemob); inline bool IsLeader(Mob* leadertest) { return leadertest==leader; }; diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index dde90988c..64bf2321c 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -1172,6 +1172,11 @@ uint64 Lua_Client::GetAllMoney() { return self->GetAllMoney(); } +uint32 Lua_Client::GetMoney(uint8 type, uint8 subtype) { + Lua_Safe_Call_Int(); + return self->GetMoney(type, subtype); +} + void Lua_Client::OpenLFGuildWindow() { Lua_Safe_Call_Void(); self->OpenLFGuildWindow(); @@ -1414,9 +1419,25 @@ void Lua_Client::QuestReward(Lua_Mob target, luabind::adl::object reward) { self->QuestReward(target, copper, silver, gold, platinum, itemid, exp, faction); } -uint32 Lua_Client::GetMoney(uint8 type, uint8 subtype) { +bool Lua_Client::IsDead() { + Lua_Safe_Call_Bool(); + return self->IsDead(); +} + +int Lua_Client::CalcCurrentWeight() { Lua_Safe_Call_Int(); - return self->GetMoney(type, subtype); + return self->CalcCurrentWeight(); +} + +int Lua_Client::CalcATK() { + Lua_Safe_Call_Int(); + return self->CalcATK(); +} + +void Lua_Client::FilteredMessage(Mob *sender, uint32 type, int filter, const char *message) +{ + Lua_Safe_Call_Void(); + self->FilteredMessage(sender, type, (eqFilterType)filter, message); } luabind::scope lua_register_client() { @@ -1653,6 +1674,7 @@ luabind::scope lua_register_client() { .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("Signal", (void(Lua_Client::*)(uint32))&Lua_Client::Signal) .def("AddAlternateCurrencyValue", (void(Lua_Client::*)(uint32,int))&Lua_Client::AddAlternateCurrencyValue) @@ -1687,7 +1709,10 @@ luabind::scope lua_register_client() { .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("GetMoney", (uint32(Lua_Client::*)(uint8, uint8))&Lua_Client::GetMoney); + .def("IsDead", &Lua_Client::IsDead) + .def("CalcCurrentWeight", &Lua_Client::CalcCurrentWeight) + .def("CalcATK", &Lua_Client::CalcATK) + .def("FilteredMessage", &Lua_Client::FilteredMessage); } luabind::scope lua_register_inventory_where() { diff --git a/zone/lua_client.h b/zone/lua_client.h index d7cf4f75f..f29e3b46f 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -297,6 +297,10 @@ public: void QuestReward(Lua_Mob target, uint32 copper, uint32 silver, uint32 gold, uint32 platinum, uint32 itemid, uint32 exp); void QuestReward(Lua_Mob target, uint32 copper, uint32 silver, uint32 gold, uint32 platinum, uint32 itemid, uint32 exp, bool faction); void QuestReward(Lua_Mob target, luabind::adl::object reward); + bool IsDead(); + int CalcCurrentWeight(); + int CalcATK(); + void FilteredMessage(Mob *sender, uint32 type, int filter, const char* message); }; #endif diff --git a/zone/lua_entity.cpp b/zone/lua_entity.cpp index ea233af4b..725e78f5e 100644 --- a/zone/lua_entity.cpp +++ b/zone/lua_entity.cpp @@ -67,6 +67,16 @@ bool Lua_Entity::IsBeacon() { return self->IsBeacon(); } +bool Lua_Entity::IsEncounter() { + Lua_Safe_Call_Bool(); + return self->IsEncounter(); +} + +bool Lua_Entity::IsBot() { + Lua_Safe_Call_Bool(); + return self->IsBot(); +} + int Lua_Entity::GetID() { Lua_Safe_Call_Bool(); return self->GetID(); @@ -124,6 +134,8 @@ luabind::scope lua_register_entity() { .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) diff --git a/zone/lua_entity.h b/zone/lua_entity.h index 7e3254ff3..b45208581 100644 --- a/zone/lua_entity.h +++ b/zone/lua_entity.h @@ -44,6 +44,8 @@ public: bool IsDoor(); bool IsTrap(); bool IsBeacon(); + bool IsEncounter(); + bool IsBot(); int GetID(); Lua_Client CastToClient(); diff --git a/zone/lua_entity_list.cpp b/zone/lua_entity_list.cpp index 7626a709b..ef4057ed1 100644 --- a/zone/lua_entity_list.cpp +++ b/zone/lua_entity_list.cpp @@ -210,6 +210,12 @@ void Lua_EntityList::MessageClose(Lua_Mob sender, bool skip_sender, float dist, self->MessageClose(sender, skip_sender, dist, type, message); } +void Lua_EntityList::FilteredMessageClose(Lua_Mob sender, bool skip_sender, float dist, uint32 type, int filter, const char *message) +{ + Lua_Safe_Call_Void(); + self->FilteredMessageClose(sender, skip_sender, dist, type, (eqFilterType)filter, message); +} + void Lua_EntityList::RemoveFromTargets(Lua_Mob mob) { Lua_Safe_Call_Void(); self->RemoveFromTargets(mob); @@ -450,16 +456,17 @@ luabind::scope lua_register_entity_list() { .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("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("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_NPC))&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("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) @@ -467,10 +474,10 @@ luabind::scope lua_register_entity_list() { .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("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("GetNPCList", (Lua_NPC_List(Lua_EntityList::*)(void))&Lua_EntityList::GetNPCList) @@ -479,7 +486,7 @@ luabind::scope lua_register_entity_list() { .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("ChannelMessage", (void(Lua_EntityList::*)(Lua_Mob, int, int, const char*))&Lua_EntityList::ChannelMessage); } luabind::scope lua_register_mob_list() { diff --git a/zone/lua_entity_list.h b/zone/lua_entity_list.h index 823499f24..17b00579e 100644 --- a/zone/lua_entity_list.h +++ b/zone/lua_entity_list.h @@ -80,6 +80,7 @@ public: void Message(uint32 guild_dbid, uint32 type, const char *message); void MessageStatus(uint32 guild_dbid, int min_status, uint32 type, const char *message); void MessageClose(Lua_Mob sender, bool skip_sender, float dist, uint32 type, const char *message); + void FilteredMessageClose(Lua_Mob sender, bool skip_sender, float dist, uint32 type, int filter, const char *message); void RemoveFromTargets(Lua_Mob mob); void RemoveFromTargets(Lua_Mob mob, bool RemoveFromXTargets); void ReplaceWithTarget(Lua_Mob target, Lua_Mob new_target); diff --git a/zone/lua_general.cpp b/zone/lua_general.cpp index bc6205c85..aad0be464 100644 --- a/zone/lua_general.cpp +++ b/zone/lua_general.cpp @@ -7,6 +7,10 @@ #include #include +#include "../common/timer.h" +#include "../common/eqemu_logsys.h" +#include "../common/classes.h" +#include "../common/rulesys.h" #include "lua_parser.h" #include "lua_item.h" #include "lua_iteminst.h" @@ -16,8 +20,6 @@ #include "quest_parser_collection.h" #include "questmgr.h" #include "qglobals.h" -#include "../common/timer.h" -#include "../common/eqemu_logsys.h" #include "encounter.h" #include "lua_encounter.h" @@ -27,6 +29,12 @@ struct Slots { }; struct Materials { }; struct ClientVersions { }; struct Appearances { }; +struct Classes { }; +struct Skills { }; +struct BodyTypes { }; +struct Filters { }; +struct MessageTypes { }; +struct Rule { }; struct lua_registered_event { std::string encounter_name; @@ -892,6 +900,10 @@ void lua_cross_zone_message_player_by_name(uint32 type, const char *player, cons quest_manager.CrossZoneMessagePlayerByName(type, player, message); } +void lua_cross_zone_set_entity_variable_by_client_name(const char *player, const char *id, const char *m_var) { + quest_manager.CrossZoneSetEntityVariableByClientName(player, id, m_var); +} + void lua_world_wide_marquee(uint32 type, uint32 priority, uint32 fadein, uint32 fadeout, uint32 duration, const char *message) { quest_manager.WorldWideMarquee(type, priority, fadein, fadeout, duration, message); } @@ -1458,6 +1470,39 @@ void lua_create_npc(luabind::adl::object table, float x, float y, float z, float npc->GiveNPCTypeData(npc_type); entity_list.AddNPC(npc); } + +int random_int(int low, int high) { + return zone->random.Int(low, high); +} + +double random_real(double low, double high) { + return zone->random.Real(low, high); +} + +bool random_roll_int(int required) { + return zone->random.Roll(required); +} + +bool random_roll_real(double required) { + return zone->random.Roll(required); +} + +int random_roll0(int max) { + return zone->random.Roll0(max); +} + +int get_rulei(int rule) { + return RuleManager::Instance()->GetIntRule((RuleManager::IntType)rule); +} + +float get_ruler(int rule) { + return RuleManager::Instance()->GetRealRule((RuleManager::RealType)rule); +} + +bool get_ruleb(int rule) { + return RuleManager::Instance()->GetBoolRule((RuleManager::BoolType)rule); +} + luabind::scope lua_register_general() { return luabind::namespace_("eq") [ @@ -1617,6 +1662,7 @@ luabind::scope lua_register_general() { luabind::def("cross_zone_signal_client_by_char_id", &lua_cross_zone_signal_client_by_char_id), luabind::def("cross_zone_signal_client_by_name", &lua_cross_zone_signal_client_by_name), luabind::def("cross_zone_message_player_by_name", &lua_cross_zone_message_player_by_name), + luabind::def("cross_zone_set_entity_variable_by_client_name", &lua_cross_zone_set_entity_variable_by_client_name), luabind::def("world_wide_marquee", &lua_world_wide_marquee), 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), @@ -1658,6 +1704,18 @@ luabind::scope lua_register_general() { ]; } +luabind::scope lua_register_random() { + return luabind::namespace_("Random") + [ + luabind::def("Int", &random_int), + luabind::def("Real", &random_real), + luabind::def("Roll", &random_roll_int), + luabind::def("RollReal", &random_roll_real), + luabind::def("Roll0", &random_roll0) + ]; +} + + luabind::scope lua_register_events() { return luabind::class_("Event") .enum_("constants") @@ -1738,7 +1796,8 @@ luabind::scope lua_register_events() { luabind::value("unhandled_opcode", static_cast(EVENT_UNHANDLED_OPCODE)), 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("death_zone", static_cast(EVENT_DEATH_ZONE)), + luabind::value("use_skill", static_cast(EVENT_USE_SKILL)) ]; } @@ -1857,4 +1916,355 @@ luabind::scope lua_register_appearance() { ]; } +luabind::scope lua_register_classes() { + return luabind::class_("Class") + .enum_("constants") + [ + luabind::value("WARRIOR", WARRIOR), + luabind::value("CLERIC", CLERIC), + luabind::value("PALADIN", PALADIN), + luabind::value("RANGER", RANGER), + luabind::value("SHADOWKNIGHT", SHADOWKNIGHT), + luabind::value("DRUID", DRUID), + luabind::value("MONK", MONK), + luabind::value("BARD", BARD), + luabind::value("ROGUE", ROGUE), + luabind::value("SHAMAN", SHAMAN), + luabind::value("NECROMANCER", NECROMANCER), + luabind::value("WIZARD", WIZARD), + luabind::value("MAGICIAN", MAGICIAN), + luabind::value("ENCHANTER", ENCHANTER), + luabind::value("BEASTLORD", BEASTLORD), + luabind::value("BERSERKER", BERSERKER), + luabind::value("WARRIORGM", WARRIORGM), + luabind::value("CLERICGM", CLERICGM), + luabind::value("PALADINGM", PALADINGM), + luabind::value("RANGERGM", RANGERGM), + luabind::value("SHADOWKNIGHTGM", SHADOWKNIGHTGM), + luabind::value("DRUIDGM", DRUIDGM), + luabind::value("MONKGM", MONKGM), + luabind::value("BARDGM", BARDGM), + luabind::value("ROGUEGM", ROGUEGM), + luabind::value("SHAMANGM", SHAMANGM), + luabind::value("NECROMANCERGM", NECROMANCERGM), + luabind::value("WIZARDGM", WIZARDGM), + luabind::value("MAGICIANGM", MAGICIANGM), + luabind::value("ENCHANTERGM", ENCHANTERGM), + luabind::value("BEASTLORDGM", BEASTLORDGM), + luabind::value("BERSERKERGM", BERSERKERGM), + luabind::value("BANKER", BANKER), + luabind::value("MERCHANT", MERCHANT), + luabind::value("DISCORD_MERCHANT", DISCORD_MERCHANT), + luabind::value("ADVENTURERECRUITER", ADVENTURERECRUITER), + luabind::value("ADVENTUREMERCHANT", ADVENTUREMERCHANT), + luabind::value("LDON_TREASURE", LDON_TREASURE), + luabind::value("CORPSE_CLASS", CORPSE_CLASS), + luabind::value("TRIBUTE_MASTER", TRIBUTE_MASTER), + luabind::value("GUILD_TRIBUTE_MASTER", GUILD_TRIBUTE_MASTER), + luabind::value("NORRATHS_KEEPERS_MERCHANT", NORRATHS_KEEPERS_MERCHANT), + luabind::value("DARK_REIGN_MERCHANT", DARK_REIGN_MERCHANT), + luabind::value("FELLOWSHIP_MASTER", FELLOWSHIP_MASTER), + luabind::value("ALT_CURRENCY_MERCHANT", ALT_CURRENCY_MERCHANT), + luabind::value("MERCERNARY_MASTER", MERCERNARY_MASTER) + ]; +} + +luabind::scope lua_register_skills() { + return luabind::class_("Skill") + .enum_("constants") + [ + luabind::value("1HBlunt", EQEmu::skills::Skill1HBlunt), + luabind::value("1HSlashing", EQEmu::skills::Skill1HSlashing), + luabind::value("2HBlunt", EQEmu::skills::Skill2HBlunt), + luabind::value("2HSlashing", EQEmu::skills::Skill2HSlashing), + luabind::value("Abjuration", EQEmu::skills::SkillAbjuration), + luabind::value("Alteration", EQEmu::skills::SkillAlteration), + luabind::value("ApplyPoison", EQEmu::skills::SkillApplyPoison), + luabind::value("Archery", EQEmu::skills::SkillArchery), + luabind::value("Backstab", EQEmu::skills::SkillBackstab), + luabind::value("BindWound", EQEmu::skills::SkillBindWound), + luabind::value("Bash", EQEmu::skills::SkillBash), + luabind::value("Block", EQEmu::skills::SkillBlock), + luabind::value("BrassInstruments", EQEmu::skills::SkillBrassInstruments), + luabind::value("Channeling", EQEmu::skills::SkillChanneling), + luabind::value("Conjuration", EQEmu::skills::SkillConjuration), + luabind::value("Defense", EQEmu::skills::SkillDefense), + luabind::value("Disarm", EQEmu::skills::SkillDisarm), + luabind::value("DisarmTraps", EQEmu::skills::SkillDisarmTraps), + luabind::value("Divination", EQEmu::skills::SkillDivination), + luabind::value("Dodge", EQEmu::skills::SkillDodge), + luabind::value("DoubleAttack", EQEmu::skills::SkillDoubleAttack), + luabind::value("DragonPunch", EQEmu::skills::SkillDragonPunch), + luabind::value("TailRake", EQEmu::skills::SkillTailRake), + luabind::value("DualWield", EQEmu::skills::SkillDualWield), + luabind::value("EagleStrike", EQEmu::skills::SkillEagleStrike), + luabind::value("Evocation", EQEmu::skills::SkillEvocation), + luabind::value("FeignDeath", EQEmu::skills::SkillFeignDeath), + luabind::value("FlyingKick", EQEmu::skills::SkillFlyingKick), + luabind::value("Forage", EQEmu::skills::SkillForage), + luabind::value("HandtoHand", EQEmu::skills::SkillHandtoHand), + luabind::value("Hide", EQEmu::skills::SkillHide), + luabind::value("Kick", EQEmu::skills::SkillKick), + luabind::value("Meditate", EQEmu::skills::SkillMeditate), + luabind::value("Mend", EQEmu::skills::SkillMend), + luabind::value("Offense", EQEmu::skills::SkillOffense), + luabind::value("Parry", EQEmu::skills::SkillParry), + luabind::value("PickLock", EQEmu::skills::SkillPickLock), + luabind::value("1HPiercing", EQEmu::skills::Skill1HPiercing), + luabind::value("Riposte", EQEmu::skills::SkillRiposte), + luabind::value("RoundKick", EQEmu::skills::SkillRoundKick), + luabind::value("SafeFall", EQEmu::skills::SkillSafeFall), + luabind::value("SenseHeading", EQEmu::skills::SkillSenseHeading), + luabind::value("Singing", EQEmu::skills::SkillSinging), + luabind::value("Sneak", EQEmu::skills::SkillSneak), + luabind::value("SpecializeAbjure", EQEmu::skills::SkillSpecializeAbjure), + luabind::value("SpecializeAlteration", EQEmu::skills::SkillSpecializeAlteration), + luabind::value("SpecializeConjuration", EQEmu::skills::SkillSpecializeConjuration), + luabind::value("SpecializeDivination", EQEmu::skills::SkillSpecializeDivination), + luabind::value("SpecializeEvocation", EQEmu::skills::SkillSpecializeEvocation), + luabind::value("PickPockets", EQEmu::skills::SkillPickPockets), + luabind::value("StringedInstruments", EQEmu::skills::SkillStringedInstruments), + luabind::value("Swimming", EQEmu::skills::SkillSwimming), + luabind::value("Throwing", EQEmu::skills::SkillThrowing), + luabind::value("TigerClaw", EQEmu::skills::SkillTigerClaw), + luabind::value("Tracking", EQEmu::skills::SkillTracking), + luabind::value("WindInstruments", EQEmu::skills::SkillWindInstruments), + luabind::value("Fishing", EQEmu::skills::SkillFishing), + luabind::value("MakePoison", EQEmu::skills::SkillMakePoison), + luabind::value("Tinkering", EQEmu::skills::SkillTinkering), + luabind::value("Research", EQEmu::skills::SkillResearch), + luabind::value("Alchemy", EQEmu::skills::SkillAlchemy), + luabind::value("Baking", EQEmu::skills::SkillBaking), + luabind::value("Tailoring", EQEmu::skills::SkillTailoring), + luabind::value("SenseTraps", EQEmu::skills::SkillSenseTraps), + luabind::value("Blacksmithing", EQEmu::skills::SkillBlacksmithing), + luabind::value("Fletching", EQEmu::skills::SkillFletching), + luabind::value("Brewing", EQEmu::skills::SkillBrewing), + luabind::value("AlcoholTolerance", EQEmu::skills::SkillAlcoholTolerance), + luabind::value("Begging", EQEmu::skills::SkillBegging), + luabind::value("JewelryMaking", EQEmu::skills::SkillJewelryMaking), + luabind::value("Pottery", EQEmu::skills::SkillPottery), + luabind::value("PercussionInstruments", EQEmu::skills::SkillPercussionInstruments), + luabind::value("Intimidation", EQEmu::skills::SkillIntimidation), + luabind::value("Berserking", EQEmu::skills::SkillBerserking), + luabind::value("Taunt", EQEmu::skills::SkillTaunt), + luabind::value("Frenzy", EQEmu::skills::SkillFrenzy), + luabind::value("RemoveTraps", EQEmu::skills::SkillRemoveTraps), + luabind::value("TripleAttack", EQEmu::skills::SkillTripleAttack), + luabind::value("2HPiercing", EQEmu::skills::Skill2HPiercing), + luabind::value("HIGHEST_SKILL", EQEmu::skills::HIGHEST_SKILL) + ]; +} + +luabind::scope lua_register_bodytypes() { + return luabind::class_("BT") + .enum_("constants") + [ + luabind::value("Humanoid", 1), + luabind::value("Lycanthrope", 2), + luabind::value("Undead", 3), + luabind::value("Giant", 4), + luabind::value("Construct", 5), + luabind::value("Extraplanar", 6), + luabind::value("Magical", 7), + luabind::value("SummonedUndead", 8), + luabind::value("RaidGiant", 9), + luabind::value("NoTarget", 11), + luabind::value("Vampire", 12), + luabind::value("Atenha_Ra", 13), + luabind::value("Greater_Akheva", 14), + luabind::value("Khati_Sha", 15), + luabind::value("Seru", 16), + luabind::value("Draz_Nurakk", 18), + luabind::value("Zek", 19), + luabind::value("Luggald", 20), + luabind::value("Animal", 21), + luabind::value("Insect", 22), + luabind::value("Monster", 23), + luabind::value("Summoned", 24), + luabind::value("Plant", 25), + luabind::value("Dragon", 26), + luabind::value("Summoned2", 27), + luabind::value("Summoned3", 28), + luabind::value("VeliousDragon", 30), + luabind::value("Dragon3", 32), + luabind::value("Boxes", 33), + luabind::value("Muramite", 34), + luabind::value("NoTarget2", 60), + luabind::value("SwarmPet", 63), + luabind::value("InvisMan", 66), + luabind::value("Special", 67) + ]; +} + +luabind::scope lua_register_filters() { + return luabind::class_("Filter") + .enum_("constants") + [ + luabind::value("None", FilterNone), + luabind::value("GuildChat", FilterGuildChat), + luabind::value("Socials", FilterSocials), + luabind::value("GroupChat", FilterGroupChat), + luabind::value("Shouts", FilterShouts), + luabind::value("Auctions", FilterAuctions), + luabind::value("OOC", FilterOOC), + luabind::value("BadWords", FilterBadWords), + luabind::value("PCSpells", FilterPCSpells), + luabind::value("NPCSpells", FilterNPCSpells), + luabind::value("BardSongs", FilterBardSongs), + luabind::value("SpellCrits", FilterSpellCrits), + luabind::value("MeleeCrits", FilterMeleeCrits), + luabind::value("SpellDamage", FilterSpellDamage), + luabind::value("MyMisses", FilterMyMisses), + luabind::value("OthersMiss", FilterOthersMiss), + luabind::value("OthersHit", FilterOthersHit), + luabind::value("MissedMe", FilterMissedMe), + luabind::value("DamageShields", FilterDamageShields), + luabind::value("DOT", FilterDOT), + luabind::value("PetHits", FilterPetHits), + luabind::value("PetMisses", FilterPetMisses), + luabind::value("FocusEffects", FilterFocusEffects), + luabind::value("PetSpells", FilterPetSpells), + luabind::value("HealOverTime", FilterHealOverTime), + luabind::value("Unknown25", FilterUnknown25), + luabind::value("Unknown26", FilterUnknown26), + luabind::value("Unknown27", FilterUnknown27), + luabind::value("Unknown28", FilterUnknown28) + ]; +} + +luabind::scope lua_register_message_types() { + return luabind::class_("MT") + .enum_("constants") + [ + luabind::value("Say", MT_Say), + luabind::value("Tell", MT_Tell), + luabind::value("Group", MT_Group), + luabind::value("Guild", MT_Guild), + luabind::value("OOC", MT_OOC), + luabind::value("Auction", MT_Auction), + luabind::value("Shout", MT_Shout), + luabind::value("Emote", MT_Emote), + luabind::value("Spells", MT_Spells), + luabind::value("YouHitOther", MT_YouHitOther), + luabind::value("OtherHitsYou", MT_OtherHitsYou), + luabind::value("YouMissOther", MT_YouMissOther), + luabind::value("OtherMissesYou", MT_OtherMissesYou), + luabind::value("Broadcasts", MT_Broadcasts), + luabind::value("Skills", MT_Skills), + luabind::value("Disciplines", MT_Disciplines), + luabind::value("Unused1", MT_Unused1), + luabind::value("DefaultText", MT_DefaultText), + luabind::value("Unused2", MT_Unused2), + luabind::value("MerchantOffer", MT_MerchantOffer), + luabind::value("MerchantBuySell", MT_MerchantBuySell), + luabind::value("YourDeath", MT_YourDeath), + luabind::value("OtherDeath", MT_OtherDeath), + luabind::value("OtherHits", MT_OtherHits), + luabind::value("OtherMisses", MT_OtherMisses), + luabind::value("Who", MT_Who), + luabind::value("YellForHelp", MT_YellForHelp), + luabind::value("NonMelee", MT_NonMelee), + luabind::value("WornOff", MT_WornOff), + luabind::value("MoneySplit", MT_MoneySplit), + luabind::value("LootMessages", MT_LootMessages), + luabind::value("DiceRoll", MT_DiceRoll), + luabind::value("OtherSpells", MT_OtherSpells), + luabind::value("SpellFailure", MT_SpellFailure), + luabind::value("Chat", MT_Chat), + luabind::value("Channel1", MT_Channel1), + luabind::value("Channel2", MT_Channel2), + luabind::value("Channel3", MT_Channel3), + luabind::value("Channel4", MT_Channel4), + luabind::value("Channel5", MT_Channel5), + luabind::value("Channel6", MT_Channel6), + luabind::value("Channel7", MT_Channel7), + luabind::value("Channel8", MT_Channel8), + luabind::value("Channel9", MT_Channel9), + luabind::value("Channel10", MT_Channel10), + luabind::value("CritMelee", MT_CritMelee), + luabind::value("SpellCrits", MT_SpellCrits), + luabind::value("TooFarAway", MT_TooFarAway), + luabind::value("NPCRampage", MT_NPCRampage), + luabind::value("NPCFlurry", MT_NPCFlurry), + luabind::value("NPCEnrage", MT_NPCEnrage), + luabind::value("SayEcho", MT_SayEcho), + luabind::value("TellEcho", MT_TellEcho), + luabind::value("GroupEcho", MT_GroupEcho), + luabind::value("GuildEcho", MT_GuildEcho), + luabind::value("OOCEcho", MT_OOCEcho), + luabind::value("AuctionEcho", MT_AuctionEcho), + luabind::value("ShoutECho", MT_ShoutECho), + luabind::value("EmoteEcho", MT_EmoteEcho), + luabind::value("Chat1Echo", MT_Chat1Echo), + luabind::value("Chat2Echo", MT_Chat2Echo), + luabind::value("Chat3Echo", MT_Chat3Echo), + luabind::value("Chat4Echo", MT_Chat4Echo), + luabind::value("Chat5Echo", MT_Chat5Echo), + luabind::value("Chat6Echo", MT_Chat6Echo), + luabind::value("Chat7Echo", MT_Chat7Echo), + luabind::value("Chat8Echo", MT_Chat8Echo), + luabind::value("Chat9Echo", MT_Chat9Echo), + luabind::value("Chat10Echo", MT_Chat10Echo), + luabind::value("DoTDamage", MT_DoTDamage), + luabind::value("ItemLink", MT_ItemLink), + luabind::value("RaidSay", MT_RaidSay), + luabind::value("MyPet", MT_MyPet), + luabind::value("DS", MT_DS), + luabind::value("Leadership", MT_Leadership), + luabind::value("PetFlurry", MT_PetFlurry), + luabind::value("PetCrit", MT_PetCrit), + luabind::value("FocusEffect", MT_FocusEffect), + luabind::value("Experience", MT_Experience), + luabind::value("System", MT_System), + luabind::value("PetSpell", MT_PetSpell), + luabind::value("PetResponse", MT_PetResponse), + luabind::value("ItemSpeech", MT_ItemSpeech), + luabind::value("StrikeThrough", MT_StrikeThrough), + luabind::value("Stun", MT_Stun) + ]; +} + +luabind::scope lua_register_rules_const() { + return luabind::class_("Rule") + .enum_("constants") + [ +#define RULE_INT(cat, rule, default_value) \ + luabind::value(#rule, RuleManager::Int__##rule), +#include "../common/ruletypes.h" + luabind::value("_IntRuleCount", RuleManager::_IntRuleCount), +#undef RULE_INT +#define RULE_REAL(cat, rule, default_value) \ + luabind::value(#rule, RuleManager::Real__##rule), +#include "../common/ruletypes.h" + luabind::value("_RealRuleCount", RuleManager::_RealRuleCount), +#undef RULE_REAL +#define RULE_BOOL(cat, rule, default_value) \ + luabind::value(#rule, RuleManager::Bool__##rule), +#include "../common/ruletypes.h" + luabind::value("_BoolRuleCount", RuleManager::_BoolRuleCount) + ]; +} + +luabind::scope lua_register_rulei() { + return luabind::namespace_("RuleI") + [ + luabind::def("Get", &get_rulei) + ]; +} + +luabind::scope lua_register_ruler() { + return luabind::namespace_("RuleR") + [ + luabind::def("Get", &get_ruler) + ]; +} + +luabind::scope lua_register_ruleb() { + return luabind::namespace_("RuleB") + [ + luabind::def("Get", &get_ruleb) + ]; +} + #endif diff --git a/zone/lua_general.h b/zone/lua_general.h index eb117cfc6..4c8b7f8d2 100644 --- a/zone/lua_general.h +++ b/zone/lua_general.h @@ -3,12 +3,22 @@ #ifdef LUA_EQEMU luabind::scope lua_register_general(); +luabind::scope lua_register_random(); luabind::scope lua_register_events(); luabind::scope lua_register_faction(); luabind::scope lua_register_slot(); luabind::scope lua_register_material(); luabind::scope lua_register_client_version(); luabind::scope lua_register_appearance(); +luabind::scope lua_register_classes(); +luabind::scope lua_register_skills(); +luabind::scope lua_register_bodytypes(); +luabind::scope lua_register_filters(); +luabind::scope lua_register_message_types(); +luabind::scope lua_register_rules_const(); +luabind::scope lua_register_rulei(); +luabind::scope lua_register_ruler(); +luabind::scope lua_register_ruleb(); #endif -#endif \ No newline at end of file +#endif diff --git a/zone/lua_mob.cpp b/zone/lua_mob.cpp index 190db1f18..18651edea 100644 --- a/zone/lua_mob.cpp +++ b/zone/lua_mob.cpp @@ -10,6 +10,7 @@ #include "lua_mob.h" #include "lua_hate_list.h" #include "lua_client.h" +#include "lua_stat_bonuses.h" struct SpecialAbilities { }; @@ -1725,6 +1726,18 @@ 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) +{ + Lua_Safe_Call_Int(); + return self->GetFcDamageAmtIncoming(caster, spell_id, use_skill, skill); +} + +int Lua_Mob::GetSkillDmgAmt(uint16 skill) +{ + Lua_Safe_Call_Int(); + return self->GetSkillDmgAmt(skill); +} + void Lua_Mob::SetAllowBeneficial(bool value) { Lua_Safe_Call_Void(); self->SetAllowBeneficial(value); @@ -1985,6 +1998,89 @@ int32 Lua_Mob::GetMeleeMitigation() { return self->GetMeleeMitigation(); } +int Lua_Mob::GetWeaponDamageBonus(Lua_Item weapon, bool offhand) { + Lua_Safe_Call_Int(); + return self->GetWeaponDamageBonus(weapon, offhand); +} + +Lua_StatBonuses Lua_Mob::GetItemBonuses() +{ + Lua_Safe_Call_Class(Lua_StatBonuses); + return self->GetItemBonusesPtr(); +} + +Lua_StatBonuses Lua_Mob::GetSpellBonuses() +{ + Lua_Safe_Call_Class(Lua_StatBonuses); + return self->GetSpellBonusesPtr(); +} + +Lua_StatBonuses Lua_Mob::GetAABonuses() +{ + Lua_Safe_Call_Class(Lua_StatBonuses); + return self->GetAABonusesPtr(); +} + +int16 Lua_Mob::GetMeleeDamageMod_SE(uint16 skill) +{ + Lua_Safe_Call_Int(); + return self->GetMeleeDamageMod_SE(skill); +} + +int16 Lua_Mob::GetMeleeMinDamageMod_SE(uint16 skill) +{ + Lua_Safe_Call_Int(); + return self->GetMeleeMinDamageMod_SE(skill); +} + +bool Lua_Mob::IsAttackAllowed(Lua_Mob target, bool isSpellAttack) { + Lua_Safe_Call_Bool(); + return self->IsAttackAllowed(target, isSpellAttack); +} + +bool Lua_Mob::IsCasting() { + Lua_Safe_Call_Bool(); + return self->IsCasting(); +} + +int Lua_Mob::AttackAnimation(int Hand, Lua_ItemInst weapon) { + Lua_Safe_Call_Int(); + return (int)self->AttackAnimation(Hand, weapon); +} + +int Lua_Mob::GetWeaponDamage(Lua_Mob against, Lua_ItemInst weapon) { + Lua_Safe_Call_Int(); + return self->GetWeaponDamage(against, weapon); +} + +bool Lua_Mob::IsBerserk() { + Lua_Safe_Call_Bool(); + return self->IsBerserk(); +} + +bool Lua_Mob::TryFinishingBlow(Lua_Mob defender, int &damage) { + Lua_Safe_Call_Bool(); + return self->TryFinishingBlow(defender, damage); +} + +int Lua_Mob::GetBodyType() +{ + Lua_Safe_Call_Int(); + return (int)self->GetBodyType(); +} + +int Lua_Mob::GetOrigBodyType() +{ + Lua_Safe_Call_Int(); + return (int)self->GetOrigBodyType(); +} + +void Lua_Mob::CheckNumHitsRemaining(int type, int32 buff_slot, uint16 spell_id) +{ + Lua_Safe_Call_Void(); + self->CheckNumHitsRemaining((NumHit)type, buff_slot, spell_id); +} + luabind::scope lua_register_mob() { return luabind::class_("Mob") .def(luabind::constructor<>()) @@ -2281,6 +2377,8 @@ luabind::scope lua_register_mob() { .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) @@ -2330,7 +2428,22 @@ luabind::scope lua_register_mob() { .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("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); } luabind::scope lua_register_special_abilities() { diff --git a/zone/lua_mob.h b/zone/lua_mob.h index 08502b1a9..c54deb6bc 100644 --- a/zone/lua_mob.h +++ b/zone/lua_mob.h @@ -8,6 +8,7 @@ class Mob; struct Lua_HateList; class Lua_Item; class Lua_ItemInst; +class Lua_StatBonuses; namespace luabind { struct scope; @@ -330,6 +331,8 @@ 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 GetSkillDmgAmt(uint16 skill); void SetAllowBeneficial(bool value); bool GetAllowBeneficial(); bool IsBeneficialAllowed(Lua_Mob target); @@ -340,7 +343,6 @@ public: void SetFlurryChance(int value); int GetFlurryChance(); int GetSkill(int skill_id); - void CalcBonuses(); int GetSpecialAbility(int ability); int GetSpecialAbilityParam(int ability, int param); void SetSpecialAbility(int ability, int level); @@ -381,6 +383,21 @@ public: bool IsSilenced(); bool IsAmnesiad(); int32 GetMeleeMitigation(); + int GetWeaponDamageBonus(Lua_Item weapon, bool offhand); + Lua_StatBonuses GetItemBonuses(); + Lua_StatBonuses GetSpellBonuses(); + Lua_StatBonuses GetAABonuses(); + int16 GetMeleeDamageMod_SE(uint16 skill); + int16 GetMeleeMinDamageMod_SE(uint16 skill); + bool IsAttackAllowed(Lua_Mob target, bool isSpellAttack); + bool IsCasting(); + int AttackAnimation(int Hand, Lua_ItemInst weapon); + int GetWeaponDamage(Lua_Mob against, Lua_ItemInst weapon); + bool IsBerserk(); + bool TryFinishingBlow(Lua_Mob defender, int &damage); + int GetBodyType(); + int GetOrigBodyType(); + void CheckNumHitsRemaining(int type, int32 buff_slot, uint16 spell_id); }; #endif diff --git a/zone/lua_mod.cpp b/zone/lua_mod.cpp new file mode 100644 index 000000000..a891fb064 --- /dev/null +++ b/zone/lua_mod.cpp @@ -0,0 +1,631 @@ +#include "lua.hpp" +#include +#include + +#include "../common/spdat.h" +#include "masterentity.h" +#include "questmgr.h" +#include "zone.h" +#include "zone_config.h" + +#include "lua_parser.h" +#include "lua_mod.h" +#include "lua_bit.h" +#include "lua_entity.h" +#include "lua_item.h" +#include "lua_iteminst.h" +#include "lua_mob.h" +#include "lua_hate_list.h" +#include "lua_client.h" +#include "lua_inventory.h" +#include "lua_npc.h" +#include "lua_spell.h" +#include "lua_entity_list.h" +#include "lua_group.h" +#include "lua_raid.h" +#include "lua_corpse.h" +#include "lua_object.h" +#include "lua_door.h" +#include "lua_spawn.h" +#include "lua_packet.h" +#include "lua_general.h" +#include "lua_encounter.h" +#include "lua_stat_bonuses.h" + +void LuaMod::Init() +{ + m_has_melee_mitigation = parser_->HasFunction("MeleeMitigation", package_name_); + m_has_apply_damage_table = parser_->HasFunction("ApplyDamageTable", package_name_); + m_has_avoid_damage = parser_->HasFunction("AvoidDamage", package_name_); + m_has_check_hit_chance = parser_->HasFunction("CheckHitChance", package_name_); + m_has_try_critical_hit = parser_->HasFunction("TryCriticalHit", package_name_); + m_has_get_required_aa_experience = parser_->HasFunction("GetRequiredAAExperience", package_name_); + m_has_get_exp_for_level = parser_->HasFunction("GetEXPForLevel", package_name_); + m_has_get_experience_for_kill = parser_->HasFunction("GetExperienceForKill", package_name_); + m_has_common_outgoing_hit_success = parser_->HasFunction("CommonOutgoingHitSuccess", package_name_); +} + +void PutDamageHitInfo(lua_State *L, luabind::adl::object &e, DamageHitInfo &hit) { + luabind::adl::object lua_hit = luabind::newtable(L); + lua_hit["base_damage"] = hit.base_damage; + lua_hit["min_damage"] = hit.min_damage; + lua_hit["damage_done"] = hit.damage_done; + lua_hit["offense"] = hit.offense; + lua_hit["tohit"] = hit.tohit; + lua_hit["hand"] = hit.hand; + lua_hit["skill"] = (int)hit.skill; + e["hit"] = lua_hit; +} + +void GetDamageHitInfo(luabind::adl::object &ret, DamageHitInfo &hit) { + auto luaHitTable = ret["hit"]; + if (luabind::type(luaHitTable) == LUA_TTABLE) { + auto base_damage = luaHitTable["base_damage"]; + auto min_damage = luaHitTable["min_damage"]; + auto damage_done = luaHitTable["damage_done"]; + auto offense = luaHitTable["offense"]; + auto tohit = luaHitTable["tohit"]; + auto hand = luaHitTable["hand"]; + auto skill = luaHitTable["skill"]; + + if (luabind::type(base_damage) == LUA_TNUMBER) { + hit.base_damage = luabind::object_cast(base_damage); + } + + if (luabind::type(min_damage) == LUA_TNUMBER) { + hit.min_damage = luabind::object_cast(min_damage); + } + + if (luabind::type(damage_done) == LUA_TNUMBER) { + hit.damage_done = luabind::object_cast(damage_done); + } + + if (luabind::type(offense) == LUA_TNUMBER) { + hit.offense = luabind::object_cast(offense); + } + + if (luabind::type(tohit) == LUA_TNUMBER) { + hit.tohit = luabind::object_cast(tohit); + } + + if (luabind::type(hand) == LUA_TNUMBER) { + hit.hand = luabind::object_cast(hand); + } + + if (luabind::type(skill) == LUA_TNUMBER) { + hit.skill = (EQEmu::skills::SkillType)luabind::object_cast(skill); + } + } +} + +void PutExtraAttackOptions(lua_State *L, luabind::adl::object &e, ExtraAttackOptions *opts) { + if (opts) { + luabind::adl::object lua_opts = luabind::newtable(L); + lua_opts["damage_percent"] = opts->damage_percent; + lua_opts["damage_flat"] = opts->damage_flat; + lua_opts["armor_pen_percent"] = opts->armor_pen_percent; + lua_opts["armor_pen_flat"] = opts->armor_pen_flat; + lua_opts["crit_percent"] = opts->crit_percent; + lua_opts["crit_flat"] = opts->crit_flat; + lua_opts["hate_percent"] = opts->hate_percent; + lua_opts["hate_flat"] = opts->hate_flat; + lua_opts["hit_chance"] = opts->hit_chance; + lua_opts["melee_damage_bonus_flat"] = opts->melee_damage_bonus_flat; + lua_opts["skilldmgtaken_bonus_flat"] = opts->skilldmgtaken_bonus_flat; + e["opts"] = lua_opts; + } +} + +void GetExtraAttackOptions(luabind::adl::object &ret, ExtraAttackOptions *opts) { + if (opts) { + auto luaOptsTable = ret["opts"]; + if (luabind::type(luaOptsTable) == LUA_TTABLE) { + auto damage_percent = luaOptsTable["damage_percent"]; + auto damage_flat = luaOptsTable["damage_flat"]; + auto armor_pen_percent = luaOptsTable["armor_pen_percent"]; + auto armor_pen_flat = luaOptsTable["armor_pen_flat"]; + auto crit_percent = luaOptsTable["crit_percent"]; + auto crit_flat = luaOptsTable["crit_flat"]; + auto hate_percent = luaOptsTable["hate_percent"]; + auto hate_flat = luaOptsTable["hate_flat"]; + auto hit_chance = luaOptsTable["hit_chance"]; + auto melee_damage_bonus_flat = luaOptsTable["melee_damage_bonus_flat"]; + auto skilldmgtaken_bonus_flat = luaOptsTable["skilldmgtaken_bonus_flat"]; + + if (luabind::type(damage_percent) == LUA_TNUMBER) { + opts->damage_percent = luabind::object_cast(damage_percent); + } + + if (luabind::type(damage_flat) == LUA_TNUMBER) { + opts->damage_flat = luabind::object_cast(damage_flat); + } + + if (luabind::type(armor_pen_percent) == LUA_TNUMBER) { + opts->armor_pen_percent = luabind::object_cast(armor_pen_percent); + } + + if (luabind::type(armor_pen_flat) == LUA_TNUMBER) { + opts->armor_pen_flat = luabind::object_cast(armor_pen_flat); + } + + if (luabind::type(crit_percent) == LUA_TNUMBER) { + opts->crit_percent = luabind::object_cast(crit_percent); + } + + if (luabind::type(crit_flat) == LUA_TNUMBER) { + opts->crit_flat = luabind::object_cast(crit_flat); + } + + if (luabind::type(hate_percent) == LUA_TNUMBER) { + opts->hate_percent = luabind::object_cast(hate_percent); + } + + if (luabind::type(hate_flat) == LUA_TNUMBER) { + opts->hate_flat = luabind::object_cast(hate_flat); + } + + if (luabind::type(hit_chance) == LUA_TNUMBER) { + opts->hit_chance = luabind::object_cast(hit_chance); + } + + if (luabind::type(melee_damage_bonus_flat) == LUA_TNUMBER) { + opts->melee_damage_bonus_flat = luabind::object_cast(melee_damage_bonus_flat); + } + + if (luabind::type(skilldmgtaken_bonus_flat) == LUA_TNUMBER) { + opts->skilldmgtaken_bonus_flat = luabind::object_cast(skilldmgtaken_bonus_flat); + } + } + } +} + +void LuaMod::MeleeMitigation(Mob *self, Mob *attacker, DamageHitInfo &hit, ExtraAttackOptions *opts, bool &ignoreDefault) { + int start = lua_gettop(L); + + try { + if (!m_has_melee_mitigation) { + return; + } + + lua_getfield(L, LUA_REGISTRYINDEX, package_name_.c_str()); + lua_getfield(L, -1, "MeleeMitigation"); + + Lua_Mob l_self(self); + Lua_Mob l_other(attacker); + luabind::adl::object e = luabind::newtable(L); + e["self"] = l_self; + e["other"] = l_other; + + PutDamageHitInfo(L, e, hit); + PutExtraAttackOptions(L, e, opts); + + e.push(L); + + if (lua_pcall(L, 1, 1, 0)) { + std::string error = lua_tostring(L, -1); + parser_->AddError(error); + lua_pop(L, 1); + return; + } + + if (lua_type(L, -1) == LUA_TTABLE) { + luabind::adl::object ret(luabind::from_stack(L, -1)); + auto IgnoreDefaultObj = ret["IgnoreDefault"]; + if (luabind::type(IgnoreDefaultObj) == LUA_TBOOLEAN) { + ignoreDefault = ignoreDefault || luabind::object_cast(IgnoreDefaultObj); + } + + GetDamageHitInfo(ret, hit); + GetExtraAttackOptions(ret, opts); + } + } + catch (std::exception &ex) { + parser_->AddError(ex.what()); + } + + int end = lua_gettop(L); + int n = end - start; + if (n > 0) { + lua_pop(L, n); + } +} + +void LuaMod::ApplyDamageTable(Mob *self, DamageHitInfo &hit, bool &ignoreDefault) { + int start = lua_gettop(L); + + try { + if (!m_has_apply_damage_table) { + return; + } + + lua_getfield(L, LUA_REGISTRYINDEX, package_name_.c_str()); + lua_getfield(L, -1, "ApplyDamageTable"); + + Lua_Mob l_self(self); + luabind::adl::object e = luabind::newtable(L); + e["self"] = l_self; + + PutDamageHitInfo(L, e, hit); + e.push(L); + + if (lua_pcall(L, 1, 1, 0)) { + std::string error = lua_tostring(L, -1); + parser_->AddError(error); + lua_pop(L, 1); + return; + } + + if (lua_type(L, -1) == LUA_TTABLE) { + luabind::adl::object ret(luabind::from_stack(L, -1)); + auto IgnoreDefaultObj = ret["IgnoreDefault"]; + if (luabind::type(IgnoreDefaultObj) == LUA_TBOOLEAN) { + ignoreDefault = ignoreDefault || luabind::object_cast(IgnoreDefaultObj); + } + + GetDamageHitInfo(ret, hit); + } + } + catch (std::exception &ex) { + parser_->AddError(ex.what()); + } + + int end = lua_gettop(L); + int n = end - start; + if (n > 0) { + lua_pop(L, n); + } +} + +void LuaMod::AvoidDamage(Mob *self, Mob *other, DamageHitInfo &hit, bool &returnValue, bool &ignoreDefault) { + int start = lua_gettop(L); + + try { + if (!m_has_avoid_damage) { + return; + } + + lua_getfield(L, LUA_REGISTRYINDEX, package_name_.c_str()); + lua_getfield(L, -1, "AvoidDamage"); + + Lua_Mob l_self(self); + Lua_Mob l_other(other); + luabind::adl::object e = luabind::newtable(L); + e["self"] = l_self; + e["other"] = l_other; + + PutDamageHitInfo(L, e, hit); + e.push(L); + + if (lua_pcall(L, 1, 1, 0)) { + std::string error = lua_tostring(L, -1); + parser_->AddError(error); + lua_pop(L, 1); + return; + } + + if (lua_type(L, -1) == LUA_TTABLE) { + luabind::adl::object ret(luabind::from_stack(L, -1)); + auto IgnoreDefaultObj = ret["IgnoreDefault"]; + if (luabind::type(IgnoreDefaultObj) == LUA_TBOOLEAN) { + ignoreDefault = ignoreDefault || luabind::object_cast(IgnoreDefaultObj); + } + + auto returnValueObj = ret["ReturnValue"]; + if (luabind::type(returnValueObj) == LUA_TBOOLEAN) { + returnValue = luabind::object_cast(returnValueObj); + } + + GetDamageHitInfo(ret, hit); + } + } + catch (std::exception &ex) { + parser_->AddError(ex.what()); + } + + int end = lua_gettop(L); + int n = end - start; + if (n > 0) { + lua_pop(L, n); + } +} + +void LuaMod::CheckHitChance(Mob *self, Mob* other, DamageHitInfo &hit, bool &returnValue, bool &ignoreDefault) { + int start = lua_gettop(L); + + try { + if (!m_has_check_hit_chance) { + return; + } + + lua_getfield(L, LUA_REGISTRYINDEX, package_name_.c_str()); + lua_getfield(L, -1, "CheckHitChance"); + + Lua_Mob l_self(self); + Lua_Mob l_other(other); + luabind::adl::object e = luabind::newtable(L); + e["self"] = l_self; + e["other"] = l_other; + + PutDamageHitInfo(L, e, hit); + e.push(L); + + if (lua_pcall(L, 1, 1, 0)) { + std::string error = lua_tostring(L, -1); + parser_->AddError(error); + lua_pop(L, 1); + return; + } + + if (lua_type(L, -1) == LUA_TTABLE) { + luabind::adl::object ret(luabind::from_stack(L, -1)); + auto IgnoreDefaultObj = ret["IgnoreDefault"]; + if (luabind::type(IgnoreDefaultObj) == LUA_TBOOLEAN) { + ignoreDefault = ignoreDefault || luabind::object_cast(IgnoreDefaultObj); + } + + auto returnValueObj = ret["ReturnValue"]; + if (luabind::type(returnValueObj) == LUA_TBOOLEAN) { + returnValue = luabind::object_cast(returnValueObj); + } + + GetDamageHitInfo(ret, hit); + } + } + catch (std::exception &ex) { + parser_->AddError(ex.what()); + } + + int end = lua_gettop(L); + int n = end - start; + if (n > 0) { + lua_pop(L, n); + } +} + +void LuaMod::CommonOutgoingHitSuccess(Mob *self, Mob *other, DamageHitInfo &hit, ExtraAttackOptions *opts, bool &ignoreDefault) +{ + int start = lua_gettop(L); + + try { + if (!m_has_common_outgoing_hit_success) { + return; + } + + lua_getfield(L, LUA_REGISTRYINDEX, package_name_.c_str()); + lua_getfield(L, -1, "CommonOutgoingHitSuccess"); + + Lua_Mob l_self(self); + Lua_Mob l_other(other); + luabind::adl::object e = luabind::newtable(L); + e["self"] = l_self; + e["other"] = l_other; + + PutDamageHitInfo(L, e, hit); + PutExtraAttackOptions(L, e, opts); + e.push(L); + + if (lua_pcall(L, 1, 1, 0)) { + std::string error = lua_tostring(L, -1); + parser_->AddError(error); + lua_pop(L, 1); + return; + } + + if (lua_type(L, -1) == LUA_TTABLE) { + luabind::adl::object ret(luabind::from_stack(L, -1)); + auto IgnoreDefaultObj = ret["IgnoreDefault"]; + if (luabind::type(IgnoreDefaultObj) == LUA_TBOOLEAN) { + ignoreDefault = ignoreDefault || luabind::object_cast(IgnoreDefaultObj); + } + + GetDamageHitInfo(ret, hit); + GetExtraAttackOptions(ret, opts); + } + } + catch (std::exception &ex) { + parser_->AddError(ex.what()); + } + + int end = lua_gettop(L); + int n = end - start; + if (n > 0) { + lua_pop(L, n); + } +} + +void LuaMod::TryCriticalHit(Mob *self, Mob *defender, DamageHitInfo &hit, ExtraAttackOptions *opts, bool &ignoreDefault) { + int start = lua_gettop(L); + + try { + if (!m_has_try_critical_hit) { + return; + } + + lua_getfield(L, LUA_REGISTRYINDEX, package_name_.c_str()); + lua_getfield(L, -1, "TryCriticalHit"); + + Lua_Mob l_self(self); + Lua_Mob l_other(defender); + luabind::adl::object e = luabind::newtable(L); + e["self"] = l_self; + e["other"] = l_other; + + PutDamageHitInfo(L, e, hit); + PutExtraAttackOptions(L, e, opts); + e.push(L); + + if (lua_pcall(L, 1, 1, 0)) { + std::string error = lua_tostring(L, -1); + parser_->AddError(error); + lua_pop(L, 1); + return; + } + + if (lua_type(L, -1) == LUA_TTABLE) { + luabind::adl::object ret(luabind::from_stack(L, -1)); + auto IgnoreDefaultObj = ret["IgnoreDefault"]; + if (luabind::type(IgnoreDefaultObj) == LUA_TBOOLEAN) { + ignoreDefault = ignoreDefault || luabind::object_cast(IgnoreDefaultObj); + } + + GetDamageHitInfo(ret, hit); + GetExtraAttackOptions(ret, opts); + } + } + catch (std::exception &ex) { + parser_->AddError(ex.what()); + } + + int end = lua_gettop(L); + int n = end - start; + if (n > 0) { + lua_pop(L, n); + } +} + +void LuaMod::GetRequiredAAExperience(Client *self, uint32 &returnValue, bool &ignoreDefault) +{ + int start = lua_gettop(L); + + try { + if (!m_has_get_required_aa_experience) { + return; + } + + lua_getfield(L, LUA_REGISTRYINDEX, package_name_.c_str()); + lua_getfield(L, -1, "GetRequiredAAExperience"); + + Lua_Client l_self(self); + luabind::adl::object e = luabind::newtable(L); + e["self"] = l_self; + e.push(L); + + if (lua_pcall(L, 1, 1, 0)) { + std::string error = lua_tostring(L, -1); + parser_->AddError(error); + lua_pop(L, 1); + return; + } + + if (lua_type(L, -1) == LUA_TTABLE) { + luabind::adl::object ret(luabind::from_stack(L, -1)); + auto IgnoreDefaultObj = ret["IgnoreDefault"]; + if (luabind::type(IgnoreDefaultObj) == LUA_TBOOLEAN) { + ignoreDefault = ignoreDefault || luabind::object_cast(IgnoreDefaultObj); + } + + auto returnValueObj = ret["ReturnValue"]; + if (luabind::type(returnValueObj) == LUA_TNUMBER) { + returnValue = luabind::object_cast(returnValueObj); + } + } + } + catch (std::exception &ex) { + parser_->AddError(ex.what()); + } + + int end = lua_gettop(L); + int n = end - start; + if (n > 0) { + lua_pop(L, n); + } +} + +void LuaMod::GetEXPForLevel(Client *self, uint16 level, uint32 &returnValue, bool &ignoreDefault) { + int start = lua_gettop(L); + + try { + if (!m_has_get_exp_for_level) { + return; + } + + lua_getfield(L, LUA_REGISTRYINDEX, package_name_.c_str()); + lua_getfield(L, -1, "GetEXPForLevel"); + + Lua_Client l_self(self); + luabind::adl::object e = luabind::newtable(L); + e["self"] = l_self; + e["level"] = level; + e.push(L); + + if (lua_pcall(L, 1, 1, 0)) { + std::string error = lua_tostring(L, -1); + parser_->AddError(error); + lua_pop(L, 1); + return; + } + + if (lua_type(L, -1) == LUA_TTABLE) { + luabind::adl::object ret(luabind::from_stack(L, -1)); + auto IgnoreDefaultObj = ret["IgnoreDefault"]; + if (luabind::type(IgnoreDefaultObj) == LUA_TBOOLEAN) { + ignoreDefault = ignoreDefault || luabind::object_cast(IgnoreDefaultObj); + } + + auto returnValueObj = ret["ReturnValue"]; + if (luabind::type(returnValueObj) == LUA_TNUMBER) { + returnValue = luabind::object_cast(returnValueObj); + } + } + } + catch (std::exception &ex) { + parser_->AddError(ex.what()); + } + + int end = lua_gettop(L); + int n = end - start; + if (n > 0) { + lua_pop(L, n); + } +} + +void LuaMod::GetExperienceForKill(Client *self, Mob *against, uint32 &returnValue, bool &ignoreDefault) +{ + int start = lua_gettop(L); + uint32 retval = 0; + + try { + if (!m_has_get_experience_for_kill) { + return; + } + + lua_getfield(L, LUA_REGISTRYINDEX, package_name_.c_str()); + lua_getfield(L, -1, "GetExperienceForKill"); + + Lua_Client l_self(self); + Lua_Mob l_other(against); + luabind::adl::object e = luabind::newtable(L); + e["self"] = l_self; + e["other"] = l_other; + e.push(L); + + if (lua_pcall(L, 1, 1, 0)) { + std::string error = lua_tostring(L, -1); + parser_->AddError(error); + lua_pop(L, 1); + return; + } + + if (lua_type(L, -1) == LUA_TTABLE) { + luabind::adl::object ret(luabind::from_stack(L, -1)); + auto IgnoreDefaultObj = ret["IgnoreDefault"]; + if (luabind::type(IgnoreDefaultObj) == LUA_TBOOLEAN) { + ignoreDefault = ignoreDefault || luabind::object_cast(IgnoreDefaultObj); + } + + auto returnValueObj = ret["ReturnValue"]; + if (luabind::type(returnValueObj) == LUA_TNUMBER) { + returnValue = luabind::object_cast(returnValueObj); + } + } + } + catch (std::exception &ex) { + parser_->AddError(ex.what()); + } + + int end = lua_gettop(L); + int n = end - start; + if (n > 0) { + lua_pop(L, n); + } +} diff --git a/zone/lua_mod.h b/zone/lua_mod.h new file mode 100644 index 000000000..defc5edab --- /dev/null +++ b/zone/lua_mod.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +struct lua_State; + +class LuaParser; +class LuaMod +{ +public: + LuaMod(lua_State *ls, LuaParser *lp, const std::string &package_name) { + L = ls; + parser_ = lp; + package_name_ = package_name; + Init(); + } + ~LuaMod() { } + void Init(); + + void MeleeMitigation(Mob *self, Mob *attacker, DamageHitInfo &hit, ExtraAttackOptions *opts, bool &ignoreDefault); + void ApplyDamageTable(Mob *self, DamageHitInfo &hit, bool &ignoreDefault); + void AvoidDamage(Mob *self, Mob *other, DamageHitInfo &hit, bool &returnValue, bool &ignoreDefault); + void CheckHitChance(Mob *self, Mob* other, DamageHitInfo &hit, bool &returnValue, bool &ignoreDefault); + void CommonOutgoingHitSuccess(Mob *self, Mob* other, DamageHitInfo &hit, ExtraAttackOptions *opts, bool &ignoreDefault); + void TryCriticalHit(Mob *self, Mob *defender, DamageHitInfo &hit, ExtraAttackOptions *opts, bool &ignoreDefault); + void GetRequiredAAExperience(Client *self, uint32 &returnValue, bool &ignoreDefault); + void GetEXPForLevel(Client *self, uint16 level, uint32 &returnValue, bool &ignoreDefault); + void GetExperienceForKill(Client *self, Mob *against, uint32 &returnValue, bool &ignoreDefault); +private: + LuaParser *parser_; + lua_State *L; + std::string package_name_; + + bool m_has_melee_mitigation; + bool m_has_apply_damage_table; + bool m_has_avoid_damage; + bool m_has_check_hit_chance; + bool m_has_common_outgoing_hit_success; + bool m_has_try_critical_hit; + bool m_has_get_required_aa_experience; + bool m_has_get_exp_for_level; + bool m_has_get_experience_for_kill; +}; diff --git a/zone/lua_npc.cpp b/zone/lua_npc.cpp index 9ed705496..50ff597f6 100644 --- a/zone/lua_npc.cpp +++ b/zone/lua_npc.cpp @@ -498,6 +498,17 @@ uint8 Lua_NPC::GetMerchantProbability() { return self->GetMerchantProbability(); } +int Lua_NPC::GetRawAC() { + Lua_Safe_Call_Int(); + return self->GetRawAC(); +} + +int Lua_NPC::GetAvoidanceRating() +{ + Lua_Safe_Call_Int(); + return self->GetAvoidanceRating(); +} + luabind::scope lua_register_npc() { return luabind::class_("NPC") .def(luabind::constructor<>()) @@ -598,7 +609,9 @@ luabind::scope lua_register_npc() { .def("MerchantOpenShop", (void(Lua_NPC::*)(void))&Lua_NPC::MerchantOpenShop) .def("MerchantCloseShop", (void(Lua_NPC::*)(void))&Lua_NPC::MerchantCloseShop) .def("SetMerchantProbability", (void(Lua_NPC::*)(void))&Lua_NPC::SetMerchantProbability) - .def("GetMerchantProbability", (uint8(Lua_NPC::*)(void))&Lua_NPC::GetMerchantProbability); + .def("GetMerchantProbability", (uint8(Lua_NPC::*)(void))&Lua_NPC::GetMerchantProbability) + .def("GetRawAC", (int(Lua_NPC::*)(void))&Lua_NPC::GetRawAC) + .def("GetAvoidanceRating", &Lua_NPC::GetAvoidanceRating); } #endif diff --git a/zone/lua_npc.h b/zone/lua_npc.h index e355c3e4d..d3d673641 100644 --- a/zone/lua_npc.h +++ b/zone/lua_npc.h @@ -125,6 +125,8 @@ public: void MerchantCloseShop(); void SetMerchantProbability(uint8 amt); uint8 GetMerchantProbability(); + int GetRawAC(); + int GetAvoidanceRating(); }; #endif diff --git a/zone/lua_parser.cpp b/zone/lua_parser.cpp index 92f0adbea..dda4b2d23 100644 --- a/zone/lua_parser.cpp +++ b/zone/lua_parser.cpp @@ -10,8 +10,13 @@ #include #include -#include "masterentity.h" #include "../common/spdat.h" +#include "masterentity.h" +#include "questmgr.h" +#include "zone.h" +#include "zone_config.h" + +#include "lua_parser.h" #include "lua_bit.h" #include "lua_entity.h" #include "lua_item.h" @@ -31,11 +36,8 @@ #include "lua_spawn.h" #include "lua_packet.h" #include "lua_general.h" -#include "questmgr.h" -#include "zone.h" -#include "zone_config.h" -#include "lua_parser.h" #include "lua_encounter.h" +#include "lua_stat_bonuses.h" const char *LuaEvents[_LargestEventID] = { "event_say", @@ -120,7 +122,8 @@ const char *LuaEvents[_LargestEventID] = { "event_unhandled_opcode", "event_tick", "event_spawn_zone", - "event_death_zone" + "event_death_zone", + "event_use_skill" }; extern Zone *zone; @@ -202,6 +205,7 @@ LuaParser::LuaParser() { PlayerArgumentDispatch[EVENT_LEAVE_AREA] = handle_player_area; PlayerArgumentDispatch[EVENT_RESPAWN] = handle_player_respawn; PlayerArgumentDispatch[EVENT_UNHANDLED_OPCODE] = handle_player_packet; + PlayerArgumentDispatch[EVENT_USE_SKILL] = handle_player_use_skill; ItemArgumentDispatch[EVENT_ITEM_CLICK] = handle_item_click; ItemArgumentDispatch[EVENT_ITEM_CLICK_CAST] = handle_item_click; @@ -797,12 +801,14 @@ void LuaParser::Init() { void LuaParser::ReloadQuests() { loaded_.clear(); errors_.clear(); + mods_.clear(); lua_encounter_events_registered.clear(); lua_encounters_loaded.clear(); for (auto encounter : lua_encounters) { encounter.second->Depop(); } + lua_encounters.clear(); // so the Depop function above depends on the Process being called again so ... // And there is situations where it wouldn't be :P @@ -815,6 +821,8 @@ void LuaParser::ReloadQuests() { L = luaL_newstate(); luaL_openlibs(L); + auto top = lua_gettop(L); + if(luaopen_bit(L) != 1) { std::string error = lua_tostring(L, -1); AddError(error); @@ -828,7 +836,7 @@ void LuaParser::ReloadQuests() { #ifdef SANITIZE_LUA_LIBS //io lua_pushnil(L); - lua_setglobal(L, "io"); + //lua_setglobal(L, "io"); //some os/debug are okay some are not lua_getglobal(L, "os"); @@ -931,24 +939,48 @@ void LuaParser::ReloadQuests() { std::string error = lua_tostring(L, -1); AddError(error); } - - return; } + else { + zone_script = Config->QuestDir; + zone_script += "/"; + zone_script += zone->GetShortName(); + zone_script += "/script_init.lua"; + f = fopen(zone_script.c_str(), "r"); + if (f) { + fclose(f); - zone_script = Config->QuestDir; - zone_script += "/"; - zone_script += zone->GetShortName(); - zone_script += "/script_init.lua"; - f = fopen(zone_script.c_str(), "r"); - if(f) { - fclose(f); - - if(luaL_dofile(L, zone_script.c_str())) { - std::string error = lua_tostring(L, -1); - AddError(error); + if (luaL_dofile(L, zone_script.c_str())) { + std::string error = lua_tostring(L, -1); + AddError(error); + } } } } + + FILE *load_order = fopen("mods/load_order.txt", "r"); + if (load_order) { + char file_name[256] = { 0 }; + while (fgets(file_name, 256, load_order) != nullptr) { + for (int i = 0; i < 256; ++i) { + auto c = file_name[i]; + if (c == '\n' || c == '\r' || c == ' ') { + file_name[i] = 0; + break; + } + } + + LoadScript("mods/" + std::string(file_name), file_name); + mods_.push_back(LuaMod(L, this, file_name)); + } + + fclose(load_order); + } + + auto end = lua_gettop(L); + int n = end - top; + if (n > 0) { + lua_pop(L, n); + } } void LuaParser::LoadScript(std::string filename, std::string package_name) { @@ -957,6 +989,7 @@ void LuaParser::LoadScript(std::string filename, std::string package_name) { return; } + auto top = lua_gettop(L); if(luaL_loadfile(L, filename.c_str())) { std::string error = lua_tostring(L, -1); AddError(error); @@ -984,14 +1017,20 @@ void LuaParser::LoadScript(std::string filename, std::string package_name) { std::string error = lua_tostring(L, -1); AddError(error); lua_pop(L, 1); - return; + } + else { + loaded_[package_name] = true; } - loaded_[package_name] = true; + auto end = lua_gettop(L); + int n = end - top; + if (n > 0) { + lua_pop(L, n); + } } bool LuaParser::HasFunction(std::string subname, std::string package_name) { - std::transform(subname.begin(), subname.end(), subname.begin(), ::tolower); + //std::transform(subname.begin(), subname.end(), subname.begin(), ::tolower); auto iter = loaded_.find(package_name); if(iter == loaded_.end()) { @@ -1018,12 +1057,18 @@ void LuaParser::MapFunctions(lua_State *L) { luabind::module(L) [ lua_register_general(), + lua_register_random(), lua_register_events(), lua_register_faction(), lua_register_slot(), lua_register_material(), lua_register_client_version(), lua_register_appearance(), + lua_register_classes(), + lua_register_skills(), + lua_register_bodytypes(), + lua_register_filters(), + lua_register_message_types(), lua_register_entity(), lua_register_encounter(), lua_register_mob(), @@ -1052,7 +1097,12 @@ void LuaParser::MapFunctions(lua_State *L) { lua_register_door(), lua_register_object(), lua_register_packet(), - lua_register_packet_opcodes() + lua_register_packet_opcodes(), + lua_register_stat_bonuses(), + lua_register_rules_const(), + lua_register_rulei(), + lua_register_ruler(), + lua_register_ruleb() ]; } catch(std::exception &ex) { @@ -1249,3 +1299,76 @@ QuestEventID LuaParser::ConvertLuaEvent(QuestEventID evt) { } #endif + +void LuaParser::MeleeMitigation(Mob *self, Mob *attacker, DamageHitInfo &hit, ExtraAttackOptions *opts, bool &ignoreDefault) +{ + for (auto &mod : mods_) { + mod.MeleeMitigation(self, attacker, hit, opts, ignoreDefault); + } +} + +void LuaParser::ApplyDamageTable(Mob *self, DamageHitInfo &hit, bool &ignoreDefault) +{ + for (auto &mod : mods_) { + mod.ApplyDamageTable(self, hit, ignoreDefault); + } +} + +bool LuaParser::AvoidDamage(Mob *self, Mob *other, DamageHitInfo &hit, bool & ignoreDefault) +{ + bool retval = false; + for (auto &mod : mods_) { + mod.AvoidDamage(self, other, hit, retval, ignoreDefault); + } + return retval; +} + +bool LuaParser::CheckHitChance(Mob *self, Mob *other, DamageHitInfo &hit, bool &ignoreDefault) +{ + bool retval = false; + for (auto &mod : mods_) { + mod.CheckHitChance(self, other, hit, retval, ignoreDefault); + } + return retval; +} + +void LuaParser::TryCriticalHit(Mob *self, Mob *defender, DamageHitInfo &hit, ExtraAttackOptions *opts, bool &ignoreDefault) +{ + for (auto &mod : mods_) { + mod.TryCriticalHit(self, defender, hit, opts, ignoreDefault); + } +} + +void LuaParser::CommonOutgoingHitSuccess(Mob *self, Mob *other, DamageHitInfo &hit, ExtraAttackOptions *opts, bool &ignoreDefault) +{ + for (auto &mod : mods_) { + mod.CommonOutgoingHitSuccess(self, other, hit, opts, ignoreDefault); + } +} + +uint32 LuaParser::GetRequiredAAExperience(Client *self, bool &ignoreDefault) +{ + uint32 retval = 0; + for (auto &mod : mods_) { + mod.GetRequiredAAExperience(self, retval, ignoreDefault); + } + return retval; +} + +uint32 LuaParser::GetEXPForLevel(Client *self, uint16 level, bool &ignoreDefault) +{ + uint32 retval = 0; + for (auto &mod : mods_) { + mod.GetEXPForLevel(self, level, retval, ignoreDefault); + } + return retval; +} + +uint32 LuaParser::GetExperienceForKill(Client *self, Mob *against, bool &ignoreDefault) +{ + uint32 retval = 0; + for (auto &mod : mods_) { + mod.GetExperienceForKill(self, against, retval, ignoreDefault); + } + return retval; +} diff --git a/zone/lua_parser.h b/zone/lua_parser.h index 003c936ce..eb57faa96 100644 --- a/zone/lua_parser.h +++ b/zone/lua_parser.h @@ -7,8 +7,10 @@ #include #include #include +#include #include "zone_config.h" +#include "lua_mod.h" extern const ZoneConfig *Config; @@ -32,7 +34,6 @@ namespace luabind { class LuaParser : public QuestInterface { public: - LuaParser(); ~LuaParser(); virtual int EventNPC(QuestEventID evt, NPC* npc, Mob *init, std::string data, uint32 extra_data, @@ -81,7 +82,29 @@ public: virtual int DispatchEventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, uint32 extra_data, std::vector *extra_pointers); + static LuaParser* Instance() { + static LuaParser inst; + return &inst; + } + + bool HasFunction(std::string function, std::string package_name); + + //Mod Extensions + void MeleeMitigation(Mob *self, Mob *attacker, DamageHitInfo &hit, ExtraAttackOptions *opts, bool &ignoreDefault); + void ApplyDamageTable(Mob *self, DamageHitInfo &hit, bool &ignoreDefault); + bool AvoidDamage(Mob *self, Mob *other, DamageHitInfo &hit, bool &ignoreDefault); + bool CheckHitChance(Mob *self, Mob* other, DamageHitInfo &hit, bool &ignoreDefault); + void TryCriticalHit(Mob *self, Mob *defender, DamageHitInfo &hit, ExtraAttackOptions *opts, bool &ignoreDefault); + void CommonOutgoingHitSuccess(Mob *self, Mob* other, DamageHitInfo &hit, ExtraAttackOptions *opts, bool &ignoreDefault); + uint32 GetRequiredAAExperience(Client *self, bool &ignoreDefault); + uint32 GetEXPForLevel(Client *self, uint16 level, bool &ignoreDefault); + uint32 GetExperienceForKill(Client *self, Mob *against, bool &ignoreDefault); + private: + LuaParser(); + LuaParser(const LuaParser&); + LuaParser& operator=(const LuaParser&); + int _EventNPC(std::string package_name, QuestEventID evt, NPC* npc, Mob *init, std::string data, uint32 extra_data, std::vector *extra_pointers, luabind::adl::object *l_func = nullptr); int _EventPlayer(std::string package_name, QuestEventID evt, Client *client, std::string data, uint32 extra_data, @@ -94,13 +117,12 @@ private: std::vector *extra_pointers); void LoadScript(std::string filename, std::string package_name); - bool HasFunction(std::string function, std::string package_name); - void ClearStates(); void MapFunctions(lua_State *L); QuestEventID ConvertLuaEvent(QuestEventID evt); std::map vars_; std::map loaded_; + std::vector mods_; lua_State *L; NPCArgumentHandler NPCArgumentDispatch[_LargestEventID]; diff --git a/zone/lua_parser_events.cpp b/zone/lua_parser_events.cpp index f01af5a6d..e70c5de9f 100644 --- a/zone/lua_parser_events.cpp +++ b/zone/lua_parser_events.cpp @@ -505,6 +505,15 @@ 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) { + Seperator sep(data.c_str()); + lua_pushinteger(L, std::stoi(sep.arg[0])); + lua_setfield(L, -2, "skill_id"); + + lua_pushinteger(L, std::stoi(sep.arg[1])); + lua_setfield(L, -2, "skill_level"); +} + //Item void handle_item_click(QuestInterface *parse, lua_State* L, Client* client, EQEmu::ItemInstance* item, Mob *mob, 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 42315ed0b..44ba9b72f 100644 --- a/zone/lua_parser_events.h +++ b/zone/lua_parser_events.h @@ -95,6 +95,8 @@ void handle_player_packet(QuestInterface *parse, lua_State* L, Client* client, s std::vector *extra_pointers); void handle_player_null(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, 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); //Item void handle_item_click(QuestInterface *parse, lua_State* L, Client* client, EQEmu::ItemInstance* item, Mob *mob, std::string data, uint32 extra_data, diff --git a/zone/lua_ptr.h b/zone/lua_ptr.h index 11478f093..d337059ae 100644 --- a/zone/lua_ptr.h +++ b/zone/lua_ptr.h @@ -2,13 +2,21 @@ #define EQEMU_LUA_PTR_H #ifdef LUA_EQEMU -//TODO: Remove the error checking by a flag since this adds significant overhead to each c call +#ifndef EQEMU_UNSAFE_LUA #define Lua_Safe_Call_Void() if(!d_) { return; } NativeType *self = reinterpret_cast(d_) #define Lua_Safe_Call_Bool() if(!d_) { return false; } NativeType *self = reinterpret_cast(d_) #define Lua_Safe_Call_Int() if(!d_) { return 0; } NativeType *self = reinterpret_cast(d_) #define Lua_Safe_Call_Real() if(!d_) { return 0.0; } NativeType *self = reinterpret_cast(d_) #define Lua_Safe_Call_String() if(!d_) { return ""; } NativeType *self = reinterpret_cast(d_) #define Lua_Safe_Call_Class(type) if(!d_) { return type(); } NativeType *self = reinterpret_cast(d_) +#else +#define Lua_Safe_Call_Void() NativeType *self = reinterpret_cast(d_) +#define Lua_Safe_Call_Bool() NativeType *self = reinterpret_cast(d_) +#define Lua_Safe_Call_Int() NativeType *self = reinterpret_cast(d_) +#define Lua_Safe_Call_Real() NativeType *self = reinterpret_cast(d_) +#define Lua_Safe_Call_String() NativeType *self = reinterpret_cast(d_) +#define Lua_Safe_Call_Class(type) NativeType *self = reinterpret_cast(d_) +#endif template class Lua_Ptr diff --git a/zone/lua_stat_bonuses.cpp b/zone/lua_stat_bonuses.cpp new file mode 100644 index 000000000..e1fb329cc --- /dev/null +++ b/zone/lua_stat_bonuses.cpp @@ -0,0 +1,1539 @@ +#include "lua.hpp" +#include + +#include "lua_stat_bonuses.h" + +int32 Lua_StatBonuses::GetAC() const { + Lua_Safe_Call_Int(); + return self->AC; +} + +int32 Lua_StatBonuses::GetHP() const { + Lua_Safe_Call_Int(); + return self->HP; +} + +int32 Lua_StatBonuses::GetHPRegen() const { + Lua_Safe_Call_Int(); + return self->HPRegen; +} + +int32 Lua_StatBonuses::GetMaxHP() const { + Lua_Safe_Call_Int(); + return self->MaxHP; +} + +int32 Lua_StatBonuses::GetManaRegen() const { + Lua_Safe_Call_Int(); + return self->ManaRegen; +} + +int32 Lua_StatBonuses::GetEnduranceRegen() const { + Lua_Safe_Call_Int(); + return self->EnduranceRegen; +} + +int32 Lua_StatBonuses::GetMana() const { + Lua_Safe_Call_Int(); + return self->Mana; +} + +int32 Lua_StatBonuses::GetEndurance() const { + Lua_Safe_Call_Int(); + return self->Endurance; +} + +int32 Lua_StatBonuses::GetATK() const { + Lua_Safe_Call_Int(); + return self->ATK; +} + +int32 Lua_StatBonuses::GetSTR() const { + Lua_Safe_Call_Int(); + return self->STR; +} + +int32 Lua_StatBonuses::GetSTRCapMod() const { + Lua_Safe_Call_Int(); + return self->STRCapMod; +} + +int32 Lua_StatBonuses::GetHeroicSTR() const { + Lua_Safe_Call_Int(); + return self->HeroicSTR; +} + +int32 Lua_StatBonuses::GetSTA() const { + Lua_Safe_Call_Int(); + return self->STA; +} + +int32 Lua_StatBonuses::GetSTACapMod() const { + Lua_Safe_Call_Int(); + return self->STACapMod; +} + +int32 Lua_StatBonuses::GetHeroicSTA() const { + Lua_Safe_Call_Int(); + return self->HeroicSTA; +} + +int32 Lua_StatBonuses::GetDEX() const { + Lua_Safe_Call_Int(); + return self->DEX; +} + +int32 Lua_StatBonuses::GetDEXCapMod() const { + Lua_Safe_Call_Int(); + return self->DEXCapMod; +} + +int32 Lua_StatBonuses::GetHeroicDEX() const { + Lua_Safe_Call_Int(); + return self->HeroicDEX; +} + +int32 Lua_StatBonuses::GetAGI() const { + Lua_Safe_Call_Int(); + return self->AGI; +} + +int32 Lua_StatBonuses::GetAGICapMod() const { + Lua_Safe_Call_Int(); + return self->AGICapMod; +} + +int32 Lua_StatBonuses::GetHeroicAGI() const { + Lua_Safe_Call_Int(); + return self->HeroicAGI; +} + +int32 Lua_StatBonuses::GetINT() const { + Lua_Safe_Call_Int(); + return self->INT; +} + +int32 Lua_StatBonuses::GetINTCapMod() const { + Lua_Safe_Call_Int(); + return self->INTCapMod; +} + +int32 Lua_StatBonuses::GetHeroicINT() const { + Lua_Safe_Call_Int(); + return self->HeroicINT; +} + +int32 Lua_StatBonuses::GetWIS() const { + Lua_Safe_Call_Int(); + return self->WIS; +} + +int32 Lua_StatBonuses::GetWISCapMod() const { + Lua_Safe_Call_Int(); + return self->WISCapMod; +} + +int32 Lua_StatBonuses::GetHeroicWIS() const { + Lua_Safe_Call_Int(); + return self->HeroicWIS; +} + +int32 Lua_StatBonuses::GetCHA() const { + Lua_Safe_Call_Int(); + return self->CHA; +} + +int32 Lua_StatBonuses::GetCHACapMod() const { + Lua_Safe_Call_Int(); + return self->CHACapMod; +} + +int32 Lua_StatBonuses::GetHeroicCHA() const { + Lua_Safe_Call_Int(); + return self->HeroicCHA; +} + +int32 Lua_StatBonuses::GetMR() const { + Lua_Safe_Call_Int(); + return self->MR; +} + +int32 Lua_StatBonuses::GetMRCapMod() const { + Lua_Safe_Call_Int(); + return self->MRCapMod; +} + +int32 Lua_StatBonuses::GetHeroicMR() const { + Lua_Safe_Call_Int(); + return self->HeroicMR; +} + +int32 Lua_StatBonuses::GetFR() const { + Lua_Safe_Call_Int(); + return self->FR; +} + +int32 Lua_StatBonuses::GetFRCapMod() const { + Lua_Safe_Call_Int(); + return self->FRCapMod; +} + +int32 Lua_StatBonuses::GetHeroicFR() const { + Lua_Safe_Call_Int(); + return self->HeroicFR; +} + +int32 Lua_StatBonuses::GetCR() const { + Lua_Safe_Call_Int(); + return self->CR; +} + +int32 Lua_StatBonuses::GetCRCapMod() const { + Lua_Safe_Call_Int(); + return self->CRCapMod; +} + +int32 Lua_StatBonuses::GetHeroicCR() const { + Lua_Safe_Call_Int(); + return self->HeroicCR; +} + +int32 Lua_StatBonuses::GetPR() const { + Lua_Safe_Call_Int(); + return self->PR; +} + +int32 Lua_StatBonuses::GetPRCapMod() const { + Lua_Safe_Call_Int(); + return self->PRCapMod; +} + +int32 Lua_StatBonuses::GetHeroicPR() const { + Lua_Safe_Call_Int(); + return self->HeroicPR; +} + +int32 Lua_StatBonuses::GetDR() const { + Lua_Safe_Call_Int(); + return self->DR; +} + +int32 Lua_StatBonuses::GetDRCapMod() const { + Lua_Safe_Call_Int(); + return self->DRCapMod; +} + +int32 Lua_StatBonuses::GetHeroicDR() const { + Lua_Safe_Call_Int(); + return self->HeroicDR; +} + +int32 Lua_StatBonuses::GetCorrup() const { + Lua_Safe_Call_Int(); + return self->Corrup; +} + +int32 Lua_StatBonuses::GetCorrupCapMod() const { + Lua_Safe_Call_Int(); + return self->CorrupCapMod; +} + +int32 Lua_StatBonuses::GetHeroicCorrup() const { + Lua_Safe_Call_Int(); + return self->HeroicCorrup; +} + +uint16 Lua_StatBonuses::GetDamageShieldSpellID() const { + Lua_Safe_Call_Int(); + return self->DamageShieldSpellID; +} + +int Lua_StatBonuses::GetDamageShield() const { + Lua_Safe_Call_Int(); + return self->DamageShield; +} + +int Lua_StatBonuses::GetDamageShieldType() const { + Lua_Safe_Call_Int(); + return self->DamageShieldType; +} + +int Lua_StatBonuses::GetSpellDamageShield() const { + Lua_Safe_Call_Int(); + return self->SpellDamageShield; +} + +int Lua_StatBonuses::GetSpellShield() const { + Lua_Safe_Call_Int(); + return self->SpellShield; +} + +int Lua_StatBonuses::GetReverseDamageShield() const { + Lua_Safe_Call_Int(); + return self->ReverseDamageShield; +} + +uint16 Lua_StatBonuses::GetReverseDamageShieldSpellID() const { + Lua_Safe_Call_Int(); + return self->ReverseDamageShieldSpellID; +} + +int Lua_StatBonuses::GetReverseDamageShieldType() const { + Lua_Safe_Call_Int(); + return self->ReverseDamageShieldType; +} + +int Lua_StatBonuses::Getmovementspeed() const { + Lua_Safe_Call_Int(); + return self->movementspeed; +} + +int32 Lua_StatBonuses::Gethaste() const { + Lua_Safe_Call_Int(); + return self->haste; +} + +int32 Lua_StatBonuses::Gethastetype2() const { + Lua_Safe_Call_Int(); + return self->hastetype2; +} + +int32 Lua_StatBonuses::Gethastetype3() const { + Lua_Safe_Call_Int(); + return self->hastetype3; +} + +int32 Lua_StatBonuses::Getinhibitmelee() const { + Lua_Safe_Call_Int(); + return self->inhibitmelee; +} + +float Lua_StatBonuses::GetAggroRange() const { + Lua_Safe_Call_Real(); + return self->AggroRange; +} + +float Lua_StatBonuses::GetAssistRange() const { + Lua_Safe_Call_Real(); + return self->AssistRange; +} + +int32 Lua_StatBonuses::Getskillmod(int idx) const { + Lua_Safe_Call_Int(); + return self->skillmod[idx]; +} + +int32 Lua_StatBonuses::Getskillmodmax(int idx) const { + Lua_Safe_Call_Int(); + return self->skillmodmax[idx]; +} + +int Lua_StatBonuses::Geteffective_casting_level() const { + Lua_Safe_Call_Int(); + return self->effective_casting_level; +} + +int Lua_StatBonuses::Getreflect_chance() const { + Lua_Safe_Call_Int(); + return self->reflect_chance; +} + +uint32 Lua_StatBonuses::GetsingingMod() const { + Lua_Safe_Call_Int(); + return self->singingMod; +} + +uint32 Lua_StatBonuses::GetAmplification() const { + Lua_Safe_Call_Int(); + return self->Amplification; +} + +uint32 Lua_StatBonuses::GetbrassMod() const { + Lua_Safe_Call_Int(); + return self->brassMod; +} + +uint32 Lua_StatBonuses::GetpercussionMod() const { + Lua_Safe_Call_Int(); + return self->percussionMod; +} + +uint32 Lua_StatBonuses::GetwindMod() const { + Lua_Safe_Call_Int(); + return self->windMod; +} + +uint32 Lua_StatBonuses::GetstringedMod() const { + Lua_Safe_Call_Int(); + return self->stringedMod; +} + +uint32 Lua_StatBonuses::GetsongModCap() const { + Lua_Safe_Call_Int(); + return self->songModCap; +} + +int8 Lua_StatBonuses::Gethatemod() const { + Lua_Safe_Call_Int(); + return self->hatemod; +} + +int32 Lua_StatBonuses::GetEnduranceReduction() const { + Lua_Safe_Call_Int(); + return self->EnduranceReduction; +} + +int32 Lua_StatBonuses::GetStrikeThrough() const { + Lua_Safe_Call_Int(); + return self->StrikeThrough; +} + +int32 Lua_StatBonuses::GetMeleeMitigation() const { + Lua_Safe_Call_Int(); + return self->MeleeMitigation; +} + +int32 Lua_StatBonuses::GetMeleeMitigationEffect() const { + Lua_Safe_Call_Int(); + return self->MeleeMitigationEffect; +} + +int32 Lua_StatBonuses::GetCriticalHitChance(int idx) const { + Lua_Safe_Call_Int(); + return self->CriticalHitChance[idx]; +} + +int32 Lua_StatBonuses::GetCriticalSpellChance() const { + Lua_Safe_Call_Int(); + return self->CriticalSpellChance; +} + +int32 Lua_StatBonuses::GetSpellCritDmgIncrease() const { + Lua_Safe_Call_Int(); + return self->SpellCritDmgIncrease; +} + +int32 Lua_StatBonuses::GetSpellCritDmgIncNoStack() const { + Lua_Safe_Call_Int(); + return self->SpellCritDmgIncNoStack; +} + +int32 Lua_StatBonuses::GetDotCritDmgIncrease() const { + Lua_Safe_Call_Int(); + return self->DotCritDmgIncrease; +} + +int32 Lua_StatBonuses::GetCriticalHealChance() const { + Lua_Safe_Call_Int(); + return self->CriticalHealChance; +} + +int32 Lua_StatBonuses::GetCriticalHealOverTime() const { + Lua_Safe_Call_Int(); + return self->CriticalHealOverTime; +} + +int32 Lua_StatBonuses::GetCriticalDoTChance() const { + Lua_Safe_Call_Int(); + return self->CriticalDoTChance; +} + +int32 Lua_StatBonuses::GetCrippBlowChance() const { + Lua_Safe_Call_Int(); + return self->CrippBlowChance; +} + +int32 Lua_StatBonuses::GetAvoidMeleeChance() const { + Lua_Safe_Call_Int(); + return self->AvoidMeleeChance; +} + +int32 Lua_StatBonuses::GetAvoidMeleeChanceEffect() const { + Lua_Safe_Call_Int(); + return self->AvoidMeleeChanceEffect; +} + +int32 Lua_StatBonuses::GetRiposteChance() const { + Lua_Safe_Call_Int(); + return self->RiposteChance; +} + +int32 Lua_StatBonuses::GetDodgeChance() const { + Lua_Safe_Call_Int(); + return self->DodgeChance; +} + +int32 Lua_StatBonuses::GetParryChance() const { + Lua_Safe_Call_Int(); + return self->ParryChance; +} + +int32 Lua_StatBonuses::GetDualWieldChance() const { + Lua_Safe_Call_Int(); + return self->DualWieldChance; +} + +int32 Lua_StatBonuses::GetDoubleAttackChance() const { + Lua_Safe_Call_Int(); + return self->DoubleAttackChance; +} + +int32 Lua_StatBonuses::GetTripleAttackChance() const { + Lua_Safe_Call_Int(); + return self->TripleAttackChance; +} + +int32 Lua_StatBonuses::GetDoubleRangedAttack() const { + Lua_Safe_Call_Int(); + return self->DoubleRangedAttack; +} + +int32 Lua_StatBonuses::GetResistSpellChance() const { + Lua_Safe_Call_Int(); + return self->ResistSpellChance; +} + +int32 Lua_StatBonuses::GetResistFearChance() const { + Lua_Safe_Call_Int(); + return self->ResistFearChance; +} + +bool Lua_StatBonuses::GetFearless() const { + Lua_Safe_Call_Bool(); + return self->Fearless; +} + +bool Lua_StatBonuses::GetIsFeared() const { + Lua_Safe_Call_Bool(); + return self->IsFeared; +} + +bool Lua_StatBonuses::GetIsBlind() const { + Lua_Safe_Call_Bool(); + return self->IsBlind; +} + +int32 Lua_StatBonuses::GetStunResist() const { + Lua_Safe_Call_Int(); + return self->StunResist; +} + +int32 Lua_StatBonuses::GetMeleeSkillCheck() const { + Lua_Safe_Call_Int(); + return self->MeleeSkillCheck; +} + +uint8 Lua_StatBonuses::GetMeleeSkillCheckSkill() const { + Lua_Safe_Call_Int(); + return self->MeleeSkillCheckSkill; +} + +int32 Lua_StatBonuses::GetHitChance() const { + Lua_Safe_Call_Int(); + return self->HitChance; +} + +int32 Lua_StatBonuses::GetHitChanceEffect(int idx) const { + Lua_Safe_Call_Int(); + return self->HitChanceEffect[idx]; +} + +int32 Lua_StatBonuses::GetDamageModifier(int idx) const { + Lua_Safe_Call_Int(); + return self->DamageModifier[idx]; +} + +int32 Lua_StatBonuses::GetDamageModifier2(int idx) const { + Lua_Safe_Call_Int(); + return self->DamageModifier2[idx]; +} + +int32 Lua_StatBonuses::GetMinDamageModifier(int idx) const { + Lua_Safe_Call_Int(); + return self->MinDamageModifier[idx]; +} + +int32 Lua_StatBonuses::GetProcChance() const { + Lua_Safe_Call_Int(); + return self->ProcChance; +} + +int32 Lua_StatBonuses::GetProcChanceSPA() const { + Lua_Safe_Call_Int(); + return self->ProcChanceSPA; +} + +int32 Lua_StatBonuses::GetExtraAttackChance() const { + Lua_Safe_Call_Int(); + return self->ExtraAttackChance; +} + +int32 Lua_StatBonuses::GetDoTShielding() const { + Lua_Safe_Call_Int(); + return self->DoTShielding; +} + +int32 Lua_StatBonuses::GetFlurryChance() const { + Lua_Safe_Call_Int(); + return self->FlurryChance; +} + +int32 Lua_StatBonuses::GetHundredHands() const { + Lua_Safe_Call_Int(); + return self->HundredHands; +} + +int32 Lua_StatBonuses::GetMeleeLifetap() const { + Lua_Safe_Call_Int(); + return self->MeleeLifetap; +} + +int32 Lua_StatBonuses::GetVampirism() const { + Lua_Safe_Call_Int(); + return self->Vampirism; +} + +int32 Lua_StatBonuses::GetHealRate() const { + Lua_Safe_Call_Int(); + return self->HealRate; +} + +int32 Lua_StatBonuses::GetMaxHPChange() const { + Lua_Safe_Call_Int(); + return self->MaxHPChange; +} + +int32 Lua_StatBonuses::GetHealAmt() const { + Lua_Safe_Call_Int(); + return self->HealAmt; +} + +int32 Lua_StatBonuses::GetSpellDmg() const { + Lua_Safe_Call_Int(); + return self->SpellDmg; +} + +int32 Lua_StatBonuses::GetClairvoyance() const { + Lua_Safe_Call_Int(); + return self->Clairvoyance; +} + +int32 Lua_StatBonuses::GetDSMitigation() const { + Lua_Safe_Call_Int(); + return self->DSMitigation; +} + +int32 Lua_StatBonuses::GetDSMitigationOffHand() const { + Lua_Safe_Call_Int(); + return self->DSMitigationOffHand; +} + +int32 Lua_StatBonuses::GetTwoHandBluntBlock() const { + Lua_Safe_Call_Int(); + return self->TwoHandBluntBlock; +} + +uint32 Lua_StatBonuses::GetItemManaRegenCap() const { + Lua_Safe_Call_Int(); + return self->ItemManaRegenCap; +} + +int32 Lua_StatBonuses::GetGravityEffect() const { + Lua_Safe_Call_Int(); + return self->GravityEffect; +} + +bool Lua_StatBonuses::GetAntiGate() const { + Lua_Safe_Call_Bool(); + return self->AntiGate; +} + +bool Lua_StatBonuses::GetMagicWeapon() const { + Lua_Safe_Call_Bool(); + return self->MagicWeapon; +} + +int32 Lua_StatBonuses::GetIncreaseBlockChance() const { + Lua_Safe_Call_Int(); + return self->IncreaseBlockChance; +} + +uint32 Lua_StatBonuses::GetPersistantCasting() const { + Lua_Safe_Call_Int(); + return self->PersistantCasting; +} + +int Lua_StatBonuses::GetXPRateMod() const { + Lua_Safe_Call_Int(); + return self->XPRateMod; +} + +bool Lua_StatBonuses::GetBlockNextSpell() const { + Lua_Safe_Call_Bool(); + return self->BlockNextSpell; +} + +bool Lua_StatBonuses::GetImmuneToFlee() const { + Lua_Safe_Call_Bool(); + return self->ImmuneToFlee; +} + +uint32 Lua_StatBonuses::GetVoiceGraft() const { + Lua_Safe_Call_Int(); + return self->VoiceGraft; +} + +int32 Lua_StatBonuses::GetSpellProcChance() const { + Lua_Safe_Call_Int(); + return self->SpellProcChance; +} + +int32 Lua_StatBonuses::GetCharmBreakChance() const { + Lua_Safe_Call_Int(); + return self->CharmBreakChance; +} + +int32 Lua_StatBonuses::GetSongRange() const { + Lua_Safe_Call_Int(); + return self->SongRange; +} + +uint32 Lua_StatBonuses::GetHPToManaConvert() const { + Lua_Safe_Call_Int(); + return self->HPToManaConvert; +} + +bool Lua_StatBonuses::GetNegateEffects() const { + Lua_Safe_Call_Bool(); + return self->NegateEffects; +} + +bool Lua_StatBonuses::GetTriggerMeleeThreshold() const { + Lua_Safe_Call_Bool(); + return self->TriggerMeleeThreshold; +} + +bool Lua_StatBonuses::GetTriggerSpellThreshold() const { + Lua_Safe_Call_Bool(); + return self->TriggerSpellThreshold; +} + +int32 Lua_StatBonuses::GetShieldBlock() const { + Lua_Safe_Call_Int(); + return self->ShieldBlock; +} + +int32 Lua_StatBonuses::GetBlockBehind() const { + Lua_Safe_Call_Int(); + return self->BlockBehind; +} + +bool Lua_StatBonuses::GetCriticalRegenDecay() const { + Lua_Safe_Call_Bool(); + return self->CriticalRegenDecay; +} + +bool Lua_StatBonuses::GetCriticalHealDecay() const { + Lua_Safe_Call_Bool(); + return self->CriticalHealDecay; +} + +bool Lua_StatBonuses::GetCriticalDotDecay() const { + Lua_Safe_Call_Bool(); + return self->CriticalDotDecay; +} + +bool Lua_StatBonuses::GetDivineAura() const { + Lua_Safe_Call_Bool(); + return self->DivineAura; +} + +bool Lua_StatBonuses::GetDistanceRemoval() const { + Lua_Safe_Call_Bool(); + return self->DistanceRemoval; +} + +int32 Lua_StatBonuses::GetFrenziedDevastation() const { + Lua_Safe_Call_Int(); + return self->FrenziedDevastation; +} + +bool Lua_StatBonuses::GetNegateIfCombat() const { + Lua_Safe_Call_Bool(); + return self->NegateIfCombat; +} + +int8 Lua_StatBonuses::GetScreech() const { + Lua_Safe_Call_Int(); + return self->Screech; +} + +int32 Lua_StatBonuses::GetAlterNPCLevel() const { + Lua_Safe_Call_Int(); + return self->AlterNPCLevel; +} + +bool Lua_StatBonuses::GetBerserkSPA() const { + Lua_Safe_Call_Bool(); + return self->BerserkSPA; +} + +int32 Lua_StatBonuses::GetMetabolism() const { + Lua_Safe_Call_Int(); + return self->Metabolism; +} + +bool Lua_StatBonuses::GetSanctuary() const { + Lua_Safe_Call_Bool(); + return self->Sanctuary; +} + +int32 Lua_StatBonuses::GetFactionModPct() const { + Lua_Safe_Call_Int(); + return self->FactionModPct; +} + +uint32 Lua_StatBonuses::GetPC_Pet_Flurry() const { + Lua_Safe_Call_Int(); + return self->PC_Pet_Flurry; +} + +int8 Lua_StatBonuses::GetPackrat() const { + Lua_Safe_Call_Int(); + return self->Packrat; +} + +uint8 Lua_StatBonuses::GetBuffSlotIncrease() const { + Lua_Safe_Call_Int(); + return self->BuffSlotIncrease; +} + +uint32 Lua_StatBonuses::GetDelayDeath() const { + Lua_Safe_Call_Int(); + return self->DelayDeath; +} + +int8 Lua_StatBonuses::GetBaseMovementSpeed() const { + Lua_Safe_Call_Int(); + return self->BaseMovementSpeed; +} + +uint8 Lua_StatBonuses::GetIncreaseRunSpeedCap() const { + Lua_Safe_Call_Int(); + return self->IncreaseRunSpeedCap; +} + +int32 Lua_StatBonuses::GetDoubleSpecialAttack() const { + Lua_Safe_Call_Int(); + return self->DoubleSpecialAttack; +} + +uint8 Lua_StatBonuses::GetFrontalStunResist() const { + Lua_Safe_Call_Int(); + return self->FrontalStunResist; +} + +int32 Lua_StatBonuses::GetBindWound() const { + Lua_Safe_Call_Int(); + return self->BindWound; +} + +int32 Lua_StatBonuses::GetMaxBindWound() const { + Lua_Safe_Call_Int(); + return self->MaxBindWound; +} + +int32 Lua_StatBonuses::GetChannelChanceSpells() const { + Lua_Safe_Call_Int(); + return self->ChannelChanceSpells; +} + +int32 Lua_StatBonuses::GetChannelChanceItems() const { + Lua_Safe_Call_Int(); + return self->ChannelChanceItems; +} + +uint8 Lua_StatBonuses::GetSeeInvis() const { + Lua_Safe_Call_Int(); + return self->SeeInvis; +} + +uint8 Lua_StatBonuses::GetTripleBackstab() const { + Lua_Safe_Call_Int(); + return self->TripleBackstab; +} + +bool Lua_StatBonuses::GetFrontalBackstabMinDmg() const { + Lua_Safe_Call_Bool(); + return self->FrontalBackstabMinDmg; +} + +uint8 Lua_StatBonuses::GetFrontalBackstabChance() const { + Lua_Safe_Call_Int(); + return self->FrontalBackstabChance; +} + +uint8 Lua_StatBonuses::GetConsumeProjectile() const { + Lua_Safe_Call_Int(); + return self->ConsumeProjectile; +} + +uint8 Lua_StatBonuses::GetForageAdditionalItems() const { + Lua_Safe_Call_Int(); + return self->ForageAdditionalItems; +} + +uint8 Lua_StatBonuses::GetSalvageChance() const { + Lua_Safe_Call_Int(); + return self->SalvageChance; +} + +uint32 Lua_StatBonuses::GetArcheryDamageModifier() const { + Lua_Safe_Call_Int(); + return self->ArcheryDamageModifier; +} + +bool Lua_StatBonuses::GetSecondaryDmgInc() const { + Lua_Safe_Call_Bool(); + return self->SecondaryDmgInc; +} + +uint32 Lua_StatBonuses::GetGiveDoubleAttack() const { + Lua_Safe_Call_Int(); + return self->GiveDoubleAttack; +} + +int32 Lua_StatBonuses::GetPetCriticalHit() const { + Lua_Safe_Call_Int(); + return self->PetCriticalHit; +} + +int32 Lua_StatBonuses::GetPetAvoidance() const { + Lua_Safe_Call_Int(); + return self->PetAvoidance; +} + +int32 Lua_StatBonuses::GetCombatStability() const { + Lua_Safe_Call_Int(); + return self->CombatStability; +} + +int32 Lua_StatBonuses::GetDoubleRiposte() const { + Lua_Safe_Call_Int(); + return self->DoubleRiposte; +} + +int32 Lua_StatBonuses::GetAmbidexterity() const { + Lua_Safe_Call_Int(); + return self->Ambidexterity; +} + +int32 Lua_StatBonuses::GetPetMaxHP() const { + Lua_Safe_Call_Int(); + return self->PetMaxHP; +} + +int32 Lua_StatBonuses::GetPetFlurry() const { + Lua_Safe_Call_Int(); + return self->PetFlurry; +} + +uint8 Lua_StatBonuses::GetMasteryofPast() const { + Lua_Safe_Call_Int(); + return self->MasteryofPast; +} + +bool Lua_StatBonuses::GetGivePetGroupTarget() const { + Lua_Safe_Call_Bool(); + return self->GivePetGroupTarget; +} + +int32 Lua_StatBonuses::GetRootBreakChance() const { + Lua_Safe_Call_Int(); + return self->RootBreakChance; +} + +int32 Lua_StatBonuses::GetUnfailingDivinity() const { + Lua_Safe_Call_Int(); + return self->UnfailingDivinity; +} + +int32 Lua_StatBonuses::GetItemHPRegenCap() const { + Lua_Safe_Call_Int(); + return self->ItemHPRegenCap; +} + +int32 Lua_StatBonuses::GetOffhandRiposteFail() const { + Lua_Safe_Call_Int(); + return self->OffhandRiposteFail; +} + +int32 Lua_StatBonuses::GetItemATKCap() const { + Lua_Safe_Call_Int(); + return self->ItemATKCap; +} + +int32 Lua_StatBonuses::GetShieldEquipDmgMod() const { + Lua_Safe_Call_Int(); + return self->ShieldEquipDmgMod; +} + +bool Lua_StatBonuses::GetTriggerOnValueAmount() const { + Lua_Safe_Call_Bool(); + return self->TriggerOnValueAmount; +} + +int8 Lua_StatBonuses::GetStunBashChance() const { + Lua_Safe_Call_Int(); + return self->StunBashChance; +} + +int8 Lua_StatBonuses::GetIncreaseChanceMemwipe() const { + Lua_Safe_Call_Int(); + return self->IncreaseChanceMemwipe; +} + +int8 Lua_StatBonuses::GetCriticalMend() const { + Lua_Safe_Call_Int(); + return self->CriticalMend; +} + +int32 Lua_StatBonuses::GetImprovedReclaimEnergy() const { + Lua_Safe_Call_Int(); + return self->ImprovedReclaimEnergy; +} + +int32 Lua_StatBonuses::GetPetMeleeMitigation() const { + Lua_Safe_Call_Int(); + return self->PetMeleeMitigation; +} + +bool Lua_StatBonuses::GetIllusionPersistence() const { + Lua_Safe_Call_Bool(); + return self->IllusionPersistence; +} + +uint16 Lua_StatBonuses::Getextra_xtargets() const { + Lua_Safe_Call_Int(); + return self->extra_xtargets; +} + +bool Lua_StatBonuses::GetShroudofStealth() const { + Lua_Safe_Call_Bool(); + return self->ShroudofStealth; +} + +uint16 Lua_StatBonuses::GetReduceFallDamage() const { + Lua_Safe_Call_Int(); + return self->ReduceFallDamage; +} + +uint8 Lua_StatBonuses::GetTradeSkillMastery() const { + Lua_Safe_Call_Int(); + return self->TradeSkillMastery; +} + +int16 Lua_StatBonuses::GetNoBreakAESneak() const { + Lua_Safe_Call_Int(); + return self->NoBreakAESneak; +} + +int16 Lua_StatBonuses::GetFeignedCastOnChance() const { + Lua_Safe_Call_Int(); + return self->FeignedCastOnChance; +} + +int32 Lua_StatBonuses::GetDivineSaveChance(int idx) const { + Lua_Safe_Call_Int(); + return self->DivineSaveChance[idx]; +} + +uint32 Lua_StatBonuses::GetDeathSave(int idx) const { + Lua_Safe_Call_Int(); + return self->DeathSave[idx]; +} + +int32 Lua_StatBonuses::GetAccuracy(int idx) const { + Lua_Safe_Call_Int(); + return self->Accuracy[idx]; +} + +int16 Lua_StatBonuses::GetSkillDmgTaken(int idx) const { + Lua_Safe_Call_Int(); + return self->SkillDmgTaken[idx]; +} + +uint32 Lua_StatBonuses::GetSpellTriggers(int idx) const { + Lua_Safe_Call_Int(); + return self->SpellTriggers[idx]; +} + +uint32 Lua_StatBonuses::GetSpellOnKill(int idx) const { + Lua_Safe_Call_Int(); + return self->SpellOnKill[idx]; +} + +uint32 Lua_StatBonuses::GetSpellOnDeath(int idx) const { + Lua_Safe_Call_Int(); + return self->SpellOnDeath[idx]; +} + +int32 Lua_StatBonuses::GetCritDmgMod(int idx) const { + Lua_Safe_Call_Int(); + return self->CritDmgMod[idx]; +} + +int32 Lua_StatBonuses::GetSkillReuseTime(int idx) const { + Lua_Safe_Call_Int(); + return self->SkillReuseTime[idx]; +} + +int32 Lua_StatBonuses::GetSkillDamageAmount(int idx) const { + Lua_Safe_Call_Int(); + return self->SkillDamageAmount[idx]; +} + +int Lua_StatBonuses::GetHPPercCap(int idx) const { + Lua_Safe_Call_Int(); + return self->HPPercCap[idx]; +} + +int Lua_StatBonuses::GetManaPercCap(int idx) const { + Lua_Safe_Call_Int(); + return self->ManaPercCap[idx]; +} + +int Lua_StatBonuses::GetEndPercCap(int idx) const { + Lua_Safe_Call_Int(); + return self->EndPercCap[idx]; +} + +uint8 Lua_StatBonuses::GetFocusEffects(int idx) const { + Lua_Safe_Call_Int(); + return self->FocusEffects[idx]; +} + +int16 Lua_StatBonuses::GetFocusEffectsWorn(int idx) const { + Lua_Safe_Call_Int(); + return self->FocusEffectsWorn[idx]; +} + +int32 Lua_StatBonuses::GetSkillDamageAmount2(int idx) const { + Lua_Safe_Call_Int(); + return self->SkillDamageAmount2[idx]; +} + +uint32 Lua_StatBonuses::GetNegateAttacks(int idx) const { + Lua_Safe_Call_Int(); + return self->NegateAttacks[idx]; +} + +uint32 Lua_StatBonuses::GetMitigateMeleeRune(int idx) const { + Lua_Safe_Call_Int(); + return self->MitigateMeleeRune[idx]; +} + +uint32 Lua_StatBonuses::GetMeleeThresholdGuard(int idx) const { + Lua_Safe_Call_Int(); + return self->MeleeThresholdGuard[idx]; +} + +uint32 Lua_StatBonuses::GetSpellThresholdGuard(int idx) const { + Lua_Safe_Call_Int(); + return self->SpellThresholdGuard[idx]; +} + +uint32 Lua_StatBonuses::GetMitigateSpellRune(int idx) const { + Lua_Safe_Call_Int(); + return self->MitigateSpellRune[idx]; +} + +uint32 Lua_StatBonuses::GetMitigateDotRune(int idx) const { + Lua_Safe_Call_Int(); + return self->MitigateDotRune[idx]; +} + +uint32 Lua_StatBonuses::GetManaAbsorbPercentDamage(int idx) const { + Lua_Safe_Call_Int(); + return self->ManaAbsorbPercentDamage[idx]; +} + +int32 Lua_StatBonuses::GetImprovedTaunt(int idx) const { + Lua_Safe_Call_Int(); + return self->ImprovedTaunt[idx]; +} + +int8 Lua_StatBonuses::GetRoot(int idx) const { + Lua_Safe_Call_Int(); + return self->Root[idx]; +} + +uint32 Lua_StatBonuses::GetAbsorbMagicAtt(int idx) const { + Lua_Safe_Call_Int(); + return self->AbsorbMagicAtt[idx]; +} + +uint32 Lua_StatBonuses::GetMeleeRune(int idx) const { + Lua_Safe_Call_Int(); + return self->MeleeRune[idx]; +} + +int32 Lua_StatBonuses::GetAStacker(int idx) const { + Lua_Safe_Call_Int(); + return self->AStacker[idx]; +} + +int32 Lua_StatBonuses::GetBStacker(int idx) const { + Lua_Safe_Call_Int(); + return self->BStacker[idx]; +} + +int32 Lua_StatBonuses::GetCStacker(int idx) const { + Lua_Safe_Call_Int(); + return self->CStacker[idx]; +} + +int32 Lua_StatBonuses::GetDStacker(int idx) const { + Lua_Safe_Call_Int(); + return self->DStacker[idx]; +} + +bool Lua_StatBonuses::GetLimitToSkill(int idx) const { + Lua_Safe_Call_Bool(); + return self->LimitToSkill[idx]; +} + +uint32 Lua_StatBonuses::GetSkillProc(int idx) const { + Lua_Safe_Call_Int(); + return self->SkillProc[idx]; +} + +uint32 Lua_StatBonuses::GetSkillProcSuccess(int idx) const { + Lua_Safe_Call_Int(); + return self->SkillProcSuccess[idx]; +} + +uint32 Lua_StatBonuses::GetPC_Pet_Rampage(int idx) const { + Lua_Safe_Call_Int(); + return self->PC_Pet_Rampage[idx]; +} + +int32 Lua_StatBonuses::GetSkillAttackProc(int idx) const { + Lua_Safe_Call_Int(); + return self->SkillAttackProc[idx]; +} + +int32 Lua_StatBonuses::GetSlayUndead(int idx) const { + Lua_Safe_Call_Int(); + return self->SlayUndead[idx]; +} + +int32 Lua_StatBonuses::GetGiveDoubleRiposte(int idx) const { + Lua_Safe_Call_Int(); + return self->GiveDoubleRiposte[idx]; +} + +uint32 Lua_StatBonuses::GetRaiseSkillCap(int idx) const { + Lua_Safe_Call_Int(); + return self->RaiseSkillCap[idx]; +} + +int32 Lua_StatBonuses::GetSEResist(int idx) const { + Lua_Safe_Call_Int(); + return self->SEResist[idx]; +} + +int32 Lua_StatBonuses::GetFinishingBlow(int idx) const { + Lua_Safe_Call_Int(); + return self->FinishingBlow[idx]; +} + +uint32 Lua_StatBonuses::GetFinishingBlowLvl(int idx) const { + Lua_Safe_Call_Int(); + return self->FinishingBlowLvl[idx]; +} + +uint32 Lua_StatBonuses::GetHeadShot(int idx) const { + Lua_Safe_Call_Int(); + return self->HeadShot[idx]; +} + +uint8 Lua_StatBonuses::GetHSLevel(int idx) const { + Lua_Safe_Call_Int(); + return self->HSLevel[idx]; +} + +uint32 Lua_StatBonuses::GetAssassinate(int idx) const { + Lua_Safe_Call_Int(); + return self->Assassinate[idx]; +} + +uint8 Lua_StatBonuses::GetAssassinateLevel(int idx) const { + Lua_Safe_Call_Int(); + return self->AssassinateLevel[idx]; +} + +int32 Lua_StatBonuses::GetReduceTradeskillFail(int idx) const { + Lua_Safe_Call_Int(); + return self->ReduceTradeskillFail[idx]; +} + +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("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); +} \ No newline at end of file diff --git a/zone/lua_stat_bonuses.h b/zone/lua_stat_bonuses.h new file mode 100644 index 000000000..9ad04d681 --- /dev/null +++ b/zone/lua_stat_bonuses.h @@ -0,0 +1,285 @@ +#pragma once + +#ifdef LUA_EQEMU + +#include "lua_ptr.h" +#include "common.h" + +struct StatBonuses; + +namespace luabind { + struct scope; +} + +luabind::scope lua_register_stat_bonuses(); + +class Lua_StatBonuses : public Lua_Ptr +{ + typedef StatBonuses NativeType; +public: + Lua_StatBonuses() : Lua_Ptr(nullptr) { } + Lua_StatBonuses(StatBonuses *d) : Lua_Ptr(d) { } + virtual ~Lua_StatBonuses() { } + + operator StatBonuses*() { + return reinterpret_cast(GetLuaPtrData()); + } + + int32 GetAC() const; + int32 GetHP() const; + int32 GetHPRegen() const; + int32 GetMaxHP() const; + int32 GetManaRegen() const; + int32 GetEnduranceRegen() const; + int32 GetMana() const; + int32 GetEndurance() const; + int32 GetATK() const; + int32 GetSTR() const; + int32 GetSTRCapMod() const; + int32 GetHeroicSTR() const; + int32 GetSTA() const; + int32 GetSTACapMod() const; + int32 GetHeroicSTA() const; + int32 GetDEX() const; + int32 GetDEXCapMod() const; + int32 GetHeroicDEX() const; + int32 GetAGI() const; + int32 GetAGICapMod() const; + int32 GetHeroicAGI() const; + int32 GetINT() const; + int32 GetINTCapMod() const; + int32 GetHeroicINT() const; + int32 GetWIS() const; + int32 GetWISCapMod() const; + int32 GetHeroicWIS() const; + int32 GetCHA() const; + int32 GetCHACapMod() const; + int32 GetHeroicCHA() const; + int32 GetMR() const; + int32 GetMRCapMod() const; + int32 GetHeroicMR() const; + int32 GetFR() const; + int32 GetFRCapMod() const; + int32 GetHeroicFR() const; + int32 GetCR() const; + int32 GetCRCapMod() const; + int32 GetHeroicCR() const; + int32 GetPR() const; + int32 GetPRCapMod() const; + int32 GetHeroicPR() const; + int32 GetDR() const; + int32 GetDRCapMod() const; + int32 GetHeroicDR() const; + int32 GetCorrup() const; + int32 GetCorrupCapMod() const; + int32 GetHeroicCorrup() const; + uint16 GetDamageShieldSpellID() const; + int GetDamageShield() const; + int GetDamageShieldType() const; + int GetSpellDamageShield() const; + int GetSpellShield() const; + int GetReverseDamageShield() const; + uint16 GetReverseDamageShieldSpellID() const; + int GetReverseDamageShieldType() const; + int Getmovementspeed() const; + int32 Gethaste() const; + int32 Gethastetype2() const; + int32 Gethastetype3() const; + int32 Getinhibitmelee() const; + float GetAggroRange() const; + float GetAssistRange() const; + int32 Getskillmod(int idx) const; + int32 Getskillmodmax(int idx) const; + int Geteffective_casting_level() const; + int Getreflect_chance() const; + uint32 GetsingingMod() const; + uint32 GetAmplification() const; + uint32 GetbrassMod() const; + uint32 GetpercussionMod() const; + uint32 GetwindMod() const; + uint32 GetstringedMod() const; + uint32 GetsongModCap() const; + int8 Gethatemod() const; + int32 GetEnduranceReduction() const; + int32 GetStrikeThrough() const; + int32 GetMeleeMitigation() const; + int32 GetMeleeMitigationEffect() const; + int32 GetCriticalHitChance(int idx) const; + int32 GetCriticalSpellChance() const; + int32 GetSpellCritDmgIncrease() const; + int32 GetSpellCritDmgIncNoStack() const; + int32 GetDotCritDmgIncrease() const; + int32 GetCriticalHealChance() const; + int32 GetCriticalHealOverTime() const; + int32 GetCriticalDoTChance() const; + int32 GetCrippBlowChance() const; + int32 GetAvoidMeleeChance() const; + int32 GetAvoidMeleeChanceEffect() const; + int32 GetRiposteChance() const; + int32 GetDodgeChance() const; + int32 GetParryChance() const; + int32 GetDualWieldChance() const; + int32 GetDoubleAttackChance() const; + int32 GetTripleAttackChance() const; + int32 GetDoubleRangedAttack() const; + int32 GetResistSpellChance() const; + int32 GetResistFearChance() const; + bool GetFearless() const; + bool GetIsFeared() const; + bool GetIsBlind() const; + int32 GetStunResist() const; + int32 GetMeleeSkillCheck() const; + uint8 GetMeleeSkillCheckSkill() const; + int32 GetHitChance() const; + int32 GetHitChanceEffect(int idx) const; + int32 GetDamageModifier(int idx) const; + int32 GetDamageModifier2(int idx) const; + int32 GetMinDamageModifier(int idx) const; + int32 GetProcChance() const; + int32 GetProcChanceSPA() const; + int32 GetExtraAttackChance() const; + int32 GetDoTShielding() const; + int32 GetFlurryChance() const; + int32 GetHundredHands() const; + int32 GetMeleeLifetap() const; + int32 GetVampirism() const; + int32 GetHealRate() const; + int32 GetMaxHPChange() const; + int32 GetHealAmt() const; + int32 GetSpellDmg() const; + int32 GetClairvoyance() const; + int32 GetDSMitigation() const; + int32 GetDSMitigationOffHand() const; + int32 GetTwoHandBluntBlock() const; + uint32 GetItemManaRegenCap() const; + int32 GetGravityEffect() const; + bool GetAntiGate() const; + bool GetMagicWeapon() const; + int32 GetIncreaseBlockChance() const; + uint32 GetPersistantCasting() const; + int GetXPRateMod() const; + bool GetBlockNextSpell() const; + bool GetImmuneToFlee() const; + uint32 GetVoiceGraft() const; + int32 GetSpellProcChance() const; + int32 GetCharmBreakChance() const; + int32 GetSongRange() const; + uint32 GetHPToManaConvert() const; + bool GetNegateEffects() const; + bool GetTriggerMeleeThreshold() const; + bool GetTriggerSpellThreshold() const; + int32 GetShieldBlock() const; + int32 GetBlockBehind() const; + bool GetCriticalRegenDecay() const; + bool GetCriticalHealDecay() const; + bool GetCriticalDotDecay() const; + bool GetDivineAura() const; + bool GetDistanceRemoval() const; + int32 GetFrenziedDevastation() const; + bool GetNegateIfCombat() const; + int8 GetScreech() const; + int32 GetAlterNPCLevel() const; + bool GetBerserkSPA() const; + int32 GetMetabolism() const; + bool GetSanctuary() const; + int32 GetFactionModPct() const; + uint32 GetPC_Pet_Flurry() const; + int8 GetPackrat() const; + uint8 GetBuffSlotIncrease() const; + uint32 GetDelayDeath() const; + int8 GetBaseMovementSpeed() const; + uint8 GetIncreaseRunSpeedCap() const; + int32 GetDoubleSpecialAttack() const; + uint8 GetFrontalStunResist() const; + int32 GetBindWound() const; + int32 GetMaxBindWound() const; + int32 GetChannelChanceSpells() const; + int32 GetChannelChanceItems() const; + uint8 GetSeeInvis() const; + uint8 GetTripleBackstab() const; + bool GetFrontalBackstabMinDmg() const; + uint8 GetFrontalBackstabChance() const; + uint8 GetConsumeProjectile() const; + uint8 GetForageAdditionalItems() const; + uint8 GetSalvageChance() const; + uint32 GetArcheryDamageModifier() const; + bool GetSecondaryDmgInc() const; + uint32 GetGiveDoubleAttack() const; + int32 GetPetCriticalHit() const; + int32 GetPetAvoidance() const; + int32 GetCombatStability() const; + int32 GetDoubleRiposte() const; + int32 GetAmbidexterity() const; + int32 GetPetMaxHP() const; + int32 GetPetFlurry() const; + uint8 GetMasteryofPast() const; + bool GetGivePetGroupTarget() const; + int32 GetRootBreakChance() const; + int32 GetUnfailingDivinity() const; + int32 GetItemHPRegenCap() const; + int32 GetOffhandRiposteFail() const; + int32 GetItemATKCap() const; + int32 GetShieldEquipDmgMod() const; + bool GetTriggerOnValueAmount() const; + int8 GetStunBashChance() const; + int8 GetIncreaseChanceMemwipe() const; + int8 GetCriticalMend() const; + int32 GetImprovedReclaimEnergy() const; + int32 GetPetMeleeMitigation() const; + bool GetIllusionPersistence() const; + uint16 Getextra_xtargets() const; + bool GetShroudofStealth() const; + uint16 GetReduceFallDamage() const; + uint8 GetTradeSkillMastery() const; + int16 GetNoBreakAESneak() const; + int16 GetFeignedCastOnChance() const; + int32 GetDivineSaveChance(int idx) const; + uint32 GetDeathSave(int idx) const; + int32 GetAccuracy(int idx) const; + int16 GetSkillDmgTaken(int idx) const; + uint32 GetSpellTriggers(int idx) const; + uint32 GetSpellOnKill(int idx) const; + uint32 GetSpellOnDeath(int idx) const; + int32 GetCritDmgMod(int idx) const; + int32 GetSkillReuseTime(int idx) const; + int32 GetSkillDamageAmount(int idx) const; + int GetHPPercCap(int idx) const; + int GetManaPercCap(int idx) const; + int GetEndPercCap(int idx) const; + uint8 GetFocusEffects(int idx) const; + int16 GetFocusEffectsWorn(int idx) const; + int32 GetSkillDamageAmount2(int idx) const; + uint32 GetNegateAttacks(int idx) const; + uint32 GetMitigateMeleeRune(int idx) const; + uint32 GetMeleeThresholdGuard(int idx) const; + uint32 GetSpellThresholdGuard(int idx) const; + uint32 GetMitigateSpellRune(int idx) const; + uint32 GetMitigateDotRune(int idx) const; + uint32 GetManaAbsorbPercentDamage(int idx) const; + int32 GetImprovedTaunt(int idx) const; + int8 GetRoot(int idx) const; + uint32 GetAbsorbMagicAtt(int idx) const; + uint32 GetMeleeRune(int idx) const; + int32 GetAStacker(int idx) const; + int32 GetBStacker(int idx) const; + int32 GetCStacker(int idx) const; + int32 GetDStacker(int idx) const; + bool GetLimitToSkill(int idx) const; + uint32 GetSkillProc(int idx) const; + uint32 GetSkillProcSuccess(int idx) const; + uint32 GetPC_Pet_Rampage(int idx) const; + int32 GetSkillAttackProc(int idx) const; + int32 GetSlayUndead(int idx) const; + int32 GetGiveDoubleRiposte(int idx) const; + uint32 GetRaiseSkillCap(int idx) const; + int32 GetSEResist(int idx) const; + int32 GetFinishingBlow(int idx) const; + uint32 GetFinishingBlowLvl(int idx) const; + uint32 GetHeadShot(int idx) const; + uint8 GetHSLevel(int idx) const; + uint32 GetAssassinate(int idx) const; + uint8 GetAssassinateLevel(int idx) const; + int32 GetReduceTradeskillFail(int idx) const; +}; + +#endif diff --git a/zone/merc.cpp b/zone/merc.cpp index 90d287152..a3ff051f2 100644 --- a/zone/merc.cpp +++ b/zone/merc.cpp @@ -906,15 +906,15 @@ int32 Merc::CalcMaxMana() max_mana = 0; } - if (cur_mana > max_mana) { - cur_mana = max_mana; + if (current_mana > max_mana) { + current_mana = max_mana; } int mana_perc_cap = spellbonuses.ManaPercCap[0]; if(mana_perc_cap) { int curMana_cap = (max_mana * mana_perc_cap) / 100; - if (cur_mana > curMana_cap || (spellbonuses.ManaPercCap[1] && cur_mana > spellbonuses.ManaPercCap[1])) - cur_mana = curMana_cap; + if (current_mana > curMana_cap || (spellbonuses.ManaPercCap[1] && current_mana > spellbonuses.ManaPercCap[1])) + current_mana = curMana_cap; } #if EQDEBUG >= 11 @@ -1211,6 +1211,7 @@ void Merc::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) { ns->spawn.flymode = 0; ns->spawn.NPC = 1; // 0=player,1=npc,2=pc corpse,3=npc corpse ns->spawn.IsMercenary = 1; + ns->spawn.show_name = true; UpdateActiveLight(); ns->spawn.light = m_Light.Type[EQEmu::lightsource::LightActive]; @@ -1589,7 +1590,7 @@ void Merc::AI_Process() { } if(IsMoving()) - SendPosUpdate(); + SendPositionUpdate(); else SendPosition(); } @@ -1714,7 +1715,7 @@ void Merc::AI_Process() { } if(IsMoving()) - SendPosUpdate(); + SendPositionUpdate(); else SendPosition(); } @@ -4987,7 +4988,7 @@ void Merc::ScaleStats(int scalepercent, bool setmax) { max_mana = (int)((float)base_mana * scalerate); base_mana = max_mana; if (setmax) - cur_mana = max_mana; + current_mana = max_mana; } if (base_end) diff --git a/zone/mob.cpp b/zone/mob.cpp index d219a27af..33863081a 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -112,18 +112,25 @@ Mob::Mob(const char* in_name, m_Position(position), tmHidden(-1), mitigation_ac(0), - m_specialattacks(eSpecialAttacks::None) + m_specialattacks(eSpecialAttacks::None), + fix_z_timer(300), + fix_z_timer_engaged(100), + attack_anim_timer(1000), + position_update_melee_push_timer(1000) { targeted = 0; tar_ndx=0; tar_vector=0; currently_fleeing = false; + last_z = 0; + + last_major_update_position = m_Position; + AI_Init(); SetMoving(false); moved=false; m_RewindLocation = glm::vec3(); - move_tic_count = 0; _egnode = nullptr; name[0]=0; @@ -172,6 +179,8 @@ Mob::Mob(const char* in_name, fearspeed = ((float)base_fearspeed) * 0.025f; } + last_hp_percent = 0; + last_hp = 0; current_speed = base_runspeed; @@ -240,7 +249,7 @@ Mob::Mob(const char* in_name, bEnraged = false; shield_target = nullptr; - cur_mana = 0; + current_mana = 0; max_mana = 0; hp_regen = in_hp_regen; mana_regen = in_mana_regen; @@ -344,8 +353,11 @@ Mob::Mob(const char* in_name, typeofpet = petNone; // default to not a pet petpower = 0; held = false; + gheld = false; nocast = false; focused = false; + pet_stop = false; + pet_regroup = false; _IsTempPet = false; pet_owner_client = false; pet_targetlock_id = 0; @@ -1290,197 +1302,219 @@ void Mob::CreateHPPacket(EQApplicationPacket* app) } // sends hp update of this mob to people who might care -void Mob::SendHPUpdate(bool skip_self) +void Mob::SendHPUpdate(bool skip_self /*= false*/, bool force_update_all /*= false*/) { - EQApplicationPacket hp_app; - Group *group = nullptr; + + /* If our HP is different from last HP update call - let's update ourself */ + if (IsClient()) { + if (cur_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::HP_Update, + "Mob::SendHPUpdate :: Update HP of self (%s) HP: %i last: %i skip_self: %s", + this->GetCleanName(), + cur_hp, + last_hp, + (skip_self ? "true" : "false") + ); - // destructor will free the pBuffer - CreateHPPacket(&hp_app); + if (!skip_self || this->CastToClient()->ClientVersion() >= EQEmu::versions::ClientVersion::SoD) { + auto client_packet = new EQApplicationPacket(OP_HPUpdate, sizeof(SpawnHPUpdate_Struct)); + SpawnHPUpdate_Struct* hp_packet_client = (SpawnHPUpdate_Struct*)client_packet->pBuffer; - // send to people who have us targeted - entity_list.QueueClientsByTarget(this, &hp_app, false, 0, false, true, EQEmu::versions::bit_AllClients); - entity_list.QueueClientsByXTarget(this, &hp_app, false); - entity_list.QueueToGroupsForNPCHealthAA(this, &hp_app); + hp_packet_client->cur_hp = CastToClient()->GetHP() - itembonuses.HP; + hp_packet_client->spawn_id = GetID(); + hp_packet_client->max_hp = CastToClient()->GetMaxHP() - itembonuses.HP; - // send to group - if(IsGrouped()) - { - group = entity_list.GetGroupByMob(this); - if(group) //not sure why this might be null, but it happens - group->SendHPPacketsFrom(this); - } + CastToClient()->QueuePacket(client_packet); - if(IsClient()){ - Raid *r = entity_list.GetRaidByClient(CastToClient()); - if(r){ - r->SendHPPacketsFrom(this); - } - } + safe_delete(client_packet); - // send to master - if(GetOwner() && GetOwner()->IsClient()) - { - GetOwner()->CastToClient()->QueuePacket(&hp_app, false); - group = entity_list.GetGroupByClient(GetOwner()->CastToClient()); - if(group) - group->SendHPPacketsFrom(this); - Raid *r = entity_list.GetRaidByClient(GetOwner()->CastToClient()); - if(r) - r->SendHPPacketsFrom(this); - } + ResetHPUpdateTimer(); + } - // send to pet - if(GetPet() && GetPet()->IsClient()) - { - GetPet()->CastToClient()->QueuePacket(&hp_app, false); - } - - // Update the damage state of destructible objects - if(IsNPC() && IsDestructibleObject()) - { - if (GetHPRatio() > 74) - { - if (GetAppearance() != eaStanding) - { - SendAppearancePacket(AT_DamageState, eaStanding); - _appearance = eaStanding; + /* Used to check if HP has changed to update self next round */ + last_hp = cur_hp; } } - else if (GetHPRatio() > 49) - { - if (GetAppearance() != eaSitting) - { + } + + int8 current_hp_percent = (max_hp == 0 ? 0 : static_cast(cur_hp * 100 / max_hp)); + + Log(Logs::General, Logs::HP_Update, "Mob::SendHPUpdate :: SendHPUpdate %s HP is %i last %i", this->GetCleanName(), current_hp_percent, last_hp_percent); + + if (current_hp_percent == last_hp_percent && !force_update_all) { + Log(Logs::General, Logs::HP_Update, "Mob::SendHPUpdate :: Same HP - skipping update"); + ResetHPUpdateTimer(); + return; + } + else { + + if (IsClient() && RuleB(Character, MarqueeHPUpdates)) + this->CastToClient()->SendHPUpdateMarquee(); + + Log(Logs::General, Logs::HP_Update, "Mob::SendHPUpdate :: HP Changed - Send update"); + + last_hp_percent = current_hp_percent; + } + + EQApplicationPacket hp_packet; + Group *group = nullptr; + + CreateHPPacket(&hp_packet); + + /* Update those who have us targeted */ + entity_list.QueueClientsByTarget(this, &hp_packet, false, 0, false, true, EQEmu::versions::bit_AllClients); + + /* Update those who have us on x-target */ + entity_list.QueueClientsByXTarget(this, &hp_packet, false); + + /* Update groups using Group LAA health name tag counter */ + entity_list.QueueToGroupsForNPCHealthAA(this, &hp_packet); + + /* Update group */ + if(IsGrouped()) { + group = entity_list.GetGroupByMob(this); + if(group) + group->SendHPPacketsFrom(this); + } + + /* Update Raid */ + if(IsClient()){ + Raid *raid = entity_list.GetRaidByClient(CastToClient()); + if (raid) + raid->SendHPPacketsFrom(this); + } + + /* Pet - Update master - group and raid if exists */ + if(GetOwner() && GetOwner()->IsClient()) { + GetOwner()->CastToClient()->QueuePacket(&hp_packet, false); + group = entity_list.GetGroupByClient(GetOwner()->CastToClient()); + + if(group) + group->SendHPPacketsFrom(this); + + Raid *raid = entity_list.GetRaidByClient(GetOwner()->CastToClient()); + if(raid) + raid->SendHPPacketsFrom(this); + } + + /* Send to pet */ + if(GetPet() && GetPet()->IsClient()) { + GetPet()->CastToClient()->QueuePacket(&hp_packet, false); + } + + /* Destructible objects */ + if (IsNPC() && IsDestructibleObject()) { + if (GetHPRatio() > 74) { + if (GetAppearance() != eaStanding) { + SendAppearancePacket(AT_DamageState, eaStanding); + _appearance = eaStanding; + } + } + else if (GetHPRatio() > 49) { + if (GetAppearance() != eaSitting) { SendAppearancePacket(AT_DamageState, eaSitting); _appearance = eaSitting; } } - else if (GetHPRatio() > 24) - { - if (GetAppearance() != eaCrouching) - { + else if (GetHPRatio() > 24) { + if (GetAppearance() != eaCrouching) { SendAppearancePacket(AT_DamageState, eaCrouching); _appearance = eaCrouching; } } - else if (GetHPRatio() > 0) - { - if (GetAppearance() != eaDead) - { + else if (GetHPRatio() > 0) { + if (GetAppearance() != eaDead) { SendAppearancePacket(AT_DamageState, eaDead); _appearance = eaDead; } } - else if (GetAppearance() != eaLooting) - { + else if (GetAppearance() != eaLooting) { SendAppearancePacket(AT_DamageState, eaLooting); _appearance = eaLooting; } } - - bool dospam = RuleB(Character, SpamHPUpdates); - // send to self - we need the actual hps here - if(IsClient() && (!skip_self || dospam)) { - - if (RuleB(Character, MarqueeHPUpdates)) - this->CastToClient()->SendHPUpdateMarquee(); - - auto hp_app2 = new EQApplicationPacket(OP_HPUpdate, sizeof(SpawnHPUpdate_Struct)); - SpawnHPUpdate_Struct* ds = (SpawnHPUpdate_Struct*)hp_app2->pBuffer; - ds->cur_hp = CastToClient()->GetHP() - itembonuses.HP; - ds->spawn_id = GetID(); - ds->max_hp = CastToClient()->GetMaxHP() - itembonuses.HP; - CastToClient()->QueuePacket(hp_app2); - safe_delete(hp_app2); - } - if (!dospam) - ResetHPUpdateTimer(); // delay the timer } -// this one just warps the mob to the current location -void Mob::SendPosition() -{ +/* Used for mobs standing still - this does not send a delta */ +void Mob::SendPosition() { auto app = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct)); PlayerPositionUpdateServer_Struct* spu = (PlayerPositionUpdateServer_Struct*)app->pBuffer; MakeSpawnUpdateNoDelta(spu); - move_tic_count = 0; - entity_list.QueueClients(this, app, true); + + /* When an NPC has made a large distance change - we should update all clients to prevent "ghosts" */ + if (DistanceSquared(last_major_update_position, m_Position) >= (100 * 100)) { + entity_list.QueueClients(this, app, true, true); + last_major_update_position = m_Position; + } + else { + entity_list.QueueCloseClients(this, app, true, RuleI(Range, MobPositionUpdates), nullptr, false); + } + safe_delete(app); } -// this one is for mobs on the move, with deltas - this makes them walk -void Mob::SendPosUpdate(uint8 iSendToSelf) { +/* Position updates for mobs on the move */ +void Mob::SendPositionUpdate(uint8 iSendToSelf) { auto app = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct)); PlayerPositionUpdateServer_Struct* spu = (PlayerPositionUpdateServer_Struct*)app->pBuffer; MakeSpawnUpdate(spu); if (iSendToSelf == 2) { if (IsClient()) { - CastToClient()->FastQueuePacket(&app,false); + CastToClient()->FastQueuePacket(&app, false); } } - else - { - if(move_tic_count == RuleI(Zone, NPCPositonUpdateTicCount)) - { - entity_list.QueueClients(this, app, (iSendToSelf == 0), false); - move_tic_count = 0; - } - else if(move_tic_count % 2 == 0) - { - entity_list.QueueCloseClients(this, app, (iSendToSelf == 0), RuleI(Range, MobPositionUpdates), nullptr, false); - move_tic_count++; - } - else { - move_tic_count++; - } + else { + entity_list.QueueCloseClients(this, app, (iSendToSelf == 0), RuleI(Range, MobPositionUpdates), nullptr, false); } safe_delete(app); } // this is for SendPosition() -void Mob::MakeSpawnUpdateNoDelta(PlayerPositionUpdateServer_Struct *spu){ - memset(spu,0xff,sizeof(PlayerPositionUpdateServer_Struct)); - spu->spawn_id = GetID(); - spu->x_pos = FloatToEQ19(m_Position.x); - spu->y_pos = FloatToEQ19(m_Position.y); - spu->z_pos = FloatToEQ19(m_Position.z); - spu->delta_x = NewFloatToEQ13(0); - spu->delta_y = NewFloatToEQ13(0); - spu->delta_z = NewFloatToEQ13(0); - spu->heading = FloatToEQ19(m_Position.w); - spu->animation = 0; +void Mob::MakeSpawnUpdateNoDelta(PlayerPositionUpdateServer_Struct *spu) { + memset(spu, 0xff, sizeof(PlayerPositionUpdateServer_Struct)); + spu->spawn_id = GetID(); + spu->x_pos = FloatToEQ19(m_Position.x); + spu->y_pos = FloatToEQ19(m_Position.y); + spu->z_pos = FloatToEQ19(m_Position.z); + spu->delta_x = NewFloatToEQ13(0); + spu->delta_y = NewFloatToEQ13(0); + spu->delta_z = NewFloatToEQ13(0); + spu->heading = FloatToEQ19(m_Position.w); + spu->animation = 0; spu->delta_heading = NewFloatToEQ13(0); - spu->padding0002 =0; - spu->padding0006 =7; - spu->padding0014 =0x7f; - spu->padding0018 =0x5df27; + spu->padding0002 = 0; + spu->padding0006 = 7; + spu->padding0014 = 0x7f; + spu->padding0018 = 0x5df27; } // this is for SendPosUpdate() void Mob::MakeSpawnUpdate(PlayerPositionUpdateServer_Struct* spu) { - spu->spawn_id = GetID(); - spu->x_pos = FloatToEQ19(m_Position.x); - spu->y_pos = FloatToEQ19(m_Position.y); - spu->z_pos = FloatToEQ19(m_Position.z); - spu->delta_x = NewFloatToEQ13(m_Delta.x); - spu->delta_y = NewFloatToEQ13(m_Delta.y); - spu->delta_z = NewFloatToEQ13(m_Delta.z); - spu->heading = FloatToEQ19(m_Position.w); - spu->padding0002 =0; - spu->padding0006 =7; - spu->padding0014 =0x7f; - spu->padding0018 =0x5df27; + spu->spawn_id = GetID(); + spu->x_pos = FloatToEQ19(m_Position.x); + spu->y_pos = FloatToEQ19(m_Position.y); + spu->z_pos = FloatToEQ19(m_Position.z); + spu->delta_x = NewFloatToEQ13(m_Delta.x); + spu->delta_y = NewFloatToEQ13(m_Delta.y); + spu->delta_z = NewFloatToEQ13(m_Delta.z); + spu->heading = FloatToEQ19(m_Position.w); + spu->padding0002 = 0; + spu->padding0006 = 7; + spu->padding0014 = 0x7f; + spu->padding0018 = 0x5df27; #ifdef BOTS if (this->IsClient() || this->IsBot()) #else - if(this->IsClient()) + if (this->IsClient()) #endif spu->animation = animation; else spu->animation = pRunAnimSpeed;//animation; - + spu->delta_heading = NewFloatToEQ13(m_Delta.w); } @@ -1528,6 +1562,9 @@ void Mob::ShowStats(Client* client) } void Mob::DoAnim(const int animnum, int type, bool ackreq, eqFilterType filter) { + if (!attack_anim_timer.Check()) + return; + auto outapp = new EQApplicationPacket(OP_Animation, sizeof(Animation_Struct)); Animation_Struct* anim = (Animation_Struct*)outapp->pBuffer; anim->spawnid = GetID(); @@ -2242,13 +2279,13 @@ const int32& Mob::SetMana(int32 amount) { CalcMaxMana(); int32 mmana = GetMaxMana(); - cur_mana = amount < 0 ? 0 : (amount > mmana ? mmana : amount); + current_mana = amount < 0 ? 0 : (amount > mmana ? mmana : amount); /* if(IsClient()) LogFile->write(EQEMuLog::Debug, "Setting mana for %s to %d (%4.1f%%)", GetName(), amount, GetManaRatio()); */ - return cur_mana; + return current_mana; } @@ -2690,25 +2727,25 @@ bool Mob::HateSummon() { return false; } -void Mob::FaceTarget(Mob* MobToFace) { - Mob* facemob = MobToFace; - if(!facemob) { +void Mob::FaceTarget(Mob* mob_to_face /*= 0*/) { + Mob* faced_mob = mob_to_face; + if(!faced_mob) { if(!GetTarget()) { return; } else { - facemob = GetTarget(); + faced_mob = GetTarget(); } } - float oldheading = GetHeading(); - float newheading = CalculateHeadingToTarget(facemob->GetX(), facemob->GetY()); - if(oldheading != newheading) { - SetHeading(newheading); - if(moving) - SendPosUpdate(); - else - { + float current_heading = GetHeading(); + float new_heading = CalculateHeadingToTarget(faced_mob->GetX(), faced_mob->GetY()); + if(current_heading != new_heading) { + SetHeading(new_heading); + if (moving) { + SendPositionUpdate(); + } + else { SendPosition(); } } @@ -3093,6 +3130,26 @@ void Mob::Say_StringID(uint32 type, uint32 string_id, const char *message3, cons ); } +void Mob::SayTo_StringID(Client *to, uint32 string_id, const char *message3, const char *message4, const char *message5, const char *message6, const char *message7, const char *message8, const char *message9) +{ + if (!to) + return; + + auto string_id_str = std::to_string(string_id); + + to->Message_StringID(10, GENERIC_STRINGID_SAY, GetCleanName(), string_id_str.c_str(), message3, message4, message5, message6, message7, message8, message9); +} + +void Mob::SayTo_StringID(Client *to, uint32 type, uint32 string_id, const char *message3, const char *message4, const char *message5, const char *message6, const char *message7, const char *message8, const char *message9) +{ + if (!to) + return; + + auto string_id_str = std::to_string(string_id); + + to->Message_StringID(type, GENERIC_STRINGID_SAY, GetCleanName(), string_id_str.c_str(), message3, message4, message5, message6, message7, message8, message9); +} + void Mob::Shout(const char *format, ...) { char buf[1000]; @@ -3349,9 +3406,13 @@ int Mob::GetHaste() } void Mob::SetTarget(Mob* mob) { - if (target == mob) return; + + if (target == mob) + return; + target = mob; entity_list.UpdateHoTT(this); + if(IsNPC()) parse->EventNPC(EVENT_TARGET_CHANGE, CastToNPC(), mob, "", 0); else if (IsClient()) @@ -3359,6 +3420,9 @@ void Mob::SetTarget(Mob* mob) { if(IsPet() && GetOwner() && GetOwner()->IsClient()) GetOwner()->CastToClient()->UpdateXTargetType(MyPetTarget, mob); + + if (this->IsClient() && this->GetTarget() && this->CastToClient()->hp_other_update_throttle_timer.Check()) + this->GetTarget()->SendHPUpdate(false, true); } float Mob::FindGroundZ(float new_x, float new_y, float z_offset) @@ -3806,7 +3870,7 @@ int32 Mob::GetVulnerability(Mob* caster, uint32 spell_id, uint32 ticsremaining) if((IsValidSpell(buffs[i].spellid) && IsEffectInSpell(buffs[i].spellid, SE_FcSpellVulnerability))){ - int32 focus = caster->CalcFocusEffect(focusSpellVulnerability, buffs[i].spellid, spell_id); + int32 focus = caster->CalcFocusEffect(focusSpellVulnerability, buffs[i].spellid, spell_id, true); if (!focus) continue; @@ -3824,6 +3888,8 @@ int32 Mob::GetVulnerability(Mob* caster, uint32 spell_id, uint32 ticsremaining) } } + tmp_focus = caster->CalcFocusEffect(focusSpellVulnerability, buffs[tmp_buffslot].spellid, spell_id); + if (tmp_focus < -99) tmp_focus = -99; @@ -4652,16 +4718,13 @@ bool Mob::TrySpellOnDeath() //in death because the heal will not register before the script kills you. } -int16 Mob::GetCritDmgMob(uint16 skill) +int16 Mob::GetCritDmgMod(uint16 skill) { int critDmg_mod = 0; // All skill dmg mod + Skill specific - critDmg_mod += itembonuses.CritDmgMob[EQEmu::skills::HIGHEST_SKILL + 1] + spellbonuses.CritDmgMob[EQEmu::skills::HIGHEST_SKILL + 1] + aabonuses.CritDmgMob[EQEmu::skills::HIGHEST_SKILL + 1] + - itembonuses.CritDmgMob[skill] + spellbonuses.CritDmgMob[skill] + aabonuses.CritDmgMob[skill]; - - if(critDmg_mod < -100) - critDmg_mod = -100; + critDmg_mod += itembonuses.CritDmgMod[EQEmu::skills::HIGHEST_SKILL + 1] + spellbonuses.CritDmgMod[EQEmu::skills::HIGHEST_SKILL + 1] + aabonuses.CritDmgMod[EQEmu::skills::HIGHEST_SKILL + 1] + + itembonuses.CritDmgMod[skill] + spellbonuses.CritDmgMod[skill] + aabonuses.CritDmgMod[skill]; return critDmg_mod; } diff --git a/zone/mob.h b/zone/mob.h index 4d43a9572..bdc1e7c3a 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -45,6 +45,8 @@ class EQApplicationPacket; class Group; class NPC; class Raid; +class Aura; +struct AuraRecord; struct NewSpawn_Struct; struct PlayerPositionUpdateServer_Struct; @@ -85,6 +87,23 @@ public: int params[MAX_SPECIAL_ATTACK_PARAMS]; }; + struct AuraInfo { + char name[64]; + int spawn_id; + int icon; + Aura *aura; + AuraInfo() : spawn_id(0), icon(0), aura(nullptr) + { + memset(name, 0, 64); + } + }; + + struct AuraMgr { + int count; // active auras + AuraInfo auras[AURA_HARDCAP]; + AuraMgr() : count(0) { } + }; + Mob(const char* in_name, const char* in_lastname, int32 in_cur_hp, @@ -233,7 +252,7 @@ public: inline bool SeeImprovedHide() const { return see_improved_hide; } bool IsInvisible(Mob* other = 0) const; void SetInvisible(uint8 state); - bool AttackAnimation(EQEmu::skills::SkillType &skillinuse, int Hand, const EQEmu::ItemInstance* weapon); + EQEmu::skills::SkillType AttackAnimation(int Hand, const EQEmu::ItemInstance* weapon, EQEmu::skills::SkillType skillinuse = EQEmu::skills::Skill1HBlunt); //Song bool UseBardSpellLogic(uint16 spell_id = 0xffff, int slot = -1); @@ -308,6 +327,7 @@ public: 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 BuffFadeAll(); void BuffFadeNonPersistDeath(); @@ -315,6 +335,7 @@ public: void BuffFadeBySlot(int slot, bool iRecalcBonuses = true); void BuffFadeDetrimentalByCaster(Mob *caster); void BuffFadeBySitModifier(); + bool IsAffectedByBuff(uint16 spell_id); void BuffModifyDurationBySpellID(uint16 spell_id, int32 newDuration); int AddBuff(Mob *caster, const uint16 spell_id, int duration = 0, int32 level_override = -1); int CanBuffStack(uint16 spellid, uint8 caster_level, bool iFailIfOverwrite = false); @@ -446,6 +467,9 @@ public: inline StatBonuses GetItemBonuses() const { return itembonuses; } inline StatBonuses GetSpellBonuses() const { return spellbonuses; } inline StatBonuses GetAABonuses() const { return aabonuses; } + inline StatBonuses* GetItemBonusesPtr() { return &itembonuses; } + inline StatBonuses* GetSpellBonusesPtr() { return &spellbonuses; } + inline StatBonuses* GetAABonusesPtr() { return &aabonuses; } inline virtual int32 GetMaxSTR() const { return GetSTR(); } inline virtual int32 GetMaxSTA() const { return GetSTA(); } inline virtual int32 GetMaxDEX() const { return GetDEX(); } @@ -463,14 +487,14 @@ public: inline int32 GetMaxHP() const { return max_hp; } virtual int32 CalcMaxHP(); inline int32 GetMaxMana() const { return max_mana; } - inline int32 GetMana() const { return cur_mana; } + inline int32 GetMana() const { return current_mana; } virtual int32 GetEndurance() const { return 0; } virtual void SetEndurance(int32 newEnd) { return; } int32 GetItemHPBonuses(); int32 GetSpellHPBonuses(); virtual const int32& SetMana(int32 amount); inline float GetManaRatio() const { return max_mana == 0 ? 100 : - ((static_cast(cur_mana) / max_mana) * 100); } + ((static_cast(current_mana) / max_mana) * 100); } virtual int32 CalcMaxMana(); uint32 GetNPCTypeID() const { return npctype_id; } void SetNPCTypeID(uint32 npctypeid) { npctype_id = npctypeid; } @@ -489,6 +513,7 @@ public: inline const float GetTarVZ() const { return m_TargetV.z; } inline const float GetTarVector() const { return tar_vector; } inline const uint8 GetTarNDX() const { return tar_ndx; } + inline const int8 GetFlyMode() const { return flymode; } bool IsBoat() const; //Group @@ -518,12 +543,13 @@ public: virtual void GMMove(float x, float y, float z, float heading = 0.01, bool SendUpdate = true); void SetDelta(const glm::vec4& delta); void SetTargetDestSteps(uint8 target_steps) { tar_ndx = target_steps; } - void SendPosUpdate(uint8 iSendToSelf = 0); + void SendPositionUpdate(uint8 iSendToSelf = 0); void MakeSpawnUpdateNoDelta(PlayerPositionUpdateServer_Struct* spu); void MakeSpawnUpdate(PlayerPositionUpdateServer_Struct* spu); void SendPosition(); void SetSpawned() { spawned = true; }; bool Spawned() { return spawned; }; + virtual bool ShouldISpawnFor(Client *c) { return true; } void SetFlyMode(uint8 flymode); inline void Teleport(glm::vec3 NewPosition) { m_Position.x = NewPosition.x; m_Position.y = NewPosition.y; m_Position.z = NewPosition.z; }; @@ -533,7 +559,7 @@ public: inline uint32 GetLevelCon(uint8 iOtherLevel) const { return this ? GetLevelCon(GetLevel(), iOtherLevel) : CON_GRAY; } virtual void AddToHateList(Mob* other, uint32 hate = 0, int32 damage = 0, bool iYellForHelp = true, - bool bFrenzy = false, bool iBuffTic = false, uint16 spell_id = SPELL_UNKNOWN); + bool bFrenzy = false, bool iBuffTic = false, uint16 spell_id = SPELL_UNKNOWN, bool pet_comand = false); bool RemoveFromHateList(Mob* mob); void SetHateAmountOnEnt(Mob* other, int32 hate = 0, int32 damage = 0) { hate_list.SetHateAmountOnEnt(other,hate,damage);} void HalveAggro(Mob *other) { uint32 in_hate = GetHateAmount(other); SetHateAmountOnEnt(other, (in_hate > 1 ? in_hate / 2 : 1)); } @@ -552,7 +578,7 @@ public: void SetPrimaryAggro(bool value) { PrimaryAggro = value; if (value) AssistAggro = false; } void SetAssistAggro(bool value) { AssistAggro = value; if (PrimaryAggro) AssistAggro = false; } bool HateSummon(); - void FaceTarget(Mob* MobToFace = 0); + void FaceTarget(Mob* mob_to_face = 0); void SetHeading(float iHeading) { if(m_Position.w != iHeading) { pLastChange = Timer::GetCurrentTime(); m_Position.w = iHeading; } } void WipeHateList(); @@ -579,7 +605,7 @@ 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); + void SendHPUpdate(bool skip_self = false, bool force_update_all = false); virtual void ResetHPUpdateTimer() {}; // does nothing //Util @@ -600,6 +626,19 @@ public: bool PlotPositionAroundTarget(Mob* target, float &x_dest, float &y_dest, float &z_dest, bool lookForAftArc = true); + // aura functions + void MakeAura(uint16 spell_id); + inline int GetAuraSlots() { return 1 + aabonuses.aura_slots + itembonuses.aura_slots + spellbonuses.aura_slots; } + inline int GetTrapSlots() { return 1 + aabonuses.trap_slots + itembonuses.trap_slots + spellbonuses.trap_slots; } + inline bool HasFreeAuraSlots() { return aura_mgr.count < GetAuraSlots(); } + inline bool HasFreeTrapSlots() { return trap_mgr.count < GetTrapSlots(); } + void AddAura(Aura *aura, AuraRecord &record); + void AddTrap(Aura *aura, AuraRecord &record); + bool CanSpawnAura(bool trap); + void RemoveAura(int spawn_id, bool skip_strip = false, bool expired = false); + void RemoveAllAuras(); + inline AuraMgr &GetAuraMgr() { return aura_mgr; } // mainly used for zone db loading/saving + //Procs void TriggerDefensiveProcs(Mob *on, uint16 hand = EQEmu::inventory::slotPrimary, bool FromSkillProc = false, int damage = 0); bool AddRangedProc(uint16 spell_id, uint16 iChance = 3, uint16 base_spell_id = SPELL_UNKNOWN); @@ -642,6 +681,10 @@ public: const char *message6 = 0, const char *message7 = 0, const char *message8 = 0, const char *message9 = 0); void Say_StringID(uint32 type, uint32 string_id, 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); + void SayTo_StringID(Client *to, uint32 string_id, 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); + void SayTo_StringID(Client *to, uint32 type, uint32 string_id, 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); void Shout(const char *format, ...); void Emote(const char *format, ...); void QuestJournalledSay(Client *QuestInitiator, const char *str); @@ -690,7 +733,7 @@ public: void CastOnCure(uint32 spell_id); void CastOnNumHitFade(uint32 spell_id); void SlowMitigation(Mob* caster); - int16 GetCritDmgMob(uint16 skill); + int16 GetCritDmgMod(uint16 skill); int16 GetMeleeDamageMod_SE(uint16 skill); int16 GetMeleeMinDamageMod_SE(uint16 skill); int16 GetCrippBlowChance(); @@ -869,10 +912,16 @@ public: inline const eStandingPetOrder GetPetOrder() const { return pStandingPetOrder; } inline void SetHeld(bool nState) { held = nState; } inline const bool IsHeld() const { return held; } + inline void SetGHeld(bool nState) { gheld = nState; } + inline const bool IsGHeld() const { return gheld; } inline void SetNoCast(bool nState) { nocast = nState; } inline const bool IsNoCast() const { return nocast; } inline void SetFocused(bool nState) { focused = nState; } inline const bool IsFocused() const { return focused; } + inline void SetPetStop(bool nState) { pet_stop = nState; } + inline const bool IsPetStop() const { return pet_stop; } + inline void SetPetRegroup(bool nState) { pet_regroup = nState; } + inline const bool IsPetRegroup() const { return pet_regroup; } inline const bool IsRoamer() const { return roamer; } inline const int GetWanderType() const { return wandertype; } inline const bool IsRooted() const { return rooted || permarooted; } @@ -900,6 +949,7 @@ public: float GetGroundZ(float new_x, float new_y, float z_offset=0.0); void SendTo(float new_x, float new_y, float new_z); void SendToFixZ(float new_x, float new_y, float new_z); + void FixZ(); void NPCSpecialAttacks(const char* parse, int permtag, bool reset = true, bool remove = false); inline uint32 DontHealMeBefore() const { return pDontHealMeBefore; } inline uint32 DontBuffMeBefore() const { return pDontBuffMeBefore; } @@ -983,7 +1033,7 @@ public: Timer GetAttackTimer() { return attack_timer; } Timer GetAttackDWTimer() { return attack_dw_timer; } inline bool IsFindable() { return findable; } - inline uint8 GetManaPercent() { return (uint8)((float)cur_mana / (float)max_mana * 100.0f); } + inline uint8 GetManaPercent() { return (uint8)((float)current_mana / (float)max_mana * 100.0f); } virtual uint8 GetEndurancePercent() { return 0; } inline virtual bool IsBlockedBuff(int16 SpellID) { return false; } @@ -1050,6 +1100,10 @@ public: void AddAssistCap() { ++npc_assist_cap; } void DelAssistCap() { --npc_assist_cap; } void ResetAssistCap() { npc_assist_cap = 0; } + int GetWeaponDamage(Mob *against, const EQEmu::ItemData *weapon_item); + int GetWeaponDamage(Mob *against, const EQEmu::ItemInstance *weapon_item, uint32 *hate = nullptr); + + float last_z; // Bots HealRotation methods #ifdef BOTS @@ -1072,7 +1126,7 @@ protected: int _GetWalkSpeed() const; int _GetRunSpeed() const; int _GetFearSpeed() const; - virtual bool MakeNewPositionAndSendUpdate(float x, float y, float z, int speed, bool checkZ); + virtual bool MakeNewPositionAndSendUpdate(float x, float y, float z, int speed); virtual bool AI_EngagedCastCheck() { return(false); } virtual bool AI_PursueCastCheck() { return(false); } @@ -1126,7 +1180,7 @@ protected: int32 cur_hp; int32 max_hp; int32 base_hp; - int32 cur_mana; + int32 current_mana; int32 max_mana; int32 hp_regen; int32 mana_regen; @@ -1165,6 +1219,8 @@ protected: uint8 orig_level; uint32 npctype_id; glm::vec4 m_Position; + /* Used to determine when an NPC has traversed so many units - to send a zone wide pos update */ + glm::vec4 last_major_update_position; uint16 animation; float base_size; float size; @@ -1179,8 +1235,11 @@ protected: uint32 pLastChange; bool held; + bool gheld; bool nocast; bool focused; + bool pet_stop; + bool pet_regroup; bool spawned; void CalcSpellBonuses(StatBonuses* newbon); virtual void CalcBonuses(); @@ -1196,8 +1255,6 @@ protected: virtual float GetDefensiveProcChances(float &ProcBonus, float &ProcChance, uint16 hand = EQEmu::inventory::slotPrimary, Mob *on = nullptr); virtual float GetSkillProcChances(uint16 ReuseTime, uint16 hand = 0); // hand = MainCharm? uint16 GetWeaponSpeedbyHand(uint16 hand); - int GetWeaponDamage(Mob *against, const EQEmu::ItemData *weapon_item); - int GetWeaponDamage(Mob *against, const EQEmu::ItemInstance *weapon_item, uint32 *hate = nullptr); #ifdef BOTS virtual #endif @@ -1353,10 +1410,13 @@ protected: void ClearItemFactionBonuses(); void CalculateFearPosition(); - uint32 move_tic_count; bool flee_mode; Timer flee_timer; + Timer fix_z_timer; + Timer fix_z_timer_engaged; + Timer attack_anim_timer; + Timer position_update_melee_push_timer; bool pAIControlled; bool roamer; @@ -1365,6 +1425,9 @@ protected: int wandertype; int pausetype; + int8 last_hp_percent; + int32 last_hp; + int cur_wp; glm::vec4 m_CurrentWayPoint; int cur_wp_pause; @@ -1437,6 +1500,9 @@ protected: bool IsHorse; + AuraMgr aura_mgr; + AuraMgr trap_mgr; + private: void _StopSong(); //this is not what you think it is Mob* target; diff --git a/zone/mob_ai.cpp b/zone/mob_ai.cpp index ddd6d1c76..14e9337ac 100644 --- a/zone/mob_ai.cpp +++ b/zone/mob_ai.cpp @@ -743,6 +743,10 @@ void Client::AI_Process() if(RuleB(Combat, EnableFearPathing)){ if(currently_fleeing) { + + if (fix_z_timer_engaged.Check()) + this->FixZ(); + if(IsRooted()) { //make sure everybody knows were not moving, for appearance sake if(IsMoving()) @@ -782,6 +786,7 @@ void Client::AI_Process() } return; } + } } @@ -935,7 +940,7 @@ void Mob::AI_Process() { bool engaged = IsEngaged(); bool doranged = false; - if (!zone->CanDoCombat()) { + if (!zone->CanDoCombat() || IsPetStop() || IsPetRegroup()) { engaged = false; } @@ -943,7 +948,7 @@ void Mob::AI_Process() { // if(RuleB(Combat, EnableFearPathing)){ if(currently_fleeing) { - if(IsRooted() || (IsBlind() && CombatRange(hate_list.GetClosestEntOnHateList(this)))) { + if((IsRooted() || (IsBlind() && CombatRange(hate_list.GetClosestEntOnHateList(this)))) && !IsPetStop() && !IsPetRegroup()) { //make sure everybody knows were not moving, for appearance sake if(IsMoving()) { @@ -991,6 +996,26 @@ void Mob::AI_Process() { if (engaged) { + /* Fix Z when following during pull, not when engaged and stationary */ + if (moving && fix_z_timer_engaged.Check()) { + if (this->GetTarget()) { + /* If we are engaged, moving and following client, let's look for best Z more often */ + float target_distance = DistanceNoZ(this->GetPosition(), this->GetTarget()->GetPosition()); + if (target_distance >= 25) { + this->FixZ(); + } + else if (!this->CheckLosFN(this->GetTarget())) { + Mob* target = this->GetTarget(); + + m_Position.x = target->GetX(); + m_Position.y = target->GetY(); + m_Position.z = target->GetZ(); + m_Position.w = target->GetHeading(); + SendPosition(); + } + } + } + if (!(m_PlayerState & static_cast(PlayerState::Aggressive))) SendAddPlayerState(PlayerState::Aggressive); // we are prevented from getting here if we are blind and don't have a target in range @@ -1300,10 +1325,12 @@ void Mob::AI_Process() { } } else { - if (m_PlayerState & static_cast(PlayerState::Aggressive)) SendRemovePlayerState(PlayerState::Aggressive); + if (IsPetStop()) // pet stop won't be engaged, so we will always get here and we want the above branch to execute + return; + if(zone->CanDoCombat() && AI_feign_remember_timer->Check()) { // 6/14/06 // Improved Feign Death Memory @@ -1409,6 +1436,8 @@ void Mob::AI_Process() { break; } } + if (IsPetRegroup()) + return; } /* Entity has been assigned another entity to follow */ else if (GetFollowID()) @@ -1507,7 +1536,11 @@ void NPC::AI_DoMovement() { Log(Logs::Detail, Logs::AI, "Roam Box: d=%.3f (%.3f->%.3f,%.3f->%.3f): Go To (%.3f,%.3f)", roambox_distance, roambox_min_x, roambox_max_x, roambox_min_y, roambox_max_y, roambox_movingto_x, roambox_movingto_y); - if (!CalculateNewPosition2(roambox_movingto_x, roambox_movingto_y, GetZ(), walksp, true)) + + float new_z = this->FindGroundZ(m_Position.x, m_Position.y, 5); + new_z += (this->GetSize() / 1.55); + + if (!CalculateNewPosition2(roambox_movingto_x, roambox_movingto_y, new_z, walksp, true)) { roambox_movingto_x = roambox_max_x + 1; // force update pLastFightingDelayMoving = Timer::GetCurrentTime() + RandomTimer(roambox_min_delay, roambox_delay); @@ -1544,6 +1577,8 @@ void NPC::AI_DoMovement() { SetHeading(m_CurrentWayPoint.w); } + this->FixZ(); + SendPosition(); //kick off event_waypoint arrive diff --git a/zone/net.cpp b/zone/net.cpp index 982b45d02..eafdde807 100644 --- a/zone/net.cpp +++ b/zone/net.cpp @@ -391,8 +391,7 @@ int main(int argc, char** argv) { parse = new QuestParserCollection(); #ifdef LUA_EQEMU - auto lua_parser = new LuaParser(); - parse->RegisterQuestInterface(lua_parser, "lua"); + parse->RegisterQuestInterface(LuaParser::Instance(), "lua"); #endif #ifdef EMBPERL @@ -542,6 +541,9 @@ int main(int argc, char** argv) { if (previous_loaded && !current_loaded) { process_timer.Stop(); process_timer.Start(1000, true); + + uint32 shutdown_timer = database.getZoneShutDownDelay(zone->GetZoneID(), zone->GetInstanceVersion()); + zone->StartShutdownTimer(shutdown_timer); } else if (!previous_loaded && current_loaded) { process_timer.Stop(); @@ -565,10 +567,6 @@ int main(int argc, char** argv) { safe_delete(perl_parser); #endif -#ifdef LUA_EQEMU - safe_delete(lua_parser); -#endif - safe_delete(Config); if (zone != 0) diff --git a/zone/npc.cpp b/zone/npc.cpp index de4ad4e7c..a4e6c4214 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -375,6 +375,7 @@ NPC::NPC(const NPCType* d, Spawn2* in_respawn, const glm::vec4& position, int if CalcBonuses(); raid_target = d->raid_target; ignore_despawn = d->ignore_despawn; + m_targetable = !d->untargetable; } NPC::~NPC() @@ -615,37 +616,54 @@ bool NPC::Process() if (currently_fleeing) ProcessFlee(); - uint32 bonus = 0; + uint32 sitting_bonus = 0; + uint32 petbonus = 0; + uint32 bestregen = 0; + int32 dbregen = GetNPCHPRegen(); if (GetAppearance() == eaSitting) - bonus += 3; + sitting_bonus += 3; int32 OOCRegen = 0; if (oocregen > 0) { //should pull from Mob class OOCRegen += GetMaxHP() * oocregen / 100; } - //Lieka Edit:Fixing NPC regen.NPCs should regen to full during a set duration, not based on their HPs.Increase NPC's HPs by % of total HPs / tick. + + // Fixing NPC regen.NPCs should regen to full during + // a set duration, not based on their HPs.Increase NPC's HPs by + // % of total HPs / tick. + // + // If oocregen set in db, apply to pets as well. + // This allows the obscene #s for pets in the db to be tweaked + // while maintaining a decent ooc regen. + + bestregen = std::max(dbregen,OOCRegen); + if ((GetHP() < GetMaxHP()) && !IsPet()) { - if (!IsEngaged()) {//NPC out of combat - if (GetNPCHPRegen() > OOCRegen) - SetHP(GetHP() + GetNPCHPRegen()); - else - SetHP(GetHP() + OOCRegen); - } + if (!IsEngaged()) + SetHP(GetHP() + bestregen + sitting_bonus); else - SetHP(GetHP() + GetNPCHPRegen()); + SetHP(GetHP() + dbregen); } else if (GetHP() < GetMaxHP() && GetOwnerID() != 0) { - if (!IsEngaged()) //pet - SetHP(GetHP() + GetNPCHPRegen() + bonus + (GetLevel() / 5)); + if (!IsEngaged()) { + if (oocregen > 0) { + petbonus = std::max(OOCRegen,dbregen); + } + else { + petbonus = dbregen + (GetLevel() / 5); + } + + SetHP(GetHP() + sitting_bonus + petbonus); + } else - SetHP(GetHP() + GetNPCHPRegen() + bonus); + SetHP(GetHP() + dbregen); } else - SetHP(GetHP() + GetNPCHPRegen()); + SetHP(GetHP() + dbregen + sitting_bonus); if (GetMana() < GetMaxMana()) { - SetMana(GetMana() + mana_regen + bonus); + SetMana(GetMana() + mana_regen + sitting_bonus); } @@ -1889,6 +1907,7 @@ void NPC::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) ns->spawn.is_npc = 1; UpdateActiveLight(); ns->spawn.light = GetActiveLightType(); + ns->spawn.show_name = NPCTypedata->show_name; } void NPC::PetOnSpawn(NewSpawn_Struct* ns) @@ -1973,7 +1992,7 @@ void NPC::ModifyNPCStat(const char *identifier, const char *newValue) else if(id == "int" || id == "_int") { INT = atoi(val.c_str()); CalcMaxMana(); return; } else if(id == "cha") { CHA = atoi(val.c_str()); return; } else if(id == "max_hp") { base_hp = atoi(val.c_str()); CalcMaxHP(); if (cur_hp > max_hp) { cur_hp = max_hp; } return; } - else if(id == "max_mana") { npc_mana = atoi(val.c_str()); CalcMaxMana(); if (cur_mana > max_mana){ cur_mana = max_mana; } return; } + else if(id == "max_mana") { npc_mana = atoi(val.c_str()); CalcMaxMana(); if (current_mana > max_mana){ current_mana = max_mana; } return; } else if(id == "mr") { MR = atoi(val.c_str()); return; } else if(id == "fr") { FR = atoi(val.c_str()); return; } else if(id == "cr") { CR = atoi(val.c_str()); return; } diff --git a/zone/npc.h b/zone/npc.h index 30e54da55..c973eb79d 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -90,6 +90,7 @@ class Client; class Group; class Raid; class Spawn2; +class Aura; namespace EQEmu { @@ -425,6 +426,7 @@ protected: NPCType* NPCTypedata_ours; //special case for npcs with uniquely created data. friend class EntityList; + friend class Aura; std::list faction_list; uint32 copper; uint32 silver; diff --git a/zone/pathing.cpp b/zone/pathing.cpp index 417ee3af2..0bc054d58 100644 --- a/zone/pathing.cpp +++ b/zone/pathing.cpp @@ -206,7 +206,7 @@ glm::vec3 PathManager::GetPathNodeCoordinates(int NodeNumber, bool BestZ) std::deque PathManager::FindRoute(int startID, int endID) { - Log(Logs::Detail, Logs::None, "FindRoute from node %i to %i", startID, endID); + Log(Logs::Detail, Logs::Pathing, "FindRoute from node %i to %i", startID, endID); memset(ClosedListFlag, 0, sizeof(int) * Head.PathNodeCount); @@ -329,7 +329,7 @@ std::deque PathManager::FindRoute(int startID, int endID) } } - Log(Logs::Detail, Logs::None, "Unable to find a route."); + Log(Logs::Detail, Logs::Pathing, "Unable to find a route."); return Route; } @@ -351,7 +351,7 @@ auto path_compare = [](const PathNodeSortStruct& a, const PathNodeSortStruct& b) std::deque PathManager::FindRoute(glm::vec3 Start, glm::vec3 End) { - Log(Logs::Detail, Logs::None, "FindRoute(%8.3f, %8.3f, %8.3f, %8.3f, %8.3f, %8.3f)", Start.x, Start.y, Start.z, End.x, End.y, End.z); + Log(Logs::Detail, Logs::Pathing, "FindRoute(%8.3f, %8.3f, %8.3f, %8.3f, %8.3f, %8.3f)", Start.x, Start.y, Start.z, End.x, End.y, End.z); std::deque noderoute; @@ -384,7 +384,7 @@ std::deque PathManager::FindRoute(glm::vec3 Start, glm::vec3 End) for(auto Iterator = SortedByDistance.begin(); Iterator != SortedByDistance.end(); ++Iterator) { - Log(Logs::Detail, Logs::None, "Checking Reachability of Node %i from Start Position.", PathNodes[(*Iterator).id].id); + Log(Logs::Detail, Logs::Pathing, "Checking Reachability of Node %i from Start Position.", PathNodes[(*Iterator).id].id); if(!zone->zonemap->LineIntersectsZone(Start, PathNodes[(*Iterator).id].v, 1.0f, nullptr)) { @@ -394,11 +394,11 @@ std::deque PathManager::FindRoute(glm::vec3 Start, glm::vec3 End) } if(ClosestPathNodeToStart <0 ) { - Log(Logs::Detail, Logs::None, "No LOS to any starting Path Node within range."); + Log(Logs::Detail, Logs::Pathing, "No LOS to any starting Path Node within range."); return noderoute; } - Log(Logs::Detail, Logs::None, "Closest Path Node To Start: %2d", ClosestPathNodeToStart); + Log(Logs::Detail, Logs::Pathing, "Closest Path Node To Start: %2d", ClosestPathNodeToStart); // Find the nearest PathNode the end point has LOS to @@ -421,8 +421,8 @@ std::deque PathManager::FindRoute(glm::vec3 Start, glm::vec3 End) for(auto Iterator = SortedByDistance.begin(); Iterator != SortedByDistance.end(); ++Iterator) { - Log(Logs::Detail, Logs::None, "Checking Reachability of Node %i from End Position.", PathNodes[(*Iterator).id].id); - Log(Logs::Detail, Logs::None, " (%8.3f, %8.3f, %8.3f) to (%8.3f, %8.3f, %8.3f)", + Log(Logs::Detail, Logs::Pathing, "Checking Reachability of Node %i from End Position.", PathNodes[(*Iterator).id].id); + Log(Logs::Detail, Logs::Pathing, " (%8.3f, %8.3f, %8.3f) to (%8.3f, %8.3f, %8.3f)", End.x, End.y, End.z, PathNodes[(*Iterator).id].v.x, PathNodes[(*Iterator).id].v.y, PathNodes[(*Iterator).id].v.z); @@ -434,11 +434,11 @@ std::deque PathManager::FindRoute(glm::vec3 Start, glm::vec3 End) } if(ClosestPathNodeToEnd < 0) { - Log(Logs::Detail, Logs::None, "No LOS to any end Path Node within range."); + Log(Logs::Detail, Logs::Pathing, "No LOS to any end Path Node within range."); return noderoute; } - Log(Logs::Detail, Logs::None, "Closest Path Node To End: %2d", ClosestPathNodeToEnd); + Log(Logs::Detail, Logs::Pathing, "Closest Path Node To End: %2d", ClosestPathNodeToEnd); if(ClosestPathNodeToStart == ClosestPathNodeToEnd) { @@ -673,7 +673,7 @@ glm::vec3 Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Wa if(To == From) return To; - Log(Logs::Detail, Logs::None, "UpdatePath. From(%8.3f, %8.3f, %8.3f) To(%8.3f, %8.3f, %8.3f)", From.x, From.y, From.z, To.x, To.y, To.z); + Log(Logs::Detail, Logs::Pathing, "UpdatePath. From(%8.3f, %8.3f, %8.3f) To(%8.3f, %8.3f, %8.3f)", From.x, From.y, From.z, To.x, To.y, To.z); if(From == PathingLastPosition) { @@ -681,7 +681,7 @@ glm::vec3 Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Wa if((PathingLoopCount > 5) && !IsRooted()) { - Log(Logs::Detail, Logs::None, "appears to be stuck. Teleporting them to next position.", GetName()); + Log(Logs::Detail, Logs::Pathing, "appears to be stuck. Teleporting them to next position.", GetName()); if(Route.empty()) { @@ -721,7 +721,7 @@ glm::vec3 Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Wa // If we are already pathing, and the destination is the same as before ... if(SameDestination) { - Log(Logs::Detail, Logs::None, " Still pathing to the same destination."); + Log(Logs::Detail, Logs::Pathing, " Still pathing to the same destination."); // Get the coordinates of the first path node we are going to. NextNode = Route.front(); @@ -730,9 +730,9 @@ glm::vec3 Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Wa // May need to refine this as rounding errors may mean we never have equality // We have reached the path node. - if(NodeLoc == From) + if(NodeLoc.x == From.x && NodeLoc.y == From.y) { - Log(Logs::Detail, Logs::None, " Arrived at node %i", NextNode); + Log(Logs::Detail, Logs::Pathing, " Arrived at node %i", NextNode); NodeReached = true; @@ -746,36 +746,36 @@ glm::vec3 Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Wa // target, and we may run past the target if we don't check LOS at this point. int RouteSize = Route.size(); - Log(Logs::Detail, Logs::None, "Route size is %i", RouteSize); + Log(Logs::Detail, Logs::Pathing, "Route size is %i", RouteSize); if((RouteSize == 2) || ((PathingTraversedNodes >= RuleI(Pathing, MinNodesTraversedForLOSCheck)) && (RouteSize <= RuleI(Pathing, MinNodesLeftForLOSCheck)) && PathingLOSCheckTimer->Check())) { - Log(Logs::Detail, Logs::None, " Checking distance to target."); + Log(Logs::Detail, Logs::Pathing, " Checking distance to target."); float Distance = VectorDistanceNoRoot(From, To); - Log(Logs::Detail, Logs::None, " Distance between From and To (NoRoot) is %8.3f", Distance); + Log(Logs::Detail, Logs::Pathing, " Distance between From and To (NoRoot) is %8.3f", Distance); if ((Distance <= RuleR(Pathing, MinDistanceForLOSCheckShort)) && - (std::abs(From.z - To.z) <= RuleR(Pathing, ZDiffThreshold))) { + (std::abs(From.z - To.z) <= RuleR(Pathing, ZDiffThresholdNew))) { if(!zone->zonemap->LineIntersectsZone(HeadPosition, To, 1.0f, nullptr)) PathingLOSState = HaveLOS; else PathingLOSState = NoLOS; - Log(Logs::Detail, Logs::None, "NoLOS"); + Log(Logs::Detail, Logs::Pathing, "NoLOS"); if((PathingLOSState == HaveLOS) && zone->pathing->NoHazards(From, To)) { - Log(Logs::Detail, Logs::None, " No hazards. Running directly to target."); + Log(Logs::Detail, Logs::Pathing, " No hazards. Running directly to target."); Route.clear(); return To; } else { - Log(Logs::Detail, Logs::None, " Continuing on node path."); + Log(Logs::Detail, Logs::Pathing, " Continuing on node path."); } } else @@ -801,7 +801,7 @@ glm::vec3 Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Wa if(Route.empty()) { - Log(Logs::Detail, Logs::None, "Missing node after teleport."); + Log(Logs::Detail, Logs::Pathing, "Missing node after teleport."); return To; } @@ -811,7 +811,7 @@ glm::vec3 Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Wa Teleport(NodeLoc); - Log(Logs::Detail, Logs::None, " TELEPORTED to %8.3f, %8.3f, %8.3f\n", NodeLoc.x, NodeLoc.y, NodeLoc.z); + Log(Logs::Detail, Logs::Pathing, " TELEPORTED to %8.3f, %8.3f, %8.3f\n", NodeLoc.x, NodeLoc.y, NodeLoc.z); Route.pop_front(); @@ -822,7 +822,7 @@ glm::vec3 Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Wa } zone->pathing->OpenDoors(PathingLastNodeVisited, NextNode, this); - Log(Logs::Detail, Logs::None, " Now moving to node %i", NextNode); + Log(Logs::Detail, Logs::Pathing, " Now moving to node %i", NextNode); return zone->pathing->GetPathNodeCoordinates(NextNode); } @@ -830,7 +830,7 @@ glm::vec3 Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Wa { // we have run all the nodes, all that is left is the direct path from the last node // to the destination - Log(Logs::Detail, Logs::None, " Reached end of node path, running direct to target."); + Log(Logs::Detail, Logs::Pathing, " Reached end of node path, running direct to target."); return To; } @@ -844,30 +844,30 @@ glm::vec3 Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Wa && (RouteSize <= RuleI(Pathing, MinNodesLeftForLOSCheck)) && PathingLOSCheckTimer->Check()) { - Log(Logs::Detail, Logs::None, " Checking distance to target."); + Log(Logs::Detail, Logs::Pathing, " Checking distance to target."); float Distance = VectorDistanceNoRoot(From, To); - Log(Logs::Detail, Logs::None, " Distance between From and To (NoRoot) is %8.3f", Distance); + Log(Logs::Detail, Logs::Pathing, " Distance between From and To (NoRoot) is %8.3f", Distance); if ((Distance <= RuleR(Pathing, MinDistanceForLOSCheckShort)) && - (std::abs(From.z - To.z) <= RuleR(Pathing, ZDiffThreshold))) { + (std::abs(From.z - To.z) <= RuleR(Pathing, ZDiffThresholdNew))) { if(!zone->zonemap->LineIntersectsZone(HeadPosition, To, 1.0f, nullptr)) PathingLOSState = HaveLOS; else PathingLOSState = NoLOS; - Log(Logs::Detail, Logs::None, "NoLOS"); + Log(Logs::Detail, Logs::Pathing, "NoLOS"); if((PathingLOSState == HaveLOS) && zone->pathing->NoHazards(From, To)) { - Log(Logs::Detail, Logs::None, " No hazards. Running directly to target."); + Log(Logs::Detail, Logs::Pathing, " No hazards. Running directly to target."); Route.clear(); return To; } else { - Log(Logs::Detail, Logs::None, " Continuing on node path."); + Log(Logs::Detail, Logs::Pathing, " Continuing on node path."); } } else @@ -879,7 +879,7 @@ glm::vec3 Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Wa { // We get here if we were already pathing, but our destination has now changed. // - Log(Logs::Detail, Logs::None, " Target has changed position."); + Log(Logs::Detail, Logs::Pathing, " Target has changed position."); // Update our record of where we are going to. PathingDestination = To; // Check if we now have LOS etc to the new destination. @@ -888,24 +888,24 @@ glm::vec3 Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Wa float Distance = VectorDistanceNoRoot(From, To); if ((Distance <= RuleR(Pathing, MinDistanceForLOSCheckShort)) && - (std::abs(From.z - To.z) <= RuleR(Pathing, ZDiffThreshold))) { - Log(Logs::Detail, Logs::None, " Checking for short LOS at distance %8.3f.", Distance); + (std::abs(From.z - To.z) <= RuleR(Pathing, ZDiffThresholdNew))) { + Log(Logs::Detail, Logs::Pathing, " Checking for short LOS at distance %8.3f.", Distance); if(!zone->zonemap->LineIntersectsZone(HeadPosition, To, 1.0f, nullptr)) PathingLOSState = HaveLOS; else PathingLOSState = NoLOS; - Log(Logs::Detail, Logs::None, "NoLOS"); + Log(Logs::Detail, Logs::Pathing, "NoLOS"); if((PathingLOSState == HaveLOS) && zone->pathing->NoHazards(From, To)) { - Log(Logs::Detail, Logs::None, " No hazards. Running directly to target."); + Log(Logs::Detail, Logs::Pathing, " No hazards. Running directly to target."); Route.clear(); return To; } else { - Log(Logs::Detail, Logs::None, " Continuing on node path."); + Log(Logs::Detail, Logs::Pathing, " Continuing on node path."); } } } @@ -916,19 +916,19 @@ glm::vec3 Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Wa { if(!PathingRouteUpdateTimerShort->Check()) { - Log(Logs::Detail, Logs::None, "Short route update timer not yet expired."); + Log(Logs::Detail, Logs::Pathing, "Short route update timer not yet expired."); return zone->pathing->GetPathNodeCoordinates(Route.front()); } - Log(Logs::Detail, Logs::None, "Short route update timer expired."); + Log(Logs::Detail, Logs::Pathing, "Short route update timer expired."); } else { if(!PathingRouteUpdateTimerLong->Check()) { - Log(Logs::Detail, Logs::None, "Long route update timer not yet expired."); + Log(Logs::Detail, Logs::Pathing, "Long route update timer not yet expired."); return zone->pathing->GetPathNodeCoordinates(Route.front()); } - Log(Logs::Detail, Logs::None, "Long route update timer expired."); + Log(Logs::Detail, Logs::Pathing, "Long route update timer expired."); } // We are already pathing, destination changed, no LOS. Find the nearest node to our destination. @@ -937,7 +937,7 @@ glm::vec3 Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Wa // Destination unreachable via pathing, return direct route. if(DestinationPathNode == -1) { - Log(Logs::Detail, Logs::None, " Unable to find path node for new destination. Running straight to target."); + Log(Logs::Detail, Logs::Pathing, " Unable to find path node for new destination. Running straight to target."); Route.clear(); return To; } @@ -945,15 +945,15 @@ glm::vec3 Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Wa // one, we will carry on on our path. if(DestinationPathNode == Route.back()) { - Log(Logs::Detail, Logs::None, " Same destination Node (%i). Continue with current path.", DestinationPathNode); + Log(Logs::Detail, Logs::Pathing, " Same destination Node (%i). Continue with current path.", DestinationPathNode); NodeLoc = zone->pathing->GetPathNodeCoordinates(Route.front()); // May need to refine this as rounding errors may mean we never have equality // Check if we have reached a path node. - if(NodeLoc == From) + if(NodeLoc.x == From.x && NodeLoc.y == From.y) { - Log(Logs::Detail, Logs::None, " Arrived at node %i, moving to next one.\n", Route.front()); + Log(Logs::Detail, Logs::Pathing, " Arrived at node %i, moving to next one.\n", Route.front()); NodeReached = true; @@ -976,7 +976,7 @@ glm::vec3 Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Wa if(Route.empty()) { - Log(Logs::Detail, Logs::None, "Missing node after teleport."); + Log(Logs::Detail, Logs::Pathing, "Missing node after teleport."); return To; } @@ -986,7 +986,7 @@ glm::vec3 Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Wa Teleport(NodeLoc); - Log(Logs::Detail, Logs::None, " TELEPORTED to %8.3f, %8.3f, %8.3f\n", NodeLoc.x, NodeLoc.y, NodeLoc.z); + Log(Logs::Detail, Logs::Pathing, " TELEPORTED to %8.3f, %8.3f, %8.3f\n", NodeLoc.x, NodeLoc.y, NodeLoc.z); Route.pop_front(); @@ -996,7 +996,7 @@ glm::vec3 Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Wa NextNode = Route.front(); } // Return the coords of our next path node on the route. - Log(Logs::Detail, Logs::None, " Now moving to node %i", NextNode); + Log(Logs::Detail, Logs::Pathing, " Now moving to node %i", NextNode); zone->pathing->OpenDoors(PathingLastNodeVisited, NextNode, this); @@ -1004,7 +1004,7 @@ glm::vec3 Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Wa } else { - Log(Logs::Detail, Logs::None, " Reached end of path grid. Running direct to target."); + Log(Logs::Detail, Logs::Pathing, " Reached end of path grid. Running direct to target."); return To; } } @@ -1012,7 +1012,7 @@ glm::vec3 Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Wa } else { - Log(Logs::Detail, Logs::None, " Target moved. End node is different. Clearing route."); + Log(Logs::Detail, Logs::Pathing, " Target moved. End node is different. Clearing route."); Route.clear(); // We will now fall through to get a new route. @@ -1022,11 +1022,11 @@ glm::vec3 Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Wa } - Log(Logs::Detail, Logs::None, " Our route list is empty."); + Log(Logs::Detail, Logs::Pathing, " Our route list is empty."); if((SameDestination) && !PathingLOSCheckTimer->Check()) { - Log(Logs::Detail, Logs::None, " Destination same as before, LOS check timer not reached. Returning To."); + Log(Logs::Detail, Logs::Pathing, " Destination same as before, LOS check timer not reached. Returning To."); return To; } @@ -1039,23 +1039,23 @@ glm::vec3 Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Wa float Distance = VectorDistanceNoRoot(From, To); if ((Distance <= RuleR(Pathing, MinDistanceForLOSCheckLong)) && - (std::abs(From.z - To.z) <= RuleR(Pathing, ZDiffThreshold))) { - Log(Logs::Detail, Logs::None, " Checking for long LOS at distance %8.3f.", Distance); + (std::abs(From.z - To.z) <= RuleR(Pathing, ZDiffThresholdNew))) { + Log(Logs::Detail, Logs::Pathing, " Checking for long LOS at distance %8.3f.", Distance); if(!zone->zonemap->LineIntersectsZone(HeadPosition, To, 1.0f, nullptr)) PathingLOSState = HaveLOS; else PathingLOSState = NoLOS; - Log(Logs::Detail, Logs::None, "NoLOS"); + Log(Logs::Detail, Logs::Pathing, "NoLOS"); if((PathingLOSState == HaveLOS) && zone->pathing->NoHazards(From, To)) { - Log(Logs::Detail, Logs::None, "Target is reachable. Running directly there."); + Log(Logs::Detail, Logs::Pathing, "Target is reachable. Running directly there."); return To; } } - Log(Logs::Detail, Logs::None, " Calculating new route to target."); + Log(Logs::Detail, Logs::Pathing, " Calculating new route to target."); Route = zone->pathing->FindRoute(From, To); @@ -1063,14 +1063,14 @@ glm::vec3 Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Wa if(Route.empty()) { - Log(Logs::Detail, Logs::None, " No route available, running direct."); + Log(Logs::Detail, Logs::Pathing, " No route available, running direct."); return To; } if(SameDestination && (Route.front() == PathingLastNodeVisited)) { - Log(Logs::Detail, Logs::None, " Probable loop detected. Same destination and Route.front() == PathingLastNodeVisited."); + Log(Logs::Detail, Logs::Pathing, " Probable loop detected. Same destination and Route.front() == PathingLastNodeVisited."); Route.clear(); @@ -1078,7 +1078,7 @@ glm::vec3 Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Wa } NodeLoc = zone->pathing->GetPathNodeCoordinates(Route.front()); - Log(Logs::Detail, Logs::None, " New route determined, heading for node %i", Route.front()); + Log(Logs::Detail, Logs::Pathing, " New route determined, heading for node %i", Route.front()); PathingLoopCount = 0; @@ -1119,7 +1119,7 @@ int PathManager::FindNearestPathNode(glm::vec3 Position) for(auto Iterator = SortedByDistance.begin(); Iterator != SortedByDistance.end(); ++Iterator) { - Log(Logs::Detail, Logs::None, "Checking Reachability of Node %i from Start Position.", PathNodes[(*Iterator).id].id); + Log(Logs::Detail, Logs::Pathing, "Checking Reachability of Node %i from Start Position.", PathNodes[(*Iterator).id].id); if(!zone->zonemap->LineIntersectsZone(Position, PathNodes[(*Iterator).id].v, 1.0f, nullptr)) { @@ -1129,7 +1129,7 @@ int PathManager::FindNearestPathNode(glm::vec3 Position) } if(ClosestPathNodeToStart <0 ) { - Log(Logs::Detail, Logs::None, "No LOS to any starting Path Node within range."); + Log(Logs::Detail, Logs::Pathing, "No LOS to any starting Path Node within range."); return -1; } return ClosestPathNodeToStart; @@ -1143,15 +1143,15 @@ bool PathManager::NoHazards(glm::vec3 From, glm::vec3 To) float NewZ = zone->zonemap->FindBestZ(MidPoint, nullptr); - if (std::abs(NewZ - From.z) > RuleR(Pathing, ZDiffThreshold)) { - Log(Logs::Detail, Logs::None, " HAZARD DETECTED moving from %8.3f, %8.3f, %8.3f to %8.3f, %8.3f, %8.3f. Z Change is %8.3f", + if (std::abs(NewZ - From.z) > RuleR(Pathing, ZDiffThresholdNew)) { + Log(Logs::Detail, Logs::Pathing, " HAZARD DETECTED moving from %8.3f, %8.3f, %8.3f to %8.3f, %8.3f, %8.3f. Z Change is %8.3f", From.x, From.y, From.z, MidPoint.x, MidPoint.y, MidPoint.z, NewZ - From.z); return false; } else { - Log(Logs::Detail, Logs::None, "No HAZARD DETECTED moving from %8.3f, %8.3f, %8.3f to %8.3f, %8.3f, %8.3f. Z Change is %8.3f", + Log(Logs::Detail, Logs::Pathing, "No HAZARD DETECTED moving from %8.3f, %8.3f, %8.3f to %8.3f, %8.3f, %8.3f. Z Change is %8.3f", From.x, From.y, From.z, MidPoint.x, MidPoint.y, MidPoint.z, NewZ - From.z); } @@ -1182,7 +1182,7 @@ bool PathManager::NoHazardsAccurate(glm::vec3 From, glm::vec3 To) glm::vec3 TestPoint(curx, cury, curz); float NewZ = zone->zonemap->FindBestZ(TestPoint, nullptr); if (std::abs(NewZ - last_z) > 5.0f) { - Log(Logs::Detail, Logs::None, " HAZARD DETECTED moving from %8.3f, %8.3f, %8.3f to %8.3f, %8.3f, %8.3f. Best Z %8.3f, Z Change is %8.3f", + Log(Logs::Detail, Logs::Pathing, " HAZARD DETECTED moving from %8.3f, %8.3f, %8.3f to %8.3f, %8.3f, %8.3f. Best Z %8.3f, Z Change is %8.3f", From.x, From.y, From.z, TestPoint.x, TestPoint.y, TestPoint.z, NewZ, NewZ - From.z); return false; } @@ -1210,20 +1210,20 @@ bool PathManager::NoHazardsAccurate(glm::vec3 From, glm::vec3 To) } if (best_z2 == -999990) { - Log(Logs::Detail, Logs::None, " HAZARD DETECTED, really deep water/lava!"); + Log(Logs::Detail, Logs::Pathing, " HAZARD DETECTED, really deep water/lava!"); return false; } else { - if (std::abs(NewZ - best_z2) > RuleR(Pathing, ZDiffThreshold)) { - Log(Logs::Detail, Logs::None, + if (std::abs(NewZ - best_z2) > RuleR(Pathing, ZDiffThresholdNew)) { + Log(Logs::Detail, Logs::Pathing, " HAZARD DETECTED, water is fairly deep at %8.3f units deep", std::abs(NewZ - best_z2)); return false; } else { - Log(Logs::Detail, Logs::None, + Log(Logs::Detail, Logs::Pathing, " HAZARD NOT DETECTED, water is shallow at %8.3f units deep", std::abs(NewZ - best_z2)); } @@ -1231,12 +1231,12 @@ bool PathManager::NoHazardsAccurate(glm::vec3 From, glm::vec3 To) } else { - Log(Logs::Detail, Logs::None, "Hazard point not in water or lava!"); + Log(Logs::Detail, Logs::Pathing, "Hazard point not in water or lava!"); } } else { - Log(Logs::Detail, Logs::None, "No water map loaded for hazards!"); + Log(Logs::Detail, Logs::Pathing, "No water map loaded for hazards!"); } curx += stepx; @@ -1291,7 +1291,7 @@ void PathManager::OpenDoors(int Node1, int Node2, Mob *ForWho) if(d && !d->IsDoorOpen() ) { - Log(Logs::Detail, Logs::None, "Opening door %i for %s", PathNodes[Node1].Neighbours[i].DoorID, ForWho->GetName()); + Log(Logs::Detail, Logs::Pathing, "Opening door %i for %s", PathNodes[Node1].Neighbours[i].DoorID, ForWho->GetName()); d->ForceOpen(ForWho); } @@ -1454,7 +1454,10 @@ void PathManager::NodeInfo(Client *c) void PathManager::DumpPath(std::string filename) { std::ofstream o_file; - o_file.open(filename.c_str(), std::ios_base::binary | std::ios_base::trunc | std::ios_base::out); + + std::string file_to_write = StringFormat("%s%s", Config->MapDir.c_str(), filename.c_str()); + + o_file.open(file_to_write.c_str(), std::ios_base::binary | std::ios_base::trunc | std::ios_base::out); o_file.write("EQEMUPATH", 9); o_file.write((const char*)&Head, sizeof(Head)); o_file.write((const char*)PathNodes, (sizeof(PathNode)*Head.PathNodeCount)); diff --git a/zone/perl_client.cpp b/zone/perl_client.cpp index 46746970c..622551ce0 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -6444,6 +6444,47 @@ XS(XS_Client_GetAccountAge) { XSRETURN(1); } +XS(XS_Client_Popup2); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Client_Popup2) +{ + dXSARGS; + if (items < 3 || items > 10) + Perl_croak(aTHX_ "Usage: Client::SendFullPopup(THIS, Title, Text, PopupID, NegativeID, Buttons, Duration, ButtonName0, ButtonName1, SoundControls)"); + { + Client * THIS; + char* Title = (char *)SvPV_nolen(ST(1)); + char* Text = (char *)SvPV_nolen(ST(2)); + uint32 PopupID = 0; + uint32 NegativeID = 0; + uint32 Buttons = 0; + uint32 Duration = 0; + char* ButtonName0 = 0; + char* ButtonName1 = 0; + uint32 SoundControls = 0; + + if (sv_derived_from(ST(0), "Client")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(Client *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type Client"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + if (items > 3) { PopupID = (uint32)SvUV(ST(3)); } + if (items > 4) { NegativeID = (uint32)SvUV(ST(4)); } + if (items > 5) { Buttons = (uint32)SvUV(ST(5)); } + if (items > 6) { Duration = (uint32)SvUV(ST(6)); } + if (items > 7) { ButtonName0 = (char *)SvPV_nolen(ST(7)); } + if (items > 8) { ButtonName1 = (char *)SvPV_nolen(ST(8)); } + if (items > 9) { SoundControls = (uint32)SvUV(ST(9)); } + + + THIS->SendFullPopup(Title, Text, PopupID, NegativeID, Buttons, Duration, ButtonName0, ButtonName1, SoundControls); + } + XSRETURN_EMPTY; +} + #ifdef __cplusplus extern "C" @@ -6698,6 +6739,7 @@ XS(boot_Client) newXSproto(strcpy(buf, "CalcEXP"), XS_Client_CalcEXP, file, "$"); newXSproto(strcpy(buf, "GetMoney"), XS_Client_GetMoney, file, "$$$"); newXSproto(strcpy(buf, "GetAccountAge"), XS_Client_GetAccountAge, file, "$"); + newXSproto(strcpy(buf, "Popup2"), XS_Client_Popup2, file, "$$$;$$$$$$$"); XSRETURN_YES; } diff --git a/zone/perl_groups.cpp b/zone/perl_groups.cpp index cdbffdd1c..e264b7d7a 100644 --- a/zone/perl_groups.cpp +++ b/zone/perl_groups.cpp @@ -389,7 +389,7 @@ XS(XS_Group_SendHPPacketsTo) if(newmember == nullptr) Perl_croak(aTHX_ "newmember is nullptr, avoiding crash."); - THIS->SendHPPacketsTo(newmember); + THIS->SendHPManaEndPacketsTo(newmember); } XSRETURN_EMPTY; } diff --git a/zone/perl_mob.cpp b/zone/perl_mob.cpp index 9af0fa43f..f95deb3de 100644 --- a/zone/perl_mob.cpp +++ b/zone/perl_mob.cpp @@ -1268,7 +1268,7 @@ XS(XS_Mob_SendPosUpdate) iSendToSelf = (uint8)SvUV(ST(1)); } - THIS->SendPosUpdate(iSendToSelf); + THIS->SendPositionUpdate(iSendToSelf); } XSRETURN_EMPTY; } diff --git a/zone/pets.cpp b/zone/pets.cpp index 39eeaeb5c..0c150f743 100644 --- a/zone/pets.cpp +++ b/zone/pets.cpp @@ -706,7 +706,7 @@ bool ZoneDatabase::GetBasePetItems(int32 equipmentset, uint32 *items) { // all of the result rows. Check if we have something in the slot // already. If no, add the item id to the equipment array. while (curset >= 0 && depth < 5) { - std::string query = StringFormat("SELECT nested_set FROM pets_equipmentset WHERE set_id = '%s'", curset); + std::string query = StringFormat("SELECT nested_set FROM pets_equipmentset WHERE set_id = '%d'", curset); auto results = QueryDatabase(query); if (!results.Success()) { return false; @@ -721,7 +721,7 @@ bool ZoneDatabase::GetBasePetItems(int32 equipmentset, uint32 *items) { auto row = results.begin(); nextset = atoi(row[0]); - query = StringFormat("SELECT slot, item_id FROM pets_equipmentset_entries WHERE set_id='%s'", curset); + query = StringFormat("SELECT slot, item_id FROM pets_equipmentset_entries WHERE set_id='%d'", curset); results = QueryDatabase(query); if (results.Success()) { for (row = results.begin(); row != results.end(); ++row) diff --git a/zone/pets.h b/zone/pets.h index 8d14d8a48..edb6dbe95 100644 --- a/zone/pets.h +++ b/zone/pets.h @@ -1,38 +1,6 @@ #ifndef PETS_H #define PETS_H -// Defines based on the RoF2 Client -#define PET_HEALTHREPORT 0 // 0x00 - /pet health or Pet Window -#define PET_LEADER 1 // 0x01 - /pet leader or Pet Window -#define PET_ATTACK 2 // 0x02 - /pet attack or Pet Window -#define PET_QATTACK 3 // 0x03 - /pet qattack or Pet Window -#define PET_FOLLOWME 4 // 0x04 - /pet follow or Pet Window -#define PET_GUARDHERE 5 // 0x05 - /pet guard or Pet Window -#define PET_SIT 6 // 0x06 - /pet sit or Pet Window -#define PET_SITDOWN 7 // 0x07 - /pet sit on -#define PET_STANDUP 8 // 0x08 - /pet sit off -#define PET_STOP 9 // 0x09 - /pet stop or Pet Window - Not implemented -#define PET_STOP_ON 10 // 0x0a - /pet stop on - Not implemented -#define PET_STOP_OFF 11 // 0x0b - /pet stop off - Not implemented -#define PET_TAUNT 12 // 0x0c - /pet taunt or Pet Window -#define PET_TAUNT_ON 13 // 0x0d - /pet taunt on -#define PET_TAUNT_OFF 14 // 0x0e - /pet taunt off -#define PET_HOLD 15 // 0x0f - /pet hold or Pet Window -#define PET_HOLD_ON 16 // 0x10 - /pet hold on -#define PET_HOLD_OFF 17 // 0x11 - /pet hold off -#define PET_SLUMBER 18 // 0x12 - What activates this? - define guessed -#define PET_SLUMBER_ON 19 // 0x13 - What activates this? - define guessed -#define PET_SLUMBER_OFF 20 // 0x14 - What activates this? - define guessed -#define PET_SPELLHOLD 21 // 0x15 - /pet no cast or /pet spellhold or Pet Window -#define PET_SPELLHOLD_ON 22 // 0x16 - /pet spellhold on -#define PET_SPELLHOLD_OFF 23 // 0x17 - /pet spellhold off -#define PET_FOCUS 24 // 0x18 - /pet focus or Pet Window -#define PET_FOCUS_ON 25 // 0x19 - /pet focus on -#define PET_FOCUS_OFF 26 // 0x1a - /pet focus off -#define PET_BACKOFF 28 // 0x1c - /pet back off -#define PET_GETLOST 29 // 0x1d - /pet get lost -#define PET_GUARDME 30 // 0x1e - Same as /pet follow, but different message in older clients - define not from client - class Mob; struct NPCType; diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index 8c52460d6..c77e96979 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -215,7 +215,7 @@ Mob* QuestManager::spawn2(int npc_type, int grid, int unused, const glm::vec4& p { npc->AssignWaypoints(grid); } - npc->SendPosUpdate(); + npc->SendPositionUpdate(); return npc; } return nullptr; @@ -237,7 +237,7 @@ Mob* QuestManager::unique_spawn(int npc_type, int grid, int unused, const glm::v { npc->AssignWaypoints(grid); } - npc->SendPosUpdate(); + npc->SendPositionUpdate(); return npc; } return nullptr; @@ -1660,7 +1660,7 @@ void QuestManager::respawn(int npcTypeID, int grid) { if(grid > 0) owner->CastToNPC()->AssignWaypoints(grid); - owner->SendPosUpdate(); + owner->SendPositionUpdate(); } } @@ -2743,20 +2743,14 @@ const char* QuestManager::saylink(char* Phrase, bool silent, const char* LinkNam if (results.RowCount() >= 1) { for (auto row = results.begin();row != results.end(); ++row) sayid = atoi(row[0]); - } else { // Add a new saylink entry to the database and query it again for the new sayid number + } else { std::string insert_query = StringFormat("INSERT INTO `saylink` (`phrase`) VALUES ('%s')", escaped_string); results = database.QueryDatabase(insert_query); if (!results.Success()) { Log(Logs::General, Logs::Error, "Error in saylink phrase queries", results.ErrorMessage().c_str()); - } else { - results = database.QueryDatabase(query); - if (results.Success()) { - if (results.RowCount() >= 1) - for(auto row = results.begin(); row != results.end(); ++row) - sayid = atoi(row[0]); - } else { - Log(Logs::General, Logs::Error, "Error in saylink phrase queries", results.ErrorMessage().c_str()); - } + } + else { + sayid = results.LastInsertedID(); } } } @@ -3004,6 +2998,20 @@ void QuestManager::CrossZoneMessagePlayerByName(uint32 Type, const char *CharNam safe_delete(pack); } +void QuestManager::CrossZoneSetEntityVariableByClientName(const char *CharName, const char *id, const char *m_var){ + uint32 message_len = strlen(id) + 1; + uint32 message_len2 = strlen(m_var) + 1; + uint32 message_len3 = strlen(CharName) + 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->CharName, CharName, 64); + strn0cpy(CZ->id, id, 256); + strn0cpy(CZ->m_var, m_var, 256); + worldserver.SendPacket(pack); + safe_delete(pack); +} + void QuestManager::CrossZoneSetEntityVariableByNPCTypeID(uint32 npctype_id, const char *id, const char *m_var){ uint32 message_len = strlen(id) + 1; uint32 message_len2 = strlen(m_var) + 1; diff --git a/zone/questmgr.h b/zone/questmgr.h index a878de71e..0c846c013 100644 --- a/zone/questmgr.h +++ b/zone/questmgr.h @@ -255,6 +255,7 @@ public: void CrossZoneSignalNPCByNPCTypeID(uint32 npctype_id, uint32 data); void CrossZoneSignalPlayerByName(const char *CharName, uint32 data); void CrossZoneSetEntityVariableByNPCTypeID(uint32 npctype_id, const char *id, const char *m_var); + void CrossZoneSetEntityVariableByClientName(const char *CharName, const char *id, const char *m_var); void CrossZoneMessagePlayerByName(uint32 Type, const char *CharName, const char *Message); void WorldWideMarquee(uint32 Type, uint32 Priority, uint32 FadeIn, uint32 FadeOut, uint32 Duration, const char *Message); bool EnableRecipe(uint32 recipe_id); diff --git a/zone/raids.cpp b/zone/raids.cpp index c9d4ee6c5..26845910c 100644 --- a/zone/raids.cpp +++ b/zone/raids.cpp @@ -145,6 +145,13 @@ void Raid::AddMember(Client *c, uint32 group, bool rleader, bool groupleader, bo } } + Raid *raid_update = nullptr; + raid_update = c->GetRaid(); + if (raid_update) { + raid_update->SendHPManaEndPacketsTo(c); + raid_update->SendHPPacketsFrom(c); + } + auto pack = new ServerPacket(ServerOP_RaidAdd, sizeof(ServerRaidGeneralAction_Struct)); ServerRaidGeneralAction_Struct *rga = (ServerRaidGeneralAction_Struct*)pack->pBuffer; rga->rid = GetID(); @@ -484,6 +491,14 @@ uint32 Raid::GetPlayerIndex(const char *name){ return 0; //should never get to here if we do everything else right, set it to 0 so we never crash things that rely on it. } +uint32 Raid::GetPlayerIndex(Client *c) +{ + for (int i = 0; i < MAX_RAID_MEMBERS; ++i) + if (c == members[i].member) + return i; + return 0xFFFFFFFF; // return sentinel value, make sure you check it unlike the above function +} + Client *Raid::GetClientByIndex(uint16 index) { if(index > MAX_RAID_MEMBERS) @@ -1539,70 +1554,126 @@ void Raid::MemberZoned(Client *c) group_mentor[gid].mentoree = nullptr; } -void Raid::SendHPPacketsTo(Client *c) +void Raid::SendHPManaEndPacketsTo(Client *client) { - if(!c) + if(!client) return; - uint32 gid = this->GetGroup(c); - EQApplicationPacket hpapp; + uint32 group_id = this->GetGroup(client); + + EQApplicationPacket hp_packet; EQApplicationPacket outapp(OP_MobManaUpdate, sizeof(MobManaUpdate_Struct)); - for(int x = 0; x < MAX_RAID_MEMBERS; x++) - { - if(members[x].member) - { - if((members[x].member != c) && (members[x].GroupNumber == gid)) - { - members[x].member->CreateHPPacket(&hpapp); - c->QueuePacket(&hpapp, false); - safe_delete_array(hpapp.pBuffer); - hpapp.size = 0; - if (c->ClientVersion() >= EQEmu::versions::ClientVersion::SoD) - { + + for(int x = 0; x < MAX_RAID_MEMBERS; x++) { + if(members[x].member) { + if((members[x].member != client) && (members[x].GroupNumber == group_id)) { + + members[x].member->CreateHPPacket(&hp_packet); + client->QueuePacket(&hp_packet, false); + safe_delete_array(hp_packet.pBuffer); + + hp_packet.size = 0; + if (client->ClientVersion() >= EQEmu::versions::ClientVersion::SoD) { + outapp.SetOpcode(OP_MobManaUpdate); - MobManaUpdate_Struct *mmus = (MobManaUpdate_Struct *)outapp.pBuffer; - mmus->spawn_id = members[x].member->GetID(); - mmus->mana = members[x].member->GetManaPercent(); - c->QueuePacket(&outapp, false); + MobManaUpdate_Struct *mana_update = (MobManaUpdate_Struct *)outapp.pBuffer; + mana_update->spawn_id = members[x].member->GetID(); + mana_update->mana = members[x].member->GetManaPercent(); + client->QueuePacket(&outapp, false); + outapp.SetOpcode(OP_MobEnduranceUpdate); - MobEnduranceUpdate_Struct *meus = (MobEnduranceUpdate_Struct *)outapp.pBuffer; - meus->endurance = members[x].member->GetEndurancePercent(); - c->QueuePacket(&outapp, false); + MobEnduranceUpdate_Struct *endurance_update = (MobEnduranceUpdate_Struct *)outapp.pBuffer; + endurance_update->endurance = members[x].member->GetEndurancePercent(); + client->QueuePacket(&outapp, false); } } } } } -void Raid::SendHPPacketsFrom(Mob *m) +void Raid::SendHPPacketsFrom(Mob *mob) { - if(!m) + if(!mob) return; - uint32 gid = 0; - if(m->IsClient()) - gid = this->GetGroup(m->CastToClient()); + uint32 group_id = 0; + + if(mob->IsClient()) + group_id = this->GetGroup(mob->CastToClient()); + EQApplicationPacket hpapp; EQApplicationPacket outapp(OP_MobManaUpdate, sizeof(MobManaUpdate_Struct)); - m->CreateHPPacket(&hpapp); - for(int x = 0; x < MAX_RAID_MEMBERS; x++) - { - if(members[x].member) - { - if(!m->IsClient() || ((members[x].member != m->CastToClient()) && (members[x].GroupNumber == gid))) - { + mob->CreateHPPacket(&hpapp); + + for(int x = 0; x < MAX_RAID_MEMBERS; x++) { + if(members[x].member) { + if(!mob->IsClient() || ((members[x].member != mob->CastToClient()) && (members[x].GroupNumber == group_id))) { members[x].member->QueuePacket(&hpapp, false); - if (members[x].member->ClientVersion() >= EQEmu::versions::ClientVersion::SoD) - { + if (members[x].member->ClientVersion() >= EQEmu::versions::ClientVersion::SoD) { outapp.SetOpcode(OP_MobManaUpdate); - MobManaUpdate_Struct *mmus = (MobManaUpdate_Struct *)outapp.pBuffer; - mmus->spawn_id = m->GetID(); - mmus->mana = m->GetManaPercent(); + MobManaUpdate_Struct *mana_update = (MobManaUpdate_Struct *)outapp.pBuffer; + mana_update->spawn_id = mob->GetID(); + mana_update->mana = mob->GetManaPercent(); members[x].member->QueuePacket(&outapp, false); + outapp.SetOpcode(OP_MobEnduranceUpdate); - MobEnduranceUpdate_Struct *meus = (MobEnduranceUpdate_Struct *)outapp.pBuffer; - meus->endurance = m->GetEndurancePercent(); + MobEnduranceUpdate_Struct *endurance_update = (MobEnduranceUpdate_Struct *)outapp.pBuffer; + endurance_update->endurance = mob->GetEndurancePercent(); + members[x].member->QueuePacket(&outapp, false); + } + } + } + } +} + +void Raid::SendManaPacketFrom(Mob *mob) +{ + if (!mob) + return; + + uint32 group_id = 0; + + if (mob->IsClient()) + group_id = this->GetGroup(mob->CastToClient()); + + EQApplicationPacket outapp(OP_MobManaUpdate, sizeof(MobManaUpdate_Struct)); + + for (int x = 0; x < MAX_RAID_MEMBERS; x++) { + if (members[x].member) { + if (!mob->IsClient() || ((members[x].member != mob->CastToClient()) && (members[x].GroupNumber == group_id))) { + if (members[x].member->ClientVersion() >= EQEmu::versions::ClientVersion::SoD) { + outapp.SetOpcode(OP_MobManaUpdate); + MobManaUpdate_Struct *mana_update = (MobManaUpdate_Struct *)outapp.pBuffer; + mana_update->spawn_id = mob->GetID(); + mana_update->mana = mob->GetManaPercent(); + members[x].member->QueuePacket(&outapp, false); + } + } + } + } +} + +void Raid::SendEndurancePacketFrom(Mob *mob) +{ + if (!mob) + return; + + uint32 group_id = 0; + + if (mob->IsClient()) + group_id = this->GetGroup(mob->CastToClient()); + + EQApplicationPacket outapp(OP_MobManaUpdate, sizeof(MobManaUpdate_Struct)); + + for (int x = 0; x < MAX_RAID_MEMBERS; x++) { + if (members[x].member) { + if (!mob->IsClient() || ((members[x].member != mob->CastToClient()) && (members[x].GroupNumber == group_id))) { + if (members[x].member->ClientVersion() >= EQEmu::versions::ClientVersion::SoD) { + outapp.SetOpcode(OP_MobEnduranceUpdate); + MobEnduranceUpdate_Struct *endurance_update = (MobEnduranceUpdate_Struct *)outapp.pBuffer; + endurance_update->spawn_id = mob->GetID(); + endurance_update->endurance = mob->GetEndurancePercent(); members[x].member->QueuePacket(&outapp, false); } } diff --git a/zone/raids.h b/zone/raids.h index 9e681153c..df43d7755 100644 --- a/zone/raids.h +++ b/zone/raids.h @@ -142,6 +142,7 @@ public: //keeps me from having to keep iterating through the list //when I want lots of data from the same entry uint32 GetPlayerIndex(const char *name); + uint32 GetPlayerIndex(Client *c); //for perl interface Client *GetClientByIndex(uint16 index); const char *GetClientNameByIndex(uint8 index); @@ -172,8 +173,10 @@ public: bool LearnMembers(); void VerifyRaid(); void MemberZoned(Client *c); - void SendHPPacketsTo(Client *c); - void SendHPPacketsFrom(Mob *m); + void SendHPManaEndPacketsTo(Client *c); + void SendHPPacketsFrom(Mob *mob); + void SendManaPacketFrom(Mob *mob); + void SendEndurancePacketFrom(Mob *mob); void RaidSay(const char *msg, Client *c); void RaidGroupSay(const char *msg, Client *c); diff --git a/zone/special_attacks.cpp b/zone/special_attacks.cpp index ef97aee39..57ea1c781 100644 --- a/zone/special_attacks.cpp +++ b/zone/special_attacks.cpp @@ -23,6 +23,7 @@ #include "entity.h" #include "mob.h" #include "string_ids.h" +#include "lua_parser.h" #include @@ -758,7 +759,6 @@ void Mob::DoArcheryAttackDmg(Mob *other, const EQEmu::ItemInstance *RangeWeapon, uint16 weapon_damage, int16 chance_mod, int16 focus, int ReuseTime, uint32 range_id, uint32 ammo_id, const EQEmu::ItemData *AmmoItem, int AmmoSlot, float speed) { - if ((other == nullptr || ((IsClient() && CastToClient()->dead) || (other->IsClient() && other->CastToClient()->dead)) || HasDied() || (!IsAttackAllowed(other)) || (other->GetInvul() || other->GetSpecialAbility(IMMUNE_MELEE)))) { diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index af96a7e2e..6a50ab142 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -277,12 +277,8 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove caster->SetMana(0); } else if (spell_id == 2755 && caster) //Lifeburn { - dmg = caster->GetHP()*-15/10; - caster->SetHP(1); - if(caster->IsClient()){ - caster->CastToClient()->SetFeigned(true); - caster->SendAppearancePacket(AT_Anim, 115); - } + dmg = -1 * caster->GetHP(); // just your current HP or should it be Max 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? } //do any AAs apply to these spells? @@ -851,7 +847,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove SetHeading(CalculateHeadingToTarget(ClosestMob->GetX(), ClosestMob->GetY())); SetTarget(ClosestMob); CastToClient()->SendTargetCommand(ClosestMob->GetID()); - SendPosUpdate(2); + SendPositionUpdate(2); } else Message_StringID(clientMessageError, SENSE_NOTHING); @@ -1240,6 +1236,23 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove else { MakePet(spell_id, spell.teleport_zone); + // TODO: we need to sync the states for these clients ... + // Will fix buttons for now + if (IsClient()) { + auto c = CastToClient(); + if (c->ClientVersionBit() & EQEmu::versions::bit_UFAndLater) { + c->SetPetCommandState(PET_BUTTON_SIT, 0); + c->SetPetCommandState(PET_BUTTON_STOP, 0); + c->SetPetCommandState(PET_BUTTON_REGROUP, 0); + c->SetPetCommandState(PET_BUTTON_FOLLOW, 1); + c->SetPetCommandState(PET_BUTTON_GUARD, 0); + c->SetPetCommandState(PET_BUTTON_TAUNT, 1); + c->SetPetCommandState(PET_BUTTON_HOLD, 0); + c->SetPetCommandState(PET_BUTTON_GHOLD, 0); + c->SetPetCommandState(PET_BUTTON_FOCUS, 0); + c->SetPetCommandState(PET_BUTTON_SPELLHOLD, 0); + } + } } break; } @@ -2778,6 +2791,10 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } + case SE_PersistentEffect: + MakeAura(spell_id); + break; + // Handled Elsewhere case SE_ImmuneFleeing: case SE_NegateSpellEffect: @@ -3627,7 +3644,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); + float resist_check = ResistSpell(spells[buff.spellid].resisttype, buff.spellid, caster,0,0,true); if (resist_check == 100) break; @@ -5007,8 +5024,19 @@ int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo break; case SE_SpellResistReduction: - if (type == focusResistRate && focus_spell.base[i] > value) - value = focus_spell.base[i]; + if (type == focusResistRate) { + 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_SpellHateMod: @@ -5054,8 +5082,18 @@ int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo break; case SE_FcSpellVulnerability: - if (type == focusSpellVulnerability) - value = focus_spell.base[i]; + 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_FcTwincast: @@ -5302,7 +5340,7 @@ int16 Client::GetFocusEffect(focusType type, uint16 spell_id) //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) && (type == focusManaCost || type == focusImprovedHeal || type == focusImprovedDamage || type == focusImprovedDamage2 || type == focusResistRate)) rand_effectiveness = true; //Check if item focus effect exists for the client. @@ -5925,7 +5963,8 @@ bool Mob::TryDeathSave() { bool Mob::AffectedBySpellExcludingSlot(int slot, int effect) { - for (int i = 0; i <= EFFECT_COUNT; i++) + int buff_count = GetMaxTotalSlots(); + for (int i = 0; i < buff_count; i++) { if (i == slot) continue; @@ -6669,23 +6708,26 @@ void Mob::ResourceTap(int32 damage, uint16 spellid) for (int i = 0; i < EFFECT_COUNT; i++) { if (spells[spellid].effectid[i] == SE_ResourceTap) { - damage += (damage * spells[spellid].base[i]) / 100; - - if (spells[spellid].max[i] && (damage > spells[spellid].max[i])) - damage = spells[spellid].max[i]; - - if (spells[spellid].base2[i] == 0) { // HP Tap - if (damage > 0) - HealDamage(damage); - else - Damage(this, -damage, 0, EQEmu::skills::SkillEvocation, false); + damage = (damage * spells[spellid].base[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 (damage > 0) + HealDamage(damage); + else + Damage(this, -damage, 0, EQEmu::skills::SkillEvocation, false); + } + + if (spells[spellid].base2[i] == 1) // Mana Tap + SetMana(GetMana() + damage); + + if (spells[spellid].base2[i] == 2 && IsClient()) // Endurance Tap + CastToClient()->SetEndurance(CastToClient()->GetEndurance() + damage); + } - - if (spells[spellid].base2[i] == 1) // Mana Tap - SetMana(GetMana() + damage); - - if (spells[spellid].base2[i] == 2 && IsClient()) // Endurance Tap - CastToClient()->SetEndurance(CastToClient()->GetEndurance() + damage); } } } diff --git a/zone/spells.cpp b/zone/spells.cpp index 001c6895c..8e7d6721a 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -2140,6 +2140,27 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, ui spell_target->CalcSpellPowerDistanceMod(spell_id, dist2); } + //AE Duration spells were ignoring distance check from item clickies + if(ae_center != nullptr && ae_center != this) { + //casting a spell on somebody but ourself, make sure they are in range + float dist2 = DistanceSquared(m_Position, ae_center->GetPosition()); + float range2 = range * range; + float min_range2 = spells[spell_id].min_range * spells[spell_id].min_range; + if(dist2 > range2) { + //target is out of range. + Log(Logs::Detail, Logs::Spells, "Spell %d: Spell target is out of range (squared: %f > %f)", spell_id, dist2, range2); + Message_StringID(13, TARGET_OUT_OF_RANGE); + return(false); + } + else if (dist2 < min_range2){ + //target is too close range. + Log(Logs::Detail, Logs::Spells, "Spell %d: Spell target is too close (squared: %f < %f)", spell_id, dist2, min_range2); + Message_StringID(13, TARGET_TOO_CLOSE); + return(false); + } + + ae_center->CalcSpellPowerDistanceMod(spell_id, dist2); + } // // Switch #2 - execute the spell @@ -2216,25 +2237,13 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, ui // special ae duration spell ae_center->CastToBeacon()->AELocationSpell(this, spell_id, resist_adjust); } else { - // regular PB AE or targeted AE spell - spell_target is null if PB - if(spell_target) // this must be an AETarget spell - { - bool cast_on_target = true; - if (spells[spell_id].targettype == ST_TargetAENoPlayersPets && spell_target->IsPetOwnerClient()) - cast_on_target = false; - if (spells[spell_id].targettype == ST_AreaClientOnly && !spell_target->IsClient()) - cast_on_target = false; - if (spells[spell_id].targettype == ST_AreaNPCOnly && !spell_target->IsNPC()) - cast_on_target = false; - - // affect the target too - if (cast_on_target) - SpellOnTarget(spell_id, spell_target, false, true, resist_adjust); - } + // unsure if we actually need this? Need to find some spell examples if(ae_center && ae_center == this && IsBeneficialSpell(spell_id)) SpellOnTarget(spell_id, this); - bool affect_caster = !IsNPC(); //NPC AE spells do not affect the NPC caster + // 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; if (spells[spell_id].targettype == ST_AETargetHateList) hate_list.SpellCast(this, spell_id, spells[spell_id].aoerange, ae_center); @@ -3038,6 +3047,12 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2, if (IsEffectIgnoredInStacking(effect1)) continue; + // 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) + continue; + /* If target is a npc and caster1 and caster2 exist If Caster1 isn't the same as Caster2 and the effect is a DoT then ignore it. @@ -3432,7 +3447,7 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r if(spelltar->IsClient() && spelltar->CastToClient()->IsHoveringForRespawn()) return false; - if(IsDetrimentalSpell(spell_id) && !IsAttackAllowed(spelltar) && !IsResurrectionEffects(spell_id)) { + if(IsDetrimentalSpell(spell_id) && !IsAttackAllowed(spelltar, true) && !IsResurrectionEffects(spell_id)) { if(!IsClient() || !CastToClient()->GetGM()) { Message_StringID(MT_SpellFailure, SPELL_NO_HOLD); return false; @@ -3791,8 +3806,22 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r break; } if (reflect_chance) { - entity_list.MessageClose_StringID(this, false, RuleI(Range, SpellMessages), MT_Spells, - SPELL_REFLECT, GetCleanName(), spelltar->GetCleanName()); + + if (RuleB(Spells, ReflectMessagesClose)) { + entity_list.MessageClose_StringID( + this, /* Sender */ + false, /* Skip Sender */ + RuleI(Range, SpellMessages), /* Range */ + MT_Spells, /* Type */ + SPELL_REFLECT, /* String ID */ + GetCleanName(), /* Message 1 */ + spelltar->GetCleanName() /* Message 2 */ + ); + } + else { + Message_StringID(MT_Spells, SPELL_REFLECT, GetCleanName(), spelltar->GetCleanName()); + } + 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 @@ -4160,6 +4189,21 @@ void Mob::BuffFadeBySpellID(uint16 spell_id) 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); + recalc_bonus = true; + } + } + + if (recalc_bonus) + CalcBonuses(); +} + // removes buffs containing effectid, skipping skipslot void Mob::BuffFadeByEffect(int effectid, int skipslot) { @@ -4178,6 +4222,16 @@ void Mob::BuffFadeByEffect(int effectid, int skipslot) 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; +} + // checks if 'this' can be affected by spell_id from caster // returns true if the spell should fail, false otherwise bool Mob::IsImmuneToSpell(uint16 spell_id, Mob *caster) diff --git a/zone/string_ids.h b/zone/string_ids.h index f03d8f44d..c8175c8b6 100644 --- a/zone/string_ids.h +++ b/zone/string_ids.h @@ -164,6 +164,12 @@ #define PVP_ON 552 //You are now player kill and follow the ways of Discord. #define GENERIC_STRINGID_SAY 554 //%1 says '%T2' #define CANNOT_WAKE 555 //%1 tells you, 'I am unable to wake %2, master.' +#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. +#define PET_FOCUS_SET_OFF 701 //The pet focus mode has been set to off. +#define PET_SPELLHOLD_SET_ON 702 //The pet spellhold mode has been set to on. +#define PET_SPELLHOLD_SET_OFF 703 //The pet spellhold mode has been set to off. #define GUILD_NAME_IN_USE 711 //You cannot create a guild with that name, that guild already exists on this server. #define GM_GAINXP 1002 //[GM] You have gained %1 AXP and %2 EXP (%3). #define MALE_SLAYUNDEAD 1007 //%1's holy blade cleanses his target!(%2) @@ -280,6 +286,7 @@ #define TRADESKILL_LEARN_RECIPE 3457 //You have learned the recipe %1! #define EXPEDITION_MIN_REMAIN 3551 //You only have %1 minutes remaining before this expedition comes to an end. #define LOOT_NOT_ALLOWED 3562 //You are not allowed to loot the item: %1. +#define NOT_YOUR_TRAP 3671 //You cannot remove this, you are only allowed to remove traps you have set. #define NO_CAST_ON_PET 4045 //You cannot cast this spell on your pet. #define REWIND_WAIT 4059 //You must wait a bit longer before using the rewind command again. #define CORPSEDRAG_LIMIT 4061 //You are already dragging as much as you can! @@ -288,6 +295,7 @@ #define CORPSEDRAG_BEGIN 4064 //You begin to drag %1. #define CORPSEDRAG_STOPALL 4065 //You stop dragging the corpses. #define CORPSEDRAG_STOP 4066 //You stop dragging the corpse. +#define SOS_KEEPS_HIDDEN 4086 //Your Shroud of Stealth keeps you hidden from watchful eyes.␣␣ #define TARGET_TOO_CLOSE 4602 //You are too close to your target. Get farther away. #define WHOALL_NO_RESULTS 5029 //There are no players in EverQuest that match those who filters. #define TELL_QUEUED_MESSAGE 5045 //You told %1 '%T2. %3' @@ -302,6 +310,7 @@ #define PET_ATTACKING 5501 //%1 tells you, 'Attacking %2 Master.' #define AVOID_STUNNING_BLOW 5753 //You avoid the stunning blow. #define FATAL_BOW_SHOT 5745 //%1 performs a FATAL BOW SHOT!! +#define SUSPECT_SEES_YOU 5746 //You suspect that this being can see you. #define MELEE_SILENCE 5806 //You *CANNOT* use this melee ability, you are suffering from amnesia! #define DISCIPLINE_REUSE_MSG 5807 //You can use the ability %1 again in %2 hour(s) %3 minute(s) %4 seconds. #define DISCIPLINE_REUSE_MSG2 5808 //You can use the ability %1 again in %2 minute(s) %3 seconds. @@ -320,6 +329,12 @@ #define SENTINEL_TRIG_YOU 6724 //You have triggered your sentinel. #define SENTINEL_TRIG_OTHER 6725 //%1 has triggered your sentinel. #define IDENTIFY_SPELL 6765 //Item Lore: %1. +#define PET_NOW_HOLDING 6834 //Now holding, Master. I will not start attacks until ordered. +#define PET_ON_GHOLD 6843 //Pet greater hold has been set to on. +#define PET_OFF_GHOLD 6846 //Pet greater hold has been set to off. +#define PET_GHOLD_ON_MSG 6847 //Now greater holding master. I will only attack something new if ordered. +#define PET_ON_REGROUPING 6854 //Now regrouping, master. +#define PET_OFF_REGROUPING 6855 //No longer regrouping, master. #define BUFF_NOT_BLOCKABLE 7608 //You cannot block this effect. #define LDON_DONT_KNOW_TRAPPED 7552 //You do not know if this object is trapped. #define LDON_HAVE_DISARMED 7553 //You have disarmed %1! @@ -347,6 +362,7 @@ #define GAIN_GROUP_LEADERSHIP_EXP 8788 // #define GAIN_RAID_LEADERSHIP_EXP 8789 // #define BUFF_MINUTES_REMAINING 8799 //%1 (%2 minutes remaining) +#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. #define SLOW_PARTIALLY_SUCCESSFUL 9030 // Your spell was partially successful. @@ -361,6 +377,7 @@ #define SHAKE_OFF_STUN 9077 //You shake off the stun effect! #define STRIKETHROUGH_STRING 9078 //You strike through your opponent's defenses! #define SPELL_REFLECT 9082 //%1's spell has been reflected by %2. +#define NO_MORE_AURAS 9160 //You do not have sufficient focus to maintain that ability. #define NEW_SPELLS_AVAIL 9149 //You have new spells available to you. Check the merchants near your guild master. #define FD_CAST_ON_NO_BREAK 9174 //The strength of your will allows you to resume feigning death. #define SNEAK_RESTRICT 9240 //You can not use this ability because you have not been hidden for long enough. diff --git a/zone/tune.cpp b/zone/tune.cpp index bbb56217e..36be51905 100644 --- a/zone/tune.cpp +++ b/zone/tune.cpp @@ -596,8 +596,7 @@ int32 Client::GetMeleeDamage(Mob* other, bool GetMinDamage) } } - EQEmu::skills::SkillType skillinuse; - AttackAnimation(skillinuse, Hand, weapon); + EQEmu::skills::SkillType skillinuse = AttackAnimation(Hand, weapon); int damage = 0; uint8 mylevel = GetLevel() ? GetLevel() : 1; @@ -665,16 +664,16 @@ void Mob::Tune_FindAccuaryByHitChance(Mob* defender, Mob *attacker, float hit_ch weapon = attacker->CastToClient()->GetInv().GetItem(EQEmu::inventory::slotPrimary); if(weapon && weapon->IsWeapon()){ - attacker->CastToClient()->AttackAnimation(skillinuse, EQEmu::inventory::slotPrimary, weapon); + skillinuse = attacker->CastToClient()->AttackAnimation(EQEmu::inventory::slotPrimary, weapon); } else { weapon = attacker->CastToClient()->GetInv().GetItem(EQEmu::inventory::slotSecondary); if (weapon && weapon->IsWeapon()) - attacker->CastToClient()->AttackAnimation(skillinuse, EQEmu::inventory::slotSecondary, weapon); + skillinuse = attacker->CastToClient()->AttackAnimation(EQEmu::inventory::slotSecondary, weapon); else { weapon = attacker->CastToClient()->GetInv().GetItem(EQEmu::inventory::slotRange); if (weapon && weapon->IsWeapon()) - attacker->CastToClient()->AttackAnimation(skillinuse, EQEmu::inventory::slotRange, weapon); + skillinuse = attacker->CastToClient()->AttackAnimation(EQEmu::inventory::slotRange, weapon); } } } @@ -745,16 +744,16 @@ void Mob::Tune_FindAvoidanceByHitChance(Mob* defender, Mob *attacker, float hit_ weapon = attacker->CastToClient()->GetInv().GetItem(EQEmu::inventory::slotPrimary); if(weapon && weapon->IsWeapon()){ - attacker->CastToClient()->AttackAnimation(skillinuse, EQEmu::inventory::slotPrimary, weapon); + skillinuse = attacker->CastToClient()->AttackAnimation(EQEmu::inventory::slotPrimary, weapon); } else { weapon = attacker->CastToClient()->GetInv().GetItem(EQEmu::inventory::slotSecondary); if (weapon && weapon->IsWeapon()) - attacker->CastToClient()->AttackAnimation(skillinuse, EQEmu::inventory::slotSecondary, weapon); + skillinuse = attacker->CastToClient()->AttackAnimation(EQEmu::inventory::slotSecondary, weapon); else { weapon = attacker->CastToClient()->GetInv().GetItem(EQEmu::inventory::slotRange); if (weapon && weapon->IsWeapon()) - attacker->CastToClient()->AttackAnimation(skillinuse, EQEmu::inventory::slotRange, weapon); + skillinuse = attacker->CastToClient()->AttackAnimation(EQEmu::inventory::slotRange, weapon); } } } diff --git a/zone/waypoints.cpp b/zone/waypoints.cpp index 64f674cde..c1997af59 100644 --- a/zone/waypoints.cpp +++ b/zone/waypoints.cpp @@ -134,17 +134,15 @@ void NPC::PauseWandering(int pausetime) { // causes wandering to stop but is resumable // 0 pausetime means pause until resumed // otherwise automatically resume when time is up - if (GetGrid() != 0) - { + if (GetGrid() != 0) { + moving = false; DistractedFromGrid = true; Log(Logs::Detail, Logs::Pathing, "Paused Wandering requested. Grid %d. Resuming in %d ms (0=not until told)", GetGrid(), pausetime); SendPosition(); - if (pausetime<1) - { // negative grid number stops him dead in his tracks until ResumeWandering() + if (pausetime < 1) { // negative grid number stops him dead in his tracks until ResumeWandering() SetGrid(0 - GetGrid()); } - else - { // specified waiting time, he'll resume after that + else { // specified waiting time, he'll resume after that AI_walking_timer->Start(pausetime * 1000); // set the timer } } @@ -212,22 +210,6 @@ void NPC::UpdateWaypoint(int wp_index) cur_wp_pause = cur->pause; Log(Logs::Detail, Logs::AI, "Next waypoint %d: (%.3f, %.3f, %.3f, %.3f)", wp_index, m_CurrentWayPoint.x, m_CurrentWayPoint.y, m_CurrentWayPoint.z, m_CurrentWayPoint.w); - //fix up pathing Z - if (zone->HasMap() && RuleB(Map, FixPathingZAtWaypoints) && !IsBoat()) - { - - if (!RuleB(Watermap, CheckForWaterAtWaypoints) || !zone->HasWaterMap() || - (zone->HasWaterMap() && !zone->watermap->InWater(glm::vec3(m_CurrentWayPoint)))) - { - glm::vec3 dest(m_CurrentWayPoint.x, m_CurrentWayPoint.y, m_CurrentWayPoint.z); - - float newz = zone->zonemap->FindBestZ(dest, nullptr); - - if ((newz > -2000) && std::abs(newz - dest.z) < RuleR(Map, FixPathingZMaxDeltaWaypoint)) - m_CurrentWayPoint.z = newz + 1; - } - } - } void NPC::CalculateNewWaypoint() @@ -469,7 +451,7 @@ float Mob::CalculateHeadingToTarget(float in_x, float in_y) { return (256 * (360 - angle) / 360.0f); } -bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, int speed, bool checkZ) { +bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, int speed) { if (GetID() == 0) return true; @@ -513,39 +495,9 @@ bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, int speed, boo m_Position.y = new_y; m_Position.z = new_z; - uint8 NPCFlyMode = 0; - - if (IsNPC()) { - if (CastToNPC()->GetFlyMode() == 1 || CastToNPC()->GetFlyMode() == 2) - NPCFlyMode = 1; - } - - //fix up pathing Z - if (!NPCFlyMode && checkZ && zone->HasMap() && RuleB(Map, FixPathingZWhenMoving)) - { - if (!RuleB(Watermap, CheckForWaterWhenMoving) || !zone->HasWaterMap() || - (zone->HasWaterMap() && !zone->watermap->InWater(glm::vec3(m_Position)))) - { - glm::vec3 dest(m_Position.x, m_Position.y, m_Position.z); - - float newz = zone->zonemap->FindBestZ(dest, nullptr) + 2.0f; - - if ((newz > -2000) && - std::abs(newz - dest.z) < RuleR(Map, FixPathingZMaxDeltaMoving)) // Sanity check. - { - if ((std::abs(x - m_Position.x) < 0.5) && - (std::abs(y - m_Position.y) < 0.5)) { - if (std::abs(z - m_Position.z) <= - RuleR(Map, FixPathingZMaxDeltaMoving)) - m_Position.z = z; - else - m_Position.z = newz + 1; - } - else - m_Position.z = newz + 1; - } - } - } + if(fix_z_timer.Check() && + (!this->IsEngaged() || flee_mode || currently_fleeing)) + this->FixZ(); tar_ndx++; return true; @@ -651,37 +603,8 @@ bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, int speed, boo m_Position.w = CalculateHeadingToTarget(x, y); } - uint8 NPCFlyMode = 0; - - if (IsNPC()) { - if (CastToNPC()->GetFlyMode() == 1 || CastToNPC()->GetFlyMode() == 2) - NPCFlyMode = 1; - } - - //fix up pathing Z - if (!NPCFlyMode && checkZ && zone->HasMap() && RuleB(Map, FixPathingZWhenMoving)) { - - if (!RuleB(Watermap, CheckForWaterWhenMoving) || !zone->HasWaterMap() || - (zone->HasWaterMap() && !zone->watermap->InWater(glm::vec3(m_Position)))) - { - glm::vec3 dest(m_Position.x, m_Position.y, m_Position.z); - - float newz = zone->zonemap->FindBestZ(dest, nullptr); - - if ((newz > -2000) && - std::abs(newz - dest.z) < RuleR(Map, FixPathingZMaxDeltaMoving)) // Sanity check. - { - if (std::abs(x - m_Position.x) < 0.5 && std::abs(y - m_Position.y) < 0.5) { - if (std::abs(z - m_Position.z) <= RuleR(Map, FixPathingZMaxDeltaMoving)) - m_Position.z = z; - else - m_Position.z = newz + 1; - } - else - m_Position.z = newz + 1; - } - } - } + if (fix_z_timer.Check() && !this->IsEngaged()) + this->FixZ(); SetMoving(true); moved = true; @@ -690,12 +613,12 @@ bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, int speed, boo if (IsClient()) { - SendPosUpdate(1); + SendPositionUpdate(1); CastToClient()->ResetPositionTimer(); } else { - SendPosUpdate(); + SendPositionUpdate(); SetAppearance(eaStanding, false); } @@ -704,7 +627,7 @@ bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, int speed, boo } bool Mob::CalculateNewPosition2(float x, float y, float z, int speed, bool checkZ, bool calcHeading) { - return MakeNewPositionAndSendUpdate(x, y, z, speed, checkZ); + return MakeNewPositionAndSendUpdate(x, y, z, speed); } bool Mob::CalculateNewPosition(float x, float y, float z, int speed, bool checkZ, bool calcHeading) { @@ -769,39 +692,8 @@ bool Mob::CalculateNewPosition(float x, float y, float z, int speed, bool checkZ Log(Logs::Detail, Logs::AI, "Next position (%.3f, %.3f, %.3f)", m_Position.x, m_Position.y, m_Position.z); } - uint8 NPCFlyMode = 0; - - if (IsNPC()) { - if (CastToNPC()->GetFlyMode() == 1 || CastToNPC()->GetFlyMode() == 2) - NPCFlyMode = 1; - } - - //fix up pathing Z - if (!NPCFlyMode && checkZ && zone->HasMap() && RuleB(Map, FixPathingZWhenMoving)) - { - if (!RuleB(Watermap, CheckForWaterWhenMoving) || !zone->HasWaterMap() || - (zone->HasWaterMap() && !zone->watermap->InWater(glm::vec3(m_Position)))) - { - glm::vec3 dest(m_Position.x, m_Position.y, m_Position.z); - - float newz = zone->zonemap->FindBestZ(dest, nullptr) + 2.0f; - - Log(Logs::Detail, Logs::AI, "BestZ returned %4.3f at %4.3f, %4.3f, %4.3f", newz, m_Position.x, m_Position.y, m_Position.z); - - if ((newz > -2000) && - std::abs(newz - dest.z) < RuleR(Map, FixPathingZMaxDeltaMoving)) // Sanity check. - { - if (std::abs(x - m_Position.x) < 0.5 && std::abs(y - m_Position.y) < 0.5) { - if (std::abs(z - m_Position.z) <= RuleR(Map, FixPathingZMaxDeltaMoving)) - m_Position.z = z; - else - m_Position.z = newz + 1; - } - else - m_Position.z = newz + 1; - } - } - } + if (fix_z_timer.Check()) + this->FixZ(); //OP_MobUpdate if ((old_test_vector != test_vector) || tar_ndx>20) { //send update @@ -809,7 +701,7 @@ bool Mob::CalculateNewPosition(float x, float y, float z, int speed, bool checkZ this->SetMoving(true); moved = true; m_Delta = glm::vec4(m_Position.x - nx, m_Position.y - ny, m_Position.z - nz, 0.0f); - SendPosUpdate(); + SendPositionUpdate(); } tar_ndx++; @@ -871,20 +763,6 @@ void NPC::AssignWaypoints(int32 grid) newwp.y = atof(row[1]); newwp.z = atof(row[2]); - if (zone->HasMap() && RuleB(Map, FixPathingZWhenLoading)) - { - auto positon = glm::vec3(newwp.x, newwp.y, newwp.z); - if (!RuleB(Watermap, CheckWaypointsInWaterWhenLoading) || !zone->HasWaterMap() || - (zone->HasWaterMap() && !zone->watermap->InWater(positon))) - { - glm::vec3 dest(newwp.x, newwp.y, newwp.z); - float newz = zone->zonemap->FindBestZ(dest, nullptr); - - if ((newz > -2000) && std::abs(newz - dest.z) < RuleR(Map, FixPathingZMaxDeltaLoading)) - newwp.z = newz + 1; - } - } - newwp.pause = atoi(row[3]); newwp.heading = atof(row[4]); Waypoints.push_back(newwp); @@ -943,9 +821,6 @@ void Mob::SendToFixZ(float new_x, float new_y, float new_z) { m_Position.y = new_y; m_Position.z = new_z + 0.1; - //fix up pathing Z, this shouldent be needed IF our waypoints - //are corrected instead - if (zone->HasMap() && RuleB(Map, FixPathingZOnSendTo)) { if (!RuleB(Watermap, CheckForWaterOnSendTo) || !zone->HasWaterMap() || @@ -955,7 +830,7 @@ void Mob::SendToFixZ(float new_x, float new_y, float new_z) { float newz = zone->zonemap->FindBestZ(dest, nullptr); - Log(Logs::Detail, Logs::AI, "BestZ returned %4.3f at %4.3f, %4.3f, %4.3f", newz, m_Position.x, m_Position.y, m_Position.z); + Log(Logs::Moderate, Logs::Pathing, "BestZ returned %4.3f at %4.3f, %4.3f, %4.3f", newz, m_Position.x, m_Position.y, m_Position.z); if ((newz > -2000) && std::abs(newz - dest.z) < RuleR(Map, FixPathingZMaxDeltaSendTo)) // Sanity check. m_Position.z = newz + 1; @@ -963,6 +838,52 @@ void Mob::SendToFixZ(float new_x, float new_y, float new_z) { } } +void Mob::FixZ() { + + BenchTimer timer; + timer.reset(); + + if (zone->HasMap() && RuleB(Map, FixZWhenMoving) && (flymode != 1 && flymode != 2)) + { + if (!RuleB(Watermap, CheckForWaterWhenMoving) || !zone->HasWaterMap() || + (zone->HasWaterMap() && !zone->watermap->InWater(glm::vec3(m_Position)))) + { + /* Any more than 5 in the offset makes NPC's hop/snap to ceiling in small corridors */ + float new_z = this->FindGroundZ(m_Position.x, m_Position.y, 5); + new_z += (this->GetSize() / 1.55); + + auto duration = timer.elapsed(); + + Log( + Logs::Moderate, + Logs::FixZ, + "Mob::FixZ() (%s) returned %4.3f at %4.3f, %4.3f, %4.3f - Took %lf", + this->GetCleanName(), + new_z, + m_Position.x, + m_Position.y, + m_Position.z, + duration + ); + + if ((new_z > -2000) && new_z != -999999) { + if (RuleB(Map, MobZVisualDebug)) + this->SendAppearanceEffect(78, 0, 0, 0, 0); + + m_Position.z = new_z; + } + else { + if (RuleB(Map, MobZVisualDebug)) + this->SendAppearanceEffect(103, 0, 0, 0, 0); + + Log(Logs::General, Logs::FixZ, "%s is failing to find Z %f", this->GetCleanName(), std::abs(m_Position.z - new_z)); + } + + last_z = m_Position.z; + } + } +} + int ZoneDatabase::GetHighestGrid(uint32 zoneid) { std::string query = StringFormat("SELECT COALESCE(MAX(id), 0) FROM grid WHERE zoneid = %i", zoneid); @@ -1209,4 +1130,4 @@ void NPC::SaveGuardSpotCharm() void NPC::RestoreGuardSpotCharm() { m_GuardPoint = m_GuardPointSaved; -} \ No newline at end of file +} diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index b0ea2ea5d..18730bb55 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -989,7 +989,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) } database.RefreshGroupFromDB(client); - group->SendHPPacketsTo(client); + group->SendHPManaEndPacketsTo(client); // If the group leader is not set, pull the group leader information from the database. if (!group->GetLeader()) @@ -1857,6 +1857,15 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) } break; } + case ServerOP_CZSetEntityVariableByClientName: + { + CZSetEntVarByClientName_Struct* CZCS = (CZSetEntVarByClientName_Struct*)pack->pBuffer; + Client* client = entity_list.GetClientByName(CZCS->CharName); + if (client != 0) { + client->SetEntityVariable(CZCS->id, CZCS->m_var); + } + break; + } case ServerOP_WWMarquee: { WWMarquee_Struct* WWMS = (WWMarquee_Struct*)pack->pBuffer; diff --git a/zone/zone.cpp b/zone/zone.cpp index 086754d55..6a0754dea 100644 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -1414,11 +1414,11 @@ bool Zone::HasWeather() void Zone::StartShutdownTimer(uint32 set_time) { if (set_time > autoshutdown_timer.GetRemainingTime()) { - if (set_time == (RuleI(Zone, AutoShutdownDelay))) - { + if (set_time == (RuleI(Zone, AutoShutdownDelay))) { set_time = database.getZoneShutDownDelay(GetZoneID(), GetInstanceVersion()); } - autoshutdown_timer.Start(set_time, false); + autoshutdown_timer.SetTimer(set_time); + Log(Logs::General, Logs::Zone_Server, "Zone::StartShutdownTimer set to %u", set_time); } } diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index d647dcbf7..bd28bcba3 100644 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -11,6 +11,7 @@ #include "merc.h" #include "zone.h" #include "zonedb.h" +#include "aura.h" #include #include @@ -1967,7 +1968,9 @@ const NPCType* ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load "npc_types.handtexture, " "npc_types.legtexture, " "npc_types.feettexture, " - "npc_types.ignore_despawn " + "npc_types.ignore_despawn, " + "npc_types.show_name, " + "npc_types.untargetable " "FROM npc_types %s", where_condition.c_str() ); @@ -2143,6 +2146,8 @@ const NPCType* ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load temp_npctype_data->legtexture = atoi(row[95]); temp_npctype_data->feettexture = atoi(row[96]); temp_npctype_data->ignore_despawn = atoi(row[97]) == 1 ? true : false; + temp_npctype_data->show_name = atoi(row[98]) != 0 ? true : false; + temp_npctype_data->untargetable = atoi(row[99]) != 0 ? true : false; // If NPC with duplicate NPC id already in table, // free item we attempted to add. @@ -3148,6 +3153,37 @@ void ZoneDatabase::LoadBuffs(Client *client) } } +void ZoneDatabase::SaveAuras(Client *c) +{ + auto query = StringFormat("DELETE FROM `character_auras` WHERE `id` = %u", c->CharacterID()); + auto results = database.QueryDatabase(query); + if (!results.Success()) + return; + + const auto &auras = c->GetAuraMgr(); + for (int i = 0; i < auras.count; ++i) { + auto aura = auras.auras[i].aura; + if (aura && aura->AuraZones()) { + query = StringFormat("INSERT INTO `character_auras` (id, slot, spell_id) VALUES(%u, %d, %d)", + c->CharacterID(), i, aura->GetAuraID()); + auto results = database.QueryDatabase(query); + if (!results.Success()) + return; + } + } +} + +void ZoneDatabase::LoadAuras(Client *c) +{ + auto query = StringFormat("SELECT `spell_id` FROM `character_auras` WHERE `id` = %u ORDER BY `slot`", c->CharacterID()); + auto results = database.QueryDatabase(query); + if (!results.Success()) + return; + + for (auto row = results.begin(); row != results.end(); ++row) + c->MakeAura(atoi(row[0])); +} + void ZoneDatabase::SavePetInfo(Client *client) { PetInfo *petinfo = nullptr; diff --git a/zone/zonedb.h b/zone/zonedb.h index b592be480..a8ab3659f 100644 --- a/zone/zonedb.h +++ b/zone/zonedb.h @@ -123,6 +123,19 @@ struct PetRecord { uint32 equipmentset; // default equipment for the pet }; +struct AuraRecord { + uint32 npc_type; + char name[64]; // name shown in UI if shown and spawn name + int spell_id; + int distance; + int aura_type; + int spawn_type; + int movement; + int duration; // seconds some live for 90 mins (normal) others for 2 mins (traps) + int icon; // -1 will use the buffs NEW_ICON + int cast_time; // seconds some auras recast on a timer, most seem to be every 12 seconds +}; + // Actual pet info for a client. struct PetInfo { uint16 SpellID; @@ -260,6 +273,8 @@ public: void SaveBuffs(Client *c); void LoadBuffs(Client *c); + void SaveAuras(Client *c); + void LoadAuras(Client *c); void LoadPetInfo(Client *c); void SavePetInfo(Client *c); void RemoveTempFactions(Client *c); @@ -404,6 +419,7 @@ public: void AddLootDropToNPC(NPC* npc, uint32 lootdrop_id, ItemList* itemlist, uint8 droplimit, uint8 mindrop); uint32 GetMaxNPCSpellsID(); uint32 GetMaxNPCSpellsEffectsID(); + bool GetAuraEntry(uint16 spell_id, AuraRecord &record); DBnpcspells_Struct* GetNPCSpells(uint32 iDBSpellsID); DBnpcspellseffects_Struct* GetNPCSpellsEffects(uint32 iDBSpellsEffectsID); diff --git a/zone/zonedump.h b/zone/zonedump.h index 713bfca7e..2bd78c2d8 100644 --- a/zone/zonedump.h +++ b/zone/zonedump.h @@ -133,6 +133,8 @@ struct NPCType uint8 legtexture; uint8 feettexture; bool ignore_despawn; + bool show_name; // should default on + bool untargetable; }; namespace player_lootitem {