diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e2f5f9e2..0b7569ed5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,8 +24,8 @@ #EQEMU_DISABLE_LOGSYS #EQEMU_COMMANDS_LOGGING #EQEMU_BUILD_SERVER -#EQEMU_BUILD_LOGIN -#EQEMU_BUILD_TESTS +#EQEMU_BUILD_LOGIN +#EQEMU_BUILD_TESTS #EQEMU_BUILD_PERL #EQEMU_BUILD_LUA #EQEMU_SANITIZE_LUA_LIBS @@ -103,7 +103,7 @@ IF(MSVC) SET(CMAKE_MODULE_LINKER_FLAGS_RELEASE "${CMAKE_MODULE_LINKER_FLAGS_RELEASE} /SAFESEH:NO") SET(CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO} /SAFESEH:NO") ENDIF(EQEMU_DISABLE_SAFESEH) - + OPTION(EQEMU_BUILD_MSVC_MP "Enable build with multiple processes." ON) IF(EQEMU_BUILD_MSVC_MP) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") @@ -115,7 +115,7 @@ IF(MSVC) STRING(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") ENDIF(${flag_var} MATCHES "/MD") ENDFOREACH(flag_var) - + ADD_DEFINITIONS(-DNOMINMAX) ELSE(MSVC) #Normally set by perl but we don't use the perl flags anymore so we set it. @@ -306,26 +306,26 @@ FIND_PACKAGE(ZLIB REQUIRED) FIND_PACKAGE(MySQL REQUIRED) IF(EQEMU_BUILD_PERL) FIND_PACKAGE(PerlLibs REQUIRED) - INCLUDE_DIRECTORIES("${PERL_INCLUDE_PATH}") + INCLUDE_DIRECTORIES(SYSTEM "${PERL_INCLUDE_PATH}") ENDIF(EQEMU_BUILD_PERL) IF(EQEMU_BUILD_LUA) FIND_PACKAGE(EQLua51 REQUIRED) SET(Boost_USE_STATIC_LIBS OFF) - SET(Boost_USE_MULTITHREADED ON) + SET(Boost_USE_MULTITHREADED ON) SET(Boost_USE_STATIC_RUNTIME OFF) SET(BOOST_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/dependencies/boost") FIND_PACKAGE(Boost REQUIRED) - INCLUDE_DIRECTORIES("${LUA_INCLUDE_DIR}" "${Boost_INCLUDE_DIRS}" "luabind") - + INCLUDE_DIRECTORIES(SYSTEM "${LUA_INCLUDE_DIR}" "${Boost_INCLUDE_DIRS}" "luabind") + OPTION(EQEMU_SANITIZE_LUA_LIBS "Sanitize Lua Libraries (Remove OS and IO standard libraries from being able to run)." ON) IF(EQEMU_SANITIZE_LUA_LIBS) ADD_DEFINITIONS(-DSANITIZE_LUA_LIBS) ENDIF(EQEMU_SANITIZE_LUA_LIBS) ENDIF(EQEMU_BUILD_LUA) -INCLUDE_DIRECTORIES("${ZLIB_INCLUDE_DIRS}" "${MySQL_INCLUDE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/common/glm/glm") +INCLUDE_DIRECTORIES(SYSTEM "${ZLIB_INCLUDE_DIRS}" "${MySQL_INCLUDE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/common/glm/glm") IF(EQEMU_BUILD_LUA) ADD_SUBDIRECTORY(luabind) diff --git a/changelog.txt b/changelog.txt index 3a72ea152..e24dcd59d 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,31 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 12/04/2014 == +Kayen: Ranged attacks will now more accurately check MAX firing range, fixing the issue where you would +hit ranged attack and nothing would happpen due to incorrect server side range checks. +Trevius: Initial addition of the RoF2 client from May 10th 2013 (currently available on Steam as the F2P client). +Trevius: RoF2 is disabled by default, but you can enable by editing /common/patches/patches.cpp (see comments) + +== 12/01/2014 == +Trevius: Mercenaries now spawn as the same Gender and Size of the Merchant they are purchased from. +Trevius: Mercenaries now spawn with randomized facial features when purchased. +Trevius: Setting a lastname for NPCs will now override any hard coded lastname (such as GM Trainers). + +Required SQL: utils/sql/git/required/2014_12_01_mercs_table_update.sql + +== 11/28/2014 == +Trevius: Fixed a zone crash related to numhits for spells. +Trevius: Fixed a query related to group leaders logging in. +Trevius (Natedog): Fixed a world crash related to attempting to join an adventure with Mercenaries. + +== 11/27/2014 == +Kayen: Projectiles (ie Arrows) fired from archery will now do damage upon impact instead of instantly (consistent w/ live). +Optional SQL: utils/sql/git/optional/2014_11_27_ProjectileDmgOnImpact.sql + +== 11/25/2014 == +Trevius: Spells that modify model size are now limited to 2 size adjustments from the base size. +Trevius: Fix to prevent Mercenaries from being set as Group Leader. + == 11/24/2014 == Trevius: Added Rule NPC:EnableMeritBasedFaction (disabled by default) - Allows faction gain to work similar to experience. diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index ac42f5e4e..06547f418 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -72,6 +72,7 @@ SET(common_sources patches/sod.cpp patches/sof.cpp patches/rof.cpp + patches/rof2.cpp patches/titanium.cpp patches/underfoot.cpp SocketLib/Base64.cpp @@ -168,6 +169,7 @@ SET(common_headers ptimer.h queue.h races.h + random.h rdtsc.h rulesys.h ruletypes.h @@ -216,6 +218,11 @@ SET(common_headers patches/rof_itemfields.h patches/rof_ops.h patches/rof_structs.h + patches/rof2.h + patches/rof2_constants.h + patches/rof2_itemfields.h + patches/rof2_ops.h + patches/rof2_structs.h patches/titanium.h patches/titanium_constants.h patches/titanium_itemfields.h @@ -269,6 +276,11 @@ SOURCE_GROUP(Patches FILES patches/rof_ops.h patches/rof_constants.h patches/rof_structs.h + patches/rof2.h + patches/rof2_itemfields.h + patches/rof2_ops.h + patches/rof2_constants.h + patches/rof2_structs.h patches/titanium.h patches/titanium_itemfields.h patches/titanium_ops.h @@ -284,6 +296,7 @@ SOURCE_GROUP(Patches FILES patches/sod.cpp patches/sof.cpp patches/rof.cpp + patches/rof2.cpp patches/titanium.cpp patches/underfoot.cpp ) @@ -334,7 +347,9 @@ ADD_LIBRARY(common ${common_sources} ${common_headers}) IF(UNIX) ADD_DEFINITIONS(-fPIC) - SET_SOURCE_FILES_PROPERTIES("patches/sod.cpp" "patches/sof.cpp" "patches/rof.cpp" "patches/underfoot.cpp" PROPERTIES COMPILE_FLAGS -O0) + SET_SOURCE_FILES_PROPERTIES("SocketLib/Mime.cpp" PROPERTY COMPILE_FLAGS -Wno-unused-result) + SET_SOURCE_FILES_PROPERTIES("patches/sod.cpp" "patches/sof.cpp" "patches/rof.cpp" "patches/rof2.cpp" "patches/underfoot.cpp" PROPERTIES COMPILE_FLAGS -O0) ENDIF(UNIX) + SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) diff --git a/common/database.cpp b/common/database.cpp index 241c4d171..4cafc56d1 100644 --- a/common/database.cpp +++ b/common/database.cpp @@ -17,19 +17,16 @@ */ #include "../common/debug.h" #include "../common/rulesys.h" -#include -#include +#include +#include #include +#include +#include +#include +#include #include #include #include -#include -#include -#include -#include -#include -#include -#include // Disgrace: for windows compile #ifdef _WINDOWS @@ -45,7 +42,6 @@ #include "database.h" #include "eq_packet_structs.h" -#include "guilds.h" #include "string_util.h" #include "extprofile.h" @@ -768,7 +764,10 @@ uint32 Database::GetCharacterID(const char *name) { std::string query = StringFormat("SELECT `id` FROM `character_data` WHERE `name` = '%s'", name); auto results = QueryDatabase(query); auto row = results.begin(); - if (row[0]){ return atoi(row[0]); } + if (results.RowCount() == 1) + { + return atoi(row[0]); + } return 0; } @@ -901,14 +900,27 @@ bool Database::CheckDatabaseConversions() { CheckDatabaseConvertCorpseDeblob(); /* Fetch Automatic Database Upgrade Script */ - if (!std::ifstream("db_update.pl")){ + // if (!std::ifstream("db_update.pl")){ std::cout << "Pulling down automatic database upgrade script...\n" << std::endl; #ifdef _WIN32 system("perl -MLWP::UserAgent -e \"require LWP::UserAgent; my $ua = LWP::UserAgent->new; $ua->timeout(10); $ua->env_proxy; my $response = $ua->get('https://raw.githubusercontent.com/EQEmu/Server/master/utils/scripts/db_update.pl'); if ($response->is_success){ open(FILE, '> db_update.pl'); print FILE $response->decoded_content; close(FILE); }\""); #else system("wget -O db_update.pl https://raw.githubusercontent.com/EQEmu/Server/master/utils/scripts/db_update.pl"); #endif - } + // } + + /* + Automatic Database Upgrade Script + Script: db_update.pl V 1 - the number that world passes to the script will + force the script to check for a newer version to update itself with + db_update.pl ran_from_world - won't bring up a menu if your database versions match + db_update.pl - ran standalone will bring up a menu prompt + */ + + /* Check for a new version of this script, the arg passed + would have to be higher than the copy they have downloaded + locally and they will re fetch */ + system("perl db_update.pl V 1"); /* Run Automatic Database Upgrade Script */ system("perl db_update.pl ran_from_world"); @@ -3254,7 +3266,7 @@ char* Database::GetGroupLeaderForLogin(const char* name, char* leaderbuf) { strcpy(leaderbuf, ""); uint32 group_id = 0; - std::string query = StringFormat("SELECT `groupid` FROM `group_id` WHERE `name = '%s'", name); + std::string query = StringFormat("SELECT `groupid` FROM `group_id` WHERE `name` = '%s'", name); auto results = QueryDatabase(query); for (auto row = results.begin(); row != results.end(); ++row) @@ -3264,7 +3276,7 @@ char* Database::GetGroupLeaderForLogin(const char* name, char* leaderbuf) { if (group_id == 0) return leaderbuf; - query = StringFormat("SELECT `leadername` FROM `group_leader` WHERE `gid` = '%u' AND `groupid` = %u LIMIT 1", group_id); + query = StringFormat("SELECT `leadername` FROM `group_leaders` WHERE `gid` = '%u' LIMIT 1", group_id); results = QueryDatabase(query); for (auto row = results.begin(); row != results.end(); ++row) diff --git a/common/database.h b/common/database.h index 9be14f1cb..6325b4e3d 100644 --- a/common/database.h +++ b/common/database.h @@ -41,6 +41,7 @@ class SpawnGroupList; class Petition; class Client; class Merc; +class MySQLRequestResult; struct Combine_Struct; //struct Faction; //struct FactionMods; @@ -68,21 +69,15 @@ uint8 eventid; EventLogDetails_Struct eld[255]; }; - -// Added By Hogie -// INSERT into variables (varname,value) values('decaytime [minlevel] [maxlevel]','[number of seconds]'); -// IE: decaytime 1 54 = Levels 1 through 54 -// decaytime 55 100 = Levels 55 through 100 -// It will always put the LAST time for the level (I think) from the Database struct npcDecayTimes_Struct { uint16 minlvl; uint16 maxlvl; uint32 seconds; }; -// Added By Hogie -- End + struct VarCache_Struct { - char varname[26]; // varname is char(25) in database + char varname[26]; char value[0]; }; diff --git a/common/eq_dictionary.cpp b/common/eq_dictionary.cpp index f85d3110f..3756ca8fe 100644 --- a/common/eq_dictionary.cpp +++ b/common/eq_dictionary.cpp @@ -539,7 +539,7 @@ uint16 EQLimits::InventoryMapSize(int16 map, uint32 version) { /*SoD*/ EmuConstants::MAP_POSSESSIONS_SIZE, /*Underfoot*/ EmuConstants::MAP_POSSESSIONS_SIZE, /*RoF*/ EmuConstants::MAP_POSSESSIONS_SIZE, -/*RoF2*/ 0, +/*RoF2*/ EmuConstants::MAP_POSSESSIONS_SIZE, /*NPC*/ EmuConstants::MAP_POSSESSIONS_SIZE, /*Merc*/ EmuConstants::MAP_POSSESSIONS_SIZE, @@ -554,7 +554,7 @@ uint16 EQLimits::InventoryMapSize(int16 map, uint32 version) { /*SoD*/ EmuConstants::MAP_BANK_SIZE, /*Underfoot*/ EmuConstants::MAP_BANK_SIZE, /*RoF*/ EmuConstants::MAP_BANK_SIZE, -/*RoF2*/ 0, +/*RoF2*/ EmuConstants::MAP_BANK_SIZE, /*NPC*/ NOT_USED, /*Merc*/ NOT_USED, @@ -569,7 +569,7 @@ uint16 EQLimits::InventoryMapSize(int16 map, uint32 version) { /*SoD*/ EmuConstants::MAP_SHARED_BANK_SIZE, /*Underfoot*/ EmuConstants::MAP_SHARED_BANK_SIZE, /*RoF*/ EmuConstants::MAP_SHARED_BANK_SIZE, -/*RoF2*/ 0, +/*RoF2*/ EmuConstants::MAP_SHARED_BANK_SIZE, /*NPC*/ NOT_USED, /*Merc*/ NOT_USED, @@ -584,7 +584,7 @@ uint16 EQLimits::InventoryMapSize(int16 map, uint32 version) { /*SoD*/ EmuConstants::MAP_TRADE_SIZE, /*Underfoot*/ EmuConstants::MAP_TRADE_SIZE, /*RoF*/ EmuConstants::MAP_TRADE_SIZE, -/*RoF2*/ 0, +/*RoF2*/ EmuConstants::MAP_TRADE_SIZE, /*NPC*/ 4, /*Merc*/ 4, @@ -599,7 +599,7 @@ uint16 EQLimits::InventoryMapSize(int16 map, uint32 version) { /*SoD*/ EmuConstants::MAP_WORLD_SIZE, /*Underfoot*/ EmuConstants::MAP_WORLD_SIZE, /*RoF*/ EmuConstants::MAP_WORLD_SIZE, -/*RoF2*/ 0, +/*RoF2*/ EmuConstants::MAP_WORLD_SIZE, /*NPC*/ NOT_USED, /*Merc*/ NOT_USED, @@ -614,7 +614,7 @@ uint16 EQLimits::InventoryMapSize(int16 map, uint32 version) { /*SoD*/ EmuConstants::MAP_LIMBO_SIZE, /*Underfoot*/ EmuConstants::MAP_LIMBO_SIZE, /*RoF*/ EmuConstants::MAP_LIMBO_SIZE, -/*RoF2*/ 0, +/*RoF2*/ EmuConstants::MAP_LIMBO_SIZE, /*NPC*/ NOT_USED, /*Merc*/ NOT_USED, @@ -629,7 +629,7 @@ uint16 EQLimits::InventoryMapSize(int16 map, uint32 version) { /*SoD*/ EmuConstants::MAP_TRIBUTE_SIZE, /*Underfoot*/ EmuConstants::MAP_TRIBUTE_SIZE, /*RoF*/ EmuConstants::MAP_TRIBUTE_SIZE, -/*RoF2*/ 0, +/*RoF2*/ EmuConstants::MAP_TRIBUTE_SIZE, /*NPC*/ 0, /*Merc*/ 0, @@ -644,7 +644,7 @@ uint16 EQLimits::InventoryMapSize(int16 map, uint32 version) { /*SoD*/ 0, /*Underfoot*/ 0, /*RoF*/ EmuConstants::MAP_TROPHY_TRIBUTE_SIZE, -/*RoF2*/ 0, +/*RoF2*/ EmuConstants::MAP_TROPHY_TRIBUTE_SIZE, /*NPC*/ 0, /*Merc*/ 0, @@ -659,7 +659,7 @@ uint16 EQLimits::InventoryMapSize(int16 map, uint32 version) { /*SoD*/ 0, /*Underfoot*/ 0, /*RoF*/ EmuConstants::MAP_GUILD_TRIBUTE_SIZE, -/*RoF2*/ 0, +/*RoF2*/ EmuConstants::MAP_GUILD_TRIBUTE_SIZE, /*NPC*/ 0, /*Merc*/ 0, @@ -674,7 +674,7 @@ uint16 EQLimits::InventoryMapSize(int16 map, uint32 version) { /*SoD*/ 0, /*Underfoot*/ 0, /*RoF*/ EmuConstants::MAP_MERCHANT_SIZE, -/*RoF2*/ 0, +/*RoF2*/ EmuConstants::MAP_MERCHANT_SIZE, /*NPC*/ 0, /*Merc*/ 0, @@ -689,7 +689,7 @@ uint16 EQLimits::InventoryMapSize(int16 map, uint32 version) { /*SoD*/ 0, /*Underfoot*/ 0, /*RoF*/ EmuConstants::MAP_DELETED_SIZE, -/*RoF2*/ 0, +/*RoF2*/ EmuConstants::MAP_DELETED_SIZE, /*NPC*/ 0, /*Merc*/ 0, @@ -704,7 +704,7 @@ uint16 EQLimits::InventoryMapSize(int16 map, uint32 version) { /*SoD*/ SoD::consts::MAP_CORPSE_SIZE, /*Underfoot*/ Underfoot::consts::MAP_CORPSE_SIZE, /*RoF*/ RoF::consts::MAP_CORPSE_SIZE, -/*RoF2*/ 0, +/*RoF2*/ RoF2::consts::MAP_CORPSE_SIZE, /*NPC*/ 0, /*Merc*/ 0, @@ -719,7 +719,7 @@ uint16 EQLimits::InventoryMapSize(int16 map, uint32 version) { /*SoD*/ EmuConstants::MAP_BAZAAR_SIZE, /*Underfoot*/ EmuConstants::MAP_BAZAAR_SIZE, /*RoF*/ EmuConstants::MAP_BAZAAR_SIZE, -/*RoF2*/ 0, +/*RoF2*/ EmuConstants::MAP_BAZAAR_SIZE, /*NPC*/ 0, // this may need to be 'EmuConstants::MAP_BAZAAR_SIZE' if offline client traders respawn as an npc /*Merc*/ 0, @@ -734,7 +734,7 @@ uint16 EQLimits::InventoryMapSize(int16 map, uint32 version) { /*SoD*/ SoD::consts::MAP_INSPECT_SIZE, /*Underfoot*/ Underfoot::consts::MAP_INSPECT_SIZE, /*RoF*/ RoF::consts::MAP_INSPECT_SIZE, -/*RoF2*/ 0, +/*RoF2*/ RoF2::consts::MAP_INSPECT_SIZE, /*NPC*/ NOT_USED, /*Merc*/ NOT_USED, @@ -749,7 +749,7 @@ uint16 EQLimits::InventoryMapSize(int16 map, uint32 version) { /*SoD*/ 0, /*Underfoot*/ 0, /*RoF*/ EmuConstants::MAP_REAL_ESTATE_SIZE, -/*RoF2*/ 0, +/*RoF2*/ EmuConstants::MAP_REAL_ESTATE_SIZE, /*NPC*/ 0, /*Merc*/ 0, @@ -764,7 +764,7 @@ uint16 EQLimits::InventoryMapSize(int16 map, uint32 version) { /*SoD*/ 0, /*Underfoot*/ 0, /*RoF*/ EmuConstants::MAP_VIEW_MOD_PC_SIZE, -/*RoF2*/ 0, +/*RoF2*/ EmuConstants::MAP_VIEW_MOD_PC_SIZE, /*NPC*/ 0, /*Merc*/ 0, @@ -779,7 +779,7 @@ uint16 EQLimits::InventoryMapSize(int16 map, uint32 version) { /*SoD*/ 0, /*Underfoot*/ 0, /*RoF*/ EmuConstants::MAP_VIEW_MOD_BANK_SIZE, -/*RoF2*/ 0, +/*RoF2*/ EmuConstants::MAP_VIEW_MOD_BANK_SIZE, /*NPC*/ 0, /*Merc*/ 0, @@ -794,7 +794,7 @@ uint16 EQLimits::InventoryMapSize(int16 map, uint32 version) { /*SoD*/ 0, /*Underfoot*/ 0, /*RoF*/ EmuConstants::MAP_VIEW_MOD_SHARED_BANK_SIZE, -/*RoF2*/ 0, +/*RoF2*/ EmuConstants::MAP_VIEW_MOD_SHARED_BANK_SIZE, /*NPC*/ 0, /*Merc*/ 0, @@ -809,7 +809,7 @@ uint16 EQLimits::InventoryMapSize(int16 map, uint32 version) { /*SoD*/ 0, /*Underfoot*/ 0, /*RoF*/ EmuConstants::MAP_VIEW_MOD_LIMBO_SIZE, -/*RoF2*/ 0, +/*RoF2*/ EmuConstants::MAP_VIEW_MOD_LIMBO_SIZE, /*NPC*/ 0, /*Merc*/ 0, @@ -824,7 +824,7 @@ uint16 EQLimits::InventoryMapSize(int16 map, uint32 version) { /*SoD*/ 0, /*Underfoot*/ 0, /*RoF*/ EmuConstants::MAP_ALT_STORAGE_SIZE, -/*RoF2*/ 0, +/*RoF2*/ EmuConstants::MAP_ALT_STORAGE_SIZE, /*NPC*/ 0, /*Merc*/ 0, @@ -839,7 +839,7 @@ uint16 EQLimits::InventoryMapSize(int16 map, uint32 version) { /*SoD*/ 0, /*Underfoot*/ 0, /*RoF*/ EmuConstants::MAP_ARCHIVED_SIZE, -/*RoF2*/ 0, +/*RoF2*/ EmuConstants::MAP_ARCHIVED_SIZE, /*NPC*/ 0, /*Merc*/ 0, @@ -854,7 +854,7 @@ uint16 EQLimits::InventoryMapSize(int16 map, uint32 version) { /*SoD*/ 0, /*Underfoot*/ 0, /*RoF*/ EmuConstants::MAP_MAIL_SIZE, -/*RoF2*/ 0, +/*RoF2*/ EmuConstants::MAP_MAIL_SIZE, /*NPC*/ 0, /*Merc*/ 0, @@ -869,7 +869,7 @@ uint16 EQLimits::InventoryMapSize(int16 map, uint32 version) { /*SoD*/ 0, /*Underfoot*/ 0, /*RoF*/ EmuConstants::MAP_GUILD_TROPHY_TRIBUTE_SIZE, -/*RoF2*/ 0, +/*RoF2*/ EmuConstants::MAP_GUILD_TROPHY_TRIBUTE_SIZE, /*NPC*/ 0, /*Merc*/ 0, @@ -884,7 +884,7 @@ uint16 EQLimits::InventoryMapSize(int16 map, uint32 version) { /*SoD*/ NOT_USED, /*Underfoot*/ NOT_USED, /*RoF*/ EmuConstants::MAP_KRONO_SIZE, -/*RoF2*/ 0, +/*RoF2*/ EmuConstants::MAP_KRONO_SIZE, /*NPC*/ 0, /*Merc*/ 0, @@ -899,7 +899,7 @@ uint16 EQLimits::InventoryMapSize(int16 map, uint32 version) { /*SoD*/ 0, /*Underfoot*/ 0, /*RoF*/ EmuConstants::MAP_OTHER_SIZE, -/*RoF2*/ 0, +/*RoF2*/ EmuConstants::MAP_OTHER_SIZE, /*NPC*/ 0, /*Merc*/ 0, @@ -1012,7 +1012,7 @@ bool EQLimits::AllowsEmptyBagInBag(uint32 version) { /*SoD*/ SoD::limits::ALLOWS_EMPTY_BAG_IN_BAG, /*Underfoot*/ Underfoot::limits::ALLOWS_EMPTY_BAG_IN_BAG, /*RoF*/ RoF::limits::ALLOWS_EMPTY_BAG_IN_BAG, -/*RoF2*/ false, +/*RoF2*/ RoF2::limits::ALLOWS_EMPTY_BAG_IN_BAG, /*NPC*/ false, /*Merc*/ false, @@ -1033,7 +1033,7 @@ bool EQLimits::AllowsClickCastFromBag(uint32 version) { /*SoD*/ SoD::limits::ALLOWS_CLICK_CAST_FROM_BAG, /*Underfoot*/ Underfoot::limits::ALLOWS_CLICK_CAST_FROM_BAG, /*RoF*/ RoF::limits::ALLOWS_CLICK_CAST_FROM_BAG, -/*RoF2*/ false, +/*RoF2*/ RoF2::limits::ALLOWS_CLICK_CAST_FROM_BAG, /*NPC*/ false, /*Merc*/ false, @@ -1054,7 +1054,7 @@ uint16 EQLimits::ItemCommonSize(uint32 version) { /*SoD*/ EmuConstants::ITEM_COMMON_SIZE, /*Underfoot*/ EmuConstants::ITEM_COMMON_SIZE, /*RoF*/ EmuConstants::ITEM_COMMON_SIZE, -/*RoF2*/ 0, +/*RoF2*/ EmuConstants::ITEM_COMMON_SIZE, /*NPC*/ EmuConstants::ITEM_COMMON_SIZE, /*Merc*/ EmuConstants::ITEM_COMMON_SIZE, @@ -1074,7 +1074,7 @@ uint16 EQLimits::ItemContainerSize(uint32 version) { /*SoD*/ EmuConstants::ITEM_CONTAINER_SIZE, /*Underfoot*/ EmuConstants::ITEM_CONTAINER_SIZE, /*RoF*/ EmuConstants::ITEM_CONTAINER_SIZE, -/*RoF2*/ 0, +/*RoF2*/ EmuConstants::ITEM_CONTAINER_SIZE, /*NPC*/ EmuConstants::ITEM_CONTAINER_SIZE, /*Merc*/ EmuConstants::ITEM_CONTAINER_SIZE, @@ -1094,7 +1094,7 @@ bool EQLimits::CoinHasWeight(uint32 version) { /*SoD*/ SoD::limits::COIN_HAS_WEIGHT, /*Underfoot*/ Underfoot::limits::COIN_HAS_WEIGHT, /*RoF*/ RoF::limits::COIN_HAS_WEIGHT, -/*RoF2*/ true, +/*RoF2*/ RoF::limits::COIN_HAS_WEIGHT, /*NPC*/ true, /*Merc*/ true, @@ -1114,7 +1114,7 @@ uint32 EQLimits::BandoliersCount(uint32 version) { /*SoD*/ EmuConstants::BANDOLIERS_COUNT, /*Underfoot*/ EmuConstants::BANDOLIERS_COUNT, /*RoF*/ EmuConstants::BANDOLIERS_COUNT, -/*RoF2*/ 0, +/*RoF2*/ EmuConstants::BANDOLIERS_COUNT, /*NPC*/ NOT_USED, /*Merc*/ NOT_USED, @@ -1134,7 +1134,7 @@ uint32 EQLimits::BandolierSize(uint32 version) { /*SoD*/ EmuConstants::BANDOLIER_SIZE, /*Underfoot*/ EmuConstants::BANDOLIER_SIZE, /*RoF*/ EmuConstants::BANDOLIER_SIZE, -/*RoF2*/ 0, +/*RoF2*/ EmuConstants::BANDOLIER_SIZE, /*NPC*/ NOT_USED, /*Merc*/ NOT_USED, @@ -1154,7 +1154,7 @@ uint32 EQLimits::PotionBeltSize(uint32 version) { /*SoD*/ EmuConstants::POTION_BELT_SIZE, /*Underfoot*/ EmuConstants::POTION_BELT_SIZE, /*RoF*/ EmuConstants::POTION_BELT_SIZE, -/*RoF2*/ 0, +/*RoF2*/ EmuConstants::POTION_BELT_SIZE, /*NPC*/ NOT_USED, /*Merc*/ NOT_USED, diff --git a/common/eq_dictionary.h b/common/eq_dictionary.h index c08809a12..8ef38c77c 100644 --- a/common/eq_dictionary.h +++ b/common/eq_dictionary.h @@ -32,7 +32,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "../common/patches/sod_constants.h" #include "../common/patches/underfoot_constants.h" #include "../common/patches/rof_constants.h" -//#include "../common/patches/rof2_constants.h" +#include "../common/patches/rof2_constants.h" // *** DO NOT CHANGE without a full understanding of the consequences..the server is set up to use these settings explicitly!! *** // *** You will cause compilation failures and corrupt your database if partial or incorrect attempts to change them are made!! *** diff --git a/common/item.cpp b/common/item.cpp index e59edebe3..f92bc6a67 100644 --- a/common/item.cpp +++ b/common/item.cpp @@ -1412,6 +1412,8 @@ ItemInst::ItemInst(const Item_Struct* item, int16 charges) { m_scaledItem = nullptr; m_evolveInfo = nullptr; m_scaling = false; + m_ornamenticon = 0; + m_ornamentidfile = 0; } ItemInst::ItemInst(SharedDatabase *db, uint32 item_id, int16 charges) { @@ -1434,6 +1436,8 @@ ItemInst::ItemInst(SharedDatabase *db, uint32 item_id, int16 charges) { m_scaledItem = nullptr; m_evolveInfo = nullptr; m_scaling = false; + m_ornamenticon = 0; + m_ornamentidfile = 0; } ItemInst::ItemInst(ItemInstTypes use_type) { @@ -1451,6 +1455,8 @@ ItemInst::ItemInst(ItemInstTypes use_type) { m_scaledItem = nullptr; m_evolveInfo = nullptr; m_scaling = false; + m_ornamenticon = 0; + m_ornamentidfile = 0; } // Make a copy of an ItemInst object @@ -1501,6 +1507,8 @@ ItemInst::ItemInst(const ItemInst& copy) m_evolveInfo = nullptr; m_scaling = copy.m_scaling; + m_ornamenticon = copy.m_ornamenticon; + m_ornamentidfile = copy.m_ornamentidfile; } // Clean up container contents @@ -1789,6 +1797,52 @@ ItemInst* ItemInst::GetOrnamentationAug(int ornamentationAugtype) const return nullptr; } +bool ItemInst::CanTransform(const Item_Struct *ItemToTry, const Item_Struct *Container, bool AllowAll) { + if (!ItemToTry || !Container) return false; + + if (ItemToTry->ItemType == ItemTypeArrow || strnlen(Container->CharmFile, 30) == 0) + return false; + + if (AllowAll && strncasecmp(Container->CharmFile, "ITEMTRANSFIGSHIELD", 18) && strncasecmp(Container->CharmFile, "ITEMTransfigBow", 15)) { + switch (ItemToTry->ItemType) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 35: + case 45: + return true; + } + } + + static std::map types; + types["itemtransfig1hp"] = 2; + types["itemtransfig1hs"] = 0; + types["itemtransfig2hb"] = 4; + types["itemtransfig2hp"] = 35; + types["itemtransfig2hs"] = 1; + types["itemtransfigblunt"] = 3; + types["itemtransfigbow"] = 5; + types["itemtransfighth"] = 45; + types["itemtransfigshield"] = 8; + types["itemtransfigslashing"] = 0; + + auto i = types.find(MakeLowerString(Container->CharmFile)); + if (i != types.end() && i->second == ItemToTry->ItemType) + return true; + + static std::map typestwo; + typestwo["itemtransfigblunt"] = 4; + typestwo["itemtransfigslashing"] = 1; + + i = typestwo.find(MakeLowerString(Container->CharmFile)); + if (i != typestwo.end() && i->second == ItemToTry->ItemType) + return true; + + return false; +} + uint32 ItemInst::GetAugmentItemID(uint8 slot) const { uint32 id = NO_ITEM; diff --git a/common/item.h b/common/item.h index e6d1e8bee..cce166eb0 100644 --- a/common/item.h +++ b/common/item.h @@ -330,6 +330,7 @@ public: ItemInst* RemoveAugment(uint8 index); bool IsAugmented(); ItemInst* GetOrnamentationAug(int ornamentationAugtype) const; + static bool CanTransform(const Item_Struct *ItemToTry, const Item_Struct *Container, bool AllowAll = false); // Has attack/delay? bool IsWeapon() const; @@ -392,6 +393,10 @@ public: void SetActivated(bool activated) { m_activated = activated; } int8 GetEvolveLvl() const { return m_evolveLvl; } void SetScaling(bool v) { m_scaling = v; } + uint32 GetOrnamentationIcon() const { return m_ornamenticon; } + void SetOrnamentIcon(uint32 ornament_icon) { m_ornamenticon = ornament_icon; } + uint32 GetOrnamentationIDFile() const { return m_ornamentidfile; } + void SetOrnamentationIDFile(uint32 ornament_idfile) { m_ornamentidfile = ornament_idfile; } void Initialize(SharedDatabase *db = nullptr); void ScaleItem(); @@ -436,6 +441,8 @@ protected: Item_Struct* m_scaledItem; EvolveInfo* m_evolveInfo; bool m_scaling; + uint32 m_ornamenticon; + uint32 m_ornamentidfile; // // Items inside of this item (augs or contents); diff --git a/common/misc_functions.cpp b/common/misc_functions.cpp index 882cc61c7..81fc2434b 100644 --- a/common/misc_functions.cpp +++ b/common/misc_functions.cpp @@ -54,20 +54,6 @@ #include #endif -static bool WELLRNG_init = false; -static int state_i = 0; -static unsigned int STATE[R]; -static unsigned int z0, z1, z2; -unsigned int (*WELLRNG19937)(void); -static unsigned int case_1 (void); -static unsigned int case_2 (void); -static unsigned int case_3 (void); -static unsigned int case_4 (void); -static unsigned int case_5 (void); -static unsigned int case_6 (void); -uint32 rnd_hash(time_t t, clock_t c); -void oneseed(const uint32 seed); - void CoutTimestamp(bool ms) { time_t rawtime; struct tm* gmt_t; @@ -179,41 +165,6 @@ const char * itoa(int num, char* a,int b) { } #endif -/* - * generate a random integer in the range low-high this - * should be used instead of the rand()%limit method - */ -int MakeRandomInt(int low, int high) -{ - if(low >= high) - return(low); - - //return (rand()%(high-low+1) + (low)); - if(!WELLRNG_init) { - WELLRNG_init = true; - oneseed( rnd_hash( time(nullptr), clock() ) ); - WELLRNG19937 = case_1; - } - unsigned int randomnum = ((WELLRNG19937)()); - if(randomnum == 0xffffffffUL) - return high; - return int ((randomnum / (double)0xffffffffUL) * (high - low + 1) + low); -} - -double MakeRandomFloat(double low, double high) -{ - if(low >= high) - return(low); - - //return (rand() / (double)RAND_MAX * (high - low) + low); - if(!WELLRNG_init) { - WELLRNG_init = true; - oneseed( rnd_hash( time(nullptr), clock() ) ); - WELLRNG19937 = case_1; - } - return ((WELLRNG19937)() / (double)0xffffffffUL * (high - low) + low); -} - uint32 rnd_hash( time_t t, clock_t c ) { // Get a uint32 from t and c @@ -239,111 +190,6 @@ uint32 rnd_hash( time_t t, clock_t c ) return ( h1 + differ++ ) ^ h2; } -void oneseed( const uint32 seed ) -{ - // Initialize generator state with seed - // See Knuth TAOCP Vol 2, 3rd Ed, p.106 for multiplier. - // In previous versions, most significant bits (MSBs) of the seed affect - // only MSBs of the state array. Modified 9 Jan 2002 by Makoto Matsumoto. - register int j = 0; - STATE[j] = seed & 0xffffffffUL; - for (j = 1; j < R; j++) - { - STATE[j] = ( 1812433253UL * ( STATE[j-1] ^ (STATE[j-1] >> 30) ) + j ) & 0xffffffffUL; - } -} - -// WELL RNG code - -/* ***************************************************************************** */ -/* Copyright: Francois Panneton and Pierre L'Ecuyer, University of Montreal */ -/* Makoto Matsumoto, Hiroshima University */ -/* Notice: This code can be used freely for personal, academic, */ -/* or non-commercial purposes. For commercial purposes, */ -/* please contact P. L'Ecuyer at: lecuyer@iro.UMontreal.ca */ -/* A modified "maximally equidistributed" implementation */ -/* by Shin Harase, Hiroshima University. */ -/* ***************************************************************************** */ - -unsigned int case_1 (void){ - // state_i == 0 - z0 = (VRm1Under & MASKL) | (VRm2Under & MASKU); - z1 = MAT0NEG (-25, V0) ^ MAT0POS (27, VM1); - z2 = MAT3POS (9, VM2) ^ MAT0POS (1, VM3); - newV1 = z1 ^ z2; - newV0Under = MAT1 (z0) ^ MAT0NEG (-9, z1) ^ MAT0NEG (-21, z2) ^ MAT0POS (21, newV1); - state_i = R - 1; - WELLRNG19937 = case_3; - return (STATE[state_i] ^ (newVM2Over & BITMASK)); -} - -static unsigned int case_2 (void){ - // state_i == 1 - z0 = (VRm1 & MASKL) | (VRm2Under & MASKU); - z1 = MAT0NEG (-25, V0) ^ MAT0POS (27, VM1); - z2 = MAT3POS (9, VM2) ^ MAT0POS (1, VM3); - newV1 = z1 ^ z2; - newV0 = MAT1 (z0) ^ MAT0NEG (-9, z1) ^ MAT0NEG (-21, z2) ^ MAT0POS (21, newV1); - state_i = 0; - WELLRNG19937 = case_1; - return (STATE[state_i] ^ (newVM2 & BITMASK)); -} - -static unsigned int case_3 (void){ - // state_i+M1 >= R - z0 = (VRm1 & MASKL) | (VRm2 & MASKU); - z1 = MAT0NEG (-25, V0) ^ MAT0POS (27, VM1Over); - z2 = MAT3POS (9, VM2Over) ^ MAT0POS (1, VM3Over); - newV1 = z1 ^ z2; - newV0 = MAT1 (z0) ^ MAT0NEG (-9, z1) ^ MAT0NEG (-21, z2) ^ MAT0POS (21, newV1); - state_i--; - if (state_i + M1 < R) - WELLRNG19937 = case_5; - return (STATE[state_i] ^ (newVM2Over & BITMASK)); -} - -static unsigned int case_4 (void){ - // state_i+M3 >= R - z0 = (VRm1 & MASKL) | (VRm2 & MASKU); - z1 = MAT0NEG (-25, V0) ^ MAT0POS (27, VM1); - z2 = MAT3POS (9, VM2) ^ MAT0POS (1, VM3Over); - newV1 = z1 ^ z2; - newV0 = MAT1 (z0) ^ MAT0NEG (-9, z1) ^ MAT0NEG (-21, z2) ^ MAT0POS (21, newV1); - state_i--; - if (state_i + M3 < R) - WELLRNG19937 = case_6; - return (STATE[state_i] ^ (newVM2 & BITMASK)); -} - -static unsigned int case_5 (void){ - // state_i+M2 >= R - z0 = (VRm1 & MASKL) | (VRm2 & MASKU); - z1 = MAT0NEG (-25, V0) ^ MAT0POS (27, VM1); - z2 = MAT3POS (9, VM2Over) ^ MAT0POS (1, VM3Over); - newV1 = z1 ^ z2; - newV0 = MAT1 (z0) ^ MAT0NEG (-9, z1) ^ MAT0NEG (-21, z2) ^ MAT0POS (21, newV1); - state_i--; - if (state_i + M2 < R) - WELLRNG19937 = case_4; - return (STATE[state_i] ^ (newVM2Over & BITMASK)); -} - -static unsigned int case_6 (void){ - // 2 <= state_i <= (R - M3 - 1) - z0 = (VRm1 & MASKL) | (VRm2 & MASKU); - z1 = MAT0NEG (-25, V0) ^ MAT0POS (27, VM1); - z2 = MAT3POS (9, VM2) ^ MAT0POS (1, VM3); - newV1 = z1 ^ z2; - newV0 = MAT1 (z0) ^ MAT0NEG (-9, z1) ^ MAT0NEG (-21, z2) ^ MAT0POS (21, newV1); - state_i--; - if (state_i == 1) - WELLRNG19937 = case_2; - return (STATE[state_i] ^ (newVM2 & BITMASK)); -} - -// end WELL RNG code - - float EQ13toFloat(int d) { return ( float(d)/float(1<<2)); diff --git a/common/patches/patches.cpp b/common/patches/patches.cpp index 212042210..d35ded9ba 100644 --- a/common/patches/patches.cpp +++ b/common/patches/patches.cpp @@ -8,7 +8,7 @@ #include "sof.h" #include "sod.h" #include "rof.h" -//#include "rof2.h" +#include "rof2.h" void RegisterAllPatches(EQStreamIdentifier &into) { Client62::Register(into); @@ -17,6 +17,7 @@ void RegisterAllPatches(EQStreamIdentifier &into) { SoD::Register(into); Underfoot::Register(into); RoF::Register(into); + // Uncomment the line below to enable RoF2 Client //RoF2::Register(into); } @@ -27,5 +28,6 @@ void ReloadAllPatches() { SoD::Reload(); Underfoot::Reload(); RoF::Reload(); + // Uncomment the line below to enable RoF2 Client //RoF2::Reload(); } diff --git a/common/patches/rof.cpp b/common/patches/rof.cpp index 092101d7d..c7efe0264 100644 --- a/common/patches/rof.cpp +++ b/common/patches/rof.cpp @@ -4888,6 +4888,16 @@ namespace RoF //Icon ornaIcon = aug_weap->Icon; } + else if (inst->GetOrnamentationIDFile() && inst->GetOrnamentationIcon()) { + char tmp[30]; memset(tmp, 0x0, 30); sprintf(tmp, "IT%d", inst->GetOrnamentationIDFile()); + //Mainhand + ss.write(tmp, strlen(tmp)); + ss.write((const char*)&null_term, sizeof(uint8)); + //Offhand + ss.write(tmp, strlen(tmp)); + ss.write((const char*)&null_term, sizeof(uint8)); + ornaIcon = inst->GetOrnamentationIcon(); + } else { ss.write((const char*)&null_term, sizeof(uint8)); //no mh ss.write((const char*)&null_term, sizeof(uint8));//no of diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp new file mode 100644 index 000000000..372da9b07 --- /dev/null +++ b/common/patches/rof2.cpp @@ -0,0 +1,5673 @@ +#include "../debug.h" +#include "rof2.h" +#include "../opcodemgr.h" +#include "../logsys.h" +#include "../eq_stream_ident.h" +#include "../crc32.h" + +#include "../eq_packet_structs.h" +#include "../misc_functions.h" +#include "../string_util.h" +#include "../item.h" +#include "rof2_structs.h" +#include "../rulesys.h" + +#include +#include + +namespace RoF2 +{ + static const char *name = "RoF2"; + static OpcodeManager *opcodes = nullptr; + static Strategy struct_strategy; + + char* SerializeItem(const ItemInst *inst, int16 slot_id, uint32 *length, uint8 depth); + + // server to client inventory location converters + static inline structs::ItemSlotStruct ServerToRoF2Slot(uint32 ServerSlot); + static inline structs::MainInvItemSlotStruct ServerToRoF2MainInvSlot(uint32 ServerSlot); + static inline uint32 ServerToRoF2CorpseSlot(uint32 ServerCorpse); + + // client to server inventory location converters + static inline uint32 RoF2ToServerSlot(structs::ItemSlotStruct RoF2Slot); + static inline uint32 RoF2ToServerMainInvSlot(structs::MainInvItemSlotStruct RoF2Slot); + static inline uint32 RoF2ToServerCorpseSlot(uint32 RoF2Corpse); + + void Register(EQStreamIdentifier &into) + { + //create our opcode manager if we havent already + if (opcodes == nullptr) { + //TODO: get this file name from the config file + std::string opfile = "patch_"; + opfile += name; + opfile += ".conf"; + //load up the opcode manager. + //TODO: figure out how to support shared memory with multiple patches... + opcodes = new RegularOpcodeManager(); + if (!opcodes->LoadOpcodes(opfile.c_str())) { + _log(NET__OPCODES, "Error loading opcodes file %s. Not registering patch %s.", opfile.c_str(), name); + return; + } + } + + //ok, now we have what we need to register. + + EQStream::Signature signature; + std::string pname; + + //register our world signature. + pname = std::string(name) + "_world"; + signature.ignore_eq_opcode = 0; + signature.first_length = sizeof(structs::LoginInfo_Struct); + signature.first_eq_opcode = opcodes->EmuToEQ(OP_SendLoginInfo); + into.RegisterPatch(signature, pname.c_str(), &opcodes, &struct_strategy); + + //register our zone signature. + pname = std::string(name) + "_zone"; + signature.ignore_eq_opcode = opcodes->EmuToEQ(OP_AckPacket); + signature.first_length = sizeof(structs::ClientZoneEntry_Struct); + signature.first_eq_opcode = opcodes->EmuToEQ(OP_ZoneEntry); + into.RegisterPatch(signature, pname.c_str(), &opcodes, &struct_strategy); + + + + _log(NET__IDENTIFY, "Registered patch %s", name); + } + + void Reload() + { + //we have a big problem to solve here when we switch back to shared memory + //opcode managers because we need to change the manager pointer, which means + //we need to go to every stream and replace it's manager. + + if (opcodes != nullptr) { + //TODO: get this file name from the config file + std::string opfile = "patch_"; + opfile += name; + opfile += ".conf"; + if (!opcodes->ReloadOpcodes(opfile.c_str())) { + _log(NET__OPCODES, "Error reloading opcodes file %s for patch %s.", opfile.c_str(), name); + return; + } + _log(NET__OPCODES, "Reloaded opcodes for patch %s", name); + } + } + + Strategy::Strategy() : StructStrategy() + { + //all opcodes default to passthrough. +#include "ss_register.h" +#include "rof_ops.h" + } + + std::string Strategy::Describe() const + { + std::string r; + r += "Patch "; + r += name; + return(r); + } + + const EQClientVersion Strategy::ClientVersion() const + { + return EQClientRoF2; + } + +#include "ss_define.h" + +// ENCODE methods + ENCODE(OP_Action) + { + ENCODE_LENGTH_EXACT(Action_Struct); + SETUP_DIRECT_ENCODE(Action_Struct, structs::ActionAlt_Struct); + + OUT(target); + OUT(source); + OUT(level); + eq->unknown06 = 0; + eq->instrument_mod = 1.0f + (emu->instrument_mod - 10) / 10.0f; + eq->bard_focus_id = emu->bard_focus_id; + eq->knockback_angle = emu->sequence; + eq->unknown22 = 0; + OUT(type); + eq->damage = 0; + eq->unknown31 = 0; + OUT(spell); + eq->level2 = eq->level; + eq->effect_flag = emu->buff_unknown; + eq->unknown39 = 14; + eq->unknown43 = 0; + eq->unknown44 = 17; + eq->unknown45 = 0; + eq->unknown46 = -1; + eq->unknown50 = 0; + eq->unknown54 = 0; + + FINISH_ENCODE(); + } + + ENCODE(OP_AdventureMerchantSell) + { + ENCODE_LENGTH_EXACT(Adventure_Sell_Struct); + SETUP_DIRECT_ENCODE(Adventure_Sell_Struct, structs::Adventure_Sell_Struct); + + eq->unknown000 = 1; + OUT(npcid); + eq->slot = ServerToRoF2MainInvSlot(emu->slot); + OUT(charges); + OUT(sell_price); + + FINISH_ENCODE(); + } + + ENCODE(OP_AltCurrency) + { + EQApplicationPacket *in = *p; + *p = nullptr; + + unsigned char *emu_buffer = in->pBuffer; + uint32 opcode = *((uint32*)emu_buffer); + + if (opcode == 8) { + AltCurrencyPopulate_Struct *populate = (AltCurrencyPopulate_Struct*)emu_buffer; + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_AltCurrency, sizeof(structs::AltCurrencyPopulate_Struct) + + sizeof(structs::AltCurrencyPopulateEntry_Struct) * populate->count); + structs::AltCurrencyPopulate_Struct *out_populate = (structs::AltCurrencyPopulate_Struct*)outapp->pBuffer; + + out_populate->opcode = populate->opcode; + out_populate->count = populate->count; + for (uint32 i = 0; i < populate->count; ++i) { + out_populate->entries[i].currency_number = populate->entries[i].currency_number; + out_populate->entries[i].unknown00 = populate->entries[i].unknown00; + out_populate->entries[i].currency_number2 = populate->entries[i].currency_number2; + out_populate->entries[i].item_id = populate->entries[i].item_id; + out_populate->entries[i].item_icon = populate->entries[i].item_icon; + out_populate->entries[i].stack_size = populate->entries[i].stack_size; + out_populate->entries[i].display = ((populate->entries[i].stack_size > 0) ? 1 : 0); + } + + dest->FastQueuePacket(&outapp, ack_req); + } + else { + EQApplicationPacket *outapp = new EQApplicationPacket(OP_AltCurrency, sizeof(AltCurrencyUpdate_Struct)); + memcpy(outapp->pBuffer, emu_buffer, sizeof(AltCurrencyUpdate_Struct)); + dest->FastQueuePacket(&outapp, ack_req); + } + + //dest->FastQueuePacket(&outapp, ack_req); + delete in; + } + + ENCODE(OP_AltCurrencySell) + { + ENCODE_LENGTH_EXACT(AltCurrencySellItem_Struct); + SETUP_DIRECT_ENCODE(AltCurrencySellItem_Struct, structs::AltCurrencySellItem_Struct); + + OUT(merchant_entity_id); + eq->slot_id = ServerToRoF2Slot(emu->slot_id); + OUT(charges); + OUT(cost); + + FINISH_ENCODE(); + } + + ENCODE(OP_Animation) + { + ENCODE_LENGTH_EXACT(Animation_Struct); + SETUP_DIRECT_ENCODE(Animation_Struct, structs::Animation_Struct); + + OUT(spawnid); + OUT(value); + OUT(action); + + FINISH_ENCODE(); + } + + ENCODE(OP_ApplyPoison) + { + ENCODE_LENGTH_EXACT(ApplyPoison_Struct); + SETUP_DIRECT_ENCODE(ApplyPoison_Struct, structs::ApplyPoison_Struct); + + eq->inventorySlot = ServerToRoF2MainInvSlot(emu->inventorySlot); + OUT(success); + + FINISH_ENCODE(); + } + + ENCODE(OP_AugmentInfo) + { + ENCODE_LENGTH_EXACT(AugmentInfo_Struct); + SETUP_DIRECT_ENCODE(AugmentInfo_Struct, structs::AugmentInfo_Struct); + + OUT(itemid); + OUT(window); + strn0cpy(eq->augment_info, emu->augment_info, 64); + + FINISH_ENCODE(); + } + + ENCODE(OP_Barter) + { + EQApplicationPacket *in = *p; + *p = nullptr; + + char *Buffer = (char *)in->pBuffer; + + uint32 SubAction = VARSTRUCT_DECODE_TYPE(uint32, Buffer); + + if (SubAction != Barter_BuyerAppearance) + { + dest->FastQueuePacket(&in, ack_req); + + return; + } + + unsigned char *__emu_buffer = in->pBuffer; + + in->size = 80; + + in->pBuffer = new unsigned char[in->size]; + + char *OutBuffer = (char *)in->pBuffer; + + char Name[64]; + + VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, SubAction); + uint32 EntityID = VARSTRUCT_DECODE_TYPE(uint32, Buffer); + VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, EntityID); + uint8 Toggle = VARSTRUCT_DECODE_TYPE(uint8, Buffer); + VARSTRUCT_DECODE_STRING(Name, Buffer); + VARSTRUCT_ENCODE_STRING(OutBuffer, Name); + OutBuffer = (char *)in->pBuffer + 72; + VARSTRUCT_ENCODE_TYPE(uint8, OutBuffer, Toggle); + + delete[] __emu_buffer; + dest->FastQueuePacket(&in, ack_req); + } + + ENCODE(OP_BazaarSearch) + { + EQApplicationPacket *in = *p; + *p = nullptr; + + char *Buffer = (char *)in->pBuffer; + + uint8 SubAction = VARSTRUCT_DECODE_TYPE(uint8, Buffer); + + if (SubAction != BazaarSearchResults) + { + dest->FastQueuePacket(&in, ack_req); + return; + } + + unsigned char *__emu_buffer = in->pBuffer; + + BazaarSearchResults_Struct *emu = (BazaarSearchResults_Struct *)__emu_buffer; + + int EntryCount = in->size / sizeof(BazaarSearchResults_Struct); + + if (EntryCount == 0 || (in->size % sizeof(BazaarSearchResults_Struct)) != 0) + { + _log(NET__STRUCTS, "Wrong size on outbound %s: Got %d, expected multiple of %d", opcodes->EmuToName(in->GetOpcode()), in->size, sizeof(BazaarSearchResults_Struct)); + delete in; + return; + } + + in->size = EntryCount * sizeof(structs::BazaarSearchResults_Struct); + in->pBuffer = new unsigned char[in->size]; + + memset(in->pBuffer, 0, in->size); + + structs::BazaarSearchResults_Struct *eq = (structs::BazaarSearchResults_Struct *)in->pBuffer; + + for (int i = 0; i < EntryCount; ++i, ++emu, ++eq) + { + OUT(Beginning.Action); + OUT(SellerID); + memcpy(eq->SellerName, emu->SellerName, sizeof(eq->SellerName)); + OUT(NumItems); + OUT(ItemID); + OUT(SerialNumber); + memcpy(eq->ItemName, emu->ItemName, sizeof(eq->ItemName)); + OUT(Cost); + OUT(ItemStat); + } + + delete[] __emu_buffer; + dest->FastQueuePacket(&in, ack_req); + } + + ENCODE(OP_BeginCast) + { + SETUP_DIRECT_ENCODE(BeginCast_Struct, structs::BeginCast_Struct); + + OUT(spell_id); + OUT(caster_id); + OUT(cast_time); + + FINISH_ENCODE(); + } + + ENCODE(OP_BlockedBuffs) + { + ENCODE_LENGTH_EXACT(BlockedBuffs_Struct); + SETUP_DIRECT_ENCODE(BlockedBuffs_Struct, structs::BlockedBuffs_Struct); + + for (uint32 i = 0; i < BLOCKED_BUFF_COUNT; ++i) + eq->SpellID[i] = emu->SpellID[i]; + + // -1 for the extra 10 added in RoF2. We should really be encoding for the older clients, not RoF2, but + // we can sort that out later. + + for (uint32 i = BLOCKED_BUFF_COUNT; i < structs::BLOCKED_BUFF_COUNT; ++i) + eq->SpellID[i] = -1; + + OUT(Count); + OUT(Pet); + OUT(Initialise); + OUT(Flags); + + FINISH_ENCODE(); + } + + ENCODE(OP_Buff) + { + ENCODE_LENGTH_EXACT(SpellBuffFade_Struct); + SETUP_DIRECT_ENCODE(SpellBuffFade_Struct, structs::SpellBuffFade_Struct_Live); + + OUT(entityid); + eq->unknown004 = 2; + //eq->level = 80; + //eq->effect = 0; + OUT(level); + OUT(effect); + eq->unknown007 = 0; + eq->unknown008 = 1.0f; + OUT(spellid); + OUT(duration); + eq->playerId = 0x7cde; + OUT(slotid); + OUT(num_hits); + if (emu->bufffade == 1) + eq->bufffade = 1; + else + eq->bufffade = 2; + + // Bit of a hack. OP_Buff appears to add/remove the buff while OP_BuffCreate adds/removes the actual buff icon + EQApplicationPacket *outapp = nullptr; + if (eq->bufffade == 1) + { + outapp = new EQApplicationPacket(OP_BuffCreate, 29); + outapp->WriteUInt32(emu->entityid); + outapp->WriteUInt32(0x0271); // Unk + outapp->WriteUInt8(0); // Type of OP_BuffCreate packet ? + outapp->WriteUInt16(1); // 1 buff in this packet + outapp->WriteUInt32(emu->slotid); + outapp->WriteUInt32(0xffffffff); // SpellID (0xffff to remove) + outapp->WriteUInt32(0); // Duration + outapp->WriteUInt32(0); // ? + outapp->WriteUInt8(0); // Caster name + outapp->WriteUInt8(0); // Terminating byte + } + FINISH_ENCODE(); + + if (outapp) + dest->FastQueuePacket(&outapp); // Send the OP_BuffCreate to remove the buff + } + + ENCODE(OP_BuffCreate) + { + SETUP_VAR_ENCODE(BuffIcon_Struct); + + uint32 sz = 12 + (17 * emu->count); + __packet->size = sz; + __packet->pBuffer = new unsigned char[sz]; + memset(__packet->pBuffer, 0, sz); + + __packet->WriteUInt32(emu->entity_id); + __packet->WriteUInt32(0); // PlayerID ? + __packet->WriteUInt8(emu->all_buffs); // 1 indicates all buffs on the player (0 to add or remove a single buff) + __packet->WriteUInt16(emu->count); + + for (uint16 i = 0; i < emu->count; ++i) + { + uint16 buffslot = emu->entries[i].buff_slot; + // Not sure if this is needs amending for RoF2 yet. + if (emu->entries[i].buff_slot >= 25) + { + buffslot += 17; + } + + __packet->WriteUInt32(buffslot); + __packet->WriteUInt32(emu->entries[i].spell_id); + __packet->WriteUInt32(emu->entries[i].tics_remaining); + __packet->WriteUInt32(emu->entries[i].num_hits); // Unknown + __packet->WriteString(""); + } + __packet->WriteUInt8(!emu->all_buffs); // Unknown + + FINISH_ENCODE(); + } + + ENCODE(OP_CancelTrade) + { + ENCODE_LENGTH_EXACT(CancelTrade_Struct); + SETUP_DIRECT_ENCODE(CancelTrade_Struct, structs::CancelTrade_Struct); + + OUT(fromid); + OUT(action); + + FINISH_ENCODE(); + } + + ENCODE(OP_CastSpell) + { + ENCODE_LENGTH_EXACT(CastSpell_Struct); + SETUP_DIRECT_ENCODE(CastSpell_Struct, structs::CastSpell_Struct); + + if (emu->slot == 10) + eq->slot = 13; + else + OUT(slot); + + OUT(spell_id); + eq->inventoryslot = ServerToRoF2Slot(emu->inventoryslot); + //OUT(inventoryslot); + OUT(target_id); + + FINISH_ENCODE(); + } + + ENCODE(OP_ChannelMessage) + { + EQApplicationPacket *in = *p; + *p = nullptr; + + ChannelMessage_Struct *emu = (ChannelMessage_Struct *)in->pBuffer; + + unsigned char *__emu_buffer = in->pBuffer; + + in->size = strlen(emu->sender) + 1 + strlen(emu->targetname) + 1 + strlen(emu->message) + 1 + 36; + in->pBuffer = new unsigned char[in->size]; + + char *OutBuffer = (char *)in->pBuffer; + + VARSTRUCT_ENCODE_STRING(OutBuffer, emu->sender); + VARSTRUCT_ENCODE_STRING(OutBuffer, emu->targetname); + VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, 0); // Unknown + VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, emu->language); + VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, emu->chan_num); + VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, 0); // Unknown + VARSTRUCT_ENCODE_TYPE(uint8, OutBuffer, 0); // Unknown + VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, emu->skill_in_language); + VARSTRUCT_ENCODE_STRING(OutBuffer, emu->message); + + VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, 0); // Unknown + VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, 0); // Unknown + VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, 0); // Unknown + VARSTRUCT_ENCODE_TYPE(uint16, OutBuffer, 0); // Unknown + VARSTRUCT_ENCODE_TYPE(uint8, OutBuffer, 0); // Unknown + + delete[] __emu_buffer; + dest->FastQueuePacket(&in, ack_req); + } + + ENCODE(OP_CharInventory) + { + //consume the packet + EQApplicationPacket *in = *p; + + *p = nullptr; + + if (in->size == 0) { + + in->size = 4; + in->pBuffer = new uchar[in->size]; + + *((uint32 *)in->pBuffer) = 0; + + dest->FastQueuePacket(&in, ack_req); + return; + } + + //store away the emu struct + unsigned char *__emu_buffer = in->pBuffer; + + int ItemCount = in->size / sizeof(InternalSerializedItem_Struct); + + if (ItemCount == 0 || (in->size % sizeof(InternalSerializedItem_Struct)) != 0) { + + _log(NET__STRUCTS, "Wrong size on outbound %s: Got %d, expected multiple of %d", + opcodes->EmuToName(in->GetOpcode()), in->size, sizeof(InternalSerializedItem_Struct)); + + delete in; + + return; + } + + InternalSerializedItem_Struct *eq = (InternalSerializedItem_Struct *)in->pBuffer; + + in->pBuffer = new uchar[4]; + *(uint32 *)in->pBuffer = ItemCount; + in->size = 4; + + for (int r = 0; r < ItemCount; r++, eq++) { + + uint32 Length = 0; + + char* Serialized = SerializeItem((const ItemInst*)eq->inst, eq->slot_id, &Length, 0); + + if (Serialized) { + + uchar *OldBuffer = in->pBuffer; + in->pBuffer = new uchar[in->size + Length]; + memcpy(in->pBuffer, OldBuffer, in->size); + + safe_delete_array(OldBuffer); + + memcpy(in->pBuffer + in->size, Serialized, Length); + in->size += Length; + + safe_delete_array(Serialized); + } + else { + _log(NET__ERROR, "Serialization failed on item slot %d during OP_CharInventory. Item skipped.", eq->slot_id); + } + } + + delete[] __emu_buffer; + + //_log(NET__ERROR, "Sending inventory to client"); + //_hex(NET__ERROR, in->pBuffer, in->size); + + dest->FastQueuePacket(&in, ack_req); + } + + ENCODE(OP_ClickObjectAction) + { + ENCODE_LENGTH_EXACT(ClickObjectAction_Struct); + SETUP_DIRECT_ENCODE(ClickObjectAction_Struct, structs::ClickObjectAction_Struct); + + OUT(drop_id); + eq->unknown04 = -1; + eq->unknown08 = -1; + OUT(type); + OUT(icon); + eq->unknown16 = 0; + OUT_str(object_name); + + FINISH_ENCODE(); + } + + ENCODE(OP_ClientUpdate) + { + ENCODE_LENGTH_EXACT(PlayerPositionUpdateServer_Struct); + SETUP_DIRECT_ENCODE(PlayerPositionUpdateServer_Struct, structs::PlayerPositionUpdateServer_Struct); + + OUT(spawn_id); + OUT(x_pos); + OUT(delta_x); + OUT(delta_y); + OUT(z_pos); + OUT(delta_heading); + OUT(y_pos); + OUT(delta_z); + OUT(animation); + OUT(heading); + + FINISH_ENCODE(); + } + + ENCODE(OP_Consider) + { + ENCODE_LENGTH_EXACT(Consider_Struct); + SETUP_DIRECT_ENCODE(Consider_Struct, structs::Consider_Struct); + + OUT(playerid); + OUT(targetid); + OUT(faction); + OUT(level); + OUT(pvpcon); + + FINISH_ENCODE(); + } + + ENCODE(OP_Damage) + { + ENCODE_LENGTH_EXACT(CombatDamage_Struct); + SETUP_DIRECT_ENCODE(CombatDamage_Struct, structs::CombatDamage_Struct); + + OUT(target); + OUT(source); + OUT(type); + OUT(spellid); + OUT(damage); + eq->sequence = emu->sequence; + + FINISH_ENCODE(); + } + + ENCODE(OP_DeleteCharge) { ENCODE_FORWARD(OP_MoveItem); } + + ENCODE(OP_DeleteItem) + { + ENCODE_LENGTH_EXACT(DeleteItem_Struct); + SETUP_DIRECT_ENCODE(DeleteItem_Struct, structs::DeleteItem_Struct); + + eq->from_slot = ServerToRoF2Slot(emu->from_slot); + eq->to_slot = ServerToRoF2Slot(emu->to_slot); + OUT(number_in_stack); + + FINISH_ENCODE(); + } + + ENCODE(OP_DeleteSpawn) + { + ENCODE_LENGTH_EXACT(DeleteSpawn_Struct); + SETUP_DIRECT_ENCODE(DeleteSpawn_Struct, structs::DeleteSpawn_Struct); + + OUT(spawn_id); + eq->unknown04 = 1; // Observed + + FINISH_ENCODE(); + } + + ENCODE(OP_DisciplineUpdate) + { + ENCODE_LENGTH_EXACT(Disciplines_Struct); + SETUP_DIRECT_ENCODE(Disciplines_Struct, structs::Disciplines_Struct); + + memcpy(&eq->values, &emu->values, sizeof(Disciplines_Struct)); + + FINISH_ENCODE(); + } + + ENCODE(OP_DzCompass) + { + SETUP_VAR_ENCODE(ExpeditionCompass_Struct); + ALLOC_VAR_ENCODE(structs::ExpeditionCompass_Struct, sizeof(structs::ExpeditionInfo_Struct) + sizeof(structs::ExpeditionCompassEntry_Struct) * emu->count); + + OUT(count); + + for (uint32 i = 0; i < emu->count; ++i) + { + OUT(entries[i].x); + OUT(entries[i].y); + OUT(entries[i].z); + } + + FINISH_ENCODE(); + } + + ENCODE(OP_DzExpeditionEndsWarning) + { + ENCODE_LENGTH_EXACT(ExpeditionExpireWarning); + SETUP_DIRECT_ENCODE(ExpeditionExpireWarning, structs::ExpeditionExpireWarning); + + OUT(minutes_remaining); + + FINISH_ENCODE(); + } + + ENCODE(OP_DzExpeditionInfo) + { + ENCODE_LENGTH_EXACT(ExpeditionInfo_Struct); + SETUP_DIRECT_ENCODE(ExpeditionInfo_Struct, structs::ExpeditionInfo_Struct); + + OUT(max_players); + eq->unknown004 = 785316192; + eq->unknown008 = 435601; + strncpy(eq->expedition_name, emu->expedition_name, sizeof(eq->expedition_name)); + strncpy(eq->leader_name, emu->leader_name, sizeof(eq->leader_name)); + + FINISH_ENCODE(); + } + + ENCODE(OP_DzExpeditionList) + { + SETUP_VAR_ENCODE(ExpeditionLockoutList_Struct); + + std::stringstream ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary); + uint32 client_id = 0; + uint8 null_term = 0; + ss.write((const char*)&client_id, sizeof(uint32)); + ss.write((const char*)&emu->count, sizeof(uint32)); + for (uint32 i = 0; i < emu->count; ++i) + { + ss.write(emu->entries[i].expedition, strlen(emu->entries[i].expedition)); + ss.write((const char*)&null_term, sizeof(char)); + ss.write((const char*)&emu->entries[i].time_left, sizeof(uint32)); + ss.write((const char*)&client_id, sizeof(uint32)); + ss.write(emu->entries[i].expedition_event, strlen(emu->entries[i].expedition_event)); + ss.write((const char*)&null_term, sizeof(char)); + } + + __packet->size = ss.str().length(); + __packet->pBuffer = new unsigned char[__packet->size]; + memcpy(__packet->pBuffer, ss.str().c_str(), __packet->size); + + FINISH_ENCODE(); + } + + ENCODE(OP_DzJoinExpeditionConfirm) + { + ENCODE_LENGTH_EXACT(ExpeditionJoinPrompt_Struct); + SETUP_DIRECT_ENCODE(ExpeditionJoinPrompt_Struct, structs::ExpeditionJoinPrompt_Struct); + + strncpy(eq->expedition_name, emu->expedition_name, sizeof(eq->expedition_name)); + strncpy(eq->player_name, emu->player_name, sizeof(eq->player_name)); + + FINISH_ENCODE(); + } + + ENCODE(OP_DzLeaderStatus) + { + SETUP_VAR_ENCODE(ExpeditionLeaderSet_Struct); + + std::stringstream ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary); + uint32 client_id = 0; + uint8 null_term = 0; + + ss.write((const char*)&client_id, sizeof(uint32)); + ss.write((const char*)&client_id, sizeof(uint32)); + ss.write(emu->leader_name, strlen(emu->leader_name)); + ss.write((const char*)&null_term, sizeof(char)); + ss.write((const char*)&client_id, sizeof(uint32)); + ss.write((const char*)&client_id, sizeof(uint32));//0xffffffff + ss.write((const char*)&client_id, sizeof(uint32)); + ss.write((const char*)&client_id, sizeof(uint32)); + ss.write((const char*)&client_id, sizeof(uint32)); + ss.write((const char*)&client_id, sizeof(uint32)); + ss.write((const char*)&client_id, sizeof(uint32)); + ss.write((const char*)&client_id, sizeof(uint32)); + ss.write((const char*)&client_id, sizeof(uint32)); + ss.write((const char*)&client_id, sizeof(uint32)); + ss.write((const char*)&client_id, sizeof(uint32)); + ss.write((const char*)&client_id, sizeof(uint32));//1 + ss.write((const char*)&client_id, sizeof(uint32)); + + __packet->size = ss.str().length(); + __packet->pBuffer = new unsigned char[__packet->size]; + memcpy(__packet->pBuffer, ss.str().c_str(), __packet->size); + + FINISH_ENCODE(); + } + + ENCODE(OP_DzMemberList) + { + SETUP_VAR_ENCODE(ExpeditionMemberList_Struct); + + std::stringstream ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary); + + uint32 client_id = 0; + uint8 null_term = 0; + ss.write((const char*)&client_id, sizeof(uint32)); + ss.write((const char*)&emu->count, sizeof(uint32)); + for (uint32 i = 0; i < emu->count; ++i) + { + ss.write(emu->entries[i].name, strlen(emu->entries[i].name)); + ss.write((const char*)&null_term, sizeof(char)); + ss.write((const char*)&emu->entries[i].status, sizeof(char)); + } + + __packet->size = ss.str().length(); + __packet->pBuffer = new unsigned char[__packet->size]; + memcpy(__packet->pBuffer, ss.str().c_str(), __packet->size); + + FINISH_ENCODE(); + } + + ENCODE(OP_ExpansionInfo) + { + ENCODE_LENGTH_EXACT(ExpansionInfo_Struct); + SETUP_DIRECT_ENCODE(ExpansionInfo_Struct, structs::ExpansionInfo_Struct); + + OUT(Expansions); + + FINISH_ENCODE(); + } + + ENCODE(OP_GMLastName) + { + ENCODE_LENGTH_EXACT(GMLastName_Struct); + SETUP_DIRECT_ENCODE(GMLastName_Struct, structs::GMLastName_Struct); + + OUT_str(name); + OUT_str(gmname); + OUT_str(lastname); + for (int i = 0; i<4; i++) + { + eq->unknown[i] = emu->unknown[i]; + } + + FINISH_ENCODE(); + } + + ENCODE(OP_GMTrainSkillConfirm) + { + ENCODE_LENGTH_EXACT(GMTrainSkillConfirm_Struct); + SETUP_DIRECT_ENCODE(GMTrainSkillConfirm_Struct, structs::GMTrainSkillConfirm_Struct); + + OUT(SkillID); + OUT(Cost); + OUT(NewSkill); + OUT_str(TrainerName); + + FINISH_ENCODE(); + } + + ENCODE(OP_GroundSpawn) + { + // We are not encoding the spawn_id field here, but it doesn't appear to matter. + // + EQApplicationPacket *in = *p; + *p = nullptr; + + Object_Struct *emu = (Object_Struct *)in->pBuffer; + + unsigned char *__emu_buffer = in->pBuffer; + + in->size = strlen(emu->object_name) + sizeof(Object_Struct)-1; + in->pBuffer = new unsigned char[in->size]; + + char *OutBuffer = (char *)in->pBuffer; + + VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, emu->drop_id); + VARSTRUCT_ENCODE_STRING(OutBuffer, emu->object_name); + VARSTRUCT_ENCODE_TYPE(uint16, OutBuffer, emu->zone_id); + VARSTRUCT_ENCODE_TYPE(uint16, OutBuffer, emu->zone_instance); + VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, emu->drop_id); // Some unique id + VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, 0); // Same for all objects in the zone + VARSTRUCT_ENCODE_TYPE(float, OutBuffer, emu->heading); + VARSTRUCT_ENCODE_TYPE(float, OutBuffer, 0); // Normally 0, but seen (float)255.0 as well + VARSTRUCT_ENCODE_TYPE(float, OutBuffer, 0); // Unknown + VARSTRUCT_ENCODE_TYPE(float, OutBuffer, 1); // Need to add emu->size to struct + VARSTRUCT_ENCODE_TYPE(float, OutBuffer, emu->y); + VARSTRUCT_ENCODE_TYPE(float, OutBuffer, emu->x); + VARSTRUCT_ENCODE_TYPE(float, OutBuffer, emu->z); + VARSTRUCT_ENCODE_TYPE(int32, OutBuffer, emu->object_type); // Unknown, observed 0x00000014 + + delete[] __emu_buffer; + dest->FastQueuePacket(&in, ack_req); + } + + ENCODE(OP_GroupCancelInvite) + { + ENCODE_LENGTH_EXACT(GroupCancel_Struct); + SETUP_DIRECT_ENCODE(GroupCancel_Struct, structs::GroupCancel_Struct); + + memcpy(eq->name1, emu->name1, sizeof(eq->name1)); + memcpy(eq->name2, emu->name2, sizeof(eq->name2)); + OUT(toggle); + + FINISH_ENCODE(); + } + + ENCODE(OP_GroupFollow) + { + ENCODE_LENGTH_EXACT(GroupGeneric_Struct); + SETUP_DIRECT_ENCODE(GroupGeneric_Struct, structs::GroupFollow_Struct); + + memcpy(eq->name1, emu->name1, sizeof(eq->name1)); + memcpy(eq->name2, emu->name2, sizeof(eq->name2)); + + FINISH_ENCODE(); + } + + ENCODE(OP_GroupFollow2) + { + ENCODE_LENGTH_EXACT(GroupGeneric_Struct); + SETUP_DIRECT_ENCODE(GroupGeneric_Struct, structs::GroupFollow_Struct); + + memcpy(eq->name1, emu->name1, sizeof(eq->name1)); + memcpy(eq->name2, emu->name2, sizeof(eq->name2)); + + FINISH_ENCODE(); + } + + ENCODE(OP_GroupInvite) + { + ENCODE_LENGTH_EXACT(GroupGeneric_Struct); + SETUP_DIRECT_ENCODE(GroupGeneric_Struct, structs::GroupInvite_Struct); + + memcpy(eq->invitee_name, emu->name1, sizeof(eq->invitee_name)); + memcpy(eq->inviter_name, emu->name2, sizeof(eq->inviter_name)); + + FINISH_ENCODE(); + } + + ENCODE(OP_GroupUpdate) + { + //_log(NET__ERROR, "OP_GroupUpdate"); + EQApplicationPacket *in = *p; + GroupJoin_Struct *gjs = (GroupJoin_Struct*)in->pBuffer; + + //_log(NET__ERROR, "Received outgoing OP_GroupUpdate with action code %i", gjs->action); + if ((gjs->action == groupActLeave) || (gjs->action == groupActDisband)) + { + if ((gjs->action == groupActDisband) || !strcmp(gjs->yourname, gjs->membername)) + { + //_log(NET__ERROR, "Group Leave, yourname = %s, membername = %s", gjs->yourname, gjs->membername); + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_GroupDisbandYou, sizeof(structs::GroupGeneric_Struct)); + + structs::GroupGeneric_Struct *ggs = (structs::GroupGeneric_Struct*)outapp->pBuffer; + memcpy(ggs->name1, gjs->yourname, sizeof(ggs->name1)); + memcpy(ggs->name2, gjs->membername, sizeof(ggs->name1)); + dest->FastQueuePacket(&outapp); + + // Make an empty GLAA packet to clear out their useable GLAAs + // + outapp = new EQApplicationPacket(OP_GroupLeadershipAAUpdate, sizeof(GroupLeadershipAAUpdate_Struct)); + + dest->FastQueuePacket(&outapp); + delete in; + return; + } + //if(gjs->action == groupActLeave) + // _log(NET__ERROR, "Group Leave, yourname = %s, membername = %s", gjs->yourname, gjs->membername); + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_GroupDisbandOther, sizeof(structs::GroupGeneric_Struct)); + + structs::GroupGeneric_Struct *ggs = (structs::GroupGeneric_Struct*)outapp->pBuffer; + memcpy(ggs->name1, gjs->yourname, sizeof(ggs->name1)); + memcpy(ggs->name2, gjs->membername, sizeof(ggs->name2)); + //_hex(NET__ERROR, outapp->pBuffer, outapp->size); + dest->FastQueuePacket(&outapp); + + delete in; + return; + } + + if (in->size == sizeof(GroupUpdate2_Struct)) + { + // Group Update2 + //_log(NET__ERROR, "Struct is GroupUpdate2"); + + unsigned char *__emu_buffer = in->pBuffer; + GroupUpdate2_Struct *gu2 = (GroupUpdate2_Struct*)__emu_buffer; + + //_log(NET__ERROR, "Yourname is %s", gu2->yourname); + + int MemberCount = 1; + int PacketLength = 8 + strlen(gu2->leadersname) + 1 + 22 + strlen(gu2->yourname) + 1; + + for (int i = 0; i < 5; ++i) + { + //_log(NET__ERROR, "Membername[%i] is %s", i, gu2->membername[i]); + if (gu2->membername[i][0] != '\0') + { + PacketLength += (22 + strlen(gu2->membername[i]) + 1); + ++MemberCount; + } + } + + //_log(NET__ERROR, "Leadername is %s", gu2->leadersname); + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_GroupUpdateB, PacketLength); + + char *Buffer = (char *)outapp->pBuffer; + + // Header + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // Think this should be SpawnID, but it doesn't seem to matter + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, MemberCount); + VARSTRUCT_ENCODE_STRING(Buffer, gu2->leadersname); + + // Leader + // + + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); + VARSTRUCT_ENCODE_STRING(Buffer, gu2->yourname); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); + //VARSTRUCT_ENCODE_STRING(Buffer, ""); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // This is a string + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0x46); // Observed 0x41 and 0x46 here + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); + VARSTRUCT_ENCODE_TYPE(uint16, Buffer, 0); + + int MemberNumber = 1; + + for (int i = 0; i < 5; ++i) + { + if (gu2->membername[i][0] == '\0') + continue; + + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, MemberNumber++); + VARSTRUCT_ENCODE_STRING(Buffer, gu2->membername[i]); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); + //VARSTRUCT_ENCODE_STRING(Buffer, ""); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // This is a string + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0x41); // Observed 0x41 and 0x46 here + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // Low byte is Main Assist Flag + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); + VARSTRUCT_ENCODE_TYPE(uint16, Buffer, 0); + } + + //_hex(NET__ERROR, outapp->pBuffer, outapp->size); + dest->FastQueuePacket(&outapp); + + outapp = new EQApplicationPacket(OP_GroupLeadershipAAUpdate, sizeof(GroupLeadershipAAUpdate_Struct)); + + GroupLeadershipAAUpdate_Struct *GLAAus = (GroupLeadershipAAUpdate_Struct*)outapp->pBuffer; + + GLAAus->NPCMarkerID = gu2->NPCMarkerID; + memcpy(&GLAAus->LeaderAAs, &gu2->leader_aas, sizeof(GLAAus->LeaderAAs)); + + dest->FastQueuePacket(&outapp); + delete in; + + return; + + } + //_log(NET__ERROR, "Generic GroupUpdate, yourname = %s, membername = %s", gjs->yourname, gjs->membername); + ENCODE_LENGTH_EXACT(GroupJoin_Struct); + SETUP_DIRECT_ENCODE(GroupJoin_Struct, structs::GroupJoin_Struct); + + memcpy(eq->membername, emu->membername, sizeof(eq->membername)); + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_GroupLeadershipAAUpdate, sizeof(GroupLeadershipAAUpdate_Struct)); + GroupLeadershipAAUpdate_Struct *GLAAus = (GroupLeadershipAAUpdate_Struct*)outapp->pBuffer; + + GLAAus->NPCMarkerID = emu->NPCMarkerID; + + memcpy(&GLAAus->LeaderAAs, &emu->leader_aas, sizeof(GLAAus->LeaderAAs)); + //_hex(NET__ERROR, __packet->pBuffer, __packet->size); + + FINISH_ENCODE(); + + dest->FastQueuePacket(&outapp); + } + + ENCODE(OP_GuildMemberList) + { + //consume the packet + EQApplicationPacket *in = *p; + *p = nullptr; + + //store away the emu struct + unsigned char *__emu_buffer = in->pBuffer; + Internal_GuildMembers_Struct *emu = (Internal_GuildMembers_Struct *)in->pBuffer; + + //make a new EQ buffer. + uint32 pnl = strlen(emu->player_name); + uint32 length = sizeof(structs::GuildMembers_Struct) + pnl + + emu->count*sizeof(structs::GuildMemberEntry_Struct) + + emu->name_length + emu->note_length; + in->pBuffer = new uint8[length]; + in->size = length; + //no memset since we fill every byte. + + uint8 *buffer; + buffer = in->pBuffer; + + //easier way to setup GuildMembers_Struct + //set prefix name + strcpy((char *)buffer, emu->player_name); + buffer += pnl; + *buffer = '\0'; + buffer++; + + // Guild ID + buffer += sizeof(uint32); + + //add member count. + *((uint32 *)buffer) = htonl(emu->count); + buffer += sizeof(uint32); + + if (emu->count > 0) { + Internal_GuildMemberEntry_Struct *emu_e = emu->member; + const char *emu_name = (const char *)(__emu_buffer + + sizeof(Internal_GuildMembers_Struct)+ //skip header + emu->count * sizeof(Internal_GuildMemberEntry_Struct) //skip static length member data + ); + const char *emu_note = (emu_name + + emu->name_length + //skip name contents + emu->count //skip string terminators + ); + + structs::GuildMemberEntry_Struct *e = (structs::GuildMemberEntry_Struct *) buffer; + + uint32 r; + for (r = 0; r < emu->count; r++, emu_e++) { + + //the order we set things here must match the struct + + //nice helper macro +#define SlideStructString(field, str) \ + { \ + int sl = strlen(str); \ + memcpy(e->field, str, sl+1); \ + e = (structs::GuildMemberEntry_Struct *) ( ((uint8 *)e) + sl ); \ + str += sl + 1; \ + } +#define PutFieldN(field) e->field = htonl(emu_e->field) + + SlideStructString(name, emu_name); + PutFieldN(level); + PutFieldN(banker); + PutFieldN(class_); + + /* Translate older ranks to new values */ + switch (emu_e->rank) { + case 0: { e->rank = htonl(5); break; } // GUILD_MEMBER 0 + case 1: { e->rank = htonl(3); break; } // GUILD_OFFICER 1 + case 2: { e->rank = htonl(1); break; } // GUILD_LEADER 2 + default: { e->rank = htonl(emu_e->rank); break; } // GUILD_NONE + } + + PutFieldN(time_last_on); + PutFieldN(tribute_enable); + e->unknown01 = 0; + PutFieldN(total_tribute); + PutFieldN(last_tribute); + e->unknown_one = htonl(1); + SlideStructString(public_note, emu_note); + e->zoneinstance = 0; + e->zone_id = htons(emu_e->zone_id); + e->unknown_one2 = htonl(1); + e->unknown04 = 0; + +#undef SlideStructString +#undef PutFieldN + + e++; + } + } + + delete[] __emu_buffer; + dest->FastQueuePacket(&in, ack_req); + } + + ENCODE(OP_GuildMemberUpdate) + { + SETUP_DIRECT_ENCODE(GuildMemberUpdate_Struct, structs::GuildMemberUpdate_Struct); + + OUT(GuildID); + memcpy(eq->MemberName, emu->MemberName, sizeof(eq->MemberName)); + OUT(ZoneID); + OUT(InstanceID); + OUT(LastSeen); + eq->Unknown76 = 0; + + FINISH_ENCODE(); + } + + ENCODE(OP_GuildsList) + { + EQApplicationPacket *in = *p; + *p = nullptr; + + uint32 NumberOfGuilds = in->size / 64; + uint32 PacketSize = 68; // 64 x 0x00 + a uint32 that I am guessing is the highest guild ID in use. + + unsigned char *__emu_buffer = in->pBuffer; + + char *InBuffer = (char *)__emu_buffer; + + uint32 HighestGuildID = 0; + + for (unsigned int i = 0; i < NumberOfGuilds; ++i) + { + if (InBuffer[0]) + { + PacketSize += (5 + strlen(InBuffer)); + HighestGuildID = i - 1; + } + InBuffer += 64; + } + + PacketSize++; // Appears to be an extra 0x00 at the very end. + + in->size = PacketSize; + in->pBuffer = new unsigned char[in->size]; + + InBuffer = (char *)__emu_buffer; + + char *OutBuffer = (char *)in->pBuffer; + + // Init the first 64 bytes to zero, as per live. + // + memset(OutBuffer, 0, 64); + + OutBuffer += 64; + + VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, HighestGuildID); + + for (unsigned int i = 0; i < NumberOfGuilds; ++i) + { + if (InBuffer[0]) + { + VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, i - 1); + VARSTRUCT_ENCODE_STRING(OutBuffer, InBuffer); + } + InBuffer += 64; + } + + VARSTRUCT_ENCODE_TYPE(uint8, OutBuffer, 0x00); + + delete[] __emu_buffer; + dest->FastQueuePacket(&in, ack_req); + } + + ENCODE(OP_HPUpdate) + { + SETUP_DIRECT_ENCODE(SpawnHPUpdate_Struct, structs::SpawnHPUpdate_Struct); + + OUT(spawn_id); + OUT(cur_hp); + OUT(max_hp); + + FINISH_ENCODE(); + } + + ENCODE(OP_Illusion) + { + ENCODE_LENGTH_EXACT(Illusion_Struct); + SETUP_DIRECT_ENCODE(Illusion_Struct, structs::Illusion_Struct); + + OUT(spawnid); + OUT_str(charname); + OUT(race); + OUT(unknown006[0]); + OUT(unknown006[1]); + OUT(gender); + OUT(texture); + OUT(helmtexture); + OUT(face); + OUT(hairstyle); + OUT(haircolor); + OUT(beard); + OUT(beardcolor); + OUT(size); + OUT(drakkin_heritage); + OUT(drakkin_tattoo); + OUT(drakkin_details); + eq->unknown316 = -1; // Observed + + FINISH_ENCODE(); + } + + /*ENCODE(OP_InspectAnswer) + { + ENCODE_LENGTH_EXACT(InspectResponse_Struct); + SETUP_DIRECT_ENCODE(InspectResponse_Struct, structs::InspectResponse_Struct); + + OUT(TargetID); + OUT(playerid); + + int r; + for (r = 0; r < 21; r++) { + strn0cpy(eq->itemnames[r], emu->itemnames[r], sizeof(eq->itemnames[r])); + } + // Swap last 2 slots for Arrow and Power Source + strn0cpy(eq->itemnames[21], emu->itemnames[22], sizeof(eq->itemnames[21])); + strn0cpy(eq->unknown_zero, emu->itemnames[21], sizeof(eq->unknown_zero)); + + int k; + for (k = 0; k < 21; k++) { + OUT(itemicons[k]); + } + // Swap last 2 slots for Arrow and Power Source + eq->itemicons[21] = emu->itemicons[22]; + eq->unknown_zero2 = emu->itemicons[21]; + strn0cpy(eq->text, emu->text, sizeof(eq->text)); + + FINISH_ENCODE(); + }*/ + + ENCODE(OP_InspectBuffs) + { + ENCODE_LENGTH_EXACT(InspectBuffs_Struct); + SETUP_DIRECT_ENCODE(InspectBuffs_Struct, structs::InspectBuffs_Struct); + + // we go over the internal 25 instead of the packet's since no entry is 0, which it will be already + for (int i = 0; i < BUFF_COUNT; i++) { + OUT(spell_id[i]); + OUT(tics_remaining[i]); + } + + FINISH_ENCODE(); + } + + ENCODE(OP_InspectRequest) + { + ENCODE_LENGTH_EXACT(Inspect_Struct); + SETUP_DIRECT_ENCODE(Inspect_Struct, structs::Inspect_Struct); + + OUT(TargetID); + OUT(PlayerID); + + FINISH_ENCODE(); + } + + ENCODE(OP_InterruptCast) + { + ENCODE_LENGTH_EXACT(InterruptCast_Struct); + SETUP_DIRECT_ENCODE(InterruptCast_Struct, structs::InterruptCast_Struct); + + OUT(spawnid); + OUT(messageid); + + FINISH_ENCODE(); + } + + ENCODE(OP_ItemLinkResponse) { ENCODE_FORWARD(OP_ItemPacket); } + + ENCODE(OP_ItemPacket) + { + //consume the packet + EQApplicationPacket *in = *p; + *p = nullptr; + + unsigned char *__emu_buffer = in->pBuffer; + ItemPacket_Struct *old_item_pkt = (ItemPacket_Struct *)__emu_buffer; + InternalSerializedItem_Struct *int_struct = (InternalSerializedItem_Struct *)(old_item_pkt->SerializedItem); + + uint32 length; + char *serialized = SerializeItem((ItemInst *)int_struct->inst, int_struct->slot_id, &length, 0); + + if (!serialized) { + _log(NET__STRUCTS, "Serialization failed on item slot %d.", int_struct->slot_id); + delete in; + return; + } + in->size = length + 4; + in->pBuffer = new unsigned char[in->size]; + ItemPacket_Struct *new_item_pkt = (ItemPacket_Struct *)in->pBuffer; + new_item_pkt->PacketType = old_item_pkt->PacketType; + memcpy(new_item_pkt->SerializedItem, serialized, length); + + delete[] __emu_buffer; + safe_delete_array(serialized); + dest->FastQueuePacket(&in, ack_req); + } + + ENCODE(OP_ItemVerifyReply) + { + ENCODE_LENGTH_EXACT(ItemVerifyReply_Struct); + SETUP_DIRECT_ENCODE(ItemVerifyReply_Struct, structs::ItemVerifyReply_Struct); + + eq->slot = ServerToRoF2Slot(emu->slot); + OUT(spell); + OUT(target); + + FINISH_ENCODE(); + } + + ENCODE(OP_LeadershipExpUpdate) + { + SETUP_DIRECT_ENCODE(LeadershipExpUpdate_Struct, structs::LeadershipExpUpdate_Struct); + + OUT(group_leadership_exp); + OUT(group_leadership_points); + OUT(raid_leadership_exp); + OUT(raid_leadership_points); + + FINISH_ENCODE(); + } + + ENCODE(OP_LogServer) + { + ENCODE_LENGTH_EXACT(LogServer_Struct); + SETUP_DIRECT_ENCODE(LogServer_Struct, structs::LogServer_Struct); + + strncpy(eq->worldshortname, emu->worldshortname, sizeof(eq->worldshortname)); + + //OUT(enablevoicemacros); // These two are lost, but must be one of the 1s in unknown[249] + //OUT(enablemail); + OUT(enable_pvp); + OUT(enable_FV); + + eq->unknown016 = 1; + eq->unknown020[0] = 1; + + eq->unknown249[0] = 1; + eq->unknown249[1] = 1; + eq->unknown249[8] = 1; + eq->unknown249[9] = 1; + eq->unknown249[12] = 1; + eq->unknown249[14] = 1; + eq->unknown249[15] = 1; + eq->unknown249[16] = 1; + + eq->unknown276[0] = 1.0f; + eq->unknown276[1] = 1.0f; + eq->unknown276[6] = 1.0f; + + FINISH_ENCODE(); + } + + ENCODE(OP_LootItem) + { + ENCODE_LENGTH_EXACT(LootingItem_Struct); + SETUP_DIRECT_ENCODE(LootingItem_Struct, structs::LootingItem_Struct); + + OUT(lootee); + OUT(looter); + eq->slot_id = ServerToRoF2CorpseSlot(emu->slot_id); + OUT(auto_loot); + + FINISH_ENCODE(); + } + + ENCODE(OP_ManaChange) + { + ENCODE_LENGTH_EXACT(ManaChange_Struct); + SETUP_DIRECT_ENCODE(ManaChange_Struct, structs::ManaChange_Struct); + + OUT(new_mana); + OUT(stamina); + OUT(spell_id); + eq->unknown16 = -1; // Self Interrupt/Success = -1, Fizzle = 1, Other Interrupt = 2? + + FINISH_ENCODE(); + } + + ENCODE(OP_MercenaryDataResponse) + { + //consume the packet + EQApplicationPacket *in = *p; + *p = nullptr; + + //store away the emu struct + unsigned char *__emu_buffer = in->pBuffer; + MercenaryMerchantList_Struct *emu = (MercenaryMerchantList_Struct *)__emu_buffer; + + char *Buffer = (char *)in->pBuffer; + + int PacketSize = sizeof(structs::MercenaryMerchantList_Struct) - 4 + emu->MercTypeCount * 4; + PacketSize += (sizeof(structs::MercenaryListEntry_Struct) - sizeof(structs::MercenaryStance_Struct)) * emu->MercCount; + + uint32 r; + uint32 k; + for (r = 0; r < emu->MercCount; r++) + { + PacketSize += sizeof(structs::MercenaryStance_Struct) * emu->Mercs[r].StanceCount; + } + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_MercenaryDataResponse, PacketSize); + Buffer = (char *)outapp->pBuffer; + + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->MercTypeCount); + + for (r = 0; r < emu->MercTypeCount; r++) + { + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->MercGrades[r]); + } + + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->MercCount); + + for (r = 0; r < emu->MercCount; r++) + { + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->Mercs[r].MercID); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->Mercs[r].MercType); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->Mercs[r].MercSubType); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->Mercs[r].PurchaseCost); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->Mercs[r].UpkeepCost); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->Mercs[r].Status); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->Mercs[r].AltCurrencyCost); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->Mercs[r].AltCurrencyUpkeep); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->Mercs[r].AltCurrencyType); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->Mercs[r].MercUnk01); + VARSTRUCT_ENCODE_TYPE(int32, Buffer, emu->Mercs[r].TimeLeft); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->Mercs[r].MerchantSlot); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->Mercs[r].MercUnk02); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->Mercs[r].StanceCount); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->Mercs[r].MercUnk03); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->Mercs[r].MercUnk04); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // MercName + for (k = 0; k < emu->Mercs[r].StanceCount; k++) + { + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->Mercs[r].Stances[k].StanceIndex); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->Mercs[r].Stances[k].Stance); + } + } + + dest->FastQueuePacket(&outapp, ack_req); + delete in; + } + + ENCODE(OP_MercenaryDataUpdate) + { + //consume the packet + EQApplicationPacket *in = *p; + *p = nullptr; + + //store away the emu struct + unsigned char *__emu_buffer = in->pBuffer; + MercenaryDataUpdate_Struct *emu = (MercenaryDataUpdate_Struct *)__emu_buffer; + + char *Buffer = (char *)in->pBuffer; + + EQApplicationPacket *outapp; + + uint32 PacketSize = 0; + + // There are 2 different sized versions of this packet depending if a merc is hired or not + if (emu->MercStatus >= 0) + { + PacketSize += sizeof(structs::MercenaryDataUpdate_Struct) + (sizeof(structs::MercenaryData_Struct) - sizeof(structs::MercenaryStance_Struct)) * emu->MercCount; + + uint32 r; + uint32 k; + for (r = 0; r < emu->MercCount; r++) + { + PacketSize += sizeof(structs::MercenaryStance_Struct) * emu->MercData[r].StanceCount; + PacketSize += strlen(emu->MercData[r].MercName); // Null Terminator size already accounted for in the struct + } + + outapp = new EQApplicationPacket(OP_MercenaryDataUpdate, PacketSize); + Buffer = (char *)outapp->pBuffer; + + VARSTRUCT_ENCODE_TYPE(int32, Buffer, emu->MercStatus); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->MercCount); + + for (r = 0; r < emu->MercCount; r++) + { + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->MercData[r].MercID); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->MercData[r].MercType); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->MercData[r].MercSubType); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->MercData[r].PurchaseCost); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->MercData[r].UpkeepCost); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->MercData[r].Status); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->MercData[r].AltCurrencyCost); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->MercData[r].AltCurrencyUpkeep); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->MercData[r].AltCurrencyType); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->MercData[r].MercUnk01); + VARSTRUCT_ENCODE_TYPE(int32, Buffer, emu->MercData[r].TimeLeft); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->MercData[r].MerchantSlot); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->MercData[r].MercUnk02); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->MercData[r].StanceCount); + VARSTRUCT_ENCODE_TYPE(int32, Buffer, emu->MercData[r].MercUnk03); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->MercData[r].MercUnk04); + //VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // MercName + VARSTRUCT_ENCODE_STRING(Buffer, emu->MercData[r].MercName); + for (k = 0; k < emu->MercData[r].StanceCount; k++) + { + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->MercData[r].Stances[k].StanceIndex); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->MercData[r].Stances[k].Stance); + } + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 1); // MercUnk05 + } + } + else + { + PacketSize += sizeof(structs::NoMercenaryHired_Struct); + + outapp = new EQApplicationPacket(OP_MercenaryDataUpdate, PacketSize); + Buffer = (char *)outapp->pBuffer; + + VARSTRUCT_ENCODE_TYPE(int32, Buffer, emu->MercStatus); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->MercCount); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 1); + } + + dest->FastQueuePacket(&outapp, ack_req); + delete in; + } + + ENCODE(OP_MoveItem) + { + ENCODE_LENGTH_EXACT(MoveItem_Struct); + SETUP_DIRECT_ENCODE(MoveItem_Struct, structs::MoveItem_Struct); + + eq->from_slot = ServerToRoF2Slot(emu->from_slot); + eq->to_slot = ServerToRoF2Slot(emu->to_slot); + OUT(number_in_stack); + + FINISH_ENCODE(); + } + + ENCODE(OP_NewSpawn) { ENCODE_FORWARD(OP_ZoneSpawns); } + + ENCODE(OP_NewZone) + { + SETUP_DIRECT_ENCODE(NewZone_Struct, structs::NewZone_Struct); + + OUT_str(char_name); + OUT_str(zone_short_name); + OUT_str(zone_long_name); + OUT(ztype); + int r; + for (r = 0; r < 4; r++) { + OUT(fog_red[r]); + OUT(fog_green[r]); + OUT(fog_blue[r]); + OUT(fog_minclip[r]); + OUT(fog_maxclip[r]); + } + OUT(gravity); + OUT(time_type); + for (r = 0; r < 4; r++) { + OUT(rain_chance[r]); + } + for (r = 0; r < 4; r++) { + OUT(rain_duration[r]); + } + for (r = 0; r < 4; r++) { + OUT(snow_chance[r]); + } + for (r = 0; r < 4; r++) { + OUT(snow_duration[r]); + } + for (r = 0; r < 32; r++) { + eq->unknown537[r] = 0xFF; //observed + } + OUT(sky); + OUT(zone_exp_multiplier); + OUT(safe_y); + OUT(safe_x); + OUT(safe_z); + OUT(max_z); + OUT(underworld); + OUT(minclip); + OUT(maxclip); + OUT_str(zone_short_name2); + OUT(zone_id); + OUT(zone_instance); + OUT(SuspendBuffs); + + eq->FogDensity = emu->fog_density; + + /*fill in some unknowns with observed values, hopefully it will help */ + eq->unknown800 = -1; + eq->unknown844 = 600; + eq->unknown880 = 50; + eq->unknown884 = 10; + eq->unknown888 = 1; + eq->unknown889 = 0; + eq->unknown890 = 1; + eq->unknown891 = 0; + eq->unknown892 = 0; + eq->unknown893 = 0; + eq->fall_damage = 0; // 0 = Fall Damage on, 1 = Fall Damage off + eq->unknown895 = 0; + eq->unknown896 = 180; + eq->unknown900 = 180; + eq->unknown904 = 180; + eq->unknown908 = 2; + eq->unknown912 = 2; + eq->unknown932 = -1; // Set from PoK Example + eq->unknown936 = -1; // Set from PoK Example + eq->unknown944 = 1.0; // Set from PoK Example + + FINISH_ENCODE(); + } + + ENCODE(OP_OnLevelMessage) + { + ENCODE_LENGTH_EXACT(OnLevelMessage_Struct); + SETUP_DIRECT_ENCODE(OnLevelMessage_Struct, structs::OnLevelMessage_Struct); + + // This packet is variable sized now, but forcing it to the old packet size for now. + eq->Title_Count = 128; + memcpy(eq->Title, emu->Title, sizeof(eq->Title)); + eq->Text_Count = 4096; + memcpy(eq->Text, emu->Text, sizeof(eq->Text)); + OUT(Buttons); + OUT(Duration); + OUT(PopupID); + OUT(NegativeID); + // These two field names are used if Buttons == 1. We should add an interface to them via Perl. + eq->ButtonName0_Count = 25; + OUT_str(ButtonName0); + eq->ButtonName1_Count = 25; + OUT_str(ButtonName1); + + FINISH_ENCODE(); + } + + /* + ENCODE(OP_OpenNewTasksWindow) + { + AvailableTaskHeader_Struct* __emu_AvailableTaskHeader; + AvailableTaskData1_Struct* __emu_AvailableTaskData1; + AvailableTaskData2_Struct* __emu_AvailableTaskData2; + AvailableTaskTrailer_Struct* __emu_AvailableTaskTrailer; + + structs::AvailableTaskHeader_Struct* __eq_AvailableTaskHeader; + structs::AvailableTaskData1_Struct* __eq_AvailableTaskData1; + structs::AvailableTaskData2_Struct* __eq_AvailableTaskData2; + structs::AvailableTaskTrailer_Struct* __eq_AvailableTaskTrailer; + + EQApplicationPacket *in = *p; + *p = nullptr; + + unsigned char *__emu_buffer = in->pBuffer; + + __emu_AvailableTaskHeader = (AvailableTaskHeader_Struct*)__emu_buffer; + + // For each task, SoF has an extra uint32 and what appears to be space for a null terminated string. + // + in->size = in->size + (__emu_AvailableTaskHeader->TaskCount * 5); + + in->pBuffer = new unsigned char[in->size]; + + unsigned char *__eq_buffer = in->pBuffer; + + __eq_AvailableTaskHeader = (structs::AvailableTaskHeader_Struct*)__eq_buffer; + + char *__eq_ptr, *__emu_Ptr; + + // Copy Header + // + // + + __eq_AvailableTaskHeader->TaskCount = __emu_AvailableTaskHeader->TaskCount; + __eq_AvailableTaskHeader->unknown1 = __emu_AvailableTaskHeader->unknown1; + __eq_AvailableTaskHeader->TaskGiver = __emu_AvailableTaskHeader->TaskGiver; + + __emu_Ptr = (char *) __emu_AvailableTaskHeader + sizeof(AvailableTaskHeader_Struct); + __eq_ptr = (char *) __eq_AvailableTaskHeader + sizeof(structs::AvailableTaskHeader_Struct); + + for(uint32 i=0; i<__emu_AvailableTaskHeader->TaskCount; i++) { + + __emu_AvailableTaskData1 = (AvailableTaskData1_Struct*)__emu_Ptr; + __eq_AvailableTaskData1 = (structs::AvailableTaskData1_Struct*)__eq_ptr; + + __eq_AvailableTaskData1->TaskID = __emu_AvailableTaskData1->TaskID; + // This next unknown seems to affect the colour of the task title. 0x3f80000 is what I have seen + // in RoF2 packets. Changing it to 0x3f000000 makes the title red. + __eq_AvailableTaskData1->unknown1 = 0x3f800000; + __eq_AvailableTaskData1->TimeLimit = __emu_AvailableTaskData1->TimeLimit; + __eq_AvailableTaskData1->unknown2 = __emu_AvailableTaskData1->unknown2; + + __emu_Ptr += sizeof(AvailableTaskData1_Struct); + __eq_ptr += sizeof(structs::AvailableTaskData1_Struct); + + strcpy(__eq_ptr, __emu_Ptr); // Title + + __emu_Ptr += strlen(__emu_Ptr) + 1; + __eq_ptr += strlen(__eq_ptr) + 1; + + strcpy(__eq_ptr, __emu_Ptr); // Description + + __emu_Ptr += strlen(__emu_Ptr) + 1; + __eq_ptr += strlen(__eq_ptr) + 1; + + __eq_ptr[0] = 0; + __eq_ptr += strlen(__eq_ptr) + 1; + + __emu_AvailableTaskData2 = (AvailableTaskData2_Struct*)__emu_Ptr; + __eq_AvailableTaskData2 = (structs::AvailableTaskData2_Struct*)__eq_ptr; + + __eq_AvailableTaskData2->unknown1 = __emu_AvailableTaskData2->unknown1; + __eq_AvailableTaskData2->unknown2 = __emu_AvailableTaskData2->unknown2; + __eq_AvailableTaskData2->unknown3 = __emu_AvailableTaskData2->unknown3; + __eq_AvailableTaskData2->unknown4 = __emu_AvailableTaskData2->unknown4; + + __emu_Ptr += sizeof(AvailableTaskData2_Struct); + __eq_ptr += sizeof(structs::AvailableTaskData2_Struct); + + strcpy(__eq_ptr, __emu_Ptr); // Unknown string + + __emu_Ptr += strlen(__emu_Ptr) + 1; + __eq_ptr += strlen(__eq_ptr) + 1; + + strcpy(__eq_ptr, __emu_Ptr); // Unknown string + + __emu_Ptr += strlen(__emu_Ptr) + 1; + __eq_ptr += strlen(__eq_ptr) + 1; + + __emu_AvailableTaskTrailer = (AvailableTaskTrailer_Struct*)__emu_Ptr; + __eq_AvailableTaskTrailer = (structs::AvailableTaskTrailer_Struct*)__eq_ptr; + + __eq_AvailableTaskTrailer->ItemCount = __emu_AvailableTaskTrailer->ItemCount; + __eq_AvailableTaskTrailer->unknown1 = __emu_AvailableTaskTrailer->unknown1; + __eq_AvailableTaskTrailer->unknown2 = __emu_AvailableTaskTrailer->unknown2; + __eq_AvailableTaskTrailer->StartZone = __emu_AvailableTaskTrailer->StartZone; + + __emu_Ptr += sizeof(AvailableTaskTrailer_Struct); + __eq_ptr += sizeof(structs::AvailableTaskTrailer_Struct); + + strcpy(__eq_ptr, __emu_Ptr); // Unknown string + + __emu_Ptr += strlen(__emu_Ptr) + 1; + __eq_ptr += strlen(__eq_ptr) + 1; + } + + delete[] __emu_buffer; + dest->FastQueuePacket(&in, ack_req); + } + */ + + ENCODE(OP_PetBuffWindow) + { + // The format of the RoF2 packet is identical to the OP_BuffCreate packet. + + SETUP_VAR_ENCODE(PetBuff_Struct); + + uint32 sz = 12 + (17 * emu->buffcount); + __packet->size = sz; + __packet->pBuffer = new unsigned char[sz]; + memset(__packet->pBuffer, 0, sz); + + __packet->WriteUInt32(emu->petid); + __packet->WriteUInt32(0); // PlayerID ? + __packet->WriteUInt8(1); // 1 indicates all buffs on the pet (0 to add or remove a single buff) + __packet->WriteUInt16(emu->buffcount); + + for (uint16 i = 0; i < BUFF_COUNT; ++i) + { + if (emu->spellid[i]) + { + __packet->WriteUInt32(i); + __packet->WriteUInt32(emu->spellid[i]); + __packet->WriteUInt32(emu->ticsremaining[i]); + __packet->WriteUInt32(0); // Unknown + __packet->WriteString(""); + } + } + __packet->WriteUInt8(0); // Unknown + + FINISH_ENCODE(); + } + + ENCODE(OP_PlayerProfile) + { + EQApplicationPacket *in = *p; + *p = nullptr; + + unsigned char *__emu_buffer = in->pBuffer; + PlayerProfile_Struct *emu = (PlayerProfile_Struct *)__emu_buffer; + + uint32 PacketSize = 40000; // Calculate this later + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_PlayerProfile, PacketSize); + + outapp->WriteUInt32(0); // Checksum, we will update this later + outapp->WriteUInt32(0); // Checksum size, we will update this later + + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(0); // Unknown + + + outapp->WriteUInt8(emu->gender); // Gender + outapp->WriteUInt32(emu->race); // Race + outapp->WriteUInt8(emu->class_); // Class + outapp->WriteUInt8(emu->level); // Level + outapp->WriteUInt8(emu->level); // Level1 + + + outapp->WriteUInt32(5); // Bind count + + for (int r = 0; r < 5; r++) + { + outapp->WriteUInt32(emu->binds[r].zoneId); + outapp->WriteFloat(emu->binds[r].x); + outapp->WriteFloat(emu->binds[r].y); + outapp->WriteFloat(emu->binds[r].z); + outapp->WriteFloat(emu->binds[r].heading); + } + + outapp->WriteUInt32(emu->deity); + outapp->WriteUInt32(emu->intoxication); + + outapp->WriteUInt32(10); // Unknown count + + for (int r = 0; r < 10; r++) + { + outapp->WriteUInt32(0); // Unknown + } + + outapp->WriteUInt32(22); // Equipment count + + for (int r = 0; r < 9; r++) + { + outapp->WriteUInt32(emu->item_material[r]); + outapp->WriteUInt32(0); + outapp->WriteUInt32(0); + outapp->WriteUInt32(0); + outapp->WriteUInt32(0); + } + + // Write zeroes for the next 13 equipment slots + + for (int r = 0; r < 13; r++) + { + outapp->WriteUInt32(0); + outapp->WriteUInt32(0); + outapp->WriteUInt32(0); + outapp->WriteUInt32(0); + outapp->WriteUInt32(0); + } + + outapp->WriteUInt32(9); // Equipment2 count + + for (int r = 0; r < 9; r++) + { + outapp->WriteUInt32(0); + outapp->WriteUInt32(0); + outapp->WriteUInt32(0); + outapp->WriteUInt32(0); + outapp->WriteUInt32(0); + } + + outapp->WriteUInt32(9); // Tint Count + + for (int r = 0; r < 7; r++) + { + outapp->WriteUInt32(emu->item_tint[r].color); + } + // Write zeroes for extra two tint values + outapp->WriteUInt32(0); + outapp->WriteUInt32(0); + + outapp->WriteUInt32(9); // Tint2 Count + + for (int r = 0; r < 7; r++) + { + outapp->WriteUInt32(emu->item_tint[r].color); + } + // Write zeroes for extra two tint values + outapp->WriteUInt32(0); + outapp->WriteUInt32(0); + + outapp->WriteUInt8(emu->haircolor); + outapp->WriteUInt8(emu->beardcolor); + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt8(emu->eyecolor1); + outapp->WriteUInt8(emu->eyecolor2); + outapp->WriteUInt8(emu->hairstyle); + outapp->WriteUInt8(emu->beard); + outapp->WriteUInt8(emu->face); + + // Think there should be an extra byte before the drakkin stuff (referred to as oldface in client) + // Then one of the five bytes following the drakkin stuff needs removing. + + outapp->WriteUInt32(emu->drakkin_heritage); + outapp->WriteUInt32(emu->drakkin_tattoo); + outapp->WriteUInt32(emu->drakkin_details); + + outapp->WriteUInt8(0); // Unknown + outapp->WriteUInt8(0); // Unknown + outapp->WriteUInt8(0); // Unknown + outapp->WriteUInt8(0); // Unknown + outapp->WriteUInt8(0); // Unknown + + outapp->WriteFloat(5.0f); // Height ? + + outapp->WriteFloat(3.0f); // Unknown + outapp->WriteFloat(2.5f); // Unknown + outapp->WriteFloat(5.5f); // Unknown + + outapp->WriteUInt32(0); // Primary ? + outapp->WriteUInt32(0); // Secondary ? + + outapp->WriteUInt32(emu->points); // Unspent skill points + outapp->WriteUInt32(emu->mana); + outapp->WriteUInt32(emu->cur_hp); + + outapp->WriteUInt32(emu->STR); + outapp->WriteUInt32(emu->STA); + outapp->WriteUInt32(emu->CHA); + outapp->WriteUInt32(emu->DEX); + outapp->WriteUInt32(emu->INT); + outapp->WriteUInt32(emu->AGI); + outapp->WriteUInt32(emu->WIS); + + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(0); // Unknown + + outapp->WriteUInt32(300); // AA Count + + for (uint32 r = 0; r < MAX_PP_AA_ARRAY; r++) + { + outapp->WriteUInt32(emu->aa_array[r].AA); + outapp->WriteUInt32(emu->aa_array[r].value); + outapp->WriteUInt32(0); + } + + // Fill the other 60 AAs with zeroes + + for (uint32 r = 0; r < structs::MAX_PP_AA_ARRAY - MAX_PP_AA_ARRAY; r++) + { + outapp->WriteUInt32(0); + outapp->WriteUInt32(0); + outapp->WriteUInt32(0); + } + + outapp->WriteUInt32(structs::MAX_PP_SKILL); + + for (uint32 r = 0; r < structs::MAX_PP_SKILL; r++) + { + outapp->WriteUInt32(emu->skills[r]); + } + + // deprecated + // Write zeroes for the rest of the skills + /* + for(uint32 r = 0; r < structs::MAX_PP_SKILL - MAX_PP_SKILL; r++) + { + outapp->WriteUInt32(emu->skills[r]); + } + */ + + outapp->WriteUInt32(25); // Unknown count + + for (uint32 r = 0; r < 25; r++) + { + outapp->WriteUInt32(0); // Unknown + } + + outapp->WriteUInt32(structs::MAX_PP_DISCIPLINES); // Discipline count + + for (uint32 r = 0; r < MAX_PP_DISCIPLINES; r++) + { + outapp->WriteUInt32(emu->disciplines.values[r]); + } + + // Write zeroes for the rest of the disciplines + for (uint32 r = 0; r < structs::MAX_PP_DISCIPLINES - MAX_PP_DISCIPLINES; r++) + { + outapp->WriteUInt32(0); + } + + outapp->WriteUInt32(20); // Timestamp count + + for (uint32 r = 0; r < 20; r++) + { + outapp->WriteUInt32(0); + } + + outapp->WriteUInt32(MAX_RECAST_TYPES); // Timestamp count + + for (uint32 r = 0; r < MAX_RECAST_TYPES; r++) + { + outapp->WriteUInt32(emu->recastTimers[r]); + } + + outapp->WriteUInt32(100); // Timestamp2 count + + for (uint32 r = 0; r < 100; r++) + { + outapp->WriteUInt32(0); + } + + outapp->WriteUInt32(structs::MAX_PP_SPELLBOOK); // Spellbook slots + + for (uint32 r = 0; r < MAX_PP_SPELLBOOK; r++) + { + outapp->WriteUInt32(emu->spell_book[r]); + } + // zeroes for the rest of the spellbook slots + for (uint32 r = 0; r < structs::MAX_PP_SPELLBOOK - MAX_PP_SPELLBOOK; r++) + { + outapp->WriteUInt32(0xFFFFFFFFU); + } + + outapp->WriteUInt32(structs::MAX_PP_MEMSPELL); // Memorised spell slots + + for (uint32 r = 0; r < MAX_PP_MEMSPELL; r++) + { + outapp->WriteUInt32(emu->mem_spells[r]); + } + // zeroes for the rest of the slots + for (uint32 r = 0; r < structs::MAX_PP_MEMSPELL - MAX_PP_MEMSPELL; r++) + { + outapp->WriteUInt32(0xFFFFFFFFU); + } + + outapp->WriteUInt32(13); // Unknown count + + for (uint32 r = 0; r < 13; r++) + { + outapp->WriteUInt32(0); // Unknown + } + + outapp->WriteUInt8(0); // Unknown + + outapp->WriteUInt32(structs::BUFF_COUNT); + + //*000*/ uint8 slotid; // badly named... seems to be 2 for a real buff, 0 otherwise + //*001*/ float unknown004; // Seen 1 for no buff + //*005*/ uint32 player_id; // 'global' ID of the caster, for wearoff messages + //*009*/ uint32 unknown016; + //*013*/ uint8 bard_modifier; + //*014*/ uint32 duration; + //*018*/ uint8 level; + //*019*/ uint32 spellid; + //*023*/ uint32 counters; + //*027*/ uint8 unknown0028[53]; + //*080*/ + + for (uint32 r = 0; r < BUFF_COUNT; r++) + { + float instrument_mod = 0.0f; + uint8 slotid = emu->buffs[r].slotid; + uint32 player_id = emu->buffs[r].player_id;; + + if (emu->buffs[r].spellid != 0xFFFF && emu->buffs[r].spellid != 0) + { + instrument_mod = 1.0f + (emu->buffs[r].bard_modifier - 10) / 10.0f; + slotid = 2; + player_id = 0x000717fd; + } + else + { + slotid = 0; + } + outapp->WriteUInt8(0); // Had this as slot, but always appears to be 0 on live. + outapp->WriteFloat(instrument_mod); + outapp->WriteUInt32(player_id); + outapp->WriteUInt8(0); + outapp->WriteUInt32(emu->buffs[r].counters); + //outapp->WriteUInt8(emu->buffs[r].bard_modifier); + outapp->WriteUInt32(emu->buffs[r].duration); + outapp->WriteUInt8(emu->buffs[r].level); + outapp->WriteUInt32(emu->buffs[r].spellid); + outapp->WriteUInt32(slotid); // Only ever seen 2 + outapp->WriteUInt32(0); + outapp->WriteUInt8(0); + outapp->WriteUInt32(emu->buffs[r].counters); // Appears twice ? + + for (uint32 j = 0; j < 44; ++j) + outapp->WriteUInt8(0); // Unknown + } + + for (uint32 r = 0; r < structs::BUFF_COUNT - BUFF_COUNT; r++) + { + // 80 bytes of zeroes + for (uint32 j = 0; j < 20; ++j) + outapp->WriteUInt32(0); + + } + + outapp->WriteUInt32(emu->platinum); + outapp->WriteUInt32(emu->gold); + outapp->WriteUInt32(emu->silver); + outapp->WriteUInt32(emu->copper); + + outapp->WriteUInt32(emu->platinum_cursor); + outapp->WriteUInt32(emu->gold_cursor); + outapp->WriteUInt32(emu->silver_cursor); + outapp->WriteUInt32(emu->copper_cursor); + + outapp->WriteUInt32(0); // Unknown + + outapp->WriteUInt32(0); // This is the cooldown timer for the monk 'Mend' skill. Client will add 6 minutes to this value the first time the + // player logs in. After that it will honour whatever value we send here. + + outapp->WriteUInt32(0); // Unknown + + outapp->WriteUInt32(emu->thirst_level); + outapp->WriteUInt32(emu->hunger_level); + + outapp->WriteUInt32(emu->aapoints_spent); + + outapp->WriteUInt32(5); // AA Points count ?? + outapp->WriteUInt32(1234); // AA Points assigned + outapp->WriteUInt32(0); // AA Points in General ? + outapp->WriteUInt32(0); // AA Points in Class ? + outapp->WriteUInt32(0); // AA Points in Archetype ? + outapp->WriteUInt32(0); // AA Points in Special ? + outapp->WriteUInt32(emu->aapoints); // AA Points unspent + + outapp->WriteUInt8(0); // Unknown + outapp->WriteUInt8(0); // Unknown + + outapp->WriteUInt32(structs::MAX_PLAYER_BANDOLIER); + + for (uint32 r = 0; r < EmuConstants::BANDOLIERS_COUNT; r++) + { + outapp->WriteString(emu->bandoliers[r].name); + + for (uint32 j = 0; j < EmuConstants::BANDOLIER_SIZE; ++j) + { + outapp->WriteString(emu->bandoliers[r].items[j].item_name); + outapp->WriteUInt32(emu->bandoliers[r].items[j].item_id); + outapp->WriteUInt32(emu->bandoliers[r].items[j].icon); + } + } + + for (uint32 r = 0; r < structs::MAX_PLAYER_BANDOLIER - EmuConstants::BANDOLIERS_COUNT; r++) + { + outapp->WriteString(""); + + for (uint32 j = 0; j < EmuConstants::BANDOLIER_SIZE; ++j) + { + outapp->WriteString(""); + outapp->WriteUInt32(0); + outapp->WriteUInt32(0); + } + } + + outapp->WriteUInt32(structs::MAX_POTIONS_IN_BELT); + + for (uint32 r = 0; r < EmuConstants::POTION_BELT_SIZE; r++) + { + outapp->WriteString(emu->potionbelt.items[r].item_name); + outapp->WriteUInt32(emu->potionbelt.items[r].item_id); + outapp->WriteUInt32(emu->potionbelt.items[r].icon); + } + + for (uint32 r = 0; r < structs::MAX_POTIONS_IN_BELT - EmuConstants::POTION_BELT_SIZE; r++) + { + outapp->WriteString(""); + outapp->WriteUInt32(0); + outapp->WriteUInt32(0); + } + + outapp->WriteSInt32(-1); // Unknown; + outapp->WriteSInt32(123); // HP Total ? + outapp->WriteSInt32(234); // Endurance Total ? + outapp->WriteSInt32(345); // Mana Total ? + + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(0); // Unknown + + outapp->WriteUInt32(20); // Unknown - Expansion count ? + + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(emu->endurance); + outapp->WriteUInt32(0); // Unknown - Observed 0x7cde - This is also seen in guild packets sent to this character. + outapp->WriteUInt32(0); // Unknown - Observed 0x64 + + outapp->WriteUInt32(64); // Name Length + + uint32 CurrentPosition = outapp->GetWritePosition(); + + outapp->WriteString(emu->name); + + outapp->SetWritePosition(CurrentPosition + 64); + + outapp->WriteUInt32(32); // Last Name Length + + CurrentPosition = outapp->GetWritePosition(); + + outapp->WriteString(emu->last_name); + + outapp->SetWritePosition(CurrentPosition + 32); + + outapp->WriteUInt32(emu->birthday); + outapp->WriteUInt32(emu->birthday); // Account start date ? + outapp->WriteUInt32(emu->lastlogin); + outapp->WriteUInt32(emu->timePlayedMin); + outapp->WriteUInt32(emu->timeentitledonaccount); + outapp->WriteUInt32(0x0007ffff); // Expansion bitmask + + outapp->WriteUInt32(structs::MAX_PP_LANGUAGE); + + for (uint32 r = 0; r < MAX_PP_LANGUAGE; r++) + { + outapp->WriteUInt8(emu->languages[r]); + } + + for (uint32 r = 0; r < structs::MAX_PP_LANGUAGE - MAX_PP_LANGUAGE; r++) + { + outapp->WriteUInt8(0); + } + + outapp->WriteUInt16(emu->zone_id); + outapp->WriteUInt16(emu->zoneInstance); + + outapp->WriteFloat(emu->y); + outapp->WriteFloat(emu->x); + outapp->WriteFloat(emu->z); + outapp->WriteFloat(emu->heading); + + outapp->WriteUInt8(0); // Unknown + outapp->WriteUInt8(emu->pvp); + outapp->WriteUInt8(0); // Unknown + outapp->WriteUInt8(emu->gm); + + outapp->WriteUInt32(emu->guild_id); + outapp->WriteUInt8(0); // Unknown - observed 1 in a live packet. + outapp->WriteUInt32(0); // Unknown - observed 1 in a live packet. + outapp->WriteUInt8(0); // Unknown - observed 1 in a live packet. + outapp->WriteUInt32(0); // Unknown + + outapp->WriteUInt64(emu->exp); + outapp->WriteUInt8(0); // Unknown + + outapp->WriteUInt32(emu->platinum_bank); + outapp->WriteUInt32(emu->gold_bank); + outapp->WriteUInt32(emu->silver_bank); + outapp->WriteUInt32(emu->copper_bank); + + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(0); // Unknown + + outapp->WriteUInt32(42); // The meaning of life ? + + for (uint32 r = 0; r < 42; r++) + { + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(0); // Unknown + } + + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(0); // Unknown + + outapp->WriteUInt32(emu->career_tribute_points); + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(emu->tribute_points); + outapp->WriteUInt32(0); // Unknown + + outapp->WriteUInt8(0); // Unknown + outapp->WriteUInt8(0); // Unknown + + outapp->WriteUInt32(EmuConstants::TRIBUTE_SIZE); + + for (uint32 r = 0; r < EmuConstants::TRIBUTE_SIZE; r++) + { + outapp->WriteUInt32(emu->tributes[r].tribute); + outapp->WriteUInt32(emu->tributes[r].tier); + } + + outapp->WriteUInt32(10); // Guild Tribute Count ? + + for (uint32 r = 0; r < 10; r++) + { + outapp->WriteUInt32(0xffffffff); + outapp->WriteUInt32(0); + } + + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(0); // Unknown + + // Block of 121 unknown bytes + for (uint32 r = 0; r < 121; r++) + outapp->WriteUInt8(0); // Unknown + + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(emu->currentRadCrystals); + outapp->WriteUInt32(emu->careerRadCrystals); + outapp->WriteUInt32(emu->currentEbonCrystals); + outapp->WriteUInt32(emu->careerEbonCrystals); + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(0); // Unknown + + // Unknown String ? + outapp->WriteUInt32(64); // Unknown + for (uint32 r = 0; r < 64; r++) + outapp->WriteUInt8(0); // Unknown + + outapp->WriteUInt8(0); // Unknown + outapp->WriteUInt8(0); // Unknown + outapp->WriteUInt8(0); // Unknown + + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(0); // Unknown + + outapp->WriteUInt8(0); // Unknown + outapp->WriteUInt8(0); // Unknown + outapp->WriteUInt8(0); // Unknown + + outapp->WriteUInt32(0); // Unknown + + outapp->WriteUInt8(0); // Unknown + outapp->WriteUInt8(0); // Unknown + outapp->WriteUInt8(0); // Unknown + + outapp->WriteUInt32(0); // Unknown + + outapp->WriteUInt8(0); // Unknown + + outapp->WriteUInt32(0); // Unknown + + // Unknown String ? + outapp->WriteUInt32(64); // Unknown + for (uint32 r = 0; r < 64; r++) + outapp->WriteUInt8(0); // Unknown + + // Unknown String ? + outapp->WriteUInt32(64); // Unknown + for (uint32 r = 0; r < 64; r++) + outapp->WriteUInt8(0); // Unknown + + outapp->WriteUInt32(0); // Unknown + + // Block of 320 unknown bytes + for (uint32 r = 0; r < 320; r++) + outapp->WriteUInt8(0); // Unknown + + // Block of 343 unknown bytes + for (uint32 r = 0; r < 343; r++) + outapp->WriteUInt8(0); // Unknown + + outapp->WriteUInt32(0); // Unknown + + outapp->WriteUInt8(emu->leadAAActive); + + outapp->WriteUInt32(6); // Count ... of LDoN stats ? + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(emu->ldon_points_guk); + outapp->WriteUInt32(emu->ldon_points_mir); + outapp->WriteUInt32(emu->ldon_points_mmc); + outapp->WriteUInt32(emu->ldon_points_ruj); + outapp->WriteUInt32(emu->ldon_points_tak); + + outapp->WriteUInt32(emu->ldon_points_available); + + outapp->WriteDouble(emu->group_leadership_exp); + outapp->WriteDouble(emu->raid_leadership_exp); + + outapp->WriteUInt32(emu->group_leadership_points); + outapp->WriteUInt32(emu->raid_leadership_points); + + outapp->WriteUInt32(64); // Group of 64 int32s follow Group/Raid Leadership abilities ? + + for (uint32 r = 0; r < MAX_LEADERSHIP_AA_ARRAY; r++) + outapp->WriteUInt32(emu->leader_abilities.ranks[r]); + + for (uint32 r = 0; r < 64 - MAX_LEADERSHIP_AA_ARRAY; r++) + outapp->WriteUInt32(0); // Unused/unsupported Leadership abilities + + outapp->WriteUInt32(emu->air_remaining); // ? + + // PVP Stats + + outapp->WriteUInt32(emu->PVPKills); + outapp->WriteUInt32(emu->PVPDeaths); + outapp->WriteUInt32(emu->PVPCurrentPoints); + outapp->WriteUInt32(emu->PVPCareerPoints); + outapp->WriteUInt32(emu->PVPBestKillStreak); + outapp->WriteUInt32(emu->PVPWorstDeathStreak); + outapp->WriteUInt32(emu->PVPCurrentKillStreak); + + // Last PVP Kill + + outapp->WriteString(emu->PVPLastKill.Name); + outapp->WriteUInt32(emu->PVPLastKill.Level); + outapp->WriteUInt32(emu->PVPLastKill.Race); + outapp->WriteUInt32(emu->PVPLastKill.Class); + outapp->WriteUInt32(emu->PVPLastKill.Zone); + outapp->WriteUInt32(emu->PVPLastKill.Time); + outapp->WriteUInt32(emu->PVPLastKill.Points); + + // Last PVP Death + + outapp->WriteString(emu->PVPLastDeath.Name); + outapp->WriteUInt32(emu->PVPLastDeath.Level); + outapp->WriteUInt32(emu->PVPLastDeath.Race); + outapp->WriteUInt32(emu->PVPLastDeath.Class); + outapp->WriteUInt32(emu->PVPLastDeath.Zone); + outapp->WriteUInt32(emu->PVPLastDeath.Time); + outapp->WriteUInt32(emu->PVPLastDeath.Points); + + outapp->WriteUInt32(emu->PVPNumberOfKillsInLast24Hours); + + // Last 50 Kills + outapp->WriteUInt32(50); + for (uint32 r = 0; r < 50; ++r) + { + outapp->WriteString(emu->PVPRecentKills[r].Name); + outapp->WriteUInt32(emu->PVPRecentKills[r].Level); + outapp->WriteUInt32(emu->PVPRecentKills[r].Race); + outapp->WriteUInt32(emu->PVPRecentKills[r].Class); + outapp->WriteUInt32(emu->PVPRecentKills[r].Zone); + outapp->WriteUInt32(emu->PVPRecentKills[r].Time); + outapp->WriteUInt32(emu->PVPRecentKills[r].Points); + } + + outapp->WriteUInt32(emu->expAA); + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(0); // Unknown + + outapp->WriteUInt8(emu->groupAutoconsent); + outapp->WriteUInt8(emu->raidAutoconsent); + outapp->WriteUInt8(emu->guildAutoconsent); + + outapp->WriteUInt8(0); // Unknown + + outapp->WriteUInt32(emu->level); // Level3 ? + + outapp->WriteUInt8(emu->showhelm); + + outapp->WriteUInt32(emu->RestTimer); + + outapp->WriteUInt32(1024); // Unknown Count + + // Block of 1024 unknown bytes + outapp->WriteUInt8(31); // Unknown + + for (uint32 r = 0; r < 1023; r++) + outapp->WriteUInt8(0); // Unknown + + outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(0); // Unknown + + // Think we need 1 byte of padding at the end + + outapp->WriteUInt8(0); // Unknown + + _log(NET__STRUCTS, "Player Profile Packet is %i bytes", outapp->GetWritePosition()); + + unsigned char *NewBuffer = new unsigned char[outapp->GetWritePosition()]; + memcpy(NewBuffer, outapp->pBuffer, outapp->GetWritePosition()); + safe_delete_array(outapp->pBuffer); + outapp->pBuffer = NewBuffer; + outapp->size = outapp->GetWritePosition(); + outapp->SetWritePosition(4); + outapp->WriteUInt32(outapp->size - 9); + + CRC32::SetEQChecksum(outapp->pBuffer, outapp->size - 1, 8); + //_hex(NET__ERROR, outapp->pBuffer, outapp->size); + + dest->FastQueuePacket(&outapp, ack_req); + delete in; + return; + } + + ENCODE(OP_RaidJoin) + { + EQApplicationPacket *inapp = *p; + unsigned char * __emu_buffer = inapp->pBuffer; + RaidCreate_Struct *raid_create = (RaidCreate_Struct*)__emu_buffer; + + EQApplicationPacket *outapp_create = new EQApplicationPacket(OP_RaidUpdate, sizeof(structs::RaidGeneral_Struct)); + structs::RaidGeneral_Struct *general = (structs::RaidGeneral_Struct*)outapp_create->pBuffer; + + general->action = 8; + general->parameter = 1; + strn0cpy(general->leader_name, raid_create->leader_name, 64); + strn0cpy(general->player_name, raid_create->leader_name, 64); + + dest->FastQueuePacket(&outapp_create); + delete[] __emu_buffer; + } + + ENCODE(OP_RaidUpdate) + { + EQApplicationPacket *inapp = *p; + *p = nullptr; + unsigned char * __emu_buffer = inapp->pBuffer; + RaidGeneral_Struct *raid_gen = (RaidGeneral_Struct*)__emu_buffer; + + if (raid_gen->action == 0) // raid add has longer length than other raid updates + { + RaidAddMember_Struct* in_add_member = (RaidAddMember_Struct*)__emu_buffer; + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_RaidUpdate, sizeof(structs::RaidAddMember_Struct)); + structs::RaidAddMember_Struct *add_member = (structs::RaidAddMember_Struct*)outapp->pBuffer; + + add_member->raidGen.action = in_add_member->raidGen.action; + add_member->raidGen.parameter = in_add_member->raidGen.parameter; + strn0cpy(add_member->raidGen.leader_name, in_add_member->raidGen.leader_name, 64); + strn0cpy(add_member->raidGen.player_name, in_add_member->raidGen.player_name, 64); + add_member->_class = in_add_member->_class; + add_member->level = in_add_member->level; + add_member->isGroupLeader = in_add_member->isGroupLeader; + add_member->flags[0] = in_add_member->flags[0]; + add_member->flags[1] = in_add_member->flags[1]; + add_member->flags[2] = in_add_member->flags[2]; + add_member->flags[3] = in_add_member->flags[3]; + add_member->flags[4] = in_add_member->flags[4]; + dest->FastQueuePacket(&outapp); + } + else if (raid_gen->action == 35) + { + RaidMOTD_Struct *inmotd = (RaidMOTD_Struct *)__emu_buffer; + EQApplicationPacket *outapp = new EQApplicationPacket(OP_RaidUpdate, sizeof(structs::RaidMOTD_Struct) + strlen(inmotd->motd) + 1); + structs::RaidMOTD_Struct *outmotd = (structs::RaidMOTD_Struct *)outapp->pBuffer; + + outmotd->general.action = inmotd->general.action; + strn0cpy(outmotd->general.player_name, inmotd->general.player_name, 64); + strn0cpy(outmotd->motd, inmotd->motd, strlen(inmotd->motd) + 1); + dest->FastQueuePacket(&outapp); + } + else if (raid_gen->action == 14 || raid_gen->action == 30) + { + RaidLeadershipUpdate_Struct *inlaa = (RaidLeadershipUpdate_Struct *)__emu_buffer; + EQApplicationPacket *outapp = new EQApplicationPacket(OP_RaidUpdate, sizeof(structs::RaidLeadershipUpdate_Struct)); + structs::RaidLeadershipUpdate_Struct *outlaa = (structs::RaidLeadershipUpdate_Struct *)outapp->pBuffer; + + outlaa->action = inlaa->action; + strn0cpy(outlaa->player_name, inlaa->player_name, 64); + strn0cpy(outlaa->leader_name, inlaa->leader_name, 64); + memcpy(&outlaa->raid, &inlaa->raid, sizeof(RaidLeadershipAA_Struct)); + dest->FastQueuePacket(&outapp); + } + else + { + RaidGeneral_Struct* in_raid_general = (RaidGeneral_Struct*)__emu_buffer; + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_RaidUpdate, sizeof(structs::RaidGeneral_Struct)); + structs::RaidGeneral_Struct *raid_general = (structs::RaidGeneral_Struct*)outapp->pBuffer; + strn0cpy(raid_general->leader_name, in_raid_general->leader_name, 64); + strn0cpy(raid_general->player_name, in_raid_general->player_name, 64); + raid_general->action = in_raid_general->action; + raid_general->parameter = in_raid_general->parameter; + dest->FastQueuePacket(&outapp); + } + + delete[] __emu_buffer; + } + + ENCODE(OP_ReadBook) + { + ENCODE_LENGTH_ATLEAST(BookText_Struct); + SETUP_DIRECT_ENCODE(BookText_Struct, structs::BookRequest_Struct); + + if (emu->window == 0xFF) + eq->window = 0xFFFFFFFF; + else + eq->window = emu->window; + OUT(type); + OUT(invslot); + strn0cpy(eq->txtfile, emu->booktext, sizeof(eq->txtfile)); + + FINISH_ENCODE(); + } + + ENCODE(OP_RecipeAutoCombine) + { + ENCODE_LENGTH_EXACT(RecipeAutoCombine_Struct); + SETUP_DIRECT_ENCODE(RecipeAutoCombine_Struct, structs::RecipeAutoCombine_Struct); + + OUT(object_type); + OUT(some_id); + eq->container_slot = ServerToRoF2Slot(emu->unknown1); + structs::ItemSlotStruct RoF2Slot; + RoF2Slot.SlotType = 8; // Observed + RoF2Slot.Unknown02 = 0; + RoF2Slot.MainSlot = 0xffff; + RoF2Slot.SubSlot = 0xffff; + RoF2Slot.AugSlot = 0xffff; + RoF2Slot.Unknown01 = 0; + eq->unknown_slot = RoF2Slot; + OUT(recipe_id); + OUT(reply_code); + + FINISH_ENCODE(); + } + + ENCODE(OP_RemoveBlockedBuffs) { ENCODE_FORWARD(OP_BlockedBuffs); } + + ENCODE(OP_RequestClientZoneChange) + { + ENCODE_LENGTH_EXACT(RequestClientZoneChange_Struct); + SETUP_DIRECT_ENCODE(RequestClientZoneChange_Struct, structs::RequestClientZoneChange_Struct); + + OUT(zone_id); + OUT(instance_id); + OUT(y); + OUT(x); + OUT(z); + OUT(heading); + eq->type = 0x0b; + eq->unknown004 = 0xffffffff; + eq->unknown172 = 0x0168b500; + + FINISH_ENCODE(); + } + + ENCODE(OP_RespondAA) + { + SETUP_DIRECT_ENCODE(AATable_Struct, structs::AATable_Struct); + + eq->aa_spent = emu->aa_spent; + // These fields may need to be correctly populated at some point + eq->aapoints_assigned = emu->aa_spent + 1; + eq->aa_spent_general = 0; + eq->aa_spent_archetype = 0; + eq->aa_spent_class = 0; + eq->aa_spent_special = 0; + + for (uint32 i = 0; i < MAX_PP_AA_ARRAY; ++i) + { + eq->aa_list[i].aa_skill = emu->aa_list[i].aa_skill; + eq->aa_list[i].aa_value = emu->aa_list[i].aa_value; + eq->aa_list[i].unknown08 = emu->aa_list[i].unknown08; + } + + FINISH_ENCODE(); + } + + ENCODE(OP_RezzRequest) + { + SETUP_DIRECT_ENCODE(Resurrect_Struct, structs::Resurrect_Struct); + + OUT(zone_id); + OUT(instance_id); + OUT(y); + OUT(x); + OUT(z); + OUT_str(your_name); + OUT_str(rezzer_name); + OUT(spellid); + OUT_str(corpse_name); + OUT(action); + + FINISH_ENCODE(); + } + + ENCODE(OP_SendAATable) + { + ENCODE_LENGTH_ATLEAST(SendAA_Struct); + SETUP_VAR_ENCODE(SendAA_Struct); + ALLOC_VAR_ENCODE(structs::SendAA_Struct, sizeof(structs::SendAA_Struct) + emu->total_abilities*sizeof(structs::AA_Ability)); + + // Check clientver field to verify this AA should be sent for SoF + // clientver 1 is for all clients and 5 is for Live + if (emu->clientver <= 5) + { + OUT(id); + eq->unknown004 = 1; + //eq->hotkey_sid = (emu->hotkey_sid==4294967295UL)?0:(emu->id - emu->current_level + 1); + //eq->hotkey_sid2 = (emu->hotkey_sid2==4294967295UL)?0:(emu->id - emu->current_level + 1); + //eq->title_sid = emu->id - emu->current_level + 1; + //eq->desc_sid = emu->id - emu->current_level + 1; + eq->hotkey_sid = (emu->hotkey_sid == 4294967295UL) ? -1 : (emu->sof_next_skill); + eq->hotkey_sid2 = (emu->hotkey_sid2 == 4294967295UL) ? -1 : (emu->sof_next_skill); + eq->title_sid = emu->sof_next_skill; + eq->desc_sid = emu->sof_next_skill; + OUT(class_type); + OUT(cost); + OUT(seq); + OUT(current_level); + eq->unknown037 = 1; // Introduced during HoT + OUT(prereq_skill); + eq->unknown045 = 1; // New Mar 21 2012 - Seen 1 + OUT(prereq_minpoints); + eq->type = emu->sof_type; + OUT(spellid); + eq->unknown057 = 1; // Introduced during HoT + OUT(spell_type); + OUT(spell_refresh); + OUT(classes); + OUT(berserker); + //eq->max_level = emu->sof_max_level; + OUT(max_level); + OUT(last_id); + OUT(next_id); + OUT(cost2); + eq->aa_expansion = emu->aa_expansion; + eq->special_category = emu->special_category; + OUT(total_abilities); + unsigned int r; + for (r = 0; r < emu->total_abilities; r++) { + OUT(abilities[r].skill_id); + OUT(abilities[r].base1); + OUT(abilities[r].base2); + OUT(abilities[r].slot); + } + } + + _hex(NET__ERROR, eq, sizeof(structs::SendAA_Struct) + emu->total_abilities*sizeof(structs::AA_Ability)); + + FINISH_ENCODE(); + } + + ENCODE(OP_SendCharInfo) + { + ENCODE_LENGTH_EXACT(CharacterSelect_Struct); + SETUP_VAR_ENCODE(CharacterSelect_Struct); + + //EQApplicationPacket *packet = *p; + //const CharacterSelect_Struct *emu = (CharacterSelect_Struct *) packet->pBuffer; + + int char_count; + int namelen = 0; + for (char_count = 0; char_count < 10; char_count++) { + if (emu->name[char_count][0] == '\0') + break; + if (strcmp(emu->name[char_count], "") == 0) + break; + namelen += strlen(emu->name[char_count]); + } + + int total_length = sizeof(structs::CharacterSelect_Struct) + + char_count * sizeof(structs::CharacterSelectEntry_Struct) + + namelen; + + ALLOC_VAR_ENCODE(structs::CharacterSelect_Struct, total_length); + + //unsigned char *eq_buffer = new unsigned char[total_length]; + //structs::CharacterSelect_Struct *eq_head = (structs::CharacterSelect_Struct *) eq_buffer; + + eq->char_count = char_count; + //eq->total_chars = 10; + + unsigned char *bufptr = (unsigned char *)eq->entries; + int r; + for (r = 0; r < char_count; r++) { + { //pre-name section... + structs::CharacterSelectEntry_Struct *eq2 = (structs::CharacterSelectEntry_Struct *) bufptr; + memcpy(eq2->name, emu->name[r], strlen(emu->name[r]) + 1); + } + //adjust for name. + bufptr += strlen(emu->name[r]); + { //post-name section... + structs::CharacterSelectEntry_Struct *eq2 = (structs::CharacterSelectEntry_Struct *) bufptr; + eq2->class_ = emu->class_[r]; + eq2->race = emu->race[r]; + eq2->level = emu->level[r]; + eq2->class_2 = emu->class_[r]; + eq2->race2 = emu->race[r]; + eq2->zone = emu->zone[r]; + eq2->instance = 0; + eq2->gender = emu->gender[r]; + eq2->face = emu->face[r]; + int k; + for (k = 0; k < _MaterialCount; k++) { + eq2->equip[k].equip0 = emu->equip[r][k]; + eq2->equip[k].equip1 = 0; + eq2->equip[k].equip2 = 0; + eq2->equip[k].itemid = 0; + eq2->equip[k].equip3 = emu->equip[r][k]; + eq2->equip[k].color.color = emu->cs_colors[r][k].color; + } + eq2->u15 = 0xff; + eq2->u19 = 0xFF; + eq2->drakkin_tattoo = emu->drakkin_tattoo[r]; + eq2->drakkin_details = emu->drakkin_details[r]; + eq2->deity = emu->deity[r]; + eq2->primary = emu->primary[r]; + eq2->secondary = emu->secondary[r]; + eq2->haircolor = emu->haircolor[r]; + eq2->beardcolor = emu->beardcolor[r]; + eq2->eyecolor1 = emu->eyecolor1[r]; + eq2->eyecolor2 = emu->eyecolor2[r]; + eq2->hairstyle = emu->hairstyle[r]; + eq2->beard = emu->beard[r]; + eq2->char_enabled = 1; + eq2->tutorial = emu->tutorial[r]; + eq2->drakkin_heritage = emu->drakkin_heritage[r]; + eq2->unknown1 = 0; + eq2->gohome = emu->gohome[r]; + eq2->LastLogin = 1212696584; + eq2->unknown2 = 0; + } + bufptr += sizeof(structs::CharacterSelectEntry_Struct); + } + + FINISH_ENCODE(); + } + + ENCODE(OP_SendMembership) + { + ENCODE_LENGTH_EXACT(Membership_Struct); + SETUP_DIRECT_ENCODE(Membership_Struct, structs::Membership_Struct); + + eq->membership = emu->membership; + eq->races = emu->races; + eq->classes = emu->classes; + eq->entrysize = 22; + for (int i = 0; i<21; i++) + { + eq->entries[i] = emu->entries[i]; + } + eq->entries[21] = 0; + + FINISH_ENCODE(); + } + + ENCODE(OP_SendZonepoints) + { + SETUP_VAR_ENCODE(ZonePoints); + ALLOC_VAR_ENCODE(structs::ZonePoints, sizeof(structs::ZonePoints) + sizeof(structs::ZonePoint_Entry) * (emu->count + 1)); + + eq->count = emu->count; + for (uint32 i = 0; i < emu->count; ++i) + { + eq->zpe[i].iterator = emu->zpe[i].iterator; + eq->zpe[i].x = emu->zpe[i].x; + eq->zpe[i].y = emu->zpe[i].y; + eq->zpe[i].z = emu->zpe[i].z; + eq->zpe[i].heading = emu->zpe[i].heading; + eq->zpe[i].zoneid = emu->zpe[i].zoneid; + eq->zpe[i].zoneinstance = emu->zpe[i].zoneinstance; + } + + FINISH_ENCODE(); + } + + ENCODE(OP_SetGuildRank) + { + ENCODE_LENGTH_EXACT(GuildSetRank_Struct); + SETUP_DIRECT_ENCODE(GuildSetRank_Struct, structs::GuildSetRank_Struct); + + eq->GuildID = emu->Unknown00; + + /* Translate older ranks to new values */ + switch (emu->Rank) { + case 0: { eq->Rank = 5; break; } // GUILD_MEMBER 0 + case 1: { eq->Rank = 3; break; } // GUILD_OFFICER 1 + case 2: { eq->Rank = 1; break; } // GUILD_LEADER 2 + default: { eq->Rank = emu->Rank; break; } + } + + memcpy(eq->MemberName, emu->MemberName, sizeof(eq->MemberName)); + OUT(Banker); + eq->Unknown76 = 1; + + FINISH_ENCODE(); + } + + ENCODE(OP_ShopPlayerBuy) + { + ENCODE_LENGTH_EXACT(Merchant_Sell_Struct); + SETUP_DIRECT_ENCODE(Merchant_Sell_Struct, structs::Merchant_Sell_Struct); + + OUT(npcid); + OUT(playerid); + OUT(itemslot); + OUT(quantity); + OUT(price); + + FINISH_ENCODE(); + } + + ENCODE(OP_ShopPlayerSell) + { + ENCODE_LENGTH_EXACT(Merchant_Purchase_Struct); + SETUP_DIRECT_ENCODE(Merchant_Purchase_Struct, structs::Merchant_Purchase_Struct); + + OUT(npcid); + eq->itemslot = ServerToRoF2MainInvSlot(emu->itemslot); + //OUT(itemslot); + OUT(quantity); + OUT(price); + + FINISH_ENCODE(); + } + + ENCODE(OP_ShopRequest) + { + ENCODE_LENGTH_EXACT(Merchant_Click_Struct); + SETUP_DIRECT_ENCODE(Merchant_Click_Struct, structs::Merchant_Click_Struct); + + OUT(npcid); + OUT(playerid); + OUT(command); + OUT(rate); + eq->unknown01 = 3; // Not sure what these values do yet, but list won't display without them + eq->unknown02 = 2592000; + + FINISH_ENCODE(); + } + + ENCODE(OP_SkillUpdate) + { + ENCODE_LENGTH_EXACT(SkillUpdate_Struct); + SETUP_DIRECT_ENCODE(SkillUpdate_Struct, structs::SkillUpdate_Struct); + + OUT(skillId); + OUT(value); + eq->unknown08 = 1; // Observed + eq->unknown09 = 80; // Observed + eq->unknown10 = 136; // Observed + eq->unknown11 = 54; // Observed + + FINISH_ENCODE(); + } + + ENCODE(OP_SomeItemPacketMaybe) + { + // This Opcode is not named very well. It is used for the animation of arrows leaving the player's bow + // and flying to the target. + // + + ENCODE_LENGTH_EXACT(Arrow_Struct); + SETUP_DIRECT_ENCODE(Arrow_Struct, structs::Arrow_Struct); + + OUT(src_y); + OUT(src_x); + OUT(src_z); + OUT(velocity); + OUT(launch_angle); + OUT(tilt); + OUT(arc); + OUT(source_id); + OUT(target_id); + OUT(item_id); + + eq->unknown070 = 175; // This needs to be set to something, else we get a 1HS animation instead of ranged. + + OUT(item_type); + OUT(skill); + + strncpy(eq->model_name, emu->model_name, sizeof(eq->model_name)); + + FINISH_ENCODE(); + } + + ENCODE(OP_SpawnAppearance) + { + EQApplicationPacket *in = *p; + *p = nullptr; + + unsigned char *emu_buffer = in->pBuffer; + + SpawnAppearance_Struct *sas = (SpawnAppearance_Struct *)emu_buffer; + + if (sas->type != AT_Size) + { + dest->FastQueuePacket(&in, ack_req); + return; + } + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_ChangeSize, sizeof(ChangeSize_Struct)); + + ChangeSize_Struct *css = (ChangeSize_Struct *)outapp->pBuffer; + + css->EntityID = sas->spawn_id; + css->Size = (float)sas->parameter; + css->Unknown08 = 0; + css->Unknown12 = 1.0f; + + dest->FastQueuePacket(&outapp, ack_req); + delete in; + } + + ENCODE(OP_SpawnDoor) + { + SETUP_VAR_ENCODE(Door_Struct); + int door_count = __packet->size / sizeof(Door_Struct); + int total_length = door_count * sizeof(structs::Door_Struct); + ALLOC_VAR_ENCODE(structs::Door_Struct, total_length); + + int r; + for (r = 0; r < door_count; r++) { + strncpy(eq[r].name, emu[r].name, sizeof(eq[r].name)); + eq[r].xPos = emu[r].xPos; + eq[r].yPos = emu[r].yPos; + eq[r].zPos = emu[r].zPos; + eq[r].heading = emu[r].heading; + eq[r].incline = emu[r].incline; + eq[r].size = emu[r].size; + eq[r].doorId = emu[r].doorId; + eq[r].opentype = emu[r].opentype; + eq[r].state_at_spawn = emu[r].state_at_spawn; + eq[r].invert_state = emu[r].invert_state; + eq[r].door_param = emu[r].door_param; + eq[r].unknown0080 = 0; + eq[r].unknown0081 = 1; // Both must be 1 to allow clicking doors + eq[r].unknown0082 = 0; + eq[r].unknown0083 = 1; // Both must be 1 to allow clicking doors + eq[r].unknown0084 = 0; + eq[r].unknown0085 = 0; + eq[r].unknown0086 = 0; + } + + FINISH_ENCODE(); + } + + ENCODE(OP_Stun) + { + ENCODE_LENGTH_EXACT(Stun_Struct); + SETUP_DIRECT_ENCODE(Stun_Struct, structs::Stun_Struct); + + OUT(duration); + eq->unknown005 = 163; + eq->unknown006 = 67; + + FINISH_ENCODE(); + } + + ENCODE(OP_TargetBuffs) { ENCODE_FORWARD(OP_BuffCreate); } + + ENCODE(OP_TaskDescription) + { + EQApplicationPacket *in = *p; + *p = nullptr; + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_TaskDescription, in->size + 1); + // Set the Write pointer as we don't know what has been done with the packet before we get it. + in->SetReadPosition(0); + // Copy the header + for (int i = 0; i < 5; ++i) + outapp->WriteUInt32(in->ReadUInt32()); + + // Copy Title + while (uint8 c = in->ReadUInt8()) + outapp->WriteUInt8(c); + outapp->WriteUInt8(0); + + outapp->WriteUInt32(in->ReadUInt32()); // Duration + outapp->WriteUInt32(in->ReadUInt32()); // Unknown + uint32 StartTime = in->ReadUInt32(); + outapp->WriteUInt32(time(nullptr) - StartTime); // RoF2 has elapsed time here rather than starttime + + // Copy the rest of the packet verbatim + uint32 BytesLeftToCopy = in->size - in->GetReadPosition(); + memcpy(outapp->pBuffer + outapp->GetWritePosition(), in->pBuffer + in->GetReadPosition(), BytesLeftToCopy); + + delete in; + dest->FastQueuePacket(&outapp, ack_req); + } + + ENCODE(OP_TaskHistoryReply) + { + EQApplicationPacket *in = *p; + *p = nullptr; + + // First we need to calculate the length of the new packet + in->SetReadPosition(4); + uint32 ActivityCount = in->ReadUInt32(); + + uint32 Text1Length = 0; + uint32 Text2Length = 0; + uint32 Text3Length = 0; + + uint32 OutboundPacketSize = 8; + + for (uint32 i = 0; i < ActivityCount; ++i) + { + Text1Length = 0; + Text2Length = 0; + Text3Length = 0; + + in->ReadUInt32(); // Activity type + + // Skip past Text1 + while (in->ReadUInt8()) + ++Text1Length; + + // Skip past Text2 + while (in->ReadUInt8()) + ++Text2Length; + + in->ReadUInt32(); + in->ReadUInt32(); + in->ReadUInt32(); + uint32 ZoneID = in->ReadUInt32(); + in->ReadUInt32(); + + // Skip past Text3 + while (in->ReadUInt8()) + ++Text3Length; + + char ZoneNumber[10]; + + sprintf(ZoneNumber, "%i", ZoneID); + + OutboundPacketSize += (24 + Text1Length + 1 + Text2Length + Text3Length + 1 + 7 + (strlen(ZoneNumber) * 2)); + } + + in->SetReadPosition(0); + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_TaskHistoryReply, OutboundPacketSize); + + outapp->WriteUInt32(in->ReadUInt32()); // Task index + outapp->WriteUInt32(in->ReadUInt32()); // Activity count + + for (uint32 i = 0; i < ActivityCount; ++i) + { + Text1Length = 0; + Text2Length = 0; + Text3Length = 0; + + outapp->WriteUInt32(in->ReadUInt32()); // ActivityType + + // Copy Text1 + while (uint8 c = in->ReadUInt8()) + outapp->WriteUInt8(c); + + outapp->WriteUInt8(0); // Text1 has a null terminator + + uint32 CurrentPosition = in->GetReadPosition(); + + // Determine Length of Text2 + while (in->ReadUInt8()) + ++Text2Length; + + outapp->WriteUInt32(Text2Length); + + in->SetReadPosition(CurrentPosition); + + // Copy Text2 + while (uint8 c = in->ReadUInt8()) + outapp->WriteUInt8(c); + + outapp->WriteUInt32(in->ReadUInt32()); // Goalcount + in->ReadUInt32(); + in->ReadUInt32(); + uint32 ZoneID = in->ReadUInt32(); + in->ReadUInt32(); + + char ZoneNumber[10]; + + sprintf(ZoneNumber, "%i", ZoneID); + + outapp->WriteUInt32(2); + outapp->WriteUInt8(0x2d); // "-" + outapp->WriteUInt8(0x31); // "1" + + outapp->WriteUInt32(2); + outapp->WriteUInt8(0x2d); // "-" + outapp->WriteUInt8(0x31); // "1" + outapp->WriteString(ZoneNumber); + + outapp->WriteUInt32(0); + + // Copy Tex3t + while (uint8 c = in->ReadUInt8()) + outapp->WriteUInt8(c); + + outapp->WriteUInt8(0); // Text3 has a null terminator + + outapp->WriteUInt8(0x31); // "1" + outapp->WriteString(ZoneNumber); + } + + delete in; + dest->FastQueuePacket(&outapp, ack_req); + } + + ENCODE(OP_Track) + { + EQApplicationPacket *in = *p; + *p = nullptr; + + unsigned char *__emu_buffer = in->pBuffer; + Track_Struct *emu = (Track_Struct *)__emu_buffer; + + int EntryCount = in->size / sizeof(Track_Struct); + + if (EntryCount == 0 || ((in->size % sizeof(Track_Struct))) != 0) + { + _log(NET__STRUCTS, "Wrong size on outbound %s: Got %d, expected multiple of %d", opcodes->EmuToName(in->GetOpcode()), in->size, sizeof(Track_Struct)); + delete in; + return; + } + + int PacketSize = 2; + + for (int i = 0; i < EntryCount; ++i, ++emu) + PacketSize += (12 + strlen(emu->name)); + + emu = (Track_Struct *)__emu_buffer; + + in->size = PacketSize; + in->pBuffer = new unsigned char[in->size]; + + char *Buffer = (char *)in->pBuffer; + + VARSTRUCT_ENCODE_TYPE(uint16, Buffer, EntryCount); + + for (int i = 0; i < EntryCount; ++i, ++emu) + { + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->entityid); + VARSTRUCT_ENCODE_TYPE(float, Buffer, emu->distance); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->level); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->NPC); + VARSTRUCT_ENCODE_STRING(Buffer, emu->name); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->GroupMember); + } + + delete[] __emu_buffer; + dest->FastQueuePacket(&in, ack_req); + } + + ENCODE(OP_Trader) + { + if ((*p)->size == sizeof(ClickTrader_Struct)) + { + ENCODE_LENGTH_EXACT(ClickTrader_Struct); + SETUP_DIRECT_ENCODE(ClickTrader_Struct, structs::ClickTrader_Struct); + + eq->Code = emu->Code; + // Live actually has 200 items now, but 80 is the most our internal struct supports + for (uint32 i = 0; i < 200; i++) + { + strncpy(eq->items[i].SerialNumber, "0000000000000000", sizeof(eq->items[i].SerialNumber)); + eq->items[i].Unknown18 = 0; + if (i < 80) { + eq->ItemCost[i] = emu->ItemCost[i]; + } + else { + eq->ItemCost[i] = 0; + } + } + + FINISH_ENCODE(); + } + else if ((*p)->size == sizeof(Trader_ShowItems_Struct)) + { + ENCODE_LENGTH_EXACT(Trader_ShowItems_Struct); + SETUP_DIRECT_ENCODE(Trader_ShowItems_Struct, structs::Trader_ShowItems_Struct); + + eq->Code = emu->Code; + strncpy(eq->SerialNumber, "0000000000000000", sizeof(eq->SerialNumber)); + eq->TraderID = emu->TraderID; + eq->Stacksize = 0; + eq->Price = 0; + + FINISH_ENCODE(); + } + else if ((*p)->size == sizeof(TraderStatus_Struct)) + { + ENCODE_LENGTH_EXACT(TraderStatus_Struct); + SETUP_DIRECT_ENCODE(TraderStatus_Struct, structs::TraderStatus_Struct); + + eq->Code = emu->Code; + + FINISH_ENCODE(); + } + else if ((*p)->size == sizeof(TraderBuy_Struct)) + { + ENCODE_FORWARD(OP_TraderBuy); + } + } + + ENCODE(OP_TraderBuy) + { + ENCODE_LENGTH_EXACT(TraderBuy_Struct); + SETUP_DIRECT_ENCODE(TraderBuy_Struct, structs::TraderBuy_Struct); + + OUT(Action); + OUT(Price); + OUT(TraderID); + memcpy(eq->ItemName, emu->ItemName, sizeof(eq->ItemName)); + OUT(ItemID); + OUT(Quantity); + OUT(AlreadySold); + + FINISH_ENCODE(); + } + + ENCODE(OP_TributeInfo) + { + ENCODE_LENGTH_ATLEAST(TributeAbility_Struct); + SETUP_VAR_ENCODE(TributeAbility_Struct); + ALLOC_VAR_ENCODE(structs::TributeAbility_Struct, sizeof(structs::TributeAbility_Struct) + strlen(emu->name) + 1); + + OUT(tribute_id); + OUT(tier_count); + + for (uint32 i = 0; i < MAX_TRIBUTE_TIERS; ++i) + { + eq->tiers[i].level = emu->tiers[i].level; + eq->tiers[i].tribute_item_id = emu->tiers[i].tribute_item_id; + eq->tiers[i].cost = emu->tiers[i].cost; + } + + eq->unknown128 = 0; + + strcpy(eq->name, emu->name); + + FINISH_ENCODE(); + } + + ENCODE(OP_TributeItem) + { + ENCODE_LENGTH_EXACT(TributeItem_Struct); + SETUP_DIRECT_ENCODE(TributeItem_Struct, structs::TributeItem_Struct); + + eq->slot = ServerToRoF2Slot(emu->slot); + OUT(quantity); + OUT(tribute_master_id); + OUT(tribute_points); + + FINISH_ENCODE(); + } + + ENCODE(OP_VetRewardsAvaliable) + { + EQApplicationPacket *inapp = *p; + unsigned char * __emu_buffer = inapp->pBuffer; + + uint32 count = ((*p)->Size() / sizeof(InternalVeteranReward)); + *p = nullptr; + + EQApplicationPacket *outapp_create = new EQApplicationPacket(OP_VetRewardsAvaliable, (sizeof(structs::VeteranReward)*count)); + uchar *old_data = __emu_buffer; + uchar *data = outapp_create->pBuffer; + for (unsigned int i = 0; i < count; ++i) + { + structs::VeteranReward *vr = (structs::VeteranReward*)data; + InternalVeteranReward *ivr = (InternalVeteranReward*)old_data; + + vr->claim_count = ivr->claim_count; + vr->claim_id = ivr->claim_id; + vr->number_available = ivr->number_available; + for (int x = 0; x < 8; ++x) + { + vr->items[x].item_id = ivr->items[x].item_id; + strncpy(vr->items[x].item_name, ivr->items[x].item_name, sizeof(vr->items[x].item_name)); + vr->items[x].charges = ivr->items[x].charges; + } + + old_data += sizeof(InternalVeteranReward); + data += sizeof(structs::VeteranReward); + } + + dest->FastQueuePacket(&outapp_create); + delete inapp; + } + + ENCODE(OP_WearChange) + { + ENCODE_LENGTH_EXACT(WearChange_Struct); + SETUP_DIRECT_ENCODE(WearChange_Struct, structs::WearChange_Struct); + + OUT(spawn_id); + OUT(material); + OUT(unknown06); + OUT(elite_material); + OUT(hero_forge_model); + OUT(unknown18); + OUT(color.color); + OUT(wear_slot_id); + + FINISH_ENCODE(); + } + + ENCODE(OP_WhoAllResponse) + { + EQApplicationPacket *in = *p; + *p = nullptr; + + char *InBuffer = (char *)in->pBuffer; + + WhoAllReturnStruct *wars = (WhoAllReturnStruct*)InBuffer; + + int Count = wars->playercount; + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_WhoAllResponse, in->size + (Count * 4)); + + char *OutBuffer = (char *)outapp->pBuffer; + + // The struct fields were moved around a bit, so adjust values before copying + wars->unknown44[0] = Count; + wars->unknown52 = 0; + + memcpy(OutBuffer, InBuffer, sizeof(WhoAllReturnStruct)); + + OutBuffer += sizeof(WhoAllReturnStruct); + InBuffer += sizeof(WhoAllReturnStruct); + + for (int i = 0; i < Count; ++i) + { + uint32 x; + + x = VARSTRUCT_DECODE_TYPE(uint32, InBuffer); + VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, x); + + InBuffer += 4; + VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, 0); + VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, 0xffffffff); + + char Name[64]; + + VARSTRUCT_DECODE_STRING(Name, InBuffer); // Char Name + VARSTRUCT_ENCODE_STRING(OutBuffer, Name); + + x = VARSTRUCT_DECODE_TYPE(uint32, InBuffer); + VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, x); + + VARSTRUCT_DECODE_STRING(Name, InBuffer); // Guild Name + VARSTRUCT_ENCODE_STRING(OutBuffer, Name); + + for (int j = 0; j < 7; ++j) + { + x = VARSTRUCT_DECODE_TYPE(uint32, InBuffer); + VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, x); + } + + VARSTRUCT_DECODE_STRING(Name, InBuffer); // Account + VARSTRUCT_ENCODE_STRING(OutBuffer, Name); + + x = VARSTRUCT_DECODE_TYPE(uint32, InBuffer); + VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, x); + } + + //_hex(NET__ERROR, outapp->pBuffer, outapp->size); + dest->FastQueuePacket(&outapp); + delete in; + } + + ENCODE(OP_ZoneChange) + { + ENCODE_LENGTH_EXACT(ZoneChange_Struct); + SETUP_DIRECT_ENCODE(ZoneChange_Struct, structs::ZoneChange_Struct); + + memcpy(eq->char_name, emu->char_name, sizeof(emu->char_name)); + OUT(zoneID); + OUT(instanceID); + OUT(y); + OUT(x); + OUT(z) + OUT(zone_reason); + OUT(success); + + FINISH_ENCODE(); + } + + ENCODE(OP_ZoneEntry) { ENCODE_FORWARD(OP_ZoneSpawns); } + + ENCODE(OP_ZonePlayerToBind) + { + ENCODE_LENGTH_ATLEAST(ZonePlayerToBind_Struct); + + ZonePlayerToBind_Struct *zps = (ZonePlayerToBind_Struct*)(*p)->pBuffer; + + std::stringstream ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary); + + unsigned char *buffer1 = new unsigned char[sizeof(structs::ZonePlayerToBindHeader_Struct) + strlen(zps->zone_name)]; + structs::ZonePlayerToBindHeader_Struct *zph = (structs::ZonePlayerToBindHeader_Struct*)buffer1; + unsigned char *buffer2 = new unsigned char[sizeof(structs::ZonePlayerToBindFooter_Struct)]; + structs::ZonePlayerToBindFooter_Struct *zpf = (structs::ZonePlayerToBindFooter_Struct*)buffer2; + + zph->x = zps->x; + zph->y = zps->y; + zph->z = zps->z; + zph->heading = zps->heading; + zph->bind_zone_id = 0; + zph->bind_instance_id = zps->bind_instance_id; + strncpy(zph->zone_name, zps->zone_name, sizeof(zph->zone_name)); + + zpf->unknown021 = 1; + zpf->unknown022 = 0; + zpf->unknown023 = 0; + zpf->unknown024 = 0; + + ss.write((const char*)buffer1, (sizeof(structs::ZonePlayerToBindHeader_Struct) + strlen(zps->zone_name))); + ss.write((const char*)buffer2, sizeof(structs::ZonePlayerToBindFooter_Struct)); + + delete[] buffer1; + delete[] buffer2; + delete[](*p)->pBuffer; + + (*p)->pBuffer = new unsigned char[ss.str().size()]; + (*p)->size = ss.str().size(); + + memcpy((*p)->pBuffer, ss.str().c_str(), ss.str().size()); + dest->FastQueuePacket(&(*p)); + } + + ENCODE(OP_ZoneServerInfo) + { + SETUP_DIRECT_ENCODE(ZoneServerInfo_Struct, ZoneServerInfo_Struct); + + OUT_str(ip); + OUT(port); + + FINISH_ENCODE(); + } + + ENCODE(OP_ZoneSpawns) + { + //consume the packet + EQApplicationPacket *in = *p; + *p = nullptr; + + //store away the emu struct + unsigned char *__emu_buffer = in->pBuffer; + Spawn_Struct *emu = (Spawn_Struct *)__emu_buffer; + + //determine and verify length + int entrycount = in->size / sizeof(Spawn_Struct); + if (entrycount == 0 || (in->size % sizeof(Spawn_Struct)) != 0) { + _log(NET__STRUCTS, "Wrong size on outbound %s: Got %d, expected multiple of %d", opcodes->EmuToName(in->GetOpcode()), in->size, sizeof(Spawn_Struct)); + delete in; + return; + } + + //_log(NET__STRUCTS, "Spawn name is [%s]", emu->name); + + emu = (Spawn_Struct *)__emu_buffer; + + //_log(NET__STRUCTS, "Spawn packet size is %i, entries = %i", in->size, entrycount); + + char *Buffer = (char *)in->pBuffer, *BufferStart; + + int r; + int k; + for (r = 0; r < entrycount; r++, emu++) { + + int PacketSize = 206; + + PacketSize += strlen(emu->name); + PacketSize += strlen(emu->lastName); + + emu->title[0] = 0; + emu->suffix[0] = 0; + + if (strlen(emu->title)) + PacketSize += strlen(emu->title) + 1; + + if (strlen(emu->suffix)) + PacketSize += strlen(emu->suffix) + 1; + + bool ShowName = 1; + if (emu->bodytype >= 66) + { + emu->race = 127; + emu->bodytype = 11; + emu->gender = 0; + ShowName = 0; + } + + float SpawnSize = emu->size; + if (!((emu->NPC == 0) || (emu->race <= 12) || (emu->race == 128) || (emu->race == 130) || (emu->race == 330) || (emu->race == 522))) + { + PacketSize += 60; + + if (emu->size == 0) + { + emu->size = 6; + SpawnSize = 6; + } + } + else + PacketSize += 216; + + if (SpawnSize == 0) + { + SpawnSize = 3; + } + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_ZoneEntry, PacketSize); + Buffer = (char *)outapp->pBuffer; + BufferStart = Buffer; + VARSTRUCT_ENCODE_STRING(Buffer, emu->name); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->spawnId); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->level); + VARSTRUCT_ENCODE_TYPE(float, Buffer, SpawnSize - 0.7); // Eye Height? + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->NPC); + + structs::Spawn_Struct_Bitfields *Bitfields = (structs::Spawn_Struct_Bitfields*)Buffer; + + Bitfields->gender = emu->gender; + Bitfields->ispet = emu->is_pet; + Bitfields->afk = emu->afk; + Bitfields->anon = emu->anon; + Bitfields->gm = emu->gm; + Bitfields->sneak = 0; + Bitfields->lfg = emu->lfg; + Bitfields->invis = emu->invis; + Bitfields->linkdead = 0; + Bitfields->showhelm = emu->showhelm; + Bitfields->trader = 0; + Bitfields->targetable = 1; + Bitfields->targetable_with_hotkey = (emu->IsMercenary ? 0 : 1); + Bitfields->showname = ShowName; + + // Not currently found + // Bitfields->statue = 0; + // Bitfields->buyer = 0; + + Buffer += sizeof(structs::Spawn_Struct_Bitfields); + + uint8 OtherData = 0; + + if (strlen(emu->title)) + OtherData = OtherData | 16; + + if (strlen(emu->suffix)) + OtherData = OtherData | 32; + + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, OtherData); + + VARSTRUCT_ENCODE_TYPE(float, Buffer, -1); // unknown3 + VARSTRUCT_ENCODE_TYPE(float, Buffer, 0); // unknown4 + + // 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. + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 1); // This is a properties count field + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->bodytype); + + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->curHp); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->haircolor); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->beardcolor); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->eyecolor1); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->eyecolor2); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->hairstyle); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->beard); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->drakkin_heritage); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->drakkin_tattoo); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->drakkin_details); + + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->equip_chest2); // unknown8 + 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(float, Buffer, emu->size); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->face); + VARSTRUCT_ENCODE_TYPE(float, Buffer, emu->walkspeed); + VARSTRUCT_ENCODE_TYPE(float, Buffer, emu->runspeed); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->race); + + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // ShowEQ calls this 'Holding' + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->deity); + if (emu->NPC) + { + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0xFFFFFFFF); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0x00000000); + } + else + { + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->guildID); + + /* Translate older ranks to new values */ + switch (emu->guildrank) { + case 0: { VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 5); break; } // GUILD_MEMBER 0 + case 1: { VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 3); break; } // GUILD_OFFICER 1 + case 2: { VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 1); break; } // GUILD_LEADER 2 + default: { VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->guildrank); break; } // + } + } + + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->class_); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // pvp + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->StandState); // standstate + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->light); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->flymode); + + VARSTRUCT_ENCODE_STRING(Buffer, emu->lastName); + + 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(uint32, Buffer, emu->petOwnerId); + + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // unknown13 + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // unknown14 - Stance 64 = normal 4 = aggressive 40 = stun/mezzed + 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 + + if ((emu->NPC == 0) || (emu->race <= 12) || (emu->race == 128) || (emu->race == 130) || (emu->race == 330) || (emu->race == 522)) + { + for (k = 0; k < 9; ++k) + { + { + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->colors[k].color); + } + } + + structs::EquipStruct *Equipment = (structs::EquipStruct *)Buffer; + + for (k = 0; k < 9; k++) { + Equipment[k].equip0 = emu->equipment[k]; + Equipment[k].equip1 = 0; + Equipment[k].equip2 = 0; + Equipment[k].equip3 = 0; + Equipment[k].itemId = 0; + } + + Buffer += (sizeof(structs::EquipStruct) * 9); + } + else + { + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); + + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->equipment[MaterialPrimary]); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); + + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->equipment[MaterialSecondary]); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); + } + + structs::Spawn_Struct_Position *Position = (structs::Spawn_Struct_Position*)Buffer; + + Position->y = emu->y; + Position->deltaZ = emu->deltaZ; + Position->deltaX = emu->deltaX; + Position->x = emu->x; + Position->heading = emu->heading; + Position->deltaHeading = emu->deltaHeading; + Position->z = emu->z; + Position->animation = emu->animation; + Position->deltaY = emu->deltaY; + + Buffer += sizeof(structs::Spawn_Struct_Position); + + if (strlen(emu->title)) + { + VARSTRUCT_ENCODE_STRING(Buffer, emu->title); + } + + if (strlen(emu->suffix)) + { + VARSTRUCT_ENCODE_STRING(Buffer, emu->suffix); + } + + 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); + // 29 zero bytes follow + Buffer += 29; + if (Buffer != (BufferStart + PacketSize)) + { + _log(NET__ERROR, "SPAWN ENCODE LOGIC PROBLEM: Buffer pointer is now %i from end", Buffer - (BufferStart + PacketSize)); + } + //_log(NET__ERROR, "Sending zone spawn for %s packet is %i bytes", emu->name, outapp->size); + //_hex(NET__ERROR, outapp->pBuffer, outapp->size); + dest->FastQueuePacket(&outapp, ack_req); + } + + delete in; + } + +// DECODE methods + DECODE(OP_AdventureMerchantSell) + { + DECODE_LENGTH_EXACT(structs::Adventure_Sell_Struct); + SETUP_DIRECT_DECODE(Adventure_Sell_Struct, structs::Adventure_Sell_Struct); + + IN(npcid); + emu->slot = RoF2ToServerMainInvSlot(eq->slot); + IN(charges); + IN(sell_price); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_AltCurrencySell) + { + DECODE_LENGTH_EXACT(structs::AltCurrencySellItem_Struct); + SETUP_DIRECT_DECODE(AltCurrencySellItem_Struct, structs::AltCurrencySellItem_Struct); + + IN(merchant_entity_id); + emu->slot_id = RoF2ToServerSlot(eq->slot_id); + IN(charges); + IN(cost); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_AltCurrencySellSelection) + { + DECODE_LENGTH_EXACT(structs::AltCurrencySelectItem_Struct); + SETUP_DIRECT_DECODE(AltCurrencySelectItem_Struct, structs::AltCurrencySelectItem_Struct); + + IN(merchant_entity_id); + emu->slot_id = RoF2ToServerSlot(eq->slot_id); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_ApplyPoison) + { + DECODE_LENGTH_EXACT(structs::ApplyPoison_Struct); + SETUP_DIRECT_DECODE(ApplyPoison_Struct, structs::ApplyPoison_Struct); + + emu->inventorySlot = RoF2ToServerMainInvSlot(eq->inventorySlot); + IN(success); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_AugmentInfo) + { + DECODE_LENGTH_EXACT(structs::AugmentInfo_Struct); + SETUP_DIRECT_DECODE(AugmentInfo_Struct, structs::AugmentInfo_Struct); + + IN(itemid); + IN(window); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_AugmentItem) + { + DECODE_LENGTH_EXACT(structs::AugmentItem_Struct); + SETUP_DIRECT_DECODE(AugmentItem_Struct, structs::AugmentItem_Struct); + + emu->container_slot = RoF2ToServerSlot(eq->container_slot); + emu->augment_slot = RoF2ToServerSlot(eq->augment_slot); + emu->container_index = eq->container_index; + emu->augment_index = eq->augment_index; + emu->dest_inst_id = eq->dest_inst_id; + emu->augment_action = eq->augment_action; + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_BazaarSearch) + { + char *Buffer = (char *)__packet->pBuffer; + + uint8 SubAction = VARSTRUCT_DECODE_TYPE(uint8, Buffer); + + if ((SubAction != BazaarInspectItem) || (__packet->size != sizeof(structs::NewBazaarInspect_Struct))) + return; + + SETUP_DIRECT_DECODE(NewBazaarInspect_Struct, structs::NewBazaarInspect_Struct); + MEMSET_IN(structs::NewBazaarInspect_Struct); + + IN(Beginning.Action); + memcpy(emu->Name, eq->Name, sizeof(emu->Name)); + IN(SerialNumber); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_BlockedBuffs) + { + DECODE_LENGTH_EXACT(structs::BlockedBuffs_Struct); + SETUP_DIRECT_DECODE(BlockedBuffs_Struct, structs::BlockedBuffs_Struct); + + for (uint32 i = 0; i < BLOCKED_BUFF_COUNT; ++i) + emu->SpellID[i] = eq->SpellID[i]; + + IN(Count); + IN(Pet); + IN(Initialise); + IN(Flags); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_Buff) + { + DECODE_LENGTH_EXACT(structs::SpellBuffFade_Struct_Live); + SETUP_DIRECT_DECODE(SpellBuffFade_Struct, structs::SpellBuffFade_Struct_Live); + + IN(entityid); + //IN(slot); + IN(level); + IN(effect); + IN(spellid); + IN(duration); + IN(slotid); + IN(bufffade); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_BuffRemoveRequest) + { + // This is to cater for the fact that short buff box buffs start at 30 as opposed to 25 in prior clients. + // + DECODE_LENGTH_EXACT(structs::BuffRemoveRequest_Struct); + SETUP_DIRECT_DECODE(BuffRemoveRequest_Struct, structs::BuffRemoveRequest_Struct); + + emu->SlotID = (eq->SlotID < 42) ? eq->SlotID : (eq->SlotID - 17); + + IN(EntityID); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_CastSpell) + { + DECODE_LENGTH_EXACT(structs::CastSpell_Struct); + SETUP_DIRECT_DECODE(CastSpell_Struct, structs::CastSpell_Struct); + + if (eq->slot == 13) + emu->slot = 10; + else + IN(slot); + + IN(spell_id); + emu->inventoryslot = RoF2ToServerSlot(eq->inventoryslot); + //IN(inventoryslot); + IN(target_id); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_ChannelMessage) + { + unsigned char *__eq_buffer = __packet->pBuffer; + + char *InBuffer = (char *)__eq_buffer; + + char Sender[64]; + char Target[64]; + + VARSTRUCT_DECODE_STRING(Sender, InBuffer); + VARSTRUCT_DECODE_STRING(Target, InBuffer); + + InBuffer += 4; + + uint32 Language = VARSTRUCT_DECODE_TYPE(uint32, InBuffer); + uint32 Channel = VARSTRUCT_DECODE_TYPE(uint32, InBuffer); + + InBuffer += 5; + + uint32 Skill = VARSTRUCT_DECODE_TYPE(uint32, InBuffer); + + __packet->size = sizeof(ChannelMessage_Struct)+strlen(InBuffer) + 1; + __packet->pBuffer = new unsigned char[__packet->size]; + ChannelMessage_Struct *emu = (ChannelMessage_Struct *)__packet->pBuffer; + + strn0cpy(emu->targetname, Target, sizeof(emu->targetname)); + strn0cpy(emu->sender, Target, sizeof(emu->sender)); + emu->language = Language; + emu->chan_num = Channel; + emu->skill_in_language = Skill; + strcpy(emu->message, InBuffer); + + delete[] __eq_buffer; + } + + DECODE(OP_CharacterCreate) + { + DECODE_LENGTH_EXACT(structs::CharCreate_Struct); + SETUP_DIRECT_DECODE(CharCreate_Struct, structs::CharCreate_Struct); + + IN(gender); + IN(race); + IN(class_); + IN(deity); + IN(start_zone); + IN(haircolor); + IN(beard); + IN(beardcolor); + IN(hairstyle); + IN(face); + IN(eyecolor1); + IN(eyecolor2); + IN(drakkin_heritage); + IN(drakkin_tattoo); + IN(drakkin_details); + IN(STR); + IN(STA); + IN(AGI); + IN(DEX); + IN(WIS); + IN(INT); + IN(CHA); + IN(tutorial); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_ClientUpdate) + { + // for some odd reason, there is an extra byte on the end of this on occasion.. + DECODE_LENGTH_ATLEAST(structs::PlayerPositionUpdateClient_Struct); + SETUP_DIRECT_DECODE(PlayerPositionUpdateClient_Struct, structs::PlayerPositionUpdateClient_Struct); + + IN(spawn_id); + IN(sequence); + IN(x_pos); + IN(y_pos); + IN(z_pos); + IN(heading); + IN(delta_x); + IN(delta_y); + IN(delta_z); + IN(delta_heading); + IN(animation); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_Consider) + { + DECODE_LENGTH_EXACT(structs::Consider_Struct); + SETUP_DIRECT_DECODE(Consider_Struct, structs::Consider_Struct); + + IN(playerid); + IN(targetid); + IN(faction); + IN(level); + //emu->cur_hp = 1; + //emu->max_hp = 2; + //emu->pvpcon = 0; + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_ConsiderCorpse) { DECODE_FORWARD(OP_Consider); } + + DECODE(OP_Consume) + { + DECODE_LENGTH_EXACT(structs::Consume_Struct); + SETUP_DIRECT_DECODE(Consume_Struct, structs::Consume_Struct); + + emu->slot = RoF2ToServerSlot(eq->slot); + IN(auto_consumed); + IN(type); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_Damage) + { + DECODE_LENGTH_EXACT(structs::CombatDamage_Struct); + SETUP_DIRECT_DECODE(CombatDamage_Struct, structs::CombatDamage_Struct); + + IN(target); + IN(source); + IN(type); + IN(spellid); + IN(damage); + emu->sequence = eq->sequence; + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_DeleteItem) + { + DECODE_LENGTH_EXACT(structs::DeleteItem_Struct); + SETUP_DIRECT_DECODE(DeleteItem_Struct, structs::DeleteItem_Struct); + + emu->from_slot = RoF2ToServerSlot(eq->from_slot); + emu->to_slot = RoF2ToServerSlot(eq->to_slot); + IN(number_in_stack); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_EnvDamage) + { + DECODE_LENGTH_EXACT(structs::EnvDamage2_Struct); + SETUP_DIRECT_DECODE(EnvDamage2_Struct, structs::EnvDamage2_Struct); + + IN(id); + IN(damage); + IN(dmgtype); + emu->constant = 0xFFFF; + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_FaceChange) + { + DECODE_LENGTH_EXACT(structs::FaceChange_Struct); + SETUP_DIRECT_DECODE(FaceChange_Struct, structs::FaceChange_Struct); + + IN(haircolor); + IN(beardcolor); + IN(eyecolor1); + IN(eyecolor2); + IN(hairstyle); + IN(beard); + IN(face); + IN(drakkin_heritage); + IN(drakkin_tattoo); + IN(drakkin_details); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_FindPersonRequest) + { + DECODE_LENGTH_EXACT(structs::FindPersonRequest_Struct); + SETUP_DIRECT_DECODE(FindPersonRequest_Struct, structs::FindPersonRequest_Struct); + + IN(npc_id); + IN(client_pos.x); + IN(client_pos.y); + IN(client_pos.z); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_GMLastName) + { + DECODE_LENGTH_EXACT(structs::GMLastName_Struct); + SETUP_DIRECT_DECODE(GMLastName_Struct, structs::GMLastName_Struct); + + memcpy(emu->name, eq->name, sizeof(emu->name)); + memcpy(emu->gmname, eq->gmname, sizeof(emu->gmname)); + memcpy(emu->lastname, eq->lastname, sizeof(emu->lastname)); + for (int i = 0; i<4; i++) + { + emu->unknown[i] = eq->unknown[i]; + } + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_GroupCancelInvite) + { + DECODE_LENGTH_EXACT(structs::GroupCancel_Struct); + SETUP_DIRECT_DECODE(GroupCancel_Struct, structs::GroupCancel_Struct); + + memcpy(emu->name1, eq->name1, sizeof(emu->name1)); + memcpy(emu->name2, eq->name2, sizeof(emu->name2)); + IN(toggle); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_GroupDisband) + { + //EQApplicationPacket *in = __packet; + //_log(NET__ERROR, "Received incoming OP_Disband"); + //_hex(NET__ERROR, in->pBuffer, in->size); + DECODE_LENGTH_EXACT(structs::GroupGeneric_Struct); + SETUP_DIRECT_DECODE(GroupGeneric_Struct, structs::GroupGeneric_Struct); + + memcpy(emu->name1, eq->name1, sizeof(emu->name1)); + memcpy(emu->name2, eq->name2, sizeof(emu->name2)); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_GroupFollow) + { + //EQApplicationPacket *in = __packet; + //_log(NET__ERROR, "Received incoming OP_GroupFollow"); + //_hex(NET__ERROR, in->pBuffer, in->size); + DECODE_LENGTH_EXACT(structs::GroupFollow_Struct); + SETUP_DIRECT_DECODE(GroupGeneric_Struct, structs::GroupFollow_Struct); + + memcpy(emu->name1, eq->name1, sizeof(emu->name1)); + memcpy(emu->name2, eq->name2, sizeof(emu->name2)); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_GroupFollow2) + { + //EQApplicationPacket *in = __packet; + //_log(NET__ERROR, "Received incoming OP_GroupFollow2"); + //_hex(NET__ERROR, in->pBuffer, in->size); + DECODE_LENGTH_EXACT(structs::GroupFollow_Struct); + SETUP_DIRECT_DECODE(GroupGeneric_Struct, structs::GroupFollow_Struct); + + memcpy(emu->name1, eq->name1, sizeof(emu->name1)); + memcpy(emu->name2, eq->name2, sizeof(emu->name2)); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_GroupInvite) + { + //EQApplicationPacket *in = __packet; + //_log(NET__ERROR, "Received incoming OP_GroupInvite"); + //_hex(NET__ERROR, in->pBuffer, in->size); + DECODE_LENGTH_EXACT(structs::GroupInvite_Struct); + SETUP_DIRECT_DECODE(GroupGeneric_Struct, structs::GroupInvite_Struct); + + memcpy(emu->name1, eq->invitee_name, sizeof(emu->name1)); + memcpy(emu->name2, eq->inviter_name, sizeof(emu->name2)); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_GroupInvite2) + { + //_log(NET__ERROR, "Received incoming OP_GroupInvite2. Forwarding"); + DECODE_FORWARD(OP_GroupInvite); + } + + DECODE(OP_GuildDemote) + { + DECODE_LENGTH_EXACT(structs::GuildDemoteStruct); + SETUP_DIRECT_DECODE(GuildDemoteStruct, structs::GuildDemoteStruct); + + strn0cpy(emu->target, eq->target, sizeof(emu->target)); + strn0cpy(emu->name, eq->name, sizeof(emu->name)); + // IN(rank); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_GuildRemove) + { + DECODE_LENGTH_EXACT(structs::GuildCommand_Struct); + SETUP_DIRECT_DECODE(GuildCommand_Struct, structs::GuildCommand_Struct); + + strn0cpy(emu->othername, eq->othername, sizeof(emu->othername)); + strn0cpy(emu->myname, eq->myname, sizeof(emu->myname)); + IN(guildeqid); + IN(officer); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_GuildStatus) + { + DECODE_LENGTH_EXACT(structs::GuildStatus_Struct); + SETUP_DIRECT_DECODE(GuildStatus_Struct, structs::GuildStatus_Struct); + + memcpy(emu->Name, eq->Name, sizeof(emu->Name)); + + FINISH_DIRECT_DECODE(); + } + + /*DECODE(OP_InspectAnswer) + { + DECODE_LENGTH_EXACT(structs::InspectResponse_Struct); + SETUP_DIRECT_DECODE(InspectResponse_Struct, structs::InspectResponse_Struct); + + IN(TargetID); + IN(playerid); + + int r; + for (r = 0; r < 21; r++) { + strn0cpy(emu->itemnames[r], eq->itemnames[r], sizeof(emu->itemnames[r])); + } + // Swap last 2 slots for Arrow and Power Source + strn0cpy(emu->itemnames[22], eq->itemnames[21], sizeof(emu->itemnames[22])); + strn0cpy(emu->itemnames[21], eq->unknown_zero, sizeof(emu->itemnames[21])); + strn0cpy(emu->unknown_zero, eq->unknown_zero, sizeof(emu->unknown_zero)); + + int k; + for (k = 0; k < 21; k++) { + IN(itemicons[k]); + } + // Swap last 2 slots for Arrow and Power Source + emu->itemicons[22] = eq->itemicons[21]; + emu->itemicons[21] = eq->unknown_zero2; + emu->unknown_zero2 = eq->unknown_zero2; + strn0cpy(emu->text, eq->text, sizeof(emu->text)); + //emu->unknown1772 = 0; + + FINISH_DIRECT_DECODE(); + }*/ + + DECODE(OP_InspectRequest) + { + DECODE_LENGTH_EXACT(structs::Inspect_Struct); + SETUP_DIRECT_DECODE(Inspect_Struct, structs::Inspect_Struct); + + IN(TargetID); + IN(PlayerID); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_ItemLinkClick) + { + DECODE_LENGTH_EXACT(structs::ItemViewRequest_Struct); + SETUP_DIRECT_DECODE(ItemViewRequest_Struct, structs::ItemViewRequest_Struct); + MEMSET_IN(ItemViewRequest_Struct); + + IN(item_id); + int r; + for (r = 0; r < 5; r++) { + IN(augments[r]); + } + // Max Augs is now 6, but no code to support that many yet + IN(link_hash); + IN(icon); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_ItemVerifyRequest) + { + DECODE_LENGTH_EXACT(structs::ItemVerifyRequest_Struct); + SETUP_DIRECT_DECODE(ItemVerifyRequest_Struct, structs::ItemVerifyRequest_Struct); + + emu->slot = RoF2ToServerSlot(eq->slot); + IN(target); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_LoadSpellSet) + { + DECODE_LENGTH_EXACT(structs::LoadSpellSet_Struct); + SETUP_DIRECT_DECODE(LoadSpellSet_Struct, structs::LoadSpellSet_Struct); + + for (unsigned int i = 0; i < MAX_PP_MEMSPELL; ++i) + { + if (eq->spell[i] == 0) + emu->spell[i] = 0xFFFFFFFF; + else + emu->spell[i] = eq->spell[i]; + } + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_LootItem) + { + DECODE_LENGTH_EXACT(structs::LootingItem_Struct); + SETUP_DIRECT_DECODE(LootingItem_Struct, structs::LootingItem_Struct); + + IN(lootee); + IN(looter); + emu->slot_id = RoF2ToServerCorpseSlot(eq->slot_id); + IN(auto_loot); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_MoveItem) + { + DECODE_LENGTH_EXACT(structs::MoveItem_Struct); + SETUP_DIRECT_DECODE(MoveItem_Struct, structs::MoveItem_Struct); + + //_log(NET__ERROR, "Moved item from %u to %u", eq->from_slot.MainSlot, eq->to_slot.MainSlot); + _log(NET__ERROR, "MoveItem SlotType from %i to %i, MainSlot from %i to %i, SubSlot from %i to %i, AugSlot from %i to %i, Unknown01 from %i to %i, Number %u", eq->from_slot.SlotType, eq->to_slot.SlotType, eq->from_slot.MainSlot, eq->to_slot.MainSlot, eq->from_slot.SubSlot, eq->to_slot.SubSlot, eq->from_slot.AugSlot, eq->to_slot.AugSlot, eq->from_slot.Unknown01, eq->to_slot.Unknown01, eq->number_in_stack); + emu->from_slot = RoF2ToServerSlot(eq->from_slot); + emu->to_slot = RoF2ToServerSlot(eq->to_slot); + IN(number_in_stack); + + _hex(NET__ERROR, eq, sizeof(structs::MoveItem_Struct)); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_PetCommands) + { + DECODE_LENGTH_EXACT(structs::PetCommand_Struct); + SETUP_DIRECT_DECODE(PetCommand_Struct, structs::PetCommand_Struct); + + switch (eq->command) + { + case 0x00: + emu->command = 0x04; // Health + break; + case 0x01: + emu->command = 0x10; // Leader + break; + case 0x02: + emu->command = 0x07; // Attack + break; + case 0x04: + emu->command = 0x08; // Follow + break; + case 0x05: + emu->command = 0x05; // Guard + break; + case 0x06: + emu->command = 0x09; // Sit. Needs work. This appears to be a toggle between Sit/Stand now. + break; + case 0x0c: + emu->command = 0x0b; // Taunt + break; + case 0x0f: + emu->command = 0x0c; // Hold + break; + case 0x10: + emu->command = 0x1b; // Hold on + break; + case 0x11: + emu->command = 0x1c; // Hold off + break; + case 0x1c: + emu->command = 0x01; // Back + break; + case 0x1d: + emu->command = 0x02; // Leave/Go Away + break; + default: + emu->command = eq->command; + } + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_RaidInvite) + { + DECODE_LENGTH_ATLEAST(structs::RaidGeneral_Struct); + + // This is a switch on the RaidGeneral action + switch (*(uint32 *)__packet->pBuffer) { + case 35: { // raidMOTD + // we don't have a nice macro for this + structs::RaidMOTD_Struct *__eq_buffer = (structs::RaidMOTD_Struct *)__packet->pBuffer; + __eq_buffer->motd[1023] = '\0'; + size_t motd_size = strlen(__eq_buffer->motd) + 1; + __packet->size = sizeof(RaidMOTD_Struct) + motd_size; + __packet->pBuffer = new unsigned char[__packet->size]; + RaidMOTD_Struct *emu = (RaidMOTD_Struct *)__packet->pBuffer; + structs::RaidMOTD_Struct *eq = (structs::RaidMOTD_Struct *)__eq_buffer; + strn0cpy(emu->general.player_name, eq->general.player_name, 64); + strn0cpy(emu->motd, eq->motd, motd_size); + IN(general.action); + IN(general.parameter); + FINISH_DIRECT_DECODE(); + break; + } + case 36: { // raidPlayerNote unhandled + break; + } + default: { + DECODE_LENGTH_EXACT(structs::RaidGeneral_Struct); + SETUP_DIRECT_DECODE(RaidGeneral_Struct, structs::RaidGeneral_Struct); + strn0cpy(emu->leader_name, eq->leader_name, 64); + strn0cpy(emu->player_name, eq->player_name, 64); + IN(action); + IN(parameter); + FINISH_DIRECT_DECODE(); + break; + } + } + } + + DECODE(OP_ReadBook) + { + DECODE_LENGTH_EXACT(structs::BookRequest_Struct); + SETUP_DIRECT_DECODE(BookRequest_Struct, structs::BookRequest_Struct); + + IN(type); + IN(invslot); + emu->window = (uint8)eq->window; + strn0cpy(emu->txtfile, eq->txtfile, sizeof(emu->txtfile)); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_RecipeAutoCombine) + { + DECODE_LENGTH_EXACT(structs::RecipeAutoCombine_Struct); + SETUP_DIRECT_DECODE(RecipeAutoCombine_Struct, structs::RecipeAutoCombine_Struct); + + IN(object_type); + IN(some_id); + emu->unknown1 = RoF2ToServerSlot(eq->container_slot); + IN(recipe_id); + IN(reply_code); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_RemoveBlockedBuffs) { DECODE_FORWARD(OP_BlockedBuffs); } + + DECODE(OP_RezzAnswer) + { + DECODE_LENGTH_EXACT(structs::Resurrect_Struct); + SETUP_DIRECT_DECODE(Resurrect_Struct, structs::Resurrect_Struct); + + IN(zone_id); + IN(instance_id); + IN(y); + IN(x); + IN(z); + memcpy(emu->your_name, eq->your_name, sizeof(emu->your_name)); + memcpy(emu->rezzer_name, eq->rezzer_name, sizeof(emu->rezzer_name)); + IN(spellid); + memcpy(emu->corpse_name, eq->corpse_name, sizeof(emu->corpse_name)); + IN(action); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_Save) + { + DECODE_LENGTH_EXACT(structs::Save_Struct); + SETUP_DIRECT_DECODE(Save_Struct, structs::Save_Struct); + + memcpy(emu->unknown00, eq->unknown00, sizeof(emu->unknown00)); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_SetServerFilter) + { + DECODE_LENGTH_EXACT(structs::SetServerFilter_Struct); + SETUP_DIRECT_DECODE(SetServerFilter_Struct, structs::SetServerFilter_Struct); + + int r; + for (r = 0; r < 29; r++) { + // Size 40 in RoF2 + IN(filters[r]); + } + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_ShopPlayerBuy) + { + DECODE_LENGTH_EXACT(structs::Merchant_Sell_Struct); + SETUP_DIRECT_DECODE(Merchant_Sell_Struct, structs::Merchant_Sell_Struct); + + IN(npcid); + IN(playerid); + IN(itemslot); + IN(quantity); + IN(price); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_ShopPlayerSell) + { + DECODE_LENGTH_EXACT(structs::Merchant_Purchase_Struct); + SETUP_DIRECT_DECODE(Merchant_Purchase_Struct, structs::Merchant_Purchase_Struct); + + IN(npcid); + emu->itemslot = RoF2ToServerMainInvSlot(eq->itemslot); + //IN(itemslot); + IN(quantity); + IN(price); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_ShopRequest) + { + DECODE_LENGTH_EXACT(structs::Merchant_Click_Struct); + SETUP_DIRECT_DECODE(Merchant_Click_Struct, structs::Merchant_Click_Struct); + + IN(npcid); + IN(playerid); + IN(command); + IN(rate); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_Trader) + { + uint32 psize = __packet->size; + if (psize == sizeof(structs::ClickTrader_Struct)) + { + DECODE_LENGTH_EXACT(structs::ClickTrader_Struct); + SETUP_DIRECT_DECODE(ClickTrader_Struct, structs::ClickTrader_Struct); + MEMSET_IN(ClickTrader_Struct); + + emu->Code = eq->Code; + // Live actually has 200 items now, but 80 is the most our internal struct supports + for (uint32 i = 0; i < 80; i++) + { + emu->SerialNumber[i] = 0; // eq->SerialNumber[i]; + emu->ItemCost[i] = eq->ItemCost[i]; + } + + FINISH_DIRECT_DECODE(); + } + else if (psize == sizeof(structs::Trader_ShowItems_Struct)) + { + DECODE_LENGTH_EXACT(structs::Trader_ShowItems_Struct); + SETUP_DIRECT_DECODE(Trader_ShowItems_Struct, structs::Trader_ShowItems_Struct); + MEMSET_IN(Trader_ShowItems_Struct); + + emu->Code = eq->Code; + emu->TraderID = eq->TraderID; + + FINISH_DIRECT_DECODE(); + } + else if (psize == sizeof(structs::TraderStatus_Struct)) + { + DECODE_LENGTH_EXACT(structs::TraderStatus_Struct); + SETUP_DIRECT_DECODE(TraderStatus_Struct, structs::TraderStatus_Struct); + MEMSET_IN(TraderStatus_Struct); + + emu->Code = eq->Code; + + FINISH_DIRECT_DECODE(); + } + } + + DECODE(OP_TraderBuy) + { + DECODE_LENGTH_EXACT(structs::TraderBuy_Struct); + SETUP_DIRECT_DECODE(TraderBuy_Struct, structs::TraderBuy_Struct); + MEMSET_IN(TraderBuy_Struct); + + IN(Action); + IN(Price); + IN(TraderID); + memcpy(emu->ItemName, eq->ItemName, sizeof(emu->ItemName)); + IN(ItemID); + IN(Quantity); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_TradeSkillCombine) + { + DECODE_LENGTH_EXACT(structs::NewCombine_Struct); + SETUP_DIRECT_DECODE(NewCombine_Struct, structs::NewCombine_Struct); + + int16 slot_id = RoF2ToServerSlot(eq->container_slot); + if (slot_id == 4000) { + slot_id = legacy::SLOT_TRADESKILL; // 1000 + } + emu->container_slot = slot_id; + emu->guildtribute_slot = RoF2ToServerSlot(eq->guildtribute_slot); // this should only return INVALID_INDEX until implemented -U + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_TributeItem) + { + DECODE_LENGTH_EXACT(structs::TributeItem_Struct); + SETUP_DIRECT_DECODE(TributeItem_Struct, structs::TributeItem_Struct); + + emu->slot = RoF2ToServerSlot(eq->slot); + IN(quantity); + IN(tribute_master_id); + IN(tribute_points); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_WhoAllRequest) + { + DECODE_LENGTH_EXACT(structs::Who_All_Struct); + SETUP_DIRECT_DECODE(Who_All_Struct, structs::Who_All_Struct); + + memcpy(emu->whom, eq->whom, sizeof(emu->whom)); + IN(wrace); + IN(wclass); + IN(lvllow); + IN(lvlhigh); + IN(gmlookup); + IN(guildid); + IN(type); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_ZoneChange) + { + DECODE_LENGTH_EXACT(structs::ZoneChange_Struct); + SETUP_DIRECT_DECODE(ZoneChange_Struct, structs::ZoneChange_Struct); + + memcpy(emu->char_name, eq->char_name, sizeof(emu->char_name)); + IN(zoneID); + IN(instanceID); + IN(y); + IN(x); + IN(z) + IN(zone_reason); + IN(success); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_ZoneEntry) + { + DECODE_LENGTH_EXACT(structs::ClientZoneEntry_Struct); + SETUP_DIRECT_DECODE(ClientZoneEntry_Struct, structs::ClientZoneEntry_Struct); + + memcpy(emu->char_name, eq->char_name, sizeof(emu->char_name)); + + FINISH_DIRECT_DECODE(); + } + +// file scope helper methods + uint32 NextItemInstSerialNumber = 1; + uint32 MaxInstances = 2000000000; + + static inline int32 GetNextItemInstSerialNumber() + { + if (NextItemInstSerialNumber >= MaxInstances) + NextItemInstSerialNumber = 1; + else + NextItemInstSerialNumber++; + + return NextItemInstSerialNumber; + } + + char* SerializeItem(const ItemInst *inst, int16 slot_id_in, uint32 *length, uint8 depth) + { + int ornamentationAugtype = RuleI(Character, OrnamentationAugmentType); + uint8 null_term = 0; + bool stackable = inst->IsStackable(); + uint32 merchant_slot = inst->GetMerchantSlot(); + uint32 charges = inst->GetCharges(); + if (!stackable && charges > 254) + charges = 0xFFFFFFFF; + + std::stringstream ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary); + + const Item_Struct *item = inst->GetUnscaledItem(); + //_log(NET__ERROR, "Serialize called for: %s", item->Name); + + RoF2::structs::ItemSerializationHeader hdr; + + //sprintf(hdr.unknown000, "06e0002Y1W00"); + + snprintf(hdr.unknown000, sizeof(hdr.unknown000), "%012d", item->ID); + + hdr.stacksize = stackable ? charges : 1; + hdr.unknown004 = 0; + + structs::ItemSlotStruct slot_id = ServerToRoF2Slot(slot_id_in); + + hdr.slot_type = (merchant_slot == 0) ? slot_id.SlotType : 9; // 9 is merchant 20 is reclaim items? + hdr.main_slot = (merchant_slot == 0) ? slot_id.MainSlot : merchant_slot; + hdr.sub_slot = (merchant_slot == 0) ? slot_id.SubSlot : 0xffff; + hdr.unknown013 = (merchant_slot == 0) ? slot_id.AugSlot : 0xffff; + //hdr.unknown013 = 0xffff; + hdr.price = inst->GetPrice(); + hdr.merchant_slot = (merchant_slot == 0) ? 1 : inst->GetMerchantCount(); + //hdr.merchant_slot = (merchant_slot == 0) ? 1 : 0xffffffff; + hdr.scaled_value = inst->IsScaling() ? inst->GetExp() / 100 : 0; + hdr.instance_id = (merchant_slot == 0) ? inst->GetSerialNumber() : merchant_slot; + hdr.unknown028 = 0; + hdr.last_cast_time = ((item->RecastDelay > 1) ? 1212693140 : 0); + hdr.charges = (stackable ? (item->MaxCharges ? 1 : 0) : charges); + hdr.inst_nodrop = inst->IsInstNoDrop() ? 1 : 0; + hdr.unknown044 = 0; + hdr.unknown048 = 0; + hdr.unknown052 = 0; + hdr.isEvolving = item->EvolvingLevel > 0 ? 1 : 0; + ss.write((const char*)&hdr, sizeof(RoF2::structs::ItemSerializationHeader)); + + if (item->EvolvingLevel > 0) { + RoF2::structs::EvolvingItem evotop; + evotop.unknown001 = 0; + evotop.unknown002 = 0; + evotop.unknown003 = 0; + evotop.unknown004 = 0; + evotop.evoLevel = item->EvolvingLevel; + evotop.progress = 95.512; + evotop.Activated = 1; + evotop.evomaxlevel = 7; + ss.write((const char*)&evotop, sizeof(RoF2::structs::EvolvingItem)); + } + //ORNAMENT IDFILE / ICON + uint16 ornaIcon = 0; + if (inst->GetOrnamentationAug(ornamentationAugtype)) { + const Item_Struct *aug_weap = inst->GetOrnamentationAug(ornamentationAugtype)->GetItem(); + //Mainhand + ss.write(aug_weap->IDFile, strlen(aug_weap->IDFile)); + ss.write((const char*)&null_term, sizeof(uint8)); + //Offhand + ss.write(aug_weap->IDFile, strlen(aug_weap->IDFile)); + ss.write((const char*)&null_term, sizeof(uint8)); + //Icon + ornaIcon = aug_weap->Icon; + } + else if (inst->GetOrnamentationIDFile() && inst->GetOrnamentationIcon()) { + char tmp[30]; memset(tmp, 0x0, 30); sprintf(tmp, "IT%d", inst->GetOrnamentationIDFile()); + //Mainhand + ss.write(tmp, strlen(tmp)); + ss.write((const char*)&null_term, sizeof(uint8)); + //Offhand + ss.write(tmp, strlen(tmp)); + ss.write((const char*)&null_term, sizeof(uint8)); + ornaIcon = inst->GetOrnamentationIcon(); + } + else { + ss.write((const char*)&null_term, sizeof(uint8)); //no mh + ss.write((const char*)&null_term, sizeof(uint8));//no of + } + + RoF2::structs::ItemSerializationHeaderFinish hdrf; + hdrf.ornamentIcon = ornaIcon; + hdrf.unknown061 = 0; + hdrf.unknown062 = 0; + hdrf.unknowna1 = 0xffffffff; + hdrf.unknowna2 = 0; + hdrf.unknown063 = 0; + hdrf.unknowna3 = 0; + hdrf.unknowna4 = 0xffffffff; + hdrf.unknowna5 = 0; + hdrf.ItemClass = item->ItemClass; + ss.write((const char*)&hdrf, sizeof(RoF2::structs::ItemSerializationHeaderFinish)); + + if (strlen(item->Name) > 0) + { + ss.write(item->Name, strlen(item->Name)); + ss.write((const char*)&null_term, sizeof(uint8)); + } + else + { + ss.write((const char*)&null_term, sizeof(uint8)); + } + + if (strlen(item->Lore) > 0) + { + ss.write(item->Lore, strlen(item->Lore)); + ss.write((const char*)&null_term, sizeof(uint8)); + } + else + { + ss.write((const char*)&null_term, sizeof(uint8)); + } + + if (strlen(item->IDFile) > 0) + { + ss.write(item->IDFile, strlen(item->IDFile)); + ss.write((const char*)&null_term, sizeof(uint8)); + } + else + { + ss.write((const char*)&null_term, sizeof(uint8)); + } + + ss.write((const char*)&null_term, sizeof(uint8)); + //_log(NET__ERROR, "ItemBody struct is %i bytes", sizeof(RoF2::structs::ItemBodyStruct)); + RoF2::structs::ItemBodyStruct ibs; + memset(&ibs, 0, sizeof(RoF2::structs::ItemBodyStruct)); + + ibs.id = item->ID; + ibs.weight = item->Weight; + ibs.norent = item->NoRent; + ibs.nodrop = item->NoDrop; + ibs.attune = item->Attuneable; + ibs.size = item->Size; + ibs.slots = SwapBits21and22(item->Slots); + ibs.price = item->Price; + ibs.icon = item->Icon; + ibs.unknown1 = 1; + ibs.unknown2 = 1; + ibs.BenefitFlag = item->BenefitFlag; + ibs.tradeskills = item->Tradeskills; + ibs.CR = item->CR; + ibs.DR = item->DR; + ibs.PR = item->PR; + ibs.MR = item->MR; + ibs.FR = item->FR; + ibs.SVCorruption = item->SVCorruption; + ibs.AStr = item->AStr; + ibs.ASta = item->ASta; + ibs.AAgi = item->AAgi; + ibs.ADex = item->ADex; + ibs.ACha = item->ACha; + ibs.AInt = item->AInt; + ibs.AWis = item->AWis; + + ibs.HP = item->HP; + ibs.Mana = item->Mana; + ibs.Endur = item->Endur; + ibs.AC = item->AC; + ibs.regen = item->Regen; + ibs.mana_regen = item->ManaRegen; + ibs.end_regen = item->EnduranceRegen; + ibs.Classes = item->Classes; + ibs.Races = item->Races; + ibs.Deity = item->Deity; + ibs.SkillModValue = item->SkillModValue; + ibs.SkillModMax = 0xffffffff; + ibs.SkillModType = (int8)(item->SkillModType); + ibs.SkillModExtra = 0; + ibs.BaneDmgRace = item->BaneDmgRace; + ibs.BaneDmgBody = item->BaneDmgBody; + ibs.BaneDmgRaceAmt = item->BaneDmgRaceAmt; + ibs.BaneDmgAmt = item->BaneDmgAmt; + ibs.Magic = item->Magic; + ibs.CastTime_ = item->CastTime_; + ibs.ReqLevel = item->ReqLevel; + if (item->ReqLevel > 100) + ibs.ReqLevel = 100; + ibs.RecLevel = item->RecLevel; + if (item->RecLevel > 100) + ibs.RecLevel = 100; + ibs.RecSkill = item->RecSkill; + ibs.BardType = item->BardType; + ibs.BardValue = item->BardValue; + ibs.Light = item->Light; + ibs.Delay = item->Delay; + ibs.ElemDmgType = item->ElemDmgType; + ibs.ElemDmgAmt = item->ElemDmgAmt; + ibs.Range = item->Range; + ibs.Damage = item->Damage; + ibs.Color = item->Color; + ibs.Prestige = 0; + ibs.ItemType = item->ItemType; + ibs.Material = item->Material; + ibs.unknown7 = 0; + ibs.EliteMaterial = item->EliteMaterial; + ibs.unknown_RoF23 = 0; + ibs.unknown_RoF24 = 0; + ibs.SellRate = item->SellRate; + ibs.CombatEffects = item->CombatEffects; + ibs.Shielding = item->Shielding; + ibs.StunResist = item->StunResist; + ibs.StrikeThrough = item->StrikeThrough; + ibs.ExtraDmgSkill = item->ExtraDmgSkill; + ibs.ExtraDmgAmt = item->ExtraDmgAmt; + ibs.SpellShield = item->SpellShield; + ibs.Avoidance = item->Avoidance; + ibs.Accuracy = item->Accuracy; + ibs.CharmFileID = item->CharmFileID; + ibs.FactionAmt1 = item->FactionAmt1; + ibs.FactionMod1 = item->FactionMod1; + ibs.FactionAmt2 = item->FactionAmt2; + ibs.FactionMod2 = item->FactionMod2; + ibs.FactionAmt3 = item->FactionAmt3; + ibs.FactionMod3 = item->FactionMod3; + ibs.FactionAmt4 = item->FactionAmt4; + ibs.FactionMod4 = item->FactionMod4; + + ss.write((const char*)&ibs, sizeof(RoF2::structs::ItemBodyStruct)); + + //charm text + if (strlen(item->CharmFile) > 0) + { + ss.write((const char*)item->CharmFile, strlen(item->CharmFile)); + ss.write((const char*)&null_term, sizeof(uint8)); + } + else + { + ss.write((const char*)&null_term, sizeof(uint8)); + } + + //_log(NET__ERROR, "ItemBody secondary struct is %i bytes", sizeof(RoF2::structs::ItemSecondaryBodyStruct)); + RoF2::structs::ItemSecondaryBodyStruct isbs; + memset(&isbs, 0, sizeof(RoF2::structs::ItemSecondaryBodyStruct)); + + isbs.augtype = item->AugType; + isbs.augdistiller = 65535; + isbs.augrestrict = item->AugRestrict; + + for (int x = AUG_BEGIN; x < EmuConstants::ITEM_COMMON_SIZE; ++x) + { + isbs.augslots[x].type = item->AugSlotType[x]; + isbs.augslots[x].visible = item->AugSlotVisible[x]; + isbs.augslots[x].unknown = item->AugSlotUnk2[x]; + } + + // Increased to 6 max aug slots + isbs.augslots[5].type = 0; + isbs.augslots[5].visible = 1; + isbs.augslots[5].unknown = 0; + + isbs.ldonpoint_type = item->PointType; + isbs.ldontheme = item->LDoNTheme; + isbs.ldonprice = item->LDoNPrice; + isbs.ldonsellbackrate = item->LDoNSellBackRate; + isbs.ldonsold = item->LDoNSold; + + isbs.bagtype = item->BagType; + isbs.bagslots = item->BagSlots; + isbs.bagsize = item->BagSize; + isbs.wreduction = item->BagWR; + + isbs.book = item->Book; + isbs.booktype = item->BookType; + + ss.write((const char*)&isbs, sizeof(RoF2::structs::ItemSecondaryBodyStruct)); + + if (strlen(item->Filename) > 0) + { + ss.write((const char*)item->Filename, strlen(item->Filename)); + ss.write((const char*)&null_term, sizeof(uint8)); + } + else + { + ss.write((const char*)&null_term, sizeof(uint8)); + } + + //_log(NET__ERROR, "ItemBody tertiary struct is %i bytes", sizeof(RoF2::structs::ItemTertiaryBodyStruct)); + RoF2::structs::ItemTertiaryBodyStruct itbs; + memset(&itbs, 0, sizeof(RoF2::structs::ItemTertiaryBodyStruct)); + + itbs.loregroup = item->LoreGroup; + itbs.artifact = item->ArtifactFlag; + itbs.summonedflag = item->SummonedFlag; + itbs.favor = item->Favor; + itbs.fvnodrop = item->FVNoDrop; + itbs.dotshield = item->DotShielding; + itbs.atk = item->Attack; + itbs.haste = item->Haste; + itbs.damage_shield = item->DamageShield; + itbs.guildfavor = item->GuildFavor; + itbs.augdistil = item->AugDistiller; + itbs.unknown3 = 0xffffffff; + itbs.unknown4 = 0; + itbs.no_pet = item->NoPet; + itbs.unknown5 = 0; + + itbs.potion_belt_enabled = item->PotionBelt; + itbs.potion_belt_slots = item->PotionBeltSlots; + itbs.stacksize = stackable ? item->StackSize : 0; + itbs.no_transfer = item->NoTransfer; + itbs.expendablearrow = item->ExpendableArrow; + + itbs.unknown8 = 0; + itbs.unknown9 = 0; + itbs.unknown10 = 0; + itbs.unknown11 = 0; + itbs.unknown12 = 0; + itbs.unknown13 = 0; + itbs.unknown14 = 0; + + ss.write((const char*)&itbs, sizeof(RoF2::structs::ItemTertiaryBodyStruct)); + + // Effect Structures Broken down to allow variable length strings for effect names + int32 effect_unknown = 0; + + //_log(NET__ERROR, "ItemBody Click effect struct is %i bytes", sizeof(RoF2::structs::ClickEffectStruct)); + RoF2::structs::ClickEffectStruct ices; + memset(&ices, 0, sizeof(RoF2::structs::ClickEffectStruct)); + + ices.effect = item->Click.Effect; + ices.level2 = item->Click.Level2; + ices.type = item->Click.Type; + ices.level = item->Click.Level; + ices.max_charges = item->MaxCharges; + ices.cast_time = item->CastTime; + ices.recast = item->RecastDelay; + ices.recast_type = item->RecastType; + + ss.write((const char*)&ices, sizeof(RoF2::structs::ClickEffectStruct)); + + if (strlen(item->ClickName) > 0) + { + ss.write((const char*)item->ClickName, strlen(item->ClickName)); + ss.write((const char*)&null_term, sizeof(uint8)); + } + else + { + ss.write((const char*)&null_term, sizeof(uint8)); + } + + ss.write((const char*)&effect_unknown, sizeof(int32)); // clickunk7 + + //_log(NET__ERROR, "ItemBody proc effect struct is %i bytes", sizeof(RoF2::structs::ProcEffectStruct)); + RoF2::structs::ProcEffectStruct ipes; + memset(&ipes, 0, sizeof(RoF2::structs::ProcEffectStruct)); + + ipes.effect = item->Proc.Effect; + ipes.level2 = item->Proc.Level2; + ipes.type = item->Proc.Type; + ipes.level = item->Proc.Level; + ipes.procrate = item->ProcRate; + + ss.write((const char*)&ipes, sizeof(RoF2::structs::ProcEffectStruct)); + + if (strlen(item->ProcName) > 0) + { + ss.write((const char*)item->ProcName, strlen(item->ProcName)); + ss.write((const char*)&null_term, sizeof(uint8)); + } + else + { + ss.write((const char*)&null_term, sizeof(uint8)); + } + + ss.write((const char*)&effect_unknown, sizeof(int32)); // unknown5 + + //_log(NET__ERROR, "ItemBody worn effect struct is %i bytes", sizeof(RoF2::structs::WornEffectStruct)); + RoF2::structs::WornEffectStruct iwes; + memset(&iwes, 0, sizeof(RoF2::structs::WornEffectStruct)); + + iwes.effect = item->Worn.Effect; + iwes.level2 = item->Worn.Level2; + iwes.type = item->Worn.Type; + iwes.level = item->Worn.Level; + + ss.write((const char*)&iwes, sizeof(RoF2::structs::WornEffectStruct)); + + if (strlen(item->WornName) > 0) + { + ss.write((const char*)item->WornName, strlen(item->WornName)); + ss.write((const char*)&null_term, sizeof(uint8)); + } + else + { + ss.write((const char*)&null_term, sizeof(uint8)); + } + + ss.write((const char*)&effect_unknown, sizeof(int32)); // unknown6 + + RoF2::structs::WornEffectStruct ifes; + memset(&ifes, 0, sizeof(RoF2::structs::WornEffectStruct)); + + ifes.effect = item->Focus.Effect; + ifes.level2 = item->Focus.Level2; + ifes.type = item->Focus.Type; + ifes.level = item->Focus.Level; + + ss.write((const char*)&ifes, sizeof(RoF2::structs::WornEffectStruct)); + + if (strlen(item->FocusName) > 0) + { + ss.write((const char*)item->FocusName, strlen(item->FocusName)); + ss.write((const char*)&null_term, sizeof(uint8)); + } + else + { + ss.write((const char*)&null_term, sizeof(uint8)); + } + + ss.write((const char*)&effect_unknown, sizeof(int32)); // unknown6 + + RoF2::structs::WornEffectStruct ises; + memset(&ises, 0, sizeof(RoF2::structs::WornEffectStruct)); + + ises.effect = item->Scroll.Effect; + ises.level2 = item->Scroll.Level2; + ises.type = item->Scroll.Type; + ises.level = item->Scroll.Level; + + ss.write((const char*)&ises, sizeof(RoF2::structs::WornEffectStruct)); + + if (strlen(item->ScrollName) > 0) + { + ss.write((const char*)item->ScrollName, strlen(item->ScrollName)); + ss.write((const char*)&null_term, sizeof(uint8)); + } + else + { + ss.write((const char*)&null_term, sizeof(uint8)); + } + + ss.write((const char*)&effect_unknown, sizeof(int32)); // unknown6 + + // Bard Effect? + RoF2::structs::WornEffectStruct ibes; + memset(&ibes, 0, sizeof(RoF2::structs::WornEffectStruct)); + + ibes.effect = 0xffffffff; + ibes.level2 = 0; + ibes.type = 0; + ibes.level = 0; + //ibes.unknown6 = 0xffffffff; + + ss.write((const char*)&ibes, sizeof(RoF2::structs::WornEffectStruct)); + + /* + if(strlen(item->BardName) > 0) + { + ss.write((const char*)item->BardName, strlen(item->BardName)); + ss.write((const char*)&null_term, sizeof(uint8)); + } + else */ + ss.write((const char*)&null_term, sizeof(uint8)); + + ss.write((const char*)&effect_unknown, sizeof(int32)); // unknown6 + // End of Effects + + //_log(NET__ERROR, "ItemBody Quaternary effect struct is %i bytes", sizeof(RoF2::structs::ItemQuaternaryBodyStruct)); + RoF2::structs::ItemQuaternaryBodyStruct iqbs; + memset(&iqbs, 0, sizeof(RoF2::structs::ItemQuaternaryBodyStruct)); + + iqbs.scriptfileid = item->ScriptFileID; + iqbs.quest_item = item->QuestItemFlag; + iqbs.Power = 0; + iqbs.Purity = item->Purity; + iqbs.unknown16 = 0; + iqbs.BackstabDmg = item->BackstabDmg; + iqbs.DSMitigation = item->DSMitigation; + iqbs.HeroicStr = item->HeroicStr; + iqbs.HeroicInt = item->HeroicInt; + iqbs.HeroicWis = item->HeroicWis; + iqbs.HeroicAgi = item->HeroicAgi; + iqbs.HeroicDex = item->HeroicDex; + iqbs.HeroicSta = item->HeroicSta; + iqbs.HeroicCha = item->HeroicCha; + iqbs.HeroicMR = item->HeroicMR; + iqbs.HeroicFR = item->HeroicFR; + iqbs.HeroicCR = item->HeroicCR; + iqbs.HeroicDR = item->HeroicDR; + iqbs.HeroicPR = item->HeroicPR; + iqbs.HeroicSVCorrup = item->HeroicSVCorrup; + iqbs.HealAmt = item->HealAmt; + iqbs.SpellDmg = item->SpellDmg; + iqbs.clairvoyance = item->Clairvoyance; + iqbs.unknown28 = 0; + + + // Begin RoF2 Test + iqbs.unknown_TEST1 = 0; + // End RoF2 Test + + iqbs.unknown30 = 0; + iqbs.unknown39 = 1; + + iqbs.subitem_count = 0; + + char *SubSerializations[10]; // + + uint32 SubLengths[10]; + + for (int x = SUB_BEGIN; x < EmuConstants::ITEM_CONTAINER_SIZE; ++x) { + + SubSerializations[x] = nullptr; + + const ItemInst* subitem = ((const ItemInst*)inst)->GetItem(x); + + if (subitem) { + + int SubSlotNumber; + + iqbs.subitem_count++; + + if (slot_id_in >= EmuConstants::GENERAL_BEGIN && slot_id_in <= EmuConstants::GENERAL_END) // (< 30) - no cursor? + //SubSlotNumber = (((slot_id_in + 3) * 10) + x + 1); + SubSlotNumber = (((slot_id_in + 3) * EmuConstants::ITEM_CONTAINER_SIZE) + x + 1); + else if (slot_id_in >= EmuConstants::BANK_BEGIN && slot_id_in <= EmuConstants::BANK_END) + //SubSlotNumber = (((slot_id_in - 2000) * 10) + 2030 + x + 1); + SubSlotNumber = (((slot_id_in - EmuConstants::BANK_BEGIN) * EmuConstants::ITEM_CONTAINER_SIZE) + EmuConstants::BANK_BAGS_BEGIN + x); + else if (slot_id_in >= EmuConstants::SHARED_BANK_BEGIN && slot_id_in <= EmuConstants::SHARED_BANK_END) + //SubSlotNumber = (((slot_id_in - 2500) * 10) + 2530 + x + 1); + SubSlotNumber = (((slot_id_in - EmuConstants::SHARED_BANK_BEGIN) * EmuConstants::ITEM_CONTAINER_SIZE) + EmuConstants::SHARED_BANK_BAGS_BEGIN + x); + else + SubSlotNumber = slot_id_in; // ??????? + + /* + // TEST CODE: + SubSlotNumber = Inventory::CalcSlotID(slot_id_in, x); + */ + + SubSerializations[x] = SerializeItem(subitem, SubSlotNumber, &SubLengths[x], depth + 1); + } + } + + ss.write((const char*)&iqbs, sizeof(RoF2::structs::ItemQuaternaryBodyStruct)); + + for (int x = SUB_BEGIN; x < EmuConstants::ITEM_CONTAINER_SIZE; ++x) { + + if (SubSerializations[x]) { + + ss.write((const char*)&x, sizeof(uint32)); + + ss.write(SubSerializations[x], SubLengths[x]); + + safe_delete_array(SubSerializations[x]); + } + } + + char* item_serial = new char[ss.tellp()]; + memset(item_serial, 0, ss.tellp()); + memcpy(item_serial, ss.str().c_str(), ss.tellp()); + + *length = ss.tellp(); + return item_serial; + } + + static inline structs::ItemSlotStruct ServerToRoF2Slot(uint32 ServerSlot) + { + structs::ItemSlotStruct RoF2Slot; + RoF2Slot.SlotType = INVALID_INDEX; + RoF2Slot.Unknown02 = NOT_USED; + RoF2Slot.MainSlot = INVALID_INDEX; + RoF2Slot.SubSlot = INVALID_INDEX; + RoF2Slot.AugSlot = INVALID_INDEX; + RoF2Slot.Unknown01 = NOT_USED; + + uint32 TempSlot = 0; + + if (ServerSlot < 56 || ServerSlot == MainPowerSource) { // Main Inventory and Cursor + RoF2Slot.SlotType = maps::MapPossessions; + RoF2Slot.MainSlot = ServerSlot; + + if (ServerSlot == MainPowerSource) + RoF2Slot.MainSlot = slots::MainPowerSource; + + else if (ServerSlot >= MainCursor) // Cursor and Extended Corpse Inventory + RoF2Slot.MainSlot += 3; + + else if (ServerSlot >= MainAmmo) // (> 20) + RoF2Slot.MainSlot += 1; + } + + /*else if (ServerSlot < 51) { // Cursor Buffer + RoF2Slot.SlotType = maps::MapLimbo; + RoF2Slot.MainSlot = ServerSlot - 31; + }*/ + + else if (ServerSlot >= EmuConstants::GENERAL_BAGS_BEGIN && ServerSlot <= EmuConstants::CURSOR_BAG_END) { // (> 250 && < 341) + RoF2Slot.SlotType = maps::MapPossessions; + TempSlot = ServerSlot - 1; + RoF2Slot.MainSlot = int(TempSlot / EmuConstants::ITEM_CONTAINER_SIZE) - 2; + RoF2Slot.SubSlot = TempSlot - ((RoF2Slot.MainSlot + 2) * EmuConstants::ITEM_CONTAINER_SIZE); + + if (RoF2Slot.MainSlot >= slots::MainGeneral9) // (> 30) + RoF2Slot.MainSlot = slots::MainCursor; + } + + else if (ServerSlot >= EmuConstants::TRIBUTE_BEGIN && ServerSlot <= EmuConstants::TRIBUTE_END) { // Tribute + RoF2Slot.SlotType = maps::MapTribute; + RoF2Slot.MainSlot = ServerSlot - EmuConstants::TRIBUTE_BEGIN; + } + + else if (ServerSlot >= EmuConstants::BANK_BEGIN && ServerSlot <= EmuConstants::BANK_BAGS_END) { + RoF2Slot.SlotType = maps::MapBank; + TempSlot = ServerSlot - EmuConstants::BANK_BEGIN; + RoF2Slot.MainSlot = TempSlot; + + if (TempSlot > 30) { // (> 30) + RoF2Slot.MainSlot = int(TempSlot / EmuConstants::ITEM_CONTAINER_SIZE) - 3; + RoF2Slot.SubSlot = TempSlot - ((RoF2Slot.MainSlot + 3) * EmuConstants::ITEM_CONTAINER_SIZE); + } + } + + else if (ServerSlot >= EmuConstants::SHARED_BANK_BEGIN && ServerSlot <= EmuConstants::SHARED_BANK_BAGS_END) { + RoF2Slot.SlotType = maps::MapSharedBank; + TempSlot = ServerSlot - EmuConstants::SHARED_BANK_BEGIN; + RoF2Slot.MainSlot = TempSlot; + + if (TempSlot > 30) { // (> 30) + RoF2Slot.MainSlot = int(TempSlot / EmuConstants::ITEM_CONTAINER_SIZE) - 3; + RoF2Slot.SubSlot = TempSlot - ((RoF2Slot.MainSlot + 3) * EmuConstants::ITEM_CONTAINER_SIZE); + } + } + + else if (ServerSlot >= EmuConstants::TRADE_BEGIN && ServerSlot <= EmuConstants::TRADE_BAGS_END) { + RoF2Slot.SlotType = maps::MapTrade; + TempSlot = ServerSlot - EmuConstants::TRADE_BEGIN; + RoF2Slot.MainSlot = TempSlot; + + if (TempSlot > 30) { + RoF2Slot.MainSlot = int(TempSlot / EmuConstants::ITEM_CONTAINER_SIZE) - 3; + RoF2Slot.SubSlot = TempSlot - ((RoF2Slot.MainSlot + 3) * EmuConstants::ITEM_CONTAINER_SIZE); + } + + /* + // OLD CODE: + if (TempSlot > 99) { + if (TempSlot > 100) + RoF2Slot.MainSlot = int((TempSlot - 100) / 10); + + else + RoF2Slot.MainSlot = 0; + + RoF2Slot.SubSlot = TempSlot - (100 + RoF2Slot.MainSlot); + } + */ + } + + else if (ServerSlot >= EmuConstants::WORLD_BEGIN && ServerSlot <= EmuConstants::WORLD_END) { + RoF2Slot.SlotType = maps::MapWorld; + TempSlot = ServerSlot - EmuConstants::WORLD_BEGIN; + RoF2Slot.MainSlot = TempSlot; + } + + _log(NET__ERROR, "Convert Server Slot %i to RoF2 Slots: Type %i, Unk2 %i, Main %i, Sub %i, Aug %i, Unk1 %i", ServerSlot, RoF2Slot.SlotType, RoF2Slot.Unknown02, RoF2Slot.MainSlot, RoF2Slot.SubSlot, RoF2Slot.AugSlot, RoF2Slot.Unknown01); + + return RoF2Slot; + } + + static inline structs::MainInvItemSlotStruct ServerToRoF2MainInvSlot(uint32 ServerSlot) + { + structs::MainInvItemSlotStruct RoF2Slot; + RoF2Slot.MainSlot = INVALID_INDEX; + RoF2Slot.SubSlot = INVALID_INDEX; + RoF2Slot.AugSlot = INVALID_INDEX; + RoF2Slot.Unknown01 = NOT_USED; + + uint32 TempSlot = 0; + + if (ServerSlot < 56 || ServerSlot == MainPowerSource) { // (< 52) + RoF2Slot.MainSlot = ServerSlot; + + if (ServerSlot == MainPowerSource) + RoF2Slot.MainSlot = slots::MainPowerSource; + + else if (ServerSlot >= MainCursor) // Cursor and Extended Corpse Inventory + RoF2Slot.MainSlot += 3; + + else if (ServerSlot >= MainAmmo) // Ammo and Personl Inventory + RoF2Slot.MainSlot += 1; + + /*else if (ServerSlot >= MainCursor) { // Cursor + RoF2Slot.MainSlot = slots::MainCursor; + + if (ServerSlot > 30) + RoF2Slot.SubSlot = (ServerSlot + 3) - 33; + }*/ + } + + else if (ServerSlot >= EmuConstants::GENERAL_BAGS_BEGIN && ServerSlot <= EmuConstants::CURSOR_BAG_END) { + TempSlot = ServerSlot - 1; + RoF2Slot.MainSlot = int(TempSlot / EmuConstants::ITEM_CONTAINER_SIZE) - 2; + RoF2Slot.SubSlot = TempSlot - ((RoF2Slot.MainSlot + 2) * EmuConstants::ITEM_CONTAINER_SIZE); + } + + _log(NET__ERROR, "Convert Server Slot %i to RoF2 Slots: Main %i, Sub %i, Aug %i, Unk1 %i", ServerSlot, RoF2Slot.MainSlot, RoF2Slot.SubSlot, RoF2Slot.AugSlot, RoF2Slot.Unknown01); + + return RoF2Slot; + } + + static inline uint32 ServerToRoF2CorpseSlot(uint32 ServerCorpse) + { + //uint32 RoF2Corpse; + return (ServerCorpse + 1); + } + + static inline uint32 RoF2ToServerSlot(structs::ItemSlotStruct RoF2Slot) + { + uint32 ServerSlot = INVALID_INDEX; + uint32 TempSlot = 0; + + if (RoF2Slot.SlotType == maps::MapPossessions && RoF2Slot.MainSlot < 57) { // Worn/Personal Inventory and Cursor (< 51) + if (RoF2Slot.MainSlot == slots::MainPowerSource) + TempSlot = MainPowerSource; + + else if (RoF2Slot.MainSlot >= slots::MainCursor) // Cursor and Extended Corpse Inventory + TempSlot = RoF2Slot.MainSlot - 3; + + /*else if (RoF2Slot.MainSlot == slots::MainGeneral9 || RoF2Slot.MainSlot == slots::MainGeneral10) { // 9th and 10th RoF2 inventory/corpse slots + // Need to figure out what to do when we get these + + // The slot range of 0 - client_max is cross-utilized between player inventory and corpse inventory. + // In the case of RoF2, player inventory is addressed as 0 - 33 and corpse inventory is addressed as 23 - 56. + // We 'could' assign the two new inventory slots as 9997 and 9998, and then work around their bag + // slot assignments, but doing so may disrupt our ability to utilize the corpse looting range properly. + + // For now, it's probably best to leave as-is and let this work itself out in the inventory rework. + }*/ + + else if (RoF2Slot.MainSlot >= slots::MainAmmo) // Ammo and Main Inventory + TempSlot = RoF2Slot.MainSlot - 1; + + else // Worn Slots + TempSlot = RoF2Slot.MainSlot; + + if (RoF2Slot.SubSlot >= SUB_BEGIN) // Bag Slots + TempSlot = ((TempSlot + 3) * EmuConstants::ITEM_CONTAINER_SIZE) + RoF2Slot.SubSlot + 1; + + ServerSlot = TempSlot; + } + + else if (RoF2Slot.SlotType == maps::MapBank) { + TempSlot = EmuConstants::BANK_BEGIN; + + if (RoF2Slot.SubSlot >= SUB_BEGIN) + TempSlot += ((RoF2Slot.MainSlot + 3) * EmuConstants::ITEM_CONTAINER_SIZE) + RoF2Slot.SubSlot + 1; + + else + TempSlot += RoF2Slot.MainSlot; + + ServerSlot = TempSlot; + } + + else if (RoF2Slot.SlotType == maps::MapSharedBank) { + TempSlot = EmuConstants::SHARED_BANK_BEGIN; + + if (RoF2Slot.SubSlot >= SUB_BEGIN) + TempSlot += ((RoF2Slot.MainSlot + 3) * EmuConstants::ITEM_CONTAINER_SIZE) + RoF2Slot.SubSlot + 1; + + else + TempSlot += RoF2Slot.MainSlot; + + ServerSlot = TempSlot; + } + + else if (RoF2Slot.SlotType == maps::MapTrade) { + TempSlot = EmuConstants::TRADE_BEGIN; + + if (RoF2Slot.SubSlot >= SUB_BEGIN) + TempSlot += ((RoF2Slot.MainSlot + 3) * EmuConstants::ITEM_CONTAINER_SIZE) + RoF2Slot.SubSlot + 1; + // OLD CODE: + //TempSlot += 100 + (RoF2Slot.MainSlot * EmuConstants::ITEM_CONTAINER_SIZE) + RoF2Slot.SubSlot; + + else + TempSlot += RoF2Slot.MainSlot; + + ServerSlot = TempSlot; + } + + else if (RoF2Slot.SlotType == maps::MapWorld) { + TempSlot = EmuConstants::WORLD_BEGIN; + + if (RoF2Slot.MainSlot >= SUB_BEGIN) + TempSlot += RoF2Slot.MainSlot; + + ServerSlot = TempSlot; + } + + /*else if (RoF2Slot.SlotType == maps::MapLimbo) { // Cursor Buffer + TempSlot = 31; + + if (RoF2Slot.MainSlot >= 0) + TempSlot += RoF2Slot.MainSlot; + + ServerSlot = TempSlot; + }*/ + + else if (RoF2Slot.SlotType == maps::MapGuildTribute) { + ServerSlot = INVALID_INDEX; + } + + _log(NET__ERROR, "Convert RoF2 Slots: Type %i, Unk2 %i, Main %i, Sub %i, Aug %i, Unk1 %i to Server Slot %i", RoF2Slot.SlotType, RoF2Slot.Unknown02, RoF2Slot.MainSlot, RoF2Slot.SubSlot, RoF2Slot.AugSlot, RoF2Slot.Unknown01, ServerSlot); + + return ServerSlot; + } + + static inline uint32 RoF2ToServerMainInvSlot(structs::MainInvItemSlotStruct RoF2Slot) + { + uint32 ServerSlot = INVALID_INDEX; + uint32 TempSlot = 0; + + if (RoF2Slot.MainSlot < 57) { // Worn/Personal Inventory and Cursor (< 33) + if (RoF2Slot.MainSlot == slots::MainPowerSource) + TempSlot = MainPowerSource; + + else if (RoF2Slot.MainSlot >= slots::MainCursor) // Cursor and Extended Corpse Inventory + TempSlot = RoF2Slot.MainSlot - 3; + + /*else if (RoF2Slot.MainSlot == slots::MainGeneral9 || RoF2Slot.MainSlot == slots::MainGeneral10) { // 9th and 10th RoF2 inventory slots + // Need to figure out what to do when we get these + + // Same as above + }*/ + + else if (RoF2Slot.MainSlot >= slots::MainAmmo) // Main Inventory and Ammo Slots + TempSlot = RoF2Slot.MainSlot - 1; + + else + TempSlot = RoF2Slot.MainSlot; + + if (RoF2Slot.SubSlot >= SUB_BEGIN) // Bag Slots + TempSlot = ((TempSlot + 3) * EmuConstants::ITEM_CONTAINER_SIZE) + RoF2Slot.SubSlot + 1; + + ServerSlot = TempSlot; + } + + _log(NET__ERROR, "Convert RoF2 Slots: Main %i, Sub %i, Aug %i, Unk1 %i to Server Slot %i", RoF2Slot.MainSlot, RoF2Slot.SubSlot, RoF2Slot.AugSlot, RoF2Slot.Unknown01, ServerSlot); + + return ServerSlot; + } + + static inline uint32 RoF2ToServerCorpseSlot(uint32 RoF2Corpse) + { + //uint32 ServerCorpse; + return (RoF2Corpse - 1); + } +} +// end namespace RoF2 diff --git a/common/patches/rof2.h b/common/patches/rof2.h new file mode 100644 index 000000000..b39849048 --- /dev/null +++ b/common/patches/rof2.h @@ -0,0 +1,37 @@ +#ifndef RoF2_H_ +#define RoF2_H_ + +#include "../struct_strategy.h" + +class EQStreamIdentifier; + +namespace RoF2 { + + //these are the only public member of this namespace. + extern void Register(EQStreamIdentifier &into); + extern void Reload(); + + + + //you should not directly access anything below.. + //I just dont feel like making a seperate header for it. + + class Strategy : public StructStrategy { + public: + Strategy(); + + protected: + + virtual std::string Describe() const; + virtual const EQClientVersion ClientVersion() const; + + //magic macro to declare our opcode processors + #include "ss_declare.h" + #include "rof2_ops.h" + }; + +}; + + + +#endif /*RoF2_H_*/ diff --git a/common/patches/rof2_constants.h b/common/patches/rof2_constants.h new file mode 100644 index 000000000..596ffa7e7 --- /dev/null +++ b/common/patches/rof2_constants.h @@ -0,0 +1,217 @@ +/* +EQEMu: Everquest Server Emulator + +Copyright (C) 2001-2014 EQEMu Development Team (http://eqemulator.net) + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY except by those people which sell it, which +are required to give you total support for your newly bought product; +without even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +*/ + +#ifndef RoF2_CONSTANTS_H_ +#define RoF2_CONSTANTS_H_ + +#include "../types.h" + +namespace RoF2 { + namespace maps { + typedef enum : int16 { + MapPossessions = 0, + MapBank, + MapSharedBank, + MapTrade, + MapWorld, + MapLimbo, + MapTribute, + MapTrophyTribute, + MapGuildTribute, + MapMerchant, + MapDeleted, + MapCorpse, + MapBazaar, + MapInspect, + MapRealEstate, + MapViewMODPC, + MapViewMODBank, + MapViewMODSharedBank, + MapViewMODLimbo, + MapAltStorage, + MapArchived, + MapMail, + MapGuildTrophyTribute, + MapKrono, + MapOther, + _MapCount + } InventoryMaps; + } + + namespace slots { + typedef enum : int16 { + MainCharm = 0, + MainEar1, + MainHead, + MainFace, + MainEar2, + MainNeck, + MainShoulders, + MainArms, + MainBack, + MainWrist1, + MainWrist2, + MainRange, + MainHands, + MainPrimary, + MainSecondary, + MainFinger1, + MainFinger2, + MainChest, + MainLegs, + MainFeet, + MainWaist, + MainPowerSource, + MainAmmo, + MainGeneral1, + MainGeneral2, + MainGeneral3, + MainGeneral4, + MainGeneral5, + MainGeneral6, + MainGeneral7, + MainGeneral8, + MainGeneral9, + MainGeneral10, + MainCursor, + _MainCount, + _MainEquipmentBegin = MainCharm, + _MainEquipmentEnd = MainAmmo, + _MainEquipmentCount = (_MainEquipmentEnd - _MainEquipmentBegin + 1), + _MainGeneralBegin = MainGeneral1, + _MainGeneralEnd = MainGeneral10, + _MainGeneralCount = (_MainGeneralEnd - _MainGeneralBegin + 1) + } EquipmentSlots; + } + + namespace consts { + static const uint16 MAP_POSSESSIONS_SIZE = slots::_MainCount; + static const uint16 MAP_BANK_SIZE = 24; + static const uint16 MAP_SHARED_BANK_SIZE = 2; + static const uint16 MAP_TRADE_SIZE = 8; + static const uint16 MAP_WORLD_SIZE = 10; + static const uint16 MAP_LIMBO_SIZE = 36; + static const uint16 MAP_TRIBUTE_SIZE = 0; //? + static const uint16 MAP_TROPHY_TRIBUTE_SIZE = 0; + static const uint16 MAP_GUILD_TRIBUTE_SIZE = 0; + static const uint16 MAP_MERCHANT_SIZE = 0; + static const uint16 MAP_DELETED_SIZE = 0; + static const uint16 MAP_CORPSE_SIZE = slots::_MainCount; + static const uint16 MAP_BAZAAR_SIZE = 200; + static const uint16 MAP_INSPECT_SIZE = slots::_MainEquipmentCount; + static const uint16 MAP_REAL_ESTATE_SIZE = 0; + static const uint16 MAP_VIEW_MOD_PC_SIZE = MAP_POSSESSIONS_SIZE; + static const uint16 MAP_VIEW_MOD_BANK_SIZE = MAP_BANK_SIZE; + static const uint16 MAP_VIEW_MOD_SHARED_BANK_SIZE = MAP_SHARED_BANK_SIZE; + static const uint16 MAP_VIEW_MOD_LIMBO_SIZE = MAP_LIMBO_SIZE; + static const uint16 MAP_ALT_STORAGE_SIZE = 0; + static const uint16 MAP_ARCHIVED_SIZE = 0; + static const uint16 MAP_MAIL_SIZE = 0; + static const uint16 MAP_GUILD_TROPHY_TRIBUTE_SIZE = 0; + static const uint16 MAP_KRONO_SIZE = NOT_USED; + static const uint16 MAP_OTHER_SIZE = 0; + + // most of these definitions will go away with the structure-based system..this maintains compatibility for now + // (bag slots and main slots beyond Possessions are assigned for compatibility with current server coding) + static const int16 EQUIPMENT_BEGIN = slots::MainCharm; + static const int16 EQUIPMENT_END = slots::MainAmmo; + static const uint16 EQUIPMENT_SIZE = slots::_MainEquipmentCount; + + static const int16 GENERAL_BEGIN = slots::MainGeneral1; + static const int16 GENERAL_END = slots::MainGeneral10; + static const uint16 GENERAL_SIZE = slots::_MainGeneralCount; + static const int16 GENERAL_BAGS_BEGIN = 251; + static const int16 GENERAL_BAGS_END_OFFSET = 99; + static const int16 GENERAL_BAGS_END = GENERAL_BAGS_BEGIN + GENERAL_BAGS_END_OFFSET; + + static const int16 CURSOR = slots::MainCursor; + static const int16 CURSOR_BAG_BEGIN = 351; + static const int16 CURSOR_BAG_END_OFFSET = 9; + static const int16 CURSOR_BAG_END = CURSOR_BAG_BEGIN + CURSOR_BAG_END_OFFSET; + + static const int16 BANK_BEGIN = 2000; + static const int16 BANK_END = 2023; + static const int16 BANK_BAGS_BEGIN = 2031; + static const int16 BANK_BAGS_END_OFFSET = 239; + static const int16 BANK_BAGS_END = BANK_BAGS_BEGIN + BANK_BAGS_END_OFFSET; + + static const int16 SHARED_BANK_BEGIN = 2500; + static const int16 SHARED_BANK_END = 2501; + static const int16 SHARED_BANK_BAGS_BEGIN = 2531; + static const int16 SHARED_BANK_BAGS_END_OFFSET = 19; + static const int16 SHARED_BANK_BAGS_END = SHARED_BANK_BAGS_BEGIN + SHARED_BANK_BAGS_END_OFFSET; + + static const int16 TRADE_BEGIN = 3000; + static const int16 TRADE_END = 3007; + static const int16 TRADE_NPC_END = 3003; + static const int16 TRADE_BAGS_BEGIN = 3031; + static const int16 TRADE_BAGS_END_OFFSET = 79; + static const int16 TRADE_BAGS_END = TRADE_BAGS_BEGIN + TRADE_BAGS_END_OFFSET; + + static const int16 WORLD_BEGIN = 4000; + static const int16 WORLD_END = 4009; + + static const int16 TRIBUTE_BEGIN = 400; + static const int16 TRIBUTE_END = 404; + + static const int16 CORPSE_BEGIN = slots::MainGeneral1; + static const int16 CORPSE_END = slots::MainGeneral1 + slots::MainCursor; + + static const uint16 ITEM_COMMON_SIZE = 6; + static const uint16 ITEM_CONTAINER_SIZE = 255; // 255; (server max will be 255..unsure what actual client is - test) + + static const uint32 BANDOLIERS_COUNT = 20; // count = number of bandolier instances + static const uint32 BANDOLIER_SIZE = 4; // size = number of equipment slots in bandolier instance + static const uint32 POTION_BELT_SIZE = 5; + } + + namespace limits { + static const bool ALLOWS_EMPTY_BAG_IN_BAG = true; + static const bool ALLOWS_CLICK_CAST_FROM_BAG = true; + static const bool COIN_HAS_WEIGHT = false; + } + +}; //end namespace RoF2 + +#endif /*RoF2_CONSTANTS_H_*/ + +/* +RoF2 Notes: + ** Structure-based inventory ** +ok Possessions: ( 0, { 0 .. 33 }, -1, -1 ) (Corpse: { 23 .. 56 } [Offset 23]) +ok [Equipment: ( 0, { 0 .. 22 }, -1, -1 )] +ok [General: ( 0, { 23 .. 32 }, -1, -1 )] +ok [Cursor: ( 0, 33, -1, -1 )] + General Bags: ( 0, { 23 .. 32 }, { 0 .. (maxsize - 1) }, -1 ) + Cursor Bags: ( 0, 33, { 0 .. (maxsize - 1) }, -1 ) + + Bank: ( 1, { 0 .. 23 }, -1, -1 ) + Bank Bags: ( 1, { 0 .. 23 }, { 0 .. (maxsize - 1)}, -1 ) + + Shared Bank: ( 2, { 0 .. 1 }, -1, -1 ) + Shared Bank Bags: ( 2, { 0 .. 1 }, { 0 .. (maxsize - 1) }, -1 ) + + Trade: ( 3, { 0 .. 8 }, -1, -1 ) + (Trade Bags: 3031 - 3110 -- server values) + + World: ( 4, { 0 .. 10 }, -1, -1 ) + +*/ diff --git a/common/patches/rof2_itemfields.h b/common/patches/rof2_itemfields.h new file mode 100644 index 000000000..ccba333b5 --- /dev/null +++ b/common/patches/rof2_itemfields.h @@ -0,0 +1,439 @@ +/* + + +These fields must be in the order of how they are serialized! + + + +*/ +#define NEW_TRY +#ifdef NEW_TRY +//* 000 */ I(ItemClass) // Leave this one off on purpose +/* 001 */ S(Name) +/* 002 */ S(Lore) +//* 003 */ C("")//lorefile - Newly Added - Field is Null +/* 004 */ S(IDFile) +/* 005 */ I(ID) +/* 006 */ I(Weight) +/* 007 */ I(NoRent) +/* 008 */ I(NoDrop) +/* 009 */ I(Size) +/* 010 */ I(Slots) +/* 011 */ I(Price) +/* 012 */ I(Icon) +/* 013 */ C("0")//UNK013 +/* 014 */ C("0")//UNK014 +/* 015 */ I(BenefitFlag) +/* 016 */ I(Tradeskills) +/* 017 */ I(CR) +/* 018 */ I(DR) +/* 019 */ I(PR) +/* 020 */ I(MR) +/* 021 */ I(FR) +/* 022 */ C("0")//svcorruption - Newly Added +/* 023 */ I(AStr) +/* 024 */ I(ASta) +/* 025 */ I(AAgi) +/* 026 */ I(ADex) +/* 027 */ I(ACha) +/* 028 */ I(AInt) +/* 029 */ I(AWis) +/* 030 */ I(HP) +/* 031 */ I(Mana) +/* 032 */ I(Endur) //endur - Relocated +/* 033 */ I(AC) +/* 034 */ I(Classes)//classes - Relocated +/* 035 */ I(Races)//races - Relocated +/* 036 */ I(Deity) +/* 037 */ I(SkillModValue) +/* 038 */ C("0")//UNK038 - Default is 0 +/* 039 */ I(SkillModType) +/* 040 */ I(BaneDmgRace) +/* 041 */ I(BaneDmgBody)//banedmgbody - Relocated +/* 042 */ I(BaneDmgRaceAmt)//banedmgraceamt - Relocated +/* 043 */ I(BaneDmgAmt)//banedmgamt - Relocated +/* 044 */ I(Magic) +/* 045 */ I(CastTime_) +/* 046 */ I(ReqLevel) +/* 047 */ I(RecLevel)//reclevel - Relocated +/* 048 */ I(RecSkill)//recskill - Relocated +/* 049 */ I(BardType) +/* 050 */ I(BardValue) +/* 051 */ I(Light) +/* 052 */ I(Delay) +/* 053 */ I(ElemDmgType) +/* 054 */ I(ElemDmgAmt) +/* 055 */ I(Range) +/* 056 */ I(Damage) +/* 057 */ I(Color) +/* 058 */ I(ItemType) +/* 059 */ I(Material) +/* 060 */ C("0")//UNK060 - Default is 0 +/* 061 */ C("0")//UNK061 - Default is 0 +/* 062 */ F(SellRate) +/* 063 */ I(CombatEffects) +/* 064 */ I(Shielding) +/* 065 */ I(StunResist) +/* 066 */ I(StrikeThrough) +/* 067 */ I(ExtraDmgSkill) +/* 068 */ I(ExtraDmgAmt) +/* 069 */ I(SpellShield) +/* 070 */ I(Avoidance) +/* 071 */ I(Accuracy) +/* 072 */ I(CharmFileID) +/* 073 */ I(FactionMod1)//Swapped these so Faction Amt comes after each Faction Mod +/* 074 */ I(FactionAmt1)//Swapped these so Faction Amt comes after each Faction Mod +/* 075 */ I(FactionMod2)//Swapped these so Faction Amt comes after each Faction Mod +/* 076 */ I(FactionAmt2)//Swapped these so Faction Amt comes after each Faction Mod +/* 077 */ I(FactionMod3)//Swapped these so Faction Amt comes after each Faction Mod +/* 078 */ I(FactionAmt3)//Swapped these so Faction Amt comes after each Faction Mod +/* 079 */ I(FactionMod4)//Swapped these so Faction Amt comes after each Faction Mod +/* 080 */ I(FactionAmt4)//Swapped these so Faction Amt comes after each Faction Mod +/* 081 */ S(CharmFile) +/* 082 */ I(AugType) +/* 083 */ I(AugRestrict)//augrestrict - Relocated +/* 084 */ I(AugDistiller)//augdistiller - Relocated +/* 085 */ I(AugSlotType[0]) +/* 086 */ I(AugSlotVisible[0])//augslot1visible - Default 1 +/* 087 */ C("0")//augslot1unk2 - Newly Added - Default 0 +/* 088 */ I(AugSlotType[1]) +/* 089 */ I(AugSlotVisible[1]) +/* 090 */ C("0")//augslot2unk2 - Newly Added +/* 091 */ I(AugSlotType[2]) +/* 092 */ I(AugSlotVisible[2]) +/* 093 */ C("0")//augslot3unk2 - Newly Added +/* 094 */ I(AugSlotType[3]) +/* 095 */ I(AugSlotVisible[3]) +/* 096 */ C("0")//augslot4unk2 - Newly Added +/* 097 */ I(AugSlotType[4]) +/* 098 */ I(AugSlotVisible[4]) +/* 099 */ C("0")//augslot5unk2 - Newly Added +/* 100 */ I(PointType)//pointtype - Relocated +/* 101 */ I(LDoNTheme) +/* 102 */ I(LDoNPrice) +/* 103 */ C("70")//UNK098 - Newly Added - Default 70, but some are set to 0 +/* 104 */ I(LDoNSold) +/* 105 */ I(BagType) +/* 106 */ I(BagSlots) +/* 107 */ I(BagSize) +/* 108 */ I(BagWR) +/* 109 */ I(Book) +/* 110 */ I(BookType) +/* 111 */ S(Filename) +/* 112 */ I(LoreGroup) +/* 113 */ I(ArtifactFlag) +/* 114 */ C("0")//I(PendingLoreFlag)?//UNK109 - Default 0, but a few are 1 +/* 115 */ I(Favor) +/* 116 */ I(GuildFavor)//guildfavor - Relocated +/* 117 */ I(FVNoDrop) +/* 118 */ I(DotShielding) +/* 119 */ I(Attack) +/* 120 */ I(Regen) +/* 121 */ I(ManaRegen) +/* 122 */ I(EnduranceRegen) +/* 123 */ I(Haste) +/* 124 */ I(DamageShield) +/* 125 */ C("-1") //UNK120 - Default is -1 +/* 126 */ C("0") //UNK121 - Default is 0 +/* 127 */ I(Attuneable) +/* 128 */ I(NoPet) +/* 129 */ C("0") //UNK124 - Default 0, but a few are 1 +/* 130 */ I(PotionBelt) +/* 131 */ C("0") //potionbeltslots - Default 0, but a few are 1 +/* 132 */ I(StackSize) +/* 133 */ I(NoTransfer) +/* 134 */ I(Stackable)//UNK129 - Default is 0, but some are much higher +/* 135 */ I(QuestItemFlag)//questitemflag - Default is 0 (off), flag on = 1 +/* 136 */ C("0")//UNK131 - Default is 0, but there is an item set to 1 +/* 137 */ C("0")//UNK132 - Default is 0? 0000000000000000000? +/* 138 */ I(Click.Effect) +/* 139 */ I(Click.Type) +/* 140 */ I(Click.Level2) +/* 141 */ I(Click.Level) +/* 142 */ I(MaxCharges)//maxcharges - Relocated +/* 143 */ I(CastTime_)//casttime - Relocated - Note Duplicate Entries for CastTime_ and none for CastTime +/* 144 */ I(RecastDelay)//recastdelay - Relocated +/* 145 */ I(RecastType)//recasttype - Relocated +/* 146 */ C("0")//clickunk5 - Newly Added - Default is 0 +/* 147 */ C("")//clickname - Newly Added - Default is Null +/* 148 */ C("-1")//clickunk7 - Newly Added - Default is -1, but some set to 0 and some much higher +/* 149 */ I(Proc.Effect) +/* 150 */ I(Proc.Type) +/* 151 */ I(Proc.Level2) +/* 152 */ I(Proc.Level) +/* 153 */ C("0")//procunk1 - Newly Added - Default is 0, but some set to -1 and 1 +/* 154 */ C("0")//procunk2 - Newly Added - Default is 0 +/* 155 */ C("0")//procunk3 - Newly Added - Default is 0 +/* 156 */ C("0")//procunk4 - Newly Added - Default is 0 +/* 157 */ I(ProcRate)//procrate - Relocated +/* 158 */ C("")//procname - Newly Added - Default is Null +/* 159 */ C("-1")//procunk7 - Newly Added - Default is -1, but some set to 0 +/* 160 */ I(Worn.Effect) +/* 161 */ I(Worn.Type) +/* 162 */ I(Worn.Level2) +/* 163 */ I(Worn.Level) +/* 164 */ C("0")//wornunk1 - Newly Added - Default is 0 +/* 165 */ C("0")//wornunk2 - Newly Added - Default is 0 +/* 166 */ C("0")//wornunk3 - Newly Added - Default is 0 +/* 167 */ C("0")//wornunk4 - Newly Added - Default is 0 +/* 168 */ C("0")//wornunk5 - Newly Added - Default is 0 +/* 169 */ C("")//wornname - Newly Added - Default is Null +/* 170 */ C("-1")//wornunk7 - Newly Added - Default is -1, but some set to 0 +/* 171 */ I(Focus.Effect) +/* 172 */ I(Focus.Type) +/* 173 */ I(Focus.Level2) +/* 174 */ I(Focus.Level) +/* 175 */ C("0")//focusunk1 - Newly Added - Default is 0 +/* 176 */ C("0")//focusunk2 - Newly Added - Default is 0 +/* 177 */ C("0")//focusunk3 - Newly Added - Default is 0 +/* 178 */ C("0")//focusunk4 - Newly Added - Default is 0 +/* 179 */ C("0")//focusunk5 - Newly Added - Default is 0 +/* 180 */ C("")//focusname - Newly Added - Default is Null +/* 181 */ C("-1")//focusunk7 - Newly Added - Default is -1, but some set to 0 +/* 182 */ I(Scroll.Effect) +/* 183 */ I(Scroll.Type) +/* 184 */ I(Scroll.Level2) +/* 185 */ I(Scroll.Level) +/* 186 */ C("0")//scrollunk1 - Renumber this*** +/* 187 */ C("0")//scrollunk2 - Newly Added - Default is 0 +/* 188 */ C("0")//scrollunk3 - Newly Added - Default is 0 +/* 189 */ C("0")//scrollunk4 - Newly Added - Default is 0 +/* 190 */ C("0")//scrollunk5 - Newly Added - Default is 0 +/* 191 */ C("")//scrollname - Newly Added - Default is Null +/* 192 */ C("-1")//scrollunk7 - Newly Added - Default is -1, but some set to 0 +/* 193 */ C("0")//UNK193 - Default is 0 +/* 194 */ C("0")//purity - Newly Added - Default is 0, but some go up to 75 +/* 195 */ C("0")//dsmitigation - Newly Added - Default is 0, but some are up to 2 +/* 196 */ C("0")//heroic_str - Newly Added - Default is 0 +/* 197 */ C("0")//heroic_int - Newly Added - Default is 0 +/* 198 */ C("0")//heroic_wis - Newly Added - Default is 0 +/* 199 */ C("0")//heroic_agi - Newly Added - Default is 0 +/* 200 */ C("0")//heroic_dex - Newly Added - Default is 0 +/* 201 */ C("0")//heroic_sta - Newly Added - Default is 0 +/* 202 */ C("0")//heroic_cha - Newly Added - Default is 0 +/* 203 */ C("0")//HeroicSvPoison - Newly Added - Default is 0 +/* 204 */ C("0")//HeroicSvMagic - Newly Added - Default is 0 +/* 205 */ C("0")//HeroicSvFire - Newly Added - Default is 0 +/* 206 */ C("0")//HeroicSvDisease - Newly Added - Default is 0 +/* 207 */ C("0")//HeroicSvCold - Newly Added - Default is 0 +/* 208 */ C("0")//HeroicSvCorruption - Newly Added - Default is 0 +/* 209 */ C("0")//healamt - Newly Added - Default is 0, but some are up to 9 +/* 210 */ C("0")//spelldmg - Newly Added - Default is 0, but some are up to 9 +/* 211 */ C("0")//clairvoyance - Newly Added - Default is 0, but some are up to 10 +/* 212 */ C("0")//backstabdmg - Newly Added - Default is 0, but some are up to 65 +//* 213 */ C("0")//evolvinglevel - Newly Added - Default is 0, but some are up to 7 +//* 214 */ C("0")//MaxPower - Newly Added +//* 215 */ C("0")//Power - Newly Added + +//This doesn't appear to be used /* 102 */ S(verified)//verified +//This doesn't appear to be used /* 102 */ S(serialized)//created +//Unsure where this goes right now (or if it is even used) /* 108 */ I(SummonedFlag) + +#else +/* 000 */ //I(ItemClass) Leave this one off on purpose +/* 001 */ S(Name) +/* 002 */ S(Lore) +/* 003 */ C("") //LoreFile? +/* 003 */ S(IDFile) +/* 004 */ I(ID) +/* 005 */ I(Weight) +/* 006 */ I(NoRent) +/* 007 */ I(NoDrop) +/* 008 */ I(Size) +/* 009 */ I(Slots) +/* 010 */ I(Price) +/* 011 */ I(Icon) +/* 013 */ C("0") +/* 014 */ C("0") +/* 014 */ I(BenefitFlag) +/* 015 */ I(Tradeskills) +/* 016 */ I(CR) +/* 017 */ I(DR) +/* 018 */ I(PR) +/* 019 */ I(MR) +/* 020 */ I(FR) + C("0") //svcorruption +/* 021 */ I(AStr) +/* 022 */ I(ASta) +/* 023 */ I(AAgi) +/* 024 */ I(ADex) +/* 025 */ I(ACha) +/* 026 */ I(AInt) +/* 027 */ I(AWis) +/* 028 */ I(HP) +/* 029 */ I(Mana) + I(Endur) +/* 030 */ I(AC) +/* 052 */ I(Classes) +/* 053 */ I(Races) +/* 031 */ I(Deity) +/* 032 */ I(SkillModValue) +/* 033 */ C("0") +/* 034 */ I(SkillModType) +/* 035 */ I(BaneDmgRace) +/* 037 */ I(BaneDmgBody) +/* 036 */ I(BaneDmgRaceAmt) +/* 036 */ I(BaneDmgAmt) +/* 038 */ I(Magic) +/* 039 */ I(CastTime_) +/* 040 */ I(ReqLevel) +/* 045 */ I(RecLevel) +/* 046 */ I(RecSkill) +/* 041 */ I(BardType) +/* 042 */ I(BardValue) +/* 043 */ I(Light) +/* 044 */ I(Delay) +/* 047 */ I(ElemDmgType) +/* 048 */ I(ElemDmgAmt) +/* 049 */ I(Range) +/* 050 */ I(Damage) +/* 051 */ I(Color) +/* 056 */ I(ItemType) +/* 057 */ I(Material) +/* 060 */ C("0") +/* 061 */ C("0") +/* 058 */ F(SellRate) +/* 063 */ I(CombatEffects) +/* 064 */ I(Shielding) +/* 065 */ I(StunResist) +/* 059 */ //C("0") +/* 061 */ //C("0") +/* 066 */ I(StrikeThrough) +/* 067 */ I(ExtraDmgSkill) +/* 068 */ I(ExtraDmgAmt) +/* 069 */ I(SpellShield) +/* 070 */ I(Avoidance) +/* 071 */ I(Accuracy) +/* 072 */ I(CharmFileID) +/* 073 */ I(FactionMod1) +/* 077 */ I(FactionAmt1) +/* 074 */ I(FactionMod2) +/* 078 */ I(FactionAmt2) +/* 075 */ I(FactionMod3) +/* 079 */ I(FactionAmt3) +/* 076 */ I(FactionMod4) +/* 080 */ I(FactionAmt4) +/* 081 */ S(CharmFile) +/* 082 */ I(AugType) +/* 082 */ I(AugRestrict) +/* 082 */ I(AugDistiller) +/* 083 */ I(AugSlotType[0]) +/* 084 */ I(AugSlotVisible[0]) +/* 084 */ I(AugSlotUnk2[0]) +/* 085 */ I(AugSlotType[1]) +/* 086 */ I(AugSlotVisible[1]) +/* 086 */ I(AugSlotUnk2[1]) +/* 087 */ I(AugSlotType[2]) +/* 088 */ I(AugSlotVisible[2]) +/* 088 */ I(AugSlotUnk2[2]) +/* 089 */ I(AugSlotType[3]) +/* 090 */ I(AugSlotVisible[3]) +/* 090 */ I(AugSlotUnk2[3]) +/* 091 */ I(AugSlotType[4]) +/* 092 */ I(AugSlotVisible[4]) +/* 092 */ I(AugSlotUnk2[4]) +/* 093 */ I(PointType) +/* 093 */ I(LDoNTheme) +/* 094 */ I(LDoNPrice) +/* 094 */ C("0") +/* 095 */ I(LDoNSold) +/* 096 */ I(BagType) +/* 097 */ I(BagSlots) +/* 098 */ I(BagSize) +/* 099 */ I(BagWR) +/* 100 */ I(Book) +/* 101 */ I(BookType) +/* 102 */ S(Filename) +/* 105 */ I(LoreGroup) +/* 106 */ //I(PendingLoreFlag) +/* 107 */ I(ArtifactFlag) +/* 094 */ C("0") +/* 108 */ //I(SummonedFlag) +/* 109 */ I(Favor) +/* 121 */ I(GuildFavor) +/* 110 */ I(FVNoDrop) +/* 112 */ I(DotShielding) +/* 113 */ I(Attack) +/* 114 */ I(Regen) +/* 115 */ I(ManaRegen) +/* 116 */ I(EnduranceRegen) +/* 117 */ I(Haste) +/* 118 */ I(DamageShield) +/* 120 */ C("0") +/* 121 */ C("0") +/* 125 */ I(Attuneable) +/* 126 */ I(NoPet) +/* 124 */ C("0") +/* 129 */ I(PotionBelt) +/* 130 */ I(PotionBeltSlots) +/* 131 */ I(StackSize) +/* 132 */ I(NoTransfer) +/* 129 */ C("0") +/* 132 */ I(QuestItemFlag) +/* 131 */ C("0") +/* 132 */ C("00000000000000000000000000000000000000") +/* 134 */ I(Click.Effect) +/* 135 */ I(Click.Type) +/* 136 */ I(Click.Level2) +/* 137 */ I(Click.Level) +/* 055 */ I(MaxCharges) +/* 060 */ I(CastTime) +/* 119 */ I(RecastDelay) +/* 120 */ I(RecastType) +/* 138 */ C("0") //clickunk5 (prolly ProcRate) +/* 138 */ C("") //clickunk6 +/* 138 */ C("-1") //clickunk7 +/* 139 */ I(Proc.Effect) +/* 140 */ I(Proc.Type) +/* 141 */ I(Proc.Level2) +/* 142 */ I(Proc.Level) +/* 143 */ C("0") //procunk1 (prolly MaxCharges) +/* 143 */ C("0") //procunk2 (prolly CastTime) +/* 143 */ C("0") //procunk3 (prolly RecastDelay) +/* 143 */ C("0") //procunk4 (prolly RecastType) +/* 062 */ I(ProcRate) +/* 143 */ C("") //procunk6 +/* 143 */ C("-1") //procunk7 +/* 144 */ I(Worn.Effect) +/* 145 */ I(Worn.Type) +/* 146 */ I(Worn.Level2) +/* 147 */ I(Worn.Level) +/* 143 */ C("0") //wornunk1 (prolly MaxCharges) +/* 143 */ C("0") //wornunk2 (prolly CastTime) +/* 143 */ C("0") //wornunk3 (prolly RecastDelay) +/* 143 */ C("0") //wornunk4 (prolly RecastType) +/* 143 */ C("0") //wornunk5 (prolly ProcRate) +/* 143 */ C("") //wornunk6 +/* 143 */ C("-1") //wornunk7 +/* 149 */ I(Focus.Effect) +/* 150 */ I(Focus.Type) +/* 151 */ I(Focus.Level2) +/* 152 */ I(Focus.Level) +/* 143 */ C("0") //focusunk1 (prolly MaxCharges) +/* 143 */ C("0") //focusunk2 (prolly CastTime) +/* 143 */ C("0") //focusunk3 (prolly RecastDelay) +/* 143 */ C("0") //focusunk4 (prolly RecastType) +/* 143 */ C("0") //focusunk5 (prolly ProcRate) +/* 143 */ C("") //focusunk6 +/* 143 */ C("-1") //focusunk7 +/* 154 */ I(Scroll.Effect) +/* 155 */ I(Scroll.Type) +/* 156 */ I(Scroll.Level2) +/* 157 */ I(Scroll.Level) +/* 143 */ C("0") //scrollunk1 (prolly MaxCharges) +/* 143 */ C("0") //scrollunk2 (prolly CastTime) +/* 143 */ C("0") //scrollunk3 (prolly RecastDelay) +/* 143 */ C("0") //scrollunk4 (prolly RecastType) +/* 143 */ C("0") //scrollunk5 (prolly ProcRate) +/* 143 */ C("") //scrollunk6 +/* 143 */ C("-1") //scrollunk7 +/* 193 */ C("0") //Power Source Capacity +/* 194 */ C("0") //purity + +#endif + +#undef I +#undef C +#undef S +#undef F + diff --git a/common/patches/rof2_ops.h b/common/patches/rof2_ops.h new file mode 100644 index 000000000..fbdd20575 --- /dev/null +++ b/common/patches/rof2_ops.h @@ -0,0 +1,173 @@ +// out-going packets that require an ENCODE translation: +// Begin RoF2 Encodes + +// incoming packets that require a DECODE translation: +// Begin RoF2 Decodes + + +// End RoF2 Encodes/Decodes + +// These require Encodes/Decodes for RoF, so they do for RoF2 as well +// Begin RoF Encodes +E(OP_Action) +E(OP_AdventureMerchantSell) +E(OP_AltCurrency) +E(OP_AltCurrencySell) +E(OP_Animation) +E(OP_ApplyPoison) +E(OP_AugmentInfo) +E(OP_Barter) +E(OP_BazaarSearch) +E(OP_BeginCast) +E(OP_BlockedBuffs) +E(OP_Buff) +E(OP_BuffCreate) +E(OP_CancelTrade) +E(OP_CastSpell) +E(OP_ChannelMessage) +E(OP_CharInventory) +E(OP_ClickObjectAction) +E(OP_ClientUpdate) +E(OP_Consider) +E(OP_Damage) +E(OP_DeleteCharge) +E(OP_DeleteItem) +E(OP_DeleteSpawn) +E(OP_DisciplineUpdate) +E(OP_DzCompass) +E(OP_DzExpeditionEndsWarning) +E(OP_DzExpeditionInfo) +E(OP_DzExpeditionList) +E(OP_DzJoinExpeditionConfirm) +E(OP_DzLeaderStatus) +E(OP_DzMemberList) +E(OP_ExpansionInfo) +E(OP_GMLastName) +E(OP_GMTrainSkillConfirm) +E(OP_GroundSpawn) +E(OP_GroupCancelInvite) +E(OP_GroupFollow) +E(OP_GroupFollow2) +E(OP_GroupInvite) +E(OP_GroupUpdate) +E(OP_GuildMemberList) +E(OP_GuildMemberUpdate) +E(OP_GuildsList) +E(OP_HPUpdate) +E(OP_Illusion) +E(OP_InspectBuffs) +E(OP_InspectRequest) +E(OP_InterruptCast) +E(OP_ItemLinkResponse) +E(OP_ItemPacket) +E(OP_ItemVerifyReply) +E(OP_LeadershipExpUpdate) +E(OP_LogServer) +E(OP_LootItem) +E(OP_ManaChange) +E(OP_MercenaryDataResponse) +E(OP_MercenaryDataUpdate) +E(OP_MoveItem) +E(OP_NewSpawn) +E(OP_NewZone) +E(OP_OnLevelMessage) +//E(OP_OpenNewTasksWindow) +E(OP_PetBuffWindow) +E(OP_PlayerProfile) +E(OP_RaidJoin) +E(OP_RaidUpdate) +E(OP_ReadBook) +E(OP_RecipeAutoCombine) +E(OP_RemoveBlockedBuffs) +E(OP_RequestClientZoneChange) +E(OP_RespondAA) +E(OP_RezzRequest) +E(OP_SendAATable) +E(OP_SendCharInfo) +E(OP_SendMembership) +E(OP_SendZonepoints) +E(OP_SetGuildRank) +E(OP_ShopPlayerBuy) +E(OP_ShopPlayerSell) +E(OP_ShopRequest) +E(OP_SkillUpdate) +E(OP_SomeItemPacketMaybe) +E(OP_SpawnAppearance) +E(OP_SpawnDoor) +E(OP_Stun) +E(OP_TargetBuffs) +E(OP_TaskDescription) +E(OP_TaskHistoryReply) +E(OP_Track) +E(OP_Trader) +E(OP_TraderBuy) +E(OP_TributeInfo) +E(OP_TributeItem) +E(OP_VetRewardsAvaliable) +E(OP_WearChange) +E(OP_WhoAllResponse) +E(OP_ZoneChange) +E(OP_ZoneEntry) +E(OP_ZonePlayerToBind) +E(OP_ZoneServerInfo) +E(OP_ZoneSpawns) +// Begin RoF Decodes +D(OP_AdventureMerchantSell) +D(OP_AltCurrencySell) +D(OP_AltCurrencySellSelection) +D(OP_ApplyPoison) +D(OP_AugmentInfo) +D(OP_AugmentItem) +D(OP_BazaarSearch) +D(OP_BlockedBuffs) +D(OP_Buff) +D(OP_BuffRemoveRequest) +D(OP_CastSpell) +D(OP_ChannelMessage) +D(OP_CharacterCreate) +D(OP_ClientUpdate) +D(OP_Consider) +D(OP_ConsiderCorpse) +D(OP_Consume) +D(OP_Damage) +D(OP_DeleteItem) +D(OP_EnvDamage) +D(OP_FaceChange) +D(OP_FindPersonRequest) +D(OP_GMLastName) +D(OP_GroupCancelInvite) +D(OP_GroupDisband) +D(OP_GroupFollow) +D(OP_GroupFollow2) +D(OP_GroupInvite) +D(OP_GroupInvite2) +D(OP_GuildDemote) +D(OP_GuildRemove) +D(OP_GuildStatus) +D(OP_InspectRequest) +D(OP_ItemLinkClick) +D(OP_ItemVerifyRequest) +D(OP_LoadSpellSet) +D(OP_LootItem) +D(OP_MoveItem) +D(OP_PetCommands) +D(OP_RaidInvite) +D(OP_ReadBook) +D(OP_RecipeAutoCombine) +D(OP_RemoveBlockedBuffs) +D(OP_RezzAnswer) +D(OP_Save) +D(OP_SetServerFilter) +D(OP_ShopPlayerBuy) +D(OP_ShopPlayerSell) +D(OP_ShopRequest) +D(OP_Trader) +D(OP_TraderBuy) +D(OP_TradeSkillCombine) +D(OP_TributeItem) +D(OP_WhoAllRequest) +D(OP_ZoneChange) +D(OP_ZoneEntry) +// End RoF Encodes/Decodes +#undef E +#undef D diff --git a/common/patches/rof2_structs.h b/common/patches/rof2_structs.h new file mode 100644 index 000000000..b75faee91 --- /dev/null +++ b/common/patches/rof2_structs.h @@ -0,0 +1,4922 @@ +#ifndef RoF2_STRUCTS_H_ +#define RoF2_STRUCTS_H_ + +namespace RoF2 { + namespace structs { + +/* +** Compiler override to ensure +** byte aligned structures +*/ +#pragma pack(1) + +struct LoginInfo_Struct { +/*000*/ char login_info[64]; +/*064*/ uint8 unknown064[124]; +/*188*/ uint8 zoning; // 01 if zoning, 00 if not +/*189*/ uint8 unknown189[275]; +/*488*/ +}; + +struct EnterWorld_Struct { +/*000*/ char name[64]; +/*064*/ uint32 tutorial; // 01 on "Enter Tutorial", 00 if not +/*068*/ uint32 return_home; // 01 on "Return Home", 00 if not +}; + +//New For SoF +struct WorldObjectsSent_Struct { +}; + +// New for RoF2 - Size: 12 +struct ItemSlotStruct { +/*000*/ int16 SlotType; // Worn and Normal inventory = 0, Bank = 1, Shared Bank = 2, Delete Item = -1 +/*002*/ int16 Unknown02; +/*004*/ int16 MainSlot; +/*006*/ int16 SubSlot; +/*008*/ int16 AugSlot; // Guessing - Seen 0xffff +/*010*/ int16 Unknown01; // Normally 0 - Seen 13262 when deleting an item, but didn't match item ID +/*012*/ +}; + +// New for RoF2 - Used for Merchant_Purchase_Struct +// Can't sellfrom other than main inventory so Slot Type is not needed. +struct MainInvItemSlotStruct { +/*000*/ int16 MainSlot; +/*002*/ int16 SubSlot; +/*004*/ int16 AugSlot; +/*006*/ int16 Unknown01; +/*008*/ +}; + +/* Name Approval Struct */ +/* Len: */ +/* Opcode: 0x8B20*/ +struct NameApproval +{ + char name[64]; + uint32 race; + uint32 class_; + uint32 deity; +}; + +/* +** Entity identification struct +** Size: 4 bytes +** OPCodes: OP_DeleteSpawn, OP_Assist +*/ +struct EntityId_Struct +{ +/*00*/ uint32 entity_id; +/*04*/ +}; + +struct Duel_Struct +{ + uint32 duel_initiator; + uint32 duel_target; +}; + +struct DuelResponse_Struct +{ + uint32 target_id; + uint32 entity_id; + uint32 unknown; +}; + +//Adventure stuff,not a net one,just one for our use +static const uint32 ADVENTURE_COLLECT = 0; +static const uint32 ADVENTURE_MASSKILL = 1; +static const uint32 ADVENTURE_NAMED = 2; +static const uint32 ADVENTURE_RESCUE = 3; + +static const uint32 BUFF_COUNT = 42; // was 25 +static const uint32 BLOCKED_BUFF_COUNT = 30; // was 20 + +static const uint32 MAX_PLAYER_TRIBUTES = 5; +static const uint32 MAX_TRIBUTE_TIERS = 10; +static const uint32 TRIBUTE_NONE = 0xFFFFFFFF; + +static const uint32 MAX_PLAYER_BANDOLIER = 20; +static const uint32 MAX_PLAYER_BANDOLIER_ITEMS = 4; + +static const uint32 MAX_POTIONS_IN_BELT = 5; + +static const uint32 MAX_GROUP_LEADERSHIP_AA_ARRAY = 16; +static const uint32 MAX_RAID_LEADERSHIP_AA_ARRAY = 16; +static const uint32 MAX_LEADERSHIP_AA_ARRAY = (MAX_GROUP_LEADERSHIP_AA_ARRAY+MAX_RAID_LEADERSHIP_AA_ARRAY); + +static const uint32 MAX_NUMBER_GUILDS = 1500; + +// Used primarily in the Player Profile: +static const uint32 MAX_PP_LANGUAGE = 32; // was 25 +static const uint32 MAX_PP_SPELLBOOK = 720; // was 480 +static const uint32 MAX_PP_MEMSPELL = 16; // was 12 +static const uint32 MAX_PP_SKILL = PACKET_SKILL_ARRAY_SIZE; // 100 - actual skills buffer size +static const uint32 MAX_PP_AA_ARRAY = 300; +static const uint32 MAX_PP_DISCIPLINES = 200; // was 100 +static const uint32 MAX_GROUP_MEMBERS = 6; +static const uint32 MAX_RECAST_TYPES = 20; + +struct AdventureInfo { + uint32 QuestID; + uint32 NPCID; + bool in_use; + uint32 status; + bool ShowCompass; + uint32 Objetive;// can be item to collect,mobs to kill,boss to kill and someone to rescue. + uint32 ObjetiveValue;// number of items,or number of needed mob kills. + char text[512]; + uint8 type; + uint32 minutes; + uint32 points; + float x; + float y; + uint32 zoneid; + uint32 zonedungeonid; +}; +/////////////////////////////////////////////////////////////////////////////// + + +/* +** Color_Struct +** Size: 4 bytes +** Used for convenience +** Merth: Gave struct a name so gcc 2.96 would compile +** +*/ +struct Color_Struct +{ + union + { + struct + { + uint8 blue; + uint8 green; + uint8 red; + uint8 use_tint; // if there's a tint this is FF + } rgb; + uint32 color; + }; +}; + +struct CharSelectEquip { + //totally guessed; + uint32 equip0; + uint32 equip1; + uint32 equip2; + uint32 itemid; + uint32 equip3; + Color_Struct color; +}; + +struct CharacterSelectEntry_Struct { +/*0000*/ char name[1]; // Name null terminated +/*0000*/ uint8 class_; +/*0000*/ uint32 race; +/*0000*/ uint8 level; +/*0000*/ uint8 class_2; +/*0000*/ uint32 race2; +/*0000*/ uint16 zone; +/*0000*/ uint16 instance; +/*0000*/ uint8 gender; +/*0000*/ uint8 face; +/*0000*/ CharSelectEquip equip[9]; +/*0000*/ uint8 u15; // Seen FF +/*0000*/ uint8 u19; // Seen FF +/*0000*/ uint32 drakkin_tattoo; +/*0000*/ uint32 drakkin_details; +/*0000*/ uint32 deity; +/*0000*/ uint32 primary; +/*0000*/ uint32 secondary; +/*0000*/ uint8 haircolor; +/*0000*/ uint8 beardcolor; +/*0000*/ uint8 eyecolor1; +/*0000*/ uint8 eyecolor2; +/*0000*/ uint8 hairstyle; +/*0000*/ uint8 beard; +/*0000*/ uint8 char_enabled; +/*0000*/ uint8 tutorial; // Seen 1 for new char or 0 for existing +/*0000*/ uint32 drakkin_heritage; +/*0000*/ uint8 unknown1; // Seen 0 +/*0000*/ uint8 gohome; // Seen 0 for new char and 1 for existing +/*0000*/ uint32 LastLogin; +/*0000*/ uint8 unknown2; // Seen 0 +}; + +/* +** Character Selection Struct +** +*/ +struct CharacterSelect_Struct { +/*000*/ uint32 char_count; //number of chars in this packet +/*004*/ CharacterSelectEntry_Struct entries[0]; +}; + +struct Membership_Entry_Struct +{ +/*000*/ uint32 purchase_id; // Seen 1, then increments 90287 to 90300 +/*004*/ uint32 bitwise_entry; // Seen 16 to 65536 - Skips 4096 +/*008*/ +}; + +struct Membership_Setting_Struct +{ +/*000*/ uint32 setting_index; // 0, 1, or 2 +/*004*/ uint32 setting_id; // 0 to 21 +/*008*/ int32 setting_value; // All can be 0, 1, or -1 +/*012*/ +}; + +struct Membership_Details_Struct +{ +/*0000*/ uint32 membership_setting_count; // Seen 66 +/*0016*/ Membership_Setting_Struct settings[66]; +/*0012*/ uint32 race_entry_count; // Seen 15 +/*1044*/ Membership_Entry_Struct membership_races[15]; +/*0012*/ uint32 class_entry_count; // Seen 15 +/*1044*/ Membership_Entry_Struct membership_classes[15]; +/*1044*/ uint32 exit_url_length; // Length of the exit_url string (0 for none) +/*1048*/ //char exit_url[42]; // Upgrade to Silver or Gold Membership URL +/*1048*/ uint32 exit_url_length2; // Length of the exit_url2 string (0 for none) +/*0000*/ //char exit_url2[49]; // Upgrade to Gold Membership URL +}; + +struct Membership_Struct +{ +/*000*/ uint32 membership; // Seen 2 on Gold Account +/*004*/ uint32 races; // Seen ff ff 01 00 +/*008*/ uint32 classes; // Seen ff ff 01 01 +/*012*/ uint32 entrysize; // Seen 22 +/*016*/ int32 entries[22]; // Most -1, 1, and 0 for Gold Status +/*104*/ +}; + + +/* +* Visible equiptment. +* Size: 20 Octets +*/ +struct EquipStruct { +/*00*/ uint32 equip0; +/*04*/ uint32 equip1; +/*08*/ uint32 equip2; +/*12*/ uint32 itemId; +/*16*/ uint32 equip3; // Same as equip0? +/*20*/ +}; + + +/* +** Generic Spawn Struct +** Length: 897 Octets +** Used in: +** spawnZoneStruct +** dbSpawnStruct +** petStruct +** newSpawnStruct +*/ +/* +showeq -> eqemu +sed -e 's/_t//g' -e 's/seto_0xFF/set_to_0xFF/g' +*/ + +struct Spawn_Struct_Bitfields +{ +/*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; +/*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 +/*22*/ unsigned linkdead:1; // 1 Toggles LD on or off after name. Correct for RoF2 +/*23*/ unsigned showhelm:1; +/*24*/ unsigned unknown24:1; // Prefixes name with ! +/*25*/ unsigned trader:1; +/*26*/ unsigned unknown26: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 + /* + // Unknown in RoF2 + unsigned betabuffed:1; + unsigned buyer:1; + unsigned buyer:1; + */ +}; + +/* +struct Spawn_Struct_Position +{ + signed padding0000:12; + signed y:19; + signed padding0001:1; + + signed deltaX:13; // change in x + signed deltaHeading:10;// change in heading + signed padding0008:9; + + signed deltaY:13; + signed z:19; + + signed x:19; + signed animation:10; // animation + signed padding0016:3; + + signed heading:12; + signed deltaZ:13; // change in z + signed padding0020:7; +}; +*/ + +struct Spawn_Struct_Position +{ + signed padding0000:12; + signed y:19; + signed padding0001:1; + + signed deltaZ:13; // change in z + signed deltaX:13; // change in x + signed padding0008:6; + + signed x:19; + signed heading:12; + signed padding0016:1; + + signed deltaHeading:10;// change in heading + signed z:19; + signed padding0020:3; + + signed animation:10; // animation + signed deltaY:13; + signed padding0023:9; +}; + +struct Spawn_Struct +{ +// Note this struct is not used as such, it is here for reference. As the struct is variable sized, the packets +// are constructed in Live.cpp +// +/*0000*/ char name[1]; //name[64]; +/*0000*/ //uint8 nullterm1; // hack to null terminate name +/*0064*/ uint32 spawnId; +/*0068*/ uint8 level; +/*0069*/ float unknown1; +/*0073*/ uint8 NPC; // 0=player,1=npc,2=pc corpse,3=npc corpse + Spawn_Struct_Bitfields Bitfields; +/*0000*/ uint8 otherData; // & 4 - has title, & 8 - has suffix, & 1 - it's a chest or untargetable +/*0000*/ float unknown3; // seen -1 +/*0000*/ float unknown4; +/*0000*/ float size; +/*0000*/ uint8 face; +/*0000*/ float walkspeed; +/*0000*/ float runspeed; +/*0000*/ uint32 race; +/*0000*/ uint8 showname; // for body types - was charProperties +/*0000*/ uint32 bodytype; +/*0000*/ //uint32 bodytype2; // this is only present if charProperties==2 + // are there more than two possible properties? +/*0000*/ uint8 curHp; +/*0000*/ uint8 haircolor; +/*0000*/ uint8 beardcolor; +/*0000*/ uint8 eyecolor1; +/*0000*/ uint8 eyecolor2; +/*0000*/ uint8 hairstyle; +/*0000*/ uint8 beard; +/*0000*/ uint32 drakkin_heritage; +/*0000*/ uint32 drakkin_tattoo; +/*0000*/ uint32 drakkin_details; +/*0000*/ uint8 statue; // was holding +/*0000*/ uint32 deity; +/*0000*/ uint32 guildID; +/*0000*/ uint32 guildrank; // 0=member, 1=officer, 2=leader, -1=not guilded +/*0000*/ uint8 class_; +/*0000*/ uint8 pvp; // 0 = normal name color, 2 = PVP name color +/*0000*/ uint8 StandState; // stand state - 0x64 for normal animation +/*0000*/ uint8 light; +/*0000*/ uint8 flymode; +/*0000*/ uint8 equip_chest2; +/*0000*/ uint8 unknown9; +/*0000*/ uint8 unknown10; +/*0000*/ uint8 helm; +/*0000*/ char lastName[1]; +/*0000*/ //uint8 lastNameNull; //hack! +/*0000*/ uint32 aatitle; // 0=none, 1=general, 2=archtype, 3=class was AARank +/*0000*/ uint8 unknown12; +/*0000*/ uint32 petOwnerId; +/*0000*/ uint8 unknown13; +/*0000*/ uint32 unknown14; // Stance 64 = normal 4 = aggressive 40 = stun/mezzed +/*0000*/ uint32 unknown15; +/*0000*/ uint32 unknown16; +/*0000*/ uint32 unknown17; +/*0000*/ //uint8 unknownRoF23; +/*0000*/ uint32 unknown18; +/*0000*/ uint32 unknown19; + Spawn_Struct_Position Position; +/*0000*/ union + { + struct + { + /*0000*/ Color_Struct color_helmet; // Color of helmet item + /*0000*/ Color_Struct color_chest; // Color of chest item + /*0000*/ Color_Struct color_arms; // Color of arms item + /*0000*/ Color_Struct color_bracers; // Color of bracers item + /*0000*/ Color_Struct color_hands; // Color of hands item + /*0000*/ Color_Struct color_legs; // Color of legs item + /*0000*/ Color_Struct color_feet; // Color of feet item + /*0000*/ Color_Struct color_primary; // Color of primary item + /*0000*/ Color_Struct color_secondary; // Color of secondary item + } equipment_colors; + /*0000*/ Color_Struct colors[9]; // Array elements correspond to struct equipment_colors above + }; + +// skip these bytes if not a valid player race +/*0000*/ union + { + struct + { + /*0000*/ EquipStruct equip_helmet; // Equiptment: Helmet visual + /*0000*/ EquipStruct equip_chest; // Equiptment: Chest visual + /*0000*/ EquipStruct equip_arms; // Equiptment: Arms visual + /*0000*/ EquipStruct equip_bracers; // Equiptment: Wrist visual + /*0000*/ EquipStruct equip_hands; // Equiptment: Hands visual + /*0000*/ EquipStruct equip_legs; // Equiptment: Legs visual + /*0000*/ EquipStruct equip_feet; // Equiptment: Boots visual + /*0000*/ EquipStruct equip_primary; // Equiptment: Main visual + /*0000*/ EquipStruct equip_secondary; // Equiptment: Off visual + } equip; + /*0000*/ EquipStruct equipment[9]; + }; + +/*0000*/ //char title[0]; // only read if(hasTitleOrSuffix & 4) +/*0000*/ //char suffix[0]; // only read if(hasTitleOrSuffix & 8) + char unknown20[8]; + uint8 IsMercenary; // If NPC == 1 and this == 1, then the NPC name is Orange. +/*0000*/ char unknown21[55]; +}; + + +/* +** Generic Spawn Struct +** Fields from old struct not yet found: +** uint8 traptype; // 65 is disarmable trap, 66 and 67 are invis triggers/traps +** uint8 is_pet; // 0=no, 1=yes +** uint8 afk; // 0=no, 1=afk +** uint8 is_npc; // 0=no, 1=yes +** uint8 max_hp; // (name prolly wrong)takes on the value 100 for players, 100 or 110 for NPCs and 120 for PC corpses... +** uint8 guildrank; // 0=normal, 1=officer, 2=leader +** uint8 eyecolor2; //not sure, may be face +** uint8 aaitle; // 0=none, 1=general, 2=archtype, 3=class +*/ + +/* +** New Spawn +** Length: 176 Bytes +** OpCode: 4921 +*/ +struct NewSpawn_Struct +{ + struct Spawn_Struct spawn; // Spawn Information +}; + + +/* +** Client Zone Entry struct +** Length: 68 Octets +** OpCode: ZoneEntryCode (when direction == client) +*/ +struct ClientZoneEntry_Struct { +/*00*/ uint32 unknown00; // ***Placeholder +/*04*/ char char_name[64]; // Player firstname [32] +/*68*/ uint32 unknown68; +/*72*/ uint32 unknown72; +}; + + +/* +** Server Zone Entry Struct +** Length: 452 Bytes +** OPCodes: OP_ServerZoneEntry +** +*/ +struct ServerZoneEntry_Struct //Adjusted from SEQ Everquest.h Struct +{ + struct NewSpawn_Struct player; +}; + + +//New Zone Struct - Size: 948 +struct NewZone_Struct { +/*0000*/ char char_name[64]; // Character Name +/*0064*/ char zone_short_name[32]; // Zone Short Name +/*0096*/ char unknown0096[96]; +/*0192*/ char zone_long_name[278]; // Zone Long Name +/*0470*/ uint8 ztype; // Zone type (usually FF) +/*0471*/ uint8 fog_red[4]; // Zone fog (red) +/*0475*/ uint8 fog_green[4]; // Zone fog (green) +/*0479*/ uint8 fog_blue[4]; // Zone fog (blue) +/*0483*/ uint8 unknown323; +/*0484*/ float fog_minclip[4]; +/*0500*/ float fog_maxclip[4]; +/*0516*/ float gravity; +/*0520*/ uint8 time_type; +/*0521*/ uint8 rain_chance[4]; +/*0525*/ uint8 rain_duration[4]; +/*0529*/ uint8 snow_chance[4]; +/*0533*/ uint8 snow_duration[4]; +/*0537*/ uint8 unknown537[33]; +/*0570*/ uint8 sky; // Sky Type +/*0571*/ uint8 unknown571[13]; // ***Placeholder +/*0584*/ float zone_exp_multiplier; // Experience Multiplier +/*0588*/ float safe_y; // Zone Safe Y +/*0592*/ float safe_x; // Zone Safe X +/*0596*/ float safe_z; // Zone Safe Z +/*0600*/ float min_z; // Guessed - NEW - Seen 0 +/*0604*/ float max_z; // Guessed +/*0608*/ float underworld; // Underworld, min z (Not Sure?) +/*0612*/ float minclip; // Minimum View Distance +/*0616*/ float maxclip; // Maximum View DIstance +/*0620*/ uint8 unknown620[84]; // ***Placeholder +/*0704*/ char zone_short_name2[96]; //zone file name? excludes instance number which can be in previous version. +/*0800*/ int32 unknown800; //seen -1 +/*0804*/ char unknown804[40]; // +/*0844*/ int32 unknown844; //seen 600 +/*0848*/ int32 unknown848; +/*0852*/ uint16 zone_id; +/*0854*/ uint16 zone_instance; +/*0856*/ char unknown856[20]; +/*0876*/ uint32 SuspendBuffs; +/*0880*/ uint32 unknown880; // Seen 50 +/*0884*/ uint32 unknown884; // Seen 10 +/*0888*/ uint8 unknown888; // Seen 1 +/*0889*/ uint8 unknown889; // Seen 0 (POK) or 1 (rujj) +/*0890*/ uint8 unknown890; // Seen 1 +/*0891*/ uint8 unknown891; // Seen 0 +/*0892*/ uint8 unknown892; // Seen 0 +/*0893*/ uint8 unknown893; // Seen 0 - 00 +/*0894*/ uint8 fall_damage; // 0 = Fall Damage on, 1 = Fall Damage off +/*0895*/ uint8 unknown895; // Seen 0 - 00 +/*0896*/ uint32 unknown896; // Seen 180 +/*0900*/ uint32 unknown900; // Seen 180 +/*0904*/ uint32 unknown904; // Seen 180 +/*0908*/ uint32 unknown908; // Seen 2 +/*0912*/ uint32 unknown912; // Seen 2 +/*0916*/ float FogDensity; // Most zones have this set to 0.33 Blightfire had 0.16 +/*0920*/ uint32 unknown920; // Seen 0 +/*0924*/ uint32 unknown924; // Seen 0 +/*0928*/ uint32 unknown928; // Seen 0 +/*0932*/ int32 unknown932; // Seen -1 +/*0936*/ int32 unknown936; // Seen -1 +/*0940*/ uint32 unknown940; // Seen 0 +/*0944*/ float unknown944; // Seen 1.0 +/*0948*/ +}; + + +/* +** Memorize Spell Struct +** Length: 16 Bytes +** +*/ +struct MemorizeSpell_Struct { +uint32 slot; // Spot in the spell book/memorized slot +uint32 spell_id; // Spell id (200 or c8 is minor healing, etc) +uint32 scribing; // 1 if memorizing a spell, set to 0 if scribing to book, 2 if un-memming +uint32 unknown12; +}; + +/* +** Make Charmed Pet +** Length: 12 Bytes +** +*/ +struct Charm_Struct { +/*00*/ uint32 owner_id; +/*04*/ uint32 pet_id; +/*08*/ uint32 command; // 1: make pet, 0: release pet +/*12*/ +}; + +struct InterruptCast_Struct +{ + uint32 spawnid; + uint32 messageid; + //char message[0]; +}; + +struct DeleteSpell_Struct +{ +/*000*/int16 spell_slot; +/*002*/uint8 unknowndss002[2]; +/*004*/uint8 success; +/*005*/uint8 unknowndss006[3]; +/*008*/ +}; + +struct ManaChange_Struct +{ + uint32 new_mana; // New Mana AMount + uint32 stamina; + uint32 spell_id; + uint32 unknown12; + uint32 unknown16; +}; + +struct SwapSpell_Struct +{ + uint32 from_slot; + uint32 to_slot; +}; + +struct BeginCast_Struct +{ +/*000*/ uint32 spell_id; +/*004*/ uint16 caster_id; +/*006*/ uint32 cast_time; // in miliseconds +/*010*/ +}; + +struct CastSpell_Struct +{ +/*00*/ uint32 slot; +/*04*/ uint32 spell_id; +/*08*/ ItemSlotStruct inventoryslot; // slot for clicky item, Seen unknown of 131 = normal cast +/*20*/ uint32 target_id; +/*24*/ uint32 cs_unknown[5]; +/*44*/ +}; + +/* +** SpawnAppearance_Struct +** Changes client appearance for all other clients in zone +** Size: 8 bytes +** Used in: OP_SpawnAppearance +** +*/ +struct SpawnAppearance_Struct +{ +/*0000*/ uint16 spawn_id; // ID of the spawn +/*0002*/ uint16 type; // Values associated with the type +/*0004*/ uint32 parameter; // Type of data sent +/*0008*/ +}; + +struct SpellBuff_Struct +{ +/*000*/ uint8 slotid; // badly named... seems to be 2 for a real buff, 0 otherwise +/*001*/ float unknown004; // Seen 1 for no buff +/*005*/ uint32 player_id; // 'global' ID of the caster, for wearoff messages +/*009*/ uint32 unknown016; +/*013*/ uint8 bard_modifier; +/*014*/ uint32 duration; +/*018*/ uint8 level; +/*019*/ uint32 spellid; +/*023*/ uint32 counters; +/*027*/ uint8 unknown0028[53]; +/*080*/ +}; + +struct SpellBuff_Struct_Old +{ +/*000*/ uint8 slotid; // badly named... seems to be 2 for a real buff, 0 otherwise +/*001*/ uint8 level; +/*002*/ uint8 bard_modifier; +/*003*/ uint8 effect; // not real +/*004*/ float unknown004; // Seen 1 for no buff +/*008*/ uint32 spellid; +/*012*/ uint32 duration; +/*016*/ uint32 unknown016; +/*020*/ uint32 player_id; // 'global' ID of the caster, for wearoff messages +/*024*/ uint32 counters; +/*028*/ uint8 unknown0028[60]; +/*088*/ +}; + +// Not functional yet, but this is what the packet looks like on Live +struct SpellBuffFade_Struct_Live { +/*000*/ uint32 entityid; // Player id who cast the buff +/*004*/ uint8 unknown004; +/*005*/ uint8 level; +/*006*/ uint8 effect; +/*007*/ uint8 unknown007; +/*008*/ float unknown008; +/*012*/ uint32 spellid; +/*016*/ uint32 duration; +/*020*/ uint32 playerId; // Global player ID? +/*024*/ uint32 num_hits; +/*028*/ uint8 unknown0028[64]; +/*092*/ uint32 slotid; +/*096*/ uint32 bufffade; +/*100*/ +}; + +struct SpellBuffFade_Struct { +/*000*/ uint32 entityid; +/*004*/ uint8 slot; +/*005*/ uint8 level; +/*006*/ uint8 effect; +/*007*/ uint8 unknown7; +/*008*/ uint32 spellid; +/*012*/ uint32 duration; +/*016*/ uint32 num_hits; +/*020*/ uint32 unknown020; // Global player ID? +/*024*/ uint32 playerId; // Player id who cast the buff +/*028*/ uint32 slotid; +/*032*/ uint32 bufffade; +/*036*/ +}; + +struct BuffRemoveRequest_Struct +{ +/*00*/ uint32 SlotID; +/*04*/ uint32 EntityID; +/*08*/ +}; + +#if 0 +// not in use +struct BuffIconEntry_Struct { +/*000*/ uint32 buff_slot; +/*004*/ uint32 spell_id; +/*008*/ uint32 tics_remaining; +/*012*/ uint32 num_hits; +// char name[0]; caster name is also here sometimes +// uint8 unknownend; 1 when single, 0 when all opposite of all_buffs? +}; + +// not in use +struct BuffIcon_Struct { +/*000*/ uint32 entity_id; +/*004*/ uint32 unknown004; +/*008*/ uint8 all_buffs; // 1 when updating all buffs, 0 when doing one +/*009*/ uint16 count; +/*011*/ BuffIconEntry_Struct entires[0]; +}; +#endif + +struct GMTrainee_Struct +{ + /*000*/ uint32 npcid; + /*004*/ uint32 playerid; + /*008*/ uint32 skills[PACKET_SKILL_ARRAY_SIZE]; + /*408*/ uint8 unknown408[40]; + /*448*/ +}; + +struct GMTrainEnd_Struct +{ + /*000*/ uint32 npcid; + /*004*/ uint32 playerid; + /*008*/ +}; + +struct GMSkillChange_Struct { +/*000*/ uint16 npcid; +/*002*/ uint8 unknown1[2]; // something like PC_ID, but not really. stays the same thru the session though +/*002*/ uint16 skillbank; // 0 if normal skills, 1 if languages +/*002*/ uint8 unknown2[2]; +/*008*/ uint16 skill_id; +/*010*/ uint8 unknown3[2]; +/*012*/ +}; + +struct GMTrainSkillConfirm_Struct { // SoF+ only +/*000*/ uint32 SkillID; +/*004*/ uint32 Cost; +/*008*/ uint8 NewSkill; // Set to 1 for 'You have learned the basics' message. +/*009*/ char TrainerName[64]; +/*073*/ uint8 Unknown073[3]; +/*076*/ +}; + +struct ConsentResponse_Struct { + char grantname[64]; + char ownername[64]; + uint8 permission; + char zonename[64]; +}; + +/* +** Name Generator Struct +** Length: 72 bytes +** OpCode: 0x0290 +*/ +struct NameGeneration_Struct +{ +/*0000*/ uint32 race; +/*0004*/ uint32 gender; +/*0008*/ char name[64]; +/*0072*/ +}; + +/* +** Character Creation struct +** Length: 96 Bytes +*/ +struct CharCreate_Struct +{ +/*0000*/ uint32 gender; +/*0004*/ uint32 race; +/*0008*/ uint32 class_; +/*0012*/ uint32 deity; +/*0016*/ uint32 start_zone; +/*0020*/ uint32 haircolor; +/*0024*/ uint32 beard; +/*0028*/ uint32 beardcolor; +/*0032*/ uint32 hairstyle; +/*0036*/ uint32 face; +/*0040*/ uint32 eyecolor1; +/*0044*/ uint32 eyecolor2; +/*0048*/ uint32 drakkin_heritage; +/*0052*/ uint32 drakkin_tattoo; +/*0056*/ uint32 drakkin_details; +/*0060*/ uint32 STR; +/*0064*/ uint32 STA; +/*0068*/ uint32 AGI; +/*0073*/ uint32 DEX; +/*0076*/ uint32 WIS; +/*0080*/ uint32 INT; +/*0084*/ uint32 CHA; +/*0088*/ uint32 tutorial; +/*0092*/ uint32 unknown0092; +/*0096*/ +}; + +/* +** Character Creation struct +** Length: 0 Bytes +** OpCode: 0x +*/ +struct CharCreate_Struct_Temp //Size is now 0 +{ +}; + +/* + *Used in PlayerProfile + */ +struct AA_Array +{ + uint32 AA; + uint32 value; + uint32 unknown08; // Looks like AA_Array is now 12 bytes in Live +}; + +struct Disciplines_Struct { + uint32 values[MAX_PP_DISCIPLINES]; +}; + + + +struct Tribute_Struct { + uint32 tribute; + uint32 tier; +}; + +struct BandolierItem_Struct { + char item_name[1]; // Variable Length + uint32 item_id; + uint32 icon; +}; + +//len = 72 +struct BandolierItem_Struct_Old { + uint32 item_id; + uint32 icon; + char item_name[64]; +}; + +//len = 320 +enum { //bandolier item positions + bandolierMainHand = 0, + bandolierOffHand, + bandolierRange, + bandolierAmmo +}; +struct Bandolier_Struct { + char name[1]; // Variable Length + BandolierItem_Struct items[MAX_PLAYER_BANDOLIER_ITEMS]; +}; + +struct Bandolier_Struct_Old { + char name[32]; + BandolierItem_Struct items[MAX_PLAYER_BANDOLIER_ITEMS]; +}; + +struct PotionBelt_Struct { + BandolierItem_Struct items[MAX_POTIONS_IN_BELT]; +}; + +struct GroupLeadershipAA_Struct { + union { + struct { + uint32 groupAAMarkNPC; + uint32 groupAANPCHealth; + uint32 groupAADelegateMainAssist; + uint32 groupAADelegateMarkNPC; + uint32 groupAA4; + uint32 groupAA5; + uint32 groupAAInspectBuffs; + uint32 groupAA7; + uint32 groupAASpellAwareness; + uint32 groupAAOffenseEnhancement; + uint32 groupAAManaEnhancement; + uint32 groupAAHealthEnhancement; + uint32 groupAAHealthRegeneration; + uint32 groupAAFindPathToPC; + uint32 groupAAHealthOfTargetsTarget; + uint32 groupAA15; + }; + uint32 ranks[MAX_GROUP_LEADERSHIP_AA_ARRAY]; + }; +}; + +struct RaidLeadershipAA_Struct { + union { + struct { + uint32 raidAAMarkNPC; + uint32 raidAANPCHealth; + uint32 raidAADelegateMainAssist; + uint32 raidAADelegateMarkNPC; + uint32 raidAA4; + uint32 raidAA5; + uint32 raidAA6; + uint32 raidAASpellAwareness; + uint32 raidAAOffenseEnhancement; + uint32 raidAAManaEnhancement; + uint32 raidAAHealthEnhancement; + uint32 raidAAHealthRegeneration; + uint32 raidAAFindPathToPC; + uint32 raidAAHealthOfTargetsTarget; + uint32 raidAA14; + uint32 raidAA15; + }; + uint32 ranks[MAX_RAID_LEADERSHIP_AA_ARRAY]; + }; +}; + +struct LeadershipAA_Struct { + union { + struct { + GroupLeadershipAA_Struct group; + RaidLeadershipAA_Struct raid; + }; + uint32 ranks[MAX_LEADERSHIP_AA_ARRAY]; + }; +}; + + /** +* A bind point. +* Size: 20 Octets +*/ +struct BindStruct { + /*000*/ uint32 zoneId; + /*004*/ float x; + /*008*/ float y; + /*012*/ float z; + /*016*/ float heading; + /*020*/ +}; + + +// Player Profile - Variable Length as of Oct 12 2012 patch +struct PlayerProfile_Struct +{ +/*00000*/ uint32 checksum; // +/*00004*/ uint32 checksum_size; // Value = ( Packet Size - 9 ) +/*00008*/ uint8 checksum_byte; // +/*00009*/ uint8 unknown_rof1[3]; // +/*00012*/ uint32 unknown_rof2; // +/*00016*/ uint8 gender; // Player Gender - 0 Male, 1 Female +/*00017*/ uint32 race; // Player race +/*00021*/ uint8 class_; // Player class +/*00022*/ uint8 level; // Level of player +/*00023*/ uint8 level1; // Level of player (again?) +/*00024*/ uint32 bind_count; // Seen 5 +/*00028*/ BindStruct binds[5]; // Bind points (primary is first) 5 fields = 100 bytes +/*00128*/ uint32 deity; // deity +/*00132*/ uint32 unknown_rof3; // Maybe Drakkin Heritage? +/*00136*/ uint32 unknown4_count; // Seen 10 +/*00140*/ uint32 unknown_rof4[10]; // Seen all 0s +/*00180*/ uint32 equip_count; // Seen 22 +union +{ + struct + { + /*00184*/ EquipStruct equip_helmet; // Equiptment: Helmet visual + /*00204*/ EquipStruct equip_chest; // Equiptment: Chest visual + /*00224*/ EquipStruct equip_arms; // Equiptment: Arms visual + /*00244*/ EquipStruct equip_bracers; // Equiptment: Wrist visual + /*00264*/ EquipStruct equip_hands; // Equiptment: Hands visual + /*00284*/ EquipStruct equip_legs; // Equiptment: Legs visual + /*00304*/ EquipStruct equip_feet; // Equiptment: Boots visual + /*00324*/ EquipStruct equip_primary; // Equiptment: Main visual + /*00344*/ EquipStruct equip_secondary; // Equiptment: Off visual + // Below slots are just guesses, but all 0s anyway... + /*00364*/ EquipStruct equip_charm; // Equiptment: Non-visual + /*00384*/ EquipStruct equip_ear1; // Equiptment: Non-visual + /*00404*/ EquipStruct equip_ear2; // Equiptment: Non-visual + /*00424*/ EquipStruct equip_face; // Equiptment: Non-visual + /*00444*/ EquipStruct equip_neck; // Equiptment: Non-visual + /*00464*/ EquipStruct equip_shoulder; // Equiptment: Non-visual + /*00484*/ EquipStruct equip_bracer2; // Equiptment: Non-visual + /*00504*/ EquipStruct equip_range; // Equiptment: Non-visual + /*00524*/ EquipStruct equip_ring1; // Equiptment: Non-visual + /*00544*/ EquipStruct equip_ring2; // Equiptment: Non-visual + /*00564*/ EquipStruct equip_waist; // Equiptment: Non-visual + /*00584*/ EquipStruct equip_powersource; // Equiptment: Non-visual + /*00604*/ EquipStruct equip_ammo; // Equiptment: Non-visual + } equip; + /*00184*/ EquipStruct equipment[22]; +}; +/*00624*/ uint32 equip2_count; // Seen 9 +/*00628*/ EquipStruct equipment2[9]; // Appears to be Visible slots, but all 0s +/*00808*/ uint32 tint_count; // Seen 9 +/*00812*/ Color_Struct item_tint[9]; // RR GG BB 00 +/*00848*/ uint32 tint_count2; // Seen 9 +/*00852*/ Color_Struct item_tint2[9]; // RR GG BB 00 +/*00888*/ uint8 haircolor; // Player hair color +/*00889*/ uint8 beardcolor; // Player beard color +/*00890*/ uint32 unknown_rof5; // +/*00894*/ uint8 eyecolor1; // Player left eye color +/*00895*/ uint8 eyecolor2; // Player right eye color +/*00896*/ uint8 hairstyle; // Player hair style +/*00897*/ uint8 beard; // Player beard type +/*00898*/ uint8 face; // Player face +/*00899*/ uint32 drakkin_heritage; // +/*00903*/ uint32 drakkin_tattoo; // +/*00907*/ uint32 drakkin_details; // +/*00911*/ uint8 unknown_rof6; // +/*00912*/ int8 unknown_rof7; // seen -1 +/*00913*/ uint8 unknown_rof8; // +/*00914*/ uint8 unknown_rof9; // +/*00915*/ uint8 unknown_rof10; // Seen 1 or 0 (on a female?) +/*00916*/ float height; // Seen 7.0 (barb), 5.0 (woodelf), 5.5 (halfelf) +/*00920*/ float unknown_rof11; // Seen 3.0 +/*00924*/ float unknown_rof12; // Seen 2.5 +/*00928*/ float unknown_rof13; // Seen 5.5 +/*00932*/ uint32 primary; // Seen 10528 +/*00936*/ uint32 secondary; // Seen 10006 +/*00940*/ uint32 points; // Unspent Practice points - RELOCATED??? +/*00944*/ uint32 mana; // Current mana +/*00948*/ uint32 cur_hp; // Current HP without +HP equipment +/*00952*/ uint32 STR; // Strength - 6e 00 00 00 - 110 +/*00956*/ uint32 STA; // Stamina - 73 00 00 00 - 115 +/*00960*/ uint32 CHA; // Charisma - 37 00 00 00 - 55 +/*00964*/ uint32 DEX; // Dexterity - 50 00 00 00 - 80 +/*00968*/ uint32 INT; // Intelligence - 3c 00 00 00 - 60 +/*00972*/ uint32 AGI; // Agility - 5f 00 00 00 - 95 +/*00976*/ uint32 WIS; // Wisdom - 46 00 00 00 - 70 +/*00980*/ uint32 unknown_rof14[7]; // Probably base resists? +/*01008*/ uint32 aa_count; // Seen 300 +/*01012*/ AA_Array aa_array[MAX_PP_AA_ARRAY]; // [300] 3600 bytes - AAs 12 bytes each +/*04612*/ uint32 skill_count; // Seen 100 +/*04616*/ uint32 skills[MAX_PP_SKILL]; // [100] 400 bytes - List of skills +/*05016*/ uint32 unknown15_count; // Seen 25 +/*05020*/ uint32 unknown_rof15[25]; // Most are 255 or 0 +/*05120*/ uint32 discipline_count; // Seen 200 +/*05124*/ Disciplines_Struct disciplines; // [200] 800 bytes Known disciplines +/*05924*/ uint32 timestamp_count; // Seen 20 +/*05928*/ uint32 timestamps[20]; // Unknown Unix Timestamps - maybe recast related? +/*06008*/ uint32 recast_count; // Seen 20 +/*06012*/ uint32 recastTimers[MAX_RECAST_TYPES];// [20] 80 bytes - Timers (UNIX Time of last use) +/*06092*/ uint32 timestamp2_count; // Seen 100 +/*06096*/ uint32 timestamps2[100]; // Unknown Unix Timestamps - maybe Skill related? +/*06496*/ uint32 spell_book_count; // Seen 720 +/*06500*/ uint32 spell_book[MAX_PP_SPELLBOOK]; // List of the Spells in spellbook 720 = 90 pages [2880 bytes] +/*09380*/ uint32 mem_spell_count; // Seen 16 +/*09384*/ int32 mem_spells[MAX_PP_MEMSPELL]; // [16] List of spells memorized - First 12 are set or -1 and last 4 are 0s +/*09448*/ uint32 unknown16_count; // Seen 13 +/*09452*/ uint32 unknown_rof16[13]; // Possibly spell or buff related +/*09504*/ uint8 unknown_rof17; // Seen 0 or 8 +/*09505*/ uint32 buff_count; // Seen 42 +/*09509*/ SpellBuff_Struct buffs[BUFF_COUNT]; // [42] 3360 bytes - Buffs currently on the player (42 Max) - (Each Size 80) +/*12869*/ uint32 platinum; // Platinum Pieces on player +/*12873*/ uint32 gold; // Gold Pieces on player +/*12877*/ uint32 silver; // Silver Pieces on player +/*12881*/ uint32 copper; // Copper Pieces on player +/*12885*/ uint32 platinum_cursor; // Platinum Pieces on cursor +/*12889*/ uint32 gold_cursor; // Gold Pieces on cursor +/*12893*/ uint32 silver_cursor; // Silver Pieces on cursor +/*12897*/ uint32 copper_cursor; // Copper Pieces on cursor +/*12901*/ uint32 intoxication; // Alcohol level (in ticks till sober?) - Position Guessed +/*12905*/ uint32 toxicity; // Potion Toxicity (15=too toxic, each potion adds 3) - Position Guessed +/*12909*/ uint32 unknown_rof19; // +/*12913*/ uint32 thirst_level; // Drink (ticks till next drink) - Position Guessed +/*12917*/ uint32 hunger_level; // Food (ticks till next eat) - Position Guessed +/*12921*/ uint32 aapoints_spent; // Number of spent AA points +/*12925*/ uint32 aapoint_count; // Seen 5 - Unsure what this is exactly +/*12929*/ uint32 aapoints_assigned; // Number of Assigned AA points - Seen 206 (total of the 4 fields below) +/*12933*/ uint32 aa_spent_general; // Seen 63 +/*12937*/ uint32 aa_spent_archetype; // Seen 40 +/*12941*/ uint32 aa_spent_class; // Seen 103 +/*12945*/ uint32 aa_spent_special; // Seen 0 +/*12949*/ uint32 aapoints; // Unspent AA points - Seen 1 +/*12953*/ uint16 unknown_rof20; // +/*12955*/ uint32 bandolier_count; // Seen 20 +/*12959*/ Bandolier_Struct bandoliers[MAX_PLAYER_BANDOLIER]; // [20] 740 bytes (Variable Name Sizes) - bandolier contents +/*13699*/ uint32 potionbelt_count; // Seen 5 +/*13703*/ PotionBelt_Struct potionbelt; // [5] 45 bytes potion belt - (Variable Name Sizes) +/*13748*/ int32 unknown_rof21; // Seen -1 +/*13752*/ int32 hp_total; // Guessed - Seen 20 on level 1 new mage +/*13756*/ int32 endurance_total; // Guessed - Seen 20 on level 1 new mage +/*13760*/ int32 mana_total; // Guessed - Only seen on casters so far - Matches above field if caster +/*13764*/ uint32 unknown_rof22[12]; // Same for all seen PPs - 48 bytes: +/* +19 00 00 00 19 00 00 00 19 00 00 00 0f 00 00 00 +0f 00 00 00 0f 00 00 00 0f 00 00 00 1f 85 eb 3e +33 33 33 3f 08 00 00 00 02 00 00 00 01 00 00 00 +*/ +/*13812*/ uint32 unknown_rof23; // Seen 5, 19, and 20 in examples +/*13816*/ uint32 unknown_rof24[4]; // +/*13832*/ uint32 unknown_rof25[2]; // Seen random numbers from 0 to 2165037 +/*13840*/ uint32 unknown_rof26; // Seen 106 +//END SUB-STRUCT used for shrouding. +/*13844*/ uint32 name_str_len; // Seen 64 +/*13848*/ char name[64]; // Name of player - 19960 for Live 1180 difference +/*13912*/ uint32 last_name_str_len; // Seen 32 +/*13916*/ char last_name[32]; // Last name of player +/*13948*/ uint32 birthday; // character birthday +/*13952*/ uint32 account_startdate; // Date the Account was started +/*13956*/ uint32 lastlogin; // character last save time +/*13960*/ uint32 timePlayedMin; // time character played +/*13964*/ uint32 timeentitledonaccount; +/*13968*/ uint32 expansions; // Bitmask for expansions ff 7f 00 00 - SoD +/*13972*/ uint32 language_count; // Seen 32 +/*13976*/ uint8 languages[MAX_PP_LANGUAGE]; // [32] 32 bytes - List of languages +/*14008*/ uint16 zone_id; // see zones.h +/*14010*/ uint16 zoneInstance; // Instance id +/*14012*/ float y; // Players y position (NOT positive about this switch) +/*14016*/ float x; // Players x position +/*14020*/ float z; // Players z position +/*14024*/ float heading; // Players heading +/*14028*/ uint32 air_remaining; // Air supply (seconds) +/*14032*/ int32 unknown_rof28; // Seen -1 +/*14036*/ uint8 unknown_rof29[10]; // +/*14046*/ uint32 unknown_rof30; // Random large number or 0 +/*14050*/ uint32 unknown_rof31; // +/*14054*/ uint32 unknown32_count; // Seen 5 +/*14058*/ uint8 unknown_rof32[29]; // +/*14087*/ uint32 unknown33_count; // Seen 32 for melee/hybrid and 21 for druid, 34 for mage +/*14091*/ uint32 unknown_rof33[64]; // Struct contains 2 ints, so double 32 count (Variable Sized) +// Position Varies after this point - Really starts varying at Bandolier names, but if no names set it starts here +/*00000*/ int32 unknown_rof34; // Seen -1 +/*00000*/ int32 unknown_rof35; // Seen -1 +/*00000*/ uint8 unknown_rof36[18]; // +/*00000*/ uint32 unknown37_count; // Seen 5 +/*00000*/ int32 unknown_rof37[10]; // Alternates -1 and 0 - Struct contains 2 ints +/*00000*/ uint32 unknown38_count; // Seen 10 +/*00000*/ int32 unknown_rof38[20]; // Alternates -1 and 0 - Struct contains 2 ints +/*00000*/ uint8 unknown_rof39[137]; // +/*00000*/ float unknown_rof40; // Seen 1.0 +/*00000*/ uint32 unknown_rof41[9]; // +/*00000*/ uint32 unknown_rof42; // Seen 0 or 1 +/*00000*/ uint32 unknown_string1_count; // Seen 64 +/*00000*/ char unknown_string1[64]; // +/*00000*/ uint8 unknown_rof43[30]; // +/*00000*/ uint32 unknown_string2_count; // Seen 64 +/*00000*/ char unknown_string2[64]; // +/*00000*/ uint32 unknown_string3_count; // Seen 64 +/*00000*/ char unknown_string3[64]; // +/*00000*/ uint32 unknown_rof44; // Seen 0 or 50 +/*00000*/ uint8 unknown_rof45[663]; // +/*00000*/ uint32 char_id; // Guessed based on char creation date and values +/*00000*/ uint8 unknown_rof46; // Seen 0 or 1 +/*00000*/ uint32 unknown_rof47; // Seen 6 +/*00000*/ uint32 unknown_rof48[13]; // Seen all 0s or some mix of ints and float? +/*00000*/ uint32 unknown_rof49; // Seen 64 +/*00000*/ uint8 unknown_rof50[256]; // Seen mostly 0s, but one example had a 2 in the middle +/*00000*/ uint32 unknown_rof51; // Seen 100 or 0 +/*00000*/ uint8 unknown_rof52[82]; // +/*00000*/ uint32 unknown_rof53; // Seen 50 + +uint8 unknown_rof54[1325]; // Unknown Section + +// Bottom of Struct: +/*00000*/ uint8 groupAutoconsent; // 0=off, 1=on +/*00000*/ uint8 raidAutoconsent; // 0=off, 1=on +/*00000*/ uint8 guildAutoconsent; // 0=off, 1=on +/*00000*/ uint8 unknown_rof55; // Seen 1 +/*00000*/ uint32 level3; // SoF looks at the level here to determine how many leadership AA you can bank. +/*00000*/ uint32 showhelm; // 0=no, 1=yes +/*00000*/ uint32 RestTimer; +/*00000*/ uint8 unknown_rof56; +/*00000*/ uint32 unknown_rof57; // Seen 49 +/*00000*/ uint8 unknown_rof58[1045]; // Seen all 0s + +/* +///////////////////// - Haven't identified the below fields in the PP yet +uint8 pvp; // 1=pvp, 0=not pvp +uint8 anon; // 2=roleplay, 1=anon, 0=not anon +uint8 gm; // 0=no, 1=yes (guessing!) +uint32 guild_id; // guildid +uint8 guildrank; // 0=member, 1=officer, 2=guildleader -1=no guild +uint32 guildbanker; +uint32 available_slots; +uint32 endurance; // Current endurance +uint32 spellSlotRefresh[MAX_PP_MEMSPELL]; // Refresh time (millis) - 4 bytes Each * 16 = 64 bytes +uint32 abilitySlotRefresh; +/////////////////////// + +uint32 platinum_bank; // Platinum Pieces in Bank +uint32 gold_bank; // Gold Pieces in Bank +uint32 silver_bank; // Silver Pieces in Bank +uint32 copper_bank; // Copper Pieces in Bank +uint32 platinum_shared; // Shared platinum pieces + +uint32 autosplit; // 0 = off, 1 = on + +char groupMembers[MAX_GROUP_MEMBERS][64];// 384 all the members in group, including self +char groupLeader[64]; // Leader of the group ? +uint32 entityid; + +uint32 leadAAActive; // 0 = leader AA off, 1 = leader AA on +int32 ldon_points_guk; // Earned GUK points +int32 ldon_points_mir; // Earned MIR points +int32 ldon_points_mmc; // Earned MMC points +int32 ldon_points_ruj; // Earned RUJ points +int32 ldon_points_tak; // Earned TAK points +int32 ldon_points_available;// Available LDON points +float tribute_time_remaining;// Time remaining on tribute (millisecs) +uint32 career_tribute_points;// Total favor points for this char +uint32 tribute_points; // Current tribute points +uint32 tribute_active; // 0 = off, 1=on +Tribute_Struct tributes[MAX_PLAYER_TRIBUTES]; // [40] Current tribute loadout +double group_leadership_exp; // Current group lead exp points +double raid_leadership_exp; // Current raid lead AA exp points +uint32 group_leadership_points; // Unspent group lead AA points +uint32 raid_leadership_points; // Unspent raid lead AA points +LeadershipAA_Struct leader_abilities; // [128]Leader AA ranks 19332 + +uint32 PVPKills; +uint32 PVPDeaths; +uint32 PVPCurrentPoints; +uint32 PVPCareerPoints; +uint32 PVPBestKillStreak; +uint32 PVPWorstDeathStreak; +uint32 PVPCurrentKillStreak; +PVPStatsEntry_Struct PVPLastKill; // size 88 +PVPStatsEntry_Struct PVPLastDeath; // size 88 +uint32 PVPNumberOfKillsInLast24Hours; +PVPStatsEntry_Struct PVPRecentKills[50]; // size 4400 - 88 each +uint32 expAA; // Exp earned in current AA point +uint32 currentRadCrystals; // Current count of radiant crystals +uint32 careerRadCrystals; // Total count of radiant crystals ever +uint32 currentEbonCrystals; // Current count of ebon crystals +uint32 careerEbonCrystals; // Total count of ebon crystals ever +*/ + +}; + +/* +** Client Target Struct +** Length: 2 Bytes +** OpCode: 6221 +*/ +struct ClientTarget_Struct { +/*000*/ uint32 new_target; // Target ID +}; + +/* +** Target Rejection Struct +** Length: 12 Bytes +** OpCode: OP_TargetReject +*/ +struct TargetReject_Struct { +/*00*/ uint8 unknown00[12]; +}; + +struct PetCommand_Struct { +/*00*/ uint32 command; +/*04*/ uint32 unknown04; +/*08*/ uint32 unknown08; +}; + +/* +** Delete Spawn +** Length: 5 Bytes +** OpCode: OP_DeleteSpawn +*/ +struct DeleteSpawn_Struct +{ +/*00*/ uint32 spawn_id; // Spawn ID to delete +/*04*/ uint8 unknown04; // Seen 1 +/*05*/ +}; + +/* +** Channel Message received or sent +** Length: 144 Bytes + Variable Length + 1 +** OpCode: OP_ChannelMessage +** +*/ +struct ChannelMessage_Struct +{ +/*000*/ char targetname[64]; // Tell recipient +/*064*/ char sender[64]; // The senders name (len might be wrong) +/*128*/ uint32 language; // Language +/*132*/ uint32 chan_num; // Channel +/*136*/ uint32 cm_unknown4[2]; // ***Placeholder +/*144*/ uint32 skill_in_language; // The players skill in this language? might be wrong +/*148*/ char message[0]; // Variable length message +}; + +/* +** Special Message +** Length: 4 Bytes + Variable Text Length + 1 +** OpCode: OP_SpecialMesg +** +*/ +/* + Theres something wrong with this... example live packet: +Server->Client: [ Opcode: OP_SpecialMesg (0x0fab) Size: 244 ] + 0: 01 02 00 0A 00 00 00 09 - 05 00 00 42 61 72 73 74 | ...........Barst + 16: 72 65 20 53 6F 6E 67 77 - 65 61 76 65 72 00 7C F9 | re Songweaver.|. + 32: FF FF 84 FF FF FF 03 00 - 00 00 47 72 65 65 74 69 | ..........Greeti + +*/ +struct SpecialMesg_Struct +{ +/*00*/ char header[3]; // 04 04 00 <-- for #emote style msg +/*03*/ uint32 msg_type; // Color of text (see MT_*** below) +/*07*/ uint32 target_spawn_id; // Who is it being said to? +/*11*/ char sayer[1]; // Who is the source of the info - Was 1 +/*12*/ uint8 unknown12[12]; +/*24*/ char message[128]; // What is being said? - was 128 +}; + +/* +** When somebody changes what they're wearing +** or give a pet a weapon (model changes) +** Length: 19 Bytes +*/ +struct WearChange_Struct{ +/*000*/ uint16 spawn_id; +/*002*/ uint32 material; +/*006*/ uint32 unknown06; +/*010*/ uint32 elite_material; // 1 for Drakkin Elite Material +/*014*/ uint32 hero_forge_model; // New to VoA +/*018*/ uint32 unknown18; // New to RoF2 +/*022*/ Color_Struct color; +/*026*/ uint8 wear_slot_id; +/*027*/ +}; + +/* +** Type: Bind Wound Structure +** Length: 8 Bytes +*/ +//Fixed for 7-14-04 patch +struct BindWound_Struct +{ +/*000*/ uint16 to; // TargetID +/*002*/ uint16 unknown2; // ***Placeholder +/*004*/ uint16 type; +/*006*/ uint16 unknown6; +}; + + +/* +** Type: Zone Change Request (before hand) +** Length: 88 bytes +** OpCode: a320 +*/ + +struct ZoneChange_Struct { +/*000*/ char char_name[64]; // Character Name +/*064*/ uint16 zoneID; +/*066*/ uint16 instanceID; +/*068*/ uint32 Unknown068; +/*072*/ uint32 Unknown072; +/*076*/ float y; +/*080*/ float x; +/*084*/ float z; +/*088*/ uint32 zone_reason; //0x0A == death, I think +/*092*/ int32 success; // =0 client->server, =1 server->client, -X=specific error +/*096*/ uint32 Unknown096; // Not sure the extra 4 bytes goes here or earlier in the struct. +/*100*/ +}; + +struct RequestClientZoneChange_Struct { +/*000*/ uint16 zone_id; +/*002*/ uint16 instance_id; +/*004*/ uint32 unknown004; +/*008*/ float y; +/*012*/ float x; +/*016*/ float z; +/*020*/ float heading; +/*024*/ uint32 type; //unknown... values +/*032*/ uint8 unknown032[144]; +/*172*/ uint32 unknown172; +/*176*/ +}; + +struct Animation_Struct { +/*00*/ uint16 spawnid; +/*02*/ uint8 value; +/*03*/ uint8 action; +/*04*/ +}; + +// solar: this is what causes the caster to animate and the target to +// get the particle effects around them when a spell is cast +// also causes a buff icon +struct Action_Struct +{ +/*00*/ uint16 target; // id of target +/*02*/ uint16 source; // id of caster +/*04*/ uint16 level; // level of caster - Seen 0 +/*06*/ uint32 unknown06; +/*10*/ float instrument_mod; +/*14*/ uint32 bard_focus_id; // seen 0 +/*18*/ float knockback_angle; //seems to go from 0-512 then it rolls over again +/*22*/ uint32 unknown22; +/*26*/ uint8 type; +/*27*/ uint32 damage; +/*31*/ uint16 unknown31; +/*33*/ uint32 spell; // spell id being cast +/*37*/ uint8 level2; // level of caster again? Or maybe the castee +/*38*/ uint8 effect_flag; // if this is 4, the effect is valid: or if two are sent at the same time? +/*39*/ +}; + +// Starting with 2/21/2006, OP_Actions seem to come in pairs, duplicating +// themselves, with the second one with slightly more information. Maybe this +// has to do with buff blocking?? +struct ActionAlt_Struct +{ +/*00*/ uint16 target; // id of target +/*02*/ uint16 source; // id of caster +/*04*/ uint16 level; // level of caster - Seen 0 +/*06*/ uint32 unknown06; +/*10*/ float instrument_mod; +/*14*/ uint32 bard_focus_id; // seen 0 +/*18*/ float knockback_angle; //seems to go from 0-512 then it rolls over again +/*22*/ uint32 unknown22; +/*26*/ uint8 type; +/*27*/ uint32 damage; +/*31*/ uint16 unknown31; +/*33*/ uint32 spell; // spell id being cast +/*37*/ uint8 level2; // level of caster again? Or maybe the castee +/*38*/ uint8 effect_flag; // if this is 4, the effect is valid: or if two are sent at the same time? +/*39*/ uint32 unknown39; // New field to Underfoot - Seen 14 +/*43*/ uint8 unknown43; // New field to Underfoot - Seen 0 +/*44*/ uint8 unknown44; // New field to Underfoot - Seen 17 +/*45*/ uint8 unknown45; // New field to Underfoot - Seen 0 +/*46*/ int32 unknown46; // New field to Underfoot - Seen -1 +/*50*/ uint32 unknown50; // New field to Underfoot - Seen 0 +/*54*/ uint16 unknown54; // New field to Underfoot - Seen 0 +/*56*/ +}; + +// solar: this is what prints the You have been struck. and the regular +// melee messages like You try to pierce, etc. It's basically the melee +// and spell damage message +struct CombatDamage_Struct +{ +/* 00 */ uint16 target; +/* 02 */ uint16 source; +/* 04 */ uint8 type; //slashing, etc. 231 (0xE7) for spells +/* 05 */ uint32 spellid; +/* 09 */ int32 damage; +/* 13 */ float unknown11; // cd cc cc 3d +/* 17 */ float sequence; // see above notes in Action_Struct +/* 21 */ uint8 unknown19[9]; // was [9] +/* 30 */ +}; + + +/* +** Consider Struct +** Length: 20 Bytes +*/ +struct Consider_Struct{ +/*000*/ uint32 playerid; // PlayerID +/*004*/ uint32 targetid; // TargetID +/*008*/ uint32 faction; // Faction +/*012*/ uint32 level; // Level +/*016*/ uint8 pvpcon; // Pvp con flag 0/1 +/*017*/ uint8 unknown017[3]; // +/*020*/ +}; + +/* +** Spawn Death Blow +** Length: 32 Bytes +** OpCode: 0114 +*/ +struct Death_Struct +{ +/*000*/ uint32 spawn_id; +/*004*/ uint32 killer_id; +/*008*/ uint32 corpseid; // was corpseid +/*012*/ uint32 attack_skill; // was type +/*016*/ uint32 spell_id; +/*020*/ uint32 bindzoneid; //bindzoneid? +/*024*/ uint32 damage; +/*028*/ uint32 unknown028; +}; + +struct BecomeCorpse_Struct { + uint32 spawn_id; + float y; + float x; + float z; +}; + +struct ZonePlayerToBind_Struct { +/*000*/ uint16 bind_zone_id; +/*002*/ uint16 bind_instance_id; +/*004*/ float x; +/*008*/ float y; +/*012*/ float z; +/*016*/ float heading; +/*020*/ char zone_name[1]; // Or "Bind Location" +/*000*/ uint8 unknown021; // Seen 1 - Maybe 0 would be to force a rezone and 1 is just respawn +/*000*/ uint32 unknown022; // Seen 32 or 59 +/*000*/ uint32 unknown023; // Seen 0 +/*000*/ uint32 unknown024; // Seen 21 or 43 +}; + +struct ZonePlayerToBindHeader_Struct +{ + /*000*/ uint16 bind_zone_id; + /*002*/ uint16 bind_instance_id; + /*004*/ float x; + /*008*/ float y; + /*012*/ float z; + /*016*/ float heading; + /*020*/ char zone_name[1]; // Or "Bind Location" +}; + +struct ZonePlayerToBindFooter_Struct +{ + /*000*/ uint8 unknown021; // Seen 1 - Maybe 0 would be to force a rezone and 1 is just respawn + /*000*/ uint32 unknown022; // Seen 32 or 59 + /*000*/ uint32 unknown023; // Seen 0 + /*000*/ uint32 unknown024; // Seen 21 or 43 +}; + +typedef struct { +/*000*/ uint32 bind_number; // Number of this bind in the iteration +/*004*/ uint32 bind_zone_id; // ID of the zone for this bind point or resurect point +/*008*/ float x; // X loc for this bind point +/*012*/ float y; // Y loc for this bind point +/*016*/ float z; // Z loc for this bind point +/*020*/ float heading; // Heading for this bind point +/*024*/ char bind_zone_name[1]; // Or "Bind Location" or "Resurrect" +/*000*/ uint8 validity; // 0 = valid choice, 1 = not a valid choice at this time (resurrection) +} RespawnOptions_Struct; + +struct RespawnWindow_Struct { +/*000*/ uint32 unknown000; // Seen 0 +/*004*/ uint32 time_remaining; // Total time before respawn in milliseconds +/*008*/ uint32 unknown008; // Seen 0 +/*012*/ uint32 total_binds; // Total Bind Point Options? - Seen 2 +/*016*/ RespawnOptions_Struct bind_points; +// First bind point is "Bind Location" and the last one is "Ressurect" +}; + + +/* +** Spawn position update - Size: 22 +** Struct sent from server->client to update position of +** another spawn's position update in zone (whether NPC or PC) +** +*/ +struct PlayerPositionUpdateServer_Struct +{ + uint16 spawn_id; + uint16 spawnId2; + signed padding0004:12; + signed y_pos:19; // y coord + unsigned padding:1; + signed delta_z:13; // change in z + signed delta_x:13; // change in x + signed padding0008:6; + signed x_pos:19; // x coord + unsigned heading:12; // heading + signed padding0016:1; + signed delta_heading:10; // change in heading + signed z_pos:19; // z coord + signed padding0020:3; + signed animation:10; // animation + signed delta_y:13; // change in y + signed padding0024:9; +}; + +/* +** Player position update - Size: 40 +** Struct sent from client->server to update +** player position on server +** +*/ +struct PlayerPositionUpdateClient_Struct +{ + uint16 sequence; // increments one each packet - Verified + uint16 spawn_id; // Player's spawn id + uint8 unknown0004[6]; // ***Placeholder + float delta_x; // Change in x + unsigned heading:12; // Directional heading + unsigned padding0040:20; // ***Placeholder + float x_pos; // x coord (2nd loc value) + float delta_z; // Change in z + float z_pos; // z coord (3rd loc value) + float y_pos; // y coord (1st loc value) + unsigned animation:10; // ***Placeholder + unsigned padding0024:22; // animation + float delta_y; // Change in y + signed delta_heading:10; // change in heading + unsigned padding0041:22; // ***Placeholder +}; + +/* +** Spawn HP Update +** Length: 10 Bytes +** OpCode: OP_HPUpdate +*/ +struct SpawnHPUpdate_Struct +{ +/*00*/ int16 spawn_id; +/*02*/ uint32 cur_hp; +/*06*/ int32 max_hp; +/*10*/ +}; + +/* +** SendExpZonein +** Length: 152 Bytes +** OpCode: OP_SendExpZonein +*/ +struct SendExpZonein_Struct +{ +/*0000*/ uint16 spawn_id; // ID of the spawn +/*0002*/ uint16 type; // Values associated with the type +/*0004*/ uint32 parameter; // Type of data sent +/*0008*/ uint32 exp; // Current experience ratio from 0 to 330 +/*0012*/ uint32 expAA; +/*0016*/ uint8 unknown0016[4]; +/*0020*/ char name[64]; +/*0084*/ char last_name[64]; +/*00148*/ uint32 unknown132; +/*00152*/ +}; + +/* +** SendExpZonein +** Length: 0 Bytes +** OpCode: OP_SendExpZonein +*/ +//struct SendExpZonein_Struct {}; + +struct SpawnHPUpdate_Struct2 +{ +/*01*/ int16 spawn_id; +/*00*/ uint8 hp; +}; +/* +** Stamina +** Length: 8 Bytes +** OpCode: 5721 +*/ +struct Stamina_Struct { +/*00*/ uint32 food; // (low more hungry 127-0) +/*02*/ uint32 water; // (low more thirsty 127-0) +}; + +/* +** Level Update +** Length: 12 Bytes +*/ +struct LevelUpdate_Struct +{ +/*00*/ uint32 level; // New level +/*04*/ uint32 level_old; // Old level +/*08*/ uint32 exp; // Current Experience +}; + +/* +** Experience Update +** Length: 14 Bytes +** OpCode: 9921 +*/ +struct ExpUpdate_Struct +{ +/*0000*/ uint32 exp; // Current experience ratio from 0 to 330 +/*0004*/ uint32 aaxp; // @BP ?? +}; + +/* +** Item Packet Struct - Works on a variety of opcodes +** Packet Types: See ItemPacketType enum +** +*/ +enum ItemPacketType +{ + ItemPacketViewLink = 0x00, + ItemPacketTradeView = 0x65, + ItemPacketLoot = 0x66, + ItemPacketTrade = 0x67, + ItemPacketCharInventory = 0x69, + ItemPacketSummonItem = 0x6A, + ItemPacketTributeItem = 0x6C, + ItemPacketMerchant = 0x64, + ItemPacketWorldContainer = 0x6B +}; +struct ItemPacket_Struct +{ +/*00*/ ItemPacketType PacketType; +/*04*/ char SerializedItem[1]; //was 1 +/*xx*/ +}; + +struct BulkItemPacket_Struct +{ +/*00*/ char SerializedItem[0]; +/*xx*/ +}; + +struct Consume_Struct +{ +/*000*/ ItemSlotStruct slot; +/*012*/ uint32 auto_consumed; // 0xffffffff when auto eating e7030000 when right click +/*016*/ uint32 type; // 0x01=Food 0x02=Water +/*020*/ uint32 c_unknown1; // Seen 2 +/*024*/ +}; + +struct ItemNamePacket_Struct { +/*000*/ uint32 item_id; +/*004*/ uint32 unkown004; +/*008*/ char name[64]; +/*072*/ +}; + +// Length: 16 +struct ItemProperties_Struct_Old { + +/*000*/ uint8 unknown01[2]; +/*002*/ uint8 charges; +/*003*/ uint8 unknown02[13]; +/*016*/ +}; + +// Length: 8 +struct ItemProperties_Struct { + +/*000*/ uint8 unknown01[4]; +/*004*/ uint8 charges; +/*005*/ uint8 unknown02[3]; +/*008*/ +}; + +struct DeleteItem_Struct { +/*0000*/ ItemSlotStruct from_slot; +/*0012*/ ItemSlotStruct to_slot; +/*0024*/ uint32 number_in_stack; +/*0028*/ +}; + +struct MoveItem_Struct { +/*0000*/ ItemSlotStruct from_slot; +/*0012*/ ItemSlotStruct to_slot; +/*0024*/ uint32 number_in_stack; +/*0028*/ +}; + +// +// from_slot/to_slot +// -1 - destroy +// 0 - cursor +// 1 - inventory +// 2 - bank +// 3 - trade +// 4 - shared bank +// +// cointype +// 0 - copeer +// 1 - silver +// 2 - gold +// 3 - platinum +// +static const uint32 COINTYPE_PP = 3; +static const uint32 COINTYPE_GP = 2; +static const uint32 COINTYPE_SP = 1; +static const uint32 COINTYPE_CP = 0; + +struct MoveCoin_Struct +{ + int32 from_slot; + int32 to_slot; + int32 cointype1; + int32 cointype2; + int32 amount; +}; +struct TradeCoin_Struct{ + uint32 trader; + uint8 slot; + uint16 unknown5; + uint8 unknown7; + uint32 amount; +}; +struct TradeMoneyUpdate_Struct{ + uint32 trader; + uint32 type; + uint32 amount; +}; +/* +** Surname struct +** Size: 100 bytes +*/ +struct Surname_Struct +{ +/*0000*/ char name[64]; +/*0064*/ uint32 unknown0064; +/*0068*/ char lastname[32]; +/*0100*/ +}; + +struct GuildsListEntry_Struct { + char name[64]; +}; + +struct GuildsList_Struct { + uint8 head[64]; // First on guild list seems to be empty... + GuildsListEntry_Struct Guilds[MAX_NUMBER_GUILDS]; +}; + +struct GuildUpdate_Struct { + uint32 guildID; + GuildsListEntry_Struct entry; +}; + +/* +** Money Loot +** Length: 22 Bytes +** OpCode: 5020 +*/ +struct moneyOnCorpseStruct { +/*0000*/ uint8 response; // 0 = someone else is, 1 = OK, 2 = not at this time +/*0001*/ uint8 unknown1; // = 0x5a +/*0002*/ uint8 unknown2; // = 0x40 +/*0003*/ uint8 unknown3; // = 0 +/*0004*/ uint32 platinum; // Platinum Pieces +/*0008*/ uint32 gold; // Gold Pieces + +/*0012*/ uint32 silver; // Silver Pieces +/*0016*/ uint32 copper; // Copper Pieces +}; + +struct LootingItem_Struct { +/*000*/ uint32 lootee; +/*004*/ uint32 looter; +/*008*/ uint16 slot_id; +/*010*/ uint16 unknown10; +/*012*/ uint32 auto_loot; +/*016*/ uint32 unknown16; +/*020*/ +}; + +struct GuildManageStatus_Struct{ + uint32 guildid; + uint32 oldrank; + uint32 newrank; + char name[64]; +}; +// Guild invite, remove +struct GuildJoin_Struct{ +/*000*/ uint32 guild_id; +/*004*/ uint32 unknown04; +/*008*/ uint32 level; +/*012*/ uint32 class_; +/*016*/ uint32 rank;//0 member, 1 officer, 2 leader +/*020*/ uint32 zoneid; +/*024*/ uint32 unknown24; +/*028*/ char name[64]; +/*092*/ +}; +struct GuildInviteAccept_Struct { + char inviter[64]; + char newmember[64]; + uint32 response; + uint32 guildeqid; +}; +struct GuildManageRemove_Struct { + uint32 guildeqid; + char member[64]; +}; +struct GuildCommand_Struct { +/*000*/ char othername[64]; +/*064*/ char myname[64]; +/*128*/ uint16 guildeqid; +/*130*/ uint8 unknown[2]; // for guildinvite all 0's, for remove 0=0x56, 2=0x02 +/*132*/ uint32 officer; +/*136*/ uint32 unknown136; // New in RoF2 +/*140*/ +}; + +// Opcode OP_GMZoneRequest +// Size = 88 bytes +struct GMZoneRequest_Struct { +/*0000*/ char charname[64]; +/*0064*/ uint32 zone_id; +/*0068*/ float x; +/*0072*/ float y; +/*0076*/ float z; +/*0080*/ char unknown0080[4]; +/*0084*/ uint32 success; // 0 if command failed, 1 if succeeded? +/*0088*/ +// /*072*/ int8 success; // =0 client->server, =1 server->client, -X=specific error +// /*073*/ uint8 unknown0073[3]; // =0 ok, =ffffff error +}; + +struct GMSummon_Struct { +/* 0*/ char charname[64]; +/* 30*/ char gmname[64]; +/* 60*/ uint32 success; +/* 61*/ uint32 zoneID; +/*92*/ int32 y; +/*96*/ int32 x; +/*100*/ int32 z; +/*104*/ uint32 unknown2; // E0 E0 56 00 +}; + +struct GMGoto_Struct { // x,y is swapped as compared to summon and makes sense as own packet +/* 0*/ char charname[64]; + +/* 64*/ char gmname[64]; +/* 128*/ uint32 success; +/* 132*/ uint32 zoneID; + +/*136*/ int32 y; +/*140*/ int32 x; +/*144*/ int32 z; +/*148*/ uint32 unknown2; // E0 E0 56 00 +}; + +struct GMLastName_Struct { +/*000*/ char name[64]; +/*064*/ char gmname[64]; +/*128*/ char lastname[64]; +/*192*/ uint16 unknown[4]; // 0x00, 0x00, 0x01, 0x00 = Update the clients +/*200*/ uint32 unknown200[8]; +/*232*/ +}; + +struct OnLevelMessage_Struct { +/*0000*/ uint32 Title_Count; +/*0000*/ char Title[128]; +/*0000*/ uint32 Text_Count; +/*0000*/ char Text[4096]; +/*0000*/ uint32 ButtonName0_Count; +/*0000*/ char ButtonName0[25]; // If Buttons = 1, these two are the text for the left and right buttons respectively +/*0000*/ uint32 ButtonName1_Count; +/*0000*/ char ButtonName1[25]; +/*0000*/ uint8 Buttons; +/*0000*/ uint8 Unknown4275; // 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 +/*0000*/ uint32 Unknown4288; +/*0000*/ uint8 Unknown4276; +/*0000*/ uint8 Unknown4277; +/*0000*/ +}; + +//Combat Abilities +struct CombatAbility_Struct { + uint32 m_target; //the ID of the target mob + uint32 m_atk; + uint32 m_skill; +}; + +//Instill Doubt +struct Instill_Doubt_Struct { + uint8 i_id; + uint8 ia_unknown; + uint8 ib_unknown; + uint8 ic_unknown; + uint8 i_atk; + + uint8 id_unknown; + uint8 ie_unknown; + uint8 if_unknown; + uint8 i_type; + uint8 ig_unknown; + uint8 ih_unknown; + uint8 ii_unknown; +}; + +struct GiveItem_Struct { + uint16 to_entity; + int16 to_equipSlot; + uint16 from_entity; + int16 from_equipSlot; +}; + +struct RandomReq_Struct { + uint32 low; + uint32 high; +}; + +/* solar: 9/23/03 reply to /random command; struct from Zaphod */ +struct RandomReply_Struct { +/* 00 */ uint32 low; +/* 04 */ uint32 high; +/* 08 */ uint32 result; +/* 12 */ char name[64]; +/* 76 */ +}; + +/* +** LFG_Appearance_Struct +** Packet sent to clients to notify when someone in zone toggles LFG flag +** Size: 8 bytes +** Used in: OP_LFGAppearance +** +*/ +struct LFG_Appearance_Struct +{ +/*0000*/ uint32 spawn_id; // ID of the client +/*0004*/ uint8 lfg; // 1=LFG, 0=Not LFG +/*0005*/ char unknown0005[3]; // +/*0008*/ +}; + + +// EverQuest Time Information: +// 72 minutes per EQ Day +// 3 minutes per EQ Hour +// 6 seconds per EQ Tick (2 minutes EQ Time) +// 3 seconds per EQ Minute + +struct TimeOfDay_Struct { + uint8 hour; + uint8 minute; + uint8 day; + uint8 month; + uint16 year; +/*0006*/ uint16 unknown0016; // Placeholder +/*0008*/ +}; + +// Darvik: shopkeeper structs +struct Merchant_Click_Struct { +/*000*/ uint32 npcid; // Merchant NPC's entity id +/*004*/ uint32 playerid; +/*008*/ uint32 command; // 1=open, 0=cancel/close +/*012*/ float rate; // cost multiplier, dosent work anymore +/*016*/ int32 unknown01; // Seen 3 from Server or -1 from Client +/*020*/ int32 unknown02; // Seen 2592000 from Server or -1 from Client +/*024*/ +}; +/* +Unknowns: +0 is e7 from 01 to // MAYBE SLOT IN PURCHASE +1 is 03 +2 is 00 +3 is 00 +4 is ?? +5 is ?? +6 is 00 from a0 to +7 is 00 from 3f to */ +/* +0 is F6 to 01 +1 is CE CE +4A 4A +00 00 +00 E0 +00 CB +00 90 +00 3F +*/ + +struct Merchant_Sell_Struct { +/*000*/ uint32 npcid; // Merchant NPC's entity id +/*004*/ uint32 playerid; // Player's entity id +/*008*/ uint32 itemslot; // Merchant Slot / Item Instance ID +/*012*/ uint32 unknown12; +/*016*/ uint32 quantity; // Already sold +/*020*/ uint32 unknown20; +/*024*/ uint32 price; +/*028*/ uint32 unknown28; // Normally 0, but seen 84 c5 63 00 as well +/*032*/ +}; + +struct Merchant_Purchase_Struct { +/*000*/ uint32 npcid; // Merchant NPC's entity id +/*004*/ MainInvItemSlotStruct itemslot; +/*012*/ uint32 quantity; +/*016*/ uint32 price; +/*020*/ +}; + +struct Merchant_DelItem_Struct{ +/*000*/ uint32 npcid; // Merchant NPC's entity id +/*004*/ uint32 playerid; // Player's entity id +/*008*/ uint32 itemslot; +}; + +struct AltCurrencyDefinition_Struct { + uint32 id; + uint32 item_id; +}; + +//One packet i didn't include here is the alt currency merchant window. +//it works much like the adventure merchant window +//it is formated like: dbstringid|1|dbstringid|count +//ex for a blank crowns window you would send: +//999999|1|999999|0 +//any items come after in much the same way adventure merchant items do except there is no theme included + +//Server -> Client +//Populates the initial Alternate Currency Window +struct AltCurrencyPopulateEntry_Struct +{ +/*000*/ uint32 currency_number; // corresponds to a dbstr id as well, the string matches what shows up in the "alternate currency" tab. +/*004*/ uint32 unknown00; // always 1 +/*008*/ uint32 currency_number2; // always same as currency number +/*012*/ uint32 item_id; // appears to be the item id +/*016*/ uint32 item_icon; // actual icon +/*020*/ uint32 stack_size; // most are set to 1000, the stack size for the item; should match db i think or there will be problems. +/*024*/ uint8 display; // If set to 0, will not display by default. +/*025*/ +}; + +struct AltCurrencyPopulate_Struct { +/*000*/ uint32 opcode; // 8 for populate +/*004*/ uint32 count; // number of entries +/*008*/ AltCurrencyPopulateEntry_Struct entries[0]; +}; + +//Server -> Client +//Updates the value of a specific Alternate Currency +struct AltCurrencyUpdate_Struct { +/*000*/ uint32 opcode; //7 for update +/*004*/ char name[64]; //name of client (who knows why just do it) +/*068*/ uint32 currency_number; //matches currency_number from populate entry +/*072*/ uint32 unknown072; //always 1 +/*076*/ uint32 amount; //new amount +/*080*/ uint32 unknown080; //seen 0 +/*084*/ uint32 unknown084; //seen 0 +}; + +//Client -> Server +//When an item is selected while the alt currency merchant window is open +struct AltCurrencySelectItem_Struct { +/*000*/ uint32 merchant_entity_id; +/*004*/ //uint32 slot_id; + ItemSlotStruct slot_id; +/*008*/ uint32 unknown008; +/*012*/ uint32 unknown012; +/*016*/ uint32 unknown016; +/*020*/ uint32 unknown020; +/*024*/ uint32 unknown024; +/*028*/ uint32 unknown028; +/*032*/ uint32 unknown032; +/*036*/ uint32 unknown036; +/*040*/ uint32 unknown040; +/*044*/ uint32 unknown044; +/*048*/ uint32 unknown048; +/*052*/ uint32 unknown052; +/*056*/ uint32 unknown056; +/*060*/ uint32 unknown060; +/*064*/ uint32 unknown064; +/*068*/ uint32 unknown068; +/*072*/ uint32 unknown072; +/*076*/ uint32 unknown076; +}; + +//Server -> Client +//As setup it makes it so that item can't be sold to the merchant. +//eg: "I will give you no doubloons for a cloth cap." +//Probably also sends amounts somewhere +struct AltCurrencySelectItemReply_Struct { +/*000*/ uint32 unknown000; +/*004*/ uint8 unknown004; //0xff +/*005*/ uint8 unknown005; //0xff +/*006*/ uint8 unknown006; //0xff +/*007*/ uint8 unknown007; //0xff +/*008*/ char item_name[64]; +/*072*/ uint32 unknown074; +/*076*/ uint32 cost; +/*080*/ uint32 unknown080; +/*084*/ uint32 unknown084; +}; + +//Client -> Server +//Requests purchase of a specific item from the vendor +struct AltCurrencyPurchaseItem_Struct { +/*000*/ uint32 merchant_entity_id; +/*004*/ uint32 item_id; +/*008*/ uint32 unknown008; //1 +}; + +//Client -> Server +//Reclaims / Create currency button pushed. +struct AltCurrencyReclaim_Struct { +/*000*/ uint32 currency_id; +/*004*/ uint32 unknown004; +/*008*/ uint32 count; +/*012*/ uint32 reclaim_flag; //1 = this is reclaim +}; + +struct AltCurrencySellItem_Struct { +/*000*/ uint32 merchant_entity_id; +/*004*/ //uint32 slot_id; + ItemSlotStruct slot_id; +/*008*/ uint32 charges; +/*012*/ uint32 cost; +}; + +struct Adventure_Purchase_Struct { +/*000*/ uint32 some_flag; //set to 1 generally... +/*004*/ uint32 npcid; +/*008*/ uint32 itemid; +/*012*/ uint32 variable; +}; + +struct Adventure_Sell_Struct { +/*000*/ uint32 unknown000; //0x01 - Stack Size/Charges? +/*004*/ uint32 npcid; +/*008*/ MainInvItemSlotStruct slot; +/*016*/ uint32 charges; +/*020*/ uint32 sell_price; +/*024*/ +}; + +struct AdventurePoints_Update_Struct { +/*000*/ uint32 ldon_available_points; // Total available points +/*004*/ uint8 unkown_apu004[20]; +/*024*/ uint32 ldon_guk_points; // Earned Deepest Guk points +/*028*/ uint32 ldon_mirugal_points; // Earned Mirugal' Mebagerie points +/*032*/ uint32 ldon_mistmoore_points; // Earned Mismoore Catacombs Points +/*036*/ uint32 ldon_rujarkian_points; // Earned Rujarkian Hills points +/*040*/ uint32 ldon_takish_points; // Earned Takish points +/*044*/ uint8 unknown_apu042[216]; +}; + + +struct AdventureFinish_Struct{ + uint32 win_lose;//Cofruben: 00 is a lose,01 is win. + uint32 points; +}; +//OP_AdventureRequest +struct AdventureRequest_Struct{ + uint32 risk;//1 normal,2 hard. + uint32 entity_id; +}; +struct AdventureRequestResponse_Struct{ + uint32 unknown000; + char text[2048]; + uint32 timetoenter; + uint32 timeleft; + uint32 risk; + float x; + float y; + float z; + uint32 showcompass; + uint32 unknown2080; +}; + +//this is mostly right but something is off that causes the client to crash sometimes +//I don't really care enough about the feature to work on it anymore though. +struct AdventureLeaderboardEntry_Struct +{ +/*004*/ char name[64]; +/*008*/ uint32 success; +/*012*/ uint32 failure; +/*016*/ +}; + +struct AdventureLeaderboard_Struct +{ +/*000*/ uint32 unknown000; +/*004*/ uint32 unknown004; +/*008*/ uint32 success; +/*012*/ uint32 failure; +/*016*/ uint32 our_rank; +/*020*/ +}; + +/*struct Item_Shop_Struct { + uint16 merchantid; + uint8 itemtype; + Item_Struct item; + uint8 iss_unknown001[6]; +};*/ + +struct Illusion_Struct { // Was size: 336 +/*000*/ uint32 spawnid; +/*004*/ char charname[64]; +/*068*/ uint16 race; +/*070*/ char unknown006[2]; // Weird green name +/*072*/ uint8 gender; +/*073*/ uint8 texture; +/*074*/ uint8 unknown074; +/*075*/ uint8 unknown075; +/*076*/ uint8 helmtexture; +/*077*/ uint8 unknown077; +/*078*/ uint8 unknown078; +/*079*/ uint8 unknown079; +/*080*/ uint32 face; +/*084*/ uint8 hairstyle; // Some Races don't change Hair Style Properly in SoF +/*085*/ uint8 haircolor; +/*086*/ uint8 beard; +/*087*/ uint8 beardcolor; +/*088*/ float size; +/*092*/ uint8 unknown092[148]; +/*240*/ uint32 unknown240; // Removes armor? +/*244*/ uint32 drakkin_heritage; +/*248*/ uint32 drakkin_tattoo; +/*252*/ uint32 drakkin_details; +/*256*/ uint8 unknown256[60]; // This and below are new to RoF2 +/*316*/ int32 unknown316; // Seen -1 +/*320*/ uint8 unknown320[16]; +/*336*/ +}; + +struct ZonePoint_Entry { //32 octets +/*0000*/ uint32 iterator; +/*0004*/ float y; +/*0008*/ float x; +/*0012*/ float z; +/*0016*/ float heading; +/*0020*/ uint16 zoneid; +/*0022*/ uint16 zoneinstance; // LDoN instance +/*0024*/ uint32 unknown024; +/*0028*/ uint32 unknown028; +/*0032*/ +}; + +struct ZonePoints { +/*0000*/ uint32 count; +/*0004*/ struct ZonePoint_Entry zpe[0]; // Always add one extra to the end after all zonepoints +//*0xxx*/ uint8 unknown0xxx[24]; //New from SEQ +}; + +struct SkillUpdate_Struct { +/*00*/ uint32 skillId; +/*04*/ uint32 value; +/*08*/ uint8 unknown08; // Seen 1 +/*09*/ uint8 unknown09; // Seen 80 +/*10*/ uint8 unknown10; // Seen 136 +/*11*/ uint8 unknown11; // Seen 54 +/*12*/ +}; + +struct ZoneUnavail_Struct { + //This actually varies, but... + char zonename[16]; + int16 unknown[4]; +}; + +struct GroupInvite_Struct { +/*0000*/ char invitee_name[64]; +/*0064*/ char inviter_name[64]; +/*0128*/ uint32 unknown0128; +/*0132*/ uint32 unknown0132; +/*0136*/ uint32 unknown0136; +/*0140*/ uint32 unknown0140; +/*0144*/ uint32 unknown0144; +/*0148*/ +}; + +struct GroupGeneric_Struct { +/*0000*/ char name1[64]; +/*0064*/ char name2[64]; +/*0128*/ uint32 unknown0128; +/*0132*/ uint32 unknown0132; +/*0136*/ uint32 unknown0136; +/*0140*/ uint32 unknown0140; +/*0144*/ uint32 unknown0144; +/*0148*/ +}; + +struct GroupCancel_Struct { +/*000*/ char name1[64]; +/*064*/ char name2[64]; +/*128*/ uint8 unknown128[20]; +/*148*/ uint32 toggle; +/*152*/ +}; + +struct GroupUpdate_Struct { +/*0000*/ uint32 action; +/*0004*/ char yourname[64]; +/*0068*/ char membername[5][64]; +/*0388*/ char leadersname[64]; +/*0452*/ +}; + +struct GroupUpdate2_Struct { +/*0000*/ uint32 action; +/*0004*/ char yourname[64]; +/*0068*/ char membername[5][64]; +/*0388*/ char leadersname[64]; +/*0452*/ GroupLeadershipAA_Struct leader_aas; +/*0516*/ uint8 unknown[252]; // Titanium uses [188] here +/*0768*/ +}; + +struct GroupUpdate_Struct_Live { // New for Live +/*0000*/ uint32 groupid; // Guess - Matches unknown0136 from GroupFollow_Struct +/*0004*/ uint32 totalmembers; // Guess +/*0000*/ //uint32 leadersname[0]; // Group Leader Name Null Terminated +/*0008*/ //GroupMembers_Struct groupmembers; +}; + +struct GroupMembers_Struct { // New for Live +/*0000*/ uint32 membernumber; // Guess - number of member in the group (0 to 5?) +/*0000*/ //char membername[0]; // Member Name Null Terminated +/*0000*/ uint8 unknown001[3]; // Seen 0 +/*0000*/ uint32 memberlevel; // Guess +/*0000*/ uint8 unknown002[11]; // Seen 0 +}; + +struct GroupJoin_Struct_Live { // New for Live +/*0000*/ uint32 unknown0000; // Matches unknown0136 from GroupFollow_Struct +/*0004*/ uint32 action; +/*0008*/ uint8 unknown0008[5]; // Seen 0 +/*0013*/ //char membername[0]; // Null Terminated? +/*0000*/ uint8 unknown0013[3]; // Seen 0 +/*0000*/ uint32 unknown0016; // Matches unknown0132 from GroupFollow_Struct +/*0000*/ uint8 unknown0020[11]; // Seen 0 +}; + +struct GroupJoin_Struct { +/*000*/ char unknown000[64]; +/*064*/ char membername[64]; +/*128*/ uint8 unknown128[20]; // Leadership AA ? +/*148*/ +}; + +struct GroupFollow_Struct { // Live Follow Struct +/*0000*/ char name1[64]; // inviter +/*0064*/ char name2[64]; // invitee +/*0128*/ uint32 unknown0128; // Seen 0 +/*0132*/ uint32 unknown0132; // Group ID or member level? +/*0136*/ uint32 unknown0136; // Maybe Voice Chat Channel or Group ID? +/*0140*/ uint32 unknown0140; // Seen 0 +/*0144*/ uint32 unknown0144; // Seen 0 +/*0148*/ uint32 unknown0148; +/*0152*/ +}; + +struct InspectBuffs_Struct { +/*000*/ uint32 spell_id[BUFF_COUNT]; +/*168*/ uint32 tics_remaining[BUFF_COUNT]; +}; + +struct LFG_Struct { +/*000*/ uint32 unknown000; +/*004*/ uint32 value; // 0x00 = off 0x01 = on +/*008*/ uint32 unknown008; +/*012*/ uint32 unknown012; +/*016*/ char name[64]; +}; + +struct FaceChange_Struct { +/*000*/ uint8 haircolor; +/*001*/ uint8 beardcolor; +/*002*/ uint8 eyecolor1; +/*003*/ uint8 eyecolor2; +/*004*/ uint8 hairstyle; +/*005*/ uint8 beard; +/*006*/ uint8 face; +/*007*/ uint8 unknown007; +/*008*/ uint32 drakkin_heritage; +/*012*/ uint32 drakkin_tattoo; +/*016*/ uint32 drakkin_details; +/*020*/ uint32 unknown020; +/*024*/ +}; +//there are only 10 faces for barbs changing woad just +//increase the face value by ten so if there were 8 woad +//designs then there would be 80 barb faces + +/* +** Trade request from one client to another +** Used to initiate a trade +** Size: 8 bytes +** Used in: OP_TradeRequest +*/ +struct TradeRequest_Struct { +/*00*/ uint32 to_mob_id; +/*04*/ uint32 from_mob_id; +/*08*/ +}; + +struct TradeAccept_Struct { +/*00*/ uint32 from_mob_id; +/*04*/ uint32 unknown4; //seems to be garbage +/*08*/ +}; + +/* +** Cancel Trade struct +** Sent when a player cancels a trade +** Size: 8 bytes +** Used In: OP_CancelTrade +** +*/ +struct CancelTrade_Struct { +/*00*/ uint32 fromid; +/*04*/ uint32 action; +/*08*/ +}; + +struct PetitionUpdate_Struct { + uint32 petnumber; // Petition Number + uint32 color; // 0x00 = green, 0x01 = yellow, 0x02 = red + uint32 status; + time_t senttime; // 4 has to be 0x1F + char accountid[32]; + char gmsenttoo[64]; + int32 quetotal; + char charname[64]; +}; + +struct Petition_Struct { + uint32 petnumber; + uint32 urgency; + char accountid[32]; + char lastgm[32]; + uint32 zone; + //char zone[32]; + char charname[64]; + uint32 charlevel; + uint32 charclass; + uint32 charrace; + uint32 unknown; + //time_t senttime; // Time? + uint32 checkouts; + uint32 unavail; + //uint8 unknown5[4]; + time_t senttime; + uint32 unknown2; + char petitiontext[1024]; + char gmtext[1024]; +}; + + +struct Who_All_Struct { // 156 length total +/*000*/ char whom[64]; +/*088*/ uint8 unknown088[64]; +/*064*/ uint32 wrace; // FF FF = no race +/*068*/ uint32 wclass; // FF FF = no class +/*072*/ uint32 lvllow; // FF FF = no numbers +/*076*/ uint32 lvlhigh; // FF FF = no numbers +/*080*/ uint32 gmlookup; // FF FF = not doing /who all gm +/*084*/ uint32 guildid; // Also used for Buyer/Trader/LFG +/*152*/ uint32 type; // 0 = /who 3 = /who all +/*156*/ +}; + +struct Stun_Struct { // 8 bytes total +/*000*/ uint32 duration; // Duration of stun +/*004*/ uint8 unknown004; // seen 0 +/*005*/ uint8 unknown005; // seen 163 +/*006*/ uint8 unknown006; // seen 67 +/*007*/ uint8 unknown007; // seen 0 +/*008*/ +}; + +struct AugmentItem_Struct { +/*00*/ uint32 dest_inst_id; // The unique serial number for the item instance that is being augmented +/*04*/ uint32 container_index; // Seen 0 +/*08*/ ItemSlotStruct container_slot; // Slot of the item being augmented +/*20*/ uint32 augment_index; // Seen 0 +/*24*/ ItemSlotStruct augment_slot; // Slot of the distiller to use (if one applies) +/*36*/ int32 augment_action; // Guessed - 0 = augment, 1 = remove with distiller, 3 = delete aug +/*36*/ //int32 augment_slot; +/*40*/ +}; + +// OP_Emote +struct Emote_Struct { +/*0000*/ uint32 unknown01; +/*0004*/ char message[1024]; // was 1024 +/*1028*/ +}; + +// Inspect +struct Inspect_Struct { + uint32 TargetID; + uint32 PlayerID; +}; + +//OP_InspectAnswer - Size: 1860 +struct InspectResponse_Struct{ +/*000*/ uint32 TargetID; +/*004*/ uint32 playerid; +/*008*/ char itemnames[23][64]; +/*1480*/uint32 itemicons[23]; +/*1572*/char text[288]; // Max number of chars in Inspect Window appears to be 254 +/*1860*/ +}; + +//OP_SetDataRate +struct SetDataRate_Struct { + float newdatarate; +}; + +//OP_SetServerFilter +struct SetServerFilter_Struct { + uint32 filters[35]; //see enum eqFilterType [31] +}; + +//Op_SetServerFilterAck +struct SetServerFilterAck_Struct { + uint8 blank[8]; +}; +struct IncreaseStat_Struct{ + /*0000*/ uint8 unknown0; + /*0001*/ uint8 str; + /*0002*/ uint8 sta; + /*0003*/ uint8 agi; + /*0004*/ uint8 dex; + /*0005*/ uint8 int_; + /*0006*/ uint8 wis; + /*0007*/ uint8 cha; + /*0008*/ uint8 fire; + /*0009*/ uint8 cold; + /*0010*/ uint8 magic; + /*0011*/ uint8 poison; + /*0012*/ uint8 disease; + /*0013*/ char unknown13[116]; + /*0129*/ uint8 str2; + /*0130*/ uint8 sta2; + /*0131*/ uint8 agi2; + /*0132*/ uint8 dex2; + /*0133*/ uint8 int_2; + /*0134*/ uint8 wis2; + /*0135*/ uint8 cha2; + /*0136*/ uint8 fire2; + /*0137*/ uint8 cold2; + /*0138*/ uint8 magic2; + /*0139*/ uint8 poison2; + /*0140*/ uint8 disease2; +}; + +struct GMName_Struct { + char oldname[64]; + char gmname[64]; + char newname[64]; + uint8 badname; + uint8 unknown[3]; +}; + +struct GMDelCorpse_Struct { + char corpsename[64]; + char gmname[64]; + uint8 unknown; +}; + +struct GMKick_Struct { + char name[64]; + char gmname[64]; + uint8 unknown; +}; + + +struct GMKill_Struct { + char name[64]; + char gmname[64]; + uint8 unknown; +}; + + +struct GMEmoteZone_Struct { + char text[512]; +}; + +// The BookText_Struct is not used in SoF and later clients. +// The BookRequest_Struct is used instead for both request and reply. +// +struct BookText_Struct { + uint8 window; // where to display the text (0xFF means new window) + uint8 type; //type: 0=scroll, 1=book, 2=item info.. prolly + char booktext[1]; // Variable Length - was 1 +}; +// This is the request to read a book. +// This is just a "text file" on the server +// or in our case, the 'name' column in our books table. +struct BookRequest_Struct { +/*0000*/ uint32 window; // where to display the text (0xFFFFFFFF means new window). +/*0004*/ uint16 invslot; // Is the slot, but the RoF2 conversion causes it to fail. Turned to 0 since it isnt required anyway. +/*0008*/ uint32 unknown006; // Seen FFFFFFFF +/*0010*/ uint16 unknown008; // seen 0000 +/*0012*/ uint32 type; // 0 = Scroll, 1 = Book, 2 = Item Info. Possibly others +/*0016*/ uint32 unknown0012; +/*0020*/ uint16 unknown0016; +/*0022*/ char txtfile[8194]; +}; + +/* +** Object/Ground Spawn struct +** Used for Forges, Ovens, ground spawns, items dropped to ground, etc +** Size: Variable +** OpCodes: OP_CreateObject +** +*/ +struct Object_Struct { +/*00*/ uint32 object_count; // Iteration count in the object list +/*00*/ char object_name[1]; // Name of object, usually something like IT63_ACTORDEF +/*00*/ uint16 zone_id; // Redudant, but: Zone the object appears in +/*00*/ uint16 zone_instance; // +/*00*/ uint32 drop_id; // Unique object id for zone +/*00*/ uint32 unknown024; // 53 9e f9 7e - same for all objects in the zone? +/*00*/ float heading; // heading +/*00*/ float unknown032[2]; // 00 00 00 00 00 00 00 00 +/*00*/ float size; // Size - default 1 +/*00*/ float z; // z coord +/*00*/ float x; // x coord +/*00*/ float y; // y coord +/*00*/ int32 object_type; // Type of object, not directly translated to OP_OpenObject +}; +// object_type - 01 = generic drop, 02 = armor, 19 = weapon +// 0xff seems to be indicative of the tradeskill/openable items that end up returning the old style item type in the OP_OpenObject + +/* +** Click Object Struct +** Client clicking on zone object (forge, groundspawn, etc) +** Size: 8 bytes +** Last Updated: Oct-17-2003 +** +*/ +struct ClickObject_Struct { +/*00*/ uint32 drop_id; // Appears to use the Object Count field now +/*04*/ uint32 player_id; +/*08*/ +}; + +struct Shielding_Struct { + uint32 target_id; +}; + +/* +** Click Object Acknowledgement Struct +** Response to client clicking on a World Container (ie, forge) +** +*/ +struct ClickObjectAction_Struct { +/*00*/ //uint32 player_id; // Appears to have been removed +/*00*/ uint32 drop_id; // Appears to use the object_count field now +/*04*/ int32 unknown04; // Seen -1 +/*08*/ int32 unknown08; // Seen -1 +/*08*/ //uint32 open; // 1=opening, 0=closing - Removed? +/*12*/ uint32 type; // See object.h, "Object Types" +/*16*/ uint32 unknown16; // +/*20*/ uint32 icon; // Icon to display for tradeskill containers +/*24*/ uint32 unknown24; // +/*28*/ char object_name[64]; // Object name to display +/*92*/ +}; + +/* +** This is different now, mostly unknown +** +*/ +struct CloseContainer_Struct { +/*00*/ uint32 player_id; // Entity Id of player who clicked object +/*04*/ uint32 drop_id; // Zone-specified unique object identifier +/*08*/ uint32 open; // 1=opening, 0=closing +/*12*/ uint32 unknown12[12]; +}; + +/* +** Generic Door Struct +** Length: 52 Octets +** Used in: +** cDoorSpawnsStruct(f721) +** +*/ +struct Door_Struct +{ +/*0000*/ char name[32]; // Filename of Door // Was 10char long before... added the 6 in the next unknown to it: Daeken M. BlackBlade +/*0032*/ float yPos; // y loc +/*0036*/ float xPos; // x loc +/*0040*/ float zPos; // z loc +/*0044*/ float heading; +/*0048*/ uint32 incline; // rotates the whole door +/*0052*/ uint32 size; // 100 is normal, smaller number = smaller model +/*0054*/ uint8 unknown0054[4]; // 00 00 00 00 +/*0060*/ uint8 doorId; // door's id # +/*0061*/ uint8 opentype; +/*0062*/ uint8 state_at_spawn; +/*0063*/ uint8 invert_state; // if this is 1, the door is normally open +/*0064*/ uint32 door_param; // normally ff ff ff ff (-1) +/*0068*/ uint32 unknown0068; // 00 00 00 00 +/*0072*/ uint32 unknown0072; // 00 00 00 00 +/*0076*/ uint8 unknown0076[4]; // New for RoF2 +/*0080*/ uint8 unknown0080; // seen 1 or 0 +/*0081*/ uint8 unknown0081; // seen 1 (always?) +/*0082*/ uint8 unknown0082; // seen 0 (always?) +/*0083*/ uint8 unknown0083; // seen 1 (always?) +/*0084*/ uint8 unknown0084; // seen 0 (always?) +/*0085*/ uint8 unknown0085; // seen 1 or 0 or rarely 2C or 90 or ED or 2D or A1 +/*0086*/ uint8 unknown0086; // seen 0 or rarely FF or FE or 10 or 5A or 82 +/*0087*/ uint8 unknown0087; // seen 0 or rarely 02 or 7C +/*0088*/ uint8 unknown0088[8]; // mostly 0s, the last 3 bytes are something tho +/*0096*/ +}; + +struct DoorSpawns_Struct { + struct Door_Struct doors[0]; +}; + +/* + OP Code: Op_ClickDoor + Size: 16 +*/ +struct ClickDoor_Struct { +/*000*/ uint8 doorid; +/*001*/ uint8 unknown001; // This may be some type of action setting +/*002*/ uint8 unknown002; // This is sometimes set after a lever is closed +/*003*/ uint8 unknown003; // Seen 0 +/*004*/ uint8 picklockskill; +/*005*/ uint8 unknown005[3]; +/*008*/ uint32 item_id; +/*012*/ uint16 player_id; +/*014*/ uint8 unknown014[2]; +/*016*/ +}; + +struct MoveDoor_Struct { + uint8 doorid; + uint8 action; +}; + + +struct BecomeNPC_Struct { + uint32 id; + int32 maxlevel; +}; + +struct Underworld_Struct { + float speed; + float y; + float x; + float z; +}; + +struct Resurrect_Struct +{ +/*000*/ uint32 unknown000; +/*004*/ uint16 zone_id; +/*006*/ uint16 instance_id; +/*008*/ float y; +/*012*/ float x; +/*016*/ float z; +/*020*/ uint32 unknown020; +/*024*/ char your_name[64]; +/*088*/ uint32 unknown088; +/*092*/ char rezzer_name[64]; +/*156*/ uint32 spellid; +/*160*/ char corpse_name[64]; +/*224*/ uint32 action; +/*228*/ uint32 unknown228; +/*232*/ +}; + +struct SetRunMode_Struct { + uint8 mode; //01=run 00=walk + uint8 unknown[3]; +}; + +// EnvDamage is EnvDamage2 without a few bytes at the end. +// Size: 37 bytes +struct EnvDamage2_Struct { +/*0000*/ uint32 id; +/*0004*/ uint16 unknown4; +/*0006*/ uint32 damage; +/*0010*/ float unknown10; // New to Underfoot - Seen 1 +/*0014*/ uint8 unknown14[12]; +/*0026*/ uint8 dmgtype; // FA = Lava; FC = Falling +/*0027*/ uint8 unknown27[4]; +/*0031*/ uint16 unknown31; // New to Underfoot - Seen 66 +/*0033*/ uint16 constant; // Always FFFF +/*0035*/ uint16 unknown35; +/*0037*/ +}; + +//Bazaar Stuff + +enum { + BazaarTrader_StartTraderMode = 1, + BazaarTrader_EndTraderMode = 2, + BazaarTrader_UpdatePrice = 3, + BazaarTrader_EndTransaction = 4, + BazaarSearchResults = 7, + BazaarWelcome = 9, + BazaarBuyItem = 10, + BazaarTrader_ShowItems = 11, + BazaarSearchDone = 12, + BazaarTrader_CustomerBrowsing = 13, + BazaarInspectItem = 18, + BazaarSearchDone2 = 19, + BazaarTrader_StartTraderMode2 = 22 +}; + +enum { + BazaarPriceChange_Fail = 0, + BazaarPriceChange_UpdatePrice = 1, + BazaarPriceChange_RemoveItem = 2, + BazaarPriceChange_AddItem = 3 +}; + +struct BazaarWindowStart_Struct { + uint8 Action; + uint8 Unknown001; + uint16 Unknown002; +}; + + +struct BazaarWelcome_Struct { + BazaarWindowStart_Struct Beginning; + uint32 Traders; + uint32 Items; + uint8 Unknown012[8]; +}; + +struct BazaarSearch_Struct { + BazaarWindowStart_Struct Beginning; + uint32 TraderID; + uint32 Class_; + uint32 Race; + uint32 ItemStat; + uint32 Slot; + uint32 Type; + char Name[64]; + uint32 MinPrice; + uint32 MaxPrice; + uint32 Minlevel; + uint32 MaxLlevel; +}; +struct BazaarInspect_Struct{ + uint32 ItemID; + uint32 Unknown004; + char Name[64]; +}; + +struct NewBazaarInspect_Struct { +/*000*/ BazaarWindowStart_Struct Beginning; +/*004*/ char Name[64]; +/*068*/ uint32 Unknown068; +/*072*/ int32 SerialNumber; +/*076*/ uint32 Unknown076; +/*080*/ uint32 SellerID; +/*084*/ uint32 Unknown084; +}; + +struct BazaarReturnDone_Struct{ + uint32 Type; + uint32 TraderID; + uint32 Unknown008; + uint32 Unknown012; + uint32 Unknown016; +}; + +struct BazaarSearchResults_Struct { +/*000*/ BazaarWindowStart_Struct Beginning; +/*004*/ uint32 SellerID; +/*008*/ char SellerName[64]; +/*072*/ uint32 NumItems; +/*076*/ uint32 ItemID; +/*080*/ uint32 SerialNumber; +/*084*/ uint32 Unknown084; +/*088*/ char ItemName[64]; +/*152*/ uint32 Cost; +/*156*/ uint32 ItemStat; +/*160*/ +}; + +struct ServerSideFilters_Struct { +uint8 clientattackfilters; // 0) No, 1) All (players) but self, 2) All (players) but group +uint8 npcattackfilters; // 0) No, 1) Ignore NPC misses (all), 2) Ignore NPC Misses + Attacks (all but self), 3) Ignores NPC Misses + Attacks (all but group) +uint8 clientcastfilters; // 0) No, 1) Ignore PC Casts (all), 2) Ignore PC Casts (not directed towards self) +uint8 npccastfilters; // 0) No, 1) Ignore NPC Casts (all), 2) Ignore NPC Casts (not directed towards self) +}; + +/* +** Client requesting item statistics +** Size: 52 bytes +** Used In: OP_ItemLinkClick +** Last Updated: 01/03/2012 +** +*/ +struct ItemViewRequest_Struct { +/*000*/ uint32 item_id; +/*004*/ uint32 augments[6]; +/*028*/ uint32 link_hash; +/*032*/ uint32 unknown028; //seems to always be 4 on SoF client +/*036*/ char unknown032[12]; //probably includes loregroup & evolving info. see Client::MakeItemLink() in zone/inventory.cpp:469 +/*048*/ uint16 icon; +/*050*/ char unknown046[2]; +/*052*/ +}; + +/* + * Client to server packet + */ +struct PickPocket_Struct { +// Size 18 + uint32 to; + uint32 from; + uint16 myskill; + uint8 type; // -1 you are being picked, 0 failed , 1 = plat, 2 = gold, 3 = silver, 4 = copper, 5 = item + uint8 unknown1; // 0 for response, unknown for input + uint32 coin; + uint8 lastsix[2]; +}; +/* + * Server to client packet + */ + +struct sPickPocket_Struct { + // Size 28 = coin/fail + uint32 to; + uint32 from; + uint32 myskill; + uint32 type; + uint32 coin; + char itemname[64]; +}; + + +struct LogServer_Struct { +// Op_Code OP_LOGSERVER +/*000*/ uint32 unknown000; +/*004*/ uint8 enable_pvp; +/*005*/ uint8 unknown005; +/*006*/ uint8 unknown006; +/*007*/ uint8 unknown007; +/*008*/ uint8 enable_FV; +/*009*/ uint8 unknown009; +/*010*/ uint8 unknown010; +/*011*/ uint8 unknown011; +/*012*/ uint32 unknown012; // htonl(1) on live +/*016*/ uint32 unknown016; // htonl(1) on live +/*020*/ uint8 unknown020[12]; +/*032*/ uint32 unknown032; +/*036*/ char worldshortname[32]; +/*068*/ uint8 unknown068[181]; +/*249*/ uint8 unknown249[27]; +/*276*/ float unknown276[7]; +/*304*/ uint8 unknown304[256]; +/*560*/ + +/* Currently lost + uint8 enablevoicemacros; + uint8 enablemail; +*/ +}; + +struct ApproveWorld_Struct { +// Size 544 +// Op_Code OP_ApproveWorld + uint8 unknown544[544]; +}; + +struct ClientError_Struct +{ +/*00001*/ char type; +/*00001*/ char unknown0001[69]; +/*00069*/ char character_name[64]; +/*00134*/ char unknown134[192]; +/*00133*/ char message[31994]; +/*32136*/ +}; + +struct MobHealth +{ + /*0000*/ uint8 hp; //health percent + /*0001*/ uint16 id;//mobs id +}; + +struct Track_Struct { + uint16 entityid; + uint16 y; + uint16 x; + uint16 z; +}; + +struct Tracking_Struct { + Track_Struct Entrys[0]; +}; + +// Looks like new tracking structures - Opcode: 0x57a7 +struct Tracking_Struct_New { + uint16 totalcount; // Total Count of mobs within tracking range + Track_Struct Entrys[0]; +}; + +struct Track_Struct_New { + uint16 entityid; // Entity ID + uint16 unknown002; // 00 00 + uint32 unknown004; // + uint8 level; // level of mob + uint8 unknown009; // 01 maybe type of mob? player/npc? + char name[1]; // name of mob +}; + + +/* +** ZoneServerInfo_Struct +** Zone server information +** Size: 130 bytes +** Used In: OP_ZoneServerInfo +** +*/ +struct ZoneServerInfo_Struct +{ +/*0000*/ char ip[128]; +/*0128*/ uint16 port; +}; + +struct WhoAllPlayer{ + uint32 formatstring; + uint32 pidstring; + int32 unknown64; // Seen -1 + char* name; + uint32 rankstring; + char* guild; + uint32 unknown80[2]; + uint32 zonestring; + uint32 zone; + uint32 class_; + uint32 level; + uint32 race; + char* account; + uint32 unknown100; +}; + +struct WhoAllReturnStruct { + uint32 id; + uint32 playerineqstring; + char line[27]; + uint8 unknown35; // 0A + uint32 unknown36; // Seen 208243456 + uint32 playersinzonestring; + uint32 unknown52; // Same as playercount? + uint32 unknown44[2]; // 0s + uint32 unknown56; // Same as playercount? + uint32 playercount; // Player Count in the who list + struct WhoAllPlayer player[0]; +}; + +// The following four structs are the WhoAllPlayer struct above broken down +// for use in World ClientList::SendFriendsWho to accomodate the user of variable +// length strings within the struct above. + +struct WhoAllPlayerPart1 { + uint32 FormatMSGID; + uint32 Unknown04; + uint32 Unknown08; + char Name[1]; +}; + +struct WhoAllPlayerPart2 { + uint32 RankMSGID; + char Guild[1]; +}; + +struct WhoAllPlayerPart3 { + uint32 Unknown80[2]; + uint32 ZoneMSGID; + uint32 Zone; + uint32 Class_; + uint32 Level; + uint32 Race; + char Account[1]; +}; + +struct WhoAllPlayerPart4 { + uint32 Unknown100; +}; + +struct TraderItemSerial_Struct { + char SerialNumber[17]; + uint8 Unknown18; +}; + +struct Trader_Struct { +/*0000*/ uint32 Code; +/*0004*/ TraderItemSerial_Struct items[200]; +/*3604*/ uint32 ItemCost[200]; +/*4404*/ +}; + +struct ClickTrader_Struct { +/*0000*/ uint32 Code; +/*0004*/ TraderItemSerial_Struct items[200]; +/*3604*/ uint32 ItemCost[200]; +/*4404*/ +}; + +struct GetItems_Struct { + uint32 items[200]; +}; + +struct BecomeTrader_Struct { + uint32 id; + uint32 code; +}; + +struct Trader_ShowItems_Struct { +/*000*/ uint32 Code; +/*004*/ char SerialNumber[17]; +/*021*/ uint8 Unknown21; +/*022*/ uint16 TraderID; +/*026*/ uint32 Stacksize; +/*030*/ uint32 Price; +/*032*/ +}; + +struct TraderStatus_Struct { +/*000*/ uint32 Code; +/*004*/ uint32 Uknown04; +/*008*/ uint32 Uknown08; +/*012*/ +}; + +struct TraderBuy_Struct { +/*000*/ uint32 Action; +/*004*/ uint32 Unknown004; +/*008*/ uint32 Price; +/*012*/ uint32 Unknown008; // Probably high order bits of a 64 bit price. +/*016*/ uint32 TraderID; +/*020*/ char ItemName[64]; +/*084*/ uint32 Unknown076; +/*088*/ uint32 ItemID; +/*092*/ uint32 AlreadySold; +/*096*/ uint32 Quantity; +/*100*/ uint32 Unknown092; +/*104*/ +}; + +struct TraderItemUpdate_Struct{ + uint32 unknown0; + uint32 traderid; + uint8 fromslot; + uint8 toslot; //7? + uint16 charges; +}; + +struct MoneyUpdate_Struct{ + int32 platinum; + int32 gold; + int32 silver; + int32 copper; +}; + +//struct MoneyUpdate_Struct +//{ +//*0000*/ uint32 spawn_id; // ***Placeholder +//*0004*/ uint32 cointype; // Coin Type +//*0008*/ uint32 amount; // Amount +//*0012*/ +//}; + + +struct TraderDelItem_Struct{ + uint32 slotid; + uint32 quantity; + uint32 unknown; +}; + +struct TraderClick_Struct{ + uint32 traderid; + uint32 unknown4[2]; + uint32 approval; +}; + +struct FormattedMessage_Struct{ + uint32 unknown0; + uint32 string_id; + uint32 type; + char message[0]; +//*0???*/ uint8 unknown0[8]; // ***Placeholder +}; +struct SimpleMessage_Struct{ + uint32 string_id; + uint32 color; + uint32 unknown8; +}; + +// Size: 52 + strings +// Other than the strings, all of this packet is network byte order (reverse from normal) +struct GuildMemberEntry_Struct { + char name[1]; // variable length + uint32 level; + uint32 banker; // 1=yes, 0=no + uint32 class_; + uint32 rank; + uint32 time_last_on; + uint32 tribute_enable; + uint32 unknown01; // Seen 0 + uint32 total_tribute; // total guild tribute donated, network byte order + uint32 last_tribute; // unix timestamp + uint32 unknown_one; // unknown, set to 1 + char public_note[1]; // variable length. + uint16 zoneinstance; // Seen 0s or -1 in RoF2 + uint16 zone_id; // Seen 0s or -1 in RoF2 + uint32 unknown_one2; // unknown, set to 1 + uint32 unknown04; // Seen 0 +}; + +//just for display purposes, this is not actually used in the message encoding other than for size. +struct GuildMembers_Struct { + char player_name[1]; // variable length. + uint32 guildid; // Was unknown02 - network byte order + uint32 count; // network byte order + GuildMemberEntry_Struct member[0]; +}; + +struct GuildMOTD_Struct{ +/*0000*/ uint32 unknown0; +/*0004*/ char name[64]; +/*0068*/ char setby_name[64]; +/*0132*/ uint32 unknown132; +/*0136*/ char motd[0]; //was 512 +}; + +struct GuildURL_Struct{ +/*0000*/ uint32 unknown0; //index? seen server send 0 w/ the Guild URL, followed by 1 with nothing. +/*0004*/ uint32 unknown4; +/*0008*/ uint32 unknown8; //seen 7 +/*0012*/ char setby_name[64]; +/*0076*/ uint32 unknown132; //seen 0xc7de +/*0136*/ char url[4040]; +}; + +struct GuildStatus_Struct +{ +/*000*/ char Name[64]; +/*064*/ uint8 Unknown064[76]; +}; + +struct GuildMemberUpdate_Struct { +/*00*/ uint32 GuildID; +/*04*/ char MemberName[64]; +/*68*/ uint16 ZoneID; +/*70*/ uint16 InstanceID; //speculated +/*72*/ uint32 LastSeen; //unix timestamp +/*76*/ uint32 Unknown76; +/*80*/ +}; + +struct GuildMemberLevelUpdate_Struct { +/*00*/ uint32 guild_id; +/*04*/ char member_name[64]; +/*68*/ uint32 level; //not sure +}; + +struct GuildUpdate_PublicNote { + uint32 unknown0; + char name[64]; + char target[64]; + char note[100]; //we are cutting this off at 100, actually around 252 +}; + +struct GuildDemoteStruct { +/*000*/ char name[64]; +/*064*/ char target[64]; +/*128*/ uint32 rank; // New in RoF2 +/*132*/ +}; + +struct GuildRemoveStruct { +/*000*/ char target[64]; +/*064*/ char name[64]; +/*128*/ uint32 GuildID; // Was unknown128 +/*132*/ uint32 leaderstatus; +/*136*/ uint32 unknown136; // New in RoF2 +/*140*/ +}; + +struct GuildMakeLeader { + char name[64]; + char target[64]; +}; + +// Server -> Client +// Update a guild members rank and banker status +struct GuildSetRank_Struct +{ +/*00*/ uint32 GuildID; // Was Unknown00 +/*04*/ uint32 Rank; +/*08*/ char MemberName[64]; +/*72*/ uint32 Banker; +/*76*/ uint32 Unknown76; // Seen 1 - Maybe Banker? +/*80*/ +}; + +struct BugStruct{ +/*0000*/ char chartype[64]; +/*0064*/ char name[96]; +/*0160*/ char ui[128]; +/*0288*/ float x; +/*0292*/ float y; +/*0296*/ float z; +/*0300*/ float heading; +/*0304*/ uint32 unknown304; +/*0308*/ uint32 type; +/*0312*/ char unknown312[2144]; +/*2456*/ char bug[1024]; +/*3480*/ char placeholder[2]; +/*3482*/ char system_info[4098]; +}; +struct Make_Pet_Struct { //Simple struct for getting pet info + uint8 level; + uint8 class_; + uint16 race; + uint8 texture; + uint8 pettype; + float size; + uint8 type; + uint32 min_dmg; + uint32 max_dmg; +}; +struct Ground_Spawn{ + float max_x; + float max_y; + float min_x; + float min_y; + float max_z; + float heading; + char name[16]; + uint32 item; + uint32 max_allowed; + uint32 respawntimer; +}; +struct Ground_Spawns { + struct Ground_Spawn spawn[50]; //Assigned max number to allow +}; +struct PetitionBug_Struct{ + uint32 petition_number; + uint32 unknown4; + char accountname[64]; + uint32 zoneid; + char name[64]; + uint32 level; + uint32 class_; + uint32 race; + uint32 unknown152[3]; + uint32 time; + uint32 unknown168; + char text[1028]; +}; + +struct DyeStruct +{ + union + { + struct + { + struct Color_Struct head; + struct Color_Struct chest; + struct Color_Struct arms; + struct Color_Struct wrists; + struct Color_Struct hands; + struct Color_Struct legs; + struct Color_Struct feet; + struct Color_Struct primary; // you can't actually dye this + struct Color_Struct secondary; // or this + } + dyes; + struct Color_Struct dye[9]; + }; +}; + +struct ApproveZone_Struct { + char name[64]; + uint32 zoneid; + uint32 approve; +}; +struct ZoneInSendName_Struct { + uint32 unknown0; + char name[64]; + char name2[64]; + uint32 unknown132; +}; +struct ZoneInSendName_Struct2 { + uint32 unknown0; + char name[64]; + uint32 unknown68[145]; +}; + +struct StartTribute_Struct { + uint32 client_id; + uint32 tribute_master_id; + uint32 response; +}; + +struct TributeLevel_Struct { + uint32 level; //backwards byte order! + uint32 tribute_item_id; //backwards byte order! + uint32 cost; //backwards byte order! +}; + +struct TributeAbility_Struct { +/*000*/ uint32 tribute_id; //backwards byte order! +/*004*/ uint32 tier_count; //backwards byte order! +/*008*/ TributeLevel_Struct tiers[MAX_TRIBUTE_TIERS]; +/*128*/ uint32 unknown128; // New to RoF2 +/*132*/ char name[0]; +}; + +struct GuildTributeAbility_Struct { + uint32 guild_id; + TributeAbility_Struct ability; +}; + +struct SelectTributeReq_Struct { + uint32 client_id; //? maybe action ID? + uint32 tribute_id; + uint32 unknown8; //seen E3 00 00 00 +}; + +struct SelectTributeReply_Struct { + uint32 client_id; //echoed from request. + uint32 tribute_id; + char desc[0]; +}; + +struct TributeInfo_Struct { + uint32 active; //0 == inactive, 1 == active + uint32 tributes[MAX_PLAYER_TRIBUTES]; //-1 == NONE + uint32 tiers[MAX_PLAYER_TRIBUTES]; //all 00's + uint32 tribute_master_id; +}; + +struct TributeItem_Struct +{ +/*00*/ ItemSlotStruct slot; +/*12*/ uint32 quantity; +/*16*/ uint32 tribute_master_id; +/*20*/ int32 tribute_points; +/*24*/ +}; + +struct TributePoint_Struct { + int32 tribute_points; + uint32 unknown04; + int32 career_tribute_points; + uint32 unknown12; +}; + +struct TributeMoney_Struct { + uint32 platinum; + uint32 tribute_master_id; + int32 tribute_points; +}; + + +struct Split_Struct +{ + uint32 platinum; + uint32 gold; + uint32 silver; + uint32 copper; +}; + + +/* +** New Combine Struct +** Client requesting to perform a tradeskill combine +** Size: 24 bytes +** Used In: OP_TradeSkillCombine +** Last Updated: 01-05-2013 +*/ +struct NewCombine_Struct { +/*00*/ ItemSlotStruct container_slot; +/*12*/ ItemSlotStruct guildtribute_slot; // Slot type is 8? (MapGuildTribute = 8 -U) +/*24*/ +}; + + +//client requesting favorite recipies +struct TradeskillFavorites_Struct { + uint32 object_type; + uint32 some_id; + uint32 favorite_recipes[500]; +}; + +//search request +struct RecipesSearch_Struct { + uint32 object_type; //same as in favorites + uint32 some_id; //same as in favorites + uint32 mintrivial; + uint32 maxtrivial; + char query[56]; + uint32 unknown4; //is set to 00 03 00 00 + uint32 unknown5; //is set to 4C DD 12 00 +/*80*/ +}; + +//one sent for each item, from server in reply to favorites or search +struct RecipeReply_Struct { + uint32 object_type; + uint32 some_id; //same as in favorites + uint32 component_count; + uint32 recipe_id; + uint32 trivial; + char recipe_name[64]; +/*84*/ +}; + +//received and sent back as an ACK with different reply_code +struct RecipeAutoCombine_Struct { +/*00*/ uint32 object_type; +/*04*/ uint32 some_id; +/*08*/ ItemSlotStruct container_slot; //echoed in reply - Was uint32 unknown1 +/*20*/ ItemSlotStruct unknown_slot; //echoed in reply +/*32*/ uint32 recipe_id; +/*36*/ uint32 reply_code; +/*40*/ +}; + +struct LevelAppearance_Struct { //Sends a little graphic on level up + uint32 spawn_id; + uint32 parm1; + uint32 value1a; + uint32 value1b; + uint32 parm2; + uint32 value2a; + uint32 value2b; + uint32 parm3; + uint32 value3a; + uint32 value3b; + uint32 parm4; + uint32 value4a; + uint32 value4b; + uint32 parm5; + uint32 value5a; + uint32 value5b; +/*64*/ +}; +struct MerchantList{ + uint32 id; + uint32 slot; + uint32 item; +}; +struct TempMerchantList{ + uint32 npcid; + uint32 slot; + uint32 item; + uint32 charges; //charges/quantity + uint32 origslot; +}; + + +struct FindPerson_Point { + float y; + float x; + float z; +}; + +struct FindPersonRequest_Struct { +/*00*/ uint32 unknown00; +/*04*/ uint32 npc_id; +/*08*/ uint32 unknown08; +/*12*/ uint32 unknown12; +/*16*/ FindPerson_Point client_pos; +/*28*/ uint32 unknown28; +/*32*/ uint32 unknown32; +/*36*/ uint32 unknown36; +}; + +//variable length packet of points +struct FindPersonResult_Struct { + FindPerson_Point dest; + FindPerson_Point path[0]; //last element must be the same as dest +}; + +struct MobRename_Struct { +/*000*/ char old_name[64]; +/*064*/ char old_name_again[64]; //not sure what the difference is +/*128*/ char new_name[64]; +/*192*/ uint32 unknown192; //set to 0 +/*196*/ uint32 unknown196; //set to 1 +/*200*/ +}; + +struct PlayMP3_Struct { + char filename[0]; +}; + +//this is for custom title display in the skill window +struct TitleEntry_Struct { + uint32 skill_id; + uint32 skill_value; + char title[1]; +}; + +struct Titles_Struct { + uint32 title_count; + TitleEntry_Struct titles[0]; +}; + +//this is for title selection by the client +struct TitleListEntry_Struct { + uint32 unknown0; //title ID + char prefix[1]; //variable length, null terminated + char postfix[1]; //variable length, null terminated +}; + +struct TitleList_Struct { + uint32 title_count; + TitleListEntry_Struct titles[0]; //list of title structs + //uint32 unknown_ending; seen 0x7265, 0 +}; + +struct SetTitle_Struct { + uint32 is_suffix; //guessed: 0 = prefix, 1 = suffix + uint32 title_id; +}; + +struct SetTitleReply_Struct { + uint32 is_suffix; //guessed: 0 = prefix, 1 = suffix + char title[32]; + uint32 entity_id; +}; + + +#if 0 +// Old struct not used by Task System implementation but left for reference +struct TaskDescription_Struct { +/*000*/ uint32 activity_count; //not right. +/*004*/ uint32 taskid; +/*008*/ uint8 unk; +/*009*/ uint32 id3; +/*013*/ uint32 unknown13; +/*017*/ char name[1]; //variable length, 0 terminated +/*018*/ uint32 unknown18; +/*022*/ uint32 unknown22; +/*026*/ uint32 unknown26; +/*030*/ char desc[1]; //variable length, 0 terminated +/*031*/ uint32 reward_count; //not sure +/*035*/ uint8 unknown31; +/*036*/ uint32 unknown31; +/*040*/ uint32 unknown35; +/*044*/ uint16 unknown39; +/*046*/ char reward_link[1]; //variable length, 0 terminated +/*047*/ uint32 unknown43; //maybe crystal count? +/*051*/ +}; +#endif + +struct TaskMemberList_Struct { +/*00*/ uint32 gopher_id; +/*04*/ uint32 unknown04; +/*08*/ uint32 member_count; //1 less than the number of members +/*12*/ char list_pointer[0]; +/* list is of the form: + char member_name[1] //null terminated string + uint8 task_leader //boolean flag +*/ +}; + +#if 0 +// Struct not used by Task System implentation but left for reference (current for RoF2) +struct TaskActivity_Struct { +/*000*/ uint32 TaskSequenceNumber; +/*004*/ uint32 unknown2; +/*008*/ uint32 TaskID; +/*012*/ uint32 ActivityID; +/*016*/ uint32 unknown3; +/*020*/ uint32 ActivityType; +/*024*/ uint32 Optional; +/*028*/ uint8 unknown5; +/*032*/ char Text1[1]; // Variable length - Null terminated +/*000*/ uint32 Text2Len; // Lenth of the following string +/*000*/ char Text2[1]; // Variable length - not Null terminated +/*000*/ uint32 GoalCount; +/*000*/ uint32 String1Len; // Lenth of the following string - Seen 2 +/*000*/ char String1[1]; // Numeric String - Seen "-1" - not Null terminated +/*000*/ uint32 String2Len; // Lenth of the following string - Seen 2 +/*000*/ char String2[1]; // Numeric String - Seen "-1" - not Null terminated +/*000*/ char ZoneIDString1[1]; // Numeric String - Seen "398" - Null terminated +/*000*/ uint32 unknown7; // Seen 0 +/*000*/ char Text3[1]; // Variable length - Null terminated +/*000*/ uint32 DoneCount; +/*000*/ uint8 unknown9; // Seen 1 +/*000*/ char ZoneIDString2[1]; // Numeric String - Seen "398" - Null terminated +}; + +struct TaskHistoryEntry_Struct { + uint32 task_id; + char name[1]; + uint32 completed_time; +}; +struct TaskHistory_Struct { + uint32 completed_count; + TaskHistoryEntry_Struct entries[0]; +}; +#endif + +struct AcceptNewTask_Struct { + uint32 unknown00; + uint32 task_id; //set to 0 for 'decline' + uint32 task_master_id; //entity ID +}; + +//was all 0's from client, server replied with same op, all 0's +struct CancelTask_Struct { + uint32 SequenceNumber; + uint32 unknown4; // Only seen 0x00000002 +}; + +#if 0 +// old struct, not used by Task System implementation but left for reference. +struct AvaliableTask_Struct { + uint32 task_index; //no idea, seen 0x1 + uint32 task_master_id; //entity ID + uint32 task_id; + uint32 unknown012; + uint32 activity_count; //not sure, seen 2 + char desc[1]; //variable length, 0 terminated + uint32 reward_platinum;//not sure on these + uint32 reward_gold; + uint32 reward_silver; + uint32 reward_copper; + char some_name[1]; //variable length, 0 terminated + uint8 unknown1; + uint32 unknown2; //0xFFFFFFFF + uint32 unknown3; //0xFFFFFFFF + uint32 unknown4; //seen 0x16 + uint8 unknown5; +}; +#endif + + +// Many of the Task System packets contain variable length strings, as well as variable numbers +// of records, hence splitting them into multiple structs (header, middle, trailer) etc. +// +struct AvailableTaskHeader_Struct { + uint32 TaskCount; + uint32 unknown1; + uint32 TaskGiver; +}; + +struct AvailableTaskData1_Struct { + uint32 TaskID; + uint32 unknown1; + uint32 TimeLimit; + uint32 unknown2; +}; + +struct AvailableTaskData2_Struct { + uint32 unknown1,unknown2,unknown3,unknown4; +}; + +struct AvailableTaskTrailer_Struct { + uint32 ItemCount; + uint32 unknown1, unknown2; + uint32 StartZone; +}; + +struct TaskDescriptionHeader_Struct { + uint32 SequenceNumber; // The order the tasks appear in the journal. 0 for first task, 1 for second, etc. + uint32 TaskID; + uint32 unknown2; + uint32 unknown3; + uint8 unknown4; +}; + +struct TaskDescriptionData1_Struct { + uint32 Duration; + uint32 unknown2; + uint32 StartTime; +}; + +struct TaskDescriptionData2_Struct { + uint32 RewardCount; // ?? + uint32 unknown1; + uint32 unknown2; + uint16 unknown3; + //uint8 unknown4; +}; + +struct TaskDescriptionTrailer_Struct { + //uint16 unknown1; // 0x0012 + uint32 Points; +}; + +struct TaskActivityHeader_Struct { + uint32 TaskSequenceNumber; + uint32 unknown2; // Seen 0x00000002 + uint32 TaskID; + uint32 ActivityID; + uint32 unknown3; + uint32 ActivityType; + uint32 Optional; + uint32 unknown5; +}; + +struct TaskActivityData1_Struct { + uint32 GoalCount; + uint32 unknown1; // 0xffffffff + uint32 unknown2; // 0xffffffff + uint32 ZoneID; // seen 0x36 + uint32 unknown3; +}; + +struct TaskActivityTrailer_Struct { + uint32 DoneCount; + uint32 unknown1; // Seen 1 +}; + +// The Short_Struct is sent for tasks that are hidden and act as a placeholder +struct TaskActivityShort_Struct { + uint32 TaskSequenceNumber; + uint32 unknown2; // Seen 0x00000002 + uint32 TaskID; + uint32 ActivityID; + uint32 unknown3; + uint32 ActivityType; // 0xffffffff for the short packet + uint32 unknown4; +}; + +struct TaskActivityComplete_Struct { + uint32 TaskIndex; + uint32 unknown2; // 0x00000002 + uint32 unknown3; + uint32 ActivityID; + uint32 unknown4; // 0x00000001 + uint32 unknown5; // 0x00000001 +}; + +#if 0 +// This is a dupe of the CancelTask struct +struct TaskComplete_Struct { + uint32 unknown00; // 0x00000000 + uint32 unknown04; // 0x00000002 +}; +#endif + +struct TaskHistoryRequest_Struct { + uint32 TaskIndex; // This is the sequence the task was sent in the Completed Tasks packet. +}; + +struct TaskHistoryReplyHeader_Struct { + uint32 TaskID; + uint32 ActivityCount; +}; + +struct TaskHistoryReplyData1_Struct { + uint32 ActivityType; +}; + +struct TaskHistoryReplyData2_Struct { + uint32 GoalCount; + uint32 unknown04; // 0xffffffff + uint32 unknown08; // 0xffffffff + uint32 ZoneID; + uint32 unknown16; +}; + +struct BankerChange_Struct { +/*00*/ uint32 platinum; +/*04*/ uint32 gold; +/*08*/ uint32 silver; +/*12*/ uint32 copper; +/*16*/ uint32 platinum_bank; +/*20*/ uint32 gold_bank; +/*24*/ uint32 silver_bank; +/*28*/ uint32 copper_bank; +/*32*/ +}; + +struct LeadershipExpUpdate_Struct { +/*00*/ double group_leadership_exp; +/*08*/ uint32 group_leadership_points; +/*12*/ uint32 Unknown12; +/*16*/ double raid_leadership_exp; +/*24*/ uint32 raid_leadership_points; +}; + +struct UpdateLeadershipAA_Struct { +/*00*/ uint32 ability_id; +/*04*/ uint32 new_rank; +/*08*/ uint32 unknown08; +/*12*/ +}; + +/** +* Leadership AA update +* Length: 32 Octets +* OpCode: LeadExpUpdate +*/ +struct leadExpUpdateStruct { + /*0000*/ uint32 unknown0000; // All zeroes? + /*0004*/ uint32 group_leadership_exp; // Group leadership exp value + /*0008*/ uint32 group_leadership_points; // Unspent group points + /*0012*/ uint32 unknown0012; // Type? + /*0016*/ uint32 unknown0016; // All zeroes? + /*0020*/ uint32 raid_leadership_exp; // Raid leadership exp value + /*0024*/ uint32 raid_leadership_points; // Unspent raid points + /*0028*/ uint32 unknown0028; +}; + +struct RaidGeneral_Struct { +/*00*/ uint32 action; +/*04*/ char player_name[64]; +/*68*/ uint32 unknown68; +/*72*/ char leader_name[64]; +/*136*/ uint32 parameter; +}; + +struct RaidAddMember_Struct { +/*000*/ RaidGeneral_Struct raidGen; //param = (group num-1); 0xFFFFFFFF = no group +/*136*/ uint8 _class; +/*137*/ uint8 level; +/*138*/ uint8 isGroupLeader; +/*139*/ uint8 flags[5]; //no idea if these are needed... +}; + +struct RaidMOTD_Struct { +/*000*/ RaidGeneral_Struct general; // leader_name and action only used +/*140*/ char motd[0]; // max size 1024, but reply is variable +}; + +struct RaidLeadershipUpdate_Struct { +/*000*/ uint32 action; +/*004*/ char player_name[64]; +/*068*/ uint32 Unknown068; +/*072*/ char leader_name[64]; +/*136*/ GroupLeadershipAA_Struct group; //unneeded +/*200*/ RaidLeadershipAA_Struct raid; +/*264*/ char Unknown264[128]; +}; + +struct RaidAdd_Struct { +/*000*/ uint32 action; //=0 +/*004*/ char player_name[64]; //should both be the player's name +/*068*/ char leader_name[64]; +/*132*/ uint8 _class; +/*133*/ uint8 level; +/*134*/ uint8 has_group; +/*135*/ uint8 unknown135; //seems to be 0x42 or 0 +}; + +struct RaidCreate_Struct { +/*00*/ uint32 action; //=8 +/*04*/ char leader_name[64]; +/*68*/ uint32 leader_id; +}; + +struct RaidMemberInfo_Struct { +/*00*/ uint8 group_number; +/*01*/ char member_name[1]; //dyanmic length, null terminated '\0' +/*00*/ uint8 unknown00; +/*01*/ uint8 _class; +/*02*/ uint8 level; +/*03*/ uint8 is_raid_leader; +/*04*/ uint8 is_group_leader; +/*05*/ uint8 main_tank; //not sure +/*06*/ uint8 unknown06[5]; //prolly more flags +}; + +struct RaidDetails_Struct { +/*000*/ uint32 action; //=6,20 +/*004*/ char leader_name[64]; +/*068*/ uint32 unknown68[4]; +/*084*/ LeadershipAA_Struct abilities; //ranks in backwards byte order +/*128*/ uint8 unknown128[142]; +/*354*/ uint32 leader_id; +}; + +struct RaidMembers_Struct { +/*000*/ RaidDetails_Struct details; +/*358*/ uint32 member_count; //including leader +/*362*/ RaidMemberInfo_Struct members[1]; +/*...*/ RaidMemberInfo_Struct empty; //seem to have an extra member with a 0 length name on the end +}; + +struct DynamicWall_Struct { +/*00*/ char name[32]; +/*32*/ float y; +/*36*/ float x; +/*40*/ float z; +/*44*/ uint32 something; +/*48*/ uint32 unknown48; //0 +/*52*/ uint32 one_hundred; //0x64 +/*56*/ uint32 unknown56; //0 +/*60*/ uint32 something2; +/*64*/ int32 unknown64; //-1 +/*68*/ uint32 unknown68; //0 +/*72*/ uint32 unknown72; //0 +/*76*/ uint32 unknown76; //0x100 +/*80*/ +}; + +enum { //bandolier actions + BandolierCreate = 0, + BandolierRemove = 1, + BandolierSet = 2 +}; + +struct BandolierCreate_Struct { +/*00*/ uint32 action; //0 for create +/*04*/ uint8 number; +/*05*/ char name[32]; +/*37*/ uint16 unknown37; //seen 0x93FD +/*39*/ uint8 unknown39; //0 +}; + +struct BandolierDelete_Struct { +/*00*/ uint32 action; +/*04*/ uint8 number; +/*05*/ uint8 unknown05[35]; +}; + +struct BandolierSet_Struct { +/*00*/ uint32 action; +/*04*/ uint8 number; +/*05*/ uint8 unknown05[35]; +}; + +struct Arrow_Struct { +/*000*/ float src_y; +/*004*/ float src_x; +/*008*/ float src_z; +/*012*/ uint8 unknown012[12]; +/*024*/ float velocity; //4 is normal, 20 is quite fast +/*028*/ float launch_angle; //0-450ish, not sure the units, 140ish is straight +/*032*/ float tilt; //on the order of 125 +/*036*/ uint8 unknown036[8]; +/*044*/ float arc; +/*048*/ uint32 source_id; +/*052*/ uint32 target_id; //entity ID +/*056*/ uint32 item_id; +/*060*/ uint32 unknown060; +/*064*/ uint32 unknown064; +/*068*/ uint8 unknown068; +/*069*/ uint8 unknown069; +/*070*/ uint8 unknown070; +/*071*/ uint8 item_type; +/*072*/ uint8 skill; +/*073*/ uint8 unknown073[16]; +/*089*/ char model_name[27]; +/*116*/ +}; + +//made a bunch of trivial structs for stuff for opcode finder to use +struct Consent_Struct { + char name[1]; //always at least a null - was 1 +}; + +struct AdventureMerchant_Struct { + uint32 unknown_flag; //seems to be 1 + uint32 entity_id; +}; + +// OP_Save - Size: 484 +struct Save_Struct { +/*000*/ uint8 unknown00[192]; +/*192*/ uint8 unknown0192[176]; +/*368*/ uint8 unknown0368[116]; +/*484*/ +}; + +struct GMToggle_Struct { + uint8 unknown0[64]; + uint32 toggle; +}; + +struct ColoredText_Struct { + uint32 color; + char msg[1]; //was 1 +/*0???*/ uint8 paddingXXX[3]; // always 0's +}; + +struct UseAA_Struct { + uint32 begin; + uint32 ability; + uint32 end; +}; + +struct AA_Ability { +/*00*/ uint32 skill_id; +/*04*/ uint32 base1; +/*08*/ uint32 base2; +/*12*/ uint32 slot; +/*16*/ +}; + +struct SendAA_Struct { +/*0000*/ uint32 id; +/*0004*/ uint8 unknown004; // uint32 unknown004; set to 1. +/*0005*/ int32 hotkey_sid; +/*0009*/ int32 hotkey_sid2; +/*0013*/ uint32 title_sid; +/*0017*/ uint32 desc_sid; +/*0021*/ uint32 class_type; +/*0025*/ uint32 cost; +/*0029*/ uint32 seq; +/*0033*/ uint32 current_level; //1s, MQ2 calls this AARankRequired +/*0037*/ uint32 unknown037; // Introduced during HoT +/*0041*/ uint32 prereq_skill; //is < 0, abs() is category # +/*0045*/ uint32 unknown045; // New Mar 21 2012 - Seen 1 +/*0049*/ uint32 prereq_minpoints; //min points in the prereq +/*0053*/ uint32 type; +/*0057*/ uint32 spellid; +/*0061*/ uint32 unknown057; // Introduced during HoT - Seen 1 - Maybe account status or enable/disable AA? +/*0065*/ uint32 spell_type; +/*0069*/ uint32 spell_refresh; +/*0073*/ uint16 classes; +/*0075*/ uint16 berserker; //seems to be 1 if its a berserker ability +/*0077*/ uint32 max_level; +/*0081*/ uint32 last_id; +/*0085*/ uint32 next_id; +/*0089*/ uint32 cost2; +/*0093*/ uint8 unknown80[7]; +/*0100*/ uint32 aa_expansion; +/*0104*/ uint32 special_category; +/*0108*/ uint32 unknown0096; +/*0112*/ uint32 total_abilities; +/*0116*/ AA_Ability abilities[0]; +}; + +struct AA_List { + SendAA_Struct* aa[0]; +}; + +struct AA_Action { +/*00*/ uint32 action; +/*04*/ uint32 ability; +/*08*/ uint32 unknown08; +/*12*/ uint32 exp_value; +/*16*/ +}; + +struct AA_Skills { //this should be removed and changed to AA_Array +/*00*/ uint32 aa_skill; // Total AAs Spent +/*04*/ uint32 aa_value; +/*08*/ uint32 unknown08; +/*12*/ +}; + +struct AAExpUpdate_Struct { +/*00*/ uint32 unknown00; //seems to be a value from AA_Action.ability +/*04*/ uint32 aapoints_unspent; +/*08*/ uint8 aaxp_percent; //% of exp that goes to AAs +/*09*/ uint8 unknown09[3]; //live doesn't always zero these, so they arnt part of aaxp_percent +/*12*/ +}; + +struct AltAdvStats_Struct { +/*000*/ uint32 experience; +/*004*/ uint16 unspent; +/*006*/ uint16 unknown006; +/*008*/ uint8 percentage; +/*009*/ uint8 unknown009[3]; +/*012*/ +}; + +struct PlayerAA_Struct { // Is this still used? + AA_Skills aa_list[MAX_PP_AA_ARRAY]; +}; + +struct AA_Values { +/*00*/ uint32 aa_skill; +/*04*/ uint32 aa_value; +/*08*/ uint32 unknown08; +/*12*/ +}; + +struct AATable_Struct { +/*00*/ uint32 aa_spent; // Total AAs Spent +/*04*/ uint32 aapoints_assigned; // Number of Assigned AA points - Seen 206 (total of the 4 fields below) +/*08*/ uint32 aa_spent_general; // Seen 63 +/*12*/ uint32 aa_spent_archetype; // Seen 40 +/*16*/ uint32 aa_spent_class; // Seen 103 +/*20*/ uint32 aa_spent_special; // Seen 0 +/*24*/ AA_Values aa_list[MAX_PP_AA_ARRAY]; +}; + +struct Weather_Struct { + uint32 val1; //generall 0x000000FF + uint32 type; //0x31=rain, 0x02=snow(i think), 0 = normal + uint32 mode; +}; + +struct ZoneInUnknown_Struct { + uint32 val1; + uint32 val2; + uint32 val3; +}; + +struct MobHealth_Struct { + uint16 entity_id; + uint8 hp; +}; + +struct AnnoyingZoneUnknown_Struct { + uint32 entity_id; + uint32 value; //always 4 +}; + +struct LoadSpellSet_Struct { + uint8 spell[12]; // 0xFFFFFFFF if no action, slot number if to unmem starting at 0 + uint32 unknown; //Seen 12 - Maybe a gem count? +}; + +struct BlockedBuffs_Struct +{ +/*000*/ int32 SpellID[BLOCKED_BUFF_COUNT]; +/*120*/ uint32 Count; +/*124*/ uint8 Pet; +/*125*/ uint8 Initialise; +/*126*/ uint16 Flags; +}; + +//Size 24 Bytes +struct WorldObfuscator_Struct { +/*000*/ uint32 var1; +/*004*/ uint32 Unknown1; +/*008*/ uint32 Unknown2; +/*012*/ uint32 Unknown3; +/*016*/ uint32 var2; +/*020*/ uint32 Unknown4; +/*024*/ +}; + +struct ExpansionInfo_Struct { +/*000*/ char Unknown000[64]; +/*064*/ uint32 Expansions; +}; + +struct ApplyPoison_Struct { + MainInvItemSlotStruct inventorySlot; + uint32 success; +}; + +struct ItemVerifyRequest_Struct { +/*000*/ ItemSlotStruct slot; +/*012*/ uint32 target; // Target Entity ID +/*016*/ +}; + +struct ItemVerifyReply_Struct { +/*000*/ ItemSlotStruct slot; +/*012*/ uint32 spell; // Spell ID to cast if different than item effect +/*016*/ uint32 target; // Target Entity ID +/*020*/ +}; + + +struct RoF2SlotStruct +{ + uint8 Bank; + uint16 MainSlot; + uint16 SubSlot; +}; + +struct ItemSerializationHeader +{ +/*000*/ char unknown000[13]; // New for HoT. Looks like a string. +/*017*/ uint32 stacksize; +/*021*/ uint32 unknown004; +/*025*/ uint8 slot_type; // 0 = normal, 1 = bank, 2 = shared bank, 9 = merchant, 20 = ? +/*026*/ uint16 main_slot; +/*028*/ uint16 sub_slot; +/*030*/ uint16 unknown013; // 0xffff +/*032*/ uint32 price; +/*036*/ uint32 merchant_slot; //1 if not a merchant item +/*040*/ uint32 scaled_value; //0 +/*044*/ uint32 instance_id; //unique instance id if not merchant item, else is merchant slot +/*048*/ uint32 unknown028; //0 +/*052*/ uint32 last_cast_time; // Unix Time from PP of last cast for this recast type if recast delay > 0 +/*056*/ uint32 charges; //Total Charges an item has (-1 for unlimited) +/*060*/ uint32 inst_nodrop; // 1 if the item is no drop (attuned items) +/*064*/ uint32 unknown044; // 0 +/*068*/ uint32 unknown048; // 0 +/*072*/ uint32 unknown052; // 0 + uint8 isEvolving; +}; + +struct EvolvingItem { + uint8 unknown001; + uint8 unknown002; + uint8 unknown003; + uint8 unknown004; + int32 evoLevel; + double progress; + uint8 Activated; + int32 evomaxlevel; + uint8 unknown005[4]; +}; + +struct ItemSerializationHeaderFinish +{ + uint16 ornamentIcon; +/*081*/ uint8 unknown061; // 0 - Add Evolving Item struct if this isn't set to 0? +/*082*/ uint8 unknown062; // 0 +/*083*/ uint32 unknowna1; // 0xffffffff +/*087*/ uint32 unknowna2; // 0 +/*091*/ uint8 unknown063; // 0 +/*092*/ uint32 unknowna3; // 0 +/*096*/ uint32 unknowna4; // 0xffffffff +/*100*/ uint32 unknowna5; // 0 +/*104*/ uint8 ItemClass; //0, 1, or 2 +/*105*/ +}; + +struct ItemBodyStruct +{ + uint32 id; + int32 weight; // Seen an item on Live with -0.1 weight + uint8 norent; + uint8 nodrop; + uint8 attune; + uint8 size; + uint32 slots; + uint32 price; + uint32 icon; + uint8 unknown1; + uint8 unknown2; + uint32 BenefitFlag; + uint8 tradeskills; + int8 CR; + int8 DR; + int8 PR; + int8 MR; + int8 FR; + int8 SVCorruption; + int8 AStr; + int8 ASta; + int8 AAgi; + int8 ADex; + int8 ACha; + int8 AInt; + int8 AWis; + int32 HP; + int32 Mana; + uint32 Endur; + int32 AC; + int32 regen; + int32 mana_regen; + int32 end_regen; + uint32 Classes; + uint32 Races; + uint32 Deity; + int32 SkillModValue; + int32 SkillModMax; // Max skill point modification + int32 SkillModType; + uint32 SkillModExtra; // Adds a "+value" after the mod percentage + uint32 BaneDmgRace; + uint32 BaneDmgBody; + uint32 BaneDmgRaceAmt; + int32 BaneDmgAmt; + uint8 Magic; + int32 CastTime_; + uint32 ReqLevel; + uint32 RecLevel; + uint32 RecSkill; + uint32 BardType; + int32 BardValue; + uint8 Light; + uint8 Delay; + uint8 ElemDmgType; + uint8 ElemDmgAmt; + uint8 Range; + uint32 Damage; + uint32 Color; + uint32 Prestige; // New to March 21 2012 client + uint8 ItemType; + uint32 Material; + uint32 unknown7; + uint32 EliteMaterial; + uint32 unknown_RoF23; // New to March 21 2012 client + uint32 unknown_RoF24; // New to December 10th 2012 client - NEW + float SellRate; + int32 CombatEffects; + int32 Shielding; + int32 StunResist; + int32 StrikeThrough; + int32 ExtraDmgSkill; + int32 ExtraDmgAmt; + int32 SpellShield; + int32 Avoidance; + int32 Accuracy; + uint32 CharmFileID; + uint32 FactionMod1; + int32 FactionAmt1; + uint32 FactionMod2; + int32 FactionAmt2; + uint32 FactionMod3; + int32 FactionAmt3; + uint32 FactionMod4; + int32 FactionAmt4; +}; + +struct AugSlotStruct +{ + uint32 type; + uint8 visible; + uint8 unknown; +}; + +struct ItemSecondaryBodyStruct +{ + uint32 augtype; + // swapped augrestrict and augdistiller positions + // (this swap does show the proper augment restrictions in Item Information window now) + // unsure what the purpose of augdistiller is at this time -U 3/17/2014 + uint32 augdistiller; // New to December 10th 2012 client - NEW + uint32 augrestrict; + AugSlotStruct augslots[6]; + + uint32 ldonpoint_type; + uint32 ldontheme; + uint32 ldonprice; + uint32 ldonsellbackrate; + uint32 ldonsold; + + uint8 bagtype; + uint8 bagslots; + uint8 bagsize; + uint8 wreduction; + + uint8 book; + uint8 booktype; + //int32 filename; filename is either 0xffffffff/0x00000000 or the null term string ex: CREWizardNote\0 +}; + +struct ItemTertiaryBodyStruct +{ + int32 loregroup; + uint8 artifact; + uint8 summonedflag; + uint32 favor; + uint8 fvnodrop; + int32 dotshield; + int32 atk; + int32 haste; + int32 damage_shield; + uint32 guildfavor; + uint32 augdistil; + int32 unknown3; // 0xffffffff + uint32 unknown4; + uint8 no_pet; + uint8 unknown5; + + uint8 potion_belt_enabled; + uint32 potion_belt_slots; + + uint32 stacksize; + uint8 no_transfer; + uint16 expendablearrow; + + uint32 unknown8; + uint32 unknown9; + uint32 unknown10; + uint32 unknown11; + uint8 unknown12; + uint8 unknown13; + uint8 unknown14; +}; + +struct ClickEffectStruct +{ + int32 effect; + uint8 level2; + uint32 type; + uint8 level; + int32 max_charges; + int32 cast_time; + uint32 recast; + int32 recast_type; + uint32 clickunk5; + //uint8 effect_string; + //int32 clickunk7; +}; + +struct ProcEffectStruct +{ + uint32 effect; + uint8 level2; + uint32 type; + uint8 level; + uint32 unknown1; // poison? + uint32 unknown2; + uint32 unknown3; + uint32 unknown4; + uint32 procrate; + //uint8 effect_string; + //uint32 unknown5; +}; + +struct WornEffectStruct //worn, focus and scroll effect +{ + uint32 effect; + uint8 level2; + uint32 type; + uint8 level; + uint32 unknown1; + uint32 unknown2; + uint32 unknown3; + uint32 unknown4; + uint32 unknown5; + //uint8 effect_string; + //uint32 unknown6; +}; + +struct ItemQuaternaryBodyStruct +{ + uint32 scriptfileid; + uint8 quest_item; + uint32 Power; // Enables "Power" percentage field used by Power Sources + uint32 Purity; + uint8 unknown16; // RoF2 + uint32 BackstabDmg; + uint32 DSMitigation; + int32 HeroicStr; + int32 HeroicInt; + int32 HeroicWis; + int32 HeroicAgi; + int32 HeroicDex; + int32 HeroicSta; + int32 HeroicCha; + int32 HeroicMR; + int32 HeroicFR; + int32 HeroicCR; + int32 HeroicDR; + int32 HeroicPR; + int32 HeroicSVCorrup; + int32 HealAmt; + int32 SpellDmg; + int32 clairvoyance; + uint8 unknown18; //Power Source Capacity or evolve filename? + uint32 evolve_string; // Some String, but being evolution related is just a guess + uint8 unknown19; + uint32 unknown20; // Bard Stuff? + //uint32 unknown21; + uint8 unknown22; + uint32 unknown23; + uint32 unknown24; + uint32 unknown25; + float unknown26; + float unknown27; + uint32 unknown_RoF26; // 0 New to March 21 2012 client + uint32 unknown28; // 0xffffffff + uint16 unknown29; + uint32 unknown30; // 0xffffffff + uint16 unknown31; + uint32 unknown32; + float unknown33; + uint32 unknown34; + uint32 unknown35; + uint32 unknown36; + uint32 unknown37; + uint32 unknown_RoF27; + uint32 unknown_RoF28; + + // Begin RoF2 Test + uint8 unknown_TEST1; + // End RoF2 Test + + uint8 unknown38; // 0 + uint8 unknown39; // 1 + uint32 subitem_count; +}; + +struct AugmentInfo_Struct +{ +/*000*/ uint32 itemid; // id of the solvent needed +/*004*/ uint32 window; // window to display the information in +/*008*/ char augment_info[64]; // total packet length 76, all the rest were always 00 +/*072*/ uint32 unknown072; +}; + +struct VeteranRewardItem +{ +/*000*/ uint32 item_id; +/*004*/ uint32 charges; +/*008*/ char item_name[64]; +}; + +struct VeteranReward +{ +/*000*/ uint32 claim_id; +/*004*/ uint32 number_available; +/*008*/ uint32 claim_count; +/*012*/ VeteranRewardItem items[8]; +}; + +struct ExpeditionEntryHeader_Struct +{ +/*000*/ uint32 unknown000; +/*000*/ uint32 number_of_entries; +}; + +struct ExpeditionJoinPrompt_Struct +{ +/*000*/ uint32 clientid; +/*004*/ uint32 unknown004; +/*008*/ char player_name[64]; +/*072*/ char expedition_name[64]; +}; + +struct ExpeditionExpireWarning +{ +/*000*/ uint32 clientid; +/*004*/ uint32 unknown004; +/*008*/ uint32 minutes_remaining; +}; + +struct ExpeditionInfo_Struct +{ +/*000*/ uint32 clientid; +/*004*/ uint32 unknown004; +/*008*/ uint32 unknown008; +/*012*/ uint32 max_players; +/*016*/ char expedition_name[128]; +/*142*/ char leader_name[64]; +}; + +struct ExpeditionCompassEntry_Struct +{ +/*000*/ float unknown000; //seen *((uint32*)) = 1584791871 +/*004*/ uint32 enabled; //guess +/*008*/ uint32 unknown008; //seen 1019 +/*012*/ float y; +/*016*/ float x; +/*020*/ float z; +}; + +struct ExpeditionCompass_Struct +{ +/*000*/ uint32 clientid; +/*004*/ uint32 count; +/*008*/ ExpeditionCompassEntry_Struct entries[0]; +}; + +struct MaxCharacters_Struct +{ +/*000*/ uint32 max_chars; // Seen 4 on Silver Account (4 chars max) +/*004*/ uint32 unknown004; // Seen 0 +/*008*/ uint32 unknown008; // Seen 0 +}; + +// Used by MercenaryListEntry_Struct +struct MercenaryStance_Struct { +/*0000*/ uint32 StanceIndex; // Index of this stance (sometimes reverse reverse order - 3, 2, 1, 0 for 4 stances etc) +/*0004*/ uint32 Stance; // From dbstr_us.txt - 1^24^Passive^0, 2^24^Balanced^0, etc +}; +// Used by MercenaryMerchantList_Struct +struct MercenaryListEntry_Struct { +/*0000*/ uint32 MercID; // ID unique to each type of mercenary (probably a DB id) +/*0004*/ uint32 MercType; // From dbstr_us.txt - Apprentice (330000100), Journeyman (330000200), Master (330000300) +/*0008*/ uint32 MercSubType; // From dbstr_us.txt - 330020105^23^Race: Guktan
Type: Healer
Confidence: High
Proficiency: Apprentice, Tier V... +/*0012*/ uint32 PurchaseCost; // Purchase Cost (in gold) +/*0016*/ uint32 UpkeepCost; // Upkeep Cost (in gold) +/*0020*/ uint32 Status; // Required Account Status (Free = 0, Silver = 1, Gold = 2) at merchants - Seen 0 (suspended) or 1 (unsuspended) on hired mercs ? +/*0024*/ uint32 AltCurrencyCost; // Alternate Currency Purchase Cost? (all seen costs show N/A Bayle Mark) - Seen 0 +/*0028*/ uint32 AltCurrencyUpkeep; // Alternate Currency Upkeep Cost? (all seen costs show 1 Bayle Mark) - Seen 1 +/*0032*/ uint32 AltCurrencyType; // Alternate Currency Type? - 19^17^Bayle Mark^0 - Seen 19 +/*0036*/ uint8 MercUnk01; // Unknown (always see 0) +/*0037*/ int32 TimeLeft; // Unknown (always see -1 at merchant) - Seen 900000 (15 minutes in ms for newly hired merc) +/*0041*/ uint32 MerchantSlot; // Merchant Slot? Increments, but not always by 1 - May be for Merc Window Options (Seen 5, 36, 1 for active mercs)? +/*0045*/ uint32 MercUnk02; // Unknown (normally see 1, but sometimes 2 or 0) +/*0049*/ uint32 StanceCount; // Iterations of MercenaryStance_Struct - Normally 2 to 4 seen +/*0053*/ int32 MercUnk03; // Unknown (always 0 at merchant) - Seen on active merc: 93 a4 03 77, b8 ed 2f 26, 88 d5 8b c3, and 93 a4 ad 77 +/*0057*/ uint8 MercUnk04; // Seen 1 +/*0058*/ char MercName[1]; // Null Terminated Mercenary Name (00 at merchants) +/*0000*/ MercenaryStance_Struct Stances[1]; // Count Varies - From dbstr_us.txt - 1^24^Passive^0, 2^24^Balanced^0, etc +}; + +// Sent by the server when browsing the Mercenary Merchant +struct MercenaryMerchantList_Struct { +/*0000*/ uint32 MercTypeCount; // Number of Merc Types to follow +/*0004*/ uint32 MercTypes[1]; // Count varies, but hard set to 3 max for now - From dbstr_us.txt - Apprentice (330000100), Journeyman (330000200), Master (330000300) +/*0016*/ uint32 MercCount; // Number of MercenaryInfo_Struct to follow +/*0020*/ MercenaryListEntry_Struct Mercs[0]; // Data for individual mercenaries in the Merchant List +}; + +// OP_MercenaryDataRequest +// Right clicking merchant - shop request +struct MercenaryMerchantShopRequest_Struct { +/*0000*/ uint32 MercMerchantID; // Entity ID of the Mercenary Merchant +/*0004*/ +}; + +// Used by MercenaryDataUpdate_Struct +struct MercenaryData_Struct { +/*0000*/ uint32 MercID; // ID unique to each type of mercenary (probably a DB id) - (if 1, do not send MercenaryData_Struct - No merc hired) +/*0004*/ uint32 MercType; // From dbstr_us.txt - Apprentice (330000100), Journeyman (330000200), Master (330000300) +/*0008*/ uint32 MercSubType; // From dbstr_us.txt - 330020105^23^Race: Guktan
Type: Healer
Confidence: High
Proficiency: Apprentice, Tier V... +/*0012*/ uint32 PurchaseCost; // Purchase Cost (in gold) +/*0016*/ uint32 UpkeepCost; // Upkeep Cost (in gold) +/*0020*/ uint32 Status; // Required Account Status (Free = 0, Silver = 1, Gold = 2) at merchants - Seen 0 (suspended) or 1 (unsuspended) on hired mercs ? +/*0024*/ uint32 AltCurrencyCost; // Alternate Currency Purchase Cost? (all seen costs show N/A Bayle Mark) - Seen 0 +/*0028*/ uint32 AltCurrencyUpkeep; // Alternate Currency Upkeep Cost? (all seen costs show 1 Bayle Mark) - Seen 1 +/*0032*/ uint32 AltCurrencyType; // Alternate Currency Type? - 19^17^Bayle Mark^0 - Seen 19 +/*0036*/ uint8 MercUnk01; // Unknown (always see 0) +/*0037*/ int32 TimeLeft; // Unknown (always see -1 at merchant) - Seen 900000 (15 minutes in ms for newly hired merc) +/*0041*/ uint32 MerchantSlot; // Merchant Slot? Increments, but not always by 1 - May be for Merc Window Options (Seen 5, 36, 1 for active mercs)? +/*0045*/ uint32 MercUnk02; // Unknown (normally see 1, but sometimes 2 or 0) +/*0049*/ uint32 StanceCount; // Iterations of MercenaryStance_Struct - Normally 2 to 4 seen +/*0053*/ int32 MercUnk03; // Unknown (always 0 at merchant) - Seen on active merc: 93 a4 03 77, b8 ed 2f 26, 88 d5 8b c3, and 93 a4 ad 77 +/*0057*/ uint8 MercUnk04; // Seen 1 +/*0058*/ char MercName[1]; // Null Terminated Mercenary Name (00 at merchants) +/*0000*/ MercenaryStance_Struct Stances[1]; // Count Varies, but hard set to 2 for now - From dbstr_us.txt - 1^24^Passive^0, 2^24^Balanced^0, etc (1 to 9 as of April 2012) +/*0000*/ uint32 MercUnk05; // Seen 1 - Extra Merc Data field that differs from MercenaryListEntry_Struct +// MercUnk05 may be a field that is at the end of the packet only, even if multiple mercs are listed (haven't seen examples of multiple mercs owned at once) +}; + +// Should be named OP_MercenaryDataResponse, but the current opcode using that name should be renamed first +// Size varies if mercenary is hired or if browsing Mercenary Merchant +// This may also be the response for Client->Server 0x0327 (size 0) packet On Live as of April 2 2012 +struct MercenaryDataUpdate_Struct { +/*0000*/ int32 MercStatus; // Seen 0 with merc and -1 with no merc hired +/*0004*/ uint32 MercCount; // Seen 1 with 1 merc hired and 0 with no merc hired +/*0008*/ MercenaryData_Struct MercData[0]; // Data for individual mercenaries in the Merchant List +}; + +// Size 12 and sent on Zone-In if no mercenary is currently hired and when merc is dismissed +// (Same packet as MercAssign_Struct?) +struct NoMercenaryHired_Struct { +/*0000*/ int32 MercStatus; // Seen -1 with no merc hired +/*0004*/ uint32 MercCount; // Seen 0 with no merc hired +/*0008*/ uint32 MercID; // Seen 1 when no merc is hired - ID unique to each type of mercenary +/*0012*/ +}; + +// OP_MercenaryAssign (Same packet as NoMercenaryHired_Struct?) +// Not actually Merc related - This is actually a weapon equp packet +struct MercenaryAssign_Struct { +/*0000*/ uint32 MercEntityID; // Seen 0 (no merc spawned) or 615843841 and 22779137 +/*0004*/ uint32 MercUnk01; // +/*0008*/ uint32 MercUnk02; // +/*0012*/ +}; + +// OP_MercenaryTimer +// Sent on Zone-In, or after Dismissing, Suspending, or Unsuspending Mercs +struct MercenaryStatus_Struct { +/*0000*/ uint32 MercEntityID; // Seen 0 (no merc spawned) or 615843841 and 22779137 +/*0004*/ uint32 UpdateInterval; // Seen 900000 - Matches from 0x6537 packet (15 minutes in ms?) +/*0008*/ uint32 MercUnk01; // Seen 180000 - 3 minutes in milleseconds? Maybe next update interval? +/*0012*/ uint32 MercState; // Seen 5 (normal) or 1 (suspended) +/*0016*/ uint32 SuspendedTime; // Seen 0 (not suspended) or c9 c2 64 4f (suspended on Sat Mar 17 11:58:49 2012) - Unix Timestamp +/*0020*/ +}; + +// Sent from the client when using the Mercenary Window +struct MercenaryCommand_Struct { +/*0000*/ uint32 MercCommand; // Seen 0 (zone in with no merc or suspended), 1 (dismiss merc), 5 (normal state), 36 (zone in with merc) +/*0004*/ int32 Option; // Seen -1 (zone in with no merc), 0 (setting to passive stance), 1 (normal or setting to balanced stance) +/*0008*/ +}; + +// Requesting to suspend or unsuspend merc +struct SuspendMercenary_Struct { +/*0000*/ uint8 SuspendMerc; // Seen 30 (48) for suspending or unsuspending +/*0001*/ +}; + +// Response to suspend merc with timestamp +struct SuspendMercenaryResponse_Struct { +/*0000*/ uint32 SuspendTime; // Unix Timestamp - Seen a9 11 78 4f +/*0004*/ +}; + +// Sent by client when requesting to view Mercenary info or Hire a Mercenary +struct MercenaryMerchantRequest_Struct { +/*0000*/ uint32 MercID; // Seen 399 and 400 for merc ID +/*0004*/ uint32 MercUnk01; // Seen 1 +/*0008*/ uint32 MercMerchantID; // Entity ID for Mercenary Merchant +/*0012*/ uint32 MercUnk02; // Seen 65302016 (00 6e e4 03) - (probably actually individual uint8 fields), but seen as DWORD in Seeds client. +/*0016*/ +}; + +// Sent by Server in response to requesting to view Mercenary info or Hire a Mercenary +struct MercenaryMerchantResponse_Struct { +/*0000*/ uint32 ResponseType; +/*0004*/ +}; + + }; //end namespace structs +}; //end namespace RoF2 + +#endif /*RoF2_STRUCTS_H_*/ diff --git a/common/patches/rof_structs.h b/common/patches/rof_structs.h index 0a62a90bf..075e6633b 100644 --- a/common/patches/rof_structs.h +++ b/common/patches/rof_structs.h @@ -4169,7 +4169,8 @@ struct Arrow_Struct { /*070*/ uint8 unknown070; /*071*/ uint8 item_type; /*072*/ uint8 skill; -/*073*/ char model_name[43]; +/*073*/ uint8 unknown073[16]; +/*089*/ char model_name[27]; /*116*/ }; diff --git a/common/patches/underfoot.cpp b/common/patches/underfoot.cpp index adc4ed6e3..c9d726a25 100644 --- a/common/patches/underfoot.cpp +++ b/common/patches/underfoot.cpp @@ -3664,6 +3664,12 @@ namespace Underfoot ss.write((const char*)&null_term, sizeof(uint8)); ornaIcon = aug_weap->Icon; } + else if (inst->GetOrnamentationIDFile() && inst->GetOrnamentationIcon()) { + char tmp[30]; memset(tmp, 0x0, 30); sprintf(tmp, "IT%d", inst->GetOrnamentationIDFile()); + ss.write(tmp, strlen(tmp)); + ss.write((const char*)&null_term, sizeof(uint8)); + ornaIcon = inst->GetOrnamentationIcon(); + } else { ss.write((const char*)&null_term, sizeof(uint8)); //no idfile } diff --git a/common/random.h b/common/random.h new file mode 100644 index 000000000..ef2b3ef73 --- /dev/null +++ b/common/random.h @@ -0,0 +1,84 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2014 EQEMu Development Team (http://eqemulator.net) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY except by those people which sell it, which + are required to give you total support for your newly bought product; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef __random_h__ +#define __random_h__ + +#include +#include + +/* This uses mt19937 seeded with the std::random_device + * The idea is to have this be included as a member of another class + * so mocking out for testing is easier + * If you need to reseed random.Reseed() + * Eventually this should be derived from an abstract base class + */ + +namespace EQEmu { + class Random { + public: + // AKA old MakeRandomInt + const int Int(int low, int high) + { + if (low > high) + std::swap(low, high); + return std::uniform_int_distribution(low, high)(m_gen); // [low, high] + } + + // AKA old MakeRandomFloat + const double Real(double low, double high) + { + if (low > high) + std::swap(low, high); + return std::uniform_real_distribution(low, high)(m_gen); // [low, high) + } + + // example Roll(50) would have a 50% success rate + // Roll(100) 100%, etc + // valid values 0-100 (well, higher works too but ...) + const bool Roll(const int required) + { + return Int(0, 99) < required; + } + + // valid values 0.0 - 1.0 + const bool Roll(const double required) + { + return Real(0.0, 1.0) <= required; + } + + void Reseed() + { + // We could do the seed_seq thing here too if we need better seeding + // but that is mostly overkill for us, so just seed once + std::random_device rd; + m_gen.seed(rd()); + } + + Random() + { + Reseed(); + } + + private: + std::mt19937 m_gen; + }; +} + +#endif /* !__random_h__ */ + diff --git a/common/ruletypes.h b/common/ruletypes.h index 052571d82..8ff077809 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -419,6 +419,7 @@ RULE_INT ( Combat, ArcheryBonusChance, 50) RULE_INT ( Combat, BerserkerFrenzyStart, 35) RULE_INT ( Combat, BerserkerFrenzyEnd, 45) RULE_BOOL ( Combat, OneProcPerWeapon, true) //If enabled, One proc per weapon per round +RULE_BOOL ( Combat, ProjectileDmgOnImpact, true) //If enabled, projectiles (ie arrows) will hit on impact, instead of instantly. RULE_CATEGORY_END() RULE_CATEGORY( NPC ) @@ -586,6 +587,8 @@ RULE_CATEGORY( Inventory ) RULE_BOOL ( Inventory, EnforceAugmentRestriction, true) // Forces augment slot restrictions RULE_BOOL ( Inventory, EnforceAugmentUsability, true) // Forces augmented item usability RULE_BOOL ( Inventory, EnforceAugmentWear, true) // Forces augment wear slot validation +RULE_BOOL ( Inventory, DeleteTransformationMold, true) //False if you want mold to last forever +RULE_BOOL ( Inventory, AllowAnyWeaponTransformation, false) //Weapons can use any weapon transformation RULE_CATEGORY_END() RULE_CATEGORY( Client ) diff --git a/common/shareddb.cpp b/common/shareddb.cpp index d543db87f..05c7b6962 100644 --- a/common/shareddb.cpp +++ b/common/shareddb.cpp @@ -199,14 +199,14 @@ bool SharedDatabase::UpdateInventorySlot(uint32 char_id, const ItemInst* inst, i // Update/Insert item std::string query = StringFormat("REPLACE INTO inventory " "(charid, slotid, itemid, charges, instnodrop, custom_data, color, " - "augslot1, augslot2, augslot3, augslot4, augslot5) " + "augslot1, augslot2, augslot3, augslot4, augslot5, ornamenticon, ornamentidfile) " "VALUES( %lu, %lu, %lu, %lu, %lu, '%s', %lu, " - "%lu, %lu, %lu, %lu, %lu)", + "%lu, %lu, %lu, %lu, %lu, %lu, %lu)", (unsigned long)char_id, (unsigned long)slot_id, (unsigned long)inst->GetItem()->ID, (unsigned long)charges, (unsigned long)(inst->IsInstNoDrop()? 1: 0), inst->GetCustomDataString().c_str(), (unsigned long)inst->GetColor(), (unsigned long)augslot[0], (unsigned long)augslot[1], (unsigned long)augslot[2], - (unsigned long)augslot[3],(unsigned long)augslot[4]); + (unsigned long)augslot[3],(unsigned long)augslot[4], (unsigned long)inst->GetOrnamentationIcon(), (unsigned long)inst->GetOrnamentationIDFile()); auto results = QueryDatabase(query); // Save bag contents, if slot supports bag contents @@ -488,7 +488,7 @@ bool SharedDatabase::GetSharedBank(uint32 id, Inventory* inv, bool is_charid) { bool SharedDatabase::GetInventory(uint32 char_id, Inventory* inv) { // Retrieve character inventory std::string query = StringFormat("SELECT slotid, itemid, charges, color, augslot1, " - "augslot2, augslot3, augslot4, augslot5, instnodrop, custom_data " + "augslot2, augslot3, augslot4, augslot5, instnodrop, custom_data, ornamenticon, ornamentidfile " "FROM inventory WHERE charid = %i ORDER BY slotid", char_id); auto results = QueryDatabase(query); if (!results.Success()) { @@ -513,6 +513,9 @@ bool SharedDatabase::GetInventory(uint32 char_id, Inventory* inv) { bool instnodrop = (row[9] && (uint16)atoi(row[9]))? true: false; + uint32 ornament_icon = (uint32)atoul(row[11]); + uint32 ornament_idfile = (uint32)atoul(row[12]); + const Item_Struct* item = GetItem(item_id); if (!item) { @@ -549,6 +552,11 @@ bool SharedDatabase::GetInventory(uint32 char_id, Inventory* inv) { value.push_back(v); } } + if (ornament_icon > 0) + inst->SetOrnamentIcon(ornament_icon); + + if (ornament_idfile > 0) + inst->SetOrnamentationIDFile(ornament_idfile); if (instnodrop || (((slot_id >= EmuConstants::EQUIPMENT_BEGIN && slot_id <= EmuConstants::EQUIPMENT_END) || slot_id == MainPowerSource) && inst->GetItem()->Attuneable)) inst->SetInstNoDrop(true); @@ -591,7 +599,7 @@ bool SharedDatabase::GetInventory(uint32 char_id, Inventory* inv) { bool SharedDatabase::GetInventory(uint32 account_id, char* name, Inventory* inv) { // Retrieve character inventory std::string query = StringFormat("SELECT slotid, itemid, charges, color, augslot1, " - "augslot2, augslot3, augslot4, augslot5, instnodrop, custom_data " + "augslot2, augslot3, augslot4, augslot5, instnodrop, custom_data, ornamenticon, ornamentidfile " "FROM inventory INNER JOIN character_data ch " "ON ch.id = charid WHERE ch.name = '%s' AND ch.account_id = %i ORDER BY slotid", name, account_id); @@ -617,6 +625,9 @@ bool SharedDatabase::GetInventory(uint32 account_id, char* name, Inventory* inv) aug[4] = (uint32)atoi(row[8]); bool instnodrop = (row[9] && (uint16)atoi(row[9])) ? true : false; + uint32 ornament_icon = (uint32)atoul(row[11]); + uint32 ornament_idfile = (uint32)atoul(row[12]); + const Item_Struct* item = GetItem(item_id); int16 put_slot_id = INVALID_INDEX; if(!item) @@ -651,6 +662,12 @@ bool SharedDatabase::GetInventory(uint32 account_id, char* name, Inventory* inv) } } + + if (ornament_icon > 0) + inst->SetOrnamentIcon(ornament_icon); + + if (ornament_idfile > 0) + inst->SetOrnamentationIDFile(ornament_idfile); if (color > 0) inst->SetColor(color); diff --git a/common/version.h b/common/version.h index caff584d8..43b4f8b10 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 9057 +#define CURRENT_BINARY_DATABASE_VERSION 9059 #define COMPILE_DATE __DATE__ #define COMPILE_TIME __TIME__ #ifndef WIN32 diff --git a/loginserver/client.cpp b/loginserver/client.cpp index 9883c059d..bc0a86fa8 100644 --- a/loginserver/client.cpp +++ b/loginserver/client.cpp @@ -389,7 +389,7 @@ void Client::GenerateKey() '6', '7', '8', '9' }; - key.append((const char*)&key_selection[MakeRandomInt(0, 35)], 1); + key.append((const char*)&key_selection[random.Int(0, 35)], 1); count++; } } diff --git a/loginserver/client.h b/loginserver/client.h index 3248aefb5..c1b44dc6f 100644 --- a/loginserver/client.h +++ b/loginserver/client.h @@ -22,6 +22,7 @@ #include "../common/opcodemgr.h" #include "../common/eq_stream_type.h" #include "../common/eq_stream_factory.h" +#include "../common/random.h" #ifndef WIN32 #include "eq_crypto_api.h" #endif @@ -129,6 +130,8 @@ public: * Gets the connection for this client. */ EQStream *GetConnection() { return connection; } + + EQEmu::Random random; private: EQStream *connection; ClientVersion version; diff --git a/luabind/CMakeLists.txt b/luabind/CMakeLists.txt index 9f3288d4a..5efdf562f 100644 --- a/luabind/CMakeLists.txt +++ b/luabind/CMakeLists.txt @@ -21,7 +21,7 @@ SET(lb_sources ) SET(lb_headers - + ) ADD_LIBRARY(luabind ${lb_sources} ${lb_headers}) @@ -29,6 +29,11 @@ ADD_LIBRARY(luabind ${lb_sources} ${lb_headers}) IF(UNIX) ADD_DEFINITIONS(-fPIC) + set_source_files_properties(${lb_sources} PROPERTY COMPILE_FLAGS -Wno-deprecated-declarations) ENDIF(UNIX) +IF(MSVC) + set_source_files_properties(${lb_sources} PROPERTY COMPILE_FLAGS " /W0 " ) +ENDIF(MSVC) + SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) diff --git a/utils/patches/patch_RoF.conf b/utils/patches/patch_RoF.conf index 027eac161..16973c202 100644 --- a/utils/patches/patch_RoF.conf +++ b/utils/patches/patch_RoF.conf @@ -94,6 +94,7 @@ OP_ClearBlockedBuffs=0x5d3c OP_WorldObjectsSent=0x7fa8 OP_SendExpZonein=0x25ab OP_SendAATable=0x7791 +OP_ShroudClearAA=0x422e OP_RespondAA=0x379d OP_UpdateAA=0x504f OP_SendAAStats=0x3d1c @@ -367,7 +368,7 @@ OP_DzLeaderStatus=0x4021 OP_DzExpeditionEndsWarning=0x32eb OP_DzMemberList=0x348f OP_DzCompass=0x0e01 # Was 0x4f09 -OP_DzChooseZone=0x0000 # Maybe 0x29d6 +OP_DzChooseZone=0x6e5e # Maybe 0x29d6 # New Opcodes OP_SpawnPositionUpdate=0x0000 # Actually OP_MobUpdate ? diff --git a/utils/patches/patch_RoF2.conf b/utils/patches/patch_RoF2.conf new file mode 100644 index 000000000..8c15c59c6 --- /dev/null +++ b/utils/patches/patch_RoF2.conf @@ -0,0 +1,660 @@ +# ShowEQ Import Notes: +# ZERO THE FILE first +# perl -pi -e 's/0x[0-9a-fA-F]{4}/0x0000/g' opcodes.conf +# Unknown Mapping: +# OP_Action2 -> OP_Damage +# OP_EnvDamage -> OP_Damage ---> might have been a one time mistake +# Name Differences: +# OP_CancelInvite -> OP_GroupCancelInvite +# OP_GMFind -> OP_FindPersonRequest +# OP_CommonMessage -> OP_ChannelMessage + +OP_Unknown=0x0000 +OP_ExploreUnknown=0x0000 # used for unknown explorer + +# world packets +# Required to reach Char Select: +OP_SendLoginInfo=0x7a09 +OP_ApproveWorld=0x7499 +OP_LogServer=0x7ceb +OP_SendCharInfo=0x00d2 +OP_ExpansionInfo=0x590d +OP_GuildsList=0x0000 +OP_EnterWorld=0x578f +OP_PostEnterWorld=0x6259 +OP_World_Client_CRC1=0x12cc +OP_World_Client_CRC2=0x0f13 +OP_SendSpellChecksum=0x0000 +OP_SendSkillCapsChecksum=0x0000 + +# Character Select Related: +OP_SendMaxCharacters=0x5475 +OP_SendMembership=0x7acc +OP_SendMembershipDetails=0x057b +OP_CharacterCreateRequest=0x6773 +OP_CharacterCreate=0x6bbf +OP_DeleteCharacter=0x1808 +OP_RandomNameGenerator=0x5954 +OP_ApproveName=0x56a2 +OP_MOTD=0x0c22 +OP_SetChatServer=0x1bc5 +OP_SetChatServer2=0x7eec +OP_ZoneServerInfo=0x4c44 +OP_WorldComplete=0x4493 +OP_WorldUnknown001=0x2301 +OP_FloatListThing=0x46c6 + +# Reasons for Disconnect: +OP_ZoneUnavail=0x4cb4 +OP_WorldClientReady=0x23c1 +OP_CharacterStillInZone=0x0000 +OP_WorldChecksumFailure=0x0000 +OP_WorldLoginFailed=0x0000 +OP_WorldLogout=0x0000 +OP_WorldLevelTooHigh=0x0000 +OP_CharInacessable=0x0000 +OP_UserCompInfo=0x0000 +OP_SendExeChecksum=0x0000 +OP_SendBaseDataChecksum=0x0000 + +# Zone in opcodes +OP_AckPacket=0x471d +OP_ZoneEntry=0x5089 +OP_ReqNewZone=0x7887 +OP_NewZone=0x1795 +OP_ZoneSpawns=0x5237 +OP_PlayerProfile=0x6506 +OP_TimeOfDay=0x5070 +OP_LevelUpdate=0x1eec +OP_Stamina=0x2a79 +OP_RequestClientZoneChange=0x3fcf +OP_ZoneChange=0x2d18 +OP_LockoutTimerInfo=0x0000 +OP_ZoneServerReady=0x0000 +OP_ZoneInUnknown=0x0000 +OP_LogoutReply=0x0000 +OP_PreLogoutReply=0x0000 + +# Required to fully log in +OP_SpawnAppearance=0x0971 +OP_ChangeSize=0x4707 +OP_TributeUpdate=0x5961 +OP_TributeTimer=0x073d +OP_SendTributes=0x729b +OP_SendGuildTributes=0x1877 +OP_TributeInfo=0x4254 +OP_Weather=0x661e +OP_ReqClientSpawn=0x35fa +OP_SpawnDoor=0x7291 +OP_GroundSpawn=0x6fca +OP_SendZonepoints=0x69a4 +OP_BlockedBuffs=0x3033 +OP_RemoveBlockedBuffs=0x0de7 +OP_ClearBlockedBuffs=0x34cb +OP_WorldObjectsSent=0x5ae2 +OP_SendExpZonein=0x5f8e +OP_SendAATable=0x66b5 +OP_RespondAA=0x7a27 +OP_UpdateAA=0x66f0 +OP_SendAAStats=0x43c8 +OP_AAExpUpdate=0x7d14 +OP_ExpUpdate=0x20ed +OP_HPUpdate=0x2828 +OP_ManaChange=0x5467 +OP_TGB=0x0876 +OP_SpecialMesg=0x083 +OP_GuildMemberList=0x12a6 +OP_GuildMOTD=0x3e13 +OP_CharInventory=0x5ca6 +OP_WearChange=0x7994 +OP_ClientUpdate=0x7dfc +OP_ClientReady=0x345d +OP_SetServerFilter=0x444d + +# Guild Opcodes - Disabled until crashes are resolved in RoF +OP_GetGuildMOTD=0x36e0 +OP_GetGuildMOTDReply=0x4f1f +OP_GuildMemberUpdate=0x69b9 +OP_GuildInvite=0x7099 +OP_GuildRemove=0x1444 +OP_GuildPeace=0x67e3 +OP_SetGuildMOTD=0x0b0b +OP_GuildList=0x6279 +OP_GuildWar=0x1ffb +OP_GuildLeader=0x7e09 +OP_GuildDelete=0x3708 +OP_GuildInviteAccept=0x7053 +OP_GuildDemote=0x2d4e +OP_GuildPromote=0x0000 +OP_GuildPublicNote=0x5053 +OP_GuildManageBanker=0x748f +OP_GuildBank=0x5134 +OP_SetGuildRank=0x0b9c +OP_GuildUpdateURLAndChannel=0x2958 +OP_GuildStatus=0x7326 +OP_GuildCreate=0x76d9 +OP_GuildMemberLevelUpdate=0x0000 # Unused? +OP_ZoneGuildList=0x0000 # Unused? +OP_GetGuildsList=0x0000 # Unused? +OP_LFGuild=0x0000 +OP_GuildManageRemove=0x0000 +OP_GuildManageAdd=0x0000 +OP_GuildManageStatus=0x0000 + +# GM/Guide Opcodes +OP_GMServers=0x08c1 +OP_GMBecomeNPC=0x3ae1 +OP_GMZoneRequest=0x62ac +OP_GMZoneRequest2=0x7e1a +OP_GMGoto=0x7d8e +OP_GMSearchCorpse=0x357c +OP_GMHideMe=0x79c5 +OP_GMDelCorpse=0x607e +OP_GMApproval=0x6db5 +OP_GMToggle=0x2097 +OP_GMSummon=0x486f +OP_GMEmoteZone=0x1cfd +OP_GMEmoteWorld=0x458e +OP_GMFind=0x4a8f +OP_GMKick=0x26a7 +OP_GMKill=0x51d3 +OP_GMNameChange=0x035f +OP_GMLastName=0x46ce + +# Misc Opcodes +OP_InspectRequest=0x57bc +OP_InspectAnswer=0x71ac +OP_InspectMessageUpdate=0x4d25 +OP_BeginCast=0x318f +OP_ColoredText=0x43af +OP_ConsentResponse=0x384a +OP_MemorizeSpell=0x217c +OP_SwapSpell=0x0efa +OP_CastSpell=0x1287 +OP_Consider=0x742b +OP_FormattedMessage=0x1024 +OP_SimpleMessage=0x213f +OP_Buff=0x659c +OP_Illusion=0x312a +OP_MoneyOnCorpse=0x5f44 +OP_RandomReply=0x106b +OP_DenyResponse=0x2382 +OP_SkillUpdate=0x04c +OP_GMTrainSkillConfirm=0x4b64 +OP_RandomReq=0x7b10 +OP_Death=0x6517 +OP_GMTraining=0x1966 +OP_GMEndTraining=0x4d6b +OP_GMTrainSkill=0x2a85 +OP_Animation=0x7177 +OP_Begging=0x6703 +OP_Consent=0x1fd1 +OP_ConsentDeny=0x7a45 +OP_AutoFire=0x241e +OP_PetCommands=0x0159 +OP_DeleteSpell=0x52e5 +OP_Surname=0x0423 +OP_ClearSurname=0x3fb0 +OP_FaceChange=0x5578 +OP_SenseHeading=0x260a +OP_Action=0x744c +OP_ConsiderCorpse=0x5204 +OP_HideCorpse=0x49e1 +OP_CorpseDrag=0x0904 +OP_CorpseDrop=0x7037 +OP_Bug=0x73f4 +OP_Feedback=0x5602 +OP_Report=0x1414 +OP_Damage=0x6f15 +OP_ChannelMessage=0x2b2d +OP_Assist=0x4478 +OP_AssistGroup=0x27f8 +OP_MoveCoin=0x0bcf +OP_ZonePlayerToBind=0x0ecb +OP_KeyRing=0x6857 +OP_WhoAllRequest=0x674b +OP_WhoAllResponse=0x51b8 +OP_FriendsWho=0x3956 +OP_ConfirmDelete=0x43a3 +OP_Logout=0x4ac6 +OP_Rewind=0x1745 +OP_TargetCommand=0x58e2 +OP_Hide=0x67fe +OP_Jump=0x31f4 +OP_Camp=0x28ec +OP_Emote=0x373b +OP_SetRunMode=0x009f +OP_BankerChange=0x791e +OP_TargetMouse=0x075d +OP_MobHealth=0x37b1 +OP_InitialMobHealth=0x0000 # Unused? +OP_TargetHoTT=0x0272 +OP_XTargetResponse=0x672f +OP_XTargetRequest=0x45be +OP_XTargetAutoAddHaters=0x792c +OP_TargetBuffs=0x4f4b +OP_BuffCreate=0x3377 +OP_BuffRemoveRequest=0x64f2 +OP_DeleteSpawn=0x7280 +OP_AutoAttack=0x109d +OP_AutoAttack2=0x3526 +OP_Consume=0x4b70 +OP_MoveItem=0x32ee +OP_DeleteItem=0x18ad +OP_DeleteCharge=0x01b8 +OP_ItemPacket=0x368e +OP_ItemLinkResponse=0x70c0 +OP_ItemLinkClick=0x4cef +OP_ItemPreview=0x6b5c +OP_NewSpawn=0x6097 +OP_Track=0x17e5 +OP_TrackTarget=0x0029 +OP_TrackUnknown=0x4577 +OP_ClickDoor=0x3a8f +OP_MoveDoor=0x08e8 +OP_RemoveAllDoors=0x700c +OP_EnvDamage=0x51fd +OP_BoardBoat=0x4211 +OP_Forage=0x5306 +OP_LeaveBoat=0x7617 +OP_ControlBoat=0x0ae7 +OP_SafeFallSuccess=0x2219 +OP_RezzComplete=0x760d +OP_RezzRequest=0x3c21 +OP_RezzAnswer=0x701c +OP_Shielding=0x48c1 +OP_RequestDuel=0x3af1 +OP_MobRename=0x2c57 +OP_AugmentItem=0x661b +OP_WeaponEquip1=0x34a7 +OP_WeaponEquip2=0x559a +OP_WeaponUnequip2=0x2d25 +OP_ApplyPoison=0x31e6 +OP_Save=0x4a39 +OP_TestBuff=0x7cb8 +OP_CustomTitles=0x100e +OP_Split=0x3a54 +OP_YellForHelp=0x4e56 +OP_LoadSpellSet=0x261d +OP_Bandolier=0x7677 +OP_PotionBelt=0x1a3e +OP_DuelResponse=0x6a46 +OP_DuelResponse2=0x68d3 +OP_SaveOnZoneReq=0x600d +OP_ReadBook=0x72df +OP_Dye=0x23b9 +OP_InterruptCast=0x048c +OP_AAAction=0x424e +OP_LeadershipExpToggle=0x6c55 +OP_LeadershipExpUpdate=0x2797 +OP_PurchaseLeadershipAA=0x0026 +OP_UpdateLeadershipAA=0x026 +OP_MarkNPC=0x5a58 +OP_MarkRaidNPC=0x74bd #unimplemented +OP_ClearNPCMarks=0x2003 +OP_ClearRaidNPCMarks=0x20d3 #unimplemented +OP_DelegateAbility=0x76b8 +OP_SetGroupTarget=0x2814 +OP_Charm=0x5d92 +OP_Stun=0x36a4 +OP_SendFindableNPCs=0x7e62 +OP_FindPersonRequest=0x5cea +OP_FindPersonReply=0x7e58 +OP_Sound=0x1a30 +OP_PetBuffWindow=0x5882 +OP_LevelAppearance=0x3bc9 +OP_Translocate=0x6580 +OP_Sacrifice=0x1821 +OP_PopupResponse=0x08a6 +OP_OnLevelMessage=0x4d6e +OP_AugmentInfo=0x0afb +OP_Petition=0x3de3 +OP_SomeItemPacketMaybe=0x747c +OP_PVPStats=0x4b15 +OP_PVPLeaderBoardRequest=0x04aa +OP_PVPLeaderBoardReply=0x071f +OP_PVPLeaderBoardDetailsRequest=0x3707 +OP_PVPLeaderBoardDetailsReply=0x25b7 +OP_RestState=0x000f +OP_RespawnWindow=0x28bc +OP_LDoNButton=0x5327 +OP_SetStartCity=0x6326 +OP_VoiceMacroIn=0x17fd +OP_VoiceMacroOut=0x409a +OP_ItemViewUnknown=0x465b +OP_VetRewardsAvaliable=0x590e +OP_VetClaimRequest=0x1126 +OP_VetClaimReply=0x16d4 +OP_DisciplineUpdate=0x759e +OP_DisciplineTimer=0x6989 +OP_BecomeCorpse=0x0000 # Unused? +OP_Action2=0x0000 # Unused? +OP_MobUpdate=0x2c84 +OP_NPCMoveUpdate=0x5892 +OP_CameraEffect=0x127f +OP_SpellEffect=0x5936 +OP_RemoveNimbusEffect=0x7b1e +OP_AltCurrency=0x62ab +OP_AltCurrencyMerchantRequest=0x61cb +OP_AltCurrencyMerchantReply=0x5409 +OP_AltCurrencyPurchase=0x0165 +OP_AltCurrencySell=0x74ec +OP_AltCurrencySellSelection=0x3788 +OP_AltCurrencyReclaim=0x3899 +OP_CrystalCountUpdate=0x467f +OP_CrystalCreate=0x7aee +OP_CrystalReclaim=0x2439 +OP_Untargetable=0x053c +OP_IncreaseStats=0x70a3 +OP_Weblink=0x6f4b +OP_OpenContainer=0x0000 +OP_Marquee=0x0000 +OP_ItemRecastDelay=0x15a9 +#OP_OpenInventory=0x0000 # Likely does not exist in RoF -U + +OP_DzQuit=0x205f +OP_DzListTimers=0x0398 +OP_DzAddPlayer=0x59ca +OP_DzRemovePlayer=0x4701 +OP_DzSwapPlayer=0x1abc +OP_DzMakeLeader=0x405b +OP_DzPlayerList=0x543d +OP_DzJoinExpeditionConfirm=0x14c6 +OP_DzJoinExpeditionReply=0x7f4b +OP_DzExpeditionInfo=0x4f7e +OP_DzExpeditionList=0x9119 +OP_DzMemberStatus=0xb2e3 +OP_DzLeaderStatus=0x32f0 +OP_DzExpeditionEndsWarning=0x7e94 +OP_DzMemberList=0x3de9 +OP_DzCompass=0x3e0e +OP_DzChooseZone=0x0000 + +# New Opcodes +OP_SpawnPositionUpdate=0x0000 # Actually OP_MobUpdate ? +OP_ManaUpdate=0x3791 +OP_EnduranceUpdate=0x5f42 +OP_MobManaUpdate=0x2404 +OP_MobEnduranceUpdate=0x1c81 + +# Mercenary Opcodes +OP_MercenaryDataUpdateRequest=0x7b89 +OP_MercenaryDataUpdate=0x61a4 +OP_MercenaryDataRequest=0x11c1 +OP_MercenaryDataResponse=0x72ce +OP_MercenaryHire=0x7169 +OP_MercenaryDismiss=0x6e83 +OP_MercenaryTimerRequest=0x31e4 +OP_MercenaryTimer=0x0763 +OP_MercenaryUnknown1=0x5d26 +OP_MercenaryCommand=0x27f2 +OP_MercenarySuspendRequest=0x4407 +OP_MercenarySuspendResponse=0x6f03 +OP_MercenaryUnsuspendResponse=0x27a0 + +# Looting +OP_LootRequest=0x0adf +OP_EndLootRequest=0x30f7 +OP_LootItem=0x4dc9 +OP_LootComplete=0x55c4 + +# bazaar trader stuff: +OP_BazaarSearch=0x39d6 +OP_TraderDelItem=0x0000 +OP_BecomeTrader=0x61b3 +OP_TraderShop=0x31df +OP_Trader=0x4ef5 +OP_TraderBuy=0x0000 +OP_Barter=0x243a +OP_ShopItem=0x0000 +OP_BazaarInspect=0x0000 +OP_Bazaar=0x0000 +OP_TraderItemUpdate=0x0000 + +# pc/npc trading +OP_TradeRequest=0x77b5 +OP_TradeAcceptClick=0x69e2 +OP_TradeRequestAck=0x14bf +OP_TradeCoins=0x4206 +OP_FinishTrade=0x3993 +OP_CancelTrade=0x354c +OP_TradeMoneyUpdate=0x68c2 +OP_MoneyUpdate=0x640c +OP_TradeBusy=0x5505 + +# Sent after canceling trade or after closing tradeskill object +OP_FinishWindow=0x7349 +OP_FinishWindow2=0x40ef + +# Sent on Live for what seems to be item existance verification +# Ex. Before Right Click Effect happens from items +OP_ItemVerifyRequest=0x189c +OP_ItemVerifyReply=0x097b + +# merchant stuff +OP_ShopPlayerSell=0x0000 +OP_ShopRequest=0x4fed +OP_ShopEnd=0x30a8 +OP_ShopEndConfirm=0x3196 +OP_ShopPlayerBuy=0x0ddd +OP_ShopDelItem=0x724f + +# tradeskill stuff: +OP_ClickObject=0x4aa1 +OP_ClickObjectAction=0x0c1e +OP_ClearObject=0x7a11 +OP_RecipeDetails=0x40d7 +OP_RecipesFavorite=0x71b1 +OP_RecipesSearch=0x1db6 +OP_RecipeReply=0x6e02 +OP_RecipeAutoCombine=0x6261 +OP_TradeSkillCombine=0x579a + +# Tribute Packets: +OP_OpenGuildTributeMaster=0x378d +OP_OpenTributeMaster=0x7666 +OP_SelectTribute=0x79fc +OP_TributeItem=0x4f3e +OP_TributeMoney=0x58fb +OP_TributeToggle=0x241d +OP_TributePointUpdate=0x5300 +OP_TributeNPC=0x0000 +OP_GuildTributeInfo=0x0000 +OP_OpenTributeReply=0x0000 +OP_GuildTributeStatus=0x0000 + +# Adventure packets: +OP_LeaveAdventure=0x5d18 +OP_AdventureFinish=0x400f +OP_AdventureInfoRequest=0x3cb0 +OP_AdventureInfo=0x4c54 +OP_AdventureRequest=0x2c6c +OP_AdventureDetails=0x5648 +OP_AdventureData=0x7171 +OP_AdventureUpdate=0x1b01 +OP_AdventureMerchantRequest=0x6922 +OP_AdventureMerchantResponse=0x3e47 +OP_AdventureMerchantPurchase=0x5b72 +OP_AdventureMerchantSell=0x2f9b +OP_AdventurePointsUpdate=0x65c3 +OP_AdventureStatsRequest=0x5a62 +OP_AdventureStatsReply=0x2370 +OP_AdventureLeaderboardRequest=0x7093 +OP_AdventureLeaderboardReply=0x7f79 + +# Group Opcodes +OP_GroupDisband=0x4c10 +OP_GroupInvite=0x6110 +OP_GroupFollow=0x1649 +OP_GroupUpdate=0x3abb +OP_GroupUpdateB=0x6194 +OP_GroupCancelInvite=0x0000 +OP_GroupAcknowledge=0x7323 +OP_GroupDelete=0x0f6c +OP_CancelInvite=0x2a50 +OP_GroupFollow2=0x2060 +OP_GroupInvite2=0x32c2 +OP_GroupDisbandYou=0x1ae5 +OP_GroupDisbandOther=0x74da +OP_GroupLeaderChange=0x21b4 +OP_GroupRoles=0x70e2 +OP_GroupMakeLeader=0x4229 +OP_DoGroupLeadershipAbility=0x1fb5 +OP_GroupLeadershipAAUpdate=0x02cf +OP_GroupMentor=0x3342 +OP_InspectBuffs=0x486c + +# LFG/LFP Opcodes +OP_LFGCommand=0x6060 +OP_LFGGetMatchesRequest=0x0340 +OP_LFGGetMatchesResponse=0x5048 +OP_LFPGetMatchesRequest=0x4d7d +OP_LFPGetMatchesResponse=0x22c6 +OP_LFPCommand=0x49a9 +OP_LFGAppearance=0x0000 +OP_LFGResponse=0x0000 + +# Raid Opcodes +OP_RaidInvite=0x55ac +OP_RaidUpdate=0x3973 +OP_RaidJoin=0x0000 + +# Button-push commands +OP_Taunt=0x2703 +OP_CombatAbility=0x3eba +OP_SenseTraps=0x02af +OP_PickPocket=0x39e8 +OP_DisarmTraps=0x78bf +OP_Disarm=0x5ec8 +OP_Sneak=0x5d55 +OP_Fishing=0x1e2a +OP_InstillDoubt=0x640e +OP_FeignDeath=0x52fa +OP_Mend=0x0ecf +OP_Bind_Wound=0x0386 +OP_LDoNOpen=0x3d5c + +# Task packets +OP_TaskDescription=0x3714 +OP_TaskActivity=0x08d3 +OP_CompletedTasks=0x4eba +OP_TaskActivityComplete=0x5e19 +OP_AcceptNewTask=0x0a23 +OP_CancelTask=0x39f0 +OP_TaskMemberList=0x5727 +OP_OpenNewTasksWindow=0x48a2 +OP_AvaliableTask=0x36e8 +OP_TaskHistoryRequest=0x5f1c +OP_TaskHistoryReply=0x3d05 +OP_DeclineAllTasks=0x0000 + +# Title opcodes +OP_NewTitlesAvailable=0x0d32 +OP_RequestTitles=0x6344 +OP_SendTitleList=0x2d08 +OP_SetTitle=0x6527 +OP_SetTitleReply=0x4c21 + +# mail opcodes +OP_Command=0x0000 +OP_MailboxHeader=0x0000 +OP_MailHeader=0x0000 +OP_MailBody=0x0000 +OP_NewMail=0x0000 +OP_SentConfirm=0x0000 + +########### Below this point should not be needed ########### + +# This section are all unknown in Titanium +OP_ForceFindPerson=0x0000 +OP_LocInfo=0x0000 +OP_ReloadUI=0x0000 +OP_ItemName=0x0000 +OP_ItemLinkText=0x0000 +OP_MultiLineMsg=0x0000 +OP_MendHPUpdate=0x0000 +OP_TargetReject=0x0000 +OP_SafePoint=0x0000 +OP_ApproveZone=0x0000 +OP_ZoneComplete=0x0000 +OP_ClientError=0x0000 +OP_DumpName=0x0000 +OP_Heartbeat=0x0000 +OP_CrashDump=0x0000 +OP_LoginComplete=0x0000 + +# discovered opcodes not yet used: +OP_PickLockSuccess=0x0000 +OP_PlayMP3=0x0000 +OP_ReclaimCrystals=0x0000 +OP_DynamicWall=0x0000 +OP_OpenDiscordMerchant=0x0000 +OP_DiscordMerchantInventory=0x0000 +OP_GiveMoney=0x0000 +OP_RequestKnowledgeBase=0x0000 +OP_KnowledgeBase=0x0000 +OP_SlashAdventure=0x0000 # /adventure +OP_BecomePVPPrompt=0x0000 +OP_MoveLogRequest=0x0000 # gone I think +OP_MoveLogDisregard=0x0000 # gone I think + +# named unknowns, to make looking for real unknown easier +OP_AnnoyingZoneUnknown=0x0000 +OP_Some6ByteHPUpdate=0x0000 seems to happen when you target group members +OP_QueryResponseThing=0x0000 + + +# realityincarnate: these are just here to stop annoying several thousand byte packet dumps +#OP_LoginUnknown1=0x46d3 # OP_SendSpellChecksum +#OP_LoginUnknown2=0x040b # OP_SendSkillCapsChecksum + +# Petition Opcodes +OP_PetitionSearch=0x0000 search term for petition +OP_PetitionSearchResults=0x0000 (list of?) matches from search +OP_PetitionSearchText=0x0000 text results of search + +OP_PetitionUpdate=0x0000 +OP_PetitionCheckout=0x0000 +OP_PetitionCheckIn=0x0000 +OP_PetitionQue=0x0000 +OP_PetitionUnCheckout=0x0000 +OP_PetitionDelete=0x0000 +OP_DeletePetition=0x0000 +OP_PetitionResolve=0x0000 +OP_PDeletePetition=0x0000 +OP_PetitionBug=0x0000 +OP_PetitionRefresh=0x0000 +OP_PetitionCheckout2=0x0000 +OP_PetitionViewPetition=0x0000 + +# Login opcodes +OP_SessionReady=0x0000 +OP_Login=0x0000 +OP_ServerListRequest=0x0000 +OP_PlayEverquestRequest=0x0000 +OP_PlayEverquestResponse=0x0000 +OP_ChatMessage=0x0000 +OP_LoginAccepted=0x0000 +OP_ServerListResponse=0x0000 +OP_Poll=0x0000 +OP_EnterChat=0x0000 +OP_PollResponse=0x0000 + +# raw opcodes +OP_RAWSessionRequest=0x0000 +OP_RAWSessionResponse=0x0000 +OP_RAWCombined=0x0000 +OP_RAWSessionDisconnect=0x0000 +OP_RAWKeepAlive=0x0000 +OP_RAWSessionStatRequest=0x0000 +OP_RAWSessionStatResponse=0x0000 +OP_RAWPacket=0x0000 +OP_RAWFragment=0x0000 +OP_RAWOutOfOrderAck=0x0000 +OP_RAWAck=0x0000 +OP_RAWAppCombined=0x0000 +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 diff --git a/utils/patches/patch_SoD.conf b/utils/patches/patch_SoD.conf index 338ee82e7..f42231238 100644 --- a/utils/patches/patch_SoD.conf +++ b/utils/patches/patch_SoD.conf @@ -158,7 +158,7 @@ OP_GMApproval=0x72fa # C OP_GMToggle=0x7566 # C OP_GMSummon=0x596d # C OP_GMEmoteZone=0x3e7c # C -OP_GMEmoteWorld=0x3e7c # C +OP_GMEmoteWorld=0x5298 # C OP_GMFind=0x6e27 # C OP_GMKick=0x799c # C OP_GMKill=0x6685 # C @@ -369,7 +369,7 @@ OP_DzExpeditionEndsWarning=0x1879 OP_DzExpeditionList=0x3657 OP_DzMemberList=0x74e4 OP_DzCompass=0x35d3 -OP_DzChooseZone=0xd8a +OP_DzChooseZone=0x0d8a #0x1d99 was grouped with these too but I don't really know it's purpose. # New Opcodes @@ -580,7 +580,6 @@ OP_MultiLineMsg=0x0000 # OP_MendHPUpdate=0x0000 # OP_TargetReject=0x0000 # OP_SafePoint=0x0000 # -OP_IncreaseStats=0x0000 # OP_ApproveZone=0x0000 # OP_ZoneComplete=0x0000 # OP_ClientError=0x0000 # diff --git a/utils/patches/patch_SoF.conf b/utils/patches/patch_SoF.conf index fc0c716e1..7b0c41ad2 100644 --- a/utils/patches/patch_SoF.conf +++ b/utils/patches/patch_SoF.conf @@ -85,6 +85,7 @@ OP_TaskActivity=0x2E60 #SEQ 12/04/08 OP_CompletedTasks=0x75AC #Derision 2009 OP_Weather=0x70A5 #SEQ 12/04/08 OP_SendAATable=0x6F05 #Trevius 12/20/08 +OP_ShroudClearAA=0x71b9 OP_UpdateAA=0x45D2 #Trevius 12/20/08 OP_RespondAA=0x4426 #Trevius 12/20/08 OP_ReqClientSpawn=0x014C #SEQ 12/04/08 @@ -331,6 +332,7 @@ OP_InspectMessageUpdate=0x67e9 # C OP_OpenInventory=0x66c8 OP_OpenContainer=0x10e3 OP_Marquee=0x2f75 +OP_Untargetable=0x3e36 #expedition OP_DzQuit=0x20d6 @@ -530,7 +532,7 @@ OP_MultiLineMsg=0x0000 # OP_MendHPUpdate=0x0000 # OP_TargetReject=0x0000 # OP_SafePoint=0x0000 # -OP_IncreaseStats=0x0000 # +OP_IncreaseStats=0x5ecb # OP_ApproveZone=0x0000 # OP_ZoneComplete=0x0000 # OP_ClientError=0x0000 # @@ -544,9 +546,11 @@ OP_PickLockSuccess=0x0000 # OP_VetRewardsAvaliable=0x044b # OP_VetClaimRequest=0x7503 OP_VetClaimReply=0x01e1 -OP_PlayMP3=0x0000 # +OP_PlayMP3=0x0d1c # OP_ReclaimCrystals=0x0000 # OP_CrystalCountUpdate=0x64C1 # +OP_CrystalCreate=0x65e2 +OP_CrystalReclaim=0x0730 OP_DynamicWall=0x0000 # OP_OpenDiscordMerchant=0x0000 # OP_DiscordMerchantInventory=0x0000 # diff --git a/utils/patches/patch_Underfoot.conf b/utils/patches/patch_Underfoot.conf index 56838da65..2b1c41dd9 100644 --- a/utils/patches/patch_Underfoot.conf +++ b/utils/patches/patch_Underfoot.conf @@ -88,6 +88,7 @@ OP_TaskActivity=0x31f3 # C OP_CompletedTasks=0x687f # C OP_Weather=0x4658 # V OP_SendAATable=0x6ef9 # V +OP_ShroudClearAA=0x2cd4 OP_UpdateAA=0x7bf6 # V OP_RespondAA=0x1fbd # C 0x2bad OP_ReqClientSpawn=0x69cd # V @@ -315,7 +316,7 @@ OP_OnLevelMessage=0x24cb # C OP_AugmentInfo=0x31b1 # C OP_Petition=0x31d1 # C OP_SomeItemPacketMaybe=0x2c27 # C -OP_PVPStats=0x0000 # +OP_PVPStats=0x5272 # OP_PVPLeaderBoardRequest=0x4973 # C OP_PVPLeaderBoardReply=0x3842 # C OP_PVPLeaderBoardDetailsRequest=0x6c75 # C @@ -373,7 +374,17 @@ OP_DzExpeditionEndsWarning=0x6ac2 OP_DzExpeditionList=0x70d8 OP_DzMemberList=0x15c4 OP_DzCompass=0x01cb -OP_DzChooseZone=0x0000 +OP_DzChooseZone=0x65e1 + +#shroud +OP_ShroudSelectionWindow=0x72ad +OP_ShroudRequestStats=0x488b +OP_ShroudRespondStats=0x1910 +OP_ShroudSelect=0x45d7 +OP_ShroudSelectCancel=0x666d +OP_ShroudProgress=0x6016 # This clears current progress and sets +OP_ShroudProgress2=0x66b4 # This just sets progress +OP_Shroud=0x1643 # New Opcodes OP_SpawnPositionUpdate=0x4656 # C diff --git a/utils/scripts/db_update.pl b/utils/scripts/db_update.pl index d592bebc3..bf92ce60f 100644 --- a/utils/scripts/db_update.pl +++ b/utils/scripts/db_update.pl @@ -6,6 +6,21 @@ #::: Purpose: To upgrade databases with ease and maintain versioning ########################################################### + +#::: If current version is less than what world is reporting, then download a new one... +$current_version = 1; +if($ARGV[0] eq "V"){ + if($ARGV[1] > $current_version){ + print "Retrieving latest database manifest...\n"; + GetRemoteFile("https://raw.githubusercontent.com/EQEmu/Server/master/utils/scripts/db_update.pl", "db_update.pl"); + exit; + } + else{ + print "No update necessary \n"; + } + exit; +} + $perl_version = $^V; $perl_version =~s/v//g; print "Perl Version is " . $perl_version . "\n"; @@ -26,14 +41,14 @@ while() { elsif(/(.*)<\/db>/i) { $db = $1; } } -print +$console_output = "============================================================ EQEmu: Automatic Database Upgrade Check ============================================================ "; use Config; -print " Operating System is: $Config{osname}\n"; +$console_output .= " Operating System is: $Config{osname}\n"; if($Config{osname}=~/linux/i){ $OS = "Linux"; } if($Config{osname}=~/Win|MS/i){ $OS = "Windows"; } @@ -48,9 +63,9 @@ if($OS eq "Windows"){ last; } } - print " (Windows) MySQL is in system path \n"; - print " Path = " . $path . "\n"; - print "============================================================\n"; + $console_output .= " (Windows) MySQL is in system path \n"; + $console_output .= " Path = " . $path . "\n"; + $console_output .= "============================================================\n"; } } @@ -62,9 +77,9 @@ if($OS eq "Linux"){ } $path =~s/\n//g; - print " (Linux) MySQL is in system path \n"; - print " Path = " . $path . "\n"; - print "============================================================\n"; + $console_output .= " (Linux) MySQL is in system path \n"; + $console_output .= " Path = " . $path . "\n"; + $console_output .= "============================================================\n"; } #::: Path not found, error and exit @@ -93,10 +108,21 @@ if(GetMySQLResult("SHOW TABLES LIKE 'db_version'") eq ""){ } if($OS eq "Windows"){ @db_version = split(': ', `world db_version`); } -if($OS eq "Linux"){ @db_version = split(': ', `./world db_version`); } +if($OS eq "Linux"){ @db_version = split(': ', `./world db_version`); } $bin_db_ver = trim($db_version[1]); $local_db_ver = trim(GetMySQLResult("SELECT version FROM db_version LIMIT 1")); + +#::: If ran from Linux startup script, supress output +if($bin_db_ver == $local_db_ver && $ARGV[0] eq "ran_from_start"){ + print "Database up to date...\n"; + exit; +} +else{ + print $console_output; +} + + print " Binary Database Version: (" . $bin_db_ver . ")\n"; print " Local Database Version: (" . $local_db_ver . ")\n\n"; @@ -135,7 +161,7 @@ sub ShowMenuPrompt { while (1) { { local $| = 1; - if(!$menu_show && $ARGV[0] eq "ran_from_world"){ + if(!$menu_show && ($ARGV[0] eq "ran_from_world" || $ARGV[0] eq "ran_from_start")){ $menu_show++; next; } diff --git a/utils/scripts/opcode_scripts/.gitignore b/utils/scripts/opcode_scripts/.gitignore new file mode 100644 index 000000000..4104a1572 --- /dev/null +++ b/utils/scripts/opcode_scripts/.gitignore @@ -0,0 +1,3 @@ +# Input and Output txt and conf files. +*.txt +*.conf diff --git a/utils/scripts/opcode_scripts/conf_to_oplist.pl b/utils/scripts/opcode_scripts/conf_to_oplist.pl new file mode 100644 index 000000000..f79de1093 --- /dev/null +++ b/utils/scripts/opcode_scripts/conf_to_oplist.pl @@ -0,0 +1,66 @@ +#!/usr/bin/perl + +# File Name: conf_to_oplist.pl +# Converts a Patch File into the Opcode List with Opcode Names for the Spreadsheet. + +# Directions to use this script: +# 1. Paste the contents of the current patch file in the patch_OLD.conf file. +# 2. Run this script using "perl conf_to_oplist.pl" +# 3. This updates the opcodelist.txt with the values from the Patch File. + + +$stopmessage = "Failed to open file"; +open OpcodeFile, "<", "opcodelist.txt" or die $stopmessage; +open PatchFile, "<", "patch_OLD.conf" or die $stopmessage; + +my @OpcodeList = ; +my @PatchFile = ; +my %PatchHash = (); + +foreach $line (@PatchFile) +{ + @equalssplit = split(/=/, $line); + $ArraySize = @equalssplit; + if ($ArraySize > 1) + { + @OpcodeArray = split(//, $equalssplit[1]); + $CurOpcode = $OpcodeArray[0].$OpcodeArray[1].$OpcodeArray[2].$OpcodeArray[3].$OpcodeArray[4].$OpcodeArray[5]; + $CurOpcode = lc($CurOpcode); + # Opcode Name => Opcode + $PatchHash{ $CurOpcode } = $equalssplit[0]; + } +} + +close(OpcodeFile); +close(PatchFile); + +# Clear out file contents +open OpcodeResultFile, ">", "opcodelist.txt" or die $stopmessage; +print OpcodeResultFile ""; +close(OpcodeResultFile); + +open OpcodeResultFile, ">>", "opcodelist.txt" or die $stopmessage; + +while( my ($k, $v) = each %$PatchFile ) +{ + #print OpcodeResultFile "key: $k, value: $v.\n"; +} + + +$TabSpace = " "; # Tab +foreach $line (@OpcodeList) +{ + @LineSplit = split(//, $line); + $CurOpcode = $LineSplit[0].$LineSplit[1].$LineSplit[2].$LineSplit[3].$LineSplit[4].$LineSplit[5]; + $CurOpcode = lc($CurOpcode); + $OpcodeName = ""; # Tab + if ($PatchHash{$CurOpcode}) + { + $NameKey = $PatchHash{$CurOpcode}; + $OpcodeName = $NameKey; + } + $CurLine = $CurOpcode.$TabSpace.$OpcodeName."\n"; + print OpcodeResultFile $CurLine; +} + +close(OpcodeResultFile); diff --git a/utils/scripts/opcode_scripts/opcodelist.txt b/utils/scripts/opcode_scripts/opcodelist.txt new file mode 100644 index 000000000..6ef0916f9 --- /dev/null +++ b/utils/scripts/opcode_scripts/opcodelist.txt @@ -0,0 +1,1671 @@ +RoF2 Built May 10 2013 23:30:08 +0x04d6 +0x5efd +0x0cc1 +0x2c3d +0x7b25 +0x4f44 +0x7592 +0x2b1c +0x1fd1 OP_Consent +0x15a5 +0x3c7e +0x6262 +0x471d OP_AckPacket +0x64a8 +0x7c06 +0x2c5e +0x24ab +0x19b5 +0x72fa +0x0770 +0x4cef OP_ItemLinkClick +0x08c1 OP_GMServers +0x318f OP_BeginCast +0x5ec8 OP_Disarm +0x6ae3 +0x2b2d OP_ChannelMessage +0x1cb1 +0x5070 OP_TimeOfDay +0x7dfc OP_ClientUpdate +0x35ea +0x3b9a +0x148d +0x345d OP_ClientReady +0x5ae2 OP_WorldObjectsSent +0x03a4 +0x69e2 OP_TradeAcceptClick +0x354c OP_CancelTrade +0x3993 OP_FinishTrade +0x165b +0x2b95 +0x6fe2 +0x42fd +0x25d7 +0x7436 +0x4206 OP_TradeCoins +0x14bf OP_TradeRequestAck +0x7349 OP_FinishWindow +0x71fd +0x0b9c OP_SetGuildRank +0x0f7d +0x1bd3 +0x5053 OP_GuildPublicNote +0x01f9 +0x2c84 OP_MobUpdate +0x279d +0x5a94 +0x579a OP_TradeSkillCombine +0x56b1 +0x0f25 +0x3c14 +0x0c16 +0x4a8f OP_GMFind +0x2ad6 +0x0160 +0x7852 +0x41bf +0x40dd +0x3ec6 +0x6af4 +0x6d9d +0x2538 +0x0017 +0x017 +0x691a +0x0520 +0x200f +0x65ab OP_WhoAllRequest +0x674b +0x6a02 +0x0e48 +0x12a6 OP_GuildMemberList +0x0eae +0x2921 +0x7056 +0x507a OP_GuildList +0x6279 +0x5a26 +0x5b51 +0x0bad +0x59ad +0x2264 +0x004c OP_SkillUpdate +0x04c OP_ShopPlayerBuy +0x0ddd +0x4101 +0x5f03 OP_Petition +0x3de3 +0x1901 OP_ShopPlayerSell +0x791b +0x3a5d +0x7013 +0x47f1 +0x6506 OP_PlayerProfile +0x30a8 OP_ShopEnd +0x724f OP_ShopDelItem +0x5829 +0x1ce1 +0x47d6 +0x07cf +0x40c9 +0x38d4 +0x7eb9 +0x1e2c +0x3ae1 OP_GMBecomeNPC +0x4849 +0x376b +0x1e2a OP_Fishing +0x1eec OP_LevelUpdate +0x4958 +0x20ed OP_ExpUpdate +0x3ed4 +0x701c OP_RezzAnswer +0x640e OP_InstillDoubt +0x0ecf OP_Mend +0x09d3 +0x70a3 OP_IncreaseStats +0x178e +0x27e8 +0x61f8 +0x4322 +0x3f84 +0x40ef OP_FinishWindow2 +0x166c +0x106b OP_RandomReply +0x5bfd +0x11c8 +0x1753 +0x009f OP_SetRunMode +0x09f +0x4ae6 +0x3dbf +0x0399 +0x5c89 +0x012c +0x381c +0x4b36 +0x36d5 +0x5f21 +0x56f7 +0x46b1 +0x357c OP_GMSearchCorpse +0x2422 +0x084e +0x67e3 OP_GuildPeace +0x0f83 +0x4577 OP_TrackUnknown +0x5d55 OP_Sneak +0x67fe OP_Hide +0x12e5 +0x2c7a +0x600d OP_SaveOnZoneReq +0x16ce +0x46ce OP_GMLastName +0x1ffb OP_GuildWar +0x6718 +0x2f3e +0x7e09 OP_GuildLeader +0x5ce4 +0x5bcc +0x551d +0x6dbc +0x2219 OP_SafeFallSuccess +0x2686 +0x2022 +0x1c53 +0x33a2 +0x3541 +0x582e +0x3cba +0x190c +0x5067 +0x46d2 +0x49cf +0x1b0c + +0x31e6 OP_ApplyPoison +0x4211 OP_BoardBoat +0x7617 OP_LeaveBoat +0x4466 +0x1287 OP_CastSpell +0x5467 OP_ManaChange +0x43af OP_ManaChange +0x6a0d +0x217c OP_MemorizeSpell +0x260a OP_SenseHeading +0x5886 +0x7e8c +0x1a37 +0x0b6f +0x62ca +0x34a4 +0x6c83 +0x5d10 +0x21a0 +0x3537 +0x2acd +0x7c6d +0x6618 + + + +0x6c65 OP_GroupInvite2 +0x32c2 OP_CancelInvite +0x2a50 OP_GroupFollow2 +0x2060 OP_Jump +0x31f4 +0x4420 +0x1256 +0x4d38 +0x50f5 +0x79c5 OP_GMHideMe +0x2fab +0x57a5 +0x32a4 +0x590d OP_ExpansionInfo +0x6f15 OP_Damage +0x42ea +0x3091 +0x6b5a +0x3234 +0x30b4 +0x51fd OP_EnvDamage +0x15f4 +0x1808 OP_DeleteCharacter +0x1795 OP_NewZone +0x7887 OP_ReqNewZone +0x2414 +0x2eb3 +0x3db3 +0x4008 + +0x373b OP_Emote +0x7280 OP_DeleteSpawn +0x4bc2 +0x15bb +0x4fed OP_ShopRequest +0x0c2f +0x4aa1 OP_ClickObject +0x6fca OP_GroundSpawn +0x1045 +0x2a9e +0x2523 +0x6213 +0x4a39 OP_Save +0x0797 +0x35fa OP_ReqClientSpawn +0x5f8e OP_SendExpZonein +0x4c10 OP_GroupDisband +0x747c OP_SomeItemPacketMaybe +0x744c OP_Action +0x00d2 OP_SendCharInfo +0x0d2 +0x11d4 +0x6bbf OP_CharacterCreate +0x6517 OP_Death +0x19f6 +0x360f +0x1951 +0x51d3 OP_GMKill +0x26a7 OP_GMKick +0x7d8e OP_GMGoto +0x475e +0x5253 +0x3c79 +0x5065 +0x0337 +0x2ded +0x777b +0x4434 +0x0e90 +0x57da +0x150e +0x1ba8 +0x70eb +0x0adf OP_LootRequest +0x30f7 OP_EndLootRequest +0x5f44 OP_MoneyOnCorpse +0x3d54 +0x482d +0x58b2 +0x7361 +0x604f +0x3d87 +0x6909 +0x4f73 +0x0a0f +0x56a2 OP_ApproveName +0x69e6 +0x3a8f OP_ClickDoor +0x08e8 OP_MoveDoor +0x7dd1 +0x5c66 +0x312a OP_Illusion +0x2807 +0x4fe2 +0x2f2e +0x5824 +0x6661 +0x4742 +0x4d02 +0x2105 +0x19e9 +0x2268 +0x7994 OP_WearChange +0x0386 OP_Bind_Wound +0x5306 OP_Forage +0x0971 OP_SpawnAppearance +0x7099 OP_GuildInvite +0x7053 OP_GuildInviteAccept +0x1444 OP_GuildRemove +0x3708 OP_GuildDelete +0x7b22 +0x610f +0x324a +0x2889 +0x6f2a +0x2998 +0x700c OP_RemoveAllDoors +0x1966 OP_GMTraining +0x4d6b OP_GMEndTraining +0x6e8d +0x2b41 +0x4dc9 OP_LootItem +0x7177 OP_Animation +0x650e +0x2d18 OP_ZoneChange +0x707b +0x0803 +0x3fc3 +0x7326 OP_GuildStatus +0x0804 +0x3f86 +0x6d20 +0x04a3 +0x4714 +0x6703 OP_Begging +0x0ae7 OP_ControlBoat +0x27d7 +0x3c21 OP_RezzRequest +0x6f22 +0x32ee OP_MoveItem +0x0bcf OP_MoveCoin +0x2452 +0x0186 +0x0522 +0x3a54 OP_Split +0x659c OP_Buff +0x225b + +0x12cc OP_World_Client_CRC1 +0x661e OP_Weather +0x742b OP_Consider +0x102e +0x0f13 OP_World_Client_CRC2 +0x6944 +0x2703 OP_Taunt +0x5602 OP_Feedback +0x68c2 OP_TradeMoneyUpdate +0x07f7 +0x3fe7 +0x43a0 +0x314c +0x4b70 OP_Consume +0x2a79 OP_Stamina +0x402e +0x750e +0x0927 +0x36a4 OP_Stun +0x2b03 +0x68d3 OP_DuelResponse2 +0x6a46 OP_DuelResponse +0x5237 OP_ZoneSpawns +0x3eba OP_CombatAbility +0x109d OP_AutoAttack +0x075d OP_TargetMouse +0x05d2 +0x2a85 OP_GMTrainSkill +0x43a3 OP_ConfirmDelete +0x6c57 +0x3906 +0x55c4 OP_LootComplete +0x3196 OP_ShopEndConfirm +0x18ad OP_DeleteItem +0x01b8 OP_DeleteCharge +0x01d7 +0x4d28 +0x4fd9 +0x02c6 +0x3e30 +0x3fcf OP_RequestClientZoneChange +0x4e0e +0x1dee +0x62ac OP_GMZoneRequest +0x4ac6 OP_Logout +0x3526 OP_AutoAttack2 +0x7ceb OP_LogServer +0x0423 OP_Surname +0x3956 OP_FriendsWho +0x45dc +0x35e0 +0x173e +0x4853 +0x393b +0x0efa OP_SwapSpell +0x3588 +0x26e9 +0x4e56 OP_YellForHelp +0x2551 +0x38c8 +0x7499 OP_ApproveWorld +0x603a +0x7b10 OP_RandomReq +0x208f +0x607e OP_GMDelCorpse +0x1821 OP_Sacrifice +0x30d6 +0x760d OP_RezzComplete +0x4dd5 +0x0fe8 +0x6e5c +0x2228 +0x21bc +0x78c3 +0x3e57 +0x69d0 +0x1e3a +0x1a30 OP_Sound +0x61a0 +0x048c OP_InterruptCast +0x6e80 +0x3d29 +0x5d92 OP_Charm +0x0159 OP_PetCommands +0x486e +0x579e +0x49b3 +0x6db5 OP_GMApproval +0x4759 +0x0c22 OP_MOTD +0x2097 OP_GMToggle +0x72f7 +0x640c OP_MoneyUpdate +0x51e5 +0x1802 +0x5de1 +0x2e0c +0x58e2 OP_TargetCommand +0x444d OP_SetServerFilter +0x4478 OP_Assist +0x2a21 +0x7e59 +0x0b0b OP_SetGuildMOTD +0x3e13 OP_GuildMOTD +0x7a11 OP_ClearObject +0x6580 OP_Translocate +0x28ec OP_Camp +0x61b3 OP_BecomeTrader +0x31af +0x0876 OP_TGB +0x4ee1 +0x7e92 +0x7d14 OP_AAExpUpdate + +0x5578 OP_FaceChange +0x1a80 +0x42ff +0x7c2d +0x2c57 OP_MobRename +0x6dab +0x4568 +0x7776 +0x5029 +0x696c +0x23b9 OP_Dye +0x5204 OP_ConsiderCorpse +0x213f OP_SimpleMessage +0x1024 OP_FormattedMessage +0x2ce5 +0x5cc9 +0x3358 +0x52e5 OP_DeleteSpell +0x48c1 OP_Shielding +0x6f14 +0x1414 OP_Report +0x0e1c +0x1219 +0x6857 OP_KeyRing +0x55ac OP_RaidInvite +0x3973 OP_RaidUpdate +0x56fe +0x7f2d +0x31df OP_TraderShop +0x4ef5 OP_Trader +0x735d +0x019b +0x5930 +0x2d73 +0x39d6 OP_BazaarSearch +0x6a96 +0x424e OP_AAAction +0x7a27 OP_RespondAA +0x5eca +0x70c6 +0x0dd4 +0x4598 +0x19b6 +0x728a +0x5232 +0x77bd +0x70c0 +0x633c OP_ItemLinkResponse +0x63eb +0x4765 +0x6290 +0x1db6 OP_RecipesSearch +0x6e02 OP_RecipeReply +0x40d7 OP_RecipeDetails +0x6261 OP_RecipeAutoCombine +0x4015 OP_SetGroupLeadershipAbilities +0x6eae OP_SetRaidLeadershipAbilities +0x1fb5 OP_DoGroupLeadershipAbility +0x5a58 OP_MarkNPC +0x74bd OP_MarkRaidNPC +0x6c55 OP_LeadershipExpToggle +0x0026 OP_PurchaseLeadershipAA +0x026 OP_UpdateLeadershipAA +0x2814 OP_SetGroupTarget +0x4c9d OP_SetRaidTarget +0x76b8 OP_DelegateAbility +0x2b33 OP_DelegateRaidAbility +0x4786 OP_SenseNPCData +0x0eb4 OP_DelegateFailed +0x486c OP_InspectBuffs +0x0272 OP_TargetHoTT +0x2003 OP_ClearNPCMarks +0x20d3 OP_ClearRaidNPCMarks +0x1c89 +0x4ee2 +0x2797 OP_LeadershipExpUpdate +0x6da5 OP_ClearLeadershipAbilities +0x0ca6 +0x7717 +0x509d +0x6bee +0x0b04 +0x6097 OP_NewSpawn +0x758c +0x2b10 +0x4d09 +0x0083 OP_SpecialMesg +0x083 OP_TaskDescription +0x3714 OP_TaskActivity +0x08d3 OP_CancelTask +0x39f0 +0x5f7a OP_ItemScriptAdjustment +0x006a +0x06a +0x7465 +0x31c0 +0x14ba +0x25e0 +0x62a0 +0x407a +0x7c88 OP_WhoAllResponse +0x51b8 +0x578c +0x1197 +0x2dd3 +0x333a +0x2925 +0x37b1 OP_MobHealth +0x7314 +0x2404 OP_MobManaUpdate +0x5f5e +0x1c81 OP_MobEnduranceUpdate +0x4a78 +0x37a2 +0x5565 +0x2d09 +0x3141 +0x695e +0x0029 OP_TrackTarget +0x029 +0x1612 +0x3e69 +0x5c59 +0x7e1a OP_GMZoneRequest2 +0x24d9 +0x5089 OP_ZoneEntry +0x61db +0x542f +0x2006 +0x52fa OP_FeignDeath +0x39e8 OP_PickPocket +0x7c2e +0x2cdd +0x5e3a +0x4214 +0x2828 OP_HPUpdate +0x14b8 +0x3791 OP_ManaUpdate +0x0698 +0x5f42 OP_EnduranceUpdate +0x7eb5 +0x73f4 OP_Bug +0x69a4 OP_SendZonepoints +0x57bc OP_InspectRequest +0x71ac OP_InspectAnswer +0x4229 OP_GroupMakeLeader +0x54f7 +0x322b +0x3264 +0x202c +0x29ae +0x7a09 OP_SendLoginInfo +0x5206 +0x7fd2 +0x6259 OP_PostEnterWorld +0x0f70 +0x00d1 +0x0d1 +0x1eac +0x1b54 +0x5e23 +0x7910 +0x152e +0x7555 +0x05ce OP_GroupFollow +0x1649 OP_GroupInvite +0x6110 +0x0641 +0x578f OP_EnterWorld +0x6cb8 +0x6ef5 +0x4c44 OP_ZoneServerInfo +0x4cb4 OP_ZoneUnavail +0x74fb +0xbe6b +0x5ac7 +0x6281 +0x1bc5 OP_SetChatServer +0x48c8 +0x677b +0x6820 +0x2623 +0x69b9 OP_GuildMemberUpdate +0x2dcb +0x5e04 +0x1d3c +0x6060 OP_LFGCommand +0x0340 OP_LFGGetMatchesRequest +0x49a9 OP_LFPCommand +0x4d7d OP_LFPGetMatchesRequest +0x5048 OP_LFGGetMatchesResponse +0x22c6 OP_LFPGetMatchesResponse +0x499e +0x2d4e OP_GuildDemote +0x2009 +0x3b26 +0x69d4 +0x40b1 +0x587d +0x3747 +0x7a66 +0x2f03 +0x01ed +0x36e0 OP_GetGuildMOTD +0x4f1f OP_GetGuildMOTDReply +0x6087 +0x5f92 +0x439a +0x4334 +0x13c2 +0x7cb8 OP_TestBuff +0x17e5 OP_Track +0x5265 +0x1440 +0x7a4b +0x0648 +0x2529 +0x486f OP_GMSummon +0x458e OP_GMEmoteWorld +0x2549 +0x1cfd OP_GMEmoteZone +0x5ca6 OP_CharInventory +0x2710 +0x7291 OP_SpawnDoor +0x7afc +0x1f65 +0x3ddd +0x85fc +0x72df OP_ReadBook +0x3af1 OP_RequestDuel +0x52f7 +0x77b5 OP_TradeRequest +0x70f2 +0x207d +0x384a OP_ConsentResponse +0x5505 OP_TradeBusy +0x0c1e OP_ClickObjectAction +0x51eb +0x466b +0x41a3 +0x0682 +0x1242 +0x261d OP_LoadSpellSet +0x32a6 +0x2c6c OP_AdventureRequest +0x39e5 +0x5648 OP_AdventureDetails +0x5327 OP_LDoNButton +0x4524 +0x5954 OP_RandomNameGenerator +0x327d +0x2945 +0x33d7 +0x2651 +0x3b86 +0x0c6c +0x24d4 +0x4441 +0x4e42 +0x50d8 +0x1afb +0x6bd4 +0x3cb0 OP_AdventureInfoRequest +0x4c54 OP_AdventureInfo +0x7171 OP_AdventureData +0x35e8 +0x7d49 +0x1848 +0x2599 +0x0dab +0x6493 +0x34f2 +0x7d40 +0x0e00 +0x1cf3 +0x71f4 +0x5d18 OP_LeaveAdventure +0x4e25 +0x50c2 +0x400f OP_AdventureFinish +0x6368 +0x6192 +0x3aa4 +0x502e +0x1b01 OP_AdventureUpdate +0x0f24 +0x1015 +0x23a6 +0x5a74 +0x037a +0x7a45 OP_ConsentDeny +0x71bc +0x2382 OP_DenyResponse +0x02a0 +0x1126 OP_VetClaimRequest +0x0668 +0x16d4 OP_VetClaimReply +0x5cea OP_FindPersonRequest +0x0cc3 +0x7e58 OP_FindPersonReply +0x9be3 OP_PickLock +0x0438 +0x3d5c OP_LDoNOpen +0x15e7 +0x368e OP_ItemPacket +0x7331 +0x4936 +0x2c4e +0x78bf OP_DisarmTraps +0x02af OP_SenseTraps +0x65c3 OP_AdventurePointsUpdate +0x0dcb +0x661b OP_AugmentItem +0x5f68 +0x5278 +0x6b53 +0x5dd1 +0x559a OP_WeaponEquip2 +0x2d25 OP_WeaponUnequip2 +0x23c1 OP_WorldClientReady +0x5a62 OP_AdventureStatsRequest +0x7093 OP_AdventureLeaderboardRequest +0x2370 OP_AdventureStatsReply +0x7f79 OP_AdventureLeaderboardReply +0x4eb3 +0x3377 OP_BuffCreate +0x5882 OP_PetBuffWindow +0x4f4b OP_TargetBuffs +0x5961 OP_TributeUpdate +0x4f3e OP_TributeItem +0x5300 OP_TributePointUpdate +0x729b OP_SendTributes +0x4254 OP_TributeInfo +0x79fc OP_SelectTribute +0x073d OP_TributeTimer +0x08bf +0x7697 +0x4b65 +0x51ae +0x51a9 +0x7666 OP_OpenTributeMaster +0x4290 +0x759e OP_DisciplineUpdate +0x01c6 +0x6989 OP_DisciplineTimer +0x58fb OP_TributeMoney +0x7422 +0x0d25 +0x4de1 +0x46d0 +0x4f50 +0x2ddc +0x7441 +0x6a12 +0x29a8 +0x3f7f +0x150b +0x11e3 +0x1ad3 +0x59ca +0x4701 OP_DzAddPlayer +0x1abc OP_DzRemovePlayer +0x405b OP_DzSwapPlayer +0x543d OP_DzMakeLeader +0x14c6 OP_DzPlayerList +0x7f4b OP_DzJoinExpeditionConfirm +0x1950 OP_DzJoinExpeditionReply +0x64b5 +0x0398 +0x7b68 OP_DzListTimers +0x4f7e +0x9119 OP_DzExpeditionInfo +0x205f OP_DzExpeditionList +0xb2e3 OP_DzQuit +0x32f0 OP_DzMemberStatus +0x3de9 OP_DzLeaderStatus +0x5ae4 OP_DzMemberList +0x4d6e +0x4fd0 OP_OnLevelMessage +0x575b +0x7e94 +0x5189 OP_DzExpeditionEndsWarning +0x383c OP_BankerChange +0x791e +0x5c74 OP_RecipesFavorite +0x71b1 +0x20ab +0x025f +0x214a OP_PopupResponse +0x08a6 OP_ItemRecastDelay +0x15a9 OP_PVPLeaderBoardDetailsRequest +0x3707 OP_PVPLeaderBoardRequest +0x04aa OP_PVPLeaderBoardDetailsReply +0x25b7 OP_PVPLeaderBoardReply +0x071f +0x2dee +0x4e62 +0x0c91 +0x18d3 OP_Weblink +0x6f4b OP_PVPStats +0x4b15 +0x6755 +0x5c32 +0x5770 +0x7425 +0x5eed +0x574e +0x11b4 +0x4ed6 +0x0d9f +0x7d23 OP_ClearSurname +0x3fb0 +0xc693 OP_RemoveNimbusEffect +0x7b1e +0x20ae +0x0727 +0x3771 +0x7fe0 +0x4d5e +0x1877 OP_SendGuildTributes +0x649f +0x5f3f +0x5bcb +0x43d2 +0x49ea +0x378d OP_OpenGuildTributeMaster +0x7f8e +0x02bc +0x7dd2 +0x7c1d +0x7a41 +0x7db5 +0x7eec OP_SetChatServer2 +0x0904 OP_CorpseDrag +0x7037 OP_CorpseDrop +0x5e19 OP_TaskActivityComplete +0x4e32 +0x241d OP_TributeToggle +0x756a +0x7745 +0x039d +0x0f50 + +0x66b5 OP_SendAATable +0x0afb OP_AugmentInfo +0x10f6 +0x1013 + +0x6344 OP_RequestTitles +0x2d08 OP_SendTitleList +0x3719 +0x7a48 +0x0a23 OP_AcceptNewTask +0x705b +0x3bc9 OP_LevelAppearance +0x60ef +0x1619 +0x17fd OP_VoiceMacroIn +0x409a OP_VoiceMacroOut +0x4493 OP_WorldComplete +0x6527 OP_SetTitle +0x4c21 OP_SetTitleReply +0x127f OP_CameraEffect +0x0d32 OP_NewTitlesAvailable +0x34a7 OP_WeaponEquip1 +0x34cd +0x5936 OP_SpellEffect +0x241e OP_AutoFire +0x66f0 OP_UpdateAA +0x100e OP_CustomTitles +0x6b65 +0x12f5 +0x7677 OP_Bandolier +0x688f +0x7adf +0x0ed4 +0x243a OP_Barter +0x1a6a +0x5623 +0x43c8 OP_SendAAStats +0x655e +0x1a3e OP_PotionBelt +0x6326 OP_SetStartCity +0x7485 +0x5416 +0x3282 +0x3752 +0x425b +0x27c8 +0x2b19 +0x70ce +0x3165 +0x786b +0x0f26 +0x3500 +0x3d04 +0x5134 OP_GuildBank +0x0521 +0x7850 +0x108b +0x5671 +0x6d2b +0x732f +0x748f OP_GuildManageBanker +0x6858 +0x5e74 +0x3f35 +0x35e9 +0x2056 +0x6922 OP_AdventureMerchantRequest +0x5b72 OP_AdventureMerchantPurchase +0x2f9b OP_AdventureMerchantSell +0x3e47 OP_AdventureMerchantResponse +0x0b7d +0x2818 +0x35bd +0x51df +0x1ff7 +0x3926 +0x6265 +0x4ab0 +0x5e6c +0x1350 +0x6288 +0x7348 +0x48a2 OP_OpenNewTasksWindow +0x3010 +0x45db +0x36e8 OP_AvaliableTask +0x4865 +0x322e +0x7582 +0x5727 OP_TaskMemberList +0x6646 +0x37f2 +0x3444 +0x5ffc +0x5cb5 +0x0119 +0x35b5 +0x6cc6 +0x4926 +0x1b1d +0x7299 +0x0bf1 +0x08b4 +0x7f7a +0x3dab +0x1e7d +0x610a +0x04c8 +0x4811 +0x609e +0x65f0 +0x467f OP_CrystalCountUpdate +0x7aee OP_CrystalCreate +0x2439 OP_CrystalReclaim +0x39c1 +0x555e +0x7c9c +0x2c94 +0x2fc2 +0x2067 +0x059e +0x7f74 +0x68b2 +0x6f2b +0x01d6 +0x5182 +0x1da2 +0x5147 +0x51f8 +0x11f3 +0x0d07 +0x272f +0x413f +0x5968 +0x3e0e OP_DzCompass +0x282a +0x035f OP_GMNameChange +0x4013 +0x0e2f +0x099e +0x4eba OP_CompletedTasks +0x5f1c OP_TaskHistoryRequest +0x3d05 OP_TaskHistoryReply +0x2b8e +0x66d6 +0x0143 +0x6a5d +0x6e61 +0x2b0f +0x46c6 OP_FloatListThing +0x3a8d +0x15e5 +0x7d89 +0x4085 +0x1507 +0x5d93 +0x1669 +0x4664 +0x312d +0x2215 +0x1745 OP_Rewind +0x0cf1 +0x6567 +0x4405 +0x72d8 +0xaaba +0x27f8 OP_AssistGroup +0x7cfb +0x1a32 +0x14fd +0x77bb +0x36d1 +0x6193 +0x184c +0x3c47 +0x4e0b +0x7c15 +0x2ec3 OP_ShroudSelectionWindow +0x2b37 OP_ShroudRequestStats +0x6c04 OP_ShroudRespondStats +0x1cd0 OP_ShroudSelect +0x79da +0x7806 +0x541d OP_ShroudProgress +0x3823 +0x6562 OP_Shroud +0x4b25 +0x2507 +0x3acc +0x006e +0x06e +0x0728 +0x66cd +0x3c54 +0x2e8d +0x4eea +0x5a67 +0x3d20 +0x649c +0x21a6 +0x28bc OP_RespawnWindow +0x0ecb OP_ZonePlayerToBind +0x08d8 +0x7b71 +0x1115 +0x18cb +0x34f4 +0x5f08 +0x5bd2 +0x5052 +0x5e69 +0x2ccc +0x655c +0x6773 OP_CharacterCreateRequest +0x590e OP_VetRewardsAvaliable +0x6b0b +0x555a +0x7786 +0x67e8 +0x6381 +0x5628 +0xb52f +0x6084 +0x2958 OP_GuildUpdateURLAndChannel +0x2301 OP_WorldUnknown001 +0x610b +0x6f8b +0x4d25 OP_InspectMessageUpdate +0x3033 OP_BlockedBuffs +0x0de7 OP_RemoveBlockedBuffs +0x30e5 +0x618c +0x58e6 +0x1456 +0x053c OP_Untargetable +0x71da +0x333f +0x49bc +0x64f2 OP_BuffRemoveRequest +0x6b3d +0x34cb OP_ClearBlockedBuffs +0x5646 +0x7d13 +0x15e0 +0x5710 +0x172b +0x1888 +0x2956 +0x6a68 +0x7631 +0x298e +0x003c +0x03c +0x0b0f +0x5a72 +0x0767 +0x2f1a +0x6c7a +0x1660 +0x344f +0x028b +0x6eea +0x7707 +0x3fb2 +0x289e +0x3342 +0x5892 OP_GroupMentor +0x189c OP_NPCMoveUpdate +0x097b OP_ItemVerifyRequest +0x2115 OP_ItemVerifyReply +0x6411 +0x6471 +0x134a +0x1304 +0x5a79 +0x2dde +0x7d50 +0x1d47 +0x10ec +0x000f +0x00f OP_RestState +0x0f +0x465b +0x2289 OP_ItemViewUnknown +0x023b +0x4223 +0x7261 +0x2af9 +0x19aa +0x66dd OP_GMTrainSkillConfirm +0x4b64 +0x319e +0x1af3 +0x449c +0x8582 #OP_LoginUnknown1 +0x4b8d #OP_LoginUnknown2 +0x298d +0x06c8 +0x4f93 +0x412d +0x001f +0x01f +0x60f6 +0x1a9e +0x798e +0x17b7 +0x3042 +0x61bd +0x1f6e +0x65a6 +0x740d +0x48da +0x20d9 +0x5258 +0x1b5d +0x49f4 +0x7aa9 +0xb350 +0x6e2a +0x5d4e +0x6e4d +0x4ffc +0x1d15 +0x6f23 +0x2296 +0x765b +0x2e01 +0x26dd +0x72d3 +0x6981 +0x3b30 +0x14ac +0x0d92 +0x0001 +0x001 +0x01 +0x09bb +0x9e18 +0x0d9d +0x7f2b +0x3651 +0x645d +0x3af2 +0x4377 +0x39c9 +0x4924 +0x1e50 +0x4683 +0x0276 +0x6dec +0x56c9 +0x3ee6 +0x7121 OP_AltCurrency +0x62ab +0x5d88 +0x05f0 OP_MercenaryAssign +0x6b6d OP_AltCurrencyMerchantRequest +0x61cb OP_AltCurrencyPurchase +0x0165 OP_AltCurrencySell +0x74ec OP_AltCurrencyMerchantReply +0x5409 OP_AltCurrencySellSelection +0x3788 +0x40b6 +0x27a2 +0x532a OP_AltCurrencyReclaim +0x3899 +0x7567 +0x4820 +0x0339 +0x0c12 +0x074d +0x47ba +0x55c8 +0x4c89 +0x18b7 +0x2950 +0x44cb +0x4477 +0x6146 +0x40cf +0x2405 +0x49e2 +0x2aff +0x0bfa +0x26c0 +0x7d39 +0x259f +0x086b +0x6dc1 +0x6f80 +0x042e +0x2ad4 +0x317b +0x73ac +0x18d7 +0x5b8b +0x5c83 +0x1f51 +0x62f7 +0x1c46 +0x2a44 +0x3e7a +0x4374 +0x3b23 +0x1df4 +0x3ed8 +0x4b50 +0x36eb +0x65c7 +0x5620 +0x587e +0x3897 OP_SendFindableNPCs +0x7e62 +0x67ae +0x74f4 +0x4613 +0x2a92 +0x6d6e +0x2c01 +0x1243 +0x133e +0x67fc +0x49e1 OP_HideCorpse +0x0e44 +0x239a +0x1a0a +0x398f +0x1ff4 +0x88a1 +0x74da OP_GroupDisbandOther +0x21b4 OP_GroupLeaderChange +0x62b7 +0x4ced +0x3abb OP_GroupUpdate +0x0f6c OP_GroupDelete +0x0cbc +0x6194 OP_GroupUpdateB +0x7fe6 +0x04d0 +0x7323 OP_GroupAcknowledge +0x1ae5 OP_GroupDisbandYou +0x4d9f +0x70e2 OP_GroupRoles +0x6875 +0x6298 +0x02cf OP_GroupLeadershipAAUpdate +0x22b8 OP_MercenarySuspend2 +0x7bff +0x5512 +0x2b57 +0x1380 +0x2bcb +0x7b89 OP_MercenaryDataUpdateRequest +0x61a4 OP_MercenaryDataUpdate +0x11c1 OP_MercenaryDataRequest +0x72ce OP_MercenaryDataResponse +0x7169 OP_MercenaryHire +0x6e83 OP_MercenaryDismiss +0x31e4 OP_MercenaryTimerRequest +0x0763 OP_MercenaryTimer +0x78f6 +0x1b37 +0x27f2 OP_MercenaryCommand +0x5df8 +0x4333 +0x69ca +0x6e9f +0x4407 OP_MercenarySuspendRequest +0x6f03 OP_MercenarySuspendResponse +0x27a0 OP_MercenaryUnsuspendResponse +0x5f88 +0x2749 +0x038f +0x0e52 +0x20b9 +0x5d26 OP_MercenaryUnknown1 +0x69e7 +0x66b9 +0x0b72 +0x7e6f +0x29ec +0x6248 +0x702b +0x2b4f +0x6e6d +0x1e9f +0x5bd5 +0x4b7f +0x1490 +0x075e +0x4263 +0x1eba +0x4a74 +0x0a37 +0x3289 +0x3171 +0x0114 +0x5148 +0x76c3 +0x09bf +0x356f +0x77a7 +0x479a +0x209f +0x54e6 +0x6c3e +0xee80 +0x40e5 +0x76d9 +0x1dc8 OP_GuildCreate +0x794a +0x35c5 +0x137d +0x004a +0x04a +0x29b4 +0x18f1 +0x17fc +0x4707 OP_ChangeSize +0x3e15 +0x7248 +0x57c6 +0x7679 +0x6c8b +0x14c3 +0x3a02 +0x7900 +0x5688 +0x3a58 +0x75dd +0x39e1 +0x47cb +0x171e +0xdab0 +0x618f +0x27b1 +0x3d0c +0x0d2a +0x8c30 +0x2b42 +0x17f8 +0x1665 +0x059d +0x72c9 +0x675d +0x28e0 +0x61df +0x3ef8 +0x4d59 +0x3763 +0x672f OP_XTargetResponse +0x45be OP_XTargetRequest +0x792c OP_XTargetAutoAddHaters +0x66bf +0x66df +0x2aa7 +0x76c6 +0x6032 +0x3e00 +0x0c13 +0x0a59 +0x393a +0x45ed +0x507f +0x68ba +0x5a63 +0x6fd0 +0x63fd +0x4f09 +0x485d +0x3968 +0x69e1 +0x3d94 +0x0351 +0x5f0a +0x36be +0x59f9 +0x7075 +0x6ee6 +0x2691 +0x3c8b +0x01df +0x218c +0x233b +0x2cf7 +0x1097 +0x1baf +0x6f35 +0x1228 +0x1cef +0x7d28 +0x087f +0x1967 +0x6917 +0x613d +0x37d9 +0x58c7 +0x21ec +0x3424 +0x2036 +0x7ad1 +0x01c3 +0x626e +0x711a +0x5b41 +0x21ba +0x2933 +0x050c +0x51e3 +0x64a3 +0x3146 +0x2aa6 +0x16df +0x2698 +0x21ea +0x796c +0x7bb1 +0x6af6 +0x499a +0x3c44 +0x3cbc +0x6561 +0x12f7 +0x6292 +0x3de0 +0x1409 +0x4604 +0x0ca1 +0x754e +0x4f2b +0x6995 +0x3745 +0x789b +0xcadf +0x4abe +0x6014 +0x7fff +0x6228 +0x10e0 +0x7452 +0x6d9f +0x7f80 +0xb07f +0x6b5c OP_ItemPreview +0x02b4 +0x3bd3 +0x3036 +0x5c3f +0x433d +0x78e2 +0x543f +0x58b4 +0x2563 +0x1820 +0x40be +0x62d6 +0x0d68 +0x6d10 +0x3a32 +0x2fed +0x1303 +0x49df +0x3e24 +0x2b35 +0x0910 +0x3849 +0x6873 +0x15e2 +0x2d85 +0x39bb +0x42e9 +0x6860 +0x15a8 +0x52b5 +0x3311 +0x58df +0x2a7f +0x6573 +0x7a4d +0x7497 +0x1fcc +0x7c23 +0x2d28 +0x5dab +0x005a +0x05a +0x4506 +0x046d +0x36db +0x5a40 +0x4cd9 +0x63d7 +0x48d4 +0x035d +0x11f5 +0x7b84 +0x4f05 +0x7369 +0x7b32 +0x4fe6 +0x6cd0 +0x6770 +0x5c24 +0x063a +0x0d93 +0x4c2a +0x2235 +0x7b95 +0x6a1f +0x46f0 +0x2de2 +0xadd7 +0x2cc6 +0x7db7 +0x7588 +0x4957 +0x6a98 + +0x057b OP_SendMembershipDetails +0x7acc OP_SendMembership +0x431f +0x42c4 +0x05a8 +0x5475 OP_SendMaxCharacters +0x4ad2 +0x03d9 +0x771e +0x2a34 +0x7273 +0x1c9e +0x5f17 +0x37a9 +0x6530 +0x3157 +0x1f5e +0x2bc8 +0x593a +0x1643 +0x16bc +0x1781 +0x53d3 +0x5369 +0x25b3 +0x2fa0 +0x73a9 +0x1595 +0x6b6f +0x65f2 +0x3562 +0x309d +0x4efa +0x1da9 +0x6678 +0x16e8 diff --git a/utils/scripts/opcode_scripts/oplist_to_conf.pl b/utils/scripts/opcode_scripts/oplist_to_conf.pl new file mode 100644 index 000000000..0d808707d --- /dev/null +++ b/utils/scripts/opcode_scripts/oplist_to_conf.pl @@ -0,0 +1,88 @@ +#!/usr/bin/perl + +# File Name: oplist_to_conf.pl +# Converts the Opcode List with Opcode Names from the Spreadsheet into a Patch File. + +# Directions to use this script: +# 1. Copy the opcodes and opcode names columns from the opcode spreadsheet +# for the columns you want to create a new .conf file from into the file. +# 2. Remove the header row entries in the newly created text file and save it. +# 3. Paste the contents of the current patch file in the patch_OLD.conf file. +# 4. Run this script using "perl oplist_to_conf.pl" +# 5. This creates a new .conf file named patch_NEW.conf +# 6. Rename patch_NEW.conf to the desired name and you are all done + + +$stopmessage = "Failed to open file"; +# Open the text file that contains the opcode and opcode name rows for a single client from the opcode list spreadsheet +open OpcodeFile, "<", "opcodelist.txt" or die $stopmessage; +# Open the .conf file to copy from +open PatchFile, "<", "patch_OLD.conf" or die $stopmessage; + +# Read these files into arrays for looping and close the files +my @OpcodeList = ; +my @PatchFile = ; +close(OpcodeFile); +close(PatchFile); + +# Open the new/destination .conf file and clear out file contents +open OpcodeResultFile, ">", "patch_NEW.conf" or die $stopmessage; +print OpcodeResultFile ""; +# Close out the newly cleared .conf file +close(OpcodeResultFile); + +# Open the new/destination .conf file again for appending +open OpcodeResultFile, ">>", "patch_NEW.conf" or die $stopmessage; + +my %PatchHash = (); + +foreach $line (@OpcodeList) +{ + @equalssplit = split(/ /, $line); + $ArraySize = @equalssplit; + if ($ArraySize > 1) + { + my $CurOpcode = ""; + my $CurOpcodeName = ""; + @OpcodeArray = split(//, $equalssplit[0]); + if ($equalssplit[1] =~ /^OP_(.*)/i) + { + $CurOpcodeName = "OP_".$1; + } + foreach $Letter (@OpcodeArray) + { + if ($Letter =~ /[A-Za-z0-9]/) + { + $CurOpcode .= $Letter; + } + } + if ($CurOpcode && $CurOpcodeName) + { + $CurOpcode = lc($CurOpcode); + $PatchHash{ $CurOpcodeName } = $CurOpcode; + #print $CurOpcodeName."=". $CurOpcode."\n"; + } + } +} + +$TabSpace = " "; # Tab +foreach $line (@PatchFile) +{ + $CurLine = $line; + if ($line =~ /^OP_([^\=]+)=([^\s]+)(.*)/i) + { + $NewOpcode = "0x0000"; + $OpcodeName = "OP_".$1; + + if ($PatchHash{$OpcodeName}) + { + $NewOpcode = $PatchHash{$OpcodeName}; + } + + $CurLine = $OpcodeName."=".$NewOpcode.$3."\n"; + } + print OpcodeResultFile $CurLine; +} + + +close(OpcodeResultFile); diff --git a/utils/scripts/opcode_scripts/patch_NEW.conf b/utils/scripts/opcode_scripts/patch_NEW.conf new file mode 100644 index 000000000..148d00ba9 --- /dev/null +++ b/utils/scripts/opcode_scripts/patch_NEW.conf @@ -0,0 +1,661 @@ +# ShowEQ Import Notes: +# ZERO THE FILE first +# perl -pi -e 's/0x[0-9a-fA-F]{4}/0x0000/g' opcodes.conf +# Unknown Mapping: +# OP_Action2 -> OP_Damage +# OP_EnvDamage -> OP_Damage ---> might have been a one time mistake +# Name Differences: +# OP_CancelInvite -> OP_GroupCancelInvite +# OP_GMFind -> OP_FindPersonRequest +# OP_CommonMessage -> OP_ChannelMessage + +OP_Unknown=0x0000 +OP_ExploreUnknown=0x0000 used for unknown explorer + +# world packets +# Required to reach Char Select: +OP_SendLoginInfo=0x7a09 +OP_ApproveWorld=0x7499 +OP_LogServer=0x7ceb +OP_SendCharInfo=0x00d2 +OP_ExpansionInfo=0x590d +OP_GuildsList=0x0000 +OP_EnterWorld=0x578f +OP_PostEnterWorld=0x6259 +OP_World_Client_CRC1=0x12cc +OP_World_Client_CRC2=0x0f13 +OP_SendSpellChecksum=0x0000 +OP_SendSkillCapsChecksum=0x0000 + +# Character Select Related: +OP_SendMaxCharacters=0x5475 +OP_SendMembership=0x7acc +OP_SendMembershipDetails=0x057b +OP_CharacterCreateRequest=0x6773 +OP_CharacterCreate=0x6bbf +OP_DeleteCharacter=0x1808 +OP_RandomNameGenerator=0x5954 +OP_ApproveName=0x56a2 +OP_MOTD=0x0c22 +OP_SetChatServer=0x1bc5 +OP_SetChatServer2=0x7eec +OP_ZoneServerInfo=0x4c44 +OP_WorldComplete=0x4493 +OP_WorldUnknown001=0x2301 +OP_FloatListThing=0x46c6 + +# Reasons for Disconnect: +OP_ZoneUnavail=0x4cb4 +OP_WorldClientReady=0x23c1 +OP_CharacterStillInZone=0x0000 +OP_WorldChecksumFailure=0x0000 +OP_WorldLoginFailed=0x0000 +OP_WorldLogout=0x0000 +OP_WorldLevelTooHigh=0x0000 +OP_CharInacessable=0x0000 +OP_UserCompInfo=0x0000 +OP_SendExeChecksum=0x0000 +OP_SendBaseDataChecksum=0x0000 + +# Zone in opcodes +OP_AckPacket=0x471d +OP_ZoneEntry=0x5089 +OP_ReqNewZone=0x7887 +OP_NewZone=0x1795 +OP_ZoneSpawns=0x5237 +OP_PlayerProfile=0x6506 +OP_TimeOfDay=0x5070 +OP_LevelUpdate=0x1eec +OP_Stamina=0x2a79 +OP_RequestClientZoneChange=0x3fcf +OP_ZoneChange=0x2d18 +OP_LockoutTimerInfo=0x0000 +OP_ZoneServerReady=0x0000 +OP_ZoneInUnknown=0x0000 +OP_LogoutReply=0x0000 +OP_PreLogoutReply=0x0000 + +# Required to fully log in +OP_SpawnAppearance=0x0971 +OP_ChangeSize=0x4707 +OP_TributeUpdate=0x5961 +OP_TributeTimer=0x073d +OP_SendTributes=0x729b +OP_SendGuildTributes=0x1877 +OP_TributeInfo=0x4254 +OP_Weather=0x661e +OP_ReqClientSpawn=0x35fa +OP_SpawnDoor=0x7291 +OP_GroundSpawn=0x6fca +OP_SendZonepoints=0x69a4 +OP_BlockedBuffs=0x3033 +OP_RemoveBlockedBuffs=0x0de7 +OP_ClearBlockedBuffs=0x34cb +OP_WorldObjectsSent=0x5ae2 +OP_SendExpZonein=0x5f8e +OP_SendAATable=0x66b5 +OP_RespondAA=0x7a27 +OP_UpdateAA=0x66f0 +OP_SendAAStats=0x43c8 +OP_AAExpUpdate=0x7d14 +OP_ExpUpdate=0x20ed +OP_HPUpdate=0x2828 +OP_ManaChange=0x43af +OP_TGB=0x0876 +OP_SpecialMesg=0x0083 +OP_GuildMemberList=0x12a6 +OP_GuildMOTD=0x3e13 +OP_CharInventory=0x5ca6 +OP_WearChange=0x7994 +OP_ClientUpdate=0x7dfc +OP_ClientReady=0x345d # 0x422d +OP_SetServerFilter=0x444d + +# Guild Opcodes - Disabled until crashes are resolved in RoF +OP_GetGuildMOTD=0x36e0 # Was 0x35dc +OP_GetGuildMOTDReply=0x4f1f # Was 0x4586 +OP_GuildMemberUpdate=0x69b9 # Was 0x5643 +OP_GuildInvite=0x7099 +OP_GuildRemove=0x1444 +OP_GuildPeace=0x67e3 +OP_SetGuildMOTD=0x0b0b +OP_GuildList=0x507a +OP_GuildWar=0x1ffb +OP_GuildLeader=0x7e09 +OP_GuildDelete=0x3708 +OP_GuildInviteAccept=0x7053 +OP_GuildDemote=0x2d4e +OP_GuildPromote=0x0000 +OP_GuildPublicNote=0x5053 +OP_GuildManageBanker=0x748f # Was 0x0737 +OP_GuildBank=0x5134 # Was 0x10c3 +OP_SetGuildRank=0x0b9c +OP_GuildUpdateURLAndChannel=0x2958 +OP_GuildStatus=0x7326 +OP_GuildCreate=0x1dc8 # or maybe 0x086e +OP_GuildMemberLevelUpdate=0x0000 # Unused? +OP_ZoneGuildList=0x0000 # Unused? +OP_GetGuildsList=0x0000 # Unused? +OP_LFGuild=0x0000 +OP_GuildManageRemove=0x0000 +OP_GuildManageAdd=0x0000 +OP_GuildManageStatus=0x0000 + +# GM/Guide Opcodes +OP_GMServers=0x08c1 +OP_GMBecomeNPC=0x3ae1 +OP_GMZoneRequest=0x62ac +OP_GMZoneRequest2=0x7e1a +OP_GMGoto=0x7d8e +OP_GMSearchCorpse=0x357c +OP_GMHideMe=0x79c5 +OP_GMDelCorpse=0x607e +OP_GMApproval=0x6db5 +OP_GMToggle=0x2097 +OP_GMSummon=0x486f # Was 0x684f +OP_GMEmoteZone=0x1cfd # Was 0x0655 +OP_GMEmoteWorld=0x458e # Was 0x1935 +OP_GMFind=0x4a8f +OP_GMKick=0x26a7 +OP_GMKill=0x51d3 +OP_GMNameChange=0x035f # Was 0x4434 +OP_GMLastName=0x46ce # Was 0x3077 + +# Misc Opcodes +OP_InspectRequest=0x57bc +OP_InspectAnswer=0x71ac +OP_InspectMessageUpdate=0x4d25 +OP_BeginCast=0x318f +OP_ColoredText=0x0000 +OP_ConsentResponse=0x384a +OP_MemorizeSpell=0x217c +OP_SwapSpell=0x0efa +OP_CastSpell=0x1287 +OP_Consider=0x742b +OP_FormattedMessage=0x1024 +OP_SimpleMessage=0x213f +OP_Buff=0x659c +OP_Illusion=0x312a +OP_MoneyOnCorpse=0x5f44 +OP_RandomReply=0x106b +OP_DenyResponse=0x2382 +OP_SkillUpdate=0x004c +OP_GMTrainSkillConfirm=0x66dd # 0x3960 +OP_RandomReq=0x7b10 +OP_Death=0x6517 +OP_GMTraining=0x1966 +OP_GMEndTraining=0x4d6b +OP_GMTrainSkill=0x2a85 +OP_Animation=0x7177 +OP_Begging=0x6703 +OP_Consent=0x1fd1 +OP_ConsentDeny=0x7a45 +OP_AutoFire=0x241e +OP_PetCommands=0x0159 +OP_DeleteSpell=0x52e5 +OP_Surname=0x0423 +OP_ClearSurname=0x7d23 +OP_FaceChange=0x5578 +OP_SenseHeading=0x260a +OP_Action=0x744c +OP_ConsiderCorpse=0x5204 +OP_HideCorpse=0x49e1 +OP_CorpseDrag=0x0904 +OP_CorpseDrop=0x7037 +OP_Bug=0x73f4 +OP_Feedback=0x5602 +OP_Report=0x1414 +OP_Damage=0x6f15 +OP_ChannelMessage=0x2b2d +OP_Assist=0x4478 +OP_AssistGroup=0x27f8 +OP_MoveCoin=0x0bcf +OP_ZonePlayerToBind=0x0ecb +OP_KeyRing=0x6857 +OP_WhoAllRequest=0x65ab +OP_WhoAllResponse=0x7c88 +OP_FriendsWho=0x3956 +OP_ConfirmDelete=0x43a3 +OP_Logout=0x4ac6 +OP_Rewind=0x1745 +OP_TargetCommand=0x58e2 +OP_Hide=0x67fe +OP_Jump=0x2060 +OP_Camp=0x28ec +OP_Emote=0x373b +OP_SetRunMode=0x009f +OP_BankerChange=0x383c +OP_TargetMouse=0x075d +OP_MobHealth=0x37b1 +OP_InitialMobHealth=0x0000 # Unused? +OP_TargetHoTT=0x0272 +OP_XTargetResponse=0x672f +OP_XTargetRequest=0x45be +OP_XTargetAutoAddHaters=0x792c +OP_TargetBuffs=0x4f4b +OP_BuffCreate=0x3377 +OP_BuffRemoveRequest=0x64f2 +OP_DeleteSpawn=0x7280 +OP_AutoAttack=0x109d +OP_AutoAttack2=0x3526 +OP_Consume=0x4b70 +OP_MoveItem=0x32ee +OP_DeleteItem=0x18ad +OP_DeleteCharge=0x01b8 +OP_ItemPacket=0x368e +OP_ItemLinkResponse=0x633c +OP_ItemLinkClick=0x4cef +OP_ItemPreview=0x6b5c +OP_NewSpawn=0x6097 +OP_Track=0x17e5 +OP_TrackTarget=0x0029 +OP_TrackUnknown=0x4577 +OP_ClickDoor=0x3a8f +OP_MoveDoor=0x08e8 +OP_RemoveAllDoors=0x700c +OP_EnvDamage=0x51fd +OP_BoardBoat=0x4211 +OP_Forage=0x5306 +OP_LeaveBoat=0x7617 +OP_ControlBoat=0x0ae7 +OP_SafeFallSuccess=0x2219 +OP_RezzComplete=0x760d +OP_RezzRequest=0x3c21 +OP_RezzAnswer=0x701c +OP_Shielding=0x48c1 +OP_RequestDuel=0x3af1 +OP_MobRename=0x2c57 +OP_AugmentItem=0x661b # Was 0x37cb +OP_WeaponEquip1=0x34a7 +OP_WeaponEquip2=0x559a # Was 0x6022 +OP_WeaponUnequip2=0x2d25 # Was 0x0110 +OP_ApplyPoison=0x31e6 +OP_Save=0x4a39 +OP_TestBuff=0x7cb8 # Was 0x3772 +OP_CustomTitles=0x100e +OP_Split=0x3a54 +OP_YellForHelp=0x4e56 +OP_LoadSpellSet=0x261d +OP_Bandolier=0x7677 +OP_PotionBelt=0x1a3e # Was 0x4d3b +OP_DuelResponse=0x6a46 +OP_DuelResponse2=0x68d3 +OP_SaveOnZoneReq=0x600d +OP_ReadBook=0x72df +OP_Dye=0x23b9 +OP_InterruptCast=0x048c +OP_AAAction=0x424e +OP_LeadershipExpToggle=0x6c55 +OP_LeadershipExpUpdate=0x2797 +OP_PurchaseLeadershipAA=0x0026 +OP_UpdateLeadershipAA=0x026 +OP_MarkNPC=0x5a58 +OP_MarkRaidNPC=0x74bd #unimplemented +OP_ClearNPCMarks=0x2003 +OP_ClearRaidNPCMarks=0x20d3 #unimplemented +OP_DelegateAbility=0x76b8 +OP_SetGroupTarget=0x2814 +OP_Charm=0x5d92 +OP_Stun=0x36a4 +OP_SendFindableNPCs=0x3897 +OP_FindPersonRequest=0x5cea +OP_FindPersonReply=0x7e58 +OP_Sound=0x1a30 +OP_PetBuffWindow=0x5882 +OP_LevelAppearance=0x3bc9 +OP_Translocate=0x6580 +OP_Sacrifice=0x1821 +OP_PopupResponse=0x214a +OP_OnLevelMessage=0x4fd0 +OP_AugmentInfo=0x0afb +OP_Petition=0x5f03 +OP_SomeItemPacketMaybe=0x747c +OP_PVPStats=0x6f4b # Unsure +OP_PVPLeaderBoardRequest=0x3707 +OP_PVPLeaderBoardReply=0x25b7 +OP_PVPLeaderBoardDetailsRequest=0x15a9 +OP_PVPLeaderBoardDetailsReply=0x04aa +OP_RestState=0x00f +OP_RespawnWindow=0x28bc +OP_LDoNButton=0x5327 +OP_SetStartCity=0x6326 # Was 0x2d1b +OP_VoiceMacroIn=0x17fd +OP_VoiceMacroOut=0x409a +OP_ItemViewUnknown=0x2289 +OP_VetRewardsAvaliable=0x590e +OP_VetClaimRequest=0x1126 +OP_VetClaimReply=0x16d4 +OP_DisciplineUpdate=0x759e # Was 0x2f05 +OP_DisciplineTimer=0x6989 # Was 0x5e3f +OP_BecomeCorpse=0x0000 # Unused? +OP_Action2=0x0000 # Unused? +OP_MobUpdate=0x2c84 +OP_NPCMoveUpdate=0x189c +OP_CameraEffect=0x127f +OP_SpellEffect=0x5936 +OP_RemoveNimbusEffect=0xc693 +OP_AltCurrency=0x7121 +OP_AltCurrencyMerchantRequest=0x6b6d +OP_AltCurrencyMerchantReply=0x74ec +OP_AltCurrencyPurchase=0x61cb +OP_AltCurrencySell=0x0165 +OP_AltCurrencySellSelection=0x5409 +OP_AltCurrencyReclaim=0x532a +OP_CrystalCountUpdate=0x467f # Was 0x3f60 +OP_CrystalCreate=0x7aee # Was 0x5a82 +OP_CrystalReclaim=0x2439 # Was 0x7616 +OP_Untargetable=0x053c +OP_IncreaseStats=0x70a3 +OP_Weblink=0x18d3 +#OP_OpenInventory=0x0000 # Likely does not exist in RoF -U +OP_OpenContainer=0x0000 +OP_Marquee=0x0000 + +OP_DzQuit=0xb2e3 +OP_DzListTimers=0x7b68 +OP_DzAddPlayer=0x4701 +OP_DzRemovePlayer=0x1abc +OP_DzSwapPlayer=0x405b +OP_DzMakeLeader=0x543d +OP_DzPlayerList=0x14c6 +OP_DzJoinExpeditionConfirm=0x7f4b +OP_DzJoinExpeditionReply=0x1950 +OP_DzExpeditionInfo=0x9119 +OP_DzExpeditionList=0x205f +OP_DzMemberStatus=0x32f0 +OP_DzLeaderStatus=0x3de9 +OP_DzExpeditionEndsWarning=0x5189 +OP_DzMemberList=0x5ae4 +OP_DzCompass=0x3e0e # Was 0x4f09 +OP_DzChooseZone=0x0000 # Maybe 0x29d6 + +# New Opcodes +OP_SpawnPositionUpdate=0x0000 # Actually OP_MobUpdate ? +OP_ManaUpdate=0x3791 +OP_EnduranceUpdate=0x5f42 +OP_MobManaUpdate=0x2404 +OP_MobEnduranceUpdate=0x1c81 + +# Mercenary Opcodes +OP_MercenaryDataUpdateRequest=0x7b89 +OP_MercenaryDataUpdate=0x61a4 +OP_MercenaryDataRequest=0x11c1 +OP_MercenaryDataResponse=0x72ce +OP_MercenaryHire=0x7169 +OP_MercenaryDismiss=0x6e83 +OP_MercenaryTimerRequest=0x31e4 +OP_MercenaryTimer=0x0763 +OP_MercenaryUnknown1=0x5d26 +OP_MercenaryCommand=0x27f2 +OP_MercenarySuspendRequest=0x4407 +OP_MercenarySuspendResponse=0x6f03 +OP_MercenaryUnsuspendResponse=0x27a0 + +# Looting +OP_LootRequest=0x0adf +OP_EndLootRequest=0x30f7 +OP_LootItem=0x4dc9 +OP_LootComplete=0x55c4 + +# bazaar trader stuff: +OP_BazaarSearch=0x39d6 +OP_TraderDelItem=0x0000 +OP_BecomeTrader=0x61b3 +OP_TraderShop=0x31df +OP_Trader=0x4ef5 # Was 0x6790 +OP_TraderBuy=0x0000 +OP_Barter=0x243a +OP_ShopItem=0x0000 +OP_BazaarInspect=0x0000 +OP_Bazaar=0x0000 +OP_TraderItemUpdate=0x0000 + +# pc/npc trading +OP_TradeRequest=0x77b5 +OP_TradeAcceptClick=0x69e2 +OP_TradeRequestAck=0x14bf +OP_TradeCoins=0x4206 +OP_FinishTrade=0x3993 +OP_CancelTrade=0x354c +OP_TradeMoneyUpdate=0x68c2 +OP_MoneyUpdate=0x640c +OP_TradeBusy=0x5505 + +# Sent after canceling trade or after closing tradeskill object +OP_FinishWindow=0x7349 +OP_FinishWindow2=0x40ef + +# Sent on Live for what seems to be item existance verification +# Ex. Before Right Click Effect happens from items +OP_ItemVerifyRequest=0x097b +OP_ItemVerifyReply=0x2115 + +# merchant stuff +OP_ShopPlayerSell=0x1901 +OP_ShopRequest=0x4fed +OP_ShopEnd=0x30a8 +OP_ShopEndConfirm=0x3196 +OP_ShopPlayerBuy=0x04c +OP_ShopDelItem=0x724f + +# tradeskill stuff: +OP_ClickObject=0x4aa1 +OP_ClickObjectAction=0x0c1e +OP_ClearObject=0x7a11 +OP_RecipeDetails=0x40d7 +OP_RecipesFavorite=0x5c74 +OP_RecipesSearch=0x1db6 +OP_RecipeReply=0x6e02 +OP_RecipeAutoCombine=0x6261 +OP_TradeSkillCombine=0x579a + +# Tribute Packets: +OP_OpenGuildTributeMaster=0x378d +OP_OpenTributeMaster=0x7666 # Was 0x40f5 +OP_SelectTribute=0x79fc +OP_TributeItem=0x4f3e +OP_TributeMoney=0x58fb # Was 0x6fed +OP_TributeToggle=0x241d +OP_TributePointUpdate=0x5300 +OP_TributeNPC=0x0000 +OP_GuildTributeInfo=0x0000 +OP_OpenTributeReply=0x0000 +OP_GuildTributeStatus=0x0000 + +# Adventure packets: +OP_LeaveAdventure=0x5d18 +OP_AdventureFinish=0x400f +OP_AdventureInfoRequest=0x3cb0 +OP_AdventureInfo=0x4c54 +OP_AdventureRequest=0x2c6c +OP_AdventureDetails=0x5648 +OP_AdventureData=0x7171 +OP_AdventureUpdate=0x1b01 +OP_AdventureMerchantRequest=0x6922 # Was 654d +OP_AdventureMerchantResponse=0x3e47 # Was 7949 +OP_AdventureMerchantPurchase=0x5b72 # Was 155a +OP_AdventureMerchantSell=0x2f9b # Was 389c +OP_AdventurePointsUpdate=0x65c3 # Was 7589 +OP_AdventureStatsRequest=0x5a62 +OP_AdventureStatsReply=0x2370 +OP_AdventureLeaderboardRequest=0x7093 +OP_AdventureLeaderboardReply=0x7f79 + +# Group Opcodes +OP_GroupDisband=0x4c10 +OP_GroupInvite=0x1649 +OP_GroupFollow=0x05ce +OP_GroupUpdate=0x3abb +OP_GroupUpdateB=0x6194 +OP_GroupCancelInvite=0x0000 +OP_GroupAcknowledge=0x7323 +OP_GroupDelete=0x0f6c +OP_CancelInvite=0x32c2 +OP_GroupFollow2=0x2a50 +OP_GroupInvite2=0x6c65 +OP_GroupDisbandYou=0x1ae5 +OP_GroupDisbandOther=0x74da +OP_GroupLeaderChange=0x21b4 +OP_GroupRoles=0x70e2 +OP_GroupMakeLeader=0x4229 +OP_DoGroupLeadershipAbility=0x1fb5 +OP_GroupLeadershipAAUpdate=0x02cf +OP_GroupMentor=0x5892 +OP_InspectBuffs=0x486c + +# LFG/LFP Opcodes +OP_LFGCommand=0x6060 +OP_LFGGetMatchesRequest=0x0340 +OP_LFGGetMatchesResponse=0x5048 +OP_LFPGetMatchesRequest=0x4d7d +OP_LFPGetMatchesResponse=0x22c6 +OP_LFPCommand=0x49a9 +OP_LFGAppearance=0x0000 +OP_LFGResponse=0x0000 + +# Raid Opcodes +OP_RaidInvite=0x55ac +OP_RaidUpdate=0x3973 +OP_RaidJoin=0x0000 + +# Button-push commands +OP_Taunt=0x2703 +OP_CombatAbility=0x3eba +OP_SenseTraps=0x02af # Was 0x2ee0 +OP_PickPocket=0x39e8 +OP_DisarmTraps=0x78bf +OP_Disarm=0x5ec8 +OP_Sneak=0x5d55 +OP_Fishing=0x1e2a +OP_InstillDoubt=0x640e +OP_FeignDeath=0x52fa +OP_Mend=0x0ecf +OP_Bind_Wound=0x0386 +OP_LDoNOpen=0x3d5c + +# Task packets +OP_TaskDescription=0x083 +OP_TaskActivity=0x3714 +OP_CompletedTasks=0x4eba +OP_TaskActivityComplete=0x5e19 +OP_AcceptNewTask=0x0a23 +OP_CancelTask=0x08d3 +OP_TaskMemberList=0x5727 # Was 0x1656 +OP_OpenNewTasksWindow=0x48a2 # Was 0x11de +OP_AvaliableTask=0x36e8 # Was 0x2377 +OP_TaskHistoryRequest=0x5f1c +OP_TaskHistoryReply=0x3d05 +OP_DeclineAllTasks=0x0000 + +# Title opcodes +OP_NewTitlesAvailable=0x0d32 +OP_RequestTitles=0x6344 +OP_SendTitleList=0x2d08 +OP_SetTitle=0x6527 +OP_SetTitleReply=0x4c21 + +# mail opcodes +OP_Command=0x0000 +OP_MailboxHeader=0x0000 +OP_MailHeader=0x0000 +OP_MailBody=0x0000 +OP_NewMail=0x0000 +OP_SentConfirm=0x0000 + +########### Below this point should not be needed ########### + +# This section are all unknown in Titanium +OP_ForceFindPerson=0x0000 +OP_LocInfo=0x0000 +OP_ReloadUI=0x0000 +OP_ItemName=0x0000 +OP_ItemLinkText=0x0000 +OP_MultiLineMsg=0x0000 +OP_MendHPUpdate=0x0000 +OP_TargetReject=0x0000 +OP_SafePoint=0x0000 +OP_ApproveZone=0x0000 +OP_ZoneComplete=0x0000 +OP_ClientError=0x0000 +OP_DumpName=0x0000 +OP_Heartbeat=0x0000 +OP_CrashDump=0x0000 +OP_LoginComplete=0x0000 + +# discovered opcodes not yet used: +OP_PickLockSuccess=0x0000 +OP_PlayMP3=0x0000 +OP_ReclaimCrystals=0x0000 +OP_DynamicWall=0x0000 +OP_OpenDiscordMerchant=0x0000 +OP_DiscordMerchantInventory=0x0000 +OP_GiveMoney=0x0000 +OP_RequestKnowledgeBase=0x0000 +OP_KnowledgeBase=0x0000 +OP_SlashAdventure=0x0000 # /adventure +OP_BecomePVPPrompt=0x0000 +OP_MoveLogRequest=0x0000 # gone I think +OP_MoveLogDisregard=0x0000 # gone I think + +# named unknowns, to make looking for real unknown easier +OP_AnnoyingZoneUnknown=0x0000 +OP_Some6ByteHPUpdate=0x0000 seems to happen when you target group members +OP_QueryResponseThing=0x0000 + + +# realityincarnate: these are just here to stop annoying several thousand byte packet dumps +#OP_LoginUnknown1=0x46d3 # OP_SendSpellChecksum +#OP_LoginUnknown2=0x040b # OP_SendSkillCapsChecksum + +# Petition Opcodes +OP_PetitionSearch=0x0000 search term for petition +OP_PetitionSearchResults=0x0000 (list of?) matches from search +OP_PetitionSearchText=0x0000 text results of search + +OP_PetitionUpdate=0x0000 +OP_PetitionCheckout=0x0000 +OP_PetitionCheckIn=0x0000 +OP_PetitionQue=0x0000 +OP_PetitionUnCheckout=0x0000 +OP_PetitionDelete=0x0000 +OP_DeletePetition=0x0000 +OP_PetitionResolve=0x0000 +OP_PDeletePetition=0x0000 +OP_PetitionBug=0x0000 +OP_PetitionRefresh=0x0000 +OP_PetitionCheckout2=0x0000 +OP_PetitionViewPetition=0x0000 + +# Login opcodes +OP_SessionReady=0x0000 +OP_Login=0x0000 +OP_ServerListRequest=0x0000 +OP_PlayEverquestRequest=0x0000 +OP_PlayEverquestResponse=0x0000 +OP_ChatMessage=0x0000 +OP_LoginAccepted=0x0000 +OP_ServerListResponse=0x0000 +OP_Poll=0x0000 +OP_EnterChat=0x0000 +OP_PollResponse=0x0000 + +# raw opcodes +OP_RAWSessionRequest=0x0000 +OP_RAWSessionResponse=0x0000 +OP_RAWCombined=0x0000 +OP_RAWSessionDisconnect=0x0000 +OP_RAWKeepAlive=0x0000 +OP_RAWSessionStatRequest=0x0000 +OP_RAWSessionStatResponse=0x0000 +OP_RAWPacket=0x0000 +OP_RAWFragment=0x0000 +OP_RAWOutOfOrderAck=0x0000 +OP_RAWAck=0x0000 +OP_RAWAppCombined=0x0000 +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 + +OP_ItemRecastDelay=0x08a6 diff --git a/utils/scripts/opcode_scripts/patch_OLD.conf b/utils/scripts/opcode_scripts/patch_OLD.conf new file mode 100644 index 000000000..4bc1f9097 --- /dev/null +++ b/utils/scripts/opcode_scripts/patch_OLD.conf @@ -0,0 +1,661 @@ +# ShowEQ Import Notes: +# ZERO THE FILE first +# perl -pi -e 's/0x[0-9a-fA-F]{4}/0x0000/g' opcodes.conf +# Unknown Mapping: +# OP_Action2 -> OP_Damage +# OP_EnvDamage -> OP_Damage ---> might have been a one time mistake +# Name Differences: +# OP_CancelInvite -> OP_GroupCancelInvite +# OP_GMFind -> OP_FindPersonRequest +# OP_CommonMessage -> OP_ChannelMessage + +OP_Unknown=0x0000 +OP_ExploreUnknown=0x0000 used for unknown explorer + +# world packets +# Required to reach Char Select: +OP_SendLoginInfo=0x7a09 +OP_ApproveWorld=0x7499 +OP_LogServer=0x7ceb +OP_SendCharInfo=0x00d2 +OP_ExpansionInfo=0x590d +OP_GuildsList=0x7056 +OP_EnterWorld=0x0641 +OP_PostEnterWorld=0x6259 +OP_World_Client_CRC1=0x12cc +OP_World_Client_CRC2=0x0f13 +OP_SendSpellChecksum=0x0000 +OP_SendSkillCapsChecksum=0x0000 + +# Character Select Related: +OP_SendMaxCharacters=0x05a8 +OP_SendMembership=0x057b +OP_SendMembershipDetails=0x6a98 +OP_CharacterCreateRequest=0x655c +OP_CharacterCreate=0x6bbf +OP_DeleteCharacter=0x1808 +OP_RandomNameGenerator=0x5954 +OP_ApproveName=0x56a2 +OP_MOTD=0x0c22 +OP_SetChatServer=0x1bc5 +OP_SetChatServer2=0x7db5 +OP_ZoneServerInfo=0x6ef5 +OP_WorldComplete=0x4493 +OP_WorldUnknown001=0x2958 +OP_FloatListThing=0x46c6 + +# Reasons for Disconnect: +OP_ZoneUnavail=0x4c44 +OP_WorldClientReady=0x23c1 +OP_CharacterStillInZone=0x0000 +OP_WorldChecksumFailure=0x0000 +OP_WorldLoginFailed=0x0000 +OP_WorldLogout=0x0000 +OP_WorldLevelTooHigh=0x0000 +OP_CharInacessable=0x0000 +OP_UserCompInfo=0x0000 +OP_SendExeChecksum=0x0000 +OP_SendBaseDataChecksum=0x0000 + +# Zone in opcodes +OP_AckPacket=0x471d +OP_ZoneEntry=0x5089 +OP_ReqNewZone=0x7887 +OP_NewZone=0x1795 +OP_ZoneSpawns=0x5237 +OP_PlayerProfile=0x6506 +OP_TimeOfDay=0x5070 +OP_LevelUpdate=0x1eec +OP_Stamina=0x2a79 +OP_RequestClientZoneChange=0x3fcf +OP_ZoneChange=0x2d18 +OP_LockoutTimerInfo=0x0000 +OP_ZoneServerReady=0x0000 +OP_ZoneInUnknown=0x0000 +OP_LogoutReply=0x0000 +OP_PreLogoutReply=0x0000 + +# Required to fully log in +OP_SpawnAppearance=0x0971 +OP_ChangeSize=0x4707 +OP_TributeUpdate=0x5961 +OP_TributeTimer=0x073d +OP_SendTributes=0x729b +OP_SendGuildTributes=0x4d5e +OP_TributeInfo=0x4254 +OP_Weather=0x661e +OP_ReqClientSpawn=0x35fa +OP_SpawnDoor=0x7291 +OP_GroundSpawn=0x6fca +OP_SendZonepoints=0x69a4 +OP_BlockedBuffs=0x4d25 +OP_RemoveBlockedBuffs=0x3033 +OP_ClearBlockedBuffs=0x6b3d +OP_WorldObjectsSent=0x5ae2 +OP_SendExpZonein=0x5f8e +OP_SendAATable=0x66b5 +OP_RespondAA=0x7a27 +OP_UpdateAA=0x66f0 +OP_SendAAStats=0x43c8 +OP_AAExpUpdate=0x7e92 +OP_ExpUpdate=0x20ed +OP_HPUpdate=0x2828 +OP_ManaChange=0x1287 +OP_TGB=0x0000 +OP_SpecialMesg=0x0000 +OP_GuildMemberList=0x0e48 +OP_GuildMOTD=0x3e13 +OP_CharInventory=0x5ca6 +OP_WearChange=0x7994 +OP_ClientUpdate=0x7dfc +OP_ClientReady=0x345d # 0x422d +OP_SetServerFilter=0x444d + +# Guild Opcodes - Disabled until crashes are resolved in RoF +OP_GetGuildMOTD=0x36e0 # Was 0x35dc +OP_GetGuildMOTDReply=0x4f1f # Was 0x4586 +OP_GuildMemberUpdate=0x69b9 # Was 0x5643 +OP_GuildInvite=0x7099 +OP_GuildRemove=0x1444 +OP_GuildPeace=0x084e +OP_SetGuildMOTD=0x0b0b +OP_GuildList=0x0000 +OP_GuildWar=0x46ce +OP_GuildLeader=0x2f3e +OP_GuildDelete=0x3708 +OP_GuildInviteAccept=0x7053 +OP_GuildDemote=0x2d4e +OP_GuildPromote=0x0000 +OP_GuildPublicNote=0x5053 +OP_GuildManageBanker=0x748f # Was 0x0737 +OP_GuildBank=0x5134 # Was 0x10c3 +OP_SetGuildRank=0x0b9c +OP_GuildUpdateURLAndChannel=0x6084 +OP_GuildStatus=0x7326 +OP_GuildCreate=0x1dc8 # or maybe 0x086e +OP_GuildMemberLevelUpdate=0x0000 # Unused? +OP_ZoneGuildList=0x0000 # Unused? +OP_GetGuildsList=0x0000 # Unused? +OP_LFGuild=0x0000 +OP_GuildManageRemove=0x0000 +OP_GuildManageAdd=0x0000 +OP_GuildManageStatus=0x0000 + +# GM/Guide Opcodes +OP_GMServers=0x08c1 +OP_GMBecomeNPC=0x3ae1 +OP_GMZoneRequest=0x62ac +OP_GMZoneRequest2=0x7e1a +OP_GMGoto=0x7d8e +OP_GMSearchCorpse=0x46b1 +OP_GMHideMe=0x79c5 +OP_GMDelCorpse=0x607e +OP_GMApproval=0x6db5 +OP_GMToggle=0x2097 +OP_GMSummon=0x486f # Was 0x684f +OP_GMEmoteZone=0x1cfd # Was 0x0655 +OP_GMEmoteWorld=0x458e # Was 0x1935 +OP_GMFind=0x4a8f +OP_GMKick=0x26a7 +OP_GMKill=0x51d3 +OP_GMNameChange=0x035f # Was 0x4434 +OP_GMLastName=0x16ce # Was 0x3077 + +# Misc Opcodes +OP_InspectRequest=0x57bc +OP_InspectAnswer=0x71ac +OP_InspectMessageUpdate=0x6f8b +OP_BeginCast=0x318f +OP_ColoredText=0x0000 +OP_ConsentResponse=0x384a +OP_MemorizeSpell=0x6a0d +OP_SwapSpell=0x0efa +OP_CastSpell=0x4466 +OP_Consider=0x742b +OP_FormattedMessage=0x1024 +OP_SimpleMessage=0x213f +OP_Buff=0x3a54 +OP_Illusion=0x312a +OP_MoneyOnCorpse=0x5f44 +OP_RandomReply=0x106b +OP_DenyResponse=0x2382 +OP_SkillUpdate=0x2264 +OP_GMTrainSkillConfirm=0x19aa # 0x3960 +OP_RandomReq=0x7b10 +OP_Death=0x6517 +OP_GMTraining=0x1966 +OP_GMEndTraining=0x4d6b +OP_GMTrainSkill=0x2a85 +OP_Animation=0x7177 +OP_Begging=0x6703 +OP_Consent=0x1fd1 +OP_ConsentDeny=0x7a45 +OP_AutoFire=0x241e +OP_PetCommands=0x0159 +OP_DeleteSpell=0x52e5 +OP_Surname=0x0423 +OP_ClearSurname=0x7d23 +OP_FaceChange=0x5578 +OP_SenseHeading=0x217c +OP_Action=0x744c +OP_ConsiderCorpse=0x5204 +OP_HideCorpse=0x67fc +OP_CorpseDrag=0x7eec +OP_CorpseDrop=0x0904 +OP_Bug=0x73f4 +OP_Feedback=0x5602 +OP_Report=0x1414 +OP_Damage=0x6f15 +OP_ChannelMessage=0x2b2d +OP_Assist=0x4478 +OP_AssistGroup=0x27f8 +OP_MoveCoin=0x0bcf +OP_ZonePlayerToBind=0x0ecb +OP_KeyRing=0x6857 +OP_WhoAllRequest=0x65ab +OP_WhoAllResponse=0x407a +OP_FriendsWho=0x3956 +OP_ConfirmDelete=0x43a3 +OP_Logout=0x4ac6 +OP_Rewind=0x1745 +OP_TargetCommand=0x58e2 +OP_Hide=0x5d55 +OP_Jump=0x2060 +OP_Camp=0x28ec +OP_Emote=0x373b +OP_SetRunMode=0x009f +OP_BankerChange=0x383c +OP_TargetMouse=0x075d +OP_MobHealth=0x2dd3 +OP_InitialMobHealth=0x0000 # Unused? +OP_TargetHoTT=0x486c +OP_XTargetResponse=0x4d59 +OP_XTargetRequest=0x3763 +OP_XTargetAutoAddHaters=0x672f +OP_TargetBuffs=0x4f4b +OP_BuffCreate=0x3377 +OP_BuffRemoveRequest=0x49bc +OP_DeleteSpawn=0x7280 +OP_AutoAttack=0x109d +OP_AutoAttack2=0x3526 +OP_Consume=0x4b70 +OP_MoveItem=0x32ee +OP_DeleteItem=0x18ad +OP_DeleteCharge=0x01b8 +OP_ItemPacket=0x368e +OP_ItemLinkResponse=0x70c0 +OP_ItemLinkClick=0x4cef +OP_ItemPreview=0x7f80 +OP_NewSpawn=0x0b04 +OP_Track=0x17e5 +OP_TrackTarget=0x0029 +OP_TrackUnknown=0x0f83 +OP_ClickDoor=0x3a8f +OP_MoveDoor=0x08e8 +OP_RemoveAllDoors=0x700c +OP_EnvDamage=0x51fd +OP_BoardBoat=0x31e6 +OP_Forage=0x5306 +OP_LeaveBoat=0x4211 +OP_ControlBoat=0x0ae7 +OP_SafeFallSuccess=0x6dbc +OP_RezzComplete=0x760d +OP_RezzRequest=0x3c21 +OP_RezzAnswer=0x701c +OP_Shielding=0x48c1 +OP_RequestDuel=0x3af1 +OP_MobRename=0x2c57 +OP_AugmentItem=0x661b # Was 0x37cb +OP_WeaponEquip1=0x34a7 +OP_WeaponEquip2=0x559a # Was 0x6022 +OP_WeaponUnequip2=0x2d25 # Was 0x0110 +OP_ApplyPoison=0x1b0c +OP_Save=0x4a39 +OP_TestBuff=0x7cb8 # Was 0x3772 +OP_CustomTitles=0x100e +OP_Split=0x0522 +OP_YellForHelp=0x4e56 +OP_LoadSpellSet=0x261d +OP_Bandolier=0x7677 +OP_PotionBelt=0x1a3e # Was 0x4d3b +OP_DuelResponse=0x6a46 +OP_DuelResponse2=0x68d3 +OP_SaveOnZoneReq=0x2c7a +OP_ReadBook=0x72df +OP_Dye=0x23b9 +OP_InterruptCast=0x048c +OP_AAAction=0x424e +OP_LeadershipExpToggle=0x74bd +OP_LeadershipExpUpdate=0x4ee2 +OP_PurchaseLeadershipAA=0x6c55 +OP_UpdateLeadershipAA=0x0026 +OP_MarkNPC=0x5a58 +OP_MarkRaidNPC=0x0000 #unimplemented +OP_ClearNPCMarks=0x0272 +OP_ClearRaidNPCMarks=0x0000 #unimplemented +OP_DelegateAbility=0x4c9d +OP_SetGroupTarget=0x026 +OP_Charm=0x5d92 +OP_Stun=0x36a4 +OP_SendFindableNPCs=0x587e +OP_FindPersonRequest=0x5cea +OP_FindPersonReply=0x7e58 +OP_Sound=0x1a30 +OP_PetBuffWindow=0x5882 +OP_LevelAppearance=0x3bc9 +OP_Translocate=0x6580 +OP_Sacrifice=0x1821 +OP_PopupResponse=0x214a +OP_OnLevelMessage=0x4fd0 +OP_AugmentInfo=0x0afb +OP_Petition=0x4101 +OP_SomeItemPacketMaybe=0x747c +OP_PVPStats=0x6f4b # Unsure +OP_PVPLeaderBoardRequest=0x3707 +OP_PVPLeaderBoardReply=0x25b7 +OP_PVPLeaderBoardDetailsRequest=0x15a9 +OP_PVPLeaderBoardDetailsReply=0x04aa +OP_RestState=0x000f +OP_RespawnWindow=0x28bc +OP_LDoNButton=0x5327 +OP_SetStartCity=0x6326 # Was 0x2d1b +OP_VoiceMacroIn=0x17fd +OP_VoiceMacroOut=0x409a +OP_ItemViewUnknown=0x465b +OP_VetRewardsAvaliable=0x6773 +OP_VetClaimRequest=0x1126 +OP_VetClaimReply=0x16d4 +OP_DisciplineUpdate=0x759e # Was 0x2f05 +OP_DisciplineTimer=0x6989 # Was 0x5e3f +OP_BecomeCorpse=0x0000 # Unused? +OP_Action2=0x0000 # Unused? +OP_MobUpdate=0x2c84 +OP_NPCMoveUpdate=0x5892 +OP_CameraEffect=0x127f +OP_SpellEffect=0x5936 +OP_RemoveNimbusEffect=0xc693 +OP_AltCurrency=0x3ee6 +OP_AltCurrencyMerchantRequest=0x05f0 +OP_AltCurrencyMerchantReply=0x0165 +OP_AltCurrencyPurchase=0x6b6d +OP_AltCurrencySell=0x61cb +OP_AltCurrencySellSelection=0x74ec +OP_AltCurrencyReclaim=0x27a2 +OP_CrystalCountUpdate=0x467f # Was 0x3f60 +OP_CrystalCreate=0x7aee # Was 0x5a82 +OP_CrystalReclaim=0x2439 # Was 0x7616 +OP_Untargetable=0x1456 +OP_IncreaseStats=0x70a3 +OP_Weblink=0x18d3 +#OP_OpenInventory=0x0000 # Likely does not exist in RoF -U +OP_OpenContainer=0x0000 +OP_Marquee=0x0000 + +OP_DzQuit=0xb2e3 +OP_DzListTimers=0x7b68 +OP_DzAddPlayer=0x4701 +OP_DzRemovePlayer=0x1abc +OP_DzSwapPlayer=0x405b +OP_DzMakeLeader=0x543d +OP_DzPlayerList=0x14c6 +OP_DzJoinExpeditionConfirm=0x7f4b +OP_DzJoinExpeditionReply=0x1950 +OP_DzExpeditionInfo=0x9119 +OP_DzExpeditionList=0x205f +OP_DzMemberStatus=0x32f0 +OP_DzLeaderStatus=0x3de9 +OP_DzExpeditionEndsWarning=0x5189 +OP_DzMemberList=0x0000 +OP_DzCompass=0x3e0e # Was 0x4f09 +OP_DzChooseZone=0x0000 # Maybe 0x29d6 + +# New Opcodes +OP_SpawnPositionUpdate=0x0000 # Actually OP_MobUpdate ? +OP_ManaUpdate=0x3791 +OP_EnduranceUpdate=0x5f42 +OP_MobManaUpdate=0x2925 +OP_MobEnduranceUpdate=0x7314 + +# Mercenary Opcodes +OP_MercenaryDataUpdateRequest=0x2bcb +OP_MercenaryDataUpdate=0x7b89 +OP_MercenaryDataRequest=0x61a4 +OP_MercenaryDataResponse=0x11c1 +OP_MercenaryHire=0x72ce +OP_MercenaryDismiss=0x7169 +OP_MercenaryTimerRequest=0x6e83 +OP_MercenaryTimer=0x31e4 +OP_MercenaryUnknown1=0x20b9 +OP_MercenaryCommand=0x1b37 +OP_MercenarySuspendRequest=0x6e9f +OP_MercenarySuspendResponse=0x4407 +OP_MercenaryUnsuspendResponse=0x6f03 + +# Looting +OP_LootRequest=0x0adf +OP_EndLootRequest=0x30f7 +OP_LootItem=0x4dc9 +OP_LootComplete=0x55c4 + +# bazaar trader stuff: +OP_BazaarSearch=0x39d6 +OP_TraderDelItem=0x0000 +OP_BecomeTrader=0x61b3 +OP_TraderShop=0x5eca +OP_Trader=0x4ef5 # Was 0x6790 +OP_TraderBuy=0x0000 +OP_Barter=0x243a +OP_ShopItem=0x0000 +OP_BazaarInspect=0x0000 +OP_Bazaar=0x0000 +OP_TraderItemUpdate=0x0000 + +# pc/npc trading +OP_TradeRequest=0x77b5 +OP_TradeAcceptClick=0x69e2 +OP_TradeRequestAck=0x14bf +OP_TradeCoins=0x4206 +OP_FinishTrade=0x3993 +OP_CancelTrade=0x354c +OP_TradeMoneyUpdate=0x68c2 +OP_MoneyUpdate=0x640c +OP_TradeBusy=0x5505 + +# Sent after canceling trade or after closing tradeskill object +OP_FinishWindow=0x7349 +OP_FinishWindow2=0x40ef + +# Sent on Live for what seems to be item existance verification +# Ex. Before Right Click Effect happens from items +OP_ItemVerifyRequest=0x189c +OP_ItemVerifyReply=0x097b + +# merchant stuff +OP_ShopPlayerSell=0x3de3 +OP_ShopRequest=0x4fed +OP_ShopEnd=0x30a8 +OP_ShopEndConfirm=0x3196 +OP_ShopPlayerBuy=0x004c +OP_ShopDelItem=0x724f + +# tradeskill stuff: +OP_ClickObject=0x4aa1 +OP_ClickObjectAction=0x0c1e +OP_ClearObject=0x7a11 +OP_RecipeDetails=0x6e02 +OP_RecipesFavorite=0x5c74 +OP_RecipesSearch=0x6290 +OP_RecipeReply=0x1db6 +OP_RecipeAutoCombine=0x40d7 +OP_TradeSkillCombine=0x579a + +# Tribute Packets: +OP_OpenGuildTributeMaster=0x49ea +OP_OpenTributeMaster=0x7666 # Was 0x40f5 +OP_SelectTribute=0x79fc +OP_TributeItem=0x4f3e +OP_TributeMoney=0x58fb # Was 0x6fed +OP_TributeToggle=0x4e32 +OP_TributePointUpdate=0x5300 +OP_TributeNPC=0x0000 +OP_GuildTributeInfo=0x0000 +OP_OpenTributeReply=0x0000 +OP_GuildTributeStatus=0x0000 + +# Adventure packets: +OP_LeaveAdventure=0x5d18 +OP_AdventureFinish=0x400f +OP_AdventureInfoRequest=0x3cb0 +OP_AdventureInfo=0x4c54 +OP_AdventureRequest=0x2c6c +OP_AdventureDetails=0x5648 +OP_AdventureData=0x7171 +OP_AdventureUpdate=0x1b01 +OP_AdventureMerchantRequest=0x6922 # Was 654d +OP_AdventureMerchantResponse=0x3e47 # Was 7949 +OP_AdventureMerchantPurchase=0x5b72 # Was 155a +OP_AdventureMerchantSell=0x2f9b # Was 389c +OP_AdventurePointsUpdate=0x65c3 # Was 7589 +OP_AdventureStatsRequest=0x5a62 +OP_AdventureStatsReply=0x2370 +OP_AdventureLeaderboardRequest=0x7093 +OP_AdventureLeaderboardReply=0x7f79 + +# Group Opcodes +OP_GroupDisband=0x4c10 +OP_GroupInvite=0x1649 +OP_GroupFollow=0x05ce +OP_GroupUpdate=0x4ced +OP_GroupUpdateB=0x0cbc +OP_GroupCancelInvite=0x0000 +OP_GroupAcknowledge=0x04d0 +OP_GroupDelete=0x3abb +OP_CancelInvite=0x32c2 +OP_GroupFollow2=0x2a50 +OP_GroupInvite2=0x6c65 +OP_GroupDisbandYou=0x7323 +OP_GroupDisbandOther=0x88a1 +OP_GroupLeaderChange=0x74da +OP_GroupRoles=0x4d9f +OP_GroupMakeLeader=0x4229 +OP_DoGroupLeadershipAbility=0x6eae +OP_GroupLeadershipAAUpdate=0x6298 +OP_GroupMentor=0x3342 +OP_InspectBuffs=0x0000 + +# LFG/LFP Opcodes +OP_LFGCommand=0x6060 +OP_LFGGetMatchesRequest=0x0340 +OP_LFGGetMatchesResponse=0x5048 +OP_LFPGetMatchesRequest=0x4d7d +OP_LFPGetMatchesResponse=0x22c6 +OP_LFPCommand=0x49a9 +OP_LFGAppearance=0x0000 +OP_LFGResponse=0x0000 + +# Raid Opcodes +OP_RaidInvite=0x3973 +OP_RaidUpdate=0x56fe +OP_RaidJoin=0x0000 + +# Button-push commands +OP_Taunt=0x2703 +OP_CombatAbility=0x3eba +OP_SenseTraps=0x02af # Was 0x2ee0 +OP_PickPocket=0x39e8 +OP_DisarmTraps=0x0000 +OP_Disarm=0x5ec8 +OP_Sneak=0x4577 +OP_Fishing=0x1e2a +OP_InstillDoubt=0x640e +OP_FeignDeath=0x52fa +OP_Mend=0x0ecf +OP_Bind_Wound=0x0386 +OP_LDoNOpen=0x3d5c + +# Task packets +OP_TaskDescription=0x0083 +OP_TaskActivity=0x083 +OP_CompletedTasks=0x4eba +OP_TaskActivityComplete=0x7037 +OP_AcceptNewTask=0x0a23 +OP_CancelTask=0x3714 +OP_TaskMemberList=0x5727 # Was 0x1656 +OP_OpenNewTasksWindow=0x48a2 # Was 0x11de +OP_AvaliableTask=0x36e8 # Was 0x2377 +OP_TaskHistoryRequest=0x5f1c +OP_TaskHistoryReply=0x3d05 +OP_DeclineAllTasks=0x0000 + +# Title opcodes +OP_NewTitlesAvailable=0x0d32 +OP_RequestTitles=0x6344 +OP_SendTitleList=0x2d08 +OP_SetTitle=0x6527 +OP_SetTitleReply=0x4c21 + +# mail opcodes +OP_Command=0x0000 +OP_MailboxHeader=0x0000 +OP_MailHeader=0x0000 +OP_MailBody=0x0000 +OP_NewMail=0x0000 +OP_SentConfirm=0x0000 + +########### Below this point should not be needed ########### + +# This section are all unknown in Titanium +OP_ForceFindPerson=0x0000 +OP_LocInfo=0x0000 +OP_ReloadUI=0x0000 +OP_ItemName=0x0000 +OP_ItemLinkText=0x0000 +OP_MultiLineMsg=0x0000 +OP_MendHPUpdate=0x0000 +OP_TargetReject=0x0000 +OP_SafePoint=0x0000 +OP_ApproveZone=0x0000 +OP_ZoneComplete=0x0000 +OP_ClientError=0x0000 +OP_DumpName=0x0000 +OP_Heartbeat=0x0000 +OP_CrashDump=0x0000 +OP_LoginComplete=0x0000 + +# discovered opcodes not yet used: +OP_PickLockSuccess=0x0000 +OP_PlayMP3=0x0000 +OP_ReclaimCrystals=0x0000 +OP_DynamicWall=0x0000 +OP_OpenDiscordMerchant=0x0000 +OP_DiscordMerchantInventory=0x0000 +OP_GiveMoney=0x0000 +OP_RequestKnowledgeBase=0x0000 +OP_KnowledgeBase=0x0000 +OP_SlashAdventure=0x0000 # /adventure +OP_BecomePVPPrompt=0x0000 +OP_MoveLogRequest=0x0000 # gone I think +OP_MoveLogDisregard=0x0000 # gone I think + +# named unknowns, to make looking for real unknown easier +OP_AnnoyingZoneUnknown=0x0000 +OP_Some6ByteHPUpdate=0x0000 seems to happen when you target group members +OP_QueryResponseThing=0x0000 + + +# realityincarnate: these are just here to stop annoying several thousand byte packet dumps +#OP_LoginUnknown1=0x46d3 # OP_SendSpellChecksum +#OP_LoginUnknown2=0x040b # OP_SendSkillCapsChecksum + +# Petition Opcodes +OP_PetitionSearch=0x0000 search term for petition +OP_PetitionSearchResults=0x0000 (list of?) matches from search +OP_PetitionSearchText=0x0000 text results of search + +OP_PetitionUpdate=0x0000 +OP_PetitionCheckout=0x0000 +OP_PetitionCheckIn=0x0000 +OP_PetitionQue=0x0000 +OP_PetitionUnCheckout=0x0000 +OP_PetitionDelete=0x0000 +OP_DeletePetition=0x0000 +OP_PetitionResolve=0x0000 +OP_PDeletePetition=0x0000 +OP_PetitionBug=0x0000 +OP_PetitionRefresh=0x0000 +OP_PetitionCheckout2=0x0000 +OP_PetitionViewPetition=0x0000 + +# Login opcodes +OP_SessionReady=0x0000 +OP_Login=0x0000 +OP_ServerListRequest=0x0000 +OP_PlayEverquestRequest=0x0000 +OP_PlayEverquestResponse=0x0000 +OP_ChatMessage=0x0000 +OP_LoginAccepted=0x0000 +OP_ServerListResponse=0x0000 +OP_Poll=0x0000 +OP_EnterChat=0x0000 +OP_PollResponse=0x0000 + +# raw opcodes +OP_RAWSessionRequest=0x0000 +OP_RAWSessionResponse=0x0000 +OP_RAWCombined=0x0000 +OP_RAWSessionDisconnect=0x0000 +OP_RAWKeepAlive=0x0000 +OP_RAWSessionStatRequest=0x0000 +OP_RAWSessionStatResponse=0x0000 +OP_RAWPacket=0x0000 +OP_RAWFragment=0x0000 +OP_RAWOutOfOrderAck=0x0000 +OP_RAWAck=0x0000 +OP_RAWAppCombined=0x0000 +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 + +OP_ItemRecastDelay=0x08a6 diff --git a/utils/sql/db_update_manifest.txt b/utils/sql/db_update_manifest.txt index 721beb002..2af5e6e90 100644 --- a/utils/sql/db_update_manifest.txt +++ b/utils/sql/db_update_manifest.txt @@ -311,6 +311,8 @@ 9055|2014_10_30_special_abilities_null.sql|SHOW COLUMNS FROM `npc_types` LIKE 'special_abilities'|contains|NO 9056|2014_11_08_RaidMembers.sql|SHOW COLUMNS FROM `raid_members` LIKE 'groupid'|missing|unsigned 9057|2014_11_13_spells_new_updates.sql|SHOW COLUMNS FROM `spells_new` LIKE 'disallow_sit'|empty| +9058|2014_11_26_InventoryTableUpdate.sql|SHOW COLUMNS FROM `inventory` LIKE 'ornamenticon'|empty| +9059|2014_12_01_mercs_table_update.sql|SHOW COLUMNS FROM `mercs` LIKE 'MercSize'|empty| # Upgrade conditions: # This won't be needed after this system is implemented, but it is used database that are not @@ -329,4 +331,4 @@ # not_empty = If the query is not empty # 4 = Text to match # -# \ No newline at end of file +# diff --git a/utils/sql/git/optional/2014_11_24_EnableMeritBasedFaction.sql b/utils/sql/git/optional/2014_11_24_EnableMeritBasedFaction.sql index 67e1b1888..942b016ab 100644 --- a/utils/sql/git/optional/2014_11_24_EnableMeritBasedFaction.sql +++ b/utils/sql/git/optional/2014_11_24_EnableMeritBasedFaction.sql @@ -1 +1 @@ -INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'NPC:EnableMeritBasedFaction', 'false', 'If set to true, faction will given in the same way as experience (solo/group/raid).'); \ No newline at end of file +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'NPC:EnableMeritBasedFaction', 'false', 'If set to true, faction will be given in the same way as experience (solo/group/raid).'); \ No newline at end of file diff --git a/utils/sql/git/optional/2014_11_26_TransformationRules.sql b/utils/sql/git/optional/2014_11_26_TransformationRules.sql new file mode 100644 index 000000000..d86e6811d --- /dev/null +++ b/utils/sql/git/optional/2014_11_26_TransformationRules.sql @@ -0,0 +1,3 @@ +/* Optional Transformation Rules */ +INSERT INTO `rule_values` VALUES (1, 'Inventory:DeleteTransformationMold', 'true', 'false to keep transformation mold forever'); +INSERT INTO `rule_values` VALUES (1, 'Inventory:AllowAnyWeaponTransformation', 'false', 'True allows any MELEE weapon to use the other melee type transformatios'); diff --git a/utils/sql/git/optional/2014_11_27_ProjectileDmgOnImpact.sql b/utils/sql/git/optional/2014_11_27_ProjectileDmgOnImpact.sql new file mode 100644 index 000000000..ce7e16905 --- /dev/null +++ b/utils/sql/git/optional/2014_11_27_ProjectileDmgOnImpact.sql @@ -0,0 +1 @@ +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Combat:ProjectileDmgOnImpact', 'true', 'If enabled, projectiles (ie arrows) will hit on impact, instead of instantly.'); diff --git a/utils/sql/git/required/2014_11_26_InventoryTableUpdate.sql b/utils/sql/git/required/2014_11_26_InventoryTableUpdate.sql new file mode 100644 index 000000000..27a6d6c37 --- /dev/null +++ b/utils/sql/git/required/2014_11_26_InventoryTableUpdate.sql @@ -0,0 +1,4 @@ +-- Inventory table update +ALTER TABLE `inventory` + ADD COLUMN `ornamenticon` INT(11) UNSIGNED NOT NULL DEFAULT '0' AFTER `custom_data`, + ADD COLUMN `ornamentidfile` INT(11) UNSIGNED NOT NULL DEFAULT '0' AFTER `ornamenticon`; diff --git a/utils/sql/git/required/2014_12_01_mercs_table_update.sql b/utils/sql/git/required/2014_12_01_mercs_table_update.sql new file mode 100644 index 000000000..e5dc895f9 --- /dev/null +++ b/utils/sql/git/required/2014_12_01_mercs_table_update.sql @@ -0,0 +1 @@ +ALTER TABLE `mercs` ADD `MercSize` float( 0 ) NOT NULL DEFAULT '5' AFTER `Gender`; diff --git a/world/adventure.cpp b/world/adventure.cpp index ffb64779c..b6db3d6d0 100644 --- a/world/adventure.cpp +++ b/world/adventure.cpp @@ -4,6 +4,7 @@ #include "../common/rulesys.h" #include "../common/misc_functions.h" #include "../common/string_util.h" +#include "../common/random.h" #include "adventure.h" #include "adventure_manager.h" #include "worlddb.h" @@ -14,6 +15,7 @@ extern ZSList zoneserver_list; extern ClientList client_list; extern AdventureManager adventure_manager; +extern EQEmu::Random emu_random; Adventure::Adventure(AdventureTemplate *t) { @@ -54,10 +56,10 @@ void Adventure::AddPlayer(std::string character_name, bool add_client_to_instanc { if(!PlayerExists(character_name)) { - int client_id = database.GetCharacterID(character_name.c_str()); - if(add_client_to_instance) + int32 character_id = database.GetCharacterID(character_name.c_str()); + if(character_id && add_client_to_instance) { - database.AddClientToInstance(instance_id, client_id); + database.AddClientToInstance(instance_id, character_id); } players.push_back(character_name); } @@ -70,8 +72,12 @@ void Adventure::RemovePlayer(std::string character_name) { if((*iter).compare(character_name) == 0) { - database.RemoveClientFromInstance(instance_id, database.GetCharacterID(character_name.c_str())); - players.erase(iter); + int32 character_id = database.GetCharacterID(character_name.c_str()); + if (character_id) + { + database.RemoveClientFromInstance(instance_id, character_id); + players.erase(iter); + } return; } ++iter; @@ -388,8 +394,8 @@ void Adventure::MoveCorpsesToGraveyard() for (auto iter = dbid_list.begin(); iter != dbid_list.end(); ++iter) { - float x = GetTemplate()->graveyard_x + MakeRandomFloat(-GetTemplate()->graveyard_radius, GetTemplate()->graveyard_radius); - float y = GetTemplate()->graveyard_y + MakeRandomFloat(-GetTemplate()->graveyard_radius, GetTemplate()->graveyard_radius); + float x = GetTemplate()->graveyard_x + emu_random.Real(-GetTemplate()->graveyard_radius, GetTemplate()->graveyard_radius); + float y = GetTemplate()->graveyard_y + emu_random.Real(-GetTemplate()->graveyard_radius, GetTemplate()->graveyard_radius); float z = GetTemplate()->graveyard_z; query = StringFormat("UPDATE character_corpses " diff --git a/world/adventure_manager.cpp b/world/adventure_manager.cpp index aeee8e3c5..47ea42c51 100644 --- a/world/adventure_manager.cpp +++ b/world/adventure_manager.cpp @@ -3,6 +3,7 @@ #include "../common/string_util.h" #include "../common/servertalk.h" #include "../common/rulesys.h" +#include "../common/random.h" #include "adventure.h" #include "adventure_manager.h" #include "worlddb.h" @@ -14,6 +15,7 @@ extern ZSList zoneserver_list; extern ClientList client_list; +extern EQEmu::Random emu_random; AdventureManager::AdventureManager() { @@ -325,7 +327,7 @@ void AdventureManager::CalculateAdventureRequestReply(const char *data) if(eligible_adventures.size() > 0) { ea_iter = eligible_adventures.begin(); - int c_index = MakeRandomInt(0, (eligible_adventures.size()-1)); + int c_index = emu_random.Int(0, (eligible_adventures.size()-1)); for(int i = 0; i < c_index; ++i) { ++ea_iter; diff --git a/world/client.cpp b/world/client.cpp index f3f1a78c8..0f9b92abf 100644 --- a/world/client.cpp +++ b/world/client.cpp @@ -15,6 +15,7 @@ #include "../common/extprofile.h" #include "../common/string_util.h" #include "../common/clientversions.h" +#include "../common/random.h" #include "client.h" #include "worlddb.h" @@ -61,6 +62,7 @@ std::vector character_create_race_class_combos; extern ZSList zoneserver_list; extern LoginServerList loginserverlist; extern ClientList client_list; +extern EQEmu::Random emu_random; extern uint32 numclients; extern volatile bool RunLoops; @@ -519,7 +521,7 @@ bool Client::HandleGenerateRandomNamePacket(const EQApplicationPacket *app) { char cons[48]="bcdfghjklmnpqrstvwxzybcdgklmnprstvwbcdgkpstrkd"; char rndname[17]="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; char paircons[33]="ngrkndstshthphsktrdrbrgrfrclcr"; - int rndnum=MakeRandomInt(0, 75),n=1; + int rndnum=emu_random.Int(0, 75),n=1; bool dlc=false; bool vwl=false; bool dbl=false; @@ -540,18 +542,18 @@ bool Client::HandleGenerateRandomNamePacket(const EQApplicationPacket *app) { rndname[0]=vowels[rndnum]; vwl=true; } - int namlen=MakeRandomInt(5, 10); + int namlen=emu_random.Int(5, 10); for (int i=n;i46) { // pick a cons pair if (i>namlen-3) // last 2 chars in name? { // name can only end in cons pair "rk" "st" "sh" "th" "ph" "sk" "nd" or "ng" - rndnum=MakeRandomInt(0, 7)*2; + rndnum=emu_random.Int(0, 7)*2; } else { // pick any from the set @@ -569,12 +571,12 @@ bool Client::HandleGenerateRandomNamePacket(const EQApplicationPacket *app) { } else { // select a vowel - rndname[i]=vowels[MakeRandomInt(0, 16)]; + rndname[i]=vowels[emu_random.Int(0, 16)]; } vwl=!vwl; if (!dbl && !dlc) { // one chance at double letters in name - if (!MakeRandomInt(0, i+9)) // chances decrease towards end of name + if (!emu_random.Int(0, i+9)) // chances decrease towards end of name { rndname[i+1]=rndname[i]; dbl=true; @@ -831,7 +833,7 @@ bool Client::HandleEnterWorldPacket(const EQApplicationPacket *app) { QueuePacket(outapp); safe_delete(outapp); - int MailKey = MakeRandomInt(1, INT_MAX); + int MailKey = emu_random.Int(1, INT_MAX); database.SetMailKey(charid, GetIP(), MailKey); @@ -1242,8 +1244,8 @@ void Client::ZoneUnavail() { bool Client::GenPassKey(char* key) { char* passKey=nullptr; - *passKey += ((char)('A'+((int)MakeRandomInt(0, 25)))); - *passKey += ((char)('A'+((int)MakeRandomInt(0, 25)))); + *passKey += ((char)('A'+((int)emu_random.Int(0, 25)))); + *passKey += ((char)('A'+((int)emu_random.Int(0, 25)))); memcpy(key, passKey, strlen(passKey)); return true; } diff --git a/world/net.cpp b/world/net.cpp index ee1158520..44107e39b 100644 --- a/world/net.cpp +++ b/world/net.cpp @@ -69,6 +69,7 @@ #include "../common/emu_tcp_server.h" #include "../common/patches/patches.h" +#include "../common/random.h" #include "zoneserver.h" #include "console.h" #include "login_server.h" @@ -97,6 +98,7 @@ UCSConnection UCSLink; QueryServConnection QSLink; LauncherList launcher_list; AdventureManager adventure_manager; +EQEmu::Random emu_random; volatile bool RunLoops = true; uint32 numclients = 0; uint32 numzones = 0; diff --git a/world/worlddb.cpp b/world/worlddb.cpp index cd01e01b2..458025349 100644 --- a/world/worlddb.cpp +++ b/world/worlddb.cpp @@ -197,6 +197,9 @@ void WorldDatabase::GetCharSelectInfo(uint32 account_id, CharacterSelect_Struct* if (item->GetOrnamentationAug(ornamentationAugtype)) { idfile = atoi(&item->GetOrnamentationAug(ornamentationAugtype)->GetItem()->IDFile[2]); } + else if (item->GetOrnamentationIcon() && item->GetOrnamentationIDFile()) { + idfile = item->GetOrnamentationIDFile(); + } else { idfile = atoi(&item->GetItem()->IDFile[2]); } diff --git a/world/zonelist.cpp b/world/zonelist.cpp index c97010aa8..c6c1043f5 100644 --- a/world/zonelist.cpp +++ b/world/zonelist.cpp @@ -24,10 +24,12 @@ #include "world_config.h" #include "../common/servertalk.h" #include "../common/string_util.h" +#include "../common/random.h" extern uint32 numzones; extern bool holdzones; extern ConsoleList console_list; +extern EQEmu::Random emu_random; void CatchSignal(int sig_num); ZSList::ZSList() @@ -565,7 +567,7 @@ void ZSList::RebootZone(const char* ip1,uint16 port,const char* ip2, uint32 skip safe_delete(tmp); return; } - uint32 z = MakeRandomInt(0, y-1); + uint32 z = emu_random.Int(0, y-1); ServerPacket* pack = new ServerPacket(ServerOP_ZoneReboot, sizeof(ServerZoneReboot_Struct)); ServerZoneReboot_Struct* s = (ServerZoneReboot_Struct*) pack->pBuffer; diff --git a/zone/aa.cpp b/zone/aa.cpp index 87b997cca..cf3b8c1c6 100644 --- a/zone/aa.cpp +++ b/zone/aa.cpp @@ -16,84 +16,34 @@ Copyright (C) 2001-2004 EQEMu Development Team (http://eqemulator.net) Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -// Test 1 - -#include - -#include "../common/debug.h" -#include "aa.h" -#include "mob.h" -#include "client.h" -#include "groups.h" -#include "raids.h" -#include "../common/spdat.h" -#include "object.h" -#include "doors.h" -#include "beacon.h" -#include "corpse.h" -#include "titles.h" -#include "../common/races.h" #include "../common/classes.h" +#include "../common/debug.h" #include "../common/eq_packet_structs.h" #include "../common/packet_dump.h" +#include "../common/races.h" +#include "../common/spdat.h" #include "../common/string_util.h" -#include "../common/logsys.h" -#include "zonedb.h" -#include "string_ids.h" + +#include "aa.h" +#include "client.h" +#include "corpse.h" +#include "groups.h" +#include "mob.h" #include "queryserv.h" +#include "raids.h" +#include "string_ids.h" +#include "titles.h" +#include "zonedb.h" extern QueryServ* QServ; -//static data arrays, really not big enough to warrant shared mem. + AA_DBAction AA_Actions[aaHighestID][MAX_AA_ACTION_RANKS]; //[aaid][rank] std::mapaas_send; std::map > aa_effects; //stores the effects from the aa_effects table in memory std::map AARequiredLevelAndCost; -/* - -Schema: - -spell_id is spell to cast, SPELL_UNKNOWN == no spell -nonspell_action is action to preform on activation which is not a spell, 0=none -nonspell_mana is mana that the nonspell action consumes -nonspell_duration is a duration which may be used by the nonspell action -redux_aa is the aa which reduces the reuse timer of the skill -redux_rate is the multiplier of redux_aa, as a percentage of total rate (10 == 10% faster) - -CREATE TABLE aa_actions ( - aaid mediumint unsigned not null, - rank tinyint unsigned not null, - reuse_time mediumint unsigned not null, - spell_id mediumint unsigned not null, - target tinyint unsigned not null, - nonspell_action tinyint unsigned not null, - nonspell_mana mediumint unsigned not null, - nonspell_duration mediumint unsigned not null, - redux_aa mediumint unsigned not null, - redux_rate tinyint not null, - - PRIMARY KEY(aaid, rank) -); - -CREATE TABLE aa_swarmpets ( - spell_id mediumint unsigned not null, - count tinyint unsigned not null, - npc_id int not null, - duration mediumint unsigned not null, - PRIMARY KEY(spell_id) -); -*/ - -/* - -Credits for this function: - -FatherNitwit: Structure and mechanism - -Wiz: Initial set of AAs, original function contents - -Branks: Much updated info and a bunch of higher-numbered AAs - -*/ int Client::GetAATimerID(aaID activate) { SendAA_Struct* aa2 = zone->FindAA(activate); diff --git a/zone/aa.h b/zone/aa.h index b143aae9a..00f74e8c6 100644 --- a/zone/aa.h +++ b/zone/aa.h @@ -2,7 +2,8 @@ #ifndef AA_H #define AA_H -#include "../common/eq_packet_structs.h" +struct AA_Ability; +struct SendAA_Struct; #define MANA_BURN 664 diff --git a/zone/aggro.cpp b/zone/aggro.cpp index 8f5da2eaa..a904b9707 100644 --- a/zone/aggro.cpp +++ b/zone/aggro.cpp @@ -329,7 +329,7 @@ bool Mob::CheckWillAggro(Mob *mob) { || ( fv == FACTION_THREATENLY - && MakeRandomInt(0,99) < THREATENLY_ARRGO_CHANCE - heroicCHA_mod + && zone->random.Roll(THREATENLY_ARRGO_CHANCE - heroicCHA_mod) ) ) ) @@ -1254,7 +1254,7 @@ bool Mob::PassCharismaCheck(Mob* caster, Mob* spellTarget, uint16 spell_id) { return true; //1: The mob has a default 25% chance of being allowed a resistance check against the charm. - if (MakeRandomInt(0, 99) > RuleI(Spells, CharmBreakCheckChance)) + if (zone->random.Int(0, 99) > RuleI(Spells, CharmBreakCheckChance)) return true; if (RuleB(Spells, CharismaCharmDuration)) @@ -1273,7 +1273,7 @@ bool Mob::PassCharismaCheck(Mob* caster, Mob* spellTarget, uint16 spell_id) { //3: At maxed ability, Total Domination has a 50% chance of preventing the charm break that otherwise would have occurred. int16 TotalDominationBonus = caster->aabonuses.CharmBreakChance + caster->spellbonuses.CharmBreakChance + caster->itembonuses.CharmBreakChance; - if (MakeRandomInt(0, 99) < TotalDominationBonus) + if (zone->random.Int(0, 99) < TotalDominationBonus) return true; } diff --git a/zone/attack.cpp b/zone/attack.cpp index 271a94f53..d61c3e888 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -21,28 +21,22 @@ #endif #include "../common/debug.h" -#include -#include -#include -#include -#include -#include - -#include "masterentity.h" -#include "npc_ai.h" -#include "../common/packet_dump.h" -#include "../common/eq_packet_structs.h" #include "../common/eq_constants.h" +#include "../common/eq_packet_structs.h" +#include "../common/rulesys.h" #include "../common/skills.h" #include "../common/spdat.h" -#include "zone.h" -#include "string_ids.h" #include "../common/string_util.h" -#include "../common/rulesys.h" +#include "queryserv.h" #include "quest_parser_collection.h" +#include "string_ids.h" #include "water_map.h" #include "worldserver.h" -#include "queryserv.h" +#include "zone.h" + +#include +#include +#include extern QueryServ* QServ; extern WorldServer worldserver; @@ -345,7 +339,7 @@ bool Mob::CheckHitChance(Mob* other, SkillUseTypes skillinuse, int Hand, int16 c // Did we hit? // - float tohit_roll = MakeRandomFloat(0, 100); + float tohit_roll = zone->random.Real(0, 100); mlog(COMBAT__TOHIT, "Final hit chance: %.2f%%. Hit roll %.2f", chancetohit, tohit_roll); @@ -421,7 +415,7 @@ bool Mob::AvoidDamage(Mob* other, int32 &damage, bool CanRiposte) //Live AA - HightenedAwareness int BlockBehindChance = aabonuses.BlockBehind + spellbonuses.BlockBehind + itembonuses.BlockBehind; - if (BlockBehindChance && (BlockBehindChance > MakeRandomInt(1, 100))){ + if (BlockBehindChance && zone->random.Roll(BlockBehindChance)) { bBlockFromRear = true; if (spellbonuses.BlockBehind || itembonuses.BlockBehind) @@ -514,7 +508,7 @@ bool Mob::AvoidDamage(Mob* other, int32 &damage, bool CanRiposte) } if(damage > 0){ - roll = MakeRandomFloat(0,100); + roll = zone->random.Real(0,100); if(roll <= RollTable[0]){ damage = -3; } @@ -680,7 +674,7 @@ void Mob::MeleeMitigation(Mob *attacker, int32 &damage, int32 minhit, ExtraAttac if (acfail>100) acfail=100; } - if (acfail<=0 || MakeRandomInt(0, 100)>acfail) { + if (acfail<=0 || zone->random.Int(0, 100)>acfail) { float acreduction=1; int acrandom=300; if (database.GetVariable("ACreduction", tmp, 9)) @@ -699,7 +693,7 @@ void Mob::MeleeMitigation(Mob *attacker, int32 &damage, int32 minhit, ExtraAttac damage -= (int32) (GetAC() * acreduction/100.0f); } if (acrandom>0) { - damage -= (myac * MakeRandomInt(0, acrandom) / 10000); + damage -= (myac * zone->random.Int(0, acrandom) / 10000); } if (damage<1) damage=1; mlog(COMBAT__DAMAGE, "AC Damage Reduction: fail chance %d%%. Failed. Reduction %.3f%%, random %d. Resulting damage %d.", acfail, acreduction, acrandom, damage); @@ -727,8 +721,8 @@ int32 Mob::GetMeleeMitDmg(Mob *attacker, int32 damage, int32 minhit, float mit_rating, float atk_rating) { float d = 10.0; - float mit_roll = MakeRandomFloat(0, mit_rating); - float atk_roll = MakeRandomFloat(0, atk_rating); + float mit_roll = zone->random.Real(0, mit_rating); + float atk_roll = zone->random.Real(0, atk_rating); if (atk_roll > mit_roll) { float a_diff = atk_roll - mit_roll; @@ -777,8 +771,8 @@ int32 Client::GetMeleeMitDmg(Mob *attacker, int32 damage, int32 minhit, dmg_bonus -= dmg_bonus * (itembonuses.MeleeMitigation / 100.0); dmg_interval -= dmg_interval * spellMeleeMit; - float mit_roll = MakeRandomFloat(0, mit_rating); - float atk_roll = MakeRandomFloat(0, atk_rating); + float mit_roll = zone->random.Real(0, mit_rating); + float atk_roll = zone->random.Real(0, atk_rating); if (atk_roll > mit_roll) { float a_diff = atk_roll - mit_roll; @@ -1278,7 +1272,7 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b if(RuleB(Combat, UseIntervalAC)) damage = max_hit; else - damage = MakeRandomInt(min_hit, max_hit); + damage = zone->random.Int(min_hit, max_hit); damage = mod_client_damage(damage, skillinuse, Hand, weapon, other); @@ -1320,7 +1314,7 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b OffhandRiposteFail *= -1; //Live uses a negative value for this. if (OffhandRiposteFail && - (OffhandRiposteFail > 99 || (MakeRandomInt(0, 100) < OffhandRiposteFail))) { + (OffhandRiposteFail > 99 || zone->random.Roll(OffhandRiposteFail))) { damage = 0; // Counts as a miss slippery_attack = true; } else @@ -1336,7 +1330,7 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b if (((damage < 0) || slippery_attack) && !bRiposte && !IsStrikethrough) { // Hack to still allow Strikethrough chance w/ Slippery Attacks AA int32 bonusStrikeThrough = itembonuses.StrikeThrough + spellbonuses.StrikeThrough + aabonuses.StrikeThrough; - if(bonusStrikeThrough && (MakeRandomInt(0, 100) < bonusStrikeThrough)) { + if(bonusStrikeThrough && zone->random.Roll(bonusStrikeThrough)) { Message_StringID(MT_StrikeThrough, STRIKETHROUGH_STRING); // You strike through your opponents defenses! Attack(other, Hand, false, true); // Strikethrough only gives another attempted hit return false; @@ -1362,11 +1356,8 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b MeleeLifeTap(damage); - if (damage > 0){ - CheckNumHitsRemaining(NUMHIT_OutgoingHitSuccess); - if (HasSkillProcSuccess() && other && other->GetHP() > 0) - TrySkillProc(other, skillinuse, 0, true, Hand); - } + if (damage > 0 && HasSkillProcSuccess() && other && other->GetHP() > 0) + TrySkillProc(other, skillinuse, 0, true, Hand); CommonBreakInvisible(); @@ -1631,14 +1622,14 @@ bool Client::Death(Mob* killerMob, int32 damage, uint16 spell, SkillUseTypes att database.GetVariable("PvPitem", tmp2, 9); int pvpitem = atoi(tmp2); if(pvpitem>0 && pvpitem<200000) - new_corpse->SetPKItem(pvpitem); + new_corpse->SetPlayerKillItemID(pvpitem); } else if(reward==2) - new_corpse->SetPKItem(-1); + new_corpse->SetPlayerKillItemID(-1); else if(reward==1) - new_corpse->SetPKItem(1); + new_corpse->SetPlayerKillItemID(1); else - new_corpse->SetPKItem(0); + new_corpse->SetPlayerKillItemID(0); if(killerMob->CastToClient()->isgrouped) { Group* group = entity_list.GetGroupByClient(killerMob->CastToClient()); if(group != 0) @@ -1647,7 +1638,7 @@ bool Client::Death(Mob* killerMob, int32 damage, uint16 spell, SkillUseTypes att { if(group->members[i] != nullptr) { - new_corpse->AllowMobLoot(group->members[i],i); + new_corpse->AllowPlayerLoot(group->members[i],i); } } } @@ -1852,7 +1843,7 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool if(RuleB(Combat, UseIntervalAC)) damage = (max_dmg+eleBane); else - damage = MakeRandomInt((min_dmg+eleBane),(max_dmg+eleBane)); + damage = zone->random.Int((min_dmg+eleBane),(max_dmg+eleBane)); //check if we're hitting above our max or below it. @@ -2301,13 +2292,13 @@ bool NPC::Death(Mob* killerMob, int32 damage, uint16 spell, SkillUseTypes attack if(killer != 0 && emoteid != 0) corpse->CastToNPC()->DoNPCEmote(AFTERDEATH, emoteid); if(killer != 0 && killer->IsClient()) { - corpse->AllowMobLoot(killer, 0); + corpse->AllowPlayerLoot(killer, 0); if(killer->IsGrouped()) { Group* group = entity_list.GetGroupByClient(killer->CastToClient()); if(group != 0) { for(int i=0;i<6;i++) { // Doesnt work right, needs work if(group->members[i] != nullptr) { - corpse->AllowMobLoot(group->members[i],i); + corpse->AllowPlayerLoot(group->members[i],i); } } } @@ -2323,30 +2314,30 @@ bool NPC::Death(Mob* killerMob, int32 damage, uint16 spell, SkillUseTypes attack case 0: case 1: if(r->members[x].member && r->members[x].IsRaidLeader){ - corpse->AllowMobLoot(r->members[x].member, i); + corpse->AllowPlayerLoot(r->members[x].member, i); i++; } break; case 2: if(r->members[x].member && r->members[x].IsRaidLeader){ - corpse->AllowMobLoot(r->members[x].member, i); + corpse->AllowPlayerLoot(r->members[x].member, i); i++; } else if(r->members[x].member && r->members[x].IsGroupLeader){ - corpse->AllowMobLoot(r->members[x].member, i); + corpse->AllowPlayerLoot(r->members[x].member, i); i++; } break; case 3: if(r->members[x].member && r->members[x].IsLooter){ - corpse->AllowMobLoot(r->members[x].member, i); + corpse->AllowPlayerLoot(r->members[x].member, i); i++; } break; case 4: if(r->members[x].member) { - corpse->AllowMobLoot(r->members[x].member, i); + corpse->AllowPlayerLoot(r->members[x].member, i); i++; } break; @@ -2478,9 +2469,8 @@ void Mob::AddToHateList(Mob* other, int32 hate, int32 damage, bool iYellForHelp, } } - auto otherPosition = xyz_location(other->GetX(), other->GetY(), other->GetZ()); if(IsNPC() && CastToNPC()->IsUnderwaterOnly() && zone->HasWaterMap()) { - if(!zone->watermap->InLiquid(otherPosition)) { + if(!zone->watermap->InLiquid(other->GetPosition())) { return; } } @@ -3448,17 +3438,16 @@ bool Client::CheckDoubleAttack(bool tripleAttack) { chance *= float(100.0f+triple_bonus)/100.0f; //Apply modifiers. } - if((MakeRandomFloat(0, 1) < chance)) + if(zone->random.Roll(chance)) return true; return false; } bool Client::CheckDoubleRangedAttack() { - int32 chance = spellbonuses.DoubleRangedAttack + itembonuses.DoubleRangedAttack + aabonuses.DoubleRangedAttack; - if(chance && (MakeRandomInt(0, 100) < chance)) + if(chance && zone->random.Roll(chance)) return true; return false; @@ -3642,7 +3631,7 @@ void Mob::CommonDamage(Mob* attacker, int32 &damage, const uint16 spell_id, cons } } - if (stun_chance && MakeRandomInt(0, 99) < stun_chance) { + if (stun_chance && zone->random.Roll(stun_chance)) { // Passed stun, try to resist now int stun_resist = itembonuses.StunResist + spellbonuses.StunResist; int frontal_stun_resist = itembonuses.FrontalStunResist + spellbonuses.FrontalStunResist; @@ -3655,18 +3644,18 @@ void Mob::CommonDamage(Mob* attacker, int32 &damage, const uint16 spell_id, cons // frontal stun check for ogres/bonuses if (((GetBaseRace() == OGRE && IsClient()) || - (frontal_stun_resist && MakeRandomInt(0, 99) < frontal_stun_resist)) && + (frontal_stun_resist && zone->random.Roll(frontal_stun_resist))) && !attacker->BehindMob(this, attacker->GetX(), attacker->GetY())) { mlog(COMBAT__HITS, "Frontal stun resisted. %d chance.", frontal_stun_resist); } else { // Normal stun resist check. - if (stun_resist && MakeRandomInt(0, 99) < stun_resist) { + if (stun_resist && zone->random.Roll(stun_resist)) { if (IsClient()) Message_StringID(MT_Stun, SHAKE_OFF_STUN); mlog(COMBAT__HITS, "Stun Resisted. %d chance.", stun_resist); } else { mlog(COMBAT__HITS, "Stunned. %d resist chance.", stun_resist); - Stun(MakeRandomInt(0, 2) * 1000); // 0-2 seconds + Stun(zone->random.Int(0, 2) * 1000); // 0-2 seconds } } } else { @@ -3947,7 +3936,7 @@ void Mob::TryDefensiveProc(const ItemInst* weapon, Mob *on, uint16 hand) { for (int i = 0; i < MAX_PROCS; i++) { if (IsValidSpell(DefensiveProcs[i].spellID)) { float chance = ProcChance * (static_cast(DefensiveProcs[i].chance)/100.0f); - if ((MakeRandomFloat(0, 1) <= chance)) { + if (zone->random.Roll(chance)) { ExecWeaponProc(nullptr, DefensiveProcs[i].spellID, on); CheckNumHitsRemaining(NUMHIT_DefensiveSpellProcs,0,DefensiveProcs[i].base_spellID); } @@ -4009,7 +3998,7 @@ void Mob::TryWeaponProc(const ItemInst *inst, const Item_Struct *weapon, Mob *on if (weapon->Proc.Type == ET_CombatProc) { float WPC = ProcChance * (100.0f + // Proc chance for this weapon static_cast(weapon->ProcRate)) / 100.0f; - if (MakeRandomFloat(0, 1) <= WPC) { // 255 dex = 0.084 chance of proc. No idea what this number should be really. + if (zone->random.Roll(WPC)) { // 255 dex = 0.084 chance of proc. No idea what this number should be really. if (weapon->Proc.Level > ourlevel) { mlog(COMBAT__PROCS, "Tried to proc (%s), but our level (%d) is lower than required (%d)", @@ -4047,7 +4036,7 @@ void Mob::TryWeaponProc(const ItemInst *inst, const Item_Struct *weapon, Mob *on if (aug->Proc.Type == ET_CombatProc) { float APC = ProcChance * (100.0f + // Proc chance for this aug static_cast(aug->ProcRate)) / 100.0f; - if (MakeRandomFloat(0, 1) <= APC) { + if (zone->random.Roll(APC)) { if (aug->Proc.Level > ourlevel) { if (IsPet()) { Mob *own = GetOwner(); @@ -4100,7 +4089,7 @@ void Mob::TrySpellProc(const ItemInst *inst, const Item_Struct *weapon, Mob *on, if (!rangedattk) { // Perma procs (AAs) if (PermaProcs[i].spellID != SPELL_UNKNOWN) { - if (MakeRandomInt(0, 99) < PermaProcs[i].chance) { // TODO: Do these get spell bonus? + if (zone->random.Roll(PermaProcs[i].chance)) { // TODO: Do these get spell bonus? mlog(COMBAT__PROCS, "Permanent proc %d procing spell %d (%d percent chance)", i, PermaProcs[i].spellID, PermaProcs[i].chance); @@ -4115,7 +4104,7 @@ void Mob::TrySpellProc(const ItemInst *inst, const Item_Struct *weapon, Mob *on, // Spell procs (buffs) if (SpellProcs[i].spellID != SPELL_UNKNOWN) { float chance = ProcChance * (static_cast(SpellProcs[i].chance) / 100.0f); - if (MakeRandomFloat(0, 1) <= chance) { + if (zone->random.Roll(chance)) { mlog(COMBAT__PROCS, "Spell proc %d procing spell %d (%.2f percent chance)", i, SpellProcs[i].spellID, chance); @@ -4131,7 +4120,7 @@ void Mob::TrySpellProc(const ItemInst *inst, const Item_Struct *weapon, Mob *on, // ranged spell procs (buffs) if (RangedProcs[i].spellID != SPELL_UNKNOWN) { float chance = ProcChance * (static_cast(RangedProcs[i].chance) / 100.0f); - if (MakeRandomFloat(0, 1) <= chance) { + if (zone->random.Roll(chance)) { mlog(COMBAT__PROCS, "Ranged proc %d procing spell %d (%.2f percent chance)", i, RangedProcs[i].spellID, chance); @@ -4199,7 +4188,7 @@ void Mob::TryPetCriticalHit(Mob *defender, uint16 skill, int32 &damage) critChance /= 100; - if(MakeRandomFloat(0, 1) < critChance) + if(zone->random.Roll(critChance)) { critMod += GetCritDmgMob(skill) * 2; // To account for base crit mod being 200 not 100 damage = (damage * critMod) / 100; @@ -4238,7 +4227,7 @@ void Mob::TryCriticalHit(Mob *defender, uint16 skill, int32 &damage, ExtraAttack int32 SlayRateBonus = aabonuses.SlayUndead[0] + itembonuses.SlayUndead[0] + spellbonuses.SlayUndead[0]; if (SlayRateBonus) { float slayChance = static_cast(SlayRateBonus) / 10000.0f; - if (MakeRandomFloat(0, 1) < slayChance) { + if (zone->random.Roll(slayChance)) { int32 SlayDmgBonus = aabonuses.SlayUndead[1] + itembonuses.SlayUndead[1] + spellbonuses.SlayUndead[1]; damage = (damage * SlayDmgBonus * 2.25) / 100; if (GetGender() == 1) // female @@ -4309,7 +4298,7 @@ void Mob::TryCriticalHit(Mob *defender, uint16 skill, int32 &damage, ExtraAttack critChance /= 100; - if(MakeRandomFloat(0, 1) < critChance) + if(zone->random.Roll(critChance)) { uint32 critMod = 200; bool crip_success = false; @@ -4322,7 +4311,7 @@ void Mob::TryCriticalHit(Mob *defender, uint16 skill, int32 &damage, ExtraAttack if (!IsBerserk() && !IsBerskerSPA) critChance *= float(CripplingBlowChance)/100.0f; - if ((IsBerserk() || IsBerskerSPA) || MakeRandomFloat(0, 1) < critChance) { + if ((IsBerserk() || IsBerskerSPA) || zone->random.Roll(critChance)) { critMod = 400; crip_success = true; } @@ -4332,7 +4321,7 @@ void Mob::TryCriticalHit(Mob *defender, uint16 skill, int32 &damage, ExtraAttack damage = damage * critMod / 100; bool deadlySuccess = false; - if (deadlyChance && MakeRandomFloat(0, 1) < static_cast(deadlyChance) / 100.0f) { + if (deadlyChance && zone->random.Roll(static_cast(deadlyChance) / 100.0f)) { if (BehindMob(defender, GetX(), GetY())) { damage *= deadlyMod; deadlySuccess = true; @@ -4379,7 +4368,7 @@ bool Mob::TryFinishingBlow(Mob *defender, SkillUseTypes skillinuse) //Proc Chance value of 500 = 5% uint32 ProcChance = (aabonuses.FinishingBlow[0] + spellbonuses.FinishingBlow[0] + spellbonuses.FinishingBlow[0])/10; - if(FB_Level && FB_Dmg && (defender->GetLevel() <= FB_Level) && (ProcChance >= MakeRandomInt(0, 1000))){ + if(FB_Level && FB_Dmg && (defender->GetLevel() <= FB_Level) && (ProcChance >= zone->random.Int(0, 1000))){ entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, FINISHING_BLOW, GetName()); DoSpecialAttackDamage(defender, skillinuse, FB_Dmg, 1, -1, 10, false, false); return true; @@ -4406,7 +4395,7 @@ void Mob::DoRiposte(Mob* defender) { defender->itembonuses.DoubleRiposte; //Live AA - Double Riposte - if(DoubleRipChance && (DoubleRipChance >= MakeRandomInt(0, 100))) { + if(DoubleRipChance && zone->random.Roll(DoubleRipChance)) { mlog(COMBAT__ATTACKS, "Preforming a double riposed (%d percent chance)", DoubleRipChance); defender->Attack(this, MainPrimary, true); if (HasDied()) return; @@ -4417,7 +4406,7 @@ void Mob::DoRiposte(Mob* defender) { DoubleRipChance = defender->aabonuses.GiveDoubleRiposte[1]; - if(DoubleRipChance && (DoubleRipChance >= MakeRandomInt(0, 100))) { + if(DoubleRipChance && zone->random.Roll(DoubleRipChance)) { mlog(COMBAT__ATTACKS, "Preforming a return SPECIAL ATTACK (%d percent chance)", DoubleRipChance); if (defender->GetClass() == MONK) @@ -4529,7 +4518,6 @@ void Mob::TrySkillProc(Mob *on, uint16 skill, uint16 ReuseTime, bool Success, ui if (spellbonuses.LimitToSkill[skill]){ for(int e = 0; e < MAX_SKILL_PROCS; e++){ - if (CanProc && (!Success && spellbonuses.SkillProc[e] && IsValidSpell(spellbonuses.SkillProc[e])) || (Success && spellbonuses.SkillProcSuccess[e] && IsValidSpell(spellbonuses.SkillProcSuccess[e]))) { @@ -4548,7 +4536,7 @@ void Mob::TrySkillProc(Mob *on, uint16 skill, uint16 ReuseTime, bool Success, ui if (CanProc && spells[base_spell_id].base[i] == skill && IsValidSpell(proc_spell_id)) { float final_chance = chance * (ProcMod / 100.0f); - if (MakeRandomFloat(0, 1) <= final_chance) { + if (zone->random.Roll(final_chance)) { ExecWeaponProc(nullptr, proc_spell_id, on); CheckNumHitsRemaining(NUMHIT_OffensiveSpellProcs,0, base_spell_id); CanProc = false; @@ -4568,7 +4556,6 @@ void Mob::TrySkillProc(Mob *on, uint16 skill, uint16 ReuseTime, bool Success, ui if (itembonuses.LimitToSkill[skill]){ CanProc = true; for(int e = 0; e < MAX_SKILL_PROCS; e++){ - if (CanProc && (!Success && itembonuses.SkillProc[e] && IsValidSpell(itembonuses.SkillProc[e])) || (Success && itembonuses.SkillProcSuccess[e] && IsValidSpell(itembonuses.SkillProcSuccess[e]))) { @@ -4587,7 +4574,7 @@ void Mob::TrySkillProc(Mob *on, uint16 skill, uint16 ReuseTime, bool Success, ui if (CanProc && spells[base_spell_id].base[i] == skill && IsValidSpell(proc_spell_id)) { float final_chance = chance * (ProcMod / 100.0f); - if (MakeRandomFloat(0, 1) <= final_chance) { + if (zone->random.Roll(final_chance)) { ExecWeaponProc(nullptr, proc_spell_id, on); CanProc = false; break; @@ -4612,7 +4599,6 @@ void Mob::TrySkillProc(Mob *on, uint16 skill, uint16 ReuseTime, bool Success, ui uint32 slot = 0; for(int e = 0; e < MAX_SKILL_PROCS; e++){ - if (CanProc && (!Success && aabonuses.SkillProc[e]) || (Success && aabonuses.SkillProcSuccess[e])){ @@ -4640,7 +4626,7 @@ void Mob::TrySkillProc(Mob *on, uint16 skill, uint16 ReuseTime, bool Success, ui if (CanProc && base1 == skill && IsValidSpell(proc_spell_id)) { float final_chance = chance * (ProcMod / 100.0f); - if (MakeRandomFloat(0, 1) <= final_chance) { + if (zone->random.Roll(final_chance)) { ExecWeaponProc(nullptr, proc_spell_id, on); CanProc = false; break; @@ -4680,26 +4666,25 @@ float Mob::GetSkillProcChances(uint16 ReuseTime, uint16 hand) { bool Mob::TryRootFadeByDamage(int buffslot, Mob* attacker) { - /*Dev Quote 2010: http://forums.station.sony.com/eq/posts/list.m?topic_id=161443 - The Viscid Roots AA does the following: Reduces the chance for root to break by X percent. - There is no distinction of any kind between the caster inflicted damage, or anyone - else's damage. There is also no distinction between Direct and DOT damage in the root code. + /*Dev Quote 2010: http://forums.station.sony.com/eq/posts/list.m?topic_id=161443 + The Viscid Roots AA does the following: Reduces the chance for root to break by X percent. + There is no distinction of any kind between the caster inflicted damage, or anyone + else's damage. There is also no distinction between Direct and DOT damage in the root code. - /* General Mechanics - - Check buffslot to make sure damage from a root does not cancel the root - - If multiple roots on target, always and only checks first root slot and if broken only removes that slots root. - - Only roots on determental spells can be broken by damage. + General Mechanics + - Check buffslot to make sure damage from a root does not cancel the root + - If multiple roots on target, always and only checks first root slot and if broken only removes that slots root. + - Only roots on determental spells can be broken by damage. - Root break chance values obtained from live parses. - */ + */ if (!attacker || !spellbonuses.Root[0] || spellbonuses.Root[1] < 0) - return false; + return false; - if (IsDetrimentalSpell(spellbonuses.Root[1]) && spellbonuses.Root[1] != buffslot){ + if (IsDetrimentalSpell(spellbonuses.Root[1]) && spellbonuses.Root[1] != buffslot){ + int BreakChance = RuleI(Spells, RootBreakFromSpells); - int BreakChance = RuleI(Spells, RootBreakFromSpells); - - BreakChance -= BreakChance*buffs[spellbonuses.Root[1]].RootBreakChance/100; + BreakChance -= BreakChance*buffs[spellbonuses.Root[1]].RootBreakChance/100; int level_diff = attacker->GetLevel() - GetLevel(); //Use baseline if level difference <= 1 (ie. If target is (1) level less than you, or equal or greater level) @@ -4713,10 +4698,10 @@ bool Mob::TryRootFadeByDamage(int buffslot, Mob* attacker) { else if (level_diff > 21) BreakChance = (BreakChance * 20) /100; //Decrease by 80%; - if (BreakChance < 1) - BreakChance = 1; + if (BreakChance < 1) + BreakChance = 1; - if (MakeRandomInt(0, 99) < BreakChance) { + if (zone->random.Roll(BreakChance)) { if (!TryFadeEffect(spellbonuses.Root[1])) { BuffFadeBySlot(spellbonuses.Root[1]); diff --git a/zone/attack.cpp.orig b/zone/attack.cpp.orig new file mode 100644 index 000000000..591be16ad --- /dev/null +++ b/zone/attack.cpp.orig @@ -0,0 +1,5054 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2002 EQEMu Development Team (http://eqemulator.net) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY except by those people which sell it, which + are required to give you total support for your newly bought product; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#if EQDEBUG >= 5 +//#define ATTACK_DEBUG 20 +#endif + +#include "../common/debug.h" +#include "../common/eq_constants.h" +#include "../common/eq_packet_structs.h" +#include "../common/rulesys.h" +#include "../common/skills.h" +#include "../common/spdat.h" +#include "../common/string_util.h" +#include "queryserv.h" +#include "quest_parser_collection.h" +#include "string_ids.h" +#include "water_map.h" +#include "worldserver.h" +#include "zone.h" + +#include +#include +#include + +extern QueryServ* QServ; +extern WorldServer worldserver; + +#ifdef _WINDOWS +#define snprintf _snprintf +#define strncasecmp _strnicmp +#define strcasecmp _stricmp +#endif + +extern EntityList entity_list; +extern Zone* zone; + +bool Mob::AttackAnimation(SkillUseTypes &skillinuse, int Hand, const ItemInst* weapon) +{ + // Determine animation + int type = 0; + if (weapon && weapon->IsType(ItemClassCommon)) { + const Item_Struct* item = weapon->GetItem(); +#if EQDEBUG >= 11 + LogFile->write(EQEMuLog::Debug, "Weapon skill:%i", item->ItemType); +#endif + switch (item->ItemType) + { + case ItemType1HSlash: // 1H Slashing + { + skillinuse = Skill1HSlashing; + type = anim1HWeapon; + break; + } + case ItemType2HSlash: // 2H Slashing + { + skillinuse = Skill2HSlashing; + type = anim2HSlashing; + break; + } + case ItemType1HPiercing: // Piercing + { + skillinuse = Skill1HPiercing; + type = animPiercing; + break; + } + case ItemType1HBlunt: // 1H Blunt + { + skillinuse = Skill1HBlunt; + type = anim1HWeapon; + break; + } + case ItemType2HBlunt: // 2H Blunt + { + skillinuse = Skill2HBlunt; + type = anim2HSlashing; //anim2HWeapon + break; + } + case ItemType2HPiercing: // 2H Piercing + { + skillinuse = Skill1HPiercing; // change to Skill2HPiercing once activated + type = anim2HWeapon; + break; + } + case ItemTypeMartial: + { + skillinuse = SkillHandtoHand; + type = animHand2Hand; + break; + } + default: + { + skillinuse = SkillHandtoHand; + type = animHand2Hand; + break; + } + }// switch + } + else if(IsNPC()) { + + switch (skillinuse) + { + case Skill1HSlashing: // 1H Slashing + { + type = anim1HWeapon; + break; + } + case Skill2HSlashing: // 2H Slashing + { + type = anim2HSlashing; + break; + } + case Skill1HPiercing: // Piercing + { + type = animPiercing; + break; + } + case Skill1HBlunt: // 1H Blunt + { + type = anim1HWeapon; + break; + } + case Skill2HBlunt: // 2H Blunt + { + type = anim2HSlashing; //anim2HWeapon + break; + } + case 99: // 2H Piercing // change to Skill2HPiercing once activated + { + type = anim2HWeapon; + break; + } + case SkillHandtoHand: + { + type = animHand2Hand; + break; + } + default: + { + type = animHand2Hand; + break; + } + }// switch + } + else { + skillinuse = SkillHandtoHand; + type = animHand2Hand; + } + + // If we're attacking with the secondary hand, play the dual wield anim + if (Hand == MainSecondary) // DW anim + type = animDualWield; + + DoAnim(type); + return true; +} + +// called when a mob is attacked, does the checks to see if it's a hit +// and does other mitigation checks. 'this' is the mob being attacked. +bool Mob::CheckHitChance(Mob* other, SkillUseTypes skillinuse, int Hand, int16 chance_mod) +{ +/*/ + //Reworked a lot of this code to achieve better balance at higher levels. + //The old code basically meant that any in high level (50+) combat, + //both parties always had 95% chance to hit the other one. +/*/ + + Mob *attacker=other; + Mob *defender=this; + float chancetohit = RuleR(Combat, BaseHitChance); + + if(attacker->IsNPC() && !attacker->IsPet()) + chancetohit += RuleR(Combat, NPCBonusHitChance); + +#if ATTACK_DEBUG>=11 + LogFile->write(EQEMuLog::Debug, "CheckHitChance(%s) attacked by %s", defender->GetName(), attacker->GetName()); +#endif + mlog(COMBAT__TOHIT,"CheckHitChance(%s) attacked by %s", defender->GetName(), attacker->GetName()); + + bool pvpmode = false; + if(IsClient() && other->IsClient()) + pvpmode = true; + + if (chance_mod >= 10000) + return true; + + float avoidanceBonus = 0; + float hitBonus = 0; + + //////////////////////////////////////////////////////// + // To hit calcs go here + //////////////////////////////////////////////////////// + + uint8 attacker_level = attacker->GetLevel() ? attacker->GetLevel() : 1; + uint8 defender_level = defender->GetLevel() ? defender->GetLevel() : 1; + + //Calculate the level difference + + mlog(COMBAT__TOHIT, "Chance to hit before level diff calc %.2f", chancetohit); + + double level_difference = attacker_level - defender_level; + double range = defender->GetLevel(); + range = ((range / 4) + 3); + + if(level_difference < 0) + { + if(level_difference >= -range) + { + chancetohit += (level_difference / range) * RuleR(Combat,HitFalloffMinor); //5 + } + else if (level_difference >= -(range+3.0)) + { + chancetohit -= RuleR(Combat,HitFalloffMinor); + chancetohit += ((level_difference+range) / (3.0)) * RuleR(Combat,HitFalloffModerate); //7 + } + else + { + chancetohit -= (RuleR(Combat,HitFalloffMinor) + RuleR(Combat,HitFalloffModerate)); + chancetohit += ((level_difference+range+3.0)/12.0) * RuleR(Combat,HitFalloffMajor); //50 + } + } + else + { + chancetohit += (RuleR(Combat,HitBonusPerLevel) * level_difference); + } + + mlog(COMBAT__TOHIT, "Chance to hit after level diff calc %.2f", chancetohit); + + chancetohit -= ((float)defender->GetAGI() * RuleR(Combat, AgiHitFactor)); + + mlog(COMBAT__TOHIT, "Chance to hit after agil calc %.2f", chancetohit); + + if(attacker->IsClient()) + { + chancetohit -= (RuleR(Combat,WeaponSkillFalloff) * (attacker->CastToClient()->MaxSkill(skillinuse) - attacker->GetSkill(skillinuse))); + mlog(COMBAT__TOHIT, "Chance to hit after weapon falloff calc (attack) %.2f", chancetohit); + } + + if(defender->IsClient()) + { + chancetohit += (RuleR(Combat,WeaponSkillFalloff) * (defender->CastToClient()->MaxSkill(SkillDefense) - defender->GetSkill(SkillDefense))); + mlog(COMBAT__TOHIT, "Chance to hit after weapon falloff calc (defense) %.2f", chancetohit); + } + + //I dont think this is 100% correct, but at least it does something... + if(attacker->spellbonuses.MeleeSkillCheckSkill == skillinuse || attacker->spellbonuses.MeleeSkillCheckSkill == 255) { + chancetohit += attacker->spellbonuses.MeleeSkillCheck; + mlog(COMBAT__TOHIT, "Applied spell melee skill bonus %d, yeilding %.2f", attacker->spellbonuses.MeleeSkillCheck, chancetohit); + } + if(attacker->itembonuses.MeleeSkillCheckSkill == skillinuse || attacker->itembonuses.MeleeSkillCheckSkill == 255) { + chancetohit += attacker->itembonuses.MeleeSkillCheck; + mlog(COMBAT__TOHIT, "Applied item melee skill bonus %d, yeilding %.2f", attacker->spellbonuses.MeleeSkillCheck, chancetohit); + } + + //Avoidance Bonuses on defender decreases baseline hit chance by percent. + avoidanceBonus = defender->spellbonuses.AvoidMeleeChanceEffect + + defender->itembonuses.AvoidMeleeChanceEffect + + defender->aabonuses.AvoidMeleeChanceEffect + + (defender->itembonuses.AvoidMeleeChance / 10.0f); //Item Mod 'Avoidence' + + Mob *owner = nullptr; + if (defender->IsPet()) + owner = defender->GetOwner(); + else if ((defender->IsNPC() && defender->CastToNPC()->GetSwarmOwner())) + owner = entity_list.GetMobID(defender->CastToNPC()->GetSwarmOwner()); + + if (owner) + avoidanceBonus += owner->aabonuses.PetAvoidance + owner->spellbonuses.PetAvoidance + owner->itembonuses.PetAvoidance; + + if(defender->IsNPC()) + avoidanceBonus += (defender->CastToNPC()->GetAvoidanceRating() / 10.0f); //Modifier from database + + //Hit Chance Bonuses on attacker increases baseline hit chance by percent. + hitBonus += attacker->itembonuses.HitChanceEffect[skillinuse] + + attacker->spellbonuses.HitChanceEffect[skillinuse]+ + attacker->aabonuses.HitChanceEffect[skillinuse]+ + attacker->itembonuses.HitChanceEffect[HIGHEST_SKILL+1] + + attacker->spellbonuses.HitChanceEffect[HIGHEST_SKILL+1] + + attacker->aabonuses.HitChanceEffect[HIGHEST_SKILL+1]; + + + //Accuracy = Spell Effect , HitChance = 'Accuracy' from Item Effect + //Only AA derived accuracy can be skill limited. ie (Precision of the Pathfinder, Dead Aim) + hitBonus += (attacker->itembonuses.Accuracy[HIGHEST_SKILL+1] + + attacker->spellbonuses.Accuracy[HIGHEST_SKILL+1] + + attacker->aabonuses.Accuracy[HIGHEST_SKILL+1] + + attacker->aabonuses.Accuracy[skillinuse] + + attacker->itembonuses.HitChance) / 15.0f; //Item Mod 'Accuracy' + + hitBonus += chance_mod; //Modifier applied from casted/disc skill attacks. + + if(attacker->IsNPC()) + hitBonus += (attacker->CastToNPC()->GetAccuracyRating() / 10.0f); //Modifier from database + + if(skillinuse == SkillArchery) + hitBonus -= hitBonus*RuleR(Combat, ArcheryHitPenalty); + + //Calculate final chance to hit + chancetohit += ((chancetohit * (hitBonus - avoidanceBonus)) / 100.0f); + mlog(COMBAT__TOHIT, "Chance to hit %.2f after accuracy calc %.2f and avoidance calc %.2f", chancetohit, hitBonus, avoidanceBonus); + + chancetohit = mod_hit_chance(chancetohit, skillinuse, attacker); + + // Chance to hit; Max 95%, Min 5% DEFAULTS + if(chancetohit > 1000 || chancetohit < -1000) { + //if chance to hit is crazy high, that means a discipline is in use, and let it stay there + } + else if(chancetohit > RuleR(Combat,MaxChancetoHit)) { + chancetohit = RuleR(Combat,MaxChancetoHit); + } + else if(chancetohit < RuleR(Combat,MinChancetoHit)) { + chancetohit = RuleR(Combat,MinChancetoHit); + } + + //I dont know the best way to handle a garunteed hit discipline being used + //agains a garunteed riposte (for example) discipline... for now, garunteed hit wins + + + #if EQDEBUG>=11 + LogFile->write(EQEMuLog::Debug, "3 FINAL calculated chance to hit is: %5.2f", chancetohit); + #endif + + // + // Did we hit? + // + + float tohit_roll = zone->random.Real(0, 100); + + mlog(COMBAT__TOHIT, "Final hit chance: %.2f%%. Hit roll %.2f", chancetohit, tohit_roll); + + return(tohit_roll <= chancetohit); +} + + +bool Mob::AvoidDamage(Mob* other, int32 &damage, bool CanRiposte) +{ + /* solar: 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. + * + * special return values: + * -1 - block + * -2 - parry + * -3 - riposte + * -4 - dodge + * + */ + float skill; + float bonus; + float RollTable[4] = {0,0,0,0}; + float roll; + Mob *attacker=other; + Mob *defender=this; + + //garunteed hit + bool ghit = false; + if((attacker->spellbonuses.MeleeSkillCheck + attacker->itembonuses.MeleeSkillCheck) > 500) + ghit = true; + + ////////////////////////////////////////////////////////// + // make enrage same as riposte + ///////////////////////////////////////////////////////// + if (IsEnraged() && other->InFrontMob(this, other->GetX(), other->GetY())) { + damage = -3; + mlog(COMBAT__DAMAGE, "I am enraged, riposting frontal attack."); + } + + ///////////////////////////////////////////////////////// + // riposte + ///////////////////////////////////////////////////////// + float riposte_chance = 0.0f; + if (CanRiposte && damage > 0 && CanThisClassRiposte() && other->InFrontMob(this, other->GetX(), other->GetY())) + { + riposte_chance = (100.0f + (float)defender->aabonuses.RiposteChance + (float)defender->spellbonuses.RiposteChance + (float)defender->itembonuses.RiposteChance) / 100.0f; + skill = GetSkill(SkillRiposte); + if (IsClient()) { + CastToClient()->CheckIncreaseSkill(SkillRiposte, other, -10); + } + + if (!ghit) { //if they are not using a garunteed hit discipline + bonus = 2.0 + skill/60.0 + (GetDEX()/200); + bonus *= riposte_chance; + bonus = mod_riposte_chance(bonus, attacker); + RollTable[0] = bonus + (itembonuses.HeroicDEX / 25); // 25 heroic = 1%, applies to ripo, parry, block + } + } + + /////////////////////////////////////////////////////// + // block + /////////////////////////////////////////////////////// + + bool bBlockFromRear = false; + bool bShieldBlockFromRear = false; + + if (this->IsClient()) { + int aaChance = 0; + + // a successful roll on this does not mean a successful block is forthcoming. only that a chance to block + // from a direction other than the rear is granted. + + //Live AA - HightenedAwareness + int BlockBehindChance = aabonuses.BlockBehind + spellbonuses.BlockBehind + itembonuses.BlockBehind; + + if (BlockBehindChance && zone->random.Roll(BlockBehindChance)) { + bBlockFromRear = true; + + if (spellbonuses.BlockBehind || itembonuses.BlockBehind) + bShieldBlockFromRear = true; //This bonus should allow a chance to Shield Block from behind. + } + } + + float block_chance = 0.0f; + if (damage > 0 && CanThisClassBlock() && (other->InFrontMob(this, other->GetX(), other->GetY()) || bBlockFromRear)) { + block_chance = (100.0f + (float)spellbonuses.IncreaseBlockChance + (float)itembonuses.IncreaseBlockChance) / 100.0f; + skill = CastToClient()->GetSkill(SkillBlock); + if (IsClient()) { + CastToClient()->CheckIncreaseSkill(SkillBlock, other, -10); + } + + if (!ghit) { //if they are not using a garunteed hit discipline + bonus = 2.0 + skill/35.0 + (GetDEX()/200); + bonus = mod_block_chance(bonus, attacker); + RollTable[1] = RollTable[0] + (bonus * block_chance); + } + } + else{ + RollTable[1] = RollTable[0]; + } + + if(damage > 0 && HasShieldEquiped() && (aabonuses.ShieldBlock || spellbonuses.ShieldBlock || itembonuses.ShieldBlock) + && (other->InFrontMob(this, other->GetX(), other->GetY()) || bShieldBlockFromRear)) { + + float bonusShieldBlock = 0.0f; + bonusShieldBlock = static_cast(aabonuses.ShieldBlock + spellbonuses.ShieldBlock + itembonuses.ShieldBlock); + RollTable[1] += bonusShieldBlock; + } + + if(IsClient() && damage > 0 && (aabonuses.TwoHandBluntBlock || spellbonuses.TwoHandBluntBlock || itembonuses.TwoHandBluntBlock) + && (other->InFrontMob(this, other->GetX(), other->GetY()) || bShieldBlockFromRear)) { + if(CastToClient()->m_inv.GetItem(MainPrimary)) { + float bonusStaffBlock = 0.0f; + if (CastToClient()->m_inv.GetItem(MainPrimary)->GetItem()->ItemType == ItemType2HBlunt){ + bonusStaffBlock = static_cast(aabonuses.TwoHandBluntBlock + spellbonuses.TwoHandBluntBlock + itembonuses.TwoHandBluntBlock); + RollTable[1] += bonusStaffBlock; + } + } + } + + ////////////////////////////////////////////////////// + // parry + ////////////////////////////////////////////////////// + float parry_chance = 0.0f; + if (damage > 0 && CanThisClassParry() && other->InFrontMob(this, other->GetX(), other->GetY())) + { + parry_chance = (100.0f + (float)defender->spellbonuses.ParryChance + (float)defender->itembonuses.ParryChance) / 100.0f; + skill = CastToClient()->GetSkill(SkillParry); + if (IsClient()) { + CastToClient()->CheckIncreaseSkill(SkillParry, other, -10); + } + + if (!ghit) { //if they are not using a garunteed hit discipline + bonus = 2.0 + skill/60.0 + (GetDEX()/200); + bonus *= parry_chance; + bonus = mod_parry_chance(bonus, attacker); + RollTable[2] = RollTable[1] + bonus; + } + } + else{ + RollTable[2] = RollTable[1]; + } + + //////////////////////////////////////////////////////// + // dodge + //////////////////////////////////////////////////////// + float dodge_chance = 0.0f; + if (damage > 0 && CanThisClassDodge() && other->InFrontMob(this, other->GetX(), other->GetY())) + { + dodge_chance = (100.0f + (float)defender->spellbonuses.DodgeChance + (float)defender->itembonuses.DodgeChance) / 100.0f; + skill = CastToClient()->GetSkill(SkillDodge); + if (IsClient()) { + CastToClient()->CheckIncreaseSkill(SkillDodge, other, -10); + } + + if (!ghit) { //if they are not using a garunteed hit discipline + bonus = 2.0 + skill/60.0 + (GetAGI()/200); + bonus *= dodge_chance; + //DCBOOMKAR + bonus = mod_dodge_chance(bonus, attacker); + RollTable[3] = RollTable[2] + bonus - (itembonuses.HeroicDEX / 25) + (itembonuses.HeroicAGI / 25); + } + } + else{ + RollTable[3] = RollTable[2]; + } + + if(damage > 0){ + roll = zone->random.Real(0,100); + if(roll <= RollTable[0]){ + damage = -3; + } + else if(roll <= RollTable[1]){ + damage = -1; + } + else if(roll <= RollTable[2]){ + damage = -2; + } + else if(roll <= RollTable[3]){ + damage = -4; + } + } + + mlog(COMBAT__DAMAGE, "Final damage after all avoidances: %d", damage); + + if (damage < 0) + return true; + return false; +} + +void Mob::MeleeMitigation(Mob *attacker, int32 &damage, int32 minhit, ExtraAttackOptions *opts) +{ + if (damage <= 0) + return; + + Mob* defender = this; + float aa_mit = (aabonuses.CombatStability + itembonuses.CombatStability + + spellbonuses.CombatStability) / 100.0f; + + if (RuleB(Combat, UseIntervalAC)) { + float softcap = (GetSkill(SkillDefense) + GetLevel()) * + RuleR(Combat, SoftcapFactor) * (1.0 + aa_mit); + float mitigation_rating = 0.0; + float attack_rating = 0.0; + int shield_ac = 0; + int armor = 0; + float weight = 0.0; + + float monkweight = RuleI(Combat, MonkACBonusWeight); + monkweight = mod_monk_weight(monkweight, attacker); + + if (IsClient()) { + armor = CastToClient()->GetRawACNoShield(shield_ac); + weight = (CastToClient()->CalcCurrentWeight() / 10.0); + } else if (IsNPC()) { + armor = CastToNPC()->GetRawAC(); + int PetACBonus = 0; + + if (!IsPet()) + armor = (armor / RuleR(Combat, NPCACFactor)); + + Mob *owner = nullptr; + if (IsPet()) + owner = GetOwner(); + else if ((CastToNPC()->GetSwarmOwner())) + owner = entity_list.GetMobID(CastToNPC()->GetSwarmOwner()); + + if (owner) + PetACBonus = owner->aabonuses.PetMeleeMitigation + owner->itembonuses.PetMeleeMitigation + owner->spellbonuses.PetMeleeMitigation; + + armor += spellbonuses.AC + itembonuses.AC + PetACBonus + 1; + } + + if (opts) { + armor *= (1.0f - opts->armor_pen_percent); + armor -= opts->armor_pen_flat; + } + + if (RuleB(Combat, OldACSoftcapRules)) { + if (GetClass() == WIZARD || GetClass() == MAGICIAN || + GetClass() == NECROMANCER || GetClass() == ENCHANTER) + softcap = RuleI(Combat, ClothACSoftcap); + else if (GetClass() == MONK && weight <= monkweight) + softcap = RuleI(Combat, MonkACSoftcap); + else if(GetClass() == DRUID || GetClass() == BEASTLORD || GetClass() == MONK) + softcap = RuleI(Combat, LeatherACSoftcap); + else if(GetClass() == SHAMAN || GetClass() == ROGUE || + GetClass() == BERSERKER || GetClass() == RANGER) + softcap = RuleI(Combat, ChainACSoftcap); + else + softcap = RuleI(Combat, PlateACSoftcap); + } + + softcap += shield_ac; + armor += shield_ac; + if (RuleB(Combat, OldACSoftcapRules)) + softcap += (softcap * (aa_mit * RuleR(Combat, AAMitigationACFactor))); + if (armor > softcap) { + int softcap_armor = armor - softcap; + if (RuleB(Combat, OldACSoftcapRules)) { + if (GetClass() == WARRIOR) + softcap_armor = softcap_armor * RuleR(Combat, WarriorACSoftcapReturn); + else if (GetClass() == SHADOWKNIGHT || GetClass() == PALADIN || + (GetClass() == MONK && weight <= monkweight)) + softcap_armor = softcap_armor * RuleR(Combat, KnightACSoftcapReturn); + else if (GetClass() == CLERIC || GetClass() == BARD || + GetClass() == BERSERKER || GetClass() == ROGUE || + GetClass() == SHAMAN || GetClass() == MONK) + softcap_armor = softcap_armor * RuleR(Combat, LowPlateChainACSoftcapReturn); + else if (GetClass() == RANGER || GetClass() == BEASTLORD) + softcap_armor = softcap_armor * RuleR(Combat, LowChainLeatherACSoftcapReturn); + else if (GetClass() == WIZARD || GetClass() == MAGICIAN || + GetClass() == NECROMANCER || GetClass() == ENCHANTER || + GetClass() == DRUID) + softcap_armor = softcap_armor * RuleR(Combat, CasterACSoftcapReturn); + else + softcap_armor = softcap_armor * RuleR(Combat, MiscACSoftcapReturn); + } else { + if (GetClass() == WARRIOR) + softcap_armor *= RuleR(Combat, WarACSoftcapReturn); + else if (GetClass() == PALADIN || GetClass() == SHADOWKNIGHT) + softcap_armor *= RuleR(Combat, PalShdACSoftcapReturn); + else if (GetClass() == CLERIC || GetClass() == RANGER || + GetClass() == MONK || GetClass() == BARD) + softcap_armor *= RuleR(Combat, ClrRngMnkBrdACSoftcapReturn); + else if (GetClass() == DRUID || GetClass() == NECROMANCER || + GetClass() == WIZARD || GetClass() == ENCHANTER || + GetClass() == MAGICIAN) + softcap_armor *= RuleR(Combat, DruNecWizEncMagACSoftcapReturn); + else if (GetClass() == ROGUE || GetClass() == SHAMAN || + GetClass() == BEASTLORD || GetClass() == BERSERKER) + softcap_armor *= RuleR(Combat, RogShmBstBerACSoftcapReturn); + else + softcap_armor *= RuleR(Combat, MiscACSoftcapReturn); + } + armor = softcap + softcap_armor; + } + + if (GetClass() == WIZARD || GetClass() == MAGICIAN || + GetClass() == NECROMANCER || GetClass() == ENCHANTER) + mitigation_rating = ((GetSkill(SkillDefense) + itembonuses.HeroicAGI/10) / 4.0) + armor + 1; + else + mitigation_rating = ((GetSkill(SkillDefense) + itembonuses.HeroicAGI/10) / 3.0) + (armor * 1.333333) + 1; + mitigation_rating *= 0.847; + + mitigation_rating = mod_mitigation_rating(mitigation_rating, attacker); + + if (attacker->IsClient()) + attack_rating = (attacker->CastToClient()->CalcATK() + ((attacker->GetSTR()-66) * 0.9) + (attacker->GetSkill(SkillOffense)*1.345)); + else + attack_rating = (attacker->GetATK() + (attacker->GetSkill(SkillOffense)*1.345) + ((attacker->GetSTR()-66) * 0.9)); + + attack_rating = attacker->mod_attack_rating(attack_rating, this); + + damage = GetMeleeMitDmg(attacker, damage, minhit, mitigation_rating, attack_rating); + } else { + //////////////////////////////////////////////////////// + // Scorpious2k: Include AC in the calculation + // use serverop variables to set values + int32 myac = GetAC(); + if(opts) { + myac *= (1.0f - opts->armor_pen_percent); + myac -= opts->armor_pen_flat; + } + + if (damage > 0 && myac > 0) { + int acfail=1000; + char tmp[10]; + + if (database.GetVariable("ACfail", tmp, 9)) { + acfail = (int) (atof(tmp) * 100); + if (acfail>100) acfail=100; + } + + if (acfail<=0 || zone->random.Int(0, 100)>acfail) { + float acreduction=1; + int acrandom=300; + if (database.GetVariable("ACreduction", tmp, 9)) + { + acreduction=atof(tmp); + if (acreduction>100) acreduction=100; + } + + if (database.GetVariable("ACrandom", tmp, 9)) + { + acrandom = (int) ((atof(tmp)+1) * 100); + if (acrandom>10100) acrandom=10100; + } + + if (acreduction>0) { + damage -= (int32) (GetAC() * acreduction/100.0f); + } + if (acrandom>0) { + damage -= (myac * zone->random.Int(0, acrandom) / 10000); + } + if (damage<1) damage=1; + mlog(COMBAT__DAMAGE, "AC Damage Reduction: fail chance %d%%. Failed. Reduction %.3f%%, random %d. Resulting damage %d.", acfail, acreduction, acrandom, damage); + } else { + mlog(COMBAT__DAMAGE, "AC Damage Reduction: fail chance %d%%. Did not fail.", acfail); + } + } + + damage -= (aa_mit * damage); + + if(damage != 0 && damage < minhit) + damage = minhit; + //reduce the damage from shielding item and aa based on the min dmg + //spells offer pure mitigation + damage -= (minhit * defender->itembonuses.MeleeMitigation / 100); + damage -= (damage * (defender->spellbonuses.MeleeMitigationEffect + defender->itembonuses.MeleeMitigationEffect + defender->aabonuses.MeleeMitigationEffect) / 100); + } + + if (damage < 0) + damage = 0; +} + +// This is called when the Mob is the one being hit +int32 Mob::GetMeleeMitDmg(Mob *attacker, int32 damage, int32 minhit, + float mit_rating, float atk_rating) +{ + float d = 10.0; + float mit_roll = zone->random.Real(0, mit_rating); + float atk_roll = zone->random.Real(0, atk_rating); + + if (atk_roll > mit_roll) { + float a_diff = atk_roll - mit_roll; + float thac0 = atk_rating * RuleR(Combat, ACthac0Factor); + float thac0cap = attacker->GetLevel() * 9 + 20; + if (thac0 > thac0cap) + thac0 = thac0cap; + + d -= 10.0 * (a_diff / thac0); + } else if (mit_roll > atk_roll) { + float m_diff = mit_roll - atk_roll; + float thac20 = mit_rating * RuleR(Combat, ACthac20Factor); + float thac20cap = GetLevel() * 9 + 20; + if (thac20 > thac20cap) + thac20 = thac20cap; + + d += 10.0 * (m_diff / thac20); + } + + if (d < 0.0) + d = 0.0; + else if (d > 20.0) + d = 20.0; + + float interval = (damage - minhit) / 20.0; + damage -= ((int)d * interval); + + damage -= (minhit * itembonuses.MeleeMitigation / 100); + damage -= (damage * (spellbonuses.MeleeMitigationEffect + itembonuses.MeleeMitigationEffect + aabonuses.MeleeMitigationEffect) / 100); + return damage; +} + +// This is called when the Client is the one being hit +int32 Client::GetMeleeMitDmg(Mob *attacker, int32 damage, int32 minhit, + float mit_rating, float atk_rating) +{ + if (!attacker->IsNPC() || RuleB(Combat, UseOldDamageIntervalRules)) + return Mob::GetMeleeMitDmg(attacker, damage, minhit, mit_rating, atk_rating); + int d = 10; + // floats for the rounding issues + float dmg_interval = (damage - minhit) / 19.0; + float dmg_bonus = minhit - dmg_interval; + float spellMeleeMit = (spellbonuses.MeleeMitigationEffect + itembonuses.MeleeMitigationEffect + aabonuses.MeleeMitigationEffect) / 100.0; + if (GetClass() == WARRIOR) + spellMeleeMit += 0.05; + dmg_bonus -= dmg_bonus * (itembonuses.MeleeMitigation / 100.0); + dmg_interval -= dmg_interval * spellMeleeMit; + + float mit_roll = zone->random.Real(0, mit_rating); + float atk_roll = zone->random.Real(0, atk_rating); + + if (atk_roll > mit_roll) { + float a_diff = atk_roll - mit_roll; + float thac0 = atk_rating * RuleR(Combat, ACthac0Factor); + float thac0cap = attacker->GetLevel() * 9 + 20; + if (thac0 > thac0cap) + thac0 = thac0cap; + + d += 10 * (a_diff / thac0); + } else if (mit_roll > atk_roll) { + float m_diff = mit_roll - atk_roll; + float thac20 = mit_rating * RuleR(Combat, ACthac20Factor); + float thac20cap = GetLevel() * 9 + 20; + if (thac20 > thac20cap) + thac20 = thac20cap; + + d -= 10 * (m_diff / thac20); + } + + if (d < 1) + d = 1; + else if (d > 20) + d = 20; + + return static_cast((dmg_bonus + dmg_interval * d)); +} + +//Returns the weapon damage against the input mob +//if we cannot hit the mob with the current weapon we will get a value less than or equal to zero +//Else we know we can hit. +//GetWeaponDamage(mob*, const Item_Struct*) is intended to be used for mobs or any other situation where we do not have a client inventory item +//GetWeaponDamage(mob*, const ItemInst*) is intended to be used for situations where we have a client inventory item +int Mob::GetWeaponDamage(Mob *against, const Item_Struct *weapon_item) { + int dmg = 0; + int banedmg = 0; + + //can't hit invulnerable stuff with weapons. + if(against->GetInvul() || against->GetSpecialAbility(IMMUNE_MELEE)){ + return 0; + } + + //check to see if our weapons or fists are magical. + if(against->GetSpecialAbility(IMMUNE_MELEE_NONMAGICAL)){ + if(weapon_item){ + if(weapon_item->Magic){ + dmg = weapon_item->Damage; + + //this is more for non weapon items, ex: boots for kick + //they don't have a dmg but we should be able to hit magical + dmg = dmg <= 0 ? 1 : dmg; + } + else + return 0; + } + else{ + if((GetClass() == MONK || GetClass() == BEASTLORD) && GetLevel() >= 30){ + dmg = GetMonkHandToHandDamage(); + } + else if(GetOwner() && GetLevel() >= RuleI(Combat, PetAttackMagicLevel)){ + //pets wouldn't actually use this but... + //it gives us an idea if we can hit due to the dual nature of this function + dmg = 1; + } + else if(GetSpecialAbility(SPECATK_MAGICAL)) + { + dmg = 1; + } + else + return 0; + } + } + else{ + if(weapon_item){ + dmg = weapon_item->Damage; + + dmg = dmg <= 0 ? 1 : dmg; + } + else{ + if(GetClass() == MONK || GetClass() == BEASTLORD){ + dmg = GetMonkHandToHandDamage(); + } + else{ + dmg = 1; + } + } + } + + int eledmg = 0; + if(!against->GetSpecialAbility(IMMUNE_MAGIC)){ + if(weapon_item && weapon_item->ElemDmgAmt){ + //we don't check resist for npcs here + eledmg = weapon_item->ElemDmgAmt; + dmg += eledmg; + } + } + + if(against->GetSpecialAbility(IMMUNE_MELEE_EXCEPT_BANE)){ + if(weapon_item){ + if(weapon_item->BaneDmgBody == against->GetBodyType()){ + banedmg += weapon_item->BaneDmgAmt; + } + + if(weapon_item->BaneDmgRace == against->GetRace()){ + banedmg += weapon_item->BaneDmgRaceAmt; + } + } + + if(!eledmg && !banedmg){ + if(!GetSpecialAbility(SPECATK_BANE)) + return 0; + else + return 1; + } + else + dmg += banedmg; + } + else{ + if(weapon_item){ + if(weapon_item->BaneDmgBody == against->GetBodyType()){ + banedmg += weapon_item->BaneDmgAmt; + } + + if(weapon_item->BaneDmgRace == against->GetRace()){ + banedmg += weapon_item->BaneDmgRaceAmt; + } + } + + dmg += (banedmg + eledmg); + } + + if(dmg <= 0){ + return 0; + } + else + return dmg; +} + +int Mob::GetWeaponDamage(Mob *against, const ItemInst *weapon_item, uint32 *hate) +{ + int dmg = 0; + int banedmg = 0; + + if(!against || against->GetInvul() || against->GetSpecialAbility(IMMUNE_MELEE)){ + return 0; + } + + //check for items being illegally attained + if(weapon_item){ + const Item_Struct *mWeaponItem = weapon_item->GetItem(); + if(mWeaponItem){ + if(mWeaponItem->ReqLevel > GetLevel()){ + return 0; + } + + if(!weapon_item->IsEquipable(GetBaseRace(), GetClass())){ + return 0; + } + } + else{ + return 0; + } + } + + if(against->GetSpecialAbility(IMMUNE_MELEE_NONMAGICAL)){ + if(weapon_item){ + // check to see if the weapon is magic + bool MagicWeapon = false; + if(weapon_item->GetItem() && weapon_item->GetItem()->Magic) + MagicWeapon = true; + else { + if(spellbonuses.MagicWeapon || itembonuses.MagicWeapon) + MagicWeapon = true; + } + + if(MagicWeapon) { + + if(IsClient() && GetLevel() < weapon_item->GetItem()->RecLevel){ + dmg = CastToClient()->CalcRecommendedLevelBonus(GetLevel(), weapon_item->GetItem()->RecLevel, weapon_item->GetItem()->Damage); + } + else{ + dmg = weapon_item->GetItem()->Damage; + } + + for(int x = 0; x < EmuConstants::ITEM_COMMON_SIZE; x++){ + if(weapon_item->GetAugment(x) && weapon_item->GetAugment(x)->GetItem()){ + dmg += weapon_item->GetAugment(x)->GetItem()->Damage; + if (hate) *hate += weapon_item->GetAugment(x)->GetItem()->Damage + weapon_item->GetAugment(x)->GetItem()->ElemDmgAmt; + } + } + dmg = dmg <= 0 ? 1 : dmg; + } + else + return 0; + } + else{ + if((GetClass() == MONK || GetClass() == BEASTLORD) && GetLevel() >= 30){ + dmg = GetMonkHandToHandDamage(); + if (hate) *hate += dmg; + } + else if(GetOwner() && GetLevel() >= RuleI(Combat, PetAttackMagicLevel)){ //pets wouldn't actually use this but... + dmg = 1; //it gives us an idea if we can hit + } + else if(GetSpecialAbility(SPECATK_MAGICAL)){ + dmg = 1; + } + else + return 0; + } + } + else{ + if(weapon_item){ + if(weapon_item->GetItem()){ + + if(IsClient() && GetLevel() < weapon_item->GetItem()->RecLevel){ + dmg = CastToClient()->CalcRecommendedLevelBonus(GetLevel(), weapon_item->GetItem()->RecLevel, weapon_item->GetItem()->Damage); + } + else{ + dmg = weapon_item->GetItem()->Damage; + } + + for (int x = 0; x < EmuConstants::ITEM_COMMON_SIZE; x++){ + if(weapon_item->GetAugment(x) && weapon_item->GetAugment(x)->GetItem()){ + dmg += weapon_item->GetAugment(x)->GetItem()->Damage; + if (hate) *hate += weapon_item->GetAugment(x)->GetItem()->Damage + weapon_item->GetAugment(x)->GetItem()->ElemDmgAmt; + } + } + dmg = dmg <= 0 ? 1 : dmg; + } + } + else{ + if(GetClass() == MONK || GetClass() == BEASTLORD){ + dmg = GetMonkHandToHandDamage(); + if (hate) *hate += dmg; + } + else{ + dmg = 1; + } + } + } + + int eledmg = 0; + if(!against->GetSpecialAbility(IMMUNE_MAGIC)){ + if(weapon_item && weapon_item->GetItem() && weapon_item->GetItem()->ElemDmgAmt){ + if(IsClient() && GetLevel() < weapon_item->GetItem()->RecLevel){ + eledmg = CastToClient()->CalcRecommendedLevelBonus(GetLevel(), weapon_item->GetItem()->RecLevel, weapon_item->GetItem()->ElemDmgAmt); + } + else{ + eledmg = weapon_item->GetItem()->ElemDmgAmt; + } + + if(eledmg) + { + eledmg = (eledmg * against->ResistSpell(weapon_item->GetItem()->ElemDmgType, 0, this) / 100); + } + } + + if(weapon_item){ + for (int x = 0; x < EmuConstants::ITEM_COMMON_SIZE; x++){ + if(weapon_item->GetAugment(x) && weapon_item->GetAugment(x)->GetItem()){ + if(weapon_item->GetAugment(x)->GetItem()->ElemDmgAmt) + eledmg += (weapon_item->GetAugment(x)->GetItem()->ElemDmgAmt * against->ResistSpell(weapon_item->GetAugment(x)->GetItem()->ElemDmgType, 0, this) / 100); + } + } + } + } + + if(against->GetSpecialAbility(IMMUNE_MELEE_EXCEPT_BANE)){ + if(weapon_item && weapon_item->GetItem()){ + if(weapon_item->GetItem()->BaneDmgBody == against->GetBodyType()){ + if(IsClient() && GetLevel() < weapon_item->GetItem()->RecLevel){ + banedmg += CastToClient()->CalcRecommendedLevelBonus(GetLevel(), weapon_item->GetItem()->RecLevel, weapon_item->GetItem()->BaneDmgAmt); + } + else{ + banedmg += weapon_item->GetItem()->BaneDmgAmt; + } + } + + if(weapon_item->GetItem()->BaneDmgRace == against->GetRace()){ + if(IsClient() && GetLevel() < weapon_item->GetItem()->RecLevel){ + banedmg += CastToClient()->CalcRecommendedLevelBonus(GetLevel(), weapon_item->GetItem()->RecLevel, weapon_item->GetItem()->BaneDmgRaceAmt); + } + else{ + banedmg += weapon_item->GetItem()->BaneDmgRaceAmt; + } + } + + for (int x = 0; x < EmuConstants::ITEM_COMMON_SIZE; x++){ + if(weapon_item->GetAugment(x) && weapon_item->GetAugment(x)->GetItem()){ + if(weapon_item->GetAugment(x)->GetItem()->BaneDmgBody == against->GetBodyType()){ + banedmg += weapon_item->GetAugment(x)->GetItem()->BaneDmgAmt; + } + + if(weapon_item->GetAugment(x)->GetItem()->BaneDmgRace == against->GetRace()){ + banedmg += weapon_item->GetAugment(x)->GetItem()->BaneDmgRaceAmt; + } + } + } + } + + if(!eledmg && !banedmg) + { + if(!GetSpecialAbility(SPECATK_BANE)) + return 0; + else + return 1; + } + else { + dmg += (banedmg + eledmg); + if (hate) *hate += banedmg; + } + } + else{ + if(weapon_item && weapon_item->GetItem()){ + if(weapon_item->GetItem()->BaneDmgBody == against->GetBodyType()){ + if(IsClient() && GetLevel() < weapon_item->GetItem()->RecLevel){ + banedmg += CastToClient()->CalcRecommendedLevelBonus(GetLevel(), weapon_item->GetItem()->RecLevel, weapon_item->GetItem()->BaneDmgAmt); + } + else{ + banedmg += weapon_item->GetItem()->BaneDmgAmt; + } + } + + if(weapon_item->GetItem()->BaneDmgRace == against->GetRace()){ + if(IsClient() && GetLevel() < weapon_item->GetItem()->RecLevel){ + banedmg += CastToClient()->CalcRecommendedLevelBonus(GetLevel(), weapon_item->GetItem()->RecLevel, weapon_item->GetItem()->BaneDmgRaceAmt); + } + else{ + banedmg += weapon_item->GetItem()->BaneDmgRaceAmt; + } + } + + for (int x = 0; x < EmuConstants::ITEM_COMMON_SIZE; x++){ + if(weapon_item->GetAugment(x) && weapon_item->GetAugment(x)->GetItem()){ + if(weapon_item->GetAugment(x)->GetItem()->BaneDmgBody == against->GetBodyType()){ + banedmg += weapon_item->GetAugment(x)->GetItem()->BaneDmgAmt; + } + + if(weapon_item->GetAugment(x)->GetItem()->BaneDmgRace == against->GetRace()){ + banedmg += weapon_item->GetAugment(x)->GetItem()->BaneDmgRaceAmt; + } + } + } + } + dmg += (banedmg + eledmg); + if (hate) *hate += banedmg; + } + + if(dmg <= 0){ + return 0; + } + else + return dmg; +} + +//note: throughout this method, setting `damage` to a negative is a way to +//stop the attack calculations +// IsFromSpell added to allow spell effects to use Attack. (Mainly for the Rampage AA right now.) +bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts) +{ + if (!other) { + SetTarget(nullptr); + LogFile->write(EQEMuLog::Error, "A null Mob object was passed to Client::Attack() for evaluation!"); + return false; + } + + if(!GetTarget()) + SetTarget(other); + + mlog(COMBAT__ATTACKS, "Attacking %s with hand %d %s", other?other->GetName():"(nullptr)", Hand, bRiposte?"(this is a riposte)":""); + + //SetAttackTimer(); + if ( + (IsCasting() && GetClass() != BARD && !IsFromSpell) + || other == nullptr + || ((IsClient() && CastToClient()->dead) || (other->IsClient() && other->CastToClient()->dead)) + || (GetHP() < 0) + || (!IsAttackAllowed(other)) + ) { + mlog(COMBAT__ATTACKS, "Attack canceled, invalid circumstances."); + return false; // Only bards can attack while casting + } + + if(DivineAura() && !GetGM()) {//cant attack while invulnerable unless your a gm + mlog(COMBAT__ATTACKS, "Attack canceled, Divine Aura is in effect."); + Message_StringID(MT_DefaultText, DIVINE_AURA_NO_ATK); //You can't attack while invulnerable! + return false; + } + + if (GetFeigned()) + return false; // Rogean: How can you attack while feigned? Moved up from Aggro Code. + + + ItemInst* weapon; + if (Hand == MainSecondary){ // Kaiyodo - Pick weapon from the attacking hand + weapon = GetInv().GetItem(MainSecondary); + OffHandAtk(true); + } + else{ + weapon = GetInv().GetItem(MainPrimary); + OffHandAtk(false); + } + + if(weapon != nullptr) { + if (!weapon->IsWeapon()) { + mlog(COMBAT__ATTACKS, "Attack canceled, Item %s (%d) is not a weapon.", weapon->GetItem()->Name, weapon->GetID()); + return(false); + } + mlog(COMBAT__ATTACKS, "Attacking with weapon: %s (%d)", weapon->GetItem()->Name, weapon->GetID()); + } else { + mlog(COMBAT__ATTACKS, "Attacking without a weapon."); + } + + // calculate attack_skill and skillinuse depending on hand and weapon + // also send Packet to near clients + SkillUseTypes skillinuse; + AttackAnimation(skillinuse, Hand, weapon); + mlog(COMBAT__ATTACKS, "Attacking with %s in slot %d using skill %d", weapon?weapon->GetItem()->Name:"Fist", Hand, skillinuse); + + /// Now figure out damage + int damage = 0; + uint8 mylevel = GetLevel() ? GetLevel() : 1; + uint32 hate = 0; + if (weapon) hate = weapon->GetItem()->Damage + weapon->GetItem()->ElemDmgAmt; + int weapon_damage = GetWeaponDamage(other, weapon, &hate); + if (hate == 0 && weapon_damage > 1) hate = weapon_damage; + + //if weapon damage > 0 then we know we can hit the target with this weapon + //otherwise we cannot and we set the damage to -5 later on + if(weapon_damage > 0){ + + //Berserker Berserk damage bonus + if(IsBerserk() && GetClass() == BERSERKER){ + int bonus = 3 + GetLevel()/10; //unverified + weapon_damage = weapon_damage * (100+bonus) / 100; + mlog(COMBAT__DAMAGE, "Berserker damage bonus increases DMG to %d", weapon_damage); + } + + //try a finishing blow.. if successful end the attack + if(TryFinishingBlow(other, skillinuse)) + return (true); + + int min_hit = 1; + int max_hit = (2*weapon_damage*GetDamageTable(skillinuse)) / 100; + + if(GetLevel() < 10 && max_hit > RuleI(Combat, HitCapPre10)) + max_hit = (RuleI(Combat, HitCapPre10)); + else if(GetLevel() < 20 && max_hit > RuleI(Combat, HitCapPre20)) + max_hit = (RuleI(Combat, HitCapPre20)); + + CheckIncreaseSkill(skillinuse, other, -15); + CheckIncreaseSkill(SkillOffense, other, -15); + + + // *************************************************************** + // *** Calculate the damage bonus, if applicable, for this hit *** + // *************************************************************** + +#ifndef EQEMU_NO_WEAPON_DAMAGE_BONUS + + // If you include the preprocessor directive "#define EQEMU_NO_WEAPON_DAMAGE_BONUS", that indicates that you do not + // want damage bonuses added to weapon damage at all. This feature was requested by ChaosSlayer on the EQEmu Forums. + // + // This is not recommended for normal usage, as the damage bonus represents a non-trivial component of the DPS output + // of weapons wielded by higher-level melee characters (especially for two-handed weapons). + + int ucDamageBonus = 0; + + if( Hand == MainPrimary && GetLevel() >= 28 && IsWarriorClass() ) + { + // Damage bonuses apply only to hits from the main hand (Hand == MainPrimary) by characters level 28 and above + // who belong to a melee class. If we're here, then all of these conditions apply. + + ucDamageBonus = GetWeaponDamageBonus( weapon ? weapon->GetItem() : (const Item_Struct*) nullptr ); + + min_hit += (int) ucDamageBonus; + max_hit += (int) ucDamageBonus; + hate += ucDamageBonus; + } +#endif + //Live AA - Sinister Strikes *Adds weapon damage bonus to offhand weapon. + if (Hand == MainSecondary) { + if (aabonuses.SecondaryDmgInc || itembonuses.SecondaryDmgInc || spellbonuses.SecondaryDmgInc){ + + ucDamageBonus = GetWeaponDamageBonus( weapon ? weapon->GetItem() : (const Item_Struct*) nullptr ); + + min_hit += (int) ucDamageBonus; + max_hit += (int) ucDamageBonus; + hate += ucDamageBonus; + } + } + + min_hit += min_hit * GetMeleeMinDamageMod_SE(skillinuse) / 100; + + if(max_hit < min_hit) + max_hit = min_hit; + + if(RuleB(Combat, UseIntervalAC)) + damage = max_hit; + else + damage = zone->random.Int(min_hit, max_hit); + + damage = mod_client_damage(damage, skillinuse, Hand, weapon, other); + + mlog(COMBAT__DAMAGE, "Damage calculated to %d (min %d, max %d, str %d, skill %d, DMG %d, lv %d)", + damage, min_hit, max_hit, GetSTR(), GetSkill(skillinuse), weapon_damage, mylevel); + + int hit_chance_bonus = 0; + + if(opts) { + damage *= opts->damage_percent; + damage += opts->damage_flat; + hate *= opts->hate_percent; + hate += opts->hate_flat; + hit_chance_bonus += opts->hit_chance; + } + + //check to see if we hit.. + if(!other->CheckHitChance(this, skillinuse, Hand, hit_chance_bonus)) { + mlog(COMBAT__ATTACKS, "Attack missed. Damage set to 0."); + damage = 0; + } else { //we hit, try to avoid it + other->AvoidDamage(this, damage); + other->MeleeMitigation(this, damage, min_hit, opts); + if(damage > 0) + CommonOutgoingHitSuccess(other, damage, skillinuse); + + mlog(COMBAT__DAMAGE, "Final damage after all reductions: %d", damage); + } + + //riposte + bool slippery_attack = false; // Part of hack to allow riposte to become a miss, but still allow a Strikethrough chance (like on Live) + if (damage == -3) { + if (bRiposte) return false; + else { + if (Hand == MainSecondary) {// Do we even have it & was attack with mainhand? If not, don't bother with other calculations + //Live AA - SlipperyAttacks + //This spell effect most likely directly modifies the actual riposte chance when using offhand attack. + int32 OffhandRiposteFail = aabonuses.OffhandRiposteFail + itembonuses.OffhandRiposteFail + spellbonuses.OffhandRiposteFail; + OffhandRiposteFail *= -1; //Live uses a negative value for this. + + if (OffhandRiposteFail && + (OffhandRiposteFail > 99 || zone->random.Roll(OffhandRiposteFail))) { + damage = 0; // Counts as a miss + slippery_attack = true; + } else + DoRiposte(other); + if (IsDead()) return false; + } + else + DoRiposte(other); + if (IsDead()) return false; + } + } + + if (((damage < 0) || slippery_attack) && !bRiposte && !IsStrikethrough) { // Hack to still allow Strikethrough chance w/ Slippery Attacks AA + int32 bonusStrikeThrough = itembonuses.StrikeThrough + spellbonuses.StrikeThrough + aabonuses.StrikeThrough; + + if(bonusStrikeThrough && zone->random.Roll(bonusStrikeThrough)) { + Message_StringID(MT_StrikeThrough, STRIKETHROUGH_STRING); // You strike through your opponents defenses! + Attack(other, Hand, false, true); // Strikethrough only gives another attempted hit + return false; + } + } + } + else{ + damage = -5; + } + + // Hate Generation is on a per swing basis, regardless of a hit, miss, or block, its always the same. + // If we are this far, this means we are atleast making a swing. + + if (!bRiposte) // Ripostes never generate any aggro. + other->AddToHateList(this, hate); + + /////////////////////////////////////////////////////////// + ////// Send Attack Damage + /////////////////////////////////////////////////////////// + other->Damage(this, damage, SPELL_UNKNOWN, skillinuse); + + if (IsDead()) return false; + + MeleeLifeTap(damage); + + if (damage > 0 && HasSkillProcSuccess() && other && other->GetHP() > 0) + TrySkillProc(other, skillinuse, 0, true, Hand); + + CommonBreakInvisible(); + + if(GetTarget()) + TriggerDefensiveProcs(weapon, other, Hand, damage); + + if (damage > 0) + return true; + + else + return false; +} + +//used by complete heal and #heal +void Mob::Heal() +{ + SetMaxHP(); + SendHPUpdate(); +} + +void Client::Damage(Mob* other, int32 damage, uint16 spell_id, SkillUseTypes attack_skill, bool avoidable, int8 buffslot, bool iBuffTic) +{ + if(dead || IsCorpse()) + return; + + if(spell_id==0) + spell_id = SPELL_UNKNOWN; + + if(spell_id!=0 && spell_id != SPELL_UNKNOWN && other && damage > 0) + { + if(other->IsNPC() && !other->IsPet()) + { + float npcspellscale = other->CastToNPC()->GetSpellScale(); + damage = ((float)damage * npcspellscale) / (float)100; + } + } + + // cut all PVP spell damage to 2/3 -solar + // Blasting ourselfs is considered PvP + //Don't do PvP mitigation if the caster is damaging himself + if(other && other->IsClient() && (other != this) && damage > 0) { + int PvPMitigation = 100; + if(attack_skill == SkillArchery) + PvPMitigation = 80; + else + PvPMitigation = 67; + damage = (damage * PvPMitigation) / 100; + } + + if(!ClientFinishedLoading()) + damage = -5; + + //do a majority of the work... + CommonDamage(other, damage, spell_id, attack_skill, avoidable, buffslot, iBuffTic); + + if (damage > 0) { + + if (spell_id == SPELL_UNKNOWN) + CheckIncreaseSkill(SkillDefense, other, -15); + } +} + +bool Client::Death(Mob* killerMob, int32 damage, uint16 spell, SkillUseTypes attack_skill) +{ + if(!ClientFinishedLoading()) + return false; + + if(dead) + return false; //cant die more than once... + + if(!spell) + spell = SPELL_UNKNOWN; + + char buffer[48] = { 0 }; + snprintf(buffer, 47, "%d %d %d %d", killerMob ? killerMob->GetID() : 0, damage, spell, static_cast(attack_skill)); + if(parse->EventPlayer(EVENT_DEATH, this, buffer, 0) != 0) { + if(GetHP() < 0) { + SetHP(0); + } + return false; + } + + if(killerMob && killerMob->IsClient() && (spell != SPELL_UNKNOWN) && damage > 0) { + char val1[20]={0}; + entity_list.MessageClose_StringID(this, false, 100, MT_NonMelee, HIT_NON_MELEE, + killerMob->GetCleanName(), GetCleanName(), ConvertArray(damage, val1)); + } + + int exploss = 0; + mlog(COMBAT__HITS, "Fatal blow dealt by %s with %d damage, spell %d, skill %d", killerMob ? killerMob->GetName() : "Unknown", damage, spell, attack_skill); + + /* + #1: Send death packet to everyone + */ + uint8 killed_level = GetLevel(); + + SendLogoutPackets(); + + /* Make self become corpse packet */ + EQApplicationPacket app2(OP_BecomeCorpse, sizeof(BecomeCorpse_Struct)); + BecomeCorpse_Struct* bc = (BecomeCorpse_Struct*)app2.pBuffer; + bc->spawn_id = GetID(); + bc->x = GetX(); + bc->y = GetY(); + bc->z = GetZ(); + QueuePacket(&app2); + + /* Make Death Packet */ + EQApplicationPacket app(OP_Death, sizeof(Death_Struct)); + Death_Struct* d = (Death_Struct*)app.pBuffer; + d->spawn_id = GetID(); + d->killer_id = killerMob ? killerMob->GetID() : 0; + d->corpseid=GetID(); + d->bindzoneid = m_pp.binds[0].zoneId; + d->spell_id = spell == SPELL_UNKNOWN ? 0xffffffff : spell; + d->attack_skill = spell != SPELL_UNKNOWN ? 0xe7 : attack_skill; + d->damage = damage; + app.priority = 6; + entity_list.QueueClients(this, &app); + + /* + #2: figure out things that affect the player dying and mark them dead + */ + + InterruptSpell(); + SetPet(0); + SetHorseId(0); + dead = true; + + if(GetMerc()) { + GetMerc()->Suspend(); + } + + if (killerMob != nullptr) + { + if (killerMob->IsNPC()) { + parse->EventNPC(EVENT_SLAY, killerMob->CastToNPC(), this, "", 0); + + mod_client_death_npc(killerMob); + + uint16 emoteid = killerMob->GetEmoteID(); + if(emoteid != 0) + killerMob->CastToNPC()->DoNPCEmote(KILLEDPC,emoteid); + killerMob->TrySpellOnKill(killed_level,spell); + } + + if(killerMob->IsClient() && (IsDueling() || killerMob->CastToClient()->IsDueling())) { + SetDueling(false); + SetDuelTarget(0); + if (killerMob->IsClient() && killerMob->CastToClient()->IsDueling() && killerMob->CastToClient()->GetDuelTarget() == GetID()) + { + //if duel opponent killed us... + killerMob->CastToClient()->SetDueling(false); + killerMob->CastToClient()->SetDuelTarget(0); + entity_list.DuelMessage(killerMob,this,false); + + mod_client_death_duel(killerMob); + + } else { + //otherwise, we just died, end the duel. + Mob* who = entity_list.GetMob(GetDuelTarget()); + if(who && who->IsClient()) { + who->CastToClient()->SetDueling(false); + who->CastToClient()->SetDuelTarget(0); + } + } + } + } + + entity_list.RemoveFromTargets(this); + hate_list.RemoveEnt(this); + RemoveAutoXTargets(); + + + //remove ourself from all proximities + ClearAllProximities(); + + /* + #3: exp loss and corpse generation + */ + + // figure out if they should lose exp + if(RuleB(Character, UseDeathExpLossMult)){ + float GetNum [] = {0.005f,0.015f,0.025f,0.035f,0.045f,0.055f,0.065f,0.075f,0.085f,0.095f,0.110f }; + int Num = RuleI(Character, DeathExpLossMultiplier); + if((Num < 0) || (Num > 10)) + Num = 3; + float loss = GetNum[Num]; + exploss=(int)((float)GetEXP() * (loss)); //loose % of total XP pending rule (choose 0-10) + } + + if(!RuleB(Character, UseDeathExpLossMult)){ + exploss = (int)(GetLevel() * (GetLevel() / 18.0) * 12000); + } + + if( (GetLevel() < RuleI(Character, DeathExpLossLevel)) || (GetLevel() > RuleI(Character, DeathExpLossMaxLevel)) || IsBecomeNPC() ) + { + exploss = 0; + } + else if( killerMob ) + { + if( killerMob->IsClient() ) + { + exploss = 0; + } + else if( killerMob->GetOwner() && killerMob->GetOwner()->IsClient() ) + { + exploss = 0; + } + } + + if(spell != SPELL_UNKNOWN) + { + uint32 buff_count = GetMaxTotalSlots(); + for(uint16 buffIt = 0; buffIt < buff_count; buffIt++) + { + if(buffs[buffIt].spellid == spell && buffs[buffIt].client) + { + exploss = 0; // no exp loss for pvp dot + break; + } + } + } + + bool LeftCorpse = false; + + // now we apply the exp loss, unmem their spells, and make a corpse + // unless they're a GM (or less than lvl 10 + if(!GetGM()) + { + if(exploss > 0) { + int32 newexp = GetEXP(); + if(exploss > newexp) { + //lost more than we have... wtf.. + newexp = 1; + } else { + newexp -= exploss; + } + SetEXP(newexp, GetAAXP()); + //m_epp.perAA = 0; //reset to no AA exp on death. + } + + //this generates a lot of 'updates' to the client that the client does not need + BuffFadeNonPersistDeath(); + if((GetClientVersionBit() & BIT_SoFAndLater) && RuleB(Character, RespawnFromHover)) + UnmemSpellAll(true); + else + UnmemSpellAll(false); + + if(RuleB(Character, LeaveCorpses) && GetLevel() >= RuleI(Character, DeathItemLossLevel) || RuleB(Character, LeaveNakedCorpses)) + { + // creating the corpse takes the cash/items off the player too + Corpse *new_corpse = new Corpse(this, exploss); + + char tmp[20]; + database.GetVariable("ServerType", tmp, 9); + if(atoi(tmp)==1 && killerMob != nullptr && killerMob->IsClient()){ + char tmp2[10] = {0}; + database.GetVariable("PvPreward", tmp, 9); + int reward = atoi(tmp); + if(reward==3){ + database.GetVariable("PvPitem", tmp2, 9); + int pvpitem = atoi(tmp2); + if(pvpitem>0 && pvpitem<200000) + new_corpse->SetPlayerKillItemID(pvpitem); + } + else if(reward==2) + new_corpse->SetPlayerKillItemID(-1); + else if(reward==1) + new_corpse->SetPlayerKillItemID(1); + else + new_corpse->SetPlayerKillItemID(0); + if(killerMob->CastToClient()->isgrouped) { + Group* group = entity_list.GetGroupByClient(killerMob->CastToClient()); + if(group != 0) + { + for(int i=0;i<6;i++) + { + if(group->members[i] != nullptr) + { + new_corpse->AllowPlayerLoot(group->members[i],i); + } + } + } + } + } + + entity_list.AddCorpse(new_corpse, GetID()); + SetID(0); + + //send the become corpse packet to everybody else in the zone. + entity_list.QueueClients(this, &app2, true); + + LeftCorpse = true; + } + } else { + BuffFadeDetrimental(); + } + + /* + Finally, send em home + + We change the mob variables, not pp directly, because Save() will copy + from these and overwrite what we set in pp anyway + */ + + if(LeftCorpse && (GetClientVersionBit() & BIT_SoFAndLater) && RuleB(Character, RespawnFromHover)) + { + ClearDraggedCorpses(); + RespawnFromHoverTimer.Start(RuleI(Character, RespawnFromHoverTimer) * 1000); + SendRespawnBinds(); + } + else + { + if(isgrouped) + { + Group *g = GetGroup(); + if(g) + g->MemberZoned(this); + } + + Raid* r = entity_list.GetRaidByClient(this); + + if(r) + r->MemberZoned(this); + + dead_timer.Start(5000, true); + m_pp.zone_id = m_pp.binds[0].zoneId; + m_pp.zoneInstance = m_pp.binds[0].instance_id; + database.MoveCharacterToZone(this->CharacterID(), database.GetZoneName(m_pp.zone_id)); + Save(); + GoToDeath(); + } + + /* QS: PlayerLogDeaths */ + if (RuleB(QueryServ, PlayerLogDeaths)){ + const char * killer_name = ""; + if (killerMob && killerMob->GetCleanName()){ killer_name = killerMob->GetCleanName(); } + std::string event_desc = StringFormat("Died in zoneid:%i instid:%i by '%s', spellid:%i, damage:%i", this->GetZoneID(), this->GetInstanceID(), killer_name, spell, damage); + QServ->PlayerLogEvent(Player_Log_Deaths, this->CharacterID(), event_desc); + } + + parse->EventPlayer(EVENT_DEATH_COMPLETE, this, buffer, 0); + return true; +} + +bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts) +{ + int damage = 0; + + if (!other) { + SetTarget(nullptr); + LogFile->write(EQEMuLog::Error, "A null Mob object was passed to NPC::Attack() for evaluation!"); + return false; + } + + if(DivineAura()) + return(false); + + if(!GetTarget()) + SetTarget(other); + + //Check that we can attack before we calc heading and face our target + if (!IsAttackAllowed(other)) { + if (this->GetOwnerID()) + this->Say_StringID(NOT_LEGAL_TARGET); + if(other) { + if (other->IsClient()) + other->CastToClient()->RemoveXTarget(this, false); + RemoveFromHateList(other); + mlog(COMBAT__ATTACKS, "I am not allowed to attack %s", other->GetName()); + } + return false; + } + + FaceTarget(GetTarget()); + + SkillUseTypes skillinuse = SkillHandtoHand; + if (Hand == MainPrimary) { + skillinuse = static_cast(GetPrimSkill()); + OffHandAtk(false); + } + if (Hand == MainSecondary) { + skillinuse = static_cast(GetSecSkill()); + OffHandAtk(true); + } + + //figure out what weapon they are using, if any + const Item_Struct* weapon = nullptr; + if (Hand == MainPrimary && equipment[MainPrimary] > 0) + weapon = database.GetItem(equipment[MainPrimary]); + else if (equipment[MainSecondary]) + weapon = database.GetItem(equipment[MainSecondary]); + + //We dont factor much from the weapon into the attack. + //Just the skill type so it doesn't look silly using punching animations and stuff while wielding weapons + if(weapon) { + mlog(COMBAT__ATTACKS, "Attacking with weapon: %s (%d) (too bad im not using it for much)", weapon->Name, weapon->ID); + + if(Hand == MainSecondary && weapon->ItemType == ItemTypeShield){ + mlog(COMBAT__ATTACKS, "Attack with shield canceled."); + return false; + } + + switch(weapon->ItemType){ + case ItemType1HSlash: + skillinuse = Skill1HSlashing; + break; + case ItemType2HSlash: + skillinuse = Skill2HSlashing; + break; + case ItemType1HPiercing: + //skillinuse = Skill1HPiercing; + //break; + case ItemType2HPiercing: + skillinuse = Skill1HPiercing; // change to Skill2HPiercing once activated + break; + case ItemType1HBlunt: + skillinuse = Skill1HBlunt; + break; + case ItemType2HBlunt: + skillinuse = Skill2HBlunt; + break; + case ItemTypeBow: + skillinuse = SkillArchery; + break; + case ItemTypeLargeThrowing: + case ItemTypeSmallThrowing: + skillinuse = SkillThrowing; + break; + default: + skillinuse = SkillHandtoHand; + break; + } + } + + int weapon_damage = GetWeaponDamage(other, weapon); + + //do attack animation regardless of whether or not we can hit below + int16 charges = 0; + ItemInst weapon_inst(weapon, charges); + AttackAnimation(skillinuse, Hand, &weapon_inst); + + // Remove this once Skill2HPiercing is activated + //Work-around for there being no 2HP skill - We use 99 for the 2HB animation and 36 for pierce messages + if(skillinuse == 99) + skillinuse = static_cast(36); + + //basically "if not immune" then do the attack + if((weapon_damage) > 0) { + + //ele and bane dmg too + //NPCs add this differently than PCs + //if NPCs can't inheriently hit the target we don't add bane/magic dmg which isn't exactly the same as PCs + uint32 eleBane = 0; + if(weapon){ + if(weapon->BaneDmgBody == other->GetBodyType()){ + eleBane += weapon->BaneDmgAmt; + } + + if(weapon->BaneDmgRace == other->GetRace()){ + eleBane += weapon->BaneDmgRaceAmt; + } + + if(weapon->ElemDmgAmt){ + eleBane += (weapon->ElemDmgAmt * other->ResistSpell(weapon->ElemDmgType, 0, this) / 100); + } + } + + if(!RuleB(NPC, UseItemBonusesForNonPets)){ + if(!GetOwner()){ + eleBane = 0; + } + } + + uint8 otherlevel = other->GetLevel(); + uint8 mylevel = this->GetLevel(); + + otherlevel = otherlevel ? otherlevel : 1; + mylevel = mylevel ? mylevel : 1; + + //instead of calcing damage in floats lets just go straight to ints + if(RuleB(Combat, UseIntervalAC)) + damage = (max_dmg+eleBane); + else + damage = zone->random.Int((min_dmg+eleBane),(max_dmg+eleBane)); + + + //check if we're hitting above our max or below it. + if((min_dmg+eleBane) != 0 && damage < (min_dmg+eleBane)) { + mlog(COMBAT__DAMAGE, "Damage (%d) is below min (%d). Setting to min.", damage, (min_dmg+eleBane)); + damage = (min_dmg+eleBane); + } + if((max_dmg+eleBane) != 0 && damage > (max_dmg+eleBane)) { + mlog(COMBAT__DAMAGE, "Damage (%d) is above max (%d). Setting to max.", damage, (max_dmg+eleBane)); + damage = (max_dmg+eleBane); + } + + damage = mod_npc_damage(damage, skillinuse, Hand, weapon, other); + + int32 hate = damage; + if(IsPet()) + { + hate = hate * 100 / GetDamageTable(skillinuse); + } + + if(other->IsClient() && other->CastToClient()->IsSitting()) { + mlog(COMBAT__DAMAGE, "Client %s is sitting. Hitting for max damage (%d).", other->GetName(), (max_dmg+eleBane)); + damage = (max_dmg+eleBane); + damage += (itembonuses.HeroicSTR / 10) + (damage * other->GetSkillDmgTaken(skillinuse) / 100) + GetSkillDmgAmt(skillinuse); + + if(opts) { + damage *= opts->damage_percent; + damage += opts->damage_flat; + hate *= opts->hate_percent; + hate += opts->hate_flat; + } + + mlog(COMBAT__HITS, "Generating hate %d towards %s", hate, GetName()); + // now add done damage to the hate list + other->AddToHateList(this, hate); + + } else { + + int hit_chance_bonus = 0; + + if(opts) { + damage *= opts->damage_percent; + damage += opts->damage_flat; + hate *= opts->hate_percent; + hate += opts->hate_flat; + hit_chance_bonus += opts->hit_chance; + } + + if(!other->CheckHitChance(this, skillinuse, Hand, hit_chance_bonus)) { + damage = 0; //miss + } else { //hit, check for damage avoidance + other->AvoidDamage(this, damage); + other->MeleeMitigation(this, damage, min_dmg+eleBane, opts); + if(damage > 0) { + CommonOutgoingHitSuccess(other, damage, skillinuse); + } + mlog(COMBAT__HITS, "Generating hate %d towards %s", hate, GetName()); + // now add done damage to the hate list + if(damage > 0) + other->AddToHateList(this, hate); + else + other->AddToHateList(this, 0); + } + } + + mlog(COMBAT__DAMAGE, "Final damage against %s: %d", other->GetName(), damage); + + if(other->IsClient() && IsPet() && GetOwner()->IsClient()) { + //pets do half damage to clients in pvp + damage=damage/2; + } + } + else + damage = -5; + + //cant riposte a riposte + if (bRiposte && damage == -3) { + mlog(COMBAT__DAMAGE, "Riposte of riposte canceled."); + return false; + } + + if(GetHP() > 0 && !other->HasDied()) { + other->Damage(this, damage, SPELL_UNKNOWN, skillinuse, false); // Not avoidable client already had thier chance to Avoid + } else + return false; + + if (HasDied()) //killed by damage shield ect + return false; + + MeleeLifeTap(damage); + + CommonBreakInvisible(); + + //I doubt this works... + if (!GetTarget()) + return true; //We killed them + + if(!bRiposte && !other->HasDied()) { + TryWeaponProc(nullptr, weapon, other, Hand); //no weapon + + if (!other->HasDied()) + TrySpellProc(nullptr, weapon, other, Hand); + + if (damage > 0 && HasSkillProcSuccess() && !other->HasDied()) + TrySkillProc(other, skillinuse, 0, true, Hand); + } + + if(GetHP() > 0 && !other->HasDied()) + TriggerDefensiveProcs(nullptr, other, Hand, damage); + + // now check ripostes + if (damage == -3) { // riposting + DoRiposte(other); + } + + if (damage > 0) + return true; + + else + return false; +} + +void NPC::Damage(Mob* other, int32 damage, uint16 spell_id, SkillUseTypes attack_skill, bool avoidable, int8 buffslot, bool iBuffTic) { + if(spell_id==0) + spell_id = SPELL_UNKNOWN; + + //handle EVENT_ATTACK. Resets after we have not been attacked for 12 seconds + if(attacked_timer.Check()) + { + mlog(COMBAT__HITS, "Triggering EVENT_ATTACK due to attack by %s", other->GetName()); + parse->EventNPC(EVENT_ATTACK, this, other, "", 0); + } + attacked_timer.Start(CombatEventTimer_expire); + + if (!IsEngaged()) + zone->AddAggroMob(); + + if(GetClass() == LDON_TREASURE) + { + if(IsLDoNLocked() && GetLDoNLockedSkill() != LDoNTypeMechanical) + { + damage = -5; + } + else + { + if(IsLDoNTrapped()) + { + Message_StringID(13, LDON_ACCIDENT_SETOFF2); + SpellFinished(GetLDoNTrapSpellID(), other, 10, 0, -1, spells[GetLDoNTrapSpellID()].ResistDiff, false); + SetLDoNTrapSpellID(0); + SetLDoNTrapped(false); + SetLDoNTrapDetected(false); + } + } + } + + //do a majority of the work... + CommonDamage(other, damage, spell_id, attack_skill, avoidable, buffslot, iBuffTic); + + if(damage > 0) { + //see if we are gunna start fleeing + if(!IsPet()) CheckFlee(); + } +} + +bool NPC::Death(Mob* killerMob, int32 damage, uint16 spell, SkillUseTypes attack_skill) { + mlog(COMBAT__HITS, "Fatal blow dealt by %s with %d damage, spell %d, skill %d", killerMob->GetName(), damage, spell, attack_skill); + + Mob *oos = nullptr; + if(killerMob) { + oos = killerMob->GetOwnerOrSelf(); + + char buffer[48] = { 0 }; + snprintf(buffer, 47, "%d %d %d %d", killerMob ? killerMob->GetID() : 0, damage, spell, static_cast(attack_skill)); + if(parse->EventNPC(EVENT_DEATH, this, oos, buffer, 0) != 0) + { + if(GetHP() < 0) { + SetHP(0); + } + return false; + } + + if(killerMob && killerMob->IsClient() && (spell != SPELL_UNKNOWN) && damage > 0) { + char val1[20]={0}; + entity_list.MessageClose_StringID(this, false, 100, MT_NonMelee, HIT_NON_MELEE, + killerMob->GetCleanName(), GetCleanName(), ConvertArray(damage, val1)); + } + } else { + + char buffer[48] = { 0 }; + snprintf(buffer, 47, "%d %d %d %d", killerMob ? killerMob->GetID() : 0, damage, spell, static_cast(attack_skill)); + if(parse->EventNPC(EVENT_DEATH, this, nullptr, buffer, 0) != 0) + { + if(GetHP() < 0) { + SetHP(0); + } + return false; + } + } + + if (IsEngaged()) + { + zone->DelAggroMob(); +#if EQDEBUG >= 11 + LogFile->write(EQEMuLog::Debug,"NPC::Death() Mobs currently Aggro %i", zone->MobsAggroCount()); +#endif + } + SetHP(0); + SetPet(0); + + if (GetSwarmOwner()){ + Mob* owner = entity_list.GetMobID(GetSwarmOwner()); + if (owner) + owner->SetTempPetCount(owner->GetTempPetCount() - 1); + } + + Mob* killer = GetHateDamageTop(this); + + entity_list.RemoveFromTargets(this, p_depop); + + if(p_depop == true) + return false; + + HasAISpellEffects = false; + BuffFadeAll(); + uint8 killed_level = GetLevel(); + + EQApplicationPacket* app= new EQApplicationPacket(OP_Death,sizeof(Death_Struct)); + Death_Struct* d = (Death_Struct*)app->pBuffer; + d->spawn_id = GetID(); + d->killer_id = killerMob ? killerMob->GetID() : 0; + d->bindzoneid = 0; + d->spell_id = spell == SPELL_UNKNOWN ? 0xffffffff : spell; + d->attack_skill = SkillDamageTypes[attack_skill]; + d->damage = damage; + app->priority = 6; + entity_list.QueueClients(killerMob, app, false); + + if(respawn2) { + respawn2->DeathReset(1); + } + + if (killerMob) { + if(GetClass() != LDON_TREASURE) + hate_list.Add(killerMob, damage); + } + + safe_delete(app); + + Mob *give_exp = hate_list.GetDamageTop(this); + + if(give_exp == nullptr) + give_exp = killer; + + if(give_exp && give_exp->HasOwner()) { + + bool ownerInGroup = false; + if((give_exp->HasGroup() && give_exp->GetGroup()->IsGroupMember(give_exp->GetUltimateOwner())) + || (give_exp->IsPet() && (give_exp->GetOwner()->IsClient() + || ( give_exp->GetOwner()->HasGroup() && give_exp->GetOwner()->GetGroup()->IsGroupMember(give_exp->GetOwner()->GetUltimateOwner()))))) + ownerInGroup = true; + + give_exp = give_exp->GetUltimateOwner(); + +#ifdef BOTS + if(!RuleB(Bots, BotGroupXP) && !ownerInGroup) { + give_exp = nullptr; + } +#endif //BOTS + } + + int PlayerCount = 0; // QueryServ Player Counting + + Client *give_exp_client = nullptr; + if(give_exp && give_exp->IsClient()) + give_exp_client = give_exp->CastToClient(); + + bool IsLdonTreasure = (this->GetClass() == LDON_TREASURE); + if (give_exp_client && !IsCorpse()) + { + Group *kg = entity_list.GetGroupByClient(give_exp_client); + Raid *kr = entity_list.GetRaidByClient(give_exp_client); + + int32 finalxp = EXP_FORMULA; + finalxp = give_exp_client->mod_client_xp(finalxp, this); + + if(kr) + { + if(!IsLdonTreasure && MerchantType == 0) { + kr->SplitExp((finalxp), this); + if(killerMob && (kr->IsRaidMember(killerMob->GetName()) || kr->IsRaidMember(killerMob->GetUltimateOwner()->GetName()))) + killerMob->TrySpellOnKill(killed_level,spell); + } + /* Send the EVENT_KILLED_MERIT event for all raid members */ + for (int i = 0; i < MAX_RAID_MEMBERS; i++) { + if (kr->members[i].member != nullptr && kr->members[i].member->IsClient()) { // If Group Member is Client + Client *c = kr->members[i].member; + parse->EventNPC(EVENT_KILLED_MERIT, this, c, "killed", 0); + + if(RuleB(NPC, EnableMeritBasedFaction)) + c->SetFactionLevel(c->CharacterID(), GetNPCFactionID(), c->GetBaseClass(), c->GetBaseRace(), c->GetDeity()); + + mod_npc_killed_merit(kr->members[i].member); + + if(RuleB(TaskSystem, EnableTaskSystem)) + kr->members[i].member->UpdateTasksOnKill(GetNPCTypeID()); + PlayerCount++; + } + } + + // QueryServ Logging - Raid Kills + if(RuleB(QueryServ, PlayerLogNPCKills)){ + ServerPacket* pack = new ServerPacket(ServerOP_QSPlayerLogNPCKills, sizeof(QSPlayerLogNPCKill_Struct) + (sizeof(QSPlayerLogNPCKillsPlayers_Struct) * PlayerCount)); + PlayerCount = 0; + QSPlayerLogNPCKill_Struct* QS = (QSPlayerLogNPCKill_Struct*) pack->pBuffer; + QS->s1.NPCID = this->GetNPCTypeID(); + QS->s1.ZoneID = this->GetZoneID(); + QS->s1.Type = 2; // Raid Fight + for (int i = 0; i < MAX_RAID_MEMBERS; i++) { + if (kr->members[i].member != nullptr && kr->members[i].member->IsClient()) { // If Group Member is Client + Client *c = kr->members[i].member; + QS->Chars[PlayerCount].char_id = c->CharacterID(); + PlayerCount++; + } + } + worldserver.SendPacket(pack); // Send Packet to World + safe_delete(pack); + } + // End QueryServ Logging + + } + else if (give_exp_client->IsGrouped() && kg != nullptr) + { + if(!IsLdonTreasure && MerchantType == 0) { + kg->SplitExp((finalxp), this); + if(killerMob && (kg->IsGroupMember(killerMob->GetName()) || kg->IsGroupMember(killerMob->GetUltimateOwner()->GetName()))) + killerMob->TrySpellOnKill(killed_level,spell); + } + /* Send the EVENT_KILLED_MERIT event and update kill tasks + * for all group members */ + for (int i = 0; i < MAX_GROUP_MEMBERS; i++) { + if (kg->members[i] != nullptr && kg->members[i]->IsClient()) { // If Group Member is Client + Client *c = kg->members[i]->CastToClient(); + parse->EventNPC(EVENT_KILLED_MERIT, this, c, "killed", 0); + + if(RuleB(NPC, EnableMeritBasedFaction)) + c->SetFactionLevel(c->CharacterID(), GetNPCFactionID(), c->GetBaseClass(), c->GetBaseRace(), c->GetDeity()); + + mod_npc_killed_merit(c); + + if(RuleB(TaskSystem, EnableTaskSystem)) + c->UpdateTasksOnKill(GetNPCTypeID()); + + PlayerCount++; + } + } + + // QueryServ Logging - Group Kills + if(RuleB(QueryServ, PlayerLogNPCKills)){ + ServerPacket* pack = new ServerPacket(ServerOP_QSPlayerLogNPCKills, sizeof(QSPlayerLogNPCKill_Struct) + (sizeof(QSPlayerLogNPCKillsPlayers_Struct) * PlayerCount)); + PlayerCount = 0; + QSPlayerLogNPCKill_Struct* QS = (QSPlayerLogNPCKill_Struct*) pack->pBuffer; + QS->s1.NPCID = this->GetNPCTypeID(); + QS->s1.ZoneID = this->GetZoneID(); + QS->s1.Type = 1; // Group Fight + for (int i = 0; i < MAX_GROUP_MEMBERS; i++) { + if (kg->members[i] != nullptr && kg->members[i]->IsClient()) { // If Group Member is Client + Client *c = kg->members[i]->CastToClient(); + QS->Chars[PlayerCount].char_id = c->CharacterID(); + PlayerCount++; + } + } + worldserver.SendPacket(pack); // Send Packet to World + safe_delete(pack); + } + // End QueryServ Logging + } + else + { + if(!IsLdonTreasure && MerchantType == 0) { + int conlevel = give_exp->GetLevelCon(GetLevel()); + if (conlevel != CON_GREEN) + { + if(!GetOwner() || (GetOwner() && !GetOwner()->IsClient())) + { + give_exp_client->AddEXP((finalxp), conlevel); + if(killerMob && (killerMob->GetID() == give_exp_client->GetID() || killerMob->GetUltimateOwner()->GetID() == give_exp_client->GetID())) + killerMob->TrySpellOnKill(killed_level,spell); + } + } + } + /* Send the EVENT_KILLED_MERIT event */ + parse->EventNPC(EVENT_KILLED_MERIT, this, give_exp_client, "killed", 0); + + if(RuleB(NPC, EnableMeritBasedFaction)) + give_exp_client->SetFactionLevel(give_exp_client->CharacterID(), GetNPCFactionID(), give_exp_client->GetBaseClass(), + give_exp_client->GetBaseRace(), give_exp_client->GetDeity()); + + mod_npc_killed_merit(give_exp_client); + + if(RuleB(TaskSystem, EnableTaskSystem)) + give_exp_client->UpdateTasksOnKill(GetNPCTypeID()); + + // QueryServ Logging - Solo + if(RuleB(QueryServ, PlayerLogNPCKills)){ + ServerPacket* pack = new ServerPacket(ServerOP_QSPlayerLogNPCKills, sizeof(QSPlayerLogNPCKill_Struct) + (sizeof(QSPlayerLogNPCKillsPlayers_Struct) * 1)); + QSPlayerLogNPCKill_Struct* QS = (QSPlayerLogNPCKill_Struct*) pack->pBuffer; + QS->s1.NPCID = this->GetNPCTypeID(); + QS->s1.ZoneID = this->GetZoneID(); + QS->s1.Type = 0; // Solo Fight + Client *c = give_exp_client; + QS->Chars[0].char_id = c->CharacterID(); + PlayerCount++; + worldserver.SendPacket(pack); // Send Packet to World + safe_delete(pack); + } + // End QueryServ Logging + } + } + + //do faction hits even if we are a merchant, so long as a player killed us + if(give_exp_client && !RuleB(NPC, EnableMeritBasedFaction)) + hate_list.DoFactionHits(GetNPCFactionID()); + + if (!HasOwner() && !IsMerc() && class_ != MERCHANT && class_ != ADVENTUREMERCHANT && !GetSwarmInfo() + && MerchantType == 0 && killer && (killer->IsClient() || (killer->HasOwner() && killer->GetUltimateOwner()->IsClient()) || + (killer->IsNPC() && killer->CastToNPC()->GetSwarmInfo() && killer->CastToNPC()->GetSwarmInfo()->GetOwner() && killer->CastToNPC()->GetSwarmInfo()->GetOwner()->IsClient()))) + { + if(killer != 0) + { + if(killer->GetOwner() != 0 && killer->GetOwner()->IsClient()) + killer = killer->GetOwner(); + + if(!killer->CastToClient()->GetGM() && killer->IsClient()) + this->CheckMinMaxLevel(killer); + } + entity_list.RemoveFromAutoXTargets(this); + uint16 emoteid = this->GetEmoteID(); + Corpse* corpse = new Corpse(this, &itemlist, GetNPCTypeID(), &NPCTypedata,level>54?RuleI(NPC,MajorNPCCorpseDecayTimeMS):RuleI(NPC,MinorNPCCorpseDecayTimeMS)); + entity_list.LimitRemoveNPC(this); + entity_list.AddCorpse(corpse, GetID()); + + entity_list.UnMarkNPC(GetID()); + entity_list.RemoveNPC(GetID()); + this->SetID(0); + if(killer != 0 && emoteid != 0) + corpse->CastToNPC()->DoNPCEmote(AFTERDEATH, emoteid); + if(killer != 0 && killer->IsClient()) { + corpse->AllowPlayerLoot(killer, 0); + if(killer->IsGrouped()) { + Group* group = entity_list.GetGroupByClient(killer->CastToClient()); + if(group != 0) { + for(int i=0;i<6;i++) { // Doesnt work right, needs work + if(group->members[i] != nullptr) { + corpse->AllowPlayerLoot(group->members[i],i); + } + } + } + } + else if(killer->IsRaidGrouped()){ + Raid* r = entity_list.GetRaidByClient(killer->CastToClient()); + if(r){ + int i = 0; + for(int x = 0; x < MAX_RAID_MEMBERS; x++) + { + switch(r->GetLootType()) + { + case 0: + case 1: + if(r->members[x].member && r->members[x].IsRaidLeader){ + corpse->AllowPlayerLoot(r->members[x].member, i); + i++; + } + break; + case 2: + if(r->members[x].member && r->members[x].IsRaidLeader){ + corpse->AllowPlayerLoot(r->members[x].member, i); + i++; + } + else if(r->members[x].member && r->members[x].IsGroupLeader){ + corpse->AllowPlayerLoot(r->members[x].member, i); + i++; + } + break; + case 3: + if(r->members[x].member && r->members[x].IsLooter){ + corpse->AllowPlayerLoot(r->members[x].member, i); + i++; + } + break; + case 4: + if(r->members[x].member) + { + corpse->AllowPlayerLoot(r->members[x].member, i); + i++; + } + break; + } + } + } + } + } + + if(zone && zone->adv_data) + { + ServerZoneAdventureDataReply_Struct *sr = (ServerZoneAdventureDataReply_Struct*)zone->adv_data; + if(sr->type == Adventure_Kill) + { + zone->DoAdventureCountIncrease(); + } + else if(sr->type == Adventure_Assassinate) + { + if(sr->data_id == GetNPCTypeID()) + { + zone->DoAdventureCountIncrease(); + } + else + { + zone->DoAdventureAssassinationCountIncrease(); + } + } + } + } + else + entity_list.RemoveFromXTargets(this); + + // Parse quests even if we're killed by an NPC + if(oos) { + mod_npc_killed(oos); + + uint16 emoteid = this->GetEmoteID(); + if(emoteid != 0) + this->DoNPCEmote(ONDEATH, emoteid); + if(oos->IsNPC()) + { + parse->EventNPC(EVENT_NPC_SLAY, oos->CastToNPC(), this, "", 0); + uint16 emoteid = oos->GetEmoteID(); + if(emoteid != 0) + oos->CastToNPC()->DoNPCEmote(KILLEDNPC, emoteid); + killerMob->TrySpellOnKill(killed_level, spell); + } + } + + WipeHateList(); + p_depop = true; + if(killerMob && killerMob->GetTarget() == this) //we can kill things without having them targeted + killerMob->SetTarget(nullptr); //via AE effects and such.. + + entity_list.UpdateFindableNPCState(this, true); + + char buffer[48] = { 0 }; + snprintf(buffer, 47, "%d %d %d %d", killerMob ? killerMob->GetID() : 0, damage, spell, static_cast(attack_skill)); + parse->EventNPC(EVENT_DEATH_COMPLETE, this, oos, buffer, 0); + return true; +} + +void Mob::AddToHateList(Mob* other, int32 hate, int32 damage, bool iYellForHelp, bool bFrenzy, bool iBuffTic) { + + assert(other != nullptr); + + if (other == this) + return; + + if(damage < 0){ + hate = 1; + } + + bool wasengaged = IsEngaged(); + Mob* owner = other->GetOwner(); + Mob* mypet = this->GetPet(); + Mob* myowner = this->GetOwner(); + Mob* targetmob = this->GetTarget(); + + if(other){ + AddRampage(other); + int hatemod = 100 + other->spellbonuses.hatemod + other->itembonuses.hatemod + other->aabonuses.hatemod; + + int32 shieldhatemod = other->spellbonuses.ShieldEquipHateMod + other->itembonuses.ShieldEquipHateMod + other->aabonuses.ShieldEquipHateMod; + + if (shieldhatemod && other->HasShieldEquiped()) + hatemod += shieldhatemod; + + if(hatemod < 1) + hatemod = 1; + hate = ((hate * (hatemod))/100); + } + + if(IsPet() && GetOwner() && GetOwner()->GetAA(aaPetDiscipline) && IsHeld() && !IsFocused()) { //ignore aggro if hold and !focus + return; + } + + if(IsPet() && GetOwner() && GetOwner()->GetAA(aaPetDiscipline) && IsHeld() && GetOwner()->GetAA(aaAdvancedPetDiscipline) >= 1 && IsFocused()) { + if (!targetmob) + return; + } + + if (other->IsNPC() && (other->IsPet() || other->CastToNPC()->GetSwarmOwner() > 0)) + TryTriggerOnValueAmount(false, false, false, true); + + if(IsClient() && !IsAIControlled()) + return; + + if(IsFamiliar() || GetSpecialAbility(IMMUNE_AGGRO)) + return; + + if (other == myowner) + return; + + if(other->GetSpecialAbility(IMMUNE_AGGRO_ON)) + return; + + if(GetSpecialAbility(NPC_TUNNELVISION)) { + int tv_mod = GetSpecialAbilityParam(NPC_TUNNELVISION, 0); + + Mob *top = GetTarget(); + if(top && top != other) { + if(tv_mod) { + float tv = tv_mod / 100.0f; + hate *= tv; + } else { + hate *= RuleR(Aggro, TunnelVisionAggroMod); + } + } + } + + auto otherPosition = xyz_location(other->GetX(), other->GetY(), other->GetZ()); + if(IsNPC() && CastToNPC()->IsUnderwaterOnly() && zone->HasWaterMap()) { + if(!zone->watermap->InLiquid(otherPosition)) { + return; + } + } + // first add self + + // The damage on the hate list is used to award XP to the killer. This check is to prevent Killstealing. + // e.g. Mob has 5000 hit points, Player A melees it down to 500 hp, Player B executes a headshot (10000 damage). + // If we add 10000 damage, Player B would get the kill credit, so we only award damage credit to player B of the + // amount of HP the mob had left. + // + if(damage > GetHP()) + damage = GetHP(); + + if (spellbonuses.ImprovedTaunt[1] && (GetLevel() < spellbonuses.ImprovedTaunt[0]) + && other && (buffs[spellbonuses.ImprovedTaunt[2]].casterid != other->GetID())) + hate = (hate*spellbonuses.ImprovedTaunt[1])/100; + + hate_list.Add(other, hate, damage, bFrenzy, !iBuffTic); + + if(other->IsClient()) + other->CastToClient()->AddAutoXTarget(this); + +#ifdef BOTS + // if other is a bot, add the bots client to the hate list + if(other->IsBot()) { + if(other->CastToBot()->GetBotOwner() && other->CastToBot()->GetBotOwner()->CastToClient()->GetFeigned()) { + AddFeignMemory(other->CastToBot()->GetBotOwner()->CastToClient()); + } + else { + if(!hate_list.IsOnHateList(other->CastToBot()->GetBotOwner())) + hate_list.Add(other->CastToBot()->GetBotOwner(), 0, 0, false, true); + } + } +#endif //BOTS + + + // if other is a merc, add the merc client to the hate list + if(other->IsMerc()) { + if(other->CastToMerc()->GetMercOwner() && other->CastToMerc()->GetMercOwner()->CastToClient()->GetFeigned()) { + AddFeignMemory(other->CastToMerc()->GetMercOwner()->CastToClient()); + } + else { + if(!hate_list.IsOnHateList(other->CastToMerc()->GetMercOwner())) + hate_list.Add(other->CastToMerc()->GetMercOwner(), 0, 0, false, true); + } + } //MERC + + // then add pet owner if there's one + if (owner) { // Other is a pet, add him and it + // EverHood 6/12/06 + // Can't add a feigned owner to hate list + if(owner->IsClient() && owner->CastToClient()->GetFeigned()) { + //they avoid hate due to feign death... + } else { + // cb:2007-08-17 + // owner must get on list, but he's not actually gained any hate yet + if(!owner->GetSpecialAbility(IMMUNE_AGGRO)) + { + hate_list.Add(owner, 0, 0, false, !iBuffTic); + if(owner->IsClient()) + owner->CastToClient()->AddAutoXTarget(this); + } + } + } + + if (mypet && (!(GetAA(aaPetDiscipline) && mypet->IsHeld()))) { // I have a pet, add other to it + if(!mypet->IsFamiliar() && !mypet->GetSpecialAbility(IMMUNE_AGGRO)) + mypet->hate_list.Add(other, 0, 0, bFrenzy); + } else if (myowner) { // I am a pet, add other to owner if it's NPC/LD + if (myowner->IsAIControlled() && !myowner->GetSpecialAbility(IMMUNE_AGGRO)) + myowner->hate_list.Add(other, 0, 0, bFrenzy); + } + + if (other->GetTempPetCount()) + entity_list.AddTempPetsToHateList(other, this, bFrenzy); + + if (!wasengaged) { + if(IsNPC() && other->IsClient() && other->CastToClient()) + parse->EventNPC(EVENT_AGGRO, this->CastToNPC(), other, "", 0); + AI_Event_Engaged(other, iYellForHelp); + } +} + +// solar: this is called from Damage() when 'this' is attacked by 'other. +// 'this' is the one being attacked +// 'other' is the attacker +// a damage shield causes damage (or healing) to whoever attacks the wearer +// a reverse ds causes damage to the wearer whenever it attack someone +// given this, a reverse ds must be checked each time the wearer is attacking +// and not when they're attacked +//a damage shield on a spell is a negative value but on an item it's a positive value so add the spell value and subtract the item value to get the end ds value +void Mob::DamageShield(Mob* attacker, bool spell_ds) { + + if(!attacker || this == attacker) + return; + + int DS = 0; + int rev_ds = 0; + uint16 spellid = 0; + + if(!spell_ds) + { + DS = spellbonuses.DamageShield; + rev_ds = attacker->spellbonuses.ReverseDamageShield; + + if(spellbonuses.DamageShieldSpellID != 0 && spellbonuses.DamageShieldSpellID != SPELL_UNKNOWN) + spellid = spellbonuses.DamageShieldSpellID; + } + else { + DS = spellbonuses.SpellDamageShield; + rev_ds = 0; + // This ID returns "you are burned", seemed most appropriate for spell DS + spellid = 2166; + } + + if(DS == 0 && rev_ds == 0) + return; + + mlog(COMBAT__HITS, "Applying Damage Shield of value %d to %s", DS, attacker->GetName()); + + //invert DS... spells yield negative values for a true damage shield + if(DS < 0) { + if(!spell_ds) { + + DS += aabonuses.DamageShield; //Live AA - coat of thistles. (negative value) + DS -= itembonuses.DamageShield; //+Damage Shield should only work when you already have a DS spell + + //Spell data for damage shield mitigation shows a negative value for spells for clients and positive + //value for spells that effect pets. Unclear as to why. For now will convert all positive to be consistent. + if (attacker->IsOffHandAtk()){ + int32 mitigation = attacker->itembonuses.DSMitigationOffHand + + attacker->spellbonuses.DSMitigationOffHand + + attacker->aabonuses.DSMitigationOffHand; + DS -= DS*mitigation/100; + } + DS -= DS * attacker->itembonuses.DSMitigation / 100; + } + attacker->Damage(this, -DS, spellid, SkillAbjuration/*hackish*/, false); + //we can assume there is a spell now + EQApplicationPacket* outapp = new EQApplicationPacket(OP_Damage, sizeof(CombatDamage_Struct)); + CombatDamage_Struct* cds = (CombatDamage_Struct*)outapp->pBuffer; + cds->target = attacker->GetID(); + cds->source = GetID(); + cds->type = spellbonuses.DamageShieldType; + cds->spellid = 0x0; + cds->damage = DS; + entity_list.QueueCloseClients(this, outapp); + safe_delete(outapp); + } else if (DS > 0 && !spell_ds) { + //we are healing the attacker... + attacker->HealDamage(DS); + //TODO: send a packet??? + } + + //Reverse DS + //this is basically a DS, but the spell is on the attacker, not the attackee + //if we've gotten to this point, we know we know "attacker" hit "this" (us) for damage & we aren't invulnerable + uint16 rev_ds_spell_id = SPELL_UNKNOWN; + + if(spellbonuses.ReverseDamageShieldSpellID != 0 && spellbonuses.ReverseDamageShieldSpellID != SPELL_UNKNOWN) + rev_ds_spell_id = spellbonuses.ReverseDamageShieldSpellID; + + if(rev_ds < 0) { + mlog(COMBAT__HITS, "Applying Reverse Damage Shield of value %d to %s", rev_ds, attacker->GetName()); + attacker->Damage(this, -rev_ds, rev_ds_spell_id, SkillAbjuration/*hackish*/, false); //"this" (us) will get the hate, etc. not sure how this works on Live, but it'll works for now, and tanks will love us for this + //do we need to send a damage packet here also? + } +} + +uint8 Mob::GetWeaponDamageBonus( const Item_Struct *Weapon ) +{ + // This function calculates and returns the damage bonus for the weapon identified by the parameter "Weapon". + // Modified 9/21/2008 by Cantus + + + // Assert: This function should only be called for hits by the mainhand, as damage bonuses apply only to the + // weapon in the primary slot. Be sure to check that Hand == MainPrimary before calling. + + // Assert: The caller should ensure that Weapon is actually a weapon before calling this function. + // The ItemInst::IsWeapon() method can be used to quickly determine this. + + // Assert: This function should not be called if the player's level is below 28, as damage bonuses do not begin + // to apply until level 28. + + // Assert: This function should not be called unless the player is a melee class, as casters do not receive a damage bonus. + + + if( Weapon == nullptr || Weapon->ItemType == ItemType1HSlash || Weapon->ItemType == ItemType1HBlunt || Weapon->ItemType == ItemTypeMartial || Weapon->ItemType == ItemType1HPiercing ) + { + // The weapon in the player's main (primary) hand is a one-handed weapon, or there is no item equipped at all. + // + // According to player posts on Allakhazam, 1H damage bonuses apply to bare fists (nothing equipped in the mainhand, + // as indicated by Weapon == nullptr). + // + // The following formula returns the correct damage bonus for all 1H weapons: + + return (uint8) ((GetLevel() - 25) / 3); + } + + // If we've gotten to this point, the weapon in the mainhand is a two-handed weapon. + // Calculating damage bonuses for 2H weapons is more complicated, as it's based on PC level AND the delay of the weapon. + // The formula to calculate 2H bonuses is HIDEOUS. It's a huge conglomeration of ternary operators and multiple operations. + // + // The following is a hybrid approach. In cases where the Level and Delay merit a formula that does not use many operators, + // the formula is used. In other cases, lookup tables are used for speed. + // Though the following code may look bloated and ridiculous, it's actually a very efficient way of calculating these bonuses. + + // Player Level is used several times in the code below, so save it into a variable. + // If GetLevel() were an ordinary function, this would DEFINITELY make sense, as it'd cut back on all of the function calling + // overhead involved with multiple calls to GetLevel(). But in this case, GetLevel() is a simple, inline accessor method. + // So it probably doesn't matter. If anyone knows for certain that there is no overhead involved with calling GetLevel(), + // as I suspect, then please feel free to delete the following line, and replace all occurences of "ucPlayerLevel" with "GetLevel()". + uint8 ucPlayerLevel = (uint8) GetLevel(); + + + // The following may look cleaner, and would certainly be easier to understand, if it was + // a simple 53x150 cell matrix. + // + // However, that would occupy 7,950 Bytes of memory (7.76 KB), and would likely result + // in "thrashing the cache" when performing lookups. + // + // Initially, I thought the best approach would be to reverse-engineer the formula used by + // Sony/Verant to calculate these 2H weapon damage bonuses. But the more than Reno and I + // worked on figuring out this formula, the more we're concluded that the formula itself ugly + // (that is, it contains so many operations and conditionals that it's fairly CPU intensive). + // Because of that, we're decided that, in most cases, a lookup table is the most efficient way + // to calculate these damage bonuses. + // + // The code below is a hybrid between a pure formulaic approach and a pure, brute-force + // lookup table. In cases where a formula is the best bet, I use a formula. In other places + // where a formula would be ugly, I use a lookup table in the interests of speed. + + + if( Weapon->Delay <= 27 ) + { + // Damage Bonuses for all 2H weapons with delays of 27 or less are identical. + // They are the same as the damage bonus would be for a corresponding 1H weapon, plus one. + // This formula applies to all levels 28-80, and will probably continue to apply if + + // the level cap on Live ever is increased beyond 80. + + return (ucPlayerLevel - 22) / 3; + } + + + if( ucPlayerLevel == 65 && Weapon->Delay <= 59 ) + { + // Consider these two facts: + // * Level 65 is the maximum level on many EQ Emu servers. + // * If you listed the levels of all characters logged on to a server, odds are that the number you'll + // see most frequently is level 65. That is, there are more level 65 toons than any other single level. + // + // Therefore, if we can optimize this function for level 65 toons, we're speeding up the server! + // + // With that goal in mind, I create an array of Damage Bonuses for level 65 characters wielding 2H weapons with + // delays between 28 and 59 (inclusive). I suspect that this one small lookup array will therefore handle + // many of the calls to this function. + + static const uint8 ucLevel65DamageBonusesForDelays28to59[] = {35, 35, 36, 36, 37, 37, 38, 38, 39, 39, 40, 40, 42, 42, 42, 45, 45, 47, 48, 49, 49, 51, 51, 52, 53, 54, 54, 56, 56, 57, 58, 59}; + + return ucLevel65DamageBonusesForDelays28to59[Weapon->Delay-28]; + } + + + if( ucPlayerLevel > 65 ) + { + if( ucPlayerLevel > 80 ) + { + // As level 80 is currently the highest achievable level on Live, we only include + // damage bonus information up to this level. + // + // If there is a custom EQEmu server that allows players to level beyond 80, the + // damage bonus for their 2H weapons will simply not increase beyond their damage + // bonus at level 80. + + ucPlayerLevel = 80; + } + + // Lucy does not list a chart of damage bonuses for players levels 66+, + // so my original version of this function just applied the level 65 damage + // bonus for level 66+ toons. That sucked for higher level toons, as their + // 2H weapons stopped ramping up in DPS as they leveled past 65. + // + // Thanks to the efforts of two guys, this is no longer the case: + // + // Janusd (Zetrakyl) ran a nifty query against the PEQ item database to list + // the name of an example 2H weapon that represents each possible unique 2H delay. + // + // Romai then wrote an excellent script to automatically look up each of those + // weapons, open the Lucy item page associated with it, and iterate through all + // levels in the range 66 - 80. He saved the damage bonus for that weapon for + // each level, and that forms the basis of the lookup tables below. + + if( Weapon->Delay <= 59 ) + { + static const uint8 ucDelay28to59Levels66to80[32][15]= + { + /* Level: */ + /* 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80 */ + + {36, 37, 38, 39, 41, 42, 43, 44, 45, 47, 49, 49, 49, 50, 53}, /* Delay = 28 */ + {36, 38, 38, 39, 42, 43, 43, 45, 46, 48, 49, 50, 51, 52, 54}, /* Delay = 29 */ + {37, 38, 39, 40, 43, 43, 44, 46, 47, 48, 50, 51, 52, 53, 55}, /* Delay = 30 */ + {37, 39, 40, 40, 43, 44, 45, 46, 47, 49, 51, 52, 52, 52, 54}, /* Delay = 31 */ + {38, 39, 40, 41, 44, 45, 45, 47, 48, 48, 50, 52, 53, 55, 57}, /* Delay = 32 */ + {38, 40, 41, 41, 44, 45, 46, 48, 49, 50, 52, 53, 54, 56, 58}, /* Delay = 33 */ + {39, 40, 41, 42, 45, 46, 47, 48, 49, 51, 53, 54, 55, 57, 58}, /* Delay = 34 */ + {39, 41, 42, 43, 46, 46, 47, 49, 50, 52, 54, 55, 56, 57, 59}, /* Delay = 35 */ + {40, 41, 42, 43, 46, 47, 48, 50, 51, 53, 55, 55, 56, 58, 60}, /* Delay = 36 */ + {40, 42, 43, 44, 47, 48, 49, 50, 51, 53, 55, 56, 57, 59, 61}, /* Delay = 37 */ + {41, 42, 43, 44, 47, 48, 49, 51, 52, 54, 56, 57, 58, 60, 62}, /* Delay = 38 */ + {41, 43, 44, 45, 48, 49, 50, 52, 53, 55, 57, 58, 59, 61, 63}, /* Delay = 39 */ + {43, 45, 46, 47, 50, 51, 52, 54, 55, 57, 59, 60, 61, 63, 65}, /* Delay = 40 */ + {43, 45, 46, 47, 50, 51, 52, 54, 55, 57, 59, 60, 61, 63, 65}, /* Delay = 41 */ + {44, 46, 47, 48, 51, 52, 53, 55, 56, 58, 60, 61, 62, 64, 66}, /* Delay = 42 */ + {46, 48, 49, 50, 53, 54, 55, 58, 59, 61, 63, 64, 65, 67, 69}, /* Delay = 43 */ + {47, 49, 50, 51, 54, 55, 56, 58, 59, 61, 64, 65, 66, 68, 70}, /* Delay = 44 */ + {48, 50, 51, 52, 56, 57, 58, 60, 61, 63, 65, 66, 68, 70, 72}, /* Delay = 45 */ + {50, 52, 53, 54, 57, 58, 59, 62, 63, 65, 67, 68, 69, 71, 74}, /* Delay = 46 */ + {50, 52, 53, 55, 58, 59, 60, 62, 63, 66, 68, 69, 70, 72, 74}, /* Delay = 47 */ + {51, 53, 54, 55, 58, 60, 61, 63, 64, 66, 69, 69, 71, 73, 75}, /* Delay = 48 */ + {52, 54, 55, 57, 60, 61, 62, 65, 66, 68, 70, 71, 73, 75, 77}, /* Delay = 49 */ + {53, 55, 56, 57, 61, 62, 63, 65, 67, 69, 71, 72, 74, 76, 78}, /* Delay = 50 */ + {53, 55, 57, 58, 61, 62, 64, 66, 67, 69, 72, 73, 74, 77, 79}, /* Delay = 51 */ + {55, 57, 58, 59, 63, 64, 65, 68, 69, 71, 74, 75, 76, 78, 81}, /* Delay = 52 */ + {57, 55, 59, 60, 63, 65, 66, 68, 70, 72, 74, 76, 77, 79, 82}, /* Delay = 53 */ + {56, 58, 59, 61, 64, 65, 67, 69, 70, 73, 75, 76, 78, 80, 82}, /* Delay = 54 */ + {57, 59, 61, 62, 66, 67, 68, 71, 72, 74, 77, 78, 80, 82, 84}, /* Delay = 55 */ + {58, 60, 61, 63, 66, 68, 69, 71, 73, 75, 78, 79, 80, 83, 85}, /* Delay = 56 */ + + /* Important Note: Janusd's search for 2H weapons did not find */ + /* any 2H weapon with a delay of 57. Therefore the values below */ + /* are interpolated, not exact! */ + {59, 61, 62, 64, 67, 69, 70, 72, 74, 76, 77, 78, 81, 84, 86}, /* Delay = 57 INTERPOLATED */ + + {60, 62, 63, 65, 68, 70, 71, 74, 75, 78, 80, 81, 83, 85, 88}, /* Delay = 58 */ + + /* Important Note: Janusd's search for 2H weapons did not find */ + /* any 2H weapon with a delay of 59. Therefore the values below */ + /* are interpolated, not exact! */ + {60, 62, 64, 65, 69, 70, 72, 74, 76, 78, 81, 82, 84, 86, 89}, /* Delay = 59 INTERPOLATED */ + }; + + return ucDelay28to59Levels66to80[Weapon->Delay-28][ucPlayerLevel-66]; + } + else + { + // Delay is 60+ + + const static uint8 ucDelayOver59Levels66to80[6][15] = + { + /* Level: */ + /* 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80 */ + + {61, 63, 65, 66, 70, 71, 73, 75, 77, 79, 82, 83, 85, 87, 90}, /* Delay = 60 */ + {65, 68, 69, 71, 75, 76, 78, 80, 82, 85, 87, 89, 91, 93, 96}, /* Delay = 65 */ + + /* Important Note: Currently, the only 2H weapon with a delay */ + /* of 66 is not player equippable (it's None/None). So I'm */ + /* leaving it commented out to keep this table smaller. */ + //{66, 68, 70, 71, 75, 77, 78, 81, 83, 85, 88, 90, 91, 94, 97}, /* Delay = 66 */ + + {70, 72, 74, 76, 80, 81, 83, 86, 88, 88, 90, 95, 97, 99, 102}, /* Delay = 70 */ + {82, 85, 87, 89, 89, 94, 98, 101, 103, 106, 109, 111, 114, 117, 120}, /* Delay = 85 */ + {90, 93, 96, 98, 103, 105, 107, 111, 113, 116, 120, 122, 125, 128, 131}, /* Delay = 95 */ + + /* Important Note: Currently, the only 2H weapons with delay */ + /* 100 are GM-only items purchased from vendors in Sunset Home */ + /* (cshome). Because they are highly unlikely to be used in */ + /* combat, I'm commenting it out to keep the table smaller. */ + //{95, 98, 101, 103, 108, 110, 113, 116, 119, 122, 126, 128, 131, 134, 138},/* Delay = 100 */ + + {136, 140, 144, 148, 154, 157, 161, 166, 170, 174, 179, 183, 187, 191, 196} /* Delay = 150 */ + }; + + if( Weapon->Delay < 65 ) + { + return ucDelayOver59Levels66to80[0][ucPlayerLevel-66]; + } + else if( Weapon->Delay < 70 ) + { + return ucDelayOver59Levels66to80[1][ucPlayerLevel-66]; + } + else if( Weapon->Delay < 85 ) + { + return ucDelayOver59Levels66to80[2][ucPlayerLevel-66]; + } + else if( Weapon->Delay < 95 ) + { + return ucDelayOver59Levels66to80[3][ucPlayerLevel-66]; + } + else if( Weapon->Delay < 150 ) + { + return ucDelayOver59Levels66to80[4][ucPlayerLevel-66]; + } + else + { + return ucDelayOver59Levels66to80[5][ucPlayerLevel-66]; + } + } + } + + + // If we've gotten to this point in the function without hitting a return statement, + // we know that the character's level is between 28 and 65, and that the 2H weapon's + // delay is 28 or higher. + + // The Damage Bonus values returned by this function (in the level 28-65 range) are + // based on a table of 2H Weapon Damage Bonuses provided by Lucy at the following address: + // http://lucy.allakhazam.com/dmgbonus.html + + if( Weapon->Delay <= 39 ) + { + if( ucPlayerLevel <= 53) + { + // The Damage Bonus for all 2H weapons with delays between 28 and 39 (inclusive) is the same for players level 53 and below... + static const uint8 ucDelay28to39LevelUnder54[] = {1, 1, 2, 3, 3, 3, 4, 5, 5, 6, 6, 6, 8, 8, 8, 9, 9, 10, 11, 11, 11, 12, 13, 14, 16, 17}; + + // As a note: The following formula accurately calculates damage bonuses for 2H weapons with delays in the range 28-39 (inclusive) + // for characters levels 28-50 (inclusive): + // return ( (ucPlayerLevel - 22) / 3 ) + ( (ucPlayerLevel - 25) / 5 ); + // + // However, the small lookup array used above is actually much faster. So we'll just use it instead of the formula + // + // (Thanks to Reno for helping figure out the above formula!) + + return ucDelay28to39LevelUnder54[ucPlayerLevel-28]; + } + else + { + // Use a matrix to look up the damage bonus for 2H weapons with delays between 28 and 39 wielded by characters level 54 and above. + static const uint8 ucDelay28to39Level54to64[12][11] = + { + /* Level: */ + /* 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64 */ + + {17, 21, 21, 23, 25, 26, 28, 30, 31, 31, 33}, /* Delay = 28 */ + {17, 21, 22, 23, 25, 26, 29, 30, 31, 32, 34}, /* Delay = 29 */ + {18, 21, 22, 23, 25, 27, 29, 31, 32, 32, 34}, /* Delay = 30 */ + {18, 21, 22, 23, 25, 27, 29, 31, 32, 33, 34}, /* Delay = 31 */ + {18, 21, 22, 24, 26, 27, 30, 32, 32, 33, 35}, /* Delay = 32 */ + {18, 21, 22, 24, 26, 27, 30, 32, 33, 34, 35}, /* Delay = 33 */ + {18, 22, 22, 24, 26, 28, 30, 32, 33, 34, 36}, /* Delay = 34 */ + {18, 22, 23, 24, 26, 28, 31, 33, 34, 34, 36}, /* Delay = 35 */ + {18, 22, 23, 25, 27, 28, 31, 33, 34, 35, 37}, /* Delay = 36 */ + {18, 22, 23, 25, 27, 29, 31, 33, 34, 35, 37}, /* Delay = 37 */ + {18, 22, 23, 25, 27, 29, 32, 34, 35, 36, 38}, /* Delay = 38 */ + {18, 22, 23, 25, 27, 29, 32, 34, 35, 36, 38} /* Delay = 39 */ + }; + + return ucDelay28to39Level54to64[Weapon->Delay-28][ucPlayerLevel-54]; + } + } + else if( Weapon->Delay <= 59 ) + { + if( ucPlayerLevel <= 52 ) + { + if( Weapon->Delay <= 45 ) + { + static const uint8 ucDelay40to45Levels28to52[6][25] = + { + /* Level: */ + /* 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52 */ + + {2, 2, 3, 4, 4, 4, 5, 6, 6, 7, 7, 7, 9, 9, 9, 10, 10, 11, 12, 12, 12, 13, 14, 16, 18}, /* Delay = 40 */ + {2, 2, 3, 4, 4, 4, 5, 6, 6, 7, 7, 7, 9, 9, 9, 10, 10, 11, 12, 12, 12, 13, 14, 16, 18}, /* Delay = 41 */ + {2, 2, 3, 4, 4, 4, 5, 6, 6, 7, 7, 7, 9, 9, 9, 10, 10, 11, 12, 12, 12, 13, 14, 16, 18}, /* Delay = 42 */ + {4, 4, 5, 6, 6, 6, 7, 8, 8, 9, 9, 9, 11, 11, 11, 12, 12, 13, 14, 14, 14, 15, 16, 18, 20}, /* Delay = 43 */ + {4, 4, 5, 6, 6, 6, 7, 8, 8, 9, 9, 9, 11, 11, 11, 12, 12, 13, 14, 14, 14, 15, 16, 18, 20}, /* Delay = 44 */ + {5, 5, 6, 7, 7, 7, 8, 9, 9, 10, 10, 10, 12, 12, 12, 13, 13, 14, 15, 15, 15, 16, 17, 19, 21} /* Delay = 45 */ + }; + + return ucDelay40to45Levels28to52[Weapon->Delay-40][ucPlayerLevel-28]; + } + else + { + static const uint8 ucDelay46Levels28to52[] = {6, 6, 7, 8, 8, 8, 9, 10, 10, 11, 11, 11, 13, 13, 13, 14, 14, 15, 16, 16, 16, 17, 18, 20, 22}; + + return ucDelay46Levels28to52[ucPlayerLevel-28] + ((Weapon->Delay-46) / 3); + } + } + else + { + // Player is in the level range 53 - 64 + + // Calculating damage bonus for 2H weapons with a delay between 40 and 59 (inclusive) involves, unforunately, a brute-force matrix lookup. + static const uint8 ucDelay40to59Levels53to64[20][37] = + { + /* Level: */ + /* 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64 */ + + {19, 20, 24, 25, 27, 29, 31, 34, 36, 37, 38, 40}, /* Delay = 40 */ + {19, 20, 24, 25, 27, 29, 31, 34, 36, 37, 38, 40}, /* Delay = 41 */ + {19, 20, 24, 25, 27, 29, 31, 34, 36, 37, 38, 40}, /* Delay = 42 */ + {21, 22, 26, 27, 29, 31, 33, 37, 39, 40, 41, 43}, /* Delay = 43 */ + {21, 22, 26, 27, 29, 32, 34, 37, 39, 40, 41, 43}, /* Delay = 44 */ + {22, 23, 27, 28, 31, 33, 35, 38, 40, 42, 43, 45}, /* Delay = 45 */ + {23, 24, 28, 30, 32, 34, 36, 40, 42, 43, 44, 46}, /* Delay = 46 */ + {23, 24, 29, 30, 32, 34, 37, 40, 42, 43, 44, 47}, /* Delay = 47 */ + {23, 24, 29, 30, 32, 35, 37, 40, 43, 44, 45, 47}, /* Delay = 48 */ + {24, 25, 30, 31, 34, 36, 38, 42, 44, 45, 46, 49}, /* Delay = 49 */ + {24, 26, 30, 31, 34, 36, 39, 42, 44, 46, 47, 49}, /* Delay = 50 */ + {24, 26, 30, 31, 34, 36, 39, 42, 45, 46, 47, 49}, /* Delay = 51 */ + {25, 27, 31, 33, 35, 38, 40, 44, 46, 47, 49, 51}, /* Delay = 52 */ + {25, 27, 31, 33, 35, 38, 40, 44, 46, 48, 49, 51}, /* Delay = 53 */ + {26, 27, 32, 33, 36, 38, 41, 44, 47, 48, 49, 52}, /* Delay = 54 */ + {27, 28, 33, 34, 37, 39, 42, 46, 48, 50, 51, 53}, /* Delay = 55 */ + {27, 28, 33, 34, 37, 40, 42, 46, 49, 50, 51, 54}, /* Delay = 56 */ + {27, 28, 33, 34, 37, 40, 43, 46, 49, 50, 52, 54}, /* Delay = 57 */ + {28, 29, 34, 36, 39, 41, 44, 48, 50, 52, 53, 56}, /* Delay = 58 */ + {28, 29, 34, 36, 39, 41, 44, 48, 51, 52, 54, 56} /* Delay = 59 */ + }; + + return ucDelay40to59Levels53to64[Weapon->Delay-40][ucPlayerLevel-53]; + } + } + else + { + // The following table allows us to look up Damage Bonuses for weapons with delays greater than or equal to 60. + // + // There aren't a lot of 2H weapons with a delay greater than 60. In fact, both a database and Lucy search run by janusd confirm + // that the only unique 2H delays greater than 60 are: 65, 70, 85, 95, and 150. + // + // To be fair, there are also weapons with delays of 66 and 100. But they are either not equippable (None/None), or are + // only available to GMs from merchants in Sunset Home (cshome). In order to keep this table "lean and mean", I will not + // include the values for delays 66 and 100. If they ever are wielded, the 66 delay weapon will use the 65 delay bonuses, + // and the 100 delay weapon will use the 95 delay bonuses. So it's not a big deal. + // + // Still, if someone in the future decides that they do want to include them, here are the tables for these two delays: + // + // {12, 12, 13, 14, 14, 14, 15, 16, 16, 17, 17, 17, 19, 19, 19, 20, 20, 21, 22, 22, 22, 23, 24, 26, 29, 30, 32, 37, 39, 42, 45, 48, 53, 55, 57, 59, 61, 64} /* Delay = 66 */ + // {24, 24, 25, 26, 26, 26, 27, 28, 28, 29, 29, 29, 31, 31, 31, 32, 32, 33, 34, 34, 34, 35, 36, 39, 43, 45, 48, 55, 57, 62, 66, 71, 77, 80, 83, 85, 89, 92} /* Delay = 100 */ + // + // In case there are 2H weapons added in the future with delays other than those listed above (and until the damage bonuses + // associated with that new delay are added to this function), this function is designed to do the following: + // + // For weapons with delays in the range 60-64, use the Damage Bonus that would apply to a 2H weapon with delay 60. + // For weapons with delays in the range 65-69, use the Damage Bonus that would apply to a 2H weapon with delay 65 + // For weapons with delays in the range 70-84, use the Damage Bonus that would apply to a 2H weapon with delay 70. + // For weapons with delays in the range 85-94, use the Damage Bonus that would apply to a 2H weapon with delay 85. + // For weapons with delays in the range 95-149, use the Damage Bonus that would apply to a 2H weapon with delay 95. + // For weapons with delays 150 or higher, use the Damage Bonus that would apply to a 2H weapon with delay 150. + + static const uint8 ucDelayOver59Levels28to65[6][38] = + { + /* Level: */ + /* 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64. 65 */ + + {10, 10, 11, 12, 12, 12, 13, 14, 14, 15, 15, 15, 17, 17, 17, 18, 18, 19, 20, 20, 20, 21, 22, 24, 27, 28, 30, 35, 36, 39, 42, 45, 49, 51, 53, 54, 57, 59}, /* Delay = 60 */ + {12, 12, 13, 14, 14, 14, 15, 16, 16, 17, 17, 17, 19, 19, 19, 20, 20, 21, 22, 22, 22, 23, 24, 26, 29, 30, 32, 37, 39, 42, 45, 48, 52, 55, 57, 58, 61, 63}, /* Delay = 65 */ + {14, 14, 15, 16, 16, 16, 17, 18, 18, 19, 19, 19, 21, 21, 21, 22, 22, 23, 24, 24, 24, 25, 26, 28, 31, 33, 35, 40, 42, 45, 48, 52, 56, 59, 61, 62, 65, 68}, /* Delay = 70 */ + {19, 19, 20, 21, 21, 21, 22, 23, 23, 24, 24, 24, 26, 26, 26, 27, 27, 28, 29, 29, 29, 30, 31, 34, 37, 39, 41, 47, 49, 54, 57, 61, 66, 69, 72, 74, 77, 80}, /* Delay = 85 */ + {22, 22, 23, 24, 24, 24, 25, 26, 26, 27, 27, 27, 29, 29, 29, 30, 30, 31, 32, 32, 32, 33, 34, 37, 40, 43, 45, 52, 54, 59, 62, 67, 73, 76, 79, 81, 84, 88}, /* Delay = 95 */ + {40, 40, 41, 42, 42, 42, 43, 44, 44, 45, 45, 45, 47, 47, 47, 48, 48, 49, 50, 50, 50, 51, 52, 56, 61, 65, 69, 78, 82, 89, 94, 102, 110, 115, 119, 122, 127, 132} /* Delay = 150 */ + }; + + if( Weapon->Delay < 65 ) + { + return ucDelayOver59Levels28to65[0][ucPlayerLevel-28]; + } + else if( Weapon->Delay < 70 ) + { + return ucDelayOver59Levels28to65[1][ucPlayerLevel-28]; + } + else if( Weapon->Delay < 85 ) + { + return ucDelayOver59Levels28to65[2][ucPlayerLevel-28]; + } + else if( Weapon->Delay < 95 ) + { + return ucDelayOver59Levels28to65[3][ucPlayerLevel-28]; + } + else if( Weapon->Delay < 150 ) + { + return ucDelayOver59Levels28to65[4][ucPlayerLevel-28]; + } + else + { + return ucDelayOver59Levels28to65[5][ucPlayerLevel-28]; + } + } +} + +int Mob::GetMonkHandToHandDamage(void) +{ + // Kaiyodo - Determine a monk's fist damage. Table data from www.monkly-business.com + // saved as static array - this should speed this function up considerably + static int damage[66] = { + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 + 99, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, + 8, 8, 8, 8, 8, 9, 9, 9, 9, 9,10,10,10,10,10,11,11,11,11,11, + 12,12,12,12,12,13,13,13,13,13,14,14,14,14,14,14,14,14,14,14, + 14,14,15,15,15,15 }; + + // Have a look to see if we have epic fists on + + if (IsClient() && CastToClient()->GetItemIDAt(12) == 10652) + return(9); + else + { + int Level = GetLevel(); + if (Level > 65) + return(19); + else + return damage[Level]; + } +} + +int Mob::GetMonkHandToHandDelay(void) +{ + // Kaiyodo - Determine a monk's fist delay. Table data from www.monkly-business.com + // saved as static array - this should speed this function up considerably + static int delayshuman[66] = { + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 + 99,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36, + 36,36,36,36,36,35,35,35,35,35,34,34,34,34,34,33,33,33,33,33, + 32,32,32,32,32,31,31,31,31,31,30,30,30,29,29,29,28,28,28,27, + 26,24,22,20,20,20 }; + static int delaysiksar[66] = { + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 + 99,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36, + 36,36,36,36,36,36,36,36,36,36,35,35,35,35,35,34,34,34,34,34, + 33,33,33,33,33,32,32,32,32,32,31,31,31,30,30,30,29,29,29,28, + 27,24,22,20,20,20 }; + + // Have a look to see if we have epic fists on + if (IsClient() && CastToClient()->GetItemIDAt(12) == 10652) + return(16); + else + { + int Level = GetLevel(); + if (GetRace() == HUMAN) + { + if (Level > 65) + return(24); + else + return delayshuman[Level]; + } + else //heko: iksar table + { + if (Level > 65) + return(25); + else + return delaysiksar[Level]; + } + } +} + + +int32 Mob::ReduceDamage(int32 damage) +{ + if(damage <= 0) + return damage; + + int32 slot = -1; + bool DisableMeleeRune = false; + + if (spellbonuses.NegateAttacks[0]){ + slot = spellbonuses.NegateAttacks[1]; + if(slot >= 0) { + if(--buffs[slot].numhits == 0) { + + if(!TryFadeEffect(slot)) + BuffFadeBySlot(slot , true); + } + + if (spellbonuses.NegateAttacks[2] && (damage > spellbonuses.NegateAttacks[2])) + damage -= spellbonuses.NegateAttacks[2]; + else + return -6; + } + } + + //Only mitigate if damage is above the minimium specified. + if (spellbonuses.MeleeThresholdGuard[0]){ + slot = spellbonuses.MeleeThresholdGuard[1]; + + if (slot >= 0 && (damage > spellbonuses.MeleeThresholdGuard[2])) + { + DisableMeleeRune = true; + int damage_to_reduce = damage * spellbonuses.MeleeThresholdGuard[0] / 100; + if(damage_to_reduce >= buffs[slot].melee_rune) + { + mlog(SPELLS__EFFECT_VALUES, "Mob::ReduceDamage SE_MeleeThresholdGuard %d damage negated, %d" + " damage remaining, fading buff.", damage_to_reduce, buffs[slot].melee_rune); + damage -= buffs[slot].melee_rune; + if(!TryFadeEffect(slot)) + BuffFadeBySlot(slot); + } + else + { + mlog(SPELLS__EFFECT_VALUES, "Mob::ReduceDamage SE_MeleeThresholdGuard %d damage negated, %d" + " damage remaining.", damage_to_reduce, buffs[slot].melee_rune); + buffs[slot].melee_rune = (buffs[slot].melee_rune - damage_to_reduce); + damage -= damage_to_reduce; + } + } + } + + + if (spellbonuses.MitigateMeleeRune[0] && !DisableMeleeRune){ + slot = spellbonuses.MitigateMeleeRune[1]; + if(slot >= 0) + { + int damage_to_reduce = damage * spellbonuses.MitigateMeleeRune[0] / 100; + + if (spellbonuses.MitigateMeleeRune[2] && (damage_to_reduce > spellbonuses.MitigateMeleeRune[2])) + damage_to_reduce = spellbonuses.MitigateMeleeRune[2]; + + if(spellbonuses.MitigateMeleeRune[3] && (damage_to_reduce >= buffs[slot].melee_rune)) + { + mlog(SPELLS__EFFECT_VALUES, "Mob::ReduceDamage SE_MitigateMeleeDamage %d damage negated, %d" + " damage remaining, fading buff.", damage_to_reduce, buffs[slot].melee_rune); + damage -= buffs[slot].melee_rune; + if(!TryFadeEffect(slot)) + BuffFadeBySlot(slot); + } + else + { + mlog(SPELLS__EFFECT_VALUES, "Mob::ReduceDamage SE_MitigateMeleeDamage %d damage negated, %d" + " damage remaining.", damage_to_reduce, buffs[slot].melee_rune); + + if (spellbonuses.MitigateMeleeRune[3]) + buffs[slot].melee_rune = (buffs[slot].melee_rune - damage_to_reduce); + + damage -= damage_to_reduce; + } + } + } + + if(damage < 1) + return -6; + + if (spellbonuses.MeleeRune[0] && spellbonuses.MeleeRune[1] >= 0) + damage = RuneAbsorb(damage, SE_Rune); + + if(damage < 1) + return -6; + + return(damage); +} + +int32 Mob::AffectMagicalDamage(int32 damage, uint16 spell_id, const bool iBuffTic, Mob* attacker) +{ + if(damage <= 0) + return damage; + + bool DisableSpellRune = false; + int32 slot = -1; + + // See if we block the spell outright first + if (!iBuffTic && spellbonuses.NegateAttacks[0]){ + slot = spellbonuses.NegateAttacks[1]; + if(slot >= 0) { + if(--buffs[slot].numhits == 0) { + + if(!TryFadeEffect(slot)) + BuffFadeBySlot(slot , true); + } + + if (spellbonuses.NegateAttacks[2] && (damage > spellbonuses.NegateAttacks[2])) + damage -= spellbonuses.NegateAttacks[2]; + else + return 0; + } + } + + // If this is a DoT, use DoT Shielding... + if(iBuffTic) { + damage -= (damage * itembonuses.DoTShielding / 100); + + if (spellbonuses.MitigateDotRune[0]){ + slot = spellbonuses.MitigateDotRune[1]; + if(slot >= 0) + { + int damage_to_reduce = damage * spellbonuses.MitigateDotRune[0] / 100; + + if (spellbonuses.MitigateDotRune[2] && (damage_to_reduce > spellbonuses.MitigateDotRune[2])) + damage_to_reduce = spellbonuses.MitigateDotRune[2]; + + if(spellbonuses.MitigateDotRune[3] && (damage_to_reduce >= buffs[slot].dot_rune)) + { + damage -= buffs[slot].dot_rune; + if(!TryFadeEffect(slot)) + BuffFadeBySlot(slot); + } + else + { + if (spellbonuses.MitigateDotRune[3]) + buffs[slot].dot_rune = (buffs[slot].dot_rune - damage_to_reduce); + + damage -= damage_to_reduce; + } + } + } + } + + // This must be a DD then so lets apply Spell Shielding and runes. + else + { + // Reduce damage by the Spell Shielding first so that the runes don't take the raw damage. + damage -= (damage * itembonuses.SpellShield / 100); + + + //Only mitigate if damage is above the minimium specified. + if (spellbonuses.SpellThresholdGuard[0]){ + slot = spellbonuses.SpellThresholdGuard[1]; + + if (slot >= 0 && (damage > spellbonuses.MeleeThresholdGuard[2])) + { + DisableSpellRune = true; + int damage_to_reduce = damage * spellbonuses.SpellThresholdGuard[0] / 100; + if(damage_to_reduce >= buffs[slot].magic_rune) + { + damage -= buffs[slot].magic_rune; + if(!TryFadeEffect(slot)) + BuffFadeBySlot(slot); + } + else + { + buffs[slot].melee_rune = (buffs[slot].magic_rune - damage_to_reduce); + damage -= damage_to_reduce; + } + } + } + + + // Do runes now. + if (spellbonuses.MitigateSpellRune[0] && !DisableSpellRune){ + slot = spellbonuses.MitigateSpellRune[1]; + if(slot >= 0) + { + int damage_to_reduce = damage * spellbonuses.MitigateSpellRune[0] / 100; + + if (spellbonuses.MitigateSpellRune[2] && (damage_to_reduce > spellbonuses.MitigateSpellRune[2])) + damage_to_reduce = spellbonuses.MitigateSpellRune[2]; + + if(spellbonuses.MitigateSpellRune[3] && (damage_to_reduce >= buffs[slot].magic_rune)) + { + mlog(SPELLS__EFFECT_VALUES, "Mob::ReduceDamage SE_MitigateSpellDamage %d damage negated, %d" + " damage remaining, fading buff.", damage_to_reduce, buffs[slot].magic_rune); + damage -= buffs[slot].magic_rune; + if(!TryFadeEffect(slot)) + BuffFadeBySlot(slot); + } + else + { + mlog(SPELLS__EFFECT_VALUES, "Mob::ReduceDamage SE_MitigateMeleeDamage %d damage negated, %d" + " damage remaining.", damage_to_reduce, buffs[slot].magic_rune); + + if (spellbonuses.MitigateSpellRune[3]) + buffs[slot].magic_rune = (buffs[slot].magic_rune - damage_to_reduce); + + damage -= damage_to_reduce; + } + } + } + + if(damage < 1) + return 0; + + //Regular runes absorb spell damage (except dots) - Confirmed on live. + if (spellbonuses.MeleeRune[0] && spellbonuses.MeleeRune[1] >= 0) + damage = RuneAbsorb(damage, SE_Rune); + + if (spellbonuses.AbsorbMagicAtt[0] && spellbonuses.AbsorbMagicAtt[1] >= 0) + damage = RuneAbsorb(damage, SE_AbsorbMagicAtt); + + if(damage < 1) + return 0; + } + return damage; +} + +int32 Mob::ReduceAllDamage(int32 damage) +{ + if(damage <= 0) + return damage; + + if(spellbonuses.ManaAbsorbPercentDamage[0]) { + int32 mana_reduced = damage * spellbonuses.ManaAbsorbPercentDamage[0] / 100; + if (GetMana() >= mana_reduced){ + damage -= mana_reduced; + SetMana(GetMana() - mana_reduced); + TryTriggerOnValueAmount(false, true); + } + } + + CheckNumHitsRemaining(NUMHIT_IncomingDamage); + + return(damage); +} + +bool Mob::HasProcs() const +{ + for (int i = 0; i < MAX_PROCS; i++) + if (PermaProcs[i].spellID != SPELL_UNKNOWN || SpellProcs[i].spellID != SPELL_UNKNOWN) + return true; + return false; +} + +bool Mob::HasDefensiveProcs() const +{ + for (int i = 0; i < MAX_PROCS; i++) + if (DefensiveProcs[i].spellID != SPELL_UNKNOWN) + return true; + return false; +} + +bool Mob::HasSkillProcs() const +{ + + for(int i = 0; i < MAX_SKILL_PROCS; i++){ + if (spellbonuses.SkillProc[i] || itembonuses.SkillProc[i] || aabonuses.SkillProc[i]) + return true; + } + return false; +} + +bool Mob::HasSkillProcSuccess() const +{ + for(int i = 0; i < MAX_SKILL_PROCS; i++){ + if (spellbonuses.SkillProcSuccess[i] || itembonuses.SkillProcSuccess[i] || aabonuses.SkillProcSuccess[i]) + return true; + } + return false; +} + +bool Mob::HasRangedProcs() const +{ + for (int i = 0; i < MAX_PROCS; i++) + if (RangedProcs[i].spellID != SPELL_UNKNOWN) + return true; + return false; +} + +bool Client::CheckDoubleAttack(bool tripleAttack) { + + //Check for bonuses that give you a double attack chance regardless of skill (ie Bestial Frenzy/Harmonious Attack AA) + uint32 bonusGiveDA = aabonuses.GiveDoubleAttack + spellbonuses.GiveDoubleAttack + itembonuses.GiveDoubleAttack; + + if(!HasSkill(SkillDoubleAttack) && !bonusGiveDA) + return false; + + float chance = 0.0f; + + uint16 skill = GetSkill(SkillDoubleAttack); + + int32 bonusDA = aabonuses.DoubleAttackChance + spellbonuses.DoubleAttackChance + itembonuses.DoubleAttackChance; + + //Use skill calculations otherwise, if you only have AA applied GiveDoubleAttack chance then use that value as the base. + if (skill) + chance = (float(skill+GetLevel()) * (float(100.0f+bonusDA+bonusGiveDA) /100.0f)) /500.0f; + else + chance = (float(bonusGiveDA) * (float(100.0f+bonusDA)/100.0f) ) /100.0f; + + //Live now uses a static Triple Attack skill (lv 46 = 2% lv 60 = 20%) - We do not have this skill on EMU ATM. + //A reasonable forumla would then be TA = 20% * chance + //AA's can also give triple attack skill over cap. (ie Burst of Power) NOTE: Skill ID in spell data is 76 (Triple Attack) + //Kayen: Need to decide if we can implement triple attack skill before working in over the cap effect. + if(tripleAttack) { + // Only some Double Attack classes get Triple Attack [This is already checked in client_processes.cpp] + int32 triple_bonus = spellbonuses.TripleAttackChance + itembonuses.TripleAttackChance; + chance *= 0.2f; //Baseline chance is 20% of your double attack chance. + chance *= float(100.0f+triple_bonus)/100.0f; //Apply modifiers. + } + + if(zone->random.Roll(chance)) + return true; + + return false; +} + +bool Client::CheckDoubleRangedAttack() { +<<<<<<< HEAD + +======= +>>>>>>> master + int32 chance = spellbonuses.DoubleRangedAttack + itembonuses.DoubleRangedAttack + aabonuses.DoubleRangedAttack; + + if(chance && zone->random.Roll(chance)) + return true; + + return false; +} + +void Mob::CommonDamage(Mob* attacker, int32 &damage, const uint16 spell_id, const SkillUseTypes skill_used, bool &avoidable, const int8 buffslot, const bool iBuffTic) { + // This method is called with skill_used=ABJURE for Damage Shield damage. + bool FromDamageShield = (skill_used == SkillAbjuration); + + mlog(COMBAT__HITS, "Applying damage %d done by %s with skill %d and spell %d, avoidable? %s, is %sa buff tic in slot %d", + damage, attacker?attacker->GetName():"NOBODY", skill_used, spell_id, avoidable?"yes":"no", iBuffTic?"":"not ", buffslot); + + if (GetInvul() || DivineAura()) { + mlog(COMBAT__DAMAGE, "Avoiding %d damage due to invulnerability.", damage); + damage = -5; + } + + if( spell_id != SPELL_UNKNOWN || attacker == nullptr ) + avoidable = false; + + // only apply DS if physical damage (no spell damage) + // damage shield calls this function with spell_id set, so its unavoidable + if (attacker && damage > 0 && spell_id == SPELL_UNKNOWN && skill_used != SkillArchery && skill_used != SkillThrowing) { + DamageShield(attacker); + } + + if (spell_id == SPELL_UNKNOWN && skill_used) { + CheckNumHitsRemaining(NUMHIT_IncomingHitAttempts); + + if (attacker) + attacker->CheckNumHitsRemaining(NUMHIT_OutgoingHitAttempts); + } + + if(attacker){ + if(attacker->IsClient()){ + if(!RuleB(Combat, EXPFromDmgShield)) { + // Damage shield damage shouldn't count towards who gets EXP + if(!attacker->CastToClient()->GetFeigned() && !FromDamageShield) + AddToHateList(attacker, 0, damage, true, false, iBuffTic); + } + else { + if(!attacker->CastToClient()->GetFeigned()) + AddToHateList(attacker, 0, damage, true, false, iBuffTic); + } + } + else + AddToHateList(attacker, 0, damage, true, false, iBuffTic); + } + + if(damage > 0) { + //if there is some damage being done and theres an attacker involved + if(attacker) { + if(spell_id == SPELL_HARM_TOUCH2 && attacker->IsClient() && attacker->CastToClient()->CheckAAEffect(aaEffectLeechTouch)){ + int healed = damage; + healed = attacker->GetActSpellHealing(spell_id, healed); + attacker->HealDamage(healed); + entity_list.MessageClose(this, true, 300, MT_Emote, "%s beams a smile at %s", attacker->GetCleanName(), this->GetCleanName() ); + attacker->CastToClient()->DisableAAEffect(aaEffectLeechTouch); + } + + // if spell is lifetap add hp to the caster + if (spell_id != SPELL_UNKNOWN && IsLifetapSpell( spell_id )) { + int healed = damage; + + healed = attacker->GetActSpellHealing(spell_id, healed); + mlog(COMBAT__DAMAGE, "Applying lifetap heal of %d to %s", healed, attacker->GetName()); + attacker->HealDamage(healed); + + //we used to do a message to the client, but its gone now. + // emote goes with every one ... even npcs + entity_list.MessageClose(this, true, 300, MT_Emote, "%s beams a smile at %s", attacker->GetCleanName(), this->GetCleanName() ); + } + } //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()) + { + if (!pet->IsHeld()) { + mlog(PETS__AGGRO, "Sending pet %s into battle due to attack.", pet->GetName()); + pet->AddToHateList(attacker, 1); + pet->SetTarget(attacker); + Message_StringID(10, PET_ATTACKING, pet->GetCleanName(), attacker->GetCleanName()); + } + } + + //see if any runes want to reduce this damage + if(spell_id == SPELL_UNKNOWN) { + damage = ReduceDamage(damage); + mlog(COMBAT__HITS, "Melee Damage reduced to %d", damage); + damage = ReduceAllDamage(damage); + TryTriggerThreshHold(damage, SE_TriggerMeleeThreshold, attacker); + } else { + int32 origdmg = damage; + damage = AffectMagicalDamage(damage, spell_id, iBuffTic, attacker); + if (origdmg != damage && attacker && attacker->IsClient()) { + if(attacker->CastToClient()->GetFilter(FilterDamageShields) != FilterHide) + attacker->Message(15, "The Spellshield absorbed %d of %d points of damage", origdmg - damage, origdmg); + } + if (damage == 0 && attacker && origdmg != damage && IsClient()) { + //Kayen: Probably need to add a filter for this - Not sure if this msg is correct but there should be a message for spell negate/runes. + Message(263, "%s tries to cast on YOU, but YOUR magical skin absorbs the spell.",attacker->GetCleanName()); + } + damage = ReduceAllDamage(damage); + TryTriggerThreshHold(damage, SE_TriggerSpellThreshold, attacker); + } + + if (skill_used) + CheckNumHitsRemaining(NUMHIT_IncomingHitSuccess); + + if(IsClient() && CastToClient()->sneaking){ + CastToClient()->sneaking = false; + SendAppearancePacket(AT_Sneak, 0); + } + if(attacker && attacker->IsClient() && attacker->CastToClient()->sneaking){ + attacker->CastToClient()->sneaking = false; + attacker->SendAppearancePacket(AT_Sneak, 0); + } + + //final damage has been determined. + + SetHP(GetHP() - damage); + + if(HasDied()) { + bool IsSaved = false; + + if(TryDivineSave()) + IsSaved = true; + + if(!IsSaved && !TrySpellOnDeath()) { + SetHP(-500); + + if(Death(attacker, damage, spell_id, skill_used)) { + return; + } + } + } + else{ + if(GetHPRatio() < 16) + TryDeathSave(); + } + + TryTriggerOnValueAmount(true); + + //fade mez if we are mezzed + if (IsMezzed() && attacker) { + mlog(COMBAT__HITS, "Breaking mez due to attack."); + entity_list.MessageClose_StringID(this, true, 100, MT_WornOff, + HAS_BEEN_AWAKENED, GetCleanName(), attacker->GetCleanName()); + BuffFadeByEffect(SE_Mez); + } + + //check stun chances if bashing + if (damage > 0 && ((skill_used == SkillBash || skill_used == SkillKick) && attacker)) { + // NPCs can stun with their bash/kick as soon as they receive it. + // Clients can stun mobs under level 56 with their kick when they get level 55 or greater. + // Clients have a chance to stun if the mob is 56+ + + // Calculate the chance to stun + int stun_chance = 0; + if (!GetSpecialAbility(UNSTUNABLE)) { + if (attacker->IsNPC()) { + stun_chance = RuleI(Combat, NPCBashKickStunChance); + } else if (attacker->IsClient()) { + // Less than base immunity + // Client vs. Client always uses the chance + if (!IsClient() && GetLevel() <= RuleI(Spells, BaseImmunityLevel)) { + if (skill_used == SkillBash) // Bash always will + stun_chance = 100; + else if (attacker->GetLevel() >= RuleI(Combat, ClientStunLevel)) + stun_chance = 100; // only if you're over level 55 and using kick + } else { // higher than base immunity or Client vs. Client + // not sure on this number, use same as NPC for now + if (skill_used == SkillKick && attacker->GetLevel() < RuleI(Combat, ClientStunLevel)) + stun_chance = RuleI(Combat, NPCBashKickStunChance); + else if (skill_used == SkillBash) + stun_chance = RuleI(Combat, NPCBashKickStunChance) + + attacker->spellbonuses.StunBashChance + + attacker->itembonuses.StunBashChance + + attacker->aabonuses.StunBashChance; + } + } + } + + if (stun_chance && zone->random.Roll(stun_chance)) { + // Passed stun, try to resist now + int stun_resist = itembonuses.StunResist + spellbonuses.StunResist; + int frontal_stun_resist = itembonuses.FrontalStunResist + spellbonuses.FrontalStunResist; + + mlog(COMBAT__HITS, "Stun passed, checking resists. Was %d chance.", stun_chance); + if (IsClient()) { + stun_resist += aabonuses.StunResist; + frontal_stun_resist += aabonuses.FrontalStunResist; + } + + // frontal stun check for ogres/bonuses + if (((GetBaseRace() == OGRE && IsClient()) || + (frontal_stun_resist && zone->random.Roll(frontal_stun_resist))) && + !attacker->BehindMob(this, attacker->GetX(), attacker->GetY())) { + mlog(COMBAT__HITS, "Frontal stun resisted. %d chance.", frontal_stun_resist); + } else { + // Normal stun resist check. + if (stun_resist && zone->random.Roll(stun_resist)) { + if (IsClient()) + Message_StringID(MT_Stun, SHAKE_OFF_STUN); + mlog(COMBAT__HITS, "Stun Resisted. %d chance.", stun_resist); + } else { + mlog(COMBAT__HITS, "Stunned. %d resist chance.", stun_resist); + Stun(zone->random.Int(0, 2) * 1000); // 0-2 seconds + } + } + } else { + mlog(COMBAT__HITS, "Stun failed. %d chance.", stun_chance); + } + } + + if(spell_id != SPELL_UNKNOWN && !iBuffTic) { + //see if root will break + if (IsRooted() && !FromDamageShield) // neotoyko: only spells cancel root + TryRootFadeByDamage(buffslot, attacker); + } + else if(spell_id == SPELL_UNKNOWN) + { + //increment chances of interrupting + if(IsCasting()) { //shouldnt interrupt on regular spell damage + attacked_count++; + mlog(COMBAT__HITS, "Melee attack while casting. Attack count %d", attacked_count); + } + } + + //send an HP update if we are hurt + if(GetHP() < GetMaxHP()) + SendHPUpdate(); + } //end `if damage was done` + + //send damage packet... + if(!iBuffTic) { //buff ticks do not send damage, instead they just call SendHPUpdate(), which is done below + EQApplicationPacket* outapp = new EQApplicationPacket(OP_Damage, sizeof(CombatDamage_Struct)); + CombatDamage_Struct* a = (CombatDamage_Struct*)outapp->pBuffer; + a->target = GetID(); + if (attacker == nullptr) + a->source = 0; + else if (attacker->IsClient() && attacker->CastToClient()->GMHideMe()) + a->source = 0; + else + a->source = attacker->GetID(); + a->type = SkillDamageTypes[skill_used]; // was 0x1c + a->damage = damage; + a->spellid = spell_id; + + //Note: if players can become pets, they will not receive damage messages of their own + //this was done to simplify the code here (since we can only effectively skip one mob on queue) + eqFilterType filter; + Mob *skip = attacker; + if(attacker && attacker->GetOwnerID()) { + //attacker is a pet, let pet owners see their pet's damage + Mob* owner = attacker->GetOwner(); + if (owner && owner->IsClient()) { + if (((spell_id != SPELL_UNKNOWN) || (FromDamageShield)) && damage>0) { + //special crap for spell damage, looks hackish to me + char val1[20]={0}; + owner->Message_StringID(MT_NonMelee,OTHER_HIT_NONMELEE,GetCleanName(),ConvertArray(damage,val1)); + } else { + if(damage > 0) { + if(spell_id != SPELL_UNKNOWN) + filter = iBuffTic ? FilterDOT : FilterSpellDamage; + else + filter = FilterPetHits; + } else if(damage == -5) + filter = FilterNone; //cant filter invulnerable + else + filter = FilterPetMisses; + + if(!FromDamageShield) + owner->CastToClient()->QueuePacket(outapp,true,CLIENT_CONNECTED,filter); + } + } + skip = owner; + } else { + //attacker is not a pet, send to the attacker + + //if the attacker is a client, try them with the correct filter + if(attacker && attacker->IsClient()) { + if (((spell_id != SPELL_UNKNOWN)||(FromDamageShield)) && damage>0) { + //special crap for spell damage, looks hackish to me + char val1[20]={0}; + if (FromDamageShield) + { + if(!attacker->CastToClient()->GetFilter(FilterDamageShields) == FilterHide) + { + attacker->Message_StringID(MT_DS,OTHER_HIT_NONMELEE,GetCleanName(),ConvertArray(damage,val1)); + } + } + else + entity_list.MessageClose_StringID(this, true, 100, MT_NonMelee,HIT_NON_MELEE,attacker->GetCleanName(),GetCleanName(),ConvertArray(damage,val1)); + } else { + if(damage > 0) { + if(spell_id != SPELL_UNKNOWN) + filter = iBuffTic ? FilterDOT : FilterSpellDamage; + else + filter = FilterNone; //cant filter our own hits + } else if(damage == -5) + filter = FilterNone; //cant filter invulnerable + else + filter = FilterMyMisses; + + attacker->CastToClient()->QueuePacket(outapp, true, CLIENT_CONNECTED, filter); + } + } + skip = attacker; + } + + //send damage to all clients around except the specified skip mob (attacker or the attacker's owner) and ourself + if(damage > 0) { + if(spell_id != SPELL_UNKNOWN) + filter = iBuffTic ? FilterDOT : FilterSpellDamage; + else + filter = FilterOthersHit; + } else if(damage == -5) + filter = FilterNone; //cant filter invulnerable + else + filter = FilterOthersMiss; + //make attacker (the attacker) send the packet so we can skip them and the owner + //this call will send the packet to `this` as well (using the wrong filter) (will not happen until PC charm works) + // If this is Damage Shield damage, the correct OP_Damage packets will be sent from Mob::DamageShield, so + // we don't send them here. + if(!FromDamageShield) { + entity_list.QueueCloseClients(this, outapp, true, 200, skip, true, filter); + //send the damage to ourself if we are a client + if(IsClient()) { + //I dont think any filters apply to damage affecting us + CastToClient()->QueuePacket(outapp); + } + } + + safe_delete(outapp); + } else { + //else, it is a buff tic... + // So we can see our dot dmg like live shows it. + if(spell_id != SPELL_UNKNOWN && damage > 0 && attacker && attacker != this && attacker->IsClient()) { + //might filter on (attack_skill>200 && attack_skill<250), but I dont think we need it + attacker->FilteredMessage_StringID(attacker, MT_DoTDamage, FilterDOT, + YOUR_HIT_DOT, GetCleanName(), itoa(damage), spells[spell_id].name); + // older clients don't have the below String ID, but it will be filtered + entity_list.FilteredMessageClose_StringID(attacker, true, 200, + MT_DoTDamage, FilterDOT, OTHER_HIT_DOT, GetCleanName(), + itoa(damage), attacker->GetCleanName(), spells[spell_id].name); + } + } //end packet sending + +} + + +void Mob::HealDamage(uint32 amount, Mob *caster, uint16 spell_id) +{ + int32 maxhp = GetMaxHP(); + int32 curhp = GetHP(); + uint32 acthealed = 0; + + if (caster && amount > 0) { + if (caster->IsNPC() && !caster->IsPet()) { + float npchealscale = caster->CastToNPC()->GetHealScale(); + amount = (static_cast(amount) * npchealscale) / 100.0f; + } + } + + if (amount > (maxhp - curhp)) + acthealed = (maxhp - curhp); + else + acthealed = amount; + + if (acthealed > 100) { + if (caster) { + if (IsBuffSpell(spell_id)) { // hots + // message to caster + if (caster->IsClient() && caster == this) { + if (caster->CastToClient()->GetClientVersionBit() & BIT_SoFAndLater) + FilteredMessage_StringID(caster, MT_NonMelee, FilterHealOverTime, + HOT_HEAL_SELF, itoa(acthealed), spells[spell_id].name); + else + FilteredMessage_StringID(caster, MT_NonMelee, FilterHealOverTime, + YOU_HEALED, GetCleanName(), itoa(acthealed)); + } else if (caster->IsClient() && caster != this) { + if (caster->CastToClient()->GetClientVersionBit() & BIT_SoFAndLater) + caster->FilteredMessage_StringID(caster, MT_NonMelee, FilterHealOverTime, + HOT_HEAL_OTHER, GetCleanName(), itoa(acthealed), + spells[spell_id].name); + else + caster->FilteredMessage_StringID(caster, MT_NonMelee, FilterHealOverTime, + YOU_HEAL, GetCleanName(), itoa(acthealed)); + } + // message to target + if (IsClient() && caster != this) { + if (CastToClient()->GetClientVersionBit() & BIT_SoFAndLater) + FilteredMessage_StringID(this, MT_NonMelee, FilterHealOverTime, + HOT_HEALED_OTHER, caster->GetCleanName(), + itoa(acthealed), spells[spell_id].name); + else + FilteredMessage_StringID(this, MT_NonMelee, FilterHealOverTime, + YOU_HEALED, caster->GetCleanName(), itoa(acthealed)); + } + } else { // normal heals + FilteredMessage_StringID(caster, MT_NonMelee, FilterSpellDamage, + YOU_HEALED, caster->GetCleanName(), itoa(acthealed)); + if (caster != this) + caster->FilteredMessage_StringID(caster, MT_NonMelee, FilterSpellDamage, + YOU_HEAL, GetCleanName(), itoa(acthealed)); + } + } else { + Message(MT_NonMelee, "You have been healed for %d points of damage.", acthealed); + } + } + + if (curhp < maxhp) { + if ((curhp + amount) > maxhp) + curhp = maxhp; + else + curhp += amount; + SetHP(curhp); + + SendHPUpdate(); + } +} + +//proc chance includes proc bonus +float Mob::GetProcChances(float ProcBonus, uint16 hand) +{ + int mydex = GetDEX(); + float ProcChance = 0.0f; + + uint32 weapon_speed = GetWeaponSpeedbyHand(hand); + + if (RuleB(Combat, AdjustProcPerMinute)) { + ProcChance = (static_cast(weapon_speed) * + RuleR(Combat, AvgProcsPerMinute) / 60000.0f); // compensate for weapon_speed being in ms + ProcBonus += static_cast(mydex) * RuleR(Combat, ProcPerMinDexContrib); + ProcChance += ProcChance * ProcBonus / 100.0f; + } else { + ProcChance = RuleR(Combat, BaseProcChance) + + static_cast(mydex) / RuleR(Combat, ProcDexDivideBy); + ProcChance += ProcChance * ProcBonus / 100.0f; + } + + mlog(COMBAT__PROCS, "Proc chance %.2f (%.2f from bonuses)", ProcChance, ProcBonus); + return ProcChance; +} + +float Mob::GetDefensiveProcChances(float &ProcBonus, float &ProcChance, uint16 hand, Mob* on) { + + if (!on) + return ProcChance; + + int myagi = on->GetAGI(); + ProcBonus = 0; + ProcChance = 0; + + uint32 weapon_speed = GetWeaponSpeedbyHand(hand); + + ProcChance = (static_cast(weapon_speed) * RuleR(Combat, AvgDefProcsPerMinute) / 60000.0f); // compensate for weapon_speed being in ms + ProcBonus += static_cast(myagi) * RuleR(Combat, DefProcPerMinAgiContrib) / 100.0f; + ProcChance = ProcChance + (ProcChance * ProcBonus); + + mlog(COMBAT__PROCS, "Defensive Proc chance %.2f (%.2f from bonuses)", ProcChance, ProcBonus); + return ProcChance; +} + +void Mob::TryDefensiveProc(const ItemInst* weapon, Mob *on, uint16 hand) { + + if (!on) { + SetTarget(nullptr); + LogFile->write(EQEMuLog::Error, "A null Mob object was passed to Mob::TryDefensiveProc for evaluation!"); + return; + } + + bool bDefensiveProc = HasDefensiveProcs(); + + if (!bDefensiveProc) + return; + + float ProcChance, ProcBonus; + on->GetDefensiveProcChances(ProcBonus, ProcChance, hand , this); + + if(hand != MainPrimary) + ProcChance /= 2; + + if (bDefensiveProc){ + for (int i = 0; i < MAX_PROCS; i++) { + if (IsValidSpell(DefensiveProcs[i].spellID)) { + float chance = ProcChance * (static_cast(DefensiveProcs[i].chance)/100.0f); + if (zone->random.Roll(chance)) { + ExecWeaponProc(nullptr, DefensiveProcs[i].spellID, on); + CheckNumHitsRemaining(NUMHIT_DefensiveSpellProcs,0,DefensiveProcs[i].base_spellID); + } + } + } + } +} + +void Mob::TryWeaponProc(const ItemInst* weapon_g, Mob *on, uint16 hand) { + if(!on) { + SetTarget(nullptr); + LogFile->write(EQEMuLog::Error, "A null Mob object was passed to Mob::TryWeaponProc for evaluation!"); + return; + } + + if (!IsAttackAllowed(on)) { + mlog(COMBAT__PROCS, "Preventing procing off of unattackable things."); + return; + } + + if(!weapon_g) { + TrySpellProc(nullptr, (const Item_Struct*)nullptr, on); + return; + } + + if(!weapon_g->IsType(ItemClassCommon)) { + TrySpellProc(nullptr, (const Item_Struct*)nullptr, on); + return; + } + + // Innate + aug procs from weapons + // TODO: powersource procs + TryWeaponProc(weapon_g, weapon_g->GetItem(), on, hand); + // Procs from Buffs and AA both melee and range + TrySpellProc(weapon_g, weapon_g->GetItem(), on, hand); + + return; +} + +void Mob::TryWeaponProc(const ItemInst *inst, const Item_Struct *weapon, Mob *on, uint16 hand) +{ + + if (!weapon) + return; + uint16 skillinuse = 28; + int ourlevel = GetLevel(); + float ProcBonus = static_cast(aabonuses.ProcChanceSPA + + spellbonuses.ProcChanceSPA + itembonuses.ProcChanceSPA); + ProcBonus += static_cast(itembonuses.ProcChance) / 10.0f; // Combat Effects + float ProcChance = GetProcChances(ProcBonus, hand); + + if (hand != MainPrimary) //Is Archery intened to proc at 50% rate? + ProcChance /= 2; + + // Try innate proc on weapon + // We can proc once here, either weapon or one aug + bool proced = false; // silly bool to prevent augs from going if weapon does + skillinuse = GetSkillByItemType(weapon->ItemType); + if (weapon->Proc.Type == ET_CombatProc) { + float WPC = ProcChance * (100.0f + // Proc chance for this weapon + static_cast(weapon->ProcRate)) / 100.0f; + if (zone->random.Roll(WPC)) { // 255 dex = 0.084 chance of proc. No idea what this number should be really. + if (weapon->Proc.Level > ourlevel) { + mlog(COMBAT__PROCS, + "Tried to proc (%s), but our level (%d) is lower than required (%d)", + weapon->Name, ourlevel, weapon->Proc.Level); + if (IsPet()) { + Mob *own = GetOwner(); + if (own) + own->Message_StringID(13, PROC_PETTOOLOW); + } else { + Message_StringID(13, PROC_TOOLOW); + } + } else { + mlog(COMBAT__PROCS, + "Attacking weapon (%s) successfully procing spell %d (%.2f percent chance)", + weapon->Name, weapon->Proc.Effect, WPC * 100); + ExecWeaponProc(inst, weapon->Proc.Effect, on); + proced = true; + } + } + } + //If OneProcPerWeapon is not enabled, we reset the try for that weapon regardless of if we procced or not. + //This is for some servers that may want to have as many procs triggering from weapons as possible in a single round. + if(!RuleB(Combat, OneProcPerWeapon)) + proced = false; + + if (!proced && inst) { + for (int r = 0; r < EmuConstants::ITEM_COMMON_SIZE; r++) { + const ItemInst *aug_i = inst->GetAugment(r); + if (!aug_i) // no aug, try next slot! + continue; + const Item_Struct *aug = aug_i->GetItem(); + if (!aug) + continue; + + if (aug->Proc.Type == ET_CombatProc) { + float APC = ProcChance * (100.0f + // Proc chance for this aug + static_cast(aug->ProcRate)) / 100.0f; + if (zone->random.Roll(APC)) { + if (aug->Proc.Level > ourlevel) { + if (IsPet()) { + Mob *own = GetOwner(); + if (own) + own->Message_StringID(13, PROC_PETTOOLOW); + } else { + Message_StringID(13, PROC_TOOLOW); + } + } else { + ExecWeaponProc(aug_i, aug->Proc.Effect, on); + if (RuleB(Combat, OneProcPerWeapon)) + break; + } + } + } + } + } + // TODO: Powersource procs + + return; +} + +void Mob::TrySpellProc(const ItemInst *inst, const Item_Struct *weapon, Mob *on, uint16 hand) +{ + float ProcBonus = static_cast(spellbonuses.SpellProcChance + + itembonuses.SpellProcChance + aabonuses.SpellProcChance); + float ProcChance = 0.0f; + ProcChance = GetProcChances(ProcBonus, hand); + + if (hand != MainPrimary) //Is Archery intened to proc at 50% rate? + ProcChance /= 2; + + bool rangedattk = false; + if (weapon && hand == MainRange) { + if (weapon->ItemType == ItemTypeArrow || + weapon->ItemType == ItemTypeLargeThrowing || + weapon->ItemType == ItemTypeSmallThrowing || + weapon->ItemType == ItemTypeBow) + rangedattk = true; + } + + if (!weapon && hand == MainRange && GetSpecialAbility(SPECATK_RANGED_ATK)) + rangedattk = true; + + for (uint32 i = 0; i < MAX_PROCS; i++) { + if (IsPet() && hand != MainPrimary) //Pets can only proc spell procs from their primay hand (ie; beastlord pets) + continue; // If pets ever can proc from off hand, this will need to change + + // Not ranged + if (!rangedattk) { + // Perma procs (AAs) + if (PermaProcs[i].spellID != SPELL_UNKNOWN) { + if (zone->random.Roll(PermaProcs[i].chance)) { // TODO: Do these get spell bonus? + mlog(COMBAT__PROCS, + "Permanent proc %d procing spell %d (%d percent chance)", + i, PermaProcs[i].spellID, PermaProcs[i].chance); + ExecWeaponProc(nullptr, PermaProcs[i].spellID, on); + } else { + mlog(COMBAT__PROCS, + "Permanent proc %d failed to proc %d (%d percent chance)", + i, PermaProcs[i].spellID, PermaProcs[i].chance); + } + } + + // Spell procs (buffs) + if (SpellProcs[i].spellID != SPELL_UNKNOWN) { + float chance = ProcChance * (static_cast(SpellProcs[i].chance) / 100.0f); + if (zone->random.Roll(chance)) { + mlog(COMBAT__PROCS, + "Spell proc %d procing spell %d (%.2f percent chance)", + i, SpellProcs[i].spellID, chance); + ExecWeaponProc(nullptr, SpellProcs[i].spellID, on); + CheckNumHitsRemaining(NUMHIT_OffensiveSpellProcs, 0, SpellProcs[i].base_spellID); + } else { + mlog(COMBAT__PROCS, + "Spell proc %d failed to proc %d (%.2f percent chance)", + i, SpellProcs[i].spellID, chance); + } + } + } else if (rangedattk) { // ranged only + // ranged spell procs (buffs) + if (RangedProcs[i].spellID != SPELL_UNKNOWN) { + float chance = ProcChance * (static_cast(RangedProcs[i].chance) / 100.0f); + if (zone->random.Roll(chance)) { + mlog(COMBAT__PROCS, + "Ranged proc %d procing spell %d (%.2f percent chance)", + i, RangedProcs[i].spellID, chance); + ExecWeaponProc(nullptr, RangedProcs[i].spellID, on); + CheckNumHitsRemaining(NUMHIT_OffensiveSpellProcs, 0, RangedProcs[i].base_spellID); + } else { + mlog(COMBAT__PROCS, + "Ranged proc %d failed to proc %d (%.2f percent chance)", + i, RangedProcs[i].spellID, chance); + } + } + } + } + + if (HasSkillProcs() && hand != MainRange){ //We check ranged skill procs within the attack functions. + uint16 skillinuse = 28; + if (weapon) + skillinuse = GetSkillByItemType(weapon->ItemType); + + TrySkillProc(on, skillinuse, 0, false, hand); + } + + return; +} + +void Mob::TryPetCriticalHit(Mob *defender, uint16 skill, int32 &damage) +{ + if(damage < 1) + return; + + //Allows pets to perform critical hits. + //Each rank adds an additional 1% chance for any melee hit (primary, secondary, kick, bash, etc) to critical, + //dealing up to 63% more damage. http://www.magecompendium.com/aa-short-library.html + + Mob *owner = nullptr; + float critChance = 0.0f; + critChance += RuleI(Combat, MeleeBaseCritChance); + uint32 critMod = 163; + + if (damage < 1) //We can't critical hit if we don't hit. + return; + + if (IsPet()) + owner = GetOwner(); + else if ((IsNPC() && CastToNPC()->GetSwarmOwner())) + owner = entity_list.GetMobID(CastToNPC()->GetSwarmOwner()); + else + return; + + if (!owner) + return; + + int32 CritPetChance = owner->aabonuses.PetCriticalHit + owner->itembonuses.PetCriticalHit + owner->spellbonuses.PetCriticalHit; + int32 CritChanceBonus = GetCriticalChanceBonus(skill); + + if (CritPetChance || critChance) { + + //For pets use PetCriticalHit for base chance, pets do not innately critical with without it + //even if buffed with a CritChanceBonus effects. + critChance += CritPetChance; + critChance += critChance*CritChanceBonus/100.0f; + } + + if(critChance > 0){ + + critChance /= 100; + + if(zone->random.Roll(critChance)) + { + critMod += GetCritDmgMob(skill) * 2; // To account for base crit mod being 200 not 100 + damage = (damage * critMod) / 100; + entity_list.FilteredMessageClose_StringID(this, false, 200, + MT_CritMelee, FilterMeleeCrits, CRITICAL_HIT, + GetCleanName(), itoa(damage)); + } + } +} + +void Mob::TryCriticalHit(Mob *defender, uint16 skill, int32 &damage, ExtraAttackOptions *opts) +{ + if(damage < 1) + return; + + // decided to branch this into it's own function since it's going to be duplicating a lot of the + // code in here, but could lead to some confusion otherwise + if (IsPet() && GetOwner()->IsClient() || (IsNPC() && CastToNPC()->GetSwarmOwner())) { + TryPetCriticalHit(defender,skill,damage); + return; + } + +#ifdef BOTS + if (this->IsPet() && this->GetOwner()->IsBot()) { + this->TryPetCriticalHit(defender,skill,damage); + return; + } +#endif //BOTS + + float critChance = 0.0f; + bool IsBerskerSPA = false; + + //1: Try Slay Undead + if (defender && (defender->GetBodyType() == BT_Undead || + defender->GetBodyType() == BT_SummonedUndead || defender->GetBodyType() == BT_Vampire)) { + int32 SlayRateBonus = aabonuses.SlayUndead[0] + itembonuses.SlayUndead[0] + spellbonuses.SlayUndead[0]; + if (SlayRateBonus) { + float slayChance = static_cast(SlayRateBonus) / 10000.0f; + if (zone->random.Roll(slayChance)) { + int32 SlayDmgBonus = aabonuses.SlayUndead[1] + itembonuses.SlayUndead[1] + spellbonuses.SlayUndead[1]; + damage = (damage * SlayDmgBonus * 2.25) / 100; + if (GetGender() == 1) // female + entity_list.FilteredMessageClose_StringID(this, false, 200, + MT_CritMelee, FilterMeleeCrits, FEMALE_SLAYUNDEAD, + GetCleanName(), itoa(damage)); + else // males and neuter I guess + entity_list.FilteredMessageClose_StringID(this, false, 200, + MT_CritMelee, FilterMeleeCrits, MALE_SLAYUNDEAD, + GetCleanName(), itoa(damage)); + return; + } + } + } + + //2: Try Melee Critical + + //Base critical rate for all classes is dervived from DEX stat, this rate is then augmented + //by item,spell and AA bonuses allowing you a chance to critical hit. If the following rules + //are defined you will have an innate chance to hit at Level 1 regardless of bonuses. + //Warning: Do not define these rules if you want live like critical hits. + critChance += RuleI(Combat, MeleeBaseCritChance); + + if (IsClient()) { + critChance += RuleI(Combat, ClientBaseCritChance); + + if (spellbonuses.BerserkSPA || itembonuses.BerserkSPA || aabonuses.BerserkSPA) + IsBerskerSPA = true; + + if (((GetClass() == WARRIOR || GetClass() == BERSERKER) && GetLevel() >= 12) || IsBerskerSPA) { + if (IsBerserk() || IsBerskerSPA) + critChance += RuleI(Combat, BerserkBaseCritChance); + else + critChance += RuleI(Combat, WarBerBaseCritChance); + } + } + + int deadlyChance = 0; + int deadlyMod = 0; + if(skill == SkillArchery && GetClass() == RANGER && GetSkill(SkillArchery) >= 65) + critChance += 6; + + if (skill == SkillThrowing && GetClass() == ROGUE && GetSkill(SkillThrowing) >= 65) { + critChance += RuleI(Combat, RogueCritThrowingChance); + deadlyChance = RuleI(Combat, RogueDeadlyStrikeChance); + deadlyMod = RuleI(Combat, RogueDeadlyStrikeMod); + } + + int CritChanceBonus = GetCriticalChanceBonus(skill); + + if (CritChanceBonus || critChance) { + + //Get Base CritChance from Dex. (200 = ~1.6%, 255 = ~2.0%, 355 = ~2.20%) Fall off rate > 255 + //http://giline.versus.jp/shiden/su.htm , http://giline.versus.jp/shiden/damage_e.htm + if (GetDEX() <= 255) + critChance += (float(GetDEX()) / 125.0f); + else if (GetDEX() > 255) + critChance += (float(GetDEX()-255)/ 500.0f) + 2.0f; + critChance += critChance*(float)CritChanceBonus /100.0f; + } + + if(opts) { + critChance *= opts->crit_percent; + critChance += opts->crit_flat; + } + + if(critChance > 0) { + + critChance /= 100; + + if(zone->random.Roll(critChance)) + { + uint32 critMod = 200; + bool crip_success = false; + int32 CripplingBlowChance = GetCrippBlowChance(); + + //Crippling Blow Chance: The percent value of the effect is applied + //to the your Chance to Critical. (ie You have 10% chance to critical and you + //have a 200% Chance to Critical Blow effect, therefore you have a 20% Chance to Critical Blow. + if (CripplingBlowChance || (IsBerserk() || IsBerskerSPA)) { + if (!IsBerserk() && !IsBerskerSPA) + critChance *= float(CripplingBlowChance)/100.0f; + + if ((IsBerserk() || IsBerskerSPA) || zone->random.Roll(critChance)) { + critMod = 400; + crip_success = true; + } + } + + critMod += GetCritDmgMob(skill) * 2; // To account for base crit mod being 200 not 100 + damage = damage * critMod / 100; + + bool deadlySuccess = false; + if (deadlyChance && zone->random.Roll(static_cast(deadlyChance) / 100.0f)) { + if (BehindMob(defender, GetX(), GetY())) { + damage *= deadlyMod; + deadlySuccess = true; + } + } + + if (crip_success) { + entity_list.FilteredMessageClose_StringID(this, false, 200, + MT_CritMelee, FilterMeleeCrits, CRIPPLING_BLOW, + GetCleanName(), itoa(damage)); + // 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)){ + defender->Emote("staggers."); + defender->Stun(0); + } + } else if (deadlySuccess) { + entity_list.FilteredMessageClose_StringID(this, false, 200, + MT_CritMelee, FilterMeleeCrits, DEADLY_STRIKE, + GetCleanName(), itoa(damage)); + } else { + entity_list.FilteredMessageClose_StringID(this, false, 200, + MT_CritMelee, FilterMeleeCrits, CRITICAL_HIT, + GetCleanName(), itoa(damage)); + } + } + } +} + + +bool Mob::TryFinishingBlow(Mob *defender, SkillUseTypes skillinuse) +{ + if (defender && !defender->IsClient() && defender->GetHPRatio() < 10){ + + uint32 FB_Dmg = aabonuses.FinishingBlow[1] + spellbonuses.FinishingBlow[1] + itembonuses.FinishingBlow[1]; + + uint32 FB_Level = 0; + FB_Level = aabonuses.FinishingBlowLvl[0]; + if (FB_Level < spellbonuses.FinishingBlowLvl[0]) + FB_Level = spellbonuses.FinishingBlowLvl[0]; + else if (FB_Level < itembonuses.FinishingBlowLvl[0]) + FB_Level = itembonuses.FinishingBlowLvl[0]; + + //Proc Chance value of 500 = 5% + uint32 ProcChance = (aabonuses.FinishingBlow[0] + spellbonuses.FinishingBlow[0] + spellbonuses.FinishingBlow[0])/10; + + if(FB_Level && FB_Dmg && (defender->GetLevel() <= FB_Level) && (ProcChance >= zone->random.Int(0, 1000))){ + entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, FINISHING_BLOW, GetName()); + DoSpecialAttackDamage(defender, skillinuse, FB_Dmg, 1, -1, 10, false, false); + return true; + } + } + return false; +} + +void Mob::DoRiposte(Mob* defender) { + mlog(COMBAT__ATTACKS, "Preforming a riposte"); + + if (!defender) + return; + + defender->Attack(this, MainPrimary, true); + if (HasDied()) return; + + int32 DoubleRipChance = defender->aabonuses.GiveDoubleRiposte[0] + + defender->spellbonuses.GiveDoubleRiposte[0] + + defender->itembonuses.GiveDoubleRiposte[0]; + + DoubleRipChance = defender->aabonuses.DoubleRiposte + + defender->spellbonuses.DoubleRiposte + + defender->itembonuses.DoubleRiposte; + + //Live AA - Double Riposte + if(DoubleRipChance && zone->random.Roll(DoubleRipChance)) { + mlog(COMBAT__ATTACKS, "Preforming a double riposed (%d percent chance)", DoubleRipChance); + defender->Attack(this, MainPrimary, true); + if (HasDied()) return; + } + + //Double Riposte effect, allows for a chance to do RIPOSTE with a skill specfic special attack (ie Return Kick). + //Coded narrowly: Limit to one per client. Limit AA only. [1 = Skill Attack Chance, 2 = Skill] + + DoubleRipChance = defender->aabonuses.GiveDoubleRiposte[1]; + + if(DoubleRipChance && zone->random.Roll(DoubleRipChance)) { + mlog(COMBAT__ATTACKS, "Preforming a return SPECIAL ATTACK (%d percent chance)", DoubleRipChance); + + if (defender->GetClass() == MONK) + defender->MonkSpecialAttack(this, defender->aabonuses.GiveDoubleRiposte[2]); + else if (defender->IsClient()) + defender->CastToClient()->DoClassAttacks(this,defender->aabonuses.GiveDoubleRiposte[2], true); + } +} + +void Mob::ApplyMeleeDamageBonus(uint16 skill, int32 &damage){ + + if(!RuleB(Combat, UseIntervalAC)){ + if(IsNPC()){ //across the board NPC damage bonuses. + //only account for STR here, assume their base STR was factored into their DB damages + int dmgbonusmod = 0; + dmgbonusmod += (100*(itembonuses.STR + spellbonuses.STR))/3; + dmgbonusmod += (100*(spellbonuses.ATK + itembonuses.ATK))/5; + mlog(COMBAT__DAMAGE, "Damage bonus: %d percent from ATK and STR bonuses.", (dmgbonusmod/100)); + damage += (damage*dmgbonusmod/10000); + } + } + + damage += damage * GetMeleeDamageMod_SE(skill) / 100; +} + +bool Mob::HasDied() { + bool Result = false; + int32 hp_below = 0; + + hp_below = (GetDelayDeath() * -1); + + if((GetHP()) <= (hp_below)) + Result = true; + + return Result; +} + +uint16 Mob::GetDamageTable(SkillUseTypes skillinuse) +{ + if(GetLevel() <= 51) + { + uint32 ret_table = 0; + int str_over_75 = 0; + if(GetSTR() > 75) + str_over_75 = GetSTR() - 75; + if(str_over_75 > 255) + ret_table = (GetSkill(skillinuse)+255)/2; + else + ret_table = (GetSkill(skillinuse)+str_over_75)/2; + + if(ret_table < 100) + return 100; + + return ret_table; + } + else if(GetLevel() >= 90) + { + if(GetClass() == MONK) + return 379; + else + return 345; + } + else + { + uint32 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(GetClass() == MONK) + return (dmg_table[GetLevel()-51]*(100+RuleI(Combat,MonkDamageTableBonus))/100); + else + return dmg_table[GetLevel()-51]; + } +} + +void Mob::TrySkillProc(Mob *on, uint16 skill, uint16 ReuseTime, bool Success, uint16 hand, bool IsDefensive) +{ + + if (!on) { + SetTarget(nullptr); + LogFile->write(EQEMuLog::Error, "A null Mob object was passed to Mob::TrySkillProc for evaluation!"); + return; + } + + if (!spellbonuses.LimitToSkill[skill] && !itembonuses.LimitToSkill[skill] && !aabonuses.LimitToSkill[skill]) + return; + + /*Allow one proc from each (Spell/Item/AA) + Kayen: Due to limited avialability of effects on live it is too difficult + to confirm how they stack at this time, will adjust formula when more data is avialablle to test.*/ + bool CanProc = true; + + uint16 base_spell_id = 0; + uint16 proc_spell_id = 0; + float ProcMod = 0; + float chance = 0; + + if (IsDefensive) + chance = on->GetSkillProcChances(ReuseTime, hand); + else + chance = GetSkillProcChances(ReuseTime, hand); + + if (spellbonuses.LimitToSkill[skill]){ + + for(int e = 0; e < MAX_SKILL_PROCS; e++){ +<<<<<<< HEAD + + if (CanProc && + (!Success && spellbonuses.SkillProc[e] && IsValidSpell(spellbonuses.SkillProc[e])) +======= + if (CanProc && + (!Success && spellbonuses.SkillProc[e] && IsValidSpell(spellbonuses.SkillProc[e])) +>>>>>>> master + || (Success && spellbonuses.SkillProcSuccess[e] && IsValidSpell(spellbonuses.SkillProcSuccess[e]))) { + base_spell_id = spellbonuses.SkillProc[e]; + base_spell_id = 0; + ProcMod = 0; + + for (int i = 0; i < EFFECT_COUNT; i++) { +<<<<<<< HEAD + +======= +>>>>>>> master + if (spells[base_spell_id].effectid[i] == SE_SkillProc) { + proc_spell_id = spells[base_spell_id].base[i]; + ProcMod = static_cast(spells[base_spell_id].base2[i]); + } + + else if (spells[base_spell_id].effectid[i] == SE_LimitToSkill && spells[base_spell_id].effectid[i] <= HIGHEST_SKILL) { + + if (CanProc && spells[base_spell_id].base[i] == skill && IsValidSpell(proc_spell_id)) { + float final_chance = chance * (ProcMod / 100.0f); + if (zone->random.Roll(final_chance)) { + ExecWeaponProc(nullptr, proc_spell_id, on); + CheckNumHitsRemaining(NUMHIT_OffensiveSpellProcs,0, base_spell_id); + CanProc = false; + break; + } + } + } + else { + proc_spell_id = 0; + ProcMod = 0; + } + } + } + } + } + + if (itembonuses.LimitToSkill[skill]){ + CanProc = true; + for(int e = 0; e < MAX_SKILL_PROCS; e++){ +<<<<<<< HEAD + + if (CanProc && + (!Success && itembonuses.SkillProc[e] && IsValidSpell(itembonuses.SkillProc[e])) +======= + if (CanProc && + (!Success && itembonuses.SkillProc[e] && IsValidSpell(itembonuses.SkillProc[e])) +>>>>>>> master + || (Success && itembonuses.SkillProcSuccess[e] && IsValidSpell(itembonuses.SkillProcSuccess[e]))) { + base_spell_id = itembonuses.SkillProc[e]; + base_spell_id = 0; + ProcMod = 0; + + for (int i = 0; i < EFFECT_COUNT; i++) { +<<<<<<< HEAD + +======= +>>>>>>> master + if (spells[base_spell_id].effectid[i] == SE_SkillProc) { + proc_spell_id = spells[base_spell_id].base[i]; + ProcMod = static_cast(spells[base_spell_id].base2[i]); + } + + else if (spells[base_spell_id].effectid[i] == SE_LimitToSkill && spells[base_spell_id].effectid[i] <= HIGHEST_SKILL) { + + if (CanProc && spells[base_spell_id].base[i] == skill && IsValidSpell(proc_spell_id)) { + float final_chance = chance * (ProcMod / 100.0f); + if (zone->random.Roll(final_chance)) { + ExecWeaponProc(nullptr, proc_spell_id, on); + CanProc = false; + break; + } + } + } + else { + proc_spell_id = 0; + ProcMod = 0; + } + } + } + } + } + + if (IsClient() && aabonuses.LimitToSkill[skill]){ + + CanProc = true; + uint32 effect = 0; + int32 base1 = 0; + int32 base2 = 0; + uint32 slot = 0; + + for(int e = 0; e < MAX_SKILL_PROCS; e++){ +<<<<<<< HEAD + +======= +>>>>>>> master + if (CanProc && + (!Success && aabonuses.SkillProc[e]) + || (Success && aabonuses.SkillProcSuccess[e])){ + int aaid = aabonuses.SkillProc[e]; + base_spell_id = 0; + ProcMod = 0; + + std::map >::const_iterator find_iter = aa_effects.find(aaid); + if(find_iter == aa_effects.end()) + break; + + for (std::map::const_iterator iter = aa_effects[aaid].begin(); iter != aa_effects[aaid].end(); ++iter) { + effect = iter->second.skill_id; + base1 = iter->second.base1; + base2 = iter->second.base2; + slot = iter->second.slot; + + if (effect == SE_SkillProc) { + proc_spell_id = base1; + ProcMod = static_cast(base2); + } + + else if (effect == SE_LimitToSkill && effect <= HIGHEST_SKILL) { + + if (CanProc && base1 == skill && IsValidSpell(proc_spell_id)) { + float final_chance = chance * (ProcMod / 100.0f); + + if (zone->random.Roll(final_chance)) { + ExecWeaponProc(nullptr, proc_spell_id, on); + CanProc = false; + break; + } + } + } + else { + proc_spell_id = 0; + ProcMod = 0; + } + } + } + } + } +} + +float Mob::GetSkillProcChances(uint16 ReuseTime, uint16 hand) { + + uint32 weapon_speed; + float ProcChance = 0; +<<<<<<< HEAD + + if (!ReuseTime && hand) { +======= +>>>>>>> master + + if (!ReuseTime && hand) { + weapon_speed = GetWeaponSpeedbyHand(hand); + ProcChance = static_cast(weapon_speed) * (RuleR(Combat, AvgProcsPerMinute) / 60000.0f); +<<<<<<< HEAD + +======= +>>>>>>> master + if (hand != MainPrimary) + ProcChance /= 2; + } + + else + ProcChance = static_cast(ReuseTime) * (RuleR(Combat, AvgProcsPerMinute) / 60000.0f); + + return ProcChance; +} + +bool Mob::TryRootFadeByDamage(int buffslot, Mob* attacker) { + +<<<<<<< HEAD + /*Dev Quote 2010: http://forums.station.sony.com/eq/posts/list.m?topic_id=161443 + The Viscid Roots AA does the following: Reduces the chance for root to break by X percent. + There is no distinction of any kind between the caster inflicted damage, or anyone + else's damage. There is also no distinction between Direct and DOT damage in the root code. + + /* General Mechanics + - Check buffslot to make sure damage from a root does not cancel the root + - If multiple roots on target, always and only checks first root slot and if broken only removes that slots root. + - Only roots on determental spells can be broken by damage. + - Root break chance values obtained from live parses. + */ + + if (!attacker || !spellbonuses.Root[0] || spellbonuses.Root[1] < 0) + return false; + + if (IsDetrimentalSpell(spellbonuses.Root[1]) && spellbonuses.Root[1] != buffslot){ + + int BreakChance = RuleI(Spells, RootBreakFromSpells); + + BreakChance -= BreakChance*buffs[spellbonuses.Root[1]].RootBreakChance/100; +======= + /*Dev Quote 2010: http://forums.station.sony.com/eq/posts/list.m?topic_id=161443 + The Viscid Roots AA does the following: Reduces the chance for root to break by X percent. + There is no distinction of any kind between the caster inflicted damage, or anyone + else's damage. There is also no distinction between Direct and DOT damage in the root code. + + General Mechanics + - Check buffslot to make sure damage from a root does not cancel the root + - If multiple roots on target, always and only checks first root slot and if broken only removes that slots root. + - Only roots on determental spells can be broken by damage. + - Root break chance values obtained from live parses. + */ + + if (!attacker || !spellbonuses.Root[0] || spellbonuses.Root[1] < 0) + return false; + + if (IsDetrimentalSpell(spellbonuses.Root[1]) && spellbonuses.Root[1] != buffslot){ + int BreakChance = RuleI(Spells, RootBreakFromSpells); + + BreakChance -= BreakChance*buffs[spellbonuses.Root[1]].RootBreakChance/100; +>>>>>>> master + int level_diff = attacker->GetLevel() - GetLevel(); + + //Use baseline if level difference <= 1 (ie. If target is (1) level less than you, or equal or greater level) + + if (level_diff == 2) + BreakChance = (BreakChance * 80) /100; //Decrease by 20%; + + else if (level_diff >= 3 && level_diff <= 20) + BreakChance = (BreakChance * 60) /100; //Decrease by 40%; + + else if (level_diff > 21) + BreakChance = (BreakChance * 20) /100; //Decrease by 80%; +<<<<<<< HEAD + + if (BreakChance < 1) + BreakChance = 1; +======= +>>>>>>> master + + if (BreakChance < 1) + BreakChance = 1; + + if (zone->random.Roll(BreakChance)) { + + if (!TryFadeEffect(spellbonuses.Root[1])) { + BuffFadeBySlot(spellbonuses.Root[1]); + mlog(COMBAT__HITS, "Spell broke root! BreakChance percent chance"); + return true; + } + } + } + + mlog(COMBAT__HITS, "Spell did not break root. BreakChance percent chance"); + return false; +} + +int32 Mob::RuneAbsorb(int32 damage, uint16 type) +{ + uint32 buff_max = GetMaxTotalSlots(); + if (type == SE_Rune){ + for(uint32 slot = 0; slot < buff_max; slot++) { + if(slot == spellbonuses.MeleeRune[1] && spellbonuses.MeleeRune[0] && buffs[slot].melee_rune && IsValidSpell(buffs[slot].spellid)){ + int melee_rune_left = buffs[slot].melee_rune; + + if(melee_rune_left > damage) + { + melee_rune_left -= damage; + buffs[slot].melee_rune = melee_rune_left; + return -6; + } + + else + { + if(melee_rune_left > 0) + damage -= melee_rune_left; + + if(!TryFadeEffect(slot)) + BuffFadeBySlot(slot); + } + } + } + } + + + else{ + for(uint32 slot = 0; slot < buff_max; slot++) { + if(slot == spellbonuses.AbsorbMagicAtt[1] && spellbonuses.AbsorbMagicAtt[0] && buffs[slot].magic_rune && IsValidSpell(buffs[slot].spellid)){ + int magic_rune_left = buffs[slot].magic_rune; + if(magic_rune_left > damage) + { + magic_rune_left -= damage; + buffs[slot].magic_rune = magic_rune_left; + return 0; + } + + else + { + if(magic_rune_left > 0) + damage -= magic_rune_left; + + if(!TryFadeEffect(slot)) + BuffFadeBySlot(slot); + } + } + } + } + + return damage; +} + +void Mob::CommonOutgoingHitSuccess(Mob* defender, int32 &damage, SkillUseTypes skillInUse) +{ + if (!defender) + return; + + ApplyMeleeDamageBonus(skillInUse, damage); + damage += (damage * defender->GetSkillDmgTaken(skillInUse) / 100) + (GetSkillDmgAmt(skillInUse) + defender->GetFcDamageAmtIncoming(this, 0, true, skillInUse)); + TryCriticalHit(defender, skillInUse, damage); + CheckNumHitsRemaining(NUMHIT_OutgoingHitSuccess); +} + +void Mob::CommonBreakInvisible() +{ + //break invis when you attack + if(invisible) { + mlog(COMBAT__ATTACKS, "Removing invisibility due to melee attack."); + BuffFadeByEffect(SE_Invisibility); + BuffFadeByEffect(SE_Invisibility2); + invisible = false; + } + if(invisible_undead) { + mlog(COMBAT__ATTACKS, "Removing invisibility vs. undead due to melee attack."); + BuffFadeByEffect(SE_InvisVsUndead); + BuffFadeByEffect(SE_InvisVsUndead2); + invisible_undead = false; + } + if(invisible_animals){ + mlog(COMBAT__ATTACKS, "Removing invisibility vs. animals due to melee attack."); + BuffFadeByEffect(SE_InvisVsAnimals); + invisible_animals = false; + } + + if (spellbonuses.NegateIfCombat) + BuffFadeByEffect(SE_NegateIfCombat); + + if(hidden || improved_hidden){ + hidden = false; + improved_hidden = false; + EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct)); + SpawnAppearance_Struct* sa_out = (SpawnAppearance_Struct*)outapp->pBuffer; + sa_out->spawn_id = GetID(); + sa_out->type = 0x03; + sa_out->parameter = 0; + entity_list.QueueClients(this, outapp, true); + safe_delete(outapp); + } + + if (spellbonuses.NegateIfCombat) + BuffFadeByEffect(SE_NegateIfCombat); + + hidden = false; + improved_hidden = false; +} + +/* Dev quotes: + * Old formula + * Final delay = (Original Delay / (haste mod *.01f)) + ((Hundred Hands / 100) * Original Delay) + * New formula + * Final delay = (Original Delay / (haste mod *.01f)) + ((Hundred Hands / 1000) * (Original Delay / (haste mod *.01f)) + * Base Delay 20 25 30 37 + * Haste 2.25 2.25 2.25 2.25 + * HHE (old) -17 -17 -17 -17 + * Final Delay 5.488888889 6.861111111 8.233333333 10.15444444 + * + * Base Delay 20 25 30 37 + * Haste 2.25 2.25 2.25 2.25 + * HHE (new) -383 -383 -383 -383 + * Final Delay 5.484444444 6.855555556 8.226666667 10.14622222 + * + * Difference -0.004444444 -0.005555556 -0.006666667 -0.008222222 + * + * These times are in 10th of a second + */ + +void Mob::SetAttackTimer() +{ + attack_timer.SetAtTrigger(4000, true); +} + +void Client::SetAttackTimer() +{ + float haste_mod = GetHaste() * 0.01f; + + //default value for attack timer in case they have + //an invalid weapon equipped: + attack_timer.SetAtTrigger(4000, true); + + Timer *TimerToUse = nullptr; + const Item_Struct *PrimaryWeapon = nullptr; + + for (int i = MainRange; i <= MainSecondary; i++) { + //pick a timer + if (i == MainPrimary) + TimerToUse = &attack_timer; + else if (i == MainRange) + TimerToUse = &ranged_timer; + else if (i == MainSecondary) + TimerToUse = &attack_dw_timer; + else //invalid slot (hands will always hit this) + continue; + + const Item_Struct *ItemToUse = nullptr; + + //find our item + ItemInst *ci = GetInv().GetItem(i); + if (ci) + ItemToUse = ci->GetItem(); + + //special offhand stuff + if (i == MainSecondary) { + //if we have a 2H weapon in our main hand, no dual + if (PrimaryWeapon != nullptr) { + if (PrimaryWeapon->ItemClass == ItemClassCommon + && (PrimaryWeapon->ItemType == ItemType2HSlash + || PrimaryWeapon->ItemType == ItemType2HBlunt + || PrimaryWeapon->ItemType == ItemType2HPiercing)) { + attack_dw_timer.Disable(); + continue; + } + } + + //if we cant dual wield, skip it + if (!CanThisClassDualWield()) { + attack_dw_timer.Disable(); + continue; + } + } + + //see if we have a valid weapon + if (ItemToUse != nullptr) { + //check type and damage/delay + if (ItemToUse->ItemClass != ItemClassCommon + || ItemToUse->Damage == 0 + || ItemToUse->Delay == 0) { + //no weapon + ItemToUse = nullptr; + } + // Check to see if skill is valid + else if ((ItemToUse->ItemType > ItemTypeLargeThrowing) && + (ItemToUse->ItemType != ItemTypeMartial) && + (ItemToUse->ItemType != ItemType2HPiercing)) { + //no weapon + ItemToUse = nullptr; + } + } + + int hhe = itembonuses.HundredHands + spellbonuses.HundredHands; + int speed = 0; + int delay = 36; + float quiver_haste = 0.0f; + + //if we have no weapon.. + if (ItemToUse == nullptr) { + //above checks ensure ranged weapons do not fall into here + // Work out if we're a monk + if (GetClass() == MONK || GetClass() == BEASTLORD) + delay = GetMonkHandToHandDelay(); + } else { + //we have a weapon, use its delay + delay = ItemToUse->Delay; + if (ItemToUse->ItemType == ItemTypeBow || ItemToUse->ItemType == ItemTypeLargeThrowing) + quiver_haste = GetQuiverHaste(); + } + if (RuleB(Spells, Jun182014HundredHandsRevamp)) + speed = static_cast(((delay / haste_mod) + ((hhe / 1000.0f) * (delay / haste_mod))) * 100); + else + speed = static_cast(((delay / haste_mod) + ((hhe / 100.0f) * delay)) * 100); + // this is probably wrong + if (quiver_haste > 0) + speed *= quiver_haste; + TimerToUse->SetAtTrigger(std::max(RuleI(Combat, MinHastedDelay), speed), true); + + if (i == MainPrimary) + PrimaryWeapon = ItemToUse; + } +} + +void NPC::SetAttackTimer() +{ + float haste_mod = GetHaste() * 0.01f; + + //default value for attack timer in case they have + //an invalid weapon equipped: + attack_timer.SetAtTrigger(4000, true); + + Timer *TimerToUse = nullptr; + int hhe = itembonuses.HundredHands + spellbonuses.HundredHands; + + // Technically NPCs should do some logic for weapons, but the effect is minimal + // What they do is take the lower of their set delay and the weapon's + // ex. Mob's delay set to 20, weapon set to 19, delay 19 + // Mob's delay set to 20, weapon set to 21, delay 20 + int speed = 0; + if (RuleB(Spells, Jun182014HundredHandsRevamp)) + speed = static_cast(((attack_delay / haste_mod) + ((hhe / 1000.0f) * (attack_delay / haste_mod))) * 100); + else + speed = static_cast(((attack_delay / haste_mod) + ((hhe / 100.0f) * attack_delay)) * 100); + + for (int i = MainRange; i <= MainSecondary; i++) { + //pick a timer + if (i == MainPrimary) + TimerToUse = &attack_timer; + else if (i == MainRange) + TimerToUse = &ranged_timer; + else if (i == MainSecondary) + TimerToUse = &attack_dw_timer; + else //invalid slot (hands will always hit this) + continue; + + //special offhand stuff + if (i == MainSecondary) { + //NPCs get it for free at 13 + if(GetLevel() < 13) { + attack_dw_timer.Disable(); + continue; + } + } + + TimerToUse->SetAtTrigger(std::max(RuleI(Combat, MinHastedDelay), speed), true); + } +} diff --git a/zone/bot.cpp b/zone/bot.cpp index 8d84899f3..2f39b2a75 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -775,49 +775,49 @@ void Bot::GenerateAppearance() { // Randomize facial appearance int iFace = 0; if(this->GetRace() == 2) { // Barbarian w/Tatoo - iFace = MakeRandomInt(0, 79); + iFace = zone->random.Int(0, 79); } else { - iFace = MakeRandomInt(0, 7); + iFace = zone->random.Int(0, 7); } int iHair = 0; int iBeard = 0; int iBeardColor = 1; if(this->GetRace() == 522) { - iHair = MakeRandomInt(0, 8); - iBeard = MakeRandomInt(0, 11); - iBeardColor = MakeRandomInt(0, 3); + iHair = zone->random.Int(0, 8); + iBeard = zone->random.Int(0, 11); + iBeardColor = zone->random.Int(0, 3); } else if(this->GetGender()) { - iHair = MakeRandomInt(0, 2); + iHair = zone->random.Int(0, 2); if(this->GetRace() == 8) { // Dwarven Females can have a beard - if(MakeRandomInt(1, 100) < 50) { + if(zone->random.Int(1, 100) < 50) { iFace += 10; } } } else { - iHair = MakeRandomInt(0, 3); - iBeard = MakeRandomInt(0, 5); - iBeardColor = MakeRandomInt(0, 19); + iHair = zone->random.Int(0, 3); + iBeard = zone->random.Int(0, 5); + iBeardColor = zone->random.Int(0, 19); } int iHairColor = 0; if(this->GetRace() == 522) { - iHairColor = MakeRandomInt(0, 3); + iHairColor = zone->random.Int(0, 3); } else { - iHairColor = MakeRandomInt(0, 19); + iHairColor = zone->random.Int(0, 19); } - uint8 iEyeColor1 = (uint8)MakeRandomInt(0, 9); + uint8 iEyeColor1 = (uint8)zone->random.Int(0, 9); uint8 iEyeColor2 = 0; if(this->GetRace() == 522) { - iEyeColor1 = iEyeColor2 = (uint8)MakeRandomInt(0, 11); + iEyeColor1 = iEyeColor2 = (uint8)zone->random.Int(0, 11); } - else if(MakeRandomInt(1, 100) > 96) { - iEyeColor2 = MakeRandomInt(0, 9); + else if(zone->random.Int(1, 100) > 96) { + iEyeColor2 = zone->random.Int(0, 9); } else { iEyeColor2 = iEyeColor1; @@ -827,9 +827,9 @@ void Bot::GenerateAppearance() { int iTattoo = 0; int iDetails = 0; if(this->GetRace() == 522) { - iHeritage = MakeRandomInt(0, 6); - iTattoo = MakeRandomInt(0, 7); - iDetails = MakeRandomInt(0, 7); + iHeritage = zone->random.Int(0, 6); + iTattoo = zone->random.Int(0, 7); + iDetails = zone->random.Int(0, 7); } this->luclinface = iFace; @@ -3098,7 +3098,7 @@ bool Bot::CheckBotDoubleAttack(bool tripleAttack) { chance *= float(100.0f+triple_bonus)/100.0f; //Apply modifiers. } - if((MakeRandomFloat(0, 1) < chance)) + if((zone->random.Real(0, 1) < chance)) return true; return false; @@ -3148,7 +3148,7 @@ void Bot::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillUseTypes if(RuleB(Combat, UseIntervalAC)) damage = max_hit; else - damage = MakeRandomInt(min_hit, max_hit); + damage = zone->random.Int(min_hit, max_hit); if(!other->CheckHitChance(this, skillinuse, Hand, chance_mod)) { damage = 0; @@ -3203,7 +3203,7 @@ void Bot::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillUseTypes if (damage > 0) CheckNumHitsRemaining(NUMHIT_OutgoingHitSuccess); - if((skillinuse == SkillDragonPunch) && GetAA(aaDragonPunch) && MakeRandomInt(0, 99) < 25){ + if((skillinuse == SkillDragonPunch) && GetAA(aaDragonPunch) && zone->random.Int(0, 99) < 25){ SpellFinished(904, other, 10, 0, -1, spells[904].ResistDiff); other->Stun(100); } @@ -3483,7 +3483,7 @@ void Bot::AI_Process() { meleeDistance = meleeDistance * .30; } else { - meleeDistance *= (float)MakeRandomFloat(.50, .85); + meleeDistance *= (float)zone->random.Real(.50, .85); } bool atArcheryRange = IsArcheryRange(GetTarget()); @@ -3616,7 +3616,7 @@ void Bot::AI_Process() { if (GetTarget() && flurrychance) { - if(MakeRandomInt(0, 100) < flurrychance) + if(zone->random.Int(0, 100) < flurrychance) { Message_StringID(MT_NPCFlurry, YOU_FLURRY); Attack(GetTarget(), MainPrimary, false); @@ -3633,7 +3633,7 @@ void Bot::AI_Process() { wpn->GetItem()->ItemType == ItemType2HBlunt || wpn->GetItem()->ItemType == ItemType2HPiercing ) { - if(MakeRandomInt(0, 100) < ExtraAttackChanceBonus) + if(zone->random.Int(0, 100) < ExtraAttackChanceBonus) { Attack(GetTarget(), MainPrimary, false); } @@ -3678,7 +3678,7 @@ void Bot::AI_Process() { int32 DWBonus = spellbonuses.DualWieldChance + itembonuses.DualWieldChance; DualWieldProbability += DualWieldProbability*float(DWBonus)/ 100.0f; - float random = MakeRandomFloat(0, 1); + float random = zone->random.Real(0, 1); if (random < DualWieldProbability){ // Max 78% of DW @@ -3912,7 +3912,7 @@ void Bot::PetAIProcess() { if (botPet->GetTarget()) // Do we still have a target? { // We're a pet so we re able to dual attack - int32 RandRoll = MakeRandomInt(0, 99); + int32 RandRoll = zone->random.Int(0, 99); if (botPet->CanThisClassDoubleAttack() && (RandRoll < (botPet->GetLevel() + NPCDualAttackModifier))) { if(botPet->Attack(botPet->GetTarget(), MainPrimary)) @@ -3945,7 +3945,7 @@ void Bot::PetAIProcess() { //aa_chance += botPet->GetOwner()->GetAA(aaCompanionsAlacrity) * 3; - if (MakeRandomInt(1, 100) < aa_chance) + if (zone->random.Int(1, 100) < aa_chance) Flurry(nullptr); } @@ -3955,12 +3955,12 @@ void Bot::PetAIProcess() { if(botPet->GetOwner()->GetLevel() >= 24) { float DualWieldProbability = (botPet->GetSkill(SkillDualWield) + botPet->GetLevel()) / 400.0f; - DualWieldProbability -= MakeRandomFloat(0, 1); + DualWieldProbability -= zone->random.Real(0, 1); if(DualWieldProbability < 0){ botPet->Attack(botPet->GetTarget(), MainSecondary); if (botPet->CanThisClassDoubleAttack()) { - int32 RandRoll = MakeRandomInt(0, 99); + int32 RandRoll = zone->random.Int(0, 99); if (RandRoll < (botPet->GetLevel() + 20)) { botPet->Attack(botPet->GetTarget(), MainSecondary); @@ -6213,7 +6213,7 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b if(RuleB(Combat, UseIntervalAC)) damage = max_hit; else - damage = MakeRandomInt(min_hit, max_hit); + damage = zone->random.Int(min_hit, max_hit); mlog(COMBAT__DAMAGE, "Damage calculated to %d (min %d, max %d, str %d, skill %d, DMG %d, lv %d)", damage, min_hit, max_hit, GetSTR(), GetSkill(skillinuse), weapon_damage, GetLevel()); @@ -6258,7 +6258,7 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b OffhandRiposteFail *= -1; //Live uses a negative value for this. if (OffhandRiposteFail && - (OffhandRiposteFail > 99 || (MakeRandomInt(0, 100) < OffhandRiposteFail))) { + (OffhandRiposteFail > 99 || (zone->random.Int(0, 100) < OffhandRiposteFail))) { damage = 0; // Counts as a miss slippery_attack = true; } else @@ -6274,7 +6274,7 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b if (((damage < 0) || slippery_attack) && !FromRiposte && !IsStrikethrough) { // Hack to still allow Strikethrough chance w/ Slippery Attacks AA int32 bonusStrikeThrough = itembonuses.StrikeThrough + spellbonuses.StrikeThrough + aabonuses.StrikeThrough; - if(bonusStrikeThrough && (MakeRandomInt(0, 100) < bonusStrikeThrough)) { + if(bonusStrikeThrough && (zone->random.Int(0, 100) < bonusStrikeThrough)) { Message_StringID(MT_StrikeThrough, STRIKETHROUGH_STRING); // You strike through your opponents defenses! Attack(other, Hand, false, true); // Strikethrough only gives another attempted hit return false; @@ -6623,7 +6623,7 @@ int32 Bot::CalcBotAAFocus(BotfocusType type, uint32 aa_ID, uint16 spell_id) { if(type == focusTriggerOnCast) { - if(MakeRandomInt(0, 100) <= base1){ + if(zone->random.Int(0, 100) <= base1){ value = base2; } @@ -6646,7 +6646,7 @@ int32 Bot::CalcBotAAFocus(BotfocusType type, uint32 aa_ID, uint16 spell_id) { if(type == focusBlockNextSpell) { - if(MakeRandomInt(1, 100) <= base1) + if(zone->random.Int(1, 100) <= base1) value = 1; } break; @@ -6670,7 +6670,7 @@ int32 Bot::CalcBotAAFocus(BotfocusType type, uint32 aa_ID, uint16 spell_id) int32 cast_time = GetActSpellCasttime(spell_id, spells[spell_id].cast_time); GetSympatheticProcChances(ProcBonus, ProcChance, cast_time, ProcRateMod); - if(MakeRandomFloat(0, 1) <= ProcChance) + if(zone->random.Real(0, 1) <= ProcChance) value = focus_id; else @@ -7147,7 +7147,7 @@ int32 Bot::CalcBotFocusEffect(BotfocusType bottype, uint16 focus_id, uint16 spel value = focus_spell.base[i]; } else { - value = MakeRandomInt(focus_spell.base[i], focus_spell.base2[i]); + value = zone->random.Int(focus_spell.base[i], focus_spell.base2[i]); } } break; @@ -7165,7 +7165,7 @@ int32 Bot::CalcBotFocusEffect(BotfocusType bottype, uint16 focus_id, uint16 spel value = focus_spell.base[i]; } else { - value = MakeRandomInt(focus_spell.base[i], focus_spell.base2[i]); + value = zone->random.Int(focus_spell.base[i], focus_spell.base2[i]); } } break; @@ -7183,7 +7183,7 @@ int32 Bot::CalcBotFocusEffect(BotfocusType bottype, uint16 focus_id, uint16 spel value = focus_spell.base[i]; } else { - value = MakeRandomInt(focus_spell.base[i], focus_spell.base2[i]); + value = zone->random.Int(focus_spell.base[i], focus_spell.base2[i]); } } break; @@ -7273,7 +7273,7 @@ int32 Bot::CalcBotFocusEffect(BotfocusType bottype, uint16 focus_id, uint16 spel { if(bottype == BotfocusTriggerOnCast) - if(MakeRandomInt(0, 100) <= focus_spell.base[i]) + if(zone->random.Int(0, 100) <= focus_spell.base[i]) value = focus_spell.base2[i]; else @@ -7293,7 +7293,7 @@ int32 Bot::CalcBotFocusEffect(BotfocusType bottype, uint16 focus_id, uint16 spel { if(bottype == BotfocusBlockNextSpell) { - if(MakeRandomInt(1, 100) <= focus_spell.base[i]) + if(zone->random.Int(1, 100) <= focus_spell.base[i]) value = 1; } break; @@ -7313,7 +7313,7 @@ int32 Bot::CalcBotFocusEffect(BotfocusType bottype, uint16 focus_id, uint16 spel float ProcChance = GetSympatheticProcChances(spell_id, focus_spell.base[i]); - if(MakeRandomFloat(0, 1) <= ProcChance) + if(zone->random.Real(0, 1) <= ProcChance) value = focus_id; else @@ -7503,7 +7503,7 @@ bool Bot::AvoidDamage(Mob* other, int32 &damage, bool CanRiposte) //Live AA - HightenedAwareness int BlockBehindChance = aabonuses.BlockBehind + spellbonuses.BlockBehind + itembonuses.BlockBehind; - if (BlockBehindChance && (BlockBehindChance > MakeRandomInt(1, 100))){ + if (BlockBehindChance && (BlockBehindChance > zone->random.Int(1, 100))){ bBlockFromRear = true; if (spellbonuses.BlockBehind || itembonuses.BlockBehind) @@ -7595,7 +7595,7 @@ bool Bot::AvoidDamage(Mob* other, int32 &damage, bool CanRiposte) if(damage > 0) { - roll = MakeRandomFloat(0,100); + roll = zone->random.Real(0,100); if(roll <= RollTable[0]){ damage = -3; } @@ -7662,7 +7662,7 @@ bool Bot::TryFinishingBlow(Mob *defender, SkillUseTypes skillinuse) uint32 damage = aabonuses.FinishingBlow[1]; uint16 levelreq = aabonuses.FinishingBlowLvl[0]; - if(defender->GetLevel() <= levelreq && (chance >= MakeRandomInt(0, 1000))){ + if(defender->GetLevel() <= levelreq && (chance >= zone->random.Int(0, 1000))){ mlog(COMBAT__ATTACKS, "Landed a finishing blow: levelreq at %d, other level %d", levelreq , defender->GetLevel()); entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, FINISHING_BLOW, GetName()); defender->Damage(this, damage, SPELL_UNKNOWN, skillinuse); @@ -7690,7 +7690,7 @@ void Bot::DoRiposte(Mob* defender) { defender->GetSpellBonuses().GiveDoubleRiposte[0] + defender->GetItemBonuses().GiveDoubleRiposte[0]; - if(DoubleRipChance && (DoubleRipChance >= MakeRandomInt(0, 100))) { + if(DoubleRipChance && (DoubleRipChance >= zone->random.Int(0, 100))) { mlog(COMBAT__ATTACKS, "Preforming a double riposte (%d percent chance)", DoubleRipChance); defender->Attack(this, MainPrimary, true); @@ -7700,7 +7700,7 @@ void Bot::DoRiposte(Mob* defender) { //Coded narrowly: Limit to one per client. Limit AA only. [1 = Skill Attack Chance, 2 = Skill] DoubleRipChance = defender->GetAABonuses().GiveDoubleRiposte[1]; - if(DoubleRipChance && (DoubleRipChance >= MakeRandomInt(0, 100))) { + if(DoubleRipChance && (DoubleRipChance >= zone->random.Int(0, 100))) { if (defender->GetClass() == MONK) defender->MonkSpecialAttack(this, defender->GetAABonuses().GiveDoubleRiposte[2]); else if (defender->IsBot()) @@ -7766,7 +7766,7 @@ void Bot::DoSpecialAttackDamage(Mob *who, SkillUseTypes skill, int32 max_damage, int kb_chance = 25; kb_chance += kb_chance*(100-aabonuses.SpecialAttackKBProc[0])/100; - if (MakeRandomInt(0, 99) < kb_chance) + if (zone->random.Int(0, 99) < kb_chance) SpellFinished(904, who, 10, 0, -1, spells[904].ResistDiff); //who->Stun(100); Kayen: This effect does not stun on live, it only moves the NPC. } @@ -7807,7 +7807,7 @@ void Bot::TryBackstab(Mob *other, int ReuseTime) { //Live AA - Seized Opportunity int FrontalBSChance = itembonuses.FrontalBackstabChance + spellbonuses.FrontalBackstabChance + aabonuses.FrontalBackstabChance; - if (FrontalBSChance && (FrontalBSChance > MakeRandomInt(0, 100))) + if (FrontalBSChance && (FrontalBSChance > zone->random.Int(0, 100))) bCanFrontalBS = true; } @@ -7821,7 +7821,7 @@ void Bot::TryBackstab(Mob *other, int ReuseTime) { !other->CastToNPC()->IsEngaged() && // not aggro other->GetHP()<=32000 && other->IsNPC() - && MakeRandomFloat(0, 99) < chance // chance + && zone->random.Real(0, 99) < chance // chance ) { entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, ASSASSINATES, GetName()); RogueAssassinate(other); @@ -7832,12 +7832,12 @@ void Bot::TryBackstab(Mob *other, int ReuseTime) { float DoubleAttackProbability = (GetSkill(SkillDoubleAttack) + GetLevel()) / 500.0f; // 62.4 max // Check for double attack with main hand assuming maxed DA Skill (MS) - if(MakeRandomFloat(0, 1) < DoubleAttackProbability) // Max 62.4 % chance of DA + if(zone->random.Real(0, 1) < DoubleAttackProbability) // Max 62.4 % chance of DA { if(other->GetHP() > 0) RogueBackstab(other,false,ReuseTime); - if (tripleChance && other->GetHP() > 0 && tripleChance > MakeRandomInt(0, 100)) + if (tripleChance && other->GetHP() > 0 && tripleChance > zone->random.Int(0, 100)) RogueBackstab(other,false,ReuseTime); } } @@ -7851,11 +7851,11 @@ void Bot::TryBackstab(Mob *other, int ReuseTime) { if (level > 54) { float DoubleAttackProbability = (GetSkill(SkillDoubleAttack) + GetLevel()) / 500.0f; // 62.4 max // Check for double attack with main hand assuming maxed DA Skill (MS) - if(MakeRandomFloat(0, 1) < DoubleAttackProbability) // Max 62.4 % chance of DA + if(zone->random.Real(0, 1) < DoubleAttackProbability) // Max 62.4 % chance of DA if(other->GetHP() > 0) RogueBackstab(other,true, ReuseTime); - if (tripleChance && other->GetHP() > 0 && tripleChance > MakeRandomInt(0, 100)) + if (tripleChance && other->GetHP() > 0 && tripleChance > zone->random.Int(0, 100)) RogueBackstab(other,false,ReuseTime); } } @@ -7929,7 +7929,7 @@ void Bot::RogueBackstab(Mob* other, bool min_damage, int ReuseTime) if(RuleB(Combat, UseIntervalAC)) ndamage = max_hit; else - ndamage = MakeRandomInt(min_hit, max_hit); + ndamage = zone->random.Int(min_hit, max_hit); } } @@ -8036,7 +8036,7 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { canBash = true; } - if(!canBash || MakeRandomInt(0, 100) > 25) { //tested on live, warrior mobs both kick and bash, kick about 75% of the time, casting doesn't seem to make a difference. + if(!canBash || zone->random.Int(0, 100) > 25) { //tested on live, warrior mobs both kick and bash, kick about 75% of the time, casting doesn't seem to make a difference. skill_to_use = SkillKick; } else { @@ -8117,7 +8117,7 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { if(RuleB(Combat, UseIntervalAC)) dmg = GetBashDamage(); else - dmg = MakeRandomInt(1, GetBashDamage()); + dmg = zone->random.Int(1, GetBashDamage()); } } @@ -8164,7 +8164,7 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { //Live parses show around 55% Triple 35% Double 10% Single, you will always get first hit. while(AtkRounds > 0) { - if (GetTarget() && (AtkRounds == 1 || MakeRandomInt(0,100) < 75)){ + if (GetTarget() && (AtkRounds == 1 || zone->random.Int(0,100) < 75)){ DoSpecialAttackDamage(GetTarget(), SkillFrenzy, max_dmg, min_dmg, max_dmg , reuse, true); } AtkRounds--; @@ -8192,7 +8192,7 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { if(RuleB(Combat, UseIntervalAC)) dmg = GetKickDamage(); else - dmg = MakeRandomInt(1, GetKickDamage()); + dmg = zone->random.Int(1, GetKickDamage()); } } @@ -8215,18 +8215,18 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { //Live AA - Technique of Master Wu uint32 bDoubleSpecialAttack = itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack; - if( bDoubleSpecialAttack && (bDoubleSpecialAttack >= 100 || bDoubleSpecialAttack > MakeRandomInt(0,100))) { + if( bDoubleSpecialAttack && (bDoubleSpecialAttack >= 100 || bDoubleSpecialAttack > zone->random.Int(0,100))) { int MonkSPA [5] = { SkillFlyingKick, SkillDragonPunch, SkillEagleStrike, SkillTigerClaw, SkillRoundKick }; - MonkSpecialAttack(target, MonkSPA[MakeRandomInt(0,4)]); + MonkSpecialAttack(target, MonkSPA[zone->random.Int(0,4)]); int TripleChance = 25; if (bDoubleSpecialAttack > 100) TripleChance += TripleChance*(100-bDoubleSpecialAttack)/100; - if(TripleChance > MakeRandomInt(0,100)) { - MonkSpecialAttack(target, MonkSPA[MakeRandomInt(0,4)]); + if(TripleChance > zone->random.Int(0,100)) { + MonkSpecialAttack(target, MonkSPA[zone->random.Int(0,4)]); } } @@ -8259,7 +8259,7 @@ bool Bot::TryHeadShot(Mob* defender, SkillUseTypes skillInUse) { // WildcardX: These chance formula's below are arbitrary. If someone has a better formula that is more // consistent with live, feel free to update these. float AttackerChance = 0.20f + ((float)(rangerLevel - 51) * 0.005f); - float DefenderChance = (float)MakeRandomFloat(0.00f, 1.00f); + float DefenderChance = (float)zone->random.Real(0.00f, 1.00f); if(AttackerChance > DefenderChance) { mlog(COMBAT__ATTACKS, "Landed a headshot: Attacker chance was %f and Defender chance was %f.", AttackerChance, DefenderChance); // WildcardX: At the time I wrote this, there wasnt a string id for something like HEADSHOT_BLOW @@ -8734,14 +8734,14 @@ int32 Bot::GetActSpellDamage(uint16 spell_id, int32 value, Mob* target) { if (spell_id == SPELL_IMP_HARM_TOUCH && (GetAA(aaSpellCastingFury) > 0) && (GetAA(aaUnholyTouch) > 0)) chance = 100; - if (MakeRandomInt(1,100) <= chance){ + if (zone->random.Int(1,100) <= chance){ Critical = true; ratio += itembonuses.SpellCritDmgIncrease + spellbonuses.SpellCritDmgIncrease + aabonuses.SpellCritDmgIncrease; ratio += itembonuses.SpellCritDmgIncNoStack + spellbonuses.SpellCritDmgIncNoStack + aabonuses.SpellCritDmgIncNoStack; } - else if (GetClass() == WIZARD && (GetLevel() >= RuleI(Spells, WizCritLevel)) && (MakeRandomInt(1,100) <= RuleI(Spells, WizCritChance))) { - ratio = MakeRandomInt(1,100); //Wizard innate critical chance is calculated seperately from spell effect and is not a set ratio. + else if (GetClass() == WIZARD && (GetLevel() >= RuleI(Spells, WizCritLevel)) && (zone->random.Int(1,100) <= RuleI(Spells, WizCritChance))) { + ratio = zone->random.Int(1,100); //Wizard innate critical chance is calculated seperately from spell effect and is not a set ratio. Critical = true; } @@ -8820,7 +8820,7 @@ int32 Bot::GetActSpellHealing(uint16 spell_id, int32 value, Mob* target) { if (spellbonuses.CriticalHealDecay) chance += GetDecayEffectValue(spell_id, SE_CriticalHealDecay); - if(chance && (MakeRandomInt(0,99) < chance)) { + if(chance && (zone->random.Int(0,99) < chance)) { Critical = true; modifier = 2; //At present time no critical heal amount modifier SPA exists. } @@ -8851,7 +8851,7 @@ int32 Bot::GetActSpellHealing(uint16 spell_id, int32 value, Mob* target) { if (spellbonuses.CriticalRegenDecay) chance += GetDecayEffectValue(spell_id, SE_CriticalRegenDecay); - if(chance && (MakeRandomInt(0,99) < chance)) + if(chance && (zone->random.Int(0,99) < chance)) return (value * 2); } @@ -8950,7 +8950,7 @@ int32 Bot::GetActSpellCost(uint16 spell_id, int32 cost) { // Formula = Unknown exact, based off a random percent chance up to mana cost(after focuses) of the cast spell if(this->itembonuses.Clairvoyance && spells[spell_id].classes[(GetClass()%16) - 1] >= GetLevel() - 5) { - int32 mana_back = this->itembonuses.Clairvoyance * MakeRandomInt(1, 100) / 100; + int32 mana_back = this->itembonuses.Clairvoyance * zone->random.Int(1, 100) / 100; // Doesnt generate mana, so best case is a free spell if(mana_back > cost) mana_back = cost; @@ -8963,7 +8963,7 @@ int32 Bot::GetActSpellCost(uint16 spell_id, int32 cost) { // WildcardX float PercentManaReduction = 0; float SpecializeSkill = GetSpecializeSkillValue(spell_id); - int SuccessChance = MakeRandomInt(0, 100); + int SuccessChance = zone->random.Int(0, 100); float bonus = 1.0; switch(GetAA(aaSpellCastingMastery)) @@ -9015,7 +9015,7 @@ int32 Bot::GetActSpellCost(uint16 spell_id, int32 cost) { if(focus_redux > 0) { - PercentManaReduction += MakeRandomFloat(1, (double)focus_redux); + PercentManaReduction += zone->random.Real(1, (double)focus_redux); } cost -= (cost * (PercentManaReduction / 100)); @@ -11864,7 +11864,7 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { } if(!strcasecmp(sep->arg[1], "inventory") && !strcasecmp(sep->arg[2], "remove")) { - if((c->GetTarget() == nullptr) || (sep->arg[3] == '\0') || !c->GetTarget()->IsBot()) + if((c->GetTarget() == nullptr) || (sep->arg[3][0] == '\0') || !c->GetTarget()->IsBot()) { c->Message(15, "Usage: #bot inventory remove [slotid] (You must have a bot targetted) "); return; @@ -15580,7 +15580,7 @@ bool EntityList::Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, fl return false; if (iChance < 100) { - uint8 tmp = MakeRandomInt(1, 100); + uint8 tmp = zone->random.Int(1, 100); if (tmp > iChance) return false; } diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index 4917ed953..a5be7341e 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -13,7 +13,7 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint16 iSpellTypes) { return false; if (iChance < 100) { - if (MakeRandomInt(0, 100) > iChance){ + if (zone->random.Int(0, 100) > iChance){ return false; } } @@ -485,7 +485,7 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint16 iSpellTypes) { if(botClass == PALADIN) stunChance = 50; - if(!tar->GetSpecialAbility(UNSTUNABLE) && !tar->IsStunned() && (MakeRandomInt(1, 100) <= stunChance)) { + if(!tar->GetSpecialAbility(UNSTUNABLE) && !tar->IsStunned() && (zone->random.Int(1, 100) <= stunChance)) { botSpell = GetBestBotSpellForStunByTargetType(this, ST_Target); } } @@ -1843,7 +1843,7 @@ std::string Bot::GetBotMagicianPetType(Bot* botCaster) { result = std::string("SumEarth"); else if(botCaster->GetLevel() < 30) { // Under level 30 - int counter = MakeRandomInt(0, 3); + int counter = zone->random.Int(0, 3); switch(counter) { case 0: @@ -1865,7 +1865,7 @@ std::string Bot::GetBotMagicianPetType(Bot* botCaster) { } else { // Over level 30 - int counter = MakeRandomInt(0, 4); + int counter = zone->random.Int(0, 4); switch(counter) { case 0: diff --git a/zone/client.cpp b/zone/client.cpp index 826e22b00..799c9d68a 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -17,13 +17,9 @@ */ #include "../common/debug.h" #include -#include -#include #include #include #include -#include -#include // for windows compile #ifdef _WINDOWS @@ -39,27 +35,17 @@ extern volatile bool RunLoops; #include "../common/features.h" -#include "../common/misc.h" #include "../common/spdat.h" -#include "../common/packet_dump.h" -#include "../common/packet_functions.h" -#include "../common/serverinfo.h" -#include "../common/zone_numbers.h" -#include "../common/moremath.h" #include "../common/guilds.h" -#include "../common/breakdowns.h" #include "../common/rulesys.h" #include "../common/string_util.h" #include "../common/data_verification.h" #include "net.h" -#include "masterentity.h" #include "worldserver.h" #include "zonedb.h" #include "petitions.h" -#include "forage.h" #include "command.h" #include "string_ids.h" -#include "npc_ai.h" #include "client_logs.h" #include "guild_mgr.h" #include "quest_parser_collection.h" @@ -1879,6 +1865,9 @@ void Client::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) if (strlen(item->IDFile) > 2) ns->spawn.equipment[MaterialPrimary] = atoi(&item->IDFile[2]); } + else if (inst->GetOrnamentationIcon() && inst->GetOrnamentationIDFile()) { + ns->spawn.equipment[MaterialPrimary] = inst->GetOrnamentationIDFile(); + } else { item = inst->GetItem(); if (strlen(item->IDFile) > 2) @@ -1891,6 +1880,9 @@ void Client::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) if (strlen(item->IDFile) > 2) ns->spawn.equipment[MaterialSecondary] = atoi(&item->IDFile[2]); } + else if (inst->GetOrnamentationIcon() && inst->GetOrnamentationIDFile()) { + ns->spawn.equipment[MaterialSecondary] = inst->GetOrnamentationIDFile(); + } else { item = inst->GetItem(); if (strlen(item->IDFile) > 2) @@ -2326,7 +2318,7 @@ bool Client::CheckIncreaseSkill(SkillUseTypes skillid, Mob *against_who, int cha if(Chance < 1) Chance = 1; // Make it always possible - if(MakeRandomFloat(0, 99) < Chance) + if(zone->random.Real(0, 99) < Chance) { SetSkill(skillid, GetRawSkill(skillid) + 1); _log(SKILLS__GAIN, "Skill %d at value %d successfully gain with %.4f%%chance (mod %d)", skillid, skillval, Chance, chancemodi); @@ -2354,7 +2346,7 @@ void Client::CheckLanguageSkillIncrease(uint8 langid, uint8 TeacherSkill) { int32 Chance = 5 + ((TeacherSkill - LangSkill)/10); // greater chance to learn if teacher's skill is much higher than yours Chance = (Chance * RuleI(Character, SkillUpModifier)/100); - if(MakeRandomFloat(0,100) < Chance) { // if they make the roll + if(zone->random.Real(0,100) < Chance) { // if they make the roll IncreaseLanguageSkill(langid); // increase the language skill by 1 _log(SKILLS__GAIN, "Language %d at value %d successfully gain with %.4f%%chance", langid, LangSkill, Chance); } @@ -2756,6 +2748,9 @@ void Client::SetMaterial(int16 in_slot, uint32 item_id) { item = inst->GetOrnamentationAug(ornamentationAugtype)->GetItem(); m_pp.item_material[MaterialPrimary] = atoi(item->IDFile + 2); } + else if (inst && inst->GetOrnamentationIcon() && inst->GetOrnamentationIDFile()) { + m_pp.item_material[MaterialPrimary] = inst->GetOrnamentationIDFile(); + } else { m_pp.item_material[MaterialPrimary] = atoi(item->IDFile + 2); } @@ -2766,6 +2761,9 @@ void Client::SetMaterial(int16 in_slot, uint32 item_id) { item = inst->GetOrnamentationAug(ornamentationAugtype)->GetItem(); m_pp.item_material[MaterialSecondary] = atoi(item->IDFile + 2); } + else if (inst && inst->GetOrnamentationIcon() && inst->GetOrnamentationIDFile()) { + m_pp.item_material[MaterialSecondary] = inst->GetOrnamentationIDFile(); + } else { m_pp.item_material[MaterialSecondary] = atoi(item->IDFile + 2); } @@ -4896,7 +4894,7 @@ int Client::LDoNChest_SkillCheck(NPC *target, int skill) chance = 100.0f - base_difficulty; } - float d100 = (float)MakeRandomFloat(0, 100); + float d100 = (float)zone->random.Real(0, 100); if(d100 <= chance) return 1; @@ -5815,6 +5813,10 @@ void Client::ProcessInspectRequest(Client* requestee, Client* requester) { strcpy(insr->itemnames[L], item->Name); insr->itemicons[L] = aug_weap->Icon; } + else if (inst->GetOrnamentationIcon() && inst->GetOrnamentationIDFile()) { + strcpy(insr->itemnames[L], item->Name); + insr->itemicons[L] = inst->GetOrnamentationIcon(); + } else { strcpy(insr->itemnames[L], item->Name); insr->itemicons[L] = item->Icon; @@ -7591,9 +7593,9 @@ void Client::GarbleMessage(char *message, uint8 variance) const char alpha_list[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; // only change alpha characters for now for (size_t i = 0; i < strlen(message); i++) { - uint8 chance = (uint8)MakeRandomInt(0, 115); // variation just over worst possible scrambling + uint8 chance = (uint8)zone->random.Int(0, 115); // variation just over worst possible scrambling if (isalpha(message[i]) && (chance <= variance)) { - uint8 rand_char = (uint8)MakeRandomInt(0,51); // choose a random character from the alpha list + uint8 rand_char = (uint8)zone->random.Int(0,51); // choose a random character from the alpha list message[i] = alpha_list[rand_char]; } } @@ -7711,7 +7713,7 @@ void Client::SetFactionLevel(uint32 char_id, uint32 npc_id, uint8 char_class, ui // If our result is truncated, then double a mob's value every once and a while to equal what they would have got else { - if (MakeRandomInt(0, 100) < faction_mod) + if (zone->random.Int(0, 100) < faction_mod) npc_value[i] *= 2; } } @@ -7804,11 +7806,11 @@ void Client::MerchantRejectMessage(Mob *merchant, int primaryfaction) } // If no primary faction or biggest influence is your faction hit if (primaryfaction <= 0 || lowestvalue == tmpFactionValue) { - merchant->Say_StringID(MakeRandomInt(WONT_SELL_DEEDS1, WONT_SELL_DEEDS6)); + merchant->Say_StringID(zone->random.Int(WONT_SELL_DEEDS1, WONT_SELL_DEEDS6)); } else if (lowestvalue == fmod.race_mod) { // race biggest // Non-standard race (ex. illusioned to wolf) if (GetRace() > PLAYER_RACE_COUNT) { - messageid = MakeRandomInt(1, 3); // these aren't sequential StringIDs :( + messageid = zone->random.Int(1, 3); // these aren't sequential StringIDs :( switch (messageid) { case 1: messageid = WONT_SELL_NONSTDRACE1; @@ -7825,7 +7827,7 @@ void Client::MerchantRejectMessage(Mob *merchant, int primaryfaction) } merchant->Say_StringID(messageid); } else { // normal player races - messageid = MakeRandomInt(1, 4); + messageid = zone->random.Int(1, 4); switch (messageid) { case 1: messageid = WONT_SELL_RACE1; @@ -7846,7 +7848,7 @@ void Client::MerchantRejectMessage(Mob *merchant, int primaryfaction) merchant->Say_StringID(messageid, itoa(GetRace())); } } else if (lowestvalue == fmod.class_mod) { - merchant->Say_StringID(MakeRandomInt(WONT_SELL_CLASS1, WONT_SELL_CLASS5), itoa(GetClass())); + merchant->Say_StringID(zone->random.Int(WONT_SELL_CLASS1, WONT_SELL_CLASS5), itoa(GetClass())); } return; } @@ -7949,7 +7951,7 @@ void Client::TryItemTick(int slot) if(zone->tick_items.count(iid) > 0) { - if( GetLevel() >= zone->tick_items[iid].level && MakeRandomInt(0, 100) >= (100 - zone->tick_items[iid].chance) && (zone->tick_items[iid].bagslot || slot <= EmuConstants::EQUIPMENT_END) ) + if( GetLevel() >= zone->tick_items[iid].level && zone->random.Int(0, 100) >= (100 - zone->tick_items[iid].chance) && (zone->tick_items[iid].bagslot || slot <= EmuConstants::EQUIPMENT_END) ) { ItemInst* e_inst = (ItemInst*)inst; parse->EventItem(EVENT_ITEM_TICK, this, e_inst, nullptr, "", slot); @@ -7968,7 +7970,7 @@ void Client::TryItemTick(int slot) if(zone->tick_items.count(iid) > 0) { - if( GetLevel() >= zone->tick_items[iid].level && MakeRandomInt(0, 100) >= (100 - zone->tick_items[iid].chance) ) + if( GetLevel() >= zone->tick_items[iid].level && zone->random.Int(0, 100) >= (100 - zone->tick_items[iid].chance) ) { ItemInst* e_inst = (ItemInst*)a_inst; parse->EventItem(EVENT_ITEM_TICK, this, e_inst, nullptr, "", slot); diff --git a/zone/client.cpp.orig b/zone/client.cpp.orig new file mode 100644 index 000000000..f23c80b8f --- /dev/null +++ b/zone/client.cpp.orig @@ -0,0 +1,8389 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2003 EQEMu Development Team (http://eqemulator.org) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY except by those people which sell it, which + are required to give you total support for your newly bought product; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +#include "../common/debug.h" +#include +#include +#include +#include + +// for windows compile +#ifdef _WINDOWS + #define abs64 _abs64 +#else + #include + #include + #include + #include "../common/unix.h" + #define abs64 abs +#endif + +extern volatile bool RunLoops; + +#include "../common/features.h" +#include "../common/spdat.h" +#include "../common/guilds.h" +#include "../common/rulesys.h" +#include "../common/string_util.h" +#include "../common/data_verification.h" +#include "net.h" +#include "worldserver.h" +#include "zonedb.h" +#include "petitions.h" +#include "command.h" +#include "string_ids.h" +#include "client_logs.h" +#include "guild_mgr.h" +#include "quest_parser_collection.h" +#include "queryserv.h" + +extern QueryServ* QServ; +extern EntityList entity_list; +extern Zone* zone; +extern volatile bool ZoneLoaded; +extern WorldServer worldserver; +extern uint32 numclients; +extern PetitionList petition_list; +bool commandlogged; +char entirecommand[255]; + +Client::Client(EQStreamInterface* ieqs) +: Mob("No name", // name + "", // lastname + 0, // cur_hp + 0, // max_hp + 0, // gender + 0, // race + 0, // class + BT_Humanoid, // bodytype + 0, // deity + 0, // level + 0, // npctypeid + 0, // size + 0.7, // runspeed + xyz_heading::Origin(), + 0, // light + 0xFF, // texture + 0xFF, // helmtexture + 0, // ac + 0, // atk + 0, // str + 0, // sta + 0, // dex + 0, // agi + 0, // int + 0, // wis + 0, // cha + 0, // Luclin Hair Colour + 0, // Luclin Beard Color + 0, // Luclin Eye1 + 0, // Luclin Eye2 + 0, // Luclin Hair Style + 0, // Luclin Face + 0, // Luclin Beard + 0, // Drakkin Heritage + 0, // Drakkin Tattoo + 0, // Drakkin Details + 0, // Armor Tint + 0xff, // AA Title + 0, // see_invis + 0, // see_invis_undead + 0, + 0, + 0, + 0, + 0, // qglobal + 0, // maxlevel + 0 // scalerate + + ), + //these must be listed in the order they appear in client.h + position_timer(250), + hpupdate_timer(1800), + camp_timer(29000), + process_timer(100), + stamina_timer(40000), + zoneinpacket_timer(3000), + linkdead_timer(RuleI(Zone,ClientLinkdeadMS)), + dead_timer(2000), + global_channel_timer(1000), + shield_timer(500), + fishing_timer(8000), + endupkeep_timer(1000), + forget_timer(0), + autosave_timer(RuleI(Character, AutosaveIntervalS)*1000), +#ifdef REVERSE_AGGRO + scanarea_timer(AIClientScanarea_delay), +#endif + tribute_timer(Tribute_duration), + proximity_timer(ClientProximity_interval), + TaskPeriodic_Timer(RuleI(TaskSystem, PeriodicCheckTimer) * 1000), + charm_update_timer(6000), + rest_timer(1), + charm_class_attacks_timer(3000), + charm_cast_timer(3500), + qglobal_purge_timer(30000), + TrackingTimer(2000), + RespawnFromHoverTimer(0), + merc_timer(RuleI(Mercs, UpkeepIntervalMS)), + ItemTickTimer(10000), + ItemQuestTimer(500), + m_Proximity(FLT_MAX, FLT_MAX, FLT_MAX), //arbitrary large number + m_ZoneSummonLocation(-2.0f,-2.0f,-2.0f), + m_AutoAttackPosition(0.0f, 0.0f, 0.0f, 0.0f), + m_AutoAttackTargetLocation(0.0f, 0.0f, 0.0f) +{ + for(int cf=0; cf < _FilterCount; cf++) + ClientFilters[cf] = FilterShow; + character_id = 0; + conn_state = NoPacketsReceived; + client_data_loaded = false; + feigned = false; + berserk = false; + dead = false; + eqs = ieqs; + ip = eqs->GetRemoteIP(); + port = ntohs(eqs->GetRemotePort()); + client_state = CLIENT_CONNECTING; + Trader=false; + Buyer = false; + CustomerID = 0; + TrackingID = 0; + WID = 0; + account_id = 0; + admin = 0; + lsaccountid = 0; + shield_target = nullptr; + SQL_log = nullptr; + guild_id = GUILD_NONE; + guildrank = 0; + GuildBanker = false; + memset(lskey, 0, sizeof(lskey)); + strcpy(account_name, ""); + tellsoff = false; + last_reported_mana = 0; + last_reported_endur = 0; + gmhideme = false; + AFK = false; + LFG = false; + LFGFromLevel = 0; + LFGToLevel = 0; + LFGMatchFilter = false; + LFGComments[0] = '\0'; + LFP = false; + gmspeed = 0; + playeraction = 0; + SetTarget(0); + auto_attack = false; + auto_fire = false; + linkdead_timer.Disable(); + zonesummon_id = 0; + zonesummon_ignorerestrictions = 0; + zoning = false; + zone_mode = ZoneUnsolicited; + casting_spell_id = 0; + npcflag = false; + npclevel = 0; + pQueuedSaveWorkID = 0; + position_timer_counter = 0; + fishing_timer.Disable(); + shield_timer.Disable(); + dead_timer.Disable(); + camp_timer.Disable(); + autosave_timer.Disable(); + GetMercTimer()->Disable(); + instalog = false; + pLastUpdate = 0; + pLastUpdateWZ = 0; + m_pp.autosplit = false; + // initialise haste variable + m_tradeskill_object = nullptr; + delaytimer = false; + PendingRezzXP = -1; + PendingRezzDBID = 0; + PendingRezzSpellID = 0; + numclients++; + // emuerror; + UpdateWindowTitle(); + horseId = 0; + tgb = false; + tribute_master_id = 0xFFFFFFFF; + tribute_timer.Disable(); + taskstate = nullptr; + TotalSecondsPlayed = 0; + keyring.clear(); + bind_sight_target = nullptr; + mercid = 0; + mercSlot = 0; + InitializeMercInfo(); + SetMerc(0); + + logging_enabled = CLIENT_DEFAULT_LOGGING_ENABLED; + + //for good measure: + memset(&m_pp, 0, sizeof(m_pp)); + memset(&m_epp, 0, sizeof(m_epp)); + PendingTranslocate = false; + PendingSacrifice = false; + BoatID = 0; + + KarmaUpdateTimer = new Timer(RuleI(Chat, KarmaUpdateIntervalMS)); + GlobalChatLimiterTimer = new Timer(RuleI(Chat, IntervalDurationMS)); + AttemptedMessages = 0; + TotalKarma = 0; + ClientVersion = EQClientUnknown; + ClientVersionBit = 0; + AggroCount = 0; + RestRegenHP = 0; + RestRegenMana = 0; + RestRegenEndurance = 0; + XPRate = 100; + cur_end = 0; + + m_TimeSinceLastPositionCheck = 0; + m_DistanceSinceLastPositionCheck = 0.0f; + m_ShadowStepExemption = 0; + m_KnockBackExemption = 0; + m_PortExemption = 0; + m_SenseExemption = 0; + m_AssistExemption = 0; + m_CheatDetectMoved = false; + CanUseReport = true; + aa_los_them_mob = nullptr; + los_status = false; + los_status_facing = false; + qGlobals = nullptr; + HideCorpseMode = HideCorpseNone; + PendingGuildInvitation = false; + + cur_end = 0; + + InitializeBuffSlots(); + + adventure_request_timer = nullptr; + adventure_create_timer = nullptr; + adventure_leave_timer = nullptr; + adventure_door_timer = nullptr; + adv_requested_data = nullptr; + adventure_stats_timer = nullptr; + adventure_leaderboard_timer = nullptr; + adv_data = nullptr; + adv_requested_theme = 0; + adv_requested_id = 0; + adv_requested_member_count = 0; + + for(int i = 0; i < XTARGET_HARDCAP; ++i) + { + XTargets[i].Type = Auto; + XTargets[i].ID = 0; + XTargets[i].Name[0] = 0; + } + MaxXTargets = 5; + XTargetAutoAddHaters = true; + LoadAccountFlags(); + + initial_respawn_selection = 0; + alternate_currency_loaded = false; + + EngagedRaidTarget = false; + SavedRaidRestTimer = 0; + + interrogateinv_flag = false; +} + +Client::~Client() { +#ifdef BOTS + Bot::ProcessBotOwnerRefDelete(this); +#endif + if(IsInAGuild()) + guild_mgr.SendGuildMemberUpdateToWorld(GetName(), GuildID(), 0, time(nullptr)); + + Mob* horse = entity_list.GetMob(this->CastToClient()->GetHorseId()); + if (horse) + horse->Depop(); + + Mob* merc = entity_list.GetMob(this->GetMercID()); + if (merc) + merc->Depop(); + + if(Trader) + database.DeleteTraderItem(this->CharacterID()); + + if(Buyer) + ToggleBuyerMode(false); + + if(conn_state != ClientConnectFinished) { + LogFile->write(EQEMuLog::Debug, "Client '%s' was destroyed before reaching the connected state:", GetName()); + ReportConnectingState(); + } + + if(m_tradeskill_object != nullptr) { + m_tradeskill_object->Close(); + m_tradeskill_object = nullptr; + } + +#ifdef CLIENT_LOGS + client_logs.unsubscribeAll(this); +#endif + + ChangeSQLLog(nullptr); + if(IsDueling() && GetDuelTarget() != 0) { + Entity* entity = entity_list.GetID(GetDuelTarget()); + if(entity != nullptr && entity->IsClient()) { + entity->CastToClient()->SetDueling(false); + entity->CastToClient()->SetDuelTarget(0); + entity_list.DuelMessage(entity->CastToClient(),this,true); + } + } + + if (shield_target) { + for (int y = 0; y < 2; y++) { + if (shield_target->shielder[y].shielder_id == GetID()) { + shield_target->shielder[y].shielder_id = 0; + shield_target->shielder[y].shielder_bonus = 0; + } + } + shield_target = nullptr; + } + + if(GetTarget()) + GetTarget()->IsTargeted(-1); + + //if we are in a group and we are not zoning, force leave the group + if(isgrouped && !zoning && ZoneLoaded) + LeaveGroup(); + + UpdateWho(2); + + if(IsHoveringForRespawn()) + { + m_pp.zone_id = m_pp.binds[0].zoneId; + m_pp.zoneInstance = m_pp.binds[0].instance_id; + m_Position.m_X = m_pp.binds[0].x; + m_Position.m_Y = m_pp.binds[0].y; + m_Position.m_Z = m_pp.binds[0].z; + } + + // we save right now, because the client might be zoning and the world + // will need this data right away + Save(2); // This fails when database destructor is called first on shutdown + + safe_delete(taskstate); + safe_delete(KarmaUpdateTimer); + safe_delete(GlobalChatLimiterTimer); + safe_delete(qGlobals); + safe_delete(adventure_request_timer); + safe_delete(adventure_create_timer); + safe_delete(adventure_leave_timer); + safe_delete(adventure_door_timer); + safe_delete(adventure_stats_timer); + safe_delete(adventure_leaderboard_timer); + safe_delete_array(adv_requested_data); + safe_delete_array(adv_data); + + ClearRespawnOptions(); + + numclients--; + UpdateWindowTitle(); + if(zone) + zone->RemoveAuth(GetName()); + + //let the stream factory know were done with this stream + eqs->Close(); + eqs->ReleaseFromUse(); + + UninitializeBuffSlots(); +} + +void Client::SendLogoutPackets() { + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_CancelTrade, sizeof(CancelTrade_Struct)); + CancelTrade_Struct* ct = (CancelTrade_Struct*) outapp->pBuffer; + ct->fromid = GetID(); + ct->action = groupActUpdate; + FastQueuePacket(&outapp); + + outapp = new EQApplicationPacket(OP_PreLogoutReply); + FastQueuePacket(&outapp); + +} + +void Client::ReportConnectingState() { + switch(conn_state) { + case NoPacketsReceived: //havent gotten anything + LogFile->write(EQEMuLog::Debug, "Client has not sent us an initial zone entry packet."); + break; + case ReceivedZoneEntry: //got the first packet, loading up PP + LogFile->write(EQEMuLog::Debug, "Client sent initial zone packet, but we never got their player info from the database."); + break; + case PlayerProfileLoaded: //our DB work is done, sending it + LogFile->write(EQEMuLog::Debug, "We were sending the player profile, tributes, tasks, spawns, time and weather, but never finished."); + break; + case ZoneInfoSent: //includes PP, tributes, tasks, spawns, time and weather + LogFile->write(EQEMuLog::Debug, "We successfully sent player info and spawns, waiting for client to request new zone."); + break; + case NewZoneRequested: //received and sent new zone request + LogFile->write(EQEMuLog::Debug, "We received client's new zone request, waiting for client spawn request."); + break; + case ClientSpawnRequested: //client sent ReqClientSpawn + LogFile->write(EQEMuLog::Debug, "We received the client spawn request, and were sending objects, doors, zone points and some other stuff, but never finished."); + break; + case ZoneContentsSent: //objects, doors, zone points + LogFile->write(EQEMuLog::Debug, "The rest of the zone contents were successfully sent, waiting for client ready notification."); + break; + case ClientReadyReceived: //client told us its ready, send them a bunch of crap like guild MOTD, etc + LogFile->write(EQEMuLog::Debug, "We received client ready notification, but never finished Client::CompleteConnect"); + break; + case ClientConnectFinished: //client finally moved to finished state, were done here + LogFile->write(EQEMuLog::Debug, "Client is successfully connected."); + break; + }; +} + +bool Client::SaveAA(){ + int first_entry = 0; + std::string rquery; + /* Save Player AA */ + int spentpoints = 0; + for (int a = 0; a < MAX_PP_AA_ARRAY; a++) { + uint32 points = aa[a]->value; + if (points > HIGHEST_AA_VALUE) { + aa[a]->value = HIGHEST_AA_VALUE; + points = HIGHEST_AA_VALUE; + } + if (points > 0) { + SendAA_Struct* curAA = zone->FindAA(aa[a]->AA - aa[a]->value + 1); + if (curAA) { + for (int rank = 0; rank::iterator RequiredLevel = AARequiredLevelAndCost.find(aa[a]->AA - aa[a]->value + 1 + rank); + if (RequiredLevel != AARequiredLevelAndCost.end()) { + spentpoints += RequiredLevel->second.Cost; + } + else + spentpoints += (curAA->cost + (curAA->cost_inc * rank)); + } + } + } + } + m_pp.aapoints_spent = spentpoints + m_epp.expended_aa; + for (int a = 0; a < MAX_PP_AA_ARRAY; a++) { + if (aa[a]->AA > 0 && aa[a]->value){ + if (first_entry != 1){ + rquery = StringFormat("REPLACE INTO `character_alternate_abilities` (id, slot, aa_id, aa_value)" + " VALUES (%u, %u, %u, %u)", character_id, a, aa[a]->AA, aa[a]->value); + first_entry = 1; + } + rquery = rquery + StringFormat(", (%u, %u, %u, %u)", character_id, a, aa[a]->AA, aa[a]->value); + } + } + auto results = database.QueryDatabase(rquery); + return true; +} + +bool Client::Save(uint8 iCommitNow) { + if(!ClientDataLoaded()) + return false; + + /* Wrote current basics to PP for saves */ + m_pp.x = m_Position.m_X; + m_pp.y = m_Position.m_Y; + m_pp.z = m_Position.m_Z; + m_pp.guildrank = guildrank; + m_pp.heading = m_Position.m_Heading; + + /* Mana and HP */ + if (GetHP() <= 0) { + m_pp.cur_hp = GetMaxHP(); + } + else { + m_pp.cur_hp = GetHP(); + } + + m_pp.mana = cur_mana; + m_pp.endurance = cur_end; + + /* Save Character Currency */ + database.SaveCharacterCurrency(CharacterID(), &m_pp); + + /* Save Current Bind Points */ + auto regularBindPosition = xyz_heading(m_pp.binds[0].x, m_pp.binds[0].y, m_pp.binds[0].z, 0.0f); + auto homeBindPosition = xyz_heading(m_pp.binds[4].x, m_pp.binds[4].y, m_pp.binds[4].z, 0.0f); + database.SaveCharacterBindPoint(CharacterID(), m_pp.binds[0].zoneId, m_pp.binds[0].instance_id, regularBindPosition, 0); /* Regular bind */ + database.SaveCharacterBindPoint(CharacterID(), m_pp.binds[4].zoneId, m_pp.binds[4].instance_id, homeBindPosition, 1); /* Home Bind */ + + /* Save Character Buffs */ + database.SaveBuffs(this); + + /* Total Time Played */ + TotalSecondsPlayed += (time(nullptr) - m_pp.lastlogin); + m_pp.timePlayedMin = (TotalSecondsPlayed / 60); + m_pp.RestTimer = rest_timer.GetRemainingTime() / 1000; + + /* Save Mercs */ + if (GetMercInfo().MercTimerRemaining > RuleI(Mercs, UpkeepIntervalMS)) { + GetMercInfo().MercTimerRemaining = RuleI(Mercs, UpkeepIntervalMS); + } + + if (GetMercTimer()->Enabled()) { + GetMercInfo().MercTimerRemaining = GetMercTimer()->GetRemainingTime(); + } + + if (!(GetMerc() && !dead)) { + memset(&m_mercinfo, 0, sizeof(struct MercInfo)); + } + + m_pp.lastlogin = time(nullptr); + + if (GetPet() && !GetPet()->IsFamiliar() && GetPet()->CastToNPC()->GetPetSpellID() && !dead) { + NPC *pet = GetPet()->CastToNPC(); + m_petinfo.SpellID = pet->CastToNPC()->GetPetSpellID(); + m_petinfo.HP = pet->GetHP(); + m_petinfo.Mana = pet->GetMana(); + pet->GetPetState(m_petinfo.Buffs, m_petinfo.Items, m_petinfo.Name); + m_petinfo.petpower = pet->GetPetPower(); + m_petinfo.size = pet->GetSize(); + } else { + memset(&m_petinfo, 0, sizeof(struct PetInfo)); + } + database.SavePetInfo(this); + + if(tribute_timer.Enabled()) { + m_pp.tribute_time_remaining = tribute_timer.GetRemainingTime(); + } + else { + m_pp.tribute_time_remaining = 0xFFFFFFFF; m_pp.tribute_active = 0; + } + + p_timers.Store(&database); + + database.SaveCharacterTribute(this->CharacterID(), &m_pp); + SaveTaskState(); /* Save Character Task */ + + m_pp.hunger_level = EQEmu::Clamp(m_pp.hunger_level, 0, 50000); + m_pp.thirst_level = EQEmu::Clamp(m_pp.thirst_level, 0, 50000); + database.SaveCharacterData(this->CharacterID(), this->AccountID(), &m_pp, &m_epp); /* Save Character Data */ + + return true; +} + +void Client::SaveBackup() { +} + +CLIENTPACKET::CLIENTPACKET() +{ + app = nullptr; + ack_req = false; +} + +CLIENTPACKET::~CLIENTPACKET() +{ + safe_delete(app); +} + +//this assumes we do not own pApp, and clones it. +bool Client::AddPacket(const EQApplicationPacket *pApp, bool bAckreq) { + if (!pApp) + return false; + if(!zoneinpacket_timer.Enabled()) { + //drop the packet because it will never get sent. + return(false); + } + CLIENTPACKET *c = new CLIENTPACKET; + + c->ack_req = bAckreq; + c->app = pApp->Copy(); + + clientpackets.Append(c); + return true; +} + +//this assumes that it owns the object pointed to by *pApp +bool Client::AddPacket(EQApplicationPacket** pApp, bool bAckreq) { + if (!pApp || !(*pApp)) + return false; + if(!zoneinpacket_timer.Enabled()) { + //drop the packet because it will never get sent. + return(false); + } + CLIENTPACKET *c = new CLIENTPACKET; + + c->ack_req = bAckreq; + c->app = *pApp; + *pApp = 0; + + clientpackets.Append(c); + return true; +} + +bool Client::SendAllPackets() { + LinkedListIterator iterator(clientpackets); + + CLIENTPACKET* cp = 0; + iterator.Reset(); + while(iterator.MoreElements()) { + cp = iterator.GetData(); + if(eqs) + eqs->FastQueuePacket((EQApplicationPacket **)&cp->app, cp->ack_req); + iterator.RemoveCurrent(); +#if EQDEBUG >= 6 + LogFile->write(EQEMuLog::Normal, "Transmitting a packet"); +#endif + } + return true; +} + +void Client::QueuePacket(const EQApplicationPacket* app, bool ack_req, CLIENT_CONN_STATUS required_state, eqFilterType filter) { + if(filter!=FilterNone){ + //this is incomplete... no support for FilterShowGroupOnly or FilterShowSelfOnly + if(GetFilter(filter) == FilterHide) + return; //Client has this filter on, no need to send packet + } + if(client_state != CLIENT_CONNECTED && required_state == CLIENT_CONNECTED){ + AddPacket(app, ack_req); + return; + } + + // if the program doesnt care about the status or if the status isnt what we requested + if (required_state != CLIENT_CONNECTINGALL && client_state != required_state) + { + // todo: save packets for later use + AddPacket(app, ack_req); + } + else + if(eqs) + eqs->QueuePacket(app, ack_req); +} + +void Client::FastQueuePacket(EQApplicationPacket** app, bool ack_req, CLIENT_CONN_STATUS required_state) { + // if the program doesnt care about the status or if the status isnt what we requested + if (required_state != CLIENT_CONNECTINGALL && client_state != required_state) { + // todo: save packets for later use + AddPacket(app, ack_req); + return; + } + else { + if(eqs) + eqs->FastQueuePacket((EQApplicationPacket **)app, ack_req); + else if (app && (*app)) + delete *app; + *app = 0; + } + return; +} + +void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_skill, const char* orig_message, const char* targetname) { + char message[4096]; + strn0cpy(message, orig_message, sizeof(message)); + + + #if EQDEBUG >= 11 + LogFile->write(EQEMuLog::Debug,"Client::ChannelMessageReceived() Channel:%i message:'%s'", chan_num, message); + #endif + + if (targetname == nullptr) { + targetname = (!GetTarget()) ? "" : GetTarget()->GetName(); + } + + if(RuleB(Chat, EnableAntiSpam)) + { + if(strcmp(targetname, "discard") != 0) + { + if(chan_num == 3 || chan_num == 4 || chan_num == 5 || chan_num == 7) + { + if(GlobalChatLimiterTimer) + { + if(GlobalChatLimiterTimer->Check(false)) + { + GlobalChatLimiterTimer->Start(RuleI(Chat, IntervalDurationMS)); + AttemptedMessages = 0; + } + } + + uint32 AllowedMessages = RuleI(Chat, MinimumMessagesPerInterval) + TotalKarma; + AllowedMessages = AllowedMessages > RuleI(Chat, MaximumMessagesPerInterval) ? RuleI(Chat, MaximumMessagesPerInterval) : AllowedMessages; + + if(RuleI(Chat, MinStatusToBypassAntiSpam) <= Admin()) + AllowedMessages = 10000; + + AttemptedMessages++; + if(AttemptedMessages > AllowedMessages) + { + if(AttemptedMessages > RuleI(Chat, MaxMessagesBeforeKick)) + { + Kick(); + return; + } + if(GlobalChatLimiterTimer) + { + Message(0, "You have been rate limited, you can send more messages in %i seconds.", + GlobalChatLimiterTimer->GetRemainingTime() / 1000); + return; + } + else + { + Message(0, "You have been rate limited, you can send more messages in 60 seconds."); + return; + } + } + } + } + } + + /* Logs Player Chat */ + if (RuleB(QueryServ, PlayerLogChat)) { + ServerPacket* pack = new ServerPacket(ServerOP_Speech, sizeof(Server_Speech_Struct) + strlen(message) + 1); + Server_Speech_Struct* sem = (Server_Speech_Struct*) pack->pBuffer; + + if(chan_num == 0) + sem->guilddbid = GuildID(); + else + sem->guilddbid = 0; + + strcpy(sem->message, message); + sem->minstatus = this->Admin(); + sem->type = chan_num; + if(targetname != 0) + strcpy(sem->to, targetname); + + if(GetName() != 0) + strcpy(sem->from, GetName()); + + pack->Deflate(); + if(worldserver.Connected()) + worldserver.SendPacket(pack); + safe_delete(pack); + } + + //Return true to proceed, false to return + if(!mod_client_message(message, chan_num)) { return; } + + // Garble the message based on drunkness + if (m_pp.intoxication > 0) { + GarbleMessage(message, (int)(m_pp.intoxication / 3)); + language = 0; // No need for language when drunk + } + + switch(chan_num) + { + case 0: { /* Guild Chat */ + if (!IsInAGuild()) + Message_StringID(MT_DefaultText, GUILD_NOT_MEMBER2); //You are not a member of any guild. + else if (!guild_mgr.CheckPermission(GuildID(), GuildRank(), GUILD_SPEAK)) + Message(0, "Error: You dont have permission to speak to the guild."); + else if (!worldserver.SendChannelMessage(this, targetname, chan_num, GuildID(), language, message)) + Message(0, "Error: World server disconnected"); + break; + } + case 2: { /* Group Chat */ + Raid* raid = entity_list.GetRaidByClient(this); + if(raid) { + raid->RaidGroupSay((const char*) message, this); + break; + } + + Group* group = GetGroup(); + if(group != nullptr) { + group->GroupMessage(this,language,lang_skill,(const char*) message); + } + break; + } + case 15: { /* Raid Say */ + Raid* raid = entity_list.GetRaidByClient(this); + if(raid){ + raid->RaidSay((const char*) message, this); + } + break; + } + case 3: { /* Shout */ + Mob *sender = this; + if (GetPet() && GetPet()->FindType(SE_VoiceGraft)) + sender = GetPet(); + + entity_list.ChannelMessage(sender, chan_num, language, lang_skill, message); + break; + } + case 4: { /* Auction */ + if(RuleB(Chat, ServerWideAuction)) + { + if(!global_channel_timer.Check()) + { + if(strlen(targetname) == 0) + ChannelMessageReceived(5, language, lang_skill, message, "discard"); //Fast typer or spammer?? + else + return; + } + + if(GetRevoked()) + { + Message(0, "You have been revoked. You may not talk on Auction."); + return; + } + + if(TotalKarma < RuleI(Chat, KarmaGlobalChatLimit)) + { + if(GetLevel() < RuleI(Chat, GlobalChatLevelLimit)) + { + Message(0, "You do not have permission to talk in Auction at this time."); + return; + } + } + + if (!worldserver.SendChannelMessage(this, 0, 4, 0, language, message)) + Message(0, "Error: World server disconnected"); + } + else if(!RuleB(Chat, ServerWideAuction)) { + Mob *sender = this; + + if (GetPet() && GetPet()->FindType(SE_VoiceGraft)) + sender = GetPet(); + + entity_list.ChannelMessage(sender, chan_num, language, message); + } + break; + } + case 5: { /* OOC */ + if(RuleB(Chat, ServerWideOOC)) + { + if(!global_channel_timer.Check()) + { + if(strlen(targetname) == 0) + ChannelMessageReceived(5, language, lang_skill, message, "discard"); //Fast typer or spammer?? + else + return; + } + if(worldserver.IsOOCMuted() && admin < 100) + { + Message(0,"OOC has been muted. Try again later."); + return; + } + + if(GetRevoked()) + { + Message(0, "You have been revoked. You may not talk on OOC."); + return; + } + + if(TotalKarma < RuleI(Chat, KarmaGlobalChatLimit)) + { + if(GetLevel() < RuleI(Chat, GlobalChatLevelLimit)) + { + Message(0, "You do not have permission to talk in OOC at this time."); + return; + } + } + + if (!worldserver.SendChannelMessage(this, 0, 5, 0, language, message)) + { + Message(0, "Error: World server disconnected"); + } + } + else if(!RuleB(Chat, ServerWideOOC)) + { + Mob *sender = this; + + if (GetPet() && GetPet()->FindType(SE_VoiceGraft)) + sender = GetPet(); + + entity_list.ChannelMessage(sender, chan_num, language, message); + } + break; + } + case 6: /* Broadcast */ + case 11: { /* GM Say */ + if (!(admin >= 80)) + Message(0, "Error: Only GMs can use this channel"); + else if (!worldserver.SendChannelMessage(this, targetname, chan_num, 0, language, message)) + Message(0, "Error: World server disconnected"); + break; + } + case 7: { /* Tell */ + if(!global_channel_timer.Check()) + { + if(strlen(targetname) == 0) + ChannelMessageReceived(7, language, lang_skill, message, "discard"); //Fast typer or spammer?? + else + return; + } + + if(GetRevoked()) + { + Message(0, "You have been revoked. You may not send tells."); + return; + } + + if(TotalKarma < RuleI(Chat, KarmaGlobalChatLimit)) + { + if(GetLevel() < RuleI(Chat, GlobalChatLevelLimit)) + { + Message(0, "You do not have permission to send tells at this time."); + return; + } + } + + char target_name[64]; + + if(targetname) + { + size_t i = strlen(targetname); + int x; + for(x = 0; x < i; ++x) + { + if(targetname[x] == '%') + { + target_name[x] = '/'; + } + else + { + target_name[x] = targetname[x]; + } + } + target_name[x] = '\0'; + } + + if(!worldserver.SendChannelMessage(this, target_name, chan_num, 0, language, message)) + Message(0, "Error: World server disconnected"); + break; + } + case 8: { /* Say */ + if(message[0] == COMMAND_CHAR) { + if(command_dispatch(this, message) == -2) { + if(parse->PlayerHasQuestSub(EVENT_COMMAND)) { + int i = parse->EventPlayer(EVENT_COMMAND, this, message, 0); + if(i == 0 && !RuleB(Chat, SuppressCommandErrors)) { + Message(13, "Command '%s' not recognized.", message); + } + } else { + if(!RuleB(Chat, SuppressCommandErrors)) + Message(13, "Command '%s' not recognized.", message); + } + } + break; + } + + Mob* sender = this; + if (GetPet() && GetPet()->FindType(SE_VoiceGraft)) + sender = GetPet(); + + entity_list.ChannelMessage(sender, chan_num, language, lang_skill, message); + parse->EventPlayer(EVENT_SAY, this, message, language); + + if (sender != this) + break; + + if(quest_manager.ProximitySayInUse()) + entity_list.ProcessProximitySay(message, this, language); + + if (GetTarget() != 0 && GetTarget()->IsNPC()) { + if(!GetTarget()->CastToNPC()->IsEngaged()) { + CheckLDoNHail(GetTarget()); + CheckEmoteHail(GetTarget(), message); + + + if(DistNoRootNoZ(*GetTarget()) <= 200) { + NPC *tar = GetTarget()->CastToNPC(); + parse->EventNPC(EVENT_SAY, tar->CastToNPC(), this, message, language); + + if(RuleB(TaskSystem, EnableTaskSystem)) { + if(UpdateTasksOnSpeakWith(tar->GetNPCTypeID())) { + tar->DoQuestPause(this); + } + } + } + } + else { + if (DistNoRootNoZ(*GetTarget()) <= 200) { + parse->EventNPC(EVENT_AGGRO_SAY, GetTarget()->CastToNPC(), this, message, language); + } + } + + } + break; + } + case 20: + { + // UCS Relay for Underfoot and later. + if(!worldserver.SendChannelMessage(this, 0, chan_num, 0, language, message)) + Message(0, "Error: World server disconnected"); + break; + } + case 22: + { + // Emotes for Underfoot and later. + // crash protection -- cheater + message[1023] = '\0'; + size_t msg_len = strlen(message); + if (msg_len > 512) + message[512] = '\0'; + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_Emote, 4 + msg_len + strlen(GetName()) + 2); + Emote_Struct* es = (Emote_Struct*)outapp->pBuffer; + char *Buffer = (char *)es; + Buffer += 4; + snprintf(Buffer, sizeof(Emote_Struct) - 4, "%s %s", GetName(), message); + entity_list.QueueCloseClients(this, outapp, true, 100, 0, true, FilterSocials); + safe_delete(outapp); + break; + } + default: { + Message(0, "Channel (%i) not implemented", (uint16)chan_num); + } + } +} + +// if no language skill is specified, call the function with a skill of 100. +void Client::ChannelMessageSend(const char* from, const char* to, uint8 chan_num, uint8 language, const char* message, ...) { + ChannelMessageSend(from, to, chan_num, language, 100, message); +} + +void Client::ChannelMessageSend(const char* from, const char* to, uint8 chan_num, uint8 language, uint8 lang_skill, const char* message, ...) { + if ((chan_num==11 && !(this->GetGM())) || (chan_num==10 && this->Admin()<80)) // dont need to send /pr & /petition to everybody + return; + va_list argptr; + char buffer[4096]; + char message_sender[64]; + + va_start(argptr, message); + vsnprintf(buffer, 4096, message, argptr); + va_end(argptr); + + EQApplicationPacket app(OP_ChannelMessage, sizeof(ChannelMessage_Struct)+strlen(buffer)+1); + ChannelMessage_Struct* cm = (ChannelMessage_Struct*)app.pBuffer; + + if (from == 0) + strcpy(cm->sender, "ZServer"); + else if (from[0] == 0) + strcpy(cm->sender, "ZServer"); + else { + CleanMobName(from, message_sender); + strcpy(cm->sender, message_sender); + } + if (to != 0) + strcpy((char *) cm->targetname, to); + else if (chan_num == 7) + strcpy(cm->targetname, m_pp.name); + else + cm->targetname[0] = 0; + + uint8 ListenerSkill; + + if (language < MAX_PP_LANGUAGE) { + ListenerSkill = m_pp.languages[language]; + if (ListenerSkill == 0) { + cm->language = (MAX_PP_LANGUAGE - 1); // in an unknown tongue + } + else { + cm->language = language; + } + } + else { + ListenerSkill = m_pp.languages[0]; + cm->language = 0; + } + + // set effective language skill = lower of sender and receiver skills + int32 EffSkill = (lang_skill < ListenerSkill ? lang_skill : ListenerSkill); + if (EffSkill > 100) // maximum language skill is 100 + EffSkill = 100; + cm->skill_in_language = EffSkill; + + // Garble the message based on listener skill + if (ListenerSkill < 100) { + GarbleMessage(buffer, (100 - ListenerSkill)); + } + + cm->chan_num = chan_num; + strcpy(&cm->message[0], buffer); + QueuePacket(&app); + + if ((chan_num == 2) && (ListenerSkill < 100)) { // group message in unmastered language, check for skill up + if ((m_pp.languages[language] <= lang_skill) && (from != this->GetName())) + CheckLanguageSkillIncrease(language, lang_skill); + } +} + +void Client::Message(uint32 type, const char* message, ...) { + if (GetFilter(FilterSpellDamage) == FilterHide && type == MT_NonMelee) + return; + if (GetFilter(FilterMeleeCrits) == FilterHide && type == MT_CritMelee) //98 is self... + return; + if (GetFilter(FilterSpellCrits) == FilterHide && type == MT_SpellCrits) + return; + + va_list argptr; + char *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; + EQApplicationPacket* 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 + const int MaxNPCNameLength = 60; + // I assume there is an upper safe limit on the message length. Don't know what it is, but 4000 doesn't crash + // the client. + const int MaxMessageLength = 4000; + + char OutNPCName[MaxNPCNameLength+1]; + char OutMessage[MaxMessageLength+1]; + + // Apparently Visual C++ snprintf is not C99 compliant and doesn't put the null terminator + // in if the formatted string >= the maximum length, so we put it in. + // + snprintf(OutNPCName, MaxNPCNameLength, "%s", npcname); OutNPCName[MaxNPCNameLength]='\0'; + snprintf(OutMessage, MaxMessageLength, "%s", message); OutMessage[MaxMessageLength]='\0'; + + uint32 len_packet = sizeof(SpecialMesg_Struct) + strlen(OutNPCName) + strlen(OutMessage); + EQApplicationPacket* app = new EQApplicationPacket(OP_SpecialMesg, len_packet); + SpecialMesg_Struct* sm=(SpecialMesg_Struct*)app->pBuffer; + + sm->header[0] = 0; + sm->header[1] = 2; + sm->header[2] = 0; + sm->msg_type = 0x0a; + sm->target_spawn_id = GetID(); + + char *dest = &sm->sayer[0]; + + memcpy(dest, OutNPCName, strlen(OutNPCName) + 1); + + dest = dest + strlen(OutNPCName) + 13; + + memcpy(dest, OutMessage, strlen(OutMessage) + 1); + + QueuePacket(app); + + safe_delete(app); +} + +void Client::SetMaxHP() { + if(dead) + return; + SetHP(CalcMaxHP()); + SendHPUpdate(); + Save(); +} + +bool Client::UpdateLDoNPoints(int32 points, uint32 theme) +{ + +/* make sure total stays in sync with individual buckets + m_pp.ldon_points_available = m_pp.ldon_points_guk + +m_pp.ldon_points_mir + +m_pp.ldon_points_mmc + +m_pp.ldon_points_ruj + +m_pp.ldon_points_tak; */ + + if(points < 0) + { + if(m_pp.ldon_points_available < (0-points)) + return false; + } + switch(theme) + { + // handle generic points (theme=0) + case 0: + { // no theme, so distribute evenly across all + int splitpts=points/5; + int gukpts=splitpts+(points%5); + int mirpts=splitpts; + int mmcpts=splitpts; + int rujpts=splitpts; + int takpts=splitpts; + + splitpts=0; + + if(points < 0) + { + if(m_pp.ldon_points_available < (0-points)) + { + return false; + } + if(m_pp.ldon_points_guk < (0-gukpts)) + { + mirpts+=gukpts+m_pp.ldon_points_guk; + gukpts=0-m_pp.ldon_points_guk; + } + if(m_pp.ldon_points_mir < (0-mirpts)) + { + mmcpts+=mirpts+m_pp.ldon_points_mir; + mirpts=0-m_pp.ldon_points_mir; + } + if(m_pp.ldon_points_mmc < (0-mmcpts)) + { + rujpts+=mmcpts+m_pp.ldon_points_mmc; + mmcpts=0-m_pp.ldon_points_mmc; + } + if(m_pp.ldon_points_ruj < (0-rujpts)) + { + takpts+=rujpts+m_pp.ldon_points_ruj; + rujpts=0-m_pp.ldon_points_ruj; + } + if(m_pp.ldon_points_tak < (0-takpts)) + { + splitpts=takpts+m_pp.ldon_points_tak; + takpts=0-m_pp.ldon_points_tak; + } + } + m_pp.ldon_points_guk += gukpts; + m_pp.ldon_points_mir+=mirpts; + m_pp.ldon_points_mmc += mmcpts; + m_pp.ldon_points_ruj += rujpts; + m_pp.ldon_points_tak += takpts; + points-=splitpts; + // if anything left, recursively loop thru again + if (splitpts !=0) + UpdateLDoNPoints(splitpts,0); + break; + } + case 1: + { + if(points < 0) + { + if(m_pp.ldon_points_guk < (0-points)) + return false; + } + m_pp.ldon_points_guk += points; + break; + } + case 2: + { + if(points < 0) + { + if(m_pp.ldon_points_mir < (0-points)) + return false; + } + m_pp.ldon_points_mir += points; + break; + } + case 3: + { + if(points < 0) + { + if(m_pp.ldon_points_mmc < (0-points)) + return false; + } + m_pp.ldon_points_mmc += points; + break; + } + case 4: + { + if(points < 0) + { + if(m_pp.ldon_points_ruj < (0-points)) + return false; + } + m_pp.ldon_points_ruj += points; + break; + } + case 5: + { + if(points < 0) + { + if(m_pp.ldon_points_tak < (0-points)) + return false; + } + m_pp.ldon_points_tak += points; + break; + } + } + m_pp.ldon_points_available += points; + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_AdventurePointsUpdate, sizeof(AdventurePoints_Update_Struct)); + AdventurePoints_Update_Struct* apus = (AdventurePoints_Update_Struct*)outapp->pBuffer; + apus->ldon_available_points = m_pp.ldon_points_available; + apus->ldon_guk_points = m_pp.ldon_points_guk; + apus->ldon_mirugal_points = m_pp.ldon_points_mir; + apus->ldon_mistmoore_points = m_pp.ldon_points_mmc; + apus->ldon_rujarkian_points = m_pp.ldon_points_ruj; + apus->ldon_takish_points = m_pp.ldon_points_tak; + outapp->priority = 6; + QueuePacket(outapp); + safe_delete(outapp); + return true; + + return(false); +} + +void Client::SetSkill(SkillUseTypes skillid, uint16 value) { + if (skillid > HIGHEST_SKILL) + return; + m_pp.skills[skillid] = value; // We need to be able to #setskill 254 and 255 to reset skills + + database.SaveCharacterSkill(this->CharacterID(), skillid, value); + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_SkillUpdate, sizeof(SkillUpdate_Struct)); + SkillUpdate_Struct* skill = (SkillUpdate_Struct*)outapp->pBuffer; + skill->skillId=skillid; + skill->value=value; + QueuePacket(outapp); + safe_delete(outapp); +} + +void Client::IncreaseLanguageSkill(int skill_id, int value) { + + if (skill_id >= MAX_PP_LANGUAGE) + return; //Invalid lang id + + m_pp.languages[skill_id] += value; + + if (m_pp.languages[skill_id] > 100) //Lang skill above max + m_pp.languages[skill_id] = 100; + + database.SaveCharacterLanguage(this->CharacterID(), skill_id, m_pp.languages[skill_id]); + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_SkillUpdate, sizeof(SkillUpdate_Struct)); + SkillUpdate_Struct* skill = (SkillUpdate_Struct*)outapp->pBuffer; + skill->skillId = 100 + skill_id; + skill->value = m_pp.languages[skill_id]; + QueuePacket(outapp); + safe_delete(outapp); + + Message_StringID( MT_Skills, LANG_SKILL_IMPROVED ); //Notify client +} + +void Client::AddSkill(SkillUseTypes skillid, uint16 value) { + if (skillid > HIGHEST_SKILL) + return; + value = GetRawSkill(skillid) + value; + uint16 max = GetMaxSkillAfterSpecializationRules(skillid, MaxSkill(skillid)); + if (value > max) + value = max; + SetSkill(skillid, value); +} + +void Client::SendSound(){//Makes a sound. + EQApplicationPacket* outapp = new EQApplicationPacket(OP_Sound, 68); + unsigned char x[68]; + memset(x, 0, 68); + x[0]=0x22; + memset(&x[4],0x8002,sizeof(uint16)); + memset(&x[8],0x8624,sizeof(uint16)); + memset(&x[12],0x4A01,sizeof(uint16)); + x[16]=0x05; + x[28]=0x00;//change this value to give gold to the client + memset(&x[40],0xFFFFFFFF,sizeof(uint32)); + memset(&x[44],0xFFFFFFFF,sizeof(uint32)); + memset(&x[48],0xFFFFFFFF,sizeof(uint32)); + memset(&x[52],0xFFFFFFFF,sizeof(uint32)); + memset(&x[56],0xFFFFFFFF,sizeof(uint32)); + memset(&x[60],0xFFFFFFFF,sizeof(uint32)); + memset(&x[64],0xffffffff,sizeof(uint32)); + memcpy(outapp->pBuffer,x,outapp->size); + QueuePacket(outapp); + safe_delete(outapp); + +} +void Client::UpdateWho(uint8 remove) { + if (account_id == 0) + return; + if (!worldserver.Connected()) + return; + ServerPacket* pack = new ServerPacket(ServerOP_ClientList, sizeof(ServerClientList_Struct)); + ServerClientList_Struct* scl = (ServerClientList_Struct*) pack->pBuffer; + scl->remove = remove; + scl->wid = this->GetWID(); + scl->IP = this->GetIP(); + scl->charid = this->CharacterID(); + strcpy(scl->name, this->GetName()); + + scl->gm = GetGM(); + scl->Admin = this->Admin(); + scl->AccountID = this->AccountID(); + strcpy(scl->AccountName, this->AccountName()); + scl->LSAccountID = this->LSAccountID(); + strn0cpy(scl->lskey, lskey, sizeof(scl->lskey)); + scl->zone = zone->GetZoneID(); + scl->instance_id = zone->GetInstanceID(); + scl->race = this->GetRace(); + scl->class_ = GetClass(); + scl->level = GetLevel(); + if (m_pp.anon == 0) + scl->anon = 0; + else if (m_pp.anon == 1) + scl->anon = 1; + else if (m_pp.anon >= 2) + scl->anon = 2; + + scl->ClientVersion = GetClientVersion(); + scl->tellsoff = tellsoff; + scl->guild_id = guild_id; + scl->LFG = LFG; + if(LFG) { + scl->LFGFromLevel = LFGFromLevel; + scl->LFGToLevel = LFGToLevel; + scl->LFGMatchFilter = LFGMatchFilter; + memcpy(scl->LFGComments, LFGComments, sizeof(scl->LFGComments)); + } + + worldserver.SendPacket(pack); + safe_delete(pack); +} + +void Client::WhoAll(Who_All_Struct* whom) { + + if (!worldserver.Connected()) + Message(0, "Error: World server disconnected"); + else { + ServerPacket* pack = new ServerPacket(ServerOP_Who, sizeof(ServerWhoAll_Struct)); + ServerWhoAll_Struct* whoall = (ServerWhoAll_Struct*) pack->pBuffer; + whoall->admin = this->Admin(); + whoall->fromid=this->GetID(); + strcpy(whoall->from, this->GetName()); + strn0cpy(whoall->whom, whom->whom, 64); + whoall->lvllow = whom->lvllow; + whoall->lvlhigh = whom->lvlhigh; + whoall->gmlookup = whom->gmlookup; + whoall->wclass = whom->wclass; + whoall->wrace = whom->wrace; + worldserver.SendPacket(pack); + safe_delete(pack); + } +} + +void Client::FriendsWho(char *FriendsString) { + + if (!worldserver.Connected()) + Message(0, "Error: World server disconnected"); + else { + ServerPacket* pack = new ServerPacket(ServerOP_FriendsWho, sizeof(ServerFriendsWho_Struct) + strlen(FriendsString)); + ServerFriendsWho_Struct* FriendsWho = (ServerFriendsWho_Struct*) pack->pBuffer; + FriendsWho->FromID = this->GetID(); + strcpy(FriendsWho->FromName, GetName()); + strcpy(FriendsWho->FriendsString, FriendsString); + worldserver.SendPacket(pack); + safe_delete(pack); + } +} + + +void Client::UpdateAdmin(bool iFromDB) { + int16 tmp = admin; + if (iFromDB) + admin = database.CheckStatus(account_id); + if (tmp == admin && iFromDB) + return; + + if(m_pp.gm) + { +#if EQDEBUG >= 5 + printf("%s is a GM\n", GetName()); +#endif +// no need for this, having it set in pp you already start as gm +// and it's also set in your spawn packet so other people see it too +// SendAppearancePacket(AT_GM, 1, false); + petition_list.UpdateGMQueue(); + } + + UpdateWho(); +} + +void Client::SetStats(uint8 type,int16 set_val){ + if(type>STAT_DISEASE){ + printf("Error in Client::IncStats, received invalid type of: %i\n",type); + return; + } + EQApplicationPacket* outapp = new EQApplicationPacket(OP_IncreaseStats,sizeof(IncreaseStat_Struct)); + IncreaseStat_Struct* iss=(IncreaseStat_Struct*)outapp->pBuffer; + switch(type){ + case STAT_STR: + if(set_val>0) + iss->str=set_val; + if(set_val<0) + m_pp.STR=0; + else if(set_val>255) + m_pp.STR=255; + else + m_pp.STR=set_val; + break; + case STAT_STA: + if(set_val>0) + iss->sta=set_val; + if(set_val<0) + m_pp.STA=0; + else if(set_val>255) + m_pp.STA=255; + else + m_pp.STA=set_val; + break; + case STAT_AGI: + if(set_val>0) + iss->agi=set_val; + if(set_val<0) + m_pp.AGI=0; + else if(set_val>255) + m_pp.AGI=255; + else + m_pp.AGI=set_val; + break; + case STAT_DEX: + if(set_val>0) + iss->dex=set_val; + if(set_val<0) + m_pp.DEX=0; + else if(set_val>255) + m_pp.DEX=255; + else + m_pp.DEX=set_val; + break; + case STAT_INT: + if(set_val>0) + iss->int_=set_val; + if(set_val<0) + m_pp.INT=0; + else if(set_val>255) + m_pp.INT=255; + else + m_pp.INT=set_val; + break; + case STAT_WIS: + if(set_val>0) + iss->wis=set_val; + if(set_val<0) + m_pp.WIS=0; + else if(set_val>255) + m_pp.WIS=255; + else + m_pp.WIS=set_val; + break; + case STAT_CHA: + if(set_val>0) + iss->cha=set_val; + if(set_val<0) + m_pp.CHA=0; + else if(set_val>255) + m_pp.CHA=255; + else + m_pp.CHA=set_val; + break; + } + QueuePacket(outapp); + safe_delete(outapp); +} + +void Client::IncStats(uint8 type,int16 increase_val){ + if(type>STAT_DISEASE){ + printf("Error in Client::IncStats, received invalid type of: %i\n",type); + return; + } + EQApplicationPacket* outapp = new EQApplicationPacket(OP_IncreaseStats,sizeof(IncreaseStat_Struct)); + IncreaseStat_Struct* iss=(IncreaseStat_Struct*)outapp->pBuffer; + switch(type){ + case STAT_STR: + if(increase_val>0) + iss->str=increase_val; + if((m_pp.STR+increase_val*2)<0) + m_pp.STR=0; + else if((m_pp.STR+increase_val*2)>255) + m_pp.STR=255; + else + m_pp.STR+=increase_val*2; + break; + case STAT_STA: + if(increase_val>0) + iss->sta=increase_val; + if((m_pp.STA+increase_val*2)<0) + m_pp.STA=0; + else if((m_pp.STA+increase_val*2)>255) + m_pp.STA=255; + else + m_pp.STA+=increase_val*2; + break; + case STAT_AGI: + if(increase_val>0) + iss->agi=increase_val; + if((m_pp.AGI+increase_val*2)<0) + m_pp.AGI=0; + else if((m_pp.AGI+increase_val*2)>255) + m_pp.AGI=255; + else + m_pp.AGI+=increase_val*2; + break; + case STAT_DEX: + if(increase_val>0) + iss->dex=increase_val; + if((m_pp.DEX+increase_val*2)<0) + m_pp.DEX=0; + else if((m_pp.DEX+increase_val*2)>255) + m_pp.DEX=255; + else + m_pp.DEX+=increase_val*2; + break; + case STAT_INT: + if(increase_val>0) + iss->int_=increase_val; + if((m_pp.INT+increase_val*2)<0) + m_pp.INT=0; + else if((m_pp.INT+increase_val*2)>255) + m_pp.INT=255; + else + m_pp.INT+=increase_val*2; + break; + case STAT_WIS: + if(increase_val>0) + iss->wis=increase_val; + if((m_pp.WIS+increase_val*2)<0) + m_pp.WIS=0; + else if((m_pp.WIS+increase_val*2)>255) + m_pp.WIS=255; + else + m_pp.WIS+=increase_val*2; + break; + case STAT_CHA: + if(increase_val>0) + iss->cha=increase_val; + if((m_pp.CHA+increase_val*2)<0) + m_pp.CHA=0; + else if((m_pp.CHA+increase_val*2)>255) + m_pp.CHA=255; + else + m_pp.CHA+=increase_val*2; + break; + } + QueuePacket(outapp); + safe_delete(outapp); +} + +const int32& Client::SetMana(int32 amount) { + bool update = false; + if (amount < 0) + amount = 0; + if (amount > GetMaxMana()) + amount = GetMaxMana(); + if (amount != cur_mana) + update = true; + cur_mana = amount; + if (update) + Mob::SetMana(amount); + SendManaUpdatePacket(); + return cur_mana; +} + +void Client::SendManaUpdatePacket() { + if (!Connected() || IsCasting()) + return; + + if (GetClientVersion() >= EQClientSoD) { + SendManaUpdate(); + SendEnduranceUpdate(); + } + + //std::cout << "Sending mana update: " << (cur_mana - last_reported_mana) << std::endl; + if (last_reported_mana != cur_mana || last_reported_endur != cur_end) { + + + + EQApplicationPacket* 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; //always going to be 0... since we check IsCasting() + outapp->priority = 6; + QueuePacket(outapp); + safe_delete(outapp); + + Group *g = GetGroup(); + + if(g) + { + outapp = new EQApplicationPacket(OP_MobManaUpdate, sizeof(MobManaUpdate_Struct)); + EQApplicationPacket *outapp2 = new EQApplicationPacket(OP_MobEnduranceUpdate, sizeof(MobEnduranceUpdate_Struct)); + + MobManaUpdate_Struct *mmus = (MobManaUpdate_Struct *)outapp->pBuffer; + MobEnduranceUpdate_Struct *meus = (MobEnduranceUpdate_Struct *)outapp2->pBuffer; + + 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()->GetClientVersion() >= EQClientSoD)) + { + g->members[i]->CastToClient()->QueuePacket(outapp); + g->members[i]->CastToClient()->QueuePacket(outapp2); + } + + safe_delete(outapp); + safe_delete(outapp2); + } + + + last_reported_mana = cur_mana; + last_reported_endur = cur_end; + } +} + +// sends mana update to self +void Client::SendManaUpdate() +{ + EQApplicationPacket* 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(); + QueuePacket(mana_app); + entity_list.QueueClientsByXTarget(this, mana_app, false); + safe_delete(mana_app); +} + +// sends endurance update to self +void Client::SendEnduranceUpdate() +{ + EQApplicationPacket* 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(); + QueuePacket(end_app); + entity_list.QueueClientsByXTarget(this, end_app, false); + safe_delete(end_app); +} + +void Client::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) +{ + Mob::FillSpawnStruct(ns, ForWho); + + // Populate client-specific spawn information + ns->spawn.afk = AFK; + ns->spawn.lfg = LFG; // afk and lfg are cleared on zoning on live + ns->spawn.anon = m_pp.anon; + ns->spawn.gm = GetGM() ? 1 : 0; + ns->spawn.guildID = GuildID(); +// ns->spawn.linkdead = IsLD() ? 1 : 0; +// ns->spawn.pvp = GetPVP() ? 1 : 0; + + + strcpy(ns->spawn.title, m_pp.title); + strcpy(ns->spawn.suffix, m_pp.suffix); + + if (IsBecomeNPC() == true) + ns->spawn.NPC = 1; + else if (ForWho == this) + ns->spawn.NPC = 10; + else + ns->spawn.NPC = 0; + ns->spawn.is_pet = 0; + + if (!IsInAGuild()) { + ns->spawn.guildrank = 0xFF; + } else { + ns->spawn.guildrank = guild_mgr.GetDisplayedRank(GuildID(), GuildRank(), AccountID()); + } + ns->spawn.size = 0; // Changing size works, but then movement stops! (wth?) + ns->spawn.runspeed = (gmspeed == 0) ? runspeed : 3.125f; + if (!m_pp.showhelm) ns->spawn.showhelm = 0; + + // pp also hold this info; should we pull from there or inventory? + // (update: i think pp should do it, as this holds LoY dye - plus, this is ugly code with Inventory!) + const Item_Struct* item = nullptr; + const ItemInst* inst = nullptr; + if ((inst = m_inv[MainHands]) && inst->IsType(ItemClassCommon)) { + item = inst->GetItem(); + ns->spawn.equipment[MaterialHands] = item->Material; + ns->spawn.colors[MaterialHands].color = GetEquipmentColor(MaterialHands); + } + if ((inst = m_inv[MainHead]) && inst->IsType(ItemClassCommon)) { + item = inst->GetItem(); + ns->spawn.equipment[MaterialHead] = item->Material; + ns->spawn.colors[MaterialHead].color = GetEquipmentColor(MaterialHead); + } + if ((inst = m_inv[MainArms]) && inst->IsType(ItemClassCommon)) { + item = inst->GetItem(); + ns->spawn.equipment[MaterialArms] = item->Material; + ns->spawn.colors[MaterialArms].color = GetEquipmentColor(MaterialArms); + } + if ((inst = m_inv[MainWrist1]) && inst->IsType(ItemClassCommon)) { + item = inst->GetItem(); + ns->spawn.equipment[MaterialWrist]= item->Material; + ns->spawn.colors[MaterialWrist].color = GetEquipmentColor(MaterialWrist); + } + + /* + // non-live behavior + if ((inst = m_inv[SLOT_BRACER02]) && inst->IsType(ItemClassCommon)) { + item = inst->GetItem(); + ns->spawn.equipment[MaterialWrist]= item->Material; + ns->spawn.colors[MaterialWrist].color = GetEquipmentColor(MaterialWrist); + } + */ + + if ((inst = m_inv[MainChest]) && inst->IsType(ItemClassCommon)) { + item = inst->GetItem(); + ns->spawn.equipment[MaterialChest] = item->Material; + ns->spawn.colors[MaterialChest].color = GetEquipmentColor(MaterialChest); + } + if ((inst = m_inv[MainLegs]) && inst->IsType(ItemClassCommon)) { + item = inst->GetItem(); + ns->spawn.equipment[MaterialLegs] = item->Material; + ns->spawn.colors[MaterialLegs].color = GetEquipmentColor(MaterialLegs); + } + if ((inst = m_inv[MainFeet]) && inst->IsType(ItemClassCommon)) { + item = inst->GetItem(); + ns->spawn.equipment[MaterialFeet] = item->Material; + ns->spawn.colors[MaterialFeet].color = GetEquipmentColor(MaterialFeet); + } + int ornamentationAugtype = RuleI(Character, OrnamentationAugmentType); + if ((inst = m_inv[MainPrimary]) && inst->IsType(ItemClassCommon)) { + if (inst->GetOrnamentationAug(ornamentationAugtype)) { + item = inst->GetOrnamentationAug(ornamentationAugtype)->GetItem(); + if (strlen(item->IDFile) > 2) + ns->spawn.equipment[MaterialPrimary] = atoi(&item->IDFile[2]); + } + else if (inst->GetOrnamentationIcon() && inst->GetOrnamentationIDFile()) { + ns->spawn.equipment[MaterialPrimary] = inst->GetOrnamentationIDFile(); + } + else { + item = inst->GetItem(); + if (strlen(item->IDFile) > 2) + ns->spawn.equipment[MaterialPrimary] = atoi(&item->IDFile[2]); + } + } + if ((inst = m_inv[MainSecondary]) && inst->IsType(ItemClassCommon)) { + if (inst->GetOrnamentationAug(ornamentationAugtype)) { + item = inst->GetOrnamentationAug(ornamentationAugtype)->GetItem(); + if (strlen(item->IDFile) > 2) + ns->spawn.equipment[MaterialSecondary] = atoi(&item->IDFile[2]); + } + else if (inst->GetOrnamentationIcon() && inst->GetOrnamentationIDFile()) { + ns->spawn.equipment[MaterialSecondary] = inst->GetOrnamentationIDFile(); + } + else { + item = inst->GetItem(); + if (strlen(item->IDFile) > 2) + ns->spawn.equipment[MaterialSecondary] = atoi(&item->IDFile[2]); + } + } + + //these two may be related to ns->spawn.texture + /* + ns->spawn.npc_armor_graphic = texture; + ns->spawn.npc_helm_graphic = helmtexture; + */ + + //filling in some unknowns to make the client happy +// ns->spawn.unknown0002[2] = 3; + +} + +bool Client::GMHideMe(Client* client) { + if (gmhideme) { + if (client == 0) + return true; + else if (admin > client->Admin()) + return true; + else + return false; + } + else + return false; +} + +void Client::Duck() { + SetAppearance(eaCrouching, false); +} + +void Client::Stand() { + SetAppearance(eaStanding, false); +} + +void Client::ChangeLastName(const char* in_lastname) { + memset(m_pp.last_name, 0, sizeof(m_pp.last_name)); + strn0cpy(m_pp.last_name, in_lastname, sizeof(m_pp.last_name)); + EQApplicationPacket* outapp = new EQApplicationPacket(OP_GMLastName, sizeof(GMLastName_Struct)); + GMLastName_Struct* gmn = (GMLastName_Struct*)outapp->pBuffer; + strcpy(gmn->name, name); + strcpy(gmn->gmname, name); + strcpy(gmn->lastname, in_lastname); + gmn->unknown[0]=1; + gmn->unknown[1]=1; + gmn->unknown[2]=1; + gmn->unknown[3]=1; + entity_list.QueueClients(this, outapp, false); + // Send name update packet here... once know what it is + safe_delete(outapp); +} + +bool Client::ChangeFirstName(const char* in_firstname, const char* gmname) +{ + // check duplicate name + bool usedname = database.CheckUsedName((const char*) in_firstname); + if (!usedname) { + return false; + } + + // update character_ + if(!database.UpdateName(GetName(), in_firstname)) + return false; + + // update pp + memset(m_pp.name, 0, sizeof(m_pp.name)); + snprintf(m_pp.name, sizeof(m_pp.name), "%s", in_firstname); + strcpy(name, m_pp.name); + Save(); + + // send name update packet + EQApplicationPacket* outapp = new EQApplicationPacket(OP_GMNameChange, sizeof(GMName_Struct)); + GMName_Struct* gmn=(GMName_Struct*)outapp->pBuffer; + strn0cpy(gmn->gmname,gmname,64); + strn0cpy(gmn->oldname,GetName(),64); + strn0cpy(gmn->newname,in_firstname,64); + gmn->unknown[0] = 1; + gmn->unknown[1] = 1; + gmn->unknown[2] = 1; + entity_list.QueueClients(this, outapp, false); + safe_delete(outapp); + + // finally, update the /who list + UpdateWho(); + + // success + return true; +} + +void Client::SetGM(bool toggle) { + m_pp.gm = toggle ? 1 : 0; + Message(13, "You are %s a GM.", m_pp.gm ? "now" : "no longer"); + SendAppearancePacket(AT_GM, m_pp.gm); + Save(); + UpdateWho(); +} + +void Client::ReadBook(BookRequest_Struct *book) { + char *txtfile = book->txtfile; + + if(txtfile[0] == '0' && txtfile[1] == '\0') { + //invalid book... coming up on non-book items. + return; + } + + std::string booktxt2 = database.GetBook(txtfile); + int length = booktxt2.length(); + + if (booktxt2[0] != '\0') { +#if EQDEBUG >= 6 + LogFile->write(EQEMuLog::Normal,"Client::ReadBook() textfile:%s Text:%s", txtfile, booktxt2.c_str()); +#endif + EQApplicationPacket* outapp = new EQApplicationPacket(OP_ReadBook, length + sizeof(BookText_Struct)); + + BookText_Struct *out = (BookText_Struct *) outapp->pBuffer; + out->window = book->window; + if(GetClientVersion() >= EQClientSoF) + { + const ItemInst *inst = m_inv[book->invslot]; + if(inst) + out->type = inst->GetItem()->Book; + else + out->type = book->type; + } + else + { + out->type = book->type; + } + out->invslot = book->invslot; + memcpy(out->booktext, booktxt2.c_str(), length); + + QueuePacket(outapp); + safe_delete(outapp); + } +} + +void Client::QuestReadBook(const char* text, uint8 type) { + std::string booktxt2 = text; + int length = booktxt2.length(); + if (booktxt2[0] != '\0') { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_ReadBook, length + sizeof(BookText_Struct)); + BookText_Struct *out = (BookText_Struct *) outapp->pBuffer; + out->window = 0xFF; + out->type = type; + out->invslot = 0; + memcpy(out->booktext, booktxt2.c_str(), length); + QueuePacket(outapp); + safe_delete(outapp); + } +} + +void Client::SendClientMoneyUpdate(uint8 type,uint32 amount){ + EQApplicationPacket* outapp = new EQApplicationPacket(OP_TradeMoneyUpdate,sizeof(TradeMoneyUpdate_Struct)); + TradeMoneyUpdate_Struct* mus= (TradeMoneyUpdate_Struct*)outapp->pBuffer; + mus->amount=amount; + mus->trader=0; + mus->type=type; + QueuePacket(outapp); + safe_delete(outapp); +} + +bool Client::TakeMoneyFromPP(uint64 copper, bool updateclient) { + int64 copperpp,silver,gold,platinum; + copperpp = m_pp.copper; + silver = static_cast(m_pp.silver) * 10; + gold = static_cast(m_pp.gold) * 100; + platinum = static_cast(m_pp.platinum) * 1000; + + int64 clienttotal = copperpp + silver + gold + platinum; + + clienttotal -= copper; + if(clienttotal < 0) + { + return false; // Not enough money! + } + else + { + copperpp -= copper; + if(copperpp <= 0) + { + copper = abs64(copperpp); + m_pp.copper = 0; + } + else + { + m_pp.copper = copperpp; + if(updateclient) + SendMoneyUpdate(); + SaveCurrency(); + return true; + } + silver -= copper; + if(silver <= 0) + { + copper = abs64(silver); + m_pp.silver = 0; + } + else + { + m_pp.silver = silver/10; + m_pp.copper += (silver-(m_pp.silver*10)); + if(updateclient) + SendMoneyUpdate(); + SaveCurrency(); + return true; + } + + gold -=copper; + + if(gold <= 0) + { + copper = abs64(gold); + m_pp.gold = 0; + } + else + { + m_pp.gold = gold/100; + uint64 silvertest = (gold-(static_cast(m_pp.gold)*100))/10; + m_pp.silver += silvertest; + uint64 coppertest = (gold-(static_cast(m_pp.gold)*100+silvertest*10)); + m_pp.copper += coppertest; + if(updateclient) + SendMoneyUpdate(); + SaveCurrency(); + return true; + } + + platinum -= copper; + + //Impossible for plat to be negative, already checked above + + m_pp.platinum = platinum/1000; + uint64 goldtest = (platinum-(static_cast(m_pp.platinum)*1000))/100; + m_pp.gold += goldtest; + uint64 silvertest = (platinum-(static_cast(m_pp.platinum)*1000+goldtest*100))/10; + m_pp.silver += silvertest; + uint64 coppertest = (platinum-(static_cast(m_pp.platinum)*1000+goldtest*100+silvertest*10)); + m_pp.copper = coppertest; + if(updateclient) + SendMoneyUpdate(); + RecalcWeight(); + SaveCurrency(); + return true; + } +} + +void Client::AddMoneyToPP(uint64 copper, bool updateclient){ + uint64 tmp; + uint64 tmp2; + tmp = copper; + + /* Add Amount of Platinum */ + tmp2 = tmp/1000; + int32 new_val = m_pp.platinum + tmp2; + if(new_val < 0) { m_pp.platinum = 0; } + else { m_pp.platinum = m_pp.platinum + tmp2; } + tmp-=tmp2*1000; + + //if (updateclient) + // SendClientMoneyUpdate(3,tmp2); + + /* Add Amount of Gold */ + tmp2 = tmp/100; + new_val = m_pp.gold + tmp2; + if(new_val < 0) { m_pp.gold = 0; } + else { m_pp.gold = m_pp.gold + tmp2; } + + tmp-=tmp2*100; + //if (updateclient) + // SendClientMoneyUpdate(2,tmp2); + + /* Add Amount of Silver */ + tmp2 = tmp/10; + new_val = m_pp.silver + tmp2; + if(new_val < 0) { + m_pp.silver = 0; + } else { + m_pp.silver = m_pp.silver + tmp2; + } + tmp-=tmp2*10; + //if (updateclient) + // SendClientMoneyUpdate(1,tmp2); + + // Add Copper + //tmp = tmp - (tmp2* 10); + //if (updateclient) + // SendClientMoneyUpdate(0,tmp); + tmp2 = tmp; + new_val = m_pp.copper + tmp2; + if(new_val < 0) { + m_pp.copper = 0; + } else { + m_pp.copper = m_pp.copper + tmp2; + } + + + //send them all at once, since the above code stopped working. + if(updateclient) + SendMoneyUpdate(); + + RecalcWeight(); + + SaveCurrency(); + + LogFile->write(EQEMuLog::Debug, "Client::AddMoneyToPP() %s should have: plat:%i gold:%i silver:%i copper:%i", GetName(), m_pp.platinum, m_pp.gold, m_pp.silver, m_pp.copper); +} + +void Client::EVENT_ITEM_ScriptStopReturn(){ + /* Set a timestamp in an entity variable for plugin check_handin.pl in return_items + This will stopgap players from items being returned if global_npc.pl has a catch all return_items + */ + struct timeval read_time; + char buffer[50]; + gettimeofday(&read_time, 0); + sprintf(buffer, "%li.%li \n", read_time.tv_sec, read_time.tv_usec); + this->SetEntityVariable("Stop_Return", buffer); +} + +void Client::AddMoneyToPP(uint32 copper, uint32 silver, uint32 gold, uint32 platinum, bool updateclient){ + this->EVENT_ITEM_ScriptStopReturn(); + + int32 new_value = m_pp.platinum + platinum; + if(new_value >= 0 && new_value > m_pp.platinum) + m_pp.platinum += platinum; + + new_value = m_pp.gold + gold; + if(new_value >= 0 && new_value > m_pp.gold) + m_pp.gold += gold; + + new_value = m_pp.silver + silver; + if(new_value >= 0 && new_value > m_pp.silver) + m_pp.silver += silver; + + new_value = m_pp.copper + copper; + if(new_value >= 0 && new_value > m_pp.copper) + m_pp.copper += copper; + + if(updateclient) + SendMoneyUpdate(); + + RecalcWeight(); + SaveCurrency(); + +#if (EQDEBUG>=5) + LogFile->write(EQEMuLog::Debug, "Client::AddMoneyToPP() %s should have: plat:%i gold:%i silver:%i copper:%i", + GetName(), m_pp.platinum, m_pp.gold, m_pp.silver, m_pp.copper); +#endif +} + +void Client::SendMoneyUpdate() { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_MoneyUpdate,sizeof(MoneyUpdate_Struct)); + MoneyUpdate_Struct* mus= (MoneyUpdate_Struct*)outapp->pBuffer; + + mus->platinum = m_pp.platinum; + mus->gold = m_pp.gold; + mus->silver = m_pp.silver; + mus->copper = m_pp.copper; + + FastQueuePacket(&outapp); +} + +bool Client::HasMoney(uint64 Copper) { + + if ((static_cast(m_pp.copper) + + (static_cast(m_pp.silver) * 10) + + (static_cast(m_pp.gold) * 100) + + (static_cast(m_pp.platinum) * 1000)) >= Copper) + return true; + + return false; +} + +uint64 Client::GetCarriedMoney() { + + return ((static_cast(m_pp.copper) + + (static_cast(m_pp.silver) * 10) + + (static_cast(m_pp.gold) * 100) + + (static_cast(m_pp.platinum) * 1000))); +} + +uint64 Client::GetAllMoney() { + + return ( + (static_cast(m_pp.copper) + + (static_cast(m_pp.silver) * 10) + + (static_cast(m_pp.gold) * 100) + + (static_cast(m_pp.platinum) * 1000) + + (static_cast(m_pp.copper_bank) + + (static_cast(m_pp.silver_bank) * 10) + + (static_cast(m_pp.gold_bank) * 100) + + (static_cast(m_pp.platinum_bank) * 1000) + + (static_cast(m_pp.copper_cursor) + + (static_cast(m_pp.silver_cursor) * 10) + + (static_cast(m_pp.gold_cursor) * 100) + + (static_cast(m_pp.platinum_cursor) * 1000) + + (static_cast(m_pp.platinum_shared) * 1000))))); +} + +bool Client::CheckIncreaseSkill(SkillUseTypes skillid, Mob *against_who, int chancemodi) { + if (IsDead() || IsUnconscious()) + return false; + if (IsAIControlled()) // no skillups while chamred =p + return false; + if (skillid > HIGHEST_SKILL) + return false; + int skillval = GetRawSkill(skillid); + int maxskill = GetMaxSkillAfterSpecializationRules(skillid, MaxSkill(skillid)); + + if(against_who) + { + if(against_who->GetSpecialAbility(IMMUNE_AGGRO) || against_who->IsClient() || + GetLevelCon(against_who->GetLevel()) == CON_GREEN) + { + //false by default + if( !mod_can_increase_skill(skillid, against_who) ) { return(false); } + } + } + + // Make sure we're not already at skill cap + if (skillval < maxskill) + { + // the higher your current skill level, the harder it is + int32 Chance = 10 + chancemodi + ((252 - skillval) / 20); + + Chance = (Chance * RuleI(Character, SkillUpModifier) / 100); + + Chance = mod_increase_skill_chance(Chance, against_who); + + if(Chance < 1) + Chance = 1; // Make it always possible + + if(zone->random.Real(0, 99) < Chance) + { + SetSkill(skillid, GetRawSkill(skillid) + 1); + _log(SKILLS__GAIN, "Skill %d at value %d successfully gain with %.4f%%chance (mod %d)", skillid, skillval, Chance, chancemodi); + return true; + } else { + _log(SKILLS__GAIN, "Skill %d at value %d failed to gain with %.4f%%chance (mod %d)", skillid, skillval, Chance, chancemodi); + } + } else { + _log(SKILLS__GAIN, "Skill %d at value %d cannot increase due to maxmum %d", skillid, skillval, maxskill); + } + return false; +} + +void Client::CheckLanguageSkillIncrease(uint8 langid, uint8 TeacherSkill) { + if (IsDead() || IsUnconscious()) + return; + if (IsAIControlled()) + return; + if (langid >= MAX_PP_LANGUAGE) + return; // do nothing if langid is an invalid language + + int LangSkill = m_pp.languages[langid]; // get current language skill + + if (LangSkill < 100) { // if the language isn't already maxed + int32 Chance = 5 + ((TeacherSkill - LangSkill)/10); // greater chance to learn if teacher's skill is much higher than yours + Chance = (Chance * RuleI(Character, SkillUpModifier)/100); + + if(zone->random.Real(0,100) < Chance) { // if they make the roll + IncreaseLanguageSkill(langid); // increase the language skill by 1 + _log(SKILLS__GAIN, "Language %d at value %d successfully gain with %.4f%%chance", langid, LangSkill, Chance); + } + else + _log(SKILLS__GAIN, "Language %d at value %d failed to gain with %.4f%%chance", langid, LangSkill, Chance); + } +} + +bool Client::HasSkill(SkillUseTypes skill_id) const { + return((GetSkill(skill_id) > 0) && CanHaveSkill(skill_id)); +} + +bool Client::CanHaveSkill(SkillUseTypes skill_id) const { + return(database.GetSkillCap(GetClass(), skill_id, RuleI(Character, MaxLevel)) > 0); + //if you don't have it by max level, then odds are you never will? +} + +uint16 Client::MaxSkill(SkillUseTypes skillid, uint16 class_, uint16 level) const { + return(database.GetSkillCap(class_, skillid, level)); +} + +uint8 Client::SkillTrainLevel(SkillUseTypes skillid, uint16 class_){ + return(database.GetTrainLevel(class_, skillid, RuleI(Character, MaxLevel))); +} + +uint16 Client::GetMaxSkillAfterSpecializationRules(SkillUseTypes skillid, uint16 maxSkill) +{ + uint16 Result = maxSkill; + + uint16 PrimarySpecialization = 0, SecondaryForte = 0; + + uint16 PrimarySkillValue = 0, SecondarySkillValue = 0; + + uint16 MaxSpecializations = GetAA(aaSecondaryForte) ? 2 : 1; + + if(skillid >= SkillSpecializeAbjure && skillid <= SkillSpecializeEvocation) + { + bool HasPrimarySpecSkill = false; + + int NumberOfPrimarySpecSkills = 0; + + for(int i = SkillSpecializeAbjure; i <= SkillSpecializeEvocation; ++i) + { + if(m_pp.skills[i] > 50) + { + HasPrimarySpecSkill = true; + NumberOfPrimarySpecSkills++; + } + if(m_pp.skills[i] > PrimarySkillValue) + { + if(PrimarySkillValue > SecondarySkillValue) + { + SecondarySkillValue = PrimarySkillValue; + SecondaryForte = PrimarySpecialization; + } + + PrimarySpecialization = i; + PrimarySkillValue = m_pp.skills[i]; + } + else if(m_pp.skills[i] > SecondarySkillValue) + { + SecondaryForte = i; + SecondarySkillValue = m_pp.skills[i]; + } + } + + if(SecondarySkillValue <=50) + SecondaryForte = 0; + + if(HasPrimarySpecSkill) + { + if(NumberOfPrimarySpecSkills <= MaxSpecializations) + { + if(MaxSpecializations == 1) + { + if(skillid != PrimarySpecialization) + { + Result = 50; + } + } + else + { + if((skillid != PrimarySpecialization) && ((skillid == SecondaryForte) || (SecondaryForte == 0))) + { + if((PrimarySkillValue > 100) || (!SecondaryForte)) + Result = 100; + } + else if(skillid != PrimarySpecialization) + { + Result = 50; + } + } + } + else + { + Message(13, "Your spell casting specializations skills have been reset. " + "Only %i primary specialization skill is allowed.", MaxSpecializations); + + for(int i = SkillSpecializeAbjure; i <= SkillSpecializeEvocation; ++i) + SetSkill((SkillUseTypes)i, 1); + + Save(); + + LogFile->write(EQEMuLog::Normal, "Reset %s's caster specialization skills to 1. " + "Too many specializations skills were above 50.", GetCleanName()); + } + + } + } + // This should possibly be handled by bonuses rather than here. + switch(skillid) + { + case SkillTracking: + { + Result += ((GetAA(aaAdvancedTracking) * 10) + (GetAA(aaTuneofPursuance) * 10)); + break; + } + + default: + break; + } + + return Result; +} + +void Client::SetPVP(bool toggle) { + m_pp.pvp = toggle ? 1 : 0; + + if(GetPVP()) + this->Message_StringID(MT_Shout,PVP_ON); + else + Message(13, "You no longer follow the ways of discord."); + + SendAppearancePacket(AT_PVP, GetPVP()); + Save(); +} + +void Client::WorldKick() { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_GMKick, sizeof(GMKick_Struct)); + GMKick_Struct* gmk = (GMKick_Struct *)outapp->pBuffer; + strcpy(gmk->name,GetName()); + QueuePacket(outapp); + safe_delete(outapp); + Kick(); +} + +void Client::GMKill() { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_GMKill, sizeof(GMKill_Struct)); + GMKill_Struct* gmk = (GMKill_Struct *)outapp->pBuffer; + strcpy(gmk->name,GetName()); + QueuePacket(outapp); + safe_delete(outapp); +} + +bool Client::CheckAccess(int16 iDBLevel, int16 iDefaultLevel) { + if ((admin >= iDBLevel) || (iDBLevel == 255 && admin >= iDefaultLevel)) + return true; + else + return false; +} + +void Client::MemorizeSpell(uint32 slot,uint32 spellid,uint32 scribing){ + EQApplicationPacket* outapp = new EQApplicationPacket(OP_MemorizeSpell,sizeof(MemorizeSpell_Struct)); + MemorizeSpell_Struct* mss=(MemorizeSpell_Struct*)outapp->pBuffer; + mss->scribing=scribing; + mss->slot=slot; + mss->spell_id=spellid; + outapp->priority = 5; + QueuePacket(outapp); + safe_delete(outapp); +} + +void Client::SetFeigned(bool in_feigned) { + if (in_feigned) + { + if(RuleB(Character, FeignKillsPet)) + { + SetPet(0); + } + SetHorseId(0); + entity_list.ClearFeignAggro(this); + forget_timer.Start(FeignMemoryDuration); + } else { + forget_timer.Disable(); + } + feigned=in_feigned; + } + +void Client::LogMerchant(Client* player, Mob* merchant, uint32 quantity, uint32 price, const Item_Struct* item, bool buying) +{ + if(!player || !merchant || !item) + return; + + std::string LogText = "Qty: "; + + char Buffer[255]; + memset(Buffer, 0, sizeof(Buffer)); + + snprintf(Buffer, sizeof(Buffer)-1, "%3i", quantity); + LogText += Buffer; + snprintf(Buffer, sizeof(Buffer)-1, "%10i", price); + LogText += " TotalValue: "; + LogText += Buffer; + snprintf(Buffer, sizeof(Buffer)-1, " ItemID: %7i", item->ID); + LogText += Buffer; + LogText += " "; + snprintf(Buffer, sizeof(Buffer)-1, " %s", item->Name); + LogText += Buffer; + + if (buying==true) { + database.logevents(player->AccountName(),player->AccountID(),player->admin,player->GetName(),merchant->GetName(),"Buying from Merchant",LogText.c_str(),2); + } + else { + database.logevents(player->AccountName(),player->AccountID(),player->admin,player->GetName(),merchant->GetName(),"Selling to Merchant",LogText.c_str(),3); + } +} + +bool Client::BindWound(Mob* bindmob, bool start, bool fail){ + EQApplicationPacket* outapp = 0; + if(!fail) { + outapp = new EQApplicationPacket(OP_Bind_Wound, sizeof(BindWound_Struct)); + BindWound_Struct* bind_out = (BindWound_Struct*) outapp->pBuffer; + // Start bind + if(!bindwound_timer.Enabled()) { + //make sure we actually have a bandage... and consume it. + int16 bslot = m_inv.HasItemByUse(ItemTypeBandage, 1, invWhereWorn|invWherePersonal); + if (bslot == INVALID_INDEX) { + bind_out->type = 3; + QueuePacket(outapp); + bind_out->type = 7; //this is the wrong message, dont know the right one. + QueuePacket(outapp); + return(true); + } + DeleteItemInInventory(bslot, 1, true); //do we need client update? + + // start complete timer + bindwound_timer.Start(10000); + bindwound_target = bindmob; + + // Send client unlock + bind_out->type = 3; + QueuePacket(outapp); + bind_out->type = 0; + // Client Unlocked + if(!bindmob) { + // send "bindmob dead" to client + bind_out->type = 4; + QueuePacket(outapp); + bind_out->type = 0; + bindwound_timer.Disable(); + bindwound_target = 0; + } + else { + // send bindmob "stand still" + if(!bindmob->IsAIControlled() && bindmob != this ) { + bind_out->type = 2; // ? + //bind_out->type = 3; // ? + bind_out->to = GetID(); // ? + bindmob->CastToClient()->QueuePacket(outapp); + bind_out->type = 0; + bind_out->to = 0; + } + else if (bindmob->IsAIControlled() && bindmob != this ){ + ; // Tell IPC to stand still? + } + else { + ; // Binding self + } + } + } else { + // finish bind + // disable complete timer + bindwound_timer.Disable(); + bindwound_target = 0; + if(!bindmob){ + // send "bindmob gone" to client + bind_out->type = 5; // not in zone + QueuePacket(outapp); + bind_out->type = 0; + } + + else { + if (!GetFeigned() && (bindmob->DistNoRoot(*this) <= 400)) { + // send bindmob bind done + if(!bindmob->IsAIControlled() && bindmob != this ) { + + } + else if(bindmob->IsAIControlled() && bindmob != this ) { + // Tell IPC to resume?? + } + else { + // Binding self + } + // Send client bind done + + bind_out->type = 1; // Done + QueuePacket(outapp); + bind_out->type = 0; + CheckIncreaseSkill(SkillBindWound, nullptr, 5); + + int maxHPBonus = spellbonuses.MaxBindWound + itembonuses.MaxBindWound + aabonuses.MaxBindWound; + + int max_percent = 50 + 10 * maxHPBonus; + + if(GetClass() == MONK && GetSkill(SkillBindWound) > 200) { + max_percent = 70 + 10 * maxHPBonus; + } + + max_percent = mod_bindwound_percent(max_percent, bindmob); + + int max_hp = bindmob->GetMaxHP()*max_percent/100; + + // send bindmob new hp's + if (bindmob->GetHP() < bindmob->GetMaxHP() && bindmob->GetHP() <= (max_hp)-1){ + // 0.120 per skill point, 0.60 per skill level, minimum 3 max 30 + int bindhps = 3; + + + if (GetSkill(SkillBindWound) > 200) { + bindhps += GetSkill(SkillBindWound)*4/10; + } else if (GetSkill(SkillBindWound) >= 10) { + bindhps += GetSkill(SkillBindWound)/4; + } + + //Implementation of aaMithanielsBinding is a guess (the multiplier) + int bindBonus = spellbonuses.BindWound + itembonuses.BindWound + aabonuses.BindWound; + + bindhps += bindhps*bindBonus / 100; + + bindhps = mod_bindwound_hp(bindhps, bindmob); + + //if the bind takes them above the max bindable + //cap it at that value. Dont know if live does it this way + //but it makes sense to me. + int chp = bindmob->GetHP() + bindhps; + if(chp > max_hp) + chp = max_hp; + + bindmob->SetHP(chp); + bindmob->SendHPUpdate(); + } + else { + //I dont have the real, live + Message(15, "You cannot bind wounds above %d%% hitpoints.", max_percent); + if(bindmob->IsClient()) + bindmob->CastToClient()->Message(15, "You cannot have your wounds bound above %d%% hitpoints.", max_percent); + // Too many hp message goes here. + } + } + else { + // Send client bind failed + if(bindmob != this) + bind_out->type = 6; // They moved + else + bind_out->type = 7; // Bandager moved + + QueuePacket(outapp); + bind_out->type = 0; + } + } + } + } + else if (bindwound_timer.Enabled()) { + // You moved + outapp = new EQApplicationPacket(OP_Bind_Wound, sizeof(BindWound_Struct)); + BindWound_Struct* bind_out = (BindWound_Struct*) outapp->pBuffer; + bindwound_timer.Disable(); + bindwound_target = 0; + bind_out->type = 7; + QueuePacket(outapp); + bind_out->type = 3; + QueuePacket(outapp); + } + safe_delete(outapp); + return true; +} + +void Client::SetMaterial(int16 in_slot, uint32 item_id) { + const Item_Struct* item = database.GetItem(item_id); + int ornamentationAugtype = RuleI(Character, OrnamentationAugmentType); + if (item && (item->ItemClass==ItemClassCommon)) { + if (in_slot==MainHead) + m_pp.item_material[MaterialHead] = item->Material; + else if (in_slot==MainChest) + m_pp.item_material[MaterialChest] = item->Material; + else if (in_slot==MainArms) + m_pp.item_material[MaterialArms] = item->Material; + else if (in_slot==MainWrist1) + m_pp.item_material[MaterialWrist] = item->Material; + else if (in_slot==MainHands) + m_pp.item_material[MaterialHands] = item->Material; + else if (in_slot==MainLegs) + m_pp.item_material[MaterialLegs] = item->Material; + else if (in_slot==MainFeet) + m_pp.item_material[MaterialFeet] = item->Material; + else if (in_slot == MainPrimary) { + const ItemInst* inst = m_inv[MainPrimary]; + if (inst && inst->GetOrnamentationAug(ornamentationAugtype)) { + item = inst->GetOrnamentationAug(ornamentationAugtype)->GetItem(); + m_pp.item_material[MaterialPrimary] = atoi(item->IDFile + 2); + } + else if (inst && inst->GetOrnamentationIcon() && inst->GetOrnamentationIDFile()) { + m_pp.item_material[MaterialPrimary] = inst->GetOrnamentationIDFile(); + } + else { + m_pp.item_material[MaterialPrimary] = atoi(item->IDFile + 2); + } + } + else if (in_slot == MainSecondary) { + const ItemInst* inst = m_inv[MainSecondary]; + if (inst && inst->GetOrnamentationAug(ornamentationAugtype)) { + item = inst->GetOrnamentationAug(ornamentationAugtype)->GetItem(); + m_pp.item_material[MaterialSecondary] = atoi(item->IDFile + 2); + } + else if (inst && inst->GetOrnamentationIcon() && inst->GetOrnamentationIDFile()) { + m_pp.item_material[MaterialSecondary] = inst->GetOrnamentationIDFile(); + } + else { + m_pp.item_material[MaterialSecondary] = atoi(item->IDFile + 2); + } + } + } +} + +void Client::ServerFilter(SetServerFilter_Struct* filter){ + +/* this code helps figure out the filter IDs in the packet if needed + static SetServerFilter_Struct ssss; + int r; + uint32 *o = (uint32 *) &ssss; + uint32 *n = (uint32 *) filter; + for(r = 0; r < (sizeof(SetServerFilter_Struct)/4); r++) { + if(*o != *n) + LogFile->write(EQEMuLog::Debug, "Filter %d changed from %d to %d", r, *o, *n); + o++; n++; + } + memcpy(&ssss, filter, sizeof(SetServerFilter_Struct)); +*/ +#define Filter0(type) \ + if(filter->filters[type] == 1) \ + ClientFilters[type] = FilterShow; \ + else \ + ClientFilters[type] = FilterHide; +#define Filter1(type) \ + if(filter->filters[type] == 0) \ + ClientFilters[type] = FilterShow; \ + else \ + ClientFilters[type] = FilterHide; + + Filter0(FilterGuildChat); + Filter0(FilterSocials); + Filter0(FilterGroupChat); + Filter0(FilterShouts); + Filter0(FilterAuctions); + Filter0(FilterOOC); + Filter0(FilterBadWords); + + if(filter->filters[FilterPCSpells] == 0) + ClientFilters[FilterPCSpells] = FilterShow; + else if(filter->filters[FilterPCSpells] == 1) + ClientFilters[FilterPCSpells] = FilterHide; + else + ClientFilters[FilterPCSpells] = FilterShowGroupOnly; + + Filter1(FilterNPCSpells); + + if(filter->filters[FilterBardSongs] == 0) + ClientFilters[FilterBardSongs] = FilterShow; + else if(filter->filters[FilterBardSongs] == 1) + ClientFilters[FilterBardSongs] = FilterShowSelfOnly; + else if(filter->filters[FilterBardSongs] == 2) + ClientFilters[FilterBardSongs] = FilterShowGroupOnly; + else + ClientFilters[FilterBardSongs] = FilterHide; + + if(filter->filters[FilterSpellCrits] == 0) + ClientFilters[FilterSpellCrits] = FilterShow; + else if(filter->filters[FilterSpellCrits] == 1) + ClientFilters[FilterSpellCrits] = FilterShowSelfOnly; + else + ClientFilters[FilterSpellCrits] = FilterHide; + + if (filter->filters[FilterMeleeCrits] == 0) + ClientFilters[FilterMeleeCrits] = FilterShow; + else if (filter->filters[FilterMeleeCrits] == 1) + ClientFilters[FilterMeleeCrits] = FilterShowSelfOnly; + else + ClientFilters[FilterMeleeCrits] = FilterHide; + + if(filter->filters[FilterSpellDamage] == 0) + ClientFilters[FilterSpellDamage] = FilterShow; + else if(filter->filters[FilterSpellDamage] == 1) + ClientFilters[FilterSpellDamage] = FilterShowSelfOnly; + else + ClientFilters[FilterSpellDamage] = FilterHide; + + Filter0(FilterMyMisses); + Filter0(FilterOthersMiss); + Filter0(FilterOthersHit); + Filter0(FilterMissedMe); + Filter1(FilterDamageShields); + + if (GetClientVersionBit() & BIT_SoDAndLater) { + if (filter->filters[FilterDOT] == 0) + ClientFilters[FilterDOT] = FilterShow; + else if (filter->filters[FilterDOT] == 1) + ClientFilters[FilterDOT] = FilterShowSelfOnly; + else if (filter->filters[FilterDOT] == 2) + ClientFilters[FilterDOT] = FilterShowGroupOnly; + else + ClientFilters[FilterDOT] = FilterHide; + } else { + if (filter->filters[FilterDOT] == 0) // show functions as self only + ClientFilters[FilterDOT] = FilterShowSelfOnly; + else + ClientFilters[FilterDOT] = FilterHide; + } + + Filter1(FilterPetHits); + Filter1(FilterPetMisses); + Filter1(FilterFocusEffects); + Filter1(FilterPetSpells); + + if (GetClientVersionBit() & BIT_SoDAndLater) { + if (filter->filters[FilterHealOverTime] == 0) + ClientFilters[FilterHealOverTime] = FilterShow; + // This is called 'Show Mine Only' in the clients, but functions the same as show + // so instead of apply special logic, just set to show + else if (filter->filters[FilterHealOverTime] == 1) + ClientFilters[FilterHealOverTime] = FilterShow; + else + ClientFilters[FilterHealOverTime] = FilterHide; + } else { + // these clients don't have a 'self only' filter + Filter1(FilterHealOverTime); + } +} + +// this version is for messages with no parameters +void Client::Message_StringID(uint32 type, uint32 string_id, uint32 distance) +{ + if (GetFilter(FilterSpellDamage) == FilterHide && type == MT_NonMelee) + return; + if (GetFilter(FilterMeleeCrits) == FilterHide && type == MT_CritMelee) //98 is self... + return; + if (GetFilter(FilterSpellCrits) == FilterHide && type == MT_SpellCrits) + return; + EQApplicationPacket* outapp = new EQApplicationPacket(OP_SimpleMessage,12); + SimpleMessage_Struct* sms = (SimpleMessage_Struct*)outapp->pBuffer; + sms->color=type; + sms->string_id=string_id; + + sms->unknown8=0; + + if(distance>0) + entity_list.QueueCloseClients(this,outapp,false,distance); + else + QueuePacket(outapp); + safe_delete(outapp); +} + +// +// this list of 9 args isn't how I want to do it, but to use va_arg +// you have to know how many args you're expecting, and to do that we have +// to load the eqstr file and count them in the string. +// This hack sucks but it's gonna work for now. +// +void Client::Message_StringID(uint32 type, uint32 string_id, const char* message1, + const char* message2,const char* message3,const char* message4, + const char* message5,const char* message6,const char* message7, + const char* message8,const char* message9, uint32 distance) +{ + if (GetFilter(FilterSpellDamage) == FilterHide && type == MT_NonMelee) + return; + if (GetFilter(FilterMeleeCrits) == FilterHide && type == MT_CritMelee) //98 is self... + return; + if (GetFilter(FilterSpellCrits) == FilterHide && type == MT_SpellCrits) + return; + if (GetFilter(FilterDamageShields) == FilterHide && type == MT_DS) + return; + + int i, argcount, length; + char *bufptr; + const char *message_arg[9] = {0}; + + if(type==MT_Emote) + type=4; + + if(!message1) + { + Message_StringID(type, string_id); // use the simple message instead + return; + } + + i = 0; + message_arg[i++] = message1; + message_arg[i++] = message2; + message_arg[i++] = message3; + message_arg[i++] = message4; + message_arg[i++] = message5; + message_arg[i++] = message6; + message_arg[i++] = message7; + message_arg[i++] = message8; + message_arg[i++] = message9; + + for(argcount = length = 0; message_arg[argcount]; argcount++) + length += strlen(message_arg[argcount]) + 1; + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_FormattedMessage, length+13); + FormattedMessage_Struct *fm = (FormattedMessage_Struct *)outapp->pBuffer; + fm->string_id = string_id; + fm->type = type; + bufptr = fm->message; + for(i = 0; i < argcount; i++) + { + strcpy(bufptr, message_arg[i]); + bufptr += strlen(message_arg[i]) + 1; + } + + + if(distance>0) + entity_list.QueueCloseClients(this,outapp,false,distance); + else + QueuePacket(outapp); + safe_delete(outapp); +} + +// helper function, returns true if we should see the message +bool Client::FilteredMessageCheck(Mob *sender, eqFilterType filter) +{ + eqFilterMode mode = GetFilter(filter); + // easy ones first + if (mode == FilterShow) + return true; + else if (mode == FilterHide) + return false; + + if (!sender && mode == FilterHide) { + return false; + } else if (sender) { + if (this == sender) { + if (mode == FilterHide) // don't need to check others + return false; + } else if (mode == FilterShowSelfOnly) { // we know sender isn't us + return false; + } else if (mode == FilterShowGroupOnly) { + Group *g = GetGroup(); + Raid *r = GetRaid(); + if (g) { + if (g->IsGroupMember(sender)) + return true; + } else if (r && sender->IsClient()) { + uint32 rgid1 = r->GetGroup(this); + uint32 rgid2 = r->GetGroup(sender->CastToClient()); + if (rgid1 != 0xFFFFFFFF && rgid1 == rgid2) + return true; + } else { + return false; + } + } + } + + // we passed our checks + return true; +} + +void Client::FilteredMessage_StringID(Mob *sender, uint32 type, + eqFilterType filter, uint32 string_id) +{ + if (!FilteredMessageCheck(sender, filter)) + return; + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_SimpleMessage, 12); + SimpleMessage_Struct *sms = (SimpleMessage_Struct *)outapp->pBuffer; + sms->color = type; + sms->string_id = string_id; + + sms->unknown8 = 0; + + QueuePacket(outapp); + safe_delete(outapp); + + return; +} + +void Client::FilteredMessage_StringID(Mob *sender, uint32 type, eqFilterType filter, uint32 string_id, + const char *message1, const char *message2, const char *message3, + const char *message4, const char *message5, const char *message6, + const char *message7, const char *message8, const char *message9) +{ + if (!FilteredMessageCheck(sender, filter)) + return; + + int i, argcount, length; + char *bufptr; + const char *message_arg[9] = {0}; + + if (type == MT_Emote) + type = 4; + + if (!message1) { + FilteredMessage_StringID(sender, type, filter, string_id); // use the simple message instead + return; + } + + i = 0; + message_arg[i++] = message1; + message_arg[i++] = message2; + message_arg[i++] = message3; + message_arg[i++] = message4; + message_arg[i++] = message5; + message_arg[i++] = message6; + message_arg[i++] = message7; + message_arg[i++] = message8; + message_arg[i++] = message9; + + for (argcount = length = 0; message_arg[argcount]; argcount++) + length += strlen(message_arg[argcount]) + 1; + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_FormattedMessage, length+13); + FormattedMessage_Struct *fm = (FormattedMessage_Struct *)outapp->pBuffer; + fm->string_id = string_id; + fm->type = type; + bufptr = fm->message; + for (i = 0; i < argcount; i++) { + strcpy(bufptr, message_arg[i]); + bufptr += strlen(message_arg[i]) + 1; + } + + QueuePacket(outapp); + safe_delete(outapp); +} + +void Client::Tell_StringID(uint32 string_id, const char *who, const char *message) +{ + char string_id_str[10]; + snprintf(string_id_str, 10, "%d", string_id); + + Message_StringID(MT_TellEcho, TELL_QUEUED_MESSAGE, who, string_id_str, message); +} + +void Client::SetTint(int16 in_slot, uint32 color) { + Color_Struct new_color; + new_color.color = color; + SetTint(in_slot, new_color); + database.SaveCharacterMaterialColor(this->CharacterID(), in_slot, color); +} + +// Still need to reconcile bracer01 versus bracer02 +void Client::SetTint(int16 in_slot, Color_Struct& color) { + if (in_slot==MainHead) + m_pp.item_tint[MaterialHead].color=color.color; + else if (in_slot==MainArms) + m_pp.item_tint[MaterialArms].color=color.color; + else if (in_slot==MainWrist1) + m_pp.item_tint[MaterialWrist].color=color.color; + /* + // non-live behavior + else if (in_slot==SLOT_BRACER02) + m_pp.item_tint[MaterialWrist].color=color.color; + */ + else if (in_slot==MainHands) + m_pp.item_tint[MaterialHands].color=color.color; + else if (in_slot==MainPrimary) + m_pp.item_tint[MaterialPrimary].color=color.color; + else if (in_slot==MainSecondary) + m_pp.item_tint[MaterialSecondary].color=color.color; + else if (in_slot==MainChest) + m_pp.item_tint[MaterialChest].color=color.color; + else if (in_slot==MainLegs) + m_pp.item_tint[MaterialLegs].color=color.color; + else if (in_slot==MainFeet) + m_pp.item_tint[MaterialFeet].color=color.color; + + database.SaveCharacterMaterialColor(this->CharacterID(), in_slot, color.color); +} + +void Client::SetHideMe(bool flag) +{ + EQApplicationPacket app; + + gmhideme = flag; + + if(gmhideme) + { + database.SetHideMe(AccountID(),true); + CreateDespawnPacket(&app, false); + entity_list.RemoveFromTargets(this); + trackable = false; + } + else + { + database.SetHideMe(AccountID(),false); + CreateSpawnPacket(&app); + trackable = true; + } + + entity_list.QueueClientsStatus(this, &app, true, 0, Admin()-1); +} + +void Client::SetLanguageSkill(int langid, int value) +{ + if (langid >= MAX_PP_LANGUAGE) + return; //Invalid Language + + if (value > 100) + value = 100; //Max lang value + + m_pp.languages[langid] = value; + database.SaveCharacterLanguage(this->CharacterID(), langid, value); + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_SkillUpdate, sizeof(SkillUpdate_Struct)); + SkillUpdate_Struct* skill = (SkillUpdate_Struct*)outapp->pBuffer; + skill->skillId = 100 + langid; + skill->value = m_pp.languages[langid]; + QueuePacket(outapp); + safe_delete(outapp); + + Message_StringID( MT_Skills, LANG_SKILL_IMPROVED ); //Notify the client +} + +void Client::LinkDead() +{ + if (GetGroup()) + { + entity_list.MessageGroup(this,true,15,"%s has gone linkdead.",GetName()); + GetGroup()->DelMember(this); + if (GetMerc()) + { + GetMerc()->RemoveMercFromGroup(GetMerc(), GetMerc()->GetGroup()); + } + } + Raid *raid = entity_list.GetRaidByClient(this); + if(raid){ + raid->MemberZoned(this); + } +// save_timer.Start(2500); + linkdead_timer.Start(RuleI(Zone,ClientLinkdeadMS)); + SendAppearancePacket(AT_Linkdead, 1); + client_state = CLIENT_LINKDEAD; + AI_Start(CLIENT_LD_TIMEOUT); +} + +uint8 Client::SlotConvert(uint8 slot,bool bracer){ + uint8 slot2=0; // why are we returning MainCharm instead of INVALID_INDEX? (must be a pre-charm segment...) + if(bracer) + return MainWrist2; + switch(slot){ + case MaterialHead: + slot2=MainHead; + break; + case MaterialChest: + slot2=MainChest; + break; + case MaterialArms: + slot2=MainArms; + break; + case MaterialWrist: + slot2=MainWrist1; + break; + case MaterialHands: + slot2=MainHands; + break; + case MaterialLegs: + slot2=MainLegs; + break; + case MaterialFeet: + slot2=MainFeet; + break; + } + return slot2; +} + +uint8 Client::SlotConvert2(uint8 slot){ + uint8 slot2=0; // same as above... + switch(slot){ + case MainHead: + slot2=MaterialHead; + break; + case MainChest: + slot2=MaterialChest; + break; + case MainArms: + slot2=MaterialArms; + break; + case MainWrist1: + slot2=MaterialWrist; + break; + case MainHands: + slot2=MaterialHands; + break; + case MainLegs: + slot2=MaterialLegs; + break; + case MainFeet: + slot2=MaterialFeet; + break; + } + return slot2; +} + +void Client::Escape() +{ + entity_list.RemoveFromTargets(this, true); + SetInvisible(1); + + Message_StringID(MT_Skills, ESCAPE); +} + +float Client::CalcPriceMod(Mob* other, bool reverse) +{ + float chaformula = 0; + if (other) + { + int factionlvl = GetFactionLevel(CharacterID(), other->CastToNPC()->GetNPCTypeID(), GetRace(), GetClass(), GetDeity(), other->CastToNPC()->GetPrimaryFaction(), other); + if (factionlvl >= FACTION_APPREHENSIVE) // Apprehensive or worse. + { + if (GetCHA() > 103) + { + chaformula = (GetCHA() - 103)*((-(RuleR(Merchant, ChaBonusMod))/100)*(RuleI(Merchant, PriceBonusPct))); // This will max out price bonus. + if (chaformula < -1*(RuleI(Merchant, PriceBonusPct))) + chaformula = -1*(RuleI(Merchant, PriceBonusPct)); + } + else if (GetCHA() < 103) + { + chaformula = (103 - GetCHA())*(((RuleR(Merchant, ChaPenaltyMod))/100)*(RuleI(Merchant, PricePenaltyPct))); // This will bottom out price penalty. + if (chaformula > 1*(RuleI(Merchant, PricePenaltyPct))) + chaformula = 1*(RuleI(Merchant, PricePenaltyPct)); + } + } + if (factionlvl <= FACTION_INDIFFERENT) // Indifferent or better. + { + if (GetCHA() > 75) + { + chaformula = (GetCHA() - 75)*((-(RuleR(Merchant, ChaBonusMod))/100)*(RuleI(Merchant, PriceBonusPct))); // This will max out price bonus. + if (chaformula < -1*(RuleI(Merchant, PriceBonusPct))) + chaformula = -1*(RuleI(Merchant, PriceBonusPct)); + } + else if (GetCHA() < 75) + { + chaformula = (75 - GetCHA())*(((RuleR(Merchant, ChaPenaltyMod))/100)*(RuleI(Merchant, PricePenaltyPct))); // Faction modifier keeps up from reaching bottom price penalty. + if (chaformula > 1*(RuleI(Merchant, PricePenaltyPct))) + chaformula = 1*(RuleI(Merchant, PricePenaltyPct)); + } + } + } + + if (reverse) + chaformula *= -1; //For selling + //Now we have, for example, 10 + chaformula /= 100; //Convert to 0.10 + chaformula += 1; //Convert to 1.10; + return chaformula; //Returns 1.10, expensive stuff! +} + +//neat idea from winter's roar, not implemented +void Client::Insight(uint32 t_id) +{ + Mob* who = entity_list.GetMob(t_id); + if (!who) + return; + if (!who->IsNPC()) + { + Message(0,"This ability can only be used on NPCs."); + return; + } + if (Dist(*who) > 200) + { + Message(0,"You must get closer to your target!"); + return; + } + if (!CheckLosFN(who)) + { + Message(0,"You must be able to see your target!"); + return; + } + char hitpoints[64]; + char resists[320]; + char dmg[64]; + memset(hitpoints,0,sizeof(hitpoints)); + memset(resists,0,sizeof(resists)); + memset(dmg,0,sizeof(dmg)); + //Start with HP blah + int avg_hp = GetLevelHP(who->GetLevel()); + int cur_hp = who->GetHP(); + if (cur_hp == avg_hp) + { + strn0cpy(hitpoints,"averagely tough",32); + } + else if (cur_hp >= avg_hp*5) + { + strn0cpy(hitpoints,"extremely tough",32); + } + else if (cur_hp >= avg_hp*4) + { + strn0cpy(hitpoints,"exceptionally tough",32); + } + else if (cur_hp >= avg_hp*3) + { + strn0cpy(hitpoints,"very tough",32); + } + else if (cur_hp >= avg_hp*2) + { + strn0cpy(hitpoints,"quite tough",32); + } + else if (cur_hp >= avg_hp*1.25) + { + strn0cpy(hitpoints,"rather tough",32); + } + else if (cur_hp > avg_hp) + { + strn0cpy(hitpoints,"slightly tough",32); + } + else if (cur_hp <= avg_hp*0.20) + { + strn0cpy(hitpoints,"extremely frail",32); + } + else if (cur_hp <= avg_hp*0.25) + { + strn0cpy(hitpoints,"exceptionally frail",32); + } + else if (cur_hp <= avg_hp*0.33) + { + strn0cpy(hitpoints,"very frail",32); + } + else if (cur_hp <= avg_hp*0.50) + { + strn0cpy(hitpoints,"quite frail",32); + } + else if (cur_hp <= avg_hp*0.75) + { + strn0cpy(hitpoints,"rather frail",32); + } + else if (cur_hp < avg_hp) + { + strn0cpy(hitpoints,"slightly frail",32); + } + + int avg_dmg = who->CastToNPC()->GetMaxDamage(who->GetLevel()); + int cur_dmg = who->CastToNPC()->GetMaxDMG(); + if (cur_dmg == avg_dmg) + { + strn0cpy(dmg,"averagely strong",32); + } + else if (cur_dmg >= avg_dmg*4) + { + strn0cpy(dmg,"extremely strong",32); + } + else if (cur_dmg >= avg_dmg*3) + { + strn0cpy(dmg,"exceptionally strong",32); + } + else if (cur_dmg >= avg_dmg*2) + { + strn0cpy(dmg,"very strong",32); + } + else if (cur_dmg >= avg_dmg*1.25) + { + strn0cpy(dmg,"quite strong",32); + } + else if (cur_dmg >= avg_dmg*1.10) + { + strn0cpy(dmg,"rather strong",32); + } + else if (cur_dmg > avg_dmg) + { + strn0cpy(dmg,"slightly strong",32); + } + else if (cur_dmg <= avg_dmg*0.20) + { + strn0cpy(dmg,"extremely weak",32); + } + else if (cur_dmg <= avg_dmg*0.25) + { + strn0cpy(dmg,"exceptionally weak",32); + } + else if (cur_dmg <= avg_dmg*0.33) + { + strn0cpy(dmg,"very weak",32); + } + else if (cur_dmg <= avg_dmg*0.50) + { + strn0cpy(dmg,"quite weak",32); + } + else if (cur_dmg <= avg_dmg*0.75) + { + strn0cpy(dmg,"rather weak",32); + } + else if (cur_dmg < avg_dmg) + { + strn0cpy(dmg,"slightly weak",32); + } + + //Resists + int res; + int i = 1; + + //MR + res = who->GetResist(i); + i++; + if (res >= 1000) + { + strcat(resists,"immune"); + } + else if (res >= 500) + { + strcat(resists,"practically immune"); + } + else if (res >= 250) + { + strcat(resists,"exceptionally resistant"); + } + else if (res >= 150) + { + strcat(resists,"very resistant"); + } + else if (res >= 100) + { + strcat(resists,"fairly resistant"); + } + else if (res >= 50) + { + strcat(resists,"averagely resistant"); + } + else if (res >= 25) + { + strcat(resists,"weakly resistant"); + } + else + { + strcat(resists,"barely resistant"); + } + strcat(resists," to magic, "); + + //FR + res = who->GetResist(i); + i++; + if (res >= 1000) + { + strcat(resists,"immune"); + } + else if (res >= 500) + { + strcat(resists,"practically immune"); + } + else if (res >= 250) + { + strcat(resists,"exceptionally resistant"); + } + else if (res >= 150) + { + strcat(resists,"very resistant"); + } + else if (res >= 100) + { + strcat(resists,"fairly resistant"); + } + else if (res >= 50) + { + strcat(resists,"averagely resistant"); + } + else if (res >= 25) + { + strcat(resists,"weakly resistant"); + } + else + { + strcat(resists,"barely resistant"); + } + strcat(resists," to fire, "); + + //CR + res = who->GetResist(i); + i++; + if (res >= 1000) + { + strcat(resists,"immune"); + } + else if (res >= 500) + { + strcat(resists,"practically immune"); + } + else if (res >= 250) + { + strcat(resists,"exceptionally resistant"); + } + else if (res >= 150) + { + strcat(resists,"very resistant"); + } + else if (res >= 100) + { + strcat(resists,"fairly resistant"); + } + else if (res >= 50) + { + strcat(resists,"averagely resistant"); + } + else if (res >= 25) + { + strcat(resists,"weakly resistant"); + } + else + { + strcat(resists,"barely resistant"); + } + strcat(resists," to cold, "); + + //PR + res = who->GetResist(i); + i++; + if (res >= 1000) + { + strcat(resists,"immune"); + } + else if (res >= 500) + { + strcat(resists,"practically immune"); + } + else if (res >= 250) + { + strcat(resists,"exceptionally resistant"); + } + else if (res >= 150) + { + strcat(resists,"very resistant"); + } + else if (res >= 100) + { + strcat(resists,"fairly resistant"); + } + else if (res >= 50) + { + strcat(resists,"averagely resistant"); + } + else if (res >= 25) + { + strcat(resists,"weakly resistant"); + } + else + { + strcat(resists,"barely resistant"); + } + strcat(resists," to poison, and "); + + //MR + res = who->GetResist(i); + i++; + if (res >= 1000) + { + strcat(resists,"immune"); + } + else if (res >= 500) + { + strcat(resists,"practically immune"); + } + else if (res >= 250) + { + strcat(resists,"exceptionally resistant"); + } + else if (res >= 150) + { + strcat(resists,"very resistant"); + } + else if (res >= 100) + { + strcat(resists,"fairly resistant"); + } + else if (res >= 50) + { + strcat(resists,"averagely resistant"); + } + else if (res >= 25) + { + strcat(resists,"weakly resistant"); + } + else + { + strcat(resists,"barely resistant"); + } + strcat(resists," to disease."); + + Message(0,"Your target is a level %i %s. It appears %s and %s for its level. It seems %s",who->GetLevel(),GetEQClassName(who->GetClass(),1),dmg,hitpoints,resists); +} + +void Client::ChangeSQLLog(const char *file) { + if(SQL_log != nullptr) { + fclose(SQL_log); + SQL_log = nullptr; + } + if(file != nullptr) { + if(strstr(file, "..") != nullptr) { + Message(13, ".. is forbibben in SQL log file names."); + return; + } + char buf[512]; + snprintf(buf, 511, "%s%s", SQL_LOG_PATH, file); + buf[511] = '\0'; + SQL_log = fopen(buf, "a"); + if(SQL_log == nullptr) { + Message(13, "Unable to open SQL log file: %s\n", strerror(errno)); + } + } +} + +void Client::LogSQL(const char *fmt, ...) { + if(SQL_log == nullptr) + return; + + va_list argptr; + va_start(argptr, fmt); + vfprintf(SQL_log, fmt, argptr ); + fputc('\n', SQL_log); + va_end(argptr); +} + +void Client::GetGroupAAs(GroupLeadershipAA_Struct *into) const { + memcpy(into, &m_pp.leader_abilities.group, sizeof(GroupLeadershipAA_Struct)); +} + +void Client::GetRaidAAs(RaidLeadershipAA_Struct *into) const { + memcpy(into, &m_pp.leader_abilities.raid, sizeof(RaidLeadershipAA_Struct)); +} + +void Client::EnteringMessages(Client* client) +{ + //server rules + char *rules; + rules = new char [4096]; + + if(database.GetVariable("Rules", rules, 4096)) + { + uint8 flag = database.GetAgreementFlag(client->AccountID()); + if(!flag) + { + client->Message(13,"You must agree to the Rules, before you can move. (type #serverrules to view the rules)"); + client->Message(13,"You must agree to the Rules, before you can move. (type #serverrules to view the rules)"); + client->Message(13,"You must agree to the Rules, before you can move. (type #serverrules to view the rules)"); + client->SendAppearancePacket(AT_Anim, ANIM_FREEZE); + } + } + safe_delete_array(rules); +} + +void Client::SendRules(Client* client) +{ + char *rules; + rules = new char [4096]; + char *ptr; + + database.GetVariable("Rules", rules, 4096); + + ptr = strtok(rules, "\n"); + while(ptr != nullptr) + { + + client->Message(0,"%s",ptr); + ptr = strtok(nullptr, "\n"); + } + safe_delete_array(rules); +} + +void Client::SetEndurance(int32 newEnd) +{ + /*Endurance can't be less than 0 or greater than max*/ + if(newEnd < 0) + newEnd = 0; + else if(newEnd > GetMaxEndurance()){ + newEnd = GetMaxEndurance(); + } + + cur_end = newEnd; + SendManaUpdatePacket(); +} + +void Client::SacrificeConfirm(Client *caster) { + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_Sacrifice, sizeof(Sacrifice_Struct)); + Sacrifice_Struct *ss = (Sacrifice_Struct*)outapp->pBuffer; + + if(!caster || PendingSacrifice) return; + + if(GetLevel() < RuleI(Spells, SacrificeMinLevel)){ + caster->Message_StringID(13, SAC_TOO_LOW); //This being is not a worthy sacrifice. + return; + } + if (GetLevel() > RuleI(Spells, SacrificeMaxLevel)) { + caster->Message_StringID(13, SAC_TOO_HIGH); + return; + } + + ss->CasterID = caster->GetID(); + ss->TargetID = GetID(); + ss->Confirm = 0; + QueuePacket(outapp); + safe_delete(outapp); + // We store the Caster's name, because when the packet comes back, it only has the victim's entityID in it, + // not the caster. + SacrificeCaster += caster->GetName(); + PendingSacrifice = true; +} + +//Essentially a special case death function +void Client::Sacrifice(Client *caster) +{ + if(GetLevel() >= RuleI(Spells, SacrificeMinLevel) && GetLevel() <= RuleI(Spells, SacrificeMaxLevel)){ + int exploss = (int)(GetLevel() * (GetLevel() / 18.0) * 12000); + if(exploss < GetEXP()){ + SetEXP(GetEXP()-exploss, GetAAXP()); + SendLogoutPackets(); + + //make our become corpse packet, and queue to ourself before OP_Death. + EQApplicationPacket app2(OP_BecomeCorpse, sizeof(BecomeCorpse_Struct)); + BecomeCorpse_Struct* bc = (BecomeCorpse_Struct*)app2.pBuffer; + bc->spawn_id = GetID(); + bc->x = GetX(); + bc->y = GetY(); + bc->z = GetZ(); + QueuePacket(&app2); + + // make death packet + EQApplicationPacket app(OP_Death, sizeof(Death_Struct)); + Death_Struct* d = (Death_Struct*)app.pBuffer; + d->spawn_id = GetID(); + d->killer_id = caster ? caster->GetID() : 0; + d->bindzoneid = GetPP().binds[0].zoneId; + d->spell_id = SPELL_UNKNOWN; + d->attack_skill = 0xe7; + d->damage = 0; + app.priority = 6; + entity_list.QueueClients(this, &app); + + BuffFadeAll(); + UnmemSpellAll(); + Group *g = GetGroup(); + if(g){ + g->MemberZoned(this); + } + Raid *r = entity_list.GetRaidByClient(this); + if(r){ + r->MemberZoned(this); + } + ClearAllProximities(); + if(RuleB(Character, LeaveCorpses)){ + Corpse *new_corpse = new Corpse(this, 0); + entity_list.AddCorpse(new_corpse, GetID()); + SetID(0); + entity_list.QueueClients(this, &app2, true); + } + Save(); + GoToDeath(); + caster->SummonItem(RuleI(Spells, SacrificeItemID)); + } + } + else{ + caster->Message_StringID(13, SAC_TOO_LOW); //This being is not a worthy sacrifice. + } +} + +void Client::SendOPTranslocateConfirm(Mob *Caster, uint16 SpellID) { + + if(!Caster || PendingTranslocate) + return; + + const SPDat_Spell_Struct &Spell = spells[SpellID]; + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_Translocate, sizeof(Translocate_Struct)); + Translocate_Struct *ts = (Translocate_Struct*)outapp->pBuffer; + + strcpy(ts->Caster, Caster->GetName()); + PendingTranslocateData.spell_id = ts->SpellID = SpellID; + + if((SpellID == 1422) || (SpellID == 1334) || (SpellID == 3243)) { + PendingTranslocateData.zone_id = ts->ZoneID = m_pp.binds[0].zoneId; + PendingTranslocateData.instance_id = m_pp.binds[0].instance_id; + PendingTranslocateData.x = ts->x = m_pp.binds[0].x; + PendingTranslocateData.y = ts->y = m_pp.binds[0].y; + PendingTranslocateData.z = ts->z = m_pp.binds[0].z; + PendingTranslocateData.heading = m_pp.binds[0].heading; + } + else { + PendingTranslocateData.zone_id = ts->ZoneID = database.GetZoneID(Spell.teleport_zone); + PendingTranslocateData.instance_id = 0; + PendingTranslocateData.y = ts->y = Spell.base[0]; + PendingTranslocateData.x = ts->x = Spell.base[1]; + PendingTranslocateData.z = ts->z = Spell.base[2]; + PendingTranslocateData.heading = 0.0; + } + + ts->unknown008 = 0; + ts->Complete = 0; + + PendingTranslocate = true; + TranslocateTime = time(nullptr); + + QueuePacket(outapp); + safe_delete(outapp); + + return; +} +void Client::SendPickPocketResponse(Mob *from, uint32 amt, int type, const Item_Struct* item){ + EQApplicationPacket* outapp = new EQApplicationPacket(OP_PickPocket, sizeof(sPickPocket_Struct)); + sPickPocket_Struct* pick_out = (sPickPocket_Struct*) outapp->pBuffer; + pick_out->coin = amt; + pick_out->from = GetID(); + pick_out->to = from->GetID(); + pick_out->myskill = GetSkill(SkillPickPockets); + + if((type >= PickPocketPlatinum) && (type <= PickPocketCopper) && (amt == 0)) + type = PickPocketFailed; + + pick_out->type = type; + if(item) + strcpy(pick_out->itemname, item->Name); + else + pick_out->itemname[0] = '\0'; + //if we do not send this packet the client will lock up and require the player to relog. + QueuePacket(outapp); + safe_delete(outapp); +} + +void Client::SetHoTT(uint32 mobid) { + EQApplicationPacket *outapp = new EQApplicationPacket(OP_TargetHoTT, sizeof(ClientTarget_Struct)); + ClientTarget_Struct *ct = (ClientTarget_Struct *) outapp->pBuffer; + ct->new_target = mobid; + QueuePacket(outapp); + safe_delete(outapp); +} + +void Client::SendPopupToClient(const char *Title, const char *Text, uint32 PopupID, uint32 Buttons, uint32 Duration) { + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_OnLevelMessage, sizeof(OnLevelMessage_Struct)); + OnLevelMessage_Struct *olms = (OnLevelMessage_Struct *) outapp->pBuffer; + + if((strlen(Title) > (sizeof(olms->Title)-1)) || + (strlen(Text) > (sizeof(olms->Text)-1))) return; + + strcpy(olms->Title, Title); + strcpy(olms->Text, Text); + + olms->Buttons = Buttons; + + if(Duration > 0) + olms->Duration = Duration * 1000; + else + olms->Duration = 0xffffffff; + + olms->PopupID = PopupID; + olms->NegativeID = 0; + + sprintf(olms->ButtonName0, "%s", "Yes"); + sprintf(olms->ButtonName1, "%s", "No"); + 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]; + + va_start(argptr, Text); + vsnprintf(buffer, sizeof(buffer), Text, argptr); + va_end(argptr); + + size_t len = strlen(buffer); + + EQApplicationPacket* app = new EQApplicationPacket(OP_OnLevelMessage, sizeof(OnLevelMessage_Struct)); + OnLevelMessage_Struct* olms=(OnLevelMessage_Struct*)app->pBuffer; + + if(strlen(Text) > (sizeof(olms->Text)-1)) + return; + + if(!target) + title_type = 0; + + switch (title_type) + { + case 1: { + char name[64] = ""; + strcpy(name, target->GetName()); + if(target->GetLastName()) { + char last_name[64] = ""; + strcpy(last_name, target->GetLastName()); + strcat(name, " "); + strcat(name, last_name); + } + strcpy(olms->Title, name); + break; + } + case 2: { + if(target->GuildID()) { + char *guild_name = (char*)guild_mgr.GetGuildName(target->GuildID()); + strcpy(olms->Title, guild_name); + } + else { + strcpy(olms->Title, "No Guild"); + } + break; + } + default: { + strcpy(olms->Title, Title); + break; + } + } + + memcpy(olms->Text, buffer, len+1); + + olms->Buttons = Buttons; + + sprintf(olms->ButtonName0, "%s", ButtonName0); + sprintf(olms->ButtonName1, "%s", ButtonName1); + + if(Duration > 0) + olms->Duration = Duration * 1000; + else + olms->Duration = 0xffffffff; + + olms->PopupID = PopupID; + olms->NegativeID = NegativeID; + + FastQueuePacket(&app); +} + +void Client::KeyRingLoad() +{ + std::string query = StringFormat("SELECT item_id FROM keyring " + "WHERE char_id = '%i' ORDER BY item_id", character_id); + auto results = database.QueryDatabase(query); + if (!results.Success()) { + std::cerr << "Error in Client::KeyRingLoad query '" << query << "' " << results.ErrorMessage() << std::endl; + return; + } + + for (auto row = results.begin(); row != results.end(); ++row) + keyring.push_back(atoi(row[0])); + +} + +void Client::KeyRingAdd(uint32 item_id) +{ + if(0==item_id) + return; + + bool found = KeyRingCheck(item_id); + if (found) + return; + + std::string query = StringFormat("INSERT INTO keyring(char_id, item_id) VALUES(%i, %i)", character_id, item_id); + auto results = database.QueryDatabase(query); + if (!results.Success()) { + std::cerr << "Error in Doors::HandleClick query '" << query << "' " << results.ErrorMessage() << std::endl; + return; + } + + Message(4,"Added to keyring."); + + keyring.push_back(item_id); +} + +bool Client::KeyRingCheck(uint32 item_id) +{ + for(std::list::iterator iter = keyring.begin(); + iter != keyring.end(); + ++iter) + { + if(*iter == item_id) + return true; + } + return false; +} + +void Client::KeyRingList() +{ + Message(4,"Keys on Keyring:"); + const Item_Struct *item = 0; + for(std::list::iterator iter = keyring.begin(); + iter != keyring.end(); + ++iter) + { + if ((item = database.GetItem(*iter))!=nullptr) { + Message(4,item->Name); + } + } +} + +bool Client::IsDiscovered(uint32 itemid) { + + std::string query = StringFormat("SELECT count(*) FROM discovered_items WHERE item_id = '%lu'", itemid); + auto results = database.QueryDatabase(query); + if (!results.Success()) { + std::cerr << "Error in IsDiscovered query '" << query << "' " << results.ErrorMessage() << std::endl; + return false; + } + + auto row = results.begin(); + if (!atoi(row[0])) + return false; + + return true; +} + +void Client::DiscoverItem(uint32 itemid) { + + std::string query = StringFormat("INSERT INTO discovered_items " + "SET item_id = %lu, char_name = '%s', " + "discovered_date = UNIX_TIMESTAMP(), account_status = %i", + itemid, GetName(), Admin()); + auto results = database.QueryDatabase(query); + + parse->EventPlayer(EVENT_DISCOVER_ITEM, this, "", itemid); +} + +void Client::UpdateLFP() { + + Group *g = GetGroup(); + + if(g && !g->IsLeader(this)) { + database.SetLFP(CharacterID(), false); + worldserver.StopLFP(CharacterID()); + LFP = false; + return; + } + + GroupLFPMemberEntry LFPMembers[MAX_GROUP_MEMBERS]; + + for(unsigned int i=0; iGetZoneID(); + + if(g) { + // Fill the LFPMembers array with the rest of the group members, excluding ourself + // We don't fill in the class, level or zone, because we may not be able to determine + // them if the other group members are not in this zone. World will fill in this information + // for us, if it can. + int NextFreeSlot = 1; + for(unsigned int i = 0; i < MAX_GROUP_MEMBERS; i++) { + if((g->membername[i][0] != '\0') && strcasecmp(g->membername[i], LFPMembers[0].Name)) + strcpy(LFPMembers[NextFreeSlot++].Name, g->membername[i]); + } + } + worldserver.UpdateLFP(CharacterID(), LFPMembers); +} + +bool Client::GroupFollow(Client* inviter) { + + if (inviter) + { + isgrouped = true; + Raid* raid = entity_list.GetRaidByClient(inviter); + Raid* iraid = entity_list.GetRaidByClient(this); + + //inviter has a raid don't do group stuff instead do raid stuff! + if (raid) + { + // Suspend the merc while in a raid (maybe a rule could be added for this) + if (GetMerc()) + GetMerc()->Suspend(); + + uint32 groupToUse = 0xFFFFFFFF; + for (int x = 0; x < MAX_RAID_MEMBERS; x++) + { + if (raid->members[x].member) + { + //this assumes the inviter is in the zone + if (raid->members[x].member == inviter){ + groupToUse = raid->members[x].GroupNumber; + break; + } + } + } + if (iraid == raid) + { + //both in same raid + uint32 ngid = raid->GetGroup(inviter->GetName()); + if (raid->GroupCount(ngid) < 6) + { + raid->MoveMember(GetName(), ngid); + raid->SendGroupDisband(this); + //raid->SendRaidGroupAdd(GetName(), ngid); + //raid->SendGroupUpdate(this); + raid->GroupUpdate(ngid); //break + } + return false; + } + if (raid->RaidCount() < MAX_RAID_MEMBERS) + { + if (raid->GroupCount(groupToUse) < 6) + { + raid->SendRaidCreate(this); + raid->SendMakeLeaderPacketTo(raid->leadername, this); + raid->AddMember(this, groupToUse); + raid->SendBulkRaid(this); + //raid->SendRaidGroupAdd(GetName(), groupToUse); + //raid->SendGroupUpdate(this); + raid->GroupUpdate(groupToUse); //break + if (raid->IsLocked()) + { + raid->SendRaidLockTo(this); + } + return false; + } + else + { + raid->SendRaidCreate(this); + raid->SendMakeLeaderPacketTo(raid->leadername, this); + raid->AddMember(this); + raid->SendBulkRaid(this); + if (raid->IsLocked()) + { + raid->SendRaidLockTo(this); + } + return false; + } + } + } + + Group* group = entity_list.GetGroupByClient(inviter); + + if (!group) + { + //Make new group + group = new Group(inviter); + + if (!group) + { + return false; + } + + entity_list.AddGroup(group); + + if (group->GetID() == 0) + { + Message(13, "Unable to get new group id. Cannot create group."); + inviter->Message(13, "Unable to get new group id. Cannot create group."); + return false; + } + + //now we have a group id, can set inviter's id + database.SetGroupID(inviter->GetName(), group->GetID(), inviter->CharacterID(), false); + database.SetGroupLeaderName(group->GetID(), inviter->GetName()); + group->UpdateGroupAAs(); + + //Invite the inviter into the group first.....dont ask + if (inviter->GetClientVersion() < EQClientSoD) + { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_GroupUpdate, sizeof(GroupJoin_Struct)); + GroupJoin_Struct* outgj = (GroupJoin_Struct*)outapp->pBuffer; + strcpy(outgj->membername, inviter->GetName()); + strcpy(outgj->yourname, inviter->GetName()); + outgj->action = groupActInviteInitial; // 'You have formed the group'. + group->GetGroupAAs(&outgj->leader_aas); + inviter->QueuePacket(outapp); + safe_delete(outapp); + } + else + { + // SoD and later + inviter->SendGroupCreatePacket(); + inviter->SendGroupLeaderChangePacket(inviter->GetName()); + inviter->SendGroupJoinAcknowledge(); + } + + } + + if (!group) + { + return false; + } + + // Remove merc from old group before adding client to the new one + if (GetMerc() && GetMerc()->HasGroup()) + { + GetMerc()->RemoveMercFromGroup(GetMerc(), GetMerc()->GetGroup()); + } + + if (!group->AddMember(this)) + { + // If failed to add client to new group, regroup with merc + if (GetMerc()) + { + GetMerc()->MercJoinClientGroup(); + } + else + { + isgrouped = false; + } + return false; + } + + if (GetClientVersion() >= EQClientSoD) + { + SendGroupJoinAcknowledge(); + } + + // Temporary hack for SoD, as things seem to work quite differently + if (inviter->IsClient() && inviter->GetClientVersion() >= EQClientSoD) + { + database.RefreshGroupFromDB(inviter); + } + + // Add the merc back into the new group if possible + if (GetMerc()) + { + GetMerc()->MercJoinClientGroup(); + } + + if (inviter->IsLFP()) + { + // If the player who invited us to a group is LFP, have them update world now that we have joined their group. + inviter->UpdateLFP(); + } + + database.RefreshGroupFromDB(this); + group->SendHPPacketsTo(this); + //send updates to clients out of zone... + group->SendGroupJoinOOZ(this); + return true; + } + return false; +} + +uint16 Client::GetPrimarySkillValue() +{ + SkillUseTypes skill = HIGHEST_SKILL; //because nullptr == 0, which is 1H Slashing, & we want it to return 0 from GetSkill + bool equiped = m_inv.GetItem(MainPrimary); + + if (!equiped) + skill = SkillHandtoHand; + + else { + + uint8 type = m_inv.GetItem(MainPrimary)->GetItem()->ItemType; //is this the best way to do this? + + switch (type) + { + case ItemType1HSlash: // 1H Slashing + { + skill = Skill1HSlashing; + break; + } + case ItemType2HSlash: // 2H Slashing + { + skill = Skill2HSlashing; + break; + } + case ItemType1HPiercing: // Piercing + { + skill = Skill1HPiercing; + break; + } + case ItemType1HBlunt: // 1H Blunt + { + skill = Skill1HBlunt; + break; + } + case ItemType2HBlunt: // 2H Blunt + { + skill = Skill2HBlunt; + break; + } + case ItemType2HPiercing: // 2H Piercing + { + skill = Skill1HPiercing; // change to Skill2HPiercing once activated + break; + } + case ItemTypeMartial: // Hand to Hand + { + skill = SkillHandtoHand; + break; + } + default: // All other types default to Hand to Hand + { + skill = SkillHandtoHand; + break; + } + } + } + + return GetSkill(skill); +} + +uint32 Client::GetTotalATK() +{ + uint32 AttackRating = 0; + uint32 WornCap = itembonuses.ATK; + + if(IsClient()) { + AttackRating = ((WornCap * 1.342) + (GetSkill(SkillOffense) * 1.345) + ((GetSTR() - 66) * 0.9) + (GetPrimarySkillValue() * 2.69)); + AttackRating += aabonuses.ATK + GroupLeadershipAAOffenseEnhancement(); + + if (AttackRating < 10) + AttackRating = 10; + } + else + AttackRating = GetATK(); + + AttackRating += spellbonuses.ATK; + + return AttackRating; +} + +uint32 Client::GetATKRating() +{ + uint32 AttackRating = 0; + if(IsClient()) { + AttackRating = (GetSkill(SkillOffense) * 1.345) + ((GetSTR() - 66) * 0.9) + (GetPrimarySkillValue() * 2.69); + + if (AttackRating < 10) + AttackRating = 10; + } + return AttackRating; +} + +void Client::VoiceMacroReceived(uint32 Type, char *Target, uint32 MacroNumber) { + + uint32 GroupOrRaidID = 0; + + switch(Type) { + + case VoiceMacroGroup: { + + Group* g = GetGroup(); + + if(g) + GroupOrRaidID = g->GetID(); + else + return; + + break; + } + + case VoiceMacroRaid: { + + Raid* r = GetRaid(); + + if(r) + GroupOrRaidID = r->GetID(); + else + return; + + break; + } + } + + if(!worldserver.SendVoiceMacro(this, Type, Target, MacroNumber, GroupOrRaidID)) + Message(0, "Error: World server disconnected"); +} + +void Client::ClearGroupAAs() { + for(unsigned int i = 0; i < MAX_GROUP_LEADERSHIP_AA_ARRAY; i++) + m_pp.leader_abilities.ranks[i] = 0; + + m_pp.group_leadership_points = 0; + m_pp.raid_leadership_points = 0; + m_pp.group_leadership_exp = 0; + m_pp.raid_leadership_exp = 0; + + Save(); + database.SaveCharacterLeadershipAA(this->CharacterID(), &m_pp); +} + +void Client::UpdateGroupAAs(int32 points, uint32 type) { + switch(type) { + case 0: { m_pp.group_leadership_points += points; break; } + case 1: { m_pp.raid_leadership_points += points; break; } + } + SendLeadershipEXPUpdate(); +} + +bool Client::IsLeadershipEXPOn() { + + if(!m_pp.leadAAActive) + return false; + + Group *g = GetGroup(); + + if (g && g->IsLeader(this) && g->GroupCount() > 2) + return true; + + Raid *r = GetRaid(); + + if (!r) + return false; + + // raid leaders can only gain raid AA XP + if (r->IsLeader(this)) { + if (r->RaidCount() > 17) + return true; + else + return false; + } + + uint32 gid = r->GetGroup(this); + + if (gid > 11) // not in a group + return false; + + if (r->IsGroupLeader(GetName()) && r->GroupCount(gid) > 2) + return true; + + return false; + +} + +int Client::GetAggroCount() { + return AggroCount; +} + +void Client::IncrementAggroCount() { + + // This method is called when a client is added to a mob's hate list. It turns the clients aggro flag on so + // rest state regen is stopped, and for SoF, it sends the opcode to show the crossed swords in-combat indicator. + // + // + AggroCount++; + + if(!RuleI(Character, RestRegenPercent)) + return; + + // If we already had aggro before this method was called, the combat indicator should already be up for SoF clients, + // so we don't need to send it again. + // + if(AggroCount > 1) + return; + + // Pause the rest timer + if (AggroCount == 1) + SavedRaidRestTimer = rest_timer.GetRemainingTime(); + + if(GetClientVersion() >= EQClientSoF) { + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_RestState, 1); + char *Buffer = (char *)outapp->pBuffer; + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0x01); + QueuePacket(outapp); + safe_delete(outapp); + } + +} + +void Client::DecrementAggroCount() { + + // This should be called when a client is removed from a mob's hate list (it dies or is memblurred). + // It checks whether any other mob is aggro on the player, and if not, starts the rest timer. + // For SoF, the opcode to start the rest state countdown timer in the UI is sent. + // + + // If we didn't have aggro before, this method should not have been called. + if(!AggroCount) + return; + + AggroCount--; + + if(!RuleI(Character, RestRegenPercent)) + return; + + // Something else is still aggro on us, can't rest yet. + if(AggroCount) return; + + uint32 time_until_rest; + if (GetEngagedRaidTarget()) { + time_until_rest = RuleI(Character, RestRegenRaidTimeToActivate) * 1000; + SetEngagedRaidTarget(false); + } else { + if (SavedRaidRestTimer > (RuleI(Character, RestRegenTimeToActivate) * 1000)) { + time_until_rest = SavedRaidRestTimer; + SavedRaidRestTimer = 0; + } else { + time_until_rest = RuleI(Character, RestRegenTimeToActivate) * 1000; + } + } + + rest_timer.Start(time_until_rest); + + if(GetClientVersion() >= EQClientSoF) { + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_RestState, 5); + char *Buffer = (char *)outapp->pBuffer; + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0x00); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, (uint32)(time_until_rest / 1000)); + QueuePacket(outapp); + safe_delete(outapp); + } +} + +void Client::SendPVPStats() +{ + // This sends the data to the client to populate the PVP Stats Window. + // + // When the PVP Stats window is opened, no opcode is sent. Therefore this method should be called + // from Client::CompleteConnect, and also when the player makes a PVP kill. + // + EQApplicationPacket *outapp = new EQApplicationPacket(OP_PVPStats, sizeof(PVPStats_Struct)); + PVPStats_Struct *pvps = (PVPStats_Struct *)outapp->pBuffer; + + pvps->Kills = m_pp.PVPKills; + pvps->Deaths = m_pp.PVPDeaths; + pvps->PVPPointsAvailable = m_pp.PVPCurrentPoints; + pvps->TotalPVPPoints = m_pp.PVPCareerPoints; + pvps->BestKillStreak = m_pp.PVPBestKillStreak; + pvps->WorstDeathStreak = m_pp.PVPWorstDeathStreak; + pvps->CurrentKillStreak = m_pp.PVPCurrentKillStreak; + + // TODO: Record and send other PVP Stats + + QueuePacket(outapp); + safe_delete(outapp); +} + +void Client::SendCrystalCounts() +{ + EQApplicationPacket *outapp = new EQApplicationPacket(OP_CrystalCountUpdate, sizeof(CrystalCountUpdate_Struct)); + CrystalCountUpdate_Struct *ccus = (CrystalCountUpdate_Struct *)outapp->pBuffer; + + ccus->CurrentRadiantCrystals = GetRadiantCrystals(); + ccus->CurrentEbonCrystals = GetEbonCrystals(); + ccus->CareerRadiantCrystals = m_pp.careerRadCrystals; + ccus->CareerEbonCrystals = m_pp.careerEbonCrystals; + + + QueuePacket(outapp); + safe_delete(outapp); +} + +void Client::SendDisciplineTimers() +{ + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_DisciplineTimer, sizeof(DisciplineTimer_Struct)); + DisciplineTimer_Struct *dts = (DisciplineTimer_Struct *)outapp->pBuffer; + + for(unsigned int i = 0; i < MAX_DISCIPLINE_TIMERS; ++i) + { + uint32 RemainingTime = p_timers.GetRemainingTime(pTimerDisciplineReuseStart + i); + + if(RemainingTime > 0) + { + dts->TimerID = i; + dts->Duration = RemainingTime; + QueuePacket(outapp); + } + } + + safe_delete(outapp); +} + +void Client::SendRespawnBinds() +{ + // This sends the data to the client to populate the Respawn from Death Window. + // + // This should be sent after OP_Death for SoF clients + // Client will respond with a 4 byte packet that includes the number of the selection made + // + + //If no options have been given, default to Bind + Rez + if (respawn_options.empty()) + { + BindStruct* b = &m_pp.binds[0]; + RespawnOption opt; + opt.name = "Bind Location"; + opt.zone_id = b->zoneId; + opt.instance_id = b->instance_id; + opt.x = b->x; + opt.y = b->y; + opt.z = b->z; + opt.heading = b->heading; + respawn_options.push_front(opt); + } + //Rez is always added at the end + RespawnOption rez; + rez.name = "Resurrect"; + rez.zone_id = zone->GetZoneID(); + rez.instance_id = zone->GetInstanceID(); + rez.x = GetX(); + rez.y = GetY(); + rez.z = GetZ(); + rez.heading = GetHeading(); + respawn_options.push_back(rez); + + int num_options = respawn_options.size(); + uint32 PacketLength = 17 + (26 * num_options); //Header size + per-option invariant size + + std::list::iterator itr; + RespawnOption* opt; + + //Find string size for each option + for (itr = respawn_options.begin(); itr != respawn_options.end(); ++itr) + { + opt = &(*itr); + PacketLength += opt->name.size() + 1; //+1 for cstring + } + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_RespawnWindow, PacketLength); + char* buffer = (char*)outapp->pBuffer; + + //Packet header + VARSTRUCT_ENCODE_TYPE(uint32, buffer, initial_respawn_selection); //initial selection (from 0) + VARSTRUCT_ENCODE_TYPE(uint32, buffer, RuleI(Character, RespawnFromHoverTimer) * 1000); + VARSTRUCT_ENCODE_TYPE(uint32, buffer, 0); //unknown + VARSTRUCT_ENCODE_TYPE(uint32, buffer, num_options); //number of options to display + + //Individual options + int count = 0; + for (itr = respawn_options.begin(); itr != respawn_options.end(); ++itr) + { + opt = &(*itr); + VARSTRUCT_ENCODE_TYPE(uint32, buffer, count++); //option num (from 0) + VARSTRUCT_ENCODE_TYPE(uint32, buffer, opt->zone_id); + VARSTRUCT_ENCODE_TYPE(float, buffer, opt->x); + VARSTRUCT_ENCODE_TYPE(float, buffer, opt->y); + VARSTRUCT_ENCODE_TYPE(float, buffer, opt->z); + VARSTRUCT_ENCODE_TYPE(float, buffer, opt->heading); + VARSTRUCT_ENCODE_STRING(buffer, opt->name.c_str()); + VARSTRUCT_ENCODE_TYPE(uint8, buffer, (count == num_options)); //is this one Rez (the last option)? + } + + QueuePacket(outapp); + safe_delete(outapp); + return; +} + +void Client::HandleLDoNOpen(NPC *target) +{ + if(target) + { + if(target->GetClass() != LDON_TREASURE) + { + LogFile->write(EQEMuLog::Debug, "%s tried to open %s but %s was not a treasure chest.", + GetName(), target->GetName(), target->GetName()); + return; + } + + if(DistNoRootNoZ(*target) > RuleI(Adventure, LDoNTrapDistanceUse)) + { + LogFile->write(EQEMuLog::Debug, "%s tried to open %s but %s was out of range", + GetName(), target->GetName(), target->GetName()); + Message(13, "Treasure chest out of range."); + return; + } + + if(target->IsLDoNTrapped()) + { + if(target->GetLDoNTrapSpellID() != 0) + { + Message_StringID(13, LDON_ACCIDENT_SETOFF2); + target->SpellFinished(target->GetLDoNTrapSpellID(), this, 10, 0, -1, spells[target->GetLDoNTrapSpellID()].ResistDiff); + target->SetLDoNTrapSpellID(0); + target->SetLDoNTrapped(false); + target->SetLDoNTrapDetected(false); + } + else + { + target->SetLDoNTrapSpellID(0); + target->SetLDoNTrapped(false); + target->SetLDoNTrapDetected(false); + } + } + + if(target->IsLDoNLocked()) + { + Message_StringID(MT_Skills, LDON_STILL_LOCKED, target->GetCleanName()); + return; + } + else + { + target->AddToHateList(this, 0, 500000, false, false, false); + if(target->GetLDoNTrapType() != 0) + { + if(GetRaid()) + { + GetRaid()->SplitExp(target->GetLevel()*target->GetLevel()*2625/10, target); + } + else if(GetGroup()) + { + GetGroup()->SplitExp(target->GetLevel()*target->GetLevel()*2625/10, target); + } + else + { + AddEXP(target->GetLevel()*target->GetLevel()*2625/10, GetLevelCon(target->GetLevel())); + } + } + target->Death(this, 1, SPELL_UNKNOWN, SkillHandtoHand); + } + } +} + +void Client::HandleLDoNSenseTraps(NPC *target, uint16 skill, uint8 type) +{ + if(target && target->GetClass() == LDON_TREASURE) + { + if(target->IsLDoNTrapped()) + { + if((target->GetLDoNTrapType() == LDoNTypeCursed || target->GetLDoNTrapType() == LDoNTypeMagical) && type != target->GetLDoNTrapType()) + { + Message_StringID(MT_Skills, LDON_CANT_DETERMINE_TRAP, target->GetCleanName()); + return; + } + + if(target->IsLDoNTrapDetected()) + { + Message_StringID(MT_Skills, LDON_CERTAIN_TRAP, target->GetCleanName()); + } + else + { + int check = LDoNChest_SkillCheck(target, skill); + switch(check) + { + case -1: + case 0: + Message_StringID(MT_Skills, LDON_DONT_KNOW_TRAPPED, target->GetCleanName()); + break; + case 1: + Message_StringID(MT_Skills, LDON_CERTAIN_TRAP, target->GetCleanName()); + target->SetLDoNTrapDetected(true); + break; + default: + break; + } + } + } + else + { + Message_StringID(MT_Skills, LDON_CERTAIN_NOT_TRAP, target->GetCleanName()); + } + } +} + +void Client::HandleLDoNDisarm(NPC *target, uint16 skill, uint8 type) +{ + if(target) + { + if(target->GetClass() == LDON_TREASURE) + { + if(!target->IsLDoNTrapped()) + { + Message_StringID(MT_Skills, LDON_WAS_NOT_TRAPPED, target->GetCleanName()); + return; + } + + if((target->GetLDoNTrapType() == LDoNTypeCursed || target->GetLDoNTrapType() == LDoNTypeMagical) && type != target->GetLDoNTrapType()) + { + Message_StringID(MT_Skills, LDON_HAVE_NOT_DISARMED, target->GetCleanName()); + return; + } + + int check = 0; + if(target->IsLDoNTrapDetected()) + { + check = LDoNChest_SkillCheck(target, skill); + } + else + { + check = LDoNChest_SkillCheck(target, skill*33/100); + } + switch(check) + { + case 1: + target->SetLDoNTrapDetected(false); + target->SetLDoNTrapped(false); + target->SetLDoNTrapSpellID(0); + Message_StringID(MT_Skills, LDON_HAVE_DISARMED, target->GetCleanName()); + break; + case 0: + Message_StringID(MT_Skills, LDON_HAVE_NOT_DISARMED, target->GetCleanName()); + break; + case -1: + Message_StringID(13, LDON_ACCIDENT_SETOFF2); + target->SpellFinished(target->GetLDoNTrapSpellID(), this, 10, 0, -1, spells[target->GetLDoNTrapSpellID()].ResistDiff); + target->SetLDoNTrapSpellID(0); + target->SetLDoNTrapped(false); + target->SetLDoNTrapDetected(false); + break; + } + } + } +} + +void Client::HandleLDoNPickLock(NPC *target, uint16 skill, uint8 type) +{ + if(target) + { + if(target->GetClass() == LDON_TREASURE) + { + if(target->IsLDoNTrapped()) + { + Message_StringID(13, LDON_ACCIDENT_SETOFF2); + target->SpellFinished(target->GetLDoNTrapSpellID(), this, 10, 0, -1, spells[target->GetLDoNTrapSpellID()].ResistDiff); + target->SetLDoNTrapSpellID(0); + target->SetLDoNTrapped(false); + target->SetLDoNTrapDetected(false); + } + + if(!target->IsLDoNLocked()) + { + Message_StringID(MT_Skills, LDON_WAS_NOT_LOCKED, target->GetCleanName()); + return; + } + + if((target->GetLDoNTrapType() == LDoNTypeCursed || target->GetLDoNTrapType() == LDoNTypeMagical) && type != target->GetLDoNTrapType()) + { + Message(MT_Skills, "You cannot unlock %s with this skill.", target->GetCleanName()); + return; + } + + int check = LDoNChest_SkillCheck(target, skill); + + switch(check) + { + case 0: + case -1: + Message_StringID(MT_Skills, LDON_PICKLOCK_FAILURE, target->GetCleanName()); + break; + case 1: + target->SetLDoNLocked(false); + Message_StringID(MT_Skills, LDON_PICKLOCK_SUCCESS, target->GetCleanName()); + break; + } + } + } +} + +int Client::LDoNChest_SkillCheck(NPC *target, int skill) +{ + if(!target) + return -1; + + int chest_difficulty = target->GetLDoNLockedSkill() == 0 ? (target->GetLevel() * 5) : target->GetLDoNLockedSkill(); + float base_difficulty = RuleR(Adventure, LDoNBaseTrapDifficulty); + + if(chest_difficulty == 0) + chest_difficulty = 5; + + float chance = ((100.0f - base_difficulty) * ((float)skill / (float)chest_difficulty)); + + if(chance > (100.0f - base_difficulty)) + { + chance = 100.0f - base_difficulty; + } + + float d100 = (float)zone->random.Real(0, 100); + + if(d100 <= chance) + return 1; + else + { + if(d100 > (chance + RuleR(Adventure, LDoNCriticalFailTrapThreshold))) + return -1; + } + + return 0; +} + +void Client::SummonAndRezzAllCorpses() +{ + PendingRezzXP = -1; + + ServerPacket *Pack = new ServerPacket(ServerOP_DepopAllPlayersCorpses, sizeof(ServerDepopAllPlayersCorpses_Struct)); + + ServerDepopAllPlayersCorpses_Struct *sdapcs = (ServerDepopAllPlayersCorpses_Struct*)Pack->pBuffer; + + sdapcs->CharacterID = CharacterID(); + sdapcs->ZoneID = zone->GetZoneID(); + sdapcs->InstanceID = zone->GetInstanceID(); + + worldserver.SendPacket(Pack); + + safe_delete(Pack); + + entity_list.RemoveAllCorpsesByCharID(CharacterID()); + + int CorpseCount = database.SummonAllCharacterCorpses(CharacterID(), zone->GetZoneID(), zone->GetInstanceID(), GetPosition()); + if(CorpseCount <= 0) + { + Message(clientMessageYellow, "You have no corpses to summnon."); + return; + } + + int RezzExp = entity_list.RezzAllCorpsesByCharID(CharacterID()); + + if(RezzExp > 0) + SetEXP(GetEXP() + RezzExp, GetAAXP(), true); + + Message(clientMessageYellow, "All your corpses have been summoned to your feet and have received a 100% resurrection."); +} + +void Client::SummonAllCorpses(const xyz_heading& position) +{ + auto summonLocation = position; + if(position.isOrigin() && position.m_Heading == 0.0f) + summonLocation = GetPosition(); + + ServerPacket *Pack = new ServerPacket(ServerOP_DepopAllPlayersCorpses, sizeof(ServerDepopAllPlayersCorpses_Struct)); + + ServerDepopAllPlayersCorpses_Struct *sdapcs = (ServerDepopAllPlayersCorpses_Struct*)Pack->pBuffer; + + sdapcs->CharacterID = CharacterID(); + sdapcs->ZoneID = zone->GetZoneID(); + sdapcs->InstanceID = zone->GetInstanceID(); + + worldserver.SendPacket(Pack); + + safe_delete(Pack); + + entity_list.RemoveAllCorpsesByCharID(CharacterID()); + + database.SummonAllCharacterCorpses(CharacterID(), zone->GetZoneID(), zone->GetInstanceID(), summonLocation); +} + +void Client::DepopAllCorpses() +{ + ServerPacket *Pack = new ServerPacket(ServerOP_DepopAllPlayersCorpses, sizeof(ServerDepopAllPlayersCorpses_Struct)); + + ServerDepopAllPlayersCorpses_Struct *sdapcs = (ServerDepopAllPlayersCorpses_Struct*)Pack->pBuffer; + + sdapcs->CharacterID = CharacterID(); + sdapcs->ZoneID = zone->GetZoneID(); + sdapcs->InstanceID = zone->GetInstanceID(); + + worldserver.SendPacket(Pack); + + safe_delete(Pack); + + entity_list.RemoveAllCorpsesByCharID(CharacterID()); +} + +void Client::DepopPlayerCorpse(uint32 dbid) +{ + ServerPacket *Pack = new ServerPacket(ServerOP_DepopPlayerCorpse, sizeof(ServerDepopPlayerCorpse_Struct)); + + ServerDepopPlayerCorpse_Struct *sdpcs = (ServerDepopPlayerCorpse_Struct*)Pack->pBuffer; + + sdpcs->DBID = dbid; + sdpcs->ZoneID = zone->GetZoneID(); + sdpcs->InstanceID = zone->GetInstanceID(); + + worldserver.SendPacket(Pack); + + safe_delete(Pack); + + entity_list.RemoveCorpseByDBID(dbid); +} + +void Client::BuryPlayerCorpses() +{ + database.BuryAllCharacterCorpses(CharacterID()); +} + +void Client::NotifyNewTitlesAvailable() +{ + EQApplicationPacket *outapp = new EQApplicationPacket(OP_NewTitlesAvailable, 0); + + QueuePacket(outapp); + + safe_delete(outapp); + +} + +void Client::SetStartZone(uint32 zoneid, float x, float y, float z) +{ + // setting city to zero allows the player to use /setstartcity to set the city themselves + if(zoneid == 0) { + m_pp.binds[4].zoneId = 0; + this->Message(15,"Your starting city has been reset. Use /setstartcity to choose a new one"); + return; + } + + // check to make sure the zone is valid + const char *target_zone_name = database.GetZoneName(zoneid); + if(target_zone_name == nullptr) + return; + + m_pp.binds[4].zoneId = zoneid; + if(zone->GetInstanceID() != 0 && zone->IsInstancePersistent()) { + m_pp.binds[4].instance_id = zone->GetInstanceID(); + } + + if (x == 0 && y == 0 && z ==0) + database.GetSafePoints(m_pp.binds[4].zoneId, 0, &m_pp.binds[4].x, &m_pp.binds[4].y, &m_pp.binds[4].z); + else { + m_pp.binds[4].x = x; + m_pp.binds[4].y = y; + m_pp.binds[4].z = z; + } +} + +uint32 Client::GetStartZone() +{ + return m_pp.binds[4].zoneId; +} + +void Client::ShowSkillsWindow() +{ + const char *WindowTitle = "Skills"; + std::string WindowText; + // using a map for easy alphabetizing of the skills list + std::map Skills; + std::map::iterator it; + + // this list of names must keep the same order as that in common/skills.h + const char* SkillName[] = {"1H Blunt","1H Slashing","2H Blunt","2H Slashing","Abjuration","Alteration","Apply Poison","Archery", + "Backstab","Bind Wound","Bash","Block","Brass Instruments","Channeling","Conjuration","Defense","Disarm","Disarm Traps","Divination", + "Dodge","Double Attack","Dragon Punch","Dual Wield","Eagle Strike","Evocation","Feign Death","Flying Kick","Forage","Hand to Hand", + "Hide","Kick","Meditate","Mend","Offense","Parry","Pick Lock","Piercing","Ripost","Round Kick","Safe Fall","Sense Heading", + "Singing","Sneak","Specialize Abjuration","Specialize Alteration","Specialize Conjuration","Specialize Divination","Specialize Evocation","Pick Pockets", + "Stringed Instruments","Swimming","Throwing","Tiger Claw","Tracking","Wind Instruments","Fishing","Make Poison","Tinkering","Research", + "Alchemy","Baking","Tailoring","Sense Traps","Blacksmithing","Fletching","Brewing","Alcohol Tolerance","Begging","Jewelry Making", + "Pottery","Percussion Instruments","Intimidation","Berserking","Taunt","Frenzy"}; + for(int i = 0; i <= (int)HIGHEST_SKILL; i++) + Skills[SkillName[i]] = (SkillUseTypes)i; + + // print out all available skills + for(it = Skills.begin(); it != Skills.end(); ++it) { + if(GetSkill(it->second) > 0 || MaxSkill(it->second) > 0) { + WindowText += it->first; + // line up the values + for (int j = 0; j < EmuConstants::ITEM_COMMON_SIZE; j++) + WindowText += " "; + WindowText += itoa(this->GetSkill(it->second)); + if (MaxSkill(it->second) > 0) { + WindowText += "/"; + WindowText += itoa(this->GetMaxSkillAfterSpecializationRules(it->second,this->MaxSkill(it->second))); + } + WindowText += "
"; + } + } + this->SendPopupToClient(WindowTitle, WindowText.c_str()); +} + + +void Client::SetShadowStepExemption(bool v) +{ + if(v == true) + { + uint32 cur_time = Timer::GetCurrentTime(); + if((cur_time - m_TimeSinceLastPositionCheck) > 1000) + { + float speed = (m_DistanceSinceLastPositionCheck * 100) / (float)(cur_time - m_TimeSinceLastPositionCheck); + float runs = GetRunspeed(); + if(speed > (runs * RuleR(Zone, MQWarpDetectionDistanceFactor))) + { + printf("%s %i moving too fast! moved: %.2f in %ims, speed %.2f\n", __FILE__, __LINE__, + m_DistanceSinceLastPositionCheck, (cur_time - m_TimeSinceLastPositionCheck), speed); + if(!GetGMSpeed() && (runs >= GetBaseRunspeed() || (speed > (GetBaseRunspeed() * RuleR(Zone, MQWarpDetectionDistanceFactor))))) + { + if(IsShadowStepExempted()) + { + if(m_DistanceSinceLastPositionCheck > 800) + { + CheatDetected(MQWarpShadowStep, GetX(), GetY(), GetZ()); + } + } + 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) + { + CheatDetected(MQWarpKnockBack, GetX(), GetY(), GetZ()); + } + } + else if(!IsPortExempted()) + { + if(!IsMQExemptedArea(zone->GetZoneID(), GetX(), GetY(), GetZ())) + { + if(speed > (runs * 2 * RuleR(Zone, MQWarpDetectionDistanceFactor))) + { + CheatDetected(MQWarp, GetX(), GetY(), GetZ()); + m_TimeSinceLastPositionCheck = cur_time; + m_DistanceSinceLastPositionCheck = 0.0f; + //Death(this, 10000000, SPELL_UNKNOWN, _1H_BLUNT); + } + else + { + CheatDetected(MQWarpLight, GetX(), GetY(), GetZ()); + } + } + } + } + } + } + m_TimeSinceLastPositionCheck = cur_time; + m_DistanceSinceLastPositionCheck = 0.0f; + } + m_ShadowStepExemption = v; +} + +void Client::SetKnockBackExemption(bool v) +{ + if(v == true) + { + uint32 cur_time = Timer::GetCurrentTime(); + if((cur_time - m_TimeSinceLastPositionCheck) > 1000) + { + float speed = (m_DistanceSinceLastPositionCheck * 100) / (float)(cur_time - m_TimeSinceLastPositionCheck); + float runs = GetRunspeed(); + if(speed > (runs * RuleR(Zone, MQWarpDetectionDistanceFactor))) + { + if(!GetGMSpeed() && (runs >= GetBaseRunspeed() || (speed > (GetBaseRunspeed() * RuleR(Zone, MQWarpDetectionDistanceFactor))))) + { + printf("%s %i moving too fast! moved: %.2f in %ims, speed %.2f\n", __FILE__, __LINE__, + m_DistanceSinceLastPositionCheck, (cur_time - m_TimeSinceLastPositionCheck), speed); + if(IsShadowStepExempted()) + { + if(m_DistanceSinceLastPositionCheck > 800) + { + CheatDetected(MQWarpShadowStep, GetX(), GetY(), GetZ()); + } + } + 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) + { + CheatDetected(MQWarpKnockBack, GetX(), GetY(), GetZ()); + } + } + else if(!IsPortExempted()) + { + if(!IsMQExemptedArea(zone->GetZoneID(), GetX(), GetY(), GetZ())) + { + if(speed > (runs * 2 * RuleR(Zone, MQWarpDetectionDistanceFactor))) + { + m_TimeSinceLastPositionCheck = cur_time; + m_DistanceSinceLastPositionCheck = 0.0f; + CheatDetected(MQWarp, GetX(), GetY(), GetZ()); + //Death(this, 10000000, SPELL_UNKNOWN, _1H_BLUNT); + } + else + { + CheatDetected(MQWarpLight, GetX(), GetY(), GetZ()); + } + } + } + } + } + } + m_TimeSinceLastPositionCheck = cur_time; + m_DistanceSinceLastPositionCheck = 0.0f; + } + m_KnockBackExemption = v; +} + +void Client::SetPortExemption(bool v) +{ + if(v == true) + { + uint32 cur_time = Timer::GetCurrentTime(); + if((cur_time - m_TimeSinceLastPositionCheck) > 1000) + { + float speed = (m_DistanceSinceLastPositionCheck * 100) / (float)(cur_time - m_TimeSinceLastPositionCheck); + float runs = GetRunspeed(); + if(speed > (runs * RuleR(Zone, MQWarpDetectionDistanceFactor))) + { + if(!GetGMSpeed() && (runs >= GetBaseRunspeed() || (speed > (GetBaseRunspeed() * RuleR(Zone, MQWarpDetectionDistanceFactor))))) + { + printf("%s %i moving too fast! moved: %.2f in %ims, speed %.2f\n", __FILE__, __LINE__, + m_DistanceSinceLastPositionCheck, (cur_time - m_TimeSinceLastPositionCheck), speed); + if(IsShadowStepExempted()) + { + if(m_DistanceSinceLastPositionCheck > 800) + { + CheatDetected(MQWarpShadowStep, GetX(), GetY(), GetZ()); + } + } + 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) + { + CheatDetected(MQWarpKnockBack, GetX(), GetY(), GetZ()); + } + } + else if(!IsPortExempted()) + { + if(!IsMQExemptedArea(zone->GetZoneID(), GetX(), GetY(), GetZ())) + { + if(speed > (runs * 2 * RuleR(Zone, MQWarpDetectionDistanceFactor))) + { + m_TimeSinceLastPositionCheck = cur_time; + m_DistanceSinceLastPositionCheck = 0.0f; + CheatDetected(MQWarp, GetX(), GetY(), GetZ()); + //Death(this, 10000000, SPELL_UNKNOWN, _1H_BLUNT); + } + else + { + CheatDetected(MQWarpLight, GetX(), GetY(), GetZ()); + } + } + } + } + } + } + m_TimeSinceLastPositionCheck = cur_time; + m_DistanceSinceLastPositionCheck = 0.0f; + } + m_PortExemption = v; +} + +void Client::Signal(uint32 data) +{ + char buf[32]; + snprintf(buf, 31, "%d", data); + buf[31] = '\0'; + parse->EventPlayer(EVENT_SIGNAL, this, buf, 0); +} + +const bool Client::IsMQExemptedArea(uint32 zoneID, float x, float y, float z) const +{ + float max_dist = 90000; + switch(zoneID) + { + case 2: + { + float delta = (x-(-713.6)); + delta *= delta; + float distance = delta; + delta = (y-(-160.2)); + delta *= delta; + distance += delta; + delta = (z-(-12.8)); + delta *= delta; + distance += delta; + + if(distance < max_dist) + return true; + + delta = (x-(-153.8)); + delta *= delta; + distance = delta; + delta = (y-(-30.3)); + delta *= delta; + distance += delta; + delta = (z-(8.2)); + delta *= delta; + distance += delta; + + if(distance < max_dist) + return true; + + break; + } + case 9: + { + float delta = (x-(-682.5)); + delta *= delta; + float distance = delta; + delta = (y-(147.0)); + delta *= delta; + distance += delta; + delta = (z-(-9.9)); + delta *= delta; + distance += delta; + + if(distance < max_dist) + return true; + + delta = (x-(-655.4)); + delta *= delta; + distance = delta; + delta = (y-(10.5)); + delta *= delta; + distance += delta; + delta = (z-(-51.8)); + delta *= delta; + distance += delta; + + if(distance < max_dist) + return true; + + break; + } + case 62: + case 75: + case 114: + case 209: + { + //The portals are so common in paineel/felwitheb that checking + //distances wouldn't be worth it cause unless you're porting to the + //start field you're going to be triggering this and that's a level of + //accuracy I'm willing to sacrifice + return true; + break; + } + + case 24: + { + float delta = (x-(-183.0)); + delta *= delta; + float distance = delta; + delta = (y-(-773.3)); + delta *= delta; + distance += delta; + delta = (z-(54.1)); + delta *= delta; + distance += delta; + + if(distance < max_dist) + return true; + + delta = (x-(-8.8)); + delta *= delta; + distance = delta; + delta = (y-(-394.1)); + delta *= delta; + distance += delta; + delta = (z-(41.1)); + delta *= delta; + distance += delta; + + if(distance < max_dist) + return true; + + delta = (x-(-310.3)); + delta *= delta; + distance = delta; + delta = (y-(-1411.6)); + delta *= delta; + distance += delta; + delta = (z-(-42.8)); + delta *= delta; + distance += delta; + + if(distance < max_dist) + return true; + + delta = (x-(-183.1)); + delta *= delta; + distance = delta; + delta = (y-(-1409.8)); + delta *= delta; + distance += delta; + delta = (z-(37.1)); + delta *= delta; + distance += delta; + + if(distance < max_dist) + return true; + + break; + } + + case 110: + case 34: + case 96: + case 93: + case 68: + case 84: + { + if(GetBoatID() != 0) + return true; + break; + } + default: + break; + } + return false; +} + +void Client::SendRewards() +{ + std::vector rewards; + std::string query = StringFormat("SELECT reward_id, amount " + "FROM account_rewards " + "WHERE account_id = %i " + "ORDER BY reward_id", AccountID()); + auto results = database.QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error in Client::SendRewards(): %s (%s)", query.c_str(), results.ErrorMessage().c_str()); + return; + } + + for (auto row = results.begin(); row != results.end(); ++row) { + ClientReward cr; + cr.id = atoi(row[0]); + cr.amount = atoi(row[1]); + rewards.push_back(cr); + } + + if(rewards.size() == 0) + return; + + EQApplicationPacket *vetapp = new EQApplicationPacket(OP_VetRewardsAvaliable, (sizeof(InternalVeteranReward) * rewards.size())); + uchar *data = vetapp->pBuffer; + for(int i = 0; i < rewards.size(); ++i) { + InternalVeteranReward *ivr = (InternalVeteranReward*)data; + ivr->claim_id = rewards[i].id; + ivr->number_available = rewards[i].amount; + auto iter = zone->VeteranRewards.begin(); + for (;iter != zone->VeteranRewards.end(); ++iter) + if((*iter).claim_id == rewards[i].id) + break; + + if(iter != zone->VeteranRewards.end()) { + InternalVeteranReward ivro = (*iter); + ivr->claim_count = ivro.claim_count; + for(int x = 0; x < ivro.claim_count; ++x) { + ivr->items[x].item_id = ivro.items[x].item_id; + ivr->items[x].charges = ivro.items[x].charges; + strcpy(ivr->items[x].item_name, ivro.items[x].item_name); + } + } + + data += sizeof(InternalVeteranReward); + } + + FastQueuePacket(&vetapp); +} + +bool Client::TryReward(uint32 claim_id) { + //Make sure we have an open spot + //Make sure we have it in our acct and count > 0 + //Make sure the entry was found + //If we meet all the criteria: + //Decrement our count by 1 if it > 1 delete if it == 1 + //Create our item in bag if necessary at the free inv slot + //save + uint32 free_slot = 0xFFFFFFFF; + + for(int i = EmuConstants::GENERAL_BEGIN; i <= EmuConstants::GENERAL_END; ++i) { + ItemInst *item = GetInv().GetItem(i); + if(!item) { + free_slot = i; + break; + } + } + + if(free_slot == 0xFFFFFFFF) + return false; + + char errbuf[MYSQL_ERRMSG_SIZE]; + std::string query = StringFormat("SELECT amount FROM account_rewards " + "WHERE account_id = %i AND reward_id = %i", + AccountID(), claim_id); + auto results = database.QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error in Client::TryReward(): %s (%s)", query.c_str(), results.ErrorMessage().c_str()); + return false; + } + + if (results.RowCount() == 0) + return false; + + auto row = results.begin(); + + uint32 amt = atoi(row[0]); + if(amt == 0) + return false; + + std::list::iterator iter = zone->VeteranRewards.begin(); + for (; iter != zone->VeteranRewards.end(); ++row) + if((*iter).claim_id == claim_id) + break; + + if(iter == zone->VeteranRewards.end()) + return false; + + if(amt == 1) { + query = StringFormat("DELETE FROM account_rewards " + "WHERE account_id = %i AND reward_id = %i", + AccountID(), claim_id); + auto results = database.QueryDatabase(query); + if(!results.Success()) + LogFile->write(EQEMuLog::Error, "Error in Client::TryReward(): %s (%s)", query.c_str(), results.ErrorMessage().c_str()); + } + else { + query = StringFormat("UPDATE account_rewards SET amount = (amount-1) " + "WHERE account_id = %i AND reward_id = %i", + AccountID(), claim_id); + auto results = database.QueryDatabase(query); + if(!results.Success()) + LogFile->write(EQEMuLog::Error, "Error in Client::TryReward(): %s (%s)", query.c_str(), results.ErrorMessage().c_str()); + } + + InternalVeteranReward ivr = (*iter); + ItemInst *claim = database.CreateItem(ivr.items[0].item_id, ivr.items[0].charges); + if(!claim) { + Save(); + return true; + } + + bool lore_conflict = CheckLoreConflict(claim->GetItem()); + + for(int y = 1; y < 8; y++) + if(ivr.items[y].item_id && claim->GetItem()->ItemClass == 1) { + ItemInst *item_temp = database.CreateItem(ivr.items[y].item_id, ivr.items[y].charges); + if(item_temp) { + if(CheckLoreConflict(item_temp->GetItem())) { + lore_conflict = true; + DuplicateLoreMessage(ivr.items[y].item_id); + } + claim->PutItem(y-1, *item_temp); + } + } + + if(lore_conflict) { + safe_delete(claim); + return true; + } + + PutItemInInventory(free_slot, *claim); + SendItemPacket(free_slot, claim, ItemPacketTrade); + + Save(); + return true; +} + +uint32 Client::GetLDoNPointsTheme(uint32 t) +{ + switch(t) + { + case 1: + return m_pp.ldon_points_guk; + case 2: + return m_pp.ldon_points_mir; + case 3: + return m_pp.ldon_points_mmc; + case 4: + return m_pp.ldon_points_ruj; + case 5: + return m_pp.ldon_points_tak; + default: + return 0; + } +} + +uint32 Client::GetLDoNWinsTheme(uint32 t) +{ + switch(t) + { + case 1: + return m_pp.ldon_wins_guk; + case 2: + return m_pp.ldon_wins_mir; + case 3: + return m_pp.ldon_wins_mmc; + case 4: + return m_pp.ldon_wins_ruj; + case 5: + return m_pp.ldon_wins_tak; + default: + return 0; + } +} + +uint32 Client::GetLDoNLossesTheme(uint32 t) +{ + switch(t) + { + case 1: + return m_pp.ldon_losses_guk; + case 2: + return m_pp.ldon_losses_mir; + case 3: + return m_pp.ldon_losses_mmc; + case 4: + return m_pp.ldon_losses_ruj; + case 5: + return m_pp.ldon_losses_tak; + default: + return 0; + } +} + +void Client::UpdateLDoNWins(uint32 t, int32 n) +{ + switch(t) + { + case 1: + m_pp.ldon_wins_guk = n; + break; + case 2: + m_pp.ldon_wins_mir = n; + break; + case 3: + m_pp.ldon_wins_mmc = n; + break; + case 4: + m_pp.ldon_wins_ruj = n; + break; + case 5: + m_pp.ldon_wins_tak = n; + break; + default: + return; + } +} + +void Client::UpdateLDoNLosses(uint32 t, int32 n) +{ + switch(t) + { + case 1: + m_pp.ldon_losses_guk = n; + break; + case 2: + m_pp.ldon_losses_mir = n; + break; + case 3: + m_pp.ldon_losses_mmc = n; + break; + case 4: + m_pp.ldon_losses_ruj = n; + break; + case 5: + m_pp.ldon_losses_tak = n; + break; + default: + return; + } +} + + +void Client::SuspendMinion() +{ + NPC *CurrentPet = GetPet()->CastToNPC(); + + int AALevel = GetAA(aaSuspendedMinion); + + if(AALevel == 0) + return; + + if(GetLevel() < 62) + return; + + if(!CurrentPet) + { + if(m_suspendedminion.SpellID > 0) + { + MakePoweredPet(m_suspendedminion.SpellID, spells[m_suspendedminion.SpellID].teleport_zone, + m_suspendedminion.petpower, m_suspendedminion.Name, m_suspendedminion.size); + + CurrentPet = GetPet()->CastToNPC(); + + if(!CurrentPet) + { + Message(13, "Failed to recall suspended minion."); + return; + } + + if(AALevel >= 2) + { + CurrentPet->SetPetState(m_suspendedminion.Buffs, m_suspendedminion.Items); + + CurrentPet->SendPetBuffsToClient(); + } + CurrentPet->CalcBonuses(); + + CurrentPet->SetHP(m_suspendedminion.HP); + + CurrentPet->SetMana(m_suspendedminion.Mana); + + Message_StringID(clientMessageTell, SUSPEND_MINION_UNSUSPEND, CurrentPet->GetCleanName()); + + memset(&m_suspendedminion, 0, sizeof(struct PetInfo)); + } + else + return; + + } + else + { + uint16 SpellID = CurrentPet->GetPetSpellID(); + + if(SpellID) + { + if(m_suspendedminion.SpellID > 0) + { + Message_StringID(clientMessageError,ONLY_ONE_PET); + + return; + } + else if(CurrentPet->IsEngaged()) + { + Message_StringID(clientMessageError,SUSPEND_MINION_FIGHTING); + + return; + } + else if(entity_list.Fighting(CurrentPet)) + { + Message_StringID(clientMessageBlue,SUSPEND_MINION_HAS_AGGRO); + } + else + { + m_suspendedminion.SpellID = SpellID; + + m_suspendedminion.HP = CurrentPet->GetHP();; + + m_suspendedminion.Mana = CurrentPet->GetMana(); + m_suspendedminion.petpower = CurrentPet->GetPetPower(); + m_suspendedminion.size = CurrentPet->GetSize(); + + if(AALevel >= 2) + CurrentPet->GetPetState(m_suspendedminion.Buffs, m_suspendedminion.Items, m_suspendedminion.Name); + else + strn0cpy(m_suspendedminion.Name, CurrentPet->GetName(), 64); // Name stays even at rank 1 + + Message_StringID(clientMessageTell, SUSPEND_MINION_SUSPEND, CurrentPet->GetCleanName()); + + CurrentPet->Depop(false); + + SetPetID(0); + } + } + else + { + Message_StringID(clientMessageError, ONLY_SUMMONED_PETS); + + return; + } + } +} + +void Client::AddPVPPoints(uint32 Points) +{ + m_pp.PVPCurrentPoints += Points; + m_pp.PVPCareerPoints += Points; + + Save(); + + SendPVPStats(); +} + +void Client::AddCrystals(uint32 Radiant, uint32 Ebon) +{ + m_pp.currentRadCrystals += Radiant; + m_pp.careerRadCrystals += Radiant; + m_pp.currentEbonCrystals += Ebon; + m_pp.careerEbonCrystals += Ebon; + + SaveCurrency(); + + SendCrystalCounts(); +} + +// Processes a client request to inspect a SoF+ client's equipment. +void Client::ProcessInspectRequest(Client* requestee, Client* requester) { + if(requestee && requester) { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_InspectAnswer, sizeof(InspectResponse_Struct)); + InspectResponse_Struct* insr = (InspectResponse_Struct*) outapp->pBuffer; + insr->TargetID = requester->GetID(); + insr->playerid = requestee->GetID(); + + const Item_Struct* item = nullptr; + const ItemInst* inst = nullptr; + int ornamentationAugtype = RuleI(Character, OrnamentationAugmentType); + for(int16 L = 0; L <= 20; L++) { + inst = requestee->GetInv().GetItem(L); + + if(inst) { + item = inst->GetItem(); + if(item) { + if (inst && inst->GetOrnamentationAug(ornamentationAugtype)) { + const Item_Struct *aug_weap = inst->GetOrnamentationAug(ornamentationAugtype)->GetItem(); + strcpy(insr->itemnames[L], item->Name); + insr->itemicons[L] = aug_weap->Icon; + } +<<<<<<< HEAD +======= + else if (inst->GetOrnamentationIcon() && inst->GetOrnamentationIDFile()) { + strcpy(insr->itemnames[L], item->Name); + insr->itemicons[L] = inst->GetOrnamentationIcon(); + } +>>>>>>> master + else { + strcpy(insr->itemnames[L], item->Name); + insr->itemicons[L] = item->Icon; + } + } + else + insr->itemicons[L] = 0xFFFFFFFF; + } + } + + inst = requestee->GetInv().GetItem(MainPowerSource); + + if(inst) { + item = inst->GetItem(); + if(item) { + // we shouldn't do this..but, that's the way it's coded atm... + // (this type of action should be handled exclusively in the client translator) + strcpy(insr->itemnames[SoF::slots::MainPowerSource], item->Name); + insr->itemicons[SoF::slots::MainPowerSource] = item->Icon; + } + else + insr->itemicons[SoF::slots::MainPowerSource] = 0xFFFFFFFF; + } + + inst = requestee->GetInv().GetItem(MainAmmo); + + if(inst) { + item = inst->GetItem(); + if(item) { + strcpy(insr->itemnames[SoF::slots::MainAmmo], item->Name); + insr->itemicons[SoF::slots::MainAmmo] = item->Icon; + } + else + insr->itemicons[SoF::slots::MainAmmo] = 0xFFFFFFFF; + } + + strcpy(insr->text, requestee->GetInspectMessage().text); + + // There could be an OP for this..or not... (Ti clients are not processed here..this message is generated client-side) + if(requestee->IsClient() && (requestee != requester)) { requestee->Message(0, "%s is looking at your equipment...", requester->GetName()); } + + requester->QueuePacket(outapp); // Send answer to requester + safe_delete(outapp); + } +} + +void Client::GuildBankAck() +{ + EQApplicationPacket *outapp = new EQApplicationPacket(OP_GuildBank, sizeof(GuildBankAck_Struct)); + + GuildBankAck_Struct *gbas = (GuildBankAck_Struct*) outapp->pBuffer; + + gbas->Action = GuildBankAcknowledge; + + FastQueuePacket(&outapp); +} + +void Client::GuildBankDepositAck(bool Fail) +{ + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_GuildBank, sizeof(GuildBankDepositAck_Struct)); + + GuildBankDepositAck_Struct *gbdas = (GuildBankDepositAck_Struct*) outapp->pBuffer; + + gbdas->Action = GuildBankDeposit; + + gbdas->Fail = Fail ? 1 : 0; + + FastQueuePacket(&outapp); +} + +void Client::ClearGuildBank() +{ + EQApplicationPacket *outapp = new EQApplicationPacket(OP_GuildBank, sizeof(GuildBankClear_Struct)); + + GuildBankClear_Struct *gbcs = (GuildBankClear_Struct*) outapp->pBuffer; + + gbcs->Action = GuildBankBulkItems; + gbcs->DepositAreaCount = 0; + gbcs->MainAreaCount = 0; + + FastQueuePacket(&outapp); +} + +void Client::SendGroupCreatePacket() +{ + // For SoD and later clients, this is sent the Group Leader upon initial creation of the group + // + EQApplicationPacket *outapp=new EQApplicationPacket(OP_GroupUpdateB, 32 + strlen(GetName())); + + char *Buffer = (char *)outapp->pBuffer; + // Header + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 1); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // Null Leader name + + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // Member 0 + VARSTRUCT_ENCODE_STRING(Buffer, GetName()); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // This is a string + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, GetLevel()); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); + VARSTRUCT_ENCODE_TYPE(uint16, Buffer, 0); + + FastQueuePacket(&outapp); +} + +void Client::SendGroupLeaderChangePacket(const char *LeaderName) +{ + // For SoD and later, send name of Group Leader to this client + + EQApplicationPacket *outapp=new EQApplicationPacket(OP_GroupLeaderChange, sizeof(GroupLeaderChange_Struct)); + + GroupLeaderChange_Struct *glcs = (GroupLeaderChange_Struct*)outapp->pBuffer; + + strn0cpy(glcs->LeaderName, LeaderName, sizeof(glcs->LeaderName)); + + FastQueuePacket(&outapp); +} + +void Client::SendGroupJoinAcknowledge() +{ + // For SoD and later, This produces the 'You have joined the group' message. + EQApplicationPacket* outapp=new EQApplicationPacket(OP_GroupAcknowledge, 4); + FastQueuePacket(&outapp); +} + +void Client::SendAdventureError(const char *error) +{ + size_t error_size = strlen(error); + EQApplicationPacket* outapp = new EQApplicationPacket(OP_AdventureInfo, (error_size + 2)); + strn0cpy((char*)outapp->pBuffer, error, error_size); + FastQueuePacket(&outapp); +} + +void Client::SendAdventureDetails() +{ + if(adv_data) + { + ServerSendAdventureData_Struct *ad = (ServerSendAdventureData_Struct*)adv_data; + EQApplicationPacket* outapp = new EQApplicationPacket(OP_AdventureData, sizeof(AdventureRequestResponse_Struct)); + AdventureRequestResponse_Struct *arr = (AdventureRequestResponse_Struct*)outapp->pBuffer; + arr->unknown000 = 0xBFC40100; + arr->unknown2080 = 0x0A; + arr->risk = ad->risk; + strcpy(arr->text, ad->text); + + if(ad->time_to_enter != 0) + { + arr->timetoenter = ad->time_to_enter; + } + else + { + arr->timeleft = ad->time_left; + } + + if(ad->zone_in_id == zone->GetZoneID()) + { + arr->y = ad->x; + arr->x = ad->y; + arr->showcompass = 1; + } + FastQueuePacket(&outapp); + + SendAdventureCount(ad->count, ad->total); + } + else + { + ServerSendAdventureData_Struct *ad = (ServerSendAdventureData_Struct*)adv_data; + EQApplicationPacket* outapp = new EQApplicationPacket(OP_AdventureData, sizeof(AdventureRequestResponse_Struct)); + FastQueuePacket(&outapp); + } +} + +void Client::SendAdventureCount(uint32 count, uint32 total) +{ + EQApplicationPacket* outapp = new EQApplicationPacket(OP_AdventureUpdate, sizeof(AdventureCountUpdate_Struct)); + AdventureCountUpdate_Struct *acu = (AdventureCountUpdate_Struct*)outapp->pBuffer; + acu->current = count; + acu->total = total; + FastQueuePacket(&outapp); +} + +void Client::NewAdventure(int id, int theme, const char *text, int member_count, const char *members) +{ + size_t text_size = strlen(text); + EQApplicationPacket* outapp = new EQApplicationPacket(OP_AdventureDetails, text_size + 2); + strn0cpy((char*)outapp->pBuffer, text, text_size); + FastQueuePacket(&outapp); + + adv_requested_id = id; + adv_requested_theme = theme; + safe_delete_array(adv_requested_data); + adv_requested_member_count = member_count; + adv_requested_data = new char[64 * member_count]; + memcpy(adv_requested_data, members, (64 * member_count)); +} + +void Client::ClearPendingAdventureData() +{ + adv_requested_id = 0; + adv_requested_theme = 0; + safe_delete_array(adv_requested_data); + adv_requested_member_count = 0; +} + +bool Client::IsOnAdventure() +{ + if(adv_data) + { + ServerSendAdventureData_Struct *ad = (ServerSendAdventureData_Struct*)adv_data; + if(ad->zone_in_id == 0) + { + return false; + } + else + { + return true; + } + } + return false; +} + +void Client::LeaveAdventure() +{ + if(!GetPendingAdventureLeave()) + { + PendingAdventureLeave(); + ServerPacket *pack = new ServerPacket(ServerOP_AdventureLeave, 64); + strcpy((char*)pack->pBuffer, GetName()); + pack->Deflate(); + worldserver.SendPacket(pack); + delete pack; + } +} + +void Client::ClearCurrentAdventure() +{ + if(adv_data) + { + ServerSendAdventureData_Struct* ds = (ServerSendAdventureData_Struct*)adv_data; + if(ds->finished_adventures > 0) + { + ds->instance_id = 0; + ds->risk = 0; + memset(ds->text, 0, 512); + ds->time_left = 0; + ds->time_to_enter = 0; + ds->x = 0; + ds->y = 0; + ds->zone_in_id = 0; + ds->zone_in_object = 0; + } + else + { + safe_delete(adv_data); + } + + SendAdventureError("You are not currently assigned to an adventure."); + } +} + +void Client::AdventureFinish(bool win, int theme, int points) +{ + UpdateLDoNPoints(points, theme); + EQApplicationPacket* outapp = new EQApplicationPacket(OP_AdventureFinish, sizeof(AdventureFinish_Struct)); + AdventureFinish_Struct *af = (AdventureFinish_Struct*)outapp->pBuffer; + af->win_lose = win ? 1 : 0; + af->points = points; + FastQueuePacket(&outapp); +} + +void Client::CheckLDoNHail(Mob *target) +{ + if(!zone->adv_data) + { + return; + } + + if(!target || !target->IsNPC()) + { + return; + } + + if(target->GetOwnerID() != 0) + { + return; + } + + ServerZoneAdventureDataReply_Struct* ds = (ServerZoneAdventureDataReply_Struct*)zone->adv_data; + if(ds->type != Adventure_Rescue) + { + return; + } + + if(ds->data_id != target->GetNPCTypeID()) + { + return; + } + + if(entity_list.CheckNPCsClose(target) != 0) + { + target->Say("You're here to save me? I couldn't possibly risk leaving yet. There are " + "far too many of those horrid things out there waiting to recapture me! Please get" + " rid of some more of those vermin and then we can try to leave."); + return; + } + + Mob *pet = GetPet(); + if(pet) + { + if(pet->GetPetType() == petCharmed) + { + pet->BuffFadeByEffect(SE_Charm); + } + else if(pet->GetPetType() == petNPCFollow) + { + pet->SetOwnerID(0); + } + else + { + pet->Depop(); + } + } + + SetPet(target); + target->SetOwnerID(GetID()); + target->Say("Wonderful! Someone to set me free! I feared for my life for so long," + " never knowing when they might choose to end my life. Now that you're here though" + " I can rest easy. Please help me find my way out of here as soon as you can" + " I'll stay close behind you!"); +} + +void Client::CheckEmoteHail(Mob *target, const char* message) +{ + if( + (message[0] != 'H' && + message[0] != 'h') || + message[1] != 'a' || + message[2] != 'i' || + message[3] != 'l'){ + return; + } + + if(!target || !target->IsNPC()) + { + return; + } + + if(target->GetOwnerID() != 0) + { + return; + } + uint16 emoteid = target->GetEmoteID(); + if(emoteid != 0) + target->CastToNPC()->DoNPCEmote(HAILED,emoteid); +} + +void Client::MarkSingleCompassLoc(float in_x, float in_y, float in_z, uint8 count) +{ + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_DzCompass, sizeof(ExpeditionInfo_Struct) + sizeof(ExpeditionCompassEntry_Struct) * count); + ExpeditionCompass_Struct *ecs = (ExpeditionCompass_Struct*)outapp->pBuffer; + //ecs->clientid = GetID(); + ecs->count = count; + + if (count) { + ecs->entries[0].x = in_x; + ecs->entries[0].y = in_y; + ecs->entries[0].z = in_z; + } + + FastQueuePacket(&outapp); + safe_delete(outapp); +} + +void Client::SendZonePoints() +{ + int count = 0; + LinkedListIterator iterator(zone->zone_point_list); + iterator.Reset(); + while(iterator.MoreElements()) + { + ZonePoint* data = iterator.GetData(); + if(GetClientVersionBit() & data->client_version_mask) + { + count++; + } + iterator.Advance(); + } + + uint32 zpsize = sizeof(ZonePoints) + ((count + 1) * sizeof(ZonePoint_Entry)); + EQApplicationPacket* outapp = new EQApplicationPacket(OP_SendZonepoints, zpsize); + ZonePoints* zp = (ZonePoints*)outapp->pBuffer; + zp->count = count; + + int i = 0; + iterator.Reset(); + while(iterator.MoreElements()) + { + ZonePoint* data = iterator.GetData(); + if(GetClientVersionBit() & data->client_version_mask) + { + zp->zpe[i].iterator = data->number; + zp->zpe[i].x = data->target_x; + zp->zpe[i].y = data->target_y; + zp->zpe[i].z = data->target_z; + zp->zpe[i].heading = data->target_heading; + zp->zpe[i].zoneid = data->target_zone_id; + zp->zpe[i].zoneinstance = data->target_zone_instance; + i++; + } + iterator.Advance(); + } + FastQueuePacket(&outapp); +} + +void Client::SendTargetCommand(uint32 EntityID) +{ + EQApplicationPacket* outapp = new EQApplicationPacket(OP_TargetCommand, sizeof(ClientTarget_Struct)); + ClientTarget_Struct *cts = (ClientTarget_Struct*)outapp->pBuffer; + cts->new_target = EntityID; + FastQueuePacket(&outapp); +} + +void Client::LocateCorpse() +{ + Corpse *ClosestCorpse = nullptr; + if(!GetTarget()) + ClosestCorpse = entity_list.GetClosestCorpse(this, nullptr); + else if(GetTarget()->IsCorpse()) + ClosestCorpse = entity_list.GetClosestCorpse(this, GetTarget()->CastToCorpse()->GetOwnerName()); + else + ClosestCorpse = entity_list.GetClosestCorpse(this, GetTarget()->GetCleanName()); + + if(ClosestCorpse) + { + Message_StringID(MT_Spells, SENSE_CORPSE_DIRECTION); + SetHeading(CalculateHeadingToTarget(ClosestCorpse->GetX(), ClosestCorpse->GetY())); + SetTarget(ClosestCorpse); + SendTargetCommand(ClosestCorpse->GetID()); + SendPosUpdate(2); + } + else if(!GetTarget()) + Message_StringID(clientMessageError, SENSE_CORPSE_NONE); + else + Message_StringID(clientMessageError, SENSE_CORPSE_NOT_NAME); +} + +void Client::NPCSpawn(NPC *target_npc, const char *identifier, uint32 extra) +{ + if (!target_npc || !identifier) + return; + + std::string id = identifier; + for(int i = 0; i < id.length(); ++i) + { + id[i] = tolower(id[i]); + } + + if (id == "create") { + // extra tries to create the npc_type ID within the range for the current zone (zone_id * 1000) + database.NPCSpawnDB(0, zone->GetShortName(), zone->GetInstanceVersion(), this, target_npc->CastToNPC(), extra); + } + else if (id == "add") { + // extra sets the respawn timer for add + database.NPCSpawnDB(1, zone->GetShortName(), zone->GetInstanceVersion(), this, target_npc->CastToNPC(), extra); + } + else if (id == "update") { + database.NPCSpawnDB(2, zone->GetShortName(), zone->GetInstanceVersion(), this, target_npc->CastToNPC()); + } + else if (id == "remove") { + database.NPCSpawnDB(3, zone->GetShortName(), zone->GetInstanceVersion(), this, target_npc->CastToNPC()); + target_npc->Depop(false); + } + else if (id == "delete") { + database.NPCSpawnDB(4, zone->GetShortName(), zone->GetInstanceVersion(), this, target_npc->CastToNPC()); + target_npc->Depop(false); + } + else { + return; + } +} + +bool Client::IsDraggingCorpse(uint16 CorpseID) +{ + for (auto It = DraggedCorpses.begin(); It != DraggedCorpses.end(); ++It) { + if (It->second == CorpseID) + return true; + } + + return false; +} + +void Client::DragCorpses() +{ + for (auto It = DraggedCorpses.begin(); It != DraggedCorpses.end(); ++It) { + Mob *corpse = entity_list.GetMob(It->second); + + if (corpse && corpse->IsPlayerCorpse() && + (DistNoRootNoZ(*corpse) <= RuleR(Character, DragCorpseDistance))) + continue; + + if (!corpse || !corpse->IsPlayerCorpse() || + corpse->CastToCorpse()->IsBeingLooted() || + !corpse->CastToCorpse()->Summon(this, false, false)) { + Message_StringID(MT_DefaultText, CORPSEDRAG_STOP); + It = DraggedCorpses.erase(It); + } + } +} + +void Client::Doppelganger(uint16 spell_id, Mob *target, const char *name_override, int pet_count, int pet_duration) +{ + if(!target || !IsValidSpell(spell_id) || this->GetID() == target->GetID()) + return; + + PetRecord record; + if(!database.GetPetEntry(spells[spell_id].teleport_zone, &record)) + { + LogFile->write(EQEMuLog::Error, "Unknown doppelganger spell id: %d, check pets table", spell_id); + Message(13, "Unable to find data for pet %s", spells[spell_id].teleport_zone); + return; + } + + AA_SwarmPet pet; + pet.count = pet_count; + pet.duration = pet_duration; + pet.npc_id = record.npc_type; + + NPCType *made_npc = nullptr; + + const NPCType *npc_type = database.GetNPCType(pet.npc_id); + if(npc_type == nullptr) { + LogFile->write(EQEMuLog::Error, "Unknown npc type for doppelganger spell id: %d", spell_id); + Message(0,"Unable to find pet!"); + return; + } + // make a custom NPC type for this + made_npc = new NPCType; + memcpy(made_npc, npc_type, sizeof(NPCType)); + + strcpy(made_npc->name, name_override); + made_npc->level = GetLevel(); + made_npc->race = GetRace(); + made_npc->gender = GetGender(); + made_npc->size = GetSize(); + made_npc->AC = GetAC(); + made_npc->STR = GetSTR(); + made_npc->STA = GetSTA(); + made_npc->DEX = GetDEX(); + made_npc->AGI = GetAGI(); + made_npc->MR = GetMR(); + made_npc->FR = GetFR(); + made_npc->CR = GetCR(); + made_npc->DR = GetDR(); + made_npc->PR = GetPR(); + made_npc->Corrup = GetCorrup(); + // looks + made_npc->texture = GetEquipmentMaterial(MaterialChest); + made_npc->helmtexture = GetEquipmentMaterial(MaterialHead); + made_npc->haircolor = GetHairColor(); + made_npc->beardcolor = GetBeardColor(); + made_npc->eyecolor1 = GetEyeColor1(); + made_npc->eyecolor2 = GetEyeColor2(); + made_npc->hairstyle = GetHairStyle(); + made_npc->luclinface = GetLuclinFace(); + made_npc->beard = GetBeard(); + made_npc->drakkin_heritage = GetDrakkinHeritage(); + made_npc->drakkin_tattoo = GetDrakkinTattoo(); + made_npc->drakkin_details = GetDrakkinDetails(); + made_npc->d_meele_texture1 = GetEquipmentMaterial(MaterialPrimary); + made_npc->d_meele_texture2 = GetEquipmentMaterial(MaterialSecondary); + for (int i = EmuConstants::MATERIAL_BEGIN; i <= EmuConstants::MATERIAL_END; i++) { + made_npc->armor_tint[i] = GetEquipmentColor(i); + } + made_npc->loottable_id = 0; + + npc_type = made_npc; + + int summon_count = 0; + summon_count = pet.count; + + if(summon_count > MAX_SWARM_PETS) + summon_count = MAX_SWARM_PETS; + + static const xy_location swarmPetLocations[MAX_SWARM_PETS] = { + {5, 5}, {-5, 5}, {5, -5}, {-5, -5}, + {10, 10}, {-10, 10}, {10, -10}, {-10, -10}, + {8, 8}, {-8, 8}, {8, -8}, {-8, -8} + }; + + while(summon_count > 0) { + NPCType *npc_dup = nullptr; + if(made_npc != nullptr) { + npc_dup = new NPCType; + memcpy(npc_dup, made_npc, sizeof(NPCType)); + } + + NPC* npca = new NPC( + (npc_dup!=nullptr)?npc_dup:npc_type, //make sure we give the NPC the correct data pointer + 0, + GetPosition()+swarmPetLocations[summon_count], + FlyMode3); + + if(!npca->GetSwarmInfo()){ + AA_SwarmPetInfo* nSI = new AA_SwarmPetInfo; + npca->SetSwarmInfo(nSI); + npca->GetSwarmInfo()->duration = new Timer(pet_duration*1000); + } + else{ + npca->GetSwarmInfo()->duration->Start(pet_duration*1000); + } + + npca->GetSwarmInfo()->owner_id = GetID(); + + // Give the pets alittle more agro than the caster and then agro them on the target + target->AddToHateList(npca, (target->GetHateAmount(this) + 100), (target->GetDamageAmount(this) + 100)); + npca->AddToHateList(target, 1000, 1000); + npca->GetSwarmInfo()->target = target->GetID(); + + //we allocated a new NPC type object, give the NPC ownership of that memory + if(npc_dup != nullptr) + npca->GiveNPCTypeData(npc_dup); + + entity_list.AddNPC(npca); + summon_count--; + } +} + +void Client::AssignToInstance(uint16 instance_id) +{ + database.AddClientToInstance(instance_id, CharacterID()); +} + +void Client::RemoveFromInstance(uint16 instance_id) +{ + database.RemoveClientFromInstance(instance_id, CharacterID()); +} + +void Client::SendStatsWindow(Client* client, bool use_window) +{ + // Define the types of page breaks we need + std::string indP = " "; + std::string indS = "          "; + std::string indM = "                          "; + std::string indL = "                                 "; + std::string div = " | "; + + std::string color_red = ""; + std::string color_blue = ""; + std::string color_green = ""; + std::string bright_green = ""; + std::string bright_red = ""; + std::string heroic_color = " +"; + + // Set Class + std::string class_Name = itoa(GetClass()); + std::string class_List[] = { "WAR", "CLR", "PAL", "RNG", "SK", "DRU", "MNK", "BRD", "ROG", "SHM", "NEC", "WIZ", "MAG", "ENC", "BST", "BER" }; + + if(GetClass() < 17 && GetClass() > 0) { class_Name = class_List[GetClass()-1]; } + + // Race + std::string race_Name = itoa(GetRace()); + switch(GetRace()) + { + case 1: race_Name = "Human"; break; + case 2: race_Name = "Barbarian"; break; + case 3: race_Name = "Erudite"; break; + case 4: race_Name = "Wood Elf"; break; + case 5: race_Name = "High Elf"; break; + case 6: race_Name = "Dark Elf"; break; + case 7: race_Name = "Half Elf"; break; + case 8: race_Name = "Dwarf"; break; + case 9: race_Name = "Troll"; break; + case 10: race_Name = "Ogre"; break; + case 11: race_Name = "Halfing"; break; + case 12: race_Name = "Gnome"; break; + case 128: race_Name = "Iksar"; break; + case 130: race_Name = "Vah Shir"; break; + case 330: race_Name = "Froglok"; break; + case 522: race_Name = "Drakkin"; break; + default: break; + } + /*########################################################## + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + H/M/E String + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ##########################################################*/ + std::string HME_row = ""; + //Loop Variables + /*===========================*/ + std::string cur_field = ""; + std::string total_field = ""; + std::string cur_name = ""; + std::string cur_spacing = ""; + std::string cur_color = ""; + + int hme_rows = 3; // Rows in display + int max_HME_value_len = 9; // 9 digits in the displayed value + + for(int hme_row_counter = 0; hme_row_counter < hme_rows; hme_row_counter++) + { + switch(hme_row_counter) { + case 0: { + cur_name = " H: "; + cur_field = itoa(GetHP()); + total_field = itoa(GetMaxHP()); + break; + } + case 1: { + if(CalcMaxMana() > 0) { + cur_name = " M: "; + cur_field = itoa(GetMana()); + total_field = itoa(CalcMaxMana()); + } + else { continue; } + + break; + } + case 2: { + cur_name = " E: "; + cur_field = itoa(GetEndurance()); + total_field = itoa(GetMaxEndurance()); + break; + } + default: { break; } + } + if(cur_field.compare(total_field) == 0) { cur_color = bright_green; } + else { cur_color = bright_red; } + + cur_spacing.clear(); + for(int a = cur_field.size(); a < max_HME_value_len; a++) { cur_spacing += " ."; } + + HME_row += indM + cur_name + cur_spacing + cur_color + cur_field + " / " + total_field + "
"; + } + /*########################################################## + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Regen String + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ##########################################################*/ + std::string regen_string; + //Loop Variables + /*===========================*/ + std::string regen_row_header = ""; + std::string regen_row_color = ""; + std::string base_regen_field = ""; + std::string base_regen_spacing = ""; + std::string item_regen_field = ""; + std::string item_regen_spacing = ""; + std::string cap_regen_field = ""; + std::string cap_regen_spacing = ""; + std::string spell_regen_field = ""; + std::string spell_regen_spacing = ""; + std::string aa_regen_field = ""; + std::string aa_regen_spacing = ""; + std::string total_regen_field = ""; + int regen_rows = 3; // Number of rows + int max_regen_value_len = 5; // 5 digits in the displayed value(larger values will not get cut off, this is just a baseline) + + for(int regen_row_counter = 0; regen_row_counter < regen_rows; regen_row_counter++) + { + switch(regen_row_counter) + { + case 0: { + regen_row_header = "H: "; + regen_row_color = color_red; + + base_regen_field = itoa(LevelRegen()); + item_regen_field = itoa(itembonuses.HPRegen); + cap_regen_field = itoa(CalcHPRegenCap()); + spell_regen_field = itoa(spellbonuses.HPRegen); + aa_regen_field = itoa(aabonuses.HPRegen); + total_regen_field = itoa(CalcHPRegen()); + break; + } + case 1: { + if(CalcMaxMana() > 0) { + regen_row_header = "M: "; + regen_row_color = color_blue; + + base_regen_field = itoa(CalcBaseManaRegen()); + item_regen_field = itoa(itembonuses.ManaRegen); + cap_regen_field = itoa(CalcManaRegenCap()); + spell_regen_field = itoa(spellbonuses.ManaRegen); + aa_regen_field = itoa(aabonuses.ManaRegen); + total_regen_field = itoa(CalcManaRegen()); + } + else { continue; } + break; + } + case 2: { + regen_row_header = "E: "; + regen_row_color = color_green; + + base_regen_field = itoa(((GetLevel() * 4 / 10) + 2)); + item_regen_field = itoa(itembonuses.EnduranceRegen); + cap_regen_field = itoa(CalcEnduranceRegenCap()); + spell_regen_field = itoa(spellbonuses.EnduranceRegen); + aa_regen_field = itoa(aabonuses.EnduranceRegen); + total_regen_field = itoa(CalcEnduranceRegen()); + break; + } + default: { break; } + } + + base_regen_spacing.clear(); + item_regen_spacing.clear(); + cap_regen_spacing.clear(); + spell_regen_spacing.clear(); + aa_regen_spacing.clear(); + + for(int b = base_regen_field.size(); b < max_regen_value_len; b++) { base_regen_spacing += " ."; } + for(int b = item_regen_field.size(); b < max_regen_value_len; b++) { item_regen_spacing += " ."; } + for(int b = cap_regen_field.size(); b < max_regen_value_len; b++) { cap_regen_spacing += " ."; } + for(int b = spell_regen_field.size(); b < max_regen_value_len; b++) { spell_regen_spacing += " ."; } + for(int b = aa_regen_field.size(); b < max_regen_value_len; b++) { aa_regen_spacing += " ."; } + + regen_string += indS + regen_row_color + regen_row_header + base_regen_spacing + base_regen_field; + regen_string += div + item_regen_spacing + item_regen_field + " (" + cap_regen_field; + regen_string += ") " + cap_regen_spacing + div + spell_regen_spacing + spell_regen_field; + regen_string += div + aa_regen_spacing + aa_regen_field + div + total_regen_field + "

"; + } + /*########################################################## + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Stat String + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ##########################################################*/ + std::string stat_field = ""; + //Loop Variables + /*===========================*/ + //first field(stat) + std::string a_stat = "";; + std::string a_stat_name = ""; + std::string a_stat_spacing = ""; + //second field(heroic stat) + std::string h_stat = ""; + std::string h_stat_spacing = ""; + //third field(resist) + std::string a_resist = ""; + std::string a_resist_name = ""; + std::string a_resist_spacing = ""; + //fourth field(heroic resist) + std::string h_resist_field = ""; + + int stat_rows = 7; // Number of rows + int max_stat_value_len = 3; // 3 digits in the displayed value + + for(int stat_row_counter = 0; stat_row_counter < stat_rows; stat_row_counter++) + { + switch(stat_row_counter) { + case 0: { + a_stat_name = " STR: "; + a_resist_name = "MR: "; + a_stat = itoa(GetSTR()); + h_stat = itoa(GetHeroicSTR()); + a_resist = itoa(GetMR()); + h_resist_field = itoa(GetHeroicMR()); + break; + } + case 1: { + a_stat_name = " STA: "; + a_resist_name = "CR: "; + a_stat = itoa(GetSTA()); + h_stat = itoa(GetHeroicSTA()); + a_resist = itoa(GetCR()); + h_resist_field = itoa(GetHeroicCR()); + break; + } + case 2: { + a_stat_name = " AGI : "; + a_resist_name = "FR: "; + a_stat = itoa(GetAGI()); + h_stat = itoa(GetHeroicAGI()); + a_resist = itoa(GetFR()); + h_resist_field = itoa(GetHeroicFR()); + break; + } + case 3: { + a_stat_name = " DEX: "; + a_resist_name = "PR: "; + a_stat = itoa(GetDEX()); + h_stat = itoa(GetHeroicDEX()); + a_resist = itoa(GetPR()); + h_resist_field = itoa(GetHeroicPR()); + break; + } + case 4: { + a_stat_name = " INT : "; + a_resist_name = "DR: "; + a_stat = itoa(GetINT()); + h_stat = itoa(GetHeroicINT()); + a_resist = itoa(GetDR()); + h_resist_field = itoa(GetHeroicDR()); + break; + } + case 5: { + a_stat_name = " WIS: "; + a_resist_name = "Cp: "; + a_stat = itoa(GetWIS()); + h_stat = itoa(GetHeroicWIS()); + a_resist = itoa(GetCorrup()); + h_resist_field = itoa(GetHeroicCorrup()); + break; + } + case 6: { + a_stat_name = " CHA: "; + a_stat = itoa(GetCHA()); + h_stat = itoa(GetHeroicCHA()); + break; + } + default: { break; } + } + + a_stat_spacing.clear(); + h_stat_spacing.clear(); + a_resist_spacing.clear(); + + for(int a = a_stat.size(); a < max_stat_value_len; a++) { a_stat_spacing += " . "; } + for(int h = h_stat.size(); h < 20; h++) { h_stat_spacing += " . "; } + for(int h = a_resist.size(); h < max_stat_value_len; h++) { a_resist_spacing += " . "; } + + stat_field += indP + a_stat_name + a_stat_spacing + a_stat + heroic_color + h_stat + "
"; + if(stat_row_counter < 6) { + stat_field += h_stat_spacing + a_resist_name + a_resist_spacing + a_resist + heroic_color + h_resist_field + "

"; + } + } + /*########################################################## + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Mod2 String + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ##########################################################*/ + std::string mod2_field = ""; + //Loop Variables + /*===========================*/ + std::string mod2a = ""; + std::string mod2a_name = ""; + std::string mod2a_spacing = ""; + std::string mod2a_cap = ""; + std::string mod_row_spacing = ""; + std::string mod2b = ""; + std::string mod2b_name = ""; + std::string mod2b_spacing = ""; + std::string mod2b_cap = ""; + int mod2a_space_count; + int mod2b_space_count; + + int mod2_rows = 4; + int max_mod2_value_len = 3; // 3 digits in the displayed value + + for(int mod2_row_counter = 0; mod2_row_counter < mod2_rows; mod2_row_counter++) + { + switch (mod2_row_counter) + { + case 0: { + mod2a_name = "Avoidance: "; + mod2b_name = "Combat Effects: "; + mod2a = itoa(GetAvoidance()); + mod2a_cap = itoa(RuleI(Character, ItemAvoidanceCap)); + mod2b = itoa(GetCombatEffects()); + mod2b_cap = itoa(RuleI(Character, ItemCombatEffectsCap)); + mod2a_space_count = 2; + mod2b_space_count = 0; + break; + } + case 1: { + mod2a_name = "Accuracy: "; + mod2b_name = "Strike Through: "; + mod2a = itoa(GetAccuracy()); + mod2a_cap = itoa(RuleI(Character, ItemAccuracyCap)); + mod2b = itoa(GetStrikeThrough()); + mod2b_cap = itoa(RuleI(Character, ItemStrikethroughCap)); + mod2a_space_count = 3; + mod2b_space_count = 1; + break; + } + case 2: { + mod2a_name = "Shielding: "; + mod2b_name = "Spell Shielding: "; + mod2a = itoa(GetShielding()); + mod2a_cap = itoa(RuleI(Character, ItemShieldingCap)); + mod2b = itoa(GetSpellShield()); + mod2b_cap = itoa(RuleI(Character, ItemSpellShieldingCap)); + mod2a_space_count = 2; + mod2b_space_count = 1; + break; + } + case 3: { + mod2a_name = "Stun Resist: "; + mod2b_name = "DoT Shielding: "; + mod2a = itoa(GetStunResist()); + mod2a_cap = itoa(RuleI(Character, ItemStunResistCap)); + mod2b = itoa(GetDoTShield()); + mod2b_cap = itoa(RuleI(Character, ItemDoTShieldingCap)); + mod2a_space_count = 0; + mod2b_space_count = 2; + break; + } + } + + mod2a_spacing.clear(); + mod_row_spacing.clear(); + mod2b_spacing.clear(); + + for(int a = mod2a.size(); a < (max_mod2_value_len + mod2a_space_count); a++) { mod2a_spacing += " . "; } + for(int a = mod2a_cap.size(); a < 6 ; a++) { mod_row_spacing += " . "; } + for(int a = mod2b.size(); a < (max_mod2_value_len + mod2b_space_count); a++) { mod2b_spacing += " . "; } + + mod2_field += indP + mod2a_name + mod2a_spacing + mod2a + " / " + mod2a_cap + mod_row_spacing; + mod2_field += mod2b_name + mod2b_spacing + mod2b + " / " + mod2b_cap + "
"; + } + + uint32 rune_number = 0; + uint32 magic_rune_number = 0; + uint32 buff_count = GetMaxTotalSlots(); + for (int i=0; i < buff_count; i++) { + if (buffs[i].spellid != SPELL_UNKNOWN) { + if (buffs[i].melee_rune > 0) { rune_number += buffs[i].melee_rune; } + + if (buffs[i].magic_rune > 0) { magic_rune_number += buffs[i].magic_rune; } + } + } + + int shield_ac = 0; + GetRawACNoShield(shield_ac); + + std::string skill_list[] = { + "1H Blunt","1H Slashing","2H Blunt","2H Slashing","Abjuration","Alteration","Apply Poison","Archery","Backstab","Bind Wound","Bash","Block","Brass Instruments","Channeling","Conjuration", + "Defense","Disarm","Disarm Traps","Divination","Dodge","Double Attack","Dragon Punch","Dual Wield","Eagle Strike","Evocation","Feign Death","Flying Kick","Forage","Hand To Hand","Hide","Kick", + "Meditate","Mend","Offense","Parry","Pick Lock","Piercing","Riposte","Round Kick","Safe Fall","Sense Heading","Singing","Sneak","Specialize Abjuration","Specialize Alteration","Specialize Conjuration", + "Specialize Divination","Specialize Evocation","Pick Pockets","Stringed_Instruments","Swimming","Throwing","Tiger Claw","Tracking","Wind Instruments","Fishing","Make Poison","Tinkering","Research","Alchemy", + "Baking","Tailoring","Sense Traps","Blacksmithing","Fletching","Brewing","Alcohol_Tolerance","Begging","Jewelry Making","Pottery","Percussion Instruments","Intimidation","Berserking","Taunt","Frenzy" + }; + + std::string skill_mods = ""; + for(int j = 0; j <= HIGHEST_SKILL; j++) { + if(itembonuses.skillmod[j] > 0) + skill_mods += indP + skill_list[j] + " : +" + itoa(itembonuses.skillmod[j]) + "%
"; + else if(itembonuses.skillmod[j] < 0) + skill_mods += indP + skill_list[j] + " : -" + itoa(itembonuses.skillmod[j]) + "%
"; + } + + std::string skill_dmgs = ""; + for(int j = 0; j <= HIGHEST_SKILL; j++) { + if((itembonuses.SkillDamageAmount[j] + spellbonuses.SkillDamageAmount[j]) > 0) + skill_dmgs += indP + skill_list[j] + " : +" + itoa(itembonuses.SkillDamageAmount[j] + spellbonuses.SkillDamageAmount[j]) + "
"; + else if((itembonuses.SkillDamageAmount[j] + spellbonuses.SkillDamageAmount[j]) < 0) + skill_dmgs += indP + skill_list[j] + " : -" + itoa(itembonuses.SkillDamageAmount[j] + spellbonuses.SkillDamageAmount[j]) + "
"; + } + + std::string faction_item_string = ""; + char faction_buf[256]; + + for(std::map ::iterator iter = item_faction_bonuses.begin(); + iter != item_faction_bonuses.end(); + ++iter) + { + memset(&faction_buf, 0, sizeof(faction_buf)); + + if(!database.GetFactionName((int32)((*iter).first), faction_buf, sizeof(faction_buf))) + strcpy(faction_buf, "Not in DB"); + + if((*iter).second > 0) { + faction_item_string += indP + faction_buf + " : +" + itoa((*iter).second) + "
"; + } + else if((*iter).second < 0) { + faction_item_string += indP + faction_buf + " : -" + itoa((*iter).second) + "
"; + } + } + + std::string bard_info = ""; + if(GetClass() == BARD) { + bard_info = indP + "Singing: " + itoa(GetSingMod()) + "
" + + indP + "Brass: " + itoa(GetBrassMod()) + "
" + + indP + "String: " + itoa(GetStringMod()) + "
" + + indP + "Percussion: " + itoa(GetPercMod()) + "
" + + indP + "Wind: " + itoa(GetWindMod()) + "
"; + } + + std::ostringstream final_string; + final_string << + /* C/L/R */ indP << "Class: " << class_Name << indS << "Level: " << static_cast(GetLevel()) << indS << "Race: " << race_Name << "
" << + /* Runes */ indP << "Rune: " << rune_number << indL << indS << "Spell Rune: " << magic_rune_number << "
" << + /* HP/M/E */ HME_row << + /* DS */ indP << "DS: " << (itembonuses.DamageShield + spellbonuses.DamageShield*-1) << " (Spell: " << (spellbonuses.DamageShield*-1) << " + Item: " << itembonuses.DamageShield << " / " << RuleI(Character, ItemDamageShieldCap) << ")
" << + /* Atk */ indP << "ATK: " << GetTotalATK() << "
" << + /* Atk2 */ indP << "- Base: " << GetATKRating() << " | Item: " << itembonuses.ATK << " (" << RuleI(Character, ItemATKCap) << ")~Used: " << (itembonuses.ATK * 1.342) << " | Spell: " << spellbonuses.ATK << "
" << + /* AC */ indP << "AC: " << CalcAC() << "
" << + /* AC2 */ indP << "- Mit: " << GetACMit() << " | Avoid: " << GetACAvoid() << " | Spell: " << spellbonuses.AC << " | Shield: " << shield_ac << "
" << + /* Haste */ indP << "Haste: " << GetHaste() << "
" << + /* Haste2 */ indP << " - Item: " << itembonuses.haste << " + Spell: " << (spellbonuses.haste + spellbonuses.hastetype2) << " (Cap: " << RuleI(Character, HasteCap) << ") | Over: " << (spellbonuses.hastetype3 + ExtraHaste) << "

" << + /* RegenLbl */ indL << indS << "Regen
" << indS << indP << indP << " Base | Items (Cap) " << indP << " | Spell | A.A.s | Total
" << + /* Regen */ regen_string << "
" << + /* Stats */ stat_field << "

" << + /* Mod2s */ mod2_field << "
" << + /* HealAmt */ indP << "Heal Amount: " << GetHealAmt() << " / " << RuleI(Character, ItemHealAmtCap) << "
" << + /* SpellDmg*/ indP << "Spell Dmg: " << GetSpellDmg() << " / " << RuleI(Character, ItemSpellDmgCap) << "
" << + /* Clair */ indP << "Clairvoyance: " << GetClair() << " / " << RuleI(Character, ItemClairvoyanceCap) << "
" << + /* DSMit */ indP << "Dmg Shld Mit: " << GetDSMit() << " / " << RuleI(Character, ItemDSMitigationCap) << "

"; + if(GetClass() == BARD) + final_string << bard_info << "
"; + if(skill_mods.size() > 0) + final_string << skill_mods << "
"; + if(skill_dmgs.size() > 0) + final_string << skill_dmgs << "
"; + if(faction_item_string.size() > 0) + final_string << faction_item_string; + + std::string final_stats = final_string.str(); + + if(use_window) { + if(final_stats.size() < 4096) + { + uint32 Buttons = (client->GetClientVersion() < EQClientSoD) ? 0 : 1; + client->SendWindow(0, POPUPID_UPDATE_SHOWSTATSWINDOW, Buttons, "Cancel", "Update", 0, 1, this, "", "%s", final_stats.c_str()); + goto Extra_Info; + } + else { + client->Message(15, "The window has exceeded its character limit, displaying stats to chat window:"); + } + } + + client->Message(15, "~~~~~ %s %s ~~~~~", GetCleanName(), GetLastName()); + client->Message(0, " Level: %i Class: %i Race: %i DS: %i/%i Size: %1.1f Weight: %.1f/%d ", GetLevel(), GetClass(), GetRace(), GetDS(), RuleI(Character, ItemDamageShieldCap), GetSize(), (float)CalcCurrentWeight() / 10.0f, GetSTR()); + client->Message(0, " HP: %i/%i HP Regen: %i/%i",GetHP(), GetMaxHP(), CalcHPRegen(), CalcHPRegenCap()); + client->Message(0, " AC: %i ( Mit.: %i + Avoid.: %i + Spell: %i ) | Shield AC: %i", CalcAC(), GetACMit(), GetACAvoid(), spellbonuses.AC, shield_ac); + if(CalcMaxMana() > 0) + client->Message(0, " Mana: %i/%i Mana Regen: %i/%i", GetMana(), GetMaxMana(), CalcManaRegen(), CalcManaRegenCap()); + client->Message(0, " End.: %i/%i End. Regen: %i/%i",GetEndurance(), GetMaxEndurance(), CalcEnduranceRegen(), CalcEnduranceRegenCap()); + client->Message(0, " ATK: %i Worn/Spell ATK %i/%i Server Side ATK: %i", GetTotalATK(), RuleI(Character, ItemATKCap), GetATKBonus(), GetATK()); + client->Message(0, " Haste: %i / %i (Item: %i + Spell: %i + Over: %i)", GetHaste(), RuleI(Character, HasteCap), itembonuses.haste, spellbonuses.haste + spellbonuses.hastetype2, spellbonuses.hastetype3 + ExtraHaste); + client->Message(0, " STR: %i STA: %i DEX: %i AGI: %i INT: %i WIS: %i CHA: %i", GetSTR(), GetSTA(), GetDEX(), GetAGI(), GetINT(), GetWIS(), GetCHA()); + client->Message(0, " hSTR: %i hSTA: %i hDEX: %i hAGI: %i hINT: %i hWIS: %i hCHA: %i", GetHeroicSTR(), GetHeroicSTA(), GetHeroicDEX(), GetHeroicAGI(), GetHeroicINT(), GetHeroicWIS(), GetHeroicCHA()); + client->Message(0, " MR: %i PR: %i FR: %i CR: %i DR: %i Corruption: %i", GetMR(), GetPR(), GetFR(), GetCR(), GetDR(), GetCorrup()); + client->Message(0, " hMR: %i hPR: %i hFR: %i hCR: %i hDR: %i hCorruption: %i", GetHeroicMR(), GetHeroicPR(), GetHeroicFR(), GetHeroicCR(), GetHeroicDR(), GetHeroicCorrup()); + client->Message(0, " Shielding: %i Spell Shield: %i DoT Shielding: %i Stun Resist: %i Strikethrough: %i Avoidance: %i Accuracy: %i Combat Effects: %i", GetShielding(), GetSpellShield(), GetDoTShield(), GetStunResist(), GetStrikeThrough(), GetAvoidance(), GetAccuracy(), GetCombatEffects()); + client->Message(0, " Heal Amt.: %i Spell Dmg.: %i Clairvoyance: %i DS Mitigation: %i", GetHealAmt(), GetSpellDmg(), GetClair(), GetDSMit()); + if(GetClass() == BARD) + client->Message(0, " Singing: %i Brass: %i String: %i Percussion: %i Wind: %i", GetSingMod(), GetBrassMod(), GetStringMod(), GetPercMod(), GetWindMod()); + + Extra_Info: + + client->Message(0, " BaseRace: %i Gender: %i BaseGender: %i Texture: %i HelmTexture: %i", GetBaseRace(), GetGender(), GetBaseGender(), GetTexture(), GetHelmTexture()); + if (client->Admin() >= 100) { + client->Message(0, " CharID: %i EntityID: %i PetID: %i OwnerID: %i AIControlled: %i Targetted: %i", CharacterID(), GetID(), GetPetID(), GetOwnerID(), IsAIControlled(), targeted); + } +} + +void Client::SendAltCurrencies() { + if(GetClientVersion() >= EQClientSoF) { + uint32 count = zone->AlternateCurrencies.size(); + if(count == 0) { + return; + } + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_AltCurrency, + sizeof(AltCurrencyPopulate_Struct) + sizeof(AltCurrencyPopulateEntry_Struct) * count); + AltCurrencyPopulate_Struct *altc = (AltCurrencyPopulate_Struct*)outapp->pBuffer; + altc->opcode = ALT_CURRENCY_OP_POPULATE; + altc->count = count; + + uint32 i = 0; + std::list::iterator iter = zone->AlternateCurrencies.begin(); + while(iter != zone->AlternateCurrencies.end()) { + const Item_Struct* item = database.GetItem((*iter).item_id); + altc->entries[i].currency_number = (*iter).id; + altc->entries[i].unknown00 = 1; + altc->entries[i].currency_number2 = (*iter).id; + altc->entries[i].item_id = (*iter).item_id; + if(item) { + altc->entries[i].item_icon = item->Icon; + altc->entries[i].stack_size = item->StackSize; + } else { + altc->entries[i].item_icon = 1000; + altc->entries[i].stack_size = 1000; + } + i++; + ++iter; + } + + FastQueuePacket(&outapp); + } +} + +void Client::SetAlternateCurrencyValue(uint32 currency_id, uint32 new_amount) +{ + alternate_currency[currency_id] = new_amount; + database.UpdateAltCurrencyValue(CharacterID(), currency_id, new_amount); + SendAlternateCurrencyValue(currency_id); +} + +void Client::AddAlternateCurrencyValue(uint32 currency_id, int32 amount, int8 method) +{ + + /* Added via Quest, rest of the logging methods may be done inline due to information available in that area of the code */ + if (method == 1){ + /* QS: PlayerLogAlternateCurrencyTransactions :: Cursor to Item Storage */ + if (RuleB(QueryServ, PlayerLogAlternateCurrencyTransactions)){ + std::string event_desc = StringFormat("Added via Quest :: Cursor to Item :: alt_currency_id:%i amount:%i in zoneid:%i instid:%i", currency_id, this->GetZoneID(), this->GetInstanceID()); + QServ->PlayerLogEvent(Player_Log_Alternate_Currency_Transactions, this->CharacterID(), event_desc); + } + } + + if(amount == 0) { + return; + } + + if(!alternate_currency_loaded) { + alternate_currency_queued_operations.push(std::make_pair(currency_id, amount)); + return; + } + + int new_value = 0; + std::map::iterator iter = alternate_currency.find(currency_id); + if(iter == alternate_currency.end()) { + new_value = amount; + } else { + new_value = (*iter).second + amount; + } + + if(new_value < 0) { + alternate_currency[currency_id] = 0; + database.UpdateAltCurrencyValue(CharacterID(), currency_id, 0); + } else { + alternate_currency[currency_id] = new_value; + database.UpdateAltCurrencyValue(CharacterID(), currency_id, new_value); + } + SendAlternateCurrencyValue(currency_id); +} + +void Client::SendAlternateCurrencyValues() +{ + std::list::iterator iter = zone->AlternateCurrencies.begin(); + while(iter != zone->AlternateCurrencies.end()) { + SendAlternateCurrencyValue((*iter).id, false); + ++iter; + } +} + +void Client::SendAlternateCurrencyValue(uint32 currency_id, bool send_if_null) +{ + uint32 value = GetAlternateCurrencyValue(currency_id); + if(value > 0 || (value == 0 && send_if_null)) { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_AltCurrency, sizeof(AltCurrencyUpdate_Struct)); + AltCurrencyUpdate_Struct *update = (AltCurrencyUpdate_Struct*)outapp->pBuffer; + update->opcode = 7; + strcpy(update->name, GetName()); + update->currency_number = currency_id; + update->amount = value; + update->unknown072 = 1; + FastQueuePacket(&outapp); + } +} + +uint32 Client::GetAlternateCurrencyValue(uint32 currency_id) const +{ + std::map::const_iterator iter = alternate_currency.find(currency_id); + if(iter == alternate_currency.end()) { + return 0; + } else { + return (*iter).second; + } +} + +void Client::ProcessAlternateCurrencyQueue() { + while(!alternate_currency_queued_operations.empty()) { + std::pair op = alternate_currency_queued_operations.front(); + + AddAlternateCurrencyValue(op.first, op.second); + + alternate_currency_queued_operations.pop(); + } +} + +void Client::OpenLFGuildWindow() +{ + EQApplicationPacket *outapp = new EQApplicationPacket(OP_LFGuild, 8); + + outapp->WriteUInt32(6); + + FastQueuePacket(&outapp); +} + +bool Client::IsXTarget(const Mob *m) const +{ + if(!XTargettingAvailable() || !m || (m->GetID() == 0)) + return false; + + for(int i = 0; i < GetMaxXTargets(); ++i) + { + if(XTargets[i].ID == m->GetID()) + return true; + } + return false; +} + +bool Client::IsClientXTarget(const Client *c) const +{ + if(!XTargettingAvailable() || !c) + return false; + + for(int i = 0; i < GetMaxXTargets(); ++i) + { + if(!strcasecmp(XTargets[i].Name, c->GetName())) + return true; + } + return false; +} + + +void Client::UpdateClientXTarget(Client *c) +{ + if(!XTargettingAvailable() || !c) + return; + + for(int i = 0; i < GetMaxXTargets(); ++i) + { + if(!strcasecmp(XTargets[i].Name, c->GetName())) + { + XTargets[i].ID = c->GetID(); + SendXTargetPacket(i, c); + } + } +} + +void Client::AddAutoXTarget(Mob *m) +{ + if(!XTargettingAvailable() || !XTargetAutoAddHaters) + return; + + if(IsXTarget(m)) + return; + + for(int i = 0; i < GetMaxXTargets(); ++i) + { + if((XTargets[i].Type == Auto) && (XTargets[i].ID == 0)) + { + XTargets[i].ID = m->GetID(); + SendXTargetPacket(i, m); + break; + } + } +} + +void Client::RemoveXTarget(Mob *m, bool OnlyAutoSlots) +{ + if(!XTargettingAvailable()) + return; + + bool HadFreeAutoSlotsBefore = false; + + int FreedAutoSlots = 0; + + if(m->GetID() == 0) + return; + + for(int i = 0; i < GetMaxXTargets(); ++i) + { + if(OnlyAutoSlots && (XTargets[i].Type !=Auto)) + continue; + + if(XTargets[i].ID == m->GetID()) + { + if(XTargets[i].Type == CurrentTargetNPC) + XTargets[i].Type = Auto; + + if(XTargets[i].Type == Auto) + ++FreedAutoSlots; + + XTargets[i].ID = 0; + + SendXTargetPacket(i, nullptr); + } + else + { + if((XTargets[i].Type == Auto) && (XTargets[i].ID == 0)) + HadFreeAutoSlotsBefore = true; + } + } + // If there are more mobs aggro on us than we had auto-hate slots, add one of those haters into the slot(s) we just freed up. + if(!HadFreeAutoSlotsBefore && FreedAutoSlots) + entity_list.RefreshAutoXTargets(this); +} + +void Client::UpdateXTargetType(XTargetType Type, Mob *m, const char *Name) +{ + if(!XTargettingAvailable()) + return; + + for(int i = 0; i < GetMaxXTargets(); ++i) + { + if(XTargets[i].Type == Type) + { + if(m) + XTargets[i].ID = m->GetID(); + else + XTargets[i].ID = 0; + + if(Name) + strncpy(XTargets[i].Name, Name, 64); + + SendXTargetPacket(i, m); + } + } +} + +void Client::SendXTargetPacket(uint32 Slot, Mob *m) +{ + if(!XTargettingAvailable()) + return; + + uint32 PacketSize = 18; + + if(m) + PacketSize += strlen(m->GetCleanName()); + else + { + PacketSize += strlen(XTargets[Slot].Name); + } + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_XTargetResponse, PacketSize); + outapp->WriteUInt32(GetMaxXTargets()); + outapp->WriteUInt32(1); + outapp->WriteUInt32(Slot); + if(m) + { + outapp->WriteUInt8(1); + } + else + { + if (strlen(XTargets[Slot].Name) && ((XTargets[Slot].Type == CurrentTargetPC) || + (XTargets[Slot].Type == GroupTank) || + (XTargets[Slot].Type == GroupAssist) || + (XTargets[Slot].Type == Puller) || + (XTargets[Slot].Type == RaidAssist1) || + (XTargets[Slot].Type == RaidAssist2) || + (XTargets[Slot].Type == RaidAssist3))) + { + outapp->WriteUInt8(2); + } + else + { + outapp->WriteUInt8(0); + } + } + outapp->WriteUInt32(XTargets[Slot].ID); + outapp->WriteString(m ? m->GetCleanName() : XTargets[Slot].Name); + FastQueuePacket(&outapp); +} + +void Client::RemoveGroupXTargets() +{ + if(!XTargettingAvailable()) + return; + + for(int i = 0; i < GetMaxXTargets(); ++i) + { + if ((XTargets[i].Type == GroupTank) || + (XTargets[i].Type == GroupAssist) || + (XTargets[i].Type == Puller) || + (XTargets[i].Type == RaidAssist1) || + (XTargets[i].Type == RaidAssist2) || + (XTargets[i].Type == RaidAssist3) || + (XTargets[i].Type == GroupMarkTarget1) || + (XTargets[i].Type == GroupMarkTarget2) || + (XTargets[i].Type == GroupMarkTarget3)) + { + XTargets[i].ID = 0; + XTargets[i].Name[0] = 0; + SendXTargetPacket(i, nullptr); + } + } +} + +void Client::RemoveAutoXTargets() +{ + if(!XTargettingAvailable()) + return; + + for(int i = 0; i < GetMaxXTargets(); ++i) + { + if(XTargets[i].Type == Auto) + { + XTargets[i].ID = 0; + XTargets[i].Name[0] = 0; + SendXTargetPacket(i, nullptr); + } + } +} + +void Client::ShowXTargets(Client *c) +{ + if(!c) + return; + + for(int i = 0; i < GetMaxXTargets(); ++i) + c->Message(0, "Xtarget Slot: %i, Type: %2i, ID: %4i, Name: %s", i, XTargets[i].Type, XTargets[i].ID, XTargets[i].Name); +} + +void Client::SetMaxXTargets(uint8 NewMax) +{ + if(!XTargettingAvailable()) + return; + + if(NewMax > XTARGET_HARDCAP) + return; + + MaxXTargets = NewMax; + + Save(0); + + for(int i = MaxXTargets; i < XTARGET_HARDCAP; ++i) + { + XTargets[i].Type = Auto; + XTargets[i].ID = 0; + XTargets[i].Name[0] = 0; + } + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_XTargetResponse, 8); + outapp->WriteUInt32(GetMaxXTargets()); + outapp->WriteUInt32(0); + FastQueuePacket(&outapp); +} + +const char* Client::GetRacePlural(Client* client) { + + switch (client->CastToMob()->GetRace()) { + case HUMAN: + return "Humans"; break; + case BARBARIAN: + return "Barbarians"; break; + case ERUDITE: + return "Erudites"; break; + case WOOD_ELF: + return "Wood Elves"; break; + case HIGH_ELF: + return "High Elves"; break; + case DARK_ELF: + return "Dark Elves"; break; + case HALF_ELF: + return "Half Elves"; break; + case DWARF: + return "Dwarves"; break; + case TROLL: + return "Trolls"; break; + case OGRE: + return "Ogres"; break; + case HALFLING: + return "Halflings"; break; + case GNOME: + return "Gnomes"; break; + case IKSAR: + return "Iksar"; break; + case VAHSHIR: + return "Vah Shir"; break; + case FROGLOK: + return "Frogloks"; break; + case DRAKKIN: + return "Drakkin"; break; + default: + return "Races"; break; + } +} + +const char* Client::GetClassPlural(Client* client) { + + switch (client->CastToMob()->GetClass()) { + case WARRIOR: + return "Warriors"; break; + case CLERIC: + return "Clerics"; break; + case PALADIN: + return "Paladins"; break; + case RANGER: + return "Rangers"; break; + case SHADOWKNIGHT: + return "Shadowknights"; break; + case DRUID: + return "Druids"; break; + case MONK: + return "Monks"; break; + case BARD: + return "Bards"; break; + case ROGUE: + return "Rogues"; break; + case SHAMAN: + return "Shamen"; break; + case NECROMANCER: + return "Necromancers"; break; + case WIZARD: + return "Wizards"; break; + case MAGICIAN: + return "Magicians"; break; + case ENCHANTER: + return "Enchanters"; break; + case BEASTLORD: + return "Beastlords"; break; + case BERSERKER: + return "Berserkers"; break; + default: + return "Classes"; break; + } +} + + +void Client::SendWebLink(const char *website) +{ + size_t len = strlen(website) + 1; + if(website != 0 && len > 1) + { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_Weblink, sizeof(Weblink_Struct) + len); + Weblink_Struct *wl = (Weblink_Struct*)outapp->pBuffer; + memcpy(wl->weblink, website, len); + wl->weblink[len] = '\0'; + + FastQueuePacket(&outapp); + } +} + +void Client::SendMercPersonalInfo() +{ + uint32 mercTypeCount = 1; + uint32 mercCount = 1; //TODO: Un-hardcode this and support multiple mercs like in later clients than SoD. + uint32 i = 0; + uint32 altCurrentType = 19; //TODO: Implement alternate currency purchases involving mercs! + + MercTemplate *mercData = &zone->merc_templates[GetMercInfo().MercTemplateID]; + + int stancecount = 0; + stancecount += zone->merc_stance_list[GetMercInfo().MercTemplateID].size(); + if(stancecount > MAX_MERC_STANCES || mercCount > MAX_MERC || mercTypeCount > MAX_MERC_GRADES) + { + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: SendMercPersonalInfo Cancelled: (%i) (%i) (%i)", stancecount, mercCount, mercTypeCount); + SendMercMerchantResponsePacket(0); + return; + } + + if(mercData) + { + if (GetClientVersion() >= EQClientRoF) + { + if (mercCount > 0) + { + EQApplicationPacket *outapp = new EQApplicationPacket(OP_MercenaryDataUpdate, sizeof(MercenaryDataUpdate_Struct)); + MercenaryDataUpdate_Struct* mdus = (MercenaryDataUpdate_Struct*)outapp->pBuffer; + mdus->MercStatus = 0; + mdus->MercCount = mercCount; + mdus->MercData[i].MercID = mercData->MercTemplateID; + mdus->MercData[i].MercType = mercData->MercType; + mdus->MercData[i].MercSubType = mercData->MercSubType; + mdus->MercData[i].PurchaseCost = Merc::CalcPurchaseCost(mercData->MercTemplateID, GetLevel(), 0); + mdus->MercData[i].UpkeepCost = Merc::CalcUpkeepCost(mercData->MercTemplateID, GetLevel(), 0); + mdus->MercData[i].Status = 0; + mdus->MercData[i].AltCurrencyCost = Merc::CalcPurchaseCost(mercData->MercTemplateID, GetLevel(), altCurrentType); + mdus->MercData[i].AltCurrencyUpkeep = Merc::CalcPurchaseCost(mercData->MercTemplateID, GetLevel(), altCurrentType); + mdus->MercData[i].AltCurrencyType = altCurrentType; + mdus->MercData[i].MercUnk01 = 0; + mdus->MercData[i].TimeLeft = GetMercInfo().MercTimerRemaining; //GetMercTimer().GetRemainingTime(); + mdus->MercData[i].MerchantSlot = i + 1; + mdus->MercData[i].MercUnk02 = 1; + mdus->MercData[i].StanceCount = zone->merc_stance_list[mercData->MercTemplateID].size(); + mdus->MercData[i].MercUnk03 = 0; + mdus->MercData[i].MercUnk04 = 1; + strn0cpy(mdus->MercData[i].MercName, GetMercInfo().merc_name , sizeof(mdus->MercData[i].MercName)); + uint32 stanceindex = 0; + if (mdus->MercData[i].StanceCount != 0) + { + std::list::iterator iter = zone->merc_stance_list[mercData->MercTemplateID].begin(); + while(iter != zone->merc_stance_list[mercData->MercTemplateID].end()) + { + mdus->MercData[i].Stances[stanceindex].StanceIndex = stanceindex; + mdus->MercData[i].Stances[stanceindex].Stance = (iter->StanceID); + stanceindex++; + ++iter; + } + } + + mdus->MercData[i].MercUnk05 = 1; + FastQueuePacket(&outapp); + safe_delete(outapp); + return; + } + } + else + { + if(mercTypeCount > 0 && mercCount > 0) + { + EQApplicationPacket *outapp = new EQApplicationPacket(OP_MercenaryDataResponse, sizeof(MercenaryMerchantList_Struct)); + MercenaryMerchantList_Struct* mml = (MercenaryMerchantList_Struct*)outapp->pBuffer; + mml->MercTypeCount = mercTypeCount; //We should only have one merc entry. + mml->MercGrades[i] = 1; + mml->MercCount = mercCount; + mml->Mercs[i].MercID = mercData->MercTemplateID; + mml->Mercs[i].MercType = mercData->MercType; + mml->Mercs[i].MercSubType = mercData->MercSubType; + mml->Mercs[i].PurchaseCost = RuleB(Mercs, ChargeMercPurchaseCost) ? Merc::CalcPurchaseCost(mercData->MercTemplateID, GetLevel(), 0): 0; + mml->Mercs[i].UpkeepCost = RuleB(Mercs, ChargeMercUpkeepCost) ? Merc::CalcUpkeepCost(mercData->MercTemplateID, GetLevel(), 0): 0; + mml->Mercs[i].Status = 0; + mml->Mercs[i].AltCurrencyCost = RuleB(Mercs, ChargeMercPurchaseCost) ? Merc::CalcPurchaseCost(mercData->MercTemplateID, GetLevel(), altCurrentType): 0; + mml->Mercs[i].AltCurrencyUpkeep = RuleB(Mercs, ChargeMercUpkeepCost) ? Merc::CalcUpkeepCost(mercData->MercTemplateID, GetLevel(), altCurrentType): 0; + mml->Mercs[i].AltCurrencyType = altCurrentType; + mml->Mercs[i].MercUnk01 = 0; + mml->Mercs[i].TimeLeft = GetMercInfo().MercTimerRemaining; + mml->Mercs[i].MerchantSlot = i + 1; + mml->Mercs[i].MercUnk02 = 1; + mml->Mercs[i].StanceCount = zone->merc_stance_list[mercData->MercTemplateID].size(); + mml->Mercs[i].MercUnk03 = 0; + mml->Mercs[i].MercUnk04 = 1; + strn0cpy(mml->Mercs[i].MercName, GetMercInfo().merc_name , sizeof(mml->Mercs[i].MercName)); + int stanceindex = 0; + if(mml->Mercs[i].StanceCount != 0) + { + std::list::iterator iter = zone->merc_stance_list[mercData->MercTemplateID].begin(); + while(iter != zone->merc_stance_list[mercData->MercTemplateID].end()) + { + mml->Mercs[i].Stances[stanceindex].StanceIndex = stanceindex; + mml->Mercs[i].Stances[stanceindex].Stance = (iter->StanceID); + stanceindex++; + ++iter; + } + } + FastQueuePacket(&outapp); + safe_delete(outapp); + return; + } + } + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: SendMercPersonalInfo Send Successful"); + + SendMercMerchantResponsePacket(0); + } + else + { + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: SendMercPersonalInfo Send Failed Due to no MercData for %i", GetMercInfo().MercTemplateID); + } + +} + +void Client::SendClearMercInfo() +{ + EQApplicationPacket* outapp = new EQApplicationPacket(OP_MercenaryDataUpdate, sizeof(NoMercenaryHired_Struct)); + NoMercenaryHired_Struct *nmhs = (NoMercenaryHired_Struct*)outapp->pBuffer; + nmhs->MercStatus = -1; + nmhs->MercCount = 0; + nmhs->MercID = 1; + FastQueuePacket(&outapp); +} + + +void Client::DuplicateLoreMessage(uint32 ItemID) +{ + if(!(ClientVersionBit & BIT_RoFAndLater)) + { + Message_StringID(0, PICK_LORE); + return; + } + + const Item_Struct *item = database.GetItem(ItemID); + + if(!item) + return; + + Message_StringID(0, PICK_LORE, item->Name); +} + +void Client::GarbleMessage(char *message, uint8 variance) +{ + // Garble message by variance% + const char alpha_list[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; // only change alpha characters for now + + for (size_t i = 0; i < strlen(message); i++) { + uint8 chance = (uint8)zone->random.Int(0, 115); // variation just over worst possible scrambling + if (isalpha(message[i]) && (chance <= variance)) { + uint8 rand_char = (uint8)zone->random.Int(0,51); // choose a random character from the alpha list + message[i] = alpha_list[rand_char]; + } + } +} + +// returns what Other thinks of this +FACTION_VALUE Client::GetReverseFactionCon(Mob* iOther) { + if (GetOwnerID()) { + return GetOwnerOrSelf()->GetReverseFactionCon(iOther); + } + + iOther = iOther->GetOwnerOrSelf(); + + if (iOther->GetPrimaryFaction() < 0) + return GetSpecialFactionCon(iOther); + + if (iOther->GetPrimaryFaction() == 0) + return FACTION_INDIFFERENT; + + return GetFactionLevel(CharacterID(), 0, GetRace(), GetClass(), GetDeity(), iOther->GetPrimaryFaction(), iOther); +} + +//o-------------------------------------------------------------- +//| Name: GetFactionLevel; Dec. 16, 2001 +//o-------------------------------------------------------------- +//| Notes: Gets the characters faction standing with the specified NPC. +//| Will return Indifferent on failure. +//o-------------------------------------------------------------- +FACTION_VALUE Client::GetFactionLevel(uint32 char_id, uint32 npc_id, uint32 p_race, uint32 p_class, uint32 p_deity, int32 pFaction, Mob* tnpc) +{ + if (pFaction < 0) + return GetSpecialFactionCon(tnpc); + FACTION_VALUE fac = FACTION_INDIFFERENT; + int32 tmpFactionValue; + FactionMods fmods; + + // few optimizations + if (GetFeigned()) + return FACTION_INDIFFERENT; + if (invisible_undead && tnpc && !tnpc->SeeInvisibleUndead()) + return FACTION_INDIFFERENT; + if (IsInvisible(tnpc)) + return FACTION_INDIFFERENT; + if (tnpc && tnpc->GetOwnerID() != 0) // pets con amiably to owner and indiff to rest + { + if (char_id == tnpc->GetOwner()->CastToClient()->CharacterID()) + return FACTION_AMIABLE; + else + return FACTION_INDIFFERENT; + } + + //First get the NPC's Primary faction + if(pFaction > 0) + { + //Get the faction data from the database + if(database.GetFactionData(&fmods, p_class, p_race, p_deity, pFaction)) + { + //Get the players current faction with pFaction + tmpFactionValue = GetCharacterFactionLevel(pFaction); + //Tack on any bonuses from Alliance type spell effects + tmpFactionValue += GetFactionBonus(pFaction); + tmpFactionValue += GetItemFactionBonus(pFaction); + //Return the faction to the client + fac = CalculateFaction(&fmods, tmpFactionValue); + } + } + else + { + return(FACTION_INDIFFERENT); + } + + // merchant fix + if (tnpc && tnpc->IsNPC() && tnpc->CastToNPC()->MerchantType && (fac == FACTION_THREATENLY || fac == FACTION_SCOWLS)) + fac = FACTION_DUBIOUS; + + if (tnpc != 0 && fac != FACTION_SCOWLS && tnpc->CastToNPC()->CheckAggro(this)) + fac = FACTION_THREATENLY; + + return fac; +} + +//Sets the characters faction standing with the specified NPC. +void Client::SetFactionLevel(uint32 char_id, uint32 npc_id, uint8 char_class, uint8 char_race, uint8 char_deity) +{ + int32 faction_id[MAX_NPC_FACTIONS] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + int32 npc_value[MAX_NPC_FACTIONS] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + uint8 temp[MAX_NPC_FACTIONS] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + int32 mod; + int32 tmpValue; + int32 current_value; + FactionMods fm; + bool change = false; + bool repair = false; + + // Get the npc faction list + if (!database.GetNPCFactionList(npc_id, faction_id, npc_value, temp)) + return; + for (int i = 0; i < MAX_NPC_FACTIONS; i++) + { + if (faction_id[i] <= 0) + continue; + + // Get the faction modifiers + if (database.GetFactionData(&fm, char_class, char_race, char_deity, faction_id[i])) + { + // Get the characters current value with that faction + current_value = GetCharacterFactionLevel(faction_id[i]); + + if (this->itembonuses.HeroicCHA) + { + int faction_mod = itembonuses.HeroicCHA / 5; + // If our result isn't truncated, then just do that + if (npc_value[i] * faction_mod / 100 != 0) + npc_value[i] += npc_value[i] * faction_mod / 100; + // If our result is truncated, then double a mob's value every once and a while to equal what they would have got + else + { + if (zone->random.Int(0, 100) < faction_mod) + npc_value[i] *= 2; + } + } + // Set flag when to update db + if (current_value > MAX_PERSONAL_FACTION) + { + current_value = MAX_PERSONAL_FACTION; + repair = true; + } + else if (current_value < MIN_PERSONAL_FACTION) + { + current_value = MIN_PERSONAL_FACTION; + repair = true; + } + else if ((m_pp.gm != 1) && (npc_value[i] != 0) && ((current_value != MAX_PERSONAL_FACTION) || (current_value != MIN_PERSONAL_FACTION))) + change = true; + + current_value += npc_value[i]; + + if (current_value > MAX_PERSONAL_FACTION) + current_value = MAX_PERSONAL_FACTION; + else if (current_value < MIN_PERSONAL_FACTION) + current_value = MIN_PERSONAL_FACTION; + + if (change || repair) + { + database.SetCharacterFactionLevel(char_id, faction_id[i], current_value, temp[i], factionvalues); + + if (change) + { + mod = fm.base + fm.class_mod + fm.race_mod + fm.deity_mod; + tmpValue = current_value + mod + npc_value[i]; + SendFactionMessage(npc_value[i], faction_id[i], tmpValue, temp[i]); + } + } + } + } + return; +} + +void Client::SetFactionLevel2(uint32 char_id, int32 faction_id, uint8 char_class, uint8 char_race, uint8 char_deity, int32 value, uint8 temp) +{ + int32 current_value; + //Get the npc faction list + if(faction_id > 0 && value != 0) { + //Get the faction modifiers + current_value = GetCharacterFactionLevel(faction_id) + value; + if(!(database.SetCharacterFactionLevel(char_id, faction_id, current_value, temp, factionvalues))) + return; + + SendFactionMessage(value, faction_id, current_value, temp); + } + return; +} + +int32 Client::GetCharacterFactionLevel(int32 faction_id) +{ + if (faction_id <= 0) + return 0; + faction_map::iterator res; + res = factionvalues.find(faction_id); + if (res == factionvalues.end()) + return 0; + return res->second; +} + +// returns the character's faction level, adjusted for racial, class, and deity modifiers +int32 Client::GetModCharacterFactionLevel(int32 faction_id) { + int32 Modded = GetCharacterFactionLevel(faction_id); + FactionMods fm; + if (database.GetFactionData(&fm, GetClass(), GetRace(), GetDeity(), faction_id)) + Modded += fm.base + fm.class_mod + fm.race_mod + fm.deity_mod; + + return Modded; +} + +void Client::MerchantRejectMessage(Mob *merchant, int primaryfaction) +{ + int messageid = 0; + int32 tmpFactionValue = 0; + int32 lowestvalue = 0; + FactionMods fmod; + + // If a faction is involved, get the data. + if (primaryfaction > 0) { + if (database.GetFactionData(&fmod, GetClass(), GetRace(), GetDeity(), primaryfaction)) { + tmpFactionValue = GetCharacterFactionLevel(primaryfaction); + lowestvalue = std::min(tmpFactionValue, std::min(fmod.class_mod, fmod.race_mod)); + } + } + // If no primary faction or biggest influence is your faction hit + if (primaryfaction <= 0 || lowestvalue == tmpFactionValue) { + merchant->Say_StringID(zone->random.Int(WONT_SELL_DEEDS1, WONT_SELL_DEEDS6)); + } else if (lowestvalue == fmod.race_mod) { // race biggest + // Non-standard race (ex. illusioned to wolf) + if (GetRace() > PLAYER_RACE_COUNT) { + messageid = zone->random.Int(1, 3); // these aren't sequential StringIDs :( + switch (messageid) { + case 1: + messageid = WONT_SELL_NONSTDRACE1; + break; + case 2: + messageid = WONT_SELL_NONSTDRACE2; + break; + case 3: + messageid = WONT_SELL_NONSTDRACE3; + break; + default: // w/e should never happen + messageid = WONT_SELL_NONSTDRACE1; + break; + } + merchant->Say_StringID(messageid); + } else { // normal player races + messageid = zone->random.Int(1, 4); + switch (messageid) { + case 1: + messageid = WONT_SELL_RACE1; + break; + case 2: + messageid = WONT_SELL_RACE2; + break; + case 3: + messageid = WONT_SELL_RACE3; + break; + case 4: + messageid = WONT_SELL_RACE4; + break; + default: // w/e should never happen + messageid = WONT_SELL_RACE1; + break; + } + merchant->Say_StringID(messageid, itoa(GetRace())); + } + } else if (lowestvalue == fmod.class_mod) { + merchant->Say_StringID(zone->random.Int(WONT_SELL_CLASS1, WONT_SELL_CLASS5), itoa(GetClass())); + } + return; +} + +//o-------------------------------------------------------------- +//| Name: SendFactionMessage +//o-------------------------------------------------------------- +//| Purpose: Send faction change message to client +//o-------------------------------------------------------------- +void Client::SendFactionMessage(int32 tmpvalue, int32 faction_id, int32 totalvalue, uint8 temp) +{ + char name[50]; + + // default to Faction# if we couldn't get the name from the ID + if (database.GetFactionName(faction_id, name, sizeof(name)) == false) + snprintf(name, sizeof(name), "Faction%i", faction_id); + + if (tmpvalue == 0 || temp == 1 || temp == 2) + return; + else if (totalvalue >= MAX_PERSONAL_FACTION) + Message_StringID(15, FACTION_BEST, name); + else if (totalvalue <= MIN_PERSONAL_FACTION) + Message_StringID(15, FACTION_WORST, name); + else if (tmpvalue > 0 && totalvalue < MAX_PERSONAL_FACTION && !RuleB(Client, UseLiveFactionMessage)) + Message_StringID(15, FACTION_BETTER, name); + else if (tmpvalue < 0 && totalvalue > MIN_PERSONAL_FACTION && !RuleB(Client, UseLiveFactionMessage)) + Message_StringID(15, FACTION_WORSE, name); + else if (RuleB(Client, UseLiveFactionMessage)) + Message(15, "Your faction standing with %s has been adjusted by %i.", name, tmpvalue); //New Live faction message (14261) + + return; +} + +void Client::LoadAccountFlags() +{ + + accountflags.clear(); + std::string query = StringFormat("SELECT p_flag, p_value " + "FROM account_flags WHERE p_accid = '%d'", + account_id); + auto results = database.QueryDatabase(query); + if (!results.Success()) { + std::cerr << "Error in LoadAccountFlags query '" << query << "' " << results.ErrorMessage() << std::endl; + return; + } + + for (auto row = results.begin(); row != results.end(); ++row) + accountflags[row[0]] = row[1]; +} + +void Client::SetAccountFlag(std::string flag, std::string val) { + + std::string query = StringFormat("REPLACE INTO account_flags (p_accid, p_flag, p_value) " + "VALUES( '%d', '%s', '%s')", + account_id, flag.c_str(), val.c_str()); + auto results = database.QueryDatabase(query); + if(!results.Success()) { + std::cerr << "Error in SetAccountFlags query '" << query << "' " << results.ErrorMessage() << std::endl; + return; + } + + accountflags[flag] = val; +} + +std::string Client::GetAccountFlag(std::string flag) +{ + return(accountflags[flag]); +} + +void Client::TickItemCheck() +{ + int i; + + if(zone->tick_items.empty()) { return; } + + //Scan equip slots for items + for(i = EmuConstants::EQUIPMENT_BEGIN; i <= EmuConstants::EQUIPMENT_END; i++) + { + TryItemTick(i); + } + //Scan main inventory + cursor + for(i = EmuConstants::GENERAL_BEGIN; i <= MainCursor; i++) + { + TryItemTick(i); + } + //Scan bags + for(i = EmuConstants::GENERAL_BAGS_BEGIN; i <= EmuConstants::CURSOR_BAG_END; i++) + { + TryItemTick(i); + } +} + +void Client::TryItemTick(int slot) +{ + int iid = 0; + const ItemInst* inst = m_inv[slot]; + if(inst == 0) { return; } + + iid = inst->GetID(); + + if(zone->tick_items.count(iid) > 0) + { + if( GetLevel() >= zone->tick_items[iid].level && zone->random.Int(0, 100) >= (100 - zone->tick_items[iid].chance) && (zone->tick_items[iid].bagslot || slot <= EmuConstants::EQUIPMENT_END) ) + { + ItemInst* e_inst = (ItemInst*)inst; + parse->EventItem(EVENT_ITEM_TICK, this, e_inst, nullptr, "", slot); + } + } + + //Only look at augs in main inventory + if(slot > EmuConstants::EQUIPMENT_END) { return; } + + for (int x = AUG_BEGIN; x < EmuConstants::ITEM_COMMON_SIZE; ++x) + { + ItemInst * a_inst = inst->GetAugment(x); + if(!a_inst) { continue; } + + iid = a_inst->GetID(); + + if(zone->tick_items.count(iid) > 0) + { + if( GetLevel() >= zone->tick_items[iid].level && zone->random.Int(0, 100) >= (100 - zone->tick_items[iid].chance) ) + { + ItemInst* e_inst = (ItemInst*)a_inst; + parse->EventItem(EVENT_ITEM_TICK, this, e_inst, nullptr, "", slot); + } + } + } +} + +void Client::ItemTimerCheck() +{ + int i; + for(i = EmuConstants::EQUIPMENT_BEGIN; i <= EmuConstants::EQUIPMENT_END; i++) + { + TryItemTimer(i); + } + + for(i = EmuConstants::GENERAL_BEGIN; i <= MainCursor; i++) + { + TryItemTimer(i); + } + + for(i = EmuConstants::GENERAL_BAGS_BEGIN; i <= EmuConstants::CURSOR_BAG_END; i++) + { + TryItemTimer(i); + } +} + +void Client::TryItemTimer(int slot) +{ + ItemInst* inst = m_inv.GetItem(slot); + if(!inst) { + return; + } + + auto item_timers = inst->GetTimers(); + auto it_iter = item_timers.begin(); + while(it_iter != item_timers.end()) { + if(it_iter->second.Check()) { + parse->EventItem(EVENT_TIMER, this, inst, nullptr, it_iter->first, 0); + } + ++it_iter; + } + + if(slot > EmuConstants::EQUIPMENT_END) { + return; + } + + for (int x = AUG_BEGIN; x < EmuConstants::ITEM_COMMON_SIZE; ++x) + { + ItemInst * a_inst = inst->GetAugment(x); + if(!a_inst) { + continue; + } + + auto item_timers = a_inst->GetTimers(); + auto it_iter = item_timers.begin(); + while(it_iter != item_timers.end()) { + if(it_iter->second.Check()) { + parse->EventItem(EVENT_TIMER, this, a_inst, nullptr, it_iter->first, 0); + } + ++it_iter; + } + } +} + +void Client::RefundAA() { + int cur = 0; + bool refunded = false; + + for(int x = 0; x < aaHighestID; x++) { + cur = GetAA(x); + if(cur > 0){ + SendAA_Struct* curaa = zone->FindAA(x); + if(cur){ + SetAA(x, 0); + for(int j = 0; j < cur; j++) { + m_pp.aapoints += curaa->cost + (curaa->cost_inc * j); + refunded = true; + } + } + else + { + m_pp.aapoints += cur; + SetAA(x, 0); + refunded = true; + } + } + } + + if(refunded) { + SaveAA(); + Save(); + // Kick(); + } +} + +void Client::IncrementAA(int aa_id) { + SendAA_Struct* aa2 = zone->FindAA(aa_id); + + if(aa2 == nullptr) + return; + + if(GetAA(aa_id) == aa2->max_level) + return; + + SetAA(aa_id, GetAA(aa_id) + 1); + + SaveAA(); + + SendAA(aa_id); + SendAATable(); + SendAAStats(); + CalcBonuses(); +} + +void Client::SendItemScale(ItemInst *inst) { + int slot = m_inv.GetSlotByItemInst(inst); + if(slot != -1) { + inst->ScaleItem(); + SendItemPacket(slot, inst, ItemPacketCharmUpdate); + CalcBonuses(); + } +} + +void Client::AddRespawnOption(std::string option_name, uint32 zoneid, uint16 instance_id, float x, float y, float z, float heading, bool initial_selection, int8 position) +{ + //If respawn window is already open, any changes would create an inconsistency with the client + if (IsHoveringForRespawn()) { return; } + + if (zoneid == 0) + zoneid = zone->GetZoneID(); + + //Create respawn option + RespawnOption res_opt; + res_opt.name = option_name; + res_opt.zone_id = zoneid; + res_opt.instance_id = instance_id; + res_opt.x = x; + res_opt.y = y; + res_opt.z = z; + res_opt.heading = heading; + + if (position == -1 || position >= respawn_options.size()) + { + //No position specified, or specified beyond the end, simply append + respawn_options.push_back(res_opt); + //Make this option the initial selection for the window if desired + if (initial_selection) + initial_respawn_selection = static_cast(respawn_options.size()) - 1; + } + else if (position == 0) + { + respawn_options.push_front(res_opt); + if (initial_selection) + initial_respawn_selection = 0; + } + else + { + //Insert new option between existing options + std::list::iterator itr; + uint8 pos = 0; + for (itr = respawn_options.begin(); itr != respawn_options.end(); ++itr) + { + if (pos++ == position) + { + respawn_options.insert(itr,res_opt); + //Make this option the initial selection for the window if desired + if (initial_selection) + initial_respawn_selection = pos; + return; + } + } + } +} + +bool Client::RemoveRespawnOption(std::string option_name) +{ + //If respawn window is already open, any changes would create an inconsistency with the client + if (IsHoveringForRespawn() || respawn_options.empty()) { return false; } + + bool had = false; + RespawnOption* opt; + std::list::iterator itr; + for (itr = respawn_options.begin(); itr != respawn_options.end(); ++itr) + { + opt = &(*itr); + if (opt->name.compare(option_name) == 0) + { + itr = respawn_options.erase(itr); + had = true; + //could be more with the same name, so keep going... + } + } + return had; +} + +bool Client::RemoveRespawnOption(uint8 position) +{ + //If respawn window is already open, any changes would create an inconsistency with the client + if (IsHoveringForRespawn() || respawn_options.empty()) { return false; } + + //Easy cases first... + if (position == 0) + { + respawn_options.pop_front(); + return true; + } + else if (position == (respawn_options.size() - 1)) + { + respawn_options.pop_back(); + return true; + } + + std::list::iterator itr; + uint8 pos = 0; + for (itr = respawn_options.begin(); itr != respawn_options.end(); ++itr) + { + if (pos++ == position) + { + respawn_options.erase(itr); + return true; + } + } + return false; +} + +void Client::SetHunger(int32 in_hunger) +{ + EQApplicationPacket *outapp; + outapp = new EQApplicationPacket(OP_Stamina, sizeof(Stamina_Struct)); + Stamina_Struct* sta = (Stamina_Struct*)outapp->pBuffer; + sta->food = in_hunger; + sta->water = m_pp.thirst_level > 6000 ? 6000 : m_pp.thirst_level; + + m_pp.hunger_level = in_hunger; + + QueuePacket(outapp); + safe_delete(outapp); +} + +void Client::SetThirst(int32 in_thirst) +{ + EQApplicationPacket *outapp; + outapp = new EQApplicationPacket(OP_Stamina, sizeof(Stamina_Struct)); + Stamina_Struct* sta = (Stamina_Struct*)outapp->pBuffer; + sta->food = m_pp.hunger_level > 6000 ? 6000 : m_pp.hunger_level; + sta->water = in_thirst; + + m_pp.thirst_level = in_thirst; + + QueuePacket(outapp); + safe_delete(outapp); +} + +void Client::SetConsumption(int32 in_hunger, int32 in_thirst) +{ + EQApplicationPacket *outapp; + outapp = new EQApplicationPacket(OP_Stamina, sizeof(Stamina_Struct)); + Stamina_Struct* sta = (Stamina_Struct*)outapp->pBuffer; + sta->food = in_hunger; + sta->water = in_thirst; + + m_pp.hunger_level = in_hunger; + m_pp.thirst_level = in_thirst; + + QueuePacket(outapp); + safe_delete(outapp); +} + +void Client::Consume(const Item_Struct *item, uint8 type, int16 slot, bool auto_consume) +{ + if(!item) { return; } + + uint32 cons_mod = 180; + + int32 metabolism_bonus = spellbonuses.Metabolism + itembonuses.Metabolism + aabonuses.Metabolism; + + if (metabolism_bonus) + cons_mod = cons_mod * metabolism_bonus * RuleI(Character, ConsumptionMultiplier) / 10000; + else + cons_mod = cons_mod * RuleI(Character, ConsumptionMultiplier) / 100; + + if(type == ItemTypeFood) + { + int hchange = item->CastTime * cons_mod; + hchange = mod_food_value(item, hchange); + + if(hchange < 0) { return; } + + m_pp.hunger_level += hchange; + DeleteItemInInventory(slot, 1, false); + + if(!auto_consume) //no message if the client consumed for us + entity_list.MessageClose_StringID(this, true, 50, 0, EATING_MESSAGE, GetName(), item->Name); + +#if EQDEBUG >= 5 + LogFile->write(EQEMuLog::Debug, "Eating from slot:%i", (int)slot); +#endif + } + else + { + int tchange = item->CastTime * cons_mod; + tchange = mod_drink_value(item, tchange); + + if(tchange < 0) { return; } + + m_pp.thirst_level += tchange; + DeleteItemInInventory(slot, 1, false); + + if(!auto_consume) //no message if the client consumed for us + entity_list.MessageClose_StringID(this, true, 50, 0, DRINKING_MESSAGE, GetName(), item->Name); + +#if EQDEBUG >= 5 + LogFile->write(EQEMuLog::Debug, "Drinking from slot:%i", (int)slot); +#endif + } +} + +void Client::SendMarqueeMessage(uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, std::string msg) +{ + if(duration == 0 || msg.length() == 0) { + return; + } + + EQApplicationPacket outapp(OP_Marquee, sizeof(ClientMarqueeMessage_Struct) + msg.length()); + ClientMarqueeMessage_Struct *cms = (ClientMarqueeMessage_Struct*)outapp.pBuffer; + + cms->type = type; + cms->unk04 = 10; + cms->priority = priority; + cms->fade_in_time = fade_in; + cms->fade_out_time = fade_out; + cms->duration = duration; + strcpy(cms->msg, msg.c_str()); + + QueuePacket(&outapp); +} + +void Client::PlayMP3(const char* fname) +{ + std::string filename = fname; + EQApplicationPacket *outapp = new EQApplicationPacket(OP_PlayMP3, filename.length() + 1); + PlayMP3_Struct* buf = (PlayMP3_Struct*)outapp->pBuffer; + strncpy(buf->filename, fname, filename.length()); + QueuePacket(outapp); + safe_delete(outapp); +} + +void Client::ExpeditionSay(const char *str, int ExpID) { + + std::string query = StringFormat("SELECT `player_name` FROM `cust_inst_players` " + "WHERE `inst_id` = %i", ExpID); + auto results = database.QueryDatabase(query); + if (!results.Success()) + return; + + if(results.RowCount() == 0) { + this->Message(14, "You say to the expedition, '%s'", str); + return; + } + + for(auto row = results.begin(); row != results.end(); ++row) { + const char* charName = row[0]; + if(strcmp(charName, this->GetCleanName()) != 0) + worldserver.SendEmoteMessage(charName, 0, 0, 14, "%s says to the expedition, '%s'", this->GetCleanName(), str); + // ChannelList->CreateChannel(ChannelName, ChannelOwner, ChannelPassword, true, atoi(row[3])); + } + + +} + +void Client::ShowNumHits() +{ + uint32 buffcount = GetMaxTotalSlots(); + for (uint32 buffslot = 0; buffslot < buffcount; buffslot++) { + const Buffs_Struct &curbuff = buffs[buffslot]; + if (curbuff.spellid != SPELL_UNKNOWN && curbuff.numhits) + Message(0, "You have %d hits left on %s", curbuff.numhits, GetSpellName(curbuff.spellid)); + } + return; +} + +float Client::GetQuiverHaste() +{ + float quiver_haste = 0; + for (int r = EmuConstants::GENERAL_BEGIN; r <= EmuConstants::GENERAL_END; r++) { + const ItemInst *pi = GetInv().GetItem(r); + if (!pi) + continue; + if (pi->IsType(ItemClassContainer) && pi->GetItem()->BagType == BagTypeQuiver) { + float temp_wr = (pi->GetItem()->BagWR / RuleI(Combat, QuiverWRHasteDiv)); + quiver_haste = std::max(temp_wr, quiver_haste); + } + } + if (quiver_haste > 0) + quiver_haste = 1.0f / (1.0f + static_cast(quiver_haste) / 100.0f); + return quiver_haste; +} + +void Client::SendColoredText(uint32 color, std::string message) +{ + // arbitrary size limit + if (message.size() > 512) // live does send this with empty strings sometimes ... + return; + EQApplicationPacket *outapp = new EQApplicationPacket(OP_ColoredText, + sizeof(ColoredText_Struct) + message.size()); + ColoredText_Struct *cts = (ColoredText_Struct *)outapp->pBuffer; + cts->color = color; + strcpy(cts->msg, message.c_str()); + QueuePacket(outapp); + safe_delete(outapp); +} + diff --git a/zone/client.h b/zone/client.h index b39777046..245e5803c 100644 --- a/zone/client.h +++ b/zone/client.h @@ -810,7 +810,7 @@ public: void QSSwapItemAuditor(MoveItem_Struct* move_in, bool postaction_call = false); void PutLootInInventory(int16 slot_id, const ItemInst &inst, ServerLootItem_Struct** bag_item_data = 0); bool AutoPutLootInInventory(ItemInst& inst, bool try_worn = false, bool try_cursor = true, ServerLootItem_Struct** bag_item_data = 0); - bool SummonItem(uint32 item_id, int16 charges = -1, uint32 aug1 = 0, uint32 aug2 = 0, uint32 aug3 = 0, uint32 aug4 = 0, uint32 aug5 = 0, bool attuned = false, uint16 to_slot = MainCursor); + bool SummonItem(uint32 item_id, int16 charges = -1, uint32 aug1 = 0, uint32 aug2 = 0, uint32 aug3 = 0, uint32 aug4 = 0, uint32 aug5 = 0, bool attuned = false, uint16 to_slot = MainCursor, uint32 ornament_icon = 0, uint32 ornament_idfile = 0); void SetStats(uint8 type,int16 set_val); void IncStats(uint8 type,int16 increase_val); void DropItem(int16 slot_id); diff --git a/zone/client_mods.cpp b/zone/client_mods.cpp index 5431b2dfe..55b522378 100644 --- a/zone/client_mods.cpp +++ b/zone/client_mods.cpp @@ -18,23 +18,17 @@ #include #include "../common/debug.h" -#include "../common/spdat.h" -#include "../common/packet_dump.h" -#include "../common/packet_functions.h" -#include "../common/serverinfo.h" -#include "../common/zone_numbers.h" -#include "../common/moremath.h" -#include "../common/guilds.h" #include "../common/logsys.h" +#include "../common/spdat.h" +#include "../common/rulesys.h" #include "masterentity.h" -#include "worldserver.h" -#include "zonedb.h" +#include "npc_ai.h" #include "petitions.h" #include "string_ids.h" -#include "npc_ai.h" +#include "worldserver.h" +#include "zonedb.h" -// Return max stat value for level int32 Client::GetMaxStat() const { if((RuleI(Character, StatCap)) > 0) diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index ad6e3745b..eb5fe8998 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -16,14 +16,11 @@ */ #include "../common/debug.h" -#include #include #include #include #include -#include #include -#include #include #include @@ -38,39 +35,28 @@ #include #endif -#include "../common/packet_functions.h" -#include "../common/packet_dump.h" -#include "worldserver.h" -#include "../common/rdtsc.h" -#include "../common/packet_dump_file.h" -#include "../common/string_util.h" -#include "../common/breakdowns.h" -#include "../common/guilds.h" -#include "../common/rulesys.h" -#include "../common/spdat.h" -#include "../common/data_verification.h" -#include "petitions.h" -#include "npc_ai.h" -#include "../common/skills.h" -#include "forage.h" -#include "zone.h" -#include "event_codes.h" -#include "../common/faction.h" #include "../common/crc32.h" +#include "../common/data_verification.h" +#include "../common/faction.h" +#include "../common/guilds.h" +#include "../common/rdtsc.h" +#include "../common/rulesys.h" +#include "../common/skills.h" +#include "../common/spdat.h" +#include "../common/string_util.h" +#include "../common/zone_numbers.h" +#include "event_codes.h" +#include "guild_mgr.h" +#include "merc.h" +#include "petitions.h" +#include "pets.h" +#include "queryserv.h" +#include "quest_parser_collection.h" #include "string_ids.h" #include "titles.h" #include "water_map.h" #include "worldserver.h" #include "zone.h" -#include "zone_config.h" -#include "guild_mgr.h" -#include "pathing.h" -#include "water_map.h" -#include "merc.h" -#include "pets.h" -#include "../common/zone_numbers.h" -#include "quest_parser_collection.h" -#include "queryserv.h" extern QueryServ* QServ; extern Zone* zone; @@ -474,10 +460,10 @@ int Client::HandlePacket(const EQApplicationPacket *app) mlog(CLIENT__NET_ERR, "Unhandled incoming opcode: %s", buffer); if(app->size < 1000) - DumpPacket(app->pBuffer, app->size); + DumpPacket(app, app->size); else{ std::cout << "Dump limited to 1000 characters:\n"; - DumpPacket(app->pBuffer, 1000); + DumpPacket(app, 1000); } #endif break; @@ -2968,7 +2954,7 @@ void Client::Handle_OP_ApplyPoison(const EQApplicationPacket *app) if ((PrimaryWeapon && PrimaryWeapon->GetItem()->ItemType == ItemType1HPiercing) || (SecondaryWeapon && SecondaryWeapon->GetItem()->ItemType == ItemType1HPiercing)) { float SuccessChance = (GetSkill(SkillApplyPoison) + GetLevel()) / 400.0f; - double ChanceRoll = MakeRandomFloat(0, 1); + double ChanceRoll = zone->random.Real(0, 1); CheckIncreaseSkill(SkillApplyPoison, nullptr, 10); @@ -3642,14 +3628,14 @@ void Client::Handle_OP_Begging(const EQApplicationPacket *app) return; } - int RandomChance = MakeRandomInt(0, 100); + int RandomChance = zone->random.Int(0, 100); int ChanceToAttack = 0; if (GetLevel() > GetTarget()->GetLevel()) - ChanceToAttack = MakeRandomInt(0, 15); + ChanceToAttack = zone->random.Int(0, 15); else - ChanceToAttack = MakeRandomInt(((this->GetTarget()->GetLevel() - this->GetLevel()) * 10) - 5, ((this->GetTarget()->GetLevel() - this->GetLevel()) * 10)); + ChanceToAttack = zone->random.Int(((this->GetTarget()->GetLevel() - this->GetLevel()) * 10) - 5, ((this->GetTarget()->GetLevel() - this->GetLevel()) * 10)); if (ChanceToAttack < 0) ChanceToAttack = -ChanceToAttack; @@ -3668,7 +3654,7 @@ void Client::Handle_OP_Begging(const EQApplicationPacket *app) if (RandomChance < ChanceToBeg) { - brs->Amount = MakeRandomInt(1, 10); + brs->Amount = zone->random.Int(1, 10); // This needs some work to determine how much money they can beg, based on skill level etc. if (CurrentSkill < 50) { @@ -4537,7 +4523,7 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) m_Delta = xyz_heading(ppu->delta_x, ppu->delta_y, ppu->delta_z, ppu->delta_heading); if(IsTracking() && ((m_Position.m_X!=ppu->x_pos) || (m_Position.m_Y!=ppu->y_pos))){ - if(MakeRandomFloat(0, 100) < 70)//should be good + if(zone->random.Real(0, 100) < 70)//should be good CheckIncreaseSkill(SkillTracking, nullptr, -20); } @@ -5235,16 +5221,16 @@ void Client::Handle_OP_DisarmTraps(const EQApplicationPacket *app) if (trap && trap->detected) { int uskill = GetSkill(SkillDisarmTraps); - if ((MakeRandomInt(0, 49) + uskill) >= (MakeRandomInt(0, 49) + trap->skill)) + if ((zone->random.Int(0, 49) + uskill) >= (zone->random.Int(0, 49) + trap->skill)) { Message(MT_Skills, "You disarm a trap."); trap->disarmed = true; trap->chkarea_timer.Disable(); - trap->respawn_timer.Start((trap->respawn_time + MakeRandomInt(0, trap->respawn_var)) * 1000); + trap->respawn_timer.Start((trap->respawn_time + zone->random.Int(0, trap->respawn_var)) * 1000); } else { - if (MakeRandomInt(0, 99) < 25){ + if (zone->random.Int(0, 99) < 25){ Message(MT_Skills, "You set off the trap while trying to disarm it!"); trap->Trigger(this); } @@ -5605,7 +5591,7 @@ void Client::Handle_OP_FeignDeath(const EQApplicationPacket *app) secfeign = 0; uint16 totalfeign = primfeign + secfeign; - if (MakeRandomFloat(0, 160) > totalfeign) { + if (zone->random.Real(0, 160) > totalfeign) { SetFeigned(false); entity_list.MessageClose_StringID(this, false, 200, 10, STRING_FEIGNFAILED, GetName()); } @@ -7869,7 +7855,7 @@ void Client::Handle_OP_Hide(const EQApplicationPacket *app) p_timers.Start(pTimerHide, reuse - 1); float hidechance = ((GetSkill(SkillHide) / 250.0f) + .25) * 100; - float random = MakeRandomFloat(0, 100); + float random = zone->random.Real(0, 100); CheckIncreaseSkill(SkillHide, nullptr, 5); if (random < hidechance) { EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct)); @@ -7893,7 +7879,7 @@ void Client::Handle_OP_Hide(const EQApplicationPacket *app) Mob *evadetar = GetTarget(); if (!auto_attack && (evadetar && evadetar->CheckAggro(this) && evadetar->IsNPC())) { - if (MakeRandomInt(0, 260) < (int)GetSkill(SkillHide)) { + if (zone->random.Int(0, 260) < (int)GetSkill(SkillHide)) { msg->string_id = EVADE_SUCCESS; RogueEvade(evadetar); } @@ -8000,6 +7986,10 @@ void Client::Handle_OP_InspectAnswer(const EQApplicationPacket *app) strcpy(insr->itemnames[L], item->Name); insr->itemicons[L] = aug_weap->Icon; } + else if (inst->GetOrnamentationIcon() && inst->GetOrnamentationIDFile()) { + strcpy(insr->itemnames[L], item->Name); + insr->itemicons[L] = inst->GetOrnamentationIcon(); + } else { strcpy(insr->itemnames[L], item->Name); insr->itemicons[L] = item->Icon; @@ -9272,11 +9262,11 @@ void Client::Handle_OP_Mend(const EQApplicationPacket *app) int mendhp = GetMaxHP() / 4; int currenthp = GetHP(); - if (MakeRandomInt(0, 199) < (int)GetSkill(SkillMend)) { + if (zone->random.Int(0, 199) < (int)GetSkill(SkillMend)) { int criticalchance = spellbonuses.CriticalMend + itembonuses.CriticalMend + aabonuses.CriticalMend; - if (MakeRandomInt(0, 99) < criticalchance){ + if (zone->random.Int(0, 99) < criticalchance){ mendhp *= 2; Message_StringID(4, MEND_CRITICAL); } @@ -9291,7 +9281,7 @@ void Client::Handle_OP_Mend(const EQApplicationPacket *app) 0 skill - 25% chance to worsen 20 skill - 23% chance to worsen 50 skill - 16% chance to worsen */ - if ((GetSkill(SkillMend) <= 75) && (MakeRandomInt(GetSkill(SkillMend), 100) < 75) && (MakeRandomInt(1, 3) == 1)) + if ((GetSkill(SkillMend) <= 75) && (zone->random.Int(GetSkill(SkillMend), 100) < 75) && (zone->random.Int(1, 3) == 1)) { SetHP(currenthp > mendhp ? (GetHP() - mendhp) : 1); SendHPUpdate(); @@ -9598,7 +9588,7 @@ void Client::Handle_OP_MercenaryHire(const EQApplicationPacket *app) TakeMoneyFromPP(cost, true); } - // 0 is approved hire request + // approved hire request SendMercMerchantResponsePacket(0); } else @@ -10596,37 +10586,28 @@ void Client::Handle_OP_RaidCommand(const EQApplicationPacket *app) case RaidCommandInviteIntoExisting: case RaidCommandInvite: { Client *i = entity_list.GetClientByName(ri->player_name); - if (i){ - Group *g = i->GetGroup(); - if (g){ - if (g->IsLeader(i) == false) - Message(13, "You can only invite an ungrouped player or group leader to join your raid."); - else{ - //This sends an "invite" to the client in question. - EQApplicationPacket* outapp = new EQApplicationPacket(OP_RaidUpdate, sizeof(RaidGeneral_Struct)); - RaidGeneral_Struct *rg = (RaidGeneral_Struct*)outapp->pBuffer; - strn0cpy(rg->leader_name, ri->leader_name, 64); - strn0cpy(rg->player_name, ri->player_name, 64); - - rg->parameter = 0; - rg->action = 20; - i->QueuePacket(outapp); - safe_delete(outapp); - } - } - else{ - //This sends an "invite" to the client in question. - EQApplicationPacket* outapp = new EQApplicationPacket(OP_RaidUpdate, sizeof(RaidGeneral_Struct)); - RaidGeneral_Struct *rg = (RaidGeneral_Struct*)outapp->pBuffer; - strn0cpy(rg->leader_name, ri->leader_name, 64); - strn0cpy(rg->player_name, ri->player_name, 64); - - rg->parameter = 0; - rg->action = 20; - i->QueuePacket(outapp); - safe_delete(outapp); - } + if (!i) + break; + Group *g = i->GetGroup(); + // These two messages should be generated by the client I think, just do this for now + if (i->HasRaid()) { + Message(13, "%s is already in a raid.", i->GetName()); + break; } + if (g && !g->IsLeader(i)) { + Message(13, "You can only invite an ungrouped player or group leader to join your raid."); + break; + } + //This sends an "invite" to the client in question. + EQApplicationPacket* outapp = new EQApplicationPacket(OP_RaidUpdate, sizeof(RaidGeneral_Struct)); + RaidGeneral_Struct *rg = (RaidGeneral_Struct*)outapp->pBuffer; + strn0cpy(rg->leader_name, ri->leader_name, 64); + strn0cpy(rg->player_name, ri->player_name, 64); + + rg->parameter = 0; + rg->action = 20; + i->QueuePacket(outapp); + safe_delete(outapp); break; } case RaidCommandAcceptInvite: { @@ -11192,7 +11173,7 @@ void Client::Handle_OP_RandomReq(const EQApplicationPacket *app) randLow = 0; randHigh = 100; } - randResult = MakeRandomInt(randLow, randHigh); + randResult = zone->random.Int(randLow, randHigh); EQApplicationPacket* outapp = new EQApplicationPacket(OP_RandomReply, sizeof(RandomReply_Struct)); RandomReply_Struct* rr = (RandomReply_Struct*)outapp->pBuffer; @@ -11682,7 +11663,7 @@ void Client::Handle_OP_SenseTraps(const EQApplicationPacket *app) if (trap && trap->skill > 0) { int uskill = GetSkill(SkillSenseTraps); - if ((MakeRandomInt(0, 99) + uskill) >= (MakeRandomInt(0, 99) + trap->skill*0.75)) + if ((zone->random.Int(0, 99) + uskill) >= (zone->random.Int(0, 99) + trap->skill*0.75)) { auto diff = trap->m_Position - GetPosition(); @@ -12425,7 +12406,7 @@ void Client::Handle_OP_ShopRequest(const EQApplicationPacket *app) // 1199 I don't have time for that now. etc if (!tmp->CastToNPC()->IsMerchantOpen()) { - tmp->Say_StringID(MakeRandomInt(1199, 1202)); + tmp->Say_StringID(zone->random.Int(1199, 1202)); action = 0; } @@ -12480,7 +12461,7 @@ void Client::Handle_OP_Sneak(const EQApplicationPacket *app) CheckIncreaseSkill(SkillSneak, nullptr, 5); } float hidechance = ((GetSkill(SkillSneak) / 300.0f) + .25) * 100; - float random = MakeRandomFloat(0, 99); + float random = zone->random.Real(0, 99); if (!was && random < hidechance) { sneaking = true; } diff --git a/zone/client_packet.cpp.orig b/zone/client_packet.cpp.orig new file mode 100644 index 000000000..aad05b175 --- /dev/null +++ b/zone/client_packet.cpp.orig @@ -0,0 +1,14039 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2009 EQEMu Development Team (http://eqemulator.net) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY except by those people which sell it, which + are required to give you total support for your newly bought product; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "../common/debug.h" +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WINDOWS + #define snprintf _snprintf + #define strncasecmp _strnicmp + #define strcasecmp _stricmp +#else + #include + #include + #include + #include +#endif + +#include "../common/crc32.h" +#include "../common/data_verification.h" +#include "../common/faction.h" +#include "../common/guilds.h" +#include "../common/rdtsc.h" +#include "../common/rulesys.h" +#include "../common/skills.h" +#include "../common/spdat.h" +#include "../common/string_util.h" +#include "../common/zone_numbers.h" +#include "event_codes.h" +#include "guild_mgr.h" +#include "merc.h" +#include "petitions.h" +#include "pets.h" +#include "queryserv.h" +#include "quest_parser_collection.h" +#include "string_ids.h" +#include "titles.h" +#include "water_map.h" +#include "worldserver.h" +#include "zone.h" + +extern QueryServ* QServ; +extern Zone* zone; +extern volatile bool ZoneLoaded; +extern WorldServer worldserver; +extern PetitionList petition_list; +extern EntityList entity_list; +typedef void (Client::*ClientPacketProc)(const EQApplicationPacket *app); + +//Use a map for connecting opcodes since it dosent get used a lot and is sparse +std::map ConnectingOpcodes; +//Use a static array for connected, for speed +ClientPacketProc ConnectedOpcodes[_maxEmuOpcode]; + +void MapOpcodes() +{ + ConnectingOpcodes.clear(); + memset(ConnectedOpcodes, 0, sizeof(ConnectedOpcodes)); + + // Now put all the opcodes into their home... + // connecting opcode handler assignments: + ConnectingOpcodes[OP_ApproveZone] = &Client::Handle_Connect_OP_ApproveZone; + ConnectingOpcodes[OP_BlockedBuffs] = &Client::Handle_OP_BlockedBuffs; + ConnectingOpcodes[OP_ClientError] = &Client::Handle_Connect_OP_ClientError; + ConnectingOpcodes[OP_ClientReady] = &Client::Handle_Connect_OP_ClientReady; + ConnectingOpcodes[OP_ClientUpdate] = &Client::Handle_Connect_OP_ClientUpdate; + ConnectingOpcodes[OP_GetGuildsList] = &Client::Handle_OP_GetGuildsList; // temporary hack + ConnectingOpcodes[OP_ReqClientSpawn] = &Client::Handle_Connect_OP_ReqClientSpawn; + ConnectingOpcodes[OP_ReqNewZone] = &Client::Handle_Connect_OP_ReqNewZone; + ConnectingOpcodes[OP_SendAAStats] = &Client::Handle_Connect_OP_SendAAStats; + ConnectingOpcodes[OP_SendAATable] = &Client::Handle_Connect_OP_SendAATable; + ConnectingOpcodes[OP_SendExpZonein] = &Client::Handle_Connect_OP_SendExpZonein; + ConnectingOpcodes[OP_SendGuildTributes] = &Client::Handle_Connect_OP_SendGuildTributes; + ConnectingOpcodes[OP_SendGuildTributes] = &Client::Handle_Connect_OP_SendGuildTributes; // I guess it didn't believe us with the first assignment? + ConnectingOpcodes[OP_SendTributes] = &Client::Handle_Connect_OP_SendTributes; + ConnectingOpcodes[OP_SetServerFilter] = &Client::Handle_Connect_OP_SetServerFilter; + ConnectingOpcodes[OP_SpawnAppearance] = &Client::Handle_Connect_OP_SpawnAppearance; + ConnectingOpcodes[OP_TGB] = &Client::Handle_Connect_OP_TGB; + ConnectingOpcodes[OP_UpdateAA] = &Client::Handle_Connect_OP_UpdateAA; + ConnectingOpcodes[OP_WearChange] = &Client::Handle_Connect_OP_WearChange; + ConnectingOpcodes[OP_WorldObjectsSent] = &Client::Handle_Connect_OP_WorldObjectsSent; + ConnectingOpcodes[OP_XTargetAutoAddHaters] = &Client::Handle_OP_XTargetAutoAddHaters; + ConnectingOpcodes[OP_XTargetRequest] = &Client::Handle_OP_XTargetRequest; + ConnectingOpcodes[OP_ZoneComplete] = &Client::Handle_Connect_OP_ZoneComplete; + ConnectingOpcodes[OP_ZoneEntry] = &Client::Handle_Connect_OP_ZoneEntry; + + // connected opcode handler assignments: + ConnectedOpcodes[OP_0x0193] = &Client::Handle_0x0193; + ConnectedOpcodes[OP_AAAction] = &Client::Handle_OP_AAAction; + ConnectedOpcodes[OP_AcceptNewTask] = &Client::Handle_OP_AcceptNewTask; + ConnectedOpcodes[OP_AdventureInfoRequest] = &Client::Handle_OP_AdventureInfoRequest; + ConnectedOpcodes[OP_AdventureLeaderboardRequest] = &Client::Handle_OP_AdventureLeaderboardRequest; + ConnectedOpcodes[OP_AdventureMerchantPurchase] = &Client::Handle_OP_AdventureMerchantPurchase; + ConnectedOpcodes[OP_AdventureMerchantRequest] = &Client::Handle_OP_AdventureMerchantRequest; + ConnectedOpcodes[OP_AdventureMerchantSell] = &Client::Handle_OP_AdventureMerchantSell; + ConnectedOpcodes[OP_AdventureRequest] = &Client::Handle_OP_AdventureRequest; + ConnectedOpcodes[OP_AdventureStatsRequest] = &Client::Handle_OP_AdventureStatsRequest; + ConnectedOpcodes[OP_AltCurrencyMerchantRequest] = &Client::Handle_OP_AltCurrencyMerchantRequest; + ConnectedOpcodes[OP_AltCurrencyPurchase] = &Client::Handle_OP_AltCurrencyPurchase; + ConnectedOpcodes[OP_AltCurrencyReclaim] = &Client::Handle_OP_AltCurrencyReclaim; + ConnectedOpcodes[OP_AltCurrencySell] = &Client::Handle_OP_AltCurrencySell; + ConnectedOpcodes[OP_AltCurrencySellSelection] = &Client::Handle_OP_AltCurrencySellSelection; + ConnectedOpcodes[OP_Animation] = &Client::Handle_OP_Animation; + ConnectedOpcodes[OP_ApplyPoison] = &Client::Handle_OP_ApplyPoison; + ConnectedOpcodes[OP_Assist] = &Client::Handle_OP_Assist; + ConnectedOpcodes[OP_AssistGroup] = &Client::Handle_OP_AssistGroup; + ConnectedOpcodes[OP_AugmentInfo] = &Client::Handle_OP_AugmentInfo; + ConnectedOpcodes[OP_AugmentItem] = &Client::Handle_OP_AugmentItem; + ConnectedOpcodes[OP_AutoAttack] = &Client::Handle_OP_AutoAttack; + ConnectedOpcodes[OP_AutoAttack2] = &Client::Handle_OP_AutoAttack2; + ConnectedOpcodes[OP_AutoFire] = &Client::Handle_OP_AutoFire; + ConnectedOpcodes[OP_Bandolier] = &Client::Handle_OP_Bandolier; + ConnectedOpcodes[OP_BankerChange] = &Client::Handle_OP_BankerChange; + ConnectedOpcodes[OP_Barter] = &Client::Handle_OP_Barter; + ConnectedOpcodes[OP_BazaarInspect] = &Client::Handle_OP_BazaarInspect; + ConnectedOpcodes[OP_BazaarSearch] = &Client::Handle_OP_BazaarSearch; + ConnectedOpcodes[OP_Begging] = &Client::Handle_OP_Begging; + ConnectedOpcodes[OP_Bind_Wound] = &Client::Handle_OP_Bind_Wound; + ConnectedOpcodes[OP_BlockedBuffs] = &Client::Handle_OP_BlockedBuffs; + ConnectedOpcodes[OP_BoardBoat] = &Client::Handle_OP_BoardBoat; + ConnectedOpcodes[OP_Buff] = &Client::Handle_OP_Buff; + ConnectedOpcodes[OP_BuffRemoveRequest] = &Client::Handle_OP_BuffRemoveRequest; + ConnectedOpcodes[OP_Bug] = &Client::Handle_OP_Bug; + ConnectedOpcodes[OP_Camp] = &Client::Handle_OP_Camp; + ConnectedOpcodes[OP_CancelTask] = &Client::Handle_OP_CancelTask; + ConnectedOpcodes[OP_CancelTrade] = &Client::Handle_OP_CancelTrade; + ConnectedOpcodes[OP_CastSpell] = &Client::Handle_OP_CastSpell; + ConnectedOpcodes[OP_ChannelMessage] = &Client::Handle_OP_ChannelMessage; + ConnectedOpcodes[OP_ClearBlockedBuffs] = &Client::Handle_OP_ClearBlockedBuffs; + ConnectedOpcodes[OP_ClearNPCMarks] = &Client::Handle_OP_ClearNPCMarks; + ConnectedOpcodes[OP_ClearSurname] = &Client::Handle_OP_ClearSurname; + ConnectedOpcodes[OP_ClickDoor] = &Client::Handle_OP_ClickDoor; + ConnectedOpcodes[OP_ClickObject] = &Client::Handle_OP_ClickObject; + ConnectedOpcodes[OP_ClickObjectAction] = &Client::Handle_OP_ClickObjectAction; + ConnectedOpcodes[OP_ClientError] = &Client::Handle_OP_ClientError; + ConnectedOpcodes[OP_ClientTimeStamp] = &Client::Handle_OP_ClientTimeStamp; + ConnectedOpcodes[OP_ClientUpdate] = &Client::Handle_OP_ClientUpdate; + ConnectedOpcodes[OP_CombatAbility] = &Client::Handle_OP_CombatAbility; + ConnectedOpcodes[OP_ConfirmDelete] = &Client::Handle_OP_ConfirmDelete; + ConnectedOpcodes[OP_Consent] = &Client::Handle_OP_Consent; + ConnectedOpcodes[OP_ConsentDeny] = &Client::Handle_OP_ConsentDeny; + ConnectedOpcodes[OP_Consider] = &Client::Handle_OP_Consider; + ConnectedOpcodes[OP_ConsiderCorpse] = &Client::Handle_OP_ConsiderCorpse; + ConnectedOpcodes[OP_Consume] = &Client::Handle_OP_Consume; + ConnectedOpcodes[OP_ControlBoat] = &Client::Handle_OP_ControlBoat; + ConnectedOpcodes[OP_CorpseDrag] = &Client::Handle_OP_CorpseDrag; + ConnectedOpcodes[OP_CorpseDrop] = &Client::Handle_OP_CorpseDrop; + ConnectedOpcodes[OP_CrashDump] = &Client::Handle_OP_CrashDump; + ConnectedOpcodes[OP_CrystalCreate] = &Client::Handle_OP_CrystalCreate; + ConnectedOpcodes[OP_CrystalReclaim] = &Client::Handle_OP_CrystalReclaim; + ConnectedOpcodes[OP_Damage] = &Client::Handle_OP_Damage; + ConnectedOpcodes[OP_Death] = &Client::Handle_OP_Death; + ConnectedOpcodes[OP_DelegateAbility] = &Client::Handle_OP_DelegateAbility; + ConnectedOpcodes[OP_DeleteItem] = &Client::Handle_OP_DeleteItem; + ConnectedOpcodes[OP_DeleteSpawn] = &Client::Handle_OP_DeleteSpawn; + ConnectedOpcodes[OP_DeleteSpell] = &Client::Handle_OP_DeleteSpell; + ConnectedOpcodes[OP_DisarmTraps] = &Client::Handle_OP_DisarmTraps; + ConnectedOpcodes[OP_DoGroupLeadershipAbility] = &Client::Handle_OP_DoGroupLeadershipAbility; + ConnectedOpcodes[OP_DuelResponse] = &Client::Handle_OP_DuelResponse; + ConnectedOpcodes[OP_DuelResponse2] = &Client::Handle_OP_DuelResponse2; + ConnectedOpcodes[OP_DumpName] = &Client::Handle_OP_DumpName; + ConnectedOpcodes[OP_Dye] = &Client::Handle_OP_Dye; + ConnectedOpcodes[OP_Emote] = &Client::Handle_OP_Emote; + ConnectedOpcodes[OP_EndLootRequest] = &Client::Handle_OP_EndLootRequest; + ConnectedOpcodes[OP_EnvDamage] = &Client::Handle_OP_EnvDamage; + ConnectedOpcodes[OP_FaceChange] = &Client::Handle_OP_FaceChange; + ConnectedOpcodes[OP_FeignDeath] = &Client::Handle_OP_FeignDeath; + ConnectedOpcodes[OP_FindPersonRequest] = &Client::Handle_OP_FindPersonRequest; + ConnectedOpcodes[OP_Fishing] = &Client::Handle_OP_Fishing; + ConnectedOpcodes[OP_FloatListThing] = &Client::Handle_OP_Ignore; + ConnectedOpcodes[OP_Forage] = &Client::Handle_OP_Forage; + ConnectedOpcodes[OP_FriendsWho] = &Client::Handle_OP_FriendsWho; + ConnectedOpcodes[OP_GetGuildMOTD] = &Client::Handle_OP_GetGuildMOTD; + ConnectedOpcodes[OP_GetGuildsList] = &Client::Handle_OP_GetGuildsList; + ConnectedOpcodes[OP_GMBecomeNPC] = &Client::Handle_OP_GMBecomeNPC; + ConnectedOpcodes[OP_GMDelCorpse] = &Client::Handle_OP_GMDelCorpse; + ConnectedOpcodes[OP_GMEmoteZone] = &Client::Handle_OP_GMEmoteZone; + ConnectedOpcodes[OP_GMEndTraining] = &Client::Handle_OP_GMEndTraining; + ConnectedOpcodes[OP_GMFind] = &Client::Handle_OP_GMFind; + ConnectedOpcodes[OP_GMGoto] = &Client::Handle_OP_GMGoto; + ConnectedOpcodes[OP_GMHideMe] = &Client::Handle_OP_GMHideMe; + ConnectedOpcodes[OP_GMKick] = &Client::Handle_OP_GMKick; + ConnectedOpcodes[OP_GMKill] = &Client::Handle_OP_GMKill; + ConnectedOpcodes[OP_GMLastName] = &Client::Handle_OP_GMLastName; + ConnectedOpcodes[OP_GMNameChange] = &Client::Handle_OP_GMNameChange; + ConnectedOpcodes[OP_GMSearchCorpse] = &Client::Handle_OP_GMSearchCorpse; + ConnectedOpcodes[OP_GMServers] = &Client::Handle_OP_GMServers; + ConnectedOpcodes[OP_GMSummon] = &Client::Handle_OP_GMSummon; + ConnectedOpcodes[OP_GMToggle] = &Client::Handle_OP_GMToggle; + ConnectedOpcodes[OP_GMTraining] = &Client::Handle_OP_GMTraining; + ConnectedOpcodes[OP_GMTrainSkill] = &Client::Handle_OP_GMTrainSkill; + ConnectedOpcodes[OP_GMZoneRequest] = &Client::Handle_OP_GMZoneRequest; + ConnectedOpcodes[OP_GMZoneRequest2] = &Client::Handle_OP_GMZoneRequest2; + ConnectedOpcodes[OP_GroundSpawn] = &Client::Handle_OP_CreateObject; + ConnectedOpcodes[OP_GroupAcknowledge] = &Client::Handle_OP_GroupAcknowledge; + ConnectedOpcodes[OP_GroupCancelInvite] = &Client::Handle_OP_GroupCancelInvite; + ConnectedOpcodes[OP_GroupDelete] = &Client::Handle_OP_GroupDelete; + ConnectedOpcodes[OP_GroupDisband] = &Client::Handle_OP_GroupDisband; + ConnectedOpcodes[OP_GroupFollow] = &Client::Handle_OP_GroupFollow; + ConnectedOpcodes[OP_GroupFollow2] = &Client::Handle_OP_GroupFollow2; + ConnectedOpcodes[OP_GroupInvite] = &Client::Handle_OP_GroupInvite; + ConnectedOpcodes[OP_GroupInvite2] = &Client::Handle_OP_GroupInvite2; + ConnectedOpcodes[OP_GroupMakeLeader] = &Client::Handle_OP_GroupMakeLeader; + ConnectedOpcodes[OP_GroupMentor] = &Client::Handle_OP_GroupMentor; + ConnectedOpcodes[OP_GroupRoles] = &Client::Handle_OP_GroupRoles; + ConnectedOpcodes[OP_GroupUpdate] = &Client::Handle_OP_GroupUpdate; + ConnectedOpcodes[OP_GuildBank] = &Client::Handle_OP_GuildBank; + ConnectedOpcodes[OP_GuildCreate] = &Client::Handle_OP_GuildCreate; + ConnectedOpcodes[OP_GuildDelete] = &Client::Handle_OP_GuildDelete; + ConnectedOpcodes[OP_GuildDemote] = &Client::Handle_OP_GuildDemote; + ConnectedOpcodes[OP_GuildInvite] = &Client::Handle_OP_GuildInvite; + ConnectedOpcodes[OP_GuildInviteAccept] = &Client::Handle_OP_GuildInviteAccept; + ConnectedOpcodes[OP_GuildLeader] = &Client::Handle_OP_GuildLeader; + ConnectedOpcodes[OP_GuildManageBanker] = &Client::Handle_OP_GuildManageBanker; + ConnectedOpcodes[OP_GuildPeace] = &Client::Handle_OP_GuildPeace; + ConnectedOpcodes[OP_GuildPromote] = &Client::Handle_OP_GuildPromote; + ConnectedOpcodes[OP_GuildPublicNote] = &Client::Handle_OP_GuildPublicNote; + ConnectedOpcodes[OP_GuildRemove] = &Client::Handle_OP_GuildRemove; + ConnectedOpcodes[OP_GuildStatus] = &Client::Handle_OP_GuildStatus; + ConnectedOpcodes[OP_GuildUpdateURLAndChannel] = &Client::Handle_OP_GuildUpdateURLAndChannel; + ConnectedOpcodes[OP_GuildWar] = &Client::Handle_OP_GuildWar; + ConnectedOpcodes[OP_Heartbeat] = &Client::Handle_OP_Heartbeat; + ConnectedOpcodes[OP_Hide] = &Client::Handle_OP_Hide; + ConnectedOpcodes[OP_HideCorpse] = &Client::Handle_OP_HideCorpse; + ConnectedOpcodes[OP_Illusion] = &Client::Handle_OP_Illusion; + ConnectedOpcodes[OP_InspectAnswer] = &Client::Handle_OP_InspectAnswer; + ConnectedOpcodes[OP_InspectMessageUpdate] = &Client::Handle_OP_InspectMessageUpdate; + ConnectedOpcodes[OP_InspectRequest] = &Client::Handle_OP_InspectRequest; + ConnectedOpcodes[OP_InstillDoubt] = &Client::Handle_OP_InstillDoubt; + ConnectedOpcodes[OP_ItemLinkClick] = &Client::Handle_OP_ItemLinkClick; + ConnectedOpcodes[OP_ItemLinkResponse] = &Client::Handle_OP_ItemLinkResponse; + ConnectedOpcodes[OP_ItemName] = &Client::Handle_OP_ItemName; + ConnectedOpcodes[OP_ItemPreview] = &Client::Handle_OP_ItemPreview; + ConnectedOpcodes[OP_ItemVerifyRequest] = &Client::Handle_OP_ItemVerifyRequest; + ConnectedOpcodes[OP_ItemViewUnknown] = &Client::Handle_OP_Ignore; + ConnectedOpcodes[OP_Jump] = &Client::Handle_OP_Jump; + ConnectedOpcodes[OP_KeyRing] = &Client::Handle_OP_KeyRing; + ConnectedOpcodes[OP_LDoNButton] = &Client::Handle_OP_LDoNButton; + ConnectedOpcodes[OP_LDoNDisarmTraps] = &Client::Handle_OP_LDoNDisarmTraps; + ConnectedOpcodes[OP_LDoNInspect] = &Client::Handle_OP_LDoNInspect; + ConnectedOpcodes[OP_LDoNOpen] = &Client::Handle_OP_LDoNOpen; + ConnectedOpcodes[OP_LDoNPickLock] = &Client::Handle_OP_LDoNPickLock; + ConnectedOpcodes[OP_LDoNSenseTraps] = &Client::Handle_OP_LDoNSenseTraps; + ConnectedOpcodes[OP_LeadershipExpToggle] = &Client::Handle_OP_LeadershipExpToggle; + ConnectedOpcodes[OP_LeaveAdventure] = &Client::Handle_OP_LeaveAdventure; + ConnectedOpcodes[OP_LeaveBoat] = &Client::Handle_OP_LeaveBoat; + ConnectedOpcodes[OP_LFGCommand] = &Client::Handle_OP_LFGCommand; + ConnectedOpcodes[OP_LFGGetMatchesRequest] = &Client::Handle_OP_LFGGetMatchesRequest; + ConnectedOpcodes[OP_LFGuild] = &Client::Handle_OP_LFGuild; + ConnectedOpcodes[OP_LFPCommand] = &Client::Handle_OP_LFPCommand; + ConnectedOpcodes[OP_LFPGetMatchesRequest] = &Client::Handle_OP_LFPGetMatchesRequest; + ConnectedOpcodes[OP_LoadSpellSet] = &Client::Handle_OP_LoadSpellSet; + ConnectedOpcodes[OP_Logout] = &Client::Handle_OP_Logout; + ConnectedOpcodes[OP_LootItem] = &Client::Handle_OP_LootItem; + ConnectedOpcodes[OP_LootRequest] = &Client::Handle_OP_LootRequest; + ConnectedOpcodes[OP_ManaChange] = &Client::Handle_OP_ManaChange; + ConnectedOpcodes[OP_MemorizeSpell] = &Client::Handle_OP_MemorizeSpell; + ConnectedOpcodes[OP_Mend] = &Client::Handle_OP_Mend; + ConnectedOpcodes[OP_MercenaryCommand] = &Client::Handle_OP_MercenaryCommand; + ConnectedOpcodes[OP_MercenaryDataRequest] = &Client::Handle_OP_MercenaryDataRequest; + ConnectedOpcodes[OP_MercenaryDataUpdateRequest] = &Client::Handle_OP_MercenaryDataUpdateRequest; + ConnectedOpcodes[OP_MercenaryDismiss] = &Client::Handle_OP_MercenaryDismiss; + ConnectedOpcodes[OP_MercenaryHire] = &Client::Handle_OP_MercenaryHire; + ConnectedOpcodes[OP_MercenarySuspendRequest] = &Client::Handle_OP_MercenarySuspendRequest; + ConnectedOpcodes[OP_MercenaryTimerRequest] = &Client::Handle_OP_MercenaryTimerRequest; + ConnectedOpcodes[OP_MoveCoin] = &Client::Handle_OP_MoveCoin; + ConnectedOpcodes[OP_MoveItem] = &Client::Handle_OP_MoveItem; + ConnectedOpcodes[OP_OpenContainer] = &Client::Handle_OP_OpenContainer; + ConnectedOpcodes[OP_OpenGuildTributeMaster] = &Client::Handle_OP_OpenGuildTributeMaster; + ConnectedOpcodes[OP_OpenInventory] = &Client::Handle_OP_OpenInventory; + ConnectedOpcodes[OP_OpenTributeMaster] = &Client::Handle_OP_OpenTributeMaster; + ConnectedOpcodes[OP_PDeletePetition] = &Client::Handle_OP_PDeletePetition; + ConnectedOpcodes[OP_PetCommands] = &Client::Handle_OP_PetCommands; + ConnectedOpcodes[OP_Petition] = &Client::Handle_OP_Petition; + ConnectedOpcodes[OP_PetitionBug] = &Client::Handle_OP_PetitionBug; + ConnectedOpcodes[OP_PetitionCheckIn] = &Client::Handle_OP_PetitionCheckIn; + ConnectedOpcodes[OP_PetitionCheckout] = &Client::Handle_OP_PetitionCheckout; + ConnectedOpcodes[OP_PetitionDelete] = &Client::Handle_OP_PetitionDelete; + ConnectedOpcodes[OP_PetitionQue] = &Client::Handle_OP_PetitionQue; + ConnectedOpcodes[OP_PetitionRefresh] = &Client::Handle_OP_PetitionRefresh; + ConnectedOpcodes[OP_PetitionResolve] = &Client::Handle_OP_PetitionResolve; + ConnectedOpcodes[OP_PetitionUnCheckout] = &Client::Handle_OP_PetitionUnCheckout; + ConnectedOpcodes[OP_PickPocket] = &Client::Handle_OP_PickPocket; + ConnectedOpcodes[OP_PopupResponse] = &Client::Handle_OP_PopupResponse; + ConnectedOpcodes[OP_PotionBelt] = &Client::Handle_OP_PotionBelt; + ConnectedOpcodes[OP_PurchaseLeadershipAA] = &Client::Handle_OP_PurchaseLeadershipAA; + ConnectedOpcodes[OP_PVPLeaderBoardDetailsRequest] = &Client::Handle_OP_PVPLeaderBoardDetailsRequest; + ConnectedOpcodes[OP_PVPLeaderBoardRequest] = &Client::Handle_OP_PVPLeaderBoardRequest; + ConnectedOpcodes[OP_RaidInvite] = &Client::Handle_OP_RaidCommand; + ConnectedOpcodes[OP_RandomReq] = &Client::Handle_OP_RandomReq; + ConnectedOpcodes[OP_ReadBook] = &Client::Handle_OP_ReadBook; + ConnectedOpcodes[OP_RecipeAutoCombine] = &Client::Handle_OP_RecipeAutoCombine; + ConnectedOpcodes[OP_RecipeDetails] = &Client::Handle_OP_RecipeDetails; + ConnectedOpcodes[OP_RecipesFavorite] = &Client::Handle_OP_RecipesFavorite; + ConnectedOpcodes[OP_RecipesSearch] = &Client::Handle_OP_RecipesSearch; + ConnectedOpcodes[OP_ReloadUI] = &Client::Handle_OP_ReloadUI; + ConnectedOpcodes[OP_RemoveBlockedBuffs] = &Client::Handle_OP_RemoveBlockedBuffs; + ConnectedOpcodes[OP_Report] = &Client::Handle_OP_Report; + ConnectedOpcodes[OP_RequestDuel] = &Client::Handle_OP_RequestDuel; + ConnectedOpcodes[OP_RequestTitles] = &Client::Handle_OP_RequestTitles; + ConnectedOpcodes[OP_RespawnWindow] = &Client::Handle_OP_RespawnWindow; + ConnectedOpcodes[OP_Rewind] = &Client::Handle_OP_Rewind; + ConnectedOpcodes[OP_RezzAnswer] = &Client::Handle_OP_RezzAnswer; + ConnectedOpcodes[OP_Sacrifice] = &Client::Handle_OP_Sacrifice; + ConnectedOpcodes[OP_SafeFallSuccess] = &Client::Handle_OP_SafeFallSuccess; + ConnectedOpcodes[OP_SafePoint] = &Client::Handle_OP_SafePoint; + ConnectedOpcodes[OP_Save] = &Client::Handle_OP_Save; + ConnectedOpcodes[OP_SaveOnZoneReq] = &Client::Handle_OP_SaveOnZoneReq; + ConnectedOpcodes[OP_SelectTribute] = &Client::Handle_OP_SelectTribute; + ConnectedOpcodes[OP_SenseHeading] = &Client::Handle_OP_Ignore; + ConnectedOpcodes[OP_SenseTraps] = &Client::Handle_OP_SenseTraps; + ConnectedOpcodes[OP_SetGuildMOTD] = &Client::Handle_OP_SetGuildMOTD; + ConnectedOpcodes[OP_SetRunMode] = &Client::Handle_OP_SetRunMode; + ConnectedOpcodes[OP_SetServerFilter] = &Client::Handle_OP_SetServerFilter; + ConnectedOpcodes[OP_SetStartCity] = &Client::Handle_OP_SetStartCity; + ConnectedOpcodes[OP_SetTitle] = &Client::Handle_OP_SetTitle; + ConnectedOpcodes[OP_Shielding] = &Client::Handle_OP_Shielding; + ConnectedOpcodes[OP_ShopEnd] = &Client::Handle_OP_ShopEnd; + ConnectedOpcodes[OP_ShopPlayerBuy] = &Client::Handle_OP_ShopPlayerBuy; + ConnectedOpcodes[OP_ShopPlayerSell] = &Client::Handle_OP_ShopPlayerSell; + ConnectedOpcodes[OP_ShopRequest] = &Client::Handle_OP_ShopRequest; + ConnectedOpcodes[OP_Sneak] = &Client::Handle_OP_Sneak; + ConnectedOpcodes[OP_SpawnAppearance] = &Client::Handle_OP_SpawnAppearance; + ConnectedOpcodes[OP_Split] = &Client::Handle_OP_Split; + ConnectedOpcodes[OP_Surname] = &Client::Handle_OP_Surname; + ConnectedOpcodes[OP_SwapSpell] = &Client::Handle_OP_SwapSpell; + ConnectedOpcodes[OP_TargetCommand] = &Client::Handle_OP_TargetCommand; + ConnectedOpcodes[OP_TargetMouse] = &Client::Handle_OP_TargetMouse; + ConnectedOpcodes[OP_TaskHistoryRequest] = &Client::Handle_OP_TaskHistoryRequest; + ConnectedOpcodes[OP_Taunt] = &Client::Handle_OP_Taunt; + ConnectedOpcodes[OP_TestBuff] = &Client::Handle_OP_TestBuff; + ConnectedOpcodes[OP_TGB] = &Client::Handle_OP_TGB; + ConnectedOpcodes[OP_Track] = &Client::Handle_OP_Track; + ConnectedOpcodes[OP_TrackTarget] = &Client::Handle_OP_TrackTarget; + ConnectedOpcodes[OP_TrackUnknown] = &Client::Handle_OP_TrackUnknown; + ConnectedOpcodes[OP_TradeAcceptClick] = &Client::Handle_OP_TradeAcceptClick; + ConnectedOpcodes[OP_TradeBusy] = &Client::Handle_OP_TradeBusy; + ConnectedOpcodes[OP_Trader] = &Client::Handle_OP_Trader; + ConnectedOpcodes[OP_TraderBuy] = &Client::Handle_OP_TraderBuy; + ConnectedOpcodes[OP_TradeRequest] = &Client::Handle_OP_TradeRequest; + ConnectedOpcodes[OP_TradeRequestAck] = &Client::Handle_OP_TradeRequestAck; + ConnectedOpcodes[OP_TraderShop] = &Client::Handle_OP_TraderShop; + ConnectedOpcodes[OP_TradeSkillCombine] = &Client::Handle_OP_TradeSkillCombine; + ConnectedOpcodes[OP_Translocate] = &Client::Handle_OP_Translocate; + ConnectedOpcodes[OP_TributeItem] = &Client::Handle_OP_TributeItem; + ConnectedOpcodes[OP_TributeMoney] = &Client::Handle_OP_TributeMoney; + ConnectedOpcodes[OP_TributeNPC] = &Client::Handle_OP_TributeNPC; + ConnectedOpcodes[OP_TributeToggle] = &Client::Handle_OP_TributeToggle; + ConnectedOpcodes[OP_TributeUpdate] = &Client::Handle_OP_TributeUpdate; + ConnectedOpcodes[OP_VetClaimRequest] = &Client::Handle_OP_VetClaimRequest; + ConnectedOpcodes[OP_VoiceMacroIn] = &Client::Handle_OP_VoiceMacroIn; + ConnectedOpcodes[OP_WearChange] = &Client::Handle_OP_WearChange; + ConnectedOpcodes[OP_WhoAllRequest] = &Client::Handle_OP_WhoAllRequest; + ConnectedOpcodes[OP_WorldUnknown001] = &Client::Handle_OP_Ignore; + ConnectedOpcodes[OP_XTargetAutoAddHaters] = &Client::Handle_OP_XTargetAutoAddHaters; + ConnectedOpcodes[OP_XTargetRequest] = &Client::Handle_OP_XTargetRequest; + ConnectedOpcodes[OP_YellForHelp] = &Client::Handle_OP_YellForHelp; + ConnectedOpcodes[OP_ZoneChange] = &Client::Handle_OP_ZoneChange; +} + +void ClearMappedOpcode(EmuOpcode op) +{ + if(op >= _maxEmuOpcode) + return; + + ConnectedOpcodes[op] = nullptr; + auto iter = ConnectingOpcodes.find(op); + if(iter != ConnectingOpcodes.end()) { + ConnectingOpcodes.erase(iter); + } +} + +// client methods +int Client::HandlePacket(const EQApplicationPacket *app) +{ + if(is_log_enabled(CLIENT__NET_IN_TRACE)) { + char buffer[64]; + app->build_header_dump(buffer); + mlog(CLIENT__NET_IN_TRACE, "Dispatch opcode: %s", buffer); + mpkt(CLIENT__NET_IN_TRACE, app); + } + + EmuOpcode opcode = app->GetOpcode(); + if (opcode == OP_AckPacket) { + return true; + } + + #if EQDEBUG >= 9 + std::cout << "Received 0x" << std::hex << std::setw(4) << std::setfill('0') << opcode << ", size=" << std::dec << app->size << std::endl; + #endif + + #ifdef SOLAR + if(0 && opcode != OP_ClientUpdate) + { + LogFile->write(EQEMuLog::Debug,"HandlePacket() OPCODE debug enabled client %s", GetName()); + std::cerr << "OPCODE: " << std::hex << std::setw(4) << std::setfill('0') << opcode << std::dec << ", size: " << app->size << std::endl; + DumpPacket(app); + } + #endif + + switch(client_state) { + case CLIENT_CONNECTING: { + if(ConnectingOpcodes.count(opcode) != 1) { + //Hate const cast but everything in lua needs to be non-const even if i make it non-mutable + std::vector args; + args.push_back(const_cast(app)); + parse->EventPlayer(EVENT_UNHANDLED_OPCODE, this, "", 1, &args); + +#if EQDEBUG >= 10 + LogFile->write(EQEMuLog::Error, "HandlePacket() Opcode error: Unexpected packet during CLIENT_CONNECTING: opcode:" + " %s (#%d eq=0x%04x), size: %i", OpcodeNames[opcode], opcode, 0, app->size); + DumpPacket(app); +#endif + break; + } + + ClientPacketProc p; + p = ConnectingOpcodes[opcode]; + + //call the processing routine + (this->*p)(app); + + //special case where connecting code needs to boot client... + if(client_state == CLIENT_KICKED) { + return(false); + } + + break; + } + case CLIENT_CONNECTED: { + ClientPacketProc p; + p = ConnectedOpcodes[opcode]; + if(p == nullptr) { + std::vector args; + args.push_back(const_cast(app)); + parse->EventPlayer(EVENT_UNHANDLED_OPCODE, this, "", 0, &args); + +#if (EQDEBUG >= 10) + char buffer[64]; + app->build_header_dump(buffer); + mlog(CLIENT__NET_ERR, "Unhandled incoming opcode: %s", buffer); + + if(app->size < 1000) + DumpPacket(app, app->size); + else{ + std::cout << "Dump limited to 1000 characters:\n"; + DumpPacket(app, 1000); + } +#endif + break; + } + + //call the processing routine + (this->*p)(app); + break; + } + case CLIENT_KICKED: + case DISCONNECTED: + case CLIENT_LINKDEAD: + break; + default: + LogFile->write(EQEMuLog::Debug, "Unknown client_state: %d\n", client_state); + break; + } + + return(true); +} + +// Finish client connecting state +void Client::CompleteConnect() +{ + UpdateWho(); + client_state = CLIENT_CONNECTED; + + hpupdate_timer.Start(); + position_timer.Start(); + autosave_timer.Start(); + SetDuelTarget(0); + SetDueling(false); + + EnteringMessages(this); + LoadZoneFlags(); + + /* Sets GM Flag if needed & Sends Petition Queue */ + UpdateAdmin(false); + + if (IsInAGuild()){ + uint8 rank = GuildRank(); + if (GetClientVersion() >= EQClientRoF) + { + switch (rank) { + case 0: { rank = 5; break; } // GUILD_MEMBER 0 + case 1: { rank = 3; break; } // GUILD_OFFICER 1 + case 2: { rank = 1; break; } // GUILD_LEADER 2 + default: { break; } // GUILD_NONE + } + } + SendAppearancePacket(AT_GuildID, GuildID(), false); + SendAppearancePacket(AT_GuildRank, rank, false); + } + for (uint32 spellInt = 0; spellInt < MAX_PP_REF_SPELLBOOK; spellInt++) { + if (m_pp.spell_book[spellInt] < 3 || m_pp.spell_book[spellInt] > 50000) + m_pp.spell_book[spellInt] = 0xFFFFFFFF; + } + //SendAATable(); + + if (GetHideMe()) Message(13, "[GM] You are currently hidden to all clients"); + + uint32 raidid = database.GetRaidID(GetName()); + Raid *raid = nullptr; + if (raidid > 0){ + raid = entity_list.GetRaidByID(raidid); + if (!raid){ + raid = new Raid(raidid); + if (raid->GetID() != 0){ + entity_list.AddRaid(raid, raidid); + raid->LoadLeadership(); // Recreating raid in new zone, get leadership from DB + } + else + raid = nullptr; + } + if (raid){ + SetRaidGrouped(true); + raid->LearnMembers(); + raid->VerifyRaid(); + raid->GetRaidDetails(); + /* + Only leader should get this; send to all for now till + I figure out correct creation; can probably also send a no longer leader packet for non leaders + but not important for now. + */ + raid->SendRaidCreate(this); + raid->SendMakeLeaderPacketTo(raid->leadername, this); + raid->SendRaidAdd(GetName(), this); + raid->SendBulkRaid(this); + raid->SendGroupUpdate(this); + raid->SendRaidMOTD(this); + if (raid->IsLeader(this)) { // We're a raid leader, lets update just in case! + raid->UpdateRaidAAs(); + raid->SendAllRaidLeadershipAA(); + } + uint32 grpID = raid->GetGroup(GetName()); + if (grpID < 12){ + raid->SendRaidGroupRemove(GetName(), grpID); + raid->SendRaidGroupAdd(GetName(), grpID); + raid->CheckGroupMentor(grpID, this); + if (raid->IsGroupLeader(GetName())) { // group leader same thing! + raid->UpdateGroupAAs(raid->GetGroup(this)); + raid->GroupUpdate(grpID, false); + } + } + raid->SendGroupLeadershipAA(this, grpID); // this may get sent an extra time ... + if (raid->IsLocked()) + raid->SendRaidLockTo(this); + } + } + + //bulk raid send in here eventually + + //reapply some buffs + uint32 buff_count = GetMaxTotalSlots(); + for (uint32 j1 = 0; j1 < buff_count; j1++) { + if (!IsValidSpell(buffs[j1].spellid)) + continue; + + const SPDat_Spell_Struct &spell = spells[buffs[j1].spellid]; + + int NimbusEffect = GetNimbusEffect(buffs[j1].spellid); + if (NimbusEffect) { + if (!IsNimbusEffectActive(NimbusEffect)) + SendSpellEffect(NimbusEffect, 500, 0, 1, 3000, true); + } + + for (int x1 = 0; x1 < EFFECT_COUNT; x1++) { + switch (spell.effectid[x1]) { + case SE_IllusionCopy: + case SE_Illusion: { + if (spell.base[x1] == -1) { + if (gender == 1) + gender = 0; + else if (gender == 0) + gender = 1; + SendIllusionPacket(GetRace(), gender, 0xFF, 0xFF); + } + else if (spell.base[x1] == -2) + { + if (GetRace() == 128 || GetRace() == 130 || GetRace() <= 12) + SendIllusionPacket(GetRace(), GetGender(), spell.max[x1], spell.max[x1]); + } + else if (spell.max[x1] > 0) + { + SendIllusionPacket(spell.base[x1], 0xFF, spell.max[x1], spell.max[x1]); + } + else + { + SendIllusionPacket(spell.base[x1], 0xFF, 0xFF, 0xFF); + } + switch (spell.base[x1]){ + case OGRE: + SendAppearancePacket(AT_Size, 9); + break; + case TROLL: + SendAppearancePacket(AT_Size, 8); + break; + case VAHSHIR: + case BARBARIAN: + SendAppearancePacket(AT_Size, 7); + break; + case HALF_ELF: + case WOOD_ELF: + case DARK_ELF: + case FROGLOK: + SendAppearancePacket(AT_Size, 5); + break; + case DWARF: + SendAppearancePacket(AT_Size, 4); + break; + case HALFLING: + case GNOME: + SendAppearancePacket(AT_Size, 3); + break; + default: + SendAppearancePacket(AT_Size, 6); + break; + } + break; + } + case SE_SummonHorse: { + SummonHorse(buffs[j1].spellid); + //hasmount = true; //this was false, is that the correct thing? + break; + } + case SE_Silence: + { + Silence(true); + break; + } + case SE_Amnesia: + { + Amnesia(true); + break; + } + case SE_DivineAura: + { + invulnerable = true; + break; + } + case SE_Invisibility2: + case SE_Invisibility: + { + invisible = true; + SendAppearancePacket(AT_Invis, 1); + break; + } + case SE_Levitate: + { + if (!zone->CanLevitate()) + { + if (!GetGM()) + { + SendAppearancePacket(AT_Levitate, 0); + BuffFadeByEffect(SE_Levitate); + Message(13, "You can't levitate in this zone."); + } + } + else{ + SendAppearancePacket(AT_Levitate, 2); + } + break; + } + case SE_InvisVsUndead2: + case SE_InvisVsUndead: + { + invisible_undead = true; + break; + } + case SE_InvisVsAnimals: + { + invisible_animals = true; + break; + } + case SE_AddMeleeProc: + case SE_WeaponProc: + { + AddProcToWeapon(GetProcID(buffs[j1].spellid, x1), false, 100 + spells[buffs[j1].spellid].base2[x1], buffs[j1].spellid); + break; + } + case SE_DefensiveProc: + { + AddDefensiveProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].base2[x1], buffs[j1].spellid); + break; + } + case SE_RangedProc: + { + AddRangedProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].base2[x1], buffs[j1].spellid); + break; + } + } + } + } + + /* Sends appearances for all mobs not doing anim_stand aka sitting, looting, playing dead */ + entity_list.SendZoneAppearance(this); + /* Sends the Nimbus particle effects (up to 3) for any mob using them */ + entity_list.SendNimbusEffects(this); + + entity_list.SendUntargetable(this); + + int x; + for (x = 0; x < 8; x++) + SendWearChange(x); + Mob *pet = GetPet(); + if (pet != nullptr) { + for (x = 0; x < 8; x++) + pet->SendWearChange(x); + } + + entity_list.SendTraders(this); + + zoneinpacket_timer.Start(); + + if (GetPet()){ + GetPet()->SendPetBuffsToClient(); + } + + if (GetGroup()) + database.RefreshGroupFromDB(this); + + if (RuleB(TaskSystem, EnableTaskSystem)) + TaskPeriodic_Timer.Start(); + else + TaskPeriodic_Timer.Disable(); + + conn_state = ClientConnectFinished; + + //enforce some rules.. + if (!CanBeInZone()) { + _log(CLIENT__ERROR, "Kicking char from zone, not allowed here"); + GoToSafeCoords(database.GetZoneID("arena"), 0); + return; + } + + if (zone) + zone->weatherSend(); + + TotalKarma = database.GetKarma(AccountID()); + SendDisciplineTimers(); + + parse->EventPlayer(EVENT_ENTER_ZONE, this, "", 0); + + /* This sub event is for if a player logs in for the first time since entering world. */ + if (firstlogon == 1){ + parse->EventPlayer(EVENT_CONNECT, this, "", 0); + /* QS: PlayerLogConnectDisconnect */ + if (RuleB(QueryServ, PlayerLogConnectDisconnect)){ + std::string event_desc = StringFormat("Connect :: Logged into zoneid:%i instid:%i", this->GetZoneID(), this->GetInstanceID()); + QServ->PlayerLogEvent(Player_Log_Connect_State, this->CharacterID(), event_desc); + } + } + + if (zone) { + if (zone->GetInstanceTimer()) { + uint32 ttime = zone->GetInstanceTimer()->GetRemainingTime(); + uint32 day = (ttime / 86400000); + uint32 hour = (ttime / 3600000) % 24; + uint32 minute = (ttime / 60000) % 60; + uint32 second = (ttime / 1000) % 60; + if (day) { + Message(15, "%s(%u) will expire in %u days, %u hours, %u minutes, and %u seconds.", + zone->GetLongName(), zone->GetInstanceID(), day, hour, minute, second); + } + else if (hour) { + Message(15, "%s(%u) will expire in %u hours, %u minutes, and %u seconds.", + zone->GetLongName(), zone->GetInstanceID(), hour, minute, second); + } + else if (minute) { + Message(15, "%s(%u) will expire in %u minutes, and %u seconds.", + zone->GetLongName(), zone->GetInstanceID(), minute, second); + } + else { + Message(15, "%s(%u) will expire in in %u seconds.", + zone->GetLongName(), zone->GetInstanceID(), second); + } + } + } + + SendRewards(); + SendAltCurrencies(); + database.LoadAltCurrencyValues(CharacterID(), alternate_currency); + SendAlternateCurrencyValues(); + alternate_currency_loaded = true; + ProcessAlternateCurrencyQueue(); + + /* This needs to be set, this determines whether or not data was loaded properly before a save */ + client_data_loaded = true; + + CalcItemScale(); + DoItemEnterZone(); + + if (zone->GetZoneID() == RuleI(World, GuildBankZoneID) && GuildBanks) + GuildBanks->SendGuildBank(this); + + if (GetClientVersion() >= EQClientSoD) + entity_list.SendFindableNPCList(this); + + if (IsInAGuild()) { + SendGuildRanks(); + guild_mgr.SendGuildMemberUpdateToWorld(GetName(), GuildID(), zone->GetZoneID(), time(nullptr)); + guild_mgr.RequestOnlineGuildMembers(this->CharacterID(), this->GuildID()); + } + + /** Request adventure info **/ + ServerPacket *pack = new ServerPacket(ServerOP_AdventureDataRequest, 64); + strcpy((char*)pack->pBuffer, GetName()); + worldserver.SendPacket(pack); + delete pack; + + if (IsClient() && CastToClient()->GetClientVersionBit() & BIT_UnderfootAndLater) { + EQApplicationPacket *outapp = MakeBuffsPacket(false); + CastToClient()->FastQueuePacket(&outapp); + } + + entity_list.RefreshClientXTargets(this); + + worldserver.RequestTellQueue(GetName()); +} + +void Client::CheatDetected(CheatTypes CheatType, float x, float y, float z) +{ + //ToDo: Break warp down for special zones. Some zones have special teleportation pads or bad .map files which can trigger the detector without a legit zone request. + + switch (CheatType) + { + case MQWarp: //Some zones may still have issues. Database updates will eliminate most if not all problems. + if (RuleB(Zone, EnableMQWarpDetector) + && ((this->Admin() < RuleI(Zone, MQWarpExemptStatus) + || (RuleI(Zone, MQWarpExemptStatus)) == -1))) + { + Message(13, "Large warp detected."); + char hString[250]; + sprintf(hString, "/MQWarp with location %.2f, %.2f, %.2f", GetX(), GetY(), GetZ()); + database.SetMQDetectionFlag(this->account_name, this->name, hString, zone->GetShortName()); + } + break; + case MQWarpShadowStep: + if (RuleB(Zone, EnableMQWarpDetector) + && ((this->Admin() < RuleI(Zone, MQWarpExemptStatus) + || (RuleI(Zone, MQWarpExemptStatus)) == -1))) + { + char *hString = nullptr; + MakeAnyLenString(&hString, "/MQWarp(SS) with location %.2f, %.2f, %.2f, the target was shadow step exempt but we still found this suspicious.", GetX(), GetY(), GetZ()); + database.SetMQDetectionFlag(this->account_name, this->name, hString, zone->GetShortName()); + safe_delete_array(hString); + } + break; + case MQWarpKnockBack: + if (RuleB(Zone, EnableMQWarpDetector) + && ((this->Admin() < RuleI(Zone, MQWarpExemptStatus) + || (RuleI(Zone, MQWarpExemptStatus)) == -1))) + { + char *hString = nullptr; + MakeAnyLenString(&hString, "/MQWarp(KB) with location %.2f, %.2f, %.2f, the target was Knock Back exempt but we still found this suspicious.", GetX(), GetY(), GetZ()); + database.SetMQDetectionFlag(this->account_name, this->name, hString, zone->GetShortName()); + safe_delete_array(hString); + } + break; + + case MQWarpLight: + if (RuleB(Zone, EnableMQWarpDetector) + && ((this->Admin() < RuleI(Zone, MQWarpExemptStatus) + || (RuleI(Zone, MQWarpExemptStatus)) == -1))) + { + if (RuleB(Zone, MarkMQWarpLT)) + { + char *hString = nullptr; + MakeAnyLenString(&hString, "/MQWarp(LT) with location %.2f, %.2f, %.2f, running fast but not fast enough to get killed, possibly: small warp, speed hack, excessive lag, marked as suspicious.", GetX(), GetY(), GetZ()); + database.SetMQDetectionFlag(this->account_name, this->name, hString, zone->GetShortName()); + safe_delete_array(hString); + } + } + break; + + case MQZone: + if (RuleB(Zone, EnableMQZoneDetector) && ((this->Admin() < RuleI(Zone, MQZoneExemptStatus) || (RuleI(Zone, MQZoneExemptStatus)) == -1))) + { + char hString[250]; + sprintf(hString, "/MQZone used at %.2f, %.2f, %.2f to %.2f %.2f %.2f", GetX(), GetY(), GetZ(), x, y, z); + database.SetMQDetectionFlag(this->account_name, this->name, hString, zone->GetShortName()); + } + break; + case MQZoneUnknownDest: + if (RuleB(Zone, EnableMQZoneDetector) && ((this->Admin() < RuleI(Zone, MQZoneExemptStatus) || (RuleI(Zone, MQZoneExemptStatus)) == -1))) + { + char hString[250]; + sprintf(hString, "/MQZone used at %.2f, %.2f, %.2f", GetX(), GetY(), GetZ()); + database.SetMQDetectionFlag(this->account_name, this->name, hString, zone->GetShortName()); + } + break; + case MQGate: + if (RuleB(Zone, EnableMQGateDetector) && ((this->Admin() < RuleI(Zone, MQGateExemptStatus) || (RuleI(Zone, MQGateExemptStatus)) == -1))) { + Message(13, "Illegal gate request."); + char hString[250]; + sprintf(hString, "/MQGate used at %.2f, %.2f, %.2f", GetX(), GetY(), GetZ()); + database.SetMQDetectionFlag(this->account_name, this->name, hString, zone->GetShortName()); + if (zone) + { + this->SetZone(this->GetZoneID(), zone->GetInstanceID()); //Prevent the player from zoning, place him back in the zone where he tried to originally /gate. + } + else + { + this->SetZone(this->GetZoneID(), 0); //Prevent the player from zoning, place him back in the zone where he tried to originally /gate. + + } + } + break; + case MQGhost: //Not currently implemented, but the framework is in place - just needs detection scenarios identified + if (RuleB(Zone, EnableMQGhostDetector) && ((this->Admin() < RuleI(Zone, MQGhostExemptStatus) || (RuleI(Zone, MQGhostExemptStatus)) == -1))) { + database.SetMQDetectionFlag(this->account_name, this->name, "/MQGhost", zone->GetShortName()); + } + break; + default: + char *hString = nullptr; + MakeAnyLenString(&hString, "Unhandled HackerDetection flag with location %.2f, %.2f, %.2f.", GetX(), GetY(), GetZ()); + database.SetMQDetectionFlag(this->account_name, this->name, hString, zone->GetShortName()); + safe_delete_array(hString); + break; + } +} + +// connecting opcode handlers +/* +void Client::Handle_Connect_0x3e33(const EQApplicationPacket *app) +{ +//OP_0x0380 = 0x642c +EQApplicationPacket* outapp = new EQApplicationPacket(OP_0x0380, sizeof(uint32)); // Dunno +QueuePacket(outapp); +safe_delete(outapp); +return; +} +*/ + +void Client::Handle_Connect_OP_ApproveZone(const EQApplicationPacket *app) +{ + if (app->size != sizeof(ApproveZone_Struct)) { + LogFile->write(EQEMuLog::Error, "Invalid size on OP_ApproveZone: Expected %i, Got %i", + sizeof(ApproveZone_Struct), app->size); + return; + } + ApproveZone_Struct* azone = (ApproveZone_Struct*)app->pBuffer; + azone->approve = 1; + QueuePacket(app); + return; +} + +void Client::Handle_Connect_OP_ClientError(const EQApplicationPacket *app) +{ + if (app->size != sizeof(ClientError_Struct)) { + LogFile->write(EQEMuLog::Error, "Invalid size on OP_ClientError: Expected %i, Got %i", + sizeof(ClientError_Struct), app->size); + return; + } + // Client reporting error to server + ClientError_Struct* error = (ClientError_Struct*)app->pBuffer; + LogFile->write(EQEMuLog::Error, "Client error: %s", error->character_name); + LogFile->write(EQEMuLog::Error, "Error message: %s", error->message); + Message(13, error->message); +#if (EQDEBUG>=5) + DumpPacket(app); +#endif + return; +} + +void Client::Handle_Connect_OP_ClientReady(const EQApplicationPacket *app) +{ + conn_state = ClientReadyReceived; + + CompleteConnect(); + SendHPUpdate(); +} + +void Client::Handle_Connect_OP_ClientUpdate(const EQApplicationPacket *app) +{ + //Once we get this, the client thinks it is connected + //So give it the benefit of the doubt and move to connected + + Handle_Connect_OP_ClientReady(app); +} + +void Client::Handle_Connect_OP_ReqClientSpawn(const EQApplicationPacket *app) +{ + conn_state = ClientSpawnRequested; + + EQApplicationPacket* outapp = new EQApplicationPacket; + + // Send Zone Doors + if (entity_list.MakeDoorSpawnPacket(outapp, this)) + { + QueuePacket(outapp); + } + safe_delete(outapp); + + // Send Zone Objects + entity_list.SendZoneObjects(this); + SendZonePoints(); + // Live does this + outapp = new EQApplicationPacket(OP_SendAAStats, 0); + FastQueuePacket(&outapp); + + // Tell client they can continue we're done + outapp = new EQApplicationPacket(OP_ZoneServerReady, 0); + FastQueuePacket(&outapp); + outapp = new EQApplicationPacket(OP_SendExpZonein, 0); + FastQueuePacket(&outapp); + + if (GetClientVersion() >= EQClientRoF) + { + outapp = new EQApplicationPacket(OP_ClientReady, 0); + FastQueuePacket(&outapp); + } + + // New for Secrets of Faydwer - Used in Place of OP_SendExpZonein + outapp = new EQApplicationPacket(OP_WorldObjectsSent, 0); + QueuePacket(outapp); + safe_delete(outapp); + + if (strncasecmp(zone->GetShortName(), "bazaar", 6) == 0) + SendBazaarWelcome(); + + conn_state = ZoneContentsSent; + + return; +} + +void Client::Handle_Connect_OP_ReqNewZone(const EQApplicationPacket *app) +{ + conn_state = NewZoneRequested; + + EQApplicationPacket* outapp; + + ///////////////////////////////////// + // New Zone Packet + outapp = new EQApplicationPacket(OP_NewZone, sizeof(NewZone_Struct)); + NewZone_Struct* nz = (NewZone_Struct*)outapp->pBuffer; + memcpy(outapp->pBuffer, &zone->newzone_data, sizeof(NewZone_Struct)); + strcpy(nz->char_name, m_pp.name); + + FastQueuePacket(&outapp); + + return; +} + +void Client::Handle_Connect_OP_SendAAStats(const EQApplicationPacket *app) +{ + SendAATimers(); + EQApplicationPacket* outapp = new EQApplicationPacket(OP_SendAAStats, 0); + QueuePacket(outapp); + safe_delete(outapp); + return; +} + +void Client::Handle_Connect_OP_SendAATable(const EQApplicationPacket *app) +{ + SendAAList(); + return; +} + +void Client::Handle_Connect_OP_SendExpZonein(const EQApplicationPacket *app) +{ + ////////////////////////////////////////////////////// + // Spawn Appearance Packet + EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct)); + SpawnAppearance_Struct* sa = (SpawnAppearance_Struct*)outapp->pBuffer; + sa->type = AT_SpawnID; // Is 0x10 used to set the player id? + sa->parameter = GetID(); // Four bytes for this parameter... + outapp->priority = 6; + QueuePacket(outapp); + safe_delete(outapp); + + // Inform the world about the client + outapp = new EQApplicationPacket(); + + CreateSpawnPacket(outapp); + outapp->priority = 6; + if (!GetHideMe()) entity_list.QueueClients(this, outapp, true); + safe_delete(outapp); + if (GetPVP()) //force a PVP update until we fix the spawn struct + SendAppearancePacket(AT_PVP, GetPVP(), true, false); + + //Send AA Exp packet: + if (GetLevel() >= 51) + SendAAStats(); + + // Send exp packets + outapp = new EQApplicationPacket(OP_ExpUpdate, sizeof(ExpUpdate_Struct)); + ExpUpdate_Struct* eu = (ExpUpdate_Struct*)outapp->pBuffer; + uint32 tmpxp1 = GetEXPForLevel(GetLevel() + 1); + uint32 tmpxp2 = GetEXPForLevel(GetLevel()); + + // Crash bug fix... Divide by zero when tmpxp1 and 2 equalled each other, most likely the error case from GetEXPForLevel() (invalid class, etc) + if (tmpxp1 != tmpxp2 && tmpxp1 != 0xFFFFFFFF && tmpxp2 != 0xFFFFFFFF) { + float tmpxp = (float)((float)m_pp.exp - tmpxp2) / ((float)tmpxp1 - tmpxp2); + eu->exp = (uint32)(330.0f * tmpxp); + outapp->priority = 6; + QueuePacket(outapp); + } + safe_delete(outapp); + + SendAATimers(); + + outapp = new EQApplicationPacket(OP_SendExpZonein, 0); + QueuePacket(outapp); + safe_delete(outapp); + + outapp = new EQApplicationPacket(OP_RaidUpdate, sizeof(ZoneInSendName_Struct)); + ZoneInSendName_Struct* zonesendname = (ZoneInSendName_Struct*)outapp->pBuffer; + strcpy(zonesendname->name, m_pp.name); + strcpy(zonesendname->name2, m_pp.name); + zonesendname->unknown0 = 0x0A; + QueuePacket(outapp); + safe_delete(outapp); + + /* this is actually the guild MOTD + outapp = new EQApplicationPacket(OP_ZoneInSendName2, sizeof(ZoneInSendName_Struct2)); + ZoneInSendName_Struct2* zonesendname2=(ZoneInSendName_Struct2*)outapp->pBuffer; + strcpy(zonesendname2->name,m_pp.name); + QueuePacket(outapp); + safe_delete(outapp);*/ + + if (IsInAGuild()) { + SendGuildMembers(); + SendGuildURL(); + SendGuildChannel(); + SendGuildLFGuildStatus(); + } + SendLFGuildStatus(); + + //No idea why live sends this if even were not in a guild + SendGuildMOTD(); + SpawnMercOnZone(); + + return; +} + +void Client::Handle_Connect_OP_SendGuildTributes(const EQApplicationPacket *app) +{ + SendGuildTributes(); + return; +} + +void Client::Handle_Connect_OP_SendTributes(const EQApplicationPacket *app) +{ + SendTributes(); + return; +} + +void Client::Handle_Connect_OP_SetServerFilter(const EQApplicationPacket *app) +{ + if (app->size != sizeof(SetServerFilter_Struct)) { + LogFile->write(EQEMuLog::Error, "Received invalid sized OP_SetServerFilter"); + DumpPacket(app); + return; + } + SetServerFilter_Struct* filter = (SetServerFilter_Struct*)app->pBuffer; + ServerFilter(filter); + return; +} + +void Client::Handle_Connect_OP_SpawnAppearance(const EQApplicationPacket *app) +{ + return; +} + +void Client::Handle_Connect_OP_TGB(const EQApplicationPacket *app) +{ + if (app->size != sizeof(uint32)) { + LogFile->write(EQEMuLog::Error, "Invalid size on OP_TGB: Expected %i, Got %i", + sizeof(uint32), app->size); + return; + } + OPTGB(app); + return; +} + +void Client::Handle_Connect_OP_UpdateAA(const EQApplicationPacket *app) +{ + SendAATable(); +} + +void Client::Handle_Connect_OP_WearChange(const EQApplicationPacket *app) +{ + //not sure what these are supposed to mean to us. + return; +} + +void Client::Handle_Connect_OP_WorldObjectsSent(const EQApplicationPacket *app) +{ + //This is a copy of SendExpZonein created for SoF due to packet order change + //This does not affect clients other than SoF + + ////////////////////////////////////////////////////// + // Spawn Appearance Packet + EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct)); + SpawnAppearance_Struct* sa = (SpawnAppearance_Struct*)outapp->pBuffer; + sa->type = AT_SpawnID; // Is 0x10 used to set the player id? + sa->parameter = GetID(); // Four bytes for this parameter... + outapp->priority = 6; + QueuePacket(outapp); + safe_delete(outapp); + + // Inform the world about the client + outapp = new EQApplicationPacket(); + + CreateSpawnPacket(outapp); + outapp->priority = 6; + if (!GetHideMe()) entity_list.QueueClients(this, outapp, true); + safe_delete(outapp); + if (GetPVP()) //force a PVP update until we fix the spawn struct + SendAppearancePacket(AT_PVP, GetPVP(), true, false); + + //Send AA Exp packet: + if (GetLevel() >= 51) + SendAAStats(); + + // Send exp packets + outapp = new EQApplicationPacket(OP_ExpUpdate, sizeof(ExpUpdate_Struct)); + ExpUpdate_Struct* eu = (ExpUpdate_Struct*)outapp->pBuffer; + uint32 tmpxp1 = GetEXPForLevel(GetLevel() + 1); + uint32 tmpxp2 = GetEXPForLevel(GetLevel()); + + // Crash bug fix... Divide by zero when tmpxp1 and 2 equalled each other, most likely the error case from GetEXPForLevel() (invalid class, etc) + if (tmpxp1 != tmpxp2 && tmpxp1 != 0xFFFFFFFF && tmpxp2 != 0xFFFFFFFF) { + float tmpxp = (float)((float)m_pp.exp - tmpxp2) / ((float)tmpxp1 - tmpxp2); + eu->exp = (uint32)(330.0f * tmpxp); + outapp->priority = 6; + QueuePacket(outapp); + } + safe_delete(outapp); + + SendAATimers(); + + // New for Secrets of Faydwer - Used in Place of OP_SendExpZonein + outapp = new EQApplicationPacket(OP_WorldObjectsSent, 0); + QueuePacket(outapp); + safe_delete(outapp); + + outapp = new EQApplicationPacket(OP_RaidUpdate, sizeof(ZoneInSendName_Struct)); + ZoneInSendName_Struct* zonesendname = (ZoneInSendName_Struct*)outapp->pBuffer; + strcpy(zonesendname->name, m_pp.name); + strcpy(zonesendname->name2, m_pp.name); + zonesendname->unknown0 = 0x0A; + QueuePacket(outapp); + safe_delete(outapp); + + if (IsInAGuild()) { + SendGuildMembers(); + SendGuildURL(); + SendGuildChannel(); + SendGuildLFGuildStatus(); + } + SendLFGuildStatus(); + + //No idea why live sends this if even were not in a guild + SendGuildMOTD(); + + SpawnMercOnZone(); + + return; +} + +void Client::Handle_Connect_OP_ZoneComplete(const EQApplicationPacket *app) +{ + EQApplicationPacket* outapp = new EQApplicationPacket(OP_0x0347, 0); + QueuePacket(outapp); + safe_delete(outapp); + return; +} + +void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) +{ + if(app->size != sizeof(ClientZoneEntry_Struct)) + return; + ClientZoneEntry_Struct *cze = (ClientZoneEntry_Struct *) app->pBuffer; + + if(strlen(cze->char_name) > 63) + return; + + conn_state = ReceivedZoneEntry; + + ClientVersion = Connection()->ClientVersion(); + if (ClientVersion != EQClientUnknown) + ClientVersionBit = 1 << (ClientVersion - 1); + else + ClientVersionBit = 0; + + bool siv = m_inv.SetInventoryVersion(ClientVersion); + LogFile->write(EQEMuLog::Debug, "%s inventory version to %s(%i)", (siv ? "Succeeded in setting" : "Failed to set"), EQClientVersionName(ClientVersion), ClientVersion); + + /* Antighost code + tmp var is so the search doesnt find this object + */ + Client* client = entity_list.GetClientByName(cze->char_name); + if (!zone->GetAuth(ip, cze->char_name, &WID, &account_id, &character_id, &admin, lskey, &tellsoff)) { + LogFile->write(EQEMuLog::Error, "GetAuth() returned false kicking client"); + if (client != 0) { + client->Save(); + client->Kick(); + } + //ret = false; // TODO: Can we tell the client to get lost in a good way + client_state = CLIENT_KICKED; + return; + } + + strcpy(name, cze->char_name); + /* Check for Client Spoofing */ + if (client != 0) { + struct in_addr ghost_addr; + ghost_addr.s_addr = eqs->GetRemoteIP(); + + LogFile->write(EQEMuLog::Error,"Ghosting client: Account ID:%i Name:%s Character:%s IP:%s", + client->AccountID(), client->AccountName(), client->GetName(), inet_ntoa(ghost_addr)); + client->Save(); + client->Disconnect(); + } + + uint32 pplen = 0; + EQApplicationPacket* outapp = 0; + MYSQL_RES* result = 0; + bool loaditems = 0; + uint32 i; + std::string query; + unsigned long* lengths; + + uint32 cid = CharacterID(); + character_id = cid; /* Global character_id reference */ + + /* Flush and reload factions */ + database.RemoveTempFactions(this); + database.LoadCharacterFactionValues(cid, factionvalues); + + /* Load Character Account Data: Temp until I move */ + query = StringFormat("SELECT `status`, `name`, `lsaccount_id`, `gmspeed`, `revoked`, `hideme` FROM `account` WHERE `id` = %u", this->AccountID()); + auto results = database.QueryDatabase(query); + for (auto row = results.begin(); row != results.end(); ++row) { + admin = atoi(row[0]); + strncpy(account_name, row[1], 30); + lsaccountid = atoi(row[2]); + gmspeed = atoi(row[3]); + revoked = atoi(row[4]); + gmhideme = atoi(row[5]); + if (account_creation){ account_creation = atoul(row[6]); } + } + + /* Load Character Data */ + query = StringFormat("SELECT `lfp`, `lfg`, `xtargets`, `firstlogon`, `guild_id`, `rank` FROM `character_data` LEFT JOIN `guild_members` ON `id` = `char_id` WHERE `id` = %i", cid); + results = database.QueryDatabase(query); + for (auto row = results.begin(); row != results.end(); ++row) { + if (row[4] && atoi(row[4]) > 0){ + guild_id = atoi(row[4]); + if (row[5] != nullptr){ guildrank = atoi(row[5]); } + else{ guildrank = GUILD_RANK_NONE; } + } + + if (LFP){ LFP = atoi(row[0]); } + if (LFG){ LFG = atoi(row[1]); } + if (firstlogon){ firstlogon = atoi(row[3]); } + } + + if (RuleB(Character, SharedBankPlat)) + m_pp.platinum_shared = database.GetSharedPlatinum(this->AccountID()); + + loaditems = database.GetInventory(cid, &m_inv); /* Load Character Inventory */ + database.LoadCharacterBandolier(cid, &m_pp); /* Load Character Bandolier */ + database.LoadCharacterBindPoint(cid, &m_pp); /* Load Character Bind */ + database.LoadCharacterMaterialColor(cid, &m_pp); /* Load Character Material */ + database.LoadCharacterPotions(cid, &m_pp); /* Load Character Potion Belt */ + database.LoadCharacterCurrency(cid, &m_pp); /* Load Character Currency into PP */ + database.LoadCharacterData(cid, &m_pp, &m_epp); /* Load Character Data from DB into PP as well as E_PP */ + database.LoadCharacterSkills(cid, &m_pp); /* Load Character Skills */ + database.LoadCharacterInspectMessage(cid, &m_inspect_message); /* Load Character Inspect Message */ + database.LoadCharacterSpellBook(cid, &m_pp); /* Load Character Spell Book */ + database.LoadCharacterMemmedSpells(cid, &m_pp); /* Load Character Memorized Spells */ + database.LoadCharacterDisciplines(cid, &m_pp); /* Load Character Disciplines */ + database.LoadCharacterLanguages(cid, &m_pp); /* Load Character Languages */ + database.LoadCharacterLeadershipAA(cid, &m_pp); /* Load Character Leadership AA's */ + database.LoadCharacterTribute(cid, &m_pp); /* Load CharacterTribute */ + + /* Load AdventureStats */ + AdventureStats_Struct as; + if(database.GetAdventureStats(cid, &as)) + { + m_pp.ldon_wins_guk = as.success.guk; + m_pp.ldon_wins_mir = as.success.mir; + m_pp.ldon_wins_mmc = as.success.mmc; + m_pp.ldon_wins_ruj = as.success.ruj; + m_pp.ldon_wins_tak = as.success.tak; + m_pp.ldon_losses_guk = as.failure.guk; + m_pp.ldon_losses_mir = as.failure.mir; + m_pp.ldon_losses_mmc = as.failure.mmc; + m_pp.ldon_losses_ruj = as.failure.ruj; + m_pp.ldon_losses_tak = as.failure.tak; + } + + /* Set item material tint */ + for (int i = EmuConstants::MATERIAL_BEGIN; i <= EmuConstants::MATERIAL_END; i++) + if (m_pp.item_tint[i].rgb.use_tint == 1 || m_pp.item_tint[i].rgb.use_tint == 255) + m_pp.item_tint[i].rgb.use_tint = 0xFF; + + if (level){ level = m_pp.level; } + + /* If GM, not trackable */ + if (gmhideme) { trackable = false; } + /* Set Con State for Reporting */ + conn_state = PlayerProfileLoaded; + + m_pp.zone_id = zone->GetZoneID(); + m_pp.zoneInstance = zone->GetInstanceID(); + + /* 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; + + strcpy(name, m_pp.name); + strcpy(lastname, m_pp.last_name); + /* If PP is set to weird coordinates */ + if ((m_pp.x == -1 && m_pp.y == -1 && m_pp.z == -1) || (m_pp.x == -2 && m_pp.y == -2 && m_pp.z == -2)) { + auto safePoint = zone->GetSafePoint(); + m_pp.x = safePoint.m_X; + m_pp.y = safePoint.m_Y; + m_pp.z = safePoint.m_Z; + } + /* If too far below ground, then fix */ + // float ground_z = GetGroundZ(m_pp.x, m_pp.y, m_pp.z); + // if (m_pp.z < (ground_z - 500)) + // m_pp.z = ground_z; + + /* Set Mob variables for spawn */ + class_ = m_pp.class_; + level = m_pp.level; + m_Position.m_X = m_pp.x; + m_Position.m_Y = m_pp.y; + m_Position.m_Z = m_pp.z; + m_Position.m_Heading = m_pp.heading; + race = m_pp.race; + base_race = m_pp.race; + gender = m_pp.gender; + base_gender = m_pp.gender; + deity = m_pp.deity; + haircolor = m_pp.haircolor; + beardcolor = m_pp.beardcolor; + eyecolor1 = m_pp.eyecolor1; + eyecolor2 = m_pp.eyecolor2; + hairstyle = m_pp.hairstyle; + luclinface = m_pp.face; + beard = m_pp.beard; + drakkin_heritage = m_pp.drakkin_heritage; + drakkin_tattoo = m_pp.drakkin_tattoo; + drakkin_details = m_pp.drakkin_details; + + /* If GM not set in DB, and does not meet min status to be GM, reset */ + if (m_pp.gm && admin < minStatusToBeGM) + m_pp.gm = 0; + + /* Load Guild */ + if (!IsInAGuild()) { m_pp.guild_id = GUILD_NONE; } + else { + m_pp.guild_id = GuildID(); + if (zone->GetZoneID() == RuleI(World, GuildBankZoneID)) + GuildBanker = (guild_mgr.IsGuildLeader(GuildID(), CharacterID()) || guild_mgr.GetBankerFlag(CharacterID())); + } + m_pp.guildbanker = GuildBanker; + + switch (race) + { + case OGRE: + size = 9; break; + case TROLL: + size = 8; break; + case VAHSHIR: case BARBARIAN: + size = 7; break; + case HUMAN: case HIGH_ELF: case ERUDITE: case IKSAR: case DRAKKIN: + size = 6; break; + case HALF_ELF: + size = 5.5; break; + case WOOD_ELF: case DARK_ELF: case FROGLOK: + size = 5; break; + case DWARF: + size = 4; break; + case HALFLING: + size = 3.5; break; + case GNOME: + size = 3; break; + default: + size = 0; + } + + /* Check for Invalid points */ + if (m_pp.ldon_points_guk < 0 || m_pp.ldon_points_guk > 2000000000){ m_pp.ldon_points_guk = 0; } + if (m_pp.ldon_points_mir < 0 || m_pp.ldon_points_mir > 2000000000){ m_pp.ldon_points_mir = 0; } + if (m_pp.ldon_points_mmc < 0 || m_pp.ldon_points_mmc > 2000000000){ m_pp.ldon_points_mmc = 0; } + if (m_pp.ldon_points_ruj < 0 || m_pp.ldon_points_ruj > 2000000000){ m_pp.ldon_points_ruj = 0; } + if (m_pp.ldon_points_tak < 0 || m_pp.ldon_points_tak > 2000000000){ m_pp.ldon_points_tak = 0; } + if (m_pp.ldon_points_available < 0 || m_pp.ldon_points_available > 2000000000){ m_pp.ldon_points_available = 0; } + + /* Set Swimming Skill 100 by default if under 100 */ + if (GetSkill(SkillSwimming) < 100) + SetSkill(SkillSwimming, 100); + + /* Initialize AA's : Move to function eventually */ + for (uint32 a = 0; a < MAX_PP_AA_ARRAY; a++){ aa[a] = &m_pp.aa_array[a]; } + query = StringFormat( + "SELECT " + "slot, " + "aa_id, " + "aa_value " + "FROM " + "`character_alternate_abilities` " + "WHERE `id` = %u ORDER BY `slot`", this->CharacterID()); + results = database.QueryDatabase(query); i = 0; + for (auto row = results.begin(); row != results.end(); ++row) { + i = atoi(row[0]); + m_pp.aa_array[i].AA = atoi(row[1]); + m_pp.aa_array[i].value = atoi(row[2]); + aa[i]->AA = atoi(row[1]); + aa[i]->value = atoi(row[2]); + } + for (uint32 a = 0; a < MAX_PP_AA_ARRAY; a++){ + uint32 id = aa[a]->AA; + //watch for invalid AA IDs + if (id == aaNone) + continue; + if (id >= aaHighestID) { + aa[a]->AA = aaNone; + aa[a]->value = 0; + continue; + } + if (aa[a]->value == 0) { + aa[a]->AA = aaNone; + continue; + } + if (aa[a]->value > HIGHEST_AA_VALUE) { + aa[a]->AA = aaNone; + aa[a]->value = 0; + continue; + } + + if (aa[a]->value > 1) /* hack in some stuff for sony's new AA method (where each level of each aa.has a seperate ID) */ + aa_points[(id - aa[a]->value + 1)] = aa[a]->value; + else + aa_points[id] = aa[a]->value; + } + + if (SPDAT_RECORDS > 0) { + for (uint32 z = 0; z= (uint32)SPDAT_RECORDS) + UnmemSpell(z, false); + } + + database.LoadBuffs(this); + uint32 max_slots = GetMaxBuffSlots(); + for (int i = 0; i < max_slots; i++) { + if (buffs[i].spellid != SPELL_UNKNOWN) { + m_pp.buffs[i].spellid = buffs[i].spellid; + m_pp.buffs[i].bard_modifier = 10; + m_pp.buffs[i].slotid = 2; + m_pp.buffs[i].player_id = 0x2211; + m_pp.buffs[i].level = buffs[i].casterlevel; + m_pp.buffs[i].effect = 0; + m_pp.buffs[i].duration = buffs[i].ticsremaining; + m_pp.buffs[i].counters = buffs[i].counters; + } + else { + m_pp.buffs[i].spellid = SPELLBOOK_UNKNOWN; + m_pp.buffs[i].bard_modifier = 10; + m_pp.buffs[i].slotid = 0; + m_pp.buffs[i].player_id = 0; + m_pp.buffs[i].level = 0; + m_pp.buffs[i].effect = 0; + m_pp.buffs[i].duration = 0; + m_pp.buffs[i].counters = 0; + } + } + } + + /* Load Character Key Ring */ + KeyRingLoad(); + + /* Send Group Members via PP */ + uint32 groupid = database.GetGroupID(GetName()); + Group* group = nullptr; + if (groupid > 0){ + group = entity_list.GetGroupByID(groupid); + if (!group) { //nobody from our is here... start a new group + group = new Group(groupid); + if (group->GetID() != 0) + entity_list.AddGroup(group, groupid); + else //error loading group members... + { + delete group; + group = nullptr; + } + } //else, somebody from our group is already here... + + if (!group) + database.SetGroupID(GetName(), 0, CharacterID(), false); //cannot re-establish group, kill it + + } + else { //no group id + //clear out the group junk in our PP + uint32 xy = 0; + for (xy = 0; xy < MAX_GROUP_MEMBERS; xy++) + memset(m_pp.groupMembers[xy], 0, 64); + } + + if (group){ + // If the group leader is not set, pull the group leader infomrmation from the database. + if (!group->GetLeader()){ + char ln[64]; + char MainTankName[64]; + char AssistName[64]; + char PullerName[64]; + char NPCMarkerName[64]; + char mentoree_name[64]; + int mentor_percent; + GroupLeadershipAA_Struct GLAA; + memset(ln, 0, 64); + strcpy(ln, database.GetGroupLeadershipInfo(group->GetID(), ln, MainTankName, AssistName, PullerName, NPCMarkerName, mentoree_name, &mentor_percent, &GLAA)); + Client *c = entity_list.GetClientByName(ln); + if (c) + group->SetLeader(c); + + group->SetMainTank(MainTankName); + group->SetMainAssist(AssistName); + group->SetPuller(PullerName); + group->SetNPCMarker(NPCMarkerName); + group->SetGroupAAs(&GLAA); + group->SetGroupMentor(mentor_percent, mentoree_name); + + //group->NotifyMainTank(this, 1); + //group->NotifyMainAssist(this, 1); + //group->NotifyPuller(this, 1); + + // If we are the leader, force an update of our group AAs to other members in the zone, in case + // we purchased a new one while out-of-zone. + if (group->IsLeader(this)) + group->SendLeadershipAAUpdate(); + + } + group->UpdatePlayer(this); + LFG = false; + } + +#ifdef BOTS + Bot::LoadAndSpawnAllZonedBots(this); +#endif + + CalcBonuses(); + if (RuleB(Zone, EnableLoggedOffReplenishments) && + time(nullptr) - m_pp.lastlogin >= RuleI(Zone, MinOfflineTimeToReplenishments)) { + m_pp.cur_hp = GetMaxHP(); + m_pp.mana = GetMaxMana(); + m_pp.endurance = GetMaxEndurance(); + } + + if (m_pp.cur_hp <= 0) + m_pp.cur_hp = GetMaxHP(); + + SetHP(m_pp.cur_hp); + Mob::SetMana(m_pp.mana); // mob function doesn't send the packet + SetEndurance(m_pp.endurance); + + /* Update LFP in case any (or all) of our group disbanded while we were zoning. */ + if (IsLFP()) { UpdateLFP(); } + + /* Get Expansions from variables table and ship via PP */ + char val[20] = { 0 }; + if (database.GetVariable("Expansions", val, 20)){ m_pp.expansions = atoi(val); } + else{ m_pp.expansions = 0x3FF; } + + p_timers.SetCharID(CharacterID()); + if (!p_timers.Load(&database)) { + LogFile->write(EQEMuLog::Error, "Unable to load ability timers from the database for %s (%i)!", GetCleanName(), CharacterID()); + } + + /* Load Spell Slot Refresh from Currently Memoried Spells */ + for (unsigned int i = 0; i < MAX_PP_MEMSPELL; ++i) + if (IsValidSpell(m_pp.mem_spells[i])) + m_pp.spellSlotRefresh[i] = p_timers.GetRemainingTime(pTimerSpellStart + m_pp.mem_spells[i]) * 1000; + + /* Ability slot refresh send SK/PAL */ + if (m_pp.class_ == SHADOWKNIGHT || m_pp.class_ == PALADIN) { + uint32 abilitynum = 0; + if (m_pp.class_ == SHADOWKNIGHT){ abilitynum = pTimerHarmTouch; } + else{ abilitynum = pTimerLayHands; } + + uint32 remaining = p_timers.GetRemainingTime(abilitynum); + if (remaining > 0 && remaining < 15300) + m_pp.abilitySlotRefresh = remaining * 1000; + else + m_pp.abilitySlotRefresh = 0; + } + +#ifdef _EQDEBUG + printf("Dumping inventory on load:\n"); + m_inv.dumpEntireInventory(); +#endif + + /* Reset to max so they dont drown on zone in if its underwater */ + m_pp.air_remaining = 60; + /* Check for PVP Zone status*/ + if (zone->IsPVPZone()) + m_pp.pvp = 1; + /* Time entitled on Account: Move to account */ + m_pp.timeentitledonaccount = database.GetTotalTimeEntitledOnAccount(AccountID()) / 1440; + /* Reset rest timer if the durations have been lowered in the database */ + if ((m_pp.RestTimer > RuleI(Character, RestRegenTimeToActivate)) && (m_pp.RestTimer > RuleI(Character, RestRegenRaidTimeToActivate))) + m_pp.RestTimer = 0; + + /* This checksum should disappear once dynamic structs are in... each struct strategy will do it */ + CRC32::SetEQChecksum((unsigned char*)&m_pp, sizeof(PlayerProfile_Struct)-4); + + outapp = new EQApplicationPacket(OP_PlayerProfile, sizeof(PlayerProfile_Struct)); + + /* The entityid field in the Player Profile is used by the Client in relation to Group Leadership AA */ + m_pp.entityid = GetID(); + memcpy(outapp->pBuffer, &m_pp, outapp->size); + outapp->priority = 6; + FastQueuePacket(&outapp); + + if (m_pp.RestTimer) + rest_timer.Start(m_pp.RestTimer * 1000); + + database.LoadPetInfo(this); + /* + This was moved before the spawn packets are sent + in hopes that it adds more consistency... + Remake pet + */ + if (m_petinfo.SpellID > 1 && !GetPet() && m_petinfo.SpellID <= SPDAT_RECORDS) { + MakePoweredPet(m_petinfo.SpellID, spells[m_petinfo.SpellID].teleport_zone, m_petinfo.petpower, m_petinfo.Name, m_petinfo.size); + if (GetPet() && GetPet()->IsNPC()) { + NPC *pet = GetPet()->CastToNPC(); + pet->SetPetState(m_petinfo.Buffs, m_petinfo.Items); + pet->CalcBonuses(); + pet->SetHP(m_petinfo.HP); + pet->SetMana(m_petinfo.Mana); + } + m_petinfo.SpellID = 0; + } + /* Moved here so it's after where we load the pet data. */ + if (!GetAA(aaPersistentMinion)) + memset(&m_suspendedminion, 0, sizeof(PetInfo)); + + /* Server Zone Entry Packet */ + outapp = new EQApplicationPacket(OP_ZoneEntry, sizeof(ServerZoneEntry_Struct)); + ServerZoneEntry_Struct* sze = (ServerZoneEntry_Struct*)outapp->pBuffer; + + FillSpawnStruct(&sze->player, CastToMob()); + sze->player.spawn.curHp = 1; + sze->player.spawn.NPC = 0; + sze->player.spawn.z += 6; //arbitrary lift, seems to help spawning under zone. + outapp->priority = 6; + FastQueuePacket(&outapp); + + /* Zone Spawns Packet */ + entity_list.SendZoneSpawnsBulk(this); + entity_list.SendZoneCorpsesBulk(this); + entity_list.SendZonePVPUpdates(this); //hack until spawn struct is fixed. + + /* Time of Day packet */ + outapp = new EQApplicationPacket(OP_TimeOfDay, sizeof(TimeOfDay_Struct)); + TimeOfDay_Struct* tod = (TimeOfDay_Struct*)outapp->pBuffer; + zone->zone_time.getEQTimeOfDay(time(0), tod); + outapp->priority = 6; + FastQueuePacket(&outapp); + + /* Tribute Packets */ + DoTributeUpdate(); + if (m_pp.tribute_active) { + //restart the tribute timer where we left off + tribute_timer.Start(m_pp.tribute_time_remaining); + } + + /* + Character Inventory Packet + this is not quite where live sends inventory, they do it after tribute + */ + if (loaditems) { /* Dont load if a length error occurs */ + BulkSendInventoryItems(); + /* Send stuff on the cursor which isnt sent in bulk */ + iter_queue it; + for (it = m_inv.cursor_begin(); it != m_inv.cursor_end(); ++it) { + /* First item cursor is sent in bulk inventory packet */ + if (it == m_inv.cursor_begin()) + continue; + const ItemInst *inst = *it; + SendItemPacket(MainCursor, inst, ItemPacketSummonItem); + } + } + + /* Task Packets */ + LoadClientTaskState(); + + if (GetClientVersion() >= EQClientRoF) { + outapp = new EQApplicationPacket(OP_ReqNewZone, 0); + Handle_Connect_OP_ReqNewZone(outapp); + safe_delete(outapp); + } + + if (ClientVersionBit & BIT_UnderfootAndLater) { + outapp = new EQApplicationPacket(OP_XTargetResponse, 8); + outapp->WriteUInt32(GetMaxXTargets()); + outapp->WriteUInt32(0); + FastQueuePacket(&outapp); + } + + /* + Weather Packet + This shouldent be moved, this seems to be what the client + uses to advance to the next state (sending ReqNewZone) + */ + outapp = new EQApplicationPacket(OP_Weather, 12); + Weather_Struct *ws = (Weather_Struct *)outapp->pBuffer; + ws->val1 = 0x000000FF; + if (zone->zone_weather == 1){ ws->type = 0x31; } // Rain + if (zone->zone_weather == 2) { + outapp->pBuffer[8] = 0x01; + ws->type = 0x02; + } + outapp->priority = 6; + QueuePacket(outapp); + safe_delete(outapp); + + SetAttackTimer(); + conn_state = ZoneInfoSent; + + return; +} + +// connected opcode handlers +void Client::Handle_0x0193(const EQApplicationPacket *app) +{ + // Not sure what this opcode does. It started being sent when OP_ClientUpdate was + // changed to pump OP_ClientUpdate back out instead of OP_MobUpdate + // 2 bytes: 00 00 + + return; +} + +void Client::Handle_OP_AAAction(const EQApplicationPacket *app) +{ + mlog(AA__IN, "Received OP_AAAction"); + mpkt(AA__IN, app); + + if (app->size != sizeof(AA_Action)){ + printf("Error! OP_AAAction size didnt match!\n"); + return; + } + AA_Action* action = (AA_Action*)app->pBuffer; + + if (action->action == aaActionActivate) {//AA Hotkey + mlog(AA__MESSAGE, "Activating AA %d", action->ability); + ActivateAA((aaID)action->ability); + } + else if (action->action == aaActionBuy) { + BuyAA(action); + } + else if (action->action == aaActionDisableEXP){ //Turn Off AA Exp + if (m_epp.perAA > 0) + Message_StringID(0, AA_OFF); + m_epp.perAA = 0; + SendAAStats(); + } + else if (action->action == aaActionSetEXP) { + if (m_epp.perAA == 0) + Message_StringID(0, AA_ON); + m_epp.perAA = action->exp_value; + if (m_epp.perAA<0 || m_epp.perAA>100) m_epp.perAA = 0; // stop exploit with sanity check + // send an update + SendAAStats(); + SendAATable(); + } + else { + printf("Unknown AA action: %u %u 0x%x %d\n", action->action, action->ability, action->unknown08, action->exp_value); + } + + return; +} + +void Client::Handle_OP_AcceptNewTask(const EQApplicationPacket *app) +{ + + if (app->size != sizeof(AcceptNewTask_Struct)) { + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_AcceptNewTask expected %i got %i", + sizeof(AcceptNewTask_Struct), app->size); + DumpPacket(app); + return; + } + AcceptNewTask_Struct *ant = (AcceptNewTask_Struct*)app->pBuffer; + + if (ant->task_id > 0 && RuleB(TaskSystem, EnableTaskSystem) && taskstate) + taskstate->AcceptNewTask(this, ant->task_id, ant->task_master_id); +} + +void Client::Handle_OP_AdventureInfoRequest(const EQApplicationPacket *app) +{ + if (app->size < sizeof(EntityId_Struct)) + { + LogFile->write(EQEMuLog::Error, "Handle_OP_AdventureInfoRequest had a packet that was too small."); + return; + } + EntityId_Struct* ent = (EntityId_Struct*)app->pBuffer; + Mob * m = entity_list.GetMob(ent->entity_id); + if (m && m->IsNPC()) + { + std::map::iterator it; + it = zone->adventure_entry_list_flavor.find(m->CastToNPC()->GetAdventureTemplate()); + if (it != zone->adventure_entry_list_flavor.end()) + { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_AdventureInfo, (it->second.size() + 2)); + strn0cpy((char*)outapp->pBuffer, it->second.c_str(), it->second.size()); + FastQueuePacket(&outapp); + } + else + { + if (m->CastToNPC()->GetAdventureTemplate() != 0) + { + std::string text = "Choose your difficulty and preferred adventure type."; + EQApplicationPacket* outapp = new EQApplicationPacket(OP_AdventureInfo, (text.size() + 2)); + strn0cpy((char*)outapp->pBuffer, text.c_str(), text.size()); + FastQueuePacket(&outapp); + } + } + } +} + +void Client::Handle_OP_AdventureLeaderboardRequest(const EQApplicationPacket *app) +{ + if (app->size < sizeof(AdventureLeaderboardRequest_Struct)) + { + return; + } + + if (adventure_leaderboard_timer) + { + return; + } + + adventure_leaderboard_timer = new Timer(4000); + ServerPacket *pack = new ServerPacket(ServerOP_AdventureLeaderboard, sizeof(ServerLeaderboardRequest_Struct)); + ServerLeaderboardRequest_Struct *lr = (ServerLeaderboardRequest_Struct*)pack->pBuffer; + strcpy(lr->player, GetName()); + + AdventureLeaderboardRequest_Struct *lrs = (AdventureLeaderboardRequest_Struct*)app->pBuffer; + lr->type = 1 + (lrs->theme * 2) + lrs->type; + worldserver.SendPacket(pack); + delete pack; +} + +void Client::Handle_OP_AdventureMerchantPurchase(const EQApplicationPacket *app) +{ + if (app->size != sizeof(Adventure_Purchase_Struct)) + { + LogFile->write(EQEMuLog::Error, "OP size error: OP_AdventureMerchantPurchase expected:%i got:%i", sizeof(Adventure_Purchase_Struct), app->size); + return; + } + + Adventure_Purchase_Struct* aps = (Adventure_Purchase_Struct*)app->pBuffer; + /* + Get item apc->itemid (can check NPC if thats necessary), ldon point theme check only if theme is not 0 (I am not sure what 1-5 are though for themes) + if(ldon_points_available >= item ldonpointcost) + { + give item (67 00 00 00 for the packettype using opcode 0x02c5) + ldon_points_available -= ldonpointcost; + } + */ + uint32 merchantid = 0; + Mob* tmp = entity_list.GetMob(aps->npcid); + if (tmp == 0 || !tmp->IsNPC() || ((tmp->GetClass() != ADVENTUREMERCHANT) && + (tmp->GetClass() != DISCORD_MERCHANT) && (tmp->GetClass() != NORRATHS_KEEPERS_MERCHANT) && (tmp->GetClass() != DARK_REIGN_MERCHANT))) + return; + + //you have to be somewhat close to them to be properly using them + if (DistNoRoot(*tmp) > USE_NPC_RANGE2) + return; + + merchantid = tmp->CastToNPC()->MerchantType; + + const Item_Struct* item = nullptr; + bool found = false; + std::list merlist = zone->merchanttable[merchantid]; + std::list::const_iterator itr; + + for (itr = merlist.begin(); itr != merlist.end(); ++itr){ + MerchantList ml = *itr; + if (GetLevel() < ml.level_required) { + continue; + } + + int32 fac = tmp->GetPrimaryFaction(); + if (fac != 0 && GetModCharacterFactionLevel(fac) < ml.faction_required) { + continue; + } + + item = database.GetItem(ml.item); + if (!item) + continue; + if (item->ID == aps->itemid) { //This check to make sure that the item is actually on the NPC, people attempt to inject packets to get items summoned... + found = true; + break; + } + } + if (!item || !found) { + Message(13, "Error: The item you purchased does not exist!"); + return; + } + + if (aps->Type == LDoNMerchant) + { + if (m_pp.ldon_points_available < int32(item->LDoNPrice)) { + Message(13, "You cannot afford that item."); + return; + } + + if (item->LDoNTheme <= 16) + { + if (item->LDoNTheme & 16) + { + if (m_pp.ldon_points_tak < int32(item->LDoNPrice)) + { + Message(13, "You need at least %u points in tak to purchase this item.", int32(item->LDoNPrice)); + return; + } + } + else if (item->LDoNTheme & 8) + { + if (m_pp.ldon_points_ruj < int32(item->LDoNPrice)) + { + Message(13, "You need at least %u points in ruj to purchase this item.", int32(item->LDoNPrice)); + return; + } + } + else if (item->LDoNTheme & 4) + { + if (m_pp.ldon_points_mmc < int32(item->LDoNPrice)) + { + Message(13, "You need at least %u points in mmc to purchase this item.", int32(item->LDoNPrice)); + return; + } + } + else if (item->LDoNTheme & 2) + { + if (m_pp.ldon_points_mir < int32(item->LDoNPrice)) + { + Message(13, "You need at least %u points in mir to purchase this item.", int32(item->LDoNPrice)); + return; + } + } + else if (item->LDoNTheme & 1) + { + if (m_pp.ldon_points_guk < int32(item->LDoNPrice)) + { + Message(13, "You need at least %u points in guk to purchase this item.", int32(item->LDoNPrice)); + return; + } + } + } + } + else if (aps->Type == DiscordMerchant) + { + if (GetPVPPoints() < item->LDoNPrice) + { + Message(13, "You need at least %u PVP points to purchase this item.", int32(item->LDoNPrice)); + return; + } + } + else if (aps->Type == NorrathsKeepersMerchant) + { + if (GetRadiantCrystals() < item->LDoNPrice) + { + Message(13, "You need at least %u Radiant Crystals to purchase this item.", int32(item->LDoNPrice)); + return; + } + } + else if (aps->Type == DarkReignMerchant) + { + if (GetEbonCrystals() < item->LDoNPrice) + { + Message(13, "You need at least %u Ebon Crystals to purchase this item.", int32(item->LDoNPrice)); + return; + } + } + else + { + Message(13, "Unknown Adventure Merchant type."); + return; + } + + + if (CheckLoreConflict(item)) + { + Message(15, "You can only have one of a lore item."); + return; + } + + if (aps->Type == LDoNMerchant) + { + int32 requiredpts = (int32)item->LDoNPrice*-1; + + if (!UpdateLDoNPoints(requiredpts, 6)) + return; + } + else if (aps->Type == DiscordMerchant) + { + SetPVPPoints(GetPVPPoints() - (int32)item->LDoNPrice); + SendPVPStats(); + } + else if (aps->Type == NorrathsKeepersMerchant) + { + SetRadiantCrystals(GetRadiantCrystals() - (int32)item->LDoNPrice); + SendCrystalCounts(); + } + else if (aps->Type == DarkReignMerchant) + { + SetEbonCrystals(GetEbonCrystals() - (int32)item->LDoNPrice); + SendCrystalCounts(); + } + int16 charges = 1; + if (item->MaxCharges != 0) + charges = item->MaxCharges; + + ItemInst *inst = database.CreateItem(item, charges); + if (!AutoPutLootInInventory(*inst, true, true)) + { + PutLootInInventory(MainCursor, *inst); + } + Save(1); +} + +void Client::Handle_OP_AdventureMerchantRequest(const EQApplicationPacket *app) +{ + if (app->size != sizeof(AdventureMerchant_Struct)) + { + LogFile->write(EQEMuLog::Error, "OP size error: OP_AdventureMerchantRequest expected:%i got:%i", sizeof(AdventureMerchant_Struct), app->size); + return; + } + std::stringstream ss(std::stringstream::in | std::stringstream::out); + + uint8 count = 0; + AdventureMerchant_Struct* eid = (AdventureMerchant_Struct*)app->pBuffer; + uint32 merchantid = 0; + + Mob* tmp = entity_list.GetMob(eid->entity_id); + if (tmp == 0 || !tmp->IsNPC() || ((tmp->GetClass() != ADVENTUREMERCHANT) && + (tmp->GetClass() != DISCORD_MERCHANT) && (tmp->GetClass() != NORRATHS_KEEPERS_MERCHANT) && (tmp->GetClass() != DARK_REIGN_MERCHANT))) + return; + + //you have to be somewhat close to them to be properly using them + if (DistNoRoot(*tmp) > USE_NPC_RANGE2) + return; + + merchantid = tmp->CastToNPC()->MerchantType; + tmp->CastToNPC()->FaceTarget(this->CastToMob()); + + const Item_Struct *item = 0; + std::list merlist = zone->merchanttable[merchantid]; + std::list::const_iterator itr; + for (itr = merlist.begin(); itr != merlist.end() && count<255; ++itr){ + const MerchantList &ml = *itr; + if (GetLevel() < ml.level_required) { + continue; + } + + int32 fac = tmp->GetPrimaryFaction(); + if (fac != 0 && GetModCharacterFactionLevel(fac) < ml.faction_required) { + continue; + } + + item = database.GetItem(ml.item); + if (item) + { + uint32 theme; + if (item->LDoNTheme > 16) + { + theme = 0; + } + else if (item->LDoNTheme & 16) + { + theme = 5; + } + else if (item->LDoNTheme & 8) + { + theme = 4; + } + else if (item->LDoNTheme & 4) + { + theme = 3; + } + else if (item->LDoNTheme & 2) + { + theme = 2; + } + else if (item->LDoNTheme & 1) + { + theme = 1; + } + else + { + theme = 0; + } + ss << "^" << item->Name << "|"; + ss << item->ID << "|"; + ss << item->LDoNPrice << "|"; + ss << theme << "|"; + ss << "0|"; + ss << "1|"; + ss << item->Races << "|"; + ss << item->Classes; + count++; + } + } + //Count + //^Item Name,Item ID,Cost in Points,Theme (0=none),0,1,races bit map,classes bitmap + EQApplicationPacket* outapp = new EQApplicationPacket(OP_AdventureMerchantResponse, ss.str().size() + 2); + outapp->pBuffer[0] = count; + strn0cpy((char*)&outapp->pBuffer[1], ss.str().c_str(), ss.str().size()); + FastQueuePacket(&outapp); +} + +void Client::Handle_OP_AdventureMerchantSell(const EQApplicationPacket *app) +{ + if (app->size != sizeof(Adventure_Sell_Struct)) + { + LogFile->write(EQEMuLog::Debug, "Size mismatch on OP_AdventureMerchantSell: got %u expected %u", + app->size, sizeof(Adventure_Sell_Struct)); + DumpPacket(app); + return; + } + + Adventure_Sell_Struct *ams_in = (Adventure_Sell_Struct*)app->pBuffer; + + Mob* vendor = entity_list.GetMob(ams_in->npcid); + if (vendor == 0 || !vendor->IsNPC() || ((vendor->GetClass() != ADVENTUREMERCHANT) && + (vendor->GetClass() != NORRATHS_KEEPERS_MERCHANT) && (vendor->GetClass() != DARK_REIGN_MERCHANT))) + { + Message(13, "Vendor was not found."); + return; + } + + if (DistNoRoot(*vendor) > USE_NPC_RANGE2) + { + Message(13, "Vendor is out of range."); + return; + } + + uint32 itemid = GetItemIDAt(ams_in->slot); + + if (itemid == 0) + { + Message(13, "Found no item at that slot."); + return; + } + + const Item_Struct* item = database.GetItem(itemid); + ItemInst* inst = GetInv().GetItem(ams_in->slot); + if (!item || !inst){ + Message(13, "You seemed to have misplaced that item..."); + return; + } + + // Note that Lucy has ldonsold values of 4 and 5 for items sold by Norrath's Keepers and Dark Reign, whereas 13th Floor + // has ldonsold = 0 for these items, so some manual editing of the items DB will be required to support sell back of the + // items. + // + // The Merchant seems to have some other way of knowing whether he will accept the item, other than the ldonsold field, + // e.g. if you summon items 76036 and 76053 (good and evil versions of Spell: Ward Of Vengeance), if you are interacting + // with a Norrath's Keeper merchant and click on 76036 in your inventory, he says he will give you radiant crystals for + // it, but he will refuse for item 76053. + // + // Similarly, just giving a cloth cap an ldonsold value of 4 will not make the Merchant buy it. + // + // Note that the the Client will not allow you to sell anything back to a Discord merchant, so there is no need to handle + // that case here. + if (item->LDoNSold == 0) + { + Message(13, "The merchant does not want that item."); + return; + } + + if (item->LDoNPrice == 0) + { + Message(13, "The merchant does not want that item."); + return; + } + + int32 price = item->LDoNPrice * 70 / 100; + + if (price == 0) + { + Message(13, "The merchant does not want that item."); + return; + } + + if (RuleB(EventLog, RecordSellToMerchant)) + LogMerchant(this, vendor, ams_in->charges, price, item, false); + + if (!inst->IsStackable()) + { + DeleteItemInInventory(ams_in->slot, 0, false); + } + else + { + if (inst->GetCharges() < ams_in->charges) + { + ams_in->charges = inst->GetCharges(); + } + + if (ams_in->charges == 0) + { + Message(13, "Charge mismatch error."); + return; + } + + DeleteItemInInventory(ams_in->slot, ams_in->charges, false); + price *= ams_in->charges; + } + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_AdventureMerchantSell, sizeof(Adventure_Sell_Struct)); + Adventure_Sell_Struct *ams = (Adventure_Sell_Struct*)outapp->pBuffer; + ams->slot = ams_in->slot; + ams->unknown000 = 1; + ams->npcid = ams->npcid; + ams->charges = ams_in->charges; + ams->sell_price = price; + FastQueuePacket(&outapp); + + switch (vendor->GetClass()) + { + case ADVENTUREMERCHANT: + { + UpdateLDoNPoints(price, 6); + break; + } + case NORRATHS_KEEPERS_MERCHANT: + { + SetRadiantCrystals(GetRadiantCrystals() + price); + break; + } + case DARK_REIGN_MERCHANT: + { + SetEbonCrystals(GetEbonCrystals() + price); + break; + } + + default: + break; + } + + Save(1); +} + +void Client::Handle_OP_AdventureRequest(const EQApplicationPacket *app) +{ + if (app->size < sizeof(AdventureRequest_Struct)) + { + LogFile->write(EQEMuLog::Error, "Handle_OP_AdventureRequest had a packet that was too small."); + return; + } + + if (IsOnAdventure()) + { + return; + } + + if (!p_timers.Expired(&database, pTimerStartAdventureTimer, false)) + { + return; + } + + if (GetPendingAdventureRequest()) + { + return; + } + + AdventureRequest_Struct* ars = (AdventureRequest_Struct*)app->pBuffer; + uint8 group_members = 0; + Raid *r = nullptr; + Group *g = nullptr; + + if (IsRaidGrouped()) + { + r = GetRaid(); + group_members = r->RaidCount(); + } + else if (IsGrouped()) + { + g = GetGroup(); + group_members = g->GroupCount(); + } + else + { + return; + } + + if (group_members < RuleI(Adventure, MinNumberForGroup) || group_members > RuleI(Adventure, MaxNumberForGroup)) + { + return; + } + + Mob* m = entity_list.GetMob(ars->entity_id); + uint32 template_id = 0; + if (m && m->IsNPC()) + { + template_id = m->CastToNPC()->GetAdventureTemplate(); + } + else + { + return; + } + + ServerPacket *packet = new ServerPacket(ServerOP_AdventureRequest, sizeof(ServerAdventureRequest_Struct)+(64 * group_members)); + ServerAdventureRequest_Struct *sar = (ServerAdventureRequest_Struct*)packet->pBuffer; + sar->member_count = group_members; + sar->risk = ars->risk; + sar->type = ars->type; + sar->template_id = template_id; + strcpy(sar->leader, GetName()); + + if (IsRaidGrouped()) + { + int i = 0; + for (int x = 0; x < 72; ++x) + { + if (i == group_members) + { + break; + } + + const char *c_name = nullptr; + c_name = r->GetClientNameByIndex(x); + if (c_name) + { + memcpy((packet->pBuffer + sizeof(ServerAdventureRequest_Struct)+(64 * i)), c_name, strlen(c_name)); + ++i; + } + } + } + else + { + int i = 0; + for (int x = 0; x < 6; ++x) + { + if (i == group_members) + { + break; + } + + const char *c_name = nullptr; + c_name = g->GetClientNameByIndex(x); + if (c_name) + { + memcpy((packet->pBuffer + sizeof(ServerAdventureRequest_Struct)+(64 * i)), c_name, strlen(c_name)); + ++i; + } + } + } + + packet->Deflate(); + worldserver.SendPacket(packet); + delete packet; + p_timers.Start(pTimerStartAdventureTimer, 5); +} + +void Client::Handle_OP_AdventureStatsRequest(const EQApplicationPacket *app) +{ + if (adventure_stats_timer) + { + return; + } + + adventure_stats_timer = new Timer(8000); + EQApplicationPacket* outapp = new EQApplicationPacket(OP_AdventureStatsReply, sizeof(AdventureStats_Struct)); + AdventureStats_Struct *as = (AdventureStats_Struct*)outapp->pBuffer; + + if (database.GetAdventureStats(CharacterID(), as)) + { + m_pp.ldon_wins_guk = as->success.guk; + m_pp.ldon_wins_mir = as->success.mir; + m_pp.ldon_wins_mmc = as->success.mmc; + m_pp.ldon_wins_ruj = as->success.ruj; + m_pp.ldon_wins_tak = as->success.tak; + m_pp.ldon_losses_guk = as->failure.guk; + m_pp.ldon_losses_mir = as->failure.mir; + m_pp.ldon_losses_mmc = as->failure.mmc; + m_pp.ldon_losses_ruj = as->failure.ruj; + m_pp.ldon_losses_tak = as->failure.tak; + } + + FastQueuePacket(&outapp); +} + +void Client::Handle_OP_AltCurrencyMerchantRequest(const EQApplicationPacket *app) +{ + VERIFY_PACKET_LENGTH(OP_AltCurrencyMerchantRequest, app, uint32); + + NPC* tar = entity_list.GetNPCByID(*((uint32*)app->pBuffer)); + if (tar) { + if (DistNoRoot(*tar) > USE_NPC_RANGE2) + return; + + if (tar->GetClass() != ALT_CURRENCY_MERCHANT) { + return; + } + + uint32 alt_cur_id = tar->GetAltCurrencyType(); + if (alt_cur_id == 0) { + return; + } + + std::list::iterator altc_iter = zone->AlternateCurrencies.begin(); + bool found = false; + while (altc_iter != zone->AlternateCurrencies.end()) { + if ((*altc_iter).id == alt_cur_id) { + found = true; + break; + } + ++altc_iter; + } + + if (!found) { + return; + } + + std::stringstream ss(std::stringstream::in | std::stringstream::out); + std::stringstream item_ss(std::stringstream::in | std::stringstream::out); + ss << alt_cur_id << "|1|" << alt_cur_id; + uint32 count = 0; + uint32 merchant_id = tar->MerchantType; + const Item_Struct *item = nullptr; + + std::list merlist = zone->merchanttable[merchant_id]; + std::list::const_iterator itr; + for (itr = merlist.begin(); itr != merlist.end() && count < 255; ++itr){ + const MerchantList &ml = *itr; + if (GetLevel() < ml.level_required) { + continue; + } + + int32 fac = tar->GetPrimaryFaction(); + if (fac != 0 && GetModCharacterFactionLevel(fac) < ml.faction_required) { + continue; + } + + item = database.GetItem(ml.item); + if (item) + { + item_ss << "^" << item->Name << "|"; + item_ss << item->ID << "|"; + item_ss << ml.alt_currency_cost << "|"; + item_ss << "0|"; + item_ss << "1|"; + item_ss << item->Races << "|"; + item_ss << item->Classes; + count++; + } + } + + if (count > 0) { + ss << "|" << count << item_ss.str(); + } + else { + ss << "|0"; + } + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_AltCurrencyMerchantReply, ss.str().length() + 1); + memcpy(outapp->pBuffer, ss.str().c_str(), ss.str().length()); + FastQueuePacket(&outapp); + } +} + +void Client::Handle_OP_AltCurrencyPurchase(const EQApplicationPacket *app) +{ + VERIFY_PACKET_LENGTH(OP_AltCurrencyPurchase, app, AltCurrencyPurchaseItem_Struct); + AltCurrencyPurchaseItem_Struct *purchase = (AltCurrencyPurchaseItem_Struct*)app->pBuffer; + NPC* tar = entity_list.GetNPCByID(purchase->merchant_entity_id); + if (tar) { + if (DistNoRoot(*tar) > USE_NPC_RANGE2) + return; + + if (tar->GetClass() != ALT_CURRENCY_MERCHANT) { + return; + } + + uint32 alt_cur_id = tar->GetAltCurrencyType(); + if (alt_cur_id == 0) { + return; + } + + const Item_Struct* item = nullptr; + uint32 cost = 0; + uint32 current_currency = GetAlternateCurrencyValue(alt_cur_id); + uint32 merchant_id = tar->MerchantType; + bool found = false; + std::list merlist = zone->merchanttable[merchant_id]; + std::list::const_iterator itr; + for (itr = merlist.begin(); itr != merlist.end(); ++itr) { + MerchantList ml = *itr; + if (GetLevel() < ml.level_required) { + continue; + } + + int32 fac = tar->GetPrimaryFaction(); + if (fac != 0 && GetModCharacterFactionLevel(fac) < ml.faction_required) { + continue; + } + + item = database.GetItem(ml.item); + if (!item) + continue; + + if (item->ID == purchase->item_id) { //This check to make sure that the item is actually on the NPC, people attempt to inject packets to get items summoned... + cost = ml.alt_currency_cost; + found = true; + break; + } + } + + if (!item || !found) { + Message(13, "Error: The item you purchased does not exist!"); + return; + } + + if (cost > current_currency) { + Message(13, "You cannot afford that item right now."); + return; + } + + if (CheckLoreConflict(item)) + { + Message(15, "You can only have one of a lore item."); + return; + } + + /* QS: PlayerLogAlternateCurrencyTransactions :: Merchant Purchase */ + if (RuleB(QueryServ, PlayerLogAlternateCurrencyTransactions)){ + std::string event_desc = StringFormat("Merchant Purchase :: Spent alt_currency_id:%i cost:%i for itemid:%i in zoneid:%i instid:%i", alt_cur_id, cost, item->ID, this->GetZoneID(), this->GetInstanceID()); + QServ->PlayerLogEvent(Player_Log_Alternate_Currency_Transactions, this->CharacterID(), event_desc); + } + + AddAlternateCurrencyValue(alt_cur_id, -((int32)cost)); + int16 charges = 1; + if (item->MaxCharges != 0) + charges = item->MaxCharges; + + ItemInst *inst = database.CreateItem(item, charges); + if (!AutoPutLootInInventory(*inst, true, true)) + { + PutLootInInventory(MainCursor, *inst); + } + + Save(1); + } +} + +void Client::Handle_OP_AltCurrencyReclaim(const EQApplicationPacket *app) +{ + VERIFY_PACKET_LENGTH(OP_AltCurrencyReclaim, app, AltCurrencyReclaim_Struct); + AltCurrencyReclaim_Struct *reclaim = (AltCurrencyReclaim_Struct*)app->pBuffer; + uint32 item_id = 0; + std::list::iterator iter = zone->AlternateCurrencies.begin(); + while (iter != zone->AlternateCurrencies.end()) { + if ((*iter).id == reclaim->currency_id) { + item_id = (*iter).item_id; + } + ++iter; + } + + if (item_id == 0) { + return; + } + + /* Item to Currency Storage */ + if (reclaim->reclaim_flag == 1) { + uint32 removed = NukeItem(item_id, invWhereWorn | invWherePersonal | invWhereCursor); + if (removed > 0) { + AddAlternateCurrencyValue(reclaim->currency_id, removed); + + /* QS: PlayerLogAlternateCurrencyTransactions :: Item to Currency */ + if (RuleB(QueryServ, PlayerLogAlternateCurrencyTransactions)){ + std::string event_desc = StringFormat("Reclaim :: Item to Currency :: alt_currency_id:%i amount:%i to currency tab in zoneid:%i instid:%i", reclaim->currency_id, removed, this->GetZoneID(), this->GetInstanceID()); + QServ->PlayerLogEvent(Player_Log_Alternate_Currency_Transactions, this->CharacterID(), event_desc); + } + } + } + /* Cursor to Item storage */ + else { + uint32 max_currency = GetAlternateCurrencyValue(reclaim->currency_id); + + /* If you input more than you have currency wise, just give the max of the currency you currently have */ + if (reclaim->count > max_currency) { + SummonItem(item_id, max_currency); + SetAlternateCurrencyValue(reclaim->currency_id, 0); + } + else { + SummonItem(item_id, reclaim->count, 0, 0, 0, 0, 0, false, MainCursor); + AddAlternateCurrencyValue(reclaim->currency_id, -((int32)reclaim->count)); + } + /* QS: PlayerLogAlternateCurrencyTransactions :: Cursor to Item Storage */ + if (RuleB(QueryServ, PlayerLogAlternateCurrencyTransactions)){ + std::string event_desc = StringFormat("Reclaim :: Cursor to Item :: alt_currency_id:%i amount:-%i in zoneid:%i instid:%i", reclaim->currency_id, reclaim->count, this->GetZoneID(), this->GetInstanceID()); + QServ->PlayerLogEvent(Player_Log_Alternate_Currency_Transactions, this->CharacterID(), event_desc); + } + } +} + +void Client::Handle_OP_AltCurrencySell(const EQApplicationPacket *app) +{ + VERIFY_PACKET_LENGTH(OP_AltCurrencySell, app, AltCurrencySellItem_Struct); + EQApplicationPacket *outapp = app->Copy(); + AltCurrencySellItem_Struct *sell = (AltCurrencySellItem_Struct*)outapp->pBuffer; + + NPC* tar = entity_list.GetNPCByID(sell->merchant_entity_id); + if (tar) { + if (DistNoRoot(*tar) > USE_NPC_RANGE2) + return; + + if (tar->GetClass() != ALT_CURRENCY_MERCHANT) { + return; + } + + uint32 alt_cur_id = tar->GetAltCurrencyType(); + if (alt_cur_id == 0) { + return; + } + + ItemInst* inst = GetInv().GetItem(sell->slot_id); + if (!inst) { + return; + } + + if (!RuleB(Merchant, EnableAltCurrencySell)) { + return; + } + + const Item_Struct* item = nullptr; + uint32 cost = 0; + uint32 current_currency = GetAlternateCurrencyValue(alt_cur_id); + uint32 merchant_id = tar->MerchantType; + uint32 npc_id = tar->GetNPCTypeID(); + bool found = false; + std::list merlist = zone->merchanttable[merchant_id]; + std::list::const_iterator itr; + for (itr = merlist.begin(); itr != merlist.end(); ++itr) { + MerchantList ml = *itr; + if (GetLevel() < ml.level_required) { + continue; + } + + int32 fac = tar->GetPrimaryFaction(); + if (fac != 0 && GetModCharacterFactionLevel(fac) < ml.faction_required) { + continue; + } + + item = database.GetItem(ml.item); + if (!item) + continue; + + if (item->ID == inst->GetItem()->ID) { + cost = ml.alt_currency_cost; + found = true; + break; + } + } + + if (!found) { + return; + } + + if (!inst->IsStackable()) + { + DeleteItemInInventory(sell->slot_id, 0, false); + } + else + { + if (inst->GetCharges() < sell->charges) + { + sell->charges = inst->GetCharges(); + } + + if (sell->charges == 0) + { + Message(13, "Charge mismatch error."); + return; + } + + DeleteItemInInventory(sell->slot_id, sell->charges, false); + cost *= sell->charges; + } + + sell->cost = cost; + + /* QS: PlayerLogAlternateCurrencyTransactions :: Sold to Merchant*/ + if (RuleB(QueryServ, PlayerLogAlternateCurrencyTransactions)){ + std::string event_desc = StringFormat("Sold to Merchant :: itemid:%u npcid:%u alt_currency_id:%u cost:%u in zoneid:%u instid:%i", item->ID, npc_id, alt_cur_id, cost, this->GetZoneID(), this->GetInstanceID()); + QServ->PlayerLogEvent(Player_Log_Alternate_Currency_Transactions, this->CharacterID(), event_desc); + } + + FastQueuePacket(&outapp); + AddAlternateCurrencyValue(alt_cur_id, cost); + Save(1); + } +} + +void Client::Handle_OP_AltCurrencySellSelection(const EQApplicationPacket *app) +{ + VERIFY_PACKET_LENGTH(OP_AltCurrencySellSelection, app, AltCurrencySelectItem_Struct); + + AltCurrencySelectItem_Struct *select = (AltCurrencySelectItem_Struct*)app->pBuffer; + NPC* tar = entity_list.GetNPCByID(select->merchant_entity_id); + if (tar) { + if (DistNoRoot(*tar) > USE_NPC_RANGE2) + return; + + if (tar->GetClass() != ALT_CURRENCY_MERCHANT) { + return; + } + + uint32 alt_cur_id = tar->GetAltCurrencyType(); + if (alt_cur_id == 0) { + return; + } + + ItemInst *inst = m_inv.GetItem(select->slot_id); + if (!inst) { + return; + } + + const Item_Struct* item = nullptr; + uint32 cost = 0; + uint32 current_currency = GetAlternateCurrencyValue(alt_cur_id); + uint32 merchant_id = tar->MerchantType; + + if (RuleB(Merchant, EnableAltCurrencySell)) { + bool found = false; + std::list merlist = zone->merchanttable[merchant_id]; + std::list::const_iterator itr; + for (itr = merlist.begin(); itr != merlist.end(); ++itr) { + MerchantList ml = *itr; + if (GetLevel() < ml.level_required) { + continue; + } + + int32 fac = tar->GetPrimaryFaction(); + if (fac != 0 && GetModCharacterFactionLevel(fac) < ml.faction_required) { + continue; + } + + item = database.GetItem(ml.item); + if (!item) + continue; + + if (item->ID == inst->GetItem()->ID) { + cost = ml.alt_currency_cost; + found = true; + break; + } + } + + if (!found) { + cost = 0; + } + } + else { + cost = 0; + } + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_AltCurrencySellSelection, sizeof(AltCurrencySelectItemReply_Struct)); + AltCurrencySelectItemReply_Struct *reply = (AltCurrencySelectItemReply_Struct*)outapp->pBuffer; + reply->unknown004 = 0xFF; + reply->unknown005 = 0xFF; + reply->unknown006 = 0xFF; + reply->unknown007 = 0xFF; + strcpy(reply->item_name, inst->GetItem()->Name); + reply->cost = cost; + FastQueuePacket(&outapp); + } +} + +void Client::Handle_OP_Animation(const EQApplicationPacket *app) +{ + if (app->size != sizeof(Animation_Struct)) { + LogFile->write(EQEMuLog::Error, "Received invalid sized " + "OP_Animation: got %d, expected %d", app->size, + sizeof(Animation_Struct)); + DumpPacket(app); + return; + } + + Animation_Struct *s = (Animation_Struct *)app->pBuffer; + + //might verify spawn ID, but it wouldent affect anything + + DoAnim(s->action, s->value); + + return; +} + +void Client::Handle_OP_ApplyPoison(const EQApplicationPacket *app) +{ + if (app->size != sizeof(ApplyPoison_Struct)) { + LogFile->write(EQEMuLog::Error, "Wrong size: OP_ApplyPoison, size=%i, expected %i", app->size, sizeof(ApplyPoison_Struct)); + DumpPacket(app); + return; + } + uint32 ApplyPoisonSuccessResult = 0; + ApplyPoison_Struct* ApplyPoisonData = (ApplyPoison_Struct*)app->pBuffer; + const ItemInst* PrimaryWeapon = GetInv().GetItem(MainPrimary); + const ItemInst* SecondaryWeapon = GetInv().GetItem(MainSecondary); + const ItemInst* PoisonItemInstance = GetInv()[ApplyPoisonData->inventorySlot]; + + bool IsPoison = PoisonItemInstance && (PoisonItemInstance->GetItem()->ItemType == ItemTypePoison); + + if (!IsPoison) + { + mlog(SPELLS__CASTING_ERR, "Item used to cast spell effect from a poison item was missing from inventory slot %d " + "after casting, or is not a poison!", ApplyPoisonData->inventorySlot); + + Message(0, "Error: item not found for inventory slot #%i or is not a poison", ApplyPoisonData->inventorySlot); + } + else if (GetClass() == ROGUE) + { + if ((PrimaryWeapon && PrimaryWeapon->GetItem()->ItemType == ItemType1HPiercing) || + (SecondaryWeapon && SecondaryWeapon->GetItem()->ItemType == ItemType1HPiercing)) { + float SuccessChance = (GetSkill(SkillApplyPoison) + GetLevel()) / 400.0f; + double ChanceRoll = zone->random.Real(0, 1); + + CheckIncreaseSkill(SkillApplyPoison, nullptr, 10); + + if (ChanceRoll < SuccessChance) { + ApplyPoisonSuccessResult = 1; + // NOTE: Someone may want to tweak the chance to proc the poison effect that is added to the weapon here. + // My thinking was that DEX should be apart of the calculation. + AddProcToWeapon(PoisonItemInstance->GetItem()->Proc.Effect, false, (GetDEX() / 100) + 103); + } + + DeleteItemInInventory(ApplyPoisonData->inventorySlot, 1, true); + + LogFile->write(EQEMuLog::Debug, "Chance to Apply Poison was %f. Roll was %f. Result is %u.", SuccessChance, ChanceRoll, ApplyPoisonSuccessResult); + } + } + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_ApplyPoison, nullptr, sizeof(ApplyPoison_Struct)); + ApplyPoison_Struct* ApplyPoisonResult = (ApplyPoison_Struct*)outapp->pBuffer; + ApplyPoisonResult->success = ApplyPoisonSuccessResult; + ApplyPoisonResult->inventorySlot = ApplyPoisonData->inventorySlot; + + FastQueuePacket(&outapp); +} + +void Client::Handle_OP_Assist(const EQApplicationPacket *app) +{ + if (app->size != sizeof(EntityId_Struct)) { + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_Assist expected %i got %i", sizeof(EntityId_Struct), app->size); + return; + } + + EntityId_Struct* eid = (EntityId_Struct*)app->pBuffer; + Entity* entity = entity_list.GetID(eid->entity_id); + + EQApplicationPacket* outapp = app->Copy(); + eid = (EntityId_Struct*)outapp->pBuffer; + if (RuleB(Combat, AssistNoTargetSelf)) + eid->entity_id = GetID(); + if (entity && entity->IsMob()) { + Mob *assistee = entity->CastToMob(); + if (assistee->GetTarget()) { + Mob *new_target = assistee->GetTarget(); + if (new_target && (GetGM() || + Dist(*assistee) <= TARGETING_RANGE)) { + SetAssistExemption(true); + eid->entity_id = new_target->GetID(); + } + } + } + + FastQueuePacket(&outapp); + return; +} + +void Client::Handle_OP_AssistGroup(const EQApplicationPacket *app) +{ + if (app->size != sizeof(EntityId_Struct)) { + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_AssistGroup expected %i got %i", sizeof(EntityId_Struct), app->size); + return; + } + QueuePacket(app); + return; +} + +void Client::Handle_OP_AugmentInfo(const EQApplicationPacket *app) +{ + // This packet is sent by the client when an Augment item information window is opened. + // Some clients this seems to nuke the charm text (ex. Adventurer's Stone) + + if (app->size != sizeof(AugmentInfo_Struct)) { + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_AugmentInfo expected %i got %i", + sizeof(AugmentInfo_Struct), app->size); + DumpPacket(app); + return; + } + + AugmentInfo_Struct* AugInfo = (AugmentInfo_Struct*)app->pBuffer; + const Item_Struct * item = database.GetItem(AugInfo->itemid); + + if (item) { + strn0cpy(AugInfo->augment_info, item->Name, 64); + AugInfo->itemid = 0; + QueuePacket(app); + } +} + +void Client::Handle_OP_AugmentItem(const EQApplicationPacket *app) +{ + if (app->size != sizeof(AugmentItem_Struct)) { + LogFile->write(EQEMuLog::Error, "Invalid size for AugmentItem_Struct: Expected: %i, Got: %i", + sizeof(AugmentItem_Struct), app->size); + return; + } + + // Delegate to tradeskill object to perform combine + AugmentItem_Struct* in_augment = (AugmentItem_Struct*)app->pBuffer; + bool deleteItems = false; + if (GetClientVersion() >= EQClientRoF) + { + ItemInst *itemOneToPush = nullptr, *itemTwoToPush = nullptr; + + //Message(15, "%i %i %i %i %i %i", in_augment->container_slot, in_augment->augment_slot, in_augment->container_index, in_augment->augment_index, in_augment->augment_action, in_augment->dest_inst_id); + + // Adding augment + if (in_augment->augment_action == 0) + { + ItemInst *tobe_auged, *auged_with = nullptr; + int8 slot = -1; + Inventory& user_inv = GetInv(); + + uint16 slot_id = in_augment->container_slot; + uint16 aug_slot_id = in_augment->augment_slot; + //Message(13, "%i AugSlot", aug_slot_id); + if (slot_id == INVALID_INDEX || aug_slot_id == INVALID_INDEX) + { + Message(13, "Error: Invalid Aug Index."); + return; + } + + tobe_auged = user_inv.GetItem(slot_id); + auged_with = user_inv.GetItem(MainCursor); + + if (tobe_auged && auged_with) + { + if (((tobe_auged->IsAugmentSlotAvailable(auged_with->GetAugmentType(), in_augment->augment_index)) != -1) && + (tobe_auged->AvailableWearSlot(auged_with->GetItem()->Slots))) + { + tobe_auged->PutAugment(in_augment->augment_index, *auged_with); + + ItemInst *aug = tobe_auged->GetAugment(in_augment->augment_index); + if (aug) { + std::vector args; + args.push_back(aug); + parse->EventItem(EVENT_AUGMENT_ITEM, this, tobe_auged, nullptr, "", in_augment->augment_index, &args); + + args.assign(1, tobe_auged); + parse->EventItem(EVENT_AUGMENT_INSERT, this, aug, nullptr, "", in_augment->augment_index, &args); + } + else + { + Message(13, "Error: Could not find augmentation at index %i. Aborting.", in_augment->augment_index); + return; + } + + itemOneToPush = tobe_auged->Clone(); + // Must push items after the items in inventory are deleted - necessary due to lore items... + if (itemOneToPush) + { + DeleteItemInInventory(slot_id, 0, true); + DeleteItemInInventory(MainCursor, 0, true); + if (PutItemInInventory(slot_id, *itemOneToPush, true)) + { + CalcBonuses(); + //Message(13, "Sucessfully added an augment to your item!"); + return; + } + else + { + Message(13, "Error: No available slot for end result. Please free up some bag space."); + } + } + else + { + Message(13, "Error in cloning item for augment. Aborted."); + } + + } + else + { + Message(13, "Error: No available slot for augment in that item."); + } + } + } + else if (in_augment->augment_action == 1) + { + ItemInst *tobe_auged, *auged_with = nullptr; + int8 slot = -1; + Inventory& user_inv = GetInv(); + + uint16 slot_id = in_augment->container_slot; + uint16 aug_slot_id = in_augment->augment_slot; //it's actually solvent slot + if (slot_id == INVALID_INDEX || aug_slot_id == INVALID_INDEX) + { + Message(13, "Error: Invalid Aug Index."); + return; + } + + tobe_auged = user_inv.GetItem(slot_id); + auged_with = user_inv.GetItem(aug_slot_id); + + ItemInst *old_aug = nullptr; + if (!auged_with) + return; + const uint32 id = auged_with->GetID(); + ItemInst *aug = tobe_auged->GetAugment(in_augment->augment_index); + if (aug) { + std::vector args; + args.push_back(aug); + parse->EventItem(EVENT_UNAUGMENT_ITEM, this, tobe_auged, nullptr, "", in_augment->augment_index, &args); + + args.assign(1, tobe_auged); + + args.push_back(false); + + parse->EventItem(EVENT_AUGMENT_REMOVE, this, aug, nullptr, "", in_augment->augment_index, &args); + } + else + { + Message(13, "Error: Could not find augmentation at index %i. Aborting."); + return; + } + old_aug = tobe_auged->RemoveAugment(in_augment->augment_index); + + itemOneToPush = tobe_auged->Clone(); + if (old_aug) + itemTwoToPush = old_aug->Clone(); + if (itemOneToPush && itemTwoToPush && auged_with) + { + DeleteItemInInventory(slot_id, 0, true); + DeleteItemInInventory(aug_slot_id, auged_with->IsStackable() ? 1 : 0, true); + if (!PutItemInInventory(slot_id, *itemOneToPush, true)) + { + Message(15, "Shouldn't happen, contact an admin!"); + } + + if (PutItemInInventory(MainCursor, *itemTwoToPush, true)) + { + CalcBonuses(); + //Message(15, "Successfully removed an augmentation!"); + } + } + } + } + else + { + Object::HandleAugmentation(this, in_augment, m_tradeskill_object); + } + return; +} + +void Client::Handle_OP_AutoAttack(const EQApplicationPacket *app) +{ + if (app->size != 4) { + LogFile->write(EQEMuLog::Error, "OP size error: OP_AutoAttack expected:4 got:%i", app->size); + return; + } + + if (app->pBuffer[0] == 0) + { + auto_attack = false; + if (IsAIControlled()) + return; + attack_timer.Disable(); + ranged_timer.Disable(); + attack_dw_timer.Disable(); + + m_AutoAttackPosition = xyz_heading::Origin(); + m_AutoAttackTargetLocation = xyz_location::Origin(); + aa_los_them_mob = nullptr; + } + else if (app->pBuffer[0] == 1) + { + auto_attack = true; + auto_fire = false; + if (IsAIControlled()) + return; + SetAttackTimer(); + + if (GetTarget()) + { + aa_los_them_mob = GetTarget(); + m_AutoAttackPosition = GetPosition(); + m_AutoAttackTargetLocation = aa_los_them_mob->GetPosition(); + los_status = CheckLosFN(aa_los_them_mob); + los_status_facing = IsFacingMob(aa_los_them_mob); + } + else + { + m_AutoAttackPosition = GetPosition(); + m_AutoAttackTargetLocation = xyz_location::Origin(); + aa_los_them_mob = nullptr; + los_status = false; + los_status_facing = false; + } + } +} + +void Client::Handle_OP_AutoAttack2(const EQApplicationPacket *app) +{ + return; +} + +void Client::Handle_OP_AutoFire(const EQApplicationPacket *app) +{ + if (app->size != sizeof(bool)) { + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_AutoFire expected %i got %i", sizeof(bool), app->size); + DumpPacket(app); + return; + } + bool *af = (bool*)app->pBuffer; + auto_fire = *af; + auto_attack = false; + SetAttackTimer(); +} + +void Client::Handle_OP_Bandolier(const EQApplicationPacket *app) +{ + + // Although there are three different structs for OP_Bandolier, they are all the same size. + // + if (app->size != sizeof(BandolierCreate_Struct)) { + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_Bandolier expected %i got %i", + sizeof(BandolierCreate_Struct), app->size); + DumpPacket(app); + return; + } + + BandolierCreate_Struct *bs = (BandolierCreate_Struct*)app->pBuffer; + + switch (bs->action) { + case BandolierCreate: + CreateBandolier(app); + break; + case BandolierRemove: + RemoveBandolier(app); + break; + case BandolierSet: + SetBandolier(app); + break; + default: + LogFile->write(EQEMuLog::Debug, "Uknown Bandolier action %i", bs->action); + + } +} + +void Client::Handle_OP_BankerChange(const EQApplicationPacket *app) +{ + if (app->size != sizeof(BankerChange_Struct) && app->size != 4) //Titanium only sends 4 Bytes for this + { + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_BankerChange expected %i got %i", sizeof(BankerChange_Struct), app->size); + DumpPacket(app); + return; + } + + uint32 distance = 0; + NPC *banker = entity_list.GetClosestBanker(this, distance); + + if (!banker || distance > USE_NPC_RANGE2) + { + char *hacked_string = nullptr; + MakeAnyLenString(&hacked_string, "Player tried to make use of a banker(money) but %s is non-existant or too far away (%u units).", + banker ? banker->GetName() : "UNKNOWN NPC", distance); + database.SetMQDetectionFlag(AccountName(), GetName(), hacked_string, zone->GetShortName()); + safe_delete_array(hacked_string); + return; + } + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_BankerChange, nullptr, sizeof(BankerChange_Struct)); + BankerChange_Struct *bc = (BankerChange_Struct *)outapp->pBuffer; + + if (m_pp.platinum < 0) + m_pp.platinum = 0; + if (m_pp.gold < 0) + m_pp.gold = 0; + if (m_pp.silver < 0) + m_pp.silver = 0; + if (m_pp.copper < 0) + m_pp.copper = 0; + + if (m_pp.platinum_bank < 0) + m_pp.platinum_bank = 0; + if (m_pp.gold_bank < 0) + m_pp.gold_bank = 0; + if (m_pp.silver_bank < 0) + m_pp.silver_bank = 0; + if (m_pp.copper_bank < 0) + m_pp.copper_bank = 0; + + uint64 cp = static_cast(m_pp.copper) + + (static_cast(m_pp.silver) * 10) + + (static_cast(m_pp.gold) * 100) + + (static_cast(m_pp.platinum) * 1000); + + m_pp.copper = cp % 10; + cp /= 10; + m_pp.silver = cp % 10; + cp /= 10; + m_pp.gold = cp % 10; + cp /= 10; + m_pp.platinum = cp; + + cp = static_cast(m_pp.copper_bank) + + (static_cast(m_pp.silver_bank) * 10) + + (static_cast(m_pp.gold_bank) * 100) + + (static_cast(m_pp.platinum_bank) * 1000); + + m_pp.copper_bank = cp % 10; + cp /= 10; + m_pp.silver_bank = cp % 10; + cp /= 10; + m_pp.gold_bank = cp % 10; + cp /= 10; + m_pp.platinum_bank = cp; + + bc->copper = m_pp.copper; + bc->silver = m_pp.silver; + bc->gold = m_pp.gold; + bc->platinum = m_pp.platinum; + + bc->copper_bank = m_pp.copper_bank; + bc->silver_bank = m_pp.silver_bank; + bc->gold_bank = m_pp.gold_bank; + bc->platinum_bank = m_pp.platinum_bank; + + FastQueuePacket(&outapp); + + return; +} + +void Client::Handle_OP_Barter(const EQApplicationPacket *app) +{ + + if (app->size < 4) + { + LogFile->write(EQEMuLog::Debug, "OP_Barter packet below minimum expected size. The packet was %i bytes.", app->size); + DumpPacket(app); + return; + } + + char* Buf = (char *)app->pBuffer; + + // The first 4 bytes of the packet determine the action. A lot of Barter packets require the + // packet the client sent, sent back to it as an acknowledgement. + // + uint32 Action = VARSTRUCT_DECODE_TYPE(uint32, Buf); + + _pkt(TRADING__BARTER, app); + + switch (Action) + { + + case Barter_BuyerSearch: + { + BuyerItemSearch(app); + break; + } + + case Barter_SellerSearch: + { + BarterSearchRequest_Struct *bsr = (BarterSearchRequest_Struct*)app->pBuffer; + SendBuyerResults(bsr->SearchString, bsr->SearchID); + break; + } + + case Barter_BuyerModeOn: + { + if (!Trader) { + ToggleBuyerMode(true); + } + else { + Buf = (char *)app->pBuffer; + VARSTRUCT_ENCODE_TYPE(uint32, Buf, Barter_BuyerModeOff); + Message(13, "You cannot be a Trader and Buyer at the same time."); + } + QueuePacket(app); + break; + } + + case Barter_BuyerModeOff: + { + QueuePacket(app); + ToggleBuyerMode(false); + break; + } + + case Barter_BuyerItemUpdate: + { + UpdateBuyLine(app); + break; + } + + case Barter_BuyerItemRemove: + { + BuyerRemoveItem_Struct* bris = (BuyerRemoveItem_Struct*)app->pBuffer; + database.RemoveBuyLine(CharacterID(), bris->BuySlot); + QueuePacket(app); + break; + } + + case Barter_SellItem: + { + SellToBuyer(app); + break; + } + + case Barter_BuyerInspectBegin: + { + ShowBuyLines(app); + break; + } + + case Barter_BuyerInspectEnd: + { + BuyerInspectRequest_Struct* bir = (BuyerInspectRequest_Struct*)app->pBuffer; + Client *Buyer = entity_list.GetClientByID(bir->BuyerID); + if (Buyer) + Buyer->WithCustomer(0); + + break; + } + + case Barter_BarterItemInspect: + { + BarterItemSearchLinkRequest_Struct* bislr = (BarterItemSearchLinkRequest_Struct*)app->pBuffer; + + const Item_Struct* item = database.GetItem(bislr->ItemID); + + if (!item) + Message(13, "Error: This item does not exist!"); + else + { + ItemInst* inst = database.CreateItem(item); + if (inst) + { + SendItemPacket(0, inst, ItemPacketViewLink); + safe_delete(inst); + } + } + break; + } + + case Barter_Welcome: + { + SendBazaarWelcome(); + break; + } + + case Barter_WelcomeMessageUpdate: + { + BuyerWelcomeMessageUpdate_Struct* bwmu = (BuyerWelcomeMessageUpdate_Struct*)app->pBuffer; + SetBuyerWelcomeMessage(bwmu->WelcomeMessage); + break; + } + + case Barter_BuyerItemInspect: + { + BuyerItemSearchLinkRequest_Struct* bislr = (BuyerItemSearchLinkRequest_Struct*)app->pBuffer; + + const Item_Struct* item = database.GetItem(bislr->ItemID); + + if (!item) + Message(13, "Error: This item does not exist!"); + else + { + ItemInst* inst = database.CreateItem(item); + if (inst) + { + SendItemPacket(0, inst, ItemPacketViewLink); + safe_delete(inst); + } + } + break; + } + + case Barter_Unknown23: + { + // Sent by SoD client for no discernible reason. + break; + } + + default: + Message(13, "Unrecognised Barter action."); + _log(TRADING__BARTER, "Unrecognised Barter Action %i", Action); + + } +} + +void Client::Handle_OP_BazaarInspect(const EQApplicationPacket *app) +{ + if (app->size != sizeof(BazaarInspect_Struct)) { + LogFile->write(EQEMuLog::Error, "Invalid size for BazaarInspect_Struct: Expected %i, Got %i", + sizeof(BazaarInspect_Struct), app->size); + return; + } + + BazaarInspect_Struct* bis = (BazaarInspect_Struct*)app->pBuffer; + + const Item_Struct* item = database.GetItem(bis->ItemID); + + if (!item) { + Message(13, "Error: This item does not exist!"); + return; + } + + ItemInst* inst = database.CreateItem(item); + + if (inst) { + SendItemPacket(0, inst, ItemPacketViewLink); + safe_delete(inst); + } + + return; +} + +void Client::Handle_OP_BazaarSearch(const EQApplicationPacket *app) +{ + _pkt(TRADING__PACKETS, app); + + if (app->size == sizeof(BazaarSearch_Struct)) { + + BazaarSearch_Struct* bss = (BazaarSearch_Struct*)app->pBuffer; + + this->SendBazaarResults(bss->TraderID, bss->Class_, bss->Race, bss->ItemStat, bss->Slot, bss->Type, + bss->Name, bss->MinPrice * 1000, bss->MaxPrice * 1000); + } + else if (app->size == sizeof(BazaarWelcome_Struct)) { + + BazaarWelcome_Struct* bws = (BazaarWelcome_Struct*)app->pBuffer; + + if (bws->Beginning.Action == BazaarWelcome) + SendBazaarWelcome(); + } + else if (app->size == sizeof(NewBazaarInspect_Struct)) { + + NewBazaarInspect_Struct *nbis = (NewBazaarInspect_Struct*)app->pBuffer; + + Client *c = entity_list.GetClientByName(nbis->Name); + if (c) { + ItemInst* inst = c->FindTraderItemBySerialNumber(nbis->SerialNumber); + if (inst) + SendItemPacket(0, inst, ItemPacketViewLink); + } + return; + } + else { + _log(TRADING__CLIENT, "Malformed BazaarSearch_Struct packe, Action %it received, ignoring..."); + LogFile->write(EQEMuLog::Error, "Malformed BazaarSearch_Struct packet received, ignoring...\n"); + } + + return; +} + +void Client::Handle_OP_Begging(const EQApplicationPacket *app) +{ + if (!p_timers.Expired(&database, pTimerBeggingPickPocket, false)) + { + Message(13, "Ability recovery time not yet met."); + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_Begging, sizeof(BeggingResponse_Struct)); + BeggingResponse_Struct *brs = (BeggingResponse_Struct*)outapp->pBuffer; + brs->Result = 0; + FastQueuePacket(&outapp); + return; + } + + if (!HasSkill(SkillBegging) || !GetTarget()) + return; + + if (GetTarget()->GetClass() == LDON_TREASURE) + return; + + p_timers.Start(pTimerBeggingPickPocket, 8); + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_Begging, sizeof(BeggingResponse_Struct)); + BeggingResponse_Struct *brs = (BeggingResponse_Struct*)outapp->pBuffer; + + brs->Result = 0; // Default, Fail. + if (GetTarget() == this) + { + FastQueuePacket(&outapp); + return; + } + + int RandomChance = zone->random.Int(0, 100); + + int ChanceToAttack = 0; + + if (GetLevel() > GetTarget()->GetLevel()) + ChanceToAttack = zone->random.Int(0, 15); + else + ChanceToAttack = zone->random.Int(((this->GetTarget()->GetLevel() - this->GetLevel()) * 10) - 5, ((this->GetTarget()->GetLevel() - this->GetLevel()) * 10)); + + if (ChanceToAttack < 0) + ChanceToAttack = -ChanceToAttack; + + if (RandomChance < ChanceToAttack) + { + GetTarget()->Attack(this); + QueuePacket(outapp); + safe_delete(outapp); + return; + } + + uint16 CurrentSkill = GetSkill(SkillBegging); + + float ChanceToBeg = ((float)(CurrentSkill / 700.0f) + 0.15f) * 100; + + if (RandomChance < ChanceToBeg) + { + brs->Amount = zone->random.Int(1, 10); + // This needs some work to determine how much money they can beg, based on skill level etc. + if (CurrentSkill < 50) + { + brs->Result = 4; // Copper + AddMoneyToPP(brs->Amount, false); + } + else + { + brs->Result = 3; // Silver + AddMoneyToPP(brs->Amount * 10, false); + } + + } + QueuePacket(outapp); + safe_delete(outapp); + CheckIncreaseSkill(SkillBegging, nullptr, -10); +} + +void Client::Handle_OP_Bind_Wound(const EQApplicationPacket *app) +{ + if (app->size != sizeof(BindWound_Struct)){ + LogFile->write(EQEMuLog::Error, "Size mismatch for Bind wound packet"); + DumpPacket(app); + } + BindWound_Struct* bind_in = (BindWound_Struct*)app->pBuffer; + Mob* bindmob = entity_list.GetMob(bind_in->to); + if (!bindmob){ + LogFile->write(EQEMuLog::Error, "Bindwound on non-exsistant mob from %s", this->GetName()); + } + else { + LogFile->write(EQEMuLog::Debug, "BindWound in: to:\'%s\' from=\'%s\'", bindmob->GetName(), GetName()); + BindWound(bindmob, true); + } + return; +} + +void Client::Handle_OP_BlockedBuffs(const EQApplicationPacket *app) +{ + if (!RuleB(Spells, EnableBlockedBuffs)) + return; + + if (app->size != sizeof(BlockedBuffs_Struct)) + { + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_BlockedBuffs expected %i got %i", + sizeof(BlockedBuffs_Struct), app->size); + + DumpPacket(app); + + return; + } + + std::set::iterator Iterator; + + BlockedBuffs_Struct *bbs = (BlockedBuffs_Struct*)app->pBuffer; + + std::set *BlockedBuffs = bbs->Pet ? &PetBlockedBuffs : &PlayerBlockedBuffs; + + if (bbs->Initialise == 1) + { + BlockedBuffs->clear(); + + for (unsigned int i = 0; i < BLOCKED_BUFF_COUNT; ++i) + { + if ((IsValidSpell(bbs->SpellID[i])) && IsBeneficialSpell(bbs->SpellID[i]) && !spells[bbs->SpellID[i]].no_block) + { + if (BlockedBuffs->find(bbs->SpellID[i]) == BlockedBuffs->end()) + BlockedBuffs->insert(bbs->SpellID[i]); + } + } + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_BlockedBuffs, sizeof(BlockedBuffs_Struct)); + + BlockedBuffs_Struct *obbs = (BlockedBuffs_Struct*)outapp->pBuffer; + + for (unsigned int i = 0; i < BLOCKED_BUFF_COUNT; ++i) + obbs->SpellID[i] = -1; + + obbs->Pet = bbs->Pet; + obbs->Initialise = 1; + obbs->Flags = 0x54; + obbs->Count = BlockedBuffs->size(); + + unsigned int Element = 0; + + Iterator = BlockedBuffs->begin(); + + while (Iterator != BlockedBuffs->end()) + { + obbs->SpellID[Element++] = (*Iterator); + ++Iterator; + } + + FastQueuePacket(&outapp); + return; + } + + if ((bbs->Initialise == 0) && (bbs->Count > 0)) + { + EQApplicationPacket *outapp = new EQApplicationPacket(OP_BlockedBuffs, sizeof(BlockedBuffs_Struct)); + + BlockedBuffs_Struct *obbs = (BlockedBuffs_Struct*)outapp->pBuffer; + + for (unsigned int i = 0; i < BLOCKED_BUFF_COUNT; ++i) + obbs->SpellID[i] = -1; + + obbs->Pet = bbs->Pet; + obbs->Initialise = 0; + obbs->Flags = 0x54; + + for (unsigned int i = 0; i < BLOCKED_BUFF_COUNT; ++i) + { + if (!IsValidSpell(bbs->SpellID[i]) || !IsBeneficialSpell(bbs->SpellID[i]) || spells[bbs->SpellID[i]].no_block) + continue; + + if ((BlockedBuffs->size() < BLOCKED_BUFF_COUNT) && (BlockedBuffs->find(bbs->SpellID[i]) == BlockedBuffs->end())) + BlockedBuffs->insert(bbs->SpellID[i]); + } + obbs->Count = BlockedBuffs->size(); + + Iterator = BlockedBuffs->begin(); + + unsigned int Element = 0; + + while (Iterator != BlockedBuffs->end()) + { + obbs->SpellID[Element++] = (*Iterator); + ++Iterator; + } + + FastQueuePacket(&outapp); + } +} + +void Client::Handle_OP_BoardBoat(const EQApplicationPacket *app) +{ + // this sends unclean mob name, so capped at 64 + // a_boat006 + if (app->size <= 5 || app->size > 64) { + LogFile->write(EQEMuLog::Error, "Size mismatch in OP_BoardBoad. Expected greater than 5 less than 64, got %i", app->size); + DumpPacket(app); + return; + } + + char boatname[64]; + memcpy(boatname, app->pBuffer, app->size); + boatname[63] = '\0'; + + 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 + + return; +} + +void Client::Handle_OP_Buff(const EQApplicationPacket *app) +{ + if (app->size != sizeof(SpellBuffFade_Struct)) + { + LogFile->write(EQEMuLog::Error, "Size mismatch in OP_Buff. expected %i got %i", sizeof(SpellBuffFade_Struct), app->size); + DumpPacket(app); + return; + } + + SpellBuffFade_Struct* sbf = (SpellBuffFade_Struct*)app->pBuffer; + uint32 spid = sbf->spellid; + mlog(SPELLS__BUFFS, "Client requested that buff with spell id %d be canceled.", spid); + + //something about IsDetrimentalSpell() crashes this portion of code.. + //tbh we shouldn't use it anyway since this is a simple red vs blue buff check and + //isdetrimentalspell() is much more complex + if (spid == 0xFFFF || (IsValidSpell(spid) && (spells[spid].goodEffect == 0))) + QueuePacket(app); + else + BuffFadeBySpellID(spid); + + return; +} + +void Client::Handle_OP_BuffRemoveRequest(const EQApplicationPacket *app) +{ + // In SoD, this is used for clicking off Pet Buffs only. In Underfoot, it is used both for Client and Pets + // The payload contains buffslot and EntityID only, so we must check if the EntityID is ours or our pets. + // + VERIFY_PACKET_LENGTH(OP_BuffRemoveRequest, app, BuffRemoveRequest_Struct); + + BuffRemoveRequest_Struct *brrs = (BuffRemoveRequest_Struct*)app->pBuffer; + + Mob *m = nullptr; + + if (brrs->EntityID == GetID()) + m = this; + else if (brrs->EntityID == GetPetID()) + m = GetPet(); + + if (!m) + return; + + if (brrs->SlotID > (uint32)m->GetMaxTotalSlots()) + return; + + uint16 SpellID = m->GetSpellIDFromSlot(brrs->SlotID); + + if (SpellID && IsBeneficialSpell(SpellID)) + m->BuffFadeBySlot(brrs->SlotID, true); +} + +void Client::Handle_OP_Bug(const EQApplicationPacket *app) +{ + if (app->size != sizeof(BugStruct)) + printf("Wrong size of BugStruct got %d expected %zu!\n", app->size, sizeof(BugStruct)); + else{ + BugStruct* bug = (BugStruct*)app->pBuffer; + database.UpdateBug(bug); + } + return; +} + +void Client::Handle_OP_Camp(const EQApplicationPacket *app) +{ +#ifdef BOTS + // This block is necessary to clean up any bot objects owned by a Client + Bot::BotHealRotationsClear(this); + Bot::BotOrderCampAll(this); +#endif + if (IsLFP()) + worldserver.StopLFP(CharacterID()); + + if (GetGM()) + { + OnDisconnect(true); + return; + } + camp_timer.Start(29000, true); + return; +} + +void Client::Handle_OP_CancelTask(const EQApplicationPacket *app) +{ + + if (app->size != sizeof(CancelTask_Struct)) { + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_CancelTask expected %i got %i", + sizeof(CancelTask_Struct), app->size); + DumpPacket(app); + return; + } + CancelTask_Struct *cts = (CancelTask_Struct*)app->pBuffer; + + if (RuleB(TaskSystem, EnableTaskSystem) && taskstate) + taskstate->CancelTask(this, cts->SequenceNumber); +} + +void Client::Handle_OP_CancelTrade(const EQApplicationPacket *app) +{ + if (app->size != sizeof(CancelTrade_Struct)) { + LogFile->write(EQEMuLog::Error, "Wrong size: OP_CancelTrade, size=%i, expected %i", app->size, sizeof(CancelTrade_Struct)); + return; + } + Mob* with = trade->With(); + if (with && with->IsClient()) { + CancelTrade_Struct* msg = (CancelTrade_Struct*)app->pBuffer; + + // Forward cancel packet to other client + msg->fromid = with->GetID(); + //msg->action = 1; + + with->CastToClient()->QueuePacket(app); + + // Put trade items/cash back into inventory + FinishTrade(this); + trade->Reset(); + } + else if (with){ + CancelTrade_Struct* msg = (CancelTrade_Struct*)app->pBuffer; + msg->fromid = with->GetID(); + QueuePacket(app); + FinishTrade(this); + trade->Reset(); + } + EQApplicationPacket end_trade1(OP_FinishWindow, 0); + QueuePacket(&end_trade1); + + EQApplicationPacket end_trade2(OP_FinishWindow2, 0); + QueuePacket(&end_trade2); + return; +} + +void Client::Handle_OP_CastSpell(const EQApplicationPacket *app) +{ + if (app->size != sizeof(CastSpell_Struct)) { + std::cout << "Wrong size: OP_CastSpell, size=" << app->size << ", expected " << sizeof(CastSpell_Struct) << std::endl; + return; + } + if (IsAIControlled()) { + this->Message_StringID(13, NOT_IN_CONTROL); + //Message(13, "You cant cast right now, you arent in control of yourself!"); + return; + } + + CastSpell_Struct* castspell = (CastSpell_Struct*)app->pBuffer; + + m_TargetRing = xyz_location(castspell->x_pos, castspell->y_pos, castspell->z_pos); + +#ifdef _EQDEBUG + LogFile->write(EQEMuLog::Debug, "cs_unknown2: %u %i", (uint8)castspell->cs_unknown[0], castspell->cs_unknown[0]); + LogFile->write(EQEMuLog::Debug, "cs_unknown2: %u %i", (uint8)castspell->cs_unknown[1], castspell->cs_unknown[1]); + LogFile->write(EQEMuLog::Debug, "cs_unknown2: %u %i", (uint8)castspell->cs_unknown[2], castspell->cs_unknown[2]); + LogFile->write(EQEMuLog::Debug, "cs_unknown2: %u %i", (uint8)castspell->cs_unknown[3], castspell->cs_unknown[3]); + LogFile->write(EQEMuLog::Debug, "cs_unknown2: 32 %p %u", &castspell->cs_unknown, *(uint32*)castspell->cs_unknown); + LogFile->write(EQEMuLog::Debug, "cs_unknown2: 32 %p %i", &castspell->cs_unknown, *(uint32*)castspell->cs_unknown); + LogFile->write(EQEMuLog::Debug, "cs_unknown2: 16 %p %u %u", &castspell->cs_unknown, *(uint16*)castspell->cs_unknown, *(uint16*)castspell->cs_unknown + sizeof(uint16)); + LogFile->write(EQEMuLog::Debug, "cs_unknown2: 16 %p %i %i", &castspell->cs_unknown, *(uint16*)castspell->cs_unknown, *(uint16*)castspell->cs_unknown + sizeof(uint16)); +#endif + LogFile->write(EQEMuLog::Debug, "OP CastSpell: slot=%d, spell=%d, target=%d, inv=%lx", castspell->slot, castspell->spell_id, castspell->target_id, (unsigned long)castspell->inventoryslot); + + std::cout << "OP_CastSpell " << castspell->slot << " spell " << castspell->spell_id << " inventory slot " << castspell->inventoryslot << "\n" << std::endl; + + /* Memorized Spell */ + if (m_pp.mem_spells[castspell->slot] && m_pp.mem_spells[castspell->slot] == castspell->spell_id){ + + uint16 spell_to_cast = 0; + if (castspell->slot < MAX_PP_MEMSPELL) { + spell_to_cast = m_pp.mem_spells[castspell->slot]; + if (spell_to_cast != castspell->spell_id) { + InterruptSpell(castspell->spell_id); //CHEATER!!! + return; + } + } + else if (castspell->slot >= MAX_PP_MEMSPELL) { + InterruptSpell(); + return; + } + + m_TargetRing = xyz_location(castspell->x_pos, castspell->y_pos, castspell->z_pos); + + CastSpell(spell_to_cast, castspell->target_id, castspell->slot); + } + /* Spell Slot or Potion Belt Slot */ + else if ((castspell->slot == USE_ITEM_SPELL_SLOT) || (castspell->slot == POTION_BELT_SPELL_SLOT)|| (castspell->slot == TARGET_RING_SPELL_SLOT)) // ITEM or POTION cast + { + //discipline, using the item spell slot + if (castspell->inventoryslot == INVALID_INDEX) { + if (!UseDiscipline(castspell->spell_id, castspell->target_id)) { + LogFile->write(EQEMuLog::Debug, "Unknown ability being used by %s, spell being cast is: %i\n", GetName(), castspell->spell_id); + InterruptSpell(castspell->spell_id); + } + return; + } + else if (m_inv.SupportsClickCasting(castspell->inventoryslot) || (castspell->slot == POTION_BELT_SPELL_SLOT) || (castspell->slot == TARGET_RING_SPELL_SLOT)) // sanity check + { + // packet field types will be reviewed as packet transistions occur -U + const ItemInst* inst = m_inv[castspell->inventoryslot]; //slot values are int16, need to check packet on this field + //bool cancast = true; + if (inst && inst->IsType(ItemClassCommon)) + { + const Item_Struct* item = inst->GetItem(); + if (item->Click.Effect != (uint32)castspell->spell_id) + { + database.SetMQDetectionFlag(account_name, name, "OP_CastSpell with item, tried to cast a different spell.", zone->GetShortName()); + InterruptSpell(castspell->spell_id); //CHEATER!! + return; + } + + if ((item->Click.Type == ET_ClickEffect) || (item->Click.Type == ET_Expendable) || (item->Click.Type == ET_EquipClick) || (item->Click.Type == ET_ClickEffect2)) + { + if (item->Click.Level2 > 0) + { + if (GetLevel() >= item->Click.Level2) + { + ItemInst* p_inst = (ItemInst*)inst; + int i = parse->EventItem(EVENT_ITEM_CLICK_CAST, this, p_inst, nullptr, "", castspell->inventoryslot); + + if (i == 0) { + CastSpell(item->Click.Effect, castspell->target_id, castspell->slot, item->CastTime, 0, 0, castspell->inventoryslot); + } + else { + InterruptSpell(castspell->spell_id); + return; + } + } + else + { + database.SetMQDetectionFlag(account_name, name, "OP_CastSpell with item, did not meet req level.", zone->GetShortName()); + Message(0, "Error: level not high enough.", castspell->inventoryslot); + InterruptSpell(castspell->spell_id); + } + } + else + { + ItemInst* p_inst = (ItemInst*)inst; + int i = parse->EventItem(EVENT_ITEM_CLICK_CAST, this, p_inst, nullptr, "", castspell->inventoryslot); + + if (i == 0) { + CastSpell(item->Click.Effect, castspell->target_id, castspell->slot, item->CastTime, 0, 0, castspell->inventoryslot); + } + else { + InterruptSpell(castspell->spell_id); + return; + } + } + } + else + { + Message(0, "Error: unknown item->Click.Type (0x%02x)", item->Click.Type); + } + } + else + { + Message(0, "Error: item not found in inventory slot #%i", castspell->inventoryslot); + InterruptSpell(castspell->spell_id); + } + } + else + { + Message(0, "Error: castspell->inventoryslot >= %i (0x%04x)", MainCursor, castspell->inventoryslot); + InterruptSpell(castspell->spell_id); + } + } + /* Discipline */ + else if (castspell->slot == DISCIPLINE_SPELL_SLOT) { + if (!UseDiscipline(castspell->spell_id, castspell->target_id)) { + printf("Unknown ability being used by %s, spell being cast is: %i\n", GetName(), castspell->spell_id); + InterruptSpell(castspell->spell_id); + return; + } + } + /* ABILITY cast (LoH and Harm Touch) */ + else if (castspell->slot == ABILITY_SPELL_SLOT) { + uint16 spell_to_cast = 0; + + if (castspell->spell_id == SPELL_LAY_ON_HANDS && GetClass() == PALADIN) { + if (!p_timers.Expired(&database, pTimerLayHands)) { + Message(13, "Ability recovery time not yet met."); + InterruptSpell(castspell->spell_id); + return; + } + spell_to_cast = SPELL_LAY_ON_HANDS; + p_timers.Start(pTimerLayHands, LayOnHandsReuseTime); + } + else if ((castspell->spell_id == SPELL_HARM_TOUCH + || castspell->spell_id == SPELL_HARM_TOUCH2) && GetClass() == SHADOWKNIGHT) { + if (!p_timers.Expired(&database, pTimerHarmTouch)) { + Message(13, "Ability recovery time not yet met."); + InterruptSpell(castspell->spell_id); + return; + } + + // determine which version of HT we are casting based on level + if (GetLevel() < 40) + spell_to_cast = SPELL_HARM_TOUCH; + else + spell_to_cast = SPELL_HARM_TOUCH2; + + p_timers.Start(pTimerHarmTouch, HarmTouchReuseTime); + } + + if (spell_to_cast > 0) // if we've matched LoH or HT, cast now + CastSpell(spell_to_cast, castspell->target_id, castspell->slot); + } + return; +} + +void Client::Handle_OP_ChannelMessage(const EQApplicationPacket *app) +{ + ChannelMessage_Struct* cm = (ChannelMessage_Struct*)app->pBuffer; + + if (app->size < sizeof(ChannelMessage_Struct)) { + std::cout << "Wrong size " << app->size << ", should be " << sizeof(ChannelMessage_Struct) << "+ on 0x" << std::hex << std::setfill('0') << std::setw(4) << app->GetOpcode() << std::dec << std::endl; + return; + } + if (IsAIControlled()) { + Message(13, "You try to speak but cant move your mouth!"); + return; + } + + ChannelMessageReceived(cm->chan_num, cm->language, cm->skill_in_language, cm->message, cm->targetname); + return; +} + +void Client::Handle_OP_ClearBlockedBuffs(const EQApplicationPacket *app) +{ + if (!RuleB(Spells, EnableBlockedBuffs)) + return; + + if (app->size != 1) + { + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_ClearBlockedBuffs expected 1 got %i", app->size); + + DumpPacket(app); + + return; + } + + bool Pet = app->pBuffer[0]; + + if (Pet) + PetBlockedBuffs.clear(); + else + PlayerBlockedBuffs.clear(); + + QueuePacket(app); +} + +void Client::Handle_OP_ClearNPCMarks(const EQApplicationPacket *app) +{ + + if (app->size != 0) + { + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_ClearNPCMarks expected 0 got %i", + app->size); + + DumpPacket(app); + + return; + } + + Group *g = GetGroup(); + + if (g) + g->ClearAllNPCMarks(); +} + +void Client::Handle_OP_ClearSurname(const EQApplicationPacket *app) +{ + ChangeLastName(""); +} + +void Client::Handle_OP_ClickDoor(const EQApplicationPacket *app) +{ + if (app->size != sizeof(ClickDoor_Struct)) { + LogFile->write(EQEMuLog::Error, "Wrong size: OP_ClickDoor, size=%i, expected %i", app->size, sizeof(ClickDoor_Struct)); + return; + } + ClickDoor_Struct* cd = (ClickDoor_Struct*)app->pBuffer; + Doors* currentdoor = entity_list.FindDoor(cd->doorid); + if (!currentdoor) + { + Message(0, "Unable to find door, please notify a GM (DoorID: %i).", cd->doorid); + return; + } + + char buf[20]; + snprintf(buf, 19, "%u", cd->doorid); + buf[19] = '\0'; + std::vector args; + args.push_back(currentdoor); + parse->EventPlayer(EVENT_CLICK_DOOR, this, buf, 0, &args); + + currentdoor->HandleClick(this, 0); + return; +} + +void Client::Handle_OP_ClickObject(const EQApplicationPacket *app) +{ + if (app->size != sizeof(ClickObject_Struct)) { + LogFile->write(EQEMuLog::Error, "Invalid size on ClickObject_Struct: Expected %i, Got %i", + sizeof(ClickObject_Struct), app->size); + return; + } + + ClickObject_Struct* click_object = (ClickObject_Struct*)app->pBuffer; + Entity* entity = entity_list.GetID(click_object->drop_id); + if (entity && entity->IsObject()) { + Object* object = entity->CastToObject(); + + object->HandleClick(this, click_object); + + std::vector args; + args.push_back(object); + + char buf[10]; + snprintf(buf, 9, "%u", click_object->drop_id); + buf[9] = '\0'; + parse->EventPlayer(EVENT_CLICK_OBJECT, this, buf, 0, &args); + } + + // Observed in RoF after OP_ClickObjectAction: + //EQApplicationPacket end_trade2(OP_FinishWindow2, 0); + //QueuePacket(&end_trade2); + return; +} + +void Client::Handle_OP_ClickObjectAction(const EQApplicationPacket *app) +{ + if (app->size == 0) { + // RoF sends this packet 0 sized when switching from auto-combine to experiment windows. + // Not completely sure if 0 sized is for this or for closing objects as commented out below + EQApplicationPacket end_trade1(OP_FinishWindow, 0); + QueuePacket(&end_trade1); + + EQApplicationPacket end_trade2(OP_FinishWindow2, 0); + QueuePacket(&end_trade2); + + return; + + // RoF sends a 0 sized packet for closing objects + /* + Object* object = GetTradeskillObject(); + if (object) { + object->CastToObject()->Close(); + } + */ + } + else + { + if (app->size != sizeof(ClickObjectAction_Struct)) { + LogFile->write(EQEMuLog::Error, "Invalid size on OP_ClickObjectAction: Expected %i, Got %i", + sizeof(ClickObjectAction_Struct), app->size); + return; + } + + ClickObjectAction_Struct* oos = (ClickObjectAction_Struct*)app->pBuffer; + Entity* entity = entity_list.GetEntityObject(oos->drop_id); + if (entity && entity->IsObject()) { + Object* object = entity->CastToObject(); + if (oos->open == 0) { + object->Close(); + } + else { + LogFile->write(EQEMuLog::Error, "Unsupported action %d in OP_ClickObjectAction", oos->open); + } + } + else { + LogFile->write(EQEMuLog::Error, "Invalid object %d in OP_ClickObjectAction", oos->drop_id); + } + } + + SetTradeskillObject(nullptr); + + EQApplicationPacket end_trade1(OP_FinishWindow, 0); + QueuePacket(&end_trade1); + + EQApplicationPacket end_trade2(OP_FinishWindow2, 0); + QueuePacket(&end_trade2); + return; +} + +void Client::Handle_OP_ClientError(const EQApplicationPacket *app) +{ + ClientError_Struct* error = (ClientError_Struct*)app->pBuffer; + LogFile->write(EQEMuLog::Error, "Client error: %s", error->character_name); + LogFile->write(EQEMuLog::Error, "Error message:%s", error->message); + return; +} + +void Client::Handle_OP_ClientTimeStamp(const EQApplicationPacket *app) +{ + return; +} + +void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) +{ + if (IsAIControlled()) + return; + + if(dead) + return; + + //currently accepting two sizes, one has an extra byte on the end + if (app->size != sizeof(PlayerPositionUpdateClient_Struct) + && app->size != (sizeof(PlayerPositionUpdateClient_Struct)+1) + ) { + LogFile->write(EQEMuLog::Error, "OP size error: OP_ClientUpdate expected:%i got:%i", sizeof(PlayerPositionUpdateClient_Struct), app->size); + return; + } + PlayerPositionUpdateClient_Struct* ppu = (PlayerPositionUpdateClient_Struct*)app->pBuffer; + + 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; + return; + } + + // set the boat's position deltas + auto boatDelta = xyz_heading(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 + EQApplicationPacket* outapp = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct)); + PlayerPositionUpdateServer_Struct* ppus = (PlayerPositionUpdateServer_Struct*)outapp->pBuffer; + boat->MakeSpawnUpdate(ppus); + entity_list.QueueCloseClients(boat,outapp,true,300,this,false); + safe_delete(outapp); + // update the boat's position on the server, without sending an update + boat->GMMove(ppu->x_pos, ppu->y_pos, ppu->z_pos, EQ19toFloat(ppu->heading), false); + return; + } + else return; // if not a boat, do nothing + } + + float dist = 0; + float tmp; + tmp = m_Position.m_X - ppu->x_pos; + dist += tmp*tmp; + tmp = m_Position.m_Y - ppu->y_pos; + 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) + { + uint32 cur_time = Timer::GetCurrentTime(); + if((cur_time - m_TimeSinceLastPositionCheck) > 0) + { + float speed = (m_DistanceSinceLastPositionCheck * 100) / (float)(cur_time - m_TimeSinceLastPositionCheck); + float runs = GetRunspeed(); + 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) + { + 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))) + { + 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 + { + CheatDetected(MQWarpLight, ppu->x_pos, ppu->y_pos, ppu->z_pos); + } + } + } + } + } + SetShadowStepExemption(false); + SetKnockBackExemption(false); + SetPortExemption(false); + m_TimeSinceLastPositionCheck = cur_time; + m_DistanceSinceLastPositionCheck = 0.0f; + m_CheatDetectMoved = false; + } + } + else + { + m_TimeSinceLastPositionCheck = Timer::GetCurrentTime(); + m_CheatDetectMoved = false; + } + } + else + { + m_DistanceSinceLastPositionCheck += dist; + m_CheatDetectMoved = true; + if(m_TimeSinceLastPositionCheck == 0) + { + m_TimeSinceLastPositionCheck = Timer::GetCurrentTime(); + } + else + { + uint32 cur_time = Timer::GetCurrentTime(); + if((cur_time - m_TimeSinceLastPositionCheck) > 2500) + { + float speed = (m_DistanceSinceLastPositionCheck * 100) / (float)(cur_time - m_TimeSinceLastPositionCheck); + float 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)) + //{ + 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) + { + 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))) + { + 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 + { + CheatDetected(MQWarpLight, ppu->x_pos, ppu->y_pos, ppu->z_pos); + } + } + } + } + } + SetShadowStepExemption(false); + SetKnockBackExemption(false); + SetPortExemption(false); + m_TimeSinceLastPositionCheck = cur_time; + m_DistanceSinceLastPositionCheck = 0.0f; + } + } + + if(IsDraggingCorpse()) + DragCorpses(); + } + + //Check to see if PPU should trigger an update to the rewind position. + float rewind_x_diff = 0; + float rewind_y_diff = 0; + + rewind_x_diff = ppu->x_pos - m_RewindLocation.m_X; + rewind_x_diff *= rewind_x_diff; + rewind_y_diff = ppu->y_pos - m_RewindLocation.m_Y; + rewind_y_diff *= rewind_y_diff; + + //We only need to store updated values if the player has moved. + //If the player has moved more than units for x or y, then we'll store + //his pre-PPU x and y for /rewind, in case he gets stuck. + if ((rewind_x_diff > 750) || (rewind_y_diff > 750)) + m_RewindLocation = 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 ((rewind_x_diff > 5000) || (rewind_y_diff > 5000)) + m_RewindLocation = xyz_location(ppu->x_pos, ppu->y_pos, ppu->z_pos); + + if(proximity_timer.Check()) { + entity_list.ProcessMove(this, xyz_location(ppu->x_pos, ppu->y_pos, ppu->z_pos)); + if(RuleB(TaskSystem, EnableTaskSystem) && RuleB(TaskSystem,EnableTaskProximity)) + ProcessTaskProximities(ppu->x_pos, ppu->y_pos, ppu->z_pos); + + m_Proximity = xyz_location(ppu->x_pos, ppu->y_pos, ppu->z_pos); + } + + // Update internal state + m_Delta = xyz_heading(ppu->delta_x, ppu->delta_y, ppu->delta_z, ppu->delta_heading); + +<<<<<<< HEAD + if(IsTracking() && ((m_Position.m_X!=ppu->x_pos) || (m_Position.m_Y!=ppu->y_pos))){ + if(MakeRandomFloat(0, 100) < 70)//should be good +======= + if(IsTracking() && ((x_pos!=ppu->x_pos) || (y_pos!=ppu->y_pos))){ + if(zone->random.Real(0, 100) < 70)//should be good +>>>>>>> master + CheckIncreaseSkill(SkillTracking, nullptr, -20); + } + + // Break Hide if moving without sneaking and set rewind timer if moved + if(ppu->y_pos != m_Position.m_Y || ppu->x_pos != m_Position.m_X){ + if((hidden || improved_hidden) && !sneaking){ + hidden = false; + improved_hidden = false; + if(!invisible) { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct)); + SpawnAppearance_Struct* sa_out = (SpawnAppearance_Struct*)outapp->pBuffer; + sa_out->spawn_id = GetID(); + sa_out->type = 0x03; + sa_out->parameter = 0; + entity_list.QueueClients(this, outapp, true); + safe_delete(outapp); + } + } + rewind_timer.Start(30000, true); + } + + // Outgoing client packet + float tmpheading = EQ19toFloat(ppu->heading); + + if (!FCMP(ppu->y_pos, m_Position.m_Y) || !FCMP(ppu->x_pos, m_Position.m_X) || !FCMP(tmpheading, m_Position.m_Heading) || ppu->animation != animation) + { + m_Position.m_X = ppu->x_pos; + m_Position.m_Y = ppu->y_pos; + m_Position.m_Z = ppu->z_pos; + m_Position.m_Heading = tmpheading; + animation = ppu->animation; + + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct)); + PlayerPositionUpdateServer_Struct* ppu = (PlayerPositionUpdateServer_Struct*)outapp->pBuffer; + MakeSpawnUpdate(ppu); + if (gmhideme) + entity_list.QueueClientsStatus(this,outapp,true,Admin(),250); + else + entity_list.QueueCloseClients(this,outapp,true,300,nullptr,false); + safe_delete(outapp); + } + + if(zone->watermap && zone->watermap->InLiquid(m_Position)) + CheckIncreaseSkill(SkillSwimming, nullptr, -17); + + 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)) { + std::cout << "Wrong size on OP_CombatAbility. Got: " << app->size << ", Expected: " << sizeof(CombatAbility_Struct) << std::endl; + return; + } + OPCombatAbility(app); + return; +} + +void Client::Handle_OP_ConfirmDelete(const EQApplicationPacket* app) +{ + return; +} + +void Client::Handle_OP_Consent(const EQApplicationPacket *app) +{ + if(app->size<64){ + Consent_Struct* c = (Consent_Struct*)app->pBuffer; + if(strcmp(c->name, GetName()) != 0) { + ServerPacket* pack = new ServerPacket(ServerOP_Consent, sizeof(ServerOP_Consent_Struct)); + ServerOP_Consent_Struct* scs = (ServerOP_Consent_Struct*)pack->pBuffer; + strcpy(scs->grantname, c->name); + strcpy(scs->ownername, GetName()); + scs->message_string_id = 0; + scs->permission = 1; + scs->zone_id = zone->GetZoneID(); + scs->instance_id = zone->GetInstanceID(); + //consent_list.push_back(scs->grantname); + worldserver.SendPacket(pack); + safe_delete(pack); + } + else { + Message_StringID(0, CONSENT_YOURSELF); + } + } + return; +} + +void Client::Handle_OP_ConsentDeny(const EQApplicationPacket *app) +{ + if(app->size<64){ + Consent_Struct* c = (Consent_Struct*)app->pBuffer; + ServerPacket* pack = new ServerPacket(ServerOP_Consent, sizeof(ServerOP_Consent_Struct)); + ServerOP_Consent_Struct* scs = (ServerOP_Consent_Struct*)pack->pBuffer; + strcpy(scs->grantname, c->name); + strcpy(scs->ownername, GetName()); + scs->message_string_id = 0; + scs->permission = 0; + scs->zone_id = zone->GetZoneID(); + scs->instance_id = zone->GetInstanceID(); + //consent_list.remove(scs->grantname); + worldserver.SendPacket(pack); + safe_delete(pack); + } + return; +} + +void Client::Handle_OP_Consider(const EQApplicationPacket *app) +{ + if (app->size != sizeof(Consider_Struct)) + { + LogFile->write(EQEMuLog::Debug, "Size mismatch in Consider expected %i got %i", sizeof(Consider_Struct), app->size); + return; + } + Consider_Struct* conin = (Consider_Struct*)app->pBuffer; + Mob* tmob = entity_list.GetMob(conin->targetid); + if (tmob == 0) + return; + + if (tmob->GetClass() == LDON_TREASURE) + { + Message(15, "%s", tmob->GetCleanName()); + return; + } + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_Consider, sizeof(Consider_Struct)); + Consider_Struct* con = (Consider_Struct*)outapp->pBuffer; + con->playerid = GetID(); + con->targetid = conin->targetid; + if (tmob->IsNPC()) + con->faction = GetFactionLevel(character_id, tmob->GetNPCTypeID(), race, class_, deity, (tmob->IsNPC()) ? tmob->CastToNPC()->GetPrimaryFaction() : 0, tmob); // Dec. 20, 2001; TODO: Send the players proper deity + else + con->faction = 1; + con->level = GetLevelCon(tmob->GetLevel()); + if (zone->IsPVPZone()) { + if (!tmob->IsNPC()) + con->pvpcon = tmob->CastToClient()->GetPVP(); + } + + // Mongrel: If we're feigned show NPC as indifferent + if (tmob->IsNPC()) + { + if (GetFeigned()) + con->faction = FACTION_INDIFFERENT; + } + + if (!(con->faction == FACTION_SCOWLS)) + { + if (tmob->IsNPC()) + { + if (tmob->CastToNPC()->IsOnHatelist(this)) + con->faction = FACTION_THREATENLY; + } + } + + if (con->faction == FACTION_APPREHENSIVE) { + con->faction = FACTION_SCOWLS; + } + else if (con->faction == FACTION_DUBIOUS) { + con->faction = FACTION_THREATENLY; + } + else if (con->faction == FACTION_SCOWLS) { + con->faction = FACTION_APPREHENSIVE; + } + else if (con->faction == FACTION_THREATENLY) { + con->faction = FACTION_DUBIOUS; + } + + mod_consider(tmob, con); + + QueuePacket(outapp); + // only wanted to check raid target once + // and need con to still be around so, do it here! + if (tmob->IsRaidTarget()) { + uint32 color = 0; + switch (con->level) { + case CON_GREEN: + color = 2; + break; + case CON_LIGHTBLUE: + color = 10; + break; + case CON_BLUE: + color = 4; + break; + case CON_WHITE: + color = 10; + break; + case CON_YELLOW: + color = 15; + break; + case CON_RED: + color = 13; + break; + } + SendColoredText(color, std::string("This creature would take an army to defeat!")); + } + safe_delete(outapp); + return; +} + +void Client::Handle_OP_ConsiderCorpse(const EQApplicationPacket *app) +{ + if (app->size != sizeof(Consider_Struct)) + { + LogFile->write(EQEMuLog::Debug, "Size mismatch in Consider corpse expected %i got %i", sizeof(Consider_Struct), app->size); + return; + } + Consider_Struct* conin = (Consider_Struct*)app->pBuffer; + Corpse* tcorpse = entity_list.GetCorpseByID(conin->targetid); + if (tcorpse && tcorpse->IsNPCCorpse()) { + uint32 min; uint32 sec; uint32 ttime; + if ((ttime = tcorpse->GetDecayTime()) != 0) { + sec = (ttime / 1000) % 60; // Total seconds + min = (ttime / 60000) % 60; // Total seconds / 60 drop .00 + char val1[20] = { 0 }; + char val2[20] = { 0 }; + Message_StringID(10, CORPSE_DECAY1, ConvertArray(min, val1), ConvertArray(sec, val2)); + } + else { + Message_StringID(10, CORPSE_DECAY_NOW); + } + } + else if (tcorpse && tcorpse->IsPlayerCorpse()) { + uint32 day, hour, min, sec, ttime; + if ((ttime = tcorpse->GetDecayTime()) != 0) { + sec = (ttime / 1000) % 60; // Total seconds + min = (ttime / 60000) % 60; // Total seconds + hour = (ttime / 3600000) % 24; // Total hours + day = ttime / 86400000; // Total Days + if (day) + Message(0, "This corpse will decay in %i days, %i hours, %i minutes and %i seconds.", day, hour, min, sec); + else if (hour) + Message(0, "This corpse will decay in %i hours, %i minutes and %i seconds.", hour, min, sec); + else + Message(0, "This corpse will decay in %i minutes and %i seconds.", min, sec); + + Message(0, "This corpse %s be resurrected.", tcorpse->IsRezzed() ? "cannot" : "can"); + /* + hour = 0; + + if((ttime = tcorpse->GetResTime()) != 0) { + sec = (ttime/1000)%60; // Total seconds + min = (ttime/60000)%60; // Total seconds + hour = (ttime/3600000)%24; // Total hours + if(hour) + Message(0, "This corpse can be resurrected for %i hours, %i minutes and %i seconds.", hour, min, sec); + else + Message(0, "This corpse can be resurrected for %i minutes and %i seconds.", min, sec); + } + else { + Message_StringID(0, CORPSE_TOO_OLD); + } + */ + } + else { + Message_StringID(10, CORPSE_DECAY_NOW); + } + } +} + +void Client::Handle_OP_Consume(const EQApplicationPacket *app) +{ + if (app->size != sizeof(Consume_Struct)) + { + LogFile->write(EQEMuLog::Error, "OP size error: OP_Consume expected:%i got:%i", sizeof(Consume_Struct), app->size); + return; + } + Consume_Struct* pcs = (Consume_Struct*)app->pBuffer; + if (pcs->type == 0x01) + { + if (m_pp.hunger_level > 6000) + { + EQApplicationPacket *outapp; + outapp = new EQApplicationPacket(OP_Stamina, sizeof(Stamina_Struct)); + Stamina_Struct* sta = (Stamina_Struct*)outapp->pBuffer; + sta->food = m_pp.hunger_level > 6000 ? 6000 : m_pp.hunger_level; + sta->water = m_pp.thirst_level > 6000 ? 6000 : m_pp.thirst_level; + + QueuePacket(outapp); + safe_delete(outapp); + return; + } + } + else if (pcs->type == 0x02) + { + if (m_pp.thirst_level > 6000) + { + EQApplicationPacket *outapp; + outapp = new EQApplicationPacket(OP_Stamina, sizeof(Stamina_Struct)); + Stamina_Struct* sta = (Stamina_Struct*)outapp->pBuffer; + sta->food = m_pp.hunger_level > 6000 ? 6000 : m_pp.hunger_level; + sta->water = m_pp.thirst_level > 6000 ? 6000 : m_pp.thirst_level; + + QueuePacket(outapp); + safe_delete(outapp); + return; + } + } + + ItemInst *myitem = GetInv().GetItem(pcs->slot); + if (myitem == nullptr) { + LogFile->write(EQEMuLog::Error, "Consuming from empty slot %d", pcs->slot); + return; + } + + const Item_Struct* eat_item = myitem->GetItem(); + if (pcs->type == 0x01) { + Consume(eat_item, ItemTypeFood, pcs->slot, (pcs->auto_consumed == 0xffffffff)); + } + else if (pcs->type == 0x02) { + Consume(eat_item, ItemTypeDrink, pcs->slot, (pcs->auto_consumed == 0xffffffff)); + } + else { + LogFile->write(EQEMuLog::Error, "OP_Consume: unknown type, type:%i", (int)pcs->type); + return; + } + if (m_pp.hunger_level > 50000) + m_pp.hunger_level = 50000; + if (m_pp.thirst_level > 50000) + m_pp.thirst_level = 50000; + EQApplicationPacket *outapp; + outapp = new EQApplicationPacket(OP_Stamina, sizeof(Stamina_Struct)); + Stamina_Struct* sta = (Stamina_Struct*)outapp->pBuffer; + sta->food = m_pp.hunger_level > 6000 ? 6000 : m_pp.hunger_level; + sta->water = m_pp.thirst_level > 6000 ? 6000 : m_pp.thirst_level; + + QueuePacket(outapp); + safe_delete(outapp); + return; +} + +void Client::Handle_OP_ControlBoat(const EQApplicationPacket *app) +{ + if (app->size != sizeof(ControlBoat_Struct)) { + LogFile->write(EQEMuLog::Error, "Wrong size: OP_ControlBoat, size=%i, expected %i", app->size, sizeof(ControlBoat_Struct)); + return; + } + ControlBoat_Struct* cbs = (ControlBoat_Struct*)app->pBuffer; + Mob* boat = entity_list.GetMob(cbs->boatId); + if (boat == 0) + return; // do nothing if the boat isn't valid + + if (!boat->IsNPC() || (boat->GetRace() != CONTROLLED_BOAT && boat->GetRace() != 502)) + { + char *hacked_string = nullptr; + MakeAnyLenString(&hacked_string, "OP_Control Boat was sent against %s which is of race %u", boat->GetName(), boat->GetRace()); + database.SetMQDetectionFlag(this->AccountName(), this->GetName(), hacked_string, zone->GetShortName()); + safe_delete_array(hacked_string); + return; + } + + if (cbs->TakeControl) { + // this uses the boat's target to indicate who has control of it. It has to check hate to make sure the boat isn't actually attacking anyone. + if ((boat->GetTarget() == 0) || (boat->GetTarget() == this && boat->GetHateAmount(this) == 0)) { + boat->SetTarget(this); + } + else { + this->Message_StringID(13, IN_USE); + return; + } + } + else + boat->SetTarget(0); + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_ControlBoat, 0); + FastQueuePacket(&outapp); + safe_delete(outapp); + // have the boat signal itself, so quests can be triggered by boat use + boat->CastToNPC()->SignalNPC(0); +} + +void Client::Handle_OP_CorpseDrag(const EQApplicationPacket *app) +{ + if (DraggedCorpses.size() >= (unsigned int)RuleI(Character, MaxDraggedCorpses)) + { + Message_StringID(13, CORPSEDRAG_LIMIT); + return; + } + + VERIFY_PACKET_LENGTH(OP_CorpseDrag, app, CorpseDrag_Struct); + + CorpseDrag_Struct *cds = (CorpseDrag_Struct*)app->pBuffer; + + Mob* corpse = entity_list.GetMob(cds->CorpseName); + + if (!corpse || !corpse->IsPlayerCorpse() || corpse->CastToCorpse()->IsBeingLooted()) + return; + + Client *c = entity_list.FindCorpseDragger(corpse->GetID()); + + if (c) + { + if (c == this) + Message_StringID(MT_DefaultText, CORPSEDRAG_ALREADY, corpse->GetCleanName()); + else + Message_StringID(MT_DefaultText, CORPSEDRAG_SOMEONE_ELSE, corpse->GetCleanName()); + + return; + } + + if (!corpse->CastToCorpse()->Summon(this, false, true)) + return; + + DraggedCorpses.push_back(std::pair(cds->CorpseName, corpse->GetID())); + + Message_StringID(MT_DefaultText, CORPSEDRAG_BEGIN, cds->CorpseName); +} + +void Client::Handle_OP_CorpseDrop(const EQApplicationPacket *app) +{ + if (app->size == 1) + { + Message_StringID(MT_DefaultText, CORPSEDRAG_STOPALL); + ClearDraggedCorpses(); + return; + } + + for (auto Iterator = DraggedCorpses.begin(); Iterator != DraggedCorpses.end(); ++Iterator) + { + if (!strcasecmp(Iterator->first.c_str(), (const char *)app->pBuffer)) + { + Message_StringID(MT_DefaultText, CORPSEDRAG_STOP); + Iterator = DraggedCorpses.erase(Iterator); + return; + } + } +} + +void Client::Handle_OP_CrashDump(const EQApplicationPacket *app) +{ + return; +} + +void Client::Handle_OP_CreateObject(const EQApplicationPacket *app) +{ + DropItem(MainCursor); + return; +} + +void Client::Handle_OP_CrystalCreate(const EQApplicationPacket *app) +{ + VERIFY_PACKET_LENGTH(OP_CrystalCreate, app, CrystalReclaim_Struct); + CrystalReclaim_Struct *cr = (CrystalReclaim_Struct*)app->pBuffer; + + if (cr->type == 5) { + if (cr->amount > GetEbonCrystals()) { + SummonItem(RuleI(Zone, EbonCrystalItemID), GetEbonCrystals()); + m_pp.currentEbonCrystals = 0; + m_pp.careerEbonCrystals = 0; + SaveCurrency(); + SendCrystalCounts(); + } + else { + SummonItem(RuleI(Zone, EbonCrystalItemID), cr->amount); + m_pp.currentEbonCrystals -= cr->amount; + m_pp.careerEbonCrystals -= cr->amount; + SaveCurrency(); + SendCrystalCounts(); + } + } + else if (cr->type == 4) { + if (cr->amount > GetRadiantCrystals()) { + SummonItem(RuleI(Zone, RadiantCrystalItemID), GetRadiantCrystals()); + m_pp.currentRadCrystals = 0; + m_pp.careerRadCrystals = 0; + SaveCurrency(); + SendCrystalCounts(); + } + else { + SummonItem(RuleI(Zone, RadiantCrystalItemID), cr->amount); + m_pp.currentRadCrystals -= cr->amount; + m_pp.careerRadCrystals -= cr->amount; + SaveCurrency(); + SendCrystalCounts(); + } + } +} + +void Client::Handle_OP_CrystalReclaim(const EQApplicationPacket *app) +{ + uint32 ebon = NukeItem(RuleI(Zone, EbonCrystalItemID), invWhereWorn | invWherePersonal | invWhereCursor); + uint32 radiant = NukeItem(RuleI(Zone, RadiantCrystalItemID), invWhereWorn | invWherePersonal | invWhereCursor); + if ((ebon + radiant) > 0) { + AddCrystals(radiant, ebon); + } +} + +void Client::Handle_OP_Damage(const EQApplicationPacket *app) +{ + if (app->size != sizeof(CombatDamage_Struct)) { + LogFile->write(EQEMuLog::Error, "Received invalid sized OP_Damage: got %d, expected %d", app->size, + sizeof(CombatDamage_Struct)); + DumpPacket(app); + return; + } + + // Broadcast to other clients + CombatDamage_Struct* damage = (CombatDamage_Struct*)app->pBuffer; + //dont send to originator of falling damage packets + entity_list.QueueClients(this, app, (damage->type == DamageTypeFalling)); + return; +} + +void Client::Handle_OP_Death(const EQApplicationPacket *app) +{ + if (app->size != sizeof(Death_Struct)) + return; + + Death_Struct* ds = (Death_Struct*)app->pBuffer; + + //I think this attack_skill value is really a value from SkillDamageTypes... + if (ds->attack_skill > HIGHEST_SKILL) { + mlog(CLIENT__ERROR, "Invalid skill in OP_Death: %d"); + return; + } + + if (GetHP() > 0) + return; + + Mob* killer = entity_list.GetMob(ds->killer_id); + Death(killer, ds->damage, ds->spell_id, (SkillUseTypes)ds->attack_skill); + return; +} + +void Client::Handle_OP_DelegateAbility(const EQApplicationPacket *app) +{ + + if (app->size != sizeof(DelegateAbility_Struct)) + { + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_DelegateAbility expected %i got %i", + sizeof(DelegateAbility_Struct), app->size); + + DumpPacket(app); + + return; + } + + DelegateAbility_Struct* das = (DelegateAbility_Struct*)app->pBuffer; + + Group *g = GetGroup(); + + if (!g) return; + + switch (das->DelegateAbility) + { + case 0: + { + g->DelegateMainAssist(das->Name); + break; + } + case 1: + { + g->DelegateMarkNPC(das->Name); + break; + } + case 2: + { + g->DelegateMainTank(das->Name); + break; + } + case 3: + { + g->DelegatePuller(das->Name); + break; + } + default: + break; + } +} + +void Client::Handle_OP_DeleteItem(const EQApplicationPacket *app) +{ + if (app->size != sizeof(DeleteItem_Struct)) { + std::cout << "Wrong size on OP_DeleteItem. Got: " << app->size << ", Expected: " << sizeof(DeleteItem_Struct) << std::endl; + return; + } + + DeleteItem_Struct* alc = (DeleteItem_Struct*)app->pBuffer; + const ItemInst *inst = GetInv().GetItem(alc->from_slot); + if (inst && inst->GetItem()->ItemType == ItemTypeAlcohol) { + entity_list.MessageClose_StringID(this, true, 50, 0, DRINKING_MESSAGE, GetName(), inst->GetItem()->Name); + CheckIncreaseSkill(SkillAlcoholTolerance, nullptr, 25); + + int16 AlcoholTolerance = GetSkill(SkillAlcoholTolerance); + int16 IntoxicationIncrease; + + if (GetClientVersion() < EQClientSoD) + IntoxicationIncrease = (200 - AlcoholTolerance) * 30 / 200 + 10; + else + IntoxicationIncrease = (270 - AlcoholTolerance) * 0.111111108 + 10; + + if (IntoxicationIncrease < 0) + IntoxicationIncrease = 1; + + m_pp.intoxication += IntoxicationIncrease; + + if (m_pp.intoxication > 200) + m_pp.intoxication = 200; + } + DeleteItemInInventory(alc->from_slot, 1); + + return; +} + +void Client::Handle_OP_DeleteSpawn(const EQApplicationPacket *app) +{ + // The client will send this with his id when he zones, maybe when he disconnects too? + //eqs->RemoveData(); // Flushing the queue of packet data to allow for proper zoning + + //just make sure this gets out + EQApplicationPacket *outapp = new EQApplicationPacket(OP_LogoutReply); + FastQueuePacket(&outapp); + + outapp = new EQApplicationPacket(OP_DeleteSpawn, sizeof(EntityId_Struct)); + EntityId_Struct* eid = (EntityId_Struct*)outapp->pBuffer; + eid->entity_id = GetID(); + + entity_list.QueueClients(this, outapp, false); + safe_delete(outapp); + + hate_list.RemoveEnt(this->CastToMob()); + + Disconnect(); + return; +} + +void Client::Handle_OP_DeleteSpell(const EQApplicationPacket *app) +{ + if (app->size != sizeof(DeleteSpell_Struct)) + return; + + EQApplicationPacket* outapp = app->Copy(); + DeleteSpell_Struct* dss = (DeleteSpell_Struct*)outapp->pBuffer; + + if (dss->spell_slot < 0 || dss->spell_slot > int(MAX_PP_SPELLBOOK)) + return; + + if (m_pp.spell_book[dss->spell_slot] != SPELLBOOK_UNKNOWN) { + m_pp.spell_book[dss->spell_slot] = SPELLBOOK_UNKNOWN; + dss->success = 1; + } + else + dss->success = 0; + + FastQueuePacket(&outapp); + return; +} + +void Client::Handle_OP_DisarmTraps(const EQApplicationPacket *app) +{ + if (!HasSkill(SkillDisarmTraps)) + return; + + if (!p_timers.Expired(&database, pTimerDisarmTraps, false)) { + Message(13, "Ability recovery time not yet met."); + return; + } + int reuse = DisarmTrapsReuseTime; + switch (GetAA(aaAdvTrapNegotiation)) { + case 1: + reuse -= 1; + break; + case 2: + reuse -= 3; + break; + case 3: + reuse -= 5; + break; + } + p_timers.Start(pTimerDisarmTraps, reuse - 1); + + Trap* trap = entity_list.FindNearbyTrap(this, 60); + if (trap && trap->detected) + { + int uskill = GetSkill(SkillDisarmTraps); + if ((zone->random.Int(0, 49) + uskill) >= (zone->random.Int(0, 49) + trap->skill)) + { + Message(MT_Skills, "You disarm a trap."); + trap->disarmed = true; + trap->chkarea_timer.Disable(); + trap->respawn_timer.Start((trap->respawn_time + zone->random.Int(0, trap->respawn_var)) * 1000); + } + else + { + if (zone->random.Int(0, 99) < 25){ + Message(MT_Skills, "You set off the trap while trying to disarm it!"); + trap->Trigger(this); + } + else{ + Message(MT_Skills, "You failed to disarm a trap."); + } + } + CheckIncreaseSkill(SkillDisarmTraps, nullptr); + return; + } + Message(MT_Skills, "You did not find any traps close enough to disarm."); + return; +} + +void Client::Handle_OP_DoGroupLeadershipAbility(const EQApplicationPacket *app) +{ + + if (app->size != sizeof(DoGroupLeadershipAbility_Struct)) { + + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_DoGroupLeadershipAbility expected %i got %i", + sizeof(DoGroupLeadershipAbility_Struct), app->size); + + DumpPacket(app); + + return; + } + + DoGroupLeadershipAbility_Struct* dglas = (DoGroupLeadershipAbility_Struct*)app->pBuffer; + + switch (dglas->Ability) + { + case GroupLeadershipAbility_MarkNPC: + { + if (GetTarget()) + { + Group* g = GetGroup(); + if (g) + g->MarkNPC(GetTarget(), dglas->Parameter); + } + break; + } + + case groupAAInspectBuffs: + { + Mob *Target = GetTarget(); + + if (!Target || !Target->IsClient()) + return; + + if (IsRaidGrouped()) { + Raid *raid = GetRaid(); + if (!raid) + return; + uint32 group_id = raid->GetGroup(this); + if (group_id > 11 || raid->GroupCount(group_id) < 3) + return; + Target->CastToClient()->InspectBuffs(this, raid->GetLeadershipAA(groupAAInspectBuffs, group_id)); + return; + } + + Group *g = GetGroup(); + + if (!g || (g->GroupCount() < 3)) + return; + + Target->CastToClient()->InspectBuffs(this, g->GetLeadershipAA(groupAAInspectBuffs)); + + break; + } + + default: + LogFile->write(EQEMuLog::Debug, "Got unhandled OP_DoGroupLeadershipAbility Ability: %d Parameter: %d", + dglas->Ability, dglas->Parameter); + break; + } +} + +void Client::Handle_OP_DuelResponse(const EQApplicationPacket *app) +{ + if (app->size != sizeof(DuelResponse_Struct)) + return; + DuelResponse_Struct* ds = (DuelResponse_Struct*)app->pBuffer; + Entity* entity = entity_list.GetID(ds->target_id); + Entity* initiator = entity_list.GetID(ds->entity_id); + if (!entity->IsClient() || !initiator->IsClient()) + return; + + entity->CastToClient()->SetDuelTarget(0); + entity->CastToClient()->SetDueling(false); + initiator->CastToClient()->SetDuelTarget(0); + initiator->CastToClient()->SetDueling(false); + if (GetID() == initiator->GetID()) + entity->CastToClient()->Message_StringID(10, DUEL_DECLINE, initiator->GetName()); + else + initiator->CastToClient()->Message_StringID(10, DUEL_DECLINE, entity->GetName()); + return; +} + +void Client::Handle_OP_DuelResponse2(const EQApplicationPacket *app) +{ + if (app->size != sizeof(Duel_Struct)) + return; + + Duel_Struct* ds = (Duel_Struct*)app->pBuffer; + Entity* entity = entity_list.GetID(ds->duel_target); + Entity* initiator = entity_list.GetID(ds->duel_initiator); + + if (entity && initiator && entity == this && initiator->IsClient()) { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_RequestDuel, sizeof(Duel_Struct)); + Duel_Struct* ds2 = (Duel_Struct*)outapp->pBuffer; + + ds2->duel_initiator = entity->GetID(); + ds2->duel_target = entity->GetID(); + initiator->CastToClient()->QueuePacket(outapp); + + outapp->SetOpcode(OP_DuelResponse2); + ds2->duel_initiator = initiator->GetID(); + + initiator->CastToClient()->QueuePacket(outapp); + + QueuePacket(outapp); + SetDueling(true); + initiator->CastToClient()->SetDueling(true); + SetDuelTarget(ds->duel_initiator); + safe_delete(outapp); + + if (IsCasting()) + InterruptSpell(); + if (initiator->CastToClient()->IsCasting()) + initiator->CastToClient()->InterruptSpell(); + } + return; +} + +void Client::Handle_OP_DumpName(const EQApplicationPacket *app) +{ + return; +} + +void Client::Handle_OP_Dye(const EQApplicationPacket *app) +{ + if (app->size != sizeof(DyeStruct)) + printf("Wrong size of DyeStruct, Got: %i, Expected: %zu\n", app->size, sizeof(DyeStruct)); + else{ + DyeStruct* dye = (DyeStruct*)app->pBuffer; + DyeArmor(dye); + } + return; +} + +void Client::Handle_OP_Emote(const EQApplicationPacket *app) +{ + if (app->size != sizeof(Emote_Struct)) { + LogFile->write(EQEMuLog::Error, "Received invalid sized " + "OP_Emote: got %d, expected %d", app->size, + sizeof(Emote_Struct)); + DumpPacket(app); + return; + } + + // Calculate new packet dimensions + Emote_Struct* in = (Emote_Struct*)app->pBuffer; + in->message[1023] = '\0'; + + const char* name = GetName(); + uint32 len_name = strlen(name); + uint32 len_msg = strlen(in->message); + // crash protection -- cheater + if (len_msg > 512) { + in->message[512] = '\0'; + len_msg = 512; + } + uint32 len_packet = sizeof(in->unknown01) + len_name + + len_msg + 1; + + // Construct outgoing packet + EQApplicationPacket* outapp = new EQApplicationPacket(OP_Emote, len_packet); + Emote_Struct* out = (Emote_Struct*)outapp->pBuffer; + out->unknown01 = in->unknown01; + memcpy(out->message, name, len_name); + memcpy(&out->message[len_name], in->message, len_msg); + + /* + if (target && target->IsClient()) { + entity_list.QueueCloseClients(this, outapp, false, 100, target); + + cptr = outapp->pBuffer + 2; + + // not sure if live does this or not. thought it was a nice feature, but would take a lot to + // clean up grammatical and other errors. Maybe with a regex parser... + replacestr((char *)cptr, target->GetName(), "you"); + replacestr((char *)cptr, " he", " you"); + replacestr((char *)cptr, " she", " you"); + replacestr((char *)cptr, " him", " you"); + replacestr((char *)cptr, " her", " you"); + target->CastToClient()->QueuePacket(outapp); + + } + else + */ + entity_list.QueueCloseClients(this, outapp, true, 100, 0, true, FilterSocials); + + safe_delete(outapp); + return; +} + +void Client::Handle_OP_EndLootRequest(const EQApplicationPacket *app) +{ + if (app->size != sizeof(uint32)) { + std::cout << "Wrong size: OP_EndLootRequest, size=" << app->size << ", expected " << sizeof(uint32) << std::endl; + return; + } + + SetLooting(0); + + Entity* entity = entity_list.GetID(*((uint16*)app->pBuffer)); + if (entity == 0) { + Message(13, "Error: OP_EndLootRequest: Corpse not found (ent = 0)"); + if (GetClientVersion() >= EQClientSoD) + Corpse::SendEndLootErrorPacket(this); + else + Corpse::SendLootReqErrorPacket(this); + return; + } + else if (!entity->IsCorpse()) { + Message(13, "Error: OP_EndLootRequest: Corpse not found (!entity->IsCorpse())"); + Corpse::SendLootReqErrorPacket(this); + return; + } + else { + entity->CastToCorpse()->EndLoot(this, app); + } + return; +} + +void Client::Handle_OP_EnvDamage(const EQApplicationPacket *app) +{ + if (!ClientFinishedLoading()) + { + SetHP(GetHP() - 1); + return; + } + + if (app->size != sizeof(EnvDamage2_Struct)) { + LogFile->write(EQEMuLog::Error, "Received invalid sized OP_EnvDamage: got %d, expected %d", app->size, + sizeof(EnvDamage2_Struct)); + DumpPacket(app); + return; + } + EnvDamage2_Struct* ed = (EnvDamage2_Struct*)app->pBuffer; + if (admin >= minStatusToAvoidFalling && GetGM()){ + Message(13, "Your GM status protects you from %i points of type %i environmental damage.", ed->damage, ed->dmgtype); + SetHP(GetHP() - 1);//needed or else the client wont acknowledge + return; + } + else if (GetInvul()) { + Message(13, "Your invuln status protects you from %i points of type %i environmental damage.", ed->damage, ed->dmgtype); + SetHP(GetHP() - 1);//needed or else the client wont acknowledge + return; + } + + int damage = ed->damage; + + if (ed->dmgtype == 252) { + + switch (GetAA(aaAcrobatics)) { //Don't know what acrobatics effect is yet but it should be done client side via aa effect.. till then + case 1: + damage = damage * 95 / 100; + break; + case 2: + damage = damage * 90 / 100; + break; + case 3: + damage = damage * 80 / 100; + break; + } + } + + if (damage < 0) + damage = 31337; + + else if (zone->GetZoneID() == 183 || zone->GetZoneID() == 184) + return; + else + SetHP(GetHP() - damage); + + if (GetHP() <= 0) + { + mod_client_death_env(); + + Death(0, 32000, SPELL_UNKNOWN, SkillHandtoHand); + } + SendHPUpdate(); + return; +} + +void Client::Handle_OP_FaceChange(const EQApplicationPacket *app) +{ + if (app->size != sizeof(FaceChange_Struct)) { + LogFile->write(EQEMuLog::Error, "Invalid size for OP_FaceChange: Expected: %i, Got: %i", + sizeof(FaceChange_Struct), app->size); + return; + } + + // Notify other clients in zone + entity_list.QueueClients(this, app, false); + + FaceChange_Struct* fc = (FaceChange_Struct*)app->pBuffer; + m_pp.haircolor = fc->haircolor; + m_pp.beardcolor = fc->beardcolor; + m_pp.eyecolor1 = fc->eyecolor1; + m_pp.eyecolor2 = fc->eyecolor2; + m_pp.hairstyle = fc->hairstyle; + m_pp.face = fc->face; + m_pp.beard = fc->beard; + m_pp.drakkin_heritage = fc->drakkin_heritage; + m_pp.drakkin_tattoo = fc->drakkin_tattoo; + m_pp.drakkin_details = fc->drakkin_details; + Save(); + Message_StringID(13, FACE_ACCEPTED); + //Message(13, "Facial features updated."); + return; +} + +void Client::Handle_OP_FeignDeath(const EQApplicationPacket *app) +{ + if (GetClass() != MONK) + return; + if (!p_timers.Expired(&database, pTimerFeignDeath, false)) { + Message(13, "Ability recovery time not yet met."); + return; + } + int reuse = FeignDeathReuseTime; + switch (GetAA(aaRapidFeign)) + { + case 1: + reuse -= 1; + break; + case 2: + reuse -= 2; + break; + case 3: + reuse -= 5; + break; + } + p_timers.Start(pTimerFeignDeath, reuse - 1); + + //BreakInvis(); + + uint16 primfeign = GetSkill(SkillFeignDeath); + uint16 secfeign = GetSkill(SkillFeignDeath); + if (primfeign > 100) { + primfeign = 100; + secfeign = secfeign - 100; + secfeign = secfeign / 2; + } + else + secfeign = 0; + + uint16 totalfeign = primfeign + secfeign; + if (zone->random.Real(0, 160) > totalfeign) { + SetFeigned(false); + entity_list.MessageClose_StringID(this, false, 200, 10, STRING_FEIGNFAILED, GetName()); + } + else { + SetFeigned(true); + } + + CheckIncreaseSkill(SkillFeignDeath, nullptr, 5); + return; +} + +void Client::Handle_OP_FindPersonRequest(const EQApplicationPacket *app) +{ + if (app->size != sizeof(FindPersonRequest_Struct)) + printf("Error in FindPersonRequest_Struct. Expected size of: %zu, but got: %i\n", sizeof(FindPersonRequest_Struct), app->size); + else { + FindPersonRequest_Struct* t = (FindPersonRequest_Struct*)app->pBuffer; + + std::vector points; + Mob* target = entity_list.GetMob(t->npc_id); + + if (target == nullptr) { + //empty length packet == not found. + EQApplicationPacket outapp(OP_FindPersonReply, 0); + QueuePacket(&outapp); + return; + } + + if (!RuleB(Pathing, Find) && RuleB(Bazaar, EnableWarpToTrader) && target->IsClient() && (target->CastToClient()->Trader || + target->CastToClient()->Buyer)) { + Message(15, "Moving you to Trader %s", target->GetName()); + MovePC(zone->GetZoneID(), zone->GetInstanceID(), target->GetX(), target->GetY(), target->GetZ(), 0.0f); + } + + if (!RuleB(Pathing, Find) || !zone->pathing) + { + //fill in the path array... + // + points.resize(2); + points[0].x = GetX(); + points[0].y = GetY(); + points[0].z = GetZ(); + points[1].x = target->GetX(); + points[1].y = target->GetY(); + points[1].z = target->GetZ(); + } + else + { + Map::Vertex Start(GetX(), GetY(), GetZ() + (GetSize() < 6.0 ? 6 : GetSize()) * HEAD_POSITION); + Map::Vertex End(target->GetX(), target->GetY(), target->GetZ() + (target->GetSize() < 6.0 ? 6 : target->GetSize()) * HEAD_POSITION); + + if (!zone->zonemap->LineIntersectsZone(Start, End, 1.0f, nullptr) && zone->pathing->NoHazards(Start, End)) + { + points.resize(2); + points[0].x = Start.x; + points[0].y = Start.y; + points[0].z = Start.z; + + points[1].x = End.x; + points[1].y = End.y; + points[1].z = End.z; + + } + else + { + std::list pathlist = zone->pathing->FindRoute(Start, End); + + if (pathlist.size() == 0) + { + EQApplicationPacket outapp(OP_FindPersonReply, 0); + QueuePacket(&outapp); + return; + } + + //the client seems to have issues with packets larger than this + if (pathlist.size() > 36) + { + EQApplicationPacket outapp(OP_FindPersonReply, 0); + QueuePacket(&outapp); + return; + } + + // Live appears to send the points in this order: + // Final destination. + // Current Position. + // rest of the points. + FindPerson_Point p; + + int PointNumber = 0; + + bool LeadsToTeleporter = false; + + Map::Vertex v = zone->pathing->GetPathNodeCoordinates(pathlist.back()); + + p.x = v.x; + p.y = v.y; + p.z = v.z; + points.push_back(p); + + p.x = GetX(); + p.y = GetY(); + p.z = GetZ(); + points.push_back(p); + + for (std::list::iterator Iterator = pathlist.begin(); Iterator != pathlist.end(); ++Iterator) + { + if ((*Iterator) == -1) // Teleporter + { + LeadsToTeleporter = true; + break; + } + + Map::Vertex v = zone->pathing->GetPathNodeCoordinates((*Iterator), false); + p.x = v.x; + p.y = v.y; + p.z = v.z; + points.push_back(p); + ++PointNumber; + } + + if (!LeadsToTeleporter) + { + p.x = target->GetX(); + p.y = target->GetY(); + p.z = target->GetZ(); + + points.push_back(p); + } + + } + } + + SendPathPacket(points); + } + return; +} + +void Client::Handle_OP_Fishing(const EQApplicationPacket *app) +{ + if (!p_timers.Expired(&database, pTimerFishing, false)) { + Message(13, "Ability recovery time not yet met."); + return; + } + + if (CanFish()) { + parse->EventPlayer(EVENT_FISH_START, this, "", 0); + + //these will trigger GoFish() after a delay if we're able to actually fish, and if not, we won't stop the client from trying again immediately (although we may need to tell it to repop the button) + p_timers.Start(pTimerFishing, FishingReuseTime - 1); + fishing_timer.Start(); + } + return; + // Changes made based on Bobs work on foraging. Now can set items in the forage database table to + // forage for. +} + +void Client::Handle_OP_Forage(const EQApplicationPacket *app) +{ + + if (!p_timers.Expired(&database, pTimerForaging, false)) { + Message(13, "Ability recovery time not yet met."); + return; + } + p_timers.Start(pTimerForaging, ForagingReuseTime - 1); + + ForageItem(); + + return; +} + +void Client::Handle_OP_FriendsWho(const EQApplicationPacket *app) +{ + char *FriendsString = (char*)app->pBuffer; + FriendsWho(FriendsString); + return; +} + +void Client::Handle_OP_GetGuildMOTD(const EQApplicationPacket *app) +{ + mlog(GUILDS__IN_PACKETS, "Received OP_GetGuildMOTD"); + mpkt(GUILDS__IN_PACKET_TRACE, app); + + SendGuildMOTD(true); + + if (IsInAGuild()) + { + SendGuildURL(); + SendGuildChannel(); + } +} + +void Client::Handle_OP_GetGuildsList(const EQApplicationPacket *app) +{ + mlog(GUILDS__IN_PACKETS, "Received OP_GetGuildsList"); + mpkt(GUILDS__IN_PACKET_TRACE, app); + + SendGuildList(); +} + +void Client::Handle_OP_GMBecomeNPC(const EQApplicationPacket *app) +{ + if (this->Admin() < minStatusToUseGMCommands) { + Message(13, "Your account has been reported for hacking."); + database.SetHackerFlag(this->account_name, this->name, "/becomenpc"); + return; + } + if (app->size != sizeof(BecomeNPC_Struct)) { + LogFile->write(EQEMuLog::Error, "Wrong size: OP_GMBecomeNPC, size=%i, expected %i", app->size, sizeof(BecomeNPC_Struct)); + return; + } + //entity_list.QueueClients(this, app, false); + BecomeNPC_Struct* bnpc = (BecomeNPC_Struct*)app->pBuffer; + + Mob* cli = (Mob*)entity_list.GetMob(bnpc->id); + if (cli == 0) + return; + + if (cli->IsClient()) + cli->CastToClient()->QueuePacket(app); + cli->SendAppearancePacket(AT_NPCName, 1, true); + cli->CastToClient()->SetBecomeNPC(true); + cli->CastToClient()->SetBecomeNPCLevel(bnpc->maxlevel); + cli->Message_StringID(0, TOGGLE_OFF); + cli->CastToClient()->tellsoff = true; + //TODO: Make this toggle a BecomeNPC flag so that it gets updated when people zone in as well; Make combat work with this. + return; +} + +void Client::Handle_OP_GMDelCorpse(const EQApplicationPacket *app) +{ + if (app->size != sizeof(GMDelCorpse_Struct)) + return; + if (this->Admin() < commandEditPlayerCorpses) { + Message(13, "Your account has been reported for hacking."); + database.SetHackerFlag(this->account_name, this->name, "/delcorpse"); + return; + } + GMDelCorpse_Struct* dc = (GMDelCorpse_Struct *)app->pBuffer; + Mob* corpse = entity_list.GetMob(dc->corpsename); + if (corpse == 0) { + return; + } + if (corpse->IsCorpse() != true) { + return; + } + corpse->CastToCorpse()->Delete(); + std::cout << name << " deleted corpse " << dc->corpsename << std::endl; + Message(13, "Corpse %s deleted.", dc->corpsename); + return; +} + +void Client::Handle_OP_GMEmoteZone(const EQApplicationPacket *app) +{ + if (this->Admin() < minStatusToUseGMCommands) { + Message(13, "Your account has been reported for hacking."); + database.SetHackerFlag(this->account_name, this->name, "/emote"); + return; + } + if (app->size != sizeof(GMEmoteZone_Struct)) { + LogFile->write(EQEMuLog::Error, "Wrong size: OP_GMEmoteZone, size=%i, expected %i", app->size, sizeof(GMEmoteZone_Struct)); + return; + } + GMEmoteZone_Struct* gmez = (GMEmoteZone_Struct*)app->pBuffer; + char* newmessage = 0; + if (strstr(gmez->text, "^") == 0) + entity_list.Message(0, 15, gmez->text); + else{ + for (newmessage = strtok((char*)gmez->text, "^"); newmessage != nullptr; newmessage = strtok(nullptr, "^")) + entity_list.Message(0, 15, newmessage); + } + return; +} + +void Client::Handle_OP_GMEndTraining(const EQApplicationPacket *app) +{ + if (app->size != sizeof(GMTrainEnd_Struct)) { + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_GMEndTraining expected %i got %i", sizeof(GMTrainEnd_Struct), app->size); + DumpPacket(app); + return; + } + OPGMEndTraining(app); + return; +} + +void Client::Handle_OP_GMFind(const EQApplicationPacket *app) +{ + if (this->Admin() < minStatusToUseGMCommands) { + Message(13, "Your account has been reported for hacking."); + database.SetHackerFlag(this->account_name, this->name, "/find"); + return; + } + if (app->size != sizeof(GMSummon_Struct)) { + LogFile->write(EQEMuLog::Error, "Wrong size: OP_GMFind, size=%i, expected %i", app->size, sizeof(GMSummon_Struct)); + return; + } + //Break down incoming + GMSummon_Struct* request = (GMSummon_Struct*)app->pBuffer; + //Create a new outgoing + EQApplicationPacket *outapp = new EQApplicationPacket(OP_GMFind, sizeof(GMSummon_Struct)); + GMSummon_Struct* foundplayer = (GMSummon_Struct*)outapp->pBuffer; + //Copy the constants + strcpy(foundplayer->charname, request->charname); + strcpy(foundplayer->gmname, request->gmname); + //Check if the NPC exits intrazone... + Mob* gt = entity_list.GetMob(request->charname); + if (gt != 0) { + foundplayer->success = 1; + foundplayer->x = (int32)gt->GetX(); + foundplayer->y = (int32)gt->GetY(); + + foundplayer->z = (int32)gt->GetZ(); + foundplayer->zoneID = zone->GetZoneID(); + } + //Send the packet... + FastQueuePacket(&outapp); + return; +} + +void Client::Handle_OP_GMGoto(const EQApplicationPacket *app) +{ + if (app->size != sizeof(GMSummon_Struct)) { + std::cout << "Wrong size on OP_GMGoto. Got: " << app->size << ", Expected: " << sizeof(GMSummon_Struct) << std::endl; + return; + } + if (this->Admin() < minStatusToUseGMCommands) { + Message(13, "Your account has been reported for hacking."); + database.SetHackerFlag(this->account_name, this->name, "/goto"); + return; + } + GMSummon_Struct* gmg = (GMSummon_Struct*)app->pBuffer; + Mob* gt = entity_list.GetMob(gmg->charname); + if (gt != nullptr) { + this->MovePC(zone->GetZoneID(), zone->GetInstanceID(), gt->GetX(), gt->GetY(), gt->GetZ(), gt->GetHeading()); + } + else if (!worldserver.Connected()) + Message(0, "Error: World server disconnected."); + else { + ServerPacket* pack = new ServerPacket(ServerOP_GMGoto, sizeof(ServerGMGoto_Struct)); + memset(pack->pBuffer, 0, pack->size); + ServerGMGoto_Struct* wsgmg = (ServerGMGoto_Struct*)pack->pBuffer; + strcpy(wsgmg->myname, this->GetName()); + strcpy(wsgmg->gotoname, gmg->charname); + wsgmg->admin = admin; + worldserver.SendPacket(pack); + safe_delete(pack); + } + return; +} + +void Client::Handle_OP_GMHideMe(const EQApplicationPacket *app) +{ + if (this->Admin() < minStatusToUseGMCommands) { + Message(13, "Your account has been reported for hacking."); + database.SetHackerFlag(this->account_name, this->name, "/hideme"); + return; + } + if (app->size != sizeof(SpawnAppearance_Struct)) { + LogFile->write(EQEMuLog::Error, "Wrong size: OP_GMHideMe, size=%i, expected %i", app->size, sizeof(SpawnAppearance_Struct)); + return; + } + SpawnAppearance_Struct* sa = (SpawnAppearance_Struct*)app->pBuffer; + Message(13, "#: %i, %i", sa->type, sa->parameter); + SetHideMe(!sa->parameter); + return; + +} + +void Client::Handle_OP_GMKick(const EQApplicationPacket *app) +{ + if (app->size != sizeof(GMKick_Struct)) + return; + if (this->Admin() < minStatusToKick) { + Message(13, "Your account has been reported for hacking."); + database.SetHackerFlag(this->account_name, this->name, "/kick"); + return; + } + GMKick_Struct* gmk = (GMKick_Struct *)app->pBuffer; + + Client* client = entity_list.GetClientByName(gmk->name); + if (client == 0) { + if (!worldserver.Connected()) + Message(0, "Error: World server disconnected"); + else { + ServerPacket* pack = new ServerPacket(ServerOP_KickPlayer, sizeof(ServerKickPlayer_Struct)); + ServerKickPlayer_Struct* skp = (ServerKickPlayer_Struct*)pack->pBuffer; + strcpy(skp->adminname, gmk->gmname); + strcpy(skp->name, gmk->name); + skp->adminrank = this->Admin(); + worldserver.SendPacket(pack); + safe_delete(pack); + } + } + else { + entity_list.QueueClients(this, app); + //client->Kick(); + } + return; +} + +void Client::Handle_OP_GMKill(const EQApplicationPacket *app) +{ + if (this->Admin() < minStatusToUseGMCommands) { + Message(13, "Your account has been reported for hacking."); + database.SetHackerFlag(this->account_name, this->name, "/kill"); + return; + } + if (app->size != sizeof(GMKill_Struct)) { + LogFile->write(EQEMuLog::Error, "Wrong size: OP_GMKill, size=%i, expected %i", app->size, sizeof(GMKill_Struct)); + return; + } + GMKill_Struct* gmk = (GMKill_Struct *)app->pBuffer; + Mob* obj = entity_list.GetMob(gmk->name); + Client* client = entity_list.GetClientByName(gmk->name); + if (obj != 0) { + if (client != 0) { + entity_list.QueueClients(this, app); + } + else { + obj->Kill(); + } + } + else { + if (!worldserver.Connected()) + Message(0, "Error: World server disconnected"); + else { + ServerPacket* pack = new ServerPacket(ServerOP_KillPlayer, sizeof(ServerKillPlayer_Struct)); + ServerKillPlayer_Struct* skp = (ServerKillPlayer_Struct*)pack->pBuffer; + strcpy(skp->gmname, gmk->gmname); + strcpy(skp->target, gmk->name); + skp->admin = this->Admin(); + worldserver.SendPacket(pack); + safe_delete(pack); + } + } + return; +} + +void Client::Handle_OP_GMLastName(const EQApplicationPacket *app) +{ + if (app->size != sizeof(GMLastName_Struct)) { + std::cout << "Wrong size on OP_GMLastName. Got: " << app->size << ", Expected: " << sizeof(GMLastName_Struct) << std::endl; + return; + } + GMLastName_Struct* gmln = (GMLastName_Struct*)app->pBuffer; + if (strlen(gmln->lastname) >= 64) { + Message(13, "/LastName: New last name too long. (max=63)"); + } + else { + Client* client = entity_list.GetClientByName(gmln->name); + if (client == 0) { + Message(13, "/LastName: %s not found", gmln->name); + } + else { + if (this->Admin() < minStatusToUseGMCommands) { + Message(13, "Your account has been reported for hacking."); + database.SetHackerFlag(client->account_name, client->name, "/lastname"); + return; + } + else + + client->ChangeLastName(gmln->lastname); + } + gmln->unknown[0] = 1; + gmln->unknown[1] = 1; + gmln->unknown[2] = 1; + gmln->unknown[3] = 1; + entity_list.QueueClients(this, app, false); + } + return; +} + +void Client::Handle_OP_GMNameChange(const EQApplicationPacket *app) +{ + if (app->size != sizeof(GMName_Struct)) { + LogFile->write(EQEMuLog::Error, "Wrong size: OP_GMNameChange, size=%i, expected %i", app->size, sizeof(GMName_Struct)); + return; + } + const GMName_Struct* gmn = (const GMName_Struct *)app->pBuffer; + if (this->Admin() < minStatusToUseGMCommands){ + Message(13, "Your account has been reported for hacking."); + database.SetHackerFlag(this->account_name, this->name, "/name"); + return; + } + Client* client = entity_list.GetClientByName(gmn->oldname); + LogFile->write(EQEMuLog::Status, "GM(%s) changeing players name. Old:%s New:%s", GetName(), gmn->oldname, gmn->newname); + bool usedname = database.CheckUsedName((const char*)gmn->newname); + if (client == 0) { + Message(13, "%s not found for name change. Operation failed!", gmn->oldname); + return; + } + if ((strlen(gmn->newname) > 63) || (strlen(gmn->newname) == 0)) { + Message(13, "Invalid number of characters in new name (%s).", gmn->newname); + return; + } + if (!usedname) { + Message(13, "%s is already in use. Operation failed!", gmn->newname); + return; + + } + database.UpdateName(gmn->oldname, gmn->newname); + strcpy(client->name, gmn->newname); + client->Save(); + + if (gmn->badname == 1) { + database.AddToNameFilter(gmn->oldname); + } + EQApplicationPacket* outapp = app->Copy(); + GMName_Struct* gmn2 = (GMName_Struct*)outapp->pBuffer; + gmn2->unknown[0] = 1; + gmn2->unknown[1] = 1; + gmn2->unknown[2] = 1; + entity_list.QueueClients(this, outapp, false); + safe_delete(outapp); + UpdateWho(); + return; +} + +void Client::Handle_OP_GMSearchCorpse(const EQApplicationPacket *app) +{ + // Could make this into a rule, although there is a hard limit since we are using a popup, of 4096 bytes that can + // be displayed in the window, including all the HTML formatting tags. + // + const int maxResults = 10; + + if (app->size < sizeof(GMSearchCorpse_Struct)) + { + LogFile->write(EQEMuLog::Debug, "OP_GMSearchCorpse size lower than expected: got %u expected at least %u", + app->size, sizeof(GMSearchCorpse_Struct)); + DumpPacket(app); + return; + } + + GMSearchCorpse_Struct *gmscs = (GMSearchCorpse_Struct *)app->pBuffer; + gmscs->Name[63] = '\0'; + + char *escSearchString = new char[129]; + database.DoEscapeString(escSearchString, gmscs->Name, strlen(gmscs->Name)); + + std::string query = StringFormat("SELECT charname, zoneid, x, y, z, time_of_death, rezzed, IsBurried " + "FROM character_corpses WheRE charname LIKE '%%%s%%' ORDER BY charname LIMIT %i", + escSearchString, maxResults); + safe_delete_array(escSearchString); + auto results = database.QueryDatabase(query); + if (!results.Success()) { + Message(0, "Query failed: %s.", results.ErrorMessage().c_str()); + return; + } + + if (results.RowCount() == 0) + return; + + if (results.RowCount() == maxResults) + Message(clientMessageError, "Your search found too many results; some are not displayed."); + else + Message(clientMessageYellow, "There are %i corpse(s) that match the search string '%s'.", results.RowCount(), gmscs->Name); + + char charName[64], time_of_death[20]; + + std::string popupText = ""; + + for (auto row = results.begin(); row != results.end(); ++row) { + + strn0cpy(charName, row[0], sizeof(charName)); + + uint32 ZoneID = atoi(row[1]); + float CorpseX = atof(row[2]); + float CorpseY = atof(row[3]); + float CorpseZ = atof(row[4]); + + strn0cpy(time_of_death, row[5], sizeof(time_of_death)); + + bool corpseRezzed = atoi(row[6]); + bool corpseBuried = atoi(row[7]); + + popupText += StringFormat("", + charName, StaticGetZoneName(ZoneID), CorpseX, CorpseY, CorpseZ, time_of_death, + corpseRezzed ? "Yes" : "No", corpseBuried ? "Yes" : "No"); + + if (popupText.size() > 4000) { + Message(clientMessageError, "Unable to display all the results."); + break; + } + + } + + popupText += "
NameZoneXYZDate" + "RezzedBuried
 " + "
%s%s%8.0f%8.0f%8.0f%s%s%s
"; + + SendPopupToClient("Corpses", popupText.c_str()); + +} + +void Client::Handle_OP_GMServers(const EQApplicationPacket *app) +{ + if (!worldserver.Connected()) + Message(0, "Error: World server disconnected"); + else { + ServerPacket* pack = new ServerPacket(ServerOP_ZoneStatus, strlen(this->GetName()) + 2); + memset(pack->pBuffer, (uint8)admin, 1); + strcpy((char *)&pack->pBuffer[1], this->GetName()); + worldserver.SendPacket(pack); + safe_delete(pack); + } + return; +} + +void Client::Handle_OP_GMSummon(const EQApplicationPacket *app) +{ + if (app->size != sizeof(GMSummon_Struct)) { + std::cout << "Wrong size on OP_GMSummon. Got: " << app->size << ", Expected: " << sizeof(GMSummon_Struct) << std::endl; + return; + } + OPGMSummon(app); + return; +} + +void Client::Handle_OP_GMToggle(const EQApplicationPacket *app) +{ + if (app->size != sizeof(GMToggle_Struct)) { + std::cout << "Wrong size on OP_GMToggle. Got: " << app->size << ", Expected: " << sizeof(GMToggle_Struct) << std::endl; + return; + } + if (this->Admin() < minStatusToUseGMCommands) { + Message(13, "Your account has been reported for hacking."); + database.SetHackerFlag(this->account_name, this->name, "/toggle"); + return; + } + GMToggle_Struct *ts = (GMToggle_Struct *)app->pBuffer; + if (ts->toggle == 0) { + this->Message_StringID(0, TOGGLE_OFF); + //Message(0, "Turning tells OFF"); + tellsoff = true; + } + else if (ts->toggle == 1) { + //Message(0, "Turning tells ON"); + this->Message_StringID(0, TOGGLE_ON); + tellsoff = false; + } + else { + Message(0, "Unkown value in /toggle packet"); + } + UpdateWho(); + return; +} + +void Client::Handle_OP_GMTraining(const EQApplicationPacket *app) +{ + if (app->size != sizeof(GMTrainee_Struct)) { + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_GMTraining expected %i got %i", sizeof(GMTrainee_Struct), app->size); + DumpPacket(app); + return; + } + OPGMTraining(app); + return; +} + +void Client::Handle_OP_GMTrainSkill(const EQApplicationPacket *app) +{ + if (app->size != sizeof(GMSkillChange_Struct)) { + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_GMTrainSkill expected %i got %i", sizeof(GMSkillChange_Struct), app->size); + DumpPacket(app); + return; + } + OPGMTrainSkill(app); + return; +} + +void Client::Handle_OP_GMZoneRequest(const EQApplicationPacket *app) +{ + if (app->size != sizeof(GMZoneRequest_Struct)) { + std::cout << "Wrong size on OP_GMZoneRequest. Got: " << app->size << ", Expected: " << sizeof(GMZoneRequest_Struct) << std::endl; + return; + } + if (this->Admin() < minStatusToBeGM) { + Message(13, "Your account has been reported for hacking."); + database.SetHackerFlag(this->account_name, this->name, "/zone"); + return; + } + + GMZoneRequest_Struct* gmzr = (GMZoneRequest_Struct*)app->pBuffer; + float tarx = -1, tary = -1, tarz = -1; + + int16 minstatus = 0; + uint8 minlevel = 0; + char tarzone[32]; + uint16 zid = gmzr->zone_id; + if (gmzr->zone_id == 0) + zid = zonesummon_id; + const char * zname = database.GetZoneName(zid); + if (zname == nullptr) + tarzone[0] = 0; + else + strcpy(tarzone, zname); + + // this both loads the safe points and does a sanity check on zone name + if (!database.GetSafePoints(tarzone, 0, &tarx, &tary, &tarz, &minstatus, &minlevel)) { + tarzone[0] = 0; + } + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_GMZoneRequest, sizeof(GMZoneRequest_Struct)); + GMZoneRequest_Struct* gmzr2 = (GMZoneRequest_Struct*)outapp->pBuffer; + strcpy(gmzr2->charname, this->GetName()); + gmzr2->zone_id = gmzr->zone_id; + gmzr2->x = tarx; + gmzr2->y = tary; + gmzr2->z = tarz; + // Next line stolen from ZoneChange as well... - This gives us a nicer message than the normal "zone is down" message... + if (tarzone[0] != 0 && admin >= minstatus && GetLevel() >= minlevel) + gmzr2->success = 1; + else { + std::cout << "GetZoneSafeCoords failed. zoneid = " << gmzr->zone_id << "; czone = " << zone->GetZoneID() << std::endl; + gmzr2->success = 0; + } + + QueuePacket(outapp); + safe_delete(outapp); + return; +} + +void Client::Handle_OP_GMZoneRequest2(const EQApplicationPacket *app) +{ + if (this->Admin() < minStatusToBeGM) { + Message(13, "Your account has been reported for hacking."); + database.SetHackerFlag(this->account_name, this->name, "/zone"); + return; + } + if (app->size < sizeof(uint32)) { + LogFile->write(EQEMuLog::Error, "OP size error: OP_GMZoneRequest2 expected:%i got:%i", sizeof(uint32), app->size); + return; + } + + uint32 zonereq = *((uint32 *)app->pBuffer); + GoToSafeCoords(zonereq, 0); + return; +} + +void Client::Handle_OP_GroupAcknowledge(const EQApplicationPacket *app) +{ + return; +} + +void Client::Handle_OP_GroupCancelInvite(const EQApplicationPacket *app) +{ + if (app->size != sizeof(GroupCancel_Struct)) { + LogFile->write(EQEMuLog::Error, "Invalid size for OP_GroupCancelInvite: Expected: %i, Got: %i", + sizeof(GroupCancel_Struct), app->size); + return; + } + + GroupCancel_Struct* gf = (GroupCancel_Struct*)app->pBuffer; + Mob* inviter = entity_list.GetClientByName(gf->name1); + + if (inviter != nullptr) + { + if (inviter->IsClient()) + inviter->CastToClient()->QueuePacket(app); + } + else + { + ServerPacket* pack = new ServerPacket(ServerOP_GroupCancelInvite, sizeof(GroupCancel_Struct)); + memcpy(pack->pBuffer, gf, sizeof(GroupCancel_Struct)); + worldserver.SendPacket(pack); + safe_delete(pack); + } + + if (!GetMerc()) + { + database.SetGroupID(GetName(), 0, CharacterID(), false); + } + return; +} + +void Client::Handle_OP_GroupDelete(const EQApplicationPacket *app) +{ + //should check for leader, only they should be able to do this.. + Group* group = GetGroup(); + if (group) + group->DisbandGroup(); + + if (LFP) + UpdateLFP(); + + return; +} + +void Client::Handle_OP_GroupDisband(const EQApplicationPacket *app) +{ + if (app->size != sizeof(GroupGeneric_Struct)) { + LogFile->write(EQEMuLog::Error, "Invalid size for GroupGeneric_Struct: Expected: %i, Got: %i", + sizeof(GroupGeneric_Struct), app->size); + return; + } + + LogFile->write(EQEMuLog::Debug, "Member Disband Request from %s\n", GetName()); + + GroupGeneric_Struct* gd = (GroupGeneric_Struct*)app->pBuffer; + + Raid *raid = entity_list.GetRaidByClient(this); + if (raid) + { + Mob* memberToDisband = nullptr; + + if (!raid->IsGroupLeader(GetName())) + memberToDisband = this; + else + memberToDisband = GetTarget(); + + if (!memberToDisband) + memberToDisband = entity_list.GetMob(gd->name2); + + if (!memberToDisband) + memberToDisband = this; + + if (!memberToDisband->IsClient()) + return; + + //we have a raid.. see if we're in a raid group + uint32 grp = raid->GetGroup(memberToDisband->GetName()); + bool wasGrpLdr = raid->members[raid->GetPlayerIndex(memberToDisband->GetName())].IsGroupLeader; + if (grp < 12){ + if (wasGrpLdr){ + raid->SetGroupLeader(memberToDisband->GetName(), false); + for (int x = 0; x < MAX_RAID_MEMBERS; x++) + { + if (raid->members[x].GroupNumber == grp) + { + if (strlen(raid->members[x].membername) > 0 && strcmp(raid->members[x].membername, memberToDisband->GetName()) != 0) + { + raid->SetGroupLeader(raid->members[x].membername); + break; + } + } + } + } + raid->MoveMember(memberToDisband->GetName(), 0xFFFFFFFF); + raid->GroupUpdate(grp); //break + //raid->SendRaidGroupRemove(memberToDisband->GetName(), grp); + //raid->SendGroupUpdate(memberToDisband->CastToClient()); + raid->SendGroupDisband(memberToDisband->CastToClient()); + } + //we're done + return; + } + + Group* group = GetGroup(); + + if (!group) + return; + +#ifdef BOTS + // this block is necessary to allow more control over controlling how bots are zoned or camped. + if (Bot::GroupHasBot(group)) { + if (group->IsLeader(this)) { + if ((GetTarget() == 0 || GetTarget() == this) || (group->GroupCount() < 3)) { + Bot::ProcessBotGroupDisband(this, std::string()); + } + else { + Mob* tempMember = entity_list.GetMob(gd->name2); + if (tempMember) { + if (tempMember->IsBot()) + Bot::ProcessBotGroupDisband(this, std::string(tempMember->GetCleanName())); + } + } + } + } +#endif + if (group->GroupCount() < 3) + { + group->DisbandGroup(); + if (GetMerc()) + GetMerc()->Suspend(); + } + else if (group->IsLeader(this) && GetTarget() == nullptr) + { + if (group->GroupCount() > 2 && GetMerc() && !GetMerc()->IsSuspended()) + { + group->DisbandGroup(); + GetMerc()->MercJoinClientGroup(); + } + else + { + group->DisbandGroup(); + if (GetMerc()) + GetMerc()->Suspend(); + } + } + else if (group->IsLeader(this) && GetTarget() == this) + { + LeaveGroup(); + if (GetMerc() && !GetMerc()->IsSuspended()) + { + GetMerc()->MercJoinClientGroup(); + } + } + else + { + Mob* memberToDisband = nullptr; + memberToDisband = GetTarget(); + + if (!memberToDisband) + memberToDisband = entity_list.GetMob(gd->name2); + + if (memberToDisband) + { + if (group->IsLeader(this)) + { + // the group leader can kick other members out of the group... + if (memberToDisband->IsClient()) + { + group->DelMember(memberToDisband, false); + Client* memberClient = memberToDisband->CastToClient(); + Merc* memberMerc = memberToDisband->CastToClient()->GetMerc(); + if (memberClient && memberMerc) + { + memberMerc->MercJoinClientGroup(); + } + } + else if (memberToDisband->IsMerc()) + { + memberToDisband->CastToMerc()->Suspend(); + } + } + else + { + // ...but other members can only remove themselves + group->DelMember(this, false); + + if (GetMerc() && !GetMerc()->IsSuspended()) + { + GetMerc()->MercJoinClientGroup(); + } + } + } + else + { + LogFile->write(EQEMuLog::Error, "Failed to remove player from group. Unable to find player named %s in player group", gd->name2); + } + } + if (LFP) + { + // If we are looking for players, update to show we are on our own now. + UpdateLFP(); + } + + return; +} + +void Client::Handle_OP_GroupFollow(const EQApplicationPacket *app) +{ + Handle_OP_GroupFollow2(app); +} + +void Client::Handle_OP_GroupFollow2(const EQApplicationPacket *app) +{ + if (app->size != sizeof(GroupGeneric_Struct)) { + LogFile->write(EQEMuLog::Error, "Invalid size for OP_GroupFollow: Expected: %i, Got: %i", + sizeof(GroupGeneric_Struct), app->size); + return; + } + + if (LFP) { + // If we were looking for players to start our own group, but we accept an invitation to another + // group, turn LFP off. + database.SetLFP(CharacterID(), false); + worldserver.StopLFP(CharacterID()); + } + + GroupGeneric_Struct* gf = (GroupGeneric_Struct*)app->pBuffer; + Mob* inviter = entity_list.GetClientByName(gf->name1); + + // Inviter and Invitee are in the same zone + if (inviter != nullptr && inviter->IsClient()) + { + if (GroupFollow(inviter->CastToClient())) + { + strn0cpy(gf->name1, inviter->GetName(), sizeof(gf->name1)); + strn0cpy(gf->name2, GetName(), sizeof(gf->name2)); + inviter->CastToClient()->QueuePacket(app);//notify inviter the client accepted + } + } + else if (inviter == nullptr) + { + // Inviter is in another zone - Remove merc from group now if any + LeaveGroup(); + + ServerPacket* pack = new ServerPacket(ServerOP_GroupFollow, sizeof(ServerGroupFollow_Struct)); + ServerGroupFollow_Struct *sgfs = (ServerGroupFollow_Struct *)pack->pBuffer; + sgfs->CharacterID = CharacterID(); + strn0cpy(sgfs->gf.name1, gf->name1, sizeof(sgfs->gf.name1)); + strn0cpy(sgfs->gf.name2, gf->name2, sizeof(sgfs->gf.name2)); + worldserver.SendPacket(pack); + safe_delete(pack); + } +} + +void Client::Handle_OP_GroupInvite(const EQApplicationPacket *app) +{ + //this seems to be the initial invite to form a group + Handle_OP_GroupInvite2(app); +} + +void Client::Handle_OP_GroupInvite2(const EQApplicationPacket *app) +{ + if (app->size != sizeof(GroupInvite_Struct)) { + LogFile->write(EQEMuLog::Error, "Invalid size for OP_GroupInvite: Expected: %i, Got: %i", + sizeof(GroupInvite_Struct), app->size); + return; + } + + GroupInvite_Struct* gis = (GroupInvite_Struct*)app->pBuffer; + + Mob *Invitee = entity_list.GetMob(gis->invitee_name); + + if (Invitee == this) + { + Message_StringID(clientMessageWhite, GROUP_INVITEE_SELF); + return; + } + + if (Invitee) + { + if (Invitee->IsClient()) + { + if(Invitee->CastToClient()->MercOnlyOrNoGroup() && !Invitee->IsRaidGrouped()) + { + if (app->GetOpcode() == OP_GroupInvite2) + { + //Make a new packet using all the same information but make sure it's a fixed GroupInvite opcode so we + //Don't have to deal with GroupFollow2 crap. + EQApplicationPacket* outapp = new EQApplicationPacket(OP_GroupInvite, sizeof(GroupInvite_Struct)); + memcpy(outapp->pBuffer, app->pBuffer, outapp->size); + Invitee->CastToClient()->QueuePacket(outapp); + safe_delete(outapp); + return; + } + else + { + //The correct opcode, no reason to bother wasting time reconstructing the packet + Invitee->CastToClient()->QueuePacket(app); + } + } + } +#ifdef BOTS + else if (Invitee->IsBot()) { + Bot::ProcessBotGroupInvite(this, std::string(Invitee->GetName())); + } +#endif + } + else + { + ServerPacket* pack = new ServerPacket(ServerOP_GroupInvite, sizeof(GroupInvite_Struct)); + memcpy(pack->pBuffer, gis, sizeof(GroupInvite_Struct)); + worldserver.SendPacket(pack); + safe_delete(pack); + } + return; +} + +void Client::Handle_OP_GroupMakeLeader(const EQApplicationPacket *app) +{ + VERIFY_PACKET_LENGTH(OP_GroupMakeLeader, app, GroupMakeLeader_Struct); + + GroupMakeLeader_Struct *gmls = (GroupMakeLeader_Struct *)app->pBuffer; + + Mob* NewLeader = entity_list.GetClientByName(gmls->NewLeader); + + Group* g = GetGroup(); + + if (NewLeader && g) + { + if (g->IsLeader(this)) + g->ChangeLeader(NewLeader); + else { + LogFile->write(EQEMuLog::Debug, "Group /makeleader request originated from non-leader member: %s", GetName()); + DumpPacket(app); + } + } +} + +void Client::Handle_OP_GroupMentor(const EQApplicationPacket *app) +{ + if (app->size != sizeof(GroupMentor_Struct)) { + LogFile->write(EQEMuLog::Error, "Wrong size: OP_GroupMentor, size=%i, expected %i", app->size, sizeof(GroupMentor_Struct)); + DumpPacket(app); + return; + } + GroupMentor_Struct *gms = (GroupMentor_Struct *)app->pBuffer; + gms->name[63] = '\0'; + + if (IsRaidGrouped()) { + Raid *raid = GetRaid(); + if (!raid) + return; + uint32 group_id = raid->GetGroup(this); + if (group_id > 11) + return; + if (strlen(gms->name)) + raid->SetGroupMentor(group_id, gms->percent, gms->name); + else + raid->ClearGroupMentor(group_id); + return; + } + + Group *group = GetGroup(); + if (!group) + return; + + if (strlen(gms->name)) + group->SetGroupMentor(gms->percent, gms->name); + else + group->ClearGroupMentor(); + + return; +} + +void Client::Handle_OP_GroupRoles(const EQApplicationPacket *app) +{ + if (app->size != sizeof(GroupRole_Struct)) { + LogFile->write(EQEMuLog::Error, "Wrong size: OP_GroupRoles, size=%i, expected %i", app->size, sizeof(GroupRole_Struct)); + DumpPacket(app); + return; + } + GroupRole_Struct *grs = (GroupRole_Struct*)app->pBuffer; + + Group *g = GetGroup(); + + if (!g) + return; + + switch (grs->RoleNumber) + { + case 1: //Main Tank + { + if (grs->Toggle) + g->DelegateMainTank(grs->Name1, grs->Toggle); + else + g->UnDelegateMainTank(grs->Name1, grs->Toggle); + break; + } + case 2: //Main Assist + { + if (grs->Toggle) + g->DelegateMainAssist(grs->Name1, grs->Toggle); + else + g->UnDelegateMainAssist(grs->Name1, grs->Toggle); + break; + } + case 3: //Puller + { + if (grs->Toggle) + g->DelegatePuller(grs->Name1, grs->Toggle); + else + g->UnDelegatePuller(grs->Name1, grs->Toggle); + break; + } + default: + break; + } +} + +void Client::Handle_OP_GroupUpdate(const EQApplicationPacket *app) +{ + if (app->size != sizeof(GroupUpdate_Struct)) + { + LogFile->write(EQEMuLog::Debug, "Size mismatch on OP_GroupUpdate: got %u expected %u", + app->size, sizeof(GroupUpdate_Struct)); + DumpPacket(app); + return; + } + + GroupUpdate_Struct* gu = (GroupUpdate_Struct*)app->pBuffer; + + switch (gu->action) { + case groupActMakeLeader: + { + Mob* newleader = entity_list.GetClientByName(gu->membername[0]); + Group* group = this->GetGroup(); + + if (newleader && group) { + // the client only sends this if it's the group leader, but check anyway + if (group->IsLeader(this)) + group->ChangeLeader(newleader); + else { + LogFile->write(EQEMuLog::Debug, "Group /makeleader request originated from non-leader member: %s", GetName()); + DumpPacket(app); + } + } + break; + } + + default: + { + LogFile->write(EQEMuLog::Debug, "Received unhandled OP_GroupUpdate requesting action %u", gu->action); + DumpPacket(app); + return; + } + } +} + +void Client::Handle_OP_GuildBank(const EQApplicationPacket *app) +{ + if (!GuildBanks) + return; + + if ((int)zone->GetZoneID() != RuleI(World, GuildBankZoneID)) + { + Message(13, "The Guild Bank is not available in this zone."); + + return; + } + + if (app->size < sizeof(uint32)) { + LogFile->write(EQEMuLog::Error, "Wrong size: OP_GuildBank, size=%i, expected %i", app->size, sizeof(uint32)); + DumpPacket(app); + return; + } + + char *Buffer = (char *)app->pBuffer; + + uint32 Action = VARSTRUCT_DECODE_TYPE(uint32, Buffer); + + if (!IsInAGuild()) + { + Message(13, "You must be in a Guild to use the Guild Bank."); + + if (Action == GuildBankDeposit) + GuildBankDepositAck(true); + else + GuildBankAck(); + + return; + } + + if (!IsGuildBanker()) + { + if ((Action != GuildBankDeposit) && (Action != GuildBankViewItem) && (Action != GuildBankWithdraw)) + { + _log(GUILDS__BANK_ERROR, "Suspected hacking attempt on guild bank from %s", GetName()); + + GuildBankAck(); + + return; + } + } + + switch (Action) + { + case GuildBankPromote: + { + if (GuildBanks->IsAreaFull(GuildID(), GuildBankMainArea)) + { + Message_StringID(13, GUILD_BANK_FULL); + + GuildBankDepositAck(true); + + return; + } + + GuildBankPromote_Struct *gbps = (GuildBankPromote_Struct*)app->pBuffer; + + int Slot = GuildBanks->Promote(GuildID(), gbps->Slot); + + if (Slot >= 0) + { + ItemInst* inst = GuildBanks->GetItem(GuildID(), GuildBankMainArea, Slot, 1); + + if (inst) + { + Message_StringID(clientMessageWhite, GUILD_BANK_TRANSFERRED, inst->GetItem()->Name); + safe_delete(inst); + } + } + else + Message(13, "Unexpected error while moving item into Guild Bank."); + + GuildBankAck(); + + break; + } + + case GuildBankViewItem: + { + GuildBankViewItem_Struct *gbvis = (GuildBankViewItem_Struct*)app->pBuffer; + + ItemInst* inst = GuildBanks->GetItem(GuildID(), gbvis->Area, gbvis->SlotID, 1); + + if (!inst) + break; + + SendItemPacket(0, inst, ItemPacketViewLink); + + safe_delete(inst); + + break; + } + + case GuildBankDeposit: // Deposit Item + { + if (GuildBanks->IsAreaFull(GuildID(), GuildBankDepositArea)) + { + Message_StringID(13, GUILD_BANK_FULL); + + GuildBankDepositAck(true); + + return; + } + + ItemInst *CursorItemInst = GetInv().GetItem(MainCursor); + + bool Allowed = true; + + if (!CursorItemInst) + { + Message(13, "No Item on the cursor."); + + GuildBankDepositAck(true); + + return; + } + + const Item_Struct* CursorItem = CursorItemInst->GetItem(); + + if (!CursorItem->NoDrop || CursorItemInst->IsInstNoDrop()) + { + Message_StringID(13, GUILD_BANK_CANNOT_DEPOSIT); + + Allowed = false; + } + else if (CursorItemInst->IsNoneEmptyContainer()) + { + Message_StringID(13, GUILD_BANK_CANNOT_DEPOSIT); + + Allowed = false; + } + else if (CursorItemInst->IsAugmented()) + { + Message_StringID(13, GUILD_BANK_CANNOT_DEPOSIT); + + Allowed = false; + } + else if (CursorItem->NoRent == 0) + { + Message_StringID(13, GUILD_BANK_CANNOT_DEPOSIT); + + Allowed = false; + } + else if (CursorItem->LoreFlag && GuildBanks->HasItem(GuildID(), CursorItem->ID)) + { + Message_StringID(13, GUILD_BANK_CANNOT_DEPOSIT); + + Allowed = false; + } + + if (!Allowed) + { + GuildBankDepositAck(true); + + return; + } + + if (GuildBanks->AddItem(GuildID(), GuildBankDepositArea, CursorItem->ID, CursorItemInst->GetCharges(), GetName(), GuildBankBankerOnly, "")) + { + GuildBankDepositAck(false); + + DeleteItemInInventory(MainCursor, 0, false); + } + + break; + } + + case GuildBankPermissions: + { + GuildBankPermissions_Struct *gbps = (GuildBankPermissions_Struct*)app->pBuffer; + + if (gbps->Permissions == 1) + GuildBanks->SetPermissions(GuildID(), gbps->SlotID, gbps->Permissions, gbps->MemberName); + else + GuildBanks->SetPermissions(GuildID(), gbps->SlotID, gbps->Permissions, ""); + + GuildBankAck(); + break; + } + + case GuildBankWithdraw: + { + if (GetInv()[MainCursor]) + { + Message_StringID(13, GUILD_BANK_EMPTY_HANDS); + + GuildBankAck(); + + break; + } + + GuildBankWithdrawItem_Struct *gbwis = (GuildBankWithdrawItem_Struct*)app->pBuffer; + + ItemInst* inst = GuildBanks->GetItem(GuildID(), gbwis->Area, gbwis->SlotID, gbwis->Quantity); + + if (!inst) + { + GuildBankAck(); + + break; + } + + if (!IsGuildBanker() && !GuildBanks->AllowedToWithdraw(GuildID(), gbwis->Area, gbwis->SlotID, GetName())) + { + _log(GUILDS__BANK_ERROR, "Suspected attempted hack on the guild bank from %s", GetName()); + + GuildBankAck(); + + safe_delete(inst); + + break; + } + + if (CheckLoreConflict(inst->GetItem())) + { + Message_StringID(13, DUP_LORE); + + GuildBankAck(); + + safe_delete(inst); + + break; + } + + if (gbwis->Quantity > 0) + { + PushItemOnCursor(*inst); + + SendItemPacket(MainCursor, inst, ItemPacketSummonItem); + + GuildBanks->DeleteItem(GuildID(), gbwis->Area, gbwis->SlotID, gbwis->Quantity); + } + else + { + Message(0, "Unable to withdraw 0 quantity of %s", inst->GetItem()->Name); + } + + safe_delete(inst); + + GuildBankAck(); + + break; + } + + case GuildBankSplitStacks: + { + if (GuildBanks->IsAreaFull(GuildID(), GuildBankMainArea)) + Message_StringID(13, GUILD_BANK_FULL); + else + { + GuildBankWithdrawItem_Struct *gbwis = (GuildBankWithdrawItem_Struct*)app->pBuffer; + + GuildBanks->SplitStack(GuildID(), gbwis->SlotID, gbwis->Quantity); + } + + GuildBankAck(); + + break; + } + + case GuildBankMergeStacks: + { + GuildBankWithdrawItem_Struct *gbwis = (GuildBankWithdrawItem_Struct*)app->pBuffer; + + GuildBanks->MergeStacks(GuildID(), gbwis->SlotID); + + GuildBankAck(); + + break; + } + + default: + { + Message(13, "Unexpected GuildBank action."); + + _log(GUILDS__BANK_ERROR, "Received unexpected guild bank action code %i from %s", Action, GetName()); + } + } +} + +void Client::Handle_OP_GuildCreate(const EQApplicationPacket *app) +{ + if (IsInAGuild()) + { + Message(clientMessageError, "You are already in a guild!"); + return; + } + + if (!RuleB(Guild, PlayerCreationAllowed)) + { + Message(clientMessageError, "This feature is disabled on this server. Contact a GM or post on your server message boards to create a guild."); + return; + } + + if ((Admin() < RuleI(Guild, PlayerCreationRequiredStatus)) || + (GetLevel() < RuleI(Guild, PlayerCreationRequiredLevel)) || + (database.GetTotalTimeEntitledOnAccount(AccountID()) < (unsigned int)RuleI(Guild, PlayerCreationRequiredTime))) + { + Message(clientMessageError, "Your status, level or time playing on this account are insufficient to use this feature."); + return; + } + + // The Underfoot client Guild Creation window will only allow a guild name of <= around 30 characters, but the packet is 64 bytes. Sanity check the + // name anway. + // + + char *GuildName = (char *)app->pBuffer; +#ifdef DARWIN +#if __DARWIN_C_LEVEL < 200809L + if (strlen(GuildName) > 60) +#else + if (strnlen(GuildName, 64) > 60) +#endif // __DARWIN_C_LEVEL +#else + if (strnlen(GuildName, 64) > 60) +#endif // DARWIN + { + Message(clientMessageError, "Guild name too long."); + return; + } + + for (unsigned int i = 0; i < strlen(GuildName); ++i) + { + if (!isalpha(GuildName[i]) && (GuildName[i] != ' ')) + { + Message(clientMessageError, "Invalid character in Guild name."); + return; + } + } + + int32 GuildCount = guild_mgr.DoesAccountContainAGuildLeader(AccountID()); + + if (GuildCount >= RuleI(Guild, PlayerCreationLimit)) + { + Message(clientMessageError, "You cannot create this guild because this account may only be leader of %i guilds.", RuleI(Guild, PlayerCreationLimit)); + return; + } + + if (guild_mgr.GetGuildIDByName(GuildName) != GUILD_NONE) + { + Message_StringID(clientMessageError, GUILD_NAME_IN_USE); + return; + } + + uint32 NewGuildID = guild_mgr.CreateGuild(GuildName, CharacterID()); + + _log(GUILDS__ACTIONS, "%s: Creating guild %s with leader %d via UF+ GUI. It was given id %lu.", GetName(), + GuildName, CharacterID(), (unsigned long)NewGuildID); + + if (NewGuildID == GUILD_NONE) + Message(clientMessageError, "Guild creation failed."); + else + { + if (!guild_mgr.SetGuild(CharacterID(), NewGuildID, GUILD_LEADER)) + Message(clientMessageError, "Unable to set guild leader's guild in the database. Contact a GM."); + else + { + Message(clientMessageYellow, "You are now the leader of %s", GuildName); + + if (zone->GetZoneID() == RuleI(World, GuildBankZoneID) && GuildBanks) + GuildBanks->SendGuildBank(this); + SendGuildRanks(); + } + } +} + +void Client::Handle_OP_GuildDelete(const EQApplicationPacket *app) +{ + mlog(GUILDS__IN_PACKETS, "Received OP_GuildDelete"); + mpkt(GUILDS__IN_PACKET_TRACE, app); + + if (!IsInAGuild() || !guild_mgr.IsGuildLeader(GuildID(), CharacterID())) + Message(0, "You are not a guild leader or not in a guild."); + else { + mlog(GUILDS__ACTIONS, "Deleting guild %s (%d)", guild_mgr.GetGuildName(GuildID()), GuildID()); + if (!guild_mgr.DeleteGuild(GuildID())) + Message(0, "Guild delete failed."); + else { + Message(0, "Guild successfully deleted."); + } + } +} + +void Client::Handle_OP_GuildDemote(const EQApplicationPacket *app) +{ + mlog(GUILDS__IN_PACKETS, "Received OP_GuildDemote"); + mpkt(GUILDS__IN_PACKET_TRACE, app); + + if (app->size != sizeof(GuildDemoteStruct)) { + mlog(GUILDS__ERROR, "Error: app size of %i != size of GuildDemoteStruct of %i\n", app->size, sizeof(GuildDemoteStruct)); + return; + } + + if (!IsInAGuild()) + Message(0, "Error: You arent in a guild!"); + else if (!guild_mgr.CheckPermission(GuildID(), GuildRank(), GUILD_DEMOTE)) + Message(0, "You dont have permission to invite."); + else if (!worldserver.Connected()) + Message(0, "Error: World server disconnected"); + else { + GuildDemoteStruct* demote = (GuildDemoteStruct*)app->pBuffer; + + CharGuildInfo gci; + if (!guild_mgr.GetCharInfo(demote->target, gci)) { + Message(0, "Unable to find '%s'", demote->target); + return; + } + if (gci.guild_id != GuildID()) { + Message(0, "You aren't in the same guild, what do you think you are doing?"); + return; + } + + if (gci.rank < 1) { + Message(0, "%s cannot be demoted any further!", demote->target); + return; + } + uint8 rank = gci.rank - 1; + + + mlog(GUILDS__ACTIONS, "Demoting %s (%d) from rank %s (%d) to %s (%d) in %s (%d)", + demote->target, gci.char_id, + guild_mgr.GetRankName(GuildID(), gci.rank), gci.rank, + guild_mgr.GetRankName(GuildID(), rank), rank, + guild_mgr.GetGuildName(GuildID()), GuildID()); + + if (!guild_mgr.SetGuildRank(gci.char_id, rank)) { + Message(13, "Error while setting rank %d on '%s'.", rank, demote->target); + return; + } + Message(0, "Successfully demoted %s to rank %d", demote->target, rank); + } + // SendGuildMembers(GuildID(), true); + return; +} + +void Client::Handle_OP_GuildInvite(const EQApplicationPacket *app) +{ + mlog(GUILDS__IN_PACKETS, "Received OP_GuildInvite"); + mpkt(GUILDS__IN_PACKET_TRACE, app); + + if (app->size != sizeof(GuildCommand_Struct)) { + std::cout << "Wrong size: OP_GuildInvite, size=" << app->size << ", expected " << sizeof(GuildCommand_Struct) << std::endl; + return; + } + + GuildCommand_Struct* gc = (GuildCommand_Struct*)app->pBuffer; + + if (!IsInAGuild()) + Message(0, "Error: You are not in a guild!"); + else if (gc->officer > GUILD_MAX_RANK) + Message(13, "Invalid rank."); + else if (!worldserver.Connected()) + Message(0, "Error: World server disconnected"); + else { + + //ok, the invite is also used for changing rank as well. + Mob* invitee = entity_list.GetMob(gc->othername); + + if (!invitee) { + Message(13, "Prospective guild member %s must be in zone to preform guild operations on them.", gc->othername); + return; + } + + if (invitee->IsClient()) { + Client* client = invitee->CastToClient(); + + //ok, figure out what they are trying to do. + if (client->GuildID() == GuildID()) { + //they are already in this guild, must be a promotion or demotion + if (gc->officer < client->GuildRank()) { + //demotion + if (!guild_mgr.CheckPermission(GuildID(), GuildRank(), GUILD_DEMOTE)) { + Message(13, "You dont have permission to demote."); + return; + } + + //we could send this to the member and prompt them to see if they want to + //be demoted (I guess), but I dont see a point in that. + + mlog(GUILDS__ACTIONS, "%s (%d) is demoting %s (%d) to rank %d in guild %s (%d)", + GetName(), CharacterID(), + client->GetName(), client->CharacterID(), + gc->officer, + guild_mgr.GetGuildName(GuildID()), GuildID()); + + if (!guild_mgr.SetGuildRank(client->CharacterID(), gc->officer)) { + Message(13, "There was an error during the demotion, DB may now be inconsistent."); + return; + } + + } + else if (gc->officer > client->GuildRank()) { + //promotion + if (!guild_mgr.CheckPermission(GuildID(), GuildRank(), GUILD_PROMOTE)) { + Message(13, "You dont have permission to demote."); + return; + } + + mlog(GUILDS__ACTIONS, "%s (%d) is asking to promote %s (%d) to rank %d in guild %s (%d)", + GetName(), CharacterID(), + client->GetName(), client->CharacterID(), + gc->officer, + guild_mgr.GetGuildName(GuildID()), GuildID()); + + //record the promotion with guild manager so we know its valid when we get the reply + guild_mgr.RecordInvite(client->CharacterID(), GuildID(), gc->officer); + + if (gc->guildeqid == 0) + gc->guildeqid = GuildID(); + + mlog(GUILDS__OUT_PACKETS, "Sending OP_GuildInvite for promotion to %s, length %d", client->GetName(), app->size); + mpkt(GUILDS__OUT_PACKET_TRACE, app); + client->QueuePacket(app); + + } + else { + Message(13, "That member is already that rank."); + return; + } + } + else if (!client->IsInAGuild()) { + //they are not in this or any other guild, this is an invite + // + if (client->GetPendingGuildInvitation()) + { + Message(13, "That person is already considering a guild invitation."); + return; + } + + if (!guild_mgr.CheckPermission(GuildID(), GuildRank(), GUILD_INVITE)) { + Message(13, "You dont have permission to invite."); + return; + } + + mlog(GUILDS__ACTIONS, "Inviting %s (%d) into guild %s (%d)", + client->GetName(), client->CharacterID(), + guild_mgr.GetGuildName(GuildID()), GuildID()); + + //record the invite with guild manager so we know its valid when we get the reply + guild_mgr.RecordInvite(client->CharacterID(), GuildID(), gc->officer); + + if (gc->guildeqid == 0) + gc->guildeqid = GuildID(); + + mlog(GUILDS__OUT_PACKETS, "Sending OP_GuildInvite for invite to %s, length %d", client->GetName(), app->size); + mpkt(GUILDS__OUT_PACKET_TRACE, app); + client->SetPendingGuildInvitation(true); + client->QueuePacket(app); + + } + else { + //they are in some other guild + Message(13, "Player is in a guild."); + return; + } + } +#ifdef BOTS + else if (invitee->IsBot()) { + // The guild system is too tightly coupled with the character_data table so we have to avoid using much of the system + Bot::ProcessGuildInvite(this, invitee->CastToBot()); + return; + } +#endif + } +} + +void Client::Handle_OP_GuildInviteAccept(const EQApplicationPacket *app) +{ + mlog(GUILDS__IN_PACKETS, "Received OP_GuildInviteAccept"); + mpkt(GUILDS__IN_PACKET_TRACE, app); + + SetPendingGuildInvitation(false); + + if (app->size != sizeof(GuildInviteAccept_Struct)) { + std::cout << "Wrong size: OP_GuildInviteAccept, size=" << app->size << ", expected " << sizeof(GuildJoin_Struct) << std::endl; + return; + } + + GuildInviteAccept_Struct* gj = (GuildInviteAccept_Struct*)app->pBuffer; + + if (GetClientVersion() >= EQClientRoF) + { + if (gj->response > 9) + { + //dont care if the check fails (since we dont know the rank), just want to clear the entry. + guild_mgr.VerifyAndClearInvite(CharacterID(), gj->guildeqid, gj->response); + worldserver.SendEmoteMessage(gj->inviter, 0, 0, "%s has declined to join the guild.", this->GetName()); + return; + } + } + if (gj->response == 5 || gj->response == 4) { + //dont care if the check fails (since we dont know the rank), just want to clear the entry. + guild_mgr.VerifyAndClearInvite(CharacterID(), gj->guildeqid, gj->response); + + worldserver.SendEmoteMessage(gj->inviter, 0, 0, "%s has declined to join the guild.", this->GetName()); + + return; + } + + //uint32 tmpeq = gj->guildeqid; + if (IsInAGuild() && gj->response == GuildRank()) + Message(0, "Error: You're already in a guild!"); + else if (!worldserver.Connected()) + Message(0, "Error: World server disconnected"); + else { + mlog(GUILDS__ACTIONS, "Guild Invite Accept: guild %d, response %d, inviter %s, person %s", + gj->guildeqid, gj->response, gj->inviter, gj->newmember); + + //we dont really care a lot about what this packet means, as long as + //it has been authorized with the guild manager + if (!guild_mgr.VerifyAndClearInvite(CharacterID(), gj->guildeqid, gj->response)) { + worldserver.SendEmoteMessage(gj->inviter, 0, 0, "%s has sent an invalid response to your invite!", GetName()); + Message(13, "Invalid invite response packet!"); + return; + } + + if (gj->guildeqid == GuildID()) { + //only need to change rank. + + mlog(GUILDS__ACTIONS, "Changing guild rank of %s (%d) to rank %d in guild %s (%d)", + GetName(), CharacterID(), + gj->response, + guild_mgr.GetGuildName(GuildID()), GuildID()); + + if (!guild_mgr.SetGuildRank(CharacterID(), gj->response)) { + Message(13, "There was an error during the rank change, DB may now be inconsistent."); + return; + } + } + else { + + mlog(GUILDS__ACTIONS, "Adding %s (%d) to guild %s (%d) at rank %d", + GetName(), CharacterID(), + guild_mgr.GetGuildName(gj->guildeqid), gj->guildeqid, + gj->response); + + //change guild and rank + + uint32 guildrank = gj->response; + + if (GetClientVersion() == EQClientRoF) + { + if (gj->response == 8) + { + guildrank = 0; + } + } + + if (!guild_mgr.SetGuild(CharacterID(), gj->guildeqid, guildrank)) { + Message(13, "There was an error during the invite, DB may now be inconsistent."); + return; + } + if (zone->GetZoneID() == RuleI(World, GuildBankZoneID) && GuildBanks) + GuildBanks->SendGuildBank(this); + } + } +} + +void Client::Handle_OP_GuildLeader(const EQApplicationPacket *app) +{ + mlog(GUILDS__IN_PACKETS, "Received OP_GuildLeader"); + mpkt(GUILDS__IN_PACKET_TRACE, app); + + if (app->size < 2) { + mlog(GUILDS__ERROR, "Invalid length %d on OP_GuildLeader", app->size); + return; + } + + app->pBuffer[app->size - 1] = 0; + GuildMakeLeader* gml = (GuildMakeLeader*)app->pBuffer; + if (!IsInAGuild()) + Message(0, "Error: You arent in a guild!"); + else if (GuildRank() != GUILD_LEADER) + Message(0, "Error: You arent the guild leader!"); + else if (!worldserver.Connected()) + Message(0, "Error: World server disconnected"); + else { + + //NOTE: we could do cross-zone lookups here... + + Client* newleader = entity_list.GetClientByName(gml->target); + if (newleader) { + + mlog(GUILDS__ACTIONS, "Transfering leadership of %s (%d) to %s (%d)", + guild_mgr.GetGuildName(GuildID()), GuildID(), + newleader->GetName(), newleader->CharacterID()); + + if (guild_mgr.SetGuildLeader(GuildID(), newleader->CharacterID())){ + Message(0, "Successfully Transfered Leadership to %s.", gml->target); + newleader->Message(15, "%s has transfered the guild leadership into your hands.", GetName()); + } + else + Message(0, "Could not change leadership at this time."); + } + else + Message(0, "Failed to change leader, could not find target."); + } + // SendGuildMembers(GuildID(), true); + return; +} + +void Client::Handle_OP_GuildManageBanker(const EQApplicationPacket *app) +{ + + mlog(GUILDS__IN_PACKETS, "Got OP_GuildManageBanker of len %d", app->size); + mpkt(GUILDS__IN_PACKET_TRACE, app); + if (app->size != sizeof(GuildManageBanker_Struct)) { + mlog(GUILDS__ERROR, "Error: app size of %i != size of OP_GuildManageBanker of %i\n", app->size, sizeof(GuildManageBanker_Struct)); + return; + } + GuildManageBanker_Struct* gmb = (GuildManageBanker_Struct*)app->pBuffer; + + if (!IsInAGuild()) { + Message(13, "Your not in a guild!"); + return; + } + + CharGuildInfo gci; + + if (!guild_mgr.GetCharInfo(gmb->member, gci)) + { + Message(0, "Unable to find '%s'", gmb->member); + return; + } + bool IsCurrentlyABanker = guild_mgr.GetBankerFlag(gci.char_id); + + bool IsCurrentlyAnAlt = guild_mgr.GetAltFlag(gci.char_id); + + bool NewBankerStatus = gmb->enabled & 0x01; + + bool NewAltStatus = gmb->enabled & 0x02; + + if ((IsCurrentlyABanker != NewBankerStatus) && !guild_mgr.IsGuildLeader(GuildID(), CharacterID())) + { + Message(13, "Only the guild leader can assign guild bankers!"); + return; + } + + if (IsCurrentlyAnAlt != NewAltStatus) + { + bool IsAllowed = !strncasecmp(GetName(), gmb->member, strlen(GetName())) || (GuildRank() >= GUILD_OFFICER); + + if (!IsAllowed) + { + Message(13, "You are not allowed to change the alt status of %s", gmb->member); + return; + } + } + + if (gci.guild_id != GuildID()) { + Message(0, "You aren't in the same guild, what do you think you are doing?"); + return; + } + + if (IsCurrentlyABanker != NewBankerStatus) + { + if (!guild_mgr.SetBankerFlag(gci.char_id, NewBankerStatus)) { + Message(13, "Error setting guild banker flag."); + return; + } + + if (NewBankerStatus) + Message(0, "%s has been made a guild banker.", gmb->member); + else + Message(0, "%s is no longer a guild banker.", gmb->member); + } + if (IsCurrentlyAnAlt != NewAltStatus) + { + if (!guild_mgr.SetAltFlag(gci.char_id, NewAltStatus)) { + Message(13, "Error setting guild alt flag."); + return; + } + + if (NewAltStatus) + Message(0, "%s has been marked as an alt.", gmb->member); + else + Message(0, "%s is no longer marked as an alt.", gmb->member); + } +} + +void Client::Handle_OP_GuildPeace(const EQApplicationPacket *app) +{ + mlog(GUILDS__IN_PACKETS, "Got OP_GuildPeace of len %d", app->size); + mpkt(GUILDS__IN_PACKET_TRACE, app); + return; +} + +void Client::Handle_OP_GuildPromote(const EQApplicationPacket *app) +{ + mlog(GUILDS__IN_PACKETS, "Received OP_GuildPromote"); + mpkt(GUILDS__IN_PACKET_TRACE, app); + + if (app->size != sizeof(GuildPromoteStruct)) { + mlog(GUILDS__ERROR, "Error: app size of %i != size of GuildDemoteStruct of %i\n", app->size, sizeof(GuildPromoteStruct)); + return; + } + + if (!IsInAGuild()) + Message(0, "Error: You arent in a guild!"); + else if (!guild_mgr.CheckPermission(GuildID(), GuildRank(), GUILD_PROMOTE)) + Message(0, "You dont have permission to invite."); + else if (!worldserver.Connected()) + Message(0, "Error: World server disconnected"); + else { + GuildPromoteStruct* promote = (GuildPromoteStruct*)app->pBuffer; + + CharGuildInfo gci; + if (!guild_mgr.GetCharInfo(promote->target, gci)) { + Message(0, "Unable to find '%s'", promote->target); + return; + } + if (gci.guild_id != GuildID()) { + Message(0, "You aren't in the same guild, what do you think you are doing?"); + return; + } + + uint8 rank = gci.rank + 1; + + if (rank > GUILD_OFFICER) + return; + + + mlog(GUILDS__ACTIONS, "Promoting %s (%d) from rank %s (%d) to %s (%d) in %s (%d)", + promote->target, gci.char_id, + guild_mgr.GetRankName(GuildID(), gci.rank), gci.rank, + guild_mgr.GetRankName(GuildID(), rank), rank, + guild_mgr.GetGuildName(GuildID()), GuildID()); + + if (!guild_mgr.SetGuildRank(gci.char_id, rank)) { + Message(13, "Error while setting rank %d on '%s'.", rank, promote->target); + return; + } + Message(0, "Successfully promoted %s to rank %d", promote->target, rank); + } + return; +} + +void Client::Handle_OP_GuildPublicNote(const EQApplicationPacket *app) +{ + mlog(GUILDS__IN_PACKETS, "Received OP_GuildPublicNote"); + mpkt(GUILDS__IN_PACKET_TRACE, app); + + if (app->size < sizeof(GuildUpdate_PublicNote)) { + // client calls for a motd on login even if they arent in a guild + printf("Error: app size of %i < size of OP_GuildPublicNote of %zu\n", app->size, sizeof(GuildUpdate_PublicNote)); + return; + } + GuildUpdate_PublicNote* gpn = (GuildUpdate_PublicNote*)app->pBuffer; + + CharGuildInfo gci; + if (!guild_mgr.GetCharInfo(gpn->target, gci)) { + Message(0, "Unable to find '%s'", gpn->target); + return; + } + if (gci.guild_id != GuildID()) { + Message(0, "You aren't in the same guild, what do you think you are doing?"); + return; + } + + mlog(GUILDS__ACTIONS, "Setting public note on %s (%d) in guild %s (%d) to: %s", + gpn->target, gci.char_id, + guild_mgr.GetGuildName(GuildID()), GuildID(), + gpn->note); + + if (!guild_mgr.SetPublicNote(gci.char_id, gpn->note)) { + Message(13, "Failed to set public note on %s", gpn->target); + } + else { + Message(0, "Successfully changed public note on %s", gpn->target); + } + // SendGuildMembers(GuildID(), true); + return; +} + +void Client::Handle_OP_GuildRemove(const EQApplicationPacket *app) +{ + mlog(GUILDS__IN_PACKETS, "Received OP_GuildRemove"); + mpkt(GUILDS__IN_PACKET_TRACE, app); + + if (app->size != sizeof(GuildCommand_Struct)) { + std::cout << "Wrong size: OP_GuildRemove, size=" << app->size << ", expected " << sizeof(GuildCommand_Struct) << std::endl; + return; + } + GuildCommand_Struct* gc = (GuildCommand_Struct*)app->pBuffer; + if (!IsInAGuild()) + Message(0, "Error: You arent in a guild!"); + // we can always remove ourself, otherwise, our rank needs remove permissions + else if (strcasecmp(gc->othername, GetName()) != 0 && + !guild_mgr.CheckPermission(GuildID(), GuildRank(), GUILD_REMOVE)) + Message(0, "You dont have permission to remove guild members."); + else if (!worldserver.Connected()) + Message(0, "Error: World server disconnected"); + else { +#ifdef BOTS + if (Bot::ProcessGuildRemoval(this, gc->othername)) + return; +#endif + uint32 char_id; + Client* client = entity_list.GetClientByName(gc->othername); + + if (client) { + if (!client->IsInGuild(GuildID())) { + Message(0, "You aren't in the same guild, what do you think you are doing?"); + return; + } + char_id = client->CharacterID(); + + mlog(GUILDS__ACTIONS, "Removing %s (%d) from guild %s (%d)", + client->GetName(), client->CharacterID(), + guild_mgr.GetGuildName(GuildID()), GuildID()); + } + else { + CharGuildInfo gci; + if (!guild_mgr.GetCharInfo(gc->othername, gci)) { + Message(0, "Unable to find '%s'", gc->othername); + return; + } + if (gci.guild_id != GuildID()) { + Message(0, "You aren't in the same guild, what do you think you are doing?"); + return; + } + char_id = gci.char_id; + + mlog(GUILDS__ACTIONS, "Removing remote/offline %s (%d) into guild %s (%d)", + gci.char_name.c_str(), gci.char_id, + guild_mgr.GetGuildName(GuildID()), GuildID()); + } + + if (!guild_mgr.SetGuild(char_id, GUILD_NONE, 0)) { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_GuildManageRemove, sizeof(GuildManageRemove_Struct)); + GuildManageRemove_Struct* gm = (GuildManageRemove_Struct*)outapp->pBuffer; + gm->guildeqid = GuildID(); + strcpy(gm->member, gc->othername); + Message(0, "%s successfully removed from your guild.", gc->othername); + entity_list.QueueClientsGuild(this, outapp, false, GuildID()); + safe_delete(outapp); + } + else + Message(0, "Unable to remove %s from your guild.", gc->othername); + } + // SendGuildMembers(GuildID(), true); + return; +} + +void Client::Handle_OP_GuildStatus(const EQApplicationPacket *app) +{ + if (app->size != sizeof(GuildStatus_Struct)) + { + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_GuildStatus expected %i got %i", + sizeof(GuildStatus_Struct), app->size); + + DumpPacket(app); + + return; + } + GuildStatus_Struct *gss = (GuildStatus_Struct*)app->pBuffer; + + Client *c = entity_list.GetClientByName(gss->Name); + + if (!c) + { + Message_StringID(clientMessageWhite, TARGET_PLAYER_FOR_GUILD_STATUS); + return; + } + + uint32 TargetGuildID = c->GuildID(); + + if (TargetGuildID == GUILD_NONE) + { + Message_StringID(clientMessageWhite, NOT_IN_A_GUILD, c->GetName()); + return; + } + + const char *GuildName = guild_mgr.GetGuildName(TargetGuildID); + + if (!GuildName) + return; + + bool IsLeader = guild_mgr.CheckPermission(TargetGuildID, c->GuildRank(), GUILD_PROMOTE); + bool IsOfficer = guild_mgr.CheckPermission(TargetGuildID, c->GuildRank(), GUILD_INVITE); + + if ((TargetGuildID == GuildID()) && (c != this)) + { + if (IsLeader) + Message_StringID(clientMessageWhite, LEADER_OF_YOUR_GUILD, c->GetName()); + else if (IsOfficer) + Message_StringID(clientMessageWhite, OFFICER_OF_YOUR_GUILD, c->GetName()); + else + Message_StringID(clientMessageWhite, MEMBER_OF_YOUR_GUILD, c->GetName()); + + return; + } + + if (IsLeader) + Message_StringID(clientMessageWhite, LEADER_OF_X_GUILD, c->GetName(), GuildName); + else if (IsOfficer) + Message_StringID(clientMessageWhite, OFFICER_OF_X_GUILD, c->GetName(), GuildName); + else + Message_StringID(clientMessageWhite, MEMBER_OF_X_GUILD, c->GetName(), GuildName); +} + +void Client::Handle_OP_GuildUpdateURLAndChannel(const EQApplicationPacket *app) +{ + if (app->size != sizeof(GuildUpdateURLAndChannel_Struct)) + { + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_GuildUpdateURLAndChannel expected %i got %i", + sizeof(GuildUpdateURLAndChannel_Struct), app->size); + + DumpPacket(app); + + return; + } + + GuildUpdateURLAndChannel_Struct *guuacs = (GuildUpdateURLAndChannel_Struct*)app->pBuffer; + + if (!IsInAGuild()) + return; + + if (!guild_mgr.IsGuildLeader(GuildID(), CharacterID())) + { + Message(13, "Only the guild leader can change the Channel or URL.!"); + return; + } + + if (guuacs->Action == 0) + guild_mgr.SetGuildURL(GuildID(), guuacs->Text); + else + guild_mgr.SetGuildChannel(GuildID(), guuacs->Text); + +} + +void Client::Handle_OP_GuildWar(const EQApplicationPacket *app) +{ + mlog(GUILDS__IN_PACKETS, "Got OP_GuildWar of len %d", app->size); + mpkt(GUILDS__IN_PACKET_TRACE, app); + return; +} + +void Client::Handle_OP_Heartbeat(const EQApplicationPacket *app) +{ + return; +} + +void Client::Handle_OP_Hide(const EQApplicationPacket *app) +{ + if (!HasSkill(SkillHide) && GetSkill(SkillHide) == 0) + { + //Can not be able to train hide but still have it from racial though + return; //You cannot hide if you do not have hide + } + + if (!p_timers.Expired(&database, pTimerHide, false)) { + Message(13, "Ability recovery time not yet met."); + return; + } + int reuse = HideReuseTime - GetAA(209); + p_timers.Start(pTimerHide, reuse - 1); + + float hidechance = ((GetSkill(SkillHide) / 250.0f) + .25) * 100; + float random = zone->random.Real(0, 100); + CheckIncreaseSkill(SkillHide, nullptr, 5); + if (random < hidechance) { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct)); + SpawnAppearance_Struct* sa_out = (SpawnAppearance_Struct*)outapp->pBuffer; + sa_out->spawn_id = GetID(); + sa_out->type = 0x03; + sa_out->parameter = 1; + entity_list.QueueClients(this, outapp, true); + safe_delete(outapp); + if (GetAA(aaShroudofStealth)){ + improved_hidden = true; + hidden = true; + } + else + hidden = true; + } + if (GetClass() == ROGUE){ + EQApplicationPacket *outapp = new EQApplicationPacket(OP_SimpleMessage, sizeof(SimpleMessage_Struct)); + SimpleMessage_Struct *msg = (SimpleMessage_Struct *)outapp->pBuffer; + msg->color = 0x010E; + Mob *evadetar = GetTarget(); + if (!auto_attack && (evadetar && evadetar->CheckAggro(this) + && evadetar->IsNPC())) { + if (zone->random.Int(0, 260) < (int)GetSkill(SkillHide)) { + msg->string_id = EVADE_SUCCESS; + RogueEvade(evadetar); + } + else { + msg->string_id = EVADE_FAIL; + } + } + else { + if (hidden){ + msg->string_id = HIDE_SUCCESS; + } + else { + msg->string_id = HIDE_FAIL; + } + } + FastQueuePacket(&outapp); + } + return; +} + +void Client::Handle_OP_HideCorpse(const EQApplicationPacket *app) +{ + // New OPCode for SOD+ as /hidecorpse is handled serverside now. + // + if (app->size != sizeof(HideCorpse_Struct)) + { + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_HideCorpse expected %i got %i", + sizeof(HideCorpse_Struct), app->size); + + DumpPacket(app); + + return; + } + + HideCorpse_Struct *hcs = (HideCorpse_Struct*)app->pBuffer; + + if (hcs->Action == HideCorpseLooted) + return; + + if ((HideCorpseMode == HideCorpseNone) && (hcs->Action == HideCorpseNone)) + return; + + entity_list.HideCorpses(this, HideCorpseMode, hcs->Action); + + HideCorpseMode = hcs->Action; +} + +void Client::Handle_OP_Ignore(const EQApplicationPacket *app) +{ + return; +} + +void Client::Handle_OP_Illusion(const EQApplicationPacket *app) +{ + if (app->size != sizeof(Illusion_Struct)) { + LogFile->write(EQEMuLog::Error, "Received invalid sized OP_Illusion: got %d, expected %d", app->size, + sizeof(Illusion_Struct)); + DumpPacket(app); + return; + } + + if (!GetGM()) + { + database.SetMQDetectionFlag(this->AccountName(), this->GetName(), "OP_Illusion sent by non Game Master.", zone->GetShortName()); + return; + } + + Illusion_Struct* bnpc = (Illusion_Struct*)app->pBuffer; + //these need to be implemented + /* + texture = bnpc->texture; + helmtexture = bnpc->helmtexture; + luclinface = bnpc->luclinface; + */ + race = bnpc->race; + size = 0; + + entity_list.QueueClients(this, app); + return; +} + +void Client::Handle_OP_InspectAnswer(const EQApplicationPacket *app) +{ + + if (app->size != sizeof(InspectResponse_Struct)) { + LogFile->write(EQEMuLog::Error, "Wrong size: OP_InspectAnswer, size=%i, expected %i", app->size, sizeof(InspectResponse_Struct)); + return; + } + + //Fills the app sent from client. + EQApplicationPacket* outapp = app->Copy(); + InspectResponse_Struct* insr = (InspectResponse_Struct*)outapp->pBuffer; + Mob* tmp = entity_list.GetMob(insr->TargetID); + const Item_Struct* item = nullptr; + + int ornamentationAugtype = RuleI(Character, OrnamentationAugmentType); + for (int16 L = EmuConstants::EQUIPMENT_BEGIN; L <= MainWaist; L++) { + const ItemInst* inst = GetInv().GetItem(L); + item = inst ? inst->GetItem() : nullptr; + + if (item) { + if (inst && inst->GetOrnamentationAug(ornamentationAugtype)) { + const Item_Struct *aug_weap = inst->GetOrnamentationAug(ornamentationAugtype)->GetItem(); + strcpy(insr->itemnames[L], item->Name); + insr->itemicons[L] = aug_weap->Icon; + } + else if (inst->GetOrnamentationIcon() && inst->GetOrnamentationIDFile()) { + strcpy(insr->itemnames[L], item->Name); + insr->itemicons[L] = inst->GetOrnamentationIcon(); + } + else { + strcpy(insr->itemnames[L], item->Name); + insr->itemicons[L] = item->Icon; + } + } + else { insr->itemicons[L] = 0xFFFFFFFF; } + } + + const ItemInst* inst = GetInv().GetItem(MainAmmo); + item = inst ? inst->GetItem() : nullptr; + + if (item) { + // another one..I did these, didn't I!!? + strcpy(insr->itemnames[SoF::slots::MainAmmo], item->Name); + insr->itemicons[SoF::slots::MainAmmo] = item->Icon; + } + else { insr->itemicons[SoF::slots::MainAmmo] = 0xFFFFFFFF; } + + InspectMessage_Struct* newmessage = (InspectMessage_Struct*)insr->text; + InspectMessage_Struct& playermessage = this->GetInspectMessage(); + memcpy(&playermessage, newmessage, sizeof(InspectMessage_Struct)); + database.SaveCharacterInspectMessage(this->CharacterID(), &playermessage); + + if (tmp != 0 && tmp->IsClient()) { tmp->CastToClient()->QueuePacket(outapp); } // Send answer to requester + + return; +} + +void Client::Handle_OP_InspectMessageUpdate(const EQApplicationPacket *app) +{ + + if (app->size != sizeof(InspectMessage_Struct)) { + LogFile->write(EQEMuLog::Error, "Wrong size: OP_InspectMessageUpdate, size=%i, expected %i", app->size, sizeof(InspectMessage_Struct)); + return; + } + + InspectMessage_Struct* newmessage = (InspectMessage_Struct*)app->pBuffer; + InspectMessage_Struct& playermessage = this->GetInspectMessage(); + memcpy(&playermessage, newmessage, sizeof(InspectMessage_Struct)); + database.SaveCharacterInspectMessage(this->CharacterID(), &playermessage); +} + +void Client::Handle_OP_InspectRequest(const EQApplicationPacket *app) +{ + + if (app->size != sizeof(Inspect_Struct)) { + LogFile->write(EQEMuLog::Error, "Wrong size: OP_InspectRequest, size=%i, expected %i", app->size, sizeof(Inspect_Struct)); + return; + } + + Inspect_Struct* ins = (Inspect_Struct*)app->pBuffer; + Mob* tmp = entity_list.GetMob(ins->TargetID); + + if (tmp != 0 && tmp->IsClient()) { + if (tmp->CastToClient()->GetClientVersion() < EQClientSoF) { tmp->CastToClient()->QueuePacket(app); } // Send request to target + // Inspecting an SoF or later client will make the server handle the request + else { ProcessInspectRequest(tmp->CastToClient(), this); } + } + +#ifdef BOTS + if (tmp != 0 && tmp->IsBot()) { Bot::ProcessBotInspectionRequest(tmp->CastToBot(), this); } +#endif + + return; +} + +void Client::Handle_OP_InstillDoubt(const EQApplicationPacket *app) +{ + //packet is empty as of 12/14/04 + + if (!p_timers.Expired(&database, pTimerInstillDoubt, false)) { + Message(13, "Ability recovery time not yet met."); + return; + } + p_timers.Start(pTimerInstillDoubt, InstillDoubtReuseTime - 1); + + InstillDoubt(GetTarget()); + return; +} + +void Client::Handle_OP_ItemLinkClick(const EQApplicationPacket *app) +{ + if (app->size != sizeof(ItemViewRequest_Struct)){ + LogFile->write(EQEMuLog::Error, "Wrong size on OP_ItemLinkClick. Got: %i, Expected: %i", app->size, sizeof(ItemViewRequest_Struct)); + DumpPacket(app); + return; + } + + DumpPacket(app); + ItemViewRequest_Struct* ivrs = (ItemViewRequest_Struct*)app->pBuffer; + + //todo: verify ivrs->link_hash based on a rule, in case we don't care about people being able to sniff data from the item DB + + const Item_Struct* item = database.GetItem(ivrs->item_id); + if (!item) { + if (ivrs->item_id > 500000) + { + std::string response = ""; + int sayid = ivrs->item_id - 500000; + bool silentsaylink = false; + + if (sayid > 250000) //Silent Saylink + { + sayid = sayid - 250000; + silentsaylink = true; + } + + if (sayid > 0) + { + + std::string query = StringFormat("SELECT `phrase` FROM saylink WHERE `id` = '%i'", sayid); + auto results = database.QueryDatabase(query); + if (!results.Success()) { + Message(13, "Error: The saylink (%s) was not found in the database.", response.c_str()); + return; + } + + if (results.RowCount() != 1) { + Message(13, "Error: The saylink (%s) was not found in the database.", response.c_str()); + return; + } + + auto row = results.begin(); + response = row[0]; + + } + + if ((response).size() > 0) + { + if (!mod_saylink(response, silentsaylink)) { return; } + + if (GetTarget() && GetTarget()->IsNPC()) + { + if (silentsaylink) + { + parse->EventNPC(EVENT_SAY, GetTarget()->CastToNPC(), this, response.c_str(), 0); + parse->EventPlayer(EVENT_SAY, this, response.c_str(), 0); + } + else + { + Message(7, "You say, '%s'", response.c_str()); + ChannelMessageReceived(8, 0, 100, response.c_str()); + } + return; + } + else + { + if (silentsaylink) + { + parse->EventPlayer(EVENT_SAY, this, response.c_str(), 0); + } + else + { + Message(7, "You say, '%s'", response.c_str()); + ChannelMessageReceived(8, 0, 100, response.c_str()); + } + return; + } + } + else + { + Message(13, "Error: Say Link not found or is too long."); + return; + } + } + else { + Message(13, "Error: The item for the link you have clicked on does not exist!"); + return; + } + + } + + ItemInst* inst = database.CreateItem(item, item->MaxCharges, ivrs->augments[0], ivrs->augments[1], ivrs->augments[2], ivrs->augments[3], ivrs->augments[4]); + if (inst) { + SendItemPacket(0, inst, ItemPacketViewLink); + safe_delete(inst); + } + return; +} + +void Client::Handle_OP_ItemLinkResponse(const EQApplicationPacket *app) +{ + if (app->size != sizeof(LDONItemViewRequest_Struct)) { + LogFile->write(EQEMuLog::Error, "OP size error: OP_ItemLinkResponse expected:%i got:%i", sizeof(LDONItemViewRequest_Struct), app->size); + return; + } + LDONItemViewRequest_Struct* item = (LDONItemViewRequest_Struct*)app->pBuffer; + ItemInst* inst = database.CreateItem(item->item_id); + if (inst) { + SendItemPacket(0, inst, ItemPacketViewLink); + safe_delete(inst); + } + return; +} + +void Client::Handle_OP_ItemName(const EQApplicationPacket *app) +{ + if (app->size != sizeof(ItemNamePacket_Struct)) { + LogFile->write(EQEMuLog::Error, "Invalid size for ItemNamePacket_Struct: Expected: %i, Got: %i", + sizeof(ItemNamePacket_Struct), app->size); + return; + } + ItemNamePacket_Struct *p = (ItemNamePacket_Struct*)app->pBuffer; + const Item_Struct *item = 0; + if ((item = database.GetItem(p->item_id)) != nullptr) { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_ItemName, sizeof(ItemNamePacket_Struct)); + p = (ItemNamePacket_Struct*)outapp->pBuffer; + memset(p, 0, sizeof(ItemNamePacket_Struct)); + strcpy(p->name, item->Name); + FastQueuePacket(&outapp); + } + return; +} + +void Client::Handle_OP_ItemPreview(const EQApplicationPacket *app) +{ + VERIFY_PACKET_LENGTH(OP_ItemPreview, app, ItemPreview_Struct); + ItemPreview_Struct *ips = (ItemPreview_Struct *)app->pBuffer; + + const Item_Struct* item = database.GetItem(ips->itemid); + + if (item) { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_ItemPreview, strlen(item->Name) + strlen(item->Lore) + strlen(item->IDFile) + 898); + + int spacer; + for (spacer = 0; spacer < 16; spacer++) { + outapp->WriteUInt8(48); + } + outapp->WriteUInt16(256); + for (spacer = 0; spacer < 7; spacer++) { + outapp->WriteUInt8(0); + } + for (spacer = 0; spacer < 7; spacer++) { + outapp->WriteUInt8(255); + } + outapp->WriteUInt32(0); + outapp->WriteUInt32(1); + outapp->WriteUInt32(0); + outapp->WriteUInt8(237); // Seems to be some kind of counter? increases by 1 for each preview that you do. + outapp->WriteUInt16(2041); //F907 + for (spacer = 0; spacer < 36; spacer++) { + outapp->WriteUInt8(0); + } + for (spacer = 0; spacer < 4; spacer++) { + outapp->WriteUInt8(255); + } + for (spacer = 0; spacer < 9; spacer++) { + outapp->WriteUInt8(0); + } + for (spacer = 0; spacer < 5; spacer++) { + outapp->WriteUInt8(255); + } + for (spacer = 0; spacer < 5; spacer++) { + outapp->WriteUInt8(0); + } + outapp->WriteString(item->Name); + outapp->WriteString(item->Lore); + outapp->WriteUInt8(0); + outapp->WriteUInt32(ips->itemid); + outapp->WriteUInt32(item->Weight); + outapp->WriteUInt8(item->NoRent); + outapp->WriteUInt8(item->NoDrop); + outapp->WriteUInt8(item->Attuneable); + outapp->WriteUInt8(item->Size); + outapp->WriteUInt32(item->Slots); + outapp->WriteUInt32(item->Price); + outapp->WriteUInt32(item->Icon); + outapp->WriteUInt8(0); //Unknown? + outapp->WriteUInt8(0); //Placeable flag? + outapp->WriteUInt32(item->BenefitFlag); + outapp->WriteUInt8(item->Tradeskills); + outapp->WriteUInt8(item->CR); + outapp->WriteUInt8(item->DR); + outapp->WriteUInt8(item->PR); + outapp->WriteUInt8(item->MR); + outapp->WriteUInt8(item->FR); + outapp->WriteUInt8(item->AStr); + outapp->WriteUInt8(item->ASta); + outapp->WriteUInt8(item->AAgi); + outapp->WriteUInt8(item->ADex); + outapp->WriteUInt8(item->ACha); + outapp->WriteUInt8(item->AInt); + outapp->WriteUInt8(item->AWis); + outapp->WriteSInt32(item->HP); + outapp->WriteSInt32(item->Mana); + outapp->WriteSInt32(item->Endur); + outapp->WriteSInt32(item->AC); + outapp->WriteUInt32(item->Regen); + outapp->WriteUInt32(item->ManaRegen); + outapp->WriteSInt32(item->EnduranceRegen); + outapp->WriteUInt32(item->Classes); + outapp->WriteUInt32(item->Races); + outapp->WriteUInt32(item->Deity); + outapp->WriteUInt32(item->SkillModValue); + outapp->WriteUInt32(0); //SkillModValue + outapp->WriteUInt32(item->SkillModType); + outapp->WriteUInt32(0); //SkillModExtra + outapp->WriteUInt32(item->BaneDmgRace); + outapp->WriteUInt32(item->BaneDmgBody); + outapp->WriteUInt32(item->BaneDmgRaceAmt); + outapp->WriteUInt32(item->BaneDmgAmt); + outapp->WriteUInt8(item->Magic); + outapp->WriteUInt32(item->CastTime_); + outapp->WriteUInt32(item->ReqLevel); + outapp->WriteUInt32(item->RecLevel); + outapp->WriteUInt32(item->RecSkill); + outapp->WriteUInt32(item->BardType); + outapp->WriteUInt32(item->BardValue); + outapp->WriteUInt8(item->Light); + outapp->WriteUInt8(item->Delay); + outapp->WriteUInt8(item->ElemDmgType); + outapp->WriteUInt8(item->ElemDmgAmt); + outapp->WriteUInt8(item->Range); + outapp->WriteUInt32(item->Damage); + outapp->WriteUInt32(item->Color); + outapp->WriteUInt32(0); // Prestige + outapp->WriteUInt8(item->ItemType); + outapp->WriteUInt32(item->Material); + outapp->WriteUInt32(0); //unknown + outapp->WriteUInt32(item->EliteMaterial); + outapp->WriteUInt32(0); // unknown + outapp->WriteUInt32(0); // unknown + outapp->WriteUInt32(0); //This is unknown057 from lucy + for (spacer = 0; spacer < 77; spacer++) { //More Item stats, but some seem to be off based on packet check + outapp->WriteUInt8(0); + } + outapp->WriteUInt32(0xFFFFFFFF); //Unknown but always seen as FF FF FF FF + outapp->WriteUInt32(0); //Unknown + for (spacer = 0; spacer < 5; spacer++) { //Augment stuff + outapp->WriteUInt32(item->AugSlotType[spacer]); + outapp->WriteUInt8(item->AugSlotVisible[spacer]); + outapp->WriteUInt8(item->AugSlotUnk2[spacer]); + } + outapp->WriteUInt32(0); //New RoF 6th Aug Slot + outapp->WriteUInt8(1); //^ + outapp->WriteUInt8(0); //^^ + outapp->WriteUInt32(item->LDoNSold); + outapp->WriteUInt32(item->LDoNTheme); + outapp->WriteUInt32(item->LDoNPrice); + outapp->WriteUInt32(item->LDoNSellBackRate); + for (spacer = 0; spacer < 11; spacer++) { //unknowns + outapp->WriteUInt8(0); + } + outapp->WriteUInt32(0xFFFFFFFF); //Unknown but always seen as FF FF FF FF + outapp->WriteUInt16(0); //Unknown + outapp->WriteUInt32(item->Favor); // Tribute + for (spacer = 0; spacer < 17; spacer++) { //unknowns + outapp->WriteUInt8(0); + } + outapp->WriteUInt32(item->GuildFavor); // Tribute + outapp->WriteUInt32(0); //Unknown + outapp->WriteUInt32(0xFFFFFFFF); //Unknown but always seen as FF FF FF FF + for (spacer = 0; spacer < 11; spacer++) { //unknowns + outapp->WriteUInt8(0); + } + outapp->WriteUInt8(1); + for (spacer = 0; spacer < 25; spacer++) { //unknowns + outapp->WriteUInt8(0); + } + for (spacer = 0; spacer < 304; spacer++) { //Cast stuff and whole bunch of unknowns + outapp->WriteUInt8(0); + } + outapp->WriteUInt8(142); // Always seen not in the item structure though 8E + outapp->WriteUInt32(0); //unknown + outapp->WriteUInt32(1); // Always seen as 1 + outapp->WriteUInt32(0); //unknown + outapp->WriteUInt32(0xCDCCCC3D); // Unknown + outapp->WriteUInt32(0); + outapp->WriteUInt16(8256); //0x4020/8256 + outapp->WriteUInt16(0); + outapp->WriteUInt32(0xFFFFFFFF); //Unknown but always seen as FF FF FF FF + outapp->WriteUInt16(0); + outapp->WriteUInt32(0xFFFFFFFF); //Unknown but always seen as FF FF FF FF + outapp->WriteUInt32(0); //unknown + outapp->WriteUInt32(0); //unknown + outapp->WriteUInt16(0); //unknown + outapp->WriteUInt32(32831); //0x3F80 + for (spacer = 0; spacer < 24; spacer++) { //whole bunch of unknowns always 0's + outapp->WriteUInt8(0); + } + outapp->WriteUInt8(1); + for (spacer = 0; spacer < 6; spacer++) { //whole bunch of unknowns always 0's + outapp->WriteUInt8(0); + } + + QueuePacket(outapp); + safe_delete(outapp); + } + else + return; +} + +void Client::Handle_OP_ItemVerifyRequest(const EQApplicationPacket *app) +{ + if (app->size != sizeof(ItemVerifyRequest_Struct)) + { + LogFile->write(EQEMuLog::Error, "OP size error: OP_ItemVerifyRequest expected:%i got:%i", sizeof(ItemVerifyRequest_Struct), app->size); + return; + } + + ItemVerifyRequest_Struct* request = (ItemVerifyRequest_Struct*)app->pBuffer; + int32 slot_id; + int32 target_id; + int32 spell_id = 0; + slot_id = request->slot; + target_id = request->target; + + + EQApplicationPacket *outapp; + outapp = new EQApplicationPacket(OP_ItemVerifyReply, sizeof(ItemVerifyReply_Struct)); + ItemVerifyReply_Struct* reply = (ItemVerifyReply_Struct*)outapp->pBuffer; + reply->slot = slot_id; + reply->target = target_id; + + QueuePacket(outapp); + safe_delete(outapp); + + + if (IsAIControlled()) { + this->Message_StringID(13, NOT_IN_CONTROL); + return; + } + + if (slot_id < 0) { + LogFile->write(EQEMuLog::Debug, "Unknown slot being used by %s, slot being used is: %i", GetName(), request->slot); + return; + } + + const ItemInst* inst = m_inv[slot_id]; + if (!inst) { + Message(0, "Error: item not found in inventory slot #%i", slot_id); + DeleteItemInInventory(slot_id, 0, true); + return; + } + + const Item_Struct* item = inst->GetItem(); + if (!item) { + Message(0, "Error: item not found in inventory slot #%i", slot_id); + DeleteItemInInventory(slot_id, 0, true); + return; + } + + spell_id = item->Click.Effect; + + if + ( + spell_id > 0 && + ( + !IsValidSpell(spell_id) || + casting_spell_id || + delaytimer || + spellend_timer.Enabled() || + IsStunned() || + IsFeared() || + IsMezzed() || + DivineAura() || + (spells[spell_id].targettype == ST_Ring) || + (IsSilenced() && !IsDiscipline(spell_id)) || + (IsAmnesiad() && IsDiscipline(spell_id)) || + (IsDetrimentalSpell(spell_id) && !zone->CanDoCombat()) + ) + ) + { + SendSpellBarEnable(spell_id); + return; + } + + LogFile->write(EQEMuLog::Debug, "OP ItemVerifyRequest: spell=%i, target=%i, inv=%i", spell_id, target_id, slot_id); + + if (m_inv.SupportsClickCasting(slot_id) || ((item->ItemType == ItemTypePotion || item->PotionBelt) && m_inv.SupportsPotionBeltCasting(slot_id))) // sanity check + { + ItemInst* p_inst = (ItemInst*)inst; + + parse->EventItem(EVENT_ITEM_CLICK, this, p_inst, nullptr, "", slot_id); + inst = m_inv[slot_id]; + if (!inst) + { + return; + } + + int r; + bool tryaug = false; + ItemInst* clickaug = 0; + Item_Struct* augitem = 0; + + for (r = 0; r < EmuConstants::ITEM_COMMON_SIZE; r++) { + const ItemInst* aug_i = inst->GetAugment(r); + if (!aug_i) + continue; + const Item_Struct* aug = aug_i->GetItem(); + if (!aug) + continue; + + if ((aug->Click.Type == ET_ClickEffect) || (aug->Click.Type == ET_Expendable) || (aug->Click.Type == ET_EquipClick) || (aug->Click.Type == ET_ClickEffect2)) + { + tryaug = true; + clickaug = (ItemInst*)aug_i; + augitem = (Item_Struct*)aug; + spell_id = aug->Click.Effect; + break; + } + } + + if ((spell_id <= 0) && (item->ItemType != ItemTypeFood && item->ItemType != ItemTypeDrink && item->ItemType != ItemTypeAlcohol && item->ItemType != ItemTypeSpell)) + { + LogFile->write(EQEMuLog::Debug, "Item with no effect right clicked by %s", GetName()); + } + else if (inst->IsType(ItemClassCommon)) + { + if (item->ItemType == ItemTypeSpell && (strstr((const char*)item->Name, "Tome of ") || strstr((const char*)item->Name, "Skill: "))) + { + DeleteItemInInventory(slot_id, 1, true); + TrainDiscipline(item->ID); + } + else if (item->ItemType == ItemTypeSpell) + { + return; + } + else if ((item->Click.Type == ET_ClickEffect) || (item->Click.Type == ET_Expendable) || (item->Click.Type == ET_EquipClick) || (item->Click.Type == ET_ClickEffect2)) + { + if (inst->GetCharges() == 0) + { + //Message(0, "This item is out of charges."); + Message_StringID(13, ITEM_OUT_OF_CHARGES); + return; + } + if (GetLevel() >= item->Click.Level2) + { + int i = parse->EventItem(EVENT_ITEM_CLICK_CAST, this, p_inst, nullptr, "", slot_id); + inst = m_inv[slot_id]; + if (!inst) + { + return; + } + + if (i == 0) { + CastSpell(item->Click.Effect, target_id, USE_ITEM_SPELL_SLOT, item->CastTime, 0, 0, slot_id); + } + } + else + { + Message_StringID(13, ITEMS_INSUFFICIENT_LEVEL); + return; + } + } + else if (tryaug) + { + if (clickaug->GetCharges() == 0) + { + //Message(0, "This item is out of charges."); + Message_StringID(13, ITEM_OUT_OF_CHARGES); + return; + } + if (GetLevel() >= augitem->Click.Level2) + { + int i = parse->EventItem(EVENT_ITEM_CLICK_CAST, this, clickaug, nullptr, "", slot_id); + inst = m_inv[slot_id]; + if (!inst) + { + return; + } + + if (i == 0) { + CastSpell(augitem->Click.Effect, target_id, USE_ITEM_SPELL_SLOT, augitem->CastTime, 0, 0, slot_id); + } + } + else + { + Message_StringID(13, ITEMS_INSUFFICIENT_LEVEL); + return; + } + } + else + { + if (GetClientVersion() >= EQClientSoD && !inst->IsEquipable(GetBaseRace(), GetClass())) + { + if (item->ItemType != ItemTypeFood && item->ItemType != ItemTypeDrink && item->ItemType != ItemTypeAlcohol) + { + LogFile->write(EQEMuLog::Debug, "Error: unknown item->Click.Type (%i)", item->Click.Type); + } + else + { + //This is food/drink - consume it + if (item->ItemType == ItemTypeFood && m_pp.hunger_level < 5000) + { + Consume(item, item->ItemType, slot_id, false); + } + else if (item->ItemType == ItemTypeDrink && m_pp.thirst_level < 5000) + { + Consume(item, item->ItemType, slot_id, false); + } + else if (item->ItemType == ItemTypeAlcohol) + { +#if EQDEBUG >= 1 + LogFile->write(EQEMuLog::Debug, "Drinking Alcohol from slot:%i", slot_id); +#endif + // This Seems to be handled in OP_DeleteItem handling + //DeleteItemInInventory(slot_id, 1, false); + //entity_list.MessageClose_StringID(this, true, 50, 0, DRINKING_MESSAGE, GetName(), item->Name); + //Should add intoxication level to the PP at some point + //CheckIncreaseSkill(ALCOHOL_TOLERANCE, nullptr, 25); + } + + if (m_pp.hunger_level > 6000) + m_pp.hunger_level = 6000; + if (m_pp.thirst_level > 6000) + m_pp.thirst_level = 6000; + + EQApplicationPacket *outapp2; + outapp2 = new EQApplicationPacket(OP_Stamina, sizeof(Stamina_Struct)); + Stamina_Struct* sta = (Stamina_Struct*)outapp2->pBuffer; + sta->food = m_pp.hunger_level; + sta->water = m_pp.thirst_level; + + QueuePacket(outapp2); + safe_delete(outapp2); + } + + } + else + { + LogFile->write(EQEMuLog::Debug, "Error: unknown item->Click.Type (%i)", item->Click.Type); + } + } + } + else + { + Message(0, "Error: item not found in inventory slot #%i", slot_id); + } + } + else + { + Message(0, "Error: Invalid inventory slot for using effects (inventory slot #%i)", slot_id); + } + + return; +} + +void Client::Handle_OP_Jump(const EQApplicationPacket *app) +{ + SetEndurance(GetEndurance() - (GetLevel()<20 ? (225 * GetLevel() / 100) : 50)); + return; +} + +void Client::Handle_OP_KeyRing(const EQApplicationPacket *app) +{ + KeyRingList(); +} + +void Client::Handle_OP_LDoNButton(const EQApplicationPacket *app) +{ + if (app->size < sizeof(bool)) + { + return; + } + + if (GetPendingAdventureCreate()) + { + return; + } + + if (IsOnAdventure()) + { + return; + } + + bool* p = (bool*)app->pBuffer; + if (*p == true) + { + ServerPacket *pack = new ServerPacket(ServerOP_AdventureRequestCreate, sizeof(ServerAdventureRequestCreate_Struct)+(64 * adv_requested_member_count)); + ServerAdventureRequestCreate_Struct *sac = (ServerAdventureRequestCreate_Struct*)pack->pBuffer; + strcpy(sac->leader, GetName()); + sac->id = adv_requested_id; + sac->theme = adv_requested_theme; + sac->member_count = adv_requested_member_count; + memcpy((pack->pBuffer + sizeof(ServerAdventureRequestCreate_Struct)), adv_requested_data, (64 * adv_requested_member_count)); + pack->Deflate(); + worldserver.SendPacket(pack); + delete pack; + PendingAdventureCreate(); + ClearPendingAdventureData(); + } + else + { + ClearPendingAdventureData(); + } +} + +void Client::Handle_OP_LDoNDisarmTraps(const EQApplicationPacket *app) +{ + Mob * target = GetTarget(); + if (target->IsNPC()) + { + if (HasSkill(SkillDisarmTraps)) + { + if (DistNoRootNoZ(*target) > RuleI(Adventure, LDoNTrapDistanceUse)) + { + Message(13, "%s is too far away.", target->GetCleanName()); + return; + } + HandleLDoNDisarm(target->CastToNPC(), GetSkill(SkillDisarmTraps), LDoNTypeMechanical); + } + else + Message(13, "You do not have the disarm trap skill."); + } +} + +void Client::Handle_OP_LDoNInspect(const EQApplicationPacket *app) +{ + Mob * target = GetTarget(); + if (target && target->GetClass() == LDON_TREASURE) + Message(15, "%s", target->GetCleanName()); +} + +void Client::Handle_OP_LDoNOpen(const EQApplicationPacket *app) +{ + Mob * target = GetTarget(); + if (target && target->IsNPC()) + HandleLDoNOpen(target->CastToNPC()); +} + +void Client::Handle_OP_LDoNPickLock(const EQApplicationPacket *app) +{ + Mob * target = GetTarget(); + if (target->IsNPC()) + { + if (HasSkill(SkillPickLock)) + { + if (DistNoRootNoZ(*target) > RuleI(Adventure, LDoNTrapDistanceUse)) + { + Message(13, "%s is too far away.", target->GetCleanName()); + return; + } + HandleLDoNPickLock(target->CastToNPC(), GetSkill(SkillPickLock), LDoNTypeMechanical); + } + else + Message(13, "You do not have the pick locks skill."); + } +} + +void Client::Handle_OP_LDoNSenseTraps(const EQApplicationPacket *app) +{ + Mob * target = GetTarget(); + if (target->IsNPC()) + { + if (HasSkill(SkillSenseTraps)) + { + if (DistNoRootNoZ(*target) > RuleI(Adventure, LDoNTrapDistanceUse)) + { + Message(13, "%s is too far away.", target->GetCleanName()); + return; + } + HandleLDoNSenseTraps(target->CastToNPC(), GetSkill(SkillSenseTraps), LDoNTypeMechanical); + } + else + Message(13, "You do not have the sense traps skill."); + } +} + +void Client::Handle_OP_LeadershipExpToggle(const EQApplicationPacket *app) +{ + if (app->size != 1) { + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_LeadershipExpToggle expected %i got %i", 1, app->size); + DumpPacket(app); + return; + } + uint8 *mode = (uint8 *)app->pBuffer; + if (*mode) { + m_pp.leadAAActive = 1; + Save(); + Message_StringID(clientMessageYellow, LEADERSHIP_EXP_ON); + } + else { + m_pp.leadAAActive = 0; + Save(); + Message_StringID(clientMessageYellow, LEADERSHIP_EXP_OFF); + } +} + +void Client::Handle_OP_LeaveAdventure(const EQApplicationPacket *app) +{ + if (!IsOnAdventure()) + { + return; + } + LeaveAdventure(); +} + +void Client::Handle_OP_LeaveBoat(const EQApplicationPacket *app) +{ + Mob* boat = entity_list.GetMob(this->BoatID); // 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; + return; +} + +void Client::Handle_OP_LFGCommand(const EQApplicationPacket *app) +{ + if (app->size != sizeof(LFG_Struct)) { + std::cout << "Wrong size on OP_LFGCommand. Got: " << app->size << ", Expected: " << sizeof(LFG_Struct) << std::endl; + DumpPacket(app); + return; + } + + // Process incoming packet + LFG_Struct* lfg = (LFG_Struct*)app->pBuffer; + + switch (lfg->value & 0xFF) { + case 0: + if (LFG) { + database.SetLFG(CharacterID(), false); + LFG = false; + LFGComments[0] = '\0'; + } + break; + case 1: + if (!LFG) { + LFG = true; + database.SetLFG(CharacterID(), true); + } + LFGFromLevel = lfg->FromLevel; + LFGToLevel = lfg->ToLevel; + LFGMatchFilter = lfg->MatchFilter; + strcpy(LFGComments, lfg->Comments); + break; + default: + Message(0, "Error: unknown LFG value %i", lfg->value); + } + + UpdateWho(); + + // Issue outgoing packet to notify other clients + EQApplicationPacket* outapp = new EQApplicationPacket(OP_LFGAppearance, sizeof(LFG_Appearance_Struct)); + LFG_Appearance_Struct* lfga = (LFG_Appearance_Struct*)outapp->pBuffer; + lfga->spawn_id = this->GetID(); + lfga->lfg = (uint8)LFG; + + entity_list.QueueClients(this, outapp, true); + safe_delete(outapp); + return; +} + +void Client::Handle_OP_LFGGetMatchesRequest(const EQApplicationPacket *app) +{ + + if (app->size != sizeof(LFGGetMatchesRequest_Struct)) { + LogFile->write(EQEMuLog::Error, "Wrong size: OP_LFGGetMatchesRequest, size=%i, expected %i", app->size, sizeof(LFGGetMatchesRequest_Struct)); + DumpPacket(app); + return; + } + LFGGetMatchesRequest_Struct* gmrs = (LFGGetMatchesRequest_Struct*)app->pBuffer; + + if (!worldserver.Connected()) + Message(0, "Error: World server disconnected"); + else { + ServerPacket* pack = new ServerPacket(ServerOP_LFGMatches, sizeof(ServerLFGMatchesRequest_Struct)); + ServerLFGMatchesRequest_Struct* smrs = (ServerLFGMatchesRequest_Struct*)pack->pBuffer; + smrs->FromID = GetID(); + smrs->QuerierLevel = GetLevel(); + strcpy(smrs->FromName, GetName()); + smrs->FromLevel = gmrs->FromLevel; + smrs->ToLevel = gmrs->ToLevel; + smrs->Classes = gmrs->Classes; + worldserver.SendPacket(pack); + safe_delete(pack); + } +} + +void Client::Handle_OP_LFGuild(const EQApplicationPacket *app) +{ + if (app->size < 4) + return; + + uint32 Command = *((uint32 *)app->pBuffer); + + switch (Command) + { + case 0: + { + VERIFY_PACKET_LENGTH(OP_LFGuild, app, LFGuild_PlayerToggle_Struct); + LFGuild_PlayerToggle_Struct *pts = (LFGuild_PlayerToggle_Struct *)app->pBuffer; + +#ifdef DARWIN +#if __DARWIN_C_LEVEL < 200809L + if (strlen(pts->Comment) > 256) +#else + if (strnlen(pts->Comment, 256) > 256) +#endif // __DARWIN_C_LEVEL +#else + if (strnlen(pts->Comment, 256) > 256) +#endif // DARWIN + return; + + ServerPacket* pack = new ServerPacket(ServerOP_QueryServGeneric, strlen(GetName()) + strlen(pts->Comment) + 38); + + pack->WriteUInt32(zone->GetZoneID()); + pack->WriteUInt32(zone->GetInstanceID()); + pack->WriteString(GetName()); + pack->WriteUInt32(QSG_LFGuild); + pack->WriteUInt32(QSG_LFGuild_UpdatePlayerInfo); + pack->WriteUInt32(GetBaseClass()); + pack->WriteUInt32(GetLevel()); + pack->WriteUInt32(GetAAPointsSpent()); + pack->WriteString(pts->Comment); + pack->WriteUInt32(pts->Toggle); + pack->WriteUInt32(pts->TimeZone); + + worldserver.SendPacket(pack); + safe_delete(pack); + + break; + } + case 1: + { + VERIFY_PACKET_LENGTH(OP_LFGuild, app, LFGuild_GuildToggle_Struct); + LFGuild_GuildToggle_Struct *gts = (LFGuild_GuildToggle_Struct *)app->pBuffer; + +#ifdef DARWIN +#if __DARWIN_C_LEVEL < 200809L + if (strlen(gts->Comment) > 256) +#else + if (strnlen(gts->Comment, 256) > 256) +#endif // __DARWIN_C_LEVEL +#else + if (strnlen(gts->Comment, 256) > 256) +#endif // __DARWIN + return; + + ServerPacket* pack = new ServerPacket(ServerOP_QueryServGeneric, strlen(GetName()) + strlen(gts->Comment) + strlen(guild_mgr.GetGuildName(GuildID())) + 43); + + pack->WriteUInt32(zone->GetZoneID()); + pack->WriteUInt32(zone->GetInstanceID()); + pack->WriteString(GetName()); + pack->WriteUInt32(QSG_LFGuild); + pack->WriteUInt32(QSG_LFGuild_UpdateGuildInfo); + pack->WriteString(guild_mgr.GetGuildName(GuildID())); + pack->WriteString(gts->Comment); + pack->WriteUInt32(gts->FromLevel); + pack->WriteUInt32(gts->ToLevel); + pack->WriteUInt32(gts->Classes); + pack->WriteUInt32(gts->AACount); + pack->WriteUInt32(gts->Toggle); + pack->WriteUInt32(gts->TimeZone); + + worldserver.SendPacket(pack); + safe_delete(pack); + + break; + } + case 3: + { + VERIFY_PACKET_LENGTH(OP_LFGuild, app, LFGuild_SearchPlayer_Struct); + + ServerPacket* pack = new ServerPacket(ServerOP_QueryServGeneric, strlen(GetName()) + 37); + + pack->WriteUInt32(zone->GetZoneID()); + pack->WriteUInt32(zone->GetInstanceID()); + pack->WriteString(GetName()); + pack->WriteUInt32(QSG_LFGuild); + pack->WriteUInt32(QSG_LFGuild_PlayerMatches); + + LFGuild_SearchPlayer_Struct *sps = (LFGuild_SearchPlayer_Struct *)app->pBuffer; + pack->WriteUInt32(sps->FromLevel); + pack->WriteUInt32(sps->ToLevel); + pack->WriteUInt32(sps->MinAA); + pack->WriteUInt32(sps->TimeZone); + pack->WriteUInt32(sps->Classes); + + worldserver.SendPacket(pack); + safe_delete(pack); + + break; + } + case 4: + { + VERIFY_PACKET_LENGTH(OP_LFGuild, app, LFGuild_SearchGuild_Struct); + + ServerPacket* pack = new ServerPacket(ServerOP_QueryServGeneric, strlen(GetName()) + 33); + + pack->WriteUInt32(zone->GetZoneID()); + pack->WriteUInt32(zone->GetInstanceID()); + pack->WriteString(GetName()); + pack->WriteUInt32(QSG_LFGuild); + pack->WriteUInt32(QSG_LFGuild_GuildMatches); + + LFGuild_SearchGuild_Struct *sgs = (LFGuild_SearchGuild_Struct *)app->pBuffer; + + pack->WriteUInt32(sgs->Level); + pack->WriteUInt32(sgs->AAPoints); + pack->WriteUInt32(sgs->TimeZone); + pack->WriteUInt32(sgs->Class); + + worldserver.SendPacket(pack); + safe_delete(pack); + + break; + } + default: + break; + } +} + +void Client::Handle_OP_LFPCommand(const EQApplicationPacket *app) +{ + + if (app->size != sizeof(LFP_Struct)) { + LogFile->write(EQEMuLog::Error, "Wrong size: OP_LFPCommand, size=%i, expected %i", app->size, sizeof(LFP_Struct)); + DumpPacket(app); + return; + } + LFP_Struct *lfp = (LFP_Struct*)app->pBuffer; + + LFP = lfp->Action != LFPOff; + database.SetLFP(CharacterID(), LFP); + + if (!LFP) { + worldserver.StopLFP(CharacterID()); + return; + } + + GroupLFPMemberEntry LFPMembers[MAX_GROUP_MEMBERS]; + + for (unsigned int i = 0; iGetZoneID(); + LFPMembers[0].GuildID = GuildID(); + + if (g) { + // This should not happen. The client checks if you are in a group and will not let you put LFP on if + // you are not the leader. + if (!g->IsLeader(this)) { + LogFile->write(EQEMuLog::Error, "Client sent LFP on for character %s who is grouped but not leader.", GetName()); + return; + } + // Fill the LFPMembers array with the rest of the group members, excluding ourself + // We don't fill in the class, level or zone, because we may not be able to determine + // them if the other group members are not in this zone. World will fill in this information + // for us, if it can. + int NextFreeSlot = 1; + for (unsigned int i = 0; i < MAX_GROUP_MEMBERS; i++) { + if (strcasecmp(g->membername[i], LFPMembers[0].Name)) + strcpy(LFPMembers[NextFreeSlot++].Name, g->membername[i]); + } + } + + + worldserver.UpdateLFP(CharacterID(), lfp->Action, lfp->MatchFilter, lfp->FromLevel, lfp->ToLevel, lfp->Classes, + lfp->Comments, LFPMembers); + + +} + +void Client::Handle_OP_LFPGetMatchesRequest(const EQApplicationPacket *app) +{ + + if (app->size != sizeof(LFPGetMatchesRequest_Struct)) { + LogFile->write(EQEMuLog::Error, "Wrong size: OP_LFPGetMatchesRequest, size=%i, expected %i", app->size, sizeof(LFPGetMatchesRequest_Struct)); + DumpPacket(app); + return; + } + LFPGetMatchesRequest_Struct* gmrs = (LFPGetMatchesRequest_Struct*)app->pBuffer; + + if (!worldserver.Connected()) + Message(0, "Error: World server disconnected"); + else { + ServerPacket* pack = new ServerPacket(ServerOP_LFPMatches, sizeof(ServerLFPMatchesRequest_Struct)); + ServerLFPMatchesRequest_Struct* smrs = (ServerLFPMatchesRequest_Struct*)pack->pBuffer; + smrs->FromID = GetID(); + smrs->FromLevel = gmrs->FromLevel; + smrs->ToLevel = gmrs->ToLevel; + smrs->QuerierLevel = GetLevel(); + smrs->QuerierClass = GetClass(); + strcpy(smrs->FromName, GetName()); + worldserver.SendPacket(pack); + safe_delete(pack); + } + + return; +} + +void Client::Handle_OP_LoadSpellSet(const EQApplicationPacket *app) +{ + if (app->size != sizeof(LoadSpellSet_Struct)) { + printf("Wrong size of LoadSpellSet_Struct! Expected: %zu, Got: %i\n", sizeof(LoadSpellSet_Struct), app->size); + return; + } + int i; + LoadSpellSet_Struct* ss = (LoadSpellSet_Struct*)app->pBuffer; + for (i = 0; ispell[i] != 0xFFFFFFFF) + UnmemSpell(i, true); + } +} + +void Client::Handle_OP_Logout(const EQApplicationPacket *app) +{ + //LogFile->write(EQEMuLog::Debug, "%s sent a logout packet.", GetName()); + //we will save when we get destroyed soon anyhow + //Save(); + + SendLogoutPackets(); + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_LogoutReply); + FastQueuePacket(&outapp); + + Disconnect(); + return; +} + +void Client::Handle_OP_LootItem(const EQApplicationPacket *app) +{ + if (app->size != sizeof(LootingItem_Struct)) { + LogFile->write(EQEMuLog::Error, "Wrong size: OP_LootItem, size=%i, expected %i", app->size, sizeof(LootingItem_Struct)); + return; + } + /* + ** fixed the looting code so that it sends the correct opcodes + ** and now correctly removes the looted item the player selected + ** as well as gives the player the proper item. + ** Also fixed a few UI lock ups that would occur. + */ + + EQApplicationPacket* outapp = 0; + Entity* entity = entity_list.GetID(*((uint16*)app->pBuffer)); + if (entity == 0) { + Message(13, "Error: OP_LootItem: Corpse not found (ent = 0)"); + outapp = new EQApplicationPacket(OP_LootComplete, 0); + QueuePacket(outapp); + safe_delete(outapp); + return; + } + + if (entity->IsCorpse()) { + entity->CastToCorpse()->LootItem(this, app); + return; + } + else { + Message(13, "Error: Corpse not found! (!ent->IsCorpse())"); + Corpse::SendEndLootErrorPacket(this); + } + + return; +} + +void Client::Handle_OP_LootRequest(const EQApplicationPacket *app) +{ + if (app->size != sizeof(uint32)) { + std::cout << "Wrong size: OP_LootRequest, size=" << app->size << ", expected " << sizeof(uint32) << std::endl; + return; + } + + Entity* ent = entity_list.GetID(*((uint32*)app->pBuffer)); + if (ent == 0) { + Message(13, "Error: OP_LootRequest: Corpse not found (ent = 0)"); + Corpse::SendLootReqErrorPacket(this); + return; + } + if (ent->IsCorpse()) + { + SetLooting(ent->GetID()); //store the entity we are looting + Corpse *ent_corpse = ent->CastToCorpse(); + if (DistNoRootNoZ(ent_corpse->GetX(), ent_corpse->GetY()) > 625) + { + Message(13, "Corpse too far away."); + Corpse::SendLootReqErrorPacket(this); + return; + } + + if (invisible) { + BuffFadeByEffect(SE_Invisibility); + BuffFadeByEffect(SE_Invisibility2); + invisible = false; + } + if (invisible_undead) { + BuffFadeByEffect(SE_InvisVsUndead); + BuffFadeByEffect(SE_InvisVsUndead2); + invisible_undead = false; + } + if (invisible_animals){ + BuffFadeByEffect(SE_InvisVsAnimals); + invisible_animals = false; + } + if (hidden || improved_hidden){ + hidden = false; + improved_hidden = false; + EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct)); + SpawnAppearance_Struct* sa_out = (SpawnAppearance_Struct*)outapp->pBuffer; + sa_out->spawn_id = GetID(); + sa_out->type = 0x03; + sa_out->parameter = 0; + entity_list.QueueClients(this, outapp, true); + safe_delete(outapp); + } + ent->CastToCorpse()->MakeLootRequestPackets(this, app); + return; + } + else { + std::cout << "npc == 0 LOOTING FOOKED3" << std::endl; + Message(13, "Error: OP_LootRequest: Corpse not a corpse?"); + Corpse::SendLootReqErrorPacket(this); + } + return; +} + +void Client::Handle_OP_ManaChange(const EQApplicationPacket *app) +{ + if (app->size == 0) { + // i think thats the sign to stop the songs + if (IsBardSong(casting_spell_id) || bardsong != 0) + InterruptSpell(SONG_ENDS, 0x121); + else + InterruptSpell(INTERRUPT_SPELL, 0x121); + + return; + } + else // I don't think the client sends proper manachanges + { // with a length, just the 0 len ones for stopping songs + //ManaChange_Struct* p = (ManaChange_Struct*)app->pBuffer; + printf("OP_ManaChange from client:\n"); + DumpPacket(app); + } + return; +} + +/* +#if 0 // I dont think there's an op for this now, and we check this +// when the client is sitting +void Client::Handle_OP_Medding(const EQApplicationPacket *app) +{ + if (app->pBuffer[0]) + medding = true; + else + medding = false; + return; +} +#endif +*/ + +void Client::Handle_OP_MemorizeSpell(const EQApplicationPacket *app) +{ + OPMemorizeSpell(app); + return; +} + +void Client::Handle_OP_Mend(const EQApplicationPacket *app) +{ + if (!HasSkill(SkillMend)) + return; + + if (!p_timers.Expired(&database, pTimerMend, false)) { + Message(13, "Ability recovery time not yet met."); + return; + } + p_timers.Start(pTimerMend, MendReuseTime - 1); + + int mendhp = GetMaxHP() / 4; + int currenthp = GetHP(); + if (zone->random.Int(0, 199) < (int)GetSkill(SkillMend)) { + + int criticalchance = spellbonuses.CriticalMend + itembonuses.CriticalMend + aabonuses.CriticalMend; + + if (zone->random.Int(0, 99) < criticalchance){ + mendhp *= 2; + Message_StringID(4, MEND_CRITICAL); + } + SetHP(GetHP() + mendhp); + SendHPUpdate(); + Message_StringID(4, MEND_SUCCESS); + } + else { + /* the purpose of the following is to make the chance to worsen wounds much less common, + which is more consistent with the way eq live works. + according to my math, this should result in the following probability: + 0 skill - 25% chance to worsen + 20 skill - 23% chance to worsen + 50 skill - 16% chance to worsen */ + if ((GetSkill(SkillMend) <= 75) && (zone->random.Int(GetSkill(SkillMend), 100) < 75) && (zone->random.Int(1, 3) == 1)) + { + SetHP(currenthp > mendhp ? (GetHP() - mendhp) : 1); + SendHPUpdate(); + Message_StringID(4, MEND_WORSEN); + } + else + Message_StringID(4, MEND_FAIL); + } + + CheckIncreaseSkill(SkillMend, nullptr, 10); + return; +} + +void Client::Handle_OP_MercenaryCommand(const EQApplicationPacket *app) +{ + if (app->size != sizeof(MercenaryCommand_Struct)) + { + Message(13, "Size mismatch in OP_MercenaryCommand expected %i got %i", sizeof(MercenaryCommand_Struct), app->size); + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_MercenaryCommand expected %i got %i", sizeof(MercenaryCommand_Struct), app->size); + DumpPacket(app); + return; + } + + MercenaryCommand_Struct* mc = (MercenaryCommand_Struct*)app->pBuffer; + uint32 merc_command = mc->MercCommand; // Seen 0 (zone in with no merc or suspended), 1 (dismiss merc), 5 (normal state), 20 (unknown), 36 (zone in with merc) + int32 option = mc->Option; // Seen -1 (zone in with no merc), 0 (setting to passive stance), 1 (normal or setting to balanced stance) + + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: Command %i, Option %i received.", merc_command, option); + + if (!RuleB(Mercs, AllowMercs)) + return; + + // Handle the Command here... + // Will need a list of what every type of command is supposed to do + // Unsure if there is a server response to this packet + if (option >= 0) + { + Merc* merc = GetMerc(); + GetMercInfo().State = option; + + if (merc) + { + uint8 numStances = 0; + + //get number of available stances for the current merc + std::list mercStanceList = zone->merc_stance_list[merc->GetMercTemplateID()]; + std::list::iterator iter = mercStanceList.begin(); + while (iter != mercStanceList.end()) { + numStances++; + ++iter; + } + + MercTemplate* mercTemplate = zone->GetMercTemplate(GetMerc()->GetMercTemplateID()); + if (mercTemplate) + { + //check to see if selected option is a valid stance slot (option is the slot the stance is in, not the actual stance) + if (option >= 0 && option < numStances) + { + merc->SetStance(mercTemplate->Stances[option]); + GetMercInfo().Stance = mercTemplate->Stances[option]; + + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: Set Stance: %u", merc->GetStance()); + } + } + } + } +} + +void Client::Handle_OP_MercenaryDataRequest(const EQApplicationPacket *app) +{ + // The payload is 4 bytes. The EntityID of the Mercenary Liason which are of class 71. + if (app->size != sizeof(MercenaryMerchantShopRequest_Struct)) + { + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_MercenaryDataRequest expected 4 got %i", app->size); + + DumpPacket(app); + + return; + } + + MercenaryMerchantShopRequest_Struct* mmsr = (MercenaryMerchantShopRequest_Struct*)app->pBuffer; + uint32 merchant_id = mmsr->MercMerchantID; + uint32 altCurrentType = 19; + + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: Data Request for Merchant ID (%i)", merchant_id); + + //client is requesting data about currently owned mercenary + if (merchant_id == 0) { + + //send info about your current merc(s) + if (GetMercInfo().mercid) + { + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: SendMercPersonalInfo Request"); + SendMercPersonalInfo(); + } + else + { + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: SendMercPersonalInfo Not Sent - MercID (%i)", GetMercInfo().mercid); + } + } + + if (!RuleB(Mercs, AllowMercs)) { + return; + } + + NPC* tar = entity_list.GetNPCByID(merchant_id); + + if (tar) { + int mercTypeCount = 0; + int mercCount = 0; + + if (DistNoRoot(*tar) > USE_NPC_RANGE2) + return; + + if (tar->GetClass() != MERCERNARY_MASTER) { + return; + } + + mercTypeCount = tar->GetNumMercTypes(GetClientVersion()); + mercCount = tar->GetNumMercs(GetClientVersion()); + + if (mercCount > MAX_MERC) + return; + + std::list mercTypeList = tar->GetMercTypesList(GetClientVersion()); + std::list mercDataList = tar->GetMercsList(GetClientVersion()); + + int i = 0; + int StanceCount = 0; + + for (std::list::iterator mercListItr = mercDataList.begin(); mercListItr != mercDataList.end(); ++mercListItr) + { + std::list::iterator siter = zone->merc_stance_list[mercListItr->MercTemplateID].begin(); + for (siter = zone->merc_stance_list[mercListItr->MercTemplateID].begin(); siter != zone->merc_stance_list[mercListItr->MercTemplateID].end(); ++siter) + { + StanceCount++; + } + } + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_MercenaryDataResponse, sizeof(MercenaryMerchantList_Struct)); + MercenaryMerchantList_Struct* mml = (MercenaryMerchantList_Struct*)outapp->pBuffer; + + mml->MercTypeCount = mercTypeCount; + if (mercTypeCount > 0) + { + for (std::list::iterator mercTypeListItr = mercTypeList.begin(); mercTypeListItr != mercTypeList.end(); ++mercTypeListItr) { + mml->MercGrades[i] = mercTypeListItr->Type; // DBStringID for Type + i++; + } + } + mml->MercCount = mercCount; + + if (mercCount > 0) + { + i = 0; + for (std::list::iterator mercListIter = mercDataList.begin(); mercListIter != mercDataList.end(); ++mercListIter) + { + mml->Mercs[i].MercID = mercListIter->MercTemplateID; + mml->Mercs[i].MercType = mercListIter->MercType; + mml->Mercs[i].MercSubType = mercListIter->MercSubType; + mml->Mercs[i].PurchaseCost = RuleB(Mercs, ChargeMercPurchaseCost) ? Merc::CalcPurchaseCost(mercListIter->MercTemplateID, GetLevel(), 0) : 0; + mml->Mercs[i].UpkeepCost = RuleB(Mercs, ChargeMercUpkeepCost) ? Merc::CalcUpkeepCost(mercListIter->MercTemplateID, GetLevel(), 0) : 0; + mml->Mercs[i].Status = 0; + mml->Mercs[i].AltCurrencyCost = RuleB(Mercs, ChargeMercPurchaseCost) ? Merc::CalcPurchaseCost(mercListIter->MercTemplateID, GetLevel(), altCurrentType) : 0; + mml->Mercs[i].AltCurrencyUpkeep = RuleB(Mercs, ChargeMercUpkeepCost) ? Merc::CalcUpkeepCost(mercListIter->MercTemplateID, GetLevel(), altCurrentType) : 0; + mml->Mercs[i].AltCurrencyType = altCurrentType; + mml->Mercs[i].MercUnk01 = 0; + mml->Mercs[i].TimeLeft = -1; + mml->Mercs[i].MerchantSlot = i + 1; + mml->Mercs[i].MercUnk02 = 1; + int mercStanceCount = 0; + std::list::iterator iter = zone->merc_stance_list[mercListIter->MercTemplateID].begin(); + for (iter = zone->merc_stance_list[mercListIter->MercTemplateID].begin(); iter != zone->merc_stance_list[mercListIter->MercTemplateID].end(); ++iter) + { + mercStanceCount++; + } + mml->Mercs[i].StanceCount = mercStanceCount; + mml->Mercs[i].MercUnk03 = 519044964; + mml->Mercs[i].MercUnk04 = 1; + //mml->Mercs[i].MercName; + int stanceindex = 0; + if (mercStanceCount > 0) + { + std::list::iterator iter2 = zone->merc_stance_list[mercListIter->MercTemplateID].begin(); + while (iter2 != zone->merc_stance_list[mercListIter->MercTemplateID].end()) + { + mml->Mercs[i].Stances[stanceindex].StanceIndex = stanceindex; + mml->Mercs[i].Stances[stanceindex].Stance = (iter2->StanceID); + stanceindex++; + ++iter2; + } + } + i++; + } + } + FastQueuePacket(&outapp); + } +} + +void Client::Handle_OP_MercenaryDataUpdateRequest(const EQApplicationPacket *app) +{ + // The payload is 0 bytes. + if (app->size != 0) + { + Message(13, "Size mismatch in OP_MercenaryDataUpdateRequest expected 0 got %i", app->size); + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_MercenaryDataUpdateRequest expected 0 got %i", app->size); + DumpPacket(app); + return; + } + + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: Data Update Request Received."); + + if (GetMercID()) + { + SendMercPersonalInfo(); + } +} + +void Client::Handle_OP_MercenaryDismiss(const EQApplicationPacket *app) +{ + // The payload is 0 or 1 bytes. + if (app->size > 1) + { + Message(13, "Size mismatch in OP_MercenaryDismiss expected 0 got %i", app->size); + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_MercenaryDismiss expected 0 got %i", app->size); + DumpPacket(app); + return; + } + + uint8 Command = 0; + if (app->size > 0) + { + char *InBuffer = (char *)app->pBuffer; + Command = VARSTRUCT_DECODE_TYPE(uint8, InBuffer); + } + + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: Dismiss Request ( %i ) Received.", Command); + + // Handle the dismiss here... + DismissMerc(GetMercInfo().mercid); + +} + +void Client::Handle_OP_MercenaryHire(const EQApplicationPacket *app) +{ + // The payload is 16 bytes. First four bytes are the Merc ID (Template ID) + if (app->size != sizeof(MercenaryMerchantRequest_Struct)) + { + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_MercenaryHire expected %i got %i", sizeof(MercenaryMerchantRequest_Struct), app->size); + + DumpPacket(app); + + return; + } + + MercenaryMerchantRequest_Struct* mmrq = (MercenaryMerchantRequest_Struct*)app->pBuffer; + uint32 merc_template_id = mmrq->MercID; + uint32 merchant_id = mmrq->MercMerchantID; + uint32 merc_unk1 = mmrq->MercUnk01; + uint32 merc_unk2 = mmrq->MercUnk02; + + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: Template ID (%i), Merchant ID (%i), Unknown1 (%i), Unknown2 (%i)", merc_template_id, merchant_id, merc_unk1, merc_unk2); + + //HirePending = true; + SetHoTT(0); + SendTargetCommand(0); + + if (!RuleB(Mercs, AllowMercs)) + return; + + MercTemplate* merc_template = zone->GetMercTemplate(merc_template_id); + + if (merc_template) + { + + Mob* merchant = entity_list.GetNPCByID(merchant_id); + if (!CheckCanHireMerc(merchant, merc_template_id)) + { + return; + } + + // Set time remaining to max on Hire + GetMercInfo().MercTimerRemaining = RuleI(Mercs, UpkeepIntervalMS); + + // Get merc, assign it to client & spawn + Merc* merc = Merc::LoadMerc(this, merc_template, merchant_id, false); + + if (merc) + { + SpawnMerc(merc, true); + merc->Save(); + + if (RuleB(Mercs, ChargeMercPurchaseCost)) + { + uint32 cost = Merc::CalcPurchaseCost(merc_template->MercTemplateID, GetLevel()) * 100; // Cost is in gold + TakeMoneyFromPP(cost, true); + } + + // approved hire request + SendMercMerchantResponsePacket(0); + } + else + { + //merc failed to spawn + SendMercMerchantResponsePacket(3); + } + } + else + { + //merc doesn't exist in db + SendMercMerchantResponsePacket(2); + } +} + +void Client::Handle_OP_MercenarySuspendRequest(const EQApplicationPacket *app) +{ + if (app->size != sizeof(SuspendMercenary_Struct)) + { + Message(13, "Size mismatch in OP_MercenarySuspendRequest expected %i got %i", sizeof(SuspendMercenary_Struct), app->size); + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_MercenarySuspendRequest expected %i got %i", sizeof(SuspendMercenary_Struct), app->size); + DumpPacket(app); + return; + } + + SuspendMercenary_Struct* sm = (SuspendMercenary_Struct*)app->pBuffer; + uint32 merc_suspend = sm->SuspendMerc; // Seen 30 for suspending or unsuspending + + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: Suspend ( %i ) received.", merc_suspend); + + if (!RuleB(Mercs, AllowMercs)) + return; + + // Check if the merc is suspended and if so, unsuspend, otherwise suspend it + SuspendMercCommand(); +} + +void Client::Handle_OP_MercenaryTimerRequest(const EQApplicationPacket *app) +{ + // The payload is 0 bytes. + if (app->size > 1) + { + Message(13, "Size mismatch in OP_MercenaryTimerRequest expected 0 got %i", app->size); + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_MercenaryTimerRequest expected 0 got %i", app->size); + DumpPacket(app); + return; + } + + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: Timer Request received."); + + if (!RuleB(Mercs, AllowMercs)) { + return; + } + + // To Do: Load Mercenary Timer Data to properly populate this reply packet + // All hard set values for now + uint32 entityID = 0; + uint32 mercState = 5; + uint32 suspendedTime = 0; + if (GetMercID()) { + Merc* merc = GetMerc(); + + if (merc) { + entityID = merc->GetID(); + + if (GetMercInfo().IsSuspended) { + mercState = 1; + suspendedTime = GetMercInfo().SuspendedTime; + } + } + } + + if (entityID > 0) { + SendMercTimerPacket(entityID, mercState, suspendedTime, GetMercInfo().MercTimerRemaining, RuleI(Mercs, SuspendIntervalMS)); + } +} + +void Client::Handle_OP_MoveCoin(const EQApplicationPacket *app) +{ + if (app->size != sizeof(MoveCoin_Struct)){ + LogFile->write(EQEMuLog::Error, "Wrong size on OP_MoveCoin. Got: %i, Expected: %i", app->size, sizeof(MoveCoin_Struct)); + DumpPacket(app); + return; + } + OPMoveCoin(app); + return; +} + +void Client::Handle_OP_MoveItem(const EQApplicationPacket *app) +{ + if (!CharacterID()) + { + return; + } + + if (app->size != sizeof(MoveItem_Struct)) { + LogFile->write(EQEMuLog::Error, "Wrong size: OP_MoveItem, size=%i, expected %i", app->size, sizeof(MoveItem_Struct)); + return; + } + + MoveItem_Struct* mi = (MoveItem_Struct*)app->pBuffer; + if (spellend_timer.Enabled() && casting_spell_id && !IsBardSong(casting_spell_id)) + { + if (mi->from_slot != mi->to_slot && (mi->from_slot <= EmuConstants::GENERAL_END || mi->from_slot > 39) && IsValidSlot(mi->from_slot) && IsValidSlot(mi->to_slot)) + { + char *detect = nullptr; + const ItemInst *itm_from = GetInv().GetItem(mi->from_slot); + const ItemInst *itm_to = GetInv().GetItem(mi->to_slot); + MakeAnyLenString(&detect, "Player issued a move item from %u(item id %u) to %u(item id %u) while casting %u.", + mi->from_slot, + itm_from ? itm_from->GetID() : 0, + mi->to_slot, + itm_to ? itm_to->GetID() : 0, + casting_spell_id); + database.SetMQDetectionFlag(AccountName(), GetName(), detect, zone->GetShortName()); + safe_delete_array(detect); + Kick(); // Kick client to prevent client and server from getting out-of-sync inventory slots + return; + } + } + + // Illegal bagslot useage checks. Currently, user only receives a message if this check is triggered. + bool mi_hack = false; + + if (mi->from_slot >= EmuConstants::GENERAL_BAGS_BEGIN && mi->from_slot <= EmuConstants::CURSOR_BAG_END) { + if (mi->from_slot >= EmuConstants::CURSOR_BAG_BEGIN) { mi_hack = true; } + else { + int16 from_parent = m_inv.CalcSlotId(mi->from_slot); + if (!m_inv[from_parent]) { mi_hack = true; } + else if (!m_inv[from_parent]->IsType(ItemClassContainer)) { mi_hack = true; } + else if (m_inv.CalcBagIdx(mi->from_slot) >= m_inv[from_parent]->GetItem()->BagSlots) { mi_hack = true; } + } + } + + if (mi->to_slot >= EmuConstants::GENERAL_BAGS_BEGIN && mi->to_slot <= EmuConstants::CURSOR_BAG_END) { + if (mi->to_slot >= EmuConstants::CURSOR_BAG_BEGIN) { mi_hack = true; } + else { + int16 to_parent = m_inv.CalcSlotId(mi->to_slot); + if (!m_inv[to_parent]) { mi_hack = true; } + else if (!m_inv[to_parent]->IsType(ItemClassContainer)) { mi_hack = true; } + else if (m_inv.CalcBagIdx(mi->to_slot) >= m_inv[to_parent]->GetItem()->BagSlots) { mi_hack = true; } + } + } + + if (mi_hack) { Message(15, "Caution: Illegal use of inaccessable bag slots!"); } + + if (!SwapItem(mi) && IsValidSlot(mi->from_slot) && IsValidSlot(mi->to_slot)) { + SwapItemResync(mi); + + bool error = false; + InterrogateInventory(this, false, true, false, error, false); + if (error) + InterrogateInventory(this, true, false, true, error); + } + + return; +} + +void Client::Handle_OP_OpenContainer(const EQApplicationPacket *app) +{ + // Does not exist in Ti client + // SoF, SoD and UF clients send a 4-byte packet indicating the 'parent' slot + // SoF, SoD and UF slots are defined by a uint32 value and currently untranslated + // RoF client sends a 12-byte packet based on the RoF::Structs::ItemSlotStruct + + // RoF structure types are defined as signed uint16 and currently untranslated + // RoF::struct.SlotType = {0 - Equipment, 1 - Bank, 2 - Shared Bank} // not tested beyond listed types + // RoF::struct.Unknown2 = 0 + // RoF::struct.MainSlot = { } + // RoF::struct.SubSlot = -1 (non-child) + // RoF::struct.AugSlot = -1 (non-child) + // RoF::struct.Unknown1 = 141 (unsure why, but always appears to be this value..combine containers not tested) + + // SideNote: Watching the slot translations, Unknown1 is showing '141' as well on certain item swaps. + // Manually looting a corpse results in a from '34' to '68' value for equipment items, '0' to '0' for inventory. +} + +void Client::Handle_OP_OpenGuildTributeMaster(const EQApplicationPacket *app) +{ + _log(TRIBUTE__IN, "Received OP_OpenGuildTributeMaster of length %d", app->size); + _pkt(TRIBUTE__IN, app); + + if (app->size != sizeof(StartTribute_Struct)) + printf("Error in OP_OpenGuildTributeMaster. Expected size of: %zu, but got: %i\n", sizeof(StartTribute_Struct), app->size); + else { + //Opens the guild tribute master window + StartTribute_Struct* st = (StartTribute_Struct*)app->pBuffer; + Mob* tribmast = entity_list.GetMob(st->tribute_master_id); + if (tribmast && tribmast->IsNPC() && tribmast->GetClass() == GUILD_TRIBUTE_MASTER + && DistNoRoot(*tribmast) <= USE_NPC_RANGE2) { + st->response = 1; + QueuePacket(app); + tribute_master_id = st->tribute_master_id; + DoTributeUpdate(); + } + else { + st->response = 0; + QueuePacket(app); + } + } + return; +} + +void Client::Handle_OP_OpenInventory(const EQApplicationPacket *app) +{ + // Does not exist in Ti, UF or RoF clients + // SoF and SoD both send a 4-byte packet with a uint32 value of '8' +} + +void Client::Handle_OP_OpenTributeMaster(const EQApplicationPacket *app) +{ + _log(TRIBUTE__IN, "Received OP_OpenTributeMaster of length %d", app->size); + _pkt(TRIBUTE__IN, app); + + if (app->size != sizeof(StartTribute_Struct)) + printf("Error in OP_OpenTributeMaster. Expected size of: %zu, but got: %i\n", sizeof(StartTribute_Struct), app->size); + else { + //Opens the tribute master window + StartTribute_Struct* st = (StartTribute_Struct*)app->pBuffer; + Mob* tribmast = entity_list.GetMob(st->tribute_master_id); + if (tribmast && tribmast->IsNPC() && tribmast->GetClass() == TRIBUTE_MASTER + && DistNoRoot(*tribmast) <= USE_NPC_RANGE2) { + st->response = 1; + QueuePacket(app); + tribute_master_id = st->tribute_master_id; + DoTributeUpdate(); + } + else { + st->response = 0; + QueuePacket(app); + } + } + return; +} + +void Client::Handle_OP_PDeletePetition(const EQApplicationPacket *app) +{ + if (app->size < 2) { + LogFile->write(EQEMuLog::Error, "Wrong size: OP_PDeletePetition, size=%i, expected %i", app->size, 2); + return; + } + if (petition_list.DeletePetitionByCharName((char*)app->pBuffer)) + Message_StringID(0, PETITION_DELETED); + else + Message_StringID(0, PETITION_NO_DELETE); + return; +} + +void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) +{ + if (app->size != sizeof(PetCommand_Struct)) { + LogFile->write(EQEMuLog::Error, "Wrong size: OP_PetCommands, size=%i, expected %i", app->size, sizeof(PetCommand_Struct)); + return; + } + char val1[20] = { 0 }; + PetCommand_Struct* pet = (PetCommand_Struct*)app->pBuffer; + Mob* mypet = this->GetPet(); + + if (!mypet || pet->command == PET_LEADER) + { + if (pet->command == PET_LEADER) + { + if (mypet && (!GetTarget() || GetTarget() == mypet)) + { + mypet->Say_StringID(PET_LEADERIS, GetName()); + } + else if ((mypet = GetTarget())) + { + Mob *Owner = mypet->GetOwner(); + if (Owner) + mypet->Say_StringID(PET_LEADERIS, Owner->GetCleanName()); + else + mypet->Say_StringID(I_FOLLOW_NOONE); + } + } + + 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; + + uint32 PetCommand = pet->command; + + // Handle Sit/Stand toggle in UF and later. + if (GetClientVersion() >= EQClientUnderfoot) + { + if (PetCommand == PET_SITDOWN) + if (mypet->GetPetOrder() == SPO_Sit) + PetCommand = PET_STANDUP; + } + + switch (PetCommand) + { + case PET_ATTACK: { + if (!GetTarget()) + break; + if (GetTarget()->IsMezzed()) { + Message_StringID(10, CANNOT_WAKE, mypet->GetCleanName(), GetTarget()->GetCleanName()); + break; + } + if (mypet->IsFeared()) + break; //prevent pet from attacking stuff while feared + + if (!mypet->IsAttackAllowed(GetTarget())) { + mypet->Say_StringID(NOT_LEGAL_TARGET); + break; + } + + if ((mypet->GetPetType() == petAnimation && GetAA(aaAnimationEmpathy) >= 2) || mypet->GetPetType() != petAnimation) { + if (GetTarget() != this && mypet->DistNoRootNoZ(*GetTarget()) <= (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(GetTarget()); + } + } + zone->AddAggroMob(); + mypet->AddToHateList(GetTarget(), 1); + Message_StringID(MT_PetResponse, PET_ATTACKING, mypet->GetCleanName(), GetTarget()->GetCleanName()); + } + } + break; + } + 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); + mypet->WipeHateList(); + mypet->SetTarget(nullptr); + } + 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()); + break; + } + case PET_GETLOST: { + if (mypet->Charmed()) + break; + if (mypet->GetPetType() == petCharmed || !mypet->IsNPC()) { + // eqlive ignores this command + // we could just remove the charm + // and continue + mypet->BuffFadeByEffect(SE_Charm); + break; + } + else { + SetPet(nullptr); + } + + mypet->Say_StringID(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. + /* + if((mypet->GetPetType() == petAnimation && GetAA(aaAnimationEmpathy) >= 2) || mypet->GetPetType() != petAnimation) { + mypet->Say_StringID(PET_GETLOST_STRING); + mypet->CastToNPC()->Depop(); + } + */ + + break; + } + 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->IsNPC()) { + mypet->SetHeld(false); + mypet->Say_StringID(MT_PetResponse, PET_GUARDINGLIFE); + mypet->SetPetOrder(SPO_Guard); + mypet->CastToNPC()->SaveGuardSpot(); + } + } + break; + } + 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); + mypet->SetPetOrder(SPO_Follow); + mypet->SendAppearancePacket(AT_Anim, ANIM_STAND); + } + break; + } + case PET_TAUNT: { + if ((mypet->GetPetType() == petAnimation && GetAA(aaAnimationEmpathy) >= 3) || mypet->GetPetType() != petAnimation) { + Message_StringID(MT_PetResponse, PET_DO_TAUNT); + mypet->CastToNPC()->SetTaunting(true); + } + break; + } + case PET_NOTAUNT: { + if ((mypet->GetPetType() == petAnimation && GetAA(aaAnimationEmpathy) >= 3) || mypet->GetPetType() != petAnimation) { + Message_StringID(MT_PetResponse, PET_NO_TAUNT); + mypet->CastToNPC()->SetTaunting(false); + } + break; + } + 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); + mypet->SetPetOrder(SPO_Follow); + mypet->SendAppearancePacket(AT_Anim, ANIM_STAND); + } + break; + } + 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); + 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_SIT); + } + break; + } + 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); + mypet->SetPetOrder(SPO_Follow); + mypet->SendAppearancePacket(AT_Anim, ANIM_STAND); + } + break; + } + case PET_SLUMBER: { + 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_HOLD: { + if (GetAA(aaPetDiscipline) && mypet->IsNPC()){ + if (mypet->IsFeared()) + break; //could be exploited like PET_BACKOFF + + mypet->Say_StringID(MT_PetResponse, PET_ON_HOLD); + mypet->WipeHateList(); + mypet->SetHeld(true); + } + break; + } + case PET_HOLD_ON: { + if (GetAA(aaPetDiscipline) && mypet->IsNPC() && !mypet->IsHeld()) { + if (mypet->IsFeared()) + break; //could be exploited like PET_BACKOFF + + mypet->Say_StringID(MT_PetResponse, PET_ON_HOLD); + mypet->WipeHateList(); + mypet->SetHeld(true); + } + break; + } + case PET_HOLD_OFF: { + if (GetAA(aaPetDiscipline) && mypet->IsNPC() && mypet->IsHeld()) + mypet->SetHeld(false); + break; + } + case PET_NOCAST: { + if (GetAA(aaAdvancedPetDiscipline) == 2 && mypet->IsNPC()) { + if (mypet->IsFeared()) + break; + if (mypet->IsNoCast()) { + Message_StringID(MT_PetResponse, PET_CASTING); + mypet->CastToNPC()->SetNoCast(false); + } + else { + Message_StringID(MT_PetResponse, PET_NOT_CASTING); + mypet->CastToNPC()->SetNoCast(true); + } + } + break; + } + case PET_FOCUS: { + if (GetAA(aaAdvancedPetDiscipline) >= 1 && mypet->IsNPC()) { + if (mypet->IsFeared()) + break; + if (mypet->IsFocused()) { + Message_StringID(MT_PetResponse, PET_NOT_FOCUSING); + mypet->CastToNPC()->SetFocused(false); + } + else { + Message_StringID(MT_PetResponse, PET_NOW_FOCUSING); + mypet->CastToNPC()->SetFocused(true); + } + } + break; + } + case PET_FOCUS_ON: { + if (GetAA(aaAdvancedPetDiscipline) >= 1 && mypet->IsNPC()) { + if (mypet->IsFeared()) + break; + if (!mypet->IsFocused()) { + Message_StringID(MT_PetResponse, PET_NOW_FOCUSING); + mypet->CastToNPC()->SetFocused(true); + } + } + break; + } + case PET_FOCUS_OFF: { + if (GetAA(aaAdvancedPetDiscipline) >= 1 && mypet->IsNPC()) { + if (mypet->IsFeared()) + break; + if (mypet->IsFocused()) { + Message_StringID(MT_PetResponse, PET_NOT_FOCUSING); + mypet->CastToNPC()->SetFocused(false); + } + } + break; + } + default: + printf("Client attempted to use a unknown pet command:\n"); + break; + } +} + +void Client::Handle_OP_Petition(const EQApplicationPacket *app) +{ + if (app->size <= 1) + return; + if (!worldserver.Connected()) + Message(0, "Error: World server disconnected"); + /*else if(petition_list.FindPetitionByAccountName(this->AccountName())) + { + Message(0,"You already have a petition in queue, you cannot petition again until this one has been responded to or you have deleted the petition."); + return; + }*/ + else + { + if (petition_list.FindPetitionByAccountName(AccountName())) + { + Message(0, "You already have a petition in the queue, you must wait for it to be answered or use /deletepetition to delete it."); + return; + } + Petition* pet = new Petition(CharacterID()); + pet->SetAName(this->AccountName()); + pet->SetClass(this->GetClass()); + pet->SetLevel(this->GetLevel()); + pet->SetCName(this->GetName()); + pet->SetRace(this->GetRace()); + pet->SetLastGM(""); + pet->SetCName(this->GetName()); + pet->SetPetitionText((char*)app->pBuffer); + pet->SetZone(zone->GetZoneID()); + pet->SetUrgency(0); + petition_list.AddPetition(pet); + database.InsertPetitionToDB(pet); + petition_list.UpdateGMQueue(); + petition_list.UpdateZoneListQueue(); + worldserver.SendEmoteMessage(0, 0, 80, 15, "%s has made a petition. #%i", GetName(), pet->GetID()); + } + return; +} + +void Client::Handle_OP_PetitionBug(const EQApplicationPacket *app) +{ + if (app->size != sizeof(PetitionBug_Struct)) + printf("Wrong size of BugStruct! Expected: %zu, Got: %i\n", sizeof(PetitionBug_Struct), app->size); + else{ + Message(0, "Petition Bugs are not supported, please use /bug."); + } + return; +} + +void Client::Handle_OP_PetitionCheckIn(const EQApplicationPacket *app) +{ + if (app->size != sizeof(Petition_Struct)) { + LogFile->write(EQEMuLog::Error, "Wrong size: OP_PetitionCheckIn, size=%i, expected %i", app->size, sizeof(Petition_Struct)); + return; + } + Petition_Struct* inpet = (Petition_Struct*)app->pBuffer; + + Petition* pet = petition_list.GetPetitionByID(inpet->petnumber); + //if (inpet->urgency != pet->GetUrgency()) + pet->SetUrgency(inpet->urgency); + pet->SetLastGM(this->GetName()); + pet->SetGMText(inpet->gmtext); + + pet->SetCheckedOut(false); + petition_list.UpdatePetition(pet); + petition_list.UpdateGMQueue(); + petition_list.UpdateZoneListQueue(); + return; +} + +void Client::Handle_OP_PetitionCheckout(const EQApplicationPacket *app) +{ + if (app->size != sizeof(uint32)) { + std::cout << "Wrong size: OP_PetitionCheckout, size=" << app->size << ", expected " << sizeof(uint32) << std::endl; + return; + } + if (!worldserver.Connected()) + Message(0, "Error: World server disconnected"); + else { + uint32 getpetnum = *((uint32*)app->pBuffer); + Petition* getpet = petition_list.GetPetitionByID(getpetnum); + if (getpet != 0) { + getpet->AddCheckout(); + getpet->SetCheckedOut(true); + getpet->SendPetitionToPlayer(this->CastToClient()); + petition_list.UpdatePetition(getpet); + petition_list.UpdateGMQueue(); + petition_list.UpdateZoneListQueue(); + } + } + return; +} + +void Client::Handle_OP_PetitionDelete(const EQApplicationPacket *app) +{ + if (app->size != sizeof(PetitionUpdate_Struct)) { + LogFile->write(EQEMuLog::Error, "Wrong size: OP_PetitionDelete, size=%i, expected %i", app->size, sizeof(PetitionUpdate_Struct)); + return; + } + EQApplicationPacket* outapp = new EQApplicationPacket(OP_PetitionUpdate, sizeof(PetitionUpdate_Struct)); + PetitionUpdate_Struct* pet = (PetitionUpdate_Struct*)outapp->pBuffer; + pet->petnumber = *((int*)app->pBuffer); + pet->color = 0x00; + pet->status = 0xFFFFFFFF; + pet->senttime = 0; + strcpy(pet->accountid, ""); + strcpy(pet->gmsenttoo, ""); + pet->quetotal = petition_list.GetTotalPetitions(); + strcpy(pet->charname, ""); + FastQueuePacket(&outapp); + + if (petition_list.DeletePetition(pet->petnumber) == -1) + std::cout << "Something is borked with: " << pet->petnumber << std::endl; + petition_list.ClearPetitions(); + petition_list.UpdateGMQueue(); + petition_list.ReadDatabase(); + petition_list.UpdateZoneListQueue(); + return; +} + +void Client::Handle_OP_PetitionQue(const EQApplicationPacket *app) +{ +#ifdef _EQDEBUG + printf("%s looking at petitions..\n", this->GetName()); +#endif + return; +} + +void Client::Handle_OP_PetitionRefresh(const EQApplicationPacket *app) +{ + // This is When Client Asks for Petition Again and Again... + // break is here because it floods the zones and causes lag if it + // Were to actually do something:P We update on our own schedule now. + return; +} + +void Client::Handle_OP_PetitionResolve(const EQApplicationPacket *app) +{ + Handle_OP_PetitionDelete(app); +} + +void Client::Handle_OP_PetitionUnCheckout(const EQApplicationPacket *app) +{ + if (app->size != sizeof(uint32)) { + std::cout << "Wrong size: OP_PetitionUnCheckout, size=" << app->size << ", expected " << sizeof(uint32) << std::endl; + return; + } + if (!worldserver.Connected()) + Message(0, "Error: World server disconnected"); + else { + uint32 getpetnum = *((uint32*)app->pBuffer); + Petition* getpet = petition_list.GetPetitionByID(getpetnum); + if (getpet != 0) { + getpet->SetCheckedOut(false); + petition_list.UpdatePetition(getpet); + petition_list.UpdateGMQueue(); + petition_list.UpdateZoneListQueue(); + } + } + return; +} + +void Client::Handle_OP_PickPocket(const EQApplicationPacket *app) +{ + if (app->size != sizeof(PickPocket_Struct)) + { + LogFile->write(EQEMuLog::Error, "Size mismatch for Pick Pocket packet"); + DumpPacket(app); + } + + if (!HasSkill(SkillPickPockets)) + { + return; + } + + if (!p_timers.Expired(&database, pTimerBeggingPickPocket, false)) + { + Message(13, "Ability recovery time not yet met."); + database.SetMQDetectionFlag(this->AccountName(), this->GetName(), "OP_PickPocket was sent again too quickly.", zone->GetShortName()); + return; + } + PickPocket_Struct* pick_in = (PickPocket_Struct*)app->pBuffer; + + Mob* victim = entity_list.GetMob(pick_in->to); + if (!victim) + return; + + p_timers.Start(pTimerBeggingPickPocket, 8); + if (victim == this){ + Message(0, "You catch yourself red-handed."); + EQApplicationPacket* outapp = new EQApplicationPacket(OP_PickPocket, sizeof(sPickPocket_Struct)); + sPickPocket_Struct* pick_out = (sPickPocket_Struct*)outapp->pBuffer; + pick_out->coin = 0; + pick_out->from = victim->GetID(); + pick_out->to = GetID(); + pick_out->myskill = GetSkill(SkillPickPockets); + pick_out->type = 0; + //if we do not send this packet the client will lock up and require the player to relog. + QueuePacket(outapp); + safe_delete(outapp); + } + else if (victim->GetOwnerID()){ + Message(0, "You cannot steal from pets!"); + EQApplicationPacket* outapp = new EQApplicationPacket(OP_PickPocket, sizeof(sPickPocket_Struct)); + sPickPocket_Struct* pick_out = (sPickPocket_Struct*)outapp->pBuffer; + pick_out->coin = 0; + pick_out->from = victim->GetID(); + pick_out->to = GetID(); + pick_out->myskill = GetSkill(SkillPickPockets); + pick_out->type = 0; + //if we do not send this packet the client will lock up and require the player to relog. + QueuePacket(outapp); + safe_delete(outapp); + } + else if (victim->IsNPC()){ + victim->CastToNPC()->PickPocket(this); + } + else{ + Message(0, "Stealing from clients not yet supported."); + EQApplicationPacket* outapp = new EQApplicationPacket(OP_PickPocket, sizeof(sPickPocket_Struct)); + sPickPocket_Struct* pick_out = (sPickPocket_Struct*)outapp->pBuffer; + pick_out->coin = 0; + pick_out->from = victim->GetID(); + pick_out->to = GetID(); + pick_out->myskill = GetSkill(SkillPickPockets); + pick_out->type = 0; + //if we do not send this packet the client will lock up and require the player to relog. + QueuePacket(outapp); + safe_delete(outapp); + } +} + +void Client::Handle_OP_PopupResponse(const EQApplicationPacket *app) +{ + + if (app->size != sizeof(PopupResponse_Struct)) { + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_PopupResponse expected %i got %i", + sizeof(PopupResponse_Struct), app->size); + DumpPacket(app); + return; + } + PopupResponse_Struct *prs = (PopupResponse_Struct*)app->pBuffer; + + // Handle any EQEmu defined popup Ids first + switch (prs->popupid) + { + case POPUPID_UPDATE_SHOWSTATSWINDOW: + if (GetTarget() && GetTarget()->IsClient()) + GetTarget()->CastToClient()->SendStatsWindow(this, true); + else + SendStatsWindow(this, true); + return; + + default: + break; + } + + char buf[16]; + sprintf(buf, "%d\0", prs->popupid); + + parse->EventPlayer(EVENT_POPUP_RESPONSE, this, buf, 0); + + Mob* Target = GetTarget(); + if (Target && Target->IsNPC()) { + parse->EventNPC(EVENT_POPUP_RESPONSE, Target->CastToNPC(), this, buf, 0); + } +} + +void Client::Handle_OP_PotionBelt(const EQApplicationPacket *app) +{ + if (app->size != sizeof(MovePotionToBelt_Struct)) { + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_PotionBelt expected %i got %i", + sizeof(MovePotionToBelt_Struct), app->size); + DumpPacket(app); + return; + } + + MovePotionToBelt_Struct *mptbs = (MovePotionToBelt_Struct*)app->pBuffer; + if(!EQEmu::ValueWithin(mptbs->SlotNumber, 0U, 3U)) { + LogFile->write(EQEMuLog::Debug, "Client::Handle_OP_PotionBelt mptbs->SlotNumber out of range."); + return; + } + + if (mptbs->Action == 0) { + const Item_Struct *BaseItem = database.GetItem(mptbs->ItemID); + if (BaseItem) { + m_pp.potionbelt.items[mptbs->SlotNumber].item_id = BaseItem->ID; + m_pp.potionbelt.items[mptbs->SlotNumber].icon = BaseItem->Icon; + strn0cpy(m_pp.potionbelt.items[mptbs->SlotNumber].item_name, BaseItem->Name, sizeof(BaseItem->Name)); + database.SaveCharacterPotionBelt(this->CharacterID(), mptbs->SlotNumber, m_pp.potionbelt.items[mptbs->SlotNumber].item_id, m_pp.potionbelt.items[mptbs->SlotNumber].icon); + } + } + else { + m_pp.potionbelt.items[mptbs->SlotNumber].item_id = 0; + m_pp.potionbelt.items[mptbs->SlotNumber].icon = 0; + strncpy(m_pp.potionbelt.items[mptbs->SlotNumber].item_name, "\0", 1); + } +} + +void Client::Handle_OP_PurchaseLeadershipAA(const EQApplicationPacket *app) +{ + if (app->size != sizeof(uint32)) { + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_LeadershipExpToggle expected %i got %i", 1, app->size); + DumpPacket(app); + return; + } + uint32 aaid = *((uint32 *)app->pBuffer); + + if (aaid >= _maxLeaderAA) + return; + + uint32 current_rank = m_pp.leader_abilities.ranks[aaid]; + if (current_rank >= MAX_LEADERSHIP_TIERS) { + Message(13, "This ability can be trained no further."); + return; + } + + uint8 cost = LeadershipAACosts[aaid][current_rank]; + if (cost == 0) { + Message(13, "This ability can be trained no further."); + return; + } + + //TODO: we need to enforce prerequisits + + if (aaid >= raidAAMarkNPC) { + //it is a raid ability. + if (cost > m_pp.raid_leadership_points) { + Message(13, "You do not have enough points to purchase this ability."); + return; + } + + //sell them the ability. + m_pp.raid_leadership_points -= cost; + m_pp.leader_abilities.ranks[aaid]++; + + database.SaveCharacterLeadershipAA(this->CharacterID(), &m_pp); + } else { + //it is a group ability. + if (cost > m_pp.group_leadership_points) { + Message(13, "You do not have enough points to purchase this ability."); + return; + } + + //sell them the ability. + m_pp.group_leadership_points -= cost; + m_pp.leader_abilities.ranks[aaid]++; + + database.SaveCharacterLeadershipAA(this->CharacterID(), &m_pp); + } + + //success, send them an update + EQApplicationPacket *outapp = new EQApplicationPacket(OP_UpdateLeadershipAA, sizeof(UpdateLeadershipAA_Struct)); + UpdateLeadershipAA_Struct *u = (UpdateLeadershipAA_Struct *)outapp->pBuffer; + u->ability_id = aaid; + u->new_rank = m_pp.leader_abilities.ranks[aaid]; + if (aaid >= raidAAMarkNPC) // raid AA + u->pointsleft = m_pp.raid_leadership_points; + else // group AA + u->pointsleft = m_pp.group_leadership_points; + FastQueuePacket(&outapp); + + // Update all group members with the new AA the leader has purchased. + if (IsRaidGrouped()) { + Raid *r = GetRaid(); + if (!r) + return; + if (aaid >= raidAAMarkNPC) { + r->UpdateRaidAAs(); + r->SendAllRaidLeadershipAA(); + } else { + uint32 gid = r->GetGroup(this); + r->UpdateGroupAAs(gid); + r->GroupUpdate(gid, false); + } + } else if (IsGrouped()) { + Group *g = GetGroup(); + if (!g) + return; + g->UpdateGroupAAs(); + g->SendLeadershipAAUpdate(); + } + +} + +void Client::Handle_OP_PVPLeaderBoardDetailsRequest(const EQApplicationPacket *app) +{ + // This opcode is sent by the client when the player right clicks a name on the PVP leaderboard and sends + // further details about the selected player, e.g. Race/Class/AAs/Guild etc. + // + if (app->size != sizeof(PVPLeaderBoardDetailsRequest_Struct)) + { + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_PVPLeaderBoardDetailsRequest expected %i got %i", + sizeof(PVPLeaderBoardDetailsRequest_Struct), app->size); + + DumpPacket(app); + + return; + } + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_PVPLeaderBoardDetailsReply, sizeof(PVPLeaderBoardDetailsReply_Struct)); + PVPLeaderBoardDetailsReply_Struct *pvplbdrs = (PVPLeaderBoardDetailsReply_Struct *)outapp->pBuffer; + + // TODO: Record and send this data. + + QueuePacket(outapp); + safe_delete(outapp); +} + +void Client::Handle_OP_PVPLeaderBoardRequest(const EQApplicationPacket *app) +{ + // This Opcode is sent by the client when the Leaderboard button on the PVP Stats window is pressed. + // + // It has a single uint32 payload which is the sort method: + // + // PVPSortByKills = 0, PVPSortByPoints = 1, PVPSortByInfamy = 2 + // + if (app->size != sizeof(PVPLeaderBoardRequest_Struct)) + { + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_PVPLeaderBoardRequest expected %i got %i", + sizeof(PVPLeaderBoardRequest_Struct), app->size); + + DumpPacket(app); + + return; + } + /*PVPLeaderBoardRequest_Struct *pvplbrs = (PVPLeaderBoardRequest_Struct *)app->pBuffer;*/ //unused + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_PVPLeaderBoardReply, sizeof(PVPLeaderBoard_Struct)); + /*PVPLeaderBoard_Struct *pvplb = (PVPLeaderBoard_Struct *)outapp->pBuffer;*/ //unused + + // TODO: Record and send this data. + + QueuePacket(outapp); + safe_delete(outapp); +} + +void Client::Handle_OP_RaidCommand(const EQApplicationPacket *app) +{ + if (app->size < sizeof(RaidGeneral_Struct)) { + LogFile->write(EQEMuLog::Error, "Wrong size: OP_RaidCommand, size=%i, expected at least %i", app->size, sizeof(RaidGeneral_Struct)); + DumpPacket(app); + return; + } + + RaidGeneral_Struct *ri = (RaidGeneral_Struct*)app->pBuffer; + switch (ri->action) + { + case RaidCommandInviteIntoExisting: + case RaidCommandInvite: { + Client *i = entity_list.GetClientByName(ri->player_name); + if (!i) + break; + Group *g = i->GetGroup(); + // These two messages should be generated by the client I think, just do this for now + if (i->HasRaid()) { + Message(13, "%s is already in a raid.", i->GetName()); + break; + } + if (g && !g->IsLeader(i)) { + Message(13, "You can only invite an ungrouped player or group leader to join your raid."); + break; + } + //This sends an "invite" to the client in question. + EQApplicationPacket* outapp = new EQApplicationPacket(OP_RaidUpdate, sizeof(RaidGeneral_Struct)); + RaidGeneral_Struct *rg = (RaidGeneral_Struct*)outapp->pBuffer; + strn0cpy(rg->leader_name, ri->leader_name, 64); + strn0cpy(rg->player_name, ri->player_name, 64); + + rg->parameter = 0; + rg->action = 20; + i->QueuePacket(outapp); + safe_delete(outapp); + break; + } + case RaidCommandAcceptInvite: { + Client *i = entity_list.GetClientByName(ri->player_name); + if (i){ + if (IsRaidGrouped()){ + i->Message_StringID(0, ALREADY_IN_RAID, GetName()); //group failed, must invite members not in raid... + return; + } + Raid *r = entity_list.GetRaidByClient(i); + if (r){ + r->VerifyRaid(); + Group *g = GetGroup(); + if (g){ + if (g->GroupCount() + r->RaidCount() > MAX_RAID_MEMBERS) + { + i->Message(13, "Invite failed, group invite would create a raid larger than the maximum number of members allowed."); + return; + } + } + else{ + if (1 + r->RaidCount() > MAX_RAID_MEMBERS) + { + i->Message(13, "Invite failed, member invite would create a raid larger than the maximum number of members allowed."); + return; + } + } + if (g){//add us all + uint32 freeGroup = r->GetFreeGroup(); + Client *addClient = nullptr; + for (int x = 0; x < 6; x++) + { + if (g->members[x]){ + Client *c = nullptr; + if (g->members[x]->IsClient()) + c = g->members[x]->CastToClient(); + else + continue; + + if (!addClient) + { + addClient = c; + r->SetGroupLeader(addClient->GetName()); + } + + r->SendRaidCreate(c); + r->SendMakeLeaderPacketTo(r->leadername, c); + if (g->IsLeader(g->members[x])) + r->AddMember(c, freeGroup, false, true); + else + r->AddMember(c, freeGroup); + r->SendBulkRaid(c); + if (r->IsLocked()) { + r->SendRaidLockTo(c); + } + } + } + g->DisbandGroup(); + r->GroupUpdate(freeGroup); + } + else{ + r->SendRaidCreate(this); + r->SendMakeLeaderPacketTo(r->leadername, this); + r->AddMember(this); + r->SendBulkRaid(this); + if (r->IsLocked()) { + r->SendRaidLockTo(this); + } + } + } + else + { + Group *ig = i->GetGroup(); + Group *g = GetGroup(); + if (g) //if our target has a group + { + r = new Raid(i); + entity_list.AddRaid(r); + r->SetRaidDetails(); + + uint32 groupFree = r->GetFreeGroup(); //get a free group + if (ig){ //if we already have a group then cycle through adding us... + Client *addClientig = nullptr; + for (int x = 0; x < 6; x++) + { + if (ig->members[x]){ + if (!addClientig){ + if (ig->members[x]->IsClient()){ + addClientig = ig->members[x]->CastToClient(); + r->SetGroupLeader(addClientig->GetName()); + } + } + if (ig->IsLeader(ig->members[x])){ + Client *c = nullptr; + if (ig->members[x]->IsClient()) + c = ig->members[x]->CastToClient(); + else + continue; + r->SendRaidCreate(c); + r->SendMakeLeaderPacketTo(r->leadername, c); + r->AddMember(c, groupFree, true, true, true); + r->SendBulkRaid(c); + if (r->IsLocked()) { + r->SendRaidLockTo(c); + } + } + else{ + Client *c = nullptr; + if (ig->members[x]->IsClient()) + c = ig->members[x]->CastToClient(); + else + continue; + r->SendRaidCreate(c); + r->SendMakeLeaderPacketTo(r->leadername, c); + r->AddMember(c, groupFree); + r->SendBulkRaid(c); + if (r->IsLocked()) { + r->SendRaidLockTo(c); + } + } + } + } + ig->DisbandGroup(); + r->GroupUpdate(groupFree); + groupFree = r->GetFreeGroup(); + } + else{ //else just add the inviter + r->SendRaidCreate(i); + r->AddMember(i, 0xFFFFFFFF, true, false, true); + } + + Client *addClient = nullptr; + //now add the existing group + for (int x = 0; x < 6; x++) + { + if (g->members[x]){ + if (!addClient) + { + if (g->members[x]->IsClient()){ + addClient = g->members[x]->CastToClient(); + r->SetGroupLeader(addClient->GetName()); + } + } + if (g->IsLeader(g->members[x])) + { + Client *c = nullptr; + if (g->members[x]->IsClient()) + c = g->members[x]->CastToClient(); + else + continue; + r->SendRaidCreate(c); + r->SendMakeLeaderPacketTo(r->leadername, c); + r->AddMember(c, groupFree, false, true); + r->SendBulkRaid(c); + if (r->IsLocked()) { + r->SendRaidLockTo(c); + } + } + else + { + Client *c = nullptr; + if (g->members[x]->IsClient()) + c = g->members[x]->CastToClient(); + else + continue; + r->SendRaidCreate(c); + r->SendMakeLeaderPacketTo(r->leadername, c); + r->AddMember(c, groupFree); + r->SendBulkRaid(c); + if (r->IsLocked()) { + r->SendRaidLockTo(c); + } + } + } + } + g->DisbandGroup(); + r->GroupUpdate(groupFree); + } + else + { + if (ig){ + r = new Raid(i); + entity_list.AddRaid(r); + r->SetRaidDetails(); + Client *addClientig = nullptr; + for (int x = 0; x < 6; x++) + { + if (ig->members[x]) + { + if (!addClientig){ + if (ig->members[x]->IsClient()){ + addClientig = ig->members[x]->CastToClient(); + r->SetGroupLeader(addClientig->GetName()); + } + } + if (ig->IsLeader(ig->members[x])) + { + Client *c = nullptr; + if (ig->members[x]->IsClient()) + c = ig->members[x]->CastToClient(); + else + continue; + + r->SendRaidCreate(c); + r->SendMakeLeaderPacketTo(r->leadername, c); + r->AddMember(c, 0, true, true, true); + r->SendBulkRaid(c); + if (r->IsLocked()) { + r->SendRaidLockTo(c); + } + } + else + { + Client *c = nullptr; + if (ig->members[x]->IsClient()) + c = ig->members[x]->CastToClient(); + else + continue; + + r->SendRaidCreate(c); + r->SendMakeLeaderPacketTo(r->leadername, c); + r->AddMember(c, 0); + r->SendBulkRaid(c); + if (r->IsLocked()) { + r->SendRaidLockTo(c); + } + } + } + } + r->SendRaidCreate(this); + r->SendMakeLeaderPacketTo(r->leadername, this); + r->SendBulkRaid(this); + r->AddMember(this); + ig->DisbandGroup(); + r->GroupUpdate(0); + if (r->IsLocked()) { + r->SendRaidLockTo(this); + } + } + else{ + r = new Raid(i); + entity_list.AddRaid(r); + r->SetRaidDetails(); + r->SendRaidCreate(i); + r->SendRaidCreate(this); + r->SendMakeLeaderPacketTo(r->leadername, this); + r->AddMember(i, 0xFFFFFFFF, true, false, true); + r->SendBulkRaid(this); + r->AddMember(this); + if (r->IsLocked()) { + r->SendRaidLockTo(this); + } + } + } + } + } + break; + } + case RaidCommandDisband: { + Raid *r = entity_list.GetRaidByClient(this); + if (r){ + //if(this == r->GetLeader()){ + uint32 grp = r->GetGroup(ri->leader_name); + + if (grp < 12){ + uint32 i = r->GetPlayerIndex(ri->leader_name); + if (r->members[i].IsGroupLeader){ //assign group leader to someone else + for (int x = 0; x < MAX_RAID_MEMBERS; x++){ + if (strlen(r->members[x].membername) > 0 && i != x){ + if (r->members[x].GroupNumber == grp){ + r->SetGroupLeader(ri->leader_name, false); + r->SetGroupLeader(r->members[x].membername); + r->UpdateGroupAAs(grp); + break; + } + } + } + + } + if (r->members[i].IsRaidLeader){ + for (int x = 0; x < MAX_RAID_MEMBERS; x++){ + if (strlen(r->members[x].membername) > 0 && strcmp(r->members[x].membername, r->members[i].membername) != 0) + { + r->SetRaidLeader(r->members[i].membername, r->members[x].membername); + r->UpdateRaidAAs(); + r->SendAllRaidLeadershipAA(); + break; + } + } + } + } + + r->RemoveMember(ri->leader_name); + Client *c = entity_list.GetClientByName(ri->leader_name); + if (c) + r->SendGroupDisband(c); + else{ + ServerPacket *pack = new ServerPacket(ServerOP_RaidGroupDisband, sizeof(ServerRaidGeneralAction_Struct)); + ServerRaidGeneralAction_Struct* rga = (ServerRaidGeneralAction_Struct*)pack->pBuffer; + rga->rid = GetID(); + rga->zoneid = zone->GetZoneID(); + rga->instance_id = zone->GetInstanceID(); + strn0cpy(rga->playername, ri->leader_name, 64); + worldserver.SendPacket(pack); + safe_delete(pack); + } + //r->SendRaidGroupRemove(ri->leader_name, grp); + r->GroupUpdate(grp);// break + //} + } + break; + } + case RaidCommandMoveGroup: + { + Raid *r = entity_list.GetRaidByClient(this); + if (r) + { + if (ri->parameter < 12) //moving to a group + { + uint8 grpcount = r->GroupCount(ri->parameter); + + if (grpcount < 6) + { + Client *c = entity_list.GetClientByName(ri->leader_name); + uint32 oldgrp = r->GetGroup(ri->leader_name); + if (ri->parameter == oldgrp) //don't rejoin grp if we order to join same group. + break; + + if (r->members[r->GetPlayerIndex(ri->leader_name)].IsGroupLeader) + { + r->SetGroupLeader(ri->leader_name, false); + if (oldgrp < 12){ //we were the leader of our old grp + for (int x = 0; x < MAX_RAID_MEMBERS; x++) //assign a new grp leader if we can + { + if (r->members[x].GroupNumber == oldgrp) + { + if (strcmp(ri->leader_name, r->members[x].membername) != 0 && strlen(ri->leader_name) > 0) + { + r->SetGroupLeader(r->members[x].membername); + r->UpdateGroupAAs(oldgrp); + Client *cgl = entity_list.GetClientByName(r->members[x].membername); + if (cgl){ + r->SendRaidRemove(r->members[x].membername, cgl); + r->SendRaidCreate(cgl); + r->SendMakeLeaderPacketTo(r->leadername, cgl); + r->SendRaidAdd(r->members[x].membername, cgl); + r->SendBulkRaid(cgl); + if (r->IsLocked()) { + r->SendRaidLockTo(cgl); + } + } + else{ + ServerPacket *pack = new ServerPacket(ServerOP_RaidChangeGroup, sizeof(ServerRaidGeneralAction_Struct)); + ServerRaidGeneralAction_Struct *rga = (ServerRaidGeneralAction_Struct*)pack->pBuffer; + rga->rid = r->GetID(); + strn0cpy(rga->playername, r->members[x].membername, 64); + rga->zoneid = zone->GetZoneID(); + rga->instance_id = zone->GetInstanceID(); + worldserver.SendPacket(pack); + safe_delete(pack); + } + break; + } + } + } + } + } + if (grpcount == 0) { + r->SetGroupLeader(ri->leader_name); + r->UpdateGroupAAs(ri->parameter); + } + + r->MoveMember(ri->leader_name, ri->parameter); + if (c){ + r->SendGroupDisband(c); + } + else{ + ServerPacket *pack = new ServerPacket(ServerOP_RaidGroupDisband, sizeof(ServerRaidGeneralAction_Struct)); + ServerRaidGeneralAction_Struct* rga = (ServerRaidGeneralAction_Struct*)pack->pBuffer; + rga->rid = r->GetID(); + rga->zoneid = zone->GetZoneID(); + rga->instance_id = zone->GetInstanceID(); + strn0cpy(rga->playername, ri->leader_name, 64); + worldserver.SendPacket(pack); + safe_delete(pack); + } + //r->SendRaidGroupAdd(ri->leader_name, ri->parameter); + //r->SendRaidGroupRemove(ri->leader_name, oldgrp); + //r->SendGroupUpdate(c); + //break + r->GroupUpdate(ri->parameter); //send group update to our new group + if (oldgrp < 12) //if our old was a group send update there too + r->GroupUpdate(oldgrp); + + //r->SendMakeGroupLeaderPacketAll(); + } + } + else //moving to ungrouped + { + Client *c = entity_list.GetClientByName(ri->leader_name); + uint32 oldgrp = r->GetGroup(ri->leader_name); + if (r->members[r->GetPlayerIndex(ri->leader_name)].IsGroupLeader){ + r->SetGroupLeader(ri->leader_name, false); + for (int x = 0; x < MAX_RAID_MEMBERS; x++) + { + if (r->members[x].GroupNumber == oldgrp && strlen(r->members[x].membername) > 0 && strcmp(r->members[x].membername, ri->leader_name) != 0) + { + r->SetGroupLeader(r->members[x].membername); + r->UpdateGroupAAs(oldgrp); + Client *cgl = entity_list.GetClientByName(r->members[x].membername); + if (cgl){ + r->SendRaidRemove(r->members[x].membername, cgl); + r->SendRaidCreate(cgl); + r->SendMakeLeaderPacketTo(r->leadername, cgl); + r->SendRaidAdd(r->members[x].membername, cgl); + r->SendBulkRaid(cgl); + if (r->IsLocked()) { + r->SendRaidLockTo(cgl); + } + } + else{ + ServerPacket *pack = new ServerPacket(ServerOP_RaidChangeGroup, sizeof(ServerRaidGeneralAction_Struct)); + ServerRaidGeneralAction_Struct *rga = (ServerRaidGeneralAction_Struct*)pack->pBuffer; + rga->rid = r->GetID(); + strn0cpy(rga->playername, r->members[x].membername, 64); + rga->zoneid = zone->GetZoneID(); + rga->instance_id = zone->GetInstanceID(); + worldserver.SendPacket(pack); + safe_delete(pack); + } + break; + } + } + } + r->MoveMember(ri->leader_name, 0xFFFFFFFF); + if (c){ + r->SendGroupDisband(c); + } + else{ + ServerPacket *pack = new ServerPacket(ServerOP_RaidGroupDisband, sizeof(ServerRaidGeneralAction_Struct)); + ServerRaidGeneralAction_Struct* rga = (ServerRaidGeneralAction_Struct*)pack->pBuffer; + rga->rid = r->GetID(); + rga->zoneid = zone->GetZoneID(); + rga->instance_id = zone->GetInstanceID(); + strn0cpy(rga->playername, ri->leader_name, 64); + worldserver.SendPacket(pack); + safe_delete(pack); + } + //r->SendRaidGroupRemove(ri->leader_name, oldgrp); + r->GroupUpdate(oldgrp); + //r->SendMakeGroupLeaderPacketAll(); + } + } + break; + } + case RaidCommandRaidLock: + { + Raid *r = entity_list.GetRaidByClient(this); + if (r) + { + if (!r->IsLocked()) + r->LockRaid(true); + else + r->SendRaidLockTo(this); + } + break; + } + case RaidCommandRaidUnlock: + { + Raid *r = entity_list.GetRaidByClient(this); + if (r) + { + if (r->IsLocked()) + r->LockRaid(false); + else + r->SendRaidUnlockTo(this); + } + break; + } + case RaidCommandLootType2: + case RaidCommandLootType: + { + Raid *r = entity_list.GetRaidByClient(this); + if (r) + { + Message(15, "Loot type changed to: %d.", ri->parameter); + r->ChangeLootType(ri->parameter); + } + break; + } + + case RaidCommandAddLooter2: + case RaidCommandAddLooter: + { + Raid *r = entity_list.GetRaidByClient(this); + if (r) + { + Message(15, "Adding %s as a raid looter.", ri->leader_name); + r->AddRaidLooter(ri->leader_name); + } + break; + } + + case RaidCommandRemoveLooter2: + case RaidCommandRemoveLooter: + { + Raid *r = entity_list.GetRaidByClient(this); + if (r) + { + Message(15, "Removing %s as a raid looter.", ri->leader_name); + r->RemoveRaidLooter(ri->leader_name); + } + break; + } + + case RaidCommandMakeLeader: + { + Raid *r = entity_list.GetRaidByClient(this); + if (r) + { + if (strcmp(r->leadername, GetName()) == 0){ + r->SetRaidLeader(GetName(), ri->leader_name); + r->UpdateRaidAAs(); + r->SendAllRaidLeadershipAA(); + } + } + break; + } + + case RaidCommandSetMotd: + { + Raid *r = entity_list.GetRaidByClient(this); + if (!r) + break; + // we don't use the RaidGeneral here! + RaidMOTD_Struct *motd = (RaidMOTD_Struct *)app->pBuffer; + r->SetRaidMOTD(std::string(motd->motd)); + r->SaveRaidMOTD(); + r->SendRaidMOTDToWorld(); + break; + } + + default: { + Message(13, "Raid command (%d) NYI", ri->action); + break; + } + } +} + +void Client::Handle_OP_RandomReq(const EQApplicationPacket *app) +{ + if (app->size != sizeof(RandomReq_Struct)) { + LogFile->write(EQEMuLog::Error, "Wrong size: OP_RandomReq, size=%i, expected %i", app->size, sizeof(RandomReq_Struct)); + return; + } + const RandomReq_Struct* rndq = (const RandomReq_Struct*)app->pBuffer; + uint32 randLow = rndq->low > rndq->high ? rndq->high : rndq->low; + uint32 randHigh = rndq->low > rndq->high ? rndq->low : rndq->high; + uint32 randResult; + + if (randLow == 0 && randHigh == 0) + { // defaults + randLow = 0; + randHigh = 100; + } + randResult = zone->random.Int(randLow, randHigh); + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_RandomReply, sizeof(RandomReply_Struct)); + RandomReply_Struct* rr = (RandomReply_Struct*)outapp->pBuffer; + rr->low = randLow; + rr->high = randHigh; + rr->result = randResult; + strcpy(rr->name, GetName()); + entity_list.QueueCloseClients(this, outapp, false, 400); + safe_delete(outapp); + return; +} + +void Client::Handle_OP_ReadBook(const EQApplicationPacket *app) +{ + if (app->size != sizeof(BookRequest_Struct)) { + LogFile->write(EQEMuLog::Error, "Wrong size: OP_ReadBook, size=%i, expected %i", app->size, sizeof(BookRequest_Struct)); + return; + } + BookRequest_Struct* book = (BookRequest_Struct*)app->pBuffer; + ReadBook(book); + if (GetClientVersion() >= EQClientSoF) + { + EQApplicationPacket EndOfBook(OP_FinishWindow, 0); + QueuePacket(&EndOfBook); + } + return; +} + +void Client::Handle_OP_RecipeAutoCombine(const EQApplicationPacket *app) +{ + if (app->size != sizeof(RecipeAutoCombine_Struct)) { + LogFile->write(EQEMuLog::Error, "Invalid size for RecipeAutoCombine_Struct: Expected: %i, Got: %i", + sizeof(RecipeAutoCombine_Struct), app->size); + return; + } + + RecipeAutoCombine_Struct* rac = (RecipeAutoCombine_Struct*)app->pBuffer; + + Object::HandleAutoCombine(this, rac); + return; +} + +void Client::Handle_OP_RecipeDetails(const EQApplicationPacket *app) +{ + if (app->size < sizeof(uint32)) { + LogFile->write(EQEMuLog::Error, "Invalid size for RecipeDetails Request: Expected: %i, Got: %i", + sizeof(uint32), app->size); + return; + } + uint32 *recipe_id = (uint32*)app->pBuffer; + + SendTradeskillDetails(*recipe_id); + + return; +} + +void Client::Handle_OP_RecipesFavorite(const EQApplicationPacket *app) +{ + if (app->size != sizeof(TradeskillFavorites_Struct)) { + LogFile->write(EQEMuLog::Error, "Invalid size for TradeskillFavorites_Struct: Expected: %i, Got: %i", + sizeof(TradeskillFavorites_Struct), app->size); + return; + } + + TradeskillFavorites_Struct* tsf = (TradeskillFavorites_Struct*)app->pBuffer; + + LogFile->write(EQEMuLog::Debug, "Requested Favorites for: %d - %d\n", tsf->object_type, tsf->some_id); + + // results show that object_type is combiner type + // some_id = 0 if world combiner, item number otherwise + + // make where clause segment for container(s) + std::string containers; + if (tsf->some_id == 0) + containers += StringFormat(" = %u ", tsf->object_type); // world combiner so no item number + else + containers += StringFormat(" in (%u, %u) ", tsf->object_type, tsf->some_id); // container in inventory + + std::string favoriteIDs; //gotta be big enough for 500 IDs + bool first = true; + //Assumes item IDs are <10 characters long + for (uint16 favoriteIndex = 0; favoriteIndex < 500; ++favoriteIndex) { + if (tsf->favorite_recipes[favoriteIndex] == 0) + continue; + + if (first) { + favoriteIDs += StringFormat("%u", tsf->favorite_recipes[favoriteIndex]); + first = false; + } + else + favoriteIDs += StringFormat(",%u", tsf->favorite_recipes[favoriteIndex]); + } + + if (first) //no favorites.... + return; + + const std::string query = StringFormat("SELECT tr.id, tr.name, tr.trivial, " + "SUM(tre.componentcount), crl.madecount,tr.tradeskill " + "FROM tradeskill_recipe AS tr " + "LEFT JOIN tradeskill_recipe_entries AS tre ON tr.id=tre.recipe_id " + "LEFT JOIN (SELECT recipe_id, madecount " + "FROM char_recipe_list " + "WHERE char_id = %u) AS crl ON tr.id=crl.recipe_id " + "WHERE tr.enabled <> 0 AND tr.id IN (%s) " + "AND tr.must_learn & 0x20 <> 0x20 AND " + "((tr.must_learn & 0x3 <> 0 AND crl.madecount IS NOT NULL) " + "OR (tr.must_learn & 0x3 = 0)) " + "GROUP BY tr.id " + "HAVING sum(if(tre.item_id %s AND tre.iscontainer > 0,1,0)) > 0 " + "LIMIT 100 ", CharacterID(), favoriteIDs.c_str(), containers.c_str()); + + TradeskillSearchResults(query, tsf->object_type, tsf->some_id); + return; +} + +void Client::Handle_OP_RecipesSearch(const EQApplicationPacket *app) +{ + if (app->size != sizeof(RecipesSearch_Struct)) { + LogFile->write(EQEMuLog::Error, "Invalid size for RecipesSearch_Struct: Expected: %i, Got: %i", + sizeof(RecipesSearch_Struct), app->size); + return; + } + + RecipesSearch_Struct* rss = (RecipesSearch_Struct*)app->pBuffer; + rss->query[55] = '\0'; //just to be sure. + + + LogFile->write(EQEMuLog::Debug, "Requested search recipes for: %d - %d\n", rss->object_type, rss->some_id); + + // make where clause segment for container(s) + char containers[30]; + if (rss->some_id == 0) { + // world combiner so no item number + snprintf(containers, 29, "= %u", rss->object_type); + } + else { + // container in inventory + snprintf(containers, 29, "in (%u,%u)", rss->object_type, rss->some_id); + } + + std::string searchClause; + + //omit the rlike clause if query is empty + if (rss->query[0] != 0) { + char buf[120]; //larger than 2X rss->query + database.DoEscapeString(buf, rss->query, strlen(rss->query)); + searchClause = StringFormat("name rlike '%s' AND", buf); + } + + //arbitrary limit of 200 recipes, makes sense to me. + const std::string query = StringFormat("SELECT tr.id, tr.name, tr.trivial, " + "SUM(tre.componentcount), crl.madecount,tr.tradeskill " + "FROM tradeskill_recipe AS tr " + "LEFT JOIN tradeskill_recipe_entries AS tre ON tr.id = tre.recipe_id " + "LEFT JOIN (SELECT recipe_id, madecount " + "FROM char_recipe_list WHERE char_id = %u) AS crl ON tr.id=crl.recipe_id " + "WHERE %s tr.trivial >= %u AND tr.trivial <= %u AND tr.enabled <> 0 " + "AND tr.must_learn & 0x20 <> 0x20 " + "AND ((tr.must_learn & 0x3 <> 0 " + "AND crl.madecount IS NOT NULL) " + "OR (tr.must_learn & 0x3 = 0)) " + "GROUP BY tr.id " + "HAVING sum(if(tre.item_id %s AND tre.iscontainer > 0,1,0)) > 0 " + "LIMIT 200 ", + CharacterID(), searchClause.c_str(), + rss->mintrivial, rss->maxtrivial, containers); + TradeskillSearchResults(query, rss->object_type, rss->some_id); + return; +} + +void Client::Handle_OP_ReloadUI(const EQApplicationPacket *app) +{ + if (IsInAGuild()) + { + SendGuildRanks(); + SendGuildMembers(); + } + return; +} + +void Client::Handle_OP_RemoveBlockedBuffs(const EQApplicationPacket *app) +{ + if (!RuleB(Spells, EnableBlockedBuffs)) + return; + + if (app->size != sizeof(BlockedBuffs_Struct)) + { + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_RemoveBlockedBuffs expected %i got %i", + sizeof(BlockedBuffs_Struct), app->size); + + DumpPacket(app); + + return; + } + BlockedBuffs_Struct *bbs = (BlockedBuffs_Struct*)app->pBuffer; + + std::set *BlockedBuffs = bbs->Pet ? &PetBlockedBuffs : &PlayerBlockedBuffs; + + std::set RemovedBuffs; + + if (bbs->Count > 0) + { + std::set::iterator Iterator; + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_RemoveBlockedBuffs, sizeof(BlockedBuffs_Struct)); + + BlockedBuffs_Struct *obbs = (BlockedBuffs_Struct*)outapp->pBuffer; + + for (unsigned int i = 0; i < BLOCKED_BUFF_COUNT; ++i) + obbs->SpellID[i] = 0; + + obbs->Pet = bbs->Pet; + obbs->Initialise = 0; + obbs->Flags = 0x5a; + + for (unsigned int i = 0; i < bbs->Count; ++i) + { + Iterator = BlockedBuffs->find(bbs->SpellID[i]); + + if (Iterator != BlockedBuffs->end()) + { + RemovedBuffs.insert(bbs->SpellID[i]); + + BlockedBuffs->erase(Iterator); + } + } + obbs->Count = RemovedBuffs.size(); + + Iterator = RemovedBuffs.begin(); + + unsigned int Element = 0; + + while (Iterator != RemovedBuffs.end()) + { + obbs->SpellID[Element++] = (*Iterator); + ++Iterator; + } + + FastQueuePacket(&outapp); + } +} + +void Client::Handle_OP_Report(const EQApplicationPacket *app) +{ + if (!CanUseReport) + { + Message_StringID(MT_System, REPORT_ONCE); + return; + } + + uint32 size = app->size; + uint32 current_point = 0; + std::string reported, reporter; + std::string current_string; + int mode = 0; + + while (current_point < size) + { + if (mode < 2) + { + if (app->pBuffer[current_point] == '|') + { + mode++; + } + else + { + if (mode == 0) + { + reported += app->pBuffer[current_point]; + } + else + { + reporter += app->pBuffer[current_point]; + } + } + current_point++; + } + else + { + if (app->pBuffer[current_point] == 0x0a) + { + current_string += '\n'; + } + else if (app->pBuffer[current_point] == 0x00) + { + CanUseReport = false; + database.AddReport(reporter, reported, current_string); + return; + } + else + { + current_string += app->pBuffer[current_point]; + } + current_point++; + } + } + + CanUseReport = false; + database.AddReport(reporter, reported, current_string); +} + +void Client::Handle_OP_RequestDuel(const EQApplicationPacket *app) +{ + if (app->size != sizeof(Duel_Struct)) + return; + + EQApplicationPacket* outapp = app->Copy(); + Duel_Struct* ds = (Duel_Struct*)outapp->pBuffer; + uint32 duel = ds->duel_initiator; + ds->duel_initiator = ds->duel_target; + ds->duel_target = duel; + Entity* entity = entity_list.GetID(ds->duel_target); + if (GetID() != ds->duel_target && entity->IsClient() && (entity->CastToClient()->IsDueling() && entity->CastToClient()->GetDuelTarget() != 0)) { + Message_StringID(10, DUEL_CONSIDERING, entity->GetName()); + return; + } + if (IsDueling()) { + Message_StringID(10, DUEL_INPROGRESS); + return; + } + + if (GetID() != ds->duel_target && entity->IsClient() && GetDuelTarget() == 0 && !IsDueling() && !entity->CastToClient()->IsDueling() && entity->CastToClient()->GetDuelTarget() == 0) { + SetDuelTarget(ds->duel_target); + entity->CastToClient()->SetDuelTarget(GetID()); + ds->duel_target = ds->duel_initiator; + entity->CastToClient()->FastQueuePacket(&outapp); + entity->CastToClient()->SetDueling(false); + SetDueling(false); + } + else + safe_delete(outapp); + return; +} + +void Client::Handle_OP_RequestTitles(const EQApplicationPacket *app) +{ + + EQApplicationPacket *outapp = title_manager.MakeTitlesPacket(this); + + if (outapp != nullptr) + FastQueuePacket(&outapp); +} + +void Client::Handle_OP_RespawnWindow(const EQApplicationPacket *app) +{ + // This opcode is sent by the client when the player choses which bind to return to. + // The client sends just a 4 byte packet with the selection number in it + // + if (app->size != 4) + { + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_RespawnWindow expected %i got %i", + 4, app->size); + DumpPacket(app); + return; + } + char *Buffer = (char *)app->pBuffer; + + uint32 Option = VARSTRUCT_DECODE_TYPE(uint32, Buffer); + HandleRespawnFromHover(Option); +} + +void Client::Handle_OP_Rewind(const EQApplicationPacket *app) +{ + if ((rewind_timer.GetRemainingTime() > 1 && rewind_timer.Enabled())) { + Message_StringID(MT_System, REWIND_WAIT); + } + else { + CastToClient()->MovePC(zone->GetZoneID(), zone->GetInstanceID(), m_RewindLocation.m_X, m_RewindLocation.m_Y, m_RewindLocation.m_Z, 0, 2, Rewind); + rewind_timer.Start(30000, true); + } +} + +void Client::Handle_OP_RezzAnswer(const EQApplicationPacket *app) +{ + VERIFY_PACKET_LENGTH(OP_RezzAnswer, app, Resurrect_Struct); + + const Resurrect_Struct* ra = (const Resurrect_Struct*)app->pBuffer; + + _log(SPELLS__REZ, "Received OP_RezzAnswer from client. Pendingrezzexp is %i, action is %s", + PendingRezzXP, ra->action ? "ACCEPT" : "DECLINE"); + + _pkt(SPELLS__REZ, app); + + OPRezzAnswer(ra->action, ra->spellid, ra->zone_id, ra->instance_id, ra->x, ra->y, ra->z); + + if (ra->action == 1) + { + EQApplicationPacket* outapp = app->Copy(); + // Send the OP_RezzComplete to the world server. This finds it's way to the zone that + // the rezzed corpse is in to mark the corpse as rezzed. + outapp->SetOpcode(OP_RezzComplete); + worldserver.RezzPlayer(outapp, 0, 0, OP_RezzComplete); + safe_delete(outapp); + } + return; +} + +void Client::Handle_OP_Sacrifice(const EQApplicationPacket *app) +{ + + if (app->size != sizeof(Sacrifice_Struct)) { + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_Sacrifice expected %i got %i", sizeof(Sacrifice_Struct), app->size); + DumpPacket(app); + return; + } + Sacrifice_Struct *ss = (Sacrifice_Struct*)app->pBuffer; + + if (!PendingSacrifice) { + LogFile->write(EQEMuLog::Error, "Unexpected OP_Sacrifice reply"); + DumpPacket(app); + return; + } + + if (ss->Confirm) { + Client *Caster = entity_list.GetClientByName(SacrificeCaster.c_str()); + if (Caster) Sacrifice(Caster); + } + PendingSacrifice = false; + SacrificeCaster.clear(); +} + +void Client::Handle_OP_SafeFallSuccess(const EQApplicationPacket *app) // bit of a misnomer, sent whenever safe fall is used (success of fail) +{ + if (HasSkill(SkillSafeFall)) //this should only get called if the client has safe fall, but just in case... + CheckIncreaseSkill(SkillSafeFall, nullptr); //check for skill up +} + +void Client::Handle_OP_SafePoint(const EQApplicationPacket *app) +{ + return; +} + +void Client::Handle_OP_Save(const EQApplicationPacket *app) +{ + // The payload is 192 bytes - Not sure what is contained in payload + Save(); + return; +} + +void Client::Handle_OP_SaveOnZoneReq(const EQApplicationPacket *app) +{ + Handle_OP_Save(app); +} + +void Client::Handle_OP_SelectTribute(const EQApplicationPacket *app) +{ + _log(TRIBUTE__IN, "Received OP_SelectTribute of length %d", app->size); + _pkt(TRIBUTE__IN, app); + + //we should enforce being near a real tribute master to change this + //but im not sure how I wanna do that right now. + if (app->size != sizeof(SelectTributeReq_Struct)) + LogFile->write(EQEMuLog::Error, "Invalid size on OP_SelectTribute packet"); + else { + SelectTributeReq_Struct *t = (SelectTributeReq_Struct *)app->pBuffer; + SendTributeDetails(t->client_id, t->tribute_id); + } + return; +} + +void Client::Handle_OP_SenseTraps(const EQApplicationPacket *app) +{ + if (!HasSkill(SkillSenseTraps)) + return; + + if (!p_timers.Expired(&database, pTimerSenseTraps, false)) { + Message(13, "Ability recovery time not yet met."); + return; + } + int reuse = SenseTrapsReuseTime; + switch (GetAA(aaAdvTrapNegotiation)) { + case 1: + reuse -= 1; + break; + case 2: + reuse -= 3; + break; + case 3: + reuse -= 5; + break; + } + p_timers.Start(pTimerSenseTraps, reuse - 1); + + Trap* trap = entity_list.FindNearbyTrap(this, 800); + + CheckIncreaseSkill(SkillSenseTraps, nullptr); + + if (trap && trap->skill > 0) { + int uskill = GetSkill(SkillSenseTraps); + if ((zone->random.Int(0, 99) + uskill) >= (zone->random.Int(0, 99) + trap->skill*0.75)) + { + auto diff = trap->m_Position - GetPosition(); + + if (diff.m_X == 0 && diff.m_Y == 0) + Message(MT_Skills, "You sense a trap right under your feet!"); + else if (diff.m_X > 10 && diff.m_Y > 10) + Message(MT_Skills, "You sense a trap to the NorthWest."); + else if (diff.m_X < -10 && diff.m_Y > 10) + Message(MT_Skills, "You sense a trap to the NorthEast."); + else if (diff.m_Y > 10) + Message(MT_Skills, "You sense a trap to the North."); + else if (diff.m_X > 10 && diff.m_Y < -10) + Message(MT_Skills, "You sense a trap to the SouthWest."); + else if (diff.m_X < -10 && diff.m_Y < -10) + Message(MT_Skills, "You sense a trap to the SouthEast."); + else if (diff.m_Y < -10) + Message(MT_Skills, "You sense a trap to the South."); + else if (diff.m_X > 10) + Message(MT_Skills, "You sense a trap to the West."); + else + Message(MT_Skills, "You sense a trap to the East."); + trap->detected = true; + + float angle = CalculateHeadingToTarget(trap->m_Position.m_X, trap->m_Position.m_Y); + + if (angle < 0) + angle = (256 + angle); + + angle *= 2; + MovePC(zone->GetZoneID(), zone->GetInstanceID(), GetX(), GetY(), GetZ(), angle); + return; + } + } + Message(MT_Skills, "You did not find any traps nearby."); + return; +} + +void Client::Handle_OP_SetGuildMOTD(const EQApplicationPacket *app) +{ + mlog(GUILDS__IN_PACKETS, "Received OP_SetGuildMOTD"); + mpkt(GUILDS__IN_PACKET_TRACE, app); + + if (app->size != sizeof(GuildMOTD_Struct)) { + // client calls for a motd on login even if they arent in a guild + printf("Error: app size of %i != size of GuildMOTD_Struct of %zu\n", app->size, sizeof(GuildMOTD_Struct)); + return; + } + if (!IsInAGuild()) { + Message(13, "You are not in a guild!"); + return; + } + if (!guild_mgr.CheckPermission(GuildID(), GuildRank(), GUILD_MOTD)) { + Message(13, "You do not have permissions to edit your guild's MOTD."); + return; + } + + GuildMOTD_Struct* gmotd = (GuildMOTD_Struct*)app->pBuffer; + + mlog(GUILDS__ACTIONS, "Setting MOTD for %s (%d) to: %s - %s", + guild_mgr.GetGuildName(GuildID()), GuildID(), GetName(), gmotd->motd); + + if (!guild_mgr.SetGuildMOTD(GuildID(), gmotd->motd, GetName())) { + Message(0, "Motd update failed."); + } + + return; +} + +void Client::Handle_OP_SetRunMode(const EQApplicationPacket *app) +{ + return; +} + +void Client::Handle_OP_SetServerFilter(const EQApplicationPacket *app) +{ + if (app->size != sizeof(SetServerFilter_Struct)) { + LogFile->write(EQEMuLog::Error, "Received invalid sized " + "OP_SetServerFilter: got %d, expected %d", app->size, + sizeof(SetServerFilter_Struct)); + DumpPacket(app); + return; + } + SetServerFilter_Struct* filter = (SetServerFilter_Struct*)app->pBuffer; + ServerFilter(filter); + return; +} + +void Client::Handle_OP_SetStartCity(const EQApplicationPacket *app) +{ + // if the character has a start city, don't let them use the command + if (m_pp.binds[4].zoneId != 0 && m_pp.binds[4].zoneId != 189) { + Message(15, "Your home city has already been set.", m_pp.binds[4].zoneId, database.GetZoneName(m_pp.binds[4].zoneId)); + return; + } + + if (app->size < 1) { + LogFile->write(EQEMuLog::Error, "Wrong size: OP_SetStartCity, size=%i, expected %i", app->size, 1); + DumpPacket(app); + return; + } + + float x(0), y(0), z(0); + uint32 zoneid = 0; + uint32 startCity = (uint32)strtol((const char*)app->pBuffer, nullptr, 10); + + std::string query = StringFormat("SELECT zone_id, bind_id, x, y, z FROM start_zones " + "WHERE player_class=%i AND player_deity=%i AND player_race=%i", + m_pp.class_, m_pp.deity, m_pp.race); + auto results = database.QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "No valid start zones found for /setstartcity"); + return; + } + + bool validCity = false; + for (auto row = results.begin(); row != results.end(); ++row) { + if (atoi(row[1]) != 0) + zoneid = atoi(row[1]); + else + zoneid = atoi(row[0]); + + if (zoneid != startCity) + continue; + + validCity = true; + x = atof(row[2]); + y = atof(row[3]); + z = atof(row[4]); + } + + if (validCity) { + Message(15, "Your home city has been set"); + SetStartZone(startCity, x, y, z); + return; + } + + query = StringFormat("SELECT zone_id, bind_id FROM start_zones " + "WHERE player_class=%i AND player_deity=%i AND player_race=%i", + m_pp.class_, m_pp.deity, m_pp.race); + results = database.QueryDatabase(query); + if (!results.Success()) + return; + + Message(15, "Use \"/startcity #\" to choose a home city from the following list:"); + + for (auto row = results.begin(); row != results.end(); ++row) { + if (atoi(row[1]) != 0) + zoneid = atoi(row[1]); + else + zoneid = atoi(row[0]); + + char* name; + database.GetZoneLongName(database.GetZoneName(zoneid), &name); + Message(15, "%d - %s", zoneid, name); + } + +} + +void Client::Handle_OP_SetTitle(const EQApplicationPacket *app) +{ + if (app->size != sizeof(SetTitle_Struct)) { + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_SetTitle expected %i got %i", sizeof(SetTitle_Struct), app->size); + DumpPacket(app); + return; + } + + SetTitle_Struct *sts = (SetTitle_Struct *)app->pBuffer; + + std::string Title; + + if (!sts->is_suffix) + { + Title = title_manager.GetPrefix(sts->title_id); + SetAATitle(Title.c_str()); + } + else + { + Title = title_manager.GetSuffix(sts->title_id); + SetTitleSuffix(Title.c_str()); + } +} + +void Client::Handle_OP_Shielding(const EQApplicationPacket *app) +{ + if (app->size != sizeof(Shielding_Struct)) { + LogFile->write(EQEMuLog::Error, "OP size error: OP_Shielding expected:%i got:%i", sizeof(Shielding_Struct), app->size); + return; + } + if (GetClass() != WARRIOR) + { + return; + } + + if (shield_target) + { + entity_list.MessageClose_StringID(this, false, 100, 0, + END_SHIELDING, GetName(), shield_target->GetName()); + for (int y = 0; y < 2; y++) + { + if (shield_target->shielder[y].shielder_id == GetID()) + { + shield_target->shielder[y].shielder_id = 0; + shield_target->shielder[y].shielder_bonus = 0; + } + } + } + Shielding_Struct* shield = (Shielding_Struct*)app->pBuffer; + shield_target = entity_list.GetMob(shield->target_id); + bool ack = false; + ItemInst* inst = GetInv().GetItem(MainSecondary); + if (!shield_target) + return; + if (inst) + { + const Item_Struct* shield = inst->GetItem(); + if (shield && shield->ItemType == ItemTypeShield) + { + for (int x = 0; x < 2; x++) + { + if (shield_target->shielder[x].shielder_id == 0) + { + entity_list.MessageClose_StringID(this, false, 100, 0, + START_SHIELDING, GetName(), shield_target->GetName()); + shield_target->shielder[x].shielder_id = GetID(); + int shieldbonus = shield->AC * 2; + switch (GetAA(197)) + { + case 1: + shieldbonus = shieldbonus * 115 / 100; + break; + case 2: + shieldbonus = shieldbonus * 125 / 100; + break; + case 3: + shieldbonus = shieldbonus * 150 / 100; + break; + } + shield_target->shielder[x].shielder_bonus = shieldbonus; + shield_timer.Start(); + ack = true; + break; + } + } + } + else + { + Message(0, "You must have a shield equipped to shield a target!"); + shield_target = 0; + return; + } + } + else + { + Message(0, "You must have a shield equipped to shield a target!"); + shield_target = 0; + return; + } + if (!ack) + { + Message_StringID(0, ALREADY_SHIELDED); + shield_target = 0; + return; + } + return; +} + +void Client::Handle_OP_ShopEnd(const EQApplicationPacket *app) +{ + EQApplicationPacket empty(OP_ShopEndConfirm); + QueuePacket(&empty); + //EQApplicationPacket* outapp = new EQApplicationPacket(OP_ShopEndConfirm, 2); + //outapp->pBuffer[0] = 0x0a; + //outapp->pBuffer[1] = 0x66; + //QueuePacket(outapp); + //safe_delete(outapp); + //Save(); + return; +} + +void Client::Handle_OP_ShopPlayerBuy(const EQApplicationPacket *app) +{ + if (app->size != sizeof(Merchant_Sell_Struct)) { + LogFile->write(EQEMuLog::Error, "Invalid size on OP_ShopPlayerBuy: Expected %i, Got %i", + sizeof(Merchant_Sell_Struct), app->size); + return; + } + RDTSC_Timer t1; + t1.start(); + Merchant_Sell_Struct* mp = (Merchant_Sell_Struct*)app->pBuffer; +#if EQDEBUG >= 5 + LogFile->write(EQEMuLog::Debug, "%s, purchase item..", GetName()); + DumpPacket(app); +#endif + + int merchantid; + bool tmpmer_used = false; + Mob* tmp = entity_list.GetMob(mp->npcid); + + if (tmp == 0 || !tmp->IsNPC() || tmp->GetClass() != MERCHANT) + return; + + if (mp->quantity < 1) return; + + //you have to be somewhat close to them to be properly using them + if (DistNoRoot(*tmp) > USE_NPC_RANGE2) + return; + + merchantid = tmp->CastToNPC()->MerchantType; + + uint32 item_id = 0; + std::list merlist = zone->merchanttable[merchantid]; + std::list::const_iterator itr; + for (itr = merlist.begin(); itr != merlist.end(); ++itr){ + MerchantList ml = *itr; + if (GetLevel() < ml.level_required) { + continue; + } + + int32 fac = tmp->GetPrimaryFaction(); + if (fac != 0 && GetModCharacterFactionLevel(fac) < ml.faction_required) { + continue; + } + + if (mp->itemslot == ml.slot){ + item_id = ml.item; + break; + } + } + const Item_Struct* item = nullptr; + uint32 prevcharges = 0; + if (item_id == 0) { //check to see if its on the temporary table + std::list tmp_merlist = zone->tmpmerchanttable[tmp->GetNPCTypeID()]; + std::list::const_iterator tmp_itr; + TempMerchantList ml; + for (tmp_itr = tmp_merlist.begin(); tmp_itr != tmp_merlist.end(); ++tmp_itr){ + ml = *tmp_itr; + if (mp->itemslot == ml.slot){ + item_id = ml.item; + tmpmer_used = true; + prevcharges = ml.charges; + break; + } + } + } + item = database.GetItem(item_id); + if (!item){ + //error finding item, client didnt get the update packet for whatever reason, roleplay a tad + Message(15, "%s tells you 'Sorry, that item is for display purposes only.' as they take the item off the shelf.", tmp->GetCleanName()); + EQApplicationPacket* delitempacket = new EQApplicationPacket(OP_ShopDelItem, sizeof(Merchant_DelItem_Struct)); + Merchant_DelItem_Struct* delitem = (Merchant_DelItem_Struct*)delitempacket->pBuffer; + delitem->itemslot = mp->itemslot; + delitem->npcid = mp->npcid; + delitem->playerid = mp->playerid; + delitempacket->priority = 6; + entity_list.QueueCloseClients(tmp, delitempacket); //que for anyone that could be using the merchant so they see the update + safe_delete(delitempacket); + return; + } + if (CheckLoreConflict(item)) + { + Message(15, "You can only have one of a lore item."); + return; + } + if (tmpmer_used && (mp->quantity > prevcharges || item->MaxCharges > 1)) + { + if (prevcharges > item->MaxCharges && item->MaxCharges > 1) + mp->quantity = item->MaxCharges; + else + mp->quantity = prevcharges; + } + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_ShopPlayerBuy, sizeof(Merchant_Sell_Struct)); + Merchant_Sell_Struct* mpo = (Merchant_Sell_Struct*)outapp->pBuffer; + mpo->quantity = mp->quantity; + mpo->playerid = mp->playerid; + mpo->npcid = mp->npcid; + mpo->itemslot = mp->itemslot; + + int16 freeslotid = INVALID_INDEX; + int16 charges = 0; + if (item->Stackable || item->MaxCharges > 1) + charges = mp->quantity; + else + charges = item->MaxCharges; + + ItemInst* inst = database.CreateItem(item, charges); + + int SinglePrice = 0; + if (RuleB(Merchant, UsePriceMod)) + SinglePrice = (item->Price * (RuleR(Merchant, SellCostMod)) * item->SellRate * Client::CalcPriceMod(tmp, false)); + else + SinglePrice = (item->Price * (RuleR(Merchant, SellCostMod)) * item->SellRate); + + if (item->MaxCharges > 1) + mpo->price = SinglePrice; + else + mpo->price = SinglePrice * mp->quantity; + if (mpo->price < 0) + { + safe_delete(outapp); + safe_delete(inst); + return; + } + + // this area needs some work..two inventory insertion check failure points + // below do not return player's money..is this the intended behavior? + + if (!TakeMoneyFromPP(mpo->price)) + { + char *hacker_str = nullptr; + MakeAnyLenString(&hacker_str, "Vendor Cheat: attempted to buy %i of %i: %s that cost %d cp but only has %d pp %d gp %d sp %d cp\n", + mpo->quantity, item->ID, item->Name, + mpo->price, m_pp.platinum, m_pp.gold, m_pp.silver, m_pp.copper); + database.SetMQDetectionFlag(AccountName(), GetName(), hacker_str, zone->GetShortName()); + safe_delete_array(hacker_str); + safe_delete(outapp); + safe_delete(inst); + return; + } + + bool stacked = TryStacking(inst); + if (!stacked) + freeslotid = m_inv.FindFreeSlot(false, true, item->Size); + + // shouldn't we be reimbursing if these two fail? + + //make sure we are not completely full... + if (freeslotid == MainCursor) { + if (m_inv.GetItem(MainCursor) != nullptr) { + Message(13, "You do not have room for any more items."); + safe_delete(outapp); + safe_delete(inst); + return; + } + } + + if (!stacked && freeslotid == INVALID_INDEX) + { + Message(13, "You do not have room for any more items."); + safe_delete(outapp); + safe_delete(inst); + return; + } + + std::string packet; + if (!stacked && inst) { + PutItemInInventory(freeslotid, *inst); + SendItemPacket(freeslotid, inst, ItemPacketTrade); + } + else if (!stacked){ + LogFile->write(EQEMuLog::Error, "OP_ShopPlayerBuy: item->ItemClass Unknown! Type: %i", item->ItemClass); + } + QueuePacket(outapp); + if (inst && tmpmer_used){ + int32 new_charges = prevcharges - mp->quantity; + zone->SaveTempItem(merchantid, tmp->GetNPCTypeID(), item_id, new_charges); + if (new_charges <= 0){ + EQApplicationPacket* delitempacket = new EQApplicationPacket(OP_ShopDelItem, sizeof(Merchant_DelItem_Struct)); + Merchant_DelItem_Struct* delitem = (Merchant_DelItem_Struct*)delitempacket->pBuffer; + delitem->itemslot = mp->itemslot; + delitem->npcid = mp->npcid; + delitem->playerid = mp->playerid; + delitempacket->priority = 6; + entity_list.QueueClients(tmp, delitempacket); //que for anyone that could be using the merchant so they see the update + safe_delete(delitempacket); + } + else { + // Update the charges/quantity in the merchant window + inst->SetCharges(new_charges); + inst->SetPrice(SinglePrice); + inst->SetMerchantSlot(mp->itemslot); + inst->SetMerchantCount(new_charges); + + SendItemPacket(mp->itemslot, inst, ItemPacketMerchant); + } + } + safe_delete(inst); + safe_delete(outapp); + + // start QS code + // stacking purchases not supported at this time - entire process will need some work to catch them properly + if (RuleB(QueryServ, PlayerLogMerchantTransactions)) { + ServerPacket* qspack = new ServerPacket(ServerOP_QSPlayerLogMerchantTransactions, sizeof(QSMerchantLogTransaction_Struct)+sizeof(QSTransactionItems_Struct)); + QSMerchantLogTransaction_Struct* qsaudit = (QSMerchantLogTransaction_Struct*)qspack->pBuffer; + + qsaudit->zone_id = zone->GetZoneID(); + qsaudit->merchant_id = tmp->CastToNPC()->MerchantType; + qsaudit->merchant_money.platinum = 0; + qsaudit->merchant_money.gold = 0; + qsaudit->merchant_money.silver = 0; + qsaudit->merchant_money.copper = 0; + qsaudit->merchant_count = 1; + qsaudit->char_id = character_id; + qsaudit->char_money.platinum = (mpo->price / 1000); + qsaudit->char_money.gold = (mpo->price / 100) % 10; + qsaudit->char_money.silver = (mpo->price / 10) % 10; + qsaudit->char_money.copper = mpo->price % 10; + qsaudit->char_count = 0; + + qsaudit->items[0].char_slot = freeslotid == INVALID_INDEX ? 0 : freeslotid; + qsaudit->items[0].item_id = item->ID; + qsaudit->items[0].charges = mpo->quantity; + + if (freeslotid == INVALID_INDEX) { + qsaudit->items[0].aug_1 = 0; + qsaudit->items[0].aug_2 = 0; + qsaudit->items[0].aug_3 = 0; + qsaudit->items[0].aug_4 = 0; + qsaudit->items[0].aug_5 = 0; + } + else { + qsaudit->items[0].aug_1 = m_inv[freeslotid]->GetAugmentItemID(0); + qsaudit->items[0].aug_2 = m_inv[freeslotid]->GetAugmentItemID(1); + qsaudit->items[0].aug_3 = m_inv[freeslotid]->GetAugmentItemID(2); + qsaudit->items[0].aug_4 = m_inv[freeslotid]->GetAugmentItemID(3); + qsaudit->items[0].aug_5 = m_inv[freeslotid]->GetAugmentItemID(4); + } + + qspack->Deflate(); + if (worldserver.Connected()) { worldserver.SendPacket(qspack); } + safe_delete(qspack); + } + // end QS code + + if (RuleB(EventLog, RecordBuyFromMerchant)) + LogMerchant(this, tmp, mpo->quantity, mpo->price, item, true); + + if ((RuleB(Character, EnableDiscoveredItems))) + { + if (!GetGM() && !IsDiscovered(item_id)) + DiscoverItem(item_id); + } + + t1.stop(); + std::cout << "At 1: " << t1.getDuration() << std::endl; + return; +} +void Client::Handle_OP_ShopPlayerSell(const EQApplicationPacket *app) +{ + if (app->size != sizeof(Merchant_Purchase_Struct)) { + LogFile->write(EQEMuLog::Error, "Invalid size on OP_ShopPlayerSell: Expected %i, Got %i", + sizeof(Merchant_Purchase_Struct), app->size); + return; + } + RDTSC_Timer t1(true); + Merchant_Purchase_Struct* mp = (Merchant_Purchase_Struct*)app->pBuffer; + + Mob* vendor = entity_list.GetMob(mp->npcid); + + if (vendor == 0 || !vendor->IsNPC() || vendor->GetClass() != MERCHANT) + return; + + //you have to be somewhat close to them to be properly using them + if (DistNoRoot(*vendor) > USE_NPC_RANGE2) + return; + + uint32 price = 0; + uint32 itemid = GetItemIDAt(mp->itemslot); + if (itemid == 0) + return; + const Item_Struct* item = database.GetItem(itemid); + ItemInst* inst = GetInv().GetItem(mp->itemslot); + if (!item || !inst){ + Message(13, "You seemed to have misplaced that item.."); + return; + } + if (mp->quantity > 1) + { + if ((inst->GetCharges() < 0) || (mp->quantity > (uint32)inst->GetCharges())) + return; + } + + if (!item->NoDrop) { + //Message(13,"%s tells you, 'LOL NOPE'", vendor->GetName()); + return; + } + + int cost_quantity = mp->quantity; + if (inst->IsCharged()) + int cost_quantity = 1; + + if (RuleB(Merchant, UsePriceMod)) + price = (int)((item->Price*cost_quantity)*(RuleR(Merchant, BuyCostMod))*Client::CalcPriceMod(vendor, true) + 0.5); // need to round up, because client does it automatically when displaying price + else + price = (int)((item->Price*cost_quantity)*(RuleR(Merchant, BuyCostMod)) + 0.5); + AddMoneyToPP(price, false); + + if (inst->IsStackable() || inst->IsCharged()) + { + unsigned int i_quan = inst->GetCharges(); + if (mp->quantity > i_quan || inst->IsCharged()) + mp->quantity = i_quan; + } + else + mp->quantity = 1; + + if (RuleB(EventLog, RecordSellToMerchant)) + LogMerchant(this, vendor, mp->quantity, price, item, false); + + int charges = mp->quantity; + //Hack workaround so usable items with 0 charges aren't simply deleted + if (charges == 0 && item->ItemType != 11 && item->ItemType != 17 && item->ItemType != 19 && item->ItemType != 21) + charges = 1; + + int freeslot = 0; + if (charges > 0 && (freeslot = zone->SaveTempItem(vendor->CastToNPC()->MerchantType, vendor->GetNPCTypeID(), itemid, charges, true)) > 0){ + ItemInst* inst2 = inst->Clone(); + if (RuleB(Merchant, UsePriceMod)){ + inst2->SetPrice(item->Price*(RuleR(Merchant, SellCostMod))*item->SellRate*Client::CalcPriceMod(vendor, false)); + } + else + inst2->SetPrice(item->Price*(RuleR(Merchant, SellCostMod))*item->SellRate); + inst2->SetMerchantSlot(freeslot); + + uint32 MerchantQuantity = zone->GetTempMerchantQuantity(vendor->GetNPCTypeID(), freeslot); + + if (inst2->IsStackable()) { + inst2->SetCharges(MerchantQuantity); + } + inst2->SetMerchantCount(MerchantQuantity); + + SendItemPacket(freeslot - 1, inst2, ItemPacketMerchant); + safe_delete(inst2); + } + + // start QS code + if (RuleB(QueryServ, PlayerLogMerchantTransactions)) { + ServerPacket* qspack = new ServerPacket(ServerOP_QSPlayerLogMerchantTransactions, sizeof(QSMerchantLogTransaction_Struct)+sizeof(QSTransactionItems_Struct)); + QSMerchantLogTransaction_Struct* qsaudit = (QSMerchantLogTransaction_Struct*)qspack->pBuffer; + + qsaudit->zone_id = zone->GetZoneID(); + qsaudit->merchant_id = vendor->CastToNPC()->MerchantType; + qsaudit->merchant_money.platinum = (price / 1000); + qsaudit->merchant_money.gold = (price / 100) % 10; + qsaudit->merchant_money.silver = (price / 10) % 10; + qsaudit->merchant_money.copper = price % 10; + qsaudit->merchant_count = 0; + qsaudit->char_id = character_id; + qsaudit->char_money.platinum = 0; + qsaudit->char_money.gold = 0; + qsaudit->char_money.silver = 0; + qsaudit->char_money.copper = 0; + qsaudit->char_count = 1; + + qsaudit->items[0].char_slot = mp->itemslot; + qsaudit->items[0].item_id = itemid; + qsaudit->items[0].charges = charges; + qsaudit->items[0].aug_1 = m_inv[mp->itemslot]->GetAugmentItemID(1); + qsaudit->items[0].aug_2 = m_inv[mp->itemslot]->GetAugmentItemID(2); + qsaudit->items[0].aug_3 = m_inv[mp->itemslot]->GetAugmentItemID(3); + qsaudit->items[0].aug_4 = m_inv[mp->itemslot]->GetAugmentItemID(4); + qsaudit->items[0].aug_5 = m_inv[mp->itemslot]->GetAugmentItemID(5); + + qspack->Deflate(); + if (worldserver.Connected()) { worldserver.SendPacket(qspack); } + safe_delete(qspack); + } + // end QS code + + // Now remove the item from the player, this happens regardless of outcome + if (!inst->IsStackable()) + this->DeleteItemInInventory(mp->itemslot, 0, false); + else + this->DeleteItemInInventory(mp->itemslot, mp->quantity, false); + + //This forces the price to show up correctly for charged items. + if (inst->IsCharged()) + mp->quantity = 1; + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_ShopPlayerSell, sizeof(Merchant_Purchase_Struct)); + Merchant_Purchase_Struct* mco = (Merchant_Purchase_Struct*)outapp->pBuffer; + mco->npcid = vendor->GetID(); + mco->itemslot = mp->itemslot; + mco->quantity = mp->quantity; + mco->price = price; + QueuePacket(outapp); + safe_delete(outapp); + SendMoneyUpdate(); + t1.start(); + Save(1); + t1.stop(); + std::cout << "Save took: " << t1.getDuration() << std::endl; + return; +} + +void Client::Handle_OP_ShopRequest(const EQApplicationPacket *app) +{ + if (app->size != sizeof(Merchant_Click_Struct)) { + LogFile->write(EQEMuLog::Error, "Wrong size: OP_ShopRequest, size=%i, expected %i", app->size, sizeof(Merchant_Click_Struct)); + return; + } + + Merchant_Click_Struct* mc = (Merchant_Click_Struct*)app->pBuffer; + + // Send back opcode OP_ShopRequest - tells client to open merchant window. + //EQApplicationPacket* outapp = new EQApplicationPacket(OP_ShopRequest, sizeof(Merchant_Click_Struct)); + //Merchant_Click_Struct* mco=(Merchant_Click_Struct*)outapp->pBuffer; + int merchantid = 0; + Mob* tmp = entity_list.GetMob(mc->npcid); + + if (tmp == 0 || !tmp->IsNPC() || tmp->GetClass() != MERCHANT) + return; + + //you have to be somewhat close to them to be properly using them + if (DistNoRoot(*tmp) > USE_NPC_RANGE2) + return; + + merchantid = tmp->CastToNPC()->MerchantType; + + int action = 1; + if (merchantid == 0) { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_ShopRequest, sizeof(Merchant_Click_Struct)); + Merchant_Click_Struct* mco = (Merchant_Click_Struct*)outapp->pBuffer; + mco->npcid = mc->npcid; + mco->playerid = 0; + mco->command = 1; //open... + mco->rate = 1.0; + QueuePacket(outapp); + safe_delete(outapp); + return; + } + if (tmp->IsEngaged()){ + this->Message_StringID(0, MERCHANT_BUSY); + action = 0; + } + if (GetFeigned() || IsInvisible()) + { + Message(0, "You cannot use a merchant right now."); + action = 0; + } + int primaryfaction = tmp->CastToNPC()->GetPrimaryFaction(); + int factionlvl = GetFactionLevel(CharacterID(), tmp->CastToNPC()->GetNPCTypeID(), GetRace(), GetClass(), GetDeity(), primaryfaction, tmp); + if (factionlvl >= 7) { + MerchantRejectMessage(tmp, primaryfaction); + action = 0; + } + + if (tmp->Charmed()) + action = 0; + + // 1199 I don't have time for that now. etc + if (!tmp->CastToNPC()->IsMerchantOpen()) { + tmp->Say_StringID(zone->random.Int(1199, 1202)); + action = 0; + } + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_ShopRequest, sizeof(Merchant_Click_Struct)); + Merchant_Click_Struct* mco = (Merchant_Click_Struct*)outapp->pBuffer; + + mco->npcid = mc->npcid; + mco->playerid = 0; + mco->command = action; // Merchant command 0x01 = open + if (RuleB(Merchant, UsePriceMod)){ + mco->rate = 1 / ((RuleR(Merchant, BuyCostMod))*Client::CalcPriceMod(tmp, true)); // works + } + else + mco->rate = 1 / (RuleR(Merchant, BuyCostMod)); + + outapp->priority = 6; + QueuePacket(outapp); + safe_delete(outapp); + + if (action == 1) + BulkSendMerchantInventory(merchantid, tmp->GetNPCTypeID()); + + return; +} + +void Client::Handle_OP_Sneak(const EQApplicationPacket *app) +{ + if (!HasSkill(SkillSneak) && GetSkill(SkillSneak) == 0) { + return; //You cannot sneak if you do not have sneak + } + + if (!p_timers.Expired(&database, pTimerSneak, false)) { + Message(13, "Ability recovery time not yet met."); + return; + } + p_timers.Start(pTimerSneak, SneakReuseTime - 1); + + bool was = sneaking; + if (sneaking){ + sneaking = false; + hidden = false; + improved_hidden = false; + EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct)); + SpawnAppearance_Struct* sa_out = (SpawnAppearance_Struct*)outapp->pBuffer; + sa_out->spawn_id = GetID(); + sa_out->type = 0x03; + sa_out->parameter = 0; + entity_list.QueueClients(this, outapp, true); + safe_delete(outapp); + } + else { + CheckIncreaseSkill(SkillSneak, nullptr, 5); + } + float hidechance = ((GetSkill(SkillSneak) / 300.0f) + .25) * 100; + float random = zone->random.Real(0, 99); + if (!was && random < hidechance) { + sneaking = true; + } + EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct)); + SpawnAppearance_Struct* sa_out = (SpawnAppearance_Struct*)outapp->pBuffer; + sa_out->spawn_id = GetID(); + sa_out->type = 0x0F; + sa_out->parameter = sneaking; + QueuePacket(outapp); + safe_delete(outapp); + if (GetClass() == ROGUE){ + outapp = new EQApplicationPacket(OP_SimpleMessage, 12); + SimpleMessage_Struct *msg = (SimpleMessage_Struct *)outapp->pBuffer; + msg->color = 0x010E; + if (sneaking){ + msg->string_id = 347; + } + else { + msg->string_id = 348; + } + FastQueuePacket(&outapp); + } + return; +} + +void Client::Handle_OP_SpawnAppearance(const EQApplicationPacket *app) +{ + if (app->size != sizeof(SpawnAppearance_Struct)) { + std::cout << "Wrong size on OP_SpawnAppearance. Got: " << app->size << ", Expected: " << sizeof(SpawnAppearance_Struct) << std::endl; + return; + } + SpawnAppearance_Struct* sa = (SpawnAppearance_Struct*)app->pBuffer; + + if (sa->spawn_id != GetID()) + return; + + if (sa->type == AT_Invis) { + if (sa->parameter != 0) + { + if (!HasSkill(SkillHide) && GetSkill(SkillHide) == 0) + { + if (GetClientVersion() < EQClientSoF) + { + char *hack_str = nullptr; + MakeAnyLenString(&hack_str, "Player sent OP_SpawnAppearance with AT_Invis: %i", sa->parameter); + database.SetMQDetectionFlag(this->account_name, this->name, hack_str, zone->GetShortName()); + safe_delete_array(hack_str); + } + } + return; + } + invisible = false; + hidden = false; + improved_hidden = false; + entity_list.QueueClients(this, app, true); + return; + } + else if (sa->type == AT_Anim) { + if (IsAIControlled()) + return; + if (sa->parameter == ANIM_STAND) { + SetAppearance(eaStanding); + playeraction = 0; + SetFeigned(false); + BindWound(this, false, true); + camp_timer.Disable(); + } + else if (sa->parameter == ANIM_SIT) { + SetAppearance(eaSitting); + playeraction = 1; + if (!UseBardSpellLogic()) + InterruptSpell(); + SetFeigned(false); + BindWound(this, false, true); + } + else if (sa->parameter == ANIM_CROUCH) { + if (!UseBardSpellLogic()) + InterruptSpell(); + SetAppearance(eaCrouching); + playeraction = 2; + SetFeigned(false); + } + else if (sa->parameter == ANIM_DEATH) { // feign death too + SetAppearance(eaDead); + playeraction = 3; + InterruptSpell(); + } + else if (sa->parameter == ANIM_LOOT) { + SetAppearance(eaLooting); + playeraction = 4; + SetFeigned(false); + } + + // This is from old code + // I have no clue what it's for + /* + else if (sa->parameter == 0x05) { + // Illusion + std::cout << "Illusion packet recv'd:" << std::endl; + DumpPacket(app); + } + */ + else { + std::cerr << "Client " << name << " unknown apperance " << (int)sa->parameter << std::endl; + return; + } + + entity_list.QueueClients(this, app, true); + } + else if (sa->type == AT_Anon) { + // For Anon/Roleplay + if (sa->parameter == 1) { // Anon + m_pp.anon = 1; + } + else if ((sa->parameter == 2) || (sa->parameter == 3)) { // This is Roleplay, or anon+rp + m_pp.anon = 2; + } + else if (sa->parameter == 0) { // This is Non-Anon + m_pp.anon = 0; + } + else { + std::cerr << "Client " << name << " unknown Anon/Roleplay Switch " << (int)sa->parameter << std::endl; + return; + } + entity_list.QueueClients(this, app, true); + UpdateWho(); + } + else if ((sa->type == AT_HP) && (dead == 0)) { + return; + } + else if (sa->type == AT_AFK) { + this->AFK = (sa->parameter == 1); + entity_list.QueueClients(this, app, true); + } + else if (sa->type == AT_Split) { + m_pp.autosplit = (sa->parameter == 1); + } + else if (sa->type == AT_Sneak) { + if (sa->parameter != 0) + { + if (!HasSkill(SkillSneak)) + { + char *hack_str = nullptr; + MakeAnyLenString(&hack_str, "Player sent OP_SpawnAppearance with AT_Sneak: %i", sa->parameter); + database.SetMQDetectionFlag(this->account_name, this->name, hack_str, zone->GetShortName()); + safe_delete_array(hack_str); + } + return; + } + this->sneaking = 0; + entity_list.QueueClients(this, app, true); + } + else if (sa->type == AT_Size) + { + char *hack_str = nullptr; + MakeAnyLenString(&hack_str, "Player sent OP_SpawnAppearance with AT_Size: %i", sa->parameter); + database.SetMQDetectionFlag(this->account_name, this->name, hack_str, zone->GetShortName()); + safe_delete_array(hack_str); + } + else if (sa->type == AT_Light) // client emitting light (lightstone, shiny shield) + { + entity_list.QueueClients(this, app, false); + } + else if (sa->type == AT_Levitate) + { + // don't do anything with this, we tell the client when it's + // levitating, not the other way around + } + else if (sa->type == AT_ShowHelm) + { + m_pp.showhelm = (sa->parameter == 1); + entity_list.QueueClients(this, app, true); + } + else { + std::cout << "Unknown SpawnAppearance type: 0x" << std::hex << std::setw(4) << std::setfill('0') << sa->type << std::dec + << " value: 0x" << std::hex << std::setw(8) << std::setfill('0') << sa->parameter << std::dec << std::endl; + } + return; +} + +void Client::Handle_OP_Split(const EQApplicationPacket *app) +{ + if (app->size != sizeof(Split_Struct)) { + LogFile->write(EQEMuLog::Error, "Wrong size: OP_Split, size=%i, expected %i", app->size, sizeof(Split_Struct)); + return; + } + // The client removes the money on its own, but we have to + // update our state anyway, and make sure they had enough to begin + // with. + Split_Struct *split = (Split_Struct *)app->pBuffer; + //Per the note above, Im not exactly sure what to do on error + //to notify the client of the error... + if (!isgrouped) { + Message(13, "You can not split money if your not in a group."); + return; + } + Group *cgroup = GetGroup(); + if (cgroup == nullptr) { + //invalid group, not sure if we should say more... + Message(13, "You can not split money if your not in a group."); + return; + } + + if (!TakeMoneyFromPP(static_cast(split->copper) + + 10 * static_cast(split->silver) + + 100 * static_cast(split->gold) + + 1000 * static_cast(split->platinum))) { + Message(13, "You do not have enough money to do that split."); + return; + } + cgroup->SplitMoney(split->copper, split->silver, split->gold, split->platinum); + + return; + +} + +void Client::Handle_OP_Surname(const EQApplicationPacket *app) +{ + if (app->size != sizeof(Surname_Struct)) + { + LogFile->write(EQEMuLog::Debug, "Size mismatch in Surname expected %i got %i", sizeof(Surname_Struct), app->size); + return; + } + + if (!p_timers.Expired(&database, pTimerSurnameChange, false) && !GetGM()) + { + Message(15, "You may only change surnames once every 7 days, your /surname is currently on cooldown."); + return; + } + + if (GetLevel() < 20) + { + Message_StringID(15, SURNAME_LEVEL); + return; + } + + Surname_Struct* surname = (Surname_Struct*)app->pBuffer; + + char *c = nullptr; + bool first = true; + for (c = surname->lastname; *c; c++) + { + if (first) + { + *c = toupper(*c); + first = false; + } + else + { + *c = tolower(*c); + } + } + + if (strlen(surname->lastname) >= 20) { + Message_StringID(15, SURNAME_TOO_LONG); + return; + } + + if (!database.CheckNameFilter(surname->lastname, true)) + { + Message_StringID(15, SURNAME_REJECTED); + return; + } + + ChangeLastName(surname->lastname); + p_timers.Start(pTimerSurnameChange, 604800); + + EQApplicationPacket* outapp = app->Copy(); + outapp = app->Copy(); + surname = (Surname_Struct*)outapp->pBuffer; + surname->unknown0064 = 1; + FastQueuePacket(&outapp); + return; +} + +void Client::Handle_OP_SwapSpell(const EQApplicationPacket *app) +{ + + if (app->size != sizeof(SwapSpell_Struct)) { + std::cout << "Wrong size on OP_SwapSpell. Got: " << app->size << ", Expected: " << sizeof(SwapSpell_Struct) << std::endl; + return; + } + const SwapSpell_Struct* swapspell = (const SwapSpell_Struct*)app->pBuffer; + int swapspelltemp; + + if (swapspell->from_slot < 0 || swapspell->from_slot > MAX_PP_SPELLBOOK || swapspell->to_slot < 0 || swapspell->to_slot > MAX_PP_SPELLBOOK) + return; + + swapspelltemp = m_pp.spell_book[swapspell->from_slot]; + if (swapspelltemp < 0){ + return; + } + m_pp.spell_book[swapspell->from_slot] = m_pp.spell_book[swapspell->to_slot]; + m_pp.spell_book[swapspell->to_slot] = swapspelltemp; + + /* Save Spell Swaps */ + if (!database.SaveCharacterSpell(this->CharacterID(), m_pp.spell_book[swapspell->from_slot], swapspell->from_slot)){ + database.DeleteCharacterSpell(this->CharacterID(), m_pp.spell_book[swapspell->from_slot], swapspell->from_slot); + } + if (!database.SaveCharacterSpell(this->CharacterID(), swapspelltemp, swapspell->to_slot)){ + database.DeleteCharacterSpell(this->CharacterID(), swapspelltemp, swapspell->to_slot); + } + + QueuePacket(app); + return; +} + +void Client::Handle_OP_TargetCommand(const EQApplicationPacket *app) +{ + if (app->size != sizeof(ClientTarget_Struct)) { + LogFile->write(EQEMuLog::Error, "OP size error: OP_TargetMouse expected:%i got:%i", sizeof(ClientTarget_Struct), app->size); + return; + } + + if (GetTarget()) + { + GetTarget()->IsTargeted(-1); + } + + // Locate and cache new target + ClientTarget_Struct* ct = (ClientTarget_Struct*)app->pBuffer; + pClientSideTarget = ct->new_target; + if (!IsAIControlled()) + { + Mob *nt = entity_list.GetMob(ct->new_target); + if (nt) + { + SetTarget(nt); + bool inspect_buffs = false; + // rank 1 gives you ability to see NPC buffs in target window (SoD+) + if (nt->IsNPC()) { + if (IsRaidGrouped()) { + Raid *raid = GetRaid(); + if (raid) { + uint32 gid = raid->GetGroup(this); + if (gid < 12 && raid->GroupCount(gid) > 2) + inspect_buffs = raid->GetLeadershipAA(groupAAInspectBuffs, gid); + } + } else { + Group *group = GetGroup(); + if (group && group->GroupCount() > 2) + inspect_buffs = group->GetLeadershipAA(groupAAInspectBuffs); + } + } + if (nt == this || inspect_buffs || (nt->IsClient() && !nt->CastToClient()->GetPVP()) || + (nt->IsPet() && nt->GetOwner() && nt->GetOwner()->IsClient() && !nt->GetOwner()->CastToClient()->GetPVP()) || + (nt->IsMerc() && nt->GetOwner() && nt->GetOwner()->IsClient() && !nt->GetOwner()->CastToClient()->GetPVP())) + nt->SendBuffsToClient(this); + } + else + { + SetTarget(nullptr); + SetHoTT(0); + UpdateXTargetType(TargetsTarget, nullptr); + + Group *g = GetGroup(); + + if (g && g->HasRole(this, RoleAssist)) + g->SetGroupAssistTarget(0); + + if (g && g->HasRole(this, RoleTank)) + g->SetGroupTankTarget(0); + + if (g && g->HasRole(this, RolePuller)) + g->SetGroupPullerTarget(0); + + return; + } + } + else + { + SetTarget(nullptr); + SetHoTT(0); + UpdateXTargetType(TargetsTarget, nullptr); + return; + } + + // HoTT + if (GetTarget() && GetTarget()->GetTarget()) + { + SetHoTT(GetTarget()->GetTarget()->GetID()); + UpdateXTargetType(TargetsTarget, GetTarget()->GetTarget()); + } + else + { + SetHoTT(0); + UpdateXTargetType(TargetsTarget, nullptr); + } + + Group *g = GetGroup(); + + if (g && g->HasRole(this, RoleAssist)) + g->SetGroupAssistTarget(GetTarget()); + + if (g && g->HasRole(this, RoleTank)) + g->SetGroupTankTarget(GetTarget()); + + if (g && g->HasRole(this, RolePuller)) + g->SetGroupPullerTarget(GetTarget()); + + // For /target, send reject or success packet + if (app->GetOpcode() == OP_TargetCommand) { + if (GetTarget() && !GetTarget()->CastToMob()->IsInvisible(this) && (DistNoRoot(*GetTarget()) <= TARGETING_RANGE*TARGETING_RANGE || GetGM())) { + if (GetTarget()->GetBodyType() == BT_NoTarget2 || GetTarget()->GetBodyType() == BT_Special + || GetTarget()->GetBodyType() == BT_NoTarget) + { + //Targeting something we shouldn't with /target + //but the client allows this without MQ so you don't flag it + EQApplicationPacket* outapp = new EQApplicationPacket(OP_TargetReject, sizeof(TargetReject_Struct)); + outapp->pBuffer[0] = 0x2f; + outapp->pBuffer[1] = 0x01; + outapp->pBuffer[4] = 0x0d; + if (GetTarget()) + { + SetTarget(nullptr); + } + QueuePacket(outapp); + safe_delete(outapp); + return; + } + + QueuePacket(app); + EQApplicationPacket hp_app; + GetTarget()->IsTargeted(1); + GetTarget()->CreateHPPacket(&hp_app); + QueuePacket(&hp_app, false); + } + else + { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_TargetReject, sizeof(TargetReject_Struct)); + outapp->pBuffer[0] = 0x2f; + outapp->pBuffer[1] = 0x01; + outapp->pBuffer[4] = 0x0d; + if (GetTarget()) + { + SetTarget(nullptr); + } + QueuePacket(outapp); + safe_delete(outapp); + } + } + else + { + if (GetTarget()) + { + if (GetGM()) + { + GetTarget()->IsTargeted(1); + return; + } + else if (IsAssistExempted()) + { + GetTarget()->IsTargeted(1); + SetAssistExemption(false); + return; + } + else if (GetTarget()->IsClient()) + { + //make sure this client is in our raid/group + GetTarget()->IsTargeted(1); + return; + } + else if (GetTarget()->GetBodyType() == BT_NoTarget2 || GetTarget()->GetBodyType() == BT_Special + || GetTarget()->GetBodyType() == BT_NoTarget) + { + char *hacker_str = nullptr; + MakeAnyLenString(&hacker_str, "%s attempting to target something untargetable, %s bodytype: %i\n", + GetName(), GetTarget()->GetName(), (int)GetTarget()->GetBodyType()); + database.SetMQDetectionFlag(AccountName(), GetName(), hacker_str, zone->GetShortName()); + safe_delete_array(hacker_str); + SetTarget((Mob*)nullptr); + return; + } + else if (IsPortExempted()) + { + GetTarget()->IsTargeted(1); + return; + } + else if (IsSenseExempted()) + { + GetTarget()->IsTargeted(1); + SetSenseExemption(false); + return; + } + else if (IsXTarget(GetTarget())) + { + GetTarget()->IsTargeted(1); + return; + } + else if (GetBindSightTarget()) + { + if (GetBindSightTarget()->DistNoRoot(*GetTarget()) > (zone->newzone_data.maxclip*zone->newzone_data.maxclip)) + { + if (DistNoRoot(*GetTarget()) > (zone->newzone_data.maxclip*zone->newzone_data.maxclip)) + { + char *hacker_str = nullptr; + MakeAnyLenString(&hacker_str, "%s attempting to target something beyond the clip plane of %.2f units," + " from (%.2f, %.2f, %.2f) to %s (%.2f, %.2f, %.2f)", GetName(), + (zone->newzone_data.maxclip*zone->newzone_data.maxclip), + GetX(), GetY(), GetZ(), GetTarget()->GetName(), GetTarget()->GetX(), GetTarget()->GetY(), GetTarget()->GetZ()); + database.SetMQDetectionFlag(AccountName(), GetName(), hacker_str, zone->GetShortName()); + safe_delete_array(hacker_str); + SetTarget(nullptr); + return; + } + } + } + else if (DistNoRoot(*GetTarget()) > (zone->newzone_data.maxclip*zone->newzone_data.maxclip)) + { + char *hacker_str = nullptr; + MakeAnyLenString(&hacker_str, "%s attempting to target something beyond the clip plane of %.2f units," + " from (%.2f, %.2f, %.2f) to %s (%.2f, %.2f, %.2f)", GetName(), + (zone->newzone_data.maxclip*zone->newzone_data.maxclip), + GetX(), GetY(), GetZ(), GetTarget()->GetName(), GetTarget()->GetX(), GetTarget()->GetY(), GetTarget()->GetZ()); + database.SetMQDetectionFlag(AccountName(), GetName(), hacker_str, zone->GetShortName()); + safe_delete_array(hacker_str); + SetTarget(nullptr); + return; + } + + GetTarget()->IsTargeted(1); + } + } + return; +} + +void Client::Handle_OP_TargetMouse(const EQApplicationPacket *app) +{ + Handle_OP_TargetCommand(app); +} + +void Client::Handle_OP_TaskHistoryRequest(const EQApplicationPacket *app) +{ + + if (app->size != sizeof(TaskHistoryRequest_Struct)) { + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_TaskHistoryRequest expected %i got %i", + sizeof(TaskHistoryRequest_Struct), app->size); + DumpPacket(app); + return; + } + TaskHistoryRequest_Struct *ths = (TaskHistoryRequest_Struct*)app->pBuffer; + + if (RuleB(TaskSystem, EnableTaskSystem) && taskstate) + taskstate->SendTaskHistory(this, ths->TaskIndex); +} + +void Client::Handle_OP_Taunt(const EQApplicationPacket *app) +{ + if (app->size != sizeof(ClientTarget_Struct)) { + std::cout << "Wrong size on OP_Taunt. Got: " << app->size << ", Expected: " << sizeof(ClientTarget_Struct) << std::endl; + return; + } + + if (!p_timers.Expired(&database, pTimerTaunt, false)) { + Message(13, "Ability recovery time not yet met."); + return; + } + p_timers.Start(pTimerTaunt, TauntReuseTime - 1); + + if (GetTarget() == nullptr || !GetTarget()->IsNPC()) + return; + + Taunt(GetTarget()->CastToNPC(), false); + return; +} + +void Client::Handle_OP_TestBuff(const EQApplicationPacket *app) +{ + return; +} + +void Client::Handle_OP_TGB(const EQApplicationPacket *app) +{ + OPTGB(app); + return; +} + +void Client::Handle_OP_Track(const EQApplicationPacket *app) +{ + if (GetClass() != RANGER && GetClass() != DRUID && GetClass() != BARD) + return; + + if (GetSkill(SkillTracking) == 0) + SetSkill(SkillTracking, 1); + else + CheckIncreaseSkill(SkillTracking, nullptr, 15); + + if (!entity_list.MakeTrackPacket(this)) + LogFile->write(EQEMuLog::Error, "Unable to generate OP_Track packet requested by client."); + + return; +} + +void Client::Handle_OP_TrackTarget(const EQApplicationPacket *app) +{ + int PlayerClass = GetClass(); + + if ((PlayerClass != RANGER) && (PlayerClass != DRUID) && (PlayerClass != BARD)) + return; + + if (app->size != sizeof(TrackTarget_Struct)) + { + LogFile->write(EQEMuLog::Error, "Invalid size for OP_TrackTarget: Expected: %i, Got: %i", + sizeof(TrackTarget_Struct), app->size); + return; + } + + TrackTarget_Struct *tts = (TrackTarget_Struct*)app->pBuffer; + + TrackingID = tts->EntityID; +} + +void Client::Handle_OP_TrackUnknown(const EQApplicationPacket *app) +{ + // size 0 send right after OP_Track + return; +} + +void Client::Handle_OP_TradeAcceptClick(const EQApplicationPacket *app) +{ + Mob* with = trade->With(); + trade->state = TradeAccepted; + + if (with && with->IsClient()) { + //finish trade... + // Have both accepted? + Client* other = with->CastToClient(); + other->QueuePacket(app); + + if (other->trade->state == trade->state) { + other->trade->state = TradeCompleting; + trade->state = TradeCompleting; + + // should we do this for NoDrop items as well? + if (CheckTradeLoreConflict(other) || other->CheckTradeLoreConflict(this)) { + Message_StringID(13, TRADE_CANCEL_LORE); + other->Message_StringID(13, TRADE_CANCEL_LORE); + this->FinishTrade(this); + other->FinishTrade(other); + other->trade->Reset(); + trade->Reset(); + } + else { + // Audit trade to database for both trade streams + other->trade->LogTrade(); + trade->LogTrade(); + + // start QS code + if (RuleB(QueryServ, PlayerLogTrades)) { + QSPlayerLogTrade_Struct event_entry; + std::list event_details; + + memset(&event_entry, 0, sizeof(QSPlayerLogTrade_Struct)); + + // Perform actual trade + this->FinishTrade(other, true, &event_entry, &event_details); + other->FinishTrade(this, false, &event_entry, &event_details); + + event_entry._detail_count = event_details.size(); + + ServerPacket* qs_pack = new ServerPacket(ServerOP_QSPlayerLogTrades, sizeof(QSPlayerLogTrade_Struct)+(sizeof(QSTradeItems_Struct)* event_entry._detail_count)); + QSPlayerLogTrade_Struct* qs_buf = (QSPlayerLogTrade_Struct*)qs_pack->pBuffer; + + memcpy(qs_buf, &event_entry, sizeof(QSPlayerLogTrade_Struct)); + + int offset = 0; + + for (std::list::iterator iter = event_details.begin(); iter != event_details.end(); ++iter, ++offset) { + QSTradeItems_Struct* detail = reinterpret_cast(*iter); + qs_buf->items[offset] = *detail; + safe_delete(detail); + } + + event_details.clear(); + + qs_pack->Deflate(); + + if (worldserver.Connected()) + worldserver.SendPacket(qs_pack); + + safe_delete(qs_pack); + // end QS code + } + else { + this->FinishTrade(other); + other->FinishTrade(this); + } + + other->trade->Reset(); + trade->Reset(); + } + // All done + EQApplicationPacket* outapp = new EQApplicationPacket(OP_FinishTrade, 0); + other->QueuePacket(outapp); + this->FastQueuePacket(&outapp); + } + } + // Trading with a Mob object that is not a Client. + else if (with) { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_FinishTrade, 0); + QueuePacket(outapp); + safe_delete(outapp); + if (with->IsNPC()) { + // Audit trade to database for player trade stream + if (RuleB(QueryServ, PlayerLogHandins)) { + QSPlayerLogHandin_Struct event_entry; + std::list event_details; + + memset(&event_entry, 0, sizeof(QSPlayerLogHandin_Struct)); + + FinishTrade(with->CastToNPC(), false, &event_entry, &event_details); + + event_entry._detail_count = event_details.size(); + + ServerPacket* qs_pack = new ServerPacket(ServerOP_QSPlayerLogHandins, sizeof(QSPlayerLogHandin_Struct)+(sizeof(QSHandinItems_Struct)* event_entry._detail_count)); + QSPlayerLogHandin_Struct* qs_buf = (QSPlayerLogHandin_Struct*)qs_pack->pBuffer; + + memcpy(qs_buf, &event_entry, sizeof(QSPlayerLogHandin_Struct)); + + int offset = 0; + + for (std::list::iterator iter = event_details.begin(); iter != event_details.end(); ++iter, ++offset) { + QSHandinItems_Struct* detail = reinterpret_cast(*iter); + qs_buf->items[offset] = *detail; + safe_delete(detail); + } + + event_details.clear(); + + qs_pack->Deflate(); + + if (worldserver.Connected()) + worldserver.SendPacket(qs_pack); + + safe_delete(qs_pack); + } + else { + FinishTrade(with->CastToNPC()); + } + } +#ifdef BOTS + // TODO: Log Bot trades + else if (with->IsBot()) + with->CastToBot()->FinishTrade(this, Bot::BotTradeClientNormal); +#endif + trade->Reset(); + } + + + return; +} + +void Client::Handle_OP_TradeBusy(const EQApplicationPacket *app) +{ + if (app->size != sizeof(TradeBusy_Struct)) { + LogFile->write(EQEMuLog::Error, "Wrong size: OP_TradeBusy, size=%i, expected %i", app->size, sizeof(TradeBusy_Struct)); + return; + } + // Trade request recipient is cancelling the trade due to being busy + // Trade requester gets message "I'm busy right now" + // Send busy message on to trade initiator if client + TradeBusy_Struct* msg = (TradeBusy_Struct*)app->pBuffer; + Mob* tradee = entity_list.GetMob(msg->to_mob_id); + + if (tradee && tradee->IsClient()) { + tradee->CastToClient()->QueuePacket(app); + } + return; +} + +void Client::Handle_OP_Trader(const EQApplicationPacket *app) +{ + // Bazaar Trader: + // + // SoF sends 1 or more unhandled OP_Trader packets of size 96 when a trade has completed. + // I don't know what they are for (yet), but it doesn't seem to matter that we ignore them. + + _pkt(TRADING__PACKETS, app); + + uint32 max_items = 80; + + /* + if (GetClientVersion() >= EQClientRoF) + max_items = 200; + */ + + //Show Items + if (app->size == sizeof(Trader_ShowItems_Struct)) + { + Trader_ShowItems_Struct* sis = (Trader_ShowItems_Struct*)app->pBuffer; + + switch (sis->Code) + { + case BazaarTrader_EndTraderMode: { + Trader_EndTrader(); + break; + } + case BazaarTrader_EndTransaction: { + + Client* c = entity_list.GetClientByID(sis->TraderID); + if (c) + c->WithCustomer(0); + else + _log(TRADING__CLIENT, "Client::Handle_OP_TraderBuy: Null Client Pointer"); + + break; + } + case BazaarTrader_ShowItems: { + Trader_ShowItems(); + break; + } + default: { + _log(TRADING__CLIENT, "Unhandled action code in OP_Trader ShowItems_Struct"); + break; + } + } + } + else if (app->size == sizeof(ClickTrader_Struct)) + { + if (Buyer) { + Trader_EndTrader(); + Message(13, "You cannot be a Trader and Buyer at the same time."); + return; + } + + ClickTrader_Struct* ints = (ClickTrader_Struct*)app->pBuffer; + + if (ints->Code == BazaarTrader_StartTraderMode) + { + GetItems_Struct* gis = GetTraderItems(); + + // Verify there are no NODROP or items with a zero price + bool TradeItemsValid = true; + + for (uint32 i = 0; i < max_items; i++) { + + if (gis->Items[i] == 0) break; + + if (ints->ItemCost[i] == 0) { + Message(13, "Item in Trader Satchel with no price. Unable to start trader mode"); + TradeItemsValid = false; + break; + } + const Item_Struct *Item = database.GetItem(gis->Items[i]); + + if (!Item) { + Message(13, "Unexpected error. Unable to start trader mode"); + TradeItemsValid = false; + break; + } + + if (Item->NoDrop == 0) { + Message(13, "NODROP Item in Trader Satchel. Unable to start trader mode"); + TradeItemsValid = false; + break; + } + } + + if (!TradeItemsValid) { + Trader_EndTrader(); + return; + } + + for (uint32 i = 0; i < max_items; i++) { + if (database.GetItem(gis->Items[i])) { + database.SaveTraderItem(this->CharacterID(), gis->Items[i], gis->SerialNumber[i], + gis->Charges[i], ints->ItemCost[i], i); + } + else { + //return; //sony doesnt memset so assume done on first bad item + break; + } + + } + safe_delete(gis); + + this->Trader_StartTrader(); + + if (GetClientVersion() >= EQClientRoF) + { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_Trader, sizeof(TraderStatus_Struct)); + TraderStatus_Struct* tss = (TraderStatus_Struct*)outapp->pBuffer; + tss->Code = BazaarTrader_StartTraderMode2; + QueuePacket(outapp); + _pkt(TRADING__PACKETS, outapp); + safe_delete(outapp); + } + } + else if (app->size == sizeof(TraderStatus_Struct)) + { + TraderStatus_Struct* tss = (TraderStatus_Struct*)app->pBuffer; + + if (tss->Code == BazaarTrader_ShowItems) + { + Trader_ShowItems(); + } + } + else { + _log(TRADING__CLIENT, "Client::Handle_OP_Trader: Unknown TraderStruct code of: %i\n", + ints->Code); + + LogFile->write(EQEMuLog::Error, "Unknown TraderStruct code of: %i\n", ints->Code); + } + } + + else if (app->size == sizeof(TraderPriceUpdate_Struct)) + { + HandleTraderPriceUpdate(app); + } + else { + _log(TRADING__CLIENT, "Unknown size for OP_Trader: %i\n", app->size); + LogFile->write(EQEMuLog::Error, "Unknown size for OP_Trader: %i\n", app->size); + DumpPacket(app); + return; + } + + return; +} + +void Client::Handle_OP_TraderBuy(const EQApplicationPacket *app) +{ + // Bazaar Trader: + // + // Client has elected to buy an item from a Trader + // + _pkt(TRADING__PACKETS, app); + + if (app->size == sizeof(TraderBuy_Struct)){ + + TraderBuy_Struct* tbs = (TraderBuy_Struct*)app->pBuffer; + + if (Client* Trader = entity_list.GetClientByID(tbs->TraderID)){ + + BuyTraderItem(tbs, Trader, app); + } + else { + _log(TRADING__CLIENT, "Client::Handle_OP_TraderBuy: Null Client Pointer"); + } + } + else { + _log(TRADING__CLIENT, "Client::Handle_OP_TraderBuy: Struct size mismatch"); + + } + return; +} + +void Client::Handle_OP_TradeRequest(const EQApplicationPacket *app) +{ + if (app->size != sizeof(TradeRequest_Struct)) { + LogFile->write(EQEMuLog::Error, "Wrong size: OP_TradeRequest, size=%i, expected %i", app->size, sizeof(TradeRequest_Struct)); + return; + } + // Client requesting a trade session from an npc/client + // Trade session not started until OP_TradeRequestAck is sent + + BreakInvis(); + + // Pass trade request on to recipient + TradeRequest_Struct* msg = (TradeRequest_Struct*)app->pBuffer; + Mob* tradee = entity_list.GetMob(msg->to_mob_id); + + if (tradee && tradee->IsClient()) { + tradee->CastToClient()->QueuePacket(app); + } +#ifndef BOTS + else if (tradee && tradee->IsNPC()) { +#else + else if (tradee && (tradee->IsNPC() || tradee->IsBot())) { +#endif + //npcs always accept + trade->Start(msg->to_mob_id); + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_TradeRequestAck, sizeof(TradeRequest_Struct)); + TradeRequest_Struct* acc = (TradeRequest_Struct*)outapp->pBuffer; + acc->from_mob_id = msg->to_mob_id; + acc->to_mob_id = msg->from_mob_id; + FastQueuePacket(&outapp); + safe_delete(outapp); + } + return; +} + +void Client::Handle_OP_TradeRequestAck(const EQApplicationPacket *app) +{ + if (app->size != sizeof(TradeRequest_Struct)) { + LogFile->write(EQEMuLog::Error, "Wrong size: OP_TradeRequestAck, size=%i, expected %i", app->size, sizeof(TradeRequest_Struct)); + return; + } + // Trade request recipient is acknowledging they are able to trade + // After this, the trade session has officially started + // Send ack on to trade initiator if client + TradeRequest_Struct* msg = (TradeRequest_Struct*)app->pBuffer; + Mob* tradee = entity_list.GetMob(msg->to_mob_id); + + if (tradee && tradee->IsClient()) { + trade->Start(msg->to_mob_id); + tradee->CastToClient()->QueuePacket(app); + } + return; +} + +void Client::Handle_OP_TraderShop(const EQApplicationPacket *app) +{ + // Bazaar Trader: + // + // This is when a potential purchaser right clicks on this client who is in Trader mode to + // browse their goods. + // + _pkt(TRADING__PACKETS, app); + + TraderClick_Struct* tcs = (TraderClick_Struct*)app->pBuffer; + + if (app->size != sizeof(TraderClick_Struct)) { + + _log(TRADING__CLIENT, "Client::Handle_OP_TraderShop: Returning due to struct size mismatch"); + + return; + } + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_TraderShop, sizeof(TraderClick_Struct)); + + TraderClick_Struct* outtcs = (TraderClick_Struct*)outapp->pBuffer; + + Client* Customer = entity_list.GetClientByID(tcs->TraderID); + + if (Customer) + outtcs->Approval = Customer->WithCustomer(GetID()); + else { + _log(TRADING__CLIENT, "Client::Handle_OP_TraderShop: entity_list.GetClientByID(tcs->traderid)" + " returned a nullptr pointer"); + return; + } + + outtcs->TraderID = tcs->TraderID; + + outtcs->Unknown008 = 0x3f800000; + + QueuePacket(outapp); + + _pkt(TRADING__PACKETS, outapp); + + if (outtcs->Approval) { + this->BulkSendTraderInventory(Customer->CharacterID()); + Customer->Trader_CustomerBrowsing(this); + } + else + Message_StringID(clientMessageYellow, TRADER_BUSY); + + safe_delete(outapp); + + return; +} + +void Client::Handle_OP_TradeSkillCombine(const EQApplicationPacket *app) +{ + if (app->size != sizeof(NewCombine_Struct)) { + LogFile->write(EQEMuLog::Error, "Invalid size for NewCombine_Struct: Expected: %i, Got: %i", + sizeof(NewCombine_Struct), app->size); + return; + } + /*if (m_tradeskill_object == nullptr) { + Message(13, "Error: Server is not aware of the tradeskill container you are attempting to use"); + return; + }*/ + + //fixed this to work for non-world objects + + // Delegate to tradeskill object to perform combine + NewCombine_Struct* in_combine = (NewCombine_Struct*)app->pBuffer; + Object::HandleCombine(this, in_combine, m_tradeskill_object); + return; +} + +void Client::Handle_OP_Translocate(const EQApplicationPacket *app) +{ + + if (app->size != sizeof(Translocate_Struct)) { + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_Translocate expected %i got %i", sizeof(Translocate_Struct), app->size); + DumpPacket(app); + return; + } + Translocate_Struct *its = (Translocate_Struct*)app->pBuffer; + + if (!PendingTranslocate) + return; + + if ((RuleI(Spells, TranslocateTimeLimit) > 0) && (time(nullptr) > (TranslocateTime + RuleI(Spells, TranslocateTimeLimit)))) { + Message(13, "You did not accept the Translocate within the required time limit."); + PendingTranslocate = false; + return; + } + + if (its->Complete == 1) { + + int SpellID = PendingTranslocateData.spell_id; + int i = parse->EventSpell(EVENT_SPELL_EFFECT_TRANSLOCATE_COMPLETE, nullptr, this, SpellID, 0); + + if (i == 0) + { + // If the spell has a translocate to bind effect, AND we are already in the zone the client + // is bound in, use the GoToBind method. If we send OP_Translocate in this case, the client moves itself + // to the bind coords it has from the PlayerProfile, but with the X and Y reversed. I suspect they are + // reversed in the pp, and since spells like Gate are handled serverside, this has not mattered before. + if (((SpellID == 1422) || (SpellID == 1334) || (SpellID == 3243)) && + (zone->GetZoneID() == PendingTranslocateData.zone_id && + zone->GetInstanceID() == PendingTranslocateData.instance_id)) + { + PendingTranslocate = false; + GoToBind(); + return; + } + + ////Was sending the packet back to initiate client zone... + ////but that could be abusable, so lets go through proper channels + MovePC(PendingTranslocateData.zone_id, PendingTranslocateData.instance_id, + PendingTranslocateData.x, PendingTranslocateData.y, + PendingTranslocateData.z, PendingTranslocateData.heading, 0, ZoneSolicited); + } + } + + PendingTranslocate = false; +} + +void Client::Handle_OP_TributeItem(const EQApplicationPacket *app) +{ + _log(TRIBUTE__IN, "Received OP_TributeItem of length %d", app->size); + _pkt(TRIBUTE__IN, app); + + //player donates an item... + if (app->size != sizeof(TributeItem_Struct)) + printf("Error in OP_TributeItem. Expected size of: %zu, but got: %i\n", sizeof(StartTribute_Struct), app->size); + else { + TributeItem_Struct* t = (TributeItem_Struct*)app->pBuffer; + + tribute_master_id = t->tribute_master_id; + //make sure they are dealing with a valid tribute master + Mob* tribmast = entity_list.GetMob(t->tribute_master_id); + if (!tribmast || !tribmast->IsNPC() || tribmast->GetClass() != TRIBUTE_MASTER) + return; + if (DistNoRoot(*tribmast) > USE_NPC_RANGE2) + return; + + t->tribute_points = TributeItem(t->slot, t->quantity); + + _log(TRIBUTE__OUT, "Sending tribute item reply with %d points", t->tribute_points); + _pkt(TRIBUTE__OUT, app); + + QueuePacket(app); + } + return; +} + +void Client::Handle_OP_TributeMoney(const EQApplicationPacket *app) +{ + _log(TRIBUTE__IN, "Received OP_TributeMoney of length %d", app->size); + _pkt(TRIBUTE__IN, app); + + //player donates money + if (app->size != sizeof(TributeMoney_Struct)) + printf("Error in OP_TributeMoney. Expected size of: %zu, but got: %i\n", sizeof(StartTribute_Struct), app->size); + else { + TributeMoney_Struct* t = (TributeMoney_Struct*)app->pBuffer; + + tribute_master_id = t->tribute_master_id; + //make sure they are dealing with a valid tribute master + Mob* tribmast = entity_list.GetMob(t->tribute_master_id); + if (!tribmast || !tribmast->IsNPC() || tribmast->GetClass() != TRIBUTE_MASTER) + return; + if (DistNoRoot(*tribmast) > USE_NPC_RANGE2) + return; + + t->tribute_points = TributeMoney(t->platinum); + + _log(TRIBUTE__OUT, "Sending tribute money reply with %d points", t->tribute_points); + _pkt(TRIBUTE__OUT, app); + + QueuePacket(app); + } + return; +} + +void Client::Handle_OP_TributeNPC(const EQApplicationPacket *app) +{ + _log(TRIBUTE__IN, "Received OP_TributeNPC of length %d", app->size); + _pkt(TRIBUTE__IN, app); + + return; +} + +void Client::Handle_OP_TributeToggle(const EQApplicationPacket *app) +{ + _log(TRIBUTE__IN, "Received OP_TributeToggle of length %d", app->size); + _pkt(TRIBUTE__IN, app); + + if (app->size != sizeof(uint32)) + LogFile->write(EQEMuLog::Error, "Invalid size on OP_TributeToggle packet"); + else { + uint32 *val = (uint32 *)app->pBuffer; + ToggleTribute(*val ? true : false); + } + return; +} + +void Client::Handle_OP_TributeUpdate(const EQApplicationPacket *app) +{ + _log(TRIBUTE__IN, "Received OP_TributeUpdate of length %d", app->size); + _pkt(TRIBUTE__IN, app); + + //sent when the client changes their tribute settings... + if (app->size != sizeof(TributeInfo_Struct)) + LogFile->write(EQEMuLog::Error, "Invalid size on OP_TributeUpdate packet"); + else { + TributeInfo_Struct *t = (TributeInfo_Struct *)app->pBuffer; + ChangeTributeSettings(t); + } + return; +} + +void Client::Handle_OP_VetClaimRequest(const EQApplicationPacket *app) +{ + if (app->size < sizeof(VeteranClaimRequest)) + { + LogFile->write(EQEMuLog::Debug, "OP_VetClaimRequest size lower than expected: got %u expected at least %u", + app->size, sizeof(VeteranClaimRequest)); + DumpPacket(app); + return; + } + + VeteranClaimRequest *vcr = (VeteranClaimRequest*)app->pBuffer; + + if (vcr->claim_id == 0xFFFFFFFF) //request update packet + { + SendRewards(); + } + else //try to claim something! + { + if (!TryReward(vcr->claim_id)) + { + Message(13, "Your claim has been rejected."); + EQApplicationPacket *vetapp = new EQApplicationPacket(OP_VetClaimReply, sizeof(VeteranClaimReply)); + VeteranClaimReply * cr = (VeteranClaimReply*)vetapp->pBuffer; + strcpy(cr->name, GetName()); + cr->claim_id = vcr->claim_id; + cr->reject_field = -1; + FastQueuePacket(&vetapp); + } + else + { + EQApplicationPacket *vetapp = new EQApplicationPacket(OP_VetClaimReply, sizeof(VeteranClaimReply)); + VeteranClaimReply * cr = (VeteranClaimReply*)vetapp->pBuffer; + strcpy(cr->name, GetName()); + cr->claim_id = vcr->claim_id; + cr->reject_field = 0; + FastQueuePacket(&vetapp); + } + } +} + +void Client::Handle_OP_VoiceMacroIn(const EQApplicationPacket *app) +{ + + if (app->size != sizeof(VoiceMacroIn_Struct)) { + + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_VoiceMacroIn expected %i got %i", + sizeof(VoiceMacroIn_Struct), app->size); + + DumpPacket(app); + + return; + } + + if (!RuleB(Chat, EnableVoiceMacros)) return; + + VoiceMacroIn_Struct* vmi = (VoiceMacroIn_Struct*)app->pBuffer; + + VoiceMacroReceived(vmi->Type, vmi->Target, vmi->MacroNumber); + +} + +void Client::Handle_OP_WearChange(const EQApplicationPacket *app) +{ + if (app->size != sizeof(WearChange_Struct)) { + std::cout << "Wrong size: OP_WearChange, size=" << app->size << ", expected " << sizeof(WearChange_Struct) << std::endl; + return; + } + + WearChange_Struct* wc = (WearChange_Struct*)app->pBuffer; + if (wc->spawn_id != GetID()) + return; + + // we could maybe ignore this and just send our own from moveitem + entity_list.QueueClients(this, app, true); + return; +} + +void Client::Handle_OP_WhoAllRequest(const EQApplicationPacket *app) +{ + if (app->size != sizeof(Who_All_Struct)) { + std::cout << "Wrong size on OP_WhoAll. Got: " << app->size << ", Expected: " << sizeof(Who_All_Struct) << std::endl; + return; + } + Who_All_Struct* whoall = (Who_All_Struct*)app->pBuffer; + + if (whoall->type == 0) // SoF only, for regular /who + entity_list.ZoneWho(this, whoall); + else + WhoAll(whoall); + return; +} + +void Client::Handle_OP_XTargetAutoAddHaters(const EQApplicationPacket *app) +{ + if (app->size != 1) + { + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_XTargetAutoAddHaters, expected 1, got %i", app->size); + DumpPacket(app); + return; + } + + XTargetAutoAddHaters = app->ReadUInt8(0); +} + +void Client::Handle_OP_XTargetRequest(const EQApplicationPacket *app) +{ + if (app->size < 12) + { + LogFile->write(EQEMuLog::Debug, "Size mismatch in OP_XTargetRequest, expected at least 12, got %i", app->size); + DumpPacket(app); + return; + } + + uint32 Unknown000 = app->ReadUInt32(0); + + if (Unknown000 != 1) + return; + + uint32 Slot = app->ReadUInt32(4); + + if (Slot >= XTARGET_HARDCAP) + return; + + XTargetType Type = (XTargetType)app->ReadUInt32(8); + + XTargets[Slot].Type = Type; + XTargets[Slot].ID = 0; + XTargets[Slot].Name[0] = 0; + + switch (Type) + { + case Empty: + case Auto: + { + break; + } + + case CurrentTargetPC: + { + char Name[65]; + + app->ReadString(Name, 12, 64); + Client *c = entity_list.GetClientByName(Name); + if (c) + { + XTargets[Slot].ID = c->GetID(); + strncpy(XTargets[Slot].Name, c->GetName(), 64); + } + else + { + strncpy(XTargets[Slot].Name, Name, 64); + } + SendXTargetPacket(Slot, c); + + break; + } + + case CurrentTargetNPC: + { + char Name[65]; + app->ReadString(Name, 12, 64); + Mob *m = entity_list.GetMob(Name); + if (m) + { + XTargets[Slot].ID = m->GetID(); + SendXTargetPacket(Slot, m); + break; + } + } + + case TargetsTarget: + { + if (GetTarget()) + UpdateXTargetType(TargetsTarget, GetTarget()->GetTarget()); + else + UpdateXTargetType(TargetsTarget, nullptr); + + break; + } + + case GroupTank: + { + Group *g = GetGroup(); + + if (g) + { + Client *c = entity_list.GetClientByName(g->GetMainTankName()); + + if (c) + { + XTargets[Slot].ID = c->GetID(); + strncpy(XTargets[Slot].Name, c->GetName(), 64); + } + else + { + strncpy(XTargets[Slot].Name, g->GetMainTankName(), 64); + } + SendXTargetPacket(Slot, c); + } + break; + } + case GroupTankTarget: + { + Group *g = GetGroup(); + + if (g) + g->NotifyTankTarget(this); + + break; + } + + case GroupAssist: + { + Group *g = GetGroup(); + + if (g) + { + Client *c = entity_list.GetClientByName(g->GetMainAssistName()); + + if (c) + { + XTargets[Slot].ID = c->GetID(); + strncpy(XTargets[Slot].Name, c->GetName(), 64); + } + else + { + strncpy(XTargets[Slot].Name, g->GetMainAssistName(), 64); + } + SendXTargetPacket(Slot, c); + } + break; + } + + case GroupAssistTarget: + { + + Group *g = GetGroup(); + + if (g) + g->NotifyAssistTarget(this); + + break; + } + + case Puller: + { + Group *g = GetGroup(); + + if (g) + { + Client *c = entity_list.GetClientByName(g->GetPullerName()); + + if (c) + { + XTargets[Slot].ID = c->GetID(); + strncpy(XTargets[Slot].Name, c->GetName(), 64); + } + else + { + strncpy(XTargets[Slot].Name, g->GetPullerName(), 64); + } + SendXTargetPacket(Slot, c); + } + break; + } + + case PullerTarget: + { + + Group *g = GetGroup(); + + if (g) + g->NotifyPullerTarget(this); + + break; + } + + case GroupMarkTarget1: + case GroupMarkTarget2: + case GroupMarkTarget3: + { + Group *g = GetGroup(); + + if (g) + g->SendMarkedNPCsToMember(this); + + break; + } + + case RaidAssist1: + case RaidAssist2: + case RaidAssist3: + case RaidAssist1Target: + case RaidAssist2Target: + case RaidAssist3Target: + case RaidMarkTarget1: + case RaidMarkTarget2: + case RaidMarkTarget3: + { + // Not implemented yet. + break; + } + + case MyPet: + { + Mob *m = GetPet(); + if (m) + { + XTargets[Slot].ID = m->GetID(); + SendXTargetPacket(Slot, m); + + } + break; + } + case MyPetTarget: + { + Mob *m = GetPet(); + + if (m) + m = m->GetTarget(); + + if (m) + { + XTargets[Slot].ID = m->GetID(); + SendXTargetPacket(Slot, m); + + } + break; + } + + default: + LogFile->write(EQEMuLog::Debug, "Unhandled XTarget Type %i", Type); + break; + } + +} + +void Client::Handle_OP_YellForHelp(const EQApplicationPacket *app) +{ + EQApplicationPacket *outapp = new EQApplicationPacket(OP_YellForHelp, 4); + *(uint32 *)outapp->pBuffer = GetID(); + entity_list.QueueCloseClients(this, outapp, true, 100.0); + safe_delete(outapp); + return; +} + +/* +void Client::Handle_OP_ZoneChange(const EQApplicationPacket *app) +{ + return; +} +*/ diff --git a/zone/client_packet.h b/zone/client_packet.h index 0868b93ba..51b6713b7 100644 --- a/zone/client_packet.h +++ b/zone/client_packet.h @@ -1,4 +1,4 @@ - // connecting opcode handlers + /* Connecting OpCode Handlers */ void Handle_Connect_0x3e33(const EQApplicationPacket *app); void Handle_Connect_OP_ApproveZone(const EQApplicationPacket *app); void Handle_Connect_OP_ClientError(const EQApplicationPacket *app); @@ -20,7 +20,7 @@ void Handle_Connect_OP_WorldObjectsSent(const EQApplicationPacket *app); void Handle_Connect_OP_ZoneComplete(const EQApplicationPacket *app); void Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app); - // connected opcode handlers + /* Connected opcode handlers*/ void Handle_0x0193(const EQApplicationPacket *app); void Handle_0x01e7(const EQApplicationPacket *app); void Handle_OP_AAAction(const EQApplicationPacket *app); diff --git a/zone/client_process.cpp b/zone/client_process.cpp index 7d2bac2c9..2c6e080c4 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -20,13 +20,8 @@ */ #include "../common/debug.h" #include -#include -#include -#include #include -#include #include -#include #ifdef _WINDOWS #include @@ -41,29 +36,20 @@ #include #endif -#include "masterentity.h" -#include "zonedb.h" -#include "../common/packet_functions.h" -#include "../common/packet_dump.h" -#include "worldserver.h" -#include "../common/packet_dump_file.h" -#include "../common/string_util.h" -#include "../common/spdat.h" -#include "petitions.h" -#include "npc_ai.h" -#include "../common/skills.h" -#include "forage.h" -#include "zone.h" -#include "event_codes.h" -#include "../common/faction.h" -#include "../common/crc32.h" #include "../common/rulesys.h" -#include "string_ids.h" -#include "map.h" +#include "../common/skills.h" +#include "../common/spdat.h" +#include "../common/string_util.h" +#include "event_codes.h" #include "guild_mgr.h" -#include -#include "quest_parser_collection.h" +#include "map.h" +#include "petitions.h" #include "queryserv.h" +#include "quest_parser_collection.h" +#include "string_ids.h" +#include "worldserver.h" +#include "zone.h" +#include "zonedb.h" extern QueryServ* QServ; extern Zone* zone; @@ -440,7 +426,7 @@ bool Client::Process() { if (auto_attack_target && flurrychance) { - if(MakeRandomInt(0, 99) < flurrychance) + if(zone->random.Int(0, 99) < flurrychance) { Message_StringID(MT_NPCFlurry, YOU_FLURRY); Attack(auto_attack_target, MainPrimary, false); @@ -457,7 +443,7 @@ bool Client::Process() { wpn->GetItem()->ItemType == ItemType2HBlunt || wpn->GetItem()->ItemType == ItemType2HPiercing ) { - if(MakeRandomInt(0, 99) < ExtraAttackChanceBonus) + if(zone->random.Int(0, 99) < ExtraAttackChanceBonus) { Attack(auto_attack_target, MainPrimary, false); } @@ -502,7 +488,7 @@ bool Client::Process() { int16 DWBonus = spellbonuses.DualWieldChance + itembonuses.DualWieldChance; DualWieldProbability += DualWieldProbability*float(DWBonus)/ 100.0f; - float random = MakeRandomFloat(0, 1); + float random = zone->random.Real(0, 1); CheckIncreaseSkill(SkillDualWield, auto_attack_target, -10); if (random < DualWieldProbability){ // Max 78% of DW if(CheckAAEffect(aaEffectRampage)) { @@ -567,9 +553,8 @@ bool Client::Process() { viral_timer_counter = 0; } - if(projectile_timer.Check()) - SpellProjectileEffect(); - + ProjectileAttack(); + if(spellbonuses.GravityEffect == 1) { if(gravity_timer.Check()) DoGravityEffect(); @@ -1006,7 +991,7 @@ void Client::BulkSendMerchantInventory(int merchant_id, int npcid) { if (fac != 0 && GetModCharacterFactionLevel(fac) < ml.faction_required) continue; - handychance = MakeRandomInt(0, merlist.size() + tmp_merlist.size() - 1); + handychance = zone->random.Int(0, merlist.size() + tmp_merlist.size() - 1); item = database.GetItem(ml.item); if (item) { @@ -1082,7 +1067,7 @@ void Client::BulkSendMerchantInventory(int merchant_id, int npcid) { zone->tmpmerchanttable[npcid] = tmp_merlist; if (merch != nullptr && handyitem) { char handy_id[8] = { 0 }; - int greeting = MakeRandomInt(0, 4); + int greeting = zone->random.Int(0, 4); int greet_id = 0; switch (greeting) { case 1: @@ -1643,7 +1628,7 @@ void Client::OPGMTraining(const EQApplicationPacket *app) // welcome message if (pTrainer && pTrainer->IsNPC()) { - pTrainer->Say_StringID(MakeRandomInt(1204, 1207), GetCleanName()); + pTrainer->Say_StringID(zone->random.Int(1204, 1207), GetCleanName()); } } @@ -1670,7 +1655,7 @@ void Client::OPGMEndTraining(const EQApplicationPacket *app) // goodbye message if (pTrainer->IsNPC()) { - pTrainer->Say_StringID(MakeRandomInt(1208, 1211), GetCleanName()); + pTrainer->Say_StringID(zone->random.Int(1208, 1211), GetCleanName()); } } @@ -2162,7 +2147,7 @@ void Client::HandleRespawnFromHover(uint32 Option) _log(SPELLS__REZ, "Found corpse. Marking corpse as rezzed."); corpse->IsRezzed(true); - corpse->CompleteRezz(); + corpse->CompleteResurrection(); } } else //Not rez diff --git a/zone/client_process.cpp.orig b/zone/client_process.cpp.orig new file mode 100644 index 000000000..3be80dc59 --- /dev/null +++ b/zone/client_process.cpp.orig @@ -0,0 +1,2427 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2003 EQEMu Development Team (http://eqemulator.net) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY except by those people which sell it, which + are required to give you total support for your newly bought product; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + client_process.cpp: + Handles client login sequence and packets sent from client to zone +*/ +#include "../common/debug.h" +#include +#include +#include + +#ifdef _WINDOWS + #include + #include + #define snprintf _snprintf + #define strncasecmp _strnicmp + #define strcasecmp _stricmp +#else + #include + #include + #include + #include +#endif + +#include "../common/rulesys.h" +#include "../common/skills.h" +#include "../common/spdat.h" +#include "../common/string_util.h" +#include "event_codes.h" +#include "guild_mgr.h" +#include "map.h" +#include "petitions.h" +#include "queryserv.h" +#include "quest_parser_collection.h" +#include "string_ids.h" +#include "worldserver.h" +#include "zone.h" +#include "zonedb.h" + +extern QueryServ* QServ; +extern Zone* zone; +extern volatile bool ZoneLoaded; +extern WorldServer worldserver; +extern PetitionList petition_list; +extern EntityList entity_list; + +bool Client::Process() { + bool ret = true; + + if(Connected() || IsLD()) + { + // try to send all packets that weren't sent before + if(!IsLD() && zoneinpacket_timer.Check()) + { + SendAllPackets(); + } + + if(adventure_request_timer) + { + if(adventure_request_timer->Check()) + { + safe_delete(adventure_request_timer); + } + } + + if(adventure_create_timer) + { + if(adventure_create_timer->Check()) + { + safe_delete(adventure_create_timer); + } + } + + if(adventure_leave_timer) + { + if(adventure_leave_timer->Check()) + { + safe_delete(adventure_leave_timer); + } + } + + if(adventure_door_timer) + { + if(adventure_door_timer->Check()) + { + safe_delete(adventure_door_timer); + } + } + + if(adventure_stats_timer) + { + if(adventure_stats_timer->Check()) + { + safe_delete(adventure_stats_timer); + } + } + + if(adventure_leaderboard_timer) + { + if(adventure_leaderboard_timer->Check()) + { + safe_delete(adventure_leaderboard_timer); + } + } + + if(dead) + { + SetHP(-100); + if(RespawnFromHoverTimer.Check()) + HandleRespawnFromHover(0); + } + + if(IsTracking() && (GetClientVersion() >= EQClientSoD) && TrackingTimer.Check()) + DoTracking(); + + if(hpupdate_timer.Check()) + SendHPUpdate(); + + if(mana_timer.Check()) + SendManaUpdatePacket(); + + if(dead && dead_timer.Check()) { + database.MoveCharacterToZone(GetName(), database.GetZoneName(m_pp.binds[0].zoneId)); + + m_pp.zone_id = m_pp.binds[0].zoneId; + m_pp.zoneInstance = m_pp.binds[0].instance_id; + m_pp.x = m_pp.binds[0].x; + m_pp.y = m_pp.binds[0].y; + m_pp.z = m_pp.binds[0].z; + Save(); + + Group *mygroup = GetGroup(); + if (mygroup) + { + entity_list.MessageGroup(this,true,15,"%s died.", GetName()); + mygroup->MemberZoned(this); + } + Raid *myraid = entity_list.GetRaidByClient(this); + if (myraid) + { + myraid->MemberZoned(this); + } + return(false); + } + + if(charm_update_timer.Check()) + { + CalcItemScale(); + } + + if(TaskPeriodic_Timer.Check() && taskstate) + taskstate->TaskPeriodicChecks(this); + + if(linkdead_timer.Check()) + { + LeaveGroup(); + Save(); + if (GetMerc()) + { + GetMerc()->Save(); + GetMerc()->Depop(); + } + + Raid *myraid = entity_list.GetRaidByClient(this); + if (myraid) + { + myraid->MemberZoned(this); + } + return false; //delete client + } + + if (camp_timer.Check()) + { + LeaveGroup(); + Save(); + if (GetMerc()) + { + GetMerc()->Save(); + GetMerc()->Depop(); + } + instalog = true; + } + + if (IsStunned() && stunned_timer.Check()) { + this->stunned = false; + this->stunned_timer.Disable(); + } + + if(!m_CheatDetectMoved) + { + m_TimeSinceLastPositionCheck = Timer::GetCurrentTime(); + } + + if (bardsong_timer.Check() && bardsong != 0) { + //NOTE: this is kinda a heavy-handed check to make sure the mob still exists before + //doing the next pulse on them... + Mob *song_target; + if(bardsong_target_id == GetID()) { + song_target = this; + } else { + song_target = entity_list.GetMob(bardsong_target_id); + } + + if (song_target == nullptr) { + InterruptSpell(SONG_ENDS_ABRUPTLY, 0x121, bardsong); + } else { + if(!ApplyNextBardPulse(bardsong, song_target, bardsong_slot)) + InterruptSpell(SONG_ENDS_ABRUPTLY, 0x121, bardsong); + //SpellFinished(bardsong, bardsong_target, bardsong_slot, spells[bardsong].mana); + } + } + + if(GetMerc()) + { + UpdateMercTimer(); + } + + if(GetMercInfo().MercTemplateID != 0 && GetMercInfo().IsSuspended) + { + if(p_timers.Expired(&database, pTimerMercSuspend, false)) + { + CheckMercSuspendTimer(); + } + } + + if(IsAIControlled()) + AI_Process(); + + if (bindwound_timer.Check() && bindwound_target != 0) { + BindWound(bindwound_target, false); + } + + if(KarmaUpdateTimer) + { + if(KarmaUpdateTimer->Check(false)) + { + KarmaUpdateTimer->Start(RuleI(Chat, KarmaUpdateIntervalMS)); + database.UpdateKarma(AccountID(), ++TotalKarma); + } + } + + if(qGlobals) + { + if(qglobal_purge_timer.Check()) + { + qGlobals->PurgeExpiredGlobals(); + } + } + + bool may_use_attacks = false; + /* + Things which prevent us from attacking: + - being under AI control, the AI does attacks + - being dead + - casting a spell and bard check + - not having a target + - being stunned or mezzed + - having used a ranged weapon recently + */ + if(auto_attack) { + if(!IsAIControlled() && !dead + && !(spellend_timer.Enabled() && casting_spell_id && !IsBardSong(casting_spell_id)) + && !IsStunned() && !IsFeared() && !IsMezzed() && GetAppearance() != eaDead && !IsMeleeDisabled() + ) + may_use_attacks = true; + + if(may_use_attacks && ranged_timer.Enabled()) { + //if the range timer is enabled, we need to consider it + if(!ranged_timer.Check(false)) { + //the ranged timer has not elapsed, cannot attack. + may_use_attacks = false; + } + } + } + + if(AutoFireEnabled()){ + ItemInst *ranged = GetInv().GetItem(MainRange); + if(ranged) + { + if(ranged->GetItem() && ranged->GetItem()->ItemType == ItemTypeBow){ + if(ranged_timer.Check(false)){ + if(GetTarget() && (GetTarget()->IsNPC() || GetTarget()->IsClient())){ + if(GetTarget()->InFrontMob(this, GetTarget()->GetX(), GetTarget()->GetY())){ + if(CheckLosFN(GetTarget())){ + //client has built in los check, but auto fire does not.. done last. + RangedAttack(GetTarget()); + if (CheckDoubleRangedAttack()) + RangedAttack(GetTarget(), true); + } + else + ranged_timer.Start(); + } + else + ranged_timer.Start(); + } + else + ranged_timer.Start(); + } + } + else if(ranged->GetItem() && (ranged->GetItem()->ItemType == ItemTypeLargeThrowing || ranged->GetItem()->ItemType == ItemTypeSmallThrowing)){ + if(ranged_timer.Check(false)){ + if(GetTarget() && (GetTarget()->IsNPC() || GetTarget()->IsClient())){ + if(GetTarget()->InFrontMob(this, GetTarget()->GetX(), GetTarget()->GetY())){ + if(CheckLosFN(GetTarget())){ + //client has built in los check, but auto fire does not.. done last. + ThrowingAttack(GetTarget()); + } + else + ranged_timer.Start(); + } + else + ranged_timer.Start(); + } + else + ranged_timer.Start(); + } + } + } + } + + Mob *auto_attack_target = GetTarget(); + if (auto_attack && auto_attack_target != nullptr && may_use_attacks && attack_timer.Check()) + { + //check if change + //only check on primary attack.. sorry offhand you gotta wait! + if(aa_los_them_mob) + { + if(auto_attack_target != aa_los_them_mob || + m_AutoAttackPosition.m_X != GetX() || + m_AutoAttackPosition.m_Y != GetY() || + m_AutoAttackPosition.m_Z != GetZ() || + m_AutoAttackTargetLocation.m_X != aa_los_them_mob->GetX() || + m_AutoAttackTargetLocation.m_Y != aa_los_them_mob->GetY() || + m_AutoAttackTargetLocation.m_Z != aa_los_them_mob->GetZ()) + { + aa_los_them_mob = auto_attack_target; + m_AutoAttackPosition = GetPosition(); + m_AutoAttackTargetLocation = aa_los_them_mob->GetPosition(); + los_status = CheckLosFN(auto_attack_target); + los_status_facing = IsFacingMob(aa_los_them_mob); + } + // If only our heading changes, we can skip the CheckLosFN call + // but above we still need to update los_status_facing + if (m_AutoAttackPosition.m_Heading != GetHeading()) { + m_AutoAttackPosition.m_Heading = GetHeading(); + los_status_facing = IsFacingMob(aa_los_them_mob); + } + } + else + { + aa_los_them_mob = auto_attack_target; + m_AutoAttackPosition = GetPosition(); + m_AutoAttackTargetLocation = aa_los_them_mob->GetPosition(); + los_status = CheckLosFN(auto_attack_target); + los_status_facing = IsFacingMob(aa_los_them_mob); + } + + if (!CombatRange(auto_attack_target)) + { + Message_StringID(MT_TooFarAway,TARGET_TOO_FAR); + } + else if (auto_attack_target == this) + { + Message_StringID(MT_TooFarAway,TRY_ATTACKING_SOMEONE); + } + else if (!los_status || !los_status_facing) + { + //you can't see your target + } + else if (auto_attack_target->GetHP() > -10) // -10 so we can watch people bleed in PvP + { + if(CheckAAEffect(aaEffectRampage)) + { + entity_list.AEAttack(this, 30); + } else { + Attack(auto_attack_target, MainPrimary); // Kaiyodo - added attacking hand to arguments + } + ItemInst *wpn = GetInv().GetItem(MainPrimary); + TryWeaponProc(wpn, auto_attack_target, MainPrimary); + + bool tripleAttackSuccess = false; + if( auto_attack_target && CanThisClassDoubleAttack() ) { + + CheckIncreaseSkill(SkillDoubleAttack, auto_attack_target, -10); + if(CheckDoubleAttack()) { + //should we allow rampage on double attack? + if(CheckAAEffect(aaEffectRampage)) { + entity_list.AEAttack(this, 30); + } else { + Attack(auto_attack_target, MainPrimary, false); + } + } + + //triple attack: rangers, monks, warriors, berserkers over level 60 + if((((GetClass() == MONK || GetClass() == WARRIOR || GetClass() == RANGER || GetClass() == BERSERKER) + && GetLevel() >= 60) || GetSpecialAbility(SPECATK_TRIPLE)) + && CheckDoubleAttack(true)) + { + tripleAttackSuccess = true; + Attack(auto_attack_target, MainPrimary, false); + } + + //quad attack, does this belong here?? + if(GetSpecialAbility(SPECATK_QUAD) && CheckDoubleAttack(true)) + { + Attack(auto_attack_target, MainPrimary, false); + } + } + + //Live AA - Flurry, Rapid Strikes ect (Flurry does not require Triple Attack). + int16 flurrychance = aabonuses.FlurryChance + spellbonuses.FlurryChance + itembonuses.FlurryChance; + + if (auto_attack_target && flurrychance) + { + if(zone->random.Int(0, 99) < flurrychance) + { + Message_StringID(MT_NPCFlurry, YOU_FLURRY); + Attack(auto_attack_target, MainPrimary, false); + Attack(auto_attack_target, MainPrimary, false); + } + } + + int16 ExtraAttackChanceBonus = spellbonuses.ExtraAttackChance + itembonuses.ExtraAttackChance + aabonuses.ExtraAttackChance; + + if (auto_attack_target && ExtraAttackChanceBonus) { + ItemInst *wpn = GetInv().GetItem(MainPrimary); + if(wpn){ + if(wpn->GetItem()->ItemType == ItemType2HSlash || + wpn->GetItem()->ItemType == ItemType2HBlunt || + wpn->GetItem()->ItemType == ItemType2HPiercing ) + { + if(zone->random.Int(0, 99) < ExtraAttackChanceBonus) + { + Attack(auto_attack_target, MainPrimary, false); + } + } + } + } + } + } + + if (GetClass() == WARRIOR || GetClass() == BERSERKER) { + if (!dead && !IsBerserk() && GetHPRatio() < RuleI(Combat, BerserkerFrenzyStart)) { + entity_list.MessageClose_StringID(this, false, 200, 0, BERSERK_START, GetName()); + berserk = true; + } + if (IsBerserk() && GetHPRatio() > RuleI(Combat, BerserkerFrenzyEnd)) { + entity_list.MessageClose_StringID(this, false, 200, 0, BERSERK_END, GetName()); + berserk = false; + } + } + + if(auto_attack && may_use_attacks && auto_attack_target != nullptr + && CanThisClassDualWield() && attack_dw_timer.Check()) + { + // Range check + if(!CombatRange(auto_attack_target)) { + // this is a duplicate message don't use it. + //Message_StringID(MT_TooFarAway,TARGET_TOO_FAR); + } + // Don't attack yourself + else if(auto_attack_target == this) { + //Message_StringID(MT_TooFarAway,TRY_ATTACKING_SOMEONE); + } + else if (!los_status || !los_status_facing) + { + //you can't see your target + } + else if(auto_attack_target->GetHP() > -10) { + float DualWieldProbability = 0.0f; + + int16 Ambidexterity = aabonuses.Ambidexterity + spellbonuses.Ambidexterity + itembonuses.Ambidexterity; + DualWieldProbability = (GetSkill(SkillDualWield) + GetLevel() + Ambidexterity) / 400.0f; // 78.0 max + int16 DWBonus = spellbonuses.DualWieldChance + itembonuses.DualWieldChance; + DualWieldProbability += DualWieldProbability*float(DWBonus)/ 100.0f; + + float random = zone->random.Real(0, 1); + CheckIncreaseSkill(SkillDualWield, auto_attack_target, -10); + if (random < DualWieldProbability){ // Max 78% of DW + if(CheckAAEffect(aaEffectRampage)) { + entity_list.AEAttack(this, 30, MainSecondary); + } else { + Attack(auto_attack_target, MainSecondary); // Single attack with offhand + } + ItemInst *wpn = GetInv().GetItem(MainSecondary); + TryWeaponProc(wpn, auto_attack_target, MainSecondary); + + if( CanThisClassDoubleAttack() && CheckDoubleAttack()) { + if(CheckAAEffect(aaEffectRampage)) { + entity_list.AEAttack(this, 30, MainSecondary); + } else { + if(auto_attack_target && auto_attack_target->GetHP() > -10) + Attack(auto_attack_target, MainSecondary); // Single attack with offhand + } + } + } + } + } + + if (position_timer.Check()) { + if (IsAIControlled()) + { + if(IsMoving()) + SendPosUpdate(2); + else + { + animation = 0; + m_Delta = xyz_heading(0.0f, 0.0f, 0.0f, m_Delta.m_Heading); + SendPosUpdate(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); + pLastUpdate = Timer::GetCurrentTime(); + pLastUpdateWZ = pLastUpdate; + position_timer_counter = 0; + } + else { + pLastUpdate = Timer::GetCurrentTime(); + position_timer_counter++; + } + } + + if(HasVirus()) { + if(viral_timer.Check()) { + viral_timer_counter++; + for(int i = 0; i < MAX_SPELL_TRIGGER*2; i+=2) { + if(viral_spells[i]) { + if(viral_timer_counter % spells[viral_spells[i]].viral_timer == 0) { + SpreadVirus(viral_spells[i], viral_spells[i+1]); + } + } + } + } + if(viral_timer_counter > 999) + viral_timer_counter = 0; + } + +<<<<<<< HEAD + if(projectile_timer.Check()) + SpellProjectileEffect(); + +======= + ProjectileAttack(); + +>>>>>>> master + if(spellbonuses.GravityEffect == 1) { + if(gravity_timer.Check()) + DoGravityEffect(); + } + + if (shield_timer.Check()) + { + if (shield_target) + { + if (!CombatRange(shield_target)) + { + entity_list.MessageClose_StringID(this, false, 100, 0, + END_SHIELDING, GetCleanName(), shield_target->GetCleanName()); + for (int y = 0; y < 2; y++) + { + if (shield_target->shielder[y].shielder_id == GetID()) + { + shield_target->shielder[y].shielder_id = 0; + shield_target->shielder[y].shielder_bonus = 0; + } + } + shield_target = 0; + shield_timer.Disable(); + } + } + else + { + shield_target = 0; + shield_timer.Disable(); + } + } + + SpellProcess(); + if (endupkeep_timer.Check() && !dead){ + DoEnduranceUpkeep(); + } + + if (tic_timer.Check() && !dead) { + CalcMaxHP(); + CalcMaxMana(); + CalcATK(); + CalcMaxEndurance(); + CalcRestState(); + DoHPRegen(); + DoManaRegen(); + DoEnduranceRegen(); + BuffProcess(); + DoStaminaUpdate(); + + if(tribute_timer.Check()) { + ToggleTribute(true); //re-activate the tribute. + } + + if (fishing_timer.Check()) { + GoFish(); + } + + if (autosave_timer.Check()) { + Save(0); + } + + if(m_pp.intoxication > 0) + { + --m_pp.intoxication; + CalcBonuses(); + } + + if(ItemTickTimer.Check()) + { + TickItemCheck(); + } + + if(ItemQuestTimer.Check()) + { + ItemTimerCheck(); + } + } + } + + if (client_state == CLIENT_KICKED) { + Save(); + OnDisconnect(true); + std::cout << "Client disconnected (cs=k): " << GetName() << std::endl; + return false; + } + + if (client_state == DISCONNECTED) { + OnDisconnect(true); + std::cout << "Client disconnected (cs=d): " << GetName() << std::endl; + database.SetMQDetectionFlag(this->AccountName(), GetName(), "/MQInstantCamp: Possible instant camp disconnect.", zone->GetShortName()); + return false; + } + + if (client_state == CLIENT_ERROR) { + OnDisconnect(true); + std::cout << "Client disconnected (cs=e): " << GetName() << std::endl; + return false; + } + + if (client_state != CLIENT_LINKDEAD && !eqs->CheckState(ESTABLISHED)) { + OnDisconnect(true); + std::cout << "Client linkdead: " << name << std::endl; + + if (GetGM()) { + if (GetMerc()) + { + GetMerc()->Save(); + GetMerc()->Depop(); + } + return false; + } + else if(!linkdead_timer.Enabled()){ + linkdead_timer.Start(RuleI(Zone,ClientLinkdeadMS)); + client_state = CLIENT_LINKDEAD; + AI_Start(CLIENT_LD_TIMEOUT); + SendAppearancePacket(AT_Linkdead, 1); + } + } + + + /************ Get all packets from packet manager out queue and process them ************/ + EQApplicationPacket *app = nullptr; + if(!eqs->CheckState(CLOSING)) + { + while(ret && (app = (EQApplicationPacket *)eqs->PopPacket())) { + if(app) + ret = HandlePacket(app); + safe_delete(app); + } + } + +#ifdef REVERSE_AGGRO + //At this point, we are still connected, everything important has taken + //place, now check to see if anybody wants to aggro us. + // only if client is not feigned + if(ret && !GetFeigned() && scanarea_timer.Check()) { + entity_list.CheckClientAggro(this); + } +#endif + + if (client_state != CLIENT_LINKDEAD && (client_state == CLIENT_ERROR || client_state == DISCONNECTED || client_state == CLIENT_KICKED || !eqs->CheckState(ESTABLISHED))) + { + //client logged out or errored out + //ResetTrade(); + if (client_state != CLIENT_KICKED) { + Save(); + } + + client_state = CLIENT_LINKDEAD; + if (zoning || instalog || GetGM()) + { + Group *mygroup = GetGroup(); + if (mygroup) + { + if (!zoning) + { + entity_list.MessageGroup(this, true, 15, "%s logged out.", GetName()); + LeaveGroup(); + } + else + { + entity_list.MessageGroup(this, true, 15, "%s left the zone.", GetName()); + mygroup->MemberZoned(this); + if (GetMerc() && GetMerc()->HasGroup()) + { + GetMerc()->RemoveMercFromGroup(GetMerc(), GetMerc()->GetGroup()); + } + } + + } + Raid *myraid = entity_list.GetRaidByClient(this); + if (myraid) + { + if (!zoning) + { + //entity_list.MessageGroup(this,true,15,"%s logged out.",GetName()); + myraid->MemberZoned(this); + } + else + { + //entity_list.MessageGroup(this,true,15,"%s left the zone.",GetName()); + myraid->MemberZoned(this); + } + } + OnDisconnect(false); + return false; + } + else + { + LinkDead(); + } + OnDisconnect(true); + } + // Feign Death 2 minutes and zone forgets you + if (forget_timer.Check()) { + forget_timer.Disable(); + entity_list.ClearZoneFeignAggro(this); + Message(0, "Your enemies have forgotten you!"); + } + + return ret; +} + +/* Just a set of actions preformed all over in Client::Process */ +void Client::OnDisconnect(bool hard_disconnect) { + if(hard_disconnect) + { + LeaveGroup(); + if (GetMerc()) + { + GetMerc()->Save(); + GetMerc()->Depop(); + } + Raid *MyRaid = entity_list.GetRaidByClient(this); + + if (MyRaid) + MyRaid->MemberZoned(this); + + parse->EventPlayer(EVENT_DISCONNECT, this, "", 0); + + /* QS: PlayerLogConnectDisconnect */ + if (RuleB(QueryServ, PlayerLogConnectDisconnect)){ + std::string event_desc = StringFormat("Disconnect :: in zoneid:%i instid:%i", this->GetZoneID(), this->GetInstanceID()); + QServ->PlayerLogEvent(Player_Log_Connect_State, this->CharacterID(), event_desc); + } + } + + Mob *Other = trade->With(); + if(Other) + { + mlog(TRADING__CLIENT, "Client disconnected during a trade. Returning their items."); + FinishTrade(this); + + if(Other->IsClient()) + Other->CastToClient()->FinishTrade(Other); + + /* Reset both sides of the trade */ + trade->Reset(); + Other->trade->Reset(); + } + + database.SetFirstLogon(CharacterID(), 0); //We change firstlogon status regardless of if a player logs out to zone or not, because we only want to trigger it on their first login from world. + + /* Remove ourself from all proximities */ + ClearAllProximities(); + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_LogoutReply); + FastQueuePacket(&outapp); + + Disconnect(); +} + +// Sends the client complete inventory used in character login + +// DO WE STILL NEED THE 'ITEMCOMBINED' CONDITIONAL CODE? -U + +//#ifdef ITEMCOMBINED +void Client::BulkSendInventoryItems() { + int16 slot_id = 0; + + // LINKDEAD TRADE ITEMS + // Move trade slot items back into normal inventory..need them there now for the proceeding validity checks -U + for(slot_id = EmuConstants::TRADE_BEGIN; slot_id <= EmuConstants::TRADE_END; slot_id++) { + ItemInst* inst = m_inv.PopItem(slot_id); + if(inst) { + bool is_arrow = (inst->GetItem()->ItemType == ItemTypeArrow) ? true : false; + int16 free_slot_id = m_inv.FindFreeSlot(inst->IsType(ItemClassContainer), true, inst->GetItem()->Size, is_arrow); + mlog(INVENTORY__ERROR, "Incomplete Trade Transaction: Moving %s from slot %i to %i", inst->GetItem()->Name, slot_id, free_slot_id); + PutItemInInventory(free_slot_id, *inst, false); + database.SaveInventory(character_id, nullptr, slot_id); + safe_delete(inst); + } + } + + bool deletenorent = database.NoRentExpired(GetName()); + if(deletenorent){ RemoveNoRent(false); } //client was offline for more than 30 minutes, delete no rent items + + RemoveDuplicateLore(false); + MoveSlotNotAllowed(false); + + // The previous three method calls took care of moving/removing expired/illegal item placements -U + + //TODO: this function is just retarded... it re-allocates the buffer for every + //new item. It should be changed to loop through once, gather the + //lengths, and item packet pointers into an array (fixed length), and + //then loop again to build the packet. + //EQApplicationPacket *packets[50]; + //unsigned long buflen = 0; + //unsigned long pos = 0; + //memset(packets, 0, sizeof(packets)); + //foreach item in the invendor sections + // packets[pos++] = ReturnItemPacket(...) + // buflen += temp->size + //... + //allocat the buffer + //for r from 0 to pos + // put pos[r]->pBuffer into the buffer + //for r from 0 to pos + // safe_delete(pos[r]); + + uint32 size = 0; + uint16 i = 0; + std::map ser_items; + std::map::iterator itr; + + //Inventory items + for(slot_id = MAIN_BEGIN; slot_id < EmuConstants::MAP_POSSESSIONS_SIZE; slot_id++) { + const ItemInst* inst = m_inv[slot_id]; + if(inst) { + std::string packet = inst->Serialize(slot_id); + ser_items[i++] = packet; + size += packet.length(); + } + } + + // Power Source + if(GetClientVersion() >= EQClientSoF) { + const ItemInst* inst = m_inv[MainPowerSource]; + if(inst) { + std::string packet = inst->Serialize(MainPowerSource); + ser_items[i++] = packet; + size += packet.length(); + } + } + + // Bank items + for(slot_id = EmuConstants::BANK_BEGIN; slot_id <= EmuConstants::BANK_END; slot_id++) { + const ItemInst* inst = m_inv[slot_id]; + if(inst) { + std::string packet = inst->Serialize(slot_id); + ser_items[i++] = packet; + size += packet.length(); + } + } + + // Shared Bank items + for(slot_id = EmuConstants::SHARED_BANK_BEGIN; slot_id <= EmuConstants::SHARED_BANK_END; slot_id++) { + const ItemInst* inst = m_inv[slot_id]; + if(inst) { + std::string packet = inst->Serialize(slot_id); + ser_items[i++] = packet; + size += packet.length(); + } + } + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_CharInventory, size); + uchar* ptr = outapp->pBuffer; + for(itr = ser_items.begin(); itr != ser_items.end(); ++itr){ + int length = itr->second.length(); + if(length > 5) { + memcpy(ptr, itr->second.c_str(), length); + ptr += length; + } + } + QueuePacket(outapp); + safe_delete(outapp); +} +/*#else +void Client::BulkSendInventoryItems() +{ + // Search all inventory buckets for items + bool deletenorent=database.NoRentExpired(GetName()); + // Worn items and Inventory items + int16 slot_id = 0; + if(deletenorent){//client was offline for more than 30 minutes, delete no rent items + RemoveNoRent(); + } + for (slot_id=EmuConstants::POSSESSIONS_BEGIN; slot_id<=EmuConstants::POSSESSIONS_END; slot_id++) { + const ItemInst* inst = m_inv[slot_id]; + if (inst){ + SendItemPacket(slot_id, inst, ItemPacketCharInventory); + } + } + // Bank items + for (slot_id=EmuConstants::BANK_BEGIN; slot_id<=EmuConstants::BANK_END; slot_id++) { // 2015... + const ItemInst* inst = m_inv[slot_id]; + if (inst){ + SendItemPacket(slot_id, inst, ItemPacketCharInventory); + } + } + + // Shared Bank items + for (slot_id=EmuConstants::SHARED_BANK_BEGIN; slot_id<=EmuConstants::SHARED_BANK_END; slot_id++) { + const ItemInst* inst = m_inv[slot_id]; + if (inst){ + SendItemPacket(slot_id, inst, ItemPacketCharInventory); + } + } + + // LINKDEAD TRADE ITEMS + // If player went LD during a trade, they have items in the trade inventory + // slots. These items are now being put into their inventory (then queue up on cursor) + for (int16 trade_slot_id=EmuConstants::TRADE_BEGIN; trade_slot_id<=EmuConstants::TRADE_END; trade_slot_id++) { + const ItemInst* inst = m_inv[slot_id]; + if (inst) { + int16 free_slot_id = m_inv.FindFreeSlot(inst->IsType(ItemClassContainer), true, inst->GetItem()->Size); + DeleteItemInInventory(trade_slot_id, 0, false); + PutItemInInventory(free_slot_id, *inst, true); + } + } +} +#endif*/ + +void Client::BulkSendMerchantInventory(int merchant_id, int npcid) { + const Item_Struct* handyitem = nullptr; + uint32 numItemSlots = 80; //The max number of items passed in the transaction. + const Item_Struct *item; + std::list merlist = zone->merchanttable[merchant_id]; + std::list::const_iterator itr; + Mob* merch = entity_list.GetMobByNpcTypeID(npcid); + if (merlist.size() == 0) { //Attempt to load the data, it might have been missed if someone spawned the merchant after the zone was loaded + zone->LoadNewMerchantData(merchant_id); + merlist = zone->merchanttable[merchant_id]; + if (merlist.size() == 0) + return; + } + std::list tmp_merlist = zone->tmpmerchanttable[npcid]; + std::list::iterator tmp_itr; + + uint32 i = 1; + uint8 handychance = 0; + for (itr = merlist.begin(); itr != merlist.end() && i <= numItemSlots; ++itr) { + MerchantList ml = *itr; + if (merch->CastToNPC()->GetMerchantProbability() > ml.probability) + continue; + + if (GetLevel() < ml.level_required) + continue; + + if (!(ml.classes_required & (1 << (GetClass() - 1)))) + continue; + + int32 fac = merch ? merch->GetPrimaryFaction() : 0; + if (fac != 0 && GetModCharacterFactionLevel(fac) < ml.faction_required) + continue; + + handychance = zone->random.Int(0, merlist.size() + tmp_merlist.size() - 1); + + item = database.GetItem(ml.item); + if (item) { + if (handychance == 0) + handyitem = item; + else + handychance--; + int charges = 1; + if (item->ItemClass == ItemClassCommon) + charges = item->MaxCharges; + ItemInst* inst = database.CreateItem(item, charges); + if (inst) { + if (RuleB(Merchant, UsePriceMod)) { + inst->SetPrice((item->Price * (RuleR(Merchant, SellCostMod)) * item->SellRate * Client::CalcPriceMod(merch, false))); + } + else + inst->SetPrice((item->Price * (RuleR(Merchant, SellCostMod)) * item->SellRate)); + inst->SetMerchantSlot(ml.slot); + inst->SetMerchantCount(-1); //unlimited + if (charges > 0) + inst->SetCharges(charges); + else + inst->SetCharges(1); + + SendItemPacket(ml.slot - 1, inst, ItemPacketMerchant); + safe_delete(inst); + } + } + // Account for merchant lists with gaps. + if (ml.slot >= i) { + if (ml.slot > i) + LogFile->write(EQEMuLog::Debug, "(WARNING) Merchantlist contains gap at slot %d. Merchant: %d, NPC: %d", i, merchant_id, npcid); + i = ml.slot + 1; + } + } + std::list origtmp_merlist = zone->tmpmerchanttable[npcid]; + tmp_merlist.clear(); + for (tmp_itr = origtmp_merlist.begin(); tmp_itr != origtmp_merlist.end() && i <= numItemSlots; ++tmp_itr) { + TempMerchantList ml = *tmp_itr; + item = database.GetItem(ml.item); + ml.slot = i; + if (item) { + if (handychance == 0) + handyitem = item; + else + handychance--; + int charges = 1; + //if(item->ItemClass==ItemClassCommon && (int16)ml.charges <= item->MaxCharges) + // charges=ml.charges; + //else + charges = item->MaxCharges; + ItemInst* inst = database.CreateItem(item, charges); + if (inst) { + if (RuleB(Merchant, UsePriceMod)) { + inst->SetPrice((item->Price * (RuleR(Merchant, SellCostMod)) * item->SellRate * Client::CalcPriceMod(merch, false))); + } + else + inst->SetPrice((item->Price * (RuleR(Merchant, SellCostMod)) * item->SellRate)); + inst->SetMerchantSlot(ml.slot); + inst->SetMerchantCount(ml.charges); + if(charges > 0) + inst->SetCharges(item->MaxCharges);//inst->SetCharges(charges); + else + inst->SetCharges(1); + SendItemPacket(ml.slot-1, inst, ItemPacketMerchant); + safe_delete(inst); + } + } + tmp_merlist.push_back(ml); + i++; + } + //this resets the slot + zone->tmpmerchanttable[npcid] = tmp_merlist; + if (merch != nullptr && handyitem) { + char handy_id[8] = { 0 }; + int greeting = zone->random.Int(0, 4); + int greet_id = 0; + switch (greeting) { + case 1: + greet_id = MERCHANT_GREETING; + break; + case 2: + greet_id = MERCHANT_HANDY_ITEM1; + break; + case 3: + greet_id = MERCHANT_HANDY_ITEM2; + break; + case 4: + greet_id = MERCHANT_HANDY_ITEM3; + break; + default: + greet_id = MERCHANT_HANDY_ITEM4; + } + sprintf(handy_id, "%i", greet_id); + + if (greet_id != MERCHANT_GREETING) + 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); +} + +uint8 Client::WithCustomer(uint16 NewCustomer){ + + if(NewCustomer == 0) { + CustomerID = 0; + return 0; + } + + if(CustomerID == 0) { + CustomerID = NewCustomer; + return 1; + } + + // Check that the player browsing our wares hasn't gone away. + + Client* c = entity_list.GetClientByID(CustomerID); + + if(!c) { + _log(TRADING__CLIENT, "Previous customer has gone away."); + CustomerID = NewCustomer; + return 1; + } + + return 0; +} + +void Client::OPRezzAnswer(uint32 Action, uint32 SpellID, uint16 ZoneID, uint16 InstanceID, float x, float y, float z) +{ + if(PendingRezzXP < 0) { + // pendingrezexp is set to -1 if we are not expecting an OP_RezzAnswer + _log(SPELLS__REZ, "Unexpected OP_RezzAnswer. Ignoring it."); + Message(13, "You have already been resurrected.\n"); + return; + } + + if (Action == 1) + { + // Mark the corpse as rezzed in the database, just in case the corpse has buried, or the zone the + // corpse is in has shutdown since the rez spell was cast. + database.MarkCorpseAsRezzed(PendingRezzDBID); + _log(SPELLS__REZ, "Player %s got a %i Rezz, spellid %i in zone%i, instance id %i", + this->name, (uint16)spells[SpellID].base[0], + SpellID, ZoneID, InstanceID); + + this->BuffFadeNonPersistDeath(); + int SpellEffectDescNum = GetSpellEffectDescNum(SpellID); + // Rez spells with Rez effects have this DescNum (first is Titanium, second is 6.2 Client) + if((SpellEffectDescNum == 82) || (SpellEffectDescNum == 39067)) { + SetMana(0); + SetHP(GetMaxHP()/5); + SpellOnTarget(756, this); // Rezz effects + } + else { + SetMana(GetMaxMana()); + SetHP(GetMaxHP()); + } + if(spells[SpellID].base[0] < 100 && spells[SpellID].base[0] > 0 && PendingRezzXP > 0) + { + SetEXP(((int)(GetEXP()+((float)((PendingRezzXP / 100) * spells[SpellID].base[0])))), + GetAAXP(),true); + } + else if (spells[SpellID].base[0] == 100 && PendingRezzXP > 0) { + SetEXP((GetEXP() + PendingRezzXP), GetAAXP(), true); + } + + //Was sending the packet back to initiate client zone... + //but that could be abusable, so lets go through proper channels + MovePC(ZoneID, InstanceID, x, y, z, GetHeading(), 0, ZoneSolicited); + entity_list.RefreshClientXTargets(this); + } + PendingRezzXP = -1; + PendingRezzSpellID = 0; +} + +void Client::OPTGB(const EQApplicationPacket *app) +{ + if(!app) return; + if(!app->pBuffer) return; + + uint32 tgb_flag = *(uint32 *)app->pBuffer; + if(tgb_flag == 2) + Message_StringID(0, TGB() ? TGB_ON : TGB_OFF); + else + tgb = tgb_flag; +} + +void Client::OPMemorizeSpell(const EQApplicationPacket* app) +{ + if(app->size != sizeof(MemorizeSpell_Struct)) + { + LogFile->write(EQEMuLog::Error,"Wrong size on OP_MemorizeSpell. Got: %i, Expected: %i", app->size, sizeof(MemorizeSpell_Struct)); + DumpPacket(app); + return; + } + + const MemorizeSpell_Struct* memspell = (const MemorizeSpell_Struct*) app->pBuffer; + + if(!IsValidSpell(memspell->spell_id)) + { + Message(13, "Unexpected error: spell id out of range"); + return; + } + + if + ( + GetClass() > 16 || + GetLevel() < spells[memspell->spell_id].classes[GetClass()-1] + ) + { + char val1[20]={0}; + Message_StringID(13,SPELL_LEVEL_TO_LOW,ConvertArray(spells[memspell->spell_id].classes[GetClass()-1],val1),spells[memspell->spell_id].name); + //Message(13, "Unexpected error: Class cant use this spell at your level!"); + return; + } + + switch(memspell->scribing) + { + case memSpellScribing: { // scribing spell to book + const ItemInst* inst = m_inv[MainCursor]; + + if(inst && inst->IsType(ItemClassCommon)) + { + const Item_Struct* item = inst->GetItem(); + + if(item && item->Scroll.Effect == (int32)(memspell->spell_id)) + { + ScribeSpell(memspell->spell_id, memspell->slot); + DeleteItemInInventory(MainCursor, 1, true); + } + else + Message(0,"Scribing spell: inst exists but item does not or spell ids do not match."); + } + else + Message(0,"Scribing a spell without an inst on your cursor?"); + break; + } + case memSpellMemorize: { // memming spell + if(HasSpellScribed(memspell->spell_id)) + { + MemSpell(memspell->spell_id, memspell->slot); + } + else + { + database.SetMQDetectionFlag(AccountName(), GetName(), "OP_MemorizeSpell but we don't have this spell scribed...", zone->GetShortName()); + } + break; + } + case memSpellForget: { // unmemming spell + UnmemSpell(memspell->slot); + break; + } + } + + Save(); +} + +void Client::BreakInvis() +{ + if (invisible) + { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct)); + SpawnAppearance_Struct* sa_out = (SpawnAppearance_Struct*)outapp->pBuffer; + sa_out->spawn_id = GetID(); + sa_out->type = 0x03; + sa_out->parameter = 0; + entity_list.QueueClients(this, outapp, true); + safe_delete(outapp); + invisible = false; + invisible_undead = false; + invisible_animals = false; + hidden = false; + improved_hidden = false; + } +} + +static uint64 CoinTypeCoppers(uint32 type) { + switch(type) { + case COINTYPE_PP: + return(1000); + case COINTYPE_GP: + return(100); + case COINTYPE_SP: + return(10); + case COINTYPE_CP: + default: + break; + } + return(1); +} + +void Client::OPMoveCoin(const EQApplicationPacket* app) +{ + MoveCoin_Struct* mc = (MoveCoin_Struct*)app->pBuffer; + uint64 value = 0, amount_to_take = 0, amount_to_add = 0; + int32 *from_bucket = 0, *to_bucket = 0; + Mob* trader = trade->With(); + + // could just do a range, but this is clearer and explicit + if + ( + ( + mc->cointype1 != COINTYPE_PP && + mc->cointype1 != COINTYPE_GP && + mc->cointype1 != COINTYPE_SP && + mc->cointype1 != COINTYPE_CP + ) || + ( + mc->cointype2 != COINTYPE_PP && + mc->cointype2 != COINTYPE_GP && + mc->cointype2 != COINTYPE_SP && + mc->cointype2 != COINTYPE_CP + ) + ) + { + return; + } + + switch(mc->from_slot) + { + case -1: // destroy + { + // I don't think you can move coin from the void, + // but need to check this + break; + } + case 0: // cursor + { + switch(mc->cointype1) + { + case COINTYPE_PP: + from_bucket = (int32 *) &m_pp.platinum_cursor; break; + case COINTYPE_GP: + from_bucket = (int32 *) &m_pp.gold_cursor; break; + case COINTYPE_SP: + from_bucket = (int32 *) &m_pp.silver_cursor; break; + case COINTYPE_CP: + from_bucket = (int32 *) &m_pp.copper_cursor; break; + } + break; + } + case 1: // inventory + { + switch(mc->cointype1) + { + case COINTYPE_PP: + from_bucket = (int32 *) &m_pp.platinum; break; + case COINTYPE_GP: + from_bucket = (int32 *) &m_pp.gold; break; + case COINTYPE_SP: + from_bucket = (int32 *) &m_pp.silver; break; + case COINTYPE_CP: + from_bucket = (int32 *) &m_pp.copper; break; + } + break; + } + case 2: // bank + { + uint32 distance = 0; + NPC *banker = entity_list.GetClosestBanker(this, distance); + if(!banker || distance > USE_NPC_RANGE2) + { + char *hacked_string = nullptr; + MakeAnyLenString(&hacked_string, "Player tried to make use of a banker(coin move) but %s is non-existant or too far away (%u units).", + banker ? banker->GetName() : "UNKNOWN NPC", distance); + database.SetMQDetectionFlag(AccountName(), GetName(), hacked_string, zone->GetShortName()); + safe_delete_array(hacked_string); + return; + } + + switch(mc->cointype1) + { + case COINTYPE_PP: + from_bucket = (int32 *) &m_pp.platinum_bank; break; + case COINTYPE_GP: + from_bucket = (int32 *) &m_pp.gold_bank; break; + case COINTYPE_SP: + from_bucket = (int32 *) &m_pp.silver_bank; break; + case COINTYPE_CP: + from_bucket = (int32 *) &m_pp.copper_bank; break; + } + break; + } + case 3: // trade + { + // can't move coin from trade + break; + } + case 4: // shared bank + { + uint32 distance = 0; + NPC *banker = entity_list.GetClosestBanker(this, distance); + if(!banker || distance > USE_NPC_RANGE2) + { + char *hacked_string = nullptr; + MakeAnyLenString(&hacked_string, "Player tried to make use of a banker(shared coin move) but %s is non-existant or too far away (%u units).", + banker ? banker->GetName() : "UNKNOWN NPC", distance); + database.SetMQDetectionFlag(AccountName(), GetName(), hacked_string, zone->GetShortName()); + safe_delete_array(hacked_string); + return; + } + if(mc->cointype1 == COINTYPE_PP) // there's only platinum here + from_bucket = (int32 *) &m_pp.platinum_shared; + break; + } + } + + switch(mc->to_slot) + { + case -1: // destroy + { + // no action required + break; + } + case 0: // cursor + { + switch(mc->cointype2) + { + case COINTYPE_PP: + to_bucket = (int32 *) &m_pp.platinum_cursor; break; + case COINTYPE_GP: + to_bucket = (int32 *) &m_pp.gold_cursor; break; + case COINTYPE_SP: + to_bucket = (int32 *) &m_pp.silver_cursor; break; + case COINTYPE_CP: + to_bucket = (int32 *) &m_pp.copper_cursor; break; + } + break; + } + case 1: // inventory + { + switch(mc->cointype2) + { + case COINTYPE_PP: + to_bucket = (int32 *) &m_pp.platinum; break; + case COINTYPE_GP: + to_bucket = (int32 *) &m_pp.gold; break; + case COINTYPE_SP: + to_bucket = (int32 *) &m_pp.silver; break; + case COINTYPE_CP: + to_bucket = (int32 *) &m_pp.copper; break; + } + break; + } + case 2: // bank + { + uint32 distance = 0; + NPC *banker = entity_list.GetClosestBanker(this, distance); + if(!banker || distance > USE_NPC_RANGE2) + { + char *hacked_string = nullptr; + MakeAnyLenString(&hacked_string, "Player tried to make use of a banker(coin move) but %s is non-existant or too far away (%u units).", + banker ? banker->GetName() : "UNKNOWN NPC", distance); + database.SetMQDetectionFlag(AccountName(), GetName(), hacked_string, zone->GetShortName()); + safe_delete_array(hacked_string); + return; + } + switch(mc->cointype2) + { + case COINTYPE_PP: + to_bucket = (int32 *) &m_pp.platinum_bank; break; + case COINTYPE_GP: + to_bucket = (int32 *) &m_pp.gold_bank; break; + case COINTYPE_SP: + to_bucket = (int32 *) &m_pp.silver_bank; break; + case COINTYPE_CP: + to_bucket = (int32 *) &m_pp.copper_bank; break; + } + break; + } + case 3: // trade + { + if(trader) + { + switch(mc->cointype2) + { + case COINTYPE_PP: + to_bucket = (int32 *) &trade->pp; break; + case COINTYPE_GP: + to_bucket = (int32 *) &trade->gp; break; + case COINTYPE_SP: + to_bucket = (int32 *) &trade->sp; break; + case COINTYPE_CP: + to_bucket = (int32 *) &trade->cp; break; + } + } + break; + } + case 4: // shared bank + { + uint32 distance = 0; + NPC *banker = entity_list.GetClosestBanker(this, distance); + if(!banker || distance > USE_NPC_RANGE2) + { + char *hacked_string = nullptr; + MakeAnyLenString(&hacked_string, "Player tried to make use of a banker(shared coin move) but %s is non-existant or too far away (%u units).", + banker ? banker->GetName() : "UNKNOWN NPC", distance); + database.SetMQDetectionFlag(AccountName(), GetName(), hacked_string, zone->GetShortName()); + safe_delete_array(hacked_string); + return; + } + if(mc->cointype2 == COINTYPE_PP) // there's only platinum here + to_bucket = (int32 *) &m_pp.platinum_shared; + break; + } + } + + if(!from_bucket) + { + return; + } + + // don't allow them to go into negatives (from our point of view) + amount_to_take = *from_bucket < mc->amount ? *from_bucket : mc->amount; + + // if you move 11 gold into a bank platinum location, the packet + // will say 11, but the client will have 1 left on their cursor, so we have + // to figure out the conversion ourselves + + amount_to_add = amount_to_take * ((float)CoinTypeCoppers(mc->cointype1) / (float)CoinTypeCoppers(mc->cointype2)); + + // the amount we're adding could be different than what was requested, so + // we have to adjust the amount we take as well + amount_to_take = amount_to_add * ((float)CoinTypeCoppers(mc->cointype2) / (float)CoinTypeCoppers(mc->cointype1)); + + // now we should have a from_bucket, a to_bucket, an amount_to_take + // and an amount_to_add + + // now we actually take it from the from bucket. if there's an error + // with the destination slot, they lose their money + *from_bucket -= amount_to_take; + // why are intentionally inducing a crash here rather than letting the code attempt to stumble on? + // assert(*from_bucket >= 0); + + if(to_bucket) + { + if(*to_bucket + amount_to_add > *to_bucket) // overflow check + *to_bucket += amount_to_add; + + //shared bank plat + if (RuleB(Character, SharedBankPlat)) + { + if (to_bucket == &m_pp.platinum_shared || from_bucket == &m_pp.platinum_shared) + { + if (from_bucket == &m_pp.platinum_shared) + amount_to_add = 0 - amount_to_take; + + database.SetSharedPlatinum(AccountID(),amount_to_add); + } + } + else{ + if (to_bucket == &m_pp.platinum_shared || from_bucket == &m_pp.platinum_shared){ + this->Message(13, "::: WARNING! ::: SHARED BANK IS DISABLED AND YOUR PLATINUM WILL BE DESTROYED IF YOU PUT IT HERE"); + } + } + } + + // if this is a trade move, inform the person being traded with + if(mc->to_slot == 3 && trader && trader->IsClient()) + { + + // If one party accepted the trade then some coin was added, their state needs to be reset + trade->state = Trading; + Mob* with = trade->With(); + if (with) + with->trade->state = Trading; + + Client* recipient = trader->CastToClient(); + recipient->Message(15, "%s adds some coins to the trade.", GetName()); + recipient->Message(15, "The total trade is: %i PP, %i GP, %i SP, %i CP", + trade->pp, trade->gp, + trade->sp, trade->cp + ); + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_TradeCoins,sizeof(TradeCoin_Struct)); + TradeCoin_Struct* tcs = (TradeCoin_Struct*)outapp->pBuffer; + tcs->trader = trader->GetID(); + tcs->slot = mc->cointype2; + tcs->unknown5 = 0x4fD2; + tcs->unknown7 = 0; + tcs->amount = amount_to_add; + recipient->QueuePacket(outapp); + safe_delete(outapp); + } + + SaveCurrency(); +} + +void Client::OPGMTraining(const EQApplicationPacket *app) +{ + + EQApplicationPacket* outapp = app->Copy(); + GMTrainee_Struct* gmtrain = (GMTrainee_Struct*) outapp->pBuffer; + + Mob* pTrainer = entity_list.GetMob(gmtrain->npcid); + + if(!pTrainer || !pTrainer->IsNPC() || pTrainer->GetClass() < WARRIORGM || pTrainer->GetClass() > BERSERKERGM) + return; + + //you can only use your own trainer, client enforces this, but why trust it + int trains_class = pTrainer->GetClass() - (WARRIORGM - WARRIOR); + if(GetClass() != trains_class) + return; + + //you have to be somewhat close to a trainer to be properly using them + if(DistNoRoot(*pTrainer) > USE_NPC_RANGE2) + return; + + // if this for-loop acts up again (crashes linux), try enabling the before and after #pragmas +//#pragma GCC push_options +//#pragma GCC optimize ("O0") + for (int sk = Skill1HBlunt; sk <= HIGHEST_SKILL; ++sk) { + if(sk == SkillTinkering && GetRace() != GNOME) { + gmtrain->skills[sk] = 0; //Non gnomes can't tinker! + } else { + gmtrain->skills[sk] = GetMaxSkillAfterSpecializationRules((SkillUseTypes)sk, MaxSkill((SkillUseTypes)sk, GetClass(), RuleI(Character, MaxLevel))); + //this is the highest level that the trainer can train you to, this is enforced clientside so we can't just + //Set it to 1 with CanHaveSkill or you wont be able to train past 1. + } + } +//#pragma GCC pop_options + + uchar ending[]={0x34,0x87,0x8a,0x3F,0x01 + ,0xC9,0xC9,0xC9,0xC9,0xC9,0xC9,0xC9,0xC9,0xC9,0xC9,0xC9,0xC9,0xC9,0xC9,0xC9,0xC9 + ,0xC9,0xC9,0xC9,0xC9,0xC9,0xC9,0xC9,0xC9,0xC9,0xC9,0xC9,0xC9,0xC9,0xC9,0xC9,0xC9 + ,0x76,0x75,0x3f}; + memcpy(&outapp->pBuffer[outapp->size-40],ending,sizeof(ending)); + FastQueuePacket(&outapp); + + // welcome message + if (pTrainer && pTrainer->IsNPC()) + { + pTrainer->Say_StringID(zone->random.Int(1204, 1207), GetCleanName()); + } +} + +void Client::OPGMEndTraining(const EQApplicationPacket *app) +{ + EQApplicationPacket *outapp = new EQApplicationPacket(OP_GMEndTrainingResponse, 0); + GMTrainEnd_Struct *p = (GMTrainEnd_Struct *)app->pBuffer; + + FastQueuePacket(&outapp); + + Mob* pTrainer = entity_list.GetMob(p->npcid); + if(!pTrainer || !pTrainer->IsNPC() || pTrainer->GetClass() < WARRIORGM || pTrainer->GetClass() > BERSERKERGM) + return; + + //you can only use your own trainer, client enforces this, but why trust it + int trains_class = pTrainer->GetClass() - (WARRIORGM - WARRIOR); + if(GetClass() != trains_class) + return; + + //you have to be somewhat close to a trainer to be properly using them + if(DistNoRoot(*pTrainer) > USE_NPC_RANGE2) + return; + + // goodbye message + if (pTrainer->IsNPC()) + { + pTrainer->Say_StringID(zone->random.Int(1208, 1211), GetCleanName()); + } +} + +void Client::OPGMTrainSkill(const EQApplicationPacket *app) +{ + if(!m_pp.points) + return; + + int Cost = 0; + + GMSkillChange_Struct* gmskill = (GMSkillChange_Struct*) app->pBuffer; + + Mob* pTrainer = entity_list.GetMob(gmskill->npcid); + if(!pTrainer || !pTrainer->IsNPC() || pTrainer->GetClass() < WARRIORGM || pTrainer->GetClass() > BERSERKERGM) + return; + + //you can only use your own trainer, client enforces this, but why trust it + int trains_class = pTrainer->GetClass() - (WARRIORGM - WARRIOR); + if(GetClass() != trains_class) + return; + + //you have to be somewhat close to a trainer to be properly using them + if(DistNoRoot(*pTrainer) > USE_NPC_RANGE2) + return; + + if (gmskill->skillbank == 0x01) + { + // languages go here + if (gmskill->skill_id > 25) + { + std::cout << "Wrong Training Skill (languages)" << std::endl; + DumpPacket(app); + return; + } + int AdjustedSkillLevel = GetLanguageSkill(gmskill->skill_id) - 10; + if(AdjustedSkillLevel > 0) + Cost = AdjustedSkillLevel * AdjustedSkillLevel * AdjustedSkillLevel / 100; + + IncreaseLanguageSkill(gmskill->skill_id); + } + else if (gmskill->skillbank == 0x00) + { + // normal skills go here + if (gmskill->skill_id > HIGHEST_SKILL) + { + std::cout << "Wrong Training Skill (abilities)" << std::endl; + DumpPacket(app); + return; + } + + SkillUseTypes skill = (SkillUseTypes) gmskill->skill_id; + + if(!CanHaveSkill(skill)) { + mlog(CLIENT__ERROR, "Tried to train skill %d, which is not allowed.", skill); + return; + } + + if(MaxSkill(skill) == 0) { + mlog(CLIENT__ERROR, "Tried to train skill %d, but training is not allowed at this level.", skill); + return; + } + + uint16 skilllevel = GetRawSkill(skill); + + if(skilllevel == 0) { + //this is a new skill.. + uint16 t_level = SkillTrainLevel(skill, GetClass()); + if (t_level == 0) + { + return; + } + + SetSkill(skill, t_level); + } else { + switch(skill) { + case SkillBrewing: + case SkillMakePoison: + case SkillTinkering: + case SkillResearch: + case SkillAlchemy: + case SkillBaking: + case SkillTailoring: + case SkillBlacksmithing: + case SkillFletching: + case SkillJewelryMaking: + case SkillPottery: + if(skilllevel >= RuleI(Skills, MaxTrainTradeskills)) { + Message_StringID(13, MORE_SKILLED_THAN_I, pTrainer->GetCleanName()); + return; + } + break; + case SkillSpecializeAbjure: + case SkillSpecializeAlteration: + case SkillSpecializeConjuration: + case SkillSpecializeDivination: + case SkillSpecializeEvocation: + if(skilllevel >= RuleI(Skills, MaxTrainSpecializations)) { + Message_StringID(13, MORE_SKILLED_THAN_I, pTrainer->GetCleanName()); + return; + } + default: + break; + } + + int MaxSkillValue = MaxSkill(skill); + if (skilllevel >= MaxSkillValue) + { + // Don't allow training over max skill level + Message_StringID(13, MORE_SKILLED_THAN_I, pTrainer->GetCleanName()); + return; + } + + if(gmskill->skill_id >= SkillSpecializeAbjure && gmskill->skill_id <= SkillSpecializeEvocation) + { + int MaxSpecSkill = GetMaxSkillAfterSpecializationRules(skill, MaxSkillValue); + if (skilllevel >= MaxSpecSkill) + { + // Restrict specialization training to follow the rules + Message_StringID(13, MORE_SKILLED_THAN_I, pTrainer->GetCleanName()); + return; + } + } + + // Client train a valid skill + // + int AdjustedSkillLevel = skilllevel - 10; + + if(AdjustedSkillLevel > 0) + Cost = AdjustedSkillLevel * AdjustedSkillLevel * AdjustedSkillLevel / 100; + + SetSkill(skill, skilllevel + 1); + + + } + } + + if(GetClientVersion() >= EQClientSoF) { + // The following packet decreases the skill points left in the Training Window and + // produces the 'You have increased your skill / learned the basics of' message. + // + EQApplicationPacket* outapp = new EQApplicationPacket(OP_GMTrainSkillConfirm, sizeof(GMTrainSkillConfirm_Struct)); + + GMTrainSkillConfirm_Struct *gmtsc = (GMTrainSkillConfirm_Struct *)outapp->pBuffer; + gmtsc->SkillID = gmskill->skill_id; + + if(gmskill->skillbank == 1) { + gmtsc->NewSkill = (GetLanguageSkill(gmtsc->SkillID) == 1); + gmtsc->SkillID += 100; + } + else + gmtsc->NewSkill = (GetRawSkill((SkillUseTypes)gmtsc->SkillID) == 1); + + gmtsc->Cost = Cost; + + strcpy(gmtsc->TrainerName, pTrainer->GetCleanName()); + QueuePacket(outapp); + safe_delete(outapp); + } + + if(Cost) + TakeMoneyFromPP(Cost); + + m_pp.points--; +} + +// this is used for /summon and /corpse +void Client::OPGMSummon(const EQApplicationPacket *app) +{ + GMSummon_Struct* gms = (GMSummon_Struct*) app->pBuffer; + Mob* st = entity_list.GetMob(gms->charname); + + if(st && st->IsCorpse()) + { + st->CastToCorpse()->Summon(this, false, true); + } + else + { + if(admin < 80) + { + return; + } + if(st) + { + Message(0, "Local: Summoning %s to %f, %f, %f", gms->charname, gms->x, gms->y, gms->z); + if (st->IsClient() && (st->CastToClient()->GetAnon() != 1 || this->Admin() >= st->CastToClient()->Admin())) + st->CastToClient()->MovePC(zone->GetZoneID(), zone->GetInstanceID(), (float)gms->x, (float)gms->y, (float)gms->z, this->GetHeading(), true); + else + st->GMMove(this->GetX(), this->GetY(), this->GetZ(),this->GetHeading()); + } + else + { + uint8 tmp = gms->charname[strlen(gms->charname)-1]; + if (!worldserver.Connected()) + { + Message(0, "Error: World server disconnected"); + } + else if (tmp < '0' || tmp > '9') // dont send to world if it's not a player's name + { + ServerPacket* pack = new ServerPacket(ServerOP_ZonePlayer, sizeof(ServerZonePlayer_Struct)); + ServerZonePlayer_Struct* szp = (ServerZonePlayer_Struct*) pack->pBuffer; + strcpy(szp->adminname, this->GetName()); + szp->adminrank = this->Admin(); + strcpy(szp->name, gms->charname); + strcpy(szp->zone, zone->GetShortName()); + szp->x_pos = (float)gms->x; + szp->y_pos = (float)gms->y; + szp->z_pos = (float)gms->z; + szp->ignorerestrictions = 2; + worldserver.SendPacket(pack); + safe_delete(pack); + } + else { + //all options have been exhausted + //summon our target... + if(GetTarget() && GetTarget()->IsCorpse()){ + GetTarget()->CastToCorpse()->Summon(this, false, true); + } + } + } + } +} + +void Client::DoHPRegen() { + SetHP(GetHP() + CalcHPRegen() + RestRegenHP); + SendHPUpdate(); +} + +void Client::DoManaRegen() { + if (GetMana() >= max_mana) + return; + + SetMana(GetMana() + CalcManaRegen() + RestRegenMana); + SendManaUpdatePacket(); +} + + +void Client::DoStaminaUpdate() { + if(!stamina_timer.Check()) + return; + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_Stamina, sizeof(Stamina_Struct)); + Stamina_Struct* sta = (Stamina_Struct*)outapp->pBuffer; + + if(zone->GetZoneID() != 151) { + int loss = RuleI(Character, FoodLossPerUpdate); + if (m_pp.hunger_level > 0) + m_pp.hunger_level-=loss; + if (m_pp.thirst_level > 0) + m_pp.thirst_level-=loss; + sta->food = m_pp.hunger_level > 6000 ? 6000 : m_pp.hunger_level; + sta->water = m_pp.thirst_level> 6000 ? 6000 : m_pp.thirst_level; + } + else { + // No auto food/drink consumption in the Bazaar + sta->food = 6000; + sta->water = 6000; + } + FastQueuePacket(&outapp); +} + +void Client::DoEnduranceRegen() +{ + if(GetEndurance() >= GetMaxEndurance()) + return; + + SetEndurance(GetEndurance() + CalcEnduranceRegen() + RestRegenEndurance); +} + +void Client::DoEnduranceUpkeep() { + + if (!HasEndurUpkeep()) + return; + + int upkeep_sum = 0; + int cost_redux = spellbonuses.EnduranceReduction + itembonuses.EnduranceReduction + aabonuses.EnduranceReduction; + + bool has_effect = false; + uint32 buffs_i; + uint32 buff_count = GetMaxTotalSlots(); + for (buffs_i = 0; buffs_i < buff_count; buffs_i++) { + if (buffs[buffs_i].spellid != SPELL_UNKNOWN) { + int upkeep = spells[buffs[buffs_i].spellid].EndurUpkeep; + if(upkeep > 0) { + has_effect = true; + if(cost_redux > 0) { + if(upkeep <= cost_redux) + continue; //reduced to 0 + upkeep -= cost_redux; + } + if((upkeep+upkeep_sum) > GetEndurance()) { + //they do not have enough to keep this one going. + BuffFadeBySlot(buffs_i); + } else { + upkeep_sum += upkeep; + } + } + } + } + + if(upkeep_sum != 0){ + SetEndurance(GetEndurance() - upkeep_sum); + TryTriggerOnValueAmount(false, false, true); + } + + if (!has_effect) + SetEndurUpkeep(false); +} + +void Client::CalcRestState() { + + // This method calculates rest state HP and mana regeneration. + // The client must have been out of combat for RuleI(Character, RestRegenTimeToActivate) seconds, + // must be sitting down, and must not have any detrimental spells affecting them. + // + if(!RuleI(Character, RestRegenPercent)) + return; + + RestRegenHP = RestRegenMana = RestRegenEndurance = 0; + + if(AggroCount || !IsSitting()) + return; + + if(!rest_timer.Check(false)) + return; + + uint32 buff_count = GetMaxTotalSlots(); + for (unsigned int j = 0; j < buff_count; j++) { + if(buffs[j].spellid != SPELL_UNKNOWN) { + if(IsDetrimentalSpell(buffs[j].spellid) && (buffs[j].ticsremaining > 0)) + if(!DetrimentalSpellAllowsRest(buffs[j].spellid)) + return; + } + } + + RestRegenHP = (GetMaxHP() * RuleI(Character, RestRegenPercent) / 100); + + RestRegenMana = (GetMaxMana() * RuleI(Character, RestRegenPercent) / 100); + + if(RuleB(Character, RestRegenEndurance)) + RestRegenEndurance = (GetMaxEndurance() * RuleI(Character, RestRegenPercent) / 100); +} + +void Client::DoTracking() +{ + if(TrackingID == 0) + return; + + Mob *m = entity_list.GetMob(TrackingID); + + if(!m || m->IsCorpse()) + { + Message_StringID(MT_Skills, TRACK_LOST_TARGET); + + TrackingID = 0; + + return; + } + + float RelativeHeading = GetHeading() - CalculateHeadingToTarget(m->GetX(), m->GetY()); + + if(RelativeHeading < 0) + RelativeHeading += 256; + + if((RelativeHeading <= 16) || (RelativeHeading >= 240)) + { + Message_StringID(MT_Skills, TRACK_STRAIGHT_AHEAD, m->GetCleanName()); + } + else if((RelativeHeading > 16) && (RelativeHeading <= 48)) + { + Message_StringID(MT_Skills, TRACK_AHEAD_AND_TO, m->GetCleanName(), "right"); + } + else if((RelativeHeading > 48) && (RelativeHeading <= 80)) + { + Message_StringID(MT_Skills, TRACK_TO_THE, m->GetCleanName(), "right"); + } + else if((RelativeHeading > 80) && (RelativeHeading <= 112)) + { + Message_StringID(MT_Skills, TRACK_BEHIND_AND_TO, m->GetCleanName(), "right"); + } + else if((RelativeHeading > 112) && (RelativeHeading <= 144)) + { + Message_StringID(MT_Skills, TRACK_BEHIND_YOU, m->GetCleanName()); + } + else if((RelativeHeading > 144) && (RelativeHeading <= 176)) + { + Message_StringID(MT_Skills, TRACK_BEHIND_AND_TO, m->GetCleanName(), "left"); + } + else if((RelativeHeading > 176) && (RelativeHeading <= 208)) + { + Message_StringID(MT_Skills, TRACK_TO_THE, m->GetCleanName(), "left"); + } + else if((RelativeHeading > 208) && (RelativeHeading < 240)) + { + Message_StringID(MT_Skills, TRACK_AHEAD_AND_TO, m->GetCleanName(), "left"); + } +} + +void Client::HandleRespawnFromHover(uint32 Option) +{ + RespawnFromHoverTimer.Disable(); + + RespawnOption* chosen = nullptr; + bool is_rez = false; + + //Find the selected option + if (Option == 0) + { + chosen = &respawn_options.front(); + } + else if (Option == (respawn_options.size() - 1)) + { + chosen = &respawn_options.back(); + is_rez = true; //Rez must always be the last option + } + else + { + std::list::iterator itr; + uint32 pos = 0; + for (itr = respawn_options.begin(); itr != respawn_options.end(); ++itr) + { + if (pos++ == Option) + { + chosen = &(*itr); + break; + } + } + } + + //If they somehow chose an option they don't have, just send them to bind + RespawnOption* default_to_bind = nullptr; + if (!chosen) + { + /* put error logging here */ + BindStruct* b = &m_pp.binds[0]; + default_to_bind = new RespawnOption; + default_to_bind->name = "Bind Location"; + default_to_bind->zone_id = b->zoneId; + default_to_bind->instance_id = b->instance_id; + default_to_bind->x = b->x; + default_to_bind->y = b->y; + default_to_bind->z = b->z; + default_to_bind->heading = b->heading; + chosen = default_to_bind; + is_rez = false; + } + + if (chosen->zone_id == zone->GetZoneID() && chosen->instance_id == zone->GetInstanceID()) //If they should respawn in the current zone... + { + if (is_rez) + { + if (PendingRezzXP < 0 || PendingRezzSpellID == 0) + { + _log(SPELLS__REZ, "Unexpected Rezz from hover request."); + return; + } + SetHP(GetMaxHP() / 5); + + Corpse* corpse = entity_list.GetCorpseByName(PendingRezzCorpseName.c_str()); + + if (corpse) + { + m_Position.m_X = corpse->GetX(); + m_Position.m_Y = corpse->GetY(); + m_Position.m_Z = corpse->GetZ(); + } + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_ZonePlayerToBind, sizeof(ZonePlayerToBind_Struct) + 10); + ZonePlayerToBind_Struct* gmg = (ZonePlayerToBind_Struct*) outapp->pBuffer; + + gmg->bind_zone_id = zone->GetZoneID(); + gmg->bind_instance_id = zone->GetInstanceID(); + gmg->x = GetX(); + gmg->y = GetY(); + gmg->z = GetZ(); + gmg->heading = GetHeading(); + strcpy(gmg->zone_name, "Resurrect"); + + FastQueuePacket(&outapp); + + ClearHover(); + SendHPUpdate(); + OPRezzAnswer(1, PendingRezzSpellID, zone->GetZoneID(), zone->GetInstanceID(), GetX(), GetY(), GetZ()); + + if (corpse && corpse->IsCorpse()) + { + _log(SPELLS__REZ, "Hover Rez in zone %s for corpse %s", + zone->GetShortName(), PendingRezzCorpseName.c_str()); + + _log(SPELLS__REZ, "Found corpse. Marking corpse as rezzed."); + + corpse->IsRezzed(true); + corpse->CompleteResurrection(); + } + } + else //Not rez + { + PendingRezzSpellID = 0; + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_ZonePlayerToBind, sizeof(ZonePlayerToBind_Struct) + chosen->name.length() + 1); + ZonePlayerToBind_Struct* gmg = (ZonePlayerToBind_Struct*) outapp->pBuffer; + + gmg->bind_zone_id = zone->GetZoneID(); + gmg->bind_instance_id = chosen->instance_id; + gmg->x = chosen->x; + gmg->y = chosen->y; + gmg->z = chosen->z; + gmg->heading = chosen->heading; + strcpy(gmg->zone_name, chosen->name.c_str()); + + FastQueuePacket(&outapp); + + CalcBonuses(); + SetHP(GetMaxHP()); + SetMana(GetMaxMana()); + SetEndurance(GetMaxEndurance()); + + m_Position.m_X = chosen->x; + m_Position.m_Y = chosen->y; + m_Position.m_Z = chosen->z; + m_Position.m_Heading = chosen->heading; + + ClearHover(); + entity_list.RefreshClientXTargets(this); + SendHPUpdate(); + } + + //After they've respawned into the same zone, trigger EVENT_RESPAWN + parse->EventPlayer(EVENT_RESPAWN, this, static_cast(itoa(Option)), is_rez ? 1 : 0); + + //Pop Rez option from the respawn options list; + //easiest way to make sure it stays at the end and + //doesn't disrupt adding/removing scripted options + respawn_options.pop_back(); + } + else + { + //Heading to a different zone + if(isgrouped) + { + Group *g = GetGroup(); + if(g) + g->MemberZoned(this); + } + + Raid* r = entity_list.GetRaidByClient(this); + if(r) + r->MemberZoned(this); + + m_pp.zone_id = chosen->zone_id; + m_pp.zoneInstance = chosen->instance_id; + database.MoveCharacterToZone(CharacterID(), database.GetZoneName(chosen->zone_id)); + + Save(); + + MovePC(chosen->zone_id, chosen->instance_id, chosen->x, chosen->y, chosen->z, chosen->heading, 1); + } + + safe_delete(default_to_bind); +} + +void Client::ClearHover() +{ + // Our Entity ID is currently zero, set in Client::Death + SetID(entity_list.GetFreeID()); + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_ZoneEntry, sizeof(ServerZoneEntry_Struct)); + ServerZoneEntry_Struct* sze = (ServerZoneEntry_Struct*)outapp->pBuffer; + + FillSpawnStruct(&sze->player,CastToMob()); + + sze->player.spawn.NPC = 0; + sze->player.spawn.z += 6; //arbitrary lift, seems to help spawning under zone. + + entity_list.QueueClients(this, outapp, false); + safe_delete(outapp); + + if(IsClient() && CastToClient()->GetClientVersionBit() & BIT_UnderfootAndLater) + { + EQApplicationPacket *outapp = MakeBuffsPacket(false); + CastToClient()->FastQueuePacket(&outapp); + } + + dead = false; +} + +void Client::HandleLFGuildResponse(ServerPacket *pack) +{ + pack->SetReadPosition(8); + + char Tmp[257]; + + pack->ReadString(Tmp); + + pack->ReadSkipBytes(4); + uint32 SubType, NumberOfMatches; + + SubType = pack->ReadUInt32(); + + switch(SubType) + { + case QSG_LFGuild_PlayerMatches: + { + NumberOfMatches = pack->ReadUInt32(); + uint32 StartOfMatches = pack->GetReadPosition(); + uint32 i = NumberOfMatches; + uint32 PacketSize = 12; + while(i > 0) + { + pack->ReadString(Tmp); + PacketSize += strlen(Tmp) + 1; + pack->ReadString(Tmp); + PacketSize += strlen(Tmp) + 1; + PacketSize += 16; + pack->ReadSkipBytes(16); + --i; + } + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_LFGuild, PacketSize); + outapp->WriteUInt32(3); + outapp->WriteUInt32(0xeb63); // Don't know the significance of this value. + outapp->WriteUInt32(NumberOfMatches); + pack->SetReadPosition(StartOfMatches); + + while(NumberOfMatches > 0) + { + pack->ReadString(Tmp); + outapp->WriteString(Tmp); + pack->ReadString(Tmp); + uint32 Level = pack->ReadUInt32(); + uint32 Class = pack->ReadUInt32(); + uint32 AACount = pack->ReadUInt32(); + uint32 TimeZone = pack->ReadUInt32(); + outapp->WriteUInt32(Level); + outapp->WriteUInt32(Class); + outapp->WriteUInt32(AACount); + outapp->WriteUInt32(TimeZone); + outapp->WriteString(Tmp); + --NumberOfMatches; + } + + FastQueuePacket(&outapp); + break; + } + case QSG_LFGuild_RequestPlayerInfo: + { + EQApplicationPacket *outapp = new EQApplicationPacket(OP_LFGuild, sizeof(LFGuild_PlayerToggle_Struct)); + LFGuild_PlayerToggle_Struct *pts = (LFGuild_PlayerToggle_Struct *)outapp->pBuffer; + + pts->Command = 0; + pack->ReadString(pts->Comment); + pts->TimeZone = pack->ReadUInt32(); + pts->TimePosted = pack->ReadUInt32(); + pts->Toggle = pack->ReadUInt32(); + + FastQueuePacket(&outapp); + + break; + } + case QSG_LFGuild_GuildMatches: + { + NumberOfMatches = pack->ReadUInt32(); + uint32 StartOfMatches = pack->GetReadPosition(); + uint32 i = NumberOfMatches; + uint32 PacketSize = 12; + while(i > 0) + { + pack->ReadString(Tmp); + PacketSize += strlen(Tmp) + 1; + pack->ReadSkipBytes(4); + pack->ReadString(Tmp); + PacketSize += strlen(Tmp) + 1; + PacketSize += 4; + --i; + } + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_LFGuild, PacketSize); + outapp->WriteUInt32(4); + outapp->WriteUInt32(0xeb63); + outapp->WriteUInt32(NumberOfMatches); + pack->SetReadPosition(StartOfMatches); + + while(NumberOfMatches > 0) + { + pack->ReadString(Tmp); + uint32 TimeZone = pack->ReadUInt32(); + outapp->WriteString(Tmp); + outapp->WriteUInt32(TimeZone); + pack->ReadString(Tmp); + outapp->WriteString(Tmp); + --NumberOfMatches; + } + FastQueuePacket(&outapp); + + break; + } + case QSG_LFGuild_RequestGuildInfo: + { + + char Comments[257]; + uint32 FromLevel, ToLevel, Classes, AACount, TimeZone, TimePosted; + + pack->ReadString(Comments); + FromLevel = pack->ReadUInt32(); + ToLevel = pack->ReadUInt32(); + Classes = pack->ReadUInt32(); + AACount = pack->ReadUInt32(); + TimeZone = pack->ReadUInt32(); + TimePosted = pack->ReadUInt32(); + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_LFGuild, sizeof(LFGuild_GuildToggle_Struct)); + + LFGuild_GuildToggle_Struct *gts = (LFGuild_GuildToggle_Struct *)outapp->pBuffer; + gts->Command = 1; + strcpy(gts->Comment, Comments); + gts->FromLevel = FromLevel; + gts->ToLevel = ToLevel; + gts->Classes = Classes; + gts->AACount = AACount; + gts->TimeZone = TimeZone; + gts->Toggle = 1; + gts->TimePosted = TimePosted; + gts->Name[0] = 0; + + FastQueuePacket(&outapp); + + break; + } + + default: + break; + } + +} + +void Client::SendLFGuildStatus() +{ + ServerPacket* pack = new ServerPacket(ServerOP_QueryServGeneric, strlen(GetName()) + 17); + + pack->WriteUInt32(zone->GetZoneID()); + pack->WriteUInt32(zone->GetInstanceID()); + pack->WriteString(GetName()); + pack->WriteUInt32(QSG_LFGuild); + pack->WriteUInt32(QSG_LFGuild_RequestPlayerInfo); + + worldserver.SendPacket(pack); + safe_delete(pack); + +} + +void Client::SendGuildLFGuildStatus() +{ + ServerPacket* pack = new ServerPacket(ServerOP_QueryServGeneric, strlen(GetName()) + +strlen(guild_mgr.GetGuildName(GuildID())) + 18); + + pack->WriteUInt32(zone->GetZoneID()); + pack->WriteUInt32(zone->GetInstanceID()); + pack->WriteString(GetName()); + pack->WriteUInt32(QSG_LFGuild); + pack->WriteUInt32(QSG_LFGuild_RequestGuildInfo); + pack->WriteString(guild_mgr.GetGuildName(GuildID())); + + worldserver.SendPacket(pack); + safe_delete(pack); +} diff --git a/zone/command.cpp b/zone/command.cpp index 31cc9aa19..1561bb5ed 100644 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -42,38 +42,34 @@ #endif #include "../common/debug.h" -#include "../common/ptimer.h" -#include "../common/packet_functions.h" -#include "../common/packet_dump.h" -#include "../common/serverinfo.h" -#include "../common/opcodemgr.h" #include "../common/eq_packet.h" -#include "../common/guilds.h" -#include "../common/rulesys.h" -#include "../common/string_util.h" -//#include "../common/servertalk.h" // for oocmute and revoke -#include "worldserver.h" -#include "masterentity.h" -#include "map.h" -#include "water_map.h" #include "../common/features.h" -#include "pathing.h" -#include "client_logs.h" -#include "guild_mgr.h" -#include "titles.h" +#include "../common/guilds.h" #include "../common/patches/patches.h" +#include "../common/ptimer.h" +#include "../common/rulesys.h" +#include "../common/serverinfo.h" +#include "../common/string_util.h" + +#include "client_logs.h" +#include "command.h" +#include "guild_mgr.h" +#include "map.h" +#include "pathing.h" +#include "qglobals.h" #include "queryserv.h" +#include "quest_parser_collection.h" +#include "string_ids.h" +#include "titles.h" +#include "water_map.h" +#include "worldserver.h" extern QueryServ* QServ; extern WorldServer worldserver; extern TaskManager *taskmanager; void CatchSignal(int sig_num); -#include "quest_parser_collection.h" -#include "string_ids.h" -#include "command.h" -#include "qglobals.h" //struct cl_struct *commandlist; // the actual linked list of commands int commandcount; // how many commands we have @@ -3416,7 +3412,7 @@ void command_corpse(Client *c, const Seperator *sep) c->Message(0, "Error: Target must be a player corpse."); else if (c->Admin() >= commandEditPlayerCorpses && target->IsPlayerCorpse()) { c->Message(0, "Depoping %s.", target->GetName()); - target->CastToCorpse()->DepopCorpse(); + target->CastToCorpse()->DepopPlayerCorpse(); if(!sep->arg[2][0] || atoi(sep->arg[2]) != 0) target->CastToCorpse()->Bury(); } @@ -3901,7 +3897,7 @@ void command_save(Client *c, const Seperator *sep) } else if (c->GetTarget()->IsPlayerCorpse()) { if (c->GetTarget()->CastToMob()->Save()) - c->Message(0, "%s successfully saved. (dbid=%u)", c->GetTarget()->GetName(), c->GetTarget()->CastToCorpse()->GetDBID()); + c->Message(0, "%s successfully saved. (dbid=%u)", c->GetTarget()->GetName(), c->GetTarget()->CastToCorpse()->GetCorpseDBID()); else c->Message(0, "Manual save for %s failed.", c->GetTarget()->GetName()); } @@ -4984,189 +4980,10 @@ void command_randomfeatures(Client *c, const Seperator *sep) c->Message(0,"Error: This command requires a target"); else { - uint16 Race = target->GetRace(); - if (Race <= 12 || Race == 128 || Race == 130 || Race == 330 || Race == 522) { - - uint8 Gender = target->GetGender(); - uint8 Texture = 0xFF; - uint8 HelmTexture = 0xFF; - uint8 HairColor = 0xFF; - uint8 BeardColor = 0xFF; - uint8 EyeColor1 = 0xFF; - uint8 EyeColor2 = 0xFF; - uint8 HairStyle = 0xFF; - uint8 LuclinFace = 0xFF; - uint8 Beard = 0xFF; - uint32 DrakkinHeritage = 0xFFFFFFFF; - uint32 DrakkinTattoo = 0xFFFFFFFF; - uint32 DrakkinDetails = 0xFFFFFFFF; - - // Set some common feature settings - EyeColor1 = MakeRandomInt(0, 9); - EyeColor2 = MakeRandomInt(0, 9); - LuclinFace = MakeRandomInt(0, 7); - - // Adjust all settings based on the min and max for each feature of each race and gender - switch (Race) - { - case 1: // Human - HairColor = MakeRandomInt(0, 19); - if (Gender == 0) { - BeardColor = HairColor; - HairStyle = MakeRandomInt(0, 3); - Beard = MakeRandomInt(0, 5); - } - if (Gender == 1) { - HairStyle = MakeRandomInt(0, 2); - } - break; - case 2: // Barbarian - HairColor = MakeRandomInt(0, 19); - LuclinFace = MakeRandomInt(0, 87); - if (Gender == 0) { - BeardColor = HairColor; - HairStyle = MakeRandomInt(0, 3); - Beard = MakeRandomInt(0, 5); - } - if (Gender == 1) { - HairStyle = MakeRandomInt(0, 2); - } - break; - case 3: // Erudite - if (Gender == 0) { - BeardColor = MakeRandomInt(0, 19); - Beard = MakeRandomInt(0, 5); - LuclinFace = MakeRandomInt(0, 57); - } - if (Gender == 1) { - LuclinFace = MakeRandomInt(0, 87); - } - break; - case 4: // WoodElf - HairColor = MakeRandomInt(0, 19); - if (Gender == 0) { - HairStyle = MakeRandomInt(0, 3); - } - if (Gender == 1) { - HairStyle = MakeRandomInt(0, 2); - } - break; - case 5: // HighElf - HairColor = MakeRandomInt(0, 14); - if (Gender == 0) { - HairStyle = MakeRandomInt(0, 3); - LuclinFace = MakeRandomInt(0, 37); - BeardColor = HairColor; - } - if (Gender == 1) { - HairStyle = MakeRandomInt(0, 2); - } - break; - case 6: // DarkElf - HairColor = MakeRandomInt(13, 18); - if (Gender == 0) { - HairStyle = MakeRandomInt(0, 3); - LuclinFace = MakeRandomInt(0, 37); - BeardColor = HairColor; - } - if (Gender == 1) { - HairStyle = MakeRandomInt(0, 2); - } - break; - case 7: // HalfElf - HairColor = MakeRandomInt(0, 19); - if (Gender == 0) { - HairStyle = MakeRandomInt(0, 3); - LuclinFace = MakeRandomInt(0, 37); - BeardColor = HairColor; - } - if (Gender == 1) { - HairStyle = MakeRandomInt(0, 2); - } - break; - case 8: // Dwarf - HairColor = MakeRandomInt(0, 19); - BeardColor = HairColor; - if (Gender == 0) { - HairStyle = MakeRandomInt(0, 3); - Beard = MakeRandomInt(0, 5); - } - if (Gender == 1) { - HairStyle = MakeRandomInt(0, 2); - LuclinFace = MakeRandomInt(0, 17); - } - break; - case 9: // Troll - EyeColor1 = MakeRandomInt(0, 10); - EyeColor2 = MakeRandomInt(0, 10); - if (Gender == 1) { - HairStyle = MakeRandomInt(0, 3); - HairColor = MakeRandomInt(0, 23); - } - break; - case 10: // Ogre - if (Gender == 1) { - HairStyle = MakeRandomInt(0, 3); - HairColor = MakeRandomInt(0, 23); - } - break; - case 11: // Halfling - HairColor = MakeRandomInt(0, 19); - if (Gender == 0) { - BeardColor = HairColor; - HairStyle = MakeRandomInt(0, 3); - Beard = MakeRandomInt(0, 5); - } - if (Gender == 1) { - HairStyle = MakeRandomInt(0, 2); - } - break; - case 12: // Gnome - HairColor = MakeRandomInt(0, 24); - if (Gender == 0) { - BeardColor = HairColor; - HairStyle = MakeRandomInt(0, 3); - Beard = MakeRandomInt(0, 5); - } - if (Gender == 1) { - HairStyle = MakeRandomInt(0, 2); - } - break; - case 128: // Iksar - case 130: // VahShir - break; - case 330: // Froglok - LuclinFace = MakeRandomInt(0, 9); - case 522: // Drakkin - HairColor = MakeRandomInt(0, 3); - BeardColor = HairColor; - EyeColor1 = MakeRandomInt(0, 11); - EyeColor2 = MakeRandomInt(0, 11); - LuclinFace = MakeRandomInt(0, 6); - DrakkinHeritage = MakeRandomInt(0, 6); - DrakkinTattoo = MakeRandomInt(0, 7); - DrakkinDetails = MakeRandomInt(0, 7); - if (Gender == 0) { - Beard = MakeRandomInt(0, 12); - HairStyle = MakeRandomInt(0, 8); - } - if (Gender == 1) { - Beard = MakeRandomInt(0, 3); - HairStyle = MakeRandomInt(0, 7); - } - break; - default: - break; - } - - target->SendIllusionPacket(Race, Gender, Texture, HelmTexture, HairColor, BeardColor, - EyeColor1, EyeColor2, HairStyle, LuclinFace, Beard, 0xFF, - DrakkinHeritage, DrakkinTattoo, DrakkinDetails); - - c->Message(0,"NPC Features Randomized"); - } + if (target->RandomizeFeatures()) + c->Message(0,"Features Randomized"); else - c->Message(0,"This command requires a Playable Race as the Target"); + c->Message(0,"This command requires a Playable Race as the target"); } } @@ -9999,7 +9816,8 @@ void command_object(Client *c, const Seperator *sep) // Couldn't copy the object. - if (results.ErrorMessage().c_str() != '\0') { + // got an error message + if (!results.Success()) { c->Message(0, "Database Error: %s", results.ErrorMessage().c_str()); return; } diff --git a/zone/command.h b/zone/command.h index db069b57c..72ee5c06a 100644 --- a/zone/command.h +++ b/zone/command.h @@ -20,9 +20,10 @@ #ifndef COMMAND_H #define COMMAND_H -#include "../common/seperator.h" -#include "../common/eq_stream.h" -#include "client.h" +class Client; +class Seperator; + +#include "../common/types.h" #define COMMAND_CHAR '#' #define CMDALIASES 5 diff --git a/zone/common.h b/zone/common.h index ea299ab11..477d5b876 100644 --- a/zone/common.h +++ b/zone/common.h @@ -459,6 +459,24 @@ struct Shielders_Struct { uint16 shielder_bonus; }; +typedef struct +{ + uint16 increment; + uint16 hit_increment; + uint16 target_id; + int32 wpn_dmg; + float origin_x; + float origin_y; + float origin_z; + float tlast_x; + float tlast_y; + uint32 ranged_id; + uint32 ammo_id; + int ammo_slot; + uint8 skill; + float speed_mod; +} tProjatk; + //eventually turn this into a typedef and //make DoAnim take it instead of int, to enforce its use. enum { //type arguments to DoAnim diff --git a/zone/corpse.cpp b/zone/corpse.cpp index 4abdc72e2..4cc6e639c 100644 --- a/zone/corpse.cpp +++ b/zone/corpse.cpp @@ -63,7 +63,7 @@ void Corpse::SendLootReqErrorPacket(Client* client, uint8 response) { safe_delete(outapp); } -Corpse* Corpse::LoadFromDBData(uint32 in_dbid, uint32 in_charid, std::string in_charname, const xyz_heading& position, std::string time_of_death, bool rezzed, bool was_at_graveyard) +Corpse* Corpse::LoadCharacterCorpseEntity(uint32 in_dbid, uint32 in_charid, std::string in_charname, const xyz_heading& position, std::string time_of_death, bool rezzed, bool was_at_graveyard) { uint32 item_count = database.GetCharacterCorpseItemCount(in_dbid); char *buffer = new char[sizeof(PlayerCorpse_Struct) + (item_count * sizeof(player_lootitem::ServerLootItem_Struct))]; @@ -76,7 +76,6 @@ Corpse* Corpse::LoadFromDBData(uint32 in_dbid, uint32 in_charid, std::string in_ for (unsigned int i = 0; i < pcs->itemcount; i++) { tmp = new ServerLootItem_Struct; memcpy(tmp, &pcs->items[i], sizeof(player_lootitem::ServerLootItem_Struct)); - tmp->equip_slot = CorpseToServerSlot(tmp->equip_slot); itemlist.push_back(tmp); } @@ -147,13 +146,15 @@ Corpse::Corpse(NPC* in_npc, ItemList* in_itemlist, uint32 in_npctypeid, const NP 0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0xff,0,0,0,0,0,0,0,0,0), corpse_decay_timer(in_decaytime), - corpse_res_timer(0), + corpse_rez_timer(0), corpse_delay_timer(RuleI(NPC, CorpseUnlockTimer)), corpse_graveyard_timer(0), loot_cooldown_timer(10) { corpse_graveyard_timer.Disable(); + memset(item_tint, 0, sizeof(item_tint)); + is_corpse_changed = false; is_player_corpse = false; is_locked = false; @@ -166,11 +167,11 @@ Corpse::Corpse(NPC* in_npc, ItemList* in_itemlist, uint32 in_npctypeid, const NP SetCash(in_npc->GetCopper(), in_npc->GetSilver(), in_npc->GetGold(), in_npc->GetPlatinum()); npctype_id = in_npctypeid; - SetPKItem(0); + SetPlayerKillItemID(0); char_id = 0; corpse_db_id = 0; player_corpse_depop = false; - strcpy(orgname, in_npc->GetName()); + strcpy(corpse_name, in_npc->GetName()); strcpy(name, in_npc->GetName()); // Added By Hogie for(int count = 0; count < 100; count++) { @@ -183,6 +184,7 @@ Corpse::Corpse(NPC* in_npc, ItemList* in_itemlist, uint32 in_npctypeid, const NP corpse_decay_timer.SetTimer(RuleI(NPC,EmptyNPCCorpseDecayTimeMS)+1000); } + if(in_npc->HasPrivateCorpse()) { corpse_delay_timer.SetTimer(corpse_decay_timer.GetRemainingTime() + 1000); } @@ -191,7 +193,7 @@ Corpse::Corpse(NPC* in_npc, ItemList* in_itemlist, uint32 in_npctypeid, const NP for (int i = 0; i < MAX_LOOTERS; i++){ allowed_looters[i] = 0; } - this->rezzexp = 0; + this->rez_experience = 0; } Corpse::Corpse(Client* client, int32 in_rezexp) : Mob ( @@ -244,7 +246,7 @@ Corpse::Corpse(Client* client, int32 in_rezexp) : Mob ( 0 // uint32 in_scalerate ), corpse_decay_timer(RuleI(Character, CorpseDecayTimeMS)), - corpse_res_timer(RuleI(Character, CorpseResTimeMS)), + corpse_rez_timer(RuleI(Character, CorpseResTimeMS)), corpse_delay_timer(RuleI(NPC, CorpseUnlockTimer)), corpse_graveyard_timer(RuleI(Zone, GraveyardTimeMS)), loot_cooldown_timer(10) @@ -266,8 +268,8 @@ Corpse::Corpse(Client* client, int32 in_rezexp) : Mob ( } is_corpse_changed = true; - rezzexp = in_rezexp; - can_rez = true; + rez_experience = in_rezexp; + can_corpse_be_rezzed = true; is_player_corpse = true; is_locked = false; being_looted_by = 0xFFFFFFFF; @@ -279,13 +281,13 @@ Corpse::Corpse(Client* client, int32 in_rezexp) : Mob ( gold = 0; platinum = 0; - strcpy(orgname, pp->name); + strcpy(corpse_name, pp->name); strcpy(name, pp->name); /* become_npc was not being initialized which led to some pretty funky things with newly created corpses */ become_npc = false; - SetPKItem(0); + SetPlayerKillItemID(0); /* Check Rule to see if we can leave corpses */ if(!RuleB(Character, LeaveNakedCorpses) || @@ -335,9 +337,9 @@ Corpse::Corpse(Client* client, int32 in_rezexp) : Mob ( // this was mainly for client profile state reflection..should match db player inventory entries now. iter_queue it; - for(it=client->GetInv().cursor_begin(),i=8001; it!=client->GetInv().cursor_end(); ++it,i++) { + for (it = client->GetInv().cursor_begin(), i = 8001; it != client->GetInv().cursor_end(); ++it, i++) { item = *it; - if((item && (!client->IsBecomeNPC())) || (item && client->IsBecomeNPC() && !item->GetItem()->NoRent)) { + if ((item && (!client->IsBecomeNPC())) || (item && client->IsBecomeNPC() && !item->GetItem()->NoRent)) { std::list slot_list = MoveItemToCorpse(client, item, i); removed_list.merge(slot_list); cursor = true; @@ -346,16 +348,17 @@ Corpse::Corpse(Client* client, int32 in_rezexp) : Mob ( } database.TransactionBegin(); - if(removed_list.size() != 0) { + if (removed_list.size() != 0) { std::stringstream ss(""); ss << "DELETE FROM inventory WHERE charid=" << client->CharacterID(); ss << " AND ("; std::list::const_iterator iter = removed_list.begin(); bool first = true; - while(iter != removed_list.end()) { - if(first) { + while (iter != removed_list.end()) { + if (first) { first = false; - } else { + } + else { ss << " OR "; } ss << "slotid=" << (*iter); @@ -365,8 +368,8 @@ Corpse::Corpse(Client* client, int32 in_rezexp) : Mob ( database.QueryDatabase(ss.str().c_str()); } - if(cursor) { // all cursor items should be on corpse (client < SoF or RespawnFromHover = false) - while(!client->GetInv().CursorEmpty()) + if (cursor) { // all cursor items should be on corpse (client < SoF or RespawnFromHover = false) + while (!client->GetInv().CursorEmpty()) client->DeleteItemInInventory(MainCursor, 0, false, false); } else { // only visible cursor made it to corpse (client >= Sof and RespawnFromHover = true) @@ -401,13 +404,13 @@ std::list Corpse::MoveItemToCorpse(Client *client, ItemInst *item, int16 returnlist.push_back(equipslot); // Qualified bag slot iterations. processing bag slots that don't exist is probably not a good idea. - if(item->IsType(ItemClassContainer) && ((equipslot >= EmuConstants::GENERAL_BEGIN && equipslot <= MainCursor))) { - for(bagindex = SUB_BEGIN; bagindex <= EmuConstants::ITEM_CONTAINER_SIZE; bagindex++) { + if (item->IsType(ItemClassContainer) && ((equipslot >= EmuConstants::GENERAL_BEGIN && equipslot <= MainCursor))) { + for (bagindex = SUB_BEGIN; bagindex <= EmuConstants::ITEM_CONTAINER_SIZE; bagindex++) { // For empty bags in cursor queue, slot was previously being resolved as SLOT_INVALID (-1) interior_slot = Inventory::CalcSlotId(equipslot, bagindex); interior_item = client->GetInv().GetItem(interior_slot); - if(interior_item) { + if (interior_item) { AddItem(interior_item->GetItem()->ID, interior_item->GetCharges(), interior_slot, interior_item->GetAugmentItemID(0), interior_item->GetAugmentItemID(1), interior_item->GetAugmentItemID(2), interior_item->GetAugmentItemID(3), interior_item->GetAugmentItemID(4)); returnlist.push_back(Inventory::CalcSlotId(equipslot, bagindex)); client->DeleteItemInInventory(interior_slot, 0, true, false); @@ -469,7 +472,7 @@ in_helmtexture, 0, 0), corpse_decay_timer(RuleI(Character, CorpseDecayTimeMS)), - corpse_res_timer(RuleI(Character, CorpseResTimeMS)), + corpse_rez_timer(RuleI(Character, CorpseResTimeMS)), corpse_delay_timer(RuleI(NPC, CorpseUnlockTimer)), corpse_graveyard_timer(RuleI(Zone, GraveyardTimeMS)), loot_cooldown_timer(10) @@ -477,7 +480,7 @@ in_helmtexture, LoadPlayerCorpseDecayTime(in_dbid); - if(!zone->HasGraveyard() || wasAtGraveyard) + if (!zone->HasGraveyard() || wasAtGraveyard) corpse_graveyard_timer.Disable(); memset(item_tint, 0, sizeof(item_tint)); @@ -492,23 +495,25 @@ in_helmtexture, itemlist = *in_itemlist; in_itemlist->clear(); - strcpy(orgname, in_charname); + strcpy(corpse_name, in_charname); strcpy(name, in_charname); + this->copper = in_copper; this->silver = in_silver; this->gold = in_gold; this->platinum = in_plat; - rezzexp = in_rezexp; - for (int i = 0; i < MAX_LOOTERS; i++) + rez_experience = in_rezexp; + + for (int i = 0; i < MAX_LOOTERS; i++){ allowed_looters[i] = 0; - - SetPKItem(0); + } + SetPlayerKillItemID(0); } Corpse::~Corpse() { if (is_player_corpse && !(player_corpse_depop && corpse_db_id == 0)) { - Save(); + Save(); } ItemList::iterator cur,end; cur = itemlist.begin(); @@ -562,7 +567,7 @@ bool Corpse::Save() { dbpc->level = level; dbpc->texture = this->texture; dbpc->helmtexture = this->helmtexture; - dbpc->exp = rezzexp; + dbpc->exp = rez_experience; memcpy(dbpc->item_tint, item_tint, sizeof(dbpc->item_tint)); dbpc->haircolor = haircolor; @@ -581,17 +586,16 @@ bool Corpse::Save() { end = itemlist.end(); for (; cur != end; ++cur) { ServerLootItem_Struct* item = *cur; - item->equip_slot = ServerToCorpseSlot(item->equip_slot); // temp hack until corpse blobs are removed memcpy((char*)&dbpc->items[x++], (char*)item, sizeof(ServerLootItem_Struct)); } /* Create New Corpse*/ if (corpse_db_id == 0) { - corpse_db_id = database.SaveCharacterCorpse(char_id, orgname, zone->GetZoneID(), zone->GetInstanceID(), dbpc, m_Position); + corpse_db_id = database.SaveCharacterCorpse(char_id, corpse_name, zone->GetZoneID(), zone->GetInstanceID(), dbpc, m_Position); } /* Update Corpse Data */ else{ - corpse_db_id = database.UpdateCharacterCorpse(corpse_db_id, char_id, orgname, zone->GetZoneID(), zone->GetInstanceID(), dbpc, m_Position, IsRezzed()); + corpse_db_id = database.UpdateCharacterCorpse(corpse_db_id, char_id, corpse_name, zone->GetZoneID(), zone->GetInstanceID(), dbpc, m_Position, IsRezzed()); } safe_delete_array(dbpc); @@ -615,13 +619,13 @@ void Corpse::Bury() { player_corpse_depop = true; } -void Corpse::Depop() { +void Corpse::DepopNPCCorpse() { if (IsNPCCorpse()) player_corpse_depop = true; } -void Corpse::DepopCorpse() { - player_corpse_depop = true; +void Corpse::DepopPlayerCorpse() { + player_corpse_depop = true; } uint32 Corpse::CountItems() { @@ -633,7 +637,9 @@ void Corpse::AddItem(uint32 itemnum, uint16 charges, int16 slot, uint32 aug1, ui return; is_corpse_changed = true; + ServerLootItem_Struct* item = new ServerLootItem_Struct; + memset(item, 0, sizeof(ServerLootItem_Struct)); item->item_id = itemnum; item->charges = charges; @@ -665,9 +671,9 @@ ServerLootItem_Struct* Corpse::GetItem(uint16 lootslot, ServerLootItem_Struct** cur = itemlist.begin(); end = itemlist.end(); - for(; cur != end; ++cur) { + for (; cur != end; ++cur) { sitem2 = *cur; - if(sitem2->equip_slot >= bagstart && sitem2->equip_slot < bagstart + 10) { + if (sitem2->equip_slot >= bagstart && sitem2->equip_slot < bagstart + 10) { bag_item_data[sitem2->equip_slot - bagstart] = sitem2; } } @@ -697,7 +703,7 @@ void Corpse::RemoveItem(uint16 lootslot) { ItemList::iterator cur,end; cur = itemlist.begin(); end = itemlist.end(); - for(; cur != end; ++cur) { + for (; cur != end; ++cur) { ServerLootItem_Struct* sitem = *cur; if (sitem->lootslot == lootslot) { RemoveItem(sitem); @@ -747,6 +753,7 @@ void Corpse::RemoveCash() { bool Corpse::IsEmpty() const { if (copper != 0 || silver != 0 || gold != 0 || platinum != 0) return false; + return(itemlist.size() == 0); } @@ -754,8 +761,8 @@ bool Corpse::Process() { if (player_corpse_depop) return false; - if(corpse_delay_timer.Check()) { - for (int i=0; iwrite(EQEMuLog::Debug, "Tagged %s player corpse has burried.", this->GetName()); } - else - { + else { LogFile->write(EQEMuLog::Error, "Unable to bury %s player corpse.", this->GetName()); return true; } @@ -817,22 +832,21 @@ void Corpse::SetDecayTimer(uint32 decaytime) { corpse_decay_timer.Start(decaytime); } -bool Corpse::CanMobLoot(int charid) { - uint8 z=0; - for(int i=0; i= MAX_LOOTERS) return; if(them == nullptr || !them->IsClient()) @@ -863,31 +877,48 @@ void Corpse::MakeLootRequestPackets(Client* client, const EQApplicationPacket* a return; } - if(being_looted_by == 0) { being_looted_by = 0xFFFFFFFF; } + if(being_looted_by == 0) + being_looted_by = 0xFFFFFFFF; if(this->being_looted_by != 0xFFFFFFFF) { // lets double check.... Entity* looter = entity_list.GetID(this->being_looted_by); - if(looter == 0) { this->being_looted_by = 0xFFFFFFFF; } + if(looter == 0) + this->being_looted_by = 0xFFFFFFFF; } - uint8 tCanLoot = 1; - bool lootcoin = false; - if(database.GetVariable("LootCoin", tmp, 9)) { lootcoin = (atoi(tmp) == 1); } + uint8 Loot_Request_Type = 1; + bool loot_coin = false; + if(database.GetVariable("LootCoin", tmp, 9)) + loot_coin = (atoi(tmp) == 1); - if(this->being_looted_by != 0xFFFFFFFF && this->being_looted_by != client->GetID()) { + if (this->being_looted_by != 0xFFFFFFFF && this->being_looted_by != client->GetID()) { SendLootReqErrorPacket(client, 0); - tCanLoot = 0; + Loot_Request_Type = 0; + } + else if (IsPlayerCorpse() && char_id == client->CharacterID()) { + Loot_Request_Type = 2; + } + else if ((IsNPCCorpse() || become_npc) && CanPlayerLoot(client->CharacterID())) { + Loot_Request_Type = 2; + } + else if (GetPlayerKillItem() == -1 && CanPlayerLoot(client->CharacterID())) { /* PVP loot all items, variable cash */ + Loot_Request_Type = 3; + } + else if (GetPlayerKillItem() == 1 && CanPlayerLoot(client->CharacterID())) { /* PVP loot 1 item, variable cash */ + Loot_Request_Type = 4; + } + else if (GetPlayerKillItem() > 1 && CanPlayerLoot(client->CharacterID())) { /* PVP loot 1 set item, variable cash */ + Loot_Request_Type = 5; } - else if(IsPlayerCorpse() && char_id == client->CharacterID()) { tCanLoot = 2; } - else if((IsNPCCorpse() || become_npc) && CanMobLoot(client->CharacterID())) { tCanLoot = 2; } - else if(GetPKItem() == -1 && CanMobLoot(client->CharacterID())) { tCanLoot = 3; } //pvp loot all items, variable cash - else if(GetPKItem() == 1 && CanMobLoot(client->CharacterID())) { tCanLoot = 4; } //pvp loot 1 item, variable cash - else if(GetPKItem() > 1 && CanMobLoot(client->CharacterID())) { tCanLoot = 5; } //pvp loot 1 set item, variable cash - if(tCanLoot == 1) { if(client->Admin() < 100 || !client->GetGM()) { SendLootReqErrorPacket(client, 2); } } + if (Loot_Request_Type == 1) { + if (client->Admin() < 100 || !client->GetGM()) { + SendLootReqErrorPacket(client, 2); + } + } - if(tCanLoot >= 2 || (tCanLoot == 1 && client->Admin() >= 100 && client->GetGM())) { + if(Loot_Request_Type >= 2 || (Loot_Request_Type == 1 && client->Admin() >= 100 && client->GetGM())) { this->being_looted_by = client->GetID(); EQApplicationPacket* outapp = new EQApplicationPacket(OP_MoneyOnCorpse, sizeof(moneyOnCorpseStruct)); moneyOnCorpseStruct* d = (moneyOnCorpseStruct*) outapp->pBuffer; @@ -895,8 +926,9 @@ void Corpse::MakeLootRequestPackets(Client* client, const EQApplicationPacket* a d->response = 1; d->unknown1 = 0x42; d->unknown2 = 0xef; - if(tCanLoot == 2 || (tCanLoot >= 3 && lootcoin)) { // dont take the coin off if it's a gm peeking at the corpse + /* Dont take the coin off if it's a gm peeking at the corpse */ + if(Loot_Request_Type == 2 || (Loot_Request_Type >= 3 && loot_coin)) { if(!IsPlayerCorpse() && client->IsGrouped() && client->AutoSplitEnabled() && client->GetGroup()) { d->copper = 0; d->silver = 0; @@ -920,15 +952,15 @@ void Corpse::MakeLootRequestPackets(Client* client, const EQApplicationPacket* a outapp->priority = 6; client->QueuePacket(outapp); safe_delete(outapp); - if(tCanLoot == 5) { - int pkitem = GetPKItem(); + if(Loot_Request_Type == 5) { + int pkitem = GetPlayerKillItem(); const Item_Struct* item = database.GetItem(pkitem); ItemInst* inst = database.CreateItem(item, item->MaxCharges); if(inst) { client->SendItemPacket(EmuConstants::CORPSE_BEGIN, inst, ItemPacketLoot); safe_delete(inst); } - else { client->Message(13, "Could not find item number %i to send!!", GetPKItem()); } + else { client->Message(13, "Could not find item number %i to send!!", GetPlayerKillItem()); } client->QueuePacket(app); return; @@ -940,8 +972,6 @@ void Corpse::MakeLootRequestPackets(Client* client, const EQApplicationPacket* a cur = itemlist.begin(); end = itemlist.end(); - uint8 containercount = 0; - int corpselootlimit = EQLimits::InventoryMapSize(MapCorpse, client->GetClientVersion()); for(; cur != end; ++cur) { @@ -951,7 +981,7 @@ void Corpse::MakeLootRequestPackets(Client* client, const EQApplicationPacket* a // Dont display the item if it's in a bag // Added cursor queue slots to corpse item visibility list. Nothing else should be making it to corpse. - if(!IsPlayerCorpse() || item_data->equip_slot <= MainCursor || item_data->equip_slot == MainPowerSource || tCanLoot>=3 || + if(!IsPlayerCorpse() || item_data->equip_slot <= MainCursor || item_data->equip_slot == MainPowerSource || Loot_Request_Type>=3 || (item_data->equip_slot >= 8000 && item_data->equip_slot <= 8999)) { if(i < corpselootlimit) { item = database.GetItem(item_data->item_id); @@ -981,14 +1011,14 @@ void Corpse::MakeLootRequestPackets(Client* client, const EQApplicationPacket* a if(IsPlayerCorpse() && i == 0 && itemlist.size() > 0) { // somehow, player corpse contains items, but client doesn't see them... client->Message(13, "This corpse contains items that are inaccessable!"); client->Message(15, "Contact a GM for item replacement, if necessary."); - client->Message(15, "BUGGED CORPSE [DBID: %i, Name: %s, Item Count: %i]", GetDBID(), GetName(), itemlist.size()); + client->Message(15, "BUGGED CORPSE [DBID: %i, Name: %s, Item Count: %i]", GetCorpseDBID(), GetName(), itemlist.size()); cur = itemlist.begin(); end = itemlist.end(); for(; cur != end; ++cur) { ServerLootItem_Struct* item_data = *cur; item = database.GetItem(item_data->item_id); - LogFile->write(EQEMuLog::Debug, "Corpse Looting: %s was not sent to client loot window (corpse_dbid: %i, charname: %s(%s))", item->Name, GetDBID(), client->GetName(), client->GetGM() ? "GM" : "Owner"); + LogFile->write(EQEMuLog::Debug, "Corpse Looting: %s was not sent to client loot window (corpse_dbid: %i, charname: %s(%s))", item->Name, GetCorpseDBID(), client->GetName(), client->GetGM() ? "GM" : "Owner"); client->Message(0, "Inaccessable Corpse Item: %s", item->Name); } } @@ -1034,7 +1064,7 @@ void Corpse::LootItem(Client* client, const EQApplicationPacket* app) { SendEndLootErrorPacket(client); return; } - if (IsPlayerCorpse() && !CanMobLoot(client->CharacterID()) && !become_npc && (char_id != client->CharacterID() && client->Admin() < 150)) { + if (IsPlayerCorpse() && !CanPlayerLoot(client->CharacterID()) && !become_npc && (char_id != client->CharacterID() && client->Admin() < 150)) { client->Message(13, "Error: This is a player corpse and you dont own it."); SendEndLootErrorPacket(client); return; @@ -1044,7 +1074,7 @@ void Corpse::LootItem(Client* client, const EQApplicationPacket* app) { client->Message(13, "Error: Corpse locked by GM."); return; } - if (IsPlayerCorpse() && (char_id != client->CharacterID()) && CanMobLoot(client->CharacterID()) && GetPKItem() == 0){ + if (IsPlayerCorpse() && (char_id != client->CharacterID()) && CanPlayerLoot(client->CharacterID()) && GetPlayerKillItem() == 0){ client->Message(13, "Error: You cannot loot any more items from this corpse."); SendEndLootErrorPacket(client); being_looted_by = 0xFFFFFFFF; @@ -1055,17 +1085,17 @@ void Corpse::LootItem(Client* client, const EQApplicationPacket* app) { ServerLootItem_Struct* item_data = nullptr, *bag_item_data[10]; memset(bag_item_data, 0, sizeof(bag_item_data)); - if (GetPKItem() > 1){ - item = database.GetItem(GetPKItem()); + if (GetPlayerKillItem() > 1){ + item = database.GetItem(GetPlayerKillItem()); } - else if (GetPKItem() == -1 || GetPKItem() == 1){ + else if (GetPlayerKillItem() == -1 || GetPlayerKillItem() == 1){ item_data = GetItem(lootitem->slot_id - EmuConstants::CORPSE_BEGIN); //dont allow them to loot entire bags of items as pvp reward } else{ item_data = GetItem(lootitem->slot_id - EmuConstants::CORPSE_BEGIN, bag_item_data); } - if (GetPKItem()<=1 && item_data != 0) { + if (GetPlayerKillItem()<=1 && item_data != 0) { item = database.GetItem(item_data->item_id); } @@ -1104,7 +1134,7 @@ void Corpse::LootItem(Client* client, const EQApplicationPacket* app) { char buf[88]; char corpse_name[64]; - strcpy(corpse_name, orgname); + strcpy(corpse_name, corpse_name); snprintf(buf, 87, "%d %d %s", inst->GetItem()->ID, inst->GetCharges(), EntityList::RemoveNumbers(corpse_name)); buf[87] = '\0'; std::vector args; @@ -1149,7 +1179,7 @@ void Corpse::LootItem(Client* client, const EQApplicationPacket* app) { } /* Remove Bag Contents */ - if (item->ItemClass == ItemClassContainer && (GetPKItem() != -1 || GetPKItem() != 1)) { + if (item->ItemClass == ItemClassContainer && (GetPlayerKillItem() != -1 || GetPlayerKillItem() != 1)) { for (int i = SUB_BEGIN; i < EmuConstants::ITEM_CONTAINER_SIZE; i++) { if (bag_item_data[i]) { /* Delete needs to be before RemoveItem because its deletes the pointer for item_data/bag_item_data */ @@ -1160,8 +1190,8 @@ void Corpse::LootItem(Client* client, const EQApplicationPacket* app) { } } - if (GetPKItem() != -1){ - SetPKItem(0); + if (GetPlayerKillItem() != -1){ + SetPlayerKillItemID(0); } /* Send message with item link to groups and such */ @@ -1211,7 +1241,6 @@ void Corpse::EndLoot(Client* client, const EQApplicationPacket* app) { client->QueuePacket(outapp); safe_delete(outapp); - //client->Save(); //inventory operations auto-commit this->being_looted_by = 0xFFFFFFFF; if (this->IsEmpty()) Delete(); @@ -1275,7 +1304,7 @@ void Corpse::QueryLoot(Client* to) { } if (IsPlayerCorpse()) { - to->Message(0, "%i visible %s (%i total) on %s (DBID: %i).", x, x==1?"item":"items", y, this->GetName(), this->GetDBID()); + to->Message(0, "%i visible %s (%i total) on %s (DBID: %i).", x, x==1?"item":"items", y, this->GetName(), this->GetCorpseDBID()); } else { to->Message(0, "%i %s on %s.", y, y==1?"item":"items", this->GetName()); @@ -1330,8 +1359,8 @@ bool Corpse::Summon(Client* client, bool spell, bool CheckDistance) { return true; } -void Corpse::CompleteRezz(){ - rezzexp = 0; +void Corpse::CompleteResurrection(){ + rez_experience = 0; is_corpse_changed = true; this->Save(); } @@ -1375,7 +1404,7 @@ uint32 Corpse::GetEquipmentColor(uint8 material_slot) const { } void Corpse::AddLooter(Mob* who) { - for (int i=0; iCastToClient()->CharacterID(); break; @@ -1400,111 +1429,5 @@ void Corpse::LoadPlayerCorpseDecayTime(uint32 corpse_db_id){ else { corpse_graveyard_timer.SetTimer(3000); } -} - -/* -** Corpse slot translations are needed until corpse database blobs are converted -** -** To account for the addition of MainPowerSource, MainGeneral9 and MainGeneral10 into -** the contiguous possessions slot enumeration, the following designations will be used: -** -** Designatiom Server Corpse Offset -** -------------------------------------------------- -** MainCharm 0 0 0 -** ... ... ... 0 -** MainWaist 20 20 0 -** MainPowerSource 21 9999 +9978 -** MainAmmo 22 21 -1 -** -** MainGeneral1 23 22 -1 -** ... ... ... -1 -** MainGeneral8 30 29 -1 -** MainGeneral9 31 9997 +9966 -** MainGeneral10 32 9998 +9966 -** -** MainCursor 33 30 -3 -** -** MainGeneral1_1 251 251 0 -** ... ... ... 0 -** MainGeneral8_10 330 330 0 -** MainGeneral9_1 331 341 +10 -** ... ... ... +10 -** MainGeneral10_10 350 360 +10 -** -** MainCursor_1 351 331 -20 -** ... ... ... -20 -** MainCursor_10 360 340 -20 -** -** (Not all slot designations are valid to all clients..see ##_constants.h files for valid slot enumerations) -*/ -int16 Corpse::ServerToCorpseSlot(int16 server_slot) -{ - return server_slot; // temporary return - - /* - switch (server_slot) - { - case MainPowerSource: - return 9999; - case MainGeneral9: - return 9997; - case MainGeneral10: - return 9998; - case MainCursor: - return 30; - case MainAmmo: - case MainGeneral1: - case MainGeneral2: - case MainGeneral3: - case MainGeneral4: - case MainGeneral5: - case MainGeneral6: - case MainGeneral7: - case MainGeneral8: - return server_slot - 1; - default: - if (server_slot >= EmuConstants::CURSOR_BAG_BEGIN && server_slot <= EmuConstants::CURSOR_BAG_END) - return server_slot - 20; - else if (server_slot >= EmuConstants::GENERAL_BAGS_END - 19 && server_slot <= EmuConstants::GENERAL_BAGS_END) - return server_slot + 10; - else - return server_slot; - } - */ -} - -int16 Corpse::CorpseToServerSlot(int16 corpse_slot) -{ - return corpse_slot; // temporary return - - /* - switch (corpse_slot) - { - case 9999: - return MainPowerSource; - case 9997: - return MainGeneral9; - case 9998: - return MainGeneral10; - case 30: - return MainCursor; - case 21: // old SLOT_AMMO - case 22: // old PERSONAL_BEGIN - case 23: - case 24: - case 25: - case 26: - case 27: - case 28: - case 29: // old PERSONAL_END - return corpse_slot + 1; - default: - if (corpse_slot >= 331 && corpse_slot <= 340) - return corpse_slot + 20; - else if (corpse_slot >= 341 && corpse_slot <= 360) - return corpse_slot - 10; - else - return corpse_slot; - } - */ + } diff --git a/zone/corpse.cpp.orig b/zone/corpse.cpp.orig new file mode 100644 index 000000000..e29ca4303 --- /dev/null +++ b/zone/corpse.cpp.orig @@ -0,0 +1,1676 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2003 EQEMu Development Team (http://eqemulator.net) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY except by those people which sell it, which + are required to give you total support for your newly bought product; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +/* +New class for handeling corpses and everything associated with them. +Child of the Mob class. +-Quagmire +*/ +#include "../common/debug.h" +#include +#include +#include +#include +#include +#ifdef _WINDOWS + #define snprintf _snprintf + #define vsnprintf _vsnprintf + #define strncasecmp _strnicmp + #define strcasecmp _stricmp +#endif + +#include "masterentity.h" +#include "../common/packet_functions.h" +#include "../common/string_util.h" +#include "../common/crc32.h" +#include "string_ids.h" +#include "worldserver.h" +#include "../common/rulesys.h" +#include "quest_parser_collection.h" + +extern EntityList entity_list; +extern Zone* zone; +extern WorldServer worldserver; +extern npcDecayTimes_Struct npcCorpseDecayTimes[100]; + +void Corpse::SendEndLootErrorPacket(Client* client) { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_LootComplete, 0); + client->QueuePacket(outapp); + safe_delete(outapp); +} + +void Corpse::SendLootReqErrorPacket(Client* client, uint8 response) { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_MoneyOnCorpse, sizeof(moneyOnCorpseStruct)); + moneyOnCorpseStruct* d = (moneyOnCorpseStruct*) outapp->pBuffer; + d->response = response; + d->unknown1 = 0x5a; + d->unknown2 = 0x40; + client->QueuePacket(outapp); + safe_delete(outapp); +} + +<<<<<<< HEAD +Corpse* Corpse::LoadFromDBData(uint32 in_dbid, uint32 in_charid, std::string in_charname, const xyz_heading& position, std::string time_of_death, bool rezzed, bool was_at_graveyard) +{ +======= +Corpse* Corpse::LoadCharacterCorpseEntity(uint32 in_dbid, uint32 in_charid, std::string in_charname, float in_x, float in_y, float in_z, float in_heading, std::string time_of_death, bool rezzed, bool was_at_graveyard){ +>>>>>>> master + uint32 item_count = database.GetCharacterCorpseItemCount(in_dbid); + char *buffer = new char[sizeof(PlayerCorpse_Struct) + (item_count * sizeof(player_lootitem::ServerLootItem_Struct))]; + PlayerCorpse_Struct *pcs = (PlayerCorpse_Struct*)buffer; + database.LoadCharacterCorpseData(in_dbid, pcs); + + /* Load Items */ + ItemList itemlist; + ServerLootItem_Struct* tmp = 0; + for (unsigned int i = 0; i < pcs->itemcount; i++) { + tmp = new ServerLootItem_Struct; + memcpy(tmp, &pcs->items[i], sizeof(player_lootitem::ServerLootItem_Struct)); + itemlist.push_back(tmp); + } + + /* Create Corpse Entity */ + Corpse* pc = new Corpse( + in_dbid, // uint32 in_dbid + in_charid, // uint32 in_charid + in_charname.c_str(), // char* in_charname + &itemlist, // ItemList* in_itemlist + pcs->copper, // uint32 in_copper + pcs->silver, // uint32 in_silver + pcs->gold, // uint32 in_gold + pcs->plat, // uint32 in_plat + position, + pcs->size, // float in_size + pcs->gender, // uint8 in_gender + pcs->race, // uint16 in_race + pcs->class_, // uint8 in_class + pcs->deity, // uint8 in_deity + pcs->level, // uint8 in_level + pcs->texture, // uint8 in_texture + pcs->helmtexture, // uint8 in_helmtexture + pcs->exp, // uint32 in_rezexp + was_at_graveyard // bool wasAtGraveyard + ); + if (pcs->locked){ + pc->Lock(); + } + + /* Load Item Tints */ + pc->item_tint[0].color = pcs->item_tint[0].color; + pc->item_tint[1].color = pcs->item_tint[1].color; + pc->item_tint[2].color = pcs->item_tint[2].color; + pc->item_tint[3].color = pcs->item_tint[3].color; + pc->item_tint[4].color = pcs->item_tint[4].color; + pc->item_tint[5].color = pcs->item_tint[5].color; + pc->item_tint[6].color = pcs->item_tint[6].color; + pc->item_tint[7].color = pcs->item_tint[7].color; + pc->item_tint[8].color = pcs->item_tint[8].color; + + /* Load Physical Appearance */ + pc->haircolor = pcs->haircolor; + pc->beardcolor = pcs->beardcolor; + pc->eyecolor1 = pcs->eyecolor1; + pc->eyecolor2 = pcs->eyecolor2; + pc->hairstyle = pcs->hairstyle; + pc->luclinface = pcs->face; + pc->beard = pcs->beard; + pc->drakkin_heritage = pcs->drakkin_heritage; + pc->drakkin_tattoo = pcs->drakkin_tattoo; + pc->drakkin_details = pcs->drakkin_details; + pc->IsRezzed(rezzed); + pc->become_npc = false; + + safe_delete_array(pcs); + + return pc; +} + +Corpse::Corpse(NPC* in_npc, ItemList* in_itemlist, uint32 in_npctypeid, const NPCType** in_npctypedata, uint32 in_decaytime) +<<<<<<< HEAD +// vesuvias - appearence fix +: Mob("Unnamed_Corpse","",0,0,in_npc->GetGender(),in_npc->GetRace(),in_npc->GetClass(),BT_Humanoid,//bodytype added + in_npc->GetDeity(),in_npc->GetLevel(),in_npc->GetNPCTypeID(),in_npc->GetSize(),0, + in_npc->GetPosition(), 0, in_npc->GetTexture(),in_npc->GetHelmTexture(), + 0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0xff,0,0,0,0,0,0,0,0,0), +======= + : Mob("Unnamed_Corpse", // const char* in_name, + "", // const char* in_lastname, + 0, // int32 in_cur_hp, + 0, // int32 in_max_hp, + in_npc->GetGender(), // uint8 in_gender, + in_npc->GetRace(), // uint16 in_race, + in_npc->GetClass(), // uint8 in_class, + BT_Humanoid, // bodyType in_bodytype, + in_npc->GetDeity(), // uint8 in_deity, + in_npc->GetLevel(), // uint8 in_level, + in_npc->GetNPCTypeID(), // uint32 in_npctype_id, + in_npc->GetSize(), // float in_size, + 0, // float in_runspeed, + in_npc->GetHeading(), // float in_heading, + in_npc->GetX(), // float in_x_pos, + in_npc->GetY(), // float in_y_pos, + in_npc->GetZ(), // float in_z_pos, + 0, // uint8 in_light, + in_npc->GetTexture(), // uint8 in_texture, + in_npc->GetHelmTexture(), // uint8 in_helmtexture, + 0, // uint16 in_ac, + 0, // uint16 in_atk, + 0, // uint16 in_str, + 0, // uint16 in_sta, + 0, // uint16 in_dex, + 0, // uint16 in_agi, + 0, // uint16 in_int, + 0, // uint16 in_wis, + 0, // uint16 in_cha, + 0, // uint8 in_haircolor, + 0, // uint8 in_beardcolor, + 0, // uint8 in_eyecolor1, // the eyecolors always seem to be the same, maybe left and right eye? + 0, // uint8 in_eyecolor2, + 0, // uint8 in_hairstyle, + 0, // uint8 in_luclinface, + 0, // uint8 in_beard, + 0, // uint32 in_drakkin_heritage, + 0, // uint32 in_drakkin_tattoo, + 0, // uint32 in_drakkin_details, + 0, // uint32 in_armor_tint[_MaterialCount], + 0xff, // uint8 in_aa_title, + 0, // uint8 in_see_invis, // see through invis/ivu + 0, // uint8 in_see_invis_undead, + 0, // uint8 in_see_hide, + 0, // uint8 in_see_improved_hide, + 0, // int32 in_hp_regen, + 0, // int32 in_mana_regen, + 0, // uint8 in_qglobal, + 0, // uint8 in_maxlevel, + 0 // uint32 in_scalerate +), +>>>>>>> master + corpse_decay_timer(in_decaytime), + corpse_rez_timer(0), + corpse_delay_timer(RuleI(NPC, CorpseUnlockTimer)), + corpse_graveyard_timer(0), + loot_cooldown_timer(10) +{ + corpse_graveyard_timer.Disable(); + + memset(item_tint, 0, sizeof(item_tint)); + + is_corpse_changed = false; + is_player_corpse = false; + is_locked = false; + being_looted_by = 0xFFFFFFFF; + if (in_itemlist) { + itemlist = *in_itemlist; + in_itemlist->clear(); + } + + SetCash(in_npc->GetCopper(), in_npc->GetSilver(), in_npc->GetGold(), in_npc->GetPlatinum()); + + npctype_id = in_npctypeid; + SetPlayerKillItemID(0); + char_id = 0; + corpse_db_id = 0; + player_corpse_depop = false; + strcpy(corpse_name, in_npc->GetName()); + strcpy(name, in_npc->GetName()); + + for(int count = 0; count < 100; count++) { + if ((level >= npcCorpseDecayTimes[count].minlvl) && (level <= npcCorpseDecayTimes[count].maxlvl)) { + corpse_decay_timer.SetTimer(npcCorpseDecayTimes[count].seconds*1000); + break; + } + } + if(IsEmpty()) { + corpse_decay_timer.SetTimer(RuleI(NPC,EmptyNPCCorpseDecayTimeMS)+1000); + } + + + if(in_npc->HasPrivateCorpse()) { + corpse_delay_timer.SetTimer(corpse_decay_timer.GetRemainingTime() + 1000); + } + + for (int i = 0; i < MAX_LOOTERS; i++){ + allowed_looters[i] = 0; + } + this->rez_experience = 0; +} + +Corpse::Corpse(Client* client, int32 in_rezexp) : Mob ( + "Unnamed_Corpse", // const char* in_name, + "", // const char* in_lastname, + 0, // int32 in_cur_hp, + 0, // int32 in_max_hp, + client->GetGender(), // uint8 in_gender, + client->GetRace(), // uint16 in_race, + client->GetClass(), // uint8 in_class, + BT_Humanoid, // bodyType in_bodytype, + client->GetDeity(), // uint8 in_deity, + client->GetLevel(), // uint8 in_level, + 0, // uint32 in_npctype_id, + client->GetSize(), // float in_size, + 0, // float in_runspeed, + client->GetPosition(), + 0, // uint8 in_light, + client->GetTexture(), // uint8 in_texture, + client->GetHelmTexture(), // uint8 in_helmtexture, + 0, // uint16 in_ac, + 0, // uint16 in_atk, + 0, // uint16 in_str, + 0, // uint16 in_sta, + 0, // uint16 in_dex, + 0, // uint16 in_agi, + 0, // uint16 in_int, + 0, // uint16 in_wis, + 0, // uint16 in_cha, + client->GetPP().haircolor, // uint8 in_haircolor, + client->GetPP().beardcolor, // uint8 in_beardcolor, + client->GetPP().eyecolor1, // uint8 in_eyecolor1, // the eyecolors always seem to be the same, maybe left and right eye? + client->GetPP().eyecolor2, // uint8 in_eyecolor2, + client->GetPP().hairstyle, // uint8 in_hairstyle, + client->GetPP().face, // uint8 in_luclinface, + client->GetPP().beard, // uint8 in_beard, + client->GetPP().drakkin_heritage, // uint32 in_drakkin_heritage, + client->GetPP().drakkin_tattoo, // uint32 in_drakkin_tattoo, + client->GetPP().drakkin_details, // uint32 in_drakkin_details, + 0, // uint32 in_armor_tint[_MaterialCount], + 0xff, // uint8 in_aa_title, + 0, // uint8 in_see_invis, // see through invis + 0, // uint8 in_see_invis_undead, // see through invis vs. undead + 0, // uint8 in_see_hide, + 0, // uint8 in_see_improved_hide, + 0, // int32 in_hp_regen, + 0, // int32 in_mana_regen, + 0, // uint8 in_qglobal, + 0, // uint8 in_maxlevel, + 0 // uint32 in_scalerate + ), + corpse_decay_timer(RuleI(Character, CorpseDecayTimeMS)), + corpse_rez_timer(RuleI(Character, CorpseResTimeMS)), + corpse_delay_timer(RuleI(NPC, CorpseUnlockTimer)), + corpse_graveyard_timer(RuleI(Zone, GraveyardTimeMS)), + loot_cooldown_timer(10) +{ + int i; + + PlayerProfile_Struct *pp = &client->GetPP(); + ItemInst *item; + + /* Check if Zone has Graveyard First */ + if(!zone->HasGraveyard()) { + corpse_graveyard_timer.Disable(); + } + + memset(item_tint, 0, sizeof(item_tint)); + + for (i = 0; i < MAX_LOOTERS; i++){ + allowed_looters[i] = 0; + } + + is_corpse_changed = true; + rez_experience = in_rezexp; + can_corpse_be_rezzed = true; + is_player_corpse = true; + is_locked = false; + being_looted_by = 0xFFFFFFFF; + char_id = client->CharacterID(); + corpse_db_id = 0; + player_corpse_depop = false; + copper = 0; + silver = 0; + gold = 0; + platinum = 0; + +<<<<<<< HEAD + strcpy(orgname, pp->name); + strcpy(name, pp->name); +======= + strcpy(corpse_name, pp->name); + strcpy(name, pp->name); +>>>>>>> master + + /* become_npc was not being initialized which led to some pretty funky things with newly created corpses */ + become_npc = false; + + SetPlayerKillItemID(0); + + /* Check Rule to see if we can leave corpses */ + if(!RuleB(Character, LeaveNakedCorpses) || + RuleB(Character, LeaveCorpses) && + GetLevel() >= RuleI(Character, DeathItemLossLevel)) { + // cash + // Let's not move the cash when 'RespawnFromHover = true' && 'client->GetClientVersion() < EQClientSoF' since the client doesn't. + // (change to first client that supports 'death hover' mode, if not SoF.) + if (!RuleB(Character, RespawnFromHover) || client->GetClientVersion() < EQClientSoF) { + SetCash(pp->copper, pp->silver, pp->gold, pp->platinum); + pp->copper = 0; + pp->silver = 0; + pp->gold = 0; + pp->platinum = 0; + } + + // get their tints + memcpy(item_tint, &client->GetPP().item_tint, sizeof(item_tint)); + + // solar: TODO soulbound items need not be added to corpse, but they need + // to go into the regular slots on the player, out of bags + + // worn + inventory + cursor + std::list removed_list; + bool cursor = false; + for(i = MAIN_BEGIN; i < EmuConstants::MAP_POSSESSIONS_SIZE; i++) { + if(i == MainAmmo && client->GetClientVersion() >= EQClientSoF) { + item = client->GetInv().GetItem(MainPowerSource); + if((item && (!client->IsBecomeNPC())) || (item && client->IsBecomeNPC() && !item->GetItem()->NoRent)) { + std::list slot_list = MoveItemToCorpse(client, item, MainPowerSource); + removed_list.merge(slot_list); + } + + } + + item = client->GetInv().GetItem(i); + if((item && (!client->IsBecomeNPC())) || (item && client->IsBecomeNPC() && !item->GetItem()->NoRent)) { + std::list slot_list = MoveItemToCorpse(client, item, i); + removed_list.merge(slot_list); + } + } + + // cursor queue // (change to first client that supports 'death hover' mode, if not SoF.) + if (!RuleB(Character, RespawnFromHover) || client->GetClientVersion() < EQClientSoF) { + + // bumped starting assignment to 8001 because any in-memory 'slot 8000' item was moved above as 'slot 30' + // this was mainly for client profile state reflection..should match db player inventory entries now. + + iter_queue it; + for (it = client->GetInv().cursor_begin(), i = 8001; it != client->GetInv().cursor_end(); ++it, i++) { + item = *it; + if ((item && (!client->IsBecomeNPC())) || (item && client->IsBecomeNPC() && !item->GetItem()->NoRent)) { + std::list slot_list = MoveItemToCorpse(client, item, i); + removed_list.merge(slot_list); + cursor = true; + } + } + } + + database.TransactionBegin(); + if (removed_list.size() != 0) { + std::stringstream ss(""); + ss << "DELETE FROM inventory WHERE charid=" << client->CharacterID(); + ss << " AND ("; + std::list::const_iterator iter = removed_list.begin(); + bool first = true; + while (iter != removed_list.end()) { + if (first) { + first = false; + } + else { + ss << " OR "; + } + ss << "slotid=" << (*iter); + ++iter; + } + ss << ")"; + database.QueryDatabase(ss.str().c_str()); + } + + if (cursor) { // all cursor items should be on corpse (client < SoF or RespawnFromHover = false) + while (!client->GetInv().CursorEmpty()) + client->DeleteItemInInventory(MainCursor, 0, false, false); + } + else { // only visible cursor made it to corpse (client >= Sof and RespawnFromHover = true) + std::list::const_iterator start = client->GetInv().cursor_begin(); + std::list::const_iterator finish = client->GetInv().cursor_end(); + database.SaveCursor(client->CharacterID(), start, finish); + } + + client->CalcBonuses(); // will only affect offline profile viewing of dead characters..unneeded overhead + client->Save(); + + IsRezzed(false); + Save(); + database.TransactionCommit(); + + return; + } //end "not leaving naked corpses" + + IsRezzed(false); + Save(); +} + +std::list Corpse::MoveItemToCorpse(Client *client, ItemInst *item, int16 equipslot) +{ + int bagindex; + int16 interior_slot; + ItemInst *interior_item; + std::list returnlist; + + AddItem(item->GetItem()->ID, item->GetCharges(), equipslot, item->GetAugmentItemID(0), item->GetAugmentItemID(1), item->GetAugmentItemID(2), item->GetAugmentItemID(3), item->GetAugmentItemID(4)); + returnlist.push_back(equipslot); + + // Qualified bag slot iterations. processing bag slots that don't exist is probably not a good idea. + if (item->IsType(ItemClassContainer) && ((equipslot >= EmuConstants::GENERAL_BEGIN && equipslot <= MainCursor))) { + for (bagindex = SUB_BEGIN; bagindex <= EmuConstants::ITEM_CONTAINER_SIZE; bagindex++) { + // For empty bags in cursor queue, slot was previously being resolved as SLOT_INVALID (-1) + interior_slot = Inventory::CalcSlotId(equipslot, bagindex); + interior_item = client->GetInv().GetItem(interior_slot); + + if (interior_item) { + AddItem(interior_item->GetItem()->ID, interior_item->GetCharges(), interior_slot, interior_item->GetAugmentItemID(0), interior_item->GetAugmentItemID(1), interior_item->GetAugmentItemID(2), interior_item->GetAugmentItemID(3), interior_item->GetAugmentItemID(4)); + returnlist.push_back(Inventory::CalcSlotId(equipslot, bagindex)); + client->DeleteItemInInventory(interior_slot, 0, true, false); + } + } + } + client->DeleteItemInInventory(equipslot, 0, true, false); + return returnlist; +} + +<<<<<<< HEAD +// To be called from LoadFromDBData +// Mongrel: added see_invis and see_invis_undead +Corpse::Corpse(uint32 in_dbid, uint32 in_charid, const char* in_charname, ItemList* in_itemlist, uint32 in_copper, uint32 in_silver, uint32 in_gold, uint32 in_plat, const xyz_heading& position, float in_size, uint8 in_gender, uint16 in_race, uint8 in_class, uint8 in_deity, uint8 in_level, uint8 in_texture, uint8 in_helmtexture,uint32 in_rezexp, bool wasAtGraveyard) +: Mob("Unnamed_Corpse", +"", +0, +0, +in_gender, +in_race, +in_class, +BT_Humanoid, +in_deity, +in_level, +0, +in_size, +0, +position, +0, +in_texture, +in_helmtexture, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0xff, +0, +0, +0, +0, +0, +0, +0, +0, +0), +======= +/* Called from Database Load */ + +Corpse::Corpse(uint32 in_dbid, uint32 in_charid, const char* in_charname, ItemList* in_itemlist, uint32 in_copper, uint32 in_silver, uint32 in_gold, uint32 in_plat, float in_x, float in_y, float in_z, float in_heading, float in_size, uint8 in_gender, uint16 in_race, uint8 in_class, uint8 in_deity, uint8 in_level, uint8 in_texture, uint8 in_helmtexture,uint32 in_rezexp, bool wasAtGraveyard) + : Mob("Unnamed_Corpse", // const char* in_name, + "", // const char* in_lastname, + 0, // int32 in_cur_hp, + 0, // int32 in_max_hp, + in_gender, // uint8 in_gender, + in_race, // uint16 in_race, + in_class, // uint8 in_class, + BT_Humanoid, // bodyType in_bodytype, + in_deity, // uint8 in_deity, + in_level, // uint8 in_level, + 0, // uint32 in_npctype_id, + in_size, // float in_size, + 0, // float in_runspeed, + in_heading, // float in_heading, + in_x, // float in_x_pos, + in_y, // float in_y_pos, + in_z, // float in_z_pos, + 0, // uint8 in_light, + in_texture, // uint8 in_texture, + in_helmtexture, // uint8 in_helmtexture, + 0, // uint16 in_ac, + 0, // uint16 in_atk, + 0, // uint16 in_str, + 0, // uint16 in_sta, + 0, // uint16 in_dex, + 0, // uint16 in_agi, + 0, // uint16 in_int, + 0, // uint16 in_wis, + 0, // uint16 in_cha, + 0, // uint8 in_haircolor, + 0, // uint8 in_beardcolor, + 0, // uint8 in_eyecolor1, // the eyecolors always seem to be the same, maybe left and right eye? + 0, // uint8 in_eyecolor2, + 0, // uint8 in_hairstyle, + 0, // uint8 in_luclinface, + 0, // uint8 in_beard, + 0, // uint32 in_drakkin_heritage, + 0, // uint32 in_drakkin_tattoo, + 0, // uint32 in_drakkin_details, + 0, // uint32 in_armor_tint[_MaterialCount], + 0xff, // uint8 in_aa_title, + 0, // uint8 in_see_invis, // see through invis/ivu + 0, // uint8 in_see_invis_undead, + 0, // uint8 in_see_hide, + 0, // uint8 in_see_improved_hide, + 0, // int32 in_hp_regen, + 0, // int32 in_mana_regen, + 0, // uint8 in_qglobal, + 0, // uint8 in_maxlevel, + 0), // uint32 in_scalerate +>>>>>>> master + corpse_decay_timer(RuleI(Character, CorpseDecayTimeMS)), + corpse_rez_timer(RuleI(Character, CorpseResTimeMS)), + corpse_delay_timer(RuleI(NPC, CorpseUnlockTimer)), + corpse_graveyard_timer(RuleI(Zone, GraveyardTimeMS)), + loot_cooldown_timer(10) +{ + + LoadPlayerCorpseDecayTime(in_dbid); + + if (!zone->HasGraveyard() || wasAtGraveyard){ + corpse_graveyard_timer.Disable(); + } + + memset(item_tint, 0, sizeof(item_tint)); + + is_corpse_changed = false; + is_player_corpse = true; + is_locked = false; + being_looted_by = 0xFFFFFFFF; + corpse_db_id = in_dbid; + player_corpse_depop = false; + char_id = in_charid; + itemlist = *in_itemlist; + in_itemlist->clear(); + + strcpy(corpse_name, in_charname); + strcpy(name, in_charname); + + this->copper = in_copper; + this->silver = in_silver; + this->gold = in_gold; + this->platinum = in_plat; + + rez_experience = in_rezexp; + + for (int i = 0; i < MAX_LOOTERS; i++) + allowed_looters[i] = 0; +<<<<<<< HEAD + + SetPKItem(0); +======= + } + SetPlayerKillItemID(0); +>>>>>>> master +} + +Corpse::~Corpse() { + if (is_player_corpse && !(player_corpse_depop && corpse_db_id == 0)) { + Save(); + } + ItemList::iterator cur,end; + cur = itemlist.begin(); + end = itemlist.end(); + for(; cur != end; ++cur) { + ServerLootItem_Struct* item = *cur; + safe_delete(item); + } + itemlist.clear(); +} + +/* +this needs to be called AFTER the entity_id is set +the client does this too, so it's unchangable +*/ +void Corpse::CalcCorpseName() { + EntityList::RemoveNumbers(name); + char tmp[64]; + if (is_player_corpse){ + snprintf(tmp, sizeof(tmp), "'s corpse%d", GetID()); + } + else{ + snprintf(tmp, sizeof(tmp), "`s_corpse%d", GetID()); + } + name[(sizeof(name) - 1) - strlen(tmp)] = 0; + strcat(name, tmp); +} + +bool Corpse::Save() { + if (!is_player_corpse) + return true; + if (!is_corpse_changed) + return true; + + uint32 tmp = this->CountItems(); + uint32 tmpsize = sizeof(PlayerCorpse_Struct) + (tmp * sizeof(player_lootitem::ServerLootItem_Struct)); + + PlayerCorpse_Struct* dbpc = (PlayerCorpse_Struct*) new uchar[tmpsize]; + memset(dbpc, 0, tmpsize); + dbpc->itemcount = tmp; + dbpc->size = this->size; + dbpc->locked = is_locked; + dbpc->copper = this->copper; + dbpc->silver = this->silver; + dbpc->gold = this->gold; + dbpc->plat = this->platinum; + dbpc->race = this->race; + dbpc->class_ = class_; + dbpc->gender = gender; + dbpc->deity = deity; + dbpc->level = level; + dbpc->texture = this->texture; + dbpc->helmtexture = this->helmtexture; + dbpc->exp = rez_experience; + + memcpy(dbpc->item_tint, item_tint, sizeof(dbpc->item_tint)); + dbpc->haircolor = haircolor; + dbpc->beardcolor = beardcolor; + dbpc->eyecolor2 = eyecolor1; + dbpc->hairstyle = hairstyle; + dbpc->face = luclinface; + dbpc->beard = beard; + dbpc->drakkin_heritage = drakkin_heritage; + dbpc->drakkin_tattoo = drakkin_tattoo; + dbpc->drakkin_details = drakkin_details; + + uint32 x = 0; + ItemList::iterator cur, end; + cur = itemlist.begin(); + end = itemlist.end(); + for (; cur != end; ++cur) { + ServerLootItem_Struct* item = *cur; + memcpy((char*)&dbpc->items[x++], (char*)item, sizeof(ServerLootItem_Struct)); + } + + /* Create New Corpse*/ + if (corpse_db_id == 0) { +<<<<<<< HEAD + corpse_db_id = database.SaveCharacterCorpse(char_id, orgname, zone->GetZoneID(), zone->GetInstanceID(), dbpc, m_Position); + } + /* Update Corpse Data */ + else{ + corpse_db_id = database.UpdateCharacterCorpse(corpse_db_id, char_id, orgname, zone->GetZoneID(), zone->GetInstanceID(), dbpc, m_Position, IsRezzed()); +======= + corpse_db_id = database.SaveCharacterCorpse(char_id, corpse_name, zone->GetZoneID(), zone->GetInstanceID(), dbpc, x_pos, y_pos, z_pos, heading); + } + /* Update Corpse Data */ + else{ + corpse_db_id = database.UpdateCharacterCorpse(corpse_db_id, char_id, corpse_name, zone->GetZoneID(), zone->GetInstanceID(), dbpc, x_pos, y_pos, z_pos, heading, IsRezzed()); +>>>>>>> master + } + + safe_delete_array(dbpc); + + return true; +} + +void Corpse::Delete() { + if (IsPlayerCorpse() && corpse_db_id != 0) + database.DeleteCharacterCorpse(corpse_db_id); + + corpse_db_id = 0; + player_corpse_depop = true; +} + +void Corpse::Bury() { + if (IsPlayerCorpse() && corpse_db_id != 0){ + database.BuryCharacterCorpse(corpse_db_id); + } + corpse_db_id = 0; + player_corpse_depop = true; +} + +void Corpse::DepopNPCCorpse() { + if (IsNPCCorpse()) + player_corpse_depop = true; +} + +void Corpse::DepopPlayerCorpse() { + player_corpse_depop = true; +} + +uint32 Corpse::CountItems() { + return itemlist.size(); +} + +void Corpse::AddItem(uint32 itemnum, uint16 charges, int16 slot, uint32 aug1, uint32 aug2, uint32 aug3, uint32 aug4, uint32 aug5) { + if (!database.GetItem(itemnum)) + return; + + is_corpse_changed = true; + + ServerLootItem_Struct* item = new ServerLootItem_Struct; + + memset(item, 0, sizeof(ServerLootItem_Struct)); + item->item_id = itemnum; + item->charges = charges; + item->equip_slot = slot; + item->aug_1=aug1; + item->aug_2=aug2; + item->aug_3=aug3; + item->aug_4=aug4; + item->aug_5=aug5; + itemlist.push_back(item); +} + +ServerLootItem_Struct* Corpse::GetItem(uint16 lootslot, ServerLootItem_Struct** bag_item_data) { + ServerLootItem_Struct *sitem = 0, *sitem2; + + ItemList::iterator cur,end; + cur = itemlist.begin(); + end = itemlist.end(); + for(; cur != end; ++cur) { + if((*cur)->lootslot == lootslot) { + sitem = *cur; + break; + } + } + + if (sitem && bag_item_data && Inventory::SupportsContainers(sitem->equip_slot)) { + int16 bagstart = Inventory::CalcSlotId(sitem->equip_slot, SUB_BEGIN); + + cur = itemlist.begin(); + end = itemlist.end(); + for (; cur != end; ++cur) { + sitem2 = *cur; + if (sitem2->equip_slot >= bagstart && sitem2->equip_slot < bagstart + 10) { + bag_item_data[sitem2->equip_slot - bagstart] = sitem2; + } + } + } + + return sitem; +} + +uint32 Corpse::GetWornItem(int16 equipSlot) const { + ItemList::const_iterator cur,end; + cur = itemlist.begin(); + end = itemlist.end(); + for(; cur != end; ++cur) { + ServerLootItem_Struct* item = *cur; + if (item->equip_slot == equipSlot) { + return item->item_id; + } + } + + return 0; +} + +void Corpse::RemoveItem(uint16 lootslot) { + if (lootslot == 0xFFFF) + return; + + ItemList::iterator cur,end; + cur = itemlist.begin(); + end = itemlist.end(); + for (; cur != end; ++cur) { + ServerLootItem_Struct* sitem = *cur; + if (sitem->lootslot == lootslot) { + RemoveItem(sitem); + return; + } + } +} + +void Corpse::RemoveItem(ServerLootItem_Struct* item_data){ + uint8 material; + ItemList::iterator cur,end; + cur = itemlist.begin(); + end = itemlist.end(); + for(; cur != end; ++cur) { + ServerLootItem_Struct* sitem = *cur; + if (sitem == item_data) { + is_corpse_changed = true; + itemlist.erase(cur); + + material = Inventory::CalcMaterialFromSlot(sitem->equip_slot); + if(material != _MaterialInvalid) + SendWearChange(material); + + safe_delete(sitem); + + return; + } + } +} + +void Corpse::SetCash(uint32 in_copper, uint32 in_silver, uint32 in_gold, uint32 in_platinum) { + this->copper = in_copper; + this->silver = in_silver; + this->gold = in_gold; + this->platinum = in_platinum; + is_corpse_changed = true; +} + +void Corpse::RemoveCash() { + this->copper = 0; + this->silver = 0; + this->gold = 0; + this->platinum = 0; + is_corpse_changed = true; +} + +bool Corpse::IsEmpty() const { + if (copper != 0 || silver != 0 || gold != 0 || platinum != 0) + return false; + + return(itemlist.size() == 0); +} + +bool Corpse::Process() { + if (player_corpse_depop){ + return false; + } + + if (corpse_delay_timer.Check()) { + for (int i = 0; i < MAX_LOOTERS; i++){ + allowed_looters[i] = 0; + } + corpse_delay_timer.Disable(); + return true; + } + + if (corpse_graveyard_timer.Check()) { + if (zone->HasGraveyard()) { + Save(); + player_corpse_depop = true; + database.SendCharacterCorpseToGraveyard(corpse_db_id, zone->graveyard_zoneid(), + (zone->GetZoneID() == zone->graveyard_zoneid()) ? zone->GetInstanceID() : 0, zone->GetGraveyardPoint()); + corpse_graveyard_timer.Disable(); + ServerPacket* pack = new ServerPacket(ServerOP_SpawnPlayerCorpse, sizeof(SpawnPlayerCorpse_Struct)); + SpawnPlayerCorpse_Struct* spc = (SpawnPlayerCorpse_Struct*)pack->pBuffer; + spc->player_corpse_id = corpse_db_id; + spc->zone_id = zone->graveyard_zoneid(); + worldserver.SendPacket(pack); + safe_delete(pack); + LogFile->write(EQEMuLog::Debug, "Moved %s player corpse to the designated graveyard in zone %s.", this->GetName(), database.GetZoneName(zone->graveyard_zoneid())); + corpse_db_id = 0; + } + + corpse_graveyard_timer.Disable(); + return false; + } + /* + if(corpse_res_timer.Check()) { + can_rez = false; + corpse_res_timer.Disable(); + } + */ + + /* This is when a corpse hits decay timer and does checks*/ + if (corpse_decay_timer.Check()) { + /* NPC */ + if (IsNPCCorpse()){ + corpse_decay_timer.Disable(); + return false; + } + /* Client */ + if (!RuleB(Zone, EnableShadowrest)){ + Delete(); + } + else { + if (database.BuryCharacterCorpse(corpse_db_id)) { + Save(); + player_corpse_depop = true; + corpse_db_id = 0; + LogFile->write(EQEMuLog::Debug, "Tagged %s player corpse has burried.", this->GetName()); + } + else { + LogFile->write(EQEMuLog::Error, "Unable to bury %s player corpse.", this->GetName()); + return true; + } + } + corpse_decay_timer.Disable(); + return false; + } + + return true; +} + +void Corpse::SetDecayTimer(uint32 decaytime) { + if (decaytime == 0) + corpse_decay_timer.Trigger(); + else + corpse_decay_timer.Start(decaytime); +} + +bool Corpse::CanPlayerLoot(int charid) { + uint8 looters = 0; + for (int i = 0; i < MAX_LOOTERS; i++) { + if (allowed_looters[i] != 0){ + looters++; + } + + if (allowed_looters[i] == charid){ + return true; + } + } + /* If we have no looters, obviously client can loot */ + if (looters == 0){ + return true; + } + return false; +} + +void Corpse::AllowPlayerLoot(Mob *them, uint8 slot) { + if(slot >= MAX_LOOTERS) + return; + if(them == nullptr || !them->IsClient()) + return; + + allowed_looters[slot] = them->CastToClient()->CharacterID(); +} + +void Corpse::MakeLootRequestPackets(Client* client, const EQApplicationPacket* app) { + // Added 12/08. Started compressing loot struct on live. + char tmp[10]; + if(player_corpse_depop) { + SendLootReqErrorPacket(client, 0); + return; + } + + if(IsPlayerCorpse() && corpse_db_id == 0) { + // SendLootReqErrorPacket(client, 0); + client->Message(13, "Warning: Corpse's dbid = 0! Corpse will not survive zone shutdown!"); + std::cout << "Error: PlayerCorpse::MakeLootRequestPackets: dbid = 0!" << std::endl; + // return; + } + + if(is_locked && client->Admin() < 100) { + SendLootReqErrorPacket(client, 0); + client->Message(13, "Error: Corpse locked by GM."); + return; + } + + if(being_looted_by == 0) { + being_looted_by = 0xFFFFFFFF; + } + + if(this->being_looted_by != 0xFFFFFFFF) { + // lets double check.... + Entity* looter = entity_list.GetID(this->being_looted_by); + if(looter == 0) { + this->being_looted_by = 0xFFFFFFFF; + } + } + + uint8 Loot_Request_Type = 1; + bool loot_coin = false; + if(database.GetVariable("LootCoin", tmp, 9)) { loot_coin = (atoi(tmp) == 1); } + + if (this->being_looted_by != 0xFFFFFFFF && this->being_looted_by != client->GetID()) { + SendLootReqErrorPacket(client, 0); + Loot_Request_Type = 0; + } + else if (IsPlayerCorpse() && char_id == client->CharacterID()) { + Loot_Request_Type = 2; + } + else if ((IsNPCCorpse() || become_npc) && CanPlayerLoot(client->CharacterID())) { + Loot_Request_Type = 2; + } + else if (GetPlayerKillItem() == -1 && CanPlayerLoot(client->CharacterID())) { /* PVP loot all items, variable cash */ + Loot_Request_Type = 3; + } + else if (GetPlayerKillItem() == 1 && CanPlayerLoot(client->CharacterID())) { /* PVP loot 1 item, variable cash */ + Loot_Request_Type = 4; + } + else if (GetPlayerKillItem() > 1 && CanPlayerLoot(client->CharacterID())) { /* PVP loot 1 set item, variable cash */ + Loot_Request_Type = 5; + } + + if (Loot_Request_Type == 1) { + if (client->Admin() < 100 || !client->GetGM()) { + SendLootReqErrorPacket(client, 2); + } + } + + if(Loot_Request_Type >= 2 || (Loot_Request_Type == 1 && client->Admin() >= 100 && client->GetGM())) { + this->being_looted_by = client->GetID(); + EQApplicationPacket* outapp = new EQApplicationPacket(OP_MoneyOnCorpse, sizeof(moneyOnCorpseStruct)); + moneyOnCorpseStruct* d = (moneyOnCorpseStruct*) outapp->pBuffer; + + d->response = 1; + d->unknown1 = 0x42; + d->unknown2 = 0xef; + + /* Dont take the coin off if it's a gm peeking at the corpse */ + if(Loot_Request_Type == 2 || (Loot_Request_Type >= 3 && loot_coin)) { + if(!IsPlayerCorpse() && client->IsGrouped() && client->AutoSplitEnabled() && client->GetGroup()) { + d->copper = 0; + d->silver = 0; + d->gold = 0; + d->platinum = 0; + Group *cgroup = client->GetGroup(); + cgroup->SplitMoney(GetCopper(), GetSilver(), GetGold(), GetPlatinum(), client); + } + else { + d->copper = this->GetCopper(); + d->silver = this->GetSilver(); + d->gold = this->GetGold(); + d->platinum = this->GetPlatinum(); + client->AddMoneyToPP(GetCopper(), GetSilver(), GetGold(), GetPlatinum(), false); + } + + RemoveCash(); + Save(); + } + + outapp->priority = 6; + client->QueuePacket(outapp); + safe_delete(outapp); + if(Loot_Request_Type == 5) { + int pkitem = GetPlayerKillItem(); + const Item_Struct* item = database.GetItem(pkitem); + ItemInst* inst = database.CreateItem(item, item->MaxCharges); + if(inst) { + client->SendItemPacket(EmuConstants::CORPSE_BEGIN, inst, ItemPacketLoot); + safe_delete(inst); + } + else { client->Message(13, "Could not find item number %i to send!!", GetPlayerKillItem()); } + + client->QueuePacket(app); + return; + } + + int i = 0; + const Item_Struct* item = 0; + ItemList::iterator cur,end; + cur = itemlist.begin(); + end = itemlist.end(); + + int corpselootlimit = EQLimits::InventoryMapSize(MapCorpse, client->GetClientVersion()); + + for(; cur != end; ++cur) { + ServerLootItem_Struct* item_data = *cur; + item_data->lootslot = 0xFFFF; + + // Dont display the item if it's in a bag + + // Added cursor queue slots to corpse item visibility list. Nothing else should be making it to corpse. + if(!IsPlayerCorpse() || item_data->equip_slot <= MainCursor || item_data->equip_slot == MainPowerSource || Loot_Request_Type>=3 || + (item_data->equip_slot >= 8000 && item_data->equip_slot <= 8999)) { + if(i < corpselootlimit) { + item = database.GetItem(item_data->item_id); + if(client && item) { + ItemInst* inst = database.CreateItem(item, item_data->charges, item_data->aug_1, item_data->aug_2, item_data->aug_3, item_data->aug_4, item_data->aug_5); + if(inst) { + // MainGeneral1 is the corpse inventory start offset for Ti(EMu) - CORPSE_END = MainGeneral1 + MainCursor + client->SendItemPacket(i + EmuConstants::CORPSE_BEGIN, inst, ItemPacketLoot); + safe_delete(inst); + } + + item_data->lootslot = i; + } + } + + i++; + } + } + + if(IsPlayerCorpse() && (char_id == client->CharacterID() || client->GetGM())) { + if(i > corpselootlimit) { + client->Message(15, "*** This corpse contains more items than can be displayed! ***"); + client->Message(0, "Remove items and re-loot corpse to access remaining inventory."); + client->Message(0, "(%s contains %i additional %s.)", GetName(), (i - corpselootlimit), (i - corpselootlimit) == 1 ? "item" : "items"); + } + + if(IsPlayerCorpse() && i == 0 && itemlist.size() > 0) { // somehow, player corpse contains items, but client doesn't see them... + client->Message(13, "This corpse contains items that are inaccessable!"); + client->Message(15, "Contact a GM for item replacement, if necessary."); + client->Message(15, "BUGGED CORPSE [DBID: %i, Name: %s, Item Count: %i]", GetCorpseDBID(), GetName(), itemlist.size()); + + cur = itemlist.begin(); + end = itemlist.end(); + for(; cur != end; ++cur) { + ServerLootItem_Struct* item_data = *cur; + item = database.GetItem(item_data->item_id); + LogFile->write(EQEMuLog::Debug, "Corpse Looting: %s was not sent to client loot window (corpse_dbid: %i, charname: %s(%s))", item->Name, GetCorpseDBID(), client->GetName(), client->GetGM() ? "GM" : "Owner"); + client->Message(0, "Inaccessable Corpse Item: %s", item->Name); + } + } + } + } + + // Disgrace: Client seems to require that we send the packet back... + client->QueuePacket(app); + + // This is required for the 'Loot All' feature to work for SoD clients. I expect it is to tell the client that the + // server has now sent all the items on the corpse. + if(client->GetClientVersion() >= EQClientSoD) { SendLootReqErrorPacket(client, 6); } +} + +void Corpse::LootItem(Client* client, const EQApplicationPacket* app) { + /* This gets sent no matter what as a sort of ACK */ + client->QueuePacket(app); + + if (!loot_cooldown_timer.Check()) { + SendEndLootErrorPacket(client); + //unlock corpse for others + if (this->being_looted_by = client->GetID()) { + being_looted_by = 0xFFFFFFFF; + } + return; + } + + /* To prevent item loss for a player using 'Loot All' who doesn't have inventory space for all their items. */ + if (RuleB(Character, CheckCursorEmptyWhenLooting) && !client->GetInv().CursorEmpty()) { + client->Message(13, "You may not loot an item while you have an item on your cursor."); + SendEndLootErrorPacket(client); + /* Unlock corpse for others */ + if (this->being_looted_by = client->GetID()) { + being_looted_by = 0xFFFFFFFF; + } + return; + } + + LootingItem_Struct* lootitem = (LootingItem_Struct*)app->pBuffer; + + if (this->being_looted_by != client->GetID()) { + client->Message(13, "Error: Corpse::LootItem: BeingLootedBy != client"); + SendEndLootErrorPacket(client); + return; + } + if (IsPlayerCorpse() && !CanPlayerLoot(client->CharacterID()) && !become_npc && (char_id != client->CharacterID() && client->Admin() < 150)) { + client->Message(13, "Error: This is a player corpse and you dont own it."); + SendEndLootErrorPacket(client); + return; + } + if (is_locked && client->Admin() < 100) { + SendLootReqErrorPacket(client, 0); + client->Message(13, "Error: Corpse locked by GM."); + return; + } + if (IsPlayerCorpse() && (char_id != client->CharacterID()) && CanPlayerLoot(client->CharacterID()) && GetPlayerKillItem() == 0){ + client->Message(13, "Error: You cannot loot any more items from this corpse."); + SendEndLootErrorPacket(client); + being_looted_by = 0xFFFFFFFF; + return; + } + const Item_Struct* item = 0; + ItemInst *inst = 0; + ServerLootItem_Struct* item_data = nullptr, *bag_item_data[10]; + + memset(bag_item_data, 0, sizeof(bag_item_data)); + if (GetPlayerKillItem() > 1){ + item = database.GetItem(GetPlayerKillItem()); + } + else if (GetPlayerKillItem() == -1 || GetPlayerKillItem() == 1){ + item_data = GetItem(lootitem->slot_id - EmuConstants::CORPSE_BEGIN); //dont allow them to loot entire bags of items as pvp reward + } + else{ + item_data = GetItem(lootitem->slot_id - EmuConstants::CORPSE_BEGIN, bag_item_data); + } + + if (GetPlayerKillItem()<=1 && item_data != 0) { + item = database.GetItem(item_data->item_id); + } + + if (item != 0) { + if (item_data){ + inst = database.CreateItem(item, item_data ? item_data->charges : 0, item_data->aug_1, item_data->aug_2, item_data->aug_3, item_data->aug_4, item_data->aug_5); + } + else { + inst = database.CreateItem(item); + } + } + + if (client && inst) { + if (client->CheckLoreConflict(item)) { + client->Message_StringID(0, LOOT_LORE_ERROR); + SendEndLootErrorPacket(client); + being_looted_by = 0; + delete inst; + return; + } + + if (inst->IsAugmented()) { + for (int i = AUG_BEGIN; i < EmuConstants::ITEM_COMMON_SIZE; i++) { + ItemInst *itm = inst->GetAugment(i); + if (itm) { + if (client->CheckLoreConflict(itm->GetItem())) { + client->Message_StringID(0, LOOT_LORE_ERROR); + SendEndLootErrorPacket(client); + being_looted_by = 0; + delete inst; + return; + } + } + } + } + + char buf[88]; + char corpse_name[64]; + strcpy(corpse_name, corpse_name); + snprintf(buf, 87, "%d %d %s", inst->GetItem()->ID, inst->GetCharges(), EntityList::RemoveNumbers(corpse_name)); + buf[87] = '\0'; + std::vector args; + args.push_back(inst); + args.push_back(this); + parse->EventPlayer(EVENT_LOOT, client, buf, 0, &args); + parse->EventItem(EVENT_LOOT, client, inst, this, buf, 0); + + if ((RuleB(Character, EnableDiscoveredItems))) { + if (client && !client->GetGM() && !client->IsDiscovered(inst->GetItem()->ID)) + client->DiscoverItem(inst->GetItem()->ID); + } + + if (zone->adv_data) { + ServerZoneAdventureDataReply_Struct *ad = (ServerZoneAdventureDataReply_Struct*)zone->adv_data; + if (ad->type == Adventure_Collect && !IsPlayerCorpse()) { + if (ad->data_id == inst->GetItem()->ID) { + zone->DoAdventureCountIncrease(); + } + } + } + + /* First add it to the looter - this will do the bag contents too */ + if (lootitem->auto_loot) { + if (!client->AutoPutLootInInventory(*inst, true, true, bag_item_data)) + client->PutLootInInventory(MainCursor, *inst, bag_item_data); + } + else { + client->PutLootInInventory(MainCursor, *inst, bag_item_data); + } + + /* Update any tasks that have an activity to loot this item */ + if (RuleB(TaskSystem, EnableTaskSystem)) + client->UpdateTasksForItem(ActivityLoot, item->ID); + + /* Remove it from Corpse */ + if (item_data){ + /* Delete needs to be before RemoveItem because its deletes the pointer for item_data/bag_item_data */ + database.DeleteItemOffCharacterCorpse(this->corpse_db_id, item_data->equip_slot, item_data->item_id); + /* Delete Item Instance */ + RemoveItem(item_data->lootslot); + } + + /* Remove Bag Contents */ + if (item->ItemClass == ItemClassContainer && (GetPlayerKillItem() != -1 || GetPlayerKillItem() != 1)) { + for (int i = SUB_BEGIN; i < EmuConstants::ITEM_CONTAINER_SIZE; i++) { + if (bag_item_data[i]) { + /* Delete needs to be before RemoveItem because its deletes the pointer for item_data/bag_item_data */ + database.DeleteItemOffCharacterCorpse(this->corpse_db_id, bag_item_data[i]->equip_slot, bag_item_data[i]->item_id); + /* Delete Item Instance */ + RemoveItem(bag_item_data[i]); + } + } + } + + if (GetPlayerKillItem() != -1){ + SetPlayerKillItemID(0); + } + + /* Send message with item link to groups and such */ + char *link = 0, *link2 = 0; //just like a db query :-) + client->MakeItemLink(link2, inst); + MakeAnyLenString(&link, "%c" "%s" "%s" "%c", + 0x12, + link2, + item->Name, + 0x12); + safe_delete_array(link2); + + client->Message_StringID(MT_LootMessages, LOOTED_MESSAGE, link); + if(!IsPlayerCorpse()) { + Group *g = client->GetGroup(); + if(g != nullptr) { + g->GroupMessage_StringID(client, MT_LootMessages, OTHER_LOOTED_MESSAGE, client->GetName(), link); + } else { + Raid *r = client->GetRaid(); + if(r != nullptr) { + r->RaidMessage_StringID(client, MT_LootMessages, OTHER_LOOTED_MESSAGE, client->GetName(), link); + } + } + } + safe_delete_array(link); + } + else { + SendEndLootErrorPacket(client); + safe_delete(inst); + return; + } + + if (IsPlayerCorpse()){ + client->SendItemLink(inst); + } + else{ + client->SendItemLink(inst, true); + } + + safe_delete(inst); +} + +void Corpse::EndLoot(Client* client, const EQApplicationPacket* app) { + EQApplicationPacket* outapp = new EQApplicationPacket; + outapp->SetOpcode(OP_LootComplete); + outapp->size = 0; + client->QueuePacket(outapp); + safe_delete(outapp); + + this->being_looted_by = 0xFFFFFFFF; + if (this->IsEmpty()) + Delete(); + else + Save(); +} + +void Corpse::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) { + Mob::FillSpawnStruct(ns, ForWho); + + ns->spawn.max_hp = 120; + + if (IsPlayerCorpse()) + ns->spawn.NPC = 3; + else + ns->spawn.NPC = 2; +} + +void Corpse::QueryLoot(Client* to) { + int x = 0, y = 0; // x = visible items, y = total items + to->Message(0, "Coin: %ip, %ig, %is, %ic", platinum, gold, silver, copper); + + ItemList::iterator cur,end; + cur = itemlist.begin(); + end = itemlist.end(); + + int corpselootlimit = EQLimits::InventoryMapSize(MapCorpse, to->GetClientVersion()); + + for(; cur != end; ++cur) { + ServerLootItem_Struct* sitem = *cur; + + if (IsPlayerCorpse()) { + if (sitem->equip_slot >= EmuConstants::GENERAL_BAGS_BEGIN && sitem->equip_slot <= EmuConstants::CURSOR_BAG_END) + sitem->lootslot = 0xFFFF; + else + x < corpselootlimit ? sitem->lootslot = x : sitem->lootslot = 0xFFFF; + + const Item_Struct* item = database.GetItem(sitem->item_id); + + if (item) + to->Message((sitem->lootslot == 0xFFFF), "LootSlot: %i (EquipSlot: %i) Item: %s (%d), Count: %i", static_cast(sitem->lootslot), sitem->equip_slot, item->Name, item->ID, sitem->charges); + else + to->Message((sitem->lootslot == 0xFFFF), "Error: 0x%04x", sitem->item_id); + + if (sitem->lootslot != 0xFFFF) + x++; + + y++; + } + else { + sitem->lootslot=y; + const Item_Struct* item = database.GetItem(sitem->item_id); + + if (item) + to->Message(0, "LootSlot: %i Item: %s (%d), Count: %i", sitem->lootslot, item->Name, item->ID, sitem->charges); + else + to->Message(0, "Error: 0x%04x", sitem->item_id); + + y++; + } + } + + if (IsPlayerCorpse()) { + to->Message(0, "%i visible %s (%i total) on %s (DBID: %i).", x, x==1?"item":"items", y, this->GetName(), this->GetCorpseDBID()); + } + else { + to->Message(0, "%i %s on %s.", y, y==1?"item":"items", this->GetName()); + } +} + +bool Corpse::Summon(Client* client, bool spell, bool CheckDistance) { + uint32 dist2 = 10000; // pow(100, 2); + if (!spell) { + if (this->GetCharID() == client->CharacterID()) { + if (IsLocked() && client->Admin() < 100) { + client->Message(13, "That corpse is locked by a GM."); + return false; + } + if (!CheckDistance || (DistNoRootNoZ(*client) <= dist2)) { + GMMove(client->GetX(), client->GetY(), client->GetZ()); + is_corpse_changed = true; + } + else { + client->Message(0, "Corpse is too far away."); + return false; + } + } + else + { + bool consented = false; + std::list::iterator itr; + for(itr = client->consent_list.begin(); itr != client->consent_list.end(); ++itr) { + if(strcmp(this->GetOwnerName(), itr->c_str()) == 0) { + if (!CheckDistance || (DistNoRootNoZ(*client) <= dist2)) { + GMMove(client->GetX(), client->GetY(), client->GetZ()); + is_corpse_changed = true; + } + else { + client->Message(0, "Corpse is too far away."); + return false; + } + consented = true; + } + } + if(!consented) { + client->Message(0, "You do not have permission to move this corpse."); + return false; + } + } + } + else { + GMMove(client->GetX(), client->GetY(), client->GetZ()); + is_corpse_changed = true; + } + Save(); + return true; +} + +void Corpse::CompleteResurrection(){ + rez_experience = 0; + is_corpse_changed = true; + this->Save(); +} + +void Corpse::Spawn() { + EQApplicationPacket* app = new EQApplicationPacket; + this->CreateSpawnPacket(app, this); + entity_list.QueueClients(this, app); + safe_delete(app); +} + +uint32 Corpse::GetEquipment(uint8 material_slot) const { + int invslot; + + if(material_slot > EmuConstants::MATERIAL_END) { + return NO_ITEM; + } + + invslot = Inventory::CalcSlotFromMaterial(material_slot); + if(invslot == INVALID_INDEX) // GetWornItem() should be returning a NO_ITEM for any invalid index... + return NO_ITEM; + + return GetWornItem(invslot); +} + +uint32 Corpse::GetEquipmentColor(uint8 material_slot) const { + const Item_Struct *item; + + if(material_slot > EmuConstants::MATERIAL_END) { + return 0; + } + + item = database.GetItem(GetEquipment(material_slot)); + if(item != NO_ITEM) { + return item_tint[material_slot].rgb.use_tint ? + item_tint[material_slot].color : + item->Color; + } + + return 0; +} + +void Corpse::AddLooter(Mob* who) { + for (int i = 0; i < MAX_LOOTERS; i++) { + if (allowed_looters[i] == 0) { + allowed_looters[i] = who->CastToClient()->CharacterID(); + break; + } + } +} + +void Corpse::LoadPlayerCorpseDecayTime(uint32 corpse_db_id){ + if(!corpse_db_id) + return; + + uint32 active_corpse_decay_timer = database.GetCharacterCorpseDecayTimer(corpse_db_id); + if (active_corpse_decay_timer > 0 && RuleI(Character, CorpseDecayTimeMS) > (active_corpse_decay_timer * 1000)) { + corpse_decay_timer.SetTimer(RuleI(Character, CorpseDecayTimeMS) - (active_corpse_decay_timer * 1000)); + } + else { + corpse_decay_timer.SetTimer(2000); + } + if (active_corpse_decay_timer > 0 && RuleI(Zone, GraveyardTimeMS) > (active_corpse_decay_timer * 1000)) { + corpse_graveyard_timer.SetTimer(RuleI(Zone, GraveyardTimeMS) - (active_corpse_decay_timer * 1000)); + } + else { + corpse_graveyard_timer.SetTimer(3000); + } +<<<<<<< HEAD +} + +/* +** Corpse slot translations are needed until corpse database blobs are converted +** +** To account for the addition of MainPowerSource, MainGeneral9 and MainGeneral10 into +** the contiguous possessions slot enumeration, the following designations will be used: +** +** Designatiom Server Corpse Offset +** -------------------------------------------------- +** MainCharm 0 0 0 +** ... ... ... 0 +** MainWaist 20 20 0 +** MainPowerSource 21 9999 +9978 +** MainAmmo 22 21 -1 +** +** MainGeneral1 23 22 -1 +** ... ... ... -1 +** MainGeneral8 30 29 -1 +** MainGeneral9 31 9997 +9966 +** MainGeneral10 32 9998 +9966 +** +** MainCursor 33 30 -3 +** +** MainGeneral1_1 251 251 0 +** ... ... ... 0 +** MainGeneral8_10 330 330 0 +** MainGeneral9_1 331 341 +10 +** ... ... ... +10 +** MainGeneral10_10 350 360 +10 +** +** MainCursor_1 351 331 -20 +** ... ... ... -20 +** MainCursor_10 360 340 -20 +** +** (Not all slot designations are valid to all clients..see ##_constants.h files for valid slot enumerations) +*/ +int16 Corpse::ServerToCorpseSlot(int16 server_slot) +{ + return server_slot; // temporary return + + /* + switch (server_slot) + { + case MainPowerSource: + return 9999; + case MainGeneral9: + return 9997; + case MainGeneral10: + return 9998; + case MainCursor: + return 30; + case MainAmmo: + case MainGeneral1: + case MainGeneral2: + case MainGeneral3: + case MainGeneral4: + case MainGeneral5: + case MainGeneral6: + case MainGeneral7: + case MainGeneral8: + return server_slot - 1; + default: + if (server_slot >= EmuConstants::CURSOR_BAG_BEGIN && server_slot <= EmuConstants::CURSOR_BAG_END) + return server_slot - 20; + else if (server_slot >= EmuConstants::GENERAL_BAGS_END - 19 && server_slot <= EmuConstants::GENERAL_BAGS_END) + return server_slot + 10; + else + return server_slot; + } + */ +} + +int16 Corpse::CorpseToServerSlot(int16 corpse_slot) +{ + return corpse_slot; // temporary return + + /* + switch (corpse_slot) + { + case 9999: + return MainPowerSource; + case 9997: + return MainGeneral9; + case 9998: + return MainGeneral10; + case 30: + return MainCursor; + case 21: // old SLOT_AMMO + case 22: // old PERSONAL_BEGIN + case 23: + case 24: + case 25: + case 26: + case 27: + case 28: + case 29: // old PERSONAL_END + return corpse_slot + 1; + default: + if (corpse_slot >= 331 && corpse_slot <= 340) + return corpse_slot + 20; + else if (corpse_slot >= 341 && corpse_slot <= 360) + return corpse_slot - 10; + else + return corpse_slot; + } + */ +} +======= +} +>>>>>>> master diff --git a/zone/corpse.h b/zone/corpse.h index 6d1d45575..af05509eb 100644 --- a/zone/corpse.h +++ b/zone/corpse.h @@ -27,7 +27,7 @@ class NPC; #define MAX_LOOTERS 72 class Corpse : public Mob { -public: + public: static void SendEndLootErrorPacket(Client* client); static void SendLootReqErrorPacket(Client* client, uint8 response = 2); @@ -35,114 +35,118 @@ public: Corpse(NPC* in_npc, ItemList* in_itemlist, uint32 in_npctypeid, const NPCType** in_npctypedata, uint32 in_decaytime = 600000); Corpse(Client* client, int32 in_rezexp); Corpse(uint32 in_corpseid, uint32 in_charid, const char* in_charname, ItemList* in_itemlist, uint32 in_copper, uint32 in_silver, uint32 in_gold, uint32 in_plat, const xyz_heading& position, float in_size, uint8 in_gender, uint16 in_race, uint8 in_class, uint8 in_deity, uint8 in_level, uint8 in_texture, uint8 in_helmtexture, uint32 in_rezexp, bool wasAtGraveyard = false); - ~Corpse(); - static Corpse* LoadFromDBData(uint32 in_dbid, uint32 in_charid, std::string in_charname, const xyz_heading& position, std::string time_of_death, bool rezzed, bool was_at_graveyard); - //abstract virtual function implementations requird by base abstract class + ~Corpse(); + static Corpse* LoadCharacterCorpseEntity(uint32 in_dbid, uint32 in_charid, std::string in_charname, const xyz_heading& position, std::string time_of_death, bool rezzed, bool was_at_graveyard); + + /* Corpse: General */ virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, SkillUseTypes attack_skill) { return true; } virtual void Damage(Mob* from, int32 damage, uint16 spell_id, SkillUseTypes attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false) { return; } - virtual bool Attack(Mob* other, int Hand = MainPrimary, bool FromRiposte = false, - bool IsStrikethrough = true, bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr) { return false; } - virtual bool HasRaid() { return false; } - virtual bool HasGroup() { return false; } - virtual Raid* GetRaid() { return 0; } - virtual Group* GetGroup() { return 0; } - - void LoadPlayerCorpseDecayTime(uint32 dbid); - + virtual bool Attack(Mob* other, int Hand = MainPrimary, bool FromRiposte = false, bool IsStrikethrough = true, bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr) { return false; } + virtual bool HasRaid() { return false; } + virtual bool HasGroup() { return false; } + virtual Raid* GetRaid() { return 0; } + virtual Group* GetGroup() { return 0; } + inline uint32 GetCorpseDBID() { return corpse_db_id; } + inline char* GetOwnerName() { return corpse_name; } + bool IsEmpty() const; bool IsCorpse() const { return true; } bool IsPlayerCorpse() const { return is_player_corpse; } bool IsNPCCorpse() const { return !is_player_corpse; } bool IsBecomeNPCCorpse() const { return become_npc; } + virtual void DepopNPCCorpse(); + virtual void DepopPlayerCorpse(); bool Process(); bool Save(); - uint32 GetCharID() { return char_id; } - uint32 SetCharID(uint32 iCharID) { if (IsPlayerCorpse()) { return (char_id = iCharID); } return 0xFFFFFFFF; }; - uint32 GetDecayTime() { if (!corpse_decay_timer.Enabled()) return 0xFFFFFFFF; else return corpse_decay_timer.GetRemainingTime(); } - uint32 GetResTime() { if (!corpse_res_timer.Enabled()) return 0; else return corpse_res_timer.GetRemainingTime(); } - void CalcCorpseName(); - inline void Lock() { is_locked = true; } - inline void UnLock() { is_locked = false; } - inline bool IsLocked() { return is_locked; } - inline void ResetLooter() { being_looted_by = 0xFFFFFFFF; } - inline bool IsBeingLooted() { return (being_looted_by != 0xFFFFFFFF); } - inline uint32 GetDBID() { return corpse_db_id; } - inline char* GetOwnerName() { return orgname;} + uint32 GetCharID() { return char_id; } + uint32 SetCharID(uint32 iCharID) { if (IsPlayerCorpse()) { return (char_id = iCharID); } return 0xFFFFFFFF; }; + uint32 GetDecayTime() { if (!corpse_decay_timer.Enabled()) return 0xFFFFFFFF; else return corpse_decay_timer.GetRemainingTime(); } + uint32 GetRezTime() { if (!corpse_rez_timer.Enabled()) return 0; else return corpse_rez_timer.GetRemainingTime(); } + void SetDecayTimer(uint32 decay_time); - void SetDecayTimer(uint32 decaytime); - bool IsEmpty() const; - void AddItem(uint32 itemnum, uint16 charges, int16 slot = 0, uint32 aug1=0, uint32 aug2=0, uint32 aug3=0, uint32 aug4=0, uint32 aug5=0); + void Delete(); + void Bury(); + void CalcCorpseName(); + void LoadPlayerCorpseDecayTime(uint32 dbid); + + /* Corpse: Items */ uint32 GetWornItem(int16 equipSlot) const; ServerLootItem_Struct* GetItem(uint16 lootslot, ServerLootItem_Struct** bag_item_data = 0); - void RemoveItem(uint16 lootslot); - void RemoveItem(ServerLootItem_Struct* item_data); - void SetCash(uint32 in_copper, uint32 in_silver, uint32 in_gold, uint32 in_platinum); - void RemoveCash(); - void QueryLoot(Client* to); - uint32 CountItems(); - void Delete(); - void Bury(); - virtual void Depop(); - virtual void DepopCorpse(); + void SetPlayerKillItemID(int32 pk_item_id) { player_kill_item = pk_item_id; } + int32 GetPlayerKillItem() { return player_kill_item; } + void RemoveItem(uint16 lootslot); + void RemoveItem(ServerLootItem_Struct* item_data); + void AddItem(uint32 itemnum, uint16 charges, int16 slot = 0, uint32 aug1 = 0, uint32 aug2 = 0, uint32 aug3 = 0, uint32 aug4 = 0, uint32 aug5 = 0); + /* Corpse: Coin */ + void SetCash(uint32 in_copper, uint32 in_silver, uint32 in_gold, uint32 in_platinum); + void RemoveCash(); uint32 GetCopper() { return copper; } uint32 GetSilver() { return silver; } uint32 GetGold() { return gold; } uint32 GetPlatinum() { return platinum; } - void FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho); - void MakeLootRequestPackets(Client* client, const EQApplicationPacket* app); - void LootItem(Client* client, const EQApplicationPacket* app); - void EndLoot(Client* client, const EQApplicationPacket* app); - bool Summon(Client* client, bool spell, bool CheckDistance); - void CastRezz(uint16 spellid, Mob* Caster); - void CompleteRezz(); - void SetPKItem(int32 id) { player_kill_item = id; } - int32 GetPKItem() { return player_kill_item; } - bool CanMobLoot(int charid); - void AllowMobLoot(Mob *them, uint8 slot); - void AddLooter(Mob *who); + /* Corpse: Resurrection */ bool IsRezzed() { return rez; } void IsRezzed(bool in_rez) { rez = in_rez; } - void Spawn(); + void CastRezz(uint16 spellid, Mob* Caster); + void CompleteResurrection(); - char orgname[64]; - uint32 GetEquipment(uint8 material_slot) const; // returns item id + /* Corpse: Loot */ + void QueryLoot(Client* to); + void LootItem(Client* client, const EQApplicationPacket* app); + void EndLoot(Client* client, const EQApplicationPacket* app); + void MakeLootRequestPackets(Client* client, const EQApplicationPacket* app); + void AllowPlayerLoot(Mob *them, uint8 slot); + void AddLooter(Mob *who); + uint32 CountItems(); + bool CanPlayerLoot(int charid); + + inline void Lock() { is_locked = true; } + inline void UnLock() { is_locked = false; } + inline bool IsLocked() { return is_locked; } + inline void ResetLooter() { being_looted_by = 0xFFFFFFFF; } + inline bool IsBeingLooted() { return (being_looted_by != 0xFFFFFFFF); } + + /* Mob */ + void FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho); + bool Summon(Client* client, bool spell, bool CheckDistance); + void Spawn(); + + char corpse_name[64]; + uint32 GetEquipment(uint8 material_slot) const; uint32 GetEquipmentColor(uint8 material_slot) const; - inline int GetRezzExp() { return rezzexp; } - - // these are a temporary work-around until corpse inventory is removed from the database blob - static int16 ServerToCorpseSlot(int16 server_slot); // encode - static int16 CorpseToServerSlot(int16 corpse_slot); // decode + inline int GetRezExp() { return rez_experience; } protected: std::list MoveItemToCorpse(Client *client, ItemInst *item, int16 equipslot); private: - bool is_player_corpse; - bool is_corpse_changed; - bool is_locked; - int32 player_kill_item; - uint32 corpse_db_id; - uint32 char_id; - ItemList itemlist; + bool is_player_corpse; /* Determines if Player Corpse or not */ + bool is_corpse_changed; /* Determines if corpse has changed or not */ + bool is_locked; /* Determines if corpse is locked */ + int32 player_kill_item; /* Determines if Player Kill Item */ + uint32 corpse_db_id; /* Corpse Database ID (Player Corpse) */ + uint32 char_id; /* Character ID */ + ItemList itemlist; /* Internal Item list used for corpses */ uint32 copper; uint32 silver; uint32 gold; uint32 platinum; - bool player_corpse_depop; - uint32 being_looted_by; - uint32 rezzexp; + bool player_corpse_depop; /* Sets up Corpse::Process to depop the player corpse */ + uint32 being_looted_by; /* Determines what the corpse is being looted by internally for logic */ + uint32 rez_experience; /* Amount of experience that the corpse would rez for */ bool rez; - bool can_rez; + bool can_corpse_be_rezzed; /* Bool declaring whether or not a corpse can be rezzed */ bool become_npc; - int allowed_looters[MAX_LOOTERS]; // People allowed to loot the corpse, character id - Timer corpse_decay_timer; - Timer corpse_res_timer; + int allowed_looters[MAX_LOOTERS]; /* People allowed to loot the corpse, character id */ + Timer corpse_decay_timer; /* The amount of time in millseconds in which a corpse will take to decay (Depop/Poof) */ + Timer corpse_rez_timer; /* The amount of time in millseconds in which a corpse can be rezzed */ Timer corpse_delay_timer; Timer corpse_graveyard_timer; - Timer loot_cooldown_timer; + Timer loot_cooldown_timer; /* Delay between loot actions on the corpse entity */ Color_Struct item_tint[9]; + }; #endif diff --git a/zone/corpse.h.orig b/zone/corpse.h.orig new file mode 100644 index 000000000..d79d84e96 --- /dev/null +++ b/zone/corpse.h.orig @@ -0,0 +1,190 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY except by those people which sell it, which + are required to give you total support for your newly bought product; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef CORPSE_H +#define CORPSE_H + +#include "mob.h" + +class Client; +class NPC; + +#define MAX_LOOTERS 72 + +class Corpse : public Mob { + public: + + static void SendEndLootErrorPacket(Client* client); + static void SendLootReqErrorPacket(Client* client, uint8 response = 2); + + Corpse(NPC* in_npc, ItemList* in_itemlist, uint32 in_npctypeid, const NPCType** in_npctypedata, uint32 in_decaytime = 600000); + Corpse(Client* client, int32 in_rezexp); +<<<<<<< HEAD + Corpse(uint32 in_corpseid, uint32 in_charid, const char* in_charname, ItemList* in_itemlist, uint32 in_copper, uint32 in_silver, uint32 in_gold, uint32 in_plat, const xyz_heading& position, float in_size, uint8 in_gender, uint16 in_race, uint8 in_class, uint8 in_deity, uint8 in_level, uint8 in_texture, uint8 in_helmtexture, uint32 in_rezexp, bool wasAtGraveyard = false); + ~Corpse(); + static Corpse* LoadFromDBData(uint32 in_dbid, uint32 in_charid, std::string in_charname, const xyz_heading& position, std::string time_of_death, bool rezzed, bool was_at_graveyard); +======= + Corpse(uint32 in_corpseid, uint32 in_charid, const char* in_charname, ItemList* in_itemlist, uint32 in_copper, uint32 in_silver, uint32 in_gold, uint32 in_plat, float in_x, float in_y, float in_z, float in_heading, float in_size, uint8 in_gender, uint16 in_race, uint8 in_class, uint8 in_deity, uint8 in_level, uint8 in_texture, uint8 in_helmtexture, uint32 in_rezexp, bool wasAtGraveyard = false); + + ~Corpse(); + static Corpse* LoadCharacterCorpseEntity(uint32 in_dbid, uint32 in_charid, std::string in_charname, float in_x, float in_y, float in_z, float in_heading, std::string time_of_death, bool rezzed, bool was_at_graveyard); +>>>>>>> master + + /* Corpse: General */ + virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, SkillUseTypes attack_skill) { return true; } + virtual void Damage(Mob* from, int32 damage, uint16 spell_id, SkillUseTypes attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false) { return; } + virtual bool Attack(Mob* other, int Hand = MainPrimary, bool FromRiposte = false, +<<<<<<< HEAD + bool IsStrikethrough = true, bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr) { return false; } + virtual bool HasRaid() { return false; } + virtual bool HasGroup() { return false; } + virtual Raid* GetRaid() { return 0; } + virtual Group* GetGroup() { return 0; } + + void LoadPlayerCorpseDecayTime(uint32 dbid); + +======= + bool IsStrikethrough = true, bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr) { + return false; + } + virtual bool HasRaid() { return false; } + virtual bool HasGroup() { return false; } + virtual Raid* GetRaid() { return 0; } + virtual Group* GetGroup() { return 0; } + inline uint32 GetCorpseDBID() { return corpse_db_id; } + inline char* GetOwnerName() { return corpse_name; } + bool IsEmpty() const; +>>>>>>> master + bool IsCorpse() const { return true; } + bool IsPlayerCorpse() const { return is_player_corpse; } + bool IsNPCCorpse() const { return !is_player_corpse; } + bool IsBecomeNPCCorpse() const { return become_npc; } + virtual void DepopNPCCorpse(); + virtual void DepopPlayerCorpse(); + bool Process(); + bool Save(); + uint32 GetCharID() { return char_id; } + uint32 SetCharID(uint32 iCharID) { if (IsPlayerCorpse()) { return (char_id = iCharID); } return 0xFFFFFFFF; }; + uint32 GetDecayTime() { if (!corpse_decay_timer.Enabled()) return 0xFFFFFFFF; else return corpse_decay_timer.GetRemainingTime(); } + uint32 GetRezTime() { if (!corpse_rez_timer.Enabled()) return 0; else return corpse_rez_timer.GetRemainingTime(); } + void SetDecayTimer(uint32 decay_time); + + void Delete(); + void Bury(); + void CalcCorpseName(); + void LoadPlayerCorpseDecayTime(uint32 dbid); + + /* Corpse: Items */ + uint32 GetWornItem(int16 equipSlot) const; + ServerLootItem_Struct* GetItem(uint16 lootslot, ServerLootItem_Struct** bag_item_data = 0); + void SetPlayerKillItemID(int32 pk_item_id) { player_kill_item = pk_item_id; } + int32 GetPlayerKillItem() { return player_kill_item; } + void RemoveItem(uint16 lootslot); + void RemoveItem(ServerLootItem_Struct* item_data); + void AddItem(uint32 itemnum, uint16 charges, int16 slot = 0, uint32 aug1 = 0, uint32 aug2 = 0, uint32 aug3 = 0, uint32 aug4 = 0, uint32 aug5 = 0); + + /* Corpse: Coin */ + void SetCash(uint32 in_copper, uint32 in_silver, uint32 in_gold, uint32 in_platinum); + void RemoveCash(); + uint32 GetCopper() { return copper; } + uint32 GetSilver() { return silver; } + uint32 GetGold() { return gold; } + uint32 GetPlatinum() { return platinum; } + + /* Corpse: Resurrection */ + bool IsRezzed() { return rez; } + void IsRezzed(bool in_rez) { rez = in_rez; } + void CastRezz(uint16 spellid, Mob* Caster); + void CompleteResurrection(); + + /* Corpse: Loot */ + void QueryLoot(Client* to); + void LootItem(Client* client, const EQApplicationPacket* app); + void EndLoot(Client* client, const EQApplicationPacket* app); + void MakeLootRequestPackets(Client* client, const EQApplicationPacket* app); + void AllowPlayerLoot(Mob *them, uint8 slot); + void AddLooter(Mob *who); + uint32 CountItems(); + bool CanPlayerLoot(int charid); + + inline void Lock() { is_locked = true; } + inline void UnLock() { is_locked = false; } + inline bool IsLocked() { return is_locked; } + inline void ResetLooter() { being_looted_by = 0xFFFFFFFF; } + inline bool IsBeingLooted() { return (being_looted_by != 0xFFFFFFFF); } + + /* Mob */ + void FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho); + bool Summon(Client* client, bool spell, bool CheckDistance); + void Spawn(); + + char corpse_name[64]; + uint32 GetEquipment(uint8 material_slot) const; + uint32 GetEquipmentColor(uint8 material_slot) const; + inline int GetRezExp() { return rez_experience; } + +protected: + std::list MoveItemToCorpse(Client *client, ItemInst *item, int16 equipslot); + +private: +<<<<<<< HEAD + bool is_player_corpse; + bool is_corpse_changed; + bool is_locked; + int32 player_kill_item; + uint32 corpse_db_id; + uint32 char_id; + ItemList itemlist; + uint32 copper; +======= + bool is_player_corpse; /* Determines if Player Corpse or not */ + bool is_corpse_changed; /* Determines if corpse has changed or not */ + bool is_locked; /* Determines if corpse is locked */ + int32 player_kill_item; /* Determines if Player Kill Item */ + uint32 corpse_db_id; /* Corpse Database ID (Player Corpse) */ + uint32 char_id; /* Character ID */ + ItemList itemlist; /* Internal Item list used for corpses */ + uint32 copper; +>>>>>>> master + uint32 silver; + uint32 gold; + uint32 platinum; + bool player_corpse_depop; /* Sets up Corpse::Process to depop the player corpse */ + uint32 being_looted_by; /* Determines what the corpse is being looted by internally for logic */ + uint32 rez_experience; /* Amount of experience that the corpse would rez for */ + bool rez; + bool can_corpse_be_rezzed; /* Bool declaring whether or not a corpse can be rezzed */ + bool become_npc; +<<<<<<< HEAD + int allowed_looters[MAX_LOOTERS]; // People allowed to loot the corpse, character id + Timer corpse_decay_timer; + Timer corpse_res_timer; + Timer corpse_delay_timer; +======= + int allowed_looters[MAX_LOOTERS]; /* People allowed to loot the corpse, character id */ + Timer corpse_decay_timer; /* The amount of time in millseconds in which a corpse will take to decay (Depop/Poof) */ + Timer corpse_rez_timer; /* The amount of time in millseconds in which a corpse can be rezzed */ + Timer corpse_delay_timer; +>>>>>>> master + Timer corpse_graveyard_timer; + Timer loot_cooldown_timer; /* Delay between loot actions on the corpse entity */ + Color_Struct item_tint[9]; + +}; + +#endif diff --git a/zone/effects.cpp b/zone/effects.cpp index 0bface8b7..5c542d16d 100644 --- a/zone/effects.cpp +++ b/zone/effects.cpp @@ -55,30 +55,25 @@ int32 NPC::GetActSpellDamage(uint16 spell_id, int32 value, Mob* target) { else value -= target->GetFcDamageAmtIncoming(this, spell_id)/spells[spell_id].buffduration; } - - value += dmg*GetSpellFocusDMG()/100; + + value += dmg*GetSpellFocusDMG()/100; if (AI_HasSpellsEffects()){ int16 chance = 0; int ratio = 0; if (spells[spell_id].buffduration == 0) { - chance += spellbonuses.CriticalSpellChance + spellbonuses.FrenziedDevastation; - - if (chance && MakeRandomInt(1,100) <= chance){ - + + if (chance && zone->random.Roll(chance)) { ratio += spellbonuses.SpellCritDmgIncrease + spellbonuses.SpellCritDmgIncNoStack; value += (value*ratio)/100; entity_list.MessageClose_StringID(this, true, 100, MT_SpellCrits, OTHER_CRIT_BLAST, GetCleanName(), itoa(-value)); } } else { - chance += spellbonuses.CriticalDoTChance; - - if (chance && MakeRandomInt(1,100) <= chance){ - + if (chance && zone->random.Roll(chance)) { ratio += spellbonuses.DotCritDmgIncrease; value += (value*ratio)/100; } @@ -119,14 +114,14 @@ int32 Client::GetActSpellDamage(uint16 spell_id, int32 value, Mob* target) { if (spell_id == SPELL_IMP_HARM_TOUCH && (GetAA(aaSpellCastingFury) > 0) && (GetAA(aaUnholyTouch) > 0)) chance = 100; - if (MakeRandomInt(1,100) <= chance){ + if (zone->random.Roll(chance)) { Critical = true; ratio += itembonuses.SpellCritDmgIncrease + spellbonuses.SpellCritDmgIncrease + aabonuses.SpellCritDmgIncrease; ratio += itembonuses.SpellCritDmgIncNoStack + spellbonuses.SpellCritDmgIncNoStack + aabonuses.SpellCritDmgIncNoStack; } - else if (GetClass() == WIZARD && (GetLevel() >= RuleI(Spells, WizCritLevel)) && (MakeRandomInt(1,100) <= RuleI(Spells, WizCritChance))) { - ratio += MakeRandomInt(20,70); //Wizard innate critical chance is calculated seperately from spell effect and is not a set ratio. (20-70 is parse confirmed) + else if (GetClass() == WIZARD && (GetLevel() >= RuleI(Spells, WizCritLevel)) && (zone->random.Roll(RuleI(Spells, WizCritChance)))) { + ratio += zone->random.Int(20,70); //Wizard innate critical chance is calculated seperately from spell effect and is not a set ratio. (20-70 is parse confirmed) Critical = true; } @@ -193,22 +188,16 @@ int32 Client::GetActDoTDamage(uint16 spell_id, int32 value, Mob* target) { if (spellbonuses.CriticalDotDecay) chance += GetDecayEffectValue(spell_id, SE_CriticalDotDecay); - + value_BaseEffect = value + (value*GetFocusEffect(focusFcBaseEffects, spell_id)/100); - if (chance > 0 && (MakeRandomInt(1, 100) <= chance)) { - + if (chance > 0 && (zone->random.Roll(chance))) { int32 ratio = 200; ratio += itembonuses.DotCritDmgIncrease + spellbonuses.DotCritDmgIncrease + aabonuses.DotCritDmgIncrease; - - value = value_BaseEffect*ratio/100; - - value += int(value_BaseEffect*GetFocusEffect(focusImprovedDamage, spell_id)/100)*ratio/100; - + value = value_BaseEffect*ratio/100; + value += int(value_BaseEffect*GetFocusEffect(focusImprovedDamage, spell_id)/100)*ratio/100; value += int(value_BaseEffect*GetFocusEffect(focusFcDamagePctCrit, spell_id)/100)*ratio/100; - - value += int(value_BaseEffect*target->GetVulnerability(this, spell_id, 0)/100)*ratio/100; - + value += int(value_BaseEffect*target->GetVulnerability(this, spell_id, 0)/100)*ratio/100; extra_dmg = target->GetFcDamageAmtIncoming(this, spell_id) + int(GetFocusEffect(focusFcDamageAmtCrit, spell_id)*ratio/100) + GetFocusEffect(focusFcDamageAmt, spell_id); @@ -216,7 +205,7 @@ int32 Client::GetActDoTDamage(uint16 spell_id, int32 value, Mob* target) { if (extra_dmg) { int duration = CalcBuffDuration(this, this, spell_id); if (duration > 0) - extra_dmg /= duration; + extra_dmg /= duration; } value -= extra_dmg; @@ -224,25 +213,20 @@ int32 Client::GetActDoTDamage(uint16 spell_id, int32 value, Mob* target) { return value; } - value = value_BaseEffect; - - value += value_BaseEffect*GetFocusEffect(focusImprovedDamage, spell_id)/100; - - value += value_BaseEffect*GetFocusEffect(focusFcDamagePctCrit, spell_id)/100; - - value += value_BaseEffect*target->GetVulnerability(this, spell_id, 0)/100; - - extra_dmg = target->GetFcDamageAmtIncoming(this, spell_id) + + value += value_BaseEffect*GetFocusEffect(focusImprovedDamage, spell_id)/100; + value += value_BaseEffect*GetFocusEffect(focusFcDamagePctCrit, spell_id)/100; + value += value_BaseEffect*target->GetVulnerability(this, spell_id, 0)/100; + extra_dmg = target->GetFcDamageAmtIncoming(this, spell_id) + GetFocusEffect(focusFcDamageAmtCrit, spell_id) + - GetFocusEffect(focusFcDamageAmt, spell_id); + GetFocusEffect(focusFcDamageAmt, spell_id); if (extra_dmg) { int duration = CalcBuffDuration(this, this, spell_id); if (duration > 0) - extra_dmg /= duration; - } - + extra_dmg /= duration; + } + value -= extra_dmg; return value; @@ -275,28 +259,26 @@ int32 NPC::GetActSpellHealing(uint16 spell_id, int32 value, Mob* target) { //Scale all NPC spell healing via SetSpellFocusHeal(value) - value += value*GetSpellFocusHeal()/100; + value += value*GetSpellFocusHeal()/100; if (target) { - value += target->GetFocusIncoming(focusFcHealAmtIncoming, SE_FcHealAmtIncoming, this, spell_id); + value += target->GetFocusIncoming(focusFcHealAmtIncoming, SE_FcHealAmtIncoming, this, spell_id); value += value*target->GetHealRate(spell_id, this)/100; } //Allow for critical heal chance if NPC is loading spell effect bonuses. if (AI_HasSpellsEffects()){ - if(spells[spell_id].buffduration < 1) { - - if(spellbonuses.CriticalHealChance && (MakeRandomInt(0,99) < spellbonuses.CriticalHealChance)) { - value = value*2; + if(spellbonuses.CriticalHealChance && (zone->random.Roll(spellbonuses.CriticalHealChance))) { + value = value*2; entity_list.MessageClose_StringID(this, true, 100, MT_SpellCrits, OTHER_CRIT_HEAL, GetCleanName(), itoa(value)); } } - else if(spellbonuses.CriticalHealOverTime && (MakeRandomInt(0,99) < spellbonuses.CriticalHealOverTime)) { - value = value*2; + else if(spellbonuses.CriticalHealOverTime && (zone->random.Roll(spellbonuses.CriticalHealOverTime))) { + value = value*2; } } - + return value; } @@ -326,7 +308,7 @@ int32 Client::GetActSpellHealing(uint16 spell_id, int32 value, Mob* target) { if (spellbonuses.CriticalHealDecay) chance += GetDecayEffectValue(spell_id, SE_CriticalHealDecay); - if(chance && (MakeRandomInt(0,99) < chance)) { + if(chance && (zone->random.Roll(chance))) { Critical = true; modifier = 2; //At present time no critical heal amount modifier SPA exists. } @@ -360,7 +342,7 @@ int32 Client::GetActSpellHealing(uint16 spell_id, int32 value, Mob* target) { if (spellbonuses.CriticalRegenDecay) chance += GetDecayEffectValue(spell_id, SE_CriticalRegenDecay); - if(chance && (MakeRandomInt(0,99) < chance)) + if(chance && zone->random.Roll(chance)) return (value * 2); } @@ -374,12 +356,12 @@ int32 Client::GetActSpellCost(uint16 spell_id, int32 cost) int16 FrenziedDevastation = itembonuses.FrenziedDevastation + spellbonuses.FrenziedDevastation + aabonuses.FrenziedDevastation; if (FrenziedDevastation && IsPureNukeSpell(spell_id)) - cost *= 2; - + cost *= 2; + // Formula = Unknown exact, based off a random percent chance up to mana cost(after focuses) of the cast spell if(this->itembonuses.Clairvoyance && spells[spell_id].classes[(GetClass()%16) - 1] >= GetLevel() - 5) { - int16 mana_back = this->itembonuses.Clairvoyance * MakeRandomInt(1, 100) / 100; + int16 mana_back = this->itembonuses.Clairvoyance * zone->random.Int(1, 100) / 100; // Doesnt generate mana, so best case is a free spell if(mana_back > cost) mana_back = cost; @@ -392,7 +374,7 @@ int32 Client::GetActSpellCost(uint16 spell_id, int32 cost) // WildcardX float PercentManaReduction = 0; float SpecializeSkill = GetSpecializeSkillValue(spell_id); - int SuccessChance = MakeRandomInt(0, 100); + int SuccessChance = zone->random.Int(0, 100); float bonus = 1.0; switch(GetAA(aaSpellCastingMastery)) @@ -444,7 +426,7 @@ int32 Client::GetActSpellCost(uint16 spell_id, int32 cost) if(focus_redux > 0) { - PercentManaReduction += MakeRandomFloat(1, (double)focus_redux); + PercentManaReduction += zone->random.Real(1, (double)focus_redux); } cost -= (cost * (PercentManaReduction / 100)); diff --git a/zone/embparser_api.cpp b/zone/embparser_api.cpp index 034c9e80e..605ad2403 100644 --- a/zone/embparser_api.cpp +++ b/zone/embparser_api.cpp @@ -1515,7 +1515,7 @@ XS(XS__ChooseRandom) if (items < 1) Perl_croak(aTHX_ "Usage: ChooseRandom(... list ...)"); - int index = MakeRandomInt(0, items-1); + int index = zone->random.Int(0, items-1); SV *tmp = ST(0); ST(0) = ST(index); diff --git a/zone/entity.cpp b/zone/entity.cpp index b084235eb..0f251724e 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -19,7 +19,6 @@ #include #include #include -#include #include #include @@ -30,19 +29,16 @@ #include "../common/unix.h" #endif -#include "net.h" -#include "masterentity.h" -#include "worldserver.h" -#include "../common/guilds.h" -#include "../common/packet_dump.h" -#include "../common/packet_functions.h" -#include "petitions.h" -#include "../common/spdat.h" #include "../common/features.h" -#include "string_ids.h" +#include "../common/guilds.h" +#include "../common/spdat.h" #include "guild_mgr.h" -#include "raids.h" +#include "net.h" +#include "petitions.h" #include "quest_parser_collection.h" +#include "raids.h" +#include "string_ids.h" +#include "worldserver.h" #ifdef _WINDOWS #define snprintf _snprintf @@ -632,7 +628,7 @@ void EntityList::AddCorpse(Corpse *corpse, uint32 in_id) void EntityList::AddNPC(NPC *npc, bool SendSpawnPacket, bool dontqueue) { npc->SetID(GetFreeID()); - npc->SetMerchantProbability((uint8) MakeRandomInt(0, 99)); + npc->SetMerchantProbability((uint8) zone->random.Int(0, 99)); parse->EventNPC(EVENT_SPAWN, npc, nullptr, "", 0); uint16 emoteid = npc->GetEmoteID(); @@ -662,10 +658,12 @@ void EntityList::AddNPC(NPC *npc, bool SendSpawnPacket, bool dontqueue) void EntityList::AddMerc(Merc *merc, bool SendSpawnPacket, bool dontqueue) { - if (merc) { + if (merc) + { merc->SetID(GetFreeID()); - if (SendSpawnPacket) { + if (SendSpawnPacket) + { if (dontqueue) { // Send immediately EQApplicationPacket *outapp = new EQApplicationPacket(); @@ -677,12 +675,10 @@ void EntityList::AddMerc(Merc *merc, bool SendSpawnPacket, bool dontqueue) // Queue the packet NewSpawn_Struct *ns = new NewSpawn_Struct; memset(ns, 0, sizeof(NewSpawn_Struct)); - merc->FillSpawnStruct(ns, merc); + merc->FillSpawnStruct(ns, 0); AddToSpawnQueue(merc->GetID(), &ns); safe_delete(ns); } - - //parse->EventMERC(EVENT_SPAWN, merc, nullptr, "", 0); } merc_list.insert(std::pair(merc->GetID(), merc)); @@ -1573,7 +1569,7 @@ Client *EntityList::GetRandomClient(const xyz_location& location, float Distance if (ClientsInRange.empty()) return nullptr; - return ClientsInRange[MakeRandomInt(0, ClientsInRange.size() - 1)]; + return ClientsInRange[zone->random.Int(0, ClientsInRange.size() - 1)]; } Corpse *EntityList::GetCorpseByOwner(Client *client) @@ -1605,7 +1601,7 @@ Corpse *EntityList::GetCorpseByDBID(uint32 dbid) { auto it = corpse_list.begin(); while (it != corpse_list.end()) { - if (it->second->GetDBID() == dbid) + if (it->second->GetCorpseDBID() == dbid) return it->second; ++it; } @@ -1659,7 +1655,7 @@ void EntityList::RemoveCorpseByDBID(uint32 dbid) { auto it = corpse_list.begin(); while (it != corpse_list.end()) { - if (it->second->GetDBID() == dbid) { + if (it->second->GetCorpseDBID() == dbid) { safe_delete(it->second); free_ids.push(it->first); it = corpse_list.erase(it); @@ -1676,9 +1672,9 @@ int EntityList::RezzAllCorpsesByCharID(uint32 charid) auto it = corpse_list.begin(); while (it != corpse_list.end()) { if (it->second->GetCharID() == charid) { - RezzExp += it->second->GetRezzExp(); + RezzExp += it->second->GetRezExp(); it->second->IsRezzed(true); - it->second->CompleteRezz(); + it->second->CompleteResurrection(); } ++it; } @@ -2654,7 +2650,7 @@ int32 EntityList::DeleteNPCCorpses() auto it = corpse_list.begin(); while (it != corpse_list.end()) { if (it->second->IsNPCCorpse()) { - it->second->Depop(); + it->second->DepopNPCCorpse(); x++; } ++it; @@ -2893,7 +2889,7 @@ void EntityList::ClearFeignAggro(Mob *targ) it->second->RemoveFromHateList(targ); if (targ->IsClient()) { - if (it->second->GetLevel() >= 35 && MakeRandomInt(1, 100) <= 60) + if (it->second->GetLevel() >= 35 && zone->random.Roll(60)) it->second->AddFeignMemory(targ->CastToClient()); else targ->CastToClient()->RemoveXTarget(it->second, false); @@ -3640,7 +3636,8 @@ void EntityList::AddTempPetsToHateList(Mob *owner, Mob* other, bool bFrenzy) NPC* n = it->second; if (n->GetSwarmInfo()) { if (n->GetSwarmInfo()->owner_id == owner->GetID()) { - n->CastToNPC()->hate_list.Add(other, 0, 0, bFrenzy); + if (!n->GetSpecialAbility(IMMUNE_AGGRO)) + n->hate_list.Add(other, 0, 0, bFrenzy); } } ++it; @@ -4499,7 +4496,7 @@ void EntityList::AddLootToNPCS(uint32 item_id, uint32 count) selection.push_back(j); while (selection.size() > 0 && count > 0) { - int k = MakeRandomInt(0, selection.size() - 1); + int k = zone->random.Int(0, selection.size() - 1); counts[selection[k]]++; count--; selection.erase(selection.begin() + k); @@ -4681,6 +4678,6 @@ Mob *EntityList::GetTargetForVirus(Mob *spreader, int range) if(TargetsInRange.size() == 0) return nullptr; - return TargetsInRange[MakeRandomInt(0, TargetsInRange.size() - 1)]; + return TargetsInRange[zone->random.Int(0, TargetsInRange.size() - 1)]; } diff --git a/zone/fearpath.cpp b/zone/fearpath.cpp index 8addf3f9a..a85b21999 100644 --- a/zone/fearpath.cpp +++ b/zone/fearpath.cpp @@ -184,8 +184,8 @@ void Mob::CalculateNewFearpoint() { int ran = 250 - (loop*2); loop++; - ranx = GetX()+MakeRandomInt(0, ran-1)-MakeRandomInt(0, ran-1); - rany = GetY()+MakeRandomInt(0, ran-1)-MakeRandomInt(0, ran-1); + ranx = GetX()+zone->random.Int(0, ran-1)-zone->random.Int(0, ran-1); + rany = GetY()+zone->random.Int(0, ran-1)-zone->random.Int(0, ran-1); ranz = FindGroundZ(ranx,rany); if (ranz == -999999) continue; diff --git a/zone/forage.cpp b/zone/forage.cpp index 2760a779c..c808fc418 100644 --- a/zone/forage.cpp +++ b/zone/forage.cpp @@ -15,75 +15,33 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + #include "../common/debug.h" -#include -#include -#include - -#ifdef _WINDOWS -#define snprintf _snprintf -#endif - -#include "forage.h" -#include "entity.h" -#include "masterentity.h" -#include "npc.h" -#include "water_map.h" -#include "titles.h" -#include "string_ids.h" #include "../common/misc_functions.h" -#include "../common/string_util.h" #include "../common/rulesys.h" +#include "../common/string_util.h" +#include "entity.h" +#include "forage.h" +#include "npc.h" +#include "quest_parser_collection.h" +#include "string_ids.h" +#include "titles.h" +#include "water_map.h" #include "zonedb.h" + +#include + #ifdef _WINDOWS #define snprintf _snprintf #endif -#include "quest_parser_collection.h" +struct NPCType; //max number of items which can be in the foraging table //for a given zone. #define FORAGE_ITEM_LIMIT 50 -/* - -The fishing and foraging need some work... -foraging currently gives each item an equal chance of dropping -fishing gives items which come in last from the select a very -very low chance of dropping. - - -Schema: -CREATE TABLE forage ( - id int(11) NOT NULL auto_increment, - zoneid int(4) NOT NULL default '0', - Itemid int(11) NOT NULL default '0', - level smallint(6) NOT NULL default '0', - chance smallint(6) NOT NULL default '0', - PRIMARY KEY (id) -) TYPE=MyISAM; - -old table upgrade: -alter table forage add chance smallint(6) NOT NULL default '0'; -update forage set chance=100; - - -CREATE TABLE fishing ( - id int(11) NOT NULL auto_increment, - zoneid int(4) NOT NULL default '0', - Itemid int(11) NOT NULL default '0', - skill_level smallint(6) NOT NULL default '0', - chance smallint(6) NOT NULL default '0', - npc_id int NOT NULL default 0, - npc_chance int NOT NULL default 0, - PRIMARY KEY (id) -) TYPE=MyISAM; - - -*/ - -// This allows EqEmu to have zone specific foraging - BoB uint32 ZoneDatabase::GetZoneForage(uint32 ZoneID, uint8 skill) { uint32 item[FORAGE_ITEM_LIMIT]; @@ -125,7 +83,7 @@ uint32 ZoneDatabase::GetZoneForage(uint32 ZoneID, uint8 skill) { ret = 0; - uint32 rindex = MakeRandomInt(1, chancepool); + uint32 rindex = zone->random.Int(1, chancepool); for(int i = 0; i < index; i++) { if(rindex <= chance[i]) { @@ -178,7 +136,7 @@ uint32 ZoneDatabase::GetZoneFishing(uint32 ZoneID, uint8 skill, uint32 &npc_id, if (index <= 0) return 0; - uint32 random = MakeRandomInt(1, chancepool); + uint32 random = zone->random.Int(1, chancepool); for (int i = 0; i < index; i++) { if (random > chance[i]) @@ -301,18 +259,18 @@ void Client::GoFish() fishing_skill = 100+((fishing_skill-100)/2); } - if (MakeRandomInt(0,175) < fishing_skill) { + if (zone->random.Int(0,175) < fishing_skill) { uint32 food_id = 0; //25% chance to fish an item. - if (MakeRandomInt(0, 399) <= fishing_skill ) { + if (zone->random.Int(0, 399) <= fishing_skill ) { uint32 npc_id = 0; uint8 npc_chance = 0; food_id = database.GetZoneFishing(m_pp.zone_id, fishing_skill, npc_id, npc_chance); //check for add NPC if(npc_chance > 0 && npc_id) { - if(npc_chance < MakeRandomInt(0, 99)) { + if(npc_chance < zone->random.Int(0, 99)) { const NPCType* tmp = database.GetNPCType(npc_id); if(tmp != nullptr) { auto positionNPC = GetPosition(); @@ -334,7 +292,7 @@ void Client::GoFish() DeleteItemInInventory(bslot, 1, true); //do we need client update? if(food_id == 0) { - int index = MakeRandomInt(0, MAX_COMMON_FISH_IDS-1); + int index = zone->random.Int(0, MAX_COMMON_FISH_IDS-1); food_id = common_fish_ids[index]; } @@ -369,11 +327,11 @@ void Client::GoFish() else { //chance to use bait when you dont catch anything... - if (MakeRandomInt(0, 4) == 1) { + if (zone->random.Int(0, 4) == 1) { DeleteItemInInventory(bslot, 1, true); //do we need client update? Message_StringID(MT_Skills, FISHING_LOST_BAIT); //You lost your bait! } else { - if (MakeRandomInt(0, 15) == 1) //give about a 1 in 15 chance to spill your beer. we could make this a rule, but it doesn't really seem worth it + if (zone->random.Int(0, 15) == 1) //give about a 1 in 15 chance to spill your beer. we could make this a rule, but it doesn't really seem worth it //TODO: check for & consume an alcoholic beverage from inventory when this triggers, and set it as a rule that's disabled by default Message_StringID(MT_Skills, FISHING_SPILL_BEER); //You spill your beer while bringing in your line. else @@ -386,7 +344,7 @@ void Client::GoFish() //chance to break fishing pole... //this is potentially exploitable in that they can fish //and then swap out items in primary slot... too lazy to fix right now - if (MakeRandomInt(0, 49) == 1) { + if (zone->random.Int(0, 49) == 1) { Message_StringID(MT_Skills, FISHING_POLE_BROKE); //Your fishing pole broke! DeleteItemInInventory(MainPrimary, 0, true); } @@ -415,18 +373,18 @@ void Client::ForageItem(bool guarantee) { }; // these may need to be fine tuned, I am just guessing here - if (guarantee || MakeRandomInt(0,199) < skill_level) { + if (guarantee || zone->random.Int(0,199) < skill_level) { uint32 foragedfood = 0; uint32 stringid = FORAGE_NOEAT; - if (MakeRandomInt(0,99) <= 25) { + if (zone->random.Roll(25)) { foragedfood = database.GetZoneForage(m_pp.zone_id, skill_level); } //not an else in case theres no DB food if(foragedfood == 0) { uint8 index = 0; - index = MakeRandomInt(0, MAX_COMMON_FOOD_IDS-1); + index = zone->random.Int(0, MAX_COMMON_FOOD_IDS-1); foragedfood = common_food_ids[index]; } @@ -483,7 +441,7 @@ void Client::ForageItem(bool guarantee) { } int ChanceSecondForage = aabonuses.ForageAdditionalItems + itembonuses.ForageAdditionalItems + spellbonuses.ForageAdditionalItems; - if(!guarantee && MakeRandomInt(0,99) < ChanceSecondForage) { + if(!guarantee && zone->random.Roll(ChanceSecondForage)) { Message_StringID(MT_Skills, FORAGE_MASTERY); ForageItem(true); } diff --git a/zone/groups.cpp b/zone/groups.cpp index 2e7fb3cfe..3180e51f1 100644 --- a/zone/groups.cpp +++ b/zone/groups.cpp @@ -592,12 +592,15 @@ bool Group::DelMemberOOZ(const char *Name) { bool Group::DelMember(Mob* oldmember,bool ignoresender) { - if (oldmember == nullptr){ + if (oldmember == nullptr) + { return false; } - for (uint32 i = 0; i < MAX_GROUP_MEMBERS; i++) { - if (members[i] == oldmember) { + for (uint32 i = 0; i < MAX_GROUP_MEMBERS; i++) + { + if (members[i] == oldmember) + { members[i] = nullptr; membername[i][0] = '\0'; memset(membername[i],0,64); @@ -606,16 +609,6 @@ bool Group::DelMember(Mob* oldmember,bool ignoresender) } } - //handle leader quitting group gracefully - if (oldmember == GetLeader() && GroupCount() >= 2) { - for(uint32 nl = 0; nl < MAX_GROUP_MEMBERS; nl++) { - if(members[nl]) { - ChangeLeader(members[nl]); - break; - } - } - } - /* This may seem pointless but the case above does not cover the following situation: * Group has Leader a, member b, member c * b and c are out of zone @@ -624,10 +617,33 @@ bool Group::DelMember(Mob* oldmember,bool ignoresender) * a is still "leader" from GetLeader()'s perspective and will crash the zone when we DelMember(b) * Ultimately we should think up a better solution to this. */ - if(oldmember == GetLeader()) { + if(oldmember == GetLeader()) + { SetLeader(nullptr); } + //handle leader quitting group gracefully + if (oldmember == GetLeader() && GroupCount() >= 2) + { + for(uint32 nl = 0; nl < MAX_GROUP_MEMBERS; nl++) + { + if(members[nl]) + { + if (members[nl]->IsClient()) + { + ChangeLeader(members[nl]); + break; + } + } + } + } + + if (GetLeader() == nullptr) + { + DisbandGroup(); + return true; + } + ServerPacket* pack = new ServerPacket(ServerOP_GroupLeave, sizeof(ServerGroupLeave_Struct)); ServerGroupLeave_Struct* gl = (ServerGroupLeave_Struct*)pack->pBuffer; gl->gid = GetID(); diff --git a/zone/guild.cpp b/zone/guild.cpp index 5f8378d2b..58b0f006e 100644 --- a/zone/guild.cpp +++ b/zone/guild.cpp @@ -15,23 +15,13 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "../common/debug.h" -#include "masterentity.h" -#include "worldserver.h" -#include "net.h" + #include "../common/database.h" -#include "../common/spdat.h" -#include "../common/packet_dump.h" -#include "../common/packet_functions.h" -#include "petitions.h" -#include "../common/serverinfo.h" -#include "../common/zone_numbers.h" -#include "../common/moremath.h" #include "../common/guilds.h" #include "../common/string_util.h" + #include "guild_mgr.h" -#include "string_ids.h" -#include "npc_ai.h" +#include "worldserver.h" extern WorldServer worldserver; diff --git a/zone/hate_list.cpp b/zone/hate_list.cpp index 5b028f0df..bb6e93098 100644 --- a/zone/hate_list.cpp +++ b/zone/hate_list.cpp @@ -487,7 +487,7 @@ Mob *HateList::GetRandom() } auto iterator = list.begin(); - int random = MakeRandomInt(0, count - 1); + int random = zone->random.Int(0, count - 1); for (int i = 0; i < random; i++) ++iterator; diff --git a/zone/inventory.cpp b/zone/inventory.cpp index 76b077e2d..6b7a0b297 100644 --- a/zone/inventory.cpp +++ b/zone/inventory.cpp @@ -15,24 +15,14 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + #include "../common/debug.h" -#include "masterentity.h" -#include "worldserver.h" -#include "net.h" -#include "zonedb.h" -#include "../common/spdat.h" -#include "../common/packet_dump.h" -#include "../common/packet_functions.h" -#include "petitions.h" -#include "../common/serverinfo.h" -#include "../common/zone_numbers.h" -#include "../common/moremath.h" -#include "../common/guilds.h" #include "../common/logsys.h" #include "../common/string_util.h" -#include "string_ids.h" -#include "npc_ai.h" #include "quest_parser_collection.h" +#include "worldserver.h" +#include "zonedb.h" + extern WorldServer worldserver; // @merth: this needs to be touched up @@ -199,7 +189,7 @@ bool Client::CheckLoreConflict(const Item_Struct* item) { return (m_inv.HasItemByLoreGroup(item->LoreGroup, ~invWhereSharedBank) != INVALID_INDEX); } -bool Client::SummonItem(uint32 item_id, int16 charges, uint32 aug1, uint32 aug2, uint32 aug3, uint32 aug4, uint32 aug5, bool attuned, uint16 to_slot) { +bool Client::SummonItem(uint32 item_id, int16 charges, uint32 aug1, uint32 aug2, uint32 aug3, uint32 aug4, uint32 aug5, bool attuned, uint16 to_slot, uint32 ornament_icon, uint32 ornament_idfile) { this->EVENT_ITEM_ScriptStopReturn(); // TODO: update calling methods and script apis to handle a failure return @@ -557,6 +547,11 @@ bool Client::SummonItem(uint32 item_id, int16 charges, uint32 aug1, uint32 aug2, // attune item if(attuned && inst->GetItem()->Attuneable) inst->SetInstNoDrop(true); + + if(ornament_icon > 0 && ornament_idfile > 0) { + inst->SetOrnamentIcon(ornament_icon); + inst->SetOrnamentationIDFile(ornament_idfile); + } // check to see if item is usable in requested slot if(enforceusable && (((to_slot >= MainCharm) && (to_slot <= MainAmmo)) || (to_slot == MainPowerSource))) { diff --git a/zone/loottables.cpp b/zone/loottables.cpp index 0c66778bd..c5fda890e 100644 --- a/zone/loottables.cpp +++ b/zone/loottables.cpp @@ -22,6 +22,7 @@ #include "npc.h" #include "masterentity.h" #include "zonedb.h" +#include "../common/loottable.h" #include "../common/misc_functions.h" #ifdef _WINDOWS #define snprintf _snprintf @@ -48,15 +49,15 @@ void ZoneDatabase::AddLootTableToNPC(NPC* npc,uint32 loottable_id, ItemList* ite if (lts->mincash == lts->maxcash) cash = lts->mincash; else - cash = MakeRandomInt(lts->mincash, lts->maxcash); + cash = zone->random.Int(lts->mincash, lts->maxcash); if (cash != 0) { if (lts->avgcoin != 0) { //this is some crazy ass stuff... and makes very little sense... dont use it, k? uint32 mincoin = (uint32) (lts->avgcoin * 0.75 + 1); uint32 maxcoin = (uint32) (lts->avgcoin * 1.25 + 1); - *copper = MakeRandomInt(mincoin, maxcoin); - *silver = MakeRandomInt(mincoin, maxcoin); - *gold = MakeRandomInt(mincoin, maxcoin); + *copper = zone->random.Int(mincoin, maxcoin); + *silver = zone->random.Int(mincoin, maxcoin); + *gold = zone->random.Int(mincoin, maxcoin); if(*copper > cash) { *copper = cash; } cash -= *copper; if(*silver>(cash/10)) { *silver = (cash/10); } @@ -91,7 +92,7 @@ void ZoneDatabase::AddLootTableToNPC(NPC* npc,uint32 loottable_id, ItemList* ite float drop_chance = 0.0f; if(ltchance > 0.0 && ltchance < 100.0) { - drop_chance = MakeRandomFloat(0.0, 100.0); + drop_chance = zone->random.Real(0.0, 100.0); } if (ltchance != 0.0 && (ltchance == 100.0 || drop_chance < ltchance)) { @@ -117,7 +118,7 @@ void ZoneDatabase::AddLootDropToNPC(NPC* npc,uint32 lootdrop_id, ItemList* iteml uint8 limit = 0; // Start at a random point in itemlist. - uint32 item = MakeRandomInt(0, lds->NumEntries-1); + uint32 item = zone->random.Int(0, lds->NumEntries-1); // Main loop. for (uint32 i=0; iNumEntries;) { @@ -136,7 +137,7 @@ void ZoneDatabase::AddLootDropToNPC(NPC* npc,uint32 lootdrop_id, ItemList* iteml float drop_chance = 0.0; if(thischance != 100.0) - drop_chance = MakeRandomFloat(0.0, 100.0); + drop_chance = zone->random.Real(0.0, 100.0); #if EQDEBUG>=11 LogFile->write(EQEMuLog::Debug, "Drop chance for npc: %s, this chance:%f, drop roll:%f", npc->GetName(), thischance, drop_chance); @@ -281,7 +282,7 @@ void NPC::AddLootDrop(const Item_Struct *item2, ItemList* itemlist, int16 charge eslot = MaterialPrimary; } else if (foundslot == MainSecondary - && (GetOwner() != nullptr || (GetLevel() >= 13 && MakeRandomInt(0,99) < NPC_DW_CHANCE) || (item2->Damage==0)) && + && (GetOwner() != nullptr || (GetLevel() >= 13 && zone->random.Roll(NPC_DW_CHANCE)) || (item2->Damage==0)) && (item2->ItemType == ItemType1HSlash || item2->ItemType == ItemType1HBlunt || item2->ItemType == ItemTypeShield || item2->ItemType == ItemType1HPiercing)) { diff --git a/zone/lua_corpse.cpp b/zone/lua_corpse.cpp index beada7e9a..789555a54 100644 --- a/zone/lua_corpse.cpp +++ b/zone/lua_corpse.cpp @@ -39,7 +39,7 @@ void Lua_Corpse::ResetLooter() { uint32 Lua_Corpse::GetDBID() { Lua_Safe_Call_Int(); - return self->GetDBID(); + return self->GetCorpseDBID(); } bool Lua_Corpse::IsRezzed() { @@ -114,12 +114,12 @@ void Lua_Corpse::SetDecayTimer(uint32 decaytime) { bool Lua_Corpse::CanMobLoot(int charid) { Lua_Safe_Call_Bool(); - return self->CanMobLoot(charid); + return self->CanPlayerLoot(charid); } void Lua_Corpse::AllowMobLoot(Lua_Mob them, uint8 slot) { Lua_Safe_Call_Void(); - self->AllowMobLoot(them, slot); + self->AllowPlayerLoot(them, slot); } bool Lua_Corpse::Summon(Lua_Client client, bool spell, bool checkdistance) { diff --git a/zone/merc.cpp b/zone/merc.cpp index aa8bb98c1..4041b18b1 100644 --- a/zone/merc.cpp +++ b/zone/merc.cpp @@ -62,7 +62,7 @@ Merc::Merc(const NPCType* d, float x, float y, float z, float heading) skills[r] = database.GetSkillCap(GetClass(),(SkillUseTypes)r,GetLevel()); } - GetMercSize(); + size = d->size; CalcBonuses(); SetHP(GetMaxHP()); @@ -112,131 +112,66 @@ void Merc::CalcBonuses() rooted = FindType(SE_Root); } -void Merc::GetMercSize() { +float Merc::GetDefaultSize() { float MercSize = GetSize(); - switch(this->GetRace()) { - case 1: // Humans have no race bonus - break; - case 2: // Barbarian - MercSize = 7.0; - break; - case 3: // Erudite - break; - case 4: // Wood Elf - MercSize = 5.0; - break; - case 5: // High Elf - break; - case 6: // Dark Elf - MercSize = 5.0; - break; - case 7: // Half Elf - MercSize = 5.5; - break; - case 8: // Dwarf - MercSize = 4.0; - break; - case 9: // Troll - MercSize = 8.0; - break; - case 10: // Ogre - MercSize = 9.0; - break; - case 11: // Halfling - MercSize = 3.5; - break; - case 12: // Gnome - MercSize = 3.0; - break; - case 128: // Iksar - break; - case 130: // Vah Shir - MercSize = 7.0; - break; - case 330: // Froglok - MercSize = 5.0; - break; - case 522: // Drakkin - MercSize = 5.0; - break; + switch(this->GetRace()) + { + case 1: // Humans + MercSize = 6.0; + break; + case 2: // Barbarian + MercSize = 7.0; + break; + case 3: // Erudite + MercSize = 6.0; + break; + case 4: // Wood Elf + MercSize = 5.0; + break; + case 5: // High Elf + MercSize = 6.0; + break; + case 6: // Dark Elf + MercSize = 5.0; + break; + case 7: // Half Elf + MercSize = 5.5; + break; + case 8: // Dwarf + MercSize = 4.0; + break; + case 9: // Troll + MercSize = 8.0; + break; + case 10: // Ogre + MercSize = 9.0; + break; + case 11: // Halfling + MercSize = 3.5; + break; + case 12: // Gnome + MercSize = 3.0; + break; + case 128: // Iksar + MercSize = 6.0; + break; + case 130: // Vah Shir + MercSize = 7.0; + break; + case 330: // Froglok + MercSize = 5.0; + break; + case 522: // Drakkin + MercSize = 5.0; + break; + default: + MercSize = 6.0; + break; } - this->size = MercSize; -} - -void Merc::GenerateAppearance() { - // Randomize facial appearance - int iFace = 0; - if(this->GetRace() == 2) { // Barbarian w/Tatoo - iFace = MakeRandomInt(0, 79); - } - else { - iFace = MakeRandomInt(0, 7); - } - - int iHair = 0; - int iBeard = 0; - int iBeardColor = 1; - if(this->GetRace() == 522) { - iHair = MakeRandomInt(0, 8); - iBeard = MakeRandomInt(0, 11); - iBeardColor = MakeRandomInt(0, 3); - } - else if(this->GetGender()) { - iHair = MakeRandomInt(0, 2); - if(this->GetRace() == 8) { // Dwarven Females can have a beard - if(MakeRandomInt(1, 100) < 50) { - iFace += 10; - } - } - } - else { - iHair = MakeRandomInt(0, 3); - iBeard = MakeRandomInt(0, 5); - iBeardColor = MakeRandomInt(0, 19); - } - - int iHairColor = 0; - if(this->GetRace() == 522) { - iHairColor = MakeRandomInt(0, 3); - } - else { - iHairColor = MakeRandomInt(0, 19); - } - - uint8 iEyeColor1 = (uint8)MakeRandomInt(0, 9); - uint8 iEyeColor2 = 0; - if(this->GetRace() == 522) { - iEyeColor1 = iEyeColor2 = (uint8)MakeRandomInt(0, 11); - } - else if(MakeRandomInt(1, 100) > 96) { - iEyeColor2 = MakeRandomInt(0, 9); - } - else { - iEyeColor2 = iEyeColor1; - } - - int iHeritage = 0; - int iTattoo = 0; - int iDetails = 0; - if(this->GetRace() == 522) { - iHeritage = MakeRandomInt(0, 6); - iTattoo = MakeRandomInt(0, 7); - iDetails = MakeRandomInt(0, 7); - } - - this->luclinface = iFace; - this->hairstyle = iHair; - this->beard = iBeard; - this->beardcolor = iBeardColor; - this->haircolor = iHairColor; - this->eyecolor1 = iEyeColor1; - this->eyecolor2 = iEyeColor2; - this->drakkin_heritage = iHeritage; - this->drakkin_tattoo = iTattoo; - this->drakkin_details = iDetails; + return MercSize; } int Merc::CalcRecommendedLevelBonus(uint8 level, uint8 reclevel, int basestat) @@ -1258,7 +1193,6 @@ void Merc::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) { ns->spawn.guildrank = 0; ns->spawn.showhelm = 1; ns->spawn.flymode = 0; - ns->spawn.size = 0; ns->spawn.NPC = 1; // 0=player,1=npc,2=pc corpse,3=npc corpse ns->spawn.IsMercenary = 1; @@ -1523,7 +1457,7 @@ void Merc::AI_Process() { meleeDistance = meleeDistance * .30; } else { - meleeDistance *= (float)MakeRandomFloat(.50, .85); + meleeDistance *= (float)zone->random.Real(.50, .85); } if(IsMercCaster() && GetLevel() > 12) { if(IsMercCasterCombatRange(GetTarget())) @@ -1624,7 +1558,7 @@ void Merc::AI_Process() { if (GetTarget() && flurrychance) { - if(MakeRandomInt(0, 100) < flurrychance) + if(zone->random.Roll(flurrychance)) { Message_StringID(MT_NPCFlurry, YOU_FLURRY); Attack(GetTarget(), MainPrimary, false); @@ -1635,7 +1569,7 @@ void Merc::AI_Process() { int16 ExtraAttackChanceBonus = spellbonuses.ExtraAttackChance + itembonuses.ExtraAttackChance + aabonuses.ExtraAttackChance; if (GetTarget() && ExtraAttackChanceBonus) { - if(MakeRandomInt(0, 100) < ExtraAttackChanceBonus) + if(zone->random.Roll(ExtraAttackChanceBonus)) { Attack(GetTarget(), MainPrimary, false); } @@ -1669,10 +1603,8 @@ void Merc::AI_Process() { int16 DWBonus = spellbonuses.DualWieldChance + itembonuses.DualWieldChance; DualWieldProbability += DualWieldProbability*float(DWBonus)/ 100.0f; - float random = MakeRandomFloat(0, 1); - // Max 78% of DW - if (random < DualWieldProbability) + if (zone->random.Roll(DualWieldProbability)) { Attack(GetTarget(), MainSecondary); // Single attack with offhand @@ -1934,7 +1866,7 @@ bool EntityList::Merc_AICheckCloseBeneficialSpells(Merc* caster, uint8 iChance, return false; if (iChance < 100) { - int8 tmp = MakeRandomInt(1, 100); + int8 tmp = zone->random.Int(1, 100); if (tmp > iChance) return false; } @@ -2030,7 +1962,7 @@ bool Merc::AICastSpell(int8 iChance, int32 iSpellTypes) { return false; if (iChance < 100) { - if (MakeRandomInt(0, 100) > iChance){ + if (zone->random.Int(0, 100) > iChance){ return false; } } @@ -2315,14 +2247,14 @@ bool Merc::AICastSpell(int8 iChance, int32 iSpellTypes) { if(selectedMercSpell.spellid == 0 && !tar->GetSpecialAbility(UNSTUNABLE) && !tar->IsStunned()) { uint8 stunChance = 15; - if(MakeRandomInt(1, 100) <= stunChance) { + if(zone->random.Roll(stunChance)) { selectedMercSpell = GetBestMercSpellForStun(this); } } if(selectedMercSpell.spellid == 0) { uint8 lureChance = 25; - if(MakeRandomInt(1, 100) <= lureChance) { + if(zone->random.Roll(lureChance)) { selectedMercSpell = GetBestMercSpellForNukeByTargetResists(this, tar); } } @@ -2742,14 +2674,14 @@ int32 Merc::GetActSpellDamage(uint16 spell_id, int32 value, Mob* target) { int32 ratio = RuleI(Spells, BaseCritRatio); //Critical modifier is applied from spell effects only. Keep at 100 for live like criticals. - if (MakeRandomInt(1,100) <= chance){ + if (zone->random.Roll(chance)) { Critical = true; ratio += itembonuses.SpellCritDmgIncrease + spellbonuses.SpellCritDmgIncrease + aabonuses.SpellCritDmgIncrease; ratio += itembonuses.SpellCritDmgIncNoStack + spellbonuses.SpellCritDmgIncNoStack + aabonuses.SpellCritDmgIncNoStack; } - else if (GetClass() == CASTERDPS && (GetLevel() >= RuleI(Spells, WizCritLevel)) && (MakeRandomInt(1,100) <= RuleI(Spells, WizCritChance))) { - ratio = MakeRandomInt(1,100); //Wizard innate critical chance is calculated seperately from spell effect and is not a set ratio. + else if (GetClass() == CASTERDPS && (GetLevel() >= RuleI(Spells, WizCritLevel)) && (zone->random.Roll(RuleI(Spells, WizCritChance)))) { + ratio = zone->random.Int(1,100); //Wizard innate critical chance is calculated seperately from spell effect and is not a set ratio. Critical = true; } @@ -2833,7 +2765,7 @@ int32 Merc::GetActSpellHealing(uint16 spell_id, int32 value, Mob* target) { if (spellbonuses.CriticalHealDecay) chance += GetDecayEffectValue(spell_id, SE_CriticalHealDecay); - if(chance && (MakeRandomInt(0,99) < chance)) { + if(chance && zone->random.Roll(chance)) { Critical = true; modifier = 2; //At present time no critical heal amount modifier SPA exists. } @@ -2864,7 +2796,7 @@ int32 Merc::GetActSpellHealing(uint16 spell_id, int32 value, Mob* target) { if (spellbonuses.CriticalRegenDecay) chance += GetDecayEffectValue(spell_id, SE_CriticalRegenDecay); - if(chance && (MakeRandomInt(0,99) < chance)) + if(chance && zone->random.Roll(chance)) return (value * 2); } @@ -2876,7 +2808,7 @@ int32 Merc::GetActSpellCost(uint16 spell_id, int32 cost) // Formula = Unknown exact, based off a random percent chance up to mana cost(after focuses) of the cast spell if(this->itembonuses.Clairvoyance && spells[spell_id].classes[(GetClass()%16) - 1] >= GetLevel() - 5) { - int16 mana_back = this->itembonuses.Clairvoyance * MakeRandomInt(1, 100) / 100; + int16 mana_back = this->itembonuses.Clairvoyance * zone->random.Int(1, 100) / 100; // Doesnt generate mana, so best case is a free spell if(mana_back > cost) mana_back = cost; @@ -2893,7 +2825,7 @@ int32 Merc::GetActSpellCost(uint16 spell_id, int32 cost) if(focus_redux > 0) { - PercentManaReduction += MakeRandomFloat(1, (double)focus_redux); + PercentManaReduction += zone->random.Real(1, (double)focus_redux); } cost -= (cost * (PercentManaReduction / 100)); @@ -3825,17 +3757,17 @@ MercSpell Merc::GetBestMercSpellForAENuke(Merc* caster, Mob* tar) { } //check of we even want to cast an AE nuke - if(MakeRandomInt(1, 100) <= initialCastChance) { + if(zone->random.Roll(initialCastChance)) { result = GetBestMercSpellForAERainNuke(caster, tar); //check if we have a spell & allow for other AE nuke types - if(result.spellid == 0 && MakeRandomInt(1, 100) <= castChanceFalloff) { + if(result.spellid == 0 && zone->random.Roll(castChanceFalloff)) { result = GetBestMercSpellForPBAENuke(caster, tar); //check if we have a spell & allow for other AE nuke types - if(result.spellid == 0 && MakeRandomInt(1, 100) <= castChanceFalloff) { + if(result.spellid == 0 && zone->random.Roll(castChanceFalloff)) { result = GetBestMercSpellForTargetedAENuke(caster, tar); } @@ -3879,7 +3811,7 @@ MercSpell Merc::GetBestMercSpellForTargetedAENuke(Merc* caster, Mob* tar) { && !IsPBAENukeSpell(mercSpellListItr->spellid) && CheckSpellRecastTimers(caster, mercSpellListItr->spellid)) { uint8 numTargets = 0; if(CheckAENuke(caster, tar, mercSpellListItr->spellid, numTargets)) { - if(numTargets >= numTargetsCheck && MakeRandomInt(1, 100) <= castChance) { + if(numTargets >= numTargetsCheck && zone->random.Roll(castChance)) { result.spellid = mercSpellListItr->spellid; result.stance = mercSpellListItr->stance; result.type = mercSpellListItr->type; @@ -3929,7 +3861,7 @@ MercSpell Merc::GetBestMercSpellForPBAENuke(Merc* caster, Mob* tar) { if(IsPBAENukeSpell(mercSpellListItr->spellid) && CheckSpellRecastTimers(caster, mercSpellListItr->spellid)) { uint8 numTargets = 0; if(CheckAENuke(caster, caster, mercSpellListItr->spellid, numTargets)) { - if(numTargets >= numTargetsCheck && MakeRandomInt(1, 100) <= castChance) { + if(numTargets >= numTargetsCheck && zone->random.Roll(castChance)) { result.spellid = mercSpellListItr->spellid; result.stance = mercSpellListItr->stance; result.type = mercSpellListItr->type; @@ -3976,7 +3908,7 @@ MercSpell Merc::GetBestMercSpellForAERainNuke(Merc* caster, Mob* tar) { for(std::list::iterator mercSpellListItr = mercSpellList.begin(); mercSpellListItr != mercSpellList.end(); ++mercSpellListItr) { // Assuming all the spells have been loaded into this list by level and in descending order - if(IsAERainNukeSpell(mercSpellListItr->spellid) && MakeRandomInt(1, 100) <= castChance && CheckSpellRecastTimers(caster, mercSpellListItr->spellid)) { + if(IsAERainNukeSpell(mercSpellListItr->spellid) && zone->random.Roll(castChance) && CheckSpellRecastTimers(caster, mercSpellListItr->spellid)) { uint8 numTargets = 0; if(CheckAENuke(caster, tar, mercSpellListItr->spellid, numTargets)) { if(numTargets >= numTargetsCheck) { @@ -4015,7 +3947,7 @@ MercSpell Merc::GetBestMercSpellForNuke(Merc* caster) { for(std::list::iterator mercSpellListItr = mercSpellList.begin(); mercSpellListItr != mercSpellList.end(); ++mercSpellListItr) { // Assuming all the spells have been loaded into this list by level and in descending order if(IsPureNukeSpell(mercSpellListItr->spellid) && !IsAENukeSpell(mercSpellListItr->spellid) - && MakeRandomInt(1, 100) <= castChance && CheckSpellRecastTimers(caster, mercSpellListItr->spellid)) { + && zone->random.Roll(castChance) && CheckSpellRecastTimers(caster, mercSpellListItr->spellid)) { result.spellid = mercSpellListItr->spellid; result.stance = mercSpellListItr->stance; result.type = mercSpellListItr->type; @@ -4447,7 +4379,7 @@ bool Merc::CheckConfidence() { ConfidenceLossChance = 25 - ( 5 * (GetTierID() - 1)); } - if(MakeRandomInt(0 ,100) < ConfidenceLossChance) { + if(zone->random.Roll(ConfidenceLossChance)) { result = false; } @@ -4593,7 +4525,7 @@ void Merc::DoClassAttacks(Mob *target) { break; case TANK:{ if(level >= RuleI(Combat, NPCBashKickLevel)){ - if(MakeRandomInt(0, 100) > 25) //tested on live, warrior mobs both kick and bash, kick about 75% of the time, casting doesn't seem to make a difference. + if(zone->random.Int(0, 100) > 25) //tested on live, warrior mobs both kick and bash, kick about 75% of the time, casting doesn't seem to make a difference. { DoAnim(animKick); int32 dmg = 0; @@ -4606,7 +4538,7 @@ void Merc::DoClassAttacks(Mob *target) { if(RuleB(Combat, UseIntervalAC)) dmg = GetKickDamage(); else - dmg = MakeRandomInt(1, GetKickDamage()); + dmg = zone->random.Int(1, GetKickDamage()); } } @@ -4628,7 +4560,7 @@ void Merc::DoClassAttacks(Mob *target) { if(RuleB(Combat, UseIntervalAC)) dmg = GetBashDamage(); else - dmg = MakeRandomInt(1, GetBashDamage()); + dmg = zone->random.Int(1, GetBashDamage()); } } @@ -4756,7 +4688,7 @@ const char* Merc::GetRandomName(){ bool valid = false; while(!valid) { - int rndnum=MakeRandomInt(0, 75),n=1; + int rndnum=zone->random.Int(0, 75),n=1; bool dlc=false; bool vwl=false; bool dbl=false; @@ -4777,18 +4709,18 @@ const char* Merc::GetRandomName(){ rndname[0]=vowels[rndnum]; vwl=true; } - int namlen=MakeRandomInt(5, 10); + int namlen=zone->random.Int(5, 10); for (int i=n;irandom.Int(0, 62); if (rndnum>46) { // pick a cons pair if (i>namlen-3) // last 2 chars in name? { // name can only end in cons pair "rk" "st" "sh" "th" "ph" "sk" "nd" or "ng" - rndnum=MakeRandomInt(0, 7)*2; + rndnum=zone->random.Int(0, 7)*2; } else { // pick any from the set @@ -4806,12 +4738,12 @@ const char* Merc::GetRandomName(){ } else { // select a vowel - rndname[i]=vowels[MakeRandomInt(0, 16)]; + rndname[i]=vowels[zone->random.Int(0, 16)]; } vwl=!vwl; if (!dbl && !dlc) { // one chance at double letters in name - if (!MakeRandomInt(0, i+9)) // chances decrease towards end of name + if (!zone->random.Int(0, i+9)) // chances decrease towards end of name { rndname[i+1]=rndname[i]; dbl=true; @@ -4940,22 +4872,37 @@ Merc* Merc::LoadMerc(Client *c, MercTemplate* merc_template, uint32 merchant_id, } snprintf(npc_type->name, 64, "%s", c->GetMercInfo().merc_name); } - uint8 gender = 0; - if(merchant_id > 0) { + + npc_type->race = merc_template->RaceID; + + // Use the Gender and Size of the Merchant if possible + uint8 tmpgender = 0; + float tmpsize = 6.0f; + if(merchant_id > 0) + { NPC* tar = entity_list.GetNPCByID(merchant_id); - if(tar) { - gender = Mob::GetDefaultGender(npc_type->race, tar->GetGender()); + if(tar) + { + tmpgender = tar->GetGender(); + tmpsize = tar->GetSize(); } + else + { + tmpgender = Mob::GetDefaultGender(npc_type->race, c->GetMercInfo().Gender); + } + } - else { - gender = c->GetMercInfo().Gender; + else + { + tmpgender = c->GetMercInfo().Gender; + tmpsize = c->GetMercInfo().MercSize; } - sprintf(npc_type->lastname, "%s's %s", c->GetName(), "Mercenary"); - npc_type->gender = gender; + sprintf(npc_type->lastname, "%s's Mercenary", c->GetName()); + npc_type->gender = tmpgender; + npc_type->size = tmpsize; npc_type->loottable_id = 0; // Loottable has to be 0, otherwise we'll be leavin' some corpses! npc_type->npc_id = 0; //NPC ID has to be 0, otherwise db gets all confuzzled. - npc_type->race = merc_template->RaceID; npc_type->class_ = merc_template->ClassID; npc_type->maxlevel = 0; //We should hard-set this to override scalerate's functionality in the NPC class when it is constructed. @@ -4975,6 +4922,7 @@ Merc* Merc::LoadMerc(Client *c, MercTemplate* merc_template, uint32 merchant_id, snprintf(merc->name, 64, "%s", c->GetMercInfo().merc_name); merc->SetSuspended(c->GetMercInfo().IsSuspended); merc->gender = c->GetMercInfo().Gender; + merc->size = c->GetMercInfo().MercSize; merc->SetHP(c->GetMercInfo().hp <= 0 ? merc->GetMaxHP() : c->GetMercInfo().hp); merc->SetMana(c->GetMercInfo().hp <= 0 ? merc->GetMaxMana() : c->GetMercInfo().mana); merc->SetEndurance(c->GetMercInfo().endurance); @@ -4989,6 +4937,11 @@ Merc* Merc::LoadMerc(Client *c, MercTemplate* merc_template, uint32 merchant_id, merc->drakkin_tattoo = c->GetMercInfo().drakkinTattoo; merc->drakkin_details = c->GetMercInfo().drakkinDetails; } + else + { + // Give Random Features to newly hired Mercs + merc->RandomizeFeatures(false, true); + } if(merc->GetMercID()) { database.LoadMercBuffs(merc); @@ -5008,7 +4961,8 @@ void Merc::UpdateMercInfo(Client *c) { snprintf(c->GetMercInfo().merc_name, 64, "%s", name); c->GetMercInfo().mercid = GetMercID(); c->GetMercInfo().IsSuspended = IsSuspended(); - c->GetMercInfo().Gender = gender; + c->GetMercInfo().Gender = GetGender(); + c->GetMercInfo().MercSize = GetSize(); c->GetMercInfo().hp = GetHP(); c->GetMercInfo().mana = GetMana(); c->GetMercInfo().endurance = GetEndurance(); @@ -5540,7 +5494,7 @@ void Client::SpawnMercOnZone() { } else { - int32 TimeDiff = GetMercInfo().SuspendedTime + RuleI(Mercs, SuspendIntervalS) - time(nullptr); + int32 TimeDiff = GetMercInfo().SuspendedTime - time(nullptr); if (TimeDiff > 0) { if (!GetPTimers().Enabled(pTimerMercSuspend)) @@ -5558,6 +5512,11 @@ void Client::SpawnMercOnZone() { Message(7, "Mercenary Debug: SpawnMercOnZone Suspended Merc."); } } + else + { + // No Merc Hired + SendClearMercInfo(); + } } void Client::SendMercTimer(Merc* merc) { @@ -5604,9 +5563,6 @@ void Client::SpawnMerc(Merc* merc, bool setMaxStats) { SetMerc(merc); merc->Unsuspend(setMaxStats); merc->SetStance(GetMercInfo().Stance); - GetMercInfo().SuspendedTime = 0; - - //SendMercTimer(merc); if (MERC_DEBUG > 0) Message(7, "Mercenary Debug: SpawnMerc Success."); @@ -5666,7 +5622,6 @@ bool Client::MercOnlyOrNoGroup() { bool Merc::Unsuspend(bool setMaxStats) { Client* mercOwner = nullptr; - bool loaded = false; if(GetMercOwner()) { mercOwner = GetMercOwner(); @@ -5694,12 +5649,8 @@ bool Merc::Unsuspend(bool setMaxStats) { if(!mercOwner->GetPTimers().Expired(&database, pTimerMercSuspend, false)) mercOwner->GetPTimers().Clear(&database, pTimerMercSuspend); - MercJoinClientGroup(); - - if(loaded) + if (MercJoinClientGroup()) { - LoadMercSpells(); - if(setMaxStats) { SetHP(GetMaxHP()); @@ -5977,7 +5928,7 @@ Merc* Client::GetMerc() { if(GetMercID() == 0) { if (MERC_DEBUG > 0) - //Message(7, "Mercenary Debug: GetMerc 0."); + Message(7, "Mercenary Debug: GetMerc 0."); return (nullptr); } @@ -5998,9 +5949,6 @@ Merc* Client::GetMerc() { return (nullptr); } - if (MERC_DEBUG > 0) - //Message(7, "Mercenary Debug: GetMerc Success."); - return (tmp); } @@ -6073,7 +6021,7 @@ void Client::SetMerc(Merc* newmerc) { GetMercInfo().IsSuspended = newmerc->IsSuspended(); GetMercInfo().SuspendedTime = 0; GetMercInfo().Gender = newmerc->GetGender(); - //GetMercInfo().State = newmerc->GetStance(); + GetMercInfo().State = newmerc->IsSuspended() ? MERC_STATE_SUSPENDED : MERC_STATE_NORMAL; snprintf(GetMercInfo().merc_name, 64, "%s", newmerc->GetName()); if (MERC_DEBUG > 0) Message(7, "Mercenary Debug: SetMerc New Merc."); diff --git a/zone/merc.cpp.orig b/zone/merc.cpp.orig new file mode 100644 index 000000000..5d2d8ca81 --- /dev/null +++ b/zone/merc.cpp.orig @@ -0,0 +1,6329 @@ +#include "merc.h" +#include "masterentity.h" +#include "npc_ai.h" +#include "../common/packet_dump.h" +#include "../common/eq_packet_structs.h" +#include "../common/eq_constants.h" +#include "../common/skills.h" +#include "../common/spdat.h" +#include "zone.h" +#include "string_ids.h" +#include "../common/misc_functions.h" +#include "../common/string_util.h" +#include "../common/rulesys.h" +#include "quest_parser_collection.h" +#include "water_map.h" + +extern volatile bool ZoneLoaded; + +Merc::Merc(const NPCType* d, float x, float y, float z, float heading) +: NPC(d, nullptr, xyz_heading(x, y, z, heading), 0, false), endupkeep_timer(1000), rest_timer(1), confidence_timer(6000), check_target_timer(2000) +{ + base_hp = d->max_hp; + base_mana = d->Mana; + _baseAC = d->AC; + _baseSTR = d->STR; + _baseSTA = d->STA; + _baseDEX = d->DEX; + _baseAGI = d->AGI; + _baseINT = d->INT; + _baseWIS = d->WIS; + _baseCHA = d->CHA; + _baseATK = d->ATK; + _baseRace = d->race; + _baseGender = d->gender; + _baseMR = d->MR; + _baseCR = d->CR; + _baseDR = d->DR; + _baseFR = d->FR; + _basePR = d->PR; + _baseCorrup = d->Corrup; + _OwnerClientVersion = EQClientTitanium; + RestRegenHP = 0; + RestRegenMana = 0; + RestRegenEndurance = 0; + cur_end = 0; + + _medding = false; + _suspended = false; + p_depop = false; + _check_confidence = false; + _lost_confidence = false; + _hatedCount = 0; + + memset(equipment, 0, sizeof(equipment)); + + SetMercID(0); + SetStance(MercStanceBalanced); + rest_timer.Disable(); + + int r; + for(r = 0; r <= HIGHEST_SKILL; r++) { + skills[r] = database.GetSkillCap(GetClass(),(SkillUseTypes)r,GetLevel()); + } + + size = d->size; + CalcBonuses(); + + SetHP(GetMaxHP()); + SetMana(GetMaxMana()); + SetEndurance(GetMaxEndurance()); + + AI_Init(); + AI_Start(); +} + +Merc::~Merc() { + AI_Stop(); + //entity_list.RemoveMerc(this->GetID()); + UninitializeBuffSlots(); +} + +void Merc::CalcBonuses() +{ + memset(&itembonuses, 0, sizeof(StatBonuses)); + memset(&aabonuses, 0, sizeof(StatBonuses)); + CalcItemBonuses(&itembonuses); + + CalcSpellBonuses(&spellbonuses); + + CalcAC(); + CalcATK(); + + CalcSTR(); + CalcSTA(); + CalcDEX(); + CalcAGI(); + CalcINT(); + CalcWIS(); + CalcCHA(); + + CalcMR(); + CalcFR(); + CalcDR(); + CalcPR(); + CalcCR(); + CalcCorrup(); + + CalcMaxHP(); + CalcMaxMana(); + CalcMaxEndurance(); + + rooted = FindType(SE_Root); +} + +float Merc::GetDefaultSize() { + + float MercSize = GetSize(); + + switch(this->GetRace()) + { + case 1: // Humans + MercSize = 6.0; + break; + case 2: // Barbarian + MercSize = 7.0; + break; + case 3: // Erudite + MercSize = 6.0; + break; + case 4: // Wood Elf + MercSize = 5.0; + break; + case 5: // High Elf + MercSize = 6.0; + break; + case 6: // Dark Elf + MercSize = 5.0; + break; + case 7: // Half Elf + MercSize = 5.5; + break; + case 8: // Dwarf + MercSize = 4.0; + break; + case 9: // Troll + MercSize = 8.0; + break; + case 10: // Ogre + MercSize = 9.0; + break; + case 11: // Halfling + MercSize = 3.5; + break; + case 12: // Gnome + MercSize = 3.0; + break; + case 128: // Iksar + MercSize = 6.0; + break; + case 130: // Vah Shir + MercSize = 7.0; + break; + case 330: // Froglok + MercSize = 5.0; + break; + case 522: // Drakkin + MercSize = 5.0; + break; + default: + MercSize = 6.0; + break; + } + + return MercSize; +} + +int Merc::CalcRecommendedLevelBonus(uint8 level, uint8 reclevel, int basestat) +{ + if( (reclevel > 0) && (level < reclevel) ) + { + int32 statmod = (level * 10000 / reclevel) * basestat; + + if( statmod < 0 ) + { + statmod -= 5000; + return (statmod/10000); + } + else + { + statmod += 5000; + return (statmod/10000); + } + } + + return 0; +} + +void Merc::CalcItemBonuses(StatBonuses* newbon) { + //memset assumed to be done by caller. + + + unsigned int i; + //should not include 21 (SLOT_AMMO) + for (i=0; i= EQClientSoF) + { + const ItemInst* inst = m_inv[MainPowerSource]; + if(inst) + AddItemBonuses(inst, newbon); + }*/ + + // Caps + if(newbon->HPRegen > CalcHPRegenCap()) + newbon->HPRegen = CalcHPRegenCap(); + + if(newbon->ManaRegen > CalcManaRegenCap()) + newbon->ManaRegen = CalcManaRegenCap(); + + if(newbon->EnduranceRegen > CalcEnduranceRegenCap()) + newbon->EnduranceRegen = CalcEnduranceRegenCap(); + + SetAttackTimer(); +} + +void Merc::AddItemBonuses(const Item_Struct *item, StatBonuses* newbon) { + + if(GetLevel() < item->ReqLevel) + { + return; + } + + if(GetLevel() >= item->RecLevel) + { + newbon->AC += item->AC; + newbon->HP += item->HP; + newbon->Mana += item->Mana; + newbon->Endurance += item->Endur; + newbon->STR += (item->AStr + item->HeroicStr); + newbon->STA += (item->ASta + item->HeroicSta); + newbon->DEX += (item->ADex + item->HeroicDex); + newbon->AGI += (item->AAgi + item->HeroicAgi); + newbon->INT += (item->AInt + item->HeroicInt); + newbon->WIS += (item->AWis + item->HeroicWis); + newbon->CHA += (item->ACha + item->HeroicCha); + + newbon->MR += (item->MR + item->HeroicMR); + newbon->FR += (item->FR + item->HeroicFR); + newbon->CR += (item->CR + item->HeroicCR); + newbon->PR += (item->PR + item->HeroicPR); + newbon->DR += (item->DR + item->HeroicDR); + newbon->Corrup += (item->SVCorruption + item->HeroicSVCorrup); + + newbon->STRCapMod += item->HeroicStr; + newbon->STACapMod += item->HeroicSta; + newbon->DEXCapMod += item->HeroicDex; + newbon->AGICapMod += item->HeroicAgi; + newbon->INTCapMod += item->HeroicInt; + newbon->WISCapMod += item->HeroicWis; + newbon->CHACapMod += item->HeroicCha; + newbon->MRCapMod += item->HeroicMR; + newbon->CRCapMod += item->HeroicFR; + newbon->FRCapMod += item->HeroicCR; + newbon->PRCapMod += item->HeroicPR; + newbon->DRCapMod += item->HeroicDR; + newbon->CorrupCapMod += item->HeroicSVCorrup; + + newbon->HeroicSTR += item->HeroicStr; + newbon->HeroicSTA += item->HeroicSta; + newbon->HeroicDEX += item->HeroicDex; + newbon->HeroicAGI += item->HeroicAgi; + newbon->HeroicINT += item->HeroicInt; + newbon->HeroicWIS += item->HeroicWis; + newbon->HeroicCHA += item->HeroicCha; + newbon->HeroicMR += item->HeroicMR; + newbon->HeroicFR += item->HeroicFR; + newbon->HeroicCR += item->HeroicCR; + newbon->HeroicPR += item->HeroicPR; + newbon->HeroicDR += item->HeroicDR; + newbon->HeroicCorrup += item->HeroicSVCorrup; + + } + else + { + int lvl = GetLevel(); + int reclvl = item->RecLevel; + + newbon->AC += CalcRecommendedLevelBonus( lvl, reclvl, item->AC ); + newbon->HP += CalcRecommendedLevelBonus( lvl, reclvl, item->HP ); + newbon->Mana += CalcRecommendedLevelBonus( lvl, reclvl, item->Mana ); + newbon->Endurance += CalcRecommendedLevelBonus( lvl, reclvl, item->Endur ); + newbon->STR += CalcRecommendedLevelBonus( lvl, reclvl, (item->AStr + item->HeroicStr) ); + newbon->STA += CalcRecommendedLevelBonus( lvl, reclvl, (item->ASta + item->HeroicSta) ); + newbon->DEX += CalcRecommendedLevelBonus( lvl, reclvl, (item->ADex + item->HeroicDex) ); + newbon->AGI += CalcRecommendedLevelBonus( lvl, reclvl, (item->AAgi + item->HeroicAgi) ); + newbon->INT += CalcRecommendedLevelBonus( lvl, reclvl, (item->AInt + item->HeroicInt) ); + newbon->WIS += CalcRecommendedLevelBonus( lvl, reclvl, (item->AWis + item->HeroicWis) ); + newbon->CHA += CalcRecommendedLevelBonus( lvl, reclvl, (item->ACha + item->HeroicCha) ); + + newbon->MR += CalcRecommendedLevelBonus( lvl, reclvl, (item->MR + item->HeroicMR) ); + newbon->FR += CalcRecommendedLevelBonus( lvl, reclvl, (item->FR + item->HeroicFR) ); + newbon->CR += CalcRecommendedLevelBonus( lvl, reclvl, (item->CR + item->HeroicCR) ); + newbon->PR += CalcRecommendedLevelBonus( lvl, reclvl, (item->PR + item->HeroicPR) ); + newbon->DR += CalcRecommendedLevelBonus( lvl, reclvl, (item->DR + item->HeroicDR) ); + newbon->Corrup += CalcRecommendedLevelBonus( lvl, reclvl, (item->SVCorruption + item->HeroicSVCorrup) ); + + newbon->STRCapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicStr ); + newbon->STACapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicSta ); + newbon->DEXCapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicDex ); + newbon->AGICapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicAgi ); + newbon->INTCapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicInt ); + newbon->WISCapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicWis ); + newbon->CHACapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicCha ); + newbon->MRCapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicMR ); + newbon->CRCapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicFR ); + newbon->FRCapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicCR ); + newbon->PRCapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicPR ); + newbon->DRCapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicDR ); + newbon->CorrupCapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicSVCorrup ); + + newbon->HeroicSTR += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicStr ); + newbon->HeroicSTA += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicSta ); + newbon->HeroicDEX += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicDex ); + newbon->HeroicAGI += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicAgi ); + newbon->HeroicINT += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicInt ); + newbon->HeroicWIS += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicWis ); + newbon->HeroicCHA += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicCha ); + newbon->HeroicMR += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicMR ); + newbon->HeroicFR += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicFR ); + newbon->HeroicCR += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicCR ); + newbon->HeroicPR += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicPR ); + newbon->HeroicDR += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicDR ); + newbon->HeroicCorrup += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicSVCorrup ); + } + + //FatherNitwit: New style haste, shields, and regens + if(newbon->haste < (int16)item->Haste) { + newbon->haste = item->Haste; + } + if(item->Regen > 0) + newbon->HPRegen += item->Regen; + + if(item->ManaRegen > 0) + newbon->ManaRegen += item->ManaRegen; + + if(item->EnduranceRegen > 0) + newbon->EnduranceRegen += item->EnduranceRegen; + + if(item->Attack > 0) { + + unsigned int cap = RuleI(Character, ItemATKCap); + cap += itembonuses.ItemATKCap + spellbonuses.ItemATKCap + aabonuses.ItemATKCap; + + if((newbon->ATK + item->Attack) > cap) + newbon->ATK = RuleI(Character, ItemATKCap); + else + newbon->ATK += item->Attack; + } + if(item->DamageShield > 0) { + if((newbon->DamageShield + item->DamageShield) > RuleI(Character, ItemDamageShieldCap)) + newbon->DamageShield = RuleI(Character, ItemDamageShieldCap); + else + newbon->DamageShield += item->DamageShield; + } + if(item->SpellShield > 0) { + if((newbon->SpellShield + item->SpellShield) > RuleI(Character, ItemSpellShieldingCap)) + newbon->SpellShield = RuleI(Character, ItemSpellShieldingCap); + else + newbon->SpellShield += item->SpellShield; + } + if(item->Shielding > 0) { + if((newbon->MeleeMitigation + item->Shielding) > RuleI(Character, ItemShieldingCap)) + newbon->MeleeMitigation = RuleI(Character, ItemShieldingCap); + else + newbon->MeleeMitigation += item->Shielding; + } + if(item->StunResist > 0) { + if((newbon->StunResist + item->StunResist) > RuleI(Character, ItemStunResistCap)) + newbon->StunResist = RuleI(Character, ItemStunResistCap); + else + newbon->StunResist += item->StunResist; + } + if(item->StrikeThrough > 0) { + if((newbon->StrikeThrough + item->StrikeThrough) > RuleI(Character, ItemStrikethroughCap)) + newbon->StrikeThrough = RuleI(Character, ItemStrikethroughCap); + else + newbon->StrikeThrough += item->StrikeThrough; + } + if(item->Avoidance > 0) { + if((newbon->AvoidMeleeChance + item->Avoidance) > RuleI(Character, ItemAvoidanceCap)) + newbon->AvoidMeleeChance = RuleI(Character, ItemAvoidanceCap); + else + newbon->AvoidMeleeChance += item->Avoidance; + } + if(item->Accuracy > 0) { + if((newbon->HitChance + item->Accuracy) > RuleI(Character, ItemAccuracyCap)) + newbon->HitChance = RuleI(Character, ItemAccuracyCap); + else + newbon->HitChance += item->Accuracy; + } + if(item->CombatEffects > 0) { + if((newbon->ProcChance + item->CombatEffects) > RuleI(Character, ItemCombatEffectsCap)) + newbon->ProcChance = RuleI(Character, ItemCombatEffectsCap); + else + newbon->ProcChance += item->CombatEffects; + } + if(item->DotShielding > 0) { + if((newbon->DoTShielding + item->DotShielding) > RuleI(Character, ItemDoTShieldingCap)) + newbon->DoTShielding = RuleI(Character, ItemDoTShieldingCap); + else + newbon->DoTShielding += item->DotShielding; + } + + if(item->HealAmt > 0) { + if((newbon->HealAmt + item->HealAmt) > RuleI(Character, ItemHealAmtCap)) + newbon->HealAmt = RuleI(Character, ItemHealAmtCap); + else + newbon->HealAmt += item->HealAmt; + } + if(item->SpellDmg > 0) { + if((newbon->SpellDmg + item->SpellDmg) > RuleI(Character, ItemSpellDmgCap)) + newbon->SpellDmg = RuleI(Character, ItemSpellDmgCap); + else + newbon->SpellDmg += item->SpellDmg; + } + if(item->Clairvoyance > 0) { + if((newbon->Clairvoyance + item->Clairvoyance) > RuleI(Character, ItemClairvoyanceCap)) + newbon->Clairvoyance = RuleI(Character, ItemClairvoyanceCap); + else + newbon->Clairvoyance += item->Clairvoyance; + } + + if(item->DSMitigation > 0) { + if((newbon->DSMitigation + item->DSMitigation) > RuleI(Character, ItemDSMitigationCap)) + newbon->DSMitigation = RuleI(Character, ItemDSMitigationCap); + else + newbon->DSMitigation += item->DSMitigation; + } + if (item->Worn.Effect>0 && (item->Worn.Type == ET_WornEffect)) { // latent effects + ApplySpellsBonuses(item->Worn.Effect, item->Worn.Level, newbon, 0, true); + } + + if (item->Focus.Effect>0 && (item->Focus.Type == ET_Focus)) { // focus effects + ApplySpellsBonuses(item->Focus.Effect, item->Focus.Level, newbon, 0, true); + } + + switch(item->BardType) + { + case 51: /* All (e.g. Singing Short Sword) */ + { + if(item->BardValue > newbon->singingMod) + newbon->singingMod = item->BardValue; + if(item->BardValue > newbon->brassMod) + newbon->brassMod = item->BardValue; + if(item->BardValue > newbon->stringedMod) + newbon->stringedMod = item->BardValue; + if(item->BardValue > newbon->percussionMod) + newbon->percussionMod = item->BardValue; + if(item->BardValue > newbon->windMod) + newbon->windMod = item->BardValue; + break; + } + case 50: /* Singing */ + { + if(item->BardValue > newbon->singingMod) + newbon->singingMod = item->BardValue; + break; + } + case 23: /* Wind */ + { + if(item->BardValue > newbon->windMod) + newbon->windMod = item->BardValue; + break; + } + case 24: /* stringed */ + { + if(item->BardValue > newbon->stringedMod) + newbon->stringedMod = item->BardValue; + break; + } + case 25: /* brass */ + { + if(item->BardValue > newbon->brassMod) + newbon->brassMod = item->BardValue; + break; + } + case 26: /* Percussion */ + { + if(item->BardValue > newbon->percussionMod) + newbon->percussionMod = item->BardValue; + break; + } + } + + if (item->SkillModValue != 0 && item->SkillModType <= HIGHEST_SKILL){ + if ((item->SkillModValue > 0 && newbon->skillmod[item->SkillModType] < item->SkillModValue) || + (item->SkillModValue < 0 && newbon->skillmod[item->SkillModType] > item->SkillModValue)) + { + newbon->skillmod[item->SkillModType] = item->SkillModValue; + } + } + + // Add Item Faction Mods + if (item->FactionMod1) + { + if (item->FactionAmt1 > 0 && item->FactionAmt1 > GetItemFactionBonus(item->FactionMod1)) + { + AddItemFactionBonus(item->FactionMod1, item->FactionAmt1); + } + else if (item->FactionAmt1 < 0 && item->FactionAmt1 < GetItemFactionBonus(item->FactionMod1)) + { + AddItemFactionBonus(item->FactionMod1, item->FactionAmt1); + } + } + if (item->FactionMod2) + { + if (item->FactionAmt2 > 0 && item->FactionAmt2 > GetItemFactionBonus(item->FactionMod2)) + { + AddItemFactionBonus(item->FactionMod2, item->FactionAmt2); + } + else if (item->FactionAmt2 < 0 && item->FactionAmt2 < GetItemFactionBonus(item->FactionMod2)) + { + AddItemFactionBonus(item->FactionMod2, item->FactionAmt2); + } + } + if (item->FactionMod3) + { + if (item->FactionAmt3 > 0 && item->FactionAmt3 > GetItemFactionBonus(item->FactionMod3)) + { + AddItemFactionBonus(item->FactionMod3, item->FactionAmt3); + } + else if (item->FactionAmt3 < 0 && item->FactionAmt3 < GetItemFactionBonus(item->FactionMod3)) + { + AddItemFactionBonus(item->FactionMod3, item->FactionAmt3); + } + } + if (item->FactionMod4) + { + if (item->FactionAmt4 > 0 && item->FactionAmt4 > GetItemFactionBonus(item->FactionMod4)) + { + AddItemFactionBonus(item->FactionMod4, item->FactionAmt4); + } + else if (item->FactionAmt4 < 0 && item->FactionAmt4 < GetItemFactionBonus(item->FactionMod4)) + { + AddItemFactionBonus(item->FactionMod4, item->FactionAmt4); + } + } + + if (item->ExtraDmgSkill != 0 && item->ExtraDmgSkill <= HIGHEST_SKILL) { + if((newbon->SkillDamageAmount[item->ExtraDmgSkill] + item->ExtraDmgAmt) > RuleI(Character, ItemExtraDmgCap)) + newbon->SkillDamageAmount[item->ExtraDmgSkill] = RuleI(Character, ItemExtraDmgCap); + else + newbon->SkillDamageAmount[item->ExtraDmgSkill] += item->ExtraDmgAmt; + } +} + +int Merc::GroupLeadershipAAHealthEnhancement() +{ + Group *g = GetGroup(); + + if(!g || (g->GroupCount() < 3)) + return 0; + + switch(g->GetLeadershipAA(groupAAHealthEnhancement)) + { + case 0: + return 0; + case 1: + return 30; + case 2: + return 60; + case 3: + return 100; + } + + return 0; +} + +int Merc::GroupLeadershipAAManaEnhancement() +{ + Group *g = GetGroup(); + + if(!g || (g->GroupCount() < 3)) + return 0; + + switch(g->GetLeadershipAA(groupAAManaEnhancement)) + { + case 0: + return 0; + case 1: + return 30; + case 2: + return 60; + case 3: + return 100; + } + + return 0; +} + +int Merc::GroupLeadershipAAHealthRegeneration() +{ + Group *g = GetGroup(); + + if(!g || (g->GroupCount() < 3)) + return 0; + + switch(g->GetLeadershipAA(groupAAHealthRegeneration)) + { + case 0: + return 0; + case 1: + return 4; + case 2: + return 6; + case 3: + return 8; + } + + return 0; +} + +int Merc::GroupLeadershipAAOffenseEnhancement() +{ + Group *g = GetGroup(); + + if(!g || (g->GroupCount() < 3)) + return 0; + + switch(g->GetLeadershipAA(groupAAOffenseEnhancement)) + { + case 0: + return 0; + case 1: + return 10; + case 2: + return 19; + case 3: + return 28; + case 4: + return 34; + case 5: + return 40; + } + return 0; +} + +int32 Merc::CalcSTR() { + int32 val = _baseSTR + itembonuses.STR + spellbonuses.STR; + + int32 mod = aabonuses.STR; + + STR = val + mod; + + if(STR < 1) + STR = 1; + + return(STR); +} + +int32 Merc::CalcSTA() { + int32 val = _baseSTA + itembonuses.STA + spellbonuses.STA; + + int32 mod = aabonuses.STA; + + STA = val + mod; + + if(STA < 1) + STA = 1; + + return(STA); +} + +int32 Merc::CalcAGI() { + int32 val = _baseAGI + itembonuses.AGI + spellbonuses.AGI; + int32 mod = aabonuses.AGI; + + int32 str = GetSTR(); + + AGI = val + mod; + + if(AGI < 1) + AGI = 1; + + return(AGI); +} + +int32 Merc::CalcDEX() { + int32 val = _baseDEX + itembonuses.DEX + spellbonuses.DEX; + + int32 mod = aabonuses.DEX; + + DEX = val + mod; + + if(DEX < 1) + DEX = 1; + + return(DEX); +} + +int32 Merc::CalcINT() { + int32 val = _baseINT + itembonuses.INT + spellbonuses.INT; + + int32 mod = aabonuses.INT; + + INT = val + mod; + + if(INT < 1) + INT = 1; + + return(INT); +} + +int32 Merc::CalcWIS() { + int32 val = _baseWIS + itembonuses.WIS + spellbonuses.WIS; + + int32 mod = aabonuses.WIS; + + WIS = val + mod; + + if(WIS < 1) + WIS = 1; + + return(WIS); +} + +int32 Merc::CalcCHA() { + int32 val = _baseCHA + itembonuses.CHA + spellbonuses.CHA; + + int32 mod = aabonuses.CHA; + + CHA = val + mod; + + if(CHA < 1) + CHA = 1; + + return(CHA); +} + +//The AA multipliers are set to be 5, but were 2 on WR +//The resistant discipline which I think should be here is implemented +//in Mob::ResistSpell +int32 Merc::CalcMR() +{ + MR = _baseMR + itembonuses.MR + spellbonuses.MR + aabonuses.MR; + + if(MR < 1) + MR = 1; + + return(MR); +} + +int32 Merc::CalcFR() +{ + FR = _baseFR + itembonuses.FR + spellbonuses.FR + aabonuses.FR; + + if(FR < 1) + FR = 1; + + return(FR); +} + +int32 Merc::CalcDR() +{ + DR = _baseDR + itembonuses.DR + spellbonuses.DR + aabonuses.DR; + + if(DR < 1) + DR = 1; + + return(DR); +} + +int32 Merc::CalcPR() +{ + PR = _basePR + itembonuses.PR + spellbonuses.PR + aabonuses.PR; + + if(PR < 1) + PR = 1; + + return(PR); +} + +int32 Merc::CalcCR() +{ + CR = _baseCR + itembonuses.CR + spellbonuses.CR + aabonuses.CR; + + if(CR < 1) + CR = 1; + + return(CR); +} + +int32 Merc::CalcCorrup() +{ + Corrup = _baseCorrup + itembonuses.Corrup + spellbonuses.Corrup + aabonuses.Corrup; + + return(Corrup); +} + +int32 Merc::CalcATK() { + ATK = _baseATK + itembonuses.ATK + spellbonuses.ATK + aabonuses.ATK + GroupLeadershipAAOffenseEnhancement(); + return(ATK); +} + +int32 Merc::CalcAC() { + //spell AC bonuses are added directly to natural total + AC = _baseAC + spellbonuses.AC; + return(AC); +} + +int32 Merc::CalcHPRegen() { + int32 regen = hp_regen + itembonuses.HPRegen + spellbonuses.HPRegen; + + regen += aabonuses.HPRegen + GroupLeadershipAAHealthRegeneration(); + + return (regen * RuleI(Character, HPRegenMultiplier) / 100); +} + +int32 Merc::CalcHPRegenCap() +{ + int cap = RuleI(Character, ItemHealthRegenCap) + itembonuses.HeroicSTA/25; + + cap += aabonuses.ItemHPRegenCap + spellbonuses.ItemHPRegenCap + itembonuses.ItemHPRegenCap; + + return (cap * RuleI(Character, HPRegenMultiplier) / 100); +} + +int32 Merc::CalcMaxHP() { + float nd = 10000; + max_hp = (CalcBaseHP() + itembonuses.HP); + + //The AA desc clearly says it only applies to base hp.. + //but the actual effect sent on live causes the client + //to apply it to (basehp + itemhp).. I will oblige to the client's whims over + //the aa description + nd += aabonuses.MaxHP; //Natural Durability, Physical Enhancement, Planar Durability + + max_hp = (float)max_hp * (float)nd / (float)10000; //this is to fix the HP-above-495k issue + max_hp += spellbonuses.HP + aabonuses.HP; + + max_hp += GroupLeadershipAAHealthEnhancement(); + + max_hp += max_hp * ((spellbonuses.MaxHPChange + itembonuses.MaxHPChange) / 10000.0f); + + if (cur_hp > max_hp) + cur_hp = max_hp; + + int hp_perc_cap = spellbonuses.HPPercCap[0]; + if(hp_perc_cap) { + int curHP_cap = (max_hp * hp_perc_cap) / 100; + if (cur_hp > curHP_cap || (spellbonuses.HPPercCap[1] && cur_hp > spellbonuses.HPPercCap[1])) + cur_hp = curHP_cap; + } + + return max_hp; +} + +int32 Merc::CalcBaseHP() +{ + return base_hp; +} + +int32 Merc::CalcMaxMana() +{ + switch(GetCasterClass()) + { + case 'I': + case 'W': { + max_mana = (CalcBaseMana() + itembonuses.Mana + spellbonuses.Mana + GroupLeadershipAAManaEnhancement()); + break; + } + case 'N': { + max_mana = 0; + break; + } + default: { + LogFile->write(EQEMuLog::Debug, "Invalid Class '%c' in CalcMaxMana", GetCasterClass()); + max_mana = 0; + break; + } + } + if (max_mana < 0) { + max_mana = 0; + } + + if (cur_mana > max_mana) { + cur_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 EQDEBUG >= 11 + LogFile->write(EQEMuLog::Debug, "Merc::CalcMaxMana() called for %s - returning %d", GetName(), max_mana); +#endif + return max_mana; +} + +int32 Merc::CalcBaseMana() +{ + return base_mana; +} + +int32 Merc::CalcBaseManaRegen() +{ + uint8 clevel = GetLevel(); + int32 regen = 0; + if (IsSitting()) + { + if(HasSkill(SkillMeditate)) + regen = (((GetSkill(SkillMeditate) / 10) + (clevel - (clevel / 4))) / 4) + 4; + else + regen = 2; + } + else { + regen = 2; + } + return regen; +} + +int32 Merc::CalcManaRegen() +{ + int32 regen = 0; + if (IsSitting()) + { + BuffFadeBySitModifier(); + if(HasSkill(SkillMeditate)) { + this->_medding = true; + regen = ((GetSkill(SkillMeditate) / 10) + mana_regen); + regen += spellbonuses.ManaRegen + itembonuses.ManaRegen; + } + else + regen = mana_regen + spellbonuses.ManaRegen + itembonuses.ManaRegen; + } + else { + this->_medding = false; + regen = mana_regen + spellbonuses.ManaRegen + itembonuses.ManaRegen; + } + + if(GetCasterClass() == 'I') + regen += (itembonuses.HeroicINT / 25); + else if(GetCasterClass() == 'W') + regen += (itembonuses.HeroicWIS / 25); + else + regen = 0; + + //AAs + regen += aabonuses.ManaRegen; + + return (regen * RuleI(Character, ManaRegenMultiplier) / 100); +} + +int32 Merc::CalcManaRegenCap() +{ + int32 cap = RuleI(Character, ItemManaRegenCap) + aabonuses.ItemManaRegenCap; + switch(GetCasterClass()) + { + case 'I': + cap += (itembonuses.HeroicINT / 25); + break; + case 'W': + cap += (itembonuses.HeroicWIS / 25); + break; + } + + return (cap * RuleI(Character, ManaRegenMultiplier) / 100); +} + +void Merc::CalcMaxEndurance() +{ + max_end = CalcBaseEndurance() + spellbonuses.Endurance + itembonuses.Endurance + aabonuses.Endurance; + + if (max_end < 0) { + max_end = 0; + } + + if (cur_end > max_end) { + cur_end = 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; + } +} + +int32 Merc::CalcBaseEndurance() +{ + int32 base_end = 0; + int32 base_endurance = 0; + int32 ConvertedStats = 0; + int32 sta_end = 0; + int Stats = 0; + + if(GetClientVersion() >= EQClientSoD && RuleB(Character, SoDClientUseSoDHPManaEnd)) { + int HeroicStats = 0; + + Stats = ((GetSTR() + GetSTA() + GetDEX() + GetAGI()) / 4); + HeroicStats = ((GetHeroicSTR() + GetHeroicSTA() + GetHeroicDEX() + GetHeroicAGI()) / 4); + + if (Stats > 100) { + ConvertedStats = (((Stats - 100) * 5 / 2) + 100); + if (Stats > 201) { + ConvertedStats -= ((Stats - 201) * 5 / 4); + } + } + else { + ConvertedStats = Stats; + } + + if (GetLevel() < 41) { + sta_end = (GetLevel() * 75 * ConvertedStats / 1000); + base_endurance = (GetLevel() * 15); + } + else if (GetLevel() < 81) { + sta_end = ((3 * ConvertedStats) + ((GetLevel() - 40) * 15 * ConvertedStats / 100)); + base_endurance = (600 + ((GetLevel() - 40) * 30)); + } + else { + sta_end = (9 * ConvertedStats); + base_endurance = (1800 + ((GetLevel() - 80) * 18)); + } + base_end = (base_endurance + sta_end + (HeroicStats * 10)); + } + else + { + Stats = GetSTR()+GetSTA()+GetDEX()+GetAGI(); + int LevelBase = GetLevel() * 15; + + int at_most_800 = Stats; + if(at_most_800 > 800) + at_most_800 = 800; + + int Bonus400to800 = 0; + int HalfBonus400to800 = 0; + int Bonus800plus = 0; + int HalfBonus800plus = 0; + + int BonusUpto800 = int( at_most_800 / 4 ) ; + if(Stats > 400) { + Bonus400to800 = int( (at_most_800 - 400) / 4 ); + HalfBonus400to800 = int( std::max( ( at_most_800 - 400 ), 0 ) / 8 ); + + if(Stats > 800) { + Bonus800plus = int( (Stats - 800) / 8 ) * 2; + HalfBonus800plus = int( (Stats - 800) / 16 ); + } + } + int bonus_sum = BonusUpto800 + Bonus400to800 + HalfBonus400to800 + Bonus800plus + HalfBonus800plus; + + base_end = LevelBase; + + //take all of the sums from above, then multiply by level*0.075 + base_end += ( bonus_sum * 3 * GetLevel() ) / 40; + } + return base_end; +} + +int32 Merc::CalcEnduranceRegen() { + int32 regen = int32(GetLevel() * 4 / 10) + 2; + regen += aabonuses.EnduranceRegen + spellbonuses.EnduranceRegen + itembonuses.EnduranceRegen; + + return (regen * RuleI(Character, EnduranceRegenMultiplier) / 100); +} + +int32 Merc::CalcEnduranceRegenCap() { + int cap = (RuleI(Character, ItemEnduranceRegenCap) + itembonuses.HeroicSTR/25 + itembonuses.HeroicDEX/25 + itembonuses.HeroicAGI/25 + itembonuses.HeroicSTA/25); + + return (cap * RuleI(Character, EnduranceRegenMultiplier) / 100); +} + +void Merc::SetEndurance(int32 newEnd) +{ + /*Endurance can't be less than 0 or greater than max*/ + if(newEnd < 0) + newEnd = 0; + else if(newEnd > GetMaxEndurance()){ + newEnd = GetMaxEndurance(); + } + + cur_end = newEnd; +} + +void Merc::DoEnduranceUpkeep() { + + if (!HasEndurUpkeep()) + return; + + int upkeep_sum = 0; + int cost_redux = spellbonuses.EnduranceReduction + itembonuses.EnduranceReduction; + + bool has_effect = false; + uint32 buffs_i; + uint32 buff_count = GetMaxTotalSlots(); + for (buffs_i = 0; buffs_i < buff_count; buffs_i++) { + if (buffs[buffs_i].spellid != SPELL_UNKNOWN) { + int upkeep = spells[buffs[buffs_i].spellid].EndurUpkeep; + if(upkeep > 0) { + has_effect = true; + if(cost_redux > 0) { + if(upkeep <= cost_redux) + continue; //reduced to 0 + upkeep -= cost_redux; + } + if((upkeep+upkeep_sum) > GetEndurance()) { + //they do not have enough to keep this one going. + BuffFadeBySlot(buffs_i); + } else { + upkeep_sum += upkeep; + } + } + } + } + + if(upkeep_sum != 0) + SetEndurance(GetEndurance() - upkeep_sum); + + if (!has_effect) + SetEndurUpkeep(false); +} + +void Merc::CalcRestState() { + + // This method calculates rest state HP and mana regeneration. + // The bot must have been out of combat for RuleI(Character, RestRegenTimeToActivate) seconds, + // must be sitting down, and must not have any detrimental spells affecting them. + // + if(!RuleI(Character, RestRegenPercent)) + return; + + RestRegenHP = RestRegenMana = RestRegenEndurance = 0; + + if(IsEngaged() || !IsSitting()) + return; + + if(!rest_timer.Check(false)) + return; + + uint32 buff_count = GetMaxTotalSlots(); + for (unsigned int j = 0; j < buff_count; j++) { + if(buffs[j].spellid != SPELL_UNKNOWN) { + if(IsDetrimentalSpell(buffs[j].spellid) && (buffs[j].ticsremaining > 0)) + if(!DetrimentalSpellAllowsRest(buffs[j].spellid)) + return; + } + } + + RestRegenHP = (GetMaxHP() * RuleI(Character, RestRegenPercent) / 100); + + RestRegenMana = (GetMaxMana() * RuleI(Character, RestRegenPercent) / 100); + + if(RuleB(Character, RestRegenEndurance)) + RestRegenEndurance = (GetMaxEndurance() * RuleI(Character, RestRegenPercent) / 100); +} + +bool Merc::HasSkill(SkillUseTypes skill_id) const { + return((GetSkill(skill_id) > 0) && CanHaveSkill(skill_id)); +} + +bool Merc::CanHaveSkill(SkillUseTypes skill_id) const { + return(database.GetSkillCap(GetClass(), skill_id, RuleI(Character, MaxLevel)) > 0); + //if you don't have it by max level, then odds are you never will? +} + +uint16 Merc::MaxSkill(SkillUseTypes skillid, uint16 class_, uint16 level) const { + return(database.GetSkillCap(class_, skillid, level)); +} + +void Merc::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) { + if(ns) { + Mob::FillSpawnStruct(ns, ForWho); + + ns->spawn.afk = 0; + ns->spawn.lfg = 0; + ns->spawn.anon = 0; + ns->spawn.gm = 0; + ns->spawn.guildID = 0xFFFFFFFF; // 0xFFFFFFFF = NO GUILD, 0 = Unknown Guild + ns->spawn.is_npc = 1; // 0=no, 1=yes + ns->spawn.is_pet = 0; + ns->spawn.guildrank = 0; + ns->spawn.showhelm = 1; + ns->spawn.flymode = 0; + ns->spawn.NPC = 1; // 0=player,1=npc,2=pc corpse,3=npc corpse + ns->spawn.IsMercenary = 1; + + unsigned int i; + //should not include 21 (SLOT_AMMO) + for (i = 0; i < MainAmmo; i++) { + if(equipment[i] == 0) + continue; + const Item_Struct* item = database.GetItem(equipment[i]); + if(item) + { + ns->spawn.equipment[i] = item->Material; + ns->spawn.colors[i].color = item->Color; + } + } + } +} + +bool Merc::Process() +{ + if(IsStunned() && stunned_timer.Check()) + { + this->stunned = false; + this->stunned_timer.Disable(); + } + + if (GetDepop()) + { + SetMercCharacterID(0); + SetOwnerID(0); + return false; + } + + if(!GetMercOwner()) { + //p_depop = true; //this was causing a crash - removed merc from entity list, but not group + //return false; //merc can live after client dies, not sure how long + } + + if(IsSuspended()) + { + return false; + } + + if (HasGroup() && GetMercOwner() && GetFollowID() == 0) { + SetFollowID(GetMercOwner()->GetID()); + } + + SpellProcess(); + + if(tic_timer.Check()) + { + //6 seconds, or whatever the rule is set to has passed, send this position to everyone to avoid ghosting + if(!IsMoving() && !IsEngaged()) + { + SendPosition(); + if(IsSitting()) { + if(!rest_timer.Enabled()) { + rest_timer.Start(RuleI(Character, RestRegenTimeToActivate) * 1000); + } + } + } + + BuffProcess(); + + CalcRestState(); + + if(GetHP() < GetMaxHP()) + SetHP(GetHP() + CalcHPRegen() + RestRegenHP); + + if(GetMana() < GetMaxMana()) + SetMana(GetMana() + CalcManaRegen() + RestRegenMana); + + if(GetEndurance() < GetMaxEndurance()) + SetEndurance(GetEndurance() + CalcEnduranceRegen() + RestRegenEndurance); + } + + if(confidence_timer.Check()) { + _check_confidence = true; + } + + if (sendhpupdate_timer.Check()) { + SendHPUpdate(); + } + + if (endupkeep_timer.Check() && GetHP() > 0){ + DoEnduranceUpkeep(); + } + + if (IsStunned() || IsMezzed()) + return true; + + // Merc AI + AI_Process(); + + return true; +} + +bool Merc::IsMercCasterCombatRange(Mob *target) { + bool result = false; + + if(target) { + float range = MercAISpellRange; + + range *= range; + + // half the max so the merc doesn't always stop at max range to allow combat movement + range *= .5; + + float targetDistance = DistNoRootNoZ(*target); + + if(targetDistance > range) + result = false; + else + result = true; + } + + return result; +} + +void Merc::AI_Process() { + if(!IsAIControlled()) + return; + + if(IsCasting()) + return; + + // A bot wont start its AI if not grouped + if(!HasGroup()) { + return; + } + + Mob* MercOwner = GetOwner(); + + if(GetAppearance() == eaDead) + { + if(!MercOwner) + { + Depop(); + } + return; + } + + // The merc needs an owner + if(!MercOwner) { + //SetTarget(0); + //SetOwnerID(0); + // TODO: Need to wait and try casting rez if merc is a healer with a dead owner + return; + } + + /* + try { + if(MercOwner->CastToClient()->IsDead()) { + SetTarget(0); + SetOwnerID(0); + return; + } + } + catch(...) { + SetTarget(0); + SetOwnerID(0); + return; + } + */ + + if(check_target_timer.Check()) { + CheckHateList(); + } + + if(IsEngaged()) + { + if(rest_timer.Enabled()) + rest_timer.Disable(); + + if(IsRooted()) + SetTarget(hate_list.GetClosest(this)); + else + FindTarget(); + + if(!GetTarget()) + return; + + if(HasPet()) + GetPet()->SetTarget(GetTarget()); + + if(!IsSitting()) + FaceTarget(GetTarget()); + + if(DivineAura()) + return; + + int hateCount = entity_list.GetHatedCount(this, nullptr); + if(GetHatedCount() < hateCount) { + SetHatedCount(hateCount); + + if(!CheckConfidence()) { + if(!confidence_timer.Enabled()) { + confidence_timer.Start(10000); + } + } + } + + //Check specific conditions for merc to lose confidence and flee (or regain confidence once fleeing) + if(_check_confidence) { + //not already running + if(!_lost_confidence) { + //and fail confidence check + if(!CheckConfidence()) { + _lost_confidence = true; + + //move to bottom of hate lists? + //Iterate though hatelist + // SetHate(other, hate, damage) + + if(RuleB(Combat, EnableFearPathing)) { + CalculateNewFearpoint(); + if(curfp) { + return; + } + } + else { + Stun(12000 - (6000 - tic_timer.GetRemainingTime())); + } + } + } + else { //are fleeing due to lost confidence + if(CheckConfidence()) { //passed test - regain confidence + _lost_confidence = false; + } + } + + //they are in flee mode + if(_lost_confidence) + return; + } + + // Let's check if we have a los with our target. + // If we don't, our hate_list is wiped. + // Else, it was causing the merc to aggro behind wall etc... causing massive trains. + if(!CheckLosFN(GetTarget()) || GetTarget()->IsMezzed() || !IsAttackAllowed(GetTarget())) { + WipeHateList(); + + if(IsMoving()) { + SetHeading(0); + SetRunAnimSpeed(0); + + if(moved) { + moved = false; + SendPosition(); + SetMoving(false); + } + } + + return; + } + + bool atCombatRange = false; + + float meleeDistance = GetMaxMeleeRangeToTarget(GetTarget()); + + if(GetClass() == SHADOWKNIGHT || GetClass() == PALADIN || GetClass() == WARRIOR) { + meleeDistance = meleeDistance * .30; + } + else { + meleeDistance *= (float)zone->random.Real(.50, .85); + } + if(IsMercCaster() && GetLevel() > 12) { + if(IsMercCasterCombatRange(GetTarget())) + atCombatRange = true; + } + else if(DistNoRoot(*GetTarget()) <= meleeDistance) { + atCombatRange = true; + } + + if(atCombatRange) + { + if(IsMoving()) + { + SetHeading(CalculateHeadingToTarget(GetTarget()->GetX(), GetTarget()->GetY())); + SetRunAnimSpeed(0); + + if(moved) { + moved = false; + SendPosition(); + SetMoving(false); + } + } + + if(AImovement_timer->Check()) + { + if(!IsMoving() && GetClass() == ROGUE && !BehindMob(GetTarget(), GetX(), GetY())) + { + // Move the rogue to behind the mob + float newX = 0; + float newY = 0; + float newZ = 0; + + if(PlotPositionAroundTarget(GetTarget(), newX, newY, newZ)) + { + CalculateNewPosition2(newX, newY, newZ, GetRunspeed()); + return; + } + } + else if(!IsMoving() && GetClass() != ROGUE && (DistNoRootNoZ(*GetTarget()) < GetTarget()->GetSize())) + { + // If we are not a rogue trying to backstab, let's try to adjust our melee range so we don't appear to be bunched up + float newX = 0; + float newY = 0; + float newZ = 0; + + if(PlotPositionAroundTarget(GetTarget(), newX, newY, newZ, false) && GetArchetype() != ARCHETYPE_CASTER) + { + CalculateNewPosition2(newX, newY, newZ, GetRunspeed()); + return; + } + } + + if(IsMoving()) + SendPosUpdate(); + else + SendPosition(); + } + + if(!IsMercCaster() && GetTarget() && !IsStunned() && !IsMezzed() && (GetAppearance() != eaDead)) + { + // we can't fight if we don't have a target, are stun/mezzed or dead.. + // Stop attacking if the target is enraged + if(IsEngaged() && !BehindMob(GetTarget(), GetX(), GetY()) && GetTarget()->IsEnraged()) + return; + //TODO: Implement Stances. + /*if(GetBotStance() == BotStancePassive) + return;*/ + + // First, special attack per class (kick, backstab etc..) + DoClassAttacks(GetTarget()); + + //try main hand first + if(attack_timer.Check()) + { + Attack(GetTarget(), MainPrimary); + + bool tripleSuccess = false; + + if(GetOwner() && GetTarget() && CanThisClassDoubleAttack()) + { + if(GetOwner()) { + Attack(GetTarget(), MainPrimary, true); + } + + if(GetOwner() && GetTarget() && GetSpecialAbility(SPECATK_TRIPLE)) { + tripleSuccess = true; + Attack(GetTarget(), MainPrimary, true); + } + + //quad attack, does this belong here?? + if(GetOwner() && GetTarget() && GetSpecialAbility(SPECATK_QUAD)) { + Attack(GetTarget(), MainPrimary, true); + } + } + + //Live AA - Flurry, Rapid Strikes ect (Flurry does not require Triple Attack). + int16 flurrychance = aabonuses.FlurryChance + spellbonuses.FlurryChance + itembonuses.FlurryChance; + + if (GetTarget() && flurrychance) + { + if(zone->random.Roll(flurrychance)) + { + Message_StringID(MT_NPCFlurry, YOU_FLURRY); + Attack(GetTarget(), MainPrimary, false); + Attack(GetTarget(), MainPrimary, false); + } + } + + int16 ExtraAttackChanceBonus = spellbonuses.ExtraAttackChance + itembonuses.ExtraAttackChance + aabonuses.ExtraAttackChance; + + if (GetTarget() && ExtraAttackChanceBonus) { + if(zone->random.Roll(ExtraAttackChanceBonus)) + { + Attack(GetTarget(), MainPrimary, false); + } + } + } + + // TODO: Do mercs berserk? Find this out on live... + //if (GetClass() == WARRIOR || GetClass() == BERSERKER) { + // if(GetHP() > 0 && !berserk && this->GetHPRatio() < 30) { + // entity_list.MessageClose_StringID(this, false, 200, 0, BERSERK_START, GetName()); + // this->berserk = true; + // } + // if (berserk && this->GetHPRatio() > 30) { + // entity_list.MessageClose_StringID(this, false, 200, 0, BERSERK_END, GetName()); + // this->berserk = false; + // } + //} + + //now off hand + if(GetTarget() && attack_dw_timer.Check() && CanThisClassDualWield()) + { + int weapontype = 0; // No weapon type + bool bIsFist = true; + + if(bIsFist || ((weapontype != ItemType2HSlash) && (weapontype != ItemType2HPiercing) && (weapontype != ItemType2HBlunt))) + { + float DualWieldProbability = 0.0f; + + int16 Ambidexterity = aabonuses.Ambidexterity + spellbonuses.Ambidexterity + itembonuses.Ambidexterity; + DualWieldProbability = (GetSkill(SkillDualWield) + GetLevel() + Ambidexterity) / 400.0f; // 78.0 max + int16 DWBonus = spellbonuses.DualWieldChance + itembonuses.DualWieldChance; + DualWieldProbability += DualWieldProbability*float(DWBonus)/ 100.0f; + + // Max 78% of DW + if (zone->random.Roll(DualWieldProbability)) + { + Attack(GetTarget(), MainSecondary); // Single attack with offhand + + if(CanThisClassDoubleAttack()) { + if(GetTarget() && GetTarget()->GetHP() > -10) + Attack(GetTarget(), MainSecondary); // Single attack with offhand + } + } + } + } + } + } + else + { + if(GetTarget()->IsFeared() && !spellend_timer.Enabled()) { + // This is a mob that is fleeing either because it has been feared or is low on hitpoints + //TODO: Implement Stances. + //if(GetStance() != MercStancePassive) + AI_PursueCastCheck(); + } + + if (AImovement_timer->Check()) + { + if(!IsRooted()) { + mlog(AI__WAYPOINTS, "Pursuing %s while engaged.", GetTarget()->GetCleanName()); + CalculateNewPosition2(GetTarget()->GetX(), GetTarget()->GetY(), GetTarget()->GetZ(), GetRunspeed()); + return; + } + + if(IsMoving()) + SendPosUpdate(); + else + SendPosition(); + } + } // end not in combat range + + if(!IsMoving() && !spellend_timer.Enabled()) + { + //TODO: Implement Stances. + //if(GetStance() == MercStancePassive) + // return; + + if(AI_EngagedCastCheck()) { + MercMeditate(false); + } + else if(GetArchetype() == ARCHETYPE_CASTER) + MercMeditate(true); + } + } + else + { + // Not engaged in combat + SetTarget(0); + SetHatedCount(0); + confidence_timer.Disable(); + _check_confidence = false; + + if(!check_target_timer.Enabled()) + check_target_timer.Start(2000, false); + + if(!IsMoving() && AIthink_timer->Check() && !spellend_timer.Enabled()) + { + //TODO: Implement passive stances. + //if(GetStance() != MercStancePassive) { + if(!AI_IdleCastCheck() && !IsCasting()) { + if(GetArchetype() == ARCHETYPE_CASTER) { + MercMeditate(true); + } + } + } + + if(AImovement_timer->Check()) + { + if(GetFollowID()) + { + Mob* follow = entity_list.GetMob(GetFollowID()); + + if(follow) + { + float dist = DistNoRoot(*follow); + float speed = GetRunspeed(); + + if(dist < GetFollowDistance() + 1000) + speed = GetWalkspeed(); + + SetRunAnimSpeed(0); + + if(dist > GetFollowDistance()) { + CalculateNewPosition2(follow->GetX(), follow->GetY(), follow->GetZ(), speed); + if(rest_timer.Enabled()) + rest_timer.Disable(); + return; + } + else + { + if(moved) + { + moved=false; + SendPosition(); + SetMoving(false); + } + } + } + } + } + } +} + +void Merc::AI_Start(int32 iMoveDelay) { + NPC::AI_Start(iMoveDelay); + if (!pAIControlled) + return; + + if (merc_spells.size() == 0) { + AIautocastspell_timer->SetTimer(1000); + AIautocastspell_timer->Disable(); + } else { + AIautocastspell_timer->SetTimer(750); + AIautocastspell_timer->Start(RandomTimer(0, 2000), false); + } + + if (NPCTypedata_ours) { + ProcessSpecialAbilities(NPCTypedata_ours->special_abilities); + } + + SendTo(GetX(), GetY(), GetZ()); + SetChanged(); + SaveGuardSpot(); +} + +void Merc::AI_Stop() { + NPC::AI_Stop(); + Mob::AI_Stop(); +} + +bool Merc::AI_EngagedCastCheck() { + bool result = false; + bool failedToCast = false; + + if (GetTarget() && AIautocastspell_timer->Check(false)) + { + AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting. + + mlog(AI__SPELLS, "Engaged autocast check triggered (MERCS)."); + + int8 mercClass = GetClass(); + + switch(mercClass) + { + case TANK: + if (!AICastSpell(GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) { + if (!AICastSpell(GetChanceToCastBySpellType(SpellType_InCombatBuff), SpellType_InCombatBuff)) { + failedToCast = true; + } + } + break; + case HEALER: + if(!entity_list.Merc_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_Heal), MercAISpellRange, SpellType_Heal)) { + if(!entity_list.Merc_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_Buff), MercAISpellRange, SpellType_Buff)) { + failedToCast = true; + } + } + break; + case MELEEDPS: + if (!AICastSpell(GetChanceToCastBySpellType(SpellType_Escape), SpellType_Escape)) { + if (!AICastSpell(GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) { + if (!AICastSpell(GetChanceToCastBySpellType(SpellType_InCombatBuff), SpellType_InCombatBuff)) { + failedToCast = true; + } + } + } + break; + case CASTERDPS: + if (!AICastSpell(GetChanceToCastBySpellType(SpellType_Escape), SpellType_Escape)) { + if (!AICastSpell(GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) { + failedToCast = true; + } + } + break; + } + + if(!AIautocastspell_timer->Enabled()) { + AIautocastspell_timer->Start(RandomTimer(100, 250), false); + } + + if(!failedToCast) + result = true; + } + + return result; +} + +bool Merc::AI_IdleCastCheck() { + bool result = false; + bool failedToCast = false; + + if (AIautocastspell_timer->Check(false)) { +#if MobAI_DEBUG_Spells >= 25 + std::cout << "Non-Engaged autocast check triggered: " << this->GetCleanName() << std::endl; +#endif + AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting. + + //Ok, IdleCastCheck depends of class. + int8 mercClass = GetClass(); + + switch(mercClass) + { + case TANK: + failedToCast = true; + break; + case HEALER: + if(!entity_list.Merc_AICheckCloseBeneficialSpells(this, 100, MercAISpellRange, SpellType_Cure)) { + if(!entity_list.Merc_AICheckCloseBeneficialSpells(this, 100, MercAISpellRange, SpellType_Heal)) { + if(!entity_list.Merc_AICheckCloseBeneficialSpells(this, 100, MercAISpellRange, SpellType_Resurrect)) { + if(!entity_list.Merc_AICheckCloseBeneficialSpells(this, 100, MercAISpellRange, SpellType_Buff)) { + failedToCast = true; + } + } + } + } + result = true; + break; + case MELEEDPS: + if(!entity_list.Merc_AICheckCloseBeneficialSpells(this, 100, MercAISpellRange, SpellType_Buff)) { + failedToCast = true; + } + break; + case CASTERDPS: + failedToCast = true; + break; + } + + if(!AIautocastspell_timer->Enabled()) + AIautocastspell_timer->Start(RandomTimer(500, 1000), false); + + if(!failedToCast) + result = true; + } + + return result; +} + +bool EntityList::Merc_AICheckCloseBeneficialSpells(Merc* caster, uint8 iChance, float iRange, uint32 iSpellTypes) { + + if((iSpellTypes&SpellTypes_Detrimental) != 0) { + //according to live, you can buff and heal through walls... + //now with PCs, this only applies if you can TARGET the target, but + // according to Rogean, Live NPCs will just cast through walls/floors, no problem.. + // + // This check was put in to address an idle-mob CPU issue + _log(AI__ERROR, "Error: detrimental spells requested from AICheckCloseBeneficialSpells!!"); + return(false); + } + + if(!caster) + return false; + + if(!caster->AI_HasSpells()) + return false; + + if (iChance < 100) { + int8 tmp = zone->random.Int(1, 100); + if (tmp > iChance) + return false; + } + + int8 mercCasterClass = caster->GetClass(); + + if(caster->HasGroup()) { + if( mercCasterClass == HEALER) { + if( iSpellTypes == SpellType_Heal ) { + if(caster->AICastSpell(100, SpellType_Heal)) + return true; + } + + if( iSpellTypes == SpellType_Cure ) { + if(caster->AICastSpell(100, SpellType_Cure)) + return true; + } + + if( iSpellTypes == SpellType_Resurrect ) { + if(caster->AICastSpell(100, SpellType_Resurrect)) + return true; + } + } + + //Ok for the buffs.. + if( iSpellTypes == SpellType_Buff) { + if(caster->AICastSpell(100, SpellType_Buff)) + return true; + } + } + + return false; +} + +bool Merc::AIDoSpellCast(uint16 spellid, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore) { + bool result = false; + MercSpell mercSpell = GetMercSpellBySpellID(this, spellid); + + // manacost has special values, -1 is no mana cost, -2 is instant cast (no mana) + int32 manaCost = mana_cost; + + if (manaCost == -1) + manaCost = spells[spellid].mana; + else if (manaCost == -2) + manaCost = 0; + + int32 extraMana = 0; + int32 hasMana = GetMana(); + + float dist2 = 0; + + if (mercSpell.type & SpellType_Escape) { + dist2 = 0; + } else + dist2 = DistNoRoot(*tar); + + if (((((spells[spellid].targettype==ST_GroupTeleport && mercSpell.type==SpellType_Heal) + || spells[spellid].targettype==ST_AECaster + || spells[spellid].targettype==ST_Group + || spells[spellid].targettype==ST_AEBard) + && dist2 <= spells[spellid].aoerange*spells[spellid].aoerange) + || dist2 <= GetActSpellRange(spellid, spells[spellid].range)*GetActSpellRange(spellid, spells[spellid].range)) && (mana_cost <= GetMana() || GetMana() == GetMaxMana())) + { + SetRunAnimSpeed(0); + SendPosition(); + SetMoving(false); + + result = CastSpell(spellid, tar->GetID(), 1, -1, mana_cost, oDontDoAgainBefore, -1, -1, 0, 0); + + if(IsCasting() && IsSitting()) + Stand(); + } + + // if the spell wasn't casted, then take back any extra mana that was given to the bot to cast that spell + if(!result) { + SetMana(hasMana); + extraMana = false; + } + else { //handle spell recast and recast timers + SetSpellTimeCanCast(mercSpell.spellid, spells[spellid].recast_time); + + if(spells[spellid].EndurTimerIndex > 0) { + SetSpellRecastTimer(spells[spellid].EndurTimerIndex, spellid, spells[spellid].recast_time); + } + } + + return result; +} + +bool Merc::AICastSpell(int8 iChance, int32 iSpellTypes) { + + if(!AI_HasSpells()) + return false; + + if (iChance < 100) { + if (zone->random.Int(0, 100) > iChance){ + return false; + } + } + + int8 mercClass = GetClass(); + uint8 mercLevel = GetLevel(); + + bool checked_los = false; //we do not check LOS until we are absolutely sure we need to, and we only do it once. + bool castedSpell = false; + bool isDiscipline = false; + + if(HasGroup()) { + Group *g = GetGroup(); + + if(g) { + MercSpell selectedMercSpell; + selectedMercSpell.spellid = 0; + selectedMercSpell.stance = 0; + selectedMercSpell.type = 0; + selectedMercSpell.slot = 0; + selectedMercSpell.proc_chance = 0; + selectedMercSpell.time_cancast = 0; + + switch(mercClass) + { + case TANK: + case MELEEDPS: + isDiscipline = true; + break; + default: + isDiscipline = false; + break; + } + + switch (iSpellTypes) { + case SpellType_Heal: { + Mob* tar = nullptr; + int8 numToHeal = g->GetNumberNeedingHealedInGroup(IsEngaged() ? 75 : 95, true); + int8 checkHPR = IsEngaged() ? 95 : 99; + int8 checkPetHPR = IsEngaged() ? 95 : 99; + + //todo: check stance to determine healing spell selection + + for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { + if(g->members[i] && !g->members[i]->qglobal) { + int8 hpr = (int8)g->members[i]->GetHPRatio(); + + if(g->members[i]->HasPet() && g->members[i]->GetPet()->GetHPRatio() < checkHPR) { + if(!tar || ((g->members[i]->GetPet()->GetHPRatio() + 25) < tar->GetHPRatio())) { + tar = g->members[i]->GetPet(); + checkPetHPR = g->members[i]->GetPet()->GetHPRatio() + 25; + } + } + + if(hpr > checkHPR) { + continue; + } + + if(IsEngaged() && (g->members[i]->GetClass() == NECROMANCER && hpr >= 50) + || (g->members[i]->GetClass() == SHAMAN && hpr >= 80)) { + //allow necros to lifetap & shaman to canni without wasting mana + continue; + } + + if(hpr < checkHPR && g->members[i] == GetMercOwner()) { + if(!tar || (hpr < tar->GetHPRatio() || (tar->IsPet() && hpr < checkPetHPR))) + tar = g->members[i]; //check owner first + } + else if(hpr < checkHPR && g->HasRole(g->members[i], RoleTank)){ + if(!tar || (hpr < tar->GetHPRatio() || (tar->IsPet() && hpr < checkPetHPR))) + tar = g->members[i]; + } + else if( hpr < checkHPR && (!tar || (hpr < tar->GetHPRatio() || (tar->IsPet() && hpr < checkPetHPR)))) { + tar = g->members[i]; + } + } + } + + if(numToHeal > 2) { + selectedMercSpell = GetBestMercSpellForGroupHeal(this); + } + + if(tar && selectedMercSpell.spellid == 0) { + if(tar->GetHPRatio() < 15) { + //check for very fast heals first (casting time < 1 s) + selectedMercSpell = GetBestMercSpellForVeryFastHeal(this); + + //check for fast heals next (casting time < 2 s) + if(selectedMercSpell.spellid == 0) { + selectedMercSpell = GetBestMercSpellForFastHeal(this); + } + + //get regular heal + if(selectedMercSpell.spellid == 0) { + selectedMercSpell = GetBestMercSpellForRegularSingleTargetHeal(this); + } + } + else if (tar->GetHPRatio() < 35) { + //check for fast heals next (casting time < 2 s) + selectedMercSpell = GetBestMercSpellForFastHeal(this); + + //get regular heal + if(selectedMercSpell.spellid == 0) { + selectedMercSpell = GetBestMercSpellForRegularSingleTargetHeal(this); + } + } + else if (tar->GetHPRatio() < 80) { + selectedMercSpell = GetBestMercSpellForPercentageHeal(this); + + //get regular heal + if(selectedMercSpell.spellid == 0) { + selectedMercSpell = GetBestMercSpellForRegularSingleTargetHeal(this); + } + } + else { + //check for heal over time. if not present, try it first + if(!tar->FindType(SE_HealOverTime)) { + selectedMercSpell = GetBestMercSpellForHealOverTime(this); + + //get regular heal + if(selectedMercSpell.spellid == 0) { + selectedMercSpell = GetBestMercSpellForRegularSingleTargetHeal(this); + } + } + } + } + + if(selectedMercSpell.spellid > 0) { + castedSpell = AIDoSpellCast(selectedMercSpell.spellid, tar, -1); + } + + if(castedSpell) { + char* gmsg = 0; + + if(tar != this) { + //we don't need spam of bots healing themselves + MakeAnyLenString(&gmsg, "Casting %s on %s.", spells[selectedMercSpell.spellid].name, tar->GetCleanName()); + if(gmsg) + { + MercGroupSay(this, gmsg); + safe_delete_array(gmsg); + } + } + } + + break; + } + case SpellType_Root: { + break; + } + case SpellType_Buff: { + + if(GetClass() == HEALER && GetManaRatio() < 50) { + return false; //mercs buff when Mana > 50% + } + + std::list buffSpellList = GetMercSpellsBySpellType(this, SpellType_Buff); + + for(std::list::iterator itr = buffSpellList.begin(); itr != buffSpellList.end(); ++itr) { + MercSpell selectedMercSpell = *itr; + + if(!((spells[selectedMercSpell.spellid].targettype == ST_Target || spells[selectedMercSpell.spellid].targettype == ST_Pet || + spells[selectedMercSpell.spellid].targettype == ST_Group || spells[selectedMercSpell.spellid].targettype == ST_GroupTeleport || + spells[selectedMercSpell.spellid].targettype == ST_Self))) { + continue; + } + + if(spells[selectedMercSpell.spellid].targettype == ST_Self) { + if( !this->IsImmuneToSpell(selectedMercSpell.spellid, this) + && (this->CanBuffStack(selectedMercSpell.spellid, mercLevel, true) >= 0)) { + + if( this->GetArchetype() == ARCHETYPE_MELEE && IsEffectInSpell(selectedMercSpell.spellid, SE_IncreaseSpellHaste)) { + continue; + } + + uint32 TempDontBuffMeBeforeTime = this->DontBuffMeBefore(); + + if(selectedMercSpell.spellid > 0) { + if(isDiscipline) { + castedSpell = UseDiscipline(selectedMercSpell.spellid, GetID()); + } + else { + castedSpell = AIDoSpellCast(selectedMercSpell.spellid, this, -1, &TempDontBuffMeBeforeTime); + + if(TempDontBuffMeBeforeTime != this->DontBuffMeBefore()) + this->SetDontBuffMeBefore(TempDontBuffMeBeforeTime); + } + } + } + } + else { + for( int i = 0; i < MAX_GROUP_MEMBERS; i++) { + if(g->members[i]) { + Mob* tar = g->members[i]; + + if( !tar->IsImmuneToSpell(selectedMercSpell.spellid, this) + && (tar->CanBuffStack(selectedMercSpell.spellid, mercLevel, true) >= 0)) { + + if( tar->GetArchetype() == ARCHETYPE_MELEE && IsEffectInSpell(selectedMercSpell.spellid, SE_IncreaseSpellHaste)) { + continue; + } + + uint32 TempDontBuffMeBeforeTime = tar->DontBuffMeBefore(); + + if(selectedMercSpell.spellid > 0) { + if(isDiscipline) { + castedSpell = UseDiscipline(selectedMercSpell.spellid, tar->GetID()); + } + else { + castedSpell = AIDoSpellCast(selectedMercSpell.spellid, tar, -1, &TempDontBuffMeBeforeTime); + + if(TempDontBuffMeBeforeTime != tar->DontBuffMeBefore()) + tar->SetDontBuffMeBefore(TempDontBuffMeBeforeTime); + } + } + } + + if(!castedSpell && tar->GetPet()) { + + //don't cast group spells on pets + if(IsGroupSpell(selectedMercSpell.spellid) + || spells[selectedMercSpell.spellid].targettype == ST_Group + || spells[selectedMercSpell.spellid].targettype == ST_GroupTeleport ) { + continue; + } + + if(!tar->GetPet()->IsImmuneToSpell(selectedMercSpell.spellid, this) + && (tar->GetPet()->CanBuffStack(selectedMercSpell.spellid, mercLevel, true) >= 0)) { + + uint32 TempDontBuffMeBeforeTime = tar->DontBuffMeBefore(); + + if(selectedMercSpell.spellid > 0) { + if(isDiscipline) { + castedSpell = UseDiscipline(selectedMercSpell.spellid, tar->GetPet()->GetID()); + } + else { + castedSpell = AIDoSpellCast(selectedMercSpell.spellid, tar->GetPet(), -1, &TempDontBuffMeBeforeTime); + + if(TempDontBuffMeBeforeTime != tar->GetPet()->DontBuffMeBefore()) + tar->GetPet()->SetDontBuffMeBefore(TempDontBuffMeBeforeTime); + } + } + } + } + } + } + } + } + break; + } + case SpellType_Nuke: { + switch(mercClass) + { + case TANK: + //check for taunt + if(CheckAETaunt()) { + if(MERC_DEBUG > 0) + GetOwner()->Message(7, "AE Taunting"); + //get AE taunt + selectedMercSpell = GetBestMercSpellForAETaunt(this); + } + + if(selectedMercSpell.spellid == 0 && CheckTaunt()) { + //get taunt + selectedMercSpell = GetBestMercSpellForTaunt(this); + } + + //get hate disc + if(selectedMercSpell.spellid == 0) { + selectedMercSpell = GetBestMercSpellForHate(this); + } + + break; + case HEALER: + break; + case MELEEDPS: + break; + case CASTERDPS: + Mob* tar = GetTarget(); + + selectedMercSpell = GetBestMercSpellForAENuke(this, tar); + + if(selectedMercSpell.spellid == 0 && !tar->GetSpecialAbility(UNSTUNABLE) && !tar->IsStunned()) { + uint8 stunChance = 15; + if(zone->random.Roll(stunChance)) { + selectedMercSpell = GetBestMercSpellForStun(this); + } + } + + if(selectedMercSpell.spellid == 0) { + uint8 lureChance = 25; + if(zone->random.Roll(lureChance)) { + selectedMercSpell = GetBestMercSpellForNukeByTargetResists(this, tar); + } + } + + if(selectedMercSpell.spellid == 0) { + selectedMercSpell = GetBestMercSpellForNuke(this); + } + + break; + } + + if(selectedMercSpell.spellid > 0) { + if(isDiscipline) { + castedSpell = UseDiscipline(selectedMercSpell.spellid, GetTarget()->GetID()); + } + else { + castedSpell = AIDoSpellCast(selectedMercSpell.spellid, GetTarget(), -1); + } + } + + break; + } + case SpellType_InCombatBuff: { + std::list buffSpellList = GetMercSpellsBySpellType(this, SpellType_InCombatBuff); + Mob* tar = this; + + for(std::list::iterator itr = buffSpellList.begin(); itr != buffSpellList.end(); ++itr) { + MercSpell selectedMercSpell = *itr; + + if(!(spells[selectedMercSpell.spellid].targettype == ST_Self)) { + continue; + } + + if(spells[selectedMercSpell.spellid].skill == SkillBackstab && spells[selectedMercSpell.spellid].targettype == ST_Self) { + if(!hidden) { + continue; + } + } + + if( !tar->IsImmuneToSpell(selectedMercSpell.spellid, this) + && (tar->CanBuffStack(selectedMercSpell.spellid, mercLevel, true) >= 0)) { + + uint32 TempDontBuffMeBeforeTime = tar->DontBuffMeBefore(); + + if(selectedMercSpell.spellid > 0) { + if(isDiscipline) { + castedSpell = UseDiscipline(selectedMercSpell.spellid, GetID()); + } + else { + castedSpell = AIDoSpellCast(selectedMercSpell.spellid, this, -1); + } + } + } + } + break; + } + case SpellType_Cure: { + Mob* tar = nullptr; + for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { + if(g->members[i] && !g->members[i]->qglobal) { + if(GetNeedsCured(g->members[i]) && (g->members[i]->DontCureMeBefore() < Timer::GetCurrentTime())) { + tar = g->members[i]; + } + } + } + + if(tar && !(g->GetNumberNeedingHealedInGroup(IsEngaged() ? 25 : 40, false) > 0) && !(g->GetNumberNeedingHealedInGroup(IsEngaged() ? 40 : 60, false) > 2)) + { + selectedMercSpell = GetBestMercSpellForCure(this, tar); + + if(selectedMercSpell.spellid == 0) + break; + + uint32 TempDontCureMeBeforeTime = tar->DontCureMeBefore(); + + castedSpell = AIDoSpellCast(selectedMercSpell.spellid, tar, spells[selectedMercSpell.spellid].mana, &TempDontCureMeBeforeTime); + + if(castedSpell) { + if(IsGroupSpell(selectedMercSpell.spellid)){ + + if(this->HasGroup()) { + Group *g = this->GetGroup(); + + if(g) { + for( int i = 0; imembers[i] && !g->members[i]->qglobal) { + if(TempDontCureMeBeforeTime != tar->DontCureMeBefore()) + g->members[i]->SetDontCureMeBefore(Timer::GetCurrentTime() + 4000); + } + } + } + } + } + else { + if(TempDontCureMeBeforeTime != tar->DontCureMeBefore()) + tar->SetDontCureMeBefore(Timer::GetCurrentTime() + 4000); + } + } + } + break; + } + case SpellType_Resurrect: { + Corpse *corpse = GetGroupMemberCorpse(); + + if(corpse) { + selectedMercSpell = GetFirstMercSpellBySpellType(this, SpellType_Resurrect); + + if(selectedMercSpell.spellid == 0) + break; + + uint32 TempDontRootMeBeforeTime = corpse->DontRootMeBefore(); + + castedSpell = AIDoSpellCast(selectedMercSpell.spellid, corpse, spells[selectedMercSpell.spellid].mana, &TempDontRootMeBeforeTime); + + //CastSpell(selectedMercSpell.spellid, corpse->GetID(), 1, -1, -1, &TempDontRootMeBeforeTime); + corpse->SetDontRootMeBefore(TempDontRootMeBeforeTime); + } + + break; + } + case SpellType_Escape: { + Mob* tar = GetTarget(); + uint8 hpr = (uint8)GetHPRatio(); + bool mayGetAggro = false; + + if(tar && (mercClass == CASTERDPS) || (mercClass == MELEEDPS)) { + mayGetAggro = HasOrMayGetAggro(); //classes have hate reducing spells + + if (mayGetAggro) { + selectedMercSpell = GetFirstMercSpellBySpellType(this, SpellType_Escape); + + if(selectedMercSpell.spellid == 0) + break; + + if(isDiscipline) { + castedSpell = UseDiscipline(selectedMercSpell.spellid, tar->GetID()); + } + else { + castedSpell = AIDoSpellCast(selectedMercSpell.spellid, tar, -1); + } + } + } + break; + } + } + } + } + + return castedSpell; +} + +void Merc::CheckHateList() { + if(check_target_timer.Enabled()) + check_target_timer.Disable(); + + if(!IsEngaged()) { + if(GetFollowID()) { + Group* g = GetGroup(); + if(g) { + Mob* MercOwner = GetOwner(); + if(MercOwner && MercOwner->GetTarget() && MercOwner->GetTarget()->IsNPC() && (MercOwner->GetTarget()->GetHateAmount(MercOwner) || MercOwner->CastToClient()->AutoAttackEnabled()) && IsAttackAllowed(MercOwner->GetTarget())) { + float range = g->HasRole(MercOwner, RolePuller) ? RuleI(Mercs, AggroRadiusPuller) : RuleI(Mercs, AggroRadius); + range = range * range; + if(MercOwner->GetTarget()->DistNoRootNoZ(*this) < range) { + AddToHateList(MercOwner->GetTarget(), 1); + } + } + else { + std::list npc_list; + entity_list.GetNPCList(npc_list); + + for(std::list::iterator itr = npc_list.begin(); itr != npc_list.end(); ++itr) { + NPC* npc = *itr; + float dist = npc->DistNoRootNoZ(*this); + int radius = RuleI(Mercs, AggroRadius); + radius *= radius; + if(dist <= radius) { + + for(int counter = 0; counter < g->GroupCount(); counter++) { + Mob* groupMember = g->members[counter]; + if(groupMember) { + if(npc->IsOnHatelist(groupMember)) { + if(!hate_list.IsOnHateList(npc)) { + float range = g->HasRole(groupMember, RolePuller) ? RuleI(Mercs, AggroRadiusPuller) : RuleI(Mercs, AggroRadius); + range *= range; + if(npc->DistNoRootNoZ(*this) < range) { + hate_list.Add(npc, 1); + } + } + } + } + } + } + } + } + } + } + } +} + +bool Merc::HasOrMayGetAggro() { + bool mayGetAggro = false; + + if(GetTarget() && GetTarget()->GetHateTop()) { + Mob *topHate = GetTarget()->GetHateTop(); + + if(topHate == this) + mayGetAggro = true; //I currently have aggro + else { + uint32 myHateAmt = GetTarget()->GetHateAmount(this); + uint32 topHateAmt = GetTarget()->GetHateAmount(topHate); + + if(myHateAmt > 0 && topHateAmt > 0 && (uint8)((myHateAmt/topHateAmt)*100) > 90) //I have 90% as much hate as top, next action may give me aggro + mayGetAggro = true; + } + } + + return mayGetAggro; +} + +bool Merc::CheckAENuke(Merc* caster, Mob* tar, uint16 spell_id, uint8 &numTargets) { + std::list npc_list; + entity_list.GetNPCList(npc_list); + + for(std::list::iterator itr = npc_list.begin(); itr != npc_list.end(); ++itr) { + NPC* npc = *itr; + + if(npc->DistNoRootNoZ(*tar) <= spells[spell_id].aoerange * spells[spell_id].aoerange) { + if(!npc->IsMezzed()) { + numTargets++; + } + else { + numTargets = 0; + return false; + } + } + } + + if(numTargets > 1) + return true; + + return false; +} + +int16 Merc::GetFocusEffect(focusType type, uint16 spell_id) { + + int16 realTotal = 0; + int16 realTotal2 = 0; + int16 realTotal3 = 0; + bool rand_effectiveness = false; + + //Improved Healing, Damage & Mana Reduction are handled differently in that some are random percentages + //In these cases we need to find the most powerful effect, so that each piece of gear wont get its own chance + if((type == focusManaCost || type == focusImprovedHeal || type == focusImprovedDamage) + && RuleB(Spells, LiveLikeFocusEffects)) + { + rand_effectiveness = true; + } + + //Check if item focus effect exists for the client. + if (itembonuses.FocusEffects[type]){ + + const Item_Struct* TempItem = 0; + const Item_Struct* UsedItem = 0; + uint16 UsedFocusID = 0; + int16 Total = 0; + int16 focus_max = 0; + int16 focus_max_real = 0; + + //item focus + for (int x = 0; x < EmuConstants::EQUIPMENT_SIZE; ++x) + { + TempItem = nullptr; + if (equipment[x] == 0) + continue; + TempItem = database.GetItem(equipment[x]); + if (TempItem && TempItem->Focus.Effect > 0 && TempItem->Focus.Effect != SPELL_UNKNOWN) { + if(rand_effectiveness) { + focus_max = CalcFocusEffect(type, TempItem->Focus.Effect, spell_id, true); + if (focus_max > 0 && focus_max_real >= 0 && focus_max > focus_max_real) { + focus_max_real = focus_max; + UsedItem = TempItem; + UsedFocusID = TempItem->Focus.Effect; + } else if (focus_max < 0 && focus_max < focus_max_real) { + focus_max_real = focus_max; + UsedItem = TempItem; + UsedFocusID = TempItem->Focus.Effect; + } + } + else { + Total = CalcFocusEffect(type, TempItem->Focus.Effect, spell_id); + if (Total > 0 && realTotal >= 0 && Total > realTotal) { + realTotal = Total; + UsedItem = TempItem; + UsedFocusID = TempItem->Focus.Effect; + } else if (Total < 0 && Total < realTotal) { + realTotal = Total; + UsedItem = TempItem; + UsedFocusID = TempItem->Focus.Effect; + } + } + } + } + + if(UsedItem && rand_effectiveness && focus_max_real != 0) + realTotal = CalcFocusEffect(type, UsedFocusID, spell_id); + + if (realTotal != 0 && UsedItem) + Message_StringID(MT_Spells, BEGINS_TO_GLOW, UsedItem->Name); + } + + //Check if spell focus effect exists for the client. + if (spellbonuses.FocusEffects[type]){ + + //Spell Focus + int16 Total2 = 0; + int16 focus_max2 = 0; + int16 focus_max_real2 = 0; + + int buff_tracker = -1; + int buff_slot = 0; + uint16 focusspellid = 0; + uint16 focusspell_tracker = 0; + uint32 buff_max = GetMaxTotalSlots(); + for (buff_slot = 0; buff_slot < buff_max; buff_slot++) { + focusspellid = buffs[buff_slot].spellid; + if (focusspellid == 0 || focusspellid >= SPDAT_RECORDS) + continue; + + if(rand_effectiveness) { + focus_max2 = CalcFocusEffect(type, focusspellid, spell_id, true); + if (focus_max2 > 0 && focus_max_real2 >= 0 && focus_max2 > focus_max_real2) { + focus_max_real2 = focus_max2; + buff_tracker = buff_slot; + focusspell_tracker = focusspellid; + } else if (focus_max2 < 0 && focus_max2 < focus_max_real2) { + focus_max_real2 = focus_max2; + buff_tracker = buff_slot; + focusspell_tracker = focusspellid; + } + } + else { + Total2 = CalcFocusEffect(type, focusspellid, spell_id); + if (Total2 > 0 && realTotal2 >= 0 && Total2 > realTotal2) { + realTotal2 = Total2; + buff_tracker = buff_slot; + focusspell_tracker = focusspellid; + } else if (Total2 < 0 && Total2 < realTotal2) { + realTotal2 = Total2; + buff_tracker = buff_slot; + focusspell_tracker = focusspellid; + } + } + } + + if(focusspell_tracker && rand_effectiveness && focus_max_real2 != 0) + realTotal2 = CalcFocusEffect(type, focusspell_tracker, spell_id); + + // For effects like gift of mana that only fire once, save the spellid into an array that consists of all available buff slots. + if(buff_tracker >= 0 && buffs[buff_tracker].numhits > 0) { + m_spellHitsLeft[buff_tracker] = focusspell_tracker; + } + } + + + // AA Focus + /*if (aabonuses.FocusEffects[type]){ + + int16 Total3 = 0; + uint32 slots = 0; + uint32 aa_AA = 0; + uint32 aa_value = 0; + + for (int i = 0; i < MAX_PP_AA_ARRAY; i++) + { + aa_AA = this->aa[i]->AA; + aa_value = this->aa[i]->value; + if (aa_AA < 1 || aa_value < 1) + continue; + + Total3 = CalcAAFocus(type, aa_AA, spell_id); + if (Total3 > 0 && realTotal3 >= 0 && Total3 > realTotal3) { + realTotal3 = Total3; + } + else if (Total3 < 0 && Total3 < realTotal3) { + realTotal3 = Total3; + } + } + }*/ + + if(type == focusReagentCost && IsSummonPetSpell(spell_id) && GetAA(aaElementalPact)) + return 100; + + if(type == focusReagentCost && (IsEffectInSpell(spell_id, SE_SummonItem) || IsSacrificeSpell(spell_id))) + return 0; + //Summon Spells that require reagents are typically imbue type spells, enchant metal, sacrifice and shouldn't be affected + //by reagent conservation for obvious reasons. + + return realTotal + realTotal2 + realTotal3; +} + + +int32 Merc::GetActSpellDamage(uint16 spell_id, int32 value, Mob* target) { + + if (spells[spell_id].targettype == ST_Self) + return value; + + bool Critical = false; + int32 value_BaseEffect = 0; + + value_BaseEffect = value + (value*GetFocusEffect(focusFcBaseEffects, spell_id)/100); + + int chance = RuleI(Spells, BaseCritChance); + chance += itembonuses.CriticalSpellChance + spellbonuses.CriticalSpellChance + aabonuses.CriticalSpellChance; + + if (chance > 0){ + + int32 ratio = RuleI(Spells, BaseCritRatio); //Critical modifier is applied from spell effects only. Keep at 100 for live like criticals. + + if (zone->random.Roll(chance)) { + Critical = true; + ratio += itembonuses.SpellCritDmgIncrease + spellbonuses.SpellCritDmgIncrease + aabonuses.SpellCritDmgIncrease; + ratio += itembonuses.SpellCritDmgIncNoStack + spellbonuses.SpellCritDmgIncNoStack + aabonuses.SpellCritDmgIncNoStack; + } + + else if (GetClass() == CASTERDPS && (GetLevel() >= RuleI(Spells, WizCritLevel)) && (zone->random.Roll(RuleI(Spells, WizCritChance)))) { + ratio = zone->random.Int(1,100); //Wizard innate critical chance is calculated seperately from spell effect and is not a set ratio. + Critical = true; + } + + ratio += RuleI(Spells, WizCritRatio); //Default is zero + + if (Critical){ + + value = value_BaseEffect*ratio/100; + + value += value_BaseEffect*GetFocusEffect(focusImprovedDamage, spell_id)/100; + + value += int(value_BaseEffect*GetFocusEffect(focusFcDamagePctCrit, spell_id)/100)*ratio/100; + + if (target) { + value += int(value_BaseEffect*target->GetVulnerability(this, spell_id, 0)/100)*ratio/100; + value -= target->GetFcDamageAmtIncoming(this, spell_id); + } + + value -= GetFocusEffect(focusFcDamageAmtCrit, spell_id)*ratio/100; + + value -= GetFocusEffect(focusFcDamageAmt, spell_id); + + if(itembonuses.SpellDmg && spells[spell_id].classes[(GetClass()%16) - 1] >= GetLevel() - 5) + value -= GetExtraSpellAmt(spell_id, itembonuses.SpellDmg, value)*ratio/100; + + value = (value * GetSpellScale() / 100); + + entity_list.MessageClose_StringID(this, false, 100, MT_SpellCrits, + OTHER_CRIT_BLAST, GetName(), itoa(-value)); + + return value; + } + } + + value = value_BaseEffect; + + value += value_BaseEffect*GetFocusEffect(focusImprovedDamage, spell_id)/100; + + value += value_BaseEffect*GetFocusEffect(focusFcDamagePctCrit, spell_id)/100; + + if (target) { + value += value_BaseEffect*target->GetVulnerability(this, spell_id, 0)/100; + value -= target->GetFcDamageAmtIncoming(this, spell_id); + } + + value -= GetFocusEffect(focusFcDamageAmtCrit, spell_id); + + value -= GetFocusEffect(focusFcDamageAmt, spell_id); + + if(itembonuses.SpellDmg && spells[spell_id].classes[(GetClass()%16) - 1] >= GetLevel() - 5) + value -= GetExtraSpellAmt(spell_id, itembonuses.SpellDmg, value); + + value = (value * GetSpellScale() / 100); + + return value; +} + +int32 Merc::GetActSpellHealing(uint16 spell_id, int32 value, Mob* target) { + + if (target == nullptr) + target = this; + + int32 value_BaseEffect = 0; + int16 chance = 0; + int8 modifier = 1; + bool Critical = false; + + value_BaseEffect = value + (value*GetFocusEffect(focusFcBaseEffects, spell_id)/100); + + value = value_BaseEffect; + + value += int(value_BaseEffect*GetFocusEffect(focusImprovedHeal, spell_id)/100); + + // Instant Heals + if(spells[spell_id].buffduration < 1) { + + chance += itembonuses.CriticalHealChance + spellbonuses.CriticalHealChance + aabonuses.CriticalHealChance; + + chance += target->GetFocusIncoming(focusFcHealPctCritIncoming, SE_FcHealPctCritIncoming, this, spell_id); + + if (spellbonuses.CriticalHealDecay) + chance += GetDecayEffectValue(spell_id, SE_CriticalHealDecay); + + if(chance && zone->random.Roll(chance)) { + Critical = true; + modifier = 2; //At present time no critical heal amount modifier SPA exists. + } + + value *= modifier; + value += GetFocusEffect(focusFcHealAmtCrit, spell_id) * modifier; + value += GetFocusEffect(focusFcHealAmt, spell_id); + value += target->GetFocusIncoming(focusFcHealAmtIncoming, SE_FcHealAmtIncoming, this, spell_id); + + if(itembonuses.HealAmt && spells[spell_id].classes[(GetClass()%16) - 1] >= GetLevel() - 5) + value += GetExtraSpellAmt(spell_id, itembonuses.HealAmt, value) * modifier; + + value += value*target->GetHealRate(spell_id, this)/100; + + if (Critical) + entity_list.MessageClose(this, false, 100, MT_SpellCrits, "%s performs an exceptional heal! (%d)", GetName(), value); + + return value; + } + + //Heal over time spells. [Heal Rate and Additional Healing effects do not increase this value] + else { + + chance = itembonuses.CriticalHealOverTime + spellbonuses.CriticalHealOverTime + aabonuses.CriticalHealOverTime; + + chance += target->GetFocusIncoming(focusFcHealPctCritIncoming, SE_FcHealPctCritIncoming, this, spell_id); + + if (spellbonuses.CriticalRegenDecay) + chance += GetDecayEffectValue(spell_id, SE_CriticalRegenDecay); + + if(chance && zone->random.Roll(chance)) + return (value * 2); + } + + return value; +} + +int32 Merc::GetActSpellCost(uint16 spell_id, int32 cost) +{ + // Formula = Unknown exact, based off a random percent chance up to mana cost(after focuses) of the cast spell + if(this->itembonuses.Clairvoyance && spells[spell_id].classes[(GetClass()%16) - 1] >= GetLevel() - 5) + { + int16 mana_back = this->itembonuses.Clairvoyance * zone->random.Int(1, 100) / 100; + // Doesnt generate mana, so best case is a free spell + if(mana_back > cost) + mana_back = cost; + + cost -= mana_back; + } + + // This formula was derived from the following resource: + // http://www.eqsummoners.com/eq1/specialization-library.html + // WildcardX + float PercentManaReduction = 0; + + int16 focus_redux = GetFocusEffect(focusManaCost, spell_id); + + if(focus_redux > 0) + { + PercentManaReduction += zone->random.Real(1, (double)focus_redux); + } + + cost -= (cost * (PercentManaReduction / 100)); + + // Gift of Mana - reduces spell cost to 1 mana + if(focus_redux >= 100) { + uint32 buff_max = GetMaxTotalSlots(); + for (int buffSlot = 0; buffSlot < buff_max; buffSlot++) { + if (buffs[buffSlot].spellid == 0 || buffs[buffSlot].spellid >= SPDAT_RECORDS) + continue; + + if(IsEffectInSpell(buffs[buffSlot].spellid, SE_ReduceManaCost)) { + if(CalcFocusEffect(focusManaCost, buffs[buffSlot].spellid, spell_id) == 100) + cost = 1; + } + } + } + + if(cost < 0) + cost = 0; + + return cost; +} + +int32 Merc::GetActSpellCasttime(uint16 spell_id, int32 casttime) +{ + int32 cast_reducer = 0; + cast_reducer += GetFocusEffect(focusSpellHaste, spell_id); + + if (cast_reducer > RuleI(Spells, MaxCastTimeReduction)) + cast_reducer = RuleI(Spells, MaxCastTimeReduction); + + casttime = (casttime*(100 - cast_reducer)/100); + + return casttime; +} + +int8 Merc::GetChanceToCastBySpellType(int16 spellType) { + int mercStance = (int)GetStance(); + int8 mercClass = GetClass(); + int8 chance = 0; + + switch (spellType) { + case SpellType_Nuke: { + switch(mercClass) + { + case TANK: { + chance = 100; + break; + } + case HEALER:{ + break; + } + case MELEEDPS:{ + chance = 100; + break; + } + case CASTERDPS:{ + chance = 100; + break; + } + } + break; + } + case SpellType_Heal: { + switch(mercClass) + { + case TANK: { + break; + } + case HEALER:{ + chance = 100; + break; + } + case MELEEDPS:{ + break; + } + case CASTERDPS:{ + break; + } + } + break; + } + case SpellType_Root: { + switch(mercClass) + { + case TANK: { + break; + } + case HEALER:{ + break; + } + case MELEEDPS:{ + break; + } + case CASTERDPS:{ + break; + } + } + break; + } + case SpellType_Buff: { + switch(mercClass) + { + case TANK: { + break; + } + case HEALER:{ + chance = IsEngaged() ? 0 : 100; + break; + } + case MELEEDPS:{ + break; + } + case CASTERDPS:{ + break; + } + } + break; + } + case SpellType_InCombatBuff: { + switch(mercClass) + { + case TANK: { + chance = 50; + break; + } + case HEALER:{ + break; + } + case MELEEDPS:{ + chance = 50; + break; + } + case CASTERDPS:{ + break; + } + } + break; + } + case SpellType_Escape: { + switch(mercClass) + { + case TANK: { + break; + } + case HEALER:{ + break; + } + case MELEEDPS:{ + chance = 100; + break; + } + case CASTERDPS:{ + chance = 100; + break; + } + } + break; + } + default: + chance = 0; + break; + } + + return chance; +} + +bool Merc::CheckStance(int16 stance) { + + //checks of current stance matches stances listed as valid for spell in database + //stance = 0 for all stances, stance # for only that stance & -stance# for all but that stance + if(stance == 0 + || (stance > 0 && stance == GetStance()) + || (stance < 0 && abs(stance) != GetStance())) { + return true; + } + + return false; +} + +std::list Merc::GetMercSpellsBySpellType(Merc* caster, int spellType) { + std::list result; + + if(caster && caster->AI_HasSpells()) { + std::vector mercSpellList = caster->GetMercSpells(); + + for (int i = mercSpellList.size() - 1; i >= 0; i--) { + if (mercSpellList[i].spellid <= 0 || mercSpellList[i].spellid >= SPDAT_RECORDS) { + // this is both to quit early to save cpu and to avoid casting bad spells + // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here + continue; + } + + if((mercSpellList[i].type & spellType) && caster->CheckStance(mercSpellList[i].stance)) { + MercSpell mercSpell; + mercSpell.spellid = mercSpellList[i].spellid; + mercSpell.stance = mercSpellList[i].stance; + mercSpell.type = mercSpellList[i].type; + mercSpell.slot = mercSpellList[i].slot; + mercSpell.proc_chance = mercSpellList[i].proc_chance; + mercSpell.time_cancast = mercSpellList[i].time_cancast; + + result.push_back(mercSpell); + } + } + } + + return result; +} + +MercSpell Merc::GetFirstMercSpellBySpellType(Merc* caster, int spellType) { + MercSpell result; + + result.spellid = 0; + result.stance = 0; + result.type = 0; + result.slot = 0; + result.proc_chance = 0; + result.time_cancast = 0; + + if(caster && caster->AI_HasSpells()) { + std::vector mercSpellList = caster->GetMercSpells(); + + for (int i = mercSpellList.size() - 1; i >= 0; i--) { + if (mercSpellList[i].spellid <= 0 || mercSpellList[i].spellid >= SPDAT_RECORDS) { + // this is both to quit early to save cpu and to avoid casting bad spells + // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here + continue; + } + + if((mercSpellList[i].type & spellType) + && caster->CheckStance(mercSpellList[i].stance) + && CheckSpellRecastTimers(caster, mercSpellList[i].spellid)) { + result.spellid = mercSpellList[i].spellid; + result.stance = mercSpellList[i].stance; + result.type = mercSpellList[i].type; + result.slot = mercSpellList[i].slot; + result.proc_chance = mercSpellList[i].proc_chance; + result.time_cancast = mercSpellList[i].time_cancast; + + break; + } + } + } + + return result; +} + +MercSpell Merc::GetMercSpellBySpellID(Merc* caster, uint16 spellid) { + MercSpell result; + + result.spellid = 0; + result.stance = 0; + result.type = 0; + result.slot = 0; + result.proc_chance = 0; + result.time_cancast = 0; + + if(caster && caster->AI_HasSpells()) { + std::vector mercSpellList = caster->GetMercSpells(); + + for (int i = mercSpellList.size() - 1; i >= 0; i--) { + if (mercSpellList[i].spellid <= 0 || mercSpellList[i].spellid >= SPDAT_RECORDS) { + // this is both to quit early to save cpu and to avoid casting bad spells + // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here + continue; + } + + if((mercSpellList[i].spellid == spellid) + && caster->CheckStance(mercSpellList[i].stance)) { + result.spellid = mercSpellList[i].spellid; + result.stance = mercSpellList[i].stance; + result.type = mercSpellList[i].type; + result.slot = mercSpellList[i].slot; + result.proc_chance = mercSpellList[i].proc_chance; + result.time_cancast = mercSpellList[i].time_cancast; + + break; + } + } + } + + return result; +} + +std::list Merc::GetMercSpellsForSpellEffect(Merc* caster, int spellEffect) { + std::list result; + + if(caster && caster->AI_HasSpells()) { + std::vector mercSpellList = caster->GetMercSpells(); + + for (int i = mercSpellList.size() - 1; i >= 0; i--) { + if (mercSpellList[i].spellid <= 0 || mercSpellList[i].spellid >= SPDAT_RECORDS) { + // this is both to quit early to save cpu and to avoid casting bad spells + // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here + continue; + } + + if(IsEffectInSpell(mercSpellList[i].spellid, spellEffect) && caster->CheckStance(mercSpellList[i].stance)) { + MercSpell MercSpell; + MercSpell.spellid = mercSpellList[i].spellid; + MercSpell.stance = mercSpellList[i].stance; + MercSpell.type = mercSpellList[i].type; + MercSpell.slot = mercSpellList[i].slot; + MercSpell.proc_chance = mercSpellList[i].proc_chance; + MercSpell.time_cancast = mercSpellList[i].time_cancast; + + result.push_back(MercSpell); + } + } + } + + return result; +} + +std::list Merc::GetMercSpellsForSpellEffectAndTargetType(Merc* caster, int spellEffect, SpellTargetType targetType) { + std::list result; + + if(caster && caster->AI_HasSpells()) { + std::vector mercSpellList = caster->GetMercSpells(); + + for (int i = mercSpellList.size() - 1; i >= 0; i--) { + if (mercSpellList[i].spellid <= 0 || mercSpellList[i].spellid >= SPDAT_RECORDS) { + // this is both to quit early to save cpu and to avoid casting bad spells + // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here + continue; + } + + if(IsEffectInSpell(mercSpellList[i].spellid, spellEffect) && caster->CheckStance(mercSpellList[i].stance)) { + if(spells[mercSpellList[i].spellid].targettype == targetType) { + MercSpell MercSpell; + MercSpell.spellid = mercSpellList[i].spellid; + MercSpell.stance = mercSpellList[i].stance; + MercSpell.type = mercSpellList[i].type; + MercSpell.slot = mercSpellList[i].slot; + MercSpell.proc_chance = mercSpellList[i].proc_chance; + MercSpell.time_cancast = mercSpellList[i].time_cancast; + + result.push_back(MercSpell); + } + } + } + } + + return result; +} + +MercSpell Merc::GetBestMercSpellForVeryFastHeal(Merc* caster) { + MercSpell result; + + result.spellid = 0; + result.stance = 0; + result.type = 0; + result.slot = 0; + result.proc_chance = 0; + result.time_cancast = 0; + + if(caster) { + std::list mercSpellList = GetMercSpellsForSpellEffect(caster, SE_CurrentHP); + + for(std::list::iterator mercSpellListItr = mercSpellList.begin(); mercSpellListItr != mercSpellList.end(); ++mercSpellListItr) { + // Assuming all the spells have been loaded into this list by level and in descending order + if(IsVeryFastHealSpell(mercSpellListItr->spellid) + && CheckSpellRecastTimers(caster, mercSpellListItr->spellid)) { + result.spellid = mercSpellListItr->spellid; + result.stance = mercSpellListItr->stance; + result.type = mercSpellListItr->type; + result.slot = mercSpellListItr->slot; + result.proc_chance = mercSpellListItr->proc_chance; + result.time_cancast = mercSpellListItr->time_cancast; + + break; + } + } + } + + return result; +} + +MercSpell Merc::GetBestMercSpellForFastHeal(Merc* caster) { + MercSpell result; + + result.spellid = 0; + result.stance = 0; + result.type = 0; + result.slot = 0; + result.proc_chance = 0; + result.time_cancast = 0; + + if(caster) { + std::list mercSpellList = GetMercSpellsForSpellEffect(caster, SE_CurrentHP); + + for(std::list::iterator mercSpellListItr = mercSpellList.begin(); mercSpellListItr != mercSpellList.end(); ++mercSpellListItr) { + // Assuming all the spells have been loaded into this list by level and in descending order + if(IsFastHealSpell(mercSpellListItr->spellid) + && CheckSpellRecastTimers(caster, mercSpellListItr->spellid)) { + result.spellid = mercSpellListItr->spellid; + result.stance = mercSpellListItr->stance; + result.type = mercSpellListItr->type; + result.slot = mercSpellListItr->slot; + result.proc_chance = mercSpellListItr->proc_chance; + result.time_cancast = mercSpellListItr->time_cancast; + + break; + } + } + } + + return result; +} + +MercSpell Merc::GetBestMercSpellForHealOverTime(Merc* caster) { + MercSpell result; + + result.spellid = 0; + result.stance = 0; + result.type = 0; + result.slot = 0; + result.proc_chance = 0; + result.time_cancast = 0; + + if(caster) { + std::list mercHoTSpellList = GetMercSpellsForSpellEffect(caster, SE_HealOverTime); + + for(std::list::iterator mercSpellListItr = mercHoTSpellList.begin(); mercSpellListItr != mercHoTSpellList.end(); ++mercSpellListItr) { + // Assuming all the spells have been loaded into this list by level and in descending order + if(IsHealOverTimeSpell(mercSpellListItr->spellid)) { + + if (mercSpellListItr->spellid <= 0 || mercSpellListItr->spellid >= SPDAT_RECORDS) { + // this is both to quit early to save cpu and to avoid casting bad spells + // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here + continue; + } + + if(CheckSpellRecastTimers(caster, mercSpellListItr->spellid)) { + result.spellid = mercSpellListItr->spellid; + result.stance = mercSpellListItr->stance; + result.type = mercSpellListItr->type; + result.slot = mercSpellListItr->slot; + result.proc_chance = mercSpellListItr->proc_chance; + result.time_cancast = mercSpellListItr->time_cancast; + } + + break; + } + } + } + + return result; +} + +MercSpell Merc::GetBestMercSpellForPercentageHeal(Merc* caster) { + MercSpell result; + + result.spellid = 0; + result.stance = 0; + result.type = 0; + result.slot = 0; + result.proc_chance = 0; + result.time_cancast = 0; + + if(caster && caster->AI_HasSpells()) { + std::list mercSpellList = GetMercSpellsForSpellEffect(caster, SE_CurrentHP); + + for(std::list::iterator mercSpellListItr = mercSpellList.begin(); mercSpellListItr != mercSpellList.end(); ++mercSpellListItr) { + // Assuming all the spells have been loaded into this list by level and in descending order + if(IsCompleteHealSpell(mercSpellListItr->spellid) + && CheckSpellRecastTimers(caster, mercSpellListItr->spellid)) { + result.spellid = mercSpellListItr->spellid; + result.stance = mercSpellListItr->stance; + result.type = mercSpellListItr->type; + result.slot = mercSpellListItr->slot; + result.proc_chance = mercSpellListItr->proc_chance; + result.time_cancast = mercSpellListItr->time_cancast; + + break; + } + } + } + + return result; +} + +MercSpell Merc::GetBestMercSpellForRegularSingleTargetHeal(Merc* caster) { + MercSpell result; + + result.spellid = 0; + result.stance = 0; + result.type = 0; + result.slot = 0; + result.proc_chance = 0; + result.time_cancast = 0; + + if(caster) { + std::list mercSpellList = GetMercSpellsForSpellEffect(caster, SE_CurrentHP); + + for(std::list::iterator mercSpellListItr = mercSpellList.begin(); mercSpellListItr != mercSpellList.end(); ++mercSpellListItr) { + // Assuming all the spells have been loaded into this list by level and in descending order + if(IsRegularSingleTargetHealSpell(mercSpellListItr->spellid) + && CheckSpellRecastTimers(caster, mercSpellListItr->spellid)) { + result.spellid = mercSpellListItr->spellid; + result.stance = mercSpellListItr->stance; + result.type = mercSpellListItr->type; + result.slot = mercSpellListItr->slot; + result.proc_chance = mercSpellListItr->proc_chance; + result.time_cancast = mercSpellListItr->time_cancast; + + break; + } + } + } + + return result; +} + +MercSpell Merc::GetFirstMercSpellForSingleTargetHeal(Merc* caster) { + MercSpell result; + + result.spellid = 0; + result.stance = 0; + result.type = 0; + result.slot = 0; + result.proc_chance = 0; + result.time_cancast = 0; + + if(caster) { + std::list mercSpellList = GetMercSpellsForSpellEffect(caster, SE_CurrentHP); + + for(std::list::iterator mercSpellListItr = mercSpellList.begin(); mercSpellListItr != mercSpellList.end(); ++mercSpellListItr) { + // Assuming all the spells have been loaded into this list by level and in descending order + if((IsRegularSingleTargetHealSpell(mercSpellListItr->spellid) + || IsFastHealSpell(mercSpellListItr->spellid)) + && CheckSpellRecastTimers(caster, mercSpellListItr->spellid)) { + result.spellid = mercSpellListItr->spellid; + result.stance = mercSpellListItr->stance; + result.type = mercSpellListItr->type; + result.slot = mercSpellListItr->slot; + result.proc_chance = mercSpellListItr->proc_chance; + result.time_cancast = mercSpellListItr->time_cancast; + + break; + } + } + } + + return result; +} + +MercSpell Merc::GetBestMercSpellForGroupHeal(Merc* caster) { + MercSpell result; + + result.spellid = 0; + result.stance = 0; + result.type = 0; + result.slot = 0; + result.proc_chance = 0; + result.time_cancast = 0; + + if(caster) { + std::list mercSpellList = GetMercSpellsForSpellEffect(caster, SE_CurrentHP); + + for(std::list::iterator mercSpellListItr = mercSpellList.begin(); mercSpellListItr != mercSpellList.end(); ++mercSpellListItr) { + // Assuming all the spells have been loaded into this list by level and in descending order + if(IsRegularGroupHealSpell(mercSpellListItr->spellid) + && CheckSpellRecastTimers(caster, mercSpellListItr->spellid)) { + result.spellid = mercSpellListItr->spellid; + result.stance = mercSpellListItr->stance; + result.type = mercSpellListItr->type; + result.slot = mercSpellListItr->slot; + result.proc_chance = mercSpellListItr->proc_chance; + result.time_cancast = mercSpellListItr->time_cancast; + + break; + } + } + } + + return result; +} + +MercSpell Merc::GetBestMercSpellForGroupHealOverTime(Merc* caster) { + MercSpell result; + + result.spellid = 0; + result.stance = 0; + result.type = 0; + result.slot = 0; + result.proc_chance = 0; + result.time_cancast = 0; + + if(caster) { + std::list mercHoTSpellList = GetMercSpellsForSpellEffect(caster, SE_HealOverTime); + + for(std::list::iterator mercSpellListItr = mercHoTSpellList.begin(); mercSpellListItr != mercHoTSpellList.end(); ++mercSpellListItr) { + // Assuming all the spells have been loaded into this list by level and in descending order + if(IsGroupHealOverTimeSpell(mercSpellListItr->spellid)) { + + if (mercSpellListItr->spellid <= 0 || mercSpellListItr->spellid >= SPDAT_RECORDS) { + // this is both to quit early to save cpu and to avoid casting bad spells + // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here + continue; + } + + if(CheckSpellRecastTimers(caster, mercSpellListItr->spellid)) { + result.spellid = mercSpellListItr->spellid; + result.stance = mercSpellListItr->stance; + result.type = mercSpellListItr->type; + result.slot = mercSpellListItr->slot; + result.proc_chance = mercSpellListItr->proc_chance; + result.time_cancast = mercSpellListItr->time_cancast; + } + + break; + } + } + } + + return result; +} + +MercSpell Merc::GetBestMercSpellForGroupCompleteHeal(Merc* caster) { + MercSpell result; + + result.spellid = 0; + result.stance = 0; + result.type = 0; + result.slot = 0; + result.proc_chance = 0; + result.time_cancast = 0; + + if(caster) { + std::list mercSpellList = GetMercSpellsForSpellEffect(caster, SE_CompleteHeal); + + for(std::list::iterator mercSpellListItr = mercSpellList.begin(); mercSpellListItr != mercSpellList.end(); ++mercSpellListItr) { + // Assuming all the spells have been loaded into this list by level and in descending order + if(IsGroupCompleteHealSpell(mercSpellListItr->spellid) + && CheckSpellRecastTimers(caster, mercSpellListItr->spellid)) { + result.spellid = mercSpellListItr->spellid; + result.stance = mercSpellListItr->stance; + result.type = mercSpellListItr->type; + result.slot = mercSpellListItr->slot; + result.proc_chance = mercSpellListItr->proc_chance; + result.time_cancast = mercSpellListItr->time_cancast; + + break; + } + } + } + + return result; +} + +MercSpell Merc::GetBestMercSpellForAETaunt(Merc* caster) { + MercSpell result; + + result.spellid = 0; + result.stance = 0; + result.type = 0; + result.slot = 0; + result.proc_chance = 0; + result.time_cancast = 0; + + if(caster) { + std::list mercSpellList = GetMercSpellsForSpellEffect(caster, SE_Taunt); + + for(std::list::iterator mercSpellListItr = mercSpellList.begin(); mercSpellListItr != mercSpellList.end(); ++mercSpellListItr) { + // Assuming all the spells have been loaded into this list by level and in descending order + if((spells[mercSpellListItr->spellid].targettype == ST_AECaster + || spells[mercSpellListItr->spellid].targettype == ST_AETarget + || spells[mercSpellListItr->spellid].targettype == ST_UndeadAE) + && CheckSpellRecastTimers(caster, mercSpellListItr->spellid)) { + result.spellid = mercSpellListItr->spellid; + result.stance = mercSpellListItr->stance; + result.type = mercSpellListItr->type; + result.slot = mercSpellListItr->slot; + result.proc_chance = mercSpellListItr->proc_chance; + result.time_cancast = mercSpellListItr->time_cancast; + + break; + } + } + } + + return result; +} + +MercSpell Merc::GetBestMercSpellForTaunt(Merc* caster) { + MercSpell result; + + result.spellid = 0; + result.stance = 0; + result.type = 0; + result.slot = 0; + result.proc_chance = 0; + result.time_cancast = 0; + + if(caster) { + std::list mercSpellList = GetMercSpellsForSpellEffect(caster, SE_Taunt); + + for(std::list::iterator mercSpellListItr = mercSpellList.begin(); mercSpellListItr != mercSpellList.end(); ++mercSpellListItr) { + // Assuming all the spells have been loaded into this list by level and in descending order + if((spells[mercSpellListItr->spellid].targettype == ST_Target) + && CheckSpellRecastTimers(caster, mercSpellListItr->spellid)) { + result.spellid = mercSpellListItr->spellid; + result.stance = mercSpellListItr->stance; + result.type = mercSpellListItr->type; + result.slot = mercSpellListItr->slot; + result.proc_chance = mercSpellListItr->proc_chance; + result.time_cancast = mercSpellListItr->time_cancast; + + break; + } + } + } + + return result; +} + +MercSpell Merc::GetBestMercSpellForHate(Merc* caster) { + MercSpell result; + + result.spellid = 0; + result.stance = 0; + result.type = 0; + result.slot = 0; + result.proc_chance = 0; + result.time_cancast = 0; + + if(caster) { + std::list mercSpellList = GetMercSpellsForSpellEffect(caster, SE_InstantHate); + + for(std::list::iterator mercSpellListItr = mercSpellList.begin(); mercSpellListItr != mercSpellList.end(); ++mercSpellListItr) { + // Assuming all the spells have been loaded into this list by level and in descending order + if(CheckSpellRecastTimers(caster, mercSpellListItr->spellid)) { + result.spellid = mercSpellListItr->spellid; + result.stance = mercSpellListItr->stance; + result.type = mercSpellListItr->type; + result.slot = mercSpellListItr->slot; + result.proc_chance = mercSpellListItr->proc_chance; + result.time_cancast = mercSpellListItr->time_cancast; + + break; + } + } + } + + return result; +} + +MercSpell Merc::GetBestMercSpellForCure(Merc* caster, Mob *tar) { + MercSpell result; + bool spellSelected = false; + + result.spellid = 0; + result.stance = 0; + result.type = 0; + result.slot = 0; + result.proc_chance = 0; + result.time_cancast = 0; + + if(!tar) + return result; + + int countNeedsCured = 0; + bool isPoisoned = tar->FindType(SE_PoisonCounter); + bool isDiseased = tar->FindType(SE_DiseaseCounter); + bool isCursed = tar->FindType(SE_CurseCounter); + bool isCorrupted = tar->FindType(SE_CorruptionCounter); + + if(caster && caster->AI_HasSpells()) { + std::list cureList = GetMercSpellsBySpellType(caster, SpellType_Cure); + + if(tar->HasGroup()) { + Group *g = tar->GetGroup(); + + if(g) { + for( int i = 0; imembers[i] && !g->members[i]->qglobal) { + if(caster->GetNeedsCured(g->members[i])) + countNeedsCured++; + } + } + } + } + + //Check for group cure first + if(countNeedsCured > 2) { + for(std::list::iterator itr = cureList.begin(); itr != cureList.end(); ++itr) { + MercSpell selectedMercSpell = *itr; + + if(IsGroupSpell(itr->spellid) && CheckSpellRecastTimers(caster, itr->spellid)) { + if(selectedMercSpell.spellid == 0) + continue; + + if(isPoisoned && IsEffectInSpell(itr->spellid, SE_PoisonCounter)) { + spellSelected = true; + } + else if(isDiseased && IsEffectInSpell(itr->spellid, SE_DiseaseCounter)) { + spellSelected = true; + } + else if(isCursed && IsEffectInSpell(itr->spellid, SE_CurseCounter)) { + spellSelected = true; + } + else if(isCorrupted && IsEffectInSpell(itr->spellid, SE_CorruptionCounter)) { + spellSelected = true; + } + else if(IsEffectInSpell(itr->spellid, SE_DispelDetrimental)) { + spellSelected = true; + } + + if(spellSelected) + { + result.spellid = itr->spellid; + result.stance = itr->stance; + result.type = itr->type; + result.slot = itr->slot; + result.proc_chance = itr->proc_chance; + result.time_cancast = itr->time_cancast; + + break; + } + } + } + } + + //no group cure for target- try to find single target spell + if(!spellSelected) { + for(std::list::iterator itr = cureList.begin(); itr != cureList.end(); ++itr) { + MercSpell selectedMercSpell = *itr; + + if(CheckSpellRecastTimers(caster, itr->spellid)) { + if(selectedMercSpell.spellid == 0) + continue; + + if(isPoisoned && IsEffectInSpell(itr->spellid, SE_PoisonCounter)) { + spellSelected = true; + } + else if(isDiseased && IsEffectInSpell(itr->spellid, SE_DiseaseCounter)) { + spellSelected = true; + } + else if(isCursed && IsEffectInSpell(itr->spellid, SE_CurseCounter)) { + spellSelected = true; + } + else if(isCorrupted && IsEffectInSpell(itr->spellid, SE_CorruptionCounter)) { + spellSelected = true; + } + else if(IsEffectInSpell(itr->spellid, SE_DispelDetrimental)) { + spellSelected = true; + } + + if(spellSelected) + { + result.spellid = itr->spellid; + result.stance = itr->stance; + result.type = itr->type; + result.slot = itr->slot; + result.proc_chance = itr->proc_chance; + result.time_cancast = itr->time_cancast; + + break; + } + } + } + } + } + + return result; +} + +MercSpell Merc::GetBestMercSpellForStun(Merc* caster) { + MercSpell result; + bool spellSelected = false; + + result.spellid = 0; + result.stance = 0; + result.type = 0; + result.slot = 0; + result.proc_chance = 0; + result.time_cancast = 0; + + if(caster) { + std::list mercSpellList = GetMercSpellsForSpellEffect(caster, SE_Stun); + + for(std::list::iterator mercSpellListItr = mercSpellList.begin(); mercSpellListItr != mercSpellList.end(); ++mercSpellListItr) { + // Assuming all the spells have been loaded into this list by level and in descending order + if(CheckSpellRecastTimers(caster, mercSpellListItr->spellid)) { + result.spellid = mercSpellListItr->spellid; + result.stance = mercSpellListItr->stance; + result.type = mercSpellListItr->type; + result.slot = mercSpellListItr->slot; + result.proc_chance = mercSpellListItr->proc_chance; + result.time_cancast = mercSpellListItr->time_cancast; + + break; + } + } + } + + return result; +} + +MercSpell Merc::GetBestMercSpellForAENuke(Merc* caster, Mob* tar) { + MercSpell result; + bool spellSelected = false; + + result.spellid = 0; + result.stance = 0; + result.type = 0; + result.slot = 0; + result.proc_chance = 0; + result.time_cancast = 0; + + if(caster) { + uint8 initialCastChance = 0; + uint8 castChanceFalloff = 75; + + switch(caster->GetStance()) + { + case MercStanceBurnAE: + initialCastChance = 50; + break; + case MercStanceBalanced: + initialCastChance = 25; + break; + case MercStanceBurn: + initialCastChance = 0; + break; + } + + //check of we even want to cast an AE nuke + if(zone->random.Roll(initialCastChance)) { + + result = GetBestMercSpellForAERainNuke(caster, tar); + + //check if we have a spell & allow for other AE nuke types + if(result.spellid == 0 && zone->random.Roll(castChanceFalloff)) { + + result = GetBestMercSpellForPBAENuke(caster, tar); + + //check if we have a spell & allow for other AE nuke types + if(result.spellid == 0 && zone->random.Roll(castChanceFalloff)) { + + result = GetBestMercSpellForTargetedAENuke(caster, tar); + } + } + } + } + + return result; +} + +MercSpell Merc::GetBestMercSpellForTargetedAENuke(Merc* caster, Mob* tar) { + MercSpell result; + int castChance = 50; //used to cycle through multiple spells (first has 50% overall chance, 2nd has 25%, etc.) + int numTargetsCheck = 1; //used to check for min number of targets to use AE + bool spellSelected = false; + + result.spellid = 0; + result.stance = 0; + result.type = 0; + result.slot = 0; + result.proc_chance = 0; + result.time_cancast = 0; + + switch(caster->GetStance()) + { + case MercStanceBurnAE: + numTargetsCheck = 1; + break; + case MercStanceBalanced: + case MercStanceBurn: + numTargetsCheck = 2; + break; + } + + if(caster) { + std::list mercSpellList = GetMercSpellsBySpellType(caster, SpellType_Nuke); + + for(std::list::iterator mercSpellListItr = mercSpellList.begin(); mercSpellListItr != mercSpellList.end(); ++mercSpellListItr) { + // Assuming all the spells have been loaded into this list by level and in descending order + if(IsAENukeSpell(mercSpellListItr->spellid) && !IsAERainNukeSpell(mercSpellListItr->spellid) + && !IsPBAENukeSpell(mercSpellListItr->spellid) && CheckSpellRecastTimers(caster, mercSpellListItr->spellid)) { + uint8 numTargets = 0; + if(CheckAENuke(caster, tar, mercSpellListItr->spellid, numTargets)) { + if(numTargets >= numTargetsCheck && zone->random.Roll(castChance)) { + result.spellid = mercSpellListItr->spellid; + result.stance = mercSpellListItr->stance; + result.type = mercSpellListItr->type; + result.slot = mercSpellListItr->slot; + result.proc_chance = mercSpellListItr->proc_chance; + result.time_cancast = mercSpellListItr->time_cancast; + } + } + + break; + } + } + } + + return result; +} + +MercSpell Merc::GetBestMercSpellForPBAENuke(Merc* caster, Mob* tar) { + MercSpell result; + int castChance = 50; //used to cycle through multiple spells (first has 50% overall chance, 2nd has 25%, etc.) + int numTargetsCheck = 1; //used to check for min number of targets to use AE + bool spellSelected = false; + + result.spellid = 0; + result.stance = 0; + result.type = 0; + result.slot = 0; + result.proc_chance = 0; + result.time_cancast = 0; + + switch(caster->GetStance()) + { + case MercStanceBurnAE: + numTargetsCheck = 2; + break; + case MercStanceBalanced: + case MercStanceBurn: + numTargetsCheck = 3; + break; + } + + if(caster) { + std::list mercSpellList = GetMercSpellsBySpellType(caster, SpellType_Nuke); + + for(std::list::iterator mercSpellListItr = mercSpellList.begin(); mercSpellListItr != mercSpellList.end(); ++mercSpellListItr) { + // Assuming all the spells have been loaded into this list by level and in descending order + if(IsPBAENukeSpell(mercSpellListItr->spellid) && CheckSpellRecastTimers(caster, mercSpellListItr->spellid)) { + uint8 numTargets = 0; + if(CheckAENuke(caster, caster, mercSpellListItr->spellid, numTargets)) { + if(numTargets >= numTargetsCheck && zone->random.Roll(castChance)) { + result.spellid = mercSpellListItr->spellid; + result.stance = mercSpellListItr->stance; + result.type = mercSpellListItr->type; + result.slot = mercSpellListItr->slot; + result.proc_chance = mercSpellListItr->proc_chance; + result.time_cancast = mercSpellListItr->time_cancast; + } + } + + break; + } + } + } + + return result; +} + +MercSpell Merc::GetBestMercSpellForAERainNuke(Merc* caster, Mob* tar) { + MercSpell result; + int castChance = 50; //used to cycle through multiple spells (first has 50% overall chance, 2nd has 25%, etc.) + int numTargetsCheck = 1; //used to check for min number of targets to use AE + bool spellSelected = false; + + result.spellid = 0; + result.stance = 0; + result.type = 0; + result.slot = 0; + result.proc_chance = 0; + result.time_cancast = 0; + + switch(caster->GetStance()) + { + case MercStanceBurnAE: + numTargetsCheck = 1; + break; + case MercStanceBalanced: + case MercStanceBurn: + numTargetsCheck = 2; + break; + } + + if(caster) { + std::list mercSpellList = GetMercSpellsBySpellType(caster, SpellType_Nuke); + + for(std::list::iterator mercSpellListItr = mercSpellList.begin(); mercSpellListItr != mercSpellList.end(); ++mercSpellListItr) { + // Assuming all the spells have been loaded into this list by level and in descending order + if(IsAERainNukeSpell(mercSpellListItr->spellid) && zone->random.Roll(castChance) && CheckSpellRecastTimers(caster, mercSpellListItr->spellid)) { + uint8 numTargets = 0; + if(CheckAENuke(caster, tar, mercSpellListItr->spellid, numTargets)) { + if(numTargets >= numTargetsCheck) { + result.spellid = mercSpellListItr->spellid; + result.stance = mercSpellListItr->stance; + result.type = mercSpellListItr->type; + result.slot = mercSpellListItr->slot; + result.proc_chance = mercSpellListItr->proc_chance; + result.time_cancast = mercSpellListItr->time_cancast; + } + } + + break; + } + } + } + + return result; +} + +MercSpell Merc::GetBestMercSpellForNuke(Merc* caster) { + MercSpell result; + int castChance = 50; //used to cycle through multiple spells (first has 50% overall chance, 2nd has 25%, etc.) + bool spellSelected = false; + + result.spellid = 0; + result.stance = 0; + result.type = 0; + result.slot = 0; + result.proc_chance = 0; + result.time_cancast = 0; + + if(caster) { + std::list mercSpellList = GetMercSpellsBySpellType(caster, SpellType_Nuke); + + for(std::list::iterator mercSpellListItr = mercSpellList.begin(); mercSpellListItr != mercSpellList.end(); ++mercSpellListItr) { + // Assuming all the spells have been loaded into this list by level and in descending order + if(IsPureNukeSpell(mercSpellListItr->spellid) && !IsAENukeSpell(mercSpellListItr->spellid) + && zone->random.Roll(castChance) && CheckSpellRecastTimers(caster, mercSpellListItr->spellid)) { + result.spellid = mercSpellListItr->spellid; + result.stance = mercSpellListItr->stance; + result.type = mercSpellListItr->type; + result.slot = mercSpellListItr->slot; + result.proc_chance = mercSpellListItr->proc_chance; + result.time_cancast = mercSpellListItr->time_cancast; + + break; + } + } + } + + return result; +} + +MercSpell Merc::GetBestMercSpellForNukeByTargetResists(Merc* caster, Mob* target) { + MercSpell result; + bool spellSelected = false; + + result.spellid = 0; + result.stance = 0; + result.type = 0; + result.slot = 0; + result.proc_chance = 0; + result.time_cancast = 0; + + if(!target) + return result; + + if(caster) { + const int lureResisValue = -100; + const int maxTargetResistValue = 300; + bool selectLureNuke = false; + + if((target->GetMR() > maxTargetResistValue) && (target->GetCR() > maxTargetResistValue) && (target->GetFR() > maxTargetResistValue)) + selectLureNuke = true; + + std::list mercSpellList = GetMercSpellsBySpellType(caster, SpellType_Nuke); + + for(std::list::iterator mercSpellListItr = mercSpellList.begin(); mercSpellListItr != mercSpellList.end(); ++mercSpellListItr) { + // Assuming all the spells have been loaded into this list by level and in descending order + + if(IsPureNukeSpell(mercSpellListItr->spellid) && !IsAENukeSpell(mercSpellListItr->spellid) && CheckSpellRecastTimers(caster, mercSpellListItr->spellid)) { + if(selectLureNuke && (spells[mercSpellListItr->spellid].ResistDiff < lureResisValue)) { + spellSelected = true; + } + else { + if(((target->GetMR() < target->GetCR()) || (target->GetMR() < target->GetFR())) && (GetSpellResistType(mercSpellListItr->spellid) == RESIST_MAGIC) + && (spells[mercSpellListItr->spellid].ResistDiff > lureResisValue)) + { + spellSelected = true; + } + else if(((target->GetCR() < target->GetMR()) || (target->GetCR() < target->GetFR())) && (GetSpellResistType(mercSpellListItr->spellid) == RESIST_COLD) + && (spells[mercSpellListItr->spellid].ResistDiff > lureResisValue)) + { + spellSelected = true; + } + else if(((target->GetFR() < target->GetCR()) || (target->GetFR() < target->GetMR())) && (GetSpellResistType(mercSpellListItr->spellid) == RESIST_FIRE) + && (spells[mercSpellListItr->spellid].ResistDiff > lureResisValue)) + { + spellSelected = true; + } + } + } + + if(spellSelected) { + result.spellid = mercSpellListItr->spellid; + result.stance = mercSpellListItr->stance; + result.type = mercSpellListItr->type; + result.slot = mercSpellListItr->slot; + result.proc_chance = mercSpellListItr->proc_chance; + result.time_cancast = mercSpellListItr->time_cancast; + + break; + } + } + } + + return result; +} + +bool Merc::GetNeedsCured(Mob *tar) { + bool needCured = false; + + if(tar) { + if(tar->FindType(SE_PoisonCounter) || tar->FindType(SE_DiseaseCounter) || tar->FindType(SE_CurseCounter) || tar->FindType(SE_CorruptionCounter)) { + uint32 buff_count = tar->GetMaxTotalSlots(); + int buffsWithCounters = 0; + needCured = true; + + for (unsigned int j = 0; j < buff_count; j++) { + if(tar->GetBuffs()[j].spellid != SPELL_UNKNOWN) { + if(CalculateCounters(tar->GetBuffs()[j].spellid) > 0) { + buffsWithCounters++; + + if(buffsWithCounters == 1 && (tar->GetBuffs()[j].ticsremaining < 2 || (int32)((tar->GetBuffs()[j].ticsremaining * 6) / tar->GetBuffs()[j].counters) < 2)) { + // Spell has ticks remaining but may have too many counters to cure in the time remaining; + // We should try to just wait it out. Could spend entire time trying to cure spell instead of healing, buffing, etc. + // Since this is the first buff with counters, don't try to cure. Cure spell will be wasted, as cure will try to + // remove counters from the first buff that has counters remaining. + needCured = false; + break; + } + } + } + } + } + } + + return needCured; +} + +void Merc::MercGroupSay(Mob *speaker, const char *msg, ...) +{ + + char buf[1000]; + va_list ap; + + va_start(ap, msg); + vsnprintf(buf, 1000, msg, ap); + va_end(ap); + + if(speaker->HasGroup()) { + Group *g = speaker->GetGroup(); + + if(g) + g->GroupMessage(speaker->CastToMob(), 0, 100, buf); + } +} + +bool Merc::UseDiscipline(int32 spell_id, int32 target) { + // Dont let client waste a reuse timer if they can't use the disc + if (IsStunned() || IsFeared() || IsMezzed() || IsAmnesiad()) + { + return(false); + } + + //make sure we can use it.. + if(!IsValidSpell(spell_id)) { + return(false); + } + + const SPDat_Spell_Struct &spell = spells[spell_id]; + + if(spell.recast_time > 0) + { + if(CheckDisciplineRecastTimers(this, spell_id, spells[spell_id].EndurTimerIndex)) { + if(spells[spell_id].EndurTimerIndex > 0) { + SetDisciplineRecastTimer(spells[spell_id].EndurTimerIndex, spell_id, spell.recast_time); + } + + SetSpellTimeCanCast(spell_id, spells[spell_id].recast_time); + } + else { + return(false); + } + } + + if(GetEndurance() > spell.EndurCost) { + SetEndurance(GetEndurance() - spell.EndurCost); + } else { + //too fatigued to use this skill right now. + return(false); + } + + if(IsCasting()) + InterruptSpell(); + + CastSpell(spell_id, target, DISCIPLINE_SPELL_SLOT); + + return(true); +} + +void Merc::SetSpellRecastTimer(uint16 timer_id, uint16 spellid, uint32 recast_delay) { + if(timer_id > 0) { + MercTimer timer; + timer.timerid = timer_id; + timer.timertype = 1; + timer.spellid = spellid; + timer.time_cancast = Timer::GetCurrentTime() + recast_delay; + timers[timer_id] = timer; + } +} + +int32 Merc::GetSpellRecastTimer(Merc *caster, uint16 timer_id) { + int32 result = 0; + if(caster && timer_id > 0) { + if(caster->timers.find(timer_id) != caster->timers.end()) { + result = caster->timers[timer_id].time_cancast; + } + } + return result; +} + +bool Merc::CheckSpellRecastTimers(Merc *caster, uint16 spell_id) { + if(caster) { + MercSpell mercSpell = GetMercSpellBySpellID(caster, spell_id); + if(mercSpell.spellid > 0 && mercSpell.time_cancast < Timer::GetCurrentTime()) { //checks spell recast + if(GetSpellRecastTimer(caster, spells[spell_id].EndurTimerIndex) < Timer::GetCurrentTime()) { //checks for spells on the same timer + return true; //can cast spell + } + } + } + return false; +} + +void Merc::SetDisciplineRecastTimer(uint16 timer_id, uint16 spellid, uint32 recast_delay) { + if(timer_id > 0) { + MercTimer timer; + timer.timerid = timer_id; + timer.timertype = 2; + timer.spellid = spellid; + timer.time_cancast = Timer::GetCurrentTime() + recast_delay; + timers[timer_id] = timer; + } +} + +int32 Merc::GetDisciplineRecastTimer(Merc *caster, uint16 timer_id) { + int32 result = 0; + if(caster && timer_id > 0) { + if(caster->timers.find(timer_id) != caster->timers.end()) { + result = caster->timers[timer_id].time_cancast; + } + } + return result; +} + +int32 Merc::GetDisciplineRemainingTime(Merc *caster, uint16 timer_id) { + int32 result = 0; + if(caster && timer_id > 0) { + int32 time_cancast = GetDisciplineRecastTimer(caster, timer_id); + if(time_cancast > Timer::GetCurrentTime()) + result = time_cancast - Timer::GetCurrentTime(); + } + return result; +} + +bool Merc::CheckDisciplineRecastTimers(Merc *caster, uint16 spell_id, uint16 timer_id) { + if(caster) { + MercSpell mercSpell = GetMercSpellBySpellID(caster, spell_id); + if(mercSpell.spellid > 0 && mercSpell.time_cancast < Timer::GetCurrentTime()) { //checks spell recast + if(timer_id > 0 && !(GetDisciplineRecastTimer(caster, timer_id) < Timer::GetCurrentTime())) { //checks for spells on the same timer + return false; //can't cast spell + } + return true; + } + } + return false; +} + +void Merc::SetSpellTimeCanCast(uint16 spellid, uint32 recast_delay) { + for (int i = 0; i < merc_spells.size(); i++) { + if(merc_spells[i].spellid == spellid) { + merc_spells[i].time_cancast = Timer::GetCurrentTime() + recast_delay; + } + } +} + +bool Merc::CheckTaunt() { + Mob* tar = GetTarget(); + //Only taunt if we are not top on target's hate list + //This ensures we have taunt available to regain aggro if needed + if(tar && tar->GetHateTop() && tar->GetHateTop() != this) { + return true; + } + return false; +} + +bool Merc::CheckAETaunt() { + //need to check area for mobs needing taunted + MercSpell mercSpell = GetBestMercSpellForAETaunt(this); + uint8 result = 0; + + if(mercSpell.spellid != 0) { + + std::list npc_list; + entity_list.GetNPCList(npc_list); + + for(std::list::iterator itr = npc_list.begin(); itr != npc_list.end(); ++itr) { + NPC* npc = *itr; + float dist = npc->DistNoRootNoZ(*this); + int range = GetActSpellRange(mercSpell.spellid, spells[mercSpell.spellid].range); + range *= range; + + if(dist <= range) { + if(!npc->IsMezzed()) { + if(HasGroup()) { + Group* g = GetGroup(); + + if(g) { + for(int i = 0; i < g->GroupCount(); i++) { + //if(npc->IsOnHatelist(g->members[i]) && g->members[i]->GetTarget() != npc && g->members[i]->IsEngaged()) { + if(GetTarget() != npc && g->members[i]->GetTarget() != npc && npc->IsOnHatelist(g->members[i])) { + result++; + } + } + } + } + } + } + } + + if(result >= 1) { + if(MERC_DEBUG > 0) + Message(7, "%s: Attempting AE Taunt", GetCleanName()); + return true; + } + } + return false; +} + +Corpse* Merc::GetGroupMemberCorpse() { + Corpse* corpse = nullptr; + + if(HasGroup()) { + Group* g = GetGroup(); + + if(g) { + for(int i = 0; i < g->GroupCount(); i++) { + if(g->members[i] && g->members[i]->IsClient()) { + corpse = entity_list.GetCorpseByOwnerWithinRange(g->members[i]->CastToClient(), this, RuleI(Mercs, ResurrectRadius)); + + if(corpse && !corpse->IsRezzed()) { + return corpse; + } + } + } + } + } + return 0; +} + +bool Merc::TryHide() { + if(GetClass() != MELEEDPS) { + return false; + } + + //don't hide if already hidden + if(hidden == true) { + return false; + } + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct)); + SpawnAppearance_Struct* sa_out = (SpawnAppearance_Struct*)outapp->pBuffer; + sa_out->spawn_id = GetID(); + sa_out->type = 0x03; + sa_out->parameter = 1; + entity_list.QueueClients(this, outapp, true); + safe_delete(outapp); + hidden = true; + + return true; +} + +//Checks if Merc still has confidence. Can be checked to begin fleeing, or to regain confidence after confidence loss - true = confident, false = confidence loss +bool Merc::CheckConfidence() { + bool result = true; + int ConfidenceLossChance = 0; + float ConfidenceCheck = 0; + int ConfidenceRating = 2 * GetProficiencyID(); + + std::list npc_list; + entity_list.GetNPCList(npc_list); + + for(std::list::iterator itr = npc_list.begin(); itr != npc_list.end(); ++itr) { + NPC* mob = *itr; + float ConRating = 1.0; + int CurrentCon = 0; + + if(!mob) continue; + + if(!mob->IsEngaged()) continue; + + if(mob->IsFeared() || mob->IsMezzed() || mob->IsStunned() || mob->IsRooted() || mob->IsCharmed()) continue; + + if(!mob->CheckAggro(this)) continue; + + float AggroRange = mob->GetAggroRange(); + + // Square it because we will be using DistNoRoot + + AggroRange = AggroRange * AggroRange; + + if(mob->DistNoRoot(*this) > AggroRange) continue; + + CurrentCon = this->GetLevelCon(mob->GetLevel()); + switch(CurrentCon) { + + case CON_GREEN: { + ConRating = 0; + break; + } + + case CON_LIGHTBLUE: { + ConRating = 0.2; + break; + } + + case CON_BLUE: { + ConRating = 0.6; + break; + } + + case CON_WHITE: { + ConRating = 1.0; + break; + } + + case CON_YELLOW: { + ConRating = 1.2; + break; + } + + case CON_RED: { + ConRating = 1.5; + break; + } + + default: { + ConRating = 0; + break; + } + } + + ConfidenceCheck += ConRating; + } + + if(ConfidenceRating < ConfidenceCheck) { + ConfidenceLossChance = 25 - ( 5 * (GetTierID() - 1)); + } + + if(zone->random.Roll(ConfidenceLossChance)) { + result = false; + } + + return result; +} + +void Merc::MercMeditate(bool isSitting) { + // Don't try to meditate if engaged or dead + if (IsEngaged() || GetAppearance() == eaDead) + { + return; + } + if(isSitting) { + // If the merc is a caster and has less than 99% mana while its not engaged, he needs to sit to meditate + if(GetManaRatio() < 99.0f) + { + if(!IsSitting()) + Sit(); + } + else + { + if(IsSitting()) + Stand(); + } + } + else + { + if(IsSitting()) + Stand(); + } + + if(IsSitting()) { + if(!rest_timer.Enabled()) { + rest_timer.Start(RuleI(Character, RestRegenTimeToActivate) * 1000); + } + } + else { + rest_timer.Disable(); + } +} + + +void Merc::Sit() { + if(IsMoving()) { + moved = false; + // SetHeading(CalculateHeadingToTarget(GetTarget()->GetX(), GetTarget()->GetY())); + SendPosition(); + SetMoving(false); + tar_ndx = 0; + } + + SetAppearance(eaSitting); +} + +void Merc::Stand() { + SetAppearance(eaStanding); +} + +bool Merc::IsSitting() { + bool result = false; + + if(GetAppearance() == eaSitting && !IsMoving()) + result = true; + + return result; +} + +bool Merc::IsStanding() { + bool result = false; + + if(GetAppearance() == eaStanding) + result = true; + + return result; +} + +float Merc::GetMaxMeleeRangeToTarget(Mob* target) { + float result = 0; + + if(target) { + float size_mod = GetSize(); + float other_size_mod = target->GetSize(); + + if(GetRace() == 49 || GetRace() == 158 || GetRace() == 196) //For races with a fixed size + size_mod = 60.0f; + else if (size_mod < 6.0) + size_mod = 8.0f; + + if(target->GetRace() == 49 || target->GetRace() == 158 || target->GetRace() == 196) //For races with a fixed size + other_size_mod = 60.0f; + else if (other_size_mod < 6.0) + other_size_mod = 8.0f; + + if (other_size_mod > size_mod) { + size_mod = other_size_mod; + } + + // this could still use some work, but for now it's an improvement.... + + if (size_mod > 29) + size_mod *= size_mod; + else if (size_mod > 19) + size_mod *= size_mod * 2; + else + size_mod *= size_mod * 4; + + // prevention of ridiculously sized hit boxes + if (size_mod > 10000) + size_mod = size_mod / 7; + + result = size_mod; + } + + return result; +} + +void Merc::DoClassAttacks(Mob *target) { + if(target == nullptr) + return; //gotta have a target for all these + + bool ca_time = classattack_timer.Check(false); + + //only check attack allowed if we are going to do something + if(ca_time && !IsAttackAllowed(target)) + return; + + if(!ca_time) + return; + + float HasteModifier = GetHaste() * 0.01f; + + int level = GetLevel(); + int reuse = TauntReuseTime * 1000; //make this very long since if they dont use it once, they prolly never will + bool did_attack = false; + //class specific stuff... + switch(GetClass()) { + case MELEEDPS: + if(level >= 10) { + reuse = BackstabReuseTime * 1000; + TryBackstab(target, reuse); + did_attack = true; + } + break; + case TANK:{ + if(level >= RuleI(Combat, NPCBashKickLevel)){ + if(zone->random.Int(0, 100) > 25) //tested on live, warrior mobs both kick and bash, kick about 75% of the time, casting doesn't seem to make a difference. + { + DoAnim(animKick); + int32 dmg = 0; + + if(GetWeaponDamage(target, (const Item_Struct*)nullptr) <= 0){ + dmg = -5; + } + else{ + if(target->CheckHitChance(this, SkillKick, 0)) { + if(RuleB(Combat, UseIntervalAC)) + dmg = GetKickDamage(); + else + dmg = zone->random.Int(1, GetKickDamage()); + + } + } + + reuse = KickReuseTime * 1000; + DoSpecialAttackDamage(target, SkillKick, dmg, 1, -1, reuse); + did_attack = true; + } + else + { + DoAnim(animTailRake); + int32 dmg = 0; + + if(GetWeaponDamage(target, (const Item_Struct*)nullptr) <= 0){ + dmg = -5; + } + else{ + if(target->CheckHitChance(this, SkillBash, 0)) { + if(RuleB(Combat, UseIntervalAC)) + dmg = GetBashDamage(); + else + dmg = zone->random.Int(1, GetBashDamage()); + } + } + + reuse = BashReuseTime * 1000; + DoSpecialAttackDamage(target, SkillBash, dmg, 1, -1, reuse); + did_attack = true; + } + } + break; + } + } + + classattack_timer.Start(reuse / HasteModifier); +} + +bool Merc::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts) +{ + if (!other) { + SetTarget(nullptr); + LogFile->write(EQEMuLog::Error, "A null Mob object was passed to Merc::Attack() for evaluation!"); + return false; + } + + return NPC::Attack(other, Hand, bRiposte, IsStrikethrough, IsFromSpell, opts); +} + +void Merc::Damage(Mob* other, int32 damage, uint16 spell_id, SkillUseTypes attack_skill, bool avoidable, int8 buffslot, bool iBuffTic) +{ + if(IsDead() || IsCorpse()) + return; + + if(spell_id==0) + spell_id = SPELL_UNKNOWN; + + NPC::Damage(other, damage, spell_id, attack_skill, avoidable, buffslot, iBuffTic); + + //Not needed since we're using NPC damage. + //CommonDamage(other, damage, spell_id, attack_skill, avoidable, buffslot, iBuffTic); +} + +bool Merc::FindTarget() { + bool found = false; + Mob* target = GetHateTop(); + + if(target) { + found = true; + SetTarget(target); + } + + return found; +} + +void Merc::SetTarget(Mob* mob) { + NPC::SetTarget(mob); +} + +Mob* Merc::GetOwnerOrSelf() { + Mob* Result = 0; + + if(this->GetMercOwner()) + Result = GetMercOwner(); + else + Result = this; + + return Result; +} + +bool Merc::Death(Mob* killerMob, int32 damage, uint16 spell, SkillUseTypes attack_skill) +{ + if(!NPC::Death(killerMob, damage, spell, attack_skill)) + { + return false; + } + + Save(); + + //no corpse, no exp if we're a merc. + //We'll suspend instead, since that's what live does. + //Not actually sure live supports 'depopping' merc corpses. + //if(entity_list.GetCorpseByID(GetID())) + // entity_list.GetCorpseByID(GetID())->Depop(); + + // If client is in zone, suspend merc, else depop it. + if (!Suspend()) + { + Depop(); + } + + return true; +} + +Client* Merc::GetMercOwner() { + Client* mercOwner = 0; + + if(GetOwner()) + { + if(GetOwner()->IsClient()) + { + mercOwner = GetOwner()->CastToClient(); + } + } + + return mercOwner; +} + +Mob* Merc::GetOwner() { + Mob* Result = 0; + + Result = entity_list.GetMob(GetOwnerID()); + + if(!Result) { + this->SetOwnerID(0); + } + + return Result->CastToMob(); +} + +const char* Merc::GetRandomName(){ + // creates up to a 10 char name + static char name[17]; + char vowels[18]="aeiouyaeiouaeioe"; + char cons[48]="bcdfghjklmnpqrstvwxzybcdgklmnprstvwbcdgkpstrkd"; + char rndname[17]="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; + char paircons[33]="ngrkndstshthphsktrdrbrgrfrclcr"; + bool valid = false; + + while(!valid) { + int rndnum=zone->random.Int(0, 75),n=1; + bool dlc=false; + bool vwl=false; + bool dbl=false; + if (rndnum>63) + { // rndnum is 0 - 75 where 64-75 is cons pair, 17-63 is cons, 0-16 is vowel + rndnum=(rndnum-61)*2; // name can't start with "ng" "nd" or "rk" + rndname[0]=paircons[rndnum]; + rndname[1]=paircons[rndnum+1]; + n=2; + } + else if (rndnum>16) + { + rndnum-=17; + rndname[0]=cons[rndnum]; + } + else + { + rndname[0]=vowels[rndnum]; + vwl=true; + } + int namlen=zone->random.Int(5, 10); + for (int i=n;irandom.Int(0, 62); + if (rndnum>46) + { // pick a cons pair + if (i>namlen-3) // last 2 chars in name? + { // name can only end in cons pair "rk" "st" "sh" "th" "ph" "sk" "nd" or "ng" + rndnum=zone->random.Int(0, 7)*2; + } + else + { // pick any from the set + rndnum=(rndnum-47)*2; + } + rndname[i]=paircons[rndnum]; + rndname[i+1]=paircons[rndnum+1]; + dlc=true; // flag keeps second letter from being doubled below + i+=1; + } + else + { // select a single cons + rndname[i]=cons[rndnum]; + } + } + else + { // select a vowel + rndname[i]=vowels[zone->random.Int(0, 16)]; + } + vwl=!vwl; + if (!dbl && !dlc) + { // one chance at double letters in name + if (!zone->random.Int(0, i+9)) // chances decrease towards end of name + { + rndname[i+1]=rndname[i]; + dbl=true; + i+=1; + } + } + } + + rndname[0]=toupper(rndname[0]); + + if(!database.CheckNameFilter(rndname)) { + valid = false; + } + else if(rndname[0] < 'A' && rndname[0] > 'Z') { + //name must begin with an upper-case letter. + valid = false; + } + else if (database.CheckUsedName(rndname)) { + valid = true; + } + else { + valid = false; + } + } + + memset(name, 0, 17); + strcpy(name, rndname); + return name; +} + +bool Compare_Merc_Spells(MercSpell i, MercSpell j); + +bool Compare_Merc_Spells(MercSpell i, MercSpell j) +{ + return(i.slot > j.slot); +} + +bool Merc::LoadMercSpells() { + // loads mercs spells into list + merc_spells.clear(); + + std::list spellList = zone->merc_spells_list[GetClass()]; + + if (spellList.size() == 0) { + AIautocastspell_timer->Disable(); + return false; + } + + uint8 proficiency_id = GetProficiencyID(); + int16 attack_proc_spell = -1; + int8 proc_chance = 0; + + for (std::list::iterator mercSpellEntryItr = spellList.begin(); mercSpellEntryItr != spellList.end(); ++mercSpellEntryItr) { + if (proficiency_id == mercSpellEntryItr->proficiencyid && GetLevel() >= mercSpellEntryItr->minlevel && GetLevel() <= mercSpellEntryItr->maxlevel && mercSpellEntryItr->spellid > 0) { + MercSpell mercSpell; + + mercSpell.spellid = mercSpellEntryItr->spellid; + mercSpell.type = mercSpellEntryItr->type; + mercSpell.stance = mercSpellEntryItr->stance; + mercSpell.slot = mercSpellEntryItr->slot; + mercSpell.proc_chance = mercSpellEntryItr->proc_chance; + mercSpell.time_cancast = 0; + + merc_spells.push_back(mercSpell); + + if(mercSpellEntryItr->proc_chance > 0) + AddProcToWeapon(mercSpellEntryItr->spellid, true, mercSpellEntryItr->proc_chance); + } + } + std::sort(merc_spells.begin(), merc_spells.end(), Compare_Merc_Spells); + + if (merc_spells.size() == 0) + AIautocastspell_timer->Disable(); + else { + HasAISpell = true; + AIautocastspell_timer->Trigger(); + } + + if(MERC_DEBUG > 0) { + /*GetMercOwner()->Message(7, "Mercenary Debug: Loaded %i spells for merc.", merc_spells.size()); + + GetMercOwner()->Message(7, "Mercenary Debug: Spell list for merc."); + for (int i = merc_spells.size() - 1; i >= 0; i--) { + GetMercOwner()->Message(7, "%i] Slot: %i, SpellID: %i, Type: %i, Stance: %i, Proc Chance: %i", i, merc_spells[i].slot, merc_spells[i].spellid, merc_spells[i].type, merc_spells[i].stance, merc_spells[i].proc_chance); + }*/ + } + + return true; +} + +bool Merc::Save() { + + if(database.SaveMerc(this)){ + return true; + } + + return false; +} + +Merc* Merc::LoadMerc(Client *c, MercTemplate* merc_template, uint32 merchant_id, bool updateFromDB) { + + if(c) + { + if(c->GetMercID()) + { + merc_template = zone->GetMercTemplate(c->GetMercInfo().MercTemplateID); + } + } + + //get mercenary data + if(merc_template) + { + //TODO: Maybe add a way of updating client merc stats in a seperate function? like, for example, on leveling up. + const NPCType* npc_type_to_copy = database.GetMercType(merc_template->MercNPCID, merc_template->RaceID, c->GetLevel()); + if(npc_type_to_copy != nullptr) + { + //This is actually a very terrible method of assigning stats, and should be changed at some point. See the comment in merc's deconstructor. + NPCType* npc_type = new NPCType; + memset(npc_type, 0, sizeof(NPCType)); + memcpy(npc_type, npc_type_to_copy, sizeof(NPCType)); + if(c && !updateFromDB) + { + if(c->GetMercInfo().merc_name[0] == 0) + { + snprintf(c->GetMercInfo().merc_name, 64, "%s", GetRandomName()); //sanity check. + } + snprintf(npc_type->name, 64, "%s", c->GetMercInfo().merc_name); + } + + npc_type->race = merc_template->RaceID; + + // Use the Gender and Size of the Merchant if possible + uint8 tmpgender = 0; + float tmpsize = 6.0f; + if(merchant_id > 0) + { + NPC* tar = entity_list.GetNPCByID(merchant_id); + if(tar) + { + tmpgender = tar->GetGender(); + tmpsize = tar->GetSize(); + } + else + { + tmpgender = Mob::GetDefaultGender(npc_type->race, c->GetMercInfo().Gender); + } + + } + else + { + tmpgender = c->GetMercInfo().Gender; + tmpsize = c->GetMercInfo().MercSize; + } + + sprintf(npc_type->lastname, "%s's Mercenary", c->GetName()); + npc_type->gender = tmpgender; + npc_type->size = tmpsize; + npc_type->loottable_id = 0; // Loottable has to be 0, otherwise we'll be leavin' some corpses! + npc_type->npc_id = 0; //NPC ID has to be 0, otherwise db gets all confuzzled. + npc_type->class_ = merc_template->ClassID; + npc_type->maxlevel = 0; //We should hard-set this to override scalerate's functionality in the NPC class when it is constructed. + + Merc* merc = new Merc(npc_type, c->GetX(), c->GetY(), c->GetZ(), 0); + + if(merc) + { + merc->SetMercData( merc_template->MercTemplateID ); + database.LoadMercEquipment(merc); + merc->UpdateMercStats(c); + + if(updateFromDB) + { + database.LoadCurrentMerc(c); + + merc->SetMercID(c->GetMercInfo().mercid); + snprintf(merc->name, 64, "%s", c->GetMercInfo().merc_name); + merc->SetSuspended(c->GetMercInfo().IsSuspended); + merc->gender = c->GetMercInfo().Gender; + merc->size = c->GetMercInfo().MercSize; + merc->SetHP(c->GetMercInfo().hp <= 0 ? merc->GetMaxHP() : c->GetMercInfo().hp); + merc->SetMana(c->GetMercInfo().hp <= 0 ? merc->GetMaxMana() : c->GetMercInfo().mana); + merc->SetEndurance(c->GetMercInfo().endurance); + merc->luclinface = c->GetMercInfo().face; + merc->hairstyle = c->GetMercInfo().luclinHairStyle; + merc->haircolor = c->GetMercInfo().luclinHairColor; + merc->eyecolor1 = c->GetMercInfo().luclinEyeColor; + merc->eyecolor2 = c->GetMercInfo().luclinEyeColor2; + merc->beardcolor = c->GetMercInfo().luclinBeardColor; + merc->beard = c->GetMercInfo().luclinBeard; + merc->drakkin_heritage = c->GetMercInfo().drakkinHeritage; + merc->drakkin_tattoo = c->GetMercInfo().drakkinTattoo; + merc->drakkin_details = c->GetMercInfo().drakkinDetails; + } + else + { + // Give Random Features to newly hired Mercs + merc->RandomizeFeatures(false, true); + } + + if(merc->GetMercID()) { + database.LoadMercBuffs(merc); + } + + merc->LoadMercSpells(); + } + + return merc; + } + } + + return 0; +} + +void Merc::UpdateMercInfo(Client *c) { + snprintf(c->GetMercInfo().merc_name, 64, "%s", name); + c->GetMercInfo().mercid = GetMercID(); + c->GetMercInfo().IsSuspended = IsSuspended(); + c->GetMercInfo().Gender = GetGender(); + c->GetMercInfo().MercSize = GetSize(); + c->GetMercInfo().hp = GetHP(); + c->GetMercInfo().mana = GetMana(); + c->GetMercInfo().endurance = GetEndurance(); + c->GetMercInfo().face = luclinface; + c->GetMercInfo().luclinHairStyle = hairstyle; + c->GetMercInfo().luclinHairColor = haircolor; + c->GetMercInfo().luclinEyeColor = eyecolor1; + c->GetMercInfo().luclinEyeColor2 = eyecolor2; + c->GetMercInfo().luclinBeardColor = beardcolor; + c->GetMercInfo().luclinBeard = beard; + c->GetMercInfo().drakkinHeritage = drakkin_heritage; + c->GetMercInfo().drakkinTattoo = drakkin_tattoo; + c->GetMercInfo().drakkinDetails = drakkin_details; +} + +void Merc::UpdateMercStats(Client *c) { + if(c->GetMercInfo().MercTemplateID >0) + { + const NPCType* npc_type = database.GetMercType( zone->GetMercTemplate(c->GetMercInfo().MercTemplateID)->MercNPCID, GetRace(), c->GetLevel()); + if (npc_type) + { + max_hp = (npc_type->max_hp * npc_type->scalerate) / 100; + base_hp = (npc_type->max_hp * npc_type->scalerate) / 100; + max_mana = (npc_type->Mana * npc_type->scalerate) / 100; + base_mana = (npc_type->Mana * npc_type->scalerate) / 100; + hp_regen = (npc_type->hp_regen * npc_type->scalerate) / 100; + mana_regen = (npc_type->mana_regen * npc_type->scalerate) / 100; + level = npc_type->level; + max_dmg = (npc_type->max_dmg * npc_type->scalerate) / 100; + min_dmg = (npc_type->min_dmg * npc_type->scalerate) / 100; + _baseSTR = (npc_type->STR * npc_type->scalerate) / 100; + _baseSTA = (npc_type->STA * npc_type->scalerate) / 100; + _baseDEX = (npc_type->DEX * npc_type->scalerate) / 100; + _baseAGI = (npc_type->AGI * npc_type->scalerate) / 100; + _baseWIS = (npc_type->WIS * npc_type->scalerate) / 100; + _baseINT = (npc_type->INT * npc_type->scalerate) / 100; + _baseCHA = (npc_type->CHA * npc_type->scalerate) / 100; + _baseATK = (npc_type->ATK * npc_type->scalerate) / 100; + _baseMR = (npc_type->MR * npc_type->scalerate) / 100; + _baseFR = (npc_type->FR * npc_type->scalerate) / 100; + _baseDR = (npc_type->DR * npc_type->scalerate) / 100; + _basePR = (npc_type->PR * npc_type->scalerate) / 100; + _baseCR = (npc_type->CR * npc_type->scalerate) / 100; + _baseCorrup = (npc_type->Corrup * npc_type->scalerate) / 100; + _baseAC = (npc_type->AC * npc_type->scalerate) / 100; + attack_speed = npc_type->attack_speed; + attack_count = npc_type->attack_count; + spellscale = npc_type->spellscale; + healscale = npc_type->healscale; + + CalcBonuses(); + + CalcMaxEndurance(); + CalcMaxHP(); + CalcMaxMana(); + } + } +} + +void Merc::UpdateMercAppearance() { + // Copied from Bot Code: + uint32 itemID = NO_ITEM; + uint8 materialFromSlot = _MaterialInvalid; + for(int i = EmuConstants::EQUIPMENT_BEGIN; i <= EmuConstants::EQUIPMENT_END; ++i) { + itemID = equipment[i]; + if(itemID != NO_ITEM) { + materialFromSlot = Inventory::CalcMaterialFromSlot(i); + if(materialFromSlot != _MaterialInvalid) + this->SendWearChange(materialFromSlot); + } + } +} + +void Merc::AddItem(uint8 slot, uint32 item_id) { + equipment[slot] = item_id; +} + +bool Merc::Spawn(Client *owner) { + + if(!owner) + return false; + + MercTemplate* merc_template = zone->GetMercTemplate(GetMercTemplateID()); + + if(!merc_template) + return false; + + entity_list.AddMerc(this, true, true); + + SendPosition(); + + if (MERC_DEBUG > 0) + owner->Message(7, "Mercenary Debug: Spawn."); + + //UpdateMercAppearance(); + + //printf("Spawned Merc with ID %i\n", npc->GetID()); fflush(stdout); + + return true; +} + +void Client::SendMercResponsePackets(uint32 ResponseType) +{ + switch (ResponseType) + { + case 0: // Mercenary Spawned Successfully? + SendMercMerchantResponsePacket(0); + break; + case 1: //You do not have enough funds to make that purchase! + SendMercMerchantResponsePacket(1); + break; + case 2: //Mercenary does not exist! + SendMercMerchantResponsePacket(2); + break; + case 3: //Mercenary failed to spawn! + SendMercMerchantResponsePacket(3); + break; + case 4: //Mercenaries are not allowed in raids! + SendMercMerchantResponsePacket(4); + break; + case 5: //You already have a pending mercenary purchase! + SendMercMerchantResponsePacket(5); + break; + case 6: //You have the maximum number of mercenaries. You must dismiss one before purchasing a new one! + SendMercMerchantResponsePacket(6); + break; + case 7: //You must dismiss your suspended mercenary before purchasing a new one! + if (GetClientVersion() < EQClientRoF) + SendMercMerchantResponsePacket(7); + else + //You have the maximum number of mercenaries. You must dismiss one before purchasing a new one! + SendMercMerchantResponsePacket(6); + break; + case 8: //You can not purchase a mercenary because your group is full! + if (GetClientVersion() < EQClientRoF) + SendMercMerchantResponsePacket(8); + else + SendMercMerchantResponsePacket(7); + break; + case 9: //You can not purchase a mercenary because you are in combat! + if (GetClientVersion() < EQClientRoF) + //Mercenary failed to spawn! + SendMercMerchantResponsePacket(3); + else + SendMercMerchantResponsePacket(8); + break; + case 10: //You have recently dismissed a mercenary and must wait a few more seconds before you can purchase a new one! + if (GetClientVersion() < EQClientRoF) + //Mercenary failed to spawn! + SendMercMerchantResponsePacket(3); + else + SendMercMerchantResponsePacket(9); + break; + case 11: //An error occurred created your mercenary! + if (GetClientVersion() < EQClientRoF) + SendMercMerchantResponsePacket(9); + else + SendMercMerchantResponsePacket(10); + break; + case 12: //Upkeep Charge Message + if (GetClientVersion() < EQClientRoF) + SendMercMerchantResponsePacket(10); + else + SendMercMerchantResponsePacket(11); + break; + case 13: // ??? + if (GetClientVersion() < EQClientRoF) + SendMercMerchantResponsePacket(11); + else + SendMercMerchantResponsePacket(12); + break; + case 14: //You ran out of funds to pay for your mercenary! + if (GetClientVersion() < EQClientRoF) + SendMercMerchantResponsePacket(12); + else + SendMercMerchantResponsePacket(13); + break; + case 15: // ??? + if (GetClientVersion() < EQClientRoF) + SendMercMerchantResponsePacket(13); + else + SendMercMerchantResponsePacket(14); + break; + case 16: //Your mercenary is about to be suspended due to insufficient funds! + if (GetClientVersion() < EQClientRoF) + SendMercMerchantResponsePacket(14); + else + SendMercMerchantResponsePacket(15); + break; + case 17: //There is no mercenary liaison nearby! + if (GetClientVersion() < EQClientRoF) + SendMercMerchantResponsePacket(15); + else + SendMercMerchantResponsePacket(16); + break; + case 18: //You are too far from the liaison! + if (GetClientVersion() < EQClientRoF) + SendMercMerchantResponsePacket(16); + else + SendMercMerchantResponsePacket(17); + break; + case 19: //You do not meet the requirements for that mercenary! + if (GetClientVersion() < EQClientRoF) + SendMercMerchantResponsePacket(17); + else + SendMercMerchantResponsePacket(18); + break; + case 20: //You are unable to interact with the liaison! + if (GetClientVersion() < EQClientRoF) + //You are too far from the liaison! + SendMercMerchantResponsePacket(16); + else + SendMercMerchantResponsePacket(19); + break; + case 21: //You do not have a high enough membership level to purchase this mercenary! + if (GetClientVersion() < EQClientRoF) + //You do not meet the requirements for that mercenary! + SendMercMerchantResponsePacket(17); + else + SendMercMerchantResponsePacket(20); + break; + case 22: //Your purchase has failed because this mercenary requires a Gold membership! + if (GetClientVersion() < EQClientRoF) + //You do not meet the requirements for that mercenary! + SendMercMerchantResponsePacket(17); + else + SendMercMerchantResponsePacket(21); + break; + case 23: //Your purchase has failed because this mercenary requires at least a Silver membership! + if (GetClientVersion() < EQClientRoF) + //You do not meet the requirements for that mercenary! + SendMercMerchantResponsePacket(17); + else + SendMercMerchantResponsePacket(22); + break; + default: //Mercenary failed to spawn! + SendMercMerchantResponsePacket(3); + break; + } + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: SendMercResponsePackets %i.", ResponseType); +} + +void Client::UpdateMercTimer() +{ + Merc *merc = GetMerc(); + + if(merc && !merc->IsSuspended()) + { + if(GetMercTimer()->Check()) + { + uint32 upkeep = merc->CalcUpkeepCost(merc->GetMercTemplateID(), GetLevel()); + + if(CheckCanRetainMerc(upkeep)) + { + if(RuleB(Mercs, ChargeMercUpkeepCost)) + { + TakeMoneyFromPP((upkeep * 100), true); + } + } + else + { + merc->Suspend(); + return; + } + + // Reset the upkeep timer + GetMercInfo().MercTimerRemaining = RuleI(Mercs, UpkeepIntervalMS); + SendMercTimer(merc); + GetMercTimer()->Start(RuleI(Mercs, UpkeepIntervalMS)); + GetMercTimer()->SetTimer(GetMercInfo().MercTimerRemaining); + + // Send upkeep charge message + SendMercResponsePackets(12); + + // Warn that mercenary is about to be suspended due to insufficient funds (on next upkeep) + if (RuleB(Mercs, ChargeMercUpkeepCost) && upkeep > 0 && !HasMoney(upkeep * 100)) + { + SendMercResponsePackets(16); + } + + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: UpdateMercTimer Complete."); + + // Normal upkeep charge message + //Message(7, "You have been charged a mercenary upkeep cost of %i plat, and %i gold and your mercenary upkeep cost timer has been reset to 15 minutes.", upkeep_plat, upkeep_gold, (int)(RuleI(Mercs, UpkeepIntervalMS) / 1000 / 60)); + + // Message below given when too low level to be charged + //Message(7, "Your mercenary waived an upkeep cost of %i plat, and %i gold or %i %s and your mercenary upkeep cost timer has been reset to %i minutes", upkeep_plat, upkeep_gold, 1, "Bayle Marks", (int)(RuleI(Mercs, UpkeepIntervalMS) / 1000 / 60)); + } + } +} + +bool Client::CheckCanHireMerc(Mob* merchant, uint32 template_id) { + + if (!CheckCanSpawnMerc(template_id)) + { + return false; + } + + MercTemplate* mercTemplate = zone->GetMercTemplate(template_id); + + //check for suspended merc + if(GetMercInfo().mercid != 0 && GetMercInfo().IsSuspended) { + SendMercResponsePackets(6); + return false; + } + + // Check if max number of mercs is already reached + if(GetNumMercs() >= MAXMERCS) { + SendMercResponsePackets(6); + return false; + } + + //check for valid merchant + if(!merchant) { + SendMercResponsePackets(17); + return false; + } + + //check for merchant too far away + if(DistNoRoot(*merchant) > USE_NPC_RANGE2) { + SendMercResponsePackets(18); + return false; + } + + //check for sufficient funds and remove them last + if(RuleB(Mercs, ChargeMercPurchaseCost)) { + uint32 cost = Merc::CalcPurchaseCost(template_id, GetLevel()) * 100; // Cost is in gold + if(cost > 0 && !HasMoney(cost)) { + SendMercResponsePackets(1); + return false; + } + } + + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: CheckCanHireMerc True."); + + return true; +} + +bool Client::CheckCanRetainMerc(uint32 upkeep) { + Merc* merc = GetMerc(); + + //check for sufficient funds + if(RuleB(Mercs, ChargeMercPurchaseCost)) { + if(merc) { + if(upkeep > 0 && !HasMoney(upkeep * 100)) { + SendMercResponsePackets(14); + return false; + } + } + } + + return true; +} + +bool Client::CheckCanSpawnMerc(uint32 template_id) { + + // Check if mercs are enabled globally + if(!RuleB(Mercs, AllowMercs)) + { + return false; + } + + // Check if zone allows mercs + if(!zone->AllowMercs()) + { + SendMercResponsePackets(3); + return false; + } + + MercTemplate* mercTemplate = zone->GetMercTemplate(template_id); + + // Invalid merc data + if(!mercTemplate) + { + SendMercResponsePackets(11); + return false; + } + + // Check client version + if(GetClientVersion() < mercTemplate->ClientVersion) + { + SendMercResponsePackets(3); + return false; + } + + // Check for raid + if(HasRaid()) + { + SendMercResponsePackets(4); + return false; + } + + // Check group size + if(GetGroup() && GetGroup()->GroupCount() >= MAX_GROUP_MEMBERS) // database.GroupCount(GetGroup()->GetID()) + { + SendMercResponsePackets(8); + return false; + } + + // Check in combat + if(GetAggroCount() > 0) + { + SendMercResponsePackets(9); + return false; + } + + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: CheckCanSpawnMerc True."); + + return true; +} + +bool Client::CheckCanUnsuspendMerc() { + + if (!CheckCanSpawnMerc(GetMercInfo().MercTemplateID)) + { + return false; + } + + MercTemplate* mercTemplate = zone->GetMercTemplate(GetMercInfo().MercTemplateID); + + if(!GetPTimers().Expired(&database, pTimerMercSuspend, false)) + { + SendMercResponsePackets(10); + //TODO: find this packet response and tell them properly. + Message(0, "You must wait %i seconds before unsuspending your mercenary.", GetPTimers().GetRemainingTime(pTimerMercSuspend)); + return false; + } + + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: CheckCanUnsuspendMerc True."); + + return true; +} + +void Client::CheckMercSuspendTimer() { + + if(GetMercInfo().SuspendedTime != 0) + { + if(time(nullptr) >= GetMercInfo().SuspendedTime) + { + GetMercInfo().SuspendedTime = 0; + SendMercResponsePackets(0); + SendMercSuspendResponsePacket(GetMercInfo().SuspendedTime); + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: CheckMercSuspendTimer Ready."); + } + } +} + +void Client::SuspendMercCommand() { + + if(GetMercInfo().MercTemplateID != 0) + { + if(GetMercInfo().IsSuspended) + { + if(!CheckCanUnsuspendMerc()) + { + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: SuspendMercCommand Unable to Unsuspend."); + + return; + } + + // Get merc, assign it to client & spawn + Merc* merc = Merc::LoadMerc(this, &zone->merc_templates[GetMercInfo().MercTemplateID], 0, true); + if(merc) + { + SpawnMerc(merc, true); + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: SuspendMercCommand Successful Unsuspend."); + } + else + { + //merc failed to spawn + SendMercResponsePackets(3); + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: SuspendMercCommand Failed to Spawn Merc."); + } + } + else + { + Merc* CurrentMerc = GetMerc(); + + if(CurrentMerc && GetMercID()) + { + CurrentMerc->Suspend(); + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: SuspendMercCommand Successful Suspend."); + } + } + } +} + + +// Handles all client zone change event +void Merc::ProcessClientZoneChange(Client* mercOwner) { + + if(mercOwner) + { + Zone(); + } +} + +void Client::SpawnMercOnZone() { + + if(!RuleB(Mercs, AllowMercs)) + return; + + if (GetMerc()) + return; + + if(database.LoadMercInfo(this)) + { + if(!GetMercInfo().IsSuspended) + { + GetMercInfo().SuspendedTime = 0; + // Get merc, assign it to client & spawn + Merc* merc = Merc::LoadMerc(this, &zone->merc_templates[GetMercInfo().MercTemplateID], 0, true); + if(merc) + { + SpawnMerc(merc, false); + } + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: SpawnMercOnZone Normal Merc."); + } + else + { + int32 TimeDiff = GetMercInfo().SuspendedTime - time(nullptr); + if (TimeDiff > 0) + { + if (!GetPTimers().Enabled(pTimerMercSuspend)) + { + // Start the timer to send the packet that refreshes the Unsuspend Button + GetPTimers().Start(pTimerMercSuspend, TimeDiff); + } + } + // Send Mercenary Status/Timer packet + SendMercTimer(GetMerc()); + //SendMercPersonalInfo(); + CheckMercSuspendTimer(); + + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: SpawnMercOnZone Suspended Merc."); + } + } + else + { + // No Merc Hired + SendClearMercInfo(); + } +} + +void Client::SendMercTimer(Merc* merc) { + + if (GetMercInfo().mercid == 0) + { + return; + } + + if (!merc) + { + SendMercTimerPacket(NO_MERC_ID, MERC_STATE_SUSPENDED, GetMercInfo().SuspendedTime, GetMercInfo().MercTimerRemaining, RuleI(Mercs, SuspendIntervalMS)); + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: SendMercTimer No Merc."); + } + else if (merc->IsSuspended()) + { + SendMercTimerPacket(NO_MERC_ID, MERC_STATE_SUSPENDED, GetMercInfo().SuspendedTime, GetMercInfo().MercTimerRemaining, RuleI(Mercs, SuspendIntervalMS)); + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: SendMercTimer Suspended Merc."); + } + else + { + SendMercTimerPacket(merc->GetID(), MERC_STATE_NORMAL, NOT_SUSPENDED_TIME, GetMercInfo().MercTimerRemaining, RuleI(Mercs, SuspendIntervalMS)); + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: SendMercTimer Normal Merc."); + } + +} + +void Client::SpawnMerc(Merc* merc, bool setMaxStats) { + + if (!merc || !CheckCanSpawnMerc(merc->GetMercTemplateID())) + { + if (merc) + { + merc->Suspend(); + } + return; + } + + merc->Spawn(this); + merc->SetSuspended(false); + SetMerc(merc); + merc->Unsuspend(setMaxStats); + merc->SetStance(GetMercInfo().Stance); + +<<<<<<< HEAD + //SendMercTimer(merc); + +======= +>>>>>>> master + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: SpawnMerc Success."); + + return; + +} + +bool Merc::Suspend() { + + Client* mercOwner = GetMercOwner(); + + if(!mercOwner) + return false; + + SetSuspended(true); + + mercOwner->GetMercInfo().IsSuspended = true; + mercOwner->GetMercInfo().SuspendedTime = time(nullptr) + RuleI(Mercs, SuspendIntervalS); + mercOwner->GetMercInfo().MercTimerRemaining = mercOwner->GetMercTimer()->GetRemainingTime(); + mercOwner->GetMercInfo().Stance = GetStance(); + Save(); + mercOwner->GetMercTimer()->Disable(); + mercOwner->SendMercSuspendResponsePacket(mercOwner->GetMercInfo().SuspendedTime); + mercOwner->SendMercTimer(this); + + Depop(); + + // Start the timer to send the packet that refreshes the Unsuspend Button + mercOwner->GetPTimers().Start(pTimerMercSuspend, RuleI(Mercs, SuspendIntervalS)); + + if (MERC_DEBUG > 0) + mercOwner->Message(7, "Mercenary Debug: Suspend Complete."); + + return true; +} + +bool Client::MercOnlyOrNoGroup() { + + if (!GetGroup()) + { + return true; + } + if (GetMerc()) + { + if (GetMerc()->HasGroup() && GetMerc()->GetGroup() == GetGroup()) + { + if (GetGroup()->GroupCount() < 3) + { + return true; + } + } + } + return false; +} + +bool Merc::Unsuspend(bool setMaxStats) { + + Client* mercOwner = nullptr; + + if(GetMercOwner()) { + mercOwner = GetMercOwner(); + } + + if(!mercOwner) + return false; + + if(GetID()) + { + // Set time remaining to max on unsuspend - there is a charge for unsuspending as well + SetSuspended(false); + + mercOwner->GetMercInfo().mercid = GetMercID(); + mercOwner->GetMercInfo().IsSuspended = false; + + mercOwner->SendMercenaryUnsuspendPacket(0); + mercOwner->SendMercenaryUnknownPacket(1); + mercOwner->GetMercInfo().SuspendedTime = 0; + // Reset the upkeep timer + mercOwner->GetMercInfo().MercTimerRemaining = RuleI(Mercs, UpkeepIntervalMS); + mercOwner->GetMercTimer()->Start(RuleI(Mercs, UpkeepIntervalMS)); + //mercOwner->GetMercTimer()->SetTimer(mercOwner->GetMercInfo().MercTimerRemaining); + mercOwner->SendMercTimer(this); + if(!mercOwner->GetPTimers().Expired(&database, pTimerMercSuspend, false)) + mercOwner->GetPTimers().Clear(&database, pTimerMercSuspend); + + if (MercJoinClientGroup()) + { + if(setMaxStats) + { + SetHP(GetMaxHP()); + SetMana(GetMaxMana()); + SetEndurance(GetMaxEndurance()); + } + + //check for sufficient funds and remove them last + if(RuleB(Mercs, ChargeMercUpkeepCost)) + { + uint32 cost = CalcUpkeepCost(GetMercTemplateID(), GetLevel()) * 100; // Cost is in gold + if(cost > 0 && !mercOwner->HasMoney(cost)) + { + mercOwner->SendMercResponsePackets(1); + Suspend(); + return false; + } + } + Save(); + } + } + + return true; +} + +bool Client::DismissMerc(uint32 MercID) { + + bool Dismissed = true; + if (!database.DeleteMerc(MercID)) + { + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: Dismiss Failed for MercID %i", MercID); + Dismissed = false; + } + else + { + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: Dismiss Successful."); + } + + if (GetMerc()) + { + GetMerc()->Depop(); + } + + SendClearMercInfo(); + SetMerc(nullptr); + + return Dismissed; +} + +void Merc::Zone() { + Save(); + Depop(); +} + +void Merc::Depop() { + + WipeHateList(); + + if(IsCasting()) + { + InterruptSpell(); + } + + entity_list.RemoveFromHateLists(this); + + if(GetGroup()) + { + RemoveMercFromGroup(this, GetGroup()); + } + + entity_list.RemoveMerc(this->GetID()); + + if(HasPet()) + { + GetPet()->Depop(); + } + + p_depop = true; + + NPC::Depop(false); +} + +bool Merc::RemoveMercFromGroup(Merc* merc, Group* group) { + + bool Result = false; + + if(merc && group) + { + if(merc->HasGroup()) + { + if(!group->IsLeader(merc)) + { + merc->SetFollowID(0); + + if(group->DelMember(merc, true)) + { + if(merc->GetMercCharacterID() != 0) + { + database.SetGroupID(merc->GetName(), 0, merc->GetMercCharacterID(), true); + } + } + + if(group->GroupCount() <= 1 && ZoneLoaded) + { + group->DisbandGroup(); + } + } + else + { + // A merc is group leader - Disband and re-group each member with their mercs + for(int i = 0; i < MAX_GROUP_MEMBERS; i++) + { + if(!group->members[i]) + continue; + + if(!group->members[i]->IsClient()) + continue; + + group->members[i]->CastToClient()->LeaveGroup(); + } + for(int i = 0; i < MAX_GROUP_MEMBERS; i++) + { + if(!group->members[i]) + continue; + + if(!group->members[i]->IsMerc()) + continue; + + group->members[i]->CastToMerc()->MercJoinClientGroup(); + } + // Group should be removed by now, but just in case: + group->DisbandGroup(); + } + + Result = true; + } + } + + return Result; +} + +bool Merc::MercJoinClientGroup() { + + Client* mercOwner = nullptr; + + if(GetMercOwner()) + { + mercOwner = GetMercOwner(); + } + + if(!mercOwner) + { + Suspend(); + return false; + } + + if(GetID()) + { + if (HasGroup()) + { + RemoveMercFromGroup(this, GetGroup()); + } + + Group* g = entity_list.GetGroupByClient(mercOwner); + + //nobody from our group is here... start a new group + if(!g) + { + g = new Group(mercOwner); + + if(!g) + { + delete g; + g = nullptr; + return false; + } + + entity_list.AddGroup(g); + + if(g->GetID() == 0) + { + delete g; + g = nullptr; + return false; + } + + if(AddMercToGroup(this, g)) + { + entity_list.AddGroup(g, g->GetID()); + database.SetGroupLeaderName(g->GetID(), mercOwner->GetName()); + database.SetGroupID(mercOwner->GetName(), g->GetID(), mercOwner->CharacterID(), false); + database.SetGroupID(this->GetName(), g->GetID(), mercOwner->CharacterID(), true); + database.RefreshGroupFromDB(mercOwner); + g->SaveGroupLeaderAA(); + if(MERC_DEBUG > 0) + mercOwner->Message(7, "Mercenary Debug: Mercenary joined new group."); + } + else + { + g->DisbandGroup(); + Suspend(); + + if(MERC_DEBUG > 0) + mercOwner->Message(7, "Mercenary Debug: Mercenary disbanded new group."); + } + + } + else if (AddMercToGroup(this, mercOwner->GetGroup())) + { + // Group already exists + database.SetGroupID(GetName(), mercOwner->GetGroup()->GetID(), mercOwner->CharacterID(), true); + database.RefreshGroupFromDB(mercOwner); + // Update members that are out of zone + GetGroup()->SendGroupJoinOOZ(this); + + if(MERC_DEBUG > 0) + mercOwner->Message(7, "Mercenary Debug: Mercenary joined existing group."); + } + else + { + Suspend(); + + if(MERC_DEBUG > 0) + mercOwner->Message(7, "Mercenary Debug: Mercenary failed to join the group - Suspending"); + } + } + + return true; +} + +bool Merc::AddMercToGroup(Merc* merc, Group* group) { + bool Result = false; + + if(merc && group) { + // Remove merc from current group if it's not the destination group + if(merc->HasGroup()) + { + if(merc->GetGroup() == group && merc->GetMercOwner()) + { + // Merc is already in the destination group + merc->SetFollowID(merc->GetMercOwner()->GetID()); + return true; + } + merc->RemoveMercFromGroup(merc, merc->GetGroup()); + } + //Try and add the member, followed by checking if the merc owner exists. + if(group->AddMember(merc) && merc->GetMercOwner()) + { + merc->SetFollowID(merc->GetMercOwner()->GetID()); + Result = true; + } + else + { + //Suspend it if the member is not added or the merc's owner is not valid. + merc->Suspend(); + } + } + + return Result; +} + +void Client::InitializeMercInfo() { + + for(int i=0; i 0) + Message(7, "Mercenary Debug: GetMerc 0."); + return (nullptr); + } + + Merc* tmp = entity_list.GetMercByID(GetMercID()); + if(tmp == nullptr) + { + SetMercID(0); + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: GetMerc No Merc."); + return (nullptr); + } + + if(tmp->GetOwnerID() != GetID()) + { + SetMercID(0); + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: GetMerc Owner Mismatch."); + return (nullptr); + } +<<<<<<< HEAD + + if (MERC_DEBUG > 0) + //Message(7, "Mercenary Debug: GetMerc Success."); +======= +>>>>>>> master + + return (tmp); +} + +uint8 Client::GetNumMercs() { + + uint8 numMercs = 0; + + for(int i=0; i 0) + Message(7, "Mercenary Debug: GetNumMercs %i.", numMercs); + + return numMercs; +} + +void Merc::SetMercData( uint32 template_id ) { + + MercTemplate* merc_template = zone->GetMercTemplate(template_id); + SetMercTemplateID( merc_template->MercTemplateID ); + SetMercType( merc_template->MercType ); + SetMercSubType( merc_template->MercSubType ); + SetProficiencyID( merc_template->ProficiencyID ); + SetTierID( merc_template->TierID ); + SetCostFormula( merc_template->CostFormula ); + SetMercNameType( merc_template->MercNameType ); + +} + +MercTemplate* Zone::GetMercTemplate( uint32 template_id ) { + return &merc_templates[template_id]; +} + +void Client::SetMerc(Merc* newmerc) { + + Merc* oldmerc = GetMerc(); + if (oldmerc) + { + oldmerc->SetOwnerID(0); + } + + if (!newmerc) + { + SetMercID(0); + GetMercInfo().mercid = 0; + GetMercInfo().MercTemplateID = 0; + GetMercInfo().myTemplate = nullptr; + GetMercInfo().IsSuspended = false; + GetMercInfo().SuspendedTime = 0; + GetMercInfo().Gender = 0; + GetMercInfo().State = 0; + memset(GetMercInfo().merc_name, 0, 64); + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: SetMerc No Merc."); + } + else + { + SetMercID(newmerc->GetID()); + //Client* oldowner = entity_list.GetClientByID(newmerc->GetOwnerID()); + newmerc->SetOwnerID(this->GetID()); + newmerc->SetMercCharacterID(this->CharacterID()); + newmerc->SetClientVersion((uint8)this->GetClientVersion()); + GetMercInfo().mercid = newmerc->GetMercID(); + GetMercInfo().MercTemplateID = newmerc->GetMercTemplateID(); + GetMercInfo().myTemplate = zone->GetMercTemplate(GetMercInfo().MercTemplateID); + GetMercInfo().IsSuspended = newmerc->IsSuspended(); + GetMercInfo().SuspendedTime = 0; + GetMercInfo().Gender = newmerc->GetGender(); + GetMercInfo().State = newmerc->IsSuspended() ? MERC_STATE_SUSPENDED : MERC_STATE_NORMAL; + snprintf(GetMercInfo().merc_name, 64, "%s", newmerc->GetName()); + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: SetMerc New Merc."); + } +} + +void Client::UpdateMercLevel() { + Merc* merc = GetMerc(); + if (merc) + { + merc->UpdateMercStats(this); + } +} + +void Client::SendMercMerchantResponsePacket(int32 response_type) { + // This response packet brings up the Mercenary Manager window + if(GetClientVersion() >= EQClientSoD) + { + EQApplicationPacket *outapp = new EQApplicationPacket(OP_MercenaryHire, sizeof(MercenaryMerchantResponse_Struct)); + MercenaryMerchantResponse_Struct* mmr = (MercenaryMerchantResponse_Struct*)outapp->pBuffer; + mmr->ResponseType = response_type; // send specified response type + FastQueuePacket(&outapp); + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: Sent SendMercMerchantResponsePacket %i.", response_type); + } +} + +void Client::SendMercenaryUnknownPacket(uint8 type) { + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_MercenaryUnknown1, 1); + outapp->WriteUInt8(type); + FastQueuePacket(&outapp); + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: Sent SendMercenaryUnknownPacket %i.", type); + +} + +void Client::SendMercenaryUnsuspendPacket(uint8 type) { + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_MercenaryUnsuspendResponse, 1); + outapp->WriteUInt8(type); + FastQueuePacket(&outapp); + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: Sent SendMercenaryUnsuspendPacket %i.", type); + +} + +void Client::SendMercSuspendResponsePacket(uint32 suspended_time) { + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_MercenarySuspendResponse, sizeof(SuspendMercenaryResponse_Struct)); + SuspendMercenaryResponse_Struct* smr = (SuspendMercenaryResponse_Struct*)outapp->pBuffer; + smr->SuspendTime = suspended_time; // Seen 0 (not suspended) or c9 c2 64 4f (suspended on Sat Mar 17 11:58:49 2012) - Unix Timestamp + FastQueuePacket(&outapp); + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: Sent SendMercSuspendResponsePacket %i.", suspended_time); + +} + +void Client::SendMercTimerPacket(int32 entity_id, int32 merc_state, int32 suspended_time, int32 update_interval, int32 unk01) { + + // Send Mercenary Status/Timer packet + EQApplicationPacket *outapp = new EQApplicationPacket(OP_MercenaryTimer, sizeof(MercenaryStatus_Struct)); + MercenaryStatus_Struct* mss = (MercenaryStatus_Struct*)outapp->pBuffer; + mss->MercEntityID = entity_id; // Seen 0 (no merc spawned) or unknown value when merc is spawned + mss->MercState = merc_state; // Seen 5 (normal) or 1 (suspended) + mss->SuspendedTime = suspended_time; // Seen 0 for not suspended or Unix Timestamp for suspended merc + mss->UpdateInterval = update_interval; // Seen 900000 - 15 minutes in ms + mss->MercUnk01 = unk01; // Seen 180000 - 3 minutes in ms - Used for the unsuspend button refresh timer + FastQueuePacket(&outapp); + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: Sent SendMercTimerPacket %i, %i, %i, %i, %i.", entity_id, merc_state, suspended_time, update_interval, unk01); + +} + +void Client::SendMercAssignPacket(uint32 entityID, uint32 unk01, uint32 unk02) { + EQApplicationPacket *outapp = new EQApplicationPacket(OP_MercenaryAssign, sizeof(MercenaryAssign_Struct)); + MercenaryAssign_Struct* mas = (MercenaryAssign_Struct*)outapp->pBuffer; + mas->MercEntityID = entityID; + mas->MercUnk01 = unk01; + mas->MercUnk02 = unk02; + FastQueuePacket(&outapp); + if (MERC_DEBUG > 0) + Message(7, "Mercenary Debug: Sent SendMercAssignPacket %i, %i, %i.", entityID, unk01, unk02); +} + +void NPC::LoadMercTypes() { + + std::string query = StringFormat("SELECT DISTINCT MTyp.dbstring, MTyp.clientversion " + "FROM merc_merchant_entries MME, merc_merchant_template_entries MMTE, " + "merc_types MTyp, merc_templates MTem " + "WHERE MME.merchant_id = %i " + "AND MME.merc_merchant_template_id = MMTE.merc_merchant_template_id " + "AND MMTE.merc_template_id = MTem.merc_template_id " + "AND MTem.merc_type_id = MTyp.merc_type_id;", GetNPCTypeID()); + auto results = database.QueryDatabase(query); + if (!results.Success()) + { + LogFile->write(EQEMuLog::Error, "Error in NPC::LoadMercTypes()"); + return; + } + + for (auto row = results.begin(); row != results.end(); ++row) + { + MercType tempMercType; + + tempMercType.Type = atoi(row[0]); + tempMercType.ClientVersion = atoi(row[1]); + + mercTypeList.push_back(tempMercType); + } + +} + +void NPC::LoadMercs() { + + std::string query = StringFormat("SELECT DISTINCT MTem.merc_template_id, MTyp.dbstring AS merc_type_id, " + "MTem.dbstring AS merc_subtype_id, 0 AS CostFormula, " + "CASE WHEN MTem.clientversion > MTyp.clientversion " + "THEN MTem.clientversion " + "ELSE MTyp.clientversion END AS clientversion, MTem.merc_npc_type_id " + "FROM merc_merchant_entries MME, merc_merchant_template_entries MMTE, " + "merc_types MTyp, merc_templates MTem " + "WHERE MME.merchant_id = %i AND " + "MME.merc_merchant_template_id = MMTE.merc_merchant_template_id " + "AND MMTE.merc_template_id = MTem.merc_template_id " + "AND MTem.merc_type_id = MTyp.merc_type_id;", GetNPCTypeID()); + auto results = database.QueryDatabase(query); + + if (!results.Success()) + { + LogFile->write(EQEMuLog::Error, "Error in NPC::LoadMercTypes()"); + return; + } + + for (auto row = results.begin(); row != results.end(); ++row) + { + MercData tempMerc; + + tempMerc.MercTemplateID = atoi(row[0]); + tempMerc.MercType = atoi(row[1]); + tempMerc.MercSubType = atoi(row[2]); + tempMerc.CostFormula = atoi(row[3]); + tempMerc.ClientVersion = atoi(row[4]); + tempMerc.NPCID = atoi(row[5]); + + mercDataList.push_back(tempMerc); + } + +} + +int NPC::GetNumMercTypes(uint32 clientVersion) { + + int count = 0; + std::list mercTypeList = GetMercTypesList(); + + for(std::list::iterator mercTypeListItr = mercTypeList.begin(); mercTypeListItr != mercTypeList.end(); ++mercTypeListItr) { + if(mercTypeListItr->ClientVersion <= clientVersion) + count++; + } + + return count; +} + +int NPC::GetNumMercs(uint32 clientVersion) { + + int count = 0; + std::list mercDataList = GetMercsList(); + + for(std::list::iterator mercListItr = mercDataList.begin(); mercListItr != mercDataList.end(); ++mercListItr) { + if(mercListItr->ClientVersion <= clientVersion) + count++; + } + + return count; +} + +std::list NPC::GetMercTypesList(uint32 clientVersion) { + + std::list result; + + if(GetNumMercTypes() > 0) + { + for(std::list::iterator mercTypeListItr = mercTypeList.begin(); mercTypeListItr != mercTypeList.end(); ++mercTypeListItr) + { + if(mercTypeListItr->ClientVersion <= clientVersion) + { + MercType mercType; + mercType.Type = mercTypeListItr->Type; + mercType.ClientVersion = mercTypeListItr->ClientVersion; + result.push_back(mercType); + } + } + } + + return result; +} + +std::list NPC::GetMercsList(uint32 clientVersion) { + + std::list result; + + if(GetNumMercs() > 0) + { + for(std::list::iterator mercListItr = mercDataList.begin(); mercListItr != mercDataList.end(); ++mercListItr) + { + if(mercListItr->ClientVersion <= clientVersion) + { + MercTemplate *merc_template = zone->GetMercTemplate(mercListItr->MercTemplateID); + + if(merc_template) + { + MercData mercData; + mercData.MercTemplateID = mercListItr->MercTemplateID; + mercData.MercType = merc_template->MercType; + mercData.MercSubType = merc_template->MercSubType; + mercData.CostFormula = merc_template->CostFormula; + mercData.ClientVersion = merc_template->ClientVersion; + mercData.NPCID = merc_template->MercNPCID; + result.push_back(mercData); + } + } + } + } + + return result; +} + +uint32 Merc::CalcPurchaseCost(uint32 templateID , uint8 level, uint8 currency_type) { + + uint32 cost = 0; + + MercTemplate *mercData = zone->GetMercTemplate(templateID); + + if(mercData) + { + //calculate cost in coin - cost in gold + if(currency_type == 0) + { + int levels_above_cutoff; + switch (mercData->CostFormula) + { + case 0: + levels_above_cutoff = level > 10 ? (level - 10) : 0; + cost = levels_above_cutoff * 300; + cost += level >= 10 ? 100 : 0; + cost /= 100; + break; + default: + break; + } + } + else if(currency_type == 19) + { + // cost in Bayle Marks + cost = 1; + } + } + + return cost; +} + +uint32 Merc::CalcUpkeepCost(uint32 templateID , uint8 level, uint8 currency_type) { + + uint32 cost = 0; + + MercTemplate *mercData = zone->GetMercTemplate(templateID); + + if(mercData) + { + //calculate cost in coin - cost in gold + if(currency_type == 0) + { + int levels_above_cutoff; + switch (mercData->CostFormula) + { + case 0: + levels_above_cutoff = level > 10 ? (level - 10) : 0; + cost = levels_above_cutoff * 300; + cost += level >= 10 ? 100 : 0; + cost /= 100; + break; + default: + break; + } + } + else if(currency_type == 19) + { + // cost in Bayle Marks + cost = 1; + } + } + + return cost; +} diff --git a/zone/merc.h b/zone/merc.h index eca59ba24..fdcdbec49 100644 --- a/zone/merc.h +++ b/zone/merc.h @@ -332,9 +332,7 @@ private: int GroupLeadershipAAHealthRegeneration(); int GroupLeadershipAAOffenseEnhancement(); - void GetMercSize(); - void GenerateAppearance(); - + float GetDefaultSize(); bool LoadMercSpells(); bool CheckStance(int16 stance); diff --git a/zone/mob.cpp b/zone/mob.cpp index 18616323a..d8f442577 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -15,17 +15,17 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "../common/debug.h" -#include "masterentity.h" + #include "../common/spdat.h" -#include "string_ids.h" -#include "worldserver.h" -#include "quest_parser_collection.h" #include "../common/string_util.h" -#include -#include +#include "quest_parser_collection.h" +#include "string_ids.h" +#include "worldserver.h" + #include +#include +#include extern EntityList entity_list; @@ -260,13 +260,24 @@ Mob::Mob(const char* in_name, casting_spell_inventory_slot = 0; target = 0; - for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_spell_id[i] = 0; } - for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_target_id[i] = 0; } - for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_increment[i] = 0; } - for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_x[i] = 0; } - for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_y[i] = 0; } - for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_z[i] = 0; } - projectile_timer.Disable(); + ActiveProjectileATK = false; + for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) + { + ProjectileAtk[i].increment = 0; + ProjectileAtk[i].hit_increment = 0; + ProjectileAtk[i].target_id = 0; + ProjectileAtk[i].wpn_dmg = 0; + ProjectileAtk[i].origin_x = 0.0f; + ProjectileAtk[i].origin_y = 0.0f; + ProjectileAtk[i].origin_z = 0.0f; + ProjectileAtk[i].tlast_x = 0.0f; + ProjectileAtk[i].tlast_y = 0.0f; + ProjectileAtk[i].ranged_id = 0; + ProjectileAtk[i].ammo_id = 0; + ProjectileAtk[i].ammo_slot = 0; + ProjectileAtk[i].skill = 0; + ProjectileAtk[i].speed_mod = 0.0f; + } memset(&itembonuses, 0, sizeof(StatBonuses)); memset(&spellbonuses, 0, sizeof(StatBonuses)); @@ -702,7 +713,8 @@ void Mob::CreateSpawnPacket(EQApplicationPacket* app, Mob* ForWho) { NewSpawn_Struct* ns = (NewSpawn_Struct*)app->pBuffer; FillSpawnStruct(ns, ForWho); - if(strlen(ns->spawn.lastName) == 0) { + if(strlen(ns->spawn.lastName) == 0) + { switch(ns->spawn.class_) { case TRIBUTE_MASTER: @@ -780,70 +792,78 @@ void Mob::CreateSpawnPacket(EQApplicationPacket* app, NewSpawn_Struct* ns) { // Custom packet data NewSpawn_Struct* ns2 = (NewSpawn_Struct*)app->pBuffer; strcpy(ns2->spawn.name, ns->spawn.name); - switch(ns->spawn.class_) - { - case TRIBUTE_MASTER: - strcpy(ns2->spawn.lastName, "Tribute Master"); - break; - case ADVENTURERECRUITER: - strcpy(ns2->spawn.lastName, "Adventure Recruiter"); - break; - case BANKER: - strcpy(ns2->spawn.lastName, "Banker"); - break; - case ADVENTUREMERCHANT: - strcpy(ns->spawn.lastName,"Adventure Merchant"); - break; - case WARRIORGM: - strcpy(ns2->spawn.lastName, "GM Warrior"); - break; - case PALADINGM: - strcpy(ns2->spawn.lastName, "GM Paladin"); - break; - case RANGERGM: - strcpy(ns2->spawn.lastName, "GM Ranger"); - break; - case SHADOWKNIGHTGM: - strcpy(ns2->spawn.lastName, "GM Shadowknight"); - break; - case DRUIDGM: - strcpy(ns2->spawn.lastName, "GM Druid"); - break; - case BARDGM: - strcpy(ns2->spawn.lastName, "GM Bard"); - break; - case ROGUEGM: - strcpy(ns2->spawn.lastName, "GM Rogue"); - break; - case SHAMANGM: - strcpy(ns2->spawn.lastName, "GM Shaman"); - break; - case NECROMANCERGM: - strcpy(ns2->spawn.lastName, "GM Necromancer"); - break; - case WIZARDGM: - strcpy(ns2->spawn.lastName, "GM Wizard"); - break; - case MAGICIANGM: - strcpy(ns2->spawn.lastName, "GM Magician"); - break; - case ENCHANTERGM: - strcpy(ns2->spawn.lastName, "GM Enchanter"); - break; - case BEASTLORDGM: - strcpy(ns2->spawn.lastName, "GM Beastlord"); - break; - case BERSERKERGM: - strcpy(ns2->spawn.lastName, "GM Berserker"); - break; - case MERCERNARY_MASTER: - strcpy(ns->spawn.lastName, "Mercenary Recruiter"); - break; - default: - strcpy(ns2->spawn.lastName, ns->spawn.lastName); - break; - } + // Set default Last Names for certain Classes if not defined + if (strlen(ns->spawn.lastName) == 0) + { + switch (ns->spawn.class_) + { + case TRIBUTE_MASTER: + strcpy(ns2->spawn.lastName, "Tribute Master"); + break; + case ADVENTURERECRUITER: + strcpy(ns2->spawn.lastName, "Adventure Recruiter"); + break; + case BANKER: + strcpy(ns2->spawn.lastName, "Banker"); + break; + case ADVENTUREMERCHANT: + strcpy(ns2->spawn.lastName, "Adventure Merchant"); + break; + case WARRIORGM: + strcpy(ns2->spawn.lastName, "GM Warrior"); + break; + case PALADINGM: + strcpy(ns2->spawn.lastName, "GM Paladin"); + break; + case RANGERGM: + strcpy(ns2->spawn.lastName, "GM Ranger"); + break; + case SHADOWKNIGHTGM: + strcpy(ns2->spawn.lastName, "GM Shadowknight"); + break; + case DRUIDGM: + strcpy(ns2->spawn.lastName, "GM Druid"); + break; + case BARDGM: + strcpy(ns2->spawn.lastName, "GM Bard"); + break; + case ROGUEGM: + strcpy(ns2->spawn.lastName, "GM Rogue"); + break; + case SHAMANGM: + strcpy(ns2->spawn.lastName, "GM Shaman"); + break; + case NECROMANCERGM: + strcpy(ns2->spawn.lastName, "GM Necromancer"); + break; + case WIZARDGM: + strcpy(ns2->spawn.lastName, "GM Wizard"); + break; + case MAGICIANGM: + strcpy(ns2->spawn.lastName, "GM Magician"); + break; + case ENCHANTERGM: + strcpy(ns2->spawn.lastName, "GM Enchanter"); + break; + case BEASTLORDGM: + strcpy(ns2->spawn.lastName, "GM Beastlord"); + break; + case BERSERKERGM: + strcpy(ns2->spawn.lastName, "GM Berserker"); + break; + case MERCERNARY_MASTER: + strcpy(ns2->spawn.lastName, "Mercenary liaison"); + break; + default: + strcpy(ns2->spawn.lastName, ns->spawn.lastName); + break; + } + } + else + { + strcpy(ns2->spawn.lastName, ns->spawn.lastName); + } memset(&app->pBuffer[sizeof(Spawn_Struct)-7], 0xFF, 7); } @@ -904,7 +924,7 @@ void Mob::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) } ns->spawn.guildrank = 0xFF; - ns->spawn.size = size; + ns->spawn.size = size; ns->spawn.bodytype = bodytype; // The 'flymode' settings have the following effect: // 0 - Mobs in water sink like a stone to the bottom @@ -1229,7 +1249,7 @@ void Mob::ShowStats(Client* client) } else if (IsCorpse()) { if (IsPlayerCorpse()) { - client->Message(0, " CharID: %i PlayerCorpse: %i", CastToCorpse()->GetCharID(), CastToCorpse()->GetDBID()); + client->Message(0, " CharID: %i PlayerCorpse: %i", CastToCorpse()->GetCharID(), CastToCorpse()->GetCorpseDBID()); } else { client->Message(0, " NPCCorpse", GetID()); @@ -1350,147 +1370,150 @@ void Mob::SendIllusionPacket(uint16 in_race, uint8 in_gender, uint8 in_texture, uint16 BaseRace = GetBaseRace(); - if (in_race == 0) { - this->race = BaseRace; + if (in_race == 0) + { + race = BaseRace; if (in_gender == 0xFF) - this->gender = GetBaseGender(); - else - this->gender = in_gender; - } - else { - this->race = in_race; - if (in_gender == 0xFF) { - uint8 tmp = Mob::GetDefaultGender(this->race, gender); - if (tmp == 2) - gender = 2; - else if (gender == 2 && GetBaseGender() == 2) - gender = tmp; - else if (gender == 2) - gender = GetBaseGender(); - } + gender = GetBaseGender(); else gender = in_gender; } - if (in_texture == 0xFF) { - if (in_race <= 12 || in_race == 128 || in_race == 130 || in_race == 330 || in_race == 522) - this->texture = 0xFF; - else - this->texture = GetTexture(); - } else - this->texture = in_texture; + { + race = in_race; + if (in_gender == 0xFF) + gender = GetDefaultGender(race, gender); + else + gender = in_gender; + } - if (in_helmtexture == 0xFF) { - if (in_race <= 12 || in_race == 128 || in_race == 130 || in_race == 330 || in_race == 522) - this->helmtexture = 0xFF; - else if (in_texture != 0xFF) - this->helmtexture = in_texture; + if (in_texture == 0xFF) + { + if (IsPlayerRace(in_race)) + texture = 0xFF; else - this->helmtexture = GetHelmTexture(); + texture = GetTexture(); } else - this->helmtexture = in_helmtexture; + { + texture = in_texture; + } + + if (in_helmtexture == 0xFF) + { + if (IsPlayerRace(in_race)) + helmtexture = 0xFF; + else if (in_texture != 0xFF) + helmtexture = in_texture; + else + helmtexture = GetHelmTexture(); + } + else + { + helmtexture = in_helmtexture; + } if (in_haircolor == 0xFF) - this->haircolor = GetHairColor(); + haircolor = GetHairColor(); else - this->haircolor = in_haircolor; + haircolor = in_haircolor; if (in_beardcolor == 0xFF) - this->beardcolor = GetBeardColor(); + beardcolor = GetBeardColor(); else - this->beardcolor = in_beardcolor; + beardcolor = in_beardcolor; if (in_eyecolor1 == 0xFF) - this->eyecolor1 = GetEyeColor1(); + eyecolor1 = GetEyeColor1(); else - this->eyecolor1 = in_eyecolor1; + eyecolor1 = in_eyecolor1; if (in_eyecolor2 == 0xFF) - this->eyecolor2 = GetEyeColor2(); + eyecolor2 = GetEyeColor2(); else - this->eyecolor2 = in_eyecolor2; + eyecolor2 = in_eyecolor2; if (in_hairstyle == 0xFF) - this->hairstyle = GetHairStyle(); + hairstyle = GetHairStyle(); else - this->hairstyle = in_hairstyle; + hairstyle = in_hairstyle; if (in_luclinface == 0xFF) - this->luclinface = GetLuclinFace(); + luclinface = GetLuclinFace(); else - this->luclinface = in_luclinface; + luclinface = in_luclinface; if (in_beard == 0xFF) - this->beard = GetBeard(); + beard = GetBeard(); else - this->beard = in_beard; + beard = in_beard; - this->aa_title = 0xFF; + aa_title = in_aa_title; if (in_drakkin_heritage == 0xFFFFFFFF) - this->drakkin_heritage = GetDrakkinHeritage(); + drakkin_heritage = GetDrakkinHeritage(); else - this->drakkin_heritage = in_drakkin_heritage; + drakkin_heritage = in_drakkin_heritage; if (in_drakkin_tattoo == 0xFFFFFFFF) - this->drakkin_tattoo = GetDrakkinTattoo(); + drakkin_tattoo = GetDrakkinTattoo(); else - this->drakkin_tattoo = in_drakkin_tattoo; + drakkin_tattoo = in_drakkin_tattoo; if (in_drakkin_details == 0xFFFFFFFF) - this->drakkin_details = GetDrakkinDetails(); + drakkin_details = GetDrakkinDetails(); else - this->drakkin_details = in_drakkin_details; + drakkin_details = in_drakkin_details; if (in_size <= 0.0f) - this->size = GetSize(); + size = GetSize(); else - this->size = in_size; + size = in_size; - // Forces the feature information to be pulled from the Player Profile - if (this->IsClient() && in_race == 0) { - this->race = CastToClient()->GetBaseRace(); - this->gender = CastToClient()->GetBaseGender(); - this->texture = 0xFF; - this->helmtexture = 0xFF; - this->haircolor = CastToClient()->GetBaseHairColor(); - this->beardcolor = CastToClient()->GetBaseBeardColor(); - this->eyecolor1 = CastToClient()->GetBaseEyeColor(); - this->eyecolor2 = CastToClient()->GetBaseEyeColor(); - this->hairstyle = CastToClient()->GetBaseHairStyle(); - this->luclinface = CastToClient()->GetBaseFace(); - this->beard = CastToClient()->GetBaseBeard(); - this->aa_title = 0xFF; - this->drakkin_heritage = CastToClient()->GetBaseHeritage(); - this->drakkin_tattoo = CastToClient()->GetBaseTattoo(); - this->drakkin_details = CastToClient()->GetBaseDetails(); + // Reset features to Base from the Player Profile + if (IsClient() && in_race == 0) + { + race = CastToClient()->GetBaseRace(); + gender = CastToClient()->GetBaseGender(); + texture = 0xFF; + helmtexture = 0xFF; + haircolor = CastToClient()->GetBaseHairColor(); + beardcolor = CastToClient()->GetBaseBeardColor(); + eyecolor1 = CastToClient()->GetBaseEyeColor(); + eyecolor2 = CastToClient()->GetBaseEyeColor(); + hairstyle = CastToClient()->GetBaseHairStyle(); + luclinface = CastToClient()->GetBaseFace(); + beard = CastToClient()->GetBaseBeard(); + aa_title = 0xFF; + drakkin_heritage = CastToClient()->GetBaseHeritage(); + drakkin_tattoo = CastToClient()->GetBaseTattoo(); + drakkin_details = CastToClient()->GetBaseDetails(); switch(race){ case OGRE: - this->size = 9; + size = 9; break; case TROLL: - this->size = 8; + size = 8; break; case VAHSHIR: case BARBARIAN: - this->size = 7; + size = 7; break; case HALF_ELF: case WOOD_ELF: case DARK_ELF: case FROGLOK: - this->size = 5; + size = 5; break; case DWARF: - this->size = 4; + size = 4; break; case HALFLING: case GNOME: - this->size = 3; + size = 3; break; default: - this->size = 6; + size = 6; break; } } @@ -1498,39 +1521,250 @@ void Mob::SendIllusionPacket(uint16 in_race, uint8 in_gender, uint8 in_texture, EQApplicationPacket* outapp = new EQApplicationPacket(OP_Illusion, sizeof(Illusion_Struct)); memset(outapp->pBuffer, 0, sizeof(outapp->pBuffer)); Illusion_Struct* is = (Illusion_Struct*) outapp->pBuffer; - is->spawnid = this->GetID(); + is->spawnid = GetID(); strcpy(is->charname, GetCleanName()); - is->race = this->race; - is->gender = this->gender; - is->texture = this->texture; - is->helmtexture = this->helmtexture; - is->haircolor = this->haircolor; - is->beardcolor = this->beardcolor; - is->beard = this->beard; - is->eyecolor1 = this->eyecolor1; - is->eyecolor2 = this->eyecolor2; - is->hairstyle = this->hairstyle; - is->face = this->luclinface; - //is->aa_title = this->aa_title; - is->drakkin_heritage = this->drakkin_heritage; - is->drakkin_tattoo = this->drakkin_tattoo; - is->drakkin_details = this->drakkin_details; - is->size = this->size; + is->race = race; + is->gender = gender; + is->texture = texture; + is->helmtexture = helmtexture; + is->haircolor = haircolor; + is->beardcolor = beardcolor; + is->beard = beard; + is->eyecolor1 = eyecolor1; + is->eyecolor2 = eyecolor2; + is->hairstyle = hairstyle; + is->face = luclinface; + is->drakkin_heritage = drakkin_heritage; + is->drakkin_tattoo = drakkin_tattoo; + is->drakkin_details = drakkin_details; + is->size = size; entity_list.QueueClients(this, outapp); safe_delete(outapp); mlog(CLIENT__SPELLS, "Illusion: Race = %i, Gender = %i, Texture = %i, HelmTexture = %i, HairColor = %i, BeardColor = %i, EyeColor1 = %i, EyeColor2 = %i, HairStyle = %i, Face = %i, DrakkinHeritage = %i, DrakkinTattoo = %i, DrakkinDetails = %i, Size = %f", - this->race, this->gender, this->texture, this->helmtexture, this->haircolor, this->beardcolor, this->eyecolor1, this->eyecolor2, this->hairstyle, this->luclinface, this->drakkin_heritage, this->drakkin_tattoo, this->drakkin_details, this->size); + race, gender, texture, helmtexture, haircolor, beardcolor, eyecolor1, eyecolor2, hairstyle, luclinface, drakkin_heritage, drakkin_tattoo, drakkin_details, size); } +bool Mob::RandomizeFeatures(bool send_illusion, bool set_variables) +{ + if (IsPlayerRace(GetRace())) + { + uint8 Gender = GetGender(); + uint8 Texture = 0xFF; + uint8 HelmTexture = 0xFF; + uint8 HairColor = 0xFF; + uint8 BeardColor = 0xFF; + uint8 EyeColor1 = 0xFF; + uint8 EyeColor2 = 0xFF; + uint8 HairStyle = 0xFF; + uint8 LuclinFace = 0xFF; + uint8 Beard = 0xFF; + uint32 DrakkinHeritage = 0xFFFFFFFF; + uint32 DrakkinTattoo = 0xFFFFFFFF; + uint32 DrakkinDetails = 0xFFFFFFFF; + + // Set some common feature settings + EyeColor1 = zone->random.Int(0, 9); + EyeColor2 = zone->random.Int(0, 9); + LuclinFace = zone->random.Int(0, 7); + + // Adjust all settings based on the min and max for each feature of each race and gender + switch (GetRace()) + { + case 1: // Human + HairColor = zone->random.Int(0, 19); + if (Gender == 0) { + BeardColor = HairColor; + HairStyle = zone->random.Int(0, 3); + Beard = zone->random.Int(0, 5); + } + if (Gender == 1) { + HairStyle = zone->random.Int(0, 2); + } + break; + case 2: // Barbarian + HairColor = zone->random.Int(0, 19); + LuclinFace = zone->random.Int(0, 87); + if (Gender == 0) { + BeardColor = HairColor; + HairStyle = zone->random.Int(0, 3); + Beard = zone->random.Int(0, 5); + } + if (Gender == 1) { + HairStyle = zone->random.Int(0, 2); + } + break; + case 3: // Erudite + if (Gender == 0) { + BeardColor = zone->random.Int(0, 19); + Beard = zone->random.Int(0, 5); + LuclinFace = zone->random.Int(0, 57); + } + if (Gender == 1) { + LuclinFace = zone->random.Int(0, 87); + } + break; + case 4: // WoodElf + HairColor = zone->random.Int(0, 19); + if (Gender == 0) { + HairStyle = zone->random.Int(0, 3); + } + if (Gender == 1) { + HairStyle = zone->random.Int(0, 2); + } + break; + case 5: // HighElf + HairColor = zone->random.Int(0, 14); + if (Gender == 0) { + HairStyle = zone->random.Int(0, 3); + LuclinFace = zone->random.Int(0, 37); + BeardColor = HairColor; + } + if (Gender == 1) { + HairStyle = zone->random.Int(0, 2); + } + break; + case 6: // DarkElf + HairColor = zone->random.Int(13, 18); + if (Gender == 0) { + HairStyle = zone->random.Int(0, 3); + LuclinFace = zone->random.Int(0, 37); + BeardColor = HairColor; + } + if (Gender == 1) { + HairStyle = zone->random.Int(0, 2); + } + break; + case 7: // HalfElf + HairColor = zone->random.Int(0, 19); + if (Gender == 0) { + HairStyle = zone->random.Int(0, 3); + LuclinFace = zone->random.Int(0, 37); + BeardColor = HairColor; + } + if (Gender == 1) { + HairStyle = zone->random.Int(0, 2); + } + break; + case 8: // Dwarf + HairColor = zone->random.Int(0, 19); + BeardColor = HairColor; + if (Gender == 0) { + HairStyle = zone->random.Int(0, 3); + Beard = zone->random.Int(0, 5); + } + if (Gender == 1) { + HairStyle = zone->random.Int(0, 2); + LuclinFace = zone->random.Int(0, 17); + } + break; + case 9: // Troll + EyeColor1 = zone->random.Int(0, 10); + EyeColor2 = zone->random.Int(0, 10); + if (Gender == 1) { + HairStyle = zone->random.Int(0, 3); + HairColor = zone->random.Int(0, 23); + } + break; + case 10: // Ogre + if (Gender == 1) { + HairStyle = zone->random.Int(0, 3); + HairColor = zone->random.Int(0, 23); + } + break; + case 11: // Halfling + HairColor = zone->random.Int(0, 19); + if (Gender == 0) { + BeardColor = HairColor; + HairStyle = zone->random.Int(0, 3); + Beard = zone->random.Int(0, 5); + } + if (Gender == 1) { + HairStyle = zone->random.Int(0, 2); + } + break; + case 12: // Gnome + HairColor = zone->random.Int(0, 24); + if (Gender == 0) { + BeardColor = HairColor; + HairStyle = zone->random.Int(0, 3); + Beard = zone->random.Int(0, 5); + } + if (Gender == 1) { + HairStyle = zone->random.Int(0, 2); + } + break; + case 128: // Iksar + case 130: // VahShir + break; + case 330: // Froglok + LuclinFace = zone->random.Int(0, 9); + case 522: // Drakkin + HairColor = zone->random.Int(0, 3); + BeardColor = HairColor; + EyeColor1 = zone->random.Int(0, 11); + EyeColor2 = zone->random.Int(0, 11); + LuclinFace = zone->random.Int(0, 6); + DrakkinHeritage = zone->random.Int(0, 6); + DrakkinTattoo = zone->random.Int(0, 7); + DrakkinDetails = zone->random.Int(0, 7); + if (Gender == 0) { + Beard = zone->random.Int(0, 12); + HairStyle = zone->random.Int(0, 8); + } + if (Gender == 1) { + Beard = zone->random.Int(0, 3); + HairStyle = zone->random.Int(0, 7); + } + break; + default: + break; + } + + if (set_variables) + { + haircolor = HairColor; + beardcolor = BeardColor; + eyecolor1 = EyeColor1; + eyecolor2 = EyeColor2; + hairstyle = HairStyle; + luclinface = LuclinFace; + beard = Beard; + drakkin_heritage = DrakkinHeritage; + drakkin_tattoo = DrakkinTattoo; + drakkin_details = DrakkinDetails; + } + + if (send_illusion) + { + SendIllusionPacket(GetRace(), Gender, Texture, HelmTexture, HairColor, BeardColor, + EyeColor1, EyeColor2, HairStyle, LuclinFace, Beard, 0xFF, DrakkinHeritage, + DrakkinTattoo, DrakkinDetails); + } + + return true; + } + return false; +} + + +bool Mob::IsPlayerRace(uint16 in_race) { + + if ((in_race >= HUMAN && in_race <= GNOME) || in_race == IKSAR || in_race == VAHSHIR || in_race == FROGLOK || in_race == DRAKKIN) + { + return true; + } + + return false; +} + + uint8 Mob::GetDefaultGender(uint16 in_race, uint8 in_gender) { //std::cout << "Gender in: " << (int)in_gender << std::endl; // undefined cout [CODEBUG] - if ((in_race > 0 && in_race <= GNOME ) - || in_race == IKSAR || in_race == VAHSHIR || in_race == FROGLOK || in_race == DRAKKIN - || in_race == 15 || in_race == 50 || in_race == 57 || in_race == 70 || in_race == 98 || in_race == 118) { + if (Mob::IsPlayerRace(in_race) || in_race == 15 || in_race == 50 || in_race == 57 || in_race == 70 || in_race == 98 || in_race == 118) { if (in_gender >= 2) { - // Female default for PC Races - return 1; + // Male default for PC Races + return 0; } else return in_gender; @@ -2317,7 +2551,7 @@ uint32 Mob::RandomTimer(int min,int max) { int r = 14000; if(min != 0 && max != 0 && min < max) { - r = MakeRandomInt(min, max); + r = zone->random.Int(min, max); } return r; } @@ -2427,6 +2661,9 @@ int32 Mob::GetEquipmentMaterial(uint8 material_slot) const item = inst->GetOrnamentationAug(ornamentationAugtype)->GetItem(); return atoi(&item->IDFile[2]); } + else if (inst->GetOrnamentationIcon() && inst->GetOrnamentationIDFile()) { + return inst->GetOrnamentationIDFile(); + } else { if (strlen(item->IDFile) > 2) return atoi(&item->IDFile[2]); @@ -2707,7 +2944,7 @@ void Mob::ExecWeaponProc(const ItemInst *inst, uint16 spell_id, Mob *on) { if(IsClient()) twinproc_chance = CastToClient()->GetFocusEffect(focusTwincast, spell_id); - if(twinproc_chance && (MakeRandomInt(0,99) < twinproc_chance)) + if(twinproc_chance && zone->random.Roll(twinproc_chance)) twinproc = true; if (IsBeneficialSpell(spell_id)) { @@ -3024,7 +3261,7 @@ void Mob::TriggerOnCast(uint32 focus_spell, uint32 spell_id, bool aa_trigger) if(IsValidSpell(trigger_spell_id) && GetTarget()){ SpellFinished(trigger_spell_id, GetTarget(),10, 0, -1, spells[trigger_spell_id].ResistDiff); - CheckNumHitsRemaining(NUMHIT_MatchingSpells,0, focus_spell); + CheckNumHitsRemaining(NUMHIT_MatchingSpells,-1, focus_spell); } } } @@ -3049,7 +3286,7 @@ bool Mob::TrySpellTrigger(Mob *target, uint32 spell_id, int effect) { if (spells[spell_id].effectid[i] == SE_SpellTrigger) { - if(MakeRandomInt(0, trig_chance) <= spells[spell_id].base[i]) + if(zone->random.Int(0, trig_chance) <= spells[spell_id].base[i]) { // If we trigger an effect then its over. if (IsValidSpell(spells[spell_id].base2[i])){ @@ -3069,7 +3306,7 @@ bool Mob::TrySpellTrigger(Mob *target, uint32 spell_id, int effect) // if the chances don't add to 100, then each effect gets a chance to fire, chance for no trigger as well. else { - if(MakeRandomInt(0, 100) <= spells[spell_id].base[effect]) + if(zone->random.Int(0, 100) <= spells[spell_id].base[effect]) { if (IsValidSpell(spells[spell_id].base2[effect])){ SpellFinished(spells[spell_id].base2[effect], target, 10, 0, -1, spells[spells[spell_id].base2[effect]].ResistDiff); @@ -3174,7 +3411,7 @@ void Mob::TryTwincast(Mob *caster, Mob *target, uint32 spell_id) if (focus > 0) { - if(MakeRandomInt(0, 100) <= focus) + if(zone->random.Roll(focus)) { Message(MT_Spells,"You twincast %s!",spells[spell_id].name); SpellFinished(spell_id, target, 10, 0, -1, spells[spell_id].ResistDiff); @@ -3193,7 +3430,7 @@ void Mob::TryTwincast(Mob *caster, Mob *target, uint32 spell_id) int32 focus = CalcFocusEffect(focusTwincast, buffs[i].spellid, spell_id); if(focus > 0) { - if(MakeRandomInt(0, 100) <= focus) + if(zone->random.Roll(focus)) { SpellFinished(spell_id, target, 10, 0, -1, spells[spell_id].ResistDiff); } @@ -3361,7 +3598,7 @@ void Mob::TrySympatheticProc(Mob *target, uint32 spell_id) SpellFinished(focus_trigger, target, 10, 0, -1, spells[focus_trigger].ResistDiff); } - CheckNumHitsRemaining(NUMHIT_MatchingSpells, 0, focus_spell); + CheckNumHitsRemaining(NUMHIT_MatchingSpells, -1, focus_spell); } } @@ -3974,7 +4211,7 @@ void Mob::TrySpellOnKill(uint8 level, uint16 spell_id) { if (IsValidSpell(spells[spell_id].base2[i]) && spells[spell_id].max[i] <= level) { - if(MakeRandomInt(0,99) < spells[spell_id].base[i]) + if(zone->random.Roll(spells[spell_id].base[i])) SpellFinished(spells[spell_id].base2[i], this, 10, 0, -1, spells[spells[spell_id].base2[i]].ResistDiff); } } @@ -3989,17 +4226,17 @@ void Mob::TrySpellOnKill(uint8 level, uint16 spell_id) for(int i = 0; i < MAX_SPELL_TRIGGER*3; i+=3) { if(aabonuses.SpellOnKill[i] && IsValidSpell(aabonuses.SpellOnKill[i]) && (level >= aabonuses.SpellOnKill[i + 2])) { - if(MakeRandomInt(0, 99) < static_cast(aabonuses.SpellOnKill[i + 1])) + if(zone->random.Roll(static_cast(aabonuses.SpellOnKill[i + 1]))) SpellFinished(aabonuses.SpellOnKill[i], this, 10, 0, -1, spells[aabonuses.SpellOnKill[i]].ResistDiff); } if(itembonuses.SpellOnKill[i] && IsValidSpell(itembonuses.SpellOnKill[i]) && (level >= itembonuses.SpellOnKill[i + 2])){ - if(MakeRandomInt(0, 99) < static_cast(itembonuses.SpellOnKill[i + 1])) + if(zone->random.Roll(static_cast(itembonuses.SpellOnKill[i + 1]))) SpellFinished(itembonuses.SpellOnKill[i], this, 10, 0, -1, spells[aabonuses.SpellOnKill[i]].ResistDiff); } if(spellbonuses.SpellOnKill[i] && IsValidSpell(spellbonuses.SpellOnKill[i]) && (level >= spellbonuses.SpellOnKill[i + 2])) { - if(MakeRandomInt(0, 99) < static_cast(spellbonuses.SpellOnKill[i + 1])) + if(zone->random.Roll(static_cast(spellbonuses.SpellOnKill[i + 1]))) SpellFinished(spellbonuses.SpellOnKill[i], this, 10, 0, -1, spells[aabonuses.SpellOnKill[i]].ResistDiff); } @@ -4016,19 +4253,19 @@ bool Mob::TrySpellOnDeath() for(int i = 0; i < MAX_SPELL_TRIGGER*2; i+=2) { if(IsClient() && aabonuses.SpellOnDeath[i] && IsValidSpell(aabonuses.SpellOnDeath[i])) { - if(MakeRandomInt(0, 99) < static_cast(aabonuses.SpellOnDeath[i + 1])) { + if(zone->random.Roll(static_cast(aabonuses.SpellOnDeath[i + 1]))) { SpellFinished(aabonuses.SpellOnDeath[i], this, 10, 0, -1, spells[aabonuses.SpellOnDeath[i]].ResistDiff); } } if(itembonuses.SpellOnDeath[i] && IsValidSpell(itembonuses.SpellOnDeath[i])) { - if(MakeRandomInt(0, 99) < static_cast(itembonuses.SpellOnDeath[i + 1])) { + if(zone->random.Roll(static_cast(itembonuses.SpellOnDeath[i + 1]))) { SpellFinished(itembonuses.SpellOnDeath[i], this, 10, 0, -1, spells[itembonuses.SpellOnDeath[i]].ResistDiff); } } if(spellbonuses.SpellOnDeath[i] && IsValidSpell(spellbonuses.SpellOnDeath[i])) { - if(MakeRandomInt(0, 99) < static_cast(spellbonuses.SpellOnDeath[i + 1])) { + if(zone->random.Roll(static_cast(spellbonuses.SpellOnDeath[i + 1]))) { SpellFinished(spellbonuses.SpellOnDeath[i], this, 10, 0, -1, spells[spellbonuses.SpellOnDeath[i]].ResistDiff); } } @@ -4191,55 +4428,12 @@ bool Mob::TryReflectSpell(uint32 spell_id) int chance = itembonuses.reflect_chance + spellbonuses.reflect_chance + aabonuses.reflect_chance; - if(chance && MakeRandomInt(0, 99) < chance) + if(chance && zone->random.Roll(chance)) return true; return false; } -void Mob::SpellProjectileEffect() -{ - bool time_disable = false; - - for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { - - if (projectile_increment[i] == 0){ - continue; - } - - Mob* target = entity_list.GetMobID(projectile_target_id[i]); - - float dist = 0; - - if (target) - dist = target->CalculateDistance(projectile_x[i], projectile_y[i], projectile_z[i]); - - int increment_end = 0; - increment_end = static_cast(dist / 10) - 1; //This pretty accurately determines end time for speed for 1.5 and timer of 250 ms - - if (increment_end <= projectile_increment[i]){ - - if (target && IsValidSpell(projectile_spell_id[i])) - SpellOnTarget(projectile_spell_id[i], target, false, true, spells[projectile_spell_id[i]].ResistDiff, true); - - projectile_spell_id[i] = 0; - projectile_target_id[i] = 0; - projectile_x[i] = 0, projectile_y[i] = 0, projectile_z[i] = 0; - projectile_increment[i] = 0; - time_disable = true; - } - - else { - projectile_increment[i]++; - time_disable = false; - } - } - - if (time_disable) - projectile_timer.Disable(); -} - - void Mob::DoGravityEffect() { Mob *caster = nullptr; diff --git a/zone/mob.h b/zone/mob.h index dd208b615..16f28161c 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -18,7 +18,6 @@ #ifndef MOB_H #define MOB_H -#include "../common/features.h" #include "common.h" #include "entity.h" #include "hate_list.h" @@ -26,14 +25,22 @@ #include "position.h" #include #include -#include char* strn0cpy(char* dest, const char* source, uint32 size); #define MAX_SPECIAL_ATTACK_PARAMS 8 class EGNode; -class MobFearState; +class Client; +class EQApplicationPacket; +class Group; +class ItemInst; +class NPC; +class Raid; +struct Item_Struct; +struct NewSpawn_Struct; +struct PlayerPositionUpdateServer_Struct; + class Mob : public Entity { public: enum CLIENT_CONN_STATUS { CLIENT_CONNECTING, CLIENT_CONNECTED, CLIENT_LINKDEAD, @@ -228,8 +235,7 @@ public: uint16 CastingSpellID() const { return casting_spell_id; } bool DoCastingChecks(); bool TryDispel(uint8 caster_level, uint8 buff_level, int level_modifier); - void SpellProjectileEffect(); - bool TrySpellProjectile(Mob* spell_target, uint16 spell_id); + bool TrySpellProjectile(Mob* spell_target, uint16 spell_id, float speed = 1.5f); void ResourceTap(int32 damage, uint16 spell_id); void TryTriggerThreshHold(int32 damage, int effect_id, Mob* attacker); bool CheckSpellCategory(uint16 spell_id, int category_id, int effect_id); @@ -273,7 +279,7 @@ public: int16 GetBuffSlotFromType(uint16 type); uint16 GetSpellIDFromSlot(uint8 slot); int CountDispellableBuffs(); - void CheckNumHitsRemaining(uint8 type, uint32 buff_slot=0, uint16 spell_id=SPELL_UNKNOWN); + void CheckNumHitsRemaining(uint8 type, int32 buff_slot=-1, uint16 spell_id=SPELL_UNKNOWN); bool HasNumhits() const { return has_numhits; } inline void Numhits(bool val) { has_numhits = val; } bool HasMGB() const { return has_MGB; } @@ -490,6 +496,7 @@ public: //Util static uint32 RandomTimer(int min, int max); static uint8 GetDefaultGender(uint16 in_race, uint8 in_gender = 0xFF); + static bool IsPlayerRace(uint16 in_race); uint16 GetSkillByItemType(int ItemType); uint8 GetItemTypeBySkill(SkillUseTypes skill); virtual void MakePet(uint16 spell_id, const char* pettype, const char *petname = nullptr); @@ -570,6 +577,7 @@ public: uint8 in_hairstyle = 0xFF, uint8 in_luclinface = 0xFF, uint8 in_beard = 0xFF, uint8 in_aa_title = 0xFF, uint32 in_drakkin_heritage = 0xFFFFFFFF, uint32 in_drakkin_tattoo = 0xFFFFFFFF, uint32 in_drakkin_details = 0xFFFFFFFF, float in_size = -1.0f); + bool RandomizeFeatures(bool send_illusion = true, bool set_variables = true); virtual void Stun(int duration); virtual void UnStun(); inline void Silence(bool newval) { silenced = newval; } @@ -721,9 +729,14 @@ public: int32 ReduceAllDamage(int32 damage); virtual void DoSpecialAttackDamage(Mob *who, SkillUseTypes skill, int32 max_damage, int32 min_damage = 1, int32 hate_override = -1, int ReuseTime = 10, bool HitChance=false, bool CanAvoid=true); - virtual void DoThrowingAttackDmg(Mob* other, const ItemInst* RangeWeapon=nullptr, const Item_Struct* item=nullptr, uint16 weapon_damage=0, int16 chance_mod=0,int16 focus=0, int ReuseTime=0); + virtual void DoThrowingAttackDmg(Mob* other, const ItemInst* RangeWeapon=nullptr, const Item_Struct* AmmoItem=nullptr, uint16 weapon_damage=0, int16 chance_mod=0,int16 focus=0, int ReuseTime=0, uint32 range_id=0, int AmmoSlot=0, float speed = 4.0f); virtual void DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillUseTypes skillinuse, int16 chance_mod=0, int16 focus=0, bool CanRiposte=false, int ReuseTime=0); - virtual void DoArcheryAttackDmg(Mob* other, const ItemInst* RangeWeapon=nullptr, const ItemInst* Ammo=nullptr, uint16 weapon_damage=0, int16 chance_mod=0, int16 focus=0, int ReuseTime=0); + virtual void DoArcheryAttackDmg(Mob* other, const ItemInst* RangeWeapon=nullptr, const ItemInst* Ammo=nullptr, uint16 weapon_damage=0, int16 chance_mod=0, int16 focus=0, int ReuseTime=0, uint32 range_id=0, uint32 ammo_id=0, const Item_Struct *AmmoItem=nullptr, int AmmoSlot=0, float speed= 4.0f); + bool TryProjectileAttack(Mob* other, const Item_Struct *item, SkillUseTypes skillInUse, uint16 weapon_dmg, const ItemInst* RangeWeapon, const ItemInst* Ammo, int AmmoSlot, float speed); + void ProjectileAttack(); + inline bool HasProjectileAttack() const { return ActiveProjectileATK; } + inline void SetProjectileAttack(bool value) { ActiveProjectileATK = value; } + float GetRangeDistTargetSizeMod(Mob* other); bool CanDoSpecialAttack(Mob *other); bool Flurry(ExtraAttackOptions *opts); bool Rampage(ExtraAttackOptions *opts); @@ -839,7 +852,7 @@ public: // HP Event inline int GetNextHPEvent() const { return nexthpevent; } void SetNextHPEvent( int hpevent ); - void SendItemAnimation(Mob *to, const Item_Struct *item, SkillUseTypes skillInUse); + void SendItemAnimation(Mob *to, const Item_Struct *item, SkillUseTypes skillInUse, float velocity= 4.0); inline int& GetNextIncHPEvent() { return nextinchpevent; } void SetNextIncHPEvent( int inchpevent ); @@ -1079,11 +1092,8 @@ protected: uint8 bardsong_slot; uint32 bardsong_target_id; - Timer projectile_timer; - uint32 projectile_spell_id[MAX_SPELL_PROJECTILE]; - uint16 projectile_target_id[MAX_SPELL_PROJECTILE]; - uint8 projectile_increment[MAX_SPELL_PROJECTILE]; - float projectile_x[MAX_SPELL_PROJECTILE], projectile_y[MAX_SPELL_PROJECTILE], projectile_z[MAX_SPELL_PROJECTILE]; + bool ActiveProjectileATK; + tProjatk ProjectileAtk[MAX_SPELL_PROJECTILE]; xyz_location m_RewindLocation; diff --git a/zone/mob_ai.cpp b/zone/mob_ai.cpp index c510aa035..5f990dc76 100644 --- a/zone/mob_ai.cpp +++ b/zone/mob_ai.cpp @@ -57,7 +57,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint16 iSpellTypes) { return false; if (iChance < 100) { - if (MakeRandomInt(0, 100) >= iChance) + if (zone->random.Int(0, 100) >= iChance) return false; } @@ -94,7 +94,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint16 iSpellTypes) { dist2 <= spells[AIspells[i].spellid].range*spells[AIspells[i].spellid].range ) && (mana_cost <= GetMana() || GetMana() == GetMaxMana()) - && (AIspells[i].time_cancast + (MakeRandomInt(0, 4) * 1000)) <= Timer::GetCurrentTime() //break up the spelling casting over a period of time. + && (AIspells[i].time_cancast + (zone->random.Int(0, 4) * 1000)) <= Timer::GetCurrentTime() //break up the spelling casting over a period of time. ) { #if MobAI_DEBUG_Spells >= 21 @@ -126,7 +126,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint16 iSpellTypes) { } case SpellType_Root: { Mob *rootee = GetHateRandom(); - if (rootee && !rootee->IsRooted() && MakeRandomInt(0, 99) < 50 + if (rootee && !rootee->IsRooted() && zone->random.Roll(50) && rootee->DontRootMeBefore() < Timer::GetCurrentTime() && rootee->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0 ) { @@ -165,7 +165,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint16 iSpellTypes) { } case SpellType_InCombatBuff: { - if(MakeRandomInt(0, 99) < 50) + if(zone->random.Roll(50)) { AIDoSpellCast(i, tar, mana_cost); return true; @@ -184,7 +184,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint16 iSpellTypes) { case SpellType_Slow: case SpellType_Debuff: { Mob * debuffee = GetHateRandom(); - if (debuffee && manaR >= 10 && MakeRandomInt(0, 99 < 70) && + if (debuffee && manaR >= 10 && zone->random.Roll(70) && debuffee->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0) { if (!checked_los) { if (!CheckLosFN(debuffee)) @@ -198,7 +198,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint16 iSpellTypes) { } case SpellType_Nuke: { if ( - manaR >= 10 && MakeRandomInt(0, 99) < 70 + manaR >= 10 && zone->random.Roll(70) && tar->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0 ) { if(!checked_los) { @@ -212,7 +212,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint16 iSpellTypes) { break; } case SpellType_Dispel: { - if(MakeRandomInt(0, 99) < 15) + if(zone->random.Roll(15)) { if(!checked_los) { if(!CheckLosFN(tar)) @@ -228,7 +228,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint16 iSpellTypes) { break; } case SpellType_Mez: { - if(MakeRandomInt(0, 99) < 20) + if(zone->random.Roll(20)) { Mob * mezTar = nullptr; mezTar = entity_list.GetTargetForMez(this); @@ -244,7 +244,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint16 iSpellTypes) { case SpellType_Charm: { - if(!IsPet() && MakeRandomInt(0, 99) < 20) + if(!IsPet() && zone->random.Roll(20)) { Mob * chrmTar = GetHateRandom(); if(chrmTar && chrmTar->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0) @@ -258,7 +258,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint16 iSpellTypes) { case SpellType_Pet: { //keep mobs from recasting pets when they have them. - if (!IsPet() && !GetPetID() && MakeRandomInt(0, 99) < 25) { + if (!IsPet() && !GetPetID() && zone->random.Roll(25)) { AIDoSpellCast(i, tar, mana_cost); return true; } @@ -266,7 +266,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint16 iSpellTypes) { } case SpellType_Lifetap: { if (GetHPRatio() <= 95 - && MakeRandomInt(0, 99) < 50 + && zone->random.Roll(50) && tar->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0 ) { if(!checked_los) { @@ -282,7 +282,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint16 iSpellTypes) { case SpellType_Snare: { if ( !tar->IsRooted() - && MakeRandomInt(0, 99) < 50 + && zone->random.Roll(50) && tar->DontSnareMeBefore() < Timer::GetCurrentTime() && tar->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0 ) { @@ -300,7 +300,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint16 iSpellTypes) { } case SpellType_DOT: { if ( - MakeRandomInt(0, 99) < 60 + zone->random.Roll(60) && tar->DontDotMeBefore() < Timer::GetCurrentTime() && tar->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0 ) { @@ -369,7 +369,7 @@ bool EntityList::AICheckCloseBeneficialSpells(NPC* caster, uint8 iChance, float return false; if (iChance < 100) { - uint8 tmp = MakeRandomInt(0, 99); + uint8 tmp = zone->random.Int(0, 99); if (tmp >= iChance) return false; } @@ -594,7 +594,6 @@ void Mob::AI_ShutDown() { tic_timer.Disable(); mana_timer.Disable(); spellend_timer.Disable(); - projectile_timer.Disable(); rewind_timer.Disable(); bindwound_timer.Disable(); stunned_timer.Disable(); @@ -685,7 +684,7 @@ void Client::AI_SpellCast() } else { - uint32 idx = MakeRandomInt(0, (valid_spells.size()-1)); + uint32 idx = zone->random.Int(0, (valid_spells.size()-1)); spell_to_cast = valid_spells[idx]; slot_to_use = slots[idx]; } @@ -873,7 +872,7 @@ void Client::AI_Process() if (flurrychance) { - if(MakeRandomInt(0, 100) < flurrychance) + if(zone->random.Roll(flurrychance)) { Message_StringID(MT_NPCFlurry, YOU_FLURRY); Attack(GetTarget(), MainPrimary, false); @@ -890,7 +889,7 @@ void Client::AI_Process() wpn->GetItem()->ItemType == ItemType2HBlunt || wpn->GetItem()->ItemType == ItemType2HPiercing ) { - if(MakeRandomInt(0, 100) < ExtraAttackChanceBonus) + if(zone->random.Roll(ExtraAttackChanceBonus)) { Attack(GetTarget(), MainPrimary, false); } @@ -929,7 +928,7 @@ void Client::AI_Process() int16 DWBonus = spellbonuses.DualWieldChance + itembonuses.DualWieldChance; DualWieldProbability += DualWieldProbability*float(DWBonus)/ 100.0f; - if(MakeRandomFloat(0.0, 1.0) < DualWieldProbability) + if(zone->random.Roll(DualWieldProbability)) { Attack(GetTarget(), MainSecondary); if(CheckDoubleAttack()) @@ -1190,7 +1189,7 @@ void Mob::AI_Process() { //we use this random value in three comparisons with different //thresholds, and if its truely random, then this should work //out reasonably and will save us compute resources. - int32 RandRoll = MakeRandomInt(0, 99); + int32 RandRoll = zone->random.Int(0, 99); if ((CanThisClassDoubleAttack() || GetSpecialAbility(SPECATK_TRIPLE) || GetSpecialAbility(SPECATK_QUAD)) //check double attack, this is NOT the same rules that clients use... @@ -1214,7 +1213,7 @@ void Mob::AI_Process() { int flurry_chance = GetSpecialAbilityParam(SPECATK_FLURRY, 0); flurry_chance = flurry_chance > 0 ? flurry_chance : RuleI(Combat, NPCFlurryChance); - if (MakeRandomInt(0, 99) < flurry_chance) { + if (zone->random.Roll(flurry_chance)) { ExtraAttackOptions opts; int cur = GetSpecialAbilityParam(SPECATK_FLURRY, 2); if (cur > 0) @@ -1256,7 +1255,7 @@ void Mob::AI_Process() { int16 flurry_chance = owner->aabonuses.PetFlurry + owner->spellbonuses.PetFlurry + owner->itembonuses.PetFlurry; - if (flurry_chance && (MakeRandomInt(0, 99) < flurry_chance)) + if (flurry_chance && zone->random.Roll(flurry_chance)) Flurry(nullptr); } } @@ -1265,7 +1264,7 @@ void Mob::AI_Process() { { int rampage_chance = GetSpecialAbilityParam(SPECATK_RAMPAGE, 0); rampage_chance = rampage_chance > 0 ? rampage_chance : 20; - if(MakeRandomInt(0, 99) < rampage_chance) { + if(zone->random.Roll(rampage_chance)) { ExtraAttackOptions opts; int cur = GetSpecialAbilityParam(SPECATK_RAMPAGE, 2); if(cur > 0) { @@ -1304,7 +1303,7 @@ void Mob::AI_Process() { { int rampage_chance = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 0); rampage_chance = rampage_chance > 0 ? rampage_chance : 20; - if(MakeRandomInt(0, 99) < rampage_chance) { + if(zone->random.Roll(rampage_chance)) { ExtraAttackOptions opts; int cur = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 2); if(cur > 0) { @@ -1348,13 +1347,12 @@ void Mob::AI_Process() { //can only dual wield without a weapon if your a monk if(GetSpecialAbility(SPECATK_INNATE_DW) || (GetEquipment(MaterialSecondary) != 0 && GetLevel() > 29) || myclass == MONK || myclass == MONKGM) { float DualWieldProbability = (GetSkill(SkillDualWield) + GetLevel()) / 400.0f; - if(MakeRandomFloat(0.0, 1.0) < DualWieldProbability) + if(zone->random.Roll(DualWieldProbability)) { Attack(target, MainSecondary); if (CanThisClassDoubleAttack()) { - int32 RandRoll = MakeRandomInt(0, 99); - if (RandRoll < (GetLevel() + 20)) + if (zone->random.Roll(GetLevel() + 20)) { Attack(target, MainSecondary); } @@ -1619,12 +1617,12 @@ void NPC::AI_DoMovement() { ) { float movedist = roambox_distance*roambox_distance; - float movex = MakeRandomFloat(0, movedist); + float movex = zone->random.Real(0, movedist); float movey = movedist - movex; movex = sqrtf(movex); movey = sqrtf(movey); - movex *= MakeRandomInt(0, 1) ? 1 : -1; - movey *= MakeRandomInt(0, 1) ? 1 : -1; + movex *= zone->random.Int(0, 1) ? 1 : -1; + movey *= zone->random.Int(0, 1) ? 1 : -1; roambox_movingto_x = GetX() + movex; roambox_movingto_y = GetY() + movey; //Try to calculate new coord using distance. @@ -1635,9 +1633,9 @@ void NPC::AI_DoMovement() { //New coord is still invalid, ignore distance and just pick a new random coord. //If we're here we may have a roambox where one side is shorter than the specified distance. Commons, Wkarana, etc. if (roambox_movingto_x > roambox_max_x || roambox_movingto_x < roambox_min_x) - roambox_movingto_x = MakeRandomFloat(roambox_min_x+1,roambox_max_x-1); + roambox_movingto_x = zone->random.Real(roambox_min_x+1,roambox_max_x-1); if (roambox_movingto_y > roambox_max_y || roambox_movingto_y < roambox_min_y) - roambox_movingto_y = MakeRandomFloat(roambox_min_y+1,roambox_max_y-1); + roambox_movingto_y = zone->random.Real(roambox_min_y+1,roambox_max_y-1); } mlog(AI__WAYPOINTS, "Roam Box: d=%.3f (%.3f->%.3f,%.3f->%.3f): Go To (%.3f,%.3f)", @@ -1879,7 +1877,7 @@ void Mob::AI_Event_NoLongerEngaged() { if (minLastFightingDelayMoving == maxLastFightingDelayMoving) pLastFightingDelayMoving += minLastFightingDelayMoving; else - pLastFightingDelayMoving += MakeRandomInt(minLastFightingDelayMoving, maxLastFightingDelayMoving); + pLastFightingDelayMoving += zone->random.Int(minLastFightingDelayMoving, maxLastFightingDelayMoving); // So mobs don't keep running as a ghost until AIwalking_timer fires // if they were moving prior to losing all hate if(IsMoving()){ diff --git a/zone/mod_functions.cpp b/zone/mod_functions.cpp index b948dff38..6ae0eddad 100644 --- a/zone/mod_functions.cpp +++ b/zone/mod_functions.cpp @@ -1,22 +1,15 @@ -#include "../common/debug.h" -#include "../common/timer.h" -#include -#include -#include "spawn2.h" -#include "entity.h" -#include "masterentity.h" -#include "zone.h" -#include "spawngroup.h" -#include "zonedb.h" -#include "npc.h" -#include "mob.h" #include "client.h" +#include "entity.h" +#include "mob.h" +#include "npc.h" #include "worldserver.h" -#include "quest_parser_collection.h" -#include "event_codes.h" -#include "embparser.h" -#include -#include +#include "zone.h" + +class ItemInst; +class Spawn2; +struct Consider_Struct; +struct DBTradeskillRecipe_Struct; +struct Item_Struct; extern EntityList entity_list; extern Zone* zone; diff --git a/zone/net.cpp b/zone/net.cpp index 498ed5102..9b4080eb2 100644 --- a/zone/net.cpp +++ b/zone/net.cpp @@ -97,7 +97,7 @@ extern Zone* zone; EQStreamFactory eqsf(ZoneStream); npcDecayTimes_Struct npcCorpseDecayTimes[100]; TitleManager title_manager; -QueryServ *QServ = 0; +QueryServ *QServ = 0; TaskManager *taskmanager = 0; QuestParserCollection *parse = 0; @@ -642,4 +642,3 @@ void UpdateWindowTitle(char* iNewTitle) { SetConsoleTitle(tmp); #endif } - diff --git a/zone/net.h b/zone/net.h index e81dd6d51..c49e1ec8e 100644 --- a/zone/net.h +++ b/zone/net.h @@ -26,9 +26,6 @@ #include #endif - -#include -#include #include "../common/types.h" #include "../common/timer.h" void CatchSignal(int); diff --git a/zone/npc.cpp b/zone/npc.cpp index f1ea80639..942245e04 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -15,15 +15,34 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "../common/bodytypes.h" +#include "../common/classes.h" #include "../common/debug.h" -#include -#include -#include -#include -#include "../common/moremath.h" -#include -#include "../common/packet_dump_file.h" +#include "../common/misc_functions.h" +#include "../common/rulesys.h" +#include "../common/seperator.h" +#include "../common/spdat.h" +#include "../common/string_util.h" +#include "../common/clientversions.h" +#include "../common/features.h" +#include "../common/item.h" +#include "../common/item_struct.h" +#include "../common/linked_list.h" +#include "../common/servertalk.h" + +#include "aa.h" +#include "client.h" +#include "entity.h" +#include "npc.h" +#include "string_ids.h" +#include "spawn2.h" #include "zone.h" + +#include +#include +#include + #ifdef _WINDOWS #define snprintf _snprintf #define strncasecmp _strnicmp @@ -33,27 +52,10 @@ #include #endif -#include "npc.h" -#include "map.h" -#include "entity.h" -#include "masterentity.h" -#include "../common/spdat.h" -#include "../common/bodytypes.h" -#include "spawngroup.h" -#include "../common/misc_functions.h" -#include "../common/string_util.h" -#include "../common/rulesys.h" -#include "string_ids.h" - -//#define SPELLQUEUE //Use only if you want to be spammed by spell testing - - extern Zone* zone; extern volatile bool ZoneLoaded; extern EntityList entity_list; -#include "quest_parser_collection.h" - NPC::NPC(const NPCType* d, Spawn2* in_respawn, const xyz_heading& position, int iflymode, bool IsCorpse) : Mob(d->name, d->lastname, @@ -283,7 +285,7 @@ NPC::NPC(const NPCType* d, Spawn2* in_respawn, const xyz_heading& position, int if(trap_list.size() > 0) { std::list::iterator trap_list_iter = trap_list.begin(); - std::advance(trap_list_iter, MakeRandomInt(0, trap_list.size() - 1)); + std::advance(trap_list_iter, zone->random.Int(0, trap_list.size() - 1)); LDoNTrapTemplate* tt = (*trap_list_iter); if(tt) { @@ -536,10 +538,10 @@ void NPC::AddCash(uint16 in_copper, uint16 in_silver, uint16 in_gold, uint16 in_ } void NPC::AddCash() { - copper = MakeRandomInt(1, 100); - silver = MakeRandomInt(1, 50); - gold = MakeRandomInt(1, 10); - platinum = MakeRandomInt(1, 5); + copper = zone->random.Int(1, 100); + silver = zone->random.Int(1, 50); + gold = zone->random.Int(1, 10); + platinum = zone->random.Int(1, 5); } void NPC::RemoveCash() { @@ -656,8 +658,7 @@ bool NPC::Process() viral_timer_counter = 0; } - if(projectile_timer.Check()) - SpellProjectileEffect(); + ProjectileAttack(); if(spellbonuses.GravityEffect == 1) { if(gravity_timer.Check()) @@ -1359,7 +1360,7 @@ void NPC::PickPocket(Client* thief) { return; } - if(MakeRandomInt(0, 100) > 95){ + if(zone->random.Roll(5)) { AddToHateList(thief, 50); Say("Stop thief!"); thief->Message(13, "You are noticed trying to steal!"); @@ -1388,7 +1389,7 @@ void NPC::PickPocket(Client* thief) { memset(charges,0,50); //Determine wheter to steal money or an item. bool no_coin = ((money[0] + money[1] + money[2] + money[3]) == 0); - bool steal_item = (MakeRandomInt(0, 99) < 50 || no_coin); + bool steal_item = (zone->random.Roll(50) || no_coin); if (steal_item) { ItemList::iterator cur,end; @@ -1418,7 +1419,7 @@ void NPC::PickPocket(Client* thief) { } if (x > 0) { - int random = MakeRandomInt(0, x-1); + int random = zone->random.Int(0, x-1); inst = database.CreateItem(steal_items[random], charges[random]); if (inst) { @@ -1453,7 +1454,7 @@ void NPC::PickPocket(Client* thief) { } if (!steal_item) //Steal money { - uint32 amt = MakeRandomInt(1, (steal_skill/25)+1); + uint32 amt = zone->random.Int(1, (steal_skill/25)+1); int steal_type = 0; if (!money[0]) { @@ -1468,7 +1469,7 @@ void NPC::PickPocket(Client* thief) { } } - if (MakeRandomInt(0, 100) <= stealchance) + if (zone->random.Roll(stealchance)) { switch (steal_type) { @@ -1949,7 +1950,7 @@ void NPC::ModifyNPCStat(const char *identifier, const char *newValue) void NPC::LevelScale() { - uint8 random_level = (MakeRandomInt(level, maxlevel)); + uint8 random_level = (zone->random.Int(level, maxlevel)); float scaling = (((random_level / (float)level) - 1) * (scalerate / 100.0f)); diff --git a/zone/npc.cpp.orig b/zone/npc.cpp.orig new file mode 100644 index 000000000..d519810f6 --- /dev/null +++ b/zone/npc.cpp.orig @@ -0,0 +1,2446 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY except by those people which sell it, which + are required to give you total support for your newly bought product; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "../common/bodytypes.h" +#include "../common/classes.h" +#include "../common/debug.h" +#include "../common/misc_functions.h" +#include "../common/rulesys.h" +#include "../common/seperator.h" +#include "../common/spdat.h" +#include "../common/string_util.h" +#include "../common/clientversions.h" +#include "../common/features.h" +#include "../common/item.h" +#include "../common/item_struct.h" +#include "../common/linked_list.h" +#include "../common/servertalk.h" + +#include "aa.h" +#include "client.h" +#include "entity.h" +#include "npc.h" +#include "string_ids.h" +#include "spawn2.h" +#include "zone.h" + +#include +#include +#include + +#ifdef _WINDOWS +#define snprintf _snprintf +#define strncasecmp _strnicmp +#define strcasecmp _stricmp +#else +#include +#include +#endif + +extern Zone* zone; +extern volatile bool ZoneLoaded; +extern EntityList entity_list; + +<<<<<<< HEAD +#include "quest_parser_collection.h" + +NPC::NPC(const NPCType* d, Spawn2* in_respawn, const xyz_heading& position, int iflymode, bool IsCorpse) +======= +NPC::NPC(const NPCType* d, Spawn2* in_respawn, float x, float y, float z, float heading, int iflymode, bool IsCorpse) +>>>>>>> master +: Mob(d->name, + d->lastname, + d->max_hp, + d->max_hp, + d->gender, + d->race, + d->class_, + (bodyType)d->bodytype, + d->deity, + d->level, + d->npc_id, + d->size, + d->runspeed, + position, + d->light, + d->texture, + d->helmtexture, + d->AC, + d->ATK, + d->STR, + d->STA, + d->DEX, + d->AGI, + d->INT, + d->WIS, + d->CHA, + d->haircolor, + d->beardcolor, + d->eyecolor1, + d->eyecolor2, + d->hairstyle, + d->luclinface, + d->beard, + d->drakkin_heritage, + d->drakkin_tattoo, + d->drakkin_details, + (uint32*)d->armor_tint, + 0, + d->see_invis, // pass see_invis/see_ivu flags to mob constructor + d->see_invis_undead, + d->see_hide, + d->see_improved_hide, + d->hp_regen, + d->mana_regen, + d->qglobal, + d->maxlevel, + d->scalerate ), + attacked_timer(CombatEventTimer_expire), + swarm_timer(100), + classattack_timer(1000), + knightattack_timer(1000), + assist_timer(AIassistcheck_delay), + qglobal_purge_timer(30000), + sendhpupdate_timer(1000), + enraged_timer(1000), + taunt_timer(TauntReuseTime * 1000), + m_SpawnPoint(position), + m_GuardPoint(-1,-1,-1,0), + m_GuardPointSaved(0,0,0,0) +{ + //What is the point of this, since the names get mangled.. + Mob* mob = entity_list.GetMob(name); + if(mob != 0) + entity_list.RemoveEntity(mob->GetID()); + + int moblevel=GetLevel(); + + NPCTypedata = d; + NPCTypedata_ours = nullptr; + respawn2 = in_respawn; + swarm_timer.Disable(); + + taunting = false; + proximity = nullptr; + copper = 0; + silver = 0; + gold = 0; + platinum = 0; + max_dmg = d->max_dmg; + min_dmg = d->min_dmg; + attack_count = d->attack_count; + grid = 0; + wp_m = 0; + max_wp=0; + save_wp = 0; + spawn_group = 0; + swarmInfoPtr = nullptr; + spellscale = d->spellscale; + healscale = d->healscale; + + logging_enabled = NPC_DEFAULT_LOGGING_ENABLED; + + pAggroRange = d->aggroradius; + pAssistRange = d->assistradius; + findable = d->findable; + trackable = d->trackable; + + MR = d->MR; + CR = d->CR; + DR = d->DR; + FR = d->FR; + PR = d->PR; + Corrup = d->Corrup; + PhR = d->PhR; + + STR = d->STR; + STA = d->STA; + AGI = d->AGI; + DEX = d->DEX; + INT = d->INT; + WIS = d->WIS; + CHA = d->CHA; + npc_mana = d->Mana; + + //quick fix of ordering if they screwed it up in the DB + if(max_dmg < min_dmg) { + int tmp = min_dmg; + min_dmg = max_dmg; + max_dmg = tmp; + } + + // Max Level and Stat Scaling if maxlevel is set + if(maxlevel > level) + { + LevelScale(); + } + + // Set Resists if they are 0 in the DB + CalcNPCResists(); + + // Set Mana and HP Regen Rates if they are 0 in the DB + CalcNPCRegen(); + + // Set Min and Max Damage if they are 0 in the DB + if(max_dmg == 0){ + CalcNPCDamage(); + } + + accuracy_rating = d->accuracy_rating; + avoidance_rating = d->avoidance_rating; + ATK = d->ATK; + + CalcMaxMana(); + SetMana(GetMaxMana()); + + MerchantType = d->merchanttype; + merchant_open = GetClass() == MERCHANT; + adventure_template_id = d->adventure_template; + flymode = iflymode; + guard_anim = eaStanding; + roambox_distance = 0; + roambox_max_x = -2; + roambox_max_y = -2; + roambox_min_x = -2; + roambox_min_y = -2; + roambox_movingto_x = -2; + roambox_movingto_y = -2; + roambox_min_delay = 1000; + roambox_delay = 1000; + p_depop = false; + loottable_id = d->loottable_id; + + no_target_hotkey = d->no_target_hotkey; + + primary_faction = 0; + SetNPCFactionID(d->npc_faction_id); + + npc_spells_id = 0; + HasAISpell = false; + HasAISpellEffects = false; + + if(GetClass() == MERCERNARY_MASTER && RuleB(Mercs, AllowMercs)) + { + LoadMercTypes(); + LoadMercs(); + } + + SpellFocusDMG = 0; + SpellFocusHeal = 0; + + pet_spell_id = 0; + + delaytimer = false; + combat_event = false; + attack_speed = d->attack_speed; + attack_delay = d->attack_delay; + slow_mitigation = d->slow_mitigation; + + EntityList::RemoveNumbers(name); + entity_list.MakeNameUnique(name); + + npc_aggro = d->npc_aggro; + + if(!IsMerc()) + AI_Start(); + + d_meele_texture1 = d->d_meele_texture1; + d_meele_texture2 = d->d_meele_texture2; + ammo_idfile = d->ammo_idfile; + memset(equipment, 0, sizeof(equipment)); + prim_melee_type = d->prim_melee_type; + sec_melee_type = d->sec_melee_type; + ranged_type = d->ranged_type; + + // If Melee Textures are not set, set attack type to Hand to Hand as default + if(!d_meele_texture1) + prim_melee_type = 28; + if(!d_meele_texture2) + sec_melee_type = 28; + + //give NPCs skill values... + int r; + for(r = 0; r <= HIGHEST_SKILL; r++) { + skills[r] = database.GetSkillCap(GetClass(),(SkillUseTypes)r,moblevel); + } + + if(d->trap_template > 0) + { + std::map >::iterator trap_ent_iter; + std::list trap_list; + + trap_ent_iter = zone->ldon_trap_entry_list.find(d->trap_template); + if(trap_ent_iter != zone->ldon_trap_entry_list.end()) + { + trap_list = trap_ent_iter->second; + if(trap_list.size() > 0) + { + std::list::iterator trap_list_iter = trap_list.begin(); + std::advance(trap_list_iter, zone->random.Int(0, trap_list.size() - 1)); + LDoNTrapTemplate* tt = (*trap_list_iter); + if(tt) + { + if((uint8)tt->spell_id > 0) + { + ldon_trapped = true; + ldon_spell_id = tt->spell_id; + } + else + { + ldon_trapped = false; + ldon_spell_id = 0; + } + + ldon_trap_type = (uint8)tt->type; + if(tt->locked > 0) + { + ldon_locked = true; + ldon_locked_skill = tt->skill; + } + else + { + ldon_locked = false; + ldon_locked_skill = 0; + } + ldon_trap_detected = 0; + } + } + else + { + ldon_trapped = false; + ldon_trap_type = 0; + ldon_spell_id = 0; + ldon_locked = false; + ldon_locked_skill = 0; + ldon_trap_detected = 0; + } + } + else + { + ldon_trapped = false; + ldon_trap_type = 0; + ldon_spell_id = 0; + ldon_locked = false; + ldon_locked_skill = 0; + ldon_trap_detected = 0; + } + } + else + { + ldon_trapped = false; + ldon_trap_type = 0; + ldon_spell_id = 0; + ldon_locked = false; + ldon_locked_skill = 0; + ldon_trap_detected = 0; + } + reface_timer = new Timer(15000); + reface_timer->Disable(); + qGlobals = nullptr; + SetEmoteID(d->emoteid); + InitializeBuffSlots(); + CalcBonuses(); + raid_target = d->raid_target; +} + +NPC::~NPC() +{ + AI_Stop(); + + if(proximity != nullptr) { + entity_list.RemoveProximity(GetID()); + safe_delete(proximity); + } + + safe_delete(NPCTypedata_ours); + + { + ItemList::iterator cur,end; + cur = itemlist.begin(); + end = itemlist.end(); + for(; cur != end; ++cur) { + ServerLootItem_Struct* item = *cur; + safe_delete(item); + } + itemlist.clear(); + } + + { + std::list::iterator cur,end; + cur = faction_list.begin(); + end = faction_list.end(); + for(; cur != end; ++cur) { + struct NPCFaction* fac = *cur; + safe_delete(fac); + } + faction_list.clear(); + } + + safe_delete(reface_timer); + safe_delete(swarmInfoPtr); + safe_delete(qGlobals); + UninitializeBuffSlots(); +} + +void NPC::SetTarget(Mob* mob) { + if(mob == GetTarget()) //dont bother if they are allready our target + return; + + //This is not the default behavior for swarm pets, must be specified from quest functions or rules value. + if(GetSwarmInfo() && GetSwarmInfo()->target && GetTarget() && (GetTarget()->GetHP() > 0)) { + Mob *targ = entity_list.GetMob(GetSwarmInfo()->target); + if(targ != mob){ + return; + } + } + + if (mob) { + SetAttackTimer(); + } else { + ranged_timer.Disable(); + //attack_timer.Disable(); + attack_dw_timer.Disable(); + } + Mob::SetTarget(mob); +} + +ServerLootItem_Struct* NPC::GetItem(int slot_id) { + ItemList::iterator cur,end; + cur = itemlist.begin(); + end = itemlist.end(); + for(; cur != end; ++cur) { + ServerLootItem_Struct* item = *cur; + if (item->equip_slot == slot_id) { + return item; + } + } + return(nullptr); +} + +void NPC::RemoveItem(uint32 item_id, uint16 quantity, uint16 slot) { + ItemList::iterator cur,end; + cur = itemlist.begin(); + end = itemlist.end(); + for(; cur != end; ++cur) { + ServerLootItem_Struct* item = *cur; + if (item->item_id == item_id && slot <= 0 && quantity <= 0) { + itemlist.erase(cur); + return; + } + else if (item->item_id == item_id && item->equip_slot == slot && quantity >= 1) { + //std::cout<<"NPC::RemoveItem"<<" equipSlot:"<equipSlot<<" quantity:"<< quantity<charges <= quantity) + itemlist.erase(cur); + else + item->charges -= quantity; + return; + } + } +} + +void NPC::CheckMinMaxLevel(Mob *them) +{ + if(them == nullptr || !them->IsClient()) + return; + + uint16 themlevel = them->GetLevel(); + uint8 material; + + std::list::iterator cur = itemlist.begin(); + while(cur != itemlist.end()) + { + if(!(*cur)) + return; + + if(themlevel < (*cur)->min_level || themlevel > (*cur)->max_level) + { + material = Inventory::CalcMaterialFromSlot((*cur)->equip_slot); + if(material != 0xFF) + SendWearChange(material); + + cur = itemlist.erase(cur); + continue; + } + ++cur; + } + +} + +void NPC::ClearItemList() { + ItemList::iterator cur,end; + cur = itemlist.begin(); + end = itemlist.end(); + for(; cur != end; ++cur) { + ServerLootItem_Struct* item = *cur; + safe_delete(item); + } + itemlist.clear(); +} + +void NPC::QueryLoot(Client* to) { + int x = 0; + to->Message(0, "Coin: %ip %ig %is %ic", platinum, gold, silver, copper); + + ItemList::iterator cur,end; + cur = itemlist.begin(); + end = itemlist.end(); + for(; cur != end; ++cur) { + const Item_Struct* item = database.GetItem((*cur)->item_id); + if (item) + if (to->GetClientVersion() >= EQClientRoF) + { + to->Message(0, "minlvl: %i maxlvl: %i %i: %c%06X0000000000000000000000000000000000000000000000000%s%c",(*cur)->min_level, (*cur)->max_level, (int) item->ID,0x12, item->ID, item->Name, 0x12); + } + else if (to->GetClientVersion() >= EQClientSoF) + { + to->Message(0, "minlvl: %i maxlvl: %i %i: %c%06X00000000000000000000000000000000000000000000%s%c",(*cur)->min_level, (*cur)->max_level, (int) item->ID,0x12, item->ID, item->Name, 0x12); + } + else + { + to->Message(0, "minlvl: %i maxlvl: %i %i: %c%06X000000000000000000000000000000000000000%s%c",(*cur)->min_level, (*cur)->max_level, (int) item->ID,0x12, item->ID, item->Name, 0x12); + } + else + LogFile->write(EQEMuLog::Error, "Database error, invalid item"); + x++; + } + to->Message(0, "%i items on %s.", x, GetName()); +} + +void NPC::AddCash(uint16 in_copper, uint16 in_silver, uint16 in_gold, uint16 in_platinum) { + if(in_copper >= 0) + copper = in_copper; + else + copper = 0; + + if(in_silver >= 0) + silver = in_silver; + else + silver = 0; + + if(in_gold >= 0) + gold = in_gold; + else + gold = 0; + + if(in_platinum >= 0) + platinum = in_platinum; + else + platinum = 0; +} + +void NPC::AddCash() { + copper = zone->random.Int(1, 100); + silver = zone->random.Int(1, 50); + gold = zone->random.Int(1, 10); + platinum = zone->random.Int(1, 5); +} + +void NPC::RemoveCash() { + copper = 0; + silver = 0; + gold = 0; + platinum = 0; +} + +bool NPC::Process() +{ + if (IsStunned() && stunned_timer.Check()) + { + this->stunned = false; + this->stunned_timer.Disable(); + this->spun_timer.Disable(); + } + + if (p_depop) + { + Mob* owner = entity_list.GetMob(this->ownerid); + if (owner != 0) + { + //if(GetBodyType() != BT_SwarmPet) + // owner->SetPetID(0); + this->ownerid = 0; + this->petid = 0; + } + return false; + } + + SpellProcess(); + + if(tic_timer.Check()) + { + BuffProcess(); + + if(curfp) + ProcessFlee(); + + uint32 bonus = 0; + + if(GetAppearance() == eaSitting) + 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. + if((GetHP() < GetMaxHP()) && !IsPet()) { + if(!IsEngaged()) {//NPC out of combat + if(GetNPCHPRegen() > OOCRegen) + SetHP(GetHP() + GetNPCHPRegen()); + else + SetHP(GetHP() + OOCRegen); + } else + SetHP(GetHP()+GetNPCHPRegen()); + } else if(GetHP() < GetMaxHP() && GetOwnerID() !=0) { + if(!IsEngaged()) //pet + SetHP(GetHP()+GetNPCHPRegen()+bonus+(GetLevel()/5)); + else + SetHP(GetHP()+GetNPCHPRegen()+bonus); + } else + SetHP(GetHP()+GetNPCHPRegen()); + + if(GetMana() < GetMaxMana()) { + SetMana(GetMana()+mana_regen+bonus); + } + + + if(zone->adv_data && !p_depop) + { + ServerZoneAdventureDataReply_Struct* ds = (ServerZoneAdventureDataReply_Struct*)zone->adv_data; + if(ds->type == Adventure_Rescue && ds->data_id == GetNPCTypeID()) + { + Mob *o = GetOwner(); + if(o && o->IsClient()) + { + float x_diff = ds->dest_x - GetX(); + float y_diff = ds->dest_y - GetY(); + float z_diff = ds->dest_z - GetZ(); + float dist = ((x_diff * x_diff) + (y_diff * y_diff) + (z_diff * z_diff)); + if(dist < RuleR(Adventure, DistanceForRescueComplete)) + { + zone->DoAdventureCountIncrease(); + Say("You don't know what this means to me. Thank you so much for finding and saving me from" + " this wretched place. I'll find my way from here."); + Depop(); + } + } + } + } + } + + if (sendhpupdate_timer.Check() && (IsTargeted() || (IsPet() && GetOwner() && GetOwner()->IsClient()))) { + if(!IsFullHP || cur_hp 999) + viral_timer_counter = 0; + } + + ProjectileAttack(); + + if(spellbonuses.GravityEffect == 1) { + if(gravity_timer.Check()) + DoGravityEffect(); + } + + if(reface_timer->Check() && !IsEngaged() && (m_GuardPoint.m_X == GetX() && m_GuardPoint.m_Y == GetY() && m_GuardPoint.m_Z == GetZ())) { + SetHeading(m_GuardPoint.m_Heading); + SendPosition(); + reface_timer->Disable(); + } + + if (IsMezzed()) + return true; + + if(IsStunned()) { + if(spun_timer.Check()) + Spin(); + return true; + } + + if (enraged_timer.Check()){ + ProcessEnrage(); + } + + //Handle assists... + if(assist_timer.Check() && IsEngaged() && !Charmed()) { + entity_list.AIYellForHelp(this, GetTarget()); + } + + if(qGlobals) + { + if(qglobal_purge_timer.Check()) + { + qGlobals->PurgeExpiredGlobals(); + } + } + + AI_Process(); + + return true; +} + +uint32 NPC::CountLoot() { + return(itemlist.size()); +} + +void NPC::Depop(bool StartSpawnTimer) { + uint16 emoteid = this->GetEmoteID(); + if(emoteid != 0) + this->DoNPCEmote(ONDESPAWN,emoteid); + p_depop = true; + if (StartSpawnTimer) { + if (respawn2 != 0) { + respawn2->DeathReset(); + } + } +} + +bool NPC::DatabaseCastAccepted(int spell_id) { + for (int i=0; i < 12; i++) { + switch(spells[spell_id].effectid[i]) { + case SE_Stamina: { + if(IsEngaged() && GetHPRatio() < 100) + return true; + else + return false; + break; + } + case SE_CurrentHPOnce: + case SE_CurrentHP: { + if(this->GetHPRatio() < 100 && spells[spell_id].buffduration == 0) + return true; + else + return false; + break; + } + + case SE_HealOverTime: { + if(this->GetHPRatio() < 100) + return true; + else + return false; + break; + } + case SE_DamageShield: { + return true; + } + case SE_NecPet: + case SE_SummonPet: { + if(GetPet()){ +#ifdef SPELLQUEUE + printf("%s: Attempted to make a second pet, denied.\n",GetName()); +#endif + return false; + } + break; + } + case SE_LocateCorpse: + case SE_SummonCorpse: { + return false; //Pfft, npcs don't need to summon corpses/locate corpses! + break; + } + default: + if(spells[spell_id].goodEffect == 1 && !(spells[spell_id].buffduration == 0 && this->GetHPRatio() == 100) && !IsEngaged()) + return true; + return false; + } + } + return false; +} + +NPC* NPC::SpawnNPC(const char* spawncommand, const xyz_heading& position, Client* client) { + if(spawncommand == 0 || spawncommand[0] == 0) { + return 0; + } + else { + Seperator sep(spawncommand); + //Lets see if someone didn't fill out the whole #spawn function properly + if (!sep.IsNumber(1)) + sprintf(sep.arg[1],"1"); + if (!sep.IsNumber(2)) + sprintf(sep.arg[2],"1"); + if (!sep.IsNumber(3)) + sprintf(sep.arg[3],"0"); + if (atoi(sep.arg[4]) > 2100000000 || atoi(sep.arg[4]) <= 0) + sprintf(sep.arg[4]," "); + if (!strcmp(sep.arg[5],"-")) + sprintf(sep.arg[5]," "); + if (!sep.IsNumber(5)) + sprintf(sep.arg[5]," "); + if (!sep.IsNumber(6)) + sprintf(sep.arg[6],"1"); + if (!sep.IsNumber(8)) + sprintf(sep.arg[8],"0"); + if (!sep.IsNumber(9)) + sprintf(sep.arg[9], "0"); + if (!sep.IsNumber(7)) + sprintf(sep.arg[7],"0"); + if (!strcmp(sep.arg[4],"-")) + sprintf(sep.arg[4]," "); + if (!sep.IsNumber(10)) // bodytype + sprintf(sep.arg[10], "0"); + //Calc MaxHP if client neglected to enter it... + if (!sep.IsNumber(4)) { + //Stolen from Client::GetMaxHP... + uint8 multiplier = 0; + int tmplevel = atoi(sep.arg[2]); + switch(atoi(sep.arg[5])) + { + case WARRIOR: + if (tmplevel < 20) + multiplier = 22; + else if (tmplevel < 30) + multiplier = 23; + else if (tmplevel < 40) + multiplier = 25; + else if (tmplevel < 53) + multiplier = 27; + else if (tmplevel < 57) + multiplier = 28; + else + multiplier = 30; + break; + + case DRUID: + case CLERIC: + case SHAMAN: + multiplier = 15; + break; + + case PALADIN: + case SHADOWKNIGHT: + if (tmplevel < 35) + multiplier = 21; + else if (tmplevel < 45) + multiplier = 22; + else if (tmplevel < 51) + multiplier = 23; + else if (tmplevel < 56) + multiplier = 24; + else if (tmplevel < 60) + multiplier = 25; + else + multiplier = 26; + break; + + case MONK: + case BARD: + case ROGUE: + //case BEASTLORD: + if (tmplevel < 51) + multiplier = 18; + else if (tmplevel < 58) + multiplier = 19; + else + multiplier = 20; + break; + + case RANGER: + if (tmplevel < 58) + multiplier = 20; + else + multiplier = 21; + break; + + case MAGICIAN: + case WIZARD: + case NECROMANCER: + case ENCHANTER: + multiplier = 12; + break; + + default: + if (tmplevel < 35) + multiplier = 21; + else if (tmplevel < 45) + multiplier = 22; + else if (tmplevel < 51) + multiplier = 23; + else if (tmplevel < 56) + multiplier = 24; + else if (tmplevel < 60) + multiplier = 25; + else + multiplier = 26; + break; + } + sprintf(sep.arg[4],"%i",5+multiplier*atoi(sep.arg[2])+multiplier*atoi(sep.arg[2])*75/300); + } + + // Autoselect NPC Gender + if (sep.arg[5][0] == 0) { + sprintf(sep.arg[5], "%i", (int) Mob::GetDefaultGender(atoi(sep.arg[1]))); + } + + //Time to create the NPC!! + NPCType* npc_type = new NPCType; + memset(npc_type, 0, sizeof(NPCType)); + + strncpy(npc_type->name, sep.arg[0], 60); + npc_type->cur_hp = atoi(sep.arg[4]); + npc_type->max_hp = atoi(sep.arg[4]); + npc_type->race = atoi(sep.arg[1]); + npc_type->gender = atoi(sep.arg[5]); + npc_type->class_ = atoi(sep.arg[6]); + npc_type->deity = 1; + npc_type->level = atoi(sep.arg[2]); + npc_type->npc_id = 0; + npc_type->loottable_id = 0; + npc_type->texture = atoi(sep.arg[3]); + npc_type->light = 0; + npc_type->runspeed = 1.25; + npc_type->d_meele_texture1 = atoi(sep.arg[7]); + npc_type->d_meele_texture2 = atoi(sep.arg[8]); + npc_type->merchanttype = atoi(sep.arg[9]); + npc_type->bodytype = atoi(sep.arg[10]); + + npc_type->STR = 150; + npc_type->STA = 150; + npc_type->DEX = 150; + npc_type->AGI = 150; + npc_type->INT = 150; + npc_type->WIS = 150; + npc_type->CHA = 150; + + npc_type->attack_delay = 30; + + npc_type->prim_melee_type = 28; + npc_type->sec_melee_type = 28; + + NPC* npc = new NPC(npc_type, nullptr, position, FlyMode3); + npc->GiveNPCTypeData(npc_type); + + entity_list.AddNPC(npc); + + if (client) { + // Notify client of spawn data + client->Message(0, "New spawn:"); + client->Message(0, "Name: %s", npc->name); + client->Message(0, "Race: %u", npc->race); + client->Message(0, "Level: %u", npc->level); + client->Message(0, "Material: %u", npc->texture); + client->Message(0, "Current/Max HP: %i", npc->max_hp); + client->Message(0, "Gender: %u", npc->gender); + client->Message(0, "Class: %u", npc->class_); + client->Message(0, "Weapon Item Number: %u/%u", npc->d_meele_texture1, npc->d_meele_texture2); + client->Message(0, "MerchantID: %u", npc->MerchantType); + client->Message(0, "Bodytype: %u", npc->bodytype); + } + + return npc; + } +} + +uint32 ZoneDatabase::CreateNewNPCCommand(const char* zone, uint32 zone_version,Client *client, NPC* spawn, uint32 extra) { + + uint32 npc_type_id = 0; + + if (extra && client && client->GetZoneID()) + { + // Set an npc_type ID within the standard range for the current zone if possible (zone_id * 1000) + int starting_npc_id = client->GetZoneID() * 1000; + + std::string query = StringFormat("SELECT MAX(id) FROM npc_types WHERE id >= %i AND id < %i", + starting_npc_id, starting_npc_id + 1000); + auto results = QueryDatabase(query); + if (results.Success()) { + if (results.RowCount() != 0) + { + auto row = results.begin(); + npc_type_id = atoi(row[0]) + 1; + // Prevent the npc_type id from exceeding the range for this zone + if (npc_type_id >= (starting_npc_id + 1000)) + npc_type_id = 0; + } + else // No npc_type IDs set in this range yet + npc_type_id = starting_npc_id; + } + } + + char tmpstr[64]; + EntityList::RemoveNumbers(strn0cpy(tmpstr, spawn->GetName(), sizeof(tmpstr))); + std::string query; + if (npc_type_id) + { + query = StringFormat("INSERT INTO npc_types (id, name, level, race, class, hp, gender, " + "texture, helmtexture, size, loottable_id, merchant_id, face, " + "runspeed, prim_melee_type, sec_melee_type) " + "VALUES(%i, \"%s\" , %i, %i, %i, %i, %i, %i, %i, %f, %i, %i, %i, %f, %i, %i)", + npc_type_id, tmpstr, spawn->GetLevel(), spawn->GetRace(), spawn->GetClass(), + spawn->GetMaxHP(), spawn->GetGender(), spawn->GetTexture(), + spawn->GetHelmTexture(), spawn->GetSize(), spawn->GetLoottableID(), + spawn->MerchantType, 0, spawn->GetRunspeed(), 28, 28); + auto results = QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "NPCSpawnDB Error: %s %s", query.c_str(), results.ErrorMessage().c_str()); + return false; + } + npc_type_id = results.LastInsertedID(); + } + else + { + query = StringFormat("INSERT INTO npc_types (name, level, race, class, hp, gender, " + "texture, helmtexture, size, loottable_id, merchant_id, face, " + "runspeed, prim_melee_type, sec_melee_type) " + "VALUES(\"%s\", %i, %i, %i, %i, %i, %i, %i, %f, %i, %i, %i, %f, %i, %i)", + tmpstr, spawn->GetLevel(), spawn->GetRace(), spawn->GetClass(), + spawn->GetMaxHP(), spawn->GetGender(), spawn->GetTexture(), + spawn->GetHelmTexture(), spawn->GetSize(), spawn->GetLoottableID(), + spawn->MerchantType, 0, spawn->GetRunspeed(), 28, 28); + auto results = QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "NPCSpawnDB Error: %s %s", query.c_str(), results.ErrorMessage().c_str()); + return false; + } + npc_type_id = results.LastInsertedID(); + } + + if(client) + client->LogSQL(query.c_str()); + + query = StringFormat("INSERT INTO spawngroup (id, name) VALUES(%i, '%s-%s')", 0, zone, spawn->GetName()); + auto results = QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "NPCSpawnDB Error: %s %s", query.c_str(), results.ErrorMessage().c_str()); + return false; + } + uint32 spawngroupid = results.LastInsertedID(); + + if(client) + client->LogSQL(query.c_str()); + + query = StringFormat("INSERT INTO spawn2 (zone, version, x, y, z, respawntime, heading, spawngroupID) " + "VALUES('%s', %u, %f, %f, %f, %i, %f, %i)", + zone, zone_version, spawn->GetX(), spawn->GetY(), spawn->GetZ(), 1200, + spawn->GetHeading(), spawngroupid); + results = QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "NPCSpawnDB Error: %s %s", query.c_str(), results.ErrorMessage().c_str()); + return false; + } + + if(client) + client->LogSQL(query.c_str()); + + query = StringFormat("INSERT INTO spawnentry (spawngroupID, npcID, chance) VALUES(%i, %i, %i)", + spawngroupid, npc_type_id, 100); + results = QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "NPCSpawnDB Error: %s %s", query.c_str(), results.ErrorMessage().c_str()); + return false; + } + + if(client) + client->LogSQL(query.c_str()); + + return true; +} + +uint32 ZoneDatabase::AddNewNPCSpawnGroupCommand(const char* zone, uint32 zone_version, Client *client, NPC* spawn, uint32 respawnTime) { + uint32 last_insert_id = 0; + + std::string query = StringFormat("INSERT INTO spawngroup (name) VALUES('%s%s%i')", + zone, spawn->GetName(), Timer::GetCurrentTime()); + auto results = QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "CreateNewNPCSpawnGroupCommand Error: %s %s", query.c_str(), results.ErrorMessage().c_str()); + return 0; + } + last_insert_id = results.LastInsertedID(); + + if(client) + client->LogSQL(query.c_str()); + + uint32 respawntime = 0; + uint32 spawnid = 0; + if (respawnTime) + respawntime = respawnTime; + else if(spawn->respawn2 && spawn->respawn2->RespawnTimer() != 0) + respawntime = spawn->respawn2->RespawnTimer(); + else + respawntime = 1200; + + query = StringFormat("INSERT INTO spawn2 (zone, version, x, y, z, respawntime, heading, spawngroupID) " + "VALUES('%s', %u, %f, %f, %f, %i, %f, %i)", + zone, zone_version, spawn->GetX(), spawn->GetY(), spawn->GetZ(), respawntime, + spawn->GetHeading(), last_insert_id); + results = QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "CreateNewNPCSpawnGroupCommand Error: %s %s", query.c_str(), results.ErrorMessage().c_str()); + return 0; + } + spawnid = results.LastInsertedID(); + + if(client) + client->LogSQL(query.c_str()); + + query = StringFormat("INSERT INTO spawnentry (spawngroupID, npcID, chance) VALUES(%i, %i, %i)", + last_insert_id, spawn->GetNPCTypeID(), 100); + results = QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "CreateNewNPCSpawnGroupCommand Error: %s %s", query.c_str(), results.ErrorMessage().c_str()); + return 0; + } + + if(client) + client->LogSQL(query.c_str()); + + return spawnid; +} + +uint32 ZoneDatabase::UpdateNPCTypeAppearance(Client *client, NPC* spawn) { + + std::string query = StringFormat("UPDATE npc_types SET name = \"%s\", level = %i, race = %i, class = %i, " + "hp = %i, gender = %i, texture = %i, helmtexture = %i, size = %i, " + "loottable_id = %i, merchant_id = %i, face = %i, WHERE id = %i", + spawn->GetName(), spawn->GetLevel(), spawn->GetRace(), spawn->GetClass(), + spawn->GetMaxHP(), spawn->GetGender(), spawn->GetTexture(), + spawn->GetHelmTexture(), spawn->GetSize(), spawn->GetLoottableID(), + spawn->MerchantType, spawn->GetNPCTypeID()); + auto results = QueryDatabase(query); + if (!results.Success() && client) + client->LogSQL(query.c_str()); + + return results.Success() == true? 1: 0; +} + +uint32 ZoneDatabase::DeleteSpawnLeaveInNPCTypeTable(const char* zone, Client *client, NPC* spawn) { + uint32 id = 0; + uint32 spawngroupID = 0; + + std::string query = StringFormat("SELECT id, spawngroupID FROM spawn2 WHERE " + "zone='%s' AND spawngroupID=%i", zone, spawn->GetSp2()); + auto results = QueryDatabase(query); + if (!results.Success()) + return 0; + + if (results.RowCount() == 0) + return 0; + + auto row = results.begin(); + if (row[0]) + id = atoi(row[0]); + + if (row[1]) + spawngroupID = atoi(row[1]); + + query = StringFormat("DELETE FROM spawn2 WHERE id = '%i'", id); + results = QueryDatabase(query); + if (!results.Success()) + return 0; + + if(client) + client->LogSQL(query.c_str()); + + query = StringFormat("DELETE FROM spawngroup WHERE id = '%i'", spawngroupID); + results = QueryDatabase(query); + if (!results.Success()) + return 0; + + if(client) + client->LogSQL(query.c_str()); + + query = StringFormat("DELETE FROM spawnentry WHERE spawngroupID = '%i'", spawngroupID); + results = QueryDatabase(query); + if (!results.Success()) + return 0; + + if(client) + client->LogSQL(query.c_str()); + + return 1; +} + +uint32 ZoneDatabase::DeleteSpawnRemoveFromNPCTypeTable(const char* zone, uint32 zone_version, Client *client, NPC* spawn) { + + uint32 id = 0; + uint32 spawngroupID = 0; + + std::string query = StringFormat("SELECT id, spawngroupID FROM spawn2 WHERE zone = '%s' " + "AND version = %u AND spawngroupID = %i", + zone, zone_version, spawn->GetSp2()); + auto results = QueryDatabase(query); + if (!results.Success()) + return 0; + + if (results.RowCount() == 0) + return 0; + + auto row = results.begin(); + + if (row[0]) + id = atoi(row[0]); + + if (row[1]) + spawngroupID = atoi(row[1]); + + query = StringFormat("DELETE FROM spawn2 WHERE id = '%i'", id); + results = QueryDatabase(query); + if (!results.Success()) + return 0; + + if(client) + client->LogSQL(query.c_str()); + + query = StringFormat("DELETE FROM spawngroup WHERE id = '%i'", spawngroupID); + results = QueryDatabase(query); + if (!results.Success()) + return 0; + + if(client) + client->LogSQL(query.c_str()); + + query = StringFormat("DELETE FROM spawnentry WHERE spawngroupID = '%i'", spawngroupID); + results = QueryDatabase(query); + if (!results.Success()) + return 0; + + if(client) + client->LogSQL(query.c_str()); + + query = StringFormat("DELETE FROM npc_types WHERE id = '%i'", spawn->GetNPCTypeID()); + results = QueryDatabase(query); + if (!results.Success()) + return 0; + + if(client) + client->LogSQL(query.c_str()); + + return 1; +} + +uint32 ZoneDatabase::AddSpawnFromSpawnGroup(const char* zone, uint32 zone_version, Client *client, NPC* spawn, uint32 spawnGroupID) { + + uint32 last_insert_id = 0; + std::string query = StringFormat("INSERT INTO spawn2 (zone, version, x, y, z, respawntime, heading, spawngroupID) " + "VALUES('%s', %u, %f, %f, %f, %i, %f, %i)", + zone, zone_version, client->GetX(), client->GetY(), client->GetZ(), + 120, client->GetHeading(), spawnGroupID); + auto results = QueryDatabase(query); + if (!results.Success()) + return 0; + + if(client) + client->LogSQL(query.c_str()); + + return 1; +} + +uint32 ZoneDatabase::AddNPCTypes(const char* zone, uint32 zone_version, Client *client, NPC* spawn, uint32 spawnGroupID) { + + uint32 npc_type_id; + char numberlessName[64]; + + EntityList::RemoveNumbers(strn0cpy(numberlessName, spawn->GetName(), sizeof(numberlessName))); + std::string query = StringFormat("INSERT INTO npc_types (name, level, race, class, hp, gender, " + "texture, helmtexture, size, loottable_id, merchant_id, face, " + "runspeed, prim_melee_type, sec_melee_type) " + "VALUES(\"%s\", %i, %i, %i, %i, %i, %i, %i, %f, %i, %i, %i, %f, %i, %i)", + numberlessName, spawn->GetLevel(), spawn->GetRace(), spawn->GetClass(), + spawn->GetMaxHP(), spawn->GetGender(), spawn->GetTexture(), + spawn->GetHelmTexture(), spawn->GetSize(), spawn->GetLoottableID(), + spawn->MerchantType, 0, spawn->GetRunspeed(), 28, 28); + auto results = QueryDatabase(query); + if (!results.Success()) + return 0; + npc_type_id = results.LastInsertedID(); + + if(client) + client->LogSQL(query.c_str()); + + if(client) + client->Message(0, "%s npc_type ID %i created successfully!", numberlessName, npc_type_id); + + return 1; +} + +uint32 ZoneDatabase::NPCSpawnDB(uint8 command, const char* zone, uint32 zone_version, Client *c, NPC* spawn, uint32 extra) { + + switch (command) { + case 0: { // Create a new NPC and add all spawn related data + return CreateNewNPCCommand(zone, zone_version, c, spawn, extra); + } + case 1:{ // Add new spawn group and spawn point for an existing NPC Type ID + return AddNewNPCSpawnGroupCommand(zone, zone_version, c, spawn, extra); + } + case 2: { // Update npc_type appearance and other data on targeted spawn + return UpdateNPCTypeAppearance(c, spawn); + } + case 3: { // delete spawn from spawning, but leave in npc_types table + return DeleteSpawnLeaveInNPCTypeTable(zone, c, spawn); + } + case 4: { //delete spawn from DB (including npc_type) + return DeleteSpawnRemoveFromNPCTypeTable(zone, zone_version, c, spawn); + } + case 5: { // add a spawn from spawngroup + return AddSpawnFromSpawnGroup(zone, zone_version, c, spawn, extra); + } + case 6: { // add npc_type + return AddNPCTypes(zone, zone_version, c, spawn, extra); + } + } + return false; +} + +int32 NPC::GetEquipmentMaterial(uint8 material_slot) const +{ + if (material_slot >= _MaterialCount) + return 0; + + int inv_slot = Inventory::CalcSlotFromMaterial(material_slot); + if (inv_slot == -1) + return 0; + if(equipment[inv_slot] == 0) { + switch(material_slot) { + case MaterialHead: + return helmtexture; + case MaterialChest: + return texture; + case MaterialPrimary: + return d_meele_texture1; + case MaterialSecondary: + return d_meele_texture2; + default: + //they have nothing in the slot, and its not a special slot... they get nothing. + return(0); + } + } + + //they have some loot item in this slot, pass it up to the default handler + return(Mob::GetEquipmentMaterial(material_slot)); +} + +uint32 NPC::GetMaxDamage(uint8 tlevel) +{ + uint32 dmg = 0; + if (tlevel < 40) + dmg = tlevel*2+2; + else if (tlevel < 50) + dmg = level*25/10+2; + else if (tlevel < 60) + dmg = (tlevel*3+2)+((tlevel-50)*30); + else + dmg = (tlevel*3+2)+((tlevel-50)*35); + return dmg; +} + +void NPC::PickPocket(Client* thief) { + + thief->CheckIncreaseSkill(SkillPickPockets, nullptr, 5); + + //make sure were allowed to targte them: + int olevel = GetLevel(); + if(olevel > (thief->GetLevel() + THIEF_PICKPOCKET_OVER)) { + thief->Message(13, "You are too inexperienced to pick pocket this target"); + thief->SendPickPocketResponse(this, 0, PickPocketFailed); + //should we check aggro + return; + } + + if(zone->random.Roll(5)) { + AddToHateList(thief, 50); + Say("Stop thief!"); + thief->Message(13, "You are noticed trying to steal!"); + thief->SendPickPocketResponse(this, 0, PickPocketFailed); + return; + } + + int steal_skill = thief->GetSkill(SkillPickPockets); + int stealchance = steal_skill*100/(5*olevel+5); + ItemInst* inst = 0; + int x = 0; + int slot[50]; + int steal_items[50]; + int charges[50]; + int money[4]; + money[0] = GetPlatinum(); + money[1] = GetGold(); + money[2] = GetSilver(); + money[3] = GetCopper(); + if (steal_skill < 125) + money[0] = 0; + if (steal_skill < 60) + money[1] = 0; + memset(slot,0,50); + memset(steal_items,0,50); + memset(charges,0,50); + //Determine wheter to steal money or an item. + bool no_coin = ((money[0] + money[1] + money[2] + money[3]) == 0); + bool steal_item = (zone->random.Roll(50) || no_coin); + if (steal_item) + { + ItemList::iterator cur,end; + cur = itemlist.begin(); + end = itemlist.end(); + for(; cur != end && x < 49; ++cur) { + ServerLootItem_Struct* citem = *cur; + const Item_Struct* item = database.GetItem(citem->item_id); + if (item) + { + inst = database.CreateItem(item, citem->charges); + bool is_arrow = (item->ItemType == ItemTypeArrow) ? true : false; + int slot_id = thief->GetInv().FindFreeSlot(false, true, inst->GetItem()->Size, is_arrow); + if (/*!Equipped(item->ID) &&*/ + !item->Magic && item->NoDrop != 0 && !inst->IsType(ItemClassContainer) && slot_id != INVALID_INDEX + /*&& steal_skill > item->StealSkill*/ ) + { + slot[x] = slot_id; + steal_items[x] = item->ID; + if (inst->IsStackable()) + charges[x] = 1; + else + charges[x] = citem->charges; + x++; + } + } + } + if (x > 0) + { + int random = zone->random.Int(0, x-1); + inst = database.CreateItem(steal_items[random], charges[random]); + if (inst) + { + const Item_Struct* item = inst->GetItem(); + if (item) + { + if (/*item->StealSkill || */steal_skill >= stealchance) + { + thief->PutItemInInventory(slot[random], *inst); + thief->SendItemPacket(slot[random], inst, ItemPacketTrade); + RemoveItem(item->ID); + thief->SendPickPocketResponse(this, 0, PickPocketItem, item); + } + else + steal_item = false; + } + else + steal_item = false; + } + else + steal_item = false; + } + else if (!no_coin) + { + steal_item = false; + } + else + { + thief->Message(0, "This target's pockets are empty"); + thief->SendPickPocketResponse(this, 0, PickPocketFailed); + } + } + if (!steal_item) //Steal money + { + uint32 amt = zone->random.Int(1, (steal_skill/25)+1); + int steal_type = 0; + if (!money[0]) + { + steal_type = 1; + if (!money[1]) + { + steal_type = 2; + if (!money[2]) + { + steal_type = 3; + } + } + } + + if (zone->random.Roll(stealchance)) + { + switch (steal_type) + { + case 0:{ + if (amt > GetPlatinum()) + amt = GetPlatinum(); + SetPlatinum(GetPlatinum()-amt); + thief->AddMoneyToPP(0,0,0,amt,false); + thief->SendPickPocketResponse(this, amt, PickPocketPlatinum); + break; + } + case 1:{ + if (amt > GetGold()) + amt = GetGold(); + SetGold(GetGold()-amt); + thief->AddMoneyToPP(0,0,amt,0,false); + thief->SendPickPocketResponse(this, amt, PickPocketGold); + break; + } + case 2:{ + if (amt > GetSilver()) + amt = GetSilver(); + SetSilver(GetSilver()-amt); + thief->AddMoneyToPP(0,amt,0,0,false); + thief->SendPickPocketResponse(this, amt, PickPocketSilver); + break; + } + case 3:{ + if (amt > GetCopper()) + amt = GetCopper(); + SetCopper(GetCopper()-amt); + thief->AddMoneyToPP(amt,0,0,0,false); + thief->SendPickPocketResponse(this, amt, PickPocketCopper); + break; + } + } + } + else + { + thief->SendPickPocketResponse(this, 0, PickPocketFailed); + } + } + safe_delete(inst); +} + +void Mob::NPCSpecialAttacks(const char* parse, int permtag, bool reset, bool remove) { + if(reset) + { + ClearSpecialAbilities(); + } + + const char* orig_parse = parse; + while (*parse) + { + switch(*parse) + { + case 'E': + SetSpecialAbility(SPECATK_ENRAGE, remove ? 0 : 1); + break; + case 'F': + SetSpecialAbility(SPECATK_FLURRY, remove ? 0 : 1); + break; + case 'R': + SetSpecialAbility(SPECATK_RAMPAGE, remove ? 0 : 1); + break; + case 'r': + SetSpecialAbility(SPECATK_AREA_RAMPAGE, remove ? 0 : 1); + break; + case 'S': + if(remove) { + SetSpecialAbility(SPECATK_SUMMON, 0); + StopSpecialAbilityTimer(SPECATK_SUMMON); + } else { + SetSpecialAbility(SPECATK_SUMMON, 1); + } + break; + case 'T': + SetSpecialAbility(SPECATK_TRIPLE, remove ? 0 : 1); + break; + case 'Q': + //quad requires triple to work properly + if(remove) { + SetSpecialAbility(SPECATK_QUAD, 0); + } else { + SetSpecialAbility(SPECATK_TRIPLE, 1); + SetSpecialAbility(SPECATK_QUAD, 1); + } + break; + case 'b': + SetSpecialAbility(SPECATK_BANE, remove ? 0 : 1); + break; + case 'm': + SetSpecialAbility(SPECATK_MAGICAL, remove ? 0 : 1); + break; + case 'U': + SetSpecialAbility(UNSLOWABLE, remove ? 0 : 1); + break; + case 'M': + SetSpecialAbility(UNMEZABLE, remove ? 0 : 1); + break; + case 'C': + SetSpecialAbility(UNCHARMABLE, remove ? 0 : 1); + break; + case 'N': + SetSpecialAbility(UNSTUNABLE, remove ? 0 : 1); + break; + case 'I': + SetSpecialAbility(UNSNAREABLE, remove ? 0 : 1); + break; + case 'D': + SetSpecialAbility(UNFEARABLE, remove ? 0 : 1); + break; + case 'K': + SetSpecialAbility(UNDISPELLABLE, remove ? 0 : 1); + break; + case 'A': + SetSpecialAbility(IMMUNE_MELEE, remove ? 0 : 1); + break; + case 'B': + SetSpecialAbility(IMMUNE_MAGIC, remove ? 0 : 1); + break; + case 'f': + SetSpecialAbility(IMMUNE_FLEEING, remove ? 0 : 1); + break; + case 'O': + SetSpecialAbility(IMMUNE_MELEE_EXCEPT_BANE, remove ? 0 : 1); + break; + case 'W': + SetSpecialAbility(IMMUNE_MELEE_NONMAGICAL, remove ? 0 : 1); + break; + case 'H': + SetSpecialAbility(IMMUNE_AGGRO, remove ? 0 : 1); + break; + case 'G': + SetSpecialAbility(IMMUNE_AGGRO_ON, remove ? 0 : 1); + break; + case 'g': + SetSpecialAbility(IMMUNE_CASTING_FROM_RANGE, remove ? 0 : 1); + break; + case 'd': + SetSpecialAbility(IMMUNE_FEIGN_DEATH, remove ? 0 : 1); + break; + case 'Y': + SetSpecialAbility(SPECATK_RANGED_ATK, remove ? 0 : 1); + break; + case 'L': + SetSpecialAbility(SPECATK_INNATE_DW, remove ? 0 : 1); + break; + case 't': + SetSpecialAbility(NPC_TUNNELVISION, remove ? 0 : 1); + break; + case 'n': + SetSpecialAbility(NPC_NO_BUFFHEAL_FRIENDS, remove ? 0 : 1); + break; + case 'p': + SetSpecialAbility(IMMUNE_PACIFY, remove ? 0 : 1); + break; + case 'J': + SetSpecialAbility(LEASH, remove ? 0 : 1); + break; + case 'j': + SetSpecialAbility(TETHER, remove ? 0 : 1); + break; + case 'o': + SetSpecialAbility(DESTRUCTIBLE_OBJECT, remove ? 0 : 1); + SetDestructibleObject(remove ? true : false); + break; + case 'Z': + SetSpecialAbility(NO_HARM_FROM_CLIENT, remove ? 0 : 1); + break; + case 'i': + SetSpecialAbility(IMMUNE_TAUNT, remove ? 0 : 1); + break; + case 'e': + SetSpecialAbility(ALWAYS_FLEE, remove ? 0 : 1); + break; + case 'h': + SetSpecialAbility(FLEE_PERCENT, remove ? 0 : 1); + break; + + default: + break; + } + parse++; + } + + if(permtag == 1 && this->GetNPCTypeID() > 0) + { + if(database.SetSpecialAttkFlag(this->GetNPCTypeID(), orig_parse)) + { + LogFile->write(EQEMuLog::Normal, "NPCTypeID: %i flagged to '%s' for Special Attacks.\n",this->GetNPCTypeID(),orig_parse); + } + } +} + +bool Mob::HasNPCSpecialAtk(const char* parse) { + + bool HasAllAttacks = true; + + while (*parse && HasAllAttacks == true) + { + switch(*parse) + { + case 'E': + if (!GetSpecialAbility(SPECATK_ENRAGE)) + HasAllAttacks = false; + break; + case 'F': + if (!GetSpecialAbility(SPECATK_FLURRY)) + HasAllAttacks = false; + break; + case 'R': + if (!GetSpecialAbility(SPECATK_RAMPAGE)) + HasAllAttacks = false; + break; + case 'r': + if (!GetSpecialAbility(SPECATK_AREA_RAMPAGE)) + HasAllAttacks = false; + break; + case 'S': + if (!GetSpecialAbility(SPECATK_SUMMON)) + HasAllAttacks = false; + break; + case 'T': + if (!GetSpecialAbility(SPECATK_TRIPLE)) + HasAllAttacks = false; + break; + case 'Q': + if (!GetSpecialAbility(SPECATK_QUAD)) + HasAllAttacks = false; + break; + case 'b': + if (!GetSpecialAbility(SPECATK_BANE)) + HasAllAttacks = false; + break; + case 'm': + if (!GetSpecialAbility(SPECATK_MAGICAL)) + HasAllAttacks = false; + break; + case 'U': + if (!GetSpecialAbility(UNSLOWABLE)) + HasAllAttacks = false; + break; + case 'M': + if (!GetSpecialAbility(UNMEZABLE)) + HasAllAttacks = false; + break; + case 'C': + if (!GetSpecialAbility(UNCHARMABLE)) + HasAllAttacks = false; + break; + case 'N': + if (!GetSpecialAbility(UNSTUNABLE)) + HasAllAttacks = false; + break; + case 'I': + if (!GetSpecialAbility(UNSNAREABLE)) + HasAllAttacks = false; + break; + case 'D': + if (!GetSpecialAbility(UNFEARABLE)) + HasAllAttacks = false; + break; + case 'A': + if (!GetSpecialAbility(IMMUNE_MELEE)) + HasAllAttacks = false; + break; + case 'B': + if (!GetSpecialAbility(IMMUNE_MAGIC)) + HasAllAttacks = false; + break; + case 'f': + if (!GetSpecialAbility(IMMUNE_FLEEING)) + HasAllAttacks = false; + break; + case 'O': + if (!GetSpecialAbility(IMMUNE_MELEE_EXCEPT_BANE)) + HasAllAttacks = false; + break; + case 'W': + if (!GetSpecialAbility(IMMUNE_MELEE_NONMAGICAL)) + HasAllAttacks = false; + break; + case 'H': + if (!GetSpecialAbility(IMMUNE_AGGRO)) + HasAllAttacks = false; + break; + case 'G': + if (!GetSpecialAbility(IMMUNE_AGGRO_ON)) + HasAllAttacks = false; + break; + case 'g': + if (!GetSpecialAbility(IMMUNE_CASTING_FROM_RANGE)) + HasAllAttacks = false; + break; + case 'd': + if (!GetSpecialAbility(IMMUNE_FEIGN_DEATH)) + HasAllAttacks = false; + break; + case 'Y': + if (!GetSpecialAbility(SPECATK_RANGED_ATK)) + HasAllAttacks = false; + break; + case 'L': + if (!GetSpecialAbility(SPECATK_INNATE_DW)) + HasAllAttacks = false; + break; + case 't': + if (!GetSpecialAbility(NPC_TUNNELVISION)) + HasAllAttacks = false; + break; + case 'n': + if (!GetSpecialAbility(NPC_NO_BUFFHEAL_FRIENDS)) + HasAllAttacks = false; + break; + case 'p': + if(!GetSpecialAbility(IMMUNE_PACIFY)) + HasAllAttacks = false; + break; + case 'J': + if(!GetSpecialAbility(LEASH)) + HasAllAttacks = false; + break; + case 'j': + if(!GetSpecialAbility(TETHER)) + HasAllAttacks = false; + break; + case 'o': + if(!GetSpecialAbility(DESTRUCTIBLE_OBJECT)) + { + HasAllAttacks = false; + SetDestructibleObject(false); + } + break; + case 'Z': + if(!GetSpecialAbility(NO_HARM_FROM_CLIENT)){ + HasAllAttacks = false; + } + break; + case 'e': + if(!GetSpecialAbility(ALWAYS_FLEE)) + HasAllAttacks = false; + break; + case 'h': + if(!GetSpecialAbility(FLEE_PERCENT)) + HasAllAttacks = false; + break; + default: + HasAllAttacks = false; + break; + } + parse++; + } + + return HasAllAttacks; +} + +void NPC::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) +{ + Mob::FillSpawnStruct(ns, ForWho); + PetOnSpawn(ns); + ns->spawn.is_npc = 1; +} + +void NPC::PetOnSpawn(NewSpawn_Struct* ns) +{ + //Basic settings to make sure swarm pets work properly. + Mob *swarmOwner = nullptr; + if (GetSwarmOwner()) + { + swarmOwner = entity_list.GetMobID(GetSwarmOwner()); + } + + if (swarmOwner != nullptr) + { + if(swarmOwner->IsClient()) + { + SetPetOwnerClient(true); //Simple flag to determine if pet belongs to a client + SetAllowBeneficial(1);//Allow temp pets to receive buffs and heals if owner is client. + //This is a hack to allow CLIENT swarm pets NOT to be targeted with F8. Warning: Will turn name 'Yellow'! + if (RuleB(Pets, SwarmPetNotTargetableWithHotKey)) + ns->spawn.IsMercenary = 1; + } + else + { + //NPC cast swarm pets should still be targetable with F8. + ns->spawn.IsMercenary = 0; + } + + SetTempPet(true); //Simple mob flag for checking if temp pet + swarmOwner->SetTempPetsActive(true); //Necessary fail safe flag set if mob ever had a swarm pet to ensure they are removed. + swarmOwner->SetTempPetCount(swarmOwner->GetTempPetCount() + 1); + + //Not recommended if using above (However, this will work better on older clients). + if (RuleB(Pets, UnTargetableSwarmPet)) + { + ns->spawn.bodytype = 11; + if(!IsCharmed() && swarmOwner->IsClient()) + sprintf(ns->spawn.lastName, "%s's Pet", swarmOwner->GetName()); + } + } + else if(GetOwnerID()) + { + ns->spawn.is_pet = 1; + if (!IsCharmed()) + { + Client *client = entity_list.GetClientByID(GetOwnerID()); + if(client) + { + SetPetOwnerClient(true); + sprintf(ns->spawn.lastName, "%s's Pet", client->GetName()); + } + } + } + else + { + ns->spawn.is_pet = 0; + } +} + +void NPC::SetLevel(uint8 in_level, bool command) +{ + if(in_level > level) + SendLevelAppearance(); + level = in_level; + SendAppearancePacket(AT_WhoLevel, in_level); +} + +void NPC::ModifyNPCStat(const char *identifier, const char *newValue) +{ + std::string id = identifier; + std::string val = newValue; + for(int i = 0; i < id.length(); ++i) { + id[i] = std::tolower(id[i]); + } + + if(id == "ac") { AC = atoi(val.c_str()); return; } + else if(id == "str") { STR = atoi(val.c_str()); return; } + else if(id == "sta") { STA = atoi(val.c_str()); return; } + else if(id == "agi") { AGI = atoi(val.c_str()); return; } + else if(id == "dex") { DEX = atoi(val.c_str()); return; } + else if(id == "wis") { WIS = atoi(val.c_str()); CalcMaxMana(); return; } + 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 == "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; } + else if(id == "pr") { PR = atoi(val.c_str()); return; } + else if(id == "dr") { DR = atoi(val.c_str()); return; } + else if(id == "PhR") { PhR = atoi(val.c_str()); return; } + else if(id == "runspeed") { runspeed = (float)atof(val.c_str()); CalcBonuses(); return; } + else if(id == "special_attacks") { NPCSpecialAttacks(val.c_str(), 0, 1); return; } + else if(id == "special_abilities") { ProcessSpecialAbilities(val.c_str()); return; } + else if(id == "attack_speed") { attack_speed = (float)atof(val.c_str()); CalcBonuses(); return; } + else if(id == "atk") { ATK = atoi(val.c_str()); return; } + else if(id == "accuracy") { accuracy_rating = atoi(val.c_str()); return; } + else if(id == "avoidance") { avoidance_rating = atoi(val.c_str()); return; } + else if(id == "trackable") { trackable = atoi(val.c_str()); return; } + else if(id == "min_hit") { min_dmg = atoi(val.c_str()); return; } + else if(id == "max_hit") { max_dmg = atoi(val.c_str()); return; } + else if(id == "attack_count") { attack_count = atoi(val.c_str()); return; } + else if(id == "see_invis") { see_invis = atoi(val.c_str()); return; } + else if(id == "see_invis_undead") { see_invis_undead = atoi(val.c_str()); return; } + else if(id == "see_hide") { see_hide = atoi(val.c_str()); return; } + else if(id == "see_improved_hide") { see_improved_hide = atoi(val.c_str()); return; } + else if(id == "hp_regen") { hp_regen = atoi(val.c_str()); return; } + else if(id == "mana_regen") { mana_regen = atoi(val.c_str()); return; } + else if(id == "level") { SetLevel(atoi(val.c_str())); return; } + else if(id == "aggro") { pAggroRange = atof(val.c_str()); return; } + else if(id == "assist") { pAssistRange = atof(val.c_str()); return; } + else if(id == "slow_mitigation") { slow_mitigation = atoi(val.c_str()); return; } + else if(id == "loottable_id") { loottable_id = atof(val.c_str()); return; } + else if(id == "healscale") { healscale = atof(val.c_str()); return; } + else if(id == "spellscale") { spellscale = atof(val.c_str()); return; } +} + +void NPC::LevelScale() { + + uint8 random_level = (zone->random.Int(level, maxlevel)); + + float scaling = (((random_level / (float)level) - 1) * (scalerate / 100.0f)); + + // Compensate for scale rates at low levels so they don't add too much + uint8 scale_adjust = 1; + if(level > 0 && level <= 5) + scale_adjust = 10; + if(level > 5 && level <= 10) + scale_adjust = 5; + if(level > 10 && level <= 15) + scale_adjust = 3; + if(level > 15 && level <= 25) + scale_adjust = 2; + + base_hp += (int)(base_hp * scaling); + max_hp += (int)(max_hp * scaling); + cur_hp = max_hp; + STR += (int)(STR * scaling / scale_adjust); + STA += (int)(STA * scaling / scale_adjust); + AGI += (int)(AGI * scaling / scale_adjust); + DEX += (int)(DEX * scaling / scale_adjust); + INT += (int)(INT * scaling / scale_adjust); + WIS += (int)(WIS * scaling / scale_adjust); + CHA += (int)(CHA * scaling / scale_adjust); + if (MR) + MR += (int)(MR * scaling / scale_adjust); + if (CR) + CR += (int)(CR * scaling / scale_adjust); + if (DR) + DR += (int)(DR * scaling / scale_adjust); + if (FR) + FR += (int)(FR * scaling / scale_adjust); + if (PR) + PR += (int)(PR * scaling / scale_adjust); + + if (max_dmg) + { + max_dmg += (int)(max_dmg * scaling / scale_adjust); + min_dmg += (int)(min_dmg * scaling / scale_adjust); + } + + level = random_level; + + return; +} + +void NPC::CalcNPCResists() { + + if (!MR) + MR = (GetLevel() * 11)/10; + if (!CR) + CR = (GetLevel() * 11)/10; + if (!DR) + DR = (GetLevel() * 11)/10; + if (!FR) + FR = (GetLevel() * 11)/10; + if (!PR) + PR = (GetLevel() * 11)/10; + if (!Corrup) + Corrup = 15; + if (!PhR) + PhR = 10; + return; +} + +void NPC::CalcNPCRegen() { + + // Fix for lazy db-updaters (regen values left at 0) + if (GetCasterClass() != 'N' && mana_regen == 0) + mana_regen = (GetLevel() / 10) + 4; + else if(mana_regen < 0) + mana_regen = 0; + else + mana_regen = mana_regen; + + // Gives low end monsters no regen if set to 0 in database. Should make low end monsters killable + // Might want to lower this to /5 rather than 10. + if(hp_regen == 0) + { + if(GetLevel() <= 6) + hp_regen = 1; + else if(GetLevel() > 6 && GetLevel() <= 10) + hp_regen = 2; + else if(GetLevel() > 10 && GetLevel() <= 15) + hp_regen = 3; + else if(GetLevel() > 15 && GetLevel() <= 20) + hp_regen = 5; + else if(GetLevel() > 20 && GetLevel() <= 30) + hp_regen = 7; + else if(GetLevel() > 30 && GetLevel() <= 35) + hp_regen = 9; + else if(GetLevel() > 35 && GetLevel() <= 40) + hp_regen = 12; + else if(GetLevel() > 40 && GetLevel() <= 45) + hp_regen = 18; + else if(GetLevel() > 45 && GetLevel() <= 50) + hp_regen = 21; + else + hp_regen = 30; + } else if(hp_regen < 0) { + hp_regen = 0; + } else + hp_regen = hp_regen; + + return; +} + +void NPC::CalcNPCDamage() { + + int AC_adjust=12; + + if (GetLevel() >= 66) { + if (min_dmg==0) + min_dmg = 220; + if (max_dmg==0) + max_dmg = ((((99000)*(GetLevel()-64))/400)*AC_adjust/10); + } + else if (GetLevel() >= 60 && GetLevel() <= 65){ + if(min_dmg==0) + min_dmg = (GetLevel()+(GetLevel()/3)); + if(max_dmg==0) + max_dmg = (GetLevel()*3)*AC_adjust/10; + } + else if (GetLevel() >= 51 && GetLevel() <= 59){ + if(min_dmg==0) + min_dmg = (GetLevel()+(GetLevel()/3)); + if(max_dmg==0) + max_dmg = (GetLevel()*3)*AC_adjust/10; + } + else if (GetLevel() >= 40 && GetLevel() <= 50) { + if (min_dmg==0) + min_dmg = GetLevel(); + if(max_dmg==0) + max_dmg = (GetLevel()*3)*AC_adjust/10; + } + else if (GetLevel() >= 28 && GetLevel() <= 39) { + if (min_dmg==0) + min_dmg = GetLevel() / 2; + if (max_dmg==0) + max_dmg = ((GetLevel()*2)+2)*AC_adjust/10; + } + else if (GetLevel() <= 27) { + if (min_dmg==0) + min_dmg=1; + if (max_dmg==0) + max_dmg = (GetLevel()*2)*AC_adjust/10; + } + + int32 clfact = GetClassLevelFactor(); + min_dmg = (min_dmg * clfact) / 220; + max_dmg = (max_dmg * clfact) / 220; + + return; +} + + +uint32 NPC::GetSpawnPointID() const +{ + if(respawn2) + { + return respawn2->GetID(); + } + return 0; +} + +void NPC::NPCSlotTexture(uint8 slot, uint16 texture) +{ + if (slot == 7) { + d_meele_texture1 = texture; + } + else if (slot == 8) { + d_meele_texture2 = texture; + } + else if (slot < 6) { + // Reserved for texturing individual armor slots + } + return; +} + +uint32 NPC::GetSwarmOwner() +{ + if(GetSwarmInfo() != nullptr) + { + return GetSwarmInfo()->owner_id; + } + return 0; +} + +uint32 NPC::GetSwarmTarget() +{ + if(GetSwarmInfo() != nullptr) + { + return GetSwarmInfo()->target; + } + return 0; +} + +void NPC::SetSwarmTarget(int target_id) +{ + if(GetSwarmInfo() != nullptr) + { + GetSwarmInfo()->target = target_id; + } + return; +} + +int32 NPC::CalcMaxMana() { + if(npc_mana == 0) { + switch (GetCasterClass()) { + case 'I': + max_mana = (((GetINT()/2)+1) * GetLevel()) + spellbonuses.Mana + itembonuses.Mana; + break; + case 'W': + max_mana = (((GetWIS()/2)+1) * GetLevel()) + spellbonuses.Mana + itembonuses.Mana; + break; + case 'N': + default: + max_mana = 0; + break; + } + if (max_mana < 0) { + max_mana = 0; + } + + return max_mana; + } else { + switch (GetCasterClass()) { + case 'I': + max_mana = npc_mana + spellbonuses.Mana + itembonuses.Mana; + break; + case 'W': + max_mana = npc_mana + spellbonuses.Mana + itembonuses.Mana; + break; + case 'N': + default: + max_mana = 0; + break; + } + if (max_mana < 0) { + max_mana = 0; + } + + return max_mana; + } +} + +void NPC::SignalNPC(int _signal_id) +{ + signal_q.push_back(_signal_id); +} + +NPC_Emote_Struct* NPC::GetNPCEmote(uint16 emoteid, uint8 event_) { + LinkedListIterator iterator(zone->NPCEmoteList); + iterator.Reset(); + while(iterator.MoreElements()) + { + NPC_Emote_Struct* nes = iterator.GetData(); + if (emoteid == nes->emoteid && event_ == nes->event_) { + return (nes); + } + iterator.Advance(); + } + return (nullptr); +} + +void NPC::DoNPCEmote(uint8 event_, uint16 emoteid) +{ + if(this == nullptr || emoteid == 0) + { + return; + } + + NPC_Emote_Struct* nes = GetNPCEmote(emoteid,event_); + if(nes == nullptr) + { + return; + } + + if(emoteid == nes->emoteid) + { + if(nes->type == 1) + this->Emote("%s",nes->text); + else if(nes->type == 2) + this->Shout("%s",nes->text); + else if(nes->type == 3) + entity_list.MessageClose_StringID(this, true, 200, 10, GENERIC_STRING, nes->text); + else + this->Say("%s",nes->text); + } +} + +bool NPC::CanTalk() +{ + //Races that should be able to talk. (Races up to Titanium) + + uint16 TalkRace[473] = + {1,2,3,4,5,6,7,8,9,10,11,12,0,0,15,16,0,18,19,20,0,0,23,0,25,0,0,0,0,0,0, + 32,0,0,0,0,0,0,39,40,0,0,0,44,0,0,0,0,49,0,51,0,53,54,55,56,57,58,0,0,0, + 62,0,64,65,66,67,0,0,70,71,0,0,0,0,0,77,78,79,0,81,82,0,0,0,86,0,0,0,90, + 0,92,93,94,95,0,0,98,99,0,101,0,103,0,0,0,0,0,0,110,111,112,0,0,0,0,0,0, + 0,0,0,0,123,0,0,126,0,128,0,130,131,0,0,0,0,136,137,0,139,140,0,0,0,144, + 0,0,0,0,0,150,151,152,153,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,183,184,0,0,187,188,189,0,0,0,0,0,195,196,0,198,0,0,0,202,0, + 0,205,0,0,208,0,0,0,0,0,0,0,0,217,0,219,0,0,0,0,0,0,226,0,0,229,230,0,0, + 0,0,235,236,0,238,239,240,241,242,243,244,0,246,247,0,0,0,251,0,0,254,255, + 256,257,0,0,0,0,0,0,0,0,266,267,0,0,270,271,0,0,0,0,0,277,278,0,0,0,0,283, + 284,0,286,0,288,289,290,0,0,0,0,295,296,297,298,299,300,0,0,0,304,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,320,0,322,323,324,325,0,0,0,0,330,331,332,333,334,335, + 336,337,338,339,340,341,342,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,359,360,361,362, + 0,364,365,366,0,368,369,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,385,386,0,0,0,0,0,392, + 393,394,395,396,397,398,0,400,402,0,0,0,0,406,0,408,0,0,411,0,413,0,0,0,417, + 0,0,420,0,0,0,0,425,0,0,0,0,0,0,0,433,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,458,0,0,0,0,0,0,0,0,467,0,0,470,0,0,473}; + + int talk_check = TalkRace[GetRace() - 1]; + + if (TalkRace[GetRace() - 1] > 0) + return true; + + return false; +} + +//this is called with 'this' as the mob being looked at, and +//iOther the mob who is doing the looking. It should figure out +//what iOther thinks about 'this' +FACTION_VALUE NPC::GetReverseFactionCon(Mob* iOther) { + iOther = iOther->GetOwnerOrSelf(); + int primaryFaction= iOther->GetPrimaryFaction(); + + //I am pretty sure that this special faction call is backwards + //and should be iOther->GetSpecialFactionCon(this) + if (primaryFaction < 0) + return GetSpecialFactionCon(iOther); + + if (primaryFaction == 0) + return FACTION_INDIFFERENT; + + //if we are a pet, use our owner's faction stuff + Mob *own = GetOwner(); + if (own != nullptr) + return own->GetReverseFactionCon(iOther); + + //make sure iOther is an npc + //also, if we dont have a faction, then they arnt gunna think anything of us either + if(!iOther->IsNPC() || GetPrimaryFaction() == 0) + return(FACTION_INDIFFERENT); + + //if we get here, iOther is an NPC too + + //otherwise, employ the npc faction stuff + //so we need to look at iOther's faction table to see + //what iOther thinks about our primary faction + return(iOther->CastToNPC()->CheckNPCFactionAlly(GetPrimaryFaction())); +} + +//Look through our faction list and return a faction con based +//on the npc_value for the other person's primary faction in our list. +FACTION_VALUE NPC::CheckNPCFactionAlly(int32 other_faction) { + std::list::iterator cur,end; + cur = faction_list.begin(); + end = faction_list.end(); + for(; cur != end; ++cur) { + struct NPCFaction* fac = *cur; + if ((int32)fac->factionID == other_faction) { + if (fac->npc_value > 0) + return FACTION_ALLY; + else if (fac->npc_value < 0) + return FACTION_SCOWLS; + else + return FACTION_INDIFFERENT; + } + } + return FACTION_INDIFFERENT; +} + +bool NPC::IsFactionListAlly(uint32 other_faction) { + return(CheckNPCFactionAlly(other_faction) == FACTION_ALLY); +} + +int NPC::GetScore() +{ + int lv = std::min(70, (int)GetLevel()); + int basedmg = (lv*2)*(1+(lv / 100)) - (lv / 2); + int minx = 0; + int basehp = 0; + int hpcontrib = 0; + int dmgcontrib = 0; + int spccontrib = 0; + int hp = GetMaxHP(); + int mindmg = min_dmg; + int maxdmg = max_dmg; + int final; + + if(lv < 46) + { + minx = static_cast (ceil( ((lv - (lv / 10.0)) - 1.0) )); + basehp = (lv * 10) + (lv * lv); + } + else + { + minx = static_cast (ceil( ((lv - (lv / 10.0)) - 1.0) - (( lv - 45.0 ) / 2.0) )); + basehp = (lv * 10) + ((lv * lv) * 4); + } + + if(hp > basehp) + { + hpcontrib = static_cast (((hp / static_cast (basehp)) * 1.5)); + if(hpcontrib > 5) { hpcontrib = 5; } + + if(maxdmg > basedmg) + { + dmgcontrib = static_cast (ceil( ((maxdmg / basedmg) * 1.5) )); + } + + if(HasNPCSpecialAtk("E")) { spccontrib++; } //Enrage + if(HasNPCSpecialAtk("F")) { spccontrib++; } //Flurry + if(HasNPCSpecialAtk("R")) { spccontrib++; } //Rampage + if(HasNPCSpecialAtk("r")) { spccontrib++; } //Area Rampage + if(HasNPCSpecialAtk("S")) { spccontrib++; } //Summon + if(HasNPCSpecialAtk("T")) { spccontrib += 2; } //Triple + if(HasNPCSpecialAtk("Q")) { spccontrib += 3; } //Quad + if(HasNPCSpecialAtk("U")) { spccontrib += 5; } //Unslowable + if(HasNPCSpecialAtk("L")) { spccontrib++; } //Innate Dual Wield + } + + if(npc_spells_id > 12) + { + if(lv < 16) + spccontrib++; + else + spccontrib += static_cast (floor(lv/15.0)); + } + + final = minx + hpcontrib + dmgcontrib + spccontrib; + final = std::max(1, final); + final = std::min(100, final); + return(final); +} + +uint32 NPC::GetSpawnKillCount() +{ + uint32 sid = GetSpawnPointID(); + + if(sid > 0) + { + return(zone->GetSpawnKillCount(sid)); + } + + return(0); +} + +void NPC::DoQuestPause(Mob *other) { + if(IsMoving() && !IsOnHatelist(other)) { + PauseWandering(RuleI(NPC, SayPauseTimeInSec)); + FaceTarget(other); + } else if(!IsMoving()) { + FaceTarget(other); + } + +} + +void NPC::DepopSwarmPets() +{ + if (GetSwarmInfo()) { + if (GetSwarmInfo()->duration->Check(false)){ + Mob* owner = entity_list.GetMobID(GetSwarmInfo()->owner_id); + if (owner) + owner->SetTempPetCount(owner->GetTempPetCount() - 1); + + Depop(); + return; + } + + //This is only used for optional quest or rule derived behavior now if you force a temp pet on a specific target. + if (GetSwarmInfo()->target) { + Mob *targMob = entity_list.GetMob(GetSwarmInfo()->target); + if(!targMob || (targMob && targMob->IsCorpse())){ + Mob* owner = entity_list.GetMobID(GetSwarmInfo()->owner_id); + if (owner) + owner->SetTempPetCount(owner->GetTempPetCount() - 1); + + Depop(); + return; + } + } + } +} diff --git a/zone/npc.h b/zone/npc.h index 8db5514de..e51d5d178 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -18,20 +18,17 @@ #ifndef NPC_H #define NPC_H -class NPC; -#include "zonedb.h" -#include "mob.h" -//#include "spawn.h" - -#include -#include - -#include "spawn2.h" -#include "../common/loottable.h" -#include "zonedump.h" -#include "qglobals.h" #include "../common/rulesys.h" +#include "mob.h" +#include "qglobals.h" +#include "zonedb.h" +#include "zonedump.h" + +#include +#include + + #ifdef _WINDOWS #define M_PI 3.141592 #endif @@ -88,8 +85,12 @@ struct AISpellsVar_Struct { uint8 idle_beneficial_chance; }; - class AA_SwarmPetInfo; +class Client; +class Group; +class Raid; +class Spawn2; +struct Item_Struct; class NPC : public Mob { @@ -353,8 +354,9 @@ public: const bool GetCombatEvent() const { return combat_event; } void SetCombatEvent(bool b) { combat_event = b; } - //The corpse we make can only be looted by people who got credit for the kill + /* Only allows players that killed corpse to loot */ const bool HasPrivateCorpse() const { return NPCTypedata->private_corpse; } + const bool IsUnderwaterOnly() const { return NPCTypedata->underwater; } const char* GetRawNPCTypeName() const { return NPCTypedata->name; } diff --git a/zone/object.cpp b/zone/object.cpp index 0201434c0..b1acc53c9 100644 --- a/zone/object.cpp +++ b/zone/object.cpp @@ -441,8 +441,8 @@ void Object::RandomSpawn(bool send_packet) { if(!m_ground_spawn) return; - m_data.x = MakeRandomFloat(m_min_x, m_max_x); - m_data.y = MakeRandomFloat(m_min_y, m_max_y); + m_data.x = zone->random.Real(m_min_x, m_max_x); + m_data.y = zone->random.Real(m_min_y, m_max_y); respawn_timer.Disable(); if(send_packet) { diff --git a/zone/pathing.cpp b/zone/pathing.cpp index 016e7afe1..c71cc3ead 100644 --- a/zone/pathing.cpp +++ b/zone/pathing.cpp @@ -1346,7 +1346,7 @@ PathNode* PathManager::FindPathNodeByCoordinates(float x, float y, float z) int PathManager::GetRandomPathNode() { - return MakeRandomInt(0, Head.PathNodeCount - 1); + return zone->random.Int(0, Head.PathNodeCount - 1); } diff --git a/zone/perl_player_corpse.cpp b/zone/perl_player_corpse.cpp index af5c79f16..675ee9697 100644 --- a/zone/perl_player_corpse.cpp +++ b/zone/perl_player_corpse.cpp @@ -208,7 +208,7 @@ XS(XS_Corpse_GetDBID) if(THIS == nullptr) Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); - RETVAL = THIS->GetDBID(); + RETVAL = THIS->GetCorpseDBID(); XSprePUSH; PUSHu((UV)RETVAL); } XSRETURN(1); @@ -662,7 +662,7 @@ XS(XS_Corpse_CompleteRezz) if(THIS == nullptr) Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); - THIS->CompleteRezz(); + THIS->CompleteResurrection(); } XSRETURN_EMPTY; } @@ -687,7 +687,7 @@ XS(XS_Corpse_CanMobLoot) if(THIS == nullptr) Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); - RETVAL = THIS->CanMobLoot(charid); + RETVAL = THIS->CanPlayerLoot(charid); ST(0) = boolSV(RETVAL); sv_2mortal(ST(0)); } @@ -723,7 +723,7 @@ XS(XS_Corpse_AllowMobLoot) if(them == nullptr) Perl_croak(aTHX_ "them is nullptr, avoiding crash."); - THIS->AllowMobLoot(them, slot); + THIS->AllowPlayerLoot(them, slot); } XSRETURN_EMPTY; } diff --git a/zone/petitions.cpp b/zone/petitions.cpp index ba57fe7f2..ad38bc04d 100644 --- a/zone/petitions.cpp +++ b/zone/petitions.cpp @@ -16,10 +16,6 @@ Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org) Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "../common/debug.h" -#include -#include -#include -#include #include #ifdef _WINDOWS #include @@ -32,16 +28,12 @@ Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org) #define strncasecmp _strnicmp #define strcasecmp _stricmp #endif -#include "../common/string_util.h" -#include "../common/packet_functions.h" -#include "../common/packet_dump.h" -#include "../common/packet_dump_file.h" -#include "../common/emu_opcodes.h" + + #include "../common/eq_packet_structs.h" #include "../common/servertalk.h" +#include "../common/string_util.h" #include "entity.h" -#include "masterentity.h" - #include "petitions.h" #include "worldserver.h" @@ -49,7 +41,6 @@ PetitionList petition_list; extern WorldServer worldserver; - void Petition::SendPetitionToPlayer(Client* clientto) { EQApplicationPacket* outapp = new EQApplicationPacket(OP_PetitionCheckout,sizeof(Petition_Struct)); Petition_Struct* pet = (Petition_Struct*) outapp->pBuffer; diff --git a/zone/petitions.h b/zone/petitions.h index 4b68362e0..604d4ddcc 100644 --- a/zone/petitions.h +++ b/zone/petitions.h @@ -19,11 +19,13 @@ #define PETITIONS_H #include "../common/linked_list.h" -#include "../common/types.h" -#include "zonedb.h" -#include "client.h" -#include "../common/mutex.h" #include "../common/misc_functions.h" +#include "../common/mutex.h" +#include "../common/types.h" +#include "client.h" +#include "zonedb.h" + +class Client; class Petition { diff --git a/zone/pets.cpp b/zone/pets.cpp index 8556bb620..0a7266809 100644 --- a/zone/pets.cpp +++ b/zone/pets.cpp @@ -15,30 +15,26 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + #include "../common/debug.h" +#include "../common/misc_functions.h" #include "../common/spdat.h" -#include "masterentity.h" -#include "../common/packet_dump.h" -#include "../common/moremath.h" -#include "../common/item.h" -#include "zonedb.h" -#include "worldserver.h" -#include "../common/skills.h" -#include "../common/bodytypes.h" -#include "../common/classes.h" #include "../common/string_util.h" +#include "../common/types.h" + +#include "entity.h" +#include "client.h" +#include "mob.h" + #include "pets.h" -#include -#include +#include "worldserver.h" +#include "zonedb.h" + #ifndef WIN32 #include #include "../common/unix.h" #endif -#include "string_ids.h" - -/////////////////////////////////////////////////////////////////////////////// -// pet related functions const char *GetRandPetName() { @@ -113,7 +109,7 @@ const char *GetRandPetName() "Zibann","Zibarer","Zibartik","Zibekn","Zibn","Zibobn","Zobaner","Zobann", "Zobarn","Zober","Zobn","Zonanab","Zonaner","Zonann","Zonantik","Zonarer", "Zonartik","Zonobn","Zonobtik","Zontik","Ztik" }; - int r = MakeRandomInt(0, (sizeof(petnames)/sizeof(const char *))-1); + int r = zone->random.Int(0, (sizeof(petnames)/sizeof(const char *))-1); printf("Pet being created: %s\n",petnames[r]); // DO NOT COMMENT THIS OUT! return petnames[r]; } diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index d49528163..eb6ddcce8 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -16,66 +16,28 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -/* - -Assuming you want to add a new perl quest function named joe -that takes 1 integer argument.... - -1. Add the prototype to the quest manager: -questmgr.h: add (~line 50) - void joe(int arg); - -2. Define the actual function in questmgr.cpp: -void QuestManager::joe(int arg) { - //... do something -} - -3. Copy one of the XS routines in perlparser.cpp, preferably - one with the same number of arguments as your routine. Rename - as needed. - Finally, add your routine to the list at the bottom of perlparser.cpp - - -4. -If you want it to work in old mode perl and .qst, edit parser.cpp -Parser::ExCommands (~line 777) - else if (!strcmp(command,"joe")) { - quest_manager.joe(atoi(arglist[0])); - } - -And then at then end of embparser.cpp, add: -"sub joe{push(@cmd_queue,{func=>'joe',args=>join(',',@_)});}" - - - -*/ - -#include "../common/debug.h" -#include "entity.h" -#include "masterentity.h" -#include - -#include -#include -#include - -#include "worldserver.h" -#include "net.h" -#include "../common/skills.h" #include "../common/classes.h" -#include "../common/races.h" -#include "zonedb.h" +#include "../common/debug.h" +#include "../common/rulesys.h" +#include "../common/skills.h" #include "../common/spdat.h" -#include "../common/packet_functions.h" #include "../common/string_util.h" -#include "spawn2.h" -#include "zone.h" +#include "entity.h" #include "event_codes.h" #include "guild_mgr.h" -#include "../common/rulesys.h" +#include "net.h" #include "qglobals.h" -#include "quest_parser_collection.h" #include "queryserv.h" +#include "questmgr.h" +#include "quest_parser_collection.h" +#include "spawn2.h" +#include "worldserver.h" +#include "zone.h" +#include "zonedb.h" +#include +#include +#include + #ifdef BOTS #include "bot.h" #endif @@ -85,9 +47,6 @@ extern Zone* zone; extern WorldServer worldserver; extern EntityList entity_list; -#include "questmgr.h" - -//declare our global instance QuestManager quest_manager; #define QuestManagerCurrentQuestVars() \ @@ -1781,7 +1740,7 @@ bool QuestManager::buryplayercorpse(uint32 char_id) if(corpse) { corpse->Save(); - corpse->DepopCorpse(); + corpse->DepopPlayerCorpse(); } else { diff --git a/zone/questmgr.h b/zone/questmgr.h index a46b7adcd..f1c559811 100644 --- a/zone/questmgr.h +++ b/zone/questmgr.h @@ -25,8 +25,10 @@ #include #include -class NPC; class Client; +class ItemInst; +class Mob; +class NPC; class QuestManager { struct running_quest { diff --git a/zone/spawn2.cpp b/zone/spawn2.cpp index f052980b7..fcbb9398a 100644 --- a/zone/spawn2.cpp +++ b/zone/spawn2.cpp @@ -109,7 +109,7 @@ uint32 Spawn2::resetTimer() if (variance_ != 0) { int var_over_2 = (variance_ * 1000) / 2; - rspawn = MakeRandomInt(rspawn - var_over_2, rspawn + var_over_2); + rspawn = zone->random.Int(rspawn - var_over_2, rspawn + var_over_2); //put a lower bound on it, not a lot of difference below 100, so set that as the bound. if(rspawn < 100) @@ -126,7 +126,7 @@ uint32 Spawn2::despawnTimer(uint32 despawn_timer) if (variance_ != 0) { int var_over_2 = (variance_ * 1000) / 2; - dspawn = MakeRandomInt(dspawn - var_over_2, dspawn + var_over_2); + dspawn = zone->random.Int(dspawn - var_over_2, dspawn + var_over_2); //put a lower bound on it, not a lot of difference below 100, so set that as the bound. if(dspawn < 100) diff --git a/zone/spawngroup.cpp b/zone/spawngroup.cpp index 81d6c5d51..6943d9349 100644 --- a/zone/spawngroup.cpp +++ b/zone/spawngroup.cpp @@ -25,8 +25,10 @@ #include "zonedb.h" #include "../common/misc_functions.h" #include "../common/string_util.h" +#include "zone.h" extern EntityList entity_list; +extern Zone* zone; SpawnEntry::SpawnEntry( uint32 in_NPCType, int in_chance, uint8 in_npc_spawn_limit ) { NPCType = in_NPCType; @@ -77,7 +79,7 @@ uint32 SpawnGroup::GetNPCType() { int32 roll = 0; - roll = MakeRandomInt(0, totalchance-1); + roll = zone->random.Int(0, totalchance-1); cur = possible.begin(); end = possible.end(); diff --git a/zone/special_attacks.cpp b/zone/special_attacks.cpp index 963bf51a8..928353149 100644 --- a/zone/special_attacks.cpp +++ b/zone/special_attacks.cpp @@ -14,19 +14,18 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ + */ + + #include "../common/debug.h" -#include -#include -#include -#include - -#include "masterentity.h" -#include "string_ids.h" #include "../common/misc_functions.h" #include "../common/rulesys.h" #include "../common/string_util.h" +#include "masterentity.h" +#include "string_ids.h" +#include +#include int Mob::GetKickDamage() { @@ -139,9 +138,9 @@ void Mob::DoSpecialAttackDamage(Mob *who, SkillUseTypes skill, int32 max_damage, who->MeleeMitigation(this, max_damage, min_damage); - if(max_damage > 0) + if(max_damage > 0) CommonOutgoingHitSuccess(who, max_damage, skill); - + } who->AddToHateList(this, hate, 0, false); @@ -156,17 +155,17 @@ void Mob::DoSpecialAttackDamage(Mob *who, SkillUseTypes skill, int32 max_damage, int kb_chance = 25; kb_chance += kb_chance*(100-aabonuses.SpecialAttackKBProc[0])/100; - if (MakeRandomInt(0, 99) < kb_chance) + if (zone->random.Roll(kb_chance)) SpellFinished(904, who, 10, 0, -1, spells[904].ResistDiff); //who->Stun(100); Kayen: This effect does not stun on live, it only moves the NPC. } if (HasSkillProcs()) TrySkillProc(who, skill, ReuseTime*1000); - + if (max_damage > 0 && HasSkillProcSuccess()) TrySkillProc(who, skill, ReuseTime*1000, true); - + if(max_damage == -3 && !who->HasDied()) DoRiposte(who); } @@ -199,7 +198,7 @@ void Client::OPCombatAbility(const EQApplicationPacket *app) { SetAttackTimer(); ThrowingAttack(GetTarget()); if (CheckDoubleRangedAttack()) - RangedAttack(GetTarget(), true); + ThrowingAttack(GetTarget(), true); return; } //ranged attack (archery) @@ -257,7 +256,7 @@ void Client::OPCombatAbility(const EQApplicationPacket *app) { if(RuleB(Combat, UseIntervalAC)) ht = dmg = GetBashDamage(); else - ht = dmg = MakeRandomInt(1, GetBashDamage()); + ht = dmg = zone->random.Int(1, GetBashDamage()); } } @@ -296,8 +295,7 @@ void Client::OPCombatAbility(const EQApplicationPacket *app) { //Live parses show around 55% Triple 35% Double 10% Single, you will always get first hit. while(AtkRounds > 0) { - - if (GetTarget() && (AtkRounds == 1 || MakeRandomInt(0,100) < 75)){ + if (GetTarget() && (AtkRounds == 1 || zone->random.Roll(75))) { DoSpecialAttackDamage(GetTarget(), SkillFrenzy, max_dmg, min_dmg, max_dmg , ReuseTime, true); } AtkRounds--; @@ -334,7 +332,7 @@ void Client::OPCombatAbility(const EQApplicationPacket *app) { if(RuleB(Combat, UseIntervalAC)) ht = dmg = GetKickDamage(); else - ht = dmg = MakeRandomInt(1, GetKickDamage()); + ht = dmg = zone->random.Int(1, GetKickDamage()); } } @@ -349,18 +347,18 @@ void Client::OPCombatAbility(const EQApplicationPacket *app) { //Live AA - Technique of Master Wu int wuchance = itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack; if (wuchance) { - if (wuchance >= 100 || wuchance > MakeRandomInt(0, 99)) { + if (wuchance >= 100 || zone->random.Roll(wuchance)) { int MonkSPA [5] = { SkillFlyingKick, SkillDragonPunch, SkillEagleStrike, SkillTigerClaw, SkillRoundKick }; int extra = 1; // always 1/4 of the double attack chance, 25% at rank 5 (100/4) - if (wuchance / 4 > MakeRandomInt(0, 99)) + if (zone->random.Roll(wuchance / 4)) extra++; // They didn't add a string ID for this. std::string msg = StringFormat("The spirit of Master Wu fills you! You gain %d additional attack(s).", extra); // live uses 400 here -- not sure if it's the best for all clients though SendColoredText(400, msg); while (extra) { - MonkSpecialAttack(GetTarget(), MonkSPA[MakeRandomInt(0, 4)]); + MonkSpecialAttack(GetTarget(), MonkSPA[zone->random.Int(0, 4)]); extra--; } } @@ -485,7 +483,7 @@ int Mob::MonkSpecialAttack(Mob* other, uint8 unchecked_type) if(RuleB(Combat, UseIntervalAC)) ht = ndamage = max_dmg; else - ht = ndamage = MakeRandomInt(min_dmg, max_dmg); + ht = ndamage = zone->random.Int(min_dmg, max_dmg); } else{ ht = max_dmg; @@ -526,15 +524,14 @@ void Mob::TryBackstab(Mob *other, int ReuseTime) { //Live AA - Seized Opportunity int FrontalBSChance = itembonuses.FrontalBackstabChance + spellbonuses.FrontalBackstabChance + aabonuses.FrontalBackstabChance; - if (FrontalBSChance && (FrontalBSChance > MakeRandomInt(0, 100))) + if (FrontalBSChance && zone->random.Roll(FrontalBSChance)) bCanFrontalBS = true; } - - if (bIsBehind || bCanFrontalBS){ // Player is behind other OR can do Frontal Backstab - if (bCanFrontalBS) + if (bIsBehind || bCanFrontalBS){ // Player is behind other OR can do Frontal Backstab + if (bCanFrontalBS) CastToClient()->Message(0,"Your fierce attack is executed with such grace, your target did not see it coming!"); - + RogueBackstab(other,false,ReuseTime); if (level > 54) { if(IsClient() && CastToClient()->CheckDoubleAttack(false)) @@ -542,14 +539,14 @@ void Mob::TryBackstab(Mob *other, int ReuseTime) { if(other->GetHP() > 0) RogueBackstab(other,false,ReuseTime); - if (tripleChance && other->GetHP() > 0 && tripleChance > MakeRandomInt(0, 100)) + if (tripleChance && other->GetHP() > 0 && zone->random.Roll(tripleChance)) RogueBackstab(other,false,ReuseTime); } } - + if(IsClient()) CastToClient()->CheckIncreaseSkill(SkillBackstab, other, 10); - + } //Live AA - Chaotic Backstab else if(aabonuses.FrontalBackstabMinDmg || itembonuses.FrontalBackstabMinDmg || spellbonuses.FrontalBackstabMinDmg) { @@ -563,7 +560,7 @@ void Mob::TryBackstab(Mob *other, int ReuseTime) { if(other->GetHP() > 0) RogueBackstab(other,true, ReuseTime); - if (tripleChance && other->GetHP() > 0 && tripleChance > MakeRandomInt(0, 100)) + if (tripleChance && other->GetHP() > 0 && zone->random.Roll(tripleChance)) RogueBackstab(other,false,ReuseTime); } @@ -642,7 +639,7 @@ void Mob::RogueBackstab(Mob* other, bool min_damage, int ReuseTime) if(RuleB(Combat, UseIntervalAC)) ndamage = max_hit; else - ndamage = MakeRandomInt(min_hit, max_hit); + ndamage = zone->random.Int(min_hit, max_hit); } } } @@ -679,7 +676,8 @@ void Mob::RogueAssassinate(Mob* other) void Client::RangedAttack(Mob* other, bool CanDoubleAttack) { //conditions to use an attack checked before we are called - + if (!other) + return; //make sure the attack and ranged timers are up //if the ranged timer is disabled, then they have no ranged weapon and shouldent be attacking anyhow if(!CanDoubleAttack && ((attack_timer.Enabled() && !attack_timer.Check(false)) || (ranged_timer.Enabled() && !ranged_timer.Check()))) { @@ -719,7 +717,7 @@ void Client::RangedAttack(Mob* other, bool CanDoubleAttack) { return; } - mlog(COMBAT__RANGED, "Shooting %s with bow %s (%d) and arrow %s (%d)", GetTarget()->GetName(), RangeItem->Name, RangeItem->ID, AmmoItem->Name, AmmoItem->ID); + mlog(COMBAT__RANGED, "Shooting %s with bow %s (%d) and arrow %s (%d)", other->GetName(), RangeItem->Name, RangeItem->ID, AmmoItem->Name, AmmoItem->ID); //look for ammo in inventory if we only have 1 left... if(Ammo->GetCharges() == 1) { @@ -766,19 +764,21 @@ void Client::RangedAttack(Mob* other, bool CanDoubleAttack) { } } - float range = RangeItem->Range + AmmoItem->Range + 5.0f; //Fudge it a little, client will let you hit something at 0 0 0 when you are at 205 0 0 + float range = RangeItem->Range + AmmoItem->Range + GetRangeDistTargetSizeMod(GetTarget()); mlog(COMBAT__RANGED, "Calculated bow range to be %.1f", range); range *= range; - if(DistNoRootNoZ(*GetTarget()) > range) { - mlog(COMBAT__RANGED, "Ranged attack out of range... client should catch this. (%f > %f).\n", DistNoRootNoZ(*GetTarget()), range); - //target is out of range, client does a message + float dist = DistNoRoot(*other); + if(dist > range) { + mlog(COMBAT__RANGED, "Ranged attack out of range... client should catch this. (%f > %f).\n", dist, range); + Message_StringID(13,TARGET_OUT_OF_RANGE);//Client enforces range and sends the message, this is a backup just incase. return; } - else if(DistNoRootNoZ(*GetTarget()) < (RuleI(Combat, MinRangedAttackDist)*RuleI(Combat, MinRangedAttackDist))){ + else if(dist < (RuleI(Combat, MinRangedAttackDist)*RuleI(Combat, MinRangedAttackDist))){ + Message_StringID(15,RANGED_TOO_CLOSE);//Client enforces range and sends the message, this is a backup just incase. return; } - if(!IsAttackAllowed(GetTarget()) || + if(!IsAttackAllowed(other) || IsCasting() || IsSitting() || (DivineAura() && !GetGM()) || @@ -789,13 +789,13 @@ void Client::RangedAttack(Mob* other, bool CanDoubleAttack) { return; } - SendItemAnimation(GetTarget(), AmmoItem, SkillArchery); - DoArcheryAttackDmg(GetTarget(), RangeWeapon, Ammo); + //Shoots projectile and/or applies the archery damage + DoArcheryAttackDmg(other, RangeWeapon, Ammo,0,0,0,0,0,0, AmmoItem, ammo_slot); //EndlessQuiver AA base1 = 100% Chance to avoid consumption arrow. int ChanceAvoidConsume = aabonuses.ConsumeProjectile + itembonuses.ConsumeProjectile + spellbonuses.ConsumeProjectile; - if (!ChanceAvoidConsume || (ChanceAvoidConsume < 100 && MakeRandomInt(0,99) > ChanceAvoidConsume)){ + if (!ChanceAvoidConsume || (ChanceAvoidConsume < 100 && zone->random.Int(0,99) > ChanceAvoidConsume)){ DeleteItemInInventory(ammo_slot, 1, true); mlog(COMBAT__RANGED, "Consumed one arrow from slot %d", ammo_slot); } else { @@ -806,147 +806,206 @@ void Client::RangedAttack(Mob* other, bool CanDoubleAttack) { CommonBreakInvisible(); } -void Mob::DoArcheryAttackDmg(Mob* other, const ItemInst* RangeWeapon, const ItemInst* Ammo, uint16 weapon_damage, int16 chance_mod, int16 focus, int ReuseTime) { - if (!CanDoSpecialAttack(other)) +void Mob::DoArcheryAttackDmg(Mob* other, const ItemInst* RangeWeapon, const ItemInst* Ammo, uint16 weapon_damage, int16 chance_mod, int16 focus, int ReuseTime, + uint32 range_id, uint32 ammo_id, const Item_Struct *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)))) + { return; + } - if (!other->CheckHitChance(this, SkillArchery, MainPrimary, chance_mod)) { + const ItemInst* _RangeWeapon = nullptr; + const ItemInst* _Ammo = nullptr; + const Item_Struct* ammo_lost = nullptr; + + /* + If LaunchProjectile is false this function will do archery damage on target, + otherwise it will shoot the projectile at the target, once the projectile hits target + this function is then run again to do the damage portion + */ + bool LaunchProjectile = false; + bool ProjectileImpact = false; + bool ProjectileMiss = false; + + if (RuleB(Combat, ProjectileDmgOnImpact)){ + + if (AmmoItem) + LaunchProjectile = true; + else{ + /* + Item sync check on projectile landing. + Weapon damage is already calculated so this only affects procs! + Ammo proc check will use database to find proc if you used up your last ammo. + If you change range item mid projectile flight, you loose your chance to proc from bow (Deal with it!). + */ + + if (!RangeWeapon && !Ammo && range_id && ammo_id){ + + ProjectileImpact = true; + + if (weapon_damage == 0) + ProjectileMiss = true; //This indicates that MISS was originally calculated. + + if (IsClient()){ + + _RangeWeapon = CastToClient()->m_inv[MainRange]; + if (_RangeWeapon && !_RangeWeapon->GetItem() && _RangeWeapon->GetItem()->ID == range_id) + RangeWeapon = _RangeWeapon; + + _Ammo = CastToClient()->m_inv[AmmoSlot]; + if (_Ammo && _Ammo->GetItem() && _Ammo->GetItem()->ID == ammo_id) + Ammo = _Ammo; + else + ammo_lost = database.GetItem(ammo_id); + } + } + } + } + else if (AmmoItem) + SendItemAnimation(other, AmmoItem, SkillArchery); + + if (ProjectileMiss || (!ProjectileImpact && !other->CheckHitChance(this, SkillArchery, MainPrimary, chance_mod))) { mlog(COMBAT__RANGED, "Ranged attack missed %s.", other->GetName()); - other->Damage(this, 0, SPELL_UNKNOWN, SkillArchery); + + if (LaunchProjectile){ + TryProjectileAttack(other, AmmoItem, SkillArchery, 0, RangeWeapon, Ammo, AmmoSlot, speed); + return; + } + else + other->Damage(this, 0, SPELL_UNKNOWN, SkillArchery); } else { mlog(COMBAT__RANGED, "Ranged attack hit %s.", other->GetName()); + bool HeadShot = false; + uint32 HeadShot_Dmg = TryHeadShot(other, SkillArchery); + if (HeadShot_Dmg) + HeadShot = true; - bool HeadShot = false; - uint32 HeadShot_Dmg = TryHeadShot(other, SkillArchery); - if (HeadShot_Dmg) - HeadShot = true; + int32 hate = 0; + int32 TotalDmg = 0; + int16 WDmg = 0; + int16 ADmg = 0; + if (!weapon_damage){ + WDmg = GetWeaponDamage(other, RangeWeapon); + ADmg = GetWeaponDamage(other, Ammo); + } + else + WDmg = weapon_damage; - int32 TotalDmg = 0; - int16 WDmg = 0; - int16 ADmg = 0; - if (!weapon_damage){ - WDmg = GetWeaponDamage(other, RangeWeapon); - ADmg = GetWeaponDamage(other, Ammo); - } - else - WDmg = weapon_damage; + if (LaunchProjectile){//1: Shoot the Projectile once we calculate weapon damage. + TryProjectileAttack(other, AmmoItem, SkillArchery, WDmg, RangeWeapon, Ammo, AmmoSlot, speed); + return; + } - if (focus) //From FcBaseEffects - WDmg += WDmg*focus/100; + if (focus) //From FcBaseEffects + WDmg += WDmg*focus/100; - if((WDmg > 0) || (ADmg > 0)) { - if(WDmg < 0) - WDmg = 0; - if(ADmg < 0) - ADmg = 0; - uint32 MaxDmg = (RuleR(Combat, ArcheryBaseDamageBonus)*(WDmg+ADmg)*GetDamageTable(SkillArchery)) / 100; - int32 hate = ((WDmg+ADmg)); - - if (HeadShot) - MaxDmg = HeadShot_Dmg; - - uint16 bonusArcheryDamageModifier = aabonuses.ArcheryDamageModifier + itembonuses.ArcheryDamageModifier + spellbonuses.ArcheryDamageModifier; - - MaxDmg += MaxDmg*bonusArcheryDamageModifier / 100; - - mlog(COMBAT__RANGED, "Bow DMG %d, Arrow DMG %d, Max Damage %d.", WDmg, ADmg, MaxDmg); - - bool dobonus = false; - if(GetClass() == RANGER && GetLevel() > 50) - { - int bonuschance = RuleI(Combat, ArcheryBonusChance); - - bonuschance = mod_archery_bonus_chance(bonuschance, RangeWeapon); - - if( !RuleB(Combat, UseArcheryBonusRoll) || (MakeRandomInt(1, 100) < bonuschance) ) - { - if(RuleB(Combat, ArcheryBonusRequiresStationary)) - { - if(other->IsNPC() && !other->IsMoving() && !other->IsRooted()) - { - dobonus = true; - } - } - else - { - dobonus = true; - } - } - - if(dobonus) - { - MaxDmg *= 2; - hate *= 2; - MaxDmg = mod_archery_bonus_damage(MaxDmg, RangeWeapon); - - mlog(COMBAT__RANGED, "Ranger. Double damage success roll, doubling damage to %d", MaxDmg); - Message_StringID(MT_CritMelee, BOW_DOUBLE_DAMAGE); - } - } - - if (MaxDmg == 0) - MaxDmg = 1; - - if(RuleB(Combat, UseIntervalAC)) - TotalDmg = MaxDmg; - else - TotalDmg = MakeRandomInt(1, MaxDmg); - - int minDmg = 1; - if(GetLevel() > 25){ - //twice, for ammo and weapon - TotalDmg += (2*((GetLevel()-25)/3)); - minDmg += (2*((GetLevel()-25)/3)); - minDmg += minDmg * GetMeleeMinDamageMod_SE(SkillArchery) / 100; - hate += (2*((GetLevel()-25)/3)); - } - - if (!HeadShot) - other->AvoidDamage(this, TotalDmg, false); - - other->MeleeMitigation(this, TotalDmg, minDmg); - if(TotalDmg > 0) - { - ApplyMeleeDamageBonus(SkillArchery, TotalDmg); - TotalDmg += other->GetFcDamageAmtIncoming(this, 0, true, SkillArchery); - TotalDmg += (itembonuses.HeroicDEX / 10) + (TotalDmg * other->GetSkillDmgTaken(SkillArchery) / 100) + GetSkillDmgAmt(SkillArchery); - - TotalDmg = mod_archery_damage(TotalDmg, dobonus, RangeWeapon); - - TryCriticalHit(other, SkillArchery, TotalDmg); - other->AddToHateList(this, hate, 0, false); - CheckNumHitsRemaining(NUMHIT_OutgoingHitSuccess); - } - } - else - TotalDmg = -5; + if((WDmg > 0) || (ADmg > 0)) { + if(WDmg < 0) + WDmg = 0; + if(ADmg < 0) + ADmg = 0; + uint32 MaxDmg = (RuleR(Combat, ArcheryBaseDamageBonus)*(WDmg+ADmg)*GetDamageTable(SkillArchery)) / 100; + hate = ((WDmg+ADmg)); if (HeadShot) - entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, FATAL_BOW_SHOT, GetName()); - - other->Damage(this, TotalDmg, SPELL_UNKNOWN, SkillArchery); - - if (TotalDmg > 0 && HasSkillProcSuccess() && GetTarget() && other && !other->HasDied()){ - if (ReuseTime) - TrySkillProc(other, SkillArchery, ReuseTime); - else - TrySkillProc(other, SkillArchery, 0, true, MainRange); + MaxDmg = HeadShot_Dmg; + + uint16 bonusArcheryDamageModifier = aabonuses.ArcheryDamageModifier + itembonuses.ArcheryDamageModifier + spellbonuses.ArcheryDamageModifier; + + MaxDmg += MaxDmg*bonusArcheryDamageModifier / 100; + + mlog(COMBAT__RANGED, "Bow DMG %d, Arrow DMG %d, Max Damage %d.", WDmg, ADmg, MaxDmg); + + bool dobonus = false; + if(GetClass() == RANGER && GetLevel() > 50){ + int bonuschance = RuleI(Combat, ArcheryBonusChance); + bonuschance = mod_archery_bonus_chance(bonuschance, RangeWeapon); + + if( !RuleB(Combat, UseArcheryBonusRoll) || zone->random.Roll(bonuschance)){ + if(RuleB(Combat, ArcheryBonusRequiresStationary)){ + if(other->IsNPC() && !other->IsMoving() && !other->IsRooted()) + dobonus = true; + } + else + dobonus = true; + } + + if(dobonus){ + MaxDmg *= 2; + hate *= 2; + MaxDmg = mod_archery_bonus_damage(MaxDmg, RangeWeapon); + + mlog(COMBAT__RANGED, "Ranger. Double damage success roll, doubling damage to %d", MaxDmg); + Message_StringID(MT_CritMelee, BOW_DOUBLE_DAMAGE); + } } + + if (MaxDmg == 0) + MaxDmg = 1; + + if(RuleB(Combat, UseIntervalAC)) + TotalDmg = MaxDmg; + else + TotalDmg = zone->random.Int(1, MaxDmg); + + int minDmg = 1; + if(GetLevel() > 25){ + //twice, for ammo and weapon + TotalDmg += (2*((GetLevel()-25)/3)); + minDmg += (2*((GetLevel()-25)/3)); + minDmg += minDmg * GetMeleeMinDamageMod_SE(SkillArchery) / 100; + hate += (2*((GetLevel()-25)/3)); + } + + if (!HeadShot) + other->AvoidDamage(this, TotalDmg, false); + + other->MeleeMitigation(this, TotalDmg, minDmg); + if(TotalDmg > 0){ + CommonOutgoingHitSuccess(other, TotalDmg, SkillArchery); + TotalDmg = mod_archery_damage(TotalDmg, dobonus, RangeWeapon); + } + } + else + TotalDmg = -5; + + if (HeadShot) + entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, FATAL_BOW_SHOT, GetName()); + + other->AddToHateList(this, hate, 0, false); + other->Damage(this, TotalDmg, SPELL_UNKNOWN, SkillArchery); + + //Skill Proc Success + if (TotalDmg > 0 && HasSkillProcSuccess() && other && !other->HasDied()){ + if (ReuseTime) + TrySkillProc(other, SkillArchery, ReuseTime); + else + TrySkillProc(other, SkillArchery, 0, true, MainRange); + } } - //try proc on hits and misses - if((RangeWeapon != nullptr) && GetTarget() && other && !other->HasDied()){ + if (LaunchProjectile) + return;//Shouldn't reach this point, but just in case. + + //Weapon Proc + if(!RangeWeapon && other && !other->HasDied()) TryWeaponProc(RangeWeapon, other, MainRange); - } - //Arrow procs because why not? - if((Ammo != NULL) && GetTarget() && other && !other->HasDied()) - { - TryWeaponProc(Ammo, other, MainRange); - } + //Ammo Proc + if (ammo_lost) + TryWeaponProc(nullptr, ammo_lost, other, MainRange); + else if(Ammo && other && !other->HasDied()) + TryWeaponProc(Ammo, other, MainRange); - if (HasSkillProcs() && GetTarget() && other && !other->HasDied()){ + //Skill Proc + if (HasSkillProcs() && other && !other->HasDied()){ if (ReuseTime) TrySkillProc(other, SkillArchery, ReuseTime); else @@ -954,6 +1013,160 @@ void Mob::DoArcheryAttackDmg(Mob* other, const ItemInst* RangeWeapon, const Item } } +bool Mob::TryProjectileAttack(Mob* other, const Item_Struct *item, SkillUseTypes skillInUse, uint16 weapon_dmg, const ItemInst* RangeWeapon, const ItemInst* Ammo, int AmmoSlot, float speed){ + + if (!other) + return false; + + int slot = -1; + + //Make sure there is an avialable slot. + for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { + if (ProjectileAtk[i].target_id == 0){ + slot = i; + break; + } + } + + if (slot < 0) + return false; + + float speed_mod = speed * 0.45f; + + float distance = other->CalculateDistance(GetX(), GetY(), GetZ()); + float hit = 60.0f + (distance / speed_mod); //Calcuation: 60 = Animation Lag, 1.8 = Speed modifier for speed of (4) + + ProjectileAtk[slot].increment = 1; + ProjectileAtk[slot].hit_increment = static_cast(hit); //This projected hit time if target does NOT MOVE + ProjectileAtk[slot].target_id = other->GetID(); + ProjectileAtk[slot].wpn_dmg = weapon_dmg; + ProjectileAtk[slot].origin_x = GetX(); + ProjectileAtk[slot].origin_y = GetY(); + ProjectileAtk[slot].origin_z = GetZ(); + + if (RangeWeapon && RangeWeapon->GetItem()) + ProjectileAtk[slot].ranged_id = RangeWeapon->GetItem()->ID; + + if (Ammo && Ammo->GetItem()) + ProjectileAtk[slot].ammo_id = Ammo->GetItem()->ID; + + ProjectileAtk[slot].ammo_slot = 0; + ProjectileAtk[slot].skill = skillInUse; + ProjectileAtk[slot].speed_mod = speed_mod; + + SetProjectileAttack(true); + + if(item) + SendItemAnimation(other, item, skillInUse, speed); + else if (IsNPC()) + ProjectileAnimation(other, 0,false,speed,0,0,0,CastToNPC()->GetAmmoIDfile(),skillInUse); + + return true; +} + + +void Mob::ProjectileAttack() +{ + if (!HasProjectileAttack()) + return;; + + Mob* target = nullptr; + bool disable = true; + + for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { + + if (ProjectileAtk[i].increment == 0){ + continue; + } + + disable = false; + Mob* target = entity_list.GetMobID(ProjectileAtk[i].target_id); + + if (target && target->IsMoving()){ //Only recalculate hit increment if target moving + //Due to frequency that we need to check increment the targets position variables may not be updated even if moving. Do a simple check before calculating distance. + if (ProjectileAtk[i].tlast_x != target->GetX() || ProjectileAtk[i].tlast_y != target->GetY()){ + ProjectileAtk[i].tlast_x = target->GetX(); + ProjectileAtk[i].tlast_y = target->GetY(); + float distance = target->CalculateDistance(ProjectileAtk[i].origin_x, ProjectileAtk[i].origin_y, ProjectileAtk[i].origin_z); + float hit = 60.0f + (distance / ProjectileAtk[i].speed_mod); //Calcuation: 60 = Animation Lag, 1.8 = Speed modifier for speed of (4) + ProjectileAtk[i].hit_increment = static_cast(hit); + } + } + + if (ProjectileAtk[i].hit_increment <= ProjectileAtk[i].increment){ + + if (target){ + if (ProjectileAtk[i].skill == SkillArchery) + DoArcheryAttackDmg(target, nullptr, nullptr,ProjectileAtk[i].wpn_dmg,0,0,0,ProjectileAtk[i].ranged_id, ProjectileAtk[i].ammo_id, nullptr, ProjectileAtk[i].ammo_slot); + else if (ProjectileAtk[i].skill == SkillThrowing) + DoThrowingAttackDmg(target, nullptr, nullptr,ProjectileAtk[i].wpn_dmg,0,0,0, ProjectileAtk[i].ranged_id, ProjectileAtk[i].ammo_slot); + else if (ProjectileAtk[i].skill == SkillConjuration && IsValidSpell(ProjectileAtk[i].wpn_dmg)) + SpellOnTarget(ProjectileAtk[i].wpn_dmg, target, false, true, spells[ProjectileAtk[i].wpn_dmg].ResistDiff, true); + } + + ProjectileAtk[i].increment = 0; + ProjectileAtk[i].target_id = 0; + ProjectileAtk[i].wpn_dmg = 0; + ProjectileAtk[i].origin_x = 0.0f; + ProjectileAtk[i].origin_y = 0.0f; + ProjectileAtk[i].origin_z = 0.0f; + ProjectileAtk[i].tlast_x = 0.0f; + ProjectileAtk[i].tlast_y = 0.0f; + ProjectileAtk[i].ranged_id = 0; + ProjectileAtk[i].ammo_id = 0; + ProjectileAtk[i].ammo_slot = 0; + ProjectileAtk[i].skill = 0; + ProjectileAtk[i].speed_mod = 0.0f; + } + + else { + ProjectileAtk[i].increment++; + } + } + + if (disable) + SetProjectileAttack(false); +} + +float Mob::GetRangeDistTargetSizeMod(Mob* other) +{ + /* + Range is enforced client side, therefore these numbers do not need to be 100% accurate just close enough to + prevent any exploitation. The range mod changes in some situations depending on if size is from spawn or from SendIllusionPacket changes. + At present time only calculate from spawn (it is no consistent what happens to the calc when changing it after spawn). + */ + if (!other) + return 0.0f; + + float tsize = other->GetSize(); + + if (GetSize() > tsize) + tsize = GetSize(); + + float mod = 0.0f; + /*These are correct numbers if mob size is changed via #size (Don't know why it matters but it does) + if (tsize < 7) + mod = 16.0f; + else if (tsize >=7 && tsize <= 20) + mod = 16.0f + (0.6f * (tsize - 6.0f)); + else if (tsize >=20 && tsize <= 60) + mod = 25.0f + (1.25f * (tsize - 20.0f)); + else + mod = 75.0f; + */ + + if (tsize < 10) + mod = 18.0f; + else if (tsize >=10 && tsize < 15) + mod = 20.0f + (4.0f * (tsize - 10.0f)); + else if (tsize >=15 && tsize <= 20) + mod = 42.0f + (5.8f * (tsize - 15.0f)); + else + mod = 75.0f; + + return (mod + 2.0f); //Add 2.0f as buffer to prevent any chance of failures, client enforce range check regardless. +} + void NPC::RangedAttack(Mob* other) { @@ -1023,9 +1236,9 @@ void NPC::RangedAttack(Mob* other) if(ammo) SendItemAnimation(other, ammo, SkillArchery); - else + else ProjectileAnimation(other, 0,false,0,0,0,0,GetAmmoIDfile(),skillinuse); - + FaceTarget(other); if (!other->CheckHitChance(this, skillinuse, MainRange, GetSpecialAbilityParam(SPECATK_RANGED_ATK, 2))) @@ -1041,17 +1254,17 @@ void NPC::RangedAttack(Mob* other) if(WDmg > 0 || ADmg > 0) { mlog(COMBAT__RANGED, "Ranged attack hit %s.", other->GetName()); - + int32 MaxDmg = max_dmg * RuleR(Combat, ArcheryNPCMultiplier); // should add a field to npc_types int32 MinDmg = min_dmg * RuleR(Combat, ArcheryNPCMultiplier); if(RuleB(Combat, UseIntervalAC)) TotalDmg = MaxDmg; else - TotalDmg = MakeRandomInt(MinDmg, MaxDmg); + TotalDmg = zone->random.Int(MinDmg, MaxDmg); TotalDmg += TotalDmg * GetSpecialAbilityParam(SPECATK_RANGED_ATK, 3) / 100; //Damage modifier - + other->AvoidDamage(this, TotalDmg, false); other->MeleeMitigation(this, TotalDmg, MinDmg); if (TotalDmg > 0) @@ -1092,7 +1305,7 @@ uint16 Mob::GetThrownDamage(int16 wDmg, int32& TotalDmg, int& minDmg) { if(RuleB(Combat, UseIntervalAC)) TotalDmg = MaxDmg; else - TotalDmg = MakeRandomInt(1, MaxDmg); + TotalDmg = zone->random.Int(1, MaxDmg); minDmg = 1; if(GetLevel() > 25){ @@ -1111,7 +1324,8 @@ uint16 Mob::GetThrownDamage(int16 wDmg, int32& TotalDmg, int& minDmg) { void Client::ThrowingAttack(Mob* other, bool CanDoubleAttack) { //old was 51 //conditions to use an attack checked before we are called - + if (!other) + return; //make sure the attack and ranged timers are up //if the ranged timer is disabled, then they have no ranged weapon and shouldent be attacking anyhow if((!CanDoubleAttack && (attack_timer.Enabled() && !attack_timer.Check(false)) || (ranged_timer.Enabled() && !ranged_timer.Check()))) { @@ -1137,7 +1351,7 @@ void Client::ThrowingAttack(Mob* other, bool CanDoubleAttack) { //old was 51 return; } - mlog(COMBAT__RANGED, "Throwing %s (%d) at %s", item->Name, item->ID, GetTarget()->GetName()); + mlog(COMBAT__RANGED, "Throwing %s (%d) at %s", item->Name, item->ID, other->GetName()); if(RangeWeapon->GetCharges() == 1) { //first check ammo @@ -1159,19 +1373,20 @@ void Client::ThrowingAttack(Mob* other, bool CanDoubleAttack) { //old was 51 } } - int range = item->Range +50/*Fudge it a little, client will let you hit something at 0 0 0 when you are at 205 0 0*/; + float range = item->Range + GetRangeDistTargetSizeMod(other); mlog(COMBAT__RANGED, "Calculated bow range to be %.1f", range); range *= range; - if(DistNoRootNoZ(*GetTarget()) > range) { - mlog(COMBAT__RANGED, "Throwing attack out of range... client should catch this. (%f > %f).\n", DistNoRootNoZ(*GetTarget()), range); - //target is out of range, client does a message + float dist = DistNoRoot(*other); + if(dist > range) { + mlog(COMBAT__RANGED, "Throwing attack out of range... client should catch this. (%f > %f).\n", dist, range); + Message_StringID(13,TARGET_OUT_OF_RANGE);//Client enforces range and sends the message, this is a backup just incase. return; } - else if(DistNoRootNoZ(*GetTarget()) < (RuleI(Combat, MinRangedAttackDist)*RuleI(Combat, MinRangedAttackDist))){ - return; + else if(dist < (RuleI(Combat, MinRangedAttackDist)*RuleI(Combat, MinRangedAttackDist))){ + Message_StringID(15,RANGED_TOO_CLOSE);//Client enforces range and sends the message, this is a backup just incase. } - if(!IsAttackAllowed(GetTarget()) || + if(!IsAttackAllowed(other) || IsCasting() || IsSitting() || (DivineAura() && !GetGM()) || @@ -1181,10 +1396,8 @@ void Client::ThrowingAttack(Mob* other, bool CanDoubleAttack) { //old was 51 (GetAppearance() == eaDead)){ return; } - //send item animation, also does the throw animation - SendItemAnimation(GetTarget(), item, SkillThrowing); - DoThrowingAttackDmg(GetTarget(), RangeWeapon, item); + DoThrowingAttackDmg(other, RangeWeapon, item); //consume ammo DeleteItemInInventory(ammo_slot, 1, true); @@ -1192,21 +1405,81 @@ void Client::ThrowingAttack(Mob* other, bool CanDoubleAttack) { //old was 51 CommonBreakInvisible(); } -void Mob::DoThrowingAttackDmg(Mob* other, const ItemInst* RangeWeapon, const Item_Struct* item, uint16 weapon_damage, int16 chance_mod,int16 focus, int ReuseTime) +void Mob::DoThrowingAttackDmg(Mob* other, const ItemInst* RangeWeapon, const Item_Struct* AmmoItem, uint16 weapon_damage, int16 chance_mod,int16 focus, int ReuseTime, uint32 range_id, int AmmoSlot, float speed) { - if (!CanDoSpecialAttack(other)) + if ((other == nullptr || + ((IsClient() && CastToClient()->dead) || + (other->IsClient() && other->CastToClient()->dead)) || + HasDied() || + (!IsAttackAllowed(other)) || + (other->GetInvul() || + other->GetSpecialAbility(IMMUNE_MELEE)))) + { return; + } - if (!other->CheckHitChance(this, SkillThrowing, MainPrimary, chance_mod)){ + const ItemInst* _RangeWeapon = nullptr; + const Item_Struct* ammo_lost = nullptr; + + /* + If LaunchProjectile is false this function will do archery damage on target, + otherwise it will shoot the projectile at the target, once the projectile hits target + this function is then run again to do the damage portion + */ + bool LaunchProjectile = false; + bool ProjectileImpact = false; + bool ProjectileMiss = false; + + if (RuleB(Combat, ProjectileDmgOnImpact)){ + + if (AmmoItem) + LaunchProjectile = true; + else{ + if (!RangeWeapon && range_id){ + + ProjectileImpact = true; + + if (weapon_damage == 0) + ProjectileMiss = true; //This indicates that MISS was originally calculated. + + if (IsClient()){ + + _RangeWeapon = CastToClient()->m_inv[AmmoSlot]; + if (_RangeWeapon && _RangeWeapon->GetItem() && _RangeWeapon->GetItem()->ID != range_id) + RangeWeapon = _RangeWeapon; + else + ammo_lost = database.GetItem(range_id); + } + } + } + } + else if (AmmoItem) + SendItemAnimation(other, AmmoItem, SkillThrowing); + + if (ProjectileMiss || (!ProjectileImpact && !other->CheckHitChance(this, SkillThrowing, MainPrimary, chance_mod))){ mlog(COMBAT__RANGED, "Ranged attack missed %s.", other->GetName()); - other->Damage(this, 0, SPELL_UNKNOWN, SkillThrowing); + if (LaunchProjectile){ + TryProjectileAttack(other, AmmoItem, SkillThrowing, 0, RangeWeapon, nullptr, AmmoSlot, speed); + return; + } + else + other->Damage(this, 0, SPELL_UNKNOWN, SkillThrowing); } else { mlog(COMBAT__RANGED, "Throwing attack hit %s.", other->GetName()); int16 WDmg = 0; - if (!weapon_damage && item != nullptr) - WDmg = GetWeaponDamage(other, item); + if (!weapon_damage){ + if (IsClient() && RangeWeapon) + WDmg = GetWeaponDamage(other, RangeWeapon); + else if (AmmoItem) + WDmg = GetWeaponDamage(other, AmmoItem); + + if (LaunchProjectile){ + TryProjectileAttack(other, AmmoItem, SkillThrowing, WDmg, RangeWeapon, nullptr, AmmoSlot, speed); + return; + } + } else WDmg = weapon_damage; @@ -1243,7 +1516,7 @@ void Mob::DoThrowingAttackDmg(Mob* other, const ItemInst* RangeWeapon, const Ite other->AddToHateList(this, 2*WDmg, 0, false); other->Damage(this, TotalDmg, SPELL_UNKNOWN, SkillThrowing); - if (TotalDmg > 0 && HasSkillProcSuccess() && GetTarget() && other && !other->HasDied()){ + if (TotalDmg > 0 && HasSkillProcSuccess() && other && !other->HasDied()){ if (ReuseTime) TrySkillProc(other, SkillThrowing, ReuseTime); else @@ -1251,19 +1524,24 @@ void Mob::DoThrowingAttackDmg(Mob* other, const ItemInst* RangeWeapon, const Ite } } - if((RangeWeapon != nullptr) && GetTarget() && other && (other->GetHP() > -10)) + if (LaunchProjectile) + return; + + //Throwing item Proc + if (ammo_lost) + TryWeaponProc(nullptr, ammo_lost, other, MainRange); + else if(RangeWeapon && other && !other->HasDied()) TryWeaponProc(RangeWeapon, other, MainRange); - if (HasSkillProcs() && GetTarget() && other && !other->HasDied()){ + if (HasSkillProcs() && other && !other->HasDied()){ if (ReuseTime) TrySkillProc(other, SkillThrowing, ReuseTime); else TrySkillProc(other, SkillThrowing, 0, false, MainRange); } - } -void Mob::SendItemAnimation(Mob *to, const Item_Struct *item, SkillUseTypes skillInUse) { +void Mob::SendItemAnimation(Mob *to, const Item_Struct *item, SkillUseTypes skillInUse, float velocity) { EQApplicationPacket *outapp = new EQApplicationPacket(OP_SomeItemPacketMaybe, sizeof(Arrow_Struct)); Arrow_Struct *as = (Arrow_Struct *) outapp->pBuffer; as->type = 1; @@ -1291,7 +1569,7 @@ void Mob::SendItemAnimation(Mob *to, const Item_Struct *item, SkillUseTypes skil Arc causes the object to form an arc in motion. A value too high will */ - as->velocity = 4.0; + as->velocity = velocity; //these angle and tilt used together seem to make the arrow/knife throw as straight as I can make it @@ -1450,7 +1728,7 @@ void NPC::DoClassAttacks(Mob *target) { } case WARRIOR: case WARRIORGM:{ if(level >= RuleI(Combat, NPCBashKickLevel)){ - if(MakeRandomInt(0, 100) > 25){ //tested on live, warrior mobs both kick and bash, kick about 75% of the time, casting doesn't seem to make a difference. + if(zone->random.Roll(75)) { //tested on live, warrior mobs both kick and bash, kick about 75% of the time, casting doesn't seem to make a difference. DoAnim(animKick); int32 dmg = 0; @@ -1462,7 +1740,7 @@ void NPC::DoClassAttacks(Mob *target) { if(RuleB(Combat, UseIntervalAC)) dmg = GetKickDamage(); else - dmg = MakeRandomInt(1, GetKickDamage()); + dmg = zone->random.Int(1, GetKickDamage()); } } @@ -1483,7 +1761,7 @@ void NPC::DoClassAttacks(Mob *target) { if(RuleB(Combat, UseIntervalAC)) dmg = GetBashDamage(); else - dmg = MakeRandomInt(1, GetBashDamage()); + dmg = zone->random.Int(1, GetBashDamage()); } } @@ -1510,8 +1788,8 @@ void NPC::DoClassAttacks(Mob *target) { reuse = FrenzyReuseTime * 1000; - while(AtkRounds > 0) { - if (GetTarget() && (AtkRounds == 1 || MakeRandomInt(0,100) < 75)){ + while(AtkRounds > 0) { + if (GetTarget() && (AtkRounds == 1 || zone->random.Roll(75))) { DoSpecialAttackDamage(GetTarget(), SkillFrenzy, max_dmg, min_dmg, -1 , reuse, true); } AtkRounds--; @@ -1536,7 +1814,7 @@ void NPC::DoClassAttacks(Mob *target) { if(RuleB(Combat, UseIntervalAC)) dmg = GetKickDamage(); else - dmg = MakeRandomInt(1, GetKickDamage()); + dmg = zone->random.Int(1, GetKickDamage()); } } @@ -1561,7 +1839,7 @@ void NPC::DoClassAttacks(Mob *target) { if(RuleB(Combat, UseIntervalAC)) dmg = GetBashDamage(); else - dmg = MakeRandomInt(1, GetBashDamage()); + dmg = zone->random.Int(1, GetBashDamage()); } } @@ -1670,7 +1948,7 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) if(RuleB(Combat, UseIntervalAC)) dmg = GetBashDamage(); else - dmg = MakeRandomInt(1, GetBashDamage()); + dmg = zone->random.Int(1, GetBashDamage()); } } @@ -1707,7 +1985,7 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) //Live parses show around 55% Triple 35% Double 10% Single, you will always get first hit. while(AtkRounds > 0) { - if (GetTarget() && (AtkRounds == 1 || MakeRandomInt(0,100) < 75)){ + if (GetTarget() && (AtkRounds == 1 || zone->random.Roll(75))) { DoSpecialAttackDamage(GetTarget(), SkillFrenzy, max_dmg, min_dmg, max_dmg , ReuseTime, true); } AtkRounds--; @@ -1734,7 +2012,7 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) if(RuleB(Combat, UseIntervalAC)) dmg = GetKickDamage(); else - dmg = MakeRandomInt(1, GetKickDamage()); + dmg = zone->random.Int(1, GetKickDamage()); } } @@ -1754,17 +2032,17 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) //Live AA - Technique of Master Wu int wuchance = itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack; if (wuchance) { - if (wuchance >= 100 || wuchance > MakeRandomInt(0, 99)) { + if (wuchance >= 100 || zone->random.Roll(wuchance)) { int MonkSPA [5] = { SkillFlyingKick, SkillDragonPunch, SkillEagleStrike, SkillTigerClaw, SkillRoundKick }; int extra = 1; - if (wuchance / 4 > MakeRandomInt(0, 99)) + if (zone->random.Roll(wuchance / 4)) extra++; // They didn't add a string ID for this. std::string msg = StringFormat("The spirit of Master Wu fills you! You gain %d additional attack(s).", extra); // live uses 400 here -- not sure if it's the best for all clients though SendColoredText(400, msg); while (extra) { - MonkSpecialAttack(ca_target, MonkSPA[MakeRandomInt(0, 4)]); + MonkSpecialAttack(ca_target, MonkSPA[zone->random.Int(0, 4)]); extra--; } } @@ -1816,7 +2094,7 @@ void Mob::Taunt(NPC* who, bool always_succeed, float chance_bonus) { int32 newhate = 0; float tauntchance = 50.0f; - + if(always_succeed) tauntchance = 101.0f; @@ -1848,7 +2126,7 @@ void Mob::Taunt(NPC* who, bool always_succeed, float chance_bonus) { tauntchance /= 100.0f; - if (tauntchance > MakeRandomFloat(0, 1)) { + if (tauntchance > zone->random.Real(0, 1)) { if (hate_top && hate_top != this){ newhate = (who->GetNPCHate(hate_top) - who->GetNPCHate(this)) + 1; who->CastToNPC()->AddToHateList(this, newhate); @@ -1870,7 +2148,7 @@ void Mob::Taunt(NPC* who, bool always_succeed, float chance_bonus) { if (HasSkillProcs()) TrySkillProc(who, SkillTaunt, TauntReuseTime*1000); - + if (Success && HasSkillProcSuccess()) TrySkillProc(who, SkillTaunt, TauntReuseTime*1000, true); } @@ -1904,7 +2182,7 @@ void Mob::InstillDoubt(Mob *who) { //target's counters value -= target->GetLevel()*4 + who->GetWIS()/4; - if (MakeRandomInt(0,99) < value) { + if (zone->random.Roll(value)) { //temporary hack... //cast fear on them... should prolly be a different spell //and should be un-resistable. @@ -1913,7 +2191,7 @@ void Mob::InstillDoubt(Mob *who) { } else { Message_StringID(4,NOT_SCARING); //Idea from WR: - /* if (target->IsNPC() && MakeRandomInt(0,99) < 10 ) { + /* if (target->IsNPC() && zone->random.Int(0,99) < 10 ) { entity_list.MessageClose(target, false, 50, MT_NPCRampage, "%s lashes out in anger!",target->GetName()); //should we actually do this? and the range is completely made up, unconfirmed entity_list.AEAttack(target, 50); @@ -1921,12 +2199,12 @@ void Mob::InstillDoubt(Mob *who) { } } -uint32 Mob::TryHeadShot(Mob* defender, SkillUseTypes skillInUse) { +uint32 Mob::TryHeadShot(Mob* defender, SkillUseTypes skillInUse) { //Only works on YOUR target. - if(defender && (defender->GetBodyType() == BT_Humanoid) && !defender->IsClient() + if(defender && (defender->GetBodyType() == BT_Humanoid) && !defender->IsClient() && (skillInUse == SkillArchery) && (GetTarget() == defender)) { - - uint32 HeadShot_Dmg = aabonuses.HeadShot[1] + spellbonuses.HeadShot[1] + itembonuses.HeadShot[1]; + + uint32 HeadShot_Dmg = aabonuses.HeadShot[1] + spellbonuses.HeadShot[1] + itembonuses.HeadShot[1]; uint8 HeadShot_Level = 0; //Get Highest Headshot Level HeadShot_Level = aabonuses.HSLevel; if (HeadShot_Level < spellbonuses.HSLevel) @@ -1934,9 +2212,9 @@ uint32 Mob::TryHeadShot(Mob* defender, SkillUseTypes skillInUse) { else if (HeadShot_Level < itembonuses.HSLevel) HeadShot_Level = itembonuses.HSLevel; - if(HeadShot_Dmg && HeadShot_Level && (defender->GetLevel() <= HeadShot_Level)){ + if(HeadShot_Dmg && HeadShot_Level && (defender->GetLevel() <= HeadShot_Level)){ float ProcChance = GetSpecialProcChances(MainRange); - if(ProcChance > MakeRandomFloat(0,1)) + if(zone->random.Roll(ProcChance)) return HeadShot_Dmg; } } @@ -1965,7 +2243,7 @@ float Mob::GetSpecialProcChances(uint16 hand) } else { /*PRE 2014 CHANGE Dev Quote - "Elidroth SOE:Proc chance is a function of your base hardcapped Dexterity / 35 + Heroic Dexterity / 25.” Kayen: Most reports suggest a ~ 6% chance to Headshot which consistent with above.*/ - + ProcChance = (static_cast(mydex/35) + static_cast(itembonuses.HeroicDEX / 25))/100.0f; } @@ -1976,9 +2254,9 @@ uint32 Mob::TryAssassinate(Mob* defender, SkillUseTypes skillInUse, uint16 Reuse if(defender && (defender->GetBodyType() == BT_Humanoid) && !defender->IsClient() && (skillInUse == SkillBackstab || skillInUse == SkillThrowing)) { - + uint32 Assassinate_Dmg = aabonuses.Assassinate[1] + spellbonuses.Assassinate[1] + itembonuses.Assassinate[1]; - + uint8 Assassinate_Level = 0; //Get Highest Headshot Level Assassinate_Level = aabonuses.AssassinateLevel; if (Assassinate_Level < spellbonuses.AssassinateLevel) @@ -1996,13 +2274,13 @@ uint32 Mob::TryAssassinate(Mob* defender, SkillUseTypes skillInUse, uint16 Reuse if(Assassinate_Dmg && Assassinate_Level && (defender->GetLevel() <= Assassinate_Level)){ float ProcChance = 0.0f; - + if (skillInUse == SkillThrowing) ProcChance = GetSpecialProcChances(MainRange); else ProcChance = GetAssassinateProcChances(ReuseTime); - if(ProcChance > MakeRandomFloat(0,1)) + if(zone->random.Roll(ProcChance)) return Assassinate_Dmg; } } @@ -2022,7 +2300,7 @@ float Mob::GetAssassinateProcChances(uint16 ReuseTime) if (RuleB(Combat, AdjustSpecialProcPerMinute)) { ProcChance = (static_cast(ReuseTime*1000) * - RuleR(Combat, AvgSpecialProcsPerMinute) / 60000.0f); + RuleR(Combat, AvgSpecialProcsPerMinute) / 60000.0f); ProcBonus += (10 + (static_cast(mydex/10) + static_cast(itembonuses.HeroicDEX /10)))/100.0f; ProcChance += ProcChance * ProcBonus / 100.0f; @@ -2084,7 +2362,7 @@ void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillUseTypes } } - ApplySpecialAttackMod(skillinuse, max_hit, min_hit); + ApplySpecialAttackMod(skillinuse, max_hit, min_hit); min_hit += min_hit * GetMeleeMinDamageMod_SE(skillinuse) / 100; if(max_hit < min_hit) @@ -2093,8 +2371,8 @@ void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillUseTypes if(RuleB(Combat, UseIntervalAC)) damage = max_hit; else - damage = MakeRandomInt(min_hit, max_hit); - + damage = zone->random.Int(min_hit, max_hit); + if(!other->CheckHitChance(this, skillinuse, Hand, chance_mod)) { damage = 0; } else { @@ -2128,13 +2406,11 @@ void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillUseTypes if (HasDied()) return; - CheckNumHitsRemaining(NUMHIT_OutgoingHitSuccess); - if(aabonuses.SpecialAttackKBProc[0] && aabonuses.SpecialAttackKBProc[1] == skillinuse){ int kb_chance = 25; kb_chance += kb_chance*(100-aabonuses.SpecialAttackKBProc[0])/100; - if (MakeRandomInt(0, 99) < kb_chance) + if (zone->random.Roll(kb_chance)) SpellFinished(904, other, 10, 0, -1, spells[904].ResistDiff); } diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 447d34efd..59526fbbc 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -15,26 +15,24 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "../common/debug.h" -#include "../common/spdat.h" -#include "masterentity.h" -#include "../common/packet_dump.h" -#include "../common/moremath.h" -#include "../common/item.h" -#include "worldserver.h" -#include "../common/skills.h" + #include "../common/bodytypes.h" #include "../common/classes.h" +#include "../common/debug.h" +#include "../common/item.h" #include "../common/rulesys.h" +#include "../common/skills.h" +#include "../common/spdat.h" +#include "quest_parser_collection.h" +#include "string_ids.h" +#include "worldserver.h" #include -#include + #ifndef WIN32 #include #include "../common/unix.h" #endif -#include "string_ids.h" -#include "quest_parser_collection.h" extern Zone* zone; extern volatile bool ZoneLoaded; @@ -457,7 +455,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) if(IsClient()) { - if(MakeRandomInt(0, 99) < RuleI(Spells, SuccorFailChance)) { //2% Fail chance by default + if(zone->random.Roll(RuleI(Spells, SuccorFailChance))) { //2% Fail chance by default if(IsClient()) { CastToClient()->Message_StringID(MT_SpellFailure,SUCCOR_FAIL); @@ -712,9 +710,9 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) if (IsClient()) stun_resist += aabonuses.StunResist; - if (stun_resist <= 0 || MakeRandomInt(0,99) >= stun_resist) { + if (stun_resist <= 0 || zone->random.Int(0,99) >= stun_resist) { mlog(COMBAT__HITS, "Stunned. We had %d percent resist chance.", stun_resist); - + if (caster->IsClient()) effect_value += effect_value*caster->CastToClient()->GetFocusEffect(focusFcStunTimeMod, spell_id)/100; @@ -1035,8 +1033,8 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) #endif if(!spellbonuses.AntiGate){ - if(MakeRandomInt(0, 99) < effect_value) - Gate(); + if(zone->random.Roll(effect_value)) + Gate(); else caster->Message_StringID(MT_SpellFailure,GATE_FAIL); } @@ -1477,16 +1475,16 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) #endif int wipechance = spells[spell_id].base[i]; int bonus = 0; - + if (caster){ - bonus = caster->spellbonuses.IncreaseChanceMemwipe + - caster->itembonuses.IncreaseChanceMemwipe + + bonus = caster->spellbonuses.IncreaseChanceMemwipe + + caster->itembonuses.IncreaseChanceMemwipe + caster->aabonuses.IncreaseChanceMemwipe; } wipechance += wipechance*bonus/100; - - if(MakeRandomInt(0, 99) < wipechance) + + if(zone->random.Roll(wipechance)) { if(IsAIControlled()) { @@ -1599,7 +1597,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) if(IsClient()) { - if (MakeRandomInt(0, 99) > spells[spell_id].base[i]) { + if (zone->random.Int(0, 99) > spells[spell_id].base[i]) { CastToClient()->SetFeigned(false); entity_list.MessageClose_StringID(this, false, 200, 10, STRING_FEIGNFAILED, GetName()); } @@ -1665,7 +1663,16 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Model Size: %d%%", effect_value); #endif - ChangeSize(GetSize() * (static_cast(effect_value) / 100.0f)); + // Only allow 2 size changes from Base Size + float modifyAmount = (static_cast(effect_value) / 100.0f); + float maxModAmount = GetBaseSize() * modifyAmount * modifyAmount; + if ((GetSize() <= GetBaseSize() && GetSize() > maxModAmount) || + (GetSize() >= GetBaseSize() && GetSize() < maxModAmount) || + (GetSize() <= GetBaseSize() && maxModAmount > 1.0f) || + (GetSize() >= GetBaseSize() && maxModAmount < 1.0f)) + { + ChangeSize(GetSize() * modifyAmount); + } break; } @@ -2180,7 +2187,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Fading Memories"); #endif - if(MakeRandomInt(0, 99) < spells[spell_id].base[i] ) { + if(zone->random.Roll(spells[spell_id].base[i])) { if(caster && caster->IsClient()) caster->CastToClient()->Escape(); @@ -2704,11 +2711,11 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) } }else{ int32 newhate = GetHateAmount(caster) + effect_value; - if (newhate < 1) + if (newhate < 1) SetHate(caster,1); - else + else SetHate(caster,newhate); - } + } } break; } @@ -2717,9 +2724,9 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) if (buffslot >= 0) break; - if(!spells[spell_id].uninterruptable && IsCasting() && MakeRandomInt(0, 100) <= spells[spell_id].base[i]) + if(!spells[spell_id].uninterruptable && IsCasting() && zone->random.Roll(spells[spell_id].base[i])) InterruptSpell(); - + break; } @@ -2735,17 +2742,16 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) Message(10, "The power of your next illusion spell will flow to your grouped target in your place."); break; } - + case SE_ApplyEffect: { if (caster && IsValidSpell(spells[spell_id].base2[i])){ - - if(MakeRandomInt(0, 100) <= spells[spell_id].base[i]) + if(zone->random.Roll(spells[spell_id].base[i])) caster->SpellFinished(spells[spell_id].base2[i], this, 10, 0, -1, spells[spells[spell_id].base2[i]].ResistDiff); } break; } - + case SE_SpellTrigger: { if (!SE_SpellTrigger_HasCast) { @@ -3202,7 +3208,7 @@ snare has both of them negative, yet their range should work the same: break; } case 123: // added 2/6/04 - result = MakeRandomInt(ubase, abs(max)); + result = zone->random.Int(ubase, abs(max)); break; case 124: // check sign @@ -3566,16 +3572,16 @@ void Mob::DoBuffTic(uint16 spell_id, int slot, uint32 ticsremaining, uint8 caste int wipechance = spells[spell_id].base[i]; int bonus = 0; - + if (caster){ - bonus = caster->spellbonuses.IncreaseChanceMemwipe + - caster->itembonuses.IncreaseChanceMemwipe + + bonus = caster->spellbonuses.IncreaseChanceMemwipe + + caster->itembonuses.IncreaseChanceMemwipe + caster->aabonuses.IncreaseChanceMemwipe; } - + wipechance += wipechance*bonus/100; - - if(MakeRandomInt(0, 99) < wipechance) + + if(zone->random.Roll(wipechance)) { if(IsAIControlled()) { @@ -3595,16 +3601,14 @@ void Mob::DoBuffTic(uint16 spell_id, int slot, uint32 ticsremaining, uint8 caste } case SE_Root: { - /* Root formula derived from extensive personal live parses - Kayen ROOT has a 70% chance to do a resist check to break. */ - if (MakeRandomInt(0, 99) < RuleI(Spells, RootBreakCheckChance)){ - + if (zone->random.Roll(RuleI(Spells, RootBreakCheckChance))) { float resist_check = ResistSpell(spells[spell_id].resisttype, spell_id, caster, 0,0,0,0,true); - if(resist_check == 100) + if(resist_check == 100) break; else if(!TryFadeEffect(slot)) @@ -3616,11 +3620,10 @@ void Mob::DoBuffTic(uint16 spell_id, int slot, uint32 ticsremaining, uint8 caste case SE_Fear: { - if (MakeRandomInt(0, 99) < RuleI(Spells, FearBreakCheckChance)){ - + if (zone->random.Roll(RuleI(Spells, FearBreakCheckChance))) { float resist_check = ResistSpell(spells[spell_id].resisttype, spell_id, caster); - if(resist_check == 100) + if(resist_check == 100) break; else if(!TryFadeEffect(slot)) @@ -3657,7 +3660,7 @@ void Mob::DoBuffTic(uint16 spell_id, int slot, uint32 ticsremaining, uint8 caste break_chance -= (2 * (((double)GetSkill(SkillDivination) + ((double)GetLevel() * 3.0)) / 650.0)); } - if(MakeRandomFloat(0.0, 100.0) < break_chance) + if(zone->random.Real(0.0, 100.0) < break_chance) { BuffModifyDurationBySpellID(spell_id, 3); } @@ -3677,7 +3680,7 @@ void Mob::DoBuffTic(uint16 spell_id, int slot, uint32 ticsremaining, uint8 caste { if(IsCasting()) { - if(MakeRandomInt(0, 100) <= spells[spell_id].base[i]) + if(zone->random.Roll(spells[spell_id].base[i])) { InterruptSpell(); } @@ -4575,11 +4578,9 @@ int16 Client::CalcAAFocus(focusType type, uint32 aa_ID, uint16 spell_id) case SE_TriggerOnCast: if(type == focusTriggerOnCast){ - if(MakeRandomInt(0, 100) <= base1){ + if(zone->random.Roll(base1)) { value = base2; - } - - else{ + } else { value = 0; LimitFailure = true; } @@ -4593,7 +4594,7 @@ int16 Client::CalcAAFocus(focusType type, uint32 aa_ID, uint16 spell_id) case SE_BlockNextSpellFocus: if(type == focusBlockNextSpell){ - if(MakeRandomInt(1, 100) <= base1) + if(zone->random.Roll(base1)) value = 1; } break; @@ -4948,7 +4949,7 @@ int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo value = focus_spell.base[i]; } else { - value = MakeRandomInt(focus_spell.base[i], focus_spell.base2[i]); + value = zone->random.Int(focus_spell.base[i], focus_spell.base2[i]); } } break; @@ -4967,7 +4968,7 @@ int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo value = focus_spell.base[i]; } else { - value = MakeRandomInt(focus_spell.base[i], focus_spell.base2[i]); + value = zone->random.Int(focus_spell.base[i], focus_spell.base2[i]); } } break; @@ -4986,7 +4987,7 @@ int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo value = focus_spell.base[i]; } else { - value = MakeRandomInt(focus_spell.base[i], focus_spell.base2[i]); + value = zone->random.Int(focus_spell.base[i], focus_spell.base2[i]); } } break; @@ -5055,7 +5056,7 @@ int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo case SE_TriggerOnCast: if(type == focusTriggerOnCast){ - if(MakeRandomInt(1, 100) <= focus_spell.base[i]) + if(zone->random.Roll(focus_spell.base[i])) value = focus_spell.base2[i]; else value = 0; @@ -5064,7 +5065,7 @@ int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo case SE_BlockNextSpellFocus: if(type == focusBlockNextSpell){ - if(MakeRandomInt(1, 100) <= focus_spell.base[i]) + if(zone->random.Roll(focus_spell.base[i])) value = 1; } break; @@ -5212,12 +5213,9 @@ int16 Client::GetSympatheticFocusEffect(focusType type, uint16 spell_id) { if (TempItem && TempItem->Focus.Effect > 0 && IsValidSpell(TempItem->Focus.Effect)) { proc_spellid = CalcFocusEffect(type, TempItem->Focus.Effect, spell_id); - if (IsValidSpell(proc_spellid)){ - ProcChance = GetSympatheticProcChances(spell_id, spells[TempItem->Focus.Effect].base[0], TempItem->ProcRate); - - if(MakeRandomFloat(0, 1) <= ProcChance) + if(zone->random.Roll(ProcChance)) SympatheticProcList.push_back(proc_spellid); } } @@ -5233,14 +5231,10 @@ int16 Client::GetSympatheticFocusEffect(focusType type, uint16 spell_id) { { const Item_Struct* TempItemAug = aug->GetItem(); if (TempItemAug && TempItemAug->Focus.Effect > 0 && IsValidSpell(TempItemAug->Focus.Effect)) { - proc_spellid = CalcFocusEffect(type, TempItemAug->Focus.Effect, spell_id); - if (IsValidSpell(proc_spellid)){ - ProcChance = GetSympatheticProcChances(spell_id, spells[TempItemAug->Focus.Effect].base[0], TempItemAug->ProcRate); - - if(MakeRandomFloat(0, 1) <= ProcChance) + if(zone->random.Roll(ProcChance)) SympatheticProcList.push_back(proc_spellid); } } @@ -5268,8 +5262,7 @@ int16 Client::GetSympatheticFocusEffect(focusType type, uint16 spell_id) { if (IsValidSpell(proc_spellid)){ ProcChance = GetSympatheticProcChances(spell_id, spells[focusspellid].base[0]); - - if(MakeRandomFloat(0, 1) <= ProcChance) + if(zone->random.Roll(ProcChance)) SympatheticProcList.push_back(proc_spellid); } } @@ -5295,10 +5288,8 @@ int16 Client::GetSympatheticFocusEffect(focusType type, uint16 spell_id) { proc_spellid = CalcAAFocus(type, aa_AA, spell_id); if (IsValidSpell(proc_spellid)){ - ProcChance = GetSympatheticProcChances(spell_id, GetAABase1(aa_AA, 1)); - - if(MakeRandomFloat(0, 1) <= ProcChance) + if(zone->random.Roll(ProcChance)) SympatheticProcList.push_back(proc_spellid); } } @@ -5306,7 +5297,7 @@ int16 Client::GetSympatheticFocusEffect(focusType type, uint16 spell_id) { if (SympatheticProcList.size() > 0) { - uint8 random = MakeRandomInt(0, SympatheticProcList.size()-1); + uint8 random = zone->random.Int(0, SympatheticProcList.size()-1); int FinalSympatheticProc = SympatheticProcList[random]; SympatheticProcList.clear(); return FinalSympatheticProc; @@ -5549,7 +5540,7 @@ int16 Client::GetFocusEffect(focusType type, uint16 spell_id) { return realTotal + realTotal2 + realTotal3; } -void Mob::CheckNumHitsRemaining(uint8 type, uint32 buff_slot, uint16 spell_id) +void Mob::CheckNumHitsRemaining(uint8 type, int32 buff_slot, uint16 spell_id) { /* Field 175 = numhits type @@ -5586,8 +5577,8 @@ void Mob::CheckNumHitsRemaining(uint8 type, uint32 buff_slot, uint16 spell_id) } } } - } else if (type == 7) { - if (buff_slot > 0) { + } else if (type == NUMHIT_MatchingSpells) { + if (buff_slot >= 0) { if (--buffs[buff_slot].numhits == 0) { CastOnNumHitFade(buffs[buff_slot].spellid); if (!TryFadeEffect(buff_slot)) @@ -5665,7 +5656,7 @@ bool Mob::TryDivineSave() */ int32 SuccessChance = aabonuses.DivineSaveChance[0] + itembonuses.DivineSaveChance[0] + spellbonuses.DivineSaveChance[0]; - if (SuccessChance && MakeRandomInt(0, 100) <= SuccessChance) + if (SuccessChance && zone->random.Roll(SuccessChance)) { SetHP(1); @@ -5724,7 +5715,7 @@ bool Mob::TryDeathSave() { if (SuccessChance > 95) SuccessChance = 95; - if(SuccessChance >= MakeRandomInt(0, 100)) { + if(zone->random.Roll(SuccessChance)) { if(spellbonuses.DeathSave[0] == 2) HealAmt = RuleI(Spells, DivineInterventionHeal); //8000HP is how much LIVE Divine Intervention max heals @@ -5755,7 +5746,7 @@ bool Mob::TryDeathSave() { if (SuccessChance > 95) SuccessChance = 95; - if(SuccessChance >= MakeRandomInt(0, 100)) { + if(zone->random.Roll(SuccessChance)) { if(spellbonuses.DeathSave[0] == 2) HealAmt = RuleI(Spells, DivineInterventionHeal); @@ -6057,7 +6048,7 @@ bool Mob::TryDispel(uint8 caster_level, uint8 buff_level, int level_modifier){ else if (dispel_chance < 10) dispel_chance = 10; - if (MakeRandomInt(0,99) < dispel_chance) + if (zone->random.Roll(dispel_chance)) return true; else return false; @@ -6393,17 +6384,16 @@ bool Mob::PassCastRestriction(bool UseCastRestriction, int16 value, bool IsDama return false; } -bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id){ +bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id, float speed){ /*For mage 'Bolt' line and other various spells. -This is mostly accurate for how the modern clients handle this effect. -It was changed at some point to use an actual projectile as done here (opposed to a particle effect in classic) - -The projectile graphic appears to be that of 'Ball of Sunlight' ID 80648 and will be visible to anyone in SoF+ -There is no LOS check to prevent a bolt from being cast. If you don't have LOS your bolt simply goes into whatever barrier and you lose your mana. If there is LOS the bolt will lock onto your target and the damage is applied when it hits the target. -If your target moves the bolt moves with it in any direction or angle (consistent with other projectiles). - -The way this is written once a bolt is cast a timer checks the distance from the initial cast to the target repeatedly - and calculates at what predicted time the bolt should hit that target in client_process (therefore accounting for any target movement). + -The way this is written once a bolt is cast a the distance from the initial cast to the target repeatedly + check and if target is moving recalculates at what predicted time the bolt should hit that target in client_process When bolt hits its predicted point the damage is then done to target. Note: Projectile speed of 1 takes 3 seconds to go 100 distance units. Calculations are based on this constant. Live Bolt speed: Projectile speed of X takes 5 seconds to go 300 distance units. @@ -6415,31 +6405,41 @@ bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id){ return false; uint8 anim = spells[spell_id].CastingAnim; - int bolt_id = -1; + int slot = -1; //Make sure there is an avialable bolt to be cast. for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { - if (projectile_spell_id[i] == 0){ - bolt_id = i; + if (ProjectileAtk[i].target_id == 0){ + slot = i; break; } } - if (bolt_id < 0) + if (slot < 0) return false; - + if (CheckLosFN(spell_target)) { - projectile_spell_id[bolt_id] = spell_id; - projectile_target_id[bolt_id] = spell_target->GetID(); - projectile_x[bolt_id] = GetX(), projectile_y[bolt_id] = GetY(), projectile_z[bolt_id] = GetZ(); - projectile_increment[bolt_id] = 1; - projectile_timer.Start(250); + float speed_mod = speed * 0.45f; //Constant for adjusting speeds to match calculated impact time. + float distance = spell_target->CalculateDistance(GetX(), GetY(), GetZ()); + float hit = 60.0f + (distance / speed_mod); + + ProjectileAtk[slot].increment = 1; + ProjectileAtk[slot].hit_increment = static_cast(hit); //This projected hit time if target does NOT MOVE + ProjectileAtk[slot].target_id = spell_target->GetID(); + ProjectileAtk[slot].wpn_dmg = spell_id; //Store spell_id in weapon damage field + ProjectileAtk[slot].origin_x = GetX(); + ProjectileAtk[slot].origin_y = GetY(); + ProjectileAtk[slot].origin_z = GetZ(); + ProjectileAtk[slot].skill = SkillConjuration; + ProjectileAtk[slot].speed_mod = speed_mod; + + SetProjectileAttack(true); } //This will use the correct graphic as defined in the player_1 field of spells_new table. Found in UF+ spell files. if (RuleB(Spells, UseLiveSpellProjectileGFX)) { - ProjectileAnimation(spell_target,0, false, 1.5,0,0,0, spells[spell_id].player_1); + ProjectileAnimation(spell_target,0, false, speed,0,0,0, spells[spell_id].player_1); } //This allows limited support for server using older spell files that do not contain data for bolt graphics. @@ -6449,19 +6449,17 @@ bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id){ if (IsClient()){ if (CastToClient()->GetClientVersionBit() <= 4) //Titanium needs alternate graphic. - ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_Titanium)), false, 1.5); + ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_Titanium)), false, speed); else - ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_SOF)), false, 1.5); + ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_SOF)), false, speed); } else - ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_NPC)), false, 1.5); - + ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_NPC)), false, speed); } - //Default to an arrow if not using a mage bolt (Use up to date spell file and enable above rules for best results) else - ProjectileAnimation(spell_target,0, 1, 1.5); + ProjectileAnimation(spell_target,0, 1, speed); } if (spells[spell_id].CastingAnim == 64) diff --git a/zone/spells.cpp b/zone/spells.cpp index 223a584f3..2c4bc7c69 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -66,20 +66,19 @@ Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org) and not SpellFinished(). */ -#include "../common/debug.h" -#include "../common/spdat.h" -#include "masterentity.h" -#include "../common/packet_dump.h" -#include "../common/moremath.h" -#include "../common/item.h" -#include "worldserver.h" -#include "../common/skills.h" #include "../common/bodytypes.h" #include "../common/classes.h" +#include "../common/debug.h" +#include "../common/item.h" #include "../common/rulesys.h" +#include "../common/skills.h" +#include "../common/spdat.h" #include "../common/string_util.h" -#include +#include "quest_parser_collection.h" +#include "string_ids.h" +#include "worldserver.h" #include +#include #ifndef WIN32 #include @@ -90,8 +89,7 @@ Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org) #include "../common/packet_dump_file.h" #endif -#include "string_ids.h" -#include "quest_parser_collection.h" + extern Zone* zone; extern volatile bool ZoneLoaded; @@ -180,7 +178,7 @@ bool Mob::CastSpell(uint16 spell_id, uint16 target_id, uint16 slot, if(IsClient()){ int chance = CastToClient()->GetFocusEffect(focusFcMute, spell_id); - if (MakeRandomInt(0,99) < chance){ + if (zone->random.Roll(chance)) { Message_StringID(13, SILENCED_STRING); if(IsClient()) CastToClient()->SendSpellBarEnable(spell_id); @@ -699,7 +697,7 @@ bool Client::CheckFizzle(uint16 spell_id) specialize = specialize * 1.3; break; } - if(((specialize/6.0f) + 15.0f) < MakeRandomFloat(0, 100)) { + if(((specialize/6.0f) + 15.0f) < zone->random.Real(0, 100)) { specialize *= SPECIALIZE_FIZZLE / 200.0f; } else { specialize = 0.0f; @@ -741,7 +739,7 @@ bool Client::CheckFizzle(uint16 spell_id) } */ - float fizzle_roll = MakeRandomFloat(0, 100); + float fizzle_roll = zone->random.Real(0, 100); mlog(SPELLS__CASTING, "Check Fizzle %s spell %d fizzlechance: %0.2f%% diff: %0.2f roll: %0.2f", GetName(), spell_id, fizzlechance, diff, fizzle_roll); @@ -1030,7 +1028,7 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, uint16 slot, mlog(SPELLS__CASTING, "Checking Interruption: spell x: %f spell y: %f cur x: %f cur y: %f channelchance %f channeling skill %d\n", GetSpellX(), GetSpellY(), GetX(), GetY(), channelchance, GetSkill(SkillChanneling)); - if(!spells[spell_id].uninterruptable && MakeRandomFloat(0, 100) > channelchance) { + if(!spells[spell_id].uninterruptable && zone->random.Real(0, 100) > channelchance) { mlog(SPELLS__CASTING_ERR, "Casting of %d canceled: interrupted.", spell_id); InterruptSpell(); return; @@ -1046,7 +1044,7 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, uint16 slot, // first check for component reduction if(IsClient()) { int reg_focus = CastToClient()->GetFocusEffect(focusReagentCost,spell_id); - if(MakeRandomInt(1, 100) <= reg_focus) { + if(zone->random.Roll(reg_focus)) { mlog(SPELLS__CASTING, "Spell %d: Reagent focus item prevented reagent consumption (%d chance)", spell_id, reg_focus); } else { if(reg_focus > 0) @@ -1670,6 +1668,12 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce } else { spell_target = this; } + + if (spell_target && spell_target->IsPet() && spells[spell_id].targettype == ST_GroupNoPets){ + Message_StringID(13,NO_CAST_ON_PET); + return false; + } + CastAction = GroupSpell; break; } @@ -3806,7 +3810,7 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob* spelltar, bool reflect, bool use_r void Corpse::CastRezz(uint16 spellid, Mob* Caster) { - _log(SPELLS__REZ, "Corpse::CastRezz spellid %i, Rezzed() is %i, rezzexp is %i", spellid,IsRezzed(),rezzexp); + _log(SPELLS__REZ, "Corpse::CastRezz spellid %i, Rezzed() is %i, rezzexp is %i", spellid,IsRezzed(),rez_experience); if(IsRezzed()){ if(Caster && Caster->IsClient()) @@ -3825,7 +3829,7 @@ void Corpse::CastRezz(uint16 spellid, Mob* Caster) EQApplicationPacket* outapp = new EQApplicationPacket(OP_RezzRequest, sizeof(Resurrect_Struct)); Resurrect_Struct* rezz = (Resurrect_Struct*) outapp->pBuffer; // Why are we truncating these names to 30 characters ? - memcpy(rezz->your_name,this->orgname,30); + memcpy(rezz->your_name,this->corpse_name,30); memcpy(rezz->corpse_name,this->name,30); memcpy(rezz->rezzer_name,Caster->GetName(),30); rezz->zone_id = zone->GetZoneID(); @@ -3838,7 +3842,7 @@ void Corpse::CastRezz(uint16 spellid, Mob* Caster) rezz->unknown020 = 0x00000000; rezz->unknown088 = 0x00000000; // We send this to world, because it needs to go to the player who may not be in this zone. - worldserver.RezzPlayer(outapp, rezzexp, corpse_db_id, OP_RezzRequest); + worldserver.RezzPlayer(outapp, rez_experience, corpse_db_id, OP_RezzRequest); _pkt(SPELLS__REZ, outapp); safe_delete(outapp); } @@ -4200,7 +4204,7 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use { IsFear = true; int fear_resist_bonuses = CalcFearResistChance(); - if(MakeRandomInt(0, 99) < fear_resist_bonuses) + if(zone->random.Roll(fear_resist_bonuses)) { mlog(SPELLS__RESISTS, "Resisted spell in fear resistance, had %d chance to resist", fear_resist_bonuses); return 0; @@ -4211,14 +4215,14 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use //Check for Spell Effect specific resistance chances (ie AA Mental Fortitude) int se_resist_bonuses = GetSpellEffectResistChance(spell_id); - if(se_resist_bonuses && (MakeRandomInt(0, 99) < se_resist_bonuses)) + if(se_resist_bonuses && zone->random.Roll(se_resist_bonuses)) { return 0; } // Check for Chance to Resist Spell bonuses (ie Sanctification Discipline) int resist_bonuses = CalcResistChanceBonus(); - if(resist_bonuses && (MakeRandomInt(0, 99) < resist_bonuses)) + if(resist_bonuses && zone->random.Roll(resist_bonuses)) { mlog(SPELLS__RESISTS, "Resisted spell in sanctification, had %d chance to resist", resist_bonuses); return 0; @@ -4444,7 +4448,7 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use } //Finally our roll - int roll = MakeRandomInt(0, 200); + int roll = zone->random.Int(0, 200); if(roll > resist_chance) { return 100; @@ -4661,7 +4665,7 @@ void Mob::Stun(int duration) if(IsValidSpell(casting_spell_id) && !spells[casting_spell_id].uninterruptable) { int persistent_casting = spellbonuses.PersistantCasting + itembonuses.PersistantCasting + aabonuses.PersistantCasting; - if(MakeRandomInt(0,99) > persistent_casting) + if(zone->random.Int(0,99) > persistent_casting) InterruptSpell(); } diff --git a/zone/string_ids.h b/zone/string_ids.h index 57711a40d..ef758dba6 100644 --- a/zone/string_ids.h +++ b/zone/string_ids.h @@ -264,6 +264,7 @@ #define TRADESKILL_MISSING_COMPONENTS 3456 //Sorry, but you don't have everything you need for this recipe in your general inventory. #define TRADESKILL_LEARN_RECIPE 3457 //You have learned the recipe %1! #define EXPEDITION_MIN_REMAIN 3551 //You only have %1 minutes remaining before this expedition comes to an end. +#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! #define CORPSEDRAG_ALREADY 4062 //You are already dragging %1. @@ -295,6 +296,9 @@ #define GUILD_BANK_FULL 6098 // There is no more room in the Guild Bank. #define GUILD_BANK_TRANSFERRED 6100 // '%1' transferred to Guild Bank from Deposits. #define GUILD_BANK_EMPTY_HANDS 6108 // You must empty your hands to withdraw from the Guild Bank. +#define TRANSFORM_FAILED 6326 //This mold cannot be applied to your %1. +#define TRANSFORM_COMPLETE 6327 //You have successfully transformed your %1. +#define DETRANSFORM_FAILED 6341 //%1 has no transformation that can be removed. #define GENERIC_STRING 6688 //%1 (used to any basic message) #define SENTINEL_TRIG_YOU 6724 //You have triggered your sentinel. #define SENTINEL_TRIG_OTHER 6725 //%1 has triggered your sentinel. @@ -392,7 +396,8 @@ #define SONG_ENDS_OTHER 12688 //%1's song ends. #define SONG_ENDS_ABRUPTLY_OTHER 12689 //%1's song ends abruptly. #define DIVINE_AURA_NO_ATK 12695 //You can't attack while invulnerable! -#define TRY_ATTACKING_SOMEONE 12696 //Try attacking someone other than yourself, it's more productive. +#define TRY_ATTACKING_SOMEONE 12696 //Try attacking someone other than yourself, it's more productive +#define RANGED_TOO_CLOSE 12698 //Your target is too close to use a ranged weapon! #define BACKSTAB_WEAPON 12874 //You need a piercing weapon as your primary weapon in order to backstab #define MORE_SKILLED_THAN_I 12931 //%1 tells you, 'You are more skilled than I! What could I possibly teach you?' #define SURNAME_EXISTS 12939 //You already have a surname. Operation failed. diff --git a/zone/tradeskills.cpp b/zone/tradeskills.cpp index c7ac3339e..58774f4b1 100644 --- a/zone/tradeskills.cpp +++ b/zone/tradeskills.cpp @@ -24,17 +24,14 @@ #include //for htonl #endif -#include "masterentity.h" -#include "zonedb.h" -#include "../common/packet_functions.h" -#include "../common/packet_dump.h" -#include "titles.h" -#include "string_ids.h" #include "../common/misc_functions.h" -#include "../common/string_util.h" #include "../common/rulesys.h" -#include "quest_parser_collection.h" +#include "../common/string_util.h" #include "queryserv.h" +#include "quest_parser_collection.h" +#include "string_ids.h" +#include "titles.h" +#include "zonedb.h" extern QueryServ* QServ; @@ -283,6 +280,44 @@ void Object::HandleCombine(Client* user, const NewCombine_Struct* in_combine, Ob } container = inst; + if (container->GetItem() && container->GetItem()->BagType == BagTypeTransformationmold) { + const ItemInst* inst = container->GetItem(0); + bool AllowAll = RuleB(Inventory, AllowAnyWeaponTransformation); + if (inst && ItemInst::CanTransform(inst->GetItem(), container->GetItem(), AllowAll)) { + const Item_Struct* new_weapon = inst->GetItem(); + user->DeleteItemInInventory(Inventory::CalcSlotId(in_combine->container_slot, 0), 0, true); + container->Clear(); + user->SummonItem(new_weapon->ID, inst->GetCharges(), inst->GetAugmentItemID(0), inst->GetAugmentItemID(1), inst->GetAugmentItemID(2), inst->GetAugmentItemID(3), inst->GetAugmentItemID(4), inst->IsInstNoDrop(), MainCursor, container->GetItem()->Icon, atoi(container->GetItem()->IDFile + 2)); + user->Message_StringID(4, TRANSFORM_COMPLETE, inst->GetItem()->Name); + if (RuleB(Inventory, DeleteTransformationMold)) + user->DeleteItemInInventory(in_combine->container_slot, 0, true); + } + else if (inst) { + user->Message_StringID(4, TRANSFORM_FAILED, inst->GetItem()->Name); + } + EQApplicationPacket* outapp = new EQApplicationPacket(OP_TradeSkillCombine, 0); + user->QueuePacket(outapp); + safe_delete(outapp); + return; + } + + if (container->GetItem() && container->GetItem()->BagType == BagTypeDetransformationmold) { + const ItemInst* inst = container->GetItem(0); + if (inst && inst->GetOrnamentationIcon() && inst->GetOrnamentationIcon()) { + const Item_Struct* new_weapon = inst->GetItem(); + user->DeleteItemInInventory(Inventory::CalcSlotId(in_combine->container_slot, 0), 0, true); + container->Clear(); + user->SummonItem(new_weapon->ID, inst->GetCharges(), inst->GetAugmentItemID(0), inst->GetAugmentItemID(1), inst->GetAugmentItemID(2), inst->GetAugmentItemID(3), inst->GetAugmentItemID(4), inst->IsInstNoDrop(), MainCursor, 0, 0); + user->Message_StringID(4, TRANSFORM_COMPLETE, inst->GetItem()->Name); + } + else if (inst) { + user->Message_StringID(4, DETRANSFORM_FAILED, inst->GetItem()->Name); + } + EQApplicationPacket* outapp = new EQApplicationPacket(OP_TradeSkillCombine, 0); + user->QueuePacket(outapp); + safe_delete(outapp); + return; + } DBTradeskillRecipe_Struct spec; if (!database.GetTradeRecipe(container, c_type, some_id, user->CharacterID(), &spec)) { @@ -888,7 +923,7 @@ bool Client::TradeskillExecute(DBTradeskillRecipe_Struct *spec) { _log(TRADESKILLS__TRACE, "...Current skill: %d , Trivial: %d , Success chance: %f percent", user_skill , spec->trivial , chance); _log(TRADESKILLS__TRACE, "...Bonusstat: %d , INT: %d , WIS: %d , DEX: %d , STR: %d", bonusstat , GetINT() , GetWIS() , GetDEX() , GetSTR()); - float res = MakeRandomFloat(0, 99); + float res = zone->random.Real(0, 99); int aa_chance = 0; //AA modifiers @@ -1022,7 +1057,7 @@ bool Client::TradeskillExecute(DBTradeskillRecipe_Struct *spec) { chance = mod_tradeskill_chance(chance, spec); - if (((spec->tradeskill==75) || GetGM() || (chance > res)) || MakeRandomInt(0, 99) < aa_chance){ + if (((spec->tradeskill==75) || GetGM() || (chance > res)) || zone->random.Roll(aa_chance)) { success_modifier = 1; if(over_trivial < 0) @@ -1092,7 +1127,7 @@ bool Client::TradeskillExecute(DBTradeskillRecipe_Struct *spec) { uint8 sc = 0; while(itr != spec->salvage.end()) { for(sc = 0; sc < itr->second; sc++) - if(MakeRandomInt(0,99) < SalvageChance) + if(zone->random.Roll(SalvageChance)) SummonItem(itr->first, 1); ++itr; } @@ -1118,7 +1153,7 @@ void Client::CheckIncreaseTradeskill(int16 bonusstat, int16 stat_modifier, float //In stage2 the only thing that matters is your current unmodified skill. //If you want to customize here you probbably need to implement your own //formula instead of tweaking the below one. - if (chance_stage1 > MakeRandomFloat(0, 99)) { + if (chance_stage1 > zone->random.Real(0, 99)) { if (current_raw_skill < 15) { //Always succeed chance_stage2 = 100; @@ -1133,7 +1168,7 @@ void Client::CheckIncreaseTradeskill(int16 bonusstat, int16 stat_modifier, float chance_stage2 = mod_tradeskill_skillup(chance_stage2); - if (chance_stage2 > MakeRandomFloat(0, 99)) { + if (chance_stage2 > zone->random.Real(0, 99)) { //Only if stage1 and stage2 succeeded you get a skillup. SetSkill(tradeskill, current_raw_skill + 1); diff --git a/zone/trap.cpp b/zone/trap.cpp index 7d53bcc70..3f1927db6 100644 --- a/zone/trap.cpp +++ b/zone/trap.cpp @@ -142,7 +142,7 @@ void Trap::Trigger(Mob* trigger) { if ((tmp = database.GetNPCType(effectvalue))) { - auto randomOffset = xyz_heading(-5 + MakeRandomInt(0, 10),-5 + MakeRandomInt(0, 10),-5 + MakeRandomInt(0, 10), MakeRandomInt(0, 249)); + auto randomOffset = xyz_heading(zone->random.Int(-5, 5),zone->random.Int(-5, 5),zone->random.Int(-5, 5), zone->random.Int(0, 249)); auto spawnPosition = randomOffset + m_Position; NPC* new_npc = new NPC(tmp, nullptr, spawnPosition, FlyMode3); new_npc->AddLootTable(); @@ -165,7 +165,7 @@ void Trap::Trigger(Mob* trigger) { if ((tmp = database.GetNPCType(effectvalue))) { - auto randomOffset = xyz_heading(-2 + MakeRandomInt(0, 5), -2 + MakeRandomInt(0, 5), -2 + MakeRandomInt(0, 5), MakeRandomInt(0, 249)); + auto randomOffset = xyz_heading(zone->random.Int(-2, 2), zone->random.Int(-2, 2), zone->random.Int(-2, 2), zone->random.Int(0, 249)); auto spawnPosition = randomOffset + m_Position; NPC* new_npc = new NPC(tmp, nullptr, spawnPosition, FlyMode3); new_npc->AddLootTable(); @@ -187,10 +187,10 @@ void Trap::Trigger(Mob* trigger) { EQApplicationPacket* outapp = new EQApplicationPacket(OP_Damage, sizeof(CombatDamage_Struct)); CombatDamage_Struct* a = (CombatDamage_Struct*)outapp->pBuffer; - int dmg = MakeRandomInt(effectvalue, effectvalue2); + int dmg = zone->random.Int(effectvalue, effectvalue2); trigger->SetHP(trigger->GetHP() - dmg); a->damage = dmg; - a->sequence = MakeRandomInt(0, 1234567); + a->sequence = zone->random.Int(0, 1234567); a->source = GetHiddenTrigger()!=nullptr ? GetHiddenTrigger()->GetID() : trigger->GetID(); a->spellid = 0; a->target = trigger->GetID(); @@ -199,7 +199,7 @@ void Trap::Trigger(Mob* trigger) safe_delete(outapp); } } - respawn_timer.Start((respawn_time + MakeRandomInt(0, respawn_var)) * 1000); + respawn_timer.Start((respawn_time + zone->random.Int(0, respawn_var)) * 1000); chkarea_timer.Disable(); disarmed = true; } @@ -243,8 +243,8 @@ Mob* EntityList::GetTrapTrigger(Trap* trap) { if ((diff.m_X*diff.m_X + diff.m_Y*diff.m_Y) <= maxdist && diff.m_Z < trap->maxzdiff) { - if (MakeRandomInt(0,100) < trap->chance) - return cur; + if (zone->random.Roll(trap->chance)) + return(cur); else savemob = cur; } diff --git a/zone/trap.cpp.orig b/zone/trap.cpp.orig new file mode 100644 index 000000000..23485bf08 --- /dev/null +++ b/zone/trap.cpp.orig @@ -0,0 +1,332 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2004 EQEMu Development Team (http://eqemulator.net) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY except by those people which sell it, which + are required to give you total support for your newly bought product; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +#include "../common/debug.h" +#include "../common/types.h" +#include "entity.h" +#include "masterentity.h" +#include "../common/spdat.h" +#include "../common/misc_functions.h" +#include "../common/string_util.h" + +/* + +Schema: +CREATE TABLE traps ( + id int(11) NOT NULL auto_increment, + zone varchar(16) NOT NULL default '', + x int(11) NOT NULL default '0', + y int(11) NOT NULL default '0', + z int(11) NOT NULL default '0', + chance tinyint NOT NULL default '0', + maxzdiff float NOT NULL default '0', + radius float NOT NULL default '0', + effect int(11) NOT NULL default '0', + effectvalue int(11) NOT NULL default '0', + effectvalue2 int(11) NOT NULL default '0', + message varcahr(200) NOT NULL; + skill int(11) NOT NULL default '0', + spawnchance int(11) NOT NULL default '0', + PRIMARY KEY (id) +) TYPE=MyISAM; + + +*/ + +Trap::Trap() : + Entity(), + respawn_timer(600000), + chkarea_timer(500), + m_Position(xyz_location::Origin()) +{ + trap_id = 0; + maxzdiff = 0; + radius = 0; + effect = 0; + effectvalue = 0; + effectvalue2 = 0; + skill = 0; + level = 0; + respawn_timer.Disable(); + detected = false; + disarmed = false; + respawn_time = 0; + respawn_var = 0; + hiddenTrigger = nullptr; + ownHiddenTrigger = false; +} + +Trap::~Trap() +{ + //don't need to clean up mob as traps are always cleaned up same time as NPCs + //cleaning up mob here can actually cause a crash via race condition +} + +bool Trap::Process() +{ + if (chkarea_timer.Enabled() && chkarea_timer.Check() + /*&& zone->GetClientCount() > 0*/ ) + { + Mob* trigger = entity_list.GetTrapTrigger(this); + if (trigger && !(trigger->IsClient() && trigger->CastToClient()->GetGM())) + { + Trigger(trigger); + } + } + if (respawn_timer.Enabled() && respawn_timer.Check()) + { + detected = false; + disarmed = false; + chkarea_timer.Enable(); + respawn_timer.Disable(); + } + return true; +} + +void Trap::Trigger(Mob* trigger) +{ + int i = 0; + const NPCType* tmp = 0; + switch (effect) + { + case trapTypeDebuff: + if(message.empty()) + { + entity_list.MessageClose(trigger,false,100,13,"%s triggers a trap!",trigger->GetName()); + } + else + { + entity_list.MessageClose(trigger,false,100,13,"%s",message.c_str()); + } + if(hiddenTrigger){ + hiddenTrigger->SpellFinished(effectvalue, trigger, 10, 0, -1, spells[effectvalue].ResistDiff); + } + break; + case trapTypeAlarm: + if (message.empty()) + { + entity_list.MessageClose(trigger,false,effectvalue,13,"A loud alarm rings out through the air..."); + } + else + { + entity_list.MessageClose(trigger,false,effectvalue,13,"%s",message.c_str()); + } + + entity_list.SendAlarm(this,trigger,effectvalue); + break; + case trapTypeMysticSpawn: + if (message.empty()) + { + entity_list.MessageClose(trigger,false,100,13,"The air shimmers..."); + } + else + { + entity_list.MessageClose(trigger,false,100,13,"%s",message.c_str()); + } + + for (i = 0; i < effectvalue2; i++) + { + if ((tmp = database.GetNPCType(effectvalue))) + { +<<<<<<< HEAD + auto randomOffset = xyz_heading(-5 + MakeRandomInt(0, 10),-5 + MakeRandomInt(0, 10),-5 + MakeRandomInt(0, 10), MakeRandomInt(0, 249)); + auto spawnPosition = randomOffset + m_Position; + NPC* new_npc = new NPC(tmp, nullptr, spawnPosition, FlyMode3); +======= + NPC* new_npc = new NPC(tmp, 0, x-5+zone->random.Int(0, 10), y-5+zone->random.Int(0, 10), z-5+zone->random.Int(0, 10), zone->random.Int(0, 249), FlyMode3); +>>>>>>> master + new_npc->AddLootTable(); + entity_list.AddNPC(new_npc); + new_npc->AddToHateList(trigger,1); + } + } + break; + case trapTypeBanditSpawn: + if (message.empty()) + { + entity_list.MessageClose(trigger,false,100,13,"A bandit leaps out from behind a tree!"); + } + else + { + entity_list.MessageClose(trigger,false,100,13,"%s",message.c_str()); + } + + for (i = 0; i < effectvalue2; i++) + { + if ((tmp = database.GetNPCType(effectvalue))) + { +<<<<<<< HEAD + auto randomOffset = xyz_heading(-2 + MakeRandomInt(0, 5), -2 + MakeRandomInt(0, 5), -2 + MakeRandomInt(0, 5), MakeRandomInt(0, 249)); + auto spawnPosition = randomOffset + m_Position; + NPC* new_npc = new NPC(tmp, nullptr, spawnPosition, FlyMode3); +======= + NPC* new_npc = new NPC(tmp, 0, x-2+zone->random.Int(0, 5), y-2+zone->random.Int(0, 5), z-2+zone->random.Int(0, 5), zone->random.Int(0, 249), FlyMode3); +>>>>>>> master + new_npc->AddLootTable(); + entity_list.AddNPC(new_npc); + new_npc->AddToHateList(trigger,1); + } + } + break; + case trapTypeDamage: + if (message.empty()) + { + entity_list.MessageClose(trigger,false,100,13,"%s triggers a trap!",trigger->GetName()); + } + else + { + entity_list.MessageClose(trigger,false,100,13,"%s",message.c_str()); + } + if(trigger->IsClient()) + { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_Damage, sizeof(CombatDamage_Struct)); + CombatDamage_Struct* a = (CombatDamage_Struct*)outapp->pBuffer; + int dmg = zone->random.Int(effectvalue, effectvalue2); + trigger->SetHP(trigger->GetHP() - dmg); + a->damage = dmg; + a->sequence = zone->random.Int(0, 1234567); + a->source = GetHiddenTrigger()!=nullptr ? GetHiddenTrigger()->GetID() : trigger->GetID(); + a->spellid = 0; + a->target = trigger->GetID(); + a->type = 253; + trigger->CastToClient()->QueuePacket(outapp); + safe_delete(outapp); + } + } + respawn_timer.Start((respawn_time + zone->random.Int(0, respawn_var)) * 1000); + chkarea_timer.Disable(); + disarmed = true; +} + +Trap* EntityList::FindNearbyTrap(Mob* searcher, float max_dist) { + float dist = 999999; + Trap* current_trap = nullptr; + + float max_dist2 = max_dist*max_dist; + Trap *cur; + + for (auto it = trap_list.begin(); it != trap_list.end(); ++it) { + cur = it->second; + if(cur->disarmed) + continue; + + auto diff = searcher->GetPosition() - cur->m_Position; + float curdist = diff.m_X * diff.m_X + diff.m_Y * diff.m_Y + diff.m_Z * diff.m_Z; + + if (curdist < max_dist2 && curdist < dist) + { + dist = curdist; + current_trap = cur; + } + } + + return current_trap; +} + +Mob* EntityList::GetTrapTrigger(Trap* trap) { + Mob* savemob = 0; + + float maxdist = trap->radius * trap->radius; + + for (auto it = client_list.begin(); it != client_list.end(); ++it) { + Client* cur = it->second; + + auto diff = cur->GetPosition() - trap->m_Position; + diff.ABS_XYZ(); + + if ((diff.m_X*diff.m_X + diff.m_Y*diff.m_Y) <= maxdist + && diff.m_Z < trap->maxzdiff) + { +<<<<<<< HEAD + if (MakeRandomInt(0,100) < trap->chance) + return cur; +======= + if (zone->random.Roll(trap->chance)) + return(cur); +>>>>>>> master + else + savemob = cur; + } + + } + + return savemob; +} + +//todo: rewrite this to not need direct access to trap members. +bool ZoneDatabase::LoadTraps(const char* zonename, int16 version) { + + std::string query = StringFormat("SELECT id, x, y, z, effect, effectvalue, effectvalue2, skill, " + "maxzdiff, radius, chance, message, respawn_time, respawn_var, level " + "FROM traps WHERE zone='%s' AND version=%u", zonename, version); + auto results = QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error in LoadTraps query '%s': %s", query.c_str(), results.ErrorMessage().c_str()); + return false; + } + + for (auto row = results.begin(); row != results.end(); ++row) { + Trap* trap = new Trap(); + trap->trap_id = atoi(row[0]); + trap->m_Position = xyz_location(atof(row[1]), atof(row[2]), atof(row[3])); + trap->effect = atoi(row[4]); + trap->effectvalue = atoi(row[5]); + trap->effectvalue2 = atoi(row[6]); + trap->skill = atoi(row[7]); + trap->maxzdiff = atof(row[8]); + trap->radius = atof(row[9]); + trap->chance = atoi(row[10]); + trap->message = row[11]; + trap->respawn_time = atoi(row[12]); + trap->respawn_var = atoi(row[13]); + trap->level = atoi(row[14]); + entity_list.AddTrap(trap); + trap->CreateHiddenTrigger(); + } + + return true; +} + +void Trap::CreateHiddenTrigger() +{ + if(hiddenTrigger) + return; + + const NPCType *base_type = database.GetNPCType(500); + NPCType *make_npc = new NPCType; + memcpy(make_npc, base_type, sizeof(NPCType)); + make_npc->max_hp = 100000; + make_npc->cur_hp = 100000; + strcpy(make_npc->name, "a_trap"); + make_npc->runspeed = 0.0f; + make_npc->bodytype = BT_Special; + make_npc->race = 127; + make_npc->gender = 0; + make_npc->loottable_id = 0; + make_npc->npc_spells_id = 0; + make_npc->d_meele_texture1 = 0; + make_npc->d_meele_texture2 = 0; + make_npc->trackable = 0; + make_npc->level = level; + strcpy(make_npc->special_abilities, "19,1^20,1^24,1^25,1"); + NPC* npca = new NPC(make_npc, nullptr, xyz_heading(m_Position, 0.0f), FlyMode3); + npca->GiveNPCTypeData(make_npc); + entity_list.AddNPC(npca); + + hiddenTrigger = npca; + ownHiddenTrigger = true; +} diff --git a/zone/waypoints.cpp b/zone/waypoints.cpp index 12a81254e..e481cb0f9 100644 --- a/zone/waypoints.cpp +++ b/zone/waypoints.cpp @@ -19,21 +19,17 @@ #ifdef _EQDEBUG #include #endif -//#include -#include -#include -#include "npc.h" -#include "masterentity.h" -#include "npc_ai.h" -#include "map.h" -#include "water_map.h" -#include "../common/moremath.h" -#include "string_ids.h" -#include "../common/misc_functions.h" -#include "../common/string_util.h" -#include "../common/rulesys.h" + #include "../common/features.h" +#include "../common/misc_functions.h" +#include "../common/rulesys.h" +#include "../common/string_util.h" +#include "map.h" +#include "npc.h" #include "quest_parser_collection.h" +#include "water_map.h" +#include +#include struct wp_distance { @@ -265,7 +261,7 @@ void NPC::CalculateNewWaypoint() if(closest.size() != 0) { iter = closest.begin(); - std::advance(iter, MakeRandomInt(0, closest.size() - 1)); + std::advance(iter, zone->random.Int(0, closest.size() - 1)); cur_wp = (*iter).index; } @@ -273,7 +269,7 @@ void NPC::CalculateNewWaypoint() } case 2: //random { - cur_wp = MakeRandomInt(0, Waypoints.size() - 1); + cur_wp = zone->random.Int(0, Waypoints.size() - 1); if(cur_wp == old_wp) { if(cur_wp == (Waypoints.size() - 1)) @@ -334,7 +330,7 @@ void NPC::CalculateNewWaypoint() if(closest.size() != 0) { iter = closest.begin(); - std::advance(iter, MakeRandomInt(0, closest.size() - 1)); + std::advance(iter, zone->random.Int(0, closest.size() - 1)); cur_wp = (*iter).index; } break; @@ -407,13 +403,13 @@ void NPC::SetWaypointPause() switch (pausetype) { case 0: //Random Half - AIwalking_timer->Start((cur_wp_pause - MakeRandomInt(0, cur_wp_pause-1)/2)*1000); + AIwalking_timer->Start((cur_wp_pause - zone->random.Int(0, cur_wp_pause-1)/2)*1000); break; case 1: //Full AIwalking_timer->Start(cur_wp_pause*1000); break; case 2: //Random Full - AIwalking_timer->Start(MakeRandomInt(0, cur_wp_pause-1)*1000); + AIwalking_timer->Start(zone->random.Int(0, cur_wp_pause-1)*1000); break; } } diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index bae01e70f..d51cb0904 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -705,7 +705,7 @@ void WorldServer::Process() { _log(SPELLS__REZ, "Found corpse. Marking corpse as rezzed."); // I don't know why Rezzed is not set to true in CompleteRezz(). corpse->IsRezzed(true); - corpse->CompleteRezz(); + corpse->CompleteResurrection(); } } diff --git a/zone/worldserver.h b/zone/worldserver.h index 6feead496..c1e86421f 100644 --- a/zone/worldserver.h +++ b/zone/worldserver.h @@ -20,12 +20,10 @@ #include "../common/worldconn.h" #include "../common/eq_packet_structs.h" -#include -struct GuildJoin_Struct; +class ServerPacket; class EQApplicationPacket; class Client; -class Database; class WorldServer : public WorldConnection { public: diff --git a/zone/zone.cpp b/zone/zone.cpp index a4d746506..e0905d31e 100644 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -15,17 +15,14 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "../common/debug.h" + +#include #include -#include +#include #include #include -#include -#include -#include #ifdef _WINDOWS -#include #define snprintf _snprintf #define vsnprintf _vsnprintf #else @@ -33,31 +30,26 @@ #include "../common/unix.h" #endif -#include "masterentity.h" +#include "../common/debug.h" #include "../common/features.h" -#include "spawngroup.h" -#include "spawn2.h" -#include "zone.h" -#include "worldserver.h" -#include "npc.h" -#include "net.h" -#include "../common/seperator.h" -#include "../common/packet_dump_file.h" -#include "../common/eq_stream_factory.h" -#include "../common/eq_stream.h" -#include "../common/string_util.h" -#include "zone_config.h" -#include "../common/breakdowns.h" -#include "map.h" -#include "water_map.h" -#include "object.h" -#include "petitions.h" -#include "pathing.h" -#include "event_codes.h" -#include "client_logs.h" #include "../common/rulesys.h" +#include "../common/seperator.h" +#include "../common/string_util.h" +#include "client_logs.h" #include "guild_mgr.h" +#include "map.h" +#include "net.h" +#include "npc.h" +#include "object.h" +#include "pathing.h" +#include "petitions.h" #include "quest_parser_collection.h" +#include "spawn2.h" +#include "spawngroup.h" +#include "water_map.h" +#include "worldserver.h" +#include "zone_config.h" +#include "zone.h" #ifdef _WINDOWS #define snprintf _snprintf @@ -65,18 +57,19 @@ #define strcasecmp _stricmp #endif - +extern bool staticzone; +extern NetConnection net; +extern PetitionList petition_list; +extern QuestParserCollection* parse; +extern uint16 adverrornum; +extern uint32 numclients; extern WorldServer worldserver; extern Zone* zone; -extern uint32 numclients; -extern NetConnection net; -extern uint16 adverrornum; -extern PetitionList petition_list; + Mutex MZoneShutdown; -extern bool staticzone; -Zone* zone = 0; + volatile bool ZoneLoaded = false; -extern QuestParserCollection* parse; +Zone* zone = 0; bool Zone::Bootup(uint32 iZoneID, uint32 iInstanceID, bool iStaticZone) { const char* zonename = database.GetZoneName(iZoneID); @@ -1301,13 +1294,13 @@ void Zone::ChangeWeather() return; } - int chance = MakeRandomInt(0, 3); + int chance = zone->random.Int(0, 3); uint8 rainchance = zone->newzone_data.rain_chance[chance]; uint8 rainduration = zone->newzone_data.rain_duration[chance]; uint8 snowchance = zone->newzone_data.snow_chance[chance]; uint8 snowduration = zone->newzone_data.snow_duration[chance]; uint32 weathertimer = 0; - uint16 tmpweather = MakeRandomInt(0, 100); + uint16 tmpweather = zone->random.Int(0, 100); uint8 duration = 0; uint8 tmpOldWeather = zone->zone_weather; bool changed = false; @@ -1316,7 +1309,7 @@ void Zone::ChangeWeather() { if(rainchance > 0 || snowchance > 0) { - uint8 intensity = MakeRandomInt(1, 10); + uint8 intensity = zone->random.Int(1, 10); if((rainchance > snowchance) || (rainchance == snowchance)) { //It's gunna rain! diff --git a/zone/zone.h b/zone/zone.h index b4adf09f2..f43f2c0b1 100644 --- a/zone/zone.h +++ b/zone/zone.h @@ -18,25 +18,14 @@ #ifndef ZONE_H #define ZONE_H -#include "../common/mutex.h" -#include "../common/linked_list.h" -#include "../common/types.h" #include "../common/eqtime.h" -#include "../common/servertalk.h" +#include "../common/linked_list.h" #include "../common/rulesys.h" -#include "../common/eq_packet_structs.h" -#include "../common/features.h" -#include "spawngroup.h" -//#include "mob.h" -#include "zonedump.h" -#include "spawn2.h" -#include "tasks.h" -#include "pathing.h" +#include "../common/types.h" +#include "../common/random.h" #include "qglobals.h" -#include - -class Map; -class WaterMap; +#include "spawn2.h" +#include "spawngroup.h" struct ZonePoint { @@ -78,12 +67,10 @@ struct item_tick_struct { std::string qglobal; }; -extern EntityList entity_list; -class database; +class Map; +class WaterMap; class PathManager; -struct SendAA_Struct; - -class database; +extern EntityList entity_list; class Zone { @@ -262,6 +249,9 @@ public: void UpdateHotzone(); std::unordered_map tick_items; + // random object that provides random values for the zone + EQEmu::Random random; + //MODDING HOOKS void mod_init(); void mod_repop(); diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index 029726bef..62d299ee9 100644 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -1,21 +1,16 @@ -#include "zonedb.h" -#include "../common/item.h" -#include "../common/string_util.h" #include "../common/extprofile.h" -#include "../common/guilds.h" +#include "../common/item.h" #include "../common/rulesys.h" -#include "../common/rdtsc.h" -#include "zone.h" -#include "corpse.h" +#include "../common/string_util.h" #include "client.h" -#include "merc.h" +#include "corpse.h" #include "groups.h" -#include "raids.h" -#include -#include -#include +#include "merc.h" +#include "zone.h" +#include "zonedb.h" #include +#include extern Zone* zone; @@ -3368,7 +3363,7 @@ uint32 ZoneDatabase::GetCharacterCorpseDecayTimer(uint32 corpse_db_id){ auto results = QueryDatabase(query); auto row = results.begin(); if (results.Success() && results.RowsAffected() != 0){ - return atoll(row[0]); + return atoul(row[0]); } return 0; } @@ -3501,7 +3496,7 @@ uint32 ZoneDatabase::GetCharacterCorpseID(uint32 char_id, uint8 corpse) { for (auto row = results.begin(); row != results.end(); ++row) { for (int i = 0; i < corpse; i++) { - return atoll(row[0]); + return atoul(row[0]); } } return 0; @@ -3525,7 +3520,7 @@ uint32 ZoneDatabase::GetCharacterCorpseItemAt(uint32 corpse_id, uint16 slotid) { if (tmp) { itemid = tmp->GetWornItem(slotid); - tmp->DepopCorpse(); + tmp->DepopPlayerCorpse(); } return itemid; } @@ -3575,7 +3570,7 @@ bool ZoneDatabase::LoadCharacterCorpseData(uint32 corpse_id, PlayerCorpse_Struct uint16 i = 0; for (auto row = results.begin(); row != results.end(); ++row) { pcs->locked = atoi(row[i++]); // is_locked, - pcs->exp = atoll(row[i++]); // exp, + pcs->exp = atoul(row[i++]); // exp, pcs->size = atoi(row[i++]); // size, pcs->level = atoi(row[i++]); // `level`, pcs->race = atoi(row[i++]); // race, @@ -3584,10 +3579,10 @@ bool ZoneDatabase::LoadCharacterCorpseData(uint32 corpse_id, PlayerCorpse_Struct pcs->deity = atoi(row[i++]); // deity, pcs->texture = atoi(row[i++]); // texture, pcs->helmtexture = atoi(row[i++]); // helm_texture, - pcs->copper = atoll(row[i++]); // copper, - pcs->silver = atoll(row[i++]); // silver, - pcs->gold = atoll(row[i++]); // gold, - pcs->plat = atoll(row[i++]); // platinum, + pcs->copper = atoul(row[i++]); // copper, + pcs->silver = atoul(row[i++]); // silver, + pcs->gold = atoul(row[i++]); // gold, + pcs->plat = atoul(row[i++]); // platinum, pcs->haircolor = atoi(row[i++]); // hair_color, pcs->beardcolor = atoi(row[i++]); // beard_color, pcs->eyecolor1 = atoi(row[i++]); // eye_color_1, @@ -3595,18 +3590,18 @@ bool ZoneDatabase::LoadCharacterCorpseData(uint32 corpse_id, PlayerCorpse_Struct pcs->hairstyle = atoi(row[i++]); // hair_style, pcs->face = atoi(row[i++]); // face, pcs->beard = atoi(row[i++]); // beard, - pcs->drakkin_heritage = atoll(row[i++]); // drakkin_heritage, - pcs->drakkin_tattoo = atoll(row[i++]); // drakkin_tattoo, - pcs->drakkin_details = atoll(row[i++]); // drakkin_details, - pcs->item_tint[0].color = atoll(row[i++]); // wc_1, - pcs->item_tint[1].color = atoll(row[i++]); // wc_2, - pcs->item_tint[2].color = atoll(row[i++]); // wc_3, - pcs->item_tint[3].color = atoll(row[i++]); // wc_4, - pcs->item_tint[4].color = atoll(row[i++]); // wc_5, - pcs->item_tint[5].color = atoll(row[i++]); // wc_6, - pcs->item_tint[6].color = atoll(row[i++]); // wc_7, - pcs->item_tint[7].color = atoll(row[i++]); // wc_8, - pcs->item_tint[8].color = atoll(row[i++]); // wc_9 + pcs->drakkin_heritage = atoul(row[i++]); // drakkin_heritage, + pcs->drakkin_tattoo = atoul(row[i++]); // drakkin_tattoo, + pcs->drakkin_details = atoul(row[i++]); // drakkin_details, + pcs->item_tint[0].color = atoul(row[i++]); // wc_1, + pcs->item_tint[1].color = atoul(row[i++]); // wc_2, + pcs->item_tint[2].color = atoul(row[i++]); // wc_3, + pcs->item_tint[3].color = atoul(row[i++]); // wc_4, + pcs->item_tint[4].color = atoul(row[i++]); // wc_5, + pcs->item_tint[5].color = atoul(row[i++]); // wc_6, + pcs->item_tint[6].color = atoul(row[i++]); // wc_7, + pcs->item_tint[7].color = atoul(row[i++]); // wc_8, + pcs->item_tint[8].color = atoul(row[i++]); // wc_9 } query = StringFormat( "SELECT \n" @@ -3622,7 +3617,6 @@ bool ZoneDatabase::LoadCharacterCorpseData(uint32 corpse_id, PlayerCorpse_Struct "FROM \n" "character_corpse_items \n" "WHERE `corpse_id` = %u\n" - // "ORDER BY `equip_slot`" , corpse_id ); @@ -3634,7 +3628,7 @@ bool ZoneDatabase::LoadCharacterCorpseData(uint32 corpse_id, PlayerCorpse_Struct for (auto row = results.begin(); row != results.end(); ++row) { memset(&pcs->items[i], 0, sizeof (player_lootitem::ServerLootItem_Struct)); pcs->items[i].equip_slot = atoi(row[r++]); // equip_slot, - pcs->items[i].item_id = atoll(row[r++]); // item_id, + pcs->items[i].item_id = atoul(row[r++]); // item_id, pcs->items[i].charges = atoi(row[r++]); // charges, pcs->items[i].aug_1 = atoi(row[r++]); // aug_1, pcs->items[i].aug_2 = atoi(row[r++]); // aug_2, @@ -3658,8 +3652,8 @@ Corpse* ZoneDatabase::SummonBuriedCharacterCorpses(uint32 char_id, uint32 dest_z auto results = QueryDatabase(query); for (auto row = results.begin(); row != results.end(); ++row) { - corpse = Corpse::LoadFromDBData( - atoll(row[0]), // uint32 in_dbid + corpse = Corpse::LoadCharacterCorpseEntity( + atoul(row[0]), // uint32 in_dbid char_id, // uint32 in_charid row[1], // char* in_charname position, @@ -3673,7 +3667,7 @@ Corpse* ZoneDatabase::SummonBuriedCharacterCorpses(uint32 char_id, uint32 dest_z entity_list.AddCorpse(corpse); corpse->SetDecayTimer(RuleI(Character, CorpseDecayTimeMS)); corpse->Spawn(); - if (!UnburyCharacterCorpse(corpse->GetDBID(), dest_zone_id, dest_instance_id, position)) + if (!UnburyCharacterCorpse(corpse->GetCorpseDBID(), dest_zone_id, dest_instance_id, position)) LogFile->write(EQEMuLog::Error, "Unable to unbury a summoned player corpse for character id %u.", char_id); } @@ -3681,7 +3675,7 @@ Corpse* ZoneDatabase::SummonBuriedCharacterCorpses(uint32 char_id, uint32 dest_z } bool ZoneDatabase::SummonAllCharacterCorpses(uint32 char_id, uint32 dest_zone_id, uint16 dest_instance_id, const xyz_heading& position) { - Corpse* NewCorpse = 0; + Corpse* corpse = nullptr; int CorpseCount = 0; std::string query = StringFormat( @@ -3697,18 +3691,19 @@ bool ZoneDatabase::SummonAllCharacterCorpses(uint32 char_id, uint32 dest_zone_id results = QueryDatabase(query); for (auto row = results.begin(); row != results.end(); ++row) { - NewCorpse = Corpse::LoadFromDBData( - atoll(row[0]), + corpse = Corpse::LoadCharacterCorpseEntity( + atoul(row[0]), char_id, row[1], position, row[2], atoi(row[3]) == 1, false); - if (NewCorpse) { - entity_list.AddCorpse(NewCorpse); - NewCorpse->SetDecayTimer(RuleI(Character, CorpseDecayTimeMS)); - NewCorpse->Spawn(); + + if (corpse) { + entity_list.AddCorpse(corpse); + corpse->SetDecayTimer(RuleI(Character, CorpseDecayTimeMS)); + corpse->Spawn(); ++CorpseCount; } else{ @@ -3743,9 +3738,9 @@ Corpse* ZoneDatabase::LoadCharacterCorpse(uint32 player_corpse_id) { auto results = QueryDatabase(query); for (auto row = results.begin(); row != results.end(); ++row) { auto position = xyz_heading(atof(row[3]), atof(row[4]), atof(row[5]), atof(row[6])); - NewCorpse = Corpse::LoadFromDBData( - atoll(row[0]), // id uint32 in_dbid - atoll(row[1]), // charid uint32 in_charid + NewCorpse = Corpse::LoadCharacterCorpseEntity( + atoul(row[0]), // id uint32 in_dbid + atoul(row[1]), // charid uint32 in_charid row[2], // char_name position, row[7], // time_of_death char* time_of_death @@ -3770,9 +3765,9 @@ bool ZoneDatabase::LoadCharacterCorpses(uint32 zone_id, uint16 instance_id) { for (auto row = results.begin(); row != results.end(); ++row) { auto position = xyz_heading(atof(row[3]), atof(row[4]), atof(row[5]), atof(row[6])); entity_list.AddCorpse( - Corpse::LoadFromDBData( - atoll(row[0]), // id uint32 in_dbid - atoll(row[1]), // charid uint32 in_charid + Corpse::LoadCharacterCorpseEntity( + atoul(row[0]), // id uint32 in_dbid + atoul(row[1]), // charid uint32 in_charid row[2], // char_name position, row[7], // time_of_death char* time_of_death @@ -3793,15 +3788,6 @@ uint32 ZoneDatabase::GetFirstCorpseID(uint32 char_id) { return 0; } -bool ZoneDatabase::ClearCorpseItems(uint32 db_id){ - std::string query = StringFormat("DELETE FROM `character_corpse_items` WHERE `corpse_id` = %u", db_id); - auto results = QueryDatabase(query); - if (results.Success() && results.RowsAffected() != 0){ - return true; - } - return false; -} - bool ZoneDatabase::DeleteItemOffCharacterCorpse(uint32 db_id, uint32 equip_slot, uint32 item_id){ std::string query = StringFormat("DELETE FROM `character_corpse_items` WHERE `corpse_id` = %u AND equip_slot = %u AND item_id = %u", db_id, equip_slot, item_id); auto results = QueryDatabase(query); @@ -3815,7 +3801,6 @@ bool ZoneDatabase::BuryCharacterCorpse(uint32 db_id) { std::string query = StringFormat("UPDATE `character_corpses` SET `is_buried` = 1 WHERE `id` = %u", db_id); auto results = QueryDatabase(query); if (results.Success() && results.RowsAffected() != 0){ - ClearCorpseItems(db_id); return true; } return false; @@ -3834,8 +3819,8 @@ bool ZoneDatabase::BuryAllCharacterCorpses(uint32 char_id) { bool ZoneDatabase::DeleteCharacterCorpse(uint32 db_id) { std::string query = StringFormat("DELETE FROM `character_corpses` WHERE `id` = %d", db_id); auto results = QueryDatabase(query); - if (results.Success() && results.RowsAffected() != 0){ + if (results.Success() && results.RowsAffected() != 0) return true; - } + return false; } diff --git a/zone/zonedb.cpp.orig b/zone/zonedb.cpp.orig new file mode 100644 index 000000000..f7c701f1f --- /dev/null +++ b/zone/zonedb.cpp.orig @@ -0,0 +1,3877 @@ + +#include "../common/extprofile.h" +#include "../common/item.h" +#include "../common/rulesys.h" +#include "../common/string_util.h" +#include "client.h" +#include "corpse.h" +#include "groups.h" +#include "merc.h" +#include "zone.h" +#include "zonedb.h" +#include +#include + +extern Zone* zone; + +ZoneDatabase database; + +ZoneDatabase::ZoneDatabase() +: SharedDatabase() +{ + ZDBInitVars(); +} + +ZoneDatabase::ZoneDatabase(const char* host, const char* user, const char* passwd, const char* database, uint32 port) +: SharedDatabase(host, user, passwd, database, port) +{ + ZDBInitVars(); +} + +void ZoneDatabase::ZDBInitVars() { + memset(door_isopen_array, 0, sizeof(door_isopen_array)); + npc_spells_maxid = 0; + npc_spellseffects_maxid = 0; + npc_spells_cache = 0; + npc_spellseffects_cache = 0; + npc_spells_loadtried = 0; + npc_spellseffects_loadtried = 0; + max_faction = 0; + faction_array = nullptr; +} + +ZoneDatabase::~ZoneDatabase() { + unsigned int x; + if (npc_spells_cache) { + for (x=0; x<=npc_spells_maxid; x++) { + safe_delete_array(npc_spells_cache[x]); + } + safe_delete_array(npc_spells_cache); + } + safe_delete_array(npc_spells_loadtried); + + if (npc_spellseffects_cache) { + for (x=0; x<=npc_spellseffects_maxid; x++) { + safe_delete_array(npc_spellseffects_cache[x]); + } + safe_delete_array(npc_spellseffects_cache); + } + safe_delete_array(npc_spellseffects_loadtried); + + if (faction_array != nullptr) { + for (x=0; x <= max_faction; x++) { + if (faction_array[x] != 0) + safe_delete(faction_array[x]); + } + safe_delete_array(faction_array); + } +} + +bool ZoneDatabase::SaveZoneCFG(uint32 zoneid, uint16 instance_id, NewZone_Struct* zd) { + + std::string query = StringFormat("UPDATE zone SET underworld = %f, minclip = %f, " + "maxclip = %f, fog_minclip = %f, fog_maxclip = %f, " + "fog_blue = %i, fog_red = %i, fog_green = %i, " + "sky = %i, ztype = %i, zone_exp_multiplier = %f, " + "safe_x = %f, safe_y = %f, safe_z = %f " + "WHERE zoneidnumber = %i AND version = %i", + zd->underworld, zd->minclip, + zd->maxclip, zd->fog_minclip[0], zd->fog_maxclip[0], + zd->fog_blue[0], zd->fog_red[0], zd->fog_green[0], + zd->sky, zd->ztype, zd->zone_exp_multiplier, + zd->safe_x, zd->safe_y, zd->safe_z, + zoneid, instance_id); + auto results = QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error in SaveZoneCFG query %s: %s", query.c_str(), results.ErrorMessage().c_str()); + return false; + } + + return true; +} + +bool ZoneDatabase::GetZoneCFG(uint32 zoneid, uint16 instance_id, NewZone_Struct *zone_data, bool &can_bind, bool &can_combat, bool &can_levitate, bool &can_castoutdoor, bool &is_city, bool &is_hotzone, bool &allow_mercs, uint8 &zone_type, int &ruleset, char **map_filename) { + + *map_filename = new char[100]; + zone_data->zone_id = zoneid; + + std::string query = StringFormat("SELECT ztype, fog_red, fog_green, fog_blue, fog_minclip, fog_maxclip, " // 5 + "fog_red2, fog_green2, fog_blue2, fog_minclip2, fog_maxclip2, " // 5 + "fog_red3, fog_green3, fog_blue3, fog_minclip3, fog_maxclip3, " // 5 + "fog_red4, fog_green4, fog_blue4, fog_minclip4, fog_maxclip4, " // 5 + "fog_density, sky, zone_exp_multiplier, safe_x, safe_y, safe_z, underworld, " // 7 + "minclip, maxclip, time_type, canbind, cancombat, canlevitate, " // 6 + "castoutdoor, hotzone, ruleset, suspendbuffs, map_file_name, short_name, " // 6 + "rain_chance1, rain_chance2, rain_chance3, rain_chance4, " // 4 + "rain_duration1, rain_duration2, rain_duration3, rain_duration4, " // 4 + "snow_chance1, snow_chance2, snow_chance3, snow_chance4, " // 4 + "snow_duration1, snow_duration2, snow_duration3, snow_duration4 " // 4 + "FROM zone WHERE zoneidnumber = %i AND version = %i", zoneid, instance_id); + auto results = QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error in GetZoneCFG query %s: %s", query.c_str(), results.ErrorMessage().c_str()); + strcpy(*map_filename, "default"); + return false; + } + + if (results.RowCount() == 0) { + strcpy(*map_filename, "default"); + return false; + } + + auto row = results.begin(); + + memset(zone_data, 0, sizeof(NewZone_Struct)); + zone_data->ztype = atoi(row[0]); + zone_type = zone_data->ztype; + + int index; + for(index = 0; index < 4; index++) { + zone_data->fog_red[index]=atoi(row[1 + index * 5]); + zone_data->fog_green[index]=atoi(row[2 + index * 5]); + zone_data->fog_blue[index]=atoi(row[3 + index * 5]); + zone_data->fog_minclip[index]=atof(row[4 + index * 5]); + zone_data->fog_maxclip[index]=atof(row[5 + index * 5]); + } + + zone_data->fog_density = atof(row[21]); + zone_data->sky=atoi(row[22]); + zone_data->zone_exp_multiplier=atof(row[23]); + zone_data->safe_x=atof(row[24]); + zone_data->safe_y=atof(row[25]); + zone_data->safe_z=atof(row[26]); + zone_data->underworld=atof(row[27]); + zone_data->minclip=atof(row[28]); + zone_data->maxclip=atof(row[29]); + zone_data->time_type=atoi(row[30]); + + //not in the DB yet: + zone_data->gravity = 0.4; + allow_mercs = true; + + int bindable = 0; + bindable = atoi(row[31]); + + can_bind = bindable == 0? false: true; + is_city = bindable == 2? true: false; + can_combat = atoi(row[32]) == 0? false: true; + can_levitate = atoi(row[33]) == 0? false: true; + can_castoutdoor = atoi(row[34]) == 0? false: true; + is_hotzone = atoi(row[35]) == 0? false: true; + + + ruleset = atoi(row[36]); + zone_data->SuspendBuffs = atoi(row[37]); + + char *file = row[38]; + if(file) + strcpy(*map_filename, file); + else + strcpy(*map_filename, row[39]); + + for(index = 0; index < 4; index++) + zone_data->rain_chance[index]=atoi(row[40 + index]); + + for(index = 0; index < 4; index++) + zone_data->rain_duration[index]=atoi(row[44 + index]); + + for(index = 0; index < 4; index++) + zone_data->snow_chance[index]=atoi(row[48 + index]); + + for(index = 0; index < 4; index++) + zone_data->snow_duration[index]=atof(row[52 + index]); + + return true; +} + +//updates or clears the respawn time in the database for the current spawn id +void ZoneDatabase::UpdateSpawn2Timeleft(uint32 id, uint16 instance_id, uint32 timeleft) +{ + timeval tv; + gettimeofday(&tv, nullptr); + uint32 cur = tv.tv_sec; + + //if we pass timeleft as 0 that means we clear from respawn time + //otherwise we update with a REPLACE INTO + if(timeleft == 0) { + std::string query = StringFormat("DELETE FROM respawn_times WHERE id=%lu " + "AND instance_id = %lu",(unsigned long)id, (unsigned long)instance_id); + auto results = QueryDatabase(query); + if (!results.Success()) + LogFile->write(EQEMuLog::Error, "Error in UpdateTimeLeft query %s: %s", query.c_str(), results.ErrorMessage().c_str()); + + return; + } + + std::string query = StringFormat("REPLACE INTO respawn_times (id, start, duration, instance_id) " + "VALUES (%lu, %lu, %lu, %lu)", + (unsigned long)id, (unsigned long)cur, + (unsigned long)timeleft, (unsigned long)instance_id); + auto results = QueryDatabase(query); + if (!results.Success()) + LogFile->write(EQEMuLog::Error, "Error in UpdateTimeLeft query %s: %s", query.c_str(), results.ErrorMessage().c_str()); + + return; +} + +//Gets the respawn time left in the database for the current spawn id +uint32 ZoneDatabase::GetSpawnTimeLeft(uint32 id, uint16 instance_id) +{ + std::string query = StringFormat("SELECT start, duration FROM respawn_times " + "WHERE id = %lu AND instance_id = %lu", + (unsigned long)id, (unsigned long)zone->GetInstanceID()); + auto results = QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error in GetSpawnTimeLeft query '%s': %s", query.c_str(), results.ErrorMessage().c_str()); + return 0; + } + + if (results.RowCount() != 1) + return 0; + + auto row = results.begin(); + + timeval tv; + gettimeofday(&tv, nullptr); + uint32 resStart = atoi(row[0]); + uint32 resDuration = atoi(row[1]); + + //compare our values to current time + if((resStart + resDuration) <= tv.tv_sec) { + //our current time was expired + return 0; + } + + //we still have time left on this timer + return ((resStart + resDuration) - tv.tv_sec); + +} + +void ZoneDatabase::UpdateSpawn2Status(uint32 id, uint8 new_status) +{ + std::string query = StringFormat("UPDATE spawn2 SET enabled = %i WHERE id = %lu", new_status, (unsigned long)id); + auto results = QueryDatabase(query); + if(!results.Success()) + LogFile->write(EQEMuLog::Error, "Error in UpdateSpawn2Status query %s: %s", query.c_str(), results.ErrorMessage().c_str()); + +} + +bool ZoneDatabase::logevents(const char* accountname,uint32 accountid,uint8 status,const char* charname, const char* target,const char* descriptiontype, const char* description,int event_nid){ + + uint32 len = strlen(description); + uint32 len2 = strlen(target); + char* descriptiontext = new char[2*len+1]; + char* targetarr = new char[2*len2+1]; + memset(descriptiontext, 0, 2*len+1); + memset(targetarr, 0, 2*len2+1); + DoEscapeString(descriptiontext, description, len); + DoEscapeString(targetarr, target, len2); + + std::string query = StringFormat("INSERT INTO eventlog (accountname, accountid, status, " + "charname, target, descriptiontype, description, event_nid) " + "VALUES('%s', %i, %i, '%s', '%s', '%s', '%s', '%i')", + accountname, accountid, status, charname, targetarr, + descriptiontype, descriptiontext, event_nid); + safe_delete_array(descriptiontext); + safe_delete_array(targetarr); + auto results = QueryDatabase(query); + if (!results.Success()) { + std::cerr << "Error in logevents" << query << "' " << results.ErrorMessage() << std::endl; + return false; + } + + return true; +} + + +void ZoneDatabase::UpdateBug(BugStruct* bug) { + + uint32 len = strlen(bug->bug); + char* bugtext = nullptr; + if(len > 0) + { + bugtext = new char[2*len+1]; + memset(bugtext, 0, 2*len+1); + DoEscapeString(bugtext, bug->bug, len); + } + + len = strlen(bug->ui); + char* uitext = nullptr; + if(len > 0) + { + uitext = new char[2*len+1]; + memset(uitext, 0, 2*len+1); + DoEscapeString(uitext, bug->ui, len); + } + + len = strlen(bug->target_name); + char* targettext = nullptr; + if(len > 0) + { + targettext = new char[2*len+1]; + memset(targettext, 0, 2*len+1); + DoEscapeString(targettext, bug->target_name, len); + } + + //x and y are intentionally swapped because eq is inversexy coords + std::string query = StringFormat("INSERT INTO bugs (zone, name, ui, x, y, z, type, flag, target, bug, date) " + "VALUES('%s', '%s', '%s', '%.2f', '%.2f', '%.2f', '%s', %d, '%s', '%s', CURDATE())", + zone->GetShortName(), bug->name, uitext == nullptr ? "": uitext, + bug->x, bug->y, bug->z, bug->chartype, bug->type, targettext == nullptr? "Unknown Target": targettext, + bugtext==nullptr?"":bugtext); + safe_delete_array(bugtext); + safe_delete_array(uitext); + safe_delete_array(targettext); + auto results = QueryDatabase(query); + if (!results.Success()) + std::cerr << "Error in UpdateBug '" << query << "' " << results.ErrorMessage() << std::endl; + +} + +void ZoneDatabase::UpdateBug(PetitionBug_Struct* bug){ + + uint32 len = strlen(bug->text); + char* bugtext = new char[2*len+1]; + memset(bugtext, 0, 2*len+1); + DoEscapeString(bugtext, bug->text, len); + + std::string query = StringFormat("INSERT INTO bugs (type, name, bugtext, flag) " + "VALUES('%s', '%s', '%s', %i)", + "Petition", bug->name, bugtext, 25); + safe_delete_array(bugtext); + auto results = QueryDatabase(query); + if (!results.Success()) + std::cerr << "Error in UpdateBug '" << query << "' " << results.ErrorMessage() << std::endl; + +} + +bool ZoneDatabase::SetSpecialAttkFlag(uint8 id, const char* flag) { + + std::string query = StringFormat("UPDATE npc_types SET npcspecialattks='%s' WHERE id = %i;", flag, id); + auto results = QueryDatabase(query); + if (!results.Success()) + return false; + + return results.RowsAffected() != 0; +} + +bool ZoneDatabase::DoorIsOpen(uint8 door_id,const char* zone_name) +{ + if(door_isopen_array[door_id] == 0) { + SetDoorPlace(1,door_id,zone_name); + return false; + } + else { + SetDoorPlace(0,door_id,zone_name); + return true; + } +} + +void ZoneDatabase::SetDoorPlace(uint8 value,uint8 door_id,const char* zone_name) +{ + door_isopen_array[door_id] = value; +} + +void ZoneDatabase::GetEventLogs(const char* name,char* target,uint32 account_id,uint8 eventid,char* detail,char* timestamp, CharacterEventLog_Struct* cel) +{ + char modifications[200]; + if(strlen(name) != 0) + sprintf(modifications,"charname=\'%s\'",name); + else if(account_id != 0) + sprintf(modifications,"accountid=%i",account_id); + + if(strlen(target) != 0) + sprintf(modifications,"%s AND target LIKE \'%%%s%%\'",modifications,target); + + if(strlen(detail) != 0) + sprintf(modifications,"%s AND description LIKE \'%%%s%%\'",modifications,detail); + + if(strlen(timestamp) != 0) + sprintf(modifications,"%s AND time LIKE \'%%%s%%\'",modifications,timestamp); + + if(eventid == 0) + eventid =1; + sprintf(modifications,"%s AND event_nid=%i",modifications,eventid); + + std::string query = StringFormat("SELECT id, accountname, accountid, status, charname, target, " + "time, descriptiontype, description FROM eventlog WHERE %s", modifications); + auto results = QueryDatabase(query); + if (!results.Success()) + return; + + int index = 0; + for (auto row = results.begin(); row != results.end(); ++row, ++index) { + if(index == 255) + break; + + cel->eld[index].id = atoi(row[0]); + strn0cpy(cel->eld[index].accountname,row[1],64); + cel->eld[index].account_id = atoi(row[2]); + cel->eld[index].status = atoi(row[3]); + strn0cpy(cel->eld[index].charactername,row[4],64); + strn0cpy(cel->eld[index].targetname,row[5],64); + sprintf(cel->eld[index].timestamp,"%s",row[6]); + strn0cpy(cel->eld[index].descriptiontype,row[7],64); + strn0cpy(cel->eld[index].details,row[8],128); + cel->eventid = eventid; + cel->count = index + 1; + } + +} + +// Load child objects for a world container (i.e., forge, bag dropped to ground, etc) +void ZoneDatabase::LoadWorldContainer(uint32 parentid, ItemInst* container) +{ + if (!container) { + LogFile->write(EQEMuLog::Error, "Programming error: LoadWorldContainer passed nullptr pointer"); + return; + } + + std::string query = StringFormat("SELECT bagidx, itemid, charges, augslot1, augslot2, augslot3, augslot4, augslot5 " + "FROM object_contents WHERE parentid = %i", parentid); + auto results = QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error in DB::LoadWorldContainer: %s", results.ErrorMessage().c_str()); + return; + } + + for (auto row = results.begin(); row != results.end(); ++row) { + uint8 index = (uint8)atoi(row[0]); + uint32 item_id = (uint32)atoi(row[1]); + int8 charges = (int8)atoi(row[2]); + uint32 aug[EmuConstants::ITEM_COMMON_SIZE]; + aug[0] = (uint32)atoi(row[3]); + aug[1] = (uint32)atoi(row[4]); + aug[2] = (uint32)atoi(row[5]); + aug[3] = (uint32)atoi(row[6]); + aug[4] = (uint32)atoi(row[7]); + + ItemInst* inst = database.CreateItem(item_id, charges); + if (inst && inst->GetItem()->ItemClass == ItemClassCommon) { + for(int i = AUG_BEGIN; i < EmuConstants::ITEM_COMMON_SIZE; i++) + if (aug[i]) + inst->PutAugment(&database, i, aug[i]); + // Put item inside world container + container->PutItem(index, *inst); + safe_delete(inst); + } + } + +} + +// Save child objects for a world container (i.e., forge, bag dropped to ground, etc) +void ZoneDatabase::SaveWorldContainer(uint32 zone_id, uint32 parent_id, const ItemInst* container) +{ + // Since state is not saved for each world container action, we'll just delete + // all and save from scratch .. we may come back later to optimize + if (!container) + return; + + //Delete all items from container + DeleteWorldContainer(parent_id,zone_id); + + // Save all 10 items, if they exist + for (uint8 index = SUB_BEGIN; index < EmuConstants::ITEM_CONTAINER_SIZE; index++) { + + ItemInst* inst = container->GetItem(index); + if (!inst) + continue; + + uint32 item_id = inst->GetItem()->ID; + uint32 augslot[EmuConstants::ITEM_COMMON_SIZE] = { NO_ITEM, NO_ITEM, NO_ITEM, NO_ITEM, NO_ITEM }; + + if (inst->IsType(ItemClassCommon)) { + for(int i = AUG_BEGIN; i < EmuConstants::ITEM_COMMON_SIZE; i++) { + ItemInst *auginst=inst->GetAugment(i); + augslot[i]=(auginst && auginst->GetItem()) ? auginst->GetItem()->ID : 0; + } + } + + std::string query = StringFormat("REPLACE INTO object_contents " + "(zoneid, parentid, bagidx, itemid, charges, " + "augslot1, augslot2, augslot3, augslot4, augslot5, droptime) " + "VALUES (%i, %i, %i, %i, %i, %i, %i, %i, %i, %i, now())", + zone_id, parent_id, index, item_id, inst->GetCharges(), + augslot[0], augslot[1], augslot[2], augslot[3], augslot[4]); + auto results = QueryDatabase(query); + if (!results.Success()) + LogFile->write(EQEMuLog::Error, "Error in ZoneDatabase::SaveWorldContainer: %s", results.ErrorMessage().c_str()); + + } + +} + +// Remove all child objects inside a world container (i.e., forge, bag dropped to ground, etc) +void ZoneDatabase::DeleteWorldContainer(uint32 parent_id, uint32 zone_id) +{ + std::string query = StringFormat("DELETE FROM object_contents WHERE parentid = %i AND zoneid = %i", parent_id, zone_id); + auto results = QueryDatabase(query); + if (!results.Success()) + LogFile->write(EQEMuLog::Error, "Error in ZoneDatabase::DeleteWorldContainer: %s", results.ErrorMessage().c_str()); + +} + +Trader_Struct* ZoneDatabase::LoadTraderItem(uint32 char_id) +{ + Trader_Struct* loadti = new Trader_Struct; + memset(loadti,0,sizeof(Trader_Struct)); + + std::string query = StringFormat("SELECT * FROM trader WHERE char_id = %i ORDER BY slot_id LIMIT 80", char_id); + auto results = QueryDatabase(query); + if (!results.Success()) { + _log(TRADING__CLIENT, "Failed to load trader information!\n"); + return loadti; + } + + loadti->Code = BazaarTrader_ShowItems; + for (auto row = results.begin(); row != results.end(); ++row) { + if (atoi(row[5]) >= 80 || atoi(row[4]) < 0) { + _log(TRADING__CLIENT, "Bad Slot number when trying to load trader information!\n"); + continue; + } + + loadti->Items[atoi(row[5])] = atoi(row[1]); + loadti->ItemCost[atoi(row[5])] = atoi(row[4]); + } + return loadti; +} + +TraderCharges_Struct* ZoneDatabase::LoadTraderItemWithCharges(uint32 char_id) +{ + TraderCharges_Struct* loadti = new TraderCharges_Struct; + memset(loadti,0,sizeof(TraderCharges_Struct)); + + std::string query = StringFormat("SELECT * FROM trader WHERE char_id=%i ORDER BY slot_id LIMIT 80", char_id); + auto results = QueryDatabase(query); + if (!results.Success()) { + _log(TRADING__CLIENT, "Failed to load trader information!\n"); + return loadti; + } + + for (auto row = results.begin(); row != results.end(); ++row) { + if (atoi(row[5]) >= 80 || atoi(row[5]) < 0) { + _log(TRADING__CLIENT, "Bad Slot number when trying to load trader information!\n"); + continue; + } + + loadti->ItemID[atoi(row[5])] = atoi(row[1]); + loadti->SerialNumber[atoi(row[5])] = atoi(row[2]); + loadti->Charges[atoi(row[5])] = atoi(row[3]); + loadti->ItemCost[atoi(row[5])] = atoi(row[4]); + } + return loadti; +} + +ItemInst* ZoneDatabase::LoadSingleTraderItem(uint32 CharID, int SerialNumber) { + std::string query = StringFormat("SELECT * FROM trader WHERE char_id = %i AND serialnumber = %i " + "ORDER BY slot_id LIMIT 80", CharID, SerialNumber); + auto results = QueryDatabase(query); + if (!results.Success()) + return nullptr; + + if (results.RowCount() == 0) { + _log(TRADING__CLIENT, "Bad result from query\n"); fflush(stdout); + return nullptr; + } + + auto row = results.begin(); + + int ItemID = atoi(row[1]); + int Charges = atoi(row[3]); + int Cost = atoi(row[4]); + + const Item_Struct *item = database.GetItem(ItemID); + + if(!item) { + _log(TRADING__CLIENT, "Unable to create item\n"); + fflush(stdout); + return nullptr; + } + + if (item->NoDrop == 0) + return nullptr; + + ItemInst* inst = database.CreateItem(item); + if(!inst) { + _log(TRADING__CLIENT, "Unable to create item instance\n"); + fflush(stdout); + return nullptr; + } + + inst->SetCharges(Charges); + inst->SetSerialNumber(SerialNumber); + inst->SetMerchantSlot(SerialNumber); + inst->SetPrice(Cost); + + if(inst->IsStackable()) + inst->SetMerchantCount(Charges); + + return inst; +} + +void ZoneDatabase::SaveTraderItem(uint32 CharID, uint32 ItemID, uint32 SerialNumber, int32 Charges, uint32 ItemCost, uint8 Slot){ + + std::string query = StringFormat("REPLACE INTO trader VALUES(%i, %i, %i, %i, %i, %i)", + CharID, ItemID, SerialNumber, Charges, ItemCost, Slot); + auto results = QueryDatabase(query); + if (!results.Success()) + _log(TRADING__CLIENT, "Failed to save trader item: %i for char_id: %i, the error was: %s\n", ItemID, CharID, results.ErrorMessage().c_str()); + +} + +void ZoneDatabase::UpdateTraderItemCharges(int CharID, uint32 SerialNumber, int32 Charges) { + _log(TRADING__CLIENT, "ZoneDatabase::UpdateTraderItemCharges(%i, %i, %i)", CharID, SerialNumber, Charges); + + std::string query = StringFormat("UPDATE trader SET charges = %i WHERE char_id = %i AND serialnumber = %i", + Charges, CharID, SerialNumber); + auto results = QueryDatabase(query); + if (!results.Success()) + _log(TRADING__CLIENT, "Failed to update charges for trader item: %i for char_id: %i, the error was: %s\n", + SerialNumber, CharID, results.ErrorMessage().c_str()); + +} + +void ZoneDatabase::UpdateTraderItemPrice(int CharID, uint32 ItemID, uint32 Charges, uint32 NewPrice) { + + _log(TRADING__CLIENT, "ZoneDatabase::UpdateTraderPrice(%i, %i, %i, %i)", CharID, ItemID, Charges, NewPrice); + + const Item_Struct *item = database.GetItem(ItemID); + + if(!item) + return; + + if(NewPrice == 0) { + _log(TRADING__CLIENT, "Removing Trader items from the DB for CharID %i, ItemID %i", CharID, ItemID); + + std::string query = StringFormat("DELETE FROM trader WHERE char_id = %i AND item_id = %i",CharID, ItemID); + auto results = QueryDatabase(query); + if (!results.Success()) + _log(TRADING__CLIENT, "Failed to remove trader item(s): %i for char_id: %i, the error was: %s\n", ItemID, CharID, results.ErrorMessage().c_str()); + + return; + } + + if(!item->Stackable) { + std::string query = StringFormat("UPDATE trader SET item_cost = %i " + "WHERE char_id = %i AND item_id = %i AND charges=%i", + NewPrice, CharID, ItemID, Charges); + auto results = QueryDatabase(query); + if (!results.Success()) + _log(TRADING__CLIENT, "Failed to update price for trader item: %i for char_id: %i, the error was: %s\n", ItemID, CharID, results.ErrorMessage().c_str()); + + return; + } + + std::string query = StringFormat("UPDATE trader SET item_cost = %i " + "WHERE char_id = %i AND item_id = %i", + NewPrice, CharID, ItemID); + auto results = QueryDatabase(query); + if (!results.Success()) + _log(TRADING__CLIENT, "Failed to update price for trader item: %i for char_id: %i, the error was: %s\n", ItemID, CharID, results.ErrorMessage().c_str()); +} + +void ZoneDatabase::DeleteTraderItem(uint32 char_id){ + + if(char_id==0) { + const std::string query = "DELETE FROM trader"; + auto results = QueryDatabase(query); + if (!results.Success()) + _log(TRADING__CLIENT, "Failed to delete all trader items data, the error was: %s\n", results.ErrorMessage().c_str()); + + return; + } + + std::string query = StringFormat("DELETE FROM trader WHERE char_id = %i", char_id); + auto results = QueryDatabase(query); + if (!results.Success()) + _log(TRADING__CLIENT, "Failed to delete trader item data for char_id: %i, the error was: %s\n", char_id, results.ErrorMessage().c_str()); + +} +void ZoneDatabase::DeleteTraderItem(uint32 CharID,uint16 SlotID) { + + std::string query = StringFormat("DELETE FROM trader WHERE char_id = %i And slot_id = %i", CharID, SlotID); + auto results = QueryDatabase(query); + if (!results.Success()) + _log(TRADING__CLIENT, "Failed to delete trader item data for char_id: %i, the error was: %s\n",CharID, results.ErrorMessage().c_str()); +} + +void ZoneDatabase::DeleteBuyLines(uint32 CharID) { + + if(CharID==0) { + const std::string query = "DELETE FROM buyer"; + auto results = QueryDatabase(query); + if (!results.Success()) + _log(TRADING__CLIENT, "Failed to delete all buyer items data, the error was: %s\n",results.ErrorMessage().c_str()); + + return; + } + + std::string query = StringFormat("DELETE FROM buyer WHERE charid = %i", CharID); + auto results = QueryDatabase(query); + if (!results.Success()) + _log(TRADING__CLIENT, "Failed to delete buyer item data for charid: %i, the error was: %s\n",CharID,results.ErrorMessage().c_str()); + +} + +void ZoneDatabase::AddBuyLine(uint32 CharID, uint32 BuySlot, uint32 ItemID, const char* ItemName, uint32 Quantity, uint32 Price) { + std::string query = StringFormat("REPLACE INTO buyer VALUES(%i, %i, %i, \"%s\", %i, %i)", + CharID, BuySlot, ItemID, ItemName, Quantity, Price); + auto results = QueryDatabase(query); + if (!results.Success()) + _log(TRADING__CLIENT, "Failed to save buline item: %i for char_id: %i, the error was: %s\n", ItemID, CharID, results.ErrorMessage().c_str()); + +} + +void ZoneDatabase::RemoveBuyLine(uint32 CharID, uint32 BuySlot) { + std::string query = StringFormat("DELETE FROM buyer WHERE charid = %i AND buyslot = %i", CharID, BuySlot); + auto results = QueryDatabase(query); + if (!results.Success()) + _log(TRADING__CLIENT, "Failed to delete buyslot %i for charid: %i, the error was: %s\n", BuySlot, CharID, results.ErrorMessage().c_str()); + +} + +void ZoneDatabase::UpdateBuyLine(uint32 CharID, uint32 BuySlot, uint32 Quantity) { + if(Quantity <= 0) { + RemoveBuyLine(CharID, BuySlot); + return; + } + + std::string query = StringFormat("UPDATE buyer SET quantity = %i WHERE charid = %i AND buyslot = %i", Quantity, CharID, BuySlot); + auto results = QueryDatabase(query); + if (!results.Success()) + _log(TRADING__CLIENT, "Failed to update quantity in buyslot %i for charid: %i, the error was: %s\n", BuySlot, CharID, results.ErrorMessage().c_str()); + +} + +#define StructDist(in, f1, f2) (uint32(&in->f2)-uint32(&in->f1)) + +bool ZoneDatabase::LoadCharacterData(uint32 character_id, PlayerProfile_Struct* pp, ExtendedProfile_Struct* m_epp){ + std::string query = StringFormat( + "SELECT " + "`name`, " + "last_name, " + "gender, " + "race, " + "class, " + "`level`, " + "deity, " + "birthday, " + "last_login, " + "time_played, " + "pvp_status, " + "level2, " + "anon, " + "gm, " + "intoxication, " + "hair_color, " + "beard_color, " + "eye_color_1, " + "eye_color_2, " + "hair_style, " + "beard, " + "ability_time_seconds, " + "ability_number, " + "ability_time_minutes, " + "ability_time_hours, " + "title, " + "suffix, " + "exp, " + "points, " + "mana, " + "cur_hp, " + "str, " + "sta, " + "cha, " + "dex, " + "`int`, " + "agi, " + "wis, " + "face, " + "y, " + "x, " + "z, " + "heading, " + "pvp2, " + "pvp_type, " + "autosplit_enabled, " + "zone_change_count, " + "drakkin_heritage, " + "drakkin_tattoo, " + "drakkin_details, " + "toxicity, " + "hunger_level, " + "thirst_level, " + "ability_up, " + "zone_id, " + "zone_instance, " + "leadership_exp_on, " + "ldon_points_guk, " + "ldon_points_mir, " + "ldon_points_mmc, " + "ldon_points_ruj, " + "ldon_points_tak, " + "ldon_points_available, " + "tribute_time_remaining, " + "show_helm, " + "career_tribute_points, " + "tribute_points, " + "tribute_active, " + "endurance, " + "group_leadership_exp, " + "raid_leadership_exp, " + "group_leadership_points, " + "raid_leadership_points, " + "air_remaining, " + "pvp_kills, " + "pvp_deaths, " + "pvp_current_points, " + "pvp_career_points, " + "pvp_best_kill_streak, " + "pvp_worst_death_streak, " + "pvp_current_kill_streak, " + "aa_points_spent, " + "aa_exp, " + "aa_points, " + "group_auto_consent, " + "raid_auto_consent, " + "guild_auto_consent, " + "RestTimer, " + "`e_aa_effects`, " + "`e_percent_to_aa`, " + "`e_expended_aa_spent` " + "FROM " + "character_data " + "WHERE `id` = %i ", character_id); + auto results = database.QueryDatabase(query); int r = 0; + for (auto row = results.begin(); row != results.end(); ++row) { + strcpy(pp->name, row[r]); r++; // "`name`, " + strcpy(pp->last_name, row[r]); r++; // "last_name, " + pp->gender = atoi(row[r]); r++; // "gender, " + pp->race = atoi(row[r]); r++; // "race, " + pp->class_ = atoi(row[r]); r++; // "class, " + pp->level = atoi(row[r]); r++; // "`level`, " + pp->deity = atoi(row[r]); r++; // "deity, " + pp->birthday = atoi(row[r]); r++; // "birthday, " + pp->lastlogin = atoi(row[r]); r++; // "last_login, " + pp->timePlayedMin = atoi(row[r]); r++; // "time_played, " + pp->pvp = atoi(row[r]); r++; // "pvp_status, " + pp->level2 = atoi(row[r]); r++; // "level2, " + pp->anon = atoi(row[r]); r++; // "anon, " + pp->gm = atoi(row[r]); r++; // "gm, " + pp->intoxication = atoi(row[r]); r++; // "intoxication, " + pp->haircolor = atoi(row[r]); r++; // "hair_color, " + pp->beardcolor = atoi(row[r]); r++; // "beard_color, " + pp->eyecolor1 = atoi(row[r]); r++; // "eye_color_1, " + pp->eyecolor2 = atoi(row[r]); r++; // "eye_color_2, " + pp->hairstyle = atoi(row[r]); r++; // "hair_style, " + pp->beard = atoi(row[r]); r++; // "beard, " + pp->ability_time_seconds = atoi(row[r]); r++; // "ability_time_seconds, " + pp->ability_number = atoi(row[r]); r++; // "ability_number, " + pp->ability_time_minutes = atoi(row[r]); r++; // "ability_time_minutes, " + pp->ability_time_hours = atoi(row[r]); r++; // "ability_time_hours, " + strcpy(pp->title, row[r]); r++; // "title, " + strcpy(pp->suffix, row[r]); r++; // "suffix, " + pp->exp = atoi(row[r]); r++; // "exp, " + pp->points = atoi(row[r]); r++; // "points, " + pp->mana = atoi(row[r]); r++; // "mana, " + pp->cur_hp = atoi(row[r]); r++; // "cur_hp, " + pp->STR = atoi(row[r]); r++; // "str, " + pp->STA = atoi(row[r]); r++; // "sta, " + pp->CHA = atoi(row[r]); r++; // "cha, " + pp->DEX = atoi(row[r]); r++; // "dex, " + pp->INT = atoi(row[r]); r++; // "`int`, " + pp->AGI = atoi(row[r]); r++; // "agi, " + pp->WIS = atoi(row[r]); r++; // "wis, " + pp->face = atoi(row[r]); r++; // "face, " + pp->y = atof(row[r]); r++; // "y, " + pp->x = atof(row[r]); r++; // "x, " + pp->z = atof(row[r]); r++; // "z, " + pp->heading = atof(row[r]); r++; // "heading, " + pp->pvp2 = atoi(row[r]); r++; // "pvp2, " + pp->pvptype = atoi(row[r]); r++; // "pvp_type, " + pp->autosplit = atoi(row[r]); r++; // "autosplit_enabled, " + pp->zone_change_count = atoi(row[r]); r++; // "zone_change_count, " + pp->drakkin_heritage = atoi(row[r]); r++; // "drakkin_heritage, " + pp->drakkin_tattoo = atoi(row[r]); r++; // "drakkin_tattoo, " + pp->drakkin_details = atoi(row[r]); r++; // "drakkin_details, " + pp->toxicity = atoi(row[r]); r++; // "toxicity, " + pp->hunger_level = atoi(row[r]); r++; // "hunger_level, " + pp->thirst_level = atoi(row[r]); r++; // "thirst_level, " + pp->ability_up = atoi(row[r]); r++; // "ability_up, " + pp->zone_id = atoi(row[r]); r++; // "zone_id, " + pp->zoneInstance = atoi(row[r]); r++; // "zone_instance, " + pp->leadAAActive = atoi(row[r]); r++; // "leadership_exp_on, " + pp->ldon_points_guk = atoi(row[r]); r++; // "ldon_points_guk, " + pp->ldon_points_mir = atoi(row[r]); r++; // "ldon_points_mir, " + pp->ldon_points_mmc = atoi(row[r]); r++; // "ldon_points_mmc, " + pp->ldon_points_ruj = atoi(row[r]); r++; // "ldon_points_ruj, " + pp->ldon_points_tak = atoi(row[r]); r++; // "ldon_points_tak, " + pp->ldon_points_available = atoi(row[r]); r++; // "ldon_points_available, " + pp->tribute_time_remaining = atoi(row[r]); r++; // "tribute_time_remaining, " + pp->showhelm = atoi(row[r]); r++; // "show_helm, " + pp->career_tribute_points = atoi(row[r]); r++; // "career_tribute_points, " + pp->tribute_points = atoi(row[r]); r++; // "tribute_points, " + pp->tribute_active = atoi(row[r]); r++; // "tribute_active, " + pp->endurance = atoi(row[r]); r++; // "endurance, " + pp->group_leadership_exp = atoi(row[r]); r++; // "group_leadership_exp, " + pp->raid_leadership_exp = atoi(row[r]); r++; // "raid_leadership_exp, " + pp->group_leadership_points = atoi(row[r]); r++; // "group_leadership_points, " + pp->raid_leadership_points = atoi(row[r]); r++; // "raid_leadership_points, " + pp->air_remaining = atoi(row[r]); r++; // "air_remaining, " + pp->PVPKills = atoi(row[r]); r++; // "pvp_kills, " + pp->PVPDeaths = atoi(row[r]); r++; // "pvp_deaths, " + pp->PVPCurrentPoints = atoi(row[r]); r++; // "pvp_current_points, " + pp->PVPCareerPoints = atoi(row[r]); r++; // "pvp_career_points, " + pp->PVPBestKillStreak = atoi(row[r]); r++; // "pvp_best_kill_streak, " + pp->PVPWorstDeathStreak = atoi(row[r]); r++; // "pvp_worst_death_streak, " + pp->PVPCurrentKillStreak = atoi(row[r]); r++; // "pvp_current_kill_streak, " + pp->aapoints_spent = atoi(row[r]); r++; // "aa_points_spent, " + pp->expAA = atoi(row[r]); r++; // "aa_exp, " + pp->aapoints = atoi(row[r]); r++; // "aa_points, " + pp->groupAutoconsent = atoi(row[r]); r++; // "group_auto_consent, " + pp->raidAutoconsent = atoi(row[r]); r++; // "raid_auto_consent, " + pp->guildAutoconsent = atoi(row[r]); r++; // "guild_auto_consent, " + pp->RestTimer = atoi(row[r]); r++; // "RestTimer, " + m_epp->aa_effects = atoi(row[r]); r++; // "`e_aa_effects`, " + m_epp->perAA = atoi(row[r]); r++; // "`e_percent_to_aa`, " + m_epp->expended_aa = atoi(row[r]); r++; // "`e_expended_aa_spent` " + } + return true; +} + +bool ZoneDatabase::LoadCharacterFactionValues(uint32 character_id, faction_map & val_list) { + std::string query = StringFormat("SELECT `faction_id`, `current_value` FROM `faction_values` WHERE `char_id` = %i", character_id); + auto results = database.QueryDatabase(query); + for (auto row = results.begin(); row != results.end(); ++row) { val_list[atoi(row[0])] = atoi(row[1]); } + return true; +} + +bool ZoneDatabase::LoadCharacterMemmedSpells(uint32 character_id, PlayerProfile_Struct* pp){ + std::string query = StringFormat( + "SELECT " + "slot_id, " + "`spell_id` " + "FROM " + "`character_memmed_spells` " + "WHERE `id` = %u ORDER BY `slot_id`", character_id); + auto results = database.QueryDatabase(query); + int i = 0; + /* Initialize Spells */ + for (i = 0; i < MAX_PP_MEMSPELL; i++){ + pp->mem_spells[i] = 0xFFFFFFFF; + } + for (auto row = results.begin(); row != results.end(); ++row) { + i = atoi(row[0]); + if (i < MAX_PP_MEMSPELL && atoi(row[1]) <= SPDAT_RECORDS){ + pp->mem_spells[i] = atoi(row[1]); + } + } + return true; +} + +bool ZoneDatabase::LoadCharacterSpellBook(uint32 character_id, PlayerProfile_Struct* pp){ + std::string query = StringFormat( + "SELECT " + "slot_id, " + "`spell_id` " + "FROM " + "`character_spells` " + "WHERE `id` = %u ORDER BY `slot_id`", character_id); + auto results = database.QueryDatabase(query); + int i = 0; + /* Initialize Spells */ + for (i = 0; i < MAX_PP_SPELLBOOK; i++){ + pp->spell_book[i] = 0xFFFFFFFF; + } + for (auto row = results.begin(); row != results.end(); ++row) { + i = atoi(row[0]); + if (i < MAX_PP_SPELLBOOK && atoi(row[1]) <= SPDAT_RECORDS){ + pp->spell_book[i] = atoi(row[1]); + } + } + return true; +} + +bool ZoneDatabase::LoadCharacterLanguages(uint32 character_id, PlayerProfile_Struct* pp){ + std::string query = StringFormat( + "SELECT " + "lang_id, " + "`value` " + "FROM " + "`character_languages` " + "WHERE `id` = %u ORDER BY `lang_id`", character_id); + auto results = database.QueryDatabase(query); int i = 0; + /* Initialize Languages */ + for (i = 0; i < MAX_PP_LANGUAGE; i++){ + pp->languages[i] = 0; + } + for (auto row = results.begin(); row != results.end(); ++row) { + i = atoi(row[0]); + if (i < MAX_PP_LANGUAGE){ + pp->languages[i] = atoi(row[1]); + } + } + return true; +} + +bool ZoneDatabase::LoadCharacterLeadershipAA(uint32 character_id, PlayerProfile_Struct* pp){ + std::string query = StringFormat("SELECT slot, rank FROM character_leadership_abilities WHERE `id` = %u", character_id); + auto results = database.QueryDatabase(query); uint32 slot = 0; + for (auto row = results.begin(); row != results.end(); ++row) { + slot = atoi(row[0]); + pp->leader_abilities.ranks[slot] = atoi(row[1]); + } + return true; +} + +bool ZoneDatabase::LoadCharacterDisciplines(uint32 character_id, PlayerProfile_Struct* pp){ + std::string query = StringFormat( + "SELECT " + "disc_id " + "FROM " + "`character_disciplines`" + "WHERE `id` = %u ORDER BY `slot_id`", character_id); + auto results = database.QueryDatabase(query); + int i = 0; + /* Initialize Disciplines */ + memset(pp->disciplines.values, 0, (sizeof(pp->disciplines.values[0]) * MAX_PP_DISCIPLINES)); + for (auto row = results.begin(); row != results.end(); ++row) { + if (i < MAX_PP_DISCIPLINES){ + pp->disciplines.values[i] = atoi(row[0]); + } + i++; + } + return true; +} + +bool ZoneDatabase::LoadCharacterSkills(uint32 character_id, PlayerProfile_Struct* pp){ + std::string query = StringFormat( + "SELECT " + "skill_id, " + "`value` " + "FROM " + "`character_skills` " + "WHERE `id` = %u ORDER BY `skill_id`", character_id); + auto results = database.QueryDatabase(query); int i = 0; + /* Initialize Skill */ + for (i = 0; i < MAX_PP_SKILL; i++){ + pp->skills[i] = 0; + } + for (auto row = results.begin(); row != results.end(); ++row) { + i = atoi(row[0]); + if (i < MAX_PP_SKILL){ + pp->skills[i] = atoi(row[1]); + } + } + return true; +} + +bool ZoneDatabase::LoadCharacterCurrency(uint32 character_id, PlayerProfile_Struct* pp){ + std::string query = StringFormat( + "SELECT " + "platinum, " + "gold, " + "silver, " + "copper, " + "platinum_bank, " + "gold_bank, " + "silver_bank, " + "copper_bank, " + "platinum_cursor, " + "gold_cursor, " + "silver_cursor, " + "copper_cursor, " + "radiant_crystals, " + "career_radiant_crystals," + "ebon_crystals, " + "career_ebon_crystals " + "FROM " + "character_currency " + "WHERE `id` = %i ", character_id); + auto results = database.QueryDatabase(query); + for (auto row = results.begin(); row != results.end(); ++row) { + pp->platinum = atoi(row[0]); + pp->gold = atoi(row[1]); + pp->silver = atoi(row[2]); + pp->copper = atoi(row[3]); + pp->platinum_bank = atoi(row[4]); + pp->gold_bank = atoi(row[5]); + pp->silver_bank = atoi(row[6]); + pp->copper_bank = atoi(row[7]); + pp->platinum_cursor = atoi(row[8]); + pp->gold_cursor = atoi(row[9]); + pp->silver_cursor = atoi(row[10]); + pp->copper_cursor = atoi(row[11]); + pp->currentRadCrystals = atoi(row[12]); + pp->careerRadCrystals = atoi(row[13]); + pp->currentEbonCrystals = atoi(row[14]); + pp->careerEbonCrystals = atoi(row[15]); + } + return true; +} + +bool ZoneDatabase::LoadCharacterMaterialColor(uint32 character_id, PlayerProfile_Struct* pp){ + std::string query = StringFormat("SELECT slot, blue, green, red, use_tint, color FROM `character_material` WHERE `id` = %u LIMIT 9", character_id); + auto results = database.QueryDatabase(query); int i = 0; int r = 0; + for (auto row = results.begin(); row != results.end(); ++row) { + r = 0; + i = atoi(row[r]); /* Slot */ r++; + pp->item_tint[i].rgb.blue = atoi(row[r]); r++; + pp->item_tint[i].rgb.green = atoi(row[r]); r++; + pp->item_tint[i].rgb.red = atoi(row[r]); r++; + pp->item_tint[i].rgb.use_tint = atoi(row[r]); + } + return true; +} + +bool ZoneDatabase::LoadCharacterBandolier(uint32 character_id, PlayerProfile_Struct* pp){ + std::string query = StringFormat("SELECT `bandolier_id`, `bandolier_slot`, `item_id`, `icon`, `bandolier_name` FROM `character_bandolier` WHERE `id` = %u LIMIT 16", character_id); + auto results = database.QueryDatabase(query); int i = 0; int r = 0; int si = 0; + for (i = 0; i <= EmuConstants::BANDOLIERS_COUNT; i++){ + for (int si = 0; si < EmuConstants::BANDOLIER_SIZE; si++){ + pp->bandoliers[i].items[si].icon = 0; + } + } + + for (auto row = results.begin(); row != results.end(); ++row) { + r = 0; + i = atoi(row[r]); /* Bandolier ID */ r++; + si = atoi(row[r]); /* Bandolier Slot */ r++; + pp->bandoliers[i].items[si].item_id = atoi(row[r]); r++; + pp->bandoliers[i].items[si].icon = atoi(row[r]); r++; + strcpy(pp->bandoliers[i].name, row[r]); r++; + si++; + } + return true; +} + +bool ZoneDatabase::LoadCharacterTribute(uint32 character_id, PlayerProfile_Struct* pp){ + std::string query = StringFormat("SELECT `tier`, `tribute` FROM `character_tribute` WHERE `id` = %u", character_id); + auto results = database.QueryDatabase(query); + int i = 0; + for (i = 0; i < EmuConstants::TRIBUTE_SIZE; i++){ + pp->tributes[i].tribute = 0xFFFFFFFF; + pp->tributes[i].tier = 0; + } + i = 0; + for (auto row = results.begin(); row != results.end(); ++row) { + if(atoi(row[1]) != TRIBUTE_NONE){ + pp->tributes[i].tier = atoi(row[0]); + pp->tributes[i].tribute = atoi(row[1]); + i++; + } + } + return true; +} + +bool ZoneDatabase::LoadCharacterPotions(uint32 character_id, PlayerProfile_Struct* pp){ + std::string query = StringFormat("SELECT `potion_id`, `item_id`, `icon` FROM `character_potionbelt` WHERE `id` = %u LIMIT 4", character_id); + auto results = database.QueryDatabase(query); int i = 0; + for (i = 0; i < EmuConstants::POTION_BELT_SIZE; i++){ + pp->potionbelt.items[i].icon = 0; + pp->potionbelt.items[i].item_id = 0; + strncpy(pp->potionbelt.items[i].item_name, "\0", 1); + } + for (auto row = results.begin(); row != results.end(); ++row) { + i = atoi(row[0]); /* Potion belt slot number */ + uint32 item_id = atoi(row[1]); + const Item_Struct *item = database.GetItem(item_id); + + if(item) { + pp->potionbelt.items[i].item_id = item_id; + pp->potionbelt.items[i].icon = atoi(row[2]); + strncpy(pp->potionbelt.items[i].item_name, item->Name, 64); + } + } + return true; +} + +bool ZoneDatabase::LoadCharacterBindPoint(uint32 character_id, PlayerProfile_Struct* pp){ + std::string query = StringFormat("SELECT `zone_id`, `instance_id`, `x`, `y`, `z`, `heading`, `is_home` FROM `character_bind` WHERE `id` = %u LIMIT 2", character_id); + auto results = database.QueryDatabase(query); int i = 0; + for (auto row = results.begin(); row != results.end(); ++row) { + i = 0; + /* Is home bind */ + if (atoi(row[6]) == 1){ + pp->binds[4].zoneId = atoi(row[i++]); + pp->binds[4].instance_id = atoi(row[i++]); + pp->binds[4].x = atoi(row[i++]); + pp->binds[4].y = atoi(row[i++]); + pp->binds[4].z = atoi(row[i++]); + pp->binds[4].heading = atoi(row[i++]); + } + /* Is regular bind point */ + else{ + pp->binds[0].zoneId = atoi(row[i++]); + pp->binds[0].instance_id = atoi(row[i++]); + pp->binds[0].x = atoi(row[i++]); + pp->binds[0].y = atoi(row[i++]); + pp->binds[0].z = atoi(row[i++]); + pp->binds[0].heading = atoi(row[i++]); + } + } + return true; +} + +bool ZoneDatabase::SaveCharacterLanguage(uint32 character_id, uint32 lang_id, uint32 value){ + std::string query = StringFormat("REPLACE INTO `character_languages` (id, lang_id, value) VALUES (%u, %u, %u)", character_id, lang_id, value); QueryDatabase(query); + LogFile->write(EQEMuLog::Debug, "ZoneDatabase::SaveCharacterLanguage for character ID: %i, lang_id:%u value:%u done", character_id, lang_id, value); + return true; +} + +bool ZoneDatabase::SaveCharacterBindPoint(uint32 character_id, uint32 zone_id, uint32 instance_id, const xyz_heading& position, uint8 is_home){ + if (zone_id <= 0) { + return false; + } + + /* Save Home Bind Point */ + std::string query = StringFormat("REPLACE INTO `character_bind` (id, zone_id, instance_id, x, y, z, heading, is_home)" + " VALUES (%u, %u, %u, %f, %f, %f, %f, %i)", character_id, zone_id, instance_id, position.m_X, position.m_Y, position.m_Z, position.m_Heading, is_home); + LogFile->write(EQEMuLog::Debug, "ZoneDatabase::SaveCharacterBindPoint for character ID: %i zone_id: %u instance_id: %u position: %s ishome: %u", character_id, zone_id, instance_id, to_string(position).c_str(), is_home); + auto results = QueryDatabase(query); + if (!results.RowsAffected()) { + LogFile->write(EQEMuLog::Debug, "ERROR Bind Home Save: %s. %s", results.ErrorMessage().c_str(), query.c_str()); + } + return true; +} + +bool ZoneDatabase::SaveCharacterMaterialColor(uint32 character_id, uint32 slot_id, uint32 color){ + uint8 red = (color & 0x00FF0000) >> 16; + uint8 green = (color & 0x0000FF00) >> 8; + uint8 blue = (color & 0x000000FF); + + std::string query = StringFormat("REPLACE INTO `character_material` (id, slot, red, green, blue, color, use_tint) VALUES (%u, %u, %u, %u, %u, %u, 255)", character_id, slot_id, red, green, blue, color); auto results = QueryDatabase(query); + LogFile->write(EQEMuLog::Debug, "ZoneDatabase::SaveCharacterMaterialColor for character ID: %i, slot_id: %u color: %u done", character_id, slot_id, color); + return true; +} + +bool ZoneDatabase::SaveCharacterSkill(uint32 character_id, uint32 skill_id, uint32 value){ + std::string query = StringFormat("REPLACE INTO `character_skills` (id, skill_id, value) VALUES (%u, %u, %u)", character_id, skill_id, value); auto results = QueryDatabase(query); + LogFile->write(EQEMuLog::Debug, "ZoneDatabase::SaveCharacterSkill for character ID: %i, skill_id:%u value:%u done", character_id, skill_id, value); + return true; +} + +bool ZoneDatabase::SaveCharacterDisc(uint32 character_id, uint32 slot_id, uint32 disc_id){ + std::string query = StringFormat("REPLACE INTO `character_disciplines` (id, slot_id, disc_id) VALUES (%u, %u, %u)", character_id, slot_id, disc_id); + auto results = QueryDatabase(query); + LogFile->write(EQEMuLog::Debug, "ZoneDatabase::SaveCharacterDisc for character ID: %i, slot:%u disc_id:%u done", character_id, slot_id, disc_id); + return true; +} + +bool ZoneDatabase::SaveCharacterTribute(uint32 character_id, PlayerProfile_Struct* pp){ + std::string query = StringFormat("DELETE FROM `character_tribute` WHERE `id` = %u", character_id); + QueryDatabase(query); + /* Save Tributes only if we have values... */ + for (int i = 0; i < EmuConstants::TRIBUTE_SIZE; i++){ + if (pp->tributes[i].tribute > 0 && pp->tributes[i].tribute != TRIBUTE_NONE){ + std::string query = StringFormat("REPLACE INTO `character_tribute` (id, tier, tribute) VALUES (%u, %u, %u)", character_id, pp->tributes[i].tier, pp->tributes[i].tribute); + QueryDatabase(query); + LogFile->write(EQEMuLog::Debug, "ZoneDatabase::SaveCharacterTribute for character ID: %i, tier:%u tribute:%u done", character_id, pp->tributes[i].tier, pp->tributes[i].tribute); + } + } + return true; +} + +bool ZoneDatabase::SaveCharacterBandolier(uint32 character_id, uint8 bandolier_id, uint8 bandolier_slot, uint32 item_id, uint32 icon, const char* bandolier_name){ + char bandolier_name_esc[64]; + DoEscapeString(bandolier_name_esc, bandolier_name, strlen(bandolier_name)); + std::string query = StringFormat("REPLACE INTO `character_bandolier` (id, bandolier_id, bandolier_slot, item_id, icon, bandolier_name) VALUES (%u, %u, %u, %u, %u,'%s')", character_id, bandolier_id, bandolier_slot, item_id, icon, bandolier_name_esc); + auto results = QueryDatabase(query); + LogFile->write(EQEMuLog::Debug, "ZoneDatabase::SaveCharacterBandolier for character ID: %i, bandolier_id: %u, bandolier_slot: %u item_id: %u, icon:%u band_name:%s done", character_id, bandolier_id, bandolier_slot, item_id, icon, bandolier_name); + if (!results.RowsAffected()){ std::cout << "ERROR Bandolier Save: " << results.ErrorMessage() << "\n\n" << query << "\n" << std::endl; } + return true; +} + +bool ZoneDatabase::SaveCharacterPotionBelt(uint32 character_id, uint8 potion_id, uint32 item_id, uint32 icon) { + std::string query = StringFormat("REPLACE INTO `character_potionbelt` (id, potion_id, item_id, icon) VALUES (%u, %u, %u, %u)", character_id, potion_id, item_id, icon); + auto results = QueryDatabase(query); + if (!results.RowsAffected()){ std::cout << "ERROR Potionbelt Save: " << results.ErrorMessage() << "\n\n" << query << "\n" << std::endl; } + return true; +} + +bool ZoneDatabase::SaveCharacterLeadershipAA(uint32 character_id, PlayerProfile_Struct* pp){ + uint8 first_entry = 0; std::string query = ""; + for (int i = 0; i < MAX_LEADERSHIP_AA_ARRAY; i++){ + if (pp->leader_abilities.ranks[i] > 0){ + if (first_entry != 1){ + query = StringFormat("REPLACE INTO `character_leadership_abilities` (id, slot, rank) VALUES (%i, %u, %u)", character_id, i, pp->leader_abilities.ranks[i]); + first_entry = 1; + } + query = query + StringFormat(", (%i, %u, %u)", character_id, i, pp->leader_abilities.ranks[i]); + } + } + auto results = QueryDatabase(query); + return true; +} + +bool ZoneDatabase::SaveCharacterData(uint32 character_id, uint32 account_id, PlayerProfile_Struct* pp, ExtendedProfile_Struct* m_epp){ + clock_t t = std::clock(); /* Function timer start */ + std::string query = StringFormat( + "REPLACE INTO `character_data` (" + " id, " + " account_id, " + " `name`, " + " last_name, " + " gender, " + " race, " + " class, " + " `level`, " + " deity, " + " birthday, " + " last_login, " + " time_played, " + " pvp_status, " + " level2, " + " anon, " + " gm, " + " intoxication, " + " hair_color, " + " beard_color, " + " eye_color_1, " + " eye_color_2, " + " hair_style, " + " beard, " + " ability_time_seconds, " + " ability_number, " + " ability_time_minutes, " + " ability_time_hours, " + " title, " + " suffix, " + " exp, " + " points, " + " mana, " + " cur_hp, " + " str, " + " sta, " + " cha, " + " dex, " + " `int`, " + " agi, " + " wis, " + " face, " + " y, " + " x, " + " z, " + " heading, " + " pvp2, " + " pvp_type, " + " autosplit_enabled, " + " zone_change_count, " + " drakkin_heritage, " + " drakkin_tattoo, " + " drakkin_details, " + " toxicity, " + " hunger_level, " + " thirst_level, " + " ability_up, " + " zone_id, " + " zone_instance, " + " leadership_exp_on, " + " ldon_points_guk, " + " ldon_points_mir, " + " ldon_points_mmc, " + " ldon_points_ruj, " + " ldon_points_tak, " + " ldon_points_available, " + " tribute_time_remaining, " + " show_helm, " + " career_tribute_points, " + " tribute_points, " + " tribute_active, " + " endurance, " + " group_leadership_exp, " + " raid_leadership_exp, " + " group_leadership_points, " + " raid_leadership_points, " + " air_remaining, " + " pvp_kills, " + " pvp_deaths, " + " pvp_current_points, " + " pvp_career_points, " + " pvp_best_kill_streak, " + " pvp_worst_death_streak, " + " pvp_current_kill_streak, " + " aa_points_spent, " + " aa_exp, " + " aa_points, " + " group_auto_consent, " + " raid_auto_consent, " + " guild_auto_consent, " + " RestTimer, " + " e_aa_effects, " + " e_percent_to_aa, " + " e_expended_aa_spent " + ") " + "VALUES (" + "%u," // id " id, " + "%u," // account_id " account_id, " + "'%s'," // `name` pp->name, " `name`, " + "'%s'," // last_name pp->last_name, " last_name, " + "%u," // gender pp->gender, " gender, " + "%u," // race pp->race, " race, " + "%u," // class pp->class_, " class, " + "%u," // `level` pp->level, " `level`, " + "%u," // deity pp->deity, " deity, " + "%u," // birthday pp->birthday, " birthday, " + "%u," // last_login pp->lastlogin, " last_login, " + "%u," // time_played pp->timePlayedMin, " time_played, " + "%u," // pvp_status pp->pvp, " pvp_status, " + "%u," // level2 pp->level2, " level2, " + "%u," // anon pp->anon, " anon, " + "%u," // gm pp->gm, " gm, " + "%u," // intoxication pp->intoxication, " intoxication, " + "%u," // hair_color pp->haircolor, " hair_color, " + "%u," // beard_color pp->beardcolor, " beard_color, " + "%u," // eye_color_1 pp->eyecolor1, " eye_color_1, " + "%u," // eye_color_2 pp->eyecolor2, " eye_color_2, " + "%u," // hair_style pp->hairstyle, " hair_style, " + "%u," // beard pp->beard, " beard, " + "%u," // ability_time_seconds pp->ability_time_seconds, " ability_time_seconds, " + "%u," // ability_number pp->ability_number, " ability_number, " + "%u," // ability_time_minutes pp->ability_time_minutes, " ability_time_minutes, " + "%u," // ability_time_hours pp->ability_time_hours, " ability_time_hours, " + "'%s'," // title pp->title, " title, " " + "'%s'," // suffix pp->suffix, " suffix, " + "%u," // exp pp->exp, " exp, " + "%u," // points pp->points, " points, " + "%u," // mana pp->mana, " mana, " + "%u," // cur_hp pp->cur_hp, " cur_hp, " + "%u," // str pp->STR, " str, " + "%u," // sta pp->STA, " sta, " + "%u," // cha pp->CHA, " cha, " + "%u," // dex pp->DEX, " dex, " + "%u," // `int` pp->INT, " `int`, " + "%u," // agi pp->AGI, " agi, " + "%u," // wis pp->WIS, " wis, " + "%u," // face pp->face, " face, " + "%f," // y pp->y, " y, " + "%f," // x pp->x, " x, " + "%f," // z pp->z, " z, " + "%f," // heading pp->heading, " heading, " + "%u," // pvp2 pp->pvp2, " pvp2, " + "%u," // pvp_type pp->pvptype, " pvp_type, " + "%u," // autosplit_enabled pp->autosplit, " autosplit_enabled, " + "%u," // zone_change_count pp->zone_change_count, " zone_change_count, " + "%u," // drakkin_heritage pp->drakkin_heritage, " drakkin_heritage, " + "%u," // drakkin_tattoo pp->drakkin_tattoo, " drakkin_tattoo, " + "%u," // drakkin_details pp->drakkin_details, " drakkin_details, " + "%i," // toxicity pp->toxicity, " toxicity, " + "%i," // hunger_level pp->hunger_level, " hunger_level, " + "%i," // thirst_level pp->thirst_level, " thirst_level, " + "%u," // ability_up pp->ability_up, " ability_up, " + "%u," // zone_id pp->zone_id, " zone_id, " + "%u," // zone_instance pp->zoneInstance, " zone_instance, " + "%u," // leadership_exp_on pp->leadAAActive, " leadership_exp_on, " + "%u," // ldon_points_guk pp->ldon_points_guk, " ldon_points_guk, " + "%u," // ldon_points_mir pp->ldon_points_mir, " ldon_points_mir, " + "%u," // ldon_points_mmc pp->ldon_points_mmc, " ldon_points_mmc, " + "%u," // ldon_points_ruj pp->ldon_points_ruj, " ldon_points_ruj, " + "%u," // ldon_points_tak pp->ldon_points_tak, " ldon_points_tak, " + "%u," // ldon_points_available pp->ldon_points_available, " ldon_points_available, " + "%u," // tribute_time_remaining pp->tribute_time_remaining, " tribute_time_remaining, " + "%u," // show_helm pp->showhelm, " show_helm, " + "%u," // career_tribute_points pp->career_tribute_points, " career_tribute_points, " + "%u," // tribute_points pp->tribute_points, " tribute_points, " + "%u," // tribute_active pp->tribute_active, " tribute_active, " + "%u," // endurance pp->endurance, " endurance, " + "%u," // group_leadership_exp pp->group_leadership_exp, " group_leadership_exp, " + "%u," // raid_leadership_exp pp->raid_leadership_exp, " raid_leadership_exp, " + "%u," // group_leadership_points pp->group_leadership_points, " group_leadership_points, " + "%u," // raid_leadership_points pp->raid_leadership_points, " raid_leadership_points, " + "%u," // air_remaining pp->air_remaining, " air_remaining, " + "%u," // pvp_kills pp->PVPKills, " pvp_kills, " + "%u," // pvp_deaths pp->PVPDeaths, " pvp_deaths, " + "%u," // pvp_current_points pp->PVPCurrentPoints, " pvp_current_points, " + "%u," // pvp_career_points pp->PVPCareerPoints, " pvp_career_points, " + "%u," // pvp_best_kill_streak pp->PVPBestKillStreak, " pvp_best_kill_streak, " + "%u," // pvp_worst_death_streak pp->PVPWorstDeathStreak, " pvp_worst_death_streak, " + "%u," // pvp_current_kill_streak pp->PVPCurrentKillStreak, " pvp_current_kill_streak, " + "%u," // aa_points_spent pp->aapoints_spent, " aa_points_spent, " + "%u," // aa_exp pp->expAA, " aa_exp, " + "%u," // aa_points pp->aapoints, " aa_points, " + "%u," // group_auto_consent pp->groupAutoconsent, " group_auto_consent, " + "%u," // raid_auto_consent pp->raidAutoconsent, " raid_auto_consent, " + "%u," // guild_auto_consent pp->guildAutoconsent, " guild_auto_consent, " + "%u," // RestTimer pp->RestTimer, " RestTimer) " + "%u," // e_aa_effects + "%u," // e_percent_to_aa + "%u" // e_expended_aa_spent + ")", + character_id, // " id, " + account_id, // " account_id, " + EscapeString(pp->name).c_str(), // " `name`, " + EscapeString(pp->last_name).c_str(), // " last_name, " + pp->gender, // " gender, " + pp->race, // " race, " + pp->class_, // " class, " + pp->level, // " `level`, " + pp->deity, // " deity, " + pp->birthday, // " birthday, " + pp->lastlogin, // " last_login, " + pp->timePlayedMin, // " time_played, " + pp->pvp, // " pvp_status, " + pp->level2, // " level2, " + pp->anon, // " anon, " + pp->gm, // " gm, " + pp->intoxication, // " intoxication, " + pp->haircolor, // " hair_color, " + pp->beardcolor, // " beard_color, " + pp->eyecolor1, // " eye_color_1, " + pp->eyecolor2, // " eye_color_2, " + pp->hairstyle, // " hair_style, " + pp->beard, // " beard, " + pp->ability_time_seconds, // " ability_time_seconds, " + pp->ability_number, // " ability_number, " + pp->ability_time_minutes, // " ability_time_minutes, " + pp->ability_time_hours, // " ability_time_hours, " + EscapeString(pp->title).c_str(), // " title, " + EscapeString(pp->suffix).c_str(), // " suffix, " + pp->exp, // " exp, " + pp->points, // " points, " + pp->mana, // " mana, " + pp->cur_hp, // " cur_hp, " + pp->STR, // " str, " + pp->STA, // " sta, " + pp->CHA, // " cha, " + pp->DEX, // " dex, " + pp->INT, // " `int`, " + pp->AGI, // " agi, " + pp->WIS, // " wis, " + pp->face, // " face, " + pp->y, // " y, " + pp->x, // " x, " + pp->z, // " z, " + pp->heading, // " heading, " + pp->pvp2, // " pvp2, " + pp->pvptype, // " pvp_type, " + pp->autosplit, // " autosplit_enabled, " + pp->zone_change_count, // " zone_change_count, " + pp->drakkin_heritage, // " drakkin_heritage, " + pp->drakkin_tattoo, // " drakkin_tattoo, " + pp->drakkin_details, // " drakkin_details, " + pp->toxicity, // " toxicity, " + pp->hunger_level, // " hunger_level, " + pp->thirst_level, // " thirst_level, " + pp->ability_up, // " ability_up, " + pp->zone_id, // " zone_id, " + pp->zoneInstance, // " zone_instance, " + pp->leadAAActive, // " leadership_exp_on, " + pp->ldon_points_guk, // " ldon_points_guk, " + pp->ldon_points_mir, // " ldon_points_mir, " + pp->ldon_points_mmc, // " ldon_points_mmc, " + pp->ldon_points_ruj, // " ldon_points_ruj, " + pp->ldon_points_tak, // " ldon_points_tak, " + pp->ldon_points_available, // " ldon_points_available, " + pp->tribute_time_remaining, // " tribute_time_remaining, " + pp->showhelm, // " show_helm, " + pp->career_tribute_points, // " career_tribute_points, " + pp->tribute_points, // " tribute_points, " + pp->tribute_active, // " tribute_active, " + pp->endurance, // " endurance, " + pp->group_leadership_exp, // " group_leadership_exp, " + pp->raid_leadership_exp, // " raid_leadership_exp, " + pp->group_leadership_points, // " group_leadership_points, " + pp->raid_leadership_points, // " raid_leadership_points, " + pp->air_remaining, // " air_remaining, " + pp->PVPKills, // " pvp_kills, " + pp->PVPDeaths, // " pvp_deaths, " + pp->PVPCurrentPoints, // " pvp_current_points, " + pp->PVPCareerPoints, // " pvp_career_points, " + pp->PVPBestKillStreak, // " pvp_best_kill_streak, " + pp->PVPWorstDeathStreak, // " pvp_worst_death_streak, " + pp->PVPCurrentKillStreak, // " pvp_current_kill_streak, " + pp->aapoints_spent, // " aa_points_spent, " + pp->expAA, // " aa_exp, " + pp->aapoints, // " aa_points, " + pp->groupAutoconsent, // " group_auto_consent, " + pp->raidAutoconsent, // " raid_auto_consent, " + pp->guildAutoconsent, // " guild_auto_consent, " + pp->RestTimer, // " RestTimer) " + m_epp->aa_effects, + m_epp->perAA, + m_epp->expended_aa + ); + auto results = database.QueryDatabase(query); + LogFile->write(EQEMuLog::Debug, "ZoneDatabase::SaveCharacterData %i, done... Took %f seconds", character_id, ((float)(std::clock() - t)) / CLOCKS_PER_SEC); + return true; +} + +bool ZoneDatabase::SaveCharacterCurrency(uint32 character_id, PlayerProfile_Struct* pp){ + if (pp->copper < 0) { pp->copper = 0; } + if (pp->silver < 0) { pp->silver = 0; } + if (pp->gold < 0) { pp->gold = 0; } + if (pp->platinum < 0) { pp->platinum = 0; } + if (pp->copper_bank < 0) { pp->copper_bank = 0; } + if (pp->silver_bank < 0) { pp->silver_bank = 0; } + if (pp->gold_bank < 0) { pp->gold_bank = 0; } + if (pp->platinum_bank < 0) { pp->platinum_bank = 0; } + if (pp->platinum_cursor < 0) { pp->platinum_cursor = 0; } + if (pp->gold_cursor < 0) { pp->gold_cursor = 0; } + if (pp->silver_cursor < 0) { pp->silver_cursor = 0; } + if (pp->copper_cursor < 0) { pp->copper_cursor = 0; } + std::string query = StringFormat( + "REPLACE INTO `character_currency` (id, platinum, gold, silver, copper," + "platinum_bank, gold_bank, silver_bank, copper_bank," + "platinum_cursor, gold_cursor, silver_cursor, copper_cursor, " + "radiant_crystals, career_radiant_crystals, ebon_crystals, career_ebon_crystals)" + "VALUES (%u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u)", + character_id, + pp->platinum, + pp->gold, + pp->silver, + pp->copper, + pp->platinum_bank, + pp->gold_bank, + pp->silver_bank, + pp->copper_bank, + pp->platinum_cursor, + pp->gold_cursor, + pp->silver_cursor, + pp->copper_cursor, + pp->currentRadCrystals, + pp->careerRadCrystals, + pp->currentEbonCrystals, + pp->careerEbonCrystals); + auto results = database.QueryDatabase(query); + LogFile->write(EQEMuLog::Debug, "Saving Currency for character ID: %i, done", character_id); + return true; +} + +bool ZoneDatabase::SaveCharacterAA(uint32 character_id, uint32 aa_id, uint32 current_level){ + std::string rquery = StringFormat("REPLACE INTO `character_alternate_abilities` (id, aa_id, aa_value)" + " VALUES (%u, %u, %u)", + character_id, aa_id, current_level); + auto results = QueryDatabase(rquery); + LogFile->write(EQEMuLog::Debug, "Saving AA for character ID: %u, aa_id: %u current_level: %u", character_id, aa_id, current_level); + return true; +} + +bool ZoneDatabase::SaveCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id){ + if (spell_id > SPDAT_RECORDS){ return false; } + std::string query = StringFormat("REPLACE INTO `character_memmed_spells` (id, slot_id, spell_id) VALUES (%u, %u, %u)", character_id, slot_id, spell_id); + QueryDatabase(query); + return true; +} + +bool ZoneDatabase::SaveCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id){ + if (spell_id > SPDAT_RECORDS){ return false; } + std::string query = StringFormat("REPLACE INTO `character_spells` (id, slot_id, spell_id) VALUES (%u, %u, %u)", character_id, slot_id, spell_id); + QueryDatabase(query); + return true; +} + +bool ZoneDatabase::DeleteCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id){ + std::string query = StringFormat("DELETE FROM `character_spells` WHERE `slot_id` = %u AND `id` = %u", slot_id, character_id); + QueryDatabase(query); + return true; +} + +bool ZoneDatabase::DeleteCharacterDisc(uint32 character_id, uint32 slot_id){ + std::string query = StringFormat("DELETE FROM `character_disciplines` WHERE `slot_id` = %u AND `id` = %u", slot_id, character_id); + QueryDatabase(query); + return true; +} + +bool ZoneDatabase::DeleteCharacterBandolier(uint32 character_id, uint32 band_id){ + std::string query = StringFormat("DELETE FROM `character_bandolier` WHERE `bandolier_id` = %u AND `id` = %u", band_id, character_id); + QueryDatabase(query); + return true; +} + +bool ZoneDatabase::DeleteCharacterLeadershipAAs(uint32 character_id){ + std::string query = StringFormat("DELETE FROM `character_leadership_abilities` WHERE `id` = %u", character_id); + QueryDatabase(query); + return true; +} + +bool ZoneDatabase::DeleteCharacterAAs(uint32 character_id){ + std::string query = StringFormat("DELETE FROM `character_alternate_abilities` WHERE `id` = %u", character_id); + QueryDatabase(query); + return true; +} + +bool ZoneDatabase::DeleteCharacterDye(uint32 character_id){ + std::string query = StringFormat("DELETE FROM `character_material` WHERE `id` = %u", character_id); + QueryDatabase(query); + return true; +} + +bool ZoneDatabase::DeleteCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id){ + std::string query = StringFormat("DELETE FROM `character_memmed_spells` WHERE `slot_id` = %u AND `id` = %u", slot_id, character_id); + QueryDatabase(query); + return true; +} + +bool ZoneDatabase::NoRentExpired(const char* name){ + std::string query = StringFormat("SELECT (UNIX_TIMESTAMP(NOW()) - last_login) FROM `character_data` WHERE name = '%s'", name); + auto results = QueryDatabase(query); + if (!results.Success()) + return false; + + if (results.RowCount() != 1) + return false; + + auto row = results.begin(); + uint32 seconds = atoi(row[0]); + + return (seconds>1800); +} + +/* Searches npctable for matching id, and returns the item if found, + * or nullptr otherwise. If id passed is 0, loads all npc_types for + * the current zone, returning the last item added. + */ +const NPCType* ZoneDatabase::GetNPCType (uint32 id) { + const NPCType *npc=nullptr; + + // If NPC is already in tree, return it. + auto itr = zone->npctable.find(id); + if(itr != zone->npctable.end()) + return itr->second; + + // Otherwise, get NPCs from database. + + + // If id is 0, load all npc_types for the current zone, + // according to spawn2. + std::string query = StringFormat("SELECT npc_types.id, npc_types.name, npc_types.level, npc_types.race, " + "npc_types.class, npc_types.hp, npc_types.mana, npc_types.gender, " + "npc_types.texture, npc_types.helmtexture, npc_types.size, " + "npc_types.loottable_id, npc_types.merchant_id, npc_types.alt_currency_id, " + "npc_types.adventure_template_id, npc_types.trap_template, npc_types.attack_speed, " + "npc_types.STR, npc_types.STA, npc_types.DEX, npc_types.AGI, npc_types._INT, " + "npc_types.WIS, npc_types.CHA, npc_types.MR, npc_types.CR, npc_types.DR, " + "npc_types.FR, npc_types.PR, npc_types.Corrup, npc_types.PhR," + "npc_types.mindmg, npc_types.maxdmg, npc_types.attack_count, npc_types.special_abilities," + "npc_types.npc_spells_id, npc_types.npc_spells_effects_id, npc_types.d_meele_texture1," + "npc_types.d_meele_texture2, npc_types.ammo_idfile, npc_types.prim_melee_type," + "npc_types.sec_melee_type, npc_types.ranged_type, npc_types.runspeed, npc_types.findable," + "npc_types.trackable, npc_types.hp_regen_rate, npc_types.mana_regen_rate, " + "npc_types.aggroradius, npc_types.assistradius, npc_types.bodytype, npc_types.npc_faction_id, " + "npc_types.face, npc_types.luclin_hairstyle, npc_types.luclin_haircolor, " + "npc_types.luclin_eyecolor, npc_types.luclin_eyecolor2, npc_types.luclin_beardcolor," + "npc_types.luclin_beard, npc_types.drakkin_heritage, npc_types.drakkin_tattoo, " + "npc_types.drakkin_details, npc_types.armortint_id, " + "npc_types.armortint_red, npc_types.armortint_green, npc_types.armortint_blue, " + "npc_types.see_invis, npc_types.see_invis_undead, npc_types.lastname, " + "npc_types.qglobal, npc_types.AC, npc_types.npc_aggro, npc_types.spawn_limit, " + "npc_types.see_hide, npc_types.see_improved_hide, npc_types.ATK, npc_types.Accuracy, " + "npc_types.Avoidance, npc_types.slow_mitigation, npc_types.maxlevel, npc_types.scalerate, " + "npc_types.private_corpse, npc_types.unique_spawn_by_name, npc_types.underwater, " + "npc_types.emoteid, npc_types.spellscale, npc_types.healscale, npc_types.no_target_hotkey," + "npc_types.raid_target, npc_types.attack_delay FROM npc_types WHERE id = %d", id); + + auto results = QueryDatabase(query); + if (!results.Success()) { + std::cerr << "Error loading NPCs from database. Bad query: " << results.ErrorMessage() << std::endl; + return nullptr; + } + + + for (auto row = results.begin(); row != results.end(); ++row) { + NPCType *tmpNPCType; + tmpNPCType = new NPCType; + memset (tmpNPCType, 0, sizeof *tmpNPCType); + + tmpNPCType->npc_id = atoi(row[0]); + + strn0cpy(tmpNPCType->name, row[1], 50); + + tmpNPCType->level = atoi(row[2]); + tmpNPCType->race = atoi(row[3]); + tmpNPCType->class_ = atoi(row[4]); + tmpNPCType->max_hp = atoi(row[5]); + tmpNPCType->cur_hp = tmpNPCType->max_hp; + tmpNPCType->Mana = atoi(row[6]); + tmpNPCType->gender = atoi(row[7]); + tmpNPCType->texture = atoi(row[8]); + tmpNPCType->helmtexture = atoi(row[9]); + tmpNPCType->size = atof(row[10]); + tmpNPCType->loottable_id = atoi(row[11]); + tmpNPCType->merchanttype = atoi(row[12]); + tmpNPCType->alt_currency_type = atoi(row[13]); + tmpNPCType->adventure_template = atoi(row[14]); + tmpNPCType->trap_template = atoi(row[15]); + tmpNPCType->attack_speed = atof(row[16]); + tmpNPCType->STR = atoi(row[17]); + tmpNPCType->STA = atoi(row[18]); + tmpNPCType->DEX = atoi(row[19]); + tmpNPCType->AGI = atoi(row[20]); + tmpNPCType->INT = atoi(row[21]); + tmpNPCType->WIS = atoi(row[22]); + tmpNPCType->CHA = atoi(row[23]); + tmpNPCType->MR = atoi(row[24]); + tmpNPCType->CR = atoi(row[25]); + tmpNPCType->DR = atoi(row[26]); + tmpNPCType->FR = atoi(row[27]); + tmpNPCType->PR = atoi(row[28]); + tmpNPCType->Corrup = atoi(row[29]); + tmpNPCType->PhR = atoi(row[30]); + tmpNPCType->min_dmg = atoi(row[31]); + tmpNPCType->max_dmg = atoi(row[32]); + tmpNPCType->attack_count = atoi(row[33]); + + if (row[34] != nullptr) + strn0cpy(tmpNPCType->special_abilities, row[34], 512); + else + tmpNPCType->special_abilities[0] = '\0'; + + tmpNPCType->npc_spells_id = atoi(row[35]); + tmpNPCType->npc_spells_effects_id = atoi(row[36]); + tmpNPCType->d_meele_texture1 = atoi(row[37]); + tmpNPCType->d_meele_texture2 = atoi(row[38]); + strn0cpy(tmpNPCType->ammo_idfile, row[39], 30); + tmpNPCType->prim_melee_type = atoi(row[40]); + tmpNPCType->sec_melee_type = atoi(row[41]); + tmpNPCType->ranged_type = atoi(row[42]); + tmpNPCType->runspeed= atof(row[43]); + tmpNPCType->findable = atoi(row[44]) == 0? false : true; + tmpNPCType->trackable = atoi(row[45]) == 0? false : true; + tmpNPCType->hp_regen = atoi(row[46]); + tmpNPCType->mana_regen = atoi(row[47]); + + // set defaultvalue for aggroradius + tmpNPCType->aggroradius = (int32)atoi(row[48]); + if (tmpNPCType->aggroradius <= 0) + tmpNPCType->aggroradius = 70; + + tmpNPCType->assistradius = (int32)atoi(row[49]); + if (tmpNPCType->assistradius <= 0) + tmpNPCType->assistradius = tmpNPCType->aggroradius; + + if (row[50] && strlen(row[50])) + tmpNPCType->bodytype = (uint8)atoi(row[50]); + else + tmpNPCType->bodytype = 0; + + tmpNPCType->npc_faction_id = atoi(row[51]); + + tmpNPCType->luclinface = atoi(row[52]); + tmpNPCType->hairstyle = atoi(row[53]); + tmpNPCType->haircolor = atoi(row[54]); + tmpNPCType->eyecolor1 = atoi(row[55]); + tmpNPCType->eyecolor2 = atoi(row[56]); + tmpNPCType->beardcolor = atoi(row[57]); + tmpNPCType->beard = atoi(row[58]); + tmpNPCType->drakkin_heritage = atoi(row[59]); + tmpNPCType->drakkin_tattoo = atoi(row[60]); + tmpNPCType->drakkin_details = atoi(row[61]); + + uint32 armor_tint_id = atoi(row[62]); + + tmpNPCType->armor_tint[0] = (atoi(row[63]) & 0xFF) << 16; + tmpNPCType->armor_tint[0] |= (atoi(row[64]) & 0xFF) << 8; + tmpNPCType->armor_tint[0] |= (atoi(row[65]) & 0xFF); + tmpNPCType->armor_tint[0] |= (tmpNPCType->armor_tint[0]) ? (0xFF << 24) : 0; + + if (armor_tint_id == 0) + for (int index = MaterialChest; index <= EmuConstants::MATERIAL_END; index++) + tmpNPCType->armor_tint[index] = tmpNPCType->armor_tint[0]; + else if (tmpNPCType->armor_tint[0] == 0) + { + std::string armortint_query = StringFormat("SELECT red1h, grn1h, blu1h, " + "red2c, grn2c, blu2c, " + "red3a, grn3a, blu3a, " + "red4b, grn4b, blu4b, " + "red5g, grn5g, blu5g, " + "red6l, grn6l, blu6l, " + "red7f, grn7f, blu7f, " + "red8x, grn8x, blu8x, " + "red9x, grn9x, blu9x " + "FROM npc_types_tint WHERE id = %d", + armor_tint_id); + auto armortint_results = QueryDatabase(armortint_query); + if (!armortint_results.Success() || armortint_results.RowCount() == 0) + armor_tint_id = 0; + else { + auto armorTint_row = armortint_results.begin(); + + for (int index = EmuConstants::MATERIAL_BEGIN; index <= EmuConstants::MATERIAL_END; index++) { + tmpNPCType->armor_tint[index] = atoi(armorTint_row[index * 3]) << 16; + tmpNPCType->armor_tint[index] |= atoi(armorTint_row[index * 3 + 1]) << 8; + tmpNPCType->armor_tint[index] |= atoi(armorTint_row[index * 3 + 2]); + tmpNPCType->armor_tint[index] |= (tmpNPCType->armor_tint[index]) ? (0xFF << 24) : 0; + } + } + } else + armor_tint_id = 0; + + tmpNPCType->see_invis = atoi(row[66]); + tmpNPCType->see_invis_undead = atoi(row[67]) == 0? false: true; // Set see_invis_undead flag + if (row[68] != nullptr) + strn0cpy(tmpNPCType->lastname, row[68], 32); + + tmpNPCType->qglobal = atoi(row[69]) == 0? false: true; // qglobal + tmpNPCType->AC = atoi(row[70]); + tmpNPCType->npc_aggro = atoi(row[71]) == 0? false: true; + tmpNPCType->spawn_limit = atoi(row[72]); + tmpNPCType->see_hide = atoi(row[73]) == 0? false: true; + tmpNPCType->see_improved_hide = atoi(row[74]) == 0? false: true; + tmpNPCType->ATK = atoi(row[75]); + tmpNPCType->accuracy_rating = atoi(row[76]); + tmpNPCType->avoidance_rating = atoi(row[77]); + tmpNPCType->slow_mitigation = atoi(row[78]); + tmpNPCType->maxlevel = atoi(row[79]); + tmpNPCType->scalerate = atoi(row[80]); + tmpNPCType->private_corpse = atoi(row[81]) == 1 ? true: false; + tmpNPCType->unique_spawn_by_name = atoi(row[82]) == 1 ? true: false; + tmpNPCType->underwater = atoi(row[83]) == 1 ? true: false; + tmpNPCType->emoteid = atoi(row[84]); + tmpNPCType->spellscale = atoi(row[85]); + tmpNPCType->healscale = atoi(row[86]); + tmpNPCType->no_target_hotkey = atoi(row[87]) == 1 ? true: false; + tmpNPCType->raid_target = atoi(row[88]) == 0 ? false: true; + tmpNPCType->attack_delay = atoi(row[89]); + + // If NPC with duplicate NPC id already in table, + // free item we attempted to add. + if (zone->npctable.find(tmpNPCType->npc_id) != zone->npctable.end()) { + std::cerr << "Error loading duplicate NPC " << tmpNPCType->npc_id << std::endl; + delete tmpNPCType; + return nullptr; + } + + zone->npctable[tmpNPCType->npc_id]=tmpNPCType; + npc = tmpNPCType; + } + + return npc; +} + +const NPCType* ZoneDatabase::GetMercType(uint32 id, uint16 raceid, uint32 clientlevel) { + + //need to save based on merc_npc_type & client level + uint32 merc_type_id = id * 100 + clientlevel; + + // If NPC is already in tree, return it. + auto itr = zone->merctable.find(merc_type_id); + if(itr != zone->merctable.end()) + return itr->second; + + //If the NPC type is 0, return nullptr. (sanity check) + if(id == 0) + return nullptr; + + // Otherwise, get NPCs from database. + // If id is 0, load all npc_types for the current zone, + // according to spawn2. + std::string query = StringFormat("SELECT vwMercNpcTypes.merc_npc_type_id, vwMercNpcTypes.name, " + "vwMercNpcTypes.level, vwMercNpcTypes.race_id, vwMercNpcTypes.class_id, " + "vwMercNpcTypes.hp, vwMercNpcTypes.mana, vwMercNpcTypes.gender, " + "vwMercNpcTypes.texture, vwMercNpcTypes.helmtexture, vwMercNpcTypes.attack_speed, " + "vwMercNpcTypes.STR, vwMercNpcTypes.STA, vwMercNpcTypes.DEX, vwMercNpcTypes.AGI, " + "vwMercNpcTypes._INT, vwMercNpcTypes.WIS, vwMercNpcTypes.CHA, vwMercNpcTypes.MR, " + "vwMercNpcTypes.CR, vwMercNpcTypes.DR, vwMercNpcTypes.FR, vwMercNpcTypes.PR, " + "vwMercNpcTypes.Corrup, vwMercNpcTypes.mindmg, vwMercNpcTypes.maxdmg, " + "vwMercNpcTypes.attack_count, vwMercNpcTypes.special_abilities, " + "vwMercNpcTypes.d_meele_texture1, vwMercNpcTypes.d_meele_texture2, " + "vwMercNpcTypes.prim_melee_type, vwMercNpcTypes.sec_melee_type, " + "vwMercNpcTypes.runspeed, vwMercNpcTypes.hp_regen_rate, vwMercNpcTypes.mana_regen_rate, " + "vwMercNpcTypes.bodytype, vwMercNpcTypes.armortint_id, " + "vwMercNpcTypes.armortint_red, vwMercNpcTypes.armortint_green, vwMercNpcTypes.armortint_blue, " + "vwMercNpcTypes.AC, vwMercNpcTypes.ATK, vwMercNpcTypes.Accuracy, vwMercNpcTypes.spellscale, " + "vwMercNpcTypes.healscale FROM vwMercNpcTypes " + "WHERE merc_npc_type_id = %d AND clientlevel = %d AND race_id = %d", + id, clientlevel, raceid); //dual primary keys. one is ID, one is level. + auto results = QueryDatabase(query); + if (!results.Success()) { + std::cerr << "Error loading NPCs from database. Bad query: " << results.ErrorMessage() << std::endl; + return nullptr; + } + + const NPCType *npc; + + // Process each row returned. + for (auto row = results.begin(); row != results.end(); ++row) { + NPCType *tmpNPCType; + tmpNPCType = new NPCType; + memset (tmpNPCType, 0, sizeof *tmpNPCType); + + tmpNPCType->npc_id = atoi(row[0]); + + strn0cpy(tmpNPCType->name, row[1], 50); + + tmpNPCType->level = atoi(row[2]); + tmpNPCType->race = atoi(row[3]); + tmpNPCType->class_ = atoi(row[4]); + tmpNPCType->max_hp = atoi(row[5]); + tmpNPCType->cur_hp = tmpNPCType->max_hp; + tmpNPCType->Mana = atoi(row[6]); + tmpNPCType->gender = atoi(row[7]); + tmpNPCType->texture = atoi(row[8]); + tmpNPCType->helmtexture = atoi(row[9]); + tmpNPCType->attack_speed = atof(row[10]); + tmpNPCType->STR = atoi(row[11]); + tmpNPCType->STA = atoi(row[12]); + tmpNPCType->DEX = atoi(row[13]); + tmpNPCType->AGI = atoi(row[14]); + tmpNPCType->INT = atoi(row[15]); + tmpNPCType->WIS = atoi(row[16]); + tmpNPCType->CHA = atoi(row[17]); + tmpNPCType->MR = atoi(row[18]); + tmpNPCType->CR = atoi(row[19]); + tmpNPCType->DR = atoi(row[20]); + tmpNPCType->FR = atoi(row[21]); + tmpNPCType->PR = atoi(row[22]); + tmpNPCType->Corrup = atoi(row[23]); + tmpNPCType->min_dmg = atoi(row[24]); + tmpNPCType->max_dmg = atoi(row[25]); + tmpNPCType->attack_count = atoi(row[26]); + + if (row[27] != nullptr) + strn0cpy(tmpNPCType->special_abilities, row[27], 512); + else + tmpNPCType->special_abilities[0] = '\0'; + + tmpNPCType->d_meele_texture1 = atoi(row[28]); + tmpNPCType->d_meele_texture2 = atoi(row[29]); + tmpNPCType->prim_melee_type = atoi(row[30]); + tmpNPCType->sec_melee_type = atoi(row[31]); + tmpNPCType->runspeed= atof(row[32]); + + tmpNPCType->hp_regen = atoi(row[33]); + tmpNPCType->mana_regen = atoi(row[34]); + + tmpNPCType->aggroradius = RuleI(Mercs, AggroRadius); + + if (row[35] && strlen(row[35])) + tmpNPCType->bodytype = (uint8)atoi(row[35]); + else + tmpNPCType->bodytype = 1; + + uint32 armor_tint_id = atoi(row[36]); + tmpNPCType->armor_tint[0] = (atoi(row[37]) & 0xFF) << 16; + tmpNPCType->armor_tint[0] |= (atoi(row[38]) & 0xFF) << 8; + tmpNPCType->armor_tint[0] |= (atoi(row[39]) & 0xFF); + tmpNPCType->armor_tint[0] |= (tmpNPCType->armor_tint[0]) ? (0xFF << 24) : 0; + + if (armor_tint_id == 0) + for (int index = MaterialChest; index <= EmuConstants::MATERIAL_END; index++) + tmpNPCType->armor_tint[index] = tmpNPCType->armor_tint[0]; + else if (tmpNPCType->armor_tint[0] == 0) { + std::string armorTint_query = StringFormat("SELECT red1h, grn1h, blu1h, " + "red2c, grn2c, blu2c, " + "red3a, grn3a, blu3a, " + "red4b, grn4b, blu4b, " + "red5g, grn5g, blu5g, " + "red6l, grn6l, blu6l, " + "red7f, grn7f, blu7f, " + "red8x, grn8x, blu8x, " + "red9x, grn9x, blu9x " + "FROM npc_types_tint WHERE id = %d", + armor_tint_id); + auto armorTint_results = QueryDatabase(armorTint_query); + if (!results.Success() || results.RowCount() == 0) + armor_tint_id = 0; + else { + auto armorTint_row = results.begin(); + + for (int index = EmuConstants::MATERIAL_BEGIN; index <= EmuConstants::MATERIAL_END; index++) { + tmpNPCType->armor_tint[index] = atoi(armorTint_row[index * 3]) << 16; + tmpNPCType->armor_tint[index] |= atoi(armorTint_row[index * 3 + 1]) << 8; + tmpNPCType->armor_tint[index] |= atoi(armorTint_row[index * 3 + 2]); + tmpNPCType->armor_tint[index] |= (tmpNPCType->armor_tint[index]) ? (0xFF << 24) : 0; + } + } + } else + armor_tint_id = 0; + + tmpNPCType->AC = atoi(row[40]); + tmpNPCType->ATK = atoi(row[41]); + tmpNPCType->accuracy_rating = atoi(row[42]); + tmpNPCType->scalerate = RuleI(Mercs, ScaleRate); + tmpNPCType->spellscale = atoi(row[43]); + tmpNPCType->healscale = atoi(row[4]); + + // If NPC with duplicate NPC id already in table, + // free item we attempted to add. + if (zone->merctable.find(tmpNPCType->npc_id * 100 + clientlevel) != zone->merctable.end()) { + delete tmpNPCType; + return nullptr; + } + + zone->merctable[tmpNPCType->npc_id * 100 + clientlevel]=tmpNPCType; + npc = tmpNPCType; + } + + return npc; +} + +bool ZoneDatabase::LoadMercInfo(Client *client) { + + std::string query = StringFormat("SELECT MercID, Slot, Name, TemplateID, SuspendedTime, " + "IsSuspended, TimerRemaining, Gender, MercSize, StanceID, HP, Mana, " + "Endurance, Face, LuclinHairStyle, LuclinHairColor, " + "LuclinEyeColor, LuclinEyeColor2, LuclinBeardColor, LuclinBeard, " + "DrakkinHeritage, DrakkinTattoo, DrakkinDetails " + "FROM mercs WHERE OwnerCharacterID = '%i' ORDER BY Slot", client->CharacterID()); + auto results = QueryDatabase(query); + if (!results.Success()) + return false; + + if(results.RowCount() == 0) + return false; + + for (auto row = results.begin(); row != results.end(); ++row) { + uint8 slot = atoi(row[1]); + + if(slot >= MAXMERCS) + continue; + + client->GetMercInfo(slot).mercid = atoi(row[0]); + client->GetMercInfo(slot).slot = slot; + snprintf(client->GetMercInfo(slot).merc_name, 64, "%s", row[2]); + client->GetMercInfo(slot).MercTemplateID = atoi(row[3]); + client->GetMercInfo(slot).SuspendedTime = atoi(row[4]); + client->GetMercInfo(slot).IsSuspended = atoi(row[5]) == 1 ? true : false; + client->GetMercInfo(slot).MercTimerRemaining = atoi(row[6]); + client->GetMercInfo(slot).Gender = atoi(row[7]); + client->GetMercInfo(slot).MercSize = atof(row[8]); + client->GetMercInfo(slot).State = 5; + client->GetMercInfo(slot).Stance = atoi(row[9]); + client->GetMercInfo(slot).hp = atoi(row[10]); + client->GetMercInfo(slot).mana = atoi(row[11]); + client->GetMercInfo(slot).endurance = atoi(row[12]); + client->GetMercInfo(slot).face = atoi(row[13]); + client->GetMercInfo(slot).luclinHairStyle = atoi(row[14]); + client->GetMercInfo(slot).luclinHairColor = atoi(row[15]); + client->GetMercInfo(slot).luclinEyeColor = atoi(row[16]); + client->GetMercInfo(slot).luclinEyeColor2 = atoi(row[17]); + client->GetMercInfo(slot).luclinBeardColor = atoi(row[18]); + client->GetMercInfo(slot).luclinBeard = atoi(row[19]); + client->GetMercInfo(slot).drakkinHeritage = atoi(row[20]); + client->GetMercInfo(slot).drakkinTattoo = atoi(row[21]); + client->GetMercInfo(slot).drakkinDetails = atoi(row[22]); + } + + return true; +} + +bool ZoneDatabase::LoadCurrentMerc(Client *client) { + + uint8 slot = client->GetMercSlot(); + + if(slot > MAXMERCS) + return false; + + std::string query = StringFormat("SELECT MercID, Name, TemplateID, SuspendedTime, " + "IsSuspended, TimerRemaining, Gender, MercSize, StanceID, HP, " + "Mana, Endurance, Face, LuclinHairStyle, LuclinHairColor, " + "LuclinEyeColor, LuclinEyeColor2, LuclinBeardColor, " + "LuclinBeard, DrakkinHeritage, DrakkinTattoo, DrakkinDetails " + "FROM mercs WHERE OwnerCharacterID = '%i' AND Slot = '%u'", + client->CharacterID(), slot); + auto results = database.QueryDatabase(query); + + if(!results.Success()) + return false; + + if(results.RowCount() == 0) + return false; + + + for (auto row = results.begin(); row != results.end(); ++row) { + client->GetMercInfo(slot).mercid = atoi(row[0]); + client->GetMercInfo(slot).slot = slot; + snprintf(client->GetMercInfo(slot).merc_name, 64, "%s", row[1]); + client->GetMercInfo(slot).MercTemplateID = atoi(row[2]); + client->GetMercInfo(slot).SuspendedTime = atoi(row[3]); + client->GetMercInfo(slot).IsSuspended = atoi(row[4]) == 1? true: false; + client->GetMercInfo(slot).MercTimerRemaining = atoi(row[5]); + client->GetMercInfo(slot).Gender = atoi(row[6]); + client->GetMercInfo(slot).MercSize = atof(row[7]); + client->GetMercInfo(slot).State = atoi(row[8]); + client->GetMercInfo(slot).hp = atoi(row[9]); + client->GetMercInfo(slot).mana = atoi(row[10]); + client->GetMercInfo(slot).endurance = atoi(row[11]); + client->GetMercInfo(slot).face = atoi(row[12]); + client->GetMercInfo(slot).luclinHairStyle = atoi(row[13]); + client->GetMercInfo(slot).luclinHairColor = atoi(row[14]); + client->GetMercInfo(slot).luclinEyeColor = atoi(row[15]); + client->GetMercInfo(slot).luclinEyeColor2 = atoi(row[16]); + client->GetMercInfo(slot).luclinBeardColor = atoi(row[17]); + client->GetMercInfo(slot).luclinBeard = atoi(row[18]); + client->GetMercInfo(slot).drakkinHeritage = atoi(row[19]); + client->GetMercInfo(slot).drakkinTattoo = atoi(row[20]); + client->GetMercInfo(slot).drakkinDetails = atoi(row[21]); + } + + return true; +} + +bool ZoneDatabase::SaveMerc(Merc *merc) { + Client *owner = merc->GetMercOwner(); + + if(!owner) + return false; + + if(merc->GetMercID() == 0) + { + // New merc record + std::string query = StringFormat("INSERT INTO mercs " + "(OwnerCharacterID, Slot, Name, TemplateID, " + "SuspendedTime, IsSuspended, TimerRemaining, " + "Gender, MercSize, StanceID, HP, Mana, Endurance, Face, " + "LuclinHairStyle, LuclinHairColor, LuclinEyeColor, " + "LuclinEyeColor2, LuclinBeardColor, LuclinBeard, " + "DrakkinHeritage, DrakkinTattoo, DrakkinDetails) " + "VALUES('%u', '%u', '%s', '%u', '%u', '%u', '%u', " + "'%u', '%u', '%f', '%u', '%u', '%u', '%i', '%i', '%i', " + "'%i', '%i', '%i', '%i', '%i', '%i', '%i')", + merc->GetMercCharacterID(), owner->GetNumMercs(), + merc->GetCleanName(), merc->GetMercTemplateID(), + owner->GetMercInfo().SuspendedTime, merc->IsSuspended(), + owner->GetMercInfo().MercTimerRemaining, merc->GetGender(), + merc->GetSize(), merc->GetStance(), merc->GetHP(), + merc->GetMana(), merc->GetEndurance(), merc->GetLuclinFace(), + merc->GetHairStyle(), merc->GetHairColor(), merc->GetEyeColor1(), + merc->GetEyeColor2(), merc->GetBeardColor(), + merc->GetBeard(), merc->GetDrakkinHeritage(), + merc->GetDrakkinTattoo(), merc->GetDrakkinDetails()); + + auto results = database.QueryDatabase(query); + if(!results.Success()) { + owner->Message(13, results.ErrorMessage().c_str()); + return false; + } else if (results.RowsAffected() != 1) { + owner->Message(13, "Unable to save merc to the database."); + return false; + } + + merc->SetMercID(results.LastInsertedID()); + merc->UpdateMercInfo(owner); + database.SaveMercBuffs(merc); + return true; + } + + // Update existing merc record + std::string query = StringFormat("UPDATE mercs SET OwnerCharacterID = '%u', Slot = '%u', " + "Name = '%s', TemplateID = '%u', SuspendedTime = '%u', " + "IsSuspended = '%u', TimerRemaining = '%u', Gender = '%u', MercSize = '%f', " + "StanceID = '%u', HP = '%u', Mana = '%u', Endurance = '%u', " + "Face = '%i', LuclinHairStyle = '%i', LuclinHairColor = '%i', " + "LuclinEyeColor = '%i', LuclinEyeColor2 = '%i', LuclinBeardColor = '%i', " + "LuclinBeard = '%i', DrakkinHeritage = '%i', DrakkinTattoo = '%i', " + "DrakkinDetails = '%i' WHERE MercID = '%u'", + merc->GetMercCharacterID(), owner->GetMercSlot(), merc->GetCleanName(), + merc->GetMercTemplateID(), owner->GetMercInfo().SuspendedTime, + merc->IsSuspended(), owner->GetMercInfo().MercTimerRemaining, + merc->GetGender(), merc->GetSize(), merc->GetStance(), merc->GetHP(), + merc->GetMana(), merc->GetEndurance(), merc->GetLuclinFace(), + merc->GetHairStyle(), merc->GetHairColor(), merc->GetEyeColor1(), + merc->GetEyeColor2(), merc->GetBeardColor(), merc->GetBeard(), + merc->GetDrakkinHeritage(), merc->GetDrakkinTattoo(), merc->GetDrakkinDetails(), + merc->GetMercID()); + + auto results = database.QueryDatabase(query); + if (!results.Success()) { + owner->Message(13, results.ErrorMessage().c_str()); + return false; + } else if (results.RowsAffected() != 1) { + owner->Message(13, "Unable to save merc to the database."); + return false; + } + + merc->UpdateMercInfo(owner); + database.SaveMercBuffs(merc); + + return true; +} + +void ZoneDatabase::SaveMercBuffs(Merc *merc) { + + Buffs_Struct *buffs = merc->GetBuffs(); + + // Remove any existing buff saves + std::string query = StringFormat("DELETE FROM merc_buffs WHERE MercId = %u", merc->GetMercID()); + auto results = database.QueryDatabase(query); + if(!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error While Deleting Merc Buffs before save: %s", results.ErrorMessage().c_str()); + return; + } + + for (int buffCount = 0; buffCount <= BUFF_COUNT; buffCount++) { + if(buffs[buffCount].spellid == 0 || buffs[buffCount].spellid == SPELL_UNKNOWN) + continue; + + int IsPersistent = buffs[buffCount].persistant_buff? 1: 0; + + query = StringFormat("INSERT INTO merc_buffs (MercId, SpellId, CasterLevel, DurationFormula, " + "TicsRemaining, PoisonCounters, DiseaseCounters, CurseCounters, " + "CorruptionCounters, HitCount, MeleeRune, MagicRune, dot_rune, " + "caston_x, Persistent, caston_y, caston_z, ExtraDIChance) " + "VALUES (%u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %i, %u, %i, %i, %i);", + merc->GetMercID(), buffs[buffCount].spellid, buffs[buffCount].casterlevel, + spells[buffs[buffCount].spellid].buffdurationformula, buffs[buffCount].ticsremaining, + CalculatePoisonCounters(buffs[buffCount].spellid) > 0 ? buffs[buffCount].counters : 0, + CalculateDiseaseCounters(buffs[buffCount].spellid) > 0 ? buffs[buffCount].counters : 0, + CalculateCurseCounters(buffs[buffCount].spellid) > 0 ? buffs[buffCount].counters : 0, + CalculateCorruptionCounters(buffs[buffCount].spellid) > 0 ? buffs[buffCount].counters : 0, + buffs[buffCount].numhits, buffs[buffCount].melee_rune, buffs[buffCount].magic_rune, + buffs[buffCount].dot_rune, buffs[buffCount].caston_x, IsPersistent, buffs[buffCount].caston_y, + buffs[buffCount].caston_z, buffs[buffCount].ExtraDIChance); + results = database.QueryDatabase(query); + if(!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error Saving Merc Buffs: %s", results.ErrorMessage().c_str()); + break; + } + } +} + +void ZoneDatabase::LoadMercBuffs(Merc *merc) { + Buffs_Struct *buffs = merc->GetBuffs(); + uint32 max_slots = merc->GetMaxBuffSlots(); + + + bool BuffsLoaded = false; + std::string query = StringFormat("SELECT SpellId, CasterLevel, DurationFormula, TicsRemaining, " + "PoisonCounters, DiseaseCounters, CurseCounters, CorruptionCounters, " + "HitCount, MeleeRune, MagicRune, dot_rune, caston_x, Persistent, " + "caston_y, caston_z, ExtraDIChance FROM merc_buffs WHERE MercId = %u", + merc->GetMercID()); + auto results = database.QueryDatabase(query); + if(!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error Loading Merc Buffs: %s", results.ErrorMessage().c_str()); + return; + } + + int buffCount = 0; + for (auto row = results.begin(); row != results.end(); ++row, ++buffCount) { + if(buffCount == BUFF_COUNT) + break; + + buffs[buffCount].spellid = atoi(row[0]); + buffs[buffCount].casterlevel = atoi(row[1]); + buffs[buffCount].ticsremaining = atoi(row[3]); + + if(CalculatePoisonCounters(buffs[buffCount].spellid) > 0) + buffs[buffCount].counters = atoi(row[4]); + + if(CalculateDiseaseCounters(buffs[buffCount].spellid) > 0) + buffs[buffCount].counters = atoi(row[5]); + + if(CalculateCurseCounters(buffs[buffCount].spellid) > 0) + buffs[buffCount].counters = atoi(row[6]); + + if(CalculateCorruptionCounters(buffs[buffCount].spellid) > 0) + buffs[buffCount].counters = atoi(row[7]); + + buffs[buffCount].numhits = atoi(row[8]); + buffs[buffCount].melee_rune = atoi(row[9]); + buffs[buffCount].magic_rune = atoi(row[10]); + buffs[buffCount].dot_rune = atoi(row[11]); + buffs[buffCount].caston_x = atoi(row[12]); + buffs[buffCount].casterid = 0; + + bool IsPersistent = atoi(row[13])? true: false; + + buffs[buffCount].caston_y = atoi(row[13]); + buffs[buffCount].caston_z = atoi(row[14]); + buffs[buffCount].ExtraDIChance = atoi(row[15]); + + buffs[buffCount].persistant_buff = IsPersistent; + + } + + query = StringFormat("DELETE FROM merc_buffs WHERE MercId = %u", merc->GetMercID()); + results = database.QueryDatabase(query); + if(!results.Success()) + LogFile->write(EQEMuLog::Error, "Error Loading Merc Buffs: %s", results.ErrorMessage().c_str()); + +} + +bool ZoneDatabase::DeleteMerc(uint32 merc_id) { + + if(merc_id == 0) + return false; + + // TODO: These queries need to be ran together as a transaction.. ie, + // if one or more fail then they all will fail to commit to the database. + // ...Not all mercs will have buffs, so why is it required that both deletes succeed? + std::string query = StringFormat("DELETE FROM merc_buffs WHERE MercId = '%u'", merc_id); + auto results = database.QueryDatabase(query); + if(!results.Success()) + { + LogFile->write(EQEMuLog::Error, "Error Deleting Merc Buffs: %s", results.ErrorMessage().c_str()); + } + + query = StringFormat("DELETE FROM mercs WHERE MercID = '%u'", merc_id); + results = database.QueryDatabase(query); + if(!results.Success()) + { + LogFile->write(EQEMuLog::Error, "Error Deleting Merc: %s", results.ErrorMessage().c_str()); + return false; + } + + return true; +} + +void ZoneDatabase::LoadMercEquipment(Merc *merc) { + + std::string query = StringFormat("SELECT item_id FROM merc_inventory " + "WHERE merc_subtype_id = (" + "SELECT merc_subtype_id FROM merc_subtypes " + "WHERE class_id = '%u' AND tier_id = '%u') " + "AND min_level <= %u AND max_level >= %u", + merc->GetClass(), merc->GetTierID(), + merc->GetLevel(), merc->GetLevel()); + auto results = database.QueryDatabase(query); + if(!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error Loading Merc Inventory: %s", results.ErrorMessage().c_str()); + return; + } + + int itemCount = 0; + for(auto row = results.begin(); row != results.end(); ++row) { + if (itemCount == EmuConstants::EQUIPMENT_SIZE) + break; + + if(atoi(row[0]) == 0) + continue; + + merc->AddItem(itemCount, atoi(row[0])); + itemCount++; + } +} + +uint8 ZoneDatabase::GetGridType(uint32 grid, uint32 zoneid ) { + + std::string query = StringFormat("SELECT type FROM grid WHERE id = %i AND zoneid = %i", grid, zoneid); + auto results = QueryDatabase(query); + if (!results.Success()) { + std::cerr << "Error in GetGridType query '" << query << "' " << results.ErrorMessage() << std::endl; + return 0; + } + + if (results.RowCount() != 1) + return 0; + + auto row = results.begin(); + + return atoi(row[0]); +} + +void ZoneDatabase::SaveMerchantTemp(uint32 npcid, uint32 slot, uint32 item, uint32 charges){ + + std::string query = StringFormat("REPLACE INTO merchantlist_temp (npcid, slot, itemid, charges) " + "VALUES(%d, %d, %d, %d)", npcid, slot, item, charges); + auto results = QueryDatabase(query); + if (!results.Success()) + std::cerr << "Error in SaveMerchantTemp query '" << query << "' " << results.ErrorMessage() << std::endl; +} + +void ZoneDatabase::DeleteMerchantTemp(uint32 npcid, uint32 slot){ + + std::string query = StringFormat("DELETE FROM merchantlist_temp WHERE npcid=%d AND slot=%d", npcid, slot); + auto results = QueryDatabase(query); + if (!results.Success()) + std::cerr << "Error in DeleteMerchantTemp query '" << query << "' " << results.ErrorMessage() << std::endl; + +} + +bool ZoneDatabase::UpdateZoneSafeCoords(const char* zonename, const xyz_location& location) { + + std::string query = StringFormat("UPDATE zone SET safe_x='%f', safe_y='%f', safe_z='%f' " + "WHERE short_name='%s';", + location.m_X, location.m_Y, location.m_Z, zonename); + auto results = QueryDatabase(query); + if (!results.Success() || results.RowsAffected() == 0) + return false; + + return true; +} + +uint8 ZoneDatabase::GetUseCFGSafeCoords() +{ + const std::string query = "SELECT value FROM variables WHERE varname='UseCFGSafeCoords'"; + auto results = QueryDatabase(query); + if (!results.Success()) { + std::cerr << "Error in GetUseCFGSafeCoords query '" << query << "' " << results.ErrorMessage() << std::endl; + return 0; + } + + if (results.RowCount() != 1) + return 0; + + auto row = results.begin(); + + return atoi(row[0]); +} + +//New functions for timezone +uint32 ZoneDatabase::GetZoneTZ(uint32 zoneid, uint32 version) { + + std::string query = StringFormat("SELECT timezone FROM zone WHERE zoneidnumber = %i " + "AND (version = %i OR version = 0) ORDER BY version DESC", + zoneid, version); + auto results = QueryDatabase(query); + if (!results.Success()) { + std::cerr << "Error in GetZoneTZ query '" << query << "' " << results.ErrorMessage() << std::endl; + return 0; + } + + if (results.RowCount() == 0) + return 0; + + auto row = results.begin(); + return atoi(row[0]); +} + +bool ZoneDatabase::SetZoneTZ(uint32 zoneid, uint32 version, uint32 tz) { + + std::string query = StringFormat("UPDATE zone SET timezone = %i " + "WHERE zoneidnumber = %i AND version = %i", + tz, zoneid, version); + auto results = QueryDatabase(query); + if (!results.Success()) { + std::cerr << "Error in SetZoneTZ query '" << query << "' " << results.ErrorMessage() << std::endl; + return false; + } + + return results.RowsAffected() == 1; +} + +void ZoneDatabase::RefreshGroupFromDB(Client *client){ + if(!client) + return; + + Group *group = client->GetGroup(); + + if(!group) + return; + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_GroupUpdate,sizeof(GroupUpdate2_Struct)); + GroupUpdate2_Struct* gu = (GroupUpdate2_Struct*)outapp->pBuffer; + gu->action = groupActUpdate; + + strcpy(gu->yourname, client->GetName()); + GetGroupLeadershipInfo(group->GetID(), gu->leadersname, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, &gu->leader_aas); + gu->NPCMarkerID = group->GetNPCMarkerID(); + + int index = 0; + + std::string query = StringFormat("SELECT name FROM group_id WHERE groupid = %d", group->GetID()); + auto results = QueryDatabase(query); + if (!results.Success()) + { + printf("Error in group update query: %s\n", results.ErrorMessage().c_str()); + } + else + { + for (auto row = results.begin(); row != results.end(); ++row) { + if(index >= 6) + continue; + + if(strcmp(client->GetName(), row[0]) == 0) + continue; + + strcpy(gu->membername[index], row[0]); + index++; + } + } + + client->QueuePacket(outapp); + safe_delete(outapp); + + if(client->GetClientVersion() >= EQClientSoD) { + group->NotifyMainTank(client, 1); + group->NotifyPuller(client, 1); + } + + group->NotifyMainAssist(client, 1); + group->NotifyMarkNPC(client); + group->NotifyAssistTarget(client); + group->NotifyTankTarget(client); + group->NotifyPullerTarget(client); + group->SendMarkedNPCsToMember(client); + +} + +uint8 ZoneDatabase::GroupCount(uint32 groupid) { + + std::string query = StringFormat("SELECT count(charid) FROM group_id WHERE groupid = %d", groupid); + auto results = QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error in ZoneDatabase::GroupCount query '%s': %s", query.c_str(), results.ErrorMessage().c_str()); + return 0; + } + + if (results.RowCount() == 0) + return 0; + + auto row = results.begin(); + + return atoi(row[0]); +} + +uint8 ZoneDatabase::RaidGroupCount(uint32 raidid, uint32 groupid) { + + std::string query = StringFormat("SELECT count(charid) FROM raid_members " + "WHERE raidid = %d AND groupid = %d;", raidid, groupid); + auto results = QueryDatabase(query); + + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error in ZoneDatabase::RaidGroupCount query '%s': %s", query.c_str(), results.ErrorMessage().c_str()); + return 0; + } + + if (results.RowCount() == 0) + return 0; + + auto row = results.begin(); + + return atoi(row[0]); + } + +int32 ZoneDatabase::GetBlockedSpellsCount(uint32 zoneid) +{ + std::string query = StringFormat("SELECT count(*) FROM blocked_spells WHERE zoneid = %d", zoneid); + auto results = QueryDatabase(query); + if (!results.Success()) { + std::cerr << "Error in GetBlockedSpellsCount query '" << query << "' " << results.ErrorMessage() << std::endl; + return -1; + } + + if (results.RowCount() == 0) + return -1; + + auto row = results.begin(); + + return atoi(row[0]); +} + +bool ZoneDatabase::LoadBlockedSpells(int32 blockedSpellsCount, ZoneSpellsBlocked* into, uint32 zoneid) +{ + LogFile->write(EQEMuLog::Status, "Loading Blocked Spells from database..."); + + std::string query = StringFormat("SELECT id, spellid, type, x, y, z, x_diff, y_diff, z_diff, message " + "FROM blocked_spells WHERE zoneid = %d ORDER BY id ASC", zoneid); + auto results = QueryDatabase(query); + if (!results.Success()) { + std::cerr << "Error in LoadBlockedSpells query '" << query << "' " << results.ErrorMessage() << std::endl; + return false; + } + + if (results.RowCount() == 0) + return true; + + int32 index = 0; + for(auto row = results.begin(); row != results.end(); ++row, ++index) { + if(index >= blockedSpellsCount) { + std::cerr << "Error, Blocked Spells Count of " << blockedSpellsCount << " exceeded." << std::endl; + break; + } + + memset(&into[index], 0, sizeof(ZoneSpellsBlocked)); + into[index].spellid = atoi(row[1]); + into[index].type = atoi(row[2]); + into[index].m_Location = xyz_location(atof(row[3]), atof(row[4]), atof(row[5])); + into[index].m_Difference = xyz_location(atof(row[6]), atof(row[7]), atof(row[8])); + strn0cpy(into[index].message, row[9], 255); + } + + return true; +} + +int ZoneDatabase::getZoneShutDownDelay(uint32 zoneID, uint32 version) +{ + std::string query = StringFormat("SELECT shutdowndelay FROM zone " + "WHERE zoneidnumber = %i AND (version=%i OR version=0) " + "ORDER BY version DESC", zoneID, version); + auto results = QueryDatabase(query); + if (!results.Success()) { + std::cerr << "Error in getZoneShutDownDelay query '" << query << "' " << results.ErrorMessage().c_str() << std::endl; + return (RuleI(Zone, AutoShutdownDelay)); + } + + if (results.RowCount() == 0) { + std::cerr << "Error in getZoneShutDownDelay no result '" << query << "' " << std::endl; + return (RuleI(Zone, AutoShutdownDelay)); + } + + auto row = results.begin(); + + return atoi(row[0]); +} + +uint32 ZoneDatabase::GetKarma(uint32 acct_id) +{ + std::string query = StringFormat("SELECT `karma` FROM `account` WHERE `id` = '%i' LIMIT 1", acct_id); + auto results = QueryDatabase(query); + if (!results.Success()) + return 0; + + auto row = results.begin(); + + return atoi(row[0]); +} + +void ZoneDatabase::UpdateKarma(uint32 acct_id, uint32 amount) +{ + std::string query = StringFormat("UPDATE account SET karma = %i WHERE id = %i", amount, acct_id); + auto results = QueryDatabase(query); + if (!results.Success()) + std::cerr << "Error in UpdateKarma query '" << query << "' " << results.ErrorMessage().c_str() << std::endl; + +} + +void ZoneDatabase::ListAllInstances(Client* client, uint32 charid) +{ + if(!client) + return; + + std::string query = StringFormat("SELECT instance_list.id, zone, version " + "FROM instance_list JOIN instance_list_player " + "ON instance_list.id = instance_list_player.id " + "WHERE instance_list_player.charid = %lu", + (unsigned long)charid); + auto results = QueryDatabase(query); + if (!results.Success()) + return; + + char name[64]; + database.GetCharName(charid, name); + client->Message(0, "%s is part of the following instances:", name); + + for (auto row = results.begin(); row != results.end(); ++row) { + client->Message(0, "%s - id: %lu, version: %lu", database.GetZoneName(atoi(row[1])), + (unsigned long)atoi(row[0]), (unsigned long)atoi(row[2])); + } +} + +void ZoneDatabase::QGlobalPurge() +{ + const std::string query = "DELETE FROM quest_globals WHERE expdate < UNIX_TIMESTAMP()"; + database.QueryDatabase(query); +} + +void ZoneDatabase::InsertDoor(uint32 ddoordbid, uint16 ddoorid, const char* ddoor_name, const xyz_heading& position, uint8 dopentype, uint16 dguildid, uint32 dlockpick, uint32 dkeyitem, uint8 ddoor_param, uint8 dinvert, int dincline, uint16 dsize){ + + std::string query = StringFormat("REPLACE INTO doors (id, doorid, zone, version, name, " + "pos_x, pos_y, pos_z, heading, opentype, guild, lockpick, " + "keyitem, door_param, invert_state, incline, size) " + "VALUES('%i', '%i', '%s', '%i', '%s', '%f', '%f', " + "'%f', '%f', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i')", + ddoordbid, ddoorid, zone->GetShortName(), zone->GetInstanceVersion(), + ddoor_name, position.m_X, position.m_Y, position.m_Z, position.m_Heading, + dopentype, dguildid, dlockpick, dkeyitem, ddoor_param, dinvert, dincline, dsize); + auto results = QueryDatabase(query); + if (!results.Success()) + std::cerr << "Error in InsertDoor" << query << "' " << results.ErrorMessage() << std::endl; +} + +void ZoneDatabase::LoadAltCurrencyValues(uint32 char_id, std::map ¤cy) { + + std::string query = StringFormat("SELECT currency_id, amount " + "FROM character_alt_currency " + "WHERE char_id = '%u'", char_id); + auto results = QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error in LoadAltCurrencyValues query '%s': %s", query.c_str(), results.ErrorMessage().c_str()); + return; + } + + for (auto row = results.begin(); row != results.end(); ++row) + currency[atoi(row[0])] = atoi(row[1]); + +} + +void ZoneDatabase::UpdateAltCurrencyValue(uint32 char_id, uint32 currency_id, uint32 value) { + + std::string query = StringFormat("REPLACE INTO character_alt_currency (char_id, currency_id, amount) " + "VALUES('%u', '%u', '%u')", char_id, currency_id, value); + database.QueryDatabase(query); + +} + +void ZoneDatabase::SaveBuffs(Client *client) { + + std::string query = StringFormat("DELETE FROM `character_buffs` WHERE `character_id` = '%u'", client->CharacterID()); + database.QueryDatabase(query); + + uint32 buff_count = client->GetMaxBuffSlots(); + Buffs_Struct *buffs = client->GetBuffs(); + + for (int index = 0; index < buff_count; index++) { + if(buffs[index].spellid == SPELL_UNKNOWN) + continue; + + query = StringFormat("INSERT INTO `character_buffs` (character_id, slot_id, spell_id, " + "caster_level, caster_name, ticsremaining, counters, numhits, melee_rune, " + "magic_rune, persistent, dot_rune, caston_x, caston_y, caston_z, ExtraDIChance) " + "VALUES('%u', '%u', '%u', '%u', '%s', '%u', '%u', '%u', '%u', '%u', '%u', '%u', " + "'%i', '%i', '%i', '%i')", client->CharacterID(), index, buffs[index].spellid, + buffs[index].casterlevel, buffs[index].caster_name, buffs[index].ticsremaining, + buffs[index].counters, buffs[index].numhits, buffs[index].melee_rune, + buffs[index].magic_rune, buffs[index].persistant_buff, buffs[index].dot_rune, + buffs[index].caston_x, buffs[index].caston_y, buffs[index].caston_z, + buffs[index].ExtraDIChance); + auto results = QueryDatabase(query); + if (!results.Success()) + LogFile->write(EQEMuLog::Error, "Error in SaveBuffs query '%s': %s", query.c_str(), results.ErrorMessage().c_str()); + + } +} + +void ZoneDatabase::LoadBuffs(Client *client) { + + Buffs_Struct *buffs = client->GetBuffs(); + uint32 max_slots = client->GetMaxBuffSlots(); + + for(int index = 0; index < max_slots; ++index) + buffs[index].spellid = SPELL_UNKNOWN; + + std::string query = StringFormat("SELECT spell_id, slot_id, caster_level, caster_name, ticsremaining, " + "counters, numhits, melee_rune, magic_rune, persistent, dot_rune, " + "caston_x, caston_y, caston_z, ExtraDIChance " + "FROM `character_buffs` WHERE `character_id` = '%u'", client->CharacterID()); + auto results = QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error in LoadBuffs query '%s': %s", query.c_str(), results.ErrorMessage().c_str()); + return; + } + + for (auto row = results.begin(); row != results.end(); ++row) { + uint32 slot_id = atoul(row[1]); + if(slot_id >= client->GetMaxBuffSlots()) + continue; + + uint32 spell_id = atoul(row[0]); + if(!IsValidSpell(spell_id)) + continue; + + Client *caster = entity_list.GetClientByName(row[3]); + uint32 caster_level = atoi(row[2]); + uint32 ticsremaining = atoul(row[4]); + uint32 counters = atoul(row[5]); + uint32 numhits = atoul(row[6]); + uint32 melee_rune = atoul(row[7]); + uint32 magic_rune = atoul(row[8]); + uint8 persistent = atoul(row[9]); + uint32 dot_rune = atoul(row[10]); + int32 caston_x = atoul(row[11]); + int32 caston_y = atoul(row[12]); + int32 caston_z = atoul(row[13]); + int32 ExtraDIChance = atoul(row[14]); + + buffs[slot_id].spellid = spell_id; + buffs[slot_id].casterlevel = caster_level; + + if(caster) { + buffs[slot_id].casterid = caster->GetID(); + strcpy(buffs[slot_id].caster_name, caster->GetName()); + buffs[slot_id].client = true; + } else { + buffs[slot_id].casterid = 0; + strcpy(buffs[slot_id].caster_name, ""); + buffs[slot_id].client = false; + } + + buffs[slot_id].ticsremaining = ticsremaining; + buffs[slot_id].counters = counters; + buffs[slot_id].numhits = numhits; + buffs[slot_id].melee_rune = melee_rune; + buffs[slot_id].magic_rune = magic_rune; + buffs[slot_id].persistant_buff = persistent? true: false; + buffs[slot_id].dot_rune = dot_rune; + buffs[slot_id].caston_x = caston_x; + buffs[slot_id].caston_y = caston_y; + buffs[slot_id].caston_z = caston_z; + buffs[slot_id].ExtraDIChance = ExtraDIChance; + buffs[slot_id].RootBreakChance = 0; + buffs[slot_id].UpdateClient = false; + + } + + max_slots = client->GetMaxBuffSlots(); + for(int index = 0; index < max_slots; ++index) { + if(!IsValidSpell(buffs[index].spellid)) + continue; + + for(int effectIndex = 0; effectIndex < 12; ++effectIndex) { + + if (spells[buffs[index].spellid].effectid[effectIndex] == SE_Charm) { + buffs[index].spellid = SPELL_UNKNOWN; + break; + } + + if (spells[buffs[index].spellid].effectid[effectIndex] == SE_Illusion) { + if(buffs[index].persistant_buff) + break; + + buffs[index].spellid = SPELL_UNKNOWN; + break; + } + } + } +} + +void ZoneDatabase::SavePetInfo(Client *client) +{ + PetInfo *petinfo = nullptr; + + std::string query = StringFormat("DELETE FROM `character_pet_buffs` WHERE `char_id` = %u", client->CharacterID()); + auto results = database.QueryDatabase(query); + if (!results.Success()) + return; + + query = StringFormat("DELETE FROM `character_pet_inventory` WHERE `char_id` = %u", client->CharacterID()); + results = database.QueryDatabase(query); + if (!results.Success()) + return; + + for (int pet = 0; pet < 2; pet++) { + petinfo = client->GetPetInfo(pet); + if (!petinfo) + continue; + + query = StringFormat("INSERT INTO `character_pet_info` " + "(`char_id`, `pet`, `petname`, `petpower`, `spell_id`, `hp`, `mana`, `size`) " + "VALUES (%u, %u, '%s', %i, %u, %u, %u, %f) " + "ON DUPLICATE KEY UPDATE `petname` = '%s', `petpower` = %i, `spell_id` = %u, " + "`hp` = %u, `mana` = %u, `size` = %f", + client->CharacterID(), pet, petinfo->Name, petinfo->petpower, petinfo->SpellID, + petinfo->HP, petinfo->Mana, petinfo->size, // and now the ON DUPLICATE ENTRIES + petinfo->Name, petinfo->petpower, petinfo->SpellID, petinfo->HP, petinfo->Mana, petinfo->size); + results = database.QueryDatabase(query); + if (!results.Success()) + return; + query.clear(); + + // pet buffs! + for (int index = 0; index < RuleI(Spells, MaxTotalSlotsPET); index++) { + if (petinfo->Buffs[index].spellid == SPELL_UNKNOWN || petinfo->Buffs[index].spellid == 0) + continue; + if (query.length() == 0) + query = StringFormat("INSERT INTO `character_pet_buffs` " + "(`char_id`, `pet`, `slot`, `spell_id`, `caster_level`, " + "`ticsremaining`, `counters`) " + "VALUES (%u, %u, %u, %u, %u, %u, %d)", + client->CharacterID(), pet, index, petinfo->Buffs[index].spellid, + petinfo->Buffs[index].level, petinfo->Buffs[index].duration, + petinfo->Buffs[index].counters); + else + query += StringFormat(", (%u, %u, %u, %u, %u, %u, %d)", + client->CharacterID(), pet, index, petinfo->Buffs[index].spellid, + petinfo->Buffs[index].level, petinfo->Buffs[index].duration, + petinfo->Buffs[index].counters); + } + database.QueryDatabase(query); + query.clear(); + + // pet inventory! + for (int index = EmuConstants::EQUIPMENT_BEGIN; index <= EmuConstants::EQUIPMENT_END; index++) { + if (!petinfo->Items[index]) + continue; + + if (query.length() == 0) + query = StringFormat("INSERT INTO `character_pet_inventory` " + "(`char_id`, `pet`, `slot`, `item_id`) " + "VALUES (%u, %u, %u, %u)", + client->CharacterID(), pet, index, petinfo->Items[index]); + else + query += StringFormat(", (%u, %u, %u, %u)", client->CharacterID(), pet, index, petinfo->Items[index]); + } + database.QueryDatabase(query); + } +} + +void ZoneDatabase::RemoveTempFactions(Client *client) { + + std::string query = StringFormat("DELETE FROM faction_values " + "WHERE temp = 1 AND char_id = %u", + client->CharacterID()); + auto results = QueryDatabase(query); + if (!results.Success()) + std::cerr << "Error in RemoveTempFactions query '" << query << "' " << results.ErrorMessage() << std::endl; + +} + +void ZoneDatabase::LoadPetInfo(Client *client) { + + // Load current pet and suspended pet + PetInfo *petinfo = client->GetPetInfo(0); + PetInfo *suspended = client->GetPetInfo(1); + + memset(petinfo, 0, sizeof(PetInfo)); + memset(suspended, 0, sizeof(PetInfo)); + + std::string query = StringFormat("SELECT `pet`, `petname`, `petpower`, `spell_id`, " + "`hp`, `mana`, `size` FROM `character_pet_info` " + "WHERE `char_id` = %u", client->CharacterID()); + auto results = database.QueryDatabase(query); + if(!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error in LoadPetInfo query '%s': %s", query.c_str(), results.ErrorMessage().c_str()); + return; + } + + PetInfo *pi; + for (auto row = results.begin(); row != results.end(); ++row) { + uint16 pet = atoi(row[0]); + + if (pet == 0) + pi = petinfo; + else if (pet == 1) + pi = suspended; + else + continue; + + strncpy(pi->Name,row[1],64); + pi->petpower = atoi(row[2]); + pi->SpellID = atoi(row[3]); + pi->HP = atoul(row[4]); + pi->Mana = atoul(row[5]); + pi->size = atof(row[6]); + } + + query = StringFormat("SELECT `pet`, `slot`, `spell_id`, `caster_level`, `castername`, " + "`ticsremaining`, `counters` FROM `character_pet_buffs` " + "WHERE `char_id` = %u", client->CharacterID()); + results = QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error in LoadPetInfo query '%s': %s", query.c_str(), results.ErrorMessage().c_str()); + return; + } + + for (auto row = results.begin(); row != results.end(); ++row) { + uint16 pet = atoi(row[0]); + if (pet == 0) + pi = petinfo; + else if (pet == 1) + pi = suspended; + else + continue; + + uint32 slot_id = atoul(row[1]); + if(slot_id >= RuleI(Spells, MaxTotalSlotsPET)) + continue; + + uint32 spell_id = atoul(row[2]); + if(!IsValidSpell(spell_id)) + continue; + + uint32 caster_level = atoi(row[3]); + int caster_id = 0; + // The castername field is currently unused + uint32 ticsremaining = atoul(row[5]); + uint32 counters = atoul(row[6]); + + pi->Buffs[slot_id].spellid = spell_id; + pi->Buffs[slot_id].level = caster_level; + pi->Buffs[slot_id].player_id = caster_id; + pi->Buffs[slot_id].slotid = 2; // Always 2 in buffs struct for real buffs + + pi->Buffs[slot_id].duration = ticsremaining; + pi->Buffs[slot_id].counters = counters; + } + + query = StringFormat("SELECT `pet`, `slot`, `item_id` " + "FROM `character_pet_inventory` " + "WHERE `char_id`=%u",client->CharacterID()); + results = database.QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error in LoadPetInfo query '%s': %s", query.c_str(), results.ErrorMessage().c_str()); + return; + } + + for(auto row = results.begin(); row != results.end(); ++row) { + uint16 pet = atoi(row[0]); + if (pet == 0) + pi = petinfo; + else if (pet == 1) + pi = suspended; + else + continue; + + int slot = atoi(row[1]); + if (slot < EmuConstants::EQUIPMENT_BEGIN || slot > EmuConstants::EQUIPMENT_END) + continue; + + pi->Items[slot] = atoul(row[2]); + } + +} + +bool ZoneDatabase::GetFactionData(FactionMods* fm, uint32 class_mod, uint32 race_mod, uint32 deity_mod, int32 faction_id) { + if (faction_id <= 0 || faction_id > (int32) max_faction) + return false; + + if (faction_array[faction_id] == 0){ + return false; + } + + fm->base = faction_array[faction_id]->base; + + if(class_mod > 0) { + char str[32]; + sprintf(str, "c%u", class_mod); + + std::map::const_iterator iter = faction_array[faction_id]->mods.find(str); + if(iter != faction_array[faction_id]->mods.end()) { + fm->class_mod = iter->second; + } else { + fm->class_mod = 0; + } + } else { + fm->class_mod = 0; + } + + if(race_mod > 0) { + char str[32]; + sprintf(str, "r%u", race_mod); + + std::map::iterator iter = faction_array[faction_id]->mods.find(str); + if(iter != faction_array[faction_id]->mods.end()) { + fm->race_mod = iter->second; + } else { + fm->race_mod = 0; + } + } else { + fm->race_mod = 0; + } + + if(deity_mod > 0) { + char str[32]; + sprintf(str, "d%u", deity_mod); + + std::map::iterator iter = faction_array[faction_id]->mods.find(str); + if(iter != faction_array[faction_id]->mods.end()) { + fm->deity_mod = iter->second; + } else { + fm->deity_mod = 0; + } + } else { + fm->deity_mod = 0; + } + + return true; +} + +//o-------------------------------------------------------------- +//| Name: GetFactionName; Dec. 16 +//o-------------------------------------------------------------- +//| Notes: Retrieves the name of the specified faction .Returns false on failure. +//o-------------------------------------------------------------- +bool ZoneDatabase::GetFactionName(int32 faction_id, char* name, uint32 buflen) { + if ((faction_id <= 0) || faction_id > int32(max_faction) ||(faction_array[faction_id] == 0)) + return false; + if (faction_array[faction_id]->name[0] != 0) { + strn0cpy(name, faction_array[faction_id]->name, buflen); + return true; + } + return false; + +} + +//o-------------------------------------------------------------- +//| Name: GetNPCFactionList; Dec. 16, 2001 +//o-------------------------------------------------------------- +//| Purpose: Gets a list of faction_id's and values bound to the npc_id. Returns false on failure. +//o-------------------------------------------------------------- +bool ZoneDatabase::GetNPCFactionList(uint32 npcfaction_id, int32* faction_id, int32* value, uint8* temp, int32* primary_faction) { + if (npcfaction_id <= 0) { + if (primary_faction) + *primary_faction = npcfaction_id; + return true; + } + const NPCFactionList* nfl = GetNPCFactionEntry(npcfaction_id); + if (!nfl) + return false; + if (primary_faction) + *primary_faction = nfl->primaryfaction; + for (int i=0; ifactionid[i]; + value[i] = nfl->factionvalue[i]; + temp[i] = nfl->factiontemp[i]; + } + return true; +} + +//o-------------------------------------------------------------- +//| Name: SetCharacterFactionLevel; Dec. 20, 2001 +//o-------------------------------------------------------------- +//| Purpose: Update characters faction level with specified faction_id to specified value. Returns false on failure. +//o-------------------------------------------------------------- +bool ZoneDatabase::SetCharacterFactionLevel(uint32 char_id, int32 faction_id, int32 value, uint8 temp, faction_map &val_list) +{ + + std::string query = StringFormat("DELETE FROM faction_values " + "WHERE char_id=%i AND faction_id = %i", + char_id, faction_id); + auto results = QueryDatabase(query); + if (!results.Success()) { + std::cerr << "Error in SetCharacterFactionLevel query '" << query << "' " << results.ErrorMessage() << std::endl; + return false; + } + + if(value == 0) + return true; + + if(temp == 2) + temp = 0; + + if(temp == 3) + temp = 1; + + query = StringFormat("INSERT INTO faction_values (char_id, faction_id, current_value, temp) " + "VALUES (%i, %i, %i, %i)", char_id, faction_id, value, temp); + results = QueryDatabase(query); + if (!results.Success()) { + std::cerr << "Error in SetCharacterFactionLevel query '" << query << "' " << results.ErrorMessage() << std::endl; + return false; + } + + if (results.RowsAffected() == 0) + return false; + + val_list[faction_id] = value; + return true; +} + +bool ZoneDatabase::LoadFactionData() +{ + std::string query = "SELECT MAX(id) FROM faction_list"; + auto results = QueryDatabase(query); + if (!results.Success()) { + std::cerr << "Error in LoadFactionData '" << query << "' " << results.ErrorMessage() << std::endl; + return false; + } + + if (results.RowCount() == 0) + return false; + + auto row = results.begin(); + + max_faction = atoi(row[0]); + faction_array = new Faction*[max_faction+1]; + for(unsigned int index=0; indexname, row[1], 50); + faction_array[index]->base = atoi(row[2]); + + query = StringFormat("SELECT `mod`, `mod_name` FROM `faction_list_mod` WHERE faction_id = %u", index); + auto modResults = QueryDatabase(query); + if (!modResults.Success()) + continue; + + for (auto modRow = modResults.begin(); modRow != modResults.end(); ++modRow) + faction_array[index]->mods[modRow[1]] = atoi(modRow[0]); + } + + return true; +} + +bool ZoneDatabase::GetFactionIdsForNPC(uint32 nfl_id, std::list *faction_list, int32* primary_faction) { + if (nfl_id <= 0) { + std::list::iterator cur,end; + cur = faction_list->begin(); + end = faction_list->end(); + for(; cur != end; ++cur) { + struct NPCFaction* tmp = *cur; + safe_delete(tmp); + } + + faction_list->clear(); + if (primary_faction) + *primary_faction = nfl_id; + return true; + } + const NPCFactionList* nfl = GetNPCFactionEntry(nfl_id); + if (!nfl) + return false; + if (primary_faction) + *primary_faction = nfl->primaryfaction; + + std::list::iterator cur,end; + cur = faction_list->begin(); + end = faction_list->end(); + for(; cur != end; ++cur) { + struct NPCFaction* tmp = *cur; + safe_delete(tmp); + } + faction_list->clear(); + for (int i=0; ifactionid[i]) { + pFac = new struct NPCFaction; + pFac->factionID = nfl->factionid[i]; + pFac->value_mod = nfl->factionvalue[i]; + pFac->npc_value = nfl->factionnpcvalue[i]; + pFac->temp = nfl->factiontemp[i]; + faction_list->push_back(pFac); + } + } + return true; +} + +/* Corpse Queries */ + +bool ZoneDatabase::DeleteGraveyard(uint32 zone_id, uint32 graveyard_id) { + std::string query = StringFormat( "UPDATE `zone` SET `graveyard_id` = 0 WHERE `zone_idnumber` = %u AND `version` = 0", zone_id); + auto results = QueryDatabase(query); + + query = StringFormat("DELETE FROM `graveyard` WHERE `id` = %u", graveyard_id); + auto results2 = QueryDatabase(query); + + if (results.Success() && results2.Success()){ + return true; + } + + return false; +} + +uint32 ZoneDatabase::AddGraveyardIDToZone(uint32 zone_id, uint32 graveyard_id) { + std::string query = StringFormat( + "UPDATE `zone` SET `graveyard_id` = %u WHERE `zone_idnumber` = %u AND `version` = 0", + graveyard_id, zone_id + ); + auto results = QueryDatabase(query); + return zone_id; +} + +uint32 ZoneDatabase::CreateGraveyardRecord(uint32 graveyard_zone_id, const xyz_heading& position) { + std::string query = StringFormat("INSERT INTO `graveyard` " + "SET `zone_id` = %u, `x` = %1.1f, `y` = %1.1f, `z` = %1.1f, `heading` = %1.1f", + graveyard_zone_id, position.m_X, position.m_Y, position.m_Z, position.m_Heading); + auto results = QueryDatabase(query); + if (results.Success()) + return results.LastInsertedID(); + + return 0; +} +uint32 ZoneDatabase::SendCharacterCorpseToGraveyard(uint32 dbid, uint32 zone_id, uint16 instance_id, const xyz_heading& position) { + std::string query = StringFormat("UPDATE `character_corpses` " + "SET `zone_id` = %u, `instance_id` = 0, " + "`x` = %1.1f, `y` = %1.1f, `z` = %1.1f, `heading` = %1.1f, " + "`was_at_graveyard` = 1 " + "WHERE `id` = %d", + zone_id, position.m_X, position.m_Y, position.m_Z, position.m_Heading, dbid); + QueryDatabase(query); + return dbid; +} + +uint32 ZoneDatabase::GetCharacterCorpseDecayTimer(uint32 corpse_db_id){ + std::string query = StringFormat("SELECT(UNIX_TIMESTAMP() - UNIX_TIMESTAMP(time_of_death)) FROM `character_corpses` WHERE `id` = %d AND NOT `time_of_death` = 0", corpse_db_id); + auto results = QueryDatabase(query); + auto row = results.begin(); + if (results.Success() && results.RowsAffected() != 0){ +<<<<<<< HEAD + return atoll(row[0]); +======= + return atoul(row[0]); +>>>>>>> master + } + return 0; +} + +uint32 ZoneDatabase::UpdateCharacterCorpse(uint32 db_id, uint32 char_id, const char* char_name, uint32 zone_id, uint16 instance_id, PlayerCorpse_Struct* dbpc, const xyz_heading& position, bool is_rezzed) { + std::string query = StringFormat("UPDATE `character_corpses` " + "SET `charname` = '%s', `zone_id` = %u, `instance_id` = %u, `charid` = %d, " + "`x` = %1.1f,`y` = %1.1f,`z` = %1.1f, `heading` = %1.1f, " + "`is_locked` = %d, `exp` = %u, `size` = %f, `level` = %u, " + "`race` = %u, `gender` = %u, `class` = %u, `deity` = %u, " + "`texture` = %u, `helm_texture` = %u, `copper` = %u, " + "`silver` = %u, `gold` = %u, `platinum` = %u, `hair_color` = %u, " + "`beard_color` = %u, `eye_color_1` = %u, `eye_color_2` = %u, " + "`hair_style` = %u, `face` = %u, `beard` = %u, `drakkin_heritage` = %u, " + "`drakkin_tattoo` = %u, `drakkin_details` = %u, `wc_1` = %u, " + "`wc_2` = %u, `wc_3` = %u, `wc_4` = %u, `wc_5` = %u, `wc_6` = %u, " + "`wc_7` = %u, `wc_8` = %u, `wc_9` = %u " + "WHERE `id` = %u", + EscapeString(char_name).c_str(), zone_id, instance_id, char_id, + position.m_X, position.m_Y, position.m_Z, position.m_Heading, + dbpc->locked, dbpc->exp, dbpc->size, dbpc->level, dbpc->race, + dbpc->gender, dbpc->class_, dbpc->deity, dbpc->texture, + dbpc->helmtexture, dbpc->copper, dbpc->silver, dbpc->gold, + dbpc->plat, dbpc->haircolor, dbpc->beardcolor, dbpc->eyecolor1, + dbpc->eyecolor2, dbpc->hairstyle, dbpc->face, dbpc->beard, + dbpc->drakkin_heritage, dbpc->drakkin_tattoo, dbpc->drakkin_details, + dbpc->item_tint[0].color, dbpc->item_tint[1].color, dbpc->item_tint[2].color, + dbpc->item_tint[3].color, dbpc->item_tint[4].color, dbpc->item_tint[5].color, + dbpc->item_tint[6].color, dbpc->item_tint[7].color, dbpc->item_tint[8].color, + db_id); + auto results = QueryDatabase(query); + + return db_id; +} + +void ZoneDatabase::MarkCorpseAsRezzed(uint32 db_id) { + std::string query = StringFormat("UPDATE `character_corpses` SET `is_rezzed` = 1 WHERE `id` = %i", db_id); + auto results = QueryDatabase(query); +} + +uint32 ZoneDatabase::SaveCharacterCorpse(uint32 charid, const char* charname, uint32 zoneid, uint16 instanceid, PlayerCorpse_Struct* dbpc, const xyz_heading& position) { + /* Dump Basic Corpse Data */ + std::string query = StringFormat("INSERT INTO `character_corpses` " + "SET `charname` = '%s', `zone_id` = %u, `instance_id` = %u, `charid` = %d," + "`x` = %1.1f, `y` = %1.1f, `z` = %1.1f, `heading` = %1.1f," + "`time_of_death` = NOW(), `is_buried` = 0, `is_locked` = %d," + "`exp` = %u, `size` = %f, `level` = %u, `race` = %u, `gender` = %u," + "`class` = %u, `deity` = %u, `texture` = %u, `helm_texture` = %u," + "`copper` = %u, `silver` = %u,`gold` = %u,`platinum` = %u," + "`hair_color` = %u, `beard_color` = %u, `eye_color_1` = %u," + "`eye_color_2` = %u, `hair_style` = %u, `face` = %u," + "`beard` = %u, `drakkin_heritage` = %u, `drakkin_tattoo` = %u," + "`drakkin_details` = %u, `wc_1` = %u, `wc_2` = %u," + "`wc_3` = %u, `wc_4` = %u, `wc_5` = %u, `wc_6` = %u," + "`wc_7` = %u,`wc_8` = %u,`wc_9` = %u", + EscapeString(charname).c_str(), zoneid, instanceid, charid, + position.m_X, position.m_Y, position.m_Z, position.m_Heading, + dbpc->locked, dbpc->exp, dbpc->size, dbpc->level, dbpc->race, + dbpc->gender, dbpc->class_, dbpc->deity, dbpc->texture, + dbpc->helmtexture, dbpc->copper, dbpc->silver, dbpc->gold, + dbpc->plat, dbpc->haircolor, dbpc->beardcolor, dbpc->eyecolor1, + dbpc->eyecolor2, dbpc->hairstyle, dbpc->face, dbpc->beard, + dbpc->drakkin_heritage, dbpc->drakkin_tattoo, dbpc->drakkin_details, + dbpc->item_tint[0].color, dbpc->item_tint[1].color, dbpc->item_tint[2].color, + dbpc->item_tint[3].color, dbpc->item_tint[4].color, dbpc->item_tint[5].color, + dbpc->item_tint[6].color, dbpc->item_tint[7].color, dbpc->item_tint[8].color); + auto results = QueryDatabase(query); + uint32 last_insert_id = results.LastInsertedID(); + + /* Dump Items from Inventory */ + uint8 first_entry = 0; + for (unsigned int i = 0; i < dbpc->itemcount; i++) { + if (first_entry != 1){ + query = StringFormat("REPLACE INTO `character_corpse_items` \n" + " (corpse_id, equip_slot, item_id, charges, aug_1, aug_2, aug_3, aug_4, aug_5, attuned) \n" + " VALUES (%u, %u, %u, %u, %u, %u, %u, %u, %u, 0) \n", + last_insert_id, + dbpc->items[i].equip_slot, + dbpc->items[i].item_id, + dbpc->items[i].charges, + dbpc->items[i].aug_1, + dbpc->items[i].aug_2, + dbpc->items[i].aug_3, + dbpc->items[i].aug_4, + dbpc->items[i].aug_5 + ); + first_entry = 1; + } + else{ + query = query + StringFormat(", (%u, %u, %u, %u, %u, %u, %u, %u, %u, 0) \n", + last_insert_id, + dbpc->items[i].equip_slot, + dbpc->items[i].item_id, + dbpc->items[i].charges, + dbpc->items[i].aug_1, + dbpc->items[i].aug_2, + dbpc->items[i].aug_3, + dbpc->items[i].aug_4, + dbpc->items[i].aug_5 + ); + } + } + auto sc_results = QueryDatabase(query); + return last_insert_id; +} + +uint32 ZoneDatabase::GetCharacterBuriedCorpseCount(uint32 char_id) { + std::string query = StringFormat("SELECT COUNT(*) FROM `character_corpses` WHERE `charid` = '%u' AND `is_buried` = 1", char_id); + auto results = QueryDatabase(query); + + for (auto row = results.begin(); row != results.end(); ++row) { + return atoi(row[0]); + } + return 0; +} + +uint32 ZoneDatabase::GetCharacterCorpseCount(uint32 char_id) { + std::string query = StringFormat("SELECT COUNT(*) FROM `character_corpses` WHERE `charid` = '%u'", char_id); + auto results = QueryDatabase(query); + + for (auto row = results.begin(); row != results.end(); ++row) { + return atoi(row[0]); + } + return 0; +} + +uint32 ZoneDatabase::GetCharacterCorpseID(uint32 char_id, uint8 corpse) { + std::string query = StringFormat("SELECT `id` FROM `character_corpses` WHERE `charid` = '%u'", char_id); + auto results = QueryDatabase(query); + + for (auto row = results.begin(); row != results.end(); ++row) { + for (int i = 0; i < corpse; i++) { + return atoul(row[0]); + } + } + return 0; +} + +uint32 ZoneDatabase::GetCharacterCorpseItemCount(uint32 corpse_id){ + std::string query = StringFormat("SELECT COUNT(*) FROM character_corpse_items WHERE `corpse_id` = %u", + corpse_id + ); + auto results = QueryDatabase(query); + auto row = results.begin(); + if (results.Success() && results.RowsAffected() != 0){ + return atoi(row[0]); + } + return 0; +} + +uint32 ZoneDatabase::GetCharacterCorpseItemAt(uint32 corpse_id, uint16 slotid) { + Corpse* tmp = LoadCharacterCorpse(corpse_id); + uint32 itemid = 0; + + if (tmp) { + itemid = tmp->GetWornItem(slotid); + tmp->DepopPlayerCorpse(); + } + return itemid; +} + +bool ZoneDatabase::LoadCharacterCorpseData(uint32 corpse_id, PlayerCorpse_Struct* pcs){ + std::string query = StringFormat( + "SELECT \n" + "is_locked, \n" + "exp, \n" + "size, \n" + "`level`, \n" + "race, \n" + "gender, \n" + "class, \n" + "deity, \n" + "texture, \n" + "helm_texture, \n" + "copper, \n" + "silver, \n" + "gold, \n" + "platinum, \n" + "hair_color, \n" + "beard_color, \n" + "eye_color_1, \n" + "eye_color_2, \n" + "hair_style, \n" + "face, \n" + "beard, \n" + "drakkin_heritage,\n" + "drakkin_tattoo, \n" + "drakkin_details, \n" + "wc_1, \n" + "wc_2, \n" + "wc_3, \n" + "wc_4, \n" + "wc_5, \n" + "wc_6, \n" + "wc_7, \n" + "wc_8, \n" + "wc_9 \n" + "FROM \n" + "character_corpses\n" + "WHERE `id` = %u LIMIT 1\n", + corpse_id + ); + auto results = QueryDatabase(query); + uint16 i = 0; + for (auto row = results.begin(); row != results.end(); ++row) { + pcs->locked = atoi(row[i++]); // is_locked, + pcs->exp = atoul(row[i++]); // exp, + pcs->size = atoi(row[i++]); // size, + pcs->level = atoi(row[i++]); // `level`, + pcs->race = atoi(row[i++]); // race, + pcs->gender = atoi(row[i++]); // gender, + pcs->class_ = atoi(row[i++]); // class, + pcs->deity = atoi(row[i++]); // deity, + pcs->texture = atoi(row[i++]); // texture, + pcs->helmtexture = atoi(row[i++]); // helm_texture, + pcs->copper = atoul(row[i++]); // copper, + pcs->silver = atoul(row[i++]); // silver, + pcs->gold = atoul(row[i++]); // gold, + pcs->plat = atoul(row[i++]); // platinum, + pcs->haircolor = atoi(row[i++]); // hair_color, + pcs->beardcolor = atoi(row[i++]); // beard_color, + pcs->eyecolor1 = atoi(row[i++]); // eye_color_1, + pcs->eyecolor2 = atoi(row[i++]); // eye_color_2, + pcs->hairstyle = atoi(row[i++]); // hair_style, + pcs->face = atoi(row[i++]); // face, + pcs->beard = atoi(row[i++]); // beard, + pcs->drakkin_heritage = atoul(row[i++]); // drakkin_heritage, + pcs->drakkin_tattoo = atoul(row[i++]); // drakkin_tattoo, + pcs->drakkin_details = atoul(row[i++]); // drakkin_details, + pcs->item_tint[0].color = atoul(row[i++]); // wc_1, + pcs->item_tint[1].color = atoul(row[i++]); // wc_2, + pcs->item_tint[2].color = atoul(row[i++]); // wc_3, + pcs->item_tint[3].color = atoul(row[i++]); // wc_4, + pcs->item_tint[4].color = atoul(row[i++]); // wc_5, + pcs->item_tint[5].color = atoul(row[i++]); // wc_6, + pcs->item_tint[6].color = atoul(row[i++]); // wc_7, + pcs->item_tint[7].color = atoul(row[i++]); // wc_8, + pcs->item_tint[8].color = atoul(row[i++]); // wc_9 + } + query = StringFormat( + "SELECT \n" + "equip_slot, \n" + "item_id, \n" + "charges, \n" + "aug_1, \n" + "aug_2, \n" + "aug_3, \n" + "aug_4, \n" + "aug_5, \n" + "attuned \n" + "FROM \n" + "character_corpse_items \n" + "WHERE `corpse_id` = %u\n" + , + corpse_id + ); + results = QueryDatabase(query); + + i = 0; + pcs->itemcount = results.RowCount(); + uint16 r = 0; + for (auto row = results.begin(); row != results.end(); ++row) { + memset(&pcs->items[i], 0, sizeof (player_lootitem::ServerLootItem_Struct)); + pcs->items[i].equip_slot = atoi(row[r++]); // equip_slot, + pcs->items[i].item_id = atoul(row[r++]); // item_id, + pcs->items[i].charges = atoi(row[r++]); // charges, + pcs->items[i].aug_1 = atoi(row[r++]); // aug_1, + pcs->items[i].aug_2 = atoi(row[r++]); // aug_2, + pcs->items[i].aug_3 = atoi(row[r++]); // aug_3, + pcs->items[i].aug_4 = atoi(row[r++]); // aug_4, + pcs->items[i].aug_5 = atoi(row[r++]); // aug_5, + r = 0; + i++; + } + + return true; +} + +Corpse* ZoneDatabase::SummonBuriedCharacterCorpses(uint32 char_id, uint32 dest_zone_id, uint16 dest_instance_id, const xyz_heading& position) { + Corpse* corpse = nullptr; + std::string query = StringFormat("SELECT `id`, `charname`, `time_of_death`, `is_rezzed` " + "FROM `character_corpses` " + "WHERE `charid` = '%u' AND `is_buried` = 1 " + "ORDER BY `time_of_death` LIMIT 1", + char_id); + auto results = QueryDatabase(query); + + for (auto row = results.begin(); row != results.end(); ++row) { +<<<<<<< HEAD + corpse = Corpse::LoadFromDBData( + atoll(row[0]), // uint32 in_dbid +======= + NewCorpse = Corpse::LoadCharacterCorpseEntity( + atoul(row[0]), // uint32 in_dbid +>>>>>>> master + char_id, // uint32 in_charid + row[1], // char* in_charname + position, + row[2], // char* time_of_death + atoi(row[3]) == 1, // bool rezzed + false // bool was_at_graveyard + ); +<<<<<<< HEAD + if (!corpse) + continue; + + entity_list.AddCorpse(corpse); + corpse->SetDecayTimer(RuleI(Character, CorpseDecayTimeMS)); + corpse->Spawn(); + if (!UnburyCharacterCorpse(corpse->GetDBID(), dest_zone_id, dest_instance_id, position)) + LogFile->write(EQEMuLog::Error, "Unable to unbury a summoned player corpse for character id %u.", char_id); +======= + if (NewCorpse) { + entity_list.AddCorpse(NewCorpse); + NewCorpse->SetDecayTimer(RuleI(Character, CorpseDecayTimeMS)); + NewCorpse->Spawn(); + if (!UnburyCharacterCorpse(NewCorpse->GetCorpseDBID(), dest_zone_id, dest_instance_id, dest_x, dest_y, dest_z, dest_heading)) + LogFile->write(EQEMuLog::Error, "Unable to unbury a summoned player corpse for character id %u.", char_id); + } +>>>>>>> master + } + + return corpse; +} + +bool ZoneDatabase::SummonAllCharacterCorpses(uint32 char_id, uint32 dest_zone_id, uint16 dest_instance_id, const xyz_heading& position) { + Corpse* NewCorpse = 0; + int CorpseCount = 0; + + std::string query = StringFormat( + "UPDATE character_corpses SET zone_id = %i, instance_id = %i, x = %f, y = %f, z = %f, heading = %f, is_buried = 0, was_at_graveyard = 0 WHERE charid = %i", + dest_zone_id, dest_instance_id, position.m_X, position.m_Y, position.m_Z, position.m_Heading, char_id + ); + auto results = QueryDatabase(query); + + query = StringFormat( + "SELECT `id`, `charname`, `time_of_death`, `is_rezzed` FROM `character_corpses` WHERE `charid` = '%u'" + "ORDER BY time_of_death", + char_id); + results = QueryDatabase(query); + + for (auto row = results.begin(); row != results.end(); ++row) { + NewCorpse = Corpse::LoadCharacterCorpseEntity( + atoul(row[0]), + char_id, + row[1], + position, + row[2], + atoi(row[3]) == 1, + false); + if (NewCorpse) { + entity_list.AddCorpse(NewCorpse); + NewCorpse->SetDecayTimer(RuleI(Character, CorpseDecayTimeMS)); + NewCorpse->Spawn(); + ++CorpseCount; + } + else{ + LogFile->write(EQEMuLog::Error, "Unable to construct a player corpse for character id %u.", char_id); + } + } + + return (CorpseCount > 0); +} + +bool ZoneDatabase::UnburyCharacterCorpse(uint32 db_id, uint32 new_zone_id, uint16 new_instance_id, const xyz_heading& position) { + std::string query = StringFormat("UPDATE `character_corpses` " + "SET `is_buried` = 0, `zone_id` = %u, `instance_id` = %u, " + "`x` = %f, `y` = %f, `z` = %f, `heading` = %f, " + "`time_of_death` = Now(), `was_at_graveyard` = 0 " + "WHERE `id` = %u", + new_zone_id, new_instance_id, + position.m_X, position.m_Y, position.m_Z, position.m_Heading, db_id); + auto results = QueryDatabase(query); + if (results.Success() && results.RowsAffected() != 0) + return true; + + return false; +} + +Corpse* ZoneDatabase::LoadCharacterCorpse(uint32 player_corpse_id) { + Corpse* NewCorpse = 0; + std::string query = StringFormat( + "SELECT `id`, `charid`, `charname`, `x`, `y`, `z`, `heading`, `time_of_death`, `is_rezzed`, `was_at_graveyard` FROM `character_corpses` WHERE `id` = '%u' LIMIT 1", + player_corpse_id + ); + auto results = QueryDatabase(query); + for (auto row = results.begin(); row != results.end(); ++row) { +<<<<<<< HEAD + auto position = xyz_heading(atof(row[3]), atof(row[4]), atof(row[5]), atof(row[6])); + NewCorpse = Corpse::LoadFromDBData( + atoll(row[0]), // id uint32 in_dbid + atoll(row[1]), // charid uint32 in_charid +======= + NewCorpse = Corpse::LoadCharacterCorpseEntity( + atoul(row[0]), // id uint32 in_dbid + atoul(row[1]), // charid uint32 in_charid +>>>>>>> master + row[2], // char_name + position, + row[7], // time_of_death char* time_of_death + atoi(row[8]) == 1, // is_rezzed bool rezzed + atoi(row[9]) // was_at_graveyard bool was_at_graveyard + ); + entity_list.AddCorpse(NewCorpse); + } + return NewCorpse; +} + +bool ZoneDatabase::LoadCharacterCorpses(uint32 zone_id, uint16 instance_id) { + std::string query; + if (!RuleB(Zone, EnableShadowrest)){ + query = StringFormat("SELECT id, charid, charname, x, y, z, heading, time_of_death, is_rezzed, was_at_graveyard FROM character_corpses WHERE zone_id='%u' AND instance_id='%u'", zone_id, instance_id); + } + else{ + query = StringFormat("SELECT id, charid, charname, x, y, z, heading, time_of_death, is_rezzed, 0 as was_at_graveyard FROM character_corpses WHERE zone_id='%u' AND instance_id='%u' AND is_buried=0", zone_id, instance_id); + } + + auto results = QueryDatabase(query); + for (auto row = results.begin(); row != results.end(); ++row) { +<<<<<<< HEAD + auto position = xyz_heading(atof(row[3]), atof(row[4]), atof(row[5]), atof(row[6])); + entity_list.AddCorpse( + Corpse::LoadFromDBData( + atoll(row[0]), // id uint32 in_dbid + atoll(row[1]), // charid uint32 in_charid + row[2], // char_name + position, +======= + entity_list.AddCorpse( + Corpse::LoadCharacterCorpseEntity( + atoul(row[0]), // id uint32 in_dbid + atoul(row[1]), // charid uint32 in_charid + row[2], // char_name + atof(row[3]), // x float in_x + atof(row[4]), // y float in_y + atof(row[5]), // z float in_z + atof(row[6]), // heading float in_heading +>>>>>>> master + row[7], // time_of_death char* time_of_death + atoi(row[8]) == 1, // is_rezzed bool rezzed + atoi(row[9])) + ); + } + + return true; +} + +uint32 ZoneDatabase::GetFirstCorpseID(uint32 char_id) { + std::string query = StringFormat("SELECT `id` FROM `character_corpses` WHERE `charid` = '%u' AND `is_buried` = 0 ORDER BY `time_of_death` LIMIT 1", char_id); + auto results = QueryDatabase(query); + for (auto row = results.begin(); row != results.end(); ++row) { + return atoi(row[0]); + } + return 0; +} + +<<<<<<< HEAD +bool ZoneDatabase::ClearCorpseItems(uint32 db_id){ + std::string query = StringFormat("DELETE FROM `character_corpse_items` WHERE `corpse_id` = %u", db_id); + auto results = QueryDatabase(query); + if (results.Success() && results.RowsAffected() != 0){ + return true; + } + return false; +} + +======= +>>>>>>> master +bool ZoneDatabase::DeleteItemOffCharacterCorpse(uint32 db_id, uint32 equip_slot, uint32 item_id){ + std::string query = StringFormat("DELETE FROM `character_corpse_items` WHERE `corpse_id` = %u AND equip_slot = %u AND item_id = %u", db_id, equip_slot, item_id); + auto results = QueryDatabase(query); + if (results.Success() && results.RowsAffected() != 0){ + return true; + } + return false; +} + +bool ZoneDatabase::BuryCharacterCorpse(uint32 db_id) { + std::string query = StringFormat("UPDATE `character_corpses` SET `is_buried` = 1 WHERE `id` = %u", db_id); + auto results = QueryDatabase(query); + if (results.Success() && results.RowsAffected() != 0){ + return true; + } + return false; +} + +bool ZoneDatabase::BuryAllCharacterCorpses(uint32 char_id) { + std::string query = StringFormat("SELECT `id` FROM `character_corpses` WHERE `charid` = %u", char_id); + auto results = QueryDatabase(query); + for (auto row = results.begin(); row != results.end(); ++row) { + BuryCharacterCorpse(atoi(row[0])); + return true; + } + return false; +} + +bool ZoneDatabase::DeleteCharacterCorpse(uint32 db_id) { + std::string query = StringFormat("DELETE FROM `character_corpses` WHERE `id` = %d", db_id); + auto results = QueryDatabase(query); + if (results.Success() && results.RowsAffected() != 0){ + return true; + } + return false; +} diff --git a/zone/zonedb.h b/zone/zonedb.h index 9e0d50e3f..ade485326 100644 --- a/zone/zonedb.h +++ b/zone/zonedb.h @@ -3,11 +3,25 @@ #include "../common/shareddb.h" #include "../common/eq_packet_structs.h" -#include "../common/loottable.h" -#include "zonedump.h" #include "position.h" #include "../common/faction.h" -#include + +class Client; +class Corpse; +class Merc; +class NPC; +class Petition; +class Spawn2; +class SpawnGroupList; +class ItemInst; +struct CharacterEventLog_Struct; +struct Door; +struct ExtendedProfile_Struct; +struct NPCType; +struct PlayerCorpse_Struct; +struct ZonePoint; +struct npcDecayTimes_Struct; +template class LinkedList; //#include "doors.h" @@ -164,6 +178,7 @@ struct MercInfo { bool IsSuspended; uint32 MercTimerRemaining; uint8 Gender; + float MercSize; int32 State; uint32 Stance; int32 hp; @@ -198,12 +213,6 @@ struct ClientMercEntry { uint32 npcid; }; -class ItemInst; -struct FactionMods; -struct FactionValue; -struct LootTable_Struct; - - class ZoneDatabase : public SharedDatabase { typedef std::list ItemList; public: @@ -292,7 +301,6 @@ public: bool NoRentExpired(const char* name); /* Corpses */ - bool ClearCorpseItems(uint32 db_id); bool DeleteItemOffCharacterCorpse(uint32 db_id, uint32 equip_slot, uint32 item_id); uint32 GetCharacterCorpseItemCount(uint32 corpse_id); bool LoadCharacterCorpseData(uint32 corpse_id, PlayerCorpse_Struct* pcs); diff --git a/zone/zonedb.h.orig b/zone/zonedb.h.orig new file mode 100644 index 000000000..a7814cd45 --- /dev/null +++ b/zone/zonedb.h.orig @@ -0,0 +1,519 @@ +#ifndef ZONEDB_H_ +#define ZONEDB_H_ + +#include "../common/shareddb.h" +#include "../common/eq_packet_structs.h" +<<<<<<< HEAD +#include "../common/loottable.h" +#include "zonedump.h" +#include "position.h" +======= +>>>>>>> master +#include "../common/faction.h" + +class Client; +class Corpse; +class Merc; +class NPC; +class Petition; +class Spawn2; +class SpawnGroupList; +class ItemInst; +struct CharacterEventLog_Struct; +struct Door; +struct ExtendedProfile_Struct; +struct NPCType; +struct PlayerCorpse_Struct; +struct ZonePoint; +struct npcDecayTimes_Struct; +template class LinkedList; + +//#include "doors.h" + +struct wplist { + int index; + float x; + float y; + float z; + int pause; + float heading; +}; + +#pragma pack(1) +struct DBnpcspells_entries_Struct { + int16 spellid; + uint16 type; + uint8 minlevel; + uint8 maxlevel; + int16 manacost; + int32 recast_delay; + int16 priority; + int16 resist_adjust; +}; +#pragma pack() + +#pragma pack(1) +struct DBnpcspellseffects_entries_Struct { + int16 spelleffectid; + uint8 minlevel; + uint8 maxlevel; + int32 base; + int32 limit; + int32 max; +}; +#pragma pack() + +struct DBnpcspells_Struct { + uint32 parent_list; + uint16 attack_proc; + uint8 proc_chance; + uint16 range_proc; + int16 rproc_chance; + uint16 defensive_proc; + int16 dproc_chance; + uint32 numentries; + uint32 fail_recast; + uint32 engaged_no_sp_recast_min; + uint32 engaged_no_sp_recast_max; + uint8 engaged_beneficial_self_chance; + uint8 engaged_beneficial_other_chance; + uint8 engaged_detrimental_chance; + uint32 pursue_no_sp_recast_min; + uint32 pursue_no_sp_recast_max; + uint8 pursue_detrimental_chance; + uint32 idle_no_sp_recast_min; + uint32 idle_no_sp_recast_max; + uint8 idle_beneficial_chance; + DBnpcspells_entries_Struct entries[0]; +}; + +struct DBnpcspellseffects_Struct { + uint32 parent_list; + uint32 numentries; + DBnpcspellseffects_entries_Struct entries[0]; +}; + +struct DBTradeskillRecipe_Struct { + SkillUseTypes tradeskill; + int16 skill_needed; + uint16 trivial; + bool nofail; + bool replace_container; + std::vector< std::pair > onsuccess; + std::vector< std::pair > onfail; + std::vector< std::pair > salvage; + std::string name; + uint8 must_learn; + bool has_learnt; + uint32 madecount; + uint32 recipe_id; + bool quest; +}; + +struct PetRecord { + uint32 npc_type; // npc_type id for the pet data to use + bool temporary; + int16 petpower; + uint8 petcontrol; // What kind of control over the pet is possible (Animation, familiar, ...) + uint8 petnaming; // How to name the pet (Warder, pet, random name, familiar, ...) + bool monsterflag; // flag for if a random monster appearance should get picked + uint32 equipmentset; // default equipment for the pet +}; + +// Actual pet info for a client. +struct PetInfo { + uint16 SpellID; + int16 petpower; + uint32 HP; + uint32 Mana; + float size; + SpellBuff_Struct Buffs[BUFF_COUNT]; + uint32 Items[EmuConstants::EQUIPMENT_SIZE]; + char Name[64]; +}; + +struct ZoneSpellsBlocked { + uint32 spellid; + int8 type; + xyz_location m_Location; + xyz_location m_Difference; + char message[256]; +}; + +struct TraderCharges_Struct { + uint32 ItemID[80]; + int32 SerialNumber[80]; + uint32 ItemCost[80]; + int32 Charges[80]; +}; + +const int MaxMercStanceID = 9; + +struct MercStanceInfo { + uint8 ProficiencyID; + uint8 ClassID; + uint32 StanceID; + uint8 IsDefault; +}; + +struct MercTemplate { + uint32 MercTemplateID; + uint32 MercType; // From dbstr_us.txt - Apprentice (330000100), Journeyman (330000200), Master (330000300) + uint32 MercSubType; // From dbstr_us.txt - 330020105^23^Race: Guktan
Type: Healer
Confidence: High
Proficiency: Apprentice, Tier V... + uint16 RaceID; + uint8 ClassID; + uint32 MercNPCID; + uint8 ProficiencyID; + uint8 TierID; + uint8 CostFormula; // To determine cost to client + uint32 ClientVersion; // Only send valid mercs per expansion + uint8 MercNameType; // Determines if merc gets random name or default text + char MercNamePrefix[25]; + char MercNameSuffix[25]; + uint32 Stances[MaxMercStanceID]; +}; + +struct MercInfo { + uint32 mercid; + uint8 slot; + char merc_name[64]; + uint32 MercTemplateID; + const MercTemplate* myTemplate; + uint32 SuspendedTime; + bool IsSuspended; + uint32 MercTimerRemaining; + uint8 Gender; + float MercSize; + int32 State; + uint32 Stance; + int32 hp; + int32 mana; + int32 endurance; + uint8 face; + uint8 luclinHairStyle; + uint8 luclinHairColor; + uint8 luclinEyeColor; + uint8 luclinEyeColor2; + uint8 luclinBeardColor; + uint8 luclinBeard; + uint32 drakkinHeritage; + uint32 drakkinTattoo; + uint32 drakkinDetails; +}; + +struct MercSpellEntry { + uint8 proficiencyid; + uint16 spellid; // <= 0 = no spell + uint32 type; // 0 = never, must be one (and only one) of the defined values + int16 stance; // 0 = all, + = only this stance, - = all except this stance + uint8 minlevel; + uint8 maxlevel; + int16 slot; + uint16 proc_chance; + uint32 time_cancast; // when we can cast this spell next +}; + +struct ClientMercEntry { + uint32 id; + uint32 npcid; +}; + +class ZoneDatabase : public SharedDatabase { + typedef std::list ItemList; +public: + ZoneDatabase(); + ZoneDatabase(const char* host, const char* user, const char* passwd, const char* database,uint32 port); + virtual ~ZoneDatabase(); + + /* Objects and World Containers */ + void LoadWorldContainer(uint32 parentid, ItemInst* container); + void SaveWorldContainer(uint32 zone_id, uint32 parent_id, const ItemInst* container); + void DeleteWorldContainer(uint32 parent_id,uint32 zone_id); + uint32 AddObject(uint32 type, uint32 icon, const Object_Struct& object, const ItemInst* inst); + void UpdateObject(uint32 id, uint32 type, uint32 icon, const Object_Struct& object, const ItemInst* inst); + void DeleteObject(uint32 id); + Ground_Spawns* LoadGroundSpawns(uint32 zone_id, int16 version, Ground_Spawns* gs); + + /* Traders */ + void SaveTraderItem(uint32 char_id,uint32 itemid,uint32 uniqueid, int32 charges,uint32 itemcost,uint8 slot); + void UpdateTraderItemCharges(int char_id, uint32 ItemInstID, int32 charges); + void UpdateTraderItemPrice(int CharID, uint32 ItemID, uint32 Charges, uint32 NewPrice); + void DeleteTraderItem(uint32 char_id); + void DeleteTraderItem(uint32 char_id,uint16 slot_id); + + ItemInst* LoadSingleTraderItem(uint32 char_id, int uniqueid); + Trader_Struct* LoadTraderItem(uint32 char_id); + TraderCharges_Struct* LoadTraderItemWithCharges(uint32 char_id); + + /* Buyer/Barter */ + void AddBuyLine(uint32 CharID, uint32 BuySlot, uint32 ItemID, const char *ItemName, uint32 Quantity, uint32 Price); + void RemoveBuyLine(uint32 CharID, uint32 BuySlot); + void DeleteBuyLines(uint32 CharID); + void UpdateBuyLine(uint32 CharID, uint32 BuySlot, uint32 Quantity); + + /* General Character Related Stuff */ + bool SetServerFilters(char* name, ServerSideFilters_Struct *ssfs); + uint32 GetServerFilters(char* name, ServerSideFilters_Struct *ssfs); + + void SaveBuffs(Client *c); + void LoadBuffs(Client *c); + void LoadPetInfo(Client *c); + void SavePetInfo(Client *c); + void RemoveTempFactions(Client *c); + + /* Character Data Loaders */ + bool LoadCharacterFactionValues(uint32 character_id, faction_map & val_list); + bool LoadCharacterSpellBook(uint32 character_id, PlayerProfile_Struct* pp); + bool LoadCharacterMemmedSpells(uint32 character_id, PlayerProfile_Struct* pp); + bool LoadCharacterLanguages(uint32 character_id, PlayerProfile_Struct* pp); + bool LoadCharacterDisciplines(uint32 character_id, PlayerProfile_Struct* pp); + bool LoadCharacterSkills(uint32 character_id, PlayerProfile_Struct* pp); + bool LoadCharacterData(uint32 character_id, PlayerProfile_Struct* pp, ExtendedProfile_Struct* m_epp); + bool LoadCharacterCurrency(uint32 character_id, PlayerProfile_Struct* pp); + bool LoadCharacterBindPoint(uint32 character_id, PlayerProfile_Struct* pp); + bool LoadCharacterMaterialColor(uint32 character_id, PlayerProfile_Struct* pp); + bool LoadCharacterBandolier(uint32 character_id, PlayerProfile_Struct* pp); + bool LoadCharacterTribute(uint32 character_id, PlayerProfile_Struct* pp); + bool LoadCharacterPotions(uint32 character_id, PlayerProfile_Struct* pp); + bool LoadCharacterLeadershipAA(uint32 character_id, PlayerProfile_Struct* pp); + + /* Character Data Saves */ + bool SaveCharacterBindPoint(uint32 character_id, uint32 zone_id, uint32 instance_id, const xyz_heading& position, uint8 is_home); + bool SaveCharacterCurrency(uint32 character_id, PlayerProfile_Struct* pp); + bool SaveCharacterData(uint32 character_id, uint32 account_id, PlayerProfile_Struct* pp, ExtendedProfile_Struct* m_epp); + bool SaveCharacterAA(uint32 character_id, uint32 aa_id, uint32 current_level); + bool SaveCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); + bool SaveCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); + bool SaveCharacterMaterialColor(uint32 character_id, uint32 slot_id, uint32 color); + bool SaveCharacterSkill(uint32 character_id, uint32 skill_id, uint32 value); + bool SaveCharacterLanguage(uint32 character_id, uint32 lang_id, uint32 value); + bool SaveCharacterDisc(uint32 character_id, uint32 slot_id, uint32 disc_id); + bool SaveCharacterTribute(uint32 character_id, PlayerProfile_Struct* pp); + bool SaveCharacterBandolier(uint32 character_id, uint8 bandolier_id, uint8 bandolier_slot, uint32 item_id, uint32 icon, const char* bandolier_name); + bool SaveCharacterPotionBelt(uint32 character_id, uint8 potion_id, uint32 item_id, uint32 icon); + bool SaveCharacterLeadershipAA(uint32 character_id, PlayerProfile_Struct* pp); + + /* Character Data Deletes */ + bool DeleteCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); + bool DeleteCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); + bool DeleteCharacterDisc(uint32 character_id, uint32 slot_id); + bool DeleteCharacterBandolier(uint32 character_id, uint32 band_id); + bool DeleteCharacterLeadershipAAs(uint32 character_id); + bool DeleteCharacterAAs(uint32 character_id); + bool DeleteCharacterDye(uint32 character_id); + + /* Character Inventory */ + bool NoRentExpired(const char* name); + + /* Corpses */ + bool DeleteItemOffCharacterCorpse(uint32 db_id, uint32 equip_slot, uint32 item_id); + uint32 GetCharacterCorpseItemCount(uint32 corpse_id); + bool LoadCharacterCorpseData(uint32 corpse_id, PlayerCorpse_Struct* pcs); + Corpse* LoadCharacterCorpse(uint32 player_corpse_id); + Corpse* SummonBuriedCharacterCorpses(uint32 char_id, uint32 dest_zoneid, uint16 dest_instanceid, const xyz_heading& position); + void MarkCorpseAsRezzed(uint32 dbid); + bool GetDecayTimes(npcDecayTimes_Struct* npcCorpseDecayTimes); + bool BuryCharacterCorpse(uint32 dbid); + bool BuryAllCharacterCorpses(uint32 charid); + bool DeleteCharacterCorpse(uint32 dbid); + bool SummonAllCharacterCorpses(uint32 char_id, uint32 dest_zoneid, uint16 dest_instanceid, const xyz_heading& position); + bool SummonAllGraveyardCorpses(uint32 cur_zoneid, uint32 dest_zoneid, uint16 dest_instanceid, const xyz_heading& position); + bool UnburyCharacterCorpse(uint32 dbid, uint32 new_zoneid, uint16 dest_instanceid, const xyz_heading& position); + bool LoadCharacterCorpses(uint32 iZoneID, uint16 iInstanceID); + bool DeleteGraveyard(uint32 zone_id, uint32 graveyard_id); + uint32 GetCharacterCorpseDecayTimer(uint32 corpse_db_id); + uint32 GetCharacterBuriedCorpseCount(uint32 char_id); + uint32 SendCharacterCorpseToGraveyard(uint32 dbid, uint32 zoneid, uint16 instanceid, const xyz_heading& position); + uint32 CreateGraveyardRecord(uint32 graveyard_zoneid, const xyz_heading& position); + uint32 AddGraveyardIDToZone(uint32 zone_id, uint32 graveyard_id); + uint32 SaveCharacterCorpse(uint32 charid, const char* charname, uint32 zoneid, uint16 instanceid, PlayerCorpse_Struct* dbpc, const xyz_heading& position); + uint32 UpdateCharacterCorpse(uint32 dbid, uint32 charid, const char* charname, uint32 zoneid, uint16 instanceid, PlayerCorpse_Struct* dbpc, const xyz_heading& position, bool rezzed = false); + uint32 GetFirstCorpseID(uint32 char_id); + uint32 GetCharacterCorpseCount(uint32 char_id); + uint32 GetCharacterCorpseID(uint32 char_id, uint8 corpse); + uint32 GetCharacterCorpseItemAt(uint32 corpse_id, uint16 slotid); + uint32 GetPlayerCorpseTimeLeft(uint8 corpse, uint8 type); + + /* Faction */ + bool GetNPCFactionList(uint32 npcfaction_id, int32* faction_id, int32* value, uint8* temp, int32* primary_faction = 0); + bool GetFactionData(FactionMods* fd, uint32 class_mod, uint32 race_mod, uint32 deity_mod, int32 faction_id); //needed for factions Dec, 16 2001 + bool GetFactionName(int32 faction_id, char* name, uint32 buflen); // needed for factions Dec, 16 2001 + bool GetFactionIdsForNPC(uint32 nfl_id, std::list *faction_list, int32* primary_faction = 0); // improve faction handling + bool SetCharacterFactionLevel(uint32 char_id, int32 faction_id, int32 value, uint8 temp, faction_map &val_list); // needed for factions Dec, 16 2001 + bool LoadFactionData(); + + /* AAs */ + bool LoadAAEffects(); + bool LoadAAEffects2(); + bool LoadSwarmSpells(); + SendAA_Struct*GetAASkillVars(uint32 skill_id); + uint8 GetTotalAALevels(uint32 skill_id); + uint32 GetSizeAA(); + uint32 CountAAs(); + void LoadAAs(SendAA_Struct **load); + uint32 CountAAEffects(); + void FillAAEffects(SendAA_Struct* aa_struct); + + /* Zone related */ + bool GetZoneCFG(uint32 zoneid, uint16 instance_id, NewZone_Struct *data, bool &can_bind, bool &can_combat, bool &can_levitate, bool &can_castoutdoor, bool &is_city, bool &is_hotzone, bool &allow_mercs, uint8 &zone_type, int &ruleset, char **map_filename); + bool SaveZoneCFG(uint32 zoneid, uint16 instance_id, NewZone_Struct* zd); + bool LoadStaticZonePoints(LinkedList* zone_point_list,const char* zonename, uint32 version); + bool UpdateZoneSafeCoords(const char* zonename, const xyz_location& location); + uint8 GetUseCFGSafeCoords(); + int getZoneShutDownDelay(uint32 zoneID, uint32 version); + + /* Spawns and Spawn Points */ + bool LoadSpawnGroups(const char* zone_name, uint16 version, SpawnGroupList* spawn_group_list); + bool LoadSpawnGroupsByID(int spawngroupid, SpawnGroupList* spawn_group_list); + bool PopulateZoneSpawnList(uint32 zoneid, LinkedList &spawn2_list, int16 version, uint32 repopdelay = 0); + Spawn2* LoadSpawn2(LinkedList &spawn2_list, uint32 spawn2id, uint32 timeleft); + bool CreateSpawn2(Client *c, uint32 spawngroup, const char* zone, const xyz_heading& position, uint32 respawn, uint32 variance, uint16 condition, int16 cond_value); + void UpdateSpawn2Timeleft(uint32 id, uint16 instance_id,uint32 timeleft); + uint32 GetSpawnTimeLeft(uint32 id, uint16 instance_id); + void UpdateSpawn2Status(uint32 id, uint8 new_status); + + /* Grids/Paths */ + uint32 GetFreeGrid(uint16 zoneid); + void DeleteGrid(Client *c, uint32 sg2, uint32 grid_num, bool grid_too, uint16 zoneid); + void DeleteWaypoint(Client *c, uint32 grid_num, uint32 wp_num, uint16 zoneid); + void AddWP(Client *c, uint32 gridid, uint32 wpnum, const xyz_heading& position, uint32 pause, uint16 zoneid); + uint32 AddWPForSpawn(Client *c, uint32 spawn2id, const xyz_heading& position, uint32 pause, int type1, int type2, uint16 zoneid); + void ModifyGrid(Client *c, bool remove, uint32 id, uint8 type = 0, uint8 type2 = 0, uint16 zoneid = 0); + void ModifyWP(Client *c, uint32 grid_id, uint32 wp_num, const xyz_location& location, uint32 script = 0, uint16 zoneid = 0); + uint8 GetGridType(uint32 grid, uint32 zoneid); + uint8 GetGridType2(uint32 grid, uint16 zoneid); + bool GetWaypoints(uint32 grid, uint16 zoneid, uint32 num, wplist* wp); + void AssignGrid(Client *client, const xy_location& location, uint32 id); + int GetHighestGrid(uint32 zoneid); + int GetHighestWaypoint(uint32 zoneid, uint32 gridid); + + /* NPCs */ + + uint32 NPCSpawnDB(uint8 command, const char* zone, uint32 zone_version, Client *c, NPC* spawn = 0, uint32 extra = 0); // 0 = Create 1 = Add; 2 = Update; 3 = Remove; 4 = Delete + uint32 CreateNewNPCCommand(const char* zone, uint32 zone_version, Client *client, NPC* spawn, uint32 extra); + uint32 AddNewNPCSpawnGroupCommand(const char* zone, uint32 zone_version, Client *client, NPC* spawn, uint32 respawnTime); + uint32 DeleteSpawnLeaveInNPCTypeTable(const char* zone, Client *client, NPC* spawn); + uint32 DeleteSpawnRemoveFromNPCTypeTable(const char* zone, uint32 zone_version, Client *client, NPC* spawn); + uint32 AddSpawnFromSpawnGroup(const char* zone, uint32 zone_version, Client *client, NPC* spawn, uint32 spawnGroupID); + uint32 AddNPCTypes(const char* zone, uint32 zone_version, Client *client, NPC* spawn, uint32 spawnGroupID); + uint32 UpdateNPCTypeAppearance(Client *client, NPC* spawn); + bool SetSpecialAttkFlag(uint8 id, const char* flag); + bool GetPetEntry(const char *pet_type, PetRecord *into); + bool GetPoweredPetEntry(const char *pet_type, int16 petpower, PetRecord *into); + bool GetBasePetItems(int32 equipmentset, uint32 *items); + void AddLootTableToNPC(NPC* npc, uint32 loottable_id, ItemList* itemlist, uint32* copper, uint32* silver, uint32* gold, uint32* plat); + void AddLootDropToNPC(NPC* npc, uint32 lootdrop_id, ItemList* itemlist, uint8 droplimit, uint8 mindrop); + uint32 GetMaxNPCSpellsID(); + uint32 GetMaxNPCSpellsEffectsID(); + + DBnpcspells_Struct* GetNPCSpells(uint32 iDBSpellsID); + DBnpcspellseffects_Struct* GetNPCSpellsEffects(uint32 iDBSpellsEffectsID); + const NPCType* GetNPCType(uint32 id); + + /* Mercs */ + const NPCType* GetMercType(uint32 id, uint16 raceid, uint32 clientlevel); + void LoadMercEquipment(Merc *merc); + void SaveMercBuffs(Merc *merc); + void LoadMercBuffs(Merc *merc); + bool LoadMercInfo(Client *c); + bool LoadCurrentMerc(Client *c); + bool SaveMerc(Merc *merc); + bool DeleteMerc(uint32 merc_id); + + /* Petitions */ + void UpdateBug(BugStruct* bug); + void UpdateBug(PetitionBug_Struct* bug); + void DeletePetitionFromDB(Petition* wpet); + void UpdatePetitionToDB(Petition* wpet); + void InsertPetitionToDB(Petition* wpet); + void RefreshPetitionsFromDB(); + + /* Merchants */ + void SaveMerchantTemp(uint32 npcid, uint32 slot, uint32 item, uint32 charges); + void DeleteMerchantTemp(uint32 npcid, uint32 slot); + + /* Tradeskills */ + bool GetTradeRecipe(const ItemInst* container, uint8 c_type, uint32 some_id, uint32 char_id, DBTradeskillRecipe_Struct *spec); + bool GetTradeRecipe(uint32 recipe_id, uint8 c_type, uint32 some_id, uint32 char_id, DBTradeskillRecipe_Struct *spec); + uint32 GetZoneForage(uint32 ZoneID, uint8 skill); /* for foraging */ + uint32 GetZoneFishing(uint32 ZoneID, uint8 skill, uint32 &npc_id, uint8 &npc_chance); + void UpdateRecipeMadecount(uint32 recipe_id, uint32 char_id, uint32 madecount); + bool EnableRecipe(uint32 recipe_id); + bool DisableRecipe(uint32 recipe_id); + + /* Tribute */ + bool LoadTributes(); + + /* Doors */ + bool DoorIsOpen(uint8 door_id,const char* zone_name); + void SetDoorPlace(uint8 value,uint8 door_id,const char* zone_name); + bool LoadDoors(int32 iDoorCount, Door *into, const char *zone_name, int16 version); + bool CheckGuildDoor(uint8 doorid,uint16 guild_id, const char* zone); + bool SetGuildDoor(uint8 doorid,uint16 guild_id, const char* zone); + uint32 GetGuildEQID(uint32 guilddbid); + void UpdateDoorGuildID(int doorid, int guild_id); + int32 GetDoorsCount(uint32* oMaxID, const char *zone_name, int16 version); + int32 GetDoorsCountPlusOne(const char *zone_name, int16 version); + int32 GetDoorsDBCountPlusOne(const char *zone_name, int16 version); + void InsertDoor(uint32 did, uint16 ddoorid, const char* ddoor_name, const xyz_heading& position, uint8 dopentype, uint16 dguildid, uint32 dlockpick, uint32 dkeyitem, uint8 ddoor_param, uint8 dinvert, int dincline, uint16 dsize); + + /* Blocked Spells */ + int32 GetBlockedSpellsCount(uint32 zoneid); + bool LoadBlockedSpells(int32 blockedSpellsCount, ZoneSpellsBlocked* into, uint32 zoneid); + + /* Traps */ + bool LoadTraps(const char* zonename, int16 version); + char* GetTrapMessage(uint32 trap_id); + + /* Time */ + uint32 GetZoneTZ(uint32 zoneid, uint32 version); + bool SetZoneTZ(uint32 zoneid, uint32 version, uint32 tz); + + /* Group */ + void RefreshGroupFromDB(Client *c); + uint8 GroupCount(uint32 groupid); + + /* Raid */ + uint8 RaidGroupCount(uint32 raidid, uint32 groupid); + + /* Instancing */ + void ListAllInstances(Client* c, uint32 charid); + + /* QGlobals */ + void QGlobalPurge(); + + /* Alternate Currency */ + void LoadAltCurrencyValues(uint32 char_id, std::map ¤cy); + void UpdateAltCurrencyValue(uint32 char_id, uint32 currency_id, uint32 value); + + /* + * Misc stuff. + * PLEASE DO NOT ADD TO THIS COLLECTION OF CRAP UNLESS YOUR METHOD + * REALLY HAS NO BETTER SECTION + */ + bool logevents(const char* accountname,uint32 accountid,uint8 status,const char* charname,const char* target, const char* descriptiontype, const char* description,int event_nid); + void GetEventLogs(const char* name,char* target,uint32 account_id=0,uint8 eventid=0,char* detail=0,char* timestamp=0, CharacterEventLog_Struct* cel=0); + uint32 GetKarma(uint32 acct_id); + void UpdateKarma(uint32 acct_id, uint32 amount); + + /* Things which really dont belong here... */ + int16 CommandRequirement(const char* commandname); + +protected: + void ZDBInitVars(); + + uint32 max_faction; + Faction** faction_array; + uint32 npc_spells_maxid; + uint32 npc_spellseffects_maxid; + DBnpcspells_Struct** npc_spells_cache; + bool* npc_spells_loadtried; + DBnpcspellseffects_Struct** npc_spellseffects_cache; + bool* npc_spellseffects_loadtried; + uint8 door_isopen_array[255]; +}; + +extern ZoneDatabase database; + +#endif /*ZONEDB_H_*/ + diff --git a/zone/zoning.cpp b/zone/zoning.cpp index 1f83ca76d..67c296c4c 100644 --- a/zone/zoning.cpp +++ b/zone/zoning.cpp @@ -15,17 +15,15 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "../common/debug.h" -#include "zone.h" -#include "worldserver.h" -#include "masterentity.h" -#include "../common/packet_dump.h" +#include "../common/debug.h" #include "../common/rulesys.h" #include "../common/string_util.h" -#include "string_ids.h" -#include "quest_parser_collection.h" #include "queryserv.h" +#include "quest_parser_collection.h" +#include "string_ids.h" +#include "worldserver.h" +#include "zone.h" extern QueryServ* QServ; extern WorldServer worldserver;