diff --git a/README.md b/README.md index 057937a0c..f99b17975 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ **EQEmulator is a custom completely from-scratch open source server implementation for EverQuest built mostly on C++** * MySQL/MariaDB is used as the database engine (over 200+ tables) * Perl and LUA are both supported scripting languages for NPC/Player/Quest oriented events - * Open source database (Project EQ) has content up to expansion GoD (included in server installs) + * Open source database (Project EQ) has content up to expansion OoW (included in server installs) * Game server environments and databases can be heavily customized to create all new experiences * Hundreds of Quests/events created and maintained by Project EQ @@ -20,14 +20,14 @@ * [Easy Install](http://wiki.eqemulator.org/p?Akkas_PEQ_Server_Installer&frm=Main#from-scratch-installation-instructions-windows) * [Advanced Setup](http://wiki.eqemulator.org/p?Complete_Windows-based_Server_Setup_Guide) -### > Debian/Ubuntu + + +### > Debian/Ubuntu/CentOS/Fedora +* You can use curl or wget to kick off the installer (whichever your OS has) +> curl -O https://raw.githubusercontent.com/EQEmu/Server/master/utils/scripts/linux_installer/install.sh install.sh && chmod 755 install.sh && ./install.sh > wget --no-check-certificate https://raw.githubusercontent.com/EQEmu/Server/master/utils/scripts/linux_installer/install.sh -O install.sh && chmod 755 install.sh && ./install.sh -### > CentOS/Fedora - -> curl -O https://raw.githubusercontent.com/EQEmu/Server/master/utils/scripts/linux_installer/install.sh install.sh && chmod 755 install.sh && ./install.sh - ## Supported Clients |Titanium Edition|Secrets of Faydwer|Seeds of Destruction|Underfoot|Rain of Fear| diff --git a/common/emu_oplist.h b/common/emu_oplist.h index cf6f4ca28..e695c94a9 100644 --- a/common/emu_oplist.h +++ b/common/emu_oplist.h @@ -355,6 +355,7 @@ N(OP_OpenTributeMaster), N(OP_PDeletePetition), N(OP_PetBuffWindow), N(OP_PetCommands), +N(OP_PetCommandState), N(OP_PetHoTT), N(OP_Petition), N(OP_PetitionBug), diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index 3730e1d14..c84c4073a 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -1116,6 +1116,11 @@ struct PetCommand_Struct { /*004*/ uint32 target; }; +struct PetCommandState_Struct { +/*00*/ uint32 button_id; +/*04*/ uint32 state; +}; + /* ** Delete Spawn ** Length: 4 Bytes diff --git a/common/item_data.h b/common/item_data.h index e5d1a24f2..ae7e3bbdb 100644 --- a/common/item_data.h +++ b/common/item_data.h @@ -332,7 +332,7 @@ namespace EQEmu }; struct ItemEffect_Struct { - int16 Effect; + int32 Effect; uint8 Type; uint8 Level; uint8 Level2; diff --git a/common/patches/sod.cpp b/common/patches/sod.cpp index 9d4db7737..c68bfacef 100644 --- a/common/patches/sod.cpp +++ b/common/patches/sod.cpp @@ -3291,73 +3291,60 @@ namespace SoD switch (eq->command) { - case 0x04: - emu->command = 0x00; // /pet health + case 1: // back off + emu->command = 28; break; - case 0x10: - emu->command = 0x01; // /pet leader + case 2: // get lost + emu->command = 29; break; - case 0x07: - emu->command = 0x02; // /pet attack or Pet Window + case 3: // as you were ??? + emu->command = 4; // fuck it follow break; - case 0x03: // Case Guessed - emu->command = 0x03; // /pet qattack - case 0x08: - emu->command = 0x04; // /pet follow or Pet Window + case 4: // report HP + emu->command = 0; break; - case 0x05: - emu->command = 0x05; // /pet guard or Pet Window + case 5: // guard here + emu->command = 5; break; - case 0x09: - emu->command = 0x07; // /pet sit or Pet Window + case 6: // guard me + emu->command = 4; // fuck it follow break; - case 0x0a: - emu->command = 0x08; // /pet stand or Pet Window + case 7: // attack + emu->command = 2; break; - case 0x06: - emu->command = 0x1e; // /pet guard me + case 8: // follow + emu->command = 4; break; - case 0x0f: // Case Made Up - emu->command = 0x09; // /pet stop + case 9: // sit down + emu->command = 7; break; - case 0x0b: - emu->command = 0x0d; // /pet taunt or Pet Window + case 10: // stand up + emu->command = 8; break; - case 0x0e: - emu->command = 0x0e; // /pet notaunt or Pet Window + case 11: // taunt toggle + emu->command = 12; break; - case 0x0c: - emu->command = 0x0f; // /pet hold + case 12: // hold toggle + emu->command = 15; break; - case 0x1b: - emu->command = 0x10; // /pet hold on + case 13: // taunt on + emu->command = 13; break; - case 0x1c: - emu->command = 0x11; // /pet hold off + case 14: // no taunt + emu->command = 14; break; - case 0x11: - emu->command = 0x12; // Slumber? + // 15 is target, doesn't send packet + case 16: // leader + emu->command = 1; break; - case 0x12: - emu->command = 0x15; // /pet no cast + case 17: // feign + emu->command = 27; break; - case 0x0d: // Case Made Up - emu->command = 0x16; // Pet Window No Cast + case 18: // no cast toggle + emu->command = 21; break; - case 0x13: - emu->command = 0x18; // /pet focus - break; - case 0x19: - emu->command = 0x19; // /pet focus on - break; - case 0x1a: - emu->command = 0x1a; // /pet focus off - break; - case 0x01: - emu->command = 0x1c; // /pet back off - break; - case 0x02: - emu->command = 0x1d; // /pet get lost + case 19: // focus toggle + emu->command = 24; break; default: emu->command = eq->command; diff --git a/common/patches/sof.cpp b/common/patches/sof.cpp index 456bc7339..44f093044 100644 --- a/common/patches/sof.cpp +++ b/common/patches/sof.cpp @@ -2675,73 +2675,60 @@ namespace SoF switch (eq->command) { - case 0x04: - emu->command = 0x00; // /pet health + case 1: // back off + emu->command = 28; break; - case 0x10: - emu->command = 0x01; // /pet leader + case 2: // get lost + emu->command = 29; break; - case 0x07: - emu->command = 0x02; // /pet attack or Pet Window + case 3: // as you were ??? + emu->command = 4; // fuck it follow break; - case 0x03: // Case Guessed - emu->command = 0x03; // /pet qattack - case 0x08: - emu->command = 0x04; // /pet follow or Pet Window + case 4: // report HP + emu->command = 0; break; - case 0x05: - emu->command = 0x05; // /pet guard or Pet Window + case 5: // guard here + emu->command = 5; break; - case 0x09: - emu->command = 0x07; // /pet sit or Pet Window + case 6: // guard me + emu->command = 4; // fuck it follow break; - case 0x0a: - emu->command = 0x08; // /pet stand or Pet Window + case 7: // attack + emu->command = 2; break; - case 0x06: - emu->command = 0x1e; // /pet guard me + case 8: // follow + emu->command = 4; break; - case 0x0f: // Case Made Up - emu->command = 0x09; // Stop? + case 9: // sit down + emu->command = 7; break; - case 0x0b: - emu->command = 0x0d; // /pet taunt or Pet Window + case 10: // stand up + emu->command = 8; break; - case 0x0e: - emu->command = 0x0e; // /pet notaunt or Pet Window + case 11: // taunt toggle + emu->command = 12; break; - case 0x0c: - emu->command = 0x0f; // /pet hold + case 12: // hold toggle + emu->command = 15; break; - case 0x1b: - emu->command = 0x10; // /pet hold on + case 13: // taunt on + emu->command = 13; break; - case 0x1c: - emu->command = 0x11; // /pet hold off + case 14: // no taunt + emu->command = 14; break; - case 0x11: - emu->command = 0x12; // Slumber? + // 15 is target, doesn't send packet + case 16: // leader + emu->command = 1; break; - case 0x12: - emu->command = 0x15; // /pet no cast + case 17: // feign + emu->command = 27; break; - case 0x0d: // Case Made Up - emu->command = 0x16; // Pet Window No Cast + case 18: // no cast toggle + emu->command = 21; break; - case 0x13: - emu->command = 0x18; // /pet focus - break; - case 0x19: - emu->command = 0x19; // /pet focus on - break; - case 0x1a: - emu->command = 0x1a; // /pet focus off - break; - case 0x01: - emu->command = 0x1c; // /pet back off - break; - case 0x02: - emu->command = 0x1d; // /pet get lost + case 19: // focus toggle + emu->command = 24; break; default: emu->command = eq->command; diff --git a/common/patches/titanium.cpp b/common/patches/titanium.cpp index 302befb6a..e9679dc06 100644 --- a/common/patches/titanium.cpp +++ b/common/patches/titanium.cpp @@ -2030,73 +2030,60 @@ namespace Titanium switch (eq->command) { - case 0x04: - emu->command = 0x00; // /pet health + case 1: // back off + emu->command = 28; break; - case 0x10: - emu->command = 0x01; // /pet leader + case 2: // get lost + emu->command = 29; break; - case 0x07: - emu->command = 0x02; // /pet attack or Pet Window + case 3: // as you were ??? + emu->command = 4; // fuck it follow break; - case 0x03: // Case Guessed - emu->command = 0x03; // /pet qattack - case 0x08: - emu->command = 0x04; // /pet follow or Pet Window + case 4: // report HP + emu->command = 0; break; - case 0x05: - emu->command = 0x05; // /pet guard or Pet Window + case 5: // guard here + emu->command = 5; break; - case 0x09: - emu->command = 0x07; // /pet sit or Pet Window + case 6: // guard me + emu->command = 4; // fuck it follow break; - case 0x0a: - emu->command = 0x08; // /pet stand or Pet Window + case 7: // attack + emu->command = 2; break; - case 0x06: - emu->command = 0x1e; // /pet guard me + case 8: // follow + emu->command = 4; break; - case 0x0f: // Case Made Up - emu->command = 0x09; // Stop? + case 9: // sit down + emu->command = 7; break; - case 0x0b: - emu->command = 0x0d; // /pet taunt or Pet Window + case 10: // stand up + emu->command = 8; break; - case 0x0e: - emu->command = 0x0e; // /pet notaunt or Pet Window + case 11: // taunt toggle + emu->command = 12; break; - case 0x0c: - emu->command = 0x0f; // /pet hold + case 12: // hold toggle + emu->command = 15; break; - case 0x1b: - emu->command = 0x10; // /pet hold on + case 13: // taunt on + emu->command = 13; break; - case 0x1c: - emu->command = 0x11; // /pet hold off + case 14: // no taunt + emu->command = 14; break; - case 0x11: - emu->command = 0x12; // Slumber? + // 15 is target, doesn't send packet + case 16: // leader + emu->command = 1; break; - case 0x12: - emu->command = 0x15; // /pet no cast + case 17: // feign + emu->command = 27; break; - case 0x0d: // Case Made Up - emu->command = 0x16; // Pet Window No Cast + case 18: // no cast toggle + emu->command = 21; break; - case 0x13: - emu->command = 0x18; // /pet focus - break; - case 0x19: - emu->command = 0x19; // /pet focus on - break; - case 0x1a: - emu->command = 0x1a; // /pet focus off - break; - case 0x01: - emu->command = 0x1c; // /pet back off - break; - case 0x02: - emu->command = 0x1d; // /pet get lost + case 19: // focus toggle + emu->command = 24; break; default: emu->command = eq->command; diff --git a/common/spdat.h b/common/spdat.h index 913ee5a4b..bd1dacdf8 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -468,7 +468,7 @@ typedef enum { #define SE_Blank 254 // implemented #define SE_ShieldDuration 255 // not implemented as bonus - increases duration of /shield #define SE_ShroudofStealth 256 // implemented -#define SE_PetDiscipline 257 // not implemented as bonus - /pet hold +#define SE_PetDiscipline 257 // not implemented as bonus - /pet hold - official name is GivePetHold #define SE_TripleBackstab 258 // implemented[AA] - chance to perform a triple backstab #define SE_CombatStability 259 // implemented[AA] - damage mitigation #define SE_AddSingingMod 260 // implemented[AA] - Instrument/Singing Mastery, base1 is the mod, base2 is the ItemType @@ -478,7 +478,7 @@ typedef enum { #define SE_HastenedAASkill 264 // implemented #define SE_MasteryofPast 265 // implemented[AA] - Spells less than effect values level can not be fizzled #define SE_ExtraAttackChance 266 // implemented - increase chance to score an extra attack with a 2-Handed Weapon. -#define SE_PetDiscipline2 267 // *not implemented - /pet focus, /pet no cast +#define SE_AddPetCommand 267 // implemented - sets command base2 to base1 #define SE_ReduceTradeskillFail 268 // implemented - reduces chance to fail with given tradeskill by a percent chance #define SE_MaxBindWound 269 // implemented[AA] - Increase max HP you can bind wound. #define SE_BardSongRange 270 // implemented[AA] - increase range of beneficial bard songs (Sionachie's Crescendo) diff --git a/utils/patches/patch_RoF.conf b/utils/patches/patch_RoF.conf index ad54b6341..a746804d9 100644 --- a/utils/patches/patch_RoF.conf +++ b/utils/patches/patch_RoF.conf @@ -196,6 +196,7 @@ OP_Consent=0x400e OP_ConsentDeny=0x34c1 OP_AutoFire=0x314e OP_PetCommands=0x0093 +OP_PetCommandState=0x74ed OP_PetHoTT=0x0df4 OP_DeleteSpell=0x305c OP_Surname=0x1a87 diff --git a/utils/patches/patch_RoF2.conf b/utils/patches/patch_RoF2.conf index 9f588d18f..ee9b0e646 100644 --- a/utils/patches/patch_RoF2.conf +++ b/utils/patches/patch_RoF2.conf @@ -195,6 +195,7 @@ OP_Consent=0x1fd1 OP_ConsentDeny=0x7a45 OP_AutoFire=0x241e OP_PetCommands=0x0159 +OP_PetCommandState=0x1dc8 OP_PetHoTT=0x794a OP_DeleteSpell=0x3358 OP_Surname=0x0423 diff --git a/utils/patches/patch_UF.conf b/utils/patches/patch_UF.conf index f41b62740..2002a85f9 100644 --- a/utils/patches/patch_UF.conf +++ b/utils/patches/patch_UF.conf @@ -198,6 +198,7 @@ OP_Consent=0x6bb9 # C OP_ConsentDeny=0x4cd1 # C OP_AutoFire=0x5db5 # C OP_PetCommands=0x7706 # C +OP_PetCommandState=0x1a79 OP_PetHoTT=0x2528 OP_DeleteSpell=0x0698 # C OP_Surname=0x44ae # C diff --git a/utils/scripts/eqemu_server.pl b/utils/scripts/eqemu_server.pl index e9ba02099..c67170785 100644 --- a/utils/scripts/eqemu_server.pl +++ b/utils/scripts/eqemu_server.pl @@ -49,6 +49,7 @@ if(-e "eqemu_server_skip_update.txt"){ #::: Check for script self update do_self_update_check_routine() if !$skip_self_update_check; +get_windows_wget(); get_perl_version(); read_eqemu_config_xml(); get_mysql_path(); @@ -517,6 +518,13 @@ sub get_perl_version { no warnings; } +sub get_windows_wget { + if(!-e "wget.exe" && $OS eq "Windows"){ + eval "use LWP::Simple qw(getstore);"; + getstore("https://raw.githubusercontent.com/Akkadius/EQEmuInstall/master/wget.exe", "wget.exe"); + } +} + sub do_self_update_check_routine { #::: Check for internet connection before updating @@ -524,7 +532,7 @@ sub do_self_update_check_routine { print "[Update] Cannot check update without internet connection...\n"; return; } - + #::: Check for script changes :: eqemu_server.pl get_remote_file($eqemu_repository_request_url . "utils/scripts/eqemu_server.pl", "updates_staged/eqemu_server.pl", 0, 1, 1); @@ -997,68 +1005,14 @@ sub get_remote_file{ } } - if($OS eq "Windows"){ - #::: For non-text type requests... - if($content_type == 1){ - $break = 0; - while($break == 0) { - eval "use LWP::Simple qw(getstore);"; - # use LWP::Simple qw(getstore); - # print "request is " . $request_url . "\n"; - # print "destination file is supposed to be " . $destination_file . "\n"; - if(!getstore($request_url, $destination_file)){ - print "[Download] Error, no connection or failed request...\n\n"; - } - # sleep(1); - #::: Make sure the file exists before continuing... - if(-e $destination_file) { - $break = 1; - print "[Download] Saved: (" . $destination_file . ") from " . $request_url . "\n" if !$silent_download; - } else { $break = 0; } - usleep(500); - - if($no_retry){ - $break = 1; - } - } - } - else{ - $break = 0; - while($break == 0) { - require LWP::UserAgent; - my $ua = LWP::UserAgent->new; - $ua->timeout(10); - $ua->env_proxy; - my $response = $ua->get($request_url); - if ($response->is_success){ - open (FILE, '> ' . $destination_file . ''); - print FILE $response->decoded_content; - close (FILE); - } - else { - print "[Download] Error, no connection or failed request...\n\n"; - } - if(-e $destination_file) { - $break = 1; - print "[Download] Saved: (" . $destination_file . ") from " . $request_url . "\n" if !$silent_download; - } else { $break = 0; } - usleep(500); - - if($no_retry){ - $break = 1; - } - } - } - } - if($OS eq "Linux"){ - #::: wget -O db_update/db_update_manifest.txt https://raw.githubusercontent.com/EQEmu/Server/master/utils/sql/db_update_manifest.txt - $wget = `wget --no-check-certificate --quiet -O $destination_file $request_url`; - print "[Download] Saved: (" . $destination_file . ") from " . $request_url . "\n" if !$silent_download; - if($wget=~/unable to resolve/i){ - print "Error, no connection or failed request...\n\n"; - #die; - } + #::: wget -O db_update/db_update_manifest.txt https://raw.githubusercontent.com/EQEmu/Server/master/utils/sql/db_update_manifest.txt + $wget = `wget -N --no-check-certificate --quiet -O $destination_file $request_url`; + print "[Download] Saved: (" . $destination_file . ") from " . $request_url . "\n" if !$silent_download; + if($wget=~/unable to resolve/i){ + print "Error, no connection or failed request...\n\n"; + #die; } + } #::: Trim Whitespaces diff --git a/utils/scripts/linux_installer/install.sh b/utils/scripts/linux_installer/install.sh index 97cfa582e..55972e8c8 100644 --- a/utils/scripts/linux_installer/install.sh +++ b/utils/scripts/linux_installer/install.sh @@ -114,9 +114,24 @@ if [[ "$OS" == "Debian" ]]; then apt-get $apt_options install open-vm-tools apt-get $apt_options install unzip apt-get $apt_options install uuid-dev + apt-get $apt_options install wget apt-get $apt_options install zlib-bin apt-get $apt_options install zlibc - + apt-get $apt_options install libsodium-dev + apt-get $apt_options install libsodium18 + + # If libsodium18 isn't installed (Debian), let's download both that and the dev package and install them. + if dpkg-query -s "libsodium18" 1>/dev/null 2>&1; then + echo "Sodium library already installed." + else + wget http://ftp.us.debian.org/debian/pool/main/libs/libsodium/libsodium-dev_1.0.11-1~bpo8+1_amd64.deb -O /home/eqemu/libsodium-dev.deb + wget http://ftp.us.debian.org/debian/pool/main/libs/libsodium/libsodium18_1.0.11-1~bpo8+1_amd64.deb -O /home/eqemu/libsodium18.deb + dpkg -i /home/eqemu/libsodium*.deb + # Cleanup after ourselves + rm -f /home/eqemu/libsodium-dev.deb + rm -f /home/eqemu/libsodium18.deb + fi + #::: Install FTP for remote FTP access echo "proftpd-basic shared/proftpd/inetd_or_standalone select standalone" | debconf-set-selections apt-get -y -q install proftpd @@ -149,8 +164,35 @@ EOF elif [[ "$OS" == "fedora_core" ]]; then # Do Fedora stuff - dnf -y install open-vm-tools vim cmake boost-devel zlib-devel mariadb-server mariadb-devel mariadb-libs perl perl-DBD-MySQL perl-IO-stringy perl-devel lua-devel lua-sql-mysql dos2unix php-mysql proftpd wget compat-lua-libs compat-lua-devel compat-lua perl-Time-HiRes - dnf -y groupinstall "Development Tools" "Basic Web Server" "C Development Tools and Libraries" + dnf -y install open-vm-tools + dnf -y install vim + dnf -y install cmake + dnf -y install boost-devel + dnf -y install zlib-devel + dnf -y install mariadb-server + dnf -y install mariadb-devel + dnf -y install mariadb-libs + dnf -y install perl + dnf -y install perl-DBD-MySQL + dnf -y install perl-IO-stringy + dnf -y install perl-devel + dnf -y install lua-devel + dnf -y install lua-sql-mysql + dnf -y install dos2unix + dnf -y install php-mysql + dnf -y install php-mysqlnd + dnf -y install proftpd + dnf -y install wget + dnf -y install compat-lua-libs + dnf -y install compat-lua-devel + dnf -y install compat-lua + dnf -y install perl-Time-HiRes + dnf -y install libuuid-devel + dnf -y install libsodium + dnf -y install libsodium-devel + dnf -y groupinstall "Development Tools" + dnf -y groupinstall "Basic Web Server" + dnf -y groupinstall "C Development Tools and Libraries" fi if [[ "$OS" == "fedora_core" ]] || [[ "$OS" == "red_hat" ]]; then diff --git a/zone/aa.cpp b/zone/aa.cpp index 2fb99a6fb..e002dd4f2 100644 --- a/zone/aa.cpp +++ b/zone/aa.cpp @@ -46,8 +46,16 @@ void Mob::TemporaryPets(uint16 spell_id, Mob *targ, const char *name_override, u if (targ != nullptr && targ->IsCorpse()) return; + // yep, even these need pet power! + int act_power = 0; + + if (IsClient()) { + act_power = CastToClient()->GetFocusEffect(focusPetPower, spell_id); + act_power = CastToClient()->mod_pet_power(act_power, spell_id); + } + PetRecord record; - if (!database.GetPetEntry(spells[spell_id].teleport_zone, &record)) + if (!database.GetPoweredPetEntry(spells[spell_id].teleport_zone, act_power, &record)) { Log(Logs::General, Logs::Error, "Unknown swarm pet spell id: %d, check pets table", spell_id); Message(13, "Unable to find data for pet %s", spells[spell_id].teleport_zone); diff --git a/zone/attack.cpp b/zone/attack.cpp index 6019fa820..60b7ead7b 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -2529,7 +2529,7 @@ bool NPC::Death(Mob* killer_mob, int32 damage, uint16 spell, EQEmu::skills::Skil return true; } -void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, bool iYellForHelp /*= true*/, bool bFrenzy /*= false*/, bool iBuffTic /*= false*/, uint16 spell_id) +void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, bool iYellForHelp /*= true*/, bool bFrenzy /*= false*/, bool iBuffTic /*= false*/, uint16 spell_id, bool pet_command) { if (!other) return; @@ -2568,13 +2568,18 @@ void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, b } } - if (IsPet() && GetOwner() && GetOwner()->GetAA(aaPetDiscipline) && IsHeld() && !IsFocused()) { //ignore aggro if hold and !focus - return; - } + // Pet that is /pet hold on will not add to their hate list if they're not engaged + // Pet that is /pet hold on and /pet focus on will not add others to their hate list + // Pet that is /pet ghold on will never add to their hate list unless /pet attack or /pet qattack - if (IsPet() && GetOwner() && GetOwner()->GetAA(aaPetDiscipline) && IsHeld() && GetOwner()->GetAA(aaAdvancedPetDiscipline) >= 1 && IsFocused()) { - if (!targetmob) - return; + // we skip these checks if it's forced through a pet command + if (!pet_command) { + if (IsPet()) { + if ((IsGHeld() || (IsHeld() && IsFocused())) && !on_hatelist) // we want them to be able to climb the hate list + return; + if ((IsHeld() || IsPetStop() || IsPetRegroup()) && !wasengaged) // not 100% sure on stop/regroup kind of hard to test, but regroup is like "classic hold" + return; + } } if (other->IsNPC() && (other->IsPet() || other->CastToNPC()->GetSwarmOwner() > 0)) @@ -2679,7 +2684,7 @@ void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, b } } - if (mypet && (!(GetAA(aaPetDiscipline) && mypet->IsHeld()))) { // I have a pet, add other to it + if (mypet && !mypet->IsHeld() && !mypet->IsPetStop()) { // I have a pet, add other to it if (!mypet->IsFamiliar() && !mypet->GetSpecialAbility(IMMUNE_AGGRO)) mypet->hate_list.AddEntToHateList(other, 0, 0, bFrenzy); } @@ -3360,7 +3365,10 @@ void Mob::CommonDamage(Mob* attacker, int &damage, const uint16 spell_id, const } //end `if there is some damage being done and theres anattacker person involved` Mob *pet = GetPet(); - if (pet && !pet->IsFamiliar() && !pet->GetSpecialAbility(IMMUNE_AGGRO) && !pet->IsEngaged() && attacker && attacker != this && !attacker->IsCorpse()) + // pets that have GHold will never automatically add NPCs + // pets that have Hold and no Focus will add NPCs if they're engaged + // pets that have Hold and Focus will not add NPCs + if (pet && !pet->IsFamiliar() && !pet->GetSpecialAbility(IMMUNE_AGGRO) && !pet->IsEngaged() && attacker && attacker != this && !attacker->IsCorpse() && !pet->IsGHeld()) { if (!pet->IsHeld()) { Log(Logs::Detail, Logs::Aggro, "Sending pet %s into battle due to attack.", pet->GetName()); @@ -5358,4 +5366,4 @@ void Mob::DoOffHandAttackRounds(Mob *target, ExtraAttackOptions *opts) } } } -} \ No newline at end of file +} diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index e732d596b..7544ceb5f 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -1442,11 +1442,19 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) newbon->FeignedCastOnChance = base1; break; + case SE_AddPetCommand: + if (base1 && base2 < PET_MAXCOMMANDS) + newbon->PetCommands[base2] = true; + break; + + case SE_FeignedMinion: + if (newbon->FeignedMinionChance < base1) + newbon->FeignedMinionChance = base1; + break; + // to do case SE_PetDiscipline: break; - case SE_PetDiscipline2: - break; case SE_PotionBeltSlots: break; case SE_BandolierSlots: @@ -1465,8 +1473,6 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; case SE_TrapCircumvention: break; - case SE_FeignedMinion: - break; // not handled here case SE_HastenedAASkill: diff --git a/zone/bot.cpp b/zone/bot.cpp index 08b17ff17..12cc040bd 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -3853,8 +3853,8 @@ void Bot::Damage(Mob *from, int32 damage, uint16 spell_id, EQEmu::skills::SkillT } //void Bot::AddToHateList(Mob* other, uint32 hate = 0, int32 damage = 0, bool iYellForHelp = true, bool bFrenzy = false, bool iBuffTic = false) -void Bot::AddToHateList(Mob* other, uint32 hate, int32 damage, bool iYellForHelp, bool bFrenzy, bool iBuffTic) { - Mob::AddToHateList(other, hate, damage, iYellForHelp, bFrenzy, iBuffTic); +void Bot::AddToHateList(Mob* other, uint32 hate, int32 damage, bool iYellForHelp, bool bFrenzy, bool iBuffTic, bool pet_command) { + Mob::AddToHateList(other, hate, damage, iYellForHelp, bFrenzy, iBuffTic, pet_command); } bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts) { diff --git a/zone/bot.h b/zone/bot.h index 6c1dfc53f..1a6bcf7cd 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -325,7 +325,7 @@ public: bool DoFinishedSpellGroupTarget(uint16 spell_id, Mob* spellTarget, EQEmu::CastingSlot slot, bool &stopLogic); void SendBotArcheryWearChange(uint8 material_slot, uint32 material, uint32 color); void Camp(bool databaseSave = true); - virtual void AddToHateList(Mob* other, uint32 hate = 0, int32 damage = 0, bool iYellForHelp = true, bool bFrenzy = false, bool iBuffTic = false); + virtual void AddToHateList(Mob* other, uint32 hate = 0, int32 damage = 0, bool iYellForHelp = true, bool bFrenzy = false, bool iBuffTic = false, bool pet_command = false); virtual void SetTarget(Mob* mob); virtual void Zone(); std::vector GetBotSpells() { return AIspells; } diff --git a/zone/client.cpp b/zone/client.cpp index e2a94638e..4ec1ba898 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -697,12 +697,13 @@ bool Client::AddPacket(const EQApplicationPacket *pApp, bool bAckreq) { //drop the packet because it will never get sent. return(false); } - auto c = new CLIENTPACKET; + + auto c = std::unique_ptr(new CLIENTPACKET); c->ack_req = bAckreq; c->app = pApp->Copy(); - clientpackets.Append(c); + clientpackets.push_back(std::move(c)); return true; } @@ -714,26 +715,23 @@ bool Client::AddPacket(EQApplicationPacket** pApp, bool bAckreq) { //drop the packet because it will never get sent. return(false); } - auto c = new CLIENTPACKET; + auto c = std::unique_ptr(new CLIENTPACKET); c->ack_req = bAckreq; c->app = *pApp; *pApp = nullptr; - clientpackets.Append(c); + clientpackets.push_back(std::move(c)); return true; } bool Client::SendAllPackets() { - LinkedListIterator iterator(clientpackets); - CLIENTPACKET* cp = nullptr; - iterator.Reset(); - while(iterator.MoreElements()) { - cp = iterator.GetData(); + while (!clientpackets.empty()) { + cp = clientpackets.front().get(); if(eqs) eqs->FastQueuePacket((EQApplicationPacket **)&cp->app, cp->ack_req); - iterator.RemoveCurrent(); + clientpackets.pop_front(); Log(Logs::Moderate, Logs::Client_Server_Packet, "Transmitting a packet"); } return true; @@ -5715,6 +5713,20 @@ void Client::SuspendMinion() Message_StringID(clientMessageTell, SUSPEND_MINION_UNSUSPEND, CurrentPet->GetCleanName()); memset(&m_suspendedminion, 0, sizeof(struct PetInfo)); + // TODO: These pet command states need to be synced ... + // Will just fix them for now + if (m_ClientVersionBit & EQEmu::versions::bit_UFAndLater) { + SetPetCommandState(PET_BUTTON_SIT, 0); + SetPetCommandState(PET_BUTTON_STOP, 0); + SetPetCommandState(PET_BUTTON_REGROUP, 0); + SetPetCommandState(PET_BUTTON_FOLLOW, 1); + SetPetCommandState(PET_BUTTON_GUARD, 0); + SetPetCommandState(PET_BUTTON_TAUNT, 1); + SetPetCommandState(PET_BUTTON_HOLD, 0); + SetPetCommandState(PET_BUTTON_GHOLD, 0); + SetPetCommandState(PET_BUTTON_FOCUS, 0); + SetPetCommandState(PET_BUTTON_SPELLHOLD, 0); + } } else return; @@ -8937,3 +8949,12 @@ void Client::ProcessAggroMeter() } } +void Client::SetPetCommandState(int button, int state) +{ + auto app = new EQApplicationPacket(OP_PetCommandState, sizeof(PetCommandState_Struct)); + auto pcs = (PetCommandState_Struct *)app->pBuffer; + pcs->button_id = button; + pcs->state = state; + FastQueuePacket(&app); +} + diff --git a/zone/client.h b/zone/client.h index aeabbd63a..19dc4c05b 100644 --- a/zone/client.h +++ b/zone/client.h @@ -70,6 +70,7 @@ namespace EQEmu #include #include #include +#include #define CLIENT_TIMEOUT 90000 @@ -351,6 +352,8 @@ public: inline InspectMessage_Struct& GetInspectMessage() { return m_inspect_message; } inline const InspectMessage_Struct& GetInspectMessage() const { return m_inspect_message; } + void SetPetCommandState(int button, int state); + bool CheckAccess(int16 iDBLevel, int16 iDefaultLevel); void CheckQuests(const char* zonename, const char* message, uint32 npc_id, uint32 item_id, Mob* other); @@ -1424,7 +1427,7 @@ private: bool AddPacket(const EQApplicationPacket *, bool); bool AddPacket(EQApplicationPacket**, bool); bool SendAllPackets(); - LinkedList clientpackets; + std::deque> clientpackets; //Zoning related stuff void SendZoneCancel(ZoneChange_Struct *zc); diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 4a9c9c22a..bbd76183d 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -858,6 +858,21 @@ void Client::CompleteConnect() CastToClient()->FastQueuePacket(&outapp); } + // TODO: load these states + // We at least will set them to the correct state for now + if (m_ClientVersionBit & EQEmu::versions::bit_UFAndLater && GetPet()) { + SetPetCommandState(PET_BUTTON_SIT, 0); + SetPetCommandState(PET_BUTTON_STOP, 0); + SetPetCommandState(PET_BUTTON_REGROUP, 0); + SetPetCommandState(PET_BUTTON_FOLLOW, 1); + SetPetCommandState(PET_BUTTON_GUARD, 0); + SetPetCommandState(PET_BUTTON_TAUNT, 1); + SetPetCommandState(PET_BUTTON_HOLD, 0); + SetPetCommandState(PET_BUTTON_GHOLD, 0); + SetPetCommandState(PET_BUTTON_FOCUS, 0); + SetPetCommandState(PET_BUTTON_SPELLHOLD, 0); + } + entity_list.RefreshClientXTargets(this); worldserver.RequestTellQueue(GetName()); @@ -2993,9 +3008,12 @@ void Client::Handle_OP_AugmentItem(const EQApplicationPacket *app) if (!solvent) { - Log(Logs::General, Logs::Error, "Player tried to safely remove an augment without a distiller."); - Message(13, "Error: Missing an augmentation distiller for safely removing this augment."); - return; + old_aug = tobe_auged->GetAugment(in_augment->augment_index); + if (!old_aug || old_aug->GetItem()->AugDistiller != 0) { + Log(Logs::General, Logs::Error, "Player tried to safely remove an augment without a distiller."); + Message(13, "Error: Missing an augmentation distiller for safely removing this augment."); + return; + } } else if (solvent->GetItem()->ItemType == EQEmu::item::ItemTypeAugmentationDistiller) { @@ -3159,7 +3177,8 @@ void Client::Handle_OP_AugmentItem(const EQApplicationPacket *app) if (itemOneToPush && itemTwoToPush) { // Consume the augment distiller - DeleteItemInInventory(solvent_slot, solvent->IsStackable() ? 1 : 0, true); + if (solvent) + DeleteItemInInventory(solvent_slot, solvent->IsStackable() ? 1 : 0, true); // Remove the augmented item DeleteItemInInventory(item_slot, 0, true); @@ -4802,6 +4821,7 @@ void Client::Handle_OP_Consider(const EQApplicationPacket *app) mod_consider(tmob, con); QueuePacket(outapp); + safe_delete(outapp); // only wanted to check raid target once // and need con to still be around so, do it here! if (tmob->IsRaidTarget()) { @@ -4839,7 +4859,15 @@ void Client::Handle_OP_Consider(const EQApplicationPacket *app) SendColoredText(color, std::string("This creature would take an army to defeat!")); } - safe_delete(outapp); + + // this could be done better, but this is only called when you con so w/e + // Shroud of Stealth has a special message + if (improved_hidden && (!tmob->see_improved_hide && (tmob->see_invis || tmob->see_hide))) + Message_StringID(10, SOS_KEEPS_HIDDEN); + // we are trying to hide but they can see us + else if ((invisible || invisible_undead || hidden || invisible_animals) && !IsInvisible(tmob)) + Message_StringID(10, SUSPECT_SEES_YOU); + return; } @@ -9939,7 +9967,7 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) Mob *Owner = mypet->GetOwner(); if (Owner) mypet->Say_StringID(PET_LEADERIS, Owner->GetCleanName()); - else + else if (mypet->IsNPC()) mypet->Say_StringID(I_FOLLOW_NOONE); } } @@ -9950,9 +9978,6 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) if (mypet->GetPetType() == petTargetLock && (pet->command != PET_HEALTHREPORT && pet->command != PET_GETLOST)) return; - if (mypet->GetPetType() == petAnimation && (pet->command != PET_HEALTHREPORT && pet->command != PET_GETLOST) && !GetAA(aaAnimationEmpathy)) - return; - // just let the command "/pet get lost" work for familiars if (mypet->GetPetType() == petFamiliar && pet->command != PET_GETLOST) return; @@ -9982,25 +10007,31 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) break; //prevent pet from attacking stuff while feared if (!mypet->IsAttackAllowed(target)) { - mypet->Say_StringID(NOT_LEGAL_TARGET); + mypet->SayTo_StringID(this, NOT_LEGAL_TARGET); break; } - if ((mypet->GetPetType() == petAnimation && GetAA(aaAnimationEmpathy) >= 2) || mypet->GetPetType() != petAnimation) { + if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { if (target != this && DistanceSquaredNoZ(mypet->GetPosition(), target->GetPosition()) <= (RuleR(Pets, AttackCommandRange)*RuleR(Pets, AttackCommandRange))) { - if (mypet->IsHeld()) { - if (!mypet->IsFocused()) { - mypet->SetHeld(false); //break the hold and guard if we explicitly tell the pet to attack. - if (mypet->GetPetOrder() != SPO_Guard) - mypet->SetPetOrder(SPO_Follow); - } - else { - mypet->SetTarget(target); - } + if (mypet->IsPetStop()) { + mypet->SetPetStop(false); + SetPetCommandState(PET_BUTTON_STOP, 0); + } + if (mypet->IsPetRegroup()) { + mypet->SetPetRegroup(false); + SetPetCommandState(PET_BUTTON_REGROUP, 0); } zone->AddAggroMob(); - mypet->AddToHateList(target, 1); + // classic acts like qattack + int hate = 1; + if (mypet->IsEngaged()) { + auto top = mypet->GetHateMost(); + if (top && top != target) + hate += mypet->GetHateAmount(top) - mypet->GetHateAmount(target) + 100; // should be enough to cause target change + } + mypet->AddToHateList(target, hate, 0, true, false, false, SPELL_UNKNOWN, true); Message_StringID(MT_PetResponse, PET_ATTACKING, mypet->GetCleanName(), target->GetCleanName()); + SetTarget(target); } } break; @@ -10017,14 +10048,22 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) } if (!mypet->IsAttackAllowed(GetTarget())) { - mypet->Say_StringID(NOT_LEGAL_TARGET); + mypet->SayTo_StringID(this, NOT_LEGAL_TARGET); break; } - if ((mypet->GetPetType() == petAnimation && GetAA(aaAnimationEmpathy) >= 2) || mypet->GetPetType() != petAnimation) { + if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { if (GetTarget() != this && DistanceSquaredNoZ(mypet->GetPosition(), GetTarget()->GetPosition()) <= (RuleR(Pets, AttackCommandRange)*RuleR(Pets, AttackCommandRange))) { + if (mypet->IsPetStop()) { + mypet->SetPetStop(false); + SetPetCommandState(PET_BUTTON_STOP, 0); + } + if (mypet->IsPetRegroup()) { + mypet->SetPetRegroup(false); + SetPetCommandState(PET_BUTTON_REGROUP, 0); + } zone->AddAggroMob(); - mypet->AddToHateList(GetTarget(), 1); + mypet->AddToHateList(GetTarget(), 1, 0, true, false, false, SPELL_UNKNOWN, true); Message_StringID(MT_PetResponse, PET_ATTACKING, mypet->GetCleanName(), GetTarget()->GetCleanName()); } } @@ -10033,17 +10072,22 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) case PET_BACKOFF: { if (mypet->IsFeared()) break; //keeps pet running while feared - if ((mypet->GetPetType() == petAnimation && GetAA(aaAnimationEmpathy) >= 3) || mypet->GetPetType() != petAnimation) { - mypet->Say_StringID(MT_PetResponse, PET_CALMING); + if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { + mypet->SayTo_StringID(this, MT_PetResponse, PET_CALMING); mypet->WipeHateList(); mypet->SetTarget(nullptr); + if (mypet->IsPetStop()) { + mypet->SetPetStop(false); + SetPetCommandState(PET_BUTTON_STOP, 0); + } } break; } case PET_HEALTHREPORT: { - Message_StringID(MT_PetResponse, PET_REPORT_HP, ConvertArrayF(mypet->GetHPRatio(), val1)); - mypet->ShowBuffList(this); - //Message(10,"%s tells you, 'I have %d percent of my hit points left.'",mypet->GetName(),(uint8)mypet->GetHPRatio()); + if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { + Message_StringID(MT_PetResponse, PET_REPORT_HP, ConvertArrayF(mypet->GetHPRatio(), val1)); + mypet->ShowBuffList(this); + } break; } case PET_GETLOST: { @@ -10060,7 +10104,7 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) SetPet(nullptr); } - mypet->Say_StringID(MT_PetResponse, PET_GETLOST_STRING); + mypet->SayTo_StringID(this, MT_PetResponse, PET_GETLOST_STRING); mypet->CastToNPC()->Depop(); //Oddly, the client (Titanium) will still allow "/pet get lost" command despite me adding the code below. If someone can figure that out, you can uncomment this code and use it. @@ -10076,12 +10120,17 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) case PET_GUARDHERE: { if (mypet->IsFeared()) break; //could be exploited like PET_BACKOFF - if ((mypet->GetPetType() == petAnimation && GetAA(aaAnimationEmpathy) >= 1) || mypet->GetPetType() != petAnimation) { + if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { if (mypet->IsNPC()) { - mypet->SetHeld(false); - mypet->Say_StringID(MT_PetResponse, PET_GUARDINGLIFE); + mypet->SayTo_StringID(this, MT_PetResponse, PET_GUARDINGLIFE); mypet->SetPetOrder(SPO_Guard); mypet->CastToNPC()->SaveGuardSpot(); + if (!mypet->GetTarget()) // want them to not twitch if they're chasing something down + mypet->SetCurrentSpeed(0); + if (mypet->IsPetStop()) { + mypet->SetPetStop(false); + SetPetCommandState(PET_BUTTON_STOP, 0); + } } } break; @@ -10089,16 +10138,19 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) case PET_FOLLOWME: { if (mypet->IsFeared()) break; //could be exploited like PET_BACKOFF - if ((mypet->GetPetType() == petAnimation && GetAA(aaAnimationEmpathy) >= 1) || mypet->GetPetType() != petAnimation) { - mypet->SetHeld(false); - mypet->Say_StringID(MT_PetResponse, PET_FOLLOWING); + if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { + mypet->SayTo_StringID(this, MT_PetResponse, PET_FOLLOWING); mypet->SetPetOrder(SPO_Follow); mypet->SendAppearancePacket(AT_Anim, ANIM_STAND); + if (mypet->IsPetStop()) { + mypet->SetPetStop(false); + SetPetCommandState(PET_BUTTON_STOP, 0); + } } break; } case PET_TAUNT: { - if ((mypet->GetPetType() == petAnimation && GetAA(aaAnimationEmpathy) >= 3) || mypet->GetPetType() != petAnimation) { + if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { if (mypet->CastToNPC()->IsTaunting()) { Message_StringID(MT_PetResponse, PET_NO_TAUNT); @@ -10113,14 +10165,14 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) break; } case PET_TAUNT_ON: { - if ((mypet->GetPetType() == petAnimation && GetAA(aaAnimationEmpathy) >= 3) || mypet->GetPetType() != petAnimation) { + if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { Message_StringID(MT_PetResponse, PET_DO_TAUNT); mypet->CastToNPC()->SetTaunting(true); } break; } case PET_TAUNT_OFF: { - if ((mypet->GetPetType() == petAnimation && GetAA(aaAnimationEmpathy) >= 3) || mypet->GetPetType() != petAnimation) { + if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { Message_StringID(MT_PetResponse, PET_NO_TAUNT); mypet->CastToNPC()->SetTaunting(false); } @@ -10129,27 +10181,30 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) case PET_GUARDME: { if (mypet->IsFeared()) break; //could be exploited like PET_BACKOFF - if ((mypet->GetPetType() == petAnimation && GetAA(aaAnimationEmpathy) >= 1) || mypet->GetPetType() != petAnimation) { - mypet->SetHeld(false); - mypet->Say_StringID(MT_PetResponse, PET_GUARDME_STRING); + if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { + mypet->SayTo_StringID(this, MT_PetResponse, PET_GUARDME_STRING); mypet->SetPetOrder(SPO_Follow); mypet->SendAppearancePacket(AT_Anim, ANIM_STAND); + if (mypet->IsPetStop()) { + mypet->SetPetStop(false); + SetPetCommandState(PET_BUTTON_STOP, 0); + } } break; } case PET_SIT: { if (mypet->IsFeared()) break; //could be exploited like PET_BACKOFF - if ((mypet->GetPetType() == petAnimation && GetAA(aaAnimationEmpathy) >= 3) || mypet->GetPetType() != petAnimation) { + if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { if (mypet->GetPetOrder() == SPO_Sit) { - mypet->Say_StringID(MT_PetResponse, PET_SIT_STRING); + mypet->SayTo_StringID(this, MT_PetResponse, PET_SIT_STRING); mypet->SetPetOrder(SPO_Follow); mypet->SendAppearancePacket(AT_Anim, ANIM_STAND); } else { - mypet->Say_StringID(MT_PetResponse, PET_SIT_STRING); + mypet->SayTo_StringID(this, MT_PetResponse, PET_SIT_STRING); mypet->SetPetOrder(SPO_Sit); mypet->SetRunAnimSpeed(0); if (!mypet->UseBardSpellLogic()) //maybe we can have a bard pet @@ -10162,8 +10217,8 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) case PET_STANDUP: { if (mypet->IsFeared()) break; //could be exploited like PET_BACKOFF - if ((mypet->GetPetType() == petAnimation && GetAA(aaAnimationEmpathy) >= 3) || mypet->GetPetType() != petAnimation) { - mypet->Say_StringID(MT_PetResponse, PET_SIT_STRING); + if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { + mypet->SayTo_StringID(this, MT_PetResponse, PET_SIT_STRING); mypet->SetPetOrder(SPO_Follow); mypet->SendAppearancePacket(AT_Anim, ANIM_STAND); } @@ -10172,8 +10227,8 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) case PET_SITDOWN: { if (mypet->IsFeared()) break; //could be exploited like PET_BACKOFF - if ((mypet->GetPetType() == petAnimation && GetAA(aaAnimationEmpathy) >= 3) || mypet->GetPetType() != petAnimation) { - mypet->Say_StringID(MT_PetResponse, PET_SIT_STRING); + if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { + mypet->SayTo_StringID(this, MT_PetResponse, PET_SIT_STRING); mypet->SetPetOrder(SPO_Sit); mypet->SetRunAnimSpeed(0); if (!mypet->UseBardSpellLogic()) //maybe we can have a bard pet @@ -10182,152 +10237,274 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) } break; } - case PET_SLUMBER: { - if (mypet->IsFeared()) break; //could be exploited like PET_BACKOFF - - if (mypet->GetPetType() != petAnimation) { - // Needs to have an IsSleeping() check added and this case should toggle on/off - mypet->Say_StringID(MT_PetResponse, PET_SIT_STRING); - mypet->SetPetOrder(SPO_Sit); - mypet->SetRunAnimSpeed(0); - if (!mypet->UseBardSpellLogic()) //maybe we can have a bard pet - mypet->InterruptSpell(); //No cast 4 u. //i guess the pet should start casting - mypet->SendAppearancePacket(AT_Anim, ANIM_DEATH); - } - break; - } - case PET_SLUMBER_ON: { - if (mypet->IsFeared()) break; //could be exploited like PET_BACKOFF - - if (mypet->GetPetType() != petAnimation) { - mypet->Say_StringID(MT_PetResponse, PET_SIT_STRING); - mypet->SetPetOrder(SPO_Sit); - mypet->SetRunAnimSpeed(0); - if (!mypet->UseBardSpellLogic()) //maybe we can have a bard pet - mypet->InterruptSpell(); //No cast 4 u. //i guess the pet should start casting - mypet->SendAppearancePacket(AT_Anim, ANIM_DEATH); - } - break; - } - case PET_SLUMBER_OFF: { - if (mypet->IsFeared()) break; //could be exploited like PET_BACKOFF - - if (mypet->GetPetType() != petAnimation) { - mypet->Say_StringID(MT_PetResponse, PET_SIT_STRING); - mypet->SetPetOrder(SPO_Follow); - mypet->SetRunAnimSpeed(mypet->GetBaseRunspeed()); - mypet->SendAppearancePacket(AT_Anim, ANIM_STAND); - } - break; - } case PET_HOLD: { - if (GetAA(aaPetDiscipline) && mypet->IsNPC()) { - if (mypet->IsFeared()) - break; //could be exploited like PET_BACKOFF - + if (aabonuses.PetCommands[PetCommand] && mypet->IsNPC()) { if (mypet->IsHeld()) { + if (m_ClientVersionBit & EQEmu::versions::bit_SoDAndLater) + Message_StringID(MT_PetResponse, PET_HOLD_SET_OFF); mypet->SetHeld(false); } else { - mypet->Say_StringID(MT_PetResponse, PET_ON_HOLD); - mypet->WipeHateList(); + if (m_ClientVersionBit & EQEmu::versions::bit_SoDAndLater) + Message_StringID(MT_PetResponse, PET_HOLD_SET_ON); + + if (m_ClientVersionBit & EQEmu::versions::bit_UFAndLater) + mypet->SayTo_StringID(this, MT_PetResponse, PET_NOW_HOLDING); + else + mypet->SayTo_StringID(this, MT_PetResponse, PET_ON_HOLD); + mypet->SetHeld(true); } + mypet->SetGHeld(false); + SetPetCommandState(PET_BUTTON_GHOLD, 0); } break; } case PET_HOLD_ON: { - if (GetAA(aaPetDiscipline) && mypet->IsNPC() && !mypet->IsHeld()) { - if (mypet->IsFeared()) - break; //could be exploited like PET_BACKOFF + if (aabonuses.PetCommands[PetCommand] && mypet->IsNPC() && !mypet->IsHeld()) { + if (m_ClientVersionBit & EQEmu::versions::bit_SoDAndLater) + Message_StringID(MT_PetResponse, PET_HOLD_SET_ON); - mypet->Say_StringID(MT_PetResponse, PET_ON_HOLD); - mypet->WipeHateList(); + if (m_ClientVersionBit & EQEmu::versions::bit_UFAndLater) + mypet->SayTo_StringID(this, MT_PetResponse, PET_NOW_HOLDING); + else + mypet->SayTo_StringID(this, MT_PetResponse, PET_ON_HOLD); mypet->SetHeld(true); + mypet->SetGHeld(false); + SetPetCommandState(PET_BUTTON_GHOLD, 0); } break; } case PET_HOLD_OFF: { - if (GetAA(aaPetDiscipline) && mypet->IsNPC() && mypet->IsHeld()) + if (aabonuses.PetCommands[PetCommand] && mypet->IsNPC() && mypet->IsHeld()) { + if (m_ClientVersionBit & EQEmu::versions::bit_SoDAndLater) + Message_StringID(MT_PetResponse, PET_HOLD_SET_OFF); mypet->SetHeld(false); + } + break; + } + case PET_GHOLD: { + if (aabonuses.PetCommands[PetCommand] && mypet->IsNPC()) { + if (mypet->IsGHeld()) + { + if (m_ClientVersionBit & EQEmu::versions::bit_UFAndLater) + Message_StringID(MT_PetResponse, PET_OFF_GHOLD); + mypet->SetGHeld(false); + } + else + { + if (m_ClientVersionBit & EQEmu::versions::bit_UFAndLater) { + Message_StringID(MT_PetResponse, PET_ON_GHOLD); + mypet->SayTo_StringID(this, MT_PetResponse, PET_GHOLD_ON_MSG); + } else { + mypet->SayTo_StringID(this, MT_PetResponse, PET_ON_HOLD); + } + mypet->SetGHeld(true); + } + mypet->SetHeld(false); + SetPetCommandState(PET_BUTTON_HOLD, 0); + } + break; + } + case PET_GHOLD_ON: { + if (aabonuses.PetCommands[PetCommand] && mypet->IsNPC()) { + if (m_ClientVersionBit & EQEmu::versions::bit_UFAndLater) { + Message_StringID(MT_PetResponse, PET_ON_GHOLD); + mypet->SayTo_StringID(this, MT_PetResponse, PET_GHOLD_ON_MSG); + } else { + mypet->SayTo_StringID(this, MT_PetResponse, PET_ON_HOLD); + } + mypet->SetGHeld(true); + mypet->SetHeld(false); + SetPetCommandState(PET_BUTTON_HOLD, 0); + } + break; + } + case PET_GHOLD_OFF: { + if (aabonuses.PetCommands[PetCommand] && mypet->IsNPC() && mypet->IsGHeld()) { + if (m_ClientVersionBit & EQEmu::versions::bit_UFAndLater) + Message_StringID(MT_PetResponse, PET_OFF_GHOLD); + mypet->SetGHeld(false); + } break; } case PET_SPELLHOLD: { - if (GetAA(aaAdvancedPetDiscipline) == 2 && mypet->IsNPC()) { + if (aabonuses.PetCommands[PetCommand] && mypet->IsNPC()) { if (mypet->IsFeared()) break; if (mypet->IsNoCast()) { Message_StringID(MT_PetResponse, PET_CASTING); - mypet->CastToNPC()->SetNoCast(false); + if (m_ClientVersionBit & EQEmu::versions::bit_SoDAndLater) + Message_StringID(MT_PetResponse, PET_SPELLHOLD_SET_OFF); + mypet->SetNoCast(false); } else { Message_StringID(MT_PetResponse, PET_NOT_CASTING); - mypet->CastToNPC()->SetNoCast(true); + if (m_ClientVersionBit & EQEmu::versions::bit_SoDAndLater) + Message_StringID(MT_PetResponse, PET_SPELLHOLD_SET_ON); + mypet->SetNoCast(true); } } break; } case PET_SPELLHOLD_ON: { - if (GetAA(aaAdvancedPetDiscipline) == 2 && mypet->IsNPC()) { + if (aabonuses.PetCommands[PetCommand] && mypet->IsNPC()) { if (mypet->IsFeared()) break; if (!mypet->IsNoCast()) { Message_StringID(MT_PetResponse, PET_NOT_CASTING); - mypet->CastToNPC()->SetNoCast(true); + if (m_ClientVersionBit & EQEmu::versions::bit_SoDAndLater) + Message_StringID(MT_PetResponse, PET_SPELLHOLD_SET_ON); + mypet->SetNoCast(true); } } break; } case PET_SPELLHOLD_OFF: { - if (GetAA(aaAdvancedPetDiscipline) == 2 && mypet->IsNPC()) { + if (aabonuses.PetCommands[PetCommand] && mypet->IsNPC()) { if (mypet->IsFeared()) break; if (mypet->IsNoCast()) { Message_StringID(MT_PetResponse, PET_CASTING); - mypet->CastToNPC()->SetNoCast(false); + if (m_ClientVersionBit & EQEmu::versions::bit_SoDAndLater) + Message_StringID(MT_PetResponse, PET_SPELLHOLD_SET_OFF); + mypet->SetNoCast(false); } } break; } case PET_FOCUS: { - if (GetAA(aaAdvancedPetDiscipline) >= 1 && mypet->IsNPC()) { + if (aabonuses.PetCommands[PetCommand] && mypet->IsNPC()) { if (mypet->IsFeared()) break; if (mypet->IsFocused()) { Message_StringID(MT_PetResponse, PET_NOT_FOCUSING); - mypet->CastToNPC()->SetFocused(false); + if (m_ClientVersionBit & EQEmu::versions::bit_SoDAndLater) + Message_StringID(MT_PetResponse, PET_FOCUS_SET_OFF); + mypet->SetFocused(false); } else { Message_StringID(MT_PetResponse, PET_NOW_FOCUSING); - mypet->CastToNPC()->SetFocused(true); + if (m_ClientVersionBit & EQEmu::versions::bit_SoDAndLater) + Message_StringID(MT_PetResponse, PET_FOCUS_SET_ON); + mypet->SetFocused(true); } } break; } case PET_FOCUS_ON: { - if (GetAA(aaAdvancedPetDiscipline) >= 1 && mypet->IsNPC()) { + if (aabonuses.PetCommands[PetCommand] && mypet->IsNPC()) { if (mypet->IsFeared()) break; if (!mypet->IsFocused()) { Message_StringID(MT_PetResponse, PET_NOW_FOCUSING); - mypet->CastToNPC()->SetFocused(true); + if (m_ClientVersionBit & EQEmu::versions::bit_SoDAndLater) + Message_StringID(MT_PetResponse, PET_FOCUS_SET_ON); + mypet->SetFocused(true); } } break; } case PET_FOCUS_OFF: { - if (GetAA(aaAdvancedPetDiscipline) >= 1 && mypet->IsNPC()) { + if (aabonuses.PetCommands[PetCommand] && mypet->IsNPC()) { if (mypet->IsFeared()) break; if (mypet->IsFocused()) { Message_StringID(MT_PetResponse, PET_NOT_FOCUSING); - mypet->CastToNPC()->SetFocused(false); + if (m_ClientVersionBit & EQEmu::versions::bit_SoDAndLater) + Message_StringID(MT_PetResponse, PET_FOCUS_SET_OFF); + mypet->SetFocused(false); } } break; } + case PET_STOP: { + if (mypet->IsFeared()) break; //could be exploited like PET_BACKOFF + + if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { + if (mypet->IsPetStop()) { + mypet->SetPetStop(false); + } else { + mypet->SetPetStop(true); + mypet->SetCurrentSpeed(0); + mypet->SetTarget(nullptr); + if (mypet->IsPetRegroup()) { + mypet->SetPetRegroup(false); + SetPetCommandState(PET_BUTTON_REGROUP, 0); + } + } + mypet->SayTo_StringID(this, MT_PetResponse, PET_GETLOST_STRING); + } + break; + } + case PET_STOP_ON: { + if (mypet->IsFeared()) break; //could be exploited like PET_BACKOFF + + if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { + mypet->SetPetStop(true); + mypet->SetCurrentSpeed(0); + mypet->SetTarget(nullptr); + mypet->SayTo_StringID(this, MT_PetResponse, PET_GETLOST_STRING); + if (mypet->IsPetRegroup()) { + mypet->SetPetRegroup(false); + SetPetCommandState(PET_BUTTON_REGROUP, 0); + } + } + break; + } + case PET_STOP_OFF: { + if (mypet->IsFeared()) break; //could be exploited like PET_BACKOFF + + if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) { + mypet->SetPetStop(false); + mypet->SayTo_StringID(this, MT_PetResponse, PET_GETLOST_STRING); + } + break; + } + case PET_REGROUP: { + if (mypet->IsFeared()) break; //could be exploited like PET_BACKOFF + + if (aabonuses.PetCommands[PetCommand]) { + if (mypet->IsPetRegroup()) { + mypet->SetPetRegroup(false); + mypet->SayTo_StringID(this, MT_PetResponse, PET_OFF_REGROUPING); + } else { + mypet->SetPetRegroup(true); + mypet->SetTarget(nullptr); + mypet->SayTo_StringID(this, MT_PetResponse, PET_ON_REGROUPING); + if (mypet->IsPetStop()) { + mypet->SetPetStop(false); + SetPetCommandState(PET_BUTTON_STOP, 0); + } + } + } + break; + } + case PET_REGROUP_ON: { + if (mypet->IsFeared()) break; //could be exploited like PET_BACKOFF + + if (aabonuses.PetCommands[PetCommand]) { + mypet->SetPetRegroup(true); + mypet->SetTarget(nullptr); + mypet->SayTo_StringID(this, MT_PetResponse, PET_ON_REGROUPING); + if (mypet->IsPetStop()) { + mypet->SetPetStop(false); + SetPetCommandState(PET_BUTTON_STOP, 0); + } + } + break; + } + case PET_REGROUP_OFF: { + if (mypet->IsFeared()) break; //could be exploited like PET_BACKOFF + + if (aabonuses.PetCommands[PetCommand]) { + mypet->SetPetRegroup(false); + mypet->SayTo_StringID(this, MT_PetResponse, PET_OFF_REGROUPING); + } + break; + } default: printf("Client attempted to use a unknown pet command:\n"); break; diff --git a/zone/common.h b/zone/common.h index f1f965740..7b63174d4 100644 --- a/zone/common.h +++ b/zone/common.h @@ -56,6 +56,55 @@ //Maximum distance from a zone point if zone was specified #define ZONEPOINT_ZONE_RANGE 40000.0f +// Defines based on the RoF2 Client +#define PET_HEALTHREPORT 0 // 0x00 - /pet health or Pet Window +#define PET_LEADER 1 // 0x01 - /pet leader or Pet Window +#define PET_ATTACK 2 // 0x02 - /pet attack or Pet Window +#define PET_QATTACK 3 // 0x03 - /pet qattack or Pet Window +#define PET_FOLLOWME 4 // 0x04 - /pet follow or Pet Window +#define PET_GUARDHERE 5 // 0x05 - /pet guard or Pet Window +#define PET_SIT 6 // 0x06 - /pet sit or Pet Window +#define PET_SITDOWN 7 // 0x07 - /pet sit on +#define PET_STANDUP 8 // 0x08 - /pet sit off +#define PET_STOP 9 // 0x09 - /pet stop or Pet Window - Not implemented +#define PET_STOP_ON 10 // 0x0a - /pet stop on - Not implemented +#define PET_STOP_OFF 11 // 0x0b - /pet stop off - Not implemented +#define PET_TAUNT 12 // 0x0c - /pet taunt or Pet Window +#define PET_TAUNT_ON 13 // 0x0d - /pet taunt on +#define PET_TAUNT_OFF 14 // 0x0e - /pet taunt off +#define PET_HOLD 15 // 0x0f - /pet hold or Pet Window, won't add to hate list unless attacking +#define PET_HOLD_ON 16 // 0x10 - /pet hold on +#define PET_HOLD_OFF 17 // 0x11 - /pet hold off +#define PET_GHOLD 18 // 0x12 - /pet ghold, will never add to hate list unless told to +#define PET_GHOLD_ON 19 // 0x13 - /pet ghold on +#define PET_GHOLD_OFF 20 // 0x14 - /pet ghold off +#define PET_SPELLHOLD 21 // 0x15 - /pet no cast or /pet spellhold or Pet Window +#define PET_SPELLHOLD_ON 22 // 0x16 - /pet spellhold on +#define PET_SPELLHOLD_OFF 23 // 0x17 - /pet spellhold off +#define PET_FOCUS 24 // 0x18 - /pet focus or Pet Window +#define PET_FOCUS_ON 25 // 0x19 - /pet focus on +#define PET_FOCUS_OFF 26 // 0x1a - /pet focus off +#define PET_FEIGN 27 // 0x1b - /pet feign +#define PET_BACKOFF 28 // 0x1c - /pet back off +#define PET_GETLOST 29 // 0x1d - /pet get lost +#define PET_GUARDME 30 // 0x1e - Same as /pet follow, but different message in older clients - define not from client /pet target in modern clients but doesn't send packet +#define PET_REGROUP 31 // 0x1f - /pet regroup, acts like classic hold. Stops attack and moves back to guard/you but doesn't clear hate list +#define PET_REGROUP_ON 32 // 0x20 - /pet regroup on, turns on regroup +#define PET_REGROUP_OFF 33 // 0x21 - /pet regroup off, turns off regroup +#define PET_MAXCOMMANDS PET_REGROUP_OFF + 1 + +// can change the state of these buttons with a packet +#define PET_BUTTON_SIT 0 +#define PET_BUTTON_STOP 1 +#define PET_BUTTON_REGROUP 2 +#define PET_BUTTON_FOLLOW 3 +#define PET_BUTTON_GUARD 4 +#define PET_BUTTON_TAUNT 5 +#define PET_BUTTON_HOLD 6 +#define PET_BUTTON_GHOLD 7 +#define PET_BUTTON_FOCUS 8 +#define PET_BUTTON_SPELLHOLD 9 + typedef enum { //focus types focusSpellHaste = 1, focusSpellDuration, @@ -485,6 +534,8 @@ struct StatBonuses { uint8 TradeSkillMastery; // Allow number of tradeskills to exceed 200 skill. int16 NoBreakAESneak; // Percent value int16 FeignedCastOnChance; // Percent Value + bool PetCommands[PET_MAXCOMMANDS]; // SPA 267 + int FeignedMinionChance; // SPA 281 base1 = chance, just like normal FD }; typedef struct diff --git a/zone/groups.cpp b/zone/groups.cpp index d2342b4d9..6216de4ac 100644 --- a/zone/groups.cpp +++ b/zone/groups.cpp @@ -648,7 +648,7 @@ bool Group::DelMember(Mob* oldmember, bool ignoresender) } } - if (GetLeader() == nullptr) + if (!GetLeaderName()) { DisbandGroup(); return true; diff --git a/zone/mob.cpp b/zone/mob.cpp index d219a27af..ae036398a 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -344,8 +344,11 @@ Mob::Mob(const char* in_name, typeofpet = petNone; // default to not a pet petpower = 0; held = false; + gheld = false; nocast = false; focused = false; + pet_stop = false; + pet_regroup = false; _IsTempPet = false; pet_owner_client = false; pet_targetlock_id = 0; @@ -3093,6 +3096,26 @@ void Mob::Say_StringID(uint32 type, uint32 string_id, const char *message3, cons ); } +void Mob::SayTo_StringID(Client *to, uint32 string_id, const char *message3, const char *message4, const char *message5, const char *message6, const char *message7, const char *message8, const char *message9) +{ + if (!to) + return; + + auto string_id_str = std::to_string(string_id); + + to->Message_StringID(10, GENERIC_STRINGID_SAY, GetCleanName(), string_id_str.c_str(), message3, message4, message5, message6, message7, message8, message9); +} + +void Mob::SayTo_StringID(Client *to, uint32 type, uint32 string_id, const char *message3, const char *message4, const char *message5, const char *message6, const char *message7, const char *message8, const char *message9) +{ + if (!to) + return; + + auto string_id_str = std::to_string(string_id); + + to->Message_StringID(type, GENERIC_STRINGID_SAY, GetCleanName(), string_id_str.c_str(), message3, message4, message5, message6, message7, message8, message9); +} + void Mob::Shout(const char *format, ...) { char buf[1000]; diff --git a/zone/mob.h b/zone/mob.h index 4ba87f2d7..619b12e5a 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -536,7 +536,7 @@ public: inline uint32 GetLevelCon(uint8 iOtherLevel) const { return this ? GetLevelCon(GetLevel(), iOtherLevel) : CON_GRAY; } virtual void AddToHateList(Mob* other, uint32 hate = 0, int32 damage = 0, bool iYellForHelp = true, - bool bFrenzy = false, bool iBuffTic = false, uint16 spell_id = SPELL_UNKNOWN); + bool bFrenzy = false, bool iBuffTic = false, uint16 spell_id = SPELL_UNKNOWN, bool pet_comand = false); bool RemoveFromHateList(Mob* mob); void SetHateAmountOnEnt(Mob* other, int32 hate = 0, int32 damage = 0) { hate_list.SetHateAmountOnEnt(other,hate,damage);} void HalveAggro(Mob *other) { uint32 in_hate = GetHateAmount(other); SetHateAmountOnEnt(other, (in_hate > 1 ? in_hate / 2 : 1)); } @@ -645,6 +645,10 @@ public: const char *message6 = 0, const char *message7 = 0, const char *message8 = 0, const char *message9 = 0); void Say_StringID(uint32 type, uint32 string_id, const char *message3 = 0, const char *message4 = 0, const char *message5 = 0, const char *message6 = 0, const char *message7 = 0, const char *message8 = 0, const char *message9 = 0); + void SayTo_StringID(Client *to, uint32 string_id, const char *message3 = 0, const char *message4 = 0, const char *message5 = 0, + const char *message6 = 0, const char *message7 = 0, const char *message8 = 0, const char *message9 = 0); + void SayTo_StringID(Client *to, uint32 type, uint32 string_id, const char *message3 = 0, const char *message4 = 0, const char *message5 = 0, + const char *message6 = 0, const char *message7 = 0, const char *message8 = 0, const char *message9 = 0); void Shout(const char *format, ...); void Emote(const char *format, ...); void QuestJournalledSay(Client *QuestInitiator, const char *str); @@ -872,10 +876,16 @@ public: inline const eStandingPetOrder GetPetOrder() const { return pStandingPetOrder; } inline void SetHeld(bool nState) { held = nState; } inline const bool IsHeld() const { return held; } + inline void SetGHeld(bool nState) { gheld = nState; } + inline const bool IsGHeld() const { return gheld; } inline void SetNoCast(bool nState) { nocast = nState; } inline const bool IsNoCast() const { return nocast; } inline void SetFocused(bool nState) { focused = nState; } inline const bool IsFocused() const { return focused; } + inline void SetPetStop(bool nState) { pet_stop = nState; } + inline const bool IsPetStop() const { return pet_stop; } + inline void SetPetRegroup(bool nState) { pet_regroup = nState; } + inline const bool IsPetRegroup() const { return pet_regroup; } inline const bool IsRoamer() const { return roamer; } inline const int GetWanderType() const { return wandertype; } inline const bool IsRooted() const { return rooted || permarooted; } @@ -1184,8 +1194,11 @@ protected: uint32 pLastChange; bool held; + bool gheld; bool nocast; bool focused; + bool pet_stop; + bool pet_regroup; bool spawned; void CalcSpellBonuses(StatBonuses* newbon); virtual void CalcBonuses(); diff --git a/zone/mob_ai.cpp b/zone/mob_ai.cpp index ddd6d1c76..fd0ec5d81 100644 --- a/zone/mob_ai.cpp +++ b/zone/mob_ai.cpp @@ -935,7 +935,7 @@ void Mob::AI_Process() { bool engaged = IsEngaged(); bool doranged = false; - if (!zone->CanDoCombat()) { + if (!zone->CanDoCombat() || IsPetStop() || IsPetRegroup()) { engaged = false; } @@ -943,7 +943,7 @@ void Mob::AI_Process() { // if(RuleB(Combat, EnableFearPathing)){ if(currently_fleeing) { - if(IsRooted() || (IsBlind() && CombatRange(hate_list.GetClosestEntOnHateList(this)))) { + if((IsRooted() || (IsBlind() && CombatRange(hate_list.GetClosestEntOnHateList(this)))) && !IsPetStop() && !IsPetRegroup()) { //make sure everybody knows were not moving, for appearance sake if(IsMoving()) { @@ -1300,10 +1300,12 @@ void Mob::AI_Process() { } } else { - if (m_PlayerState & static_cast(PlayerState::Aggressive)) SendRemovePlayerState(PlayerState::Aggressive); + if (IsPetStop()) // pet stop won't be engaged, so we will always get here and we want the above branch to execute + return; + if(zone->CanDoCombat() && AI_feign_remember_timer->Check()) { // 6/14/06 // Improved Feign Death Memory @@ -1409,6 +1411,8 @@ void Mob::AI_Process() { break; } } + if (IsPetRegroup()) + return; } /* Entity has been assigned another entity to follow */ else if (GetFollowID()) diff --git a/zone/pets.cpp b/zone/pets.cpp index 39eeaeb5c..0c150f743 100644 --- a/zone/pets.cpp +++ b/zone/pets.cpp @@ -706,7 +706,7 @@ bool ZoneDatabase::GetBasePetItems(int32 equipmentset, uint32 *items) { // all of the result rows. Check if we have something in the slot // already. If no, add the item id to the equipment array. while (curset >= 0 && depth < 5) { - std::string query = StringFormat("SELECT nested_set FROM pets_equipmentset WHERE set_id = '%s'", curset); + std::string query = StringFormat("SELECT nested_set FROM pets_equipmentset WHERE set_id = '%d'", curset); auto results = QueryDatabase(query); if (!results.Success()) { return false; @@ -721,7 +721,7 @@ bool ZoneDatabase::GetBasePetItems(int32 equipmentset, uint32 *items) { auto row = results.begin(); nextset = atoi(row[0]); - query = StringFormat("SELECT slot, item_id FROM pets_equipmentset_entries WHERE set_id='%s'", curset); + query = StringFormat("SELECT slot, item_id FROM pets_equipmentset_entries WHERE set_id='%d'", curset); results = QueryDatabase(query); if (results.Success()) { for (row = results.begin(); row != results.end(); ++row) diff --git a/zone/pets.h b/zone/pets.h index 8d14d8a48..edb6dbe95 100644 --- a/zone/pets.h +++ b/zone/pets.h @@ -1,38 +1,6 @@ #ifndef PETS_H #define PETS_H -// Defines based on the RoF2 Client -#define PET_HEALTHREPORT 0 // 0x00 - /pet health or Pet Window -#define PET_LEADER 1 // 0x01 - /pet leader or Pet Window -#define PET_ATTACK 2 // 0x02 - /pet attack or Pet Window -#define PET_QATTACK 3 // 0x03 - /pet qattack or Pet Window -#define PET_FOLLOWME 4 // 0x04 - /pet follow or Pet Window -#define PET_GUARDHERE 5 // 0x05 - /pet guard or Pet Window -#define PET_SIT 6 // 0x06 - /pet sit or Pet Window -#define PET_SITDOWN 7 // 0x07 - /pet sit on -#define PET_STANDUP 8 // 0x08 - /pet sit off -#define PET_STOP 9 // 0x09 - /pet stop or Pet Window - Not implemented -#define PET_STOP_ON 10 // 0x0a - /pet stop on - Not implemented -#define PET_STOP_OFF 11 // 0x0b - /pet stop off - Not implemented -#define PET_TAUNT 12 // 0x0c - /pet taunt or Pet Window -#define PET_TAUNT_ON 13 // 0x0d - /pet taunt on -#define PET_TAUNT_OFF 14 // 0x0e - /pet taunt off -#define PET_HOLD 15 // 0x0f - /pet hold or Pet Window -#define PET_HOLD_ON 16 // 0x10 - /pet hold on -#define PET_HOLD_OFF 17 // 0x11 - /pet hold off -#define PET_SLUMBER 18 // 0x12 - What activates this? - define guessed -#define PET_SLUMBER_ON 19 // 0x13 - What activates this? - define guessed -#define PET_SLUMBER_OFF 20 // 0x14 - What activates this? - define guessed -#define PET_SPELLHOLD 21 // 0x15 - /pet no cast or /pet spellhold or Pet Window -#define PET_SPELLHOLD_ON 22 // 0x16 - /pet spellhold on -#define PET_SPELLHOLD_OFF 23 // 0x17 - /pet spellhold off -#define PET_FOCUS 24 // 0x18 - /pet focus or Pet Window -#define PET_FOCUS_ON 25 // 0x19 - /pet focus on -#define PET_FOCUS_OFF 26 // 0x1a - /pet focus off -#define PET_BACKOFF 28 // 0x1c - /pet back off -#define PET_GETLOST 29 // 0x1d - /pet get lost -#define PET_GUARDME 30 // 0x1e - Same as /pet follow, but different message in older clients - define not from client - class Mob; struct NPCType; diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index af96a7e2e..e04a136e6 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -1240,6 +1240,23 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove else { MakePet(spell_id, spell.teleport_zone); + // TODO: we need to sync the states for these clients ... + // Will fix buttons for now + if (IsClient()) { + auto c = CastToClient(); + if (c->ClientVersionBit() & EQEmu::versions::bit_UFAndLater) { + c->SetPetCommandState(PET_BUTTON_SIT, 0); + c->SetPetCommandState(PET_BUTTON_STOP, 0); + c->SetPetCommandState(PET_BUTTON_REGROUP, 0); + c->SetPetCommandState(PET_BUTTON_FOLLOW, 1); + c->SetPetCommandState(PET_BUTTON_GUARD, 0); + c->SetPetCommandState(PET_BUTTON_TAUNT, 1); + c->SetPetCommandState(PET_BUTTON_HOLD, 0); + c->SetPetCommandState(PET_BUTTON_GHOLD, 0); + c->SetPetCommandState(PET_BUTTON_FOCUS, 0); + c->SetPetCommandState(PET_BUTTON_SPELLHOLD, 0); + } + } } break; } @@ -6669,23 +6686,26 @@ void Mob::ResourceTap(int32 damage, uint16 spellid) for (int i = 0; i < EFFECT_COUNT; i++) { if (spells[spellid].effectid[i] == SE_ResourceTap) { - damage += (damage * spells[spellid].base[i]) / 100; - - if (spells[spellid].max[i] && (damage > spells[spellid].max[i])) - damage = spells[spellid].max[i]; - - if (spells[spellid].base2[i] == 0) { // HP Tap - if (damage > 0) - HealDamage(damage); - else - Damage(this, -damage, 0, EQEmu::skills::SkillEvocation, false); + damage = (damage * spells[spellid].base[i]) / 1000; + + if (damage) { + if (spells[spellid].max[i] && (damage > spells[spellid].max[i])) + damage = spells[spellid].max[i]; + + if (spells[spellid].base2[i] == 0) { // HP Tap + if (damage > 0) + HealDamage(damage); + else + Damage(this, -damage, 0, EQEmu::skills::SkillEvocation, false); + } + + if (spells[spellid].base2[i] == 1) // Mana Tap + SetMana(GetMana() + damage); + + if (spells[spellid].base2[i] == 2 && IsClient()) // Endurance Tap + CastToClient()->SetEndurance(CastToClient()->GetEndurance() + damage); + } - - if (spells[spellid].base2[i] == 1) // Mana Tap - SetMana(GetMana() + damage); - - if (spells[spellid].base2[i] == 2 && IsClient()) // Endurance Tap - CastToClient()->SetEndurance(CastToClient()->GetEndurance() + damage); } } } diff --git a/zone/spells.cpp b/zone/spells.cpp index 001c6895c..2be0a0bce 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -2140,6 +2140,27 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, ui spell_target->CalcSpellPowerDistanceMod(spell_id, dist2); } + //AE Duration spells were ignoring distance check from item clickies + if(ae_center != nullptr && ae_center != this) { + //casting a spell on somebody but ourself, make sure they are in range + float dist2 = DistanceSquared(m_Position, ae_center->GetPosition()); + float range2 = range * range; + float min_range2 = spells[spell_id].min_range * spells[spell_id].min_range; + if(dist2 > range2) { + //target is out of range. + Log(Logs::Detail, Logs::Spells, "Spell %d: Spell target is out of range (squared: %f > %f)", spell_id, dist2, range2); + Message_StringID(13, TARGET_OUT_OF_RANGE); + return(false); + } + else if (dist2 < min_range2){ + //target is too close range. + Log(Logs::Detail, Logs::Spells, "Spell %d: Spell target is too close (squared: %f < %f)", spell_id, dist2, min_range2); + Message_StringID(13, TARGET_TOO_CLOSE); + return(false); + } + + ae_center->CalcSpellPowerDistanceMod(spell_id, dist2); + } // // Switch #2 - execute the spell diff --git a/zone/string_ids.h b/zone/string_ids.h index f03d8f44d..65810d576 100644 --- a/zone/string_ids.h +++ b/zone/string_ids.h @@ -164,6 +164,12 @@ #define PVP_ON 552 //You are now player kill and follow the ways of Discord. #define GENERIC_STRINGID_SAY 554 //%1 says '%T2' #define CANNOT_WAKE 555 //%1 tells you, 'I am unable to wake %2, master.' +#define PET_HOLD_SET_ON 698 //The pet hold mode has been set to on. +#define PET_HOLD_SET_OFF 699 //The pet hold mode has been set to off. +#define PET_FOCUS_SET_ON 700 //The pet focus mode has been set to on. +#define PET_FOCUS_SET_OFF 701 //The pet focus mode has been set to off. +#define PET_SPELLHOLD_SET_ON 702 //The pet spellhold mode has been set to on. +#define PET_SPELLHOLD_SET_OFF 703 //The pet spellhold mode has been set to off. #define GUILD_NAME_IN_USE 711 //You cannot create a guild with that name, that guild already exists on this server. #define GM_GAINXP 1002 //[GM] You have gained %1 AXP and %2 EXP (%3). #define MALE_SLAYUNDEAD 1007 //%1's holy blade cleanses his target!(%2) @@ -288,6 +294,7 @@ #define CORPSEDRAG_BEGIN 4064 //You begin to drag %1. #define CORPSEDRAG_STOPALL 4065 //You stop dragging the corpses. #define CORPSEDRAG_STOP 4066 //You stop dragging the corpse. +#define SOS_KEEPS_HIDDEN 4086 //Your Shroud of Stealth keeps you hidden from watchful eyes.␣␣ #define TARGET_TOO_CLOSE 4602 //You are too close to your target. Get farther away. #define WHOALL_NO_RESULTS 5029 //There are no players in EverQuest that match those who filters. #define TELL_QUEUED_MESSAGE 5045 //You told %1 '%T2. %3' @@ -302,6 +309,7 @@ #define PET_ATTACKING 5501 //%1 tells you, 'Attacking %2 Master.' #define AVOID_STUNNING_BLOW 5753 //You avoid the stunning blow. #define FATAL_BOW_SHOT 5745 //%1 performs a FATAL BOW SHOT!! +#define SUSPECT_SEES_YOU 5746 //You suspect that this being can see you. #define MELEE_SILENCE 5806 //You *CANNOT* use this melee ability, you are suffering from amnesia! #define DISCIPLINE_REUSE_MSG 5807 //You can use the ability %1 again in %2 hour(s) %3 minute(s) %4 seconds. #define DISCIPLINE_REUSE_MSG2 5808 //You can use the ability %1 again in %2 minute(s) %3 seconds. @@ -320,6 +328,12 @@ #define SENTINEL_TRIG_YOU 6724 //You have triggered your sentinel. #define SENTINEL_TRIG_OTHER 6725 //%1 has triggered your sentinel. #define IDENTIFY_SPELL 6765 //Item Lore: %1. +#define PET_NOW_HOLDING 6834 //Now holding, Master. I will not start attacks until ordered. +#define PET_ON_GHOLD 6843 //Pet greater hold has been set to on. +#define PET_OFF_GHOLD 6846 //Pet greater hold has been set to off. +#define PET_GHOLD_ON_MSG 6847 //Now greater holding master. I will only attack something new if ordered. +#define PET_ON_REGROUPING 6854 //Now regrouping, master. +#define PET_OFF_REGROUPING 6855 //No longer regrouping, master. #define BUFF_NOT_BLOCKABLE 7608 //You cannot block this effect. #define LDON_DONT_KNOW_TRAPPED 7552 //You do not know if this object is trapped. #define LDON_HAVE_DISARMED 7553 //You have disarmed %1!