diff --git a/common/eqemu_config.cpp b/common/eqemu_config.cpp index 3516f15d1..9dcbe26af 100644 --- a/common/eqemu_config.cpp +++ b/common/eqemu_config.cpp @@ -27,7 +27,7 @@ std::string EQEmuConfig::ConfigFile = "eqemu_config.json"; EQEmuConfig *EQEmuConfig::_config = nullptr; void EQEmuConfig::parse_config() { - + ShortName = _root["server"]["world"].get("shortname", "").asString(); LongName = _root["server"]["world"].get("longname", "").asString(); WorldAddress = _root["server"]["world"].get("address", "").asString(); @@ -63,15 +63,15 @@ void EQEmuConfig::parse_config() { loginlist.Insert(loginconfig); } while (LoginCount < 100); } - - - // from xml converts to json as locked: "", so i default to "false". + + + // from xml converts to json as locked: "", so i default to "false". //The only way to enable locked is by switching to true, meaning this value is always false until manually set true Locked = false; if (_root["server"]["world"].get("locked", "false").asString() == "true") Locked = true; WorldIP = _root["server"]["world"]["tcp"].get("host", "127.0.0.1").asString(); WorldTCPPort = atoi(_root["server"]["world"]["tcp"].get("port", "9000").asString().c_str()); - + TelnetIP = _root["server"]["world"]["telnet"].get("ip", "127.0.0.1").asString(); TelnetTCPPort = atoi(_root["server"]["world"]["telnet"].get("port", "9001").asString().c_str()); TelnetEnabled = false; @@ -92,7 +92,7 @@ void EQEmuConfig::parse_config() { DatabasePassword = _root["server"]["database"].get("password", "eq").asString(); DatabaseHost = _root["server"]["database"].get("host", "localhost").asString(); DatabasePort = atoi(_root["server"]["database"].get("port", "3306").asString().c_str()); - DatabaseDB = _root["server"]["database"].get("db", "eq").asString(); + DatabaseDB = _root["server"]["database"].get("db", "eq").asString(); QSDatabaseHost = _root["server"]["qsdatabase"].get("host", "localhost").asString(); QSDatabasePort = atoi(_root["server"]["qsdatabase"].get("port", "3306").asString().c_str()); diff --git a/common/eqemu_config.h b/common/eqemu_config.h index 66cf962c6..e12be0370 100644 --- a/common/eqemu_config.h +++ b/common/eqemu_config.h @@ -122,7 +122,7 @@ class EQEmuConfig void parse_config(); EQEmuConfig() - { + { } virtual ~EQEmuConfig() {} @@ -166,7 +166,7 @@ class EQEmuConfig } catch (std::exception) { return false; - } + } return true; } diff --git a/common/ruletypes.h b/common/ruletypes.h index 24d9e809b..19d84fbba 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -400,6 +400,7 @@ RULE_BOOL(Spells, IgnoreSpellDmgLvlRestriction, false) // ignore the 5 level spr RULE_BOOL(Spells, AllowItemTGB, false) // TGB doesn't work with items on live, custom servers want it though RULE_BOOL(Spells, NPCInnateProcOverride, true) // NPC innate procs override the target type to single target. RULE_BOOL(Spells, OldRainTargets, false) // use old incorrectly implemented max targets for rains +RULE_BOOL(Spells, Oct182017AAResistOtherSPAFix, false) // they were not saving the base2 of AAs before this patch RULE_CATEGORY_END() RULE_CATEGORY(Combat) diff --git a/common/spdat.h b/common/spdat.h index 51ec8327b..1d62a8b11 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -215,6 +215,13 @@ typedef enum { DS_THORNS = 249 } DmgShieldType; +enum SuppressionMask { + SM_Buffs = 1, + SM_Items = 2, + SM_AAs = 4, + SM_All = 255 +}; + //Spell Effect IDs // https://forums.daybreakgames.com/eq/index.php?threads/enumerated-spa-list.206288/ // mirror: http://pastebin.com/MYeQqGwe @@ -534,7 +541,7 @@ typedef enum { #define SE_ForageAdditionalItems 313 // implemented[AA] - chance to forage additional items #define SE_Invisibility2 314 // implemented - fixed duration invisible #define SE_InvisVsUndead2 315 // implemented - fixed duration ITU -//#define SE_ImprovedInvisAnimals 316 // not used +#define SE_ImprovedInvisAnimals 316 // not used #define SE_ItemHPRegenCapIncrease 317 // implemented[AA] - increases amount of health regen gained via items #define SE_ItemManaRegenCapIncrease 318 // implemented - increases amount of mana regen you can gain via items #define SE_CriticalHealOverTime 319 // implemented diff --git a/utils/scripts/eqemu_server.pl b/utils/scripts/eqemu_server.pl index c70862aaa..8a5b2077e 100644 --- a/utils/scripts/eqemu_server.pl +++ b/utils/scripts/eqemu_server.pl @@ -15,7 +15,7 @@ use File::Copy qw(copy); use POSIX qw(strftime); use File::Path; use File::Find; -use Time::HiRes qw(usleep); +use Time::HiRes qw(usleep); #::: Variables $install_repository_request_url = "https://raw.githubusercontent.com/Akkadius/EQEmuInstall/master/"; @@ -24,7 +24,7 @@ $eqemu_repository_request_url = "https://raw.githubusercontent.com/EQEmu/Server/ #::: Globals $time_stamp = strftime('%m-%d-%Y', gmtime()); $db_run_stage = 0; #::: Sets database run stage check -if($Config{osname}=~/freebsd|linux/i){ +if($Config{osname}=~/freebsd|linux/i){ $OS = "Linux"; $os_flavor = ""; if(-e "/etc/debian_version"){ @@ -37,8 +37,8 @@ if($Config{osname}=~/freebsd|linux/i){ $os_flavor = "red_hat"; } } -if($Config{osname}=~/Win|MS/i){ - $OS = "Windows"; +if($Config{osname}=~/Win|MS/i){ + $OS = "Windows"; } $has_internet_connection = check_internet_connection(); ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(); @@ -65,11 +65,11 @@ get_mysql_path(); #::: Remove old eqemu_update.pl if(-e "eqemu_update.pl"){ unlink("eqemu_update.pl"); -} +} print "[Info] For EQEmu Server management utilities - run eqemu_server.pl\n" if $ARGV[0] eq "ran_from_world"; -#::: Check if db_version table exists... +#::: Check if db_version table exists... if(trim(get_mysql_result("SHOW COLUMNS FROM db_version LIKE 'Revision'")) ne "" && $db){ print get_mysql_result("DROP TABLE db_version"); print "[Database] Old db_version table present, dropping...\n\n"; @@ -94,12 +94,12 @@ sub urldecode { sub analytics_insertion { $event_name = urlencode($_[0]); $event_data = urlencode($_[1]); - + #::: Check for internet connection before doing analytics if(!$has_internet_connection || $can_see_analytics_server == -1){ return; } - + #::: Check for analytics server connectivity so that the script doesn't break when its offline if(!$can_see_analytics_server){ if($OS eq "Linux"){ @@ -108,7 +108,7 @@ sub analytics_insertion { if($OS eq "Windows"){ $count = "n"; } - + if (`ping analytics.akkadius.com -$count 1 -w 500`=~/Reply from|1 received/i) { $can_see_analytics_server = 1; } @@ -116,12 +116,12 @@ sub analytics_insertion { $can_see_analytics_server = -1; } } - + $server_name = ""; if($long_name){ - $server_name = "&server_name=" . urlencode($long_name); + $server_name = "&server_name=" . urlencode($long_name); } - + if(!$extended_os){ if($OS eq "Linux"){ $extended_os = `cat /proc/version`; @@ -137,7 +137,7 @@ sub analytics_insertion { } } } - + $url = "http://analytics.akkadius.com/"; $url .= "?api_key=24a0bde2e5bacd65bcab06a9ac40b62c"; $url .= "&event=" . $event_name; @@ -145,14 +145,14 @@ sub analytics_insertion { $url .= "&OS=" . urlencode($OS); $url .= "&extended_os=" . urlencode($extended_os); $url .= $server_name; - - # print "Calling url :: '" . $url . "'\n"; - + + # print "Calling url :: '" . $url . "'\n"; + if($OS eq "Windows"){ eval('require LWP::UserAgent;'); - my $ua = LWP::UserAgent->new; + my $ua = LWP::UserAgent->new; $ua->timeout(1); - $ua->env_proxy; + $ua->env_proxy; my $response = $ua->get($url); } if($OS eq "Linux"){ @@ -178,7 +178,7 @@ sub show_install_summary_info { print " - " . $data[0] . "\t" . $data[1] . "\n"; } close (INSTALL_VARS); - + if($OS eq "Windows"){ print "[Install] Windows Utility Scripts:\n"; print " - t_start_server.bat Starts EQEmu server with 30 dynamic zones, UCS & Queryserv, dynamic zones\n"; @@ -194,9 +194,9 @@ sub show_install_summary_info { print " - server_stop.sh Stops EQEmu Server (No warning)\n"; print " - server_status.sh Prints the status of the EQEmu Server processes\n"; } - + print "[Configure] eqemu_config.xml Edit to change server settings and name\n"; - + analytics_insertion("install_complete", "null"); } @@ -208,42 +208,42 @@ sub new_server { $file_count++; } closedir(DIR); - - if($file_count > 4 && (!-e "install_variables.txt" && !-e "../install_variables.txt")){ + + if($file_count > 4 && (!-e "install_variables.txt" && !-e "../install_variables.txt")){ print "[New Server] ERROR: You must run eqemu_server.pl in an empty directory\n"; <>; exit; } - if(-e "install_variables.txt" || -e "../install_variables.txt"){ + if(-e "install_variables.txt" || -e "../install_variables.txt"){ get_installation_variables(); } while(1){ - + $database_name = $installation_variables{"mysql_eqemu_db_name"}; $database_user = $installation_variables{"mysql_eqemu_user"}; $database_password = $installation_variables{"mysql_eqemu_password"}; - + if($database_name ne ""){ $mysql_pass = 1; } else { - + print "\n"; print "[New Server] For a new server folder install, we assume Perl and MySQL are configured\n"; print "[New Server] This will install a fresh PEQ Database, with all server assets\n"; print "[New Server] You will need to supply database credentials to get started...\n\n"; - - check_for_input("MySQL User: "); + + check_for_input("MySQL User: "); $database_user = trim($input); - check_for_input("MySQL Password: "); + check_for_input("MySQL Password: "); $database_password = trim($input); - + $check_connection = `mysql -u $database_user -p$database_password -N -B -e "SHOW PROCESSLIST" > mysqlcheck.txt`; $mysql_pass = 0; - open (MYSQL_CHECK, "mysqlcheck.txt"); + open (MYSQL_CHECK, "mysqlcheck.txt"); while (){ chomp; $o = $_; @@ -253,15 +253,15 @@ sub new_server { close (MYSQL_CHECK); unlink("mysqlcheck.txt"); } - + if($mysql_pass == 1){ - - if((!-e "install_variables.txt" && !-e "../install_variables.txt")){ + + if((!-e "install_variables.txt" && !-e "../install_variables.txt")){ print "[New Server] Success! We have a database connection\n"; - - check_for_input("Specify a NEW database name that PEQ will be installed to: "); + + check_for_input("Specify a NEW database name that PEQ will be installed to: "); $database_name = trim($input); - + #::: Write install vars open (INSTALL_VARS, '>', 'install_variables.txt'); print INSTALL_VARS ""; @@ -271,30 +271,30 @@ sub new_server { close (INSTALL_VARS); } analytics_insertion("new_server::install", $database_name); - + if($OS eq "Linux"){ build_linux_source("login"); } - + do_installer_routines(); - + if($OS eq "Linux"){ print `chmod 755 *.sh`; } - + analytics_insertion("new_server::install_complete", $database_name . " :: Binary DB Version / Local DB Version :: " . $binary_database_version . " / " . $local_database_version); - + print "[New Server] New server folder install complete\n"; print "[New Server] Below is your installation info:\n"; - + show_install_summary_info(); - + if($OS eq "Linux") { unlink('/home/eqemu/install_variables.txt'); } - + rmtree('updates_staged'); - + return; } else { @@ -333,16 +333,16 @@ sub check_xml_to_json_conversion { $json->canonical(1); print $json->pretty->indent_length(5)->utf8->encode($result),"\n"; - + open(my $fh, '>', 'eqemu_config.json'); print $fh $json->pretty->indent_length(5)->utf8->encode($result); close $fh; - + mkdir('backups'); copy_file("eqemu_config.xml", "backups/eqemu_config.xml"); unlink('eqemu_config.xml'); unlink('db_dumper.pl'); - + print "[Server Maintenance] eqemu_config.xml is now DEPRECATED \n"; print "[Server Maintenance] eqemu_config.json is now the new Server config format \n"; print " A backup of this old config is located in the backups folder of your server directory\n"; @@ -352,19 +352,19 @@ sub check_xml_to_json_conversion { print " --- \n"; print " Thanks for your understanding\n"; print " The EQEmulator Team\n\n"; - + exit; } - + } sub build_linux_source { - + $build_options = $_[0]; - + $cmake_options = ""; $source_folder_post_fix = ""; - + if($build_options =~/bots/i){ $cmake_options .= " -DEQEMU_ENABLE_BOTS=ON"; $source_folder_post_fix = "_bots"; @@ -379,11 +379,11 @@ sub build_linux_source { } my $eqemu_server_directory = "/home/eqemu"; my $source_dir = $eqemu_server_directory . '/' . $last_directory . '_source' . $source_folder_post_fix; - + $current_directory = trim($current_directory); - + mkdir($source_dir) if (!-e $source_dir); - + # print 'server_dir: ' . $eqemu_server_directory . "\n"; # print 'source_dir: ' . $source_dir . "\n"; # print 'current_dir: \'' . $current_directory . "'\n"; @@ -391,7 +391,7 @@ sub build_linux_source { chdir($source_dir); print `git clone https://github.com/EQEmu/Server.git`; - + mkdir ($source_dir . "/Server/build") if (!-e $source_dir . "/Server/build"); chdir ($source_dir . "/Server/build"); @@ -399,16 +399,16 @@ sub build_linux_source { if($os_flavor eq "fedora_core"){ print `cmake $cmake_options -DEQEMU_BUILD_LOGIN=ON -DEQEMU_BUILD_LUA=ON -DLUA_INCLUDE_DIR=/usr/include/lua-5.1/ -G "Unix Makefiles" ..`; } - else { + else { print `cmake $cmake_options -DEQEMU_BUILD_LOGIN=ON -DEQEMU_BUILD_LUA=ON -G "Unix Makefiles" ..`; } print "Building EQEmu Server code. This will take a while."; #::: Build print `make`; - + chdir ($current_directory); - + print `ln -s -f $source_dir/Server/build/bin/eqlaunch .`; print `ln -s -f $source_dir/Server/build/bin/export_client_files .`; print `ln -s -f $source_dir/Server/build/bin/import_client_files .`; @@ -424,21 +424,21 @@ sub build_linux_source { sub do_installer_routines { print "[Install] EQEmu Server Installer... LOADING... PLEASE WAIT...\n"; - + #::: Make some local server directories... mkdir('logs'); mkdir('updates_staged'); mkdir('shared'); - + do_install_config_json(); read_eqemu_config_json(); get_installation_variables(); - + $db_name = "peq"; if($installation_variables{"mysql_eqemu_db_name"}){ $db_name = $installation_variables{"mysql_eqemu_db_name"}; } - + #::: Download assets if($OS eq "Windows"){ fetch_latest_windows_binaries(); @@ -453,30 +453,30 @@ sub do_installer_routines { quest_files_fetch(); lua_modules_fetch(); fetch_utility_scripts(); - + #::: Database Routines print "[Database] Creating Database '" . $db_name . "'\n"; print `"$path" --host $host --user $user --password="$pass" -N -B -e "DROP DATABASE IF EXISTS $db_name;"`; print `"$path" --host $host --user $user --password="$pass" -N -B -e "CREATE DATABASE $db_name"`; - + #::: Get Binary DB version - if($OS eq "Windows"){ @db_version = split(': ', `world db_version`); } - if($OS eq "Linux"){ @db_version = split(': ', `./world db_version`); } + if($OS eq "Windows"){ @db_version = split(': ', `world db_version`); } + if($OS eq "Linux"){ @db_version = split(': ', `./world db_version`); } $binary_database_version = trim($db_version[1]); - + #::: Local DB Version check_db_version_table(); $local_database_version = trim(get_mysql_result("SELECT version FROM db_version LIMIT 1")); - + #::: Download PEQ latest fetch_peq_db_full(); print "[Database] Fetching Latest Database Updates...\n"; main_db_management(); print "[Database] Applying Latest Database Updates...\n"; main_db_management(); - + remove_duplicate_rule_values(); - + if($OS eq "Windows"){ check_windows_firewall_rules(); do_windows_login_server_setup(); @@ -493,12 +493,12 @@ sub check_for_input { } sub check_for_world_bootup_database_update { - if($OS eq "Windows"){ - @db_version = split(': ', `world db_version`); + if($OS eq "Windows"){ + @db_version = split(': ', `world db_version`); + } + if($OS eq "Linux"){ + @db_version = split(': ', `./world db_version`); } - if($OS eq "Linux"){ - @db_version = split(': ', `./world db_version`); - } $binary_database_version = trim($db_version[1]); $local_database_version = trim(get_mysql_result("SELECT version FROM db_version LIMIT 1")); @@ -515,21 +515,21 @@ sub check_for_world_bootup_database_update { database_dump_compress(); print "[Update] Updating bots database...\n"; sleep(1); - bots_db_management(); + bots_db_management(); run_database_check(); print "[Update] Continuing bootup\n"; analytics_insertion("auto database bots upgrade world", $db . " :: Binary DB Version / Local DB Version :: " . $binary_database_version . " / " . $local_database_version); - + exit; } else { print "[Update] Bots database up to Date: Continuing World Bootup...\n"; } } - - if($binary_database_version == $local_database_version && $ARGV[0] eq "ran_from_world"){ - print "[Update] Database up to date...\n"; - exit; + + if($binary_database_version == $local_database_version && $ARGV[0] eq "ran_from_world"){ + print "[Update] Database up to date...\n"; + exit; } else { #::: We ran world - Database needs to update, lets backup and run updates and continue world bootup @@ -543,7 +543,7 @@ sub check_for_world_bootup_database_update { main_db_management(); print "[Update] Continuing bootup\n"; analytics_insertion("auto database upgrade world", $db . " :: Binary DB Version / Local DB Version :: " . $binary_database_version . " / " . $local_database_version); - + exit; } @@ -556,18 +556,18 @@ sub check_for_world_bootup_database_update { } sub check_internet_connection { - if($OS eq "Linux"){ + if($OS eq "Linux"){ $count = "c"; } if($OS eq "Windows"){ $count = "n"; } - if (`ping 8.8.8.8 -$count 1 -w 500`=~/TTL|1 received/i) { + if (`ping 8.8.8.8 -$count 1 -w 500`=~/TTL|1 received/i) { # print "[Update] We have a connection to the internet, continuing...\n"; return 1; } - elsif (`ping 4.2.2.2 -$count 1 -w 500`=~/TTL|1 received/i) { + elsif (`ping 4.2.2.2 -$count 1 -w 500`=~/TTL|1 received/i) { # print "[Update] We have a connection to the internet, continuing...\n"; return 1; } @@ -582,8 +582,8 @@ sub get_perl_version { $perl_version = $^V; $perl_version =~s/v//g; print "[Update] Perl Version is " . $perl_version . "\n" if $debug; - if($perl_version > 5.12){ - no warnings 'uninitialized'; + if($perl_version > 5.12){ + no warnings 'uninitialized'; } no warnings; } @@ -596,37 +596,37 @@ sub get_windows_wget { } sub do_self_update_check_routine { - + #::: Check for internet connection before updating if(!$has_internet_connection){ print "[Update] Cannot check update without internet connection...\n"; return; } - + #::: Check for script changes :: eqemu_server.pl get_remote_file($eqemu_repository_request_url . "utils/scripts/eqemu_server.pl", "updates_staged/eqemu_server.pl", 0, 1, 1); - - if(-e "updates_staged/eqemu_server.pl") { - + + if(-e "updates_staged/eqemu_server.pl") { + my $remote_script_size = -s "updates_staged/eqemu_server.pl"; my $local_script_size = -s "eqemu_server.pl"; - + if($remote_script_size != $local_script_size){ print "[Update] Script has been updated, updating...\n"; - + my @files; my $start_dir = "updates_staged/"; - find( - sub { push @files, $File::Find::name unless -d; }, + find( + sub { push @files, $File::Find::name unless -d; }, $start_dir ); for my $file (@files) { - if($file=~/eqemu_server/i){ + if($file=~/eqemu_server/i){ $destination_file = $file; $destination_file =~s/updates_staged\///g; print "[Install] Installing :: " . $destination_file . "\n"; unlink($destination_file); - copy_file($file, $destination_file); + copy_file($file, $destination_file); if($OS eq "Linux"){ system("chmod 755 eqemu_server.pl"); } @@ -668,10 +668,10 @@ sub get_installation_variables{ sub do_install_config_json { get_installation_variables(); - + #::: Fetch json template get_remote_file($install_repository_request_url . "eqemu_config.json", "eqemu_config_template.json"); - + use JSON; my $json = new JSON(); @@ -683,33 +683,33 @@ sub do_install_config_json { close($fh); $config = $json->decode($content); - + $long_name = "Akkas " . $OS . " PEQ Installer (" . generate_random_password(5) . ')'; $config->{"server"}{"world"}{"longname"} = $long_name; $config->{"server"}{"world"}{"key"} = generate_random_password(30); - + if($installation_variables{"mysql_eqemu_db_name"}){ $db_name = $installation_variables{"mysql_eqemu_db_name"}; } else { $db_name = "peq"; } - + $config->{"server"}{"database"}{"username"} = $installation_variables{"mysql_eqemu_user"}; $config->{"server"}{"database"}{"password"} = $installation_variables{"mysql_eqemu_password"}; $config->{"server"}{"database"}{"db"} = $db_name; - + $config->{"server"}{"qsdatabase"}{"username"} = $installation_variables{"mysql_eqemu_user"}; $config->{"server"}{"qsdatabase"}{"password"} = $installation_variables{"mysql_eqemu_password"}; $config->{"server"}{"qsdatabase"}{"db"} = $db_name; - + $json->canonical(1); $json->indent_length(5); - + open(my $fh, '>', 'eqemu_config.json'); print $fh $json->pretty->indent_length(5)->utf8->encode($config); close $fh; - + unlink("eqemu_config_template.json"); } @@ -734,14 +734,14 @@ sub fetch_utility_scripts { sub setup_bots { if($OS eq "Windows"){ - fetch_latest_windows_binaries_bots(); + fetch_latest_windows_binaries_bots(); } if($OS eq "Linux"){ build_linux_source("bots"); } - bots_db_management(); + bots_db_management(); run_database_check(); - + print "Bots should be setup, run your server and the #bot command should be available in-game\n"; } @@ -749,20 +749,20 @@ sub show_menu_prompt { $dc = 0; while (1) { - + if($ARGV[0] ne ""){ $input = trim($ARGV[0]); } else { $input = trim($input); } - + $errored_command = 0; - + if($input eq "database"){ print "\n>>> Database Menu\n\n"; print " [backup_database] Back up database to backups/ directory\n"; - print " [backup_player_tables] Back up player tables to backups/ directory\n"; + print " [backup_player_tables] Back up player tables to backups/ directory\n"; print " [backup_database_compressed] Back up database compressed to backups/ directory\n"; print " \n"; print " [check_db_updates] Checks for database updates manually\n"; @@ -771,7 +771,7 @@ sub show_menu_prompt { print " [aa_tables] Downloads and installs clean slate AA data from PEQ\n"; print " [remove_duplicate_rules] Removes duplicate rules from rule_values table\n"; print " [drop_bots_db_schema] Removes bot database schema\n"; - + print " \n> main - go back to main menu\n"; print "Enter a command #> "; $last_menu = trim($input); @@ -793,7 +793,7 @@ sub show_menu_prompt { print " [setup_loginserver] Sets up loginserver for Windows\n"; } print " \n> main - go back to main menu\n"; - print "Enter a command #> "; + print "Enter a command #> "; $last_menu = trim($input); } elsif($input eq "backup_database"){ database_dump(); $dc = 1; } @@ -836,20 +836,20 @@ sub show_menu_prompt { else { print_main_menu(); } - + #::: Errored command checking if($errored_command == 1){ $input = $last_menu; } elsif($dc == 1){ analytics_insertion("menu", trim($input)); - $dc = 0; + $dc = 0; $input = ""; } else { $input = <>; } - + #::: If we're processing a CLI command, kill the loop if($ARGV[0] ne ""){ analytics_insertion("cli", trim($input)); @@ -857,7 +857,7 @@ sub show_menu_prompt { $ARGV[0] = ""; exit; } - } + } } sub print_main_menu { @@ -870,18 +870,18 @@ sub print_main_menu { print " [setup_bots] Enables bots on server - builds code and database requirements \n"; print "\n"; print " exit \n"; - print "\n"; + print "\n"; print "Enter a command #> "; } sub get_mysql_path { if($OS eq "Windows"){ $has_mysql_path = `echo %PATH%`; - if($has_mysql_path=~/MySQL|MariaDB/i){ + if($has_mysql_path=~/MySQL|MariaDB/i){ @mysql = split(';', $has_mysql_path); foreach my $v (@mysql){ - if($v=~/MySQL|MariaDB/i){ - $v =~s/\n//g; + if($v=~/MySQL|MariaDB/i){ + $v =~s/\n//g; $path = trim($v) . "/mysql"; last; } @@ -889,13 +889,13 @@ sub get_mysql_path { } } if($OS eq "Linux"){ - $path = `which mysql`; + $path = `which mysql`; if ($path eq "") { - $path = `which mariadb`; + $path = `which mariadb`; } - $path =~s/\n//g; + $path =~s/\n//g; } - + #::: Path not found, error and exit if($path eq ""){ print "[Error:eqemu_server.pl] MySQL path not found, please add the path for automatic database upgrading to continue... \n\n"; @@ -912,28 +912,28 @@ sub check_for_database_dump_script{ #::: Check for script changes :: database_dumper.pl get_remote_file($eqemu_repository_request_url . "utils/scripts/database_dumper.pl", "updates_staged/database_dumper.pl", 0, 1, 1); - - if(-e "updates_staged/database_dumper.pl") { - + + if(-e "updates_staged/database_dumper.pl") { + my $remote_script_size = -s "updates_staged/database_dumper.pl"; my $local_script_size = -s "database_dumper.pl"; - + if($remote_script_size != $local_script_size){ print "[Update] Script has been updated, updating...\n"; - + my @files; my $start_dir = "updates_staged/"; - find( - sub { push @files, $File::Find::name unless -d; }, + find( + sub { push @files, $File::Find::name unless -d; }, $start_dir ); for my $file (@files) { - if($file=~/database_dumper/i){ + if($file=~/database_dumper/i){ $destination_file = $file; $destination_file =~s/updates_staged\///g; print "[Install] Installing :: " . $destination_file . "\n"; unlink($destination_file); - copy_file($file, $destination_file); + copy_file($file, $destination_file); if($OS eq "Linux"){ system("chmod 755 database_dumper.pl"); } @@ -947,22 +947,22 @@ sub check_for_database_dump_script{ unlink("updates_staged/database_dumper.pl"); } - + return; - + } -sub database_dump { +sub database_dump { check_for_database_dump_script(); print "[Database] Performing database backup....\n"; print `perl database_dumper.pl database="$db" loc="backups"`; } -sub database_dump_player_tables { +sub database_dump_player_tables { check_for_database_dump_script(); print "[Database] Performing database backup of player tables....\n"; get_remote_file($eqemu_repository_request_url . "utils/sql/character_table_list.txt", "backups/character_table_list.txt"); - + $tables = ""; open (FILE, "backups/character_table_list.txt"); $i = 0; @@ -974,20 +974,20 @@ sub database_dump_player_tables { $tables = substr($tables, 0, -1); print `perl database_dumper.pl database="$db" loc="backups" tables="$tables" backup_name="player_tables_export" nolock`; - + print "[Database] Press any key to continue...\n"; <>; #Read from STDIN - + } -sub database_dump_compress { +sub database_dump_compress { check_for_database_dump_script(); print "[Database] Performing database backup....\n"; print `perl database_dumper.pl database="$db" loc="backups" compress`; } -sub script_exit{ +sub script_exit{ #::: Cleanup staged folder... rmtree("updates_staged/"); exit; @@ -1009,9 +1009,9 @@ sub get_mysql_result{ my $run_query = $_[0]; if(!$db){ return; } if($OS eq "Windows"){ return `"$path" --host $host --user $user --password="$pass" $db -N -B -e "$run_query"`; } - if($OS eq "Linux"){ + if($OS eq "Linux"){ $run_query =~s/`//g; - return `$path --user="$user" --host $host --password="$pass" $db -N -B -e "$run_query"`; + return `$path --user="$user" --host $host --password="$pass" $db -N -B -e "$run_query"`; } } @@ -1030,23 +1030,23 @@ sub get_remote_file{ my $content_type = $_[2]; my $no_retry = $_[3]; my $silent_download = $_[4]; - + if(!$has_internet_connection){ print "[Download] Cannot download without internet connection...\n"; return; } - + #::: Build file path of the destination file so that we may check for the folder's existence and make it if necessary - + if($destination_file=~/\//i){ my @directory_path = split('/', $destination_file); $build_path = ""; $directory_index = 0; while($directory_path[$directory_index] && $directory_path[$directory_index + 1]){ - $build_path .= $directory_path[$directory_index] . "/"; + $build_path .= $directory_path[$directory_index] . "/"; # print "checking '" . $build_path . "'\n"; #::: If path does not exist, create the directory... - if (!-d $build_path) { + if (!-d $build_path) { print "[Copy] folder doesn't exist, creating '" . $build_path . "'\n"; mkdir($build_path); } @@ -1058,23 +1058,23 @@ sub get_remote_file{ $directory_index++; } } - + #::: wget -O db_update/db_update_manifest.txt https://raw.githubusercontent.com/EQEmu/Server/master/utils/sql/db_update_manifest.txt $wget = `wget -N --cache=no --no-check-certificate --quiet -O $destination_file $request_url`; print "[Download] Saved: (" . $destination_file . ") from " . $request_url . "\n" if !$silent_download; - if($wget=~/unable to resolve/i){ + if($wget=~/unable to resolve/i){ print "Error, no connection or failed request...\n\n"; #die; } - + } #::: Trim Whitespaces -sub trim { - my $string = $_[0]; - $string =~ s/^\s+//; - $string =~ s/\s+$//; - return $string; +sub trim { + my $string = $_[0]; + $string =~ s/^\s+//; + $string =~ s/\s+$//; + return $string; } sub read_eqemu_config_xml { @@ -1082,11 +1082,11 @@ sub read_eqemu_config_xml { while (){ chomp; $o = $_; - + if($o=~/\<\!--/i){ - next; + next; } - + if($o=~/database/i && $o=~/\<\//i){ $in_database_tag = 0; } @@ -1094,15 +1094,15 @@ sub read_eqemu_config_xml { print "IN DATABASE TAG\n" if $debug; $in_database_tag = 1; } - if($o=~//i){ + if($o=~//i){ ($long_name) = $o =~ /(.*)<\/longname>/; print "Long Name: '" . $long_name . "'\n" if $debug; } if($in_database_tag == 1){ - @left = split (">", $o); + @left = split (">", $o); @right = split("<", $left[1]); $tag_data = trim($right[0]); - + if($o=~//i && $in_database_tag){ $user = $tag_data; print "Database User: '" . $user . "'\n" if $debug; @@ -1136,7 +1136,7 @@ sub read_eqemu_config_json { close($fh); $config = $json->decode($content); - + $db = $config->{"server"}{"database"}{"db"}; $host = $config->{"server"}{"database"}{"host"}; $user = $config->{"server"}{"database"}{"username"}; @@ -1160,7 +1160,7 @@ sub aa_fetch{ #::: Fetch Latest Opcodes sub opcodes_fetch{ - print "[Update] Pulling down latest opcodes...\n"; + print "[Update] Pulling down latest opcodes...\n"; %opcodes = ( 1 => ["opcodes", $eqemu_repository_request_url . "utils/patches/opcodes.conf"], 2 => ["mail_opcodes", $eqemu_repository_request_url . "utils/patches/mail_opcodes.conf"], @@ -1172,27 +1172,27 @@ sub opcodes_fetch{ 8 => ["Rain of Fear 2", $eqemu_repository_request_url . "utils/patches/patch_RoF2.conf"], ); $loop = 1; - while($opcodes{$loop}[0]){ + while($opcodes{$loop}[0]){ #::: Split the request_url by the patches folder to get the file name from request_url @real_file = split("patches/", $opcodes{$loop}[1]); $find = 0; while($real_file[$find]){ - $file_name = $real_file[$find]; + $file_name = $real_file[$find]; $find++; } get_remote_file($opcodes{$loop}[1], $file_name); - $loop++; + $loop++; } - print "[Update] Done...\n"; + print "[Update] Done...\n"; } sub remove_duplicate_rule_values { $ruleset_id = trim(get_mysql_result("SELECT `ruleset_id` FROM `rule_sets` WHERE `name` = 'default'")); print "[Database] Default Ruleset ID: " . $ruleset_id . "\n"; - + $total_removed = 0; - + #::: Store Default values... $mysql_result = get_mysql_result("SELECT * FROM `rule_values` WHERE `ruleset_id` = " . $ruleset_id); my @lines = split("\n", $mysql_result); @@ -1200,7 +1200,7 @@ sub remove_duplicate_rule_values { my @values = split("\t", $val); $rule_set_values{$values[1]}[0] = $values[2]; } - + #::: Compare default values against other rulesets to check for duplicates... $mysql_result = get_mysql_result("SELECT * FROM `rule_values` WHERE `ruleset_id` != " . $ruleset_id); my @lines = split("\n", $mysql_result); @@ -1212,7 +1212,7 @@ sub remove_duplicate_rule_values { $total_removed++; } } - + print "[Database] Total duplicate rules removed... " . $total_removed . "\n"; } @@ -1250,8 +1250,8 @@ sub fetch_latest_windows_appveyor { unzip('updates_staged/master_windows_build_pdb.zip', 'updates_staged/binaries/'); my @files; my $start_dir = "updates_staged/binaries"; - find( - sub { push @files, $File::Find::name unless -d; }, + find( + sub { push @files, $File::Find::name unless -d; }, $start_dir ); for my $file (@files) { @@ -1261,7 +1261,7 @@ sub fetch_latest_windows_appveyor { copy_file($file, $destination_file); } print "[Update] Done\n"; - + rmtree('updates_staged'); } @@ -1273,8 +1273,8 @@ sub fetch_latest_windows_binaries { unzip('updates_staged/master_windows_build.zip', 'updates_staged/binaries/'); my @files; my $start_dir = "updates_staged/binaries"; - find( - sub { push @files, $File::Find::name unless -d; }, + find( + sub { push @files, $File::Find::name unless -d; }, $start_dir ); for my $file (@files) { @@ -1284,7 +1284,7 @@ sub fetch_latest_windows_binaries { copy_file($file, $destination_file); } print "[Update] Done\n"; - + rmtree('updates_staged'); } @@ -1296,8 +1296,8 @@ sub fetch_latest_windows_binaries_bots { unzip('updates_staged/master_windows_build_bots.zip', 'updates_staged/binaries/'); my @files; my $start_dir = "updates_staged/binaries"; - find( - sub { push @files, $File::Find::name unless -d; }, + find( + sub { push @files, $File::Find::name unless -d; }, $start_dir ); for my $file (@files) { @@ -1307,7 +1307,7 @@ sub fetch_latest_windows_binaries_bots { copy_file($file, $destination_file); } print "[Update] Done...\n"; - + rmtree('updates_staged'); } @@ -1318,8 +1318,8 @@ sub do_windows_login_server_setup { unzip('updates_staged/login_server.zip', 'updates_staged/login_server/'); my @files; my $start_dir = "updates_staged/login_server"; - find( - sub { push @files, $File::Find::name unless -d; }, + find( + sub { push @files, $File::Find::name unless -d; }, $start_dir ); for my $file (@files) { @@ -1329,56 +1329,56 @@ sub do_windows_login_server_setup { copy_file($file, $destination_file); } print "[Install] Done... \n"; - + print "[Install] Pulling down Loginserver database tables...\n"; get_remote_file($install_repository_request_url . "login_server_tables.sql", "db_update/login_server_tables.sql"); print "[Install] Installing Loginserver tables...\n"; print get_mysql_result_from_file("db_update/login_server_tables.sql"); print "[Install] Done...\n"; - + add_login_server_firewall_rules(); - + rmtree('updates_staged'); rmtree('db_update'); - + print "[Install] Press any key to continue...\n"; - - <>; #Read from STDIN - + + <>; #Read from STDIN + } sub do_linux_login_server_setup { - + build_linux_source(); - + for my $file (@files) { - $destination_file = $file; + $destination_file = $file; $destination_file =~s/updates_staged\/login_server\///g; print "[Install] Installing :: " . $destination_file . "\n"; copy_file($file, $destination_file); } print "\n Done... \n"; - + print "[Install] Pulling down Loginserver database tables...\n"; get_remote_file($install_repository_request_url . "login_server_tables.sql", "db_update/login_server_tables.sql"); print "[Install] Installing Loginserver tables...\n"; print get_mysql_result_from_file("db_update/login_server_tables.sql"); print "[Install] Done...\n\n"; - + rmtree('updates_staged'); rmtree('db_update'); - + get_remote_file($install_repository_request_url . "linux/login.ini", "login_template.ini"); get_remote_file($install_repository_request_url . "linux/login_opcodes.conf", "login_opcodes.conf"); get_remote_file($install_repository_request_url . "linux/login_opcodes_sod.conf", "login_opcodes_sod.conf"); get_remote_file($install_repository_request_url . "linux/server_start_with_login.sh", "server_start_with_login.sh"); system("chmod 755 *.sh"); - + get_installation_variables(); my $db_name = $installation_variables{"mysql_eqemu_db_name"}; my $db_user = $installation_variables{"mysql_eqemu_user"}; my $db_password = $installation_variables{"mysql_eqemu_password"}; - + #::: Open new config file open (NEW_CONFIG, '>', 'login.ini'); @@ -1391,18 +1391,18 @@ sub do_linux_login_server_setup { if($o=~/db/i){ $o = "db = " . $db_name; } if($o=~/user/i){ $o = "user = " . $db_user; } if($o=~/password/i){ $o = "password = " . $db_password; } - + print NEW_CONFIG $o . "\n"; } - + close(FILE_TEMPLATE); close(NEW_CONFIG); unlink("login_template.ini"); - + print "[Install] Press any key to continue...\n"; - + <>; #Read from STDIN - + } sub add_login_server_firewall_rules { @@ -1425,7 +1425,7 @@ sub add_login_server_firewall_rules { } } } - + if($has_loginserver_rules_titanium == 0){ print "[Install] Attempting to add EQEmu Loginserver Firewall Rules (Titanium) (TCP) port 5998 \n"; print `netsh advfirewall firewall add rule name="EQEmu Loginserver (Titanium) (5998) TCP" dir=in action=allow protocol=TCP localport=5998`; @@ -1438,7 +1438,7 @@ sub add_login_server_firewall_rules { print "[Install] Attempting to add EQEmu Loginserver Firewall Rules (SOD+) (UDP) port 5999 \n"; print `netsh advfirewall firewall add rule name="EQEmu Loginserver (SOD+) (5999) UDP" dir=in action=allow protocol=UDP localport=5999`; } - + print "If firewall rules don't add you must run this script (eqemu_server.pl) as administrator\n"; print "\n"; print "[Install] Instructions \n"; @@ -1479,7 +1479,7 @@ sub check_windows_firewall_rules{ } } } - + if($has_world_rules == 0){ print "[Install] Attempting to add EQEmu World Firewall Rules (TCP) port 9000 \n"; print `netsh advfirewall firewall add rule name="EQEmu World (9000) TCP" dir=in action=allow protocol=TCP localport=9000`; @@ -1507,8 +1507,8 @@ sub fetch_peq_db_full{ 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"; - find( - sub { push @files, $File::Find::name unless -d; }, + find( + sub { push @files, $File::Find::name unless -d; }, $start_dir ); for my $file (@files) { @@ -1531,8 +1531,8 @@ sub map_files_fetch_bulk{ unzip('maps/maps.zip', 'maps/'); my @files; my $start_dir = "maps/EQEmuMaps-master/"; - find( - sub { push @files, $File::Find::name unless -d; }, + find( + sub { push @files, $File::Find::name unless -d; }, $start_dir ); for my $file (@files) { @@ -1542,16 +1542,16 @@ sub map_files_fetch_bulk{ copy_file($file, "maps/" . $new_file); } print "[Install] Fetched Latest Maps\n"; - + rmtree('maps/EQEmuMaps-master'); unlink('maps/maps.zip'); } sub map_files_fetch{ print "[Install] Fetching Latest Maps --- \n"; - + get_remote_file("https://raw.githubusercontent.com/Akkadius/EQEmuMaps/master/!eqemu_maps_manifest.txt", "updates_staged/eqemu_maps_manifest.txt"); - + #::: Get Data from manifest open (FILE, "updates_staged/eqemu_maps_manifest.txt"); $i = 0; @@ -1564,8 +1564,8 @@ sub map_files_fetch{ $i++; } } - - #::: Download + + #::: Download $fc = 0; for($m = 0; $m <= $i; $m++){ my $file_existing = $maps_manifest[$m][0]; @@ -1576,7 +1576,7 @@ sub map_files_fetch{ $fc++; } } - + if($fc == 0){ print "[Install] No Map Updates found... \n\n"; } @@ -1590,15 +1590,15 @@ sub quest_files_fetch{ mkdir('updates_staged'); unzip('updates_staged/projecteqquests-master.zip', 'updates_staged/'); } - + $fc = 0; use File::Find; use File::Compare; - + my @files; my $start_dir = "updates_staged/projecteqquests-master/"; - find( - sub { push @files, $File::Find::name unless -d; }, + find( + sub { push @files, $File::Find::name unless -d; }, $start_dir ); for my $file (@files) { @@ -1606,7 +1606,7 @@ sub quest_files_fetch{ $staged_file = $file; $destination_file = $file; $destination_file =~s/updates_staged\/projecteqquests-master\//quests\//g; - + if (!-e $destination_file) { copy_file($staged_file, $destination_file); print "[Install] Installing :: '" . $destination_file . "'\n"; @@ -1616,7 +1616,7 @@ sub quest_files_fetch{ $directory_indexff = do_file_diff($destination_file, $staged_file); if($directory_indexff ne ""){ $backup_dest = "updates_backups/" . $time_stamp . "/" . $destination_file; - + print $directory_indexff . "\n"; print "[Update] File Different :: '" . $destination_file . "'\n"; print "[Update] Do you wish to update this Quest? '" . $destination_file . "' [Yes (Enter) - No (N)] \nA backup will be found in '" . $backup_dest . "'\n"; @@ -1634,7 +1634,7 @@ sub quest_files_fetch{ } } } - + if($fc == 0){ print "[Update] No Quest Updates found... \n\n"; } @@ -1648,17 +1648,17 @@ sub lua_modules_fetch { mkdir('updates_staged'); unzip('updates_staged/projecteqquests-master.zip', 'updates_staged/'); } - + $fc = 0; use File::Find; use File::Compare; - + mkdir('lua_modules'); - + my @files; my $start_dir = "updates_staged/projecteqquests-master/lua_modules/"; - find( - sub { push @files, $File::Find::name unless -d; }, + find( + sub { push @files, $File::Find::name unless -d; }, $start_dir ); for my $file (@files) { @@ -1666,7 +1666,7 @@ sub lua_modules_fetch { $staged_file = $file; $destination_file = $file; $destination_file =~s/updates_staged\/projecteqquests-master\/lua_modules\//lua_modules\//g; - + if (!-e $destination_file) { copy_file($staged_file, $destination_file); print "[Install] Installing :: '" . $destination_file . "'\n"; @@ -1693,10 +1693,10 @@ sub lua_modules_fetch { } } } - + if($fc == 0){ print "[Update] No LUA Modules Updates found... \n\n"; - } + } } sub plugins_fetch{ @@ -1707,17 +1707,17 @@ sub plugins_fetch{ mkdir('updates_staged'); unzip('updates_staged/projecteqquests-master.zip', 'updates_staged/'); } - + $fc = 0; use File::Find; use File::Compare; - + mkdir('plugins'); - + my @files; my $start_dir = "updates_staged/projecteqquests-master/plugins/"; - find( - sub { push @files, $File::Find::name unless -d; }, + find( + sub { push @files, $File::Find::name unless -d; }, $start_dir ); for my $file (@files) { @@ -1725,7 +1725,7 @@ sub plugins_fetch{ $staged_file = $file; $destination_file = $file; $destination_file =~s/updates_staged\/projecteqquests-master\///g; - + if (!-e $destination_file) { copy_file($staged_file, $destination_file); print "[Install] Installing :: '" . $destination_file . "'\n"; @@ -1755,7 +1755,7 @@ sub plugins_fetch{ if($fc == 0){ print "[Update] No Plugin Updates found... \n\n"; - } + } } sub do_file_diff{ @@ -1775,8 +1775,8 @@ sub do_file_diff{ sub unzip { $archive_to_unzip = $_[0]; $dest_folder = $_[1]; - - if($OS eq "Windows"){ + + if($OS eq "Windows"){ eval "use Archive::Zip qw( :ERROR_CODES :CONSTANTS )"; my $zip = Archive::Zip->new(); unless ( $zip->read($archive_to_unzip) == AZ_OK ) { @@ -1807,30 +1807,30 @@ sub do_bots_db_schema_drop{ print "[Database] Fetching drop_bots.sql...\n"; get_remote_file($eqemu_repository_request_url . "utils/sql/git/bots/drop_bots.sql", "db_update/drop_bots.sql"); print get_mysql_result_from_file("db_update/drop_bots.sql"); - + print "[Database] Removing bot database tables...\n"; print get_mysql_result("DELETE FROM `rule_values` WHERE `rule_name` LIKE 'Bots:%';"); - + if(get_mysql_result("SHOW TABLES LIKE 'commands'") ne "" && $db){ print get_mysql_result("DELETE FROM `commands` WHERE `command` LIKE 'bot';"); } - + if(get_mysql_result("SHOW TABLES LIKE 'command_settings'") ne "" && $db){ print get_mysql_result("DELETE FROM `command_settings` WHERE `command` LIKE 'bot';"); } - + if(get_mysql_result("SHOW KEYS FROM `group_id` WHERE `Key_name` LIKE 'PRIMARY'") ne "" && $db){ print get_mysql_result("ALTER TABLE `group_id` DROP PRIMARY KEY;"); } print get_mysql_result("ALTER TABLE `group_id` ADD PRIMARY KEY (`groupid`, `charid`, `ismerc`);"); - + if(get_mysql_result("SHOW KEYS FROM `guild_members` WHERE `Key_name` LIKE 'PRIMARY'") ne "" && $db){ print get_mysql_result("ALTER TABLE `guild_members` DROP PRIMARY KEY;"); } print get_mysql_result("ALTER TABLE `guild_members` ADD PRIMARY KEY (`char_id`);"); - + print get_mysql_result("UPDATE `spawn2` SET `enabled` = 0 WHERE `id` IN (59297,59298);"); - + if(get_mysql_result("SHOW COLUMNS FROM `db_version` LIKE 'bots_version'") ne "" && $db){ print get_mysql_result("UPDATE `db_version` SET `bots_version` = 0;"); } @@ -1841,80 +1841,80 @@ sub modify_db_for_bots{ #Called after the db bots schema (2015_09_30_bots.sql) has been loaded print "[Database] Modifying database for bots...\n"; print get_mysql_result("UPDATE `spawn2` SET `enabled` = 1 WHERE `id` IN (59297,59298);"); - + if(get_mysql_result("SHOW KEYS FROM `guild_members` WHERE `Key_name` LIKE 'PRIMARY'") ne "" && $db){ print get_mysql_result("ALTER TABLE `guild_members` DROP PRIMARY KEY;"); } - + if(get_mysql_result("SHOW KEYS FROM `group_id` WHERE `Key_name` LIKE 'PRIMARY'") ne "" && $db){ print get_mysql_result("ALTER TABLE `group_id` DROP PRIMARY KEY;"); } print get_mysql_result("ALTER TABLE `group_id` ADD PRIMARY KEY USING BTREE(`groupid`, `charid`, `name`, `ismerc`);"); - + if(get_mysql_result("SHOW TABLES LIKE 'command_settings'") ne "" && get_mysql_result("SELECT `command` FROM `command_settings` WHERE `command` LIKE 'bot'") eq "" && $db){ print get_mysql_result("INSERT INTO `command_settings` VALUES ('bot', '0', '');"); } - + if(get_mysql_result("SHOW TABLES LIKE 'commands'") ne "" && get_mysql_result("SELECT `command` FROM `commands` WHERE `command` LIKE 'bot'") eq "" && $db){ print get_mysql_result("INSERT INTO `commands` VALUES ('bot', '0');"); } - + if(get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:BotAAExpansion'") ne "" && $db){ print get_mysql_result("UPDATE `rule_values` SET `rule_name` = 'Bots:AAExpansion' WHERE `rule_name` LIKE 'Bots:BotAAExpansion';"); - } + } if(get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:AAExpansion'") eq "" && $db){ print get_mysql_result("INSERT INTO `rule_values` VALUES ('1', 'Bots:AAExpansion', '8', 'The expansion through which bots will obtain AAs');"); } - + if(get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:CreateBotCount'") ne "" && $db){ print get_mysql_result("UPDATE `rule_values` SET `rule_name` = 'Bots:CreationLimit' WHERE `rule_name` LIKE 'Bots:CreateBotCount';"); } if(get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:CreationLimit'") eq "" && $db){ print get_mysql_result("INSERT INTO `rule_values` VALUES ('1', 'Bots:CreationLimit', '150', 'Number of bots that each account can create');"); } - + if(get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:BotFinishBuffing'") ne "" && $db){ print get_mysql_result("UPDATE `rule_values` SET `rule_name` = 'Bots:FinishBuffing' WHERE `rule_name` LIKE 'Bots:BotFinishBuffing';"); } if(get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:FinishBuffing'") eq "" && $db){ print get_mysql_result("INSERT INTO `rule_values` VALUES ('1', 'Bots:FinishBuffing', 'false', 'Allow for buffs to complete even if the bot caster is out of mana. Only affects buffing out of combat.');"); } - + if(get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:BotGroupBuffing'") ne "" && $db){ print get_mysql_result("UPDATE `rule_values` SET `rule_name` = 'Bots:GroupBuffing' WHERE `rule_name` LIKE 'Bots:BotGroupBuffing';"); } if(get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:GroupBuffing'") eq "" && $db){ print get_mysql_result("INSERT INTO `rule_values` VALUES ('1', 'Bots:GroupBuffing', 'false', 'Bots will cast single target buffs as group buffs, default is false for single. Does not make single target buffs work for MGB.');"); } - + if(get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:BotManaRegen'") ne "" && $db){ print get_mysql_result("UPDATE `rule_values` SET `rule_name` = 'Bots:ManaRegen' WHERE `rule_name` LIKE 'Bots:BotManaRegen';"); } if(get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:ManaRegen'") eq "" && $db){ print get_mysql_result("INSERT INTO `rule_values` VALUES ('1', 'Bots:ManaRegen', '3.0', 'Adjust mana regen for bots, 1 is fast and higher numbers slow it down 3 is about the same as players.');"); } - + if(get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:BotQuest'") ne "" && $db){ print get_mysql_result("UPDATE `rule_values` SET `rule_name` = 'Bots:QuestableSpawnLimit' WHERE `rule_name` LIKE 'Bots:BotQuest';"); } if(get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:QuestableSpawnLimit'") eq "" && $db){ print get_mysql_result("INSERT INTO `rule_values` VALUES ('1', 'Bots:QuestableSpawnLimit', 'false', 'Optional quest method to manage bot spawn limits using the quest_globals name bot_spawn_limit, see: /bazaar/Aediles_Thrall.pl');"); } - + if(get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:BotSpellQuest'") ne "" && $db){ print get_mysql_result("UPDATE `rule_values` SET `rule_name` = 'Bots:QuestableSpells' WHERE `rule_name` LIKE 'Bots:BotSpellQuest';"); } if(get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:QuestableSpells'") eq "" && $db){ print get_mysql_result("INSERT INTO `rule_values` VALUES ('1', 'Bots:QuestableSpells', 'false', 'Anita Thrall\\\'s (Anita_Thrall.pl) Bot Spell Scriber quests.');"); } - + if(get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:SpawnBotCount'") ne "" && $db){ print get_mysql_result("UPDATE `rule_values` SET `rule_name` = 'Bots:SpawnLimit' WHERE `rule_name` LIKE 'Bots:SpawnBotCount';"); } if(get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:SpawnLimit'") eq "" && $db){ print get_mysql_result("INSERT INTO `rule_values` VALUES ('1', 'Bots:SpawnLimit', '71', 'Number of bots a character can have spawned at one time, You + 71 bots is a 12 group raid');"); } - + convert_existing_bot_data(); } @@ -1922,93 +1922,93 @@ sub convert_existing_bot_data{ if(get_mysql_result("SHOW TABLES LIKE 'bots'") ne "" && $db){ print "[Database] Converting existing bot data...\n"; print get_mysql_result("INSERT INTO `bot_data` (`bot_id`, `owner_id`, `spells_id`, `name`, `last_name`, `zone_id`, `gender`, `race`, `class`, `level`, `creation_day`, `last_spawn`, `time_spawned`, `size`, `face`, `hair_color`, `hair_style`, `beard`, `beard_color`, `eye_color_1`, `eye_color_2`, `drakkin_heritage`, `drakkin_tattoo`, `drakkin_details`, `ac`, `atk`, `hp`, `mana`, `str`, `sta`, `cha`, `dex`, `int`, `agi`, `wis`, `fire`, `cold`, `magic`, `poison`, `disease`, `corruption`) SELECT `BotID`, `BotOwnerCharacterID`, `BotSpellsID`, `Name`, `LastName`, `LastZoneId`, `Gender`, `Race`, `Class`, `BotLevel`, UNIX_TIMESTAMP(`BotCreateDate`), UNIX_TIMESTAMP(`LastSpawnDate`), `TotalPlayTime`, `Size`, `Face`, `LuclinHairColor`, `LuclinHairStyle`, `LuclinBeard`, `LuclinBeardColor`, `LuclinEyeColor`, `LuclinEyeColor2`, `DrakkinHeritage`, `DrakkinTattoo`, `DrakkinDetails`, `AC`, `ATK`, `HP`, `Mana`, `STR`, `STA`, `CHA`, `DEX`, `_INT`, `AGI`, `WIS`, `FR`, `CR`, `MR`, `PR`, `DR`, `Corrup` FROM `bots`;"); - + print get_mysql_result("INSERT INTO `bot_inspect_messages` (`bot_id`, `inspect_message`) SELECT `BotID`, `BotInspectMessage` FROM `bots`;"); - + print get_mysql_result("RENAME TABLE `bots` TO `bots_old`;"); } - + if(get_mysql_result("SHOW TABLES LIKE 'botstances'") ne "" && $db){ print get_mysql_result("INSERT INTO `bot_stances` (`bot_id`, `stance_id`) SELECT bs.`BotID`, bs.`StanceID` FROM `botstances` bs INNER JOIN `bot_data` bd ON bs.`BotID` = bd.`bot_id`;"); - + print get_mysql_result("RENAME TABLE `botstances` TO `botstances_old`;"); } - + if(get_mysql_result("SHOW TABLES LIKE 'bottimers'") ne "" && $db){ print get_mysql_result("INSERT INTO `bot_timers` (`bot_id`, `timer_id`, `timer_value`) SELECT bt.`BotID`, bt.`TimerID`, bt.`Value` FROM `bottimers` bt INNER JOIN `bot_data` bd ON bt.`BotID` = bd.`bot_id`;"); - + print get_mysql_result("RENAME TABLE `bottimers` TO `bottimers_old`;"); } - + if(get_mysql_result("SHOW TABLES LIKE 'botbuffs'") ne "" && $db){ print get_mysql_result("INSERT INTO `bot_buffs` (`buffs_index`, `bot_id`, `spell_id`, `caster_level`, `duration_formula`, `tics_remaining`, `poison_counters`, `disease_counters`, `curse_counters`, `corruption_counters`, `numhits`, `melee_rune`, `magic_rune`, `persistent`) SELECT bb.`BotBuffId`, bb.`BotId`, bb.`SpellId`, bb.`CasterLevel`, bb.`DurationFormula`, bb.`TicsRemaining`, bb.`PoisonCounters`, bb.`DiseaseCounters`, bb.`CurseCounters`, bb.`CorruptionCounters`, bb.`HitCount`, bb.`MeleeRune`, bb.`MagicRune`, bb.`Persistent` FROM `botbuffs` bb INNER JOIN `bot_data` bd ON bb.`BotId` = bd.`bot_id`;"); - + if(get_mysql_result("SHOW COLUMNS FROM `botbuffs` LIKE 'dot_rune'") ne "" && $db){ print get_mysql_result("UPDATE `bot_buffs` bb INNER JOIN `botbuffs` bbo ON bb.`buffs_index` = bbo.`BotBuffId` SET bb.`dot_rune` = bbo.`dot_rune` WHERE bb.`bot_id` = bbo.`BotID`;"); } - + if(get_mysql_result("SHOW COLUMNS FROM `botbuffs` LIKE 'caston_x'") ne "" && $db){ print get_mysql_result("UPDATE `bot_buffs` bb INNER JOIN `botbuffs` bbo ON bb.`buffs_index` = bbo.`BotBuffId` SET bb.`caston_x` = bbo.`caston_x` WHERE bb.`bot_id` = bbo.`BotID`;"); } - + if(get_mysql_result("SHOW COLUMNS FROM `botbuffs` LIKE 'caston_y'") ne "" && $db){ print get_mysql_result("UPDATE `bot_buffs` bb INNER JOIN `botbuffs` bbo ON bb.`buffs_index` = bbo.`BotBuffId` SET bb.`caston_y` = bbo.`caston_y` WHERE bb.`bot_id` = bbo.`BotID`;"); } - + if(get_mysql_result("SHOW COLUMNS FROM `botbuffs` LIKE 'caston_z'") ne "" && $db){ print get_mysql_result("UPDATE `bot_buffs` bb INNER JOIN `botbuffs` bbo ON bb.`buffs_index` = bbo.`BotBuffId` SET bb.`caston_z` = bbo.`caston_z` WHERE bb.`bot_id` = bbo.`BotID`;"); } - + if(get_mysql_result("SHOW COLUMNS FROM `botbuffs` LIKE 'ExtraDIChance'") ne "" && $db){ print get_mysql_result("UPDATE `bot_buffs` bb INNER JOIN `botbuffs` bbo ON bb.`buffs_index` = bbo.`BotBuffId` SET bb.`extra_di_chance` = bbo.`ExtraDIChance` WHERE bb.`bot_id` = bbo.`BotID`;"); } - + print get_mysql_result("RENAME TABLE `botbuffs` TO `botbuffs_old`;"); } - + if(get_mysql_result("SHOW TABLES LIKE 'botinventory'") ne "" && $db){ print get_mysql_result("INSERT INTO `bot_inventories` (`inventories_index`, `bot_id`, `slot_id`, `item_id`, `inst_charges`, `inst_color`, `inst_no_drop`, `augment_1`, `augment_2`, `augment_3`, `augment_4`, `augment_5`) SELECT bi.`BotInventoryID`, bi.`BotID`, bi.`SlotID`, bi.`ItemID`, bi.`charges`, bi.`color`, bi.`instnodrop`, bi.`augslot1`, bi.`augslot2`, bi.`augslot3`, bi.`augslot4`, bi.`augslot5` FROM `botinventory` bi INNER JOIN `bot_data` bd ON bi.`BotID` = bd.`bot_id`;"); - + if(get_mysql_result("SHOW COLUMNS FROM `botinventory` LIKE 'augslot6'") ne "" && $db){ print get_mysql_result("UPDATE `bot_inventories` bi INNER JOIN `botinventory` bio ON bi.`inventories_index` = bio.`BotInventoryID` SET bi.`augment_6` = bio.`augslot6` WHERE bi.`bot_id` = bio.`BotID`;"); } - + print get_mysql_result("RENAME TABLE `botinventory` TO `botinventory_old`;"); } - + if(get_mysql_result("SHOW TABLES LIKE 'botpets'") ne "" && $db){ print get_mysql_result("INSERT INTO `bot_pets` (`pets_index`, `pet_id`, `bot_id`, `name`, `mana`, `hp`) SELECT bp.`BotPetsId`, bp.`PetId`, bp.`BotId`, bp.`Name`, bp.`Mana`, bp.`HitPoints` FROM `botpets` bp INNER JOIN `bot_data` bd ON bp.`BotId` = bd.`bot_id`;"); - + print get_mysql_result("RENAME TABLE `botpets` TO `botpets_old`;"); } - + if(get_mysql_result("SHOW TABLES LIKE 'botpetbuffs'") ne "" && $db){ print get_mysql_result("INSERT INTO `bot_pet_buffs` (`pet_buffs_index`, `pets_index`, `spell_id`, `caster_level`, `duration`) SELECT bpb.`BotPetBuffId`, bpb.`BotPetsId`, bpb.`SpellId`, bpb.`CasterLevel`, bpb.`Duration` FROM `botpetbuffs` bpb INNER JOIN `bot_pets` bp ON bpb.`BotPetsId` = bp.`pets_index`;"); - + print get_mysql_result("RENAME TABLE `botpetbuffs` TO `botpetbuffs_old`;"); } - + if(get_mysql_result("SHOW TABLES LIKE 'botpetinventory'") ne "" && $db){ print get_mysql_result("INSERT INTO `bot_pet_inventories` (`pet_inventories_index`, `pets_index`, `item_id`) SELECT bpi.`BotPetInventoryId`, bpi.`BotPetsId`, bpi.`ItemId` FROM `botpetinventory` bpi INNER JOIN `bot_pets` bp ON bpi.`BotPetsId` = bp.`pets_index`;"); - + print get_mysql_result("RENAME TABLE `botpetinventory` TO `botpetinventory_old`;"); } - + if(get_mysql_result("SHOW TABLES LIKE 'botgroup'") ne "" && $db){ print get_mysql_result("INSERT INTO `bot_groups` (`groups_index`, `group_leader_id`, `group_name`) SELECT bg.`BotGroupId`, bg.`BotGroupLeaderBotId`, bg.`BotGroupName` FROM `botgroup` bg INNER JOIN `bot_data` bd ON bg.`BotGroupLeaderBotId` = bd.`bot_id`;"); - + print get_mysql_result("RENAME TABLE `botgroup` TO `botgroup_old`;"); } - + if(get_mysql_result("SHOW TABLES LIKE 'botgroupmembers'") ne "" && $db){ print get_mysql_result("INSERT INTO `bot_group_members` (`group_members_index`, `groups_index`, `bot_id`) SELECT bgm.`BotGroupMemberId`, bgm.`BotGroupId`, bgm.`BotId` FROM `botgroupmembers` bgm INNER JOIN `bot_groups` bg ON bgm.`BotGroupId` = bg.`groups_index` INNER JOIN `bot_data` bd ON bgm.`BotId` = bd.`bot_id`;"); - + print get_mysql_result("RENAME TABLE `botgroupmembers` TO `botgroupmembers_old`;"); } - + if(get_mysql_result("SHOW TABLES LIKE 'botguildmembers'") ne "" && $db){ print get_mysql_result("INSERT INTO `bot_guild_members` (`bot_id`, `guild_id`, `rank`, `tribute_enable`, `total_tribute`, `last_tribute`, `banker`, `public_note`, `alt`) SELECT bgm.`char_id`, bgm.`guild_id`, bgm.`rank`, bgm.`tribute_enable`, bgm.`total_tribute`, bgm.`last_tribute`, bgm.`banker`, bgm.`public_note`, bgm.`alt` FROM `botguildmembers` bgm INNER JOIN `guilds` g ON bgm.`guild_id` = g.`id` INNER JOIN `bot_data` bd ON bgm.`char_id` = bd.`bot_id`;"); - + print get_mysql_result("RENAME TABLE `botguildmembers` TO `botguildmembers_old`;"); } } @@ -2024,16 +2024,16 @@ sub get_bots_db_version{ } sub bots_db_management{ - if($OS eq "Windows"){ - @db_version = split(': ', `world db_version`); + if($OS eq "Windows"){ + @db_version = split(': ', `world db_version`); + } + if($OS eq "Linux"){ + @db_version = split(': ', `./world db_version`); } - if($OS eq "Linux"){ - @db_version = split(': ', `./world db_version`); - } #::: Main Binary Database version $binary_database_version = trim($db_version[2]); - + #::: If we have stale data from main db run if($db_run_stage > 0 && $bots_db_management == 0){ clear_database_runs(); @@ -2043,14 +2043,14 @@ sub bots_db_management{ print "[Database] Your server binaries (world/zone) are not compiled for bots...\n\n"; return; } - + #::: Set on flag for running bot updates... $bots_db_management = 1; - + $bots_local_db_version = get_bots_db_version(); - + $local_database_version = $bots_local_db_version; - + run_database_check(); } @@ -2062,7 +2062,7 @@ sub main_db_management{ #::: Main Binary Database version $binary_database_version = trim($db_version[1]); - + $bots_db_management = 0; run_database_check(); } @@ -2078,18 +2078,18 @@ sub clear_database_runs{ } #::: Responsible for Database Upgrade Routines -sub run_database_check{ +sub run_database_check{ if(!$db){ print "No database present, check your eqemu_config.xml for proper MySQL/MariaDB configuration...\n"; return; } - + if(!@total_updates){ #::: Pull down bots database manifest if($bots_db_management == 1){ print "[Database] Retrieving latest bots database manifest...\n"; - get_remote_file($eqemu_repository_request_url . "utils/sql/git/bots/bots_db_update_manifest.txt", "db_update/db_update_manifest.txt"); + get_remote_file($eqemu_repository_request_url . "utils/sql/git/bots/bots_db_update_manifest.txt", "db_update/db_update_manifest.txt"); } #::: Pull down mainstream database manifest else{ @@ -2106,7 +2106,7 @@ sub run_database_check{ print "[Database] Running Update: " . $val . " - " . $file_name . "\n"; print get_mysql_result_from_file("db_update/$file_name"); print get_mysql_result("UPDATE db_version SET version = $val WHERE version < $val"); - + if($bots_db_management == 1 && $val == 9000){ modify_db_for_bots(); } @@ -2118,7 +2118,7 @@ sub run_database_check{ print "[Database] Reading manifest...\n"; use Data::Dumper; open (FILE, "db_update/db_update_manifest.txt"); - while () { + while () { chomp; $o = $_; if($o=~/#/i){ next; } @@ -2128,9 +2128,9 @@ sub run_database_check{ #::: Setting Manifest stage... $db_run_stage = 1; } - + @total_updates = (); - + #::: This is where we set checkpoints for where a database might be so we don't check so far back in the manifest... if($local_database_version >= 9000){ $revision_check = $local_database_version; @@ -2141,16 +2141,16 @@ sub run_database_check{ $revision_check = 8999; } } - + #::: Iterate through Manifest backwards from binary version down to local version... - for($i = $binary_database_version ; $i > $revision_check; $i--){ - if(!defined($m_d{$i}[0])){ next; } - + for($i = $binary_database_version ; $i > $revision_check; $i--){ + if(!defined($m_d{$i}[0])){ next; } + $file_name = trim($m_d{$i}[1]); $query_check = trim($m_d{$i}[2]); $match_type = trim($m_d{$i}[3]); $match_text = trim($m_d{$i}[4]); - + #::: Match type update if($match_type eq "contains"){ if(trim(get_mysql_result($query_check))=~/$match_text/i){ @@ -2165,9 +2165,9 @@ sub run_database_check{ print_break(); } if($match_type eq "missing"){ - if(get_mysql_result($query_check)=~/$match_text/i){ + if(get_mysql_result($query_check)=~/$match_text/i){ print "[Database] has update: " . $i . " - '" . $file_name . "' \n"; - next; + next; } else{ print "[Database] missing update: " . $i . " '" . $file_name . "' \n"; @@ -2202,19 +2202,19 @@ sub run_database_check{ print_break(); } } - print "\n"; - + print "\n"; + if(scalar (@total_updates) == 0 && $db_run_stage == 2){ print "[Database] No updates need to be run...\n"; if($bots_db_management == 1){ print "[Database] Setting Database to Bots Binary Version (" . $binary_database_version . ") if not already...\n\n"; - get_mysql_result("UPDATE db_version SET bots_version = $binary_database_version "); + get_mysql_result("UPDATE db_version SET bots_version = $binary_database_version "); } - else{ + else{ print "[Database] Setting Database to Binary Version (" . $binary_database_version . ") if not already...\n\n"; - get_mysql_result("UPDATE db_version SET version = $binary_database_version "); + get_mysql_result("UPDATE db_version SET version = $binary_database_version "); } - + clear_database_runs(); } } @@ -2235,7 +2235,7 @@ sub fetch_missing_db_update{ } } -sub print_match_debug{ +sub print_match_debug{ if(!$debug){ return; } print " Match Type: '" . $match_type . "'\n"; print " Match Text: '" . $match_text . "'\n"; @@ -2243,15 +2243,15 @@ sub print_match_debug{ print " Result: '" . trim(get_mysql_result($query_check)) . "'\n"; } -sub print_break{ - if(!$debug){ return; } - print "\n==============================================\n"; +sub print_break{ + if(!$debug){ return; } + print "\n==============================================\n"; } sub generate_random_password { my $passwordsize = shift; my @alphanumeric = ('a'..'z', 'A'..'Z', 0..9); - my $randpassword = join '', + my $randpassword = join '', map $alphanumeric[rand @alphanumeric], 0..$passwordsize; return $randpassword; diff --git a/zone/CMakeLists.txt b/zone/CMakeLists.txt index 0171c8eb9..3971666b1 100644 --- a/zone/CMakeLists.txt +++ b/zone/CMakeLists.txt @@ -112,6 +112,7 @@ SET(zone_sources spawn2.h spawngroup.cpp special_attacks.cpp + spell_cache.cpp spell_effects.cpp spells.cpp tasks.cpp @@ -217,6 +218,7 @@ SET(zone_headers spawn2.cpp spawn2.h spawngroup.h + spell_cache.h string_ids.h tasks.h titles.h diff --git a/zone/aa.cpp b/zone/aa.cpp index 38d477fb8..e8ef40109 100644 --- a/zone/aa.cpp +++ b/zone/aa.cpp @@ -486,6 +486,8 @@ void Client::ResetAA() { m_pp.group_leadership_exp = 0; m_pp.raid_leadership_exp = 0; + m_spell_cache.SetAltCached(false); + database.DeleteCharacterLeadershipAAs(CharacterID()); // undefined for these clients if (ClientVersionBit() & EQEmu::versions::bit_TitaniumAndEarlier) @@ -801,6 +803,7 @@ void Client::RefundAA() { Save(); } + m_spell_cache.SetAltCached(false); SendAlternateAdvancementTable(); SendAlternateAdvancementPoints(); SendAlternateAdvancementStats(); @@ -1115,6 +1118,7 @@ void Client::FinishAlternateAdvancementPurchase(AA::Rank *rank, bool ignore_cost } } + m_spell_cache.SetAltCached(false); CalcBonuses(); if(cost > 0) { diff --git a/zone/attack.cpp b/zone/attack.cpp index ce96675a0..84a4dcc97 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -1517,12 +1517,14 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b /////////////////////////////////////////////////////////// ////// Send Attack Damage /////////////////////////////////////////////////////////// - if (my_hit.damage_done > 0 && aabonuses.SkillAttackProc[0] && aabonuses.SkillAttackProc[1] == my_hit.skill && - IsValidSpell(aabonuses.SkillAttackProc[2])) { - float chance = aabonuses.SkillAttackProc[0] / 1000.0f; - if (zone->random.Roll(chance)) - SpellFinished(aabonuses.SkillAttackProc[2], other, EQEmu::CastingSlot::Item, 0, -1, - spells[aabonuses.SkillAttackProc[2]].ResistDiff); + if (my_hit.damage_done > 0 && m_spell_cache.HasSkillAttackProcs()) { + auto end = m_spell_cache.skill_attack_proc_end(); + for (auto it = m_spell_cache.skill_attack_proc_begin(); it != end; ++it) { + if (it->skill == my_hit.skill && IsValidSpell(it->spell)) { + if (zone->random.Roll(it->chance / 1000.0f)) + SpellFinished(it->spell, other, EQEmu::CastingSlot::Item, 0, -1, spells[it->spell].ResistDiff); + } + } } other->Damage(this, my_hit.damage_done, SPELL_UNKNOWN, my_hit.skill, true, -1, false, m_specialattacks); @@ -4226,12 +4228,11 @@ void Mob::TryCriticalHit(Mob *defender, DamageHitInfo &hit, ExtraAttackOptions * // 1: Try Slay Undead if (defender->GetBodyType() == BT_Undead || defender->GetBodyType() == BT_SummonedUndead || defender->GetBodyType() == BT_Vampire) { - int SlayRateBonus = aabonuses.SlayUndead[0] + itembonuses.SlayUndead[0] + spellbonuses.SlayUndead[0]; + int SlayRateBonus = TotalEffect(SE_SlayUndead, 0); if (SlayRateBonus) { float slayChance = static_cast(SlayRateBonus) / 10000.0f; if (zone->random.Roll(slayChance)) { - int SlayDmgBonus = std::max( - { aabonuses.SlayUndead[1], itembonuses.SlayUndead[1], spellbonuses.SlayUndead[1] }); + int SlayDmgBonus = BestEffect(SE_SlayUndead, 1); hit.damage_done = std::max(hit.damage_done, hit.base_damage) + 5; hit.damage_done = (hit.damage_done * SlayDmgBonus) / 100; @@ -4402,20 +4403,12 @@ bool Mob::TryFinishingBlow(Mob *defender, int &damage) { // base2 of FinishingBlowLvl is the HP limit (cur / max) * 1000, 10% is listed as 100 if (defender && !defender->IsClient() && defender->GetHPRatio() < 10) { + int FB_Dmg = TotalEffect(SE_FinishingBlow, 1); - uint32 FB_Dmg = - aabonuses.FinishingBlow[1] + spellbonuses.FinishingBlow[1] + itembonuses.FinishingBlow[1]; - - uint32 FB_Level = 0; - FB_Level = aabonuses.FinishingBlowLvl[0]; - if (FB_Level < spellbonuses.FinishingBlowLvl[0]) - FB_Level = spellbonuses.FinishingBlowLvl[0]; - else if (FB_Level < itembonuses.FinishingBlowLvl[0]) - FB_Level = itembonuses.FinishingBlowLvl[0]; + int FB_Level = BestEffect(SE_FinishingBlowLvl, 0); // modern AA description says rank 1 (500) is 50% chance - int ProcChance = - aabonuses.FinishingBlow[0] + spellbonuses.FinishingBlow[0] + spellbonuses.FinishingBlow[0]; + int ProcChance = TotalEffect(SE_FinishingBlow, 0); if (FB_Level && FB_Dmg && (defender->GetLevel() <= FB_Level) && (ProcChance >= zone->random.Int(1, 1000))) { @@ -4699,177 +4692,64 @@ void Mob::ApplyDamageTable(DamageHitInfo &hit) void Mob::TrySkillProc(Mob *on, uint16 skill, uint16 ReuseTime, bool Success, uint16 hand, bool IsDefensive) { - if (!on) { SetTarget(nullptr); Log(Logs::General, Logs::Error, "A null Mob object was passed to Mob::TrySkillProc for evaluation!"); return; } - if (!spellbonuses.LimitToSkill[skill] && !itembonuses.LimitToSkill[skill] && !aabonuses.LimitToSkill[skill]) - return; - /*Allow one proc from each (Spell/Item/AA) - Kayen: Due to limited avialability of effects on live it is too difficult - to confirm how they stack at this time, will adjust formula when more data is avialablle to test.*/ - bool CanProc = true; + Kayen: Due to limited availability of effects on live it is too difficult + to confirm how they stack at this time, will adjust formula when more data is available to test.*/ - uint16 base_spell_id = 0; - uint16 proc_spell_id = 0; - float ProcMod = 0; - float chance = 0; + float ProcMod = 0.0f; + float chance = 0.0f; if (IsDefensive) chance = on->GetSkillProcChances(ReuseTime, hand); else chance = GetSkillProcChances(ReuseTime, hand); - if (spellbonuses.LimitToSkill[skill]) { + std::vector::const_iterator it, end; + auto type = SpellCache::eSkillProc::Buff; + int i = 0; - for (int e = 0; e < MAX_SKILL_PROCS; e++) { - if (CanProc && - ((!Success && spellbonuses.SkillProc[e] && IsValidSpell(spellbonuses.SkillProc[e])) - || (Success && spellbonuses.SkillProcSuccess[e] && IsValidSpell(spellbonuses.SkillProcSuccess[e])))) { + while (i < 3) { + if (Success) { + it = m_spell_cache.skill_proc_success_begin(type); + end = m_spell_cache.skill_proc_success_end(type); + } else { + it = m_spell_cache.skill_proc_attempt_begin(type); + end = m_spell_cache.skill_proc_attempt_end(type); + } - if (Success) - base_spell_id = spellbonuses.SkillProcSuccess[e]; - else - base_spell_id = spellbonuses.SkillProc[e]; - - proc_spell_id = 0; - ProcMod = 0; - - for (int i = 0; i < EFFECT_COUNT; i++) { - - if (spells[base_spell_id].effectid[i] == SE_SkillProc || spells[base_spell_id].effectid[i] == SE_SkillProcSuccess) { - proc_spell_id = spells[base_spell_id].base[i]; - ProcMod = static_cast(spells[base_spell_id].base2[i]); - } - - else if (spells[base_spell_id].effectid[i] == SE_LimitToSkill && spells[base_spell_id].base[i] <= EQEmu::skills::HIGHEST_SKILL) { - - if (CanProc && spells[base_spell_id].base[i] == skill && IsValidSpell(proc_spell_id)) { - float final_chance = chance * (ProcMod / 100.0f); - if (zone->random.Roll(final_chance)) { - ExecWeaponProc(nullptr, proc_spell_id, on); - CheckNumHitsRemaining(NumHit::OffensiveSpellProcs, 0, - base_spell_id); - CanProc = false; - break; - } - } - } - else { - //Reset and check for proc in sequence - proc_spell_id = 0; - ProcMod = 0; - } + for (; it != end; ++it) { + // abilities that have no skill limits can always proc? (see Lingering Death, Shrouding Speed Discipline) + if ((it->skills.empty() || + it->skills.count(static_cast(skill)) != 0) && + IsValidSpell(it->spell)) { + ProcMod = static_cast(it->chance); + float final_chance = chance * (ProcMod / 100.0f); + if (zone->random.Roll(final_chance)) { + ExecWeaponProc(nullptr, it->spell, on); + if (type == SpellCache::eSkillProc::Buff) + CheckNumHitsRemaining(NumHit::OffensiveSpellProcs, 0, + buffs[it->slot].spellid); + return; // do we want to allow others to have a chance? } } } - } - if (itembonuses.LimitToSkill[skill]) { - CanProc = true; - for (int e = 0; e < MAX_SKILL_PROCS; e++) { - if (CanProc && - ((!Success && itembonuses.SkillProc[e] && IsValidSpell(itembonuses.SkillProc[e])) - || (Success && itembonuses.SkillProcSuccess[e] && IsValidSpell(itembonuses.SkillProcSuccess[e])))) { - - if (Success) - base_spell_id = itembonuses.SkillProcSuccess[e]; - else - base_spell_id = itembonuses.SkillProc[e]; - - proc_spell_id = 0; - ProcMod = 0; - - for (int i = 0; i < EFFECT_COUNT; i++) { - if (spells[base_spell_id].effectid[i] == SE_SkillProc || spells[base_spell_id].effectid[i] == SE_SkillProcSuccess) { - proc_spell_id = spells[base_spell_id].base[i]; - ProcMod = static_cast(spells[base_spell_id].base2[i]); - } - - else if (spells[base_spell_id].effectid[i] == SE_LimitToSkill && spells[base_spell_id].base[i] <= EQEmu::skills::HIGHEST_SKILL) { - - if (CanProc && spells[base_spell_id].base[i] == skill && IsValidSpell(proc_spell_id)) { - float final_chance = chance * (ProcMod / 100.0f); - if (zone->random.Roll(final_chance)) { - ExecWeaponProc(nullptr, proc_spell_id, on); - CanProc = false; - break; - } - } - } - else { - proc_spell_id = 0; - ProcMod = 0; - } - } - } - } - } - - if (IsClient() && aabonuses.LimitToSkill[skill]) { - - CanProc = true; - uint32 effect_id = 0; - int32 base1 = 0; - int32 base2 = 0; - uint32 slot = 0; - - for (int e = 0; e < MAX_SKILL_PROCS; e++) { - if (CanProc && - ((!Success && aabonuses.SkillProc[e]) - || (Success && aabonuses.SkillProcSuccess[e]))) { - int aaid = 0; - - if (Success) - base_spell_id = aabonuses.SkillProcSuccess[e]; - else - base_spell_id = aabonuses.SkillProc[e]; - - proc_spell_id = 0; - ProcMod = 0; - - for (auto &rank_info : aa_ranks) { - auto ability_rank = zone->GetAlternateAdvancementAbilityAndRank(rank_info.first, rank_info.second.first); - auto ability = ability_rank.first; - auto rank = ability_rank.second; - - if (!ability) { - continue; - } - - for (auto &effect : rank->effects) { - effect_id = effect.effect_id; - base1 = effect.base1; - base2 = effect.base2; - slot = effect.slot; - - if (effect_id == SE_SkillProc || effect_id == SE_SkillProcSuccess) { - proc_spell_id = base1; - ProcMod = static_cast(base2); - } - else if (effect_id == SE_LimitToSkill && base1 <= EQEmu::skills::HIGHEST_SKILL) { - - if (CanProc && base1 == skill && IsValidSpell(proc_spell_id)) { - float final_chance = chance * (ProcMod / 100.0f); - - if (zone->random.Roll(final_chance)) { - ExecWeaponProc(nullptr, proc_spell_id, on); - CanProc = false; - break; - } - } - } - else { - proc_spell_id = 0; - ProcMod = 0; - } - } - } - } + ++i; + switch (i) { + case 1: + type = SpellCache::eSkillProc::Worn; + break; + case 2: + type = SpellCache::eSkillProc::AA; + break; + default: + break; } } } diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index c444ff9dd..22fa9fc3d 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -628,7 +628,9 @@ void Client::CalcEdibleBonuses(StatBonuses* newbon) { void Mob::CalcAABonuses(StatBonuses *newbon) { - memset(newbon, 0, sizeof(StatBonuses)); // start fresh + m_spell_cache.ClearAltEffect(); + + RecacheSuppressionSpells(); // we gotta do this :P for (const auto &aa : aa_ranks) { auto ability_rank = zone->GetAlternateAdvancementAbilityAndRank(aa.first, aa.second.first); @@ -645,6 +647,8 @@ void Mob::CalcAABonuses(StatBonuses *newbon) ApplyAABonuses(*rank, newbon); } + + m_spell_cache.SetAltCached(true); } //A lot of the normal spell functions (IsBlankSpellEffect, etc) are set for just spells (in common/spdat.h). @@ -660,17 +664,19 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) int32 base2 = 0; // only really used for SE_RaiseStatCap & SE_ReduceSkillTimer in aa_effects table uint32 slot = 0; + int last_skill_proc = 0; // some book keeping for SE_SkillProc and SE_SkillProcSuccess for SE_LimitToSkill + for (const auto &e : rank.effects) { effect = e.effect_id; base1 = e.base1; - base2 = e.base2; + base2 = 0; // only some AAs use the base2 slot = e.slot; // we default to 0 (SE_CurrentHP) for the effect, so if there aren't any base1/2 values, we'll just skip it if (effect == 0 && base1 == 0 && base2 == 0) continue; - // IsBlankSpellEffect() + // IsBlankSpellEffect() -- these shouldn't be in AAs if (effect == SE_Blank || (effect == SE_CHA && base1 == 0) || effect == SE_StackingCommand_Block || effect == SE_StackingCommand_Overwrite) continue; @@ -678,12 +684,116 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) Log(Logs::Detail, Logs::AA, "Applying Effect %d from AA %u in slot %d (base1: %d, base2: %d) on %s", effect, rank.id, slot, base1, base2, GetCleanName()); + // this should be removed when rewritten uint8 focus = IsFocusEffect(0, 0, true, effect); if (focus) { newbon->FocusEffects[focus] = static_cast(effect); continue; } + // SE_NegateSpellEffects + if (!(GetSuppresionSpellMask(effect) & SM_AAs)) + continue; + + /* TODO: Other SPAs that need special handling + * SE_ProcOnKillShot + * SE_SpellOnDeath + */ + + // special handling for AAs that live doesn't appear to do + // Alternative to this would be to just iterate over the AA list + // each of these are special and we skip below + switch (effect) { + case SE_SkillAttackProc: + m_spell_cache.InsertSkillAttackProc(base1, e.base2, rank.spell); + continue; + case SE_SkillProc: + last_skill_proc = effect; + m_spell_cache.InsertSkillProcAttempt(SpellCache::eSkillProc::AA, base1, e.base2); + continue; + case SE_SkillProcSuccess: + last_skill_proc = effect; + m_spell_cache.InsertSkillProcSuccess(SpellCache::eSkillProc::AA, base1, e.base2); + continue; + case SE_LimitToSkill: + m_spell_cache.InsertSkillLimit(SpellCache::eSkillProc::AA, static_cast(base1), last_skill_proc == SE_SkillProcSuccess); + continue; + default: + break; + } + + // Okay, effects that use base2 + switch (effect) { + case SE_SpellEffectResistChance: + if (!RuleB(Spells, Oct182017AAResistOtherSPAFix)) // this is broken before this patch + break; + case SE_Levitate: + case SE_CriticalHitChance: + case SE_MeleeSkillCheck: + case SE_HitChance: + case SE_DamageModifier: + case SE_MinDamageModifier: + case SE_SkillDamageTaken: + case SE_Accuracy: + case SE_SkillDamageAmount: + case SE_GiveDoubleRiposte: + case SE_ReduceSkillTimer: + case SE_RaiseSkillCap: + case SE_AddSingingMod: + case SE_RaiseStatCap: + case SE_HastenedAASkill: + case SE_AddPetCommand: + case SE_ReduceTradeskillFail: + case SE_CriticalDamageMob: + case SE_SkillDamageAmount2: + base2 = e.base2; + break; + default: + break; + } + + // these don't stack and some treat base2 differently and don't stack + switch (effect) { + case SE_MasteryofPast: + case SE_Assassinate: + case SE_AssassinateLevel: + case SE_HeadShot: + case SE_HeadShotLevel: + case SE_FinishingBlowLvl: + case SE_FinishingBlow: + case SE_DivineSave: // non stacking + if (base1 > m_spell_cache.GetCachedAltEffect(effect)) + m_spell_cache.InsertAltEffect(effect, base1, 0); + break; + default: //stacking + m_spell_cache.InsertAltEffect(effect, base1 + m_spell_cache.GetCachedAltEffect(effect), base2); + break; + } + + // RoF2 doesn't have SE_CriticalSpellChance here, but our AA data looks like it's processed like this + // client doesn't handle SE_DivineSave like this + // we support older Frenzied Devastation SPA too, this was revamped on live at some point + // SE_SlayUndead isn't done here on client, but it should work for how it works + switch (effect) { + case SE_Assassinate: + case SE_HeadShot: + case SE_FinishingBlow: + case SE_AssassinateLevel: + case SE_HeadShotLevel: + case SE_CriticalSpellChance: + case SE_DivineSave: + case SE_FrenziedDevastation: + case SE_SlayUndead: + case SE_PC_Pet_Rampage: + base2 = e.base2; // uses base2 for fun times! + if (base2 > m_spell_cache.GetCachedAltEffect(effect, 1)) + m_spell_cache.InsertAltEffect(effect, base2, 1); + break; + default: + break; + } + + // TODO remove switch (effect) { case SE_ACv2: case SE_ArmorClass: diff --git a/zone/client.h b/zone/client.h index e0732fd92..e777238a0 100644 --- a/zone/client.h +++ b/zone/client.h @@ -437,6 +437,10 @@ public: void EnableAreaRegens(int value); void DisableAreaRegens(); + // new spell cache stuff + virtual void RecacheItemEffects(); + virtual void RecacheSuppressionItems(); // work around for NPC invs being dumb + void ServerFilter(SetServerFilter_Struct* filter); void BulkSendTraderInventory(uint32 char_id); void SendSingleTraderItem(uint32 char_id, int uniqueid); diff --git a/zone/common.h b/zone/common.h index df6b22637..723d00dff 100644 --- a/zone/common.h +++ b/zone/common.h @@ -286,6 +286,61 @@ struct Buffs_Struct { bool UpdateClient; }; +// we will use this for item stat bonus now only rather then spell effects +// TODO: rename when other is removed +struct StatBonuses2 { + int STR; + int STA; + int CHA; + int DEX; + int INT; + int AGI; + int WIS; + int MR; + int CR; + int FR; + int PR; + int DR; + int Corrup; + int HP; + int Mana; + int Endurance; + int AC; + int ProcChance; // ProcChance/10 == % increase i = CombatEffects + int MeleeMitigation; //i = Shielding + int SpellShield; + int AvoidMeleeChance; //AvoidMeleeChance/10 == % chance i = Avoidance (item mod) + int HitChance; //HitChance/15 == % increase i = Accuracy (Item: Accuracy) + int StunResist; //i + int DoTShielding; + int DSMitigation; // Item Effect + int HeroicSTR; + int HeroicINT; + int HeroicWIS; + int HeroicAGI; + int HeroicDEX; + int HeroicSTA; + int HeroicCHA; + int HeroicMR; + int HeroicFR; + int HeroicCR; + int HeroicDR; + int HeroicPR; + int HeroicCorrup; + int HealAmt; // Item Effect + int SpellDmg; // Item Effect + int Clairvoyance; // Item Effect + int StrikeThrough; // PoP: Strike Through % + int ATK; + int HPRegen; + int ManaRegen; + int EnduranceRegen; + int DamageShield; // this is damage done to mobs that attack this + int PotionBeltSlots; + int SkillMinDamageMod[9]; + int haste; +}; + struct StatBonuses { int32 AC; int32 HP; diff --git a/zone/effects.cpp b/zone/effects.cpp index 6b0c2a2ad..8d83a52bb 100644 --- a/zone/effects.cpp +++ b/zone/effects.cpp @@ -62,8 +62,8 @@ int32 Mob::GetActSpellDamage(uint16 spell_id, int32 value, Mob* target) { value -= GetAA(aaUnholyTouch) * 450; //Unholy Touch chance = RuleI(Spells, BaseCritChance); //Wizard base critical chance is 2% (Does not scale with level) - chance += itembonuses.CriticalSpellChance + spellbonuses.CriticalSpellChance + aabonuses.CriticalSpellChance; - chance += itembonuses.FrenziedDevastation + spellbonuses.FrenziedDevastation + aabonuses.FrenziedDevastation; + chance += TotalEffect(SE_CriticalSpellChance, 0); + chance += TotalEffect(SE_FrenziedDevastation, 1); //Crtical Hit Calculation pathway if (chance > 0 || (IsClient() && GetClass() == WIZARD && GetLevel() >= RuleI(Spells, WizCritLevel))) { @@ -79,8 +79,8 @@ int32 Mob::GetActSpellDamage(uint16 spell_id, int32 value, Mob* target) { if (zone->random.Roll(chance)) { Critical = true; - ratio += itembonuses.SpellCritDmgIncrease + spellbonuses.SpellCritDmgIncrease + aabonuses.SpellCritDmgIncrease; - ratio += itembonuses.SpellCritDmgIncNoStack + spellbonuses.SpellCritDmgIncNoStack + aabonuses.SpellCritDmgIncNoStack; + ratio += TotalEffect(SE_SpellCritDmgIncrease); + ratio += TotalEffect(SE_CriticalSpellChance, 1); } else if ((IsClient() && GetClass() == WIZARD) || (IsMerc() && GetClass() == CASTERDPS)) { diff --git a/zone/mob.cpp b/zone/mob.cpp index 9efdda70c..677600e84 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -116,6 +116,7 @@ Mob::Mob(const char* in_name, fix_z_timer_engaged(100), attack_anim_timer(1000), position_update_melee_push_timer(1000), + bItemDirty(false), mHateListCleanup(6000) { targeted = 0; diff --git a/zone/mob.h b/zone/mob.h index 6266aa029..9bc8c24e7 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -25,6 +25,7 @@ #include "position.h" #include "aa_ability.h" #include "aa.h" +#include "spell_cache.h" #include "../common/light_source.h" #include "../common/emu_constants.h" #include @@ -537,6 +538,35 @@ public: inline const int8 GetFlyMode() const { return flymode; } bool IsBoat() const; + // Spell Cache stuff + int TotalEffect(int spaID, int subindex = 0, bool bIncludeItems = true, bool bIncludeAA = true, bool bIncludeBuffs = true); + // this one just skips special behavior, it's /not/ usable with SPAs with special behavior + int TotalEffectSimple(int spaID, int subindex = 0) { return GetCachedPlayerEffect(spaID, subindex) + GetCachedItemEffect(spaID, subindex) + GetCachedAltEffect(spaID, subindex); } + // returns the highest, also not usable with SPAs that need special behavior, but I guess this it's own special behavior + int BestEffect(int spaID, int subindex = 0) { return std::max(GetCachedPlayerEffect(spaID, subindex), std::max(GetCachedItemEffect(spaID, subindex), GetCachedAltEffect(spaID, subindex))); } + void RecacheSpellEffects(); + virtual void RecacheItemEffects() {} // virtual since mob/client have different inv structs + void RecacheSuppressionSpells(); + virtual void RecacheSuppressionItems() {} // work around for invs being different for mobs/clients + inline int GetSuppresionSpellMask(int spaID) { + return GetCachedPlayerEffect(SE_NegateSpellEffect, spaID) | GetCachedItemEffect(SE_NegateSpellEffect, spaID) | GetCachedAltEffect(SE_NegateSpellEffect, spaID); + } + inline int GetCachedPlayerEffect(int spaID, int subindex = 0) { + if (!m_spell_cache.IsSpellCached()) + RecacheSpellEffects(); + return m_spell_cache.GetCachedPlayerEffect(spaID, subindex); + } + inline int GetCachedItemEffect(int spaID, int subindex = 0) { + if (!m_spell_cache.IsItemCached()) + RecacheItemEffects(); + return m_spell_cache.GetCachedItemEffect(spaID, subindex); + } + inline int GetCachedAltEffect(int spaID, int subindex = 0) { + if (!m_spell_cache.IsAltCached()) + CalcAABonuses(&aabonuses); + return m_spell_cache.GetCachedAltEffect(spaID, subindex); + } + //Group virtual bool HasRaid() = 0; virtual bool HasGroup() = 0; @@ -1215,9 +1245,11 @@ protected: uint32 scalerate; Buffs_Struct *buffs; uint32 current_buff_count; + bool bItemDirty; StatBonuses itembonuses; StatBonuses spellbonuses; StatBonuses aabonuses; + SpellCache m_spell_cache; uint16 petid; uint16 ownerid; PetType typeofpet; diff --git a/zone/mob_ai.cpp b/zone/mob_ai.cpp index 9db5b2577..82fb51b26 100644 --- a/zone/mob_ai.cpp +++ b/zone/mob_ai.cpp @@ -399,7 +399,7 @@ bool EntityList::AICheckCloseBeneficialSpells(NPC* caster, uint8 iChance, float //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; } @@ -482,7 +482,7 @@ void Mob::AI_Start(uint32 iMoveDelay) { if(CastToNPC()->WillAggroNPCs()) AI_scan_area_timer = std::unique_ptr(new Timer(RandomTimer(RuleI(NPC, NPCToNPCAggroTimerMin), RuleI(NPC, NPCToNPCAggroTimerMax)))); - + AI_check_signal_timer = std::unique_ptr(new Timer(AI_check_signal_timer_delay)); @@ -1439,7 +1439,7 @@ void Mob::AI_Process() { } else { - CalculateNewPosition2(owner->GetX(), + CalculateNewPosition2(owner->GetX(), owner->GetY(), owner->GetZ(), speed); } } @@ -1579,14 +1579,14 @@ void NPC::AI_DoMovement() { roambox_movingto_x = zone->random.Real(roambox_min_x+1,roambox_max_x-1); if (roambox_movingto_y > roambox_max_y || roambox_movingto_y < roambox_min_y) roambox_movingto_y = zone->random.Real(roambox_min_y+1,roambox_max_y-1); - Log(Logs::Detail, Logs::AI, + Log(Logs::Detail, Logs::AI, "Roam Box: d=%.3f (%.3f->%.3f,%.3f->%.3f): Go To (%.3f,%.3f)", - roambox_distance, roambox_min_x, roambox_max_x, roambox_min_y, + roambox_distance, roambox_min_x, roambox_max_x, roambox_min_y, roambox_max_y, roambox_movingto_x, roambox_movingto_y); } // Keep calling with updates, using wherever we are in Z. - if (!MakeNewPositionAndSendUpdate(roambox_movingto_x, + if (!MakeNewPositionAndSendUpdate(roambox_movingto_x, roambox_movingto_y, m_Position.z, walksp)) { this->FixZ(); // FixZ on final arrival point. @@ -1617,7 +1617,7 @@ void NPC::AI_DoMovement() { if (m_CurrentWayPoint.x == GetX() && m_CurrentWayPoint.y == GetY()) { // are we there yet? then stop Log(Logs::Detail, Logs::AI, "We have reached waypoint %d (%.3f,%.3f,%.3f) on grid %d", cur_wp, GetX(), GetY(), GetZ(), GetGrid()); - + SetWaypointPause(); SetAppearance(eaStanding, false); SetMoving(false); diff --git a/zone/npc.h b/zone/npc.h index 0ff7f3b03..5e38335df 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -160,6 +160,10 @@ public: virtual void InitializeBuffSlots(); virtual void UninitializeBuffSlots(); + // new spell cache stuff + virtual void RecacheItemEffects(); + virtual void RecacheSuppressionItems(); // work around for NPC invs being dumb + virtual void SetAttackTimer(); virtual void RangedAttack(Mob* other); virtual void ThrowingAttack(Mob* other) { } @@ -457,7 +461,7 @@ protected: uint32 npc_spells_id; uint8 casting_spell_AIindex; - + uint32* pDontCastBefore_casting_spell; std::vector AIspells; bool HasAISpell; diff --git a/zone/pets.cpp b/zone/pets.cpp index bbc4f8eea..34166b0bc 100644 --- a/zone/pets.cpp +++ b/zone/pets.cpp @@ -268,7 +268,7 @@ void Mob::MakePoweredPet(uint16 spell_id, const char* pettype, int16 petpower, memcpy(npc_type, base, sizeof(NPCType)); // If pet power is set to -1 in the DB, use stat scaling - if ((this->IsClient() + if ((this->IsClient() #ifdef BOTS || this->IsBot() #endif @@ -658,7 +658,7 @@ void NPC::SetPetState(SpellBuff_Struct *pet_buffs, uint32 *items) { if (item2) { bool noDrop=(item2->NoDrop == 0); // Field is reverse logic - bool petCanHaveNoDrop = (RuleB(Pets, CanTakeNoDrop) && + bool petCanHaveNoDrop = (RuleB(Pets, CanTakeNoDrop) && _CLIENTPET(this) && GetPetType() <= petOther); if (!noDrop || petCanHaveNoDrop) { diff --git a/zone/special_attacks.cpp b/zone/special_attacks.cpp index 03b144b5a..474d16d40 100644 --- a/zone/special_attacks.cpp +++ b/zone/special_attacks.cpp @@ -2014,9 +2014,8 @@ int Mob::TryHeadShot(Mob *defender, EQEmu::skills::SkillType skillInUse) // Only works on YOUR target. if (defender && defender->GetBodyType() == BT_Humanoid && !defender->IsClient() && skillInUse == EQEmu::skills::SkillArchery && GetTarget() == defender) { - uint32 HeadShot_Dmg = aabonuses.HeadShot[1] + spellbonuses.HeadShot[1] + itembonuses.HeadShot[1]; - uint8 HeadShot_Level = 0; // Get Highest Headshot Level - HeadShot_Level = std::max({aabonuses.HSLevel[0], spellbonuses.HSLevel[0], itembonuses.HSLevel[0]}); + int HeadShot_Dmg = TotalEffect(SE_HeadShot, 1); + int HeadShot_Level = BestEffect(SE_HeadShotLevel); if (HeadShot_Dmg && HeadShot_Level && (defender->GetLevel() <= HeadShot_Level)) { int chance = GetDEX(); @@ -2024,10 +2023,10 @@ int Mob::TryHeadShot(Mob *defender, EQEmu::skills::SkillType skillInUse) if (IsClient()) chance += CastToClient()->GetHeroicDEX() / 25; chance *= 10; - int norm = aabonuses.HSLevel[1]; + int norm = TotalEffect(SE_HeadShotLevel, 1); if (norm > 0) chance = chance * norm / 100; - chance += aabonuses.HeadShot[0] + spellbonuses.HeadShot[0] + itembonuses.HeadShot[0]; + chance += TotalEffect(SE_HeadShot, 0); if (zone->random.Int(1, 1000) <= chance) { entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, FATAL_BOW_SHOT, GetName()); @@ -2049,7 +2048,7 @@ int Mob::TryAssassinate(Mob *defender, EQEmu::skills::SkillType skillInUse) if (IsClient()) chance += CastToClient()->GetHeroicDEX(); chance *= 10; - int norm = aabonuses.AssassinateLevel[1]; + int norm = TotalEffect(SE_AssassinateLevel, 1); if (norm > 0) chance = chance * norm / 100; } else if (skillInUse == EQEmu::skills::SkillThrowing) { @@ -2059,14 +2058,11 @@ int Mob::TryAssassinate(Mob *defender, EQEmu::skills::SkillType skillInUse) chance += 5; } - chance += aabonuses.Assassinate[0] + spellbonuses.Assassinate[0] + itembonuses.Assassinate[0]; + chance += TotalEffect(SE_Assassinate, 0); // base2 == 0 is chance - uint32 Assassinate_Dmg = - aabonuses.Assassinate[1] + spellbonuses.Assassinate[1] + itembonuses.Assassinate[1]; + int Assassinate_Dmg = TotalEffect(SE_Assassinate, 1); // base2 == 1 is dmg - uint8 Assassinate_Level = 0; // Get Highest Headshot Level - Assassinate_Level = std::max( - {aabonuses.AssassinateLevel[0], spellbonuses.AssassinateLevel[0], itembonuses.AssassinateLevel[0]}); + int Assassinate_Level = BestEffect(SE_AssassinateLevel, 0); // revamped AAs require AA line I believe? if (!Assassinate_Level) diff --git a/zone/spell_cache.cpp b/zone/spell_cache.cpp new file mode 100644 index 000000000..3d6bfce40 --- /dev/null +++ b/zone/spell_cache.cpp @@ -0,0 +1,227 @@ +#include "spell_cache.h" + +void SpellCache::InsertSpellEffect(int affect, int value, int subindex) +{ + auto range = m_spelleffect.equal_range(affect); + auto effect_iter = range.first; + + while (effect_iter != range.second) { + if (effect_iter->second.base2 == subindex) + break; + ++effect_iter; + } + + if (effect_iter == range.second) {// we didn't find one + sEffectCache e{affect, value, subindex}; + m_spelleffect.emplace(affect, e); + return; + } + + // we gotta update + effect_iter->second.base1 = value; +} + +void SpellCache::InsertItemEffect(int affect, int value, int subindex) +{ + auto range = m_itemeffect.equal_range(affect); + auto effect_iter = range.first; + + while (effect_iter != range.second) { + if (effect_iter->second.base2 == subindex) + break; + ++effect_iter; + } + + if (effect_iter == range.second) {// we didn't find one + sEffectCache e{affect, value, subindex}; + m_itemeffect.emplace(affect, e); + return; + } + + // we gotta update + effect_iter->second.base1 = value; +} + +void SpellCache::InsertAltEffect(int affect, int value, int subindex) +{ + auto range = m_alteffect.equal_range(affect); + auto effect_iter = range.first; + + while (effect_iter != range.second) { + if (effect_iter->second.base2 == subindex) + break; + ++effect_iter; + } + + if (effect_iter == range.second) {// we didn't find one + sEffectCache e{affect, value, subindex}; + m_alteffect.emplace(affect, e); + return; + } + + // we gotta update + effect_iter->second.base1 = value; +} + +void SpellCache::InsertSkillAttackProc(int chance, int skill, int spell) +{ + m_skill_attack_proc.push_back({chance, skill, spell}); +} + +const SpellCache::sEffectCache *SpellCache::GetSpellCached(int affect, int subindex) const +{ + auto range = m_spelleffect.equal_range(affect); + auto effect_iter = range.first; + + while (effect_iter != range.second) { + if (effect_iter->second.base2 == subindex) + return &effect_iter->second; + ++effect_iter; + } + + return nullptr; +} + +const SpellCache::sEffectCache *SpellCache::GetItemCached(int affect, int subindex) const +{ + auto range = m_itemeffect.equal_range(affect); + auto effect_iter = range.first; + + while (effect_iter != range.second) { + if (effect_iter->second.base2 == subindex) + return &effect_iter->second; + ++effect_iter; + } + + return nullptr; +} + +const SpellCache::sEffectCache *SpellCache::GetAltCached(int affect, int subindex) const +{ + auto range = m_alteffect.equal_range(affect); + auto effect_iter = range.first; + + while (effect_iter != range.second) { + if (effect_iter->second.base2 == subindex) + return &effect_iter->second; + ++effect_iter; + } + + return nullptr; +} + +void SpellCache::InsertSkillProcAttempt(SpellCache::eSkillProc type, int spell, int chance, int slot) +{ + sSkillProc e; + e.spell = spell; + e.chance = chance; + e.slot = slot; + + switch (type) { + case eSkillProc::AA: + m_skill_proc_attempt.aa.push_back(e); + return; + case eSkillProc::Buff: + m_skill_proc_attempt.buff.push_back(e); + return; + case eSkillProc::Worn: + m_skill_proc_attempt.worn.push_back(e); + return; + } +} + +void SpellCache::InsertSkillProcSuccess(SpellCache::eSkillProc type, int spell, int chance, int slot) +{ + sSkillProc e; + e.spell = spell; + e.chance = chance; + e.slot = slot; + + switch (type) { + case eSkillProc::AA: + m_skill_proc_success.aa.push_back(e); + return; + case eSkillProc::Buff: + m_skill_proc_success.buff.push_back(e); + return; + case eSkillProc::Worn: + m_skill_proc_success.worn.push_back(e); + return; + } +} + +void SpellCache::InsertSkillLimit(SpellCache::eSkillProc type, EQEmu::skills::SkillType skill, bool on_success) +{ + auto &which = on_success ? m_skill_proc_success : m_skill_proc_attempt; + + // we have to assume the last one inserted is correct, otherwise it would be bad data + switch (type) { + case eSkillProc::AA: { + auto it = which.aa.rbegin(); + if (it != which.aa.rend()) + it->skills.insert(skill); + return; + } + case eSkillProc::Buff: { + auto it = which.buff.rbegin(); + if (it != which.buff.rend()) + it->skills.insert(skill); + return; + } + case eSkillProc::Worn: { + auto it = which.worn.rbegin(); + if (it != which.worn.rend()) + it->skills.insert(skill); + return; + } + } +} + +std::vector::const_iterator SpellCache::skill_proc_attempt_begin(SpellCache::eSkillProc type) const +{ + switch (type) { + case eSkillProc::AA: + return m_skill_proc_attempt.aa.cbegin(); + case eSkillProc::Buff: + return m_skill_proc_attempt.buff.cbegin(); + case eSkillProc::Worn: + return m_skill_proc_attempt.worn.cbegin(); + } +} + +std::vector::const_iterator SpellCache::skill_proc_attempt_end(SpellCache::eSkillProc type) const +{ + switch (type) { + case eSkillProc::AA: + return m_skill_proc_attempt.aa.cend(); + case eSkillProc::Buff: + return m_skill_proc_attempt.buff.cend(); + case eSkillProc::Worn: + return m_skill_proc_attempt.worn.cend(); + } +} + +std::vector::const_iterator SpellCache::skill_proc_success_begin(SpellCache::eSkillProc type) const +{ + switch (type) { + case eSkillProc::AA: + return m_skill_proc_success.aa.cbegin(); + case eSkillProc::Buff: + return m_skill_proc_success.buff.cbegin(); + case eSkillProc::Worn: + return m_skill_proc_success.worn.cbegin(); + } +} + +std::vector::const_iterator SpellCache::skill_proc_success_end(SpellCache::eSkillProc type) const +{ + switch (type) { + case eSkillProc::AA: + return m_skill_proc_success.aa.cend(); + case eSkillProc::Buff: + return m_skill_proc_success.buff.cend(); + case eSkillProc::Worn: + return m_skill_proc_success.worn.cend(); + } +} + diff --git a/zone/spell_cache.h b/zone/spell_cache.h new file mode 100644 index 000000000..0f8ab027d --- /dev/null +++ b/zone/spell_cache.h @@ -0,0 +1,144 @@ +#ifndef SPELL_CACHE_H +#define SPELL_CACHE_H + +#include +#include +#include + +#include "../common/skills.h" + +class SpellCache +{ +public: + struct sEffectCache { + int affect; + int base1; + int base2; + }; + + // can't really get away with doing a cache trick with this since you can have multiple + // (see Decapitation) + // This is for SE_SkillAttackProc (SPA 288) only, which is purely AA + struct sSkillAttackProc { + int chance; + int skill; + int spell; + }; + + // enum for skill proc functions + enum class eSkillProc { + Buff, + Worn, + AA + }; + + // SE_SpellOnDeath + struct sDeathProc { + int chance; + int spell; + int slot; + }; + + // this is used for SE_SkillProc and SE_SkillProcSuccess which are matched with SE_LimitToSkill + struct sSkillProc { + int chance; + int spell; + int slot; // used for buff slot when it's a buff + std::set skills; + }; + + // we need to recache these separately + template + struct sProcs { + std::vector buff; + std::vector worn; + std::vector aa; + }; + + SpellCache() : spell_cached(false), item_cached(false), alt_cached(false) {} + ~SpellCache() {} + + void InsertSpellEffect(int affect, int value, int subindex); + void InsertItemEffect(int affect, int value, int subindex); + void InsertAltEffect(int affect, int value, int subindex); + void InsertSkillAttackProc(int chace, int skill, int spell); + + inline void ClearSpellEffect() { m_spelleffect.clear(); m_skill_proc_attempt.buff.clear(); m_skill_proc_success.buff.clear(); m_death_proc.buff.clear(); } + inline void ClearItemEffect() { m_itemeffect.clear(); m_skill_proc_attempt.worn.clear(); m_skill_proc_success.worn.clear(); m_death_proc.worn.clear(); } + inline void ClearAltEffect() { m_alteffect.clear(); m_skill_attack_proc.clear(); m_skill_proc_attempt.aa.clear(); m_skill_proc_success.aa.clear(); m_death_proc.aa.clear(); } + + inline void SetSpellCached(bool v) { spell_cached = v; } + inline void SetItemCached(bool v) { item_cached = v; } + inline void SetAltCached(bool v) { alt_cached = v; } + + inline bool IsSpellCached() const { return spell_cached; } + inline bool IsItemCached() const { return item_cached; } + inline bool IsAltCached() const { return alt_cached; } + inline bool HasSkillAttackProcs() const { return !m_skill_attack_proc.empty(); } + + void InsertSkillProcAttempt(eSkillProc type, int spell, int chance, int slot = -1); + void InsertSkillProcSuccess(eSkillProc type, int spell, int chance, int slot = -1); + void InsertSkillLimit(eSkillProc type, EQEmu::skills::SkillType skill, bool on_success); + inline bool HasSkillProcAttempt() const { return !m_skill_proc_attempt.aa.empty() || !m_skill_proc_attempt.buff.empty() || !m_skill_proc_attempt.worn.empty(); } + inline bool HasSkillProcSuccess() const { return !m_skill_proc_success.aa.empty() || !m_skill_proc_success.buff.empty() || !m_skill_proc_success.worn.empty(); } + + void InsertDeathProc(eSkillProc type, int spell, int chance, int slot = -1); + + const sEffectCache *GetSpellCached(int affect, int subindex = 0) const; + const sEffectCache *GetItemCached(int affect, int subindex = 0) const; + const sEffectCache *GetAltCached(int affect, int subindex = 0) const; + + std::vector::const_iterator skill_attack_proc_begin() const { return m_skill_attack_proc.cbegin(); } + std::vector::const_iterator skill_attack_proc_end() const { return m_skill_attack_proc.cend(); } + + std::vector::const_iterator skill_proc_attempt_begin(eSkillProc type) const; + std::vector::const_iterator skill_proc_attempt_end(eSkillProc type) const; + + std::vector::const_iterator skill_proc_success_begin(eSkillProc type) const; + std::vector::const_iterator skill_proc_success_end(eSkillProc type) const; + + std::vector::const_iterator death_proc_begin(eSkillProc type) const; + std::vector::const_iterator death_proc_end(eSkillProc type) const; + + // inlines for common operations + inline int GetCachedPlayerEffect(int affect, int subindex = 0) const { + auto res = GetSpellCached(affect, subindex); + if (res) + return res->base1; + return 0; + } + + inline int GetCachedItemEffect(int affect, int subindex = 0) const { + auto res = GetItemCached(affect, subindex); + if (res) + return res->base1; + return 0; + } + + inline int GetCachedAltEffect(int affect, int subindex = 0) const { + auto res = GetAltCached(affect, subindex); + if (res) + return res->base1; + return 0; + } + +private: + bool spell_cached; + bool item_cached; + bool alt_cached; + // maybe use unordered_multimap + std::multimap m_spelleffect; + std::multimap m_itemeffect; + std::multimap m_alteffect; + + // this should be fine for how it works with live AAs + // if custom servers want to give someone a million of these ahhh + // we need something better :P + std::vector m_skill_attack_proc; + + sProcs m_skill_proc_attempt; // SE_SkillProc + sProcs m_skill_proc_success; // SE_SkillProcSuccess + sProcs m_death_proc; // SE_SpellOnDeath +}; + +#endif /* !SPELL_CACHE_H */ diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 5ebde5d05..850cd6aad 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -1308,7 +1308,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove #endif if (caster) effect_value = caster->ApplySpellEffectiveness(spell_id, effect_value); - + buffs[buffslot].melee_rune = effect_value; break; } @@ -1800,7 +1800,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove else if (caster) { char level[4]; ConvertArray(effect_value, level); - caster->Message_StringID(MT_SpellFailure, + caster->Message_StringID(MT_SpellFailure, SPELL_LEVEL_REQ, level); } } @@ -2281,7 +2281,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove } case SE_AETaunt: - { + { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "AE Taunt"); #endif @@ -5837,16 +5837,16 @@ bool Mob::TryDivineSave() -If desired, additional spells can be triggered from the AA/item/spell effect, generally a heal. */ - int32 SuccessChance = aabonuses.DivineSaveChance[0] + itembonuses.DivineSaveChance[0] + spellbonuses.DivineSaveChance[0]; + int32 SuccessChance = TotalEffect(SE_DivineSave, 0); // base2 == 0 is chances if (SuccessChance && zone->random.Roll(SuccessChance)) { SetHP(1); int32 EffectsToTry[] = { - aabonuses.DivineSaveChance[1], - itembonuses.DivineSaveChance[1], - spellbonuses.DivineSaveChance[1] + m_spell_cache.GetCachedPlayerEffect(SE_DivineSave, 1), // base2 == 1 is spell + m_spell_cache.GetCachedItemEffect(SE_DivineSave, 1), + m_spell_cache.GetCachedAltEffect(SE_DivineSave, 1) }; //Fade the divine save effect here after saving the old effects off. //That way, if desired, the effect could apply SE_DivineSave again. @@ -6710,24 +6710,24 @@ void Mob::ResourceTap(int32 damage, uint16 spellid) for (int i = 0; i < EFFECT_COUNT; i++) { if (spells[spellid].effectid[i] == SE_ResourceTap) { damage = (damage * spells[spellid].base[i]) / 1000; - + if (damage) { if (spells[spellid].max[i] && (damage > spells[spellid].max[i])) damage = spells[spellid].max[i]; - + if (spells[spellid].base2[i] == 0) { // HP Tap if (damage > 0) HealDamage(damage); else Damage(this, -damage, 0, EQEmu::skills::SkillEvocation, false); } - + if (spells[spellid].base2[i] == 1) // Mana Tap SetMana(GetMana() + damage); - + if (spells[spellid].base2[i] == 2 && IsClient()) // Endurance Tap CastToClient()->SetEndurance(CastToClient()->GetEndurance() + damage); - + } } } @@ -6904,8 +6904,238 @@ void Client::BreakFeignDeathWhenCastOn(bool IsResisted) Message_StringID(MT_SpellFailure,FD_CAST_ON_NO_BREAK); return; } - + SetFeigned(false); Message_StringID(MT_SpellFailure,FD_CAST_ON); } } + +/* This function will recache the buff, item, and AA SE_NegateSpellEffects + * This has to be done before the other effects are cached, since they make use of this data + * This function does not clear the cache, so we will have to do it before we call it + */ +void Mob::RecacheSuppressionSpells() +{ + // first we cache buffs + int buff_count = GetMaxTotalSlots(); + for (int i = buff_count; i < buff_count; ++i) { + if (!IsValidSpell(buffs[i].spellid)) + continue; + const auto &spell = spells[buffs[i].spellid]; + for (int j = 0; j < EFFECT_COUNT; ++j) { + if (spell.effectid[j] != SE_NegateSpellEffect) + continue; + auto base = spell.base[j] == 0 ? SM_All : spell.base[j]; + m_spell_cache.InsertSpellEffect(SE_NegateSpellEffect, m_spell_cache.GetCachedPlayerEffect(SE_NegateSpellEffect, spell.base2[j]) | base, spell.base2[j]); + } + } + m_spell_cache.InsertSpellEffect(SE_NegateSpellEffect, SM_All, SE_NegateSpellEffect); // this will cause it to be ignored in recache + + // now we cache items -- NPC invs are dumb, so work around + RecacheSuppressionItems(); + + m_spell_cache.InsertItemEffect(SE_NegateSpellEffect, SM_All, SE_NegateSpellEffect); // this will cause it to be ignored in recache + // now we cache AAs + for (const auto &aa : aa_ranks) { + auto ability_rank = zone->GetAlternateAdvancementAbilityAndRank(aa.first, aa.second.first); + auto ability = ability_rank.first; + auto rank = ability_rank.second; + + if (!ability || rank->effects.empty()) + continue; + + for (const auto &e : rank->effects) { + if (e.effect_id != SE_NegateSpellEffect) + continue; + + auto base = e.base1 == 0 ? SM_All : e.base1; + m_spell_cache.InsertAltEffect(SE_NegateSpellEffect, m_spell_cache.GetCachedAltEffect(SE_NegateSpellEffect, e.base2) | base, e.base2); + } + } + m_spell_cache.InsertAltEffect(SE_NegateSpellEffect, SM_All, SE_NegateSpellEffect); // this will cause it to be ignored in recache +} + +// NPCs and clients have incompatible inventory systems, so we work around that issue here +void NPC::RecacheSuppressionItems() +{ + // luckily we get to skip augs and shit here! and tributes! + for (int i = 0; i < EQEmu::legacy::EQUIPMENT_SIZE; ++i) { + const auto cur = database.GetItem(equipment[i]); + if (cur->Worn.Effect > 0 && cur->Worn.Type == EQEmu::item::ItemEffectWorn) { + if (!IsValidSpell(cur->Worn.Effect)) + continue; + const auto &spell = spells[cur->Worn.Effect]; + for (int j = 0; j < EFFECT_COUNT; ++j) { + if (spell.effectid[j] != SE_NegateSpellEffect) + continue; + auto base = spell.base[j] == 0 ? SM_All : spell.base[j]; + m_spell_cache.InsertItemEffect(SE_NegateSpellEffect, m_spell_cache.GetCachedItemEffect(SE_NegateSpellEffect, spell.base2[j]) | base, spell.base2[j]); + } + } + } +} + +void Client::RecacheSuppressionItems() +{ + auto apply_effect = [this] (const EQEmu::ItemInstance *inst) { + const auto item = inst->GetItem(); + if (item->Worn.Effect > 0 && item->Worn.Type == EQEmu::item::ItemEffectWorn) { + if (!IsValidSpell(item->Worn.Effect)) + return; + const auto &spell = spells[item->Worn.Effect]; + for (int i = 0; i < EFFECT_COUNT; ++i) { + if (spell.effectid[i] != SE_NegateSpellEffect) + continue; + auto base = spell.base[i] == 0 ? SM_All : spell.base[i]; + m_spell_cache.InsertItemEffect(SE_NegateSpellEffect, m_spell_cache.GetCachedItemEffect(SE_NegateSpellEffect, spell.base2[i]) | base, spell.base2[i]); + } + } + }; + + // we don't do ammo slot here. The client also does not seem to be visiting augs + for (int i = EQEmu::inventory::slotCharm; i < EQEmu::inventory::slotAmmo; ++i) { + const auto inst = m_inv[i]; + if (inst == nullptr || !inst->IsClassCommon() || inst->GetExp() < 0) + continue; + apply_effect(inst); + } + + // powersource ... + if (ClientVersion() >= EQEmu::versions::ClientVersion::SoF) { + const auto inst = m_inv[EQEmu::inventory::slotPowerSource]; + if (inst) + apply_effect(inst); + } + + for (int i = 0; i < EQEmu::legacy::TRIBUTE_SIZE; ++i) { + const auto inst = m_inv[EQEmu::legacy::TRIBUTE_BEGIN + i]; + if (inst == nullptr || !inst->IsClassCommon() || inst->GetExp() < 0) + continue; + apply_effect(inst); + } +} + +int Mob::TotalEffect(int spaID, int subindex, bool bIncludeItems, bool bIncludeAA, bool bIncludeBuffs) +{ + // pure melee don't have mana regen + if ((spaID == SE_CurrentMana || spaID == SE_ManaRegen_v2) && + (GetClass() == MONK || GetClass() == ROGUE || GetClass() == WARRIOR || GetClass() == BERSERKER)) + return 0; + int total = 0, item_total = 0, aa_total = 0; + + // invis / see invis has special capping + switch (spaID) { + case SE_Invisibility: + case SE_SeeInvis: + case SE_InvisVsUndead: + case SE_InvisVsAnimals: + case SE_Invisibility2: + case SE_InvisVsUndead2: + case SE_ImprovedInvisAnimals: + if (bIncludeBuffs) + total = GetCachedPlayerEffect(spaID); + if (bIncludeItems) + item_total = GetCachedItemEffect(spaID); + if (bIncludeAA) + aa_total = GetCachedAltEffect(spaID); + return std::min(3000, std::max({total, item_total, aa_total})); + default: + break; + } + + // SE_MovementSpeed is only items or buffs (this is client behavior so useless for custom) + // and they don't stack, take best or worst if one is negative + if (spaID == SE_MovementSpeed) { + if (bIncludeBuffs) + total = GetCachedPlayerEffect(spaID); + if (bIncludeItems) + item_total = GetCachedItemEffect(spaID); + if (total < 0 || item_total < 0) + return std::min(total, item_total); + return std::max(total, item_total); + } + + // bunch of special behavior here! + if (spaID == SE_AttackSpeed) { + int haste = GetCachedPlayerEffect(SE_AttackSpeed); + int haste_v2 = GetCachedPlayerEffect(SE_AttackSpeed2); + int overhaste = GetCachedPlayerEffect(SE_AttackSpeed3); + int slow = GetCachedPlayerEffect(SE_AttackSpeed4); + int item_haste = 0; /* bIncludeItems ? item_bonuses.Haste : 0; //or something */ + + if (haste) { // we have a non-zero haste + if (haste < 100) { // we're slowed! special case for SE_AttackSpeed4 + int temp = 100 - slow; + if (haste > temp) // SE_AttackSpeed4 is hurting us more, so use that + haste = temp; + } else { // SE_AttackSpeed4 will lower our SE_AttackSpeed + haste -= slow; + } + } else { + haste = 100 - slow; + } + if (haste <= 0) + haste = 1; + // if we have no attack speed buffs, haste will be 100, if it's less than 100 we abort adn we are done :P + if (haste >= 100) { + total = haste + item_haste; + if (overhaste > 0) { + if (overhaste > 100) + overhaste -= 100; + if (GetLevel() <= 50) + overhaste = std::min(10, overhaste); + else + overhaste = std::min(25, overhaste); + } + + // ItemHaste function returns value + 100 + if (item_haste >= 100) + total -= 100; + + if (haste_v2 > 100 && GetLevel() > 49) + total += std::min(haste_v2, 110) - 100; + + int haste_cap = 0; + if (IsClient()) { + if (GetLevel() > 59) + haste_cap = 200; + else if (GetLevel() > 50) + haste_cap = 185; + else + haste_cap = GetLevel() + 125; + } else { // Pets/NPC + if (IsPetOwnerClient()) { // client checks the bSummoned flag, which seems true for PC summoned pets/swarm pets + haste_cap = GetLevel() + 110; + Mob *owner = nullptr; + if (IsPet()) + owner = GetOwner(); + else if (IsNPC() && CastToNPC()->GetSwarmOwner()) + owner = entity_list.GetMobID(CastToNPC()->GetSwarmOwner()); + if (owner) + haste_cap += std::max(0, owner->GetLevel() - 39) + std::max(0, owner->GetLevel() - 60); + } else { + haste_cap = 250; // normal NPCs + } + } + + haste_cap += overhaste; + + if (total) + total += overhaste; + else if (overhaste) + total = overhaste + 100; + + return std::min(total, haste_cap); + } + } + + if (bIncludeBuffs) + total = GetCachedPlayerEffect(spaID, subindex); + if (bIncludeAA) + total += GetCachedAltEffect(spaID, subindex); + if (bIncludeItems) // the client is using 0 for the mana regens in items, but subindex for others ... + total += + GetCachedItemEffect(spaID, (spaID == SE_CurrentMana || spaID == SE_ManaRegen_v2) ? 0 : subindex); + return total; +} + diff --git a/zone/trading.cpp b/zone/trading.cpp index 70d5c85c1..00e4e2fcc 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -163,7 +163,7 @@ void Trade::SendItemData(const EQEmu::ItemInstance* inst, int16 dest_slot_id) { if (inst == nullptr) return; - + // @merth: This needs to be redone with new item classes Mob* mob = With(); if (!mob->IsClient()) @@ -886,11 +886,11 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st const EQEmu::ItemData* item = inst->GetItem(); if(item && quest_npc == false) { - bool isPetAndCanHaveNoDrop = (RuleB(Pets, CanTakeNoDrop) && - _CLIENTPET(tradingWith) && + bool isPetAndCanHaveNoDrop = (RuleB(Pets, CanTakeNoDrop) && + _CLIENTPET(tradingWith) && tradingWith->GetPetType()<=petOther); // if it was not a NO DROP or Attuned item (or if a GM is trading), let the NPC have it - if(GetGM() || (inst->IsAttuned() == false && + if(GetGM() || (inst->IsAttuned() == false && (item->NoDrop != 0 || isPetAndCanHaveNoDrop))) { // pets need to look inside bags and try to equip items found there if (item->IsClassBag() && item->BagSlots > 0) { @@ -944,7 +944,7 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st parse->AddVar(temp1, temp2); snprintf(temp1, 100, "platinum.%d", tradingWith->GetNPCTypeID()); snprintf(temp2, 100, "%u", trade->pp); - parse->AddVar(temp1, temp2); + parse->AddVar(temp1, temp2); if(tradingWith->GetAppearance() != eaDead) { tradingWith->FaceTarget(this); @@ -1434,7 +1434,7 @@ void Client::FindAndNukeTraderItem(int32 SerialNumber, uint16 Quantity, Client* int16 Charges=0; uint16 SlotID = FindTraderItem(SerialNumber, Quantity); - + if(SlotID > 0) { item = this->GetInv().GetItem(SlotID);