diff --git a/.gitignore b/.gitignore index daba92762..89dd521c5 100644 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,6 @@ perl/ *cbp submodules/* -cmake-build-debug/ \ No newline at end of file +cmake-build-debug/ + +.nfs.* \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index faed5b846..38db9d797 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,26 +1,18 @@ language: cpp compiler: gcc -dist: trusty +dist: bionic -before_install: - - sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test - - sudo apt-get update -qq - - mkdir $HOME/usr - - export PATH="$HOME/usr/bin:$PATH" - - wget https://cmake.org/files/v3.11/cmake-3.11.2-Linux-x86_64.sh - - chmod +x cmake-3.11.2-Linux-x86_64.sh - - ./cmake-3.11.2-Linux-x86_64.sh --prefix=$HOME/usr --exclude-subdir --skip-license +addons: + apt: + packages: + - libmysqlclient-dev + - libperl-dev + - libboost-dev + - liblua5.1-0-dev + - zlib1g-dev + - uuid-dev + - libssl-dev -install: - - sudo apt-get install -qq g++-7 - - sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-7 90 - - sudo apt-get install libmysqlclient-dev - - sudo apt-get install libperl-dev - - sudo apt-get install libboost-dev - - sudo apt-get install liblua5.1-0-dev - - sudo apt-get install zlib1g-dev - - sudo apt-get install uuid-dev - - sudo apt-get install libssl-dev script: - cmake -G "Unix Makefiles" -DEQEMU_BUILD_TESTS=ON -DEQEMU_ENABLE_BOTS=ON -DEQEMU_BUILD_LOGIN=ON - make -j2 diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index c4dec4074..a959d9093 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -122,6 +122,7 @@ SET(common_headers cli/terminal_color.hpp data_verification.h database.h + database_schema.h dbcore.h deity.h emu_constants.h diff --git a/common/classes.cpp b/common/classes.cpp index be683fbd1..3aaeeded3 100644 --- a/common/classes.cpp +++ b/common/classes.cpp @@ -573,6 +573,20 @@ bool IsNonSpellFighterClass(uint8 class_id) } } +bool IsHybridClass(uint8 class_id) +{ + switch (class_id) { + case PALADIN: + case RANGER: + case SHADOWKNIGHT: + case BARD: + case BEASTLORD: + return true; + default: + return false; + } +} + bool IsCasterClass(uint8 class_id) { switch (class_id) { diff --git a/common/classes.h b/common/classes.h index 2ca9a3c4d..f63758937 100644 --- a/common/classes.h +++ b/common/classes.h @@ -135,6 +135,7 @@ uint8 GetClassIDFromPlayerClassBit(uint32 player_class_bit); bool IsFighterClass(uint8 class_id); bool IsSpellFighterClass(uint8 class_id); bool IsNonSpellFighterClass(uint8 class_id); +bool IsHybridClass(uint8 class_id); bool IsCasterClass(uint8 class_id); bool IsINTCasterClass(uint8 class_id); bool IsWISCasterClass(uint8 class_id); diff --git a/common/cli/eqemu_command_handler.cpp b/common/cli/eqemu_command_handler.cpp index ef46e0d9f..bbcdb4612 100644 --- a/common/cli/eqemu_command_handler.cpp +++ b/common/cli/eqemu_command_handler.cpp @@ -38,10 +38,6 @@ namespace EQEmuCommand { void DisplayDebug(argh::parser &cmd) { if (cmd[{"-d", "--debug"}]) { - std::cout << "Positional args:\n"; - for (auto &pos_arg : cmd) - std::cout << '\t' << pos_arg << std::endl; - std::cout << "Positional args:\n"; for (auto &pos_arg : cmd.pos_args()) std::cout << '\t' << pos_arg << std::endl; @@ -73,29 +69,37 @@ namespace EQEmuCommand { { bool arguments_filled = true; + int index = 2; for (auto &arg : arguments) { - if (cmd(arg).str().empty()) { + if (cmd(arg).str().empty() && cmd(index).str().empty()) { arguments_filled = false; } + index++; } if (!arguments_filled || argc == 2) { std::string arguments_string; for (auto &arg : arguments) { - arguments_string += " " + arg + "=*\n"; + arguments_string += " " + arg; } std::string options_string; - for (auto &opt : options) { + for (auto &opt : options) { options_string += " " + opt + "\n"; } - std::cout << fmt::format( - "Command\n\n{0} \n\nArgs\n{1}\nOptions\n{2}", - argv[1], - arguments_string, - options_string - ) << std::endl; + std::stringstream command_string; + + command_string << + termcolor::colorize << + termcolor::yellow << + "\nCommand" << + termcolor::reset << "\n\n" << + termcolor::green << argv[1] << arguments_string << termcolor::reset << "\n" << + termcolor::yellow << (!options_string.empty() ? "\nOptions\n" : "") << + termcolor::reset << termcolor::cyan << options_string << termcolor::reset; + + std::cout << command_string.str() << std::endl; exit(1); } @@ -123,10 +127,6 @@ namespace EQEmuCommand { bool ran_command = false; for (auto &it: in_function_map) { if (it.first == argv[1]) { - std::cout << std::endl; - std::cout << "> " << termcolor::cyan << "Executing CLI Command" << termcolor::reset << std::endl; - std::cout << std::endl; - (it.second)(argc, argv, cmd, description); ran_command = true; } @@ -182,12 +182,13 @@ namespace EQEmuCommand { } std::cout << std::endl; - } - else if (!ran_command) { - std::cerr << "Unknown command [" << argv[1] << "] ! Try --help" << std::endl; + + std::exit(1); } - exit(1); + if (ran_command) { + std::exit(1); + } } } diff --git a/common/database.cpp b/common/database.cpp index 70741cf86..352ecbbc1 100644 --- a/common/database.cpp +++ b/common/database.cpp @@ -45,6 +45,7 @@ #include "eq_packet_structs.h" #include "extprofile.h" #include "string_util.h" +#include "database_schema.h" extern Client client; @@ -124,7 +125,7 @@ uint32 Database::CheckLogin(const char* name, const char* password, const char * //Get Banned IP Address List - Only return false if the incoming connection's IP address is not present in the banned_ips table. bool Database::CheckBannedIPs(const char* loginIP) { - std::string query = StringFormat("SELECT ip_address FROM Banned_IPs WHERE ip_address='%s'", loginIP); + std::string query = StringFormat("SELECT ip_address FROM banned_ips WHERE ip_address='%s'", loginIP); auto results = QueryDatabase(query); @@ -140,7 +141,7 @@ bool Database::CheckBannedIPs(const char* loginIP) } bool Database::AddBannedIP(char* bannedIP, const char* notes) { - std::string query = StringFormat("INSERT into Banned_IPs SET ip_address='%s', notes='%s'", bannedIP, notes); + std::string query = StringFormat("INSERT into banned_ips SET ip_address='%s', notes='%s'", bannedIP, notes); auto results = QueryDatabase(query); if (!results.Success()) { return false; @@ -293,6 +294,37 @@ bool Database::SetAccountStatus(const char* name, int16 status) { return true; } +/** + * @param account_name + * @param status + * @return + */ +bool Database::SetAccountStatus(const std::string& account_name, int16 status) +{ + LogInfo("Account [{}] is attempting to be set to status [{}]", account_name, status); + + std::string query = fmt::format( + SQL( + UPDATE account SET status = {} WHERE name = '{}' + ), + status, + account_name + ); + + auto results = QueryDatabase(query); + + if (!results.Success()) { + return false; + } + + if (results.RowsAffected() == 0) { + LogWarning("Account [{}] does not exist!", account_name); + return false; + } + + return true; +} + /* This initially creates the character during character create */ bool Database::ReserveName(uint32 account_id, char* name) { std::string query = StringFormat("SELECT `account_id`, `name` FROM `character_data` WHERE `name` = '%s'", name); @@ -307,70 +339,81 @@ bool Database::ReserveName(uint32 account_id, char* name) { query = StringFormat("INSERT INTO `character_data` SET `account_id` = %i, `name` = '%s'", account_id, name); results = QueryDatabase(query); if (!results.Success() || results.ErrorMessage() != ""){ return false; } + + // Put character into the default guild if rule is being used. + int guild_id = RuleI(Character, DefaultGuild); + + if (guild_id != 0) { + int character_id=results.LastInsertedID(); + if (character_id > -1) { + query = StringFormat("INSERT INTO `guild_members` SET `char_id` = %i, `guild_id` = '%i'", character_id, guild_id); + results = QueryDatabase(query); + if (!results.Success() || results.ErrorMessage() != ""){ + LogInfo("Could not put character [{}] into default Guild", name); + } + } + } + return true; } -/* - Delete the character with the name "name" - returns false on failure, true otherwise -*/ -bool Database::DeleteCharacter(char *name) { - uint32 charid = 0; - if(!name || !strlen(name)) { +/** + * @param character_name + * @return + */ +bool Database::DeleteCharacter(char *character_name) { + uint32 character_id = 0; + if(!character_name || !strlen(character_name)) { LogInfo("DeleteCharacter: request to delete without a name (empty char slot)"); return false; } - LogInfo("Database::DeleteCharacter name : [{}]", name); - /* Get id from character_data before deleting record so we can clean up the rest of the tables */ - std::string query = StringFormat("SELECT `id` from `character_data` WHERE `name` = '%s'", name); - auto results = QueryDatabase(query); - for (auto row = results.begin(); row != results.end(); ++row) { charid = atoi(row[0]); } - if (charid <= 0){ - LogError("Database::DeleteCharacter :: Character ({}) not found, stopping delete...", name); + std::string query = StringFormat("SELECT `id` from `character_data` WHERE `name` = '%s'", character_name); + auto results = QueryDatabase(query); + for (auto row = results.begin(); row != results.end(); ++row) { + character_id = atoi(row[0]); + } + + if (character_id <= 0) { + LogError("DeleteCharacter | Invalid Character ID [{}]", character_name); return false; } - query = StringFormat("DELETE FROM `quest_globals` WHERE `charid` = '%d'", charid); QueryDatabase(query); - query = StringFormat("DELETE FROM `character_activities` WHERE `charid` = '%d'", charid); QueryDatabase(query); - query = StringFormat("DELETE FROM `character_enabledtasks` WHERE `charid` = '%d'", charid); QueryDatabase(query); - query = StringFormat("DELETE FROM `character_tasks` WHERE `charid` = '%d'", charid); QueryDatabase(query); - query = StringFormat("DELETE FROM `completed_tasks` WHERE `charid` = '%d'", charid); QueryDatabase(query); - query = StringFormat("DELETE FROM `friends` WHERE `charid` = '%d'", charid); QueryDatabase(query); - query = StringFormat("DELETE FROM `mail` WHERE `charid` = '%d'", charid); QueryDatabase(query); - query = StringFormat("DELETE FROM `timers` WHERE `char_id` = '%d'", charid); QueryDatabase(query); - query = StringFormat("DELETE FROM `inventory` WHERE `charid` = '%d'", charid); QueryDatabase(query); - query = StringFormat("DELETE FROM `char_recipe_list` WHERE `char_id` = '%d'", charid); QueryDatabase(query); - query = StringFormat("DELETE FROM `adventure_stats` WHERE `player_id` ='%d'", charid); QueryDatabase(query); - query = StringFormat("DELETE FROM `zone_flags` WHERE `charID` = '%d'", charid); QueryDatabase(query); - query = StringFormat("DELETE FROM `titles` WHERE `char_id` = '%d'", charid); QueryDatabase(query); - query = StringFormat("DELETE FROM `player_titlesets` WHERE `char_id` = '%d'", charid); QueryDatabase(query); - query = StringFormat("DELETE FROM `keyring` WHERE `char_id` = '%d'", charid); QueryDatabase(query); - query = StringFormat("DELETE FROM `faction_values` WHERE `char_id` = '%d'", charid); QueryDatabase(query); - query = StringFormat("DELETE FROM `instance_list_player` WHERE `charid` = '%d'", charid); QueryDatabase(query); - query = StringFormat("DELETE FROM `character_data` WHERE `id` = '%d'", charid); QueryDatabase(query); - query = StringFormat("DELETE FROM `character_skills` WHERE `id` = %u", charid); QueryDatabase(query); - query = StringFormat("DELETE FROM `character_languages` WHERE `id` = %u", charid); QueryDatabase(query); - query = StringFormat("DELETE FROM `character_bind` WHERE `id` = %u", charid); QueryDatabase(query); - query = StringFormat("DELETE FROM `character_alternate_abilities` WHERE `id` = %u", charid); QueryDatabase(query); - query = StringFormat("DELETE FROM `character_currency` WHERE `id` = %u", charid); QueryDatabase(query); - query = StringFormat("DELETE FROM `character_data` WHERE `id` = %u", charid); QueryDatabase(query); - query = StringFormat("DELETE FROM `character_spells` WHERE `id` = %u", charid); QueryDatabase(query); - query = StringFormat("DELETE FROM `character_memmed_spells` WHERE `id` = %u", charid); QueryDatabase(query); - query = StringFormat("DELETE FROM `character_disciplines` WHERE `id` = %u", charid); QueryDatabase(query); - query = StringFormat("DELETE FROM `character_material` WHERE `id` = %u", charid); QueryDatabase(query); - query = StringFormat("DELETE FROM `character_tribute` WHERE `id` = %u", charid); QueryDatabase(query); - query = StringFormat("DELETE FROM `character_bandolier` WHERE `id` = %u", charid); QueryDatabase(query); - query = StringFormat("DELETE FROM `character_potionbelt` WHERE `id` = %u", charid); QueryDatabase(query); - query = StringFormat("DELETE FROM `character_inspect_messages` WHERE `id` = %u", charid); QueryDatabase(query); - query = StringFormat("DELETE FROM `character_leadership_abilities` WHERE `id` = %u", charid); QueryDatabase(query); - query = StringFormat("DELETE FROM `character_alt_currency` WHERE `char_id` = '%d'", charid); QueryDatabase(query); + std::string delete_type = "hard-deleted"; + if (RuleB(Character, SoftDeletes)) { + delete_type = "soft-deleted"; + std::string query = fmt::format( + SQL( + UPDATE + character_data + SET + name = SUBSTRING(CONCAT(name, '-deleted-', UNIX_TIMESTAMP()), 1, 64), + deleted_at = NOW() + WHERE + id = '{}' + ), + character_id + ); + + QueryDatabase(query); + + return true; + } + + LogInfo("DeleteCharacter | Character [{}] ({}) is being [{}]", character_name, character_id, delete_type); + + for (const auto& iter : DatabaseSchema::GetCharacterTables()) { + std::string table_name = iter.first; + std::string character_id_column_name = iter.second; + + QueryDatabase(fmt::format("DELETE FROM {} WHERE {} = {}", table_name, character_id_column_name, character_id)); + } + #ifdef BOTS - query = StringFormat("DELETE FROM `guild_members` WHERE `char_id` = '%d' AND GetMobTypeById(%i) = 'C'", charid); // note: only use of GetMobTypeById() -#else - query = StringFormat("DELETE FROM `guild_members` WHERE `char_id` = '%d'", charid); -#endif + query = StringFormat("DELETE FROM `guild_members` WHERE `char_id` = '%d' AND GetMobTypeById(%i) = 'C'", character_id); // note: only use of GetMobTypeById() QueryDatabase(query); +#endif + return true; } @@ -652,6 +695,7 @@ bool Database::SaveCharacterCreate(uint32 character_id, uint32 account_id, Playe pp->RestTimer // " RestTimer) " ); auto results = QueryDatabase(query); + /* Save Bind Points */ query = StringFormat("REPLACE INTO `character_bind` (id, zone_id, instance_id, x, y, z, heading, slot)" " VALUES (%u, %u, %u, %f, %f, %f, %f, %i), " diff --git a/common/database.h b/common/database.h index 0dce5318d..f569da20b 100644 --- a/common/database.h +++ b/common/database.h @@ -107,7 +107,7 @@ public: bool AddToNameFilter(const char* name); bool CreateCharacter(uint32 account_id, char* name, uint16 gender, uint16 race, uint16 class_, uint8 str, uint8 sta, uint8 cha, uint8 dex, uint8 int_, uint8 agi, uint8 wis, uint8 face); - bool DeleteCharacter(char* name); + bool DeleteCharacter(char* character_name); bool MoveCharacterToZone(const char* charname, const char* zonename); bool MoveCharacterToZone(const char* charname, const char* zonename,uint32 zoneid); bool MoveCharacterToZone(uint32 iCharID, const char* iZonename); @@ -120,7 +120,7 @@ public: /* General Information Queries */ - bool AddBannedIP(char* bannedIP, const char* notes); //Add IP address to the Banned_IPs table. + bool AddBannedIP(char* bannedIP, const char* notes); //Add IP address to the banned_ips table. bool AddGMIP(char* ip_address, char* name); bool CheckBannedIPs(const char* loginIP); //Check incoming connection against banned IP table. bool CheckGMIPs(const char* loginIP, uint32 account_id); @@ -179,6 +179,7 @@ public: bool DeleteAccount(const char *name, const char* loginserver); bool GetLiveChar(uint32 account_id, char* cname); bool SetAccountStatus(const char* name, int16 status); + bool SetAccountStatus(const std::string& account_name, int16 status); bool SetLocalPassword(uint32 accid, const char* password); bool UpdateLiveChar(char* charname, uint32 account_id); diff --git a/common/database_schema.h b/common/database_schema.h new file mode 100644 index 000000000..b497ad474 --- /dev/null +++ b/common/database_schema.h @@ -0,0 +1,342 @@ +/** + * EQEmulator: Everquest Server Emulator + * Copyright (C) 2001-2019 EQEmulator Development Team (https://github.com/EQEmu/Server) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY except by those people which sell it, which + * are required to give you total support for your newly bought product; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef EQEMU_DATABASE_SCHEMA_H +#define EQEMU_DATABASE_SCHEMA_H + +#include +#include + +namespace DatabaseSchema { + + /** + * Character-specific tables + * + * Does not included related meta-data tables such as 'guilds', 'accounts' + * @return + */ + static std::map GetCharacterTables() + { + return { + {"adventure_stats", "player_id"}, + {"buyer", "charid"}, + {"char_recipe_list", "char_id"}, + {"character_activities", "charid"}, + {"character_alt_currency", "char_id"}, + {"character_alternate_abilities", "id"}, + {"character_auras", "id"}, + {"character_bandolier", "id"}, + {"character_bind", "id"}, + {"character_buffs", "character_id"}, + {"character_corpses", "id"}, + {"character_currency", "id"}, + {"character_data", "id"}, + {"character_disciplines", "id"}, + {"character_enabledtasks", "charid"}, + {"character_inspect_messages", "id"}, + {"character_item_recast", "id"}, + {"character_languages", "id"}, + {"character_leadership_abilities", "id"}, + {"character_material", "id"}, + {"character_memmed_spells", "id"}, + {"character_pet_buffs", "char_id"}, + {"character_pet_info", "char_id"}, + {"character_pet_inventory", "char_id"}, + {"character_potionbelt", "id"}, + {"character_skills", "id"}, + {"character_spells", "id"}, + {"character_tasks", "charid"}, + {"character_tribute", "id"}, + {"completed_tasks", "charid"}, + {"data_buckets", "id"}, + {"faction_values", "char_id"}, + {"friends", "charid"}, + {"guild_members", "char_id"}, + {"guilds", "id"}, + {"instance_list_player", "id"}, + {"inventory", "charid"}, + {"inventory_snapshots", "charid"}, + {"keyring", "char_id"}, + {"mail", "charid"}, + {"player_titlesets", "char_id"}, + {"quest_globals", "charid"}, + {"timers", "char_id"}, + {"titles", "char_id"}, + {"trader", "char_id"}, + {"zone_flags", "charID"} + }; + } + + /** + * Gets all player and meta-data tables + * + * @return + */ + static std::vector GetPlayerTables() + { + return { + "account", + "account_ip", + "account_flags", + "account_rewards", + "adventure_details", + "adventure_stats", + "buyer", + "char_recipe_list", + "character_activities", + "character_alt_currency", + "character_alternate_abilities", + "character_auras", + "character_bandolier", + "character_bind", + "character_buffs", + "character_corpse_items", + "character_corpses", + "character_currency", + "character_data", + "character_disciplines", + "character_enabledtasks", + "character_inspect_messages", + "character_item_recast", + "character_languages", + "character_leadership_abilities", + "character_material", + "character_memmed_spells", + "character_pet_buffs", + "character_pet_info", + "character_pet_inventory", + "character_potionbelt", + "character_skills", + "character_spells", + "character_tasks", + "character_tribute", + "completed_tasks", + "data_buckets", + "faction_values", + "friends", + "guild_bank", + "guild_members", + "guild_ranks", + "guild_relations", + "guilds", + "instance_list_player", + "inventory", + "inventory_snapshots", + "keyring", + "mail", + "player_titlesets", + "quest_globals", + "sharedbank", + "timers", + "titles", + "trader", + "trader_audit", + "zone_flags" + }; + } + + /** + * Gets content tables + * + * @return + */ + static std::vector GetContentTables() + { + return { + "aa_ability", + "aa_actions", + "aa_effects", + "aa_rank_effects", + "aa_rank_prereqs", + "aa_ranks", + "aa_required_level_cost", + "adventure_template", + "adventure_template_entry", + "adventure_template_entry_flavor", + "altadv_vars", + "alternate_currency", + "auras", + "base_data", + "blocked_spells", + "books", + "char_create_combinations", + "char_create_point_allocations", + "class_skill", + "damageshieldtypes", + "doors", + "faction_base_data", + "faction_list", + "faction_list_mod", + "fear_hints", + "fishing", + "forage", + "global_loot", + "goallists", + "graveyard", + "grid", + "grid_entries", + "ground_spawns", + "horses", + "instance_list", + "items", + "ldon_trap_entries", + "ldon_trap_templates", + "lootdrop", + "lootdrop_entries", + "loottable", + "loottable_entries", + "merchantlist", + "npc_emotes", + "npc_faction", + "npc_faction_entries", + "npc_scale_global_base", + "npc_spells", + "npc_spells_effects", + "npc_spells_effects_entries", + "npc_spells_entries", + "npc_types", + "npc_types_metadata", + "npc_types_tint", + "object", + "pets", + "pets_equipmentset", + "pets_equipmentset_entries", + "proximities", + "races", + "skill_caps", + "spawn2", + "spawn_condition_values", + "spawn_conditions", + "spawn_events", + "spawnentry", + "spawngroup", + "spells_new", + "start_zones", + "starting_items", + "task_activities", + "tasks", + "tasksets", + "titles", + "tradeskill_recipe", + "tradeskill_recipe_entries", + "traps", + "tribute_levels", + "tributes", + "veteran_reward_templates", + "zone", + "zone_points", + "zone_server", + "zoneserver_auth", + }; + } + + /** + * Gets server tables + * + * @return + */ + static std::vector GetServerTables() + { + return { + "banned_ips", + "bugs", + "bug_reports", + "command_settings", + "db_str", + "discovered_items", + "eqtime", + "eventlog", + "gm_ips", + "hackers", + "ip_exemptions", + "launcher", + "launcher_zones", + "level_exp_mods", + "logsys_categories", + "name_filter", + "perl_event_export_settings", + "petitions", + "profanity_list", + "reports", + "rule_sets", + "rule_values", + "saylink", + "variables", + }; + } + + /** + * Gets state tables + * Tables that keep track of server state + * + * @return + */ + static std::vector GetStateTables() + { + return { + "adventure_members", + "chatchannels", + "group_id", + "group_leaders", + "item_tick", + "lfguild", + "merchantlist_temp", + "object_contents", + "raid_details", + "raid_leaders", + "raid_members", + "respawn_times", + "spell_buckets", + "spell_globals", + }; + } + + /** + * Gets login tables + * + * @return + */ + static std::vector GetLoginTables() + { + return { + "login_accounts", + "login_api_tokens", + "login_server_admins", + "login_server_list_types", + "login_world_servers", + }; + } + + /** + * Gets login tables + * + * @return + */ + static std::vector GetVersionTables() + { + return { + "db_version", + "inventory_versions", + }; + } + +} + +#endif //EQEMU_DATABASE_SCHEMA_H diff --git a/common/dbcore.cpp b/common/dbcore.cpp index 2c46f298a..638fab4ae 100644 --- a/common/dbcore.cpp +++ b/common/dbcore.cpp @@ -115,14 +115,14 @@ MySQLRequestResult DBcore::QueryDatabase(const char *query, uint32 querylen, boo auto errorBuffer = new char[MYSQL_ERRMSG_SIZE]; snprintf(errorBuffer, MYSQL_ERRMSG_SIZE, "#%i: %s", mysql_errno(&mysql), mysql_error(&mysql)); - /* Implement Logging at the Root */ + /** + * Error logging + */ if (mysql_errno(&mysql) > 0 && strlen(query) > 0) { - if (LogSys.log_settings[Logs::MySQLError].is_category_enabled == 1) - Log(Logs::General, Logs::MySQLError, "%i: %s \n %s", mysql_errno(&mysql), mysql_error(&mysql), query); + LogMySQLError("[{}] [{}]\n[{}]", mysql_errno(&mysql), mysql_error(&mysql), query); } return MySQLRequestResult(nullptr, 0, 0, 0, 0, mysql_errno(&mysql), errorBuffer); - } // successful query. get results. @@ -143,10 +143,8 @@ MySQLRequestResult DBcore::QueryDatabase(const char *query, uint32 querylen, boo if (LogSys.log_settings[Logs::MySQLQuery].is_category_enabled == 1) { if ((strncasecmp(query, "select", 6) == 0)) { - LogF( - Logs::General, - Logs::MySQLQuery, - "{0} ({1} row{2} returned) ({3}ms)", + LogMySQLQuery( + "{0} ({1} row{2} returned) ({3}s)", query, requestResult.RowCount(), requestResult.RowCount() == 1 ? "" : "s", @@ -154,10 +152,8 @@ MySQLRequestResult DBcore::QueryDatabase(const char *query, uint32 querylen, boo ); } else { - LogF( - Logs::General, - Logs::MySQLQuery, - "{0} ({1} row{2} affected) ({3}ms)", + LogMySQLQuery( + "{0} ({1} row{2} affected) ({3}s)", query, requestResult.RowsAffected(), requestResult.RowsAffected() == 1 ? "" : "s", diff --git a/common/emu_constants.h b/common/emu_constants.h index c6491b3b3..945f513b5 100644 --- a/common/emu_constants.h +++ b/common/emu_constants.h @@ -317,6 +317,15 @@ namespace EQEmu QuestControlGrid = -1 }; + namespace consent { + enum eConsentType : uint8 { + Normal = 0, + Group, + Raid, + Guild + }; + }; // namespace consent + } /*EQEmu*/ #endif /*COMMON_EMU_CONSTANTS_H*/ diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index 53d7021ba..3fe2430db 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -35,6 +35,7 @@ static const uint32 MAX_MERC = 100; static const uint32 MAX_MERC_GRADES = 10; static const uint32 MAX_MERC_STANCES = 10; static const uint32 BLOCKED_BUFF_COUNT = 20; +static const uint32 QUESTREWARD_COUNT = 8; /* @@ -2180,14 +2181,7 @@ struct QuestReward_Struct /*024*/ uint32 silver; // Gives silver to the client /*028*/ uint32 gold; // Gives gold to the client /*032*/ uint32 platinum; // Gives platinum to the client - /*036*/ uint32 item_id; - /*040*/ uint32 unknown040; - /*044*/ uint32 unknown044; - /*048*/ uint32 unknown048; - /*052*/ uint32 unknown052; - /*056*/ uint32 unknown056; - /*060*/ uint32 unknown060; - /*064*/ uint32 unknown064; + /*036*/ int32 item_id[QUESTREWARD_COUNT]; // -1 for nothing /*068*/ }; diff --git a/common/eqemu_config.h b/common/eqemu_config.h index e75737d76..a4fb7797f 100644 --- a/common/eqemu_config.h +++ b/common/eqemu_config.h @@ -165,7 +165,7 @@ class EQEmuConfig fconfig >> _config->_root; _config->parse_config(); } - catch (std::exception) { + catch (std::exception &) { return false; } return true; diff --git a/common/eqemu_logsys.cpp b/common/eqemu_logsys.cpp index c2d786d94..f5d58a5fd 100644 --- a/common/eqemu_logsys.cpp +++ b/common/eqemu_logsys.cpp @@ -579,3 +579,25 @@ void EQEmuLogSys::StartFileLogs(const std::string &log_name) ); } } + +/** + * Silence console logging + */ +void EQEmuLogSys::SilenceConsoleLogging() +{ + for (int log_index = Logs::AA; log_index != Logs::MaxCategoryID; log_index++) { + log_settings[log_index].log_to_console = 0; + log_settings[log_index].is_category_enabled = 0; + } +} + +/** + * Enables console logging + */ +void EQEmuLogSys::EnableConsoleLogging() +{ + for (int log_index = Logs::AA; log_index != Logs::MaxCategoryID; log_index++) { + log_settings[log_index].log_to_console = Logs::General; + log_settings[log_index].is_category_enabled = 1; + } +} diff --git a/common/eqemu_logsys.h b/common/eqemu_logsys.h index 11645c6fa..9fa164d98 100644 --- a/common/eqemu_logsys.h +++ b/common/eqemu_logsys.h @@ -107,6 +107,13 @@ namespace Logs { Emergency, Alert, Notice, + AIScanClose, + AIYellForHelp, + AICastBeneficialClose, + AoeCast, + EntityManagement, + Flee, + Aura, MaxCategoryID /* Don't Remove this */ }; @@ -172,7 +179,14 @@ namespace Logs { "Critical", "Emergency", "Alert", - "Notice" + "Notice", + "AI Scan Close", + "AI Yell For Help", + "AI Cast Beneficial Close", + "AOE Cast", + "Entity Management", + "Flee", + "Aura", }; } @@ -279,6 +293,16 @@ public: */ void SetConsoleHandler(std::function f) { on_log_console_hook = f; } + /** + * Silence console logging + */ + void SilenceConsoleLogging(); + + /** + * Turn on all console logging + */ + void EnableConsoleLogging(); + private: /** diff --git a/common/eqemu_logsys_log_aliases.h b/common/eqemu_logsys_log_aliases.h index f90d0e223..ccfc54f3a 100644 --- a/common/eqemu_logsys_log_aliases.h +++ b/common/eqemu_logsys_log_aliases.h @@ -491,6 +491,76 @@ OutF(LogSys, Logs::Detail, Logs::Status, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) +#define LogAIScanClose(message, ...) do {\ + if (LogSys.log_settings[Logs::AIScanClose].is_category_enabled == 1)\ + OutF(LogSys, Logs::General, Logs::AIScanClose, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogAIScanCloseDetail(message, ...) do {\ + if (LogSys.log_settings[Logs::AIScanClose].is_category_enabled == 1)\ + OutF(LogSys, Logs::Detail, Logs::AIScanClose, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogAIYellForHelp(message, ...) do {\ + if (LogSys.log_settings[Logs::AIYellForHelp].is_category_enabled == 1)\ + OutF(LogSys, Logs::General, Logs::AIYellForHelp, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogAIYellForHelpDetail(message, ...) do {\ + if (LogSys.log_settings[Logs::AIYellForHelp].is_category_enabled == 1)\ + OutF(LogSys, Logs::Detail, Logs::AIYellForHelp, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogAICastBeneficialClose(message, ...) do {\ + if (LogSys.log_settings[Logs::AICastBeneficialClose].is_category_enabled == 1)\ + OutF(LogSys, Logs::General, Logs::AICastBeneficialClose, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogAICastBeneficialCloseDetail(message, ...) do {\ + if (LogSys.log_settings[Logs::AICastBeneficialClose].is_category_enabled == 1)\ + OutF(LogSys, Logs::Detail, Logs::AICastBeneficialClose, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogAoeCast(message, ...) do {\ + if (LogSys.log_settings[Logs::AoeCast].is_category_enabled == 1)\ + OutF(LogSys, Logs::General, Logs::AoeCast, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogAoeCastDetail(message, ...) do {\ + if (LogSys.log_settings[Logs::AoeCast].is_category_enabled == 1)\ + OutF(LogSys, Logs::Detail, Logs::AoeCast, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogEntityManagement(message, ...) do {\ + if (LogSys.log_settings[Logs::EntityManagement].is_category_enabled == 1)\ + OutF(LogSys, Logs::General, Logs::EntityManagement, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogEntityManagementDetail(message, ...) do {\ + if (LogSys.log_settings[Logs::EntityManagement].is_category_enabled == 1)\ + OutF(LogSys, Logs::Detail, Logs::EntityManagement, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogFlee(message, ...) do {\ + if (LogSys.log_settings[Logs::Flee].is_category_enabled == 1)\ + OutF(LogSys, Logs::General, Logs::Flee, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogFleeDetail(message, ...) do {\ + if (LogSys.log_settings[Logs::Flee].is_category_enabled == 1)\ + OutF(LogSys, Logs::Detail, Logs::Flee, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogAura(message, ...) do {\ + if (LogSys.log_settings[Logs::Aura].is_category_enabled == 1)\ + OutF(LogSys, Logs::General, Logs::Aura, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogAuraDetail(message, ...) do {\ + if (LogSys.log_settings[Logs::Aura].is_category_enabled == 1)\ + OutF(LogSys, Logs::Detail, Logs::Aura, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + #define Log(debug_level, log_category, message, ...) do {\ if (LogSys.log_settings[log_category].is_category_enabled == 1)\ LogSys.Out(debug_level, log_category, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ @@ -782,6 +852,48 @@ #define LogStatusDetail(message, ...) do {\ } while (0) +#define LogAIScanClose(message, ...) do {\ +} while (0) + +#define LogAIScanCloseDetail(message, ...) do {\ +} while (0) + +#define LogAIYellForHelp(message, ...) do {\ +} while (0) + +#define LogAIYellForHelpDetail(message, ...) do {\ +} while (0) + +#define LogAICastBeneficialClose(message, ...) do {\ +} while (0) + +#define LogAICastBeneficialCloseDetail(message, ...) do {\ +} while (0) + +#define LogAoeCast(message, ...) do {\ +} while (0) + +#define LogAoeCastDetail(message, ...) do {\ +} while (0) + +#define LogEntityManagement(message, ...) do {\ +} while (0) + +#define LogEntityManagementDetail(message, ...) do {\ +} while (0) + +#define LogFlee(message, ...) do {\ +} while (0) + +#define LogFleeDetail(message, ...) do {\ +} while (0) + +#define LogAura(message, ...) do {\ +} while (0) + +#define LogAuraDetail(message, ...) do {\ +} while (0) + #define Log(debug_level, log_category, message, ...) do {\ } while (0) diff --git a/common/event/event_loop.h b/common/event/event_loop.h index 268f78c4d..295c0532d 100644 --- a/common/event/event_loop.h +++ b/common/event/event_loop.h @@ -21,6 +21,10 @@ namespace EQ uv_run(&m_loop, UV_RUN_NOWAIT); } + void Run() { + uv_run(&m_loop, UV_RUN_DEFAULT); + } + uv_loop_t* Handle() { return &m_loop; } private: diff --git a/common/json/jsoncpp.cpp b/common/json/jsoncpp.cpp index 00c48700d..759e68fa6 100644 --- a/common/json/jsoncpp.cpp +++ b/common/json/jsoncpp.cpp @@ -5277,7 +5277,7 @@ void StreamWriterBuilder::setDefaults(Json::Value* settings) { //! [StreamWriterBuilderDefaults] (*settings)["commentStyle"] = "All"; - (*settings)["indentation"] = "\t"; + (*settings)["indentation"] = " "; (*settings)["enableYAMLCompatibility"] = false; (*settings)["dropNullPlaceholders"] = false; (*settings)["useSpecialFloats"] = false; diff --git a/common/json_config.cpp b/common/json_config.cpp index d262038f6..5f4c47b71 100644 --- a/common/json_config.cpp +++ b/common/json_config.cpp @@ -32,7 +32,7 @@ EQ::JsonConfigFile EQ::JsonConfigFile::Load( try { ifs >> ret.m_root; } - catch (std::exception) { + catch (std::exception &) { return ret; } @@ -81,7 +81,7 @@ std::string EQ::JsonConfigFile::GetVariableString( return m_root[title][parameter].asString(); } } - catch (std::exception) { + catch (std::exception &) { return default_value; } @@ -105,7 +105,7 @@ int EQ::JsonConfigFile::GetVariableInt( return m_root[title][parameter].asInt(); } } - catch (std::exception) { + catch (std::exception &) { return default_value; } @@ -129,7 +129,7 @@ bool EQ::JsonConfigFile::GetVariableBool( return m_root[title][parameter].asBool(); } } - catch (std::exception) { + catch (std::exception &) { return default_value; } @@ -153,7 +153,7 @@ double EQ::JsonConfigFile::GetVariableDouble( return m_root[title][parameter].asDouble(); } } - catch (std::exception) { + catch (std::exception &) { return default_value; } diff --git a/common/linked_list.h b/common/linked_list.h index 246677db4..8ac4b51d8 100644 --- a/common/linked_list.h +++ b/common/linked_list.h @@ -278,12 +278,6 @@ void LinkedListIterator::Replace(const TYPE& new_data) template void LinkedListIterator::Reset() { - if (!(&list)) - { - current_element=0; - return; - } - if (dir == FORWARD) { current_element = list.first; diff --git a/common/misc.cpp b/common/misc.cpp index 792dfcbab..b9dc31d28 100644 --- a/common/misc.cpp +++ b/common/misc.cpp @@ -19,9 +19,6 @@ #include "types.h" #include -#define ENC(c) (((c) & 0x3f) + ' ') -#define DEC(c) (((c) - ' ') & 0x3f) - std::map DBFieldNames; #ifndef WIN32 @@ -333,64 +330,6 @@ void LoadItemDBFieldNames() { DBFieldNames[113]="unknown115"; // ? (end quote) } -void encode_length(unsigned long length, char *out) -{ -char buf[4]; - memcpy(buf,&length,sizeof(unsigned long)); - encode_chunk(buf,3,out); -} - -unsigned long encode(char *in, unsigned long length, char *out) -{ -unsigned long used=0,len=0; - while(used> 2); - *(out+1)=ENC((in[0] << 4)|(((len<2 ? 0 : in[1]) >> 4) & 0xF)); - *(out+2)=ENC(((len<2 ? 0 : in[1]) << 2)|(((len<3 ? 0 : in[2]) >> 6) & 0x3)); - *(out+3)=ENC((len<3 ? 0 : in[2])); -} - -void decode_chunk(char *in, char *out) -{ - *out = DEC(*in) << 2 | DEC(in[1]) >> 4; - *(out+1) = DEC(in[1]) << 4 | DEC(in[2]) >> 2; - *(out+2) = DEC(in[2]) << 6 | DEC(in[3]); -} - void dump_message_column(unsigned char *buffer, unsigned long length, std::string leader, FILE *to) { unsigned long i,j; diff --git a/common/misc.h b/common/misc.h index b33f2f32d..a099ab823 100644 --- a/common/misc.h +++ b/common/misc.h @@ -17,13 +17,6 @@ int Tokenize(std::string s, std::map & tokens, char delim='|'); void LoadItemDBFieldNames(); -void encode_length(unsigned long length, char *out); -unsigned long decode_length(char *in); -unsigned long encode(char *in, unsigned long length, char *out); -void decode(char *in, char *out); -void encode_chunk(char *in, int len, char *out); -void decode_chunk(char *in, char *out); - #ifndef WIN32 int print_stacktrace(); #endif diff --git a/common/net/console_server_connection.cpp b/common/net/console_server_connection.cpp index 795c73140..aab26d188 100644 --- a/common/net/console_server_connection.cpp +++ b/common/net/console_server_connection.cpp @@ -116,17 +116,21 @@ bool EQ::Net::ConsoleServerConnection::SendChannelMessage(const ServerChannelMes } switch (scm->chan_num) { - if (RuleB(Chat, ServerWideAuction)) { - case 4: { + case 4: { + if (RuleB(Chat, ServerWideAuction)) { QueueMessage(fmt::format("{0} auctions, '{1}'", scm->from, scm->message)); break; + } else { // I think we want default action in this case? + return false; } } - if (RuleB(Chat, ServerWideOOC)) { - case 5: { + case 5: { + if (RuleB(Chat, ServerWideOOC)) { QueueMessage(fmt::format("{0} says ooc, '{1}'", scm->from, scm->message)); break; + } else { // I think we want default action in this case? + return false; } } diff --git a/common/net/daybreak_connection.cpp b/common/net/daybreak_connection.cpp index b10203854..8448049f5 100644 --- a/common/net/daybreak_connection.cpp +++ b/common/net/daybreak_connection.cpp @@ -399,7 +399,7 @@ void EQ::Net::DaybreakConnection::Process() ProcessQueue(); } - catch (std::exception ex) { + catch (std::exception &ex) { if (m_owner->m_on_error_message) { m_owner->m_on_error_message(fmt::format("Error processing connection: {0}", ex.what())); } diff --git a/common/net/packet.h b/common/net/packet.h index 1ce173a10..4ff5f8510 100644 --- a/common/net/packet.h +++ b/common/net/packet.h @@ -89,9 +89,9 @@ namespace EQ { public: StaticPacket(void *data, size_t size) { m_data = data; m_data_length = size; m_max_data_length = size; } virtual ~StaticPacket() { } - StaticPacket(const StaticPacket &o) { m_data = o.m_data; m_data_length = o.m_data_length; } + StaticPacket(const StaticPacket &o) { m_data = o.m_data; m_data_length = o.m_data_length; m_max_data_length = o.m_max_data_length; } StaticPacket& operator=(const StaticPacket &o) { m_data = o.m_data; m_data_length = o.m_data_length; return *this; } - StaticPacket(StaticPacket &&o) { m_data = o.m_data; m_data_length = o.m_data_length; } + StaticPacket(StaticPacket &&o) noexcept { m_data = o.m_data; m_data_length = o.m_data_length; } virtual const void *Data() const { return m_data; } virtual void *Data() { return m_data; } @@ -112,7 +112,7 @@ namespace EQ { public: DynamicPacket() { } virtual ~DynamicPacket() { } - DynamicPacket(DynamicPacket &&o) { m_data = std::move(o.m_data); } + DynamicPacket(DynamicPacket &&o) noexcept { m_data = std::move(o.m_data); } DynamicPacket(const DynamicPacket &o) { m_data = o.m_data; } DynamicPacket& operator=(const DynamicPacket &o) { m_data = o.m_data; return *this; } @@ -127,4 +127,4 @@ namespace EQ { std::vector m_data; }; } -} \ No newline at end of file +} diff --git a/common/net/websocket_server.cpp b/common/net/websocket_server.cpp index de1a0cf99..b46fc7953 100644 --- a/common/net/websocket_server.cpp +++ b/common/net/websocket_server.cpp @@ -61,7 +61,7 @@ EQ::Net::WebsocketServer::WebsocketServer(const std::string &addr, int port) auto &connection = iter->second; connection->GetWebsocketConnection()->ping("keepalive"); } - catch (std::exception) { + catch (std::exception &) { iter->second->GetTCPConnection()->Disconnect(); } @@ -157,7 +157,7 @@ void EQ::Net::WebsocketServer::DispatchEvent(WebsocketSubscriptionEvent evt, Jso } } } - catch (std::exception) { + catch (std::exception &) { } } @@ -190,7 +190,7 @@ Json::Value EQ::Net::WebsocketServer::Login(WebsocketServerConnection *connectio return ret; } - catch (std::exception) { + catch (std::exception &) { throw WebsocketException("Unable to process login request"); } } @@ -212,7 +212,7 @@ Json::Value EQ::Net::WebsocketServer::Subscribe(WebsocketServerConnection *conne catch (WebsocketException &ex) { throw ex; } - catch (std::exception) { + catch (std::exception &) { throw WebsocketException("Unable to process unsubscribe request"); } } @@ -234,7 +234,7 @@ Json::Value EQ::Net::WebsocketServer::Unsubscribe(WebsocketServerConnection *con catch (WebsocketException &ex) { throw ex; } - catch (std::exception) { + catch (std::exception &) { throw WebsocketException("Unable to process unsubscribe request"); } } diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index 11bb99af7..36257771b 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -37,6 +37,7 @@ #include #include #include +#include namespace RoF2 @@ -3548,7 +3549,7 @@ namespace RoF2 { eq->items[i].Unknown18 = 0; if (i < 80) { - snprintf(eq->items[i].SerialNumber, sizeof(eq->items[i].SerialNumber), "%016d", emu->SerialNumber[i]); + snprintf(eq->items[i].SerialNumber, sizeof(eq->items[i].SerialNumber), "%016" PRId64, emu->SerialNumber[i]); eq->ItemCost[i] = emu->ItemCost[i]; } else { diff --git a/common/ptimer.cpp b/common/ptimer.cpp index 032ac5945..e6338bdf9 100644 --- a/common/ptimer.cpp +++ b/common/ptimer.cpp @@ -190,10 +190,6 @@ bool PersistentTimer::Clear(Database *db) { /* This function checks if the timer triggered */ bool PersistentTimer::Expired(Database *db, bool iReset) { - if (this == nullptr) { - LogError("Null timer during ->Check()!?\n"); - return(true); - } uint32 current_time = get_current_time(); if (current_time-start_time >= timer_time) { if (enabled && iReset) { diff --git a/common/ruletypes.h b/common/ruletypes.h index 0a388b4eb..5797d614a 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -157,6 +157,9 @@ RULE_BOOL(Character, OPClientUpdateVisualDebug, false, "Shows a pulse and forwar RULE_BOOL(Character, AllowCrossClassTrainers, false, "") RULE_BOOL(Character, PetsUseReagents, true, "Pets use reagent on spells") RULE_BOOL(Character, DismountWater, true, "Dismount horses when entering water") +RULE_BOOL(Character, UseNoJunkFishing, false, "Disregards junk items when fishing") +RULE_BOOL(Character, SoftDeletes, true, "When characters are deleted in character select, they are only soft deleted") +RULE_INT(Character, DefaultGuild, 0, "If not 0, new characters placed into the guild # indicated") RULE_CATEGORY_END() RULE_CATEGORY(Mercs) @@ -211,7 +214,7 @@ RULE_CATEGORY_END() RULE_CATEGORY(World) RULE_INT(World, ZoneAutobootTimeoutMS, 60000, "") RULE_INT(World, ClientKeepaliveTimeoutMS, 65000, "") -RULE_BOOL(World, UseBannedIPsTable, false, "Toggle whether or not to check incoming client connections against the Banned_IPs table. Set this value to false to disable this feature") +RULE_BOOL(World, UseBannedIPsTable, false, "Toggle whether or not to check incoming client connections against the banned_ips table. Set this value to false to disable this feature") RULE_BOOL(World, EnableTutorialButton, true, "") RULE_BOOL(World, EnableReturnHomeButton, true, "") RULE_INT(World, MaxLevelForTutorial, 10, "") @@ -292,6 +295,7 @@ RULE_BOOL(Pathing, Find, true, "Enable pathing for FindPerson requests from the RULE_BOOL(Pathing, Fear, true, "Enable pathing for fear") RULE_REAL(Pathing, NavmeshStepSize, 100.0f, "") RULE_REAL(Pathing, ShortMovementUpdateRange, 130.0f, "") +RULE_INT(Pathing, MaxNavmeshNodes, 4092, "Max navmesh nodes in a traversable path") RULE_CATEGORY_END() RULE_CATEGORY(Watermap) @@ -517,7 +521,7 @@ RULE_INT(NPC, NPCToNPCAggroTimerMin, 500, "") RULE_INT(NPC, NPCToNPCAggroTimerMax, 6000, "") RULE_BOOL(NPC, UseClassAsLastName, true, "Uses class archetype as LastName for npcs with none") RULE_BOOL(NPC, NewLevelScaling, true, "Better level scaling, use old if new formulas would break your server") -RULE_INT(NPC, NPCGatePercent, 5, "% at which the NPC Will attempt to gate at") +RULE_INT(NPC, NPCGatePercent, 20, "% at which the NPC Will attempt to gate at") RULE_BOOL(NPC, NPCGateNearBind, false, "Will NPC attempt to gate when near bind location?") RULE_INT(NPC, NPCGateDistanceBind, 75, "Distance from bind before NPC will attempt to gate") RULE_BOOL(NPC, NPCHealOnGate, true, "Will the NPC Heal on Gate") @@ -566,13 +570,13 @@ RULE_INT(Range, MobPositionUpdates, 600, "") RULE_INT(Range, ClientPositionUpdates, 300, "") RULE_INT(Range, ClientForceSpawnUpdateRange, 1000, "") RULE_INT(Range, CriticalDamage, 80, "") -RULE_INT(Range, ClientNPCScan, 300, "") +RULE_INT(Range, MobCloseScanDistance, 600, "") RULE_CATEGORY_END() #ifdef BOTS RULE_CATEGORY(Bots) -RULE_INT(Bots, AAExpansion, 8, "Bots get AAs through this expansion") +RULE_INT(Bots, BotExpansionSettings, 16383, "Sets the expansion settings for bot use. Defaults to all expansions enabled up to TSS") RULE_BOOL(Bots, AllowCamelCaseNames, false, "Allows the use of 'MyBot' type names") RULE_INT(Bots, CommandSpellRank, 1, "Filters bot command spells by rank (1, 2 and 3 are valid filters - any other number allows all ranks)") RULE_INT(Bots, CreationLimit, 150, "Number of bots that each account can create") @@ -599,6 +603,9 @@ RULE_INT(Bots, AllowedGenders, 0x3, "Bitmask of allowed bot genders") RULE_BOOL(Bots, AllowOwnerOptionAltCombat, true, "When option is enabled, bots will use an auto-/shared-aggro combat model") RULE_BOOL(Bots, AllowOwnerOptionAutoDefend, true, "When option is enabled, bots will defend their owner on enemy aggro") RULE_REAL(Bots, LeashDistance, 562500.0f, "Distance a bot is allowed to travel from leash owner before being pulled back (squared value)") +RULE_BOOL(Bots, AllowApplyPoisonCommand, true, "Allows the use of the bot command 'applypoison'") +RULE_BOOL(Bots, AllowApplyPotionCommand, true, "Allows the use of the bot command 'applypotion'") +RULE_BOOL(Bots, RestrictApplyPotionToRogue, true, "Restricts the bot command 'applypotion' to rogue-usable potions (i.e., poisons)") RULE_CATEGORY_END() #endif diff --git a/common/servertalk.h b/common/servertalk.h index 3aef636ca..aa3529756 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -262,9 +262,6 @@ public: } ServerPacket* Copy() { - if (this == 0) { - return 0; - } ServerPacket* ret = new ServerPacket(this->opcode, this->size); if (this->size) memcpy(ret->pBuffer, this->pBuffer, this->size); @@ -869,10 +866,12 @@ struct SpawnPlayerCorpse_Struct { struct ServerOP_Consent_Struct { char grantname[64]; char ownername[64]; + char zonename[32]; uint8 permission; uint32 zone_id; uint16 instance_id; - uint32 message_string_id; + uint8 consent_type; // 0 = normal, 1 = group, 2 = raid, 3 = guild + uint32 consent_id; }; struct ReloadTasks_Struct { diff --git a/common/spdat.cpp b/common/spdat.cpp index 3fd6d19e4..3f0ae6e41 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -87,8 +87,9 @@ bool IsTargetableAESpell(uint16 spell_id) { - if (IsValidSpell(spell_id) && spells[spell_id].targettype == ST_AETarget) + if (IsValidSpell(spell_id) && spells[spell_id].targettype == ST_AETarget) { return true; + } return false; } diff --git a/common/spdat.h b/common/spdat.h index 416f63561..c7c6027c6 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -607,7 +607,7 @@ typedef enum { #define SE_LimitSpellGroup 385 // implemented - Limits to spell group(ie type 3 reuse reduction augs that are class specific and thus all share s SG) #define SE_CastOnCurer 386 // implemented - Casts a spell on the person curing #define SE_CastOnCure 387 // implemented - Casts a spell on the cured person -//#define SE_SummonCorpseZone 388 // *not implemented - summons a corpse from any zone(nec AA) +#define SE_SummonCorpseZone 388 // implemented - summons a corpse from any zone(nec AA) #define SE_FcTimerRefresh 389 // implemented - Refresh spell icons //#define SE_FcTimerLockout 390 // *not implemented - Sets recast timers to specific value, focus limited. #define SE_LimitManaMax 391 // implemented diff --git a/common/string_util.cpp b/common/string_util.cpp index b48bee1b3..df3790def 100644 --- a/common/string_util.cpp +++ b/common/string_util.cpp @@ -222,7 +222,7 @@ bool StringIsNumber(const std::string &s) { auto r = stod(s); return true; } - catch (std::exception) { + catch (std::exception &) { return false; } } diff --git a/common/string_util.h b/common/string_util.h index 0727b98e2..037d6a2d8 100644 --- a/common/string_util.h +++ b/common/string_util.h @@ -27,6 +27,12 @@ #include #endif +#ifdef _WINDOWS +#include +#include +#include +#endif + #include "types.h" //std::string based @@ -38,6 +44,38 @@ const std::string StringFormat(const char* format, ...); const std::string vStringFormat(const char* format, va_list args); std::string implode(std::string glue, std::vector src); +/** + * @param str + * @param chars + * @return + */ +inline std::string <rim(std::string &str, const std::string &chars = "\t\n\v\f\r ") +{ + str.erase(0, str.find_first_not_of(chars)); + return str; +} + +/** + * @param str + * @param chars + * @return + */ +inline std::string &rtrim(std::string &str, const std::string &chars = "\t\n\v\f\r ") +{ + str.erase(str.find_last_not_of(chars) + 1); + return str; +} + +/** + * @param str + * @param chars + * @return + */ +inline std::string &trim(std::string &str, const std::string &chars = "\t\n\v\f\r ") +{ + return ltrim(rtrim(str, chars), chars); +} + template std::string implode(const std::string &glue, const std::pair &encapsulation, const std::vector &src) { diff --git a/common/version.h b/common/version.h index bf889cc21..ca37150ec 100644 --- a/common/version.h +++ b/common/version.h @@ -1,20 +1,22 @@ -/* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.net) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; version 2 of the License. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY except by those people which sell it, which - are required to give you total support for your newly bought product; - without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ +/** + * EQEmulator: Everquest Server Emulator + * Copyright (C) 2001-2019 EQEmulator Development Team (https://github.com/EQEmu/Server) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY except by those people which sell it, which + * are required to give you total support for your newly bought product; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ #ifndef _EQEMU_VERSION_H #define _EQEMU_VERSION_H @@ -22,22 +24,24 @@ #define LOGIN_VERSION "0.8.0" #define EQEMU_PROTOCOL_VERSION "0.3.10" -#define CURRENT_VERSION "1.1.3" - -/* - Everytime a Database SQL is added to Github, - increment CURRENT_BINARY_DATABASE_VERSION number and make sure you update the manifest - Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt -*/ +#define CURRENT_VERSION "2.0" -#define CURRENT_BINARY_DATABASE_VERSION 9144 +/** + * Every time a Database SQL is added to Github increment CURRENT_BINARY_DATABASE_VERSION + * number and make sure you update the manifest + * + * Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt + */ + +#define CURRENT_BINARY_DATABASE_VERSION 9148 #ifdef BOTS #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9026 #else #define CURRENT_BINARY_BOTS_DATABASE_VERSION 0 // must be 0 #endif + #define COMPILE_DATE __DATE__ #define COMPILE_TIME __TIME__ #ifndef WIN32 diff --git a/loginserver/encryption.cpp b/loginserver/encryption.cpp index ed98eae5e..7080f1996 100644 --- a/loginserver/encryption.cpp +++ b/loginserver/encryption.cpp @@ -21,6 +21,10 @@ #endif +/** + * @param mode + * @return + */ std::string GetEncryptionByModeId(uint32 mode) { switch (mode) { @@ -57,6 +61,13 @@ std::string GetEncryptionByModeId(uint32 mode) } } +/** + * @param buffer_in + * @param buffer_in_sz + * @param buffer_out + * @param enc + * @return + */ const char *eqcrypt_block(const char *buffer_in, size_t buffer_in_sz, char *buffer_out, bool enc) { #ifdef EQEMU_USE_MBEDTLS @@ -125,6 +136,10 @@ const char *eqcrypt_block(const char *buffer_in, size_t buffer_in_sz, char *buff return buffer_out; } +/** + * @param msg + * @return + */ std::string eqcrypt_md5(const std::string &msg) { std::string ret; @@ -159,6 +174,10 @@ std::string eqcrypt_md5(const std::string &msg) return ret; } +/** + * @param msg + * @return + */ std::string eqcrypt_sha1(const std::string &msg) { std::string ret; @@ -193,6 +212,10 @@ std::string eqcrypt_sha1(const std::string &msg) return ret; } +/** + * @param msg + * @return + */ std::string eqcrypt_sha512(const std::string &msg) { std::string ret; diff --git a/loginserver/login_server.h b/loginserver/login_server.h index e71cc7da0..c130c41d2 100644 --- a/loginserver/login_server.h +++ b/loginserver/login_server.h @@ -38,6 +38,7 @@ struct LoginServer { public: + LoginServer() : db(nullptr), server_manager(nullptr) { } diff --git a/loginserver/loginserver_command_handler.cpp b/loginserver/loginserver_command_handler.cpp index ae487d676..35599e299 100644 --- a/loginserver/loginserver_command_handler.cpp +++ b/loginserver/loginserver_command_handler.cpp @@ -112,6 +112,9 @@ namespace LoginserverCommandHandler { return; } + server.token_manager = new LoginserverWebserver::TokenManager; + server.token_manager->LoadApiTokens(); + for (auto &it : server.token_manager->loaded_api_tokens) { LogInfo( "token [{0}] can_write [{1}] can_read [{2}]", @@ -133,8 +136,8 @@ namespace LoginserverCommandHandler { description = "Creates Local Loginserver Account"; std::vector arguments = { - "--username", - "--password" + "{username}", + "{password}" }; std::vector options = { "--email=*" @@ -147,8 +150,8 @@ namespace LoginserverCommandHandler { EQEmuCommand::ValidateCmdInput(arguments, options, cmd, argc, argv); AccountManagement::CreateLoginServerAccount( - cmd("--username").str(), - cmd("--password").str(), + cmd(2).str(), + cmd(3).str(), cmd("--email").str() ); } @@ -164,9 +167,9 @@ namespace LoginserverCommandHandler { description = "Creates Loginserver World Administrator Account"; std::vector arguments = { - "--username", - "--password", - "--email" + "{username}", + "{password}", + "{email}" }; std::vector options = {}; @@ -177,9 +180,9 @@ namespace LoginserverCommandHandler { EQEmuCommand::ValidateCmdInput(arguments, options, cmd, argc, argv); AccountManagement::CreateLoginserverWorldAdminAccount( - cmd("--username").str(), - cmd("--password").str(), - cmd("--email").str() + cmd(2).str(), + cmd(3).str(), + cmd(4).str() ); } @@ -194,8 +197,8 @@ namespace LoginserverCommandHandler { description = "Check user login credentials"; std::vector arguments = { - "--username", - "--password" + "{username}", + "{password}" }; std::vector options = {}; @@ -206,11 +209,11 @@ namespace LoginserverCommandHandler { EQEmuCommand::ValidateCmdInput(arguments, options, cmd, argc, argv); auto res = AccountManagement::CheckLoginserverUserCredentials( - cmd("--username").str(), - cmd("--password").str() + cmd(2).str(), + cmd(3).str() ); - LogInfo("Credentials were {0}", res == true ? "accepted" : "not accepted"); + LogInfo("Credentials were {0}", res != 0 ? "accepted" : "not accepted"); } /** @@ -224,8 +227,8 @@ namespace LoginserverCommandHandler { description = "Change user login credentials"; std::vector arguments = { - "--username", - "--password" + "{username}", + "{password}" }; std::vector options = {}; @@ -236,8 +239,8 @@ namespace LoginserverCommandHandler { EQEmuCommand::ValidateCmdInput(arguments, options, cmd, argc, argv); AccountManagement::UpdateLoginserverUserCredentials( - cmd("--username").str(), - cmd("--password").str() + cmd(2).str(), + cmd(3).str() ); } @@ -252,8 +255,8 @@ namespace LoginserverCommandHandler { description = "Check user external login credentials"; std::vector arguments = { - "--username", - "--password" + "{username}", + "{password}" }; std::vector options = {}; @@ -264,8 +267,8 @@ namespace LoginserverCommandHandler { EQEmuCommand::ValidateCmdInput(arguments, options, cmd, argc, argv); auto res = AccountManagement::CheckExternalLoginserverUserCredentials( - cmd("--username").str(), - cmd("--password").str() + cmd(2).str(), + cmd(3).str() ); LogInfo("Credentials were {0}", res ? "accepted" : "not accepted"); @@ -282,8 +285,8 @@ namespace LoginserverCommandHandler { description = "Update world admin account password"; std::vector arguments = { - "--username", - "--password" + "{username}", + "{password}" }; std::vector options = {}; @@ -294,8 +297,8 @@ namespace LoginserverCommandHandler { EQEmuCommand::ValidateCmdInput(arguments, options, cmd, argc, argv); AccountManagement::UpdateLoginserverWorldAdminAccountPasswordByName( - cmd("--username").str(), - cmd("--password").str() + cmd(2).str(), + cmd(3).str() ); } } diff --git a/loginserver/main.cpp b/loginserver/main.cpp index 6941ad7e6..9e6006342 100644 --- a/loginserver/main.cpp +++ b/loginserver/main.cpp @@ -43,22 +43,28 @@ void CatchSignal(int sig_num) { } -int main(int argc, char **argv) +void LoadDatabaseConnection() { - RegisterExecutablePlatform(ExePlatformLogin); - set_exception_handler(); + LogInfo("MySQL Database Init"); - LogInfo("Logging System Init"); + server.db = new Database( + server.config.GetVariableString("database", "user", "root"), + server.config.GetVariableString("database", "password", ""), + server.config.GetVariableString("database", "host", "localhost"), + server.config.GetVariableString("database", "port", "3306"), + server.config.GetVariableString("database", "db", "peq") + ); - if (argc == 1) { - LogSys.LoadLogSettingsDefaults(); - } +} +void LoadServerConfig() +{ server.config = EQ::JsonConfigFile::Load("login.json"); LogInfo("Config System Init"); + /** - * options: logging + * Logging */ server.options.Trace(server.config.GetVariableBool("logging", "trace", false)); server.options.WorldTrace(server.config.GetVariableBool("logging", "world_trace", false)); @@ -66,7 +72,7 @@ int main(int argc, char **argv) server.options.DumpOutPackets(server.config.GetVariableBool("logging", "dump_packets_out", false)); /** - * options: worldservers + * Worldservers */ server.options.RejectDuplicateServers( server.config.GetVariableBool( @@ -77,7 +83,7 @@ int main(int argc, char **argv) server.options.AllowUnregistered(server.config.GetVariableBool("worldservers", "unregistered_allowed", true)); /** - * options: account + * Account */ server.options.AutoCreateAccounts(server.config.GetVariableBool("account", "auto_create_accounts", true)); server.options.AutoLinkAccounts(server.config.GetVariableBool("account", "auto_link_accounts", false)); @@ -92,6 +98,9 @@ int main(int argc, char **argv) ); #endif + /** + * Default Loginserver Name (Don't change) + */ server.options.DefaultLoginServerName( server.config.GetVariableString( "general", @@ -100,6 +109,10 @@ int main(int argc, char **argv) ) ); + /** + * Security + */ + #ifdef ENABLE_SECURITY server.options.EncryptionMode(server.config.GetVariableInt("security", "mode", 13)); #else @@ -115,19 +128,41 @@ int main(int argc, char **argv) true ) ); +} + +int main(int argc, char **argv) +{ + RegisterExecutablePlatform(ExePlatformLogin); + set_exception_handler(); + + LogInfo("Logging System Init"); + + if (argc == 1) { + LogSys.LoadLogSettingsDefaults(); + } + + /** + * Command handler + */ + if (argc > 1) { + LogSys.SilenceConsoleLogging(); + + LoadServerConfig(); + LoadDatabaseConnection(); + + LogSys.LoadLogSettingsDefaults(); + LogSys.log_settings[Logs::Debug].log_to_console = static_cast(Logs::General); + LogSys.log_settings[Logs::Debug].is_category_enabled = 1; + + LoginserverCommandHandler::CommandHandler(argc, argv); + } + + LoadServerConfig(); /** * mysql connect */ - LogInfo("MySQL Database Init"); - - server.db = new Database( - server.config.GetVariableString("database", "user", "root"), - server.config.GetVariableString("database", "password", ""), - server.config.GetVariableString("database", "host", "localhost"), - server.config.GetVariableString("database", "port", "3306"), - server.config.GetVariableString("database", "db", "peq") - ); + LoadDatabaseConnection(); if (argc == 1) { server.db->LoadLogSettings(LogSys.log_settings); @@ -195,14 +230,6 @@ int main(int argc, char **argv) LoginserverWebserver::RegisterRoutes(api); } - if (argc > 1) { - LogSys.LoadLogSettingsDefaults(); - LogSys.log_settings[Logs::Debug].log_to_console = static_cast(Logs::General); - LogSys.log_settings[Logs::Debug].is_category_enabled = 1; - - LoginserverCommandHandler::CommandHandler(argc, argv); - } - LogInfo("[Config] [Logging] IsTraceOn [{0}]", server.options.IsTraceOn()); LogInfo("[Config] [Logging] IsWorldTraceOn [{0}]", server.options.IsWorldTraceOn()); LogInfo("[Config] [Logging] IsDumpInPacketsOn [{0}]", server.options.IsDumpInPacketsOn()); diff --git a/utils/mods/legacy_combat.lua b/utils/mods/legacy_combat.lua index 9ab70cf4d..c04644a8d 100644 --- a/utils/mods/legacy_combat.lua +++ b/utils/mods/legacy_combat.lua @@ -1,110 +1,147 @@ -MonkACBonusWeight = RuleI.Get(Rule.MonkACBonusWeight); -NPCACFactor = RuleR.Get(Rule.NPCACFactor); -OldACSoftcapRules = RuleB.Get(Rule.OldACSoftcapRules); -ClothACSoftcap = RuleI.Get(Rule.ClothACSoftcap); -LeatherACSoftcap = RuleI.Get(Rule.LeatherACSoftcap); -MonkACSoftcap = RuleI.Get(Rule.MonkACSoftcap); -ChainACSoftcap = RuleI.Get(Rule.ChainACSoftcap); -PlateACSoftcap = RuleI.Get(Rule.PlateACSoftcap); +--[[ + * + * The purpose of this lua file is to backport combat formulas pre-overhaul + * https://github.com/EQEmu/Server/commit/9e824876ba5dac262b121c0e60d022bb2ecc45bd + * + * If your server has years and years of content built on old formulas, it may be appropriate to use this + * instead of taking on the massive task of rescaling tons of carefully tested and layered content + * +]] -AAMitigationACFactor = RuleR.Get(Rule.AAMitigationACFactor); -WarriorACSoftcapReturn = RuleR.Get(Rule.WarriorACSoftcapReturn); -KnightACSoftcapReturn = RuleR.Get(Rule.KnightACSoftcapReturn); -LowPlateChainACSoftcapReturn = RuleR.Get(Rule.LowPlateChainACSoftcapReturn); +MonkACBonusWeight = RuleI.Get(Rule.MonkACBonusWeight); +NPCACFactor = RuleR.Get(Rule.NPCACFactor); +OldACSoftcapRules = RuleB.Get(Rule.OldACSoftcapRules); +ClothACSoftcap = RuleI.Get(Rule.ClothACSoftcap); +LeatherACSoftcap = RuleI.Get(Rule.LeatherACSoftcap); +MonkACSoftcap = RuleI.Get(Rule.MonkACSoftcap); +ChainACSoftcap = RuleI.Get(Rule.ChainACSoftcap); +PlateACSoftcap = RuleI.Get(Rule.PlateACSoftcap); +AAMitigationACFactor = RuleR.Get(Rule.AAMitigationACFactor); +WarriorACSoftcapReturn = RuleR.Get(Rule.WarriorACSoftcapReturn); +KnightACSoftcapReturn = RuleR.Get(Rule.KnightACSoftcapReturn); +LowPlateChainACSoftcapReturn = RuleR.Get(Rule.LowPlateChainACSoftcapReturn); LowChainLeatherACSoftcapReturn = RuleR.Get(Rule.LowChainLeatherACSoftcapReturn); -CasterACSoftcapReturn = RuleR.Get(Rule.CasterACSoftcapReturn); -MiscACSoftcapReturn = RuleR.Get(Rule.MiscACSoftcapReturn); -WarACSoftcapReturn = RuleR.Get(Rule.WarACSoftcapReturn); -ClrRngMnkBrdACSoftcapReturn = RuleR.Get(Rule.ClrRngMnkBrdACSoftcapReturn); -PalShdACSoftcapReturn = RuleR.Get(Rule.PalShdACSoftcapReturn); +CasterACSoftcapReturn = RuleR.Get(Rule.CasterACSoftcapReturn); +MiscACSoftcapReturn = RuleR.Get(Rule.MiscACSoftcapReturn); +WarACSoftcapReturn = RuleR.Get(Rule.WarACSoftcapReturn); +ClrRngMnkBrdACSoftcapReturn = RuleR.Get(Rule.ClrRngMnkBrdACSoftcapReturn); +PalShdACSoftcapReturn = RuleR.Get(Rule.PalShdACSoftcapReturn); DruNecWizEncMagACSoftcapReturn = RuleR.Get(Rule.DruNecWizEncMagACSoftcapReturn); -RogShmBstBerACSoftcapReturn = RuleR.Get(Rule.RogShmBstBerACSoftcapReturn); -SoftcapFactor = RuleR.Get(Rule.SoftcapFactor); -ACthac0Factor = RuleR.Get(Rule.ACthac0Factor); -ACthac20Factor = RuleR.Get(Rule.ACthac20Factor); +RogShmBstBerACSoftcapReturn = RuleR.Get(Rule.RogShmBstBerACSoftcapReturn); +SoftcapFactor = RuleR.Get(Rule.SoftcapFactor); +ACthac0Factor = RuleR.Get(Rule.ACthac0Factor); +ACthac20Factor = RuleR.Get(Rule.ACthac20Factor); +BaseHitChance = RuleR.Get(Rule.BaseHitChance); +NPCBonusHitChance = RuleR.Get(Rule.NPCBonusHitChance); +HitFalloffMinor = RuleR.Get(Rule.HitFalloffMinor); +HitFalloffModerate = RuleR.Get(Rule.HitFalloffModerate); +HitFalloffMajor = RuleR.Get(Rule.HitFalloffMajor); +HitBonusPerLevel = RuleR.Get(Rule.HitBonusPerLevel); +AgiHitFactor = RuleR.Get(Rule.AgiHitFactor); +WeaponSkillFalloff = RuleR.Get(Rule.WeaponSkillFalloff); +ArcheryHitPenalty = RuleR.Get(Rule.ArcheryHitPenalty); +UseOldDamageIntervalRules = RuleB.Get(Rule.UseOldDamageIntervalRules); +CriticalMessageRange = RuleI.Get(Rule.CriticalDamage); -MeleeBaseCritChance = 0.0; -ClientBaseCritChance = 0.0; -BerserkBaseCritChance = 6.0; -WarBerBaseCritChance = 3.0; -RogueCritThrowingChance = 25; -RogueDeadlyStrikeChance = 80; -RogueDeadlyStrikeMod = 2; +--[[ + * + * These are rule values that were removed in the latest combat overhaul, if you are coming from + * older source code, you will need to reference what your rule values were for these variables + * to make sure that things line up correctly + * +]] -BaseHitChance = RuleR.Get(Rule.BaseHitChance); -NPCBonusHitChance = RuleR.Get(Rule.NPCBonusHitChance); -HitFalloffMinor = RuleR.Get(Rule.HitFalloffMinor); -HitFalloffModerate = RuleR.Get(Rule.HitFalloffModerate); -HitFalloffMajor = RuleR.Get(Rule.HitFalloffMajor); -HitBonusPerLevel = RuleR.Get(Rule.HitBonusPerLevel); -AgiHitFactor = RuleR.Get(Rule.AgiHitFactor); -WeaponSkillFalloff = RuleR.Get(Rule.WeaponSkillFalloff); -ArcheryHitPenalty = RuleR.Get(Rule.ArcheryHitPenalty); -UseOldDamageIntervalRules = RuleB.Get(Rule.UseOldDamageIntervalRules); - -CriticalMessageRange = RuleI.Get(Rule.CriticalDamage); +MeleeBaseCritChance = 0.0; +ClientBaseCritChance = 0.0; +BerserkBaseCritChance = 6.0; +WarBerBaseCritChance = 3.0; +RogueCritThrowingChance = 25; +RogueDeadlyStrikeChance = 80; +RogueDeadlyStrikeMod = 2; +-- Source Function: Mob::MeleeMitigation() +-- Partial: Rest happens in DoMeleeMitigation function MeleeMitigation(e) e.IgnoreDefault = true; - + if e.hit.damage_done < 0 or e.hit.base_damage == 0 then return e; end - + + eq.log_combat( + string.format("[%s] [ClientAttack] Damage Table [%i] WeaponDMG [%i]", + e.self:GetCleanName(), + GetDamageTable(e.other, e.hit.skill), + e.hit.base_damage + ) + ); + e.hit.damage_done = 2 * e.hit.base_damage * GetDamageTable(e.other, e.hit.skill) / 100; - e.hit = DoMeleeMitigation(e.self, e.other, e.hit, e.opts); + + eq.log_combat( + string.format("[%s] [ClientAttack] DamageDone [%i] BaseDamage [%i] HitSkill [%i]", + e.self:GetCleanName(), + e.hit.damage_done, + e.hit.base_damage, + e.hit.skill + ) + ); + + e.hit = DoMeleeMitigation(e.self, e.other, e.hit, e.opts); + return e; end +-- Source Function: Mob::CheckHitChance() function CheckHitChance(e) - e.IgnoreDefault = true; - - local other = e.other; - local attacker = other; - local self = e.self; - local defender = self; + e.IgnoreDefault = true; + + local other = e.other; + local attacker = other; + local self = e.self; + local defender = self; local chancetohit = BaseHitChance; - local chance_mod = 0; - - if(e.opts ~= nil) then + local chance_mod = 0; + + if (e.opts ~= nil) then chance_mod = e.opts.hit_chance; end - - if(attacker:IsNPC() and not attacker:IsPet()) then + + if (attacker:IsNPC() and not attacker:IsPet()) then chancetohit = chancetohit + NPCBonusHitChance; end local pvpmode = false; - if(self:IsClient() and other:IsClient()) then + if (self:IsClient() and other:IsClient()) then pvpmode = true; end - + if (chance_mod >= 10000) then e.ReturnValue = true; return e; end - + local avoidanceBonus = 0; - local hitBonus = 0; - + local hitBonus = 0; + local attacker_level = attacker:GetLevel(); - if(attacker_level < 1) then + if (attacker_level < 1) then attacker_level = 1; end - + local defender_level = defender:GetLevel(); - if(defender_level < 1) then + if (defender_level < 1) then defender_level = 1; end local level_difference = attacker_level - defender_level; - local range = defender_level; - range = ((range / 4) + 3); - - if(level_difference < 0) then - if(level_difference >= -range) then + local range = defender_level; + range = ((range / 4) + 3); + + if (level_difference < 0) then + if (level_difference >= -range) then chancetohit = chancetohit + ((level_difference / range) * HitFalloffMinor); - elseif (level_difference >= -(range+3.0)) then + elseif (level_difference >= -(range + 3.0)) then chancetohit = chancetohit - HitFalloffMinor; chancetohit = chancetohit + (((level_difference + range) / 3.0) * HitFalloffModerate); else @@ -116,85 +153,95 @@ function CheckHitChance(e) end chancetohit = chancetohit - (defender:GetAGI() * AgiHitFactor); - - if(attacker:IsClient()) then + + if (attacker:IsClient()) then chancetohit = chancetohit - (WeaponSkillFalloff * (attacker:CastToClient():MaxSkill(e.hit.skill) - attacker:GetSkill(e.hit.skill))); end - - if(defender:IsClient()) then + + if (defender:IsClient()) then chancetohit = chancetohit + (WeaponSkillFalloff * (defender:CastToClient():MaxSkill(Skill.Defense) - defender:GetSkill(Skill.Defense))); end - + local attacker_spellbonuses = attacker:GetSpellBonuses(); - local attacker_itembonuses = attacker:GetItemBonuses(); - local attacker_aabonuses = attacker:GetAABonuses(); + local attacker_itembonuses = attacker:GetItemBonuses(); + local attacker_aabonuses = attacker:GetAABonuses(); local defender_spellbonuses = defender:GetSpellBonuses(); - local defender_itembonuses = defender:GetItemBonuses(); - local defender_aabonuses = defender:GetAABonuses(); - - if(attacker_spellbonuses:MeleeSkillCheckSkill() == e.hit.skill or attacker_spellbonuses:MeleeSkillCheckSkill() == 255) then + local defender_itembonuses = defender:GetItemBonuses(); + local defender_aabonuses = defender:GetAABonuses(); + + if (attacker_spellbonuses:MeleeSkillCheckSkill() == e.hit.skill or attacker_spellbonuses:MeleeSkillCheckSkill() == 255) then chancetohit = chancetohit + attacker_spellbonuses:MeleeSkillCheck(); end - - if(attacker_itembonuses:MeleeSkillCheckSkill() == e.hit.skill or attacker_itembonuses:MeleeSkillCheckSkill() == 255) then - chancetohit = chancetohit + attacker_itembonuses:MeleeSkillCheck(); + + if (attacker_itembonuses:MeleeSkillCheckSkill() == e.hit.skill or attacker_itembonuses:MeleeSkillCheckSkill() == 255) then + chancetohit = chancetohit + attacker_itembonuses:MeleeSkillCheck(); end avoidanceBonus = defender_spellbonuses:AvoidMeleeChanceEffect() + - defender_itembonuses:AvoidMeleeChanceEffect() + - defender_aabonuses:AvoidMeleeChanceEffect() + - (defender_itembonuses:AvoidMeleeChance() / 10.0); - - local owner = Mob(); + defender_itembonuses:AvoidMeleeChanceEffect() + + defender_aabonuses:AvoidMeleeChanceEffect() + + (defender_itembonuses:AvoidMeleeChance() / 10.0); + + local owner = Mob(); if (defender:IsPet()) then owner = defender:GetOwner(); elseif (defender:IsNPC() and defender:CastToNPC():GetSwarmOwner()) then local entity_list = eq.get_entity_list(); - owner = entity_list:GetMobID(defender:CastToNPC():GetSwarmOwner()); + owner = entity_list:GetMobID(defender:CastToNPC():GetSwarmOwner()); end - + if (owner.valid) then avoidanceBonus = avoidanceBonus + owner:GetAABonuses():PetAvoidance() + owner:GetSpellBonuses():PetAvoidance() + owner:GetItemBonuses():PetAvoidance(); end - if(defender:IsNPC()) then + if (defender:IsNPC()) then avoidanceBonus = avoidanceBonus + (defender:CastToNPC():GetAvoidanceRating() / 10.0); end hitBonus = hitBonus + attacker_itembonuses:HitChanceEffect(e.hit.skill) + - attacker_spellbonuses:HitChanceEffect(e.hit.skill) + - attacker_aabonuses:HitChanceEffect(e.hit.skill) + - attacker_itembonuses:HitChanceEffect(Skill.HIGHEST_SKILL + 1) + - attacker_spellbonuses:HitChanceEffect(Skill.HIGHEST_SKILL + 1) + - attacker_aabonuses:HitChanceEffect(Skill.HIGHEST_SKILL + 1); - + attacker_spellbonuses:HitChanceEffect(e.hit.skill) + + attacker_aabonuses:HitChanceEffect(e.hit.skill) + + attacker_itembonuses:HitChanceEffect(Skill.HIGHEST_SKILL + 1) + + attacker_spellbonuses:HitChanceEffect(Skill.HIGHEST_SKILL + 1) + + attacker_aabonuses:HitChanceEffect(Skill.HIGHEST_SKILL + 1); + hitBonus = hitBonus + (attacker_itembonuses:Accuracy(Skill.HIGHEST_SKILL + 1) + - attacker_spellbonuses:Accuracy(Skill.HIGHEST_SKILL + 1) + - attacker_aabonuses:Accuracy(Skill.HIGHEST_SKILL + 1) + - attacker_aabonuses:Accuracy(e.hit.skill) + - attacker_itembonuses:HitChance()) / 15.0; - + attacker_spellbonuses:Accuracy(Skill.HIGHEST_SKILL + 1) + + attacker_aabonuses:Accuracy(Skill.HIGHEST_SKILL + 1) + + attacker_aabonuses:Accuracy(e.hit.skill) + + attacker_itembonuses:HitChance()) / 15.0; + hitBonus = hitBonus + chance_mod; - - if(attacker:IsNPC()) then + + if (attacker:IsNPC()) then hitBonus = hitBonus + (attacker:CastToNPC():GetAccuracyRating() / 10.0); end - + if (e.hit.skill == Skill.Archery) then hitBonus = hitBonus - (hitBonus * ArcheryHitPenalty); end chancetohit = chancetohit + ((chancetohit * (hitBonus - avoidanceBonus)) / 100.0); - if(chancetohit > 1000 or chancetohit < -1000) then - elseif(chancetohit > 95) then + if (chancetohit > 1000 or chancetohit < -1000) then + elseif (chancetohit > 95) then chancetohit = 95; - elseif(chancetohit < 5) then + elseif (chancetohit < 5) then chancetohit = 5; end local tohit_roll = Random.Real(0, 100); - if(tohit_roll <= chancetohit) then + + eq.log_combat( + string.format("[%s] [Mob::CheckHitChance] Chance [%i] ToHitRoll [%i] Hit? [%s]", + e.self:GetCleanName(), + chancetohit, + tohit_roll, + (tohit_roll <= chancetohit) and "true" or "false" + ) + ); + + if (tohit_roll <= chancetohit) then e.ReturnValue = true; else e.ReturnValue = false; @@ -203,32 +250,33 @@ function CheckHitChance(e) return e; end +-- Source Function: Mob::TryCriticalHit() function TryCriticalHit(e) e.IgnoreDefault = true; - - local self = e.self; - local defender = e.other; - - if(e.hit.damage_done < 1 or defender.null) then + + local self = e.self; + local defender = e.other; + + if (e.hit.damage_done < 1 or defender.null) then return e; end - + if ((self:IsPet() and self:GetOwner():IsClient()) or (self:IsNPC() and self:CastToNPC():GetSwarmOwner() ~= 0)) then e.hit = TryPetCriticalHit(self, defender, e.hit); return e; end - + if (self:IsPet() and self:GetOwner().valid and self:GetOwner():IsBot()) then e.hit = TryPetCriticalHit(self, defender, e.hit); return e; end - local critChance = 0.0; + local critChance = 0.0; local IsBerskerSPA = false; - local aabonuses = self:GetAABonuses(); - local itembonuses = self:GetItemBonuses(); + local aabonuses = self:GetAABonuses(); + local itembonuses = self:GetItemBonuses(); local spellbonuses = self:GetSpellBonuses(); - local entity_list = eq.get_entity_list(); + local entity_list = eq.get_entity_list(); if (defender:GetBodyType() == BT.Undead or defender:GetBodyType() == BT.SummonedUndead or defender:GetBodyType() == BT.Vampire) then local SlayRateBonus = aabonuses:SlayUndead(0) + itembonuses:SlayUndead(0) + spellbonuses:SlayUndead(0); @@ -236,28 +284,28 @@ function TryCriticalHit(e) local slayChance = SlayRateBonus / 10000.0; if (Random.RollReal(slayChance)) then local SlayDmgBonus = aabonuses:SlayUndead(1) + itembonuses:SlayUndead(1) + spellbonuses:SlayUndead(1); - e.hit.damage_done = (e.hit.damage_done * SlayDmgBonus * 2.25) / 100; - + e.hit.damage_done = (e.hit.damage_done * SlayDmgBonus * 2.25) / 100; + if (self:GetGender() == 1) then entity_list:FilteredMessageClose(self, false, CriticalMessageRange, MT.CritMelee, Filter.MeleeCrits, string.format('%s\'s holy blade cleanses her target! (%d)', self:GetCleanName(), e.hit.damage_done)); else entity_list:FilteredMessageClose(self, false, CriticalMessageRange, MT.CritMelee, Filter.MeleeCrits, string.format('%s\'s holy blade cleanses his target! (%d)', self:GetCleanName(), e.hit.damage_done)); end - + return e; end end end - + critChance = critChance + MeleeBaseCritChance; - + if (self:IsClient()) then - critChance = critChance + ClientBaseCritChance; - + critChance = critChance + ClientBaseCritChance; + if (spellbonuses:BerserkSPA() or itembonuses:BerserkSPA() or aabonuses:BerserkSPA()) then IsBerskerSPA = true; end - + if (((self:GetClass() == Class.WARRIOR or self:GetClass() == Class.BERSERKER) and self:GetLevel() >= 12) or IsBerskerSPA) then if (self:IsBerserk() or IsBerskerSPA) then critChance = critChance + BerserkBaseCritChance; @@ -266,21 +314,21 @@ function TryCriticalHit(e) end end end - + local deadlyChance = 0; - local deadlyMod = 0; + local deadlyMod = 0; if (e.hit.skill == Skill.Archery and self:GetClass() == Class.RANGER and self:GetSkill(Skill.Archery) >= 65) then critChance = critChance + 6; end if (e.hit.skill == Skill.Throwing and self:GetClass() == Class.ROGUE and self:GetSkill(Skill.Throwing) >= 65) then - critChance = critChance + RogueCritThrowingChance; + critChance = critChance + RogueCritThrowingChance; deadlyChance = RogueDeadlyStrikeChance; - deadlyMod = RogueDeadlyStrikeMod; + deadlyMod = RogueDeadlyStrikeMod; end - + local CritChanceBonus = GetCriticalChanceBonus(self, e.hit.skill); - + if (CritChanceBonus > 0 or critChance > 0) then if (self:GetDEX() <= 255) then critChance = critChance + (self:GetDEX() / 125.0); @@ -289,43 +337,63 @@ function TryCriticalHit(e) end critChance = critChance + (critChance * CritChanceBonus / 100.0); end - - if(opts ~= nil) then + + if (opts ~= nil) then critChance = critChance * opts.crit_percent; critChance = critChance + opts.crit_flat; end - - if(critChance > 0) then - + + eq.log_combat( + string.format("[%s] [Mob::TryCriticalHit] CritChance [%i] CritChanceBonus [%i] Dex [%i] Post-Dex-Block", + e.self:GetCleanName(), + critChance, + CritChanceBonus, + e.self:GetDEX() + ) + ); + + if (critChance > 0) then + critChance = critChance / 100; - - if(Random.RollReal(critChance)) then - local critMod = 200; - local crip_success = false; + + if (Random.RollReal(critChance)) then + local critMod = 200; + local crip_success = false; local CripplingBlowChance = GetCrippBlowChance(self); - + if (CripplingBlowChance > 0 or (self:IsBerserk() or IsBerskerSPA)) then if (not self:IsBerserk() and not IsBerskerSPA) then critChance = critChance * (CripplingBlowChance / 100.0); end - + if ((self:IsBerserk() or IsBerskerSPA) or Random.RollReal(critChance)) then - critMod = 400; + critMod = 400; crip_success = true; end end - + critMod = critMod + GetCritDmgMod(self, e.hit.skill) * 2; - e.hit.damage_done = e.hit.damage_done * critMod / 100; - + + eq.log_combat( + string.format("[%s] [Mob::TryCriticalHit] CritChance [%i] CritMod [%i] GetCritDmgMod [%i] CripSuccess [%s]", + e.self:GetCleanName(), + critChance, + critMod, + GetCritDmgMod(self, e.hit.skill), + (crip_success) and "true" or "false" + ) + ); + + e.hit.damage_done = e.hit.damage_done * critMod / 100; + local deadlySuccess = false; if (deadlyChance > 0 and Random.RollReal(deadlyChance / 100.0)) then if (self:BehindMob(defender, self:GetX(), self:GetY())) then e.hit.damage_done = e.hit.damage_done * deadlyMod; - deadlySuccess = true; + deadlySuccess = true; end end - + if (crip_success) then entity_list:FilteredMessageClose(self, false, CriticalMessageRange, MT.CritMelee, Filter.MeleeCrits, string.format('%s lands a Crippling Blow! (%d)', self:GetCleanName(), e.hit.damage_done)); if (defender:GetLevel() <= 55 and not defender:GetSpecialAbility(SpecialAbility.unstunable)) then @@ -339,24 +407,25 @@ function TryCriticalHit(e) end end end - + return e; end +-- Source Function: Mob::TryPetCriticalHit() function TryPetCriticalHit(self, defender, hit) - if(hit.damage_done < 1) then + if (hit.damage_done < 1) then return hit; end - local owner = Mob(); + local owner = Mob(); local critChance = MeleeBaseCritChance; - local critMod = 163; + local critMod = 163; if (self:IsPet()) then owner = self:GetOwner(); elseif (self:IsNPC() and self:CastToNPC():GetSwarmOwner()) then local entity_list = eq.get_entity_list(); - owner = entity_list:GetMobID(self:CastToNPC():GetSwarmOwner()); + owner = entity_list:GetMobID(self:CastToNPC():GetSwarmOwner()); else return hit; end @@ -364,128 +433,198 @@ function TryPetCriticalHit(self, defender, hit) if (owner.null) then return hit; end - - local CritPetChance = owner:GetAABonuses():PetCriticalHit() + owner:GetItemBonuses():PetCriticalHit() + owner:GetSpellBonuses():PetCriticalHit(); + + local CritPetChance = owner:GetAABonuses():PetCriticalHit() + owner:GetItemBonuses():PetCriticalHit() + owner:GetSpellBonuses():PetCriticalHit(); local CritChanceBonus = GetCriticalChanceBonus(self, hit.skill); + eq.log_combat( + string.format("[%s] [Mob::TryPetCriticalHit] CritPetChance [%i] CritChanceBonus [%i] | Bonuses AA [%i] Item [%i] Spell [%i]", + self:GetCleanName(), + CritPetChance, + CritChanceBonus, + owner:GetAABonuses():PetCriticalHit(), + owner:GetItemBonuses():PetCriticalHit(), + owner:GetSpellBonuses():PetCriticalHit() + ) + ); + if (CritPetChance or critChance) then critChance = critChance + CritPetChance; critChance = critChance + (critChance * CritChanceBonus / 100.0); + + eq.log_combat( + string.format("[%s] [Mob::TryPetCriticalHit] critChance [%i] PostCalcs", + self:GetCleanName(), + critChance + ) + ); + end - if(critChance > 0) then + if (critChance > 0) then critChance = critChance / 100; - if(Random.RollReal(critChance)) then + if (Random.RollReal(critChance)) then local entity_list = eq.get_entity_list(); - critMod = critMod + GetCritDmgMod(self, hit.skill) * 2; - hit.damage_done = (hit.damage_done * critMod) / 100; - entity_list:FilteredMessageClose(this, false, CriticalMessageRange, - MT.CritMelee, Filter.MeleeCrits, string.format('%s scores a critical hit! (%d)', - self:GetCleanName(), e.hit.damage_done)); + critMod = critMod + GetCritDmgMod(self, hit.skill) * 2; + hit.damage_done = (hit.damage_done * critMod) / 100; + + eq.log_combat( + string.format("[%s] [Mob::TryPetCriticalHit] critMod [%i] DmgMod [%i] DamageDone [%i]", + self:GetCleanName(), + critMod, + GetCritDmgMod(self, hit.skill), + hit.damage_done + ) + ); + + entity_list:FilteredMessageClose( + this, + false, + CriticalMessageRange, + MT.CritMelee, + Filter.MeleeCrits, + string.format('%s scores a critical hit! (%d)', self:GetCleanName(), e.hit.damage_done) + ); end end - + return hit; end +-- Source Function: Mob::GetCriticalChanceBonus() function GetCriticalChanceBonus(self, skill) - + local critical_chance = 0; - - local aabonuses = self:GetAABonuses(); - local itembonuses = self:GetItemBonuses(); - local spellbonuses = self:GetSpellBonuses(); - - critical_chance = critical_chance + itembonuses:CriticalHitChance(Skill.HIGHEST_SKILL + 1); - critical_chance = critical_chance + spellbonuses:CriticalHitChance(Skill.HIGHEST_SKILL + 1); - critical_chance = critical_chance + aabonuses:CriticalHitChance(Skill.HIGHEST_SKILL + 1); - critical_chance = critical_chance + itembonuses:CriticalHitChance(skill); - critical_chance = critical_chance + spellbonuses:CriticalHitChance(skill); - critical_chance = critical_chance + aabonuses:CriticalHitChance(skill); - + + local aabonuses = self:GetAABonuses(); + local itembonuses = self:GetItemBonuses(); + local spellbonuses = self:GetSpellBonuses(); + + critical_chance = critical_chance + itembonuses:CriticalHitChance(Skill.HIGHEST_SKILL + 1); + critical_chance = critical_chance + spellbonuses:CriticalHitChance(Skill.HIGHEST_SKILL + 1); + critical_chance = critical_chance + aabonuses:CriticalHitChance(Skill.HIGHEST_SKILL + 1); + critical_chance = critical_chance + itembonuses:CriticalHitChance(skill); + critical_chance = critical_chance + spellbonuses:CriticalHitChance(skill); + critical_chance = critical_chance + aabonuses:CriticalHitChance(skill); + + eq.log_combat( + string.format("[%s] [Mob::GetCriticalChanceBonus] Bonuses | Item [%i] Spell [%i] AA [%i] | 2nd Item [%i] Spell [%i] AA [%i] Final Chance [%i]", + self:GetCleanName(), + itembonuses:CriticalHitChance(Skill.HIGHEST_SKILL + 1), + spellbonuses:CriticalHitChance(Skill.HIGHEST_SKILL + 1), + aabonuses:CriticalHitChance(Skill.HIGHEST_SKILL + 1), + itembonuses:CriticalHitChance(skill), + spellbonuses:CriticalHitChance(skill), + aabonuses:CriticalHitChance(skill), + critical_chance + ) + ); + return critical_chance; end +-- Source Function: Mob::GetCritDmgMob() function GetCritDmgMod(self, skill) - local critDmg_mod = 0; - - local aabonuses = self:GetAABonuses(); - local itembonuses = self:GetItemBonuses(); + local critDmg_mod = 0; + + local aabonuses = self:GetAABonuses(); + local itembonuses = self:GetItemBonuses(); local spellbonuses = self:GetSpellBonuses(); - - critDmg_mod = critDmg_mod + itembonuses:CritDmgMod(Skill.HIGHEST_SKILL + 1); - critDmg_mod = critDmg_mod + spellbonuses:CritDmgMod(Skill.HIGHEST_SKILL + 1); - critDmg_mod = critDmg_mod + aabonuses:CritDmgMod(Skill.HIGHEST_SKILL + 1); - critDmg_mod = critDmg_mod + itembonuses:CritDmgMod(skill); - critDmg_mod = critDmg_mod + spellbonuses:CritDmgMod(skill); - critDmg_mod = critDmg_mod + aabonuses:CritDmgMod(skill); - + critDmg_mod = critDmg_mod + itembonuses:CritDmgMod(Skill.HIGHEST_SKILL + 1); + critDmg_mod = critDmg_mod + spellbonuses:CritDmgMod(Skill.HIGHEST_SKILL + 1); + critDmg_mod = critDmg_mod + aabonuses:CritDmgMod(Skill.HIGHEST_SKILL + 1); + critDmg_mod = critDmg_mod + itembonuses:CritDmgMod(skill); + critDmg_mod = critDmg_mod + spellbonuses:CritDmgMod(skill); + critDmg_mod = critDmg_mod + aabonuses:CritDmgMod(skill); + + if (critDmg_mod < -100) then + critDmg_mod = -100; + end + return critDmg_mod; end +-- Source Function: Mob::GetCrippBlowChance() function GetCrippBlowChance(self) - local aabonuses = self:GetAABonuses(); - local itembonuses = self:GetItemBonuses(); + local aabonuses = self:GetAABonuses(); + local itembonuses = self:GetItemBonuses(); local spellbonuses = self:GetSpellBonuses(); - local crip_chance = itembonuses:CrippBlowChance() + spellbonuses:CrippBlowChance() + aabonuses:CrippBlowChance(); + local crip_chance = itembonuses:CrippBlowChance() + spellbonuses:CrippBlowChance() + aabonuses:CrippBlowChance(); - if(crip_chance < 0) then + if (crip_chance < 0) then crip_chance = 0; end return crip_chance; end +-- Source Function: Mob::MeleeMitigation() function DoMeleeMitigation(defender, attacker, hit, opts) if hit.damage_done <= 0 then return hit; end - - local aabonuses = defender:GetAABonuses(); - local itembonuses = defender:GetItemBonuses(); - local spellbonuses = defender:GetSpellBonuses(); - - local aa_mit = (aabonuses:CombatStability() + itembonuses:CombatStability() + spellbonuses:CombatStability()) / 100.0; - local softcap = (defender:GetSkill(15) + defender:GetLevel()) * SoftcapFactor * (1.0 + aa_mit); + + local aabonuses = defender:GetAABonuses(); + local itembonuses = defender:GetItemBonuses(); + local spellbonuses = defender:GetSpellBonuses(); + + local aa_mit = (aabonuses:CombatStability() + itembonuses:CombatStability() + spellbonuses:CombatStability()) / 100.0; + local softcap = (defender:GetSkill(15) + defender:GetLevel()) * SoftcapFactor * (1.0 + aa_mit); local mitigation_rating = 0.0; - local attack_rating = 0.0; - local shield_ac = 0; - local armor = 0; - local weight = 0.0; - local monkweight = MonkACBonusWeight; - + local attack_rating = 0.0; + local shield_ac = 0; + local armor = 0; + local weight = 0.0; + local monkweight = MonkACBonusWeight; + + eq.log_combat( + string.format("[%s] [Mob::MeleeMitigation] Stability Bonuses | AA [%i] Item [%i] Spell [%i]", + defender:GetCleanName(), + aabonuses:CombatStability(), + itembonuses:CombatStability(), + spellbonuses:CombatStability() + ) + ); + + eq.log_combat( + string.format("[%s] [Mob::MeleeMitigation] Soft Cap [%i]", + defender:GetCleanName(), + softcap + ) + ); + if defender:IsClient() then armor, shield_ac = GetRawACNoShield(defender); - weight = defender:CastToClient():CalcCurrentWeight() / 10; + weight = defender:CastToClient():CalcCurrentWeight() / 10; elseif defender:IsNPC() then - armor = defender:CastToNPC():GetRawAC(); + armor = defender:CastToNPC():GetRawAC(); local PetACBonus = 0; - + if not defender:IsPet() then armor = armor / NPCACFactor; end - + local owner = Mob(); if defender:IsPet() then owner = defender:GetOwner(); elseif defender:CastToNPC():GetSwarmOwner() ~= 0 then local entity_list = eq.get_entity_list(); - owner = entity_list:GetMobID(defender:CastToNPC():GetSwarmOwner()); + owner = entity_list:GetMobID(defender:CastToNPC():GetSwarmOwner()); end - + if owner.valid then PetACBonus = owner:GetAABonuses():PetMeleeMitigation() + owner:GetItemBonuses():PetMeleeMitigation() + owner:GetSpellBonuses():PetMeleeMitigation(); end - + armor = armor + defender:GetSpellBonuses():AC() + defender:GetItemBonuses():AC() + PetACBonus + 1; end - + if (opts ~= nil) then armor = armor * (1.0 - opts.armor_pen_percent); armor = armor - opts.armor_pen_flat; end - + local defender_class = defender:GetClass(); if OldACSoftcapRules then if defender_class == Class.WIZARD or defender_class == Class.MAGICIAN or defender_class == Class.NECROMANCER or defender_class == Class.ENCHANTER then @@ -502,12 +641,12 @@ function DoMeleeMitigation(defender, attacker, hit, opts) end softcap = softcap + shield_ac; - armor = armor + shield_ac; - + armor = armor + shield_ac; + if OldACSoftcapRules then softcap = softcap + (softcap * (aa_mit * AAMitigationACFactor)); end - + if armor > softcap then local softcap_armor = armor - softcap; if OldACSoftcapRules then @@ -529,7 +668,7 @@ function DoMeleeMitigation(defender, attacker, hit, opts) softcap_armor = softcap_armor * WarACSoftcapReturn; elseif defender_class == Class.PALADIN or defender_class == Class.SHADOWKNIGHT then softcap_armor = softcap_armor * PalShdACSoftcapReturn; - elseif defender_class == Class.CLERIC or defender_class == Class.RANGER or defender_class == Class.MONK or defender_class == Class.BARD then + elseif defender_class == Class.CLERIC or defender_class == Class.RANGER or defender_class == Class.MONK or defender_class == Class.BARD then softcap_armor = softcap_armor * ClrRngMnkBrdACSoftcapReturn; elseif defender_class == Class.DRUID or defender_class == Class.NECROMANCER or defender_class == Class.WIZARD or defender_class == Class.ENCHANTER or defender_class == Class.MAGICIAN then softcap_armor = softcap_armor * DruNecWizEncMagACSoftcapReturn; @@ -539,35 +678,52 @@ function DoMeleeMitigation(defender, attacker, hit, opts) softcap_armor = softcap_armor * MiscACSoftcapReturn; end end - + armor = softcap + softcap_armor; end - + local mitigation_rating; if defender_class == Class.WIZARD or defender_class == Class.MAGICIAN or defender_class == Class.NECROMANCER or defender_class == Class.ENCHANTER then mitigation_rating = ((defender:GetSkill(Skill.Defense) + defender:GetItemBonuses():HeroicAGI() / 10) / 4.0) + armor + 1; else mitigation_rating = ((defender:GetSkill(Skill.Defense) + defender:GetItemBonuses():HeroicAGI() / 10) / 3.0) + (armor * 1.333333) + 1; end - + mitigation_rating = mitigation_rating * 0.847; - + local attack_rating; if attacker:IsClient() then - attack_rating = (attacker:CastToClient():CalcATK() + ((attacker:GetSTR() - 66) * 0.9) + (attacker:GetSkill(Skill.Offense)*1.345)); + attack_rating = (attacker:CastToClient():CalcATK() + ((attacker:GetSTR() - 66) * 0.9) + (attacker:GetSkill(Skill.Offense) * 1.345)); else - attack_rating = (attacker:GetATK() + (attacker:GetSkill(Skill.Offense)*1.345) + ((attacker:GetSTR() - 66) * 0.9)); + attack_rating = (attacker:GetATK() + (attacker:GetSkill(Skill.Offense) * 1.345) + ((attacker:GetSTR() - 66) * 0.9)); end - + + eq.log_combat( + string.format("[%s] [Mob::MeleeMitigation] Attack Rating [%02f] Mitigation Rating [%02f] Damage [%i]", + defender:GetCleanName(), + attack_rating, + mitigation_rating, + hit.damage_done + ) + ); + hit.damage_done = GetMeleeMitDmg(defender, attacker, hit.damage_done, hit.min_damage, mitigation_rating, attack_rating); - if hit.damage_done < 0 then + if hit.damage_done < 0 then hit.damage_done = 0; end - + + eq.log_combat( + string.format("[%s] [Mob::MeleeMitigation] Final Damage [%i]", + defender:GetCleanName(), + hit.damage_done + ) + ); + return hit; end +-- Source Function: N/A function GetMeleeMitDmg(defender, attacker, damage, min_damage, mitigation_rating, attack_rating) if defender:IsClient() then return ClientGetMeleeMitDmg(defender, attacker, damage, min_damage, mitigation_rating, attack_rating); @@ -576,28 +732,29 @@ function GetMeleeMitDmg(defender, attacker, damage, min_damage, mitigation_ratin end end +-- Source Function: Client::GetMeleeMitDmg() function ClientGetMeleeMitDmg(defender, attacker, damage, min_damage, mitigation_rating, attack_rating) if (not attacker:IsNPC() or UseOldDamageIntervalRules) then return MobGetMeleeMitDmg(defender, attacker, damage, min_damage, mitigation_rating, attack_rating); end - - local d = 10; - local dmg_interval = (damage - min_damage) / 19.0; - local dmg_bonus = min_damage - dmg_interval; + + local d = 10; + local dmg_interval = (damage - min_damage) / 19.0; + local dmg_bonus = min_damage - dmg_interval; local spellMeleeMit = (defender:GetSpellBonuses():MeleeMitigationEffect() + defender:GetItemBonuses():MeleeMitigationEffect() + defender:GetAABonuses():MeleeMitigationEffect()) / 100.0; if (defender:GetClass() == Class.WARRIOR) then spellMeleeMit = spellMeleeMit - 0.05; end - - dmg_bonus = dmg_bonus - (dmg_bonus * (defender:GetItemBonuses():MeleeMitigation() / 100.0)); - dmg_interval = dmg_interval + (dmg_interval * spellMeleeMit); + + dmg_bonus = dmg_bonus - (dmg_bonus * (defender:GetItemBonuses():MeleeMitigation() / 100.0)); + dmg_interval = dmg_interval + (dmg_interval * spellMeleeMit); local mit_roll = Random.Real(0, mitigation_rating); local atk_roll = Random.Real(0, attack_rating); if (atk_roll > mit_roll) then - local a_diff = atk_roll - mit_roll; - local thac0 = attack_rating * ACthac0Factor; + local a_diff = atk_roll - mit_roll; + local thac0 = attack_rating * ACthac0Factor; local thac0cap = attacker:GetLevel() * 9 + 20; if (thac0 > thac0cap) then thac0 = thac0cap; @@ -605,8 +762,8 @@ function ClientGetMeleeMitDmg(defender, attacker, damage, min_damage, mitigation d = d + (10 * (a_diff / thac0)); elseif (mit_roll > atk_roll) then - local m_diff = mit_roll - atk_roll; - local thac20 = mitigation_rating * ACthac20Factor; + local m_diff = mit_roll - atk_roll; + local thac20 = mitigation_rating * ACthac20Factor; local thac20cap = defender:GetLevel() * 9 + 20; if (thac20 > thac20cap) then thac20 = thac20cap; @@ -624,14 +781,23 @@ function ClientGetMeleeMitDmg(defender, attacker, damage, min_damage, mitigation return math.floor(dmg_bonus + dmg_interval * d); end -function MobGetMeleeMitDmg(defender, attacker, damage, min_damage, mitigation_rating, attack_rating) - local d = 10.0; +-- Source Function: Mob::GetMeleeMitDmg() +function MobGetMeleeMitDmg(defender, attacker, damage, min_damage, mitigation_rating, attack_rating) + local d = 10.0; local mit_roll = Random.Real(0, mitigation_rating); local atk_roll = Random.Real(0, attack_rating); + eq.log_combat( + string.format("[%s] [Mob::GetMeleeMitDmg] MitigationRoll [%02f] AtkRoll [%02f]", + defender:GetCleanName(), + mit_roll, + atk_roll + ) + ); + if (atk_roll > mit_roll) then - local a_diff = atk_roll - mit_roll; - local thac0 = attack_rating * ACthac0Factor; + local a_diff = atk_roll - mit_roll; + local thac0 = attack_rating * ACthac0Factor; local thac0cap = attacker:GetLevel() * 9 + 20; if (thac0 > thac0cap) then thac0 = thac0cap; @@ -639,8 +805,8 @@ function MobGetMeleeMitDmg(defender, attacker, damage, min_damage, mitigation_ra d = d - (10.0 * (a_diff / thac0)); elseif (mit_roll > atk_roll) then - local m_diff = mit_roll - atk_roll; - local thac20 = mitigation_rating * ACthac20Factor; + local m_diff = mit_roll - atk_roll; + local thac20 = mitigation_rating * ACthac20Factor; local thac20cap = defender:GetLevel() * 9 + 20; if (thac20 > thac20cap) then thac20 = thac20cap; @@ -654,26 +820,64 @@ function MobGetMeleeMitDmg(defender, attacker, damage, min_damage, mitigation_ra elseif (d > 20.0) then d = 20.0; end - + local interval = (damage - min_damage) / 20.0; - damage = damage - (math.floor(d) * interval); - damage = damage - (min_damage * defender:GetItemBonuses():MeleeMitigation() / 100); - damage = damage + (damage * (defender:GetSpellBonuses():MeleeMitigationEffect() + defender:GetItemBonuses():MeleeMitigationEffect() + defender:GetAABonuses():MeleeMitigationEffect()) / 100); - + + eq.log_combat( + string.format("[%s] [Mob::GetMeleeMitDmg] Interval [%02f] d [%02f]", + defender:GetCleanName(), + interval, + d + ) + ); + + damage = damage - (math.floor(d) * interval); + + eq.log_combat( + string.format("[%s] [Mob::GetMeleeMitDmg] Damage [%02f] Post Interval", + defender:GetCleanName(), + damage + ) + ); + + damage = damage - (min_damage * defender:GetItemBonuses():MeleeMitigation() / 100); + + eq.log_combat( + string.format("[%s] [Mob::GetMeleeMitDmg] Damage [%02f] Mitigation [%i] Post Mitigation MinDmg", + defender:GetCleanName(), + damage, + defender:GetItemBonuses():MeleeMitigation() + ) + ); + + -- Changed from positive to negative per original + damage = damage - (damage * (defender:GetSpellBonuses():MeleeMitigationEffect() + defender:GetItemBonuses():MeleeMitigationEffect() + defender:GetAABonuses():MeleeMitigationEffect()) / 100); + + eq.log_combat( + string.format("[%s] [Mob::GetMeleeMitDmg] Damage [%02f] SpellMit [%i] ItemMit [%i] AAMit [%i] Post All Mit Bonuses", + defender:GetCleanName(), + damage, + defender:GetSpellBonuses():MeleeMitigationEffect(), + defender:GetItemBonuses():MeleeMitigationEffect(), + defender:GetAABonuses():MeleeMitigationEffect() + ) + ); + return damage; end +-- Source Function: Client::GetRawACNoShield() function GetRawACNoShield(self) - self = self:CastToClient(); - - local ac = self:GetItemBonuses():AC() + self:GetSpellBonuses():AC() + self:GetAABonuses():AC(); + self = self:CastToClient(); + + local ac = self:GetItemBonuses():AC() + self:GetSpellBonuses():AC() + self:GetAABonuses():AC(); local shield_ac = 0; - local inst = self:GetInventory():GetItem(Slot.Secondary); - + local inst = self:GetInventory():GetItem(Slot.Secondary); + if inst.valid then if inst:GetItem():ItemType() == 8 then shield_ac = inst:GetItem():AC(); - + for i = 1, 6 do local augment = inst:GetAugment(i - 1); if augment.valid then @@ -682,33 +886,45 @@ function GetRawACNoShield(self) end end end - + ac = ac - shield_ac; + + eq.log_combat( + string.format("[%s] [Client::GetRawACNoShield] AC [%i] ItemAC [%i] SpellAC [%i] AAAC [%i]", + self:GetCleanName(), + ac, + self:GetItemBonuses():AC(), + self:GetSpellBonuses():AC(), + self:GetAABonuses():AC() + ) + ); + return ac, shield_ac; end +-- Source Function: Mob::GetDamageTable() function GetDamageTable(attacker, skill) if not attacker:IsClient() then return 100; end - + if attacker:GetLevel() <= 51 then - local ret_table = 0; + local ret_table = 0; local str_over_75 = 0; if attacker:GetSTR() > 75 then str_over_75 = attacker:GetSTR() - 75; end - + if str_over_75 > 255 then ret_table = (attacker:GetSkill(skill) + 255) / 2; else ret_table = (attacker:GetSkill(skill) + str_over_75) / 2; end - + if ret_table < 100 then return 100; end - + return ret_table; elseif attacker:GetLevel() >= 90 then if attacker:GetClass() == 7 then @@ -718,7 +934,7 @@ function GetDamageTable(attacker, skill) end else local dmg_table = { 275, 275, 275, 275, 275, 280, 280, 280, 280, 285, 285, 285, 290, 290, 295, 295, 300, 300, 300, 305, 305, 305, 310, 310, 315, 315, 320, 320, 320, 325, 325, 325, 330, 330, 335, 335, 340, 340, 340 }; - + if attacker:GetClass() == 7 then local monkDamageTableBonus = 20; return (dmg_table[attacker:GetLevel() - 50] * (100 + monkDamageTableBonus) / 100); @@ -729,26 +945,56 @@ function GetDamageTable(attacker, skill) return 100; end +-- Source Function: N/A - Not used function ApplyDamageTable(e) e.IgnoreDefault = true; return e; end +-- Source Function: Mob::CommonOutgoingHitSuccess() function CommonOutgoingHitSuccess(e) e = ApplyMeleeDamageBonus(e); + + eq.log_combat( + string.format("[%s] [Mob::CommonOutgoingHitSuccess] Dmg [%i] Post ApplyMeleeDamageBonus", + e.self:GetCleanName(), + e.hit.damage_done + ) + ); + e.hit.damage_done = e.hit.damage_done + (e.hit.damage_done * e.other:GetSkillDmgTaken(e.hit.skill) / 100) + (e.self:GetSkillDmgAmt(e.hit.skill) + e.other:GetFcDamageAmtIncoming(e.self, 0, true, e.hit.skill)); + + eq.log_combat( + string.format("[%s] [Mob::CommonOutgoingHitSuccess] Dmg [%i] SkillDmgTaken [%i] SkillDmgtAmt [%i] FcDmgAmtIncoming [%i] Post DmgCalcs", + e.self:GetCleanName(), + e.hit.damage_done, + e.other:GetSkillDmgTaken(e.hit.skill), + e.self:GetSkillDmgAmt(e.hit.skill), + e.other:GetFcDamageAmtIncoming(e.self, 0, true, e.hit.skill) + ) + ); + e = TryCriticalHit(e); - e.self:CheckNumHitsRemaining(5, -1, 65535); + e.self:CheckNumHitsRemaining(5, -1, 65535); e.IgnoreDefault = true; return e; end +-- Source Function: Mob::ApplyMeleeDamageBonus() function ApplyMeleeDamageBonus(e) local dmgbonusmod = e.self:GetMeleeDamageMod_SE(e.hit.skill); if (opts) then dmgbonusmod = dmgbonusmod + e.opts.melee_damage_bonus_flat; end + eq.log_combat( + string.format("[%s] [Mob::ApplyMeleeDamageBonus] DmgBonusMod [%i] Dmg [%i]", + e.self:GetCleanName(), + dmgbonusmod, + e.hit.damage_done + ) + ); + e.hit.damage_done = e.hit.damage_done + (e.hit.damage_done * dmgbonusmod / 100); return e; -end +end \ No newline at end of file diff --git a/utils/scripts/eqemu_server.pl b/utils/scripts/eqemu_server.pl old mode 100644 new mode 100755 index 95d7b4ad7..2c769e811 --- a/utils/scripts/eqemu_server.pl +++ b/utils/scripts/eqemu_server.pl @@ -516,13 +516,20 @@ sub check_for_input { } sub check_for_world_bootup_database_update { - if ($OS eq "Windows") { - @db_version = split(': ', `world db_version`); - } - if ($OS eq "Linux") { - @db_version = split(': ', `./world db_version`); + + my $world_path = "world"; + if (-e "bin/world") { + $world_path = "bin/world"; } + #::: Get Binary DB version + if ($OS eq "Windows") { + @db_version = split(': ', `$world_path db_version`); + } + if ($OS eq "Linux") { + @db_version = split(': ', `./$world_path db_version`); + } + $binary_database_version = trim($db_version[1]); $local_database_version = trim(get_mysql_result("SELECT version FROM db_version LIMIT 1")); @@ -806,7 +813,7 @@ sub fetch_utility_scripts { sub setup_bots { if ($OS eq "Windows") { - fetch_latest_windows_binaries_bots(); + fetch_latest_windows_appveyor_bots(); } if ($OS eq "Linux") { build_linux_source("bots"); @@ -814,7 +821,7 @@ sub setup_bots { bots_db_management(); run_database_check(); - print "Bots should be setup, run your server and the #bot command should be available in-game\n"; + print "Bots should be setup, run your server and the bot command should be available in-game (type '^help')\n"; } sub show_menu_prompt { @@ -1686,7 +1693,7 @@ sub fetch_server_dlls { sub fetch_peq_db_full { print "[Install] Downloading latest PEQ Database... Please wait...\n"; - get_remote_file("http://edit.peqtgc.com/weekly/peq_beta.zip", "updates_staged/peq_beta.zip", 1); + get_remote_file("http://edit.projecteq.net/weekly/peq_beta.zip", "updates_staged/peq_beta.zip", 1); print "[Install] Downloaded latest PEQ Database... Extracting...\n"; unzip('updates_staged/peq_beta.zip', 'updates_staged/peq_db/'); my $start_dir = "updates_staged/peq_db"; @@ -1821,6 +1828,8 @@ sub quest_files_fetch { if ($fc == 0) { print "[Update] No Quest Updates found... \n\n"; } + + rmtree("updates_staged/"); } sub lua_modules_fetch { @@ -2207,11 +2216,18 @@ sub get_bots_db_version { } sub bots_db_management { + + my $world_path = "world"; + if (-e "bin/world") { + $world_path = "bin/world"; + } + + #::: Get Binary DB version if ($OS eq "Windows") { - @db_version = split(': ', `world db_version`); + @db_version = split(': ', `$world_path db_version`); } if ($OS eq "Linux") { - @db_version = split(': ', `./world db_version`); + @db_version = split(': ', `./$world_path db_version`); } #::: Main Binary Database version diff --git a/utils/scripts/schema.xml b/utils/scripts/schema.xml deleted file mode 100644 index dcfa687be..000000000 --- a/utils/scripts/schema.xml +++ /dev/null @@ -1,5738 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -
diff --git a/utils/sql/character_table_list.txt b/utils/sql/character_table_list.txt index 8a47abf67..ab6610dab 100644 --- a/utils/sql/character_table_list.txt +++ b/utils/sql/character_table_list.txt @@ -1,9 +1,15 @@ +account +account_ip +account_flags +account_rewards +adventure_details adventure_stats +buyer char_recipe_list -character_auras character_activities character_alt_currency character_alternate_abilities +character_auras character_bandolier character_bind character_buffs @@ -20,15 +26,22 @@ character_leadership_abilities character_material character_memmed_spells character_pet_buffs +character_pet_info character_pet_inventory character_potionbelt character_skills character_spells +character_tasks character_tribute completed_tasks +data_buckets faction_values friends +guild_bank guild_members +guild_ranks +guild_relations +guilds instance_list_player inventory inventory_snapshots @@ -36,6 +49,9 @@ keyring mail player_titlesets quest_globals +sharedbank timers titles -zone_flags +trader +trader_audit +zone_flags" \ No newline at end of file diff --git a/utils/sql/db_update_manifest.txt b/utils/sql/db_update_manifest.txt index c00255a55..8e3172b87 100644 --- a/utils/sql/db_update_manifest.txt +++ b/utils/sql/db_update_manifest.txt @@ -397,7 +397,11 @@ 9141|2019_07_10_npc_flymode.sql|SHOW COLUMNS FROM `npc_types` LIKE 'flymode'|empty| 9142|2019_09_02_required_spawn_filter.sql|SHOW COLUMNS FROM `spawnentry` LIKE 'condition_value_filter'|empty| 9143|2019_09_16_account_table_changes.sql|SHOW COLUMNS FROM `account` LIKE 'ls_id'|empty| -9144|2019_11_09_logsys_description_update.sql|SELECT * FROM `logsys_categories`|not_empty| +9144|2019_11_09_logsys_description_update.sql|SELECT * FROM db_version WHERE version >= 9143|empty| +9145|2019_12_24_banned_ips_update.sql|SHOW TABLES LIKE 'Banned_IPs'|not_empty| +9146|2020_01_10_character_soft_deletes.sql|SHOW COLUMNS FROM `character_data` LIKE 'deleted_at'|empty| +9147|2020_01_24_grid_centerpoint_wp.sql|SHOW COLUMNS FROM `grid_entries` LIKE 'centerpoint'|empty| +9148|2020_01_28_corpse_guild_consent_id.sql|SHOW COLUMNS FROM `character_corpses` LIKE 'guild_consent_id'|empty| # Upgrade conditions: # This won't be needed after this system is implemented, but it is used database that are not diff --git a/utils/sql/git/bots/bots_db_update_manifest.txt b/utils/sql/git/bots/bots_db_update_manifest.txt index 0a1e4466a..1e18516b7 100644 --- a/utils/sql/git/bots/bots_db_update_manifest.txt +++ b/utils/sql/git/bots/bots_db_update_manifest.txt @@ -21,9 +21,9 @@ 9020|2018_08_13_bots_inventory_update.sql|SELECT * FROM `inventory_versions` WHERE `version` = 2 and `bot_step` = 0|not_empty| 9021|2018_10_09_bots_owner_options.sql|SHOW TABLES LIKE 'bot_owner_options'|empty| 9022|2019_02_07_bots_stance_type_update.sql|SELECT * FROM `bot_spell_casting_chances` WHERE `spell_type_index` = '255' AND `class_id` = '255' AND `stance_index` = '0'|not_empty| -9023|2019_06_22_bots_owner_option_stats_update.sql|SHOW COLUMNS FROM `bot_owner_options` LIKE 'stats_update'|empty| +9023|2019_06_22_bots_owner_option_stats_update.sql|SELECT * FROM db_version WHERE bots_version >= 9023|empty| 9024|2019_06_27_bots_pet_get_lost.sql|SELECT `bot_command` FROM `bot_command_settings` WHERE `bot_command` LIKE 'petgetlost'|empty| -9025|2019_08_26_bots_owner_option_spawn_message.sql|SHOW COLUMNS FROM `bot_owner_options` LIKE 'spawn_message_enabled'|empty| +9025|2019_08_26_bots_owner_option_spawn_message.sql|SELECT * FROM db_version WHERE bots_version >= 9025|empty| 9026|2019_09_09_bots_owner_options_rework.sql|SHOW COLUMNS FROM `bot_owner_options` LIKE 'option_type'|empty| # Upgrade conditions: diff --git a/utils/sql/git/optional/2020_01_26_soft_delete_retro.sql b/utils/sql/git/optional/2020_01_26_soft_delete_retro.sql new file mode 100644 index 000000000..d3d0c7c4b --- /dev/null +++ b/utils/sql/git/optional/2020_01_26_soft_delete_retro.sql @@ -0,0 +1,12 @@ +-- Run this to un-reserve deleted characters +UPDATE + character_data +SET + name = SUBSTRING( + CONCAT(name, '-deleted-', UNIX_TIMESTAMP()), + 1, + 64 + ) +WHERE + deleted_at IS NOT NULL + AND name NOT LIKE '%-deleted-%'; \ No newline at end of file diff --git a/utils/sql/git/required/2019_12_24_banned_ips_update.sql b/utils/sql/git/required/2019_12_24_banned_ips_update.sql new file mode 100644 index 000000000..b05bd682b --- /dev/null +++ b/utils/sql/git/required/2019_12_24_banned_ips_update.sql @@ -0,0 +1,5 @@ +RENAME TABLE `Banned_IPs` TO `Banned_IPs_`; + +CREATE TABLE `banned_ips` (PRIMARY KEY (`ip_address`)) SELECT `ip_address`, `notes` FROM `Banned_IPs_`; + +DROP TABLE IF EXISTS `Banned_IPs_`; diff --git a/utils/sql/git/required/2020_01_10_character_soft_deletes.sql b/utils/sql/git/required/2020_01_10_character_soft_deletes.sql new file mode 100644 index 000000000..17496b141 --- /dev/null +++ b/utils/sql/git/required/2020_01_10_character_soft_deletes.sql @@ -0,0 +1 @@ +ALTER TABLE `character_data` ADD COLUMN `deleted_at` datetime NULL DEFAULT NULL; \ No newline at end of file diff --git a/utils/sql/git/required/2020_01_24_grid_centerpoint_wp.sql b/utils/sql/git/required/2020_01_24_grid_centerpoint_wp.sql new file mode 100644 index 000000000..b4a118242 --- /dev/null +++ b/utils/sql/git/required/2020_01_24_grid_centerpoint_wp.sql @@ -0,0 +1,2 @@ +alter table grid_entries add column `centerpoint` tinyint(4) not null default 0; +alter table spawngroup add column `wp_spawns` tinyint(1) unsigned not null default 0; \ No newline at end of file diff --git a/utils/sql/git/required/2020_01_28_corpse_guild_consent_id.sql b/utils/sql/git/required/2020_01_28_corpse_guild_consent_id.sql new file mode 100644 index 000000000..b42f257ea --- /dev/null +++ b/utils/sql/git/required/2020_01_28_corpse_guild_consent_id.sql @@ -0,0 +1 @@ +ALTER TABLE `character_corpses` ADD COLUMN `guild_consent_id` INT(11) UNSIGNED NOT NULL DEFAULT '0' AFTER `time_of_death`; diff --git a/utils/sql/user_tables.txt b/utils/sql/user_tables.txt index 9806395e8..ee19fe472 100644 --- a/utils/sql/user_tables.txt +++ b/utils/sql/user_tables.txt @@ -6,7 +6,7 @@ account_rewards adventure_details adventure_members adventure_stats -Banned_IPs +banned_ips bugs buyer char_recipe_list diff --git a/world/CMakeLists.txt b/world/CMakeLists.txt index c042a7364..9906b0cfc 100644 --- a/world/CMakeLists.txt +++ b/world/CMakeLists.txt @@ -14,7 +14,7 @@ SET(world_sources lfplist.cpp login_server.cpp login_server_list.cpp - net.cpp + main.cpp queryserv.cpp ucs.cpp web_interface.cpp @@ -22,6 +22,7 @@ SET(world_sources wguild_mgr.cpp world_config.cpp world_console_connection.cpp + world_server_command_handler.cpp worlddb.cpp zonelist.cpp zoneserver.cpp @@ -51,6 +52,7 @@ SET(world_headers world_config.h world_console_connection.h world_tcp_connection.h + world_server_command_handler.h worlddb.h zonelist.h zoneserver.h diff --git a/world/adventure_manager.cpp b/world/adventure_manager.cpp index 65e264a19..c3e70b7ed 100644 --- a/world/adventure_manager.cpp +++ b/world/adventure_manager.cpp @@ -2119,102 +2119,3 @@ void AdventureManager::Save() } } -void AdventureManager::Load() -{ - //disabled for now - return; - - char *data = nullptr; - FILE *f = fopen("adventure_state.dat", "r"); - if(f) - { - fseek(f, 0, SEEK_END); - long length = ftell(f); - if(length > 0) - { - data = new char[length]; - fseek(f, 0, SEEK_SET); - fread(data, length, 1, f); - } - fclose(f); - } - - if(data) - { - char *ptr = data; - - int number_of_adventures = *((int*)ptr); - ptr += sizeof(int); - - for(int i = 0; i < number_of_adventures; ++i) - { - int count = *((int*)ptr); - ptr += sizeof(int); - - int a_count = *((int*)ptr); - ptr += sizeof(int); - - int template_id = *((int*)ptr); - ptr += sizeof(int); - - int status = *((int*)ptr); - ptr += sizeof(int); - - int instance_id = *((int*)ptr); - ptr += sizeof(int); - - int rem_time = *((int*)ptr); - ptr += sizeof(int); - - int num_players = *((int*)ptr); - ptr += sizeof(int); - - AdventureTemplate *t = GetAdventureTemplate(template_id); - if(t) - { - auto adv = - new Adventure(t, count, a_count, (AdventureStatus)status, instance_id, rem_time); - for(int j = 0; j < num_players; ++j) - { - adv->AddPlayer((const char*)ptr, false); - ptr += strlen((const char*)ptr); - ptr += 1; - } - adventure_list.push_back(adv); - } - else - { - for(int j = 0; j < num_players; ++j) - { - ptr += strlen((const char*)ptr); - ptr += 1; - } - } - } - - int number_of_finished = *((int*)ptr); - ptr += sizeof(int); - - for(int k = 0; k < number_of_finished; ++k) - { - AdventureFinishEvent afe; - afe.win = *((bool*)ptr); - ptr += sizeof(bool); - - afe.points = *((int*)ptr); - ptr += sizeof(int); - - afe.theme = *((int*)ptr); - ptr += sizeof(int); - - afe.name = (const char*)ptr; - ptr += strlen((const char*)ptr); - ptr += 1; - - finished_list.push_back(afe); - } - - safe_delete_array(data); - } -} - diff --git a/world/adventure_manager.h b/world/adventure_manager.h index 5c9a4e560..ae4bf950f 100644 --- a/world/adventure_manager.h +++ b/world/adventure_manager.h @@ -34,7 +34,6 @@ public: void AddFinishedEvent(AdventureFinishEvent fe) { finished_list.push_back(fe); Save(); } bool PopFinishedEvent(const char *name, AdventureFinishEvent &fe); void Save(); - void Load(); Adventure **GetFinishedAdventures(const char *player, int &count); Adventure *GetActiveAdventure(const char *player); diff --git a/world/net.cpp b/world/main.cpp similarity index 76% rename from world/net.cpp rename to world/main.cpp index aeb9d8b4d..72cc6d391 100644 --- a/world/net.cpp +++ b/world/main.cpp @@ -1,20 +1,23 @@ -/* EQEMu: Everquest Server Emulator -Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org) +/** + * EQEmulator: Everquest Server Emulator + * Copyright (C) 2001-2019 EQEmulator Development Team (https://github.com/EQEmu/Server) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY except by those people which sell it, which + * are required to give you total support for your newly bought product; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; version 2 of the License. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY except by those people which sell it, which -are required to give you total support for your newly bought product; -without even the implied warranty of MERCHANTABILITY or FITNESS FOR -A PARTICULAR PURPOSE. See the GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ #include "../common/global_define.h" #include @@ -86,6 +89,7 @@ union semun { #include "../common/net/servertalk_server.h" #include "../zone/data_bucket.h" +#include "world_server_command_handler.h" ClientList client_list; GroupLFPList LFPGroupList; @@ -113,14 +117,30 @@ inline void UpdateWindowTitle(std::string new_title) { #endif } -int main(int argc, char** argv) { - RegisterExecutablePlatform(ExePlatformWorld); - LogSys.LoadLogSettingsDefaults(); - set_exception_handler(); +void LoadDatabaseConnections() +{ + LogInfo( + "Connecting to MySQL [{}]@[{}]:[{}]", + Config->DatabaseUsername.c_str(), + Config->DatabaseHost.c_str(), + Config->DatabasePort + ); - /** - * Auto convert json config from xml - */ + if (!database.Connect( + Config->DatabaseHost.c_str(), + Config->DatabaseUsername.c_str(), + Config->DatabasePassword.c_str(), + Config->DatabaseDB.c_str(), + Config->DatabasePort + )) { + LogError("Cannot continue without a database connection"); + + std::exit(1); + } +} + +void CheckForXMLConfigUpgrade() +{ if (!std::ifstream("eqemu_config.json") && std::ifstream("eqemu_config.xml")) { CheckForServerScript(true); if(system("perl eqemu_server.pl convert_xml")); @@ -128,26 +148,105 @@ int main(int argc, char** argv) { else { CheckForServerScript(); } +} + +void LoadServerConfig() +{ + LogInfo("Loading server configuration"); + if (!WorldConfig::LoadConfig()) { + LogError("Loading server configuration failed"); + std::exit(1); + } +} + +void RegisterLoginservers() +{ + if (Config->LoginCount == 0) { + if (Config->LoginHost.length()) { + loginserverlist.Add( + Config->LoginHost.c_str(), + Config->LoginPort, + Config->LoginAccount.c_str(), + Config->LoginPassword.c_str(), + Config->LoginLegacy + ); + LogInfo("Added loginserver [{}]:[{}]", Config->LoginHost.c_str(), Config->LoginPort); + } + } + else { + LinkedList loginlist = Config->loginlist; + LinkedListIterator iterator(loginlist); + iterator.Reset(); + while (iterator.MoreElements()) { + if (iterator.GetData()->LoginHost.length()) { + loginserverlist.Add( + iterator.GetData()->LoginHost.c_str(), + iterator.GetData()->LoginPort, + iterator.GetData()->LoginAccount.c_str(), + iterator.GetData()->LoginPassword.c_str(), + iterator.GetData()->LoginLegacy + ); + + LogInfo( + "Added loginserver [{}]:[{}]", + iterator.GetData()->LoginHost.c_str(), + iterator.GetData()->LoginPort + ); + } + iterator.Advance(); + } + } +} + +/** + * World process entrypoint + * + * @param argc + * @param argv + * @return + */ +int main(int argc, char** argv) { + RegisterExecutablePlatform(ExePlatformWorld); + LogSys.LoadLogSettingsDefaults(); + set_exception_handler(); /** * Database version */ - uint32 Database_Version = CURRENT_BINARY_DATABASE_VERSION; - uint32 Bots_Database_Version = CURRENT_BINARY_BOTS_DATABASE_VERSION; + uint32 database_version = CURRENT_BINARY_DATABASE_VERSION; + uint32 bots_database_version = CURRENT_BINARY_BOTS_DATABASE_VERSION; if (argc >= 2) { if (strcasecmp(argv[1], "db_version") == 0) { - std::cout << "Binary Database Version: " << Database_Version << " : " << Bots_Database_Version << std::endl; + std::cout << "Binary Database Version: " << database_version << " : " << bots_database_version << std::endl; return 0; } } - // Load server configuration - LogInfo("Loading server configuration"); - if (!WorldConfig::LoadConfig()) { - LogError("Loading server configuration failed"); - return 1; + /** + * Command handler + */ + if (argc > 1) { + LogSys.SilenceConsoleLogging(); + + /** + * Get Config + */ + WorldConfig::LoadConfig(); + Config = WorldConfig::get(); + + /** + * Load database + */ + LoadDatabaseConnections(); + + LogSys.EnableConsoleLogging(); + + WorldserverCommandHandler::CommandHandler(argc, argv); } + CheckForXMLConfigUpgrade(); + LoadServerConfig(); + Config = WorldConfig::get(); LogInfo("CURRENT_VERSION: [{}]", CURRENT_VERSION); @@ -169,146 +268,23 @@ int main(int argc, char** argv) { } #endif - /** - * Add Loginserver - */ - if (Config->LoginCount == 0) { - if (Config->LoginHost.length()) { - loginserverlist.Add( - Config->LoginHost.c_str(), - Config->LoginPort, - Config->LoginAccount.c_str(), - Config->LoginPassword.c_str(), - Config->LoginLegacy - ); - LogInfo("Added loginserver [{}]:[{}]", Config->LoginHost.c_str(), Config->LoginPort); - } - } - else { - LinkedList loginlist = Config->loginlist; - LinkedListIterator iterator(loginlist); - iterator.Reset(); - while (iterator.MoreElements()) { - loginserverlist.Add( - iterator.GetData()->LoginHost.c_str(), - iterator.GetData()->LoginPort, - iterator.GetData()->LoginAccount.c_str(), - iterator.GetData()->LoginPassword.c_str(), - iterator.GetData()->LoginLegacy - ); + RegisterLoginservers(); + LoadDatabaseConnections(); - LogInfo("Added loginserver [{}]:[{}]", iterator.GetData()->LoginHost.c_str(), iterator.GetData()->LoginPort); - iterator.Advance(); - } - } - - LogInfo("Connecting to MySQL [{}]@[{}]:[{}]", Config->DatabaseUsername.c_str(), Config->DatabaseHost.c_str(), Config->DatabasePort); - if (!database.Connect( - Config->DatabaseHost.c_str(), - Config->DatabaseUsername.c_str(), - Config->DatabasePassword.c_str(), - Config->DatabaseDB.c_str(), - Config->DatabasePort)) { - LogError("Cannot continue without a database connection"); - return 1; - } guild_mgr.SetDatabase(&database); - /* Register Log System and Settings */ + /** + * Logging + */ database.LoadLogSettings(LogSys.log_settings); LogSys.StartFileLogs(); + /** + * Parse simple CLI passes + */ bool ignore_db = false; if (argc >= 2) { - std::string tmp; - if (strcasecmp(argv[1], "help") == 0 || strcasecmp(argv[1], "?") == 0 || strcasecmp(argv[1], "/?") == 0 || strcasecmp(argv[1], "-?") == 0 || strcasecmp(argv[1], "-h") == 0 || strcasecmp(argv[1], "-help") == 0) { - std::cout << "Worldserver command line commands:" << std::endl; - std::cout << "adduser username password flag - adds a user account" << std::endl; - std::cout << "flag username flag - sets GM flag on the account" << std::endl; - std::cout << "startzone zoneshortname - sets the starting zone" << std::endl; - std::cout << "-holdzones - reboots lost zones" << std::endl; - return 0; - } - else if (strcasecmp(argv[1], "-holdzones") == 0) { - std::cout << "Reboot Zones mode ON" << std::endl; - holdzones = true; - } - else if (database.GetVariable("disablecommandline", tmp)) { - if (tmp.length() == 1) { - if (tmp[0] == '1') { - std::cerr << "Command line disabled in database... exiting" << std::endl; - return 1; - } - } - } - else if (strcasecmp(argv[1], "adduser") == 0) { - if (argc == 5) { - if (Seperator::IsNumber(argv[4])) { - if (atoi(argv[4]) >= 0 && atoi(argv[4]) <= 255) { - std::string user; - std::string loginserver; - - ParseAccountString(argv[2], user, loginserver); - - if (database.CreateAccount(argv[2], argv[3], atoi(argv[4]), loginserver.c_str(), 0) == 0) { - std::cerr << "database.CreateAccount failed." << std::endl; - return 1; - } - else { - std::cout << "Account created: Username='" << argv[2] << "', Password='" << argv[3] << "', status=" << argv[4] << std::endl; - return 0; - } - } - } - } - std::cout << "Usage: world adduser username password flag" << std::endl; - std::cout << "flag = 0, 1 or 2" << std::endl; - return 0; - } - else if (strcasecmp(argv[1], "flag") == 0) { - if (argc == 4) { - if (Seperator::IsNumber(argv[3])) { - if (atoi(argv[3]) >= 0 && atoi(argv[3]) <= 255) { - if (database.SetAccountStatus(argv[2], atoi(argv[3]))) { - std::cout << "Account flagged: Username='" << argv[2] << "', status=" << argv[3] << std::endl; - return 0; - } - else { - std::cerr << "database.SetAccountStatus failed." << std::endl; - return 1; - } - } - } - } - std::cout << "Usage: world flag username flag" << std::endl; - std::cout << "flag = 0-200" << std::endl; - return 0; - } - else if (strcasecmp(argv[1], "startzone") == 0) { - if (argc == 3) { - if (strlen(argv[2]) < 3) { - std::cerr << "Error: zone name too short" << std::endl; - return 1; - } - else if (strlen(argv[2]) > 15) { - std::cerr << "Error: zone name too long" << std::endl; - return 1; - } - else { - if (database.SetVariable("startzone", argv[2])) { - std::cout << "Starting zone changed: '" << argv[2] << "'" << std::endl; - return 0; - } - else { - std::cerr << "database.SetVariable failed." << std::endl; - return 1; - } - } - } - std::cout << "Usage: world startzone zoneshortname" << std::endl; - return 0; - } - else if (strcasecmp(argv[1], "ignore_db") == 0) { + if (strcasecmp(argv[1], "ignore_db") == 0) { ignore_db = true; } else { @@ -360,7 +336,7 @@ int main(int argc, char** argv) { if (!RuleManager::Instance()->UpdateOrphanedRules(&database)) { LogInfo("Failed to process 'Orphaned Rules' update operation."); } - + if (!RuleManager::Instance()->UpdateInjectedRules(&database, "default")) { LogInfo("Failed to process 'Injected Rules' for ruleset 'default' update operation."); } @@ -407,13 +383,6 @@ int main(int argc, char** argv) { LogInfo("Loading launcher list"); launcher_list.LoadList(); - std::string tmp; - database.GetVariable("holdzones", tmp); - if (tmp.length() == 1 && tmp[0] == '1') { - holdzones = true; - } - LogInfo("Reboot zone modes [{}]", holdzones ? "ON" : "OFF"); - LogInfo("Deleted [{}] stale player corpses from database", database.DeleteStalePlayerCorpses()); LogInfo("Loading adventures"); @@ -427,7 +396,6 @@ int main(int argc, char** argv) { LogInfo("Unable to load adventure templates"); } - adventure_manager.Load(); adventure_manager.LoadLeaderboardInfo(); LogInfo("Purging expired instances"); diff --git a/world/net.h b/world/main.h similarity index 100% rename from world/net.h rename to world/main.h diff --git a/world/web_interface.cpp b/world/web_interface.cpp index 191d8cca1..773e955b6 100644 --- a/world/web_interface.cpp +++ b/world/web_interface.cpp @@ -24,7 +24,7 @@ void WebInterface::OnCall(uint16 opcode, EQ::Net::Packet &p) std::stringstream ss(json_str); ss >> root; } - catch (std::exception) { + catch (std::exception &) { SendError("Could not parse request"); return; } @@ -40,7 +40,7 @@ void WebInterface::OnCall(uint16 opcode, EQ::Net::Packet &p) return; } } - catch (std::exception) { + catch (std::exception &) { SendError("Invalid request: method not supplied"); return; } @@ -49,7 +49,7 @@ void WebInterface::OnCall(uint16 opcode, EQ::Net::Packet &p) try { params = root["params"]; } - catch (std::exception) { + catch (std::exception &) { params = nullptr; } @@ -57,7 +57,7 @@ void WebInterface::OnCall(uint16 opcode, EQ::Net::Packet &p) try { id = root["id"].asString(); } - catch (std::exception) { + catch (std::exception &) { id = ""; } @@ -82,7 +82,7 @@ void WebInterface::Send(const Json::Value &value) p.PutString(0, ss.str()); m_connection->Send(ServerOP_WebInterfaceCall, p); } - catch (std::exception) { + catch (std::exception &) { //Log error } } @@ -116,7 +116,7 @@ void WebInterface::SendEvent(const Json::Value &value) p.PutString(0, ss.str()); m_connection->Send(ServerOP_WebInterfaceEvent, p); } - catch (std::exception) { + catch (std::exception &) { //Log error } } diff --git a/world/world_server_command_handler.cpp b/world/world_server_command_handler.cpp new file mode 100644 index 000000000..3a929d89f --- /dev/null +++ b/world/world_server_command_handler.cpp @@ -0,0 +1,205 @@ +/** + * EQEmulator: Everquest Server Emulator + * Copyright (C) 2001-2019 EQEmulator Development Team (https://github.com/EQEmu/Server) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY except by those people which sell it, which + * are required to give you total support for your newly bought product; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "world_server_command_handler.h" +#include "../common/eqemu_logsys.h" +#include "../common/json/json.h" +#include "../common/version.h" +#include "worlddb.h" +#include "../common/database_schema.h" + +namespace WorldserverCommandHandler { + + /** + * @param argc + * @param argv + */ + void CommandHandler(int argc, char **argv) + { + if (argc == 1) { return; } + + argh::parser cmd; + cmd.parse(argc, argv, argh::parser::PREFER_PARAM_FOR_UNREG_OPTION); + EQEmuCommand::DisplayDebug(cmd); + + /** + * Declare command mapping + */ + auto function_map = EQEmuCommand::function_map; + + /** + * Register commands + */ + function_map["world:version"] = &WorldserverCommandHandler::Version; + function_map["database:version"] = &WorldserverCommandHandler::DatabaseVersion; + function_map["database:set-account-status"] = &WorldserverCommandHandler::DatabaseSetAccountStatus; + function_map["database:schema"] = &WorldserverCommandHandler::DatabaseGetSchema; + + EQEmuCommand::HandleMenu(function_map, cmd, argc, argv); + } + + /** + * @param argc + * @param argv + * @param cmd + * @param description + */ + void DatabaseVersion(int argc, char **argv, argh::parser &cmd, std::string &description) + { + description = "Shows database version"; + + if (cmd[{"-h", "--help"}]) { + return; + } + + Json::Value database_version; + + database_version["database_version"] = CURRENT_BINARY_DATABASE_VERSION; + database_version["bots_database_version"] = CURRENT_BINARY_BOTS_DATABASE_VERSION; + + std::stringstream payload; + payload << database_version; + + std::cout << payload.str() << std::endl; + } + + /** + * @param argc + * @param argv + * @param cmd + * @param description + */ + void Version(int argc, char **argv, argh::parser &cmd, std::string &description) + { + description = "Shows server version"; + + if (cmd[{"-h", "--help"}]) { + return; + } + + Json::Value database_version; + + database_version["bots_database_version"] = CURRENT_BINARY_BOTS_DATABASE_VERSION; + database_version["compile_date"] = COMPILE_DATE; + database_version["compile_time"] = COMPILE_TIME; + database_version["database_version"] = CURRENT_BINARY_DATABASE_VERSION; + database_version["server_version"] = CURRENT_VERSION; + + std::stringstream payload; + payload << database_version; + + std::cout << payload.str() << std::endl; + } + + /** + * @param argc + * @param argv + * @param cmd + * @param description + */ + void DatabaseSetAccountStatus(int argc, char **argv, argh::parser &cmd, std::string &description) + { + description = "Sets account status by account name"; + + std::vector arguments = { + "{name}", + "{status}" + }; + + std::vector options = {}; + + if (cmd[{"-h", "--help"}]) { + return; + } + + EQEmuCommand::ValidateCmdInput(arguments, options, cmd, argc, argv); + + database.SetAccountStatus( + cmd(2).str(), + std::stoi(cmd(3).str()) + ); + } + + /** + * @param argc + * @param argv + * @param cmd + * @param description + */ + void DatabaseGetSchema(int argc, char **argv, argh::parser &cmd, std::string &description) + { + description = "Displays server database schema"; + + if (cmd[{"-h", "--help"}]) { + return; + } + + Json::Value player_tables_json; + std::vector player_tables = DatabaseSchema::GetPlayerTables(); + for (const auto &table : player_tables) { + player_tables_json.append(table); + } + + Json::Value content_tables_json; + std::vector content_tables = DatabaseSchema::GetContentTables(); + for (const auto &table : content_tables) { + content_tables_json.append(table); + } + + Json::Value server_tables_json; + std::vector server_tables = DatabaseSchema::GetServerTables(); + for (const auto &table : server_tables) { + server_tables_json.append(table); + } + + Json::Value login_tables_json; + std::vector login_tables = DatabaseSchema::GetLoginTables(); + for (const auto &table : login_tables) { + login_tables_json.append(table); + } + + Json::Value state_tables_json; + std::vector state_tables = DatabaseSchema::GetStateTables(); + for (const auto &table : state_tables) { + state_tables_json.append(table); + } + + Json::Value version_tables_json; + std::vector version_tables = DatabaseSchema::GetVersionTables(); + for (const auto &table : version_tables) { + version_tables_json.append(table); + } + + Json::Value schema; + + schema["content_tables"] = content_tables_json; + schema["login_tables"] = login_tables_json; + schema["player_tables"] = player_tables_json; + schema["server_tables"] = server_tables_json; + schema["state_tables"] = state_tables_json; + schema["version_tables"] = version_tables_json; + + std::stringstream payload; + payload << schema; + + std::cout << payload.str() << std::endl; + } + +} \ No newline at end of file diff --git a/world/world_server_command_handler.h b/world/world_server_command_handler.h new file mode 100644 index 000000000..f3d4317f2 --- /dev/null +++ b/world/world_server_command_handler.h @@ -0,0 +1,36 @@ +/** + * EQEmulator: Everquest Server Emulator + * Copyright (C) 2001-2019 EQEmulator Development Team (https://github.com/EQEmu/Server) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY except by those people which sell it, which + * are required to give you total support for your newly bought product; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "iostream" +#include "../common/cli/eqemu_command_handler.h" + +#ifndef EQEMU_WORLD_SERVER_COMMAND_HANDLER_H +#define EQEMU_WORLD_SERVER_COMMAND_HANDLER_H + +namespace WorldserverCommandHandler { + void CommandHandler(int argc, char **argv); + void Version(int argc, char **argv, argh::parser &cmd, std::string &description); + void DatabaseVersion(int argc, char **argv, argh::parser &cmd, std::string &description); + void DatabaseSetAccountStatus(int argc, char **argv, argh::parser &cmd, std::string &description); + void DatabaseGetSchema(int argc, char **argv, argh::parser &cmd, std::string &description); +}; + + +#endif //EQEMU_WORLD_SERVER_COMMAND_HANDLER_H diff --git a/world/worlddb.cpp b/world/worlddb.cpp index fbd4cdf94..f45207273 100644 --- a/world/worlddb.cpp +++ b/world/worlddb.cpp @@ -31,23 +31,27 @@ extern std::vector character_create_allocations; extern std::vector character_create_race_class_combos; -// the current stuff is at the bottom of this function -void WorldDatabase::GetCharSelectInfo(uint32 accountID, EQApplicationPacket **outApp, uint32 clientVersionBit) +/** + * @param account_id + * @param out_app + * @param client_version_bit + */ +void WorldDatabase::GetCharSelectInfo(uint32 account_id, EQApplicationPacket **out_app, uint32 client_version_bit) { - /* Set Character Creation Limit */ - EQEmu::versions::ClientVersion client_version = EQEmu::versions::ConvertClientVersionBitToClientVersion(clientVersionBit); + EQEmu::versions::ClientVersion + client_version = EQEmu::versions::ConvertClientVersionBitToClientVersion(client_version_bit); size_t character_limit = EQEmu::constants::StaticLookup(client_version)->CharacterCreationLimit; - - // Validate against absolute server max - if (character_limit > EQEmu::constants::CHARACTER_CREATION_LIMIT) + + if (character_limit > EQEmu::constants::CHARACTER_CREATION_LIMIT) { character_limit = EQEmu::constants::CHARACTER_CREATION_LIMIT; + } // Force Titanium clients to use '8' - if (client_version == EQEmu::versions::ClientVersion::Titanium) + if (client_version == EQEmu::versions::ClientVersion::Titanium) { character_limit = 8; - - /* Get Character Info */ - std::string cquery = StringFormat( + } + + std::string character_list_query = StringFormat( "SELECT " "`id`, " // 0 "name, " // 1 @@ -71,237 +75,281 @@ void WorldDatabase::GetCharSelectInfo(uint32 accountID, EQApplicationPacket **ou "zone_id " // 19 "FROM " "character_data " - "WHERE `account_id` = %i ORDER BY `name` LIMIT %u", accountID, character_limit); - auto results = database.QueryDatabase(cquery); + "WHERE `account_id` = %i AND deleted_at IS NULL ORDER BY `name` LIMIT %u", + account_id, + character_limit + ); + + auto results = database.QueryDatabase(character_list_query); size_t character_count = results.RowCount(); if (character_count == 0) { - *outApp = new EQApplicationPacket(OP_SendCharInfo, sizeof(CharacterSelect_Struct)); - CharacterSelect_Struct *cs = (CharacterSelect_Struct *)(*outApp)->pBuffer; - cs->CharCount = 0; + *out_app = new EQApplicationPacket(OP_SendCharInfo, sizeof(CharacterSelect_Struct)); + CharacterSelect_Struct *cs = (CharacterSelect_Struct *) (*out_app)->pBuffer; + cs->CharCount = 0; cs->TotalChars = character_limit; return; } size_t packet_size = sizeof(CharacterSelect_Struct) + (sizeof(CharacterSelectEntry_Struct) * character_count); - *outApp = new EQApplicationPacket(OP_SendCharInfo, packet_size); + *out_app = new EQApplicationPacket(OP_SendCharInfo, packet_size); - unsigned char *buff_ptr = (*outApp)->pBuffer; - CharacterSelect_Struct *cs = (CharacterSelect_Struct *)buff_ptr; + unsigned char *buff_ptr = (*out_app)->pBuffer; + CharacterSelect_Struct *cs = (CharacterSelect_Struct *) buff_ptr; - cs->CharCount = character_count; + cs->CharCount = character_count; cs->TotalChars = character_limit; buff_ptr += sizeof(CharacterSelect_Struct); for (auto row = results.begin(); row != results.end(); ++row) { - CharacterSelectEntry_Struct *cse = (CharacterSelectEntry_Struct *)buff_ptr; - PlayerProfile_Struct pp; - EQEmu::InventoryProfile inv; + CharacterSelectEntry_Struct *p_character_select_entry_struct = (CharacterSelectEntry_Struct *) buff_ptr; + PlayerProfile_Struct player_profile_struct; + EQEmu::InventoryProfile inventory_profile; - pp.SetPlayerProfileVersion(EQEmu::versions::ConvertClientVersionToMobVersion(client_version)); - inv.SetInventoryVersion(client_version); - inv.SetGMInventory(true); // charsel can not interact with items..but, no harm in setting to full expansion support + player_profile_struct.SetPlayerProfileVersion(EQEmu::versions::ConvertClientVersionToMobVersion(client_version)); + inventory_profile.SetInventoryVersion(client_version); + inventory_profile.SetGMInventory(true); // charsel can not interact with items..but, no harm in setting to full expansion support - uint32 character_id = (uint32)atoi(row[0]); - uint8 has_home = 0; - uint8 has_bind = 0; + uint32 character_id = (uint32) atoi(row[0]); + uint8 has_home = 0; + uint8 has_bind = 0; - memset(&pp, 0, sizeof(PlayerProfile_Struct)); - - /* Fill CharacterSelectEntry_Struct */ - memset(cse->Name, 0, sizeof(cse->Name)); - strcpy(cse->Name, row[1]); - cse->Class = (uint8)atoi(row[4]); - cse->Race = (uint32)atoi(row[3]); - cse->Level = (uint8)atoi(row[5]); - cse->ShroudClass = cse->Class; - cse->ShroudRace = cse->Race; - cse->Zone = (uint16)atoi(row[19]); - cse->Instance = 0; - cse->Gender = (uint8)atoi(row[2]); - cse->Face = (uint8)atoi(row[15]); + memset(&player_profile_struct, 0, sizeof(PlayerProfile_Struct)); - for (uint32 matslot = 0; matslot < EQEmu::textures::materialCount; matslot++) { // Processed below - cse->Equip[matslot].Material = 0; - cse->Equip[matslot].Unknown1 = 0; - cse->Equip[matslot].EliteModel = 0; - cse->Equip[matslot].HerosForgeModel = 0; - cse->Equip[matslot].Unknown2 = 0; - cse->Equip[matslot].Color = 0; - } + memset(p_character_select_entry_struct->Name, 0, sizeof(p_character_select_entry_struct->Name)); + strcpy(p_character_select_entry_struct->Name, row[1]); + p_character_select_entry_struct->Class = (uint8) atoi(row[4]); + p_character_select_entry_struct->Race = (uint32) atoi(row[3]); + p_character_select_entry_struct->Level = (uint8) atoi(row[5]); + p_character_select_entry_struct->ShroudClass = p_character_select_entry_struct->Class; + p_character_select_entry_struct->ShroudRace = p_character_select_entry_struct->Race; + p_character_select_entry_struct->Zone = (uint16) atoi(row[19]); + p_character_select_entry_struct->Instance = 0; + p_character_select_entry_struct->Gender = (uint8) atoi(row[2]); + p_character_select_entry_struct->Face = (uint8) atoi(row[15]); - cse->Unknown15 = 0xFF; - cse->Unknown19 = 0xFF; - cse->DrakkinTattoo = (uint32)atoi(row[17]); - cse->DrakkinDetails = (uint32)atoi(row[18]); - cse->Deity = (uint32)atoi(row[6]); - cse->PrimaryIDFile = 0; // Processed Below - cse->SecondaryIDFile = 0; // Processed Below - cse->HairColor = (uint8)atoi(row[9]); - cse->BeardColor = (uint8)atoi(row[10]); - cse->EyeColor1 = (uint8)atoi(row[11]); - cse->EyeColor2 = (uint8)atoi(row[12]); - cse->HairStyle = (uint8)atoi(row[13]); - cse->Beard = (uint8)atoi(row[14]); - cse->GoHome = 0; // Processed Below - cse->Tutorial = 0; // Processed Below - cse->DrakkinHeritage = (uint32)atoi(row[16]); - cse->Unknown1 = 0; - cse->Enabled = 1; - cse->LastLogin = (uint32)atoi(row[7]); // RoF2 value: 1212696584 - cse->Unknown2 = 0; - /* Fill End */ + for (uint32 material_slot = 0; material_slot < EQEmu::textures::materialCount; material_slot++) { + p_character_select_entry_struct->Equip[material_slot].Material = 0; + p_character_select_entry_struct->Equip[material_slot].Unknown1 = 0; + p_character_select_entry_struct->Equip[material_slot].EliteModel = 0; + p_character_select_entry_struct->Equip[material_slot].HerosForgeModel = 0; + p_character_select_entry_struct->Equip[material_slot].Unknown2 = 0; + p_character_select_entry_struct->Equip[material_slot].Color = 0; + } + + p_character_select_entry_struct->Unknown15 = 0xFF; + p_character_select_entry_struct->Unknown19 = 0xFF; + p_character_select_entry_struct->DrakkinTattoo = (uint32) atoi(row[17]); + p_character_select_entry_struct->DrakkinDetails = (uint32) atoi(row[18]); + p_character_select_entry_struct->Deity = (uint32) atoi(row[6]); + p_character_select_entry_struct->PrimaryIDFile = 0; // Processed Below + p_character_select_entry_struct->SecondaryIDFile = 0; // Processed Below + p_character_select_entry_struct->HairColor = (uint8) atoi(row[9]); + p_character_select_entry_struct->BeardColor = (uint8) atoi(row[10]); + p_character_select_entry_struct->EyeColor1 = (uint8) atoi(row[11]); + p_character_select_entry_struct->EyeColor2 = (uint8) atoi(row[12]); + p_character_select_entry_struct->HairStyle = (uint8) atoi(row[13]); + p_character_select_entry_struct->Beard = (uint8) atoi(row[14]); + p_character_select_entry_struct->GoHome = 0; // Processed Below + p_character_select_entry_struct->Tutorial = 0; // Processed Below + p_character_select_entry_struct->DrakkinHeritage = (uint32) atoi(row[16]); + p_character_select_entry_struct->Unknown1 = 0; + p_character_select_entry_struct->Enabled = 1; + p_character_select_entry_struct->LastLogin = (uint32) atoi(row[7]); // RoF2 value: 1212696584 + p_character_select_entry_struct->Unknown2 = 0; if (RuleB(World, EnableReturnHomeButton)) { int now = time(nullptr); - if ((now - atoi(row[7])) >= RuleI(World, MinOfflineTimeToReturnHome)) - cse->GoHome = 1; + if ((now - atoi(row[7])) >= RuleI(World, MinOfflineTimeToReturnHome)) { + p_character_select_entry_struct->GoHome = 1; + } } - if (RuleB(World, EnableTutorialButton) && (cse->Level <= RuleI(World, MaxLevelForTutorial))) { - cse->Tutorial = 1; + if (RuleB(World, EnableTutorialButton) && (p_character_select_entry_struct->Level <= RuleI(World, MaxLevelForTutorial))) { + p_character_select_entry_struct->Tutorial = 1; } - /* Set Bind Point Data for any character that may possibly be missing it for any reason */ - cquery = StringFormat("SELECT `zone_id`, `instance_id`, `x`, `y`, `z`, `heading`, `slot` FROM `character_bind` WHERE `id` = %i LIMIT 5", character_id); - auto results_bind = database.QueryDatabase(cquery); - auto bind_count = results_bind.RowCount(); - for (auto row_b = results_bind.begin(); row_b != results_bind.end(); ++row_b) { + /** + * Bind + */ + character_list_query = StringFormat( + "SELECT `zone_id`, `instance_id`, `x`, `y`, `z`, `heading`, `slot` FROM `character_bind` WHERE `id` = %i LIMIT 5", + character_id + ); + auto results_bind = database.QueryDatabase(character_list_query); + auto bind_count = results_bind.RowCount(); + for (auto row_b = results_bind.begin(); row_b != results_bind.end(); ++row_b) { if (row_b[6] && atoi(row_b[6]) == 4) { has_home = 1; // If our bind count is less than 5, we need to actually make use of this data so lets parse it if (bind_count < 5) { - pp.binds[4].zoneId = atoi(row_b[0]); - pp.binds[4].instance_id = atoi(row_b[1]); - pp.binds[4].x = atof(row_b[2]); - pp.binds[4].y = atof(row_b[3]); - pp.binds[4].z = atof(row_b[4]); - pp.binds[4].heading = atof(row_b[5]); + player_profile_struct.binds[4].zoneId = atoi(row_b[0]); + player_profile_struct.binds[4].instance_id = atoi(row_b[1]); + player_profile_struct.binds[4].x = atof(row_b[2]); + player_profile_struct.binds[4].y = atof(row_b[3]); + player_profile_struct.binds[4].z = atof(row_b[4]); + player_profile_struct.binds[4].heading = atof(row_b[5]); } } - if (row_b[6] && atoi(row_b[6]) == 0){ has_bind = 1; } + if (row_b[6] && atoi(row_b[6]) == 0) { has_bind = 1; } } if (has_home == 0 || has_bind == 0) { - cquery = StringFormat("SELECT `zone_id`, `bind_id`, `x`, `y`, `z` FROM `start_zones` WHERE `player_class` = %i AND `player_deity` = %i AND `player_race` = %i", - cse->Class, cse->Deity, cse->Race); - auto results_bind = database.QueryDatabase(cquery); - for (auto row_d = results_bind.begin(); row_d != results_bind.end(); ++row_d) { + character_list_query = StringFormat( + "SELECT `zone_id`, `bind_id`, `x`, `y`, `z` FROM `start_zones` WHERE `player_class` = %i AND `player_deity` = %i AND `player_race` = %i", + p_character_select_entry_struct->Class, + p_character_select_entry_struct->Deity, + p_character_select_entry_struct->Race + ); + auto results_bind = database.QueryDatabase(character_list_query); + for (auto row_d = results_bind.begin(); row_d != results_bind.end(); ++row_d) { /* If a bind_id is specified, make them start there */ if (atoi(row_d[1]) != 0) { - pp.binds[4].zoneId = (uint32)atoi(row_d[1]); - GetSafePoints(pp.binds[4].zoneId, 0, &pp.binds[4].x, &pp.binds[4].y, &pp.binds[4].z); + player_profile_struct.binds[4].zoneId = (uint32) atoi(row_d[1]); + GetSafePoints(player_profile_struct.binds[4].zoneId, 0, &player_profile_struct.binds[4].x, &player_profile_struct.binds[4].y, &player_profile_struct.binds[4].z); } - /* Otherwise, use the zone and coordinates given */ + /* Otherwise, use the zone and coordinates given */ else { - pp.binds[4].zoneId = (uint32)atoi(row_d[0]); + player_profile_struct.binds[4].zoneId = (uint32) atoi(row_d[0]); float x = atof(row_d[2]); float y = atof(row_d[3]); float z = atof(row_d[4]); - if (x == 0 && y == 0 && z == 0){ GetSafePoints(pp.binds[4].zoneId, 0, &x, &y, &z); } - pp.binds[4].x = x; pp.binds[4].y = y; pp.binds[4].z = z; + if (x == 0 && y == 0 && z == 0) { GetSafePoints(player_profile_struct.binds[4].zoneId, 0, &x, &y, &z); } + player_profile_struct.binds[4].x = x; + player_profile_struct.binds[4].y = y; + player_profile_struct.binds[4].z = z; } } - pp.binds[0] = pp.binds[4]; + player_profile_struct.binds[0] = player_profile_struct.binds[4]; /* If no home bind set, set it */ if (has_home == 0) { - std::string query = StringFormat("REPLACE INTO `character_bind` (id, zone_id, instance_id, x, y, z, heading, slot)" + std::string query = StringFormat( + "REPLACE INTO `character_bind` (id, zone_id, instance_id, x, y, z, heading, slot)" " VALUES (%u, %u, %u, %f, %f, %f, %f, %i)", - character_id, pp.binds[4].zoneId, 0, pp.binds[4].x, pp.binds[4].y, pp.binds[4].z, pp.binds[4].heading, 4); - auto results_bset = QueryDatabase(query); + character_id, + player_profile_struct.binds[4].zoneId, + 0, + player_profile_struct.binds[4].x, + player_profile_struct.binds[4].y, + player_profile_struct.binds[4].z, + player_profile_struct.binds[4].heading, + 4 + ); + auto results_bset = QueryDatabase(query); } /* If no regular bind set, set it */ if (has_bind == 0) { - std::string query = StringFormat("REPLACE INTO `character_bind` (id, zone_id, instance_id, x, y, z, heading, slot)" + std::string query = StringFormat( + "REPLACE INTO `character_bind` (id, zone_id, instance_id, x, y, z, heading, slot)" " VALUES (%u, %u, %u, %f, %f, %f, %f, %i)", - character_id, pp.binds[0].zoneId, 0, pp.binds[0].x, pp.binds[0].y, pp.binds[0].z, pp.binds[0].heading, 0); - auto results_bset = QueryDatabase(query); + character_id, + player_profile_struct.binds[0].zoneId, + 0, + player_profile_struct.binds[0].x, + player_profile_struct.binds[0].y, + player_profile_struct.binds[0].z, + player_profile_struct.binds[0].heading, + 0 + ); + auto results_bset = QueryDatabase(query); } } /* If our bind count is less than 5, then we have null data that needs to be filled in. */ if (bind_count < 5) { // we know that home and main bind must be valid here, so we don't check those // we also use home to fill in the null data like live does. - for (int i = 1; i < 4; i++) { - if (pp.binds[i].zoneId != 0) // we assume 0 is the only invalid one ... + for (int i = 1; i < 4; i++) { + if (player_profile_struct.binds[i].zoneId != 0) { // we assume 0 is the only invalid one ... continue; + } - std::string query = StringFormat("REPLACE INTO `character_bind` (id, zone_id, instance_id, x, y, z, heading, slot)" + std::string query = StringFormat( + "REPLACE INTO `character_bind` (id, zone_id, instance_id, x, y, z, heading, slot)" " VALUES (%u, %u, %u, %f, %f, %f, %f, %i)", - character_id, pp.binds[4].zoneId, 0, pp.binds[4].x, pp.binds[4].y, pp.binds[4].z, pp.binds[4].heading, i); - auto results_bset = QueryDatabase(query); + character_id, + player_profile_struct.binds[4].zoneId, + 0, + player_profile_struct.binds[4].x, + player_profile_struct.binds[4].y, + player_profile_struct.binds[4].z, + player_profile_struct.binds[4].heading, + i + ); + auto results_bset = QueryDatabase(query); } } - /* Bind End */ - /* Load Character Material Data for Char Select */ - cquery = StringFormat("SELECT slot, red, green, blue, use_tint, color FROM `character_material` WHERE `id` = %u", character_id); - auto results_b = database.QueryDatabase(cquery); uint8 slot = 0; - for (auto row_b = results_b.begin(); row_b != results_b.end(); ++row_b) { + character_list_query = StringFormat( + "SELECT slot, red, green, blue, use_tint, color FROM `character_material` WHERE `id` = %u", + character_id + ); + auto results_b = database.QueryDatabase(character_list_query); + uint8 slot = 0; + for (auto row_b = results_b.begin(); row_b != results_b.end(); ++row_b) { slot = atoi(row_b[0]); - pp.item_tint.Slot[slot].Red = atoi(row_b[1]); - pp.item_tint.Slot[slot].Green = atoi(row_b[2]); - pp.item_tint.Slot[slot].Blue = atoi(row_b[3]); - pp.item_tint.Slot[slot].UseTint = atoi(row_b[4]); + player_profile_struct.item_tint.Slot[slot].Red = atoi(row_b[1]); + player_profile_struct.item_tint.Slot[slot].Green = atoi(row_b[2]); + player_profile_struct.item_tint.Slot[slot].Blue = atoi(row_b[3]); + player_profile_struct.item_tint.Slot[slot].UseTint = atoi(row_b[4]); } - /* Character Material Data End */ - /* Load Inventory */ - // If we ensure that the material data is updated appropriately, we can do away with inventory loads - if (GetCharSelInventory(accountID, cse->Name, &inv)) { - const EQEmu::ItemData* item = nullptr; - const EQEmu::ItemInstance* inst = nullptr; - int16 invslot = 0; + if (GetCharSelInventory(account_id, p_character_select_entry_struct->Name, &inventory_profile)) { + const EQEmu::ItemData *item = nullptr; + const EQEmu::ItemInstance *inst = nullptr; + int16 inventory_slot = 0; for (uint32 matslot = EQEmu::textures::textureBegin; matslot < EQEmu::textures::materialCount; matslot++) { - invslot = EQEmu::InventoryProfile::CalcSlotFromMaterial(matslot); - if (invslot == INVALID_INDEX) { continue; } - inst = inv.GetItem(invslot); - if (inst == nullptr) { continue; } + inventory_slot = EQEmu::InventoryProfile::CalcSlotFromMaterial(matslot); + if (inventory_slot == INVALID_INDEX) { continue; } + inst = inventory_profile.GetItem(inventory_slot); + if (inst == nullptr) { + continue; + } item = inst->GetItem(); - if (item == nullptr) { continue; } + if (item == nullptr) { + continue; + } if (matslot > 6) { - uint32 idfile = 0; + uint32 item_id_file = 0; // Weapon Models if (inst->GetOrnamentationIDFile() != 0) { - idfile = inst->GetOrnamentationIDFile(); - cse->Equip[matslot].Material = idfile; + item_id_file = inst->GetOrnamentationIDFile(); + p_character_select_entry_struct->Equip[matslot].Material = item_id_file; } else { if (strlen(item->IDFile) > 2) { - idfile = atoi(&item->IDFile[2]); - cse->Equip[matslot].Material = idfile; + item_id_file = atoi(&item->IDFile[2]); + p_character_select_entry_struct->Equip[matslot].Material = item_id_file; } } if (matslot == EQEmu::textures::weaponPrimary) { - cse->PrimaryIDFile = idfile; + p_character_select_entry_struct->PrimaryIDFile = item_id_file; } else { - cse->SecondaryIDFile = idfile; + p_character_select_entry_struct->SecondaryIDFile = item_id_file; } } else { uint32 color = 0; - if (pp.item_tint.Slot[matslot].UseTint) { - color = pp.item_tint.Slot[matslot].Color; + if (player_profile_struct.item_tint.Slot[matslot].UseTint) { + color = player_profile_struct.item_tint.Slot[matslot].Color; } else { color = inst->GetColor(); } // Armor Materials/Models - cse->Equip[matslot].Material = item->Material; - cse->Equip[matslot].EliteModel = item->EliteMaterial; - cse->Equip[matslot].HerosForgeModel = inst->GetOrnamentHeroModel(matslot); - cse->Equip[matslot].Color = color; + p_character_select_entry_struct->Equip[matslot].Material = item->Material; + p_character_select_entry_struct->Equip[matslot].EliteModel = item->EliteMaterial; + p_character_select_entry_struct->Equip[matslot].HerosForgeModel = inst->GetOrnamentHeroModel(matslot); + p_character_select_entry_struct->Equip[matslot].Color = color; } } } else { - printf("Error loading inventory for %s\n", cse->Name); + printf("Error loading inventory for %s\n", p_character_select_entry_struct->Name); } - /* Load Inventory End */ buff_ptr += sizeof(CharacterSelectEntry_Struct); } diff --git a/world/worlddb.h b/world/worlddb.h index 036c0dc40..e367803ec 100644 --- a/world/worlddb.h +++ b/world/worlddb.h @@ -30,7 +30,7 @@ struct CharacterSelect_Struct; class WorldDatabase : public SharedDatabase { public: bool GetStartZone(PlayerProfile_Struct* in_pp, CharCreate_Struct* in_cc, bool isTitanium); - void GetCharSelectInfo(uint32 accountID, EQApplicationPacket **outApp, uint32 clientVersionBit); + void GetCharSelectInfo(uint32 account_id, EQApplicationPacket **out_app, uint32 client_version_bit); int MoveCharacterToBind(int CharID, uint8 bindnum = 0); void GetLauncherList(std::vector &result); diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index dd55fac39..08ccbfaff 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -1057,110 +1057,42 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { break; } case ServerOP_Consent: { - // Message string id's likely to be used here are: - // CONSENT_YOURSELF = 399 - // CONSENT_INVALID_NAME = 397 - // TARGET_NOT_FOUND = 101 - ZoneServer* zs; - ServerOP_Consent_Struct* s = (ServerOP_Consent_Struct*)pack->pBuffer; - ClientListEntry* cle = client_list.FindCharacter(s->grantname); - if (cle) { - if (cle->instance() != 0) - { - zs = zoneserver_list.FindByInstanceID(cle->instance()); - if (zs) { - zs->SendPacket(pack); - } - else - { - auto pack = new ServerPacket(ServerOP_Consent_Response, sizeof(ServerOP_Consent_Struct)); - ServerOP_Consent_Struct* scs = (ServerOP_Consent_Struct*)pack->pBuffer; - strcpy(scs->grantname, s->grantname); - strcpy(scs->ownername, s->ownername); - scs->permission = s->permission; - scs->zone_id = s->zone_id; - scs->instance_id = s->instance_id; - scs->message_string_id = 101; - zs = zoneserver_list.FindByInstanceID(s->instance_id); - if (zs) { - zs->SendPacket(pack); - } - else { - LogInfo("Unable to locate zone record for instance id [{}] in zoneserver list for ServerOP_Consent_Response operation", s->instance_id); - } - safe_delete(pack); - } - } - else - { - zs = zoneserver_list.FindByZoneID(cle->zone()); - if (zs) { - zs->SendPacket(pack); - } - else { - // send target not found back to requester - auto pack = new ServerPacket(ServerOP_Consent_Response, sizeof(ServerOP_Consent_Struct)); - ServerOP_Consent_Struct* scs = (ServerOP_Consent_Struct*)pack->pBuffer; - strcpy(scs->grantname, s->grantname); - strcpy(scs->ownername, s->ownername); - scs->permission = s->permission; - scs->zone_id = s->zone_id; - scs->message_string_id = 101; - zs = zoneserver_list.FindByZoneID(s->zone_id); - if (zs) { - zs->SendPacket(pack); - } - else { - LogInfo("Unable to locate zone record for zone id [{}] in zoneserver list for ServerOP_Consent_Response operation", s->zone_id); - } - safe_delete(pack); - } - } - } - else { - // send target not found back to requester - auto pack = new ServerPacket(ServerOP_Consent_Response, sizeof(ServerOP_Consent_Struct)); - ServerOP_Consent_Struct* scs = (ServerOP_Consent_Struct*)pack->pBuffer; - strcpy(scs->grantname, s->grantname); - strcpy(scs->ownername, s->ownername); - scs->permission = s->permission; - scs->zone_id = s->zone_id; - scs->message_string_id = 397; - zs = zoneserver_list.FindByZoneID(s->zone_id); - if (zs) { - zs->SendPacket(pack); - } - else { - LogInfo("Unable to locate zone record for zone id [{}] in zoneserver list for ServerOP_Consent_Response operation", s->zone_id); - } - safe_delete(pack); - } + zoneserver_list.SendPacket(pack); // update corpses in all zones break; } case ServerOP_Consent_Response: { - // Message string id's likely to be used here are: - // CONSENT_YOURSELF = 399 - // CONSENT_INVALID_NAME = 397 - // TARGET_NOT_FOUND = 101 ServerOP_Consent_Struct* s = (ServerOP_Consent_Struct*)pack->pBuffer; - if (s->instance_id != 0) - { - ZoneServer* zs = zoneserver_list.FindByInstanceID(s->instance_id); - if (zs) { - zs->SendPacket(pack); - } - else { - LogInfo("Unable to locate zone record for instance id [{}] in zoneserver list for ServerOP_Consent_Response operation", s->instance_id); - } + + ZoneServer* owner_zs = nullptr; + if (s->instance_id == 0) { + owner_zs = zoneserver_list.FindByZoneID(s->zone_id); } - else - { - ZoneServer* zs = zoneserver_list.FindByZoneID(s->zone_id); - if (zs) { - zs->SendPacket(pack); - } - else { - LogInfo("Unable to locate zone record for zone id [{}] in zoneserver list for ServerOP_Consent_Response operation", s->zone_id); + else { + owner_zs = zoneserver_list.FindByInstanceID(s->instance_id); + } + + if (owner_zs) { + owner_zs->SendPacket(pack); + } + else { + LogInfo("Unable to locate zone record for zone id [{}] or instance id [{}] in zoneserver list for ServerOP_Consent_Response operation", s->zone_id, s->instance_id); + } + + if (s->consent_type == EQEmu::consent::Normal) { + // send the message to the client being granted or denied permission + ClientListEntry* cle = client_list.FindCharacter(s->grantname); + if (cle) { + ZoneServer* granted_zs = nullptr; + if (cle->instance() == 0) { + granted_zs = zoneserver_list.FindByZoneID(cle->zone()); + } + else { + granted_zs = zoneserver_list.FindByInstanceID(cle->instance()); + } + // avoid sending twice if owner and granted are in same zone + if (granted_zs && granted_zs != owner_zs) { + granted_zs->SendPacket(pack); + } } } break; diff --git a/zone/aa.cpp b/zone/aa.cpp index 09a954331..1383ad2f9 100644 --- a/zone/aa.cpp +++ b/zone/aa.cpp @@ -1467,12 +1467,20 @@ bool Mob::CanUseAlternateAdvancementRank(AA::Rank *rank) { } } - if(IsClient()) { - if(rank->expansion && !(CastToClient()->GetPP().expansions & (1 << (rank->expansion - 1)))) { + if (IsClient()) { + if (rank->expansion && !(CastToClient()->GetPP().expansions & (1 << (rank->expansion - 1)))) { return false; } - } else { - if(rank->expansion && !(RuleI(World, ExpansionSettings) & (1 << (rank->expansion - 1)))) { + } +#ifdef BOTS + else if (IsBot()) { + if (rank->expansion && !(RuleI(Bots, BotExpansionSettings) & (1 << (rank->expansion - 1)))) { + return false; + } + } +#endif + else { + if (rank->expansion && !(RuleI(World, ExpansionSettings) & (1 << (rank->expansion - 1)))) { return false; } } diff --git a/zone/aggro.cpp b/zone/aggro.cpp index 1d579567f..ff0077353 100644 --- a/zone/aggro.cpp +++ b/zone/aggro.cpp @@ -36,19 +36,6 @@ extern Zone* zone; //#define LOSDEBUG 6 -//look around a client for things which might aggro the client. -void EntityList::CheckClientAggro(Client *around) -{ - for (auto it = mob_list.begin(); it != mob_list.end(); ++it) { - Mob *mob = it->second; - if (mob->IsClient()) //also ensures that mob != around - continue; - - if (mob->CheckWillAggro(around) && !mob->CheckAggro(around)) - mob->AddToHateList(around, 25); - } -} - void EntityList::DescribeAggro(Client *towho, NPC *from_who, float d, bool verbose) { float d2 = d*d; @@ -402,22 +389,6 @@ bool Mob::CheckWillAggro(Mob *mob) { return(false); } -Mob* EntityList::AICheckNPCtoNPCAggro(Mob* sender, float iAggroRange, float iAssistRange) { - if (!sender || !sender->IsNPC()) - return(nullptr); - - auto it = npc_list.begin(); - while (it != npc_list.end()) { - Mob *mob = it->second; - - if (sender->CheckWillAggro(mob)) - return mob; - ++it; - } - - return nullptr; -} - int EntityList::GetHatedCount(Mob *attacker, Mob *exclude, bool inc_gray_con) { // Return a list of how many non-feared, non-mezzed, non-green mobs, within aggro range, hate *attacker @@ -462,82 +433,11 @@ int EntityList::GetHatedCount(Mob *attacker, Mob *exclude, bool inc_gray_con) return Count; } -void EntityList::AIYellForHelp(Mob* sender, Mob* attacker) { - if(!sender || !attacker) - return; - if (sender->GetPrimaryFaction() == 0 ) - return; // well, if we dont have a faction set, we're gonna be indiff to everybody - - if (sender->HasAssistAggro()) - return; - - for (auto it = npc_list.begin(); it != npc_list.end(); ++it) { - NPC *mob = it->second; - if (!mob) - continue; - - if (mob->CheckAggro(attacker)) - continue; - - if (sender->NPCAssistCap() >= RuleI(Combat, NPCAssistCap)) - break; - - float r = mob->GetAssistRange(); - r = r * r; - - if ( - mob != sender - && mob != attacker -// && !mob->IsCorpse() -// && mob->IsAIControlled() - && mob->GetPrimaryFaction() != 0 - && DistanceSquared(mob->GetPosition(), sender->GetPosition()) <= r - && !mob->IsEngaged() - && ((!mob->IsPet()) || (mob->IsPet() && mob->GetOwner() && !mob->GetOwner()->IsClient())) - // If we're a pet we don't react to any calls for help if our owner is a client - ) - { - //if they are in range, make sure we are not green... - //then jump in if they are our friend - if(mob->GetLevel() >= 50 || attacker->GetLevelCon(mob->GetLevel()) != CON_GRAY) - { - bool useprimfaction = false; - if(mob->GetPrimaryFaction() == sender->CastToNPC()->GetPrimaryFaction()) - { - const NPCFactionList *cf = database.GetNPCFactionEntry(mob->GetNPCFactionID()); - if(cf){ - if(cf->assistprimaryfaction != 0) - useprimfaction = true; - } - } - - if(useprimfaction || sender->GetReverseFactionCon(mob) <= FACTION_AMIABLE ) - { - //attacking someone on same faction, or a friend - //Father Nitwit: make sure we can see them. - if(mob->CheckLosFN(sender)) { -#if (EQDEBUG>=5) - LogDebug("AIYellForHelp(\"[{}]\",\"[{}]\") [{}] attacking [{}] Dist [{}] Z [{}]", - sender->GetName(), attacker->GetName(), mob->GetName(), - attacker->GetName(), DistanceSquared(mob->GetPosition(), - sender->GetPosition()), std::abs(sender->GetZ()+mob->GetZ())); -#endif - mob->AddToHateList(attacker, 25, 0, false); - sender->AddAssistCap(); - } - } - } - } - } -} - -/* -returns false if attack should not be allowed -I try to list every type of conflict that's possible here, so it's easy -to see how the decision is made. Yea, it could be condensed and made -faster, but I'm doing it this way to make it readable and easy to modify -*/ - +/** + * @param target + * @param isSpellAttack + * @return + */ bool Mob::IsAttackAllowed(Mob *target, bool isSpellAttack) { diff --git a/zone/attack.cpp b/zone/attack.cpp index f39fb6b90..308592041 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -724,20 +724,20 @@ int Mob::GetClassRaceACBonus() hardcap = 32; softcap = 15; } - int weight = IsClient() ? CastToClient()->CalcCurrentWeight() : 0; + int weight = IsClient() ? CastToClient()->CalcCurrentWeight()/10 : 0; if (weight < hardcap - 1) { - int temp = level + 5; + double temp = level + 5; if (weight > softcap) { - double redux = (weight - softcap) * 6.66667; + double redux = static_cast(weight - softcap) * 6.66667; redux = (100.0 - std::min(100.0, redux)) * 0.01; - temp = std::max(0, static_cast(temp * redux)); + temp = std::max(0.0, temp * redux); } - ac_bonus = (4 * temp) / 3; + ac_bonus = static_cast((4.0 * temp) / 3.0); } else if (weight > hardcap + 1) { - int temp = level + 5; - double multiplier = std::min(1.0, (weight - (hardcap - 10.0)) / 100.0); - temp = (4 * temp) / 3; + double temp = level + 5; + double multiplier = std::min(1.0, (weight - (static_cast(hardcap) - 10.0)) / 100.0); + temp = (4.0 * temp) / 3.0; ac_bonus -= static_cast(temp * multiplier); } } @@ -850,11 +850,12 @@ int Mob::ACSum() auto over_cap = ac - softcap; ac = softcap + (over_cap * returns); } - LogCombat("ACSum ac [{}] softcap [{}] returns [{}]", ac, softcap, returns); + LogCombatDetail("ACSum ac [{}] softcap [{}] returns [{}]", ac, softcap, returns); } else { - LogCombat("ACSum ac [{}]", ac); + LogCombatDetail("ACSum ac [{}]", ac); } + return ac; } @@ -2465,6 +2466,9 @@ bool NPC::Death(Mob* killer_mob, int32 damage, uint16 spell, EQEmu::skills::Skil entity_list.UnMarkNPC(GetID()); entity_list.RemoveNPC(GetID()); + // entity_list.RemoveMobFromCloseLists(this); + close_mobs.clear(); + this->SetID(0); if (killer != 0 && emoteid != 0) diff --git a/zone/aura.cpp b/zone/aura.cpp index 0830816c1..50026f998 100644 --- a/zone/aura.cpp +++ b/zone/aura.cpp @@ -6,8 +6,9 @@ #include "raids.h" Aura::Aura(NPCType *type_data, Mob *owner, AuraRecord &record) - : NPC(type_data, 0, owner->GetPosition(), GravityBehavior::Flying), spell_id(record.spell_id), distance(record.distance), - remove_timer(record.duration), movement_timer(100), process_timer(1000), aura_id(-1) + : NPC(type_data, 0, owner->GetPosition(), GravityBehavior::Flying), spell_id(record.spell_id), + distance(record.distance), + remove_timer(record.duration), movement_timer(100), process_timer(1000), aura_id(-1) { GiveNPCTypeData(type_data); // we will delete this later on m_owner = owner->GetID(); @@ -17,42 +18,48 @@ Aura::Aura(NPCType *type_data, Mob *owner, AuraRecord &record) cast_timer.Disable(); // we don't want to be enabled yet } - if (record.aura_type < static_cast(AuraType::Max)) + if (record.aura_type < static_cast(AuraType::Max)) { type = static_cast(record.aura_type); - else + } + else { type = AuraType::OnAllGroupMembers; + } - if (record.spawn_type < static_cast(AuraSpawns::Max)) + if (record.spawn_type < static_cast(AuraSpawns::Max)) { spawn_type = static_cast(record.spawn_type); - else + } + else { spawn_type = AuraSpawns::GroupMembers; + } - if (record.movement < static_cast(AuraMovement::Max)) + if (record.movement < static_cast(AuraMovement::Max)) { movement_type = static_cast(record.movement); - else + } + else { movement_type = AuraMovement::Follow; + } switch (type) { - case AuraType::OnAllFriendlies: - process_func = &Aura::ProcessOnAllFriendlies; - break; - case AuraType::OnAllGroupMembers: - process_func = &Aura::ProcessOnAllGroupMembers; - break; - case AuraType::OnGroupMembersPets: - process_func = &Aura::ProcessOnGroupMembersPets; - break; - case AuraType::Totem: - process_func = &Aura::ProcessTotem; - break; - case AuraType::EnterTrap: - process_func = &Aura::ProcessEnterTrap; - break; - case AuraType::ExitTrap: - process_func = &Aura::ProcessExitTrap; - break; - default: - process_func = nullptr; + case AuraType::OnAllFriendlies: + process_func = &Aura::ProcessOnAllFriendlies; + break; + case AuraType::OnAllGroupMembers: + process_func = &Aura::ProcessOnAllGroupMembers; + break; + case AuraType::OnGroupMembersPets: + process_func = &Aura::ProcessOnGroupMembersPets; + break; + case AuraType::Totem: + process_func = &Aura::ProcessTotem; + break; + case AuraType::EnterTrap: + process_func = &Aura::ProcessEnterTrap; + break; + case AuraType::ExitTrap: + process_func = &Aura::ProcessExitTrap; + break; + default: + process_func = nullptr; } } @@ -64,9 +71,9 @@ Mob *Aura::GetOwner() // not 100% sure how this one should work and PVP affects ... void Aura::ProcessOnAllFriendlies(Mob *owner) { - auto &mob_list = entity_list.GetMobList(); // read only reference so we can do it all inline + auto &mob_list = entity_list.GetCloseMobList(this, distance); std::set delayed_remove; - bool is_buff = IsBuffSpell(spell_id); // non-buff spells don't cast on enter + bool is_buff = IsBuffSpell(spell_id); // non-buff spells don't cast on enter for (auto &e : mob_list) { auto mob = e.second; @@ -74,13 +81,16 @@ void Aura::ProcessOnAllFriendlies(Mob *owner) auto it = casted_on.find(mob->GetID()); if (it != casted_on.end()) { // we are already on the list, let's check for removal - if (DistanceSquared(GetPosition(), mob->GetPosition()) > distance) + if (DistanceSquared(GetPosition(), mob->GetPosition()) > distance) { delayed_remove.insert(mob->GetID()); - } else { // not on list, lets check if we're in range + } + } + else { // not on list, lets check if we're in range if (DistanceSquared(GetPosition(), mob->GetPosition()) <= distance) { casted_on.insert(mob->GetID()); - if (is_buff) + if (is_buff) { SpellFinished(spell_id, mob); + } } } } @@ -88,30 +98,34 @@ void Aura::ProcessOnAllFriendlies(Mob *owner) for (auto &e : delayed_remove) { auto mob = entity_list.GetMob(e); - if (mob != nullptr && is_buff) // some auras cast instant spells so no need to remove + if (mob != nullptr && is_buff) { // some auras cast instant spells so no need to remove mob->BuffFadeBySpellIDAndCaster(spell_id, GetID()); + } casted_on.erase(e); } // so if we have a cast timer and our set isn't empty and timer is disabled we need to enable it - if (cast_timer.GetDuration() > 0 && !cast_timer.Enabled() && !casted_on.empty()) + if (cast_timer.GetDuration() > 0 && !cast_timer.Enabled() && !casted_on.empty()) { cast_timer.Start(); + } - if (!cast_timer.Enabled() || !cast_timer.Check()) + if (!cast_timer.Enabled() || !cast_timer.Check()) { return; + } for (auto &e : casted_on) { auto mob = entity_list.GetMob(e); - if (mob != nullptr) + if (mob != nullptr) { SpellFinished(spell_id, mob); + } } } void Aura::ProcessOnAllGroupMembers(Mob *owner) { - auto &mob_list = entity_list.GetMobList(); // read only reference so we can do it all inline + auto &mob_list = entity_list.GetCloseMobList(this, distance); std::set delayed_remove; - bool is_buff = IsBuffSpell(spell_id); // non-buff spells don't cast on enter + bool is_buff = IsBuffSpell(spell_id); // non-buff spells don't cast on enter if (owner->IsRaidGrouped() && owner->IsClient()) { // currently raids are just client, but safety check auto raid = owner->GetRaid(); @@ -126,9 +140,12 @@ void Aura::ProcessOnAllGroupMembers(Mob *owner) auto idx = raid->GetPlayerIndex(c); if (c->GetID() == m_owner) { return DistanceSquared(GetPosition(), c->GetPosition()) <= distance; - } else if (idx == 0xFFFFFFFF || raid->members[idx].GroupNumber != group_id || raid->members[idx].GroupNumber == 0xFFFFFFFF) { + } + else if (idx == 0xFFFFFFFF || raid->members[idx].GroupNumber != group_id || + raid->members[idx].GroupNumber == 0xFFFFFFFF) { return false; - } else if (DistanceSquared(GetPosition(), c->GetPosition()) > distance) { + } + else if (DistanceSquared(GetPosition(), c->GetPosition()) > distance) { return false; } return true; @@ -138,9 +155,12 @@ void Aura::ProcessOnAllGroupMembers(Mob *owner) auto idx = raid->GetPlayerIndex(m->GetOwner()->CastToClient()); if (m->GetOwner()->GetID() == m_owner) { return DistanceSquared(GetPosition(), m->GetPosition()) <= distance; - } else if (idx == 0xFFFFFFFF || raid->members[idx].GroupNumber != group_id || raid->members[idx].GroupNumber == 0xFFFFFFFF) { + } + else if (idx == 0xFFFFFFFF || raid->members[idx].GroupNumber != group_id || + raid->members[idx].GroupNumber == 0xFFFFFFFF) { return false; - } else if (DistanceSquared(GetPosition(), m->GetPosition()) > distance) { + } + else if (DistanceSquared(GetPosition(), m->GetPosition()) > distance) { return false; } return true; @@ -148,14 +168,18 @@ void Aura::ProcessOnAllGroupMembers(Mob *owner) auto verify_raid_client_swarm = [&raid, &group_id, this](NPC *n) { auto owner = entity_list.GetMob(n->GetSwarmOwner()); - if (owner == nullptr) + if (owner == nullptr) { return false; + } auto idx = raid->GetPlayerIndex(owner->CastToClient()); if (owner->GetID() == m_owner) { return DistanceSquared(GetPosition(), n->GetPosition()) <= distance; - } else if (idx == 0xFFFFFFFF || raid->members[idx].GroupNumber != group_id || raid->members[idx].GroupNumber == 0xFFFFFFFF) { + } + else if (idx == 0xFFFFFFFF || raid->members[idx].GroupNumber != group_id || + raid->members[idx].GroupNumber == 0xFFFFFFFF) { return false; - } else if (DistanceSquared(GetPosition(), n->GetPosition()) > distance) { + } + else if (DistanceSquared(GetPosition(), n->GetPosition()) > distance) { return false; } return true; @@ -164,40 +188,52 @@ void Aura::ProcessOnAllGroupMembers(Mob *owner) for (auto &e : mob_list) { auto mob = e.second; // step 1: check if we're already managing this NPC's buff - auto it = casted_on.find(mob->GetID()); + auto it = casted_on.find(mob->GetID()); if (it != casted_on.end()) { // verify still good! if (mob->IsClient()) { - if (!verify_raid_client(mob->CastToClient())) - delayed_remove.insert(mob->GetID()); - } else if (mob->IsPet() && mob->IsPetOwnerClient() && mob->GetOwner()) { - if (!verify_raid_client_pet(mob)) - delayed_remove.insert(mob->GetID()); - } else if (mob->IsNPC() && mob->IsPetOwnerClient()) { - auto npc = mob->CastToNPC(); - if (!verify_raid_client_swarm(npc)) + if (!verify_raid_client(mob->CastToClient())) { delayed_remove.insert(mob->GetID()); + } } - } else { // we're not on it! + else if (mob->IsPet() && mob->IsPetOwnerClient() && mob->GetOwner()) { + if (!verify_raid_client_pet(mob)) { + delayed_remove.insert(mob->GetID()); + } + } + else if (mob->IsNPC() && mob->IsPetOwnerClient()) { + auto npc = mob->CastToNPC(); + if (!verify_raid_client_swarm(npc)) { + delayed_remove.insert(mob->GetID()); + } + } + } + else { // we're not on it! if (mob->IsClient() && verify_raid_client(mob->CastToClient())) { casted_on.insert(mob->GetID()); - if (is_buff) + if (is_buff) { SpellFinished(spell_id, mob); - } else if (mob->IsPet() && mob->IsPetOwnerClient() && mob->GetOwner() && verify_raid_client_pet(mob)) { + } + } + else if (mob->IsPet() && mob->IsPetOwnerClient() && mob->GetOwner() && verify_raid_client_pet(mob)) { casted_on.insert(mob->GetID()); - if (is_buff) + if (is_buff) { SpellFinished(spell_id, mob); - } else if (mob->IsNPC() && mob->IsPetOwnerClient()) { + } + } + else if (mob->IsNPC() && mob->IsPetOwnerClient()) { auto npc = mob->CastToNPC(); if (verify_raid_client_swarm(npc)) { casted_on.insert(mob->GetID()); - if (is_buff) + if (is_buff) { SpellFinished(spell_id, mob); + } } } } } - } else if (owner->IsGrouped()) { + } + else if (owner->IsGrouped()) { auto group = owner->GetGroup(); if (group == nullptr) { // uh oh owner->RemoveAura(GetID(), false, true); @@ -207,107 +243,133 @@ void Aura::ProcessOnAllGroupMembers(Mob *owner) // lambdas to make for loop less ugly auto verify_group_pet = [&group, this](Mob *m) { auto owner = m->GetOwner(); - if (owner != nullptr && group->IsGroupMember(owner) && DistanceSquared(GetPosition(), m->GetPosition()) <= distance) + if (owner != nullptr && group->IsGroupMember(owner) && + DistanceSquared(GetPosition(), m->GetPosition()) <= distance) { return true; + } return false; }; auto verify_group_swarm = [&group, this](NPC *n) { auto owner = entity_list.GetMob(n->GetSwarmOwner()); - if (owner != nullptr && group->IsGroupMember(owner) && DistanceSquared(GetPosition(), n->GetPosition()) <= distance) + if (owner != nullptr && group->IsGroupMember(owner) && + DistanceSquared(GetPosition(), n->GetPosition()) <= distance) { return true; + } return false; }; for (auto &e : mob_list) { auto mob = e.second; - auto it = casted_on.find(mob->GetID()); + auto it = casted_on.find(mob->GetID()); if (it != casted_on.end()) { // make sure we're still valid if (mob->IsPet()) { - if (!verify_group_pet(mob)) + if (!verify_group_pet(mob)) { delayed_remove.insert(mob->GetID()); - } else if (mob->IsNPC() && mob->CastToNPC()->GetSwarmInfo()) { - if (!verify_group_swarm(mob->CastToNPC())) + } + } + else if (mob->IsNPC() && mob->CastToNPC()->GetSwarmInfo()) { + if (!verify_group_swarm(mob->CastToNPC())) { delayed_remove.insert(mob->GetID()); - } else if (!group->IsGroupMember(mob) || DistanceSquared(GetPosition(), mob->GetPosition()) > distance) { + } + } + else if (!group->IsGroupMember(mob) || DistanceSquared(GetPosition(), mob->GetPosition()) > distance) { delayed_remove.insert(mob->GetID()); } - } else { // not on, check if we should be! + } + else { // not on, check if we should be! if (mob->IsPet() && verify_group_pet(mob)) { casted_on.insert(mob->GetID()); - if (is_buff) + if (is_buff) { SpellFinished(spell_id, mob); - } else if (mob->IsNPC() && mob->CastToNPC()->GetSwarmInfo() && verify_group_swarm(mob->CastToNPC())) { + } + } + else if (mob->IsNPC() && mob->CastToNPC()->GetSwarmInfo() && verify_group_swarm(mob->CastToNPC())) { casted_on.insert(mob->GetID()); - if (is_buff) + if (is_buff) { SpellFinished(spell_id, mob); - } else if (group->IsGroupMember(mob) && DistanceSquared(GetPosition(), mob->GetPosition()) <= distance) { + } + } + else if (group->IsGroupMember(mob) && DistanceSquared(GetPosition(), mob->GetPosition()) <= distance) { casted_on.insert(mob->GetID()); - if (is_buff) + if (is_buff) { SpellFinished(spell_id, mob); + } } } } - } else { - auto verify_solo = [&owner, this](Mob *m) { - if (m->IsPet() && m->GetOwnerID() == owner->GetID()) + } + else { + auto verify_solo = [&owner, this](Mob *m) { + if (m->IsPet() && m->GetOwnerID() == owner->GetID()) { return true; - else if (m->IsNPC() && m->CastToNPC()->GetSwarmOwner() == owner->GetID()) + } + else if (m->IsNPC() && m->CastToNPC()->GetSwarmOwner() == owner->GetID()) { return true; - else if (m->GetID() == owner->GetID()) + } + else if (m->GetID() == owner->GetID()) { return true; - else + } + else { return false; + } }; for (auto &e : mob_list) { - auto mob = e.second; - auto it = casted_on.find(mob->GetID()); + auto mob = e.second; + auto it = casted_on.find(mob->GetID()); bool good = verify_solo(mob); if (it != casted_on.end()) { // make sure still valid if (!good || DistanceSquared(GetPosition(), mob->GetPosition()) > distance) { delayed_remove.insert(mob->GetID()); } - } else if (good && DistanceSquared(GetPosition(), mob->GetPosition()) <= distance) { + } + else if (good && DistanceSquared(GetPosition(), mob->GetPosition()) <= distance) { casted_on.insert(mob->GetID()); - if (is_buff) + if (is_buff) { SpellFinished(spell_id, mob); + } } } } for (auto &e : delayed_remove) { auto mob = entity_list.GetMob(e); - if (mob != nullptr && is_buff) // some auras cast instant spells so no need to remove + if (mob != nullptr && is_buff) { // some auras cast instant spells so no need to remove mob->BuffFadeBySpellIDAndCaster(spell_id, GetID()); + } casted_on.erase(e); } // so if we have a cast timer and our set isn't empty and timer is disabled we need to enable it - if (cast_timer.GetDuration() > 0 && !cast_timer.Enabled() && !casted_on.empty()) + if (cast_timer.GetDuration() > 0 && !cast_timer.Enabled() && !casted_on.empty()) { cast_timer.Start(); + } - if (!cast_timer.Enabled() || !cast_timer.Check()) + if (!cast_timer.Enabled() || !cast_timer.Check()) { return; + } // some auras have to recast (DRU for example, non-buff too) for (auto &e : casted_on) { auto mob = entity_list.GetMob(e); - if (mob != nullptr) + if (mob != nullptr) { SpellFinished(spell_id, mob); + } } } void Aura::ProcessOnGroupMembersPets(Mob *owner) { - auto &mob_list = entity_list.GetMobList(); // read only reference so we can do it all inline + auto &mob_list = entity_list.GetCloseMobList(this,distance); std::set delayed_remove; - bool is_buff = IsBuffSpell(spell_id); // non-buff spells don't cast on enter + bool is_buff = IsBuffSpell(spell_id); // non-buff spells don't cast on enter // This type can either live on the pet (level 55/70 MAG aura) or on the pet owner (level 85 MAG aura) - auto group_member = owner->GetOwnerOrSelf(); + auto group_member = owner->GetOwnerOrSelf(); - if (group_member->IsRaidGrouped() && group_member->IsClient()) { // currently raids are just client, but safety check + if (group_member->IsRaidGrouped() && + group_member->IsClient()) { // currently raids are just client, but safety check auto raid = group_member->GetRaid(); if (raid == nullptr) { // well shit owner->RemoveAura(GetID(), false, true); @@ -320,9 +382,12 @@ void Aura::ProcessOnGroupMembersPets(Mob *owner) auto idx = raid->GetPlayerIndex(m->GetOwner()->CastToClient()); if (m->GetOwner()->GetID() == group_member->GetID()) { return DistanceSquared(GetPosition(), m->GetPosition()) <= distance; - } else if (idx == 0xFFFFFFFF || raid->members[idx].GroupNumber != group_id || raid->members[idx].GroupNumber == 0xFFFFFFFF) { + } + else if (idx == 0xFFFFFFFF || raid->members[idx].GroupNumber != group_id || + raid->members[idx].GroupNumber == 0xFFFFFFFF) { return false; - } else if (DistanceSquared(GetPosition(), m->GetPosition()) > distance) { + } + else if (DistanceSquared(GetPosition(), m->GetPosition()) > distance) { return false; } return true; @@ -330,14 +395,18 @@ void Aura::ProcessOnGroupMembersPets(Mob *owner) auto verify_raid_client_swarm = [&raid, &group_id, &group_member, this](NPC *n) { auto owner = entity_list.GetMob(n->GetSwarmOwner()); - if (owner == nullptr) + if (owner == nullptr) { return false; + } auto idx = raid->GetPlayerIndex(owner->CastToClient()); if (owner->GetID() == group_member->GetID()) { return DistanceSquared(GetPosition(), n->GetPosition()) <= distance; - } else if (idx == 0xFFFFFFFF || raid->members[idx].GroupNumber != group_id || raid->members[idx].GroupNumber == 0xFFFFFFFF) { + } + else if (idx == 0xFFFFFFFF || raid->members[idx].GroupNumber != group_id || + raid->members[idx].GroupNumber == 0xFFFFFFFF) { return false; - } else if (DistanceSquared(GetPosition(), n->GetPosition()) > distance) { + } + else if (DistanceSquared(GetPosition(), n->GetPosition()) > distance) { return false; } return true; @@ -346,35 +415,44 @@ void Aura::ProcessOnGroupMembersPets(Mob *owner) for (auto &e : mob_list) { auto mob = e.second; // step 1: check if we're already managing this NPC's buff - auto it = casted_on.find(mob->GetID()); + auto it = casted_on.find(mob->GetID()); if (it != casted_on.end()) { // verify still good! if (mob->IsPet() && mob->IsPetOwnerClient() && mob->GetOwner()) { - if (!verify_raid_client_pet(mob)) - delayed_remove.insert(mob->GetID()); - } else if (mob->IsNPC() && mob->IsPetOwnerClient()) { - auto npc = mob->CastToNPC(); - if (!verify_raid_client_swarm(npc)) + if (!verify_raid_client_pet(mob)) { delayed_remove.insert(mob->GetID()); + } } - } else { // we're not on it! + else if (mob->IsNPC() && mob->IsPetOwnerClient()) { + auto npc = mob->CastToNPC(); + if (!verify_raid_client_swarm(npc)) { + delayed_remove.insert(mob->GetID()); + } + } + } + else { // we're not on it! if (mob->IsClient()) { continue; // never hit client - } else if (mob->IsPet() && mob->IsPetOwnerClient() && mob->GetOwner() && verify_raid_client_pet(mob)) { + } + else if (mob->IsPet() && mob->IsPetOwnerClient() && mob->GetOwner() && verify_raid_client_pet(mob)) { casted_on.insert(mob->GetID()); - if (is_buff) + if (is_buff) { SpellFinished(spell_id, mob); - } else if (mob->IsNPC() && mob->IsPetOwnerClient()) { + } + } + else if (mob->IsNPC() && mob->IsPetOwnerClient()) { auto npc = mob->CastToNPC(); if (verify_raid_client_swarm(npc)) { casted_on.insert(mob->GetID()); - if (is_buff) + if (is_buff) { SpellFinished(spell_id, mob); + } } } } } - } else if (group_member->IsGrouped()) { + } + else if (group_member->IsGrouped()) { auto group = group_member->GetGroup(); if (group == nullptr) { // uh oh owner->RemoveAura(GetID(), false, true); @@ -384,111 +462,131 @@ void Aura::ProcessOnGroupMembersPets(Mob *owner) // lambdas to make for loop less ugly auto verify_group_pet = [&group, this](Mob *m) { auto owner = m->GetOwner(); - if (owner != nullptr && group->IsGroupMember(owner) && DistanceSquared(GetPosition(), m->GetPosition()) <= distance) - return true; - return false; + return owner != nullptr && group->IsGroupMember(owner) && + DistanceSquared(GetPosition(), m->GetPosition()) <= distance; }; auto verify_group_swarm = [&group, this](NPC *n) { auto owner = entity_list.GetMob(n->GetSwarmOwner()); - if (owner != nullptr && group->IsGroupMember(owner) && DistanceSquared(GetPosition(), n->GetPosition()) <= distance) - return true; - return false; + return owner != nullptr && group->IsGroupMember(owner) && + DistanceSquared(GetPosition(), n->GetPosition()) <= distance; }; for (auto &e : mob_list) { auto mob = e.second; - auto it = casted_on.find(mob->GetID()); + auto it = casted_on.find(mob->GetID()); if (it != casted_on.end()) { // make sure we're still valid if (mob->IsPet()) { - if (!verify_group_pet(mob)) - delayed_remove.insert(mob->GetID()); - } else if (mob->IsNPC() && mob->CastToNPC()->GetSwarmInfo()) { - if (!verify_group_swarm(mob->CastToNPC())) + if (!verify_group_pet(mob)) { delayed_remove.insert(mob->GetID()); + } } - } else { // not on, check if we should be! + else if (mob->IsNPC() && mob->CastToNPC()->GetSwarmInfo()) { + if (!verify_group_swarm(mob->CastToNPC())) { + delayed_remove.insert(mob->GetID()); + } + } + } + else { // not on, check if we should be! if (mob->IsClient()) { continue; - } else if (mob->IsPet() && verify_group_pet(mob)) { + } + else if (mob->IsPet() && verify_group_pet(mob)) { casted_on.insert(mob->GetID()); - if (is_buff) + if (is_buff) { SpellFinished(spell_id, mob); - } else if (mob->IsNPC() && mob->CastToNPC()->GetSwarmInfo() && verify_group_swarm(mob->CastToNPC())) { + } + } + else if (mob->IsNPC() && mob->CastToNPC()->GetSwarmInfo() && verify_group_swarm(mob->CastToNPC())) { casted_on.insert(mob->GetID()); - if (is_buff) + if (is_buff) { SpellFinished(spell_id, mob); + } } } } - } else { - auto verify_solo = [&group_member, this](Mob *m) { - if (m->IsPet() && m->GetOwnerID() == group_member->GetID()) + } + else { + auto verify_solo = [&group_member, this](Mob *m) { + if (m->IsPet() && m->GetOwnerID() == group_member->GetID()) { return true; - else if (m->IsNPC() && m->CastToNPC()->GetSwarmOwner() == group_member->GetID()) + } + else if (m->IsNPC() && m->CastToNPC()->GetSwarmOwner() == group_member->GetID()) { return true; - else + } + else { return false; + } }; for (auto &e : mob_list) { - auto mob = e.second; - auto it = casted_on.find(mob->GetID()); + auto mob = e.second; + auto it = casted_on.find(mob->GetID()); bool good = verify_solo(mob); if (it != casted_on.end()) { // make sure still valid if (!good || DistanceSquared(GetPosition(), mob->GetPosition()) > distance) { delayed_remove.insert(mob->GetID()); } - } else if (good && DistanceSquared(GetPosition(), mob->GetPosition()) <= distance) { + } + else if (good && DistanceSquared(GetPosition(), mob->GetPosition()) <= distance) { casted_on.insert(mob->GetID()); - if (is_buff) + if (is_buff) { SpellFinished(spell_id, mob); + } } } } for (auto &e : delayed_remove) { auto mob = entity_list.GetMob(e); - if (mob != nullptr && is_buff) // some auras cast instant spells so no need to remove + if (mob != nullptr && is_buff) { // some auras cast instant spells so no need to remove mob->BuffFadeBySpellIDAndCaster(spell_id, GetID()); + } casted_on.erase(e); } // so if we have a cast timer and our set isn't empty and timer is disabled we need to enable it - if (cast_timer.GetDuration() > 0 && !cast_timer.Enabled() && !casted_on.empty()) + if (cast_timer.GetDuration() > 0 && !cast_timer.Enabled() && !casted_on.empty()) { cast_timer.Start(); + } - if (!cast_timer.Enabled() || !cast_timer.Check()) + if (!cast_timer.Enabled() || !cast_timer.Check()) { return; + } // some auras have to recast (DRU for example, non-buff too) for (auto &e : casted_on) { auto mob = entity_list.GetMob(e); - if (mob != nullptr) + if (mob != nullptr) { SpellFinished(spell_id, mob); + } } } void Aura::ProcessTotem(Mob *owner) { - auto &mob_list = entity_list.GetMobList(); // read only reference so we can do it all inline + auto &mob_list = entity_list.GetCloseMobList(this, distance); std::set delayed_remove; - bool is_buff = IsBuffSpell(spell_id); // non-buff spells don't cast on enter + bool is_buff = IsBuffSpell(spell_id); // non-buff spells don't cast on enter for (auto &e : mob_list) { auto mob = e.second; - if (mob == this) + if (mob == this) { continue; - if (mob == owner) + } + if (mob == owner) { continue; + } if (owner->IsAttackAllowed(mob)) { // might need more checks ... bool in_range = DistanceSquared(GetPosition(), mob->GetPosition()) <= distance; - auto it = casted_on.find(mob->GetID()); + auto it = casted_on.find(mob->GetID()); if (it != casted_on.end()) { - if (!in_range) + if (!in_range) { delayed_remove.insert(mob->GetID()); - } else if (in_range) { + } + } + else if (in_range) { casted_on.insert(mob->GetID()); SpellFinished(spell_id, mob); } @@ -497,33 +595,38 @@ void Aura::ProcessTotem(Mob *owner) for (auto &e : delayed_remove) { auto mob = entity_list.GetMob(e); - if (mob != nullptr && is_buff) // some auras cast instant spells so no need to remove + if (mob != nullptr && is_buff) { // some auras cast instant spells so no need to remove mob->BuffFadeBySpellIDAndCaster(spell_id, GetID()); + } casted_on.erase(e); } // so if we have a cast timer and our set isn't empty and timer is disabled we need to enable it - if (cast_timer.GetDuration() > 0 && !cast_timer.Enabled() && !casted_on.empty()) + if (cast_timer.GetDuration() > 0 && !cast_timer.Enabled() && !casted_on.empty()) { cast_timer.Start(); + } - if (!cast_timer.Enabled() || !cast_timer.Check()) + if (!cast_timer.Enabled() || !cast_timer.Check()) { return; + } for (auto &e : casted_on) { auto mob = entity_list.GetMob(e); - if (mob != nullptr) + if (mob != nullptr) { SpellFinished(spell_id, mob); + } } } void Aura::ProcessEnterTrap(Mob *owner) { - auto &mob_list = entity_list.GetMobList(); // read only reference so we can do it all inline + auto &mob_list = entity_list.GetCloseMobList(this, distance); for (auto &e : mob_list) { auto mob = e.second; - if (mob == this) + if (mob == this) { continue; + } // might need more checks ... if (owner->IsAttackAllowed(mob) && DistanceSquared(GetPosition(), mob->GetPosition()) <= distance) { SpellFinished(spell_id, mob); @@ -535,23 +638,25 @@ void Aura::ProcessEnterTrap(Mob *owner) void Aura::ProcessExitTrap(Mob *owner) { - auto &mob_list = entity_list.GetMobList(); // read only reference so we can do it all inline + auto &mob_list = entity_list.GetCloseMobList(this, distance); for (auto &e : mob_list) { auto mob = e.second; - if (mob == this) + if (mob == this) { continue; + } // might need more checks ... if (owner->IsAttackAllowed(mob)) { bool in_range = DistanceSquared(GetPosition(), mob->GetPosition()) <= distance; - auto it = casted_on.find(mob->GetID()); + auto it = casted_on.find(mob->GetID()); if (it != casted_on.end()) { if (!in_range) { SpellFinished(spell_id, mob); owner->RemoveAura(GetID(), false); // if we're a buff we don't want to strip :P break; } - } else if (in_range) { + } + else if (in_range) { casted_on.insert(mob->GetID()); } } @@ -562,9 +667,14 @@ void Aura::ProcessExitTrap(Mob *owner) // and hard to reason about void Aura::ProcessSpawns() { - const auto &clients = entity_list.GetClientList(); - for (auto &e : clients) { - auto c = e.second; + const auto &clients = entity_list.GetCloseMobList(this, distance); + for (auto &e : clients) { + if (!e.second->IsClient()) { + continue; + } + + auto c = e.second->CastToClient(); + bool spawned = spawned_for.find(c->GetID()) != spawned_for.end(); if (ShouldISpawnFor(c)) { if (!spawned) { @@ -574,21 +684,22 @@ void Aura::ProcessSpawns() SendArmorAppearance(c); spawned_for.insert(c->GetID()); } - } else if (spawned) { + } + else if (spawned) { EQApplicationPacket app; CreateDespawnPacket(&app, false); c->QueuePacket(&app); spawned_for.erase(c->GetID()); } } - return; } bool Aura::Process() { // Aura::Depop clears buffs - if (p_depop) + if (p_depop) { return false; + } auto owner = entity_list.GetMob(m_owner); if (owner == nullptr) { @@ -604,7 +715,7 @@ bool Aura::Process() if (movement_type == AuraMovement::Follow && GetPosition() != owner->GetPosition() && movement_timer.Check()) { m_Position = owner->GetPosition(); auto app = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct)); - auto spu = (PlayerPositionUpdateServer_Struct*)app->pBuffer; + auto spu = (PlayerPositionUpdateServer_Struct *) app->pBuffer; MakeSpawnUpdate(spu); auto it = spawned_for.begin(); while (it != spawned_for.end()) { @@ -612,7 +723,8 @@ bool Aura::Process() if (client) { client->QueuePacket(app); ++it; - } else { + } + else { it = spawned_for.erase(it); } } @@ -620,14 +732,17 @@ bool Aura::Process() } // TODO: waypoints? - if (!process_timer.Check()) + if (!process_timer.Check()) { return true; + } - if (spawn_type != AuraSpawns::Noone) - ProcessSpawns(); // bit of a hack + if (spawn_type != AuraSpawns::Noone) { + ProcessSpawns(); + } // bit of a hack - if (process_func) + if (process_func) { process_func(*this, owner); + } // TODO: quest calls return true; @@ -635,49 +750,61 @@ bool Aura::Process() bool Aura::ShouldISpawnFor(Client *c) { - if (spawn_type == AuraSpawns::Noone) + if (spawn_type == AuraSpawns::Noone) { return false; + } - if (spawn_type == AuraSpawns::Everyone) + if (spawn_type == AuraSpawns::Everyone) { return true; + } // hey, it's our owner! - if (c->GetID() == m_owner) + if (c->GetID() == m_owner) { return true; + } // so this one is a bit trickier auto owner = GetOwner(); - if (owner == nullptr) - return false; // hmm + if (owner == nullptr) { + return false; + } // hmm owner = owner->GetOwnerOrSelf(); // pet auras we need the pet's owner - if (owner == nullptr) // shouldn't really be needed + if (owner == nullptr) { // shouldn't really be needed return false; + } // gotta check again for pet aura case -.- - if (owner == c) + if (owner == c) { return true; + } if (owner->IsRaidGrouped() && owner->IsClient()) { auto raid = owner->GetRaid(); - if (raid == nullptr) - return false; // hmm - auto group_id = raid->GetGroup(owner->CastToClient()); - if (group_id == 0xFFFFFFFF) // owner handled above, and they're in a raid and groupless + if (raid == nullptr) { return false; + } // hmm + auto group_id = raid->GetGroup(owner->CastToClient()); + if (group_id == 0xFFFFFFFF) { // owner handled above, and they're in a raid and groupless + return false; + } auto idx = raid->GetPlayerIndex(c); - if (idx == 0xFFFFFFFF) // they're not in our raid! + if (idx == 0xFFFFFFFF) { // they're not in our raid! return false; + } - if (raid->members[idx].GroupNumber != group_id) // in our raid, but not our group + if (raid->members[idx].GroupNumber != group_id) { // in our raid, but not our group return false; + } return true; // we got here so we know that 1 they're in our raid and 2 they're in our group! - } else if (owner->IsGrouped()) { + } + else if (owner->IsGrouped()) { auto group = owner->GetGroup(); - if (group == nullptr) - return false; // hmm + if (group == nullptr) { + return false; + } // hmm // easy, in our group return group->IsGroupMember(c); @@ -693,22 +820,23 @@ void Aura::Depop(bool skip_strip) if (!skip_strip && IsBuffSpell(spell_id)) { for (auto &e : casted_on) { auto mob = entity_list.GetMob(e); - if (mob != nullptr) + if (mob != nullptr) { mob->BuffFadeBySpellIDAndCaster(spell_id, GetID()); + } } } casted_on.clear(); p_depop = true; } -// This creates an aura from a casted spell void Mob::MakeAura(uint16 spell_id) { // TODO: verify room in AuraMgr - if (!IsValidSpell(spell_id)) + if (!IsValidSpell(spell_id)) { return; + } - AuraRecord record; + AuraRecord record{}; if (!database.GetAuraEntry(spell_id, record)) { Message(Chat::Red, "Unable to find data for aura %s", spells[spell_id].name); LogError("Unable to find data for aura [{}], check auras table", spell_id); @@ -718,7 +846,7 @@ void Mob::MakeAura(uint16 spell_id) if (!IsValidSpell(record.spell_id)) { Message(Chat::Red, "Casted spell (%d) is not valid for aura %s", record.spell_id, spells[spell_id].name); LogError("Casted spell ([{}]) is not valid for aura [{}], check auras table", - record.spell_id, spell_id); + record.spell_id, spell_id); return; } @@ -729,23 +857,28 @@ void Mob::MakeAura(uint16 spell_id) bool trap = false; switch (static_cast(record.aura_type)) { - case AuraType::ExitTrap: - case AuraType::EnterTrap: - case AuraType::Totem: - trap = true; - break; - default: - trap = false; - break; + case AuraType::ExitTrap: + case AuraType::EnterTrap: + case AuraType::Totem: + trap = true; + break; + default: + trap = false; + break; } - if (!CanSpawnAura(trap)) + if (!CanSpawnAura(trap)) { return; + } const auto base = database.LoadNPCTypesData(record.npc_type); if (base == nullptr) { Message(Chat::Red, "Unable to load NPC data for aura %s", spells[spell_id].teleport_zone); - LogError("Unable to load NPC data for aura [{}] (NPC ID [{}]), check auras and npc_types tables", spells[spell_id].teleport_zone, record.npc_type); + LogError( + "Unable to load NPC data for aura [{}] (NPC ID [{}]), check auras and npc_types tables", + spells[spell_id].teleport_zone, + record.npc_type + ); return; } @@ -756,65 +889,82 @@ void Mob::MakeAura(uint16 spell_id) auto npc = new Aura(npc_type, this, record); npc->SetAuraID(spell_id); - if (trap) - npc->TryMoveAlong(5.0f, 0.0f, false); // try to place 5 units in front + if (trap) { + npc->TryMoveAlong(5.0f, 0.0f, false); + } // try to place 5 units in front entity_list.AddNPC(npc, false); - if (trap) + if (trap) { AddTrap(npc, record); - else + } + else { AddAura(npc, record); + } } bool ZoneDatabase::GetAuraEntry(uint16 spell_id, AuraRecord &record) { - auto query = StringFormat("SELECT npc_type, name, spell_id, distance, aura_type, spawn_type, movement, " - "duration, icon, cast_time FROM auras WHERE type='%d'", - spell_id); + auto query = StringFormat( + "SELECT npc_type, name, spell_id, distance, aura_type, spawn_type, movement, " + "duration, icon, cast_time FROM auras WHERE type='%d'", + spell_id + ); auto results = QueryDatabase(query); - if (!results.Success()) + if (!results.Success()) { return false; + } - if (results.RowCount() != 1) + if (results.RowCount() != 1) { return false; + } auto row = results.begin(); record.npc_type = atoi(row[0]); strn0cpy(record.name, row[1], 64); - record.spell_id = atoi(row[2]); - record.distance = atoi(row[3]); + record.spell_id = atoi(row[2]); + record.distance = atoi(row[3]); record.distance *= record.distance; // so we can avoid sqrt - record.aura_type = atoi(row[4]); + record.aura_type = atoi(row[4]); record.spawn_type = atoi(row[5]); - record.movement = atoi(row[6]); - record.duration = atoi(row[7]) * 1000; // DB is in seconds - record.icon = atoi(row[8]); - record.cast_time = atoi(row[9]) * 1000; // DB is in seconds + record.movement = atoi(row[6]); + record.duration = atoi(row[7]) * 1000; // DB is in seconds + record.icon = atoi(row[8]); + record.cast_time = atoi(row[9]) * 1000; // DB is in seconds return true; } void Mob::AddAura(Aura *aura, AuraRecord &record) { + LogAura( + "[AddAura] aura owner [{}] spawn_id [{}] aura_name [{}]", + GetCleanName(), + aura->GetID(), + aura->GetCleanName() + ); + // this is called only when it's safe assert(aura != nullptr); strn0cpy(aura_mgr.auras[aura_mgr.count].name, aura->GetCleanName(), 64); aura_mgr.auras[aura_mgr.count].spawn_id = aura->GetID(); - aura_mgr.auras[aura_mgr.count].aura = aura; - if (record.icon == -1) + aura_mgr.auras[aura_mgr.count].aura = aura; + if (record.icon == -1) { aura_mgr.auras[aura_mgr.count].icon = spells[record.spell_id].new_icon; - else + } + else { aura_mgr.auras[aura_mgr.count].icon = record.icon; + } + if (IsClient()) { - auto outapp = new EQApplicationPacket(OP_UpdateAura, sizeof(AuraCreate_Struct)); - auto aura_create = (AuraCreate_Struct *)outapp->pBuffer; + auto outapp = new EQApplicationPacket(OP_UpdateAura, sizeof(AuraCreate_Struct)); + auto aura_create = (AuraCreate_Struct *) outapp->pBuffer; aura_create->action = 0; - aura_create->type = 1; // this can be 0 sometimes too + aura_create->type = 1; // this can be 0 sometimes too strn0cpy(aura_create->aura_name, aura_mgr.auras[aura_mgr.count].name, 64); aura_create->entity_id = aura_mgr.auras[aura_mgr.count].spawn_id; - aura_create->icon = aura_mgr.auras[aura_mgr.count].icon; + aura_create->icon = aura_mgr.auras[aura_mgr.count].icon; CastToClient()->FastQueuePacket(&outapp); } // we can increment this now @@ -823,15 +973,24 @@ void Mob::AddAura(Aura *aura, AuraRecord &record) void Mob::AddTrap(Aura *aura, AuraRecord &record) { + LogAura( + "[AddTrap] aura owner [{}] spawn_id [{}] aura_name [{}]", + GetCleanName(), + aura->GetID(), + aura->GetCleanName() + ); + // this is called only when it's safe assert(aura != nullptr); strn0cpy(trap_mgr.auras[trap_mgr.count].name, aura->GetCleanName(), 64); trap_mgr.auras[trap_mgr.count].spawn_id = aura->GetID(); - trap_mgr.auras[trap_mgr.count].aura = aura; - if (record.icon == -1) + trap_mgr.auras[trap_mgr.count].aura = aura; + if (record.icon == -1) { trap_mgr.auras[trap_mgr.count].icon = spells[record.spell_id].new_icon; - else + } + else { trap_mgr.auras[trap_mgr.count].icon = record.icon; + } // doesn't send to client trap_mgr.count++; } @@ -841,7 +1000,8 @@ bool Mob::CanSpawnAura(bool trap) if (trap && !HasFreeTrapSlots()) { MessageString(Chat::SpellFailure, NO_MORE_TRAPS); return false; - } else if (!trap && !HasFreeAuraSlots()) { + } + else if (!trap && !HasFreeAuraSlots()) { MessageString(Chat::SpellFailure, NO_MORE_AURAS); return false; } @@ -861,8 +1021,16 @@ void Mob::RemoveAllAuras() // this is sent on camp/zone, so it just despawns? if (aura_mgr.count) { for (auto &e : aura_mgr.auras) { - if (e.aura) + if (e.aura) { + LogAura( + "[RemoveAllAuras] aura owner [{}] spawn_id [{}] aura_name [{}]", + GetCleanName(), + e.spawn_id, + e.name + ); + e.aura->Depop(); + } } } @@ -870,8 +1038,16 @@ void Mob::RemoveAllAuras() if (trap_mgr.count) { for (auto &e : trap_mgr.auras) { - if (e.aura) + if (e.aura) { + LogAura( + "[RemoveAllAuras] trap owner [{}] spawn_id [{}] aura_name [{}]", + GetCleanName(), + e.spawn_id, + e.name + ); + e.aura->Depop(); + } } } @@ -883,24 +1059,36 @@ void Mob::RemoveAura(int spawn_id, bool skip_strip, bool expired) for (int i = 0; i < aura_mgr.count; ++i) { auto &aura = aura_mgr.auras[i]; if (aura.spawn_id == spawn_id) { - if (aura.aura) + LogAura( + "[RemoveAura] mob [{}] spawn_id [{}] skip_strip [{}] expired [{}]", + GetCleanName(), + spawn_id, + skip_strip ? "true" : "false", + expired ? "true" : "false" + ); + + if (aura.aura) { aura.aura->Depop(skip_strip); + } if (expired && IsClient()) { + // TODO: verify color CastToClient()->SendColoredText( - Chat::Yellow, StringFormat("%s has expired.", aura.name)); // TODO: verify color + Chat::Yellow, + StringFormat("%s has expired.", aura.name) + ); // need to update client UI too auto app = new EQApplicationPacket(OP_UpdateAura, sizeof(AuraDestory_Struct)); - auto ads = (AuraDestory_Struct *)app->pBuffer; - ads->action = 1; // delete + auto ads = (AuraDestory_Struct *) app->pBuffer; + ads->action = 1; // delete ads->entity_id = spawn_id; CastToClient()->QueuePacket(app); safe_delete(app); } while (aura_mgr.count - 1 > i) { i++; - aura.spawn_id = aura_mgr.auras[i].spawn_id; - aura.icon = aura_mgr.auras[i].icon; - aura.aura = aura_mgr.auras[i].aura; + aura.spawn_id = aura_mgr.auras[i].spawn_id; + aura.icon = aura_mgr.auras[i].icon; + aura.aura = aura_mgr.auras[i].aura; aura_mgr.auras[i].aura = nullptr; strn0cpy(aura.name, aura_mgr.auras[i].name, 64); } @@ -912,16 +1100,18 @@ void Mob::RemoveAura(int spawn_id, bool skip_strip, bool expired) for (int i = 0; i < trap_mgr.count; ++i) { auto &aura = trap_mgr.auras[i]; if (aura.spawn_id == spawn_id) { - if (aura.aura) + if (aura.aura) { aura.aura->Depop(skip_strip); - if (expired && IsClient()) + } + if (expired && IsClient()) { CastToClient()->SendColoredText( - Chat::Yellow, StringFormat("%s has expired.", aura.name)); // TODO: verify color + Chat::Yellow, StringFormat("%s has expired.", aura.name)); + } // TODO: verify color while (trap_mgr.count - 1 > i) { i++; - aura.spawn_id = trap_mgr.auras[i].spawn_id; - aura.icon = trap_mgr.auras[i].icon; - aura.aura = trap_mgr.auras[i].aura; + aura.spawn_id = trap_mgr.auras[i].spawn_id; + aura.icon = trap_mgr.auras[i].icon; + aura.aura = trap_mgr.auras[i].aura; trap_mgr.auras[i].aura = nullptr; strn0cpy(aura.name, trap_mgr.auras[i].name, 64); } @@ -930,6 +1120,5 @@ void Mob::RemoveAura(int spawn_id, bool skip_strip, bool expired) } } - return; } diff --git a/zone/aura.h b/zone/aura.h index ff4f2d51c..ae4cd0a4a 100644 --- a/zone/aura.h +++ b/zone/aura.h @@ -73,7 +73,7 @@ private: int m_owner; int aura_id; // spell ID of the aura spell -1 if aura isn't from a casted spell int spell_id; // spell we cast - int distance; // distance we remove + float distance; // distance we remove Timer remove_timer; // when we depop Timer process_timer; // rate limit process calls Timer cast_timer; // some auras pulse diff --git a/zone/bot.cpp b/zone/bot.cpp index b9e329292..5579b4bc5 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -83,6 +83,7 @@ Bot::Bot(NPCType *npcTypeData, Client* botOwner) : NPC(npcTypeData, nullptr, glm SetPauseAI(false); m_alt_combat_hate_timer.Start(250); + m_auto_defend_timer.Disable(); //m_combat_jitter_timer.Disable(); //SetCombatJitterFlag(false); SetGuardFlag(false); @@ -180,6 +181,7 @@ Bot::Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double to SetPauseAI(false); m_alt_combat_hate_timer.Start(250); + m_auto_defend_timer.Disable(); //m_combat_jitter_timer.Disable(); //SetCombatJitterFlag(false); SetGuardFlag(false); @@ -236,8 +238,157 @@ Bot::Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double to LoadAAs(); - if (!database.botdb.LoadBuffs(this) && bot_owner) + // copied from client CompleteConnect() handler - watch for problems + // (may have to move to post-spawn location if certain buffs still don't process correctly) + if (database.botdb.LoadBuffs(this) && bot_owner) { + + //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) // WTF IS THIS + { + if (GetRace() == 128 || GetRace() == 130 || GetRace() <= 12) + SendIllusionPacket(GetRace(), GetGender(), spell.base2[x1], spell.max[x1]); + } + else if (spell.max[x1] > 0) + { + SendIllusionPacket(spell.base[x1], 0xFF, spell.base2[x1], spell.max[x1]); + } + else + { + SendIllusionPacket(spell.base[x1], 0xFF, 0xFF, 0xFF); + } + switch (spell.base[x1]) { + case OGRE: + SendAppearancePacket(AT_Size, 9); + break; + case TROLL: + SendAppearancePacket(AT_Size, 8); + break; + case VAHSHIR: + case BARBARIAN: + SendAppearancePacket(AT_Size, 7); + break; + case HALF_ELF: + case WOOD_ELF: + case DARK_ELF: + case FROGLOK: + SendAppearancePacket(AT_Size, 5); + break; + case DWARF: + SendAppearancePacket(AT_Size, 4); + break; + case HALFLING: + case GNOME: + SendAppearancePacket(AT_Size, 3); + break; + default: + SendAppearancePacket(AT_Size, 6); + break; + } + 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(Chat::Red, "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, buffs[j1].casterlevel); + 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; + } + } + } + } + + + } + else { bot_owner->Message(Chat::Red, "&s for '%s'", BotDatabase::fail::LoadBuffs(), GetCleanName()); + } CalcBotStats(false); hp_regen = CalcHPRegen(); @@ -282,15 +433,61 @@ void Bot::SetBotSpellID(uint32 newSpellID) { } void Bot::SetSurname(std::string bot_surname) { + _surname = bot_surname.substr(0, 31); + + if (spawned) { + + auto outapp = new EQApplicationPacket(OP_GMLastName, sizeof(GMLastName_Struct)); + GMLastName_Struct* gmn = (GMLastName_Struct*)outapp->pBuffer; + + strcpy(gmn->name, GetCleanName()); + strcpy(gmn->gmname, GetCleanName()); + strcpy(gmn->lastname, GetSurname().c_str()); + gmn->unknown[0] = 1; + gmn->unknown[1] = 1; + gmn->unknown[2] = 1; + gmn->unknown[3] = 1; + + entity_list.QueueClients(this, outapp); + safe_delete(outapp); + } } void Bot::SetTitle(std::string bot_title) { - _title = bot_title.substr(0, 31); + + _title = bot_title.substr(0, 31); + + if (spawned) { + + auto outapp = new EQApplicationPacket(OP_SetTitleReply, sizeof(SetTitleReply_Struct)); + SetTitleReply_Struct* strs = (SetTitleReply_Struct*)outapp->pBuffer; + + strs->is_suffix = 0; + strn0cpy(strs->title, _title.c_str(), sizeof(strs->title)); + strs->entity_id = GetID(); + + entity_list.QueueClients(this, outapp, false); + safe_delete(outapp); + } } void Bot::SetSuffix(std::string bot_suffix) { - _suffix = bot_suffix.substr(0, 31); + + _suffix = bot_suffix.substr(0, 31); + + if (spawned) { + + auto outapp = new EQApplicationPacket(OP_SetTitleReply, sizeof(SetTitleReply_Struct)); + SetTitleReply_Struct* strs = (SetTitleReply_Struct*)outapp->pBuffer; + + strs->is_suffix = 1; + strn0cpy(strs->title, _suffix.c_str(), sizeof(strs->title)); + strs->entity_id = GetID(); + + entity_list.QueueClients(this, outapp, false); + safe_delete(outapp); + } } uint32 Bot::GetBotArcheryRange() { @@ -1457,7 +1654,7 @@ int32 Bot::GenerateBaseHitPoints() { } void Bot::LoadAAs() { - int maxAAExpansion = RuleI(Bots, AAExpansion); //get expansion to get AAs up to + aa_ranks.clear(); int id = 0; @@ -2014,6 +2211,17 @@ bool Bot::Process() return false; } + if (mob_scan_close.Check()) { + LogAIScanClose( + "is_moving [{}] bot [{}] timer [{}]", + moving ? "true" : "false", + GetCleanName(), + mob_scan_close.GetDuration() + ); + + entity_list.ScanCloseClientMobs(close_mobs, this); + } + SpellProcess(); if(tic_timer.Check()) { @@ -2284,7 +2492,7 @@ void Bot::SetTarget(Mob* mob) { } void Bot::SetStopMeleeLevel(uint8 level) { - if (IsCasterClass(GetClass()) || IsSpellFighterClass(GetClass())) + if (IsCasterClass(GetClass()) || IsHybridClass(GetClass())) _stopMeleeLevel = level; else _stopMeleeLevel = 255; @@ -2307,15 +2515,16 @@ void Bot::SetHoldMode() { } // AI Processing for the Bot object + +constexpr float MAX_CASTER_DISTANCE[PLAYER_CLASS_COUNT] = { + 0, (34 * 34), (24 * 24), (28 * 28), (26 * 26), (42 * 42), 0, (30 * 30), 0, (38 * 38), (54 * 54), (48 * 48), (52 * 52), (50 * 50), (32 * 32), 0 +// W C P R S D M B R S N W M E B B +// A L A N H R N R O H E I A N S E +// R R L G D U K D G M C Z G C T R +}; + void Bot::AI_Process() { - constexpr float MAX_CASTER_DISTANCE[PLAYER_CLASS_COUNT] = { - 0, (34 * 34), (24 * 24), (28 * 28), (26 * 26), (42 * 42), 0, 0, 0, (38 * 38), (54 * 54), (48 * 48), (52 * 52), (50 * 50), (30 * 30), 0 - // W C P R S D M B R S N W M E B B - // A L A N H R N R O H E I A N S E - // R R L G D U K D G M C Z G C T R - }; - #define TEST_COMBATANTS() if (!GetTarget() || GetAppearance() == eaDead) { return; } #define PULLING_BOT (GetPullingFlag() || GetReturningFlag()) #define NOT_PULLING_BOT (!GetPullingFlag() && !GetReturningFlag()) @@ -2627,7 +2836,7 @@ void Bot::AI_Process() return; } - else if (HasTargetReflection()) { + else if (GetTarget()->GetHateList().size()) { WipeHateList(); SetTarget(nullptr); @@ -2711,14 +2920,14 @@ void Bot::AI_Process() if (find_target) { if (IsRooted()) { - SetTarget(hate_list.GetClosestEntOnHateList(this)); + SetTarget(hate_list.GetClosestEntOnHateList(this, true)); } else { // This will keep bots on target for now..but, future updates will allow for rooting/stunning SetTarget(hate_list.GetEscapingEntOnHateList(leash_owner, leash_distance)); if (!GetTarget()) { - SetTarget(hate_list.GetEntWithMostHateOnList(this)); + SetTarget(hate_list.GetEntWithMostHateOnList(this, nullptr, true)); } } } @@ -3192,7 +3401,7 @@ void Bot::AI_Process() BotRangedAttack(tar); } } - else if (!IsBotArcher() && (IsBotNonSpellFighter() || GetLevel() < GetStopMeleeLevel())) { + else if (!IsBotArcher() && GetLevel() < GetStopMeleeLevel()) { // We can't fight if we don't have a target, are stun/mezzed or dead.. // Stop attacking if the target is enraged @@ -3400,9 +3609,15 @@ void Bot::AI_Process() // This is as close as I could get without modifying the aggro mechanics and making it an expensive process... // 'class Client' doesn't make use of hate_list... - if (bot_owner->GetAggroCount() && bot_owner->GetBotOption(Client::booAutoDefend)) { + if (RuleB(Bots, AllowOwnerOptionAutoDefend) && bot_owner->GetBotOption(Client::booAutoDefend)) { - if (RuleB(Bots, AllowOwnerOptionAutoDefend)) { + if (!m_auto_defend_timer.Enabled()) { + + m_auto_defend_timer.Start(zone->random.Int(250, 1250)); // random timer to simulate 'awareness' (cuts down on scanning overhead) + return; + } + + if (m_auto_defend_timer.Check() && bot_owner->GetAggroCount()) { if (NOT_HOLDING && NOT_PASSIVE) { @@ -3420,7 +3635,7 @@ void Bot::AI_Process() } auto hater = entity_list.GetMob(hater_iter.spawn_id); - if (hater && DistanceSquared(hater->GetPosition(), bot_owner->GetPosition()) <= leash_distance) { + if (hater && !hater->IsMezzed() && DistanceSquared(hater->GetPosition(), bot_owner->GetPosition()) <= leash_distance) { // This is roughly equivilent to npc attacking a client pet owner AddToHateList(hater, 1); @@ -3432,6 +3647,8 @@ void Bot::AI_Process() GetPet()->SetTarget(hater); } + m_auto_defend_timer.Disable(); + return; } } @@ -4726,9 +4943,9 @@ bool Bot::Death(Mob *killerMob, int32 damage, uint16 spell_id, EQEmu::skills::Sk Mob *my_owner = GetBotOwner(); if (my_owner && my_owner->IsClient() && my_owner->CastToClient()->GetBotOption(Client::booDeathMarquee)) { if (killerMob) - my_owner->CastToClient()->SendMarqueeMessage(Chat::Yellow, 510, 0, 1000, 3000, StringFormat("%s has been slain by %s", GetCleanName(), killerMob->GetCleanName())); + my_owner->CastToClient()->SendMarqueeMessage(Chat::Red, 510, 0, 1000, 3000, StringFormat("%s has been slain by %s", GetCleanName(), killerMob->GetCleanName())); else - my_owner->CastToClient()->SendMarqueeMessage(Chat::Yellow, 510, 0, 1000, 3000, StringFormat("%s has been slain", GetCleanName())); + my_owner->CastToClient()->SendMarqueeMessage(Chat::Red, 510, 0, 1000, 3000, StringFormat("%s has been slain", GetCleanName())); } Mob *give_exp = hate_list.GetDamageTopOnHateList(this); @@ -5017,7 +5234,7 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b return false; } -int32 Bot::CalcBotAAFocus(BotfocusType type, uint32 aa_ID, uint32 points, uint16 spell_id) +int32 Bot::CalcBotAAFocus(focusType type, uint32 aa_ID, uint32 points, uint16 spell_id) { const SPDat_Spell_Struct &spell = spells[spell_id]; int32 value = 0; @@ -5171,6 +5388,10 @@ int32 Bot::CalcBotAAFocus(BotfocusType type, uint32 aa_ID, uint32 points, uint16 if (type == focusImprovedDamage && base1 > value) value = base1; break; + case SE_ImprovedDamage2: + if (type == focusImprovedDamage2 && base1 > value) + value = base1; + break; case SE_ImprovedHeal: if (type == focusImprovedHeal && base1 > value) value = base1; @@ -5282,6 +5503,11 @@ int32 Bot::CalcBotAAFocus(BotfocusType type, uint32 aa_ID, uint32 points, uint16 value = base1; break; } + case SE_FcDamageAmt2: { + if(type == focusFcDamageAmt2) + value = base1; + break; + } case SE_FcDamageAmtCrit: { if(type == focusFcDamageAmtCrit) value = base1; @@ -5340,8 +5566,8 @@ int32 Bot::CalcBotAAFocus(BotfocusType type, uint32 aa_ID, uint32 points, uint16 return (value * lvlModifier / 100); } -int32 Bot::GetBotFocusEffect(BotfocusType bottype, uint16 spell_id) { - if (IsBardSong(spell_id) && bottype != BotfocusFcBaseEffects) +int32 Bot::GetBotFocusEffect(focusType bottype, uint16 spell_id) { + if (IsBardSong(spell_id) && bottype != focusFcBaseEffects) return 0; int32 realTotal = 0; @@ -5350,7 +5576,7 @@ int32 Bot::GetBotFocusEffect(BotfocusType bottype, uint16 spell_id) { 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((bottype == BotfocusManaCost || bottype == BotfocusImprovedHeal || bottype == BotfocusImprovedDamage) && RuleB(Spells, LiveLikeFocusEffects)) + if(RuleB(Spells, LiveLikeFocusEffects) && (bottype == focusManaCost || bottype == focusImprovedHeal || bottype == focusImprovedDamage || bottype == focusImprovedDamage2 || bottype == focusResistRate)) rand_effectiveness = true; //Check if item focus effect exists for the client. @@ -5491,16 +5717,16 @@ int32 Bot::GetBotFocusEffect(BotfocusType bottype, uint16 spell_id) { } } - if(bottype == BotfocusReagentCost && IsSummonPetSpell(spell_id) && GetAA(aaElementalPact)) + if(bottype == focusReagentCost && IsSummonPetSpell(spell_id) && GetAA(aaElementalPact)) return 100; - if(bottype == BotfocusReagentCost && (IsEffectInSpell(spell_id, SE_SummonItem) || IsSacrificeSpell(spell_id))) + if(bottype == focusReagentCost && (IsEffectInSpell(spell_id, SE_SummonItem) || IsSacrificeSpell(spell_id))) return 0; return (realTotal + realTotal2); } -int32 Bot::CalcBotFocusEffect(BotfocusType bottype, uint16 focus_id, uint16 spell_id, bool best_focus) { +int32 Bot::CalcBotFocusEffect(focusType bottype, uint16 focus_id, uint16 spell_id, bool best_focus) { if(!IsValidSpell(focus_id) || !IsValidSpell(spell_id)) return 0; @@ -5630,7 +5856,21 @@ int32 Bot::CalcBotFocusEffect(BotfocusType bottype, uint16 focus_id, uint16 spel return 0; break; case SE_ImprovedDamage: - if (bottype == BotfocusImprovedDamage) { + if (bottype == focusImprovedDamage) { + if(best_focus) { + if (focus_spell.base2[i] != 0) + value = focus_spell.base2[i]; + else + value = focus_spell.base[i]; + } + else if (focus_spell.base2[i] == 0 || focus_spell.base[i] == focus_spell.base2[i]) + value = focus_spell.base[i]; + else + value = zone->random.Int(focus_spell.base[i], focus_spell.base2[i]); + } + break; + case SE_ImprovedDamage2: + if (bottype == focusImprovedDamage2) { if(best_focus) { if (focus_spell.base2[i] != 0) value = focus_spell.base2[i]; @@ -5644,7 +5884,7 @@ int32 Bot::CalcBotFocusEffect(BotfocusType bottype, uint16 focus_id, uint16 spel } break; case SE_ImprovedHeal: - if (bottype == BotfocusImprovedHeal) { + if (bottype == focusImprovedHeal) { if(best_focus) { if (focus_spell.base2[i] != 0) value = focus_spell.base2[i]; @@ -5658,7 +5898,7 @@ int32 Bot::CalcBotFocusEffect(BotfocusType bottype, uint16 focus_id, uint16 spel } break; case SE_ReduceManaCost: - if (bottype == BotfocusManaCost) { + if (bottype == focusManaCost) { if(best_focus) { if (focus_spell.base2[i] != 0) value = focus_spell.base2[i]; @@ -5672,39 +5912,39 @@ int32 Bot::CalcBotFocusEffect(BotfocusType bottype, uint16 focus_id, uint16 spel } break; case SE_IncreaseSpellHaste: - if (bottype == BotfocusSpellHaste && focus_spell.base[i] > value) + if (bottype == focusSpellHaste && focus_spell.base[i] > value) value = focus_spell.base[i]; break; case SE_IncreaseSpellDuration: - if (bottype == BotfocusSpellDuration && focus_spell.base[i] > value) + if (bottype == focusSpellDuration && focus_spell.base[i] > value) value = focus_spell.base[i]; break; case SE_SpellDurationIncByTic: - if (bottype == BotfocusSpellDurByTic && focus_spell.base[i] > value) + if (bottype == focusSpellDurByTic && focus_spell.base[i] > value) value = focus_spell.base[i]; break; case SE_SwarmPetDuration: - if (bottype == BotfocusSwarmPetDuration && focus_spell.base[i] > value) + if (bottype == focusSwarmPetDuration && focus_spell.base[i] > value) value = focus_spell.base[i]; break; case SE_IncreaseRange: - if (bottype == BotfocusRange && focus_spell.base[i] > value) + if (bottype == focusRange && focus_spell.base[i] > value) value = focus_spell.base[i]; break; case SE_ReduceReagentCost: - if (bottype == BotfocusReagentCost && focus_spell.base[i] > value) + if (bottype == focusReagentCost && focus_spell.base[i] > value) value = focus_spell.base[i]; break; case SE_PetPowerIncrease: - if (bottype == BotfocusPetPower && focus_spell.base[i] > value) + if (bottype == focusPetPower && focus_spell.base[i] > value) value = focus_spell.base[i]; break; case SE_SpellResistReduction: - if (bottype == BotfocusResistRate && focus_spell.base[i] > value) + if (bottype == focusResistRate && focus_spell.base[i] > value) value = focus_spell.base[i]; break; case SE_SpellHateMod: - if (bottype == BotfocusSpellHateMod) { + if (bottype == focusSpellHateMod) { if(value != 0) { if(value > 0) { if(focus_spell.base[i] > value) @@ -5719,12 +5959,12 @@ int32 Bot::CalcBotFocusEffect(BotfocusType bottype, uint16 focus_id, uint16 spel } break; case SE_ReduceReuseTimer: { - if(bottype == BotfocusReduceRecastTime) + if(bottype == focusReduceRecastTime) value = (focus_spell.base[i] / 1000); break; } case SE_TriggerOnCast: { - if(bottype == BotfocusTriggerOnCast) { + if(bottype == focusTriggerOnCast) { if(zone->random.Int(0, 100) <= focus_spell.base[i]) value = focus_spell.base2[i]; else @@ -5733,24 +5973,24 @@ int32 Bot::CalcBotFocusEffect(BotfocusType bottype, uint16 focus_id, uint16 spel break; } case SE_FcSpellVulnerability: { - if(bottype == BotfocusSpellVulnerability) + if(bottype == focusSpellVulnerability) value = focus_spell.base[i]; break; } case SE_BlockNextSpellFocus: { - if(bottype == BotfocusBlockNextSpell) { + if(bottype == focusBlockNextSpell) { if(zone->random.Int(1, 100) <= focus_spell.base[i]) value = 1; } break; } case SE_FcTwincast: { - if(bottype == BotfocusTwincast) + if(bottype == focusTwincast) value = focus_spell.base[i]; break; } case SE_SympatheticProc: { - if(bottype == BotfocusSympatheticProc) { + if(bottype == focusSympatheticProc) { float ProcChance = GetSympatheticProcChances(spell_id, focus_spell.base[i]); if(zone->random.Real(0, 1) <= ProcChance) value = focus_id; @@ -5760,49 +6000,54 @@ int32 Bot::CalcBotFocusEffect(BotfocusType bottype, uint16 focus_id, uint16 spel break; } case SE_FcDamageAmt: { - if(bottype == BotfocusFcDamageAmt) + if(bottype == focusFcDamageAmt) + value = focus_spell.base[i]; + break; + } + case SE_FcDamageAmt2: { + if(bottype == focusFcDamageAmt2) value = focus_spell.base[i]; break; } case SE_FcDamageAmtCrit: { - if(bottype == BotfocusFcDamageAmtCrit) + if(bottype == focusFcDamageAmtCrit) value = focus_spell.base[i]; break; } case SE_FcHealAmtIncoming: - if(bottype == BotfocusFcHealAmtIncoming) + if(bottype == focusFcHealAmtIncoming) value = focus_spell.base[i]; break; case SE_FcHealPctCritIncoming: - if (bottype == BotfocusFcHealPctCritIncoming) + if (bottype == focusFcHealPctCritIncoming) value = focus_spell.base[i]; break; case SE_FcHealAmtCrit: - if(bottype == BotfocusFcHealAmtCrit) + if(bottype == focusFcHealAmtCrit) value = focus_spell.base[i]; break; case SE_FcHealAmt: - if(bottype == BotfocusFcHealAmt) + if(bottype == focusFcHealAmt) value = focus_spell.base[i]; break; case SE_FcHealPctIncoming: - if(bottype == BotfocusFcHealPctIncoming) + if(bottype == focusFcHealPctIncoming) value = focus_spell.base[i]; break; case SE_FcBaseEffects: { - if (bottype == BotfocusFcBaseEffects) + if (bottype == focusFcBaseEffects) value = focus_spell.base[i]; break; } case SE_FcDamagePctCrit: { - if(bottype == BotfocusFcDamagePctCrit) + if(bottype == focusFcDamagePctCrit) value = focus_spell.base[i]; break; } case SE_FcIncreaseNumHits: { - if(bottype == BotfocusIncreaseNumHits) + if(bottype == focusIncreaseNumHits) value = focus_spell.base[i]; break; @@ -6342,14 +6587,14 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { int32 Bot::CheckAggroAmount(uint16 spellid) { int32 AggroAmount = Mob::CheckAggroAmount(spellid, nullptr); - int32 focusAggro = GetBotFocusEffect(BotfocusSpellHateMod, spellid); + int32 focusAggro = GetBotFocusEffect(focusSpellHateMod, spellid); AggroAmount = (AggroAmount * (100 + focusAggro) / 100); return AggroAmount; } int32 Bot::CheckHealAggroAmount(uint16 spellid, Mob *target, uint32 heal_possible) { int32 AggroAmount = Mob::CheckHealAggroAmount(spellid, target, heal_possible); - int32 focusAggro = GetBotFocusEffect(BotfocusSpellHateMod, spellid); + int32 focusAggro = GetBotFocusEffect(focusSpellHateMod, spellid); AggroAmount = (AggroAmount * (100 + focusAggro) / 100); return AggroAmount; } @@ -6623,7 +6868,7 @@ int32 Bot::GetActSpellDamage(uint16 spell_id, int32 value, Mob* target) { bool Critical = false; int32 value_BaseEffect = 0; - value_BaseEffect = (value + (value*GetBotFocusEffect(BotfocusFcBaseEffects, spell_id) / 100)); + value_BaseEffect = (value + (value*GetBotFocusEffect(focusFcBaseEffects, spell_id) / 100)); // Need to scale HT damage differently after level 40! It no longer scales by the constant value in the spell file. It scales differently, instead of 10 more damage per level, it does 30 more damage per level. So we multiply the level minus 40 times 20 if they are over level 40. if ( (spell_id == SPELL_HARM_TOUCH || spell_id == SPELL_HARM_TOUCH2 || spell_id == SPELL_IMP_HARM_TOUCH ) && GetLevel() > 40) value -= ((GetLevel() - 40) * 20); @@ -6651,16 +6896,18 @@ int32 Bot::GetActSpellDamage(uint16 spell_id, int32 value, Mob* target) { ratio += RuleI(Spells, WizCritRatio); if (Critical) { value = (value_BaseEffect * ratio / 100); - value += (value_BaseEffect * GetBotFocusEffect(BotfocusImprovedDamage, spell_id) / 100); - value += (int(value_BaseEffect * GetBotFocusEffect(BotfocusFcDamagePctCrit, spell_id) / 100) * ratio / 100); + value += (value_BaseEffect * GetBotFocusEffect(focusImprovedDamage, spell_id) / 100); + value += (value_BaseEffect * GetBotFocusEffect(focusImprovedDamage2, spell_id) / 100); + value += (int(value_BaseEffect * GetBotFocusEffect(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 -= (GetBotFocusEffect(BotfocusFcDamageAmtCrit, spell_id) * ratio / 100); + value -= (GetBotFocusEffect(focusFcDamageAmtCrit, spell_id) * ratio / 100); - value -= GetBotFocusEffect(BotfocusFcDamageAmt, spell_id); + value -= GetBotFocusEffect(focusFcDamageAmt, spell_id); + value -= GetBotFocusEffect(focusFcDamageAmt2, spell_id); if(itembonuses.SpellDmg && spells[spell_id].classes[(GetClass() % 17) - 1] >= GetLevel() - 5) value += (GetExtraSpellAmt(spell_id, itembonuses.SpellDmg, value) * ratio / 100); @@ -6672,15 +6919,17 @@ int32 Bot::GetActSpellDamage(uint16 spell_id, int32 value, Mob* target) { } value = value_BaseEffect; - value += (value_BaseEffect * GetBotFocusEffect(BotfocusImprovedDamage, spell_id) / 100); - value += (value_BaseEffect * GetBotFocusEffect(BotfocusFcDamagePctCrit, spell_id) / 100); + value += (value_BaseEffect * GetBotFocusEffect(focusImprovedDamage, spell_id) / 100); + value += (value_BaseEffect * GetBotFocusEffect(focusImprovedDamage2, spell_id) / 100); + value += (value_BaseEffect * GetBotFocusEffect(focusFcDamagePctCrit, spell_id) / 100); if (target) { value += (value_BaseEffect * target->GetVulnerability(this, spell_id, 0) / 100); value -= target->GetFcDamageAmtIncoming(this, spell_id); } - value -= GetBotFocusEffect(BotfocusFcDamageAmtCrit, spell_id); - value -= GetBotFocusEffect(BotfocusFcDamageAmt, spell_id); + value -= GetBotFocusEffect(focusFcDamageAmtCrit, spell_id); + value -= GetBotFocusEffect(focusFcDamageAmt, spell_id); + value -= GetBotFocusEffect(focusFcDamageAmt2, spell_id); if(itembonuses.SpellDmg && spells[spell_id].classes[(GetClass() % 17) - 1] >= GetLevel() - 5) value += GetExtraSpellAmt(spell_id, itembonuses.SpellDmg, value); @@ -6695,9 +6944,9 @@ int32 Bot::GetActSpellHealing(uint16 spell_id, int32 value, Mob* target) { int32 chance = 0; int8 modifier = 1; bool Critical = false; - value_BaseEffect = (value + (value*GetBotFocusEffect(BotfocusFcBaseEffects, spell_id) / 100)); + value_BaseEffect = (value + (value*GetBotFocusEffect(focusFcBaseEffects, spell_id) / 100)); value = value_BaseEffect; - value += int(value_BaseEffect*GetBotFocusEffect(BotfocusImprovedHeal, spell_id) / 100); + value += int(value_BaseEffect*GetBotFocusEffect(focusImprovedHeal, spell_id) / 100); if(spells[spell_id].buffduration < 1) { chance += (itembonuses.CriticalHealChance + spellbonuses.CriticalHealChance + aabonuses.CriticalHealChance); chance += target->GetFocusIncoming(focusFcHealPctCritIncoming, SE_FcHealPctCritIncoming, this, spell_id); @@ -6710,8 +6959,8 @@ int32 Bot::GetActSpellHealing(uint16 spell_id, int32 value, Mob* target) { } value *= modifier; - value += (GetBotFocusEffect(BotfocusFcHealAmtCrit, spell_id) * modifier); - value += GetBotFocusEffect(BotfocusFcHealAmt, spell_id); + value += (GetBotFocusEffect(focusFcHealAmtCrit, spell_id) * modifier); + value += GetBotFocusEffect(focusFcHealAmt, spell_id); value += target->GetFocusIncoming(focusFcHealAmtIncoming, SE_FcHealAmtIncoming, this, spell_id); if(itembonuses.HealAmt && spells[spell_id].classes[(GetClass() % 17) - 1] >= GetLevel() - 5) @@ -6736,7 +6985,7 @@ int32 Bot::GetActSpellHealing(uint16 spell_id, int32 value, Mob* target) { int32 Bot::GetActSpellCasttime(uint16 spell_id, int32 casttime) { int32 cast_reducer = 0; - cast_reducer += GetBotFocusEffect(BotfocusSpellHaste, spell_id); + cast_reducer += GetBotFocusEffect(focusSpellHaste, spell_id); uint8 botlevel = GetLevel(); uint8 botclass = GetClass(); if (botlevel >= 51 && casttime >= 3000 && !BeneficialSpell(spell_id) && (botclass == SHADOWKNIGHT || botclass == RANGER || botclass == PALADIN || botclass == BEASTLORD )) @@ -6871,7 +7120,7 @@ int32 Bot::GetActSpellCost(uint16 spell_id, int32 cost) { } } - int32 focus_redux = GetBotFocusEffect(BotfocusManaCost, spell_id); + int32 focus_redux = GetBotFocusEffect(focusManaCost, spell_id); if(focus_redux > 0) PercentManaReduction += zone->random.Real(1, (double)focus_redux); @@ -6898,14 +7147,14 @@ int32 Bot::GetActSpellCost(uint16 spell_id, int32 cost) { float Bot::GetActSpellRange(uint16 spell_id, float range) { float extrange = 100; - extrange += GetBotFocusEffect(BotfocusRange, spell_id); + extrange += GetBotFocusEffect(focusRange, spell_id); return ((range * extrange) / 100); } int32 Bot::GetActSpellDuration(uint16 spell_id, int32 duration) { int increase = 100; - increase += GetBotFocusEffect(BotfocusSpellDuration, spell_id); - int tic_inc = 0; tic_inc = GetBotFocusEffect(BotfocusSpellDurByTic, spell_id); + increase += GetBotFocusEffect(focusSpellDuration, spell_id); + int tic_inc = 0; tic_inc = GetBotFocusEffect(focusSpellDurByTic, spell_id); if(IsBeneficialSpell(spell_id)) { switch (GetAA(aaSpellCastingReinforcement)) { @@ -8703,15 +8952,21 @@ void Bot::CalcBotStats(bool showtext) { GetBotOwner()->Message(Chat::Yellow, "Updating %s...", GetCleanName()); } - if(!IsValidRaceClassCombo()) { + // this code is annoying since many classes change their name and illusions change the race id + /*if(!IsValidRaceClassCombo()) { GetBotOwner()->Message(Chat::Yellow, "A %s - %s bot was detected. Is this Race/Class combination allowed?.", GetRaceIDName(GetRace()), GetClassIDName(GetClass(), GetLevel())); GetBotOwner()->Message(Chat::Yellow, "Previous Bots Code releases did not check Race/Class combinations during create."); GetBotOwner()->Message(Chat::Yellow, "Unless you are experiencing heavy lag, you should delete and remake this bot."); - } + }*/ if(GetBotOwner()->GetLevel() != GetLevel()) SetLevel(GetBotOwner()->GetLevel()); + for (int sindex = 0; sindex <= EQEmu::skills::HIGHEST_SKILL; ++sindex) { + skills[sindex] = database.GetSkillCap(GetClass(), (EQEmu::skills::SkillType)sindex, GetLevel()); + } + + LoadAAs(); GenerateSpecialAttacks(); if(showtext) { @@ -8989,6 +9244,20 @@ Bot* EntityList::GetBotByBotName(std::string botName) { return Result; } +Client* EntityList::GetBotOwnerByBotEntityID(uint16 entityID) { + Client* Result = nullptr; + if (entityID > 0) { + for (std::list::iterator botListItr = bot_list.begin(); botListItr != bot_list.end(); ++botListItr) { + Bot* tempBot = *botListItr; + if (tempBot && tempBot->GetID() == entityID) { + Result = tempBot->GetBotOwner()->CastToClient(); + break; + } + } + } + return Result; +} + void EntityList::AddBot(Bot *newBot, bool SendSpawnPacket, bool dontqueue) { if(newBot) { newBot->SetID(GetFreeID()); @@ -9151,6 +9420,39 @@ void EntityList::ShowSpawnWindow(Client* client, int Distance, bool NamedOnly) { return; } +/** + * @param close_mobs + * @param scanning_mob + */ +void EntityList::ScanCloseClientMobs(std::unordered_map& close_mobs, Mob* scanning_mob) +{ + float scan_range = RuleI(Range, MobCloseScanDistance) * RuleI(Range, MobCloseScanDistance); + + close_mobs.clear(); + + for (auto& e : mob_list) { + auto mob = e.second; + + if (!mob->IsClient()) { + continue; + } + + if (mob->GetID() <= 0) { + continue; + } + + float distance = DistanceSquared(scanning_mob->GetPosition(), mob->GetPosition()); + if (distance <= scan_range) { + close_mobs.insert(std::pair(mob->GetID(), mob)); + } + else if (mob->GetAggroRange() >= scan_range) { + close_mobs.insert(std::pair(mob->GetID(), mob)); + } + } + + LogAIScanClose("Close Client Mob List Size [{}] for mob [{}]", close_mobs.size(), scanning_mob->GetCleanName()); +} + uint8 Bot::GetNumberNeedingHealedInGroup(uint8 hpr, bool includePets) { uint8 needHealed = 0; Group *g = nullptr; diff --git a/zone/bot.h b/zone/bot.h index 63195e23b..b456fd77f 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -99,40 +99,6 @@ class Bot : public NPC { friend class Mob; public: // Class enums - enum BotfocusType { //focus types - BotfocusSpellHaste = 1, - BotfocusSpellDuration, - BotfocusRange, - BotfocusReagentCost, - BotfocusManaCost, - BotfocusImprovedHeal, - BotfocusImprovedDamage, - BotfocusImprovedDOT, //i dont know about this... - BotfocusFcDamagePctCrit, - BotfocusImprovedUndeadDamage, - BotfocusPetPower, - BotfocusResistRate, - BotfocusSpellHateMod, - BotfocusTriggerOnCast, - BotfocusSpellVulnerability, - BotfocusTwincast, - BotfocusSympatheticProc, - BotfocusFcDamageAmt, - BotfocusFcDamageAmtCrit, - BotfocusSpellDurByTic, - BotfocusSwarmPetDuration, - BotfocusReduceRecastTime, - BotfocusBlockNextSpell, - BotfocusFcHealPctIncoming, - BotfocusFcDamageAmtIncoming, - BotfocusFcHealAmtIncoming, - BotfocusFcBaseEffects, - BotfocusIncreaseNumHits, - BotfocusFcHealPctCritIncoming, - BotfocusFcHealAmt, - BotfocusFcHealAmtCrit, - }; - enum BotTradeType { // types of trades a bot can do BotTradeClientNormal, BotTradeClientNoDropNoTrade @@ -383,6 +349,7 @@ public: void EquipBot(std::string* errorMessage); bool CheckLoreConflict(const EQEmu::ItemData* item); virtual void UpdateEquipmentLight() { m_Light.Type[EQEmu::lightsource::LightEquipment] = m_inv.FindBrightestLightType(); m_Light.Level[EQEmu::lightsource::LightEquipment] = EQEmu::lightsource::TypeToLevel(m_Light.Type[EQEmu::lightsource::LightEquipment]); } + const EQEmu::InventoryProfile& GetBotInv() const { return m_inv; } // Static Class Methods //static void DestroyBotRaidObjects(Client* client); // Can be removed after bot raids are dumped @@ -635,9 +602,9 @@ protected: virtual void PetAIProcess(); virtual void BotMeditate(bool isSitting); virtual bool CheckBotDoubleAttack(bool Triple = false); - virtual int32 GetBotFocusEffect(BotfocusType bottype, uint16 spell_id); - virtual int32 CalcBotFocusEffect(BotfocusType bottype, uint16 focus_id, uint16 spell_id, bool best_focus=false); - virtual int32 CalcBotAAFocus(BotfocusType type, uint32 aa_ID, uint32 points, uint16 spell_id); + virtual int32 GetBotFocusEffect(focusType bottype, uint16 spell_id); + virtual int32 CalcBotFocusEffect(focusType bottype, uint16 focus_id, uint16 spell_id, bool best_focus=false); + virtual int32 CalcBotAAFocus(focusType type, uint32 aa_ID, uint32 points, uint16 spell_id); virtual void PerformTradeWithClient(int16 beginSlotID, int16 endSlotID, Client* client); virtual bool AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore = 0); @@ -692,6 +659,7 @@ private: Timer m_evade_timer; // can be moved to pTimers at some point Timer m_alt_combat_hate_timer; + Timer m_auto_defend_timer; //Timer m_combat_jitter_timer; //bool m_combat_jitter_flag; bool m_guard_flag; diff --git a/zone/bot_command.cpp b/zone/bot_command.cpp index 6a0ae2ff0..dc9d40aab 100644 --- a/zone/bot_command.cpp +++ b/zone/bot_command.cpp @@ -1321,6 +1321,8 @@ int bot_command_init(void) if ( bot_command_add("actionable", "Lists actionable command arguments and use descriptions", 0, bot_command_actionable) || bot_command_add("aggressive", "Orders a bot to use a aggressive discipline", 0, bot_command_aggressive) || + bot_command_add("applypoison", "Applies cursor-held poison to a rogue bot's weapon", 0, bot_command_apply_poison) || + bot_command_add("applypotion", "Applies cursor-held potion to a bot's effects", 0, bot_command_apply_potion) || bot_command_add("attack", "Orders bots to attack a designated target", 0, bot_command_attack) || bot_command_add("bindaffinity", "Orders a bot to attempt an affinity binding", 0, bot_command_bind_affinity) || bot_command_add("bot", "Lists the available bot management [subcommands]", 0, bot_command_bot) || @@ -1369,7 +1371,7 @@ int bot_command_init(void) bot_command_add("depart", "Orders a bot to open a magical doorway to a specified destination", 0, bot_command_depart) || bot_command_add("escape", "Orders a bot to send a target group to a safe location within the zone", 0, bot_command_escape) || bot_command_add("findaliases", "Find available aliases for a bot command", 0, bot_command_find_aliases) || - bot_command_add("follow", "Orders bots to follow a designated target", 0, bot_command_follow) || + bot_command_add("follow", "Orders bots to follow a designated target (option 'chain' auto-links eligible spawned bots)", 0, bot_command_follow) || bot_command_add("guard", "Orders bots to guard their current positions", 0, bot_command_guard) || bot_command_add("healrotation", "Lists the available bot heal rotation [subcommands]", 0, bot_command_heal_rotation) || bot_command_add("healrotationadaptivetargeting", "Enables or disables adaptive targeting within the heal rotation instance", 0, bot_subcommand_heal_rotation_adaptive_targeting) || @@ -1401,6 +1403,7 @@ int bot_command_init(void) bot_command_add("inventoryremove", "Removes an item from a bot's inventory", 0, bot_subcommand_inventory_remove) || bot_command_add("inventorywindow", "Displays all items in a bot's inventory in a pop-up window", 0, bot_subcommand_inventory_window) || bot_command_add("invisibility", "Orders a bot to cast a cloak of invisibility, or allow them to be seen", 0, bot_command_invisibility) || + bot_command_add("itemuse", "Elicits a report from spawned bots that can use the item on your cursor (option 'empty' yields only empty slots)", 0, bot_command_item_use) || bot_command_add("levitation", "Orders a bot to cast a levitation spell", 0, bot_command_levitation) || bot_command_add("lull", "Orders a bot to cast a pacification spell", 0, bot_command_lull) || bot_command_add("mesmerize", "Orders a bot to cast a mesmerization spell", 0, bot_command_mesmerize) || @@ -2579,6 +2582,166 @@ void bot_command_aggressive(Client *c, const Seperator *sep) c->Message(m_action, "%i of %i bots have used aggressive disciplines", success_count, candidate_count); } +void bot_command_apply_poison(Client *c, const Seperator *sep) +{ + if (helper_command_disabled(c, RuleB(Bots, AllowApplyPoisonCommand), "applypoison")) { + return; + } + if (helper_command_alias_fail(c, "bot_command_apply_poison", sep->arg[0], "applypoison")) { + return; + } + if (helper_is_help_or_usage(sep->arg[1])) { + + c->Message(m_usage, "usage: %s", sep->arg[0]); + return; + } + + Bot *my_rogue_bot = nullptr; + if (c->GetTarget() && c->GetTarget()->IsBot() && c->GetTarget()->CastToBot()->GetBotOwnerCharacterID() == c->CharacterID() && c->GetTarget()->CastToBot()->GetClass() == ROGUE) { + my_rogue_bot = c->GetTarget()->CastToBot(); + } + if (!my_rogue_bot) { + + c->Message(m_fail, "You must target a rogue bot that you own to use this command!"); + return; + } + if (my_rogue_bot->GetLevel() < 18) { + + c->Message(m_fail, "Your rogue bot must be level 18 before %s can apply poison!", (my_rogue_bot->GetGender() == 1 ? "she" : "he")); + return; + } + + const auto poison_instance = c->GetInv().GetItem(EQEmu::invslot::slotCursor); + if (!poison_instance) { + + c->Message(m_fail, "No item found on cursor!"); + return; + } + + auto poison_data = poison_instance->GetItem(); + if (!poison_data) { + + c->Message(m_fail, "No data found for cursor item!"); + return; + } + + if (poison_data->ItemType == EQEmu::item::ItemTypePoison) { + + if ((~poison_data->Races) & GetPlayerRaceBit(my_rogue_bot->GetRace())) { + + c->Message(m_fail, "Invalid race for weapon poison!"); + return; + } + + if (poison_data->Proc.Level2 > my_rogue_bot->GetLevel()) { + + c->Message(m_fail, "This poison is too powerful for your intended target!"); + return; + } + + // generalized from client ApplyPoison handler + double ChanceRoll = zone->random.Real(0, 1); + uint16 poison_skill = 95 + ((my_rogue_bot->GetLevel() - 18) * 5); + if (poison_skill > 200) { + poison_skill = 200; + } + bool apply_poison_chance = (ChanceRoll < (.75 + poison_skill / 1000)); + + if (apply_poison_chance && my_rogue_bot->AddProcToWeapon(poison_data->Proc.Effect, false, (my_rogue_bot->GetDEX() / 100) + 103, POISON_PROC)) { + c->Message(m_action, "Successfully applied %s to %s's weapon.", poison_data->Name, my_rogue_bot->GetCleanName()); + } + else { + c->Message(m_fail, "Failed to apply %s to %s's weapon.", poison_data->Name, my_rogue_bot->GetCleanName()); + } + + c->DeleteItemInInventory(EQEmu::invslot::slotCursor, 1, true); + } + else { + + c->Message(m_fail, "Item on cursor is not a weapon poison!"); + return; + } +} + +void bot_command_apply_potion(Client* c, const Seperator* sep) +{ + if (helper_command_disabled(c, RuleB(Bots, AllowApplyPotionCommand), "applypotion")) { + return; + } + if (helper_command_alias_fail(c, "bot_command_apply_potion", sep->arg[0], "applypotion")) { + return; + } + if (helper_is_help_or_usage(sep->arg[1])) { + + c->Message(m_usage, "usage: %s", sep->arg[0]); + return; + } + + Bot* my_bot = nullptr; + if (c->GetTarget() && c->GetTarget()->IsBot() && c->GetTarget()->CastToBot()->GetBotOwnerCharacterID() == c->CharacterID()) { + my_bot = c->GetTarget()->CastToBot(); + } + if (!my_bot) { + + c->Message(m_fail, "You must target a bot that you own to use this command!"); + return; + } + + const auto potion_instance = c->GetInv().GetItem(EQEmu::invslot::slotCursor); + if (!potion_instance) { + + c->Message(m_fail, "No item found on cursor!"); + return; + } + + auto potion_data = potion_instance->GetItem(); + if (!potion_data) { + + c->Message(m_fail, "No data found for cursor item!"); + return; + } + + if (potion_data->ItemType == EQEmu::item::ItemTypePotion && potion_data->Click.Effect > 0) { + + if (RuleB(Bots, RestrictApplyPotionToRogue) && potion_data->Classes != PLAYER_CLASS_ROGUE_BIT) { + + c->Message(m_fail, "This command is restricted to rogue poison potions only!"); + return; + } + if ((~potion_data->Races) & GetPlayerRaceBit(my_bot->GetRace())) { + + c->Message(m_fail, "Invalid race for potion!"); + return; + } + if ((~potion_data->Classes) & GetPlayerClassBit(my_bot->GetClass())) { + + c->Message(m_fail, "Invalid class for potion!"); + return; + } + + if (potion_data->Click.Level2 > my_bot->GetLevel()) { + + c->Message(m_fail, "This potion is too powerful for your intended target!"); + return; + } + + // TODO: figure out best way to handle casting time/animation + if (my_bot->SpellFinished(potion_data->Click.Effect, my_bot, EQEmu::spells::CastingSlot::Item, 0)) { + c->Message(m_action, "Successfully applied %s to %s's buff effects.", potion_data->Name, my_bot->GetCleanName()); + } + else { + c->Message(m_fail, "Failed to apply %s to %s's buff effects.", potion_data->Name, my_bot->GetCleanName()); + } + + c->DeleteItemInInventory(EQEmu::invslot::slotCursor, 1, true); + } + else { + + c->Message(m_fail, "Item on cursor is not a potion!"); + return; + } +} + void bot_command_attack(Client *c, const Seperator *sep) { if (helper_command_alias_fail(c, "bot_command_attack", sep->arg[0], "attack")) { @@ -3051,6 +3214,7 @@ void bot_command_follow(Client *c, const Seperator *sep) return; if (helper_is_help_or_usage(sep->arg[1])) { c->Message(m_usage, "usage: () %s ([option: reset]) [actionable: byname | ownergroup | botgroup | namesgroup | healrotation | spawned] ([actionable_name])", sep->arg[0]); + c->Message(m_usage, "usage: %s chain", sep->arg[0]); return; } const int ab_mask = ActionableBots::ABM_Type2; @@ -3060,8 +3224,15 @@ void bot_command_follow(Client *c, const Seperator *sep) int name_arg = 2; Mob* target_mob = nullptr; - std::string reset_arg = sep->arg[1]; - if (!reset_arg.compare("reset")) { + std::string optional_arg = sep->arg[1]; + if (!optional_arg.compare("chain")) { + + auto chain_count = helper_bot_follow_option_chain(c); + c->Message(m_action, "%i of your bots %s now chain following you", chain_count, (chain_count == 1 ? "is" : "are")); + + return; + } + else if (!optional_arg.compare("reset")) { reset = true; ab_arg = 2; name_arg = 3; @@ -3088,16 +3259,21 @@ void bot_command_follow(Client *c, const Seperator *sep) bot_iter->SetFollowID(c->GetID()); else bot_iter->SetFollowID(my_group->GetLeader()->GetID()); + + bot_iter->SetManualFollow(false); } else { if (bot_iter == target_mob) bot_iter->SetFollowID(c->GetID()); else bot_iter->SetFollowID(target_mob->GetID()); + + bot_iter->SetManualFollow(true); } } else { bot_iter->SetFollowID(0); + bot_iter->SetManualFollow(false); } if (!bot_iter->GetPet()) continue; @@ -3238,6 +3414,12 @@ void bot_command_help(Client *c, const Seperator *sep) c->Message(m_usage, "%c%s - %s", BOT_COMMAND_CHAR, command_iter.first.c_str(), command_iter.second->desc == nullptr ? "[no description]" : command_iter.second->desc); ++bot_commands_shown; } + if (parse->PlayerHasQuestSub(EVENT_BOT_COMMAND)) { + int i = parse->EventPlayer(EVENT_BOT_COMMAND, c, sep->msg, 0); + if (i >= 1) { + bot_commands_shown += i; + } + } c->Message(m_message, "%d bot command%s listed.", bot_commands_shown, bot_commands_shown != 1 ? "s" : ""); c->Message(m_note, "type %ccommand [help | usage] for more information", BOT_COMMAND_CHAR); } @@ -3403,6 +3585,105 @@ void bot_command_invisibility(Client *c, const Seperator *sep) helper_no_available_bots(c, my_bot); } +void bot_command_item_use(Client* c, const Seperator* sep) +{ + if (helper_is_help_or_usage(sep->arg[1])) { + + c->Message(m_usage, "usage: %s ([empty])", sep->arg[0]); + return; + } + + bool empty_only = false; + std::string arg1 = sep->arg[1]; + if (arg1.compare("empty") == 0) { + empty_only = true; + } + + const auto item_instance = c->GetInv().GetItem(EQEmu::invslot::slotCursor); + if (!item_instance) { + + c->Message(m_fail, "No item found on cursor!"); + return; + } + + auto item_data = item_instance->GetItem(); + if (!item_data) { + + c->Message(m_fail, "No data found for cursor item!"); + return; + } + + if (item_data->ItemClass != EQEmu::item::ItemClassCommon || item_data->Slots == 0) { + + c->Message(m_fail, "'%s' is not an equipable item!", item_data->Name); + return; + } + + std::list equipable_slot_list; + for (int16 equipable_slot = EQEmu::invslot::EQUIPMENT_BEGIN; equipable_slot <= EQEmu::invslot::EQUIPMENT_END; ++equipable_slot) { + if (item_data->Slots & (1 << equipable_slot)) { + equipable_slot_list.push_back(equipable_slot); + } + } + + std::string msg; + std::string text_link; + + EQEmu::SayLinkEngine linker; + linker.SetLinkType(EQEmu::saylink::SayLinkItemInst); + + std::list sbl; + MyBots::PopulateSBL_BySpawnedBots(c, sbl); + + for (auto bot_iter : sbl) { + + if (!bot_iter) { + continue; + } + + if (((~item_data->Races) & GetPlayerRaceBit(bot_iter->GetRace())) || ((~item_data->Classes) & GetPlayerClassBit(bot_iter->GetClass()))) { + continue; + } + + msg = StringFormat("%cinventorygive byname %s", BOT_COMMAND_CHAR, bot_iter->GetCleanName()); + text_link = bot_iter->CreateSayLink(c, msg.c_str(), bot_iter->GetCleanName()); + + for (auto slot_iter : equipable_slot_list) { + + // needs more failure criteria - this should cover the bulk for now + if (slot_iter == EQEmu::invslot::slotSecondary && item_data->Damage && !bot_iter->CanThisClassDualWield()) { + continue; + } + + auto equipped_item = bot_iter->GetBotInv()[slot_iter]; + + if (equipped_item && !empty_only) { + + linker.SetItemInst(equipped_item); + + c->Message( + Chat::Say, + "[%s] says, 'I can use that for my %s! (replaces: [%s])'", + text_link.c_str(), + EQEmu::invslot::GetInvPossessionsSlotName(slot_iter), + linker.GenerateLink().c_str() + ); + bot_iter->DoAnim(29); + } + else if (!equipped_item) { + + c->Message( + Chat::Say, + "[%s] says, 'I can use that for my %s!'", + text_link.c_str(), + EQEmu::invslot::GetInvPossessionsSlotName(slot_iter) + ); + bot_iter->DoAnim(29); + } + } + } +} + void bot_command_levitation(Client *c, const Seperator *sep) { bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Levitation]; @@ -3637,6 +3918,16 @@ void bot_command_owner_option(Client *c, const Seperator *sep) "null" "(toggles)" "" + "" + "buffcounter" + "enable | disable" + "marquee message on buff counter change" + "" + "" + "" + "null" + "(toggles)" + "" "" "current" "" @@ -3796,6 +4087,22 @@ void bot_command_owner_option(Client *c, const Seperator *sep) c->Message(m_fail, "Bot owner option 'autodefend' is not allowed on this server."); } } + else if (!owner_option.compare("buffcounter")) { + + if (!argument.compare("enable")) { + c->SetBotOption(Client::booBuffCounter, true); + } + else if (!argument.compare("disable")) { + c->SetBotOption(Client::booBuffCounter, false); + } + else { + c->SetBotOption(Client::booBuffCounter, !c->GetBotOption(Client::booBuffCounter)); + } + + database.botdb.SaveOwnerOption(c->CharacterID(), Client::booBuffCounter, c->GetBotOption(Client::booBuffCounter)); + + c->Message(m_action, "Bot 'buff counter' is now %s.", (c->GetBotOption(Client::booBuffCounter) == true ? "enabled" : "disabled")); + } else if (!owner_option.compare("current")) { std::string window_title = "Current Bot Owner Options Settings"; @@ -3811,13 +4118,15 @@ void bot_command_owner_option(Client *c, const Seperator *sep) "" "spawnmessage" "{}" "" "" "altcombat" "{}" "" "" "autodefend" "{}" "" + "" "buffcounter" "{}" "" "", (c->GetBotOption(Client::booDeathMarquee) ? "enabled" : "disabled"), (c->GetBotOption(Client::booStatsUpdate) ? "enabled" : "disabled"), (c->GetBotOption(Client::booSpawnMessageSay) ? "say" : (c->GetBotOption(Client::booSpawnMessageTell) ? "tell" : "silent")), (c->GetBotOption(Client::booSpawnMessageClassSpecific) ? "class" : "default"), (RuleB(Bots, AllowOwnerOptionAltCombat) ? (c->GetBotOption(Client::booAltCombat) ? "enabled" : "disabled") : "restricted"), - (RuleB(Bots, AllowOwnerOptionAutoDefend) ? (c->GetBotOption(Client::booAutoDefend) ? "enabled" : "disabled") : "restricted") + (RuleB(Bots, AllowOwnerOptionAutoDefend) ? (c->GetBotOption(Client::booAutoDefend) ? "enabled" : "disabled") : "restricted"), + (c->GetBotOption(Client::booBuffCounter) ? "enabled" : "disabled") ); c->SendPopupToClient(window_title.c_str(), window_text.c_str()); @@ -3961,6 +4270,12 @@ void bot_command_pull(Client *c, const Seperator *sep) return; } + if (target_mob->IsNPC() && target_mob->GetHateList().size()) { + + c->Message(m_fail, "Your current target is already engaged!"); + return; + } + Bot* bot_puller = nullptr; for (auto bot_iter : sbl) { @@ -5455,7 +5770,7 @@ void bot_subcommand_bot_list(Client *c, const Seperator *sep) } Bot * botCheckNotOnline = entity_list.GetBotByBotName(bots_iter.Name); std::string botspawn_saylink = StringFormat("^botspawn %s", bots_iter.Name); - c->Message(Chat::White, "%s is a level %u %s %s %s who is owned by %s", + c->Message(Chat::White, "[%s] is a level %u %s %s %s who is owned by %s", ((c->CharacterID() == bots_iter.Owner_ID) && (!botCheckNotOnline) ? (EQEmu::SayLinkEngine::GenerateQuestSaylink(botspawn_saylink, false, bots_iter.Name).c_str()) : (bots_iter.Name)), bots_iter.Level, Bot::RaceIdToString(bots_iter.Race).c_str(), @@ -5536,23 +5851,12 @@ void bot_subcommand_bot_surname(Client *c, const Seperator *sep) std::string bot_surname = sep->arg[1]; bot_surname = (bot_surname == "-remove") ? "" : bot_surname; std::replace(bot_surname.begin(), bot_surname.end(), '_', ' '); + my_bot->SetSurname(bot_surname); if (!database.botdb.SaveBot(my_bot)) { c->Message(Chat::Red, BotDatabase::fail::SaveBot()); - return; } else { - auto outapp = new EQApplicationPacket(OP_GMLastName, sizeof(GMLastName_Struct)); - GMLastName_Struct * gmn = (GMLastName_Struct*)outapp->pBuffer; - strcpy(gmn->name, my_bot->GetCleanName()); - strcpy(gmn->gmname, my_bot->GetCleanName()); - strcpy(gmn->lastname, my_bot->GetSurname().c_str()); - gmn->unknown[0] = 1; - gmn->unknown[1] = 1; - gmn->unknown[2] = 1; - gmn->unknown[3] = 1; - entity_list.QueueClients(my_bot->CastToClient(), outapp); - safe_delete(outapp); c->Message(Chat::Yellow, "Bot Surname Saved."); } } @@ -5575,13 +5879,12 @@ void bot_subcommand_bot_title(Client *c, const Seperator *sep) std::string bot_title = sep->arg[1]; bot_title = (bot_title == "-remove") ? "" : bot_title; std::replace(bot_title.begin(), bot_title.end(), '_', ' '); + my_bot->SetTitle(bot_title); if (!database.botdb.SaveBot(my_bot)) { c->Message(Chat::Red, BotDatabase::fail::SaveBot()); - return; } else { - my_bot->CastToClient()->SetAATitle(my_bot->GetTitle().c_str()); c->Message(Chat::Yellow, "Bot Title Saved."); } } @@ -5604,13 +5907,12 @@ void bot_subcommand_bot_suffix(Client *c, const Seperator *sep) std::string bot_suffix = sep->arg[1]; bot_suffix = (bot_suffix == "-remove") ? "" : bot_suffix; std::replace(bot_suffix.begin(), bot_suffix.end(), '_', ' '); + my_bot->SetSuffix(bot_suffix); if (!database.botdb.SaveBot(my_bot)) { c->Message(Chat::Red, BotDatabase::fail::SaveBot()); - return; } else { - my_bot->CastToClient()->SetTitleSuffix(my_bot->GetSuffix().c_str()); c->Message(Chat::Yellow, "Bot Suffix Saved."); } } @@ -5855,7 +6157,7 @@ void bot_subcommand_bot_stop_melee_level(Client *c, const Seperator *sep) return; if (helper_is_help_or_usage(sep->arg[1])) { c->Message(m_usage, "usage: %s [current | reset | sync | value: 0-255]", sep->arg[0]); - c->Message(m_note, "note: Only caster and spell-casting fighter class bots may be modified"); + c->Message(m_note, "note: Only caster or hybrid class bots may be modified"); c->Message(m_note, "note: Use [reset] to set stop melee level to server rule"); c->Message(m_note, "note: Use [sync] to set stop melee level to current bot level"); return; @@ -5866,8 +6168,8 @@ void bot_subcommand_bot_stop_melee_level(Client *c, const Seperator *sep) c->Message(m_fail, "You must a bot that you own to use this command"); return; } - if (!IsCasterClass(my_bot->GetClass()) && !IsSpellFighterClass(my_bot->GetClass())) { - c->Message(m_fail, "You must a caster or spell-casting fighter class bot to use this command"); + if (!IsCasterClass(my_bot->GetClass()) && !IsHybridClass(my_bot->GetClass())) { + c->Message(m_fail, "You must a caster or hybrid class bot to use this command"); return; } @@ -8432,6 +8734,75 @@ void helper_bot_out_of_combat(Client *bot_owner, Bot *my_bot) } } +int helper_bot_follow_option_chain(Client* bot_owner) +{ + if (!bot_owner) { + return 0; + } + + std::list sbl; + MyBots::PopulateSBL_BySpawnedBots(bot_owner, sbl); + if (sbl.empty()) { + return 0; + } + + int chain_follow_count = 0; + Mob* followee = bot_owner; + + // only add groups that do not belong to bot_owner + std::map bot_group_map; + for (auto bot_iter : sbl) { + + if (!bot_iter || bot_iter->GetManualFollow() || bot_iter->GetGroup() == bot_owner->GetGroup()) { + continue; + } + + Group* bot_group = bot_iter->GetGroup(); + if (!bot_iter->GetGroup()) { + continue; + } + + bot_group_map[bot_group->GetID()] = bot_group; + } + + std::list bot_member_list; + if (bot_owner->GetGroup()) { + + bot_owner->GetGroup()->GetBotList(bot_member_list); + for (auto bot_member_iter : bot_member_list) { + + if (!bot_member_iter || bot_member_iter->GetBotOwnerCharacterID() != bot_owner->CharacterID() || bot_member_iter == followee || bot_member_iter->GetManualFollow()) { + continue; + } + + bot_member_iter->SetFollowID(followee->GetID()); + followee = bot_member_iter; + ++chain_follow_count; + } + } + + for (auto bot_group_iter : bot_group_map) { + + if (!bot_group_iter.second) { + continue; + } + + bot_group_iter.second->GetBotList(bot_member_list); + for (auto bot_member_iter : bot_member_list) { + + if (!bot_member_iter || bot_member_iter->GetBotOwnerCharacterID() != bot_owner->CharacterID() || bot_member_iter == followee || bot_member_iter->GetManualFollow()) { + continue; + } + + bot_member_iter->SetFollowID(followee->GetID()); + followee = bot_member_iter; + ++chain_follow_count; + } + } + + return chain_follow_count; +} + bool helper_cast_standard_spell(Bot* casting_bot, Mob* target_mob, int spell_id, bool annouce_cast, uint32* dont_root_before) { if (!casting_bot || !target_mob) @@ -8444,6 +8815,16 @@ bool helper_cast_standard_spell(Bot* casting_bot, Mob* target_mob, int spell_id, return casting_bot->CastSpell(spell_id, target_mob->GetID(), EQEmu::spells::CastingSlot::Gem2, -1, -1, dont_root_before); } +bool helper_command_disabled(Client* bot_owner, bool rule_value, const char* command) +{ + if (rule_value == false) { + bot_owner->Message(m_fail, "Bot command %s is not enabled on this server.", command); + return true; + } + + return false; +} + bool helper_command_alias_fail(Client *bot_owner, const char* command_handler, const char *alias, const char *command) { auto alias_iter = bot_command_aliases.find(&alias[1]); diff --git a/zone/bot_command.h b/zone/bot_command.h index d710e106f..84a56f239 100644 --- a/zone/bot_command.h +++ b/zone/bot_command.h @@ -553,6 +553,8 @@ void bot_command_log_command(Client *c, const char *message); // bot commands void bot_command_actionable(Client *c, const Seperator *sep); void bot_command_aggressive(Client *c, const Seperator *sep); +void bot_command_apply_poison(Client *c, const Seperator *sep); +void bot_command_apply_potion(Client* c, const Seperator* sep); void bot_command_attack(Client *c, const Seperator *sep); void bot_command_bind_affinity(Client *c, const Seperator *sep); void bot_command_bot(Client *c, const Seperator *sep); @@ -571,6 +573,7 @@ void bot_command_hold(Client *c, const Seperator *sep); void bot_command_identify(Client *c, const Seperator *sep); void bot_command_inventory(Client *c, const Seperator *sep); void bot_command_invisibility(Client *c, const Seperator *sep); +void bot_command_item_use(Client *c, const Seperator *sep); void bot_command_levitation(Client *c, const Seperator *sep); void bot_command_lull(Client *c, const Seperator *sep); void bot_command_mesmerize(Client *c, const Seperator *sep); @@ -670,7 +673,9 @@ void helper_bot_appearance_form_final(Client *bot_owner, Bot *my_bot); void helper_bot_appearance_form_update(Bot *my_bot); uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_class, uint16 bot_race, uint8 bot_gender); void helper_bot_out_of_combat(Client *bot_owner, Bot *my_bot); +int helper_bot_follow_option_chain(Client *bot_owner); bool helper_cast_standard_spell(Bot* casting_bot, Mob* target_mob, int spell_id, bool annouce_cast = true, uint32* dont_root_before = nullptr); +bool helper_command_disabled(Client *bot_owner, bool rule_value, const char *command); bool helper_command_alias_fail(Client *bot_owner, const char* command_handler, const char *alias, const char *command); void helper_command_depart_list(Client* bot_owner, Bot* druid_bot, Bot* wizard_bot, bcst_list* local_list, bool single_flag = false); bool helper_is_help_or_usage(const char* arg); diff --git a/zone/bot_database.cpp b/zone/bot_database.cpp index 8496dfcd8..2f6f9c1c6 100644 --- a/zone/bot_database.cpp +++ b/zone/bot_database.cpp @@ -2256,6 +2256,7 @@ bool BotDatabase::SaveOwnerOption(const uint32 owner_id, size_t type, const bool case Client::booSpawnMessageClassSpecific: case Client::booAltCombat: case Client::booAutoDefend: + case Client::booBuffCounter: { query = fmt::format( "REPLACE INTO `bot_owner_options`(`owner_id`, `option_type`, `option_value`) VALUES ('{}', '{}', '{}')", diff --git a/zone/client.cpp b/zone/client.cpp index dae6c8912..2398db500 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -123,49 +123,50 @@ Client::Client(EQStreamInterface* ieqs) 0, 0 ), - hpupdate_timer(2000), - camp_timer(29000), - process_timer(100), - consume_food_timer(CONSUMPTION_TIMER), - zoneinpacket_timer(1000), - linkdead_timer(RuleI(Zone,ClientLinkdeadMS)), - dead_timer(2000), - global_channel_timer(1000), - shield_timer(500), - fishing_timer(8000), - endupkeep_timer(1000), - forget_timer(0), - autosave_timer(RuleI(Character, AutosaveIntervalS) * 1000), - client_scan_npc_aggro_timer(RuleI(Aggro, ClientAggroCheckInterval) * 1000), - client_zone_wide_full_position_update_timer(5 * 60 * 1000), - 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), - anon_toggle_timer(250), - afk_toggle_timer(250), - helm_toggle_timer(250), - aggro_meter_timer(AGGRO_METER_UPDATE_MS), - m_Proximity(FLT_MAX, FLT_MAX, FLT_MAX), //arbitrary large number + hpupdate_timer(2000), + camp_timer(29000), + process_timer(100), + consume_food_timer(CONSUMPTION_TIMER), + zoneinpacket_timer(1000), + linkdead_timer(RuleI(Zone,ClientLinkdeadMS)), + dead_timer(2000), + global_channel_timer(1000), + shield_timer(500), + fishing_timer(8000), + endupkeep_timer(1000), + forget_timer(0), + autosave_timer(RuleI(Character, AutosaveIntervalS) * 1000), + client_scan_npc_aggro_timer(RuleI(Aggro, ClientAggroCheckInterval) * 1000), + client_zone_wide_full_position_update_timer(5 * 60 * 1000), + 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), + anon_toggle_timer(250), + afk_toggle_timer(250), + helm_toggle_timer(250), + aggro_meter_timer(AGGRO_METER_UPDATE_MS), + m_Proximity(FLT_MAX, FLT_MAX, FLT_MAX), //arbitrary large number m_ZoneSummonLocation(-2.0f,-2.0f,-2.0f), - m_AutoAttackPosition(0.0f, 0.0f, 0.0f, 0.0f), - m_AutoAttackTargetLocation(0.0f, 0.0f, 0.0f), - last_region_type(RegionTypeUnsupported), - m_dirtyautohaters(false), - npc_close_scan_timer(6000), - hp_self_update_throttle_timer(300), - hp_other_update_throttle_timer(500), - position_update_timer(10000), - tmSitting(0) + m_AutoAttackPosition(0.0f, 0.0f, 0.0f, 0.0f), + m_AutoAttackTargetLocation(0.0f, 0.0f, 0.0f), + last_region_type(RegionTypeUnsupported), + m_dirtyautohaters(false), + mob_close_scan_timer(6000), + hp_self_update_throttle_timer(300), + hp_other_update_throttle_timer(500), + position_update_timer(10000), + consent_throttle_timer(2000), + tmSitting(0) { for (int client_filter = 0; client_filter < _FilterCount; client_filter++) @@ -356,6 +357,7 @@ Client::Client(EQStreamInterface* ieqs) bot_owner_options[booSpawnMessageClassSpecific] = true; bot_owner_options[booAltCombat] = RuleB(Bots, AllowOwnerOptionAltCombat); bot_owner_options[booAutoDefend] = RuleB(Bots, AllowOwnerOptionAutoDefend); + bot_owner_options[booBuffCounter] = false; SetBotPulling(false); SetBotPrecombat(false); @@ -1105,40 +1107,56 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s case ChatChannel_Say: { /* Say */ if(message[0] == COMMAND_CHAR) { if(command_dispatch(this, message) == -2) { - if(parse->PlayerHasQuestSub(EVENT_COMMAND)) { + if (parse->PlayerHasQuestSub(EVENT_COMMAND)) { int i = parse->EventPlayer(EVENT_COMMAND, this, message, 0); - if(i == 0 && !RuleB(Chat, SuppressCommandErrors)) { + if (i == 0 && !RuleB(Chat, SuppressCommandErrors)) { Message(Chat::Red, "Command '%s' not recognized.", message); } - } else { - if(!RuleB(Chat, SuppressCommandErrors)) + } + else if (parse->PlayerHasQuestSub(EVENT_SAY)) { + int i = parse->EventPlayer(EVENT_SAY, this, message, 0); + if (i == 0 && !RuleB(Chat, SuppressCommandErrors)) { Message(Chat::Red, "Command '%s' not recognized.", message); + } + } + else { + if (!RuleB(Chat, SuppressCommandErrors)) { + Message(Chat::Red, "Command '%s' not recognized.", message); + } } } break; } - if (EQEmu::ProfanityManager::IsCensorshipActive()) - EQEmu::ProfanityManager::RedactMessage(message); - #ifdef BOTS if (message[0] == BOT_COMMAND_CHAR) { if (bot_command_dispatch(this, message) == -2) { - if (parse->PlayerHasQuestSub(EVENT_COMMAND)) { - int i = parse->EventPlayer(EVENT_COMMAND, this, message, 0); + if (parse->PlayerHasQuestSub(EVENT_BOT_COMMAND)) { + int i = parse->EventPlayer(EVENT_BOT_COMMAND, this, message, 0); + if (i == 0 && !RuleB(Chat, SuppressCommandErrors)) { + Message(Chat::Red, "Bot command '%s' not recognized.", message); + } + } + else if (parse->PlayerHasQuestSub(EVENT_SAY)) { + int i = parse->EventPlayer(EVENT_SAY, this, message, 0); if (i == 0 && !RuleB(Chat, SuppressCommandErrors)) { Message(Chat::Red, "Bot command '%s' not recognized.", message); } } else { - if (!RuleB(Chat, SuppressCommandErrors)) + if (!RuleB(Chat, SuppressCommandErrors)) { Message(Chat::Red, "Bot command '%s' not recognized.", message); + } } } break; } #endif + if (EQEmu::ProfanityManager::IsCensorshipActive()) { + EQEmu::ProfanityManager::RedactMessage(message); + } + Mob* sender = this; if (GetPet() && GetTarget() == GetPet() && GetPet()->FindType(SE_VoiceGraft)) sender = GetPet(); @@ -6237,6 +6255,52 @@ void Client::DragCorpses() } } +void Client::ConsentCorpses(std::string consent_name, bool deny) +{ + if (strcasecmp(consent_name.c_str(), GetName()) == 0) { + MessageString(Chat::Red, CONSENT_YOURSELF); + } + else if (!consent_throttle_timer.Check()) { + MessageString(Chat::Red, CONSENT_WAIT); + } + else { + auto pack = new ServerPacket(ServerOP_Consent, sizeof(ServerOP_Consent_Struct)); + ServerOP_Consent_Struct* scs = (ServerOP_Consent_Struct*)pack->pBuffer; + strn0cpy(scs->grantname, consent_name.c_str(), sizeof(scs->grantname)); + strn0cpy(scs->ownername, GetName(), sizeof(scs->ownername)); + strn0cpy(scs->zonename, "Unknown", sizeof(scs->zonename)); + scs->permission = deny ? 0 : 1; + scs->zone_id = zone->GetZoneID(); + scs->instance_id = zone->GetInstanceID(); + scs->consent_type = EQEmu::consent::Normal; + scs->consent_id = 0; + if (strcasecmp(scs->grantname, "group") == 0) { + if (!deny) { + Group* grp = GetGroup(); + scs->consent_id = grp ? grp->GetID() : 0; + } + scs->consent_type = EQEmu::consent::Group; + } + else if (strcasecmp(scs->grantname, "raid") == 0) { + if (!deny) { + Raid* raid = GetRaid(); + scs->consent_id = raid ? raid->GetID() : 0; + } + scs->consent_type = EQEmu::consent::Raid; + } + else if (strcasecmp(scs->grantname, "guild") == 0) { + if (!deny) { + scs->consent_id = GuildID(); + } + scs->consent_type = EQEmu::consent::Guild; + // update all corpses in db so buried/unloaded corpses see new consent id + database.UpdateCharacterCorpseConsent(CharacterID(), scs->consent_id); + } + worldserver.SendPacket(pack); + safe_delete(pack); + } +} + 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()) @@ -6742,11 +6806,22 @@ void Client::SendStatsWindow(Client* client, bool use_window) 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" + "1H Blunt","1H Slashing","2H Blunt","2H Slashing","Abjuration", + "Alteration","Apply Poison","Archery","Backstab","Bind Wound", + "Bash","Block","Brass Instruments","Channeling","Conjuration", + "Defense","Disarm","Disarm Traps","Divination","Dodge", + "Double Attack","Dragon Punch","Dual Wield","Eagle Strike","Evocation", + "Feign Death","Flying Kick","Forage","Hand To Hand","Hide", + "Kick","Meditate","Mend","Offense","Parry", + "Pick Lock","1H Piercing","Riposte","Round Kick","Safe Fall", + "Sense Heading","Singing","Sneak","Specialize Abjuration","Specialize Alteration", + "Specialize Conjuration","Specialize Divination","Specialize Evocation","Pick Pockets","Stringed Instruments", + "Swimming","Throwing","Tiger Claw","Tracking","Wind Instruments", + "Fishing","Make Poison","Tinkering","Research","Alchemy", + "Baking","Tailoring","Sense Traps","Blacksmithing","Fletching", + "Brewing","Alcohol_Tolerance","Begging","Jewelry Making","Pottery", + "Percussion Instruments","Intimidation","Berserking","Taunt","Frenzy", + "Remove Traps","Triple Attack","2H Piercing" }; std::string skill_mods = ""; @@ -8505,13 +8580,13 @@ void Client::QuestReward(Mob* target, uint32 copper, uint32 silver, uint32 gold, memset(outapp->pBuffer, 0, sizeof(QuestReward_Struct)); QuestReward_Struct* qr = (QuestReward_Struct*)outapp->pBuffer; - qr->mob_id = target->GetID(); // Entity ID for the from mob name + qr->mob_id = target ? target->GetID() : 0; // Entity ID for the from mob name qr->target_id = GetID(); // The Client ID (this) qr->copper = copper; qr->silver = silver; qr->gold = gold; qr->platinum = platinum; - qr->item_id = itemid; + qr->item_id[0] = itemid; qr->exp_reward = exp; if (copper > 0 || silver > 0 || gold > 0 || platinum > 0) @@ -8522,7 +8597,7 @@ void Client::QuestReward(Mob* target, uint32 copper, uint32 silver, uint32 gold, if (faction) { - if (target->IsNPC()) + if (target && target->IsNPC()) { int32 nfl_id = target->CastToNPC()->GetNPCFactionID(); SetFactionLevel(CharacterID(), nfl_id, GetBaseClass(), GetBaseRace(), GetDeity(), true); @@ -8538,6 +8613,42 @@ void Client::QuestReward(Mob* target, uint32 copper, uint32 silver, uint32 gold, safe_delete(outapp); } +void Client::QuestReward(Mob* target, const QuestReward_Struct &reward, bool faction) +{ + auto outapp = new EQApplicationPacket(OP_Sound, sizeof(QuestReward_Struct)); + memset(outapp->pBuffer, 0, sizeof(QuestReward_Struct)); + QuestReward_Struct* qr = (QuestReward_Struct*)outapp->pBuffer; + + memcpy(qr, &reward, sizeof(QuestReward_Struct)); + + // not set in caller because reasons + qr->mob_id = target ? target->GetID() : 0; // Entity ID for the from mob name + + if (reward.copper > 0 || reward.silver > 0 || reward.gold > 0 || reward.platinum > 0) + AddMoneyToPP(reward.copper, reward.silver, reward.gold, reward.platinum, false); + + for (int i = 0; i < QUESTREWARD_COUNT; ++i) + if (reward.item_id[i] > 0) + SummonItem(reward.item_id[i], 0, 0, 0, 0, 0, 0, false, EQEmu::invslot::slotCursor); + + if (faction) + { + if (target && target->IsNPC()) + { + int32 nfl_id = target->CastToNPC()->GetNPCFactionID(); + SetFactionLevel(CharacterID(), nfl_id, GetBaseClass(), GetBaseRace(), GetDeity(), true); + qr->faction = target->CastToNPC()->GetPrimaryFaction(); + qr->faction_mod = 1; // Too lazy to get real value, not sure if this is even used by client anyhow. + } + } + + if (reward.exp_reward> 0) + AddEXP(reward.exp_reward); + + QueuePacket(outapp, true, Client::CLIENT_CONNECTED); + safe_delete(outapp); +} + void Client::SendHPUpdateMarquee(){ if (!this || !this->IsClient() || !this->current_hp || !this->max_hp) return; diff --git a/zone/client.h b/zone/client.h index 6fa4835c9..a0655584b 100644 --- a/zone/client.h +++ b/zone/client.h @@ -226,7 +226,6 @@ public: Client(EQStreamInterface * ieqs); ~Client(); - std::unordered_map close_mobs; bool is_client_moving; void SetDisplayMobInfoWindow(bool display_mob_info_window); @@ -794,6 +793,9 @@ public: virtual void UpdateEquipmentLight() { m_Light.Type[EQEmu::lightsource::LightEquipment] = m_inv.FindBrightestLightType(); m_Light.Level[EQEmu::lightsource::LightEquipment] = EQEmu::lightsource::TypeToLevel(m_Light.Type[EQEmu::lightsource::LightEquipment]); } inline bool AutoSplitEnabled() { return m_pp.autosplit != 0; } + inline bool AutoConsentGroupEnabled() const { return m_pp.groupAutoconsent != 0; } + inline bool AutoConsentRaidEnabled() const { return m_pp.raidAutoconsent != 0; } + inline bool AutoConsentGuildEnabled() const { return m_pp.guildAutoconsent != 0; } void SummonHorse(uint16 spell_id); void SetHorseId(uint16 horseid_in); @@ -958,7 +960,6 @@ public: void EnteringMessages(Client* client); void SendRules(Client* client); - std::list consent_list; const bool GetGMSpeed() const { return (gmspeed > 0); } bool CanUseReport; @@ -1138,6 +1139,7 @@ public: inline bool IsDraggingCorpse() { return (DraggedCorpses.size() > 0); } void DragCorpses(); inline void ClearDraggedCorpses() { DraggedCorpses.clear(); } + void ConsentCorpses(std::string consent_name, bool deny = false); void SendAltCurrencies(); void SetAlternateCurrencyValue(uint32 currency_id, uint32 new_amount); void AddAlternateCurrencyValue(uint32 currency_id, int32 amount, int8 method = 0); @@ -1283,6 +1285,7 @@ public: int32 GetMeleeDamage(Mob* other, bool GetMinDamage = false); void QuestReward(Mob* target, uint32 copper = 0, uint32 silver = 0, uint32 gold = 0, uint32 platinum = 0, uint32 itemid = 0, uint32 exp = 0, bool faction = false); + void QuestReward(Mob* target, const QuestReward_Struct &reward, bool faction); // TODO: Fix faction processing void ResetHPUpdateTimer() { hpupdate_timer.Start(); } @@ -1524,10 +1527,11 @@ private: Timer afk_toggle_timer; Timer helm_toggle_timer; Timer aggro_meter_timer; - Timer npc_close_scan_timer; + Timer mob_close_scan_timer; Timer hp_self_update_throttle_timer; /* This is to prevent excessive packet sending under trains/fast combat */ Timer hp_other_update_throttle_timer; /* This is to keep clients from DOSing the server with macros that change client targets constantly */ Timer position_update_timer; /* Timer used when client hasn't updated within a 10 second window */ + Timer consent_throttle_timer; glm::vec3 m_Proximity; glm::vec4 last_position_before_bulk_update; @@ -1640,6 +1644,7 @@ public: booSpawnMessageClassSpecific, booAltCombat, booAutoDefend, + booBuffCounter, _booCount }; diff --git a/zone/client_mods.cpp b/zone/client_mods.cpp index 1b8735d7d..255201090 100644 --- a/zone/client_mods.cpp +++ b/zone/client_mods.cpp @@ -336,6 +336,10 @@ int32 Client::CalcMaxHP() current_hp = curHP_cap; } } + + // hack fix for client health not reflecting server value + last_max_hp = 0; + return max_hp; } @@ -610,14 +614,13 @@ int32 Client::CalcBaseMana() case 'I': WisInt = GetINT(); if (ClientVersion() >= EQEmu::versions::ClientVersion::SoF && RuleB(Character, SoDClientUseSoDHPManaEnd)) { + ConvertedWisInt = WisInt; + int over200 = WisInt; if (WisInt > 100) { - ConvertedWisInt = (((WisInt - 100) * 5 / 2) + 100); - if (WisInt > 201) { - ConvertedWisInt -= ((WisInt - 201) * 5 / 4); + if (WisInt > 200) { + over200 = (WisInt - 200) / -2 + WisInt; } - } - else { - ConvertedWisInt = WisInt; + ConvertedWisInt = (3 * over200 - 300) / 2 + over200; } auto base_data = database.GetBaseData(GetLevel(), GetClass()); if (base_data) { @@ -643,14 +646,13 @@ int32 Client::CalcBaseMana() case 'W': WisInt = GetWIS(); if (ClientVersion() >= EQEmu::versions::ClientVersion::SoF && RuleB(Character, SoDClientUseSoDHPManaEnd)) { + ConvertedWisInt = WisInt; + int over200 = WisInt; if (WisInt > 100) { - ConvertedWisInt = (((WisInt - 100) * 5 / 2) + 100); - if (WisInt > 201) { - ConvertedWisInt -= ((WisInt - 201) * 5 / 4); + if (WisInt > 200) { + over200 = (WisInt - 200) / -2 + WisInt; } - } - else { - ConvertedWisInt = WisInt; + ConvertedWisInt = (3 * over200 - 300) / 2 + over200; } auto base_data = database.GetBaseData(GetLevel(), GetClass()); if (base_data) { diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 8b21c2f70..0ea11e8ae 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -905,6 +905,8 @@ void Client::CompleteConnect() entity_list.RefreshClientXTargets(this); worldserver.RequestTellQueue(GetName()); + + entity_list.ScanCloseMobs(close_mobs, this); } // connecting opcode handlers @@ -4641,43 +4643,16 @@ 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) { - auto 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 { - MessageString(Chat::White, CONSENT_YOURSELF); - } + ConsentCorpses(c->name, false); } - return; } void Client::Handle_OP_ConsentDeny(const EQApplicationPacket *app) { if (app->size<64) { Consent_Struct* c = (Consent_Struct*)app->pBuffer; - auto 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); + ConsentCorpses(c->name, true); } - return; } void Client::Handle_OP_Consider(const EQApplicationPacket *app) @@ -8286,7 +8261,18 @@ void Client::Handle_OP_ItemLinkClick(const EQApplicationPacket *app) if (GetTarget() && GetTarget()->IsNPC()) { if (silentsaylink) { parse->EventNPC(EVENT_SAY, GetTarget()->CastToNPC(), this, response.c_str(), 0); - parse->EventPlayer(EVENT_SAY, this, response.c_str(), 0); + + if (response[0] == '#' && parse->PlayerHasQuestSub(EVENT_COMMAND)) { + parse->EventPlayer(EVENT_COMMAND, this, response.c_str(), 0); + } +#ifdef BOTS + else if (response[0] == '^' && parse->PlayerHasQuestSub(EVENT_BOT_COMMAND)) { + parse->EventPlayer(EVENT_BOT_COMMAND, this, response.c_str(), 0); + } +#endif + else { + parse->EventPlayer(EVENT_SAY, this, response.c_str(), 0); + } } else { Message(Chat::LightGray, "You say, '%s'", response.c_str()); @@ -8296,7 +8282,17 @@ void Client::Handle_OP_ItemLinkClick(const EQApplicationPacket *app) } else { if (silentsaylink) { - parse->EventPlayer(EVENT_SAY, this, response.c_str(), 0); + if (response[0] == '#' && parse->PlayerHasQuestSub(EVENT_COMMAND)) { + parse->EventPlayer(EVENT_COMMAND, this, response.c_str(), 0); + } +#ifdef BOTS + else if (response[0] == '^' && parse->PlayerHasQuestSub(EVENT_BOT_COMMAND)) { + parse->EventPlayer(EVENT_BOT_COMMAND, this, response.c_str(), 0); + } +#endif + else { + parse->EventPlayer(EVENT_SAY, this, response.c_str(), 0); + } } else { Message(Chat::LightGray, "You say, '%s'", response.c_str()); @@ -11107,6 +11103,11 @@ void Client::Handle_OP_RaidCommand(const EQApplicationPacket *app) break; } + if (player_to_invite_group && player_to_invite_group->IsGroupMember(this)) { + MessageString(Chat::Red, ALREADY_IN_PARTY); + break; + } + if (player_to_invite_group && !player_to_invite_group->IsLeader(player_to_invite)) { Message(Chat::Red, "You can only invite an ungrouped player or group leader to join your raid."); break; @@ -13292,6 +13293,21 @@ void Client::Handle_OP_SpawnAppearance(const EQApplicationPacket *app) entity_list.QueueClients(this, app, true); } } + else if (sa->type == AT_GroupConsent) + { + m_pp.groupAutoconsent = (sa->parameter == 1); + ConsentCorpses("Group", (sa->parameter != 1)); + } + else if (sa->type == AT_RaidConsent) + { + m_pp.raidAutoconsent = (sa->parameter == 1); + ConsentCorpses("Raid", (sa->parameter != 1)); + } + else if (sa->type == AT_GuildConsent) + { + m_pp.guildAutoconsent = (sa->parameter == 1); + ConsentCorpses("Guild", (sa->parameter != 1)); + } 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; @@ -14139,16 +14155,16 @@ void Client::Handle_OP_TradeRequest(const EQApplicationPacket *app) #else else if (tradee && (tradee->IsNPC() || tradee->IsBot())) { #endif - //npcs always accept - trade->Start(msg->to_mob_id); - - auto 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); - } + if (!tradee->IsEngaged()) { + 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; } diff --git a/zone/client_process.cpp b/zone/client_process.cpp index a857c3c21..d8ec451a9 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -251,26 +251,12 @@ bool Client::Process() { } } - /* Build a close range list of NPC's */ - if (npc_close_scan_timer.Check()) { - close_mobs.clear(); - //Force spawn updates when traveled far - bool force_spawn_updates = false; - float client_update_range = (RuleI(Range, ClientForceSpawnUpdateRange) * RuleI(Range, ClientForceSpawnUpdateRange)); - float scan_range = (RuleI(Range, ClientNPCScan) * RuleI(Range, ClientNPCScan)); - auto &mob_list = entity_list.GetMobList(); - for (auto itr = mob_list.begin(); itr != mob_list.end(); ++itr) { - Mob* mob = itr->second; - float distance = DistanceSquared(m_Position, mob->GetPosition()); - if (mob->IsNPC()) { - if (distance <= scan_range) { - close_mobs.insert(std::pair(mob, distance)); - } - else if ((mob->GetAggroRange() * mob->GetAggroRange()) > scan_range) { - close_mobs.insert(std::pair(mob, distance)); - } - } - } + /** + * Scan close range mobs + * Used in aggro checks + */ + if (mob_close_scan_timer.Check()) { + entity_list.ScanCloseMobs(close_mobs, this); } bool may_use_attacks = false; @@ -593,7 +579,7 @@ bool Client::Process() { if (zone->CanDoCombat() && ret && !GetFeigned() && client_scan_npc_aggro_timer.Check()) { int npc_scan_count = 0; for (auto & close_mob : close_mobs) { - Mob *mob = close_mob.first; + Mob *mob = close_mob.second; if (!mob) continue; diff --git a/zone/command.cpp b/zone/command.cpp index 150750494..757e2d522 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -295,6 +295,7 @@ int command_init(void) command_add("npcstats", "- Show stats about target NPC", 80, command_npcstats) || command_add("npctype_cache", "[id] or all - Clears the npc type cache for either the id or all npcs.", 250, command_npctype_cache) || command_add("npctypespawn", "[npctypeid] [factionid] - Spawn an NPC from the db", 10, command_npctypespawn) || + command_add("nudge", "- Nudge your target's current position by specific values", 80, command_nudge) || command_add("nukebuffs", "- Strip all buffs on you or your target", 50, command_nukebuffs) || command_add("nukeitem", "[itemid] - Remove itemid from your player target's inventory", 150, command_nukeitem) || command_add("object", "List|Add|Edit|Move|Rotate|Copy|Save|Undo|Delete - Manipulate static and tradeskill objects within the zone", 100, command_object) || @@ -782,6 +783,12 @@ void command_help(Client *c, const Seperator *sep) commands_shown++; c->Message(Chat::White, " %c%s %s", COMMAND_CHAR, cur->first.c_str(), cur->second->desc == nullptr?"":cur->second->desc); } + if (parse->PlayerHasQuestSub(EVENT_COMMAND)) { + int i = parse->EventPlayer(EVENT_COMMAND, c, sep->msg, 0); + if (i >= 1) { + commands_shown += i; + } + } c->Message(Chat::White, "%d command%s listed.", commands_shown, commands_shown!=1?"s":""); } @@ -2166,25 +2173,6 @@ void command_spoff(Client *c, const Seperator *sep) safe_delete(outapp); } -void command_itemtest(Client *c, const Seperator *sep) -{ - char chBuffer[8192] = {0}; - //Using this to determine new item layout - FILE* f = nullptr; - if (!(f = fopen("c:\\EQEMUcvs\\ItemDump.txt", "rb"))) { - c->Message(Chat::Red, "Error: Could not open c:\\EQEMUcvs\\ItemDump.txt"); - return; - } - - fread(chBuffer, sizeof(chBuffer), sizeof(char), f); - fclose(f); - - auto outapp = new EQApplicationPacket(OP_ItemLinkResponse, strlen(chBuffer) + 5); - memcpy(&outapp->pBuffer[4], chBuffer, strlen(chBuffer)); - c->QueuePacket(outapp); - safe_delete(outapp); -} - void command_gassign(Client *c, const Seperator *sep) { if (sep->IsNumber(1) && c->GetTarget() && c->GetTarget()->IsNPC() && c->GetTarget()->CastToNPC()->GetSpawnPointID() > 0) { @@ -2502,6 +2490,7 @@ void command_grid(Client *c, const Seperator *sep) else { c->Message(Chat::White, "Usage: #grid add/delete grid_num wandertype pausetype"); c->Message(Chat::White, "Usage: #grid max - displays the highest grid ID used in this zone (for add)"); + c->Message(Chat::White, "Usage: #grid show - displays wp nodes as boxes"); } } @@ -2554,7 +2543,7 @@ void command_size(Client *c, const Seperator *sep) else if (!target) c->Message(Chat::White,"Error: this command requires a target"); else { - uint16 Race = target->GetRace(); + uint16 Race = target->GetModel(); uint8 Gender = target->GetGender(); uint8 Texture = 0xFF; uint8 HelmTexture = 0xFF; @@ -3127,6 +3116,81 @@ void command_npctypespawn(Client *c, const Seperator *sep) } +void command_nudge(Client* c, const Seperator* sep) +{ + if (sep->arg[1][0] == 0) { + c->Message(Chat::White, "Usage: #nudge [x=f] [y=f] [z=f] [h=f] (partial/mixed arguments allowed)"); + } + else { + + auto target = c->GetTarget(); + if (!target) { + + c->Message(Chat::Yellow, "This command requires a target."); + return; + } + if (target->IsMoving()) { + + c->Message(Chat::Yellow, "This command requires a stationary target."); + return; + } + + glm::vec4 position_offset(0.0f, 0.0f, 0.0f, 0.0f); + for (auto index = 1; index <= 4; ++index) { + + if (!sep->arg[index]) { + continue; + } + + Seperator argsep(sep->arg[index], '='); + if (!argsep.arg[1][0]) { + continue; + } + + switch (argsep.arg[0][0]) { + case 'x': + position_offset.x = atof(argsep.arg[1]); + break; + case 'y': + position_offset.y = atof(argsep.arg[1]); + break; + case 'z': + position_offset.z = atof(argsep.arg[1]); + break; + case 'h': + position_offset.w = atof(argsep.arg[1]); + break; + default: + break; + } + } + + const auto& current_position = target->GetPosition(); + glm::vec4 new_position( + (current_position.x + position_offset.x), + (current_position.y + position_offset.y), + (current_position.z + position_offset.z), + (current_position.w + position_offset.w) + ); + + target->GMMove(new_position.x, new_position.y, new_position.z, new_position.w); + + c->Message( + Chat::White, + "Nudging '%s' to {%1.3f, %1.3f, %1.3f, %1.2f} (adjustment: {%1.3f, %1.3f, %1.3f, %1.2f})", + target->GetName(), + new_position.x, + new_position.y, + new_position.z, + new_position.w, + position_offset.x, + position_offset.y, + position_offset.z, + position_offset.w + ); + } +} + void command_heal(Client *c, const Seperator *sep) { if (c->GetTarget()==0) @@ -4176,10 +4240,15 @@ void command_corpsefix(Client *c, const Seperator *sep) void command_reloadworld(Client *c, const Seperator *sep) { - c->Message(Chat::White, "Reloading quest cache and repopping zones worldwide."); + int world_repop = atoi(sep->arg[1]); + if (world_repop == 0) + c->Message(Chat::White, "Reloading quest cache worldwide."); + else + c->Message(Chat::White, "Reloading quest cache and repopping zones worldwide."); + auto pack = new ServerPacket(ServerOP_ReloadWorld, sizeof(ReloadWorld_Struct)); ReloadWorld_Struct* RW = (ReloadWorld_Struct*) pack->pBuffer; - RW->Option = ((atoi(sep->arg[1]) == 1) ? 1 : 0); + RW->Option = world_repop; worldserver.SendPacket(pack); safe_delete(pack); } @@ -7471,7 +7540,7 @@ void command_ipban(Client *c, const Seperator *sep) c->Message(Chat::White, "Usage: #ipban [xxx.xxx.xxx.xxx]"); } else { if(database.AddBannedIP(sep->arg[1], c->GetName())) { - c->Message(Chat::White, "%s has been successfully added to the Banned_IPs table by %s", sep->arg[1], c->GetName()); + c->Message(Chat::White, "%s has been successfully added to the banned_ips table by %s", sep->arg[1], c->GetName()); } else { c->Message(Chat::White, "IPBan Failed (IP address is possibly already in the table?)"); } @@ -13182,8 +13251,8 @@ void command_bot(Client *c, const Seperator *sep) } if (bot_command_dispatch(c, bot_message.c_str()) == -2) { - if (parse->PlayerHasQuestSub(EVENT_COMMAND)) { - int i = parse->EventPlayer(EVENT_COMMAND, c, bot_message, 0); + if (parse->PlayerHasQuestSub(EVENT_BOT_COMMAND)) { + int i = parse->EventPlayer(EVENT_BOT_COMMAND, c, bot_message, 0); if (i == 0 && !RuleB(Chat, SuppressCommandErrors)) { c->Message(Chat::Red, "Bot command '%s' not recognized.", bot_message.c_str()); } diff --git a/zone/command.h b/zone/command.h index 3a7dcc400..d64950f9c 100644 --- a/zone/command.h +++ b/zone/command.h @@ -150,7 +150,6 @@ void command_ipc(Client *c, const Seperator *sep); void command_iplookup(Client *c, const Seperator *sep); void command_iteminfo(Client *c, const Seperator *sep); void command_itemsearch(Client *c, const Seperator *sep); -void command_itemtest(Client *c, const Seperator *sep); void command_kick(Client *c, const Seperator *sep); void command_killallnpcs(Client *c, const Seperator *sep); void command_kill(Client *c, const Seperator *sep); @@ -194,6 +193,7 @@ void command_npcspecialattk(Client *c, const Seperator *sep); void command_npcstats(Client *c, const Seperator *sep); void command_npctype_cache(Client *c, const Seperator *sep); void command_npctypespawn(Client *c, const Seperator *sep); +void command_nudge(Client* c, const Seperator* sep); void command_nukebuffs(Client *c, const Seperator *sep); void command_nukeitem(Client *c, const Seperator *sep); void command_numauths(Client *c, const Seperator *sep); diff --git a/zone/common.h b/zone/common.h index e06fa134a..2d97527fc 100644 --- a/zone/common.h +++ b/zone/common.h @@ -647,6 +647,19 @@ enum { SKILLUP_FAILURE = 2 }; +enum { + GridCircular, + GridRandom10, + GridRandom, + GridPatrol, + GridOneWayRepop, + GridRand5LoS, + GridOneWayDepop, + GridCenterPoint, + GridRandomCenterPoint, + GridRandomPath +}; + typedef enum { petFamiliar, //only listens to /pet get lost petAnimation, //does not listen to any commands diff --git a/zone/corpse.cpp b/zone/corpse.cpp index 09661826d..0ac100997 100644 --- a/zone/corpse.cpp +++ b/zone/corpse.cpp @@ -73,7 +73,7 @@ void Corpse::SendLootReqErrorPacket(Client* client, LootResponse response) { safe_delete(outapp); } -Corpse* Corpse::LoadCharacterCorpseEntity(uint32 in_dbid, uint32 in_charid, std::string in_charname, const glm::vec4& 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 glm::vec4& position, std::string time_of_death, bool rezzed, bool was_at_graveyard, uint32 guild_consent_id) { uint32 item_count = database.GetCharacterCorpseItemCount(in_dbid); auto buffer = new char[sizeof(PlayerCorpse_Struct) + (item_count * sizeof(player_lootitem::ServerLootItem_Struct))]; @@ -138,6 +138,7 @@ Corpse* Corpse::LoadCharacterCorpseEntity(uint32 in_dbid, uint32 in_charid, std: pc->drakkin_details = pcs->drakkin_details; pc->IsRezzed(rezzed); pc->become_npc = false; + pc->consented_guild_id = guild_consent_id; pc->UpdateEquipmentLight(); // itemlist populated above..need to determine actual values @@ -282,6 +283,18 @@ Corpse::Corpse(Client* client, int32 in_rezexp) : Mob ( allowed_looters[i] = 0; } + if (client->AutoConsentGroupEnabled()) { + Group* grp = client->GetGroup(); + consented_group_id = grp ? grp->GetID() : 0; + } + + if (client->AutoConsentRaidEnabled()) { + Raid* raid = client->GetRaid(); + consented_raid_id = raid ? raid->GetID() : 0; + } + + consented_guild_id = client->AutoConsentGuildEnabled() ? client->GuildID() : 0; + is_corpse_changed = true; rez_experience = in_rezexp; can_corpse_be_rezzed = true; @@ -611,11 +624,11 @@ bool Corpse::Save() { /* Create New Corpse*/ if (corpse_db_id == 0) { - corpse_db_id = database.SaveCharacterCorpse(char_id, corpse_name, zone->GetZoneID(), zone->GetInstanceID(), dbpc, m_Position); + corpse_db_id = database.SaveCharacterCorpse(char_id, corpse_name, zone->GetZoneID(), zone->GetInstanceID(), dbpc, m_Position, consented_guild_id); } /* Update Corpse Data */ else{ - corpse_db_id = database.UpdateCharacterCorpse(corpse_db_id, char_id, corpse_name, 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, consented_guild_id, IsRezzed()); } safe_delete_array(dbpc); @@ -647,6 +660,25 @@ void Corpse::DepopPlayerCorpse() { player_corpse_depop = true; } +void Corpse::AddConsentName(std::string consent_player_name) +{ + for (const auto& consented_player_name : consented_player_names) { + if (strcasecmp(consented_player_name.c_str(), consent_player_name.c_str()) == 0) { + return; + } + } + consented_player_names.emplace_back(consent_player_name); +} + +void Corpse::RemoveConsentName(std::string consent_player_name) +{ + consented_player_names.erase(std::remove_if(consented_player_names.begin(), consented_player_names.end(), + [consent_player_name](const std::string& consented_player_name) { + return strcasecmp(consented_player_name.c_str(), consent_player_name.c_str()) == 0; + } + ), consented_player_names.end()); +} + uint32 Corpse::CountItems() { return itemlist.size(); } @@ -1434,29 +1466,50 @@ bool Corpse::Summon(Client* client, bool spell, bool CheckDistance) { is_corpse_changed = true; } else { - client->Message(Chat::White, "Corpse is too far away."); + client->MessageString(Chat::Red, CORPSE_TOO_FAR); 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 || (DistanceSquaredNoZ(m_Position, client->GetPosition()) <= dist2)) { - GMMove(client->GetX(), client->GetY(), client->GetZ()); - is_corpse_changed = true; - } - else { - client->Message(Chat::White, "Corpse is too far away."); - return false; - } + for (const auto& consented_player_name : consented_player_names) { + if (strcasecmp(client->GetName(), consented_player_name.c_str()) == 0) { + consented = true; + break; + } + } + + if (!consented && consented_guild_id && consented_guild_id != GUILD_NONE) { + if (client->GuildID() == consented_guild_id) { consented = true; } } - if(!consented) { - client->Message(Chat::White, "You do not have permission to move this corpse."); + if (!consented && consented_group_id) { + Group* grp = client->GetGroup(); + if (grp && grp->GetID() == consented_group_id) { + consented = true; + } + } + if (!consented && consented_raid_id) { + Raid* raid = client->GetRaid(); + if (raid && raid->GetID() == consented_raid_id) { + consented = true; + } + } + + if (consented) { + if (!CheckDistance || (DistanceSquaredNoZ(m_Position, client->GetPosition()) <= dist2)) { + GMMove(client->GetX(), client->GetY(), client->GetZ()); + is_corpse_changed = true; + } + else { + client->MessageString(Chat::Red, CORPSE_TOO_FAR); + return false; + } + } + else { + client->MessageString(Chat::Red, CONSENT_DENIED); return false; } } diff --git a/zone/corpse.h b/zone/corpse.h index e7a7c5805..d453c7e9e 100644 --- a/zone/corpse.h +++ b/zone/corpse.h @@ -48,7 +48,7 @@ class Corpse : public Mob { 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 glm::vec4& 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* LoadCharacterCorpseEntity(uint32 in_dbid, uint32 in_charid, std::string in_charname, const glm::vec4& position, std::string time_of_death, bool rezzed, bool was_at_graveyard); + static Corpse* LoadCharacterCorpseEntity(uint32 in_dbid, uint32 in_charid, std::string in_charname, const glm::vec4& position, std::string time_of_death, bool rezzed, bool was_at_graveyard, uint32 guild_consent_id); /* Corpse: General */ virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill) { return true; } @@ -74,6 +74,11 @@ class Corpse : public Mob { 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 SetConsentGroupID(uint32 group_id) { if (IsPlayerCorpse()) { consented_group_id = group_id; } } + void SetConsentRaidID(uint32 raid_id) { if (IsPlayerCorpse()) { consented_raid_id = raid_id; } } + void SetConsentGuildID(uint32 guild_id) { if (IsPlayerCorpse()) { consented_guild_id = guild_id; } } + void AddConsentName(std::string consent_player_name); + void RemoveConsentName(std::string consent_player_name); void Delete(); void Bury(); @@ -142,6 +147,9 @@ private: int32 player_kill_item; /* Determines if Player Kill Item */ uint32 corpse_db_id; /* Corpse Database ID (Player Corpse) */ uint32 char_id; /* Character ID */ + uint32 consented_group_id = 0; + uint32 consented_raid_id = 0; + uint32 consented_guild_id = 0; ItemList itemlist; /* Internal Item list used for corpses */ uint32 copper; uint32 silver; @@ -160,6 +168,7 @@ private: Timer corpse_graveyard_timer; Timer loot_cooldown_timer; /* Delay between loot actions on the corpse entity */ EQEmu::TintProfile item_tint; + std::vector consented_player_names; LootRequestType loot_request_type; }; diff --git a/zone/effects.cpp b/zone/effects.cpp index ba82cf3f0..7e2ecdbc7 100644 --- a/zone/effects.cpp +++ b/zone/effects.cpp @@ -673,252 +673,429 @@ void Client::SendDisciplineTimer(uint32 timer_id, uint32 duration) } } -void EntityList::AETaunt(Client* taunter, float range, int32 bonus_hate) +/** + * @param taunter + * @param range + * @param bonus_hate + */ +void EntityList::AETaunt(Client *taunter, float range, int32 bonus_hate) { - if (range == 0) - range = 40; //Live AE taunt range - Hardcoded. - range = range * range; + /** + * Live AE taunt range - Hardcoded. + */ + if (range == 0) { + range = 40; + } - auto it = npc_list.begin(); - while (it != npc_list.end()) { - NPC *them = it->second; - float zdiff = taunter->GetZ() - them->GetZ(); - if (zdiff < 0) - zdiff *= -1; - if (zdiff < 10 - && taunter->IsAttackAllowed(them) - && DistanceSquaredNoZ(taunter->GetPosition(), them->GetPosition()) <= range) { + float range_squared = range * range; + + for (auto &it : entity_list.GetCloseMobList(taunter, range)) { + Mob *them = it.second; + + if (!them->IsNPC()) { + continue; + } + + float z_difference = taunter->GetZ() - them->GetZ(); + if (z_difference < 0) { + z_difference *= -1; + } + + if (z_difference < 10 + && taunter->IsAttackAllowed(them) + && DistanceSquaredNoZ(taunter->GetPosition(), them->GetPosition()) <= range_squared) { if (taunter->CheckLosFN(them)) { - taunter->Taunt(them, true,0,true,bonus_hate); + taunter->Taunt(them->CastToNPC(), true, 0, true, bonus_hate); } } - ++it; } } -// causes caster to hit every mob within dist range of center with -// spell_id. -// NPC spells will only affect other NPCs with compatible faction -void EntityList::AESpell(Mob *caster, Mob *center, uint16 spell_id, bool affect_caster, int16 resist_adjust, int *max_targets) +/** + * Causes caster to hit every mob within dist range of center with spell_id + * + * @param caster_mob + * @param center_mob + * @param spell_id + * @param affect_caster + * @param resist_adjust + * @param max_targets + */ +void EntityList::AESpell( + Mob *caster_mob, + Mob *center_mob, + uint16 spell_id, + bool affect_caster, + int16 resist_adjust, + int *max_targets +) { - Mob *curmob = nullptr; + const auto &cast_target_position = + spells[spell_id].targettype == ST_Ring ? + caster_mob->GetTargetRingLocation() : + static_cast(center_mob->GetPosition()); - float dist = caster->GetAOERange(spell_id); - float dist2 = dist * dist; - float min_range2 = spells[spell_id].min_range * spells[spell_id].min_range; - float dist_targ = 0; + Mob *current_mob = nullptr; + bool is_detrimental_spell = IsDetrimentalSpell(spell_id); + bool is_npc = caster_mob->IsNPC(); + float distance = caster_mob->GetAOERange(spell_id); + float distance_squared = distance * distance; + float min_range2 = spells[spell_id].min_range * spells[spell_id].min_range; + glm::vec2 min = {cast_target_position.x - distance, cast_target_position.y - distance}; + glm::vec2 max = {cast_target_position.x + distance, cast_target_position.y + distance}; - const auto &position = spells[spell_id].targettype == ST_Ring ? caster->GetTargetRingLocation() : static_cast(center->GetPosition()); - glm::vec2 min = { position.x - dist, position.y - dist }; - glm::vec2 max = { position.x + dist, position.y + dist }; + /** + * If using Old Rain Targets - there is no max target limitation + */ + if (RuleB(Spells, OldRainTargets)) { + max_targets = nullptr; + } - bool bad = IsDetrimentalSpell(spell_id); - bool isnpc = caster->IsNPC(); - - if (RuleB(Spells, OldRainTargets)) - max_targets = nullptr; // ignore it! - - // if we have a passed in value, use it, otherwise default to data - // detrimental Target AEs have a default value of 4 for PCs and unlimited for NPCs + /** + * Max AOE targets + */ int max_targets_allowed = 0; // unlimited - if (max_targets) // rains pass this in since they need to preserve the count through waves + if (max_targets) { // rains pass this in since they need to preserve the count through waves max_targets_allowed = *max_targets; - else if (spells[spell_id].aemaxtargets) + } + else if (spells[spell_id].aemaxtargets) { max_targets_allowed = spells[spell_id].aemaxtargets; - else if (IsTargetableAESpell(spell_id) && bad && !isnpc) + } + else if (IsTargetableAESpell(spell_id) && is_detrimental_spell && !is_npc) { max_targets_allowed = 4; + } - int iCounter = 0; + int target_hit_counter = 0; + float distance_to_target = 0; - for (auto it = mob_list.begin(); it != mob_list.end(); ++it) { - curmob = it->second; - // test to fix possible cause of random zone crashes..external methods accessing client properties before they're initialized - if (curmob->IsClient() && !curmob->CastToClient()->ClientFinishedLoading()) - continue; - if (curmob == caster && !affect_caster) //watch for caster too - continue; - if (spells[spell_id].targettype == ST_TargetAENoPlayersPets && curmob->IsPetOwnerClient()) - continue; - if (spells[spell_id].targettype == ST_AreaClientOnly && !curmob->IsClient()) - continue; - if (spells[spell_id].targettype == ST_AreaNPCOnly && !curmob->IsNPC()) - continue; - // check PC/NPC only flag 1 = PCs, 2 = NPCs - if (spells[spell_id].pcnpc_only_flag == 1 && !curmob->IsClient() && !curmob->IsMerc()) - continue; - if (spells[spell_id].pcnpc_only_flag == 2 && (curmob->IsClient() || curmob->IsMerc())) - continue; - if (!IsWithinAxisAlignedBox(static_cast(curmob->GetPosition()), min, max)) - continue; + LogAoeCast( + "Close scan distance [{}] cast distance [{}]", + RuleI(Range, MobCloseScanDistance), + distance + ); - dist_targ = DistanceSquared(curmob->GetPosition(), position); + for (auto &it : entity_list.GetCloseMobList(caster_mob, distance)) { + current_mob = it.second; - if (dist_targ > dist2) //make sure they are in range + if (!current_mob) { continue; - if (dist_targ < min_range2) //make sure they are in range + } + + LogAoeCast("Checking AOE against mob [{}]", current_mob->GetCleanName()); + + if (current_mob->IsClient() && !current_mob->CastToClient()->ClientFinishedLoading()) { continue; - if (isnpc && curmob->IsNPC() && spells[spell_id].targettype != ST_AreaNPCOnly) { //check npc->npc casting - FACTION_VALUE f = curmob->GetReverseFactionCon(caster); - if (bad) { + } + + if (current_mob == caster_mob && !affect_caster) { + continue; + } + + if (spells[spell_id].targettype == ST_TargetAENoPlayersPets && current_mob->IsPetOwnerClient()) { + continue; + } + + if (spells[spell_id].targettype == ST_AreaClientOnly && !current_mob->IsClient()) { + continue; + } + + if (spells[spell_id].targettype == ST_AreaNPCOnly && !current_mob->IsNPC()) { + continue; + } + + /** + * Check PC / NPC + * 1 = PC + * 2 = NPC + */ + if (spells[spell_id].pcnpc_only_flag == 1 && !current_mob->IsClient() && !current_mob->IsMerc() && + !current_mob->IsBot()) { + continue; + } + + if (spells[spell_id].pcnpc_only_flag == 2 && + (current_mob->IsClient() || current_mob->IsMerc() || current_mob->IsBot())) { + continue; + } + + if (!IsWithinAxisAlignedBox(static_cast(current_mob->GetPosition()), min, max)) { + continue; + } + + distance_to_target = DistanceSquared(current_mob->GetPosition(), cast_target_position); + + if (distance_to_target > distance_squared) { + continue; + } + + if (distance_to_target < min_range2) { + continue; + } + + if (is_npc && current_mob->IsNPC() && + spells[spell_id].targettype != ST_AreaNPCOnly) { //check npc->npc casting + FACTION_VALUE faction_value = current_mob->GetReverseFactionCon(caster_mob); + if (is_detrimental_spell) { //affect mobs that are on our hate list, or //which have bad faction with us - if (!(caster->CheckAggro(curmob) || f == FACTION_THREATENLY || f == FACTION_SCOWLS) ) + if ( + !(caster_mob->CheckAggro(current_mob) || + faction_value == FACTION_THREATENLY || + faction_value == FACTION_SCOWLS)) { continue; - } else { + } + } + else { //only affect mobs we would assist. - if (!(f <= FACTION_AMIABLE)) + if (!(faction_value <= FACTION_AMIABLE)) { continue; + } } } - //finally, make sure they are within range - if (bad) { - if (!caster->IsAttackAllowed(curmob, true)) + + /** + * Finally, make sure they are within range + */ + if (is_detrimental_spell) { + if (!caster_mob->IsAttackAllowed(current_mob, true)) { continue; - if (center && !spells[spell_id].npc_no_los && !center->CheckLosFN(curmob)) + } + if (center_mob && !spells[spell_id].npc_no_los && !center_mob->CheckLosFN(current_mob)) { continue; - if (!center && !spells[spell_id].npc_no_los && !caster->CheckLosFN(caster->GetTargetRingX(), caster->GetTargetRingY(), caster->GetTargetRingZ(), curmob->GetSize())) + } + if (!center_mob && !spells[spell_id].npc_no_los && !caster_mob->CheckLosFN( + caster_mob->GetTargetRingX(), + caster_mob->GetTargetRingY(), + caster_mob->GetTargetRingZ(), + current_mob->GetSize())) { continue; - } else { // check to stop casting beneficial ae buffs (to wit: bard songs) on enemies... - // This does not check faction for beneficial AE buffs..only agro and attackable. - // I've tested for spells that I can find without problem, but a faction-based - // check may still be needed. Any changes here should also reflect in BardAEPulse() - if (caster->IsAttackAllowed(curmob, true)) + } + } + else { + + /** + * Check to stop casting beneficial ae buffs (to wit: bard songs) on enemies... + * This does not check faction for beneficial AE buffs... only agro and attackable. + * I've tested for spells that I can find without problem, but a faction-based + * check may still be needed. Any changes here should also reflect in BardAEPulse() + */ + if (caster_mob->IsAttackAllowed(current_mob, true)) { continue; - if (caster->CheckAggro(curmob)) + } + if (caster_mob->CheckAggro(current_mob)) { continue; + } } - curmob->CalcSpellPowerDistanceMod(spell_id, dist_targ); - caster->SpellOnTarget(spell_id, curmob, false, true, resist_adjust); - - if (max_targets_allowed) { // if we have a limit, increment count - iCounter++; - if (iCounter >= max_targets_allowed) // we done + /** + * Increment hit count if max targets + */ + if (max_targets_allowed) { + target_hit_counter++; + if (target_hit_counter >= max_targets_allowed) { break; + } } + + current_mob->CalcSpellPowerDistanceMod(spell_id, distance_to_target); + caster_mob->SpellOnTarget(spell_id, current_mob, false, true, resist_adjust); } - if (max_targets && max_targets_allowed) - *max_targets = *max_targets - iCounter; + LogAoeCast("Done iterating [{}]", caster_mob->GetCleanName()); + + if (max_targets && max_targets_allowed) { + *max_targets = *max_targets - target_hit_counter; + } } -void EntityList::MassGroupBuff(Mob *caster, Mob *center, uint16 spell_id, bool affect_caster) +/** + * @param caster + * @param center + * @param spell_id + * @param affect_caster + */ +void EntityList::MassGroupBuff( + Mob *caster, + Mob *center, + uint16 spell_id, + bool affect_caster) { - Mob *curmob = nullptr; + Mob *current_mob = nullptr; + float distance = caster->GetAOERange(spell_id); + float distance_squared = distance * distance; + bool is_detrimental_spell = IsDetrimentalSpell(spell_id); - float dist = caster->GetAOERange(spell_id); - float dist2 = dist * dist; + for (auto &it : entity_list.GetCloseMobList(caster, distance)) { + current_mob = it.second; - bool bad = IsDetrimentalSpell(spell_id); - - for (auto it = mob_list.begin(); it != mob_list.end(); ++it) { - curmob = it->second; - if (curmob == center) //do not affect center - continue; - if (curmob == caster && !affect_caster) //watch for caster too - continue; - if (DistanceSquared(center->GetPosition(), curmob->GetPosition()) > dist2) //make sure they are in range + /** + * Skip center + */ + if (current_mob == center) { continue; + } - //Only npcs mgb should hit are client pets... - if (curmob->IsNPC()) { - Mob *owner = curmob->GetOwner(); + /** + * Skip self + */ + if (current_mob == caster && !affect_caster) { + continue; + } + + if (DistanceSquared(center->GetPosition(), current_mob->GetPosition()) > distance_squared) { //make sure they are in range + continue; + } + + /** + * Pets + */ + if (current_mob->IsNPC()) { + Mob *owner = current_mob->GetOwner(); if (owner) { if (!owner->IsClient()) { continue; } - } else { + } + else { continue; } } - if (bad) { + if (is_detrimental_spell) { continue; } - caster->SpellOnTarget(spell_id, curmob); + caster->SpellOnTarget(spell_id, current_mob); } } -// causes caster to hit every mob within dist range of center with -// a bard pulse of spell_id. -// NPC spells will only affect other NPCs with compatible faction -void EntityList::AEBardPulse(Mob *caster, Mob *center, uint16 spell_id, bool affect_caster) +/** + * Causes caster to hit every mob within dist range of center with a bard pulse of spell_id + * NPC spells will only affect other NPCs with compatible faction + * + * @param caster + * @param center + * @param spell_id + * @param affect_caster + */ +void EntityList::AEBardPulse( + Mob *caster, + Mob *center, + uint16 spell_id, + bool affect_caster) { - Mob *curmob = nullptr; + Mob *current_mob = nullptr; + float distance = caster->GetAOERange(spell_id); + float distance_squared = distance * distance; + bool is_detrimental_spell = IsDetrimentalSpell(spell_id); + bool is_npc = caster->IsNPC(); - float dist = caster->GetAOERange(spell_id); - float dist2 = dist * dist; + for (auto &it : entity_list.GetCloseMobList(caster, distance)) { + current_mob = it.second; - bool bad = IsDetrimentalSpell(spell_id); - bool isnpc = caster->IsNPC(); + /** + * Skip self + */ + if (current_mob == center) { + continue; + } - for (auto it = mob_list.begin(); it != mob_list.end(); ++it) { - curmob = it->second; - if (curmob == center) //do not affect center + if (current_mob == caster && !affect_caster) { continue; - if (curmob == caster && !affect_caster) //watch for caster too + } + + if (DistanceSquared(center->GetPosition(), current_mob->GetPosition()) > distance_squared) { //make sure they are in range continue; - if (DistanceSquared(center->GetPosition(), curmob->GetPosition()) > dist2) //make sure they are in range - continue; - if (isnpc && curmob->IsNPC()) { //check npc->npc casting - FACTION_VALUE f = curmob->GetReverseFactionCon(caster); - if (bad) { + } + + /** + * check npc->npc casting + */ + if (is_npc && current_mob->IsNPC()) { + FACTION_VALUE faction = current_mob->GetReverseFactionCon(caster); + if (is_detrimental_spell) { //affect mobs that are on our hate list, or //which have bad faction with us - if (!(caster->CheckAggro(curmob) || f == FACTION_THREATENLY || f == FACTION_SCOWLS) ) + if (!(caster->CheckAggro(current_mob) || faction == FACTION_THREATENLY || faction == FACTION_SCOWLS)) { continue; - } else { + } + } + else { //only affect mobs we would assist. - if (!(f <= FACTION_AMIABLE)) + if (!(faction <= FACTION_AMIABLE)) { continue; + } } } - //finally, make sure they are within range - if (bad) { - if (!center->CheckLosFN(curmob)) + + /** + * LOS + */ + if (is_detrimental_spell) { + if (!center->CheckLosFN(current_mob)) { continue; - } else { // check to stop casting beneficial ae buffs (to wit: bard songs) on enemies... + } + } + else { // check to stop casting beneficial ae buffs (to wit: bard songs) on enemies... // See notes in AESpell() above for more info. - if (caster->IsAttackAllowed(curmob, true)) + if (caster->IsAttackAllowed(current_mob, true)) { continue; - if (caster->CheckAggro(curmob)) + } + if (caster->CheckAggro(current_mob)) { continue; + } } - //if we get here... cast the spell. - curmob->BardPulse(spell_id, caster); + current_mob->BardPulse(spell_id, caster); } - if (caster->IsClient()) + if (caster->IsClient()) { caster->CastToClient()->CheckSongSkillIncrease(spell_id); + } } -// Rampage and stuff for clients. Normal and Duration rampages -//NPCs handle it differently in Mob::Rampage -void EntityList::AEAttack(Mob *attacker, float dist, int Hand, int count, bool IsFromSpell) { -//Dook- Will need tweaking, currently no pets or players or horses - Mob *curmob = nullptr; +/** + * Rampage - Normal and Duration rampages + * NPCs handle it differently in Mob::Rampage + * + * @param attacker + * @param distance + * @param Hand + * @param count + * @param is_from_spell + */ +void EntityList::AEAttack( + Mob *attacker, + float distance, + int Hand, + int count, + bool is_from_spell) +{ + Mob *current_mob = nullptr; + float distance_squared = distance * distance; + int hit_count = 0; - float dist2 = dist * dist; + for (auto &it : entity_list.GetCloseMobList(attacker, distance)) { + current_mob = it.second; - int hit = 0; + if (current_mob->IsNPC() + && current_mob != attacker //this is not needed unless NPCs can use this + && (attacker->IsAttackAllowed(current_mob)) + && current_mob->GetRace() != 216 && current_mob->GetRace() != 472 /* dont attack horses */ + && (DistanceSquared(current_mob->GetPosition(), attacker->GetPosition()) <= distance_squared) + ) { - for (auto it = mob_list.begin(); it != mob_list.end(); ++it) { - curmob = it->second; - if (curmob->IsNPC() - && curmob != attacker //this is not needed unless NPCs can use this - &&(attacker->IsAttackAllowed(curmob)) - && curmob->GetRace() != 216 && curmob->GetRace() != 472 /* dont attack horses */ - && (DistanceSquared(curmob->GetPosition(), attacker->GetPosition()) <= dist2) - ) { - if (!attacker->IsClient() || attacker->GetClass() == MONK || attacker->GetClass() == RANGER) - attacker->Attack(curmob, Hand, false, false, IsFromSpell); - else - attacker->CastToClient()->DoAttackRounds(curmob, Hand, IsFromSpell); - hit++; - if (count != 0 && hit >= count) + if (!attacker->IsClient() || attacker->GetClass() == MONK || attacker->GetClass() == RANGER) { + attacker->Attack(current_mob, Hand, false, false, is_from_spell); + } + else { + attacker->CastToClient()->DoAttackRounds(current_mob, Hand, is_from_spell); + } + + hit_count++; + if (count != 0 && hit_count >= count) { return; + } } } } diff --git a/zone/embparser.cpp b/zone/embparser.cpp index 4ccb0747e..f89c09ad4 100644 --- a/zone/embparser.cpp +++ b/zone/embparser.cpp @@ -31,7 +31,7 @@ #include #include -extern Zone* zone; +extern Zone *zone; const char *QuestEventSubroutines[_LargestEventID] = { "EVENT_SAY", @@ -98,7 +98,7 @@ const char *QuestEventSubroutines[_LargestEventID] = { "EVENT_DUEL_LOSE", "EVENT_ENCOUNTER_LOAD", "EVENT_ENCOUNTER_UNLOAD", - "EVENT_SAY", + "EVENT_COMMAND", "EVENT_DROP_ITEM", "EVENT_DESTROY_ITEM", "EVENT_FEIGN_DEATH", @@ -118,110 +118,144 @@ const char *QuestEventSubroutines[_LargestEventID] = { "EVENT_SPAWN_ZONE", "EVENT_DEATH_ZONE", "EVENT_USE_SKILL", + "EVENT_COMBINE_VALIDATE", + "EVENT_BOT_COMMAND" }; -PerlembParser::PerlembParser() : perl(nullptr) { - global_npc_quest_status_ = questUnloaded; - player_quest_status_ = questUnloaded; +PerlembParser::PerlembParser() : perl(nullptr) +{ + global_npc_quest_status_ = questUnloaded; + player_quest_status_ = questUnloaded; global_player_quest_status_ = questUnloaded; } -PerlembParser::~PerlembParser() { +PerlembParser::~PerlembParser() +{ safe_delete(perl); } -void PerlembParser::ReloadQuests() { +void PerlembParser::ReloadQuests() +{ try { - if(perl == nullptr) { + if (perl == nullptr) { perl = new Embperl; - } else { + } + else { perl->Reinit(); } MapFunctions(); } - catch(std::exception &e) { - if(perl != nullptr) { + catch (std::exception &e) { + if (perl != nullptr) { delete perl; perl = nullptr; } - LogInfo("Error re-initializing perlembed: [{}]", e.what()); + LogInfo("Error Re-Initializing PerlEmbed: [{}]", e.what()); throw e.what(); } errors_.clear(); npc_quest_status_.clear(); - global_npc_quest_status_ = questUnloaded; - player_quest_status_ = questUnloaded; + global_npc_quest_status_ = questUnloaded; + player_quest_status_ = questUnloaded; global_player_quest_status_ = questUnloaded; item_quest_status_.clear(); spell_quest_status_.clear(); } -int PerlembParser::EventCommon(QuestEventID event, uint32 objid, const char * data, NPC* npcmob, EQEmu::ItemInstance* item_inst, Mob* mob, - uint32 extradata, bool global, std::vector *extra_pointers) +int PerlembParser::EventCommon( + QuestEventID event, uint32 objid, const char *data, NPC *npcmob, EQEmu::ItemInstance *item_inst, Mob *mob, + uint32 extradata, bool global, std::vector *extra_pointers +) { - if(!perl) + if (!perl) { return 0; + } - if(event >= _LargestEventID) + if (event >= _LargestEventID) { return 0; + } - bool isPlayerQuest = false; - bool isGlobalPlayerQuest = false; - bool isGlobalNPC = false; - bool isItemQuest = false; - bool isSpellQuest = false; + bool isPlayerQuest = false; + bool isGlobalPlayerQuest = false; + bool isGlobalNPC = false; + bool isItemQuest = false; + bool isSpellQuest = false; std::string package_name; - GetQuestTypes(isPlayerQuest, isGlobalPlayerQuest, isGlobalNPC, isItemQuest, isSpellQuest, - event, npcmob, item_inst, mob, global); - GetQuestPackageName(isPlayerQuest, isGlobalPlayerQuest, isGlobalNPC, isItemQuest, isSpellQuest, - package_name, event, objid, data, npcmob, item_inst, global); + GetQuestTypes( + isPlayerQuest, isGlobalPlayerQuest, isGlobalNPC, isItemQuest, isSpellQuest, + event, npcmob, item_inst, mob, global + ); + GetQuestPackageName( + isPlayerQuest, isGlobalPlayerQuest, isGlobalNPC, isItemQuest, isSpellQuest, + package_name, event, objid, data, npcmob, item_inst, global + ); const char *sub_name = QuestEventSubroutines[event]; - if(!perl->SubExists(package_name.c_str(), sub_name)) { + if (!perl->SubExists(package_name.c_str(), sub_name)) { return 0; } int char_id = 0; ExportCharID(package_name, char_id, npcmob, mob); - + /* Check for QGlobal export event enable */ - if (parse->perl_event_export_settings[event].qglobals){ - ExportQGlobals(isPlayerQuest, isGlobalPlayerQuest, isGlobalNPC, isItemQuest, isSpellQuest, package_name, npcmob, mob, char_id); + if (parse->perl_event_export_settings[event].qglobals) { + ExportQGlobals( + isPlayerQuest, + isGlobalPlayerQuest, + isGlobalNPC, + isItemQuest, + isSpellQuest, + package_name, + npcmob, + mob, + char_id + ); } /* Check for Mob export event enable */ - if (parse->perl_event_export_settings[event].mob){ - ExportMobVariables(isPlayerQuest, isGlobalPlayerQuest, isGlobalNPC, isItemQuest, isSpellQuest, package_name, mob, npcmob); + if (parse->perl_event_export_settings[event].mob) { + ExportMobVariables( + isPlayerQuest, + isGlobalPlayerQuest, + isGlobalNPC, + isItemQuest, + isSpellQuest, + package_name, + mob, + npcmob + ); } /* Check for Zone export event enable */ - if (parse->perl_event_export_settings[event].zone){ + if (parse->perl_event_export_settings[event].zone) { ExportZoneVariables(package_name); } /* Check for Item export event enable */ - if (parse->perl_event_export_settings[event].item){ + if (parse->perl_event_export_settings[event].item) { ExportItemVariables(package_name, mob); } /* Check for Event export event enable */ - if (parse->perl_event_export_settings[event].event_variables){ + if (parse->perl_event_export_settings[event].event_variables) { ExportEventVariables(package_name, event, objid, data, npcmob, item_inst, mob, extradata, extra_pointers); } - if(isPlayerQuest || isGlobalPlayerQuest){ + if (isPlayerQuest || isGlobalPlayerQuest) { return SendCommands(package_name.c_str(), sub_name, 0, mob, mob, nullptr); } - else if(isItemQuest) { + else if (isItemQuest) { return SendCommands(package_name.c_str(), sub_name, 0, mob, mob, item_inst); } - else if(isSpellQuest){ - if(mob) { + else if (isSpellQuest) { + if (mob) { return SendCommands(package_name.c_str(), sub_name, 0, mob, mob, nullptr); - } else { + } + else { return SendCommands(package_name.c_str(), sub_name, 0, npcmob, mob, nullptr); } } @@ -230,169 +264,212 @@ int PerlembParser::EventCommon(QuestEventID event, uint32 objid, const char * da } } -int PerlembParser::EventNPC(QuestEventID evt, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers) { +int PerlembParser::EventNPC( + QuestEventID evt, NPC *npc, Mob *init, std::string data, uint32 extra_data, + std::vector *extra_pointers +) +{ return EventCommon(evt, npc->GetNPCTypeID(), data.c_str(), npc, nullptr, init, extra_data, false, extra_pointers); } -int PerlembParser::EventGlobalNPC(QuestEventID evt, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers) { +int PerlembParser::EventGlobalNPC( + QuestEventID evt, NPC *npc, Mob *init, std::string data, uint32 extra_data, + std::vector *extra_pointers +) +{ return EventCommon(evt, npc->GetNPCTypeID(), data.c_str(), npc, nullptr, init, extra_data, true, extra_pointers); } -int PerlembParser::EventPlayer(QuestEventID evt, Client *client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { +int PerlembParser::EventPlayer( + QuestEventID evt, Client *client, std::string data, uint32 extra_data, + std::vector *extra_pointers +) +{ return EventCommon(evt, 0, data.c_str(), nullptr, nullptr, client, extra_data, false, extra_pointers); } -int PerlembParser::EventGlobalPlayer(QuestEventID evt, Client *client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { +int PerlembParser::EventGlobalPlayer( + QuestEventID evt, Client *client, std::string data, uint32 extra_data, + std::vector *extra_pointers +) +{ return EventCommon(evt, 0, data.c_str(), nullptr, nullptr, client, extra_data, true, extra_pointers); } -int PerlembParser::EventItem(QuestEventID evt, Client *client, EQEmu::ItemInstance *item, Mob *mob, std::string data, uint32 extra_data, - std::vector *extra_pointers) { +int PerlembParser::EventItem( + QuestEventID evt, Client *client, EQEmu::ItemInstance *item, Mob *mob, std::string data, uint32 extra_data, + std::vector *extra_pointers +) +{ // needs pointer validation on 'item' argument return EventCommon(evt, item->GetID(), nullptr, nullptr, item, client, extra_data, false, extra_pointers); } -int PerlembParser::EventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, uint32 extra_data, - std::vector *extra_pointers) { +int PerlembParser::EventSpell( + QuestEventID evt, NPC *npc, Client *client, uint32 spell_id, uint32 extra_data, + std::vector *extra_pointers +) +{ return EventCommon(evt, 0, itoa(spell_id), npc, nullptr, client, extra_data, false, extra_pointers); } -bool PerlembParser::HasQuestSub(uint32 npcid, QuestEventID evt) { +bool PerlembParser::HasQuestSub(uint32 npcid, QuestEventID evt) +{ std::stringstream package_name; package_name << "qst_npc_" << npcid; - if(!perl) + if (!perl) { return false; + } - if(evt >= _LargestEventID) + if (evt >= _LargestEventID) { return false; + } const char *subname = QuestEventSubroutines[evt]; auto iter = npc_quest_status_.find(npcid); - if(iter == npc_quest_status_.end() || iter->second == QuestFailedToLoad) { + if (iter == npc_quest_status_.end() || iter->second == QuestFailedToLoad) { return false; } - return(perl->SubExists(package_name.str().c_str(), subname)); + return (perl->SubExists(package_name.str().c_str(), subname)); } -bool PerlembParser::HasGlobalQuestSub(QuestEventID evt) { - if(!perl) - return false; - - if(global_npc_quest_status_ != questLoaded) { +bool PerlembParser::HasGlobalQuestSub(QuestEventID evt) +{ + if (!perl) { return false; } - if(evt >= _LargestEventID) + if (global_npc_quest_status_ != questLoaded) { return false; + } + + if (evt >= _LargestEventID) { + return false; + } const char *subname = QuestEventSubroutines[evt]; - return(perl->SubExists("qst_global_npc", subname)); + return (perl->SubExists("qst_global_npc", subname)); } -bool PerlembParser::PlayerHasQuestSub(QuestEventID evt) { - if(!perl) - return false; - - if(player_quest_status_ != questLoaded) { +bool PerlembParser::PlayerHasQuestSub(QuestEventID evt) +{ + if (!perl) { return false; } - if(evt >= _LargestEventID) - return false; - - const char *subname = QuestEventSubroutines[evt]; - - return(perl->SubExists("qst_player", subname)); -} - -bool PerlembParser::GlobalPlayerHasQuestSub(QuestEventID evt) { - if(!perl) - return false; - - if(global_player_quest_status_ != questLoaded) { + if (player_quest_status_ != questLoaded) { return false; } - if(evt >= _LargestEventID) + if (evt >= _LargestEventID) { return false; + } const char *subname = QuestEventSubroutines[evt]; - return(perl->SubExists("qst_global_player", subname)); + return (perl->SubExists("qst_player", subname)); } -bool PerlembParser::SpellHasQuestSub(uint32 spell_id, QuestEventID evt) { +bool PerlembParser::GlobalPlayerHasQuestSub(QuestEventID evt) +{ + if (!perl) { + return false; + } + + if (global_player_quest_status_ != questLoaded) { + return false; + } + + if (evt >= _LargestEventID) { + return false; + } + + const char *subname = QuestEventSubroutines[evt]; + + return (perl->SubExists("qst_global_player", subname)); +} + +bool PerlembParser::SpellHasQuestSub(uint32 spell_id, QuestEventID evt) +{ std::stringstream package_name; package_name << "qst_spell_" << spell_id; - if(!perl) - return false; - - auto iter = spell_quest_status_.find(spell_id); - if(iter == spell_quest_status_.end() || iter->second == QuestFailedToLoad) { + if (!perl) { return false; } - if(evt >= _LargestEventID) + auto iter = spell_quest_status_.find(spell_id); + if (iter == spell_quest_status_.end() || iter->second == QuestFailedToLoad) { return false; + } + + if (evt >= _LargestEventID) { + return false; + } const char *subname = QuestEventSubroutines[evt]; - return(perl->SubExists(package_name.str().c_str(), subname)); + return (perl->SubExists(package_name.str().c_str(), subname)); } -bool PerlembParser::ItemHasQuestSub(EQEmu::ItemInstance *itm, QuestEventID evt) { +bool PerlembParser::ItemHasQuestSub(EQEmu::ItemInstance *itm, QuestEventID evt) +{ std::stringstream package_name; package_name << "qst_item_" << itm->GetID(); - if(!perl) + if (!perl) { return false; + } - if (itm == nullptr) + if (itm == nullptr) { return false; + } - if(evt >= _LargestEventID) + if (evt >= _LargestEventID) { return false; + } const char *subname = QuestEventSubroutines[evt]; auto iter = item_quest_status_.find(itm->GetID()); - if(iter == item_quest_status_.end() || iter->second == QuestFailedToLoad) { + if (iter == item_quest_status_.end() || iter->second == QuestFailedToLoad) { return false; } - return(perl->SubExists(package_name.str().c_str(), subname)); + return (perl->SubExists(package_name.str().c_str(), subname)); } -void PerlembParser::LoadNPCScript(std::string filename, int npc_id) { +void PerlembParser::LoadNPCScript(std::string filename, int npc_id) +{ std::stringstream package_name; package_name << "qst_npc_" << npc_id; - if(!perl) + if (!perl) { return; + } auto iter = npc_quest_status_.find(npc_id); - if(iter != npc_quest_status_.end()) { + if (iter != npc_quest_status_.end()) { return; } try { perl->eval_file(package_name.str().c_str(), filename.c_str()); } - catch(const char *err) - { - std::string error = "Error compiling quest file " + filename; - error += ": "; - error += err; - AddError(error); + catch (std::string e) { + AddError( + fmt::format( + "Error Compiling NPC Quest File [{}] NPC ID [{}] Error [{}]", + filename, + npc_id, + e + ) + ); + npc_quest_status_[npc_id] = questFailedToLoad; return; } @@ -400,23 +477,28 @@ void PerlembParser::LoadNPCScript(std::string filename, int npc_id) { npc_quest_status_[npc_id] = questLoaded; } -void PerlembParser::LoadGlobalNPCScript(std::string filename) { - if(!perl) +void PerlembParser::LoadGlobalNPCScript(std::string filename) +{ + if (!perl) { return; + } - if(global_npc_quest_status_ != questUnloaded) { + if (global_npc_quest_status_ != questUnloaded) { return; } try { perl->eval_file("qst_global_npc", filename.c_str()); } - catch(const char *err) - { - std::string error = "Error compiling quest file " + filename; - error += ": "; - error += err; - AddError(error); + catch (std::string e) { + AddError( + fmt::format( + "Error Compiling Global NPC Quest File [{}] Error [{}]", + filename, + e + ) + ); + global_npc_quest_status_ = questFailedToLoad; return; } @@ -424,23 +506,28 @@ void PerlembParser::LoadGlobalNPCScript(std::string filename) { global_npc_quest_status_ = questLoaded; } -void PerlembParser::LoadPlayerScript(std::string filename) { - if(!perl) +void PerlembParser::LoadPlayerScript(std::string filename) +{ + if (!perl) { return; + } - if(player_quest_status_ != questUnloaded) { + if (player_quest_status_ != questUnloaded) { return; } try { perl->eval_file("qst_player", filename.c_str()); } - catch(const char *err) - { - std::string error = "Error compiling quest file " + filename; - error += ": "; - error += err; - AddError(error); + catch (std::string e) { + AddError( + fmt::format( + "Error Compiling Player Quest File [{}] Error [{}]", + filename, + e + ) + ); + player_quest_status_ = questFailedToLoad; return; } @@ -448,23 +535,28 @@ void PerlembParser::LoadPlayerScript(std::string filename) { player_quest_status_ = questLoaded; } -void PerlembParser::LoadGlobalPlayerScript(std::string filename) { - if(!perl) +void PerlembParser::LoadGlobalPlayerScript(std::string filename) +{ + if (!perl) { return; + } - if(global_player_quest_status_ != questUnloaded) { + if (global_player_quest_status_ != questUnloaded) { return; } try { perl->eval_file("qst_global_player", filename.c_str()); } - catch(const char *err) - { - std::string error = "Error compiling quest file " + filename; - error += ": "; - error += err; - AddError(error); + catch (std::string e) { + AddError( + fmt::format( + "Error Compiling Global Player Quest File [{}] Error [{}]", + filename, + e + ) + ); + global_player_quest_status_ = questFailedToLoad; return; } @@ -472,30 +564,37 @@ void PerlembParser::LoadGlobalPlayerScript(std::string filename) { global_player_quest_status_ = questLoaded; } -void PerlembParser::LoadItemScript(std::string filename, EQEmu::ItemInstance *item) { - if (item == nullptr) +void PerlembParser::LoadItemScript(std::string filename, EQEmu::ItemInstance *item) +{ + if (item == nullptr) { return; + } std::stringstream package_name; package_name << "qst_item_" << item->GetID(); - if(!perl) + if (!perl) { return; + } auto iter = item_quest_status_.find(item->GetID()); - if(iter != item_quest_status_.end()) { + if (iter != item_quest_status_.end()) { return; } try { perl->eval_file(package_name.str().c_str(), filename.c_str()); } - catch(const char *err) - { - std::string error = "Error compiling quest file " + filename; - error += ": "; - error += err; - AddError(error); + catch (std::string e) { + AddError( + fmt::format( + "Error Compiling Item Quest File [{}] Item ID [{}] Error [{}]", + filename, + item->GetID(), + e + ) + ); + item_quest_status_[item->GetID()] = questFailedToLoad; return; } @@ -503,27 +602,33 @@ void PerlembParser::LoadItemScript(std::string filename, EQEmu::ItemInstance *it item_quest_status_[item->GetID()] = questLoaded; } -void PerlembParser::LoadSpellScript(std::string filename, uint32 spell_id) { +void PerlembParser::LoadSpellScript(std::string filename, uint32 spell_id) +{ std::stringstream package_name; package_name << "qst_spell_" << spell_id; - if(!perl) + if (!perl) { return; + } auto iter = spell_quest_status_.find(spell_id); - if(iter != spell_quest_status_.end()) { + if (iter != spell_quest_status_.end()) { return; } try { perl->eval_file(package_name.str().c_str(), filename.c_str()); } - catch(const char *err) - { - std::string error = "Error compiling quest file " + filename; - error += ": "; - error += err; - AddError(error); + catch (std::string e) { + AddError( + fmt::format( + "Error Compiling Spell Quest File [{}] Spell ID [{}] Error [{}]", + filename, + spell_id, + e + ) + ); + spell_quest_status_[spell_id] = questFailedToLoad; return; } @@ -531,13 +636,15 @@ void PerlembParser::LoadSpellScript(std::string filename, uint32 spell_id) { spell_quest_status_[spell_id] = questLoaded; } -void PerlembParser::AddVar(std::string name, std::string val) { +void PerlembParser::AddVar(std::string name, std::string val) +{ vars_[name] = val; } -std::string PerlembParser::GetVar(std::string name) { +std::string PerlembParser::GetVar(std::string name) +{ auto iter = vars_.find(name); - if(iter != vars_.end()) { + if (iter != vars_.end()) { return iter->second; } @@ -546,126 +653,157 @@ std::string PerlembParser::GetVar(std::string name) { void PerlembParser::ExportHash(const char *pkgprefix, const char *hashname, std::map &vals) { - if(!perl) + if (!perl) { return; + } - try - { + try { perl->sethash( std::string(pkgprefix).append("::").append(hashname).c_str(), vals ); - } catch(const char *err) { - std::string error = "Error exporting hash: "; - error += err; - AddError(error); + } catch (std::string e) { + AddError( + fmt::format( + "Error exporting Perl hash [{}]", + e + ) + ); } } -void PerlembParser::ExportVar(const char * pkgprefix, const char * varname, int value) +void PerlembParser::ExportVar(const char *pkgprefix, const char *varname, int value) { - if(!perl) + if (!perl) { return; + } try { perl->seti(std::string(pkgprefix).append("::").append(varname).c_str(), value); - } catch(const char * err) { - std::string error = "Error exporting var: "; - error += err; - AddError(error); + } + catch (std::string e) { + AddError( + fmt::format( + "Error exporting Perl variable [{}]", + e + ) + ); } } -void PerlembParser::ExportVar(const char * pkgprefix, const char * varname, unsigned int value) +void PerlembParser::ExportVar(const char *pkgprefix, const char *varname, unsigned int value) { - if(!perl) + if (!perl) { return; + } try { perl->seti(std::string(pkgprefix).append("::").append(varname).c_str(), value); - } catch(const char * err) { - std::string error = "Error exporting var: "; - error += err; - AddError(error); + } catch (std::string e) { + AddError( + fmt::format( + "Error exporting Perl variable [{}]", + e + ) + ); } } -void PerlembParser::ExportVar(const char * pkgprefix, const char * varname, float value) +void PerlembParser::ExportVar(const char *pkgprefix, const char *varname, float value) { - if(!perl) + if (!perl) { return; + } try { perl->setd(std::string(pkgprefix).append("::").append(varname).c_str(), value); - } catch(const char * err) { - std::string error = "Error exporting var: "; - error += err; - AddError(error); + } catch (std::string e) { + AddError( + fmt::format( + "Error exporting Perl variable [{}]", + e + ) + ); } } -void PerlembParser::ExportVarComplex(const char * pkgprefix, const char *varname, const char *value) +void PerlembParser::ExportVarComplex(const char *pkgprefix, const char *varname, const char *value) { - if(!perl) + if (!perl) { return; - try - { + } + try { perl->eval(std::string("$").append(pkgprefix).append("::").append(varname).append("=").append(value).append(";").c_str()); } - catch(const char * err) - { - std::string error = "Error exporting var: "; - error += err; - AddError(error); + catch (std::string e) { + AddError( + fmt::format( + "Error exporting Perl variable [{}]", + e + ) + ); } } void PerlembParser::ExportVar(const char *pkgprefix, const char *varname, const char *value) { - if(!perl) + if (!perl) { return; + } - try - { + try { perl->setstr(std::string(pkgprefix).append("::").append(varname).c_str(), value); } - catch(const char * err) - { - std::string error = "Error exporting var: "; - error += err; - AddError(error); + catch (std::string e) { + AddError( + fmt::format( + "Error exporting Perl variable [{}]", + e + ) + ); } } -int PerlembParser::SendCommands(const char *pkgprefix, const char *event, uint32 npcid, Mob* other, Mob* mob, EQEmu::ItemInstance* item_inst) { - if(!perl) +int PerlembParser::SendCommands( + const char *pkgprefix, + const char *event, + uint32 npcid, + Mob *other, + Mob *mob, + EQEmu::ItemInstance *item_inst +) +{ + if (!perl) { return 0; + } int ret_value = 0; - if(mob && mob->IsClient()) + if (mob && mob->IsClient()) { quest_manager.StartQuest(other, mob->CastToClient(), item_inst); - else + } + else { quest_manager.StartQuest(other, nullptr, nullptr); + } try { - std::string cmd = "package " + (std::string)(pkgprefix) + (std::string)(";"); + std::string cmd = "package " + (std::string) (pkgprefix) + (std::string) (";"); perl->eval(cmd.c_str()); #ifdef EMBPERL_XS_CLASSES { - std::string cl = (std::string)"$" + (std::string)pkgprefix + (std::string)"::client"; - std::string np = (std::string)"$" + (std::string)pkgprefix + (std::string)"::npc"; - std::string qi = (std::string)"$" + (std::string)pkgprefix + (std::string)"::questitem"; - std::string enl = (std::string)"$" + (std::string)pkgprefix + (std::string)"::entity_list"; - if(clear_vars_.find(cl) != clear_vars_.end()) { + std::string cl = (std::string) "$" + (std::string) pkgprefix + (std::string) "::client"; + std::string np = (std::string) "$" + (std::string) pkgprefix + (std::string) "::npc"; + std::string qi = (std::string) "$" + (std::string) pkgprefix + (std::string) "::questitem"; + std::string enl = (std::string) "$" + (std::string) pkgprefix + (std::string) "::entity_list"; + if (clear_vars_.find(cl) != clear_vars_.end()) { std::string eval_str = cl; eval_str += " = undef;"; perl->eval(eval_str.c_str()); @@ -696,15 +834,16 @@ int PerlembParser::SendCommands(const char *pkgprefix, const char *event, uint32 Client *curc = quest_manager.GetInitiator(); snprintf(namebuf, 64, "%s::client", pkgprefix); SV *client = get_sv(namebuf, true); - if(curc != nullptr) { + if (curc != nullptr) { sv_setref_pv(client, "Client", curc); - } else { + } + else { //clear out the value, mainly to get rid of blessedness sv_setsv(client, _empty_sv); } //only export NPC if it's a npc quest - if(!other->IsClient()){ + if (!other->IsClient()) { NPC *curn = quest_manager.GetNPC(); snprintf(namebuf, 64, "%s::npc", pkgprefix); SV *npc = get_sv(namebuf, true); @@ -712,8 +851,8 @@ int PerlembParser::SendCommands(const char *pkgprefix, const char *event, uint32 } //only export QuestItem if it's an item quest - if(item_inst) { - EQEmu::ItemInstance* curi = quest_manager.GetQuestItem(); + if (item_inst) { + EQEmu::ItemInstance *curi = quest_manager.GetQuestItem(); snprintf(namebuf, 64, "%s::questitem", pkgprefix); SV *questitem = get_sv(namebuf, true); sv_setref_pv(questitem, "QuestItem", curi); @@ -729,40 +868,35 @@ int PerlembParser::SendCommands(const char *pkgprefix, const char *event, uint32 #ifdef EMBPERL_XS_CLASSES { - std::string cl = (std::string)"$" + (std::string)pkgprefix + (std::string)"::client"; - std::string np = (std::string)"$" + (std::string)pkgprefix + (std::string)"::npc"; - std::string qi = (std::string)"$" + (std::string)pkgprefix + (std::string)"::questitem"; - std::string enl = (std::string)"$" + (std::string)pkgprefix + (std::string)"::entity_list"; - clear_vars_[cl] = 1; - clear_vars_[np] = 1; - clear_vars_[qi] = 1; + std::string cl = (std::string) "$" + (std::string) pkgprefix + (std::string) "::client"; + std::string np = (std::string) "$" + (std::string) pkgprefix + (std::string) "::npc"; + std::string qi = (std::string) "$" + (std::string) pkgprefix + (std::string) "::questitem"; + std::string enl = (std::string) "$" + (std::string) pkgprefix + (std::string) "::entity_list"; + clear_vars_[cl] = 1; + clear_vars_[np] = 1; + clear_vars_[qi] = 1; clear_vars_[enl] = 1; } #endif - } catch(const char * err) { - - //try to reduce some of the console spam... - //todo: tweak this to be more accurate at deciding what to filter (we don't want to gag legit errors) - if(!strstr(err,"Undefined subroutine")) { - std::string error = "Script error: "; - error += pkgprefix; - error += "::"; - error += event; - error += " - "; - if(strlen(err) > 0) - error += err; - AddError(error); - } + } catch (std::string e) { + AddError( + fmt::format( + "Script Error | Package [{}] Event [{}] Error [{}]", + pkgprefix, + event, + trim(e) + ) + ); } quest_manager.EndQuest(); #ifdef EMBPERL_XS_CLASSES - if(!quest_manager.QuestsRunning()) { - auto iter = clear_vars_.begin(); + if (!quest_manager.QuestsRunning()) { + auto iter = clear_vars_.begin(); std::string eval_str; - while(iter != clear_vars_.end()) { + while (iter != clear_vars_.end()) { eval_str += iter->first; eval_str += " = undef;"; ++iter; @@ -772,11 +906,13 @@ int PerlembParser::SendCommands(const char *pkgprefix, const char *event, uint32 try { perl->eval(eval_str.c_str()); } - catch (const char * err) { - std::string error = "Script clear error: "; - if (strlen(err) > 0) - error += err; - AddError(error); + catch (std::string e) { + AddError( + fmt::format( + "Script Clear Error | Error [{}]", + e + ) + ); } } #endif @@ -784,198 +920,221 @@ int PerlembParser::SendCommands(const char *pkgprefix, const char *event, uint32 return ret_value; } -void PerlembParser::MapFunctions() { +void PerlembParser::MapFunctions() +{ _empty_sv = newSV(0); perl->eval( - "{" - "package quest;" - "&boot_quest;" //load our quest XS -#ifdef EMBPERL_XS_CLASSES - "package Mob;" - "&boot_Mob;" //load our Mob XS + "{" + "package quest;" + "&boot_quest;" //load our quest XS + #ifdef EMBPERL_XS_CLASSES + "package Mob;" + "&boot_Mob;" //load our Mob XS - "package Client;" - "our @ISA = qw(Mob);" //client inherits mob. - "&boot_Mob;" //load our Mob XS - "&boot_Client;" //load our Client XS + "package Client;" + "our @ISA = qw(Mob);" //client inherits mob. + "&boot_Mob;" //load our Mob XS + "&boot_Client;" //load our Client XS - "package NPC;" - "our @ISA = qw(Mob);" //NPC inherits mob. - "&boot_Mob;" //load our Mob XS - "&boot_NPC;" //load our NPC XS + "package NPC;" + "our @ISA = qw(Mob);" //NPC inherits mob. + "&boot_Mob;" //load our Mob XS + "&boot_NPC;" //load our NPC XS - "package Corpse;" - "our @ISA = qw(Mob);" //Corpse inherits mob. - "&boot_Mob;" //load our Mob XS - "&boot_Corpse;" //load our Mob XS + "package Corpse;" + "our @ISA = qw(Mob);" //Corpse inherits mob. + "&boot_Mob;" //load our Mob XS + "&boot_Corpse;" //load our Mob XS - "package EntityList;" - "&boot_EntityList;" //load our EntityList XS + "package EntityList;" + "&boot_EntityList;" //load our EntityList XS - "package PerlPacket;" - "&boot_PerlPacket;" //load our PerlPacket XS + "package PerlPacket;" + "&boot_PerlPacket;" //load our PerlPacket XS - "package Group;" - "&boot_Group;" //load our Group XS + "package Group;" + "&boot_Group;" //load our Group XS - "package Raid;" - "&boot_Raid;" //load our Raid XS + "package Raid;" + "&boot_Raid;" //load our Raid XS - "package QuestItem;" - "&boot_QuestItem;" // load quest Item XS + "package QuestItem;" + "&boot_QuestItem;" // load quest Item XS - "package HateEntry;" - "&boot_HateEntry;" // load quest Hate XS + "package HateEntry;" + "&boot_HateEntry;" // load quest Hate XS - "package Object;" - "&boot_Object;" // load quest Object XS + "package Object;" + "&boot_Object;" // load quest Object XS - "package Doors;" - "&boot_Doors;" // load quest Doors XS + "package Doors;" + "&boot_Doors;" // load quest Doors XS -#endif - "package main;" - "}" + #endif + "package main;" + "}" ); } -void PerlembParser::GetQuestTypes(bool &isPlayerQuest, bool &isGlobalPlayerQuest, bool &isGlobalNPC, bool &isItemQuest, - bool &isSpellQuest, QuestEventID event, NPC* npcmob, EQEmu::ItemInstance* item_inst, Mob* mob, bool global) +void PerlembParser::GetQuestTypes( + bool &isPlayerQuest, bool &isGlobalPlayerQuest, bool &isGlobalNPC, bool &isItemQuest, + bool &isSpellQuest, QuestEventID event, NPC *npcmob, EQEmu::ItemInstance *item_inst, Mob *mob, bool global +) { - if(event == EVENT_SPELL_EFFECT_CLIENT || + if (event == EVENT_SPELL_EFFECT_CLIENT || event == EVENT_SPELL_EFFECT_NPC || event == EVENT_SPELL_BUFF_TIC_CLIENT || event == EVENT_SPELL_BUFF_TIC_NPC || event == EVENT_SPELL_FADE || - event == EVENT_SPELL_EFFECT_TRANSLOCATE_COMPLETE) - { + event == EVENT_SPELL_EFFECT_TRANSLOCATE_COMPLETE) { isSpellQuest = true; } - else - { - if(!npcmob && mob) { - if(!item_inst) { - if(global) { + else { + if (!npcmob && mob) { + if (!item_inst) { + if (global) { isGlobalPlayerQuest = true; - } else { + } + else { isPlayerQuest = true; } } - else + else { isItemQuest = true; + } } } } -void PerlembParser::GetQuestPackageName(bool &isPlayerQuest, bool &isGlobalPlayerQuest, bool &isGlobalNPC, bool &isItemQuest, - bool &isSpellQuest, std::string &package_name, QuestEventID event, uint32 objid, const char * data, - NPC* npcmob, EQEmu::ItemInstance* item_inst, bool global) +void PerlembParser::GetQuestPackageName( + bool &isPlayerQuest, + bool &isGlobalPlayerQuest, + bool &isGlobalNPC, + bool &isItemQuest, + bool &isSpellQuest, + std::string &package_name, + QuestEventID event, + uint32 objid, + const char *data, + NPC *npcmob, + EQEmu::ItemInstance *item_inst, + bool global +) { - if(!isPlayerQuest && !isGlobalPlayerQuest && !isItemQuest && !isSpellQuest) { - if(global) { - isGlobalNPC = true; + if (!isPlayerQuest && !isGlobalPlayerQuest && !isItemQuest && !isSpellQuest) { + if (global) { + isGlobalNPC = true; package_name = "qst_global_npc"; - } else { + } + else { package_name = "qst_npc_"; package_name += itoa(npcmob->GetNPCTypeID()); } } - else if(isItemQuest) { + else if (isItemQuest) { // need a valid EQEmu::ItemInstance pointer check here..unsure how to cancel this process - const EQEmu::ItemData* item = item_inst->GetItem(); + const EQEmu::ItemData *item = item_inst->GetItem(); package_name = "qst_item_"; package_name += itoa(item->ID); } - else if(isPlayerQuest) { + else if (isPlayerQuest) { package_name = "qst_player"; } - else if(isGlobalPlayerQuest) { + else if (isGlobalPlayerQuest) { package_name = "qst_global_player"; } - else - { + else { package_name = "qst_spell_"; package_name += data; } } -void PerlembParser::ExportCharID(const std::string &package_name, int &char_id, NPC *npcmob, Mob *mob) { +void PerlembParser::ExportCharID(const std::string &package_name, int &char_id, NPC *npcmob, Mob *mob) +{ if (mob && mob->IsClient()) { // some events like waypoint and spawn don't have a player involved char_id = mob->CastToClient()->CharacterID(); - } else { - if(npcmob) - { + } + else { + if (npcmob) { char_id = -static_cast(npcmob->GetNPCTypeID()); // make char id negative npc id as a fudge } - else if(mob && mob->IsNPC()) - { + else if (mob && mob->IsNPC()) { char_id = -static_cast(mob->CastToNPC()->GetNPCTypeID()); // make char id negative npc id as a fudge } } ExportVar(package_name.c_str(), "charid", char_id); } -void PerlembParser::ExportQGlobals(bool isPlayerQuest, bool isGlobalPlayerQuest, bool isGlobalNPC, bool isItemQuest, - bool isSpellQuest, std::string &package_name, NPC *npcmob, Mob *mob, int char_id) { +void PerlembParser::ExportQGlobals( + bool isPlayerQuest, bool isGlobalPlayerQuest, bool isGlobalNPC, bool isItemQuest, + bool isSpellQuest, std::string &package_name, NPC *npcmob, Mob *mob, int char_id +) +{ //NPC quest - if(!isPlayerQuest && !isGlobalPlayerQuest && !isItemQuest && !isSpellQuest) - { + if (!isPlayerQuest && !isGlobalPlayerQuest && !isItemQuest && !isSpellQuest) { //only export for npcs that are global enabled. - if(npcmob && npcmob->GetQglobal()) - { + if (npcmob && npcmob->GetQglobal()) { std::map globhash; - QGlobalCache *npc_c = nullptr; - QGlobalCache *char_c = nullptr; - QGlobalCache *zone_c = nullptr; + QGlobalCache *npc_c = nullptr; + QGlobalCache *char_c = nullptr; + QGlobalCache *zone_c = nullptr; //retrieve our globals - npc_c = npcmob->GetQGlobals(); - if(mob && mob->IsClient()) + npc_c = npcmob->GetQGlobals(); + if (mob && mob->IsClient()) { char_c = mob->CastToClient()->GetQGlobals(); + } zone_c = zone->GetQGlobals(); - if(!npc_c) - { + if (!npc_c) { npc_c = npcmob->CreateQGlobals(); npc_c->LoadByNPCID(npcmob->GetNPCTypeID()); } - if(!char_c) - { - if(mob && mob->IsClient()) - { + if (!char_c) { + if (mob && mob->IsClient()) { char_c = mob->CastToClient()->CreateQGlobals(); char_c->LoadByCharID(mob->CastToClient()->CharacterID()); } } - if(!zone_c) - { + if (!zone_c) { zone_c = zone->CreateQGlobals(); zone_c->LoadByZoneID(zone->GetZoneID()); zone_c->LoadByGlobalContext(); } std::list globalMap; - if(npc_c) - { - QGlobalCache::Combine(globalMap, npc_c->GetBucket(), npcmob->GetNPCTypeID(), char_id, zone->GetZoneID()); + if (npc_c) { + QGlobalCache::Combine( + globalMap, + npc_c->GetBucket(), + npcmob->GetNPCTypeID(), + char_id, + zone->GetZoneID()); } - if(char_c) - { - QGlobalCache::Combine(globalMap, char_c->GetBucket(), npcmob->GetNPCTypeID(), char_id, zone->GetZoneID()); + if (char_c) { + QGlobalCache::Combine( + globalMap, + char_c->GetBucket(), + npcmob->GetNPCTypeID(), + char_id, + zone->GetZoneID()); } - if(zone_c) - { - QGlobalCache::Combine(globalMap, zone_c->GetBucket(), npcmob->GetNPCTypeID(), char_id, zone->GetZoneID()); + if (zone_c) { + QGlobalCache::Combine( + globalMap, + zone_c->GetBucket(), + npcmob->GetNPCTypeID(), + char_id, + zone->GetZoneID()); } auto iter = globalMap.begin(); - while(iter != globalMap.end()) - { + while (iter != globalMap.end()) { globhash[(*iter).name] = (*iter).value; ExportVar(package_name.c_str(), (*iter).name.c_str(), (*iter).value.c_str()); ++iter; @@ -983,47 +1142,41 @@ void PerlembParser::ExportQGlobals(bool isPlayerQuest, bool isGlobalPlayerQuest, ExportHash(package_name.c_str(), "qglobals", globhash); } } - else - { + else { std::map globhash; - QGlobalCache *char_c = nullptr; - QGlobalCache *zone_c = nullptr; + QGlobalCache *char_c = nullptr; + QGlobalCache *zone_c = nullptr; //retrieve our globals - if(mob && mob->IsClient()) + if (mob && mob->IsClient()) { char_c = mob->CastToClient()->GetQGlobals(); - zone_c = zone->GetQGlobals(); + } + zone_c = zone->GetQGlobals(); - if(!char_c) - { - if(mob && mob->IsClient()) - { + if (!char_c) { + if (mob && mob->IsClient()) { char_c = mob->CastToClient()->CreateQGlobals(); char_c->LoadByCharID(mob->CastToClient()->CharacterID()); } } - if(!zone_c) - { + if (!zone_c) { zone_c = zone->CreateQGlobals(); zone_c->LoadByZoneID(zone->GetZoneID()); zone_c->LoadByGlobalContext(); } std::list globalMap; - if(char_c) - { + if (char_c) { QGlobalCache::Combine(globalMap, char_c->GetBucket(), 0, char_id, zone->GetZoneID()); } - if(zone_c) - { + if (zone_c) { QGlobalCache::Combine(globalMap, zone_c->GetBucket(), 0, char_id, zone->GetZoneID()); } auto iter = globalMap.begin(); - while(iter != globalMap.end()) - { + while (iter != globalMap.end()) { globhash[(*iter).name] = (*iter).value; ExportVar(package_name.c_str(), (*iter).name.c_str(), (*iter).value.c_str()); ++iter; @@ -1032,8 +1185,10 @@ void PerlembParser::ExportQGlobals(bool isPlayerQuest, bool isGlobalPlayerQuest, } } -void PerlembParser::ExportMobVariables(bool isPlayerQuest, bool isGlobalPlayerQuest, bool isGlobalNPC, bool isItemQuest, - bool isSpellQuest, std::string &package_name, Mob *mob, NPC *npcmob) +void PerlembParser::ExportMobVariables( + bool isPlayerQuest, bool isGlobalPlayerQuest, bool isGlobalNPC, bool isItemQuest, + bool isSpellQuest, std::string &package_name, Mob *mob, NPC *npcmob +) { uint8 fac = 0; if (mob && mob->IsClient()) { @@ -1042,16 +1197,18 @@ void PerlembParser::ExportMobVariables(bool isPlayerQuest, bool isGlobalPlayerQu ExportVar(package_name.c_str(), "status", mob->CastToClient()->Admin()); } - if(!isPlayerQuest && !isGlobalPlayerQuest && !isItemQuest) { + if (!isPlayerQuest && !isGlobalPlayerQuest && !isItemQuest) { if (mob && npcmob && mob->IsClient()) { - Client* client = mob->CastToClient(); + Client *client = mob->CastToClient(); - fac = client->GetFactionLevel(client->CharacterID(), npcmob->GetID(), client->GetFactionRace(), - client->GetClass(), client->GetDeity(), npcmob->GetPrimaryFaction(), npcmob); + fac = client->GetFactionLevel( + client->CharacterID(), npcmob->GetID(), client->GetFactionRace(), + client->GetClass(), client->GetDeity(), npcmob->GetPrimaryFaction(), npcmob + ); } } - if(mob) { + if (mob) { ExportVar(package_name.c_str(), "name", mob->GetName()); ExportVar(package_name.c_str(), "race", GetRaceIDName(mob->GetRace())); ExportVar(package_name.c_str(), "class", GetClassIDName(mob->GetClass())); @@ -1059,19 +1216,17 @@ void PerlembParser::ExportMobVariables(bool isPlayerQuest, bool isGlobalPlayerQu ExportVar(package_name.c_str(), "userid", mob->GetID()); } - if(!isPlayerQuest && !isGlobalPlayerQuest && !isItemQuest && !isSpellQuest) - { - if (npcmob) - { + if (!isPlayerQuest && !isGlobalPlayerQuest && !isItemQuest && !isSpellQuest) { + if (npcmob) { ExportVar(package_name.c_str(), "mname", npcmob->GetName()); ExportVar(package_name.c_str(), "mobid", npcmob->GetID()); ExportVar(package_name.c_str(), "mlevel", npcmob->GetLevel()); - ExportVar(package_name.c_str(), "hpratio",npcmob->GetHPRatio()); - ExportVar(package_name.c_str(), "x", npcmob->GetX() ); - ExportVar(package_name.c_str(), "y", npcmob->GetY() ); - ExportVar(package_name.c_str(), "z", npcmob->GetZ() ); - ExportVar(package_name.c_str(), "h", npcmob->GetHeading() ); - if(npcmob->GetTarget()) { + ExportVar(package_name.c_str(), "hpratio", npcmob->GetHPRatio()); + ExportVar(package_name.c_str(), "x", npcmob->GetX()); + ExportVar(package_name.c_str(), "y", npcmob->GetY()); + ExportVar(package_name.c_str(), "z", npcmob->GetZ()); + ExportVar(package_name.c_str(), "h", npcmob->GetHeading()); + if (npcmob->GetTarget()) { ExportVar(package_name.c_str(), "targetid", npcmob->GetTarget()->GetID()); ExportVar(package_name.c_str(), "targetname", npcmob->GetTarget()->GetName()); } @@ -1083,7 +1238,8 @@ void PerlembParser::ExportMobVariables(bool isPlayerQuest, bool isGlobalPlayerQu } } -void PerlembParser::ExportZoneVariables(std::string &package_name) { +void PerlembParser::ExportZoneVariables(std::string &package_name) +{ if (zone) { ExportVar(package_name.c_str(), "zoneid", zone->GetZoneID()); ExportVar(package_name.c_str(), "zoneln", zone->GetLongName()); @@ -1091,7 +1247,7 @@ void PerlembParser::ExportZoneVariables(std::string &package_name) { ExportVar(package_name.c_str(), "instanceid", zone->GetInstanceID()); ExportVar(package_name.c_str(), "instanceversion", zone->GetInstanceVersion()); TimeOfDay_Struct eqTime; - zone->zone_time.GetCurrentEQTimeOfDay( time(0), &eqTime); + zone->zone_time.GetCurrentEQTimeOfDay(time(0), &eqTime); ExportVar(package_name.c_str(), "zonehour", eqTime.hour - 1); ExportVar(package_name.c_str(), "zonemin", eqTime.minute); ExportVar(package_name.c_str(), "zonetime", (eqTime.hour - 1) * 100 + eqTime.minute); @@ -1099,20 +1255,18 @@ void PerlembParser::ExportZoneVariables(std::string &package_name) { } } -void PerlembParser::ExportItemVariables(std::string &package_name, Mob *mob) { - if(mob && mob->IsClient()) - { +void PerlembParser::ExportItemVariables(std::string &package_name, Mob *mob) +{ + if (mob && mob->IsClient()) { std::string hashname = package_name + std::string("::hasitem"); //start with an empty hash perl->eval(std::string("%").append(hashname).append(" = ();").c_str()); - for(int slot = EQEmu::invslot::EQUIPMENT_BEGIN; slot <= EQEmu::invslot::GENERAL_END; slot++) - { - char *hi_decl=nullptr; - int itemid = mob->CastToClient()->GetItemIDAt(slot); - if(itemid != -1 && itemid != 0) - { + for (int slot = EQEmu::invslot::EQUIPMENT_BEGIN; slot <= EQEmu::invslot::GENERAL_END; slot++) { + char *hi_decl = nullptr; + int itemid = mob->CastToClient()->GetItemIDAt(slot); + if (itemid != -1 && itemid != 0) { MakeAnyLenString(&hi_decl, "push (@{$%s{%d}},%d);", hashname.c_str(), itemid, slot); perl->eval(hi_decl); safe_delete_array(hi_decl); @@ -1120,25 +1274,27 @@ void PerlembParser::ExportItemVariables(std::string &package_name, Mob *mob) { } } - if(mob && mob->IsClient()) { + if (mob && mob->IsClient()) { std::string hashname = package_name + std::string("::oncursor"); perl->eval(std::string("%").append(hashname).append(" = ();").c_str()); char *hi_decl = nullptr; - int itemid = mob->CastToClient()->GetItemIDAt(EQEmu::invslot::slotCursor); - if(itemid != -1 && itemid != 0) { - MakeAnyLenString(&hi_decl, "push (@{$%s{%d}},%d);",hashname.c_str(), itemid, EQEmu::invslot::slotCursor); + int itemid = mob->CastToClient()->GetItemIDAt(EQEmu::invslot::slotCursor); + if (itemid != -1 && itemid != 0) { + MakeAnyLenString(&hi_decl, "push (@{$%s{%d}},%d);", hashname.c_str(), itemid, EQEmu::invslot::slotCursor); perl->eval(hi_decl); safe_delete_array(hi_decl); } } } -void PerlembParser::ExportEventVariables(std::string &package_name, QuestEventID event, uint32 objid, const char * data, - NPC* npcmob, EQEmu::ItemInstance* item_inst, Mob* mob, uint32 extradata, std::vector *extra_pointers) +void PerlembParser::ExportEventVariables( + std::string &package_name, QuestEventID event, uint32 objid, const char *data, + NPC *npcmob, EQEmu::ItemInstance *item_inst, Mob *mob, uint32 extradata, std::vector *extra_pointers +) { switch (event) { case EVENT_SAY: { - if(npcmob && mob) { + if (npcmob && mob) { npcmob->DoQuestPause(mob); } @@ -1149,15 +1305,15 @@ void PerlembParser::ExportEventVariables(std::string &package_name, QuestEventID } case EVENT_TRADE: { - if(extra_pointers) { - size_t sz = extra_pointers->size(); - for(size_t i = 0; i < sz; ++i) { - EQEmu::ItemInstance *inst = EQEmu::any_cast(extra_pointers->at(i)); + if (extra_pointers) { + size_t sz = extra_pointers->size(); + for (size_t i = 0; i < sz; ++i) { + EQEmu::ItemInstance *inst = EQEmu::any_cast(extra_pointers->at(i)); std::string var_name = "item"; var_name += std::to_string(i + 1); - if(inst) { + if (inst) { ExportVar(package_name.c_str(), var_name.c_str(), inst->GetItem()->ID); std::string temp_var_name = var_name; @@ -1167,7 +1323,8 @@ void PerlembParser::ExportEventVariables(std::string &package_name, QuestEventID temp_var_name = var_name; temp_var_name += "_attuned"; ExportVar(package_name.c_str(), temp_var_name.c_str(), inst->IsAttuned()); - } else { + } + else { ExportVar(package_name.c_str(), var_name.c_str(), 0); std::string temp_var_name = var_name; @@ -1205,8 +1362,7 @@ void PerlembParser::ExportEventVariables(std::string &package_name, QuestEventID ExportVar(package_name.c_str(), "hpevent", "-1"); ExportVar(package_name.c_str(), "inchpevent", data); } - else - { + else { ExportVar(package_name.c_str(), "hpevent", data); ExportVar(package_name.c_str(), "inchpevent", "-1"); } @@ -1247,7 +1403,7 @@ void PerlembParser::ExportEventVariables(std::string &package_name, QuestEventID break; } - case EVENT_ZONE:{ + case EVENT_ZONE: { ExportVar(package_name.c_str(), "target_zone_id", data); break; } @@ -1259,26 +1415,26 @@ void PerlembParser::ExportEventVariables(std::string &package_name, QuestEventID break; } - case EVENT_TASK_ACCEPTED:{ + case EVENT_TASK_ACCEPTED: { ExportVar(package_name.c_str(), "task_id", data); break; } - case EVENT_TASK_STAGE_COMPLETE:{ + case EVENT_TASK_STAGE_COMPLETE: { Seperator sep(data); ExportVar(package_name.c_str(), "task_id", sep.arg[0]); ExportVar(package_name.c_str(), "activity_id", sep.arg[1]); break; } - case EVENT_TASK_FAIL:{ + case EVENT_TASK_FAIL: { Seperator sep(data); ExportVar(package_name.c_str(), "task_id", sep.arg[0]); break; } case EVENT_TASK_COMPLETE: - case EVENT_TASK_UPDATE:{ + case EVENT_TASK_UPDATE: { Seperator sep(data); ExportVar(package_name.c_str(), "donecount", sep.arg[0]); ExportVar(package_name.c_str(), "activity_id", sep.arg[1]); @@ -1286,7 +1442,7 @@ void PerlembParser::ExportEventVariables(std::string &package_name, QuestEventID break; } - case EVENT_PLAYER_PICKUP:{ + case EVENT_PLAYER_PICKUP: { ExportVar(package_name.c_str(), "picked_up_id", data); ExportVar(package_name.c_str(), "picked_up_entity_id", extradata); break; @@ -1299,11 +1455,11 @@ void PerlembParser::ExportEventVariables(std::string &package_name, QuestEventID break; } - case EVENT_POPUP_RESPONSE:{ + case EVENT_POPUP_RESPONSE: { ExportVar(package_name.c_str(), "popupid", data); break; } - case EVENT_ENVIRONMENTAL_DAMAGE:{ + case EVENT_ENVIRONMENTAL_DAMAGE: { Seperator sep(data); ExportVar(package_name.c_str(), "env_damage", sep.arg[0]); ExportVar(package_name.c_str(), "env_damage_type", sep.arg[1]); @@ -1337,8 +1493,7 @@ void PerlembParser::ExportEventVariables(std::string &package_name, QuestEventID } case EVENT_GROUP_CHANGE: { - if(mob && mob->IsClient()) - { + if (mob && mob->IsClient()) { ExportVar(package_name.c_str(), "grouped", mob->IsGrouped()); ExportVar(package_name.c_str(), "raided", mob->IsRaidGrouped()); } @@ -1353,16 +1508,14 @@ void PerlembParser::ExportEventVariables(std::string &package_name, QuestEventID case EVENT_SPELL_EFFECT_CLIENT: case EVENT_SPELL_EFFECT_NPC: case EVENT_SPELL_BUFF_TIC_CLIENT: - case EVENT_SPELL_BUFF_TIC_NPC: - { + case EVENT_SPELL_BUFF_TIC_NPC: { ExportVar(package_name.c_str(), "caster_id", extradata); break; } - //tradeskill events + //tradeskill events case EVENT_COMBINE_SUCCESS: - case EVENT_COMBINE_FAILURE: - { + case EVENT_COMBINE_FAILURE: { ExportVar(package_name.c_str(), "recipe_id", extradata); ExportVar(package_name.c_str(), "recipe_name", data); break; @@ -1390,9 +1543,12 @@ void PerlembParser::ExportEventVariables(std::string &package_name, QuestEventID } case EVENT_COMMAND: { + Seperator sep(data); + ExportVar(package_name.c_str(), "command", (sep.arg[0] + 1)); + ExportVar(package_name.c_str(), "args", (sep.argnum >= 1 ? (&data[strlen(sep.arg[0]) + 1]) : "0")); + ExportVar(package_name.c_str(), "data", objid); ExportVar(package_name.c_str(), "text", data); - ExportVar(package_name.c_str(), "data", "0"); - ExportVar(package_name.c_str(), "langid", "0"); + ExportVar(package_name.c_str(), "langid", extradata); break; } @@ -1434,12 +1590,39 @@ void PerlembParser::ExportEventVariables(std::string &package_name, QuestEventID ExportVar(package_name.c_str(), "killed_npc_id", sep.arg[4]); break; } - case EVENT_USE_SKILL:{ + case EVENT_USE_SKILL: { Seperator sep(data); ExportVar(package_name.c_str(), "skill_id", sep.arg[0]); ExportVar(package_name.c_str(), "skill_level", sep.arg[1]); break; } + case EVENT_COMBINE_VALIDATE: { + Seperator sep(data); + ExportVar(package_name.c_str(), "recipe_id", extradata); + ExportVar(package_name.c_str(), "validate_type", sep.arg[0]); + + std::string zone_id = "-1"; + std::string tradeskill_id = "-1"; + if (strcmp(sep.arg[0], "check_zone") == 0) { + zone_id = sep.arg[1]; + } + else if (strcmp(sep.arg[0], "check_tradeskill") == 0) { + tradeskill_id = sep.arg[1]; + } + + ExportVar(package_name.c_str(), "zone_id", zone_id.c_str()); + ExportVar(package_name.c_str(), "tradeskill_id", tradeskill_id.c_str()); + break; + } + case EVENT_BOT_COMMAND: { + Seperator sep(data); + ExportVar(package_name.c_str(), "bot_command", (sep.arg[0] + 1)); + ExportVar(package_name.c_str(), "args", (sep.argnum >= 1 ? (&data[strlen(sep.arg[0]) + 1]) : "0")); + ExportVar(package_name.c_str(), "data", objid); + ExportVar(package_name.c_str(), "text", data); + ExportVar(package_name.c_str(), "langid", extradata); + break; + } default: { break; diff --git a/zone/embparser_api.cpp b/zone/embparser_api.cpp index c29c28e20..871cc0ced 100644 --- a/zone/embparser_api.cpp +++ b/zone/embparser_api.cpp @@ -1812,6 +1812,43 @@ XS(XS__summonallplayercorpses) { XSRETURN(1); } +XS(XS__getplayercorpsecount); +XS(XS__getplayercorpsecount) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::getplayercorpsecount(uint32 char_id)"); + + uint32 RETVAL; + dXSTARG; + + uint32 char_id = (int) SvIV(ST(0)); + + RETVAL = quest_manager.getplayercorpsecount(char_id); + XSprePUSH; + PUSHu((IV) RETVAL); + + XSRETURN(1); +} + +XS(XS__getplayercorpsecountbyzoneid); +XS(XS__getplayercorpsecountbyzoneid) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::getplayercorpsecountbyzoneid(uint32 char_id, uint32 zone_id)"); + + uint32 RETVAL; + dXSTARG; + + uint32 char_id = (int) SvIV(ST(0)); + uint32 zone_id = (int)SvIV(ST(1)); + + RETVAL = quest_manager.getplayercorpsecountbyzoneid(char_id, zone_id); + XSprePUSH; + PUSHu((IV) RETVAL); + + XSRETURN(1); +} + XS(XS__getplayerburiedcorpsecount); XS(XS__getplayerburiedcorpsecount) { dXSARGS; @@ -3907,6 +3944,8 @@ EXTERN_C XS(boot_quest) { newXS(strcpy(buf, "getguildnamebyid"), XS__getguildnamebyid, file); newXS(strcpy(buf, "getlevel"), XS__getlevel, file); newXS(strcpy(buf, "getplayerburiedcorpsecount"), XS__getplayerburiedcorpsecount, file); + newXS(strcpy(buf, "getplayercorpsecount"), XS__getplayercorpsecount, file); + newXS(strcpy(buf, "getplayercorpsecountbyzoneid"), XS__getplayercorpsecountbyzoneid, file); newXS(strcpy(buf, "gettaskactivitydonecount"), XS__gettaskactivitydonecount, file); newXS(strcpy(buf, "givecash"), XS__givecash, file); newXS(strcpy(buf, "gmmove"), XS__gmmove, file); diff --git a/zone/embperl.cpp b/zone/embperl.cpp index 22c248fd7..c7ebb8060 100644 --- a/zone/embperl.cpp +++ b/zone/embperl.cpp @@ -137,10 +137,10 @@ void Embperl::DoInit() { try { init_eval_file(); } - catch(const char *err) + catch(std::string e) { //remember... lasterr() is no good if we crap out here, in construction - LogQuests("perl error: [{}]", err); + LogQuests("Perl Error [{}]", e); throw "failed to install eval_file hook"; } @@ -177,9 +177,9 @@ void Embperl::DoInit() { perl_command = "main::eval_file('plugin', '" + Config->PluginPlFile + "');"; eval_pv(perl_command.c_str(), FALSE); } - catch(const char *err) + catch(std::string e) { - LogQuests("Warning - [{}]: [{}]", Config->PluginPlFile.c_str(), err); + LogQuests("Warning [{}]: [{}]", Config->PluginPlFile, e); } try { @@ -195,9 +195,9 @@ void Embperl::DoInit() { "}"; eval_pv(perl_command.c_str(),FALSE); } - catch(const char *err) + catch(std::string e) { - LogQuests("Perl warning: [{}]", err); + LogQuests("Warning [{}]", e); } #endif //EMBPERL_PLUGIN in_use = false; @@ -237,7 +237,7 @@ void Embperl::init_eval_file(void) { eval_pv( "our %Cache;" - "no warnings;" + "no warnings 'all';" "use Symbol qw(delete_package);" "sub eval_file {" "my($package, $filename) = @_;" @@ -315,7 +315,7 @@ int Embperl::dosub(const char * subname, const std::vector * args, { std::string errmsg = "Perl runtime error: "; errmsg += SvPVX(ERRSV); - throw errmsg.c_str(); + throw errmsg; } return ret_value; diff --git a/zone/embxs.cpp b/zone/embxs.cpp index b9013335b..4571a8f8f 100644 --- a/zone/embxs.cpp +++ b/zone/embxs.cpp @@ -92,35 +92,43 @@ XS(XS_EQEmuIO_PRINT) // Perl_croak(aTHX_ "Usage: EQEmuIO::PRINT(@strings)"); int r; - for(r = 1; r < items; r++) { + for (r = 1; r < items; r++) { char *str = SvPV_nolen(ST(r)); char *cur = str; - + /* Strip newlines from log message 'str' */ *std::remove(str, str + strlen(str), '\n') = '\0'; std::string log_string = str; - if (log_string.find("did not return a true") != std::string::npos) - return;; + + if (log_string.find("did not return a true") != std::string::npos) { + return; + } + + if (log_string.find("is experimental") != std::string::npos) { + return; + } int i; int pos = 0; int len = 0; - for(i = 0; *cur != '\0'; i++, cur++) { - if(*cur == '\n') { - Log(Logs::General, Logs::Quests, str); + + for (i = 0; *cur != '\0'; i++, cur++) { + if (*cur == '\n') { + LogQuests("{}", str); len = 0; - pos = i+1; - } else { + pos = i + 1; + } + else { len++; } } - if(len > 0) { - Log(Logs::General, Logs::Quests, str); + if (!log_string.empty()) { + LogQuests("{}", log_string); } - } - - XSRETURN_EMPTY; + } + + XSRETURN_EMPTY; } #endif //EMBPERL_IO_CAPTURE diff --git a/zone/entity.cpp b/zone/entity.cpp index 7f016f197..b13fb5649 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -62,7 +62,8 @@ extern char errorname[32]; Entity::Entity() { - id = 0; + id = 0; + initial_id = 0; spawn_timestamp = time(nullptr); } @@ -1582,41 +1583,73 @@ void EntityList::QueueClientsByXTarget(Mob *sender, const EQApplicationPacket *a } } -void EntityList::QueueCloseClients(Mob *sender, const EQApplicationPacket *app, - bool ignore_sender, float dist, Mob *SkipThisMob, bool ackreq, eqFilterType filter) +/** + * @param sender + * @param app + * @param ignore_sender + * @param distance + * @param skipped_mob + * @param is_ack_required + * @param filter + */ +void EntityList::QueueCloseClients( + Mob *sender, + const EQApplicationPacket *app, + bool ignore_sender, + float distance, + Mob *skipped_mob, + bool is_ack_required, + eqFilterType filter +) { if (sender == nullptr) { QueueClients(sender, app, ignore_sender); return; } - if (dist <= 0) - dist = 600; - float dist2 = dist * dist; //pow(dist, 2); + if (distance <= 0) { + distance = 600; + } - auto it = client_list.begin(); - while (it != client_list.end()) { - Client *ent = it->second; + float distance_squared = distance * distance; - if ((!ignore_sender || ent != sender) && (ent != SkipThisMob)) { - eqFilterMode filter2 = ent->GetFilter(filter); - if(ent->Connected() && - (filter == FilterNone - || filter2 == FilterShow - || (filter2 == FilterShowGroupOnly && (sender == ent || - (ent->GetGroup() && ent->GetGroup()->IsGroupMember(sender)))) - || (filter2 == FilterShowSelfOnly && ent == sender)) - && (DistanceSquared(ent->GetPosition(), sender->GetPosition()) <= dist2)) { - ent->QueuePacket(app, ackreq, Client::CLIENT_CONNECTED); + for (auto &e : GetCloseMobList(sender, distance)) { + Mob *mob = e.second; + + if (!mob->IsClient()) { + continue; + } + + Client *client = mob->CastToClient(); + + if ((!ignore_sender || client != sender) && (client != skipped_mob)) { + + if (DistanceSquared(client->GetPosition(), sender->GetPosition()) >= distance_squared) { + continue; + } + + if (!client->Connected()) { + continue; + } + + eqFilterMode client_filter = client->GetFilter(filter); + if ( + filter == FilterNone || client_filter == FilterShow || + (client_filter == FilterShowGroupOnly && + (sender == client || (client->GetGroup() && client->GetGroup()->IsGroupMember(sender)))) || + (client_filter == FilterShowSelfOnly && client == sender) + ) { + client->QueuePacket(app, is_ack_required, Client::CLIENT_CONNECTED); } } - ++it; } } //sender can be null -void EntityList::QueueClients(Mob *sender, const EQApplicationPacket *app, - bool ignore_sender, bool ackreq) +void EntityList::QueueClients( + Mob *sender, const EQApplicationPacket *app, + bool ignore_sender, bool ackreq +) { auto it = client_list.begin(); while (it != client_list.end()) { @@ -2486,43 +2519,51 @@ void EntityList::RemoveAllEncounters() } } +/** + * @param delete_id + * @return + */ bool EntityList::RemoveMob(uint16 delete_id) { - if (delete_id == 0) + if (delete_id == 0) { return true; + } auto it = mob_list.find(delete_id); if (it != mob_list.end()) { - - RemoveMobFromClientCloseLists(it->second); - - if (npc_list.count(delete_id)) + if (npc_list.count(delete_id)) { entity_list.RemoveNPC(delete_id); - else if (client_list.count(delete_id)) + } + else if (client_list.count(delete_id)) { entity_list.RemoveClient(delete_id); + } safe_delete(it->second); - if (!corpse_list.count(delete_id)) + if (!corpse_list.count(delete_id)) { free_ids.push(it->first); + } mob_list.erase(it); return true; } return false; } -// This is for if the ID is deleted for some reason +/** + * @param delete_mob + * @return + */ bool EntityList::RemoveMob(Mob *delete_mob) { - if (delete_mob == 0) + if (delete_mob == 0) { return true; + } auto it = mob_list.begin(); while (it != mob_list.end()) { if (it->second == delete_mob) { - RemoveMobFromClientCloseLists(it->second); - safe_delete(it->second); - if (!corpse_list.count(it->first)) + if (!corpse_list.count(it->first)) { free_ids.push(it->first); + } mob_list.erase(it); return true; } @@ -2531,36 +2572,114 @@ bool EntityList::RemoveMob(Mob *delete_mob) return false; } +/** + * @param delete_id + * @return + */ bool EntityList::RemoveNPC(uint16 delete_id) { auto it = npc_list.find(delete_id); if (it != npc_list.end()) { NPC *npc = it->second; - // make sure its proximity is removed RemoveProximity(delete_id); - // remove from client close lists - RemoveMobFromClientCloseLists(npc->CastToMob()); - // remove from the list npc_list.erase(it); - // remove from limit list if needed - if (npc_limit_list.count(delete_id)) + if (npc_limit_list.count(delete_id)) { npc_limit_list.erase(delete_id); + } + return true; } return false; } -bool EntityList::RemoveMobFromClientCloseLists(Mob *mob) +/** + * @param mob + * @return + */ +bool EntityList::RemoveMobFromCloseLists(Mob *mob) { - auto it = client_list.begin(); - while (it != client_list.end()) { - it->second->close_mobs.erase(mob); + uint16 entity_id = mob->GetID() > 0 ? mob->GetID() : mob->GetInitialId(); + + LogEntityManagement( + "Attempting to remove mob [{}] from close lists entity_id ({})", + mob->GetCleanName(), + entity_id + ); + + auto it = mob_list.begin(); + while (it != mob_list.end()) { + + LogEntityManagement( + "Removing mob [{}] from [{}] close list entity_id ({})", + mob->GetCleanName(), + it->second->GetCleanName(), + entity_id + ); + + it->second->close_mobs.erase(entity_id); ++it; } + return false; } +/** + * @param mob + * @return + */ +void EntityList::RemoveAuraFromMobs(Mob *aura) +{ + LogEntityManagement( + "Attempting to remove aura [{}] from mobs entity_id ({})", + aura->GetCleanName(), + aura->GetID() + ); + + for (auto &it : mob_list) { + auto mob = it.second; + mob->RemoveAura(aura->GetID()); + } +} + +/** + * @param close_mobs + * @param scanning_mob + */ +void EntityList::ScanCloseMobs(std::unordered_map &close_mobs, Mob *scanning_mob) +{ + float scan_range = RuleI(Range, MobCloseScanDistance) * RuleI(Range, MobCloseScanDistance); + + close_mobs.clear(); + + for (auto &e : mob_list) { + auto mob = e.second; + + if (!mob->IsNPC() && !mob->IsClient()) { + continue; + } + + if (mob->GetID() <= 0) { + continue; + } + + float distance = DistanceSquared(scanning_mob->GetPosition(), mob->GetPosition()); + if (distance <= scan_range) { + close_mobs.insert(std::pair(mob->GetID(), mob)); + } + else if (mob->GetAggroRange() >= scan_range) { + close_mobs.insert(std::pair(mob->GetID(), mob)); + } + } + + LogAIScanClose( + "[{}] Scanning Close List | list_size [{}] moving [{}]", + scanning_mob->GetCleanName(), + close_mobs.size(), + scanning_mob->IsMoving() ? "true" : "false" + ); +} + bool EntityList::RemoveMerc(uint16 delete_id) { auto it = merc_list.find(delete_id); @@ -4859,10 +4978,10 @@ void EntityList::GetTargetsForConeArea(Mob *start, float min_radius, float radiu continue; } // check PC/NPC only flag 1 = PCs, 2 = NPCs - if (pcnpc == 1 && !ptr->IsClient() && !ptr->IsMerc()) { + if (pcnpc == 1 && !ptr->IsClient() && !ptr->IsMerc() && !ptr->IsBot()) { ++it; continue; - } else if (pcnpc == 2 && (ptr->IsClient() || ptr->IsMerc())) { + } else if (pcnpc == 2 && (ptr->IsClient() || ptr->IsMerc() || ptr->IsBot())) { ++it; continue; } @@ -4972,3 +5091,21 @@ void EntityList::ReloadMerchants() { } } } + +/** + * If we have a distance requested that is greater than our scanning distance + * then we return the full list + * + * @param mob + * @param distance + * @return + */ +std::unordered_map &EntityList::GetCloseMobList(Mob *mob, float distance) +{ + if (distance <= RuleI(Range, MobCloseScanDistance)) { + return mob->close_mobs; + } + + return mob_list; +} + diff --git a/zone/entity.h b/zone/entity.h index 6b22f1a4c..505f34963 100644 --- a/zone/entity.h +++ b/zone/entity.h @@ -109,6 +109,7 @@ public: const Beacon *CastToBeacon() const; const Encounter *CastToEncounter() const; + inline const uint16& GetInitialId() const { return initial_id; } inline const uint16& GetID() const { return id; } inline const time_t& GetSpawnTimeStamp() const { return spawn_timestamp; } @@ -122,10 +123,17 @@ public: protected: friend class EntityList; - inline virtual void SetID(uint16 set_id) { id = set_id; } + inline virtual void SetID(uint16 set_id) { + id = set_id; + + if (initial_id == 0 && set_id > 0) { + initial_id = set_id; + } + } uint32 pDBAsyncWorkID; private: uint16 id; + uint16 initial_id; time_t spawn_timestamp; }; @@ -284,7 +292,8 @@ public: bool RemoveTrap(uint16 delete_id); bool RemoveObject(uint16 delete_id); bool RemoveProximity(uint16 delete_npc_id); - bool RemoveMobFromClientCloseLists(Mob *mob); + bool RemoveMobFromCloseLists(Mob *mob); + void RemoveAuraFromMobs(Mob *aura); void RemoveAllMobs(); void RemoveAllClients(); void RemoveAllNPCs(); @@ -376,7 +385,7 @@ public: void RemoveFromXTargets(Mob* mob); void RemoveFromAutoXTargets(Mob* mob); void ReplaceWithTarget(Mob* pOldMob, Mob*pNewTarget); - void QueueCloseClients(Mob* sender, const EQApplicationPacket* app, bool ignore_sender=false, float dist=200, Mob* SkipThisMob = 0, bool ackreq = true,eqFilterType filter=FilterNone); + void QueueCloseClients(Mob* sender, const EQApplicationPacket* app, bool ignore_sender=false, float distance=200, Mob* skipped_mob = 0, bool is_ack_required = true, eqFilterType filter=FilterNone); void QueueClients(Mob* sender, const EQApplicationPacket* app, bool ignore_sender=false, bool ackreq = true); void QueueClientsStatus(Mob* sender, const EQApplicationPacket* app, bool ignore_sender = false, uint8 minstatus = 0, uint8 maxstatus = 0); void QueueClientsGuild(Mob* sender, const EQApplicationPacket* app, bool ignore_sender = false, uint32 guildeqid = 0); @@ -388,11 +397,24 @@ public: void QueueToGroupsForNPCHealthAA(Mob* sender, const EQApplicationPacket* app); void QueueManaged(Mob* sender, const EQApplicationPacket* app, bool ignore_sender=false, bool ackreq = true); - void AEAttack(Mob *attacker, float dist, int Hand = EQEmu::invslot::slotPrimary, int count = 0, bool IsFromSpell = false); - void AETaunt(Client *caster, float range=0, int32 bonus_hate=0); - void AESpell(Mob *caster, Mob *center, uint16 spell_id, bool affect_caster = true, int16 resist_adjust = 0, int *max_targets = nullptr); - void MassGroupBuff(Mob *caster, Mob *center, uint16 spell_id, bool affect_caster = true); - void AEBardPulse(Mob *caster, Mob *center, uint16 spell_id, bool affect_caster = true); + void AEAttack( + Mob *attacker, + float distance, + int Hand = EQEmu::invslot::slotPrimary, + int count = 0, + bool is_from_spell = false + ); + void AETaunt(Client *caster, float range = 0, int32 bonus_hate = 0); + void AESpell( + Mob *caster, + Mob *center, + uint16 spell_id, + bool affect_caster = true, + int16 resist_adjust = 0, + int *max_targets = nullptr + ); + void MassGroupBuff(Mob *caster, Mob *center, uint16 spell_id, bool affect_caster = true); + void AEBardPulse(Mob *caster, Mob *center, uint16 spell_id, bool affect_caster = true); //trap stuff Mob* GetTrapTrigger(Trap* trap); @@ -443,11 +465,7 @@ public: bool LimitCheckBoth(uint32 npc_type, uint32 spawngroup_id, int group_count, int type_count); bool LimitCheckName(const char* npc_name); - void CheckClientAggro(Client *around); - Mob* AICheckNPCtoNPCAggro(Mob* sender, float iAggroRange, float iAssistRange); int GetHatedCount(Mob *attacker, Mob *exclude, bool inc_gray_con); - void AIYellForHelp(Mob* sender, Mob* attacker); - bool AICheckCloseBeneficialSpells(NPC* caster, uint8 iChance, float iRange, uint32 iSpellTypes); bool Merc_AICheckCloseBeneficialSpells(Merc* caster, uint8 iChance, float iRange, uint32 iSpellTypes); Mob* GetTargetForMez(Mob* caster); uint32 CheckNPCsClose(Mob *center); @@ -495,17 +513,21 @@ public: inline const std::unordered_map &GetObjectList() { return object_list; } inline const std::unordered_map &GetDoorsList() { return door_list; } + std::unordered_map &GetCloseMobList(Mob *mob, float distance = 0); + void DepopAll(int NPCTypeID, bool StartSpawnTimer = true); uint16 GetFreeID(); void RefreshAutoXTargets(Client *c); void RefreshClientXTargets(Client *c); void SendAlternateAdvancementStats(); + void ScanCloseMobs(std::unordered_map &close_mobs, Mob *scanning_mob); void GetTrapInfo(Client* client); bool IsTrapGroupSpawned(uint32 trap_id, uint8 group); void UpdateAllTraps(bool respawn, bool repopnow = false); void ClearTrapPointers(); + protected: friend class Zone; void Depop(bool StartSpawnTimer = false); @@ -553,10 +575,13 @@ private: Mob* GetMobByBotID(uint32 botID); Bot* GetBotByBotID(uint32 botID); Bot* GetBotByBotName(std::string botName); + Client* GetBotOwnerByBotEntityID(uint16 entityID); std::list GetBotsByBotOwnerCharacterID(uint32 botOwnerCharacterID); bool Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, float iRange, uint32 iSpellTypes); // TODO: Evaluate this closesly in hopes to eliminate void ShowSpawnWindow(Client* client, int Distance, bool NamedOnly); // TODO: Implement ShowSpawnWindow in the bot class but it needs entity list stuff + + void ScanCloseClientMobs(std::unordered_map& close_mobs, Mob* scanning_mob); private: std::list bot_list; #endif diff --git a/zone/event_codes.h b/zone/event_codes.h index bb623f041..110101cf9 100644 --- a/zone/event_codes.h +++ b/zone/event_codes.h @@ -86,6 +86,8 @@ typedef enum { EVENT_SPAWN_ZONE, EVENT_DEATH_ZONE, EVENT_USE_SKILL, + EVENT_COMBINE_VALIDATE, + EVENT_BOT_COMMAND, _LargestEventID } QuestEventID; diff --git a/zone/fearpath.cpp b/zone/fearpath.cpp index 3f14b059a..cedf1d410 100644 --- a/zone/fearpath.cpp +++ b/zone/fearpath.cpp @@ -30,65 +30,80 @@ extern Zone* zone; #define FEAR_PATHING_DEBUG //this is called whenever we are damaged to process possible fleeing -void Mob::CheckFlee() { +void Mob::CheckFlee() +{ // if mob is dead why would you run? - if(GetHP() == 0) { + if (GetHP() == 0) { return; } // if were already fleeing, don't need to check more... - if(flee_mode && currently_fleeing) { + if (flee_mode && currently_fleeing) { return; } //dont bother if we are immune to fleeing - if(GetSpecialAbility(IMMUNE_FLEEING) || spellbonuses.ImmuneToFlee) { + if (GetSpecialAbility(IMMUNE_FLEEING) || spellbonuses.ImmuneToFlee) { + LogFlee("Mob [{}] is immune to fleeing via special ability or spell bonus", GetCleanName()); return; } // Check if Flee Timer is cleared - if(!flee_timer.Check()) { + if (!flee_timer.Check()) { return; } - int hpratio = GetIntHPRatio(); - int fleeratio = GetSpecialAbility(FLEE_PERCENT); // if a special flee_percent exists - Mob *hate_top = GetHateTop(); + int hp_ratio = GetIntHPRatio(); + int flee_ratio = GetSpecialAbility(FLEE_PERCENT); // if a special flee_percent exists + Mob *hate_top = GetHateTop(); + + LogFlee("Mob [{}] hp_ratio [{}] flee_ratio [{}]", GetCleanName(), hp_ratio, flee_ratio); // Sanity Check for race conditions - if(hate_top == nullptr) { + if (hate_top == nullptr) { return; } // If no special flee_percent check for Gray or Other con rates - if(GetLevelCon(hate_top->GetLevel(), GetLevel()) == CON_GRAY && fleeratio == 0 && RuleB(Combat, FleeGray) && GetLevel() <= RuleI(Combat, FleeGrayMaxLevel)) { - fleeratio = RuleI(Combat, FleeGrayHPRatio); - } else if(fleeratio == 0) { - fleeratio = RuleI(Combat, FleeHPRatio ); + if (GetLevelCon(hate_top->GetLevel(), GetLevel()) == CON_GRAY && flee_ratio == 0 && RuleB(Combat, FleeGray) && + GetLevel() <= RuleI(Combat, FleeGrayMaxLevel)) { + flee_ratio = RuleI(Combat, FleeGrayHPRatio); + LogFlee("Mob [{}] using combat flee gray hp_ratio [{}] flee_ratio [{}]", GetCleanName(), hp_ratio, flee_ratio); + } + else if (flee_ratio == 0) { + flee_ratio = RuleI(Combat, FleeHPRatio); + LogFlee("Mob [{}] using combat flee hp_ratio [{}] flee_ratio [{}]", GetCleanName(), hp_ratio, flee_ratio); } - // Mob does not have low enough health to flee - if(hpratio >= fleeratio) { + bool mob_has_low_enough_health_to_flee = hp_ratio >= flee_ratio; + if (mob_has_low_enough_health_to_flee) { + LogFlee( + "Mob [{}] does not have low enough health to flee | hp_ratio [{}] flee_ratio [{}]", + GetCleanName(), + hp_ratio, + flee_ratio + ); return; } // Sanity Check this should never happen... - if(!hate_top) { + if (!hate_top) { + currently_fleeing = true; StartFleeing(); return; } int other_ratio = hate_top->GetIntHPRatio(); // If the Client is nearing death the NPC will not flee and instead try to kill the client. - if(other_ratio < 20) { + if (other_ratio < 20) { return; } // Flee Chance checking based on con. uint32 con = GetLevelCon(hate_top->GetLevel(), GetLevel()); - int flee_chance; - switch(con) { + int flee_chance; + switch (con) { //these values are not 100% researched case CON_GRAY: flee_chance = 100; @@ -107,9 +122,32 @@ void Mob::CheckFlee() { break; } - // If we got here we are allowed to roll on flee chance if there is not other hated NPC's in the area. + LogFlee( + "Post con-switch | Mob [{}] con [{}] hp_ratio [{}] flee_ratio [{}] flee_chance [{}]", + GetCleanName(), + con, + hp_ratio, + flee_ratio, + flee_chance + ); + + // If we got here we are allowed to roll on flee chance if there is not other hated NPC's in the area. + // ALWAYS_FLEE, skip roll + // if FleeIfNotAlone is true, we skip alone check + // roll chance + if (GetSpecialAbility(ALWAYS_FLEE) || + ((RuleB(Combat, FleeIfNotAlone) || entity_list.GetHatedCount(hate_top, this, true) == 0) && + zone->random.Roll(flee_chance))) { + + LogFlee( + "Passed all checks to flee | Mob [{}] con [{}] hp_ratio [{}] flee_ratio [{}] flee_chance [{}]", + GetCleanName(), + con, + hp_ratio, + flee_ratio, + flee_chance + ); - if(RuleB(Combat, FleeIfNotAlone) || GetSpecialAbility(ALWAYS_FLEE) || zone->random.Roll(flee_chance) && entity_list.GetHatedCount(hate_top, this, true) == 0) { currently_fleeing = true; StartFleeing(); } @@ -155,7 +193,8 @@ void Mob::ProcessFlee() } } -void Mob::CalculateNewFearpoint() { +void Mob::CalculateNewFearpoint() +{ if (RuleB(Pathing, Fear) && zone->pathing) { auto Node = zone->pathing->GetRandomLocation(glm::vec3(GetX(), GetY(), GetZ())); if (Node.x != 0.0f || Node.y != 0.0f || Node.z != 0.0f) { @@ -165,9 +204,7 @@ void Mob::CalculateNewFearpoint() { return; } - Log(Logs::Detail, - Logs::Pathing, - "No path found to selected node during CalculateNewFearpoint."); + LogPathing("No path found to selected node during CalculateNewFearpoint."); } } diff --git a/zone/forage.cpp b/zone/forage.cpp index 3c69dce19..24c4641e4 100644 --- a/zone/forage.cpp +++ b/zone/forage.cpp @@ -309,40 +309,42 @@ void Client::GoFish() if(food_id == 0) { int index = zone->random.Int(0, MAX_COMMON_FISH_IDS-1); - food_id = common_fish_ids[index]; + food_id = (RuleB(Character, UseNoJunkFishing) ? 13019 : common_fish_ids[index]); } const EQEmu::ItemData* food_item = database.GetItem(food_id); + if (food_item) { - if (food_item->ItemType != EQEmu::item::ItemTypeFood) { - MessageString(Chat::Skills, FISHING_SUCCESS); - } - else { - MessageString(Chat::Skills, FISHING_SUCCESS_FISH_NAME, food_item->Name); - } - - EQEmu::ItemInstance* inst = database.CreateItem(food_item, 1); - if(inst != nullptr) { - if(CheckLoreConflict(inst->GetItem())) - { - MessageString(Chat::White, DUP_LORE); - safe_delete(inst); + if (food_item->ItemType != EQEmu::item::ItemTypeFood) { + MessageString(Chat::Skills, FISHING_SUCCESS); } - else - { - PushItemOnCursor(*inst); - SendItemPacket(EQEmu::invslot::slotCursor, inst, ItemPacketLimbo); - if(RuleB(TaskSystem, EnableTaskSystem)) - UpdateTasksForItem(ActivityFish, food_id); - - safe_delete(inst); - inst = m_inv.GetItem(EQEmu::invslot::slotCursor); + else { + MessageString(Chat::Skills, FISHING_SUCCESS_FISH_NAME, food_item->Name); } - if(inst) { - std::vector args; - args.push_back(inst); - parse->EventPlayer(EVENT_FISH_SUCCESS, this, "", inst->GetID(), &args); + EQEmu::ItemInstance* inst = database.CreateItem(food_item, 1); + if (inst != nullptr) { + if (CheckLoreConflict(inst->GetItem())) + { + MessageString(Chat::White, DUP_LORE); + safe_delete(inst); + } + else + { + PushItemOnCursor(*inst); + SendItemPacket(EQEmu::invslot::slotCursor, inst, ItemPacketLimbo); + if (RuleB(TaskSystem, EnableTaskSystem)) + UpdateTasksForItem(ActivityFish, food_id); + + safe_delete(inst); + inst = m_inv.GetItem(EQEmu::invslot::slotCursor); + } + + if (inst) { + std::vector args; + args.push_back(inst); + parse->EventPlayer(EVENT_FISH_SUCCESS, this, "", inst->GetID(), &args); + } } } } diff --git a/zone/hate_list.cpp b/zone/hate_list.cpp index 478c9ac50..6b26cba8a 100644 --- a/zone/hate_list.cpp +++ b/zone/hate_list.cpp @@ -155,13 +155,18 @@ Mob* HateList::GetDamageTopOnHateList(Mob* hater) return current; } -Mob* HateList::GetClosestEntOnHateList(Mob *hater) { +Mob* HateList::GetClosestEntOnHateList(Mob *hater, bool skip_mezzed) { Mob* close_entity = nullptr; float close_distance = 99999.9f; float this_distance; auto iterator = list.begin(); while (iterator != list.end()) { + if (skip_mezzed && (*iterator)->entity_on_hatelist->IsMezzed()) { + ++iterator; + continue; + } + this_distance = DistanceSquaredNoZ((*iterator)->entity_on_hatelist->GetPosition(), hater->GetPosition()); if ((*iterator)->entity_on_hatelist != nullptr && this_distance <= close_distance) { close_distance = this_distance; @@ -297,7 +302,7 @@ int HateList::GetHateRatio(Mob *top, Mob *other) // skip is used to ignore a certain mob on the list // Currently used for getting 2nd on list for aggro meter -Mob *HateList::GetEntWithMostHateOnList(Mob *center, Mob *skip) +Mob *HateList::GetEntWithMostHateOnList(Mob *center, Mob *skip, bool skip_mezzed) { // hack fix for zone shutdown crashes on some servers if (!zone->IsLoaded()) @@ -335,6 +340,11 @@ Mob *HateList::GetEntWithMostHateOnList(Mob *center, Mob *skip) continue; } + if (skip_mezzed && cur->entity_on_hatelist->IsMezzed()) { + ++iterator; + continue; + } + if (cur->entity_on_hatelist->Sanctuary()) { if (hate == -1) { @@ -465,6 +475,11 @@ Mob *HateList::GetEntWithMostHateOnList(Mob *center, Mob *skip) continue; } + if (skip_mezzed && cur->entity_on_hatelist->IsMezzed()) { + ++iterator; + continue; + } + if (cur->entity_on_hatelist != nullptr && ((cur->stored_hate_amount > hate) || cur->is_entity_frenzy)) { top_hate = cur->entity_on_hatelist; @@ -480,7 +495,7 @@ Mob *HateList::GetEntWithMostHateOnList(Mob *center, Mob *skip) return nullptr; } -Mob *HateList::GetEntWithMostHateOnList(){ +Mob *HateList::GetEntWithMostHateOnList(bool skip_mezzed){ Mob* top = nullptr; int64 hate = -1; @@ -490,8 +505,10 @@ Mob *HateList::GetEntWithMostHateOnList(){ struct_HateList *cur = (*iterator); if (cur && cur->entity_on_hatelist != nullptr && (cur->stored_hate_amount > hate)) { - top = cur->entity_on_hatelist; - hate = cur->stored_hate_amount; + if (!skip_mezzed || !cur->entity_on_hatelist->IsMezzed()) { + top = cur->entity_on_hatelist; + hate = cur->stored_hate_amount; + } } ++iterator; } @@ -499,26 +516,50 @@ Mob *HateList::GetEntWithMostHateOnList(){ } -Mob *HateList::GetRandomEntOnHateList() +Mob *HateList::GetRandomEntOnHateList(bool skip_mezzed) { int count = list.size(); - if (count == 0) //If we don't have any entries it'll crash getting a random 0, -1 position. - return NULL; + if (count <= 0) //If we don't have any entries it'll crash getting a random 0, -1 position. + return nullptr; if (count == 1) //No need to do all that extra work if we only have one hate entry { - if (*list.begin()) // Just in case tHateEntry is invalidated somehow... + if (*list.begin() && (!skip_mezzed || !(*list.begin())->entity_on_hatelist->IsMezzed())) // Just in case tHateEntry is invalidated somehow... return (*list.begin())->entity_on_hatelist; - return NULL; + return nullptr; } - auto iterator = list.begin(); - int random = zone->random.Int(0, count - 1); - for (int i = 0; i < random; i++) - ++iterator; + if (skip_mezzed) { - return (*iterator)->entity_on_hatelist; + for (auto iter : list) { + if (iter->entity_on_hatelist->IsMezzed()) { + --count; + } + } + if (count <= 0) { + return nullptr; + } + } + + int random = zone->random.Int(0, count - 1); + int counter = 0; + + for (auto iter : list) { + + if (skip_mezzed && iter->entity_on_hatelist->IsMezzed()) { + continue; + } + if (counter < random) { + + ++counter; + continue; + } + + return iter->entity_on_hatelist; + } + + return nullptr; } Mob *HateList::GetEscapingEntOnHateList() { diff --git a/zone/hate_list.h b/zone/hate_list.h index 96d8ed067..44613bdea 100644 --- a/zone/hate_list.h +++ b/zone/hate_list.h @@ -41,11 +41,11 @@ public: HateList(); ~HateList(); - Mob *GetClosestEntOnHateList(Mob *hater); - Mob *GetDamageTopOnHateList(Mob *hater); - Mob *GetEntWithMostHateOnList(Mob *center, Mob *skip = nullptr); - Mob *GetRandomEntOnHateList(); - Mob *GetEntWithMostHateOnList(); + Mob *GetClosestEntOnHateList(Mob *hater, bool skip_mezzed = false); + Mob *GetDamageTopOnHateList(Mob *hater); // didn't add 'skip_mezzed' due to calls being in ::Death() + Mob *GetEntWithMostHateOnList(Mob *center, Mob *skip = nullptr, bool skip_mezzed = false); + Mob *GetRandomEntOnHateList(bool skip_mezzed = false); + Mob *GetEntWithMostHateOnList(bool skip_mezzed = false); Mob *GetEscapingEntOnHateList(); // returns first eligble entity Mob *GetEscapingEntOnHateList(Mob *center, float range = 0.0f, bool first = false); diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index 64ce0cafa..9d0873360 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -1385,59 +1385,87 @@ void Lua_Client::QuestReward(Lua_Mob target, luabind::adl::object reward) { return; } - uint32 copper = 0; - uint32 silver = 0; - uint32 gold = 0; - uint32 platinum = 0; - uint32 itemid = 0; - uint32 exp = 0; + QuestReward_Struct quest_reward; + quest_reward.mob_id = 0; + quest_reward.target_id = self->GetID(); + quest_reward.copper = 0; + quest_reward.silver = 0; + quest_reward.gold = 0; + quest_reward.platinum = 0; + quest_reward.exp_reward = 0; + quest_reward.faction = 0; + quest_reward.faction_mod = 0; bool faction = false; + std::fill(std::begin(quest_reward.item_id), std::end(quest_reward.item_id), -1); auto cur = reward["copper"]; if (luabind::type(cur) != LUA_TNIL) { try { - copper = luabind::object_cast(cur); - } catch (luabind::cast_failed) { + quest_reward.copper = luabind::object_cast(cur); + } catch (luabind::cast_failed &) { } } cur = reward["silver"]; if (luabind::type(cur) != LUA_TNIL) { try { - silver = luabind::object_cast(cur); - } catch (luabind::cast_failed) { + quest_reward.silver = luabind::object_cast(cur); + } catch (luabind::cast_failed &) { } } cur = reward["gold"]; if (luabind::type(cur) != LUA_TNIL) { try { - gold = luabind::object_cast(cur); - } catch (luabind::cast_failed) { + quest_reward.gold = luabind::object_cast(cur); + } catch (luabind::cast_failed &) { } } cur = reward["platinum"]; if (luabind::type(cur) != LUA_TNIL) { try { - platinum = luabind::object_cast(cur); - } catch (luabind::cast_failed) { + quest_reward.platinum = luabind::object_cast(cur); + } catch (luabind::cast_failed &) { } } cur = reward["itemid"]; if (luabind::type(cur) != LUA_TNIL) { try { - itemid = luabind::object_cast(cur); - } catch (luabind::cast_failed) { + quest_reward.item_id[0] = luabind::object_cast(cur); + } catch (luabind::cast_failed &) { + } + } + + // if you define both an itemid and items table, the itemid is thrown away + // should we error? + cur = reward["items"]; + if (luabind::type(cur) == LUA_TTABLE) { + try { + // assume they defined a compatible table + for (int i = 1; i <= QUESTREWARD_COUNT; ++i) { + auto item = cur[i]; + int cur_value = -1; + if (luabind::type(item) != LUA_TNIL) { + try { + cur_value = luabind::object_cast(item); + } catch (luabind::cast_failed &) { + } + } else { + break; + } + quest_reward.item_id[i - 1] = cur_value; + } + } catch (luabind::cast_failed &) { } } cur = reward["exp"]; if (luabind::type(cur) != LUA_TNIL) { try { - exp = luabind::object_cast(cur); - } catch (luabind::cast_failed) { + quest_reward.exp_reward = luabind::object_cast(cur); + } catch (luabind::cast_failed &) { } } @@ -1445,11 +1473,11 @@ void Lua_Client::QuestReward(Lua_Mob target, luabind::adl::object reward) { if (luabind::type(cur) != LUA_TNIL) { try { faction = luabind::object_cast(cur); - } catch (luabind::cast_failed) { + } catch (luabind::cast_failed &) { } } - self->QuestReward(target, copper, silver, gold, platinum, itemid, exp, faction); + self->QuestReward(target, quest_reward, faction); } bool Lua_Client::IsDead() { diff --git a/zone/lua_general.cpp b/zone/lua_general.cpp index 7f714fd6b..0d1086ad1 100644 --- a/zone/lua_general.cpp +++ b/zone/lua_general.cpp @@ -549,6 +549,14 @@ void lua_summon_all_player_corpses(uint32 char_id, float x, float y, float z, fl quest_manager.summonallplayercorpses(char_id, glm::vec4(x, y, z, h)); } +int lua_get_player_corpse_count(uint32 char_id) { + return database.CountCharacterCorpses(char_id); +} + +int lua_get_player_corpse_count_by_zone_id(uint32 char_id, uint32 zone_id) { + return database.CountCharacterCorpsesByZoneID(char_id, zone_id); +} + int lua_get_player_buried_corpse_count(uint32 char_id) { return quest_manager.getplayerburiedcorpsecount(char_id); } @@ -571,7 +579,7 @@ void lua_task_selector(luabind::adl::object table) { if(luabind::type(cur) != LUA_TNIL) { try { cur_value = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { } } else { count = i - 1; @@ -601,7 +609,7 @@ void lua_enable_task(luabind::adl::object table) { if(luabind::type(cur) != LUA_TNIL) { try { cur_value = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { } } else { count = i - 1; @@ -628,7 +636,7 @@ void lua_disable_task(luabind::adl::object table) { if(luabind::type(cur) != LUA_TNIL) { try { cur_value = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { } } else { count = i - 1; @@ -1156,7 +1164,7 @@ void lua_add_spawn_point(luabind::adl::object table) { if(luabind::type(cur) != LUA_TNIL) { try { spawn2_id = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { return; } } else { @@ -1167,7 +1175,7 @@ void lua_add_spawn_point(luabind::adl::object table) { if(luabind::type(cur) != LUA_TNIL) { try { spawngroup_id = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { return; } } else { @@ -1178,7 +1186,7 @@ void lua_add_spawn_point(luabind::adl::object table) { if(luabind::type(cur) != LUA_TNIL) { try { x = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { return; } } else { @@ -1189,7 +1197,7 @@ void lua_add_spawn_point(luabind::adl::object table) { if(luabind::type(cur) != LUA_TNIL) { try { y = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { return; } } else { @@ -1200,7 +1208,7 @@ void lua_add_spawn_point(luabind::adl::object table) { if(luabind::type(cur) != LUA_TNIL) { try { z = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { return; } } else { @@ -1211,7 +1219,7 @@ void lua_add_spawn_point(luabind::adl::object table) { if(luabind::type(cur) != LUA_TNIL) { try { heading = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { return; } } else { @@ -1222,7 +1230,7 @@ void lua_add_spawn_point(luabind::adl::object table) { if(luabind::type(cur) != LUA_TNIL) { try { respawn = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { return; } } else { @@ -1233,7 +1241,7 @@ void lua_add_spawn_point(luabind::adl::object table) { if(luabind::type(cur) != LUA_TNIL) { try { variance = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { return; } } else { @@ -1244,7 +1252,7 @@ void lua_add_spawn_point(luabind::adl::object table) { if(luabind::type(cur) != LUA_TNIL) { try { timeleft = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { } } @@ -1252,7 +1260,7 @@ void lua_add_spawn_point(luabind::adl::object table) { if(luabind::type(cur) != LUA_TNIL) { try { grid = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { } } @@ -1260,7 +1268,7 @@ void lua_add_spawn_point(luabind::adl::object table) { if(luabind::type(cur) != LUA_TNIL) { try { condition_id = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { } } @@ -1268,7 +1276,7 @@ void lua_add_spawn_point(luabind::adl::object table) { if(luabind::type(cur) != LUA_TNIL) { try { condition_min_value = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { } } @@ -1276,7 +1284,7 @@ void lua_add_spawn_point(luabind::adl::object table) { if(luabind::type(cur) != LUA_TNIL) { try { enabled = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { } } @@ -1284,7 +1292,7 @@ void lua_add_spawn_point(luabind::adl::object table) { if(luabind::type(cur) != LUA_TNIL) { try { animation = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { } } @@ -1399,7 +1407,7 @@ void lua_update_zone_header(std::string type, std::string value) { try { \ npc_type->name = luabind::object_cast(cur); \ } \ - catch(luabind::cast_failed) { \ + catch(luabind::cast_failed &) { \ npc_type->size = default_value; \ } \ } \ @@ -1415,7 +1423,7 @@ void lua_update_zone_header(std::string type, std::string value) { std::string tmp = luabind::object_cast(cur); \ strncpy(npc_type->name, tmp.c_str(), str_length); \ } \ - catch(luabind::cast_failed) { \ + catch(luabind::cast_failed &) { \ strncpy(npc_type->name, default_value, str_length); \ } \ } \ @@ -1663,6 +1671,8 @@ luabind::scope lua_register_general() { luabind::def("toggle_spawn_event", &lua_toggle_spawn_event), luabind::def("summon_buried_player_corpse", &lua_summon_buried_player_corpse), luabind::def("summon_all_player_corpses", &lua_summon_all_player_corpses), + luabind::def("get_player_corpse_count", &lua_get_player_corpse_count), + luabind::def("get_player_corpse_count_by_zone_id", &lua_get_player_corpse_count_by_zone_id), luabind::def("get_player_buried_corpse_count", &lua_get_player_buried_corpse_count), luabind::def("bury_player_corpse", &lua_bury_player_corpse), luabind::def("task_selector", &lua_task_selector), diff --git a/zone/lua_mob.cpp b/zone/lua_mob.cpp index 7d769b8dd..4860ae1e8 100644 --- a/zone/lua_mob.cpp +++ b/zone/lua_mob.cpp @@ -113,7 +113,7 @@ bool Lua_Mob::Attack(Lua_Mob other, int hand, bool from_riposte, bool is_striket if(luabind::type(cur) != LUA_TNIL) { try { options.armor_pen_flat = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { } } @@ -121,7 +121,7 @@ bool Lua_Mob::Attack(Lua_Mob other, int hand, bool from_riposte, bool is_striket if(luabind::type(cur) != LUA_TNIL) { try { options.crit_flat = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { } } @@ -129,7 +129,7 @@ bool Lua_Mob::Attack(Lua_Mob other, int hand, bool from_riposte, bool is_striket if(luabind::type(cur) != LUA_TNIL) { try { options.damage_flat = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { } } @@ -137,7 +137,7 @@ bool Lua_Mob::Attack(Lua_Mob other, int hand, bool from_riposte, bool is_striket if(luabind::type(cur) != LUA_TNIL) { try { options.hate_flat = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { } } @@ -145,7 +145,7 @@ bool Lua_Mob::Attack(Lua_Mob other, int hand, bool from_riposte, bool is_striket if(luabind::type(cur) != LUA_TNIL) { try { options.armor_pen_percent = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { } } @@ -153,7 +153,7 @@ bool Lua_Mob::Attack(Lua_Mob other, int hand, bool from_riposte, bool is_striket if(luabind::type(cur) != LUA_TNIL) { try { options.crit_percent = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { } } @@ -161,7 +161,7 @@ bool Lua_Mob::Attack(Lua_Mob other, int hand, bool from_riposte, bool is_striket if(luabind::type(cur) != LUA_TNIL) { try { options.damage_percent = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { } } @@ -169,7 +169,7 @@ bool Lua_Mob::Attack(Lua_Mob other, int hand, bool from_riposte, bool is_striket if(luabind::type(cur) != LUA_TNIL) { try { options.hate_percent = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { } } } @@ -785,7 +785,7 @@ void Lua_Mob::QuestSay(Lua_Client client, const char *message, luabind::adl::obj if (luabind::type(cur) != LUA_TNIL) { try { journal_opts.speak_mode = static_cast(luabind::object_cast(cur)); - } catch (luabind::cast_failed) { + } catch (luabind::cast_failed &) { } } @@ -793,7 +793,7 @@ void Lua_Mob::QuestSay(Lua_Client client, const char *message, luabind::adl::obj if (luabind::type(cur) != LUA_TNIL) { try { journal_opts.journal_mode = static_cast(luabind::object_cast(cur)); - } catch (luabind::cast_failed) { + } catch (luabind::cast_failed &) { } } @@ -801,7 +801,7 @@ void Lua_Mob::QuestSay(Lua_Client client, const char *message, luabind::adl::obj if (luabind::type(cur) != LUA_TNIL) { try { journal_opts.language = luabind::object_cast(cur); - } catch (luabind::cast_failed) { + } catch (luabind::cast_failed &) { } } @@ -809,7 +809,7 @@ void Lua_Mob::QuestSay(Lua_Client client, const char *message, luabind::adl::obj if (luabind::type(cur) != LUA_TNIL) { try { journal_opts.message_type = luabind::object_cast(cur); - } catch (luabind::cast_failed) { + } catch (luabind::cast_failed &) { } } } @@ -1568,7 +1568,7 @@ void Lua_Mob::SendIllusionPacket(luabind::adl::object illusion) { if(luabind::type(cur) != LUA_TNIL) { try { race = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { } } @@ -1576,7 +1576,7 @@ void Lua_Mob::SendIllusionPacket(luabind::adl::object illusion) { if(luabind::type(cur) != LUA_TNIL) { try { gender = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { } } @@ -1584,7 +1584,7 @@ void Lua_Mob::SendIllusionPacket(luabind::adl::object illusion) { if(luabind::type(cur) != LUA_TNIL) { try { texture = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { } } @@ -1592,7 +1592,7 @@ void Lua_Mob::SendIllusionPacket(luabind::adl::object illusion) { if(luabind::type(cur) != LUA_TNIL) { try { helmtexture = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { } } @@ -1600,7 +1600,7 @@ void Lua_Mob::SendIllusionPacket(luabind::adl::object illusion) { if(luabind::type(cur) != LUA_TNIL) { try { haircolor = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { } } @@ -1608,7 +1608,7 @@ void Lua_Mob::SendIllusionPacket(luabind::adl::object illusion) { if(luabind::type(cur) != LUA_TNIL) { try { beardcolor = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { } } @@ -1616,7 +1616,7 @@ void Lua_Mob::SendIllusionPacket(luabind::adl::object illusion) { if(luabind::type(cur) != LUA_TNIL) { try { eyecolor1 = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { } } @@ -1624,7 +1624,7 @@ void Lua_Mob::SendIllusionPacket(luabind::adl::object illusion) { if(luabind::type(cur) != LUA_TNIL) { try { eyecolor2 = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { } } @@ -1632,7 +1632,7 @@ void Lua_Mob::SendIllusionPacket(luabind::adl::object illusion) { if(luabind::type(cur) != LUA_TNIL) { try { hairstyle = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { } } @@ -1640,7 +1640,7 @@ void Lua_Mob::SendIllusionPacket(luabind::adl::object illusion) { if(luabind::type(cur) != LUA_TNIL) { try { luclinface = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { } } @@ -1648,7 +1648,7 @@ void Lua_Mob::SendIllusionPacket(luabind::adl::object illusion) { if(luabind::type(cur) != LUA_TNIL) { try { beard = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { } } @@ -1656,7 +1656,7 @@ void Lua_Mob::SendIllusionPacket(luabind::adl::object illusion) { if(luabind::type(cur) != LUA_TNIL) { try { aa_title = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { } } @@ -1664,7 +1664,7 @@ void Lua_Mob::SendIllusionPacket(luabind::adl::object illusion) { if(luabind::type(cur) != LUA_TNIL) { try { drakkin_heritage = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { } } @@ -1672,7 +1672,7 @@ void Lua_Mob::SendIllusionPacket(luabind::adl::object illusion) { if(luabind::type(cur) != LUA_TNIL) { try { drakkin_tattoo = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { } } @@ -1680,7 +1680,7 @@ void Lua_Mob::SendIllusionPacket(luabind::adl::object illusion) { if(luabind::type(cur) != LUA_TNIL) { try { drakkin_details = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { } } @@ -1688,7 +1688,7 @@ void Lua_Mob::SendIllusionPacket(luabind::adl::object illusion) { if(luabind::type(cur) != LUA_TNIL) { try { size = luabind::object_cast(cur); - } catch(luabind::cast_failed) { + } catch(luabind::cast_failed &) { } } diff --git a/zone/lua_parser.cpp b/zone/lua_parser.cpp index 7c7242e4c..620ee14d1 100644 --- a/zone/lua_parser.cpp +++ b/zone/lua_parser.cpp @@ -123,7 +123,9 @@ const char *LuaEvents[_LargestEventID] = { "event_tick", "event_spawn_zone", "event_death_zone", - "event_use_skill" + "event_use_skill", + "event_combine_validate", + "event_bot_command" }; extern Zone *zone; @@ -206,6 +208,8 @@ LuaParser::LuaParser() { PlayerArgumentDispatch[EVENT_RESPAWN] = handle_player_respawn; PlayerArgumentDispatch[EVENT_UNHANDLED_OPCODE] = handle_player_packet; PlayerArgumentDispatch[EVENT_USE_SKILL] = handle_player_use_skill; + PlayerArgumentDispatch[EVENT_COMBINE_VALIDATE] = handle_player_combine_validate; + PlayerArgumentDispatch[EVENT_BOT_COMMAND] = handle_player_bot_command; ItemArgumentDispatch[EVENT_ITEM_CLICK] = handle_item_click; ItemArgumentDispatch[EVENT_ITEM_CLICK_CAST] = handle_item_click; diff --git a/zone/lua_parser_events.cpp b/zone/lua_parser_events.cpp index e70c5de9f..3ca9edad7 100644 --- a/zone/lua_parser_events.cpp +++ b/zone/lua_parser_events.cpp @@ -514,6 +514,50 @@ void handle_player_use_skill(QuestInterface *parse, lua_State* L, Client* client lua_setfield(L, -2, "skill_level"); } +void handle_player_combine_validate(QuestInterface* parse, lua_State* L, Client* client, std::string data, uint32 extra_data, + std::vector* extra_pointers) { + Seperator sep(data.c_str()); + lua_pushinteger(L, extra_data); + lua_setfield(L, -2, "recipe_id"); + + lua_pushstring(L, sep.arg[0]); + lua_setfield(L, -2, "validate_type"); + + int zone_id = -1; + int tradeskill_id = -1; + if (strcmp(sep.arg[0], "check_zone") == 0) { + zone_id = std::stoi(sep.arg[1]); + } + else if (strcmp(sep.arg[0], "check_tradeskill") == 0) { + tradeskill_id = std::stoi(sep.arg[1]); + } + + lua_pushinteger(L, zone_id); + lua_setfield(L, -2, "zone_id"); + + lua_pushinteger(L, tradeskill_id); + lua_setfield(L, -2, "tradeskill_id"); +} + +void handle_player_bot_command(QuestInterface* parse, lua_State* L, Client* client, std::string data, uint32 extra_data, + std::vector* extra_pointers) { + Seperator sep(data.c_str(), ' ', 10, 100, true); + std::string bot_command(sep.arg[0] + 1); + lua_pushstring(L, bot_command.c_str()); + lua_setfield(L, -2, "bot_command"); + + luabind::adl::object args = luabind::newtable(L); + int max_args = sep.GetMaxArgNum(); + for (int i = 1; i < max_args; ++i) { + if (strlen(sep.arg[i]) > 0) { + args[i] = std::string(sep.arg[i]); + } + } + + args.push(L); + lua_setfield(L, -2, "args"); +} + //Item void handle_item_click(QuestInterface *parse, lua_State* L, Client* client, EQEmu::ItemInstance* item, Mob *mob, std::string data, uint32 extra_data, std::vector *extra_pointers) { diff --git a/zone/lua_parser_events.h b/zone/lua_parser_events.h index 44ba9b72f..9eeeb11cb 100644 --- a/zone/lua_parser_events.h +++ b/zone/lua_parser_events.h @@ -97,6 +97,10 @@ void handle_player_null(QuestInterface *parse, lua_State* L, Client* client, std std::vector *extra_pointers); void handle_player_use_skill(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, std::vector *extra_pointers); +void handle_player_combine_validate(QuestInterface* parse, lua_State* L, Client* client, std::string data, uint32 extra_data, + std::vector* extra_pointers); +void handle_player_bot_command(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, + std::vector *extra_pointers); //Item void handle_item_click(QuestInterface *parse, lua_State* L, Client* client, EQEmu::ItemInstance* item, Mob *mob, std::string data, uint32 extra_data, diff --git a/zone/main.cpp b/zone/main.cpp index 9ee3eda25..6d3eee96c 100644 --- a/zone/main.cpp +++ b/zone/main.cpp @@ -548,29 +548,9 @@ int main(int argc, char** argv) { }; EQ::Timer process_timer(loop_fn); - process_timer.Start(1000, true); + process_timer.Start(32, true); - while (RunLoops) { - bool previous_loaded = is_zone_loaded && numclients > 0; - EQ::EventLoop::Get().Process(); - - bool current_loaded = is_zone_loaded && numclients > 0; - if (previous_loaded && !current_loaded) { - process_timer.Stop(); - process_timer.Start(1000, true); - } - else if (!previous_loaded && current_loaded) { - process_timer.Stop(); - process_timer.Start(32, true); - } - - if (current_loaded) { - Sleep(1); - } - else { - Sleep(10); - } - } + EQ::EventLoop::Get().Run(); entity_list.Clear(); entity_list.RemoveAllEncounters(); // gotta do it manually or rewrite lots of shit :P diff --git a/zone/map.cpp b/zone/map.cpp index a0e3e6400..dbfa3928e 100644 --- a/zone/map.cpp +++ b/zone/map.cpp @@ -242,46 +242,65 @@ bool Map::Load(std::string filename, bool force_mmf_overwrite) return true; } #else -bool Map::Load(std::string filename) + +/** + * @param filename + * @return + */ +bool Map::Load(const std::string &filename) { #endif /*USE_MAP_MMFS*/ - FILE *f = fopen(filename.c_str(), "rb"); - if(f) { + FILE *map_file = fopen(filename.c_str(), "rb"); + if (map_file) { uint32 version; - if(fread(&version, sizeof(version), 1, f) != 1) { - fclose(f); + if (fread(&version, sizeof(version), 1, map_file) != 1) { + fclose(map_file); return false; } - - if(version == 0x01000000) { + + if (version == 0x01000000) { LogInfo("Loaded V1 Map File [{}]", filename.c_str()); - bool v = LoadV1(f); - fclose(f); + bool loaded_map_file = LoadV1(map_file); + fclose(map_file); + + if (loaded_map_file) { + LogInfo("Loaded V1 Map File [{}]", filename.c_str()); + } else { + LogError("Failed to load V1 Map File [{}]", filename.c_str()); + } #ifdef USE_MAP_MMFS if (v) return SaveMMF(filename, force_mmf_overwrite); #endif /*USE_MAP_MMFS*/ - return v; - } else if(version == 0x02000000) { - LogInfo("Loaded V2 Map File [{}]", filename.c_str()); - bool v = LoadV2(f); - fclose(f); + return loaded_map_file; + } + else if (version == 0x02000000) { + LogInfo("Loading V2 Map File [{}]", filename.c_str()); + bool loaded_map_file = LoadV2(map_file); + fclose(map_file); + + if (loaded_map_file) { + LogInfo("Loaded V2 Map File [{}]", filename.c_str()); + } else { + LogError("Failed to load V2 Map File [{}]", filename.c_str()); + } #ifdef USE_MAP_MMFS if (v) return SaveMMF(filename, force_mmf_overwrite); #endif /*USE_MAP_MMFS*/ - return v; - } else { - fclose(f); + return loaded_map_file; + } + else { + fclose(map_file); return false; } } - + return false; } diff --git a/zone/map.h b/zone/map.h index 8a2ec7f68..eefb43f2d 100644 --- a/zone/map.h +++ b/zone/map.h @@ -47,7 +47,7 @@ public: #ifdef USE_MAP_MMFS bool Load(std::string filename, bool force_mmf_overwrite = false); #else - bool Load(std::string filename); + bool Load(const std::string& filename); #endif static Map *LoadMapFile(std::string file); diff --git a/zone/mob.cpp b/zone/mob.cpp index b12df39c1..a6de82eee 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -116,7 +116,9 @@ Mob::Mob( m_specialattacks(eSpecialAttacks::None), attack_anim_timer(1000), position_update_melee_push_timer(500), - hate_list_cleanup_timer(6000) + hate_list_cleanup_timer(6000), + mob_scan_close(6000), + mob_check_moving_timer(1000) { mMovementManager = &MobMovementManager::Get(); mMovementManager->AddMob(this); @@ -184,6 +186,7 @@ Mob::Mob( last_hp_percent = 0; last_hp = 0; + last_max_hp = 0; current_speed = base_runspeed; @@ -453,6 +456,12 @@ Mob::Mob( PrimaryAggro = false; AssistAggro = false; npc_assist_cap = 0; + +#ifdef BOTS + m_manual_follow = false; +#endif + + mob_scan_close.Trigger(); } Mob::~Mob() @@ -461,35 +470,44 @@ Mob::~Mob() AI_Stop(); if (GetPet()) { - if (GetPet()->Charmed()) + if (GetPet()->Charmed()) { GetPet()->BuffFadeByEffect(SE_Charm); - else + } + else { SetPet(0); + } } EQApplicationPacket app; CreateDespawnPacket(&app, !IsCorpse()); - Corpse* corpse = entity_list.GetCorpseByID(GetID()); - if(!corpse || (corpse && !corpse->IsPlayerCorpse())) + Corpse *corpse = entity_list.GetCorpseByID(GetID()); + if (!corpse || (corpse && !corpse->IsPlayerCorpse())) { entity_list.QueueClients(this, &app, true); + } entity_list.RemoveFromTargets(this, true); - if(trade) { + if (trade) { Mob *with = trade->With(); - if(with && with->IsClient()) { + if (with && with->IsClient()) { with->CastToClient()->FinishTrade(with); with->trade->Reset(); } delete trade; } - if(HasTempPetsActive()){ + if (HasTempPetsActive()) { entity_list.DestroyTempPets(this); } + entity_list.UnMarkNPC(GetID()); UninitializeBuffSlots(); + entity_list.RemoveMobFromCloseLists(this); + entity_list.RemoveAuraFromMobs(this); + + close_mobs.clear(); + #ifdef BOTS LeaveHealRotationTargetPool(); #endif @@ -594,7 +612,7 @@ int Mob::_GetWalkSpeed() const { runspeedcap += itembonuses.IncreaseRunSpeedCap + spellbonuses.IncreaseRunSpeedCap + aabonuses.IncreaseRunSpeedCap; aa_mod += aabonuses.BaseMovementSpeed; - if (IsClient()) { + if (IsClient() && CastToClient()->GetHorseId()) { Mob *horse = entity_list.GetMob(CastToClient()->GetHorseId()); if (horse) { speed_mod = horse->GetBaseRunspeed(); @@ -652,7 +670,7 @@ int Mob::_GetRunSpeed() const { { speed_mod = 325; } - else + else if (CastToClient()->GetHorseId()) { Mob* horse = entity_list.GetMob(CastToClient()->GetHorseId()); if(horse) @@ -1317,6 +1335,16 @@ void Mob::SendHPUpdate(bool skip_self /*= false*/, bool force_update_all /*= fal * If our HP is different from last HP update call - let's update selves */ if (IsClient()) { + + // delay to allow the client to catch up on buff states + if (max_hp != last_max_hp) { + + last_max_hp = max_hp; + CastToClient()->hp_self_update_throttle_timer.Trigger(); + + return; + } + if (current_hp != last_hp || force_update_all) { /** @@ -1324,10 +1352,12 @@ void Mob::SendHPUpdate(bool skip_self /*= false*/, bool force_update_all /*= fal */ if (this->CastToClient()->hp_self_update_throttle_timer.Check() || force_update_all) { Log(Logs::General, Logs::HPUpdate, - "Mob::SendHPUpdate :: Update HP of self (%s) HP: %i last: %i skip_self: %s", + "Mob::SendHPUpdate :: Update HP of self (%s) HP: %i/%i last: %i/%i skip_self: %s", this->GetCleanName(), current_hp, + max_hp, last_hp, + last_max_hp, (skip_self ? "true" : "false") ); @@ -1354,7 +1384,7 @@ void Mob::SendHPUpdate(bool skip_self /*= false*/, bool force_update_all /*= fal } } - int8 current_hp_percent = static_cast(max_hp == 0 ? 0 : static_cast(current_hp * 100 / max_hp)); + auto current_hp_percent = GetIntHPRatio(); Log(Logs::General, Logs::HPUpdate, diff --git a/zone/mob.h b/zone/mob.h index 2578db0c8..7c095a001 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -1,3 +1,4 @@ + /* EQEMu: Everquest Server Emulator Copyright (C) 2001-2016 EQEMu Development Team (http://eqemu.org) @@ -169,6 +170,10 @@ public: void DisplayInfo(Mob *mob); + std::unordered_map close_mobs; + Timer mob_scan_close; + Timer mob_check_moving_timer; + //Somewhat sorted: needs documenting! //Attack @@ -490,8 +495,8 @@ public: inline Mob* GetTarget() const { return target; } virtual void SetTarget(Mob* mob); inline bool HasTargetReflection() const { return (target && target != this && target->target == this); } - virtual inline float GetHPRatio() const { return max_hp == 0 ? 0 : ((float)current_hp/max_hp*100); } - virtual inline int GetIntHPRatio() const { return max_hp == 0 ? 0 : static_cast(current_hp * 100 / max_hp); } + virtual inline float GetHPRatio() const { return max_hp == 0 ? 0 : ((float) current_hp / max_hp * 100); } + virtual inline int GetIntHPRatio() const { return max_hp == 0 ? 0 : static_cast(GetHPRatio()); } inline int32 GetAC() const { return AC; } inline virtual int32 GetATK() const { return ATK + itembonuses.ATK + spellbonuses.ATK; } inline virtual int32 GetATKBonus() const { return itembonuses.ATK + spellbonuses.ATK; } @@ -619,8 +624,7 @@ public: //AI static uint32 GetLevelCon(uint8 mylevel, uint8 iOtherLevel); - inline uint32 GetLevelCon(uint8 iOtherLevel) const { - return this ? GetLevelCon(GetLevel(), iOtherLevel) : CON_GRAY; } + inline uint32 GetLevelCon(uint8 iOtherLevel) const { return GetLevelCon(GetLevel(), iOtherLevel); } virtual void AddToHateList(Mob* other, uint32 hate = 0, int32 damage = 0, bool iYellForHelp = true, bool bFrenzy = false, bool iBuffTic = false, uint16 spell_id = SPELL_UNKNOWN, bool pet_comand = false); bool RemoveFromHateList(Mob* mob); @@ -968,7 +972,7 @@ public: void SetEntityVariable(const char *id, const char *m_var); bool EntityVariableExists(const char *id); - void AI_Event_Engaged(Mob* attacker, bool iYellForHelp = true); + void AI_Event_Engaged(Mob* attacker, bool yell_for_help = true); void AI_Event_NoLongerEngaged(); FACTION_VALUE GetSpecialFactionCon(Mob* iOther); @@ -1188,8 +1192,8 @@ public: int32 GetManaRegen() const; - // Bots HealRotation methods #ifdef BOTS + // Bots HealRotation methods bool IsHealRotationTarget() { return (m_target_of_heal_rotation.use_count() && m_target_of_heal_rotation.get()); } bool JoinHealRotationTargetPool(std::shared_ptr* heal_rotation); bool LeaveHealRotationTargetPool(); @@ -1200,6 +1204,11 @@ public: float HealRotationExtendedHealFrequency(); const std::shared_ptr* TargetOfHealRotation() const { return &m_target_of_heal_rotation; } + + + // not Bots HealRotation methods + void SetManualFollow(bool flag) { m_manual_follow = flag; } + bool GetManualFollow() const { return m_manual_follow; } #endif protected: @@ -1517,6 +1526,7 @@ protected: int8 last_hp_percent; int32 last_hp; + int32 last_max_hp; int cur_wp; glm::vec4 m_CurrentWayPoint; @@ -1581,6 +1591,8 @@ private: #ifdef BOTS std::shared_ptr m_target_of_heal_rotation; + + bool m_manual_follow; #endif }; diff --git a/zone/mob_ai.cpp b/zone/mob_ai.cpp index 4546b5c45..43d85c450 100644 --- a/zone/mob_ai.cpp +++ b/zone/mob_ai.cpp @@ -378,59 +378,6 @@ bool NPC::AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain return CastSpell(AIspells[i].spellid, tar->GetID(), EQEmu::spells::CastingSlot::Gem2, AIspells[i].manacost == -2 ? 0 : -1, mana_cost, oDontDoAgainBefore, -1, -1, 0, &(AIspells[i].resist_adjust)); } -bool EntityList::AICheckCloseBeneficialSpells(NPC* caster, uint8 iChance, float iRange, uint32 iSpellTypes) { - if((iSpellTypes & SPELL_TYPES_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 - LogError("Error: detrimental spells requested from AICheckCloseBeneficialSpells!!"); - return(false); - } - - if(!caster) - return false; - - if(caster->AI_HasSpells() == false) - return false; - - if(caster->GetSpecialAbility(NPC_NO_BUFFHEAL_FRIENDS)) - return false; - - if (iChance < 100) { - uint8 tmp = zone->random.Int(0, 99); - if (tmp >= iChance) - return false; - } - if (caster->GetPrimaryFaction() == 0 ) - return(false); // well, if we dont have a faction set, we're gonna be indiff to everybody - - float iRange2 = iRange*iRange; - - //Only iterate through NPCs - for (auto it = npc_list.begin(); it != npc_list.end(); ++it) { - NPC* mob = it->second; - - if (mob->GetReverseFactionCon(caster) >= FACTION_KINDLY) { - continue; - } - - if (DistanceSquared(caster->GetPosition(), mob->GetPosition()) > iRange2) { - continue; - } - - if ((iSpellTypes & SpellType_Buff) && !RuleB(NPC, BuffFriends)) { - if (mob != caster) - iSpellTypes = SpellType_Heal; - } - - if (caster->AICastSpell(mob, 100, iSpellTypes)) - return true; - } - return false; -} - void Mob::AI_Init() { pAIControlled = false; @@ -1138,6 +1085,17 @@ void Mob::AI_Process() { return; } + if (target->IsMezzed() && IsPet()) { + + auto pet_owner = GetOwner(); + if (pet_owner && pet_owner->IsClient()) { + pet_owner->MessageString(Chat::NPCQuestSay, CANNOT_WAKE, GetCleanName(), target->GetCleanName()); + } + + RemoveFromHateList(target); + return; + } + #ifdef BOTS if (IsPet() && GetOwner() && GetOwner()->IsBot() && target == GetOwner()) { @@ -1404,12 +1362,19 @@ void Mob::AI_Process() { } else if (zone->CanDoCombat() && CastToNPC()->WillAggroNPCs() && AI_scan_area_timer->Check()) { - /* - * NPC to NPC aggro checking, npc needs npc_aggro flag - */ - Mob *temp_target = entity_list.AICheckNPCtoNPCAggro(this, GetAggroRange(), GetAssistRange()); - if (temp_target) { - AddToHateList(temp_target); + /** + * NPC to NPC aggro (npc_aggro flag set) + */ + for (auto &close_mob : close_mobs) { + Mob *mob = close_mob.second; + + if (mob->IsClient()) { + continue; + } + + if (this->CheckWillAggro(mob)) { + this->AddToHateList(mob); + } } AI_scan_area_timer->Disable(); @@ -1728,12 +1693,32 @@ void NPC::AI_DoMovement() { GetZ(), GetGrid()); + if (wandertype == GridRandomPath) + { + if (cur_wp == patrol) + { + // reached our randomly selected destination; force a pause + if (cur_wp_pause == 0) + { + if (Waypoints.size() > 0 && Waypoints[0].pause) + cur_wp_pause = Waypoints[0].pause; + else + cur_wp_pause = 38; + } + Log(Logs::Detail, Logs::AI, "NPC using wander type GridRandomPath on grid %d at waypoint %d has reached its random destination; pause time is %d", GetGrid(), cur_wp, cur_wp_pause); + } + else + cur_wp_pause = 0; // skipping pauses until destination + } + SetWaypointPause(); - SetAppearance(eaStanding, false); - if (cur_wp_pause > 0) { + if (GetAppearance() != eaStanding) { + SetAppearance(eaStanding, false); + } + if (cur_wp_pause > 0 && m_CurrentWayPoint.w >= 0.0) { RotateTo(m_CurrentWayPoint.w); } - + //kick off event_waypoint arrive char temp[16]; sprintf(temp, "%d", cur_wp); @@ -1824,12 +1809,12 @@ void NPC::AI_SetupNextWaypoint() { } } - if (wandertype == 4 && cur_wp == CastToNPC()->GetMaxWp()) { + if (wandertype == GridOneWayRepop && cur_wp == CastToNPC()->GetMaxWp()) { CastToNPC()->Depop(true); //depop and restart spawn timer if (found_spawn) found_spawn->SetNPCPointerNull(); } - else if (wandertype == 6 && cur_wp == CastToNPC()->GetMaxWp()) { + else if (wandertype == GridOneWayDepop && cur_wp == CastToNPC()->GetMaxWp()) { CastToNPC()->Depop(false);//depop without spawn timer if (found_spawn) found_spawn->SetNPCPointerNull(); @@ -1866,47 +1851,46 @@ void NPC::AI_SetupNextWaypoint() { } } -// Note: Mob that caused this may not get added to the hate list until after this function call completes -void Mob::AI_Event_Engaged(Mob* attacker, bool iYellForHelp) { - if (!IsAIControlled()) +/** + * @param attacker + * @param yell_for_help + */ +void Mob::AI_Event_Engaged(Mob *attacker, bool yell_for_help) +{ + if (!IsAIControlled()) { return; + } SetAppearance(eaStanding); - /* - Kick off auto cast timer - */ - if (this->IsNPC()) - this->CastToNPC()->AIautocastspell_timer->Start(300, false); + if (IsNPC()) { + CastToNPC()->AIautocastspell_timer->Start(300, false); - if (iYellForHelp) { - if(IsPet()) { - GetOwner()->AI_Event_Engaged(attacker, iYellForHelp); - } else if (!HasAssistAggro() && NPCAssistCap() < RuleI(Combat, NPCAssistCap)) { - entity_list.AIYellForHelp(this, attacker); - if (NPCAssistCap() > 0 && !assist_cap_timer.Enabled()) - assist_cap_timer.Start(RuleI(Combat, NPCAssistCapTimer)); + if (yell_for_help) { + if (IsPet()) { + GetOwner()->AI_Event_Engaged(attacker, yell_for_help); + } + else if (!HasAssistAggro() && NPCAssistCap() < RuleI(Combat, NPCAssistCap)) { + CastToNPC()->AIYellForHelp(this, attacker); + if (NPCAssistCap() > 0 && !assist_cap_timer.Enabled()) { + assist_cap_timer.Start(RuleI(Combat, NPCAssistCapTimer)); + } + } } - } - if(IsNPC()) - { - if(CastToNPC()->GetGrid() > 0) - { + if (CastToNPC()->GetGrid() > 0) { DistractedFromGrid = true; } - if(attacker && !attacker->IsCorpse()) - { + if (attacker && !attacker->IsCorpse()) { //Because sometimes the AIYellForHelp triggers another engaged and then immediately a not engaged //if the target dies before it goes off - if(attacker->GetHP() > 0) - { - if(!CastToNPC()->GetCombatEvent() && GetHP() > 0) - { + if (attacker->GetHP() > 0) { + if (!CastToNPC()->GetCombatEvent() && GetHP() > 0) { parse->EventNPC(EVENT_COMBAT, CastToNPC(), attacker, "1", 0); uint16 emoteid = GetEmoteID(); - if(emoteid != 0) - CastToNPC()->DoNPCEmote(ENTERCOMBAT,emoteid); + if (emoteid != 0) { + CastToNPC()->DoNPCEmote(ENTERCOMBAT, emoteid); + } CastToNPC()->SetCombatEvent(true); } } @@ -1985,7 +1969,7 @@ bool NPC::AI_EngagedCastCheck() { // try casting a heal or gate if (!AICastSpell(this, AISpellVar.engaged_beneficial_self_chance, SpellType_Heal | SpellType_Escape | SpellType_InCombatBuff)) { // try casting a heal on nearby - if (!entity_list.AICheckCloseBeneficialSpells(this, AISpellVar.engaged_beneficial_other_chance, MobAISpellRange, SpellType_Heal)) { + if (!AICheckCloseBeneficialSpells(this, AISpellVar.engaged_beneficial_other_chance, MobAISpellRange, SpellType_Heal)) { //nobody to heal, try some detrimental spells. if(!AICastSpell(GetTarget(), AISpellVar.engaged_detrimental_chance, SpellType_Nuke | SpellType_Lifetap | SpellType_DOT | SpellType_Dispel | SpellType_Mez | SpellType_Slow | SpellType_Debuff | SpellType_Charm | SpellType_Root)) { //no spell to cast, try again soon. @@ -2022,7 +2006,7 @@ bool NPC::AI_IdleCastCheck() { if (AIautocastspell_timer->Check(false)) { AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting. if (!AICastSpell(this, AISpellVar.idle_beneficial_chance, SpellType_Heal | SpellType_Buff | SpellType_Pet)) { - if(!entity_list.AICheckCloseBeneficialSpells(this, 33, MobAISpellRange, SpellType_Heal | SpellType_Buff)) { + if(!AICheckCloseBeneficialSpells(this, 33, MobAISpellRange, SpellType_Heal | SpellType_Buff)) { //if we didnt cast any spells, our autocast timer just resets to the //last duration it was set to... try to put up a more reasonable timer... AIautocastspell_timer->Start(RandomTimer(AISpellVar.idle_no_sp_recast_min, AISpellVar.idle_no_sp_recast_max), false); diff --git a/zone/mob_movement_manager.cpp b/zone/mob_movement_manager.cpp index 5e40dfeee..4ac0c7f23 100644 --- a/zone/mob_movement_manager.cpp +++ b/zone/mob_movement_manager.cpp @@ -1300,7 +1300,9 @@ void MobMovementManager::PushEvadeCombat(MobMovementEntry &mob_movement_entry) */ void MobMovementManager::HandleStuckBehavior(Mob *who, float x, float y, float z, MobMovementMode mob_movement_mode) { - auto sb = who->GetStuckBehavior(); + LogDebug("Handle stuck behavior for {0} at ({1}, {2}, {3}) with movement_mode {4}", who->GetName(), x, y, z, mob_movement_mode); + + auto sb = who->GetStuckBehavior(); MobStuckBehavior behavior = RunToTarget; if (sb >= 0 && sb < MaxStuckBehavior) { @@ -1308,7 +1310,7 @@ void MobMovementManager::HandleStuckBehavior(Mob *who, float x, float y, float z } auto eiter = _impl->Entries.find(who); - auto &ent = (*eiter); + auto &ent = (*eiter); switch (sb) { case RunToTarget: @@ -1323,8 +1325,7 @@ void MobMovementManager::HandleStuckBehavior(Mob *who, float x, float y, float z PushStopMoving(ent.second); break; case EvadeCombat: - //PushEvadeCombat(ent.second); - PushStopMoving(ent.second); + PushEvadeCombat(ent.second); break; } } diff --git a/zone/npc.cpp b/zone/npc.cpp index 46c2e54a7..ed5f37bb5 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -307,16 +307,18 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi // some overrides -- really we need to be able to set skills for mobs in the DB // There are some known low level SHM/BST pets that do not follow this, which supports // the theory of needing to be able to set skills for each mob separately - if (moblevel > 50) { - skills[EQEmu::skills::SkillDoubleAttack] = 250; - skills[EQEmu::skills::SkillDualWield] = 250; - } - else if (moblevel > 3) { - skills[EQEmu::skills::SkillDoubleAttack] = moblevel * 5; - skills[EQEmu::skills::SkillDualWield] = skills[EQEmu::skills::SkillDoubleAttack]; - } - else { - skills[EQEmu::skills::SkillDoubleAttack] = moblevel * 5; + if (!IsBot()) { + if (moblevel > 50) { + skills[EQEmu::skills::SkillDoubleAttack] = 250; + skills[EQEmu::skills::SkillDualWield] = 250; + } + else if (moblevel > 3) { + skills[EQEmu::skills::SkillDoubleAttack] = moblevel * 5; + skills[EQEmu::skills::SkillDualWield] = skills[EQEmu::skills::SkillDoubleAttack]; + } + else { + skills[EQEmu::skills::SkillDoubleAttack] = moblevel * 5; + } } ldon_trapped = false; @@ -702,6 +704,24 @@ bool NPC::Process() SpellProcess(); + if (mob_scan_close.Check()) { + + entity_list.ScanCloseMobs(close_mobs, this); + + if (moving) { + mob_scan_close.Disable(); + mob_scan_close.Start(RandomTimer(3000, 6000)); + } + else { + mob_scan_close.Disable(); + mob_scan_close.Start(RandomTimer(6000, 60000)); + } + } + + if (mob_check_moving_timer.Check() && moving) { + mob_scan_close.Trigger(); + } + if (tic_timer.Check()) { parse->EventNPC(EVENT_TICK, this, nullptr, "", 0); BuffProcess(); @@ -849,7 +869,7 @@ bool NPC::Process() if (assist_timer.Check() && IsEngaged() && !Charmed() && !HasAssistAggro() && NPCAssistCap() < RuleI(Combat, NPCAssistCap)) { - entity_list.AIYellForHelp(this, GetTarget()); + AIYellForHelp(this, GetTarget()); if (NPCAssistCap() > 0 && !assist_cap_timer.Enabled()) assist_cap_timer.Start(RuleI(Combat, NPCAssistCapTimer)); } @@ -2973,6 +2993,11 @@ bool NPC::IsProximitySet() return false; } +/** + * @param box_size + * @param move_distance + * @param move_delay + */ void NPC::SetSimpleRoamBox(float box_size, float move_distance, int move_delay) { AI_SetRoambox( @@ -2983,4 +3008,191 @@ void NPC::SetSimpleRoamBox(float box_size, float move_distance, int move_delay) GetY() - box_size, move_delay ); +} + +/** + * @param caster + * @param chance + * @param cast_range + * @param spell_types + * @return + */ +bool NPC::AICheckCloseBeneficialSpells( + NPC *caster, + uint8 chance, + float cast_range, + uint32 spell_types +) +{ + if((spell_types & SPELL_TYPES_DETRIMENTAL) != 0) { + LogError("Detrimental spells requested from AICheckCloseBeneficialSpells!"); + return false; + } + + if (!caster) { + return false; + } + + if (!caster->AI_HasSpells()) { + return false; + } + + if (caster->GetSpecialAbility(NPC_NO_BUFFHEAL_FRIENDS)) { + return false; + } + + if (chance < 100) { + uint8 tmp = zone->random.Int(0, 99); + if (tmp >= chance) { + return false; + } + } + + /** + * Indifferent + */ + if (caster->GetPrimaryFaction() == 0) { + return false; + } + + /** + * Check through close range mobs + */ + for (auto & close_mob : entity_list.GetCloseMobList(caster, cast_range)) { + Mob *mob = close_mob.second; + + if (mob->IsClient()) { + continue; + } + + float distance = Distance(mob->GetPosition(), caster->GetPosition()); + if (distance > cast_range) { + continue; + } + + LogAICastBeneficialClose( + "NPC [{}] Distance [{}] Cast Range [{}] Caster [{}]", + mob->GetCleanName(), + distance, + cast_range, + caster->GetCleanName() + ); + + if (mob->GetReverseFactionCon(caster) >= FACTION_KINDLY) { + continue; + } + + if ((spell_types & SpellType_Buff) && !RuleB(NPC, BuffFriends)) { + if (mob != caster) { + spell_types = SpellType_Heal; + } + } + + if (caster->AICastSpell(mob, 100, spell_types)) { + return true; + } + } + + return false; +} + +/** + * @param sender + * @param attacker + */ +void NPC::AIYellForHelp(Mob *sender, Mob *attacker) +{ + if (!sender || !attacker) { + return; + } + + /** + * If we dont have a faction set, we're gonna be indiff to everybody + */ + if (sender->GetPrimaryFaction() == 0) { + return; + } + + if (sender->HasAssistAggro()) + return; + + LogAIYellForHelp( + "NPC [{}] ID [{}] is starting to scan", + GetCleanName(), + GetID() + ); + + for (auto &close_mob : entity_list.GetCloseMobList(sender)) { + Mob *mob = close_mob.second; + float distance = DistanceSquared(m_Position, mob->GetPosition()); + + if (mob->IsClient()) { + continue; + } + + float assist_range = (mob->GetAssistRange() * mob->GetAssistRange()); + if (distance > assist_range) { + continue; + } + + LogAIYellForHelpDetail( + "NPC [{}] ID [{}] is scanning - checking against NPC [{}] range [{}] dist [{}] in_range [{}]", + GetCleanName(), + GetID(), + mob->GetCleanName(), + assist_range, + distance, + (distance < assist_range) + ); + + if (mob->CheckAggro(attacker)) { + continue; + } + + if (sender->NPCAssistCap() >= RuleI(Combat, NPCAssistCap)) { + break; + } + + if ( + mob != sender + && mob != attacker + && mob->GetPrimaryFaction() != 0 + && !mob->IsEngaged() + && ((!mob->IsPet()) || (mob->IsPet() && mob->GetOwner() && !mob->GetOwner()->IsClient())) + ) { + + /** + * if they are in range, make sure we are not green... + * then jump in if they are our friend + */ + if (mob->GetLevel() >= 50 || attacker->GetLevelCon(mob->GetLevel()) != CON_GRAY) { + bool use_primary_faction = false; + if (mob->GetPrimaryFaction() == sender->CastToNPC()->GetPrimaryFaction()) { + const NPCFactionList *cf = database.GetNPCFactionEntry(mob->CastToNPC()->GetNPCFactionID()); + if (cf) { + if (cf->assistprimaryfaction != 0) { + use_primary_faction = true; + } + } + } + + if (use_primary_faction || sender->GetReverseFactionCon(mob) <= FACTION_AMIABLE) { + //attacking someone on same faction, or a friend + //Father Nitwit: make sure we can see them. + if (mob->CheckLosFN(sender)) { + mob->AddToHateList(attacker, 25, 0, false); + sender->AddAssistCap(); + + LogAIYellForHelpDetail( + "NPC [{}] is assisting [{}] against target [{}]", + mob->GetCleanName(), + this->GetCleanName(), + attacker->GetCleanName() + ); + } + } + } + } + } + } \ No newline at end of file diff --git a/zone/npc.h b/zone/npc.h index 5d7a7bd73..d0ecdf1ca 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -143,6 +143,9 @@ public: virtual bool AI_IdleCastCheck(); virtual void AI_Event_SpellCastFinished(bool iCastSucceeded, uint16 slot); + bool AICheckCloseBeneficialSpells(NPC* caster, uint8 chance, float cast_range, uint32 spell_types); + void AIYellForHelp(Mob* sender, Mob* attacker); + void LevelScale(); virtual void SetTarget(Mob* mob); @@ -300,7 +303,7 @@ public: int GetMaxWp() const { return max_wp; } void DisplayWaypointInfo(Client *to); void CalculateNewWaypoint(); - void AssignWaypoints(int32 grid); + void AssignWaypoints(int32 grid, int start_wp = 0); void SetWaypointPause(); void UpdateWaypoint(int wp_index); @@ -309,7 +312,8 @@ public: void ResumeWandering(); void PauseWandering(int pausetime); void MoveTo(const glm::vec4& position, bool saveguardspot); - void GetClosestWaypoint(std::list &wp_list, int count, const glm::vec3& location); + void GetClosestWaypoints(std::list &wp_list, int count, const glm::vec3& location); + int GetClosestWaypoint(const glm::vec3& location); uint32 GetEquippedItemFromTextureSlot(uint8 material_slot) const; // returns item id int32 GetEquipmentMaterial(uint8 material_slot) const; diff --git a/zone/pathfinder_nav_mesh.cpp b/zone/pathfinder_nav_mesh.cpp index 5be10b64f..63e76f486 100644 --- a/zone/pathfinder_nav_mesh.cpp +++ b/zone/pathfinder_nav_mesh.cpp @@ -12,8 +12,6 @@ extern Zone *zone; -const int MaxNavmeshNodes = 1024; - struct PathfinderNavmesh::Implementation { dtNavMesh *nav_mesh; @@ -36,19 +34,19 @@ PathfinderNavmesh::~PathfinderNavmesh() IPathfinder::IPath PathfinderNavmesh::FindRoute(const glm::vec3 &start, const glm::vec3 &end, bool &partial, bool &stuck, int flags) { partial = false; - + if (!m_impl->nav_mesh) { return IPath(); } - + if (!m_impl->query) { m_impl->query = dtAllocNavMeshQuery(); } - - m_impl->query->init(m_impl->nav_mesh, MaxNavmeshNodes); + + m_impl->query->init(m_impl->nav_mesh, RuleI(Pathing, MaxNavmeshNodes)); glm::vec3 current_location(start.x, start.z, start.y); glm::vec3 dest_location(end.x, end.z, end.y); - + dtQueryFilter filter; filter.setIncludeFlags(flags); filter.setAreaCost(0, 1.0f); //Normal @@ -61,48 +59,48 @@ IPathfinder::IPath PathfinderNavmesh::FindRoute(const glm::vec3 &start, const gl filter.setAreaCost(8, 1.0f); //General Area filter.setAreaCost(9, 0.1f); //Portal filter.setAreaCost(10, 0.1f); //Prefer - + dtPolyRef start_ref; dtPolyRef end_ref; glm::vec3 ext(5.0f, 100.0f, 5.0f); - + m_impl->query->findNearestPoly(¤t_location[0], &ext[0], &filter, &start_ref, 0); m_impl->query->findNearestPoly(&dest_location[0], &ext[0], &filter, &end_ref, 0); - + if (!start_ref || !end_ref) { return IPath(); } - + int npoly = 0; dtPolyRef path[1024] = { 0 }; auto status = m_impl->query->findPath(start_ref, end_ref, ¤t_location[0], &dest_location[0], &filter, path, &npoly, 1024); - + if (npoly) { glm::vec3 epos = dest_location; if (path[npoly - 1] != end_ref) { m_impl->query->closestPointOnPoly(path[npoly - 1], &dest_location[0], &epos[0], 0); partial = true; - + auto dist = DistanceSquared(epos, current_location); if (dist < 10000.0f) { stuck = true; } } - + float straight_path[2048 * 3]; unsigned char straight_path_flags[2048]; - + int n_straight_polys; dtPolyRef straight_path_polys[2048]; - + status = m_impl->query->findStraightPath(¤t_location[0], &epos[0], path, npoly, straight_path, straight_path_flags, straight_path_polys, &n_straight_polys, 2048, DT_STRAIGHTPATH_AREA_CROSSINGS); - + if (dtStatusFailed(status)) { return IPath(); } - + if (n_straight_polys) { IPath Route; for (int i = 0; i < n_straight_polys; ++i) @@ -111,9 +109,9 @@ IPathfinder::IPath PathfinderNavmesh::FindRoute(const glm::vec3 &start, const gl node.x = straight_path[i * 3]; node.z = straight_path[i * 3 + 1]; node.y = straight_path[i * 3 + 2]; - + Route.push_back(node); - + unsigned short flag = 0; if (dtStatusSucceed(m_impl->nav_mesh->getPolyFlags(straight_path_polys[i], &flag))) { if (flag & 512) { @@ -121,11 +119,11 @@ IPathfinder::IPath PathfinderNavmesh::FindRoute(const glm::vec3 &start, const gl } } } - + return Route; } } - + IPath Route; Route.push_back(end); return Route; @@ -134,19 +132,19 @@ IPathfinder::IPath PathfinderNavmesh::FindRoute(const glm::vec3 &start, const gl IPathfinder::IPath PathfinderNavmesh::FindPath(const glm::vec3 &start, const glm::vec3 &end, bool &partial, bool &stuck, const PathfinderOptions &opts) { partial = false; - + if (!m_impl->nav_mesh) { return IPath(); } - + if (!m_impl->query) { m_impl->query = dtAllocNavMeshQuery(); } - - m_impl->query->init(m_impl->nav_mesh, MaxNavmeshNodes); + + m_impl->query->init(m_impl->nav_mesh, RuleI(Pathing, MaxNavmeshNodes)); glm::vec3 current_location(start.x, start.z, start.y); glm::vec3 dest_location(end.x, end.z, end.y); - + dtQueryFilter filter; filter.setIncludeFlags(opts.flags); filter.setAreaCost(0, opts.flag_cost[0]); //Normal @@ -159,83 +157,78 @@ IPathfinder::IPath PathfinderNavmesh::FindPath(const glm::vec3 &start, const glm filter.setAreaCost(8, opts.flag_cost[7]); //General Area filter.setAreaCost(9, opts.flag_cost[8]); //Portal filter.setAreaCost(10, opts.flag_cost[9]); //Prefer - + static const int max_polys = 256; dtPolyRef start_ref; dtPolyRef end_ref; glm::vec3 ext(10.0f, 200.0f, 10.0f); - + m_impl->query->findNearestPoly(¤t_location[0], &ext[0], &filter, &start_ref, 0); m_impl->query->findNearestPoly(&dest_location[0], &ext[0], &filter, &end_ref, 0); - + if (!start_ref || !end_ref) { return IPath(); } - + int npoly = 0; dtPolyRef path[max_polys] = { 0 }; - m_impl->query->findPath(start_ref, end_ref, ¤t_location[0], &dest_location[0], &filter, path, &npoly, max_polys); - + auto status = m_impl->query->findPath(start_ref, end_ref, ¤t_location[0], &dest_location[0], &filter, path, &npoly, max_polys); + if (npoly) { glm::vec3 epos = dest_location; if (path[npoly - 1] != end_ref) { m_impl->query->closestPointOnPoly(path[npoly - 1], &dest_location[0], &epos[0], 0); partial = true; - - auto dist = DistanceSquared(epos, current_location); - if (dist < 10000.0f) { - stuck = true; - } } - + int n_straight_polys; glm::vec3 straight_path[max_polys]; unsigned char straight_path_flags[max_polys]; dtPolyRef straight_path_polys[max_polys]; - + auto status = m_impl->query->findStraightPath(¤t_location[0], &epos[0], path, npoly, (float*)&straight_path[0], straight_path_flags, straight_path_polys, &n_straight_polys, 2048, DT_STRAIGHTPATH_AREA_CROSSINGS | DT_STRAIGHTPATH_ALL_CROSSINGS); - + if (dtStatusFailed(status)) { return IPath(); } - + if (n_straight_polys) { if (opts.smooth_path) { IPath Route; - + //Add the first point { auto &flag = straight_path_flags[0]; if (flag & DT_STRAIGHTPATH_OFFMESH_CONNECTION) { auto &p = straight_path[0]; - + Route.push_back(glm::vec3(p.x, p.z, p.y)); } else { auto &p = straight_path[0]; - + float h = 0.0f; if (dtStatusSucceed(GetPolyHeightOnPath(path, npoly, p, &h))) { p.y = h + opts.offset; } - + Route.push_back(glm::vec3(p.x, p.z, p.y)); } } - + for (int i = 0; i < n_straight_polys - 1; ++i) { auto &flag = straight_path_flags[i]; - + if (flag & DT_STRAIGHTPATH_OFFMESH_CONNECTION) { auto &poly = straight_path_polys[i]; - + auto &p2 = straight_path[i + 1]; glm::vec3 node(p2.x, p2.z, p2.y); Route.push_back(node); - + unsigned short pflag = 0; if (dtStatusSucceed(m_impl->nav_mesh->getPolyFlags(straight_path_polys[i], &pflag))) { if (pflag & 512) { @@ -250,12 +243,12 @@ IPathfinder::IPath PathfinderNavmesh::FindPath(const glm::vec3 &start, const glm auto dir = glm::normalize(p2 - p1); float total = 0.0f; glm::vec3 previous_pt = p1; - + while (total < dist) { glm::vec3 current_pt; float dist_to_move = opts.step_size; float ff = opts.step_size / 2.0f; - + if (total + dist_to_move + ff >= dist) { current_pt = p2; total = dist; @@ -264,18 +257,18 @@ IPathfinder::IPath PathfinderNavmesh::FindPath(const glm::vec3 &start, const glm total += dist_to_move; current_pt = p1 + dir * total; } - + float h = 0.0f; if (dtStatusSucceed(GetPolyHeightOnPath(path, npoly, current_pt, &h))) { current_pt.y = h + opts.offset; } - + Route.push_back(glm::vec3(current_pt.x, current_pt.z, current_pt.y)); previous_pt = current_pt; } } } - + return Route; } else { @@ -285,7 +278,7 @@ IPathfinder::IPath PathfinderNavmesh::FindPath(const glm::vec3 &start, const glm auto ¤t = straight_path[i]; glm::vec3 node(current.x, current.z, current.y); Route.push_back(node); - + unsigned short flag = 0; if (dtStatusSucceed(m_impl->nav_mesh->getPolyFlags(straight_path_polys[i], &flag))) { if (flag & 512) { @@ -293,7 +286,7 @@ IPathfinder::IPath PathfinderNavmesh::FindPath(const glm::vec3 &start, const glm } } } - + return Route; } } @@ -313,7 +306,7 @@ glm::vec3 PathfinderNavmesh::GetRandomLocation(const glm::vec3 &start) if (!m_impl->query) { m_impl->query = dtAllocNavMeshQuery(); - m_impl->query->init(m_impl->nav_mesh, MaxNavmeshNodes); + m_impl->query->init(m_impl->nav_mesh, RuleI(Pathing, MaxNavmeshNodes)); } dtQueryFilter filter; diff --git a/zone/perl_mob.cpp b/zone/perl_mob.cpp index ca72ca803..cf6ef4be7 100644 --- a/zone/perl_mob.cpp +++ b/zone/perl_mob.cpp @@ -95,6 +95,31 @@ XS(XS_Mob_IsNPC) { XSRETURN(1); } +XS(XS_Mob_IsBot); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Mob_IsBot) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Mob::IsBot(THIS)"); + { + Mob* THIS; + bool RETVAL; + + if (sv_derived_from(ST(0), "Mob")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(Mob*, tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type Mob"); + if (THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + RETVAL = THIS->IsBot(); + ST(0) = boolSV(RETVAL); + sv_2mortal(ST(0)); + } + XSRETURN(1); +} + XS(XS_Mob_IsMob); /* prototype to pass -Wmissing-prototypes */ XS(XS_Mob_IsMob) { dXSARGS; @@ -8564,6 +8589,7 @@ XS(boot_Mob) { newXSproto(strcpy(buf, "IsClient"), XS_Mob_IsClient, file, "$"); newXSproto(strcpy(buf, "IsNPC"), XS_Mob_IsNPC, file, "$"); + newXSproto(strcpy(buf, "IsBot"), XS_Mob_IsBot, file, "$"); newXSproto(strcpy(buf, "IsMob"), XS_Mob_IsMob, file, "$"); newXSproto(strcpy(buf, "IsCorpse"), XS_Mob_IsCorpse, file, "$"); newXSproto(strcpy(buf, "IsPlayerCorpse"), XS_Mob_IsPlayerCorpse, file, "$"); diff --git a/zone/pets.cpp b/zone/pets.cpp index 438509b77..b4897940a 100644 --- a/zone/pets.cpp +++ b/zone/pets.cpp @@ -199,7 +199,7 @@ void Mob::MakePoweredPet(uint16 spell_id, const char* pettype, int16 petpower, } #ifdef BOTS else if (this->IsBot()) - act_power = CastToBot()->GetBotFocusEffect(Bot::BotfocusPetPower, spell_id); + act_power = CastToBot()->GetBotFocusEffect(focusPetPower, spell_id); #endif } else if (petpower > 0) diff --git a/zone/quest_interface.h b/zone/quest_interface.h index 36ca9458f..63310efae 100644 --- a/zone/quest_interface.h +++ b/zone/quest_interface.h @@ -84,7 +84,9 @@ public: err.insert(err.end(), errors_.begin(), errors_.end()); } - virtual void AddError(std::string error) { + virtual void AddError(std::string error) { + LogQuests("{}", error); + errors_.push_back(error); if(errors_.size() > 30) { errors_.pop_front(); diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index 69e55a57f..bea136e28 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -1910,6 +1910,21 @@ bool QuestManager::summonallplayercorpses(uint32 char_id, const glm::vec4& posit return true; } +int QuestManager::getplayercorpsecount(uint32 char_id) { + if (char_id > 0) { + return database.CountCharacterCorpses(char_id); + } + return 0; + +} + +int QuestManager::getplayercorpsecountbyzoneid(uint32 char_id, uint32 zone_id) { + if (char_id > 0 && zone_id > 0) { + return database.CountCharacterCorpsesByZoneID(char_id, zone_id); + } + return 0; +} + uint32 QuestManager::getplayerburiedcorpsecount(uint32 char_id) { uint32 Result = 0; diff --git a/zone/questmgr.h b/zone/questmgr.h index 1b9ea9c4d..5ad551c43 100644 --- a/zone/questmgr.h +++ b/zone/questmgr.h @@ -172,6 +172,8 @@ public: bool summonburiedplayercorpse(uint32 char_id, const glm::vec4& position); bool summonallplayercorpses(uint32 char_id, const glm::vec4& position); uint32 getplayerburiedcorpsecount(uint32 char_id); + int getplayercorpsecount(uint32 char_id); + int getplayercorpsecountbyzoneid(uint32 char_id, uint32 zone_id); bool buryplayercorpse(uint32 char_id); void forcedooropen(uint32 doorid, bool altmode); void forcedoorclose(uint32 doorid, bool altmode); diff --git a/zone/spawn2.cpp b/zone/spawn2.cpp index e6d265813..958500550 100644 --- a/zone/spawn2.cpp +++ b/zone/spawn2.cpp @@ -233,6 +233,20 @@ bool Spawn2::Process() { } currentnpcid = npcid; + + glm::vec4 loc(x, y, z, heading); + int starting_wp = 0; + if (spawn_group->wp_spawns && grid_ > 0) + { + glm::vec4 wploc; + starting_wp = database.GetRandomWaypointLocFromGrid(wploc, zone->GetZoneID(), grid_); + if (wploc.x != 0.0f || wploc.y != 0.0f || wploc.z != 0.0f) + { + loc = wploc; + Log(Logs::General, Logs::Spawns, "spawning at random waypoint #%i loc: (%.3f, %.3f, %.3f).", starting_wp , loc.x, loc.y, loc.z); + } + } + NPC *npc = new NPC(tmp, this, glm::vec4(x, y, z, heading), GravityBehavior::Water); npc->mod_prespawn(this); @@ -275,7 +289,7 @@ bool Spawn2::Process() { z ); - LoadGrid(); + LoadGrid(starting_wp); } else { LogSpawns("Spawn2 [{}]: Group [{}] spawned [{}] ([{}]) at ([{}], [{}], [{}]). Grid loading delayed", @@ -302,7 +316,7 @@ void Spawn2::Disable() enabled = false; } -void Spawn2::LoadGrid() { +void Spawn2::LoadGrid(int start_wp) { if (!npcthis) return; if (grid_ < 1) @@ -311,8 +325,8 @@ void Spawn2::LoadGrid() { return; //dont set an NPC's grid until its loaded for them. npcthis->SetGrid(grid_); - npcthis->AssignWaypoints(grid_); - LogSpawns("Spawn2 [{}]: Loading grid [{}] for [{}]", spawn2_id, grid_, npcthis->GetName()); + npcthis->AssignWaypoints(grid_, start_wp); + LogSpawns("Spawn2 [{}]: Loading grid [{}] for [{}]; starting wp is [{}]", spawn2_id, grid_, npcthis->GetName(), start_wp); } /* diff --git a/zone/spawn2.h b/zone/spawn2.h index a626b7084..bf6530876 100644 --- a/zone/spawn2.h +++ b/zone/spawn2.h @@ -36,7 +36,7 @@ public: uint16 cond_id = SC_AlwaysEnabled, int16 min_value = 0, bool in_enabled = true, EmuAppearance anim = eaStanding); ~Spawn2(); - void LoadGrid(); + void LoadGrid(int start_wp = 0); void Enable() { enabled = true; } void Disable(); bool Enabled() { return enabled; } diff --git a/zone/spawngroup.cpp b/zone/spawngroup.cpp index cc3735386..6a7817bce 100644 --- a/zone/spawngroup.cpp +++ b/zone/spawngroup.cpp @@ -48,7 +48,8 @@ SpawnGroup::SpawnGroup( int delay_in, int despawn_in, uint32 despawn_timer_in, - int min_delay_in + int min_delay_in, + bool wp_spawns_in ) { id = in_id; @@ -63,6 +64,7 @@ SpawnGroup::SpawnGroup( delay = delay_in; despawn = despawn_in; despawn_timer = despawn_timer_in; + wp_spawns = wp_spawns_in; } uint32 SpawnGroup::GetNPCType(uint16 in_filter) @@ -198,7 +200,8 @@ bool ZoneDatabase::LoadSpawnGroups(const char *zone_name, uint16 version, SpawnG spawngroup.delay, spawngroup.despawn, spawngroup.despawn_timer, - spawngroup.mindelay + spawngroup.mindelay, + spawngroup.wp_spawns FROM spawn2, spawngroup @@ -229,7 +232,8 @@ bool ZoneDatabase::LoadSpawnGroups(const char *zone_name, uint16 version, SpawnG atoi(row[8]), atoi(row[9]), atoi(row[10]), - atoi(row[11]) + atoi(row[11]), + atoi(row[12]) ); spawn_group_list->AddSpawnGroup(new_spawn_group); @@ -305,7 +309,8 @@ bool ZoneDatabase::LoadSpawnGroupsByID(int spawn_group_id, SpawnGroupList *spawn spawngroup.delay, spawngroup.despawn, spawngroup.despawn_timer, - spawngroup.mindelay + spawngroup.mindelay, + spawngroup.wp_spawns FROM spawngroup WHERE @@ -332,7 +337,8 @@ bool ZoneDatabase::LoadSpawnGroupsByID(int spawn_group_id, SpawnGroupList *spawn atoi(row[8]), atoi(row[9]), atoi(row[10]), - atoi(row[11]) + atoi(row[11]), + atoi(row[12]) ); spawn_group_list->AddSpawnGroup(new_spawn_group); diff --git a/zone/spawngroup.h b/zone/spawngroup.h index 2a4c1af6c..a1068cea0 100644 --- a/zone/spawngroup.h +++ b/zone/spawngroup.h @@ -49,13 +49,15 @@ public: int delay_in, int despawn_in, uint32 despawn_timer_in, - int min_delay_in + int min_delay_in, + bool wp_spawns_in ); ~SpawnGroup(); uint32 GetNPCType(uint16 condition_value_filter=1); void AddSpawnEntry(SpawnEntry *newEntry); uint32 id; + bool wp_spawns; // if true, spawn NPCs at a random waypoint location (if spawnpoint has a grid) instead of the spawnpoint's loc float roamdist; float roambox[4]; int min_delay; diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 406a801d0..63a4a113f 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -286,6 +286,9 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove dmg = -dmg; } + // hack fix for client health not reflecting server value + last_hp = 0; + //do any AAs apply to these spells? if(dmg < 0) { if (!PassCastRestriction(false, spells[spell_id].base2[i], true)) @@ -1820,6 +1823,58 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } + case SE_SummonCorpseZone: + { + if (IsClient()) { + Client* client_target = this->CastToClient(); + if (client_target->IsGrouped()) { + Group* group = client_target->GetGroup(); + if (!group->IsGroupMember(caster)) { + if (caster != this) { + caster->MessageString(Chat::Red, SUMMON_ONLY_GROUP_CORPSE); + break; + } + } + } else if (caster) { + if (caster->IsRaidGrouped()) { + Raid *raid = caster->GetRaid(); + uint32 group_id = raid->GetGroup(caster->GetName()); + if (group_id > 0 && group_id < MAX_RAID_GROUPS) { + if (raid->GetGroup(client_target->GetName()) != group_id) { + caster->MessageString(Chat::Red, SUMMON_ONLY_GROUP_CORPSE); + break; + } + } + } else { + if (caster != this) { + caster->MessageString(Chat::Red, SUMMON_ONLY_GROUP_CORPSE); + break; + } + } + } + + if (client_target) { + if (database.CountCharacterCorpses(client_target->CharacterID()) == 0) { + if (caster == this) { + Message(Chat::Yellow, "You have no corpses to summon."); + } else { + caster->Message(Chat::Yellow, "%s has no corpses to summon.", client_target->GetCleanName()); + } + } else { + if (caster == this) { + Message(Chat::Spells, "Summoning your corpses."); + } else { + caster->MessageString(Chat::Spells, SUMMONING_CORPSE_ZONE, client_target->GetCleanName()); + } + client_target->SummonAllCorpses(client_target->GetPosition()); + } + } else { + MessageString(Chat::Spells, TARGET_NOT_FOUND); + LogError("[{}] attempted to cast spell id [{}] with spell effect SE_SummonCorpseZone, but could not cast target into a Client object", GetCleanName(), spell_id); + } + } + break; + } case SE_AddMeleeProc: case SE_WeaponProc: { @@ -4011,10 +4066,9 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses) break; } - case SE_ImmuneFleeing: - { - if(RuleB(Combat, EnableFearPathing)){ - if(flee_mode) { + case SE_ImmuneFleeing: { + if (RuleB(Combat, EnableFearPathing)) { + if (flee_mode) { currently_fleeing = true; CheckFlee(); break; @@ -5662,11 +5716,24 @@ void Mob::CheckNumHitsRemaining(NumHit type, int32 buff_slot, uint16 spell_id) bool bDepleted = false; int buff_max = GetMaxTotalSlots(); +#ifdef BOTS + std::string buff_name; + size_t buff_counter = 0; + bool buff_update = false; +#endif + //Spell specific procs [Type 7,10,11] if (IsValidSpell(spell_id)) { for (int d = 0; d < buff_max; d++) { if (buffs[d].spellid == spell_id && buffs[d].numhits > 0 && spells[buffs[d].spellid].numhitstype == static_cast(type)) { + +#ifdef BOTS + buff_name = spells[buffs[d].spellid].name; + buff_counter = (buffs[d].numhits - 1); + buff_update = true; +#endif + if (--buffs[d].numhits == 0) { CastOnNumHitFade(buffs[d].spellid); if (!TryFadeEffect(d)) @@ -5679,6 +5746,13 @@ void Mob::CheckNumHitsRemaining(NumHit type, int32 buff_slot, uint16 spell_id) } else if (type == NumHit::MatchingSpells) { if (buff_slot >= 0) { if (--buffs[buff_slot].numhits == 0) { + +#ifdef BOTS + buff_name = spells[buffs[buff_slot].spellid].name; + buff_counter = (buffs[buff_slot].numhits - 1); + buff_update = true; +#endif + CastOnNumHitFade(buffs[buff_slot].spellid); if (!TryFadeEffect(buff_slot)) BuffFadeBySlot(buff_slot , true); @@ -5691,6 +5765,13 @@ void Mob::CheckNumHitsRemaining(NumHit type, int32 buff_slot, uint16 spell_id) continue; if (IsValidSpell(buffs[d].spellid) && m_spellHitsLeft[d] == buffs[d].spellid) { + +#ifdef BOTS + buff_name = spells[buffs[d].spellid].name; + buff_counter = (buffs[d].numhits - 1); + buff_update = true; +#endif + if (--buffs[d].numhits == 0) { CastOnNumHitFade(buffs[d].spellid); m_spellHitsLeft[d] = 0; @@ -5706,6 +5787,13 @@ void Mob::CheckNumHitsRemaining(NumHit type, int32 buff_slot, uint16 spell_id) for (int d = 0; d < buff_max; d++) { if (IsValidSpell(buffs[d].spellid) && buffs[d].numhits > 0 && spells[buffs[d].spellid].numhitstype == static_cast(type)) { + +#ifdef BOTS + buff_name = spells[buffs[d].spellid].name; + buff_counter = (buffs[d].numhits - 1); + buff_update = true; +#endif + if (--buffs[d].numhits == 0) { CastOnNumHitFade(buffs[d].spellid); if (!TryFadeEffect(d)) @@ -5716,6 +5804,28 @@ void Mob::CheckNumHitsRemaining(NumHit type, int32 buff_slot, uint16 spell_id) } } } + +#ifdef BOTS + if (IsBot() && buff_update) { + auto bot_owner = entity_list.GetBotOwnerByBotEntityID(GetID()); + if (bot_owner && bot_owner->GetBotOption(Client::booBuffCounter)) { + bot_owner->CastToClient()->SendMarqueeMessage( + Chat::Yellow, + 510, + 0, + 1000, + 3000, + StringFormat( + "%s has [%u] hit%s remaining on '%s'", + GetCleanName(), + buff_counter, + (buff_counter == 1 ? "" : "s"), + buff_name.c_str() + ) + ); + } + } +#endif } //for some stupid reason SK procs return theirs one base off... diff --git a/zone/spells.cpp b/zone/spells.cpp index cec6e8142..a1e926a1c 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -967,6 +967,7 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo uint16 mana_used, uint32 inventory_slot, int16 resist_adjust) { bool IsFromItem = false; + EQEmu::ItemInstance *item = nullptr; if(IsClient() && slot != CastingSlot::Item && slot != CastingSlot::PotionBelt && spells[spell_id].recast_time > 1000) { // 10 is item if(!CastToClient()->GetPTimers().Expired(&database, pTimerSpellStart + spell_id, false)) { @@ -981,10 +982,10 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo if(IsClient() && (slot == CastingSlot::Item || slot == CastingSlot::PotionBelt)) { IsFromItem = true; - EQEmu::ItemInstance *itm = CastToClient()->GetInv().GetItem(inventory_slot); - if(itm && itm->GetItem()->RecastDelay > 0) + item = CastToClient()->GetInv().GetItem(inventory_slot); + if(item && item->GetItem()->RecastDelay > 0) { - if(!CastToClient()->GetPTimers().Expired(&database, (pTimerItemStart + itm->GetItem()->RecastType), false)) { + if(!CastToClient()->GetPTimers().Expired(&database, (pTimerItemStart + item->GetItem()->RecastType), false)) { MessageString(Chat::Red, SPELL_RECAST); LogSpells("Casting of [{}] canceled: item spell reuse timer not expired", spell_id); StopCasting(); @@ -1150,7 +1151,13 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo // first check for component reduction if(IsClient()) { int reg_focus = CastToClient()->GetFocusEffect(focusReagentCost,spell_id);//Client only - if(zone->random.Roll(reg_focus)) { + /* it seems something causes some items not to consume reagents, it's not click type or temp flag + * it maybe cast time being instant, which I had a hard time disproving, so lets do that + * Items that might prove this wrong: Mystic Cloak (1057), Moss Mask (1400), and a bunch others + */ + if (item && item->GetItem() && item->GetItem()->CastTime == 0) { + LogSpells("Spell [{}]: Casted from instant clicky, prevent reagent consumption", spell_id); + } else if(zone->random.Roll(reg_focus)) { LogSpells("Spell [{}]: Reagent focus item prevented reagent consumption ([{}] chance)", spell_id, reg_focus); } else { if(reg_focus > 0) @@ -1298,17 +1305,16 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo && inventory_slot != 0xFFFFFFFF) // 10 is an item { bool fromaug = false; - const EQEmu::ItemInstance* inst = CastToClient()->GetInv()[inventory_slot]; EQEmu::ItemData* augitem = nullptr; uint32 recastdelay = 0; uint32 recasttype = 0; while (true) { - if (inst == nullptr) + if (item == nullptr) break; for (int r = EQEmu::invaug::SOCKET_BEGIN; r <= EQEmu::invaug::SOCKET_END; r++) { - const EQEmu::ItemInstance* aug_i = inst->GetAugment(r); + const EQEmu::ItemInstance* aug_i = item->GetAugment(r); if (!aug_i) continue; @@ -1346,18 +1352,18 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo } } - if (inst && inst->IsClassCommon() && (inst->GetItem()->Click.Effect == spell_id) && inst->GetCharges() || fromaug) + if (item && item->IsClassCommon() && (item->GetItem()->Click.Effect == spell_id) && item->GetCharges() || fromaug) { - //const ItemData* item = inst->GetItem(); - int16 charges = inst->GetItem()->MaxCharges; + //const ItemData* item = item->GetItem(); + int16 charges = item->GetItem()->MaxCharges; if(fromaug) { charges = -1; } //Don't destroy the parent item if(charges > -1) { // charged item, expend a charge - LogSpells("Spell [{}]: Consuming a charge from item [{}] ([{}]) which had [{}]/[{}] charges", spell_id, inst->GetItem()->Name, inst->GetItem()->ID, inst->GetCharges(), inst->GetItem()->MaxCharges); + LogSpells("Spell [{}]: Consuming a charge from item [{}] ([{}]) which had [{}]/[{}] charges", spell_id, item->GetItem()->Name, item->GetItem()->ID, item->GetCharges(), item->GetItem()->MaxCharges); DeleteChargeFromSlot = inventory_slot; } else { - LogSpells("Spell [{}]: Cast from unlimited charge item [{}] ([{}]) ([{}] charges)", spell_id, inst->GetItem()->Name, inst->GetItem()->ID, inst->GetItem()->MaxCharges); + LogSpells("Spell [{}]: Cast from unlimited charge item [{}] ([{}]) ([{}] charges)", spell_id, item->GetItem()->Name, item->GetItem()->ID, item->GetItem()->MaxCharges); } } else @@ -3449,9 +3455,9 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r // 1 = PCs, 2 = NPCs if (spells[spell_id].pcnpc_only_flag && spells[spell_id].targettype != ST_AETargetHateList && spells[spell_id].targettype != ST_HateList) { - if (spells[spell_id].pcnpc_only_flag == 1 && !spelltar->IsClient() && !spelltar->IsMerc()) + if (spells[spell_id].pcnpc_only_flag == 1 && !spelltar->IsClient() && !spelltar->IsMerc() && !spelltar->IsBot()) return false; - else if (spells[spell_id].pcnpc_only_flag == 2 && (spelltar->IsClient() || spelltar->IsMerc())) + else if (spells[spell_id].pcnpc_only_flag == 2 && (spelltar->IsClient() || spelltar->IsMerc() || spelltar->IsBot())) return false; } @@ -4793,24 +4799,32 @@ int16 Mob::CalcFearResistChance() return resistchance; } -float Mob::GetAOERange(uint16 spell_id) { - float range; +/** + * @param spell_id + * @return + */ +float Mob::GetAOERange(uint16 spell_id) +{ + float range = spells[spell_id].aoerange; - range = spells[spell_id].aoerange; - if(range == 0) //for TGB spells, they prolly do not have an aoe range + /** + * For TGB + */ + if (range == 0) { range = spells[spell_id].range; - if(range == 0) - range = 10; //something.... - - if(IsBardSong(spell_id) && IsBeneficialSpell(spell_id)) { - //Live AA - Extended Notes, SionachiesCrescendo - float song_bonus = static_cast(aabonuses.SongRange + spellbonuses.SongRange + itembonuses.SongRange); - range += range*song_bonus /100.0f; } - range = GetActSpellRange(spell_id, range); + if (range == 0) { + range = 10; + } - return(range); + if (IsBardSong(spell_id) && IsBeneficialSpell(spell_id)) { + //Live AA - Extended Notes, SionachiesCrescendo + float song_bonus = static_cast(aabonuses.SongRange + spellbonuses.SongRange + itembonuses.SongRange); + range += range * song_bonus / 100.0f; + } + + return GetActSpellRange(spell_id, range); } /////////////////////////////////////////////////////////////////////////////// diff --git a/zone/string_ids.h b/zone/string_ids.h index acab3df22..e543007a0 100644 --- a/zone/string_ids.h +++ b/zone/string_ids.h @@ -65,6 +65,7 @@ #define SILENCED_STRING 207 //You *CANNOT* cast spells, you have been silenced! #define CANNOT_AFFECT_PC 210 //That spell can not affect this target PC. #define SPELL_NEED_TAR 214 //You must first select a target for this spell! +#define SUMMON_ONLY_GROUP_CORPSE 215 //You must first target a living group member whose corpse you wish to summon. #define ONLY_ON_CORPSES 221 //This spell only works on corpses. #define CANT_DRAIN_SELF 224 //You can't drain yourself! #define CORPSE_NOT_VALID 230 //This corpse is not valid. @@ -121,11 +122,13 @@ #define LOOT_LORE_ERROR 371 //You cannot loot this Lore Item. You already have one. #define PICK_LORE 379 //You cannot pick up a lore item you already possess. #define POISON_TOO_HIGH 382 // This poison is too high level for you to apply. +#define CORPSE_TOO_FAR 389 //The corpse is too far away to summon. #define CONSENT_DENIED 390 //You do not have consent to summon that corpse. #define DISCIPLINE_RDY 393 //You are ready to use a new discipline now. #define CONSENT_INVALID_NAME 397 //Not a valid consent name. #define CONSENT_NPC 398 //You cannot consent NPC\'s. #define CONSENT_YOURSELF 399 //You cannot consent yourself. +#define CONSENT_WAIT 400 //You must wait 2 seconds between consents. #define SONG_NEEDS_DRUM 405 //You need to play a percussion instrument for this song #define SONG_NEEDS_WIND 406 //You need to play a wind instrument for this song #define SONG_NEEDS_STRINGS 407 //You need to play a stringed instrument for this song @@ -169,6 +172,7 @@ #define PVP_ON 552 //You are now player kill and follow the ways of Discord. #define GENERIC_STRINGID_SAY 554 //%1 says '%T2' #define CANNOT_WAKE 555 //%1 tells you, 'I am unable to wake %2, master.' +#define SUMMONING_CORPSE_ZONE 596 //Summoning %1's corpse(s). #define PET_HOLD_SET_ON 698 //The pet hold mode has been set to on. #define PET_HOLD_SET_OFF 699 //The pet hold mode has been set to off. #define PET_FOCUS_SET_ON 700 //The pet focus mode has been set to on. @@ -415,6 +419,7 @@ #define TARGET_PLAYER_FOR_GUILD_STATUS 12260 #define GROUP_INVITEE_NOT_FOUND 12268 //You must target a player or use /invite to invite someone to your group. #define GROUP_INVITEE_SELF 12270 //12270 You cannot invite yourself. +#define ALREADY_IN_PARTY 12272 //That person is already in your party. #define NO_LONGER_HIDDEN 12337 //You are no longer hidden. #define STOP_SNEAKING 12338 //You stop sneaking #define NOT_IN_CONTROL 12368 //You do not have control of yourself right now. diff --git a/zone/tradeskills.cpp b/zone/tradeskills.cpp index 5113ad4b6..b4451119f 100644 --- a/zone/tradeskills.cpp +++ b/zone/tradeskills.cpp @@ -392,6 +392,15 @@ void Object::HandleCombine(Client* user, const NewCombine_Struct* in_combine, Ob } } + // final check for any additional quest requirements .. "check_zone" in this case - exported as variable [validate_type] + if (parse->EventPlayer(EVENT_COMBINE_VALIDATE, user, fmt::format("check_zone {}", zone->GetZoneID()), spec.recipe_id) != 0) { + user->Message(Chat::Emote, "You cannot make this combine because the location requirement has not been met."); + auto outapp = new EQApplicationPacket(OP_TradeSkillCombine, 0); + user->QueuePacket(outapp); + safe_delete(outapp); + return; + } + // Send acknowledgement packets to client auto outapp = new EQApplicationPacket(OP_TradeSkillCombine, 0); user->QueuePacket(outapp); diff --git a/zone/water_map.cpp b/zone/water_map.cpp index d2e6daad1..ec5575f24 100644 --- a/zone/water_map.cpp +++ b/zone/water_map.cpp @@ -10,10 +10,34 @@ #include #include +/** + * @param name + * @return + */ +inline bool file_exists(const std::string& name) { + std::ifstream f(name.c_str()); + return f.good(); +} + +/** + * @param zone_name + * @return + */ WaterMap* WaterMap::LoadWaterMapfile(std::string zone_name) { std::transform(zone_name.begin(), zone_name.end(), zone_name.begin(), ::tolower); - - std::string file_path = Config->MapDir + "water/" + zone_name + std::string(".wtr"); + + std::string filename; + if (file_exists("maps")) { + filename = "maps"; + } + else if (file_exists("Maps")) { + filename = "Maps"; + } + else { + filename = Config->MapDir; + } + + std::string file_path = filename + "/water/" + zone_name + std::string(".wtr"); LogDebug("Attempting to load water map with path [{}]", file_path.c_str()); FILE *f = fopen(file_path.c_str(), "rb"); if(f) { diff --git a/zone/waypoints.cpp b/zone/waypoints.cpp index 10e4a11f3..65f4fd099 100644 --- a/zone/waypoints.cpp +++ b/zone/waypoints.cpp @@ -159,7 +159,7 @@ void NPC::PauseWandering(int pausetime) if (GetGrid() != 0) { moving = false; DistractedFromGrid = true; - LogPathing("Paused Wandering requested. Grid [{}]. Resuming in [{}] ms (0=not until told)", GetGrid(), pausetime); + LogPathing("Paused Wandering requested. Grid [{}]. Resuming in [{}] seconds (0=not until told)", GetGrid(), pausetime); StopNavigation(); if (pausetime < 1) { // negative grid number stops him dead in his tracks until ResumeWandering() SetGrid(0 - GetGrid()); @@ -244,14 +244,14 @@ void NPC::CalculateNewWaypoint() int old_wp = cur_wp; bool reached_end = false; bool reached_beginning = false; - if (cur_wp == max_wp) + if (cur_wp == max_wp - 1) //cur_wp starts at 0, max_wp starts at 1. reached_end = true; if (cur_wp == 0) reached_beginning = true; switch (wandertype) { - case 0: //circle + case GridCircular: { if (reached_end) cur_wp = 0; @@ -259,10 +259,10 @@ void NPC::CalculateNewWaypoint() cur_wp = cur_wp + 1; break; } - case 1: //10 closest + case GridRandom10: { std::list closest; - GetClosestWaypoint(closest, 10, glm::vec3(GetPosition())); + GetClosestWaypoints(closest, 10, glm::vec3(GetPosition())); auto iter = closest.begin(); if (closest.size() != 0) { @@ -273,30 +273,64 @@ void NPC::CalculateNewWaypoint() break; } - case 2: //random + case GridRandom: + case GridCenterPoint: { - cur_wp = zone->random.Int(0, Waypoints.size() - 1); - if (cur_wp == old_wp) + if (wandertype == GridCenterPoint && !reached_beginning) { - if (cur_wp == (Waypoints.size() - 1)) + cur_wp = 0; + } + else + { + cur_wp = zone->random.Int(0, Waypoints.size() - 1); + if (cur_wp == old_wp || (wandertype == GridCenterPoint && cur_wp == 0)) { - if (cur_wp > 0) + if (cur_wp == (Waypoints.size() - 1)) { - cur_wp--; + if (cur_wp > 0) + { + cur_wp--; + } } - } - else if (cur_wp == 0) - { - if ((Waypoints.size() - 1) > 0) + else if (cur_wp == 0) { - cur_wp++; + if ((Waypoints.size() - 1) > 0) + { + cur_wp++; + } } } } break; } - case 3: //patrol + case GridRandomCenterPoint: + { + bool on_center = Waypoints[cur_wp].centerpoint; + std::vector random_waypoints; + for (auto &w : Waypoints) + { + wplist wpl = w; + if (wpl.index != cur_wp && + ((on_center && !wpl.centerpoint) || (!on_center && wpl.centerpoint))) + { + random_waypoints.push_back(w); + } + } + + if (random_waypoints.size() == 0) + { + cur_wp = 0; + } + else + { + int windex = zone->random.Roll0(random_waypoints.size()); + cur_wp = random_waypoints[windex].index; + } + + break; + } + case GridPatrol: { if (reached_end) patrol = 1; @@ -309,16 +343,16 @@ void NPC::CalculateNewWaypoint() break; } - case 4: //goto the end and depop with spawn timer - case 6: //goto the end and depop without spawn timer + case GridOneWayRepop: + case GridOneWayDepop: { cur_wp = cur_wp + 1; break; } - case 5: //pick random closest 5 and pick one that's in sight + case GridRand5LoS: { std::list closest; - GetClosestWaypoint(closest, 5, glm::vec3(GetPosition())); + GetClosestWaypoints(closest, 5, glm::vec3(GetPosition())); auto iter = closest.begin(); while (iter != closest.end()) @@ -341,6 +375,25 @@ void NPC::CalculateNewWaypoint() } break; } + case GridRandomPath: // randomly select a waypoint but follow path to it instead of walk directly to it ignoring walls + { + if (Waypoints.size() == 0) + { + cur_wp = 0; + } + else + { + if (cur_wp == patrol) // reutilizing patrol member instead of making new member for this wander type; here we use it to save a random waypoint + { + while (patrol == cur_wp) + patrol = zone->random.Int(0, Waypoints.size() - 1); + } + if (patrol > cur_wp) + cur_wp = cur_wp + 1; + else + cur_wp = cur_wp - 1; + } + } } // Preserve waypoint setting for quest controlled NPCs @@ -357,7 +410,30 @@ bool wp_distance_pred(const wp_distance& left, const wp_distance& right) return left.dist < right.dist; } -void NPC::GetClosestWaypoint(std::list &wp_list, int count, const glm::vec3& location) +int NPC::GetClosestWaypoint(const glm::vec3& location) +{ + if (Waypoints.size() <= 1) + return 0; + + int closest = 0; + float closestDist = 9999999.0f; + float dist; + + for (int i = 0; i < Waypoints.size(); ++i) + { + dist = DistanceSquared(location, glm::vec3(Waypoints[i].x, Waypoints[i].y, Waypoints[i].z)); + + if (dist < closestDist) + { + closestDist = dist; + closest = i; + } + } + return closest; +} + +// fills wp_list with the closest count number of waypoints +void NPC::GetClosestWaypoints(std::list &wp_list, int count, const glm::vec3& location) { wp_list.clear(); if (Waypoints.size() <= count) @@ -485,7 +561,7 @@ void Mob::StopNavigation() { mMovementManager->StopNavigation(this); } -void NPC::AssignWaypoints(int32 grid) +void NPC::AssignWaypoints(int32 grid, int start_wp) { if (grid == 0) return; // grid ID 0 not supported @@ -518,7 +594,7 @@ void NPC::AssignWaypoints(int32 grid) SetGrid(grid); // Assign grid number // Retrieve all waypoints for this grid - query = StringFormat("SELECT `x`,`y`,`z`,`pause`,`heading` " + query = StringFormat("SELECT `x`,`y`,`z`,`pause`,`heading`, `centerpoint` " "FROM grid_entries WHERE `gridid` = %i AND `zoneid` = %i " "ORDER BY `number`", grid, zone->GetZoneID()); results = database.QueryDatabase(query); @@ -539,14 +615,22 @@ void NPC::AssignWaypoints(int32 grid) newwp.pause = atoi(row[3]); newwp.heading = atof(row[4]); + newwp.centerpoint = atobool(row[5]); Waypoints.push_back(newwp); } - UpdateWaypoint(0); + cur_wp = start_wp; + UpdateWaypoint(start_wp); SetWaypointPause(); - if (wandertype == 1 || wandertype == 2 || wandertype == 5) + if (wandertype == GridRandomPath) { + cur_wp = GetClosestWaypoint(glm::vec3(GetPosition())); + patrol = cur_wp; + } + + if (wandertype == GridRandom10 || wandertype == GridRandom || wandertype == GridRand5LoS) CalculateNewWaypoint(); + } void Mob::SendTo(float new_x, float new_y, float new_z) { @@ -687,7 +771,7 @@ void Mob::FixZ(int32 z_find_offset /*= 5*/, bool fix_client_z /*= false*/) { float Mob::GetZOffset() const { float offset = 3.125f; - switch (race) { + switch (GetModel()) { case RACE_BASILISK_436: offset = 0.577f; break; @@ -1058,6 +1142,37 @@ int ZoneDatabase::GetHighestWaypoint(uint32 zoneid, uint32 gridid) { return atoi(row[0]); } +int ZoneDatabase::GetRandomWaypointLocFromGrid(glm::vec4 &loc, uint16 zoneid, int grid) +{ + loc.x = loc.y = loc.z = loc.w = 0.0f; + + std::string query = StringFormat("SELECT `x`,`y`,`z`,`heading` " + "FROM grid_entries WHERE `gridid` = %i AND `zoneid` = %u ORDER BY `number`", grid, zone->GetZoneID()); + auto results = database.QueryDatabase(query); + if (!results.Success()) { + Log(Logs::General, Logs::Error, "MySQL Error while trying get random waypoint loc from grid %i in zoneid %u; %s", grid, zoneid, results.ErrorMessage().c_str()); + return 0; + } + + if (results.RowCount() > 0) + { + int roll = zone->random.Int(0, results.RowCount() - 1); + int i = 0; + auto row = results.begin(); + while (i < roll) + { + row++; + i++; + } + loc.x = atof(row[0]); + loc.y = atof(row[1]); + loc.z = atof(row[2]); + loc.w = atof(row[3]); + return i; + } + return 0; +} + void NPC::SaveGuardSpotCharm() { m_GuardPointSaved = m_GuardPoint; diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index 4f7e35ace..56f99067c 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -1442,50 +1442,65 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) } case ServerOP_Consent: { ServerOP_Consent_Struct* s = (ServerOP_Consent_Struct*)pack->pBuffer; - Client* client = entity_list.GetClientByName(s->grantname); - if (client) { - if (s->permission == 1) - client->consent_list.push_back(s->ownername); - else - client->consent_list.remove(s->ownername); - auto outapp = - new EQApplicationPacket(OP_ConsentResponse, sizeof(ConsentResponse_Struct)); - ConsentResponse_Struct* crs = (ConsentResponse_Struct*)outapp->pBuffer; - strcpy(crs->grantname, s->grantname); - strcpy(crs->ownername, s->ownername); - crs->permission = s->permission; - strcpy(crs->zonename, "all zones"); - client->QueuePacket(outapp); - safe_delete(outapp); + bool found_corpse = false; + for (auto const& it : entity_list.GetCorpseList()) { + if (it.second->IsPlayerCorpse() && strcmp(it.second->GetOwnerName(), s->ownername) == 0) { + if (s->consent_type == EQEmu::consent::Normal) { + if (s->permission == 1) { + it.second->AddConsentName(s->grantname); + } + else { + it.second->RemoveConsentName(s->grantname); + } + } + else if (s->consent_type == EQEmu::consent::Group) { + it.second->SetConsentGroupID(s->consent_id); + } + else if (s->consent_type == EQEmu::consent::Raid) { + it.second->SetConsentRaidID(s->consent_id); + } + else if (s->consent_type == EQEmu::consent::Guild) { + it.second->SetConsentGuildID(s->consent_id); + } + found_corpse = true; + } } - else { - // target not found - // Message string id's likely to be used here are: - // CONSENT_YOURSELF = 399 - // CONSENT_INVALID_NAME = 397 - // TARGET_NOT_FOUND = 101 - - auto scs_pack = - new ServerPacket(ServerOP_Consent_Response, sizeof(ServerOP_Consent_Struct)); - ServerOP_Consent_Struct* scs = (ServerOP_Consent_Struct*)scs_pack->pBuffer; - strcpy(scs->grantname, s->grantname); - strcpy(scs->ownername, s->ownername); - scs->permission = s->permission; - scs->zone_id = s->zone_id; - scs->instance_id = s->instance_id; - scs->message_string_id = TARGET_NOT_FOUND; - worldserver.SendPacket(scs_pack); - safe_delete(scs_pack); + if (found_corpse) { + // forward the grant/deny message for this zone to both owner and granted + auto outapp = new ServerPacket(ServerOP_Consent_Response, sizeof(ServerOP_Consent_Struct)); + ServerOP_Consent_Struct* scs = (ServerOP_Consent_Struct*)outapp->pBuffer; + memcpy(outapp->pBuffer, s, sizeof(ServerOP_Consent_Struct)); + if (zone) { + strn0cpy(scs->zonename, zone->GetLongName(), sizeof(scs->zonename)); + } + worldserver.SendPacket(outapp); + safe_delete(outapp); } break; } case ServerOP_Consent_Response: { ServerOP_Consent_Struct* s = (ServerOP_Consent_Struct*)pack->pBuffer; - Client* client = entity_list.GetClientByName(s->ownername); - if (client) { - client->MessageString(Chat::White, s->message_string_id); + Client* owner_client = entity_list.GetClientByName(s->ownername); + Client* grant_client = nullptr; + if (s->consent_type == EQEmu::consent::Normal) { + grant_client = entity_list.GetClientByName(s->grantname); + } + if (owner_client || grant_client) { + auto outapp = new EQApplicationPacket(OP_ConsentResponse, sizeof(ConsentResponse_Struct)); + ConsentResponse_Struct* crs = (ConsentResponse_Struct*)outapp->pBuffer; + strn0cpy(crs->grantname, s->grantname, sizeof(crs->grantname)); + strn0cpy(crs->ownername, s->ownername, sizeof(crs->ownername)); + crs->permission = s->permission; + strn0cpy(crs->zonename, s->zonename, sizeof(crs->zonename)); + if (owner_client) { + owner_client->QueuePacket(outapp); // confirmation message to the owner + } + if (grant_client) { + grant_client->QueuePacket(outapp); // message to the client being granted/denied + } + safe_delete(outapp); } break; } @@ -1806,8 +1821,8 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) worldserver.SendEmoteMessage( 0, 0, 100, 15, "Rules reloaded for Zone: '%s' Instance ID: %u", - zone->GetLongName(), - zone->GetInstanceID() + (zone ? zone->GetLongName() : StringFormat("Null zone pointer [pid]:[%i]", getpid()).c_str()), + (zone ? zone->GetInstanceID() : 0xFFFFFFFFF) ); RuleManager::Instance()->LoadRules(&database, RuleManager::Instance()->GetActiveRuleset(), true); break; diff --git a/zone/zone.cpp b/zone/zone.cpp index 99c120cab..b88ddd071 100755 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -1997,7 +1997,6 @@ const char *Zone::GetSpellBlockedMessage(uint32 spell_id, const glm::vec3 &locat if (spell_id != blocked_spells[x].spellid && blocked_spells[x].spellid != 0) { continue; } - switch (blocked_spells[x].type) { case ZoneBlockedSpellTypes::ZoneWide: { return blocked_spells[x].message; @@ -2033,21 +2032,21 @@ void Zone::SetInstanceTimer(uint32 new_duration) void Zone::LoadLDoNTraps() { - const std::string query = "SELECT id, type, spell_id, skill, locked FROM ldon_trap_templates"; - auto results = database.QueryDatabase(query); - if (!results.Success()) { + const std::string query = "SELECT id, type, spell_id, skill, locked FROM ldon_trap_templates"; + auto results = database.QueryDatabase(query); + if (!results.Success()) { return; - } + } - for (auto row = results.begin();row != results.end(); ++row) { - auto lt = new LDoNTrapTemplate; - lt->id = atoi(row[0]); - lt->type = (LDoNChestTypes)atoi(row[1]); - lt->spell_id = atoi(row[2]); - lt->skill = atoi(row[3]); - lt->locked = atoi(row[4]); - ldon_trap_list[lt->id] = lt; - } + for (auto row = results.begin(); row != results.end(); ++row) { + auto lt = new LDoNTrapTemplate; + lt->id = atoi(row[0]); + lt->type = (LDoNChestTypes) atoi(row[1]); + lt->spell_id = atoi(row[2]); + lt->skill = atoi(row[3]); + lt->locked = atoi(row[4]); + ldon_trap_list[lt->id] = lt; + } } diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index dc2e29cad..ea4287f9d 100755 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -4288,10 +4288,10 @@ uint32 ZoneDatabase::GetCharacterCorpseDecayTimer(uint32 corpse_db_id){ 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 glm::vec4& position, bool is_rezzed) { +uint32 ZoneDatabase::UpdateCharacterCorpse(uint32 db_id, uint32 char_id, const char* char_name, uint32 zone_id, uint16 instance_id, PlayerCorpse_Struct* dbpc, const glm::vec4& position, uint32 guild_id, 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, " + "`x` = %1.1f,`y` = %1.1f,`z` = %1.1f, `heading` = %1.1f, `guild_consent_id` = %u, " "`is_locked` = %d, `exp` = %u, `size` = %f, `level` = %u, " "`race` = %u, `gender` = %u, `class` = %u, `deity` = %u, " "`texture` = %u, `helm_texture` = %u, `copper` = %u, " @@ -4303,7 +4303,7 @@ uint32 ZoneDatabase::UpdateCharacterCorpse(uint32 db_id, uint32 char_id, const c "`wc_7` = %u, `wc_8` = %u, `wc_9` = %u " "WHERE `id` = %u", EscapeString(char_name).c_str(), zone_id, instance_id, char_id, - position.x, position.y, position.z, position.w, + position.x, position.y, position.z, position.w, guild_id, 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, @@ -4319,12 +4319,19 @@ uint32 ZoneDatabase::UpdateCharacterCorpse(uint32 db_id, uint32 char_id, const c return db_id; } +uint32 ZoneDatabase::UpdateCharacterCorpseConsent(uint32 charid, uint32 guildid) +{ + std::string query = fmt::format("UPDATE `character_corpses` SET `guild_consent_id` = '{}' WHERE charid = '{}'", guildid, charid); + auto results = QueryDatabase(query); + return results.RowsAffected(); +} + 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 glm::vec4& position) { +uint32 ZoneDatabase::SaveCharacterCorpse(uint32 charid, const char* charname, uint32 zoneid, uint16 instanceid, PlayerCorpse_Struct* dbpc, const glm::vec4& position, uint32 guildid) { /* Dump Basic Corpse Data */ std::string query = StringFormat( "INSERT INTO `character_corpses` " @@ -4336,6 +4343,7 @@ uint32 ZoneDatabase::SaveCharacterCorpse(uint32 charid, const char* charname, ui "`y` = %1.1f, " "`z` = %1.1f, " "`heading` = %1.1f, " + "`guild_consent_id` = %u, " "`time_of_death` = NOW(), " "`is_buried` = 0, " "`is_locked` = %d, " @@ -4379,6 +4387,7 @@ uint32 ZoneDatabase::SaveCharacterCorpse(uint32 charid, const char* charname, ui position.y, position.z, position.w, + guildid, dbpc->locked, dbpc->exp, dbpc->size, @@ -4637,7 +4646,7 @@ bool ZoneDatabase::LoadCharacterCorpseData(uint32 corpse_id, PlayerCorpse_Struct Corpse* ZoneDatabase::SummonBuriedCharacterCorpses(uint32 char_id, uint32 dest_zone_id, uint16 dest_instance_id, const glm::vec4& position) { Corpse* corpse = nullptr; - std::string query = StringFormat("SELECT `id`, `charname`, `time_of_death`, `is_rezzed` " + std::string query = StringFormat("SELECT `id`, `charname`, `time_of_death`, `is_rezzed`, `guild_consent_id` " "FROM `character_corpses` " "WHERE `charid` = '%u' AND `is_buried` = 1 " "ORDER BY `time_of_death` LIMIT 1", @@ -4652,7 +4661,8 @@ Corpse* ZoneDatabase::SummonBuriedCharacterCorpses(uint32 char_id, uint32 dest_z position, row[2], // char* time_of_death atoi(row[3]) == 1, // bool rezzed - false // bool was_at_graveyard + false, // bool was_at_graveyard + atoul(row[4]) // uint32 guild_consent_id ); if (!corpse) continue; @@ -4678,7 +4688,7 @@ bool ZoneDatabase::SummonAllCharacterCorpses(uint32 char_id, uint32 dest_zone_id auto results = QueryDatabase(query); query = StringFormat( - "SELECT `id`, `charname`, `time_of_death`, `is_rezzed` FROM `character_corpses` WHERE `charid` = '%u'" + "SELECT `id`, `charname`, `time_of_death`, `is_rezzed`, `guild_consent_id` FROM `character_corpses` WHERE `charid` = '%u'" "ORDER BY time_of_death", char_id); results = QueryDatabase(query); @@ -4691,7 +4701,8 @@ bool ZoneDatabase::SummonAllCharacterCorpses(uint32 char_id, uint32 dest_zone_id position, row[2], atoi(row[3]) == 1, - false); + false, + atoul(row[4])); if (corpse) { entity_list.AddCorpse(corpse); @@ -4707,6 +4718,47 @@ bool ZoneDatabase::SummonAllCharacterCorpses(uint32 char_id, uint32 dest_zone_id return (CorpseCount > 0); } +int ZoneDatabase::CountCharacterCorpses(uint32 char_id) { + std::string query = fmt::format( + SQL( + SELECT + COUNT(*) + FROM + character_corpses + WHERE + charid = '{}' + ), + char_id + ); + auto results = QueryDatabase(query); + for (auto row = results.begin(); row != results.end(); ++row) { + return atoi(row[0]); + } + return 0; +} + +int ZoneDatabase::CountCharacterCorpsesByZoneID(uint32 char_id, uint32 zone_id) { + std::string query = fmt::format( + SQL( + SELECT + COUNT(*) + FROM + character_corpses + WHERE + charid = '{}' + AND + zone_id = '{}' + ), + char_id, + zone_id + ); + auto results = QueryDatabase(query); + for (auto row = results.begin(); row != results.end(); ++row) { + return atoi(row[0]); + } + return 0; +} + bool ZoneDatabase::UnburyCharacterCorpse(uint32 db_id, uint32 new_zone_id, uint16 new_instance_id, const glm::vec4& position) { std::string query = StringFormat("UPDATE `character_corpses` " "SET `is_buried` = 0, `zone_id` = %u, `instance_id` = %u, " @@ -4725,7 +4777,7 @@ bool ZoneDatabase::UnburyCharacterCorpse(uint32 db_id, uint32 new_zone_id, uint1 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", + "SELECT `id`, `charid`, `charname`, `x`, `y`, `z`, `heading`, `time_of_death`, `is_rezzed`, `was_at_graveyard`, `guild_consent_id` FROM `character_corpses` WHERE `id` = '%u' LIMIT 1", player_corpse_id ); auto results = QueryDatabase(query); @@ -4738,7 +4790,8 @@ Corpse* ZoneDatabase::LoadCharacterCorpse(uint32 player_corpse_id) { 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 + atoi(row[9]), // was_at_graveyard bool was_at_graveyard + atoul(row[10]) // guild_consent_id uint32 guild_consent_id ); entity_list.AddCorpse(NewCorpse); } @@ -4748,10 +4801,10 @@ Corpse* ZoneDatabase::LoadCharacterCorpse(uint32 player_corpse_id) { 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); + query = StringFormat("SELECT id, charid, charname, x, y, z, heading, time_of_death, is_rezzed, was_at_graveyard, guild_consent_id 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); + query = StringFormat("SELECT id, charid, charname, x, y, z, heading, time_of_death, is_rezzed, 0 as was_at_graveyard, guild_consent_id FROM character_corpses WHERE zone_id='%u' AND instance_id='%u' AND is_buried=0", zone_id, instance_id); } auto results = QueryDatabase(query); @@ -4765,7 +4818,8 @@ bool ZoneDatabase::LoadCharacterCorpses(uint32 zone_id, uint16 instance_id) { position, row[7], // time_of_death char* time_of_death atoi(row[8]) == 1, // is_rezzed bool rezzed - atoi(row[9])) + atoi(row[9]), + atoul(row[10])) // guild_consent_id uint32 guild_consent_id ); } diff --git a/zone/zonedb.h b/zone/zonedb.h index e93a40713..b1d88539d 100644 --- a/zone/zonedb.h +++ b/zone/zonedb.h @@ -47,6 +47,7 @@ struct wplist { float z; int pause; float heading; + bool centerpoint; }; #pragma pack(1) @@ -356,6 +357,8 @@ public: bool DeleteCharacterCorpse(uint32 dbid); bool SummonAllCharacterCorpses(uint32 char_id, uint32 dest_zoneid, uint16 dest_instanceid, const glm::vec4& position); bool SummonAllGraveyardCorpses(uint32 cur_zoneid, uint32 dest_zoneid, uint16 dest_instanceid, const glm::vec4& position); + int CountCharacterCorpses(uint32 char_id); + int CountCharacterCorpsesByZoneID(uint32 char_id, uint32 zone_id); bool UnburyCharacterCorpse(uint32 dbid, uint32 new_zoneid, uint16 dest_instanceid, const glm::vec4& position); bool LoadCharacterCorpses(uint32 iZoneID, uint16 iInstanceID); bool DeleteGraveyard(uint32 zone_id, uint32 graveyard_id); @@ -364,8 +367,9 @@ public: uint32 SendCharacterCorpseToGraveyard(uint32 dbid, uint32 zoneid, uint16 instanceid, const glm::vec4& position); uint32 CreateGraveyardRecord(uint32 graveyard_zoneid, const glm::vec4& position); uint32 AddGraveyardIDToZone(uint32 zone_id, uint32 graveyard_id); - uint32 SaveCharacterCorpse(uint32 charid, const char* charname, uint32 zoneid, uint16 instanceid, PlayerCorpse_Struct* dbpc, const glm::vec4& position); - uint32 UpdateCharacterCorpse(uint32 dbid, uint32 charid, const char* charname, uint32 zoneid, uint16 instanceid, PlayerCorpse_Struct* dbpc, const glm::vec4& position, bool rezzed = false); + uint32 SaveCharacterCorpse(uint32 charid, const char* charname, uint32 zoneid, uint16 instanceid, PlayerCorpse_Struct* dbpc, const glm::vec4& position, uint32 guildid); + uint32 UpdateCharacterCorpse(uint32 dbid, uint32 charid, const char* charname, uint32 zoneid, uint16 instanceid, PlayerCorpse_Struct* dbpc, const glm::vec4& position, uint32 guildid, bool rezzed = false); + uint32 UpdateCharacterCorpseConsent(uint32 charid, uint32 guildid); uint32 GetFirstCorpseID(uint32 char_id); uint32 GetCharacterCorpseCount(uint32 char_id); uint32 GetCharacterCorpseID(uint32 char_id, uint8 corpse); @@ -432,6 +436,7 @@ public: void AssignGrid(Client *client, int grid, int spawn2id); int GetHighestGrid(uint32 zoneid); int GetHighestWaypoint(uint32 zoneid, uint32 gridid); + int GetRandomWaypointLocFromGrid(glm::vec4 &loc, uint16 zoneid, int grid); /* NPCs */ diff --git a/zone/zonedump.h b/zone/zonedump.h index 302d4ee25..6010c9480 100644 --- a/zone/zonedump.h +++ b/zone/zonedump.h @@ -44,7 +44,7 @@ struct NPCType uint16 race; uint8 class_; uint8 bodytype; // added for targettype support - uint8 deity; //not loaded from DB + uint32 deity; //not loaded from DB uint8 level; uint32 npc_id; uint8 texture;