diff --git a/changelog.txt b/changelog.txt index 09cd52d88..30cdf9ba7 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,16 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 03/12/2015 == +Akkadius: [eqemu_update.pl V7] Add Option 9) LUA Modules - Download latest LUA Modules (Required for Lua) + +== 03/11/2015 == +Akkadius: [eqemu_update.pl] Add Option 7) Plugins - Download latest Perl plugins +Akkadius: [eqemu_update.pl] Add Option 8) Quests - Download latest PEQ quests and stage updates +Akkadius: [eqemu_update.pl] Set version 5 of script + +== 03/10/2015 == +Akkadius: [eqemu_update.pl] Add Option 6) Download Latest map and water files + == 03/04/2015 == Akkadius: Fix Spell Book Deletion diff --git a/common/database_conversions.cpp b/common/database_conversions.cpp index f6c43687f..a96f51967 100644 --- a/common/database_conversions.cpp +++ b/common/database_conversions.cpp @@ -494,7 +494,7 @@ bool Database::CheckDatabaseConversions() { /* Check for a new version of this script, the arg passed would have to be higher than the copy they have downloaded locally and they will re fetch */ - system("perl eqemu_update.pl V 2"); + system("perl eqemu_update.pl V 7"); /* Run Automatic Database Upgrade Script */ system("perl eqemu_update.pl ran_from_world"); diff --git a/common/spdat.h b/common/spdat.h index b70d708f0..487b5d00a 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -38,6 +38,7 @@ #define MAX_RESISTABLE_EFFECTS 12 // Number of effects that are typcially checked agianst resists. #define MaxLimitInclude 16 //Number(x 0.5) of focus Limiters that have inclusive checks used when calcing focus effects #define MAX_SKILL_PROCS 4 //Number of spells to check skill procs from. (This is arbitrary) [Single spell can have multiple proc checks] +#define MAX_SYMPATHETIC_PROCS 10 // Number of sympathetic procs a client can have (This is arbitrary) const int Z_AGGRO=10; diff --git a/utils/scripts/eqemu_update.pl b/utils/scripts/eqemu_update.pl index 04c5367b3..3b1a0272c 100644 --- a/utils/scripts/eqemu_update.pl +++ b/utils/scripts/eqemu_update.pl @@ -9,12 +9,20 @@ $menu_displayed = 0; use Config; +use File::Copy qw(copy); +use POSIX qw(strftime); +use File::Path; +use File::Find; +use URI::Escape; + +$time_stamp = strftime('%m-%d-%Y', gmtime()); + $console_output .= " Operating System is: $Config{osname}\n"; if($Config{osname}=~/linux/i){ $OS = "Linux"; } if($Config{osname}=~/Win|MS/i){ $OS = "Windows"; } #::: If current version is less than what world is reporting, then download a new one... -$current_version = 2; +$current_version = 7; if($ARGV[0] eq "V"){ if($ARGV[1] > $current_version){ @@ -36,8 +44,13 @@ print "Perl Version is " . $perl_version . "\n"; if($perl_version > 5.12){ no warnings 'uninitialized'; } no warnings; +($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(); + +#::: Cleanup staged folder... +rmtree("updates_staged/"); + my $confile = "eqemu_config.xml"; #default -open(F, "<$confile") or die "Unable to open config: $confile\n"; +open(F, "<$confile"); my $indb = 0; while() { s/\r//g; @@ -97,12 +110,12 @@ if($path eq ""){ mkdir('db_update'); #::: Check if db_version table exists... -if(trim(GetMySQLResult("SHOW COLUMNS FROM db_version LIKE 'Revision'")) ne ""){ +if(trim(GetMySQLResult("SHOW COLUMNS FROM db_version LIKE 'Revision'")) ne "" && $db){ print GetMySQLResult("DROP TABLE db_version"); print "Old db_version table present, dropping...\n\n"; } -if(GetMySQLResult("SHOW TABLES LIKE 'db_version'") eq ""){ +if(GetMySQLResult("SHOW TABLES LIKE 'db_version'") eq "" && $db){ print GetMySQLResult(" CREATE TABLE db_version ( version int(11) DEFAULT '0' @@ -123,24 +136,24 @@ if($bin_db_ver == $local_db_ver && $ARGV[0] eq "ran_from_start"){ exit; } else{ - print $console_output; + print $console_output if $db; } +if($db){ + print " Binary Database Version: (" . $bin_db_ver . ")\n"; + print " Local Database Version: (" . $local_db_ver . ")\n\n"; -print " Binary Database Version: (" . $bin_db_ver . ")\n"; -print " Local Database Version: (" . $local_db_ver . ")\n\n"; + #::: If World ran this script, and our version is up to date, continue... + if($bin_db_ver <= $local_db_ver && $ARGV[0] eq "ran_from_world"){ + print " Database up to Date: Continuing World Bootup...\n"; + print "============================================================\n"; + exit; + } -#::: If World ran this script, and our version is up to date, continue... -if($bin_db_ver <= $local_db_ver && $ARGV[0] eq "ran_from_world"){ - print " Database up to Date: Continuing World Bootup...\n"; - print "============================================================\n"; - exit; + print "Retrieving latest database manifest...\n"; + GetRemoteFile("https://raw.githubusercontent.com/EQEmu/Server/master/utils/sql/db_update_manifest.txt", "db_update/db_update_manifest.txt"); } -print "Retrieving latest database manifest...\n"; -GetRemoteFile("https://raw.githubusercontent.com/EQEmu/Server/master/utils/sql/db_update_manifest.txt", "db_update/db_update_manifest.txt"); -# GetRemoteFile("https://dl.dropboxusercontent.com/u/50023467/dl/db_update_manifest.txt", "db_update/db_update_manifest.txt"); - if($local_db_ver < $bin_db_ver && $ARGV[0] eq "ran_from_world"){ print "You have missing database updates, type 1 or 2 to backup your database before running them as recommended...\n\n"; #::: Display Menu @@ -152,6 +165,10 @@ else{ ShowMenuPrompt(); } +sub UpdateSelf{ + GetRemoteFile("https://raw.githubusercontent.com/EQEmu/Server/master/utils/scripts/eqemu_update.pl", "eqemu_update.pl"); + die "Rerun eqemu_update.pl"; +} sub ShowMenuPrompt { my %dispatch = ( @@ -160,6 +177,11 @@ sub ShowMenuPrompt { 3 => \&Run_Database_Check, 4 => \&AA_Fetch, 5 => \&OpCodes_Fetch, + 6 => \&MapFiles_Fetch, + 7 => \&Plugins_Fetch, + 8 => \&QuestFiles_Fetch, + 9 => \&LUA_Modules_Fetch, + 20 => \&UpdateSelf, 0 => \&Exit, ); @@ -198,25 +220,30 @@ sub ShowMenuPrompt { } sub MenuOptions { + if(@total_updates){ $option[3] = "Run pending REQUIRED updates... (" . scalar (@total_updates) . ")"; } else{ - $option[3] = "Check for pending REQUIRED Database updates - Stages updates for automatic upgrade..."; + $option[3] = "Check and stage pending REQUIRED Database updates"; } return <new; - $ua->timeout(10); - $ua->env_proxy; - my $response = $ua->get($URL); - - if ($response->is_success){ - open (FILE, '> ' . $Dest_File . ''); - print FILE $response->decoded_content; - close (FILE); - print " URL: " . $URL . "\n"; - print " Saved: " . $Dest_File . " \n"; + #::: For non-text type requests... + if($content_type == 1){ + use LWP::Simple qw(getstore); + if(!getstore($URL, $Dest_File)){ + print "Error, no connection or failed request...\n\n"; + } + else{ + print " o URL: (" . $URL . ")\n"; + print " o Saved: (" . $Dest_File . ") \n"; + } } - else { - print "Error, no connection to the internet...\n\n"; - die $response->status_line; + else{ + require LWP::UserAgent; + my $ua = LWP::UserAgent->new; + $ua->timeout(10); + $ua->env_proxy; + my $response = $ua->get($URL); + + if ($response->is_success){ + open (FILE, '> ' . $Dest_File . ''); + print FILE $response->decoded_content; + close (FILE); + print " o URL: (" . $URL . ")\n"; + print " o Saved: (" . $Dest_File . ") \n"; + } + else { + print "Error, no connection or failed request...\n\n"; + } } } if($OS eq "Linux"){ #::: wget -O db_update/db_update_manifest.txt https://raw.githubusercontent.com/EQEmu/Server/master/utils/sql/db_update_manifest.txt $wget = `wget --no-check-certificate --quiet -O $Dest_File $URL`; - print " URL: " . $URL . "\n"; - print " Saved: " . $Dest_File . " \n"; + print " o URL: (" . $URL . ")\n"; + print " o Saved: (" . $Dest_File . ") \n"; if($wget=~/unable to resolve/i){ - print "Error, no connection to the internet...\n\n"; - die; + print "Error, no connection or failed request...\n\n"; + #die; } } } @@ -309,6 +371,11 @@ sub trim { #::: Fetch Latest PEQ AA's sub AA_Fetch{ + if(!$db){ + print "No database present, check your eqemu_config.xml for proper MySQL/MariaDB configuration...\n"; + return; + } + print "Pulling down PEQ AA Tables...\n"; GetRemoteFile("https://raw.githubusercontent.com/EQEmu/Server/master/utils/sql/peq_aa_tables.sql", "db_update/peq_aa_tables.sql"); print "\n\nInstalling AA Tables...\n"; @@ -341,13 +408,292 @@ sub OpCodes_Fetch{ print "\nDownloading (" . $opcodes{$loop}[0] . ") File: '" . $file_name . "'...\n\n"; GetRemoteFile($opcodes{$loop}[1], $file_name); - $loop++; + $loop++; } print "\nDone...\n\n"; } +sub CopyFile{ + $l_source_file = $_[0]; + $l_dest_file = $_[1]; + if($l_dest_file=~/\//i){ + my @dir_path = split('/', $l_dest_file); + $build_path = ""; + $di = 0; + while($dir_path[$di]){ + $build_path .= $dir_path[$di] . "/"; + #::: If path does not exist, create the directory... + if (!-d $build_path) { + mkdir($build_path); + } + if(!$dir_path[$di + 2] && $dir_path[$di + 1]){ + # print $actual_path . "\n"; + $actual_path = $build_path; + last; + } + $di++; + } + } + copy $l_source_file, $l_dest_file; +} + +sub MapFiles_Fetch{ + print "\n --- Fetching Latest Maps --- \n"; + + GetRemoteFile("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; + while (){ + chomp; + $o = $_; + @manifest_map_data = split(',', $o); + if($manifest_map_data[0] ne ""){ + $maps_manifest[$i] = [$manifest_map_data[0], $manifest_map_data[1]]; + $i++; + } + } + + #::: Download + $fc = 0; + for($m = 0; $m <= $i; $m++){ + my $file_existing = $maps_manifest[$m][0]; + my $file_existing_size = (stat $file_existing)[7]; + if($file_existing_size != $maps_manifest[$m][1]){ + print "Updating: '" . $maps_manifest[$m][0] . "'\n"; + GetRemoteFile("https://raw.githubusercontent.com/Akkadius/EQEmuMaps/master/" . $maps_manifest[$m][0], $maps_manifest[$m][0], 1); + $fc++; + } + } + + if($fc == 0){ + print "\nNo Map Updates found... \n\n"; + } +} + +sub QuestFiles_Fetch{ + if (!-e "updates_staged/Quests-Plugins-master/quests/") { + print "\n --- Fetching Latest Quests --- \n"; + GetRemoteFile("https://github.com/EQEmu/Quests-Plugins/archive/master.zip", "updates_staged/Quests-Plugins-master.zip", 1); + print "\nFetched latest quests...\n"; + mkdir('updates_staged'); + UnZip('updates_staged/Quests-Plugins-master.zip', 'updates_staged/'); + } + + $fc = 0; + use File::Find; + use File::Compare; + + my @files; + my $start_dir = "updates_staged/Quests-Plugins-master/quests/"; + find( + sub { push @files, $File::Find::name unless -d; }, + $start_dir + ); + for my $file (@files) { + if($file=~/\.pl|\.lua|\.ext/i){ + $staged_file = $file; + $dest_file = $file; + $dest_file =~s/updates_staged\/Quests-Plugins-master\///g; + + if (!-e $dest_file) { + CopyFile($staged_file, $dest_file); + print "Installing :: '" . $dest_file . "'\n"; + $fc++; + } + else{ + $diff = Diff($dest_file, $staged_file); + if($diff ne ""){ + $backup_dest = "updates_backups/" . $time_stamp . "/" . $dest_file; + + print $diff . "\n"; + print "\nFile Different :: '" . $dest_file . "'\n"; + print "\nDo you wish to update this Quest? '" . $dest_file . "' [Yes (Enter) - No (N)] \nA backup will be found in '" . $backup_dest . "'\n"; + my $input = ; + if($input=~/N/i){} + else{ + #::: Make a backup + CopyFile($dest_file, $backup_dest); + #::: Copy staged to running + copy($staged_file, $dest_file); + print "Installing :: '" . $dest_file . "'\n\n"; + } + $fc++; + } + } + } + } + + if($fc == 0){ + print "\nNo Quest Updates found... \n\n"; + } +} + +sub LUA_Modules_Fetch{ + if (!-e "updates_staged/Quests-Plugins-master/quests/lua_modules/") { + print "\n --- Fetching Latest LUA Modules --- \n"; + GetRemoteFile("https://github.com/EQEmu/Quests-Plugins/archive/master.zip", "updates_staged/Quests-Plugins-master.zip", 1); + print "\nFetched latest LUA Modules...\n"; + UnZip('updates_staged/Quests-Plugins-master.zip', 'updates_staged/'); + } + + $fc = 0; + use File::Find; + use File::Compare; + + my @files; + my $start_dir = "updates_staged/Quests-Plugins-master/quests/lua_modules/"; + find( + sub { push @files, $File::Find::name unless -d; }, + $start_dir + ); + for my $file (@files) { + if($file=~/\.pl|\.lua|\.ext/i){ + $staged_file = $file; + $dest_file = $file; + $dest_file =~s/updates_staged\/Quests-Plugins-master\/quests\///g; + + if (!-e $dest_file) { + CopyFile($staged_file, $dest_file); + print "Installing :: '" . $dest_file . "'\n"; + $fc++; + } + else{ + $diff = Diff($dest_file, $staged_file); + if($diff ne ""){ + $backup_dest = "updates_backups/" . $time_stamp . "/" . $dest_file; + print $diff . "\n"; + print "\nFile Different :: '" . $dest_file . "'\n"; + print "\nDo you wish to update this LUA Module? '" . $dest_file . "' [Yes (Enter) - No (N)] \nA backup will be found in '" . $backup_dest . "'\n"; + my $input = ; + if($input=~/N/i){} + else{ + #::: Make a backup + CopyFile($dest_file, $backup_dest); + #::: Copy staged to running + copy($staged_file, $dest_file); + print "Installing :: '" . $dest_file . "'\n\n"; + } + $fc++; + } + } + } + } + + if($fc == 0){ + print "\nNo LUA Modules Updates found... \n\n"; + } +} + +sub Plugins_Fetch{ + if (!-e "updates_staged/Quests-Plugins-master/plugins/") { + print "\n --- Fetching Latest Plugins --- \n"; + GetRemoteFile("https://github.com/EQEmu/Quests-Plugins/archive/master.zip", "updates_staged/Quests-Plugins-master.zip", 1); + print "\nFetched latest plugins...\n"; + UnZip('updates_staged/Quests-Plugins-master.zip', 'updates_staged/'); + } + + $fc = 0; + use File::Find; + use File::Compare; + + my @files; + my $start_dir = "updates_staged/Quests-Plugins-master/plugins/"; + find( + sub { push @files, $File::Find::name unless -d; }, + $start_dir + ); + for my $file (@files) { + if($file=~/\.pl|\.lua|\.ext/i){ + $staged_file = $file; + $dest_file = $file; + $dest_file =~s/updates_staged\/Quests-Plugins-master\///g; + + if (!-e $dest_file) { + CopyFile($staged_file, $dest_file); + print "Installing :: '" . $dest_file . "'\n"; + $fc++; + } + else{ + $diff = Diff($dest_file, $staged_file); + if($diff ne ""){ + $backup_dest = "updates_backups/" . $time_stamp . "/" . $dest_file; + print $diff . "\n"; + print "\nFile Different :: '" . $dest_file . "'\n"; + print "\nDo you wish to update this Plugin? '" . $dest_file . "' [Yes (Enter) - No (N)] \nA backup will be found in '" . $backup_dest . "'\n"; + my $input = ; + if($input=~/N/i){} + else{ + #::: Make a backup + CopyFile($dest_file, $backup_dest); + #::: Copy staged to running + copy($staged_file, $dest_file); + print "Installing :: '" . $dest_file . "'\n\n"; + } + $fc++; + } + } + } + } + + if($fc == 0){ + print "\nNo Plugin Updates found... \n\n"; + } +} + +sub Diff{ + $file_1 = $_[0]; + $file_2 = $_[1]; + if($OS eq "Windows"){ + eval "use Text::Diff"; + $diff = diff($file_1, $file_2, { STYLE => "Unified" }); + return $diff; + } + if($OS eq "Linux"){ + # print 'diff -u "$file_1" "$file_2"' . "\n"; + return `diff -u "$file_1" "$file_2"`; + } +} + +sub UnZip{ + $archive_to_unzip = $_[0]; + $dest_folder = $_[1]; + + 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 ) { + die 'read error'; + } + print "Extracting...\n"; + $zip->extractTree('', $dest_folder); + } + if($OS eq "Linux"){ + print `unzip -o "$archive_to_unzip" -d "$dest_folder"`; + } +} + +sub AreFileSizesDifferent{ + $file_1 = $_[0]; + $file_2 = $_[1]; + my $file_1 = (stat $file_1)[7]; + my $file_2 = (stat $file_2)[7]; + # print $file_1 . " :: " . $file_2 . "\n"; + if($file_1 != $file_2){ + return 1; + } + return; +} + #::: Responsible for Database Upgrade Routines sub Run_Database_Check{ + + if(!$db){ + print "No database present, check your eqemu_config.xml for proper MySQL/MariaDB configuration...\n"; + return; + } + #::: Run 2 - Running pending updates... if(defined(@total_updates)){ @total_updates = sort @total_updates; diff --git a/zone/attack.cpp b/zone/attack.cpp index 95b408443..e0a362704 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -3650,6 +3650,7 @@ void Mob::CommonDamage(Mob* attacker, int32 &damage, const uint16 spell_id, cons (frontal_stun_resist && zone->random.Roll(frontal_stun_resist))) && !attacker->BehindMob(this, attacker->GetX(), attacker->GetY())) { Log.Out(Logs::Detail, Logs::Combat, "Frontal stun resisted. %d chance.", frontal_stun_resist); + } else { // Normal stun resist check. if (stun_resist && zone->random.Roll(stun_resist)) { @@ -3994,7 +3995,7 @@ void Mob::TryWeaponProc(const ItemInst *inst, const ItemData *weapon, Mob *on, u // We can proc once here, either weapon or one aug bool proced = false; // silly bool to prevent augs from going if weapon does skillinuse = GetSkillByItemType(weapon->ItemType); - if (weapon->Proc.Type == ET_CombatProc) { + if (weapon->Proc.Type == ET_CombatProc && IsValidSpell(weapon->Proc.Effect)) { float WPC = ProcChance * (100.0f + // Proc chance for this weapon static_cast(weapon->ProcRate)) / 100.0f; if (zone->random.Roll(WPC)) { // 255 dex = 0.084 chance of proc. No idea what this number should be really. @@ -4032,7 +4033,7 @@ void Mob::TryWeaponProc(const ItemInst *inst, const ItemData *weapon, Mob *on, u if (!aug) continue; - if (aug->Proc.Type == ET_CombatProc) { + if (aug->Proc.Type == ET_CombatProc && IsValidSpell(aug->Proc.Effect)) { float APC = ProcChance * (100.0f + // Proc chance for this aug static_cast(aug->ProcRate)) / 100.0f; if (zone->random.Roll(APC)) { diff --git a/zone/client.h b/zone/client.h index a7e0cf296..0b188dd64 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1291,7 +1291,7 @@ protected: bool client_data_loaded; int16 GetFocusEffect(focusType type, uint16 spell_id); - int16 GetSympatheticFocusEffect(focusType type, uint16 spell_id); + uint16 GetSympatheticFocusEffect(focusType type, uint16 spell_id); Mob* bind_sight_target; diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index c68b9984b..6ec6b1754 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -2607,7 +2607,7 @@ void Client::Handle_OP_AltCurrencyReclaim(const EQApplicationPacket *app) ++iter; } - if (item_id == 0) { + if (item_id == 0 || reclaim->count == 0) { return; } @@ -13319,6 +13319,10 @@ void Client::Handle_OP_Trader(const EQApplicationPacket *app) if (database.GetItem(gis->Items[i])) { database.SaveTraderItem(this->CharacterID(), gis->Items[i], gis->SerialNumber[i], gis->Charges[i], ints->ItemCost[i], i); + + auto inst = FindTraderItemBySerialNumber(gis->SerialNumber[i]); + if(inst) + inst->SetPrice(ints->ItemCost[i]); } else { //return; //sony doesnt memset so assume done on first bad item diff --git a/zone/embparser_api.cpp b/zone/embparser_api.cpp index 8b49f01b2..1729978b0 100644 --- a/zone/embparser_api.cpp +++ b/zone/embparser_api.cpp @@ -3429,6 +3429,16 @@ XS(XS__clear_npctype_cache) XSRETURN_EMPTY; } +XS(XS__reloadzonestaticdata); +XS(XS__reloadzonestaticdata) +{ + dXSARGS; + + quest_manager.ReloadZoneStaticData(); + + XSRETURN_EMPTY; +} + XS(XS__qs_send_query); XS(XS__qs_send_query) { @@ -3686,6 +3696,7 @@ EXTERN_C XS(boot_quest) newXS(strcpy(buf, "qs_send_query"), XS__qs_send_query, file); newXS(strcpy(buf, "rain"), XS__rain, file); newXS(strcpy(buf, "rebind"), XS__rebind, file); + newXS(strcpy(buf, "reloadzonestaticdata"), XS__reloadzonestaticdata, file); newXS(strcpy(buf, "removetitle"), XS__removetitle, file); newXS(strcpy(buf, "repopzone"), XS__repopzone, file); newXS(strcpy(buf, "resettaskactivity"), XS__resettaskactivity, file); diff --git a/zone/lua_general.cpp b/zone/lua_general.cpp index 50afdb6e9..59a5d3740 100644 --- a/zone/lua_general.cpp +++ b/zone/lua_general.cpp @@ -1242,6 +1242,10 @@ void lua_clear_npctype_cache(int npctype_id) { quest_manager.ClearNPCTypeCache(npctype_id); } +void lua_reloadzonestaticdata() { + quest_manager.ReloadZoneStaticData(); +} + double lua_clock() { timeval read_time; gettimeofday(&read_time, nullptr); @@ -1592,6 +1596,7 @@ luabind::scope lua_register_general() { luabind::def("enable_recipe", &lua_enable_recipe), luabind::def("disable_recipe", &lua_disable_recipe), luabind::def("clear_npctype_cache", &lua_clear_npctype_cache), + luabind::def("reloadzonestaticdata", &lua_reloadzonestaticdata), luabind::def("clock", &lua_clock), luabind::def("create_npc", &lua_create_npc), luabind::def("debug", (void(*)(std::string))&lua_debug), diff --git a/zone/mob.cpp b/zone/mob.cpp index 4793e7a66..ef77c7912 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -3616,36 +3616,41 @@ bool Mob::TryFadeEffect(int slot) void Mob::TrySympatheticProc(Mob *target, uint32 spell_id) { - if(target == nullptr || !IsValidSpell(spell_id)) + if(target == nullptr || !IsValidSpell(spell_id) || !IsClient()) return; - int focus_spell = CastToClient()->GetSympatheticFocusEffect(focusSympatheticProc,spell_id); + uint16 focus_spell = CastToClient()->GetSympatheticFocusEffect(focusSympatheticProc,spell_id); - if(IsValidSpell(focus_spell)){ - int focus_trigger = spells[focus_spell].base2[0]; - // For beneficial spells, if the triggered spell is also beneficial then proc it on the target - // if the triggered spell is detrimental, then it will trigger on the caster(ie cursed items) - if(IsBeneficialSpell(spell_id)) - { - if(IsBeneficialSpell(focus_trigger)) - SpellFinished(focus_trigger, target); + if(!IsValidSpell(focus_spell)) + return; - else - SpellFinished(focus_trigger, this, 10, 0, -1, spells[focus_trigger].ResistDiff); - } - // For detrimental spells, if the triggered spell is beneficial, then it will land on the caster - // if the triggered spell is also detrimental, then it will land on the target - else - { - if(IsBeneficialSpell(focus_trigger)) - SpellFinished(focus_trigger, this); + uint16 focus_trigger = GetSympatheticSpellProcID(focus_spell); - else - SpellFinished(focus_trigger, target, 10, 0, -1, spells[focus_trigger].ResistDiff); - } + if(!IsValidSpell(focus_trigger)) + return; - CheckNumHitsRemaining(NumHit::MatchingSpells, -1, focus_spell); - } + // For beneficial spells, if the triggered spell is also beneficial then proc it on the target + // if the triggered spell is detrimental, then it will trigger on the caster(ie cursed items) + if(IsBeneficialSpell(spell_id)) + { + if(IsBeneficialSpell(focus_trigger)) + SpellFinished(focus_trigger, target); + + else + SpellFinished(focus_trigger, this, 10, 0, -1, spells[focus_trigger].ResistDiff); + } + // For detrimental spells, if the triggered spell is beneficial, then it will land on the caster + // if the triggered spell is also detrimental, then it will land on the target + else + { + if(IsBeneficialSpell(focus_trigger)) + SpellFinished(focus_trigger, this); + + else + SpellFinished(focus_trigger, target, 10, 0, -1, spells[focus_trigger].ResistDiff); + } + + CheckNumHitsRemaining(NumHit::MatchingSpells, -1, focus_spell); } int32 Mob::GetItemStat(uint32 itemid, const char *identifier) diff --git a/zone/mob.h b/zone/mob.h index 856a519c8..156231d09 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -1067,6 +1067,8 @@ protected: void PrintRoute(); virtual float GetSympatheticProcChances(uint16 spell_id, int16 ProcRateMod, int32 ItemProcRate = 0); + int16 GetSympatheticSpellProcRate(uint16 spell_id); + uint16 GetSympatheticSpellProcID(uint16 spell_id); enum {MAX_PROCS = 4}; tProc PermaProcs[MAX_PROCS]; diff --git a/zone/npc.cpp b/zone/npc.cpp index 269b36b8d..a4b4b3955 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -1839,7 +1839,7 @@ void NPC::PetOnSpawn(NewSpawn_Struct* ns) if(swarmOwner->IsClient()) { SetPetOwnerClient(true); //Simple flag to determine if pet belongs to a client - SetAllowBeneficial(1);//Allow temp pets to receive buffs and heals if owner is client. + SetAllowBeneficial(true);//Allow temp pets to receive buffs and heals if owner is client. //This will allow CLIENT swarm pets NOT to be targeted with F8. ns->spawn.targetable_with_hotkey = 0; no_target_hotkey = 1; @@ -2258,8 +2258,6 @@ bool NPC::CanTalk() 0,0,420,0,0,0,0,425,0,0,0,0,0,0,0,433,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,458,0,0,0,0,0,0,0,0,467,0,0,470,0,0,473}; - int talk_check = TalkRace[GetRace() - 1]; - if (TalkRace[GetRace() - 1] > 0) return true; diff --git a/zone/pathing.cpp b/zone/pathing.cpp index e01cb6916..8224f3692 100644 --- a/zone/pathing.cpp +++ b/zone/pathing.cpp @@ -1478,6 +1478,11 @@ int32 PathManager::AddNode(float x, float y, float z, float best_z, int32 reques { for(uint32 i = 0; i < Head.PathNodeCount; ++i) { + if(PathNodes[i].id - new_id > 1) { + new_id = PathNodes[i].id - 1; + break; + } + if(PathNodes[i].id > new_id) new_id = PathNodes[i].id; } diff --git a/zone/perl_npc.cpp b/zone/perl_npc.cpp index febee6941..0c9cec797 100644 --- a/zone/perl_npc.cpp +++ b/zone/perl_npc.cpp @@ -2530,6 +2530,32 @@ XS(XS_NPC_ClearLastName) XSRETURN_EMPTY; } +XS(XS_NPC_GetCombatState); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetCombatState) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetCombatState(THIS)"); + { + NPC * THIS; + bool RETVAL; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + RETVAL = THIS->GetCombatEvent(); + ST(0) = boolSV(RETVAL); + sv_2mortal(ST(0)); + } + XSRETURN(1); +} + #ifdef __cplusplus extern "C" #endif @@ -2643,6 +2669,7 @@ XS(boot_NPC) newXSproto(strcpy(buf, "RemoveDefensiveProc"), XS_NPC_RemoveDefensiveProc, file, "$$"); newXSproto(strcpy(buf, "ChangeLastName"), XS_NPC_ChangeLastName, file, "$:$"); newXSproto(strcpy(buf, "ClearLastName"), XS_NPC_ClearLastName, file, "$"); + newXSproto(strcpy(buf, "GetCombatState"), XS_NPC_GetCombatState, file, "$"); XSRETURN_YES; } diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index b129d00cc..b64d9e767 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -2927,6 +2927,13 @@ void QuestManager::ClearNPCTypeCache(int npctype_id) { } } +void QuestManager::ReloadZoneStaticData() +{ + if (zone) { + zone->ReloadStaticData(); + } +} + Client *QuestManager::GetInitiator() const { if(!quests_running_.empty()) { running_quest e = quests_running_.top(); diff --git a/zone/questmgr.h b/zone/questmgr.h index 7051bbb2b..d2f018b8c 100644 --- a/zone/questmgr.h +++ b/zone/questmgr.h @@ -249,6 +249,7 @@ public: bool EnableRecipe(uint32 recipe_id); bool DisableRecipe(uint32 recipe_id); void ClearNPCTypeCache(int npctype_id); + void ReloadZoneStaticData(); Client *GetInitiator() const; NPC *GetNPC() const; diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index c60923346..bfc662730 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -5177,13 +5177,12 @@ int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo return(value*lvlModifier/100); } -int16 Client::GetSympatheticFocusEffect(focusType type, uint16 spell_id) { +uint16 Client::GetSympatheticFocusEffect(focusType type, uint16 spell_id) { if (IsBardSong(spell_id)) return 0; uint16 proc_spellid = 0; - uint8 MAX_SYMPATHETIC = 10; float ProcChance = 0.0f; std::vector SympatheticProcList; @@ -5195,7 +5194,7 @@ int16 Client::GetSympatheticFocusEffect(focusType type, uint16 spell_id) { for(int x = EmuConstants::EQUIPMENT_BEGIN; x <= EmuConstants::EQUIPMENT_END; x++) { - if (SympatheticProcList.size() > MAX_SYMPATHETIC) + if (SympatheticProcList.size() > MAX_SYMPATHETIC_PROCS) continue; TempItem = nullptr; @@ -5215,7 +5214,7 @@ int16 Client::GetSympatheticFocusEffect(focusType type, uint16 spell_id) { for (int y = AUG_BEGIN; y < EmuConstants::ITEM_COMMON_SIZE; ++y) { - if (SympatheticProcList.size() > MAX_SYMPATHETIC) + if (SympatheticProcList.size() > MAX_SYMPATHETIC_PROCS) continue; ItemInst *aug = nullptr; @@ -5243,18 +5242,18 @@ int16 Client::GetSympatheticFocusEffect(focusType type, uint16 spell_id) { int buff_max = GetMaxTotalSlots(); for (buff_slot = 0; buff_slot < buff_max; buff_slot++) { - if (SympatheticProcList.size() > MAX_SYMPATHETIC) + if (SympatheticProcList.size() > MAX_SYMPATHETIC_PROCS) continue; focusspellid = buffs[buff_slot].spellid; - if (IsValidSpell(focusspellid)) + if (!IsValidSpell(focusspellid)) continue; proc_spellid = CalcFocusEffect(type, focusspellid, spell_id); if (IsValidSpell(proc_spellid)){ - ProcChance = GetSympatheticProcChances(spell_id, spells[focusspellid].base[0]); + ProcChance = GetSympatheticProcChances(spell_id, GetSympatheticSpellProcRate(spell_id)); if(zone->random.Roll(ProcChance)) SympatheticProcList.push_back(proc_spellid); } @@ -5275,7 +5274,7 @@ int16 Client::GetSympatheticFocusEffect(focusType type, uint16 spell_id) { if (aa_AA < 1 || aa_value < 1) continue; - if (SympatheticProcList.size() > MAX_SYMPATHETIC) + if (SympatheticProcList.size() > MAX_SYMPATHETIC_PROCS) continue; proc_spellid = CalcAAFocus(type, aa_AA, spell_id); @@ -5930,6 +5929,26 @@ float Mob::GetSympatheticProcChances(uint16 spell_id, int16 ProcRateMod, int32 I return ProcChance; } +int16 Mob::GetSympatheticSpellProcRate(uint16 spell_id) +{ + for (int i = 0; i < EFFECT_COUNT; i++){ + if (spells[spell_id].effectid[i] == SE_SympatheticProc) + return spells[spell_id].base[i]; + } + + return 0; +} + +uint16 Mob::GetSympatheticSpellProcID(uint16 spell_id) +{ + for (int i = 0; i < EFFECT_COUNT; i++){ + if (spells[spell_id].effectid[i] == SE_SympatheticProc) + return spells[spell_id].base2[i]; + } + + return 0; +} + bool Mob::DoHPToManaCovert(uint16 mana_cost) { if (spellbonuses.HPToManaConvert){ diff --git a/zone/spells.cpp b/zone/spells.cpp index dd70dd6b7..65a4bfabd 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -2151,7 +2151,7 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, uint16 slot, uint16 // it can affect up to 7 people if the targeted group is not our own // Allow pets who cast group spells to affect the group. - if (spell_target->IsPetOwnerClient()){ + if (spell_target->IsPetOwnerClient() && IsPetOwnerClient()){ Mob* owner = spell_target->GetOwner(); if (owner) diff --git a/zone/trading.cpp b/zone/trading.cpp index f1e7bdc1f..16283e7c8 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -1583,6 +1583,8 @@ void Client::BuyTraderItem(TraderBuy_Struct* tbs, Client* Trader, const EQApplic return; } + tbs->Price = BuyItem->GetPrice(); + Log.Out(Logs::Detail, Logs::Trading, "Buyitem: Name: %s, IsStackable: %i, Requested Quantity: %i, Charges on Item %i", BuyItem->GetItem()->Name, BuyItem->IsStackable(), tbs->Quantity, BuyItem->GetCharges()); // If the item is not stackable, then we can only be buying one of them. @@ -1650,7 +1652,12 @@ void Client::BuyTraderItem(TraderBuy_Struct* tbs, Client* Trader, const EQApplic outtbs->Price = TotalCost; } - this->TakeMoneyFromPP(TotalCost); + if(!TakeMoneyFromPP(TotalCost)) { + database.SetHackerFlag(account_name, name, "Attempted to buy something in bazaar but did not have enough money."); + TradeRequestFailed(app); + safe_delete(outapp); + return; + } Log.Out(Logs::Detail, Logs::Trading, "Customer Paid: %d in Copper", TotalCost);